腾讯时序数据库之准实时揭秘

点击上方蓝字每天学习数据库

| 本文作者:游成松,腾讯云数据库后台开发,负责腾讯云数据库CTSDB产品的设计、研发、运维等工作。曾负责腾讯云数据库SQLServer、PostgreSQL、TDSQL、Tbase、CynosDB产品的研发工作。


一个理想的查询过程中,新增加或者修改的数据应该能立即被查询到。腾讯时序数据库CTSDB给人的第一印象好像就是如此工作的,而事实上并非如此。那它实际情况是怎么样的呢?

在进行说明之前先大概介绍一下CTSDB处理请求的流程。

在CTSDB和磁盘之间有一层FileSystem Cache的系统缓存,以使得能够更快地处理搜索请求。插入请求到来时document会先被放入到indexing buffer,然后被重写为一个segment直接写入到filesystem cache,这个操作是非常轻量级的,相对耗时较少,之后经过一定的间隔或外部触发后才会被flush到磁盘上,这个操作非常耗时。但只要sengment文件被写入cache后就可以被打开和查询,在短时间内就可以搜到,而不用执行一个flush也就是fsync操作。其请求处理流程如下图:

下面通过一个案例来验证进行观察分析。

新增加一条数据到新创建的索引中。

curl -u root:xxxxxx -XPOST 127.0.0.1:9200/test/test/1 -d '{    "title":"test"}'

为了进行验证,我们修改这条数据,并尝试立即查询它,连续执行下面两行命令。

curl -u root:xxxxxx -XPOST 127.0.0.1:9200/test/test/1 -d '{    "title":"test2"}'curl -u root:xxxxxx -XGET 127.0.0.1:9200/test/test/_search -d '{    "docvalue_fields":[        "title"    ]}'

前面两行命令返回结果如下:

{    "_index":"test@1555344000000_30",    "_type":"test",    "_id":"1",    "_version":2,    "result":"updated",    "_shards":{        "total":2,        "successful":2,        "failed":0    },    "created":false}{    "took":0,    "timed_out":false,    "_shards":{        "total":3,        "successful":3,        "skipped":0,        "failed":0    },    "hits":{        "total":1,        "max_score":1,        "hits":[            {                "_index":"test@1555344000000_30",                "_type":"test",                "_id":"1",                "_score":1,                "_source":{                    "title":"test",                    "timestamp":1556299203460                },                "fields":{                    "title":[                        "test"                    ]                }            }        ]    }}

我们可以看到第1条命令修改数据成功了,返回结果version字段为2,一切正常。但是第2条命令返回的title字段应该返回test2的,但是还是返回修改之前的数据test。

由于CTSDB底层是基于ElasticSearch的,而ElasticSearch的索引是基于Apache Lucene索引的,那我们先来看看Lucene的内部机制,Lucene是如何让新索引的文档在搜索时可用?

索引更新及更新提交

索引新文档时会被写入索引段。不时会有新增的索引段被添加到可被搜索的索引段集合中,Lucene通过创建后续的(基于索引只写一次的特性)segments_N文件来实现此功能。这个过程被称为提交,Lucene会以安全、原子的操作来提交保证数据一致性。

我们回来之前的案例中,尽管第1个命令修改了文档,但它还没有执行提交操作。然而执行了提交操作也不能保证能被搜索到,因为Lucene使用一个叫Searcher的抽象类来执行索引的读取。该类需要被刷新,如果索引更新提交了,但Searcher实例没有重新打开(刷新),那么Searcher察觉不到有新索引段的加入。对于Searcher的刷新间隔时间可以通过refresh_interval来进行设置。

同时在ElasticSearch中提供了refresh端点进行强制Searcher刷新。我们可以在上面两行命令中间加入强制刷新API再来验证。

curl -u root:xxxxxx -XPOST 127.0.0.1:9200/test/test/1 -d '{    "title":"test3"}'curl -u root:xxxxxx -XGET 127.0.0.1:9200/test@1555344000000_30/_refreshcurl -u root:xxxxxx -XGET 127.0.0.1:9200/test/test/_search -d '{    "docvalue_fields":[        "title"    ]}'

上面三行命令返回结果如下:

{    "_index":"test@1555344000000_30",    "_type":"test",    "_id":"1",    "_version":3,    "result":"updated",    "_shards":{        "total":2,        "successful":2,        "failed":0    },    "created":false}{    "_shards":{        "total":6,        "successful":6,        "failed":0    }}{    "took":0,    "timed_out":false,    "_shards":{        "total":3,        "successful":3,        "skipped":0,        "failed":0    },    "hits":{        "total":1,        "max_score":1,        "hits":[            {                "_index":"test@1555344000000_30",                "_type":"test",                "_id":"1",                "_score":1,                "_source":{                    "title":"test3",                    "timestamp":"1556301337152"                },                "fields":{                    "title":[                        "test3"                    ]                }            }        ]    }}

可以看到第3条命令返回的title字段为test3,返回了修改之后的数据test3。

事务日志

在ElasticSearch的索引实现中Apache Lucene能保证索引的一致性,但这不能保证当向索引中写数据失败时不损失数据(例如,磁盘空间不足,设备异常)。另外一个问题是频繁提交(触发一个索引段的创建操作,同时也可能触发索引段的合并)会导致性能问题。ElasticSearch使用事务日志来解决这些问题,事务日志保存所有未提交的事务。当有错误发生时,事务日志会被检查,必要时再次执行某些操作,以确保没有丢失任何更改。事务日志中的信息与存储介质之间的同步被称为事务日志刷新。同样事务日志的刷新间隔可以通过index.translog.flush相关参数进行配置,也可以通过_flush端点进行强制事务日志刷新。

事务日志刷新flush用于保证数据写入磁盘并清空事务日志。Searcher刷新refresh用于搜索到最新的文档。

准实时读取

事务日志给ElasticSearch带来了一个特性:实时读取。实时读取从索引中读取数据时,会先检查事物日志中是否有可用的新版本(未提交版本),如果有就会返回事务日志中的最新版本的文档。

为了演示实时读取,连续执行下面两条语句,第2条语句查询时指定索引文档id来查询,会从事务日志中读取最新的数据。

curl -u root:xxxxxx -XPOST 127.0.0.1:9200/test/test/1 -d '{    "title":"test4"}'curl -u root:xxxxxx -XGET 127.0.0.1:9200/test@1555344000000_30/test/1

上面2行数据返回结果如下:

{    "_index":"test@1555344000000_30",    "_type":"test",    "_id":"1",    "_version":4,    "result":"updated",    "_shards":{        "total":2,        "successful":2,        "failed":0    },    "created":false}{    "_index":"test@1555344000000_30",    "_type":"test",    "_id":"1",    "_version":4,    "found":true,    "_source":{        "title":"test4",        "timestamp":"1556305107154"    }}

可以看到第2条命令返回的title字段为test4,返回了修改之后的数据test4。

这时我们并没有使用refresh刷新技巧就查询到了最新的文档。

总结

修改数据、search数据:不一定会查询到最新的数据。

修改数据、refresh强制刷新、search数据:会查询到最新的数据。

修改数据、指定文档id查询数据:会查询到最新的数据。

往期推荐

《鹅厂老司机教你学习Innodb》

《腾讯数据库专家雷海林分享智能运维架构》

年中薅羊毛,可省18040元

云数据库MySQL年中疯狂折扣中,买MySQL高可用版送6个月数据迁移服务,1核1G内存100G SSD盘低至96.8元/月。免费数据管理DMC,双节点架构,自动容灾,最高可省18040元!点击左下角“阅读原文”立即参与~

↓↓点“阅读原文”享年中福利

好文和朋友一起看!

var first_sceen__time = (+new Date());if ("" == 1 && document.getElementById('js_content')) { document.getElementById('js_content').addEventListener("selectstart",function(e){ e.preventDefault(); }); } (function(){ if (navigator.userAgent.indexOf("WindowsWechat") != -1){ var link = document.createElement('link'); var head = document.getElementsByTagName('head')[0]; link.rel = 'stylesheet'; link.type = 'text/css'; link.href = "//res.wx.qq.com/mmbizwap/zh_CN/htmledition/style/page/appmsg_new/winwx45ba31.css"; head.appendChild(link); } })();

游成松

赞赏

长按二维码向我转账

受苹果公司新规定影响,微信 iOS 版的赞赏功能被关闭,可通过二维码转账支持公众号。

阅读原文

阅读

分享 在看

已同步到看一看

取消 发送

我知道了

朋友会在“发现-看一看”看到你“在看”的内容

确定

已同步到看一看写下你的想法

最多200字,当前共字 发送

已发送

朋友将在看一看看到

确定

写下你的想法...

取消

发布到看一看

确定

最多200字,当前共字

发送中

微信扫一扫 关注该公众号

微信扫一扫 使用小程序

即将打开""小程序

取消 打开

本文分享自微信公众号 - 腾讯云数据库(TencentDB)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-05-16

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券

年度创作总结 领取年终奖励