SpringBoot+Redis实现分布式WebSocket

什么是分布式WebSocket?

是指在分布式系统架构中实现WebSocket的通信机制,它允许在不同的服务器节点之间共享和同步WebSocket会话状态,从而实现跨多个服务器的实时消息传递。

在分布式环境中实现WebSocket的挑战主要包括以下几点:

  1. 会话共享:在分布式系统中,用户的WebSocket连接可能与不同的服务器建立,这就要求系统能够在不同服务器间共享WebSocket会话信息,以便消息能够被正确地传递到所有相关的客户端。
  2. 负载均衡:使用负载均衡可以提高系统的可用性和伸缩性。但是,当WebSocket请求在服务器之间负载均衡时,需要确保客户端可以与正确的服务器建立连接,并且能够接收到所有的消息。
  3. 故障转移:在出现服务器故障时,系统需要能够将WebSocket会话无缝迁移到其他健康的服务器上,以保证服务的连续性。
  4. 一致性:确保所有用户在任何时候看到的都是一致的消息状态,这对于实时通信非常重要。

为了解决这些挑战,可以采取以下几种策略:

  1. 使用消息代理:通过引入一个中心化的消息代理(如RabbitMQ、Redis Pub/Sub等),可以让所有的服务器都连接到这个消息代理。当一个服务器需要发送消息时,它将消息发送到消息代理,然后由消息代理负责将消息分发到所有连接的客户端。这样可以确保消息的一致性和可靠性。
  2. 共享会话存储:使用一个共享的会话存储(如数据库或内存数据网格)来保存WebSocket会话的状态。这样,即使客户端最初连接到的服务器发生故障,其他服务器也可以接管会话并继续处理消息。
  3. 基于路由的负载均衡:使用智能负载均衡器(如Nginx、HAProxy等),它们可以根据特定的路由规则(如会话ID或用户ID)将WebSocket连接定向到特定的服务器。
  4. 服务发现:在微服务架构中,可以使用服务发现机制来动态地找到负责特定会话的服务器,并将消息路由到那里。
  5. WebSocket代理:使用专门的WebSocket代理服务器,它可以在多个后端服务器之间代理WebSocket连接,并确保消息的传递和会话的同步。
  6. 应用层协议:设计应用层协议来处理分布式WebSocket的复杂性,例如通过引入心跳机制来检测连接的健康状况,并通过预定的协议来同步会话状态。

总的来说,在实践中,可能需要结合多种策略来构建一个健壮的分布式WebSocket解决方案,以满足不同场景下的需求。此外,还需要考虑安全性、性能和可扩展性等因素,以确保系统的稳定性和可靠性。

温故而知新:单点WebSocket实现

SpringBoot2.0集成WebSocket,实现后台向前端推送信息_springboot集成websocket-CSDN博客icon-default.png?t=N7T8https://zhengkai.blog.csdn.net/article/details/80275084

简单版本:在Java中使用Redis实现WebSocket

要在Java中使用Redis实现WebSocket,你需要使用一个支持WebSocket的Java Web框架,如Spring Boot,以及一个支持Redis的Java库,如Jedis。以下是一个简单的示例:

添加依赖项到你的pom.xml文件

<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency>
</dependencies>

创建一个WebSocket配置类

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.listener.ChannelTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.listener.adapter.MessageListenerAdapter;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
//by zhengkai.blog.csdn.net
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {@Overridepublic void registerStompEndpoints(StompEndpointRegistry registry) {registry.addEndpoint("/websocket").withSockJS();}@Overridepublic void configureMessageBroker(org.springframework.messaging.simp.config.MessageBrokerRegistry registry) {registry.enableSimpleBroker("/topic");registry.setApplicationDestinationPrefixes("/app");}@Beanpublic JedisConnectionFactory jedisConnectionFactory() {return new JedisConnectionFactory();}@Beanpublic RedisTemplate<String, Object> redisTemplate() {RedisTemplate<String, Object> template = new RedisTemplate<>();template.setConnectionFactory(jedisConnectionFactory());return template;}@Beanpublic MessageListenerAdapter messageListenerAdapter() {return new MessageListenerAdapter(new RedisMessageListener());}@Beanpublic RedisMessageListenerContainer redisMessageListenerContainer() {RedisMessageListenerContainer container = new RedisMessageListenerContainer();container.setConnectionFactory(jedisConnectionFactory());container.addMessageListener(messageListenerAdapter(), topic());return container;}@Beanpublic ChannelTopic topic() {return new ChannelTopic("websocket-topic");}
}

创建一个WebSocket消息监听器

import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.stereotype.Component;@Component
public class RedisMessageListener implements MessageListener {@Overridepublic void onMessage(Message message, byte[] pattern) {System.out.println("Received message: " + message);}
}

发送消息到WebSocket客户端

在你的控制器中,你可以使用SimpMessagingTemplate来发送消息到WebSocket客户端:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
public class WebSocketController {@Autowiredprivate SimpMessagingTemplate messagingTemplate;@GetMapping("/send")public String sendMessage() {messagingTemplate.convertAndSend("/topic/websocket-topic", "Hello from Redis!");return "Message sent!";}
}

正式版本:用SpringBoot+Redis实现分布式WebSocket

 

  1. 将消息(<用户id消息内容>)统一推送到一个消息队列(RedisKafka等)的的topic,然后每个应用节点都订阅这个topic,在接收到WebSocket消息后取出这个消息的“消息接收者的用户ID/用户名”,然后再比对自身是否存在相应用户的连接,如果存在则推送消息,否则丢弃接收到的这个消息(这个消息接收者所在的应用节点会处理)
  2. 在用户建立WebSocket连接后,使用Redis缓存记录用户的WebSocket建立在哪个应用节点上,然后同样使用消息队列将消息推送到接收者所在的应用节点上面(实现上比方案一要复杂,但是网络流量会更低)

 

 1. 定义一个WebSocket Channel枚举类

public enum WebSocketChannelEnum {//测试使用的简易点对点聊天CHAT("CHAT", "测试使用的简易点对点聊天", "/topic/reply");WebSocketChannelEnum(String code, String description, String subscribeUrl) {this.code = code;this.description = description;this.subscribeUrl = subscribeUrl;}/*** 唯一CODE*/private String code;/*** 描述*/private String description;/*** WebSocket客户端订阅的URL*/private String subscribeUrl;public String getCode() {return code;}public String getDescription() {return description;}public String getSubscribeUrl() {return subscribeUrl;}/*** 通过CODE查找枚举类*/public static WebSocketChannelEnum fromCode(String code){if(StringUtils.isNoneBlank(code)){for(WebSocketChannelEnum channelEnum : values()){if(channelEnum.code.equals(code)){return channelEnum;}}}return null;}}

2. 配置基于Redis的消息队列

需要注意的是,在大中型正式项目中并不推荐使用Redis实现的消息队列,因为经过测试它并不是特别可靠,所以应该考虑使用KafkarabbitMQ等专业的消息队列中间件

@Configuration
@ConditionalOnClass({JedisCluster.class})
public class RedisConfig {@Value("${spring.redis.timeout}")private String timeOut;@Value("${spring.redis.cluster.nodes}")private String nodes;@Value("${spring.redis.cluster.max-redirects}")private int maxRedirects;@Value("${spring.redis.jedis.pool.max-active}")private int maxActive;@Value("${spring.redis.jedis.pool.max-wait}")private int maxWait;@Value("${spring.redis.jedis.pool.max-idle}")private int maxIdle;@Value("${spring.redis.jedis.pool.min-idle}")private int minIdle;@Value("${spring.redis.message.topic-name}")private String topicName;@Beanpublic JedisPoolConfig jedisPoolConfig(){JedisPoolConfig config = new JedisPoolConfig();config.setMaxTotal(maxActive);config.setMaxIdle(maxIdle);config.setMinIdle(minIdle);config.setMaxWaitMillis(maxWait);return config;}@Beanpublic RedisClusterConfiguration redisClusterConfiguration(){RedisClusterConfiguration configuration = new RedisClusterConfiguration(Arrays.asList(nodes));configuration.setMaxRedirects(maxRedirects);return configuration;}/*** JedisConnectionFactory*/@Beanpublic JedisConnectionFactory jedisConnectionFactory(RedisClusterConfiguration configuration,JedisPoolConfig jedisPoolConfig){return new JedisConnectionFactory(configuration,jedisPoolConfig);}/*** 使用Jackson序列化对象*/@Beanpublic Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer(){Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<Object>(Object.class);ObjectMapper objectMapper = new ObjectMapper();objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);serializer.setObjectMapper(objectMapper);return serializer;}/*** RedisTemplate*/@Beanpublic RedisTemplate<String, Object> redisTemplate(JedisConnectionFactory factory, Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer){RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();redisTemplate.setConnectionFactory(factory);//字符串方式序列化KEYStringRedisSerializer stringRedisSerializer = new StringRedisSerializer();redisTemplate.setKeySerializer(stringRedisSerializer);redisTemplate.setHashKeySerializer(stringRedisSerializer);//JSON方式序列化VALUEredisTemplate.setValueSerializer(jackson2JsonRedisSerializer);redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);redisTemplate.afterPropertiesSet();return redisTemplate;}/*** 消息监听器*/@BeanMessageListenerAdapter messageListenerAdapter(MessageReceiver messageReceiver, Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer){//消息接收者以及对应的默认处理方法MessageListenerAdapter messageListenerAdapter = new MessageListenerAdapter(messageReceiver, "receiveMessage");//消息的反序列化方式messageListenerAdapter.setSerializer(jackson2JsonRedisSerializer);return messageListenerAdapter;}/*** message listener container*/@BeanRedisMessageListenerContainer container(RedisConnectionFactory connectionFactory, MessageListenerAdapter messageListenerAdapter){RedisMessageListenerContainer container = new RedisMessageListenerContainer();container.setConnectionFactory(connectionFactory);//添加消息监听器container.addMessageListener(messageListenerAdapter, new PatternTopic(topicName));return container;}}

这里使用的配置:

spring:...#redisredis:cluster:nodes: namenode22:6379,datanode23:6379,datanode24:6379max-redirects: 6timeout: 300000jedis:pool:max-active: 8max-wait: 100000max-idle: 8min-idle: 0#自定义的监听的TOPIC路径message:topic-name: topic-test

3. 定义一个Redis消息的处理者

@Component
public class MessageReceiver {private final Logger logger = LoggerFactory.getLogger(getClass());@Autowiredprivate SimpMessagingTemplate messagingTemplate;@Autowiredprivate SimpUserRegistry userRegistry;/*** 处理WebSocket消息*/public void receiveMessage(RedisWebsocketMsg redisWebsocketMsg) {logger.info(MessageFormat.format("Received Message: {0}", redisWebsocketMsg));//1. 取出用户名并判断是否连接到当前应用节点的WebSocketSimpUser simpUser = userRegistry.getUser(redisWebsocketMsg.getReceiver());if(simpUser != null && StringUtils.isNoneBlank(simpUser.getName())){//2. 获取WebSocket客户端的订阅地址WebSocketChannelEnum channelEnum = WebSocketChannelEnum.fromCode(redisWebsocketMsg.getChannelCode());if(channelEnum != null){//3. 给WebSocket客户端发送消息messagingTemplate.convertAndSendToUser(redisWebsocketMsg.getReceiver(), channelEnum.getSubscribeUrl(), redisWebsocketMsg.getContent());}}}
}

4. 在Controller中发送WebSocket消息

@Controller
@RequestMapping(("/wsTemplate"))
public class RedisMessageController {private final Logger logger = LoggerFactory.getLogger(getClass());@Value("${spring.redis.message.topic-name}")private String topicName;@Autowiredprivate SimpMessagingTemplate messagingTemplate;@Autowiredprivate SimpUserRegistry userRegistry;@Resource(name = "redisServiceImpl")private RedisService redisService;/*** 给指定用户发送WebSocket消息*/@PostMapping("/sendToUser")@ResponseBodypublic String chat(HttpServletRequest request) {//消息接收者String receiver = request.getParameter("receiver");//消息内容String msg = request.getParameter("msg");HttpSession session = SpringContextUtils.getSession();User loginUser = (User) session.getAttribute(Constants.SESSION_USER);HelloMessage resultData = new HelloMessage(MessageFormat.format("{0} say: {1}", loginUser.getUsername(), msg));this.sendToUser(loginUser.getUsername(), receiver, WebSocketChannelEnum.CHAT.getSubscribeUrl(), JsonUtils.toJson(resultData));return "ok";}/*** 给指定用户发送消息,并处理接收者不在线的情况* @param sender 消息发送者* @param receiver 消息接收者* @param destination 目的地* @param payload 消息正文*/private void sendToUser(String sender, String receiver, String destination, String payload){SimpUser simpUser = userRegistry.getUser(receiver);//如果接收者存在,则发送消息if(simpUser != null && StringUtils.isNoneBlank(simpUser.getName())){messagingTemplate.convertAndSendToUser(receiver, destination, payload);}//如果接收者在线,则说明接收者连接了集群的其他节点,需要通知接收者连接的那个节点发送消息else if(redisService.isSetMember(Constants.REDIS_WEBSOCKET_USER_SET, receiver)){RedisWebsocketMsg<String> redisWebsocketMsg = new RedisWebsocketMsg<>(receiver, WebSocketChannelEnum.CHAT.getCode(), payload);redisService.convertAndSend(topicName, redisWebsocketMsg);}//否则将消息存储到redis,等用户上线后主动拉取未读消息else{//存储消息的Redis列表名String listKey = Constants.REDIS_UNREAD_MSG_PREFIX + receiver + ":" + destination;logger.info(MessageFormat.format("消息接收者{0}还未建立WebSocket连接,{1}发送的消息【{2}】将被存储到Redis的【{3}】列表中", receiver, sender, payload, listKey));//存储消息到Redis中redisService.addToListRight(listKey, ExpireEnum.UNREAD_MSG, payload);}}/*** 拉取指定监听路径的未读的WebSocket消息* @param destination 指定监听路径* @return java.util.Map<java.lang.String,java.lang.Object>*/@PostMapping("/pullUnreadMessage")@ResponseBodypublic Map<String, Object> pullUnreadMessage(String destination){Map<String, Object> result = new HashMap<>();try {HttpSession session = SpringContextUtils.getSession();//当前登录用户User loginUser = (User) session.getAttribute(Constants.SESSION_USER);//存储消息的Redis列表名String listKey = Constants.REDIS_UNREAD_MSG_PREFIX + loginUser.getUsername() + ":" + destination;//从Redis中拉取所有未读消息List<Object> messageList = redisService.rangeList(listKey, 0, -1);result.put("code", "200");if(messageList !=null && messageList.size() > 0){//删除Redis中的这个未读消息列表redisService.delete(listKey);//将数据添加到返回集,供前台页面展示result.put("result", messageList);}}catch (Exception e){result.put("code", "500");result.put("msg", e.getMessage());}return result;}}

5. WebSocket相关配置

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer{@Autowiredprivate AuthHandshakeInterceptor authHandshakeInterceptor;@Autowiredprivate MyHandshakeHandler myHandshakeHandler;@Autowiredprivate MyChannelInterceptor myChannelInterceptor;@Overridepublic void registerStompEndpoints(StompEndpointRegistry registry) {registry.addEndpoint("/chat-websocket").addInterceptors(authHandshakeInterceptor).setHandshakeHandler(myHandshakeHandler).withSockJS();}@Overridepublic void configureMessageBroker(MessageBrokerRegistry registry) {//客户端需要把消息发送到/message/xxx地址registry.setApplicationDestinationPrefixes("/message");//服务端广播消息的路径前缀,客户端需要相应订阅/topic/yyy这个地址的消息registry.enableSimpleBroker("/topic");//给指定用户发送消息的路径前缀,默认值是/user/registry.setUserDestinationPrefix("/user/");}@Overridepublic void configureClientInboundChannel(ChannelRegistration registration) {registration.interceptors(myChannelInterceptor);}}

6. 示例页面

<head><meta content="text/html;charset=UTF-8"/><meta http-equiv="Content-Type" content="text/html; charset=utf-8"/><meta http-equiv="X-UA-Compatible" content="IE=edge"/><meta name="viewport" content="width=device-width, initial-scale=1"/><title>Chat With STOMP Message</title><script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/sockjs-client/1.1.4/sockjs.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/stomp.js/2.3.3/stomp.min.js"></script><script th:src="@{/layui/layui.js}"></script><script th:src="@{/layui/lay/modules/layer.js}"></script><link th:href="@{/layui/css/layui.css}" rel="stylesheet"><link th:href="@{/layui/css/modules/layer/default/layer.css}" rel="stylesheet"><link th:href="@{/css/style.css}" rel="stylesheet"><style type="text/css">#connect-container {margin: 0 auto;width: 400px;}#connect-container div {padding: 5px;margin: 0 7px 10px 0;}.message input {padding: 5px;margin: 0 7px 10px 0;}.layui-btn {display: inline-block;}</style><script type="text/javascript">var stompClient = null;$(function () {var target = $("#target");if (window.location.protocol === 'http:') {target.val('http://' + window.location.host + target.val());} else {target.val('https://' + window.location.host + target.val());}});function setConnected(connected) {var connect = $("#connect");var disconnect = $("#disconnect");var echo = $("#echo");if (connected) {connect.addClass("layui-btn-disabled");disconnect.removeClass("layui-btn-disabled");echo.removeClass("layui-btn-disabled");} else {connect.removeClass("layui-btn-disabled");disconnect.addClass("layui-btn-disabled");echo.addClass("layui-btn-disabled");}connect.attr("disabled", connected);disconnect.attr("disabled", !connected);echo.attr("disabled", !connected);}//连接function connect() {var target = $("#target").val();var ws = new SockJS(target);stompClient = Stomp.over(ws);stompClient.connect({}, function () {setConnected(true);log('Info: STOMP connection opened.');//连接成功后,主动拉取未读消息pullUnreadMessage("/topic/reply");//订阅服务端的/topic/reply地址stompClient.subscribe("/user/topic/reply", function (response) {log(JSON.parse(response.body).content);})},function () {//断开处理setConnected(false);log('Info: STOMP connection closed.');});}//断开连接function disconnect() {if (stompClient != null) {stompClient.disconnect();stompClient = null;}setConnected(false);log('Info: STOMP connection closed.');}//向指定用户发送消息function sendMessage() {if (stompClient != null) {var receiver = $("#receiver").val();var msg = $("#message").val();log('Sent: ' + JSON.stringify({'receiver': receiver, 'msg':msg}));$.ajax({url: "/wsTemplate/sendToUser",type: "POST",dataType: "json",async: true,data: {"receiver": receiver,"msg": msg},success: function (data) {}});} else {layer.msg('STOMP connection not established, please connect.', {offset: 'auto',icon: 2});}}//从服务器拉取未读消息function pullUnreadMessage(destination) {$.ajax({url: "/wsTemplate/pullUnreadMessage",type: "POST",dataType: "json",async: true,data: {"destination": destination},success: function (data) {if (data.result != null) {$.each(data.result, function (i, item) {log(JSON.parse(item).content);})} else if (data.code !=null && data.code == "500") {layer.msg(data.msg, {offset: 'auto',icon: 2});}}});}//日志输出function log(message) {console.debug(message);}</script>
</head>
<body><noscript><h2 style="color: #ff0000">Seems your browser doesn't support Javascript! Websockets rely on Javascript beingenabled. Please enableJavascript and reload this page!</h2></noscript><div><div id="connect-container" class="layui-elem-field"><legend>Chat With STOMP Message</legend><div><input id="target" type="text" class="layui-input" size="40" style="width: 350px" value="/chat-websocket"/></div><div><button id="connect" class="layui-btn layui-btn-normal" onclick="connect();">Connect</button><button id="disconnect" class="layui-btn layui-btn-normal layui-btn-disabled" disabled="disabled"onclick="disconnect();">Disconnect</button></div><div class="message"><input id="receiver" type="text" class="layui-input" size="40" style="width: 350px" placeholder="接收者姓名" value=""/><input id="message" type="text" class="layui-input" size="40" style="width: 350px" placeholder="消息内容" value=""/></div><div><button id="echo" class="layui-btn layui-btn-normal layui-btn-disabled" disabled="disabled"onclick="sendMessage();">Send Message</button></div></div></div>
</body>
</html>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/762773.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

头条网盘拉新项目该怎么选择授权

作为十二月份首发上线的项目——头条网盘拉新。一经上线就受到很多想要做这行业人的关注&#xff0c;光是佣金已经是业内比较高的了&#xff01;每拉新一位新用户就可以获取到价格为9元一单的佣金&#xff0c;拉失活用户也可以获取价格为4元的佣金&#xff0c;推广方式和其他网…

基于python+vue云上水果超市的设计与实现flask-django-php-nodejs

本论文的主要内容包括&#xff1a; 第一&#xff0c;研究分析当下主流的web技术&#xff0c;结合超市日常管理方式&#xff0c;进行云上水果超市的数据库设计&#xff0c;设计云上水果超市功能&#xff0c;并对每个模块进行说明。 第二&#xff0c;陈列说明该系统实现所采用的架…

财报解读:“高端化”告一段落,华住开始“全球化”?

2023年旅游业快速复苏&#xff0c;全球酒店业直接受益&#xff0c;总体运营指标大放异彩&#xff0c;多数酒店企业都实现了营收上的明显增长&#xff0c;身为国内龙头的华住也不例外。 3月20日晚&#xff0c;华住集团发布2023年四季度及全年财报。整体实现扭亏为盈&#xff0c;…

飞跃前端瓶颈:技术进阶指南精华篇

引言&#xff1a; 在互联网的快车道上&#xff0c;前端技术日新月异。对于前端工程师而言&#xff0c;技术水平达到一定高度后&#xff0c;往往会遭遇成长的天花板。本文将探讨如何识别并突破这些技术瓶颈&#xff0c;分享实用的进阶策略和实践案例。 一、技术等级概览&#xf…

WP免费主题2个分享给需要的人

免费wordpress主题 粉色高端大气的免费wordpress主题&#xff0c;用免费的主题也可以搭建wordpress网站。 https://www.wpniu.com/themes/12.html 免费WP模板 绿色清爽的wordpress建站模板&#xff0c;用免费的WP模板也可以搭建出精美网站。 https://www.wpniu.com/themes/…

图解 LFU 缓存淘汰算法以及在 Redis 中的应用(附带个人完整代码实现)

文章目录 LFU 算法理论介绍算法实现数据结构查询操作插入/更新操作 Redis 缓存淘汰算法缓存污染难题Redis LFU缓存淘汰策略 本篇博客的主要内容&#xff1a; 以图解的方式&#xff0c;介绍 LFU 算法的一种实现&#xff1b;介绍 LFU 算法在 Redis 中的应用。 LFU 算法 理论介…

人工智能驱动客服行业变革:迈向智能化、自动化与数据驱动的新纪元

在科技浪潮的推动下&#xff0c;人工智能&#xff08;AI&#xff09;已经逐渐渗透到各个行业之中&#xff0c;其中客服行业正经历着一场前所未有的变革。AI技术的引入&#xff0c;为客服行业注入了智能化、自动化和数据驱动的新动力&#xff0c;引领着客服行业迈向一个崭新的时…

数据仓库的数据处理架构Lambda和Kappa

1.数据仓库 数据仓库(Data Warehouse),简写DW。顾名思义,数据仓库是一个很大的数据存储集合,为企业分析性报告和决策支持而创建,是对多元业务数据的筛选与整合,具备一定的BI能力,主要用于企业的数据分析、数据挖掘、数据报表等方向,指导业务流程改进、监视时间、成本、…

[Linux开发工具]——make/Makefile的使用

Linux项目自动化构建工具——make/Makefile 前言&#xff1a;一、背景二、认识make和makefile2.1 创建Makefile文件2.2 创建test.c文件&#xff0c;并打开Makefile2.3 我们想要test.c生成test文件2.4 编译2.5 清理可执行文件 三、理解依赖关系和依赖方法3.1 依赖关系3.2 依赖方…

发展的挺快的Rust

C 可能在将来会逐步的退出历史舞台 Rust 在linux 上出现的频次越来越多了 新的语言和重构带来了更方便快捷的体验 好玩的命令集合 https://github.com/ibraheemdev/modern-unix.git 这速度&#xff0c;这花活儿

07、面向对象进阶

面向对象进阶 文章目录 面向对象进阶static关键字继承多态包final抽象类与抽象方法接口拓展 内部类成员内部类静态内部类局部内部类匿名内部类 static关键字 static表示静态&#xff0c;是Java中的一个修饰符&#xff0c;可以修饰成员方法&#xff0c;成员变量 静态变量是随着…

C++ Qt开发:QProcess进程管理模块

Qt 是一个跨平台C图形界面开发库&#xff0c;利用Qt可以快速开发跨平台窗体应用程序&#xff0c;在Qt中我们可以通过拖拽的方式将不同组件放到指定的位置&#xff0c;实现图形化开发极大的方便了开发效率&#xff0c;本章将重点介绍如何运用QProcess组件实现针对进程的控制管理…

mysqly索引(explain 执行计划)

关键词 执行计划 EXPLAIN 语句查看mysql 优化后的语句 show warnings;EXPLAIN 执行后&#xff0c;各列的含义 要点&#xff1a; select_type 如何查询 表type 如何查询 行key 如何使用 索引key_len 索引 使用多少rows 行 预计使用多少extra 表 的额外信息 1.id id列的编…

Spring之@Qualifier注解

场景重现 当我们注入的依赖存在多个候选者,我们得使用一些方法来筛选出唯一候选者,否则就会抛出异常 demo演示 创建接口Car,以及两个实现其接口的bean public interface Car { }Component public class RedCar implements Car { }Component public class WhiteCar implemen…

荒野大嫖客2找不到emp.dll解决办法

首先我参考了几篇文章尝试来解决这个问题&#xff0c; s霍格沃兹emp.dll文件丢失要怎么处理&#xff1f;快速修复emp.dll的方法-CSDN博客 使用电脑自带的修复工具&#xff0c;直接按键盘的winr&#xff0c;然后输入cmd&#xff0c;打开小黑板&#xff0c;我们再在小黑板上输入…

MQTT 简介

MQTT 简介 MQTT 是非常简单的协议&#xff0c;最初由 IBM 的两位工程师 Andy Stanford-Clark 以及 Arlen Nipper 在 1999 年为监控输油管道设计的。它被设计的场景就是有限的带宽、轻量级以及很小的耗电量&#xff0c;在那个时候&#xff0c;卫星宽带就是那么小&#xff0c;且…

【理解机器学习算法】之Clustering算法(Agglomerative Clustering)

聚合聚类(Agglomerative Clustering)是一种层次聚类算法&#xff0c;通过逐步合并或“聚集”它们来构建嵌套聚类。这种方法采用自底向上的方式构建聚类层次&#xff1a;它从将每个数据点作为单个聚类开始&#xff0c;然后迭代合并最接近的聚类对&#xff0c;直到所有数据点合并…

【机器学习】一文搞懂算法模型之:Transformer

Transformer 1、引言2、Transformer2.1 定义2.2 原理2.3 算法公式2.3.1 自注意力机制2.3.1 多头自注意力机制2.3.1 位置编码 2.4 代码示例 3、总结 1、引言 小屌丝&#xff1a;鱼哥&#xff0c; 你说transformer是个啥&#xff1f; 小鱼&#xff1a;嗯… 啊… 嗯…就是… 小屌…

【Java Web基础】一些网页设计基础(四)

文章目录 1. 做Tab切换2. 下面的内容展示——Card样式3. 采供分类&#xff0c;分类用面包屑导航做4. 出名企业展示&#xff0c;就是普通的图片5. 用热门商品类似的panel做一个农博会展览 1. 做Tab切换 使用BootStrap提供的样式&#xff1a; <ul class"nav nav-tabs&q…

【Redis】缓存穿透

问题发生背景&#xff1a;客户端请求的数据再缓存中和数据库中都不存在。 导致的问题&#xff1a;缓存永远不会生效&#xff0c;这些请求都会去请求数据库—导致数据库压力增大。 解决方案&#xff1a; 1.缓存空对象 在Redis中缓存空对象&#xff0c;告诉客户端数据库中没有该值…