持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第17天,点击查看活动详情
作者平台:
| CSDN:blog.csdn.net/qq_4115394…
| 知乎:www.zhihu.com/people/1024…
| GitHub:github.com/JiangXia-10…
| 微信公众号:1024笔记
本文一共2438字,预计阅读7分钟
前言
最近在一个业务中遇到一个限时秒杀的场景,就是规定时间内抢名额的问题。秒杀场景在很多公司或者系统中都有使用。就是对于某个业务场景的时间限制,比如,填写了一个问卷、下了一个订单,超过一定的时间内没有提交,那么就会失效。这种场景业务也是比较常见的一个业务之一了。
解决方案
这种情况有两种解决方案,一种是使用定时任务,定时扫描数据库中过期的数据,然后进行修改,但是为了数据的精确性和对时间的控制,这样扫描的间隔是比较短的,从而就会导致对数据库的频繁访问,造成了数据库压力。所以这种方案在一定的业务背景下不是很可取,因为一般来说设置过期时间的场景很多都是类似于秒杀的业务,这样的需要之一对于数据库的要求肯定是比较大的。
那么另一种情况就是使用消息通知,如果数据失效就会发送消息,程序接受到了消息就会对数据库进行修改,比如逻辑删除或者直接物理删除过期数据,这种情况对于数据库的访问压力就比较小了。
这里需要使用到的是redis的订阅与发布。Redis订阅发布一种消息通信模式:发送者发送消息,订阅者接收消息。Redis客户端可以订阅任意数量的频道。当有新消息通过PUBLISH命令发送给频道时, 这个消息就会被发送给订阅它的几个客户端。
正文
使用redis解决过期问题,这里主要涉及到的API是Jedis和RedisTemplate。
Jedis是Redis官方推出的一款面向Java的客户端,提供了很多供Java语言调用的接口。
SpringData Redis是Spring官方推出的,可以理解是Spring框架集成了Redis操作的一个子框架,并且封装了很多Redis的命令,所以可以很方便的使用Spring操作Redis数据库。
首先需要引入Spring相关的依赖,具体如下:
<!-- spring data redis相关依赖-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.6.2</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.2</version>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
</dependency>
<!-- spring 相关依赖-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</dependency>
<!-- mybatis相关依赖-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.5</version>
</dependency>
<!-- mybatis整合spring -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.1</version>
</dependency>
<!-- 数据库相关依赖-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
然后新建一个xml文件,配置SpringData Redis,可以参考如下配置
<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>
<!-- jedis连接工厂 -->
<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>
<!-- jedis连接池的配置信息 -->
<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>
写一个测试类,测试项目是否创建成功:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath:applicationContext-data-redis.xml")
public class RedisTest {
private static Logger log = Logger.getLogger(RedisTest.class);
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Test
public void testSend() {
redisTemplate.opsForValue().set("itcast","very good!");
String value = redisTemplate.opsForValue().get("itcast");
log.info("从redis中获取的数据:"+value);
}
}
结果如下:
redis数据库查询结果:
说明项目创建成功,能够从redis中获取数据。
因为是使用redis进行消息的通知,所以接下来需要做的就是监听redis的消息。在代码中需要自定义处理redis消息的监听器,从而监听redis的主题消息,具体代码如下:
public class RedisMessageListener implements MessageListener {
public void onMessage(Message message, byte[] pattern) {
System.out.println("从频道为" + new String(message.getChannel())
+ "中获取了一条新的消息,消息内容:" + new String(message.getBody()));
}
}
上述代码主要是实现了MessageListener接口,并且实现了onMessage方法,当从redis中获取消息后,打印主题名称和基本的消息。这样就自定义好了一个redis消息的监听器,当订阅的频道有一条新的消息发送过来之后,通过此监听器中的onMessage方法处理。接下来还需要在springData redis的配置文件中添加监听器以及订阅的频道主题,我们测试订阅的频道为JIANGXIA,配置如下:
<!-- 配置处理消息的消息监听适配器 -->
<bean class="org.springframework.data.redis.listener.adapter.MessageListenerAdapter" id="messageListener">
<!-- 构造方法注入:自定义的消息监听 -->
<constructor-arg>
<bean class="cn.jiangxia.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="JIANGXIA"></constructor-arg>
</bean>
</list>
</entry>
</map>
</property>
</bean>
接下来需要做的就是结合redis key的失效机制和消息通信机制完成过期数据的处理。
核心逻辑是当用户获取到订单是会将该订单数据存入到redis中,并且设置有效期,当该key超过有效期时,就会想定制的频道发送一条消息,如果监听到此消息就获取数据库中的di,然后修改该对应数据即可(修改状态或者直接物理删除)。
核心方法如下:
模拟获取订单并存入数据库和redis缓存中
public void testSave() {
Date now = new Date();
int timeOut = 1;//设置的失效时间一分钟
Order order = new Order();
order.setName("测试");//设置名称
order.setMoney(BigDecimal.ONE);//设置金额
order.setCouponDesc("苹果手机1000块");//设置说明
order.setCreateTime(now);//设置获取时间
//设置超时时间:有效期1分钟后超时
order.setExpireTime(DataUtils.addTime(now, timeOut));
//设置状态:0-可用 1-已失效 2-已使用
order.setState(0);
orderMapper.save(order);
redisTemplate.opsForValue().set("order:"+order.getId(), order.getId()+"", (long)timeOut, TimeUnit.MINUTES);
}
在OnMessage中接收redis中key失效消息,然后完成过期数据的处理:
public void onMessage(Message message, byte[] pattern) {
//1.获取失效的key
String key = new String(message.getBody());
//判断是否是失效通知
if(key.startsWith("order")){
//2.从key中分离出失效订单的id
String redisId = key.split(":")[1];
//3.查询订单信息
Order order = orderMapper.selectOrderById(Long.parseLong(redisId));
//4.修改订单状态
order.setState(1);
//5.更新数据库
orderMapper.updateOrder(order);
}
}
总结
以上就是如何使用SpringData Redis的key失效机制和消息通信机制,完成对于数据库中过期数据的处理问题,主要适用于秒杀或者限时业务场景中数据的处理。