最近来了个新的需求,需要使用定时器完成,本想以为用个@Scheduled()就轻易搞定的,详细了解后,事情却并没有这么简单......。所以接到需求后,需要找产品明确明确再次明确,才开工,不然的话你本以为做好的工作却是一场空。
业务场景逻辑解析:第一个请求进来,需要把请求参数暂时保存下来,并触发一个定时器。如果第二个请求在定时器未过期期间进来,去拿第一次请求的参数和第二次请求的参数进行对比,选出一个更合理的参数进行操作。如果第二个请求在定时器过期时还未过来,则过期触发取第一次请求的参数来执行操作。如果在定时器过期后,第二个请求才过来,还是需要拿第一次参数和第二次参数进行对比,若第二次参数更合理,则再次执行。
业务与技术相结合:因为第一次请求参数只是暂时保存下来,自然而然就想到放到缓存里。把第一次请求的参数保存在redis的0号库和1号库里头,并且以一定的规则命名key值,使得两次请求的key值是一样的,0号库设置过期时间为30S,1号库则设置过期时间为1天。启用0号库的key过期监听,每次请求或者过期触发,都去比较参数,若执行则加一个执行标识在value里,这样就可以完成以上的业务场景。
以上只是一时兴起,回忆一下做的功能,下面进入主题。
一、Redis启动key过期监听,基于spring
redis可以实现类似于消息中间件的一个发布/订阅的功能,在spring里发布和订阅的方式有多种方式,我只展示其中的一种(key过期)。
①发布key过期消息通道
key的过期监听,其实就是发布了一个消息通道,一旦有key过期了,就会把该key推送出去。当然,在该通道发布之前,我们就必须进行订阅。
这个key过期发布的通道,redis有提供一个专门打开的开关,在redis.conf里进行配置:
(当然,我们也可以自己发布其他消息通道)
默认是不打开(打开对CUP有消耗):
notify-keyspace-events ""
打开后:
notify-keyspace-events "Ex"
然后重启redis服务,就可以生效。
打开这个key过期发布的消息通道后,我们就编写程序该通道进行订阅。
*redis.conf是在redis安装目录下
②订阅key过期消息通道
基于springBoot,一启动就进行订阅key过期消息通道,新建一个类,并注解@Component,并且实现CommandLineRunner接口,这样当spring容器加载完之后,就会马上执行该组件
package com.appscomm.device.common;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.jedis.JedisConnection;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
@Component
public class RedisSubscribeThread implements CommandLineRunner {
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Override
public void run(String... strings) throws Exception {
redisTemplate.execute(new RedisCallback() {
@Override
public Object doInRedis(RedisConnection connection) throws DataAccessException {
connection.pSubscribe(new MessageListener() {
@Override
public void onMessage(Message message, byte[] pattern) {
System.out.println("message:"+message);
//业务处理
}
},"*@0*".getBytes());
return null;
}
});
}
}
稍作解析:
1.@Component,spring的组件注解,会加入到bean容器
2.CommandLineRunner,spring容器启动完后进行加载
3."*@0*".getBytes(),监听redis0号库的所有发布消息(因为只有1一个地方用到,所以只有一个)
自此,key过期订阅已经实现。测试:
redisTemplate.opsForValue().set("keyTest","123",5,TimeUnit.SECONDS);
默认插入是redis的0号库
5秒钟后,key过期的打印结果:
二、操作redis的多个数据库
基于spring,切换redis的数据库进行存储和获取:
通过redisTemplate来进行获取JedisConnectionFactory 连接工厂进行切换。
JedisConnectionFactory factory =(JedisConnectionFactory)redisTemplate.getConnectionFactory();
factory.setDatabase(0);
JedisConnection jedisConnectionZero=factory.getConnection(); //获取redis的0号库连接
factory.setDatabase(1);
JedisConnection jedisConnectionOne=factory.getConnection(); //获取redis的1号库连接
try {
jedisConnectionZero.setEx("testKey0".getBytes(),30,"aaa".getBytes()); //存到redis的0号库
jedisConnectionOne.setEx("testKey1".getBytes(),50,"bbb".getBytes());//存到redis的1号库
String testKey0Value=new String(jedisConnectionZero.get("testKey0".getBytes())); //从redis的0号库取出
String testKey1Value=new String(jedisConnectionOne.get("testKey1".getBytes())); //从redis的1号库取出
System.out.println("0号库的key为testKey0对应的Value值:"+testKey0Value);
System.out.println("1号库的key为testKey1对应的Value值:"+testKey1Value);
}catch (Exception e){
System.out.println("redis存取错误!")
}finally {
if(jedisConnectionZero!=null){
jedisConnectionZero.close();
}
if(jedisConnectionOne!=null){
jedisConnectionOne.close();
}
}
稍作解析:
redisTemplate获取JedisConnectionFactory 工厂对象,再获取jedis连接,每次切换数据库(factory.setDatabase(数据库号)),都需要重新获取jedis的连接
最后记得把jedis的连接关闭!!!
测试结果:
自此,redis切换数据库进行操作完成,如有错漏,请大家指正。