最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Redis監(jiān)聽過期的key實現(xiàn)流程詳解

 更新時間:2023年02月28日 09:48:12   作者:嘉禾嘉寧papa  
本文主要介紹了Redis監(jiān)聽key的過期時間,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧

一、簡介

  本文今天主要是講Redis中對過期key的監(jiān)聽,可能很多小伙伴不會,或者使用會出現(xiàn)一些不可思議的問題,比如在系統(tǒng)中設(shè)置了一個緩存,希望在緩存失效后去做什么操作,但是實際中可能又出現(xiàn)了操作重復(fù)的問題。所以今天來討論下怎么正確使用。我們來個最簡單的集群架構(gòu),如下圖:

  我們上面圖中看到是服務(wù)A和服務(wù)B就是同一個服務(wù)的不同實例。

二、maven依賴

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.0</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.alian</groupId>
    <artifactId>expiration</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>expiration</name>
    <description>redis-key-expiration-listener</description>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <project.package.directory>target</project.package.directory>
        <java.version>1.8</java.version>
        <!--com.fasterxml.jackson 版本-->
        <jackson.version>2.9.10</jackson.version>
        <!--阿里巴巴fastjson 版本-->
        <fastjson.version>1.2.68</fastjson.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
        <!--redis依賴-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
            <version>${parent.version}</version>
        </dependency>
        <!--用于序列化-->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>${jackson.version}</version>
        </dependency>
        <!--java 8時間序列化-->
        <dependency>
            <groupId>com.fasterxml.jackson.datatype</groupId>
            <artifactId>jackson-datatype-jsr310</artifactId>
            <version>${jackson.version}</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.68</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.16.14</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.2</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

三、編碼實現(xiàn)

3.1、application.properties

# 端口
server.port=8090
# 上下文路徑
server.servlet.context-path=/expiration

# Redis數(shù)據(jù)庫索引(默認(rèn)為0)
spring.redis.database=0
# Redis服務(wù)器地址
spring.redis.host=192.168.0.193
#spring.redis.host=127.0.0.1
# Redis服務(wù)器連接端口
spring.redis.port=6379
# Redis服務(wù)器連接密碼(默認(rèn)為空)
spring.redis.password=
# 連接池最大連接數(shù)(使用負(fù)值表示沒有限制)
spring.redis.jedis.pool.max-active=20
# 連接池中的最小空閑連接
spring.redis.jedis.pool.min-idle=10
# 連接池中的最大空閑連接
spring.redis.jedis.pool.max-idle=10
# 連接池最大阻塞等待時間(使用負(fù)值表示沒有限制)
spring.redis.jedis.pool.max-wait=20000
# 讀時間(毫秒)
spring.redis.timeout=10000
# 連接超時時間(毫秒)
spring.redis.connect-timeout=10000

3.2、Redis配置類

RedisConfig

package com.alian.expiration.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
@Configuration
public class RedisConfig {
    /**
     * redis配置
     *
     * @param redisConnectionFactory
     * @return
     */
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        // 實例化redisTemplate
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        //設(shè)置連接工廠
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        // key采用String的序列化
        redisTemplate.setKeySerializer(keySerializer());
        // value采用jackson序列化
        redisTemplate.setValueSerializer(valueSerializer());
        // Hash key采用String的序列化
        redisTemplate.setHashKeySerializer(keySerializer());
        // Hash value采用jackson序列化
        redisTemplate.setHashValueSerializer(valueSerializer());
        // 支持事務(wù)
        // redisTemplate.setEnableTransactionSupport(true);
        //執(zhí)行函數(shù),初始化RedisTemplate
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }
    /**
     * key類型采用String序列化
     *
     * @return
     */
    private RedisSerializer<String> keySerializer() {
        return new StringRedisSerializer();
    }
    /**
     * value采用JSON序列化
     *
     * @return
     */
    private RedisSerializer<Object> valueSerializer() {
        //設(shè)置jackson序列化
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        //設(shè)置序列化對象
        jackson2JsonRedisSerializer.setObjectMapper(getMapper());
        return jackson2JsonRedisSerializer;
    }
    /**
     * 使用com.fasterxml.jackson.databind.ObjectMapper
     * 對數(shù)據(jù)進行處理包括java8里的時間
     *
     * @return
     */
    private ObjectMapper getMapper() {
        ObjectMapper mapper = new ObjectMapper();
        //設(shè)置可見性
        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        //默認(rèn)鍵入對象
        mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        //設(shè)置Java 8 時間序列化
        JavaTimeModule timeModule = new JavaTimeModule();
        timeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        timeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
        timeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern("HH:mm:ss")));
        timeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        timeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
        timeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern("HH:mm:ss")));
        //禁用把時間轉(zhuǎn)為時間戳
        mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
        mapper.registerModule(timeModule);
        return mapper;
    }
    @Bean
    RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory) {
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(connectionFactory);
        return container;
    }
}

  和我們之前整合redis差不多,只不過在最后增加了一個redis消息監(jiān)聽監(jiān)聽容器RedisMessageListenerContainer

3.3、監(jiān)聽器

RedisKeyExpirationListener

package com.alian.expiration.listener;
import com.alian.expiration.service.RedisExpirationService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.listener.KeyExpirationEventMessageListener;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class RedisKeyExpirationListener extends KeyExpirationEventMessageListener {
    @Autowired
    private RedisExpirationService redisExpirationService;
	// 把我們上面一步配置的bean注入進去
    public RedisKeyExpirationListener(RedisMessageListenerContainer listenerContainer) {
        super(listenerContainer);
    }
    /**
     * 針對redis數(shù)據(jù)失效事件,進行數(shù)據(jù)處理
     *
     * @param message
     * @param pattern
     */
    @Override
    public void onMessage(Message message, byte[] pattern) {
        // 用戶做自己的業(yè)務(wù)處理即可,注意message.toString()可以獲取失效的key
        String expiredKey = message.toString();
        log.info("onMessage --> redis 過期的key是:{}", expiredKey);
        try {
            // 對過期key進行處理
            redisExpirationService.processingExpiredKey(expiredKey);
            log.info("過期key處理完成:{}", expiredKey);
        } catch (Exception e) {
            e.printStackTrace();
            log.error("處理redis 過期的key異常:{}", expiredKey, e);
        }
    }
}

  實現(xiàn)的步驟如下:

  • 繼承KeyExpirationEventMessageListener
  • 把redis消息監(jiān)聽監(jiān)聽容器RedisMessageListenerContainer 注入到密鑰空間事件消息偵 聽器中
  • 重寫onMessage方法
  • 通過Message 的 toString() 方法就可以獲取到過期的key
  • 對key中關(guān)鍵信息進行業(yè)務(wù)處理,比如 id

3.4、服務(wù)類

RedisExpirationService

package com.alian.expiration.service;
import com.alian.expiration.util.SignUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Slf4j
@Service
public class RedisExpirationService {
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    public void processingExpiredKey(String expiredKey) {
        // 如果是優(yōu)惠券的key(一定要規(guī)范命名)
        if (expiredKey.startsWith("com.mall.coupon.id")) {
            // 臨時key,此key可以在業(yè)務(wù)處理完,然后延遲一定時間刪除,或者不處理
            String tempKey = SignUtils.md5(expiredKey, "UTF-8");
            // 臨時key不存在才設(shè)置值,key超時時間為10秒(此處相當(dāng)于分布式鎖的應(yīng)用)
            Boolean exist = redisTemplate.opsForValue().setIfAbsent(tempKey, "1", 10, TimeUnit.SECONDS);
            if (Boolean.TRUE.equals(exist)) {
                log.info("Business Handing...");
                // 比如截取里面的id,然后關(guān)聯(lián)數(shù)據(jù)庫進行處理
            } else {
                log.info("Other service is handing...");
            }
        } else {
            log.info("Expired keys without processing");
        }
    }
}

  基本流程如下:

  • 判斷是否是需要處理的key,一般這種key通過命名規(guī)范加以處理
  • 以當(dāng)前key生成一個新的key作為分布式key
  • 如果redis中不存在這個新的key,則為新的key設(shè)置一個值,達到分布式服務(wù)處理(核心)
  • 設(shè)置成功的,進行業(yè)務(wù)處理;設(shè)置失敗了,說明其他服務(wù)正在處理這個key
  • 根據(jù) key 的關(guān)鍵信息(比如截取id),進行業(yè)務(wù)處理

3.5、工具類

SignUtils

package com.alian.expiration.util;
import java.security.MessageDigest;
public class SignUtils {
    public static final String md5(String s, String charset) {
        char[] hexDigits = new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
        try {
            byte[] btInput = s.getBytes(charset);
            MessageDigest mdInst = MessageDigest.getInstance("MD5");
            mdInst.update(btInput);
            byte[] md = mdInst.digest();
            int j = md.length;
            char[] str = new char[j * 2];
            int k = 0;
            for (byte byte0 : md) {
                str[k++] = hexDigits[byte0 >>> 4 & 15];
                str[k++] = hexDigits[byte0 & 15];
            }
            return new String(str);
        } catch (Exception var11) {
            return "";
        }
    }
}

四、測試

4.1、測試類

  簡單模擬下發(fā)送一個優(yōu)惠券數(shù)據(jù)到redis,然后設(shè)置超時時間

package com.alian.expiration;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@Slf4j
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class RedisKeyExpirationTest {
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    @Test
    public void keyExpiration() {
        // 優(yōu)惠券信息
        String id = "2023021685264735";
        Map<String, String> map = new HashMap<>();
        map.put("id", id);
        map.put("amount", "1000");
        map.put("type", "1001");
        map.put("describe", "滿減紅包");
        // 緩存到redis
        redisTemplate.opsForHash().putAll("com.mall.coupon.id." + id, map);
        // 設(shè)置過期時間
        redisTemplate.expire("com.mall.coupon.id." + id, 10, TimeUnit.SECONDS);
    }
}

4.2、單實例

  單實例就是服務(wù)只部署了一份,我們啟動一份,端口是8090,然后通過上面的測試類,發(fā)送一個消息,結(jié)果如下:

10:23:39 701 INFO [container-2]:onMessage --> redis 過期的key是:com.mall.coupon.id.2023021685264735
10:23:39 988 INFO [container-2]:Business Handing...
10:23:39 989 INFO [container-2]:過期key處理完成:com.mall.coupon.id.2023021685264735
10:23:50 005 INFO [container-3]:onMessage --> redis 過期的key是:450FCC35415BADC16805962CA5BC7E12
10:23:50 005 INFO [container-3]:Expired keys without processing
10:23:50 005 INFO [container-3]:過期key處理完成:450FCC35415BADC16805962CA5BC7E12

4.3、多實例

  多實例就是服務(wù)部署了多份,比如我們啟動兩份,端口分別為8090和8091,然后通過上面的測試類,發(fā)送一個消息,8090端口的服務(wù)結(jié)果如下(Business Handing…):

11:39:06 691 INFO [container-2]:onMessage --> redis 過期的key是:com.mall.coupon.id.2023021685264735
11:39:06 707 INFO [container-2]:Business Handing...
11:39:06 707 INFO [container-2]:過期key處理完成:com.mall.coupon.id.2023021685264735
11:39:16 796 INFO [container-3]:onMessage --> redis 過期的key是:450FCC35415BADC16805962CA5BC7E12
11:39:16 796 INFO [container-3]:Expired keys without processing
11:39:16 796 INFO [container-3]:過期key處理完成:450FCC35415BADC16805962CA5BC7E12

  8091端口的服務(wù)結(jié)果如下(Other service is handing…):

11:39:06 691 INFO [container-2]:onMessage --> redis 過期的key是:com.mall.coupon.id.2023021685264735
11:39:06 707 INFO [container-2]:Other service is handing...
11:39:06 707 INFO [container-2]:過期key處理完成:com.mall.coupon.id.2023021685264735
11:39:16 796 INFO [container-3]:onMessage --> redis 過期的key是:450FCC35415BADC16805962CA5BC7E12
11:39:16 796 INFO [container-3]:Expired keys without processing
11:39:16 796 INFO [container-3]:過期key處理完成:450FCC35415BADC16805962CA5BC7E12

  結(jié)果分析:

  • 多實例的情況下,每個實例都會收到過期key通知
  • 通過redis分布式鎖,實現(xiàn)只有一個實例會進行業(yè)務(wù)處理,防止重復(fù)
  • 使用分布式鎖會有一個新的key過期,并且收到該key的通知,你可以業(yè)務(wù)執(zhí)行完延遲一定時間(避免重復(fù)執(zhí)行),再刪除,也可以不處理(因為本就不是要處理業(yè)務(wù)的key)

結(jié)語

  多實例的情況下,每個實例都會收到過期key通知,可以通過分布式鎖的方式去處理業(yè)務(wù),避免業(yè)務(wù)重復(fù)執(zhí)行

到此這篇關(guān)于Redis監(jiān)聽過期的key實現(xiàn)流程詳解的文章就介紹到這了,更多相關(guān)Redis監(jiān)聽key內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Java版本不兼容問題詳細(xì)解決方案步驟

    Java版本不兼容問題詳細(xì)解決方案步驟

    這篇文章主要介紹了Java版本不兼容問題解決的相關(guān)資料,詳細(xì)分析了問題原因,并提供了解決方案,包括統(tǒng)一JDK版本、修改項目配置和清理舊版本殘留等步驟,需要的朋友可以參考下
    2025-05-05
  • Java設(shè)計模式中的代理設(shè)計模式詳細(xì)解析

    Java設(shè)計模式中的代理設(shè)計模式詳細(xì)解析

    這篇文章主要介紹了Java設(shè)計模式中的代理設(shè)計模式詳細(xì)解析,代理模式,重要的在于代理二字,何為代理,我們可以聯(lián)想到生活中的例子,比如秘書、中介這類職業(yè),我們可以委托中介去幫我們完成某些事情,而我們自己只需要關(guān)注我們必須完成的事情,需要的朋友可以參考下
    2023-12-12
  • Spring Boot 如何將 Word 轉(zhuǎn)換為 PDF

    Spring Boot 如何將 Word 轉(zhuǎn)換為 PDF

    這篇文章主要介紹了Spring Boot將Word轉(zhuǎn)換為 PDF,本文通過示例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2023-08-08
  • SpringBoot(cloud)自動裝配bean找不到類型的問題

    SpringBoot(cloud)自動裝配bean找不到類型的問題

    這篇文章主要介紹了SpringBoot(cloud)自動裝配bean找不到類型的問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-02-02
  • Java讀取文件及基于正則表達式的獲取電話號碼功能詳解

    Java讀取文件及基于正則表達式的獲取電話號碼功能詳解

    這篇文章主要介紹了Java讀取文件及基于正則表達式的獲取電話號碼功能,結(jié)合實例形式詳細(xì)分析了正則匹配操作的相關(guān)語法及電話號碼匹配的原理與實現(xiàn)技巧,需要的朋友可以參考下
    2017-09-09
  • java selenium操作彈出對話框示例講解

    java selenium操作彈出對話框示例講解

    本文主要介紹java selenium操作彈出對話框,這里給大家整理了相關(guān)資料,并附示例代碼和實現(xiàn)效果圖,有興趣的小伙伴可以參考下
    2016-08-08
  • spring事務(wù)隔離級別、傳播機制以及簡單配置方式

    spring事務(wù)隔離級別、傳播機制以及簡單配置方式

    這篇文章主要介紹了spring事務(wù)隔離級別、傳播機制以及簡單配置方式,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2021-01-01
  • springcloud?eureka切換nacos的配置方法

    springcloud?eureka切換nacos的配置方法

    這篇文章主要介紹了springcloud?eureka切換nacos,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2023-01-01
  • Spring如何將bean添加到容器中

    Spring如何將bean添加到容器中

    這篇文章主要介紹了Spring如何將bean添加到容器中,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2020-05-05
  • Spring Security自定義身份認(rèn)證的實現(xiàn)方法

    Spring Security自定義身份認(rèn)證的實現(xiàn)方法

    這篇文章主要介紹了Spring Security自定義身份認(rèn)證的實現(xiàn)方法,下面對Spring Security的這三種自定義身份認(rèn)證進行詳細(xì)講解,需要的朋友可以參考下
    2025-04-04

最新評論

西和县| 双柏县| 邵阳县| 安溪县| 龙口市| 邹城市| 静宁县| 咸阳市| 建瓯市| 黄大仙区| 灵宝市| 岱山县| 洪洞县| 长沙市| 湖北省| 芦山县| 安化县| 望城县| 灵丘县| 开封市| 财经| 阿荣旗| 莎车县| 迁安市| 南宫市| 沙湾县| 白山市| 沈丘县| 聂拉木县| 枝江市| 乌鲁木齐市| 改则县| 郁南县| 墨竹工卡县| 西丰县| 乡城县| 宣恩县| 阳谷县| 雅安市| 普宁市| 金溪县|