您好,登錄后才能下訂單哦!
本篇內容介紹了“spring整合redis消息監聽通知使用的方法是什么”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!
在電商系統中,秒殺,搶購,紅包優惠卷等操作,一般都會設置時間限制,比如訂單15分鐘不付款自動關閉,紅包有效期24小時等等。那對于這種需求最簡單的處理方式就是使用定時任務,定時掃描數據庫的方式處理。但是為了更加精確的時間控制,定時任務的執行時間會設置的很短,所以會造成很大的數據庫壓力。
是否有更加穩妥的解決方式呢?我們可以利用REDIS的key失效機制結合REDIS的消息通知機制結合完成類似問題的處理。
在電商系統中,秒殺,搶購,紅包優惠卷等操作,一般都會設置時間限制,比如訂單15分鐘不付款自動關閉,紅包有效期24小時等等
目前企業中最常見的解決方案大致分為兩種:
使用定時任務處理,定時掃描數據庫中過期的數據,然后進行修改。但是為了更加精確的時間控制,定時任務的執行時間會設置的很短,所以會造成很大的數據庫壓力。
使用消息通知,當數據失效時發送消息,程序接收到失效消息后對響應的數據進行狀態修改。此種方式不會對數據庫造成太大的壓力
我們使用redis解決過期優惠券和紅包等問題,并且在java環境中使用redis的消息通知。目前世面比較流行的java代碼操作redis的AIP有:Jedis和RedisTemplate
Jedis是Redis官方推出的一款面向Java的客戶端,提供了很多接口供Java語言調用。
SpringData Redis是Spring官方推出,可以算是Spring框架集成Redis操作的一個子框架,封裝了Redis的很多命令,可以很方便的使用Spring操作Redis數據庫。由于現代企業開發中都使用Spring整合項目,所以在API的選擇上我們使用Spring提供的SpringData Redis
如果要在java代碼中監聽redis的主題消息,我們還需要自定義處理消息的監聽器,
MessageListener類的源碼:
package org.springframework.data.redis.connection; import org.springframework.lang.Nullable; /** * Listener of messages published in Redis. * */ public interface MessageListener { /** * Callback for processing received objects through Redis. * * @param message message must not be {@literal null}. * @param pattern pattern matching the channel (if specified) - can be {@literal null}. */ void onMessage(Message message, @Nullable byte[] pattern); }
拓展這個接口的代碼如下
/** * 消息監聽器:需要實現MessageListener接口 * 實現onMessage方法 */ public class RedisMessageListener implements MessageListener { /** * 處理redis消息:當從redis中獲取消息后,打印主題名稱和基本的消息 */ public void onMessage(Message message, byte[] pattern) { System.out.println("從channel為" + new String(message.getChannel()) + "中獲取了一條新的消息,消息內容:" + new String(message.getBody())); } }
這樣我們就定義好了一個消息監聽器,當訂閱的頻道有一條新的消息發送過來之后,通過此監聽器中的onMessage方法處理
當監聽器程序寫好之后,我們還需要在springData redis的配置文件中添加監聽器以及訂閱的頻道主題,
我們測試訂閱的頻道為ITCAST,配置如下:
<!-- 配置處理消息的消息監聽適配器 --> <bean class="org.springframework.data.redis.listener.adapter.MessageListenerAdapter" id="messageListener"> <!-- 構造方法注入:自定義的消息監聽 --> <constructor-arg> <bean class="cn.itcast.redis.listener.RedisKeyExpiredMessageDelegate"/> </constructor-arg> </bean> <!-- 消息監聽者容器:對所有的消息進行統一管理 --> <bean class="org.springframework.data.redis.listener.RedisMessageListenerContainer" id="redisContainer"> <property name="connectionFactory" ref="connectionFactory"/> <property name="messageListeners"> <map> <!-- 配置頻道與監聽器 將此頻道中的內容交由此監聽器處理 key-ref:監聽,處理消息 ChannelTopic:訂閱的消息頻道 --> <entry key-ref="messageListener"> <list> <bean class="org.springframework.data.redis.listener.ChannelTopic"> <constructor-arg value="ITCAST"></constructor-arg> </bean> </list> </entry> </map> </property> </bean>
配置好消息監聽,已經訂閱的主題之后就可以啟動程序進行測試了。由于有監聽程序在,只需要已java代碼的形式啟動,創建spring容器(當spring容器加載之后,會創建監聽器一直監聽對應的消息)。
public static void main(String[] args) { ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext-data-redis.xml"); }
當程序啟動之后,會一直保持運行狀態。即訂閱了ITCSAT頻道的消息,這個時候通過redis的客戶端程序(redis-cli)發布一條消息
命令解釋:
publish topic名稱 消息內容 : 向指定頻道發送一條消息
發送消息之后,我們在來看java控制臺輸出可驗證獲取到了此消息
解決過期優惠券的問題處理起來比較簡單:
在redis的內部當一個key失效時,也會向固定的頻道中發送一條消息,我們只需要監聽到此消息獲取數據庫中的id,修改對應的優惠券狀態就可以了。這也帶來了一些繁瑣的操作:用戶獲取到優惠券之后需要將優惠券存入redis服務器并設置超時時間。
由于要借助redis的key失效通知,有兩個注意事項各位需要注意:
事件通過 Redis 的訂閱與發布功能(pub/sub)來進行分發,故需要訂閱(__keyevent@0__:expired)頻道 0表示db0 根據自己的dbindex選擇合適的數字
修改 redis.conf 文件
修改 notify-keyspace-events Ex
# K 鍵空間通知,以__keyspace@<db>__為前綴 # E 鍵事件通知,以__keysevent@<db>__為前綴 # g del , expipre , rename 等類型無關的通用命令的通知, ... # $ String命令 # l List命令 # s Set命令 # h Hash命令 # z 有序集合命令 # x 過期事件(每次key過期時生成) # e 驅逐事件(當key在內存滿了被清除時生成) # A g$lshzxe的別名,因此”AKE”意味著所有的事件
前置性的內容已經和大家都介紹完畢,接下來我們就可以使用redis的消息通知結合springDataRedis完成一個過期優惠券的處理,為了更加直觀的展示問題,這里準備了兩個程序:
第一個程序(coupon-achieve)用來模擬用戶獲取一張優惠券并保存到數據庫,存入redis緩存。
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations="classpath:applicationContext.xml") public class CouponTest { @Autowired private CouponMapper couponMapper; @Autowired private RedisTemplate<String, String> redisTemplate; @Test public void testSaveCoupon() { Date now = new Date(); int timeOut = 1;//設置優惠券的失效時間(一分鐘后失效) //自定義一張優惠券, Coupon coupon = new Coupon(); coupon.setName("測試優惠券");//設置名稱 coupon.setMoney(BigDecimal.ONE);//設置金額 coupon.setCouponDesc("全品類優惠10元");//設置說明 coupon.setCreateTime(now);//設置獲取時間 //設置超時時間:優惠券有效期1分鐘后超時 coupon.setExpireTime(DataUtils.addTime(now, timeOut)); //設置狀態:0-可用 1-已失效 2-已使用 coupon.setState(0); couponMapper.saveCoupon(coupon ); /** * 將優惠券信息保存到redis服務器中: * 為了方便處理,由于我們處理的時候只需要獲取id就可以了, * 所以保存的key設置為coupon:優惠券的主鍵 * value:設置為主鍵 */ redisTemplate.opsForValue().set("coupon:"+coupon.getId(), coupon.getId()+"", (long)timeOut, TimeUnit.MINUTES); }
第二個程序(coupon-expired)配置消息通知監聽redis的key失效,獲取通知之后修改優惠券狀態
數據庫表:
CREATE TABLE `t_coupon` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主鍵', `name` varchar(60) DEFAULT NULL COMMENT '優惠券名稱', `money` decimal(10,0) DEFAULT NULL COMMENT '金額', `coupon_desc` varchar(128) DEFAULT NULL COMMENT '優惠券說明', `create_time` datetime DEFAULT NULL COMMENT '獲取時間', `expire_time` datetime DEFAULT NULL COMMENT '失效時間', `state` int(1) DEFAULT NULL COMMENT '狀態,0-有效,1-已失效,2-已使用', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:task="http://www.springframework.org/schema/task" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd "> <description>spring-data整合jedis</description> <!-- springData Redis的核心API --> <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate"> <property name="connectionFactory" ref="connectionFactory"></property> <property name="keySerializer"> <bean class="org.springframework.data.redis.serializer.StringRedisSerializer"></bean> </property> <property name="valueSerializer"> <bean class="org.springframework.data.redis.serializer.StringRedisSerializer"></bean> </property> </bean> <!-- 連接工廠 --> <bean id="connectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"> <property name="hostName" value="127.0.0.1"></property> <property name="port" value="6379"></property> <property name="database" value="0"></property> <property name="poolConfig" ref="poolConfig"></property> </bean> <!-- 連接池基本配置 --> <bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig"> <property name="maxIdle" value="5"></property> <property name="maxTotal" value="10"></property> <property name="testOnBorrow" value="true"></property> </bean> <!-- 配置監聽 --> <bean class="org.springframework.data.redis.listener.adapter.MessageListenerAdapter" id="messageListener"> <constructor-arg> <bean class="cn.itcast.redis.listener.RedisKeyExpiredMessageDelegate"/> </constructor-arg> </bean> <!-- 監聽容器 --> <bean class="org.springframework.data.redis.listener.RedisMessageListenerContainer" id="redisContainer"> <property name="connectionFactory" ref="connectionFactory"/> <property name="messageListeners"> <map> <entry key-ref="messageListener"> <list> <!-- __keyevent@0__:expired 配置訂閱的主題名稱 此名稱時redis提供的名稱,標識過期key消息通知 0表示db0 根據自己的dbindex選擇合適的數字 --> <bean class="org.springframework.data.redis.listener.ChannelTopic"> <constructor-arg value="__keyevent@0__:expired"></constructor-arg> </bean> </list> </entry> </map> </property> </bean> </beans>
package cn.itcast.redis.listener; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.connection.Message; import org.springframework.data.redis.connection.MessageListener; import cn.itcast.entity.Coupon; import cn.itcast.mapper.CouponMapper; public class RedisKeyExpiredMessageDelegate implements MessageListener { @Autowired private CouponMapper couponMapper; public void onMessage(Message message, byte[] pattern) { //1.獲取失效的key String key = new String(message.getBody()); //判斷是否時優惠券失效通知 if(key.startsWith("coupon")){ //2.從key中分離出失效優惠券id String redisId = key.split(":")[1]; //3.查詢優惠卷信息 Coupon coupon = couponMapper.selectCouponById(Long.parseLong(redisId)); //4.修改狀態 coupon.setState(1); //5.更新數據庫 couponMapper.updateCoupon(coupon); } } }
測試日志如下:
通過日志我們發現,當優惠券到失效時,redis立即發送一條消息告知此優惠券失效,我們可以在監聽程序中獲取當前的id,查詢數據庫修改狀態
“spring整合redis消息監聽通知使用的方法是什么”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。