對一些歷史數據的查詢,如果將數據放在存儲中如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集成。