Spring-data-redis: 分佈式隊列

Redis中list數據結構,具有“雙端隊列”的特性,同時redis具有持久數據的能力,因此redis實現分佈式隊列是非常安全可靠的。它類似於JMS中的“Queue”,只不過功能和可靠性(事務性)並沒有JMS嚴格。

Redis中的隊列阻塞時,整個connection都無法繼續進行其他操作,因此在基於連接池設計是需要注意。

我們通過spring-data-redis,來實現“同步隊列”,設計風格類似與JMS。

一.配置文件:

  1. <beans xmlns="http://www.springframework.org/schema/beans"   
  2. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  3. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd" default-autowire="byName">  
  4.     <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">  
  5.         <property name="maxActive" value="32"></property>  
  6.         <property name="maxIdle" value="6"></property>  
  7.         <property name="maxWait" value="15000"></property>  
  8.         <property name="minEvictableIdleTimeMillis" value="300000"></property>  
  9.         <property name="numTestsPerEvictionRun" value="3"></property>  
  10.         <property name="timeBetweenEvictionRunsMillis" value="60000"></property>  
  11.         <property name="whenExhaustedAction" value="1"></property>  
  12.     </bean>  
  13.     <bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" destroy-method="destroy">  
  14.         <property name="poolConfig" ref="jedisPoolConfig"></property>  
  15.         <property name="hostName" value="127.0.0.1"></property>  
  16.         <property name="port" value="6379"></property>  
  17.         <property name="password" value="0123456"></property>  
  18.         <property name="timeout" value="15000"></property>  
  19.         <property name="usePool" value="true"></property>  
  20.     </bean>  
  21.     <bean id="jedisTemplate" class="org.springframework.data.redis.core.RedisTemplate">  
  22.         <property name="connectionFactory" ref="jedisConnectionFactory"></property>  
  23.         <property name="defaultSerializer">  
  24.             <bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/>  
  25.         </property>  
  26.     </bean>  
  27.     <bean id="jedisQueueListener" class="com.sample.redis.sdr.QueueListener"/>  
  28.     <bean id="jedisQueue" class="com.sample.redis.sdr.RedisQueue" destroy-method="destroy">  
  29.         <property name="redisTemplate" ref="jedisTemplate"></property>  
  30.         <property name="key" value="user:queue"></property>  
  31.         <property name="listener" ref="jedisQueueListener"></property>  
  32.     </bean>  
  33. </beans>  

二.程序實例:

1) QueueListener:當隊列中有數據時,可以執行類似於JMS的回調操作。

  1. public interface RedisQueueListener<T> {  
  2.   
  3.     public void onMessage(T value);  
  4. }  
  1. public class QueueListener<String> implements RedisQueueListener<String> {  
  2.   
  3.     @Override  
  4.     public void onMessage(String value) {  
  5.         System.out.println(value);  
  6.           
  7.     }  
  8.   
  9. }  

2) RedisQueue:隊列操作,內部封裝redisTemplate實例;如果配置了“listener”,那麼queue將採用“消息回調”的方式執行,listenerThread是一個後臺線程,用來自動處理“隊列信息”。如果不配置“listener”,那麼你可以將redisQueue注入到其他spring bean中,手動去“take”數據即可。

  1. public class RedisQueue<T> implements InitializingBean,DisposableBean{  
  2.     private RedisTemplate redisTemplate;  
  3.     private String key;  
  4.     private int cap = Short.MAX_VALUE;//最大阻塞的容量,超過容量將會導致清空舊數據  
  5.     private byte[] rawKey;  
  6.     private RedisConnectionFactory factory;  
  7.     private RedisConnection connection;//for blocking  
  8.     private BoundListOperations<String, T> listOperations;//noblocking  
  9.       
  10.     private Lock lock = new ReentrantLock();//基於底層IO阻塞考慮  
  11.       
  12.     private RedisQueueListener listener;//異步回調  
  13.     private Thread listenerThread;  
  14.       
  15.     private boolean isClosed;  
  16.       
  17.     public void setRedisTemplate(RedisTemplate redisTemplate) {  
  18.         this.redisTemplate = redisTemplate;  
  19.     }  
  20.   
  21.     public void setListener(RedisQueueListener listener) {  
  22.         this.listener = listener;  
  23.     }  
  24.   
  25.     public void setKey(String key) {  
  26.         this.key = key;  
  27.     }  
  28.       
  29.   
  30.     @Override  
  31.     public void afterPropertiesSet() throws Exception {  
  32.         factory = redisTemplate.getConnectionFactory();  
  33.         connection = RedisConnectionUtils.getConnection(factory);  
  34.         rawKey = redisTemplate.getKeySerializer().serialize(key);  
  35.         listOperations = redisTemplate.boundListOps(key);  
  36.         if(listener != null){  
  37.             listenerThread = new ListenerThread();  
  38.             listenerThread.setDaemon(true);  
  39.             listenerThread.start();  
  40.         }  
  41.     }  
  42.       
  43.       
  44.     /** 
  45.      * blocking 
  46.      * remove and get last item from queue:BRPOP 
  47.      * @return 
  48.      */  
  49.     public T takeFromTail(int timeout) throws InterruptedException{   
  50.         lock.lockInterruptibly();  
  51.         try{  
  52.             List<byte[]> results = connection.bRPop(timeout, rawKey);  
  53.             if(CollectionUtils.isEmpty(results)){  
  54.                 return null;  
  55.             }  
  56.             return (T)redisTemplate.getValueSerializer().deserialize(results.get(1));  
  57.         }finally{  
  58.             lock.unlock();  
  59.         }  
  60.     }  
  61.       
  62.     public T takeFromTail() throws InterruptedException{  
  63.         return takeFromHead(0);  
  64.     }  
  65.       
  66.     /** 
  67.      * 從隊列的頭,插入 
  68.      */  
  69.     public void pushFromHead(T value){  
  70.         listOperations.leftPush(value);  
  71.     }  
  72.       
  73.     public void pushFromTail(T value){  
  74.         listOperations.rightPush(value);  
  75.     }  
  76.       
  77.     /** 
  78.      * noblocking 
  79.      * @return null if no item in queue 
  80.      */  
  81.     public T removeFromHead(){  
  82.         return listOperations.leftPop();  
  83.     }  
  84.       
  85.     public T removeFromTail(){  
  86.         return listOperations.rightPop();  
  87.     }  
  88.       
  89.     /** 
  90.      * blocking 
  91.      * remove and get first item from queue:BLPOP 
  92.      * @return 
  93.      */  
  94.     public T takeFromHead(int timeout) throws InterruptedException{  
  95.         lock.lockInterruptibly();  
  96.         try{  
  97.             List<byte[]> results = connection.bLPop(timeout, rawKey);  
  98.             if(CollectionUtils.isEmpty(results)){  
  99.                 return null;  
  100.             }  
  101.             return (T)redisTemplate.getValueSerializer().deserialize(results.get(1));  
  102.         }finally{  
  103.             lock.unlock();  
  104.         }  
  105.     }  
  106.       
  107.     public T takeFromHead() throws InterruptedException{  
  108.         return takeFromHead(0);  
  109.     }  
  110.   
  111.     @Override  
  112.     public void destroy() throws Exception {  
  113.         if(isClosed){  
  114.             return;  
  115.         }  
  116.         shutdown();  
  117.         RedisConnectionUtils.releaseConnection(connection, factory);  
  118.     }  
  119.       
  120.     private void shutdown(){  
  121.         try{  
  122.             listenerThread.interrupt();  
  123.         }catch(Exception e){  
  124.             //  
  125.         }  
  126.     }  
  127.       
  128.     class ListenerThread extends Thread {  
  129.           
  130.         @Override  
  131.         public void run(){  
  132.             try{  
  133.                 while(true){  
  134.                     T value = takeFromHead();//cast exceptionyou should check.  
  135.                     //逐個執行  
  136.                     if(value != null){  
  137.                         try{  
  138.                             listener.onMessage(value);  
  139.                         }catch(Exception e){  
  140.                             //  
  141.                         }  
  142.                     }  
  143.                 }  
  144.             }catch(InterruptedException e){  
  145.                 //  
  146.             }  
  147.         }  
  148.     }  
  149.       
  150. }  

3) 使用與測試:

  1. public static void main(String[] args) throws Exception{  
  2.     ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring-redis-beans.xml");  
  3.     RedisQueue<String> redisQueue = (RedisQueue)context.getBean("jedisQueue");  
  4.     redisQueue.pushFromHead("test:app");  
  5.     Thread.sleep(15000);  
  6.     redisQueue.pushFromHead("test:app");  
  7.     Thread.sleep(15000);  
  8.     redisQueue.destroy();  
  9. }  

在程序運行期間,你可以通過redis-cli(客戶端窗口)執行“lpush”,你會發現程序的控制檯仍然能夠正常打印隊列信息。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章