Redis入門手冊

前段時間由於本身再鼓搗zk和dubbo玩了下分佈式的相關內容,想到自己redis也是用來實現分佈式鎖的一種手段,因此心癢之下,整理回顧了redis的相關內容。java

知識點:node

  1. Redis數據結構及命令介紹
  2. Redis實現簡易的分佈式鎖
  3. Lua語言的簡介和入門
  4. Redis中lua腳本的運用
  5. Redis的持久化策略RDB&AOF
  6. Redis集羣
  7. 關於緩存穿透擊穿及失效雪崩的思考和解決方案

學習準備:linux

  1. 虛擬機三臺 Centos7.2
  2. Redis-3.2.11.tar.gz 安裝包一個 具體能夠在以下網址中下載 download.redis.io/releases/

聲明:如下操做都是基於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的安裝安全

  1. 下載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]完成安裝】bash

咱們看看bin下面有些什麼命令

啓動中止redis 先要複製一份conf文件到redis目錄下 cp /data/program/redis-3.2.11/redis.conf ../redis.conf

./redis-server ../redis.conf

修改配置文件使其可以以進程形式start

./redis-cli shutdown 之後臺進程的方式啓動,修改redis.conf daemonize =yes

鏈接到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 服務器配置

多數據庫支持

默認支持16個數據庫;能夠理解爲一個命名空間
跟關係型數據庫不同的點

  1. redis不支持自定義數據庫名稱
  2. 每一個數據庫不能單獨設置受權
  3. 每一個數據庫之間並非徹底隔離的。 能夠經過flushall命令清空redis實例面的全部數據庫中的數據
    經過 select dbid 去選擇不一樣的數據庫命名空間 。 dbid的取值範圍默認是0 -15

使用入門:
1)得到一個符合匹配規則的鍵名列表
keys lulf:allen
keys pattern [? / * /[]]

2)判斷一個鍵是否存在 , EXISTS key
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【超時】

incryby key increment 遞增指定的整數 decr key 原子遞減 append key value 向指定的key追加字符串 strlen key 得到key對應的value的長度 mget key key.. 同時得到多個key的value【建議使用,減小網絡傳輸】 mset key value key value key value … setnx

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 … 一次性得到多個值

hgetall key 得到hash的全部信息,包括key和value
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;

邏輯表達式 加減乘除 +-*/ 關係運算符 == 等於 ~= 不等於

邏輯運算符 and/or not(a and b)

鏈接字符串

計算字符串長度

邏輯控制語句

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

函數【全局scope 局部local】 scope function() return 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這個配置文件去看下系統默認的快照規則

save
當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文件進行優化
壓縮策略

auto-aof-rewrite-percentage 100
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服務器

master和slave數據是一致
默認狀況下slave是隻讀的,slave上的數據沒法同步到master

配置過程 修改48.134和48.136的redis.conf文件,增長slaveof masterip masterport
slaveof 192.168.48.133 6379
實現原理

  1. slave第一次或者重連到master上之後,會向master發送一個SYNC的命令
  2. master收到SYNC的時候,會作兩件事
    a. 執行bgsave(rdb的快照文件)
    b. master會把新收到的修改命令存入到緩衝區
    缺點 沒有辦法對master進行動態選舉【master掛了,就只能讀不能寫了】

在slave作一個監聽,在master中操做redis,咱們能在slave中看到相應信息

複製的方式

  1. 基於rdb文件的複製(第一次鏈接或者重連的時候)
  2. 無硬盤複製

3. 增量複製
PSYNC master run id. offset

  1. 哨兵機制

sentinel

  1. 監控master和salve是否正常運行
  2. 若是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
而後啓動

而後關閉133的master,咱們可以監聽出以下內容:

info中顯示133因爲中斷因此哨兵從新選擇了134做爲master,咱們也能在136中能夠看到這個信息。
此時咱們發現每臺slave的配置文件動態修改了

哨兵也是能夠集羣的。

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
刪除節點
先將節點的數據移動到其餘節點上,而後才能執行刪除

市面上提供了集羣方案

  1. redis shardding 並且jedis客戶端就支持shardding操做 SharddingJedis ; 增長和減小節點的問題; pre shardding 3臺虛擬機 redis 。可是我部署了9個節點 。每一臺部署3個redis增長cpu的利用率 9臺虛擬機單獨拆分到9臺服務器
  2. codis基於redis2.8.13分支開發了一個codis-server
  3. 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分鐘隨機,這樣每個緩存的過時時間的重複率就會下降,就很難引起集體失效的事件。

相關文章
相關標籤/搜索