zoukankan      html  css  js  c++  java
  • redis 学习笔记(5)-Spring与Jedis的集成

    首先不得不服Spring这个宇宙无敌的开源框架,几乎整合了所有流行的其它框架,http://projects.spring.io/spring-data/ 从这上面看,当下流行的redis、solr、hadoop、mongoDB、couchBase... 全都收入囊中。对于redis整合而言,主要用到的是spring-data-redis

    使用步骤:

    一、pom添加依赖项

            <dependency>
                <groupId>org.springframework.data</groupId>
                <artifactId>spring-data-redis</artifactId>
                <version>1.4.1.RELEASE</version>
            </dependency>

    其它Spring必备组件,比如Core,Beans之类,大家自行添加吧

    观察一下:

    jedis、jredis等常用java的redis client已经支持了,不知道以后会不会集成Redisson,spring-data-redis提供了一个非常有用的类:StringRedisTemplate

    对于大多数缓存应用场景而言,字符串是最常用的缓存项,用StringRedisTemplate可以轻松应付。

    二、spring配置

     1     <bean id="redisSentinelConfiguration"
     2         class="org.springframework.data.redis.connection.RedisSentinelConfiguration">
     3         <property name="master">
     4             <bean class="org.springframework.data.redis.connection.RedisNode">
     5                 <property name="name" value="mymaster"></property>
     6             </bean>
     7         </property>
     8         <property name="sentinels">
     9             <set>
    10                 <bean class="org.springframework.data.redis.connection.RedisNode">
    11                     <constructor-arg index="0" value="10.6.1**.**5" />
    12                     <constructor-arg index="1" value="7031" />                    
    13                 </bean>
    14                 <bean class="org.springframework.data.redis.connection.RedisNode">
    15                     <constructor-arg index="0" value="10.6.1**.**6" />
    16                     <constructor-arg index="1" value="7031" />                
    17                 </bean>
    18                 <bean class="org.springframework.data.redis.connection.RedisNode">                    
    19                     <constructor-arg index="0" value="10.6.1**.**1" />
    20                     <constructor-arg index="1" value="7031" />                
    21                 </bean>
    22             </set>
    23         </property>
    24     </bean>
    25 
    26      <bean id="jedisConnFactory"
    27         class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
    28         <constructor-arg ref="redisSentinelConfiguration" />        
    29     </bean>
    30 
    31     <bean id="stringRedisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate">
    32         <property name="connectionFactory" ref="jedisConnFactory" />
    33     </bean>
    View Code

    提示:上面配置中的端口为sentinel的端口,而非redis-server的端口。

    这里我们使用Sentinel模式来配置redis连接,从上篇学习知道,sentinel是一种高可用架构,个人推荐在生产环境中使用sentinel模式。

    注:26-28行,经试验,如果修改了默认端口,这里必须明细指定hostName及port,否则运行后,无法正确读写缓存,参考下面的配置:

    (2016-4-2更新:最新1.6.4版的spring-data-redis 已经修正了这个问题,无需再指定端口和hostname)

        <bean id="jedisConnFactory"
              class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
            <property name="hostName" value="10.6.53.xxx"/>
            <property name="port" value="8830"/>
            <property name="usePool" value="false"/>
            <constructor-arg ref="redisSentinelConfiguration"/>
        </bean>

    其中hostName为当前master的IP,port为redis-server的运行端口(非sentinel端口),此外还要设置usePool为false,由于sentinel可能会自行切换master节点,如果不清楚当前的master节点是哪台机器,可以用前面提到的命令./redis-cli -p <sentinal端口号> sentinel masters查看,或者用java代码输出,参考下面的代码:

    1         ApplicationContext ctx = new FileSystemXmlApplicationContext("/opt/app/spring-redis.xml");
    2         StringRedisTemplate template = ctx.getBean(StringRedisTemplate.class);
    3         for (RedisServer m : template.getConnectionFactory().getSentinelConnection().masters()) {
    4             logger.debug(m);
    5         }

    另外<property name="usePool" value="false"/> 这里的value值如果改成true,经实际测试,发现偶尔会报如下错误(如果报错,换成false通常就可以了):

    redis.clients.jedis.exceptions.JedisDataException: ERR unknown command 'SET'

    其它注意事项:

    配置文件中的sentinels属性的Set 中的节点,并非一定要在同一个master下,也可以是归属于多个master,即:如果这里配置了10个node信息,其中1-3归属于master1,剩下的4-10属于master2,这也是允许的。

    这样调用时,通过StringRedisTemplate.getConnectionFactory().getSentinelConnection().masters()可以返回一个master的列表,然后代码中根据需要,向某一个需要的master写入缓存.

    三、单元测试

     1     @Test
     2     public void testSpringRedis() {
     3         ConfigurableApplicationContext ctx = null;
     4         try {
     5             ctx = new ClassPathXmlApplicationContext("spring.xml");
     6 
     7             StringRedisTemplate stringRedisTemplate = ctx.getBean("stringRedisTemplate", StringRedisTemplate.class);
     8 
     9             // String读写
    10             stringRedisTemplate.delete("myStr");
    11             stringRedisTemplate.opsForValue().set("myStr", "http://yjmyzz.cnblogs.com/");
    12             System.out.println(stringRedisTemplate.opsForValue().get("myStr"));
    13             System.out.println("---------------");
    14 
    15             // List读写
    16             stringRedisTemplate.delete("myList");
    17             stringRedisTemplate.opsForList().rightPush("myList", "A");
    18             stringRedisTemplate.opsForList().rightPush("myList", "B");
    19             stringRedisTemplate.opsForList().leftPush("myList", "0");
    20             List<String> listCache = stringRedisTemplate.opsForList().range(
    21                     "myList", 0, -1);
    22             for (String s : listCache) {
    23                 System.out.println(s);
    24             }
    25             System.out.println("---------------");
    26 
    27             // Set读写
    28             stringRedisTemplate.delete("mySet");
    29             stringRedisTemplate.opsForSet().add("mySet", "A");
    30             stringRedisTemplate.opsForSet().add("mySet", "B");
    31             stringRedisTemplate.opsForSet().add("mySet", "C");
    32             Set<String> setCache = stringRedisTemplate.opsForSet().members(
    33                     "mySet");
    34             for (String s : setCache) {
    35                 System.out.println(s);
    36             }
    37             System.out.println("---------------");
    38 
    39             // Hash读写
    40             stringRedisTemplate.delete("myHash");
    41             stringRedisTemplate.opsForHash().put("myHash", "PEK", "北京");
    42             stringRedisTemplate.opsForHash().put("myHash", "SHA", "上海虹桥");
    43             stringRedisTemplate.opsForHash().put("myHash", "PVG", "浦东");
    44             Map<Object, Object> hashCache = stringRedisTemplate.opsForHash()
    45                     .entries("myHash");
    46             for (Map.Entry<Object, Object> entry : hashCache.entrySet()) {
    47                 System.out.println(entry.getKey() + " - " + entry.getValue());
    48             }
    49 
    50             System.out.println("---------------");
    51 
    52         } finally {
    53             if (ctx != null && ctx.isActive()) {
    54                 ctx.close();
    55             }
    56         }
    57 
    58     }
    View Code

    运行一下,行云流水般的输出:

    ...

    信息: Created JedisPool to master at 10.6.144.***:7030
    http://yjmyzz.cnblogs.com/
    ---------------
    0
    A
    B
    ---------------
    C
    B
    A
    ---------------
    SHA - 上海虹桥
    PVG - 浦东
    PEK - 北京
    ---------------

    ...

    注意红色标出部分,从eclipse控制台的输出,还能看出当前的master是哪台服务器

    这里再补充一点小技巧:如果想遍历所有master及slave可以参考以下代码

     1     @Test
     2     public void testGetAllMasterAndSlaves() {
     3         ApplicationContext ctx = new FileSystemXmlApplicationContext("D:/spring-redis.xml");
     4         StringRedisTemplate template = ctx.getBean(StringRedisTemplate.class);
     5         RedisSentinelConnection conn = template.getConnectionFactory().getSentinelConnection();
     6         for (RedisServer m : conn.masters()) {
     7             System.out.println("master => " + m);//打印master信息
     8             Collection<RedisServer> slaves = conn.slaves(m);
     9             //打印该master下的所有slave信息
    10             for (RedisServer s : slaves) {
    11                 System.out.println("slaves of " + m + " => " + s);
    12             }
    13             System.out.println("--------------");
    14         }
    15         ((FileSystemXmlApplicationContext) ctx).close();
    16     }
    View Code

    输出类似下面的结果:

    master => 172.20.16.191:6379
    slaves of 172.20.16.191:6379 => 172.20.16.192:6379

    注:这里输出的slaves列表,经实际测试,发现只是根据redis server端的配置呆板的返回slave node列表,不管这些node是死是活,换句话说,就算某个slave已经down掉,这里依然会返回。

    三、POJO对象的缓存

    Spring提供的StringRedisTemplate只能对String操作,大多数情况下已经够用,但如果真需要向redis中存放POJO对象也不难,我们可以参考StringRedisTemplate的源码,扩展出ObjectRedisTemplate

     1 package org.springframework.data.redis.core;
     2 
     3 import org.springframework.data.redis.connection.DefaultStringRedisConnection;
     4 import org.springframework.data.redis.connection.RedisConnection;
     5 import org.springframework.data.redis.connection.RedisConnectionFactory;
     6 import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
     7 import org.springframework.data.redis.serializer.RedisSerializer;
     8 
     9 public class ObjectRedisTemplate<T> extends RedisTemplate<String, T> {
    10 
    11     public ObjectRedisTemplate(RedisConnectionFactory connectionFactory,
    12             Class<T> clazz) {
    13 
    14         RedisSerializer<T> objectSerializer = new Jackson2JsonRedisSerializer<T>(
    15                 clazz);
    16 
    17         RedisSerializer<String> objectKeySerializer = new Jackson2JsonRedisSerializer<String>(
    18                 String.class);
    19 
    20         setKeySerializer(objectKeySerializer);
    21         setValueSerializer(objectSerializer);
    22         setHashKeySerializer(objectSerializer);
    23         setHashValueSerializer(objectSerializer);
    24 
    25         setConnectionFactory(connectionFactory);
    26         afterPropertiesSet();
    27     }
    28 
    29     protected RedisConnection preProcessConnection(RedisConnection connection,
    30             boolean existingConnection) {
    31         return new DefaultStringRedisConnection(connection);
    32     }
    33 }
    View Code

    然后就可以这样用了:

     1     @Test
     2     public void testSpringRedis() {
     3         ConfigurableApplicationContext ctx = null;
     4         try {
     5             ctx = new ClassPathXmlApplicationContext("spring.xml");
     6 
     7             JedisConnectionFactory connFactory = ctx.getBean(
     8                     "jedisConnFactory", JedisConnectionFactory.class);
     9 
    10             ObjectRedisTemplate<SampleBean> template = new ObjectRedisTemplate<SampleBean>(
    11                     connFactory, SampleBean.class);
    12 
    13             template.delete("myBean");
    14             SampleBean bean = new SampleBean("菩提树下的杨过");
    15             template.opsForValue().set("myBean", bean);
    16 
    17             System.out.println(template.opsForValue().get("myBean"));
    18             
    19         } finally {
    20             if (ctx != null && ctx.isActive()) {
    21                 ctx.close();
    22             }
    23         }
    24     }
    View Code

    其中SampleBean的定义如下:

     1 package com.cnblogs.yjmyzz;
     2 
     3 import java.io.Serializable;
     4 
     5 public class SampleBean implements Serializable {
     6 
     7     private static final long serialVersionUID = -303232410998377570L;
     8 
     9     private String name;
    10 
    11     public SampleBean() {
    12     }
    13 
    14     public SampleBean(String name) {
    15         this.name = name;
    16     }
    17 
    18     public String getName() {
    19         return name;
    20     }
    21 
    22     public void setName(String name) {
    23         this.name = name;
    24     }
    25 
    26     public String toString() {
    27         return "name:" + name;
    28     }
    29 
    30 }
    View Code

    注:由于不是标准的String类型,所以在redis控制台,用./redis-cli get myBean是看不到缓存内容的,只能得到nil的输出,不要误以为set没成功!通过代码是可以正常get到缓存值的。 

    另外关于POJO对象的缓存,还有二个注意事项:

    a) POJO类必须要有默认的无参构造函数,否则反序列化时会报错

    b) ObjectRedisTemplate<T>中的T不能是接口,比如 DomainModelA继承自接口 IModelA,使用ObjectRedisTemplate时,要写成ObjectRedisTemplate<DomainModelA>而不是ObjectRedisTemplate<IModelA>,否则反序列化时也会出错

  • 相关阅读:
    301重定向 修改默认网站访问文件
    修改本地host 文件 实现多“域名”访问 host'实现FQ
    thinkphp3.2.3 整合 富文本编辑器
    thinkphp3.2.3 跨数据库查询
    git 码云上关联仓库 克隆代码
    PHP GD库中输出图像乱码问题
    mysql 新建用户
    
    算法思想:
    算法思想:
  • 原文地址:https://www.cnblogs.com/yjmyzz/p/integrate-redis-with-spring.html
Copyright © 2011-2022 走看看