Redis 是目前使用最普遍的緩存中間件,相比 Memcached,Redis 支持更多的數據結構和更豐富的數據操做,另外 Redis 有着豐富的集羣方案和使用場景,這一課咱們一塊兒學習 Redis 的經常使用操做。git
Redis 是一個速度很是快的非關係數據庫(Non-Relational Database),它能夠存儲鍵(Key)與 5 種不一樣類型的值(Value)之間的映射(Mapping),能夠將存儲在內存的鍵值對數據持久化到硬盤,可使用複製特性來擴展讀性能,還可使用客戶端分片來擴展寫性能。github
爲了知足高性能,Redis 採用內存(in-memory)數據集(Dataset),根據使用場景,能夠經過每隔一段時間轉儲數據集到磁盤,或者追加每條命令到日誌來持久化。持久化也能夠被禁用,若是你只是須要一個功能豐富、網絡化的內存緩存。redis
數據模型spring
Redis 數據模型不只與關係數據庫管理系統(RDBMS)不一樣,也不一樣於任何簡單的 NoSQL 鍵-值數據存儲。Redis 數據類型相似於編程語言的基礎數據類型,所以開發人員感受很天然,每一個數據類型都支持適用於其類型的操做,受支持的數據類型包括:數據庫
關鍵優點apache
Redis 的優點包括它的速度、對富數據類型的支持、操做的原子性,以及通用性:編程
Spring Boot 提供了對 Redis 集成的組件包:spring-boot-starter-data-redis,它依賴於 spring-data-redis 和 lettuce。Spring Boot 1.0 默認使用的是 Jedis 客戶端,2.0 替換成了 Lettuce,但若是你從 Spring Boot 1.5.X 切換過來,幾乎感覺不大差別,這是由於 spring-boot-starter-data-redis 爲咱們隔離了其中的差別性。緩存
能夠用如下方式來表達它們之間的關係:安全
Lettuce→ Spring Data Redis → Spring Data → spring-boot-starter-data-redis
所以 Spring Data Redis 和 Lettuce 具有的功能,spring-boot-starter-data-redis 幾乎都會有。服務器
引入依賴包
<dependency>
<groupId></groupId> org.springframework.boot
<artifactId></artifactId> spring-boot-starter-data-redis
</dependency>
<dependency>
<groupId></groupId> org.apache.commons
<artifactId></artifactId> commons-pool2
</dependency>
引入 commons-pool 2 是由於 Lettuce 須要使用 commons-pool 2 建立 Redis 鏈接池。
application 配置
# Redis 數據庫索引(默認爲 0)
0spring.redis.database=
# Redis 服務器地址
spring.redis.host=localhost
# Redis 服務器鏈接端口
6379spring.redis.port=
# Redis 服務器鏈接密碼(默認爲空)
spring.redis.password=
# 鏈接池最大鏈接數(使用負值表示沒有限制)默認 8
8spring.redis.lettuce.pool.max-active=
# 鏈接池最大阻塞等待時間(使用負值表示沒有限制)默認 -1
wait1spring.redis.lettuce.pool.max-=-
# 鏈接池中的最大空閒鏈接默認 8
8spring.redis.lettuce.pool.max-idle=
# 鏈接池中的最小空閒鏈接默認 0
0spring.redis.lettuce.pool.min-idle=
從配置也能夠看出 Spring Boot 默認支持 Lettuce 鏈接池。
在這裏能夠爲 Redis 設置一些全局配置,好比配置主鍵的生產策略 KeyGenerator,如不配置會默認使用參數名做爲主鍵。
@Configuration
@EnableCaching
publicclass RedisConfig extends CachingConfigurerSupport{
@Bean
public KeyGenerator keyGenerator() {
returnnew KeyGenerator() {
@Override
public Object generate(Object target, Method method, Object... params) {
new StringBuilder sb =StringBuilder();
sb.append(target.getClass().getName());
sb.append(method.getName());
for (Object obj : params) {
sb.append(obj.toString());
}
return sb.toString();
}
};
}
}
注意,咱們使用了註解:@EnableCaching 來開啓緩存。
在單元測試中,注入 RedisTemplate。String 是最經常使用的一種數據類型,普通的 key/value 存儲均可以歸爲此類,value 其實不只是 String 也能夠是數字。
@RunWith(SpringRunner.class)
@SpringBootTest
publicclass TestRedisTemplate {
@Autowired
private RedisTemplate redisTemplate;
@Test
public void testString() {
"neo""ityouknow" redisTemplate.opsForValue().set(,);
"ityouknow""neo" Assert.assertEquals(, redisTemplate.opsForValue().get());
}
}
在這個單元測試中,咱們使用 redisTemplate 存儲了一個字符串 "ityouknow",存儲以後獲取進行驗證,屢次進行 set 相同的 key,鍵對應的值會被覆蓋。
從上面的整個流程來看,使用 spring-boot-starter-data-redis 只須要三步就能夠快速地集成 Redis 進行操做,下面介紹 Redis 如何操做各類數據類型。
咱們知道 Redis 支持多種數據類型,實體、哈希、列表、集合、有序集合,那麼在 Spring Boot 體系中都如何使用呢?
先來看 Redis 對 Pojo 的支持,新建一個 User 對象,放到緩存中,再取出來。
Test@
public void testObj(){
new"ityouknow@126.com""smile""youknow""know""2020" User user=User(,,,,);
ValueOperations<String, User> operations=redisTemplate.opsForValue();
set"com.neo" operations.(, user);
get"com.neo" User u=operations.();
out"user: " System..println(+u.toString());
}
輸出結果:
usercom.neo.domain.User16fb356idnulluserNameknowpassWordyouknowemailityouknow126comnickNamesmileregTime2020:@[=<>,=,=,=@.,=,=]
驗證發現完美支持對象的存入和讀取。
Redis 在存入每個數據的時候均可以設置一個超時時間,過了這個時間就會自動刪除數據,這種特性很是適合咱們對階段數據的緩存。
新建一個 User 對象,存入 Redis 的同時設置 100 毫秒後失效,設置一個線程暫停 1000 毫秒以後,判斷數據是否存在並打印結果。
Test@
public void testExpire() throws InterruptedException {
new"ityouknow@126.com""expire""youknow""expire""2020" User user=User(,,,,);
ValueOperations<String, User> operations=redisTemplate.opsForValue();
set"expire"100 operations.(, user,,TimeUnit.MILLISECONDS);
1000 Thread.sleep();
"expire" boolean exists=redisTemplate.hasKey();
if (exists){
out"exists is true" System..println();
else }{
out"exists is false" System..println();
}
}
輸出結果:
isfalseexists
從結果能夠看出,Reids 中已經不存在 User 對象了,此數據已通過期,同時咱們在這個測試的方法中使用了 hasKey("expire") 方法,能夠判斷 key 是否存在。
有些時候,咱們須要對過時的緩存進行刪除,下面來測試此場景的使用。首 set 一個字符串「ityouknow」,緊接着刪除此 key 的值,再進行判斷。
Test@
public void testDelete() {
ValueOperations<String, User> operations=redisTemplate.opsForValue();
set"deletekey""ityouknow" redisTemplate.opsForValue().(,);
"deletekey" redisTemplate.delete();
"deletekey" boolean exists=redisTemplate.hasKey();
if (exists){
out"exists is true" System..println();
else }{
out"exists is false" System..println();
}
}
輸出結果:
isfalseexists
結果代表字符串「ityouknow」已經被成功刪除。
通常咱們存儲一個鍵,很天然的就會使用 get/set 去存儲,實際上這並非很好的作法。Redis 存儲一個 key 會有一個最小內存,無論你存的這個鍵多小,都不會低於這個內存,所以合理的使用 Hash 能夠幫咱們節省不少內存。
Hash Set 就在哈希表 Key 中的域(Field)的值設爲 value。若是 Key 不存在,一個新的哈希表被建立並進行 Hset 操做;若是域(Field)已經存在於哈希表中,舊值將被覆蓋。
Test@
public void testHash() {
HashOperations<String, Object, Object> hash = redisTemplate.opsForHash();
"hash""you""you" hash.put(,,);
valueget"hash""you" String=(String) hash.(,);
out"hash value :"value System..println(+);
}
輸出結果:
hashvalue :you
根據上面測試用例發現,Hash set 的時候須要傳入三個參數,第一個爲 key,第二個爲 Field,第三個爲存儲的值。通常狀況下 Key 表明一組數據,Field 爲 key 相關的屬性,而 Value 就是屬性對應的值。
Redis List 的應用場景很是多,也是 Redis 最重要的數據結構之一。 使用 List 能夠輕鬆的實現一個隊列,List 典型的應用場景就是消息隊列,能夠利用 List 的 Push 操做,將任務存在 List 中,而後工做線程再用 POP 操做將任務取出進行執行。
Test@
public void testList() {
list ListOperations<String, String>= redisTemplate.opsForList();
list"list""it" .leftPush(,);
list"list""you" .leftPush(,);
list"list""know" .leftPush(,);
list"list" String value=(String).leftPop();
"list value :" System.out.println(+value.toString());
}
輸出結果:
listvalue :know
上面的例子咱們從左側插入一個 key 爲 "list" 的隊列,而後取出左側最近的一條數據。其實 List 有不少 API 能夠操做,好比從右側進行插入隊列從右側進行讀取,或者經過方法 range 讀取隊列的一部分。接着上面的例子咱們使用 range 來讀取。
String"list"02List<> values=list.range(,,);
forString (v:values){
"list range :" System.out.println(+v);
}
輸出結果:
listrange :know
listrange :you
listrange :it
range 後面的兩個參數就是插入數據的位置,輸入不一樣的參數就能夠取出隊列中對應的數據。
Redis List 的實現爲一個雙向鏈表,便可以支持反向查找和遍歷,更方便操做,不過帶來了部分額外的內存開銷,Redis 內部的不少實現,包括髮送緩衝隊列等也都是用的這個數據結構。
Redis Set 對外提供的功能與 List 相似是一個列表的功能,特殊之處在於 Set 是能夠自動排重的,當你須要存儲一個列表數據,又不但願出現重複數據時,Set 是一個很好的選擇,而且 Set 提供了判斷某個成員是否在一個 Set 集合內的重要接口,這個也是 List 所不能提供的。
Test@
public void testSet() {
"set" String key=;
set SetOperations<String, String>= redisTemplate.opsForSet();
setadd"it" .(key,);
setadd"you" .(key,);
setadd"you" .(key,);
setadd"know" .(key,);
set Set<String> values=.members(key);
for (String v:values){
out"set value :" System..println(+v);
}
}
輸出結果:
setvalue:it
setvalue:know
setvalue:you
經過上面的例子咱們發現,輸入了兩個相同的值「you」,所有讀取的時候只剩下了一條,說明 Set 對隊列進行了自動的排重操做。
Redis 爲集合提供了求交集、並集、差集等操做,能夠很是方便的使用。
測試 difference
setSetOperations<String, String>= redisTemplate.opsForSet();
"setMore1"String key1=;
"setMore2"String key2=;
setadd"it".(key1,);
setadd"you".(key1,);
setadd"you".(key1,);
setadd"know".(key1,);
setadd"xx".(key2,);
setadd"know".(key2,);
setSet<String> diffs=.difference(key1,key2);
for(String v:diffs){
out"diffs set value :" System..println(+v);
}
輸出結果:
setvaluediffs:it
setvaluediffs:you
根據上面這個例子能夠看出,difference() 函數會把 key 1 中不一樣於 key 2 的數據對比出來,這個特性適合咱們在金融場景中對帳的時候使用。
測試 unions
setSetOperations<String, String>= redisTemplate.opsForSet();
"setMore3"String key3=;
"setMore4"String key4=;
setadd"it".(key3,);
setadd"you".(key3,);
setadd"xx".(key3,);
setadd"aa".(key4,);
setadd"bb".(key4,);
setadd"know".(key4,);
setSet<String> unions=.union(key3,key4);
for(String v:unions){
out"unions value :" System..println(+v);
}
輸出結果:
valueunions:know
valueunions:you
valueunions:xx
valueunions:it
valueunions:bb
valueunions:aa
根據例子咱們發現,unions 會取兩個集合的合集,Set 還有其餘不少相似的操做,很是方便咱們對集合進行數據處理。
Set 的內部實現是一個 Value 永遠爲 null 的 HashMap,實際就是經過計算 Hash 的方式來快速排重,這也是 Set 能提供判斷一個成員是否在集合內的緣由。
Redis Sorted Set 的使用場景與 Set 相似,區別是 Set 不是自動有序的,而 Sorted Set 能夠經過用戶額外提供一個優先級(Score)的參數來爲成員排序,而且是插入有序,即自動排序。
在使用 Zset 的時候須要額外的輸入一個參數 Score,Zset 會自動根據 Score 的值對集合進行排序,咱們能夠利用這個特性來作具備權重的隊列,好比普通消息的 Score 爲1,重要消息的 Score 爲 2,而後工做線程能夠選擇按 Score 的倒序來獲取工做任務。
@Test
voidpublictestZset(){
String"zset" key=;
redisTemplate.delete(key);
StringString ZSetOperations<,> zset = redisTemplate.opsForZSet();
"it"1 zset.add(key,,);
"you"6 zset.add(key,,);
"know"4 zset.add(key,,);
"neo"3 zset.add(key,,);
SetString03 <> zsets=zset.range(key,,);
forString (v:zsets){
"zset value :" System.out.println(+v);
}
SetString03 <> zsetB=zset.rangeByScore(key,,);
forString (v:zsetB){
"zsetB value :" System.out.println(+v);
}
}
輸出結果:
valuezset:it
valuezset:neo
valuezset:know
valuezset:you
valuezsetB:it
valuezsetB:neo
經過上面的例子咱們發現插入到 Zset 的數據會自動根據 Score 進行排序,根據這個特性咱們能夠作優先隊列等各類常見的場景。另外 Redis 還提供了 rangeByScore 這樣的一個方法,能夠只獲取 Score 範圍內排序後的數據。
Redis Sorted Set 的內部使用 HashMap 和跳躍表(SkipList)來保證數據的存儲和有序,HashMap 裏放的是成員到 Score 的映射,而跳躍表裏存放的是全部的成員,排序依據是 HashMap 裏存的 Score,使用跳躍表的結構能夠得到比較高的查找效率,而且在實現上比較簡單。
在咱們實際的使用過程當中,不會給每個使用的類都注入 redisTemplate 來直接使用,通常都會對業務進行簡單的包裝,最後提供出來對外使用。
咱們舉兩個例子說明。
首先定義一個 RedisService 服務,將 RedisTemplate 注入到類中。
@Service
publicclass RedisService {
@Autowired
private RedisTemplate redisTemplate;
}
封裝簡單插入操做:
public boolean set(final String key, Object value) {
false boolean result =;
try {
ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
setvalue operations.(key,);
true result =;
catch }(Exception e) {
"set error: key {}, value {}"value logger.error(,key,,e);
}
return result;
}
會對其中出現的異常繼續處理,反饋給調用方。
好比咱們想刪除某一類的 Key 的值。
public void removePattern(final String pattern) {
Set<Serializable> keys = redisTemplate.keys(pattern);
if0 (keys.size() >)
delete redisTemplate.(keys);
}
使用 Redis 的 Pattern 來匹配出一批符合條件的緩存,而後批量進行刪除。
還有其餘封裝方法,好比刪除的時候先判斷 Key 是否存在等,這些簡單的業務判斷都應該封裝在 RedisService,對外提供最簡單的 API 調用便可。
@Autowired
privateRedisService redisService;
@Test
public void testString() throws Exception {
"neo""ityouknow" redisService.set(,);
"ityouknow""neo" Assert.assertEquals(, redisService.get());
}
在其餘服務使用的時候將 RedisService 注入其中,調用對應的方法來操做 Redis,這樣會更優雅簡單一些。
Redis 是一款很是優秀的高性能緩存中間件,被普遍的使用在各互聯網公司中,Spring Boot 對 Redis 的操做提供了不少支持,能夠很是方便的去集成。Redis 擁有豐富的數據類型,方便咱們在不一樣的業務場景中去使用,特別是提供了不少內置的高效集合操做,在業務中使用很是方便。