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的设计和实现原理做了一个介绍很总结,有些东西我也介绍的不是很详细准确,尽量在自己的理解范围内把一些知识点和关键性技术做一个描述.如有错误,还望见谅,欢迎指 ...
随机推荐
- 中国顶级黑客X档案
sunwear QQ:47347 微博: http://t.qq.com/sunwe4r 博客:http://hi.baidu.com/patricksunwear 好像不用了 日娃哥.EST核心成员 ...
- 1--Python 入门--Python基础数据类型
一.Python基础语法 初次使用Python,首先要明确三点: Python的标识符(例如变量名.函数名等),可用字母.数字和下划线构成,不能以数字开头,且区分大小写. Python对于缩进敏感.在 ...
- ORA-03001,GATHER_TABLE_STATS数据库自动收集统计信息报错
1.根据Alert报错信息,查询Trace日志 /oracle/app/oracle/admin/fgsquery/bdump/fgsquery_j001_11111.trc Oracle Datab ...
- shell常用函数封装-main.sh
#!/bin/bash #sunlight sp monitor system #created on 2018/01/07#by chao.dong#used by sp servers consi ...
- 田螺便利店—ipconfig命令不是内部命令或外部命令怎么解决?
查询网卡ID在运行后输入ipconfig/all点回车后提示ipconfig不是内部或外部命令,也不是可运行的程序或批处理文件? 首先确认你的输入是无误的,确保输入无误,仍提示 ipconfig 不是 ...
- Fedora初体验
========1. 下载https://getfedora.org/zh_CN/workstation/download/下载如下2个文件:Fedora-Workstation-Live-x86_6 ...
- C++数组排序
#include<stdio.h> #include<stdlib.h> #include<windows.h> #define SIZE 5 //数组中元素的数量 ...
- Python基础之二进制
引子 首先,计算机一共就能做两件事:计算和通信 那在讲计算机之前,我们先来讲一个故事,大家知道古时候的中国是如何通信的么? 假如,战国时期两个国家要打仗了,我们垒了城墙,每隔一段就有兵镇守,现在有人来 ...
- 有关O_APPEND标志和lseek()的使用
编程之路刚刚开始,错误难免,希望大家能够指出. O_APPEND表示以每次写操作都写入文件的末尾.lseek()可以调整文件读写位置. <<Linux/UNIX系统编程手册>> ...
- oracle Awr报告
Select DBID,INSTANCE_NUMBER,SNAP_ID,TO_CHAR(END_INTERVAL_TIME,'YYYY-MM-DD HH24:MM:SS') AS END_TIME,T ...