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架構下的數據讀寫分離。html
redis是基於內存的高性能key-value數據庫,若要讓redis的數據更穩定安全,須要引入多實例以及相關的高可用架構。而近年來redis的高可用架構亦不斷改進,前後出現了本地持久化、主從備份、哨兵模式、redis-cluster羣集高可用架構等等方案。node
經過持久化功能,Redis保證了即便在服務器重啓的狀況下也不會損失(或少許損失)數據,由於持久化會把內存中數據保存到硬盤上,重啓會從硬盤上加載數據。 。可是因爲數據是存儲在一臺服務器上的,若是這臺服務器出現硬盤故障等問題,也會致使數據丟失。爲了不單點故障,一般的作法是將數據庫複製多個副本以部署在不一樣的服務器上,這樣即便有一臺服務器出現故障,其餘服務器依然能夠繼續提供服務。爲此, Redis 提供了複製(replication)功能,能夠實現當一臺數據庫中的數據更新後,自動將更新的數據同步到其餘數據庫上。git
在複製的概念中,數據庫分爲兩類,一類是主數據庫(master),另外一類是從數據庫(slave)。主數據庫能夠進行讀寫操做,當寫操做致使數據變化時會自動將數據同步給從數據庫。而從數據庫通常是隻讀的,並接受主數據庫同步過來的數據。一個主數據庫能夠擁有多個從數據庫,而一個從數據庫只能擁有一個主數據庫。
github
主從模式的配置,通常只須要再做爲slave的redis節點的conf文件上加入「slaveof masterip masterport」, 或者做爲slave的redis節點啓動時使用以下參考命令:redis
redis-server --port 6380 --slaveof masterIp masterPort
redis的普通主從模式,能較好地避免單獨故障問題,以及提出了讀寫分離,下降了Master節點的壓力。互聯網上大多數的對redis讀寫分離的教程,都是基於這一模式或架構下進行的。但實際上這一架構並不是是目前最好的redis高可用架構。算法
當主數據庫遇到異常中斷服務後,開發者能夠經過手動的方式選擇一個從數據庫來升格爲主數據庫,以使得系統可以繼續提供服務。然而整個過程相對麻煩且須要人工介入,難以實現自動化。 爲此,Redis 2.8開始提供了哨兵工具來實現自動化的系統監控和故障恢復功能。 哨兵的做用就是監控redis主、從數據庫是否正常運行,主出現故障自動將從數據庫轉換爲主數據庫。spring
顧名思義,哨兵的做用就是監控Redis系統的運行情況。它的功能包括如下兩個。數據庫
(1)監控主數據庫和從數據庫是否正常運行。
(2)主數據庫出現故障時自動將從數據庫轉換爲主數據庫。安全
能夠用info replication查看主從狀況 例子: 1主2從 1哨兵,能夠用命令起也能夠用配置文件裏 可使用雙哨兵,更安全,參考命令以下:springboot
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
即便使用哨兵,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-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節點,A一、B一、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中任意一個端口,進行數據獲取測試。
因爲Jedis類通常只能對一臺redis-master進行數據操做,因此面對redis-cluster多臺master與slave的羣集,Jedis類就不能知足了。這個時候咱們須要引用另一個操做類:JedisCluster類。
例如咱們有6臺機器組成的redis-cluster:
172.20.52.85:7000、 172.20.52.85:700一、172.20.52.85:700二、172.20.52.85:700三、172.20.52.85:700四、172.20.52.85:7005
其中master機器對應端口:7000、700四、7005
slave對應端口:700一、700二、7003
使用JedisCluster對redis-cluster進行數據操做的參考代碼以下:
// 添加nodes服務節點到Set集合 Set<HostAndPort> hostAndPortsSet = new HashSet<HostAndPort>(); // 添加節點 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-cluster架構下master提供讀寫功能,而slave通常只做爲對應master機器的數據備份不提供讀寫。若是咱們只在hostAndPortsSet中只配置slave,而不配置master,實際上仍是能夠讀到數據,但其內部操做實際是經過slave重定向到相關的master主機上,而後再將結果獲取和輸出。
上面是普通項目使用JedisCluster的簡單過程,若在spring boot項目中,能夠定義JedisConfig類,使用@Configuration、@Value、@Bean等一些列註解完成JedisCluster的配置,而後再注入該JedisCluster到相關service邏輯中引用,這裏介紹略。
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:700一、172.20.52.85:700二、172.20.52.85:700三、172.20.52.85:700四、172.20.52.85:7005
其中master機器對應端口:7000、700四、7005
slave對應端口:700一、700二、7003
在spring boot 2.X版本中使用Lettuce操做redis-cluster數據的方法參考以下:
(1)pom文件參考以下:
parent中指出spring boot的版本,要求2.X以上:
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.4.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent>
依賴中須要加入spring-boot-starter-data-redis,參考以下:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
(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<String, Serializable> redisTemplate() { RedisTemplate<String, Serializable> 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<String, Object> source = new HashMap<String, Object>(); 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); } }
運行結果的截圖以下:
以上的介紹,是採用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近年來的高可用架構的演變,以及在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說明內容:
實際上自己master就是能夠任意擴展的,因此若是要支撐更大的讀吞吐量,或者寫吞吐量,或者數據量,均可以直接對master進行橫向水平擴展就能夠了。也就是說,擴容master,跟以前擴容slave並進行讀寫分離,效果是同樣的或者說更好。
因此下面咱們將按照redis-cluster架構下分別進行水平擴展Master,以及在redis-cluster架構下對master、slave進行讀寫分離兩套方案進行講解。
咱們能夠經過master的水平擴展,來直接提高讀寫吞吐量,而且能支撐更多的海量數據。對master進行水平擴展有兩種方法,一種是在原有的機器基礎上面進行master實例的增長,另外一種是新增機器部署新的master實例。同時,咱們建議每新增一個master,也至少新增一個對應的slave。
(1) 在原有機器上經過啓動新進程創建新redis-master實例:
咱們能夠在原redis-cluster架構中的每臺物理機上增長redis進程實例:
通常的,對於redis單個實例的讀吞吐是4w/s~5W/s,寫吞吐爲2w/s。
在同一臺機器上合理開啓redis多個實例狀況下(通常實例或線程數最好爲CPU核數的倍數),總吞吐量會有所上升,但每一個實例線程的平均處理能力會有所降低。例如一個2核CPU,開啓2個實例的時候,總讀吞吐能上升是6W/s~7W/s,即每一個實例線程平均約3W/s再多一些。但同一臺物理機器中過多的redis實例反而會限制了總吞吐量,並且一旦一臺物理機器失效,會致使多個實例同時失效,從而致使redis-cluster全體失效的風險增長。
在機器上新增redis實例須要分開路徑在不一樣的文件夾內部署新的redis運行環境,而且配置好redis.conf文件,分配好不一樣的port,而後再分別使用命令啓動。
(2)新增機器,部署新redis-master實例,即物理上的水平擴展:
例如,咱們能夠再原來只有3臺master的基礎上,連入新機器繼續新實例的部署,最終水平擴展爲6臺master(建議每新增一個master,也新增一個對應的slave)。例如以前每臺master的處理能力假設是讀吞吐5W/s,寫吞吐2W/s,擴展前一共的處理能力是:15W/s讀,6W/s寫。若是咱們水平擴展到6臺master,讀吞吐能夠達到總量30W/s,寫能夠達到12w/s,性能可以成倍增長。
固然,若是咱們的機器性能良好,咱們能夠將上面兩種方式結合。例如若是須要水平擴展到6個master和6個slave甚至12個slave的redis-cluster,其實沒必要要引入真正的十幾臺機器來運行,實際上能夠將他們合理部署到6臺機器便可,以下圖所示:
其中,每一臺機器下面都有master和slave,但同一臺機器下的slave邏輯上並不是是屬於該物理機器下的master,而是交叉地服務與另一臺物理機器上的master,做爲其備份。這樣能避免當一臺物理機器失效了,就整個master和其對應的slave同時失效,進而致使整個redis-cluster則沒法繼續提供服務。錯開交叉方式的master-slave配置,具有更高的可用性。這樣的redis-cluster中的機器,即便損壞或失效兩臺,只要不是最壞的狀況下,仍能繼續運行提供redis服務。若是一個master對應不止一個slave,而是2個,則可用性又會再次提升。
上面經過了兩種方法介紹了水平擴展redis-cluster以達到能力和高可用性的提高,使用該方桉的優勢有:
(1)符合redis官方要求和數據的準確性。
(2)真實的水平擴展能達到更大吞吐量的性能擴展。
(3)新增機器部署多套實例與備份實例能極大增長穩定性與健壯性。
(4)無需代碼的大量更改,只需在配置文件中從新配置新的節點信息。
固然缺點也是有的:
(1)水平擴展須要新增機器來提高性能,這意味着會增長必定的成本。但不妨租用成本相對較低的雲服務器來部署新實例,一樣達到擴容的效果。
(2)若不新增機器,則須要原來的實例所運行的機器性能較好,能進行以多進程實例的方式部署新實例。但隨着進程/線程的增多,而機器的能力不足以支撐的時候,實際上整體能力會提高不太明顯,反而會增長單點失效致使的redis-cluster總體失效的風險。
(3)redis-cluster進行新的水平擴容後,須要對master進行新的hash slot從新分配。但咱們能夠經過經過rebuild數據,從新初始化redis數據到水平擴展後的redis-cluster中來忽略這一問題,這個操做對應用自己不影響。
經過上面的一些章節,咱們已經能夠了解到Lettuce客戶端讀取redis的一些操做,使用Lettuce能體現出了簡單,安全,高效。實際上,查閱了Lettuce對redis的讀寫,許多地方都進行了redis的讀寫分離。但這些都是基於上述redis架構中最普通的主從分離架構下的讀寫分離,而對於redis-cluster架構下,Lettuce多是遵循了redis官方的意見,在該架構下,Lettuce在源碼中直接設置了只由master上進行讀寫(具體參見gitHub的Lettuce項目):
那麼若是真的須要讓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:700一、172.20.52.85:700二、172.20.52.85:700三、172.20.52.85:700四、172.20.52.85:7005
其中master機器對應端口:7000、700四、7005
slave對應端口:700一、700二、7003
在spring boot 2.X版本中使用Lettuce操做redis-cluster數據的方法參考以下:
(1)pom文件參考以下:
parent中指出spring boot的版本,要求2.X以上:
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.4.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent>
依賴中須要加入spring-boot-starter-data-redis,參考以下:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
(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<RedisURI> listRedisURIs = new ArrayList<RedisURI>(); 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<String, String> connection = clusterClient.connect(); connection.setReadFrom(ReadFrom.SLAVE); return connection; } }
其中,這三行代碼是能進行redis-cluster架構下讀寫分離的核心:
RedisClusterClient clusterClient = RedisClusterClient.create(listRedisURIs); StatefulRedisClusterConnection<String, String> 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<String, String> redisClusterConnection = (StatefulRedisClusterConnection)context.getBean("redisClusterConnection"); System.out.println(redisClusterConnection.sync().get("event:10")); } }
運行的結果以下圖所示:
能夠看到,通過改寫的redisClusterConnection的確能讀取到redis-cluster的數據。但這一個數據咱們還須要驗證一下究竟是不是經過slave讀取到的,又或者仍是經過slave重定向給master才獲取到的?
帶着疑問,咱們能夠開通debug模式,在redisClusterConnection.sync().get("event:10")等相似的獲取數據的代碼行上面打上斷點。經過代碼的走查,咱們能夠看到,在ReadFromImpl類中,最終會select到key所在的slave節點,進行返回,並在該slave中進行數據的讀取:
ReadFromImpl顯示:
另外咱們經過connectFuture中的顯示也驗證了對於slave的readonly生效了:
這樣,就達到了經過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來達到性能的提高。
1,網文《關於redis主從、哨兵、集羣的介紹》:https://blog.csdn.net/c295477887/article/details/52487621 2,知乎《lettuce與jedis對比介紹》:https://www.zhihu.com/question/53124685 3,網文《Redis 高可用架構最佳實踐問答集錦》:http://www.talkwithtrend.com/Article/178165 4,網文《Redis進階實踐之十一 Redis的Cluster集羣搭建》:https://www.cnblogs.com/PatrickLiu/p/8458788.html 5,redis官方在線文檔:https://redis.io/ 6,網文《redis cluster的介紹及搭建(6)》:https://blog.csdn.net/qq1137623160/article/details/79184686 7,網文《Springboot2.X集成redis集羣(Lettuce)鏈接》:http://www.cnblogs.com/xymBlog/p/9303032.html 8,Jedis的gitHub地址:https://github.com/xetorthio/jedis 9,Lettuce的gitHub地址:https://github.com/lettuce-io/lettuce-core/