1. Redis 概述
- Redis:遠程字典服務器(REmote DIctionary Server);
- Redis 是一個高性能(key/value)分布式內存數據庫,基於內存運行並支持持久化的NoSQL數據庫;
- Redis 三個特點:
- Redis 支持數據的持久化,可以將內存中的數據保存在磁盤中,重啟的時候,可以再次加載進行使用;
- Redis 不僅僅支持簡單的key-value類型的數據,同時還提供list,set,zset,hash等數據結構的存儲;
- Redis 支持數據的備份,即master-slave模式的數據備份;
2. Redis 數據類型
2.1 Redis 的五大數據類型
- String(字符串)
- Hash(哈希,類似java里的Map)
- List
- Set
- Zset(sorted set: 有序集合)
2.2 Redis 鍵(key)
set 鍵名 鍵值
: 向數據庫中存儲鍵值對;keys *
: 查看當前數據庫所有鍵;exits 鍵名
: 判斷某個key是否存在,存在,返回"1";不存在,返回"0";move 鍵名 數據庫(例如2)
: 將某個鍵剪切至2號數據庫;expire key 秒
: 為指定的key設置過期時間;ttl key
: 查看該key,還有多少秒過期,-1
表示永不過期,-2
表示已過期;type key
: 查看key的類型;
2.3 Redis 字符串(String)
set/get/del 鍵名
: 設置/獲取/刪除;append 鍵名(key) 值
: 向key所對應的value后面添加值;strlen 鍵名(key)
: 獲取某個key所對應的value的字符串長度;incr/decr/incrby/decrby
: 一定要是數字才能進行加減;getrange k2 0 3
: 表示獲取k2所對應的value的前四個字符;setrange
: 設置指定區間范圍內的值;setex(set with expire) 秒
: 設置過期時間;setnx(set if not exist)
mset/mget/msetnx
: 設置(獲取)多個, 例,mset k2 v2 k3 v3 k4 v4
;getset
: 先get再set;
2.4 Redis 列表(List)
LPUSH list01 1 2 3 4 5
: 存儲list; "LPUSH(Left push)"LRANGE list01
: 獲取,"5,4,3,2,1";RPUSH list02 1 2 3 4 5
LRANGE list02
: 獲取, "1 2 3 4 5"LPOP/RPOP
LINDEX
: 按照索引下標獲得元素(從上到下);LLEN
: 獲取list集合的長度;RUPUSH list03 1 1 1 2 2 2 3 3 3 4 4 4 5 6 7
LREM list03 2 3
: 刪除list03集合中兩個3(LREM, left remove);LTRIM list03 0 4
: 截取指定范圍(0~4)的值,然后賦值給list03;RPOPLPUSH 源列表 目的列表
LSET list03 1 x
: 將list03中的下標為1的鍵的值設置為"x";LINSERT list03 before(after) x java
: 在"x"的前面(或后面)插入"java";
2.5 Redis 集合(Set)
SADD set01 1 1 2 2 3 3
: 向set01中插入值,只會插入"1 2 3",即不允許重復;SMEMBERS set01
: 獲取set01集合中的值SISMEMBER set01 1
: 判斷"1"是否為集合set01成員; 如果是,返回1;不是,返回0;SCARD
: 獲取集合里面元素個數;SREM key member[memeber...]
:移除集合key中的一個或多個memeber元素;SRANDMEMEBER set01 3
: 從set01集合中隨機取三個值;SPOP set01
:從set01集合中,隨機移除一個元素,並返回該元素;SMOVE source destination memeber
: 將 memeber 元素從source集合移動到destination集合;SDIFF
: 差集;SINTER
: 交集;SUNION
: 並集;
2.6 Redis 哈希(Hash)
- 類似於
Map<String,Object>
; - KV模式不變,但V是一個鍵值對;
hset user gender male
: 將 user 哈希表中的gender值設為male;hget user gender
: 獲取;hmset customer id 11 name zhangsan age 22
:同時將多個field-value(域-值)對設置到哈希表customer中hmget customer id name age
: 獲取多個值;hgetall customer
: 獲取所有的域和值;hdel customer id name
: 刪除哈希表中的id和name域;hlen customer
:獲取哈希表的長度;hexists customer id
: 查看哈希表customer中是否存在id域;hkeys customer
: 獲取哈希表customer中的所有域;hvalues customer
: 獲取哈希表customer中的所有域的值;hincryby key field increment
: 為哈希表key中的域field的值加上增量increment;hincrbyfloat
hsetnx key field value
: 當域field不存在時,將該域field的值設置為value;
2.7 Redis 有序集合(Zset,sorted set)
- Zset 在set基礎上,加上一個score值;之前set集合是:
k1 v1 v2 v3
,現在,Zset是k1 score1 v1 score2 v2
zadd key score1 mem1 score2 mem2
:將一個或多個memeber元素及其score的值加入到有序集key當中;zrangebyscore key 開始min 結束max
: 返回有序集key中,所有score介於min和max之間;zrangebysroce zset (1 5
: 返回所有符合條件 1<score<=5 的成員;zrem key memeber
: 移除有序集key中的一個或多個成員;zcard key
: 返回有序集合key的基數;zcount key min max
: 返回有序集key中,score值在min和max之間的成員數量;zrank key member
: 返回有序集key中成員memeber的下標;zscore key member
: 返回有序集key中,成員memeber對應的score值;zrevrank key member
: 逆序獲得成員member下標值;zrevrange key 0 -1
: 逆序獲得有序集key的所有的member成員;zrevrangebyscore
3. Redis 的持久化
- RDB(Redis DataBase)
- AOF(Append Only File)
3.1 RDB(Redis DataBase)
3.1.1 RDB 概述
- RDB: 在指定的時間間隔內,將內存中的數據集快照(snapshot)寫入磁盤,它恢復時,是將快照文件直接讀到內存里;
- Redis 會單獨創建(fork)一個子進程來進行持久化,會先將數據寫入到一個臨時文件中,待持久化過程都結束了,
在用這個臨時文件替換上次持久化好的文件; - 整個過程中,主進程是不進行任何IO操作的,這就確保了極高的性能;
- 如果需要進行大規模數據的恢復,且對於數據恢復的完整性不是非常敏感,那RDB方式要比AOF方式更加高效;
- RDB 的缺點: 最后一次持久化后的數據可能丟失;
- RDB 保存的是
dump.rdb
; - RDB 是整個內存壓縮過的Snapshot, RDB持久化策略,與快照觸發條件一樣,默認值
- 1分鍾內修改了1萬次;
- 5分鍾內修改了10次;
- 15分鍾內修改了1次;
3.1.2 Fork
- Fork的作用: 是復制一個與當前進程一樣的進程;新進程的所有數據(變量,環境變量,程序計數器等)數值都和原進程
一致,但是是一個全新的進程,並作為原進程的子進程;
3.1.3 觸發RDB快照的方式(三種)
- 配置文件中默認的快照配置,冷拷貝以后重新使用(即備份文件和要恢復備份的機器不是同一台);
save
或bgsave
命令,立刻生成dump.rbd文件;save
時,只管保存,其他不管,全部阻塞;bgsave
:Redis 會在后台異步執行快照操作,快照的同時還可以響應客戶端請求;可以通過lastsave命令
獲取最后一次成功執行快照的時間;
- 執行
flushall
命令,也會產生dump.rdb文件,但里面是空的,無意義;
3.1.4 恢復備份文件
- 將備份文件(dump.rdb)移動到redis安裝目錄並啟動服務即可;
3.1.5 RDB優缺點
- 優勢:
- 適合大規模的數據恢復;
- 對數據完整型和一致性要求不高;
- 劣勢
- 在一定間隔時間做一次備份,所以如果redis意外down掉的話,就會丟失最后一次快照后的所有修改;
- Fork 的適合,內存中的數據被克隆了一份,大致2倍的膨脹性需要考慮;
3.2 AOF(Append Only File)
3.2.1 AOF 概述
- 以日志的形式來記錄每個寫操作,將Redis執行過的所有寫指令記錄下來(讀操作不記錄),只需追加文件但不可以改寫文
件, redis啟動之初會讀取該文件,重新構建數據,換言之,redis重啟的話,就根據日志文件的內容將寫指令從前到后執行一
次以完成數據的恢復工作; redis.conf
中的配置appendonly no
,即該種持久化方式默認為關閉;redis.conf
中的配置appendfsync=everysec
,出廠默認值,異步操作,每秒記錄,若一秒內宕機,有數據丟失;appendfsync=always
: 同步持久化,每次發生數據變更會被立即記錄到磁盤,性能較差但數據完整性比較好;
3.2.2 Rewrite
- AOF 采用文件追加方式,文件會越來越大,為避免出現此種情況,新增了重寫機制,當AOF文件的大小超過所設定的閾值時,
Redis就會啟動AOF文件的內容壓縮,只保留可以恢復數據的最小指令集,可以使用命令bgrewriteaof
; - 重寫原理:AOF文件持續增長而過大時,會fork出一條新進程來將文件重寫(也是先寫臨時文件最后再rename),遍歷新進
程的內存中數據;重寫aof文件的操作,並沒有讀取舊的aof文件,而是將整個內存中的數據庫內容用命令的方式重寫了一
個新的aof文件,這點和快照有點類似; - 觸發機制:Redis會記錄上次重寫時的AOF大小,默認配置是當AOF文件大小是上次rewrite后大小的一倍且文件大於64M時觸發;
3.2.3 AOF 優勢和劣勢
- 優勢:
- 同步持久化,
appendfsync=always
; - 每修改同步,
appendfsync=everysec
;
- 同步持久化,
- 劣勢:
- 相同數據集的數據而言,aof文件要遠大於rdb文件,恢復速度慢於rdb;
- AOF運行效率要慢於rdb,每秒同步策略效率較好,不同步效率和rdb相同;
3.3 RDB 和 AOF
- RDB 持久化方式能夠在指定的時間間隔,對數據進行快照存儲;
- AOF 持久化方式記錄每次對服務器寫的操作,當服務器重啟的時候,會重新執行這些命令來恢復原始的數據,AOF 命令
以redis協議追加保存每次寫的操作到文件末尾,Redis 還能對AOF文件進行后台重寫,使得AOF文件的體積不至於過大; - 同時開啟兩種持久化方式:
- 在這種情況下,當redis重啟的時候,會優先加載AOF文件來恢復原始的數據,因為在通常情況下,AOF文件保存的數
據集要比RDB文件保存的數據集完整;
- 在這種情況下,當redis重啟的時候,會優先加載AOF文件來恢復原始的數據,因為在通常情況下,AOF文件保存的數
4. Redis 的事務
4.1 事務
- 事務本質上是一組命令的集合;一個事務中的所有命令都會序列化,按順序地串行化執行而不會被其他命令插入,不許加塞;
4.2 常用命令
- MULTI
: 標記一個事務塊的開始;
- EXEC
: 執行所有事務塊內的命令;
- DISCARD
: 取消事務,放棄執行事務塊內的所有命令;
- WATCH
: 監控一個(或多個)key,如果在事務執行之前,這個(或這些)key被其他命令所改動,那么事務將被打斷;
- UNWATCH
: 取消WATCH命令對所有key的監視;
4.3 事務的執行情況:
- 正常執行
- 放棄事務
- 全體連坐:不管命令正確錯誤,都不執行;
- 冤頭債主:錯誤的命令不執行,正確的命令執行;
- watch監控
4.4 Redis 事務的三階段
- 開啟: 以MULTI開始一個事務;
- 入隊: 將多個命令入隊到事務中,接到這些命令並不會立即執行,而是放到等待執行的事務隊列里面;
- 執行: 以EXEC命令觸發事務;
4.5 Redis 事務的三個特性
- 單獨的隔離操作:事務中的所有命令都會序列化,按順序地執行;事務在執行的過程中,不會被其他客戶端發送來的命令請求所打斷;
- 沒有隔離級別的概念:隊列中的命令,在沒有提交之前不會實際的被執行,因為事務提交前任何指令都不會被實際執行,
也就不存在"事務內的查詢要看到事務里的更新,在事務外查詢不能看到"這個讓人萬分頭痛的問題;
- 不保證原子性:redis同一個事務中,如果有一條命令執行失敗,其他的命令仍然會被執行,沒有回滾;
5. Redis 的發布/訂閱
- Redis 的發布/訂閱:進程間的一種信息通信模式,發送者(pub)發送消息,訂閱者(sub)接收消息;
6. Redis 主從復制(Master/Slave)
6.1 概述
- 主從復制: 主機數據更新后,根據配置和策略,自動同步到備機的master/slave機制,Master以寫為主,Slave以讀為主;
6.2 應用
- 讀寫分離
- 容災備份
6.3 使用方法
-
配從(庫)不配主(庫);
-
從庫配置:
slaveof 主庫IP 主庫端口
- 每次與master斷開之后,都需要重新連接,除非配置進redis.conf文件;
info replication
: 查看當前庫的狀態;
-
修改配置文件
- 拷貝多個 redis.conf 文件;
- 設置
daemonize yes
,Pid 文件名字
,指定端口
,Log文件的名字
,Dump.rbd名字
;
-
常用3招
- 一主二從(一個Master,兩個Slave)
- 薪火相傳
- 上一個Slave可以是下一個Slave的Master,Slave同樣可以接收其他Slaves的連接和同步請求,那么,該
Slave就是下一個Slave的Master,這樣,可以有效減輕master的寫壓力; - 中途變更轉向:會清除之前的數據,重新建立拷貝最新的;
slaveof 新主庫IP 新主庫端口
;
- 上一個Slave可以是下一個Slave的Master,Slave同樣可以接收其他Slaves的連接和同步請求,那么,該
- 反客為主
slaveof no one
- 使當前數據庫停止與其他數據庫的同步,轉成主數據庫;
-
復制原理
- slave 成功連接到master后,會發送一個sync命令;
- Master 接到命令,啟動后台的存盤進程,同時,收集所有接收到的用於修改數據集命令,在后台進程執行完畢之后,
master 將傳送整個數據文件到slave,以完成一次完全同步; 全量復制
:slave 服務在接收到數據庫文件數據后,將其存盤並加載到內存中;增量復制
:Master繼續將新的所有收集到的修改命令依次傳給slave,完成同步;- 但是,只要是重新連接master,一次完全同步(全量復制)將被自動執行;
復制的缺點
:復制的延時;由於所有的寫操作都是先在Master上操作,然后同步更新到Slave上,所以從Master同步
到slave機器有一定的延遲,當系統很繁忙的時候,延遲問題會更加嚴重,Slave機器數量的增加也會使這個問題更加嚴重;
-
哨兵模式(Sentinel)
- "反客為主"的自動版,能夠后台監控主機是否故障,如果發生故障,根據投票數自動將從庫轉換為主庫;
- 使用步驟
- 配置
sentinel.conf
, 具體參考地址
- 配置
- 一個sentinel能同時監控多個Master;
7. Jedis 使用
7.1 導 jar 包
Commons-pool-1.6.jar
Jedis-2.1.0.jar
7.2 編寫測試類
// TestJedis.java
public class TestJedis{
public static void main(String[] args){
// 建立連接
Jedis jedis = new Jedis("127.0.0.1",6379);
// 1. 測試連通性
System.out.println(jedis.ping());
// 2. Redis 常用API
// 執行添加操作
jedis.set("k1","v1");
jedis.set("k2","v2");
jedis.set("k3","v3");
// 獲取
System.out.println(jedis.get("k1"));
// 獲取所有
Set<String> sets = jedis.keys("*"))
System.out.println(sets);
// 3. 事務操作
// 開啟事務
Transaction transaction = jedis.multi();
transaction.set("k11","v11");
transaction.set("k12","v12");
// 提交事務: transaction.exec();
// 回滾事務:
transaction.discard();
// 3.1 事務加鎖(watch 命令)
// watch 命令就是標記一個鍵,如果標記了一個鍵,在提交事務前,如果該鍵被別人修改過,那事務
// 就會失敗,這種情況通常可以在程序中重新再嘗試一次;
TestJedis test = new TestJedis();
boolean retValue = test.transMethod();
System.out.println("事務執行情況:"+retValue);
// 4. 主從復制(主寫,從讀)
Jedis jedis_M = new Jedis("127.0.0.1",6380);
Jedis jedis_S = new Jedis("127.0.0.1",6381);
jedis_S.slaveof("127.0.0.1",6380);
jedis_M.set("name","zhangsan");
System.out.println(jedis_S.get("name"));
}
// 3.1 事務中調用的方法
public boolean transMethod(){
Jedis jedis = new Jedis("127.0.0.1",6379);
int balance; //可用余額
int debt; // 欠額
int amtToSubtract = 10; // 實刷額度
// watch 監控
jedis.watch("balance");
balace = Integer.parseInt(jedis.get("balance"));
if(balance < amtToSubtract){
jedis.unwatch();
System.out.println("余額不足,請充值");
return false;
} else {
System.out.println("#############transaction 開始");
Transaction transaction = jedis.multi();
transaction.decrBy("balance",amtToSubtract);
transaction.incrBy("debt",amtToSubtract);
transaction.exec();
balance = Integer.parseInt(jedis.get("balance"));
debt = Integer.parseInt(jedis.get("debt"));
System.out.println("########"+balace);
System.out.println("##########"+debt);
return true;
}
}
}
7.3 JedisPool
// JedisPoolUtil.java
public class JedisPoolUtil{
// 連接池,單例設計模式
private static volatile JedisPool jedisPool = null;
private JedisPoolUtil(){};
public static JedisPool getJedisPoolInstance(){
if(null == jedisPool){
// 同步代碼塊
synchronized(jedisPoolUtil.class){
if(null == jedisPool){
// 連接池配置
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxActive(1000);
poolConfig.setMaxIdle(32);
poolConfig.setMaxWait(100*1000);
// 獲得一個jedis實例的時候,是否檢查連接可用性
poolConfig.setTestOnBorrow(true);
jedisPool = new JedisPool(poolConfig,'127.0.0.1',6379);
}
}
}
return jedisPool;
}
// 將連接歸還連接池
public static void release(JedisPool jedisPool,Jedis jedis){
if(null != jedis){
jedisPool.returnResourceObject(jedis);
}
}
}
// 測試類
public class TestPool{
public static void main(String[] args){
JedisPool jedisPool = JedisPoolUtil.getJedisPoolInstance();
Jedis jedis = null;
try{
jedis = jedisPool.getResrouce();
jedis.set("name","lisi");
}catch(Exception e){
e.printStackTrace();
}finally{
// 釋放連接
jedisPoolUtil.release(jedisPool,jedis);
}
}
}
參考資料