上一篇文章基於redis的分佈式鎖實現寫了基於redis實現的分佈式鎖。分佈式環境下,不會還使用單點的redis,作到高可用和容災,起碼也是redis主從。redis的單線程工做,一臺物理機只運行一個redis實例太過浪費,redis單機顯然是存在單點故障的隱患。內存資源每每受限,縱向不停擴展內存並非很實際,所以橫向可伸縮擴展,須要多臺主機協同提供服務,即分佈式下多個Redis實例協同運行。前端
在以前的文章Redis Cluster深刻與實踐介紹過Redis Cluster的相關內容,以前特意花時間在redis官網看了redis cluster的相關文檔和實現。本文是那篇文章的續集,由於筆者最近在調研redis的主從切換到redis 集羣的方案,將會講下redis集羣的幾種方案選型和redis cluster的實踐。java
redis集羣的幾種實現方式以下:node
下面咱們分別介紹下這幾種方案。git
Redis Sharding是Redis Cluster出來以前,業界廣泛使用的多Redis實例集羣方法。其主要思想是採用哈希算法將Redis數據的key進行散列,經過hash函數,特定的key會映射到特定的Redis節點上。java redis客戶端驅動jedis,支持Redis Sharding功能,即ShardedJedis以及結合緩存池的ShardedJedisPool。github
Redis Sentinel提供了主備模式下Redis監控、故障轉移功能達到系統的高可用性。在主Redis宕機時,備Redis接管過來,上升爲主Redis,繼續提供服務。主備共同組成一個Redis節點,經過自動故障轉移,保證了節點的高可用性。web
客戶端sharding技術其優點在於很是簡單,服務端的Redis實例彼此獨立,相互無關聯,每一個Redis實例像單服務器同樣運行,很是容易線性擴展,系統的靈活性很強。redis
客戶端sharding的劣勢也是很明顯的。因爲sharding處理放到客戶端,規模進一步擴大時給運維帶來挑戰。客戶端sharding不支持動態增刪節點。服務端Redis實例羣拓撲結構有變化時,每一個客戶端都須要更新調整。鏈接不能共享,當應用規模增大時,資源浪費制約優化。算法
客戶端發送請求到一個代理組件,代理解析客戶端的數據,並將請求轉發至正確的節點,最後將結果回覆給客戶端。spring
該模式的特性以下:數據庫
簡單的結構圖以下:
主流的組件有:Twemproxy和Codis。
Twemproxy也叫nutcraker,是twtter開源的一個redis和memcache代理服務器程序。redis做爲一個高效的緩存服務器,很是具備應用價值。但在用戶數據量增大時,須要運行多個redis實例,此時將迫切須要一種工具統一管理多個redis實例,避免在每一個客戶端管理全部鏈接帶來的不方便和不易維護,Twemproxy即爲此目標而生。
Twemproxy有如下幾個特色:
TwemProxy 官網介紹瞭如上的特性。TwemProxy的使用能夠像訪問redis客戶端同樣訪問TwemProxy。然而Twitter已經好久放棄了更新TwemProxy。Twemproxy最大的痛點在於,沒法平滑地擴容/縮容。Twemproxy另外一個痛點是,運維不友好,甚至沒有控制面板。
Codis是豌豆莢開源的redis集羣方案,是一個分佈式 Redis 解決方案, 對於上層的應用來講, 鏈接到 Codis Proxy 和鏈接原生的 Redis Server 沒有顯著區別 , 上層應用能夠像使用單機的 Redis 同樣使用, Codis 底層會處理請求的轉發, 不停機的數據遷移等工做, 全部後邊的一切事情, 對於前面的客戶端來講是透明的, 能夠簡單的認爲後邊鏈接的是一個內存無限大的 Redis 服務。
Codis當前最新release 版本爲 codis-3.2,codis-server 基於 redis-3.2.8。有一下組件組成:
至於具體的安裝與使用,見官網CodisLabs,不在此涉及。
Codis的特性:
Redis Cluster是一種服務器Sharding技術,3.0版本開始正式提供。Redis Cluster並無使用一致性hash,而是採用slot(槽)的概念,一共分紅16384個槽。將請求發送到任意節點,接收到請求的節點會將查詢請求發送到正確的節點上執行。當客戶端操做的key沒有分配到該node上時,就像操做單一Redis實例同樣,當客戶端操做的key沒有分配到該node上時,Redis會返回轉向指令,指向正確的node,這有點兒像瀏覽器頁面的302 redirect跳轉。
Redis集羣,要保證16384個槽對應的node都正常工做,若是某個node發生故障,那它負責的slots也就失效,整個集羣將不能工做。爲了增長集羣的可訪問性,官方推薦的方案是將node配置成主從結構,即一個master主節點,掛n個slave從節點。這時,若是主節點失效,Redis Cluster會根據選舉算法從slave節點中選擇一個上升爲主節點,整個集羣繼續對外提供服務。
特色:
缺點是運維也很複雜,數據遷移須要人工干預,只能使用0號數據庫,不支持批量操做,分佈式邏輯和存儲模塊耦合等。
選型最後肯定redis cluster。主要緣由是性能高,去中心化支持擴展。運維方面的數據遷移暫時業內也沒有特別成熟的方案解決,redis cluster是redis官方提供,咱們期待redis官方在後面可以完美支持。
官方推薦集羣至少須要六個節點,即三主三從。六個節點的配置文件基本相同,只須要修改端口號。
port 7000
cluster-enabled yes #開啓集羣模式
cluster-config-file nodes.conf
cluster-node-timeout 5000
appendonly yes
複製代碼
啓動後,能夠看到以下的日誌。
[82462] 26 Nov 11:56:55.329 * No cluster configuration found, I'm 97a3a64667477371c4479320d683e4c8db5858b1
因爲沒有nodes.conf存在,每一個實例啓動後都會給本身分配一個ID。爲了在集羣的環境中有一個惟一的名字,該ID將會被永久使用。每一個實例都會保存其餘節點使用的ID,而不是經過IP和端口。IP和端口可能會改變,可是惟一的node ID將不會改變直至該node的死亡。
咱們如今已經啓動了六個redis實例, 須要經過寫一些有意義的配置信息到各個節點來建立集羣。 redis cluster的命令行工具redis-trib,利用Ruby程序在實例上執行一些特殊的命令,很容易實現建立新的集羣、檢查或者reshard現有的集羣等。
./redis-trib.rb create --replicas 1 127.0.0.1:7000 127.0.0.1:7001 \
127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005
複製代碼
--replicas 1
參數是將每一個master帶上一個slave。
@Configuration
public class JedisClusterConfig {
private static Logger logger = LoggerFactory.getLogger(JedisClusterConfig.class);
@Value("${redis.cluster.nodes}")
private String clusterNodes;
@Value("${redis.cluster.timeout}")
private int timeout;
@Value("${redis.cluster.max-redirects}")
private int redirects;
@Autowired
private JedisPoolConfig jedisPoolConfig;
@Bean
public RedisClusterConfiguration getClusterConfiguration() {
Map<String, Object> source = new HashMap();
source.put("spring.redis.cluster.nodes", clusterNodes);
logger.info("clusterNodes: {}", clusterNodes);
source.put("spring.redis.cluster.max-redirects", redirects);
return new RedisClusterConfiguration(new MapPropertySource("RedisClusterConfiguration", source));
}
@Bean
public JedisConnectionFactory getConnectionFactory() {
JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory(getClusterConfiguration());
jedisConnectionFactory.setTimeout(timeout);
return jedisConnectionFactory;
}
@Bean
public JedisClusterConnection getJedisClusterConnection() {
return (JedisClusterConnection) getConnectionFactory().getConnection();
}
@Bean
public RedisTemplate getRedisTemplate() {
RedisTemplate clusterTemplate = new RedisTemplate();
clusterTemplate.setConnectionFactory(getConnectionFactory());
clusterTemplate.setKeySerializer(new StringRedisSerializer());
clusterTemplate.setDefaultSerializer(new GenericJackson2JsonRedisSerializer());
return clusterTemplate;
}
}
複製代碼
能夠配置密碼,cluster對密碼支持不太友好,若是對集羣設置密碼,那麼requirepass和masterauth都須要設置,不然發生主從切換時,就會遇到受權問題。
redis:
cluster:
enabled: true
timeout: 2000
max-redirects: 8
nodes: 127.0.0.1:7000,127.0.0.1:7001
複製代碼
主要配置了redis cluster的節點、超時時間等。
@RunWith(SpringRunner.class)
@SpringBootTest
public class RedisConfigTest {
@Autowired
RedisTemplate redisTemplate;
@Test
public void clusterTest() {
redisTemplate.opsForValue().set("foo", "bar");
System.out.println(redisTemplate.opsForValue().get("foo"));
}
}
複製代碼
用法很簡單,注入RedisTemplate便可進行操做,RedisTemplate用法比較豐富,能夠自行查閱。
本文主要講了redis集羣的選型,主要有三種:客戶端分片、基於代理的分片以及路由查詢。對於前兩種方式,分別進行簡單地介紹,最後選擇redis官方提供的redis cluster方案,並進行了實踐。雖然正式版的推出時間不長,目前成功實踐的案例也還很少,可是整體來講,redis cluster的整個設計是比較簡單的,大部分操做均可以按照單點的操做流程進行操做。筆者使用的jedis客戶端支持JedisCluster也是比較好,用起來也很方便。其實還有個壓測的數據,後面再補上吧。