溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

spring整合redis消息監(jiān)聽通知使用的方法是什么

發(fā)布時間:2021-12-07 14:00:31 來源:億速云 閱讀:145 作者:iii 欄目:開發(fā)技術(shù)

本篇內(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é)合完成類似問題的處理。

1.1 過期問題描述

在電商系統(tǒng)中,秒殺,搶購,紅包優(yōu)惠卷等操作,一般都會設(shè)置時間限制,比如訂單15分鐘不付款自動關(guān)閉,紅包有效期24小時等等

1.2 常用解決方案分析

目前企業(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ù)庫造成太大的壓力

1.3.整合SpringData Redis開發(fā)

我們使用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

spring整合redis監(jiān)聽消息

1. 配置監(jiān)聽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>

2 測試消息

配置好消息監(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ā)布一條消息

spring整合redis消息監(jiān)聽通知使用的方法是什么

命令解釋:

publish topic名稱 消息內(nèi)容 : 向指定頻道發(fā)送一條消息

發(fā)送消息之后,我們在來看java控制臺輸出可驗(yàn)證獲取到了此消息

結(jié)合redis的key失效機(jī)制和消息完成過期優(yōu)惠券處理

解決過期優(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失效通知,有兩個注意事項各位需要注意:

  1. 事件通過 Redis 的訂閱與發(fā)布功能(pub/sub)來進(jìn)行分發(fā),故需要訂閱(__keyevent@0__:expired)頻道 0表示db0 根據(jù)自己的dbindex選擇合適的數(shù)字

  2. 修改 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”意味著所有的事件

1 模擬過期代金卷案例

前置性的內(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

2 配置redis中key失效的消息監(jiān)聽

<?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>

3 接收失效消息完成過期代金卷處理

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);
		}
	}
}

測試日志如下:

spring整合redis消息監(jiān)聽通知使用的方法是什么

通過日志我們發(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í)用文章!

向AI問一下細(xì)節(jié)

免責(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)容。

AI