redis水平扩展实践,完全配置,无需代码改动
设计思路
思路很简单,就是基于用户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水平扩展实践,完全配置,无需代码改动的更多相关文章
- Redis+PHP扩展的安装和Redis集群的配置 与 PHP负载均衡开发方案
以前有想过用 Memcache 实现M/S架构的负载均衡方案,直到听说了 Redis 后才发现它做得更好.发了几天时间研究了一下 Redis ,感觉真的很不错,特整理一下! 以下操作都是在 SUSE ...
- [ 搭建Redis本地服务器实践系列二 ] :图解CentOS7配置Redis
上一章 [ 搭建Redis本地服务器实践系列一 ] :图解CentOS7安装Redis 详细的介绍了Redis的安装步骤,那么只是安装完成,此时的Redis服务器还无法正常运作,我们需要对其进行一些配 ...
- inux redis 安装配置, 以及redis php扩展
一,什么是redis redis是一个key-value存储系统. 和Memcached类似,它支持存储的value类型相对更多,包括string(字符串).list(链表).set(集合)和zset ...
- Redis系列(三):Redis集群的水平扩展与伸缩
一.Redis集群的水平扩展 Redis3.0版本以后,有了集群的功能,提供了比之前版本的哨兵模式更高的性能与可用性,但是集群的水平扩展却比较麻烦,接下来介绍下Redis高可用集群如何做水平扩展,在原 ...
- linux安装配置Redis,Swoole扩展
我是使用的是lnmp环境(php5.6.3) 一.安装redis数据库(参考w3c手册) 下载地址:http://redis.io/download 本教程使用的最新文档版本为 2.8.17,下载并安 ...
- Redis与Java - 实践
Redis与Java - 实践 标签 : Java与NoSQL Transaction Redis事务(transaction)是一组命令的集合,同命令一样也是Redis的最小执行单位, Redis保 ...
- 知乎技术分享:从单机到2000万QPS并发的Redis高性能缓存实践之路
本文来自知乎官方技术团队的“知乎技术专栏”,感谢原作者陈鹏的无私分享. 1.引言 知乎存储平台团队基于开源Redis 组件打造的知乎 Redis 平台,经过不断的研发迭代,目前已经形成了一整套完整自动 ...
- Mysql 调优和水平扩展思路
系统调优参数 一些比较重要的参数: back_log:back_log值指出在MySQL暂时停止回答新请求之前的短时间内多少个请求可以被存在堆栈中.如果MySql的连接数据达到max_connecti ...
- Redis原理与实践总结
Redis原理与实践总结 本文主要对Redis的设计和实现原理做了一个介绍很总结,有些东西我也介绍的不是很详细准确,尽量在自己的理解范围内把一些知识点和关键性技术做一个描述.如有错误,还望见谅,欢迎指 ...
随机推荐
- scrollIntoView()方法
该方法用于建立一个锚点,点击锚点,会跳到相应的内容,且该内容头部与浏览器头部平齐 实例: <html> <head> <title>TODO supply a ti ...
- puppet替换文件中的string
文件 <VirtualHost :80> RewriteEngine On RewriteCond %{HTTPS} off RewriteRule (.) https://%{SERVE ...
- HDU 6143 17多校8 Killer Names(组合数学)
题目传送:Killer Names Problem Description > Galen Marek, codenamed Starkiller, was a male Human appre ...
- 大数据-12-Spark+Kafka构建实时分析Dashboard
转自 http://dblab.xmu.edu.cn/post/8274/ 0.案例概述 本案例利用Spark+Kafka实时分析男女生每秒购物人数,利用Spark Streaming实时处理用户购物 ...
- grep命令相关用法
grep命令相关参数: -i:忽略大小写 --color:高亮显示匹配到的信息 -v:反向查找,没匹配到的行显示出来 -o:只显示被模式匹配到的串本身 正则表达式: .*:任意长度的任意字符,贪婪模式 ...
- Python 正则 —— 捕获与分组
\n:表示第 n 个捕获: >> s = "<html><h1>what the fuck!</h1></html>" ...
- ss-libev控制脚本
适用于:shadowsocks-libev-3.0.3 操作系统:CentOS6.8 #!/bin/sh SHADOWSOCKS_SERVER="/usr/local/shadowsocks ...
- 20165228 2017-2018-2 《Java程序设计》第6周学习总结
20165228 2017-2018-2 <Java程序设计>第6周学习总结 教材学习内容总结 String类用来处理字符序列及其用法 StringTokenizer对象及类的使用 Sca ...
- scala quick check
Scala 特性 面向对象特性 函数式编程 Scala也是一种函数式语言,其函数也能当成值来使用.Scala提供了轻量级的语法用以定义匿名函数,支持高阶函数,允许嵌套多层函数,并支持柯里化.Sca ...
- 创建一个版本库,把文件夹用Git管理起来
创建一个文件夹,把这个文件夹用Git管理起来,那么这个文件夹的改变都可以被Git跟踪到,当然也可以将Git中的文件还原到某一个时刻. 首先创建一个空的目录,然后将空的目录由Git来管理 1.建立一个文 ...