<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          請(qǐng)勿過度依賴Redis的過期監(jiān)聽

          共 6647字,需瀏覽 14分鐘

           ·

          2020-12-24 22:43



          作者:迪殼

          https://juejin.im/post/6844904158227595271

          Redis 過期監(jiān)聽場(chǎng)景

          業(yè)務(wù)中有類似等待一定時(shí)間之后執(zhí)行某種行為的需求 , 比如 30 分鐘之后關(guān)閉訂單 . 網(wǎng)上有很多使用 Redis 過期監(jiān)聽的 Demo , 但是其實(shí)這是個(gè)大坑 , 因?yàn)?Redis 不能確保 key 在指定時(shí)間被刪除 , 也就造成了通知的延期 . 不多說 , 跑個(gè)測(cè)試

          測(cè)試情況

          先說環(huán)境 , redis 運(yùn)行在 Docker 容器中 , 分配了 一個(gè) cpu 以及 512MB 內(nèi)存, 在 Docker 中執(zhí)行?redis-benchmark -t set -r 100000 -n 1000000?結(jié)果如下:

          \====== SET ======
          1000000 requests completed in 171.03 seconds
          50 parallel clients
          3 bytes payload
          keep alive: 1
          host configuration "save": 3600 1 300 100 60 10000
          host configuration "appendonly": no
          multi-thread: no

          其實(shí)這里有些不嚴(yán)謹(jǐn)?benchmark?線程不應(yīng)該在 Docker 容器內(nèi)部運(yùn)行 . 跑分的時(shí)候大概?benchmark?和 redis 主線程各自持有 50%CPU

          測(cè)試代碼如下:

          @Service
          @Slf4j
          public class RedisJob {
          @Autowired
          private StringRedisTemplate stringRedisTemplate;

          public DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
          public LocalDateTime end = LocalDateTime.of(LocalDate.of(2020, 5, 12), LocalTime.of(8, 0));

          @Scheduled(cron = "0 56 \* \* \* ?")
          public void initKeys() {
          LocalDateTime now = LocalDateTime.now();
          ValueOperations operations = stringRedisTemplate.opsForValue();
          log.info("開始設(shè)置key");
          LocalDateTime begin = now.withMinute(0).withSecond(0).withNano(0);
          for (int i = 1; i < 17; i++) {
          setExpireKey(begin.plusHours(i), 8, operations);
          }
          log.info("設(shè)置完畢: " + Duration.between(now, LocalDateTime.now()));
          }

          private void setExpireKey(LocalDateTime expireTime, int step, ValueOperations operations) {
          LocalDateTime localDateTime = LocalDateTime.now().withNano(0);
          String nowTime = dateTimeFormatter.format(localDateTime);
          while (expireTime.getMinute() < 55) {
          operations.set(nowTime + "@" + dateTimeFormatter.format(expireTime), "A", Duration.between(expireTime, LocalDateTime.now()).abs());
          expireTime = expireTime.plusSeconds(step);
          }
          }
          }

          大概意思就是每小時(shí) 56 分的時(shí)候 , 會(huì)增加一批在接下來 16 小時(shí)過期的 key , 過期時(shí)間間隔 8 秒 , 且過期時(shí)間都在 55 分之前

          @Slf4j
          @Component
          public class RedisKeyExpirationListener extends KeyExpirationEventMessageListener {

          public RedisKeyExpirationListener(RedisMessageListenerContainer listenerContainer) {
          super(listenerContainer);
          }

          public DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
          @Autowired
          private StringRedisTemplate stringRedisTemplate;


          @Override
          public void onMessage(Message message, byte\[\] pattern) {
          String keyName = new String(message.getBody());
          LocalDateTime parse = LocalDateTime.parse(keyName.split("@")\[1\], dateTimeFormatter);
          long seconds = Duration.between(parse, LocalDateTime.now()).getSeconds();
          stringRedisTemplate.execute((RedisCallback) connection -> {
          Long size = connection.dbSize();
          log.info("過期key:" + keyName + " ,當(dāng)前size:" + size + " ,滯后時(shí)間" + seconds);
          return null;
          });
          }
          }

          這里是監(jiān)測(cè)到過期之后打印當(dāng)前的 dbSize 以及滯后時(shí)間

          @Bean
          public RedisMessageListenerContainer configRedisMessageListenerContainer(RedisConnectionFactory connectionFactory) {
          ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
          executor.setCorePoolSize(100);
          executor.setMaxPoolSize(100);
          executor.setQueueCapacity(100);
          executor.setKeepAliveSeconds(3600);
          executor.setThreadNamePrefix("redis");
          // rejection-policy:當(dāng)pool已經(jīng)達(dá)到max size的時(shí)候,如何處理新任務(wù)
          // CALLER\_RUNS:不在新線程中執(zhí)行任務(wù),而是由調(diào)用者所在的線程來執(zhí)行
          executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
          executor.initialize();
          RedisMessageListenerContainer container = new RedisMessageListenerContainer();
          // 設(shè)置Redis的連接工廠
          container.setConnectionFactory(connectionFactory);
          // 設(shè)置監(jiān)聽使用的線程池
          container.setTaskExecutor(executor);
          // 設(shè)置監(jiān)聽的Topic
          return container;
          }

          設(shè)置 Redis 的過期監(jiān)聽 以及線程池信息 ,

          最后的測(cè)試結(jié)果是當(dāng) key 數(shù)量小于 1 萬的時(shí)候 , 基本上都可以在 10s 內(nèi)完成過期通知 , 但是如果數(shù)量到 3 萬 , 就有部分 key 會(huì)延遲 120s . 順便貼一下我最新的日志

          2020-05-13 22:16:48.383  : 過期key:2020-05-13 11:56:02@2020-05-13 22:14:08 ,當(dāng)前size:57405 ,滯后時(shí)間160
          2020-05-13 22:16:49.389 : 過期key:2020-05-13 11:56:02@2020-05-13 22:14:32 ,當(dāng)前size:57404 ,滯后時(shí)間137
          2020-05-13 22:16:49.591 : 過期key:2020-05-13 10:56:02@2020-05-13 22:13:20 ,當(dāng)前size:57403 ,滯后時(shí)間209
          2020-05-13 22:16:50.093 : 過期key:2020-05-13 20:56:00@2020-05-13 22:12:32 ,當(dāng)前size:57402 ,滯后時(shí)間258
          2020-05-13 22:16:50.596 : 過期key:2020-05-13 07:56:03@2020-05-13 22:13:28 ,當(dāng)前size:57401 ,滯后時(shí)間202
          2020-05-13 22:16:50.697 : 過期key:2020-05-13 20:56:00@2020-05-13 22:14:32 ,當(dāng)前size:57400 ,滯后時(shí)間138
          2020-05-13 22:16:50.999 : 過期key:2020-05-13 19:56:00@2020-05-13 22:13:44 ,當(dāng)前size:57399 ,滯后時(shí)間186
          2020-05-13 22:16:51.199 : 過期key:2020-05-13 20:56:00@2020-05-13 22:14:40 ,當(dāng)前size:57398 ,滯后時(shí)間131
          2020-05-13 22:16:52.205 : 過期key:2020-05-13 15:56:01@2020-05-13 22:16:24 ,當(dāng)前size:57397 ,滯后時(shí)間28
          2020-05-13 22:16:52.808 : 過期key:2020-05-13 06:56:03@2020-05-13 22:15:04 ,當(dāng)前size:57396 ,滯后時(shí)間108
          2020-05-13 22:16:53.009 : 過期key:2020-05-13 06:56:03@2020-05-13 22:16:40 ,當(dāng)前size:57395 ,滯后時(shí)間13
          2020-05-13 22:16:53.110 : 過期key:2020-05-13 20:56:00@2020-05-13 22:14:56 ,當(dāng)前size:57394 ,滯后時(shí)間117
          2020-05-13 22:16:53.211 : 過期key:2020-05-13 06:56:03@2020-05-13 22:13:44 ,當(dāng)前size:57393 ,滯后時(shí)間189
          2020-05-13 22:16:53.613 : 過期key:2020-05-13 15:56:01@2020-05-13 22:12:24 ,當(dāng)前size:57392 ,滯后時(shí)間269
          2020-05-13 22:16:54.317 : 過期key:2020-05-13 15:56:01@2020-05-13 22:16:00 ,當(dāng)前size:57391 ,滯后時(shí)間54
          2020-05-13 22:16:54.517 : 過期key:2020-05-13 18:56:00@2020-05-13 22:15:44 ,當(dāng)前size:57390 ,滯后時(shí)間70
          2020-05-13 22:16:54.618 : 過期key:2020-05-13 21:56:00@2020-05-13 22:14:24 ,當(dāng)前size:57389 ,滯后時(shí)間150
          2020-05-13 22:16:54.819 : 過期key:2020-05-13 17:56:00@2020-05-13 22:14:40 ,當(dāng)前size:57388 ,滯后時(shí)間134
          2020-05-13 22:16:55.322 : 過期key:2020-05-13 10:56:02@2020-05-13 22:13:52 ,當(dāng)前size:57387 ,滯后時(shí)間183
          2020-05-13 22:16:55.423 : 過期key:2020-05-13 07:56:03@2020-05-13 22:14:16 ,當(dāng)前size:57386 ,滯后時(shí)間159

          可以看到 , 當(dāng)數(shù)量到達(dá) 5 萬的時(shí)候 , 大部分都已經(jīng)滯后了兩分鐘 , 對(duì)于業(yè)務(wù)方來說已經(jīng)完全無法忍受了

          總結(jié)

          可能到這里 , 你會(huì)說 Redis 給你挖了一個(gè)大坑 , 但其實(shí)這些都在文檔上寫的明明白白

          • How Redis expires keys:https://redis.io/commands/expire#how-redis-expires-keys

          • Timing of expired events:https://redis.io/topics/notifications#timing-of-expired-events

          尤其是在?Timing of expired events??中 , 明確的說明了 "Basically?expired?events?are generated when the Redis server deletes the key?and not when the time to live theoretically reaches the value of zero.", 這兩個(gè)文章讀下來你會(huì)感覺 ,? 臥槽 Redis 的過期策略其實(shí)也挺'Low'的

          其實(shí)公眾號(hào)看多了 , 你會(huì)發(fā)現(xiàn)大部分 Demo 都是互相抄來抄去 , 以及翻譯官方 Demo . 建議大家還是謹(jǐn)慎一些 , 真要使用的話 , 最好讀一下官方文檔 , 哪怕用百度翻譯也要有一些自己的理解 .

          文章比較枯燥 , 感謝大家耐心閱讀 ,? 如有建議 懇請(qǐng)留言.

          更多好文章

          1. Java高并發(fā)系列(共34篇)
          2. MySql高手系列(共27篇)
          3. Maven高手系列(共10篇)
          4. Mybatis系列(共12篇)
          5. 聊聊db和緩存一致性常見的實(shí)現(xiàn)方式
          6. 接口冪等性這么重要,它是什么?怎么實(shí)現(xiàn)?
          7. 泛型,有點(diǎn)難度,會(huì)讓很多人懵逼,那是因?yàn)槟銢]有看這篇文章!

          瀏覽 102
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                    <th id="afajh"><progress id="afajh"></progress></th>
                    亚欧自拍| 久久丫精品 | 中文字幕乱码人妻二区三区 | 999无码在线观看 | 欧美成人免费专区精品高清 |