Redis學習筆記

Redis


第一節 Redis簡介

1.1 NoSQL

NoSQL,泛指非關係型的數據庫,NoSQL即Not-Only SQL,它可以作爲關係型數據庫的良好補充。隨着互聯網web2.0網站的興起,非關係型的數據庫現在成了一個極其熱門的新領域,非關係數據庫產品的發展非常迅速

而傳統的關係數據庫在應付web2.0網站,特別是超大規模和高併發的SNS類型的web2.0純動態網站已經顯得力不從心,暴露了很多難以克服的問題,例如:

  1. High performance - 對數據庫高併發讀寫的需求
    web2.0網站要根據用戶個性化信息來實時生成動態頁面和提供動態信息,所以基本上無法使用動態頁面靜態化技術,因此數據庫併發負載非常高,往往要達到每秒上萬次讀寫請求。關係數據庫應付上萬次SQL查詢還勉強頂得住,但是應付上萬次SQL寫數據請求,硬盤IO就已經無法承受了。其實對於普通的BBS網站,往往也存在對高併發寫請求的需求,例如網站的實時統計在線用戶狀態,記錄熱門帖子的點擊次數,投票計數等,因此這是一個相當普遍的需求。
  2. Huge Storage - 對海量數據的高效率存儲和訪問的需求
    類似Facebook,twitter,Friendfeed這樣的SNS網站,每天用戶產生海量的用戶動態,以Friendfeed爲例,一個月就達到了2.5億條用戶動態,對於關係數據庫來說,在一張2.5億條記錄的表裏面進行SQL查詢,效率是極其低下乃至不可忍受的。再例如大型web網站的用戶登錄系統,例如騰訊,盛大,動輒數以億計的帳號,關係數據庫也很難應付。
  3. 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配置文件
  1. 註釋掉bind綁定配置# bind 127.0.0.1
  2. 搜索並修改爲 protected-mode no。關閉保護模式,使其他主機的客戶端能夠連接到該Redis服務器。
  3. 搜索並修改爲 requirepass yourpassword。防止外部未知主機的客戶端破解並進行Redis連接,設置連接密碼。
  4. 注意:如果你的redis服務器是在華爲雲/阿里雲服務器上自建的,默認redis端口6379是不允許外部訪問的。解決辦法:在華爲雲/阿里雲控制檯的安全組管理中,開啓6379端口。
    在這裏插入圖片描述
  • 設置完成後,打開軟件,如圖設置即可。
    在這裏插入圖片描述

補充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客戶端
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提供瞭如下功能:

  1. 連接池自動管理,提供了一個高度封裝的“RedisTemplate”類;
  2. 針對jedis客戶端中大量api進行了歸類封裝,將同一類型操作封裝爲operation接口

ValueOperations:簡單K-V操作 String
SetOperations:set類型數據操作 Set
ZSetOperations:zset類型數據操作 ZSet
HashOperations:針對map類型的數據操作 Hash
ListOperations:針對list類型的數據操作 List

Spring Data Redis使用方式:

  1. 項目中引入想給依賴
		<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>
  1. 配置文件中配置好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
  1. 在項目中引入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的示例
執行步驟:

  1. 首先啓動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 發佈訂閱(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 …]] 指退訂給定的頻道。
  1. 分別克隆額外的2個會話,重命名client1、client2 ,而且三個會話都運行redis的客戶端
    在這裏插入圖片描述

  2. 分別在client1和client2進行訂閱
    在這裏插入圖片描述

  3. 進行消息發佈
    在這裏插入圖片描述

7.2 Java代碼方式
  1. 消息監聽類
import org.springframework.stereotype.Component;

@Component
public class RedisReceiver {
    public void receiveMessage(String message) {
        // TODO 這裏是收到通道的消息之後執行的方法
        System.out.println("RedisReceiver接收到訂閱消息啦!消息內容:" + message);
    }
}
  1. 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);
    }
}
  1. 測試接口
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 主數據庫地址  主數據庫端口
主數據庫不需要任何配置。

配置步驟:

  1. 重新安裝一份redis,作爲從庫,拷貝redis.conf 並修改
端口號爲6380
設置slaveof  主庫ip地址 端口號
設置masterauth  主庫密碼

在這裏插入圖片描述
在這裏插入圖片描述

  1. 啓動從庫
/usr/local/redisslave1/bin/redis-server /usr/local/redisslave1/bin/redis.conf 

在這裏插入圖片描述

  1. 分別使用2個客戶端連接主庫和從庫
    拷貝連接會話,並重命名slave,打開客戶端連接從庫
    在主庫和從庫的連接客戶端中輸入:

info replication
在這裏插入圖片描述

  1. 測試數據的主從複製

在這裏插入圖片描述

如果主庫宕機,那麼就無法再繼續寫入數據
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模式就失效了,且之前的數據會被清空。

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