目前工业物联网项目 数据推送使用Redis 推送订阅方式,特作下此篇,算个记录吧;
一、Redis 推送订阅特性
官网介绍:https://redis.com.cn/redis-pub-sub.html
Redis 发布/订阅是一种消息传模式,其中发送者(在Redis术语中称为发布者)发送消息,而接收者(订阅者)接收消息。传递消息的通道称为channel。
Redis的发布和订阅 消息没有持久化,所发布的消息是 当时没收到便后续就收不到了;就目前验证的现象来看,redis 发布者的消息属于 没有消费就丢弃,并没有在channel 里边缓存;
订阅模式见下
二、发布订阅实现
1. 引入库
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>5.0.2</version>
</dependency>
2. 发布端代码
注意 Jedis jedis = getRedisConnect().getResource();
这行代码是每次从连接池里边取出空闲连接,注意需使用Jedis 变量 然后再去操作,不然容易报无法获得连接
package org.jetlinks.community.mq;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import redis.clients.jedis.Jedis;
import java.util.Date;
import static org.jetlinks.community.mq.JedisConnectUtil.getRedisConnect;
public class RedisPublish {
private Gson gson = new Gson();
private Jedis jedis = getRedisConnect().getResource();
public static void main(String[] args) throws InterruptedException {
RedisPublish redisPublish = new RedisPublish();
for (int i = 0; i < 60; i++) {
redisPublish.publishOne(i);
Thread.currentThread().sleep(1000);
//redisPublish.publishTwo(i);
}
/*RedisPublish redisPublish = new RedisPublish();
redisPublish.publish();*/
}
private void publishOne(int num) {
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("id", 20016l);
jsonObject.addProperty("pos_x", 420622);
jsonObject.addProperty("pos_y", 175052);
jsonObject.addProperty("ip", "10.104.114.50");
jsonObject.addProperty("num", num);
String mes = gson.toJson(jsonObject);
System.out.println(new Date().toLocaleString() + " redis 发布: " + mes);
jedis.publish("channel", mes);
}
private void publishTwo(int num) {
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("num", num);
jsonObject.addProperty("two", "two");
String mes = gson.toJson(jsonObject);
System.out.println(new Date().toLocaleString() + " redis 发布: " + mes);
jedis.publish("xxx", mes);
}
}
3. 订阅端代码
package org.jetlinks.community.mq;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import org.jetbrains.annotations.NotNull;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Scheduler;
import reactor.core.scheduler.Schedulers;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPubSub;
import java.io.IOException;
import java.util.Date;
import static org.jetlinks.community.mq.JedisConnectUtil.getRedisConnect;
public class RedisSubscribe extends JedisPubSub {
private static Jedis jedis = getRedisConnect().getResource();
private static Gson gson = new Gson();
private static RedisSubscribe redisSubscribe = new RedisSubscribe();
public static void main(String[] args) throws IOException {
start();
System.out.println("继续执行=====");
System.in.read();
}
public static void start() {
new Thread(() ->
jedis.subscribe(redisSubscribe, "channel")
).start();
}
@Override
public void onMessage(String channel, String message) {
System.out.println(new Date().toLocaleString() +
" 订阅 onMessage channel" + channel + " message : " + message+ " subNum: "+redisSubscribe.getSubscribedChannels());
JsonObject jsonObject = gson.fromJson(message, JsonObject.class);
int num = jsonObject.get("num").getAsInt();
/* System.out.println("解除不存在的 channel ==");
redisSubscribe.unsubscribe("channel");*/
if (num == 30) {
System.out.println(" 重新订阅 =========== " + num);
//redisSubscribe.unsubscribe(channel);
jedis.subscribe(redisSubscribe, "xxx");
jedis.subscribe(redisSubscribe, "channel");
} else if (num == 35) {
// getRedisConnect().getResource().close();
}
//super.onMessage(channel, message);
}
@Override
public void onSubscribe(String channel, int subscribedChannels) {
//RedisSubscribe redisSubscribe = new RedisSubscribe();
System.out.println(new Date().toLocaleString() + " 订阅 onSubscribe channel" + channel + " subscribedChannels : " + subscribedChannels);
// super.onSubscribe(channel, subscribedChannels);
//jedis.pubsubChannels().forEach(channel1 -> System.out.println("channel: " + channel1));
//redisSubscribe.jedis.pubsubChannels(channel).forEach(channel1 -> System.out.println("channel: " + channel1));
}
}
4. 解除订阅
- 解除订阅之前先判断该redis 是否有订阅,不然直接 unsubscribe() 会报错;
- 取消订阅只需要RedisSubscribe.unsubscribe() 就行;无需再 jedis.subscribe(redisSubscribe, channel);
- 当执行该代码后,目前看到的现象是还会消费 1-2 条消息;可能是在解除订阅的间隙过程中发送了到channel;
// 判断当前redis 订阅的channel
int subscribedNum = subscribe.getSubscribedChannels();
// 解除该channel 的订阅:
redisSubscribe.unsubscribe(channel);
// 解除全部订阅
redisSubscribe.unsubscribe();
// 查询所有订阅的topic
jedis.pubsubChannels();
5. 注意事项
目前项目上使用的是project.Reactor ,在使用jedis api 的过程中遇到一些问题如下:
- 在 执行 jedis.subscribe(redisSubscribe, channel); 后,会立即进入到 JedisPubSub 里边的 onSubscribe() / onMessage()方法,代码会立即陷入阻塞;如果有一些前端接口操作需将 jedis.subscribe(redisSubscribe, channel); 这一行做成异步操作;
private ExecutorService executor = Executors.newSingleThreadExecutor();
executor.submit(() -> jedis.subscribe(subscribe, redisChannels));
- Mono.fromRunnable() 后续需看下这个代码作用,是否有异步;
总结
- 在使用Jedis api 还是有些不清晰,导致在开发的过程中占用的大量时间调试问题;
- 在Project.Reactor api应用上也不甚熟悉,也缺乏正确资料,导致在异步的过程中尝试不成功,最后回到显示的线程池方案;