物聯網專案接收裝置實時資料的一個想法

艾@Acker發表於2020-12-14

場景是:若干裝置會每隔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
結果:不知道怎麼算,不過先到的消費頻率還是高一些

結語:大家看看這樣行不行。

相關文章