赵磊:从PHP到Node.js,漫谈Uber技术架构的变迁
Posted TGO鲲鹏会
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了赵磊:从PHP到Node.js,漫谈Uber技术架构的变迁相关的知识,希望对你有一定的参考价值。
7月19日,EGO走进深圳,并邀请Uber高级工程师赵磊与大家分享Uber的系统架构变迁,以及是如何从php转到Node.js。
基本上,Uber service分成两大部分,一部分是Real-time Service,一部分是Offline System。
从乘客的角度,打开Uber APP,就可以看到周围的driver。这时候乘客点一个request,我们的client就会发送一个request到Real-time Service里,然后会发送一个offer或dispatch到driver方,等其中一个driver接受offer后,就开始一个trip。直到乘客下车,这个trip结束,整个trip都是在Real-time的系统里做的。
为什么叫Real-time,因为这个系统是绝对不能down掉的,如果down掉了,可能你在车上都不知道去哪儿,而且它的latency要非常低。等一个trip结束后,我们会把它放到一个Message Queue或者是TaskQueue里。
Real-time Service后面就是Offline System,它会去queue中获取task,然后做一些任务,如build a credit card等,乘客和driver之间可以互相read。这些都是在Offline System里做的,因为它们可以delay一段时间,work states等那几分钟可能并没有什么关系。
现在在Uber,Real-time system基本上都是用Node.js写的,而Offline System基本上都是用Python写的。
我今天主要介绍Real-time system,因为这是最复杂的,要保证high availability,而且我们的Real-time是一个city shared service,也就是说全球有几百个城市,每个城市会run在几个worker上,它前面有一个我们自己写的load balancer,任何一个request进来之后,就能看到你现在是在哪个城市,之后就能把这个request forward到相应的worker,然后那个worker就process这个request。Shared service有很多challenge,我们最近的很多努力主要是在service routing和service discovery上。
先讲service routing是怎么回事。你的系统现在其实变成一个micro service的架构,做一个flow可能会需要很多的service来协作完成一个request。这里就会有service A发送request给service B的情况,那A怎么知道B在哪呢?这就是service routing,我要知道B的host name或IP或port,然后把request通过一个RPC发送给B。这里有两个选择,一个是client routing,一个是server routing。我们从client routing转向了server routing。
<Client Routing>
先看一下client routing,假设我要把一个request发送给B的worker,那我首先有个sharding function,就是可能你的keyspace user的费用太大,manage这么大的keyspace也会非常复杂,所以就用sharding的方式把这么大的keyspace变成很小的shard,比如说1个million。
然后就有一个global的shard map,知道哪个shard应该在哪个worker上,从client的角度考虑,就可以通过sharding的方式找到这个user在哪个shard,然后再在那个shard map里找到在哪个server,就可以把这个request发送过去了。
这种方法比较常见,比如说MemCache现在基本上就是一个client routing。如果你有个MemCache service,假设有10个node,想写一个key,那怎么知道去哪个node呢?Run一个hash function,用consistent hashing就能找到你要的node了。
但这里还有一个connection reuse的问题,大家都知道TCP有一个three-way handshake,这里就有一个round-trip time,虽然one-trip time非常低,但是如果你有很多request的话,这个cost也非常高。有一个简单的解决方法,做connection polling,我可以一直maintain一个persist TCP connection,当我发送request的时候,就不需要做handshake,直接用现有的TCP connection发送就可以了,非常简单。
在client routing的架构里面,要怎么做connection reuse呢?比如我刚刚举的例子A-B,如果你有100个A、1000个B,A可能会连到任何一个B,所以每一个A的node都要1000个connection,这时候的connection数量就是N×M,也就是100×1000,而且这只是A-B,实际上A会有其他各种各样的downstream service,比如说AC、AD,所以你的connection数就是N×M×你的整个service数,会非常多。
所以,我们现在正慢慢地把Uber的整个架构从client routing往server routing这个方向发展。
<Server Routing>
Server routing中,虽然你的service是shard,但是从client的角度来看它是non-shard,这样就能把很多complexity都放进server里面,从client来看是看不出来的。
那它是怎么做sharding的呢?Server routing中,server之间是互相认识的,它们知道应该怎么去shard这个user,client只要把request发送给任何一个server,它就会把这个request proxy给对应的node,就是你的shard应该在的那个node。
这样的好处在于,server routing的架构里面,client是没有sharding function的,否则,server和client都有sharding function的话,你upgrade的时候就很难去改动。特别是对于connection reuse,这种方法非常容易,因为原本一个client要连到1000个server,现在连到任何一个server就能做所有的事,所以这种情况下它一般会选一个很小的selection,有时是3个,那我的client只要保持3个connection到3个server node就ok了,每一个request都能发送给它们中的任何一个。这时候client到server之间的connection基本上就非常少了,比如说刚刚那个例子里有1000个server,这样我只要保持3000个connection就ok了,刚才是100K,现在是3K,会小很多。
另外,如果你的系统里有多种语言,比如有Java、有Python、有Go、有javascript等,那你的client当然是越简单越好。同样的server,如果你的client比较复杂,像刚才说的要做sharding function上的routing的话,你可能会需要把同样的逻辑在每个语言上都实现一遍。如果你是server sharding的话,client非常简单,你很容易就可以把任何一个语言的client都纳入这个server,这一点在Uber比较重要。现在,Uber有Node.js还有Python,很多新的项目也在开始尝试用Go。
Uber用了server routing之后,自己做了一个service broker或者说service discovery,它有一个单独的service,client本来是要talk to A、talk to B,这时候它只要把request发送给这个routing service,然后routing service会把它forward给B,这样client只要认识这个routing service就ok了,不需要它直接认识B。
很早以前,Uber的dispatch是用PHP写的,后来我们换成了Node.js,当时做这个决定的原因主要有几点。
第一是因为downstream service,一般来说,做任何事时,dispatch system会把你的request发送给下面的downstream service,拿到response,再发送给下一个downstream service,要做很多不同的RPC才能完成一个request。
在PHP里面,基本上每一个IO都是sync,任何一个downstream service,都要先发送一个request,需要等一段时间拿到response后才能进行下一步,中间不能做任何事情,这个latency可能很长,等你的downstream service越来越多的时候,你的整个的latency可能会越来越长。但是Node.js有一个好处,它所有的IO都是一个sync,你可以把所有的request都发送出去,等它们拿到所有response的时候,再一起process那些response,这样,你的overall latency会低很多。
第二是因为in memory state,PHP是一个完全面向web的语言,当有request时,它会开始一个新的thread,这个thread会run你的handler,你的handler会process这个request。等你的response结束以后,这个thread就会结束,而且你的handler不能run很长时间,在里面你很难做到能够有cross request state,一般大家都会把这个state放到一个external里面,比如说MemCache。
但在dispatch system里,我们还是想要有一些in memory Cache,比如现在in memory里,我们会知道这个城市里有哪些driver、有哪些rider。当我做dispatch时,我知道这个rider周围的driver都在哪,一旦这个driver发出offer以后,driver state就会变,这个state我们就放在memory里面,而用PHP的话,这个基本上是没办法实现的。
第三是因为user sharding,如我之前所说,dispatch system是city sharding,如果用PHP的话,一般PHP appliment都是一个比较简单的load balancer,它把这种request发送给任何一个PHP node,这里面是没有sharding的。我们如果做node service的时候,可以再做一个简单的client side,因为有a memory state,user必须在你的node上,所以你之前需要有一个比较聪明的load balancer,能够把你的request发到那个user应该在的那个node上。
版权所有 转载请注明:
以上是关于赵磊:从PHP到Node.js,漫谈Uber技术架构的变迁的主要内容,如果未能解决你的问题,请参考以下文章
10个最佳Node.js企业应用案例:从Uber到LinkedIn
10个最佳Node.js企业应用案例:从Uber到LinkedIn
如何评价Uber不用Node.js,而用Go语言构建地理查询服务?