文章出處

首先不得不服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>,否則反序列化時也會出錯


文章列表




Avast logo

Avast 防毒軟體已檢查此封電子郵件的病毒。
www.avast.com


全站熱搜
創作者介紹
創作者 大師兄 的頭像
大師兄

IT工程師數位筆記本

大師兄 發表在 痞客邦 留言(0) 人氣()