对一些历史数据的查询,如果将数据放在存储中如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用于轻松、高效实施数据监测、监控类、实时交易类项目