<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>

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

          共 1378字,需瀏覽 3分鐘

           ·

          2020-12-07 06:03

          程序員的成長之路
          互聯(lián)網(wǎng)/程序員/技術/資料共享?
          關注


          閱讀本文大概需要 5?分鐘。

          來自:juejin.im/post/6844904158227595271

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

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

          測試情況

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

          \======?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

          其實這里有些不嚴謹?benchmark?線程不應該在 Docker 容器內部運行 . 跑分的時候大概 benchmark 和 redis 主線程各自持有 50%CPU
          測試代碼如下:

          @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("開始設置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("設置完畢:?"?+?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);
          ????????}
          ????}
          }

          大概意思就是每小時 56 分的時候 , 會增加一批在接下來 16 小時過期的 key , 過期時間間隔 8 秒 , 且過期時間都在 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?+?"?,當前size:"?+?size?+?"?,滯后時間"?+?seconds);
          ????????????return?null;
          ????????});
          ????}
          }

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

          @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:當pool已經(jīng)達到max size的時候,如何處理新任務
          ????// CALLER\_RUNS:不在新線程中執(zhí)行任務,而是由調用者所在的線程來執(zhí)行
          ????executor.setRejectedExecutionHandler(new?ThreadPoolExecutor.CallerRunsPolicy());
          ????executor.initialize();
          ????RedisMessageListenerContainer?container?=?new?RedisMessageListenerContainer();
          ????//?設置Redis的連接工廠
          ????container.setConnectionFactory(connectionFactory);
          ????//?設置監(jiān)聽使用的線程池
          ????container.setTaskExecutor(executor);
          ????//?設置監(jiān)聽的Topic
          ????return?container;
          }

          設置 Redis 的過期監(jiān)聽 以及線程池信息 ,
          最后的測試結果是當 key 數(shù)量小于 1 萬的時候 , 基本上都可以在 10s 內完成過期通知 , 但是如果數(shù)量到 3 萬 , 就有部分 key 會延遲 120s . 順便貼一下我最新的日志。

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

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

          總結

          可能到這里 , 你會說 Redis 給你挖了一個大坑 , 但其實這些都在文檔上寫的明明白白。
          • 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.", 這兩個文章讀下來你會感覺 , 臥槽 Redis 的過期策略其實也挺'Low'的。

          推薦閱讀:

          BOSS直聘變BOSS直約:在線招嫖太荒唐

          哈哈,用Java打造一個有趣的表情生成器(附源碼)

          5T技術資源大放送!包括但不限于:C/C++,Linux,Python,Java,PHP,人工智能,單片機,樹莓派,等等。在公眾號內回復「2048」,即可免費獲?。?!

          微信掃描二維碼,關注我的公眾號

          朕已閱?

          瀏覽 115
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <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>
                    亚洲色情影视 | 亚洲一区无码视频 | 国产黄色视屏 | 成人91AV视频 | 四虎成人精品无码永久在线 |