一、 慢查詢原因分析
與mysql一樣:當執行時間超過閥值,會將發生時間耗時的命令記錄
redis命令生命周期:發送 排隊 執行 返回
慢查詢只統計第3個執行步驟的時間
預設閥值:兩種方式,默認為10毫秒
1,動態設置6379:> config set slowlog-log-slower-than 10000 //10毫秒10000微秒
使用config set完后,若想將配置持久化保存到redis.conf,要執行config rewrite
2,redis.conf修改:找到slowlog-log-slower-than 10000 ,修改保存即可
注意:slowlog-log-slower-than =0記錄所有命令 -1命令都不記錄
原理:慢查詢記錄也是存在隊列里的,slow-max-len 存放的記錄最大條數,比如設置的slow-max-len=10,當有第11條慢查詢命令插入時,隊列的第一條命令就會出列,第11條入列到慢查詢隊列中, 可以config set動態設置,也可以修改redis.conf完成配置。
2 命令:
獲取隊列里慢查詢的命令:slowlog get 查詢返回的結構如下
獲取慢查詢列表當前的長度:slowlog len //返回7
對慢查詢列表清理(重置):slowlog reset //再查slowlog len 此時返回0 清空
對於線上slow-max-len配置的建議:線上可加大slow-max-len的值,記錄慢查詢存長命令時redis會做截斷,不會占用大量內存,線上可設置1000以上
對於線上slowlog-log-slower-than配置的建議:默認為10毫秒,根據redis並發量來調整,對於高並發比建議為1毫秒
注意:
1,慢查詢只記錄命令在redis的執行時間,不包括排隊、網絡傳輸時間
2,慢查詢是先進先出的隊列,訪問日志記錄出列丟失,需定期執行slow get,將結果存儲到其它設備中(如mysql)
二、redis-cli詳解
./redis-cli -r 3 -h 192.168.1.111 -a 12345678 ping //返回pong表示127.0.0.1:6379能通,r代表次數
./redis-cli -r 100 -i 1 info |grep used_memory_human //每秒輸出內存使用量,輸100次,i代表執行的時間間隔
./redis-cli -p 6379 -h 192.168.1.111 -a 12345678
對於我們來說,這些常用指令以上可滿足,但如果要了解更多
執行./redis-cli --help, 可百度
三、 redis-server詳解
./redis-server ./redis.conf & //指定配置文件啟動
./redis-server --test-memory 1024 //檢測操作系統能否提供1G內存給redis, 常用於測試,想快速占滿機器內存做極端條件的測試,可使用這個指令
redis上線前,做一次測試
四、redis-benchmark:基准性測試,測試redis的性能
100個客戶端同時請求redis,共執行10000次,會對各類數據結構的命令進行測試:
./redis-benchmark -h 127.0.0.1 -c 100 -n 10000 //100個並發連接,100000個請求,檢測host為localhost 端口為6379的redis服務器性能
測試存取大小為100字節的數據包的性能:
./redis-benchmark -h 127.0.0.1 -p 6379 -q -d 100
只測試 set,lpush操作的性能,-q只顯示每秒鍾能處理多少請求數結果:
./redis-benchmark -h 127.0.0.1 -t set,lpush -n 100000 -q
只測試某些數值存取的性能, 比如說我在慢查詢中發現,大部分為set語句比較慢,我們自己可以測一下Set是不是真的慢:
./redis-benchmark -h 192.168.1.111 -n 100000 -q script load "redis.call('set','foo','bar')"
五、Pipeline(管道)
1.背景:
沒有pipeline之前,一般的redis命令的執行過程都是:發送命令-〉命令排隊-〉命令執行-〉返回結果。
多條命令的時候就會產生更多的網絡開銷
這個時候需要pipeline來解決這個問題:使用pipeline來打包執行N條命令,這樣的話就只需簡歷一次網絡連接,網絡開銷就少了
2. 使用pipeline和未使用pipeline的性能對比:
使用Pipeline執行速度與逐條執行要快,特別是客戶端與服務端的網絡延遲越大,性能體能越明顯
3.原生批命令(mset, mget)與Pipeline對比
1) 原生批命令是原子性,pipeline是非原子性, (原子性概念:一個事務是一個不可分割的最小工作單位,要么都成功要么都失敗。原子操作是指你的一個業務邏輯必須是不可拆分的. 處理一件事情要么都成功要么都失敗,其實也引用了生物里概念,分子-〉原子,原子不可拆分)
2) 原生批命令一命令多個key, 但pipeline支持多命令(存在事務),非原子性
3) 原生批命令是服務端實現,而pipeline需要服務端與客戶端共同完成
4. pipeline正確使用方式:
使用pipeline組裝的命令個數不能太多,不然數據量過大,增加客戶端的等待時間,還可能造成網絡阻塞,可以將大量命令的拆分多個小的pipeline命令完成
如:有300個命令需要執行,可以拆分成每30個一個pipeline執行
六、redis事務
pipeline是多條命令的組合,為了保證它的原子性,redis提供了簡單的事務;redis的事物與mysql事物的最大區別是redis事物不支持事物回滾
事務:事務是指一組動作的執行,這一組動作要么都成功,要么都失敗。
1. redis的簡單事務,將一組需要一起執行的命令放到multi和exec兩個命令之間,其中multi代表事務開始,exec代表事務結束
2.停止事務discard
3. 命令錯誤,語法不正確,導致事務不能正常執行,即事物的原子性
4. watch命令:使用watch后, multi失效,事務失效,其他的線程任然可以對值進行修改
在客戶端1設置值使用watch監聽key並使用multi開啟事物,在客戶端2追加完c之后再來客戶端1追加redis,然后執行事物,可以看到在客戶端1追加的redis沒有起效果:
客戶端1:
客戶端2:
七、LUA語言與Redis配合使用
1. LUA腳本語言是C開發的,類似存儲過程
1).減少網絡開銷:本來5次網絡請求的操作,可以用一個請求完成,原先5次請求的邏輯放在redis服務器上完成。使用腳本,減少了網絡往返時延。
2).原子操作:Redis會將整個腳本作為一個整體執行,中間不會被其他命令插入。
3).復用:客戶端發送的腳本會永久存儲在Redis中,意味着其他客戶端可以復用這一腳本而不需要使用代碼完成同樣的邏輯
語法:
eval+腳本+KEYS[1]+鍵個數+鍵——》eval script numkeys key [key ...]
eval "return redis.call('get',KEYS[1])" 1 name
語法示例1:
1 local int sum = 0 2 local int i =0 3 while i <= 100 4 do sum = sum+i 5 i = i+1 6 end 7 print(sum)
語法示例2:
1 local tables myArray={“james”,”java”,false,34} //定義 2 local int sum = 0 3 print(myArray[3]) //返回false 4 for i = 1,100 5 do 6 sum = sum+1 7 end 8 print(sum) 9 for j = 1,#myArray //遍歷數組 10 do 11 print(myArray[j]) 12 if myArray[j] == “james” 13 then 14 print(“true”) 15 break 16 else 17 print(“false”) 18 end 19 end
2. 案例:
實現訪問頻率限制: 實現訪問者 $ip 127.0.0.1在一定的時間 $time 20S內只能訪問 $limit 10次.
a)使用JAVA語言實現:
1 private boolean accessLimit(String ip, int limit, 2 int time, Jedis jedis) { 3 boolean result = true; 4 5 String key = "rate.limit:" + ip; 6 if (jedis.exists(key)) { 7 long afterValue = jedis.incr(key); 8 if (afterValue > limit) { 9 result = false; 10 } 11 } else { 12 Transaction transaction = jedis.multi(); 13 transaction.incr(key); 14 transaction.expire(key, time); 15 transaction.exec(); 16 } 17 return result; 18 }
以上代碼有兩點缺陷 :
可能會出現競態條件: 解決方法是用 WATCH 監控 rate.limit:$IP 的變動, 但較為麻煩;
以上代碼在不使用 pipeline 的情況下最多需要向Redis請求5條指令, 傳輸過多.
b)使用lua腳本來處理,包括了原子性
lua腳本:
ipAccessCount.lua
1 local key = KEYS[1] 2 local limit = tonumber(ARGV[1]) 3 local expire_time = ARGV[2] 4 5 local is_exists = redis.call("EXISTS", key) 6 if is_exists == 1 then 7 if redis.call("INCR", key) > limit then 8 return 0 9 else 10 return 1 11 end 12 else 13 redis.call("SET", key, 1) 14 redis.call("EXPIRE", key, expire_time) 15 return 1 16 end
使用redis命令獲取lua腳本來執行:
./redis-cli -p 6379 -a 12345678 --eval ipAccessCount.lua rate.limit:127.0.0.1, 10 20
和lua腳本的對應關系為: keys[1] = rate.limit:127.0.0.1 argv[1]=10次, argv[2]=20S失效
執行邏輯:使用redis-cli --eavl時,客戶端把lua腳本字符串發給redis服務端,將結果返回客戶端,如下圖
3. redis對Lua腳本的管理
1.將Lua腳本加載到redis中,得到 返回的sha1值(類似於我們說的數字簽名):afe90689cdeec602e374ebad421e3911022f47c0
redis-cli -h 192.168.1.111 -a 12345678 script load "$(cat random.lua)"
2.檢查腳本加載是否成功,返回1 已加載成功
script exists afe90689cdeec602e374ebad421e3911022f47c0
3.清空Lua腳本內容
script flush
4.殺掉正在執行的Lua腳本
script kill
八、發布與訂閱
redis提供了“發布、訂閱”模式的消息機制,其中消息訂閱者與發布者不直接通信,發布者向指定的頻道(channel)發布消息,訂閱該頻道的每個客戶端都可以接收到消息
redis主要提供發布消息、訂閱頻道、取消訂閱以及按照模式訂閱和取消訂閱
1.發布消息
publish channel:test "hello world"
2.訂閱消息
subscrible channel:test
此時另一個客戶端發布一個消息:publish channel:test "james test"
當前訂閱者客戶端會收到如下消息:
和很多專業的消息隊列(kafka rabbitmq),redis的發布訂閱顯得很lower, 比如無法實現消息規程和回溯, 但就是簡單,如果能滿足應用場景,用這個也可以
3.查看訂閱數:
pubsub numsub channel:test // 頻道channel:test的訂閱數
4.取消訂閱
unsubscribe channel:test
客戶端可以通過unsubscribe命令取消對指定頻道的訂閱,取消后,不會再收到該頻道的消息
5.按模式訂閱和取消訂閱
psubscribe ch* //訂閱以ch開頭的所有頻道
punsubscribe ch* //取消以ch開頭的所有頻道
6.應用場景:
1、今日頭條訂閱號、微信訂閱公眾號、新浪微博關注、郵件訂閱系統
2、即時通信系統
3、群聊部落系統(微信群)