zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    打破 Serverless 落地边界,阿里云 SAE 发布 5 大新特性
    2021云栖大会|东方通正式加入阿里云云原生合作伙伴计划,强强联手共创国产数字化转型新风向!
    跨越行业绊脚石,阿里云函数计算发布 7 大技术突破
    OpenYurt 深度解读|开启边缘设备的云原生管理能力
    云原生网关开源、自研、商业化三位一体战略背后的思考
    云栖发布|企业级互联网架构全新升级 ,助力数字创新
    云栖收官:想跟远道而来的朋友们说
    阿里云容器服务多项重磅发布:高效智能、安全无界的新一代平台
    云栖掠影|回首开源十年,RocketMQ 焕发新生
    云栖大会第二天:ACK Anywhere 来了
  • 原文地址:https://www.cnblogs.com/nizuimeiabc1/p/8568758.html
Copyright © 2011-2022 走看看