支持商务旅行者不仅仅是让他们从A地到b地。企业客户需要报销,需要与差旅管理系统集成集中式支付方式,以及给合适人群提供合适出行选择的能力。的Raybet2优步企业团队创造了许多工具,让在优步上管理差旅变得更容易。但直到最近,这些功能大多是为旅行管理员提供的。这款应用中几乎没有什么功能是为员工设计的。去年秋天,当我们推出一个新功能时,情况发生了变化允许用户使用优步出行在不同的配置文件。

现在,任何人都可以创建一个业务简介,让他们的乘车收据转发到公司的电子邮件中,并选择接收关于他们在优步出行的每周和每月的详细报告,这让管理他们的支出变得更加容易。
这是怎么发生的?现在我们已经很好地完成了U4B的发布,以下是我们如何设计U4B最新的一套以乘客为中心的功能的故事。
产生旅游的报告
单独获取和呈现PDF人们的旅行很简单。我们面临的挑战是要以优步的规模和速度来实现这一目标,因为优步的服务每几个月就会增长10倍。为了满足这一需求,我们知道必须创建一个新的服务来自动化呈现和提供pdf文件。我们有两个用例:
- 为个人商务旅行者建立每周和每月的旅行报告。
- 为公司管理层建立月度员工差旅汇总报表。
U4B超过5万家公司注册了Uber for Business。Raybet2当我们在内部引入构建该功能的计划时,我们不仅要考虑管理员,还要考虑他们接收摘要和旅行报告的员工。考虑到优步的增长率,我们知道这可能导致一次产生数百万pdf文件。
一个简单的方法:客户端调用
我们可以通过考虑客户机和服务器之间发生的对话来设计服务。编写语句服务的一种简单方法是让客户机在需要新的PDF时调用它。在这个系统下,每周一,当U4B需要更多的每周旅行报告时,对话是这样的:
客户端:给用户做一个每周报表uuid1满足
以下标准:
-此用户在业务简介下乘坐的行程。
这个用户上周发生的旅行。
服务好吧,我知道了。我已经拿了所有的旅行。
这是你的PDF。
客户端:谢谢!现在给用户做一个每周报表uuid2…
这是一种很自然的开始方式,但它的性能和交通问题阻碍了它满足优步的规模需求。具体地说:
延迟。获取数据和呈现PDF需要几秒钟;因此,成千上万的请求需要几天的计算机时间来处理。当然,许多机器可以同时处理这些任务,但即使是一个大型的并行批处理作业,也可能需要几个小时才能让所有线程完成。自rpc不会持续打开数小时,作业将失败并重试,向我们的系统发送重复的工作。
DDoS。我们的对话示例让它看起来像是U4B连续地构建语句,一个接着一个,但实际上它是让它们都并行。因此,每周一U4B会用成千上万的内部基础设施请求(从一个服务到另一个面向服务的体系结构)在短时间内。当这可能迅速扩展到数千万个请求同时进入时,这将使语句服务不堪重负并崩溃。我们可以提供足够的服务器来处理星期一的沉重负载,或者使用动态分配。但是,这将增加更多的服务依赖关系,而不能解决响应不可预测和突发负载的根本问题。
另一种方法:倒置
还有另一个简单的策略可以解决这个问题:如果我们担心有太多客户同时打电话过来:别让他们。让服务工作通过有服务发起对话,让客户完全被动。这样,客户端就永远不能通过DDoS向中心服务发送请求。有了这个建议,对话就变成了:
服务我在做上周的每周声明。做你的
用户想要吗?
客户端是的,我要给用户的报表uuid1,uuid2,…
服务:好的,用户是什么uuid1需要在最后的声明中
星期?
客户端所有这些信息…
…
服务如我已经做完了uuid1在这里,这里是。
客户端:谢谢!
有了这样的设计,我们的系统将不再出现每周一服务被请求沉重打击的情况。相反,服务以一种有序的方式调用每个客户机,并以它能处理的速度收集所需的信息。因为语句服务本身现在控制着所有的rpc,所以它不会因为一次做太多事情而关闭自己。但是现在的问题是如何让客户端响应服务的请求并表演我们编写的对话。
一个节俭的解决方案
因为Uber的所有服务都会说话节俭,我们使用TChannel通过轻量级Thrift集成构建,作为我们的网络拓扑,用于服务发现和路由。一个服务向网络宣布它自己,并通过它的名称(例如,“分类账”或“地区”)来标识,它可以运行任何数量的Thrift服务。
我们通过定义一个Thrift服务契约来利用这一点,语句服务的所有客户端都必须支持这个服务契约。如果它们可以只响应这三个rpc,它们就可以得到为它们生成的语句。
每个RPC只是服务器-客户端对话中的其中一行的适配:
通常,建立一个新的Thrift服务是一项重要的任务,但使用TChannel时,客户机只需一行代码就可以添加另一个Thrift服务。此外,这些rpc都是对信息的简单请求,因此可以无状态地实现它们,并且没有副作用。服务所有者可以实现StatementService不用担心破坏自己的数据或流程。优步的咄咄逼人的转变为SOA已经解决了这个问题。
uConfig是优步的动态配置解决方案(将在其即将发表的文章中介绍)。它允许您将配置值(常量、白名单、可调参数等)放在单独的服务中,并在生产中对它们进行更改,而不必重新启动使用这些常量的服务。
我们使用uConfig来存储客户端服务名的白名单,这意味着建立一个新的客户端服务不需要在语句服务中更改代码。如果UberEATS团队想给你发一份声明,上面有你上周订购的所有食物的图片,他们只需要执行StatementClientService,确保它正在运行,然后将其“ubereats - Service”的名称添加为语句服务的客户端。
我们要做的
陈述式服务设计是一个例子,说明即使是看似简单的服务,也必须防御性地建立,才能在优步的规模和增长速度下生存下来。它还显示了超级的SOA原则已经把所有的工具放在适当的位置,使这些设计的实现很容易。
通常,实现服务契约设计是不现实的,因为每个客户端服务可能运行不同的语言和框架,这些语言和框架都需要特殊处理。但随着优步致力于Thrift和TChannel,这个设计成为了它们原则的自然应用。最好的部分是,我们的解决方案的关键部分都是开源。





