前段時間因為自己再鼓搗zk和dubbo玩了下分散式的相關內容,想到本身redis也是用來實現分散式鎖的一種手段,所以心癢之下,整理回顧了redis的相關內容。
知識點:
- Redis資料結構及命令介紹
- Redis實現簡易的分散式鎖
- Lua語言的簡介和入門
- Redis中lua指令碼的運用
- Redis的持久化策略RDB&AOF
- Redis叢集
- 關於快取穿透擊穿及失效雪崩的思考和解決方案
學習準備:
- 虛擬機器三臺 Centos7.2
- Redis-3.2.11.tar.gz 安裝包一個 具體可以在如下網址中下載 download.redis.io/releases/
宣告:以下操作都是基於demo層級的實踐,由於深度不夠所以如果出現有誤的請聯絡本人共同學習成長。
學習前瞻
redis的優勢
儲存結構:
1)字元型別
2)雜湊型別
3)列表型別
4)集合
5)有序集合
redis的功能:
1)可以為每個key設定超時時間;expire
2)可以通過列表型別來實現分散式佇列
3)支援釋出訂閱的訊息模式 pub sub模型
簡單 1)提供了很多命令與redis進行互動
Redis的應用場景
1)資料快取(商品資料,新聞,熱點)
2)單點登入
3)秒殺,搶購
4)網站訪問排名
5)應用模組開發
redis的安裝
- 下載redis安裝包 解壓tar包
2)進去到redis目錄下中執行make命令來編譯
yum install gcc
3)通過make test測試編譯狀態【一路ok就代表沒問題】
如果出現You need tcl 8.5 or newer in order to run the Redis test
那就yum install tcl
make MALLOC=libc
4)通過make install 完成安裝【make install [prefix=/path]完成安裝】


啟動停止redis 先要複製一份conf檔案到redis目錄下 cp /data/program/redis-3.2.11/redis.conf ../redis.conf




連線到redis的命令
./redis-cli -h 127.0.0.1 -p 6379
其他命令說明
Redis-server 啟動服務
Redis-cli 訪問到redis的控制檯
redis-benchmark 效能測試的工具
redis-check-aof aof檔案進行檢測的工具
redis-check-dump rdb檔案檢查工具
redis-sentinel sentinel 伺服器配置
多資料庫支援

跟關係型資料庫不一樣的點
- redis不支援自定義資料庫名稱
- 每個資料庫不能單獨設定授權
- 每個資料庫之間並不是完全隔離的。 可以通過flushall命令清空redis例項面的所有資料庫中的資料
通過 select dbid 去選擇不同的資料庫名稱空間 。 dbid的取值範圍預設是0 -15
使用入門:
1)獲得一個符合匹配規則的鍵名列表
keys lulf:allen
keys pattern [? / * /[]]

3)type key 去獲得這個key的資料結構型別
各種資料結構的使用
1)字元型別
一個字元型別的key預設儲存的最大容量是512M
賦值和取值
SET Key Value
get Key

incr key

int value= get key;
value =value +1;
set key value;
key的設計
物件型別:物件id:物件屬性:物件子屬性
建議對key進行分類,同步在wiki統一管理
簡訊重發機制:sms:limit:mobile 138。。。。。 expire【超時】

2)雜湊型別
hash key value 不支援資料型別的巢狀
比較適合儲存物件
person
age 18
sex 男
name allen

hset key field value
hget key filed
hmset key filed value [filed value …] 一次性設定多個值
hmget key field field … 一次性獲得多個值


hexists key field 判斷欄位是否存在。 存在返回1. 不存在返回0
hincryby
hsetnx
hdel key field [field …] 刪除一個或者多個欄位
3)列表型別
list, 可以儲存一個有序的字串列表
LPUSH/RPUSH: 從左邊或者右邊push資料
LPUSH/RPUSH key value value …
{17 20 19 18 16}
llen num 獲得列表的長度
lrange key start stop ; 索引可以是負數, -1表示最右邊的第一個元素
lrem key count value
lset key index value
LPOP/RPOP : 取資料【相當於彈出】

4)集合
set 跟list 不一樣的點。 集合型別不能存在重複的資料。而且是無序的
sadd key member [member ...] 增加資料; 如果value已經存在,則會忽略存在的值,並且返回成功加入的元素的數量
srem key member 刪除元素
smembers key 獲得所有資料
sdiff key key … 對多個集合執行差集運算
sunion 對多個集合執行並集操作, 同時存在在兩個集合裡的所有值

5)有序集合
zadd key score member
zrange key start stop [withscores] 去獲得元素。 withscores是可以獲得元素的分數
如果兩個元素的score是相同的話,那麼根據(0<9<A<Z<a<z) 方式從小到大
網站訪問的前10名。

redis的事務處理
MULTI 去開啟事務
EXEC 去執行事務

redis的過期時間
expire key seconds
ttl 獲得key的過期時間

redis 的釋出訂閱 pub/sub 模型
publish channel message
subscribe channel [ …]
codis . twmproxy
註釋掉,代表外網可以訪問


edis的分散式鎖 資料庫可以做 activemq
快取 -redis setnx
Zookeeper
分散式鎖本質【用來解決什麼問題】
分散式架構是多程式的架構,利用第三方解決方案來解決併發情況下的多程式訪問共享資源的問題。
1)資源共享的競爭問題
2)資料的安全性
解決分散式鎖:
1)zookeeper 有序節點 , watcher機制
2)資料庫
3)redis setnx
分散式鎖的實現


package com.Allen.redis;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
public class RedisManager {
private static JedisPool jedisPool;
static{
JedisPoolConfig jedisPoolConfig=new JedisPoolConfig();
jedisPoolConfig.setMaxTotal(20);
jedisPoolConfig.setMaxIdle(10);
jedisPool=new JedisPool(jedisPoolConfig,"192.168.48.133",6379);
}
public static Jedis getJedis() throws Exception{
if(jedisPool!=null){
return jedisPool.getResource();
}
throw new Exception("Jedis is null");
}
}
複製程式碼
package com.Allen.redis;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;
public class RedisLock {
// 獲取鎖
public String getLock(String key, int timeout) {
try {
Jedis jedis = RedisManager.getJedis();
String value = UUID.randomUUID().toString();
// 超時時間
long end = System.currentTimeMillis() + timeout;
while (System.currentTimeMillis() < end) {// 阻塞
if (jedis.setnx(key, value) == 1) {
jedis.expire(key, timeout);//過期時間
// 鎖設定成功
return value;
}
if(jedis.ttl(key)==-1){//檢測過期時間
jedis.expire(key, timeout);
}
TimeUnit.SECONDS.sleep(1);
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
// 釋放鎖
public boolean releaseLock(String key, String value) {
try {
Jedis jedis = RedisManager.getJedis();
while (true) {
jedis.watch(key);
// 判斷獲取鎖的執行緒和當前redis中的鎖是同一個
if (value.equals(jedis.get(key))) {
// 獲取事務
Transaction transaction = jedis.multi();
transaction.del(key);
List<Object> list = transaction.exec();
if (list == null) {
continue;
}
return true;
}
jedis.unwatch();
break;
}
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
}
複製程式碼
如果報錯



package com.Allen.redis;
public class Test {
public static void main(String[] args) {
RedisLock redisLock = new RedisLock();
// 獲取鎖
String lockID = redisLock.getLock("Allen", 10000);
System.out.println("第一次獲取Allen物件鎖---> lockID是 " + lockID);
String lockID2 = redisLock.getLock("Allen", 10000);
if (null == lockID2) {
System.out.println("第二次獲取Allen物件鎖失敗");
} else {
System.out.println("第二次獲取Allen物件鎖---> lockID是 " + lockID2);
}
}
}
複製程式碼
redis效能這塊關於多路複用機制
IO多路複用機制
同步阻塞IO
同步非阻塞IO
多路複用

lua語言:
好處:
1)減少網路開銷
2)原子操作
3)複用性
輕量級指令碼語言
安裝lua
www.lua.org/ftp/
tar zxf lua-5.3.0.tar.gz cd lua-5.3.0
yum install readline-devel make linux test make install
Lua的語法學習 lua是動態型別的語言 先談下lua語言的變數,lua變數分成全域性變數和區域性變數。 a=1; local b=2;


連線字串



foreach local xx={"aa","bb","cc"} for i,v in ipairs(xx) do print(v) end


寫一個lua指令碼 進入到redis中 eval 後面的就是luo指令碼 eval 指令碼內容 keynumber key args。。。

-- 對某個ip的頻率進行限制 一分鐘訪問十次 local num=redis.call('incr',KEYS[1]) if tonumber(num)==1 then redis.call('expire',KEYS[1],ARGV[1]) return 1 elseif tonumber(num)>tonumber(ARGV[2]) then return 0 else return 1 end


luo指令碼未執行完,其他操作會存在問題

package com.Allen.redis;
import java.util.ArrayList;
import java.util.List;
import redis.clients.jedis.Jedis;
public class LuoDemo {
public static void main(String[] args) throws Exception {
Jedis jedis = RedisManager.getJedis();
String luostr = "local num=redis.call('incr',KEYS[1])\n" + "if tonumber(num)==1 then\n"
+ "redis.call('expire',KEYS[1],ARGV[1])\n" + "return 1\n"
+ "elseif tonumber(num)>tonumber(ARGV[2]) then\n" + "return 0\n" + "else\n" + "return 1\n" + "end\n";
List<String> KEYS=new ArrayList<String>();
KEYS.add("ip:limit:192.168.48.133");
List<String> ARGVS=new ArrayList<String>();
ARGVS.add("60000");
ARGVS.add("10");
Object obj=jedis.eval(luostr,KEYS,ARGVS);
System.out.println(obj);
}
}
複製程式碼
redis持久化機制
提供了倆種持久化策略
RDB
RDB的持久化策略,按照規則定時將記憶體的資料同步到磁碟
redis在指定的情況下會觸發快照
1)自己配置的快照規則
我們首先看下redis.conf這個配置檔案去看下系統預設的快照規則

當900秒內,被更改的key的數量大於1的時候就執行快照
save 900 1
save 300 10
save 60 10000
2)save或者bgsave
執行記憶體資料同步到磁碟的操作,這個操作會阻塞客戶端請求【save】
後臺非同步執行快照操作,這個操作不會阻塞客戶端請求【bgsave】background
3)執行flushall的時候
清除記憶體所有資料,只要快照規則不為空,那麼redis就會執行快照
4)執行復制的時候
redis叢集
快照檔案如下:

快照的實現原理:
redis會使用fork函式複製一份當前的程式副本(子程式)
父程式可以繼續進行客戶端請求,子程式會把記憶體資料同步到磁碟的臨時檔案上,所以不會影響到當前應用的使用。
redis的優缺點
缺點:redis可能會存在資料丟失的情況,執行快照和執行下一次快照中間的資料可能會丟失,當機。
優點:可以最大化redis的效能
AOF AOF的持久化策略,每次執行完命令後會把命令本身儲存下來【類似於實時備份】 倆種持久化策略可以使用一種也可以是同時使用,如果同時使用 重啟時候會優先使用AOF還原資料。
redis會把每一條命令追加到磁碟檔案中,會對效能有所影響。

改成yes 即開啟了aof


修改redis.conf 中的appendonly yes
重啟執行對資料的變更命令,會在bin目錄下生成對應的.aof檔案,aof會記錄所有的操作命令
如下倆個引數可以對aof檔案進行優化
壓縮策略

ps:表示當前aof檔案大小超過上一次aof檔案大小的百分之多少的時候會進行重寫,如果之前沒有重寫,以啟動時的aof檔案大小為準
auto-aof-rewrite-min-size 64mb
ps:限制允許重寫最小aof檔案大小,也就是檔案大小肖宇64mb的時候,不需要進行優化
aof重寫的原理:
aof重寫的整個過程是安全的
Redis 可以在 AOF 檔案體積變得過大時,自動地在後臺對 AOF 進行重寫: 重寫後的新 AOF 檔案包含了恢復當前資料集所需的最小命令集合。 整個重寫操作是絕對安全的,因為 Redis 在建立新 AOF 檔案的過程中,會繼續將命令追加到現有的 AOF 檔案裡面,即使重寫過程中發生停機,現有的 AOF 檔案也不會丟失。 而一旦新 AOF 檔案建立完畢,Redis 就會從舊 AOF 檔案切換到新 AOF 檔案,並開始對新 AOF 檔案進行追加操作。AOF 檔案有序地儲存了對資料庫執行的所有寫入操作, 這些寫入操作以 Redis 協議的格式儲存, 因此 AOF 檔案的內容非常容易被人讀懂, 對檔案進行分析(parse)也很輕鬆

同步磁碟資料
redis每次更改資料的時候,aof機制都會將命令記錄到aof檔案,但是實際上由於作業系統的快取機制,資料沒有實時地寫入硬碟,而是進入硬碟快取,再通過硬碟快取機制去重新整理儲存到檔案。【理論上可能出現資料丟失】

appendfsync always 每次執行寫入都會同步,最安全,效率最低
appendfsync everysec 每一秒執行
appendfsync no 不主動進行同步,由作業系統進行同步,這是最快,最不安全
檔案損壞修復
通過 redis-check-aof -fix
Redis叢集
叢集方式
1)master/slave

弄三臺伺服器 192.168.48.133 192.168.48.134 192.168.48.136

倆臺從機器 192.168.48.134 192.168.48.136 slave機器配置 slaveof 192.168.48.133 6379
然後我們進去master伺服器

預設情況下slave是隻讀的,slave上的資料無法同步到master
配置過程
修改48.134和48.136的redis.conf檔案,增加slaveof masterip masterport
slaveof 192.168.48.133 6379
實現原理
- slave第一次或者重連到master上以後,會向master傳送一個SYNC的命令
- master收到SYNC的時候,會做兩件事
a. 執行bgsave(rdb的快照檔案)
b. master會把新收到的修改命令存入到緩衝區
缺點 沒有辦法對master進行動態選舉【master掛了,就只能讀不能寫了】

複製的方式
- 基於rdb檔案的複製(第一次連線或者重連的時候)
- 無硬碟複製

PSYNC master run id. offset
- 哨兵機制

- 監控master和salve是否正常執行
- 如果master出現故障,那麼會把其中一臺salve資料升級為master
配置哨兵
首先我們準備三臺redis配置好master和slave模式【配置master/slave具體參考上面內容】
192.168.48.133 master
192.168.48.134 slave
192.168.48.136 slave
在134上覆制一份sentinel.conf

sentinel monitor mymaster 192.168.48.133 6379 1
然後啟動




3)叢集【redis3.0之後的功能】
叢集原理
Redis Cluster中,Sharding採用slot(槽)的概念,一共分成16384個槽,這有點兒類似前面講的pre sharding思路。對於每個進入Redis的鍵值對,根據key進行雜湊,分配到這16384個slot中的某一箇中。使用的hash演算法也比較簡單,就是CRC16後16384取模。Redis叢集中的每個node(節點)負責分攤這16384個slot中的一部分,也就是說,每個slot都對應一個node負責處理。當動態新增或減少node節點時,需要將16384個槽做個再分配,槽中的鍵值也要遷移。當然,這一過程,在目前實現中,還處於半自動狀態,需要人工介入。Redis叢集,要保證16384個槽對應的node都正常工作,如果某個node發生故障,那它負責的slots也就失效,整個叢集將不能工作。為了增加叢集的可訪問性,官方推薦的方案是將node配置成主從結構,即一個master主節點,掛n個slave從節點。這時,如果主節點失效,Redis Cluster會根據選舉演算法從slave節點中選擇一個上升為主節點,整個叢集繼續對外提供服務。這非常類似伺服器節點通過Sentinel監控架構成主從結構,只是Redis Cluster本身提供了故障轉移容錯的能力。
slot(槽)的概念,在redis叢集中一共會有16384個槽,
根據key 的CRC16演算法,得到的結果再對16384進行取模。 假如有3個節點
node1 0 5460
node2 5461 10922
node3 10923 16383
節點新增
node4 0-1364,5461-6826,10923-12287
刪除節點
先將節點的資料移動到其他節點上,然後才能執行刪除
市面上提供了叢集方案
- redis shardding 而且jedis客戶端就支援shardding操作 SharddingJedis ; 增加和減少節點的問題; pre shardding 3臺虛擬機器 redis 。但是我部署了9個節點 。每一臺部署3個redis增加cpu的利用率 9臺虛擬機器單獨拆分到9臺伺服器
- codis基於redis2.8.13分支開發了一個codis-server
- twemproxy twitter提供的開源解決方案
redis 設定密碼 requirepass

redis 快取的更新
倆個不同的儲存,如何保證原子性
1【先刪除快取,在更新資料庫】可能出現髒讀
2【先更新資料庫,更新成功之後,讓快取失效】減少了髒讀的可能性,但是還是有概率出現髒讀
3【更新資料的時候,只更新快取,不更新資料庫,然後通過非同步排程去批量更新資料庫】提升效能,但是無法保證強一致性。
關於快取穿透擊穿及失效雪崩的思考和解決方案
1)快取穿透
概念:快取穿透是指查詢一個一定不存在的資料,由於快取是不命中時被動寫的,並且出於容錯考慮,如果從儲存層查不到資料則不寫入快取,這將導致這個不存在的資料每次請求都要到儲存層去查詢,失去了快取的意義。在流量大時,可能DB就掛掉了,要是有人利用不存在的key頻繁攻擊我們的應用,這就是漏洞。
解決方案【倆種方式】:
1)最常見的是採取布隆過濾器,把所有可能存在的資料雜湊到一個足夠大的bitmap中,一個不存在的資料會被bitmap攔截掉,避免對底層儲存系統的查詢壓力。
2)如果查詢為空,把這個空結果快取,過期時間設定不超過5分鐘。
2)快取擊穿
概念:對於一些設定了過期時間的key,如果這些key可能會在某些時間點被超高併發地訪問,是一種非常“熱點”的資料。這個時候,需要考慮一個問題:快取被“擊穿”的問題,這個和快取雪崩的區別在於這裡針對某一key快取,前者則是很多key。
快取在某個時間點過期的時候,恰好在這個時間點對這個Key有大量的併發請求過來,這些請求發現快取過期一般都會從後端DB載入資料並回設到快取,這個時候大併發的請求可能會瞬間把後端DB壓垮。
解決方案:
1)使用互斥鎖
2)提前使用互斥鎖
3)不過期,非同步構建快取,不會阻塞執行緒池
4)資源隔離元件hystrix
3)快取失效
概念:快取雪崩是指在我們設定快取時採用了相同的過期時間,導致快取在某一時刻同時失效,請求全部轉發到DB,DB瞬時壓力過重雪崩。
解決方案:
快取失效時的雪崩效應對底層系統的衝擊非常可怕。大多數系統設計者考慮用加鎖或者佇列的方式保證快取的單線 程(程式)寫,從而避免失效時大量的併發請求落到底層儲存系統上。這裡分享一個簡單方案就時講快取失效時間分散開,比如我們可以在原有的失效時間基礎上增加一個隨機值,比如1-5分鐘隨機,這樣每一個快取的過期時間的重複率就會降低,就很難引發集體失效的事件。