应用层的微服务实例分散在全球范围内的云选择区域。由KongGateway提供支持的API层允许微服务通过简单的REST端点相互通信。
全局负载均衡器在最近的PoP(存在点)拦截用户请求,并将请求转发到地理上离用户最近的微服务实例。
一旦微服务实例收到来自负载均衡器的请求,服务很可能需要从数据库中读取数据或向其写入更改。而这一步可能会成为一个痛苦的瓶颈——如果数据(数据库)远离微服务实例和用户,您将需要解决这个问题。
在本文中,我将选择一些多区域数据库部署选项,并演示如何保持数据库查询的读写延迟较低,而不管用户的位置如何。
所以,如果你还在我的旅途中陪我,那么,就像海盗们常说的那样,“称重锚,起锚!” 这意味着,“拉起锚,让这艘船航行!”
多区域数据库部署选项
多区域数据库部署没有灵丹妙药。这都是关于权衡的。每个选项都提供优势,而其他选项会让您付出代价。
YugabyteDB是我选择的分布式SQL数据库,它支持地理分布式应用程序使用的四种多区域数据库部署选项:
-
单一延伸集群:数据库集群跨多个区域“延伸”。这些地区通常位于相对较近的地方(例如,美国中西部和东部地区)。
-
具有只读副本的单一延伸集群:与前一个选项一样,集群部署在一个地理位置(例如,北美),跨云区域相对较近(例如,美国中西部和东部地区)。但是使用此选项,您可以将只读副本节点添加到遥远的地理位置(例如欧洲和亚洲),以提高读取工作负载的性能。
-
单一地理分区集群:数据库集群分布在多个遥远的地理位置(例如北美、欧洲和亚洲)。每个地理位置都有自己的一组节点,它们部署在一个或多个非常接近的本地区域(例如,美国中西部和东部地区)。根据地理分区列的值,数据会自动固定到特定的节点组。使用此部署选项,您可以在全球范围内实现读取和写入工作负载的低延迟。
-
具有异步复制的多个集群:多个独立集群部署在各个区域。这些区域可以位于相对较近的位置(例如,美国中西部和东部地区)或位于较远的位置(例如,美国东部和亚洲南部地区)。更改在集群之间异步复制。您将在全球范围内实现读取和写入工作负载的低延迟,与之前的选项相同,但您将处理异步交换更改的多个独立集群。
好的,伙计,让我们继续并查看地理信使用例的前三个部署选项。我将跳过第四个,因为它不适合需要单个数据库集群的信使架构。
美国的单一延伸集群
第一个集群跨越美国的三个地区——美国西部、中部和东部。
应用程序/微服务实例和API服务器(图中未显示)在相同的位置以及欧洲西部和亚洲东部地区运行。
美国流量的数据库读/写延迟
假设Blue女士本周在美国爱荷华州工作。她打开地理信使向公司频道发送便条。她的流量将由部署在美国中部地区的微服务实例处理。
在Blue女士可以发送消息之前,USCentral微服务实例必须加载通道的消息历史记录。哪个数据库节点将为该读取请求提供服务?
在我的例子中,美国中部地区被配置为YugabyteDB的首选地区,这意味着来自该地区的数据库节点将处理来自应用层的所有读取请求。将频道的消息历史从美国中央数据库节点加载到同一区域的应用程序实例需要10-15毫秒。这是我的SpringBoot日志的输出,最后一行显示了查询执行时间:
Hibernate:selectmessage0_.country_codeascountry_1_1_,message0_.idasid2_1_,message0_.attachmentasattachme3_1_,message0_.channel_idaschannel_4_1_,message0_.messageasmessage5_1_,message0_.sender_country_codeassender_c6_1_,message0_.sender_idassender_i7_1_,message0_.sent_atassent_at8_1_frommessagemessage0_wheremessage0_.channel_id=?orderbymessage0_.idasc
INFO11744---[-nio-80-exec-10]i.StatisticalLoggingSessionEventListener:SessionMetrics{
1337790nanosecondsspentacquiring1JDBCconnections;
0nanosecondsspentreleasing0JDBCconnections;
1413081nanosecondsspentpreparing1JDBCstatements;
14788369nanosecondsspentexecuting1JDBCstatements;(14ms!)
接下来,当Blue女士将消息发送到通道中时,微服务和数据库之间的延迟将在90毫秒左右。比之前的操作要花更多的时间,因为Hibernate为我的方法调用生成了多个SQL查询JpaRepository.save(message)
(当然可以优化),然后YugabyteDB需要在跨美国西部、中部和东部运行的所有节点上存储消息的副本. 以下是输出和延迟的样子:
Hibernate:selectmessage0_.country_codeascountry_1_1_0_,message0_.idasid2_1_0_,message0_.attachmentasattachme3_1_0_,message0_.channel_idaschannel_4_1_0_,message0_.messageasmessage5_1_0_,message0_.sender_country_codeassender_c6_1_0_,message0_.sender_idassender_i7_1_0_,message0_.sent_atassent_at8_1_0_frommessagemessage0_wheremessage0_.country_code=?andmessage0_.id=?
Hibernate:selectnextval('message_id_seq')
Hibernate:insertintomessage(attachment,channel_id,message,sender_country_code,sender_id,sent_at,country_code,id)values(?,?,?,?,?,?,?,?)
31908nanosecondsspentacquiring1JDBCconnections;
0nanosecondsspentreleasing0JDBCconnections;
461058nanosecondsspentpreparing3JDBCstatements;
91272173nanosecondsspentexecuting3JDBCstatements;(90ms)
亚太地区流量的数据库读/写延迟
请记住,每次多区域数据库部署都会有一些权衡。在当前的数据库部署中,美国流量的读/写延迟较低,但来自其他更远位置的流量较高。让我们看一个例子。
想象一下,Blue女士的同事Red先生收到来自Blue女士的最新消息的推送通知。由于Red先生在台湾出差,他的流量将由部署在该岛上的应用程序实例处理。
但是台湾附近没有部署数据库节点,所以微服务实例要查询美国运行的数据库节点。这就是为什么在Red先生看到Blue女士的消息之前平均需要165毫秒来加载整个频道的历史记录:
Hibernate:selectmessage0_.country_codeascountry_1_1_,message0_.idasid2_1_,message0_.attachmentasattachme3_1_,message0_.channel_idaschannel_4_1_,message0_.messageasmessage5_1_,message0_.sender_country_codeassender_c6_1_,message0_.sender_idassender_i7_1_,message0_.sent_atassent_at8_1_frommessagemessage0_wheremessage0_.channel_id=?orderbymessage0_.idasc
[p-nio-80-exec-8]i.StatisticalLoggingSessionEventListener:SessionMetrics{
153152267nanosecondsspentacquiring1JDBCconnections;
0nanosecondsspentreleasing0JDBCconnections;
153217915nanosecondsspentpreparing1JDBCstatements;
165798894nanosecondsspentexecuting1JDBCstatements;(165ms)
当Red先生在同一频道响应Blue女士时,Hibernate大约需要450毫秒来准备、发送和存储消息到数据库中。好吧,由于物理定律,带有消息的数据包必须从台湾穿越太平洋,然后消息副本必须存储在美国西部、中部和东部:
Hibernate:selectmessage0_.country_codeascountry_1_1_0_,message0_.idasid2_1_0_,message0_.attachmentasattachme3_1_0_,message0_.channel_idaschannel_4_1_0_,message0_.messageasmessage5_1_0_,message0_.sender_country_codeassender_c6_1_0_,message0_.sender_idassender_i7_1_0_,message0_.sent_atassent_at8_1_0_frommessagemessage0_wheremessage0_.country_code=?andmessage0_.id=?
selectnextval('message_id_seq')
insertintomessage(attachment,channel_id,message,sender_country_code,sender_id,sent_at,country_code,id)values(?,?,?,?,?,?,?,?)
i.StatisticalLoggingSessionEventListener:SessionMetrics
23488nanosecondsspentacquiring1JDBCconnections;
0nanosecondsspentreleasing0JDBCconnections;
137247nanosecondsspentpreparing3JDBCstatements;
454281135nanosecondsspentexecuting3JDBCstatements(450ms);
现在,对于不针对该地区用户的应用程序来说,基于亚太地区的流量的高延迟可能不是什么大问题,但在我的情况下并非如此。我的地理信使必须在全球范围内顺利运行。让我们对抗这种高延迟,伙计!我们将从阅读开始!
在远程位置部署只读副本
改善YugabyteDB中读取工作负载延迟的最直接方法是在远程位置部署只读副本节点。这是一项可以在实时集群上执行的纯操作任务。
因此,我将一个副本节点“附加”到我的基于美国的实时数据库集群,并且该副本被放置在为Mr.Red提供请求的微服务实例附近的亚洲东部地区。
然后我请求台湾的应用程序实例使用该副本节点进行数据库查询。Mr.Red的频道历史预加载延迟时间从165毫秒降至10-15毫秒!这与在美国的Blue女士一样快。
Hibernate:selectmessage0_.country_codeascountry_1_1_,message0_.idasid2_1_,message0_.attachmentasattachme3_1_,message0_.channel_idaschannel_4_1_,message0_.messageasmessage5_1_,message0_.sender_country_codeassender_c6_1_,message0_.sender_idassender_i7_1_,message0_.sent_atassent_at8_1_frommessagemessage0_wheremessage0_.channel_id=?orderbymessage0_.idasc
i.StatisticalLoggingSessionEventListener:SessionMetrics
1210615nanosecondsspentacquiring1JDBCconnections;
0nanosecondsspentreleasing0JDBCconnections;
1255989nanosecondsspentpreparing1JDBCstatements;
12772870nanosecondsspentexecuting1JDBCstatements;(12mseconds)
因此,使用只读副本,我的地理信使可以以低延迟服务读取请求,而不管用户的位置如何!
但现在庆祝还为时过早。写入仍然太慢。
想象一下,Mr.Red向频道发送了另一条消息。来自台湾的微服务实例将要求副本节点执行查询。并且副本会将Hibernate生成的几乎所有请求转发到存储主要记录副本的美国节点。因此,亚太地区流量的延迟仍可能高达640毫秒:
Hibernate:settransactionreadwrite;
Hibernate:selectmessage0_.country_codeascountry_1_1_0_,message0_.idasid2_1_0_,message0_.attachmentasattachme3_1_0_,message0_.channel_idaschannel_4_1_0_,message0_.messageasmessage5_1_0_,message0_.sender_country_codeassender_c6_1_0_,message0_.sender_idassender_i7_1_0_,message0_.sent_atassent_at8_1_0_frommessagemessage0_wheremessage0_.country_code=?andmessage0_.id=?
Hibernate:selectnextval('message_id_seq')
Hibernate:insertintomessage(attachment,channel_id,message,sender_country_code,sender_id,sent_at,country_code,id)values(?,?,?,?,?,?,?,?)
i.StatisticalLoggingSessionEventListener:SessionMetrics
23215nanosecondsspentacquiring1JDBCconnections;
0nanosecondsspentreleasing0JDBCconnections;
141888nanosecondsspentpreparing4JDBCstatements;
640199316nanosecondsspentexecuting4JDBCstatements;(640ms)
最后,伙计,让我们一劳永逸地解决这个问题!
切换到全球地理分区集群
全局地理分区集群可以跨远程位置提供快速读取和写入,但需要您在数据库模式中引入特殊的地理分区列。根据列的值,数据库将自动决定一条记录属于哪个地理区域的哪个数据库节点。
数据库架构更改
简而言之,我的表(例如Messageone)定义了country_code
列:
CREATETABLEMessage(
idintegerNOTNULLDEFAULTnextval('message_id_seq'),
channel_idinteger,
sender_idintegerNOTNULL,
messagetextNOTNULL,
attachmentbooleanNOTNULLDEFAULTFALSE,
sent_atTIMESTAMPNOTNULLDEFAULTNOW(),
country_codevarchar(3)NOTNULL
)PARTITIONBYLIST(country_code);
根据该列的值,可以将记录放置在以下数据库分区之一中:
CREATETABLEMessage_USA
PARTITIONOFMessage
(id,channel_id,sender_id,message,sent_at,country_code,sender_country_code,
PRIMARYKEY(id,country_code))
FORVALUESIN('USA')TABLESPACEus_central1_ts;
CREATETABLEMessage_EU
PARTITIONOFMessage
(id,channel_id,sender_id,message,sent_at,country_code,sender_country_code,
PRIMARYKEY(id,country_code))
FORVALUESIN('DEU')TABLESPACEeurope_west3_ts;
CREATETABLEMessage_APAC
PARTITIONOFMessage
(id,channel_id,sender_id,message,sent_at,country_code,sender_country_code,
PRIMARYKEY(id,country_code))
FORVALUESIN('TWN')TABLESPACEasia_east1_ts;
每个分区都映射到其中一个表空间:
CREATETABLESPACEus_central1_tsWITH(
replica_placement='{"num_replicas":1,"placement_blocks":
[{"cloud":"gcp","region":"us-central1","zone":"us-central1-b","min_num_replicas":1}]}'
);
CREATETABLESPACEeurope_west3_tsWITH(
replica_placement='{"num_replicas":1,"placement_blocks":
[{"cloud":"gcp","region":"europe-west3","zone":"europe-west3-b","min_num_replicas":1}]}'
);
CREATETABLESPACEasia_east1_tsWITH(
replica_placement='{"num_replicas":1,"placement_blocks":
[{"cloud":"gcp","region":"asia-east1","zone":"asia-east1-b","min_num_replicas":1}]}'
);
每个表空间都属于来自特定地理位置的一组数据库节点。例如,所有国家代码为台湾country_code=TWN
(如果您想了解地理分区的详细信息,请查看以下文章。
跨大陆的低读/写延迟
因此,我在美国中部、欧洲西部和亚洲东部地区部署了一个三节点地理分区集群。
现在,让我们确保Red先生的请求的读取延迟保持不变。与只读副本部署一样,加载通道的消息历史记录仍需要5-15毫秒(对于属于APAC区域的通道和消息):
Hibernate:selectmessage0_.country_codeascountry_1_1_,message0_.idasid2_1_,message0_.attachmentasattachme3_1_,message0_.channel_idaschannel_4_1_,message0_.messageasmessage5_1_,message0_.sender_country_codeassender_c6_1_,message0_.sender_idassender_i7_1_,message0_.sent_atassent_at8_1_frommessagemessage0_wheremessage0_.channel_id=?andmessage0_.country_code=?orderbymessage0_.idasc
i.StatisticalLoggingSessionEventListener:SessionMetrics
1516450nanosecondsspentacquiring1JDBCconnections;
0nanosecondsspentreleasing0JDBCconnections;
1640860nanosecondsspentpreparing1JDBCstatements;
7495719nanosecondsspentexecuting1JDBCstatements;(7ms)
并且……请鼓掌……当Red先生向APAC频道发布消息时,写入延迟已从平均400-650毫秒降至6毫秒!
Hibernate:selectmessage0_.country_codeascountry_1_1_0_,message0_.idasid2_1_0_,message0_.attachmentasattachme3_1_0_,message0_.channel_idaschannel_4_1_0_,message0_.messageasmessage5_1_0_,message0_.sender_country_codeassender_c6_1_0_,message0_.sender_idassender_i7_1_0_,message0_.sent_atassent_at8_1_0_frommessagemessage0_wheremessage0_.country_code=?andmessage0_.id=?
Hibernate:selectnextval('message_id_seq')
Hibernate:insertintomessage(attachment,channel_id,message,sender_country_code,sender_id,sent_at,country_code,id)values(?,?,?,?,?,?,?,?)
1123280nanosecondsspentacquiring1JDBCconnections;
0nanosecondsspentreleasing0JDBCconnections;
123249nanosecondsspentpreparing3JDBCstatements;
6597471nanosecondsspentexecuting3JDBCstatements;(6ms)
任务完成,伙计!现在,我的geo-messenger的数据库可以跨国家和大洲以低延迟提供读取和写入服务。我只需要告诉我的信使在哪里部署微服务实例和数据库节点。
跨大陆查询的案例
现在快速评论一下为什么我跳过了具有多个独立YugabyteDB集群的数据库部署选项。
对我来说,为Messenger提供一个数据库很重要,这样:
- 用户可以加入属于任何地区的讨论频道。
- 任何位置的微服务实例都可以访问任何位置的数据。
例如,如果Red先生加入属于美国节点的讨论频道country_code=’USA'
(-基于对应物。此操作的延迟由三个SQL请求组成,大约为165毫秒:
Hibernate:selectmessage0_.country_codeascountry_1_1_0_,message0_.idasid2_1_0_,message0_.attachmentasattachme3_1_0_,message0_.channel_idaschannel_4_1_0_,message0_.messageasmessage5_1_0_,message0_.sender_country_codeassender_c6_1_0_,message0_.sender_idassender_i7_1_0_,message0_.sent_atassent_at8_1_0_frommessagemessage0_wheremessage0_.country_code=?andmessage0_.id=?
Hibernate:selectnextval('message_id_seq')
Hibernate:insertintomessage(attachment,channel_id,message,sender_country_code,sender_id,sent_at,country_code,id)values(?,?,?,?,?,?,?,?)
i.StatisticalLoggingSessionEventListener:SessionMetrics
1310550nanosecondsspentacquiring1JDBCconnections;
0nanosecondsspentreleasing0JDBCconnections;
159080nanosecondsspentpreparing3JDBCstatements;
164660288nanosecondsspentexecuting3JDBCstatements;(164ms)
165毫秒无疑高于6毫秒(Red先生将消息发布到基于亚太地区的本地频道时的延迟),但这里重要的是能够在必要时通过单个数据库连接发出跨大陆请求。另外,正如执行计划所示,在Hibernate级别有很大的优化空间。目前,Hibernate将我的JpaRepository.save(message)
调用转换为3条JDBC语句。这是可以进一步优化的,以将跨大陆请求的延迟从165毫秒降低到低得多的值。
以上就是今天(2022年11月9日19:35:58)小编为大家整理的icode9【精华分享大全】,希望对大家有所帮助。
原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/292873.html