在电商系统中,因为存有预支付的情况
所以库存的扣减
客户下单-->延迟支付-->支付成功/支付不成功
所以就存在预扣除你库存的情况,如果取消支付的话,就需要归还库存。关于归还库存,需明白
1、怎么归还库存
2、什么时间节点归还库存
3、谁来触发归还库存的操作
方法一:主动轮询
使用定时器的方式,每1s轮询,查看到过期的订单就归还库存
1、JAVA层面:Timer
2、Linux层面:Corntab
3、定时任务框架:Quartz
常用的队列:
RocketMQ、RabbitMQ、kafka
方法二、延迟消息队列:
Redis、RocketMQ
下面开始讲解Redis
一、windows安装Redis
参考:https://blog.csdn.net/qq_52253798/article/details/122204459
安装和配置Redis
1、在GitHub中下载安装包redis
https://github.com/MicrosoftArchive/redis/releases
2、直接下载最新版本,选择.msi格式的安装版本(另外一种.zip通过命令安装)
3、设置Redis的密码
打开安装目录,windows的话找到“ redis.windows-service.conf ”,linux的话“redis.conf”
找到 requirepass。如图设置密码
进入计算机服务中(右键计算机–>管理–>服务和应用程序–>服务),再在右侧找到Redis名称的服务,查看启动情况。如未启动,则手动启动之。正常情况下,服务应该正常启动并运行了,但是因为前面修改过配置文件,需要重启服务,切记。
使用服务前需要先通过密码验证。输入“auth 123456”并回车(123456是之前设定的密码)。返回提示OK表示验证通过
Redis常用操作
- 设置指定 key 的值 set key_name fox
- 获取指定 key 的值 get key_name
- 查看所有key(key 正则匹配) keys *
- 删除指定key的值 del key_name
Redis键空间通知
Redis键空间通知是指Redis收到事件的影响(del、expired等)发布一个通知。我们可以通过发布订阅的方式来实现延迟消息队列。
例如 使用set key :
1、set name fox
2、del name
此时的键空间通知
key-space--->del
key-event--->name
1、修改配置文件
windows的话找到“ redis.windows-service.conf ”,linux的话“redis.conf” 加入“notify-keyspace-events Ex”
2、然后我们重启redis服务
进入控制台,获得 CONFIG GET notify-keyspace-events
3、控制台进入Redis数据库设置监听(订阅监听过期事物)
shell> SUBSCRIBE __keyevent@0__:expired
打开新的控制台订阅键过期事件
SETEX KEY_NAME TIMEOUT VALUE
redis1> SUBSCRIBE __keyevent@0__:expired
1) "subscribe"
2) "__keyevent@0__:expired"
3) (integer) 1
# redis2> SETEX greeting 1 "hello world"
# 等待1秒后:
1) "message"
2) "__keyevent@0__:expired"
3) "greeting"
打开新的控制台订阅所有事件
redis1> PSUBSCRIBE __key*@*__:*
1) "psubscribe"
2) "__key*@*__:*"
3) (integer) 1
# redis2> SET greeting "hello world"
1) "pmessage"
2) "__key*@*__:*"
3) "__keyspace@0__:greeting"
4) "set"
1) "pmessage"
2) "__key*@*__:*"
3) "__keyevent@0__:set"
4) "greeting"
SpringBoot引用Redis
在pom.xml中添加引用
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
在配置文件中设置参数。Redis一共有15个数据库,根据实际情况链接。此处使用到0号数据库
spring:
redis:
localhost: localhost
port: 6379
database: 0
password: 123456
listen-pattern: __keyevent@0__:expired
创建公共处理类 RedisUtils
在项目中创建package,并命名manager,在下级创建一个package,并命名为redis,并创建 RedisUtils 工具类
package com.fcors.fcors.manager.redis;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
@Component
public class RedisUtils {
@Autowired
private RedisTemplate<String, String> redisTemplate;
/**
* 读取缓存
*
* @param key
* @return
*/
public String get(final String key) {
return redisTemplate.opsForValue().get(key);
}
/**
* 写入缓存
*/
public boolean set(final String key, String value) {
boolean result = false;
try {
redisTemplate.opsForValue().set(key, value);
result = true;
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
/**
* 写入缓存,并设置过期时间
*
* @param key
* @param value
* @param timeout
* @param unit
* @return
*/
public boolean set(final String key, String value, long timeout, TimeUnit unit) {
boolean result = false;
try {
redisTemplate.opsForValue().set(key, value, timeout, unit);
result = true;
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
/**
* 更新缓存
*/
public boolean getAndSet(final String key, String value) {
boolean result = false;
try {
redisTemplate.opsForValue().getAndSet(key, value);
result = true;
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
/**
* 删除缓存
*/
public boolean delete(final String key) {
boolean result = false;
try {
redisTemplate.delete(key);
result = true;
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
}
SpringBoot设置键空间通知,实现延时消息队列。
1、设置监听规则
spring:
redis:
localhost: localhost
port: 6379
database: 0
password: 123456
listen-pattern: __keyevent@0__:expired
2、创建类实现消息监听TopicMessageListener
package com.fcors.fcors.manager.redis;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.stereotype.Component;
@Component
public class TopicMessageListener implements MessageListener {
@Override
public void onMessage(Message message, byte[] bytes) {
byte[] body = message.getBody();
byte[] channel = message.getChannel();
String expiredKey = new String(body);
String topic = new String(channel);
System.out.println("Redis延迟消息队列接收内容");
System.out.println("expiredKey"+expiredKey);
System.out.println("topic"+topic);
}
}
3、设置MessageListenerConfiguration
package com.fcors.fcors.manager.redis;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.listener.PatternTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.listener.Topic;
@Configuration
public class MessageListenerConfiguration {
@Value("${spring.redis.listen-pattern}")
public String pattern;
@Bean
public RedisMessageListenerContainer listenerContainer(RedisConnectionFactory redisConnection) {
// return new TopicMessageListener()
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(redisConnection);
/*设置监听主题 */
Topic topic = new PatternTopic(this.pattern);
/*添加监听处理 和 监听主题*/
container.addMessageListener(new TopicMessageListener(), topic);
return container;
}
}
4、在TestService处插入超时
@Autowired
private RedisUtils redisUtils;
public long invoiceCount = 0;
public String TestRedis(){
String a = "test";
/*写入缓存*/
redisUtils.set("fox_key","fox_value");
/*读取缓存*/
String Redis_value = redisUtils.get("fox_key");
System.out.println("获得设置的fox_value"+Redis_value);
/*更新缓存*/
Boolean IsSuccess = redisUtils.getAndSet("fox_key","fox_value1");
System.out.println("是否成功更新"+IsSuccess);
Boolean IsSuccess2 = redisUtils.getAndSet("fox_key2","fox_value2");
System.out.println("是否成功更新"+IsSuccess2);
/*设置超时*/
Boolean IsSuccess3 = redisUtils.set("TimeOut","Timeout7Second",7, TimeUnit.SECONDS);
System.out.println("是否成功插入超时缓存"+IsSuccess2);
return a;
}
最终执行效果
结合优惠券实战案例:
考虑到优惠券和库存扣减归还,我们在Redis的key规则orderId,couponId,userId
1、创建OrderMessageBO
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class OrderMessageBO {
private Long orderId;
private Long couponId;
private Long userId;
private String message;
public OrderMessageBO(String message) {
this.message = message;
this.parseId(message);
}
private void parseId(String message) {
String[] temp = message.split(",");
this.userId = Long.valueOf(temp[0]);
this.orderId = Long.valueOf(temp[1]);
this.couponId = Long.valueOf(temp[2]);
}
}
2、创建类实现消息监听TopicMessageListener
package com.fcors.fcors.manager.redis;
import com.fcors.fcors.bo.OrderMessageBO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationListener;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.stereotype.Component;
@Component
public class TopicMessageListenerNew implements MessageListener {
@Autowired
private static ApplicationEventPublisher publisher;
@Autowired
public static void setPublisher(ApplicationEventPublisher publisher) {
TopicMessageListenerNew.publisher = publisher;
}
@Override
public void onMessage(Message message, byte[] bytes) {
byte[] body = message.getBody();
byte[] channel = message.getChannel();
String expiredKey = new String(body);
String topic = new String(channel);
OrderMessageBO messageBO = new OrderMessageBO(expiredKey);
TopicMessageListenerNew.publisher.publishEvent(messageBO);
}
}
3、接收监听返回信息在service
@EventListener
@Transactional
public void RedisMessageBack(OrderMessageBO bo){
Long couponId = bo.getCouponId();
Long uid = bo.getUserId();
}
下面附上一个SpringBoot的消息发布订阅
https://juejin.cn/post/7078481193133408270
redis普通消息队列
https://blog.csdn.net/weixin_41359273/article/details/120681977