设计思路

思路很简单,就是基于用户ID进行分库,将用户的ID字符串按照byte逐个计算ID对应的hash原值(一个数字,取绝对值,因为原始值可能过大溢出,变成负数),然后,再用这个hash原值对库的个数进行求模,这个模值就是库列表的索引值,也就选择好了用什么库。

hash算法

 /**
* Created by chengsh05 on 2017/12/22.
*/
public class BKDRHashUtil {
public static int BKDRHash(char[] str) {
int seed = ; // 31 131 1313 13131 131313 etc..
int hash = ;
for (int i = ; i < str.length; i++) {
hash = hash * seed + (str[i]);
}
return (hash & 0x7FFFFFFF);
}
}

对于redis的水平扩容,做到完全基于配置实现扩容,最好选择通过xml的配置的方式实现,因为配置在线上只需要改改配置信息,既可以重启服务器实现更新扩容。其实,其他中间件的扩容一样可以这么个逻辑实现。

XML配置

 <?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.3.xsd"> <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxIdle" value="${spring.redis.pool.maxIdle}"></property>
<property name="minIdle" value="${spring.redis.pool.minIdle}"></property>
<property name="maxTotal" value="${spring.redis.pool.maxActive}"></property>
<property name="maxWaitMillis" value="${spring.redis.pool.maxWait}"></property>
</bean> <!--第一组主从redis-->
<bean id="jedisConnectionFactoryMaster1" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" destroy-method="destroy" primary="true">
<property name="poolConfig" ref="jedisPoolConfig"></property>
<property name="hostName" value="${spring.master1.redis.hostName}"></property>
<property name="port" value="${spring.master1.redis.port}"></property>
<property name="database" value="${spring.redis.database}"></property>
<property name="timeout" value="${spring.redis.timeout}"></property>
</bean>
<bean id="redisTemplateMaster1" class="org.springframework.data.redis.core.RedisTemplate">
<property name="connectionFactory" ref="jedisConnectionFactoryMaster1"></property>
</bean>
<bean id="jedisConnectionFactorySlave1" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" destroy-method="destroy">
<property name="poolConfig" ref="jedisPoolConfig"></property>
<property name="hostName" value="${spring.slave1.redis.hostName}"></property>
<property name="port" value="${spring.slave1.redis.port}"></property>
<property name="database" value="${spring.redis.database}"></property>
<property name="timeout" value="${spring.redis.timeout}"></property>
</bean>
<bean id="redisTemplateSlave1" class="org.springframework.data.redis.core.RedisTemplate">
<property name="connectionFactory" ref="jedisConnectionFactorySlave1"></property>
</bean> <!-- 第2组主从redis -->
<bean id="jedisConnectionFactoryMaster2" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" destroy-method="destroy">
<property name="poolConfig" ref="jedisPoolConfig"></property>
<property name="hostName" value="${spring.master2.redis.hostName}"></property>
<property name="port" value="${spring.master2.redis.port}"></property>
<property name="database" value="${spring.redis.database}"></property>
<property name="timeout" value="${spring.redis.timeout}"></property>
</bean>
<bean id="redisTemplateMaster2" class="org.springframework.data.redis.core.RedisTemplate">
<property name="connectionFactory" ref="jedisConnectionFactoryMaster2"></property>
</bean>
<bean id="jedisConnectionFactorySlave2" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" destroy-method="destroy">
<property name="poolConfig" ref="jedisPoolConfig"></property>
<property name="hostName" value="${spring.slave2.redis.hostName}"></property>
<property name="port" value="${spring.slave2.redis.port}"></property>
<property name="database" value="${spring.redis.database}"></property>
<property name="timeout" value="${spring.redis.timeout}"></property>
</bean>
<bean id="redisTemplateSlave2" class="org.springframework.data.redis.core.RedisTemplate">
<property name="connectionFactory" ref="jedisConnectionFactorySlave2"></property>
</bean> <bean id="commonRedisService" class="org.whuims.web.service.AutoScaleRedisService">
<!--
Modified: shihuc, --
shihuc, -- 全配置,无需计算中间参数,目的就是提升性能。
注意,这里的改造,是方便redis的水平扩展,目的是为了在增加redis主从服务器的时候,只需要修改一下此处的配置文件,然后重启应用即可。
这里配置相对多了点,目的是换取性能。
另外:. readRedisTemplateKeyInstancePairs,writeRedisTemplateKeyInstancePairs两个主要的键值结构配置读写实例表。
. 读写redis,主从关系,必须配对填写好,不要出现主从的错位配置。例如rw1、rr1表示第一组的写读关系。
. readRedisKeys列表取值必须和readRedisTemplateKeyInstancePairs的key值一样,writeRedisKeys列表的取值必须
和writeRedisTemplateKeyInstancePairs的key值一样。
-->
<property name="readRedisTemplateKeyInstancePairs">
<map key-type="java.lang.String">
<entry key="rr1" value-ref="redisTemplateSlave1"></entry>
<entry key="rr2" value-ref="redisTemplateSlave2"></entry>
</map>
</property>
<property name="readRedisKeys">
<list>
<value>rr1</value>
<value>rr2</value>
</list>
</property>
<property name="writeRedisTemplateKeyInstancePairs">
<map key-type="java.lang.String">
<entry key="rw1" value-ref="redisTemplateMaster1"></entry>
<entry key="rw2" value-ref="redisTemplateMaster2"></entry>
</map>
</property>
<property name="writeRedisKeys">
<list>
<value>rw1</value>
<value>rw2</value>
</list>
</property>
</bean>
</beans>

其中用到的参数,通过spring的占位符逻辑,redis的配置数据来自配置文件,这里配置文件信息,简要示例(springboot的配置文件app.properties里面的局部):

#common part used in redis configuration for below multi redis
spring.redis.pool.maxActive=
spring.redis.pool.maxWait=-
spring.redis.pool.maxIdle=
spring.redis.pool.minIdle=
spring.redis.timeout=
spring.redis.database= #how many redis group to use is depended your business. but, at least one master/slave configuration needed
#below is for master1 redis configuration
spring.master1.redis.hostName=100.126.22.177
spring.master1.redis.port=
#below is for slave1 redis configuration
spring.slave1.redis.hostName=100.126.22.178
spring.slave1.redis.port= #below is for master2 redis configuration
spring.master2.redis.hostName=100.126.22.189
spring.master2.redis.port=
#below is for slave1 redis configuration
spring.slave2.redis.hostName=100.126.22.190
spring.slave2.redis.port=

springboot中Java启用配置

/**
* Created by chengsh05 on 2017/12/22.
*
* 通过XML的方式进行redis的配置管理,目的在于方便容量扩缩的时候,只需要进行配置文件的变更即可,这样
* 可以做到容量的水平管理,不需要动业务逻辑代码。
*
* 上线的时候,改改配置文件,再重启一下应用即可完成扩缩容。
*/
@Configuration
@ImportResource(value = {"file:${user.dir}/resources/spring-redis.xml"})
public class RedisXmlConfig {
}

分库服务

 /**
* Created by chengsh05 on 2017/12/22.
*
* 方便redis组件的水平扩展,扩展的时候,主要改改spring-redis.xml以及app.properties配置文件,不需要动java
* 代码,重启应用,即可实现扩容。
*/
public class AutoScaleRedisService { Logger logger = Logger.getLogger(AutoScaleRedisService.class); /**
* Added by shihuc, 2017-12-22
* redis水平扩展,中间层抽象逻辑
*
* Modified by shihuc 2018-01-02
* 将redis水平扩展部分,改成完全基于配置,不需要计算,应用层面,对于源的选取,完全就是读的操作,没有计算了,对于计算性能的提升有好处,配置相对麻烦一点。
*
* Key: rw1,rr1, and so on
* value: RedisTemplate instance
*/
private Map<String, RedisTemplate<String, Object>> readRedisTemplateKeyInstancePairs; private Map<String, RedisTemplate<String, Object>> writeRedisTemplateKeyInstancePairs; private List<String> readRedisKeys; private List<String> writeRedisKeys; public Map<String, RedisTemplate<String, Object>> getReadRedisTemplateKeyInstancePairs() {
return readRedisTemplateKeyInstancePairs;
} public void setReadRedisTemplateKeyInstancePairs(Map<String, RedisTemplate<String, Object>> readRedisTemplateKeyInstancePairs) {
this.readRedisTemplateKeyInstancePairs = readRedisTemplateKeyInstancePairs;
} public Map<String, RedisTemplate<String, Object>> getWriteRedisTemplateKeyInstancePairs() {
return writeRedisTemplateKeyInstancePairs;
} public void setWriteRedisTemplateKeyInstancePairs(Map<String, RedisTemplate<String, Object>> writeRedisTemplateKeyInstancePairs) {
this.writeRedisTemplateKeyInstancePairs = writeRedisTemplateKeyInstancePairs;
} public List<String> getReadRedisKeys() {
return readRedisKeys;
} public void setReadRedisKeys(List<String> readRedisKeys) {
this.readRedisKeys = readRedisKeys;
} public List<String> getWriteRedisKeys() {
return writeRedisKeys;
} public void setWriteRedisKeys(List<String> writeRedisKeys) {
this.writeRedisKeys = writeRedisKeys;
} /**
* @author shihuc
* @param userId
* @return
*/
private String getReadKey(String userId) {
int hash = BKDRHashUtil.BKDRHash(userId.toCharArray());
int abs = Math.abs(hash);
int idx = abs % getReadRedisCount();
logger.info("userId: " + userId + ", hash: " + hash + ", idx: " + idx);
String insKey = getReadRedisKeys().get(idx);
return insKey;
} /**
* @author shihuc
* @param userId
* @return
*/
private String getWriteKey(String userId) {
int hash = BKDRHashUtil.BKDRHash(userId.toCharArray());
int abs = Math.abs(hash);
int idx = abs % getWriteRedisCount();
logger.info("userId: " + userId + ", hash: " + hash + ", idx: " + idx);
String insKey = getWriteRedisKeys().get(idx);
return insKey;
} /**
* @author shihuc
* @return the count of read redis instance
*/
public int getReadRedisCount() {
return readRedisKeys.size();
} /**
* @author shihuc
* @return the count of write redis instance
*/
public int getWriteRedisCount() {
return writeRedisKeys.size();
} /**
* @author shihuc
* @param userId
* @param type
* @param log
* @return
*/
public RedisTemplate<String, Object> getRedisTemplate(String userId, String type, boolean log){
return getRedisTemplate(userId, type, log, null);
} /**
* 获取redisTemplate实例
* @author shihuc
* @param userId
* @param type
* @param log
* @return
*/
public RedisTemplate<String, Object> getRedisTemplate(String userId, String type, boolean log, String info){
String insKey = null;
RedisTemplate<String, Object> redisTemplate = null;
if(Constants.REDIS_TYPE_READ.equalsIgnoreCase(type)){
insKey = getReadKey(userId);
redisTemplate = readRedisTemplateKeyInstancePairs.get(insKey);
}else {
insKey = getWriteKey(userId);
redisTemplate = writeRedisTemplateKeyInstancePairs.get(insKey);
}
if (log) {
if(info != null) {
logger.info("userId: " + userId + ", redis: " + insKey + ", type: " + type + ", info: " + info);
}else{
logger.info("userId: " + userId + ", redis: " + insKey + ", type: " + type);
}
}
return redisTemplate;
} /**
* 用于校验配置的时候,读写实例的key值和键值列表的value之间是否是对应的关系。
*/
@PostConstruct
public void init() throws Exception {
int ridx = ;
for(Map.Entry<String, RedisTemplate<String, Object>> rele: readRedisTemplateKeyInstancePairs.entrySet()) {
String rkey = rele.getKey();
String trkey = readRedisKeys.get(ridx);
if(!rkey.equals(trkey)){
throw new Exception("[read] redis group configuration error, order is not matched");
}
ridx++;
}
int widx = ;
for(Map.Entry<String, RedisTemplate<String, Object>> wele: writeRedisTemplateKeyInstancePairs.entrySet()) {
String wkey = wele.getKey();
String twkey = writeRedisKeys.get(widx);
if(!wkey.equals(twkey)){
throw new Exception("[write] redis group configuration error, order is not matched");
}
widx++;
}
}
}

使用案例

    @RequestMapping("/redischeck")
@ResponseBody
public String redisCheck(@RequestParam(value = "query") String query) {
System.out.println("check:" + query);
int rdc = autoScaleRedisService.getReadRedisCount();
int wtc = autoScaleRedisService.getWriteRedisCount(); RedisTemplate redisTemplate = autoScaleRedisService.getRedisTemplate(query, Constants.REDIS_TYPE_READ, true, "buildSession");
RedisTemplate redisTemplate2 = autoScaleRedisService.getRedisTemplate(query, Constants.REDIS_TYPE_WRITE, true, "buildSession");
return "rdc: " + rdc + ", wtc: " + wtc;
}

整个思路和实现过程,其实非常通俗易懂,非常方便的用于各种中间件的场景,当然,若有特殊需求,也无外乎类似的逻辑。

若有什么不妥,欢迎探讨,若有更好的巧妙方案,也可以探讨!

转载请指明出处,谢谢!欢迎加关注!

redis水平扩展实践,完全配置,无需代码改动的更多相关文章

  1. Redis+PHP扩展的安装和Redis集群的配置 与 PHP负载均衡开发方案

    以前有想过用 Memcache 实现M/S架构的负载均衡方案,直到听说了 Redis 后才发现它做得更好.发了几天时间研究了一下 Redis ,感觉真的很不错,特整理一下! 以下操作都是在 SUSE  ...

  2. [ 搭建Redis本地服务器实践系列二 ] :图解CentOS7配置Redis

    上一章 [ 搭建Redis本地服务器实践系列一 ] :图解CentOS7安装Redis 详细的介绍了Redis的安装步骤,那么只是安装完成,此时的Redis服务器还无法正常运作,我们需要对其进行一些配 ...

  3. inux redis 安装配置, 以及redis php扩展

    一,什么是redis redis是一个key-value存储系统. 和Memcached类似,它支持存储的value类型相对更多,包括string(字符串).list(链表).set(集合)和zset ...

  4. Redis系列(三):Redis集群的水平扩展与伸缩

    一.Redis集群的水平扩展 Redis3.0版本以后,有了集群的功能,提供了比之前版本的哨兵模式更高的性能与可用性,但是集群的水平扩展却比较麻烦,接下来介绍下Redis高可用集群如何做水平扩展,在原 ...

  5. linux安装配置Redis,Swoole扩展

    我是使用的是lnmp环境(php5.6.3) 一.安装redis数据库(参考w3c手册) 下载地址:http://redis.io/download 本教程使用的最新文档版本为 2.8.17,下载并安 ...

  6. Redis与Java - 实践

    Redis与Java - 实践 标签 : Java与NoSQL Transaction Redis事务(transaction)是一组命令的集合,同命令一样也是Redis的最小执行单位, Redis保 ...

  7. 知乎技术分享:从单机到2000万QPS并发的Redis高性能缓存实践之路

    本文来自知乎官方技术团队的“知乎技术专栏”,感谢原作者陈鹏的无私分享. 1.引言 知乎存储平台团队基于开源Redis 组件打造的知乎 Redis 平台,经过不断的研发迭代,目前已经形成了一整套完整自动 ...

  8. Mysql 调优和水平扩展思路

    系统调优参数 一些比较重要的参数: back_log:back_log值指出在MySQL暂时停止回答新请求之前的短时间内多少个请求可以被存在堆栈中.如果MySql的连接数据达到max_connecti ...

  9. Redis原理与实践总结

    Redis原理与实践总结 本文主要对Redis的设计和实现原理做了一个介绍很总结,有些东西我也介绍的不是很详细准确,尽量在自己的理解范围内把一些知识点和关键性技术做一个描述.如有错误,还望见谅,欢迎指 ...

随机推荐

  1. 《统计学习方法》笔记(9):EM算法和隐马尔科夫模型

    EM也称期望极大算法(Expectation Maximization),是一种用来对含有隐含变量的概率模型进行极大似然估计的迭代算法.该算法可应用于隐马尔科夫模型的参数估计. 1.含有隐含参数的概率 ...

  2. Microsoft Project 常用快捷键

    任务升级 : ALT  +  SHIFT + 向左键 任务降级: ALT  +  SHIFT + 向右键 滚动到表头(第一个任务):Ctrl + HOME 滚动到表尾(最后一个任务):Ctrl + E ...

  3. Spring Boot 揭秘与实战(二) 数据存储篇 - 声明式事务管理

    文章目录 1. 声明式事务 2. Spring Boot默认集成事务 3. 实战演练4. 源代码 3.1. 实体对象 3.2. DAO 相关 3.3. Service 相关 3.4. 测试,测试 本文 ...

  4. Python学习笔记第六周

    目录 一.基础概念 面向对象编程 1.面向对象的几个核心特点 1.class类 2.object对象 3.encapsulation封装 4.inheritance继承 5.polymorphism多 ...

  5. 一个简单的 IDA f5插件问题分析

    有人提出问题,以下汇编f5结果缺失代码: .text:00000C18 Java_com_a_b_c .text:00000C18 PUSH {R3,LR} .text:00000C1A CMP R2 ...

  6. python调用caffe环境配置

    背景是这样的,项目需要,必须将训练的模型通过C++进行调用,所以必须使用caffe或者mxnet,而caffe是用C++实现,所以有时候简单的加载一张图片然后再进行预测十分不方便 用caffe写pro ...

  7. 添加aimate动画

    .page3_ship{ background:url(../image/boat_02.png) 0 center no-repeat; background-size: 486px 385px; ...

  8. jquery选择后代以及toggle,toggleClass用法

    $("#id").find(".classname")      选择id为xx下的类名为xx的元素 toggle  $(this).next(".c ...

  9. 洛谷 P3373:【模板】线段树 2(区间更新)

    题目描述 如题,已知一个数列,你需要进行下面三种操作: 1.将某区间每一个数乘上x 2.将某区间每一个数加上x 3.求出某区间每一个数的和 输入输出格式 输入格式: 第一行包含三个整数N.M.P,分别 ...

  10. php基础-2

    php的逻辑运算 &&符号 <?php function tarcrnum() { for ($i = 0; $i <= 100; $i++) { if ($i % 2 = ...