點擊▲關注 “爪哇筆記” 給公衆號標星置頂
更多精彩 第一時間直達
大家好,我是本羣的提醒喝水小助手,這是今天的第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之後給用戶發送短信通知,限時支付、緩存系統、紅包過期、開獎提醒等等。
小結
以上方案爲單機模式,生產環境可根據實際業務需求選擇使用。