自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

同為分布式緩存,為何Redis更勝一籌?

開(kāi)發(fā) 前端 其他數(shù)據(jù)庫(kù) 分布式 Redis
如今,市面上的緩存解決方案已經(jīng)逐步成熟了,今天我將選取其中一些代表性的方案包括Redis、Memcached和Tair進(jìn)行對(duì)比,幫助大家在生產(chǎn)實(shí)踐中更好地進(jìn)行技術(shù)選型。

本文節(jié)選自即將出版的《可伸縮服務(wù)架構(gòu):框架與中間件》一書,作者:李艷鵬、楊彪、李海亮、賈博巖、劉淏

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

一、常用的分布式緩存的對(duì)比

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

 

下面我們從9個(gè)大方面來(lái)對(duì)比最常用的Redis和Memcached。

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

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

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

2.線程模型

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

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

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

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

3.持久機(jī)制

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

Memcahe并不提供持久機(jī)制,因?yàn)镸emache的設(shè)計(jì)理念就是設(shè)計(jì)一個(gè)單純的緩存,緩存的數(shù)據(jù)都是臨時(shí)的,不應(yīng)該是持久的,也不應(yīng)該是一個(gè)大數(shù)據(jù)的數(shù)據(jù)庫(kù),緩存未***時(shí)回源查詢數(shù)據(jù)庫(kù)是天經(jīng)地義的,但可以通過(guò)第三方庫(kù)MemcacheDB來(lái)支持它的持久性。

4.客戶端

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

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

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

5.高可用

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

Memecache不支持高可用模型,可使用第三方Megagent代理,當(dāng)一個(gè)實(shí)例宕機(jī)時(shí),可以連接另外一個(gè)實(shí)例來(lái)實(shí)現(xiàn)。

6.對(duì)隊(duì)列的支持

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

Memcache不支持隊(duì)列,可通過(guò)第三方MemcachQ來(lái)實(shí)現(xiàn)。

7.事務(wù)

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

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

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

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

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

9.內(nèi)存分配

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

  • 若系統(tǒng)中存在Google的TC_MALLOC庫(kù),則使用tc_malloc一族的函數(shù)代替原本的malloc一族的函數(shù)。
  • 若當(dāng)前系統(tǒng)是Mac系統(tǒng),則使用系統(tǒng)的內(nèi)存分配函數(shù)。
  • 對(duì)于其它情況,在每一段分配好的空間前面同時(shí)多分配一個(gè)定長(zhǎng)的字段,用來(lái)記錄分配的空間大小,通過(guò)這種方式來(lái)實(shí)現(xiàn)簡(jiǎn)單有效的內(nèi)存分配。

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

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

二、Redis初體驗(yàn)

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

1.使用場(chǎng)景

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

一個(gè)常見(jiàn)的JSON數(shù)據(jù)的Java對(duì)象定義如下:

 

  1. public class Commodity {  
  2. private long price;  
  3. private String title;  
  4. ……  

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

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

 

  1. HSET commodity:123 price 100  
  2. HSET commodity:124 price 101  
  3. HSET commodity:12345 price 101  
  4.  
  5. HSET commodity:123 title banana  
  6. HSET commodity:124 title apple  
  7. HSET commodity:12345 title orange 

也就是說(shuō),用商品的類型名和ID組成一個(gè)Redis哈希對(duì)象的KEY。在獲取某一屬性時(shí)只需這樣做就可以獲取單獨(dú)的屬性:HGET commodity: 12345。

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

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

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

 

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

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

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

Sentinel集群的特性保證了機(jī)器B和機(jī)器C上的兩個(gè)Sentinel實(shí)例得到了關(guān)于主節(jié)點(diǎn)的***配置。但機(jī)器A上的Sentinel節(jié)點(diǎn)依然持有舊的配置,因?yàn)樗c外界隔離了。

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

如果把Redis當(dāng)作緩存來(lái)使用,那么我們也許能容忍這部分?jǐn)?shù)據(jù)的丟失,但若把Redis當(dāng)作一個(gè)存儲(chǔ)系統(tǒng)來(lái)使用,就無(wú)法容忍這部分?jǐn)?shù)據(jù)的丟失了,因?yàn)镽edis采用的是異步復(fù)制,在這樣的場(chǎng)景下無(wú)法避免數(shù)據(jù)的丟失。

在這里,我們可以通過(guò)以下配置來(lái)配置每個(gè)Redis實(shí)例,使得數(shù)據(jù)不會(huì)丟失:

 

  1. min-slaves-to-write 1  
  2. min-slaves-max-lag 10 

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

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

3.Redis集群

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

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

  • A節(jié)點(diǎn)包含0-5500的哈希槽;
  • B節(jié)點(diǎn)包含5500-11000的哈希槽;
  • C節(jié)點(diǎn)包含11000-16384的哈希槽。

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

 

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

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

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

作者介紹

楊彪,螞蟻金服技術(shù)專家,《分布式服務(wù)架構(gòu):原理、設(shè)計(jì)與實(shí)戰(zhàn)》和《可伸縮服務(wù)架構(gòu):框架與中間件》作者。近10年互聯(lián)網(wǎng)和游戲行業(yè)工作經(jīng)驗(yàn),曾在酷我音樂(lè)盒、人人游戲和掌趣科技等上市公司擔(dān)任核心研發(fā)職位,做過(guò)日活躍用戶量達(dá)千萬(wàn)的項(xiàng)目,也做過(guò)多款月流水千萬(wàn)以上的游戲。

責(zé)任編輯:未麗燕 來(lái)源: DBAplus社群
相關(guān)推薦

2024-07-31 09:39:33

2020-04-29 16:17:37

分布式存儲(chǔ)產(chǎn)品

2020-03-06 09:21:28

PWA原生應(yīng)用Web

2010-05-28 11:21:17

2014-03-06 15:07:41

青橙小米

2018-06-12 10:09:41

編程語(yǔ)言PythonJava

2022-07-20 08:16:54

Lombokjava工具

2023-08-23 15:14:13

Web開(kāi)發(fā)Javascript編程語(yǔ)言

2012-11-14 09:44:20

apReduceHadoopCoronApache

2020-02-02 15:42:22

PythonC++編程語(yǔ)言

2020-01-18 14:55:03

架構(gòu)運(yùn)維技術(shù)

2022-08-24 08:00:00

Node.isJavaScriptDeno

2014-05-22 11:26:26

航班app體驗(yàn)

2017-01-11 14:38:39

編程語(yǔ)言Java

2017-04-15 18:58:31

PythonRuby編程語(yǔ)言

2017-11-13 15:38:03

VMwareOpenStack混合云

2023-08-09 18:08:35

ChatGPTStackOverflow

2018-10-12 13:54:26

2010-05-02 14:43:43

Meego開(kāi)發(fā)

2019-01-04 09:59:14

KafkaRabbitMQMQ
點(diǎn)贊
收藏

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