延时队列
概念
DelayQueue,放入队列中的数据取出的时候不是按照放入的顺序取出,而是按照事先指定的每个数据的过期时间来决定的,谁先过期,谁先取出,如果没到第一个过期的元素的过期时间,是无法取到任何元数据的
实现要点
让要放入队列的元素实现Delayed接口,实现其两个方法
- getDelay(Timeunit unit)
用于获取队列中元素的剩余有效时间(过期时间-当前时间) - compareTo(Delayed o)
用于队列中元素的排序
应用场景
- 用户订单超时未支付取消
- 发送数据失败,重试
- 缓存定时过期策略
案例
下面的代码模拟3个用户订单一分钟未付款,来按照超时时间获取超时订单
package com.radient.trydemo.test;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.annotation.JSONField;
import lombok.Data;
import lombok.SneakyThrows;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import javax.annotation.Nonnull;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;
/**
* 模拟用户1分钟未付款,则订单取消
*/
public class DelayedTest {
public static void main(String[] args) {
DelayQueue<Order> queue=new DelayQueue<>();
Thread aThread=new Thread(new Runnable() {
@SneakyThrows
@Override
public void run() {
//模拟按照时间不同,生成3个订单 张三的订单过后4秒李四有了订单,再过2秒王五有了订单
Order o1=new Order("张三","海信电视",4000d,new Date());
TimeUnit.SECONDS.sleep(5);
Order o2=new Order("李四","华为P30pro",3000d,new Date());
TimeUnit.SECONDS.sleep(2);
Order o3=new Order("王五","冰箱",2000d,new Date());
//为了更直观的查看延时队列取出的时候与放入的顺序不一致,只跟过期时间有关因此将生成订单时间最晚过期的王五的订单先装载
//先将王五的订单装载,再装载李四的订单,在装载张三的订单
queue.add(o3);
queue.add(o2);
queue.add(o1);
}
});
aThread.start();
Thread bThread=new Thread(new Runnable() {
@Override
public void run() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
while(true){
try {
Order order = queue.take();
System.out.println("\n"+sdf.format(new Date())+"获取到结果:\n\t"+ order);
System.out.println("==============================================================================");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
bThread.start();
}
}
@Data
@ToString
class Order implements Delayed {
private String userName;
private String productName;
private Double price;
@JSONField(format="yyyy-MM-dd HH:mm:ss")
private Date createTime; //订单的创建时间加上1分钟为过期时间
@JSONField(format="yyyy-MM-dd HH:mm:ss")
private Date overTime;
Order(String userName, String productName, Double price, Date createTime) {
this.userName = userName;
this.productName = productName;
this.price = price;
this.createTime = createTime;
this.overTime = new Date(this.createTime.getTime()+1*60*1000);
System.out.println(JSON.toJSONString(this));
}
@Override
public long getDelay(@Nonnull TimeUnit unit) {
return unit.convert(overTime.getTime()-System.currentTimeMillis(),TimeUnit.MILLISECONDS);
}
@Override
public int compareTo(Delayed o) {
Order item = (Order) o;
if(this.overTime.getTime()-item.overTime.getTime()>0){
return 1;
}else{
return -1;
}
}
}
执行结果:
从上图可以看到,虽然王五的订单最先放进队列,但是因为张三的订单是最早过期的,因此,取出来的顺序中张三还是第一位
指数退避算法
Exponential backoff is an algorithm that uses feedback to multiplicatively decrease the rate of some process, in order to gradually find an acceptable rate
退避算法就是网络上的节点在发送数据冲突后,我们认为马上进行重试失败几率增大,因此我们考虑等待一定时间后再发提高成功率,等待时间是随指数增长,从而避免频繁的触发冲突,到一定次数,指数运算停止,等待时间不会无限的增加下去,因为可能重试次数走完都没成功
重试策略
配置参数:
首次重试退避时间:firstRetryBackoffMs
最大重试次数:maxRetryTimes
计算第n次重试的等待时间:firstRetryBackoffMs * 2 ^ (n-1)
最大重试退避时间:maxRetryBackoffMs
int alreadyRetryTimes=2;
long nextRetryTime = firstRetryBackoffMs * LongMath.pow(2, alreadyRetryTimes)
return Math.min(nextRetryTime ,maxRetryBackoffMs)