• Redis 键空间事件通知


    出处: 使用Redis完成定时任务

    场景

      使用Java做过项目的人大概都用过定时器。一般来说,项目里订单模块和评论模块,都会涉及到定时任务执行。比如说:

    • 用户下订单后,需要在5分钟内完成支付,否则订单关闭;
    • 用户在完成订单后,如果没有评论,过一星期,系统自动评论,并完结。

    我以前曾经做过一个租车系统,其中订单的预约逻辑是这样的:

    1. 用户选择车辆并预约
    2. 后台系统开始计时
    3. 计时结束后,如果用户没有进行支付,则取消本次订单

     当时后台计时部分的技术,用的就是java中的定时器类Timer ,使用schedule来设置定时任务。虽然说功能实现了,但还是有很多问题,因为Timer本质上还是启动了一个线程来进行处理。当预约用户过多时,系统内存就会飙升,而且当发布新功能时,如果重启服务器,所有的定时器都会丢失。

    解决思路

    薛定谔解决法

      在订单信息中,加入过期时间,当用户查询订单或其他操作时,检查一下有没有过期的预订单,如果有,则进入逻辑处理。也就是说,当用户不进行操作时,这个预订单是不会自己结束的。这样做的好处在于,当系统重启时,这个订单的状态是不会收到影响的。坏处当然也显而易见,延迟率太高,主动权完全决定在用户手中。

    轮询法

      同样的,在订单信息中加入过期时间,后台启动一个定时线程,每隔一段时间遍历一次订单信息,如果有到期的,则结束订单。这种方法同样会影响系统性能。

    使用Redis定时器解决

    Redis定时器

      Redis中有一个expire命令,用来设置key的过期时间。使用发布订阅,可以接收到key的过期提醒,当key过期时,再执行取消订单的逻辑,就可以了。

    redis 定时器演示

    1
    2
    3
    4
    127.0.0.1:6379> set test tom EX 10
    OK
    127.0.0.1:6379> get test
    (nil)

    设置test(key)的过期时间为10秒,10秒过后key自动销毁。

    当然,仅仅有定时器还是不够的,接下来看redis的另一个功能,发布订阅。

    Redis发布订阅

    Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。

    Redis 客户端可以订阅任意数量的频道。

    用一张图来展示频道(channel1)与订阅者(client2, client5, client1)的关系:

    订阅

    当有消息发布时,他们的关系:

    发布

    消息经由频道广播到每个订阅者中。

    发布订阅演示:

    首先创建一个频道:

    1
    2
    3
    4
    5
    127.0.0.1:6379> SUBSCRIBE chat1
    Reading messages... (press Ctrl-C to quit)
    1) "subscribe"
    2) "chat1"
    3) (integer) 1

    此时控制台进入阻塞状态。

    开启2个控制台,分别订阅chat1频道

    1
    2
    3
    4
    5
    127.0.0.1:6379> PSUBSCRIBE chat1
    Reading messages... (press Ctrl-C to quit)
    1) "psubscribe"
    2) "chat1"
    3) (integer) 1

    这两个控制台也依次进入阻塞状态。

    再开一个控制台,进行信息的发布:

    1
    2
    3
    127.0.0.1:6379> PUBLISH chat1 "hello everyone!"
    (integer) 3
    127.0.0.1:6379>

    此时两个订阅者和一个频道创建者都分别收到了相同的内容 hello everyone!

    这里放上一张图片,效果可能会比较好:

    发消息之前

    发消息之前

    发消息之后

    发消息之后

    redis的key过期通知也是基于发布订阅模型的。不同的是订阅频道是固定的__keyevent@0__:expired,当然,redis还有好多类似与这种特定频道的通知,想了解更多,可以看这里Redis键空间通知

    Redis过期通知

    要使用Redis的过期通知功能,需要首先开启该功能 (2.8.0及以上的版本才有此功能)。

    在配置文件中加入如下语句:

    1
    notify-keyspace-events Ex

    控制台1订阅频道__keyevent@0__:expired

    1
    2
    3
    4
    5
    127.0.0.1:6379> PSUBSCRIBE __keyevent@0__:expired
    Reading messages... (press Ctrl-C to quit)
    1) "psubscribe"
    2) "__keyevent@0__:expired"
    3) (integer) 1

    控制台2存入一个key,并设置过期时间

    1
    2
    127.0.0.1:6379> set test tom EX 10
    OK

    当10秒过后,控制台1收到信息

    1
    2
    3
    4
    1) "pmessage"
    2) "__keyevent@0__:expired"
    3) "__keyevent@0__:expired"
    4) "test"

    使用springboot接收Redis过期通知

    首先在maven中配置redis
    1
    2
    3
    4
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>

    这个包是spring实现的redis client功能的包。

    在springboot配置文件中配置redis
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    #Redis配置
    #数据库索引,默认为0
    spring.redis.database=0
    #服务器地址,默认localhost
    spring.redis.host=localhost
    #端口,默认6379
    spring.redis.port=6379
    #密码,默认为空
    spring.redis.password=
    #连接池最大连接数,默认为8
    spring.redis.jedis.pool.max-active=8
    #连接池最大阻塞等待时间,使用负值表示没有限制
    spring.redis.jedis.pool.max-wait=-1
    #连接池最大空闲连接,默认为8
    spring.redis.jedis.pool.max-idle=8
    #连接池中的最小空闲连接,默认为0
    spring.redis.jedis.pool.min-idle=0
    #连接超时时间(毫秒)
    spring.redis.timeout=10
    代码实现

    首先写接收通知的处理方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    @Component
    public class RedisMessageReceiver {

    /**
    * 接收redis消息,并处理
    *
    * @param message 过期的redis key
    */
    public void receiveMessage(String message) {
    System.out.println("通知的key是:" + message);
    }

    }

    再写频道订阅的代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    @Configuration
    public class RedisConfig {

    /**
    * redis 订阅频道
    *
    * @param connectionFactory
    * @param listenerAdapter
    * @return
    */
    @Bean
    RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory,
    MessageListenerAdapter listenerAdapter) {

    RedisMessageListenerContainer container = new RedisMessageListenerContainer();
    container.setConnectionFactory(connectionFactory);
    // 订阅通道,key过期时通知
    container.addMessageListener(listenerAdapter, new PatternTopic("__keyevent@0__:expired"));
    // 可以订阅多个通道

    return container;
    }

    /**
    * 配置redis事件监听处理器
    *
    * @param receiver
    * @return
    */
    @Bean
    MessageListenerAdapter listenerAdapter(RedisMessageReceiver receiver) {
    return new MessageListenerAdapter(receiver, "receiveMessage");
    }

    }

    这样整个代码就完成了。我们来测试一下效果:

    使用控制台,新增一个key,并设置过期时间为10秒

    1
    2
    3
    127.0.0.1:6379> set testnofity xxx EX 10
    OK
    127.0.0.1:6379>

    切换到我们的程序中,可以在控制台看到如下信息:

    redis通知

      好了,现在我们就可以根据不同的key做不同的业务逻辑处理了。比如规定,所有订单的key,都必须以order-订单号的形式存入,这样,当接收到订单过期的通知时,就可以解析出订单信息,进一步处理了。

      当然,这个只是一种解决思路,你也可以使用一些其他的方式实现,比如说使用RabbitMQ消息队列实现。

  • 相关阅读:
    Vue浏览器调试工具VueTools安装以及使用
    克莱姆法则 学习
    IfcFacetedBrep —Example Basin faceted brep
    行列式学习
    matlab矩阵旋转任意角度的函数 imrotate
    matlab双杆系统的支撑反力 学习
    matlab矩阵运算——乘法、除法学习
    matlab求航线图问题 学习
    matlab范德蒙矩阵生成学习
    matlab特殊矩阵生成学习
  • 原文地址:https://www.cnblogs.com/myseries/p/11890206.html
Copyright © 2020-2023  润新知