Redis
第一節 Redis簡介
1.1 NoSQL
NoSQL,泛指非關系型的資料庫,NoSQL即Not-Only SQL,它可以作為關系型資料庫的良好補充。随着網際網路web2.0網站的興起,非關系型的資料庫現在成了一個極其熱門的新領域,非關系資料庫産品的發展非常迅速
而傳統的關系資料庫在應付web2.0網站,特别是超大規模和高并發的SNS類型的web2.0純動态網站已經顯得力不從心,暴露了很多難以克服的問題,例如:
-
High performance - 對資料庫高并發讀寫的需求
web2.0網站要根據使用者個性化資訊來實時生成動态頁面和提供動态資訊,是以基本上無法使用動态頁面靜态化技術,是以資料庫并發負載非常高,往往要達到每秒上萬次讀寫請求。關系資料庫應付上萬次SQL查詢還勉強頂得住,但是應付上萬次SQL寫資料請求,硬碟IO就已經無法承受了。其實對于普通的BBS網站,往往也存在對高并發寫請求的需求,例如網站的實時統計線上使用者狀态,記錄熱門文章的點選次數,投票計數等,是以這是一個相當普遍的需求。
-
Huge Storage - 對海量資料的高效率存儲和通路的需求
類似Facebook,twitter,Friendfeed這樣的SNS網站,每天使用者産生海量的使用者動态,以Friendfeed為例,一個月就達到了2.5億條使用者動态,對于關系資料庫來說,在一張2.5億條記錄的表裡面進行SQL查詢,效率是極其低下乃至不可忍受的。再例如大型web網站的使用者登入系統,例如騰訊,盛大,動辄數以億計的帳号,關系資料庫也很難應付。
-
High Scalability && High Availability- 對資料庫的高可擴充性和高可用性的需求
在基于web的架構當中,資料庫是最難進行橫向擴充的,當一個應用系統的使用者量和通路量與日俱增的時候,你的資料庫卻沒有辦法像web server和app server那樣簡單的通過添加更多的硬體和服務節點來擴充性能和負載能力。對于很多需要提供24小時不間斷服務的網站來說,對資料庫系統進行更新和擴充是非常痛苦的事情,往往需要停機維護和資料遷移,為什麼資料庫不能通過不斷的添加伺服器節點來實作擴充呢?
NoSQL資料庫的産生就是為了解決大規模資料集合多重資料種類帶來的挑戰,尤其是大資料應用難題

1.2 NoSQL的類别
1.2.1鍵值(Key-Value)存儲資料庫
相關産品: Tokyo Cabinet/Tyrant、 Redis、Voldemort、Berkeley DB
典型應用: 内容緩存,主要用于處理大量資料的高通路負載。
資料模型: 一系列鍵值對
優勢: 快速查詢
劣勢: 存儲的資料缺少結構化
1.2.2 列存儲資料庫
相關産品:Cassandra, HBase, Riak
典型應用:分布式的檔案系統
資料模型:以列簇式存儲,将同一列資料存在一起
優勢:查找速度快,可擴充性強,更容易進行分布式擴充
劣勢:功能相對局限
1.2.3 文檔型資料庫
相關産品:CouchDB、MongoDB
典型應用:Web應用(與Key-Value類似,Value是結構化的)
資料模型: 一系列鍵值對
優勢:資料結構要求不嚴格
劣勢: 查詢性能不高,而且缺乏統一的查詢文法
1.2.4 圖形(Graph)資料庫
相關資料庫:Neo4J、InfoGrid、Infinite Graph
典型應用:社交網絡
資料模型:圖結構
優勢:利用圖結構相關算法。
劣勢:需要對整個圖做計算才能得出結果,不容易做分布式的叢集方案。
1.3 Redis是什麼
2008年,意大利的一家創業公司Merzia推出了一款基于MySQL的網站實時統計系統LLOOGG,然而沒過多久該公司的創始人 Salvatore Sanfilippo便對MySQL的性能感到失望,于是他決定親自為LLOOGG量身定做一個資料庫,并于2009年開發完成,這個資料庫就是Redis。不過Salvatore Sanfilippo并不滿足隻将Redis用于LLOOGG這一款産品,而是希望更多的人使用它,于是在同一年Salvatore Sanfilippo将Redis開源釋出,并開始和Redis的另一名主要的代碼貢獻者Pieter Noordhuis一起繼續着Redis的開發,直到今天。
Salvatore Sanfilippo自己也沒有想到,短短的幾年時間,Redis就擁有了龐大的使用者群體。Hacker News在2012年釋出了一份資料庫的使用情況調查,結果顯示有近12%的公司在使用Redis。國内如新浪微網誌、街旁網、知乎網,國外如GitHub、Stack Overflow、Flickr等都是Redis的使用者。
VMware公司從2010年開始贊助Redis的開發, Salvatore Sanfilippo和Pieter Noordhuis也分别在3月和5月加入VMware,全職開發Redis。
Redis是用C語言開發的一個開源的高性能鍵值對(key-value)資料庫。
它通過提供多種鍵值資料類型來适應不同場景下的存儲需求,目前為止Redis支援的鍵值資料類型如下:
字元串類型
散列類型
清單類型
集合類型
有序集合類型
Redis 與其他 key - value 緩存産品有以下三個特點:
- Redis支援資料的持久化,可以将記憶體中的資料儲存在磁盤中,重新開機的時候可以再次加載進行使用。
- Redis不僅僅支援簡單的key-value類型的資料,同時還提供list,set,zset,hash等資料結構的存儲。
- Redis支援資料的備份,即master-slave模式的資料備份。
Redis 提供的API支援:C、C++、C#、Clojure、Java、JavaScript、Lua、PHP、Python、Ruby、Go、Scala、Perl等多種語言。
1.4 Redis的應用場景
目前全球最大的Redis使用者是新浪微網誌,在新浪有200多台實體機,400多個端口正在運作Redis,有+4G的資料在Redis上來為微網誌使用者提供服務
- 取最新的N個資料(取最新文檔、排行榜等)
- 需要精确設定過期時間的應用
- 計數器應用
- 實時性要求的高并發讀寫
- 消息系統Pub/Sub
- 建構隊列
- 緩存
1.5 Redis優缺點
1.5.1 Redis 優勢
對資料高并發讀寫(基于記憶體)
對海量資料的高效率存儲和通路(基于記憶體)
對資料的可擴充性和高可用性
垂直擴充:提升硬體
水準擴充:叢集
1.5.2 Redis 缺點
redis(ACID處理非常簡單)無法做到太複雜的關系資料庫模型
1.6 Redis面向網際網路的解決方案
- 主從:一主多從,主機可寫,從機備份。類似于Mysql的讀寫分離,存在問題是一但主節點down掉,整個Redis不可用。
哨兵(2.x):啟用一個哨兵程式(節點),監控其餘節點的狀态,根據選舉政策,進行主從切換。
缺點:每個節點的資料依舊是一緻的,仍無法實作分布式的資料庫。
- 叢集(3.x):結合上述兩種模式,多主多從,實作高可用、分布式資料存儲
第二節 Redis的安裝
2.1 下載下傳
從官網下載下傳,Redis官網點選下載下傳
通過XShell将下載下傳的檔案上傳到/usr/local目錄
解壓&編譯&安裝
cd /usr/local 切換到指定目錄
tar -zxvf redis-3.2.11.tar.gz 解壓
cd redis-3.2.11 切換到解壓目錄
make 編譯
make PREFIX=/usr/local/redis install 指定安裝目錄進行安裝
源碼目錄分析:
在/usr/local/src/redis3.2/下有一個redis.conf檔案,這個檔案為redis核心配置檔案。
在/usr/local/src/redis3.2/src/下,有redis的常用指令,安裝完成後,會将這些指令自動放入到安裝路徑下的bin目錄下
在/usr/local/src/redis3.2/utils/下,有redis的服務啟動腳本
2.2 配置和啟動
redis.conf是redis的配置檔案,redis.conf在redis源碼目錄。
注意修改port作為redis程序的端口,port預設6379。
拷貝配置檔案到安裝目錄下
進入源碼目錄,裡面有一份配置檔案 redis.conf,修改然後将其拷貝到安裝路徑下
cd /usr/local/redis 切換目錄
mkdir conf 建立配置檔案的目錄
cp /usr/local/redis-3.2.11/redis.conf /usr/local/redis/conf 複制配置檔案
./redis-server 啟動
redis關閉的方式
pkill redis server
kill 程序号
/usr/local/redis/bin/redis-cli shutdown
注意:
這裡直接執行Redis-server 啟動的Redis服務,是在前台直接運作的(效果如上圖),也就是說,執行完該指令後,如果Lunix關閉目前會話,則Redis服務也随即關閉。正常情況下,啟動Redis服務需要從背景啟動,并且指定啟動配置檔案。
2.3 後端模式啟動
修改redis.conf配置檔案, 設定
daemonize yes
以後端模式啟動。
ps aux|grep redis 查詢redis是否啟動
./bin/redis-server ./redis.conf 啟動redis
補充1:redis desktop manager連接配接遠端伺服器redis
- 修改redis.conf配置檔案
- 注釋掉bind綁定配置
# bind 127.0.0.1
- 搜尋并修改為
。關閉保護模式,使其他主機的用戶端能夠連接配接到該Redis伺服器。protected-mode no
- 搜尋并修改為
。防止外部未知主機的用戶端破解并進行Redis連接配接,設定連接配接密碼。requirepass yourpassword
- 注意:如果你的redis伺服器是在華為雲/阿裡雲伺服器上自建的,預設redis端口6379是不允許外部通路的。解決辦法:在華為雲/阿裡雲控制台的安全組管理中,開啟6379端口。
Redis學習筆記Redis
- 設定完成後,打開軟體,如圖設定即可。
Redis學習筆記Redis
補充2:redis.conf配置檔案詳解
4.1. Redis預設不是以守護程序的方式運作,可以通過該配置項修改,使用yes啟用守護程序
daemonize no
4.2. 當Redis以守護程序方式運作時,Redis預設會把pid寫入/var/run/redis.pid檔案,可以通過pidfile指定
pidfile /var/run/redis.pid
4.3. 指定Redis監聽端口,預設端口為6379,作者在自己的一篇博文中解釋了為什麼選用6379作為預設端口,因為6379在手機按鍵上MERZ對應的号碼,而MERZ取自意大利歌女Alessia Merz的名字
port 6379
4.4. 綁定的主機位址
bind 127.0.0.1
4.5.當 用戶端閑置多長時間後關閉連接配接,如果指定為0,表示關閉該功能
timeout 300
4.6. 指定日志記錄級别,Redis總共支援四個級别:debug、verbose、notice、warning,預設為verbose
loglevel verbose
4.7. 日志記錄方式,預設為标準輸出,如果配置Redis為守護程序方式運作,而這裡又配置為日志記錄方式為标準輸出,則日志将會發送給/dev/null
logfile stdout
4.8. 設定資料庫的數量,預設資料庫為0,可以使用SELECT <dbid>指令在連接配接上指定資料庫id
databases 16
4.9. 指定在多長時間内,有多少次更新操作,就将資料同步到資料檔案,可以多個條件配合
save <seconds> <changes>
Redis預設配置檔案中提供了三個條件:
save 900 1
save 300 10
save 60 10000
分别表示900秒(15分鐘)内有1個更改,300秒(5分鐘)内有10個更改以及60秒内有10000個更改。
4.10. 指定存儲至本地資料庫時是否壓縮資料,預設為yes,Redis采用LZF壓縮,如果為了節省CPU時間,可以關閉該選項,但會導緻資料庫檔案變的巨大
rdbcompression yes
4.11. 指定本地資料庫檔案名,預設值為dump.rdb
dbfilename dump.rdb
4.12. 指定本地資料庫存放目錄
dir ./
4.13. 設定當本機為slav服務時,設定master服務的IP位址及端口,在Redis啟動時,它會自動從master進行資料同步
slaveof <masterip> <masterport>
4.14. 當master服務設定了密碼保護時,slav服務連接配接master的密碼
masterauth <master-password>
4.15. 設定Redis連接配接密碼,如果配置了連接配接密碼,用戶端在連接配接Redis時需要通過AUTH <password>指令提供密碼,預設關閉
requirepass foobared
4.16. 設定同一時間最大用戶端連接配接數,預設無限制,Redis可以同時打開的用戶端連接配接數為Redis程序可以打開的最大檔案描述符數,如果設定 maxclients 0,表示不作限制。當用戶端連接配接數到達限制時,Redis會關閉新的連接配接并向用戶端傳回max number of clients reached錯誤資訊
maxclients 128
4.17. 指定Redis最大記憶體限制,Redis在啟動時會把資料加載到記憶體中,達到最大記憶體後,Redis會先嘗試清除已到期或即将到期的Key,當此方法處理 後,仍然到達最大記憶體設定,将無法再進行寫入操作,但仍然可以進行讀取操作。Redis新的vm機制,會把Key存放記憶體,Value會存放在swap區
maxmemory <bytes>
4.18. 指定是否在每次更新操作後進行日志記錄,Redis在預設情況下是異步的把資料寫入磁盤,如果不開啟,可能會在斷電時導緻一段時間内的資料丢失。因為 redis本身同步資料檔案是按上面save條件來同步的,是以有的資料會在一段時間内隻存在于記憶體中。預設為no
appendonly no
4.19. 指定更新日志檔案名,預設為appendonly.aof
appendfilename appendonly.aof
4.20. 指定更新日志條件,共有3個可選值:
no:表示等作業系統進行資料緩存同步到磁盤(快)
always:表示每次更新操作後手動調用fsync()将資料寫到磁盤(慢,安全)
everysec:表示每秒同步一次(折衷,預設值)
appendfsync everysec
4.21. 指定是否啟用虛拟記憶體機制,預設值為no,簡單的介紹一下,VM機制将資料分頁存放,由Redis将通路量較少的頁即冷資料swap到磁盤上,通路多的頁面由磁盤自動換出到記憶體中(在後面的文章我會仔細分析Redis的VM機制)
vm-enabled no
4.22. 虛拟記憶體檔案路徑,預設值為/tmp/redis.swap,不可多個Redis執行個體共享
vm-swap-file /tmp/redis.swap
4.23. 将所有大于vm-max-memory的資料存入虛拟記憶體,無論vm-max-memory設定多小,所有索引資料都是記憶體存儲的(Redis的索引資料 就是keys),也就是說,當vm-max-memory設定為0的時候,其實是所有value都存在于磁盤。預設值為0
vm-max-memory 0
4.24. Redis swap檔案分成了很多的page,一個對象可以儲存在多個page上面,但一個page上不能被多個對象共享,vm-page-size是要根據存儲的 資料大小來設定的,作者建議如果存儲很多小對象,page大小最好設定為32或者64bytes;如果存儲很大大對象,則可以使用更大的page,如果不 确定,就使用預設值
vm-page-size 32
4.25. 設定swap檔案中的page數量,由于頁表(一種表示頁面空閑或使用的bitmap)是在放在記憶體中的,,在磁盤上每8個pages将消耗1byte的記憶體。
vm-pages 134217728
4.26. 設定通路swap檔案的線程數,最好不要超過機器的核數,如果設定為0,那麼所有對swap檔案的操作都是串行的,可能會造成比較長時間的延遲。預設值為4
vm-max-threads 4
4.27. 設定在向用戶端應答時,是否把較小的包合并為一個包發送,預設為開啟
glueoutputbuf yes
4.28. 指定在超過一定的數量或者最大的元素超過某一臨界值時,采用一種特殊的雜湊演算法
hash-max-zipmap-entries 64
hash-max-zipmap-value 512
4.29. 指定是否激活重置哈希,預設為開啟(後面在介紹Redis的雜湊演算法時具體介紹)
activerehashing yes
4.30. 指定包含其它的配置檔案,可以在同一主機上多個Redis執行個體之間使用同一份配置檔案,而同時各個執行個體又擁有自己的特定配置檔案
include /path/to/local.conf
2.4 啟動多個redis程序
2.4.1 啟動時指定端口
啟動時指定端口可在一台伺服器啟動多個redis程序
cd /usr/local/redis/bin
./redis-server ../conf/redis.conf --port 6380
2.4.2 建立多個redis目錄
建立多個redis目錄,以端口号命名,推薦使用此種方式
比如:建立6381、6382兩個目錄,将redis的安裝檔案bin和conf拷貝至這兩個目錄。
修改6381目錄下的redis.conf設定端口号為6381
修改6382目錄下的redis.conf設定端口号為6382
對應配置檔案修改port 6381、 port 6382啟動6381和6382目錄下的redis-server程式:
cd 6381
./redis-server . /redis.conf
cd 6382
./redis-server . /redis.conf
2.5 redis用戶端
在redis的安裝目錄中有redis的用戶端,即redis-cli(Redis Command Line Interface),它是Redis自帶的基于指令行的Redis用戶端
ps aux|grep redis 查詢redis是否啟動
./redis-server ../conf/redis.conf 啟動redis
./redis-cli -h 127.0.0.1 -p 6379 啟動redis用戶端
如果conf檔案設定密碼,則使用./redis-cli -h 127.0.0.1 -p 6379 -a xxx 來啟動用戶端
ping Redis提供了PING指令來測試用戶端與Redis的連接配接是否正常,如果連接配接正常會收到回複PONG
第三節 redis常用指令
3.1 基礎指令
3.1.1 操作String類型
String 資料結構是簡單的key-value類型,value其實不僅是String,也可以是數字,是包含很多種類型的特殊類型,并且是二進制安全的。比如序列化的對象進行存儲,比如一張圖檔進行二進制存儲,比如一個簡單的字元串,數值等等。
常用指令:
設值:set name zhangsan (說明:多次設定name會覆寫)
指令:
set key value ex 有效時間n(秒):設定key-value在n秒後自動删除
setnx key1 value1: (not exist) 如果key1不存在,則設值 并傳回1。如果key1存在,則不設值并傳回0;
setex key1 10 lx :(expired) 設定key1的值為lx,過期時間為10秒,10秒後key1清除(key也清除)
setrange string range value 替換字元串
取值: get key
删值:del keys
批量寫:mset k1 v1 k2 v2 ... 一次性寫入多個值
批量讀:mget k1 k2 k3
一次性設值和讀取(傳回舊值,寫上新值):getset name lx
數值類型自增減:incr key,decr key 注意這些 key 對應的必須是數字類型字元串,否則會出錯,自增或者自減1
自增或自減指定長度 incrby key increment,decrby key increment 對應的 key 自增或者自減increment值
字元串尾部拼接:append key value 向 key 對應的字元串尾部追加 value
字元串長度:strlen key
setrange pw 1 user 将key為pw的值從索引為1的開始進行替換,後面的由原來的字元補齊
指令示例:
3.1.2 Hash類型
Hash類型是String類型的field和value的映射表,或者說是一個String集合。它特别适合存儲對象,相比較而言,将一個對象類型存儲在Hash類型要存儲在String類型裡占用更少的記憶體空間,并方整個對象的存取。
常用指令:
設值:hset hashname field value(hset是設值指令,hashname是集合名字,field是字段名,value是值)
取值:hget hashname field
批量設定:hmset hashname field1 value1 field2 value2 ….
批量取值:hmget hashname field1 field2 ...
hsetnx key field value:和setnx大同小異
HINCRBY key field increment:指定字段增加指定值increment
hexists key field:指定 key 中是否存在指定 field,如果存在傳回1,不存在傳回0
hdel key field 删除指定key的hash的field
hlen key:傳回hash集合裡的所有的鍵數量(size)
hkeys key:傳回hash裡所有的field名稱
hvals key:傳回hash的所有field 對應的 value
hgetall key:傳回hash裡所有的field和value
指令示範
3.1.3 List類型
List類型是一個連結清單結構的集合,其主要功能有push、pop、擷取元素等。更詳細的說,List類型是一個雙端連結清單的節後,我們可以通過相關的操作進行集合的頭部或者尾部添加和删除元素,List的設計非常簡單精巧,即可以作為棧,又可以作為隊列,滿足絕大多數的需求。
常用指令:
lpush key1 value1 value2...:從頭部加入元素(棧,先進後出)
rpush key1 value1 value2 ...:從尾部加入元素(隊列,先進先出)
linsert key BEFORE|AFTER pivot value
該指令首先會在清單中從左到右查找值為pivot的元素,然後根據第二個參數是BEFORE還是AFTER來決定将value插入到該元素的前面還是後面
lrange key start stop:擷取指定索引内的所有元素,隻可從左到右 0 -1代表所有
lset key index value:将key 集合中 index下表的元素替換掉
lrem key count value
lrem指令會删除清單中前count個值為value的元素,傳回實際删除的元素個數。根據count值的不同,該指令的執行方式會有所不同:
當count>0時, LREM會從清單左邊開始删除。
當count<0時, LREM會從清單後邊開始删除。
當count=0時, LREM删除所有值為value的元素。
ltrim key start stop:保留制定key的值範圍内的資料, 其他資料會删掉, 和 lrange 一樣的參數範圍
lpop key:從list的頭部删除元素,并傳回删除元素。
rpop key:從list的尾部删除元素,并傳回删除元素
rpoplpush list1 list2:從list1尾部删除元素,并将被移除的元素添加到list2的頭部,傳回被移除的元素,可以實作MQ
llen key:傳回元素個數
lindex key index:傳回名稱為key的list中index位置的元素
3.1.4 Set類型
set集合是string類型的無序集合,set是通過hashtable實作的,對集合我們可以取交集、并集、差集
常用指令
SADD key member [member ...]:向名稱為key的set中添加元素,set集合不允許重複元素。
SMEMBERS key:檢視set集合中的元素。
SREM key member [member ...]:删除set集合的元素
SPOP key:随機删除指定set中的一個内容并将删除的内容傳回
SDIFF key [key ...]:差集運算,傳回在第一個set 中存在,第二個set 中不存在的内容
sdiffstore set4 set2 set3 将set2 set3不同元素的比較結果儲存到set4中
SINTER key [key ...]:取交集,集合重複的資料
sinterstore:set3 set1 set2取交集後儲存到 set3
SUNION key [key ...]:取并集,因為是 set 是以相同部分隻會取一次
sunionstore set3 set1 set2:取并集後儲存到 set1
smove set1 set2:從一個set集合移動到另一個set集合裡
SCARD key:檢視集合裡的元素個數
SISMEMBER key member:判斷某個元素是否為集合中的元素,是,傳回1。不是,傳回0。
srandmember key:随機傳回一個元素
3.1.5 Zset類型
有序集合和集合一樣也是string類型元素的集合,且不允許重複的成員。不同的是每個元素都會關聯一個double類型的分數。redis正是通過分數來為集合中的成員進行從小到大的排序。
有序集合的成員是唯一的,但分數(score)卻可以重複。
集合是通過哈希表實作的,是以添加,删除,查找的複雜度都是O(1)。 集合中最大的成員數為 2^32 - 1 (4294967295, 每個集合可存儲40多億個成員)。
常用指令
ZADD key score member [score member ...]: score 是分, member 是内容, score 必須是數字,向有序集合中添加一個元素,該元素如果存在則更新順序,如果分值相同元素不同會同時存在兩個元素。
ZSCORE key member 擷取指定key 中指定内容的分數
ZREM key member [member ...] :删除zset名稱key中的member元素
ZRANGE key start stop [WITHSCORES] 獲得排名在某個範圍的元素清單,照元素分數從小到大的順序傳回索引從start到stop之間的所有元素(包含兩端的元素)[WITHSCORES]為可選項,代表是否在結果中顯示分數
ZREVRANGE key start stop [WITHSCORES] 照元素分數從大到小的順序傳回索引從start到stop之間的所有元素(包含兩端的元素)
ZRANK key member 傳回有序集合中指定成員的索引(從小到大排序)
ZREVRANK key member 傳回有序集合中指定成員的排名,有序內建員按分數值遞減(從大到小)排序
ZCARD key 傳回集合裡所有元素的個數
ZCOUNT key min max 傳回集合中score在給定區間中的數量
zincrby key increment member: 有序集合中對指定成員的分數加上增量 increment
zrangebyscore key min max [WITHSCORES] [LIMIT offset count] :通過分數傳回有序集合指定區間内的成員 min max 代表分數範圍 ,offset 代表偏移量, count 代表擷取多少個,類似于資料庫
zremrangebyrank key start stop :移除有序集合中給定的排名區間的所有成員
zremrangebyscore key min max:移除有序集合中給定的分數區間的所有成員
ZINCRBY key increment member 增加memeber元素的分數increment,傳回值是更改後的分數
3.1.6 HyperLogLog
Redis 在 2.8.9 版本添加了 HyperLogLog 結構。
Redis HyperLogLog 是用來做基數統計的算法,HyperLogLog 的優點是,在輸入元素的數量或者體積非常非常大時,計算基數所需的空間總是固定 的、并且是很小的。
在 Redis 裡面,每個 HyperLogLog 鍵隻需要花費 12 KB 記憶體,就可以計算接近 2^64 個不同元素的基 數。這和計算基數時,元素越多耗費記憶體就越多的集合形成鮮明對比。
但是,因為 HyperLogLog 隻會根據輸入元素來計算基數,而不會儲存輸入元素本身,是以 HyperLogLog 不能像集合那樣,傳回輸入的各個元素。
什麼是基數?
比如資料集 {1, 2, 1, 2} 那麼這個資料集的基數集為 {1, 2}, 基數(不重複元素)為2。基數估計就是在誤差可接受的範圍内,快速計算基數。
常用指令:
PFADD 新增元素
PFCOUNT 擷取基數的估計值
PFMERGE 将多個 HyperLogLog 合并為一個 HyperLogLog
指令示範
3.2 進階指令
3.2.1 常用指令
keys * : 傳回滿足的所有鍵 ,可以模糊比對 比如 keys abc* 代表 abc 開頭的 key
exists key :是否存在指定的key,存在傳回1,不存在傳回0
expire key second:設定某個key的過期時間 時間為秒
del key:删除某個key
ttl key:檢視剩餘時間,當key不存在時,傳回 -2;存在但沒有設定剩餘生存時間時,傳回 -1,否則,以秒為機關,傳回 key 的剩餘生存時間。
persist key:取消過去時間
PEXPIRE key milliseconds 修改key 的過期時間為毫秒
select : 選擇資料庫 資料庫為0-15(預設一共16個資料庫) s
設計成多個資料庫實際上是為了資料庫安全和備份
move key dbindex : 将目前資料中的key轉移到其他資料庫
randomkey:随機傳回一個key
rename key key2:重命名key
echo:列印指令
dbsize:檢視資料庫的key數量
info:檢視資料庫資訊
config get * 實時傳儲收到的請求,傳回相關的配置
flushdb :清空目前資料庫
flushall :清空所有資料庫
指令示範
3.2.2 Redis 資料備份與恢複
資料備份
Redis SAVE 指令用于建立目前資料庫的備份。
恢複資料
如果需要恢複資料,隻需将備份檔案 (dump.rdb) 移動到 redis 安裝目錄并啟動服務即可。
3.2.3 Redis 安全
因為redis速度相當快,是以一台比較好的伺服器下,一個外部使用者在一秒内可以進行15W次密碼嘗試,這意味着你需要設定非常強大的密碼來防止暴力破解.
可以通過 redis 的配置檔案設定密碼參數,這樣用戶端連接配接到 redis 服務就需要密碼驗證,這樣可以讓你的 redis 服務更安全
常用指令:
vim /usr/local/redis/conf/redis.conf 編輯配置檔案,
修改:#reqirepass foobared 為 : reqirepass redis(你的密碼)
pkill redis-server 關閉redis-server
./bin/redis-server ./conf/redis.conf 啟動redis
./bin/redis-cli 打開用戶端
第四節、Jedis
Jedis現在基本很少使用了,這裡隻做簡單介紹。
4.1 Jedis的使用方式
4.1.1 單執行個體連接配接
public static void main(String[] args) {
Jedis jedis = new Jedis("127.0.0.1",6379);
jedis.set("user","chengsw");
String value = jedis.get("user");
System.out.println(value);
}
4.1.1 連接配接池連接配接
package com.aicai.qa.tools.statics.redis;
import com.aicai.qa.tools.statics.config.SysConfigUtil;
import redis.clients.jedis.BinaryClient;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class RedisUtil {
private JedisPool pool = null;
private RedisUtil() {
if (pool == null) {
String ip = SysConfigUtil.getSysConfigUtil("redis.properties").getString("redis.host");
int port = SysConfigUtil.getSysConfigUtil("redis.properties").getInt("redis.port");
String password = SysConfigUtil.getSysConfigUtil("redis.properties").getString("redis.password");
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
jedisPoolConfig.setMaxTotal(SysConfigUtil.getSysConfigUtil("redis.properties").getInt("redis.maxTotal"));
jedisPoolConfig.setMaxIdle(SysConfigUtil.getSysConfigUtil("redis.properties").getInt("redis.maxIdle"));
jedisPoolConfig.setMaxWaitMillis(SysConfigUtil.getSysConfigUtil("redis.properties").getLong("redis.maxWaitMillis"));
jedisPoolConfig.setTestOnBorrow(SysConfigUtil.getSysConfigUtil("redis.properties").getBoolean("redis.testOnBorrow"));
if (password != null && !"".equals(password)) {
// redis 設定了密碼
pool = new JedisPool(jedisPoolConfig, ip, port, 10000, password);
} else {
// redis 未設定密碼
pool = new JedisPool(jedisPoolConfig, ip, port, 10000);
}
}
}
/**
* 擷取指定key的值,如果key不存在傳回null,如果該Key存儲的不是字元串,會抛出一個錯誤
*
* @param key
* @return
*/
public String get(String key) {
Jedis jedis = getJedis();
String value = null;
value = jedis.get(key);
return value;
}
/**
* 設定key的值為value
*
* @param key
* @param value
* @return
*/
public String set(String key, String value) {
Jedis jedis = getJedis();
return jedis.set(key, value);
}
/**
* 删除指定的key,也可以傳入一個包含key的數組
*
* @param keys
* @return
*/
public Long del(String... keys) {
Jedis jedis = getJedis();
return jedis.del(keys);
}
/**
* 通過key向指定的value值追加值
*
* @param key
* @param str
* @return
*/
public Long append(String key, String str) {
Jedis jedis = getJedis();
return jedis.append(key, str);
}
/**
* 判斷key是否存在
*
* @param key
* @return
*/
public Boolean exists(String key) {
Jedis jedis = getJedis();
return jedis.exists(key);
}
/**
* 設定key value,如果key已經存在則傳回0
*
* @param key
* @param value
* @return
*/
public Long setnx(String key, String value) {
Jedis jedis = getJedis();
return jedis.setnx(key, value);
}
/**
* 設定key value并指定這個鍵值的有效期
*
* @param key
* @param seconds
* @param value
* @return
*/
public String setex(String key, int seconds, String value) {
Jedis jedis = getJedis();
return jedis.setex(key, seconds, value);
}
/**
* 通過key 和offset 從指定的位置開始将原先value替換
*
* @param key
* @param offset
* @param str
* @return
*/
public Long setrange(String key, int offset, String str) {
Jedis jedis = getJedis();
return jedis.setrange(key, offset, str);
}
/**
* 通過批量的key擷取批量的value
*
* @param keys
* @return
*/
public List<String> mget(String... keys) {
Jedis jedis = getJedis();
return jedis.mget(keys);
}
/**
* 批量的設定key:value,也可以一個
*
* @param keysValues
* @return
*/
public String mset(String... keysValues) {
Jedis jedis = getJedis();
return jedis.mset(keysValues);
}
/**
* 批量的設定key:value,可以一個,如果key已經存在則會失敗,操作會復原
*
* @param keysValues
* @return
*/
public Long msetnx(String... keysValues) {
Jedis jedis = getJedis();
return jedis.msetnx(keysValues);
}
/**
* 設定key的值,并傳回一個舊值
*
* @param key
* @param value
* @return
*/
public String getSet(String key, String value) {
Jedis jedis = getJedis();
return jedis.getSet(key, value);
}
/**
* 通過下标 和key 擷取指定下标位置的 value
*
* @param key
* @param startOffset
* @param endOffset
* @return
*/
public String getrange(String key, int startOffset, int endOffset) {
Jedis jedis = getJedis();
return jedis.getrange(key, startOffset, endOffset);
}
/**
* 通過key 對value進行加值+1操作,當value不是int類型時會傳回錯誤,當key不存在是則value為1
*
* @param key
* @return
*/
public Long incr(String key) {
Jedis jedis = getJedis();
return jedis.incr(key);
}
/**
* 通過key給指定的value加值,如果key不存在,則這是value為該值
*
* @param key
* @param integer
* @return
*/
public Long incrBy(String key, long integer) {
Jedis jedis = getJedis();
return jedis.incrBy(key, integer);
}
/**
* 對key的值做減減操作,如果key不存在,則設定key為-1
*
* @param key
* @return
*/
public Long decr(String key) {
Jedis jedis = getJedis();
return jedis.decr(key);
}
/**
* 減去指定的值
*
* @param key
* @param integer
* @return
*/
public Long decrBy(String key, long integer) {
Jedis jedis = getJedis();
return jedis.decrBy(key, integer);
}
/**
* 通過key擷取value值的長度
*
* @param key
* @return
*/
public Long strLen(String key) {
Jedis jedis = getJedis();
return jedis.strlen(key);
}
/**
* 通過key給field設定指定的值,如果key不存在則先建立,如果field已經存在,傳回0
*
* @param key
* @param field
* @param value
* @return
*/
public Long hsetnx(String key, String field, String value) {
Jedis jedis = getJedis();
return jedis.hsetnx(key, field, value);
}
/**
* 通過key給field設定指定的值,如果key不存在,則先建立
*
* @param key
* @param field
* @param value
* @return
*/
public Long hset(String key, String field, String value) {
Jedis jedis = getJedis();
return jedis.hset(key, field, value);
}
/**
* 通過key同時設定 hash的多個field
*
* @param key
* @param hash
* @return
*/
public String hmset(String key, Map<String, String> hash) {
Jedis jedis = getJedis();
return jedis.hmset(key, hash);
}
/**
* 通過key 和 field 擷取指定的 value
*
* @param key
* @param failed
* @return
*/
public String hget(String key, String failed) {
Jedis jedis = getJedis();
return jedis.hget(key, failed);
}
/**
* 設定key的逾時時間為seconds
*
* @param key
* @param seconds
* @return
*/
public Long expire(String key, int seconds) {
Jedis jedis = getJedis();
return jedis.expire(key, seconds);
}
/**
* 通過key 和 fields 擷取指定的value 如果沒有對應的value則傳回null
*
* @param key
* @param fields 可以是 一個String 也可以是 String數組
* @return
*/
public List<String> hmget(String key, String... fields) {
Jedis jedis = getJedis();
return jedis.hmget(key, fields);
}
/**
* 通過key給指定的field的value加上給定的值
*
* @param key
* @param field
* @param value
* @return
*/
public Long hincrby(String key, String field, Long value) {
Jedis jedis = getJedis();
return jedis.hincrBy(key, field, value);
}
/**
* 通過key和field判斷是否有指定的value存在
*
* @param key
* @param field
* @return
*/
public Boolean hexists(String key, String field) {
Jedis jedis = getJedis();
return jedis.hexists(key, field);
}
/**
* 通過key傳回field的數量
*
* @param key
* @return
*/
public Long hlen(String key) {
Jedis jedis = getJedis();
return jedis.hlen(key);
}
/**
* 通過key 删除指定的 field
*
* @param key
* @param fields 可以是 一個 field 也可以是 一個數組
* @return
*/
public Long hdel(String key, String... fields) {
Jedis jedis = getJedis();
return jedis.hdel(key, fields);
}
/**
* 通過key傳回所有的field
*
* @param key
* @return
*/
public Set<String> hkeys(String key) {
Jedis jedis = getJedis();
return jedis.hkeys(key);
}
/**
* 通過key傳回所有和key有關的value
*
* @param key
* @return
*/
public List<String> hvals(String key) {
Jedis jedis = getJedis();
return jedis.hvals(key);
}
/**
* 通過key擷取所有的field和value
*
* @param key
* @return
*/
public Map<String, String> hgetall(String key) {
Jedis jedis = getJedis();
return jedis.hgetAll(key);
}
/**
* 通過key向list頭部添加字元串
*
* @param key
* @param strs 可以是一個string 也可以是string數組
* @return 傳回list的value個數
*/
public Long lpush(String key, String... strs) {
Jedis jedis = getJedis();
return jedis.lpush(key, strs);
}
/**
* 通過key向list尾部添加字元串
*
* @param key
* @param strs 可以是一個string 也可以是string數組
* @return 傳回list的value個數
*/
public Long rpush(String key, String... strs) {
Jedis jedis = getJedis();
return jedis.rpush(key, strs);
}
/**
* 通過key在list指定的位置之前或者之後 添加字元串元素
*
* @param key
* @param where LIST_POSITION枚舉類型
* @param pivot list裡面的value
* @param value 添加的value
* @return
*/
public Long linsert(String key, BinaryClient.LIST_POSITION where,
String pivot, String value) {
Jedis jedis = getJedis();
return jedis.linsert(key, where, pivot, value);
}
/**
* 通過key設定list指定下标位置的value
* 如果下标超過list裡面value的個數則報錯
*
* @param key
* @param index 從0開始
* @param value
* @return 成功傳回OK
*/
public String lset(String key, Long index, String value) {
Jedis jedis = getJedis();
return jedis.lset(key, index, value);
}
/**
* 通過key從對應的list中删除指定的count個 和 value相同的元素
*
* @param key
* @param count 當count為0時删除全部
* @param value
* @return 傳回被删除的個數
*/
public Long lrem(String key, long count, String value) {
Jedis jedis = getJedis();
return jedis.lrem(key, count, value);
}
/**
* 通過key保留list中從strat下标開始到end下标結束的value值
*
* @param key
* @param start
* @param end
* @return 成功傳回OK
*/
public String ltrim(String key, long start, long end) {
Jedis jedis = getJedis();
return jedis.ltrim(key, start, end);
}
/**
* 通過key從list的頭部删除一個value,并傳回該value
*
* @param key
* @return
*/
public synchronized String lpop(String key) {
Jedis jedis = getJedis();
return jedis.lpop(key);
}
/**
* 通過key從list尾部删除一個value,并傳回該元素
*
* @param key
* @return
*/
synchronized public String rpop(String key) {
Jedis jedis = getJedis();
return jedis.rpop(key);
}
/**
* 通過key從一個list的尾部删除一個value并添加到另一個list的頭部,并傳回該value
* 如果第一個list為空或者不存在則傳回null
*
* @param srckey
* @param dstkey
* @return
*/
public String rpoplpush(String srckey, String dstkey) {
Jedis jedis = getJedis();
return jedis.rpoplpush(srckey, dstkey);
}
/**
* 通過key擷取list中指定下标位置的value
*
* @param key
* @param index
* @return 如果沒有傳回null
*/
public String lindex(String key, long index) {
Jedis jedis = getJedis();
return jedis.lindex(key, index);
}
/**
* 通過key傳回list的長度
*
* @param key
* @return
*/
public Long llen(String key) {
Jedis jedis = getJedis();
return jedis.llen(key);
}
/**
* 通過key擷取list指定下标位置的value
* 如果start 為 0 end 為 -1 則傳回全部的list中的value
*
* @param key
* @param start
* @param end
* @return
*/
public List<String> lrange(String key, long start, long end) {
Jedis jedis = getJedis();
return jedis.lrange(key, start, end);
}
/**
* 通過key向指定的set中添加value
*
* @param key
* @param members 可以是一個String 也可以是一個String數組
* @return 添加成功的個數
*/
public Long sadd(String key, String... members) {
Jedis jedis = getJedis();
return jedis.sadd(key, members);
}
/**
* 通過key删除set中對應的value值
*
* @param key
* @param members 可以是一個String 也可以是一個String數組
* @return 删除的個數
*/
public Long srem(String key, String... members) {
Jedis jedis = getJedis();
return jedis.srem(key, members);
}
/**
* 通過key随機删除一個set中的value并傳回該值
*
* @param key
* @return
*/
public String spop(String key) {
Jedis jedis = getJedis();
return jedis.spop(key);
}
/**
* 通過key擷取set中的差集
* 以第一個set為标準
*
* @param keys 可以 是一個string 則傳回set中所有的value 也可以是string數組
* @return
*/
public Set<String> sdiff(String... keys) {
Jedis jedis = getJedis();
return jedis.sdiff(keys);
}
/**
* 通過key擷取set中的差集并存入到另一個key中
* 以第一個set為标準
*
* @param dstkey 差集存入的key
* @param keys 可以 是一個string 則傳回set中所有的value 也可以是string數組
* @return
*/
public Long sdiffstore(String dstkey, String... keys) {
Jedis jedis = getJedis();
return jedis.sdiffstore(dstkey, keys);
}
/**
* 通過key擷取指定set中的交集
*
* @param keys 可以 是一個string 也可以是一個string數組
* @return
*/
public Set<String> sinter(String... keys) {
Jedis jedis = getJedis();
return jedis.sinter(keys);
}
/**
* 通過key擷取指定set中的交集 并将結果存入新的set中
*
* @param dstkey
* @param keys 可以 是一個string 也可以是一個string數組
* @return
*/
public Long sinterstore(String dstkey, String... keys) {
Jedis jedis = getJedis();
return jedis.sinterstore(dstkey, keys);
}
/**
* 通過key傳回所有set的并集
*
* @param keys 可以 是一個string 也可以是一個string數組
* @return
*/
public Set<String> sunion(String... keys) {
Jedis jedis = getJedis();
return jedis.sunion(keys);
}
/**
* 通過key傳回所有set的并集,并存入到新的set中
*
* @param dstkey
* @param keys 可以 是一個string 也可以是一個string數組
* @return
*/
public Long sunionstore(String dstkey, String... keys) {
Jedis jedis = getJedis();
return jedis.sunionstore(dstkey, keys);
}
/**
* 通過key将set中的value移除并添加到第二個set中
*
* @param srckey 需要移除的
* @param dstkey 添加的
* @param member set中的value
* @return
*/
public Long smove(String srckey, String dstkey, String member) {
Jedis jedis = getJedis();
return jedis.smove(srckey, dstkey, member);
}
/**
* 通過key擷取set中value的個數
*
* @param key
* @return
*/
public Long scard(String key) {
Jedis jedis = getJedis();
return jedis.scard(key);
}
/**
* 通過key判斷value是否是set中的元素
*
* @param key
* @param member
* @return
*/
public Boolean sismember(String key, String member) {
Jedis jedis = getJedis();
return jedis.sismember(key, member);
}
/**
* 通過key擷取set中随機的value,不删除元素
*
* @param key
* @return
*/
public String srandmember(String key) {
Jedis jedis = getJedis();
return jedis.srandmember(key);
}
/**
* 通過key擷取set中所有的value
*
* @param key
* @return
*/
public Set<String> smembers(String key) {
Jedis jedis = getJedis();
return jedis.smembers(key);
}
/**
* 通過key向zset中添加value,score,其中score就是用來排序的
* 如果該value已經存在則根據score更新元素
*
* @param key
* @param score
* @param member
* @return
*/
public Long zadd(String key, double score, String member) {
Jedis jedis = getJedis();
return jedis.zadd(key, score, member);
}
/**
* 通過key删除在zset中指定的value
*
* @param key
* @param members 可以 是一個string 也可以是一個string數組
* @return
*/
public Long zrem(String key, String... members) {
Jedis jedis = getJedis();
return jedis.zrem(key, members);
}
/**
* 通過key增加該zset中value的score的值
*
* @param key
* @param score
* @param member
* @return
*/
public Double zincrby(String key, double score, String member) {
Jedis jedis = getJedis();
return jedis.zincrby(key, score, member);
}
/**
* 通過key傳回zset中value的排名
* 下标從小到大排序
*
* @param key
* @param member
* @return
*/
public Long zrank(String key, String member) {
Jedis jedis = getJedis();
return jedis.zrank(key, member);
}
/**
* 通過key傳回zset中value的排名
* 下标從大到小排序
*
* @param key
* @param member
* @return
*/
public Long zrevrank(String key, String member) {
Jedis jedis = getJedis();
return jedis.zrevrank(key, member);
}
/**
* 通過key将擷取score從start到end中zset的value
* socre從大到小排序
* 當start為0 end為-1時傳回全部
*
* @param key
* @param start
* @param end
* @return
*/
public Set<String> zrevrange(String key, long start, long end) {
Jedis jedis = getJedis();
return jedis.zrevrange(key, start, end);
}
/**
* 通過key傳回指定score内zset中的value
*
* @param key
* @param max
* @param min
* @return
*/
public Set<String> zrangebyscore(String key, String max, String min) {
Jedis jedis = getJedis();
return jedis.zrevrangeByScore(key, max, min);
}
/**
* 通過key傳回指定score内zset中的value
*
* @param key
* @param max
* @param min
* @return
*/
public Set<String> zrangeByScore(String key, double max, double min) {
Jedis jedis = getJedis();
return jedis.zrevrangeByScore(key, max, min);
}
/**
* 傳回指定區間内zset中value的數量
*
* @param key
* @param min
* @param max
* @return
*/
public Long zcount(String key, String min, String max) {
Jedis jedis = getJedis();
return jedis.zcount(key, min, max);
}
/**
* 通過key傳回zset中的value個數
*
* @param key
* @return
*/
public Long zcard(String key) {
Jedis jedis = getJedis();
return jedis.zcard(key);
}
/**
* 通過key擷取zset中value的score值
*
* @param key
* @param member
* @return
*/
public Double zscore(String key, String member) {
Jedis jedis = getJedis();
return jedis.zscore(key, member);
}
/**
* 通過key删除給定區間内的元素
*
* @param key
* @param start
* @param end
* @return
*/
public Long zremrangeByRank(String key, long start, long end) {
Jedis jedis = getJedis();
return jedis.zremrangeByRank(key, start, end);
}
/**
* 通過key删除指定score内的元素
*
* @param key
* @param start
* @param end
* @return
*/
public Long zremrangeByScore(String key, double start, double end) {
Jedis jedis = getJedis();
return jedis.zremrangeByScore(key, start, end);
}
/**
* 傳回滿足pattern表達式的所有key
* keys(*)
* 傳回所有的key
*
* @param pattern
* @return
*/
public Set<String> keys(String pattern) {
Jedis jedis = getJedis();
return jedis.keys(pattern);
}
/**
* 通過key判斷值得類型
*
* @param key
* @return
*/
public String type(String key) {
Jedis jedis = getJedis();
return jedis.type(key);
}
private void close(Jedis jedis) {
if (jedis != null) {
jedis.close();
}
}
private Jedis getJedis() {
return pool.getResource();
}
public static RedisUtil getRedisUtil() {
return new RedisUtil();
}
}
第五節 Spring Data Redis
Spring Data Redis是Spring家族的一部分,提供了在Spring應用中通過簡單的配置通路redis服務,對redis底層開發包(Jedis,JRedis,RJC)進行了高度封裝,RedisTemplate提供了redis各種操作、異常處理及序列化,支援釋出訂閱,并對spring 3.1 cache進行了實作。
Spring Data Redis針對jedis提供了如下功能:
- 連接配接池自動管理,提供了一個高度封裝的“RedisTemplate”類;
- 針對jedis用戶端中大量api進行了歸類封裝,将同一類型操作封裝為operation接口
ValueOperations:簡單K-V操作 String
SetOperations:set類型資料操作 Set
ZSetOperations:zset類型資料操作 ZSet
HashOperations:針對map類型的資料操作 Hash
ListOperations:針對list類型的資料操作 List
Spring Data Redis使用方式:
- 項目中引入想給依賴
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
- 配置檔案中配置好redis資訊
spring:
redis:
host: 127.0.0.1
port: 6379
password: 123456
# 連接配接逾時時間(毫秒)
timeout: 10000
# Redis預設情況下有16個分片,這裡配置具體使用的分片,預設是0
database: 0
lettuce:
pool:
# 連接配接池最大連接配接數(使用負值表示沒有限制) 預設 8
max-active: 8
# 連接配接池最大阻塞等待時間(使用負值表示沒有限制) 預設 -1
max-wait: -1
# 連接配接池中的最大空閑連接配接 預設 8
max-idle: 8
# 連接配接池中的最小空閑連接配接 預設 0
min-idle: 0
- 在項目中引入RedisTemplate
@Autowired
private RedisTemplate redisTemplate;
@GetMapping("/test")
public void test() {
redisTemplate.boundValueOps("user").set("chengsw",30, TimeUnit.SECONDS);
String user = (String) redisTemplate.boundValueOps("user").get();
Long size = redisTemplate.boundValueOps("user").size();
System.out.println(user+ "===" + size);
}
注意:
Spring Data Redis預設情況下是使用org.springframework.data.redis.serializer.JdkSerializationRedisSerializer來做序列化。
在使用 RedisTemplate 時會産生亂碼。
不過Spring Boot整合Spring Data Redis還給我們提供了StringRedisTemplate,它繼承了RedisTemplate 并更新了StringRedisSerializer的序列化方式,解決了亂碼問題。
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package org.springframework.data.redis.core;
import org.springframework.data.redis.connection.DefaultStringRedisConnection;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
public class StringRedisTemplate extends RedisTemplate<String, String> {
public StringRedisTemplate() {
RedisSerializer<String> stringSerializer = new StringRedisSerializer();
this.setKeySerializer(stringSerializer);
this.setValueSerializer(stringSerializer);
this.setHashKeySerializer(stringSerializer);
this.setHashValueSerializer(stringSerializer);
}
public StringRedisTemplate(RedisConnectionFactory connectionFactory) {
this();
this.setConnectionFactory(connectionFactory);
this.afterPropertiesSet();
}
protected RedisConnection preProcessConnection(RedisConnection connection, boolean existingConnection) {
return new DefaultStringRedisConnection(connection);
}
}
第六節 Redis事務
Redis中的事務(transaction)是一組指令的集合,至少是兩個或兩個以上的指令,redis事
務保證這些指令被執行時中間不會被任何其他操作打斷。
事務是一個單獨的隔離操作:
事務中的所有指令都會序列化、按順序地執行。
事務在執行的過程中,不會被其他用戶端發送來的指令請求所打斷。
事務是一個原子操作:
事務中的指令要麼全部被執行,要麼全部都不執行
一個事務從開始到執行會經曆以下三個階段:
開始事務
指令入隊
執行事務
常用指令:
6.1 指令行執行事務
指令示範:
6.2 Java代碼
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@Slf4j
public class RedisController {
@Autowired
StringRedisTemplate stringRedisTemplate;
@RequestMapping(value = "/transaction")
public String Transaction() {
//開啟事務權限
stringRedisTemplate.setEnableTransactionSupport(true);
try {
//開啟事務
stringRedisTemplate.multi();
//事務下的操作
stringRedisTemplate.opsForValue().set("username","chengsw");
// int i = 1 / 0;
stringRedisTemplate.opsForValue().set("password","666666");
//送出事務
stringRedisTemplate.exec();
} catch (Exception e) {
log.error("error:", e);
stringRedisTemplate.discard();
}
return "success";
}
}
6.3 Redis事務Watch機制
A、Redis的WATCH機制
WATCH機制原理:
WATCH機制:使用WATCH監視一個或多個key,跟蹤key的value修改情況,如果有key的value值在事務EXEC執行之前被修改了,整個事務被取消。EXEC傳回提示資訊,表示事務已經失敗。
WATCH機制使的事務EXEC變的有條件,事務隻有在被WATCH的key沒有修改的前提下才能執行。不滿足條件,事務被取消。使用WATCH監視了一個帶過期時間的鍵,那麼即使這個鍵過期了,事務仍然可以正常執行。
大多數情況下,不同的用戶端會通路不同的鍵,互相同時競争同一key的情況一般都很少,樂觀鎖能夠以很好的性能解決資料沖突的問題。
B、何時取消key的監視(WATCH)?
①WATCH指令可以被調用多次。對鍵的監視從WATCH執行之後開始生效,直到調用EXEC為止。不管事務是否成功執行,對所有鍵的監視都會被取消。
②當用戶端斷開連接配接時,該用戶端對鍵的監視也會被取消。
③UNWATCH指令可以手動取消對所有鍵的監視。
C、WATCH的示例
執行步驟:
-
首先啟動redis-server,再開啟兩個用戶端連接配接。分别叫A用戶端和B用戶端。
A用戶端:WATCH某個key,同時執行事務
B用戶端:對A 用戶端WATCH的key 修改其 value值
1)在A用戶端設定key:csdn.redis為10
2)在A用戶端監視key:csdn.redis
3)在A用戶端開啟事務multi
4)在A用戶端修改csdn.redis的值為20
5)在B用戶端修改csdn.redis的值為10086
6)在A用戶端執行事務exec
7)在A用戶端檢視csdn.redis值,A用戶端執行的事務沒有送出,因為WATCH的csdn.redis的值已
經被修改了,所有放棄事務。
Redis學習筆記Redis
第七節 釋出與訂閱消息
Redis 釋出訂閱(pub/sub)是一種消息通信模式:發送者(pub)發送消息,訂閱者(sub)接收消息。
Redis 用戶端可以訂閱任意數量的頻道。
下圖展示了頻道 channel1 , 以及訂閱這個頻道的三個用戶端 —— client2 、 client5 和 client1 之間的關系
當有新消息通過 PUBLISH 指令發送給頻道 channel1 時, 這個消息就會被發送給訂閱它的三個用戶端:
7.1 指令行方式
配置訂閱和釋出
常用指令
subscribe [頻道] 進行訂閱監聽
publish [頻道 釋出内容] 進行釋出消息廣播
序号 | 指令 | 描述 |
---|---|---|
1 | PSUBSCRIBE pattern [pattern …] | 訂閱一個或多個符合給定模式的頻道。 |
2 | PUBSUB subcommand [argument [argument …]] | 檢視訂閱與釋出系統狀态。 |
3 | PUBLISH channel message | 将資訊發送到指定的頻道。 |
4 | PUNSUBSCRIBE [pattern [pattern …]] | 退訂所有給定模式的頻道。 |
5 | SUBSCRIBE channel [channel …] | 訂閱給定的一個或多個頻道的資訊。 |
6 | UNSUBSCRIBE [channel [channel …]] | 指退訂給定的頻道。 |
- 分别克隆額外的2個會話,重命名client1、client2 ,而且三個會話都運作redis的用戶端
Redis學習筆記Redis - 分别在client1和client2進行訂閱
Redis學習筆記Redis - 進行消息釋出
Redis學習筆記Redis
7.2 Java代碼方式
- 消息監聽類
import org.springframework.stereotype.Component;
@Component
public class RedisReceiver {
public void receiveMessage(String message) {
// TODO 這裡是收到通道的消息之後執行的方法
System.out.println("RedisReceiver接收到訂閱消息啦!消息内容:" + message);
}
}
- Redis消息訂閱配置類
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.listener.PatternTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.listener.adapter.MessageListenerAdapter;
@Configuration
@EnableCaching
public class RedisCacheConfig {
@Bean
RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory, MessageListenerAdapter listenerAdapter) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
// 可以添加多個 messageListener,配置不同的交換機
container.addMessageListener(listenerAdapter, new PatternTopic("channel:chengsw"));
return container;
}
/**
* 消息監聽器擴充卡,綁定消息處理器,利用反射技術調用消息處理器的業務方法
*
* @param receiver
* @return
*/
@Bean
MessageListenerAdapter listenerAdapter(RedisReceiver receiver) {
System.out.println("消息擴充卡1");
return new MessageListenerAdapter(receiver, "receiveMessage");
}
@Bean
StringRedisTemplate template(RedisConnectionFactory connectionFactory) {
return new StringRedisTemplate(connectionFactory);
}
}
- 測試接口
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Date;
@RestController
@Slf4j
public class RedisController {
@Autowired
StringRedisTemplate stringRedisTemplate;
@RequestMapping(value = "/syncmessage")
public String SyncMessage(){
for(int i = 1; i <= 5; i++){
try{
// 為了模拟消息,sleep一下。
Thread.sleep(2000);
}catch(Exception e){
log.error("error: ",e);
}
stringRedisTemplate.convertAndSend("channel:chengsw", String.format("我是消息{%d}号: %tT", i, new Date()));
}
return "success";
}
}
第八節 redis進階使用
8.1 主從複制
使用一台伺服器進行模拟Redis的主從複制
8.1.1 概念
一般來說,要将Redis運用于工程項目中,隻使用一台Redis是萬萬不能的,原因如下:
從結構上,單個Redis伺服器會發生單點故障,并且一台伺服器需要處理所有的請求負載,壓力較大;
從容量上,單個Redis伺服器記憶體容量有限,就算一台Redis伺服器内容容量為256G,也不能将所有内容用作Redis存儲記憶體,一般來說,單台Redis最大使用記憶體不應該超過20G。
考慮如下一種場景:電子商務網站上的商品,一般都是一次上傳,無數次浏覽的,說專業點也就是”多讀少寫”。
對于這種場景,我們可以使如下這種架構:
如圖所示,将一台Redis伺服器作主庫(Matser),其他三台作為從庫(Slave),主庫隻負責寫資料,每次有資料更新都将更新的資料同步到它所有的從庫,而從庫隻負責讀資料。
這樣一來,就有了兩個好處:
①讀寫分離,不僅可以提高伺服器的負載能力并且可以根據讀請求的規模自由增加或者減少從庫的數量棒極了;
② 資料被複制成了好幾份,就算有一台機器出現故障,也可以使用其他機器的資料快速恢複。
需要注意的是:在Redis主從模式中,一台主庫可以擁有多個從庫,但是一個從庫隻能隸屬于一個主庫。
8.1.2 redis主從複制特點
1、Master可以擁有多個Slave;
2、多個salve可以連接配接同一個Master,還可以連結到其他的slave
3、主從複制不會阻塞Master,在同步資料時,master可以繼續處理client請求
4、提供系統的伸縮性。
8.1.3 redis主從複制配置
在Redis中,要實作主從複制架構非常簡單,隻需要在從資料庫的配置檔案中加上如下指令即可:
slaveof 主資料庫位址 主資料庫端口
主資料庫不需要任何配置。
配置步驟:
- 重新安裝一份redis,作為從庫,拷貝redis.conf 并修改
端口号為6380
設定slaveof 主庫ip位址 端口号
設定masterauth 主庫密碼
- 啟動從庫
/usr/local/redisslave1/bin/redis-server /usr/local/redisslave1/bin/redis.conf
-
分别使用2個用戶端連接配接主庫和從庫
拷貝連接配接會話,并重命名slave,打開用戶端連接配接從庫
在主庫和從庫的連接配接用戶端中輸入:
info replication![]()
Redis學習筆記Redis
- 測試資料的主從複制
如果主庫當機,那麼就無法再繼續寫入資料
8.2 哨兵模式
8.2.1 概念
Redis-Sentinel(哨兵模式)是官方推薦的高可用解決方案,當redis在做master-slave的高可用方案時,假如master當機了,redis本身(以及其很多用戶端)都沒有實作自動進行主備切換,而redis-sentinel本身也是獨立運作的程序,可以部署在其他與redis叢集可通訊的機器中監控redis叢集。
有了主從複制的實作之後,我們如果想從伺服器進行監控,那麼在redis2.6以後提供了一個“哨兵”機制,并在2.8版本以後功能穩定起來。
哨兵:顧名思義,就是監控Redis系統的運作狀況。
哨兵模式的特點:
1、不時地監控redis是否按照預期良好地運作;
2、如果發現某個redis節點運作出現狀況,能夠通知另外一個程序(例如它的用戶端);
3、能夠進行自動切換。當一個master節點不可用時,能夠選舉出master的多個slave(如果有超過一個slave的話)中的一個來作為新的master,其它的slave節點會将它所追随的master的位址改為被提升為master的slave的新位址。
4、哨兵為用戶端提供服務發現,用戶端連結哨兵,哨兵提供目前master的位址然後提供服務,如果出現切換,也就是master挂了,哨兵會提供用戶端一個新位址。
哨兵(sentinel)本身也是支援叢集的:
很顯然,單個哨兵會存在自己挂掉而無法監控整個叢集的問題,是以哨兵也是支援叢集的,通常用三台哨兵機器來監控一組redis叢集。
8.2.2 配置
配置哨兵模式主要是防止主庫當機,是以本文就在從庫中進行配置哨兵模式
拷貝配置檔案,并修改
指令:
cp /usr/local/redis-3.2.11/sentinel.conf /usr/local/redisslave1/bin/ 拷貝哨兵的配置
vim /usr/local/redisslave1/bin/sentinel.conf 修改哨兵模式的配置檔案
配置解釋
dir: 日志路徑,根據自己的要求儲存。
sentinel monitor mymaster 10.0.31.144 6379 1 解釋:#名稱 #ip #端口号 # 投票選舉次數(即有多少票就被選舉為主,這裡節點少,就配置為1)。
sentinel down-after-millisecond mymaster 5000 解釋:哨兵程式多久去監控一次叢集,#預設 30s 檢查一次,這裡配置逾時30000毫秒為當機。
sentinel parallel-syncs mymaster 1 解釋:有多少個從節點
sentinel failover-timeout mymaster 600000 解釋:若哨兵在該配置值内未完成failover操作(即發生故障時,m/s切換),本次failover失敗
8.2.3 啟動
/usr/local/redisslave1/bin/redis-server /usr/local/redisslave1/bin/sentinel.conf --sentinel
啟動哨兵模式
/usr/local/redis/bin/redis-cli -h 10.211.55.12 -p 26379 info sentinel
8.3 持久化機制
Redis持久化存儲支援兩種方式:RDB和AOF。RDB一定時間取存儲檔案,AOF預設每秒去存儲曆史指令,預設使用RDB,且RDB預設是啟動的。
沒有持久化的redis和memcache一樣,相當于一個純記憶體的資料庫
Redis是支援持久化的記憶體資料庫,也就是說redis需要經常将記憶體中的資料同步到硬碟來保證持久化。
相同資料集,AOF檔案要遠大于RDB檔案,恢複速度慢于RDB
AOF運作效率慢于RDB,但是同步政策效率好,不同步效率和RDB相同
Redis 持久化 之 AOF 和 RDB 同時開啟,Redis聽誰的?
答:聽AOF的,RDB與AOF同時開啟,預設無腦加載AOF的配置檔案
8.3.1 RDB
RDB(snapshotting快照)是将資料寫入一個臨時檔案,持久化結束後,用這個臨時檔案替換上次持久化的檔案,達到資料恢複。
- 優點:使用單獨子程序來進行持久化,主程序不會進行任何IO操作,保證了redis的高性能
- 缺點:RDB是間隔一段時間進行持久化,如果持久化之間redis發生故障,會發生資料丢失。是以這種方式更适合資料要求不嚴謹的時候
預設方式,将記憶體中以快照的方式寫入到二進制檔案中,預設為dump.rdb,可以通過配置設定自動做快照持久化的方式。我們可以配置redis在n秒内如果m個key修改,就自動做快照.
vim /usr/local/redis/conf/redis.conf 修改配置檔案
RDB預設開啟,redis.conf中的具體配置參數如下:
save 900 1 #900秒内,超過1個key被修改,則發起快照儲存
save 300 10 #300秒内,超過10個key被修改,則發起快照儲存
save 60 10000 #60秒内,超過10000個key被修改,則發起快照儲存
dbfilename dump.rdb 持久化資料存儲在本地的檔案
dir ./ 持久化資料存儲在本地的路徑,如果是在/redis/redis-3.0.6/src下啟動的redis-cli,則資料會存儲在目前src目錄下
持久化過程:
當滿足save的條件時,比如更改了1個key,900s後會将資料寫入臨時檔案,持久化完成後将臨時檔案替換舊的dump.rdb。(存儲資料的節點是到觸發時間時的的節點)
使用RDB恢複資料:
自動的持久化資料存儲到dump.rdb後。實際隻要重新開機redis服務即可完成(啟動redis的server時會從dump.rdb中先同步資料)
使用指令進行持久化save存儲:
./redis-cli -h ip -p port save
./redis-cli -h ip -p port bgsave
一個是在前台進行存儲,一個是在背景進行存儲。
8.3.2 AOF
AOF(append-only file)是将執行過的指令記錄下來,資料恢複時按照從前到後的順序再将指令執行一遍,實作資料恢複
- 優點:可以保持更高的資料完整性,如果設定追加file的時間是1s,如果redis發生故障,最多會丢失1s的資料;且如果日志寫入不完整支援redis-check-aof來進行日志修複;AOF檔案沒被rewrite之前(檔案過大時會對指令進行合并重寫),可以删除其中的某些指令(比如誤操作的flushall)。
- 缺點:AOF檔案比RDB檔案大,且恢複速度慢。
類似于mysql日志,由于快照方式是在一定時間間隔做一次,是以可能發生redis意外當機的情況就會丢失最後一次快照後的所有被修改的資料,aof比快照方式有更好的持久化型,是由于redis在使用aof時,redis會将每一個收到的寫指令都通過write函數追加到指令中,在redis重新啟動時會重新執行檔案中儲存的寫指令在記憶體中重建這個資料庫的内容,這個檔案在redis/bin目錄下,appendonly.aof。aof不是立即寫到硬碟上,可以通過配置檔案修改強制寫到硬碟中。
修改配置
appendonly yes //啟動aof持久化 ,持久化有三種方式:
#appendfsync always //收到寫指令就立即寫入到磁盤,效率最慢,但是保證完整的持久化(最常用)
#appendfsync everysec //每秒寫一次硬碟,在性能和持久化方面做了很好的這種
#appendfsync no //完全依賴os,性能最好,持久化沒保證。
重新開機redis發現bin/目錄下多了一個appendonly.aof
提示: 開啟aof後之前的rdb模式就失效了,且之前的資料會被清空。