中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

springboot中RedisTemplate如何使用

發布時間:2022-03-29 13:57:18 來源:億速云 閱讀:380 作者:iii 欄目:大數據

這篇文章主要介紹了springboot中RedisTemplate如何使用的相關知識,內容詳細易懂,操作簡單快捷,具有一定借鑒價值,相信大家閱讀完這篇springboot中RedisTemplate如何使用文章都會有所收獲,下面我們一起來看看吧。

問題

Q:springboot中RedisTemplate的使用?

Q:RedisTemplate的是一定要配置么?不配置也能使用,配置RedisTemplate是為了我們能自定義其序列化方式么?不配置的話表示默認采用其默認的配置序列化方式?

Q:布隆過濾器是什么?有什么用?怎么使用?

大白話布隆過濾器

Q:我們在編寫RedisConfig配置類時是否繼承于CachingConfigurerSupport類有什么區別?為什么有的繼承了有的不選擇繼承,是繼承了的話可以結合Springboot的@EnableCaching的注解開啟緩存么?以便使用如下注解自動開啟緩存么?那在項目中是使用好還是不使用好?

開啟緩存后注解使用參考:

注解緩存的使用
@Cacheable:在方法執行前Spring先查看緩存中是否有數據,如果有數據,則直接返回緩存數據;沒有則調用方法并將方法返回值放進緩存。
@CachePut:將方法的返回值放到緩存中。
@CacheEvict:刪除緩存中的數據。

Q:Spring Boot中混合使用StringRedisTemplate和RedisTemplate的坑——存儲到Redis的數據取不到值。

A:因為他同時使用了StringRedisTemplate和RedisTemplate在Redis中存儲和讀取數據。它們最重要的一個區別就是默認采用的序列化方式不同。StringRedisTemplate采用的是RedisSerializer.string()來序列化Redis中存儲數據的Key ;RedisTemplate使用的序列化類為defaultSerializer,默認情況下為JdkSerializationRedisSerializer。如果未指定Key的序列化類,keySerializer與defaultSerializer采用相同的序列化類。

解決方法:需要指定統一的Key的序列化處理類,比如在RedisTemplate序列化時指定與StringRedisTemplate相同的類。

Q:Redis的brpop命令對應RedisTemplate中的什么方法?

Q:雙向鏈表數據結構的了解?

Q:單元測試時@RunWith(SpringJUnit4ClassRunner.class)@SpringBootTest(classes = MyBootApplication.class)兩個注解的作用?

A:如下。

1)@RunWith:用于指定junit運行環境,是junit提供給其他框架測試環境接口擴展,為了便于使用spring的依賴注入,spring提供了org.springframework.test.context.junit4.SpringJUnit4ClassRunner作為Junit測試環境。

2)@ContextConfiguration({"classes=Congfig.clsss",classpath:applicationContext.xml"}) 這里可以用classes來直接導入同包下寫的配置類。或者導入配置文件。

3)@SpringBootTest替代了spring-test中的@ContextConfiguration注解,目的是加載ApplicationContext,啟動spring容器。

springboot使用單元測試步驟:

1、引入依賴

<!--springboot 集成 junit 起步依賴-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <version>2.1.6.RELEASE</version>
    <scope>test</scope>
</dependency>

2、編寫測試類,注意加注解@RunWith(SpringJUnit4ClassRunner.class)@SpringBootTest(classes = MyBootApplication.class),模板如下:

//用于指定junit運行環境,是junit提供給其他框架測試環境接口擴展,為了便于使用spring的依賴注入
@RunWith(SpringJUnit4ClassRunner.class)
//用于加載ApplicationContext,啟動spring容器
@SpringBootTest(classes = MyBootApplication.class)
public class RedisTemplateTest {
    @Autowired
    private RedisTemplate redisTemplate;
	//因為spring自動注入管理了bean容器,直接用@Autowired取即可
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Test
    public void testString(){
        //設置值
        stringRedisTemplate.opsForValue().set("String","Mao");
    }
}

Q:緩存并發問題,這里的并發指的是多個 Redis 的客戶端同時 set 值引起的并發問題。比較有效的解決方案就是把 set 操作放在隊列中使其串行化,必須得一個一個執行。我的疑問是,redis本身就是單線程串行執行的,怎么會有并發問題呢?

A:我們說的緩存并發指的是多個Redis客戶端同時SET Key時會引起并發問題。我們知道,Redis是單線程的,在多個Client并發操作時,秉承“先發起先執行”的原則,其它的處于阻塞狀態。

常見緩存并發有兩種場景:

  • 緩存過期后會從后端數據庫查詢數據然后存入Redis緩存,但在高并發情況下可能在還沒來得及將庫中查出來的數據存入Redis時,其它Client又從數據庫里查詢數據再存入Redis了。這樣一來會造成多個請求并發的從數據庫獲取數據,對后端數據庫會造成壓力。

  • 在高并發場景下,某個Key正在更新時,可能有很多Client在獲取此Key的數據,這樣會導致“臟數據”。

如何解決緩存并發問題呢?

1、加鎖。我們常借助“鎖”來實現,具體實現邏輯為:

在更新緩存或者讀取過期緩存的情況下,我們先獲取“鎖”,當完成了緩存數據的更新后再釋放鎖,這樣一來,其它的請求需要在釋放鎖之后執行,會犧牲一點時間。

2、異步隊列串行執行。把 set 操作放在隊列中使其串行化,必須得一個一個執行。如通過消息中間件異步執行。

3、使用類似SQL的樂觀鎖機制  。解決途徑是在并發寫入Redis緩存時,用要寫入數據的版本號和時間戳 與 Redis中的數據進行對比,如果寫入的數據時間戳或者版本號 比Redis 高,則寫入Redis中;否則就不寫入。


Redis 簡介

Redis 是一個開源(BSD 許可)、內存存儲的數據結構服務器,可用作數據庫,高速緩存和消息隊列代理。它支持字符串、哈希表、列表、集合、有序集合等數據類型。內置復制、Lua 腳本、LRU 收回、事務以及不同級別磁盤持久化功能,同時通過 Redis Sentinel 提供高可用,通過 Redis Cluster 提供自動分區。

Redis 使用場景

微服務以及分布式被廣泛使用后,Redis 的使用場景就越來越多了,這里我羅列了主要的幾種場景。

  1. 分布式緩存:在分布式的系統架構中,將緩存存儲在內存中顯然不當,因為緩存需要與其他機器共享,這時 Redis 便挺身而出了,緩存也是 Redis 使用最多的場景。

  2. 分布式鎖:在高并發的情況下,我們需要一個鎖來防止并發帶來的臟數據,Java 自帶的鎖機制顯然對進程間的并發并不好使,此時可以利用 Redis 單線程的特性來實現我們的分布式鎖,如何實現,可以參考這篇文章。

  3. Session 存儲/共享:Redis 可以將 Session 持久化到存儲中,這樣可以避免由于機器宕機而丟失用戶會話信息。(個人認為更重要的是因為服務集群的出現,需要一個分布式Session來作為統一的會話)

  4. 發布/訂閱:Redis 還有一個發布/訂閱的功能,您可以設定對某一個 key 值進行消息發布及消息訂閱,當一個 key 值上進行了消息發布后,所有訂閱它的客戶端都會收到相應的消息。這一功能最明顯的用法就是用作實時消息系統。

  5. 任務隊列:Redis 的 lpush+brpop 命令組合即可實現阻塞隊列,生產者客戶端使用 lrpush 從列表左側插入元素,多個消費者客戶端使用 brpop 命令阻塞式的”搶”列表尾部的元素,多個客戶端保證了消費的負載均衡和高可用性。

  6. 限速,接口訪問頻率限制:比如發送短信驗證碼的接口,通常為了防止別人惡意頻刷,會限制用戶每分鐘獲取驗證碼的頻率,例如一分鐘不能超過 5 次。

當然 Redis 的使用場景并不僅僅只有這么多,還有很多未列出的場景,如計數、排行榜等。

Redis 數據類型

前面也提到過,Redis 支持字符串、哈希表、列表、集合、有序集合五種數據類型的存儲。

字符串(string)

  • string類型是二進制安全的。意思是redis的string可以包含任何數據。比如jpg圖片或者序列化的對象 。

  • string類型是Redis最基本的數據類型,一個鍵最大能存儲512MB。

string 這種數據結構應該是我們最為常用的。在 Redis 中 string 表示的是一個可變的字節數組,我們初始化字符串的內容、可以拿到字符串的長度,可以獲取 string 的子串,可以覆蓋 string 的子串內容,可以追加子串。

圖 1. Redis 的 string 類型數據結構

如上圖所示,在 Redis 中我們初始化一個字符串時,會采用預分配冗余空間的方式來減少內存的頻繁分配,如圖 1 所示,實際分配的空間 capacity 一般要高于實際字符串長度 len。如果您看過 Java 的 ArrayList 的源碼相信會對此種模式很熟悉。

列表(list)

  • redis列表是簡單的字符串列表,排序為插入的順序。列表的最大長度為2^32-1。

  • redis的列表是使用鏈表實現的,這意味著,即使列表中有上百萬個元素,增加一個元素到列表的頭部或尾部的操作都是在常量的時間完成。

  • 可以用列表獲取最新的內容(像帖子,微博等),用ltrim很容易就會獲取最新的內容,并移除舊的內容。

  • 用列表可以實現生產者消費者模式,生產者調用lpush添加項到列表中,消費者調用rpop從列表中提取,如果沒有元素,則輪詢去獲取,或者使用brpop等待生產者添加項到列表中。

在 Redis 中列表 list 采用的存儲結構是雙向鏈表,由此可見其隨機定位性能較差,比較適合首位插入刪除。像 Java 中的數組一樣,Redis 中的列表支持通過下標訪問,不同的是 Redis 還為列表提供了一種負下標,-1 表示倒數一個元素,-2 表示倒數第二個數,依此類推。綜合列表首尾增刪性能優異的特點,通常我們使用 rpush/rpop/lpush/lpop 四條指令將列表作為隊列來使用。

圖 2. List 類型數據結構

如上圖所示,在列表元素較少的情況下會使用一塊連續的內存存儲,這個結構是 ziplist,也即是壓縮列表。它將所有的元素緊挨著一起存儲,分配的是一塊連續的內存。當數據量比較多的時候才會改成 quicklist。因為普通的鏈表需要的附加指針空間太大,會比較浪費空間。比如這個列表里存的只是 int 類型的數據,結構上還需要兩個額外的指針 prev 和 next。所以 Redis 將鏈表和 ziplist 結合起來組成了 quicklist。也就是將多個 ziplist 使用雙向指針串起來使用。這樣既滿足了快速的插入刪除性能,又不會出現太大的空間冗余。

哈希表(hash)

  • redis的哈希值是字符串字段和字符串之間的映射,是表示對象的完美數據類型。

  • 哈希中的字段數量沒有限制,所以可以在你的應用程序以不同的方式來使用哈希。

hash 與 Java 中的 HashMap 差不多,實現上采用二維結構,第一維是數組,第二維是鏈表。hash 的 key 與 value 都存儲在鏈表中,而數組中存儲的則是各個鏈表的表頭。在檢索時,首先計算 key 的 hashcode,然后通過 hashcode 定位到鏈表的表頭,再遍歷鏈表得到 value 值。可能您比較好奇為啥要用鏈表來存儲 key 和 value,直接用 key 和 value 一對一存儲不就可以了嗎?其實是因為有些時候我們無法保證 hashcode 值的唯一,若兩個不同的 key 產生了相同的 hashcode,我們需要一個鏈表在存儲兩對鍵值對,這就是所謂的 hash 碰撞。

集合(set)

  • redis集合是無序的字符串集合,集合中的值是唯一的,無序的。可以對集合執行很多操作,例如,測試元素是否存在,對多個集合執行交集、并集和差集等等。

  • 我們通常可以用集合存儲一些無關順序的,表達對象間關系的數據,例如用戶的角色,可以用sismember很容易就判斷用戶是否擁有某個角色。

  • 在一些用到隨機值的場合是非常適合的,可以用 srandmember/spop 獲取/彈出一個隨機元素。
    同時,使用@EnableCaching開啟聲明式緩存支持,這樣就可以使用基于注解的緩存技術。注解緩存是一個對緩存使用的抽象,通過在代碼中添加下面的一些注解,達到緩存的效果。


    • @Cacheable:在方法執行前Spring先查看緩存中是否有數據,如果有數據,則直接返回緩存數據;沒有則調用方法并將方法返回值放進緩存。

    • @CachePut:將方法的返回值放到緩存中。

    • @CacheEvict:刪除緩存中的數據。

熟悉 Java 的同學應該知道 HashSet 的內部實現使用的是 HashMap,只不過所有的 value 都指向同一個對象。Redis 的 Set 結構也是一樣,它的內部也使用 Hash 結構,所有的 value 都指向同一個內部值。

有序集合(sorted set)

  • 有序集合由唯一的,不重復的字符串元素組成。有序集合中的每個元素都關聯了一個浮點值,稱為分數。可以把有序看成hash和集合的混合體,分數即為hash的key。

  • 有序集合中的元素是按序存儲的,不是請求時才排序的。

有時也被稱作 ZSet,是 Redis 中一個比較特別的數據結構,在有序集合中我們會給每個元素賦予一個權重,其內部元素會按照權重進行排序,我們可以通過命令查詢某個范圍權重內的元素,這個特性在我們做一個排行榜的功能時可以說非常實用了。其底層的實現使用了兩個數據結構, hash 和跳躍列表,hash 的作用就是關聯元素 value 和權重 score,保障元素 value 的唯一性,可以通過元素 value 找到相應的 score 值。跳躍列表的目的在于給元素 value 排序,根據 score 的范圍獲取元素列表。

在 Spring Boot 項目中使用 Redis

spring-data-redis針對jedis提供了如下功能:

1. 連接池自動管理,提供了一個高度封裝的“RedisTemplate”類

2. 針對jedis客戶端中大量api進行了歸類封裝,將同一類型操作封裝為operation接口

ValueOperations:簡單K-V操作
SetOperations:set類型數據操作
ZSetOperations:zset類型數據操作
HashOperations:針對map類型的數據操作
ListOperations:針對list類型的數據操作

3. 提供了對key的“bound”(綁定)便捷化操作API,可以通過bound封裝指定的key,然后進行一系列的操作而無須“顯式”的再次指定Key,即BoundKeyOperations:

BoundValueOperations
BoundSetOperations
BoundListOperations
BoundSetOperations
BoundHashOperations

4. 將事務操作封裝,有容器控制。

5. 針對數據的“序列化/反序列化”,提供了多種可選擇策略(RedisSerializer)

JdkSerializationRedisSerializer:POJO對象的存取場景,使用JDK本身序列化機制,將pojo類通過ObjectInputStream/ObjectOutputStream進行序列化操作,最終redis-server中將存儲字節序列。是目前最常用的序列化策略。

StringRedisSerializer:Key或者value為字符串的場景,根據指定的charset對數據的字節序列編碼成string,是“new String(bytes, charset)”和“string.getBytes(charset)”的直接封裝。是最輕量級和高效的策略。

JacksonJsonRedisSerializer:jackson-json工具提供了javabean與json之間的轉換能力,可以將pojo實例序列化成json格式存儲在redis中,也可以將json格式的數據轉換成pojo實例。因為jackson工具在序列化和反序列化時,需要明確指定Class類型,因此此策略封裝起來稍微復雜。【需要jackson-mapper-asl工具支持】

OxmSerializer:提供了將javabean與xml之間的轉換能力,目前可用的三方支持包括jaxb,apache-xmlbeans;redis存儲的數據將是xml工具。不過使用此策略,編程將會有些難度,而且效率最低;不建議使用。【需要spring-oxm模塊的支持】

如果你的數據需要被第三方工具解析,那么數據應該使用StringRedisSerializer而不是JdkSerializationRedisSerializer。

如果使用的是默認的JdkSerializationRedisSerializer,注意一定要讓緩存的對象實現序列化接口用于序列化 。

關于key的設計

key的存活時間:

無論什么時候,只要有可能就利用key超時的優勢。

關系型數據庫的redis

1: 把表名轉換為key前綴 如, tag:

2: 第2段放置用于區分區key的字段–對應mysql中的主鍵的列名,如userid

3: 第3段放置主鍵值,如2,3,4…., a , b ,c

4: 第4段,寫要存儲的列名

例:user:userid:9:username

使用前配置

添加 Redis 依賴

<!-- 配置使用 redis 啟動器 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<!--springboot 集成 junit 起步依賴-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <version>2.1.6.RELEASE</version>
    <scope>test</scope>
</dependency>

Spring Boot 中配置 Redis: application.properties

spring.redis.host=127.0.0.1
spring.redis.port=6379
# Redis 數據庫索引(默認為 0)
spring.redis.database=0 
# Redis 服務器連接端口
# Redis 服務器連接密碼(默認為空)
spring.redis.password=123456
#連接池最大連接數(使用負值表示沒有限制)
spring.redis.jedis.pool.max-active=8
# 連接池最大阻塞等待時間(使用負值表示沒有限制)
spring.redis.jedis.pool.max-wait=-1
# 連接池中的最大空閑連接
spring.redis.jedis.pool.max-idle=8
# 連接池中的最小空閑連接
spring.redis.jedis.pool.min-idle=0
# 連接超時時間(毫秒) 如果連接超時時間不設置,這要注釋掉配置而不能=0,否則會報連接超時錯誤:Command timed out after no timeout,,有超時時間最后設置為200以上
spring.redis.timeout=300

RedisTemplate 的配置

@Configuration
public class RedisConfig {

    /**
     * 默認是JDK的序列化策略,這里配置redisTemplate采用的是Jackson2JsonRedisSerializer的序列化策略
     * @param redisConnectionFactory
     * @return
     */
    @Bean
    public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory redisConnectionFactory){
        //使用Jackson2JsonRedisSerializer來序列化和反序列化redis的value值(默認使用JDK的序列化方式)
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper om = new ObjectMapper();
        // 指定要序列化的域,field,get和set,以及修飾符范圍,ANY是都有包括private和public
        om.setVisibility(PropertyAccessor.ALL,JsonAutoDetect.Visibility.ANY);
        // 指定序列化輸入的類型,類必須是非final修飾的,final修飾的類,比如String,Integer等會拋出異常
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        // 配置連接工廠
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        //使用StringRedisSerializer來序列化和反序列化redis的key值
        //redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setKeySerializer(jackson2JsonRedisSerializer);
        // 值采用json序列化
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.setHashKeySerializer(jackson2JsonRedisSerializer);
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }

    /***
     * stringRedisTemplate默認采用的是String的序列化策略
     * @param redisConnectionFactory
     * @return
     */
    @Bean
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory){
        StringRedisTemplate stringRedisTemplate = new StringRedisTemplate();
        stringRedisTemplate.setConnectionFactory(redisConnectionFactory);
        return stringRedisTemplate;
    }
}

RedisTemplate

Spring Boot 的 spring-boot-starter-data-redis 為 Redis 的相關操作提供了一個高度封裝的 RedisTemplate 類,而且對每種類型的數據結構都進行了歸類,將同一類型操作封裝為 operation 接口。RedisTemplate 對五種數據結構分別定義了操作,如下所示:

  • 操作字符串:redisTemplate.opsForValue()

  • 操作 Hash:redisTemplate.opsForHash()

  • 操作 List:redisTemplate.opsForList()

  • 操作 Set:redisTemplate.opsForSet()

  • 操作 ZSet:redisTemplate.opsForZSet()

但是對于 string 類型的數據,Spring Boot 還專門提供了 StringRedisTemplate 類,而且官方也建議使用該類來操作 String 類型的數據。那么它和 RedisTemplate 又有啥區別呢?

  1. RedisTemplate 是一個泛型類,而 StringRedisTemplate 不是,后者只能對鍵和值都為 String 類型的數據進行操作,而前者則可以操作任何類型。

  2. 兩者的數據是不共通的,StringRedisTemplate 只能管理 StringRedisTemplate 里面的數據,RedisTemplate 只能管理 RedisTemplate 中 的數據。

實踐

//用于指定junit運行環境,是junit提供給其他框架測試環境接口擴展,為了便于使用spring的依賴注入
@RunWith(SpringJUnit4ClassRunner.class)
//用于加載ApplicationContext,啟動spring容器
@SpringBootTest(classes = MyBootApplication.class)
public class RedisTemplateTest {

    @Autowired
    private RedisTemplate redisTemplate;
    //因為spring自動注入管理了bean容器,直接用@Autowired取即可
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 操作字符串
     */
    @Test
    public void testString() {
        //設置值
        stringRedisTemplate.opsForValue().set("String", "Mao");
        //獲取值
        String string = stringRedisTemplate.opsForValue().get("String");

        //設置值且設置超時時間
        stringRedisTemplate.opsForValue().set("Middle", "Yu", 3, TimeUnit.MINUTES);
        String middle = stringRedisTemplate.opsForValue().get("Middle");
        System.out.println(middle);

        //刪除數據
        Boolean isDelete = stringRedisTemplate.delete("String");
        System.out.println(isDelete ? "Yes" : "No");
    }

    /**
     * 操作列表
     */
    @Test
    public void testList() {
        ListOperations listOp = redisTemplate.opsForList();
        //往 List 左側插入一個元素
        listOp.leftPush("namelist", "mike");
        listOp.leftPush("namelist", "kim");
        //往 List 右側插入一個元素
        listOp.rightPush("namelist", "jimmy");
        listOp.rightPush("namelist", "chuck");
        //List 大小
        Long size = listOp.size("namelist");
        //遍歷整個List
        List namelist1 = listOp.range("namelist", 0, size);
        //遍歷整個List,-1表示倒數第一個即最后一個
        List namelist = listOp.range("namelist", 0, -1);
        System.out.println(JSON.toJSONString(namelist));
        //從 List 左側取出第一個元素,并移除
        Object name1 = listOp.leftPop("namelist", 200, TimeUnit.MILLISECONDS);
        System.out.println("is kim:" + name1.equals("kim"));
        //從 List 右側取出第一個元素,并移除
        Object name2 = listOp.rightPop("namelist");
        System.out.println("is chuck:" + name2.equals("chuck"));
    }

    /**
     * 操作 Hash
     */
    @Test
    public void testHash() {
        //添加泛型方便操作和返回想要的具體類型
        HashOperations<String, String, Integer> hashOp = redisTemplate.opsForHash();
        //Hash 中新增元素。
        hashOp.put("score", "Mike", 10);
        hashOp.put("score", "Jimmy", 9);
        hashOp.put("score", "Kim", 8);
        //判斷指定 key 對應的 Hash 中是否存在指定的 map 鍵
        Assert.assertTrue(hashOp.hasKey("score", "Kim"));
        //獲取指定 key 對應的 Hash 中指定鍵的值
        Integer kim = hashOp.get("score", "Kim");
        System.out.println("kim score:" + kim);
        //獲取hash表所有的key集合
        Set<String> name = hashOp.keys("score");
        System.out.println(JSON.toJSONString(name));
        //獲取hash表所有的values集合
        List<Integer> score = hashOp.values("score");
        System.out.println(JSON.toJSONString(score));
        //獲取"score"對應的hash表Map
        Map<String, Integer> map = hashOp.entries("score");
        System.out.println(JSON.toJSONString(map));
        //刪除指定 key 對應 Hash 中指定鍵的鍵值對
        hashOp.delete("score", "Mike");
        //如果要刪除整個hash表,要用redisTemplate.delete("score")方法,否則報錯:Fields must not be empty
        //hashOp.delete("score");
        //刪除整個hash表
        redisTemplate.delete("score");
        Map<String, Integer> map1 = hashOp.entries("score");
        System.out.println(JSON.toJSONString(map1));
    }

    /**
     * 操作集合
     */
    @Test
    public void testSet() {
        SetOperations<String, String> setOp = redisTemplate.opsForSet();
        //向集合中添加元素,set元素具有唯一性
        setOp.add("city", "quanzhou", "newyork", "paris", "hongkong", "hongkong");
        Long size = setOp.size("city");
        System.out.println("city size:" + size);
        //獲取集合中的元素
        Set city = setOp.members("city");
        System.out.println(JSON.toJSONString(city));
        //移除集合中的元素,可以一個或多個
        setOp.remove("city", "paris");
        //判斷是否是集合中的元素
        Boolean isMember = setOp.isMember("city", "paris");
        System.out.println("paris is in city:" + isMember);
        //移除并返回集合中的一個隨機元素
        String city1 = setOp.pop("city");
        System.out.println(city1);
    }

    /**
     * 操作有序集合
     */
    @Test
    public void testZSet() {
        ZSetOperations<String, String> zSetOp = redisTemplate.opsForZSet();
        zSetOp.add("zcity", "beijing", 100);
        zSetOp.add("zcity", "shanghai", 95);
        zSetOp.add("zcity", "guangzhou", 75);
        zSetOp.add("zcity", "shenzhen", 85);
        //獲取變量指定區間的元素。0, -1表示全部
        Set<String> zcity = zSetOp.range("zcity", 0, -1);
        System.out.println(JSON.toJSONString(zcity));
        //通過分數返回有序集合指定區間內的成員,其中有序集成員按分數值遞增(從小到大)順序排列
        Set<String> byScore = zSetOp.rangeByScore("zcity", 85, 100);
        System.out.println(JSON.toJSONString(byScore));
        //獲取有序集合的成員數
        Long aLong = zSetOp.zCard("zcity");
        System.out.println("zcity size: " + aLong);

        ZSetOperations<String, Integer> zSetOp1 = redisTemplate.opsForZSet();
        zSetOp1.add("board", 1, 100);
        zSetOp1.add("board", 2, 100);
        zSetOp1.add("board", 3, 100);
        zSetOp1.add("board", 4, 100);
        Set<Integer> board = zSetOp1.range("board", 0, -1);
        System.out.println(JSON.toJSONString(board));
        RedisZSetCommands.Range range = new RedisZSetCommands.Range();
        //less than
        range.lt("3");
        RedisZSetCommands.Limit limit = new RedisZSetCommands.Limit();
        limit.count(1);
        limit.offset(1);
        //用于獲取滿足非 score 的排序取值。這個排序只有在有相同分數的情況下才能使用,如果有不同的分數則返回值不確定。
        //rangeByLex應用在數值上比較
        Set<Integer> set = zSetOp1.rangeByLex("board", range);
        System.out.println(JSON.toJSONString(set));
        //用于獲取滿足非 score 的設置下標開始的長度排序取值。
        Set<Integer> setlmt = zSetOp1.rangeByLex("board", range, limit);
        System.out.println(JSON.toJSONString(setlmt));
    }

    /**
     * 分布式鎖
     */
    @Test
    public void testLock() {
        String value = UUID.randomUUID().toString();
        Boolean lock = lock("buy", value,120L, TimeUnit.SECONDS);
        if (!lock) {
            System.out.println("can't lock buy");
        }
        try {
            Thread.sleep(6);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Boolean unLock = unLock("buy",value);
        if (!unLock) {
            System.out.println("can't unlock buy");
        }
    }

    public Boolean lock(String key,String value, Long timeout, TimeUnit timeUnit) {
        Boolean lockStat = stringRedisTemplate.execute((RedisCallback<Boolean>) connection ->
                connection.set(key.getBytes(Charset.forName("UTF-8")), value.getBytes(Charset.forName("UTF-8")),
                        Expiration.from(timeout, timeUnit), RedisStringCommands.SetOption.SET_IF_ABSENT));
        return lockStat;
    }

    public Boolean unLock(String key,String value) {
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        boolean unLockStat = stringRedisTemplate.execute((RedisCallback<Boolean>) connection ->
                connection.eval(script.getBytes(), ReturnType.BOOLEAN, 1,
                        key.getBytes(Charset.forName("UTF-8")), value.getBytes(Charset.forName("UTF-8"))));
        return unLockStat;
    }
    
    @Test
    public void boundTest(){
        BoundListOperations bound = redisTemplate.boundListOps("bound");
        bound.leftPush("haha");
        bound.rightPush("hehe");
        List list = bound.range(0, -1);
        System.out.println(JSON.toJSONString(list));
    }
}

報錯1:

Caused by: io.lettuce.core.RedisCommandExecutionException: ERR Client sent AUTH, but no password is set
	at io.lettuce.core.ExceptionFactory.createExecutionException(ExceptionFactory.java:135)
	
A:這是因為服務器沒有設置密碼,客戶端設置了密碼去連接,導致連接失敗。我們在啟動服務器時通過命令(config set requirepass "123456")手動設置了密碼,一旦Redis服務器關閉后,重新啟動加載了Redis啟動的配置,因為啟動配置菜單(redis-server.exe redis.windows.conf)中沒有配置密碼,所以重新啟動的redis是沒有設置密碼的,這樣就會報錯。
解決方法:如果要設置密碼則要在Redis啟動的配置文件中修改添加;或者如果不設置密碼則客戶端連接時application.properties不要設置密碼連接
# Redis 服務器連接密碼(默認為空)
#spring.redis.password=123456

報錯2:

org.springframework.dao.QueryTimeoutException: Redis command timed out; nested exception is io.lettuce.core.RedisCommandTimeoutException: Command timed out after no timeout at org.springframework.data.redis.connection.lettuce.LettuceExceptionConverter.convert(LettuceExceptionConverter.java:70)
A:這是因為客戶端連接時application.properties中設置了連接超時時間為0導致的.
解決方法:
# 連接超時時間(毫秒) 如果連接超時時間不設置,這要注釋掉配置而不能=0,否則會報連接超時錯誤:Command timed out after no timeout,有超時時間最后設置為200以上
spring.redis.timeout=300



org.springframework.data.redis.RedisSystemException: Unknown redis exception; nested exception is java.lang.IllegalArgumentException: Fields must not be empty at org.springframework.data.redis.FallbackExceptionTranslationStrategy.getFallback(FallbackExceptionTranslationStrategy.java:53)

A:如果要刪除整個hash表,要用redisTemplate.delete("score")方法,否則報錯:Fields must not be empty
解決方法:使用redisTemplate方法,HashOperations的delete是用于刪除hash表字段用
		HashOperations<String,String,Integer> hashOp = redisTemplate.opsForHash();
        //hashOp.delete("score");
        redisTemplate.delete("score");

關于 Redis 的幾個經典問題

緩存與數據庫一致性問題

對于既有數據庫操作又有緩存操作的接口,一般分為兩種執行順序。

  1. 先操作數據庫,再操作緩存。這種情況下如果數據庫操作成功,緩存操作失敗就會導致緩存和數據庫不一致。

  2. 第二種情況就是先操作緩存再操作數據庫,這種情況下如果緩存操作成功,數據庫操作失敗也會導致數據庫和緩存不一致。

大部分情況下,我們的緩存理論上都是需要可以從數據庫恢復出來的,所以基本上采取第一種順序都是不會有問題的。針對那些必須保證數據庫和緩存一致的情況,通常是不建議使用緩存的。

緩存擊穿問題

緩存擊穿表示惡意用戶頻繁的模擬請求緩存中不存在的數據,以致這些請求短時間內直接落在了數據庫上,導致數據庫性能急劇下降,最終影響服務整體的性能。這個在實際項目很容易遇到,如搶購活動、秒殺活動的接口 API 被大量的惡意用戶刷,導致短時間內數據庫宕機。對于緩存擊穿的問題,有以下幾種解決方案,這里只做簡要說明。

  1. 使用互斥鎖排隊。當從緩存中獲取數據失敗時,給當前接口加上鎖,從數據庫中加載完數據并寫入后再釋放鎖。若其它線程獲取鎖失敗,則等待一段時間后重試。(數據庫取數據時加鎖)

  2. 使用布隆過濾器。將所有可能存在的數據緩存放到布隆過濾器中,當黑客訪問不存在的緩存時迅速返回避免緩存及 DB 掛掉。

緩存雪崩問題

在短時間內有大量緩存失效,如果這期間有大量的請求發生同樣也有可能導致數據庫發生宕機。在 Redis 機群的數據分布算法上如果使用的是傳統的 hash 取模算法,在增加或者移除 Redis 節點的時候就會出現大量的緩存臨時失效的情形。

  1. 像解決緩存穿透一樣加鎖排隊。

  2. 建立備份緩存,緩存 A 和緩存 B,A 設置超時時間,B 不設值超時時間,先從 A 讀緩存,A 沒有讀 B,并且更新 A 緩存和 B 緩存。

  3. 計算數據緩存節點的時候采用一致性 hash 算法,這樣在節點數量發生改變時不會存在大量的緩存數據需要遷移的情況發生。

緩存并發問題

這里的并發指的是多個 Redis 的客戶端同時 set 值引起的并發問題。比較有效的解決方案就是把 set 操作放在隊列中使其串行化,必須得一個一個執行。

描述

brpop : block right pop 
BRPOP key1 [key2 ] timeout 
移出并獲取列表的最后一個元素, 如果列表沒有元素會阻塞列表直到等待超時或發現可彈出元素為止。

關于“springboot中RedisTemplate如何使用”這篇文章的內容就介紹到這里,感謝各位的閱讀!相信大家對“springboot中RedisTemplate如何使用”知識都有一定的了解,大家如果還想學習更多知識,歡迎關注億速云行業資訊頻道。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

中山市| 铁岭县| 新巴尔虎左旗| 义马市| 九寨沟县| 晋宁县| 蓬莱市| 湖州市| 疏勒县| 浏阳市| 宁城县| 泾川县| 泰兴市| 清远市| 延川县| 廊坊市| 文成县| 东平县| 西吉县| 土默特右旗| 枝江市| 徐州市| 读书| 荣成市| 铜梁县| 灵山县| 崇州市| 山丹县| 临沂市| 元氏县| 眉山市| 绍兴县| 昌都县| 济阳县| 呼和浩特市| 霸州市| 永顺县| 屏东市| 平江县| 衡水市| 寻乌县|