• Redis - 订阅发布替换 Etcd 解决方案


    为了减轻项目的中间件臃肿,由于我们项目本身就应用了 Redis,正好 Redis 的也具备订阅发布监听的特性,正好应对 Etcd 的功能,所以本次给大家讲解如何使用 Redis 消息订阅发布来替代 Etcd 的解决方案。接下来,我们先看 Redis 订阅发布的常见情景……

    Redis 订阅发布公共类

    RedisConfig.java
    1. import com.fasterxml.jackson.annotation.JsonAutoDetect;
    2. import com.fasterxml.jackson.annotation.PropertyAccessor;
    3. import com.fasterxml.jackson.databind.ObjectMapper;
    4. import org.springframework.context.annotation.Bean;
    5. import org.springframework.context.annotation.ComponentScan;
    6. import org.springframework.context.annotation.Configuration;
    7. import org.springframework.data.redis.connection.RedisConnectionFactory;
    8. import org.springframework.data.redis.core.RedisTemplate;
    9. import org.springframework.data.redis.listener.RedisMessageListenerContainer;
    10. import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
    11. import org.springframework.data.redis.serializer.StringRedisSerializer;
    12. import java.net.UnknownHostException;
    13. @Configuration
    14. @ComponentScan({"cn.hutool.extra.spring"})
    15. public class RedisConfig {
    16. @Bean
    17. RedisMessageListenerContainer container (RedisConnectionFactory redisConnectionFactory){
    18. RedisMessageListenerContainer container = new RedisMessageListenerContainer();
    19. container.setConnectionFactory(redisConnectionFactory);
    20. return container;
    21. }
    22. @Bean
    23. public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
    24. RedisTemplate template = new RedisTemplate();
    25. // 连接工厂
    26. template.setConnectionFactory(redisConnectionFactory);
    27. // 序列化配置
    28. Jackson2JsonRedisSerializer objectJackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
    29. ObjectMapper objectMapper = new ObjectMapper();
    30. objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
    31. objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
    32. objectJackson2JsonRedisSerializer.setObjectMapper(objectMapper);
    33. StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
    34. // 配置具体序列化
    35. // key采用string的序列化方式
    36. template.setKeySerializer(stringRedisSerializer);
    37. // hash的key采用string的序列化方式
    38. template.setHashKeySerializer(stringRedisSerializer);
    39. // value序列化采用jackson
    40. template.setValueSerializer(objectJackson2JsonRedisSerializer);
    41. // hash的value序列化采用jackson
    42. template.setHashValueSerializer(objectJackson2JsonRedisSerializer);
    43. template.afterPropertiesSet();
    44. return template;
    45. }
    46. }
    RedisUtil.java
    1. import lombok.extern.slf4j.Slf4j;
    2. import org.springframework.data.redis.core.RedisTemplate;
    3. import org.springframework.stereotype.Component;
    4. import javax.annotation.Resource;
    5. @Slf4j
    6. @Component
    7. public class RedisUtil {
    8. @Resource
    9. private RedisTemplate redisTemplate;
    10. /**
    11. * 消息发送
    12. * @param topic 主题
    13. * @param message 消息
    14. */
    15. public void publish(String topic, String message) {
    16. redisTemplate.convertAndSend(topic, message);
    17. }
    18. }
    application.yml
    1. server:
    2. port: 7077
    3. spring:
    4. application:
    5. name: redis-demo
    6. redis:
    7. host: localhost
    8. timeout: 3000
    9. jedis:
    10. pool:
    11. max-active: 300
    12. max-idle: 100
    13. max-wait: 10000
    14. port: 6379
    RedisController.java
    1. import org.springframework.web.bind.annotation.*;
    2. import javax.annotation.Resource;
    3. /**
    4. * @author Lux Sun
    5. * @date 2023/9/12
    6. */
    7. @RestController
    8. @RequestMapping("/redis")
    9. public class RedisController {
    10. @Resource
    11. private RedisUtil redisUtil;
    12. @PostMapping
    13. public String publish(@RequestParam String topic, @RequestParam String msg) {
    14. redisUtil.publish(topic, msg);
    15. return "发送成功: " + topic + " - " + msg;
    16. }
    17. }

    一、业务情景:1 个消费者监听 1 个 Topic

    教程三步走(下文业务情景类似不再描述)
    1. 实现接口 MessageListener
    2. 消息订阅,绑定业务 Topic
    3. 重写 onMessage 消费者业务方法
    1. import lombok.extern.slf4j.Slf4j;
    2. import org.springframework.context.annotation.Bean;
    3. import org.springframework.data.redis.connection.Message;
    4. import org.springframework.data.redis.connection.MessageListener;
    5. import org.springframework.data.redis.listener.PatternTopic;
    6. import org.springframework.data.redis.listener.RedisMessageListenerContainer;
    7. import org.springframework.data.redis.listener.adapter.MessageListenerAdapter;
    8. import org.springframework.stereotype.Component;
    9. import javax.annotation.Resource;
    10. @Slf4j
    11. @Component
    12. public class RedisReceiver1 implements MessageListener {
    13. @Resource
    14. private RedisMessageListenerContainer container;
    15. /**
    16. * 重点关注这方法, 进行消息订阅
    17. */
    18. @PostConstruct
    19. public void init() {
    20. MessageListenerAdapter adapter = new MessageListenerAdapter(this);
    21. // 绑定 Topic 语法为正则表达式
    22. container.addMessageListener(adapter, new PatternTopic("topic1.*"));
    23. }
    24. @Override
    25. public void onMessage(Message message, byte[] bytes) {
    26. String key = new String(message.getChannel());
    27. String value = new String(message.getBody());
    28. log.info("Key: {}", key);
    29. log.info("Value: {}", value);
    30. }
    31. }
    测试
    1. curl --location '127.0.0.1:7077/redis' \
    2. --header 'Content-Type: application/x-www-form-urlencoded' \
    3. --data-urlencode 'topic=topic1.msg' \
    4. --data-urlencode 'msg=我是消息1'
    结果
    1. 2023-11-15 10:22:38.445 INFO 59189 --- [ container-2] com.xxx.redis.demo.RedisReceiver1 : Key: topic1.msg
    2. 2023-11-15 10:22:38.445 INFO 59189 --- [ container-2] com.xxx.redis.demo.RedisReceiver1 : Value: "我是消息1"

    二、业务情景:1 个消费者监听 N 个 Topic

    1. import lombok.extern.slf4j.Slf4j;
    2. import org.springframework.data.redis.connection.Message;
    3. import org.springframework.data.redis.connection.MessageListener;
    4. import org.springframework.data.redis.listener.PatternTopic;
    5. import org.springframework.data.redis.listener.RedisMessageListenerContainer;
    6. import org.springframework.data.redis.listener.adapter.MessageListenerAdapter;
    7. import org.springframework.stereotype.Component;
    8. import javax.annotation.PostConstruct;
    9. import javax.annotation.Resource;
    10. @Slf4j
    11. @Component
    12. public class RedisReceiver1 implements MessageListener {
    13. @Resource
    14. private RedisMessageListenerContainer container;
    15. /**
    16. * 重点关注这方法, 进行消息订阅
    17. */
    18. @PostConstruct
    19. public void init() {
    20. MessageListenerAdapter adapter = new MessageListenerAdapter(this);
    21. // 绑定 Topic 语法为正则表达式
    22. container.addMessageListener(adapter, new PatternTopic("topic1.*"));
    23. // 只需再绑定业务 Topic 即可
    24. container.addMessageListener(adapter, new PatternTopic("topic2.*"));
    25. }
    26. @Override
    27. public void onMessage(Message message, byte[] bytes) {
    28. String key = new String(message.getChannel());
    29. String value = new String(message.getBody());
    30. log.info("Key: {}", key);
    31. log.info("Value: {}", value);
    32. }
    33. }
    测试
    1. curl --location '127.0.0.1:7077/redis' \
    2. --header 'Content-Type: application/x-www-form-urlencoded' \
    3. --data-urlencode 'topic=topic2.msg' \
    4. --data-urlencode 'msg=我是消息2'
    结果
    1. 2023-11-15 10:22:38.445 INFO 59189 --- [ container-2] com.xxx.redis.demo.RedisReceiver1 : Key: topic2.msg
    2. 2023-11-15 10:22:38.445 INFO 59189 --- [ container-2] com.xxx.redis.demo.RedisReceiver1 : Value: "我是消息2"

    三、业务情景:N 个消费者监听 1 个 Topic

    我们看一下,现在又新增一个 RedisReceiver2,按理讲测试的时候,RedisReceiver1 和 RedisReceiver2 会同时收到消息

    1. import lombok.extern.slf4j.Slf4j;
    2. import org.springframework.data.redis.connection.Message;
    3. import org.springframework.data.redis.connection.MessageListener;
    4. import org.springframework.data.redis.listener.PatternTopic;
    5. import org.springframework.data.redis.listener.RedisMessageListenerContainer;
    6. import org.springframework.data.redis.listener.adapter.MessageListenerAdapter;
    7. import org.springframework.stereotype.Component;
    8. import javax.annotation.PostConstruct;
    9. import javax.annotation.Resource;
    10. @Slf4j
    11. @Component
    12. public class RedisReceiver2 implements MessageListener {
    13. @Resource
    14. private RedisMessageListenerContainer container;
    15. /**
    16. * 重点关注这方法, 进行消息订阅
    17. */
    18. @PostConstruct
    19. public void init() {
    20. MessageListenerAdapter adapter = new MessageListenerAdapter(this);
    21. // 绑定 Topic 语法为正则表达式
    22. container.addMessageListener(adapter, new PatternTopic("topic1.*"));
    23. }
    24. @Override
    25. public void onMessage(Message message, byte[] bytes) {
    26. String key = new String(message.getChannel());
    27. String value = new String(message.getBody());
    28. log.info("Key: {}", key);
    29. log.info("Value: {}", value);
    30. }
    31. }
    测试
    1. curl --location '127.0.0.1:7077/redis' \
    2. --header 'Content-Type: application/x-www-form-urlencoded' \
    3. --data-urlencode 'topic=topic1.msg' \
    4. --data-urlencode 'msg=我是消息1'
    结果
    1. 2023-11-15 10:22:38.445 INFO 59189 --- [ container-2] com.xxx.redis.demo.RedisReceiver1 : Key: topic1.msg
    2. 2023-11-15 10:22:38.449 INFO 59189 --- [ container-3] com.xxx.redis.demo.RedisReceiver2 : Key: topic1.msg
    3. 2023-11-15 10:22:38.545 INFO 59189 --- [ container-2] com.xxx.redis.demo.RedisReceiver1 : Value: "我是消息1"
    4. 2023-11-15 10:22:38.645 INFO 59189 --- [ container-3] com.xxx.redis.demo.RedisReceiver2 : Value: "我是消息1"

    四、业务情景:N 个消费者监听 N 个 Topic

    都到这阶段了,应该不难理解了吧~

    1. import lombok.extern.slf4j.Slf4j;
    2. import org.springframework.data.redis.connection.Message;
    3. import org.springframework.data.redis.connection.MessageListener;
    4. import org.springframework.data.redis.listener.PatternTopic;
    5. import org.springframework.data.redis.listener.RedisMessageListenerContainer;
    6. import org.springframework.data.redis.listener.adapter.MessageListenerAdapter;
    7. import org.springframework.stereotype.Component;
    8. import javax.annotation.PostConstruct;
    9. import javax.annotation.Resource;
    10. @Slf4j
    11. @Component
    12. public class RedisReceiver2 implements MessageListener {
    13. @Resource
    14. private RedisMessageListenerContainer container;
    15. /**
    16. * 重点关注这方法, 进行消息订阅
    17. */
    18. @PostConstruct
    19. public void init() {
    20. MessageListenerAdapter adapter = new MessageListenerAdapter(this);
    21. // 绑定 Topic 语法为正则表达式
    22. container.addMessageListener(adapter, new PatternTopic("topic1.*"));
    23. // 只需再绑定业务 Topic 即可
    24. container.addMessageListener(adapter, new PatternTopic("topic2.*"));
    25. }
    26. @Override
    27. public void onMessage(Message message, byte[] bytes) {
    28. String key = new String(message.getChannel());
    29. String value = new String(message.getBody());
    30. log.info("Key: {}", key);
    31. log.info("Value: {}", value);
    32. }
    33. }
    测试
    1. curl --location '127.0.0.1:7077/redis' \
    2. --header 'Content-Type: application/x-www-form-urlencoded' \
    3. --data-urlencode 'topic=topic2.msg' \
    4. --data-urlencode 'msg=我是消息2'
    结果
    1. 2023-11-15 10:22:38.445 INFO 59189 --- [ container-2] com.xxx.redis.demo.RedisReceiver1 : Key: topic2.msg
    2. 2023-11-15 10:22:38.449 INFO 59189 --- [ container-3] com.xxx.redis.demo.RedisReceiver2 : Key: topic2.msg
    3. 2023-11-15 10:22:38.545 INFO 59189 --- [ container-2] com.xxx.redis.demo.RedisReceiver1 : Value: "我是消息2"
    4. 2023-11-15 10:22:38.645 INFO 59189 --- [ container-3] com.xxx.redis.demo.RedisReceiver2 : Value: "我是消息2"

    好了,Redis 订阅发布的教程到此为止。接下来,我们看下如何用它来替代 Etcd 的业务情景?

    这之前,我们先大概聊下 Etcd 的 2 个要点:

    1. Etcd 消息事件类型
    2. Etcd 持久层数据

    那么问题来了,Redis 虽然具备基本的消息订阅发布,但是如何契合 Etcd 的这 2 点特性,我们目前给出对应的解决方案是:

    1. 使用 Redis K-V 的 value 作为 Etcd 消息事件类型
    2. 使用 MySQL 作为 Etcd 持久层数据:字段 id 随机 UUID、字段 key 对应 Etcd key、字段 value 对应 Etcd value,这样做的一个好处是无需重构数据结构
    1. SET NAMES utf8mb4;
    2. SET FOREIGN_KEY_CHECKS = 0;
    3. DROP TABLE IF EXISTS `t_redis_msg`;
    4. CREATE TABLE `t_redis_msg` (
    5. `id` varchar(32) NOT NULL,
    6. `key` varchar(255) NOT NULL,
    7. `value` longtext,
    8. PRIMARY KEY (`id`)
    9. ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
    10. SET FOREIGN_KEY_CHECKS = 1;

    所以,如果想平替 Etcd 的事件类型和持久层数据的解决方案需要 MySQL & Redis 结合,接下来直接上代码……

    Redis & MySQL 整合

    application.yml(升级)
    1. spring:
    2. application:
    3. name: redis-demo
    4. datasource:
    5. username: root
    6. password: 123456
    7. url: jdbc:mysql://localhost:3306/db_demo?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
    8. driver-class-name: com.mysql.cj.jdbc.Driver
    9. hikari:
    10. connection-test-query: SELECT 1
    11. idle-timeout: 40000
    12. max-lifetime: 1880000
    13. connection-timeout: 40000
    14. minimum-idle: 1
    15. validation-timeout: 60000
    16. maximum-pool-size: 20
    RedisMsg.java
    1. import com.baomidou.mybatisplus.annotation.IdType;
    2. import com.baomidou.mybatisplus.annotation.TableField;
    3. import com.baomidou.mybatisplus.annotation.TableId;
    4. import com.baomidou.mybatisplus.annotation.TableName;
    5. import lombok.AllArgsConstructor;
    6. import lombok.Data;
    7. import lombok.NoArgsConstructor;
    8. import lombok.experimental.SuperBuilder;
    9. /**
    10. * @author Lux Sun
    11. * @date 2021/2/19
    12. */
    13. @Data
    14. @SuperBuilder
    15. @NoArgsConstructor
    16. @AllArgsConstructor
    17. @TableName(value = "t_redis_msg", autoResultMap = true)
    18. public class RedisMsg {
    19. @TableId(type = IdType.ASSIGN_UUID)
    20. private String id;
    21. @TableField(value = "`key`")
    22. private String key;
    23. private String value;
    24. }
    RedisMsgEnum.java
    1. /**
    2. * @author Lux Sun
    3. * @date 2022/11/11
    4. */
    5. public enum RedisMsgEnum {
    6. PUT("PUT"),
    7. DEL("DEL");
    8. private String code;
    9. RedisMsgEnum(String code) {
    10. this.code = code;
    11. }
    12. public String getCode() {
    13. return code;
    14. }
    15. }
    RedisMsgService.java
    1. import com.baomidou.mybatisplus.extension.service.IService;
    2. import java.util.List;
    3. import java.util.Map;
    4. /**
    5. * @author Lux Sun
    6. * @date 2020/6/16
    7. */
    8. public interface RedisMsgService extends IService {
    9. /**
    10. * 获取消息
    11. * @param key
    12. */
    13. RedisMsg get(String key);
    14. /**
    15. * 获取消息列表
    16. * @param key
    17. */
    18. Map map(String key);
    19. /**
    20. * 获取消息值
    21. * @param key
    22. */
    23. String getValue(String key);
    24. /**
    25. * 获取消息列表
    26. * @param key
    27. */
    28. List list(String key);
    29. /**
    30. * 插入消息
    31. * @param key
    32. * @param value
    33. */
    34. void put(String key, String value);
    35. /**
    36. * 删除消息
    37. * @param key
    38. */
    39. void del(String key);
    40. }
    RedisMsgServiceImpl.java
    1. import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
    2. import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    3. import lombok.extern.slf4j.Slf4j;
    4. import org.springframework.stereotype.Service;
    5. import org.springframework.transaction.annotation.Propagation;
    6. import org.springframework.transaction.annotation.Transactional;
    7. import javax.annotation.Resource;
    8. import java.util.List;
    9. import java.util.Map;
    10. import java.util.stream.Collectors;
    11. /**
    12. * @author Lux Sun
    13. * @date 2020/6/16
    14. */
    15. @Slf4j
    16. @Service
    17. public class RedisMsgServiceImpl extends ServiceImpl implements RedisMsgService {
    18. @Resource
    19. private RedisMsgDao redisMsgDao;
    20. @Resource
    21. private RedisUtil redisUtil;
    22. /**
    23. * 获取消息
    24. *
    25. * @param key
    26. */
    27. @Override
    28. public RedisMsg get(String key) {
    29. LambdaQueryWrapper lqw = new LambdaQueryWrapper<>();
    30. lqw.eq(RedisMsg::getKey, key);
    31. return redisMsgDao.selectOne(lqw);
    32. }
    33. /**
    34. * 获取消息列表
    35. *
    36. * @param key
    37. */
    38. @Override
    39. public Map map(String key) {
    40. List redisMsgs = this.list(key);
    41. return redisMsgs.stream().collect(Collectors.toMap(RedisMsg::getKey, RedisMsg::getValue));
    42. }
    43. /**
    44. * 获取消息值
    45. *
    46. * @param key
    47. */
    48. @Override
    49. public String getValue(String key) {
    50. RedisMsg redisMsg = this.get(key);
    51. return redisMsg.getValue();
    52. }
    53. /**
    54. * 获取消息列表
    55. *
    56. * @param key
    57. */
    58. @Override
    59. public List list(String key) {
    60. LambdaQueryWrapper lqw = new LambdaQueryWrapper<>();
    61. lqw.likeRight(RedisMsg::getKey, key);
    62. return redisMsgDao.selectList(lqw);
    63. }
    64. /**
    65. * 插入消息
    66. *
    67. * @param key
    68. * @param value
    69. */
    70. @Override
    71. public void put(String key, String value) {
    72. log.info("开始添加 - key: {},value: {}", key, value);
    73. LambdaQueryWrapper lqw = new LambdaQueryWrapper<>();
    74. lqw.eq(RedisMsg::getKey, key);
    75. this.saveOrUpdate(RedisMsg.builder().key(key).value(value).build(), lqw);
    76. redisUtil.putMsg(key);
    77. log.info("添加成功 - key: {},value: {}", key, value);
    78. }
    79. /**
    80. * 删除消息
    81. *
    82. * @param key
    83. */
    84. @Override
    85. public void del(String key) {
    86. log.info("开始删除 - key: {}", key);
    87. LambdaQueryWrapper lqw = new LambdaQueryWrapper<>();
    88. lqw.likeRight(RedisMsg::getKey, key);
    89. redisMsgDao.delete(lqw);
    90. redisUtil.delMsg(key);
    91. log.info("删除成功 - key: {}", key);
    92. }
    93. }
    RedisUtil.java(升级)
    1. import lombok.extern.slf4j.Slf4j;
    2. import org.springframework.data.redis.core.RedisTemplate;
    3. import org.springframework.stereotype.Component;
    4. import javax.annotation.Resource;
    5. @Slf4j
    6. @Component
    7. public class RedisUtil {
    8. @Resource
    9. private RedisTemplate redisTemplate;
    10. /**
    11. * 消息发送
    12. * @param topic 主题
    13. * @param message 消息
    14. */
    15. public void publish(String topic, String message) {
    16. redisTemplate.convertAndSend(topic, message);
    17. }
    18. /**
    19. * 消息发送 PUT
    20. * @param topic 主题
    21. */
    22. public void putMsg(String topic) {
    23. redisTemplate.convertAndSend(topic, RedisMsgEnum.PUT);
    24. }
    25. /**
    26. * 消息发送 DELETE
    27. * @param topic 主题
    28. */
    29. public void delMsg(String topic) {
    30. redisTemplate.convertAndSend(topic, RedisMsgEnum.DEL);
    31. }
    32. }

    演示 DEMO

    RedisMsgController.java
    1. import org.springframework.web.bind.annotation.PostMapping;
    2. import org.springframework.web.bind.annotation.RequestMapping;
    3. import org.springframework.web.bind.annotation.RequestParam;
    4. import org.springframework.web.bind.annotation.RestController;
    5. import javax.annotation.Resource;
    6. /**
    7. * @author Lux Sun
    8. * @date 2023/9/12
    9. */
    10. @RestController
    11. @RequestMapping("/redisMsg")
    12. public class RedisMsgController {
    13. @Resource
    14. private RedisMsgService redisMsgService;
    15. @PostMapping
    16. public String publish(@RequestParam String topic, @RequestParam String msg) {
    17. redisMsgService.put(topic, msg);
    18. return "发送成功: " + topic + " - " + msg;
    19. }
    20. }
    RedisMsgReceiver.java
    1. import lombok.extern.slf4j.Slf4j;
    2. import org.springframework.data.redis.connection.Message;
    3. import org.springframework.data.redis.connection.MessageListener;
    4. import org.springframework.data.redis.listener.PatternTopic;
    5. import org.springframework.data.redis.listener.RedisMessageListenerContainer;
    6. import org.springframework.data.redis.listener.adapter.MessageListenerAdapter;
    7. import org.springframework.stereotype.Component;
    8. import javax.annotation.PostConstruct;
    9. import javax.annotation.Resource;
    10. @Slf4j
    11. @Component
    12. public class RedisMsgReceiver implements MessageListener {
    13. @Resource
    14. private RedisMsgService redisMsgService;
    15. @Resource
    16. private RedisMessageListenerContainer container;
    17. @PostConstruct
    18. public void init() {
    19. MessageListenerAdapter adapter = new MessageListenerAdapter(this);
    20. container.addMessageListener(adapter, new PatternTopic("topic3.*"));
    21. }
    22. @Override
    23. public void onMessage(Message message, byte[] bytes) {
    24. String key = new String(message.getChannel());
    25. String event = new String(message.getBody());
    26. String value = redisMsgService.getValue(key);
    27. log.info("Key: {}", key);
    28. log.info("Event: {}", event);
    29. log.info("Value: {}", value);
    30. }
    31. }
    测试
    1. curl --location '127.0.0.1:7077/redisMsg' \
    2. --header 'Content-Type: application/x-www-form-urlencoded' \
    3. --data-urlencode 'topic=topic3.msg' \
    4. --data-urlencode 'msg=我是消息3'
    结果
    1. 2023-11-16 10:24:35.721 INFO 43794 --- [nio-7077-exec-1] c.c.redis.demo.RedisMsgServiceImpl : 开始添加 - key: topic3.msg,value: 我是消息3
    2. 2023-11-16 10:24:35.935 INFO 43794 --- [nio-7077-exec-1] c.c.redis.demo.RedisMsgServiceImpl : 添加成功 - key: topic3.msg,value: 我是消息3
    3. 2023-11-16 10:24:35.950 INFO 43794 --- [ container-2] c.xxx.redis.demo.RedisMsgReceiver : Key: topic3.msg
    4. 2023-11-16 10:24:35.950 INFO 43794 --- [ container-2] c.xxx.redis.demo.RedisMsgReceiver : Event: "PUT"
    5. 2023-11-16 10:24:35.950 INFO 43794 --- [ container-2] c.xxx.redis.demo.RedisMsgReceiver : Value: 我是消息3

  • 相关阅读:
    magic API构建和基础实现
    [RK3588-Android12] 双HDMI+喇叭Speak同音问题
    叛乱沙漠风暴server安装 ubuntu 22.04
    Qt之Windows Server 2012 R2不支持openssl
    IE让我首次遭受了社会的毒打
    Tkinter常用功能示例(一)
    C++数据结构补充(线性表及其顺序存储结构)
    lsblk 硬盘属性查看
    vue Router从入门到精通
    牛客day 8
  • 原文地址:https://blog.csdn.net/Dream_Weave/article/details/134436221