system desing 系统设计(十三): LBS/O2O服务系统设计

     自从十几年前智能机和MBB开始普及,移动互联网迎来了井喷式的发展,到现在人手一部智能手机。既然是移动互联网,基于LBS的O2O自然是非常重要的业务之一,国内的滴滴、美团、饿了么都用过吧?这些O2O的后台又是怎么设计的了?

  1、(1)先来分析一下业务场景:  

  第一段:
    • Driver report locations
    • Rider request Uber, match a driver with rider
  • 第二段:
    • Driver deny / accept a request
    • Driver cancel a matched request
    • Rider cancel a request
    • Driver pick up a rider / start a trip
    • Driver drop off a rider / end a trip
    串起来讲:司机上线,给后台上报自己的位置。如果有乘客想打车,也同时上报自己的位置。后台开始匹配,找到乘客附近的司机后通知司机接单,然后司机赶往乘客的地点接人,开始运送乘客。只要是打过滴滴的小伙伴肯定都能理解整个流程!
   (2)再来分析一下通信的数据量,这会直接影响后台对性能的要求!从业务上讲,client和server之间最大的QPS就是location的上报了,尤其是driver的location,肯定是要每个固定的周期给server上报的,江湖传闻uber是4s的period,滴滴应该也类似,暂且也按照4s计算【上报周期越大,延迟越长,这就是大家在打车APP看到车和实际车位置总是不一致的原因】!这里以滴滴IPO时自己披露的运营数据为例来计算:招股书显示,滴滴全球年活跃用户为4.93亿,全球年活跃司机1500万。其中,滴滴在中国拥有3.77亿年活跃用户和1300万年活跃司机,2021年第一季度,中国出行业务日均交易量为25million次。我个人经常打滴滴,日常和司机师傅聊天时得知:滴滴强制要求司机师傅每天载客累计总时长不能超过8hour【核心是为了防止疲劳驾驶】。按照平均一单30minus计算,每个司机平均每天接16单。按照上面中国出行业务日均交易量为25million次计算,每天的active driver大约有25million/16=1.56million,所以这里就假设每日平均在线司机数量为1.56million:

  • Average Driver QPS = 1.56million / 4 = 390k
    • Driver report locations by every 4 seconds
  • Peak Driver QPS = 390k * 2 = 780 k【早晚上下班高峰期,还有工业园区996社畜晚上10点后下班,这个QPS的压力是比较大的!】
  计算出了diver的QPS,再来看看乘客rider的:和driver比,rider是不需要经常上报location的,只是到了需要打车的时候才需要上报自己的location。还是按照上述“中国出行业务日均交易量为25million次”来计算,乘客rider的QPS约:

  • Average Rider QPS = 25million / 86400 = 290;

  • Peak Rider iver QPS =  290 *2 = 580;这个QPS和driver的相比,几乎可以忽略不记.......

  再来看看存储空间的需求:
  假如每条Location记录,并且每条记录100byte计算,每天需要:(390k+290)*100byte*86400=3.1T 
       • 假如只记录最后一次上报的Location,那么需要(390k+290)*100byte=37M

  从数据量来看是非常大的,需要找读写都很快的存储系统

    2、接下来站在开发角度,继续分析网约车服务:

   (1).乘客出打车请求,服建一次Trip
    • trip_id 返回
    • 乘客每隔几秒询问一次服器是否匹配成功
  (2). 器找到匹配的司机,写入Trip,状态为等待司机回
    • 修改 Driver Table 中的司机状态为不可用,并存入对应trip_id
  (3). 司机汇报自己的位置
    • 便在 Driver Table 发现有分配自己的 trip_id
    • Trip Table 查询对应Trip,返回司机
  (4). 司机接受打车请
    • 修改 Driver Table, Trip 中的状信息
    • 乘客发现自己匹配成功,得司机信息
  (5). 司机拒车请
    • 修改 Driver TableTrip 中的状信息,标记该司机已trip
    • 重新匹配一个司机,重复第(2)

  整个流程图示如下:
  

   为了配合上述流程,库表的模型设计如下:

  

   (1)driver角度:最频繁的就是上报location数据了!为了便于后续的复盘和追溯,driver每条的location数据都会在location table中持久保存!这张表写的频率远比读高,建议用nosql存储!由于这部分数据是存放在磁盘的,在和rider匹配时如果全部从磁盘读取,效率太低,所以diver的location数据还需要在内存的redis存一份,格式就是Driver table中的KV形式!注意:driver table的数据是记录driver最后一次上报的位置,所以每个driver只可能有1条数据存这里面。如果driver趴着一动不动,这个数据是不需要更新的

             其次,如果匹配到rider的打车请求,会在trip table中更改status为“on the way to pick up”/"in trip"等!同时也在driver table中把status改成对应的值,避免再和其他rider匹配了!

        完成打车后,需要在driver table中把状态改成可用;同时trip table中的status也要改成“cancelled”或“end”!

      (2)站在rider角度,唯一的目的就是匹配diver,所以rider上报自己的location后,后台是要第一时间匹配相应的driver的,怎么做了?利用geohash算法!匹配的结果以KV形式保存在user location table中!由于driver的location在不断变化,所以user location table的数据也是在不断变化的,这就对这个table读写效率的要求很高了,最终还是采用redis最合适!截至目前,redis需要存放的数据有driver table和user location table!

  当rider需要打车时,发出request请求后,这个请求需要在trip table中新增一条记录。这条记录除了driver_id外,其他的字段都可以填写完毕!一旦从user location table中匹配上了driver,就要第一时间填写driver_id字段,并更新statuse字段了!

       

   3、(1)rider和driver上报的都是原始的经纬度数据,怎么匹配了?这里用的geohash算法了,这是一种典型的二分计算法,具体原理这里不赘诉了,最终的结果就是可以把二维的经纬度数据转成一个字符串。根据字符串相同字符的个数,来确认两个坐标点之间的距离,如下:比如两个getohash算法结果的字符串前面4个字符都是相同的,那么这两个点的距离不超过20km;

  

   下面是3家互联网大厂总部的geohash值:

  • LinkedIn HQ: 9q9hu3hhsjxx
  • Google HQ: 9q9hvu7wbq2s
  • Facebook HQ: 9q9j45zvr0se

  可以看到:linkedIn和google有4位相同,距离不超过20km,员工跳巢都很方便,但facebook就不同了,只有3位相同,相距不超过78km,严重影响了人员流动.....

  现在以Google HQ: 9q9hvu7wbq2sw为例,怎么匹配附近的driver?这本质上就是个字符串匹配的问题,怎么高效快速匹配了?

  (2)站在打车角度,driver和rider的距离也就第5、6最合适了,也就是距离控制在2.4km以内,所以字符串匹配到第5位就够了!站在工程快速实现的角度考虑,有这么几种方法:

  (2.1)SQL 数据
    • 首先需要geohash 建索引
      • CREATE INDEX on geohash;
    • 使用 Like Query
      • SELECT * FROM location WHERE geohash LIKE` 9q9hv%`;

    由于location table的写入量巨大,sql数据库显然是不合适的,这里直接pass不考虑了!

  (2.2) NoSQL - Cassandra
      • geohash 设为 column key
      • 使用 range query (9q9hv0, 9q9hvz)

          把location table存在nosql数据库,同时计算出geohash的值,设置为column key【rowkey还是driver_id】,然后用range query (9q9hv0, 9q9hvz),这是完全可行的!

  (2.3)NoSQL - Redis
      • Driver 的位置分
        • Driver 的位置如果是 9q9hvt9q9hvt9q9hv9q9h 3 key 中【和search engine的typeahead是不是很类似了?完全可以用MapReduce来处理】
        • 6geohash 的精度已在一公里以内,Uber 这类应用足
      • key = 9q9hvt, value = set of drivers in this location

      所以user location table的key既可以放rider的geohash,也可以放driver的geohash!如果是rider的geohash,后续用drive的geohash做predix match!

  (3)为了提速,在存放driver table和location table的时候使用redis。redis速度是快,但也不是一点缺陷都没有:内存贵!其实也可以用其他的nosql数据库替代,大不了多增加一些大磁盘的服务器嘛!既然一定要用多台机器的话,我们可以用 1000 台 Cassandra / Riak这样的 NoSQL 数据库,平均每台分摊 300 的QPS, 能更好的处理 Replica 和挂掉之后恢复的问题

    4、号外号外,除了用frida等常见的工具软修改GPS坐标点,某宝上已经有人售卖物理修改GPS坐标的外挂了:

   

 

 

参考:

1、https://github.com/uber/ringpop-node  RingPop架构

2、https://github.com/uber/tchannel  tchannel RPC协议

3、https://github.com/google/s2-geometry-library-java  google S2
4、https://www.allthingsdistributed.com/files/amazon-dynamo-sosp2007.pdf    Dynamo: Amazon’s Highly Available Key-value Store

5、https://www.bilibili.com/video/BV1Za411Y7rz?p=95&vd_source=241a5bcb1c13e6828e519dd1f78f35b2  基于LBS的服务

6、https://www.sohu.com/a/472168419_121092262  滴滴运营的财务数据

7、https://zhuanlan.zhihu.com/p/35940647  geohash算法原理

posted @ 2022-08-27 16:27  第七子007  阅读(350)  评论(0编辑  收藏  举报