Daijia

Posted by 佳运 Blog on April 9, 2025

代驾宝项目总结及面试问题

项目地址:gjyyyy/daijia

为什么采用mongoDB存储司机轨迹数据

MongoDB中每一条数据记录就是一个文档,数据结构由键值(key=>value)对组成

MongoDB适用场景

MongoDB不需要去明确指定一张表的具体结构,对字段的管理非常灵活,有很强的可扩展性。

支持高并发、高可用、高可扩展性,自带数据压缩功能,支持海量数据的高效存储和访问。

支持基本的CRUD、数据聚合、文本搜索和地理空间查询功能。

适用场景:

  • 网站数据:Mongo非常适合实时的插入,更新与查询,并具备网站实时数据存储所需的复制及高度伸缩性。
  • 高伸缩性的场景:Mongo非常适合由数十或数百台服务器组成的数据库。
  • 大尺寸,低价值的数据:使用传统的关系型数据库存储一些数据时可能会比较昂贵,在此之前,很多时候程序员往往会选择传统的文件进行存储。
  • 缓存:由于性能很高,Mongo也适合作为信息基础设施的缓存层。在系统重启之后,由Mongo搭建的持久化缓存层可以避免下层的数据源过载。

文档类似于 JSON 对象,它的数据结构被叫做BSON(Binary JSON)。

img

MongoDB支持水平扩展,可以通过增加更多的服务器来处理更大的数据量和更高的请求量,这对于可能需要处理大量点集数据的代驾系统来说非常重要,并且MongoDB支持地理空间索引,所以考虑使用了mongodb.

总结一句话:MongoDB每一条数据是由键值对组成,并且支持地理空间索引,非常适合实时的插入,更新和查询操作,故采用mongoDB来存储司机轨迹数据。

乘客下单和司机抢单的完整过程(使用到xxl-job、rabbitmq、redisson)

一、乘客下单后先将订单信息存入数据库和redis中(redis只存”order:“+订单id)

开启定时任务(xxl-job):每15s查询一下是否有符合条件(比如司机自己会设置只抢哪个范围内的订单)的司机(通过GEORADIUS查询周围位置),若找到符合条件的司机则将订单信息封装后放入对应的消息队列(通过发布订阅模式 将司机的driverID作为routingkey进行交换机和消息队列的绑定),同时如果超过15分钟没有司机接单则自动取消订单(使用TTL+死信队列实现)

二、司机端轮询查询消息队列时要判断可以抢的订单时要判断该订单是否已经取消

在抢单过程中要防止超卖问题,这里本项目采用的是Redisson分布式锁来解决。

如果一个消息被投递到了多个消息队列中,如果这个消息被其中一个消费者优先抢到并且消费了,有办法把其他消息队列中这条的消息都给删除吗

这里采用不删除的思路:乘客下单后已经将orderid存入了redis中,因此每次遍历完消息队列之后再去redis中找一下是否还存在,如果不存在了,就说明该订单不可以返回给司机端了。

Drools规则引擎

Drools规则引擎用来计算 :分账,奖励,预估订单金额

只需要编写好规则引擎的规则代码(.drl文件),在执行时激活规则即可执行并得到需要的结果,所以引入规则引擎后可以及时调整计费规则,并且不需要修改代码,可以用来解耦。

CompletableFuture异步编程提升执行效率

见Java八股

Redisson的看门狗机制

Redisson的出现,其中的看门狗机制很好解决续期的问题,它的主要步骤如下:

  • 在获取锁的时候,不能指定leaseTime或者只能将leaseTime设置为-1,这样才能开启看门狗机制。

  • 在tryLockInnerAsync方法里尝试获取锁,如果获取锁成功调用scheduleExpirationRenewal执行看门狗机制
  • 在scheduleExpirationRenewal中比较重要的方法就是renewExpiration,当线程第一次获取到锁(也就是不是重入的情况),那么就会调用renewExpiration方法开启看门狗机制。
  • 在renewExpiration会为当前锁添加一个延迟任务task,这个延迟任务会在10s后执行执行的任务就是将锁的有效期刷新为30s(这是看门狗机制的默认锁释放时间)
  • 并且在任务最后还会继续递归调用renewExpiration。

当程序出现异常,那么看门狗机制就不会继续递归调用renewExpiration,这样锁会在30s后自动释放。

或者,在程序主动释放锁后,流程如下:

  • 将锁对应的线程ID移除
  • 接着从锁中获取出延迟任务,将延迟任务取消
  • 在将这把锁从EXPIRATION_RENEWAL_MAP中移除。