转载

Go 在 Grab 地理服务中的实践

我叫张志印,来自 Grab,这次主要跟大家分享一下我们在地理服务中的 Golang 实践。本次分享大纲:

  • What's Grab

  • 一个典型的派单流程

  • 一个核心地理服务系统演进历程

  • Why go

  • 压测与调优

  • QA

Grab 是东南亚最大的出行平台,我们不只是做出行,还在做支付以及送餐业务。能做出行的一定能做送餐,做送餐的也可以做出行,这是很自然的一个选择。出行就是连接供需,一个是需求端,一个是供给端,这两端连接起来出行生意就做起来了。

一个典型的派单流程

Go 在 Grab 地理服务中的实践

从派单流程说起,一个典型的派单流程是什么。首先会查找附近的司机,然后我们有过滤和分配策略,包括 ETA 和 ETD 的计算,以及各种 Allocation 策略, 我们基本上也是很复杂的 RPC 调用,然后锁定司机,通知司机,司机收到并确认就成单了。对于地理服务来说,就是如何找到附近的司机,大家可以看到,这是一个关键路径,如果这个 Nearby 服务 down 了,整个派单流程就全部 down 了。

如何构建一个高可用的 Nearby 服务呢?

这个看起来很简单,就是找附近的司机。那么,首先来看要解决什么问题?第一个就是我们有大量的司机做秒级的实时上传,并且司机的坐标是不断动态变化,如从A区到B区。找附近的司机,坐标是有时效性的,你不能找到一个小时之前的司机。可用性也说了,要求就是不能宕机,还有性能要好。

简单聊一下 Nearby 服务演进的过程

我们的目标是做一个类似于特斯拉一样的系统,性能好,续航时间又长。

但是实际上一开始不可能造出特斯拉,从0到1,最开始做的时候都比较简陋。跟大家一样,如果你做 Nearby 的服务要用什么呢?要成熟的东西,比如 PostGIS、MongoDB 等这些都可以用,但是这个场景其实不适合。我们有大量的并发写,这里使用的是 PostGIS,而 PostGIS 是基于 R-Tree,那 R-Tree 在这个场景下有什么问题呢?我们看R-Tree的结构图:

Go 在 Grab 地理服务中的实践

当写入一个新结点,从根结点遍历直到查找到相应的叶子结点, 如果有可写空间就写进去,如果已满, 就回溯,不断地向上分裂, 直到根结点。这个时间复杂度比较高,想一下,把整个地理空间搞到一个  R-Tree 里面,而且要加锁,这个时候写性能会非常差。另一个问题是流量高峰时,造成服务经常崩掉,这样的可用性是完全不可接受的。

有节操的程序猿都是在不断地造轮子,如果当前系统不好用,我们造一个新的轮子好了。在我们内部一次 Hackathon 的时候,一个澳洲大哥说我们造个轮子吧,就是 Astrolabe,中文名称是星盘。

Go 在 Grab 地理服务中的实践

 Astrolabe 完全是用 Golang 实现的 Nearby 服务。

引擎方面,我们把空间索引从 R-Tree 换 Geohash,Geohash 有一个特点,就是地理位置相近的地方,前缀是相似的,另外就是把二字符串维坐标转成一维数据,点击上图链接大家会看到返回的坐标是小南国花园酒店。因为前缀相似的一维字符串表示的地理空间是相近的,我们可以把二维空间的搜索转成 Range 查询,相对来讲,Nearby 的邻近搜索写性能会好一些。  

Go 在 Grab 地理服务中的实践

我们看到"写"就非常简单了,先计算 Geohash,然后把 Geohash 值存储到zset 中,"读"也比较简单,先找到查询半径的最小外界矩形的左下、右上两个点,这个时候可以基于 range 查询粗略筛选一批司机,对这些司机再计算一下距目标点的距离,就可以精确的找到附近的司机。

最后解决了哪些问题呢?写性能确实提高了,可用性也更好了,主要是什么原因? 一个是 Redis 很少宕机;另外就是我们用 Golang 写的,做出来的系统也很稳定,实际上在当时那个时间结点, Golang 逐渐成为 Grab 后端首选语言。



但 Astrolabe 也有一些问题,主要是 Geohash 会有跨纬度前缀跳变问题。

Go 在 Grab 地理服务中的实践

国内还好,中国大部是在130、132,不是那么明显,但是东南亚是132、310,在赤道附近会产生突变。

Astrolabe-主要问题

  • 准确度低。赤道附近会查找到司机不全,准确度有一些问题。

  • 还有一个问题就是 CPU 和 IO 比较高,内存非常低。IO 比较高一个很大原因是有大量的网络 IO 在 Astrolabe 与 Redis 之间。

  • 很难做横向扩展。你要做 Range 查询,必须把一个城市的所有司机分配在一个 SortedSet 里面,但司机人数在不断快速增长,势必会成为一个瓶颈。

现在的问题就是发现不能横向扩展了,但是业务增长非常快速,如果我们不做更新换代,只有死路一条。 我们内部就说那再造一个轮子吧,我们又搞了一个更高级的定位系统,叫 Sextant,中文名称是六分仪, 这个也是航海用的定位系统,接下来分享一下Sextant 这个系统。

回顾一下我们要解决的问题

Go 在 Grab 地理服务中的实践

业务高速增长,上百万的司机在做秒级实时地理位置上传,都会打到你空间引擎里面。可用性要求越来越高,如果因为不能查找到附近司机而导致订单不能完成,这是绝对不可接受的 。

Go 在 Grab 地理服务中的实践

总结一下,Astrolabe 最大的问题就是不能扩容,一个系统如果是可持续、可增长的,必须要支持扩容。我们首先从扩容谈起。大家能想到的应该就是做 Sharding,就是分片,怎么做分片呢,道理很简单,基本上就是找一个空间映射引,把司机按坐标映射到一个相应的 Shard 上,再把 Shard 分到服务器节点上,最终实现司机在各个服务器节点上的分布。Shard 主要是存储在服务器本地内存,内存是比较廉价的,可以快速增加内存容量,如果数据存储在本地就可以大幅减少网络 IO,这样成本也会减少很多。另外作为高可用,不能有单点,至少要在两个结点上做一个副本集。   

这个空间索引怎么做呢?

Go 在 Grab 地理服务中的实践

一般想法就是做 Grid,把地球的平面划成格子,每个格子由经纬度以及 Resolution 生成,Resolution 描述的就是格子的尺寸,不同的 Resolution 会生成不同尺寸的格子。我们现在做的是分层的结构,先把地球的平面做一个大的划分,我们叫做 shard,这里面包含有很多个小格子叫做 cell,对于每个司机的位置你都可以计算出在哪个 shard 以及哪个 cell 里面。

再来看下内存数据结构设计

Go 在 Grab 地理服务中的实践

首先采用树或 Geohash 结构, 你没有办法做一个 sharding,更新起来可能并发性能也有问题。这里我们把 shard 分成 cell,就是考虑到并发问题,在每一个cell 里面有一个 LRU 队列,并且每个 LRU 队列长度是不一样的,图中的红色部分描述了不同长度队列中司机数量是不均衡的。另外我们还需要知道每个司机上一次所在坐标的 cell 是什么。  

"写"就很简单了,根据经纬度计算出一个司机在哪个 Shard 里面,然后找出相应的 cell。

Go 在 Grab 地理服务中的实践

有两个问题,这个司机上一次是在哪一个 cell 里面呢?大家可以相像一下,如果司机从 cell0 移动到 cell1,他的坐标位置在不断地变化吧?如果上一个坐标位置是在 cell0,首先需要先把这个司机从 cell0 里面删除掉,然后加到 cell1 的LRU 队列里面。如果该司机上次在 cell0,这次也在 cell0,直接更新相应的LRU 队列就可以了。

另外就是"读",如何做一个 Nearby Search?一次打车需求会触发一次Nearby 的查询,我们会发现这个 Nearby 查询半径的最小外接矩形跟四个 shard 相交。先找到四个 shard, 然后对每个 shard 作并行 Nearby 计算。那么具体在 Shard 里面,我们怎么做呢?就是做了广度优先搜索,从中心向外查找,过滤符合条件的司机,如果找到指定数量的司机就返回,返回之后在外层  Shard 聚合并取 Top N,这是非常简单的。我们的理念是能够找到简单有效的方法就不要搞太复杂。

还有一个问题,就是一个司机把它的坐标位置更新到 cell 里面了,无论哪些原因,如网络中断或 App  关机等,可能会产生垃圾数据,这些需要定期的清洗。

Go 在 Grab 地理服务中的实践

另外 shard 如何映射到服务器节点?

传统有很多搞法,这里我们使用的是一致性哈希, 经典的 Ketema Algorithm,并且我们还支持 replica。但是在"写"的时候有个问题,因为每一个 Shard 里面司机数量不均衡,简单按 Shard 哈希造成司机数量在各服务器节点极度不均衡, 如左下图所示,显示的是司机在各服务器节点上的分布热图。

Go 在 Grab 地理服务中的实践

热图越靠近顶部表示司机数量越多,越靠近底部表示司机数量越少;颜色越深表示当前相似司机数量的服务器节点越多,大家可以看一下差距是非常大的。这个会带来什么问题呢?就是你的 CPU 是不均匀的,有的 CPU 很忙,有的 CPU 很空闲,这个如何解决呢?我们做了一个 ShardTable,就是针对大的 Shard 进行定制化, 以每个 Shard 中的司机数量作为权重进行服务器节点分配,使司机在节点间分布更均衡,一个简单的小算法就可以实现。

如图所示

Go 在 Grab 地理服务中的实践

反映的结果就是整体集群的 CPU,高的更低了,低的趋于更高了,最终达到了CPU 资源利用率的均衡。

同时,一致性哈希仍然是有益的补充,如果当前 Shard 配置了 ShardTable,就用所配置的结点,如果没有配置,仍然使用一致性哈希。

为什么要保留一致性哈希呢?大家都知道司机位置在不断变化,有可能产生新的 ShardID,而且你的 Shard 数量不可预知的,所以对一些小的 Shard,临时产生的 Shard 结点,我们走的还是一致性哈希分配策略。

最后的 Shard 分布就是说,比如雅加达是东南亚最大的城市,也是司机数量最多的城市,它现在是独享一组结点。这个配置非常灵活,可以为雅加达配置多个节点。

另外,多个 Shard 也可以共用一个节点。如新加坡和马尼拉这样的城市就是几个 Shard 在同一个结点上。

如何配置呢?大家可以想象一下,你这边有好多包裹,首先会按包裹大小排序,先把大的包裹放在容器里,再用小的包裹填充这个容器,这样容器的利用率是最高的。 

接下来还有一个问题,就是你可以为大的 Shard 独立分配结点,但是每个服务器节点的吞吐是有上限的,当然你可以做一次硬件升级,但是 配置再高 的硬件也会有瓶颈,这个时候怎么办?

Go 在 Grab 地理服务中的实践

我们要把大的 Shard 再分裂,分裂成小的 Shard,再把小的 Shard 去做一个分配,简单来讲就是这些 Shard 可能从4个分成16个,如上图所示。   

另外一个就是还有读的问题

Go 在 Grab 地理服务中的实践

大家知道我们是一个供需的平台,供给和需求,特别是需求是最不可控的,你很难预测需求在哪儿。比如新加坡地铁如果宕了,我们系统压力就非常之大,这时候就需要对相应的 Shard 进行扩容来平衡它的流量,简单讲就是加节点。

接下来说一下服务治理怎么做

首先聊一下分布式怎么做,大家可以想一下,我们这个系统如果让大家做,怎么做?就是你做一个有中心还是无中心的服务?你的系统是强一致性还是弱一致性?你更倾向于选择 AP 还是 CP?

对我们来说一致性这块要求没有那么高,业务上要求不高,只是找司机,只是其中一个很基础流程,我们还有其它很多个 RPC 调用。另一方面,从工程上来讲,一致性其实是有时间窗口的,不能说我这一天是一致的,我们是一个百万量级司机秒级实时上传坐标数据,在秒级做强一致性,这个服务是跑不起来的。

再者,我们处于关键路径,核心诉求就是不能 Down。大家知道,对外依赖的越多,崩掉的可能性越大。在设计时, 我们就考虑去掉一切可以去掉的依赖,比如说服务发现 ,在 Grab 内部都是基于 ETCD,但是在 Sextant,我们做了一个完全去中心化的系统,主要是基于 Gossip 协议,我们用的是 serf。

我们看一下 serf 有什么特性。 首先是 Gossip,Gossip 是什么呢?

在信息工程领域, Gossip 是反熵。 那么"熵"是什么?就是杂乱无章,就是混沌状态。反熵就是如何从杂乱无章、混沌的状态取得一致,另外 Cassandra 也是 Gossip 的经典应用。

Go 在 Grab 地理服务中的实践  

另外一个什么叫 Membership,简单来讲就是找节点,最开始有一个种子节点,大家都知道这个种子节点,每个节点与这个种子节点做一次信息交换,就可以拿到当前整个网络拓扑,这简单来讲就是一个服务发现。

Go 在 Grab 地理服务中的实践

但是更重要的是故障检测,特别是大集群时,故障是常态。

故障检测很重要的点是正确识别故障,如果我是非故障你把我识别为故障了,这个就有一个误报率的问题,要准确快速的识别出来是真故障还是假故障,识别出来要快速摘除,所以说不只是服务发现,更重要的是如何保证可靠性。试想如果每个节点拿到的 server list 是不断抖动的,整体的服务没办法做到一致性,这个服务麻烦就大了。

Go 在 Grab 地理服务中的实践

对于 Serf 来讲,主要基于 SWIM 协议,中文来讲就是可扩展、弱一致性、感染型进程组成员协议。

Go 在 Grab 地理服务中的实践

可扩展指的是什么?就是要有扩展性。传统的 membership 一般基于 heart-beating 机制,比如说 PING 10个节点,一秒钟要 PING 一百次,这个还好。如果有1000个节点,每秒钟要 PING 一百万次,系统资源都消耗在维护一致性上,这个时候系统基本不可用了。但是 serf 可以做到快速收敛,如图所示,一万个节点时,系统状态快速达到一致,大家可以看到是秒级。

另外是 Weakly,我不是强一致性,我是弱一致性的保证,某一时刻不同结点看到不同系统的状态,但是一定时间内,刚才说的一致性是有时间窗口的,serf是大概一秒钟之内就可以收敛到一致状态。

Infection-style, 就是 Gossip,每个结点可以通过 Gossip 其它结点的信息而获得全局信息。还有就是 membership,这个就是讲我们如何找到彼此, 就是信息传播。  

Go 在 Grab 地理服务中的实践

SWIM 分了两部分,一个是如何做故障检测,另外一个才是传播,故障检测在这个协议里面是非常重要的关注点,我们把它分解成下面两个图来说明如何做故障检测。

Go 在 Grab 地理服务中的实践

来看下 N0 如何对本地成员列表做故障检测。假如 N0 需要检测 N1 是否正常,N1 先发个 PING 消息给 N1,但 N1 没有回复,这个时候 N0 就随机找几个节点,如找 N2 或者 N3,你帮我 ping 下 N1 吧,我找不到它了。N3 说好的,然后就 ping 一下 N0,把它 ping 通了,N3 会说 N1 还活着呢,可能刚才吃饭去了。这是一个间接的探测,什么意思呢?如果我第一次直接 PING 没有成功,会随机找几个节点帮忙 PING 目标节点,如果至少有一个节点告诉我目标节点 PING 成功,我就认为目标节点仍然活着。

Go 在 Grab 地理服务中的实践

另一个就是如果随机发了几个消息出去,大家都告诉我说它(目标节点)死了,我们就认为它吃饭不想回来了。但是实际上还有一些更好的建议,比如大家都认为一个节点死了的时候不标记为死亡,而是标记为疑似死亡。

serf 中的做法是标记为疑似死亡后,仍然会向这些节点发送全状态同步信息。这时候 N1 收到了自己被标记为疑似死亡的消息,就会反驳说:我没死呢,我刚才吃饭付钱去了。然后其它节点收到这个消息后,再把 N1 节点标记为正常。 这个反驳的机会很重要,比如某个进程或者节点在做 CPU 运算或者 IO Block,可能没顾得上回你的消息,但并没有真正死亡不可用,这样可以大幅减少 server list 抖动的机会。

SWIM 里如何做信息传播呢?为了减少整个网络带宽消耗,故障检测时会稍带发送 JOINED、FAILED两 个消息。在 serf 里专门还有一个 Gossip 层来做传播。

Go 在 Grab 地理服务中的实践

大家有兴趣的话,可以关注下这两个 paper。

其中,Lifeguard 是 serf 中的新 Feature 主要讲如何减少故障误报率以及更快收敛。

Go 在 Grab 地理服务中的实践

总结一下,从 CAP 来看我们是一个 AP 系统,也是一个去中心化的系统。

服务治理另一方面就是如何保证可用性,这个其实也是我们的一个终极目的。

关键路径前面已经提了多次,最主要的就是我们不能 down。

可用性里面还有一条就是你可以 Down,但是你可以快速恢复。

另外就是你可以部分可用或者说局部可用,区域性不可用不能影响全局,从业务层面来讲就是你不能因为雅加达那边司机数量有一个热点,然后你把马尼拉的服务给打死了,把整个的集群拖死了,这个不允许的。还有就是做容错,你不能说我部署的时候,重启结点导致相应的 Shard 不能用了,这个也是不允许的。

另外就是怎么做过载保护,如果一个系统 SLA 定义是1000并发时可以做到100毫秒延时,但给我打过来2000个并发,这个时候可能保证不了100毫秒延时,也可能把服务给打死。过载保护,就是超出吞吐能力范围的请求会给丢弃掉。另外就是快速失败,我们的一个观点是说如果一个结点死了就让它赶快死去,不要给他重试的机会,重试是万恶之源,关键时刻会把你的系统打的起不来,应该及早摘除并告警。

我们说 No downtime,是指整体集群不允许 Down,而不是说你的节点不能 Down。恰恰相反 Down 反而是常态,如何处理 Down 才是关键。基于 serf 为我们提供了高可靠性保证,可以快速摘除坏掉的节点。另外我们的基础设施都在 AWS 上, AWS 的自动扩容机制也可以快速上线新的节点。

另外就是你要冗余,这里主要基于副本集。冗余度越高,消耗的资源越多。如果你使用2个节点就可以达到5个9的可用性,非要搞成10个节点,边际效益会越来越小,这就需要做个 trade off,我们这里默认为2个副本。

多副本写的一致性如何保证?前面一直在强调我们是弱一致性的系统,这里可以设置一致性 level,比如写成功50%即返回。   另外读的时候怎么做呢?我们有两个读的模式,其中之一是 Fanout,就是扇出。Demand 会将 Nearby 查询发到当前 Shard 所在的两个结点上,哪一个结点返回快,就用哪一个,有个好处是可以充分利用系统资源并保证整体系统性能最优。   

Go 在 Grab 地理服务中的实践

另外一个读模式就是刚才讲的 Round-robin,这里要强调的是,一个 shard 被分配到哪个节点上是动态的,无论是数量还是具体哪些节点,所以我们支持一个动态的 Round-robin。

快速恢复,传统的解决方案就是要可以了,定时存储快照,重启时按快照重建空间索引,整体的系统就恢复了。

对于局部可用,我们刚才也说到,分几种:

其一,某一个节点宕掉基于 serf 可以快速摘除。基于 AWS 的自动扩容机制会触发增加新的节点。

另一种,如果某 Shard 高并发读写导致相应节点负载高,这也不会影响到其它处于不同节点 shard 上的读写。

日常部署时,会将"读"切到 fanout 模式,这样至少有一个节点可用。

过载保护这一块,其实有大量基础库,我们是基于 circuit breaker 做熔断,你对并发量、超时都可以在 circuit breaker做一个熔断措施出来,超过阈值可以快速在 SDK 侧断掉与服务的通信。

在服务侧就是 rate limitor,可以基于 Golang 的 Buffered Channel 做一个简单的限流器,另外我们内部还有一个叫 Quotas 的服务帮你做限流。

快速失败其实还是基于 Serf 做到快速的故障检测,快速摘除。

Go 在 Grab 地理服务中的实践

Sextant 有一个重要特性,我们叫流量转发或者请求转发。哪个 Shard 分到哪一个节点上,对于你的用户来讲,如果让他维护这些状态,这个是不人道的,所以我们要提供的是一个对外无状态的服务。

对于"写"来说,先将请求打到 LB 这个负载均衡器,LB 会随机选择一个节点如 Shard_1 所在节点。如果当前更新的坐标位置位于 Shard_2,Sextant 会发现当前节点并没有 Shard_2,但因为每个 Sextant 节点都有全局的网络拓扑,它会转发这个写请求到 Shard_2 所在的两个节点并执行写操作。

对于"读"来讲,LB 可能会将 Nearby 查询请求打到 Shard_5 所在的节点上,Sextant 同样会发现这个 Nearby 主要发生在 Shard_3、Shard_4,它便会转发Nearby 查询到相应的4个节点(假设是 fanout 模式)并在相应的 Shard 中做 Nearby 查找。   

这个是 Overall 的架构图

Go 在 Grab 地理服务中的实践

整体的系统可以分为两层,一个是有状态,一个是无状态,整体是用Serf串起来。

对于 Proxy 这种节点的话,就是无状态,可以任意做机器的扩充来提高吞吐。

Orchestration 这个是编排层,编排层有大量的路由策略,包括 shardTable 及一致性哈希的补偿机制。

引擎层主要是各种空间索引实现,实际上我们抽象出多个层以方便灵活扩展。

Go 在 Grab 地理服务中的实践

这是Shard与节点关系的抽象表示,其中 GeoIndex 就是指空间索引。

我们有多个 Shard,每一个 Shard 可以有一组 replica set(副本集),每个replica被分配在不同的节点上,每个节点可以有多个 Shard。

现在来看,我们做到了什么

我们的业务增长非常快,百万级司机实时更新他们的坐标数据,我们要求快速查到附近的司机,最重要的是我们要保证高可用,要保证扩展性,基本上来看都做到了。

我们提供了一个去中心化的高可靠的系统, 并且在关键路径不依赖于任何第三方的服务,总体的是自洽的。

我们支持扩容,以及可以做 Reblance,比如某个 Shard 耗时长了,我们可以加一个新的节点;或者某个节点上负载高了,我们可以把一些 Shard 平滑迁移到另外一个节点,来做一个流量再平衡。

最重要的是没有 Down time,我们这个系统每天流量非常大,支持几十亿甚至百亿是没有问题的,上线这一年多以来,我们持续的做发布,没有出现一次Downtime,更重要的是说我们没有出现一次 对派单流程的不可用。

性能也是比较好的,有更低的读写 Latency,总体说来说就是支撑了业务高速增长,我们毕竟还是要做外卖嘛。

另外我们哪里可以做得更好?

是不是还可以做 Shard 得更好呢?比如说现在做 Shard 分裂的时候,现在这个架构不支持做平滑的分裂,还需要重启。

另外,当前这个空间索引不是最优的,如果说 cell 分布正好在查询半径的最小外接矩形的角上,可能需要遍历整个 Shard 空间。

还有,我们是不是可以支持更多的引擎呢?

Why Go

上面跟大家分享了 Nearby 这个核心地理服务演进历程,接下来说一下为什么要用 Go?

也很简单,我们业务场景是什么? 大量的几何计算 ,大家可以想到做地理空间搜索免不了几何计算,另外就是 网络吞吐非常高 ,实时上传坐标数据,我们发现这是一个 CPU 和 I、O 都密集的系统。

对于 Golang, 基于 goroutine、channel,并行处理非常出色 ,比如对于 http request,一般会分配一个独立的 goroutine 去处理, 这样如果一旦 panic的话不至于整个进程 down 掉,这样便会有很多 goroutine 给造出来。

还有原生的 基于 epoll,对高并发支持非常好 ,最重要的就是它的 工程化能力非常强悍 ,可以方便的执行 test、profilling 等。另外就是它周边的生态比较好,比如说 Serf 这样一个框架,当然还有很多。

最后就是 简洁、易上手 ,这可能是大多数人的想法,对于我本身来讲,基本上是到 Grab 才写 Golang 的,我们 Grab 对语言不挑食,对工程师原来做什么也不挑食,但是进来 Grab 后,我们基本上都是写 Golang,这样看来好像 Grab 还是挑食的。

安利下 Go 在 Grab 怎么用的,这个不可避免涉及到研发文化。除了北京Office,Grab 总部在新加坡,西雅图也有办公室,整体更偏向于硅谷那边。

我们新人来的时候先搞一个 Golang Camp,参加 Golang 新人训练营,搞一堆case 让你去做并熟悉一下我们的上线流程。

大家可能听过去年 Gopher 我们同事的分享,Grab 使用一个统一的 Repo,你不仅可以从 wiki 里看到一个系统的整体架构是什么,也可以去代码层面去看具体如何实现的,可以学到很多东西,如果只看系统框图只能得到一个感性认识。

另外,我们积累了大量的基础库及框架。例如我们内部有个grab-kit的框架,类似于 go-kit ,但不一样的是集成了我们内部的公用库,一个比较好的特性是支持自动代码生成,非常方便,你可以1分钟内搭建一个可运行的系统出来,解放了工程师的生产力。

关于 Review,我们不只是把 Review 作为一个环节做到发布流程,更多的是文化渗透太强大,我的代码写完第一时间就是自发找人 Review。大家一般会注意到系统架构的高可用,有没有关注到人的高可用呢?一般来讲,人是最不可靠的。Review 机制可以保证同一个功能至少两个人互备一下,比如我以前 Review 过别人的代码,但那个系统老出问题,就老是找我。

单元测试覆盖率,在 Grab 我们要求要达到70%,不过好像实际也没那么高,但至少60%是有的。

到于 CI 我们主要依赖于 Jenkins,另外 Grab 内部普遍具有 Geek 精神,我们总是想自动化一切可以自动化的东西或流程。

接下来说一下压测和调优,我们主要使用 GoReplay 做基于生产数据的测试,这样可以无限接近生产环境。

Go 在 Grab 地理服务中的实践

GoReplay 作为一个 Proxy,可以旁路流量到你的 staging 或开发测试环境中。

分布式压测,主要基于 Vegeta 及 Fabric。

Go 在 Grab 地理服务中的实践

社区相关的博客、文章特别多。

这里简单介绍下 go-torch 这个工具,非常赞。可以将你的 profiling 文件给生成火焰图,注意使用火焰图时,颜色只是为了方便观察,宽度才是你要消除的瓶颈。

我们前不久收购了 Uber 东南亚业务,说这个什么意思呢?因为我们经常性的基于线上真实数据做分布式并行压测,我们对 Sextant 系统状况非常了解,收购之后带来更多的流量及更快的增长,但我们现在可以保持内心平静,来参加 Gopher China 大会,我们不用恐慌,No Panic!因为我们对自己的系统非常有信心!

Go 在 Grab 地理服务中的实践



这是我们地理服务相关的主要技术栈,我们主要基础设施在 AWS 上, 我们使用 DataDog  来做监控平台,其它基本跟业内差不多。   

最后谢谢大家,很高兴能够参加今天的会议 !

【提问环节】

提问: 我刚才看到那个 Shard 分片的时候有三个问题要问一下,但是我看Shard 如果分裂的时候要重启那就没有问题了。

另外一个问题刚才这个 Serf 在发现结点的时候,肯定是有一个自己的列表。

  张志印 这是种子结点。

提问: 那就是说它完全可以发现未知的结点,还可以确认这个结点是否是 alive 的状态?

志印 :我们现在是这样做的,就是种子节点放在 Redis 里,每个 Sextant 实例启动的时候从 redis 中心节点拿到种子,然后通过 Gossip 协议会获取整个server list。这个中心式存放种子的方式不影响整体的无中心架构,这个是没有关系的。

  提问: 当一个司机从一个 Shard 到另一个 Shard ,从一个 cell 到另外一个cell,或者一个地区开到另外一个地区,那个 Shard 的流程里面有一个司机位置,但是这个司机本来是在 Shard1,后来跑到了 Shard2,Shard1 里面的数据怎么办?

张志印: 这是一个很好的问题。如果一个 Shard 一定时间内没有数据更新,我们会把它回收掉。

  提问: 比如说雅加达这个地区里面,这个里面可能不止一个 Shard,有多个 Shard,但是司机从 Shard1 开到 Shard2,那 Shard1 里面的司机的信息怎么处理?

张志印: 是这样的,其实 Shard 主要是为了 sharding,比如按 shard 扩容,做 rebalance。主要的 Nearby 查询发生在 cell 里面,在 cell 中做 Nearby 查询时,会过滤掉过期的司机信息。你这里说的司机位置变更其实是前面讲的 cell 中垃圾数据的清理。有一组专门的 groutine 负责这组回收的,是主动回收的。

 提问: 我看到选择 Golang 的原因是有密集的地理位置上的计算,但是用事务是可以用 sql 直接在数据库里计算,这样就可以省掉网络传输的时间,省掉取数据的步骤,为什么选择用 Golang 业务逻辑做计算呢?

 张志印: 一开始就讲到了 PostGIS 的写问题,我们有大量的并发写,PostGIS对大并发写支持的并不好,流量高峰时期这个系统可能会崩掉,所以我们最终用 Golang 做了可以支持我们业务场景的 Nearby 服务,实际上在 Grab 内部我们称它为一个通用的地理空间索引数据库,我们有与业务无关的 filter 机制,不止是打车,外卖也可以接入且不用特别定制。

 提问 为了解耦和性能的问题是这样理解吗?

 张志印: 也可以。主要是一般空间索引数据库使用的如 R-Tree 不太适合。R-Tree 适合什么场景呢?比如酒店、POI、电子围栏等静态的地理位置信息,而 Sextant 管理的是大量的动态地理位置信息,主要是这个问题。

更多讲师 ppt 下载,请点击“ 阅读原文

原文  https://mp.weixin.qq.com/s/SDHcQN-tgQVJv6ETMzQcNQ
正文到此结束
Loading...