分佈式常見問題(如:分佈式事務,ID,session一致)

一致性

session一致性

tomcat廣播
	優點:簡單容易實現
	缺點
		當服務器多的時候,廣播佔用大量帶寬,而且每臺服務器保存session佔用內存
客戶端存儲
	優點
		服務器不需要存儲
	缺點
		每次請求需要攜帶session,佔用帶寬
		安全問題
		session存儲客戶端,受限於cookie限制
nginx固定分配一臺服務器處理
	4層代理hash,使用ip作爲hash分配服務器
	7層代理hash,使用業務屬性分配,如order_id,user_id等		
	優點:
		只需要修改nginx
		支持水平擴展
	缺點:
		重啓,容易造成session丟失
		當服務器水平擴展後,服務器訪問時重新分配新的主機處理,造成訪問不了session

存儲內存
	找到一個所有服務器都會訪問的地方,如數據庫,redis.可把session存儲在此
	優點:
		存儲服務端,沒安全隱患
		水平擴展
		只要存儲session服務器不重啓,web-server重啓無所謂
	缺點:
		增加一次網絡調用
		修改業務代碼
	建議:redis,因爲sessin作爲一個高訪問的玩意兒,而且丟失也沒事,所以存儲redis.


分佈式事務一致性

二階段提交(2PC)

	引入一個協調者,統一掌控所有參與者,並指示他們是否操作結果提交還是回滾
	分爲2個階段
		投票階段:參與則通知協調者,協調者反饋結果
		提交階段:收到參與者反饋後,協調者再向參與者發出通知,根據反饋情況決定每個參與者到底還是提交還是回滾
	例子:老大BOSS說要喫飯,統計下面員工意見,有員工ABC同意去,但D沒回復,則BOSS依舊處於接受信息狀態,ABC則阻塞.當統計了ABCD員工意見後,如果有一個員工不同意,則通知ABCD不去(回滾),然後ABCD收到後ACK BOSS,這個階段如果BOSS沒收到會一直阻塞
	缺點
		執行過程中,所有節點處於阻塞,所有節點持有資源鎖定狀態,當有一個節點出現問題無法回覆,則會一直阻塞,就需要更復雜的超時機制處理.

補償事務

在業務端執行業務逆向操作事務
	flag1=執行事務一返回結果
	if(flag1){
		flag2=執行事務二返回結果
		if(flag2){
			...執行事務...
		}else{
		回滾事務二
		}
	}else{
		回滾事務一
	}
缺點
	嵌套過多
	不同業務需要寫不同的補償事務
	不具備通用性
	沒有考慮補償事務失敗
		

後置提交優化

一個事務,分別爲執行和提交2階段
執行比提交耗時長

改變事務執行與提交時序,變成事務先執行然後一起提交

執行事務一,提交.執行事務耗時200ms,提交2ms
執行事務二,提交.執行事務耗時200ms,提交2ms
執行事務三,提交.執行事務耗時200ms,提交2ms
當執行事務二~三之間出現異常,數據就不一致,時間範圍爲202+202ms

通過改變時序後:
執行事務一,執行事務耗時200ms
執行事務二,執行事務耗時200ms
執行事務三,執行事務耗時200ms
提交事務一,提交2ms
提交事務二,提交2ms
提交事務三,提交2ms
後置優化後,在提交事務二~三階段纔會出現不一致情況,時間耗時4ms

雖然不能根本解決數據一致問題,但通過縮小時間概率,降低不一致概率

缺點:
	串行執行事務提交後,連接就釋放了,但後置提交優化後,所有庫的連接,需要等待全部事務執行完才能釋放,數據庫連接佔用時間加長,吞吐降低了

增量日誌

確保最終一致性,延遲取決於日誌對比時間
模仿mysql主從複製,binLog,涉及修改添加到日誌,然後間隔多長時間進行正向反向表數據比對.

三階段提交,性能更加比較差.略

TCC(Try、Confirm、Cancel)

最大努力通知

XA

本地消息表(ebay研發出的)

半消息/最終一致性(RocketMQ)

數據庫主從一致性

當A修改主數據庫時,主數據庫在同步讀數據庫階段,如果有B讀取讀數據庫,因爲主數據庫還未同步,所以造成B讀取的是舊值
解決方案
	半同步複製
		執行寫操作後,等主從同步執行完,在返回請求,這個時候讀數據庫讀到的時最新的數據
		優點:
			利用數據庫原生功能,簡單實現
		缺點:
			寫請求時間延長,降低吞吐
	強制讀主庫
		優點:不需要修改
		缺點:需要採用緩存優化數據庫訪問效率
	數據庫中間件
		1:所有讀走從庫,寫走主庫
		2:記錄所有路由到主庫的key
		3:在主從同步時間內,如果有讀請求訪問,則讀主庫
		4:主從同步時間過後,讀請求訪問從庫
		優點:有現成中間件可以使用
		缺點:沒發現啊- - 
	緩存記錄寫key
		key緩存redis,設置超時時間(一般主從同步時間預估值)
		當讀請求,cache hit則說明這個key剛進行寫操作,則讀主庫
		cache miss,說明說明key近期沒有寫操作,則讀從庫
		優點:輕量級實現中間件
		缺點:引入cache,與業務綁定		

數據庫雙從一致性

冗餘寫庫保證高可用
當有2個請求分別寫入AB主庫,這個時候生成的ID都是一樣的,A同步B,B同步A,就會同步失敗,造成數據不一致
解決方案:
	設置不同初始值,以及相同步長
	設置id生成策略,參考10種分佈式id生成
	對外只提供一臺寫庫,另外一臺作爲影子shadow
		存在問題:當A庫同步B時出現異常,KeepAlive虛ip漂移,切換到B庫,在A庫同步成功之前,插入一條數據到B庫,造成數據不一致問題.
	內網DNS探測
		上述存在問題,本質需要同步成功後,在實施虛ip漂移
		寫一個小腳本輪詢探測主庫ip連通性,當連接失敗,則delay一個X秒延時,等主庫B同步成功後,在將B庫ip連通

主從DB與緩存一致性


方法一:
	淘汰緩存
	主從同步期間
	休眠主從同步時間或者新開timer,再次淘汰緩存
	缺點:所有的寫請求阻塞固定時間,降低吞吐,增加處理時間
方法二:
	寫走庫,讀走緩存.另外開啓一個線程讀取庫binlog,實時寫入cache
	缺點:實現比較複雜
方法三:MQ異步淘汰
	淘汰緩存
	寫庫
	發送一條淘汰緩存的異步消息
	直接返回
	缺點:增加一次cache miss
方法四:讀binlog異步淘汰
	取消發送淘汰異步消息步驟
	新增一個線下binlog的異步淘汰線程,讀取binlog數據,異步淘汰緩存數據
方法五:寫請求串行化
	將寫請求更新之前先獲取分佈式鎖,獲得之後才能更新,這樣實現寫請求的串行化,但是會導致效率變低。

庫存扣減一致性

直接設置扣減
缺點:當重試時,導致重複扣減,主要原因是扣減是一個非冪等操作,不能重複執行

直接設置最新庫存即可,是一個冪等操作
缺點:當存在多個線程設置的時候,容易造成覆蓋

數據庫CAS扣減
update store set num=$num_new where id=$id and num=$num_old
缺點:基於CAS的設計,當存在多個線程爭搶的時候只有一個會成功執行,爲了保證後續邏輯,需要增加重試機制,而因爲數據庫比較重量,所以可以採用redis優化


redis原子性扣減
redis緩存數據庫真實庫存,原子扣減.後MQ到數據庫
需要同步到數據庫,增加一次操作,而且當redis宕機,真實的數據還在redis沒有來得及同步數據庫,容易造成數據不一致問題

MQ緩存請求
利用MQ,將請求線性維護,後臺MQ接收端,執行數據庫寫請求.

十種分佈式id生成

需要滿足如下條件

  1. 全局唯一
  2. 高性能,高可用
  3. 趨勢遞增
UUID
	簡單,但id本身看不出特殊意義,且不能遞增,且Mysql明確表示id越短越好,
取當前毫秒數
	id趨勢遞增,id整數數據庫查詢效率高,本地生成即可,不需要遠程調用,但因爲毫秒1000範圍內,所以當併發超過1000,容易重複.
數據庫自增id
	需要一個單獨的mysql實例生成id,查詢快,id遞增,但單點容易宕機,且抗不出併發場景,可使用keepalived+vip做個影子備用,但因此引發新的問題,2個數據庫使用率只有50% =  =
數據庫多主模式
	多主解決了單點宕機風險,但id重複.但是可以設置步長,譬如A數據庫步長爲2,起始爲1.B數據庫步長爲2,起始爲2.但引發了新的問題,擴展集羣后,需要修改步長,而且影響到之前的AB數據庫起始值.
號段模式(發射器)
	從數據庫批量獲取自增id,加載到內存(如redis),然後更改數據庫最新起始值,當內存使用完id後在此獲取.這裏爲了取段的時候出現併發場景,可表多設置一個字段,版本號.CAS修改.
redis
	incr,但需要注意持久化問題.而且redis作爲緩存服務器不應該與具體業務耦合,不推薦.
雪花算法
	正數位+時間戳+機器id+數據中心+自增值,佔用8字節.操作靈活,實現簡單,只需要維護機器id就行了
TinyId(滴滴)
	基於號段
Uidgenerator(百度)
	基於雪花算法,
Leaf(美團)
	同時支持號段模式與雪花算法,

分佈式鎖

數據庫分佈式鎖

	
	樂觀鎖增加版本號
		根據版本號來判斷更新之前有沒有其他線程更新過,如果被更新過,則獲取鎖失敗。
	悲觀鎖
		當想要獲得鎖時,就向數據庫中插入一條記錄,記錄執行方法名(添加唯一約束),釋放鎖時就刪除這條記錄。如果記錄具有唯一索引,就不會同時插入同一條記錄。
		這種方式存在以下幾個問題:
			鎖沒有失效時間,解鎖失敗會導致死鎖,其他線程無法再獲得鎖。
			只能是非阻塞鎖,插入失敗直接就報錯了,無法重試。
			不可重入,同一線程在沒有釋放鎖之前無法再獲得鎖。

redis分佈式鎖

redisson源碼解析點擊這裏

redis實現,借鑑Redlock
獲得鎖:setnx(id,過期時間)
	exec
刪除鎖:del(key)
存在問題:
	超時,但線程還在工作
		開啓一個守護線程監控任務是否完成,沒完成添加過期時間
	無法釋放鎖:設置超時時間
	無法重入:
		本地ThreadLocal計數
		redis hash存儲可重入次數,lub腳本加鎖解鎖
	其他人刪除鎖
		id爲唯一標識,刪除鎖的時候判斷是不是自己加的鎖,不是不刪除
			var value=get lock_name
			if value== 鎖的時候填充的值
				釋放鎖
			else
				釋放鎖失敗
			因爲這個步驟不是原子性,所以需要lua腳本執行判斷和釋放鎖
	死鎖:設置獲取鎖的優先級策略

存儲一對key-value,value爲當前時間+過期毫秒數
當cur_data>K-value時,獲得鎖
官方給出了Redlock算法,大致意思如下:
在分佈式版本的算法裏我們假設我們有N個Redis Master節點,這些節點都是完全獨立的,我們不用任何複製或者其他隱含的分佈式協調算法(如果您採用的是Redis Cluster集羣此方案可能不適用,因爲Redis Cluster是按哈希槽 (hash slot)的方式來分配到不同節點上的,明顯存在分佈式協調算法)。
我們把N設成5,因此我們需要在不同的計算機或者虛擬機上運行5個master節點來保證他們大多數情況下都不會同時宕機。一個客戶端需要做如下操作來獲取鎖:
1、獲取當前時間(單位是毫秒)。
2、輪流用相同的key和隨機值(客戶端的唯一標識)在N個節點上請求鎖,在這一步裏,客戶端在每個master上請求鎖時,會有一個和總的鎖釋放時間相比小的多的超時時間。比如如果鎖自動釋放時間是10秒鐘,那每個節點鎖請求的超時時間可能是5-50毫秒的範圍,這個可以防止一個客戶端在某個宕掉的master節點上阻塞過長時間,如果一個master節點不可用了,我們應該儘快嘗試下一個master節點。
3、客戶端計算第二步中獲取鎖所花的時間,只有當客戶端在大多數master節點上成功獲取了鎖(在這裏是3個),而且總共消耗的時間不超過鎖釋放時間,這個鎖就認爲是獲取成功了。
4、如果鎖獲取成功了,那現在鎖自動釋放時間就是最初的鎖釋放時間減去之前獲取鎖所消耗的時間。
5、如果鎖獲取失敗了,不管是因爲獲取成功的鎖不超過一半(N/2+1)還是因爲總消耗時間超過了鎖釋放時間,客戶端都會到每個master節點上釋放鎖,即便是那些他認爲沒有獲取成功的鎖。
雖然說RedLock算法可以解決單點Redis分佈式鎖的高可用問題,但如果集羣中有節點發生崩潰重啓,還是會出現鎖的安全性問題。具體出現問題的場景如下:
假設一共有A, B, C, D, E,5個Redis節點,設想發生瞭如下的事件序列:
1、客戶端1成功鎖住了A, B, C,獲取鎖成功(但D和E沒有鎖住)
2、節點C崩潰重啓了,但客戶端1在C上加的鎖沒有持久化下來,丟失了
3、節點C重啓後,客戶端2鎖住了C, D, E,獲取鎖成功
這樣,客戶端1和客戶端2同時獲得了鎖(針對同一資源)。針對這樣場景,解決方式也很簡單,也就是讓Redis崩潰後延遲重啓,並且這個延遲時間大於鎖的過期時間就好。這樣等節點重啓後,所有節點上的鎖都已經失效了。也不存在以上出現2個客戶端獲取同一個資源的情況了。
總之用Redis集羣實現分佈式鎖要考慮的特殊情況比較多,尤其是服務器比較多的情況下,需要多測試。

/**
 * 分佈式鎖的簡單實現代碼
 * Created by liuyang on 2017/4/20.
 */
public class DistributedLock {
 
    private final JedisPool jedisPool;
 
    public DistributedLock(JedisPool jedisPool) {
        this.jedisPool = jedisPool;
    }
 
    /**
     * 加鎖
     * @param lockName       鎖的key
     * @param acquireTimeout 獲取超時時間
     * @param timeout        鎖的超時時間
     * @return 鎖標識
     */
    public String lockWithTimeout(String lockName, long acquireTimeout, long timeout) {
        Jedis conn = null;
        String retIdentifier = null;
        try {
            // 獲取連接
            conn = jedisPool.getResource();
            // 隨機生成一個value
            String identifier = UUID.randomUUID().toString();
            // 鎖名,即key值
            String lockKey = "lock:" + lockName;
            // 超時時間,上鎖後超過此時間則自動釋放鎖
            int lockExpire = (int) (timeout / 1000);
 
            // 獲取鎖的超時時間,超過這個時間則放棄獲取鎖
            long end = System.currentTimeMillis() + acquireTimeout;
            while (System.currentTimeMillis() < end) {
                if (conn.setnx(lockKey, identifier) == 1) {
                    conn.expire(lockKey, lockExpire);
                    // 返回value值,用於釋放鎖時間確認
                    retIdentifier = identifier;
                    return retIdentifier;
                }
                // 返回-1代表key沒有設置超時時間,爲key設置一個超時時間
                if (conn.ttl(lockKey) == -1) {
                    conn.expire(lockKey, lockExpire);
                }
 
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
        } catch (JedisException e) {
            e.printStackTrace();
        } finally {
            if (conn != null) {
                conn.close();
            }
        }
        return retIdentifier;
    }
 
    /**
     * 釋放鎖
     * @param lockName   鎖的key
     * @param identifier 釋放鎖的標識
     * @return
     */
    public boolean releaseLock(String lockName, String identifier) {
        Jedis conn = null;
        String lockKey = "lock:" + lockName;
        boolean retFlag = false;
        try {
            conn = jedisPool.getResource();
            while (true) {
                // 監視lock,準備開始事務
                conn.watch(lockKey);
                // 通過前面返回的value值判斷是不是該鎖,若是該鎖,則刪除,釋放鎖
                if (identifier.equals(conn.get(lockKey))) {
                    Transaction transaction = conn.multi();
                    transaction.del(lockKey);
                    List<Object> results = transaction.exec();
                    if (results == null) {
                        continue;
                    }
                    retFlag = true;
                }
                conn.unwatch();
                break;
            }
        } catch (JedisException e) {
            e.printStackTrace();
        } finally {
            if (conn != null) {
                conn.close();
            }
        }
        return retFlag;
    }
}
 

zookeeper分佈式鎖

總結

數據庫鎖:

優點:直接使用數據庫,使用簡單。

缺點:分佈式系統大多數瓶頸都在數據庫,使用數據庫鎖會增加數據庫負擔。

緩存鎖:

優點:性能高,實現起來較爲方便,在允許偶發的鎖失效情況,不影響系統正常使用,建議採用緩存鎖。

缺點:通過鎖超時機制不是十分可靠,當線程獲得鎖後,處理時間過長導致鎖超時,就失效了鎖的作用。

zookeeper鎖:

優點:不依靠超時時間釋放鎖;可靠性高;系統要求高可靠性時,建議採用zookeeper鎖。

缺點:性能比不上緩存鎖,因爲要頻繁的創建節點刪除節點。

性能角度:緩存>zookeeper>數據庫
可靠性:zookeeper>緩存>數據庫

案例

秒殺

圍繞秒殺,6個字,緩存讀,異步寫
緩存越貼近上游,下游服務器壓力就越小
	squid/varnish做緩存代理
	lvs+keepalived++nginx
	CDN加速
	頁面靜態化
	靜態資源nginx代理
	js限制點擊數
	重複點擊,返回同一個頁面
	對同一uid限制請求數和請求速度,以及黑名單攔截
	拋棄請求,服務降級

寫
	比如商品只有2000,那麼只允許2000請求進來,其餘的請求返回已售完.MQ接收端可單個接受MQ,也可以批量拉取MQ請求.
	數據庫層面,如果訂單數量是已知的,可提前生成好2000個訂單數據,直接update訂單和用戶關聯就行了,而不需要insert.這樣也可以避免併發場景數據不一致.
讀
	redis緩存熱點數據
	redis緩存請求數,每次請求一個進來,原子減少請求數,當請求數=0,則拋棄後續所有請求.
數據庫,主從複製,讀寫分離,分庫分表

業務層面處理
下單和支付放2個環節



ABA問題

執行CAS時,假設A查詢庫存爲N,在將N修改爲M途中,有B線程修改了N爲M,C線程修改M爲N,這個時候A利用下列SQL執行依舊會成功,因爲N沒有改變,但是實際上N改變了
update store set num=$num_new where id=$id and num=$num_old

針對這個問題添加一個版本號就行了
update store set num=$num_new where id=$id and version=$version

java原子類也會遇到這個問題,有一個類添加了對版本號的支持

分庫分表

主從複製,解決的是數據庫集羣數據同步問題
讀寫分離,解決的是讀操作效率以及併發量的問題

而分庫分表是在數據庫級別,解決單表單庫數據過量的問題
分庫:不同業務數據表部署在不同數據庫集羣上,垮庫不能join)
分表:一張表拆開分別存儲在多個數據庫中,cobar,amoeba)
數據遷移(利用hash一致算法)
	路由算法
			hash取餘:命中率低至(N/N+1)
			一致性hash算法(一致性hash環):通常使用二叉查找樹實現。命中率高至99%
					缺點:新加入節點隻影響附近最近的節點,其他節點還是負載那麼多
						添加虛擬層,每個物理服務器節點虛擬爲一組緩存服務器,然後根據hash值放置hash環上
							命中高達75%,同時解決資源分配不合理問題
			地理位置
			時間範圍
			數據範圍1-1000 A庫,1001-2000 B庫

單個庫太大
	垂直切分:表多而數據多,根據業務切分成不同的庫。
	水平切分:將單張表的數據切分到多個服務器上去,每個服務器具有相應的庫與表,只是表中數據集合不同。

單個表太大
	垂直拆分,字段多,拆分成多個表這些表還在同個數據庫中
	水平拆分,數據多,按照規則將數據分別存儲多個相同結構表,
分庫分表的順序應該是先垂直分,後水平分。 因爲垂直分更簡單,更符合我們處理現實世界問題的方式。

存在問題
	分佈式事務
	範圍查詢面臨問題
	多庫結果集合並(group by,order by)

冗餘分庫

當訂單數據,用戶要用,商家也要用,這個時候商家分庫,那麼用戶查就需要掃描多次,同理用戶分庫也是
冗餘分庫,解決的是同一個庫,按照不同的維度查詢的問題,涉及的主要問題就是分庫的數據冗餘以及數據一致問題問題

保證數據冗餘
方案一:
	server同步寫到冗餘數據庫
	優點:簡單實現
	缺點:一次寫,改成2.因爲不是原子性,寫入庫1後斷電,2丟數據
方案二:
	MQ同步
	優點:請求時間簡短
	缺點:引入MQ,系統複雜.
		數據同步存在延時
		MQ丟失數據,無法保證數據同步可達
方案三:
	異步任務binlog同步
	優點:解耦,請求時間短
	缺點:存在時間空窗期,但保證了最終一致

保證數據一致
	線下全量掃描,不停對比2個表的數據,不一致則修復
	掃增量
		寫入A庫後,寫入log1,寫入B庫後,寫入log2.線下對比log1和log2,就行了,爲了以防萬一,添加一個全量掃描
	線上檢測消息對
		新增消費者,分別監聽A庫寫入和A1庫寫入完成消息,如果二者接收間隔時間長,則啓動補償修復

redis

事前:Redis 高可用,主從+哨兵,Redis cluster,避免全盤崩潰。
事中:本地 ehcache 緩存 + Hystrix 限流+降級,避免MySQL被打死。
事後:Redis 持久化 RDB+AOF,一旦重啓,自動從磁盤上加載數據,快速恢復緩存數據。


雪崩:緩存失效
	隨機設置過期時間,或者不過期
	雙緩存機制,緩存A的失效時間爲20分鐘,緩存B沒有失效時間,從緩存A讀取數據,緩存A中沒有時,去緩存B中讀取數據,並且啓動一個異步線程來更新緩存A。
	
緩存穿透:訪問沒有緩存的數據,如用戶id-1
	設置校驗
	緩存爲null之內的
	布隆過濾器
	
緩存擊穿:熱點數據不停訪問,當key失效
	加互斥鎖,緩存中沒有熱點key對應的數據時,等待100ms,由獲得鎖的線程去讀取數據庫然後設置緩存。
	key永不過期
	Hash分key,一個key拆分多個key,分佈不同節點,防止單點過熱.
	本地緩存,hashMap,或者ehcache
	
	
redis持久化方式
一:rdb快照持久化
工作原理
每隔[N分鐘][N次]寫操作後,從內存dump數據形成rdb文件[壓縮]放在[備份目錄][]裏面的內容可以配置
二:aof(日誌)
對每條寫入命令作爲日誌,以 append-only 的模式寫入一個日誌文件中,因爲這個模式是隻追加的方式,所以沒有任何磁盤尋址的開銷,所以很快,有點像Mysql中的binlog。


master down之後
哨兵必須用三個實例去保證自己的健壯性的,哨兵+主從並不能保證數據不丟失,但是可以保證集羣的高可用。
一:手工配置
1:指定任意一臺slave設置slaveof no one,表示角色更換爲master
2:設置slave-read-only no:表示可寫
3:其他slave,新的master
	設置:slaveof IP地址 端口
二:使用sentinel監控主服務器
1:cp 源代碼目錄下sentinel.conf 到redis目錄
2:sentinel.conf配置信息
sentinel monitor 定義的名字 ip 端口 次數 :監視那個地址,一定的失效次數沒有連接上,確定失效
sentinel auth-pass 名字跟上面定義的名字一致 012_345:設置密碼
sentinel down-after-milliseconds 名字同上 30000:30秒沒有連接上,失效次數增加1
sentinel can-failover 名字同上 yes:是否允許故障轉移(slave更改爲master),建議只有一臺設置爲yes,因爲監控的sentinel多了之後,配置的yes也多了之後,會導致一臺服務器失效後,一起修改。
sentinel parallel-syncs 名字 數量:當master死機時候,指定新的master時,指定一定的數量slave啓動連接,防止masterio劇增
sentinel failover-timeout 名字 時間:超過時間沒有完成設置,監控失敗。
3:開啓
 ./bin/redis-server --help,開啓server幫助
 開啓哨兵模式
 ./bin/redis-server  ./sentinel.conf --sentinel &
4:master死機後,哨兵隨機指定新的master,手動配置
每一個redis.conf裏面配置slave-priority優先級,數字越小越靠前,也就是越有限指定爲master

過期策略
定期刪除:默認100ms就隨機抽一些設置了過期時間的key,去檢查是否過期,過期了就刪了。
惰性刪除:獲取的時候,判斷是否過期,過期則刪除
如果過期沒刪除,且沒查詢,redis過期數據過多?則涉及內存淘汰機制
	noeviction:返回錯誤當內存限制達到並且客戶端嘗試執行會讓更多內存被使用的命令(大部分的寫入指令,但DEL和幾個例外)
	allkeys-lru: 嘗試回收最少使用的鍵(LRU),使得新添加的數據有空間存放。
	volatile-lru: 嘗試回收最少使用的鍵(LRU),但僅限於在過期集合的鍵,使得新添加的數據有空間存放。
	allkeys-random: 回收隨機的鍵使得新添加的數據有空間存放。
	volatile-random: 回收隨機的鍵使得新添加的數據有空間存放,但僅限於在過期集合的鍵。
	volatile-ttl: 回收在過期集合的鍵,並且優先回收存活時間(TTL)較短的鍵,使得新添加的數據有空間存放。
	如果沒有鍵滿足回收的前提條件的話,策略volatile-lru, volatile-random以及volatile-ttl就和noeviction 差不多了。

MQ

削峯填谷
client根據自身處理能力,間隔一定時間或者每次拉取N條消息處理
達到限速執行目的.批量拉取消息,減輕MQ隊列壓力.

消息必達
MQserver收到消息首先落地
MQserver發送client時,超時重傳
client發送失敗,超時重傳server

重複消費
重複消費就是爲了保證冪等的問題
如insert更改爲update,,update可重複set值,而值是一樣的.如果爲了確定順序,可添加版本號.
發送端的冪等
	發送端發送消息給server時,server內部生成id,全局唯一,且MQ生成與業務無關.
	當發送端沒有收到server消息,重試的時候,server根據id判斷唯一
接收端的冪等
	消息體中存在一個業務場景全局唯一,如訂單號,流水號.接收端根據唯一id判斷重複.
	強校驗
	新增訂單的時候,增加一個流水.下次消息重試,去查詢流水
	弱校驗
	Id+場景唯一id緩存起來,加上過期時間

延時
	輪詢
		啓動一個timer,循環遍歷超時處理
	timer觸發
		針對每個請求生成一個timer監控超時
	時間輪
		還是比較簡單實現的,工作中實現過.就一個環形隊列大小爲3600,
		轉完一圈相當於一個小時.每一秒指針移動到下一個位置.位置中存儲對應的任務和圈數,
		圈數相當於記錄的多少小時之後執行,2圈則2個小時.每次遍歷一圈後,
		減少每個遍歷格子裏面的圈數,然後圈數爲0,則執行格子任務.
		
順序執行
	rocketmq 根據唯一標識hash取模,然後把消息確保投遞到同一條queue

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章