原文:https://blog.csdn.net/cmqwan/article/details/97128715
Redis
和memcache的區別
數據結構
內存使用率,key-value的話memcache更好
效率,單個value的大小100k以上redis更好
集羣部署,redis有原生支持
爲什麼單線程能有很高的效率
具體原因
單線程模型,避免了上下文切換
IO多路複用機制
純內存操作
連接過程
文件事件處理器(網絡事件處理器、file event handler),這個是單線程的採用IO多路複用機制監聽多個socket。
socket進來之後,如果有事件(比如說連接),IO多路複用程序就會將這個socket(這時候連接已經和connect事件綁定) 推到消息隊列中。
文件事件分派器從隊列中取出socket,檢查事件,根據不同的事件分給不同的處理器。
包括多個socket,io多路複用程序,消息隊列,文件事件分派器,事件處理器(命令請求處理器、命令回覆處理器、連接應答處理器,等等)
一次連接流程
服務端打開socket監聽
客戶端和服務端連接socket,這時候產生一個connect事件,後臺表示爲AE_READABLE
io多路複用程序將這個socket推到消息隊列裏面
分派器判斷是 連接應答處理器,進行處理,連接成功
這時候將socket和ae_writeable綁定,
io多路複用程序看到這個又有事件了,就又推到消息隊列
分派器判斷是 命令回覆處理器,就返回數據,然後和這個事件取消關聯
多個socket,io多路複用程序,消息隊列,文件事件分派器,事件處理器(命令請求處理器、命令回覆處理器、連接應答處理器,等等)
哪些類型
string
字符串
set key val,get key
簡單的key-value存儲,部門組織樹,用戶數據
list
數組
lpush key val1 val2,lpop key,lrange key 0 10
存粉絲、評論、lrange可以分頁,消息隊列
hash
鍵值對
hmset key key1 val1 key2 val2,hget key key1
用戶信息-鑑權碼-私鑰
set
不重複無序列表
sadd key val1 val2,smembers key
部門關係緩存
sort set
有序數組/帶權重值列表
zadd key score1 val1 score2 val2,zrangebyscore key
排行榜
從海量數據中查找某個key前綴
keys
keys pattern,
一次性返回全部滿足條件數據,會阻塞redis
scan
scan cursor pattern count,
按pattern條件從下標cursor開始找count個數據,不一定會是count,大致相等。返回結果包括下一個遊標位置和列表
持久化
持久化的意義
故障恢復
雲備份到一個存儲上
rdb
內存快照的形式
RDB方式,sava 600 10,600秒內有10次寫操作,則觸發。
將數據快照保存,有可能丟失數據。
優點:適合做冷備份、性能(不需要每時每刻),恢復快
缺點:丟數據
aof
把所有操作指令保存下來,存到一個文件中
內存和文件中有一層os-cache,每隔1s會調用f-sync
一次只會寫一個aof文件
aof文件不可能無限增大,BG-REWRITE-AOF。會根據當前快照,進行重寫aof文件
優點:數據丟少(1s),append-only模式寫磁盤-速度快,記錄是人可讀的
缺點:佔用磁盤大,qps寫會降低,脆弱點,數據恢復比較慢
序列化方式
JdkSerializationRedisSerializer
使用JDK提供的序列化功能。 優點是反序列化時不需要提供類型信息(class),但缺點是需要實現Serializable接口,還有序列化後的結果非常龐大,是JSON格式的5倍左右,這樣就會消耗redis服務器的大量內存。
GenericJackson2JsonRedisSerializer
StringRedisSerializer
不能序列化Bean,只能序列化字符串類型的數據,
如果value都是字符串類型,可以用該方式序列化
GenericFastJsonRedisSerializer
數據過期/淘汰
這個是緩存,有容量限制
過期之後,還是佔用內存
過期策略
設置了過期時間的key什麼時候刪除?定期刪除和惰性刪除,
這2個結合起來還是有可能漏掉一些key,這時候就需要內存淘汰機制登場
定期刪除
每隔100ms隨機抽去一些設置了超時時間的key,檢查是否過期
過期則刪除
這個會導致有可能一些key已經過期,但是沒有刪掉
惰性刪除
查詢某個key的時候,惰性檢查,是否過期
如果過期則返回空
內存淘汰機制
redis內存佔用過多的時候,會進行內存淘汰
具體策略
noeviction,報錯
allkeys-lru,所有key走lru算法
allkeys-random,所有key走隨機刪除
volatile-lru,設置過期時間走lru算法
volatile-random,設置過期時間的key走隨機刪除
volatile-ttl,設置過期時間的key走"按過期時間最短"的算法
LRU代碼實現
鏈表+hashmap
add、remove、refresh用來操作鏈表
get、put用來提供api
package com.lizhaoblog.code.io.redis;
import java.util.HashMap;
class Node {
public Node(String key, String value) {
this.key = key;
this.value = value;
}
public Node pre;
public Node next;
public String key;
public String value;
}
public class LRUCache {
private Node head;
private Node end;
//緩存上限
private int limit;
private HashMap<String,Node> map;
public LRUCache(int limit) {
this.limit = limit;
map = new HashMap();
}
public String get(String key) {
Node node = map.get(key);
if (node == null) {
return null;
}
//調整node到尾部
refreshNode(node);
return node.value;
}
public void put(String key, String value) {
Node node = map.get(key);
if (node == null) {
//key不存在直接插入
while (map.size() >= limit) {
//去除鏈表內的節點
String oldKey = removeNode(head);
//去除map中的緩存
map.remove(oldKey);
}
node = new Node(key, value);
//鏈表中加入節點
addNode(node);
//map中加入節點
map.put(key, node);
} else {
//更新節點並調整到尾部
node.value = value;
refreshNode(node);
}
}
private void refreshNode(Node node) {
//如果訪問的是尾節點,無須移動節點
if (node == end) {
return;
}
//把節點移動到尾部,相當於做一次刪除插入操作
removeNode(node);
addNode(node);
}
private String removeNode(Node node) {
//尾節點
if (node == end) {
end = end.pre;
} else if (node == head) {
//頭結點
head = head.next;
} else {
//中間節點
node.pre.next = node.next;
node.next.pre = node.pre;
}
return node.key;
}
private void addNode(Node node) {
if (end != null) {
end.next = node;
node.pre = end;
node.next = null;
}
end = node;
if (head == null) {
head = node;
}
}
}