Ousta是埃及领先的Ride hailing公交公司(译注:这是一种不设立固定站点,乘客随上随下的公交运营模式。详见: https://en.wikipedia.org/wiki/Hail_and_ride ),该公司的乘客数和车辆数正在经历飞速增长。我们的系统包含两个移动应用,应用可以与基于微服务的事件驱动的架构进行交互。我们通过配合使用EDA和微服务搭建了自动化的模拟系统,借此打造了一个可供我们快速演进的环境。
我们分别针对乘客和司机开发了两个应用。在开始实现这些应用时,我们决定先从面向客户的乘客应用着手,乘客可以通过这个应用约车。
开发可以实时更新的应用程序会遇到额外的挑战。每次需要对约车过程进行测试时,都需要模拟下列高层事件:
每次移动应用或后端服务发生变化后,我们往往很难对整个流程进行测试,因为我们手头只有乘客应用和API。
我们开始考虑通过自动化解决方案在测试环境中测试整个流程。可是该如何针对乘客应用中进行的操作执行司机API?我们曾考虑使用外部微服务执行司机API,监听由于乘客操作产生的系统事件。在我们所用的事件驱动的架构帮助下,这一切可以很容易地实现。
事件驱动的架构极为强大,针对系统执行的每个操作都会产生一个JSON对象形式的事件,其中包含了所需的全部信息。
例如约车请求会产生一个事件,其中包含了乘客信息、上车位置、价格等信息。JSON事件内容如下:
{ // Meta part contains information about the event type and category. "meta":{ "messageCategory":"event_TripInfo", "messageType":"newTrip" }, "body":{ "tripId":"d2eac36c-8131-4409-8c37-863b0725fb4d", "status":"processing", "riderId":"4524b80a-f82a-4f1c-ad77-69a620c5c628", "requestTime":"2016-09-09T15:42:32+0000", "requestTimestamp":1473435752, "currencyCode":"EGP", "rider":{ "riderId":"4524b80a-f82a-4f1c-ad77-69a620c5c626", "firstName":"Mostafa", "lastName":"Saeed", "emailAddress":"msaeedatteya@gmail.com", "mobileNumber":"+201272509147", "pictureUrl":null, "rating":"5" }, "service":{ "serviceId":"7f888b1f-14e4-4b7e-8f8d-1c1ee1df074c", "displayName":"Super Saver", "description":"Ousta super saver service", "code":"Super Saver", "image":"https:////s3-eu-west-1.amazonaws.com//oustaridersapp//services//ico_cartype_economy.png", "carPictureUrl":"https:////s3-eu-west-1.amazonaws.com//oustaridersapp//services//img_onmap_car_taxi.png", "priceDetails":{ "costPerDistance":"1.45", "costPerMinute":"0.25", "distanceUnit":"KM", "base":"4.0", "cancellation":"10", "minimum":"10", "currencyCode":"EGP", "commissionPercentage":"0.2", "commissionFees":"0", "commissionType":"percentage" } }, "city":{ "name":"Cairo", "code":"CA", "location":{ "latitude":"30.0594699", "longitude":"31.1884239" } }, "pickupLocation":{ "latitude":"30.067615031955", "longitude":"31.210990101099", "text":"Tahrir Square, Meret Basha, Qasr an Nile, Cairo Governorate" }, // Driver & vehicle are null because ride is not accepted by a driver yet. "driver": null, "vehicle": null, "paymentInfo":{ "paymentMethodId":"4496839d-8cd2-4f98-98bb-e059ca7ac5c1", "type":"cash", } } }
该事件将由多个其他微服务来处理,例如发送推送通知、短信、邮件,或处于分析需要将乘客信息存储在其他位置。我们使用Amazon SNS 发布事件,使用Amazon SQS 接收事件,并将消息队列中的客户事件持久存储。
下图展示了系统中的事件从发布到最终被不同微服务处理的完整事件流过程。
(点击放大图像)
如图所示,事件驱动的架构使得我们可以轻松地在系统中加入不同用途的新增微服务,整个过程只需要下列步骤:
我们曾实现过一种模拟器微服务。通过侦听TripEvents SNS话题,该服务可以很容易地根据产生的事件执行必要的操作。例如当收到的事件表示某位司机准备接受或拒绝某个约车请求后,该服务会通过调用API代表司机应用接受该行程,具体过程如上图所示。
最开始我们只实现了一种较为简单的版本,涵盖了从乘客发出请求到行程结束的整个过程。不同调用按顺序执行,每个调用之间等待10秒钟。完整的行程过程如下所示:
(点击放大图像)
我们希望让模拟的过程更真实,因此开始通过模拟司机在不同街道上的活动对模拟器进行优化:
随后我们考虑到可以在测试环境中模拟在我们位于开罗市区的总部周围四处行驶的司机。我们希望每隔x秒调用一次司机API,借此向后端系统告知司机当前位置的经纬度。
由于希望让模拟的过程显得更真实,我们还对模拟服务进行了优化,用一系列代表经纬度的坐标点代表实际路程,这些坐标点代表了从司机接受行程时的位置到乘客上车位置的全过程,以及从上车位置到乘客(请求者)在应用程序中指定的最终目的地的全过程。为此我们使用 Google Distance Matrix API 获取坐标点列表形式代表的两点间完整路径,这样在司机从A点到B点的行驶过程中模拟器就可以按顺序发送这些点的坐标。
位置更新触发器
最麻烦的部分在于,该如何设置安排好的触发器,以便将每x秒一次的位置更新发送给每位模拟的司机。我们考虑了一种解决方案,使用一个可自动触发给服务的Cron作业,但这种方式存在局限,最小间隔为一分钟。因此我们需要用一种特殊的方法使用Amazon SQS。SQS提供了一个名为 Delay Queues 的强大功能,使得加入队列的消息可以在实际使用前维持x秒的不可见状态。下图展示了我们使用这一功能模拟司机行驶过程的方法。
(点击放大图像)
根据应用程序的需求,我们选择了每四秒一次的采样率。精确频繁的位置更新对我们的应用程序和乘车体验很重要,因为我们需要通过这样的更新计算行程的实际距离。
(点击放大图像)
模拟服务使得我们可以在将司机应用程序开发完成之前随时测试乘客应用程序的所有功能,这一特性解决了我们开发工作中一个非常大的依赖性问题,无需同时使用这两个应用程序就可以对整个行程进行测试。
两分钟内完成整个测试周期
借助上述方法,我们打造了一个完整的自动化测试解决方案,可以帮助我们更放心地发布新功能,应用变更,修复现有问题,并让每天多次的生产部署变为可能。
Mostafa Saeed 是Ousta公司的软件架构师,这是一家位于埃及,正在经历飞速增长的公交公司。Mostafa领导着开发团队打造了一套事件驱动,可容错,基于微服务的架构。同时他也是AWS顾问,帮助大量初创公司应用有关高可用云,以及缩放能力方面的最佳实践。
作者: Mostafa Saeed , 阅读英文原文 : How Ousta Simulates Rides within a Two-Minute Test Cycle