【IM产品开发系列之Redis发布订阅】SpringBoot2.0中使用redis的发布订阅模式

本文详细介绍了在IM即时通讯工具中,如何利用Redis的发布订阅机制实现实时更新用户聊天列表的未读消息数,包括代码实现和Spring Boot框架下的配置。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

        最近项目组要做个IM即时通讯工具,用于渠道营销人员使用。拿到需求后,一时茫然不知如何实现,之前也没做过IM的经验,于是花了2天研究业界,设计个方案,拉上项目组(项目经理、技术经理和几个核心研发人员)评审通过,开始组建团队开工干活。今天主要介绍下用户上线后触发聊天列表的推送机制。

        聊天列表主要是:发送者、未读消息条数、最近一条消息内容、最近一条消息发送时间、消息全局流水号ID。由于用户上线,要触发消息的推送,推送走NIO框架Netty发送。负责消息推送的是连接服务器(用户终端和连接服务器建立socket链接),而链接服务器是分布式集群部署,用户上线后的路由信息全局存储至Redis,这里如何做到新消息到来,立即推送未读消息条数给用户终端,用到了Redis的发布订阅机制。消息到来,存储至消息Redis,同时向指定channel发布消息,通知订阅方,订阅方接收消息后根据接收账号和发送账号读取消息Redis库,计算接收者有多少条未读消息,利用webscoket协议推送给用户终端。

     上面是业务场景,下面讲解具体代码如何实现,项目使用Springboot框架:

  • RedisConfig配置类
@Configuration
public class RedisConfig {
	
	@Value("${server.host}")
	private String serverip;  //连接服务器IP
	
	@Bean
	@SuppressWarnings("all")
	public RedisTemplate redisTemplate(RedisConnectionFactory factory) {
		
		RedisTemplate template = new StringRedisTemplate();
		template.setConnectionFactory(factory);
		Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
		ObjectMapper om = new ObjectMapper();
		om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
		om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
		jackson2JsonRedisSerializer.setObjectMapper(om);
		StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
		
		// key采用String的序列化方式
		template.setKeySerializer(stringRedisSerializer);
		
		// hash的key也采用String的序列化方式
		template.setHashKeySerializer(stringRedisSerializer);
		
		// value序列化方式采用jackson
		template.setValueSerializer(jackson2JsonRedisSerializer);
		
		// hash的value序列化方式采用jackson
		template.setHashValueSerializer(jackson2JsonRedisSerializer);
		template.afterPropertiesSet();
		
		return template;
	}
	
	@Bean(name = "redisUtils")
    public RedisUtils redisUtils(RedisTemplate<?,?> redisTemplate){
        RedisUtils redisUtils = new RedisUtils();
        redisUtils.setRedisTemplate(redisTemplate);
        return redisUtils;
    }
	
	
	
	//初始化监听器
    @Bean
    public RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory,
            MessageListenerAdapter listenerUserOnlineAdapter) {
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(connectionFactory);
        
        String channel_useronline = String.format(Constants.RedisKey.channel_useronline, serverip);
        container.addMessageListener(listenerNewMessageAdapter, new PatternTopic(channel_newMessage));
        return container;
    }
    
    @Bean
    MessageListenerAdapter listenerUserOnlineAdapter(RedisUserOnlineReceiver receiver) {
        return new MessageListenerAdapter(receiver, "receiveMessage");
    }
}

Note:

  1、其中String channel_useronline = String.format(Constants.RedisKey.channel_useronline, serverip);container.addMessageListener(listenerUserOnlineAdapter, new PatternTopic(channel_useronline));即订阅该channel。 其中RedisMessageListenerContainer是消息监听器容器,可以添加多个监听不同话题的redis监听器,比如:

 container.addMessageListener(listenerNewMessageAdapter1, new PatternTopic(channel_newMessage1));

 container.addMessageListener(listenerNewMessageAdapter2, new PatternTopic(channel_newMessage2));

只需要把消息监听器和相应的消息订阅处理器绑定,该消息监听器通过Reflect调用消息订阅处理器的相关方法进行一些业务处理。

2、listenerUserOnlineAdapter则是消息监听器适配器,绑定消息处理器RedisUserOnlineReceiver,利用反射技术调用消息处理器的业务方法receiveMessage。

  • 订阅处理器类RedisUserOnlineReceiver
    /**
     * redis订阅用户上线事件,接收事件后进行未读消息的推送
     **/
    @Service
    public class RedisUserOnlineReceiver {
    	
    	private final Logger logger = LoggerFactory.getLogger(RedisUserOnlineReceiver.class);
    	
    	public void receiveMessage(String message) {
            //编写订阅后的业务逻辑处理
    	    logger.info("-----------RedisUserOnlineReceiver subscribe new message=" + message);
        }
    }

    结合我的业务背景,此处业务逻辑是:访问消息Redis库,查询指定接收账号的未读消息条数,组织报文通过webscoket协议走Netty的Channe.writeAndFlush方法推送给用户终端!

  • 采用Spring session时,如果消息通知频繁,并发比较高,接收事件的线程池可能应付不了,需要配置线程池,来复用线程,减少创建线程的开销,提高响应速度,代码如下:

    @Bean
    	public ThreadPoolTaskExecutor springSessionRedisTaskExecutor(){
    	    ThreadPoolTaskExecutor springSessionRedisTaskExecutor = new ThreadPoolTaskExecutor();
    	    springSessionRedisTaskExecutor.setCorePoolSize(20);
    	    springSessionRedisTaskExecutor.setMaxPoolSize(20);
    	    springSessionRedisTaskExecutor.setKeepAliveSeconds(10);
    	    springSessionRedisTaskExecutor.setQueueCapacity(1000);
    	    springSessionRedisTaskExecutor.setThreadNamePrefix("Spring session redis executor thread: ");
    	    return springSessionRedisTaskExecutor;
    	}

    其实本质原因是Spring session(redis存储方式)监听导致创建大量redisMessageListenerContailner-X线程。默认情况下监听器线程池不配置就采用框架自带的SimpleAsyncTaskExecutor线程池,而SimpleAsyncTaskExecutor每次都将创建新的线程处理请求,其实它应该算伪线程池,但它可以设置最大并发线程数量。具体源码请见

  • 具体细节请读者自行查看下源码并分析下原理。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值