成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

緩存一致:讀多寫(xiě)少時(shí),如何解決數(shù)據(jù)更新緩存不同步?

開(kāi)發(fā) 前端
臨時(shí)緩存是有 TTL 的,如果 60 秒內(nèi)修改了用戶(hù)的昵稱(chēng),緩存是不會(huì)馬上更新的。最糟糕的情況是在 60 秒后才會(huì)刷新這個(gè)用戶(hù)的昵稱(chēng)緩存,顯然這會(huì)給系統(tǒng)帶來(lái)一些不必要的麻煩。其實(shí)對(duì)于這種緩存數(shù)據(jù)刷新,可以分成幾種情況,不同情況的刷新方式有所不同,接下來(lái)我給你分別講講。

我們之前提到過(guò),互聯(lián)網(wǎng)大多數(shù)業(yè)務(wù)場(chǎng)景的數(shù)據(jù)都屬于讀多寫(xiě)少,在請(qǐng)求的讀寫(xiě)比例中,寫(xiě)的比例會(huì)達(dá)到百分之一,甚至千分之一。而對(duì)于用戶(hù)中心的業(yè)務(wù)來(lái)說(shuō),這個(gè)比例會(huì)更大一些,畢竟用戶(hù)不會(huì)頻繁地更新自己的信息和密碼,所以這種讀多寫(xiě)少的場(chǎng)景特別適合做讀取緩存。通過(guò)緩存可以大大降低系統(tǒng)數(shù)據(jù)層的查詢(xún)壓力,擁有更好的并發(fā)查詢(xún)性能。但是,使用緩存后往往會(huì)碰到更新不同步的問(wèn)題,下面我們具體看一看。

緩存性?xún)r(jià)比

是的,緩存的確有可能被濫用,特別是在像用戶(hù)中心這樣對(duì)數(shù)據(jù)準(zhǔn)確性要求很高的場(chǎng)景中。你提到在對(duì)用戶(hù)中心進(jìn)行優(yōu)化時(shí),首要想到的就是將用戶(hù)信息放入緩存,以提高性能。這確實(shí)是一個(gè)常見(jiàn)的優(yōu)化思路,因?yàn)榫彺婺軌蝻@著減少數(shù)據(jù)庫(kù)的訪(fǎng)問(wèn)頻率,提升系統(tǒng)響應(yīng)速度。

# 表結(jié)構(gòu)
CREATE TABLE `accounts` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`account` varchar(15) NOT NULL DEFAULT '',
`password` char(32) NOT NULL,
`salt` char(16) NOT NULL,
`status` tinyint(3) NOT NULL DEFAULT '0'
`update_time` int(10) NOT NULL DEFAULT '0',
`create_time` int(10) NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`),
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;


# 登錄查詢(xún)
select id, account, update_time from accounts 
where account = 'user1'
and password = '6b9260b1e02041a665d4e4a5117cfe16'
and status = 1

確實(shí),這是一個(gè)簡(jiǎn)單的查詢(xún)需求。乍一看,似乎將 2000 萬(wàn)條用戶(hù)數(shù)據(jù)都放入緩存可以極大地提升性能,但實(shí)際上并不完全如此。雖然緩存能提供高性能的服務(wù),但其性?xún)r(jià)比并不一定高。這個(gè)表主要用于賬號(hào)登錄的查詢(xún),而登錄操作本身即使頻繁,也不會(huì)對(duì)系統(tǒng)帶來(lái)巨大的流量壓力。因此,即便將所有用戶(hù)數(shù)據(jù)放入緩存,大部分時(shí)間這些數(shù)據(jù)都處于閑置狀態(tài)。這樣一來(lái),緩存資源反而被浪費(fèi),我們也不必要將并發(fā)量不高的數(shù)據(jù)緩存起來(lái),從而增加預(yù)算開(kāi)銷(xiāo)。

這就引出一個(gè)核心問(wèn)題:緩存的使用需要考慮性?xún)r(jià)比。如果花費(fèi)大量時(shí)間和資源將某些數(shù)據(jù)放入緩存,但對(duì)系統(tǒng)性能并沒(méi)有顯著的提升,甚至增加了額外的成本,那么這樣的緩存策略就是不合理的。緩存的效果需要經(jīng)過(guò)評(píng)估,通常來(lái)說(shuō),只有熱點(diǎn)數(shù)據(jù)才值得放入緩存。

臨時(shí)熱緩存

在推翻了將所有賬號(hào)信息都放入緩存的方案后,我們將目標(biāo)轉(zhuǎn)向那些被頻繁查詢(xún)的信息上,比如用戶(hù)信息。用戶(hù)信息的使用頻率非常高,尤其是在論壇等場(chǎng)景中,常常需要頻繁展示,例如用戶(hù)的頭像、昵稱(chēng)和性別等。不過(guò),由于這些數(shù)據(jù)量較大,全部緩存起來(lái)不僅浪費(fèi)空間,還不具備性?xún)r(jià)比。

針對(duì)這種情況,我們可以考慮使用一種臨時(shí)緩存的策略:當(dāng)某個(gè)用戶(hù)信息首次被訪(fǎng)問(wèn)時(shí),將其存入緩存;在短時(shí)間內(nèi),若有類(lèi)似查詢(xún)請(qǐng)求,就可以直接從緩存中獲取。這樣既可以有效地降低數(shù)據(jù)庫(kù)查詢(xún)壓力,又不會(huì)占用過(guò)多的緩存空間。以下是一個(gè)常用的實(shí)現(xiàn)臨時(shí)緩存的代碼示例:

# 示例代碼
def get_user_info(user_id):
# 首先嘗試從緩存中獲取用戶(hù)信息
    user_info = cache.get(user_id)
if user_info:
return user_info


# 如果緩存中沒(méi)有,查詢(xún)數(shù)據(jù)庫(kù)
    user_info = db.query_user_info(user_id)


# 將查詢(xún)到的信息存入緩存,并設(shè)置一個(gè)合理的過(guò)期時(shí)間
    cache.set(user_id, user_info, timeout=300)  # 緩存五分鐘
return user_info

正如我們看到的,這種策略將數(shù)據(jù)臨時(shí)放入緩存,在 60 秒過(guò)期后自動(dòng)淘汰。如果在這段時(shí)間內(nèi)再次查詢(xún)相同數(shù)據(jù),我們的代碼會(huì)重新將數(shù)據(jù)填入緩存,繼續(xù)提供使用。這種臨時(shí)緩存策略非常適合數(shù)據(jù)量大但熱點(diǎn)數(shù)據(jù)較少的場(chǎng)景,有助于緩解數(shù)據(jù)庫(kù)的查詢(xún)壓力。

設(shè)置緩存的 TTL(Time-to-Live)是為了更有效地利用內(nèi)存資源。當(dāng)數(shù)據(jù)在指定時(shí)間內(nèi)未被再次訪(fǎng)問(wèn),就會(huì)被自動(dòng)清除,這樣我們就能避免購(gòu)買(mǎi)過(guò)多內(nèi)存。通過(guò)這種方式,可以在節(jié)省成本的同時(shí),提高緩存的性?xún)r(jià)比,且實(shí)現(xiàn)起來(lái)簡(jiǎn)單,維護(hù)也方便,是一種很常用的策略

緩存更新不及時(shí)問(wèn)題

臨時(shí)緩存是有 TTL 的,如果 60 秒內(nèi)修改了用戶(hù)的昵稱(chēng),緩存是不會(huì)馬上更新的。最糟糕的情況是在 60 秒后才會(huì)刷新這個(gè)用戶(hù)的昵稱(chēng)緩存,顯然這會(huì)給系統(tǒng)帶來(lái)一些不必要的麻煩。其實(shí)對(duì)于這種緩存數(shù)據(jù)刷新,可以分成幾種情況,不同情況的刷新方式有所不同,接下來(lái)我給你分別講講。

1. 單條實(shí)體數(shù)據(jù)緩存刷新

單條實(shí)體數(shù)據(jù)緩存更新是最簡(jiǎn)單的一個(gè)方式,比如我們緩存了 9527 這個(gè)用戶(hù)的 info 信息,當(dāng)我們對(duì)這條數(shù)據(jù)做了修改,我們就可以在數(shù)據(jù)更新時(shí)同步更新對(duì)應(yīng)的數(shù)據(jù)緩存:

Type UserInfo struct {
  Id         int    `gorm:"column:id;type:int(11);primary_key;AUTO_INCREMENT" json:"id"`
  Uid        int    `gorm:"column:uid;type:int(4);NOT NULL" json:"uid"`
  NickName   string `gorm:"column:nickname;type:varchar(32) unsigned;NOT NULL" json:"nickname"`
  Status     int16  `gorm:"column:status;type:tinyint(4);default:1;NOT NULL" json:"status"`
  CreateTime int64  `gorm:"column:create_time;type:bigint(11);NOT NULL" json:"create_time"`
  UpdateTime int64  `gorm:"column:update_time;type:bigint(11);NOT NULL" json:"update_time"`
}


//更新用戶(hù)昵稱(chēng)
func (m *UserInfo)UpdateUserNickname(ctx context.Context, name string, uid int) (bool, int64, error) {
//先更新數(shù)據(jù)庫(kù)
  ret, err := m.db.UpdateUserNickNameById(ctx, uid, name)
if ret {
//然后清理緩存,讓下次讀取時(shí)刷新緩存,防止并發(fā)修改導(dǎo)致臨時(shí)數(shù)據(jù)進(jìn)入緩存
//這個(gè)方式刷新較快,使用很方便,維護(hù)成本低
    Redis.Del("user_info_" + strconv.Itoa(uid))
  }
return ret, count, err
}

總體來(lái)說(shuō),我們可以先識(shí)別出被修改的數(shù)據(jù) ID,然后根據(jù)這些 ID 刪除相應(yīng)的數(shù)據(jù)緩存。在下次請(qǐng)求到來(lái)時(shí),系統(tǒng)會(huì)重新獲取最新的數(shù)據(jù)并更新到緩存中,這樣可以有效減少并發(fā)操作將臟數(shù)據(jù)寫(xiě)入緩存的可能性。

除了這種方法,我們還可以向隊(duì)列發(fā)送更新消息,讓子系統(tǒng)處理更新,或者開(kāi)發(fā)中間件,將數(shù)據(jù)操作發(fā)送到子系統(tǒng),讓其自行決定需要更新的數(shù)據(jù)范圍。然而,通過(guò)隊(duì)列更新消息時(shí),我們可能會(huì)遇到一個(gè)問(wèn)題——條件批量更新時(shí),可能無(wú)法直接確定具體有多少個(gè) ID 發(fā)生了變化。常見(jiàn)的解決方法是:首先按照相同的條件查詢(xún)出所有受影響的 ID,然后執(zhí)行更新操作,最后使用這些相關(guān)的 ID 更新具體的緩存。

2. 關(guān)系型和統(tǒng)計(jì)型數(shù)據(jù)緩存刷新

首先,有一種人工維護(hù)緩存的方式。眾所周知,關(guān)系型數(shù)據(jù)或統(tǒng)計(jì)結(jié)果的緩存刷新具有一定的難度,主要原因在于這些統(tǒng)計(jì)數(shù)據(jù)通常是基于多條數(shù)據(jù)計(jì)算得出的。當(dāng)我們需要刷新這類(lèi)數(shù)據(jù)的緩存時(shí),很難準(zhǔn)確識(shí)別出需要更新的關(guān)聯(lián)緩存。

為了解決這個(gè)問(wèn)題,可以通過(guò)人工方式,在集中管理的地方記錄或定義特定的刷新邏輯,以實(shí)現(xiàn)關(guān)聯(lián)緩存的更新。

圖片圖片

不過(guò)這種方式比較精細(xì),如果刷新緩存很多,那么緩存更新會(huì)比較慢,并且存在延遲。而且人工書(shū)寫(xiě)還需要考慮如何查找到新增數(shù)據(jù)關(guān)聯(lián)的所有 ID,因?yàn)樾略鰯?shù)據(jù)沒(méi)有登記在 ID 內(nèi),人工編碼維護(hù)會(huì)很麻煩。除了人工維護(hù)緩存外,還有一種方式就是通過(guò)訂閱數(shù)據(jù)庫(kù)來(lái)找到 ID 數(shù)據(jù)變化。如下圖,我們可以使用 Maxwell 或 Canal,對(duì) MySQL 的更新進(jìn)行監(jiān)控。

圖片圖片

在這種方案中,變更信息會(huì)被推送到 Kafka。我們可以根據(jù)表名和具體的 SQL 確認(rèn)哪些數(shù)據(jù) ID 發(fā)生了更新,然后依據(jù)腳本中設(shè)定的邏輯,對(duì)相關(guān)緩存 key 進(jìn)行更新。比如,當(dāng)用戶(hù)更新了昵稱(chēng),緩存更新服務(wù)就能夠識(shí)別需要更新 user_info_9527 這個(gè)緩存,同時(shí)根據(jù)配置找到并刪除其他相關(guān)的緩存。這種方法的優(yōu)勢(shì)在于,可以快速地更新簡(jiǎn)單的緩存,并且核心系統(tǒng)可以向子系統(tǒng)廣播數(shù)據(jù)變更信息,代碼實(shí)現(xiàn)也相對(duì)簡(jiǎn)單。不過(guò),對(duì)于復(fù)雜的關(guān)聯(lián)關(guān)系刷新,仍然需要人工書(shū)寫(xiě)邏輯來(lái)實(shí)現(xiàn)。

如果表內(nèi)數(shù)據(jù)更新較少,還可以考慮使用版本號(hào)緩存策略。這種方法比較直接:一旦有任何更新,表中所有數(shù)據(jù)緩存都會(huì)過(guò)期。例如,可以為 user_info 表設(shè)置一個(gè)版本號(hào) key,比如 user_info_version。當(dāng)表數(shù)據(jù)發(fā)生更新時(shí),直接將 user_info_version 自增 1。寫(xiě)入緩存時(shí),同時(shí)記錄當(dāng)前版本號(hào);讀取時(shí),業(yè)務(wù)邏輯會(huì)檢查緩存版本號(hào)與表版本號(hào)是否一致。如果不一致,就更新緩存數(shù)據(jù)。需要注意的是,如果版本號(hào)頻繁更新,緩存命中率會(huì)大幅下降,因此該方法更適合數(shù)據(jù)更新不頻繁的表

當(dāng)然,我們還可以對(duì)這個(gè)表做一個(gè)范圍拆分,比如按 ID 范圍分塊拆分出多個(gè) version,通過(guò)這樣的方式來(lái)減少緩存刷新的范圍和頻率。

圖片圖片

此外,關(guān)聯(lián)型數(shù)據(jù)更新還可以通過(guò)識(shí)別主要實(shí)體 ID 來(lái)刷新緩存。這要保證其他緩存保存的 key 也是主要實(shí)體 ID,這樣當(dāng)某一條關(guān)聯(lián)數(shù)據(jù)發(fā)生變化時(shí),就可以根據(jù)主要實(shí)體 ID 對(duì)所有緩存進(jìn)行刷新。這個(gè)方式的缺點(diǎn)是,我們的緩存要能夠根據(jù)修改的數(shù)據(jù)反向找到它關(guān)聯(lián)的主體 ID 才行。

圖片圖片

最后,還有一種方法是通過(guò)異步腳本遍歷數(shù)據(jù)庫(kù)來(lái)刷新所有相關(guān)緩存。這種方式適用于在兩個(gè)系統(tǒng)之間進(jìn)行數(shù)據(jù)同步,能夠減少系統(tǒng)之間的接口交互頻率。其缺點(diǎn)是,在數(shù)據(jù)被刪除后,還需要手動(dòng)刪除相應(yīng)的緩存,因此更新存在一定延遲。不過(guò),如果結(jié)合訂閱更新消息廣播機(jī)制,這種方案可以實(shí)現(xiàn)近乎同步的數(shù)據(jù)更新。

長(zhǎng)期熱數(shù)據(jù)緩存

回過(guò)頭來(lái)看之前提到的臨時(shí)緩存方案,雖然它能解決大部分問(wèn)題,但有個(gè)潛在風(fēng)險(xiǎn)需要考慮:當(dāng) TTL 到期時(shí),如果有大量緩存請(qǐng)求未命中,透?jìng)鞯牧髁靠赡軙?huì)給數(shù)據(jù)庫(kù)帶來(lái)巨大的壓力,甚至可能導(dǎo)致數(shù)據(jù)庫(kù)崩潰。這就是業(yè)內(nèi)常說(shuō)的緩存穿透問(wèn)題。如果發(fā)生大規(guī)模的并發(fā)穿透,服務(wù)可能宕機(jī)。因此,如果數(shù)據(jù)庫(kù)無(wú)法承受日常流量,就不能依賴(lài)臨時(shí)緩存方案來(lái)設(shè)計(jì)緩存系統(tǒng),而應(yīng)該采用長(zhǎng)期緩存的方式來(lái)實(shí)現(xiàn)熱點(diǎn)緩存,以避免緩存穿透對(duì)數(shù)據(jù)庫(kù)的影響。

要實(shí)現(xiàn)長(zhǎng)期緩存,需要更多的人工操作來(lái)保證緩存與數(shù)據(jù)表的一致性。長(zhǎng)期緩存的普及主要得益于 NoSQL 技術(shù)的發(fā)展,它與臨時(shí)緩存不同,需要業(yè)務(wù)幾乎不依賴(lài)數(shù)據(jù)庫(kù),所有在服務(wù)運(yùn)行期間所需的數(shù)據(jù)都必須在緩存中可用,并確保緩存不會(huì)在使用期間丟失。這帶來(lái)的挑戰(zhàn)是,我們需要精確知道緩存中的數(shù)據(jù),并提前對(duì)這些數(shù)據(jù)進(jìn)行預(yù)熱。如果數(shù)據(jù)規(guī)模較小,還可以考慮將所有數(shù)據(jù)緩存起來(lái),這樣的實(shí)現(xiàn)會(huì)相對(duì)簡(jiǎn)單一些。

總結(jié)

并不是所有數(shù)據(jù)放入緩存都會(huì)帶來(lái)良好的收益,因此我們需要從數(shù)據(jù)量、使用頻率和緩存命中率三個(gè)方面進(jìn)行分析。對(duì)于讀多寫(xiě)少的數(shù)據(jù),雖然將其緩存能夠降低數(shù)據(jù)層的壓力,但仍需根據(jù)一致性需求來(lái)更新緩存中的數(shù)據(jù)。

在這方面,單條實(shí)體數(shù)據(jù)的緩存更新相對(duì)容易實(shí)現(xiàn),但對(duì)于需要條件查詢(xún)的統(tǒng)計(jì)結(jié)果,實(shí)時(shí)更新則較為困難。因此,在設(shè)計(jì)緩存策略時(shí),需綜合考慮這些因素,以確保緩存的有效性和數(shù)據(jù)的一致。

圖片 圖片

責(zé)任編輯:武曉燕 來(lái)源: 二進(jìn)制跳動(dòng)
相關(guān)推薦

2020-06-01 22:09:48

緩存緩存同步緩存誤用

2021-04-18 15:01:56

緩存系統(tǒng)數(shù)據(jù)

2020-05-12 10:43:22

Redis緩存數(shù)據(jù)庫(kù)

2022-09-06 15:30:20

緩存一致性

2022-03-31 08:21:14

數(shù)據(jù)庫(kù)緩存雙寫(xiě)數(shù)據(jù)一致性

2021-06-11 09:21:58

緩存數(shù)據(jù)庫(kù)Redis

2024-12-26 15:01:29

2022-12-14 08:23:30

2020-09-03 09:45:38

緩存數(shù)據(jù)庫(kù)分布式

2021-01-19 10:39:03

Redis緩存數(shù)據(jù)

2015-11-25 11:20:23

WindowsUbuntu時(shí)間同步

2022-03-29 10:39:10

緩存數(shù)據(jù)庫(kù)數(shù)據(jù)

2024-10-28 12:41:25

2018-07-15 08:18:44

緩存數(shù)據(jù)庫(kù)數(shù)據(jù)

2023-04-13 08:15:47

Redis緩存一致性

2024-04-11 13:45:14

Redis數(shù)據(jù)庫(kù)緩存

2022-07-25 09:48:22

緩存數(shù)據(jù)服務(wù)

2022-04-01 16:55:22

數(shù)據(jù)庫(kù)緩存日志

2023-05-09 10:59:33

緩存技術(shù)派MySQL

2018-12-13 12:43:07

Redis緩存穿透
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

主站蜘蛛池模板: 第一福利社区1024 | 手机看黄av免费网址 | 午夜91 | 国产成人精品免高潮在线观看 | 精品乱人伦一区二区三区 | 中文字幕在线观看一区二区 | 在线免费观看亚洲 | 99av成人精品国语自产拍 | 成人av网站在线观看 | www国产成人免费观看视频,深夜成人网 | 久久久久久久国产精品视频 | 呦呦在线视频 | 国产高清一区二区三区 | 国内精品伊人久久久久网站 | 97影院在线午夜 | 一级在线免费观看 | 午夜网站视频 | 久久久久久久久99精品 | 国产毛片视频 | 国内精品一区二区 | 99国内精品久久久久久久 | 免费观看一级视频 | 欧美激情一区二区三区 | 日本三级做a全过程在线观看 | 日韩在线播放av | 蜜桃在线一区二区三区 | 国产精品区一区二区三区 | 久久伊人精品 | 日日夜夜精品视频 | 欧日韩在线观看 | 欧美一区不卡 | 国产欧美精品 | 亚洲一区自拍 | 影音先锋中文字幕在线观看 | 一区精品国产欧美在线 | 久久国产精品一区二区三区 | 99久久国产综合精品麻豆 | 国产精品久久久久一区二区三区 | 欧美日韩国产在线 | 国产一区二区三区在线看 | 一级看片免费视频 |