https://mp.weixin.qq.com/s/kC-YTfnmcIAceYmH51tTTw
一、 緩存redis的用途
高性能 和 高併發
二、redis 的優勢
1、 複雜的數據類型和操作
2、官方支持集羣模式 (sentinel 和 cluster)
3 、純內存
4、單線程模型
(1)單線程能帶來幾個好處:
第一,單線程可以簡化數據結構和算法的實現。
第二,單線程避免了線程切換和競態產生的消耗,對於服務端開發來說,鎖和線程切換通常是性能殺手。
第三: 官方FAQ表示,因爲Redis是基於內存的操作,CPU不是Redis的瓶頸,Redis的瓶頸最有可能是機器內存的大小或者網絡帶寬。
(2)單線程引起的問題:
對於每個命令的執行時間是有要求的。如果某個命令執行過長,會造成其他命令的阻塞,所以 redis 適用於那些需要快速執行的場景。
三 、redis 應用
1)數據類型(特殊操作)
(1)string
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 … 同時設置多個key,value
setnx key value 加鎖
(2)hash -》對象
hset key field value 設置單個field
hget key filed 獲取單個field
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 key value value … 增加元素
llen num 獲得列表的長度
lrange key start stop ; 索引可以是負數, -1表示最右邊的第一個元素
lrem key count value 刪除指定數量和值的元素
lset key index value 設置指定位置的元素
LPOP/RPOP : 取數據
(4)set -》去重 交集 並集 差集
sadd key member [member ...] 增加數據; 如果value已經存在,則會忽略存在的值,並且返回成功加入的元素的數量
srem key member 刪除元素
smembers key 獲得所有數據
sdiff key key … 對多個集合執行差集運算
sunion 對多個集合執行並集操作, 同時存在在兩個集合裏的所有值
(5)sorted set -》排序
zadd key score member 增加元素
zrange key start stop [withscores] 去獲得元素。 withscores是可以獲得元素的分數
如果兩個元素的score是相同的話,那麼根據(0<9<A<Z<a<z) 方式從小到大
2)redis的事務處理
MULTI 去開啓事務
EXEC 去執行事務
3)發佈訂閱
publish channel message
subscribe channel [ …]
4) redis +lua
(1)redis支持調用lua,lua中支持調用redis
(2)原子性
(3) 最經典的redis鎖實現 redission
(4)注意性能問題
四、單線程模型架構解析
非阻塞的io(對服務端來說同時處理多個客戶端不阻塞)
基於 epoll 的 IO 多路複用技術 - reactor模式的文件事件處理器
(1)通俗解釋
參考文章 :https://cloud.tencent.com/developer/article/1403767
(2)redis reactor線程模型圖
(3)中華石杉老師的講解
1)文件事件處理器
redis基於reactor模式開發了網絡事件處理器,這個處理器叫做文件事件處理器,file event handler。這個文件事件處理器,是單線程的,redis才叫做單線程的模型,採用IO多路複用機制同時監聽多個socket,根據socket上的事件來選擇對應的事件處理器來處理這個事件。
如果被監聽的socket準備好執行accept、read、write、close等操作的時候,跟操作對應的文件事件就會產生,這個時候文件事件處理器就會調用之前關聯好的事件處理器來處理這個事件。
文件事件處理器是單線程模式運行的,但是通過IO多路複用機制監聽多個socket,可以實現高性能的網絡通信模型,又可以跟內部其他單線程的模塊進行對接,保證了redis內部的線程模型的簡單性。
文件事件處理器的結構包含4個部分:多個socket,IO多路複用程序,文件事件分派器,事件處理器(命令請求處理器、命令回覆處理器、連接應答處理器,等等)。
多個socket可能併發的產生不同的操作,每個操作對應不同的文件事件,但是IO多路複用程序會監聽多個socket,但是會將socket放入一個隊列中排隊,每次從隊列中取出一個socket給事件分派器,事件分派器把socket給對應的事件處理器。
然後一個socket的事件處理完之後,IO多路複用程序纔會將隊列中的下一個socket給事件分派器。文件事件分派器會根據每個socket當前產生的事件,來選擇對應的事件處理器來處理。
2)文件事件
當socket變得可讀時(比如客戶端對redis執行write操作,或者close操作),或者有新的可以應答的sccket出現時(客戶端對redis執行connect操作),socket就會產生一個AE_READABLE事件。
當socket變得可寫的時候(客戶端對redis執行read操作),socket會產生一個AE_WRITABLE事件
IO多路複用程序可以同時監聽AE_REABLE和AE_WRITABLE兩種事件,要是一個socket同時產生了AE_READABLE和AE_WRITABLE兩種事件,那麼文件事件分派器優先處理AE_REABLE事件,然後纔是AE_WRITABLE事件。
3)文件事件處理器
如果是客戶端要連接redis,那麼會爲socket關聯連接應答處理器
如果是客戶端要寫數據到redis,那麼會爲socket關聯命令請求處理器
如果是客戶端要從redis讀數據,那麼會爲socket關聯命令回覆處理器
4)客戶端與redis通信的一次流程
在redis啓動初始化的時候,redis會將連接應答處理器跟AE_READABLE事件關聯起來,接着如果一個客戶端跟redis發起連接,此時會產生一個AE_READABLE事件,然後由連接應答處理器來處理跟客戶端建立連接,創建客戶端對應的socket,同時將這個socket的AE_READABLE事件跟命令請求處理器關聯起來。
當客戶端向redis發起請求的時候(不管是讀請求還是寫請求,都一樣),首先就會在socket產生一個AE_READABLE事件,然後由對應的命令請求處理器來處理。這個命令請求處理器就會從socket中讀取請求相關數據,然後進行執行和處理。
接着redis這邊準備好了給客戶端的響應數據之後,就會將socket的AE_WRITABLE事件跟命令回覆處理器關聯起來,當客戶端這邊準備好讀取響應數據時,就會在socket上產生一個AE_WRITABLE事件,會由對應的命令回覆處理器來處理,就是將準備好的響應數據寫入socket,供客戶端來讀取。
命令回覆處理器寫完之後,就會刪除這個socket的AE_WRITABLE事件和命令回覆處理器的關聯關係。
五 、 過期策略 + 內存淘汰機制
(1)過期策略
定期刪除+ 惰性刪除
(2)內存淘汰機制
1)六種機制
noeviction:當內存不足以容納新寫入數據時,新寫入操作會報錯,這個一般沒人用吧,實在是太噁心了(默認)
allkeys-lru:當內存不足以容納新寫入數據時,在鍵空間中,移除最近最少使用的key(這個是最常用的)
allkeys-random:當內存不足以容納新寫入數據時,在鍵空間中,隨機移除某個key,這個一般沒人用吧,爲啥要隨機,肯定是把最近最少使用的key給幹掉啊
volatile-lru:當內存不足以容納新寫入數據時,在設置了過期時間的鍵空間中,移除最近最少使用的key
volatile-random:當內存不足以容納新寫入數據時,在設置了過期時間的鍵空間中,隨機移除某個key
volatile-ttl:當內存不足以容納新寫入數據時,在設置了過期時間的鍵空間中,有更早過期時間的key優先移除
2)內存淘汰機制選擇策略參考
https://www.cnblogs.com/changbosha/p/5849982.html
3)LRU 算法
LRU(Least recently used,最近最少使用)算法根據數據的歷史訪問記錄來進行淘汰數據,其核心思想是“如果數據最近被訪問過,那麼將來被訪問的機率也更高”
Java簡單實現
public class LRUCache<K, V> extends LinkedHashMap<K, V> {
private final int CACHE_SIZE;
// 這裏就是傳遞進來最多能緩存多少數據
public LRUCache(int cacheSize) {
super((int) Math.ceil(cacheSize / 0.75) + 1, 0.75f, true); // 這塊就是設置一個hashmap的初始大小,同時最後一個true指的是讓linkedhashmap按照訪問順序來進行排序,最近訪問的放在頭,最老訪問的就在尾
CACHE_SIZE = cacheSize;
}
@Override
protected boolean removeEldestEntry(Map.Entry eldest) {
return size() > CACHE_SIZE; // 這個意思就是說當map中的數據量大於指定的緩存個數的時候,就自動刪除最老的數據,從鏈表尾部刪除
}
}
參考自:中華石杉老師 ,公衆號 石杉架構筆記
總結一共三篇,第二篇後續馬上發出
如果喜歡,歡迎關注我的公衆號:喬志勇筆記