近日遇到需要发消息的情况,脑中直接浮现了kafka和emq(rabbitMQ)工具。
但是我仔细一想,发现了问题:
1.这次需要的消息功能非常简单,用量也不大
2.服务器的内存和硬盘都很小,还是单节点,kafka和mq这种会对服务器造成压力,而且有大材小用的意思
3.忽然间想起了redis也有消息队列的功能。
查询了一下,决定使用jedis的JedisPubSub。
和网上的例子不同的是,他们的是在服务器启动的时候直接订阅了channel,再也不会改变。
而我需要的是在用户登陆的时候,每个用户订阅属于自己特定的channel。
主要的过程是:
1.登陆后传入userid,使用jedis.subscribe订阅特定的channel;
2.使用JedisPubSub的onMessage接收消息;
解决的难点有:
1.jedis的pub和sub是阻塞的(例如onsubscribe),为此需要专门开辟线程进行处理
2.登陆后,订阅channel后,退出登陆,再次登陆,这时会重复订阅channel,导致收到多变消息;
2.1 jedis.unsubscribe不好用,一直在报client是null的错,解决方案是,在登陆的时候发送特定消息,在onMessage中判定加密后的特定字符串,如果相等,则在JedisPubSub的onMessage中直接调用unsubscribe取消订阅,这样直接就解决了jedis.unsubscribe的问题
3.在thread中无法正常注入jedisPool,只能在thread的构造方法中传入,其实,对于我的功能来说这是一个不错的方法,因为还需要传入别的参数
下面直接上代码,有问题可以联系我。
/**
登陆
**/
public class LoginController {
@Autowired
private MsgLauncher msgLauncher;
@PostMapping(value = "/login")
public Odin login(@RequestBody User user){
msgLauncher.prepared(u.getId());
msgLauncher.lanuch(u.getId());
return odin;
}
}
/**
消息登陆器
*/
package com.zzj.msg;
import com.zzj.msg.whistleRedis.WhistleRedisThread;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import redis.clients.jedis.JedisPool;
/**
* Created by Administrator on 2019/1/7.
*/
@Service
public class MsgLauncher {
@Autowired
JedisPool jedisPool;
@Autowired
private RedisTemplate redisTemplate;
public MsgLauncher() {}
public void lanuch (String userID) {
WhistleRedisThread thread = new WhistleRedisThread(userID,jedisPool);
thread.start();
}
public void prepared (String userID) {
WhistleRedisThread thread = new WhistleRedisThread(userID,jedisPool,true);
thread.start();
}
}
/**
阻塞处理
*/
package com.zzj.msg.whistleRedis;
import com.zzj.security.encrypter.MD5Cheater;
import com.zzj.utils.Constants;
import lombok.extern.slf4j.Slf4j;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
@Slf4j
public class WhistleRedisThread extends Thread {
private String userID;
private JedisPool jedisPool;
private boolean clean = false;
@Override
public void run() {
Jedis jedis = jedisPool.getResource();
try {
if(clean){
//取消订阅
WhistleRedisHandler.Sender.send(jedis,userID,MD5Cheater.md5(Constants.CHANNEL_KEY));
log.info("=================unsubscribe CHANNEL=============================");
log.info("=================================================================");
log.info("=================【" + userID + "】===============================");
log.info("=================================================================");
log.info("=================unsubscribe CHANNEL=============================");
return;
}
WhistleRedisHandler.Getter getter = new WhistleRedisHandler.Getter();
jedis.subscribe(getter,userID);
log.info("=================subscribe CHANNEL=============================");
log.info("=================================================================");
log.info("=================【" + userID + "】===============================");
log.info("=================================================================");
log.info("=================subscribe CHANNEL=============================");
} catch (Exception e) {
e.printStackTrace();
} finally {
return;
}
}
public WhistleRedisThread(String userID,JedisPool jedisPool) {
this.userID = userID;
this.jedisPool = jedisPool;
}
public WhistleRedisThread(String userID,JedisPool jedisPool,boolean clean) {
this.userID = userID;
this.jedisPool = jedisPool;
this.clean = clean;
}
public WhistleRedisThread() {
}
}
/**
真正的处理器
*/
package com.zzj.msg.whistleRedis;
import com.zzj.security.encrypter.MD5Cheater;
import com.zzj.utils.Constants;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPubSub;
/**
* Created by Administrator on 2019/1/7.
*/
@Slf4j
public class WhistleRedisHandler {
public WhistleRedisHandler() {
}
public static class Getter extends JedisPubSub{
@Override
public void onMessage(String channel, String message) {
//订阅消息
if(MD5Cheater.md5(Constants.CHANNEL_KEY).equals(message)){
this.unsubscribe(channel);
log.info("YesCommander,Cancel This Channel [" + channel +"]");
}
log.info("message is comming: channel is: " + channel );
log.info("message is comming: message is: " + message );
}
@Override
public void onSubscribe(String channel, int subscribedChannels) {
// 订阅了频道 channel
log.info("Subscribe success: channel is: " + channel );
log.info("Subscribe success: subscribedChannels is: " + subscribedChannels );
}
@Override
public void onUnsubscribe(String channel, int subscribedChannels) {
log.info("Unsubscribe channel: channel is: " + channel );
log.info("Unsubscribe channel: subscribedChannels is: " + subscribedChannels );
}
}
public static class Sender {
public static void send(Jedis jedis,String channel, String message){
jedis.publish(channel,message);
}
}
}
/**
常量
**/
package com.zzj.utils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class Constants {
public static String CHANNEL_KEY;
@Value(value = "${spring.redis.pubsub.keywords.unsubscribe}")
public void setChannelKey(String channelKey) {
CHANNEL_KEY = channelKey;
}
}
可以查看详细内容