Redis學習筆記(一)

2020-08-07 20:32:07

學習視訊鏈接: https://www.bilibili.com/video/BV1S54y1R7SB

一、Nosql概述

1.1. 爲什麼要用Nosql

1、單機MySQL的年代

image.png

90年代,一個基本的網站存取量一般不會太大,單個數據庫完全足夠!

那個時候,更多的去使用靜態網頁Html~伺服器根本沒有太大的壓力!

思考一下,這種情況下:整個網站的瓶頸是什麼?

1、數據量如果太大、一個機器放不下了!

2、數據的索引(B+Tree),一個機器記憶體也放不下

3、存取量(讀寫混合),一個伺服器承受不了~

只要你開始出現以上的三種情況之一,那麼你就必須要晉級!

2、Memcached(快取)+MySQL+垂直拆分

網站80%的情況都是在讀,每次都要去查詢數據庫的話就十分的麻煩!所以說我們希望減輕數據的壓力,我們可以使用快取來保證效率!(解決讀的效率問題)

發展過程:優化數據結構和索引 ==> 檔案快取(IO) ==> Memcached(當時最熱門的技術!)

image.png

3、分庫分表 + 水平拆分 + MySQL叢集

技術和業務在發展的同時,對人的要求也越來越高!(解決寫的問題)

早些年 MyISAM :表鎖,十分影響效率!高併發下就會出現嚴重的鎖問題(100W條數據查張三密碼,在查詢的過程中會將此表鎖定,無法查詢其餘的資訊)

轉戰 Innodb : 行鎖(提高效率)

慢慢的就開始使用分庫分表來解決寫的壓力!MySQL在那個年代推出了表分割區!這個並沒有多少公司使用!

推出 MySQL 叢集,很好滿足哪個年代的所有需求!

image.png

4、如今最近的年代

2010 – 2020 十年之間,世界已經發生了翻天覆地的變化;(定位,音樂,熱榜!)

MySQL 等關係型數據庫不夠用!數據量很多,變化很快!

MySQL 有些人使用它來儲存一些比較大的檔案、部落格、圖片!數據庫表很多,效率就低了! 如果有一種數據庫來專門處理這種數據,MySQL 壓力就變得十分小(研究如何處理這些問題!)大數據的 IO 壓力下,表幾乎沒法更大!(eg:幾億的量,如果新增一個列,即新增幾億的數據)

目前的一個網際網路專案(架構)

image.png

爲什麼要用 NoSQL!

使用者的個人資訊,社羣網路,地理位置。使用者自己產生的數據,使用者日誌等等爆發式增長!

這時候我們就需要使用NoSQL數據庫的,NoSQL可以很好的處理以上的情況

1.2. 什麼是NoSQL

NoSQL

NoSQL = Not Only SQL (不僅僅是SQL)

關係型數據庫:表格、行、列

泛指非關係型數據庫的,隨着web2.0網際網路的誕生!傳統的關係型數據庫很難對付web2.0時代!尤其是超大規模的高併發的社羣!暴露出來很多難以克服的問題,NoSQL在當今大數據環境下發展的十分迅速, Redis 是發展最快的,而且是我們當下必須要掌握的一個技術!

很多的數據型別使用者的個人資訊,社羣網路,地理位置。這些數據型別的儲存不需要一個固定的格式!不需要過多的操作就可以橫向擴充套件的!Map<String,Object> 使用鍵值對來控制!

NoSQL 特點

解耦!

1、方便擴充套件(數據之間沒有關係,很好擴充套件!)

2、大數據量高效能(Redis 一秒寫 8w次,讀取 11w,NoSQL的快取記錄級,是一種細粒度的快取,效能會比較高!)

3、數據型別是多樣型的!(不需要事先設計數據庫!隨取隨用!如果是數據庫十分大的表,很多人就無法設計了!)

4、傳統 RDBMS(關係型數據庫) 和 NoSQL(非關係型數據庫)

傳統的 RDBMS
- 結構化組織
- SQL
- 數據和關係都存在單獨的表中 row col
- 操作,數據定義語言
- 嚴格的一致性
- 基礎的事務
- ......
NoSQL
- 不僅僅是數據
- 沒有固定的查詢語言
- 鍵值對儲存,列儲存,文件儲存,圖形數據庫(社交關係)
- 最終一致性,
- CAP定理和BASE(異地多活)初級架構師!(狂神理念:只要學不死,就往死裡學!)
- 高效能,高可用,高可擴
- ....

瞭解: 3V + 3 高

大數據時代的3V:主要是描述問題的

  1. 海量 Volume

  2. 多樣 Variety

  3. 實時 Velocity

大數據時代的3高:主要是對程式的要求

  1. 高併發

  2. 高可擴(隨時水平拆分,機器不夠可以通過擴充套件機器來調節)

  3. 高效能(保證使用者體驗和效能!)

真正在公司中的實踐: NoSQL+RDBMS一起使用纔是最強的,阿裡巴巴的架構演進!

技術沒有高低之分,就看你如何去使用!(提升內功,思維的提高!)

1.3. 阿裡巴巴演進分析

敏捷開發、極限程式設計

image.png

架構師:沒有什麼是加一層解決不了的!

# 1、商品的基本資訊
		名稱、價格、商品資訊:
		關係型數據庫就可以解決了! MySQL/Oracle(淘寶早年就去IOE)
		淘寶內部的MySQL不是大家用的MySQL,他們根據自身業務對MySQL進行修改
# 2、商品的描述、評論(文字比較多)
		文件型數據庫中,MongoDB
# 3、圖片
		分佈式檔案系統 FastDFS
		- 淘寶自己的  TFS
		- Gooale的   GFS
		- Hadoop  	 HJDFS
		- 阿裡雲的    oss
# 4、商品的關鍵字(搜尋)
		- 搜尋引擎 solr elasticsearch
		- ISerach: 多隆(多瞭解一些技術大佬)
		
# 5、商品熱門的波段資訊
		- 記憶體數據庫
		- Redis、Tair、Memache
# 6、商品的交易,外部的支付介面
		- 三方應用

一個簡單的網頁背後的技術一定不是大家所想的那麼簡單!

大型網際網路應用問題:

  • 數據型別太多了
  • 數據員繁多,經常重構!
  • 數據要改造,大面積改造

解決問題:(新增多一層)

image.png

image.png

這裏以上都是NoSQL入門概述,不僅能夠提高大家的知識,還可以幫助大家瞭解大廠 的工作內容!

1.4. NoSQL的四大分類

KV鍵值對:

  • 新浪:Redis

  • 美團:Redis + Tair

  • 阿裡、百度: Redis + Memecache

文件型數據庫(bson格式和json一樣):

  • MongoDB (一般必須要掌握)
    • MongoDB 是一個基於分佈式檔案儲存的數據庫,C++ 編寫, 主要用來處理大量的文件!
    • MOngoDB 是一個介於關係型數據庫和非關係型數據庫中的產品!MongoDB 是非關係型數據庫中功能最豐富,最像關係型數據庫的!
  • ConthDB

列儲存數據庫

  • HBase
  • 分佈式檔案系統

圖關係數據庫

image.png

  • 他不是存圖形,放的是關係,比如:朋友圈社羣網路,廣告推薦!
  • Neo4j,InfoGrid;

四者對比

image.png

二、Redis 入門

2.1. 概述

Redis 是什麼?

Redis(Remote Dictionary Server ),即遠端字典服務

是一個開源的使用ANSI C語言編寫、支援網路、可基於記憶體亦可持久化的日誌型、Key-Value數據庫,並提供多種語言的API

image.png

redis會週期性的把更新的數據寫入磁碟或者把修改操作寫入追加的記錄檔案,並且在此基礎上實現了master-slave(主從)同步。

免費和開源!是當下最熱門的NoSQL技術之一!也被人們稱之爲結構化數據庫!

Redis 能幹嗎?

1、記憶體儲存、持久化,記憶體中是斷電即失、所以說持久化很重要(rdb、aof

2、效率高,可以用於快取記憶體

3、發佈訂閱系統

4、地圖資訊分析

5、計時器、計數器(瀏覽量)

特性

1、多樣的數據型別

2、持久化

3、叢集

4、事務

學習中需要的東西

1、官網: https://redis.io/

2、中文網: https://www.redis.net.cn/

image.png

3、下載地址:通過官網下載即可

image.png

注意:Window 在 Github 上下載(停更很久!)

Reids推薦都是在Linux伺服器上搭建的,我們是基於Linux學習!

2.2. Windows安裝

1、下載安裝包:

2、下載完畢得到壓縮包:

image.png

3、解壓到自己電腦上的環境目錄下即可!Redis 十分小,只有 5M

image.png

4、開啓Redis,雙擊 redis-server.exe 即可

image.png

5、使用 Redis 用戶端來連線 Redis (redis-cli.exe)

image.png

Window 下使用確實簡單,但是 Redis 推薦我們使用 Linux 系統使用

image.png

2.3. Linux安裝

1、官網下載 Redis

2、將 Redis 安裝包解壓到 程式/opt 目錄下

image.png

3、進入解壓後的檔案,可以看到我們 Redis 的組態檔

image.png

4、基本的環境安裝

# 安裝環境
yum install gcc-c++
# 設定
make
# 安裝
make install

5、redis 的預設安裝路徑:/usr/local/bin

image.png

6、將 redis 組態檔。複製到我們當前目錄下

image.png

7、redis 預設不是後臺啓動的,修改組態檔!

image.png

8、啓動 Redis 服務!

image.png

9、使用 redis-cli 進行連線測試!

image.png

10、檢視redis的進程是否開啓

image.png

11、如何關閉 Redis 服務? shutdown

image.png

12、再次檢視進程是否存在

image.png

13、後面我們多會使用單機多 Redis 啓動叢集測試!

2.4. 測試效能

redis-benchmark 是一個壓力測試工具!

官方自帶的效能測試工具

redis-benchmark 命令參數:

image.png

簡單測試:

# 測試: 100個併發連線 100000請求
redis-benchmark -h localhost -p 6379 -c 100 -n 100000

image.png

如何檢視這些分析?

image.png

2.5. 基礎知識

image.png

預設使用的是第0個

可以使用 select 來切換數據庫

127.0.0.1:6379> select 3 # 切換數據庫
OK
127.0.0.1:6379[3]> DBSIZE # 檢視DB大小!
(integer)0

image.png

127.0.0.1:6379[3] keys * # 檢視數據庫所有的key
1)"name"

清除當前數據庫 flushdb

清除全部數據庫的內容 flushAll

127.0.0.1:6379[3]> flushdb 
OK
127.0.0.1:6379[3]>keys *
(empty list or set)

Redis 是單執行緒的!

明白 Redis 是很快的,官方表示,Redis 是基於記憶體操作,CPU不是 Redis 效能瓶頸,Redis 的瓶頸是根據機器的記憶體和網路頻寬,既然可以使用單執行緒來實現,就使用單執行緒了!

Redis 是C語言寫的,官方提供的數據爲100000+的 QPS,完全不比同樣是使用 key-value 的 Memecache 差!

Redis 爲什麼單執行緒還這麼快?

1、誤區1:高效能的伺服器一定是多執行緒的?

2、誤區2:多執行緒(CPU上下文會切換!)一定比單執行緒效率高!

要對 CPU > 記憶體 > 硬碟 的速度要有所瞭解!

核心:redis 是將所有的數據全部放在記憶體中的,所以說使用單執行緒去操作效率就是最高的,多執行緒(CPU上下文會切換:耗時的操作!!!),對於記憶體系統來說,如果沒有上下文切換效率就是最高的!多次讀寫都是在一個CPU上的,在記憶體情況下,這個就是最佳的方案!

三、五大數據型別

官網文件

image.png

全段翻譯:

Redis 是一個開源(BSD許可)的,記憶體中的數據結構儲存系統,它可以用作數據庫快取訊息中介軟體MQ。 它支援多種型別的數據結構,如 字串(strings)雜湊(hashes)列表(lists)集合(sets)有序集合(sorted sets) 與範圍查詢, bitmapshyperloglogs地理空間(geospatial) 索引半徑查詢。 Redis 內建了 複製(replication)LUA指令碼(Lua scripting)LRU驅動事件(LRU eviction)事務(transactions) 和不同級別的 磁碟持久化(persistence), 並通過 Redis哨兵(Sentinel)和自動 分割區(Cluster)提供高可用性(high availability)。

3.1. Redis-key

127.0.0.1:6379> keys * # 檢視所有的key
(empty list or set)
127.0.0.1:6379> set name kuangshen # set key
OK
127.0.0.1:6379> keys *
1)"name"
127.0.0.1:6379> set age 1
oK
127.0.0.1:6379> keys *
1)"age"
2)"name"
127.0.0.1:6379> EXISTS name # 判斷當前的key是否存在
(integer)1
127.0.0.1:6379> EXISTS name1
(integer)0
127.0.0.1:6379> move name 1 # 移除當前的key
(integer)1
127.0.0.1:6379> keys *
1)"age"
127.0.0.1:6379> set name qinjiang
OK
127.0.0.1:6379> keys *
1)"age"
2)"name"
127.0.0.1:6379> get name
"qinjiang"
127.0.0.1:6379> EXPIRE name 10 # 設定key的過期時間,單位是秒
(integer)1
127.0.0.1:6379> tt1 name # 檢視當前key的剩餘時間
(integer)4
127.0.0.1:6379> tt1 name
(integer)3
127.0.0.1:6379> tt1 name
(integer)2
127.0.0.1:6379> ttl name
(integer)1
127.0.0.1:6379> ttl name
(integer)-2
127.0.0.1:6379> get name
(nil)
127.0.0.1:6379> type age # 檢視當前key的型別
string

# 判斷是否存在
exists key
# 移除某 key
move key
# 計時器倒計時移除某key 
expire key second
# 檢視當前key的剩餘時間
ttl key
# 檢視當前key的型別
type key
# 
append

strlen

後面如果遇到不會或者不認識的命令,可以直接在官網檢視幫助文件

image.png

3.2. String(字串)

###############################################################
127.0.0.1:6379> get key1 # 獲得值
"VI"
127.0.0.1:6379> keys * # 獲得所有的key
1)"key1"
127.0.0.1:6379> EXISTS key1 # 判斷某一個key是否存在
(integer)1
127.0.0.1:6379> APPEND key1 "he11o" # 追加字串,如果當前key不存在,就相當於setkey
(integer)7
127.0.0.1:6379> get key1
"v1he11o"
127.0.0.1:6379> STRLEN key1 # 獲取字串的長度!
(integer)7
127.0.0.1:6379> APPEND key1 ",kaungshen"
(integer)17
127.0.0.1:6379> strlen key1
(integer)17
127.0.0.1:6379> get key1
"v1he11o,kaungshen"

###############################################################
# i++  incr key 相當於i++   decr key 相當於i--
# 步長 i+=  incrby key length   incrby key length 相當於i+=length
127.0.0.1:6379> set views 0  # 初始瀏覽量爲0
OK
127.0.0.1:6379> get views
"O"
127.0.0.1:6379> incr views  # 自增1 瀏覽量變爲1
(integer)1
127.0.0.1:6379> incr views
(integer)2
127.0.0.1:6379> get views
"2"
127.0.0.1:6379> decr views  # 自減1 瀏覽量變爲0
(integer)1
127.0.0.1:6379> decr views
(integer)0
127.0.0.1:6379> decr views
(integer)-1
127.0.0.1:6379> get views
"-1"
127.0.0.1:6379> INCRBY views 10 # 可以設定步長,指定增量
(integer) 9
127.0.0.1:6379> INCRBY views 10
(integer) 19
127.0.0.1:6379> DECRBY views 5
(integer) 14

###############################################################
# 字串範圍 range
127.0.0.1:6379> set key1 "he1lo,kuangshen" # 設定key1的值
oK
127.0.0.1:6379> get key1
"he11o,kuangshen"
127.0.0.1:6379> GETRANGE key1 0 3 # 擷取字串[0,3]
"he11"
127.0.0.1:6379> GETRANGE key10 -1 # 獲取全部的字串和get key是一樣的
"he11o,kuangshen"

# 替換 
127.0.0.1:6379> set key2 abcdefg 
OK
127.0.0.1:6379> get key2
"abcdefg"
127.0.0.1:6379> SETRANGE key2 1 xx # 替換指定位置開始的字串
(integer)7
127.0.0.1:6379> get key2
"axxdefg"

##############################################################
# setex (set with expire) # 設定過期時間
# setnx (set if not exist) # 不存在再設定(在分佈式鎖中會常常使用)

127.0.0.1:6379> setex key3 30 "hello" # 設定key3的值爲he11o,30秒後過期
0K
127.0.0.1:6379> ttl key3
(integer)26
127.0.0.1:6379> get key3
"hello"
127.0.0.1:6379> setnx mykey "redis" # 如果mykey不存在,建立mykey
(integer)1
127.0.0.1:6379> keys *
1)"key2"
2)"mykey"
3)"keyl"
127.0.0.1:6379>ttl key3
(integer)-2
127.0.0.1:6379>setnx mykey "MongoDB" # 如果mykey存在,建立失敗!
(integer)0
127.0.0.1:6379> get mykey
"redis"

###############################################################
# 批次設定或獲取 mset mget 
127.0.0.1:6379> mset kl v1 k2 v2 k3 v3 # 同時設定多個值
OK
127.0.0.1:6379> keys *
1)"k1"
2)"k2"
3)"k3"
127.0.0.1:6379> mget k1 k2 k3 # 同時獲取多個值
1)"v1"
2)"v2"
3)"v3"
127.0.0.1:6379> msetnx k1 v1 k4 v4 # msetnx是一個原子性的操作,要麼一起成功,要麼一起失敗!
(integer)0
127.0.0.1:6379> get k4
(nil)

# 物件
set user:1{name:zhangsan,age:3} # 設定一個user:1物件值爲json字元來儲存一個物件!
# 這裏的key是一個巧妙的設計:user:{id}:{filed},如此設計在Redis中是完全ok了!
127.0.0.1:6379>mset user:1:name zhangsan user:1:age 2
oK
127.0.0.1:6379>mget user:1:name user:1:age
1)"zhangsan"
2)"2"

#############################################################
getset # 先get然後在set

127.0.0.1:6379> getset db redis # 如果不存在值,則返回nil
(nil)
127.0.0.1:6379> get db
"redis
127.0.0.1:6379> getset db mongodb # 如果存在值,獲取原來的值,並設定新的值
"redis"
127.0.0.1:6379> get db
"mongodb"

String類似的使用場景:value除了是我們的字串還可以是我們的數位!

  • 計數器
  • 統計多單位的數量
  • 粉絲數
  • 物件快取儲存

3.3. List(列表)

基本的數據型別,列表

image.png

在 Redis 中,我們可以把 list 玩成 棧、佇列、阻塞佇列!

所有的 list 命令都是 l 開頭的

##############################################################
127.0.0.1:6379> LPUSH list one # 將一個值或者多個值,插入到列表頭部(左)
(integer)1
127.0.0.1:6379> LPUSH list two
(integer)2
127.0.0.1:6379> LPUSH list three
(integer)3
127.0.0.1:6379> LRANGE list 0 -1 # 獲取list中的值
1)"three"
2)"two"
3)"one"
127.0.0.1:6379> LRANGE list 0 1 # 通過去間獲取具體的值
1)"three"
2)"two"
127.0.0.1:6379> Rpush list right #將一個值或者多個值,插入到列表位部(右)
(integer)4
127.0.0.1:6379> LRANGE list 0 -1
1)"three"
2)"two"
3)"one"
4)"right"

###############################################################
LPOP 左邊移除
RPOP 右邊移除

127.0.0.1:6379> LRANGE list 0 -1
1)"three"
2)"two"
3)"one"
4)"righr"
127.0.0.1:6379> Lpop 1ist # 移除1ist的第一個元素
"three"
127.0.0.1:6379> Rpop list # 移除1ist的最後一個元素
"righr"
127.0.0.1:6379>LRANGE list 0 -1
1)"two"
2)"one"

###############################################################
LIndex

127.0.0.1:6379>LRANGE list 0 -1
1)"two"
2)"one"
127.0.0.1:6379>1index list 1 # 通過下標獲得list中的某一個值!
"one"
127.0.0.1:6379>lindex list 0
"two"
###############################################################
Llen

127.0.0.1:6379> Lpush list one
(integer)1
127.0.0.1:6379> Lpush list two
(integer)2
127.0.0.1:6379> Lpush list three
(integer)3
127.0.0.1:6379> Llen 1ist # 返回列表的長度
(integer)3
##############################################################
Lrem  移除指定的id

127.0.0.1:6379> LRANGE list 0 -1
1)"three"
2)"three"
3)"two"
4)"one"
127.0.0.1:6379> Lrem list 1 one # 移除list集閤中指定個數的value,精確匹配
(integer)1
127.0.0.1:6379> LRANGE list 0 -1
1)"three"
2)"three"
3)"two"
127.0.0.1:6379> Lrem list 1 three
(integer)1
127.0.0.1:6379> LRANGE list 0 -1
1)"three"
2)"two"
127.0.0.1:6379> Lpush list three
(integer)3
127.0.0.1:6379> Lrem list 2 three
(integer)2
127.0.0.1:6379> LRANGE list 0 -1
1)"two"

##############################################################
Ltrim 修剪 

127.0.0.1:6379> keys *
(empty list or set)
127.0.0.1:6379> Rpush mylist "hello"
(integer)1
127.0.0.1:6379> Rpush mylist "hello1"
(integer)2
127.0.0.1:6379> Rpush mylist "hello2"
(integer)3
127.0.0.1:6379> Rpush mylist "hello3"
(integer)4
127.0.0.1:6379> Ltrim mylist 1 2 # 通過下標擷取指定的長度,這個1ist已經被改變了,截斷了只剩下擷取的元素!
127.0.0.1:6379> LRANGE mylist 0 -1
1)"hello1"
2)"hello2"

##############################################################
rpoplpush # 移除列表的最後一個元素,將他移動到新的列表中!

127.0.0.1:6379> rpush mylist "hel1o"
(integer)1
127.0.0.1:6379> rpush mylist "hello1"
(integer)2
127.0.0.1:6379> rpush mylist "hello2"
(integer)3
127.0.0.1:6379> rpoplpush mylist myotherlist #移除列表的最後一個元素,將他移動到新的列表中!
"he11o2"
127.0.0.1:6379> Lrange mylist 0 -1 # 檢視原本的列表
1)"hello"
2)"hello1"
127.0.0.1:6379> Lrange myotherlist 0 -1 #檢視目標列表中,確實存在改值!
1)"hello2"

##############################################################
Lset # 將列表中指定下標的值替換爲另外一個值,更新操作

127.0.0.1:6379> EXISTS 1ist # 判斷這個列表是否存在
(integer)0
127.0.0.1:6379> Lset list 0 item # 如果不存在列表我們去更新就會報錯
(error)ERR no such key
127.0.0.1:6379> Lpush list value1
(integer)1
127.0.0.1:6379> LRANGE list 0 0
1)"value1"
127.0.0.1:6379> 1set list 0 item # 如果存在,更新當前下標的值
OK
127.0.0.1:6379> LRANGE list 0 0
1)"item"
127.0.0.1:6379> Lset list 1 other # 如果不存在,則會報錯!|
(error)ERR index out of range

##############################################################
Linsert # 將某個具體的value插入到列表中某個元素的前面或者後面!

127.0.0.1:6379> Rpush mylist "hello"
(integer)1
127.0.0.1:6379> Rpush mylist "world"
(integer)2
127.0.0.1:6379> LINSERT mylist before "world" "other"
(integer)3
127.0.0.1:6379> LRANGE mylist0-1
1)"hello"
2)"other"
3)"world"
127.0.0.1:6379> LINSERT mylist after world new
(integer)4
127.0.0.1:6379> LRANGE mylist 0 -1
1)"hello"
2)"other"
3)"world"
4)"new"

小結

  • 他實際上是一個鏈表,before Node after,left,right都可以插入值
  • 如果key不存在,建立新的鏈表
  • 如果key存在,新增內容
  • 如果移除了所有值,空鏈表,也代表不存在!
  • 在兩邊插入或者改動值,效率最高!中間元素,相對來說效率會低一點

使用場景

  • 訊息排隊
  • 訊息佇列(Lpush,Rpop)
  • 棧(Lpush,Lpop)

3.4. Set(集合)

set中的值是不能重複的!

所有的 set 命令開頭都是 s

##############################################################
sadd 新增元素  、 smembers 檢視指定set的所有值

127.0.0.1:6379> sadd myset "he1lo" # 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)"1ovekuangshen"
3)"kuangshen"
127.0.0.1:6379> SISMEMBER myset he1lo # 判斷某一個值是不是在set集閤中!
(integer)1
127.0.0.1:6379> SIsMember myset world
(integer)0

##############################################################
scard 獲取set集閤中的元素個數

127.0.0.1:6379> scard myset # 獲取set集閤中的內容元素個數!
(integer)4

##############################################################
srem 移除 set 集閤中的指定元素 

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)"1ovekuangshen'"
3)"kuangshen"

##############################################################
set 無序不重複集合,抽隨機 SRandMember

127.0.0.1:6379> SMEMBERS myset
1)"lovekuangshen2"
2)"1ovekuangshen"
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)"1ovekuangshen2"
127.0.0.1:6379> SRANDMEMBER myset 2
1)"lovekuangshen"
2)"lovekuangshen2"
127.0.0.1:6379> SRANDMEMBER myset # 隨機抽選出一個元素
"1ovekuangshen2"

##############################################################
刪除指定的key,隨機刪除

127.0.0.1:6379> SMEMBERS myset
1)"lovekuangshen2"
2)"lovekuangshen"
3)"kuangshen"
127.0.0.1:6379> spop myset # 隨機刪除一些set集閤中的元素
"1ovekuangshen2"
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"

##############################################################
微博,B站,共同關注!(並集)
數位集合類:
 - 差集 SDIFF
 - 交集 SINTER
 - 並集 SUNION

127.0.0.1:6379> SDIFF keyl key2 # 差集
1)"b"
2)"a"
127.0.0.1:6379> SINTER key1 key2 # 交集共同好友就可以這樣實現
1)"c"
127.0.0.1:6379> SUNION key1 key2 # 並集
1)"b"
2)"c"
3)"e"
4)"a"
5)"d"

使用場景

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

3.5. Hash(雜湊)

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

Redis 中 Hash 的語法是前面多加 h

###############################################################
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 fieldl hello field2 world # set多個key-vlaue
127.0.0.1:6379> hmget myhash fieldl 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 獲取hash表的長度

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

###############################################################
hexists  判斷hash中指定欄位是否存在
hsetnx 判斷hash中指定欄位是否存在,如果存在則不做操作;如果不存在則新增此欄位

127.0.0.1:6379> HEXISTS myhash field1 # 判斷hash中指定欄位是否存在!
(integer)1
127.0.0.1:6379> HEXISTS myhash field3
(integer)0
127.0.0.1:6379> hsetnx myhash field4 hello # 如果不存在則設定
(integer)1
127.0.0.1:6379> hsetnx myhash field4 world # 如果存在則不設定
(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> hset user name zhangsan
(integer) 1
127.0.0.1:6379> hset user age 20
(integer) 1
127.0.0.1:6379> hset user height 190
(integer) 1
127.0.0.1:6379> hgetall user
1) "name"
2) "zhangsan"
3) "age"
4) "20"
5) "height"
6) "190"

###############################################################

使用場景

  • hash變更的數據 user name age,尤其是是使用者資訊之類的,經常變動的資訊!hash更適合於物件的儲存,String更加適合字串儲存!

3.6. Zset(有序集合)

在set的基礎上,增加了一個值,set k1 v1 有序集合:zset k1 score1 v1

在 Redis 中 Zset 數據型別語法的開頭爲 z

127.0.0.1:6379>zadd myset 1 one # 新增一個值
(integer)1
127.0.0.1:6379>zadd myset 2 two 3 three # 新增多個值
(integer)2
127.0.0.1:6379>ZRANGE myset 0 -1
1)"one"
2)"twol"
3)"three"
################################################################
排序如何實現 
zRangeByScoure key min max  # 正序
zRevrange key 0 -1  # 倒序


127.0.0.1:6379> zadd salary 2500 xiaohong # 新增三個使用者
(integer)1
127.0.0.1:6379> zadd salary 5000 zhangsan
(integer)1
127.0.0.1:6379> zadd salary 500 kaungshen
(integer)1
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf # 顯示全部的使用者從小到大!
1)"kaungshen"
2)"xiaohong"
3)"zhangsan"
127.0.0.1:6379> zRevrange salary 0 -1 # 從大到小進行排序
1)"zhangsan"
2)"xiaohong"
3)"kaungshen"
127.0.0.1:6379>ZRANGEBYSCORE salary -inf +inf withscores # 顯示全部的使用者並且附帶成績
1)"kaungshen"
2)"500"
3)"xiaohong"
4)"2500"
5)"zhangsan"
6)"5000"
127.0.0.1:6379>ZRANGEBYSCORE salary-inf 2500 withscores # 顯示工資小於2500員工的升序排序!
1)"kaungshen"
2)"500"
3)"xiaohong"
4)"2500"

##############################################################
zrem 移除元素 

127.0.0.1:6379> zrange salary 0 -1
1)"kaungshen"
2)"xiaohong"
3)"zhangsan"
127.0.0.1:6379> zrem salary xiaohong # 移除有序集閤中的元素
(integer)1
127.0.0.1:6379> zrange salary 0 -1
1)"kaungshen"
2)"zhangsan"
127.0.0.1:6379> zcard salary # 獲取有序集閤中的個數
(integer)2

##############################################################
獲取指定區間的成員數量

127.0.0.1:6379> zadd myset 1 hello
(integer)1
127.0.0.1:6379> zadd myset 2 world 3 kuangshen
(integer)2
127.0.0.1:6379> zcount myset 1 3 # 獲取指定區間的成員數量!
(integer)3
127.0.0.1:6379> zcount myset 1 2
(integer)2

其餘的一些 API 可以從官網中檢視文件進行學習

使用場景

  • set 排序 儲存班級成績表,工資表排序
  • 普通訊息 1 ,重要訊息 2 ,帶權重進行判斷!
  • 排行榜應用實現, 取 Top N 測試

四、三種特殊數據型別

4.1. geospatial 地理位置

朋友的定位,附近的人,打車距離計算?

Redis 的 Geo 在 Redis3.2 版本就推出了!這個功能可以推算地理位置的資訊,兩地之間的距離,方圓幾裡的人!

可以查詢一些測試數據: http://www.jsons.cn/lngcode/

只有六個命令

image.png

官方文件: https://www.redis.net.cn/order/3685.html

geoadd

# geoadd 新增地理位置
# 規則:兩級無法直接新增,我們一般會下載城市數據,直接通過Java程式一次性匯入
# 有效的經度從-180度到180度。
# 有效的緯度從-85.05112878度到85.05112878度。
# 當座標位置超出上述指定範圍時,該命令將會返回一個錯誤。
# 127.0.0.1:6379>geoadd china:city 39.90116.40 beijin
#(error)ERR invalid longitude,latitude pair 39.900000,116.400000
# 參數 key 值(緯度、經度、名稱)
127.0.0.1:6379> geoadd china:city 116.40 39.90 beijing
(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 106.50 29.53 chongqi 114.05 22.52 shenzhen
(integer)2
127.0.0.1:6379> geoadd china:city 120.16 30.24 hangzhou 108.96 34.26 xianmen
(integer)2
# 

geopos

獲取當前定位:一定是一個座標值!

127.0.0.1:6379> GEOPOS china:city beijing #獲取指定的城市的經度和緯度!
1)1)"116.39999896287918091"
2)"39.90000009167092543"
127.0.0.1:6379> GEoPoS china:city beijing chongqi
1)1)"116.39999896287918091"
2)"39.90000009167092543"
2)1)"106.49999767541885376"
2)"29.52999957900659211"

geodist

兩人之間的距離!

單位:

  • m 表示單位爲米。
  • km 表示單位爲千米。
  • mi 表示單位爲英裡。
  • ft 表示單位爲英尺。
 127.0.0.1:6379> GEoDIST china:city beijing shanghai km # 檢視上海到北京的直線距離
"1067.3788"
127.0.0.1:6379> GEoDIST china:city beijing chongqi km # 檢視重慶到北京的直線距離
"1464.0708"

georadius 以給定的經緯度爲重心,找出某一半徑內的元素

我附近的人?(獲取所有附近的人的地址,定位!)通過半徑來查詢

獲得指定數量的人,200

所有數據應該都錄入:china:city,纔會讓結果更加準確!

127.0.0.1:6379> GEORADIUS china: city 110 301000 km # 以 110,30 這個經緯度爲中心,尋找城市1000km內的城市
1)"chongqi"
2)"xian"
3)"shengzhen"
4)"hangzhou"
127.0.0.1:6379> GEORADIUS china: city 110 30 500 km
1)"chongqi"
2)"xian"
127.0.0.1:6379> GEORADIUS china: city 110 30 500 km withdist # 顯示到中間距離的位置
1)1)"chongqi"
  2)"341.9374"
2)1)"xian"
  2)"483.8340"
127.0.0.1:6379> GEORADIUS china: city 110 30 500 km withcoord # 顯示他人的定位資訊
1)1)"chongqi"
  2)1)"106.49999767541885376"
    2)"29.52999957900659211"
2)1)"xian"
  2)1)"108.96000176668167114"
    2)"34.25999964418929977"
127.0.0.1:6379> GEORADIUS china: city 110 30 500 km withdist withcoord count 1  # 篩選出指定的結果!
1)1)"chongqi"
  2)"341.9374"
  3)1)"106.49999767541885376"
    2)"29.52999957900659211"
127.0.0.1:6379> GEORADIUS china: city 110 30 500 km withdist withcoord count 2
1)1)"chongqi"
  2)"341.9374"
  3)1)"106.49999767541885376"
    2)"29.52999957900659211"
2)1)"xian"
  2)"483.8340"
  3)1)"108.96000176668167114"
    2)"34.25999964418929977"

geoRadiusByMember

# 找出位於指定元素周圍的其他元素
127.0.0.1:6379> GEORADIUSBYMEMBER china: city beijing 1000 km
1)"beijing"
2)"xian"
127.0.0.1:6379> GEORADIUSBYMEMBER china: city shanghai 400 km
1)"hangzhou"
2)"shanghai"

geoHash 返回一個或多個位置元素的 Geohash 表示

該命令將返回11個字元的Geohash字串!

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

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

127.0.0.1:6379> ZRANGE china: city 0 -1 # 檢視地圖中全部的元素
1)"chongqi"
2)"xian"
3)"shengzhen"
4)"hangzhou"
5)"shanghai"
6)"beijing"
127.0.0.1:6379> zrem china: city beijing # 移除指定元素
(integer)1
127.0.0.1:6379> ZRANGE china: city 0 -1
1)"chongqi"
2)"xian"
3)"shengzhen"
4)"hangzhou"
5)"shanghai"

4.2. Hyperloglog

什麼是基數?

A{1,3,5,7,8,7}
B(1,3,5,7,8}
基數(不重複的元素)=5,可以接受誤差!

簡介

Redis 2.8.9版本就更新了Hyperloglog數據結構!

Redis Hyperloglog 基數統計的演算法!

網頁的UV(一個人存取一個網站多次,但是還是算作一個人!)

  • 傳統的方式,set 儲存使用者的id,然後就可以統計 set 中的元素數量作爲標準判斷!(因爲 set 中的元素不會出現重複,所以得出的存取量是準確的)
    • 缺點:這個方式如果儲存大量的使用者id,就會比較麻煩!我們的目的是爲了計數,而不是儲存使用者id;
  • Hyperloglog
    • 優點:佔用的記憶體是固定,2^64 不同的元素的基數,只需要使用12KB記憶體!如果要從記憶體角度來比較,Hyperloglog 首選!

測試使用

Redis 的 Hyperloglog:

  • 新增元素( pfadd key element [element...]
  • 統計某元素的基數數量 ( pfcount key [key...]
  • 合併兩組元素取兩組元素的並集( pfMerge destkey sourcekey [sourcekey...]
127.0.0.1:6379> PFadd mykey a b c d e f g h i j # 建立第一組元素 mykey
(integer)1
127.0.0.1:6379> PFCOUNT mykey # 統計 mykey 元素的基數數量
(integer)10
127.0.0.1:6379> PFadd mykey2 i j z x c v b n m  # 建立第二組元素 mykey2
(integer)1
127.0.0.1:6379> PFCOUNT mykey2 # 統計 mykey2 元素的基數數量
(integer)9
127.0.0.1:6379> PFmerge mykey3 mykey mykey2 # 合併兩組 mykey ∪ mykey2 => mykey3 
OK
127.0.0.1:6379> pfcount mykey3 # 看並集的數量
(integer)15

如果允許容錯,那麼一定可以使用 Hyperloglog !

如果不允許容錯,就使用 set 或者自己的數據型別即可!

4.3. Bitmaps

位儲存

統計疫情感染人數:0 1 0 1

統計使用者資訊,活躍,不活躍!登錄、未登錄!打卡,365天打卡!兩個狀態的業務都可以使用 Bitmaps!

Bitmaps 點陣圖,數據結構!都是操作二進制位來進行記錄,就只有0和1兩個狀態!
365天=365bit 1位元組=8bit 46個位元組左右!

測試

Redis 的 Bitmaps:

  • 新增元素和狀態( setbit key offset value
  • 獲取元素某下標的狀態 ( getbit key offset
  • 統計某元素狀態爲 1 的數量 ( bitcount key [start end]

image.png

使用 bitmap 來記錄 週一到週日的打卡!

週一:1週二:0週三:0週四:1……

image.png

檢視某一天是否有打卡!

127.0.0.1:6379>getbit sign 3 # 獲取某一天的打卡情況
(integer)1
127.0.0.1:6379>getbit sign 6
(integer)0

統計打卡的天數

127.0.0.1:6379>bitcount sign # 統計這周的打卡天數,就可以看到是否有全勤
(integer)3

五、事務

Redis 事務本質:一組命令的集合!一個事務中的所有命令都會被序列化,在事務執行過程的中,會按照順序執行!

一次性、順序性、排他性!執行一些列的命令!

Redis事務沒有沒有隔離級別的概念!

所有的命令在事務中,並沒有直接被執行!只有發起執行命令的時候纔會執行!exec

Redis單條命令式儲存原子性的,但是事務不保證原子性!

Redis 的事務:

  • 開啓事務( multi
  • 命令入隊( ...
  • 執行事務( exec

正常制行事務!

127.0.0.1:6379> multi # 開啓事務
OK
# 命令入隊
127.0.0.1:6379> set kl v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> get k2
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> exec # 執行事務
1) oK
2) oK
3)"v2"
4) OK

放棄事務!

127.0.0.1:6379> multi # 開啓事務
OK
127.0.0.1:6379> set kl v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> set k4 v4
QUEUED
127.0.0.1:6379> DISCARD # 取消事務
OK
127.0.0.1:6379> get k4 # 事務佇列中的命令都不會被執行!
(nil)

編譯型異常(程式碼有問題!命令有錯!),事務中所有的命令都不會被執行

127.0.0.1:6379> multi
OK
127.0.0.1:6379> set kl 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> getset k3 # 錯誤的命令
(error) ERR wrong number of arguments for ' getset' command
127.0.0.1:6379> set k4 v4
QUEUED
127.0.0.1:6379> set k5 v5
QUEUED
127.0.0.1:6379> exec # 執行事務報錯!
(error) EXECABoRT Transaction discarded because of previous errors.
127.0.0.1:6379> get k5 # 事務中所有的命令都不會被執行!
(nil)

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

127.0.0.1:6379> set k1 "v1"
OK
127.0.0.1:6379> multi
OK
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> get k3
QUEUED
127.0.0.1:6379> exec
1)(error) ERR value is not an integer or out of range # 雖然第一條命令報錯了,但是依舊正常執行成功了!
2) oK
3) oK
4)"v3"
127.0.0.1:6379> get k2
"v2"
127.0.0.1:6379> get k3
"v3"

監控!Watch(面試常問!)

悲觀鎖:

  • ·悲觀,認爲什麼時候都會出問題,無論做什麼都會加鎖!

樂觀鎖:

  • 很樂觀,認爲什麼時候都不會出問題,所以不會上鎖!更新數據的時候去判斷一下,在此期間是否有人修改過這個數據(如果被修改了數據,更新數據失敗)
  • 獲取 version
  • 更新的時候比較 version

Redis 監控測試

127.0.0.1:6379> set money 100
OK
127.0.0.1:6379> set out 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 20
QUEUED
127.0.0.1:6379> INCRBY out 20
QUEUED
127.0.0.1:6379> exec
1)(integer)80
2)(integer)20

測試多執行緒(事務啓動後另一邊修改值),使用 watch 可以當做 Redis 樂觀鎖操作!

127.0.0.1:6379> watch money # 監視 Money
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> DECRBY money 10
QUEUED
127.0.0.1:6379> INCRBY out 10
QUEUED
127.0.0.1:6379> exec # 執行之前另外一個執行緒修改了我們的值,這個時候,就會導致事務執行失敗!
(nil)

如果修改失敗,重新獲取值即可!

image.png

樂觀鎖實現業務:

  • 秒殺業務系統

六、Jedis

我們要使用 Java 來操作 Redis ,那麼可以使用 Jedis

什麼是 Jedis?是 Redis 官方推薦的 Java 連線開發工具! 使用 Java 操作 Redis 中介軟體!如果你要使用 Java 操作 Redis,那麼一定要對 Jedis 十分熟悉!

其他語言:官網中有推薦不同語言對應的Redis操作包

1、匯入對應的依賴

<dependencies>
  <!-- jedis -->
  <dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>3.2.0</version>
  </dependency>
	<!-- fastjson -->
  <dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.62</version>
  </dependency>
</dependencies>

2、編碼測試:

  • 連線數據庫
  • 操作命令
  • 斷開連線
import redis.clients.jedis.Jedis;

public class TestPing {
  public static void main(String[] args) {
    // 1、new Jedis 物件即可
    Jedis jedis = new Jedis("127.0.0.1", 6379);
    // Jedis 所有的命令就是我們之前學習的所有指令!
    System.out.println(jedis.ping());
  }
}

輸出

image.png

6.1. 常用的API

對 String 的操作命令

public class TestString {
  public static void main(String[] args) {
    Jedis jedis = new Jedis("127.0.0.1", 6379);
    jedis.flushDB();
    System.out.println("===========增加數據===========");
    System.out.println(jedis.set("key1","value1"));
    System.out.println(jedis.set("key2","value2"));
    System.out.println(jedis.set("key3", "value3"));
    System.out.println("刪除鍵key2:"+jedis.del("key2"));
    System.out.println("獲取鍵key2:"+jedis.get("key2"));
    System.out.println("修改key1:"+jedis.set("key1", "value1Changed"));
    System.out.println("獲取key1的值:"+jedis.get("key1"));
    System.out.println("在key3後面加入值:"+jedis.append("key3", "End"));
    System.out.println("key3的值:"+jedis.get("key3"));
    System.out.println("增加多個鍵值對:"+jedis.mset("key01","value01","key02","value02","key03","value03"));
    System.out.println("獲取多個鍵值對:"+jedis.mget("key01","key02","key03"));
    System.out.println("獲取多個鍵值對:"+jedis.mget("key01","key02","key03","key04"));
    System.out.println("刪除多個鍵值對:"+jedis.del("key01","key02"));
    System.out.println("獲取多個鍵值對:"+jedis.mget("key01","key02","key03"));

    jedis.flushDB();
    System.out.println("===========新增鍵值對防止覆蓋原先值==============");
    System.out.println(jedis.setnx("key1", "value1"));
    System.out.println(jedis.setnx("key2", "value2"));
    System.out.println(jedis.setnx("key2", "value2-new"));
    System.out.println(jedis.get("key1"));
    System.out.println(jedis.get("key2"));

    System.out.println("===========新增鍵值對並設定有效時間=============");
    System.out.println(jedis.setex("key3", 2, "value3"));
    System.out.println(jedis.get("key3"));
    try {
      TimeUnit.SECONDS.sleep(3);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    System.out.println(jedis.get("key3"));

    System.out.println("===========獲取原值,更新爲新值==========");
    System.out.println(jedis.getSet("key2", "key2GetSet"));
    System.out.println(jedis.get("key2"));

    System.out.println("獲得key2的值的字串:"+jedis.getrange("key2", 2, 4));
  }
}

對 List 的操作命令

public class TestList {
  public static void main(String[] args) {
    Jedis jedis = new Jedis("127.0.0.1", 6379);
    jedis.flushDB();
    System.out.println("===========新增一個list===========");
    jedis.lpush("collections", "ArrayList", "Vector", "Stack", "HashMap", "WeakHashMap", "LinkedHashMap");
    jedis.lpush("collections", "HashSet");
    jedis.lpush("collections", "TreeSet");
    jedis.lpush("collections", "TreeMap");
    System.out.println("collections的內容:" + jedis.lrange("collections", 0, -1));//-1代表倒數第一個元素,-2代表倒數第二個元素,end爲-1表示查詢全部
    System.out.println("collections區間0-3的元素:" + jedis.lrange("collections", 0, 3));
    System.out.println("===============================");
    // 刪除列表指定的值 ,第二個參數爲刪除的個數(有重複時),後add進去的值先被刪,類似於出棧
    System.out.println("刪除指定元素個數:" + jedis.lrem("collections", 2, "HashMap"));
    System.out.println("collections的內容:" + jedis.lrange("collections", 0, -1));
    System.out.println("刪除下表0-3區間之外的元素:" + jedis.ltrim("collections", 0, 3));
    System.out.println("collections的內容:" + jedis.lrange("collections", 0, -1));
    System.out.println("collections列表出棧(左端):" + jedis.lpop("collections"));
    System.out.println("collections的內容:" + jedis.lrange("collections", 0, -1));
    System.out.println("collections新增元素,從列表右端,與lpush相對應:" + jedis.rpush("collections", "EnumMap"));
    System.out.println("collections的內容:" + jedis.lrange("collections", 0, -1));
    System.out.println("collections列表出棧(右端):" + jedis.rpop("collections"));
    System.out.println("collections的內容:" + jedis.lrange("collections", 0, -1));
    System.out.println("修改collections指定下標1的內容:" + jedis.lset("collections", 1, "LinkedArrayList"));
    System.out.println("collections的內容:" + jedis.lrange("collections", 0, -1));
    System.out.println("===============================");
    System.out.println("collections的長度:" + jedis.llen("collections"));
    System.out.println("獲取collections下標爲2的元素:" + jedis.lindex("collections", 2));
    System.out.println("===============================");
    jedis.lpush("sortedList", "3", "6", "2", "0", "7", "4");
    System.out.println("sortedList排序前:" + jedis.lrange("sortedList", 0, -1));
    System.out.println(jedis.sort("sortedList"));
    System.out.println("sortedList排序後:" + jedis.lrange("sortedList", 0, -1));
  }
}

對 Set 的操作命令

public class TestSet {
  public static void main(String[] args) {
    Jedis jedis = new Jedis("127.0.0.1", 6379);
    jedis.flushDB();
    System.out.println("============向集閤中新增元素(不重複)============");
    System.out.println(jedis.sadd("eleSet", "e1", "e2", "e4", "e3", "e0", "e8", "e7", "e5"));
    System.out.println(jedis.sadd("eleSet", "e6"));
    System.out.println(jedis.sadd("eleSet", "e6"));
    System.out.println("eleSet的所有元素爲:" + jedis.smembers("eleSet"));
    System.out.println("刪除一個元素e0:" + jedis.srem("eleSet", "e0"));
    System.out.println("eleSet的所有元素爲:" + jedis.smembers("eleSet"));
    System.out.println("刪除兩個元素e7和e6:" + jedis.srem("eleSet", "e7", "e6"));
    System.out.println("eleSet的所有元素爲:" + jedis.smembers("eleSet"));
    System.out.println("隨機的移除集閤中的一個元素:" + jedis.spop("eleSet"));
    System.out.println("隨機的移除集閤中的一個元素:" + jedis.spop("eleSet"));
    System.out.println("eleSet的所有元素爲:" + jedis.smembers("eleSet"));
    System.out.println("eleSet中包含元素的個數:" + jedis.scard("eleSet"));
    System.out.println("e3是否在eleSet中:" + jedis.sismember("eleSet", "e3"));
    System.out.println("e1是否在eleSet中:" + jedis.sismember("eleSet", "e1"));
    System.out.println("e1是否在eleSet中:" + jedis.sismember("eleSet", "e5"));
    System.out.println("=================================");
    System.out.println(jedis.sadd("eleSet1", "e1", "e2", "e4", "e3", "e0", "e8", "e7", "e5"));
    System.out.println(jedis.sadd("eleSet2", "e1", "e2", "e4", "e3", "e0", "e8"));
    System.out.println("將eleSet1中刪除e1並存入eleSet3中:" + jedis.smove("eleSet1", "eleSet3", "e1"));//移到集合元素
    System.out.println("將eleSet1中刪除e2並存入eleSet3中:" + jedis.smove("eleSet1", "eleSet3", "e2"));
    System.out.println("eleSet1中的元素:" + jedis.smembers("eleSet1"));
    System.out.println("eleSet3中的元素:" + jedis.smembers("eleSet3"));
    System.out.println("============集合運算=================");
    System.out.println("eleSet1中的元素:" + jedis.smembers("eleSet1"));
    System.out.println("eleSet2中的元素:" + jedis.smembers("eleSet2"));
    System.out.println("eleSet1和eleSet2的交集:" + jedis.sinter("eleSet1", "eleSet2"));
    System.out.println("eleSet1和eleSet2的並集:" + jedis.sunion("eleSet1", "eleSet2"));
    System.out.println("eleSet1和eleSet2的差集:" + jedis.sdiff("eleSet1", "eleSet2"));//eleSet1中有,eleSet2中沒有
    jedis.sinterstore("eleSet4", "eleSet1", "eleSet2");//求交集並將交集儲存到dstkey的集合
    System.out.println("eleSet4中的元素:" + jedis.smembers("eleSet4"));
  }
}

對 Hash 的操作命令

public class TestHash {
  public static void main(String[] args) {
    Jedis jedis = new Jedis("127.0.0.1", 6379);
    jedis.flushDB();
    Map<String, String> map = new HashMap<String, String>();
    map.put("key1", "value1");
    map.put("key2", "value2");
    map.put("key3", "value3");
    map.put("key4", "value4");
    //新增名稱爲hash(key)的hash元素
    jedis.hmset("hash", map);
    //向名稱爲hash的hash中新增key爲key5,value爲value5元素
    jedis.hset("hash", "key5", "value5");
    System.out.println("雜湊hash的所有鍵值對爲:" + jedis.hgetAll("hash"));//return Map<String,String>
    System.out.println("雜湊hash的所有鍵爲:" + jedis.hkeys("hash"));//return Set<String>
    System.out.println("雜湊hash的所有值爲:" + jedis.hvals("hash"));//return List<String>
    System.out.println("將key6儲存的值加上一個整數,如果key6不存在則新增key6:" + jedis.hincrBy("hash", "key6", 6));
    System.out.println("雜湊hash的所有鍵值對爲:" + jedis.hgetAll("hash"));
    System.out.println("將key6儲存的值加上一個整數,如果key6不存在則新增key6:" + jedis.hincrBy("hash", "key6", 3));
    System.out.println("雜湊hash的所有鍵值對爲:" + jedis.hgetAll("hash"));
    System.out.println("刪除一個或者多個鍵值對:" + jedis.hdel("hash", "key2"));
    System.out.println("雜湊hash的所有鍵值對爲:" + jedis.hgetAll("hash"));
    System.out.println("雜湊hash中鍵值對的個數:" + jedis.hlen("hash"));
    System.out.println("判斷hash中是否存在key2:" + jedis.hexists("hash", "key2"));
    System.out.println("判斷hash中是否存在key3:" + jedis.hexists("hash", "key3"));
    System.out.println("獲取hash中的值:" + jedis.hmget("hash", "key3"));
    System.out.println("獲取hash中的值:" + jedis.hmget("hash", "key3", "key4"));
  }
}

對於事務的基本使用

public class TestTX {
  public static void main(String[] args) {
    Jedis jedis = new Jedis();
    JSONObject jsonObject = new JSONObject();
    jsonObject.put("hello", "world");
    jsonObject.put("name", "ChangingTao");
    String result = jsonObject.toJSONString();
    // 開啓事務
    Transaction multi = jedis.multi();
    try {
      multi.set("user1", result);
      multi.set("user2", result);
      int i = 1/0;   // 報錯,事務停止使用
      multi.exec();  // 執行事務
    } catch (Exception e) {
      multi.discard(); // 放棄事務
      e.printStackTrace();
    } finally {
      System.out.println(jedis.get("user1"));
      System.out.println(jedis.get("user2"));
      jedis.close(); // 關閉連線
    }
  }
}

七、SpringBoot整合

SpringBoot 操作數據層:spring-data jpa、jdbc、mongodb、redis!

Spring Data 也是和 SpringBoot 齊名的專案!

說明: 在 SpringBoot 2.X 之後,原來使用的jedis被替換爲 lettuce

jedis:採用的直連,多個執行緒操作的話,是不安全的,如果想要避免不安全的,使用jedis pool 連線池!更像 BIO 模式

lettuce:採用 netty,範例可以在多個執行緒中進行共用,不存線上程不安全的情況!可以減少執行緒數據了,更像 NIO 模式

原始碼分析:

public class RedisAutoConfiguration {

  @Bean
  @ConditionalOnMissingBean(name = "redisTemplate") // 我們可以自己定義一個 redisTemplate 來替代這個預設的Bean
  public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory)
    throws UnknownHostException {
    //  預設的 RedisTemplate 沒有過多的設定,redis 物件都是需要序列化!
    // 兩個泛型都是 Object, Object 的型別,我們後面使用需要強制轉換 <String, Object>
    RedisTemplate<Object, Object> template = new RedisTemplate<>();
    template.setConnectionFactory(redisConnectionFactory);
    return template;
  }

  @Bean
  @ConditionalOnMissingBean // 由於 String 是 redis 中最常使用的型別,所以單獨提出來了一個bean
  public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory)
    throws UnknownHostException {
    StringRedisTemplate template = new StringRedisTemplate();
    template.setConnectionFactory(redisConnectionFactory);
    return template;
  }

}

整合測試

1、匯入依賴

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

2、設定連線

# SpringBoot 所有的設定類,都有一個自動設定類 RedisAutoConfiguration
# 自動設定類都會系結一個 properties 組態檔 RedisProperties
spring.redis.host=127.0.0.1
spring.redis.port=6379
# 注意設定連線池需要使用 lettuce,不能使用 Jedis ,因爲原始碼中 Jedis 很多都失效(SpringBoot2.x以上預設使用 Lettuce,沒有匯入 Jedis的包)

3、測試!

@SpringBootTest
class Redis02SpringbootApplicationTests {

  @Autowired
  private RedisTemplate redisTemplate;

  @Test
  void contextLoads() {
    // redisTemplate 操作不同的數據型別,api和我們的指令是一樣的
    // opsForValue   操作字元申 類似 string
    // opsForList    操作list  類似 List
    // opsForSet
    // opsForHash
    // opsForZSet
    // opsForGeo
    // opsForHyperLogLog
    
    redisTemplate.opsForValue().set("mykey", "kuangshen");
    redisTemplate.opsForValue().get("mykey");

    /* 除了基本的操作,我們常用的方法都可以直接通過 redisTemplate 操作,比如事務,和基本的CRUD

        redisTemplate.multi();
        redisTemplate.discard();
        redisTemplate.exec();
        */

    /* 獲取 redis 的連線物件

        RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
        connection.flushDb();
        connection.flushAll();
        */
  }

}

image.png

image.png

關於物件的儲存:

  • 解決方法:
    • 將物件轉成 Json 格式的字串進行儲存
    • 將物件的實體類接入序列化介面( implements Serializable

image.png

SpringBoot Redis自動設定類(RedisAutoConfiguration),我們可以自己編寫 RedisTemplate 來覆蓋原本的 RedisTRemplate ,自己設定序列化型別

@Configuration
public class RedisConfig {
  // 自己定義了一個新的RedisTemplate
  @Bean
  @SuppressWarnings("all")
  public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory)
    throws UnknownHostException {
    // 我們爲了開發方便,一般直接使用 <String, Object>
    RedisTemplate<String, Object> template = new RedisTemplate<>();
    template.setConnectionFactory(factory);

    /* 序列化設定 */
    // JSON 的序列化設定
    Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
    ObjectMapper om = new ObjectMapper();
    om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
    om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
    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;
  }
}

因爲頻繁的呼叫原生的 redisTemplate 會比較繁瑣且自由度不高,所以在公司企業中,一般都是存在一個對應的 RedisUtils,方便我們進行 Redis 操作。

@Component
public final class RedisUtil {

  @Autowired
  private RedisTemplate<String, Object> redisTemplate;

  // =============================common============================
  /**
     * 指定快取失效時間
     * @param key  鍵
     * @param time 時間(秒)
     */
  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;
    }
  }

  /**
     * 根據key 獲取過期時間
     * @param key 鍵 不能爲null
     * @return 時間(秒) 返回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 可以傳一個值 或多個
     */
  @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=============================

  /**
     * 普通快取獲取
     * @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;
    }
  }


  /**
     * 普通快取放入並設定時間
     * @param key   鍵
     * @param value 值
     * @param time  時間(秒) time要大於0 如果time小於等於0 將設定無限期
     * @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)
     */
  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)
     */
  public long decr(String key, long delta) {
    if (delta < 0) {
      throw new RuntimeException("遞減因子必須大於0");
    }
    return redisTemplate.opsForValue().increment(key, -delta);
  }


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

  /**
     * HashGet
     * @param key  鍵 不能爲null
     * @param item 項 不能爲null
     */
  public Object hget(String key, String item) {
    return redisTemplate.opsForHash().get(key, item);
  }

  /**
     * 獲取hashKey對應的所有鍵值
     * @param key 鍵
     * @return 對應的多個鍵值
     */
  public Map<Object, Object> hmget(String key) {
    return redisTemplate.opsForHash().entries(key);
  }

  /**
     * HashSet
     * @param key 鍵
     * @param map 對應多個鍵值
     */
  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 並設定時間
     * @param key  鍵
     * @param map  對應多個鍵值
     * @param time 時間(秒)
     * @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表中放入數據,如果不存在將建立
     *
     * @param key   鍵
     * @param item  項
     * @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表中放入數據,如果不存在將建立
     *
     * @param key   鍵
     * @param item  項
     * @param value 值
     * @param time  時間(秒) 注意:如果已存在的hash表有時間,這裏將會替換原有的時間
     * @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;
    }
  }


  /**
     * 刪除hash表中的值
     *
     * @param key  鍵 不能爲null
     * @param item 項 可以使多個 不能爲null
     */
  public void hdel(String key, Object... item) {
    redisTemplate.opsForHash().delete(key, item);
  }


  /**
     * 判斷hash表中是否有該項的值
     *
     * @param key  鍵 不能爲null
     * @param item 項 不能爲null
     * @return true 存在 false不存在
     */
  public boolean hHasKey(String key, String item) {
    return redisTemplate.opsForHash().hasKey(key, item);
  }


  /**
     * hash遞增 如果不存在,就會建立一個 並把新增後的值返回
     *
     * @param key  鍵
     * @param item 項
     * @param by   要增加幾(大於0)
     */
  public double hincr(String key, String item, double by) {
    return redisTemplate.opsForHash().increment(key, item, by);
  }


  /**
     * hash遞減
     *
     * @param key  鍵
     * @param item 項
     * @param by   要減少記(小於0)
     */
  public double hdecr(String key, String item, double by) {
    return redisTemplate.opsForHash().increment(key, item, -by);
  }


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

  /**
     * 根據key獲取Set中的所有值
     * @param key 鍵
     */
  public Set<Object> sGet(String key) {
    try {
      return redisTemplate.opsForSet().members(key);
    } catch (Exception e) {
      e.printStackTrace();
      return null;
    }
  }


  /**
     * 根據value從一個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;
    }
  }


  /**
     * 將數據放入set快取
     *
     * @param key    鍵
     * @param values 值 可以是多個
     * @return 成功個數
     */
  public long sSet(String key, Object... values) {
    try {
      return redisTemplate.opsForSet().add(key, values);
    } catch (Exception e) {
      e.printStackTrace();
      return 0;
    }
  }


  /**
     * 將set數據放入快取
     *
     * @param key    鍵
     * @param time   時間(秒)
     * @param values 值 可以是多個
     * @return 成功個數
     */
  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快取的長度
     *
     * @param key 鍵
     */
  public long sGetSetSize(String key) {
    try {
      return redisTemplate.opsForSet().size(key);
    } catch (Exception e) {
      e.printStackTrace();
      return 0;
    }
  }


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

  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快取的內容
     *
     * @param key   鍵
     * @param start 開始
     * @param end   結束 0 到 -1代表所有值
     */
  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快取的長度
     *
     * @param key 鍵
     */
  public long lGetListSize(String key) {
    try {
      return redisTemplate.opsForList().size(key);
    } catch (Exception e) {
      e.printStackTrace();
      return 0;
    }
  }


  /**
     * 通過索引 獲取list中的值
     *
     * @param key   鍵
     * @param index 索引 index>=0時, 0 表頭,1 第二個元素,依次類推;index<0時,-1,表尾,-2倒數第二個元素,依次類推
     */
  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 值
     */
  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  時間(秒)
     */
  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  時間(秒)
     * @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;
    }
  }


  /**
     * 根據索引修改list中的某條數據
     *
     * @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個值爲value
     *
     * @param key   鍵
     * @param count 移除多少個
     * @param value 值
     * @return 移除的個數
     */

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

  }

}