打车项目的难点
首先它是一个SpringCloud项目,分为7大模块
网关模块JWT令牌校验,请求的路由转发,
account模块我们的一些人员注册,增删改查乘车人
stroke路径模块做的一个行程的路径匹配,以及面对超时回复的时候,情况的处理
order订单模块行程一些比如说计费,多少米什么的信息
pay支付模块:需要进行微信支付的沙箱模拟操作,以及一些沙箱的回调。
首先我们发布行程,会有一个hash来存储对应的key是字符串+用户的ID,value 经度纬度
两端的位置如何定位与记录
双方形成的坐标匹配如何设计
对话实时通讯
匹配成功后,司机座位数的统计
司机发出邀请之后,乘客无响应如何处理
高并发的行程数据的实时坐标如何记录和同步
计算选型
后端:springcloud+nacos+gateway+feign
前端:nginx,动静分离
存储基本业务数据mysql+即时位置geo(redis距离运算,,)mongodb(车辆位置,并不涉及过多位置计算,比如车辆到达那些位置)
缓存:redis,难在缓存如何设计上
消息:rabbit+kafka+websocket
前段h5+css+jquerymobile,地图对接,websocket,接口通用
注册中心nacos,服务网关:gateway,
行程中心stoke订单中心order
用户中心account,支付中心payment,存储中心storage,消息中心:notice.
mysql,或许也是可以的用户和商家距离,用户很多,Point(120,30) 可以使用一个where lat beween a-x and a+x and long between b-y and b+y,需要用一个联合索引(latitude,longtitude)
筛选后,使用聚合函数,进行一个排序
select id,acos()*cos...as distance from t having distance<1 order by distance asc limit 0,20.
分布式
雪花算法snowflake是一种优秀的分布式ID生成方案,其优点突出:它能生成全局唯一且递增的ID,确保了数据的一致性和准确性;同时,该算法灵活性强,可自定义各部分bit位,满足不同业务场景的需求;此外,雪花算法生成ID的速度快,效率高,能有效应对高并发场景,是分布式系统中不可或缺的组件。
第一位是标记(由于long基本类型在Java中是带符号的,最高位是符号位,0或者1)默认是0,41位是时间戳
WrapObjectFactory-赋值工具,通过拦截器,通过AccountVo根据ID号来赋值
自定义注解@RequestInitial实现,id借助@InitialResolver注解+雪花Resolver生成
从Request拦截她,只要是带@RequestInitial,再去找参数。
表
附件表:一个是,生成文件的md5,每次上传fastdfs前比对md5信息。避免重复上传造成空间浪费,而且慢,
mongo:
notice表:放到的是即使消息,与坐标数据,
location:司机与乘客行程轨迹。
业务库:起始点
消息
kafka场景:监控数据,超高吞吐量场景,司机运行轨迹数据,发布行程的数据,可以送入kafks,进入监控大屏等展示操作
rabbitMQ场景:乘客迟迟不响应,所以采用死信队列,超时队列(超时之后,进入死信)
实时的位置数据(高并发发送实时坐标,削峰处理),websocket+mongo存储+多线程轮询扫描标记已读情况。
缓存
乘客:每个行程,司机:每个行程
匹配度计算:
zset:保存的对应行程的按照匹配度分值的进行排名,我们排行中以列表页的形式展示
map:保存我们的距离数据,用来显示车辆距离起点终点距离我们有多远
zset:10,98.22 按照匹配度排名(行程id号。 得分)
Map:10,1.2:1.1(距离起点和终点距离)
w=(a*w1+b*w2)/(w1+w2) -不是整数,而是百分比.
1-(中心点距离起点的公里数*0.5+中心点距离终点的公里数*0.5)/半径的范围))*100
他们的哈希:名字是司机,然后键是乘客(司机存储的话)
GEO哈希编码规则
每一个字符串代表某一矩形区域,这个矩形区域的点都共享相同的GeoHash字符串,这样既可以保护隐私,又比较容易缓存
GEO原理
整个地图所分割区域进行划分,采用base32编码方式,每个字母或者数字都由5bit组成
5bit可以有32种不同组合,这样我们可以将整个地图分为32个区域,通过00000~11111来标识这32个区域五个位置,一部分给经度一部分给纬度。奇数表示的维度序列,偶数是经度序列,进行第一次划分的时候,Geohash0,1前5个bit,3bit表示经度,2bit表示维度(从0位置开始数呗)当一个乘客,选择好起点与终点的时候,
生成STROKE_PASSENTER_GEO_END
STROKE_PASSENGER_GEO_START
司机发布行程
STROKE_DRIVER_GEO_START
STROKE_DRIVER_GEO_END
我们的操作
先放自己的,然后找到对方的行程,也要把我加进去
STROKE_GEO_ZSET_xxx(行程ID)乘客看司机时候,看的是司机的分
STROKE_GEO_DISTANCE_(行程ID)这里面的key都是和行程ID不同的
假如行程ID是乘客的,那么他的key就是司机
匹配相关用户的行程
行程匹配度排行
车主发起邀请,乘客接受/拒绝邀请
剩余座位判断
邀请超时取消
路径计算
计费-装饰者模式
空座数:座位数有限,采用记账(因为不止有加加减减,还有一些超时的)
避免i--的假如不来还要加回去座位的问题,我直接选择i++,我开始知道一共多少个位置,使用redis的hash来保存,进行一个i++,此时就是键,1,然后我们取出来是开始4个,4-获取hash的value(加就肯定是加
redisHelper.addzset(HitchConstants.STOKE_GEO_ZSET_PREFIX,hitchGeoBo.getTargetID()(乘客ID),stroke.getId()->(司机的ID),getscore(hitchGeoBo);
邀请乘客信息
如果他不这样乘客这里存储司机的ID,我们会发现假如再来一个司机,他是无法获取最新的司机消息,因为他是根据键获取的,假如我们不去添加,像是一个哈希表
哈希名称HitchConstants.STOKE_GEO_ZSET_PREFIX.100乘客ID(这就相当于hash名字)
我们是根据这个key来获取最新的司机,同时司机也会存储对应的乘客。
假如司机是480,存储的乘客720,然后value是状态吗(0,3)
720也存储对应的480,value
类似于NACOS的服务注册和服务发现
邀请流程图,
行程ID是司机的,key就是乘客。
0:未确认
1:已确认
2:已拒绝
3:已超时
STROKE_INVITE_行程ID: 对方ID 邀请状态,每次都一对关系,
超时(轮询,定时器,死信队列)
放一个生产者->缓冲队列(行程的邀请关系,超时逻辑TTL,如果超时了,会自动扔给另一个队列)->DLX(死信队列)(过期之后,通过死信队列DLX发送给实际消费队列->实际消费队列.->消费者(假如是DLX转发的话,就把状态改为超时。
调用rabbitMqTemplate