一、Redis Cluster集群简介
Redis Cluster是一种服务器Sharding技术,3.0及以后版本开始正式提供。
redis cluster集群理解: https://www.cnblogs.com/yingchen/p/6763524.htmlredis集群配置(liunx centos)参考:http://blog.csdn.net/nuli888/article/details/52134117
redis集群节点配置参考(liunx):https://www.cnblogs.com/lykxqhh/p/5690923.html
3.0之后的功能,至少需要3(Master)+3(Slave)才能建立集群(为什么需要3个Master呢?如果你了解过Hadoop/Storm/Zookeeper这些的话,你就会明白一般分布式要求基数个节点,这样便于选举(少数服从多数的原则)),是无中心的分布式存储架构,可以在多个节点之间进行数据共享,解决了Redis高可用、可扩展等问题。
1.1、redis集群提供了以下两个好处
(1)将数据自动切分(split)到多个节点
(2)当集群中的某一个节点故障时,redis还可以继续处理客户端的请求。
一个 redis 集群包含 16384 个哈希槽(hash slot),数据库中的每个数据都属于这16384个哈希槽中的一个。集群使用公式 CRC16(key) % 16384 来计算键 key 属于哪个槽。集群中的每一个节点负责处理一部分哈希槽。
1.2、集群中的主从复制
集群中的每个节点都有1个至N个复制品,其中一个为主节点,其余的为从节点,如果主节点下线了,集群就会把这个主节点的一个从节点设置为新的主节点,继续工作。这样集群就不会因为一个主节点的下线而无法正常工作
注意:
(1)如果某一个主节点和他所有的从节点都下线的话,redis集群就会停止工作了。redis集群不保证数据的强一致性,在特定的情况下,redis集群会丢失已经被执行过的写命令
(2)使用异步复制(asynchronous replication)是redis 集群可能会丢失写命令的其中一个原因,有时候由于网络原因,如果网络断开时间太长,redis集群就会启用新的主节点,之前发给主节点的数据就会丢失。
(3)什么时候整个集群不可用(cluster_state:fail),当集群不可用时,所有对集群的操作做都不可用,收到((error) CLUSTERDOWN The cluster is down)错误
a:如果集群任意master挂掉,且当前master没有slave.集群进入fail状态,也可以理解成进群的slot映射[0-16383]不完成时进入fail状态.
b:如果集群超过半数以上master挂掉,无论是否有slave集群进入fail状态.
(4)Redis数据库内容数据量不宜过大,主要是主库宕机时恢复需要的时间开销比较大:
参考:http://storage.chinabyte.com/465/13788465.shtml
先来看一下主库宕机容灾过程:
在主库宕机的时候,我们最常见的容灾策略为“切主”。具体为从该集群剩余从库中选出一个从库并将其升级为主库,该从库升级为主库后再将剩余从库挂载至其下成为其从库,最终恢复整个主从集群结构。
以上是一个完整的容灾过程,而代价最大的过程为从库的重新挂载,而非主库的切换。
这是因为redis无法像mysql、mongodb那样基于同步的点位在主库发生变化后从新的主库继续同步数据。 在redis集群中一旦从库换主,redis的做法是将更换主库的从库清空然后从新主库完整同步一份数据再进行续传。
整个从库重做流程是这样的:
主库bgsave自身数据到磁盘—》主库发送rdb文件到从库-》 从库开始加载-》加载完毕开始续传,同时开始提供服务
很明显,在这个过程中redis的内存体积越大以上每一个步骤的时间都会被拉长,实际测试的数据如下(我们自认我们的机器性能比较好):
可以看到,当数据达到20G的时候,一个从库的恢复时间已经被拉长到了将近20分钟,如果有10个从库那么如果依次恢复则共需200分钟,而如果此时该从库承担着大量的读取请求你能够忍受这么长的恢复时间吗?
本案例演示平台为windows7 64系统,参考redis集群配置(windows)实现,若是liunx上搭建,改下项目中配置即可。
二、项目配置
2.1pom依赖
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<springframework.version>4.3.13.RELEASE</springframework.version>
<redis.version>2.9.0</redis.version>
<spring.redis.version>2.0.2.RELEASE</spring.redis.version>
<commons-lang.version>2.6</commons-lang.version>
<commons-beanutils.version>1.9.0</commons-beanutils.version>
<commons-collections.version>3.2.1</commons-collections.version>
<commons-io.version>2.4</commons-io.version>
<commons-logging.version>1.1.1</commons-logging.version>
<commons-codec.version>1.10</commons-codec.version>
<slf4j.version>1.7.7</slf4j.version>
<log4j.version>1.2.16</log4j.version>
<logback.version>1.1.2</logback.version>
</properties>
<repositories>
<!-- 有了仓库的组的概念, 我们只需要做一次引用就可以了 -->
<!-- Nexus中预设了2个仓库组,public repositories和public snapshot repositories -->
<repository>
<id>nexus</id>
<name>Team Nexus Repository</name>
<url>http://mvnrepository.com</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>${commons-lang.version}</version>
</dependency>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>${commons-collections.version}</version>
</dependency>
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>${commons-beanutils.version}</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>${commons-logging.version}</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>${commons-codec.version}</version>
</dependency>
<!--logger begin -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>${log4j.version}</version>
<exclusions>
<exclusion>
<groupId>javax.mail</groupId>
<artifactId>mail</artifactId>
</exclusion>
<exclusion>
<groupId>javax.jms</groupId>
<artifactId>jms</artifactId>
</exclusion>
<exclusion>
<groupId>com.sun.jdmk</groupId>
<artifactId>jmxtools</artifactId>
</exclusion>
<exclusion>
<groupId>com.sun.jmx</groupId>
<artifactId>jmxri</artifactId>
</exclusion>
<exclusion>
<groupId>oro</groupId>
<artifactId>oro</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- logger end -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>${redis.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.data/spring-data-redis -->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>1.8.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${springframework.version}</version>
</dependency>
</dependencies>
2.2 集群参数配置文件配置(application.properties)
#\u6700\u5927\u5206\u914D\u7684\u5BF9\u8C61\u6570
redis.pool.maxActive=1024
#\u6700\u5927\u80FD\u591F\u4FDD\u6301idel\u72B6\u6001\u7684\u5BF9\u8C61\u6570
redis.pool.maxIdle=200
#\u5F53\u6C60\u5185\u6CA1\u6709\u8FD4\u56DE\u5BF9\u8C61\u65F6\uFF0C\u6700\u5927\u7B49\u5F85\u65F6\u95F4
redis.pool.maxWait=1000
#\u5F53\u8C03\u7528borrow Object\u65B9\u6CD5\u65F6\uFF0C\u662F\u5426\u8FDB\u884C\u6709\u6548\u6027\u68C0\u67E5
redis.pool.testOnBorrow=true
redis.pool.testWhileIdle=true
redis.pool.timeBetweenEvictionRunsMillis=60000
2.3 Spring 配置文件(applicationContext-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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.1.xsd"
default-autowire="byName">
<!--以下为cluster 多redis配置 -->
<!-- 加载配置文件 -->
<context:property-placeholder location="classpath:application.properties" />
<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxTotal" value="2048" />
<property name="maxIdle" value="200" />
<property name="numTestsPerEvictionRun" value="1024" />
<property name="minEvictableIdleTimeMillis" value="-1" />
<property name="softMinEvictableIdleTimeMillis" value="10000" />
<property name="maxWaitMillis" value="1500" />
<property name="testOnReturn" value="false" />
<property name="jmxEnabled" value="true" />
<property name="blockWhenExhausted" value="false" />
<!-- 三个配置配合设置,可以起到以下三点作用: (1)、保证每次获取的连接都是可用的 (2)、定时清理失效连接,补充新连接,提高效率 -->
<property name="testOnBorrow" value="true" />
<property name="testWhileIdle" value="true" />
<property name="timeBetweenEvictionRunsMillis" value="30000" />
</bean>
<!-- 采用redis-cluster的redis -->
<bean id="redisClusterConfiguration" class="org.springframework.data.redis.connection.RedisClusterConfiguration">
<property name="maxRedirects" value="3" />
<property name="clusterNodes">
<set>
<bean class="org.springframework.data.redis.connection.RedisNode">
<constructor-arg name="host" value="127.0.0.1" />
<constructor-arg name="port" value="7000" />
</bean>
<bean class="org.springframework.data.redis.connection.RedisNode">
<constructor-arg name="host" value="127.0.0.1" />
<constructor-arg name="port" value="7001" />
</bean>
<bean class="org.springframework.data.redis.connection.RedisNode ">
<constructor-arg name="host" value="127.0.0.1" />
<constructor-arg name="port" value="7002" />
</bean>
<bean class="org.springframework.data.redis.connection.RedisNode">
<constructor-arg name="host" value="127.0.0.1" />
<constructor-arg name="port" value="7003" />
</bean>
<bean class="org.springframework.data.redis.connection.RedisNode">
<constructor-arg name="host" value="127.0.0.1" />
<constructor-arg name="port" value="7004" />
</bean>
<bean class="org.springframework.data.redis.connection.RedisNode ">
<constructor-arg name="host" value="127.0.0.1" />
<constructor-arg name="port" value="7005" />
</bean>
</set>
</property>
</bean>
<bean id="clusterJedisConnectionFactory"
class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" >
<!-- p:pool-config-ref="jedisPoolConfig" -->
<constructor-arg name="clusterConfig" ref="redisClusterConfiguration" />
<constructor-arg name="poolConfig" ref="jedisPoolConfig" />
</bean>
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
<property name="connectionFactory" ref="clusterJedisConnectionFactory" />
<property name="keySerializer">
<bean class="org.springframework.data.redis.serializer.StringRedisSerializer" />
</property>
<property name="valueSerializer">
<bean class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer" />
</property>
<!-- <property name="keySerializer"><bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/></property>
<property name="valueSerializer"><bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/></property>
<property name="hashKeySerializer"><bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/></property>
<property name="hashValueSerializer"><bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/></property> -->
</bean>
</beans>
2.4 实体类配置
public class User implements Serializable {
private static final long serialVersionUID = -1267719235225203410L;
private String uid;
private String address;
private String mobile;
private String postCode;
public String getUid() {
return uid;
}
public void setUid(String uid) {
this.uid = uid;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getMobile() {
return mobile;
}
public void setMobile(String mobile) {
this.mobile = mobile;
}
public String getPostCode() {
return postCode;
}
public void setPostCode(String postCode) {
this.postCode = postCode;
}
@Override
public String toString() {
return "User [uid=" + uid + ", address=" + address + ", mobile="
+ mobile + ", postCode=" + postCode + "]";
}
}
2.5 测试类
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.data.redis.core.DefaultTypedTuple;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ZSetOperations.TypedTuple;
/**
* 本代码需配合Redis 3.0及以上版本实现cluster功能</br>
* redis cluster集群理解:https://www.cnblogs.com/yingchen/p/6763524.html
* redis集群配置(liunx centos)参考:http://blog.csdn.net/nuli888/article/details/52134117
* redis集群配置(windows)参考:http://blog.csdn.net/mrxiagc/article/details/52799081
*
* @Description:
* @Create: 2017年12月28日 下午3:50:01
* @Modification History
* @Date Author Version Description
*/
public class SpringJedisClusterTest {
private static ApplicationContext context = null;
private static RedisTemplate<String, User> redisTemplate = null;
static {
context = new ClassPathXmlApplicationContext(
"classpath*:applicationContext-cluster.xml");
redisTemplate = (RedisTemplate) context.getBean("redisTemplate");
}
public static void main(String[] args) {
User user = new User();
user.setUid("11112222322233");
user.setAddress("上海市");
user.setMobile("mobile");
user.setPostCode("postcode");
save(user);
System.out.println(read(user));
}
public static void save(User user) {
redisTemplate.opsForValue().set(user.getUid(), user);
}
public static User read(User user) {
return redisTemplate.opsForValue().get(user.getUid());
}
public static void listSave(User user) {
List<User> users = new ArrayList<User>();
users.add(user);
redisTemplate.opsForList().leftPushAll(
"user.list.ops." + user.getUid(), users);
}
public static void listRead(User user) {
List<User> users = redisTemplate.opsForList().range(
"user.list.ops." + user.getUid(), 0, -1);
for (Iterator iterator = users.iterator(); iterator.hasNext();) {
User user1 = (User) iterator.next();
System.out.println(user1);
}
}
public static void hashSave(User user) {
Map<String, String> map = new HashMap<String, String>();
map.put("address", user.getAddress());
map.put("mobile", user.getMobile());
map.put("postCode", user.getPostCode());
redisTemplate.opsForHash()
.putAll("user.hash.ops." + user.getUid(), map);
}
public static void hashRead(User user) {
Map<Object, Object> map = redisTemplate.opsForHash().entries(
"user.hash.ops." + user.getUid());
Iterator<Entry<Object, Object>> it = map.entrySet().iterator();
while (it.hasNext()) {
Entry<Object, Object> type = it.next();
System.out.println("key:" + type.getKey() + ",value:"
+ type.getValue());
}
}
public static void setSave(User user) {
/*
* 竟然没有批量新增方法????
*/
redisTemplate.opsForSet().add("user.set.ops." + user.getUid(), user);
}
public static void setRead(User user) {
Set<User> sets = redisTemplate.opsForSet().members(
"user.set.ops." + user.getUid());
for (Iterator iterator = sets.iterator(); iterator.hasNext();) {
User user1 = (User) iterator.next();
System.out.println("user1:" + user1);
}
}
public static void zsetSave(User user) {
Set<TypedTuple<User>> zsets=new HashSet<TypedTuple<User>>();
TypedTuple<User> tuple0=new DefaultTypedTuple(user, 1d);
zsets.add(tuple0);
redisTemplate.opsForZSet().add("user.zset.ops." + user.getUid(), zsets);
}
public static void zsetRead(User user) {
Set<TypedTuple<User>> zsets=new HashSet<TypedTuple<User>>();
TypedTuple<User> tuple0=new DefaultTypedTuple(user, 1d);
zsets.add(tuple0);
Set<User> users=redisTemplate.opsForZSet().range("user.zset.ops." + user.getUid(), 0, -1);
for (Iterator iterator = users.iterator(); iterator.hasNext();) {
User user2 = (User) iterator.next();
System.out.println("user2:"+user2);
}
}
}
至此,大家就可以运行代码测试下集群功能了,在运行代码之前,最好是看下文字开头推荐的《redis cluster集群理解》博文,看了这个之后,大家在运行代码时,才会有更深刻的理解。
代码GIT地址:https://github.com/TaavettiTao/Redis.git