究竟什么算实时
实时或者准实时的说法,没有对错,毕竟不停的听众,对相应的信息的理解是不一样的。有说2分钟执行数据client 开始导入也是实时,有说秒级别更新可见,有说<35ms 更新可见。个中种种说法,仿佛说,我的qps达到6000,我的qps达到600,我的qps达到300一样,只有数字,没有上下文,没有任何条件。而对于做搜索的,这么说某种程度是不科学,甚至是错误的。
什么样的数据,字段类型是什么?分词是什么?数据条数规模如何,单机100w数据,单机1000w数据,单机1亿数据?索引体积规模如何?什么配置硬件设施,48g、16core 物理机 ,4core 8g虚拟机,全ssd?什么查询?key-value查询? 区间查询?统计查询?group 查询?高亮?只是某个极端场景、最佳的配置这样的请求qps,只是那种特殊场景的情况。query特征不同,相同的索引也是不一样的性能。笼统的说自己的qps,并依此去PK另外的系统,完全是不科学、不尊重引擎的实际特点,放大说就是自我封闭,自我的level认识不够。当然,实际过程往往这个能忽悠倒一大批不懂行情的人员,并博得“赞赏”。还是那句话,同行的看法才是相对客观的。
实时索引可见一般流程
路线1
1--1--> client开始组装结构化数据<结构化数据这是引擎需要的>
--2--> 传入中间层(消息队列或者本地日志系统)<缓冲或者持久化,确保性能和数据最终一致性>
--3--> 检索结点异步去拉取增量数据,然后进入内存增量索引--4--> 当设置的时间阀值或者记录条数或者内存大小<批量提交>,开始重新打开内存reader试图,这样最新的数据就可见了。
路线2
--1--> client开始组装结构化数据<结构化数据这是引擎需要的>
--2--> 传入中间层(消息队列或者本地日志系统)<缓冲或者持久化,确保性能和数据最终一致性>
--3--> 固定的build或者dump结点,异步去拉取增量数据,在离线构建完整的增量索引,当然包括删除的信息,并且build或者dump结点保留完整的全量索引--4--> 检索结点去build或者dump结点,同步已经离线好的增量索引,在线执行增量索引的加载和卸
对于路线1
由于是增量和检索是合体的,那么资源无疑会发生争抢(查询和构建索引的内存和cpu以及io争抢)
一条一次reopen内存索引试图,意味着单条记录的更新非常快。记住,提交并重新打开reader是非常昂贵的操作,尽管内存的绝对开销值比磁盘小很多。reopen频率更短,意味着吞吐量有影响。reopen执行批量doc时候,批量性能提升。意味着,reopen频率不能太小,否则批量性能没法体现。到此,说更新可见时间单纯这个维度看,看不到系统的整体吞吐量。
重新打开索引视图,一方面,在重新打开前执行merger判断,在条件满足的时候执行“内存压缩”,当然意味着内存merger发生时候的额外“峰值”。这样内存的利用率更高。另一方面,在重新打开前不执行merger判断,紧紧打开新生成的segment对应的reader,这样reopen速度更快,内存更快的“饱满”,因为散的segment不够紧凑。只好在flush时刻合并了。并且,如果一条doc一个segment,那么非常槽糕,如果多条记录那么必须结合时间参数,例如35ms,那么这一批的doc量多大? 是否 内存的快速“饱满”,内存利用率不高,flush更频繁,这样IO资源争抢厉害,系统整体吞吐量如何保证?
对应路线2
比较难实现秒级别更新。 这种实现可以达到更大的吞吐量和更好的稳定性。 相同的增量数据只需一次增量构建,和多次分发,并且分发的时候还可以共享内存。数据到达后只有内存加载的工作,而索引往往紧凑,IO效率更高。这个比在线构建时候CPU消耗可能更低,网络传输可以大块,直接内存对内存。 另外一个优势,离线的build不停的增量,从而使得离线的全量也更加最近当前时间,一旦替换全量索引,也更加快捷(补增量时间更短)。容灾和恢复优势明显。
实际系统需要权衡的点
实时为了整体的吞吐量,client的实时数据需要缓冲和落地,从而不会受下游索引构建阻塞主流程。缓冲可以是消息队列,也可以直接落地磁盘。然后异步拉 或者推的方式执行消费。推荐拉的方式,这样每个消费者知道自己消费的点,并且可以根据本身的消费能力而调整消费速度和节奏,包括重启。当然也例如扩容源头 数据的分发。 实时为了整体的稳定性,能尽力离线搞定的,尽力离线搞定,然后离线数据同步到线上,线上只是简单的load、unload,而无其他的索引写计算开销。整个系统确保了查询的稳定性。 实时为了扩容和容灾,保存离线的索引备份和离线的源数据备份是必要的。资源允许的情况下,每个node都有对应的同等角色的node提供服务或者接管服务。 实时有时序要求,那么单线程或者多线程下的时间戳必须考虑。
实例lucene 的NRT
向 lucene indexWriter updateDocument 之后,执行commit可以使得索引更新的数据可见。而commit操作有可能触发merger,merger之后重新reopen内存索引试图。并且内存索引越大,reopen时间开销更短。通常1-5ms搞定。 如果不直接走commit接口实现索引试图更新,那么直接内存reopen。此时,内存零散的segment更多,内存利用率稍低,空间换得了更高的 reopen可见速度。flush时候的merger开销会有所增加。