三、金融业企业服务总线链路追踪监控分析平台的建设实践--CASSANDRA存储方案

上篇我们讲了ORACLE的存储方案,知道这种方案有很大缺陷,只能做一个临时的方案,18年的时候我们启动的第二波改造行动。
开始的时候我们考虑了使用HBASE来存储数据,使用hbase保存数据其实再好不过,但是,需要自己搭建环境并运维,我们调研了一段时间,hbase依赖的组件太多,维护困难,就直接PASS掉了。

一、首先简单介绍下CASSANDRA原理及工作过程

架构: 简单说就是 一致性hash + Gossip。
Gossip:负责维护集群状态
一致性hash:组织数据如何在集群节点中存储。
hash环
1、写数据:

依据LSM-TREE实现,其核心是,假定内存足够大,因此不需要每次有数据更新就必须将数据写入到磁盘中,而可以先将最新的数据驻留在内存中,等到积累到足够多之后,再使用归并排序的方式将内存中的数据合并追加到磁盘队尾,能显著地减少硬盘磁盘臂的开销,写入操作主要包含以下4个步骤
(1)记录数据到commit log
(2)写数据到memtable
(3)当commit log的size达到阀值或者memtable的size达到阀值时,将memtable中数据flush到SSTable中,flush完成后清空commit log和memtable
(4)sstalbe Compaction.

在这里插入图片描述
2、读过程:

(1)检查 memtable 
(2) 如果开启了row cache, 读row cache
(3) 读Bloom Filter,Bloom filter用于检查当前查询的partition key位于哪一个SSTable中。
(4) 如果开启了partition key cache,读partition key cache。partition key cache是partition index的缓存。
(5) 如果在partition key缓存中找到了partition key,直接去compression offset map中,如果没有,检查 partition summary 
(6)根据compression offset map找到数据位置 
(7)从磁盘的SSTable中取出数据
如下图所示。

在这里插入图片描述
行缓存及键缓存流程图

二、cassandra设计一般原则。

1、避免实时热点数据写:
	小表:无需关注;
	大表:集群中每个节点(假设各节点性能一样)接收的数据应该基本相同。
2、避免查询扩散:
	一次查询,尽量在一个节点上查出所有数据,避免扩散到多个节点查询数据。
3、避免使用cassandra索引:
	小表:可以使用,如基表等数据量少且数据固定。
	大表:禁止使用。
4、Row cache: 查多写少时使用,写多查少时禁用
5、避免批量提交数据。

三、设计方案

上篇《ORACLE存储方案》我们已经讲了,我们会存储两种数据:统计数据及明细数据,明细数据量大、统计数据量小,下面我们以明细表来讲解百亿级数据的保存及查询,以统计表讲解多维报表的实现。
1,明细数据保存:

	   明细主表:
	      create table m_trace(
	      traceId text,
	        side text,
	        spanId text,
	         ......
	         primary key(traceId,side,spanId)
	         )with compaction = {'class' : 'TimeWindowCompactionStrategy','compaction_window_size':'6,'compaction_window_unit':'HOURS'}
	         and default_time_to_live = 864000
	         and gc_grace_seconds = 60
	         and dclocal_read_repair_chance = 0
	         and read_repair_chance = 0;
		  索引表:
		  create table m_trace_idx_by_uri(
		  	traceId text,
		  	strTime text,//形如202011212030
		  	requestUri text,
		  	   side text,
		  	   spanId text,
		  	   ......
		  	   primary key((requestUri,strTime),side,traceId)
		  )with compaction = {'class' : 'TimeWindowCompactionStrategy','compaction_window_size':'6,'compaction_window_unit':'HOURS'}
	         and default_time_to_live = 864000
	         and gc_grace_seconds = 60
	         and dclocal_read_repair_chance = 0
	         and read_repair_chance = 0;
		代码说明: 	
		a、Partition Key:选择或设计一个合理的Partition Key可以避免热点数据、避免查询扩散。明细主表中的分区键是traceId,索引表的分区键是(requestUri,strTime),它们都有一个特点,即:同一时刻,存在大量的键值。 对写,可以避免出现热点数据,数据均匀流向各个节点;对读,明细表根据traceID查,精确查询效率高,索引表根据URI及时间联合查询,集合查询,每次查询只从一个节点读数据(应用层控制,每次只查一个分区的数据),CASSANDRA是按顺序报错,DB读的时候按块读入内存,因此大多数情况一个IO就可以查询出大量需要的接口,效率也非常的高。
        b、Clustering Key:Clustering Key可以用来在Partition内部排序,利用这个特性来实现列表查询的分页功能,如,索引表查询出大量数据时需要上下分页。 利用这个特性实现翻页还是有一些局限,比如,side,traceId ,traceId是在side下面是按顺序排列的。
        c、压缩策略:TWCS
    	理由:trace都是时序类数据(时序是根据primarykey判断,一个primarykey只出现在很短的时间段内),数据只有insert,没有update,数据的生命周期是10天,过期会清理。需要特别注意乱序问题,比如说,正常情况下某个traceId只会出现在某一时刻,但是因为某些原因,比如代码BUG,造成采集上了的相同traceId持续出现几天,这种情况会造成SSTABLE不能及时清理(traceId分布在的所有SSTABLE都过期时才能删除),为避免这种情况,需要在primarykey的末尾加上时间相关的键值,避免造成大量数据不能及时清理。
    	d、read repair:dclocal_read_repair_chance = 0  read_repair_chance = 0
    		 这两个参数是调整读修复的概率,并不能完全禁止。
	    e、数据清理:default_time_to_live = 864000   gc_grace_seconds = 60
		    default_time_to_live:定义数据生命周期
		    gc_grace_seconds:默认10天。删除数据,本质是插入一条tombstone数据,此参数表示tombstone数据能够保留多长时间才会被真正删除。当Cassandra进行compaction操作并且一条数据的deletion time + gc_grace_seconds小于当前时间时,此条数据才会被真正删除。
		    我们的整张表设置一个默认的_time_to_live属性。每条数据都被标记为一个TTLs;如果一条记录超过了表级别的TTL,Cassanda会立即将它删除,而没有标记墓碑或者compaction过程,因此gc_grace_seconds设置很短或不设置都是可以的。
		f、异步提交、禁止批量提交
			异步提交:主要是跨机房保存,能提高应用的并发能力。
			批量提交:若批量提交的数据都在同一个节点,并且这些数据之间存在事务关系,否则不能使用批量提交。

2、多维分析报表
a,表设计,涉及到分钟表、小时表、天表、月表、年表,先上代码。

      年表:
      create table m_report_year(
      time_y text,   --2020
      side text,  --consumer,provider
      c_id text,   --消费标识
      p_id text,  --服务标识
     t_sorce text,  --来源,移动端还是其它
      uri text,
      f_count bigint,   --失败调用量
      t_count bigint,   --总调用量
      t_avg double,   --平均耗时
     primary key ((time_y,side),t_source,c_id,p_id,uri,)
      ) with compaction = {'class' : 'LeveledCompactionStrategy'};
 月表:
      create table m_report_month(
      time_m text,   --202011
      time_y text,     --2020
      side text,  --consumer,provider
      c_id text,   --消费标识
      p_id text,  --服务标识
     t_sorce text,  --来源,移动端还是其它
      uri text,
      f_count bigint,   --失败调用量
      t_count bigint,   --总调用量
      t_avg double,   --平均耗时
     primary key ((time_y,side),time_m,t_source,c_id,p_id,uri,)
      ) with compaction = {'class' : 'LeveledCompactionStrategy'};

 日表:
      create table m_report_day(
      time_d text,      --20201111
      time_m text,     --202011
      side text,  --consumer,provider
      c_id text,   --消费标识
      p_id text,  --服务标识
     t_sorce text,  --来源,移动端还是其它
      uri text,
      f_count bigint,   --失败调用量
      t_count bigint,   --总调用量
      t_avg double,   --平均耗时
     primary key ((time_m,side),time_d,t_source,c_id,p_id,uri,)
      ) with compaction = {'class' : 'LeveledCompactionStrategy'};
小时表:
      create table m_report_hour(
      time_h text,     --2020111111
      time_d text,     --20201111
      side text,  --consumer,provider
      c_id text,   --消费标识
      p_id text,  --服务标识
     t_sorce text,  --来源,移动端还是其它
      uri text,
      f_count bigint,   --失败调用量
      t_count bigint,   --总调用量
      t_avg double,   --平均耗时
     primary key ((time_d,side),time_h,t_source,c_id,p_id,uri,)
      ) with compaction = {'class' : 'TimeWindowCompactionStrategy','compaction_window_size':'1','compaction_window_unit':'DAYS'}
      AND default_time_to_live=2592000
      and gc_grace_seconds = 20
      and dclocal_read_repair_chance = 0
      and read_repair_chance = 0;

分钟表:
      create table m_report_min(
      time_min text,     --202011111111
      time_h text,         --2020111111
      side text,  --consumer,provider
      c_id text,   --消费标识
      p_id text,  --服务标识
     t_sorce text,  --来源,移动端还是其它
      uri text,
      f_count bigint,   --失败调用量
      t_count bigint,   --总调用量
      t_avg double,   --平均耗时
     primary key ((time_h,side,time_min),t_source,c_id,p_id,uri,)
      ) with compaction = {'class' : 'TimeWindowCompactionStrategy','compaction_window_size':'1','compaction_window_unit':'DAYS'}
      AND default_time_to_live=2592000
      and gc_grace_seconds = 20
      and dclocal_read_repair_chance = 0
      and read_repair_chance = 0; 
      
说明:年表、月表、天表永久保存,因此选择LCS;
       由于统计表量很小(相比明细表),partitionkey的 选择并没有遵循数据均匀分布的原则,而是从查询性能方面来设计,可以发现除年表外,所有表的partitionkey都是按上级时间维度来进行分区的,比如,天表的partitionkey是按月粒度进行分区,这样做的好处是一次在一个节点可以查询多天的数据,直接返回前端;
       主键中,查询时必输的条件,尽量往左靠,区分度越低字段越往左边靠,如,报表一定会有时间限制,所以以上几个表的时间字段都靠左,SIDE只有两个值,因此也靠在左边最前面,由于数据是连续存放在磁盘,因此,一次或几次IO就可以将数据读入到cassandra,然后在内存做一些计算操作也会很快。
   
临时表:
create table m_report_group_tmp(
taskId text,
long1 bigint,
long2 bigint,
double1 double,
group0 text,
group1 text,
group2 text,
group3 text,
group4 text,
group5 text,
primary key (taskId,group0,group1,group2,group3,group4,group5)
)with compaction = {'class' : 	'TimeWindowCompactionStrategy','compaction_window_size':'1','compaction_window_unit':'HOURS'}
      AND default_time_to_live=3600
      and gc_grace_seconds = 60
      and dclocal_read_repair_chance = 0
      and read_repair_chance = 0;
说明:cassandra的sql使用时有很多限制,比如,group by必须严格按主键字段的顺序排列,而我们的分钟表、小时表等等主键都定义好了,意味在这些原始表上进行分组合并计算有很多限制,中间表的作用主要就是为分组合并计算使用。

b、应用及前端设计:

统计维度:
 	时间粒度:年、月、日、小时、分钟
	消费方:不区分、c_id
 	服务方:不区分、p_id、uri
 	来源:不区分、区分
 
查询条件:
	时间:根据统计时间粒度选择,必录项
		分钟:默认最近2小时;
		小时:默认最近1天;
		日:最近7日;
		月:默认最近3月
	指定消费方:c_id,不选默认全部
	指定服务方:p_id, uri,   不选默认全部。

c、数据处理流图:
在这里插入图片描述
如图所示,一次报表查询需要3次DB交互
1).根据查询条件组装SQL,根据条件查询,不做分组,
2).执行SQL
3) .返回结果集
4).将结果集数据按group by字段顺序重新组织插入m_report_group_tmp
5).执行重新组装SQL,加入group by分组,计算会用到自定义的聚合函数,如,sum,count,max,min等。
6) .返回结果集
7).返回前端展示

四、总结

cassandra很适合存储海量数据,但是在sql使用上还有一些限制,造成易用性不是很好。

对明细数据:用cassandra是很好的选择,海量存储,无限扩容,查询也比较方便;

对统计数据:如果要做多维度报表查询,其实不太适合,维度太多,而cassandra计算功能比较薄弱,

	建表还有很多限制,增加了应用的复杂性,使得在我们的方案实现中就做的比较复杂;

	如果用关系型DB,这么大量的更新操作,需要考虑分库分表,增加了架构复杂性。

总的来说,cassandra并不完美,在我们后续的调研中,发现elasticsearch更适合做这个事情,它不仅可以存储海量数据,而且还有强大的聚会计算功能。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值