物流系统项目开发经验分享:技术面试与项目面试要点解析

2024-08-03
来源:网络整理

介绍:

Java面试一般分为技术面试和项目面试两个部分,相信大部分小伙伴都刷过不少技术面试题,就连博主本人也刷过不少无聊的面试题。但是对于项目经验方面的面试,很多新朋友可能正处于空白期。本文主要围绕一个物流系统,介绍一些实际开发中遇到的业务和经验,一共介绍了六个模块,供大家参考。但是实际开发中,一个人不会同时开发那么多模块,大家可以根据需求将2-3个模块改造到自己的项目中,切记不要完全照搬。模块2是技术参考最多的,里面包括了分布式系统交易问题,分布式幂等性,以及支付相关问题,大家可以重点了解一下。

项目简介:

开发环境:idea+JDK1.8++++

软件框架:+Boot+-plus+xxl-job+++

+++

项目描述:本项目为物流为主的业务类型,有多种邮寄方式(上门取货、网点自提)。用户在浏览器填写相关信息,选择卡车和货量,服务器会验证订单价格,用户点击支付后,订单生成,快递员上门取货,系统会根据类型和状态跟踪订单,到达目的地后,快递员会发短信让收件人取货。

面试答案:

物流最重要的是把邮寄的物品从出发地送到目的地,所以我们的项目是从用户选择不同的邮寄方式(例如平邮、一站式包车、定时派送)开始,通过不同的业务逻辑计算物流订单的价格,然后用户下单、付款,才真正生成订单,系统会根据不同的订单类型、订单状态进行派送。

建筑学:

技术选择:

我在开发这个项目的时候,和几个同事一起,考虑到网站的流量会随着市场份额的增加而增加,所以我们选择了市面上比较流行的微服务架构+,当然我们也考虑过+(比较优势和差异)。相比之下,

我们选择使用它是因为它在微服务场景下提供了一站式的解决方案,有很多成熟的组件可以供我们开发使用,比如,等等,而且调用方式比较轻量,开发效率高,服务间代码解耦,所以我们选择使用微服务架构。

考虑到我们项目上线之后用户量会越来越大,为了让我们的程序能够处理更多的并发请求,我们以服务器作为项目的切入点(好处和作用),同时为了防止单点故障,我们采用两台服务器一主一备。主节点提供公网虚拟IP,当主节点发生故障时,从节点继续响应。然后,为了防止部分用户恶意刷端口,我们采用限流的方式限制用户的访问频率和系统的最大并发量。(漏桶算法原理)

然后用户的请求会被转发到服务网关集群,由于网关是我们整个系统的入口,负责过滤、验证、路由请求,所以我们也会进行限流,以应对高并发。

服务统一​​注册到注册中心集群,这样做的原因是它功能强大,不仅可以作为服务注册中心,还可以作为服务配置中心(这点和其他几大注册中心不一样)。

由于我们采用的是微服务架构,当一个客户端请求需要调用多个服务才能完成的时候,我们采用服务之间的调用,这样就具备了负载均衡和熔断的能力。可以分担我们的访问压力,并且在服务请求失败的时候能够快速响应,防止出现服务雪崩的现象。(为什么具备负载均衡和熔断的能力)

大家知道,网站首页是一个查询较多但更新较少的模块,所以我们使用

通过缓存来防止大量的请求对数据库造成压力,进而提高高可用性。

我们搭建了一个集群。(为什么这么快?集群,高可用架构)

然后搜索方面我们采用的是es的索引库,由于es实时性高,并且支持分布式,所以我们选择使用es的索引库(es是近实时,倒排索引,和solr的区别,同步原理);在一些需要异步处理的场景,我们使用消息中间件,并且我们项目中也使用事务消息来解决分布式事务的问题(事务消息,好处等)。

这些就是我们在设计微服务架构时想到的解决方案。

我负责开发模块 1:运输

在用户点击下订单之前,我们首先要判断用户是否登录,如果没有登录,我们会提示用户登录,如果已经登录,我们才会让用户填写数据并下订单。

我们的配送方式分为五种模式:1、上门取货2、网点自配送3、委托配送4、公益配送5、同城30分钟配送。

如果您想自己发货,您可以根据货物的体积和重量选择卡车规格,然后填写地址信息,发件人信息和收件人信息。价格方面,我们根据自己写的一个价格和时间工具来估算订单价格,实际费用由工作人员看货后收取。

如果是普通订单,可以直接下单,如果是网络订单,我们提供上门取货服务,当天用户可以预约明天早上9点到下午5点的工作时间,具体时间间隔为半小时(例如:早上9点,9点半,10点等等)。当用户下单后,用户的订单信息会存入数据库,并返回订单数据,用于后续支付。

如果是上门取货,我们同样使用定时任务(),每30分钟执行一次,此时查询订单信息为未取货的订单状态以及该订单的取货地址、取货时间、发件人信息,然后查询不在上班状态的快递员及手机号,根据订单的取货时间,提前30分钟将订单地址及发件人信息通过MQ异步短信发送给快递员,快递员上门取货。

在派单时,如果是站内单,我们会通过每5分钟执行一次的定时任务,检查该订单是否为未处理订单,如果是未处理订单,我们会查询不在岗的快递员手机号,通过MQ异步发送短信或者邮件的方式将订单派发给快递员,快递员收到订单后,电话联系客户。

亲自负责开发模块2:订单支付(支付宝)

用户填写完配送信息并生成相应订单后,支付状态为未付款,用户可以在订单页面查看未付款的订单并选择支付方式。

(付款方式为即时付款或货到付款)。

用户支付成功后,支付宝有两次支付回调,理论上是先执行同步回调再执行异步回调,但实际上同步回调和异步回调并不是按顺序执行的,当用户完成扫码后,支付宝会直接返回支付成功给用户,并执行同步回调,同步回调的值一般用于跳转某个页面,提示用户支付已经成功。

在支付成功的异步回调逻辑中,最重要的是将订单的支付状态修改为已付款,并且将支付宝返回的交易流水号等支付信息添加到我们的订单表中,然后根据用户实际支付金额和应付金额修改用户的积分。

由于支付模块和积分模块是两个不同的服务,所以此时就涉及到分布式事务。

保证分布式事务一致性的方法有很多种,我们使用 来保证事务的一致性。

在设计上,客户端与客户端之间具备双向通信能力,这使得它们天然地充当了事务协调者的角色;它们可以保证本地事务执行和消息发送的原子性。

它提供的存储机制使得交易消息变得持久化。

高可用性机制和可靠的消息设计,保证即使系统出现异常,事务消息也能保证分布式事务的最终一致性。

我们在支付的异步回调中向MQ发送一个交易消息,这个消息我们称之为消息,这个消息不会放到真正的业务中,而是先放到MQ内置的Half中。

这种情况下,不会为该消息创建消息索引,对端将无法看到该消息。

MQ收到这个消息之后会给我们这边一个反馈消息,告诉

成功接收端消息,端收到反馈消息,需要实现

支付模块英文_支付模块_支付模块流程

接口,然后重写里面的两个方法,我们需要在其中一个方法中处理本地事务,也就是修改订单状态,并将支付信息保存到记录表中。

如果本地事务成功,我们将事务状态返回给MQ。

此时MQ会把Half中的消息取出来并生成一个消息索引,然后把这个消息传递给业务,此时这个消息对于消费者来说是可见的,消费者开始处理加积分的交易。

如果本地事务失败,我们把事务状态返回给MQ,此时MQ不会生成消息索引,该消息对消费者不可见,消费者也不会增加积分。

如果本地事务超时或者崩溃,MQ 将继续询问所有

收到查询消息后,检查中该查询消息对应的本地事务的状态,并根据本地事务的状态进行返回或者发回。

交易消息审核也很简单,我们要知道一个交易消息的成功投递需要经过half、op、三个阶段。

首先我们会维护一个死循环,默认一分钟执行一次。MQ用half和op来存储事务消息的进度状态,half存储消息,op存储消息对应的状态,也就是or。MQ通过对比两个队列的差异来找出定时或者宕机的未提交事务处理结果的事务,然后在端调用相关方法查看事务处理结果。

此时我们只保证本地事务和消息发送的原子性,也就是说只要我们本地事务执行成功,消息就会被发送出去。

还有一点就是消费者加点的时候我们通过ACK重试机制以及死信队列的人工干预来保证消息一定会被消费成功,为了保证消息一定会被消费成功,只有当消费者明确返回消息消费成功,也就是返回

只有消息被接收到,才算是消息消费成功,当然我们手动对消息进行了ACK,这样保证了断电或者抛出异常时,消息消费不会被认为是成功,并发起重试。

默认重试次数为16次,当达到最大重试次数时,消息就会被投递到死信队列中,我们可以根据业务需求自定义最大重试次数进行消费。当死信消息被投递到死信队列后,死信消息的业务是需要人工干预的,其实重试三五次之后就可以认为当前业务出现了异常,继续重试也没有意义了,那么我们就提交当前消息,返回状态,停止消息的重新发送。同时将消息存入我们业务自定义的死信消息表(),将业务参数存入数据库,相关操作通过查询死信消息表进行相应的业务补偿操作。

因为消息重试,我们还需要考虑消息重复消费的问题,需要自定义一张日志表,把已经消费成功的消息ID放到这张日志表中,如果新收到的消息ID已经在日志表中,则说明该消息已经被消费过,不会再次消费。

当用户长时间未付款时

如果用户选择在线支付但是一直没有付款,我们限制用户的付款时间为24小时,生成订单的时候发送延时消息,并指定消费时间为24小时,这个消息监控只能用于24小时内的消费。消费的时候获取发送的订单号查询数据的付款状态,如果当前状态是未付款,我们就改成付款超时,然后发短信或者邮件给他提醒付款。如果商品送到准备签名之前还未付款,站点可以打电话询问寄件人是否是货到付款,如果是,就让收件人付款,并将他的付款方式改成货到付款。如果不是,就不选择货到付款,让他付款,成功之后才让他提货。

为什么要使用分布式事务:

因为其他的一致性强,而且的效率比其他的快,而且我们团队之前一直在用,所以很熟悉;

个人责任模块3:积分模块

我们的转换方法很简单:一美元转换为一点。

物流到达目的地后,等待用户签收,签名成功后发送交易信息,增加用户总积分。

用户的积分可以用来兑换一些生活用品,当然都是包邮的,下单就可以。

积分也可以用来购买一些商品,首先对通过网关购买商品设置一个时限,也就是预热,防止并发量过高导致服务器崩溃。

而且我们的商品也是提前存储好的,这是为了防止超卖,借助decr,首先它们可以保证原子性,使用商品id作为key,当用户点击抢购的时候,向服务端发送请求,根据传递的商品id去查询库存和抢购开始时间查询,如果当前时间小于抢购开始时间,直接返回错误,防止用户拿到接口直接调用出现问题。

然后检查库存,如果库存小于等于0,则直接返回失败,这样后续的大量请求就不会对系统造成压力。

如果当前还有货,我们会根据当前用户ID和商品ID进行查询,购买成功后我们会标记当前用户,避免同一账号重复购买。

然后如果库存充足,并且没有重复抢购,就执行decr操作,decr操作会执行减一并返回当前值,然后再次判断当前值是否小于0,是则返回失败,否则调用以用户id和商品id为key,为默认的代表抢购成功(刚才说的重复抢购问题),然后MQ异步方法生成订单并返回成功标记给前端。

个人责任模块4:订单搜索

当我们通过订单进行查询的时候,需要在数据库中进行多表联查,然后展示在前台,但是在搜索的时候,考虑到并发量和联表查询,可能会出现几个问题:

首先,会增加数据库的连接数,造成数据库的压力;

其次,如果联合查询的数据量很大,查询效率会很低(引入正向索引和倒排索引)

因此我们将联合查询得到的数据信息保存到ES中,另外也考虑使用Solr,虽然都是基于实现,但是Solr创建索引的时候搜索效率会下降,实时搜索效率不高。es的实时搜索效率比较高,而且es支持分布,节点对外表现为相等,添加时节点会自动平衡。使用ES可以实现模糊搜索、分词查询、高亮、复合查询等。

索引库的更新。对于这个索引库的更新,我们利用的是数据库和ES之间的数据同步。它的工作原理很简单,就是在固定的时间执行我们配置文件中定义的SQL语句。它需要两个插件,一个用于读取msql数据,一个用于同步ES。

使用ES搜索的时候还需要注意分词器,默认自带分词器,但是ES是国外的产品,他们只会对英文进行分词,而我们中文分词会把每个汉字分成一个词,因此我选择了国内常用的IK分词器(之前听说过庖丁分词器)查询高亮:在ES查询的时候,我们可以设置高亮查询,指定高亮样式和高亮字段进行高亮,搜索符合条件的订单信息,并分页展示(量大的情况下如何提高ES的查询效率)

亲自负责模块 5:客户服务模块(es,,)

现在客服有两种类型:自动回复和人工客服。

为了实现自动回复,我们使用es的ik分词器,分出一些关键词,存放在es中。

比如用户输入快递,就会出现如下内容:(这个快递要多少天发货,这个快递要多少天才能到,发什么快递)

用户可以根据自己的需求进行查询,并将聊天记录保存到服务器。

对于人工客服,我们调用()实现点对点的聊天,当用户需要人工干预时,会创建一个作为唯一标识,然后把这个唯一标识发送到指定的客服机上,建立用户和客服之间的通话通道,直到对话结束,并保存聊天记录。

它是针对H5浏览器的实时通讯协议,可以实时推送数据,适用于广泛的工作环境,例如客服系统、物联网数据传输系统等。

个人责任模块6:交接表模块

交接单模块:在装车前我们根据目的地配送点数量生成相应的交接单,比如货物需要到5个配送点,就需要生成5张交接单,每张交接单按照装车车辆进行罗列。

当时我们考虑到交接单会产生大量的数据,数据结构比较松散,就想到用它来保存交接单信息。订单状态在发货过程中会不断更新,以内嵌数组的形式存储,订单的所有变化只要一次查询就能读出来。

之所以用它是因为它是非关系型数据库,扩展方便,可以做分布式文件存储。所以在项目中我们用它来存储交接订单信息(如订单ID、站点ID、内容、配送员、订单状态、配送员电话等),并且为了提高可用性和高并发,我们当时使用了3台服务器做成的副本集,其中一台做为主节点,另两台做为副本节点。这样当任何一台服务器宕机时,都会自动进行故障转移,不会影响应用程序的运行。为了减轻主节点读写压力过大的问题,我还在副本集上做了读写分离,这样写操作在主节点进行,读操作在副本节点进行。

分享