实现功能描述:

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依赖配置

  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

  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

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

4、RedisServiceImpl.java

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

自定义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测试用例)

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

注意

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进行主从切换的更多相关文章

  1. Spring整合Redis&JSON序列化&Spring/Web项目部署相关

    几种JSON框架用法和效率对比: https://blog.csdn.net/sisyphus_z/article/details/53333925 https://blog.csdn.net/wei ...

  2. 网站性能优化小结和spring整合redis

    现在越来越多的地方需要非关系型数据库了,最近网站优化,当然从页面到服务器做了相应的优化后,通过在线网站测试工具与之前没优化对比,发现有显著提升. 服务器优化目前主要优化tomcat,在tomcat目录 ...

  3. Redis集群的主从切换研究

    目录 目录 1 1. 前言 1 2. slave发起选举 2 3. master响应选举 5 4. 选举示例 5 5. 哈希槽传播方式 6 6. 一次主从切换记录1 6 6.1. 相关参数 6 6.2 ...

  4. spring整合redis之hello

    1.pom.xml文件 <dependencies> <!-- spring核心包 --> <dependency> <groupId>org.spri ...

  5. Spring整合Redis时报错:java.util.NoSuchElementException: Unable to validate object

    我在Spring整合Redis时报错,我是犯了一个很低级的错误! 我设置了Redis的访问密码,在Spring的配置文件却没有配置密码这一项,配置上密码后,终于不报错了!

  6. Redis的安装以及spring整合Redis时出现Could not get a resource from the pool

    Redis的下载与安装 在Linux上使用wget http://download.redis.io/releases/redis-5.0.0.tar.gz下载源码到指定位置 解压:tar -xvf ...

  7. Spring整合redis实现key过期事件监听

    打开redis服务的配置文件   添加notify-keyspace-events Ex  如果是注释了,就取消注释 这个是在以下基础上进行添加的 Spring整合redis:https://www. ...

  8. SpringBoot开发二十四-Redis入门以及Spring整合Redis

    需求介绍 安装 Redis,熟悉 Redis 的命令以及整合Redis,在Spring 中使用Redis. 代码实现 Redis 内置了 16 个库,索引是 0-15 ,默认选择第 0 个 Redis ...

  9. (转)Spring整合Redis作为缓存

           采用Redis作为Web系统的缓存.用Spring的Cache整合Redis. 一.关于redis的相关xml文件的写法 <?xml version="1.0" ...

随机推荐

  1. TensorFlowIO操作(三)------图像操作

    图像操作 图像基本概念 在图像数字化表示当中,分为黑白和彩色两种.在数字化表示图片的时候,有三个因素.分别是图片的长.图片的宽.图片的颜色通道数.那么黑白图片的颜色通道数为1,它只需要一个数字就可以表 ...

  2. 对SVM的个人理解---浅显易懂

    原文:http://blog.csdn.net/arthur503/article/details/19966891 之前以为SVM很强大很神秘,自己了解了之后发现原理并不难,不过,“大师的功力在于将 ...

  3. (笔试题)数组A中任意两个相邻元素大小相差1,在其中查找某个数。

    题目: 数组A中任意两个相邻元素大小相差1,现给定这样的数组A和目标整数t,找出t在数组A中的位置.如数组:[1,2,3,4,3,4,5,6,5],找到4在数组中的位置. 思路: 很明显,在数组中寻找 ...

  4. 用thinkphp操作session

    写了一段代码,对session进行一些常用的操作: <?php namespace Home\Controller; use Think\Controller; class Demo1Contr ...

  5. linux下运行telnet命令出现command not find解决办法

    原因是没有安装telnet客户端和服务(缺一不可) yum list telnet*   查看telnet相关的安装包yum install telnet-server 安装telnet服务yum i ...

  6. android-文字的处理-随心

    一.计算文字的大小 String timeStr = "00:00"; int textWidth = (int)Layout.getDesiredWidth(timeStr, 0 ...

  7. filter中的dispatcher解析

    两种include方式 我自己写了一个original.jsp,另外有一个includedPage.jsp,我想在original.jsp中把includedPage.jsp引进来有两种方式: 1.& ...

  8. Android 再按一次退出应用的代码

    private long exitTime = 0; @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (key ...

  9. 解决Android 5.1系统以上通知状态栏小图标仅仅显示白色问题

    看上图,想必大家都有遇到过吧.近期俺也遇到了,找到了解决方法,如今分享下也做个记录哈. 问题发生的规则是Android5.1或者以上的手机系统使用了非常多的颜色的通知图标,就会出现,怎么解决呢,非常e ...

  10. 关于Javascript表单验证

    //验证字符串非空        var Validator = {    VerityLib: {         IsNotEmpty: function (input) {        if ...