日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 运维知识 > 数据库 >内容正文

数据库

Redis基于客户端分片的集群案例(待实践)

發(fā)布時(shí)間:2023/12/19 数据库 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Redis基于客户端分片的集群案例(待实践) 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

說(shuō)明:

下面的示例基本都是基于Linux去實(shí)現(xiàn),目的是為了環(huán)境的統(tǒng)一,以便于把性能調(diào)整到最優(yōu)。且基于Java。建議生產(chǎn)環(huán)境不要使用Windows/Mac OS這些。

在Java領(lǐng)域,基于客戶端進(jìn)行分片最常用的庫(kù)應(yīng)該是Jedis,下面基本是基于Jedis進(jìn)行實(shí)例實(shí)踐。當(dāng)然,除了這個(gè)還可以基于自己的業(yè)務(wù)去實(shí)現(xiàn)。

現(xiàn)在官方已經(jīng)出到了4.0版本,也同樣支持了集群功能,那么現(xiàn)在市面上基本不用客戶端去實(shí)現(xiàn)分片做集群,主要集中在服務(wù)端來(lái)達(dá)到高可用的Redis集群,所以,是否有必要客戶端去實(shí)現(xiàn)集群,需要在自己的業(yè)務(wù)上來(lái)深入考究。

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

在客戶端進(jìn)行分片來(lái)達(dá)到集群的效果,最簡(jiǎn)單的理解應(yīng)該是這樣:A和B兩個(gè)Key,通過(guò)Hash得到A放在Redis1,B放在Redis2中。(先忽略Redis其中一臺(tái)掛掉的問(wèn)題,對(duì)于算法遠(yuǎn)沒(méi)有在這里說(shuō)的那么簡(jiǎn)單)。

分片的大致原理都是基于Hash算法來(lái)制定哪個(gè)Key放到哪個(gè)Redis中。

在這篇http://www.cnblogs.com/EasonJim/p/7625738.html文章中提到的幾款客戶端中都已經(jīng)實(shí)現(xiàn)了分片的操作。

下面是基于Jedis去實(shí)現(xiàn)了客戶端分片功能配置:

對(duì)于單實(shí)例的Redis的使用,我們可以用Jedis,并發(fā)環(huán)境下我們可以用JedisPool。但是這兩種方法否是針對(duì)于單實(shí)例的Redis的情況下使用的,但是有時(shí)候我們的業(yè)務(wù)可能不是單實(shí)例Redis能支撐的,那么我們這時(shí)候需要引入多個(gè)實(shí)例進(jìn)行“數(shù)據(jù)分區(qū)”。其實(shí)好多人都說(shuō),用Redis集群不就搞定了嗎?但是Redis集群無(wú)論部署還是維護(hù)成本都比較高,對(duì)于一些業(yè)務(wù)來(lái)說(shuō),使用起來(lái)還是成本很高。所以,對(duì)我們來(lái)說(shuō)更好的方案可能是在客戶端實(shí)現(xiàn)對(duì)數(shù)據(jù)的手動(dòng)分區(qū).

對(duì)于分區(qū)的方案,我感覺(jué)大多數(shù)人都會(huì)想到Hash,的確Hash是最簡(jiǎn)單最有效的方式。但是Hash的問(wèn)題是:“單節(jié)點(diǎn)掛掉不可用,數(shù)據(jù)量大了不好擴(kuò)容”。對(duì)于如果業(yè)務(wù)的可靠性要求不高同時(shí)數(shù)據(jù)可控的情況下可以考慮數(shù)據(jù)分區(qū)的方式。

其實(shí)數(shù)據(jù)分區(qū)就是Shard,其實(shí)Redis已經(jīng)對(duì)Shard有很好的支持了,用到的是ShardedJedisPool,接下來(lái)簡(jiǎn)單的搞一下數(shù)據(jù)分片:

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的測(cè)試類*/ 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做的測(cè)試,我在本機(jī)開了兩個(gè)Redis實(shí)例:

端口號(hào)分別是6381和6382。然后用ShardedJedisPool實(shí)現(xiàn)了一個(gè)Shard,主要是生成了50個(gè)Key,分別存到Redis中。運(yùn)行結(jié)果如下:

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分別分發(fā)到了不同的Redis實(shí)例,這種Shard的方式需要我們提前計(jì)算好數(shù)據(jù)量的大小,便于決定實(shí)例的個(gè)數(shù)。同時(shí)這種shard的可靠性不是很好,如果單個(gè)Redis實(shí)例掛掉了,那么這個(gè)實(shí)例便不可用了。

其實(shí)Shard使用起來(lái)很簡(jiǎn)單,接下來(lái)我們看看ShardedJedisPool的具體的實(shí)現(xiàn):

首先在初始化ShardedJedisPool的時(shí)候我們需要?jiǎng)?chuàng)建一個(gè)JedisShardInfo實(shí)例,JedisShardInfo主要是對(duì)單個(gè)連接的相關(guān)配置:

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;?

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

同時(shí)還需要進(jìn)行JedisPoolConfig的設(shè)置,可以猜到ShardedJedisPool也是基于JedisPool來(lái)實(shí)現(xiàn)的。

看看ShardedJedisPool的構(gòu)造:

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);}

構(gòu)造方法很長(zhǎng),但是很清晰,關(guān)鍵點(diǎn)在ShardedJedisFactory的構(gòu)建,因?yàn)檫@是使用commons-pool的必要工廠類。同時(shí)我們可以看到,這里分分片策略使用的確實(shí)是Hash,而且還是沖突率很低的MURMUR_HASH。

那么我們直接看ShardedJedisFactory類就好了,因?yàn)閏ommons-pool就是基于這個(gè)工廠類來(lái)管理相關(guān)的對(duì)象的,這里緩存的對(duì)象是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;}}

其實(shí)這里makeObject是創(chuàng)建一個(gè)ShardedJedis,同時(shí)ShardedJedis也是連接池里保存的對(duì)象。

可以看到destroyObject和validateObject都是將ShardedJedis里的redis實(shí)例當(dāng)做了一個(gè)整體去對(duì)待,一個(gè)失敗,全部失敗。

接下來(lái)看下ShardedJedis的實(shí)現(xiàn),這個(gè)里面主要做了Hash的處理和各個(gè)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是對(duì)連接池的引用,用于在Close的時(shí)候資源返還。和JedisPool的思想差不多。

由于ShardedJedis是BinaryShardedJedis的子類,所以構(gòu)造函數(shù)會(huì)一直向上調(diào)用,在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());}}

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

首先調(diào)用ShardedJedisPool的getResource方法,從對(duì)象池中獲取一個(gè)ShardedJedis:

ShardedJedis jedisClient = sharedPool.getResource();

調(diào)用ShardedJedis的getShard方法獲取一個(gè)Jedis實(shí)例——一個(gè)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());}

這里主要是對(duì)key做hash,然后去TreeMap中判斷,當(dāng)前的key落在哪個(gè)區(qū)間上,再通過(guò)這個(gè)區(qū)間上的ShardInfo從resources的Map中獲取對(duì)應(yīng)的Jedis實(shí)例。

這也就是說(shuō),每一個(gè)ShardedJedis都維護(hù)了所有的分片,將多個(gè)實(shí)例當(dāng)成一個(gè)整體去使用,這也就導(dǎo)致,只要集群中一個(gè)實(shí)例不可用,整個(gè)ShardedJedis就不可用了。同時(shí)對(duì)于Hash的分片方式,是不可擴(kuò)容的,擴(kuò)容之后原本應(yīng)該存儲(chǔ)在一起的數(shù)據(jù)就分離了。

其實(shí)這種是Jedis默認(rèn)提供的分片方式,其實(shí)針對(duì)我們自己的場(chǎng)景我們也可以嘗試自己做一個(gè)路由機(jī)制,例如根據(jù)不同年份、月份的數(shù)據(jù)落到一個(gè)實(shí)例上。

而對(duì)于在Spring中集成Jedis分片來(lái)說(shuō),應(yīng)該是做簡(jiǎn)單的:

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(以上內(nèi)容轉(zhuǎn)自此篇文章)

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(以上內(nèi)容部分轉(zhuǎn)自此篇文章)

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

==>如有問(wèn)題,請(qǐng)聯(lián)系我:easonjim#163.com,或者下方發(fā)表評(píng)論。<==

總結(jié)

以上是生活随笔為你收集整理的Redis基于客户端分片的集群案例(待实践)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。