SODBASE CEP學習進階篇(五):與分佈式緩存集成

對一些歷史數據的查詢,如果將數據放在存儲中如Hbase或RDBMS中,讀寫性能可能會拖累整個CEP系統的性能。CEP引擎和分佈式緩存系統集成是解決這個問題的一種方法。如果CEP Server機器的內存足夠大,直接開闢在CEP Server的內存中存數據當然速度最快。以下場合可考慮外接分佈式緩存

(1)CEP Server內存不足,想通過外接緩存的方式來存儲數據。

(2)已經有了分佈式緩存系統,想增加CEP計算和規則管理能力。

本文以Redis集成爲例,因爲Redis作爲一個成熟的分佈式緩存系統,業界包括一線互聯網企業都有在生產環境的部署。

1.示例操作步驟

例1,在線商城中我們要查詢某商品實時UV(Unique Visitor),對於千萬級以上用戶的系統做這樣的實時計算。一種方法是在Redis中建一個<商品ID,獨立訪問次數>的hash map。爲每個用戶在Redis中建一個bitmap key,key的bit位置代表商品id。當一條<用戶ID,商品ID,timestamp>數據實時傳過來時,我們先需要看此用戶ID是否第一次訪問此商品,如果不是就SETBIT useridvisited productid 1。注意:Redis現在bitmap索引的最大長度是512M,也就是一個bitmap中商品的數目不超過512M。如果超了這個數也不要緊,可多建立幾個bitmap,因爲bitmap的數量是不限的。

(1)網上下載一個Redis,SODBASE CEP已支持與Redis 3.0以上的Redis Cluster集成。如果覺得麻煩想快速把例子跑起來,可以用這個Windows版,解壓,根據自己機器選擇32bit或64bit文件夾,運行redis-server.exe,默認端口6379啓動。注意若linux版本,防火牆要開啓相應端口。

(2)下載SODBASE Studio 2.0.21(sp3)版本以上

下載示例CEP模型redis01.sod,redis02.sod,redis03.sod

(3)運行SODBASE Studio,導入redis01.sod,redis02.sod,redis03.sod

redis01:模擬用戶的商品瀏覽(初始模擬了100個用戶,100種商品),並查redis bitmap此用戶是否已經瀏覽過此商品

redis02:計算uv,更新bitmap

redis03:屏幕打印

(4)將三個EPL模型全部測試運行起來

(5)輸出結果


例2:還是在線商城中統計商品的獨立訪問量。如果商品數量很大,那麼即使爲每個商品建了bitmap索引,佔的內存空間也會很大。可以用HyperLogLogs的PFCOUNT命令,單個商品的 HyperLogLogs數據結構佔內存小。PFCOUNT 對於一個key值的複雜度爲O(1),當然因爲是概率算法,統計有一些誤差。

例3:如果要求滑動窗口在數小時、或者1天以上,且包含數據量很大,可採用SODBASE CEP新增的sodbase-hexpire等命令結合Redis實現大窗口,並且保證處理性能。

2. 工作原理和注意事項

2.1  模擬用戶訪問商品輸入適配器

/**
 * 
 */
package com.sodbase.inputadaptor.simulation;

import java.util.Date;
import java.util.Random;

import zstreamplus.eventbuffer.PrimitiveEvent;
import zstreamplus.eventbuffer.ValueType;
import zstreamplus.streamsource.StreamSource;

import com.sodbase.inputadaptor.OptimizedInputAdaptorI;

/**
 * 模擬在線商城用戶對商品的訪問
 *參數列表:
 *params[0] 	streamname
 *params[1] 	period
 *params[2]     user number
 *params[3]     product numbers
 */
public class ProductVisitStreamInput extends OptimizedInputAdaptorI
{

	private boolean running=true;
	private long period=1000;
	private int usernumber=100;
	private int productnumber=100;

	/* (non-Javadoc)
	 * @see com.sodbase.inputadaptor.OptimizedInputAdaptorI#run()
	 */
	@SuppressWarnings("unchecked")
	@Override
	public void run()
	{
		
		int count=0;
		while (running)
		{
			//debug
			count++;
			/*if(count>10)
				running=false;*/
			
			try
			{
				Thread.sleep(period);
			}
			catch (InterruptedException e)
			{
				e.printStackTrace();
			}
			
			/**
			 * API: 新建一個Primitive Event基本事件(複雜事件由基本事件組成)
			 * 
			 */
			
			PrimitiveEvent primitiveEvent = new PrimitiveEvent();
			Random random = new Random();
			int random1 = random.nextInt(usernumber);
			int random2 = random.nextInt(productnumber);
			/**
			 * 爲primitiveEvent name字段賦值
			 */
			ValueType valueType = new ValueType(String.valueOf(random1),
					"string");
			primitiveEvent.getAttributeMap().put("userid", valueType);

			ValueType valueType2 = new ValueType(String.valueOf(random2),
					"string");
			primitiveEvent.getAttributeMap().put("productid", valueType2);			
			Date d = new Date();			
			/**
			 * API: 爲primitiveEvent 設置時間戳,通常每個基本事件必須有時間戳
			 * 每個event都有start time 和end time
			 */
			long time = d.getTime();
			primitiveEvent.setStart_ts(time);
			primitiveEvent.setEnd_ts(time);
			this.putEventToStream(primitiveEvent);
		}
	
	}

	/* (non-Javadoc)
	 * @see com.sodbase.inputadaptor.OptimizedInputAdaptorI#setUp()
	 */
	@Override
	public void setUp()
	{
		if(params[0]!=null)
			this.streamName= params[0];
		if(params.length>1)
			this.period= Long.valueOf(params[1]);
		if(params.length>2)
			this.usernumber= Long.valueOf(params[2]);	
		if(params.length>3)
			this.productnumber= Long.valueOf(params[3]);	
		StreamSource.instance().createDataSource(streamName);
	
		
	}

	/* (non-Javadoc)
	 * @see stream.adaptor.inputadaptor.InputAdaptorI#stopInputStream()
	 */
	@Override
	public void stopInputStream()
	{
		running=false;
		
	}

	/* (non-Javadoc)
	 * @see stream.adaptor.inputadaptor.InputAdaptorI#isRunning()
	 */
	@Override
	public boolean isRunning()
	{
		return running;
	}

}

2.2 操作Redis

CEP的輸出適配器也常常作爲動作輸出,執行動作。redis01的輸出適配器就是操作Redis。



命令名 getbit

參數 ProductVisitBM:?{productid} ?{userid}

?{}一般是用來引用字段值的,如果productid=a,userid=20,完整的Redis命令就是 getbit ProductVisitBM:a 20,獲取bitmap ProductVisitBM:a的第20位。

返回值賦給了增加的字段visited,並連同其它字段一起接入下一個EPL的輸入流redis02.input。


redis02的filter如下圖所示



redis02的第一個輸出配置如下圖所示

redis02的第二個輸出配置如下

redis03將結果屏幕打印出來。

當然,使用Redis的讀者,也要注意官方文檔介紹,單次查詢響應時間可能會很慢,達到幾十毫秒甚至上百毫秒,因爲發給緩存系統的查詢要經過網絡傳輸返回結果(所有的分佈式緩存都有這個問題)。CEP應用的地方往往需要響應延遲在幾十毫秒以內,所以CEP服務器和分佈式緩存服務器之間最好是高速網絡連接。如果業務允許批量操作,儘量批量操作即pipeline,再將結果返回。和批量入庫的原理類似,這樣性能整體得到了提升。

在集羣管理方面,Redis推出了Redis Cluster正式版,可以動態伸縮集羣,相配套的client也趨於完善。之前Redis作爲Data Store,集羣的數目和數據映射是不能變的。Redis作爲Cache用,則不影響,因爲Cache沒有命中可以到硬盤上去找。

SODBASE CEP用於輕鬆、高效實施數據監測、監控類、實時交易類項目微笑嵌入式方式編程參見運行第一個EPL例子。與Storm集成參見EPL與Storm集成


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章