亲密接触Redis-第三天(Redis的Load Balance)
前言
上两天讲述了Redis的基本搭建和基于HA的集群布署方式以及相关的策略和注意点。今天开始讲述Redis的Cluster功能,而这块目前来说网上资料不是太全,就算有1,2篇也只是单讲服务端的搭建也并未提及相关的客户端怎么和Redis Cluster间的调用问题。
我们今天要讲述的Redis Cluster是真正的Load Balance,它和Sentinel不一样,Sentinel虽然也叫集群,可是它是一种HA策略即High Available或者又通俗的被称为“灾难转移”策略。
Redis3.x中引入的Load Balance
从Redis3.x开始已经支持Load Balance功能了。
Redis Cluster 是Redis的集群实现,内置数据自动分片机制,集群内部将所有的key映射到16384个Slot中,集群中的每个Redis Instance负责其中的一部分的Slot的读写。集群客户端连接集群中任一Redis Instance即可发送命令,当Redis Instance收到自己不负责的Slot的请求时,会将负责请求Key所在Slot的Redis Instance地址返回给客户端,客户端收到后自动将原请求重新发往这个地址,对外部透明。一个Key到底属于哪个Slot由crc16(key) % 16384 决定。在Redis Cluster里对于负载均衡和HA相关都已经支持的相当完善了。
- 负载均衡(Load Balance):集群的Redis Instance之间可以迁移数据,以Slot为单位,但不是自动的,需要外部命令触发。
- 集群成员管理:集群的节点(Redis Instance)和节点之间两两定期交换集群内节点信息并且更新,从发送节点的角度看,这些信息包括:集群内有哪些节点,IP和PORT是什么,节点名字是什么,节点的状态(比如OK,PFAIL,FAIL,后面详述)是什么,包括节点角色(master 或者 slave)等。
关于可用性,集群由N组主从Redis Instance组成。
主可以没有从,但是没有从 意味着主宕机后主负责的Slot读写服务不可用。
一个主可以有多个从,主宕机时,某个从会被提升为主,具体哪个从被提升为主,协议类似于Raft,参见这里。如何检测主宕机?Redis Cluster采用quorum+心跳的机制。从节点的角度看,节点会定期给其他所有的节点发送Ping,cluster-node-timeout(可配置,秒级)时间内没有收到对方的回复,则单方面认为对端节点宕机,将该节点标为PFAIL状态。通过节点之间交换信息收集到quorum个节点都认为这个节点为PFAIL,则将该节点标记为FAIL,并且将其发送给其他所有节点,其他所有节点收到后立即认为该节点宕机。从这里可以看出,主宕机后,至少cluster-node-timeout时间内该主所负责的Slot的读写服务不可用。
Redis Cluster的特点如下:
- 节点自动发现
- slave->master选举,集群容错
- Hot resharding:在线分片
- 集群管理:clusterxxx
- 基于配置(nodes-port.conf)的集群管理
- ASK 转向/MOVED转向机制
- 布署无需指定master
- 可以支持超过1,000台节点的集群
Redis Cluster的实现
Redis Cluster依赖于其官方位于Redis编译包内(我们此处使用的是redis-stable版本)/src目录下的redis-trib.rb 文件,这是一个ruby脚本,为此你必须把你的服务器环境作一个先期准备。
搭建Cluster前的环境准备
- 安装CentOS或者是RHE
- 在安装Linux时需要一定记得安装GCC库、LibC、LibStdC++、Rubby库(1.9.2或以上)、ZLIB库(1.2.6或以上),如果你装机时没有安装这些“optional package”可以通过yum install gcc这样的命令在Linux联网的情况下来进行Linux安装后的额外包的安装
- 安装ruby gems库(1.8.16或以上)- rubygems-1.8.16.tgz(运行内在的setup.rb),下载 rubygems-1.8.16.tgz 后在解压包的/path/gem 运行
sudo ruby setup.rb
- 在ruby gems安装后,你必须安装gem的redis模块,可以通过官网或者相关可信任连接下载redis-3.2.1.gem(先下载.gem文件再用gem安装)如:
gem install -l redis-3.2.1.gem
Redis集群节点的规划
使用make PREFIX=/usr/local/redis1 install这样的命令连续搭建至少6个nodes,Redis Cluster的最低要求是(3个Master,3个Slave),这在我们的“亲密接触Redis-第一天”中有详细描述怎么编译安装一个redis了,此处全部是采用redis-stable这个版本。
集群节点的配置
每个节点在第一次配置时,除去:
- .pid文件和路径
- /logfile文件和路径
- Data存放路径
- Port
其余配置,完全一样,并且无master和slave之分。
PORT端口配置要点:
所有Master(如3个Master的端口号以+1方式递增:7001,7002,7003)
所有的Slaver的端口号必须且一定要符合这样的原则:slave的端口比相关的master大1000号,如7001的slave的端口号为8001。
举例来说:
3个Master为7001,7002,7003,我们的3个Slave就为8001,8002,8003
redis.conf文件内:
- maxmemory-policy allkeys-lru
- cluster-enabled yes(启用,把前面的#注释掉)
- cluster-config-file nodes-7001.conf(启用,把前面的#注释掉,此处建议使用本节点的PORT号为文件名,这样便于区分
- cluster-node-timeout 15000(启用,把前面的#注释掉)
- cluster-migration-barrier 1(启用,把前面的#注释掉)
使用Rubb Gem的Redis模块+redis-trib.rb创建集群
一切准备就绪后,把6个redis节点全部启动起来。
redis-trib.rb位于下载下来的redis-stable目录的/src目录内。
使用如下命令创建Redis集群:
./redis-trib.rb create --replicas 1 192.168.0.1:7001 192.168.0.1:7002 192.168.0.1:7003 192.168.0.1:8001 192.168.0.1:8002 192.168.0.1:8003
注:
命令行中一定要把所有的master列出后,再列出所有的slave(依照master的顺序列slave),6个节点全部列在命令行中。
集群创建成功后会显示如下信息:
这就说明Redis集群已经创建了,以后只要使用redis-server redis.conf这样的命令把每个节点启动起来就可以了。
使用Jedis Client来连接Redis集群(Load Balance)
目前Redis和Spring的结合都是依靠Jedis和Spring Data的redisTemplate来做的,而现在spring data似乎对redis cluster的功能支持并不好(此处指的是load balance功能),为此我们自己封装了一个redis的客户端来支持redis的cluster方式,也结合spring来用(如果redistemplate已经支持redis的load balance功能后建议大家使用spring data的redis template,因为那个无论是在封装性还是性能上会更好)。
先给出代码需要的相关jar包吧。
pom.xml
<!-- redis start -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.8.0</version>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>1.0.2</version>
</dependency>
<!-- redis end -->
spring配置文件redis-cluster.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:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:jee="http://www.springframework.org/schema/jee" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:property-placeholder location="classpath:/spring/redis.properties" />
<context:component-scan base-package="org.sky.redis">
</context:component-scan>
<bean name="genericObjectPoolConfig" class="org.apache.commons.pool2.impl.GenericObjectPoolConfig">
<property name="maxWaitMillis" value="-1" />
<property name="maxTotal" value="1000" />
<property name="minIdle" value="8" />
<property name="maxIdle" value="100" />
</bean> <bean id="jedisCluster" class="com.qf.platform.redis.cluster.JedisClusterFactory">
<property name="addressConfig">
<value>classpath:/spring/redis.properties</value>
</property>
<property name="addressKeyPrefix" value="clusternode" /> <!-- 属性文件里 key的前缀 --> <property name="timeout" value="300000" />
<property name="maxRedirections" value="6" />
<property name="genericObjectPoolConfig" ref="genericObjectPoolConfig" />
</bean>
<bean id="customExceptionHandler" class="sample.MyHandlerExceptionResolver" />
</beans>
这边我们看到有一个叫com.qf.platform.redis.cluster.JedisClusterFactory的类,这个类就是我们通过“GenericObjectPoolConfig”来封装的用于支持redis cluster功能的客户端接口。
com.qf.platform.redis.cluster.JedisClusterFactory类内容
/**
* @Title: [JedisClusterFactory.java]
* @Package: [com.qf.platform.redis.cluster]
* @author: [MingkaiYuan]
* @CreateDate: [2016年3月9日 下午3:42:26]
* @UpdateUser: [MingkaiYuan]
* @UpdateDate: [2016年3月9日 下午3:42:26]
* @UpdateRemark: [说明本次修改内容]
* @Description: [TODO(用一句话描述该文件做什么)]
* @version: [V1.0]
*/
package com.qf.platform.redis.cluster; import java.util.HashSet;
import java.util.Properties;
import java.util.Set;
import java.util.regex.Pattern; import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.io.Resource; import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.JedisCluster; /**
* @ClassName: JedisClusterFactory
* @author: [MingkaiYuan]
* @CreateDate: [2016年3月9日 下午3:42:26]
* @UpdateUser: [MingkaiYuan]
* @UpdateDate: [2016年3月9日 下午3:42:26]
* @UpdateRemark: [说明本次修改内容]
* @Description: [TODO(用一句话描述该文件做什么)]
* @version: [V1.0]
*/
public class JedisClusterFactory implements FactoryBean<JedisCluster>, InitializingBean {
private Resource addressConfig;
private String addressKeyPrefix; public void setAddressKeyPrefix(String addressKeyPrefix) {
this.addressKeyPrefix = addressKeyPrefix;
} private JedisCluster jedisCluster;
private Integer timeout;
private Integer maxRedirections;
private GenericObjectPoolConfig genericObjectPoolConfig; private Pattern p = Pattern.compile("^.+[:]\\d{1,5}\\s*$"); @Override
public JedisCluster getObject() throws Exception {
return jedisCluster;
} @Override
public Class<? extends JedisCluster> getObjectType() {
return (this.jedisCluster != null ? this.jedisCluster.getClass() : JedisCluster.class);
} @Override
public boolean isSingleton() {
return true;
} private Set<HostAndPort> parseHostAndPort() throws Exception {
try {
Properties prop = new Properties();
prop.load(this.addressConfig.getInputStream()); Set<HostAndPort> haps = new HashSet<HostAndPort>();
for (Object key : prop.keySet()) { if (!((String) key).startsWith(addressKeyPrefix)) {
continue;
} String val = (String) prop.get(key); boolean isIpPort = p.matcher(val).matches(); if (!isIpPort) {
throw new IllegalArgumentException("ip 或 port 不合法");
}
String[] ipAndPort = val.split(":");
System.out.println(String.valueOf(val));
HostAndPort hap = new HostAndPort(ipAndPort[0], Integer.parseInt(ipAndPort[1]));
haps.add(hap);
} return haps;
} catch (IllegalArgumentException ex) {
throw ex;
} catch (Exception ex) {
throw new Exception("解析 jedis 配置文件失败", ex);
}
} @Override
public void afterPropertiesSet() throws Exception {
Set<HostAndPort> haps = this.parseHostAndPort(); jedisCluster = new JedisCluster(haps, timeout, maxRedirections, genericObjectPoolConfig); } public void setAddressConfig(Resource addressConfig) {
this.addressConfig = addressConfig;
} public void setTimeout(int timeout) {
this.timeout = timeout;
} public void setMaxRedirections(int maxRedirections) {
this.maxRedirections = maxRedirections;
} public void setGenericObjectPoolConfig(GenericObjectPoolConfig genericObjectPoolConfig) {
this.genericObjectPoolConfig = genericObjectPoolConfig;
}
}
通过该类和spring中的配置
<property name="addressConfig">
<value>classpath:/spring/redis.properties</value>
</property>
<property name="addressKeyPrefix" value="clusternode" />
我们得知,该类会通过/src/main/resources目录下的/spring目录中的redis.properties去读各个redis cluster节点的信息
redis.properties文件内容
# Redis settings cach.host.ip=192.168.0.101
cach.host.port=6380 redis.host.ip=192.168.0.101
redis.host.port=6379 redis.maxTotal=1000
redis.maxIdle=100
redis.maxWait=2000
redis.testOnBorrow=false
redis.testOnReturn=true redis.sentinel.addr=172.30.32.127:26379 clusternode1=192.168.0.101:7001
clusternode2=192.168.0.101:7002
clusternode3=192.168.0.101:7003
clusternode4=192.168.0.101:8001
clusternode5=192.168.0.101:8002
clusternode6=192.168.0.101:8003
该类使用时很方便,看下面的示例代码
客户端调用Redis Cluster示例代码
@Autowired
JedisCluster jedisCluster;
......
jedisCluster.set(key, value);
......
简单吧。
Redis集群的注意事项(与坑)
redis-cli客户端使用事项
- 使用redis-cli连接cluster时要使用: redis-cli –c –h –p这样的格式
- 在客户端连上任意一个redis节点set一个值后,该值不一定存到该节点,而是自动转向至由Redis集群选举出来的一个节点中并存入值,此时客户端的物理连接也会被指向该节点。举例:
此时你的redis-cli连接已经被重定向至了7002了而不是在7001下虽然显示符>前显示的还是7001
使用redis-cli连接cluster中任意一个节点如:
可以看到7003节点内无 key=1的元素,但是集群内是有key=1的元素的,于是:
reds-cli会动态从集群中查到含有key=1的节点,把客户端连接重定向至该节点并调用该key所绑的value
添加新master节点
- 添加一个master节点:创建一个空节点(empty node),然后将某些slot移动到这个空节点上,这个过程目前需要人工干预
- establish_config.sh根据端口生成配置文件establish_config.sh 6386?> conf/redis-6386.conf
- 启动该节点
- 加入空节点到集群redis-trib.rb add-node newip:newport 已有集群中任意ip:已有集群中任意port
注:
新节点没有包含任何数据, 因为它没有包含任何slot。新加入的加点是一个主节点, 当集群需 要将某个从节点升级为新的主节点时, 这个新节点不会被选中,同时新的主节点因为没有包含任何slot,不参加选举和failover。
- 为新节点分配slot:redis-trib.rb reshard ip:port
添加slave节点
- 按照添加master节点的前3步
- redis-cli连接上新节点shell,输入命令:cluster replicate 对应master的node-id
注意:
在线添加slave 时,需要bgsave整个master数据,并传递到slave,再由 slave加载rdb文件到内存,rdb生成和传输的过程中消耗Master大量内存和网络IO,以此不建议单实例内存过大,线上小心操作。
删除Master节点
- 删除master节点之前首先要使用reshard移除当前master的全部slot到新的master上
- 删除空的master节点。
JEDIS(Redis JAVA客户端)的一些坑
- cluster环境下slave默认不接受任何读写操作,在slave执行readonly命令后,可执行读操作
- client端不支持多key操作(mget,mset等),但当keys集合对应的slot相同时支持mget操作
- 不支持多数据库,只有一个db,select 0。
- JedisCluster 没有针对byte[]的API,需要自己扩展(Jedis-3.0.0及以上开始才支持byte[]操作)
截止到今天,Redis所有搭建和使用讲解完毕,还有更深入的功能由其是性能优化、配置还需要大家在平时工作中不断的实践,我在文中也只能涉及到50%左右的内容,更多还是要靠读者自己去潜心发掘。
总得来说Redis是一个相当优秀的NOSQL,大都网站都在使用它作为主选NOSQL或者是缓存。
用,人人都会!
但用的精,这是需要付出一定的专研精神的。
谢谢!
亲密接触Redis-第三天(Redis的Load Balance)的更多相关文章
- 亲密接触Redis-第二天(Redis Sentinel)
简介 经过上次轻松搭建了一个Redis的环境并用Java代码调通后,这次我们要来看看Redis的一些坑以及Redis2.8以后带来的一个新的特性即支持高可用特性功能的Sentinel(哨兵). Red ...
- Redis(三)Redis基本命令操作与API
一Redis 连接 Redis 连接命令主要是用于连接 redis 服务. 实例 以下实例演示了客户端如何通过密码验证连接到 redis 服务,并检测服务是否在运行: redis 127.0.0.1: ...
- Redis学习三:Redis数据类型
一.Redis的五大数据类型 1.String(字符串) string是redis最基本的类型,你可以理解成与Memcached一模一样的类型,一个key对应一个value.string类型是二进制安 ...
- 【Redis】三、Redis安装及简单示例
(四)Redis安装及使用 Redis的安装比较简单,仍然和大多数的Apache开源软件一样,只需要下载,解压,配置环境变量即可.具体安装过程参考:菜鸟教程Redis安装. 安装完成后,通过r ...
- Redis系列(三):Redis的持久化机制(RDB、AOF)
本篇博客是Redis系列的第3篇,主要讲解下Redis的2种持久化机制:RDB和AOF. 本系列的前2篇可以点击以下链接查看: Redis系列(一):Redis简介及环境安装. Redis系列(二): ...
- Redis(三)--- Redis的五大数据类型的底层实现
1.简介 Redis的五大数据类型也称五大数据对象:前面介绍过6大数据结构,Redis并没有直接使用这些结构来实现键值对数据库,而是使用这些结构构建了一个对象系统redisObject:这个对象系统包 ...
- Redis(三)Redis附加功能
一.慢查询分析 许多存储系统(例如MySql)提供慢查询日志帮助开发和运维人员定位系统存在的慢操作. 所谓慢查询日志就是系统在命令执行前后计算每条命令的执行时间,当超过预设阈值,就将这条命令的相关信息 ...
- Redis学习三:Redis高可用之哨兵模式
申明 本文章首发自本人公众号:壹枝花算不算浪漫,如若转载请标明来源! 感兴趣的小伙伴可关注个人公众号:壹枝花算不算浪漫 22.jpg 前言 Redis 的 Sentinel 系统用于管理多个 Redi ...
- Redis系列三(redis配置文件分析)
在第一篇文章中有提到过redis.conf这个文件,这个文件就是redis-server的具体配置了.要使用好redis,一定要搞清楚redis的配置文件,这样才能最大的发挥redis的性能. # B ...
- redis学习三,Redis主从复制和哨兵模式
Redis主从复制 java架构师项目实战,高并发集群分布式,大数据高可用,视频教程 1.Master可以拥有多个slave 2.多个slave可以连接同一个Master外,还可以连接到其他的slav ...
随机推荐
- [转]Python爬虫框架--pyspider初体验
标签: python爬虫pyspider 2015-09-05 10:57 9752人阅读 评论(0) 收藏 举报 分类: Python(8) 版权声明:本文为博主原创文章,未经博主允许不得转载. ...
- Microsoft CRM-QueryExpression 成员
名称 ColumnSet 获取或设置要包含的列. Criteria 获取或设置过滤查询结果的复杂条件和逻辑过滤器表达式. Distinct 获取或设置查询的结果是否包含重复的实体实例. Entit ...
- Delphi X10.2 + FireDAC 使用 SQL 语句 INSERT
// CREATE TABLE [tabusers]( // [id] INTEGER PRIMARY KEY AUTOINCREMENT, // [username] CHAR NOT NULL, ...
- Linux(二)CentOS的安装
centos6.8 链接:https://pan.baidu.com/s/1TjCYXzijMzfpiZ9Z-D1Qhg 密码:7mvn 2.1 新建虚拟机 1 2.2 选中稍后安装操作系统(先把虚拟 ...
- 福利:100G Java全套学习视频免费送了
嗯 是的 众所周知 java工会自开办以来 一直致力于分享一些 java技术总结 学习方法..等等等 所以 从我做这个公众号以来 我的手机就没有消停过一天 因为 每天都有很多粉丝问我 "您好 ...
- Animations in UWP Community Toolkit - Overview
概述 UWP Community Toolkit 中有一个 Animations 的集合,它们可以帮助开发者实现很多的动画,本篇我们先来看一下 Animations 的功能都有哪些,再后面会针对每一 ...
- Swagger+Asp.net WebApi实例
第一步新建WebApi项目 文件-新建-项目,弹出以下页面 第二步,新建参数项目 第三步 1.自定义输入参数 2.定义公用输出参数 3.定义输出参数 4.定义返回模型 第四步,在webapi项目中新增 ...
- 【Swift】ios开发中巧用 description 打印对象时,打印对象的属性
ios开发中我们打印对象的时候,会直接输出对象地址,这样不方便我们开发.我们可以 巧用 description 打印对象时,输出对象的属性 在oc中直接重写即可.swift中需要遵守Printable ...
- [测试题]数组(array)
Description Input Output Sample Input1 3 2 75 4 2 Sample Output1 999999732 Sample Explanation1 Sampl ...
- 锐捷Linux版的下载和使用(福大客户端)
下载锐捷程序包 点此下载 没有连接到锐捷里就进不了这个安装包的官方下载界面(好矛盾啊这个),所以我把它上传到博客园了. 解压文件 schaepher:~$ cd Downloads/ schaephe ...