大家好,我是本群的提醒喝水小助手,这是今天的第X轮。希望此刻看到消息的人可以和我一起来一杯水。一小时后的我继续提醒大家喝水。和我一起成为一天八杯水的人吧!
最初是为了提醒大家注意身体健康,也有提醒运动、提醒站立等等,后来大家脑洞大开的创作出了各种滑稽搞笑的表情包,例如提醒喝可乐,提醒上厕所等等,该表情包也开始在不同的圈子里流行起来,例如在游戏里有提醒开黑,在B站有提醒更新视频等等。
定时任务
利用SpringTask、Quartz
实现定时提醒发送,不过小程序的消息模板推送已经不支持随意给用户发消息了,需要用户自行去触发。
延迟队列
用户手动触发一次喝水动作,这时候我们可以使用延迟队列来实现,推荐使用开源的第三方工具包redisson
,同时保证redis
支持lua
特性。
配置application.properties
:
# redisson lock
redisson.address=redis://127.0.0.1:6379
redisson.password=123456
参数配置类RedissonProperties
:
@Data
@ConfigurationProperties(prefix = "redisson")
public class RedissonProperties {
private int timeout = 3000;
private String address;
private String password;
private int connectionPoolSize = 64;
private int connectionMinimumIdleSize=10;
private int slaveConnectionPoolSize = 250;
private int masterConnectionPoolSize = 250;
private String[] sentinelAddresses;
private String masterName;
}
自动装配 RedissonAutoConfiguration
:
@Configuration
@ConditionalOnClass(Config.class)
@EnableConfigurationProperties(RedissonProperties.class)
public class RedissonAutoConfiguration {
@Autowired
private RedissonProperties redssionProperties;
/**
* 单机模式自动装配
* @return
*/
@Bean
RedissonClient redissonSingle() {
Config config = new Config();
SingleServerConfig serverConfig = config.useSingleServer()
.setAddress(redssionProperties.getAddress)
.setTimeout(redssionProperties.getTimeout())
.setConnectionPoolSize(redssionProperties.getConnectionPoolSize())
.setConnectionMinimumIdleSize(redssionProperties.getConnectionMinimumIdleSize());
if(StringUtils.isNotBlank(redssionProperties.getPassword())) {
serverConfig.setPassword(redssionProperties.getPassword());
}
return Redisson.create(config);
}
/**
* 装配locker类,并将实例注入到RedissLockUtil中
* @return
*/
@Bean
RedissLockUtil redissLockUtil(RedissonClient redissonClient) {
RedissLockUtil redissLockUtil = new RedissLockUtil();
redissLockUtil.setRedissonClient(redissonClient);
return redissLockUtil;
}
}
工具类RedissLockUtil
:
/**
* redis分布式锁帮助类
* @author 爪哇笔记 By https://blog.52itstyle.vip
*/
public class RedissLockUtil {
private static RedissonClient redissonClient;
public void setRedissonClient(RedissonClient locker) {
redissonClient = locker;
}
/**
* 初始红包数量
* @param key
* @param count
*/
public void initCount(String key,int count) {
RMapCache<String, Integer> mapCache = redissonClient.getMapCache("skill");
mapCache.putIfAbsent(key,count,3,TimeUnit.DAYS);
}
/**
* 递增
* @param key
* @param delta 要增加几(大于0)
* @return
*/
public int incr(String key, int delta) {
RMapCache<String, Integer> mapCache = redissonClient.getMapCache("skill");
if (delta < 0) {
throw new RuntimeException("递增因子必须大于0");
}
return mapCache.addAndGet(key, 1);//加1并获取计算后的值
}
/**
* 递减
* @param key 键
* @param delta 要减少几(小于0)
* @return
*/
public int decr(String key, int delta) {
RMapCache<String, Integer> mapCache = redissonClient.getMapCache("skill");
if (delta < 0) {
throw new RuntimeException("递减因子必须大于0");
}
return mapCache.addAndGet(key, -delta);//加1并获取计算后的值
}
public static RedissonClient getRedissonClient() {
return redissonClient;
}
}
入队列,伪代码:
RedissonClient redissonClient = RedissLockUtil.getRedissonClient();
/**
* 目标队列
*/
RBlockingQueue<RemindDelay> blockingRemindQueue
= redissonClient.getBlockingQueue("remindDelayQueue");
/**
* 定时任务将到期的元素转移到目标队列
*/
RDelayedQueue<RemindDelay> delayedRemindQueue
= redissonClient.getDelayedQueue(blockingRemindQueue);
/**
* 延时信息入队列
*/
delayedRemindQueue.offer(new RemindDelay(remind.getId()), sec, TimeUnit.SECONDS);
队列消息实体标识:
public class RemindDelay implements Serializable {
/**
* 主键ID
*/
private long id;
/**
* 创建时间戳
*/
private long timestamp;
public RemindDelay() {
}
public RemindDelay(long id) {
this.id = id;
this.timestamp = System.currentTimeMillis();
}
public long getId() {
return id;
}
public long getTimestamp() {
return timestamp;
}
}
消费提醒队列:
/**
* 消费提醒队列
*/
@Component
public class RemindRunner implements ApplicationRunner {
private final static Logger LOGGER = LoggerFactory.getLogger(RemindRunner.class);
private static int corePoolSize = Runtime.getRuntime().availableProcessors();
private static ThreadPoolExecutor executor =
new ThreadPoolExecutor(corePoolSize, corePoolSize+1, 10L, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(100));
@Autowired
private RemindService remindService;
@Autowired
private WxMaService wxService;
@Override
public void run(ApplicationArguments args) throws Exception {
/**
* 出队列
*/
RedissonClient redissonClient = RedissLockUtil.getRedissonClient();
RBlockingQueue<RemindDelay> delayedRemindQueue
= redissonClient.getBlockingQueue("remindDelayQueue");
new Thread(() -> {
LOGGER.info("提醒队列启动成功");
while (true){
try {
RemindDelay delay = delayedRemindQueue.take();
push(delay);
LOGGER.info("提醒ID:{}",delay.getId());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
public void push(RemindDelay delay){
/**
* 推送
*/
Runnable task = () -> {
//发送消息通知逻辑
};
executor.execute(task);
}
}
适用场景
淘宝订单到期,下单成功后60s之后给用户发送短信通知,限时支付、缓存系统、红包过期、开奖提醒等等。
小结
以上方案单机模式,生产环境可根据实际业务需求选择使用。