亲密接触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节点

  1. 按照添加master节点的前3步
  2. redis-cli连接上新节点shell,输入命令:cluster replicate 对应master的node-id

注意:


在线添加slave 时,需要bgsave整个master数据,并传递到slave,再由 slave加载rdb文件到内存,rdb生成和传输的过程中消耗Master大量内存和网络IO,以此不建议单实例内存过大,线上小心操作。

删除Master节点

  1. 删除master节点之前首先要使用reshard移除当前master的全部slot到新的master上
  2. 删除空的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或者是缓存。

用,人人都会!

但用的精,这是需要付出一定的专研精神的。

谢谢!

时间: 2024-09-17 04:36:01

亲密接触Redis-第三天(Redis的Load Balance)的相关文章

NoSQL之Redis(三) --- Redis在项目中的运用

NoSQL之Redis(三) --- Redis在项目中的运用            又是一个喧闹的新年,少了的不是年味,变了的是人们的内心.难得的假期静下心来回顾上一年,展望下一年.思考,总结,陪陪家人是否更值得去做?养精蓄锐,是不是又能更好迎接来年的挑战?年轻不是放纵的资本,需要的事更好的珍惜眼前的时光.            本文笔者会简单的描述一下Redis在"jrkj"这个项目中的哪些场景中使用的,以及是如何使用的.            应用场景一            首

亲密接触Redis-第二天(Redis Sentinel)

简介 经过上次轻松搭建了一个Redis的环境并用Java代码调通后,这次我们要来看看Redis的一些坑以及Redis2.8以后带来的一个新的特性即支持高可用特性功能的Sentinel(哨兵). Redis的一些坑 Redis是一个很优秀的NoSql,它支持键值对,查询方便,被大量应用在Internet的应用中,它即可以用作Http Session的分离如上一次举例中的和Spring Session的结合,还可以直接配置在Tomcat中和Tomcat容器结合并可以自动使用Redis作Session

Redis教程(三):List数据类型_Lua

一.概述:       在Redis中,List类型是按照插入顺序排序的字符串链表.和数据结构中的普通链表一样,我们可以在其头部(left)和尾部(right)添加新的元素.在插入时,如果该键并不存在,Redis将为该键创建一个新的链表.与此相反,如果链表中所有的元素均被移除,那么该键也将会被从数据库中删除.List中可以包含的最大元素数量是4294967295.       从元素插入和删除的效率视角来看,如果我们是在链表的两头插入或删除元素,这将会是非常高效的操作,即使链表中已经存储了百万条

15天玩转redis —— 第三篇 无敌的列表类型

据说60%的人使用redis看重的是redis中的list类型,那这个list有什么用呢???不用我说大家都明白,做队列使用呗,为什么用它呢,很简单呗, 因为有了它我就不需要专门的MQ产品啦,比如说RabbitMQ,ActiveMQ等等...对吧. 一:实战 先我们还是看一下List列表给我们提供的方法. 这些方法还是稀里糊涂的有一些的,没关系,做队列使用的话,常用的也就四个:LPOP,LPUSH,RPOP,RPUSH,从这四个单词上面,你应该就明白这 有点像数据结构中的"双端队列",

亲密接触Redis-第一天

引言 nosql,大规模分布式缓存遍天下,Internet的时代在中国由其走得前沿,这一切归功于我国特色的电商.因此nosql.大数据技术在中国应用的比国外还要前沿.从这一章开始我们将开始进入到真正的SOA.PAAS.SAAS.互联网的领域,因此每一篇我都会加入一小段业务的基础知识,让大家在学习技术的同时也可以了解一些业务,这边的业务不是指的business logic不是让大家去做业务人员,而是为大家带来IDEA,"没有做不到只有想不到",阿里支付宝为什么发了...不是技术,而是它的

从零开始与网站开发亲密接触

从零开始与网站开发亲密接触 去年我接手第一个网站项目开发时,并没有做网站的经验,只能试着按照以前我参与做Microsoft Office时的方法来做: 首先是打造一个便于公司内部沟通交流的内部网,其中包含"传统软件"研发需要的三个工具:文档库(存放公司各项目的文档).CVS(保存项目的各种源代码).BugFree(记录项目的各种缺陷). 然后,抓住"需求.开发.测试"三个环节: 1 要做好规划.明确需求.为什么要做这个网站.要达到什么目标?特别是需求,要详细到每个页

Redis集群_3.redis主从自动切换Sentinel(转)

Redis SentinelSentinel(哨兵)是用于监控redis集群中Master状态的工具,其已经被集成在redis2.4+的版本中 一.Sentinel作用:1):Master状态检测 2):如果Master异常,则会进行Master-Slave切换,将其中一个Slave作为Master,将之前的Master作为Slave3):Master-Slave切换后,master_redis.conf.slave_redis.conf和sentinel.conf的内容都会发生改变,即mast

AWS F1 正式上线,深度解析 FPGA 与公有云的亲密接触

1. 背景 历经近 5 个月的邀请内测,Amazon AWS 于 4 月 20 日宣布 FPGA EC2 实例 F1 正式上线.就在 F1 内测公布后这短短几个月时间,国内互联网巨头 BAT 加华为纷纷借势宣布开展 FPGA 云加速器业务,这一波异构计算之风势头之猛可见一斑. 其实,FPGA 以其高能效和可重编程的优势,在大型互联网企业内部早有应用并逐渐成为常态.例如媒体压缩,加解密,AI,大数据处理等领域,FPGA 方案较传统 CPU 和 GPGPU,往往可达到几倍甚至几十倍的能效提升.然而过

要赚钱 就要让网站与传统亲密接触

中介交易 http://www.aliyun.com/zixun/aggregation/6858.html">SEO诊断淘宝客 站长团购 云主机 技术大厅 网站命根是来自人们生活的需求,只有响应源自生活的呐喊,网站才会受到人们的认可,也只有借鉴传统的力量它才有原始的生命.网络需要创意创新,因为它区别于传统却要补充.改变.颠覆.代替传统,使网络变成新传统. 有很多人总会问,现在做什么样的网站或者做什么类型的网站能赚钱有前途.这个就等于向别人问他自己的价值观是什么了,这个其他人怎么会知道!每