介绍
一个简单的消息订阅/发布
git地址
https://gitee.com/ichiva/message-pub-sub.git
快速入门
- 实例化一个异步的消息服务
MessageService messageService = new AsyncMessageService();
- 订阅游戏部的消息
HashMap<String, Object> header = new HashMap<>();
header.put("department","game");
messageService.subscribe(header,System.out::println);
- 向游戏部发送消息
Message message = new Message();
message.setContext("今晚吃鸡");
HashMap<String, Object> sendheader = new HashMap<>();
sendheader.put("department","game");
message.setHeader(sendheader);
messageService.publish(message);
实现细节
- 定义消息体
@ToString
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Message {
/**
* 消息类型
*/
private int type;
/**
* 消息头
*/
private Map<String,Object> header = new HashMap<>();
/**
* 消息体
*/
private String context;
}
- 定义消息服务接口
public interface MessageService {
/**
* 发布消息
* @param message 消息
*/
void publish(Message message);
/**
* 订阅
* @param header 头部
* @param consumer 消费者
* @return
*/
String subscribe(Map<String,Object> header, Consumer<Message> consumer);
/**
* 取消订阅
* @param subscribeId 订阅编号
*/
void unsubscribe(String subscribeId);
}
- 实现一个同步的消息服务
@Slf4j
public class SimpleMessageService implements MessageService {
protected final ConcurrentHashMap<String,Handler> map = new ConcurrentHashMap<>();
public SimpleMessageService(){
log.info("Simple Message Service start.");
}
@Override
public void publish(Message message) {
map.values()
.stream()
.filter(e -> isContain(message.getHeader(), e.header))
.map(e -> e.consumer)
.forEach(e -> e.accept(message));
}
/**
* 大map是否包含小map
* @param bigMap
* @param smallMap
* @return
*/
public boolean isContain(Map<String,Object> bigMap,Map<String,Object> smallMap){
if(smallMap.size() == 0) return true;
for (Map.Entry<String, Object> entry : smallMap.entrySet()) {
if (!entry.getValue().equals(bigMap.get(entry.getKey()))) {
return false;
}
}
return true;
}
@Override
public String subscribe(Map<String,Object> header, Consumer<Message> consumer) {
String uuid = UUID.randomUUID().toString();
map.put(uuid,new Handler(header,consumer));
log.debug("subscribe channel size = {}",map.size());
return uuid;
}
@Override
public void unsubscribe(String id) {
if(id != null){
map.remove(id);
log.debug("unsubscribe channel size = {}",map.size());
}
}
public static class Handler{
Map<String,Object> header;
Consumer<Message> consumer;
public Handler(Map<String, Object> header, Consumer<Message> consumer) {
this.header = header;
this.consumer = consumer;
}
}
}
- 实现异步的消息服务
@Slf4j
public class AsyncMessageService extends SimpleMessageService {
private final AsyncOptions opt;
protected final ExecutorService executorService;
protected final LinkedBlockingQueue<Message> queue = new LinkedBlockingQueue<>(100000);
public AsyncMessageService(){
this(new AsyncOptions());
}
public AsyncMessageService(AsyncOptions opt){
this.opt = opt;
executorService = Executors.newFixedThreadPool(opt.nThread,new AsyncThreadFactory());
for (int i = 0; i < opt.nThread; i++) {
executorService.submit(() -> head());
}
}
private void head() {
while (true){
try {
Message take = queue.take();
super.publish(take);
} catch (InterruptedException e) {
break;
} catch (Exception e){
log.warn("异步发送消息失败",e);
}
}
}
@Override
public void publish(Message message) {
queue.add(message);
}
public AsyncOptions getOpt() {
return opt;
}
private static class AsyncThreadFactory implements ThreadFactory{
AtomicInteger index = new AtomicInteger();
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
thread.setName("AsyncMessageService-thread-" + index.incrementAndGet());
return thread;
}
}
}
- 扩展redis的消息服务
@Slf4j
public class RedisMessageService extends SimpleMessageService {
private final RedisClient client;
private final String channel;
private final StatefulRedisPubSubConnection<String, String> listenerConnection;
private final RedisPubSubAsyncCommands<String, String> listenerCmd;
private final StatefulRedisPubSubConnection<String, String> publishConnection;
private final RedisPubSubAsyncCommands<String, String> publishCmd;
public RedisMessageService(RedisClient client, String channel) {
this.client = client;
this.channel = channel;
this.listenerConnection = client.connectPubSub();
this.publishConnection = client.connectPubSub();
this.publishCmd = publishConnection.async();
this.listenerCmd = listenerConnection.async();
//添加监听通道
listenerCmd.subscribe(this.channel);
//添加消息监听器
listenerConnection.addListener(new RedisPubSubAdapter<String,String>(){
@Override
public void message(String channel, String text) {
Message message = JSON.parseObject(text, Message.class);
RedisMessageService.super.publish(message);
}
});
}
@Override
public void publish(Message message) {
publishCmd.publish(this.channel,JSON.toJSONString(message));
}
}
git地址
https://gitee.com/ichiva/message-pub-sub.git