ClickHouse 副本感知路由开启公测
DataHot 速览
ClickHouse 推出副本感知路由(Replica-aware routing)公测,面向 Enterprise 客户。问题在于临时表和命名会话只存在于创建它们的单个副本上,后续查询若被负载均衡到其他副本就会看不到。该功能通过 HTTP 的 X-ClickHouse-Replica-Tag 头或原生协议的 TLS SNI 覆盖,将同一路由键的请求固定到同一副本,从而支持临时表/会话访问和读写后一致性。原文给出 HTTP 与原生协议的使用示例,并说明其实现与适用场景。
为什么值得关注:多副本 ClickHouse 用户常受临时表/会话丢失困扰,该功能提供可直接落地的路由键方案,并支撑 read-after-write 一致性。
译文
AI 逐段翻译简介
想象一下,你创建了一个临时表,然后一秒钟后运行查询来读取它,结果失败了,提示该表不存在。
这从通常意义上来说并不是一个 bug。对于拥有多个副本的服务,你的 ClickHouse 临时表和命名会话只存在于创建它们的那个副本上。后续查询有可能被负载均衡到另一个副本,然后就好像它从未被创建过一样。
这就是我们构建副本感知路由的原因:让你始终可以访问。副本感知路由只做一件简单的事:将你的请求发送到同一个副本。除了临时会话和表访问之外,它还为实现读己之写一致性等体验打开了大门。
今天,我们想向你展示如何使用它、我们是如何构建它的,当然,还有何时该使用它。副本感知路由现已面向 Enterprise 客户开放公共 Beta,即将来到你附近的一个组织。
工作原理
让我们以读己之写一致性这个用例来说明。如果你使用 HTTP,你只需在发送查询时带上一个你选择的请求头。对于 native 连接,你只需覆盖 SNI 值。
1### Replica-aware routing over HTTP23# Write, tagged with a routing key4echo"INSERT INTO events VALUES (now(), 'signup')" | curl \5 -H 'X-ClickHouse-User: default' \6 -H 'X-ClickHouse-Key: <password>' \7 -H 'X-ClickHouse-Replica-Tag: amy_test' \8'https://<host>:8443/' -d @-910# Read it back on the same replica, using the same tag11echo'SELECT count() FROM events' | curl \12 -H 'X-ClickHouse-User: default' \13 -H 'X-ClickHouse-Key: <password>' \14 -H 'X-ClickHouse-Replica-Tag: amy_test' \15'https://<host>:8443/' -d @-1617### Replica-aware routing over native1819# Write with routing key20clickhouse client --user default \21 --password <password> \22 --host <host> \23 --query "INSERT INTO events VALUES (now(), 'signup')" \24 --secure \25 --tls-sni-override jan_key.sticky.<host>2627# Read with routing key28clickhouse client --user default \29 --password <password> \30 --host <host> \31 --query 'SELECT count() FROM events' \32 --secure \33 --tls-sni-override jan_key.sticky.<host>就是这样 🙂 请求携带相同的 X-ClickHouse-Replica-Tag 或 SNI 覆盖值,因此它们会落到同一个副本上,读取能看到写入,即使其他副本仍在追赶复制。使用不同的值,它会独立地进行哈希,并可能落到其他地方。
何时应该使用它?
和任何工具一样,副本感知路由值得为特定任务而使用。有三个场景反复出现。
你正在使用临时表或命名会话。 会话作用域的对象只存在于创建它们的那个副本上。为整个会话复用一个路由键,你的查询就会在同一个副本上执行。
你希望副本缓存保持热状态。 当同一个副本持续服务相同的工作负载时,其本地缓存会保持热状态:文件系统缓存、解压后的数据块、延迟加载的主键和索引、查询缓存。(老实说:我们的分布式缓存是这方面更好的长期答案,但副本内存中仍然有宝贵的缓存。)
读写后的一致性。 在多副本服务上,写入某个副本的数据可能要等到复制追上后才会在其他副本上可见。使用副本感知路由,即使其他副本仍在追赶,你也可以读取到你自己写入的数据。这对于交互式应用以及那些在继续之前验证插入的 ETL 作业来说非常方便。与此相关的是,如果你更改了 schema 且它尚未跨副本同步,使用副本感知路由将确保你插入到新的 schema 中,从而避免错误。
我们是如何构建它的
寻找用于哈希的键
我们的代理层构建在 Istio 和 Envoy 之上。Istio(更准确地说,是 Istio Pilot)管理配置,而 Envoy 是数据平面,即实际移动字节的代理。
我们最初实现粘性路由的方法是使用基于 URL 的子域名。然而,由于我们的证书提供商在证书方面的限制,这种方法无法扩展。经过头脑风暴,我们想到,如果我们运行 L7 代理而不是 L4 代理,Envoy 就可以读取请求本身,比如查询参数,并据此进行路由。L4 代理不行,因为 Envoy 只能看到 TCP 数据包。
第一种方法是通过 session_id 来实现的,但后来放弃了,因为 ClickHouse 在一个会话中只允许同时执行一个查询。这绝对不理想!
作为 session_id 的替代方案,我们使用了一个 ClickHouse 会直接忽略的请求头。我们使用 X-ClickHouse 命名空间 来存放 ClickHouse 特定的设置。客户通过请求头传递一个值 → Envoy 用它进行一致性哈希 → 请求路由到其中一个副本。
你可以自己测试这一点。下面的命令会反复调用你的实例,并打印出服务该请求的副本的主机名。启用基于 HTTP 的粘性路由后,你将始终看到同一个副本。请先确保你的集群中至少有两个副本。如果你只有一个,演示效果就大打折扣了 😉
1whiletrue; do2echo'select hostname()' | curl \3'https://abcdefghij.eu-west-1.aws.clickhouse.cloud:8443' \4 -H 'X-ClickHouse-Replica-Tag: some-string' \5 -H 'X-ClickHouse-User: YOUR-USER' \6 -H 'X-ClickHouse-Key: YOUR-PASSWORD' \7 -d @-8doneNative 支持
但我们不能止步于 HTTP。我们的客户还通过 native 连接进行连接。
回想一下最初的限制:为每个使用粘性路由的实例都配置新证书是无法扩展的。但如果我们能直接复用已有的证书呢?
ClickHouse 客户端提供了一种覆盖服务器名称指示(SNI)的方法。
1clickhouse client --help | grep tls-sni-override2# --tls-sni-override arg Override the SNI host name used for TLS connections如果在 TLS 连接期间,你可以验证全局证书,但使用 SNI 进行路由呢?这正是我们 native 支持背后的想法。
1whiletrue; do2 clickhouse client \3 --host abcdefghij.eu-north-1.aws.clickhouse.cloud \4 --secure \5 --tls-sni-override some_string.sticky.abcdefghij.eu-north-1.aws.clickhouse.cloud \6 --query 'select hostname()'7done--host 是我们要在证书中验证的主机名。--secure 表示此连接应使用 TLS。--tls-sni-override 是我们用于路由的值。客户端将验证区域证书。在这种情况下,我们还会发送 SNI 值,Envoy 随后会对其进行哈希并用于路由。这与最初方法中基于主机名的环形哈希机制相同,只是多了一个新技巧:--tls-sni-override 将我们用于路由的主机名(SNI)与我们用于验证证书的主机名(--host)解耦。
这是在 Golang 中的样子:
1tlsConfig.ServerName = sniOverride2// disable the default check. If we don't disable then our 3// client will try to match some_string.sticky.abcdefghij.eu-north-1.aws.clickhouse.cloud4// which the cert does not cover5tlsConfig.InsecureSkipVerify = true67tlsConfig.VerifyPeerCertificate = func(rawCerts [][]byte, _ [][]*x509.Certificate)error {8// get all the certs9 certs := make([]*x509.Certificate, 0, len(rawCerts))10for _, raw := range rawCerts {11 cert, err := x509.ParseCertificate(raw)12if err != nil {13return err14 }15 certs = append(certs, cert)16 }1718// error if no certs19iflen(certs) == 0 {20return fmt.Errorf("no certificate presented by %s", dialHost)21 }2223// check the --host instead of the SNI value24// pool is for intermediate certs25 opts := x509.VerifyOptions{DNSName: dialHost, Intermediates: x509.NewCertPool()}2627for _, cert := range certs[1:] {28 opts.Intermediates.AddCert(cert)29 }3031// validate the leaf cert, which 32// abcdefghij.eu-north-1.aws.clickhouse.cloud33 _, err := certs[0].Verify(opts)34return err35}现在你有两种方式使用副本感知路由。一种是通过 HTTP 使用请求头,另一种是通过 native 使用 SNI 覆盖。
几点需要记住的事项
一些坦诚的注意事项,以免在生产环境中出现意外。
- 粘性是尽力而为,而非保证。 任何重塑服务的操作都可能破坏路由,使查询落到不同的副本上。这包括升级、重启以及扩容或缩容。当这种情况发生时,一个键可能移动到不同的副本,而你依赖的任何临时表或会话设置都需要重新创建。一个简单的
SELECT hostName()总能告诉你你在哪里。 - 它不是工作负载隔离。 副本路由控制哪个副本处理请求,但该副本仍然服务其他流量。
- 仅限 Enterprise。此功能正向 Enterprise 层级方案推出,可通过您的服务设置页面使用。如果它尚未出现在您的账户中,请随时提交支持工单以提前启用。
TLDR
现在,如果您只想了解要点,请看这里 :) 副本感知路由会将您的查询路由到相同的副本。如果您拥有 Enterprise 层级账户,则可以通过 HTTP 或原生连接访问它,标准 ClickHouse Cloud 账户和 BYOC 均可使用。请记住,粘性是尽力而为的。更多信息请参阅文档。祝您查询愉快!
这篇内容对你有用吗?
反馈只用于改善内容筛选,不等同于收藏