Redis基于客戶端分片的集群案例(待實踐)

說明:

下面的示例基本都是基于Linux去實現,目的是為了環境的統一,以便于把性能調整到最優。且基于Java。建議生產環境不要使用Windows/Mac OS這些。

在Java領域,基于客戶端進行分片最常用的庫應該是Jedis,下面基本是基于Jedis進行實例實踐。當然,除了這個還可以基于自己的業務去實現。

現在官方已經出到了4.0版本,也同樣支持了集群功能,那么現在市面上基本不用客戶端去實現分片做集群,主要集中在服務端來達到高可用的Redis集群,所以,是否有必要客戶端去實現集群,需要在自己的業務上來深入考究。

同樣的,除了官方集群外,還有很多成熟的方案去實現服務端集群,比如推特、豌豆莢這些官方開源的方案等。

在客戶端進行分片來達到集群的效果,最簡單的理解應該是這樣:A和B兩個Key,通過Hash得到A放在Redis1,B放在Redis2中。(先忽略Redis其中一臺掛掉的問題,對于算法遠沒有在這里說的那么簡單)。

分片的大致原理都是基于Hash算法來制定哪個Key放到哪個Redis中。

在這篇http://www.cnblogs.com/EasonJim/p/7625738.html文章中提到的幾款客戶端中都已經實現了分片的操作。

下面是基于Jedis去實現了客戶端分片功能配置:

對于單實例的Redis的使用,我們可以用Jedis,并發環境下我們可以用JedisPool。但是這兩種方法否是針對于單實例的Redis的情況下使用的,但是有時候我們的業務可能不是單實例Redis能支撐的,那么我們這時候需要引入多個實例進行“數據分區”。其實好多人都說,用Redis集群不就搞定了嗎?但是Redis集群無論部署還是維護成本都比較高,對于一些業務來說,使用起來還是成本很高。所以,對我們來說更好的方案可能是在客戶端實現對數據的手動分區.

對于分區的方案,我感覺大多數人都會想到Hash,的確Hash是最簡單最有效的方式。但是Hash的問題是:“單節點掛掉不可用,數據量大了不好擴容”。對于如果業務的可靠性要求不高同時數據可控的情況下可以考慮數據分區的方式。

其實數據分區就是Shard,其實Redis已經對Shard有很好的支持了,用到的是ShardedJedisPool,接下來簡單的搞一下數據分片:

package redis.clients.jedis.tests;import org.junit.Before;
import org.junit.Test;
import redis.clients.jedis.*;import java.util.ArrayList;
import java.util.List;/*** ShardJedis的測試類*/
public class ShardJedisTest {private ShardedJedisPool sharedPool;@Beforepublic void initJedis(){JedisPoolConfig config =new JedisPoolConfig();//Jedis池配置config.setTestOnBorrow(true);String hostA = "127.0.0.1";int portA = 6381;String hostB = "127.0.0.1";int portB = 6382;List<JedisShardInfo> jdsInfoList =new ArrayList<JedisShardInfo>(2);JedisShardInfo infoA = new JedisShardInfo(hostA, portA);JedisShardInfo infoB = new JedisShardInfo(hostB, portB);jdsInfoList.add(infoA);jdsInfoList.add(infoB);sharedPool =new ShardedJedisPool(config, jdsInfoList);}@Testpublic void testSetKV() throws InterruptedException {try {for (int i=0;i<50;i++){String key = "test"+i;ShardedJedis jedisClient = sharedPool.getResource();System.out.println(key+":"+jedisClient.getShard(key).getClient().getHost()+":"+jedisClient.getShard(key).getClient().getPort());System.out.println(jedisClient.set(key,Math.random()+""));jedisClient.close();}}catch (Exception e){e.printStackTrace();}}}

這里我是用JUnit做的測試,我在本機開了兩個Redis實例:

端口號分別是6381和6382。然后用ShardedJedisPool實現了一個Shard,主要是生成了50個Key,分別存到Redis中。運行結果如下:

test0:127.0.0.1:6382
OK
test1:127.0.0.1:6382
OK
test2:127.0.0.1:6381
OK
test3:127.0.0.1:6382
OK
test4:127.0.0.1:6382
OK
test5:127.0.0.1:6382
OK
test6:127.0.0.1:6382
OK
test7:127.0.0.1:6382
OK
test8:127.0.0.1:6381
OK
test9:127.0.0.1:6381

可以看到,KV分別分發到了不同的Redis實例,這種Shard的方式需要我們提前計算好數據量的大小,便于決定實例的個數。同時這種shard的可靠性不是很好,如果單個Redis實例掛掉了,那么這個實例便不可用了。

其實Shard使用起來很簡單,接下來我們看看ShardedJedisPool的具體的實現:

首先在初始化ShardedJedisPool的時候我們需要創建一個JedisShardInfo實例,JedisShardInfo主要是對單個連接的相關配置:

public class JedisShardInfo extends ShardInfo<Jedis> {private static final String REDISS = "rediss";private int connectionTimeout;private int soTimeout;private String host;private int port;private String password = null;private String name = null;// Default Redis DBprivate int db = 0;private boolean ssl;private SSLSocketFactory sslSocketFactory;private SSLParameters sslParameters;private HostnameVerifier hostnameVerifier;?

像連接超時時間、發送超時時間、Host和port等。這些都是之前我們實例化Jedis用到的。

同時還需要進行JedisPoolConfig的設置,可以猜到ShardedJedisPool也是基于JedisPool來實現的。

看看ShardedJedisPool的構造:

  public ShardedJedisPool(final GenericObjectPoolConfig poolConfig, List<JedisShardInfo> shards) {this(poolConfig, shards, Hashing.MURMUR_HASH);}public ShardedJedisPool(final GenericObjectPoolConfig poolConfig, List<JedisShardInfo> shards,Hashing algo) {this(poolConfig, shards, algo, null);}public ShardedJedisPool(final GenericObjectPoolConfig poolConfig, List<JedisShardInfo> shards,Hashing algo, Pattern keyTagPattern) {super(poolConfig, new ShardedJedisFactory(shards, algo, keyTagPattern));}public Pool(final GenericObjectPoolConfig poolConfig, PooledObjectFactory<T> factory) {initPool(poolConfig, factory);}public void initPool(final GenericObjectPoolConfig poolConfig, PooledObjectFactory<T> factory) {if (this.internalPool != null) {try {closeInternalPool();} catch (Exception e) {}}this.internalPool = new GenericObjectPool<T>(factory, poolConfig);}

構造方法很長,但是很清晰,關鍵點在ShardedJedisFactory的構建,因為這是使用commons-pool的必要工廠類。同時我們可以看到,這里分分片策略使用的確實是Hash,而且還是沖突率很低的MURMUR_HASH。

那么我們直接看ShardedJedisFactory類就好了,因為commons-pool就是基于這個工廠類來管理相關的對象的,這里緩存的對象是ShardedJedis

我們先看一下ShardedJedisFactory:

    public ShardedJedisFactory(List<JedisShardInfo> shards, Hashing algo, Pattern keyTagPattern) {this.shards = shards;this.algo = algo;this.keyTagPattern = keyTagPattern;}@Overridepublic PooledObject<ShardedJedis> makeObject() throws Exception {ShardedJedis jedis = new ShardedJedis(shards, algo, keyTagPattern);return new DefaultPooledObject<ShardedJedis>(jedis);}@Overridepublic void destroyObject(PooledObject<ShardedJedis> pooledShardedJedis) throws Exception {final ShardedJedis shardedJedis = pooledShardedJedis.getObject();for (Jedis jedis : shardedJedis.getAllShards()) {try {try {jedis.quit();} catch (Exception e) {}jedis.disconnect();} catch (Exception e) {}}}@Overridepublic boolean validateObject(PooledObject<ShardedJedis> pooledShardedJedis) {try {ShardedJedis jedis = pooledShardedJedis.getObject();for (Jedis shard : jedis.getAllShards()) {if (!shard.ping().equals("PONG")) {return false;}}return true;} catch (Exception ex) {return false;}}

其實這里makeObject是創建一個ShardedJedis,同時ShardedJedis也是連接池里保存的對象。

可以看到destroyObject和validateObject都是將ShardedJedis里的redis實例當做了一個整體去對待,一個失敗,全部失敗。

接下來看下ShardedJedis的實現,這個里面主要做了Hash的處理和各個Shard的Client的緩存。

  public class ShardedJedis extends BinaryShardedJedis implements JedisCommands, Closeable {protected ShardedJedisPool dataSource = null;public ShardedJedis(List<JedisShardInfo> shards) {super(shards);}public ShardedJedis(List<JedisShardInfo> shards, Hashing algo) {super(shards, algo);}public ShardedJedis(List<JedisShardInfo> shards, Pattern keyTagPattern) {super(shards, keyTagPattern);}public ShardedJedis(List<JedisShardInfo> shards, Hashing algo, Pattern keyTagPattern) {super(shards, algo, keyTagPattern);}

?

這里的dataSource是對連接池的引用,用于在Close的時候資源返還。和JedisPool的思想差不多。

由于ShardedJedis是BinaryShardedJedis的子類,所以構造函數會一直向上調用,在Shard中:

  public Sharded(List<S> shards, Hashing algo, Pattern tagPattern) {this.algo = algo;this.tagPattern = tagPattern;initialize(shards);}private void initialize(List<S> shards) {nodes = new TreeMap<Long, S>();for (int i = 0; i != shards.size(); ++i) {final S shardInfo = shards.get(i);if (shardInfo.getName() == null) for (int n = 0; n < 160 * shardInfo.getWeight(); n++) {nodes.put(this.algo.hash("SHARD-" + i + "-NODE-" + n), shardInfo);}else for (int n = 0; n < 160 * shardInfo.getWeight(); n++) {nodes.put(this.algo.hash(shardInfo.getName() + "*" + shardInfo.getWeight() + n), shardInfo);}resources.put(shardInfo, shardInfo.createResource());}}

這里主要做整個ShardedJedis中Jedis緩存池的初始化和分片的實現,可以看到首先獲取shardInfo就是之前的JedisShardInfo,根據shardInfo生成多個槽位,將這些槽位存到TreeMap中,同時將shardInfo和Jedis的映射存到resources中。當我們做Client的獲取的時候:

首先調用ShardedJedisPool的getResource方法,從對象池中獲取一個ShardedJedis:

ShardedJedis jedisClient = sharedPool.getResource();

調用ShardedJedis的getShard方法獲取一個Jedis實例——一個shard。

  public R getShard(String key) {return resources.get(getShardInfo(key));}public S getShardInfo(String key) {return getShardInfo(SafeEncoder.encode(getKeyTag(key)));}public S getShardInfo(byte[] key) {SortedMap<Long, S> tail = nodes.tailMap(algo.hash(key));if (tail.isEmpty()) {return nodes.get(nodes.firstKey());}return tail.get(tail.firstKey());}

這里主要是對key做hash,然后去TreeMap中判斷,當前的key落在哪個區間上,再通過這個區間上的ShardInfo從resources的Map中獲取對應的Jedis實例。

這也就是說,每一個ShardedJedis都維護了所有的分片,將多個實例當成一個整體去使用,這也就導致,只要集群中一個實例不可用,整個ShardedJedis就不可用了。同時對于Hash的分片方式,是不可擴容的,擴容之后原本應該存儲在一起的數據就分離了。

其實這種是Jedis默認提供的分片方式,其實針對我們自己的場景我們也可以嘗試自己做一個路由機制,例如根據不同年份、月份的數據落到一個實例上。

而對于在Spring中集成Jedis分片來說,應該是做簡單的:

1、在properties中定義其它Redis

redis.host2=192.168.142.34  

2、注入Bean

<bean id = "shardedJedisPool" class = "redis.clients.jedis.ShardedJedisPool">  <constructor-arg index="0" ref="jedisPoolConfig"/>  <constructor-arg index="1">  <list>  <bean class="redis.clients.jedis.JedisShardInfo">  <constructor-arg index="0" value="${redis.host}"/>         <constructor-arg index="1" value="${redis.port}" type="int"/>  <constructor-arg index="2" value="${redis.timeout}" type="int"/>  <property name="password" value="${redis.password}"/>  </bean>  <bean class="redis.clients.jedis.JedisShardInfo">  <constructor-arg index="0" value="${redis.host2}"/>         <constructor-arg index="1" value="${redis.port}" type="int"/>  <constructor-arg index="2" value="${redis.timeout}" type="int"/>  <property name="password" value="${redis.password}"/>  </bean>  </list>             </constructor-arg>  
</bean>  

3、代碼使用

//獲取Bean
ShardedJedisPool shardedPool = (ShardedJedisPool)context.getBean("shardedJedisPool");  
ShardedJedis shardedJedis = shardedPool.getResource();  ...  
shardedPool.returnResource(shardedJedis);   
//操作
shardedJedis.set("test", "123");  
String president = shardedJedis.get("test");  
shardedJedis.del("test");  

?

參考:

http://www.jianshu.com/p/af0ea8d61dda(以上內容轉自此篇文章)

http://www.jianshu.com/p/37b5b6cdb277

http://www.jianshu.com/p/a1038eed6d44

http://blog.csdn.net/yfkiss/article/details/38944179

http://hello-nick-xu.iteye.com/blog/2078153(以上內容部分轉自此篇文章)

http://blog.csdn.net/benxiaohai529/article/details/52935216

http://blog.csdn.net/mid120/article/details/52799241

http://blog.csdn.net/lang_man_xing/article/details/38405269

http://www.cnblogs.com/hk315523748/p/6122263.html

http://ihenu.iteye.com/blog/2267881

http://blog.csdn.net/koushr/article/details/50956870

==>如有問題,請聯系我:easonjim#163.com,或者下方發表評論。<==

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/454915.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/454915.shtml
英文地址,請注明出處:http://en.pswp.cn/news/454915.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

mysql select 效能_MYSQL的聯合查詢最好是少用,效能差異巨大

同樣的功能,不同的寫法,時間和內存占用差了幾千倍,不廢話,直接上代碼第一種寫法:代碼如下:$RsDB::get($_ENV[DB],3,"SELECT * FROM _xiazhu WHERE uid IN(SELECT id FROM _user WHERE id<5000)");var_dump($Rs);內存和時間:內存使用:96514.53Kb 運行時間:1272.73m…

高效程序員應該養成的七個習慣

對于軟件工程師來說&#xff0c;工作也許意味著許多東西 -- 穩定的收入、做自己感興趣的項目、找一份更好工作的跳板&#xff0c;或者你只是喜歡與其他程序員共事。但說到“效率”&#xff0c;強調的是在一定時間內按質完成項目的能力。Phil Chu根據自己的經驗提出了高效程序員…

概率論與數理統計思維導圖知識框架_考研概率論與數理統計 綜合題型秘籍思維導圖① 隨機變量1~3章 [21考研上岸之旅]...

Hello World&#xff0c;我的朋友&#xff0c;這里是一顆小白蛋&#xff0c;大千世界&#xff0c;很高興以這樣的方式與你相遇前言在復習過程中發現概率論知識點很少且集中 所以沒有分開章節去整理王安式概率論輔導講義張宇概率論9講方浩強化班1800題 綜合題型秘籍在學習過程中…

關于軟件的架構設計

好的開始相當于成功一半 開始之初的架構設計決定著軟件產品的生死存亡。“好的開始相當于成功一半”。 開始的架構設計也是最難的&#xff0c;需要調研同類產品的情況以及技術特征&#xff0c;了解當前世界上對這種產品所能提供的理論支持和技術平臺支持。再結合自己項目的特…

[Oracle]快速構造大量數據的方法

[Oracle]快速構造大量數據的方法&#xff1a;create table tab001(id integer primary key, val varchar2(100));insert into tab001 select ij,rpad(to_char(ij),100,A) from ( with DATA2(j) as ( select 0 j from DUAL …

mysql用supervisor管理_Supervisor使用詳解

一、supervisor簡介Supervisor是用Python開發的一套通用的進程管理程序&#xff0c;能將一個普通的命令行進程變為后臺daemon&#xff0c;并監控進程狀態&#xff0c;異常退出時能自動重啟。它是通過fork/exec的方式把這些被管理的進程當作supervisor的子進程來啟動&#xff0c…

Handle/Body pattern(Wrapper pattern)

Handle Body Pattern 一些設計模式&#xff0c;通過一系列非直接的間接的方式&#xff08;這種間接的方式&#xff0c;可稱其為 handle&#xff08;把手&#xff09;&#xff09;&#xff0c;完成接口與實現&#xff08;實現可稱為 body&#xff08;主體&#xff09;&#xff0…

架構設計貴在務實

(本文是我給溫昱先生新書寫的讀后感節選。&#xff09; 我最早聽說“軟件架構”這個概念以及UML的名字&#xff0c;是在1999年的水木清華BBS上。當時有一篇文章介紹了軟件架構作為一個相對獨立的領域的發展情況&#xff0c;順便提到在此前一年被接納為OMG標準的UML。該文作者…

php mysql刪除失敗_php+MySQL實戰案例【七】數據編輯、刪除

?本節內容中講解用戶管理模塊中的修改用戶信息和刪除用戶。修改用戶信息&#xff1a;可對選擇的用戶記錄進行編輯&#xff0c;可修改用戶名、密碼、性別、手機、郵箱和地址信息。刪除用戶信息&#xff1a;將列表中指定的用戶進行刪除操作。二、編輯用戶信息編輯用戶信息&#…

前端每周清單第 34 期:Vue 現狀盤點與 3.0 展望,React 代碼遷移與優化,圖片優化詳論...

作者&#xff1a;王下邀月熊 編輯&#xff1a;徐川 前端每周清單專注前端領域內容&#xff0c;以對外文資料的搜集為主&#xff0c;幫助開發者了解一周前端熱點&#xff1b;分為新聞熱點、開發教程、工程實踐、深度閱讀、開源項目、巔峰人生等欄目。歡迎關注【前端之巔】微信公…

1.three.js世界的4大要素

一、三大組件 在Three.js中&#xff0c;要渲染物體到網頁中&#xff0c;我們需要3個組建&#xff1a;場景&#xff08;scene&#xff09;、相機&#xff08;camera&#xff09;和渲染器&#xff08;renderer&#xff09;。有了這三樣東西&#xff0c;才能將物體渲染到網頁中去。…

架構設計 例子和實踐

系統設計說明書(架構、概要、詳細)目錄結構 雖然這些文檔一般來說公司都是有模板的&#xff0c;但我寫這些文檔以來基本上是每寫一次就把目錄結構給改一次&#xff0c;應該說這是因為自己對這些文檔的理解開始加深&#xff0c;慢慢的越來越明白這些文檔的作用和其中需要闡述的東…

activiti 批量 mysql_Activiti6系列(3)- 快速體驗

一、部署啟動activiti####1、部署&#xff0c;將兩個war包拷貝到Tomcat下即可。![](https://img2018.cnblogs.com/blog/1755845/201908/1755845-20190802010532367-2002399291.png)2、啟動tomcat&#xff0c;訪問http://127.0.0.1:8080/activiti-app默認賬號密碼&#xff1a;ad…

【啟動】Windows上啟動圖形化軟件,報錯: 無法啟動此程序,因為計算機中丟失api-ms-win-crt-runtime-1-1-0.dll...

今天在安裝了jetbrains官網上的ToolBox軟件后&#xff0c;沒有辦法啟動起來&#xff0c;報錯如下&#xff1a; 無法啟動此程序&#xff0c;因為計算機中丟失api-ms-win-crt-runtime-1-1-0.dll 嘗試重新安裝了并沒有什么用&#xff0c;現在來解決方法&#xff1a; 直接找到官方的…

論文: YOLO9000-Better,Faster,Stronger

論文閱讀&#xff1a; YOLO9000-Better,Faster,Stronger YOLOv2 是經過改造之后的YOLO Batch Normalization&#xff1a;在所有的conv layer后加了BN之后提高了2% mAP&#xff0c;BN可以幫助regularize模型&#xff0c;這樣的話就可以放棄 dropout。 High Resolution Classifi…

怎么ie取消要打開或保存來自_取消認證后,發票抵扣就這么簡單!

點擊標題下「中財訊集團」可快速關注3月1日起&#xff0c;取消增值稅發票認證的納稅人范圍擴大至全部一般納稅人。一般納稅人可以自愿使用增值稅發票選擇確認平臺查詢、選擇用于申報抵扣、出口退稅或者代辦退稅的增值稅發票信息。具體如何操作呢&#xff1f;掌握以下步驟&#…

Google發布文檔數據庫Firestore

Google發布了Cloud Firestore&#xff0c;它是用于移動、網絡和服務器應用程序的文檔數據庫。\\去年我們曾報道過Google Firebase&#xff0c;它是一種用于移動和網絡開發的數據庫&#xff0c;提供實時的和離線的數據訪問&#xff0c;與許多Google服務相集成。他們現在推出了一…

好的軟件架構設計

什么是架構 前言&#xff1a;軟體設計師中有一些技術水平較高、經驗較為豐富的人&#xff0c;他們需要承擔軟件系統的架構設計&#xff0c;也就是需要設計系統的元件如何劃分、元件之間如何發生相互作用&#xff0c;以及系統中邏輯的、物理的、系統的重要決定的作出。在很多公…

HihoCoder 1323 回文字符串

回文字符串 思路 動態規劃&#xff1a; 可以有三種修改決策 將開頭和結尾字符改成一樣在開頭加一個和末尾相同的字符在末尾加一個和開頭形同的字符代碼&#xff1a; #include <stdio.h> #include <iostream> #include <string.h> using namespace std; char …

python線程狀態_Python線程

1. 線程基礎1.1. 線程狀態線程有5種狀態&#xff0c;狀態轉換的過程如下圖所示&#xff1a;1.2. 線程同步(鎖)多線程的優勢在于可以同時運行多個任務(至少感覺起來是這樣)。但是當線程需要共享數據時&#xff0c;可能存在數據不同步的問題。考慮這樣一種情況&#xff1a;一個列…