redis架構演變與redis-cluster羣集讀寫方案

redis架構演變與redis-cluster群集讀寫方案

導言

redis-cluster是近年來redis架構不斷改進中的相對較好的redis高可用方案。本文涉及到近年來redis多實例架構的演變過程,包括普通主從架構(Master、slave可進行寫讀分離)、哨兵模式下的主從架構、redis-cluster高可用架構(redis官方默認cluster下不進行讀寫分離)的簡介。同時還介紹使用Java的兩大redis客戶端:Jedis與Lettuce用於讀寫redis-cluster的數據的一般方法。再通過官方文檔以及互聯網的相關技術文檔,給出redis-cluster架構下的讀寫能力的優化方案,包括官方的推薦的擴展redis-cluster下的Master數量以及非官方默認的redis-cluster的讀寫分離方案,案例中使用Lettuce的特定方法進行redis-cluster架構下的數據讀寫分離。

近年來redis多實例用架構的演變過程

redis是基於內存的高性能key-value數據庫,若要讓redis的數據更穩定安全,需要引入多實例以及相關的高可用架構。而近年來redis的高可用架構亦不斷改進,先後出現了本地持久化、主從備份、哨兵模式、redis-cluster群集高可用架構等等方案。

1、redis普通主從模式

通過持久化功能,Redis保證了即使在服務器重啟的情況下也不會損失(或少量損失)數據,因為持久化會把內存中數據保存到硬盤上,重啟會從硬盤上加載數據。 。但是由於數據是存儲在一臺服務器上的,如果這臺服務器出現硬盤故障等問題,也會導致數據丟失。為了避免單點故障,通常的做法是將數據庫複製多個副本以部署在不同的服務器上,這樣即使有一臺服務器出現故障,其他服務器依然可以繼續提供服務。為此, Redis 提供了複製(replication)功能,可以實現當一臺數據庫中的數據更新後,自動將更新的數據同步到其他數據庫上。

在複製的概念中,數據庫分為兩類,一類是主數據庫(master),另一類是從數據庫(slave)。主數據庫可以進行讀寫操作,當寫操作導致數據變化時會自動將數據同步給從數據庫。而從數據庫一般是隻讀的,並接受主數據庫同步過來的數據。一個主數據庫可以擁有多個從數據庫,而一個從數據庫只能擁有一個主數據庫。

redis架構演變與redis-cluster群集讀寫方案

主從模式的配置,一般只需要再作為slave的redis節點的conf文件上加入“slaveof masterip masterport”, 或者作為slave的redis節點啟動時使用如下參考命令:

redis-server --port 6380 --slaveof masterIp masterPort

redis的普通主從模式,能較好地避免單獨故障問題,以及提出了讀寫分離,降低了Master節點的壓力。互聯網上大多數的對redis讀寫分離的教程,都是基於這一模式或架構下進行的。但實際上這一架構並非是目前最好的redis高可用架構。

2、redis哨兵模式高可用架構

當主數據庫遇到異常中斷服務後,開發者可以通過手動的方式選擇一個從數據庫來升格為主數據庫,以使得系統能夠繼續提供服務。然而整個過程相對麻煩且需要人工介入,難以實現自動化。 為此,Redis 2.8開始提供了哨兵工具來實現自動化的系統監控和故障恢復功能。 哨兵的作用就是監控redis主、從數據庫是否正常運行,主出現故障自動將從數據庫轉換為主數據庫。

顧名思義,哨兵的作用就是監控Redis系統的運行狀況。它的功能包括以下兩個。

(1)監控主數據庫和從數據庫是否正常運行。

(2)主數據庫出現故障時自動將從數據庫轉換為主數據庫。

redis架構演變與redis-cluster群集讀寫方案

可以用info replication查看主從情況 例子: 1主2從 1哨兵,可以用命令起也可以用配置文件裡 可以使用雙哨兵,更安全,參考命令如下:

redis-server --port 6379

redis-server --port 6380 --slaveof 192.168.0.167 6379

redis-server --port 6381 --slaveof 192.168.0.167 6379

redis-sentinel sentinel.conf

其中,哨兵配置文件sentinel.conf參考如下:

sentinel monitor mymaster 192.168.0.167 6379 1

其中mymaster表示要監控的主數據庫的名字。配置哨兵監控一個系統時,只需要配置其監控主數據庫即可,哨兵會自動發現所有複製該主數據庫的從數據庫。

Master與slave的切換過程:

(1)slave leader升級為master

(2)其他slave修改為新master的slave

(3)客戶端修改連接

(4)老的master如果重啟成功,變為新master的slave

3、redis-cluster群集高可用架構

即使使用哨兵,redis每個實例也是全量存儲,每個redis存儲的內容都是完整的數據,浪費內存且有木桶效應。為了最大化利用內存,可以採用cluster群集,就是分佈式存儲。即每臺redis存儲不同的內容。

採用redis-cluster架構正是滿足這種分佈式存儲要求的集群的一種體現。redis-cluster架構中,被設計成共有16384個hash slot。每個master分得一部分slot,其算法為:hash_slot = crc16(key) mod 16384 ,這就找到對應slot。採用hash slot的算法,實際上是解決了redis-cluster架構下,有多個master節點的時候,數據如何分佈到這些節點上去。key是可用key,如果有{}則取{}內的作為可用key,否則整個可以是可用key。群集至少需要3主3從,且每個實例使用不同的配置文件。

redis架構演變與redis-cluster群集讀寫方案

在redis-cluster架構中,redis-master節點一般用於接收讀寫,而redis-slave節點則一般只用於備份,其與對應的master擁有相同的slot集合,若某個redis-master意外失效,則再將其對應的slave進行升級為臨時redis-master。

在redis的官方文檔中,對redis-cluster架構上,有這樣的說明:在cluster架構下,默認的,一般redis-master用於接收讀寫,而redis-slave則用於備份,當有請求是在向slave發起時,會直接重定向到對應key所在的master來處理。但如果不介意讀取的是redis-cluster中有可能過期的數據並且對寫請求不感興趣時,則亦可通過readonly命令,將slave設置成可讀,然後通過slave獲取相關的key,達到讀寫分離。具體可以參閱redis官方文檔(https://redis.io/commands/readonly)等相關內容:

Enables read queries for a connection to a Redis Cluster slave node.

Normally slave nodes will redirect clients to the authoritative master for the hash slot involved in a given command, however clients can use slaves in order to scale reads using the READONLY command.

READONLY tells a Redis Cluster slave node that the client is willing to read possibly stale data and is not interested in running write queries.

When the connection is in readonly mode, the cluster will send a redirection to the client only if the operation involves keys not served by the slave's master node. This may happen because:

The client sent a command about hash slots never served by the master of this slave.

The cluster was reconfigured (for example resharded) and the slave is no longer able to serve commands for a given hash slot.

例如,我們假設已經建立了一個三主三從的redis-cluster架構,其中A、B、C節點都是redis-master節點,A1、B1、C1節點都是對應的redis-slave節點。在我們只有master節點A,B,C的情況下,對應redis-cluster如果節點B失敗,則群集無法繼續,因為我們沒有辦法再在節點B的所具有的約三分之一的hash slot集合範圍內提供相對應的slot。然而,如果我們為每個主服務器節點添加一個從服務器節點,以便最終集群由作為主服務器節點的A,B,C以及作為從服務器節點的A1,B1,C1組成,那麼如果節點B發生故障,系統能夠繼續運行。節點B1複製B,並且B失效時,則redis-cluster將促使B的從節點B1作為新的主服務器節點並且將繼續正確地操作。但請注意,如果節點B和B1在同一時間發生故障,則Redis群集無法繼續運行。

Redis群集配置參數:在繼續之前,讓我們介紹一下Redis Cluster在redis.conf文件中引入的配置參數。有些命令的意思是顯而易見的,有些命令在你閱讀下面的解釋後才會更加清晰。

(1)cluster-enabled :如果想在特定的Redis實例中啟用Redis群集支持就設置為yes。 否則,實例通常作為獨立實例啟動。

(2)cluster-config-file :請注意,儘管有此選項的名稱,但這不是用戶可編輯的配置文件,而是Redis群集節點每次發生更改時自動保留群集配置(基本上為狀態)的文件。

(3)cluster-node-timeout :Redis群集節點可以不可用的最長時間,而不會將其視為失敗。 如果主節點超過指定的時間不可達,它將由其從屬設備進行故障切換。

(4)cluster-slave-validity-factor :如果設置為0,無論主設備和從設備之間的鏈路保持斷開連接的時間長短,從設備都將嘗試故障切換主設備。 如果該值為正值,則計算最大斷開時間作為節點超時值乘以此選項提供的係數,如果該節點是從節點,則在主鏈路斷開連接的時間超過指定的超時值時,它不會嘗試啟動故障切換。

(5)cluster-migration-barrier :主設備將保持連接的最小從設備數量,以便另一個從設備遷移到不受任何從設備覆蓋的主設備。有關更多信息,請參閱本教程中有關副本遷移的相應部分。

(6)cluster-require-full-coverage :如果將其設置為yes,則默認情況下,如果key的空間的某個百分比未被任何節點覆蓋,則集群停止接受寫入。 如果該選項設置為no,則即使只處理關於keys子集的請求,群集仍將提供查詢。

以下是最小的Redis集群配置文件:

port 7000

cluster-enabled yes

cluster-config-file nodes.conf

cluster-node-timeout 5000

appendonly yes

注意:

(1)redis-cluster最小配置為三主三從,當1個主故障,大家會給對應的從投票,把從立為主,若沒有從數據庫可以恢復則redis群集就down了。

(2)在這個redis cluster中,如果你要在slave讀取數據,那麼需要帶上readonly指令。redis cluster的核心的理念,主要是用slave做高可用的,每個master掛一兩個slave,主要是做數據的熱備,當master故障時的作為主備切換,實現高可用的。redis cluster默認是不支持slave節點讀或者寫的,跟我們手動基於replication搭建的主從架構不一樣的。slave node要設置readonly,然後再get,這個時候才能在slave node進行讀取。對於redis -cluster主從架構,若要進行讀寫分離,官方其實是不建議的,但也能做,只是會複雜一些。具體見下面的章節。

(3)redis-cluster的架構下,實際上本身master就是可以任意擴展的,你如果要支撐更大的讀吞吐量,或者寫吞吐量,或者數據量,都可以直接對master進行橫向擴展就可以了。也擴容master,跟之前擴容slave進行讀寫分離,效果是一樣的或者說更好。

(4)可以使用自帶客戶端連接:使用redis-cli -c -p cluster中任意一個端口,進行數據獲取測試。

Java中對redis-cluster數據的一般讀取方法簡介

使用Jedis讀寫redis-cluster的數據

由於Jedis類一般只能對一臺redis-master進行數據操作,所以面對redis-cluster多臺master與slave的群集,Jedis類就不能滿足了。這個時候我們需要引用另外一個操作類:JedisCluster類。

例如我們有6臺機器組成的redis-cluster:

172.20.52.85:7000、 172.20.52.85:7001、172.20.52.85:7002、172.20.52.85:7003、172.20.52.85:7004、172.20.52.85:7005

其中master機器對應端口:7000、7004、7005

slave對應端口:7001、7002、7003

使用JedisCluster對redis-cluster進行數據操作的參考代碼如下:

// 添加nodes服務節點到Set集合

Set

hostAndPortsSet = new HashSet();

// 添加節點

hostAndPortsSet.add(new HostAndPort("172.20.52.85", 7000));

hostAndPortsSet.add(new HostAndPort("172.20.52.85", 7001));

hostAndPortsSet.add(new HostAndPort("172.20.52.85", 7002));

hostAndPortsSet.add(new HostAndPort("172.20.52.85", 7003));

hostAndPortsSet.add(new HostAndPort("172.20.52.85", 7004));

hostAndPortsSet.add(new HostAndPort("172.20.52.85", 7005));

// Jedis連接池配置

JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();

jedisPoolConfig.setMaxIdle(100);

jedisPoolConfig.setMaxTotal(500);

jedisPoolConfig.setMinIdle(0);

jedisPoolConfig.setMaxWaitMillis(2000); // 設置2秒

jedisPoolConfig.setTestOnBorrow(true);

JedisCluster jedisCluster = new JedisCluster(hostAndPortsSet ,jedisPoolConfig);

String result = jedisCluster.get("event:10");

System.out.println(result);

運行結果截圖如下圖所示:

redis架構演變與redis-cluster群集讀寫方案

第一節中我們已經介紹了redis-cluster架構下master提供讀寫功能,而slave一般只作為對應master機器的數據備份不提供讀寫。如果我們只在hostAndPortsSet中只配置slave,而不配置master,實際上還是可以讀到數據,但其內部操作實際是通過slave重定向到相關的master主機上,然後再將結果獲取和輸出。

上面是普通項目使用JedisCluster的簡單過程,若在spring boot項目中,可以定義JedisConfig類,使用@Configuration、@Value、@Bean等一些列註解完成JedisCluster的配置,然後再注入該JedisCluster到相關service邏輯中引用,這裡介紹略。

使用Lettuce讀寫redis-cluster數據

Lettuce 和 Jedis 的定位都是Redis的client。Jedis在實現上是直接連接的redis server,如果在多線程環境下是非線程安全的,這個時候只有使用連接池,為每個Jedis實例增加物理連接,每個線程都去拿自己的 Jedis 實例,當連接數量增多時,物理連接成本就較高了。

Lettuce的連接是基於Netty的,連接實例(StatefulRedisConnection)可以在多個線程間併發訪問,應為StatefulRedisConnection是線程安全的,所以一個連接實例(StatefulRedisConnection)就可以滿足多線程環境下的併發訪問,當然這個也是可伸縮的設計,一個連接實例不夠的情況也可以按需增加連接實例。

其中spring boot 2.X版本中,依賴的spring-session-data-redis已經默認替換成Lettuce了。

同樣,例如我們有6臺機器組成的redis-cluster:

172.20.52.85:7000、 172.20.52.85:7001、172.20.52.85:7002、172.20.52.85:7003、172.20.52.85:7004、172.20.52.85:7005

其中master機器對應端口:7000、7004、7005

slave對應端口:7001、7002、7003

在spring boot 2.X版本中使用Lettuce操作redis-cluster數據的方法參考如下:

(1)pom文件參考如下:

parent中指出spring boot的版本,要求2.X以上:

org.springframework.boot

spring-boot-starter-parent

2.0.4.RELEASE

依賴中需要加入spring-boot-starter-data-redis,參考如下:

org.springframework.boot

spring-boot-starter-data-redis

(2)springboot的配置文件要包含如下內容:

spring.redis.database=0

spring.redis.lettuce.pool.max-idle=10

spring.redis.lettuce.pool.max-wait=500

spring.redis.cluster.timeout=1000

spring.redis.cluster.max-redirects=3

spring.redis.cluster.nodes=172.20.52.85:7000,172.20.52.85:7001,172.20.52.85:7002,172.20.52.85:7003,172.20.52.85:7004,172.20.52.85:7005

(3)新建RedisConfiguration類,參考代碼如下:

@Configuration

public class RedisConfiguration {

[@Resource](https://my.oschina.net/u/929718)

private LettuceConnectionFactory myLettuceConnectionFactory;

@Bean

public RedisTemplate redisTemplate() {

RedisTemplate template = new RedisTemplate<>();

template.setKeySerializer(new StringRedisSerializer());

//template.setValueSerializer(new GenericJackson2JsonRedisSerializer());

template.setValueSerializer(new StringRedisSerializer());

template.setConnectionFactory(myLettuceConnectionFactory);

return template;

}

}

(4)新建RedisFactoryConfig類,參考代碼如下:

@Configuration

public class RedisFactoryConfig {

@Autowired

private Environment environment;

@Bean

public RedisConnectionFactory myLettuceConnectionFactory() {

Map source = new HashMap();

source.put("spring.redis.cluster.nodes", environment.getProperty("spring.redis.cluster.nodes"));

source.put("spring.redis.cluster.timeout", environment.getProperty("spring.redis.cluster.timeout"));

source.put("spring.redis.cluster.max-redirects", environment.getProperty("spring.redis.cluster.max-redirects"));

RedisClusterConfiguration redisClusterConfiguration;

redisClusterConfiguration = new RedisClusterConfiguration(new MapPropertySource("RedisClusterConfiguration", source));

return new LettuceConnectionFactory(redisClusterConfiguration);

}

}

(5)在業務類service中注入Lettuce相關的RedisTemplate,進行相關操作。以下是我化簡到了springbootstarter中進行,參考代碼如下:

@SpringBootApplication

public class NewRedisClientApplication {

public static void main(String[] args) {

ApplicationContext context = SpringApplication.run(NewRedisClientApplication.class, args);

RedisTemplate redisTemplate = (RedisTemplate)context.getBean("redisTemplate");

String rtnValue = (String)redisTemplate.opsForValue().get("event:10");

System.out.println(rtnValue);

}

}

運行結果的截圖如下:

redis架構演變與redis-cluster群集讀寫方案

以上的介紹,是採用Jedis以及Lettuce對redis-cluster數據的簡單讀取。Jedis也好,Lettuce也好,其對於redis-cluster架構下的數據的讀取,都是默認是按照redis官方對redis-cluster的設計,自動進行重定向到master節點中進行的,哪怕是我們在配置中列出了所有的master節點和slave節點。查閱了Jedis以及Lettuce的github上的源碼,默認不支持redis-cluster下的讀寫分離,可以看出Jedis若要支持redis-cluster架構下的讀寫分離,需要自己改寫和構建多一些包裝類,定義好Master和slave節點的邏輯;而Lettuce的源碼中,實際上預留了方法(setReadForm(ReadFrom.SLAVE))進行redis-cluster架構下的讀寫分離,相對來說修改會簡單一些,具體可以參考後面的章節。

redis-cluster架構下的讀寫能力的優化方案

在上面的一些章節中,已經有講到redis近年來的高可用架構的演變,以及在redis-cluster架構下,官方對redis-master、redis-slave的其實有使用上的建議,即redis-master節點一般用於接收讀寫,而redis-slave節點則一般只用於備份,其與對應的master擁有相同的slot集合,若某個redis-master意外失效,則再將其對應的slave進行升級為臨時redis-master。但如果不介意讀取的是redis-cluster中有可能過期的數據並且對寫請求不感興趣時,則亦可通過readonly命令,將slave設置成可讀,然後通過slave獲取相關的key,達到讀寫分離。

具體可以參閱redis官方文檔(https://redis.io/commands/readonly),以下是reids在線文檔中,對slave的readonly說明內容:

redis架構演變與redis-cluster群集讀寫方案

實際上本身master就是可以任意擴展的,所以如果要支撐更大的讀吞吐量,或者寫吞吐量,或者數據量,都可以直接對master進行橫向水平擴展就可以了。也就是說,擴容master,跟之前擴容slave並進行讀寫分離,效果是一樣的或者說更好。

所以下面我們將按照redis-cluster架構下分別進行水平擴展Master,以及在redis-cluster架構下對master、slave進行讀寫分離兩套方案進行講解。

(一)水平擴展Master實例來進行redis-cluster性能的提升

redis官方在線文檔以及一些互聯網的參考資料都表明,在redis-cluster架構下,實際上不建議做物理的讀寫分離。那麼如果我們真的不做讀寫分離的話,能否通過簡單的方法進行redis-cluster下的性能的提升?我們可以通過master的水平擴展,來橫向擴展讀寫吞吐量,並且能支撐更多的海量數據。

對master進行水平擴展有兩種方法,一種是單機上面進行master實例的增加(建議每新增一個master,也新增一個對應的slave),另一種是新增機器部署新的master實例(同樣建議每新增一個master,也新增一個對應的slave)。當然,我們也可以進行這兩種方法的有效結合。

(1)單機上通過多線程建立新redis-master實例,即邏輯上的水平擴展:

一般的,對於redis單機,單線程的讀吞吐是4w/s~5W/s,寫吞吐為2w/s。

單機合理開啟redis多線程情況下(一般線程數為CPU核數的倍數),總吞吐量會有所上升,但每個線程的平均處理能力會有所下降。例如一個2核CPU,開啟2線程的時候,總讀吞吐能上升是6W/s~7W/s,即每個線程平均約3W/s再多一些。但過多的redis線程反而會限制了總吞吐量。

(2)擴展更多的機器,部署新redis-master實例,即物理上的水平擴展:

例如,我們可以再原來只有3臺master的基礎上,連入新機器繼續新實例的部署,最終水平擴展為6臺master(建議每新增一個master,也新增一個對應的slave)。例如之前每臺master的處理能力假設是讀吞吐5W/s,寫吞吐2W/s,擴展前一共的處理能力是:15W/s讀,6W/s寫。如果我們水平擴展到6臺master,讀吞吐可以達到總量30W/s,寫可以達到12w/s,性能能夠成倍增加。

(3)若原本每臺部署redis-master實例的機器都性能良好,則可以通過上述兩者的結合,進行一個更優的組合。

使用該方案進行redis-cluster性能的提升的優點有:

(1)符合redis官方要求和數據的準確性。

(2)真正達到更大吞吐量的性能擴展。

(3)無需代碼的大量更改,只需在配置文件中重新配置新的節點信息。

當然缺點也是有的:

(1)需要新增機器,提升性能,即成本會增加。

(2)若不新增機器,則需要原來的實例所運行的機器性能較好,能進行以多線程的方式部署新實例。但隨著線程的增多,而機器的能力不足以支撐的時候,實際上總體能力會提升不太明顯。

(3)redis-cluster進行新的水平擴容後,需要對master進行新的hash slot重新分配,這相當於需要重新加載所有的key,並按算法平均分配到各個Master的slot當中。

(二)引入Lettuce以及修改相關方法,達到對redis-cluster的讀寫分離

通過上面的一些章節,我們已經可以瞭解到Lettuce客戶端讀取redis的一些操作,使用Lettuce能體現出了簡單,安全,高效。實際上,查閱了Lettuce對redis的讀寫,許多地方都進行了redis的讀寫分離。但這些都是基於上述redis架構中最普通的主從分離架構下的讀寫分離,而對於redis-cluster架構下,Lettuce可能是遵循了redis官方的意見,在該架構下,Lettuce在源碼中直接設置了只由master上進行讀寫(具體參見gitHub的Lettuce項目):

redis架構演變與redis-cluster群集讀寫方案

那麼如果真的需要讓Lettuce改為能夠讀取redis-cluster的slave,進行讀寫分離,是否可行?實際上還是可以的。這就需要我們自己在項目中進行二次加工,即不使用spring-boot中的默認Lettuce初始化方法,而是自己去寫一個屬於自己的Lettuce的新RedisClusterClient的連接,並且對該RedisClusterClient的連接進行一個比較重要的設置,那就是由connection.setReadFrom(ReadFrom.MASTER)改為connection.setReadFrom(ReadFrom.SLAVE)。

下面我們開始對之前章節中的Lettuce讀取redis-cluster數據的例子,進行改寫,讓Lettuce能夠支持該架構下的讀寫分離:

spring boot 2.X版本中,依賴的spring-session-data-redis已經默認替換成Lettuce了。

同樣,例如我們有6臺機器組成的redis-cluster:

172.20.52.85:7000、 172.20.52.85:7001、172.20.52.85:7002、172.20.52.85:7003、172.20.52.85:7004、172.20.52.85:7005

其中master機器對應端口:7000、7004、7005

slave對應端口:7001、7002、7003

在spring boot 2.X版本中使用Lettuce操作redis-cluster數據的方法參考如下:

(1)pom文件參考如下:

parent中指出spring boot的版本,要求2.X以上:

org.springframework.boot

spring-boot-starter-parent

2.0.4.RELEASE

依賴中需要加入spring-boot-starter-data-redis,參考如下:

org.springframework.boot

spring-boot-starter-data-redis

(2)springboot的配置文件要包含如下內容:

spring.redis.database=0

spring.redis.lettuce.pool.max-idle=10

spring.redis.lettuce.pool.max-wait=500

spring.redis.cluster.timeout=1000

spring.redis.cluster.max-redirects=3

spring.redis.cluster.nodes=172.20.52.85:7000,172.20.52.85:7001,172.20.52.85:7002,172.20.52.85:7003,172.20.52.85:7004,172.20.52.85:7005

(3)我們回到RedisConfiguration類中,刪除或屏蔽之前的RedisTemplate方法,新增自定義的redisClusterConnection方法,並且設置好讀寫分離,參考代碼如下:

@Configuration

public class RedisConfiguration {

@Autowired

private Environment environment;

@Bean

public StatefulRedisClusterConnection redisClusterConnection(){

String strRedisClusterNodes = environment.getProperty("spring.redis.cluster.nodes");

String[] listNodesInfos = strRedisClusterNodes.split(",");

List listRedisURIs = new ArrayList();

for(String tmpNodeInfo : listNodesInfos){

String[] tmpInfo = tmpNodeInfo.split(":");

listRedisURIs.add(new RedisURI(tmpInfo[0],Integer.parseInt(tmpInfo[1]),Duration.ofDays(10)));

}

RedisClusterClient clusterClient = RedisClusterClient.create(listRedisURIs);

StatefulRedisClusterConnection connection = clusterClient.connect();

connection.setReadFrom(ReadFrom.SLAVE);

return connection;

}

}

其中,這三行代碼是能進行redis-cluster架構下讀寫分離的核心:

RedisClusterClient clusterClient = RedisClusterClient.create(listRedisURIs);

StatefulRedisClusterConnection connection = clusterClient.connect();

connection.setReadFrom(ReadFrom.SLAVE);

在業務類service中注入Lettuce相關的redisClusterConnection,進行相關讀寫操作。以下是我直接化簡到了springbootstarter中進行,參考代碼如下:

@SpringBootApplication

public class NewRedisClientApplication {

public static void main(String[] args) {

ApplicationContext context = SpringApplication.run(NewRedisClientApplication.class, args);

StatefulRedisClusterConnection redisClusterConnection = (StatefulRedisClusterConnection)context.getBean("redisClusterConnection");

System.out.println(redisClusterConnection.sync().get("event:10"));

}

}

運行的結果如下圖所示:

redis架構演變與redis-cluster群集讀寫方案

可以看到,經過改寫的redisClusterConnection的確能讀取到redis-cluster的數據。但這一個數據我們還需要驗證一下到底是不是通過slave讀取到的,又或者還是通過slave重定向給master才獲取到的?

帶著疑問,我們可以開通debug模式,在redisClusterConnection.sync().get("event:10")等類似的獲取數據的代碼行上面打上斷點。通過代碼的走查,我們可以看到,在ReadFromImpl類中,最終會select到key所在的slave節點,進行返回,並在該slave中進行數據的讀取:

ReadFromImpl顯示:

redis架構演變與redis-cluster群集讀寫方案

另外我們通過connectFuture中的顯示也驗證了對於slave的readonly生效了:

redis架構演變與redis-cluster群集讀寫方案

這樣,就達到了通過Lettuce客戶端對redis-cluster的讀寫分離了。

使用該方案進行redis-cluster性能的提升的優點有:

(1)直接通過代碼級更改,而不需要配置新的redis-cluster環境。

(2)無需增加機器或升級硬件設備。

但同時,該方案也有缺點:

(1)非官方對redis-cluster的推薦方案,因為在redis-cluster架構下,進行讀寫分離,有可能會讀到過期的數據。

(2)需對項目進行全面的替換,將Jedis客戶端變為Lettuce客戶端,對代碼的改動較大,而且使用Lettuce時,使用的並非spring boot的自帶集成Lettuce的redisTemplate配置方法,而是自己配置讀寫分離的 redisClusterConnetcion,日後遇到問題的時候,可能官方文檔的支持率或支撐能力會比較低。

(3)需修改redis-cluster的master、slave配置,在各個節點中都需要加入slave-read-only yes。

(4)性能的提升沒有水平擴展master主機和實例來得直接乾脆。

總結

總體上來說,redis-cluster高可用架構方案是目前最好的redis架構方案,redis的官方對redis-cluster架構是建議redis-master用於接收讀寫,而redis-slave則用於備份(備用),默認不建議讀寫分離。但如果不介意讀取的是redis-cluster中有可能過期的數據並且對寫請求不感興趣時,則亦可通過readonly命令,將slave設置成可讀,然後通過slave獲取相關的key,達到讀寫分離。Jedis、Lettuce都可以進行redis-cluster的讀寫操作,而且默認只針對Master進行讀寫,若要對redis-cluster架構下進行讀寫分離,則Jedis需要進行源碼的較大改動,而Lettuce開放了setReadFrom()方法,可以進行二次封裝成讀寫分離的客戶端,相對簡單,而且Lettuce比Jedis更安全。redis-cluster架構下可以直接通過水平擴展master來達到性能的提升。


分享到:


相關文章: