Redis是大規模互聯網應用常用的記憶體高速緩存資料庫,它的讀寫速度非常快,據官方 Bench-mark的數據,它讀的速度能到11萬次/秒,寫的速度是8.1萬次/秒。 1. 認識Spring Cache 在很多應用場景中通常是獲取前後相同或更新不頻繁的數據,比如訪問產品信息數據、網頁數據。如果沒有使用 ...
Redis是大規模互聯網應用常用的記憶體高速緩存資料庫,它的讀寫速度非常快,據官方 Bench-mark的數據,它讀的速度能到11萬次/秒,寫的速度是8.1萬次/秒。
1. 認識Spring Cache
在很多應用場景中通常是獲取前後相同或更新不頻繁的數據,比如訪問產品信息數據、網頁數據。如果沒有使用緩存,則訪問每次需要重覆請求資料庫,這會導致大部分時間都耗費在資料庫查詢和方法調用上,因為資料庫進行I/O操作非常耗費時間,這時就可以利用Spring Cache來解決。
Spring Cache是Spring提供的一整套緩存解決方案。它本身並不提供緩存實現,而是提供統 一的介面和代碼規範、配置、註解等,以便整合各種Cache方案,使用戶不用關心Cache的細節。
Spring支持“透明”地嚮應用程式添加緩存,將緩存應用於方法,在方法執行前檢查緩存中是否有可用的數據。這樣可以減少方法執行的次數,同時提高響應的速度。緩存的應用方式“透明”, 不會對調用者造成任何干擾。只要通過註解@EnableCaching啟用了緩存支持,Spring Boot就會自動處理好緩存的基礎配置。
Spring Cache作用在方法上。當調用一個緩存方法時,會把該方法參數和返回結果作為一個 “鍵值對”(key/value )存放在緩存中,下次用同樣的參數來調用該方法時將不再執行該方法,而是直接從緩存中獲取結果進行返回。所以在使用Spring Cache 時,要保證在緩存的方法和方法參數相同時返回相同的結果。
1.1 聲明式緩存註解
Spring Boot提供的聲明式緩存(cache)註解,見表11-1。
圖 11-1
1.@EnableCaching
標註在入口類上,用於開啟緩存。
2.@Cacheable
可以作用在類和方法上,以鍵值對的方式緩存類或方法的返回值。鍵可以有預設策略和自定義策略。
@Cacheable註解會先查詢是否己經有緩存,如果己有則會使用緩存,如果沒有則會執行方法併進行緩存。
@Cacheabfe 可以指定 3 個屬性----- value、key 和 condition。
-
- value :緩存的名稱,在Spring配置文件中定義,必須指定至少一個。如, @Cacheable(value= "cache1" )、@Cacheable(value={ "cache1" , "cache2n }。
- key:緩存的key可以為空,如果自定義key,則要按照SpEL表達式編寫。可以自動按照方法的參數組合。如,@Cacheable(value= "cache1",key= "#id” )
- condition:緩存的條件可以為空,如果自定義condition,則使用SpEL表達式輻寫,以返 回true或false值,只有返回true才進行緩存。如,@Cacheable(value= “cache1" ,condition= "#id.length()>2" )。
@Cacheable註解的使用方法見以下代碼:
@Override
@Cacheable(value = "emp",key = "targetClass + methodName + #p0")
public List<Card> getCardList() {
return cardRepository.findAll();
}
代碼解釋如下。
- value是必需的,它指定了緩存存放的位置。
- key使用的是SpEL表達式。
- User實體類一定要實現序列化,否則會報“java.io.NotSerializableException”異常。序 列化可以繼承 Serializable,如 public class User implements Serializable。
3. @CachePut
@CachePut標註的方法在執行前不檢查緩存中是否存在之前執行過的結果,而是每次都會執行該方法,並將執行結果以鍵值對的形式存入指定的緩存中。和@Cacheable不同的是, @CachePut每次都會觸發真實方法的調用,比如用戶更新緩存數據。
需要註意的是,該註解的value和key必須與要更新的緩存相同,即與@Cacheable 相同。 具體見下麵兩段代碼:
@Override
@CachePut(value = "usr",key = "targetClass + #p0")
public Book update(Book book) {
return null;
}
@Override
@Cacheable(value = "usr",key = "targetClass + #p0")
public Book save(Book book) {
return null;
}
4. @CacheEvict
@CacheEvict用來標註需要清除緩存元素的方法或類。該註解用於觸發緩存的清除操作。其屬性有value、key、condition、allEntries 和 beforeInvocation。可以用這些屬性來指定清除的條件。使用方法如下:
@Override
@Cacheable(value = "usr",key = "#p0.id")
public Book save(Book book) {
return null;
}
@Override
@CacheEvict(value = "usr",key = "#id")
public void delete(int id) {
}
@Override
@CacheEvict(value = "accountCache",allEntries = true)
public void deleteAll() {
}
@Override
@CacheEvict(value = "accountCache",beforeInvocation = true)
public void deleteAll() {
}
5. @Caching
註解@Caching用來組合多個註解標簽,有3個屬性:cacheable、put 和 evict,用於指定 @Cacheable、@CachePut 和 @CacheEvict。使用方法如下:
@Override
@Caching(cacheable = {@Cacheable(value = "usr",key = "#p0"),},
put = {@CachePut(value = "usr",key = "#p0"),},
evict = {@CacheEvict(value = "usr",key = "#p0"),})
public Book save(Book book) {
return null;
}
1.2 實例:用Spring Cache進行緩存管理
本實例展示Spring Cache是如何使用簡單緩存(SIMPLE方式)進行緩存管理的。
(1)添加依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
<version>2.7.2</version>
</dependency>
(2)配置緩存管理器
在application.yml文件中配置目標緩存管理器,支持Ehcache、Generic、Redis、 Jcache 等。這裡使用 SIMPLE 方式 "spring: cache: type: SIMPLE”。
(3)開啟緩存功能
在入口類添加註解@EnableCaching,開啟緩存功能。
(4)在服務實現里編寫緩存業務邏輯:
@Service
public class BookServiceImpl implements BookService{
@Autowired
private BookDao bookDao;
@Override
public Book getById(Integer id) {
return bookDao.getById(id);
}
@Override
public void insert(Book book) {
bookDao.insert(book);
}
@Override
@CachePut(value = "usr",key = "targetClass + #p0")
public Book update(Book book) {
return null;
}
@Override
@Caching(cacheable = {@Cacheable(value = "usr",key = "#p0"),},
put = {@CachePut(value = "usr",key = "#p0"),},
evict = {@CacheEvict(value = "usr",key = "#p0"),})
public Book save(Book book) {
return null;
}
@Override
@CacheEvict(value = "usr",key = "#id")
public void delete(int id) {
}
@Override
@CacheEvict(value = "accountCache",allEntries = true)
public void deleteAll() {
}
}
上述代碼可以看出,查找用戶的方法使用了註解@Cacheable來開啟緩存。修改和添加方法使用了註解@CachePut。它是先處理方法,然後把結果進行緩存的。要想刪除數據,則需要使用註解@CacheEvict來清空緩存。
(5)控制器里調用緩存服務
查看代碼
@EnableCaching
@Controller
public class BookController {
@Autowired
private BookService bookService;
int id = 0;
@RequestMapping("/book")
public String insert(){
Book book = new Book();
book.setUsername("拉行啊");
book.setPassword("123");
String jsonObject = JSON.toJSONString(book);
System.out.println(jsonObject);
book.setJson(jsonObject);
bookService.insert(book);
id = book.getId();
return book.toString();
}
@RequestMapping(value = "/get",method = RequestMethod.GET,produces = "application/json")
public Book get(){
Book book = new Book();
book.setUsername("拉行啊");
book.setPassword("123");
return book;
}
@RequestMapping(value = "/put")
public String put(@RequestParam("upload")MultipartFile file, RedirectAttributes redirectAttributes){
Date date = new Date();
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd");
String nyr = dateFormat.format(date);
if (file.getOriginalFilename().endsWith(".jpg")||file.getOriginalFilename().endsWith(".png")||
file.getOriginalFilename().endsWith(".git")){
try {
byte[] bytes = file.getBytes();
String s = nyr+Math.random()+file.getOriginalFilename();
Path path = Paths.get("./"+s);
Files.write(path, bytes);
return "success";
} catch (Throwable e) {
e.printStackTrace();
}
}else {
return "格式不支持";
}
return "error";
}
@RequestMapping(value = "/test")
public String test(Model model) throws Exception{
Book book = new Book();
book.setPassword("123");
model.addAttribute("book",book);
return "test";
}
}
1.3 整合 Ehcache
Spring Boot支持多種不同的緩存產品。在預設情況下使用的是簡單緩存,不建議在正式環境中使用。我們可以配置一些更加強大的緩存,比如Ehcache。Ehcache是一種廣泛使用的開源Java分散式緩存,它具有記憶體和磁碟存儲、緩存載入器、緩存擴展、緩存異常處理、GZIP緩存、Servlet過濾器,以及支持REST和SOAP API等特點。
@Service
@CacheConfig(cacheNames = {"userCache"})
public class BookServiceImpl implements BookService{
@Autowired
private BookDao bookDao;
@Override
@Cacheable(value = "usr",key = "targetClass + #p0")
public Page<Book> findAll() {
return bookDao.findAll();
}
}
1.4 整合 Caffeine
Caffeine是使用Java 8對Guava緩存的重寫版本。它基於LRU演算法實現,支持多種緩存過期策略。要使用它,需要在pom.xml文件中增加Caffeine依賴,這樣Spring Boot就會自動用 Caffeine替換預設的簡單緩存。
增加Caffeine依賴的代碼如下:
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
然後配置參數,見以下代碼:
spring:
cache:
type: caffeine
cache-names: myCaffeine
caffeine:
spec: maximumSize=1,expireAfterAccess=5s
代碼解釋如下。
- cache.type:指定使用哪個緩存供應商。
- cache.cache-names:在啟動時創建緩存名稱(即前面的cacheNames )。如果有多個名稱,則用逗號進行分隔。
- cache.caffeine.spec:這是 Caffeine 緩存的專用配置。
- maximumSize=1:最大緩存數量。如果超出最大緩存數量,則保留後進(最新)的,最開始的緩存會被清除。
- expireAfterAccess=5s:緩存5s,即緩存在5 s之內沒有被使用,就會被清除,在預設情況下,緩存的數據會一直保存在記憶體中。有些數據可能用一次後很長時間都不會再月,這樣會有大量無用的數據長時間占用記憶體,我們可以通過配置及時清除不需要的緩存。
2. 認識Redis
2.1 對比 Redis 與 Memcached
Cache可以和Redis一起用,Spring Boot支持把Cache存到Redis里。如果是單伺服器, 則用Cache、Ehcache或Caffeine,性能更高也能滿足需求。如果擁有伺服器集群,則可以使用 Redis,這樣性能更高。
1. Redis
Redis是目前使用最廣泛的記憶體數據存儲系統之一。它支持更豐富的數據結構,支持數據持久化、事務、HA (高可用High Available)雙機集群系統、主從庫。
Redis是key-value存儲系統。它支持的value類型包括String、List、Set、Zset (有序集合)和Hash。這些數據類型都支持push/pop、add/remove,以及取交集、並集、差集或更豐富的操作,而且這些操作都是原子性的。在此基礎上,Redis支持各種不同方式的排序和演算法。
Redis會周期性地把更新後的數據寫入磁碟,或把修改操作寫入追加的記錄文件中(RDB和 AOF兩種方式),並且在此基礎上實現了 master-slave (主從)同步。機器重啟後,能通過持久化數據自動重建記憶體。如果使用Redis作為Cache,則機器宕機後熱點數據不會丟失。
豐富的數據結構加上Redis兼具緩存系統和資料庫等特性,使得Redis擁有更加豐富的應用場景。
Redis可能會導致的問題:
-
- 緩存和資料庫雙寫一致性問題。
- 緩存雪崩問題。
- 緩存擊穿問題。
- 緩存的併發競爭問題。
Redis為什麼快:
-
- 純記憶體操作。
- 單線程操作,避免了頻繁的上下文切換。
- 採用了非阻塞I/O多路復用機制。
2. Memcached
Memcached的協議簡單,它基於Libevent的事件處理,內置記憶體存儲方式。Memcached 的分散式不互相通信,即各個Memcached不會互相通信以共用信息,分佈策略由客戶端實現。它不會對數據進行持久化,重啟Memcached、重啟操作系統都會導致全部數據消失。
Memcached常見的應用場景一存儲一些讀取頻繁但更新較少的數據,如靜態網頁、系統配置及規則數據、活躍用戶的基本數據和個性化定製數據、實時統計信息等。
3. 比較 Redis 與 Memcached
(1)關註度。
近年來,Redis越來越火熱,人們對Redis的關註度越來越高;對 Memcached關註度比較平穩,且有下降的趨勢。
(2)性能。
兩者的性能都比較高。
(3)數據類型。
Memcached的數據結構單一。
Redis非常豐富。
(4)記憶體大小。
Redis在2.0版本後增加了自己的VM特性,突破物理記憶體的限制。
Memcached可以修改最大可用記憶體的大小來管理記憶體,採用LRU演算法.
(5)可用性。
Redis依賴客戶端來實現分散式讀寫,在主從複製時,每次從節點重新連接主節點都要依賴整個快照,無增量複製。Redis不支持自動分片(sharding)。如果要實現分片功能,則需要依賴程式設定一致的散列(hash)機制。
Memcached採用成熟的hash或環狀的演算法,來解決單點故障引起的抖動問題,其本身沒有數據冗餘機制。
(6)持久化。
Redis依賴快照、AOF進行持久化。但AOF在增強可靠性的同時,對性能也有所影響。
Memcached不支持持久化,通常用來做緩存,以提升性能。
(7)value數據大小。
Redis的value的最大限制是1GB。
Memcached只能保存1MB以內的數據。
(8)數據一致性(事務支持)。
Memcached在併發場景下用CAS保證一致性。
Redis對事務支持比較弱,只能保證事務中的每個操作連續執行。
(9)應用場景。
Redis:適合數據量較少、性能操作和運算要求高的場景。
Memcached:適合提升性能的場景。適合讀多與少,如果數據量比較大,則可以採用分片的方式來解決。
2.2 Redis的適用場景
- 高併發的讀寫
Redis特別適合將方法的運行結果放入緩存,以便後續在請求方法時直接去緩存中讀取。對執行耗時,且結果不頻繁變動的SQL查詢的支持極好。
在高併發的情況下,應盡暈避免請求直接訪問資料庫,這時可以使用Redis進行緩衝操作,讓請求先訪問Redis。
- 計數器
電商網站(APP)商品的瀏覽量、視頻網站(APP)視頻的播放數等數據都會被統計,以便用於運營或產品分析。為了保證數據實時生效,每次瀏覽都得+1,這會導致非常高的併發量。這時可以用Redis提供的incr命令來實現計數器功能,這一切在記憶體中操作,所以性能非常好,非常適用於這些計數場景。
- 排行榜
可以利用Redis提供的有序集合數據類,實現各種複雜的排行榜應用。如京東、淘寶的銷量榜單,商品按時間、銷量排行等。
- 分散式會話
在集群模式下,一般都會搭建以Redis等記憶體資料庫為中心的Session (會活)服務,它不再由容器管理,而是由Session服務及記憶體資料庫管理。
- 互動場景
使用Redis提供的散列、集合等數據結構,可以很方便地實現網站(APP)中的點贊、踩、關註共同好友等社交場景的基本功能。
- 最新列表
Redis可以通過LPUSH在列表頭部插入一個內容ID作為關鍵字,LTRIM可用來限制列表的數量,這樣列表永遠為N個ID,無須查詢最新的列表,直接根據ID查找対應的內容即可。
3. Redis的數據類型
Redis有5種數據類型,見表11-2。
表 11-2
- 字元串(string)
Redis字元串可以包含任意類型的數據、字元、整數、浮點數等。
一個字元串類型的值的容量有512MB,代表能存儲最大512MB的內容。
可以使用INCR (DECR、INCRBY)命令來把字元串當作原子計敬器使用。
使用APPEND命令在字元串後添加內容。
應用場景:計數器。
- 列表(list)
Redis列表是簡單的字元串列表,按照插入順序排序。可以通過LPUSH、RPUSH命令添加一個元素到列表的頭部或尾部。
一個列表最多可以包含以”232-1“(4294967295)個元素。
應用場景:取最新N個數據的操作、消息隊列、刪除與過濾、實時分析正在發生的情況,數據統計與防止垃圾郵件(結合Set )。
- 集合(set)
Redis集合是一個無序的、不允許相同成員存在的字元串合集。
支持一些伺服器端的命令從現有的集合出發去進行集合運算,如合併(並集:union)、求交(交集intersection)、差集,找出不同元素的操作(共同好友、二度好友)。
應用場景:Unique操作,可以獲取某段時間內所有數據“排重值”,比如用於共同好友、二度好友、統計獨立IP、好友推薦等。
- 散列(hash )
Redis hash是字元串欄位和字元串值之間的映射,主要用來表示對象,也能夠存儲許多元素。
應用場景:存儲、讀取、修改用戶屬性。
- 有序集合(sorted set、zset)
Redis有序集合和Redis集合類似,是不包含相同字元串的合集。每個有序集合的成員都關聯著一個評分,這個評分用於把有序集合中的成員按最低分到最高分排列(排行榜應用,取TOP N 操作)。
使用有序集合,可以非常快捷地完成添加、刪除和更新元素的操作。元素是在插入時就排好序 的,所以很快地通過評分(score )或位次(position )獲得一個範圍的元素。
應用場景:排行榜應用、取TOP N、需要精準設定過期時間的應用(時間戳作為Score)、帶有權重的元素(游戲用戶得分排行榜)、過期項目處理、按照時間排序等。
4. 用RedisTemplate操作Redis的五種數據類型
4.1 認識opsFor方法
Spring封裝了 RedisTemplate來操作Redis,它支持所有的Redis原生的API,在 RedisTemplate中定義了對5種數據結構的操作方法。
-
- opsForValueQ:操作字元串。
- opsForHashO:操作散列。
- opsForList():操作列表。
- opsForSet():操作集合。
- opsForZSetO:操作有序集合。
下麵通過實例來理解和應用這些方法。這裡需要特別註意的是,運行上述方法後要對數據進行清空操作,否則多次運行會導致數據重覆操作。
4.2 操作字元串
字元串(string )是Redis最基本的類型。string的一個“key”對應一個"value”,即key-value 鍵值對。string是二進位安全的,可以存儲任何數據(比如圖片或序列化的對象)。值最大能存儲512MB的數據。一般用於一些複雜的計數功能的緩存。RedisTemplate提供以下操作string的方法。
(1)set void set(K key, V value);get V get(Object key)
@SpringBootTest
class RedisApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
void contextLoads() {
redisTemplate.opsForValue().set("hello", "world");
redisTemplate.opsForValue().set("staing","somewhere");
Object s = redisTemplate.opsForValue().get("hello");
Object s2 = redisTemplate.opsForValue().get("staing");
System.out.println(s);
System.out.println(s2);
}
}
(2)set void set(K key, V value, long timeout, TimeUnit unit)
以下代碼設置3 s失效。3 s之內查詢有結果,3 s之後查詢則返回為null。具體用法見以下代碼:
@SpringBootTest
class RedisApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
void String(){
redisTemplate.opsForValue().set("hello", "world",3, TimeUnit.SECONDS);
try {
Object s = redisTemplate.opsForValue().get("hello");
System.out.println(s);
Thread.currentThread().sleep(2000);
s = redisTemplate.opsForValue().get("hello");
System.out.println(s);
Thread.currentThread().sleep(2000);
s = redisTemplate.opsForValue().get("hello");
System.out.println(s);
Thread.currentThread().sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
TimeUnit是java.util.concurrent包下麵的一個類,表示給定單元粒度的時間段,常用的顆粒度有:
- 小時(TimeUnit.HOURS )
- 分鐘(TimeUnit.MINUTES )
- 秒(TimeUnit.SECONDS )
- 毫秒(TimeUnit.MILLISECONDS )
(3)set void set(K key, V value, long offset)
給定key所存儲的字元串值,從偏移量 offset開始。具體用法見以下代碼:
@SpringBootTest
class RedisApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
void test() {
redisTemplate.opsForValue().set("key", "hello world",6);
System.out.println(redisTemplate.opsForValue().get("key"));
}
}
運行測試,輸出如下結果:
hello
(4)getAndSet V getAndSet(K key, V value)
設置鍵的字元串值,並返回其舊值。具體用法見以下代碼:
@SpringBootTest
class RedisApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
void test() {
redisTemplate.opsForValue().set("hello", "world");
System.out.println(redisTemplate.opsForValue().getAndSet("hello", "hey"));
System.out.println(redisTemplate.opsForValue().get("hello"));
}
}
運行測試,輸出如下結果:
world
hey
(5)append Integer append(K key, String value)
如果key已經存在,並且是一個字元串,則該命令將該值追加到字元串的末尾。如果key不存在,則它將被創建並設置為空字元串,因此append在這種特殊情況下類似於set。用法見以下代碼:
@SpringBootTest
class RedisApplicationTests {
@Autowired
private RedisTemplate<String,String> redisTemplate;
@Test
void test() {
redisTemplate.opsForValue().append("hello", "hello");
System.out.println(redisTemplate.opsForValue().get("hello"));
redisTemplate.opsForValue().append("hello", "world");
System.out.println(redisTemplate.opsForValue().get("hello"));
}
}
運行測試,輸出如下結果:
hello
helloworld
這裡一定要註意反序列化配置,否則會報借。
(6)size Long size(K key)
返回key所對應的value值的長度,見以下代碼:
@SpringBootTest
class RedisApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
void test() {
redisTemplate.opsForValue().set("key", "1");
System.out.println(redisTemplate.opsForValue().size("key"));
}
}
運行測試,輸岀如下結果:
3
4.3 操作散列
Redis hash (散列)是一個string類型的field和value的映射表,hash特別適合用於存儲對象。value中存放的是結構化的對象.利用這種數據結構,可以方便地操作其中的某個欄位。比如在“單點登錄”時,可以用這種數據結構存儲用戶信息。以Cookield作為key,設置30分鐘為緩存過期時間,能很好地模擬出類似Session的效果。
(1)void putAII(H key, Map<? extends HK, ? extends HV> m)
用m中提供的多個散列欄位設置到key對應的散列表中,用法見以下代碼:
@SpringBootTest
class RedisApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
void test() {
Map<String, Object> map = new HashMap<String, Object>();
map.put("key1", "value1");
map.put("key2", "value2");
redisTemplate.opsForHash().putAll("HASH",map);
System.out.println(redisTemplate.opsForHash().entries("HASH"));
}
}
運行測試,輸出如下結果:
{key1=value1, key2=value2}
(2)void put(H key, HK hashKey, HV value)
設置hashKey的值,用法見以下代碼:
@SpringBootTest
class RedisApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
void test() {
redisTemplate.opsForHash().put("redis","name","li");
redisTemplate.opsForHash().put("redis","sex","male");
System.out.println(redisTemplate.opsForHash().entries("redis"));
}
}
運行測試,輸出如下結果:
{name=li, sex=male}
(3)List<HV> values(H key)
根據密鑰獲取整個散列存儲的值,用法見以下代碼:
@SpringBootTest
class RedisApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
void test() {
redisTemplate.opsForHash().put("redis","name","li");
redisTemplate.opsForHash().put("redis","sex","male");
System.out.println(redisTemplate.opsForHash().values("redis"));
}
}
運行測試,輸出如下結果:
[li, male]
(4)Map<HK, HV> entries(H key)
根據密鑰獲取整個散列存儲,用法見以下代碼:
@SpringBootTest
class RedisApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
void test() {
redisTemplate.opsForHash().put("redis","name","li");
redisTemplate.opsForHash().put("redis","sex","male");
System.out.println(redisTemplate.opsForHash().entries("redis"));
}
}
運行測試,輸出如下結果:
{name=li, sex=male}
(5)Long delete(H key, Object... hashKeys)
刪除給定的hashKeys,用法見以下代碼:
@SpringBootTest
class RedisApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
void test() {
redisTemplate.opsForHash().put("redis","name","li");
redisTemplate.opsForHash().put("redis","sex","male");
System.out.println(redisTemplate.opsForHash().delete("redis","name"));
System.out.println(redisTemplate.opsForHash().entries("redis"));
}
}
運行測試,輸出如下結果:
1
{sex=male}
(6)Boolean hasKey(H key, Object hashKey)
確定hashKey是否存在,用法見以下代碼:
@SpringBootTest
class RedisApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
void test() {
redisTemplate.opsForHash().put("redis","name","li");
redisTemplate.opsForHash().put("redis","sex","male");
System.out.println(redisTemplate.opsForHash().hasKey("redis","name"));
System.out.println(redisTemplate.opsForHash().hasKey("redis","sex"));
}
}
運行測試,輸出如下結果:
true
true
(7)HV get(H key, Object hashKey)
從鍵中的散列獲取給定hashKey的值,用法見以下代碼:
@SpringBootTest
class RedisApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
void test() {
redisTemplate.opsForHash().put("redis","name","li");
redisTemplate.opsForHash().put("redis","sex","male");
System.out.println(redisTemplate.opsForHash().get("redis","name"));
}
}
運行測試,輸出如下結果:
li
(8)Set<HK> keys(H key)
獲取key所對應的key的值,用法見以下代碼:
@SpringBootTest
class RedisApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
void test() {
redisTemplate.opsForHash().put("redis","name","li");
redisTemplate.opsForHash().put("redis","sex","male");
System.out.println(redisTemplate.opsForHash().keys("redis"));
}
}
運行測試,輸出如下結果:
[sex, name]
(9)Long size(H key)
獲取key所對應的散列表的大小個數,用法見以下代碼:
@SpringBootTest
class RedisApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
void test() {
redisTemplate.opsForHash().put("redis","name","li");
redisTemplate.opsForHash().put("redis","sex","male");
System.out.println(redisTemplate.opsForHash().size("redis"));
}
}
運行測試,輸出如下結果:
2
4.4 操作列表
Redis列表是簡單的字元串列表,按照插入順序排序。可以添加一個元素到列表的頭部(左邊) 或尾部(右邊)。
使用list數據結構,可以做簡單的消息隊列的功能。還可以利用Irange命令,做基於Redis的分頁功能,性能極佳。而使用SQL語句做分頁功能往往效果扱差。
(1)Long leftPushAII(K key, V... values)
leftPushAII表示把一個數組插入列表中,用法見以下代碼:
@SpringBootTest
class RedisApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
void test() {
String[] strings = new String[] {"1","2","3"};
redisTemplate.opsForList().leftPushAll("list",strings);
System.out.println(redisTemplate.opsForList().range("list",0,-1));
}
}
運行測試,輸岀如下結果:
[3,2,1]
(2)Long size(K key)
返回存儲在鍵中的列表的長度。如果鍵不存在,則將其解釋為空列表,並返回0。如果key存儲的值不是列表,則返回錯誤。用法見以下代碼:
@SpringBootTest
class RedisApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
void test() {
String[] strings = new String[] {"1","2","3"};
redisTemplate.opsForList().leftPushAll("list",strings);
System.out.println(redisTemplate.opsForList().size("list"));
}
}
運行測試,輸岀如下結果:
6
(3)Long leftPush(K key, V value)
將所有指定的值插入在鍵的列表的頭部,如果鍵不存在,則在執行推送操作之前將其創建為空列表(從左邊插入)。用法見以下代碼:
@SpringBootTest
class RedisApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
void test() {
redisTemplate.opsForList().leftPush("list","1");
System.out.println(redisTemplate.opsForList().size("list"));
redisTemplate.opsForList().leftPush("list","2");
System.out.println(redisTemplate.opsForList().size("list"));
redisTemplate.opsForList().leftPush("list","3");
System.out.println(redisTemplate.opsForList().size("list"));
}
}
(4)Long rightPush(K key, V value)
將所有指定的值插入存儲在鍵的列表的頭部。如果鍵不存在,則在執行推送操作之前將其創建為空列表(從右邊插入)。用法見以下代碼:
@SpringBootTest
class RedisApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
void test() {
redisTemplate.opsForList().rightPush("list","1");
System.out.println(redisTemplate.opsForList().size("list"));
redisTemplate.opsForList().rightPush("list","2");
System.out.println(redisTemplate.opsForList().size("list"));
redisTemplate.opsForList().rightPush("list","3");
System.out.println(redisTemplate.opsForList().size("list"));
}
}
(5)Long rightPushAII(K key, V... values)
通過rightPushAII方法向最右邊批量添加元素,用法見以下代碼:
@SpringBootTest
class RedisApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
void test() {
String[] strings = new String[]{"1", "2", "3"};
redisTemplate.opsForList().rightPushAll("list",strings);
System.out.println(redisTemplate.opsForList().range("list",0,-1));
}
}
(6)void set(K key, long index, V value)
在列表中index的位置設置value,用法見以下代碼:
@SpringBootTest
class RedisApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
void test() {
String[] strings = new String[]{"1", "2", "3"};
redisTemplate.opsForList().rightPushAll("list",strings);
System.out.println(redisTemplate.opsForList().range("list",0,-1));
redisTemplate.opsForList().set("list",1,"值");
System.out.println(redisTemplate.opsForList().range("list",0,-1));
}
}
運行測試,輸出如下結果:
[1, 2, 3]
[1, 值, 3]
(7)Long remove(K key, long count, Object value)
從存儲在鍵中的列表,刪除給定“count”值的元素的第1個計數事件。其中,參數count的 含義如下。
-
- count=0:刪除等於value的所有元素。
- count>0:刪除等於從頭到尾移動的值的元素:
- counK<0:刪除等於從尾到頭移動的值的元素。
以下代碼用於刪除列表中第一次出現的值。
@SpringBootTest
class RedisApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
void test() {
String[] strings = new String[]{"1", "2", "3"};
redisTemplate.opsForList().rightPushAll("list",strings);
System.out.println(redisTemplate.opsForList().range("list",0,-1));
redisTemplate.opsForList().remove("list",1,"2");
System.out.println(redisTemplate.opsForList().range("list",0,-1));
}
}
運行測試,輸出如下結果:
[1, 2, 3]
[1, 3]
(8)V index(K key, long index)
根據下標獲取列表中的值(下標從0幵始),用法見以下代碼:
@SpringBootTest
class RedisApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
void test() {
String[] strings = new String[]{"1", "2", "3"};
redisTemplate.opsForList().rightPushAll("list",strings);
System.out.println(redisTemplate.opsForList().range("list",0,-1));
System.out.println(redisTemplate.opsForList().index("list",2));
}
}
運行測試,輸出如下結果:
[1, 2, 3]
3
(9)V leftPop(K key)
彈出最左邊的元素,彈出之後該值在列表中將不復存在,用法見以下代碼:
@SpringBootTest
class RedisApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
void test() {
String[] strings = new String[]{"1", "2", "3"};
redisTemplate.opsForList().rightPushAll("list",strings);
System.out.println(redisTemplate.opsForList().range("list",0,-1));
System.out.println(redisTemplate.opsForList().leftPop("list"));
System.out.println(redisTemplate.opsForList().range("list",0,-1));
}
}
運行測試,輸出如下結果:
[1, 2, 3]
1
[2, 3]
(10)V rightPop(K key)
彈出最右邊的元素,彈出之後該值任列表中將不復存在,用法見以下代碼:
@SpringBootTest
class RedisApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
void test() {
String[] strings = new String[]{"1", "2", "3"};
redisTemplate.opsForList().rightPushAll("list",strings);
System.out.println(redisTemplate.opsForList().range("list",0,-1));
System.out.println(redisTemplate.opsForList().rightPop("list"));
System.out.println(redisTemplate.opsForList().range("list",0,-1));
}
}
運行測試,輸出如下結果:
[1, 2, 3]
3
[1, 2]
4.5 操作集合
set是存放不重覆值的集合。利用set可以做全局去重的功能。還可以進行交集、並集、差集等 操作,也可用來實現計算共同喜好、全部的喜好、自己獨有的喜好等功能。
Redis的set是string類型的無序集合,通過散列表實現。
(1)Long add(K key, .. values)
在無序集合中添加元素,返回添加個數,用法見以下代碼:
@SpringBootTest
class RedisApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
void test() {
String[] strings = new String[]{"str1", "str2"};
System.out.println(redisTemplate.opsForSet().add("set1",strings));
System.out.println(redisTemplate.opsForSet().add("set1","1","2","3"));
}
}
運行測試,輸出如下結果:
2
3
(2)Long remove(K key, Object... values)
移除集合中一個或多個成員,用法見以下代碼:
@SpringBootTest
class RedisApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
void test() {
String[] strings = new String[]{"str1", "str2"};
System.out.println(redisTemplate.opsForSet().add("set1",strings));
System.out.println(redisTemplate.opsForSet().remove("set1",strings));
}
}
運行測試,輸出如下結果:
2
0
(3)V pop(K key)
移除並返回集合中的一個隨機元素,用法見以下代碼:
@SpringBootTest
class RedisApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
void test() {
String[] strings = new String[]{"str1", "str2"};
System.out.println(redisTemplate.opsForSet().add("set1",strings));
System.out.println(redisTemplate.opsForSet().pop("set1"));
System.out.println(redisTemplate.opsForSet().members("set1"));
}
}
運行測試,輸出如下結果:
2
str2
[str1]
(4)Boolean move(K key, V value, K destKey)
將member元素移動,用法見以下代碼:
@SpringBootTest
class RedisApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
void test() {
String[] strings = new String[]{"str1", "str2"};
System.out.println(redisTemplate.opsForSet().add("set1",strings));
redisTemplate.opsForSet().move("set1","str1","set1tostr1");
System.out.println(redisTemplate.opsForSet().members("set1"));
System.out.println(redisTemplate.opsForSet().members("set1tostr1"));
}
}
運行測試,輸出如下結果:
2
[str2]
[str1]
(5)Long size(K key)
獲取無序集合的大小長度,用法見以下代碼:
@SpringBootTest
class RedisApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
void test() {
String[] strings = new String[]{"str1", "str2"};
System.out.println(redisTemplate.opsForSet().add("set",strings));
System.out.println(redisTemplate.opsForSet().size("set"));
}
}
運行測試,輸出如下結果:
2
2
(6)Set<V> members(K key)
返回集合中的所有成員,用法見以下代碼:
@SpringBootTest
class RedisApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
void test() {
String[] strings = new String[]{"str1", "str2"};
System.out.println(redisTemplate.opsForSet().add("set",strings));
System.out.println(redisTemplate.opsForSet().members("set"));
}
}
運行測試,輸出如下結果:
2
[str1,str2]
(7)Cursor<V> scan(K key, ScanOptions options)
遍歷Set,用法見以下代碼:
@SpringBootTest
class RedisApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
void test() {
String[] strings = new String[]{"str1", "str2"};
System.out.println(redisTemplate.opsForSet().add("set",strings));
Cursor<Object> cursor = redisTemplate.opsForSet().scan("set", ScanOptions.NONE);
while (cursor.hasNext()) {
System.out.println(cursor.next());
}
}
}
運行測試,輸岀如下結果:
2
str2
str1
4.6 操作有序集合
zset (sorted set,有序集合)也是string類型元素的集合,且不允許重覆的成員。每個元素都會關聯一個double類型的分數。可以通過分數將該集合中的成員從小到大進行排序。
zset的成員是唯一的,但權重參數分數(score)卻可以重覆。集合中的元素能夠按score進行排列。它可以用來做排行榜應用、取TOP N 延時任務、範圍查找等。
(1)Long add(K key, Set<TypedTuple<V>>tuples)
新增一個有序集合,用法見以下代碼:
@SpringBootTest
class RedisApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
void test() {
ZSetOperations.TypedTuple<Object> objectTypedTuple1 = new DefaultTypedTuple<>("zset-1",9.6);
ZSetOperations.TypedTuple<Object> objectTypedTuple2 = new DefaultTypedTuple<>("zset-2",9.9);
Set<ZSetOperations.TypedTuple<Object>> tuples = new HashSet<>();
tuples.add(objectTypedTuple1);
tuples.add(objectTypedTuple2);
System.out.println(redisTemplate.opsForZSet().add("zset",tuples));
System.out.println(redisTemplate.opsForZSet().range("zset",0,-1));
}
}
運行測試,輸出如下結果:
1
[zset-1, zset-2]
(2)Boolean add(K key, V value, double score)
新增一個有序集合,如果存在則返回false,如果不存在則返回true,用法見以下代碼:
@SpringBootTest
class RedisApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
void test() {
System.out.println(redisTemplate.opsForZSet().add("zset","zset-1",1.0));
System.out.println(redisTemplate.opsForZSet().add("zset","zset-1",1.0));
}
}
運行測試,輸出如下結果:
true
false
(3)Long remove(K key, Object... values)
從有序集合中移除一個或多個元素,用法見以下代碼:
@SpringBootTest
class RedisApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
void test() {
System.out.println(redisTemplate.opsForZSet().add("zset","zset-1",1.0));
System.out.println(redisTemplate.opsForZSet().add("zset","zset-2",1.0));
System.out.println(redisTemplate.opsForZSet().range("zset",0,-1));
System.out.println(redisTemplate.opsForZSet().remove("zset","zset-2"));
System.out.println(redisTemplate.opsForZSet().range("zset",0,-1));
}
}
運行測試,輸出如下結果:
true
true
[zset-1, zset-2]
1
[zset-1]
(4)Long rank(K key, Object o)
返回有序集中指定成員的排名,按分數值遞增排列,用法見以下代碼:
@SpringBootTest
class RedisApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
void test() {
System.out.println(redisTemplate.opsForZSet().add("zset","zset-1",1.0));
System.out.println(redisTemplate.opsForZSet().add("zset","zset-2",1.0));
System.out.println(redisTemplate.opsForZSet().range("zset",0,-1));
System.out.println(redisTemplate.opsForZSet().remove("rank","zset-1"));
}
}
運行測試,輸出如下結果:
true
true
[zset-1, zset-2]
0
(5)Set<V> range(K key, long start, long end)
通過索引區間返回有序集合指定區間內的成員,按分數值遞增排列,用法見以下代碼:
@SpringBootTest
class RedisApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
void test() {
ZSetOperations.TypedTuple<Object> objectTypedTuple1 = new DefaultTypedTuple<>("zset-1",9.6);
ZSetOperations.TypedTuple<Object> objectTypedTuple2 = new DefaultTypedTuple<>("zset-2",8.1);
Set<ZSetOperations.TypedTuple<Object>> tuples = new HashSet<>();
tuples.add(objectTypedTuple1);
tuples.add(objectTypedTuple2);
System.out.println(redisTemplate.opsForZSet().add("zset",tuples));
System.out.println(redisTemplate.opsForZSet().range("zset",0,-1));
}
}
運行測試,輸出如下結果:
0
[zset-2, zset-1]
(6)Long count(K key, double min, double max)
通過分數返回有序集合指定區間內的成員個數,用法見以下代碼:
@SpringBootTest
class RedisApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
void test() {
ZSetOperations.TypedTuple<Object> objectTypedTuple1 = new DefaultTypedTuple<>("zset-1",3.6);
ZSetOperations.TypedTuple<Object> objectTypedTuple2 = new DefaultTypedTuple<>("zset-2",4.1);
ZSetOperations.TypedTuple<Object> objectTypedTuple3 = new DefaultTypedTuple<>("zset-3",5.7);
Set<ZSetOperations.TypedTuple<Object>> tuples = new HashSet<>();
tuples.add(objectTypedTuple1);
tuples.add(objectTypedTuple2);
tuples.add(objectTypedTuple3);
System.out.println(redisTemplate.opsForZSet().add("zset",tuples));
System.out.println(redisTemplate.opsForZSet().rangeByScore("zset",0,9));
System.out.println(redisTemplate.opsForZSet().count("zset",0,5));
}
}
運行測試,輸出如下結果:
1
[zset-1, zset-2, zset-3]
2
(7)Long size(K key)
獲取有序集合的成員數,用法見以下代碼:
@SpringBootTest
class RedisApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
void test() {
ZSetOperations.TypedTuple<Object> objectTypedTuple1 = new DefaultTypedTuple<>("zset-1",3.6);
ZSetOperations.TypedTuple<Object> objectTypedTuple2 = new DefaultTypedTuple<>("zset-2",4.1);
ZSetOperations.TypedTuple<Object> objectTypedTuple3 = new DefaultTypedTuple<>("zset-3",5.7);
Set<ZSetOperations.TypedTuple<Object>> tuples = new HashSet<>();
tuples.add(objectTypedTuple1);
tuples.add(objectTypedTuple2);
tuples.add(objectTypedTuple3);
System.out.println(redisTemplate.opsForZSet().add("zset",tuples));
System.out.println(redisTemplate.opsForZSet().size("zset"));
}
}
運行測試,輸出如下結果:
0
3
(8)Double score(K key, Object o)
獲取指定成員的score值,用法見以下代碼:
@SpringBootTest
class RedisApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
void test() {
ZSetOperations.TypedTuple<Object> objectTypedTuple1 = new DefaultTypedTuple<>("zset-1",3.6);
ZSetOperations.TypedTuple<Object> objectTypedTuple2 = new DefaultTypedTuple<>("zset-2",4.1);
ZSetOperations.TypedTuple<Object> objectTypedTuple3 = new DefaultTypedTuple<>("zset-3",5.7);
Set<ZSetOperations.TypedTuple<Object>> tuples = new HashSet<>();
tuples.add(objectTypedTuple1);
tuples.add(objectTypedTuple2);
tuples.add(objectTypedTuple3);
System.out.println(redisTemplate.opsForZSet().add("zset",tuples));
System.out.println(redisTemplate.opsForZSet().score("zset","zset-1"));
}
}
運行測試,輸出如下結果:
0
3.6
(9)Long removeRange(K key, long start, long end)
移除指定索引位置的成員,有序集成員按分數值遞增排列,用法見以下代碼:
@SpringBootTest
class RedisApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
void test() {
ZSetOperations.TypedTuple<Object> objectTypedTuple1 = new DefaultTypedTuple<>("zset-1",3.6);
ZSetOperations.TypedTuple<Object> objectTypedTuple2 = new DefaultTypedTuple<>("zset-2",4.1);
ZSetOperations.TypedTuple<Object> objectTypedTuple3 = new DefaultTypedTuple<>("zset-3",2.7);
Set<ZSetOperations.TypedTuple<Object>> tuples = new HashSet<>();
tuples.add(objectTypedTuple1);
tuples.add(objectTypedTuple2);
tuples.add(objectTypedTuple3);
System.out.println(redisTemplate.opsForZSet().add("zset",tuples));
System.out.println(redisTemplate.opsForZSet().range("zset",0,-1));
System.out.println(redisTemplate.opsForZSet().removeRange("zset",1,2));
System.out.println(redisTemplate.opsForZSet().range("zset",0,-1));
}
}
運行測試,輸岀如下結果:
2
[zset-3, zset-1, zset-2]
2
[zset-3]
(10)Cursor<TypedTuple<V>>scan(K key, ScanOptions options)
遍歷zset,用法見以下代碼:
@SpringBootTest
class RedisApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
void test() {
ZSetOperations.TypedTuple<Object> objectTypedTuple1 = new DefaultTypedTuple<>("zset-1",3.6);
ZSetOperations.TypedTuple<Object> objectTypedTuple2 = new DefaultTypedTuple<>("zset-2",5.1);
ZSetOperations.TypedTuple<Object> objectTypedTuple3 = new DefaultTypedTuple<>("zset-3",2.7);
Set<ZSetOperations.TypedTuple<Object>> tuples = new HashSet<>();
tuples.add(objectTypedTuple1);
tuples.add(objectTypedTuple2);
tuples.add(objectTypedTuple3);
System.out.println(redisTemplate.opsForZSet().add("zset",tuples));
Cursor<ZSetOperations.TypedTuple<Object>> cursor = redisTemplate.opsForZSet().scan("zset",ScanOptions.NONE);
while (cursor.hasNext()) {
ZSetOperations.TypedTuple<Object> item = cursor.next();
System.out.println(item.getValue()+":"+item.getScore());
}
}
}
運行測試,輸出如下結果:
2
zset-3:2.7
zset-1:3.6
zset-2:5.1
除使用opsForXXX方法外,還可以使用Execute方法。opsForXXX方法的底層,是通過調用Execute方法來實現的。psForXXX方法實際上是封裝了 Execute方法,定義了序列化,以便使用起來更簡單便捷。
4.7 比較 RedisTemplate 和 StringRedisTemplate
StringRedisTemplate繼承於RedisTemplate,兩者的數據是不相通的。
- StringRedisTemplate 只能管理 StringRedisTemplate 中的數據。
- RedisTemplate 只能管理 RedisTemplate 中的數據。
StnngRedisTemplate預設採用的是string的序列化策略,RedisTemplate預設採用的是 JDK的序列化策略。