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.mzph.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;一个列…