Spring项目中添加Redis服务与缓存同步问题

一、Redis应用场景

     本项目使用redis对图片进行缓存存储,展示图片时,先根据图片ID去Redis缓存中查取,没有再查数据库,同时将该图片set进Redis。

     使用spring 容器对Jedis进行管理。

二、Redis与Spring整合

1、新建applicationContext-jedis.xml配置文件,用于配置Redis连接信息和构造方法参数,实例化Jedis对象 

[html]  view plain  copy
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <beans xmlns="http://www.springframework.org/schema/beans"  
  3.     xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"  
  4.     xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"  
  5.     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  6.     xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd  
  7.     http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd  
  8.     http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd  
  9.     http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.0.xsd">  
  10.   
  11.   
  12.     <!-- 连接池配置 -->  
  13.     <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">  
  14.         <!-- 最大连接数 -->  
  15.         <property name="maxTotal" value="30" />  
  16.         <!-- 最大空闲连接数 -->  
  17.         <property name="maxIdle" value="10" />  
  18.         <!-- 每次释放连接的最大数目 -->  
  19.         <property name="numTestsPerEvictionRun" value="1024" />  
  20.         <!-- 释放连接的扫描间隔(毫秒) -->  
  21.         <property name="timeBetweenEvictionRunsMillis" value="30000" />  
  22.         <!-- 连接最小空闲时间 -->  
  23.         <property name="minEvictableIdleTimeMillis" value="1800000" />  
  24.         <!-- 连接空闲多久后释放, 当空闲时间>该值 且 空闲连接>最大空闲连接数 时直接释放 -->  
  25.         <property name="softMinEvictableIdleTimeMillis" value="10000" />  
  26.         <!-- 获取连接时的最大等待毫秒数,小于零:阻塞不确定的时间,默认-1 -->  
  27.         <property name="maxWaitMillis" value="1500" />  
  28.         <!-- 在获取连接的时候检查有效性, 默认false -->  
  29.         <property name="testOnBorrow" value="true" />  
  30.         <!-- 在空闲时检查有效性, 默认false -->  
  31.         <property name="testWhileIdle" value="true" />  
  32.         <!-- 连接耗尽时是否阻塞, false报异常,ture阻塞直到超时, 默认true -->  
  33.         <property name="blockWhenExhausted" value="false" />  
  34.     </bean>  
  35.   
  36.     <!-- jedis客户端单机版 -->  
  37.    
  38.     <bean id="redisClient" class="redis.clients.jedis.JedisPool">  
  39.           
  40.         <constructor-arg name="host" value="192.168.132.128"></constructor-arg>  
  41.         <constructor-arg name="port" value="6379"></constructor-arg>  
  42.     </bean>   
  43.     <!-- 注入单机版bean -->  
  44.     <bean id="jedisClient" class="com.taotao.rest.dao.JedisClientSingle"/>  
  45.   
  46.   
  47.     <!-- 集群版 -->  
  48.     <!-- <bean id="redisClient" class="redis.clients.jedis.JedisCluster">  
  49.         <constructor-arg name="nodes">  
  50.             <set>  
  51.                 <bean class="redis.clients.jedis.HostAndPort">  
  52.                     <constructor-arg name="host" value="192.168.132.128" />  
  53.                     <constructor-arg name="port" value="7001" />  
  54.                 </bean>  
  55.                 <bean class="redis.clients.jedis.HostAndPort">  
  56.                     <constructor-arg name="host" value="192.168.132.128" />  
  57.                     <constructor-arg name="port" value="7002" />  
  58.                 </bean>  
  59.                 <bean class="redis.clients.jedis.HostAndPort">  
  60.                     <constructor-arg name="host" value="192.168.132.128" />  
  61.                     <constructor-arg name="port" value="7003" />  
  62.                 </bean>  
  63.                 <bean class="redis.clients.jedis.HostAndPort">  
  64.                     <constructor-arg name="host" value="192.168.132.128" />  
  65.                     <constructor-arg name="port" value="7004" />  
  66.                 </bean>  
  67.                 <bean class="redis.clients.jedis.HostAndPort">  
  68.                     <constructor-arg name="host" value="192.168.132.128" />  
  69.                     <constructor-arg name="port" value="7005" />  
  70.                 </bean>  
  71.                 <bean class="redis.clients.jedis.HostAndPort">  
  72.                     <constructor-arg name="host" value="192.168.132.128" />  
  73.                     <constructor-arg name="port" value="7006" />  
  74.                 </bean>  
  75.             </set>  
  76.         </constructor-arg>  
  77.         <constructor-arg name="poolConfig" ref="jedisPoolConfig"></constructor-arg>  
  78.     </bean> -->  
  79.     <!-- 集群版bean -->  
  80.     <!-- <bean id="jedisClientCluster" class="com.taotao.rest.dao.JedisClientCluster"/> -->  
  81. </beans>  

2、编写测试类测试链接并进行数据操作

[html]  view plain  copy
  1.       public void springSingle() {  
  2.     ApplicationContext c=new ClassPathXmlApplicationContext("classpath:spring/applicationContext-*.xml");  
  3.     JedisPool pool=(JedisPool)c.getBean("redisClient");  
  4.     Jedis jedis =pool.getResource();  
  5.     String string=jedis.get("key1");  
  6.     System.out.println(string);  
  7.     jedis.close();  
  8.     pool.close();         
  9. }  
  10.   
  11.   
  12. public void springCluster() {  
  13.     ApplicationContext c=new ClassPathXmlApplicationContext("classpath:spring/applicationContext-*.xml");  
  14.     JedisCluster cluster=(JedisCluster)c.getBean("redisClient");  
  15.     String string =cluster.get("key");  
  16.     System.out.println(string);  
  17.     cluster.close();          
  18. }     

三、Redis项目应用

1、单机版和集群版底层抽象

     由于Jedis提供的类,单机版考虑到避免每次新建连接,采取RedisPool对象创建redis连接池,在使用getResourse方法获得连接对象。但Cluster集群环境下,可直接通过set、get方法对集群对象进行操作,集群内部节点根据经过hash算法后得到的值匹配存储到指定的节点内存中。

     所以为了应对这两种不同的Redis类型,本项目将常用的方法写入接口,提供量靠不同的实现。下面仅展示1个实现

[html]  view plain  copy
  1. //接口  
  2. public interface JedisClient {  
  3.     String get(String key);  
  4.     String set(String key ,String value);  
  5.       
  6.     //hash hashkey 对应多个值得key-value  
  7.     String hget(String hkey,String key);  
  8.     long hset(String hkey,String key,String value);  
  9.       
  10.     //其他常用方法  
  11.     long incr(String key);  
  12.     long expire(String key, int second);  
  13.     long ttl(String key);  
  14.     long del(String key);  
  15.     long hdel(String hkey,String key);  
  16. }  
  17. //集群实现  
  18. public class JedisClientCluster implements JedisClient{  
  19.     @Autowired  
  20.     private JedisCluster jedisCluster;  
  21.       
  22.     @Override  
  23.     public String get(String key) {  
  24.         return jedisCluster.get(key);  
  25.     }  
  26. }  
  27. //连接池实现  
  28. public class JedisClientSingle implements JedisClient{  
  29.     @Autowired//bytype 注入spring容器中的jedisPool对象  
  30.     private JedisPool jedisPool;  
  31.       
  32.     @Override  
  33.     public String get(String key) {  
  34.         Jedis jedis=jedisPool.getResource();  
  35.         String string=jedis.get(key);  
  36.         jedis.close();  
  37.         return string;  
  38.     }  
  39. }  

2、在项目中添加缓存处理

     在项目中添加缓存处理需要注意,即便缓存操作失败,也不能影响正常业务操作。

[html]  view plain  copy
  1.         @Autowired  
  2. private JedisClient jedisClient;  
  3. @Value("${INDEX_CONTENT_REDIS_KEY}")  
  4. private String INDEX_CONTENT_REDIS_KEY;  
  5.   
  6. @Override  
  7. public List<TbContent> getContentList(long contentId) {  
  8.     //先查缓存  
  9.     try {  
  10.         String result=jedisClient.hget(INDEX_CONTENT_REDIS_KEY, contentId+"");  
  11.         if(!StringUtils.isEmpty(result))  
  12.         {  
  13.             //吧字符串转换成list  
  14.             List<TbContent> resultList=JsonUtils.jsonToList(result, TbContent);  
  15.             return resultList;  
  16.         }  
  17.     } catch (Exception e) {  
  18.         // TODO: handle exception  
  19.     }  
  20.     /*  
  21.      * 正常业务操作-查询数据库  
  22.      * 代码省略  
  23.      */  
  24.               
  25.     //查询结束后,将数据添加到redis中  
  26.     try{  
  27.         //把string转换成字符串  
  28.         String cacheString=JsonUtils.objectToJson(list);  
  29.         //这个常量有什么作用?  
  30.         jedisClient.hset(INDEX_CONTENT_REDIS_KEY, contentId+"", cacheString);  
  31.     }catch(Exception e1)  
  32.     {  
  33.         e1.printStackTrace();  
  34.     }  
  35.     return list;  
  36. }  

3、Redis缓存数据同步问题

     不管在任何项目中都使用缓存都存在缓存中数据一致性问题。数据库进行数据更新操作时,如果这些数据在缓存中也有一份,业务设计一般都先查询缓存,没有再查询数据库。这就要求缓存的数据需要实时更新或者在数据库触发更新操作是更新对应数据。

     实时更新适用于对数据要求比较高的系统,例如以前做过的一个数据同步系统,传输的数据很重要,为减少数据库压力而添加缓存。这种情况就可以采用timer定时器或者任务调度相关的技术如Quartz,实时根据数据库更新缓存中的数据。

     对于数据要求不高的缓存数据同步,则可采用更新哪些数据,同步到缓存即可。下面以此为例,展示一下设计思路。

1、设计编写一个缓存同步服务,在service层更新数据的业务中添加根据修改对象主键,在Redis中删除该对象key-value。

[html]  view plain  copy
  1. @Autowired  
  2. private JedisClient jedisClient;  
  3. @Value("${INDEX_CONTENT_REDIS_KEY}")//常量,用于标识hkey值  
  4. private String INDEX_CONTENT_REDIS_KEY;  
  5.   
  6. @Override  
  7. public Result syncContent(long contentId) {  
  8.     try {  
  9.         jedisClient.hdel(INDEX_CONTENT_REDIS_KEY, contentId+"");//转成字符串  
  10.     } catch (Exception e) {  
  11.         e.printStackTrace();  
  12.     }  
  13.     return Result.ok();  
  14. }  

2、在业务逻辑中数据更新时需要进行缓存同步的业务中调用该服务,服务调用方式主要是rest,传入URL即可

[html]  view plain  copy
  1. public String insertContent(TbContent content)  
  2. {  
  3.     mapper.insert(content);  
  4.       
  5.     //调用缓存同步服务  
  6.     HttpClientUtil.doget(REST_BASE_URL+REST_CONTENT_SYN_URL);  
  7.       
  8.     return "index";  
  9. }  

猜你喜欢

转载自blog.csdn.net/qq_27676247/article/details/74280060
今日推荐