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

netcat20000
发布于 2022-7-15 17:53
浏览
0收藏

 

一个理想的查询过程中,新增加或者修改的数据应该能立即被查询到。腾讯时序数据库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/_refresh
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":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查询数据:会查询到最新的数据。

 

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

 

文章转自公众号:腾讯云数据库

分类
标签
已于2022-7-15 17:53:43修改
收藏
回复
举报
回复
    相关推荐