1、添加队列,生产
log.info("=============发送消息,添加延迟7天队列===========");
Map<String, Object> params = new HashMap<>();
params.put("teamId", pkRunningActivity.getTeamId());
long millis = System.currentTimeMillis() + 60000 * 60 * 24 * 7;
DelayMessage delayMessage = new DelayMessage(params, millis, pkRunningActivity.getTeamId());
queue.add("PKTeam", delayMessage);
2、监控,到期消费
package com.vanke.service.PkTeamOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
public abstract class DelayQueueConsumer implements ApplicationRunner {
@Autowired
private DelayQueue queue;
public abstract String getQueueName();
public abstract void consume(DelayMessage message);
public abstract void catchInterruptedException(InterruptedException e);
@Override
public void run(ApplicationArguments args) throws Exception {
//获取队列名称
String queueName = getQueueName();
if (queueName == null) {
throw new NullPointerException("the return value of getQueueName method cannot be null");
}
Thread thread = new Thread(() -> {
while (true) {
try {
//获取消息
DelayMessage message = queue.take(queueName);
//消费消息
this.consume(message);
} catch (Exception e) {
e.printStackTrace();
}
}
});
thread.setDaemon(true);
thread.start();
}
}
3、其他类
package com.vanke.service.PkTeamOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Service;
import java.lang.reflect.Array;
import java.util.Set;
@Service
public class DelayQueue {
@Autowired
private RedisTemplate redisTemplate;
/**
* 加入一个消息
*
* @param queueName 队列名称
* @param message 延迟消息类
*/
public void add(String queueName, DelayMessage message) {
redisTemplate.opsForZSet().add(queueName, message, message.getExpire());
synchronized (this) {
//加入一个消息后唤醒所有睡眠的线程
this.notifyAll();
}
}
/**
* 取出一个消息
*
* @param queueName 队列名称
* @return
* @throws InterruptedException
*/
public DelayMessage take(String queueName) throws InterruptedException {
while (true) {
ZSetOperations.TypedTuple<DelayMessage> tuple = getFirst(queueName);
if (tuple == null) {
synchronized (this) {
//如果该队列为空则休眠该线程
this.wait();
tuple = getFirst(queueName);
}
}
if (tuple != null) {
//消息到期时间戳
long expire = tuple.getScore().longValue();
//延迟时间
long delay = expire - System.currentTimeMillis();
//未到达消费时间
if (delay > 0) {
synchronized (this) {
//让线程休眠delay毫秒
this.wait(delay);
}
}
/*
这里判断消息消费时间是否大于当前时间的原因:
当该线程执行上一步 this.wait(delay);
正好有消息消费在休眠准备消费时;
有消息加入执行了add 加入消息,此时唤醒了休眠的线程
所有这里再做一次判断,防止未到时间消息就消费了
*/
if (System.currentTimeMillis() >= expire) {
//取出消息
DelayMessage delayMessage = tuple.getValue();
//把消息 id取出 建立一个key
String lockKey = queueName + "_" + delayMessage.getSeqId() + "_lock";
//setIfAbsent的作用是:
//如果该key有值1,则返回false
//如果该key没有值为1,则返回ture而且再为key加上值为1
Boolean ok = redisTemplate.opsForValue().setIfAbsent(lockKey, "1");
if (ok) {
//移除redis之前存的消息
redisTemplate.opsForZSet().remove(queueName, delayMessage);
//删除刚刚为了保证原子性的key
redisTemplate.delete(lockKey);
//返回消息主体
return delayMessage;
}
}
}
}
}
/**
* 得到该队列的第一条数据
*
* @param queueName 队列名称
* @return
*/
private ZSetOperations.TypedTuple getFirst(String queueName) {
//获取队列名称为 queueName的所有的消息
Set<ZSetOperations.TypedTuple<DelayMessage>> tuples = redisTemplate.opsForZSet().rangeWithScores(queueName, 0, -1);
if (tuples.size() == 0) {
return null;
}
ZSetOperations.TypedTuple[] array = (ZSetOperations.TypedTuple[]) Array.newInstance(ZSetOperations.TypedTuple.class, tuples.size());
return tuples.toArray(array)[0];
}
/**
* 手动接受挑战删除队列消息(这个是作为不愿意等,7天内中途消费的)
*
* @param queueName
* @param teamId
*/
public void removeMessage(String queueName, String teamId) {
while (true) {
ZSetOperations.TypedTuple<DelayMessage> tuple = getFirst(queueName);
if (tuple != null) {
//取出消息
DelayMessage delayMessage = tuple.getValue();
if (delayMessage.getTeamId().equals(teamId)) {
//把消息 id取出 建立一个key
String lockKey = queueName + "_" + delayMessage.getSeqId() + "_lock";
//setIfAbsent的作用是:
//如果该key有值1,则返回false
//如果该key没有值为1,则返回ture而且再为key加上值为1
Boolean ok = redisTemplate.opsForValue().setIfAbsent(lockKey, "1");
if (ok) {
//移除redis之前存的消息
redisTemplate.opsForZSet().remove(queueName, delayMessage);
//删除刚刚为了保证原子性的key
redisTemplate.delete(lockKey);
break;
}
}
}
}
}
}
package com.vanke.service.PkTeamOrder;
import java.io.Serializable;
public class DelayMessage implements Serializable {
/**
* 队列id 用原子类生产
*/
private String seqId;
private String teamId;
/**
* 消息主体
*/
private Object body;
/**
* 消息消费的时间戳
*/
private long expire;
public DelayMessage() {
}
public DelayMessage(Object body, long expire, String teamId) {
this.seqId = IdWorker.getNextIdStr();
this.body = body;
this.expire = expire;
this.teamId = teamId;
}
public DelayMessage(Object body, int delay, String teamId) {
this(body, System.currentTimeMillis() + delay, teamId);
}
public DelayMessage(String seqId, Object body, long expire) {
this.seqId = seqId;
this.body = body;
this.expire = expire;
}
public String getSeqId() {
return seqId;
}
public Object getBody() {
return body;
}
public long getExpire() {
return expire;
}
public String getTeamId() {
return teamId;
}
}
package com.vanke.service.PkTeamOrder;
import com.vanke.dao.externalPk.PkRunningActivityDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Date;
import java.util.Map;
import lombok.extern.slf4j.Slf4j;
@Service
@Slf4j
public class MessageTask extends DelayQueueConsumer {
@Autowired
private PkRunningActivityDao pkRunningActivityDao;
@Override
public String getQueueName() {
return "PKTeam";
}
@Override
public void consume(DelayMessage message) {
Map body = (Map) message.getBody();
String teamId = body.get("teamId").toString();
boolean boActivity = pkRunningActivityDao.updateActivity(teamId);
log.info("延迟消费消息成功:" + boActivity + "==" + new Date() + "==" + teamId);
}
@Override
public void catchInterruptedException(InterruptedException e) {
}
}
package com.vanke.service.PkTeamOrder;
import java.util.concurrent.atomic.AtomicLong;
public class IdWorker {
private static final AtomicLong WORKER = new AtomicLong(0);
public static long getNextId() {
return WORKER.incrementAndGet();
}
public static String getNextIdStr() {
return String.valueOf(getNextId());
}
}