SpringBoot+Redis實(shí)現(xiàn)分布式緩存的方法步驟
背景
緩存(Cache):指將程序或系統(tǒng)中常用的數(shù)據(jù)對(duì)象存儲(chǔ)在像內(nèi)存這樣特定的介質(zhì)中,以避免在每次程序調(diào)用時(shí),重新創(chuàng)建或組織數(shù)據(jù)所帶來(lái)的性能損耗,從而提高了系統(tǒng)的整體運(yùn)行速度。
以目前的系統(tǒng)架構(gòu)來(lái)說(shuō),用戶的請(qǐng)求一般會(huì)先經(jīng)過(guò)緩存系統(tǒng),如果緩存中沒有相關(guān)的數(shù)據(jù),就會(huì)在其他系統(tǒng)中查詢到相應(yīng)的數(shù)據(jù)并保存在緩存中,最后返回給調(diào)用方。
本地緩存:指程序級(jí)別的緩存組件,它的特點(diǎn)是本地緩存和應(yīng)用程序會(huì)運(yùn)行在同一個(gè)進(jìn)程中,所以本地緩存的操作會(huì)非常快,因?yàn)樵谕粋€(gè)進(jìn)程內(nèi)也意味著不會(huì)有網(wǎng)絡(luò)上的延遲和開銷。
本地緩存適用于單節(jié)點(diǎn)非集群的應(yīng)用場(chǎng)景,它的優(yōu)點(diǎn)是快,缺點(diǎn)是多程序無(wú)法共享緩存,比如分布式用戶 Session 會(huì)話信息保存,由于每次用戶訪問(wèn)的服務(wù)器可能是不同的,如果不能共享緩存,那么就意味著每次的請(qǐng)求操作都有可能被系統(tǒng)阻止,因?yàn)闀?huì)話信息只保存在某一個(gè)服務(wù)器上,當(dāng)請(qǐng)求沒有被轉(zhuǎn)發(fā)到這臺(tái)存儲(chǔ)了用戶信息的服務(wù)器時(shí),就會(huì)被認(rèn)為是非登錄的違規(guī)操作。
除此之外,無(wú)法共享緩存可能會(huì)造成系統(tǒng)資源的浪費(fèi),這是因?yàn)槊總€(gè)系統(tǒng)都單獨(dú)維護(hù)了一份屬于自己的緩存,而同一份緩存有可能被多個(gè)系統(tǒng)單獨(dú)進(jìn)行存儲(chǔ),從而浪費(fèi)了系統(tǒng)資源。
分布式緩存:指將應(yīng)用系統(tǒng)和緩存組件進(jìn)行分離的緩存機(jī)制,這樣多個(gè)應(yīng)用系統(tǒng)就可以共享一套緩存數(shù)據(jù)了,它的特點(diǎn)是共享緩存服務(wù)和可集群部署,為緩存系統(tǒng)提供了高可用的運(yùn)行環(huán)境,以及緩存共享的程序運(yùn)行機(jī)制。
本地緩存可以使用EhCache 和 Google 的 Guava來(lái)實(shí)現(xiàn),而分布式緩存可以使用 Redis 或 Memcached 來(lái)實(shí)現(xiàn)。
由于 Redis 本身就是獨(dú)立的緩存系統(tǒng),因此可以作為第三方來(lái)提供共享的數(shù)據(jù)緩存,而 Redis 的分布式支持主從、哨兵和集群的模式,所以它就可以支持分布式的緩存,而 Memcached 的情況也是類似的。
分布式緩存常見文件及解決方案
分布式緩存設(shè)計(jì)的核心問(wèn)題是以哪種方式進(jìn)行緩存預(yù)熱和緩存更新, 以及如何優(yōu)雅解決緩存雪崩、緩存穿透、緩存降級(jí)等問(wèn)題。這些問(wèn)題在不 同的應(yīng)用場(chǎng)景下有不同的解決方案。
緩存預(yù)熱: 緩存預(yù)熱指在用戶請(qǐng)求數(shù)據(jù)前先將數(shù)據(jù)加載到緩存系統(tǒng)中,用戶查詢 事先被預(yù)熱的緩存數(shù)據(jù),以提高系統(tǒng)查詢效率。緩存預(yù)熱一般有系統(tǒng)啟動(dòng) 加載、定時(shí)加載等方式。
緩存更新: 緩存更新指在數(shù)據(jù)發(fā)生變化后及時(shí)將變化后的數(shù)據(jù)更新到緩存中。常 見的緩存更新策略有以下4種。
- 定時(shí)更新:定時(shí)將底層數(shù)據(jù)庫(kù)內(nèi)的數(shù)據(jù)更新到緩存中,該方法比較 簡(jiǎn)單,適合需要緩存的數(shù)據(jù)量不是很大的應(yīng)用場(chǎng)景。
- 過(guò)期更新:定時(shí)將緩存中過(guò)期的數(shù)據(jù)更新為最新數(shù)據(jù)并更新緩存的 過(guò)期時(shí)間。
- 寫請(qǐng)求更新:在用戶有寫請(qǐng)求時(shí)先寫數(shù)據(jù)庫(kù)同時(shí)更新緩存,這適用 于用戶對(duì)緩存數(shù)據(jù)和數(shù)據(jù)庫(kù)的數(shù)據(jù)有實(shí)時(shí)強(qiáng)一致性要求的情況。
- 讀請(qǐng)求更新:在用戶有讀請(qǐng)求時(shí),先判斷該請(qǐng)求數(shù)據(jù)的緩存是否存 在或過(guò)期,如果不存在或已過(guò)期,則進(jìn)行底層數(shù)據(jù)庫(kù)查詢并將查詢結(jié)果更 新到緩存中,同時(shí)將查詢結(jié)果返回給用戶。
緩存淘汰策略 在緩存數(shù)據(jù)過(guò)多時(shí)需要使用某種淘汰算法決定淘汰哪些數(shù)據(jù)。常用的 淘汰算法有以下幾種。
- FIFO(First In First Out,先進(jìn)先出):判斷被存儲(chǔ)的時(shí)間,離 目前最遠(yuǎn)的數(shù)據(jù)優(yōu)先被淘汰。
- LRU(Least Recently Used,最近最少使用):判斷緩存最近被使 用的時(shí)間,距離當(dāng)前時(shí)間最遠(yuǎn)的數(shù)據(jù)優(yōu)先被淘汰。
- LFU(Least Frequently Used,最不經(jīng)常使用):在一段時(shí)間內(nèi), 被使用次數(shù)最少的緩存優(yōu)先被淘汰。
緩存雪崩
緩存雪崩指在同一時(shí)刻由于大量緩存失效,導(dǎo)致大量原本應(yīng)該訪問(wèn)緩 存的請(qǐng)求都去查詢數(shù)據(jù)庫(kù),而對(duì)數(shù)據(jù)庫(kù)的CPU和內(nèi)存造成巨大壓力,嚴(yán)重的 話會(huì)導(dǎo)致數(shù)據(jù)庫(kù)宕機(jī),從而形成一系列連鎖反應(yīng),使整個(gè)系統(tǒng)崩潰。
解決方案:
- 請(qǐng)求加鎖:對(duì)于并發(fā)量不是很多的應(yīng)用,使用請(qǐng)求加鎖排隊(duì)的方案 防止過(guò)多請(qǐng)求數(shù)據(jù)庫(kù)。
- 失效更新:為每一個(gè)緩存數(shù)據(jù)都增加過(guò)期標(biāo)記來(lái)記錄緩存數(shù)據(jù)是否 失效,如果緩存標(biāo)記失效,則更新數(shù)據(jù)緩存。
- 緩存數(shù)據(jù)的過(guò)期時(shí)間設(shè)置隨機(jī):為不同的數(shù)據(jù)設(shè)置不同的緩存失效時(shí)間,防止在同一時(shí)刻有大量的數(shù)據(jù)失效。
- 如果緩存數(shù)據(jù)庫(kù)是分布式部署,將熱點(diǎn)數(shù)據(jù)均勻分布在不同的緩存數(shù)據(jù)庫(kù)中。
- 設(shè)置熱點(diǎn)數(shù)據(jù)永遠(yuǎn)不過(guò)期。
緩存穿透
緩存穿透指由于緩存系統(tǒng)故障或者用戶頻繁查詢系統(tǒng)中不存在(在系 統(tǒng)中不存在,在自然數(shù)據(jù)庫(kù)和緩存中都不存在)的數(shù)據(jù),而這時(shí)請(qǐng)求穿過(guò) 緩存不斷被發(fā)送到數(shù)據(jù)庫(kù),導(dǎo)致數(shù)據(jù)庫(kù)過(guò)載,進(jìn)而引發(fā)一連串并發(fā)問(wèn)題。 比如用戶發(fā)起一個(gè)userName為zhangsan的請(qǐng)求,而在系統(tǒng)中并沒有名 為zhangsan的用戶,這樣就導(dǎo)致每次查詢時(shí)在緩存中都找不到該數(shù)據(jù),然 后去數(shù)據(jù)庫(kù)中再查詢一遍。由于zhangsan用戶本身在系統(tǒng)中不存在,自然 返回空,導(dǎo)致請(qǐng)求穿過(guò)緩存頻繁查詢數(shù)據(jù)庫(kù),在用戶頻繁發(fā)送該請(qǐng)求時(shí)將 導(dǎo)致數(shù)據(jù)庫(kù)系統(tǒng)負(fù)載增大,從而可能引發(fā)其他問(wèn)題。常用的解決緩存穿透 問(wèn)題的方法有布隆過(guò)濾器和cache null策略。
解決方案:
- 接口層增加校驗(yàn),如用戶鑒權(quán)校驗(yàn),id做基礎(chǔ)校驗(yàn),id<=0的直接攔截,一定不存在的不去查詢數(shù)據(jù)庫(kù)。
- 布隆過(guò)濾器:指將所有可能存在的數(shù)據(jù)都映射到一個(gè)足夠大的 Bitmap中,在用戶發(fā)起請(qǐng)求時(shí)首先經(jīng)過(guò)布隆過(guò)濾器的攔截,一個(gè)一定不存 在的數(shù)據(jù)會(huì)被這個(gè)布隆過(guò)濾器攔截,從而避免對(duì)底層存儲(chǔ)系統(tǒng)帶來(lái)查詢上 的壓力。
- cache null策略:指如果一個(gè)查詢返回的結(jié)果為null(可能是數(shù)據(jù) 不存在,也可能是系統(tǒng)故障),我們?nèi)匀痪彺孢@個(gè)null結(jié)果,但它的過(guò)期 時(shí)間會(huì)很短,通常不超過(guò) 5 分鐘;在用戶再次請(qǐng)求該數(shù)據(jù)時(shí)直接返回 null,而不會(huì)繼續(xù)訪問(wèn)數(shù)據(jù)庫(kù),從而有效保障數(shù)據(jù)庫(kù)的安全。其實(shí)cache null策略的核心原理是:在緩存中記錄一個(gè)短暫的(數(shù)據(jù)過(guò)期時(shí)間內(nèi))數(shù) 據(jù)在系統(tǒng)中是否存在的狀態(tài),如果不存在,則直接返回null,不再查詢數(shù) 據(jù)庫(kù),從而避免緩存穿透到數(shù)據(jù)庫(kù)上。
緩存擊穿
緩存擊穿是指緩存中沒有但數(shù)據(jù)庫(kù)中有的數(shù)據(jù)(一般是緩存時(shí)間到期),這時(shí)由于并發(fā)用戶特別多,同時(shí)讀緩存沒讀到數(shù)據(jù),又同時(shí)去數(shù)據(jù)庫(kù)去取數(shù)據(jù),引起數(shù)據(jù)庫(kù)壓力瞬間增大,造成過(guò)大壓力
解決方案:
- 設(shè)置熱點(diǎn)數(shù)據(jù)永遠(yuǎn)不過(guò)期。
- 加互斥鎖。
緩存降級(jí)
緩存降級(jí)指由于訪問(wèn)量劇增導(dǎo)致服務(wù)出現(xiàn)問(wèn)題(如響應(yīng)時(shí)間慢或不響 應(yīng))時(shí),優(yōu)先保障核心業(yè)務(wù)的運(yùn)行,減少或關(guān)閉非核心業(yè)務(wù)對(duì)資源的使 用。
服務(wù)降級(jí)策略:
- 寫降級(jí):在寫請(qǐng)求增大時(shí),可以只進(jìn)行Cache的更新,然后將數(shù)據(jù) 異步更新到數(shù)據(jù)庫(kù)中,保證最終一致性即可,即將寫請(qǐng)求從數(shù)據(jù)庫(kù)降級(jí)為 Cache。
- 讀降級(jí):在數(shù)據(jù)庫(kù)服務(wù)負(fù)載過(guò)高或數(shù)據(jù)庫(kù)系統(tǒng)故障時(shí),可以只對(duì) Cache進(jìn)行讀取并將結(jié)果返回給用戶,在數(shù)據(jù)庫(kù)服務(wù)正常后再去查詢數(shù)據(jù) 庫(kù),即將讀請(qǐng)求從數(shù)據(jù)庫(kù)降級(jí)為Cache。這種方式適用于對(duì)數(shù)據(jù)實(shí)時(shí)性要求 不高的場(chǎng)景,保障了在系統(tǒng)發(fā)生故障的情況下用戶依然能夠訪問(wèn)到數(shù)據(jù), 只是訪問(wèn)到的數(shù)據(jù)相對(duì)有延遲。
一、在pom中添加依賴
<!--springboot redis依賴--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
二、在配置文件中配置Redis連接
# Redis數(shù)據(jù)庫(kù)索引(默認(rèn)為0) spring.redis.database=0 # Redis服務(wù)器地址 spring.redis.host=127.0.0.1 # Redis服務(wù)器連接端口 spring.redis.port=6379 # Redis服務(wù)器連接密碼(默認(rèn)為空) spring.redis.password=123456 # 連接池最大連接數(shù)(使用負(fù)值表示沒有限制) spring.redis.jedis.pool.max-idle=10 # 連接池最大阻塞等待時(shí)間(使用負(fù)值表示沒有限制) spring.redis.jedis.pool.max-wait=3000 # 連接池中的最小空閑連接 spring.redis.jedis.pool.min-idle=0 # 連接超時(shí)時(shí)間(毫秒) spring.redis.timeout=3000
三、編寫Redis配置文件
@Configuration @EnableCaching public class RedisConfig extends CachingConfigurerSupport { /** * RedisTemplate相關(guān)配置 * 使redis支持插入對(duì)象 * * @param factory * @return 方法緩存 Methods the cache */ @Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) { RedisTemplate<String, Object> template = new RedisTemplate<>(); // 配置連接工廠 template.setConnectionFactory(factory); // 序列化和反序列化redis的value值(默認(rèn)使用JDK的序列化方式) Jackson2JsonRedisSerializer jacksonSerializer = 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等會(huì)跑出異常 om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); jacksonSerializer.setObjectMapper(om); // 值采用json序列化 template.setValueSerializer(jacksonSerializer); // 使用StringRedisSerializer來(lái)序列化和反序列化redis的key值 template.setKeySerializer(new StringRedisSerializer()); // 設(shè)置hash key 和value序列化模式 template.setHashKeySerializer(new StringRedisSerializer()); template.setHashValueSerializer(jacksonSerializer); template.afterPropertiesSet(); return template; } }
四、測(cè)試Redis緩存
/** * @Author: oyc * @Description: redis 測(cè)試控制類 * @Since 2020年5月12日 23:35:05 */ @RestController @RequestMapping("/redis") public class OyRedisController { /** * 依賴注入,注入redisTemplate */ @Autowired private RedisTemplate redisTemplate; /** * 測(cè)試redis string add */ @GetMapping("/string/add") public String addStringKeyValue(@RequestParam(value = "key", defaultValue = "key1") String key, @RequestParam(value = "value", defaultValue = "redis value") String value) { redisTemplate.opsForValue().set(key, value); return (String) redisTemplate.opsForValue().get(key); } /** * 測(cè)試redis string add */ @GetMapping("/object/add") public Object addObjectKeyValue(@RequestParam(value = "key", defaultValue = "key1") String key) { OyUser user = new OyUser(1, "宋江", "18", "male"); redisTemplate.opsForValue().set(key, user); return redisTemplate.opsForValue().get(key); } /** * 測(cè)試redis string get */ @GetMapping("/string/get") public Object getStringByKey(@RequestParam(value = "key", defaultValue = "key1") String key) { return redisTemplate.opsForValue().get(key); } }
4.1 測(cè)試key、value 為string
4.1.1 使用接口往redis添加記錄:
4.1.2 使用IDEA的redis客戶端插件查看記錄:
4.1.3 使用接口獲取緩存數(shù)據(jù):
4.2 測(cè)試key為string,value 為對(duì)象
五、工具類
上面案例都是直接用RedisTemplate操作Redis,操作比較復(fù)雜,借鑒網(wǎng)友寫了一個(gè)RedisUtils工具類,RedisUtils交給Spring容器實(shí)例化,使用時(shí)直接注解注入,使用方便簡(jiǎn)單,減少使用難度。
/** * @Description: redisTemplate封裝 * Redis支持五種數(shù)據(jù)類型:string(字符串),hash(哈希),list(列表),set(集合)及zset(sorted set:有序集合)。 * @Author: oyc * @Since 2020年5月12日 23:41:08 */ @Component public class RedisUtil { @Autowired private RedisTemplate<String, Object> redisTemplate; public RedisUtil(RedisTemplate<String, Object> redisTemplate) { this.redisTemplate = redisTemplate; } //==========================基本操作=============================== /** * 指定緩存失效時(shí)間 * * @param key 鍵 * @param time 時(shí)間(秒) * @return */ public boolean expire(String key, long time) { try { if (time > 0) { redisTemplate.expire(key, time, TimeUnit.SECONDS); } return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 根據(jù)key 獲取過(guò)期時(shí)間 * * @param key 鍵 不能為null * @return 時(shí)間(秒) 返回0代表為永久有效 */ public long getExpire(String key) { return redisTemplate.getExpire(key, TimeUnit.SECONDS); } /** * 判斷key是否存在 * * @param key 鍵 * @return true 存在 false不存在 */ public boolean hasKey(String key) { try { return redisTemplate.hasKey(key); } catch (Exception e) { e.printStackTrace(); return false; } } /** * 刪除緩存 * * @param key 可以傳一個(gè)值 或多個(gè) */ public void del(String... key) { if (key != null && key.length > 0) { if (key.length == 1) { redisTemplate.delete(key[0]); } else { redisTemplate.delete(CollectionUtils.arrayToList(key)); } } } /** * 模糊查詢獲取key值 * * @param pattern * @return */ public Set keys(String pattern) { return redisTemplate.keys(pattern); } /** * 使用Redis的消息隊(duì)列 * * @param channel * @param message 消息內(nèi)容 */ public void convertAndSend(String channel, Object message) { redisTemplate.convertAndSend(channel, message); } //============================String============================= /** * 普通緩存獲取 * * @param key 鍵 * @return 值 */ public Object get(String key) { return key == null ? null : redisTemplate.opsForValue().get(key); } /** * 普通緩存放入 * * @param key 鍵 * @param value 值 * @return true成功 false失敗 */ public boolean set(String key, Object value) { try { redisTemplate.opsForValue().set(key, value); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 普通緩存放入并設(shè)置時(shí)間 * * @param key 鍵 * @param value 值 * @param time 時(shí)間(秒) time要大于0 如果time小于等于0 將設(shè)置無(wú)限期 * @return true成功 false 失敗 */ public boolean set(String key, Object value, long time) { try { if (time > 0) { redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS); } else { set(key, value); } return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 遞增 * * @param key 鍵 * @param delta 要增加幾(大于0) * @return */ public long incr(String key, long delta) { if (delta < 0) { throw new RuntimeException("遞增因子必須大于0"); } return redisTemplate.opsForValue().increment(key, delta); } /** * 遞減 * * @param key 鍵 * @param delta 要減少幾(小于0) * @return */ public long decr(String key, long delta) { if (delta < 0) { throw new RuntimeException("遞減因子必須大于0"); } return redisTemplate.opsForValue().increment(key, -delta); } //================================hash================================= /** * 向一張hash表中放入數(shù)據(jù),如果不存在將創(chuàng)建 * * @param key 鍵 * @param item 項(xiàng) * @param value 值 * @return true 成功 false失敗 */ public boolean hset(String key, String item, Object value) { try { redisTemplate.opsForHash().put(key, item, value); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 向一張hash表中放入數(shù)據(jù),如果不存在將創(chuàng)建 * * @param key 鍵 * @param item 項(xiàng) * @param value 值 * @param time 時(shí)間(秒) 注意:如果已存在的hash表有時(shí)間,這里將會(huì)替換原有的時(shí)間 * @return true 成功 false失敗 */ public boolean hset(String key, String item, Object value, long time) { try { redisTemplate.opsForHash().put(key, item, value); if (time > 0) { expire(key, time); } return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * HashGet * * @param key 鍵 不能為null * @param item 項(xiàng) 不能為null * @return 值 */ public Object hget(String key, String item) { return redisTemplate.opsForHash().get(key, item); } /** * 刪除hash表中的值 * * @param key 鍵 不能為null * @param item 項(xiàng) 可以使多個(gè) 不能為null */ public void hdel(String key, Object... item) { redisTemplate.opsForHash().delete(key, item); } /** * 獲取hashKey對(duì)應(yīng)的所有鍵值 * * @param key 鍵 * @return 對(duì)應(yīng)的多個(gè)鍵值 */ public Map<Object, Object> hmget(String key) { return redisTemplate.opsForHash().entries(key); } /** * HashSet * * @param key 鍵 * @param map 對(duì)應(yīng)多個(gè)鍵值 * @return true 成功 false 失敗 */ public boolean hmset(String key, Map<String, Object> map) { try { redisTemplate.opsForHash().putAll(key, map); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * HashSet 并設(shè)置時(shí)間 * * @param key 鍵 * @param map 對(duì)應(yīng)多個(gè)鍵值 * @param time 時(shí)間(秒) * @return true成功 false失敗 */ public boolean hmset(String key, Map<String, Object> map, long time) { try { redisTemplate.opsForHash().putAll(key, map); if (time > 0) { expire(key, time); } return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 判斷hash表中是否有該項(xiàng)的值 * * @param key 鍵 不能為null * @param item 項(xiàng) 不能為null * @return true 存在 false不存在 */ public boolean hHasKey(String key, String item) { return redisTemplate.opsForHash().hasKey(key, item); } /** * hash遞增 如果不存在,就會(huì)創(chuàng)建一個(gè) 并把新增后的值返回 * * @param key 鍵 * @param item 項(xiàng) * @param by 要增加幾(大于0) * @return */ public double hincr(String key, String item, double by) { return redisTemplate.opsForHash().increment(key, item, by); } /** * hash遞減 * * @param key 鍵 * @param item 項(xiàng) * @param by 要減少記(小于0) * @return */ public double hdecr(String key, String item, double by) { return redisTemplate.opsForHash().increment(key, item, -by); } //============================set============================= /** * 根據(jù)key獲取Set中的所有值 * * @param key 鍵 * @return */ public Set<Object> sGet(String key) { try { return redisTemplate.opsForSet().members(key); } catch (Exception e) { e.printStackTrace(); return null; } } /** * 根據(jù)value從一個(gè)set中查詢,是否存在 * * @param key 鍵 * @param value 值 * @return true 存在 false不存在 */ public boolean sHasKey(String key, Object value) { try { return redisTemplate.opsForSet().isMember(key, value); } catch (Exception e) { e.printStackTrace(); return false; } } /** * 將數(shù)據(jù)放入set緩存 * * @param key 鍵 * @param values 值 可以是多個(gè) * @return 成功個(gè)數(shù) */ public long sSet(String key, Object... values) { try { return redisTemplate.opsForSet().add(key, values); } catch (Exception e) { e.printStackTrace(); return 0; } } /** * 將set數(shù)據(jù)放入緩存 * * @param key 鍵 * @param time 時(shí)間(秒) * @param values 值 可以是多個(gè) * @return 成功個(gè)數(shù) */ public long sSetAndTime(String key, long time, Object... values) { try { Long count = redisTemplate.opsForSet().add(key, values); if (time > 0) { expire(key, time); } return count; } catch (Exception e) { e.printStackTrace(); return 0; } } /** * 獲取set緩存的長(zhǎng)度 * * @param key 鍵 * @return */ public long sGetSetSize(String key) { try { return redisTemplate.opsForSet().size(key); } catch (Exception e) { e.printStackTrace(); return 0; } } /** * 移除值為value的 * * @param key 鍵 * @param values 值 可以是多個(gè) * @return 移除的個(gè)數(shù) */ public long setRemove(String key, Object... values) { try { Long count = redisTemplate.opsForSet().remove(key, values); return count; } catch (Exception e) { e.printStackTrace(); return 0; } } //===============================list================================= /** * 獲取list緩存的內(nèi)容 * * @param key 鍵 * @param start 開始 * @param end 結(jié)束 0 到 -1代表所有值 * @return */ public List<Object> lGet(String key, long start, long end) { try { return redisTemplate.opsForList().range(key, start, end); } catch (Exception e) { e.printStackTrace(); return null; } } /** * 獲取list緩存的長(zhǎng)度 * * @param key 鍵 * @return */ public long lGetListSize(String key) { try { return redisTemplate.opsForList().size(key); } catch (Exception e) { e.printStackTrace(); return 0; } } /** * 通過(guò)索引 獲取list中的值 * * @param key 鍵 * @param index 索引 index>=0時(shí), 0 表頭,1 第二個(gè)元素,依次類推;index<0時(shí),-1,表尾,-2倒數(shù)第二個(gè)元素,依次類推 * @return */ public Object lGetIndex(String key, long index) { try { return redisTemplate.opsForList().index(key, index); } catch (Exception e) { e.printStackTrace(); return null; } } /** * 將list放入緩存 * * @param key 鍵 * @param value 值 * @return */ public boolean lSet(String key, Object value) { try { redisTemplate.opsForList().rightPush(key, value); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 將list放入緩存 * * @param key 鍵 * @param value 值 * @param time 時(shí)間(秒) * @return */ public boolean lSet(String key, Object value, long time) { try { redisTemplate.opsForList().rightPush(key, value); if (time > 0) { expire(key, time); } return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 將list放入緩存 * * @param key 鍵 * @param value 值 * @return */ public boolean lSet(String key, List<Object> value) { try { redisTemplate.opsForList().rightPushAll(key, value); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 將list放入緩存 * * @param key 鍵 * @param value 值 * @param time 時(shí)間(秒) * @return */ public boolean lSet(String key, List<Object> value, long time) { try { redisTemplate.opsForList().rightPushAll(key, value); if (time > 0) { expire(key, time); } return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 根據(jù)索引修改list中的某條數(shù)據(jù) * * @param key 鍵 * @param index 索引 * @param value 值 * @return */ public boolean lUpdateIndex(String key, long index, Object value) { try { redisTemplate.opsForList().set(key, index, value); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 移除N個(gè)值為value * * @param key 鍵 * @param count 移除多少個(gè) * @param value 值 * @return 移除的個(gè)數(shù) */ public long lRemove(String key, long count, Object value) { try { Long remove = redisTemplate.opsForList().remove(key, count, value); return remove; } catch (Exception e) { e.printStackTrace(); return 0; } } }
源碼: https://github.com/oycyqr/springboot-learning-demo/tree/master/springboot-redis
到此這篇關(guān)于SpringBoot+Redis 實(shí)現(xiàn)分布式緩存的方法步驟的文章就介紹到這了,更多相關(guān)SpringBoot Redis 分布式緩存內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
在啟動(dòng)后臺(tái) jar包時(shí),使用指定的 application.yml操作
這篇文章主要介紹了在啟動(dòng)后臺(tái) jar包時(shí),使用指定的 application.yml操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-10-10MyBatisPlus條件構(gòu)造器圖文實(shí)例詳解
這篇文章主要介紹了MyBatisPlus條件構(gòu)造器,了解內(nèi)部原理是為了幫助我們做擴(kuò)展,同時(shí)也是驗(yàn)證了一個(gè)人的學(xué)習(xí)能力,如果你想讓自己的職業(yè)道路更上一層樓,這些底層的東西你是必須要會(huì)的2023-01-01springboot2?使用activiti6?idea插件的過(guò)程詳解
這篇文章主要介紹了springboot2?使用activiti6?idea插件,本文通過(guò)截圖實(shí)例代碼相結(jié)合給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-03-03FastJson時(shí)間格式化問(wèn)題避坑經(jīng)驗(yàn)分享
這篇文章主要為大家介紹了FastJson時(shí)間格式化問(wèn)題避坑經(jīng)驗(yàn)分享,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-08-08SpringBoot項(xiàng)目Pom文件的基本配置方式
這篇文章主要介紹了SpringBoot項(xiàng)目Pom文件的基本配置方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-06-06

Java靜態(tài)代理和動(dòng)態(tài)代理總結(jié)

Spring組件初始化擴(kuò)展點(diǎn)BeanPostProcessor的作用詳解