溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

螞蟻技術(shù)專家:同為分布式緩存,為何 Redis 更勝一籌?

發(fā)布時間:2020-08-10 23:50:22 來源:ITPUB博客 閱讀:128 作者:Java大蝸牛 欄目:編程語言

螞蟻技術(shù)專家:同為分布式緩存,為何 Redis 更勝一籌?


如今,市面上的緩存解決方案已經(jīng)逐步成熟了,今天我將選取其中一些代表性的方案包括Redis、Memcached和Tair進行對比,幫助大家 在生產(chǎn)實踐中更好地進行技術(shù)選型。

一、常用的分布式緩存的對比

常用的分布式緩存包括Redis、Memcached和阿里巴巴的Tair(見下表),因為Redis提供的數(shù)據(jù)結(jié)構(gòu)比較豐富且簡單易用,所以Redis的使用廣泛。

螞蟻技術(shù)專家:同為分布式緩存,為何 Redis 更勝一籌?


下面我們從9個大方面來對比最常用的Redis和Memcached。

1.數(shù)據(jù)類型

Redis一共支持5種數(shù)據(jù)類型,每種數(shù)據(jù)類型對應(yīng)不同的數(shù)據(jù)結(jié)構(gòu),有簡單的String類型、壓縮串、字典、跳躍表等。 跳躍表是比較新型的數(shù)據(jù)結(jié)構(gòu),常用于高性能的查找,可以達到log2N的查詢速度,而且跳躍表相對于紅黑樹,在更新時變更的節(jié)點較少,更易于實現(xiàn)并發(fā)操作。

Memcache只支持對鍵值對的存儲,并不支持其它數(shù)據(jù)結(jié)構(gòu)。

2.線程模型

Redis使用單線程實現(xiàn),Memcache等使用多線程實現(xiàn),因此我們不推薦在Redis中存儲太大的內(nèi)容,否則會阻塞其它請求。

因為緩存操作都是內(nèi)存操作,只有很少的計算操作,所以在單線程下性能很好。Redis實現(xiàn)的單線程的非阻塞網(wǎng)絡(luò)I/O模型,適合快速地操作邏輯,有復(fù)雜的長邏輯時會影響性能。 對于長邏輯應(yīng)該配置多個實例來提高多核CPU的利用率,也就是說,可以使用單機器多端口來配置多個實例,官方的推薦是一臺機器使用8個實例。

它實現(xiàn)的非阻塞I/O模型基于Libevent庫中關(guān)于Epoll的兩個文件加上自己簡單實現(xiàn)的事件通知模型,簡單小巧,作者的思想就是保持實現(xiàn)簡單、減少依賴。由于在服務(wù)器中只有一個線程,因此提供了管道來合并請求和批量執(zhí)行,縮短了通信消耗的時間。

Memcache也使用了非阻塞I/O模型,但是使用了多線程,可以應(yīng)用于多種場景,請求的邏輯可大可小、可長可短,不會出現(xiàn)一個邏輯復(fù)雜的請求阻塞對其它請求的響應(yīng)的場景。它直接依賴Libevent庫實現(xiàn),依賴比較復(fù)雜,損失了在一些特定環(huán)境下的高性能。

3.持久機制

Redis提供了兩種持久機制,包括RDB和AOF,前者是定時的持久機制,但在出現(xiàn)宕機時可能會出現(xiàn)數(shù)據(jù)丟失,后者是基于操作日志的持久機制。

Memcahe并不提供持久機制,因為Memache的設(shè)計理念就是設(shè)計一個單純的緩存,緩存的數(shù)據(jù)都是臨時的,不應(yīng)該是持久的,也不應(yīng)該是一個大數(shù)據(jù)的數(shù)據(jù)庫,緩存未命中時回源查詢數(shù)據(jù)庫是天經(jīng)地義的,但可以通過第三方庫MemcacheDB來支持它的持久性。

4.客戶端

常見的Redis Java客戶端Jedis使用阻塞I/O,但可以配置連接池,并提供了一致性哈希分片的邏輯,也可以使用開源的客戶端分片框架Redic。

Memecache的客戶端包括Memcache Java Client、Spy Client、XMemcache等,Memcache Java Client使用阻塞I/O,而Spy Client/XMemcache使用非阻塞I/O。

我們知道,阻塞I/O不需要額外的線程,非阻塞I/O會開啟額外的請求線程(在Boss線程池里)監(jiān)聽端口,一個請求在處理后就釋放工作者線程(在Worker線程池中),請求線程在監(jiān)聽到有返回結(jié)果時,一旦有I/O返回結(jié)果就被喚醒,然后開始處理響應(yīng)數(shù)據(jù)并寫回網(wǎng)絡(luò)Socket連接,所以從理論上來講,非阻塞I/O的吞吐量和響應(yīng)能力會更高。

5.高可用

Redis支持主從節(jié)點復(fù)制配置,從節(jié)點可使用RDB和緩存的AOF命令進行同步和恢復(fù)。Redis還支持Sentinel和Cluster(從3.0版本開始)等高可用集群方案。

Memecache不支持高可用模型,可使用第三方Megagent代理,當一個實例宕機時,可以連接另外一個實例來實現(xiàn)。

6.對隊列的支持

Redis本身支持lpush/brpop、publish/subscribe/psubscribe等隊列和訂閱模式。

Memcache不支持隊列,可通過第三方MemcachQ來實現(xiàn)。

7.事務(wù)

Redis提供了一些在一定程度上支持線程安全和事務(wù)的命令,例如:multi/exec、watch、inc等。由于Redis服務(wù)器是單線程的,任何單一請求的服務(wù)器操作命令都是原子的,但跨客戶端的操作并不保證原子性,所以對于同一個連接的多個操作序列也不保證事務(wù)。

Memcached的單個命令也是線程安全的,單個連接的多個命令序列不是線程安全的,它也提供了inc等線程安全的自加命令,并提供了gets/cas保證線程安全。

8.數(shù)據(jù)淘汰策略

Redis提供了豐富的淘汰策略,包括maxmemory、maxmemory-policy、volatile-lru、allkeys-lru、volatile-random、allkeys-random、volatile-ttl、noeviction(return error)等。

Memecache在容量達到指定值后,就基于LRU(Least Recently Used)算法自動刪除不使用的緩存。在某些情況下LRU機制反倒會帶來麻煩,會將不期待的數(shù)據(jù)從內(nèi)存中清除,在這種情況下啟動Memcache時,可以通過“M”參數(shù)禁止LRU算法。

9.內(nèi)存分配

Redis為了屏蔽不同平臺之間的差異及統(tǒng)計內(nèi)存占用量等,對內(nèi)存分配函數(shù)進行了一層封裝,在程序中統(tǒng)一使用zmalloc、zfree系列函數(shù),這些函數(shù)位于zmalloc.h/zmalloc.c文件中。封裝就是為了屏蔽底層平臺的差異,同時方便自己實現(xiàn)相關(guān)的統(tǒng)計函數(shù)。具體的實現(xiàn)方式如下:

若系統(tǒng)中存在Google的TC_MALLOC庫,則使用tc_malloc一族的函數(shù)代替原本的malloc一族的函數(shù)。

若當前系統(tǒng)是Mac系統(tǒng),則使用系統(tǒng)的內(nèi)存分配函數(shù)。

對于其它情況,在每一段分配好的空間前面同時多分配一個定長的字段,用來記錄分配的空間大小,通過這種方式來實現(xiàn)簡單有效的內(nèi)存分配。

Memcache采用slab table的方式分配內(nèi)存,首先把可得的內(nèi)存按照不同的大小來分類,在使用時根據(jù)需求找到最接近于需求大小的塊分配,來減少內(nèi)存碎片,但是這需要進行合理配置才能達到效果。

從上面的對比可以看到,Redis在實現(xiàn)和使用上更簡單,但是功能更強大,效率更高,應(yīng)用也更廣泛。下面將對Redis進行初步介紹,給初學(xué)者一個初體驗式的學(xué)習(xí)引導(dǎo)。

二、Redis初體驗

Redis是一個能夠存儲多種數(shù)據(jù)對象的開源Key-Value存儲系統(tǒng),使用ANSI C語言編寫,可以僅僅當作內(nèi)存數(shù)據(jù)庫使用,也可以作為以日志為存儲方式的數(shù)據(jù)庫系統(tǒng),并提供多種語言的API。

1.使用場景

我們通常把Redis當作一個非本地緩存來使用,很少用到它的一些高級功能。在使用中最容易出問題的是用Redis來保存JSON數(shù)據(jù),因為Redis不像Elasticsearch或者PostgreSQL那樣可以很好地支持JSON數(shù)據(jù)。 所以我們經(jīng)常把JSON當作一個大的String直接放到Redis中,但現(xiàn)在的JSON數(shù)據(jù)都是連環(huán)嵌套的,每次更新時都要先獲取整個JSON,然后更改其中一個字段再放上去。

一個常見的JSON數(shù)據(jù)的Java對象定義如下:

public class Commodity {

private long price;

private String title;

……

}

在海量請求的前提下,在Redis中每次更新一個字段,比如銷量字段,都會產(chǎn)生較大的流量。在實際情況下,JSON字符串往往非常復(fù)雜,體積達到數(shù)百KB都是有可能的,導(dǎo)致在頻繁更新數(shù)據(jù)時使網(wǎng)絡(luò)I/O跑滿,甚至導(dǎo)致系統(tǒng)超時、崩潰。

因此,Redis官方推薦采用哈希來保存對象,比如有3個商品對象,ID分別是123、124和12345,我們通過哈希把它們保存在Redis中,在更新其中的字段時可以這樣做:

HSET commodity:123 price 100

HSET commodity:124 price 101

HSET commodity:12345 price 101

HSET commodity:123 title banana

HSET commodity:124 title apple

HSET commodity:12345 title orange

也就是說,用商品的類型名和ID組成一個Redis哈希對象的KEY。在獲取某一屬性時只需這樣做就可以獲取單獨的屬性: HGET commodity: 12345。

2.Redis的高可用方案:哨兵

Redis官方推出了一個集群管理工具,叫作哨兵(Sentinel),負責(zé)在節(jié)點中選出主節(jié)點,按照分布式集群的管理辦法來操作集群節(jié)點的上線、下線、監(jiān)控、提醒、自動故障切換(主備切換),且實現(xiàn)了著名的RAFT選主協(xié)議,從而保證了系統(tǒng)選主的一致性。

這里給出一個哨兵的通用部署方案。哨兵節(jié)點一般至少要部署3份,可以和被監(jiān)控的節(jié)點放在一個虛擬機中,常見的哨兵部署如圖所示。

螞蟻技術(shù)專家:同為分布式緩存,為何 Redis 更勝一籌?


在這個系統(tǒng)中,初始狀態(tài)下的機器A是主節(jié)點,機器B和機器C是從節(jié)點。

由于有3個哨兵節(jié)點,每個機器運行1個哨兵節(jié)點,所以這里設(shè)置quorum = 2,也就是在主節(jié)點無響應(yīng)后,有至少兩個哨兵無法與主節(jié)點通信,則認為主節(jié)點宕機,然后在從節(jié)點中選舉新的主節(jié)點來使用。

在發(fā)生網(wǎng)絡(luò)分區(qū)時,若機器A所在的主機網(wǎng)絡(luò)不可用,則機器B和機器C上的兩個Sentinel實例會啟動failover并把機器B選舉為主節(jié)點。

Sentinel集群的特性保證了機器B和機器C上的兩個Sentinel實例得到了關(guān)于主節(jié)點的最新配置。但機器A上的Sentinel節(jié)點依然持有舊的配置,因為它與外界隔離了。

在 網(wǎng)絡(luò)恢復(fù)后,我們知道 機器 A 上的 Sentinel 實例 將會更新它的配置。但是,如果客戶端所連接的 主機節(jié)點也 被網(wǎng)絡(luò)隔離, 則 客戶端將依然可以向 機器 A 的 Redis 節(jié)點 寫數(shù)據(jù),但 在 網(wǎng)絡(luò)恢復(fù)后, 機器 A 的 Redis 節(jié)點 就會變成一個 從節(jié)點 ,那么在網(wǎng)絡(luò)隔離期間,客戶端向 機器 A的 Redis 節(jié)點寫入 的數(shù)據(jù)將會丟失 ,這是不可避免的。

如果把 Redis 當作 緩存來使用,那么 我們 也許能容忍這部分數(shù)據(jù)的丟失 ,但若 把 Redis 當作一個存儲系統(tǒng)來使用,就無法容忍這部分數(shù)據(jù)的丟失了 , 因為 Redis 采用的是異步復(fù)制,在這樣的場景下 無法 避免數(shù)據(jù)的丟失。

在這里,我們可以通過以下配置來配置每個Redis實例,使得數(shù)據(jù)不會丟失:

min-slaves-to-write 1

min-slaves-max-lag 10

通過上面的配置,當一個Redis是主節(jié)點時,如果它不能向至少一個從節(jié)點寫數(shù)據(jù)(上面的min-slaves-to-write指定了slave的數(shù)量),則它將會拒絕接收客戶端的寫請求。由于復(fù)制是異步的,所以主節(jié)點無法向從節(jié)點寫數(shù)據(jù)就意味著從節(jié)點要么斷開了連接,要么沒在指定的時間內(nèi)向主節(jié)點發(fā)送同步數(shù)據(jù)的請求。

所以,采用這樣的配置可排除網(wǎng)絡(luò)分區(qū)后主節(jié)點被孤立但仍然寫入數(shù)據(jù),從而導(dǎo)致數(shù)據(jù)丟失的場景。

3.Redis集群

Redis在3.0中也引入了集群的概念,用于解決一些大數(shù)據(jù)量和高可用的問題,但是,為了達到高性能的目的,集群不是強一致性的,使用的是異步復(fù)制,在數(shù)據(jù)到主節(jié)點后,主節(jié)點返回成功,數(shù)據(jù)被異步地復(fù)制給從節(jié)點。

首先,我們來學(xué)習(xí)Redis的集群分片機制。Redis使用CRC16(key) mod 16384進行分片,一共分16384個哈希槽,比如若集群有3個節(jié)點,則我們按照如下規(guī)則分配哈希槽:

A節(jié)點包含0-5500的哈希槽;

B節(jié)點包含5500-11000的哈希槽;

C節(jié)點包含11000-16384的哈希槽。

這里設(shè)置了3個主節(jié)點和3個從節(jié)點,集群分片如圖所示。

螞蟻技術(shù)專家:同為分布式緩存,為何 Redis 更勝一籌?


圖中共有3個Redis主從服務(wù)器的復(fù)制節(jié)點,其中任意兩個節(jié)點之間都是相互連通的,客戶端可以與其中任意一個節(jié)點相連接,然后訪問集群中的任意一個節(jié)點,對其進行存取和其他操作。

那Redis是怎么做到的呢?首先,在Redis的每個節(jié)點上都會存儲哈希槽信息,我們可以將它理解為是一個可以存儲兩個數(shù)值的變量,這個變量的取值范圍是0-16383。根據(jù)這些信息,我們就可以找到每個節(jié)點負責(zé)的哈希槽,進而找到數(shù)據(jù)所在的節(jié)點。

Redis集群實際上是一個集群管理的插件,當我們提供一個存取的關(guān)鍵字時,就會根據(jù)CRC16的算法得出一個結(jié)果,然后把結(jié)果除以16384求余數(shù),這樣每個關(guān)鍵字都會對應(yīng)一個編號為0-16383的哈希槽,通過這個值找到對應(yīng)的插槽所對應(yīng)的節(jié)點,然后直接自動跳轉(zhuǎn)到這個對應(yīng)的節(jié)點上進行存取操作。但是這些都是由集群的內(nèi)部機制實現(xiàn)的,我們不需要手工實現(xiàn)。

為了讓學(xué)習(xí)變得輕松、高效,今天給大家免費分享一套阿里架構(gòu)師傳授的一套教學(xué)資源。幫助大家在成為架構(gòu)師的道路上披荊斬棘。

這套視頻課程詳細講解了(Spring,MyBatis,Netty源碼分析,高并發(fā)、高性能、分布式、微服務(wù)架構(gòu)的原理,JVM性能優(yōu)化、分布式架構(gòu))等這些成為架構(gòu)師必備的內(nèi)容!

而且還把框架需要用到的各種程序進行了打包,根據(jù)基礎(chǔ)視頻可以讓你輕松搭建分布式框架環(huán)境,像在企業(yè)生產(chǎn)環(huán)境一樣進行學(xué)習(xí)和實踐。

加Java高級交流群: 725633148  就可以馬上免費獲得這套價值一萬八的內(nèi)部教材!


向AI問一下細節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI