Redis实战——Redis的pub/Sub(订阅与发布)在java中的实现
借鉴:https://blog.csdn.net/canot/article/details/51938955
1. 什么是 pub/sub
Pub/Sub 功能(means Publish, Subscribe)即发布及订阅功能。基于事件的系统中,Pub/Sub 是目前广泛使用的通信模型,它采用事件作为基本的通信机制,提供大规模系统所要求的松散耦合的交互模式:订阅者 (如客户端) 以事件订阅的方式表达出它有兴趣接收的一个事件或一类事件;发布者 (如服务器) 可将订阅者感兴趣的事件随时通知相关订阅者。熟悉设计模式的朋友应该了解这与 23 种设计模式中的观察者模式极为相似。
同样,Redis 的 pub/sub 是一种消息通信模式,主要的目的是解除消息发布者和消息订阅者之间的耦合,Redis 作为一个 pub/sub 的 server, 在订阅者和发布者之间起到了消息路由的功能。
2.Redis pub/sub 的实现
Redis 通过 publish 和 subscribe 命令实现订阅和发布的功能。订阅者可以通过 subscribe 向 redis server 订阅自己感兴趣的消息类型。redis 将信息类型称为通道 (channel)。当发布者通过 publish 命令向 redis server 发送特定类型的信息时,订阅该消息类型的全部订阅者都会收到此消息。
客户端 1 订阅 CCTV1:
127.0.0.1:6379> subscribe CCTV1
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "CCTV1"
3)(integer) 1
客户端 2 订阅 CCTV1 和 CCTV2:
127.0.0.1:6379> subscribe CCTV1 CCTV2
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "CCTV1"
3)(integer) 1
1) "subscribe"
2) "CCTV2"
3)(integer) 2
此时这两个客户端分别监听这指定的频道。现在另一个客户端向服务器推送了关于这两个频道的信息。
127.0.0.1:6379> publish CCTV1 "cctv1 is good"
(integer) 2
// 返回 2 表示两个客户端接收了次消息。被接收到消息的客户端如下所示。
1) "message"
2) "CCTV1"
3) "cctv1 is good"
----
1) "message"
2) "CCTV1"
3) "cctv1 is good"
如上的订阅 / 发布也称订阅发布到频道 (使用 publish 与 subscribe 命令),此外还有订阅发布到模式 (使用 psubscribe 来订阅一个模式)
订阅 CCTV 的全部频道
127.0.0.1:6379> psubscribe CCTV*
Reading messages... (press Ctrl-C to quit)
1) "psubscribe"
2) "CCTV*"
3)(integer) 1
当依然先如上推送一个 CCTV1 的消息时,该客户端正常接收。
3.Pub/Sub 在 java 中的实现
导入 Redis 驱动:
<dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.9.0</version> </dependency>
Redis 驱动包提供了一个抽象类:JedisPubSub…继承这个类就完成了对客户端对订阅的监听。示例代码:
/** * redis 发布订阅消息监听器 * @ClassName: RedisMsgPubSubListener * @Description: TODO * @author OnlyMate * @Date 2018 年 8 月 22 日 上午 10:05:35 * */ public class RedisMsgPubSubListener extends JedisPubSub { private Logger logger = LoggerFactory.getLogger(RedisMsgPubSubListener.class);@Override </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> unsubscribe() { </span><span style="color: rgba(0, 0, 255, 1)">super</span><span style="color: rgba(0, 0, 0, 1)">.unsubscribe(); } @Override </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> unsubscribe(String... channels) { </span><span style="color: rgba(0, 0, 255, 1)">super</span><span style="color: rgba(0, 0, 0, 1)">.unsubscribe(channels); } @Override </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> subscribe(String... channels) { </span><span style="color: rgba(0, 0, 255, 1)">super</span><span style="color: rgba(0, 0, 0, 1)">.subscribe(channels); } @Override </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> psubscribe(String... patterns) { </span><span style="color: rgba(0, 0, 255, 1)">super</span><span style="color: rgba(0, 0, 0, 1)">.psubscribe(patterns); } @Override </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> punsubscribe() { </span><span style="color: rgba(0, 0, 255, 1)">super</span><span style="color: rgba(0, 0, 0, 1)">.punsubscribe(); } @Override </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> punsubscribe(String... patterns) { </span><span style="color: rgba(0, 0, 255, 1)">super</span><span style="color: rgba(0, 0, 0, 1)">.punsubscribe(patterns); } @Override </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> onMessage(String channel, String message) { logger.info(</span>"onMessage: channel[{}], message[{}]"<span style="color: rgba(0, 0, 0, 1)">,channel, message); } @Override </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> onPMessage(String pattern, String channel, String message) { logger.info(</span>"onPMessage: pattern[{}], channel[{}], message[{}]"<span style="color: rgba(0, 0, 0, 1)">, pattern, channel, message); } @Override </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span> onSubscribe(String channel, <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> subscribedChannels) { logger.info(</span>"onSubscribe: channel[{}], subscribedChannels[{}]"<span style="color: rgba(0, 0, 0, 1)">, channel, subscribedChannels); } @Override </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span> onPUnsubscribe(String pattern, <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> subscribedChannels) { logger.info(</span>"onPUnsubscribe: pattern[{}], subscribedChannels[{}]"<span style="color: rgba(0, 0, 0, 1)">, pattern, subscribedChannels); } @Override </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span> onPSubscribe(String pattern, <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> subscribedChannels) { logger.info(</span>"onPSubscribe: pattern[{}], subscribedChannels[{}]"<span style="color: rgba(0, 0, 0, 1)">, pattern, subscribedChannels); } @Override </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span> onUnsubscribe(String channel, <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> subscribedChannels) { logger.info(</span>"channel:{} is been subscribed:{}"<span style="color: rgba(0, 0, 0, 1)">, channel, subscribedChannels); }
}
如上所示, 抽象类中存在的方法。分别表示
- 监听到订阅模式接受到消息时的回调 (onPMessage)
- 监听到订阅频道接受到消息时的回调 (onMessage)
- 订阅频道时的回调 (onSubscribe)
- 取消订阅频道时的回调 (onUnsubscribe)
- 订阅频道模式时的回调 (onPSubscribe)
- 取消订阅模式时的回调 (onPUnsubscribe)
运行我们刚刚编写的类:
订阅者
/** * 订阅者 * @ClassName: RedisSubTest * @Description: TODO * @author OnlyMate * @Date 2018 年 8 月 23 日 下午 2:59:42 * */ public class RedisSubTest { @Test public void subjava() { System.out.println("订阅者"); Jedis jr = null; try { jr = new Jedis("127.0.0.1", 6379, 0);// redis 服务地址和端口号 RedisMsgPubSubListener sp = new RedisMsgPubSubListener(); // jr 客户端配置监听两个 channel jr.subscribe(sp, "news.share", "news.blog");} catch (Exception e) {e.printStackTrace(); } finally { if (jr != null) {jr.disconnect(); } } } }
发布者
/** * 发布者 * @ClassName: RedisPubTest * @Description: TODO * @author OnlyMate * @Date 2018 年 8 月 23 日 下午 2:59:25 * */ public class RedisPubTest { @Test public void pubjava() { System.out.println("发布者"); Jedis jr = null; try { jr = new Jedis("127.0.0.1", 6379, 0);// redis 服务地址和端口号 // jr 客户端配置监听两个 channel jr.publish( "news.share", "新闻分享"); jr.publish( "news.blog", "新闻博客");} catch (Exception e) {e.printStackTrace(); } finally { if (jr != null) {jr.disconnect(); } } } }
从代码中我们不难看出,我们声明的一个 redis 链接在设置监听后就可以执行一些操作,例如发布消息,订阅消息等。。。
当运行上述代码后会在控制台输出:
此时当在有客户端向 new.share 或者 new.blog 通道 publish 消息时,onMessage 方法即可被相应。(jedis.publish(channel, message))。
4.Pub/Sub 在 Spring 中的实践
导入依赖 jar
<dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.9.0</version> </dependency> <dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-redis</artifactId> <version>2.0.8.RELEASE</version> </dependency>
核心消息监听器
/** * redis 发布订阅消息监听器 * @ClassName: RedisMsgPubSubListener * @Description: TODO * @author OnlyMate * @Date 2018 年 8 月 22 日 上午 10:05:35 * */ public class RedisMsgPubSubListener implements MessageListener { private Logger logger = LoggerFactory.getLogger(RedisMsgPubSubListener.class);@Override </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span> onMessage( <span style="color: rgba(0, 0, 255, 1)">final</span> Message message, <span style="color: rgba(0, 0, 255, 1)">final</span> <span style="color: rgba(0, 0, 255, 1)">byte</span><span style="color: rgba(0, 0, 0, 1)">[] pattern ) { RedisSerializer</span><?> serializer =<span style="color: rgba(0, 0, 0, 1)"> redisTemplate.getValueSerializer(); </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> message.getBody()是Redis的值,需要用redis的valueSerializer反序列化</span> logger.info("Message receive-->pattern:{},message: {},{}", <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> String(pattern), serializer.deserialize(message.getBody()), redisTemplate.getStringSerializer().deserialize(message.getChannel())); logger.info(message.toString()); JSONObject json </span>=<span style="color: rgba(0, 0, 0, 1)"> JSONObject.parseObject(serializer.deserialize(message.getBody()).toString()); String cutomerId </span>= json.getString("cutomerId"<span style="color: rgba(0, 0, 0, 1)">); </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">可以与WebSocket结合使用,解决分布式服务中,共享Session</span> <span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)">(StringUtils.isNotEmpty(cutomerId)) { logger.info(</span>"cutomerId: {},消息:{}"<span style="color: rgba(0, 0, 0, 1)">, cutomerId, message.toString()); }</span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> { logger.info(</span>"cutomerId 为空,无法推送给对应的客户端,消息:{}"<span style="color: rgba(0, 0, 0, 1)">, message.toString()); } }
}
现在我们在获取 RedisTemplate, 并给 WEB_SOCKET:LOTTERY 这个 channel publish 数据。
/** * 发布者 * @ClassName: RedisMsgPubClient * @Description: TODO * @author OnlyMate * @Date 2018 年 8 月 23 日 下午 3:59:33 * */ @Controller @RequestMapping(value="/redisMsgPubClientBySpring") public class RedisMsgPubClient { private Logger logger = LoggerFactory.getLogger(RedisMsgPubClient.class);@Autowired </span><span style="color: rgba(0, 0, 255, 1)">private</span> RedisTemplate<Object,Object><span style="color: rgba(0, 0, 0, 1)"> redisTemplate; @RequestMapping @ResponseBody </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> String pubMsg(HttpServletRequest request, HttpServletResponse response) { String cutomerId </span>= request.getParameter("cutomerId"<span style="color: rgba(0, 0, 0, 1)">).toString(); String msg </span>= request.getParameter("msg"<span style="color: rgba(0, 0, 0, 1)">).toString(); logger.info(</span>"发布消息:{}", request.getParameter("msg"<span style="color: rgba(0, 0, 0, 1)">).toString()); JSONObject json </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> JSONObject(); json.put(</span>"cutomerId"<span style="color: rgba(0, 0, 0, 1)">, cutomerId); json.put(</span>"msg"<span style="color: rgba(0, 0, 0, 1)">, msg); redisTemplate.convertAndSend(</span>"WEB_SOCKET:LOTTERY"<span style="color: rgba(0, 0, 0, 1)">, json); </span><span style="color: rgba(0, 0, 255, 1)">return</span> "成功"<span style="color: rgba(0, 0, 0, 1)">; }
}
最后一步 reids 的配置
<?xml version="1.0" encoding="UTF-8" ?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:cache="http://www.springframework.org/schema/cache" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd" default-autowire="byName"><span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">description</span><span style="color: rgba(0, 0, 255, 1)">></span>redis 相关类 Spring 托管<span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">description</span><span style="color: rgba(0, 0, 255, 1)">></span> <span style="color: rgba(0, 128, 0, 1)"><!--</span><span style="color: rgba(0, 128, 0, 1)"> 开启缓存 </span><span style="color: rgba(0, 128, 0, 1)">--></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">cache:annotation-driven </span><span style="color: rgba(0, 0, 255, 1)">/></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">bean </span><span style="color: rgba(255, 0, 0, 1)">name</span><span style="color: rgba(0, 0, 255, 1)">="springCacheAnnotationParser"</span><span style="color: rgba(255, 0, 0, 1)"> class</span><span style="color: rgba(0, 0, 255, 1)">="org.springframework.cache.annotation.SpringCacheAnnotationParser"</span><span style="color: rgba(0, 0, 255, 1)">></</span><span style="color: rgba(128, 0, 0, 1)">bean</span><span style="color: rgba(0, 0, 255, 1)">></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">bean </span><span style="color: rgba(255, 0, 0, 1)">name</span><span style="color: rgba(0, 0, 255, 1)">="annotationCacheOperationSource"</span><span style="color: rgba(255, 0, 0, 1)"> class</span><span style="color: rgba(0, 0, 255, 1)">="org.springframework.cache.annotation.AnnotationCacheOperationSource"</span><span style="color: rgba(0, 0, 255, 1)">></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">constructor-arg</span><span style="color: rgba(0, 0, 255, 1)">></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">array</span><span style="color: rgba(0, 0, 255, 1)">></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">ref </span><span style="color: rgba(255, 0, 0, 1)">bean</span><span style="color: rgba(0, 0, 255, 1)">="springCacheAnnotationParser"</span><span style="color: rgba(0, 0, 255, 1)">/></span> <span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">array</span><span style="color: rgba(0, 0, 255, 1)">></span> <span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">constructor-arg</span><span style="color: rgba(0, 0, 255, 1)">></span> <span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">bean</span><span style="color: rgba(0, 0, 255, 1)">></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">bean </span><span style="color: rgba(255, 0, 0, 1)">name</span><span style="color: rgba(0, 0, 255, 1)">="cacheInterceptor"</span><span style="color: rgba(255, 0, 0, 1)"> class</span><span style="color: rgba(0, 0, 255, 1)">="org.springframework.cache.interceptor.CacheInterceptor"</span><span style="color: rgba(0, 0, 255, 1)">></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">property </span><span style="color: rgba(255, 0, 0, 1)">name</span><span style="color: rgba(0, 0, 255, 1)">="cacheOperationSources"</span><span style="color: rgba(255, 0, 0, 1)"> ref</span><span style="color: rgba(0, 0, 255, 1)">="annotationCacheOperationSource"</span> <span style="color: rgba(0, 0, 255, 1)">/></span> <span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">bean</span><span style="color: rgba(0, 0, 255, 1)">></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">bean </span><span style="color: rgba(255, 0, 0, 1)">class</span><span style="color: rgba(0, 0, 255, 1)">="org.springframework.cache.interceptor.BeanFactoryCacheOperationSourceAdvisor"</span><span style="color: rgba(0, 0, 255, 1)">></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">property </span><span style="color: rgba(255, 0, 0, 1)">name</span><span style="color: rgba(0, 0, 255, 1)">="cacheOperationSource"</span><span style="color: rgba(255, 0, 0, 1)"> ref</span><span style="color: rgba(0, 0, 255, 1)">="annotationCacheOperationSource"</span> <span style="color: rgba(0, 0, 255, 1)">/></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">property </span><span style="color: rgba(255, 0, 0, 1)">name</span><span style="color: rgba(0, 0, 255, 1)">="advice"</span><span style="color: rgba(255, 0, 0, 1)"> ref</span><span style="color: rgba(0, 0, 255, 1)">="cacheInterceptor"</span> <span style="color: rgba(0, 0, 255, 1)">/></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">property </span><span style="color: rgba(255, 0, 0, 1)">name</span><span style="color: rgba(0, 0, 255, 1)">="order"</span><span style="color: rgba(255, 0, 0, 1)"> value</span><span style="color: rgba(0, 0, 255, 1)">="2147483647"</span> <span style="color: rgba(0, 0, 255, 1)">/></span> <span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">bean</span><span style="color: rgba(0, 0, 255, 1)">></span> <span style="color: rgba(0, 128, 0, 1)"><!--</span><span style="color: rgba(0, 128, 0, 1)">载入 redis 配置文件</span><span style="color: rgba(0, 128, 0, 1)">--></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">context:property-placeholder </span><span style="color: rgba(255, 0, 0, 1)">location</span><span style="color: rgba(0, 0, 255, 1)">="classpath:redis.properties"</span><span style="color: rgba(255, 0, 0, 1)"> ignore-unresolvable</span><span style="color: rgba(0, 0, 255, 1)">="true"</span><span style="color: rgba(0, 0, 255, 1)">/></span> <span style="color: rgba(0, 128, 0, 1)"><!--</span><span style="color: rgba(0, 128, 0, 1)"> 配置JedisConnectionFactory </span><span style="color: rgba(0, 128, 0, 1)">--></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">bean </span><span style="color: rgba(255, 0, 0, 1)">id</span><span style="color: rgba(0, 0, 255, 1)">="jedisConnectionFactory"</span><span style="color: rgba(255, 0, 0, 1)"> class</span><span style="color: rgba(0, 0, 255, 1)">="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"</span><span style="color: rgba(0, 0, 255, 1)">></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">property </span><span style="color: rgba(255, 0, 0, 1)">name</span><span style="color: rgba(0, 0, 255, 1)">="hostName"</span><span style="color: rgba(255, 0, 0, 1)"> value</span><span style="color: rgba(0, 0, 255, 1)">="${redis.host}"</span><span style="color: rgba(0, 0, 255, 1)">/></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">property </span><span style="color: rgba(255, 0, 0, 1)">name</span><span style="color: rgba(0, 0, 255, 1)">="port"</span><span style="color: rgba(255, 0, 0, 1)"> value</span><span style="color: rgba(0, 0, 255, 1)">="${redis.port}"</span><span style="color: rgba(0, 0, 255, 1)">/></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">property </span><span style="color: rgba(255, 0, 0, 1)">name</span><span style="color: rgba(0, 0, 255, 1)">="password"</span><span style="color: rgba(255, 0, 0, 1)"> value</span><span style="color: rgba(0, 0, 255, 1)">="${redis.pass}"</span><span style="color: rgba(0, 0, 255, 1)">/></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">property </span><span style="color: rgba(255, 0, 0, 1)">name</span><span style="color: rgba(0, 0, 255, 1)">="database"</span><span style="color: rgba(255, 0, 0, 1)"> value</span><span style="color: rgba(0, 0, 255, 1)">="${redis.dbIndex}"</span><span style="color: rgba(0, 0, 255, 1)">/></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">property </span><span style="color: rgba(255, 0, 0, 1)">name</span><span style="color: rgba(0, 0, 255, 1)">="poolConfig"</span><span style="color: rgba(255, 0, 0, 1)"> ref</span><span style="color: rgba(0, 0, 255, 1)">="jedisPoolConfig"</span><span style="color: rgba(0, 0, 255, 1)">/></span> <span style="color: rgba(0, 128, 0, 1)"><!--</span><span style="color: rgba(0, 128, 0, 1)"> <constructor-arg name="sentinelConfig" ref="redisSentinelConfiguration" /> </span><span style="color: rgba(0, 128, 0, 1)">--></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">constructor-arg </span><span style="color: rgba(255, 0, 0, 1)">name</span><span style="color: rgba(0, 0, 255, 1)">="poolConfig"</span><span style="color: rgba(255, 0, 0, 1)"> ref</span><span style="color: rgba(0, 0, 255, 1)">="jedisPoolConfig"</span> <span style="color: rgba(0, 0, 255, 1)">/></span> <span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">bean</span><span style="color: rgba(0, 0, 255, 1)">></span> <span style="color: rgba(0, 128, 0, 1)"><!--</span><span style="color: rgba(0, 128, 0, 1)"> 配置 JedisPoolConfig 实例 </span><span style="color: rgba(0, 128, 0, 1)">--></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">bean </span><span style="color: rgba(255, 0, 0, 1)">id</span><span style="color: rgba(0, 0, 255, 1)">="jedisPoolConfig"</span><span style="color: rgba(255, 0, 0, 1)"> class</span><span style="color: rgba(0, 0, 255, 1)">="redis.clients.jedis.JedisPoolConfig"</span><span style="color: rgba(0, 0, 255, 1)">></span> <span style="color: rgba(0, 128, 0, 1)"><!--</span><span style="color: rgba(0, 128, 0, 1)"> 最大连接数 </span><span style="color: rgba(0, 128, 0, 1)">--></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">property </span><span style="color: rgba(255, 0, 0, 1)">name</span><span style="color: rgba(0, 0, 255, 1)">="maxTotal"</span><span style="color: rgba(255, 0, 0, 1)"> value</span><span style="color: rgba(0, 0, 255, 1)">="${redis.pool.maxActive}"</span><span style="color: rgba(0, 0, 255, 1)">/></span> <span style="color: rgba(0, 128, 0, 1)"><!--</span><span style="color: rgba(0, 128, 0, 1)"> 最大空闲时间 </span><span style="color: rgba(0, 128, 0, 1)">--></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">property </span><span style="color: rgba(255, 0, 0, 1)">name</span><span style="color: rgba(0, 0, 255, 1)">="maxIdle"</span><span style="color: rgba(255, 0, 0, 1)"> value</span><span style="color: rgba(0, 0, 255, 1)">="${redis.pool.maxIdle}"</span><span style="color: rgba(0, 0, 255, 1)">/></span> <span style="color: rgba(0, 128, 0, 1)"><!--</span><span style="color: rgba(0, 128, 0, 1)"> 最小空闲时间 </span><span style="color: rgba(0, 128, 0, 1)">--></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">property </span><span style="color: rgba(255, 0, 0, 1)">name</span><span style="color: rgba(0, 0, 255, 1)">="minIdle"</span><span style="color: rgba(255, 0, 0, 1)"> value</span><span style="color: rgba(0, 0, 255, 1)">="${redis.pool.minIdle}"</span><span style="color: rgba(0, 0, 255, 1)">/></span> <span style="color: rgba(0, 128, 0, 1)"><!--</span><span style="color: rgba(0, 128, 0, 1)"> 获得链接时的最大等待毫秒数,小于0:阻塞不确定时间,默认-1 </span><span style="color: rgba(0, 128, 0, 1)">--></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">property </span><span style="color: rgba(255, 0, 0, 1)">name</span><span style="color: rgba(0, 0, 255, 1)">="maxWaitMillis"</span><span style="color: rgba(255, 0, 0, 1)"> value</span><span style="color: rgba(0, 0, 255, 1)">="${redis.pool.maxWaitMillis}"</span><span style="color: rgba(0, 0, 255, 1)">/></span> <span style="color: rgba(0, 128, 0, 1)"><!--</span><span style="color: rgba(0, 128, 0, 1)"> 在borrow一个jedis实例时,是否提前进行validate操作;如果为true,则得到的jedis实例均是可用的 </span><span style="color: rgba(0, 128, 0, 1)">--></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">property </span><span style="color: rgba(255, 0, 0, 1)">name</span><span style="color: rgba(0, 0, 255, 1)">="testOnBorrow"</span><span style="color: rgba(255, 0, 0, 1)"> value</span><span style="color: rgba(0, 0, 255, 1)">="${redis.pool.testOnBorrow}"</span><span style="color: rgba(0, 0, 255, 1)">/></span> <span style="color: rgba(0, 128, 0, 1)"><!--</span><span style="color: rgba(0, 128, 0, 1)"> 在空闲时检查有效性,默认false </span><span style="color: rgba(0, 128, 0, 1)">--></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">property </span><span style="color: rgba(255, 0, 0, 1)">name</span><span style="color: rgba(0, 0, 255, 1)">="testWhileIdle"</span><span style="color: rgba(255, 0, 0, 1)"> value</span><span style="color: rgba(0, 0, 255, 1)">="${redis.pool.testOnBorrow}"</span><span style="color: rgba(0, 0, 255, 1)">/></span> <span style="color: rgba(0, 128, 0, 1)"><!--</span><span style="color: rgba(0, 128, 0, 1)"> 表示idle object evitor两次扫描之间要sleep的毫秒数 </span><span style="color: rgba(0, 128, 0, 1)">--></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">property </span><span style="color: rgba(255, 0, 0, 1)">name</span><span style="color: rgba(0, 0, 255, 1)">="timeBetweenEvictionRunsMillis"</span><span style="color: rgba(255, 0, 0, 1)"> value</span><span style="color: rgba(0, 0, 255, 1)">="${redis.pool.timeBetweenEvictionRunsMillis}"</span> <span style="color: rgba(0, 0, 255, 1)">/></span> <span style="color: rgba(0, 128, 0, 1)"><!--</span><span style="color: rgba(0, 128, 0, 1)"> 表示一个对象至少停留在idle状态的最短时间,然后才能被idle object evitor扫描并驱逐;这一项只有在timeBetweenEvictionRunsMillis大于0时才有意义 </span><span style="color: rgba(0, 128, 0, 1)">--></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">property </span><span style="color: rgba(255, 0, 0, 1)">name</span><span style="color: rgba(0, 0, 255, 1)">="minEvictableIdleTimeMillis"</span><span style="color: rgba(255, 0, 0, 1)"> value</span><span style="color: rgba(0, 0, 255, 1)">="${redis.pool.minEvictableIdleTimeMillis}"</span> <span style="color: rgba(0, 0, 255, 1)">/></span> <span style="color: rgba(0, 128, 0, 1)"><!--</span><span style="color: rgba(0, 128, 0, 1)"> 表示idle object evitor每次扫描的最多的对象数 </span><span style="color: rgba(0, 128, 0, 1)">--></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">property </span><span style="color: rgba(255, 0, 0, 1)">name</span><span style="color: rgba(0, 0, 255, 1)">="numTestsPerEvictionRun"</span><span style="color: rgba(255, 0, 0, 1)"> value</span><span style="color: rgba(0, 0, 255, 1)">="${redis.pool.numTestsPerEvictionRun}"</span> <span style="color: rgba(0, 0, 255, 1)">/></span> <span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">bean</span><span style="color: rgba(0, 0, 255, 1)">></span> <span style="color: rgba(0, 128, 0, 1)"><!--</span><span style="color: rgba(0, 128, 0, 1)"> 配置哨兵 </span><span style="color: rgba(0, 128, 0, 1)">--></span> <span style="color: rgba(0, 128, 0, 1)"><!--</span><span style="color: rgba(0, 128, 0, 1)"> <bean id="redisSentinelConfiguration" class="org.springframework.data.redis.connection.RedisSentinelConfiguration"> <property name="master"> <bean class="org.springframework.data.redis.connection.RedisNode"> <property name="name" value="mymaster" /> </bean> </property> <property name="sentinels"> <set> <bean class="org.springframework.data.redis.connection.RedisNode"> <constructor-arg name="host" value="10.252.2.137" /> <constructor-arg name="port" value="26391" /> </bean> <bean class="org.springframework.data.redis.connection.RedisNode"> <constructor-arg name="host" value="10.252.2.137" /> <constructor-arg name="port" value="26392" /> </bean> <bean class="org.springframework.data.redis.connection.RedisNode"> <constructor-arg name="host" value="10.252.2.137" /> <constructor-arg name="port" value="26393" /> </bean> </set> </property> </bean> </span><span style="color: rgba(0, 128, 0, 1)">--></span> <span style="color: rgba(0, 128, 0, 1)"><!--</span><span style="color: rgba(0, 128, 0, 1)"> SDR默认采用的序列化策略有两种,一种是String的序列化策略,一种是JDK的序列化策略。 StringRedisTemplate默认采用的是String的序列化策略,保存的key和value都是采用此策略序列化保存的。 RedisTemplate默认采用的是JDK的序列化策略,保存的key和value都是采用此策略序列化保存的。 就是因为序列化策略的不同,即使是同一个key用不同的Template去序列化,结果是不同的。所以根据key去删除数据的时候就出现了删除失败的问题。 </span><span style="color: rgba(0, 128, 0, 1)">--></span> <span style="color: rgba(0, 128, 0, 1)"><!--</span><span style="color: rgba(0, 128, 0, 1)"> redis 序列化策略 ,通常情况下key值采用String序列化策略, </span><span style="color: rgba(0, 128, 0, 1)">--></span> <span style="color: rgba(0, 128, 0, 1)"><!--</span><span style="color: rgba(0, 128, 0, 1)"> 如果不指定序列化策略,StringRedisTemplate的key和value都将采用String序列化策略; </span><span style="color: rgba(0, 128, 0, 1)">--></span> <span style="color: rgba(0, 128, 0, 1)"><!--</span><span style="color: rgba(0, 128, 0, 1)"> 但是RedisTemplate的key和value都将采用JDK序列化 这样就会出现采用不同template保存的数据不能用同一个template删除的问题 </span><span style="color: rgba(0, 128, 0, 1)">--></span> <span style="color: rgba(0, 128, 0, 1)"><!--</span><span style="color: rgba(0, 128, 0, 1)"> 配置RedisTemplate </span><span style="color: rgba(0, 128, 0, 1)">--></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">bean </span><span style="color: rgba(255, 0, 0, 1)">id</span><span style="color: rgba(0, 0, 255, 1)">="redisTemplate"</span><span style="color: rgba(255, 0, 0, 1)"> class</span><span style="color: rgba(0, 0, 255, 1)">="org.springframework.data.redis.core.RedisTemplate"</span><span style="color: rgba(0, 0, 255, 1)">></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">property </span><span style="color: rgba(255, 0, 0, 1)">name</span><span style="color: rgba(0, 0, 255, 1)">="connectionFactory"</span><span style="color: rgba(255, 0, 0, 1)"> ref</span><span style="color: rgba(0, 0, 255, 1)">="jedisConnectionFactory"</span> <span style="color: rgba(0, 0, 255, 1)">/></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">property </span><span style="color: rgba(255, 0, 0, 1)">name</span><span style="color: rgba(0, 0, 255, 1)">="keySerializer"</span><span style="color: rgba(0, 0, 255, 1)">></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">bean </span><span style="color: rgba(255, 0, 0, 1)">class</span><span style="color: rgba(0, 0, 255, 1)">="org.springframework.data.redis.serializer.StringRedisSerializer"</span> <span style="color: rgba(0, 0, 255, 1)">/></span> <span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">property</span><span style="color: rgba(0, 0, 255, 1)">></span> <span style="color: rgba(0, 128, 0, 1)"><!--</span><span style="color: rgba(0, 128, 0, 1)"> <property name="valueSerializer" ref="stringRedisSerializer" /> value值如果是对象,这不能用stringRedisSerializer,报类型转换错误</span><span style="color: rgba(0, 128, 0, 1)">--></span> <span style="color: rgba(0, 128, 0, 1)"><!--</span><span style="color: rgba(0, 128, 0, 1)"> <property name="valueSerializer"> hex(十六进制)的格式 <bean class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer" /> </property> </span><span style="color: rgba(0, 128, 0, 1)">--></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">property </span><span style="color: rgba(255, 0, 0, 1)">name</span><span style="color: rgba(0, 0, 255, 1)">="valueSerializer"</span> <span style="color: rgba(0, 0, 255, 1)">></span> <span style="color: rgba(0, 128, 0, 1)"><!--</span><span style="color: rgba(0, 128, 0, 1)"> json的格式,要注意实体属性名有没有‘_’,如user_name,有的话要加注解 ,@JsonNaming会将userName处理为user_name @JsonSerialize @JsonNaming(PropertyNamingStrategy.LowerCaseWithUnderscoresStrategy.class) </span><span style="color: rgba(0, 128, 0, 1)">--></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">bean </span><span style="color: rgba(255, 0, 0, 1)">class</span><span style="color: rgba(0, 0, 255, 1)">="org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer"</span> <span style="color: rgba(0, 0, 255, 1)">/></span> <span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">property</span><span style="color: rgba(0, 0, 255, 1)">></span> <span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">bean</span><span style="color: rgba(0, 0, 255, 1)">></span> <span style="color: rgba(0, 128, 0, 1)"><!--</span><span style="color: rgba(0, 128, 0, 1)"> spring自己的缓存管理器,这里定义了缓存位置名称 ,即注解中的value </span><span style="color: rgba(0, 128, 0, 1)">--></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">bean </span><span style="color: rgba(255, 0, 0, 1)">id</span><span style="color: rgba(0, 0, 255, 1)">="cacheManager"</span><span style="color: rgba(255, 0, 0, 1)"> class</span><span style="color: rgba(0, 0, 255, 1)">="org.springframework.cache.support.SimpleCacheManager"</span><span style="color: rgba(0, 0, 255, 1)">></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">property </span><span style="color: rgba(255, 0, 0, 1)">name</span><span style="color: rgba(0, 0, 255, 1)">="caches"</span><span style="color: rgba(0, 0, 255, 1)">></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">set</span><span style="color: rgba(0, 0, 255, 1)">></span> <span style="color: rgba(0, 128, 0, 1)"><!--</span><span style="color: rgba(0, 128, 0, 1)"> 这里可以配置多个redis </span><span style="color: rgba(0, 128, 0, 1)">--></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">bean </span><span style="color: rgba(255, 0, 0, 1)">class</span><span style="color: rgba(0, 0, 255, 1)">="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean"</span><span style="color: rgba(0, 0, 255, 1)">></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">property </span><span style="color: rgba(255, 0, 0, 1)">name</span><span style="color: rgba(0, 0, 255, 1)">="name"</span><span style="color: rgba(255, 0, 0, 1)"> value</span><span style="color: rgba(0, 0, 255, 1)">="localDefault"</span> <span style="color: rgba(0, 0, 255, 1)">/></span><span style="color: rgba(0, 128, 0, 1)"><!--</span><span style="color: rgba(0, 128, 0, 1)"> 缺省本地缓存 </span><span style="color: rgba(0, 128, 0, 1)">--></span> <span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">bean</span><span style="color: rgba(0, 0, 255, 1)">></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">bean </span><span style="color: rgba(255, 0, 0, 1)">class</span><span style="color: rgba(0, 0, 255, 1)">="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean"</span><span style="color: rgba(0, 0, 255, 1)">></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">property </span><span style="color: rgba(255, 0, 0, 1)">name</span><span style="color: rgba(0, 0, 255, 1)">="name"</span><span style="color: rgba(255, 0, 0, 1)"> value</span><span style="color: rgba(0, 0, 255, 1)">="WSLocalTableCache"</span> <span style="color: rgba(0, 0, 255, 1)">/></span><span style="color: rgba(0, 128, 0, 1)"><!--</span><span style="color: rgba(0, 128, 0, 1)"> 单表配置 </span><span style="color: rgba(0, 128, 0, 1)">--></span> <span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">bean</span><span style="color: rgba(0, 0, 255, 1)">></span> <span style="color: rgba(0, 128, 0, 1)"><!--</span><span style="color: rgba(0, 128, 0, 1)"> 本地缓存2:管理缓存失效 </span><span style="color: rgba(0, 128, 0, 1)">--></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">bean </span><span style="color: rgba(255, 0, 0, 1)">class</span><span style="color: rgba(0, 0, 255, 1)">="com.only.mate.utils.RedisCache"</span><span style="color: rgba(0, 0, 255, 1)">></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">property </span><span style="color: rgba(255, 0, 0, 1)">name</span><span style="color: rgba(0, 0, 255, 1)">="name"</span><span style="color: rgba(255, 0, 0, 1)"> value</span><span style="color: rgba(0, 0, 255, 1)">="localTest"</span> <span style="color: rgba(0, 0, 255, 1)">/></span><span style="color: rgba(0, 128, 0, 1)"><!--</span><span style="color: rgba(0, 128, 0, 1)"> 本地缓存名 </span><span style="color: rgba(0, 128, 0, 1)">--></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">property </span><span style="color: rgba(255, 0, 0, 1)">name</span><span style="color: rgba(0, 0, 255, 1)">="timeout"</span><span style="color: rgba(255, 0, 0, 1)"> value</span><span style="color: rgba(0, 0, 255, 1)">="10"</span> <span style="color: rgba(0, 0, 255, 1)">/></span> <span style="color: rgba(0, 128, 0, 1)"><!--</span><span style="color: rgba(0, 128, 0, 1)"> seconds </span><span style="color: rgba(0, 128, 0, 1)">--></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">property </span><span style="color: rgba(255, 0, 0, 1)">name</span><span style="color: rgba(0, 0, 255, 1)">="removeTimeout"</span><span style="color: rgba(255, 0, 0, 1)"> value</span><span style="color: rgba(0, 0, 255, 1)">="true"</span> <span style="color: rgba(0, 0, 255, 1)">/></span> <span style="color: rgba(0, 128, 0, 1)"><!--</span><span style="color: rgba(0, 128, 0, 1)"> 超时移除 </span><span style="color: rgba(0, 128, 0, 1)">--></span> <span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">bean</span><span style="color: rgba(0, 0, 255, 1)">></span> <span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">set</span><span style="color: rgba(0, 0, 255, 1)">></span> <span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">property</span><span style="color: rgba(0, 0, 255, 1)">></span> <span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">bean</span><span style="color: rgba(0, 0, 255, 1)">></span> <span style="color: rgba(0, 128, 0, 1)"><!--</span><span style="color: rgba(0, 128, 0, 1)"> 配置redis发布订阅模式 </span><span style="color: rgba(0, 128, 0, 1)">--></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">bean </span><span style="color: rgba(255, 0, 0, 1)">id</span><span style="color: rgba(0, 0, 255, 1)">="redisMessageListenerContainer"</span><span style="color: rgba(255, 0, 0, 1)"> class</span><span style="color: rgba(0, 0, 255, 1)">="org.springframework.data.redis.listener.RedisMessageListenerContainer"</span><span style="color: rgba(0, 0, 255, 1)">></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">property </span><span style="color: rgba(255, 0, 0, 1)">name</span><span style="color: rgba(0, 0, 255, 1)">="connectionFactory"</span><span style="color: rgba(255, 0, 0, 1)"> ref</span><span style="color: rgba(0, 0, 255, 1)">="jedisConnectionFactory"</span> <span style="color: rgba(0, 0, 255, 1)">/></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">property </span><span style="color: rgba(255, 0, 0, 1)">name</span><span style="color: rgba(0, 0, 255, 1)">="messageListeners"</span><span style="color: rgba(0, 0, 255, 1)">></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">map</span><span style="color: rgba(0, 0, 255, 1)">></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">entry </span><span style="color: rgba(255, 0, 0, 1)">key-ref</span><span style="color: rgba(0, 0, 255, 1)">="messageListenerAdapter"</span><span style="color: rgba(0, 0, 255, 1)">></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">bean </span><span style="color: rgba(255, 0, 0, 1)">class</span><span style="color: rgba(0, 0, 255, 1)">="org.springframework.data.redis.listener.ChannelTopic"</span><span style="color: rgba(0, 0, 255, 1)">></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">constructor-arg </span><span style="color: rgba(255, 0, 0, 1)">value</span><span style="color: rgba(0, 0, 255, 1)">="WEB_SOCKET:LOTTERY"</span><span style="color: rgba(0, 0, 255, 1)">></</span><span style="color: rgba(128, 0, 0, 1)">constructor-arg</span><span style="color: rgba(0, 0, 255, 1)">></span> <span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">bean</span><span style="color: rgba(0, 0, 255, 1)">></span> <span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">entry</span><span style="color: rgba(0, 0, 255, 1)">></span> <span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">map</span><span style="color: rgba(0, 0, 255, 1)">></span> <span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">property</span><span style="color: rgba(0, 0, 255, 1)">></span> <span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">bean</span><span style="color: rgba(0, 0, 255, 1)">></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">bean </span><span style="color: rgba(255, 0, 0, 1)">id</span><span style="color: rgba(0, 0, 255, 1)">="messageListenerAdapter"</span><span style="color: rgba(255, 0, 0, 1)"> class</span><span style="color: rgba(0, 0, 255, 1)">="org.springframework.data.redis.listener.adapter.MessageListenerAdapter"</span><span style="color: rgba(0, 0, 255, 1)">></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">constructor-arg </span><span style="color: rgba(255, 0, 0, 1)">ref</span><span style="color: rgba(0, 0, 255, 1)">="redisMsgPubSubListener"</span><span style="color: rgba(0, 0, 255, 1)">></</span><span style="color: rgba(128, 0, 0, 1)">constructor-arg</span><span style="color: rgba(0, 0, 255, 1)">></span> <span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">bean</span><span style="color: rgba(0, 0, 255, 1)">></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">bean </span><span style="color: rgba(255, 0, 0, 1)">id</span><span style="color: rgba(0, 0, 255, 1)">="redisMsgPubSubListener"</span><span style="color: rgba(255, 0, 0, 1)"> class</span><span style="color: rgba(0, 0, 255, 1)">="com.redis.pubsub.spring.RedisMsgPubSubListener"</span><span style="color: rgba(0, 0, 255, 1)">></</span><span style="color: rgba(128, 0, 0, 1)">bean</span><span style="color: rgba(0, 0, 255, 1)">></span>
</beans>
如上的配置即配置了对 Redis 的链接。在配置类中的将 ChannelTopic 加入 IOC 容器。则在 Spring 启动时会在一个 RedisTemplate(一个对 Redis 的链接) 中设置的一个 channel,即 WEB_SOCKET:LOTTERY。
在上述配置中,RedisMsgPubSubListener 是我们生成的,这个类即为核心监听类,RedisTemplate 接受到数据如何处理就是在该类中处理的。
附加上 Java 配置
import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List;import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.listener.ChannelTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.listener.Topic;
import org.springframework.data.redis.listener.adapter.MessageListenerAdapter;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.gfss.common.listener.CustomRedisMsgPubSubListener;@Configuration
@EnableCaching
public class RedisConfiguration extends CachingConfigurerSupport {@Override @Bean </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> KeyGenerator keyGenerator() { </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> KeyGenerator() { @Override </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> Object generate(Object target, Method method, Object... params) { StringBuilder sb </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> StringBuilder(); sb.append(target.getClass().getName()); sb.append(method.getName()); </span><span style="color: rgba(0, 0, 255, 1)">for</span><span style="color: rgba(0, 0, 0, 1)"> (Object obj : params) { sb.append(obj.toString()); } </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> sb.toString(); } }; } @Bean </span><span style="color: rgba(0, 0, 255, 1)">public</span> CacheManager cacheManager(RedisTemplate<?, ?><span style="color: rgba(0, 0, 0, 1)"> redisTemplate) { </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> RedisCacheManager(redisTemplate); } @Bean </span><span style="color: rgba(0, 0, 255, 1)">public</span> RedisTemplate<String, String><span style="color: rgba(0, 0, 0, 1)"> redisTemplate(RedisConnectionFactory factory) { StringRedisTemplate template </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> StringRedisTemplate(factory); Jackson2JsonRedisSerializer</span><Object> jackson2JsonRedisSerializer = <span style="color: rgba(0, 0, 255, 1)">new</span> Jackson2JsonRedisSerializer<Object><span style="color: rgba(0, 0, 0, 1)">( Object.</span><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)">); ObjectMapper om </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> ObjectMapper(); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); jackson2JsonRedisSerializer.setObjectMapper(om); template.setValueSerializer(jackson2JsonRedisSerializer); template.afterPropertiesSet(); </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> template; } @Bean </span><span style="color: rgba(0, 0, 255, 1)">public</span> RedisTemplate<String, Object><span style="color: rgba(0, 0, 0, 1)"> objectRedisTemplate(RedisConnectionFactory factory) { RedisTemplate</span><String, Object> template = <span style="color: rgba(0, 0, 255, 1)">new</span> RedisTemplate<String, Object><span style="color: rgba(0, 0, 0, 1)">(); template.setConnectionFactory(factory); template.setKeySerializer(</span><span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> StringRedisSerializer()); </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> template; } </span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)">************ 配置redis发布订阅模式 ******************************</span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)"> @Bean </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> CustomRedisMsgPubSubListener customRedisMsgPubSubListener() { </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> CustomRedisMsgPubSubListener(); } @Bean </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> MessageListenerAdapter messageListenerAdapter(MessageListener messageListener) { </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> MessageListenerAdapter(messageListener); } @Bean </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> RedisMessageListenerContainer redisMessageListenerContainer(RedisConnectionFactory redisConnectionFactory, MessageListenerAdapter messageListenerAdapter) { List</span><Topic> collection = <span style="color: rgba(0, 0, 255, 1)">new</span> ArrayList<Topic><span style="color: rgba(0, 0, 0, 1)">(); </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 普通订阅,订阅具体的频道</span> ChannelTopic channelTopic = <span style="color: rgba(0, 0, 255, 1)">new</span> ChannelTopic("WEB_SOCKET:LOTTERY"<span style="color: rgba(0, 0, 0, 1)">); collection.add(channelTopic); </span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">// 模式订阅,支持模式匹配订阅,*为模糊匹配符 PatternTopic PatternTopic = new PatternTopic("WEB_SOCKET:*"); collection.add(PatternTopic); // 匹配所有频道 PatternTopic PatternTopicAll = new PatternTopic("*"); collection.add(PatternTopicAll);</span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)"> RedisMessageListenerContainer redisMessageListenerContainer </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> RedisMessageListenerContainer(); redisMessageListenerContainer.setConnectionFactory(redisConnectionFactory); redisMessageListenerContainer.addMessageListener(messageListenerAdapter, collection); </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> redisMessageListenerContainer; }
}
访问页面去调用发布者
http://localhost:8088/redis/redisMsgPubClientBySpring?cutomerId=all&msg= 你们好
订阅收到的消息
5. 拓展开发
在分布式服务中,可以结合 WebSocket 与 Redis 的发布订阅模式相结合,解决 session 不能共享的问题。
当业务处理完成之后,通过 Redis 的发布订阅模式,发布消息到每个订阅该频道的服务节点,然后由每个服务节点通过 key 寻找自己内存缓存中的 session,然后找到了就向客户端推消息,否则不处理。
Dubbo 只能传输可序列化的对象,Redis 只能缓存可序列化的对象,Dubbo 基于网络流(TCP),Redis 缓存的数据要存储在硬盘上,而 WebSocketSession 是没有实现序列化的,所以不能跨服务传递 WebSocketSession,也不能使用 Redis 存储 WebSocketSession,只能自定义一块缓存区。
6. 动态订阅频道
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.data.redis.connection.Message; import org.springframework.data.redis.connection.MessageListener; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.listener.ChannelTopic; import org.springframework.data.redis.listener.PatternTopic; import org.springframework.data.redis.listener.RedisMessageListenerContainer; import org.springframework.data.redis.listener.adapter.MessageListenerAdapter; import org.springframework.data.redis.serializer.RedisSerializer;import com.alibaba.fastjson.JSONObject;
import com.gfss.common.websocket.CustomWebSocketHandler;/**
- redis 发布订阅消息监听器
- @ClassName: RedisMsgPubSubListener
- @Description: TODO
- @author OnlyMate
- @Date 2018 年 8 月 22 日 上午 10:05:35
*/
public class CustomRedisMsgPubSubListener implements MessageListener {
private Logger logger = LoggerFactory.getLogger(CustomRedisMsgPubSubListener.class);@Autowired </span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> CustomWebSocketHandler customWebSocketHandler; @Autowired </span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> ApplicationContext applicationContext; @Autowired </span><span style="color: rgba(0, 0, 255, 1)">private</span> RedisTemplate<String, String><span style="color: rgba(0, 0, 0, 1)"> redisTemplate; </span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)"> * 实例: * JSONObject json = new JSONObject(); * json.put("cutomerId", notifyResult.getResult()); * json.put("resultCode", map.get("resultCode")); * //向redis发布消息 * redisTemplate.convertAndSend(channelName, json); * </span><span style="color: rgba(128, 128, 128, 1)">@param</span><span style="color: rgba(0, 128, 0, 1)"> message * </span><span style="color: rgba(128, 128, 128, 1)">@param</span><span style="color: rgba(0, 128, 0, 1)"> pattern * @Throws * @Author: chetao * @Date: 2019年1月8日 下午10:40:21 * </span><span style="color: rgba(128, 128, 128, 1)">@see</span><span style="color: rgba(0, 128, 0, 1)"> org.springframework.data.redis.connection.MessageListener#onMessage(org.springframework.data.redis.connection.Message, byte[]) </span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)"> @Override </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span> onMessage( <span style="color: rgba(0, 0, 255, 1)">final</span> Message message, <span style="color: rgba(0, 0, 255, 1)">final</span> <span style="color: rgba(0, 0, 255, 1)">byte</span><span style="color: rgba(0, 0, 0, 1)">[] pattern ) { RedisSerializer</span><?> serializer =<span style="color: rgba(0, 0, 0, 1)"> redisTemplate.getKeySerializer(); logger.info(</span>"Message receive-->pattern:{},message: {},{}"<span style="color: rgba(0, 0, 0, 1)">, serializer.deserialize(pattern), serializer.deserialize(message.getBody()), serializer.deserialize(message.getChannel())); </span><span style="color: rgba(0, 0, 255, 1)">if</span> ("WEB_SOCKET:PAY_NOTIFY"<span style="color: rgba(0, 0, 0, 1)">.equals(serializer.deserialize(message.getChannel()))) { RedisMessageListenerContainer redisMessageListenerContainer </span>=<span style="color: rgba(0, 0, 0, 1)"> applicationContext .getBean(</span>"redisMessageListenerContainer", RedisMessageListenerContainer.<span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)">); MessageListenerAdapter messageListenerAdapter </span>= applicationContext.getBean("messageListenerAdapter"<span style="color: rgba(0, 0, 0, 1)">, MessageListenerAdapter.</span><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)">); </span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">List<Topic> collection = new ArrayList<Topic>(); // 动态添加订阅主题 ChannelTopic channelTopic = new ChannelTopic("WEB_SOCKET1:PAY_NOTIFY"); collection.add(channelTopic); PatternTopic PatternTopic = new PatternTopic("WEB_SOCKET:*"); collection.add(PatternTopic); redisMessageListenerContainer.addMessageListener(messageListenerAdapter, collection);</span><span style="color: rgba(0, 128, 0, 1)">*/</span> <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 动态添加订阅主题</span> ChannelTopic channelTopic = <span style="color: rgba(0, 0, 255, 1)">new</span> ChannelTopic("WEB_SOCKET1:PAY_NOTIFY"<span style="color: rgba(0, 0, 0, 1)">); redisMessageListenerContainer.addMessageListener(messageListenerAdapter, channelTopic); PatternTopic PatternTopic </span>= <span style="color: rgba(0, 0, 255, 1)">new</span> PatternTopic("WEB_SOCKET:*"<span style="color: rgba(0, 0, 0, 1)">); redisMessageListenerContainer.addMessageListener(messageListenerAdapter, PatternTopic); } JSONObject json </span>=<span style="color: rgba(0, 0, 0, 1)"> JSONObject.parseObject(message.toString()); customWebSocketHandler.sendMessage(json.toJSONString()); }
}
上面两种动态订阅频道的方式都可以,本人已测试是可行的,可以结合自己的业务去拓展,如:临时订阅频道后退订频道