场景是:若干设备会每隔n秒,发送p数量的数据到服务端,服务端消费数据的能力为每n秒c个数据。
如果 c >= p, 则服务端正常运行。
如果 c<p,则服务端面临崩溃,越来越多的数据得不到消费,数据延迟会越来越高。
解决方法: 假设每4s 产生5个数据,服务端每4s消费4个数据
第0s到来的数据:0,1,2,3,4 (版本1)
第4s消费情况:已消费:0,1,2,3,未消费:4(版本1)
同时:设备又发来数据:0,1,2,3,4 (版本2)
后续消费方式:4(版本2),0,1,2
相当于消费能力之外的数据丢弃,下一次消费优先选择上次丢弃的数据
代码实现
public abstract class MapQueueElement<K extends Serializable> {
private long timestamp;
public long getTimestamp() {
return timestamp;
}
void markTime() {
if(timestamp==0l) {
timestamp=System.currentTimeMillis();
}
}
public abstract K eleKey();
}
public class MapQueue<K extends Serializable,E extends MapQueueElement<K>> {
public ConcurrentHashMap<K, E> dataMap;
private BlockingQueue<E> dataQueue;
/**
* 默认map容量
* 实际应该根据可能存在的数据对象的多少来设置,减少扩容
*/
public static final int DEFAULT_DATAMAP_CAPACITY = 16;
/**
* 默认队列容量
*/
public static final int DEFAULT_DATAQUEUE_CAPACITY = Integer.MAX_VALUE;
public MapQueue(int dataMapCapacity) {
this.dataMap=new ConcurrentHashMap<K, E>(dataMapCapacity);
this.dataQueue=new LinkedBlockingQueue<E>();
}
public MapQueue(int dataMapCapacity,int dataQueueCapacity) {
this.dataMap=new ConcurrentHashMap<K, E>(dataMapCapacity);
this.dataQueue=new LinkedBlockingQueue<E>(dataQueueCapacity);
}
public MapQueue(int dataMapCapacity,BlockingQueue<E> dataQueue) {
this.dataMap=new ConcurrentHashMap<K, E>(dataMapCapacity);
this.dataQueue=dataQueue;
}
/**
* 线程安全的,得到数据直接put进来就可以
*/
public void put(E e) throws InterruptedException {
E mapE=dataMap.get(e.eleKey());
e.markTime();
dataMap.put(e.eleKey(), e);
if(mapE==null) {
dataQueue.put(e);
}
}
/**
* 程序里不停的take数据,得到数据后放到定长线程池里处理
*/
public E take() throws InterruptedException {
E queueE=dataQueue.take();
E mapE=dataMap.get(queueE.eleKey());
if(mapE!=null&&queueE.getTimestamp()<mapE.getTimestamp()) {//队列里的时间早于map里的时间,使用map的数据,消费能力小于生产能力时,超出部分会走这里
dataMap.remove(queueE.eleKey());
queueE=null;
return mapE;
}
dataMap.remove(queueE.eleKey());//消费能力大于等于生产能力时,会走这里
return queueE;
}
}
测试方法
public class ProData extends MapQueueElement<String>{
private String key;
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public ProData(String key) {
this.key = key;
}
@Override
public String eleKey() {
return key;
}
}
public class QueueTest {
public static void main(String[] args) throws InterruptedException {
MapQueue<String,ProData> mapQueue=new MapQueue<>(1024);
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
for(int i=0;i<5;i++) {
try {
mapQueue.put(new ProData(String.valueOf(i)));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
},0, 4, TimeUnit.SECONDS);
while(true) {
ProData proData=mapQueue.take();
System.out.println(proData.getKey()+"当前时间:"+new Date(System.currentTimeMillis())+"---数据时间"+new Date(proData.getTimestamp()));
Thread.sleep(1000);
}
}
}
得到的结果也如我当初设想:
前提:消费能力c < 生产能力p
条件:c > p/2
结果:保证 前2c-p部分 的数据被实时消费,后面2个p-c部分 的数据 以0.5的概率被消费,保证每次消费能力(2c-p + p-c)= c
条件: c = p/2
结果:每次交替消费前后p/2的数据
条件:c < p/2
结果:不知道怎么算,不过先到的消费频率还是高一些
结语:大家看看这样行不行。