您好,登錄后才能下訂單哦!
本篇內(nèi)容介紹了“spring整合redis消息監(jiān)聽通知使用的方法是什么”的有關(guān)知識,在實(shí)際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!
在電商系統(tǒng)中,秒殺,搶購,紅包優(yōu)惠卷等操作,一般都會設(shè)置時間限制,比如訂單15分鐘不付款自動關(guān)閉,紅包有效期24小時等等。那對于這種需求最簡單的處理方式就是使用定時任務(wù),定時掃描數(shù)據(jù)庫的方式處理。但是為了更加精確的時間控制,定時任務(wù)的執(zhí)行時間會設(shè)置的很短,所以會造成很大的數(shù)據(jù)庫壓力。
是否有更加穩(wěn)妥的解決方式呢?我們可以利用REDIS的key失效機(jī)制結(jié)合REDIS的消息通知機(jī)制結(jié)合完成類似問題的處理。
在電商系統(tǒng)中,秒殺,搶購,紅包優(yōu)惠卷等操作,一般都會設(shè)置時間限制,比如訂單15分鐘不付款自動關(guān)閉,紅包有效期24小時等等
目前企業(yè)中最常見的解決方案大致分為兩種:
使用定時任務(wù)處理,定時掃描數(shù)據(jù)庫中過期的數(shù)據(jù),然后進(jìn)行修改。但是為了更加精確的時間控制,定時任務(wù)的執(zhí)行時間會設(shè)置的很短,所以會造成很大的數(shù)據(jù)庫壓力。
使用消息通知,當(dāng)數(shù)據(jù)失效時發(fā)送消息,程序接收到失效消息后對響應(yīng)的數(shù)據(jù)進(jìn)行狀態(tài)修改。此種方式不會對數(shù)據(jù)庫造成太大的壓力
我們使用redis解決過期優(yōu)惠券和紅包等問題,并且在java環(huán)境中使用redis的消息通知。目前世面比較流行的java代碼操作redis的AIP有:Jedis和RedisTemplate
Jedis是Redis官方推出的一款面向Java的客戶端,提供了很多接口供Java語言調(diào)用。
SpringData Redis是Spring官方推出,可以算是Spring框架集成Redis操作的一個子框架,封裝了Redis的很多命令,可以很方便的使用Spring操作Redis數(shù)據(jù)庫。由于現(xiàn)代企業(yè)開發(fā)中都使用Spring整合項目,所以在API的選擇上我們使用Spring提供的SpringData Redis
如果要在java代碼中監(jiān)聽redis的主題消息,我們還需要自定義處理消息的監(jiān)聽器,
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); }
拓展這個接口的代碼如下
/** * 消息監(jiān)聽器:需要實(shí)現(xiàn)MessageListener接口 * 實(shí)現(xiàn)onMessage方法 */ public class RedisMessageListener implements MessageListener { /** * 處理redis消息:當(dāng)從redis中獲取消息后,打印主題名稱和基本的消息 */ public void onMessage(Message message, byte[] pattern) { System.out.println("從channel為" + new String(message.getChannel()) + "中獲取了一條新的消息,消息內(nèi)容:" + new String(message.getBody())); } }
這樣我們就定義好了一個消息監(jiān)聽器,當(dāng)訂閱的頻道有一條新的消息發(fā)送過來之后,通過此監(jiān)聽器中的onMessage方法處理
當(dāng)監(jiān)聽器程序?qū)懞弥?,我們還需要在springData redis的配置文件中添加監(jiān)聽器以及訂閱的頻道主題,
我們測試訂閱的頻道為ITCAST,配置如下:
<!-- 配置處理消息的消息監(jiān)聽適配器 --> <bean class="org.springframework.data.redis.listener.adapter.MessageListenerAdapter" id="messageListener"> <!-- 構(gòu)造方法注入:自定義的消息監(jiān)聽 --> <constructor-arg> <bean class="cn.itcast.redis.listener.RedisKeyExpiredMessageDelegate"/> </constructor-arg> </bean> <!-- 消息監(jiān)聽者容器:對所有的消息進(jìn)行統(tǒng)一管理 --> <bean class="org.springframework.data.redis.listener.RedisMessageListenerContainer" id="redisContainer"> <property name="connectionFactory" ref="connectionFactory"/> <property name="messageListeners"> <map> <!-- 配置頻道與監(jiān)聽器 將此頻道中的內(nèi)容交由此監(jiān)聽器處理 key-ref:監(jiān)聽,處理消息 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>
配置好消息監(jiān)聽,已經(jīng)訂閱的主題之后就可以啟動程序進(jìn)行測試了。由于有監(jiān)聽程序在,只需要已java代碼的形式啟動,創(chuàng)建spring容器(當(dāng)spring容器加載之后,會創(chuàng)建監(jiān)聽器一直監(jiān)聽對應(yīng)的消息)。
public static void main(String[] args) { ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext-data-redis.xml"); }
當(dāng)程序啟動之后,會一直保持運(yùn)行狀態(tài)。即訂閱了ITCSAT頻道的消息,這個時候通過redis的客戶端程序(redis-cli)發(fā)布一條消息
命令解釋:
publish topic名稱 消息內(nèi)容 : 向指定頻道發(fā)送一條消息
發(fā)送消息之后,我們在來看java控制臺輸出可驗(yàn)證獲取到了此消息
解決過期優(yōu)惠券的問題處理起來比較簡單:
在redis的內(nèi)部當(dāng)一個key失效時,也會向固定的頻道中發(fā)送一條消息,我們只需要監(jiān)聽到此消息獲取數(shù)據(jù)庫中的id,修改對應(yīng)的優(yōu)惠券狀態(tài)就可以了。這也帶來了一些繁瑣的操作:用戶獲取到優(yōu)惠券之后需要將優(yōu)惠券存入redis服務(wù)器并設(shè)置超時時間。
由于要借助redis的key失效通知,有兩個注意事項各位需要注意:
事件通過 Redis 的訂閱與發(fā)布功能(pub/sub)來進(jìn)行分發(fā),故需要訂閱(__keyevent@0__:expired)頻道 0表示db0 根據(jù)自己的dbindex選擇合適的數(shù)字
修改 redis.conf 文件
修改 notify-keyspace-events Ex
# K 鍵空間通知,以__keyspace@<db>__為前綴 # E 鍵事件通知,以__keysevent@<db>__為前綴 # g del , expipre , rename 等類型無關(guān)的通用命令的通知, ... # $ String命令 # l List命令 # s Set命令 # h Hash命令 # z 有序集合命令 # x 過期事件(每次key過期時生成) # e 驅(qū)逐事件(當(dāng)key在內(nèi)存滿了被清除時生成) # A g$lshzxe的別名,因此”AKE”意味著所有的事件
前置性的內(nèi)容已經(jīng)和大家都介紹完畢,接下來我們就可以使用redis的消息通知結(jié)合springDataRedis完成一個過期優(yōu)惠券的處理,為了更加直觀的展示問題,這里準(zhǔn)備了兩個程序:
第一個程序(coupon-achieve)用來模擬用戶獲取一張優(yōu)惠券并保存到數(shù)據(jù)庫,存入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;//設(shè)置優(yōu)惠券的失效時間(一分鐘后失效) //自定義一張優(yōu)惠券, Coupon coupon = new Coupon(); coupon.setName("測試優(yōu)惠券");//設(shè)置名稱 coupon.setMoney(BigDecimal.ONE);//設(shè)置金額 coupon.setCouponDesc("全品類優(yōu)惠10元");//設(shè)置說明 coupon.setCreateTime(now);//設(shè)置獲取時間 //設(shè)置超時時間:優(yōu)惠券有效期1分鐘后超時 coupon.setExpireTime(DataUtils.addTime(now, timeOut)); //設(shè)置狀態(tài):0-可用 1-已失效 2-已使用 coupon.setState(0); couponMapper.saveCoupon(coupon ); /** * 將優(yōu)惠券信息保存到redis服務(wù)器中: * 為了方便處理,由于我們處理的時候只需要獲取id就可以了, * 所以保存的key設(shè)置為coupon:優(yōu)惠券的主鍵 * value:設(shè)置為主鍵 */ redisTemplate.opsForValue().set("coupon:"+coupon.getId(), coupon.getId()+"", (long)timeOut, TimeUnit.MINUTES); }
第二個程序(coupon-expired)配置消息通知監(jiān)聽redis的key失效,獲取通知之后修改優(yōu)惠券狀態(tài)
數(shù)據(jù)庫表:
CREATE TABLE `t_coupon` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主鍵', `name` varchar(60) DEFAULT NULL COMMENT '優(yōu)惠券名稱', `money` decimal(10,0) DEFAULT NULL COMMENT '金額', `coupon_desc` varchar(128) DEFAULT NULL COMMENT '優(yōu)惠券說明', `create_time` datetime DEFAULT NULL COMMENT '獲取時間', `expire_time` datetime DEFAULT NULL COMMENT '失效時間', `state` int(1) DEFAULT NULL COMMENT '狀態(tài),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> <!-- 配置監(jiān)聽 --> <bean class="org.springframework.data.redis.listener.adapter.MessageListenerAdapter" id="messageListener"> <constructor-arg> <bean class="cn.itcast.redis.listener.RedisKeyExpiredMessageDelegate"/> </constructor-arg> </bean> <!-- 監(jiān)聽容器 --> <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提供的名稱,標(biāo)識過期key消息通知 0表示db0 根據(jù)自己的dbindex選擇合適的數(shù)字 --> <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()); //判斷是否時優(yōu)惠券失效通知 if(key.startsWith("coupon")){ //2.從key中分離出失效優(yōu)惠券id String redisId = key.split(":")[1]; //3.查詢優(yōu)惠卷信息 Coupon coupon = couponMapper.selectCouponById(Long.parseLong(redisId)); //4.修改狀態(tài) coupon.setState(1); //5.更新數(shù)據(jù)庫 couponMapper.updateCoupon(coupon); } } }
測試日志如下:
通過日志我們發(fā)現(xiàn),當(dāng)優(yōu)惠券到失效時,redis立即發(fā)送一條消息告知此優(yōu)惠券失效,我們可以在監(jiān)聽程序中獲取當(dāng)前的id,查詢數(shù)據(jù)庫修改狀態(tài)
“spring整合redis消息監(jiān)聽通知使用的方法是什么”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實(shí)用文章!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。