redis从单点、主从、哨兵、到集群的总结(阶段性总结)

2024-06-22 11:32

本文主要是介绍redis从单点、主从、哨兵、到集群的总结(阶段性总结),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

我最近看项目:发现我们当前项目用的redis是主从,但是跟单点其实没有什么区别,因为我们在应用层面没有做读写分离,所以其实从服务器只是做了一个主从复制的工作,其他的什么都没有做。

那么如果我们的系统升级,用户量上升,那么一主一从可能扛不住那么大的压力,可能需要一主多从做备机,那么假如主服务器宕机了,选举哪台从服务器做主呢?这就是一个问题,需要一个第三个人来解决,所以我查了一下,哨兵模式可以解决这个问题。哨兵模式的细节下面会讲到。

然后我又想了,那如果单台服务器无法承受100%的存储压力,那就应该将存储压力分散开来,所以集群就可以解决这个问题 了,比如我们用6台服务器做集群,3主3从,那么每台服务器只需要存储1/3即可。好,那么我们就来详细看一下这些具体怎么做的。

一、redis单点(主从)

基本上就是一主一从,我们应用层主要使用的是主节点,从节点的主要工作是从主节点做主从复制。关键时刻,如果主服务器挂掉,可以手动启动从服务器,然后更改应用层的redis的ip即可。

二、redis读写分离

常见的应用场景下我觉得redis没必要进行读写分离。

先来讨论一下为什么要读写分离:

读写分离使用于大量读请求的情况,通过多个slave分摊了读的压力,从而增加了读的性能。

过多的select会阻塞住数据库,使你增删改不能执行,而且到并发量过大时,数据库会拒绝服务。

因而通过读写分离,从而增加性能,避免拒绝服务的发生。

我认为需要读写分离的应用场景是:写请求在可接受范围内,但读请求要远大于写请求的场景。

再来讨论一下redis常见的应用场景:

  1. 1.缓存

  2. 2. 排名型的应用,访问计数型应用

  3. 3. 实时消息系统

首先说一下缓存集群,这也是非常常见的应用场景:

  1. 缓存主要解决的是用户访问时,怎么以更快的速度得到数据。

  2. 2. 单机的内存资源是很有限的,所以缓存集群会通过某种算法将不同的数据放入到不同的机器中。

  3. 3. 不同持久化数据库,一般来说,内存数据库单机可以支持大量的增删查改。

  4. 4. 如果一台机器支持不住,可以用主从复制,进行缓存的方法解决。

  5. 综上,在这个场景下应用redis 进行读写分离,完全就失去了读写分离的意义。

当然,也有可能我考虑不到的地方需要读写分离,毕竟“存在即合理”嘛,那么我们就来介绍一下这个读写分离吧。

读写分离:

对于读占比较高的场景,可以通过把一部分流量分摊导出从节点(salve) 来减轻主节点(master)压力,同时需要主要只对主节点执行写操作,如下图:

当使用从节点响应读请求时,业务端可能会遇到以下问题:

复制数据延迟
读到过期数据
从节点故障

1.数据延迟

Redis 复制数的延迟由于异步复制特性是无法避免的,延迟取决于网络带宽和命令阻塞情况,对于无法容忍大量延迟场景,可以编写外部监控程序监听主从节点的复制偏移量,当延迟较大时触发报警或者通知客户端避免读取延迟过高的从节点,实现逻辑如下图:

说明如下:

1) 监控程序(monitor) 定期检查主从节点的偏移量,主节点偏移量在info replication 的master_repl_offset 指标记录,从节点 偏移量可以查询主节点的slave0 字段的offset指标,它们的差值就是主从节点延迟的字节 量。

2)当延迟字节量过高时,比如超过10M。监控程序触发报警并通知客户端从节点延迟过高。可以采用Zookeeper的监听回调机制实现客户端通知。

3) 客户端接到具体的从节点高延迟通知后,修改读命令路由到其他从节点或主节点上。当延迟回复后,再次通知客户端,回复从节点的读命令请求。

这种方案成本较高,需要单独修改适配Redis的客户端类库。

2.读到过期数据

当主节点存储大量设置超时的数据时,如缓存数据,Redis内部需要维护过期数据删除策略,删除策略主要有两种:惰性删除和定时删除。

惰性删除:主节点每次处理读取命令时,都会检查键是否超时,如果超时则执行del命令删除键对象那个,之后del命令也会异步 发送给 从节点

需要注意的是为了保证复制的一致性,从节点自身永远不会主动删除超时数据,如上图。

定时删除:

Redis主节点在内部定时任务会循环采样一定数量的键,当发现采样的键过期就执行del命令,之后再同步给从节点,如下图

如果此时 数据的大量超时,主节点采样速度跟不上过期速度且主节点没有读取过期键的操作,那么从节点将无法收到del命令,这时在从节点 上可以读取到已经超时的数据。Redis在3.2 版本解决了这个问题,从节点 读取数据之前会检查键的过期时间来决定是否返回数据,可以升级到3.2版本来规避这个问题。

3.从节点故障问题

对于从节点故障问题,需要在客户端维护可用从节点列表,当从节点故障时立刻切换到其他从节点或主节点上。
但是建议如果仅仅是为性能考虑的话最好使用集群。

运维操作:

准备三个redis服务,依次命名文件夹子master,slave1,slave2.这里为在测试机上,不干扰原来的redis服务,我们master使用6000端口。

配置文件(redis.conf)

master配置修改端口:

   port 6000requirepass 123456

 

slave1修改配置:

 

port 6001slaveof 127.0.0.1 6000masterauth 123456 requirepass 123456

  

slave2修改配置:  

port 6002slaveof 127.0.0.1 6000masterauth 123456 requirepass 123456

requirepass是认证密码,应该之后要作主从切换,所以建议所有的密码都一致, masterauth是从机对主机验证时,所需的密码。(即主机的requirepass)

启动主机

redis-server redis.conf  

启动从机:

 redis-server redis1.confredis-server redis2.conf

输入:

ps -ef |grep redis
root      6617     1  0 18:34 ?        00:00:01 redis-server *:6000    
root      6647     1  0 18:43 ?        00:00:00 redis-server *:6001     
root      6653     1  0 18:43 ?        00:00:00 redis-server *:6002     
root      6658  6570  0 18:43 pts/0    00:00:00 grep redis

可以看到主从机的redis已经相应启动。

我们来验证下 主从复制。

master:

[root@localhost master]# redis-cli -p 6000
127.0.0.1:6000> auth 123456
OK
127.0.0.1:6000> set test chenqm
OK

slave1:

[root@localhost slave2]# redis-cli -p 6001
127.0.0.1:6001> auth 123456
OK
127.0.0.1:6001> get test
"chenqm"

slave2:

[root@localhost slave2]# redis-cli -p 6002
127.0.0.1:6002> auth 123456
OK
127.0.0.1:6002> get test
"chenqm"

 

 

可以看到主机执行写命令,从机能同步主机的值,主从复制,读写分离就实现了。

三、一主多从的哨兵模式

 

工作原理:

1.用户链接时先通过哨兵获取主机Master的信息

2.获取Master的链接后实现redis的操作(set/get)

3.当master出现宕机时,哨兵的心跳检测发现主机长时间没有响应.这时哨兵会进行推选.推选出新的主机完成任务.

4.当新的主机出现时,其余的全部机器都充当该主机的从机

这就有一个问题,就是添加哨兵以后,所有的请求都会经过哨兵询问当前的主服务器是谁,所以如果哨兵部在主服务器上面的话可能会增加服务器的压力,所以最好是将哨兵单独放在一个服务器上面。以分解压力。然后可能还有人担心哨兵服务器宕机了怎么办啊,首先哨兵服务器宕机的可能性很小,然后是如果哨兵服务器宕机了,使用人工干预重启即可,就会导致主从服务器监控的暂时不可用,不影响主从服务器的正常运行。

先配置服务器(本地)哨兵模式,直接从redis官网下载安装或者解压版,安装后的目录结构

 

然后配置哨兵模式

测试采用3个哨兵,1个主redis,2个从redis。

复制6份redis.windows.conf文件并重命名如下(开发者可根据自己的开发习惯进行重命名)

配置master.6378.conf

port:6379
#设置连接密码
requirepass:grs
#连接密码
masterauth:grs

slave.6380.conf配置

port:6380
dbfilename dump6380.rdb
#配置master
slaveof 127.0.0.1 6379

slave.6381.conf配置

port 6381
slaveof 127.0.0.1 6379
dbfilename "dump.rdb"

配置哨兵sentinel.63791.conf(其他两个哨兵配置文件一致,只修改端口号码即可)

port 63791
#主master,2个sentinel选举成功后才有效,这里的master-1是名称,在整合的时候需要一致,这里可以随便更改
sentinel monitor master-1 127.0.0.1 6379 2
#判断主master的挂机时间(毫秒),超时未返回正确信息后标记为sdown状态
sentinel down-after-milliseconds master-1 5000
#若sentinel在该配置值内未能完成failover操作(即故障时master/slave自动切换),则认为本次failover失败。
sentinel failover-timeout master-1 18000
#选项指定了在执行故障转移时, 最多可以有多少个从服务器同时对新的主服务器进行同步,这个数字越小,完成故障转移所需的时间就越长
sentinel config-epoch master-1 2

需要注意的地方

1、若通过redis-cli -h 127.0.0.1 -p 6379连接,无需改变配置文件,配置文件默认配置为bind 127.0.0.1(只允许127.0.0.1连接访问)若通过redis-cli -h 192.168.180.78 -p 6379连接,需改变配置文件,配置信息为bind 127.0.0.1 192.168.180.78(只允许127.0.0.1和192.168.180.78访问)或者将bind 127.0.0.1注释掉(允许所有远程访问)

2、masterauth为所要连接的master服务器的requirepass,如果一个redis集群中有一个master服务器,两个slave服务器,当master服务器挂掉时,sentinel哨兵会随机选择一个slave服务器充当master服务器,鉴于这种机制,解决办法是将所有的主从服务器的requirepass和masterauth都设置为一样。

3、sentinel monitor master-1 127.0.0.1 6379 2 行尾最后的一个2代表什么意思呢?我们知道,网络是不可靠的,有时候一个sentinel会因为网络堵塞而误以为一个master redis已经死掉了,当sentinel集群式,解决这个问题的方法就变得很简单,只需要多个sentinel互相沟通来确认某个master是否真的死了,这个2代表,当集群中有2个sentinel认为master死了时,才能真正认为该master已经不可用了。(sentinel集群中各个sentinel也有互相通信,通过gossip协议)。

依次启动redis

redis-server master.6379.conf


redis-server slave.6380.conf

redis-server slave.6381.conf

 

redis-server sentinel.63791.conf --sentinel(linux:redis-sentinel sentinel.63791.conf)其他两个哨兵也这样启动

使用客户端查看一下master状态

查看一下哨兵状态

现在就可以在master插入数据,所有的redis服务都可以获取到,slave只能读

整合spring,导入依赖

<dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId><version>2.8.0</version></dependency><!-- spring-redis --><dependency><groupId>org.springframework.data</groupId><artifactId>spring-data-redis</artifactId><version>1.6.4.RELEASE</version></dependency>
<dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId><version>2.4.2</version>
</dependency>

redis.properties

#redis中心  
redis.host=127.0.0.1
#redis.host=10.75.202.11  
redis.port=6379
redis.password=  
#redis.password=123456  
redis.maxTotal=200
redis.maxIdle=100
redis.minIdle=8
redis.maxWaitMillis=100000
redis.maxActive=300
redis.testOnBorrow=true
redis.testOnReturn=true
#Idle时进行连接扫描
redis.testWhileIdle=true
#表示idle object evitor两次扫描之间要sleep的毫秒数
redis.timeBetweenEvictionRunsMillis=30000
#表示idle object evitor每次扫描的最多的对象数
redis.numTestsPerEvictionRun=10
#表示一个对象至少停留在idle状态的最短时间,然后才能被idle object evitor扫描并驱逐;这一项只有在timeBetweenEvictionRunsMillis大于0时才有意义
redis.minEvictableIdleTimeMillis=60000redis.timeout=100000

配置sentinel方式一

<!-- 这个这个bean是继承RedisSentinelConfiguration,原因是我直接使用时,在注入哨兵的时候spring获取不到注入属性的方法,老是报参数异常 --><bean id="redisSentinelConfiguration" class="com.uec.village.redis.RedisConfiguration"><!-- 这里是配置哨兵模式 --><constructor-arg name="sentinels"><set><bean  class="org.springframework.data.redis.connection.RedisNode"><constructor-arg name="host" value="${redis.host}"/><constructor-arg name="port" value="63791"/></bean><bean  class="org.springframework.data.redis.connection.RedisNode"><constructor-arg name="host" value="${redis.host}"/><constructor-arg name="port" value="63792"/></bean><bean  class="org.springframework.data.redis.connection.RedisNode"><constructor-arg name="host" value="${redis.host}"/><constructor-arg name="port" value="63793"/></bean></set></constructor-arg><property name="master"><bean class="org.springframework.data.redis.connection.RedisNode"><!--必须指定主节点名称,与服务中的master名称一致--><property name="name" value="master-1"/>
<!--                 <constructor-arg name="host" value="${redis.host}"/> -->
<!--                 <constructor-arg name="port" value="6379"/> --></bean></property></bean>
<!-- 可以直接配置sentinel --><!-- 
<bean id="redisSentinelConfiguration" class="org.springframework.data.redis.connection.RedisSentinelConfiguration"><property name="sentinels"><set><bean name="sentinelNode1" class="org.springframework.data.redis.connection.RedisNode"><constructor-arg name="host" value="${redis.host}"/><constructor-arg name="port" value="63791"/></bean><bean name="sentinelNode2" class="org.springframework.data.redis.connection.RedisNode"><constructor-arg name="host" value="${redis.host}"/><constructor-arg name="port" value="63792"/></bean><bean name="sentinelNode3" class="org.springframework.data.redis.connection.RedisNode"><constructor-arg name="host" value="${redis.host}"/><constructor-arg name="port" value="63793"/></bean></set></property><property name="master"><bean name="masterNode" class="org.springframework.data.redis.connection.RedisNode"><!--必须指定主节点名称--><property name="name" value="mymaster"/><!-- <constructor-arg name="host" value="${redis.host}"/><constructor-arg name="port" value="6379"/>
--></bean></property>
</bean>
-->

配置sentinel方式二

<bean id="redisSentinelConfiguration" class="org.springframework.data.redis.connection.RedisSentinelConfiguration"><constructor-arg name="propertySource" ref="propertySource"/>
</bean><bean name="propertySource" class="org.springframework.core.io.support.ResourcePropertySource"><constructor-arg name="location" value="classpath:spring-redis-sentinel.properties" />
</bean>

spring-redis-sentinel.properties内容:

#哨兵监控主redis节点名称,必选
spring.redis.sentinel.master=mymaster
#哨兵节点
spring.redis.sentinel.nodes=192.168.48.31:26379,192.168.48.32:26379,192.168.48.33:26379

xml配置

<!-- jedis 配置 -->  <bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig" >  <property name="maxTotal" value="${redis.maxTotal}" />  <property name="maxIdle" value="${redis.maxIdle}" />  <property name="minIdle" value="${redis.minIdle}" />  <property name="maxWaitMillis" value="${redis.maxWaitMillis}" />  <property name="testOnBorrow" value="${redis.testOnBorrow}" />  <property name="testOnReturn" value="${redis.testOnReturn}" />  <property name="testWhileIdle" value="${redis.testWhileIdle}" />  <property name="timeBetweenEvictionRunsMillis" value="${redis.timeBetweenEvictionRunsMillis}" />  <property name="numTestsPerEvictionRun" value="${redis.numTestsPerEvictionRun}" />  <property name="minEvictableIdleTimeMillis" value="${redis.minEvictableIdleTimeMillis}" />  </bean >  <!-- redis服务器中心 -->  <bean id="connectionFactory"  class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" ><constructor-arg name="sentinelConfig" ref="redisSentinelConfiguration"/><constructor-arg name="poolConfig" ref="poolConfig" />  
<!--          <property name="poolConfig" ref="poolConfig" />   如果不是哨兵模式,把这行放开,注释掉上面两行的构造方法注入--><property name="port" value="${redis.port}" />  <property name="hostName" value="${redis.host}" />  <property name="password" value="${redis.password}" />  <property name="timeout" value="${redis.timeout}" ></property>  </bean >  <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate" >  <property name="connectionFactory" ref="connectionFactory" />  <property name="keySerializer" >  <bean class="org.springframework.data.redis.serializer.StringRedisSerializer" />  </property>  <property name="valueSerializer" >  <bean class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer" />  </property>  </bean >  <!-- cache配置 -->  <bean id="redisUtil" class="com.uec.village.util.RedisUtil" >  <property name="redisTemplate" ref="redisTemplate" />  </bean >  <bean id="methodCacheInterceptor" class="com.uec.village.interceptor.MethodCacheInterceptor" >  <property name="redisUtil" ref="redisUtil" />  </bean >  <!-- 配置拦截需要缓存的方法,根绝注解决定 --><bean id="methodCachePointCut" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor" > <property name="advice" > <ref local="methodCacheInterceptor" /> </property> <property name="patterns" > <set>  <!-- 确定正则表达式列表 --><value>com.service.impl...*ServiceImpl.*</value > </set> </property> </bean >

RedisConfiguration类

import static org.springframework.util.Assert.notNull;import java.util.Set;import org.springframework.data.redis.connection.RedisNode;
import org.springframework.data.redis.connection.RedisSentinelConfiguration;public class RedisConfiguration extends RedisSentinelConfiguration{public RedisConfiguration(){}public RedisConfiguration(Iterable<RedisNode> sentinels){notNull(sentinels, "Cannot set sentinels to 'null'.");Set<RedisNode> sentinels2 = getSentinels();if(!sentinels2.isEmpty()){sentinels2.clear();}for (RedisNode sentinel : sentinels) {addSentinel(sentinel);}}}

MethodCacheInterceptor拦截器

import java.lang.reflect.Method;import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;import com.uec.village.annotation.RedisCache;
import com.uec.village.util.RedisUtil;/*** 用户登录过滤器* @author snw**/
public class MethodCacheInterceptor implements MethodInterceptor {  private RedisUtil redisUtil;  /** * 初始化读取不需要加入缓存的类名和方法名称 */  public MethodCacheInterceptor() { }  @Override  public Object invoke(MethodInvocation invocation) throws Throwable {Object value = null;  String targetName = invocation.getThis().getClass().getName();  Method method = invocation.getMethod();String methodName = method.getName();  RedisCache annotation = method.getAnnotation(RedisCache.class);//说明当前方法不需要缓存的,if(annotation == null){return invocation.proceed();  }Object[] arguments = invocation.getArguments();  String key = getCacheKey(targetName, methodName, arguments);  System.out.println(key);  try {  // 判断是否有缓存  if (redisUtil.exists(key)) {  System.out.println("方法名称为:"+methodName+",根据:"+key+",从缓存中获取");return redisUtil.get(key);  }  // 写入缓存  value = invocation.proceed();  if (value != null) {  final String tkey = key;  final Object tvalue = value;  if(annotation.isTime()){redisUtil.set(tkey, tvalue);}  }  return value;} catch (Exception e) {  e.printStackTrace();  if (value == null) {  return invocation.proceed();  }  }  return value;  }  /** * 创建缓存key * * @param targetName * @param methodName * @param arguments */  private String getCacheKey(String targetName, String methodName,  Object[] arguments) {  StringBuffer sbu = new StringBuffer();  sbu.append(targetName).append("_").append(methodName);  if ((arguments != null) && (arguments.length != 0)) {  for (int i = 0; i < arguments.length; i++) {  sbu.append("_").append(arguments[i]);  }  }  return sbu.toString();  }  public void setRedisUtil(RedisUtil redisUtil) {  this.redisUtil = redisUtil;  }  }

redis工具类

import java.io.Serializable;
import java.util.Set;
import java.util.concurrent.TimeUnit;import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;/*** redis工具* @author snw**/
public class RedisUtil { private RedisTemplate<Serializable, Object> redisTemplate;  /** * 批量删除对应的value *  * @param keys */  public void remove(final String... keys) {  for (String key : keys) {  remove(key);  }  }  /** * 批量删除key *  * @param pattern */  public void removePattern(final String pattern) {  Set<Serializable> keys = redisTemplate.keys(pattern);  if (keys.size() > 0)  redisTemplate.delete(keys);  }  /** * 删除对应的value *  * @param key */  public void remove(final String key) {  if (exists(key)) {  redisTemplate.delete(key);  }  }  /** * 判断缓存中是否有对应的value *  * @param key * @return */  public boolean exists(final String key) {  return redisTemplate.hasKey(key);  }  /** * 读取缓存 *  * @param key * @return */  public Object get(final String key) {  Object result = null;  ValueOperations<Serializable, Object> operations = redisTemplate  .opsForValue();  result = operations.get(key);  return result;  }  /** * 写入缓存 *  * @param key * @param value * @return */  public boolean set(final String key, Object value) {  boolean result = false;  try {  ValueOperations<Serializable, Object> operations = redisTemplate  .opsForValue();  operations.set(key, value);  result = true;  } catch (Exception e) {  e.printStackTrace();  }  return result;  }  /** * 写入缓存 *  * @param key * @param value * @return */  public boolean set(final String key, Object value, Long expireTime) {  boolean result = false;  try {  ValueOperations<Serializable, Object> operations = redisTemplate  .opsForValue();  operations.set(key, value);  redisTemplate.expire(key, expireTime, TimeUnit.SECONDS);  result = true;  } catch (Exception e) {  e.printStackTrace();  }  return result;  }  public void setRedisTemplate(  RedisTemplate<Serializable, Object> redisTemplate) {  this.redisTemplate = redisTemplate;  }  
}

自定义注解

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RedisCache { boolean isCache() default false;
}

 

四、集群模式

一、概述

    Redis3.0版本之后支持Cluster.

1.1、redis cluster的现状

   目前redis支持的cluster特性:

  1):节点自动发现

  2):slave->master 选举,集群容错

  3):Hot resharding:在线分片

  4):进群管理:cluster xxx

  5):基于配置(nodes-port.conf)的集群管理

  6):ASK 转向/MOVED 转向机制.

1.2、redis cluster 架构

  1)redis-cluster架构图

  架构细节:

  (1)所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽.

  (2)节点的fail是通过集群中超过半数的节点检测失效时才生效.

  (3)客户端与redis节点直连,不需要中间proxy层.客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可

  (4)redis-cluster把所有的物理节点映射到[0-16383]slot上,cluster 负责维护node<->slot<->value

 

   2) redis-cluster选举:容错

  (1)领着选举过程是集群中所有master参与,如果半数以上master节点与master节点通信超过(cluster-node-timeout),认为当前master节点挂掉.

  (2):什么时候整个集群不可用(cluster_state:fail),当集群不可用时,所有对集群的操作做都不可用,收到((error) CLUSTERDOWN The cluster is down)错误

      a:如果集群任意master挂掉,且当前master没有slave.集群进入fail状态,也可以理解成进群的slot映射[0-16383]不完成时进入fail状态.

      b:如果进群超过半数以上master挂掉,无论是否有slave集群进入fail状态.

 

二、redis cluster安装

    1、下载和解包

1

2

3

cd /usr/local/

wget http://download.redis.io/releases/redis-3.2.1.tar.gz

tar -zxvf /redis-3.2.1.tar.gz

 2、 编译安装

 cd redis-3.2.1make && make install

  3、创建redis节点

     测试我们选择2台服务器,分别为:192.168.1.237,192.168.1.238.每分服务器有3个节点。

  我先在192.168.1.237创建3个节点:

 

cd /usr/local/mkdir redis_cluster  //创建集群目录mkdir 7000 7001 7002  //分别代表三个节点    其对应端口 7000 7001 7002//创建7000节点为例,拷贝到7000目录cp /usr/local/redis-3.2.1/redis.conf  ./redis_cluster/7000/   //拷贝到7001目录cp /usr/local/redis-3.2.1/redis.conf  ./redis_cluster/7001/   //拷贝到7002目录cp /usr/local/redis-3.2.1/redis.conf  ./redis_cluster/7002/

   分别对7001,7002、7003文件夹中的3个文件修改对应的配置

 

daemonize    yes                          //redis后台运行
pidfile  /var/run/redis_7000.pid          //pidfile文件对应7000,7002,7003
port  7000                                //端口7000,7002,7003
cluster-enabled  yes                      //开启集群  把注释#去掉
cluster-config-file  nodes_7000.conf      //集群的配置  配置文件首次启动自动生成 7000,7001,7002
cluster-node-timeout  5000                //请求超时  设置5秒够了
appendonly  yes                           //aof日志开启  有需要就开启,它会每次写操作都记录一条日志

   在192.168.1.238创建3个节点:对应的端口改为7003,7004,7005.配置对应的改一下就可以了。

   4、两台机启动各节点(两台服务器方式一样)

 

cd /usr/local
redis-server  redis_cluster/7000/redis.conf
redis-server  redis_cluster/7001/redis.conf
redis-server  redis_cluster/7002/redis.conf
redis-server  redis_cluster/7003/redis.conf
redis-server  redis_cluster/7004/redis.conf
redis-server  redis_cluster/7005/redis.conf

   5、查看服务

      ps -ef | grep redis   #查看是否启动成功

     netstat -tnlp | grep redis #可以看到redis监听端口

三、创建集群

  前面已经准备好了搭建集群的redis节点,接下来我们要把这些节点都串连起来搭建集群。官方提供了一个工具:redis-trib.rb(/usr/local/redis-3.2.1/src/redis-trib.rb) 看后缀就知道这鸟东西不能直接执行,它是用ruby写的一个程序,所以我们还得安装ruby.

yum -y install ruby ruby-devel rubygems rpm-build 

  再用 gem 这个命令来安装 redis接口    gem是ruby的一个工具包.

gem install redis    //等一会儿就好了
当然,方便操作,两台Server都要安装。

  上面的步骤完事了,接下来运行一下redis-trib.rb

/usr/local/redis-3.2.1/src/redis-trib.rbUsage: redis-trib <command> <options> <arguments ...>reshard        host:port--to <arg>--yes--slots <arg>--from <arg>check          host:portcall            host:port command arg arg .. argset-timeout    host:port millisecondsadd-node        new_host:new_port existing_host:existing_port--master-id <arg>--slavedel-node        host:port node_idfix            host:portimport          host:port--from <arg>help            (show this help)create          host1:port1 ... hostN:portN--replicas <arg>
For check, fix, reshard, del-node, set-timeout you can specify the host and port of any working node in the cluster.

 

     看到这,应该明白了吧, 就是靠上面这些操作 完成redis集群搭建的.

 确认所有的节点都启动,接下来使用参数create 创建 (在192.168.1.237中来创建)

 /usr/local/redis-3.2.1/src/redis-trib.rb  create  --replicas  1  192.168.1.237:7000 192.168.1.237:7001  192.168.1.237:7003 192.168.1.238:7003  192.168.1.238:7004  192.168.1.238:7005

    解释下, --replicas  1  表示 自动为每一个master节点分配一个slave节点    上面有6个节点,程序会按照一定规则生成 3个master(主)3个slave(从)

    前面已经提醒过的 防火墙一定要开放监听的端口,否则会创建失败。

 运行中,提示Can I set the above configuration? (type 'yes' to accept): yes    //输入yes

 接下来 提示  Waiting for the cluster to join..........  安装的时候在这里就一直等等等,没反应,傻傻等半天,看这句提示上面一句,Sending Cluster Meet Message to join the Cluster.

    这下明白了,我刚开始在一台Server上去配,也是不需要等的,这里还需要跑到Server2上做一些这样的操作。

    在192.168.1.238, redis-cli -c -p 700*  分别进入redis各节点的客户端命令窗口, 依次输入 cluster meet 192.168.1.238 7000……

    回到Server1,已经创建完毕了。

    查看一下 /usr/local/redis/src/redis-trib.rb check 192.168.1.237:7000

    到这里集群已经初步搭建好了。

 

四、测试

1)get 和 set数据

    redis-cli -c -p 7000

    进入命令窗口,直接 set  hello  howareyou

    直接根据hash匹配切换到相应的slot的节点上。

    还是要说明一下,redis集群有16383个slot组成,通过分片分布到多个节点上,读写都发生在master节点。

  2)假设测试

    果断先把192.168.1.238服务Down掉,(192.168.1.238有1个Master, 2个Slave) ,  跑回192.168.1.238, 查看一下 发生了什么事,192.168.1.237的3个节点全部都是Master,其他几个Server2的不见了

    测试一下,依然没有问题,集群依然能继续工作。

    原因:  redis集群  通过选举方式进行容错,保证一台Server挂了还能跑,这个选举是全部集群超过半数以上的Master发现其他Master挂了后,会将其他对应的Slave节点升级成Master.

    疑问: 要是挂的是192.168.1.237怎么办?    哥试了,cluster is down!!    没办法,超过半数挂了那救不了了,整个集群就无法工作了。 要是有三台Server,每台两Master,切记对应的主从节点

            不要放在一台Server,别问我为什么自己用脑子想想看,互相交叉配置主从,挂哪台也没事,你要说同时两台crash了,呵呵哒......

  3)关于一致性

    我还没有这么大胆拿redis来做数据库持久化哥网站数据,只是拿来做cache,官网说的很清楚,Redis Cluster is not able to guarantee strong consistency. 

 

 五、安装遇到的问题

     1、

  CC adlist.o
  /bin/sh: cc: command not found
  make[1]: *** [adlist.o] Error 127
  make[1]: Leaving directory `/usr/local/redis-3.2.1/src
  make: *** [all] Error 2

     解决办法:GCC没有安装或版本不对,安装一下

yum  install  gcc

   2、

  zmalloc.h:50:31: 
  error: jemalloc/jemalloc.h: No such file or directory
  zmalloc.h:55:2: error: 

  #error "Newer version of jemalloc required"
  make[1]: *** [adlist.o] Error 
  1
  make[1]: Leaving directory `/data0/src/redis-2.6.2/src
  make: *** [all] 
  Error 2

    解决办法:原因是没有安装jemalloc内存分配器,可以安装jemalloc 或 直接

     输入make MALLOC=libc  && make install

这篇关于redis从单点、主从、哨兵、到集群的总结(阶段性总结)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1084162

相关文章

redis群集简单部署过程

《redis群集简单部署过程》文章介绍了Redis,一个高性能的键值存储系统,其支持多种数据结构和命令,它还讨论了Redis的服务器端架构、数据存储和获取、协议和命令、高可用性方案、缓存机制以及监控和... 目录Redis介绍1. 基本概念2. 服务器端3. 存储和获取数据4. 协议和命令5. 高可用性6.

Redis的数据过期策略和数据淘汰策略

《Redis的数据过期策略和数据淘汰策略》本文主要介绍了Redis的数据过期策略和数据淘汰策略,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一... 目录一、数据过期策略1、惰性删除2、定期删除二、数据淘汰策略1、数据淘汰策略概念2、8种数据淘汰策略

Redis存储的列表分页和检索的实现方法

《Redis存储的列表分页和检索的实现方法》在Redis中,列表(List)是一种有序的数据结构,通常用于存储一系列元素,由于列表是有序的,可以通过索引来访问元素,因此可以很方便地实现分页和检索功能,... 目录一、Redis 列表的基本操作二、分页实现三、检索实现3.1 方法 1:客户端过滤3.2 方法

Python中操作Redis的常用方法小结

《Python中操作Redis的常用方法小结》这篇文章主要为大家详细介绍了Python中操作Redis的常用方法,文中的示例代码简洁易懂,具有一定的借鉴价值,有需要的小伙伴可以了解一下... 目录安装Redis开启、关闭Redisredis数据结构redis-cli操作安装redis-py数据库连接和释放增

Python中连接不同数据库的方法总结

《Python中连接不同数据库的方法总结》在数据驱动的现代应用开发中,Python凭借其丰富的库和强大的生态系统,成为连接各种数据库的理想编程语言,下面我们就来看看如何使用Python实现连接常用的几... 目录一、连接mysql数据库二、连接PostgreSQL数据库三、连接SQLite数据库四、连接Mo

redis防止短信恶意调用的实现

《redis防止短信恶意调用的实现》本文主要介绍了在场景登录或注册接口中使用短信验证码时遇到的恶意调用问题,并通过使用Redis分布式锁来解决,具有一定的参考价值,感兴趣的可以了解一下... 目录1.场景2.排查3.解决方案3.1 Redis锁实现3.2 方法调用1.场景登录或注册接口中,使用短信验证码场

Redis 多规则限流和防重复提交方案实现小结

《Redis多规则限流和防重复提交方案实现小结》本文主要介绍了Redis多规则限流和防重复提交方案实现小结,包括使用String结构和Zset结构来记录用户IP的访问次数,具有一定的参考价值,感兴趣... 目录一:使用 String 结构记录固定时间段内某用户 IP 访问某接口的次数二:使用 Zset 进行

解读Redis秒杀优化方案(阻塞队列+基于Stream流的消息队列)

《解读Redis秒杀优化方案(阻塞队列+基于Stream流的消息队列)》该文章介绍了使用Redis的阻塞队列和Stream流的消息队列来优化秒杀系统的方案,通过将秒杀流程拆分为两条流水线,使用Redi... 目录Redis秒杀优化方案(阻塞队列+Stream流的消息队列)什么是消息队列?消费者组的工作方式每

Redis如何使用zset处理排行榜和计数问题

《Redis如何使用zset处理排行榜和计数问题》Redis的ZSET数据结构非常适合处理排行榜和计数问题,它可以在高并发的点赞业务中高效地管理点赞的排名,并且由于ZSET的排序特性,可以轻松实现根据... 目录Redis使用zset处理排行榜和计数业务逻辑ZSET 数据结构优化高并发的点赞操作ZSET 结

Git提交代码详细流程及问题总结

《Git提交代码详细流程及问题总结》:本文主要介绍Git的三大分区,分别是工作区、暂存区和版本库,并详细描述了提交、推送、拉取代码和合并分支的流程,文中通过代码介绍的非常详解,需要的朋友可以参考下... 目录1.git 三大分区2.Git提交、推送、拉取代码、合并分支详细流程3.问题总结4.git push