菜鳥的redis學習總結

說明

本文主要整理了非關係型數據庫redis的相關知識,本文會持續更新,不斷地擴充

本文僅爲記錄學習軌跡,如有侵權,聯繫刪除

一、Nosql和Mysql

mysql作爲目前使用人數比較多的一種數據庫屬於關係型數據庫,有關係型數據庫就有非關係型數據庫,也就是Nosql(Not Only Sql),在所有的非關係型數據庫中,用的最多的就是redis

兩者的區別
像平時我們所用到的MSSQL Server、Mysql等是關係型數據庫,它們是建立在關係模型基礎上的數據庫,依靠表、字段等關係模型,結合集合代數等數學方法來處理數據。而非關係型數據庫,Nosql,Not only sql,是以Key-Value形式進行存儲的,用來解決文檔方面數據的存儲。

也就是說最直觀的區別就是兩者之前的數據存儲方式不同,關係型數據庫存儲方式是用的表結構,通過表的一行一行的方式來存儲數據,而像redis這種非關係型數據庫,存儲方式就比較簡單粗暴,直接通過鍵值對的方式存儲,它沒有行、列的概念,集合就相當於“表”,文檔就相當於“行”。下面給出一張在網上看到的一張圖
在這裏插入圖片描述
一句話總結就是:MySQL是一個基於表格設計的關係數據庫,而NoSQL本質上是非關係型的基於文檔的設計

兩者優缺點比較
(1)MySQL中創建數據庫之前需要詳細的數據庫模型,而在NoSQL數據庫類型的情況下不需要詳細的建模。
(2)MySQL的嚴格模式限制並不容易擴展,而NoSQL可以通過動態模式特性輕鬆擴展。
(3)MySQL提供了大量的報告工具,可以幫助應用程序有效,而NoSQL數據庫缺少用於分析和性能測試的報告工具。
(4)MySQL是一個關係數據庫,其設計約束靈活性較低;而NoSQL本質上是非關係型的,與MySQL相比,它提供了更靈活的設計。
(5)MySQL中使用的標準語言是SQL;而NoSQL中缺乏標準的查詢語言。
(6)Mysql在進行CURD操作時,會用到I/O操作,讀寫效率較慢,而redis數據是存儲在緩存中的,讀寫效率自然比mysql高,尤其是在高併發的情況下。
在這裏插入圖片描述
在上面這張圖中,個人覺得最重要的一點就是高併發時數據的讀取,因爲像redis數據可以存儲在緩存中,緩存的讀取速度快,能夠大大的提高運行效率,redis 的性能非常出色,每秒可以處理超過 10 萬次讀寫操作,是已知性能最快的 Key-Value 數據庫,但是保存時間有限。此外就是redis雖然是存儲在緩存中,但也是可以做數據持久化的。

兩者的應用場景
這兩種數據庫的優缺點就決定了它們的使用場景,mysql的使用場景就不多說了,可以說現在的開發基本離不開這種關係型數據,但是考慮到mysql的讀寫效率,如果是數據量較少的情況下,mysql夠用了,但如果涉及到一些大數據的分析處理等,就必須藉助redis的力量,可以說它們兩個是相輔相成的,關係型數據庫爲主,非關係型數據庫爲輔。下面給出redis的常見的使用常景

使用場景 說明
緩存 緩存現在幾乎是所有中大型網站都在用的必殺技,合理的利用緩存不僅能夠提升網站訪問速度,還能大大降低數據庫的壓力。Redis提供了鍵過期功能,也提供了靈活的鍵淘汰策略,所以,現在Redis用在緩存的場合非常多。
排行榜 很多網站都有排行榜應用的,如京東的月度銷量榜單、商品按時間的上新排行榜等。Redis提供的有序集合數據類構能實現各種複雜的排行榜應用。
計數器 什麼是計數器,如電商網站商品的瀏覽量、視頻網站視頻的播放數等。爲了保證數據實時效,每次瀏覽都得給+1,併發量高時如果每次都請求數據庫操作無疑是種挑戰和壓力。Redis提供的incr命令來實現計數器功能,內存操作,性能非常好,非常適用於這些計數場景。
分佈式會話 集羣模式下,在應用不多的情況下一般使用容器自帶的session複製功能就能滿足,當應用增多相對複雜的系統中,一般都會搭建以Redis等內存數據庫爲中心的session服務,session不再由容器管理,而是由session服務及內存數據庫管理。
分佈式鎖 在很多互聯網公司中都使用了分佈式技術,分佈式技術帶來的技術挑戰是對同一個資源的併發訪問,如全局ID、減庫存、秒殺等場景,併發量不大的場景可以使用數據庫的悲觀鎖、樂觀鎖來實現,但在併發量高的場合中,利用數據庫鎖來控制資源的併發訪問是不太理想的,大大影響了數據庫的性能。可以利用Redis的setnx功能來編寫分佈式的鎖,如果設置返回1說明獲取鎖成功,否則獲取鎖失敗,實際應用中要考慮的細節要更多。
社交網絡 點贊、踩、關注/被關注、共同好友等是社交網站的基本功能,社交網站的訪問量通常來說比較大,而且傳統的關係數據庫類型不適合存儲這種類型的數據,Redis提供的哈希、集合等數據結構能很方便的的實現這些功能。
最新列表 Redis列表結構,LPUSH可以在列表頭部插入一個內容ID作爲關鍵字,LTRIM可用來限制列表的數量,這樣列表永遠爲N個ID,無需查詢最新的列表,直接根據ID去到對應的內容頁即可。
消息系統 消息隊列是大型網站必用中間件,如ActiveMQ、RabbitMQ、Kafka等流行的消息隊列中間件,主要用於業務解耦、流量削峯及異步處理實時性低的業務。Redis提供了發佈/訂閱及阻塞隊列功能,能實現一個簡單的消息隊列系統。另外,這個不能和專業的消息中間件相比。

上面的這些redis使用場景是整理的網絡上的,有些自己也不是完全懂,但是自己想了一下,redis數據既然存在內存中,所以用來做緩存的話是最佳的選擇,然後像排行榜,點贊關注等功能,則是因爲redis的數據存儲類型,所以用來做這些相關的功能會很方便,同時效率也高,但是有一個場景是不建議用redis的,像是經常改動的數據,這種頻繁變動的數據就不適合用redis,弄不好可能造成數據的丟失。

總結如下
(mysql)關係型數據庫適合存儲結構化數據,如用戶的帳號、地址等
(1)這些數據通常需要做結構化查詢,比如join,這時候,關係型數據庫就要勝出一籌
(2)這些數據的規模、增長的速度通常是可以預期的
(3)保證數據的事務性、一致性要求。

(redis)NoSQL適合存儲非結構化數據,如發微博、文章、評論
(1)這些數據通常用於模糊處理,如全文搜索、機器學習
(2)這些數據是海量的,而且增長的速度是難以預期的,
(3)根據數據的特點,NoSQL數據庫通常具有無限(至少接近)伸縮性
(4)按key獲取數據效率很高,但是對join或其他結構化查詢的支持就比較差

目前許多大型互聯網項目都會選用MySQL(或任何關係型數據庫) + NoSQL的組合方案。

二、Nosql常見類型及比較

這裏整理了一下網上常見的4種Nosql類型:列式、文檔、圖形和內存鍵值。
在這裏插入圖片描述
對於這4種類型的Nosql,之前在網上看到一張圖
在這裏插入圖片描述
redis就是屬於內存鍵值的Nosql類型。

三、簡介

Redis是一個開源的使用ANSI C語言編寫、遵守BSD協議、支持網絡、可基於內存亦可持久化的日誌型、Key-Value數據庫,並提供多種語言的API。它通常被稱爲數據結構服務器,因爲值(value)可以是 字符串(String), 哈希(Map), 列表(list), 集合(sets)有序集合(sorted sets)等類型,是當下熱門的 NoSQL 技術之一!

Redis 能幹嘛
(1)內存存儲、持久化,內存中是斷電即失、所以說持久化很重要(rdb、aof)
(2)效率高,可以用於高速緩存
(3)發佈訂閱系統
(4)地圖信息分析 5、計時器、計數器(瀏覽量!)
(5)…

特性
(1)多樣的數據類型
(2)持久化
(3)集羣
(4)事務
(5)…

五大基本數據類型
(1)String
(2)List
(3)Set
(4)Hash
(5)Zset

三種特殊數據類型
(1)geo
(2)hyperloglog
(3)bitmap

四、入門系列

(1)性能測試

按照官方的介紹,redis的性能是非常高的,每秒可以處理超過 10 萬次讀寫操作。對與redis的性能,我們可以用它自帶的工具redis-benchmark進行性能測試,測試的方式也簡單,具體如下

redis-benchmark 相關命令參數

參數如下表
在這裏插入圖片描述

測試案例(測試100個併發數,每個請求數10萬)
命令:redis-benchmark -h localhost -p 6379 -c 100 -n 100000

用進入redis的安裝目錄,可以看redis-benchmark測試工具
在這裏插入圖片描述
先開啓redis的服務,用命令提示符的方式進入該目錄,並輸入上面的測試的命令
在這裏插入圖片描述

(2)String類型

基本的使用

redis 127.0.0.1:6379> keys *     #獲取所有的key
(empty list or set)
redis 127.0.0.1:6379> set name Tony  #設置值
OK
redis 127.0.0.1:6379> keys *
1) "name"
redis 127.0.0.1:6379> get name  #獲取值
"Tony"
redis 127.0.0.1:6379> exists name  #判斷某一個key是否存在,存在就返回1,否則就返回0
(integer) 1
redis 127.0.0.1:6379> exists name1
(integer) 0
redis 127.0.0.1:6379> append name " is boy" #往一個key追加值,追加字符串
(integer) 11
redis 127.0.0.1:6379> get name   
"Tony is boy"
redis 127.0.0.1:6379> strlen name  #獲取一個key對應值的字符串長度
(integer) 11
redis 127.0.0.1:6379> get name
"Tony is boy"
redis 127.0.0.1:6379> getrange name 0 2  #截取key對應的值範圍,下標範圍[0,2]
"Ton"
redis 127.0.0.1:6379> getrange name 0 -1  #截取key對應的值範圍,下標範圍[0,-1]-1表示截取到最後一個字符串
"Tony is boy"
redis 127.0.0.1:6379> setrange name 8 student   #替換指定位置開始的字符串! 
(integer) 15
redis 127.0.0.1:6379> get name
"Tony is student"
redis 127.0.0.1:6379> setex k1 10  v1  #設置k1 的值爲 v1,10秒後過期 
OK
redis 127.0.0.1:6379> ttl k1  #查看key還有多久過期
(integer) 4
redis 127.0.0.1:6379> get k1
"v1"
redis 127.0.0.1:6379> get k1
(nil)
redis 127.0.0.1:6379> setnx mykey "redis"   #如果mykey 不存在,則創建mykey 
(integer) 1
redis 127.0.0.1:6379> keys *
1) "mykey"
2) "username"
redis 127.0.0.1:6379> setnx mykey "mysql"  #如果mykey存在則創建失敗
(integer) 0
redis 127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3  #批量創建鍵值對
OK
redis 127.0.0.1:6379> keys *
1) "mykey"
2) "username"
3) "k2"
4) "k3"
5) "k1"
redis 127.0.0.1:6379> mget k1 k2 j3  #批量獲取鍵值對
1) "v1"
2) "v2"
3) (nil)
redis 127.0.0.1:6379> msetnx k1 v1 k4 v4  # msetnx 是一個原子性的操作,要麼一起成功,要麼一起 失敗! 
(integer) 0

注意區分創建值的其中兩種方式,set和setnx
(1)set創建值時,如果key不存在就創建值,如果存在則會覆蓋掉之前的值
(2)setnx創建值時,如果key不存在就會創建值,如果存在就創建失敗

對象操作
在用java進行開發的時候,對象是接觸得最多的,對象經常用來封裝數據,然後存儲,在redis裏面存儲對象,有兩種方式,一種是把對象轉成json,再用String類型存儲;另一種是用user:{id}:{filed} 的方式存儲,這是redis支持的語法

假設要存儲一個對象user

public class user{
	private Integer id;
	private String username;
	private Integer age;
	private String password;
}

jsond的方式存儲

redis 127.0.0.1:6379> set user:2 {username:zs,age:19,password:123}
OK
redis 127.0.0.1:6379> get user:2
"{username:zs,age:19,password:123}"

user:{id}:{filed} 的方式存儲
存儲:mset user : {id} : username 值 user : {id} : age 值 user : {id} : password 值
獲取:mget user : {id} : username user : {id} :age user : {id} : password

redis 127.0.0.1:6379> mset user:1:username Mike user:1:age 18 user:1:password 123
OK
redis 127.0.0.1:6379> mget user:1:username  user:1:age  user:1:password
1) "Mike"
2) "18"
3) "123"

高級使用,統計瀏覽量

redis 127.0.0.1:6379> set views 0   #初始化瀏覽量爲0
OK
redis 127.0.0.1:6379> keys *
1) "name"
2) "views"
redis 127.0.0.1:6379> incr views  #瀏覽量自增1
(integer) 1
redis 127.0.0.1:6379> incr views
(integer) 2
redis 127.0.0.1:6379> get views
"2"
redis 127.0.0.1:6379> incr views
(integer) 3
redis 127.0.0.1:6379> get views
"3"
redis 127.0.0.1:6379> decr views  #瀏覽量自減1
(integer) 2
redis 127.0.0.1:6379> get views
"2"
redis 127.0.0.1:6379> incrby views 10  #瀏覽量增加10
(integer) 12
redis 127.0.0.1:6379> get views
"12"
redis 127.0.0.1:6379> decrby views 5 #瀏覽量減少5
(integer) 7
redis 127.0.0.1:6379> get views
"7"
redis 127.0.0.1:6379>

(3)List類型

在redis裏面,list類型可以唄當成 棧、隊列、阻塞隊列等結構來使用

基本使用

redis 127.0.0.1:6379> lpush list1 one    # 將一個值或者多個值,插入到列表頭部 (左)
(integer) 1
redis 127.0.0.1:6379> lpush list1 two
(integer) 2
redis 127.0.0.1:6379> lpush list1 three
(integer) 3
redis 127.0.0.1:6379> lrange list1 0 -1   #通過區間的方式獲取list1的值
1) "three"
2) "two"
3) "one"
redis 127.0.0.1:6379> rpush list2 one   # 將一個值或者多個值,插入到列表位部 (右) 
(integer) 1
redis 127.0.0.1:6379> rpush list2 two
(integer) 2
redis 127.0.0.1:6379> rpush list2 three
(integer) 3
redis 127.0.0.1:6379> lrange list2 0 -1
1) "one"
2) "two"
3) "three"
redis 127.0.0.1:6379> lpop list2  #移除最左邊的元素
"one"
redis 127.0.0.1:6379> rpop list2 #移除最右邊的元素
"three"
redis 127.0.0.1:6379> lrange list2 0 -1
1) "two"
redis 127.0.0.1:6379> lpush list2 one
(integer) 2
redis 127.0.0.1:6379> rpush list2 three
(integer) 3
redis 127.0.0.1:6379> lrange list2 0 -1
1) "one"
2) "two"
3) "three"
redis 127.0.0.1:6379> lindex list2 0
"one"
redis 127.0.0.1:6379> lindex list2 2
"three"
redis 127.0.0.1:6379> llen list2   #獲取列表的元素個數
(integer) 3
redis 127.0.0.1:6379> rpush list2 three
(integer) 4
redis 127.0.0.1:6379> rpush list2 three
(integer) 5
redis 127.0.0.1:6379> lrange list2 0 -1
1) "one"
2) "two"
3) "three"
4) "three"
5) "three"
redis 127.0.0.1:6379> lrem list2 3 three  #移除3個值等於three的元素
(integer) 3
redis 127.0.0.1:6379> lrange list2 0 -1
1) "one"
2) "two"
redis 127.0.0.1:6379> rpush list2 three
(integer) 3
redis 127.0.0.1:6379> lrange list2 0 -1
1) "one"
2) "two"
3) "three"
redis 127.0.0.1:6379> rpush list2 four
(integer) 4
redis 127.0.0.1:6379> rpush list2 five
(integer) 5
redis 127.0.0.1:6379> lrange list2 0 -1
1) "one"
2) "two"
3) "three"
4) "four"
5) "five"
redis 127.0.0.1:6379> ltrim list2 1 3  # 通過下標截取指定的長度,這個list已經被改變了,截斷了 只剩下截取的元素! 
OK
redis 127.0.0.1:6379> lrange list2 0 -1
1) "two"
2) "three"
3) "four"
redis 127.0.0.1:6379> lpush list2 one
(integer) 4
redis 127.0.0.1:6379> lrange list2 0 -1
1) "one"
2) "two"
3) "three"
4) "four"
redis 127.0.0.1:6379> rpoplpush list2 list3   # 移除列表的後一個元素,將他移動到新的列表中!

"four"
redis 127.0.0.1:6379> lrange list3 0 -1
1) "four"
redis 127.0.0.1:6379> lrange list2 0 -1
1) "one"
2) "two"
3) "three"
redis 127.0.0.1:6379> exists list2    #判斷列表是否存在
(integer) 1
redis 127.0.0.1:6379> lset list2 1 number2   #將列表中指定下標的值替換爲另外一個值,更新操作
OK
redis 127.0.0.1:6379> lrange list2 0 -1
1) "one"
2) "number2"
3) "three"
redis 127.0.0.1:6379> lrange list2 0 -1
1) "one"
2) "number2"
3) "three"
redis 127.0.0.1:6379> lset list2 1 two
OK
redis 127.0.0.1:6379> lrange list2 0 -1
1) "one"
2) "two"
3) "three"
redis 127.0.0.1:6379> linsert list2 before three number3 # 將某個具體的value插入到列把你中某個元素的前面

(integer) 4
redis 127.0.0.1:6379> linsert list2 after three four    # 將某個具體的value插入到列把你中某個元素的後面!

(integer) 5
redis 127.0.0.1:6379> lrange list2 0 -1
1) "one"
2) "two"
3) "number3"
4) "three"
5) "four"

注意:list類型的值是可以重複的
小結
(1)他實際上是一個鏈表,before Node after , left,right 都可以插入值
(2)如果key 不存在,創建新的鏈表
(3)如果key存在,新增內容
(4)如果移除了所有值,空鏈表,也代表不存在!
(5)在兩邊插入或者改動值,效率高! 中間元素,相對來說效率會低一點~

消息排隊!消息隊列 (Lpush Rpop), 棧( Lpush Lpop)!

(4)Set集合

#######################################################################
127.0.0.1:6379> sadd myset "hello"   # set集合中添加勻速 
(integer) 
1 127.0.0.1:6379> sadd myset "kuangshen" 
(integer) 1 
127.0.0.1:6379> sadd myset "lovekuangshen" 
(integer) 
1 127.0.0.1:6379> SMEMBERS myset     # 查看指定set的所有值 
1) "hello" 
2) "lovekuangshen" 
3) "kuangshen" 
127.0.0.1:6379> SISMEMBER myset hello    # 判斷某一個值是不是在set集合中! 
(integer) 1 
127.0.0.1:6379> SISMEMBER myset world 
(integer) 0
#######################################################################
127.0.0.1:6379> scard myset  # 獲取set集合中的內容元素個數! 
(integer) 4
#######################################################################
127.0.0.1:6379> srem myset hello  # 移除set集合中的指定元素 
(integer) 
1 127.0.0.1:6379> scard myset 
(integer) 3 
127.0.0.1:6379> SMEMBERS myset 
1) "lovekuangshen2" 
2) "lovekuangshen" 
3) "kuangshen"
#######################################################################
# set 無序不重複集合。抽隨機!
127.0.0.1:6379> SMEMBERS myset 
1) "lovekuangshen2" 
2) "lovekuangshen" 
3) "kuangshen" 
127.0.0.1:6379> SRANDMEMBER myset  # 隨機抽選出一個元素 
"kuangshen" 
127.0.0.1:6379> SRANDMEMBER myset 
"kuangshen" 
127.0.0.1:6379> SRANDMEMBER myset 
"kuangshen" 
127.0.0.1:6379> SRANDMEMBER myset 
"kuangshen" 
127.0.0.1:6379> SRANDMEMBER myset 2  # 隨機抽選出指定個數的元素 
1) "lovekuangshen" 
2) "lovekuangshen2" 
127.0.0.1:6379> SRANDMEMBER myset 2 
1) "lovekuangshen" 
2) "lovekuangshen2" 
127.0.0.1:6379> SRANDMEMBER myset      # 隨機抽選出一個元素 
"lovekuangshen2"
#######################################################################
# 刪除定的key,隨機刪除key!
127.0.0.1:6379> SMEMBERS myset 
1) "lovekuangshen2" 
2) "lovekuangshen" 
3) "kuangshen" 
127.0.0.1:6379> spop myset  # 隨機刪除一些set集合中的元素! 
"lovekuangshen2" 
127.0.0.1:6379> spop myset 
"lovekuangshen" 
127.0.0.1:6379> SMEMBERS myset 
1) "kuangshen"
#######################################################################
# 將一個指定的值,移動到另外一個set集合! 
127.0.0.1:6379> sadd myset "hello" 
(integer) 1 
127.0.0.1:6379> sadd myset "world" 
(integer) 1 
127.0.0.1:6379> sadd myset "kuangshen" 
(integer) 1 
127.0.0.1:6379> sadd myset2 "set2" 
(integer) 1 
127.0.0.1:6379> smove myset myset2 "kuangshen" # 將一個指定的值,移動到另外一個set集 合! 
(integer) 1 
127.0.0.1:6379> SMEMBERS myset 
1) "world" 
2) "hello" 
127.0.0.1:6379> SMEMBERS myset2 
1) "kuangshen" 
2) "set2"

高級應用,求共同好友
微博,A用戶將所有關注的人放在一個set集合中!將它的粉絲也放在一個集合中!
共同關注,共同愛好,二度好友,推薦好友!(六度分割理論)

redis 127.0.0.1:6379> smembers k1
1) "a"
2) "d"
3) "b"
4) "c"
redis 127.0.0.1:6379> smembers k2
1) "f"
2) "d"
3) "e"
4) "c"
redis 127.0.0.1:6379> sdiff k1 k2    # 差集 
1) "a"
2) "b"
redis 127.0.0.1:6379> sinter k1 k2   # 交集   共同好友就可以這樣實現 
1) "d"
2) "c"
redis 127.0.0.1:6379> sunion k1 k2  # 並集 
1) "a"
2) "f"
3) "b"
4) "c"
5) "e"
6) "d"

(5)Hash類型

Map集合,key-map! 時候這個值是一個map集合! 本質和String類型沒有太大區別,還是一個簡單的 key-vlaue!

基本應用

########################################################################## 
127.0.0.1:6379> hset myhash field1 kuangshen  # set一個具體 key-vlaue 
(integer) 1 
127.0.0.1:6379> hget myhash field1  # 獲取一個字段值 
"kuangshen" 
127.0.0.1:6379> hmset myhash field1 hello field2 world   # set多個 key-vlaue 
OK 
127.0.0.1:6379> hmget myhash field1 field2   # 獲取多個字段值 
1) "hello" 
2) "world" 
127.0.0.1:6379> hgetall myhash   # 獲取全部的數據, 
1) "field1" 
2) "hello" 
3) "field2" 
4) "world" 
127.0.0.1:6379> hdel myhash field1  # 刪除hash指定key字段!對應的value值也就消失了! 
(integer) 1 127.0.0.1:6379> hgetall myhash 
1) "field2" 
2) "world" #######################################################################
### hlen
127.0.0.1:6379> hmset myhash field1 hello field2 world 
OK 
127.0.0.1:6379> HGETALL myhash 
1) "field2" 
2) "world" 
3) "field1" 
4) "hello" 
127.0.0.1:6379> hlen myhash  # 獲取hash表的字段數量! 
(integer) 2
#######################################################################
### 
127.0.0.1:6379> HEXISTS myhash field1  # 判斷hash中指定字段是否存在!
 (integer) 1
127.0.0.1:6379> HEXISTS myhash field3 
(integer) 0
#######################################################################
### # 只獲得所有field # 只獲得所有value 
127.0.0.1:6379> hkeys myhash  # 只獲得所有field 
1) "field2" 
2) "field1"
127.0.0.1:6379> hvals myhash  # 只獲得所有value 
1) "world" 
2) "hello" #######################################################################
### incr   decr
127.0.0.1:6379> hset myhash field3 5    #指定增量! 
(integer) 1 
127.0.0.1:6379> HINCRBY myhash field3 1 
(integer) 6 
127.0.0.1:6379> HINCRBY myhash field3 -1 
(integer) 5 
127.0.0.1:6379> hsetnx myhash field4 hello  # 如果不存在則可以設置 
(integer) 1 
127.0.0.1:6379> hsetnx myhash field4 world  # 如果存在則不能設置 
(integer) 0

(6)Zset有序集合

在set的基礎上,增加了一個值score,在做排序等操作時需要用到這個score值:zset k1 score1 v1

基本操作

redis 127.0.0.1:6379> zadd salary 8000 xm  #添加用戶xm,工資8000
(integer) 1
redis 127.0.0.1:6379> zadd salary 9000 xh
(integer) 1
redis 127.0.0.1:6379> zadd salary 10000 zs
(integer) 1
redis 127.0.0.1:6379> zadd salary 8800 ls
(integer) 1
redis 127.0.0.1:6379> zrangebyscore salary -inf +inf  # 顯示全部的用戶 從小到大! 
1) "xm"
2) "ls"
3) "xh"
4) "zs"
redis 127.0.0.1:6379> zrevrange salary 0 -1   # 顯示全部的用戶 從大到小! 
1) "zs"
2) "xh"
3) "ls"
4) "xm"
redis 127.0.0.1:6379> zrangebyscore salary -inf +inf withscores  # 顯示全部的用戶從小到大並且附帶成 績
1) "xm"
2) "8000"
3) "ls"
4) "8800"
5) "xh"
6) "9000"
7) "zs"
8) "10000"
redis 127.0.0.1:6379> zrangebyscore salary -inf 9000 withscores  # 顯示工資小於9000員工的升 序排序! 
1) "xm"
2) "8000"
3) "ls"
4) "8800"
5) "xh"
6) "9000"
redis 127.0.0.1:6379> zrange salary 0 -1  #查詢所有用戶
1) "xm"
2) "ls"
3) "xh"
4) "zs"
redis 127.0.0.1:6379> zrem salary ls  #移除用戶
(integer) 1
redis 127.0.0.1:6379> zcard salary   # 獲取有序集合中的個數 
(integer) 3
redis 127.0.0.1:6379> zadd myzset 1 hello 2 world 3 nice  #批量增加用戶
(integer) 3
redis 127.0.0.1:6379> zcount myzset 1 2  # 獲取指定區間的成員數量! 
(integer) 2

(7)Geospatial 地理位置

朋友的定位,附近的人,打車距離計算?這些功能都可以用reids來實現,Redis 的 Geo 在Redis3.2 版本就推出了! 這個功能可以推算地理位置的信息,兩地之間的距離,方圓 幾裏的人!

常見命令

GEOHASH:該命令將返回11個字符的Geohash字符串!
GEOPOS:獲得當前定位:一定是一個座標值!
GEODIST:獲取兩個城市之間的直線距離
GEORADIUS:以給定的經緯度爲中心, 找出某一半徑內的元素
GEOADD:添加key的經緯度座標及名稱
GEORADIUSBYMEMBER:找出位於指定元素周圍的其他元素

GEOADD
添加key的經緯度座標及名稱:GEOADD key 經度 緯度 名稱

#添加中國幾個城市的經緯度座標
127.0.0.1:6379>  geoadd china:city 116.40 39.90 beijing
(integer) 1
127.0.0.1:6379> geoadd china:city 113.38 22.52 zhongshan
(integer) 1
127.0.0.1:6379> geoadd china:city 113.28 23.12 guangzhou
(integer) 1
127.0.0.1:6379> geoadd china:city 114.08 22.54 shengzhen
(integer) 1
127.0.0.1:6379> geoadd china:city 121.47 31.23 shanghai
(integer) 1
127.0.0.1:6379> geoadd china:city 108.94 34.26 xianshi
(integer) 1

GEOPOS
獲得當前定位,一定是一個座標值:GEOPOS key 名稱

#獲取上海和北京的定位,前提是這些城市要提前添加到相應的key(china:city)裏面
127.0.0.1:6379> GEOPOS china:city shanghai
1) 1) "121.47000163793564"
   2) "31.229999039757836"
127.0.0.1:6379> GEOPOS china:city beijing
1) 1) "116.39999896287918"
   2) "39.900000091670925"

GEODIST
獲取兩個城市之間的直線距離:GEODIST key 城市1 城市2 單位
m 表示單位爲米。 km 表示單位爲千米。 mi 表示單位爲英里。 ft 表示單位爲英尺

#獲取中山到廣州的距離,單位是km
127.0.0.1:6379> GEODIST china:city zhongshan guangzhou km
"67.5185"
127.0.0.1:6379> GEODIST china:city beijing shanghai km
"1067.3788"

GEORADIUS
以給定的經緯度爲中心, 找出某一半徑內的元素:GEORADIUS key 經度 維度 半徑 單位

127.0.0.1:6379> GEORADIUS china:city 110 30 1000 km
1) "xianshi"
2) "zhongshan"
3) "shengzhen"
4) "guangzhou"
127.0.0.1:6379> GEORADIUS china:city 110 30 500 km
1) "xianshi"
127.0.0.1:6379>

GEORADIUSBYMEMBER
找出位於指定元素周圍的其他元素! :GEORADIUSBYMEMBER key 城市 距離 單位

127.0.0.1:6379> GEORADIUSBYMEMBER china:city beijing 1000 km
1) "beijing"
2) "xianshi"

GEOHASH
將二維的經緯度轉換爲一維的字符串,如果兩個字符串越接近,那麼則距離越近!

# 將二維的經緯度轉換爲一維的字符串,如果兩個字符串越接近,那麼則距離越近! 
127.0.0.1:6379> geohash china:city beijing chongqi 
1) "wx4fbxxfke0" 
2) "wm5xzrybty0"

底層原理
GEO 底層的實現原理其實就是 Zset!我們可以使用Zset命令來操作geo

127.0.0.1:6379> zrange china:city 0 -1  #獲取所有的值
1) "xianshi"
2) "zhongshan"
3) "shengzhen"
4) "guangzhou"
5) "shanghai"
6) "beijing"
127.0.0.1:6379> zrem china:city xianshi  #移除值
(integer) 1
127.0.0.1:6379> zcard china:city  #查看值的個數
(integer) 5
127.0.0.1:6379>

(8)Hyperloglo基數技術

基數
在數學上,基數(cardinal number)也叫勢(cardinality),指集合論中刻畫任意集合所含元素數量多少的一個概念。比如:集合A={1,2,3,4,5,5,5,6},集合B={3,4,5,6,7},基數(兩個集合不重複的元素) = 7

優點
佔用的內存是固定,2^64 不同的元素的技術,只需要花費 12KB內存!如果要從內存角度來比較的 話 Hyperloglog 首選!

使用場景
網頁的 UV (一個人訪問一個網站多次,但是還是算作一個人!),傳統的方式, set 保存用戶的id,然後就可以統計 set 中的元素數量作爲標準判斷 ! 這個方式如果保存大量的用戶id,就會比較麻煩!我們的目的是爲了計數,而不是保存用戶id;根據官方給的數據,Hyperloglo會有0.81% 錯誤率! 統計UV任務,可以忽略不計的!

127.0.0.1:6379> PFADD H1 A B C  # # 創建第一組元素 H1
(integer) 1
127.0.0.1:6379> PFADD H2 C B A A D E G
(integer) 1
127.0.0.1:6379> PFADD H3 Q W E R
(integer) 1
127.0.0.1:6379> PFCOUNT H1 # 統計 H1元素的基數數量 
(integer) 3
127.0.0.1:6379> PFMERGE H H1 H2 H3  # 合併三組 H1 H2 H3 => H 並集 
OK
127.0.0.1:6379> PFCOUNT H
(integer) 9

(9)Bitmap位存儲

Bitmap存儲的數據只有0和1,利用0和1可以用來做一些狀態的記錄,前提是這種狀態只有兩種情況

應用場景
統計用戶信息,活躍,不活躍! 登錄 、 未登錄! 打卡,365天打卡! 兩個狀態的,都可以使用 Bitmaps!Bitmap 位圖,數據結構! 都是操作二進制位來進行記錄,就只有0 和 1 兩個狀態! 365 天 = 365 bit 1字節 = 8bit 46 個字節左右

應用場景一:一週的打卡記錄
0到6表示週一到周天,打卡狀態由0和1記錄,0代表缺勤,1代表已打卡

127.0.0.1:6379> setbit sign 0 1  #設置打卡狀態
(integer) 0
127.0.0.1:6379> setbit sign 1 1
(integer) 0
127.0.0.1:6379> setbit sign 2 1
(integer) 0
127.0.0.1:6379> setbit sign 3 1
(integer) 0
127.0.0.1:6379> setbit sign 4 0
(integer) 0
127.0.0.1:6379> setbit sign 5 0
(integer) 0
127.0.0.1:6379> setbit sign 6 1
(integer) 0
127.0.0.1:6379> getbit sign 0  #查看某一天的打卡狀態
(integer) 1
127.0.0.1:6379> getbit sign 4
(integer) 0
127.0.0.1:6379> bitcount sign  #統計這一週的打卡情況
(integer) 5

(10)事務

Redis事務的概念
Redis 事務的本質是一組命令的集合。事務支持一次執行多個命令,一個事務中所有命令都會被序列化。在事務執行過程,會按照順序串行化執行隊列中的命令,其他客戶端提交的命令請求不會插入到事務執行命令序列中。

總結說:redis事務就是一次性、順序性、排他性的執行一個隊列中的一系列命令。

Redis事務沒有隔離級別的概念
批量操作在發送 EXEC 命令前被放入隊列緩存,並不會被實際執行,也就不存在事務內的查詢要看到事務裏的更新,事務外查詢不能看到。

Redis不保證原子性
Redis中,單條命令是原子性執行的,但事務不保證原子性,且沒有回滾。事務中任意命令執行失敗,其餘的命令仍會被執行。

Redis事務的三個階段
(1)開始事務(multi)
(2)命令入隊
(3)執行事務(exec)

正常執行事務

127.0.0.1:6379> multi  #開啓事務
OK
127.0.0.1:6379> set k1 v1  #執行的命令都不會立刻執行,只會先放入隊列
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> exec   #執行事務,只有執行了exec命令後,隊列裏面的命令纔會執行
1) OK
2) OK
3) OK
127.0.0.1:6379>

事務的編譯時異常
事務在執行的時候有可能會遇到一些異常,比如編譯時的異常,運行時的異常,遇到編譯時異常,事務中所有的命令都不會被執行!

127.0.0.1:6379> multi    #開啓事務
OK
127.0.0.1:6379> set k1 v1     #執行的命令都不會立刻執行,只會先放入隊列
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> getsdfsafsaf   #隨便輸入不存在的命令,遇到編譯時異常
(error) ERR unknown command 'getsdfsafsaf'
127.0.0.1:6379> set k4 v4    #再設置命令
QUEUED
127.0.0.1:6379> exec   #執行事務,發現會失敗,事務中所有的命令都不會被執行
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get k1   
(nil)
127.0.0.1:6379>

運行時異常
運行時異常(1/0), 如果事務隊列中存在語法性,那麼執行命令的時候,其他命令是可以正常執行 的,錯誤命令拋出異常

127.0.0.1:6379> multi    #開啓事務
OK
127.0.0.1:6379> set k1 "hello"  #執行的命令都不會立刻執行,只會先放入隊列
QUEUED
127.0.0.1:6379> incr k1     #對字符串進行自增1,遇到運行時異常
QUEUED
127.0.0.1:6379> set k2 v2   #再設置命令
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> exec   #執行事務,發現除了那一條異常的命令,其餘都執行成功了
1) OK
2) (error) ERR value is not an integer or out of range
3) OK
4) OK
127.0.0.1:6379> keys *
1) "H2"
2) "k3"
3) "k1"
4) "sign"
5) "k2"
6) "china:city"
7) "H1"
8) "H"
9) "H3"
127.0.0.1:6379>

放棄事務

127.0.0.1:6379> multi   #開啓事務
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> discard   #放棄事務
OK
127.0.0.1:6379> keys *
1) "H2"
2) "sign"
3) "china:city"
4) "H1"
5) "H"
6) "H3"
127.0.0.1:6379>

(11)watch監控實現樂觀鎖

悲觀鎖:顧名思義就是很“悲觀”,無論執行什麼操作都覺得會出問題,所以在執行任何操作的時候都會加上鎖,這樣就會影響性能
樂觀鎖:顧名思義就是很“樂觀”,認爲什麼時候都不會出問題,所以不會上鎖! 在數據進行提交更新的時候,纔會正式對數據的衝突與否進行檢測,如果發現衝突了,則返回給用戶錯誤的信息,讓用戶決定如何去做。

redis利用watch的監控功能可以實現樂觀鎖,正常實現樂觀鎖的流程就是利用版本號比較機制,只是在讀數據的時候,將讀到的數據的版本號一起讀出來,當對數據的操作結束後,準備寫數據的時候,再進行一次數據版本號的比較,若版本號沒有變化,即認爲數據是一致的,沒有更改,可以直接寫入,若版本號有變化,則認爲數據被更新,不能寫入,防止髒寫

watch監控測試

127.0.0.1:6379> set money 1000   #假設現有1000塊錢
OK
127.0.0.1:6379> set out 0   #消費了0塊錢
OK
127.0.0.1:6379> watch money   #開啓監控,會監控money值的變化
OK
127.0.0.1:6379> multi   #開啓事務
OK
127.0.0.1:6379> decrby money 100   #花費了100塊錢,剩下900
QUEUED
127.0.0.1:6379> incrby out 100   #消費餘額100塊錢
QUEUED
127.0.0.1:6379> exec    #執行事務,單線程情況下,watch沒有監控到其餘線程對money進行操作,正常執行
1) (integer) 900
2) (integer) 100
127.0.0.1:6379>

多線程修改值 , 利用watch 實現redis的樂觀鎖操作
上面的例子由於是單線程的,watch沒有監控到其餘線程對money進行操作,所以正常執行,如果watch監控到有其餘線程已經對money進行了修改,則會執行失敗,這就是redis利用watch的監控功能實現樂觀鎖操作的原理

在這裏插入圖片描述

(12)通過Jedis操作redis

簡單說一下Jedis,Jedis是Redis官方推薦的Java連接開發工具。要在Java開發中使用好Redis中間件,必須對Jedis熟悉才能寫成漂亮的代碼。

String類型

    public static void main(String[] args) {
        //連接redis
        Jedis jedis = new Jedis("127.0.0.1",6379);

        /**設置值*/
        jedis.set("k1", "v1");//設置一個值
        jedis.mset("k2","v2","k3","v3","k4","v4","k5","v5");//設置多個值
        jedis.append("k1","hello");//再k1後面再追加值“hello”
        jedis.setnx("k6","v6");//注意與set的區別,setnx表示如果不存在才創建
        jedis.setnx("k5","v5");//setnx如果存在就創建失敗,set的話則會覆蓋
        jedis.setex("k7",10,"v7");//設置一個值,10秒後過期


        /**獲取值*/
        String k1 = jedis.get("k1");//獲取一個值
        List<String> mgetlist = jedis.mget("k2", "k3", "k4","k5","k6","k7");//獲取多個值
        System.out.println("k1 = "+k1);
        for (int i = 0; i <mgetlist.size(); i++) {
            System.out.println("k"+(i+2)+" = "+mgetlist.get(i));
        }



        /**刪除值*/
        jedis.del("k1");//刪除單個值
        jedis.del("k2","k3","k4","k5","k6","k7");//刪除多個值

        jedis.close();//關閉連接
    }

List類型

    public static void main(String[] args) {
        //連接redis
        Jedis jedis = new Jedis("127.0.0.1",6379);

        /**設置值*/
        jedis.lpush("list1","java");//設置一個值,左邊插入
        jedis.lpush("list1","c","c++","python");//設置多個值,左邊插入
        jedis.rpush("list1","php");//設置一個值,右邊插入
        jedis.rpush("list1","c#","js","jq");//設置多個值,右邊插入



        /**獲取值*/
        List<String> list1 = jedis.lrange("list1", 0, -1);//通過區間的方式獲取list1的值
        String str = jedis.lindex("list1", 0);//獲取指定下標的元素
        Long len = jedis.llen("list1");//獲取key的所有元素的長度
        for (int i = 0; i < list1.size(); i++) {
            System.out.println("list1["+i+"] = "+list1.get(i));
        }
        System.out.println("list1[0] = "+str);
        System.out.println("list1長度 = "+len);

        /**修改值*/
        jedis.lset("list1",1,"new Value");//修改某一個key對應下標的值


        /**刪除值*/
        jedis.lrem("list1",1,"c");//刪除一個list1中名爲”c“的值,如果存在重複元素,可以刪除多個
        jedis.ltrim("list1",0,2);//截取下標爲0-2的元素,其餘的刪掉
        jedis.lpop("list1");//出棧,左邊
        jedis.rpop("list1");//出棧,右邊

        jedis.close();

    }

Set類型

    public static void main(String[] args) {
        //連接redis
        Jedis jedis = new Jedis("127.0.0.1",6379);

        /**設置值*/
        jedis.sadd("set1","a","b","c","c");//設置多個值
        jedis.sadd("set2","a","c","e","u","q");

        /**獲取值*/
        jedis.smembers("set1");//獲取所有的值
        jedis.scard("set1");//獲取元素中包含的個數
        jedis.sismember("set1","a");//判斷set1元素中是否包含有a這個元素


        /**刪除值*/
        jedis.srem("set1","a");//刪除值”a“
        jedis.srem("list1","b","c");//刪除多個值



        /**集合運算*/
        jedis.sinter("set1","set2");//set1與set2交集
        jedis.sunion("set1","set2");//set1與set2並集
        jedis.sdiff("set1","set2");//set1與set2差集
        jedis.sinterstore("set3","set1","set2");//求set1與set2交集並賦值給set3


        jedis.close();

    }

Hash類型

    public static void main(String[] args) {
        //連接redis
        Jedis jedis = new Jedis("127.0.0.1",6379);

        Map<String,String> map = new HashMap<>();
        map.put("k1","v1");
        map.put("k2","v2");
        map.put("k3","v3");

        /**添加值*/
        jedis.hmset("hash1",map);//添加map
        jedis.hset("hash1","k4","v5");//向hash1中添加key爲k5,value爲v5的元素


        /**獲取值*/
        Map<String, String> map1 = jedis.hgetAll("hash1");//獲取hash1的所有的鍵值對
        Set<String> keys = jedis.hkeys("hash1");//hash1的所有鍵
        List<String> values = jedis.hvals("hash1");//hash1的所有值
        List<String> hash1 = jedis.hmget("hash1", "k1");//獲取hash1中的值
        Long len = jedis.hlen("hash1");//hash1的長度
        Boolean hexists = jedis.hexists("hash1", "k8");//判斷hash1中是否含有k8


        /**刪除值*/
        jedis.hdel("hash1","k1","k2","k3");

        jedis.close();
    }

Zset類型

    public static void main(String[] args) {
        //連接redis
        Jedis jedis = new Jedis("127.0.0.1",6379);

        /**添加值*/
        jedis.zadd("salary",8000,"xm");
        jedis.zadd("salary",10000,"zs");
        jedis.zadd("salary",9000,"ls");
        jedis.zadd("salary",9800,"xh");


        /**獲取值*/
        Set<String> salary1 = jedis.zrangeByScore("salary",0 , 10001);//將salary從小到大排序
        Set<String> salary2 = jedis.zrevrange("salary", 0, -1);//倒序輸出,因爲上面已經排過序了,所以倒序就是從大到小輸出!
        Set<String> salary3 = jedis.zrange("salary", 0, -1);//順序輸出,已經排過序了
        Long len = jedis.zcard("salary");//集合的元素個數
        System.out.println("從小到大輸出");
        salary1.forEach(System.out::println);
        System.out.println("從大到小輸出");
        salary2.forEach(System.out::println);
        System.out.println("順序輸出");
        salary3.forEach(System.out::println);
        System.out.println("長度");
        System.out.println(len);

        /**刪除值*/
        jedis.zrem("salary","ls");//移除用戶

        jedis.close();
    }

事務(重點)

    public static void main(String[] args) {
        //連接redis
        Jedis jedis = new Jedis("127.0.0.1", 6379);

        JSONObject jsonObject = new JSONObject();
        jsonObject.put("name","zs");
        jsonObject.put("age","12");
        jsonObject.put("sex","雄");
        String user = jsonObject.toString();


        //開啓事務
        Transaction multi = jedis.multi();

        try{
            multi.set("user1",user);
            int i = 1/0;//遇到運行時異常
            multi.set("user2",user);
            multi.exec();//執行事務
        }catch (Exception e){
            multi.discard();//放棄事務
            e.printStackTrace();
        }finally {
            System.out.println(jedis.get("user1"));
            System.out.println(jedis.get("user2"));
            jedis.close();
        }
        
    }

運行結果
在這裏插入圖片描述

(13)springboot整合redis

在 SpringBoot2.x 之後,原來使用的jedis 被替換爲了 lettuce? jedis採用的直連,多個線程操作的話,是不安全的,如果想要避免不安全的,使用 jedis pool 連接 池! 更像 BIO 模式, lettuce採用netty,實例可以再多個線程中進行共享,不存在線程不安全的情況!可以減少線程數據 了,更像 NIO 模式。

說明:關於lettuce本人只是簡單瞭解了一下,也不是很懂

(1)源碼分析

在這裏插入圖片描述
我這邊的springboot版本是2.2.6,從上面的圖可以看到springboot2.x用的redis的java客戶端是lettuce

redis自動配置類(autoconfiguration)
在這裏插入圖片描述
對應的配置文件RedisProperties,裏面的參數就是我們可以配置的
在這裏插入圖片描述

(2)使用配置好模板RedisTemplate

獲取RedisTemplate的bean對象,之後就可以直接像調用redis那樣直接調用接口,api就跟正常操作redis的命令一樣
在這裏插入圖片描述
使用上面配置好的RedisTemplate模板來操作redis,但是會出現一個問題,那就是序列化的問題,比如下面的例子,用該模板存儲幾個值,發現在idea裏面可以正常獲取,但是一打開redis-cil查看卻發現,key前面多了很多轉義字符
在這裏插入圖片描述
原因分析
在查詢了一些資料之後,發現任何數據存進redis裏面都必須先序列化,用哪種序列化方式可以自己設置,如果不設置的話,默認使用JdkSerializationRedisSerializer進行數據序列化。

所以很明顯,那些轉義字符都是JdkSerializationRedisSerializer進行序列化時,加上去的,此外還有其他序列化的方式,具體如下
(1)StringRedisSerializer
(2)Jackson2JsonRedisSerialize
(3)JdkSerializationRedisSerializer
(4)GenericToStringSerializer
(5)OxmSerializer

給出一張表,不同的序列化後的值前後對比
在這裏插入圖片描述
所以一般不會用這個默認的配置模板,而是會自己寫一個模板,並且採用其他序列化的方式,這樣就不會有上面的問題。

拓展
這讓我想起一件事,就是之前在學習面向接口編程的時候,有一條規則,就是所有的實體類pojo創建完後必須實現序列化接口,如果我們不實現序列化接口,直接就將用戶類儲存進redis,他會報一個序列化錯誤的異常,如果實現了序列化接口,可以直接存redis

(3)使用自定義模板RedisTemplate

如果使用自定義的模板RedisTemplate,會出現序列化的問題,所以才需要自己寫一個RedisTemplate來供自己操作redis

/**
 * redis配置類
 */
@Configuration
public class RedisConfig {

    /**redis配置模板,可直接copy使用**/
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory)
            throws UnknownHostException {

        //爲了開發的方便,一般直接使用<String, Object>
        RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
        template.setConnectionFactory(factory);

        //json的序列化配置
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.activateDefaultTyping(
                LaissezFaireSubTypeValidator.instance ,
                ObjectMapper.DefaultTyping.NON_FINAL,
                JsonTypeInfo.As.PROPERTY);
        jackson2JsonRedisSerializer.setObjectMapper(om);

        //String的序列化
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();

        // key採用String的序列化方式
        template.setKeySerializer(stringRedisSerializer);

        // hash的key也採用String的序列化方式
        template.setHashKeySerializer(stringRedisSerializer);

        // value序列化方式採用jackson
        template.setValueSerializer(jackson2JsonRedisSerializer);

        // hash的value序列化方式採用jackson
        template.setHashValueSerializer(jackson2JsonRedisSerializer);

        template.afterPropertiesSet();

        return template;
    }
}

採用了string和json序列化的方式,成功解決上面的問題
在這裏插入圖片描述

(4)使用RedisUtil

自定義的模板看起來很好,但是還有一種方式更好,那就是將操作redis的命令封裝成一個工具類,讓我們能跟直接操作redis的api一樣,這樣在使用redis的java客戶端時就能實現無縫切換,因爲封裝好的工具類的接口就跟直接操作redis的接口一樣

package com.zsc.util;

import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

/**
 *
 * Redis工具類,一般企業開發常用的一個工具類,不會去用原生的redis配置類
 *
 */

@Component
public final class RedisUtil {
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    // =============================common============================

    /**
     * 26
     * 指定緩存失效時間
     * 27
     *
     * @param key  鍵
     *             28
     * @param time 時間(秒)
     *             29
     * @return 30
     */

    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;

        }

    }

    /**
     * 44
     * 根據key 獲取過期時間
     * 45
     *
     * @param key 鍵 不能爲null
     *            46
     * @return 時間(秒) 返回0代表爲永久有效
     * 47
     */

    public long getExpire(String key) {

        return redisTemplate.getExpire(key, TimeUnit.SECONDS);

    }

    /**
     * 53
     * 判斷key是否存在
     * 54
     *
     * @param key 鍵
     *            55
     * @return true 存在 false不存在
     * 56
     */

    public boolean hasKey(String key) {

        try {

            return redisTemplate.hasKey(key);

        } catch (Exception e) {

            e.printStackTrace();

            return false;

        }

    }

    /**
     * 67
     * 刪除緩存
     * 68
     *
     * @param key 可以傳一個值 或多個
     *            69
     */

    @SuppressWarnings("unchecked")

    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));

            }

        }

    }

    // ============================String=============================

    /**
     * 83
     * 普通緩存獲取
     * 84
     *
     * @param key 鍵
     *            85
     * @return 值
     * 86
     */

    public Object get(String key) {

        return key == null ? null : redisTemplate.opsForValue().get(key);

    }

    /**
     * 92
     * 普通緩存放入
     * 93
     *
     * @param key   鍵
     *              94
     * @param value 值
     *              95
     * @return true成功 false失敗
     * 96
     */

    public boolean set(String key, Object value) {

        try {

            redisTemplate.opsForValue().set(key, value);

            return true;

        } catch (Exception e) {

            e.printStackTrace();

            return false;

        }

    }

    /**
     * 109
     * 普通緩存放入並設置時間
     * 110
     *
     * @param key   鍵
     *              111
     * @param value 值
     *              112
     * @param time  時間(秒) time要大於0 如果time小於等於0 將設置無限期
     *              113
     * @return true成功 false 失敗
     * 114
     */

    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;

        }

    }

    /**
     * 130
     * 遞增
     * 131
     *
     * @param key   鍵
     *              132
     * @param delta 要增加幾(大於0)
     *              133
     * @return 134
     */

    public long incr(String key, long delta) {

        if (delta < 0) {

            throw new RuntimeException("遞增因子必須大於0");

        }

        return redisTemplate.opsForValue().increment(key, delta);

    }

    /**
     * 143
     * 遞減
     * 144
     *
     * @param key   鍵
     *              145
     * @param delta 要減少幾(小於0)
     *              146
     * @return 147
     */

    public long decr(String key, long delta) {

        if (delta < 0) {

            throw new RuntimeException("遞減因子必須大於0");

        }

        return redisTemplate.opsForValue().increment(key, -delta);

    }

    // ================================Map=================================

    /**
     * 157
     * HashGet
     * 158
     *
     * @param key  鍵 不能爲null
     *             159
     * @param item 項 不能爲null
     *             160
     * @return 值
     * 161
     */

    public Object hget(String key, String item) {

        return redisTemplate.opsForHash().get(key, item);

    }

    /**
     * 167
     * 獲取hashKey對應的所有鍵值
     * 168
     *
     * @param key 鍵
     *            169
     * @return 對應的多個鍵值
     * 170
     */

    public Map<Object, Object> hmget(String key) {

        return redisTemplate.opsForHash().entries(key);

    }

    /**
     * 176
     * HashSet
     * 177
     *
     * @param key 鍵
     *            178
     * @param map 對應多個鍵值
     *            179
     * @return true 成功 false 失敗
     * 180
     */

    public boolean hmset(String key, Map<String, Object> map) {

        try {

            redisTemplate.opsForHash().putAll(key, map);

            return true;

        } catch (Exception e) {

            e.printStackTrace();

            return false;

        }

    }

    /**
     * 192
     * HashSet 並設置時間
     * 193
     *
     * @param key  鍵
     *             194
     * @param map  對應多個鍵值
     *             195
     * @param time 時間(秒)
     *             196
     * @return true成功 false失敗
     * 197
     */

    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;

        }

    }

    /**
     * 212
     * 向一張hash表中放入數據,如果不存在將創建
     * 213
     *
     * @param key   鍵
     *              214
     * @param item  項
     *              215
     * @param value 值
     *              216
     * @return true 成功 false失敗
     * 217
     */

    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;

        }

    }

    /**
     * 229
     * 向一張hash表中放入數據,如果不存在將創建
     * 230
     *
     * @param key   鍵
     *              231
     * @param item  項
     *              232
     * @param value 值
     *              233
     * @param time  時間(秒) 注意:如果已存在的hash表有時間,這裏將會替換原有的時間
     *              234
     * @return true 成功 false失敗
     * 235
     */

    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;

        }

    }

    /**
     * 250
     * 刪除hash表中的值
     * 251
     *
     * @param key  鍵 不能爲null
     *             252
     * @param item 項 可以使多個 不能爲null
     *             253
     */

    public void hdel(String key, Object... item) {

        redisTemplate.opsForHash().delete(key, item);

    }

    /**
     * 259
     * 判斷hash表中是否有該項的值
     * 260
     *
     * @param key  鍵 不能爲null
     *             261
     * @param item 項 不能爲null
     *             262
     * @return true 存在 false不存在
     * 263
     */

    public boolean hHasKey(String key, String item) {

        return redisTemplate.opsForHash().hasKey(key, item);

    }

    /**
     * 269
     * hash遞增 如果不存在,就會創建一個 並把新增後的值返回
     * 270
     *
     * @param key  鍵
     *             271
     * @param item 項
     *             272
     * @param by   要增加幾(大於0)
     *             273
     * @return 274
     */

    public double hincr(String key, String item, double by) {

        return redisTemplate.opsForHash().increment(key, item, by);

    }

    /**
     * 280
     * hash遞減
     * 281
     *
     * @param key  鍵
     *             282
     * @param item 項
     *             283
     * @param by   要減少記(小於0)
     *             284
     * @return 285
     */

    public double hdecr(String key, String item, double by) {

        return redisTemplate.opsForHash().increment(key, item, -by);

    }

    // ============================set=============================

    /**
     * 292
     * 根據key獲取Set中的所有值
     * 293
     *
     * @param key 鍵
     *            294
     * @return 295
     */

    public Set<Object> sGet(String key) {

        try {

            return redisTemplate.opsForSet().members(key);

        } catch (Exception e) {

            e.printStackTrace();

            return null;

        }

    }

    /**
     * 306
     * 根據value從一個set中查詢,是否存在
     * 307
     *
     * @param key   鍵
     *              308
     * @param value 值
     *              309
     * @return true 存在 false不存在
     * 310
     */

    public boolean sHasKey(String key, Object value) {

        try {

            return redisTemplate.opsForSet().isMember(key, value);

        } catch (Exception e) {

            e.printStackTrace();

            return false;

        }

    }

    /**
     * 321
     * 將數據放入set緩存
     * 322
     *
     * @param key    鍵
     *               323
     * @param values 值 可以是多個
     *               324
     * @return 成功個數
     * 325
     */

    public long sSet(String key, Object... values) {

        try {

            return redisTemplate.opsForSet().add(key, values);

        } catch (Exception e) {

            e.printStackTrace();

            return 0;

        }

    }

    /**
     * 336
     * 將set數據放入緩存
     * 337
     *
     * @param key    鍵
     *               338
     * @param time   時間(秒)
     *               339
     * @param values 值 可以是多個
     *               340
     * @return 成功個數
     * 341
     */

    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;

        }

    }

    /**
     * 355
     * 獲取set緩存的長度
     * 356
     *
     * @param key 鍵
     *            357
     * @return 358
     */

    public long sGetSetSize(String key) {

        try {

            return redisTemplate.opsForSet().size(key);

        } catch (Exception e) {

            e.printStackTrace();

            return 0;

        }

    }

    /**
     * 369
     * 移除值爲value的
     * 370
     *
     * @param key    鍵
     *               371
     * @param values 值 可以是多個
     *               372
     * @return 移除的個數
     * 373
     */

    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=================================

    /**
     * 386
     * 獲取list緩存的內容
     * 387
     *
     * @param key   鍵
     *              388
     * @param start 開始
     *              389
     * @param end   結束 0 到 -1代表所有值
     *              390
     * @return 391
     */

    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;

        }

    }

    /**
     * 402
     * 獲取list緩存的長度
     * 403
     *
     * @param key 鍵
     *            404
     * @return 405
     */

    public long lGetListSize(String key) {

        try {

            return redisTemplate.opsForList().size(key);

        } catch (Exception e) {

            e.printStackTrace();

            return 0;

        }

    }

    /**
     * 416
     * 通過索引 獲取list中的值
     * 417
     *
     * @param key   鍵
     *              418
     * @param index 索引 index>=0時, 0 表頭,1 第二個元素,依次類推;index<0時,-1,表尾,-2倒數第二個元素,依次類推
     *              419
     * @return 420
     */

    public Object lGetIndex(String key, long index) {

        try {

            return redisTemplate.opsForList().index(key, index);

        } catch (Exception e) {

            e.printStackTrace();

            return null;

        }

    }

    /**
     * 431
     * 將list放入緩存
     * 432
     *
     * @param key   鍵
     *              433
     * @param value 值
     *              434
     * //@param time  時間(秒)
     *              435
     * @return 436
     */

    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  時間(秒)
     * @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;

        }

    }

    /**
     * 467
     * 將list放入緩存
     * 468
     *
     * @param key   鍵
     *              469
     * @param value 值
     *              470
     * //@param time  時間(秒)
     *              471
     * @return 472
     */

    public boolean lSet(String key, List<Object> value) {

        try {

            redisTemplate.opsForList().rightPushAll(key, value);

            return true;

        } catch (Exception e) {

            e.printStackTrace();

            return false;

        }

    }

    /**
     * 484
     * 將list放入緩存
     * 485
     * <p>
     * 486
     *
     * @param key   鍵
     *              487
     * @param value 值
     *              488
     * @param time  時間(秒)
     *              489
     * @return 490
     */

    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;

        }

    }

    /**
     * 504
     * 根據索引修改list中的某條數據
     * 505
     *
     * @param key   鍵
     *              506
     * @param index 索引
     *              507
     * @param value 值
     *              508
     * @return 509
     */

    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;

        }

    }

    /**
     * 521
     * 移除N個值爲value
     * 522
     *
     * @param key   鍵
     *              523
     * @param count 移除多少個
     *              524
     * @param value 值
     *              525
     * @return 移除的個數
     * 526
     */

    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;

        }

    }
}

使用的方式就跟直接操作redis一樣在這裏插入圖片描述
真正實現了無縫切換,事實上這個也是使用最多的方式之一。

五、redis配置文件詳解

在學習這一部分的內容時,我查詢了大量的資料,現將查詢到的相關知識記錄在下面

配置文件位於redis的安裝目錄下,本人這裏用的win10系統,配置文件如下圖
在這裏插入圖片描述
爲了方便解讀,我將配置文件的內容複製了一份,並且在idea項目裏面新建一個文本,將內容粘貼在該文本,這麼做的目的主要是我的idea有安裝英語的翻譯插件,方便解讀英語的註釋

首先是一開始的說明
在這裏插入圖片描述
INCLUDES(包括)
在這裏插入圖片描述
NETWORK(網絡)
在這裏插入圖片描述
在這裏插入圖片描述
GENERAL(通用)
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
SNAPSHOTTING(快照)
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

SECURITY(安全)
在這裏插入圖片描述

LIMITS(限制)
在這裏插入圖片描述
在這裏插入圖片描述

APPEND ONLY MODE
在這裏插入圖片描述
除此之外還有其他配置,上面列的是一些相對重要的配置。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章