前段時間由於本身再鼓搗zk和dubbo玩了下分佈式的相關內容,想到自己redis也是用來實現分佈式鎖的一種手段,因此心癢之下,整理回顧了redis的相關內容。java
知識點:node
學習準備:linux
聲明:如下操做都是基於demo層級的實踐,因爲深度不夠因此若是出現有誤的請聯繫本人共同窗習成長。redis
學習前瞻 redis的優點 存儲結構:
1)字符類型
2)散列類型
3)列表類型
4)集合
5)有序集合算法
redis的功能:
1)能夠爲每一個key設置超時時間;expire
2)能夠經過列表類型來實現分佈式隊列
3)支持發佈訂閱的消息模式 pub sub模型數據庫
簡單 1)提供了不少命令與redis進行交互後端
Redis的應用場景 1)數據緩存(商品數據,新聞,熱點)
2)單點登陸
3)秒殺,搶購
4)網站訪問排名
5)應用模塊開發緩存
redis的安裝安全
make MALLOC=libc
4)經過make install 完成安裝【make install [prefix=/path]完成安裝】bash
啓動中止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 服務器配置
多數據庫支持
使用入門:
1)得到一個符合匹配規則的鍵名列表
keys lulf:allen
keys pattern [? / * /[]]
各類數據結構的使用
1)字符類型
一個字符類型的key默認存儲的最大容量是512M
賦值和取值
SET Key Value
get Key
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 … 一次性得到多個值
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這個配置文件去看下系統默認的快照規則
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文件進行優化
壓縮策略
aof重寫的原理:
aof重寫的整個過程是安全的
Redis 能夠在 AOF 文件體積變得過大時,自動地在後臺對 AOF 進行重寫: 重寫後的新 AOF 文件包含了恢復當前數據集所需的最小命令集合。 整個重寫操做是絕對安全的,由於 Redis 在建立新 AOF 文件的過程當中,會繼續將命令追加到現有的 AOF 文件裏面,即便重寫過程當中發生停機,現有的 AOF 文件也不會丟失。 而一旦新 AOF 文件建立完畢,Redis 就會從舊 AOF 文件切換到新 AOF 文件,並開始對新 AOF 文件進行追加操做。AOF 文件有序地保存了對數據庫執行的全部寫入操做, 這些寫入操做以 Redis 協議的格式保存, 所以 AOF 文件的內容很是容易被人讀懂, 對文件進行分析(parse)也很輕鬆
同步磁盤數據
redis每次更改數據的時候,aof機制都會將命令記錄到aof文件,可是實際上因爲操做系統的緩存機制,數據沒有實時地寫入硬盤,而是進入硬盤緩存,再經過硬盤緩存機制去刷新保存到文件。【理論上可能出現數據丟失】
appendfsync everysec 每一秒執行
文件損壞修復
經過 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服務器
配置過程 修改48.134和48.136的redis.conf文件,增長slaveof masterip masterport
slaveof 192.168.48.133 6379
實現原理
複製的方式
配置哨兵
首先咱們準備三臺redis配置好master和slave模式【配置master/slave具體參考上面內容】
192.168.48.133 master
192.168.48.134 slave
192.168.48.136 slave
在134上覆制一份sentinel.conf
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 設置密碼 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分鐘隨機,這樣每個緩存的過時時間的重複率就會下降,就很難引起集體失效的事件。