Spring整合redis,通过sentinel进行主从切换

Posted ppjj

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring整合redis,通过sentinel进行主从切换相关的知识,希望对你有一定的参考价值。

实现功能描述:

        redis服务器进行Master-slaver-slaver-....主从配置,通过2台sentinel进行failOver故障转移,自动切换,采用该代码完全可以直接用于实际生产环境。

       

       题外话:

  研究Redis也有一段时间了,在前面的Redis系列文章中,介绍了Redis的安装,集群配置,及节点的增加和删除,但是并未实际的使用到项目中,趁这周末时间,

  参照项目中实际的使用场景,做了一个Redis集群Spring整合的案例,在介绍案例之前,先简单介绍下Redis集群的方式有哪些 
  1、单机版 不解释 
  2、Sentinel 哨兵模式
  3、Redis Cluster Redis官方集群方案(服务端集群)
  4、Redis Sharding集群(客户端集群)

  

  redis集群分为服务端集群和客户端分片,redis3.0以上版本实现了集群机制,即服务端集群,3.0以下使用客户端分片(Sharding),即Redis Sharding集群。

  redis3.0服务端集群,Redis Cluster,它解决了多Redis实例协同服务问题。Redis Cluster可以说是服务端Sharding分片技术的体现,

  即将键值按照一定算法合理分配到各个实例分片上,同时各个实例节点协调沟通,共同对外承担一致服务。即使用哈希槽,计算key的CRC16结果再模16834。

  3.0以下版本采用Key的一致性hash算法来区分key存储在哪个Redis实例上。正确的说,哨兵模式是一主多从的结构,并不属于真正的集群,

  真正的集群应该是多主多从的结构,需要有多台不同物理地址的主机,无赖家里只有一台PC,只能使用Sentinel模式来做集群。

  先来看下Sentinel模式的集群架构图

技术分享图片
  首先需要有一台监控服务器,也就是Sentinel,一台主服务器Master,多台从服务器Slave,具体的配置可以参考另一篇博文《Redis序列之Sentinel》,

  假设Sentinel,Master,Slave都已经配置好了,对应地址分别为: 
  Sentinel 127.0.0.1:26379,127.0.0.1:26479 
  Master 127.0.0.1:10003 
  Slave 127.0.0.1:10001,127.0.0.1:10002

       一般来说这样的部署足以支持数以百万级的用户,但如果数量实在是太高,此时redis的Master-Slaver主从不一定能够满足,因此进行redis的分片。

       本文不讲解redis的分片,但如果你使用了,需要注意的按照另一篇文章的介绍:Sentinel&Jedis看上去是个完美的解决方案,这句话只说对了一半,

       在无分片的情况是这样,但我们的应用使用了数据分片-sharing,数据被平均分布到4个不同的实例上,每个实例以主从结构部署,Jedis没有提供

       基于Sentinel的ShardedJedisPool,也就是说在4个分片中,如果其中一个分片发生主从切换,应用所使用的ShardedJedisPool无法获得通知,所有

         对那个分片的操作将会失败。文章中提出了解决方案,请参考《基于Redis Sentinel的Redis集群(主从&Sharding)高可用方案

        该代码模拟多线程向redis中set/get。

1、maven依赖配置

[html] view plain copy
 
  1. <dependency>  
  2.     <groupId>org.springframework.data</groupId>  
  3.         <artifactId>spring-data-redis</artifactId>  
  4.         <version>1.4.1.RELEASE</version>  
  5.     </dependency>  
  6.     <dependency>  
  7.         <groupId>redis.clients</groupId>  
  8.         <artifactId>jedis</artifactId>  
  9.         <version>2.6.2</version>  
  10.     </dependency>  
  11.     <dependency>  
  12.         <groupId>org.apache.commons</groupId>  
  13.         <artifactId>commons-lang3</artifactId>  
  14.         <version>3.3.2</version>  
  15. </dependency>  

2、redis.properties

[plain] view plain copy
 
  1. # Redis settings  
  2. #sentinel1的IP和端口  
  3. im.hs.server.redis.sentinel1.host=192.168.62.154  
  4. im.hs.server.redis.sentinel1.port=26379  
  5. #sentinel2的IP和端口  
  6. im.hs.server.redis.sentinel2.host=192.168.62.153  
  7. im.hs.server.redis.sentinel2.port=26379  
  8. #sentinel的鉴权密码  
  9. im.hs.server.redis.sentinel.masterName=155Master  
  10. im.hs.server.redis.sentinel.password=hezhixiong  
  11. #最大闲置连接数  
  12. im.hs.server.redis.maxIdle=500  
  13. #最大连接数,超过此连接时操作redis会报错  
  14. im.hs.server.redis.maxTotal=5000  
  15. im.hs.server.redis.maxWaitTime=1000  
  16. im.hs.server.redis.testOnBorrow=true  
  17. #最小闲置连接数,spring启动的时候自动建立该数目的连接供应用程序使用,不够的时候会申请。  
  18. im.hs.server.redis.minIdle=300  

3、spring-redis.xml

[html] view plain copy
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <beans xmlns="http://www.springframework.org/schema/beans"  
  3.     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"  
  4.     xmlns:context="http://www.springframework.org/schema/context"  
  5.     xmlns:jee="http://www.springframework.org/schema/jee" xmlns:tx="http://www.springframework.org/schema/tx"  
  6.     xmlns:aop="http://www.springframework.org/schema/aop"  
  7.     xsi:schemaLocation="  
  8.             http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd  
  9.             http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">  
  10.   
  11.     <!-- Spring自动将该包目录下标记为@Service的所有类作为spring的Bean -->  
  12.     <context:component-scan base-package="com.gaojiasoft.test.redis" />  
  13.   <!-- redis属性文件 -->
  14.     <context:property-placeholder location="classpath:conf/redis/redis.properties" />  
  15.   <!-- 启动缓存注解功能,否则注解不会生效 -->
  16.   <cache:annotation-driven cache-manager="cacheManager"/>
  17.   <!-- redis属性配置 -->
  18.     <bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">  
  19.         <property name="maxTotal" value="${im.hs.server.redis.maxTotal}" />  
  20.         <property name="minIdle" value="${im.hs.server.redis.minIdle}" />  
  21.         <property name="maxWaitMillis" value="${im.hs.server.redis.maxWaitTime}" />  
  22.         <property name="maxIdle" value="${im.hs.server.redis.maxIdle}" />  
  23.         <property name="testOnBorrow" value="${im.hs.server.redis.testOnBorrow}" />  
  24.         <property name="testOnReturn" value="true" />  
  25.         <property name="testWhileIdle" value="true" />  
  26.     </bean>  
  27.   
  28. <!-- redis集群配置 哨兵模式 -->
  29.     <bean id="sentinelConfiguration"  
  30.         class="org.springframework.data.redis.connection.RedisSentinelConfiguration">  
  31.         <property name="master">  
  32.             <bean class="org.springframework.data.redis.connection.RedisNode">  
  33. <!--这个值要和Sentinel中指定的master的值一致,不然启动时找不到Sentinel会报错的-->
  34.                 <property name="name" value="${im.hs.server.redis.sentinel.masterName}"></property>  
  35.             </bean>  
  36.         </property>
  37. <!--记住了,这里是指定Sentinel的IP和端口,不是Master和Slave的-->
  38.         <property name="sentinels">  
  39.             <set>  
  40.                 <bean class="org.springframework.data.redis.connection.RedisNode">  
  41.                     <constructor-arg name="host"  
  42.                         value="${im.hs.server.redis.sentinel1.host}"></constructor-arg>  
  43.                     <constructor-arg name="port"  
  44.                         value="${im.hs.server.redis.sentinel1.port}"></constructor-arg>  
  45.                 </bean>  
  46.                 <bean class="org.springframework.data.redis.connection.RedisNode">  
  47.                     <constructor-arg name="host"  
  48.                         value="${im.hs.server.redis.sentinel2.host}"></constructor-arg>  
  49.                     <constructor-arg name="port"  
  50.                         value="${im.hs.server.redis.sentinel2.port}"></constructor-arg>  
  51.                 </bean>  
  52.             </set>  
  53.         </property>  
  54.     </bean>  
  55.   
  56.     <bean id="connectionFactory"  
  57.         class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" p:password="${im.hs.server.redis.sentinel.password}">  
  58.         <constructor-arg name="sentinelConfig" ref="sentinelConfiguration"></constructor-arg>  
  59.         <constructor-arg name="poolConfig" ref="poolConfig"></constructor-arg>  
  60.     </bean>  
  61.   
  62.     <bean id="redisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate">  
  63.         <property name="connectionFactory" ref="connectionFactory" />  
  64.     </bean>
  65. <!-- 缓存管理器 -->
  66. <bean id="cacheManager" class="org.springframework.data.redis.cache.RedisCacheManager">
  67. <constructor-arg ref="redisTemplate" />
  68. </bean>
  69. </beans>

4、RedisServiceImpl.java

[java] view plain copy
  1. package com.gaojiasoft.test.redis;  
  2.   
  3. import java.util.concurrent.LinkedBlockingQueue;  
  4. import java.util.concurrent.ThreadPoolExecutor;  
  5. import java.util.concurrent.TimeUnit;  
  6.   
  7. import org.apache.commons.lang3.concurrent.BasicThreadFactory;  
  8. import org.slf4j.Logger;  
  9. import org.slf4j.LoggerFactory;  
  10. import org.springframework.beans.factory.annotation.Autowired;  
  11. import org.springframework.dao.DataAccessException;  
  12. import org.springframework.data.redis.connection.RedisConnection;  
  13. import org.springframework.data.redis.core.RedisCallback;  
  14. import org.springframework.data.redis.core.RedisTemplate;  
  15. import org.springframework.stereotype.Service;  
  16.   
  17. @Service("redisService")  
  18. public class RedisServiceImpl {  
  19.   
  20.     private Logger logger = LoggerFactory.getLogger("RedisServiceImpl");  
  21.   
  22.     @Autowired  
  23.     RedisTemplate<?, ?> redisTemplate;  
  24.   
  25.     // 线程池  
  26.     private static final ThreadPoolExecutor executor = new ThreadPoolExecutor(  
  27.             256, 256, 30L, TimeUnit.SECONDS,  
  28.             new LinkedBlockingQueue<Runnable>(),  
  29.             new BasicThreadFactory.Builder().daemon(true)  
  30.                     .namingPattern("redis-oper-%d").build(),  
  31.             new ThreadPoolExecutor.CallerRunsPolicy());  
  32.     public void set(final String key, final String value) {  
  33.         redisTemplate.execute(new RedisCallback<Object>() {  
  34.             @Override  
  35.             public Object doInRedis(RedisConnection connection)  
  36.                     throws DataAccessException {  
  37.                 connection.set(  
  38.                         redisTemplate.getStringSerializer().serialize(key),  
  39.                         redisTemplate.getStringSerializer().serialize(value));  
  40.                 logger.debug("save key:" + key + ",value:" + value);  
  41.                 return null;  
  42.             }  
  43.         });  
  44.     }  
  45.   
  46. @Cacheable(value="user")
  47.     public String get(final String key) {  
  48.         return redisTemplate.execute(new RedisCallback<String>() {  
  49.             @Override  
  50.             public String doInRedis(RedisConnection connection)  
  51.                     throws DataAccessException {  
  52.                 byte[] byteKye = redisTemplate.getStringSerializer().serialize(  
  53.                         key);  
  54.                 if (connection.exists(byteKye)) {  
  55.                     byte[] byteValue = connection.get(byteKye);  
  56.                     String value = redisTemplate.getStringSerializer()  
  57.                             .deserialize(byteValue);  
  58.                     logger.debug("get key:" + key + ",value:" + value);  
  59.                     return value;  
  60.                 }  
  61.                 logger.error("valus does not exist!,key:"+key);  
  62.                 return null;  
  63.             }  
  64.         });  
  65.     }  
  66.   
  67.     public void delete(final String key) {  
  68.         redisTemplate.execute(new RedisCallback<Object>() {  
  69.             public Object doInRedis(RedisConnection connection) {  
  70.                 connection.del(redisTemplate.getStringSerializer().serialize(  
  71.                         key));  
  72.                 return null;  
  73.             }  
  74.         });  
  75.     }  
  76.   
  77.     /** 
  78.      * 线程池并发操作redis 
  79.      *  
  80.      * @param keyvalue 
  81.      */  
  82.     public void mulitThreadSaveAndFind(final String keyvalue) {  
  83.         executor.execute(new Runnable() {  
  84.             @Override  
  85.             public void run() {  
  86.                 try {  
  87.                     set(keyvalue, keyvalue);  
  88.                     get(keyvalue);  
  89.                 } catch (Throwable th) {  
  90.                     // 防御性容错,避免高并发下的一些问题  
  91.                     logger.error("", th);  
  92.                 }  
  93.             }  
  94.         });  
  95.     }  
  96. }  

自定义Key生成器
默认生成器是SimpleKeyGenerator,生成的Key是经过HashCode转换过的,不能通过Key清除指定接口的缓存,因此需要我们自己实现Key生成器,增加Key生成器实现类CacheKeyGenerator,并实现KeyGenerator接口,代码如下:

public class CacheKeyGenerator implements KeyGenerator {
    @Override
    public Object generate(Object target, Method method, Object... params) {
        StringBuilder key = new StringBuilder();
        key.append(target.getClass().getName());
        key.append(".");
        key.append(method.getName());
        return key.toString();
    }
}

这里Key的生成规则是类的限定名+方法名,可以确保在同一项目中,key不会重名,如果还需要把查询条件也作为Key的一部分,可以把params加进来

Key生成器需要配置在applicationContext.xml文件中,如下

<!-- 启动缓存注解功能,否则缓解不会生效,并自定义Key生成策略  -->
<cache:annotation-driven cache-manager="cacheManager" key-generator="cacheKeyGenerator"/>
<bean id="cacheKeyGenerator" class="com.bug.common.CacheKeyGenerator"></bean>

5、RedisTest.java   (Junit测试用例)

[java] view plain copy
 
    1. package com.gaojiasoft.test.redis;  
    2.   
    3. import org.junit.Test;  
    4. import org.springframework.context.ConfigurableApplicationContext;  
    5. import org.springframework.context.support.ClassPathXmlApplicationContext;  
    6.   
    7. public class RedisTest {  
    8.   
    9.     private static ConfigurableApplicationContext context;  
    10.   
    11.     RedisServiceImpl service;  
    12.   
    13.     @Test  
    14.     public void testSave() throws InterruptedException {  
    15.         context = new ClassPathXmlApplicationContext(  
    16.                 "classpath:conf/redis/spring-redis.xml");  
    17.         service = (RedisServiceImpl) context.getBean("redisService");  
    18.   
    19.         int i = 1;  
    20.         while (true) {  
    21.             Thread.sleep(1);  
    22.             try {  
    23.                 service.mulitThreadSaveAndFind("" + i);  
    24.             } catch (Exception e) {  
    25.                 e.printStackTrace();  
    26.             }  
    27.             i++;  
    28.         }  
    29.     }  
    30. }  

注意

1) 在配置Redis的sentinel.conf文件时注意使用外部可以访问的ip地址,因为当redis-sentinel服务和redis-server在同一台机器的时候,

  主服务发生变化时配置文件中将主服务ip变为127.0.0.1,这样外部就无法访问了。

2)发生master迁移后,如果遇到运维需要,想重启所有redis,必须最先重启“新的”master节点,否则sentinel会一直找不到master。

3)Sentinel的集群模型,在Java端配置时,只需要指定有哪些Sentinel就可以了,不需要指定Master和Slave,之前不清楚,导致启动一直报找不到可用的Sentinel 
4)在Spring配置文件中一定要增加<cache:annotation-driven cache-manager="cacheManager"/>注解声明,否则缓存不会生效,之前缓存一直不生效的原因

  就是这个

5)spring版本要升到4.X

6)<cache:annotation-driven/>的cache-manager属性默认值是cacheManager,默认情况下可以不指定,如果我们的定义的CacheManager名称不是cacheManager,

  就需要显示指定; <cache:annotation-driven/>的另一个属性是model,可选值是proxy和aspectj,默认是proxy,当model=proxy时,

  只有当缓存方法在声名的类外部被调用时,spring cache才会生效,换句话说就是如果在同一个类中一个方法调用当前类的缓存方法,缓存不生效,

  而model=aspectj时就不会有这个问题;另外model=proxy时,@Cacheable等注解加在public方法上,缓存才会生效,加在private上是不会生效的,

  而model=aspectj时也不会有这个问题,<cache:annotation-driven/>还可以指定一个proxy-target-class属性,表示是否要代理class,默认为false。

  @Cacheable、@cacheEvict等也可以标注在接口上,这对于基于接口的代理来说是没有什么问题的,但要注意的是当设置proxy-target-class为true或者mode

  为aspectj时,是直接基于class进行操作的,定义在接口上的@Cacheable等Cache注解不会被识别到,对应的Spring Cache也不会起作用

 

  本文转自:http://blog.csdn.net/kity9420/article/details/53571718    http://blog.csdn.net/tzszhzx/article/details/44590871

 











以上是关于Spring整合redis,通过sentinel进行主从切换的主要内容,如果未能解决你的问题,请参考以下文章

Spring Cloud Gateway 整合 sentinel 实现流控熔断

Spring Cloud Gateway 整合 sentinel 实现流控熔断

Spring Cloud Gateway 整合 sentinel 实现流控熔断

Redis Cluste部署与Jedis整合Sentinel与Clusterr

Spring Cloud Gateway 整合 sentinel 实现流控熔断

Spring Cloud Gateway 整合 sentinel 实现流控熔断