0
点赞
收藏
分享

微信扫一扫

windows下本地部署安装hadoop+scala+spark-【不需要虚拟机】

Raow1 01-24 12:00 阅读 8

云岚到家项目100问 v1.0

第一章 运营基础管理

你的项目是做什么业务的?

项目涉及哪些角色?

说下你的项目的业务流程?

你的项目包括哪些模块?

你的项目用的什么架构?

项目是基于Spring Cloud Alibaba框架构建的微服务项目,采用前后端分离模式进行开发

平台共包括四个端:运营端(PC)、服务端(APP)、机构端(PC)、用户端(小程序)

网关我们用的SpringCloudGateWay网关,最前边是Nginx进行负载均衡

服务层我们划分了运营基础服务、客户管理服务、公共服务、订单管理服务、抢单服务、派单服务、支付服务等微服务

服务层用到了Nacos、XXL-JOB、RabbitMQ、Elasticsearch、Canal、Sentinel等中间件

数据层用的是MySQL数据库,使用ShardingShphere进行分库分表,使用TiDB分布式数据库存储历史订单数据,还用到了消息队列RabbitMQ、Redis缓存及Elasticsearch等中间件

说说你的项目吧?

如何对现有代码进行断点调试?

1.Debug启动项目

2.设置断点:

3.执行代码:

4.调试过程中查看变量和调用栈:

5.继续执行:

6.停止调试:

运营基础工程用到哪些技术?

设计一个接口需要设计哪些内容?

如何去定义一个接口?

Mybatis-Plus有几种主键生成策略?

如何开发一个接口的持久层?

@Resource 和 @Autowired有什么区别?

如何开发controller方法?

项目的分页查询是怎么实现的?

如何开发一个接口?

如何开发一个接口的service方法?

接口的异常处理怎么实现的?

第二章 客户管理

本项目的认证方式有哪些?

小程序认证流程是什么?

当前认证通过的用户信息保存到哪里了?

前端的storage中

后端的ThreadLocal中

手机验证码认证流程是什么?

如何开发小程序定位功能?

手机验证码服务的实现方案?

你负责模块的怎么设计的

第三章 门户

实现一个门户用到的技术方案有哪些?

Spring Cache 用在项目哪里了?怎么用的?

Spring Cache @Cacheable 注解的工作原理?

Spring Cache有哪些常用的注解,都有什么用

什么是缓存穿透?如何解决缓存穿透?

什么是布隆过滤器?如何使用布隆过滤器?

什么是缓存击穿?如何解决缓存击穿?

什么是缓存雪崩?如何解决缓存雪崩?

如何保证缓存一致性?

注意:在使用缓存时不论采用哪种方式如果没有特殊要求一定要对key加过期时间,即使一段时间缓存不一致当缓存过期后最终数据是一致的

项目哪里进行了缓存,缓存方案是什么?

项目中如何保证缓存的一致性?

项目为什么要用xxl-job?

xxl-job的执行器和调度中心有什么区别?

项目中哪里用了xxl-job?怎么用的?

项目为什么要用Elasticsearch?数据很多吗?

项目中如何进行索引同步的?

如何保证MQ消息的可靠性?

可以百分百保证MQ的消息可靠性吗?

如何保证MQ幂等性?或 如何防止重复消费?

Canal是怎么伪装成 MySQL slave?

Canal数据同步异常了怎么处理?

如何保证Canal+MQ同步消息的顺序性?

第四章 预约下单

订单的状态有哪些?

订单表是怎么设计的?

常见的订单号生成规则有哪些?

自增数字序列

时间戳+随机数

订单类型+日期+序号

分布式唯一ID生成器

Feign和OpenFeign的区别?

微服务保护怎么做?

你的项目怎么实现微服务保护的?怎么实现熔断降级的?

微服务之间远程调用怎么实现的?

Service方法事务失效的原因是什么?

Spring 如何解决循环依赖?

你们对接的哪个支付接口?怎么对接的?

用户端是微信小程序,我们对接的小程序支付接口。

我们使用的SDK是微信支付的httpclient程序(wechatpay-apache-httpclient )。

首先用户发起支付会调用小程序的下单接口,微信返回一个下单标识。

小程序前端程序使用该下单标识调用小程序的方法唤起支付窗口,用户进行支付。

支付成功,微信通过通知接口调用服务端接口。

服务端程序也会调用微信的支付结果查询接口查询支付结果,根据支付结果更新订单的支付状态。

用户退款接口我们对接小程序的退款接口,用户发起退款申请,通过退款结果查询接口查询退款状态。

项目的支付服务有哪些接口?

项目的支付服务对接了微信、支付宝的Native、jsapi等常用支付方法。

支付服务提供以下接口:

支付接口,此接口请求第三方支付平台的下单接口,下单成功生成支付二维码返回给业务系统,如果已生成支付二维码会将二维码返回给业务系统。

支付结果查询接口,根据支付服务的交易单查询支付结果,支付服务会请求第三方的支付结果查询接口查询支付结果。

退款接口,此接口请求第三方支付平台的退款接口,如果已退款此接口会返回退款记录给业务系统。

支付通知接口,支付服务获取支付结果通过MQ通知业务系统。

支付服务设计了几张表?

核心的表有支付渠道表、交易单表、退款记录表。

支付接口:收到支付请求后请求第三方支付的下单接口,并向交易单表新增记录。

查询交易结果接口:请求第三方支付的查询支付结果并更新交易单表的支付状态。

接收第三方通过支付结果:更新交易单表的支付状态。

退款接口:新增退款记录

更新退款状态:请求第三方退款结果查询接口查询退款状态,并更新退款状态。

如何防止重复支付?

重复支付是一个订单客户支付多次,造成重复支付。

我们项目实现的是扫码支付,可能存在重复支付的问题,通过以下方式去避免重复支付:

1、同一个订单同一个支付渠道只生成一个支付二维码。

2、在请求第三方支付下单使用分布式锁控制不会重复请求第三方下单。

3、切换支付渠道时先关闭原渠道的交易单再生成新渠道的交易单。

4、使用定时任务每天扫描交易单表,如果存在多个支付成功的交易单则进行自动退款。

支付接口是怎么开发的?

项目有统一的支付服务与第三方支付平台对接,业务系统对接支付服务完成支付流程。

首先请求通过支付服务请求第三方支付平台的支付下单接口,如果是小程序支付下单成功会返回一个会话标识,前端通过该会话标识调起支付窗口,如果是扫码支付下单成功会返回二维码URL,生成二维码返回给前端。

用户支付成功,获取支付结果更新订单表的订单状态字段。

获取支付结果有两种方法:

调用 支付服务的查询支付结果接口查询支付结果。

通过监听MQ,支付服务将支付结果通知给业务系统。

项目中有用到MQ吗?怎么用的?

项目有很多地方都用到了MQ,我们用的是RabbitMQ。

\1. 数据同步用到了MQ

项目在数据同步中用到了MQ,我们用的是Canal加MQ将MySQL的数据同步到其它服务,比如:ES、Redis等。

具体的流程是:

Canal会定时读取数据库的binlog日志,解析出增加、修改及删除的数据内容并将其写入MQ。

同步程序监听MQ,收到增加、修改及删除的数据的消息后请求ES同步索引。

\2. 支付通知用到了MQ

项目在支付通知中用到了MQ,支付服务将支付结果发给MQ,订单服务监听MQ收到支付结果更新订单的支付状态。

具体的流程是:

支付服务将支付结果发送给专门传输支付通知的交换机,交换机使用的是topic类型,该交换机绑定了多个队列,因为考虑会有多个微服务对接支付通知,每个微服务监听一个队列。

当支付服务向交换机发送一条支付通知消息,所有绑定此交换机的队列会收到支付通知,业务系统收到支付结果后解析出业务系统应用标识,判断是否属于自己的支付结果通知,如果是再进行处理。

项目的退款功能怎么实现的?

取消订单执行退款操作。

1、首先使用一个事务保存以下数据

更新订单状态。

保存取消订单记录,记录取消的原因等信息。

保存退款记录。

2、事务提交后先启动一个线程请求支付服务的退款接口

3、定时任务扫描退款记录表,对未退款的记录请求支付服务进行退款,退款成功更新订单的退款状态,并删除退款记录。

说明:

第2步的作用为了第一时间申请退款,因为定时任务会有一定的延迟。

第3步的作用是由定时任务去更新退款的状态,因为调用了退款接口只是申请退款了,退款结果可能还没有拿到,通过定时任务再次请求支付服务的退款接口,拿到退款结果,更新订单的退款状态,并删除退款记录。

第五、六章 项目实战

优惠券包括几个模块?

优惠券模块包括:活动管理、抢券、核销三个模块。

活动管理

对优惠券活动进行管理,运营人员新增优惠券活动、修改优惠券活动、撤销优惠券活动及优惠券统计等。

抢券

到了优惠券发放时间用户进行抢券,抢券过程对优惠券库存、对用户领取优惠券数量等进行校验,抢券成功记录用户领取优惠券的记录。

核销

用户在下单时使用优惠券得到优惠金额,实付金额等于订单金额减去优惠金额,下单成功优惠券核销成功。

优惠券核销是指:顾客在购买商品使用优惠券,当此次消费符合优惠券的条件时提交订单后将优惠券的折扣应用到顾客的订单中,最后将优惠券标记为已使用或作废。

优惠券核销后还可以取消核销,如果用户取消订单会将优惠券取消核销即退回优惠券,退回优惠券后可以继续使用。

优惠券模块的核心表的哪些,是怎么设计的?

优惠券活动表

优惠券活动记录优惠券活动信息。

关键字段:活动id、活动名称、优惠券类型、折扣、发放时间等。

优惠券表

优惠券表记录用户领取的优惠券记录。

关键字段:用户id、活动id、折扣、优惠券类型、有效期等。

优惠券核销表:

优惠券核销表记录用户使用优惠券的记录,记录是哪个用户的哪个订单使用了哪个优惠券。

关键字段 :用户id、优惠券id、订单id,核销时间。

优惠券退回表

优惠券退回表记录记录用户退回优惠券的信息。

关键字段:用户id、优惠券id、退回时间。

在活动管理模块开发中你遇到了什么问题?

设计方面、开发调试方面都可以说。

第七章 系统优化

什么是状态机,它解决了什么问题?

状态机就是对状态进行统一管理的数学模型,应用在软件领域是状态机设计模式,有了状态机就可以避免在业务代码中对状态进行硬编码,增加系统的可扩展性。状态机设计模式包括四个要素:现态、事件、动作、次态。

1、现态:是指当前所处的状态。

2、事件:当一个条件被满足,状态会由现态变为新的状态,事件发生可能会触发一个动作,或者执行一次状态的迁移。

3、动作:发生事件执行的动作,动作执行完毕后,可以迁移到新的状态,也可以仍旧保持原状态。动作不是必需的。

4、次态:条件满足后要迁往的新状态。

通过状态机更改状态只需要指定事件名称即可,避免了状态字段在代码中硬编码。

如何实现一个订单状态机?

我们封装了一个状态机组件,包括:状态机抽象类、状态接口、事件接口、动作接口、快照基础类。

实现一个订单状态机的步骤(其它状态实现类似):

\1. 定义订单状态枚举类,实现状态接口,定义订单的各个状态

\2. 定义订单状态变更事件类,实现事件接口,实现状态之间变更的事件

\3. 定义订单快照类,继承快照基础类

\4. 定义事件变更动作类实现动作接口,并在订单状态变更事件类中指定动作类

\5. 定义订单状态机类继承状态机抽象类,指定状态机的名称、指定订单初始状态。

本项目使用状态机实现什么功能?为什么这样做?

本项目使用状态机对订单的状态的进行管理。

订单的状态很多,并且状态之间的变更复杂多样,如果在业务代码中对状态进行硬编码不利于系统的扩展和维护,使用状态机就可以对订单的状态进行统一管理,避免在业务代码中对订单状态进行硬编码,提高系统的可扩展性。

使用状态机对订单状态的变更:

在状态机中针对订单状态之间的变更定义了事件,每个事件对应一个动作类,对订单状态的变更是在动作类统一维护,这样就实现了对状态的统一管理,避免在业务代码中对订单状态进行硬编码,提高系统的可扩展性。

项目为什么进行分库分表?

架构师考虑订单数据量达到一定程度会影响系统的性能,在系统架构时我们项目对订单数据库进行分库分表。

项目使用ShardingSphere实战订单数据库的分库分表。

分库分表有哪些形式?

分库分表包括四种形式:垂直分表、垂直分库、水平分库、水平分表。

垂直分表是将一个表按照字段分成多表,每个表存储其中一部分字段,比如商品表按冷热字段进行拆分,商品基本信息表和商品详细信息表。

垂直分库是指按照业务将表进行分类,分布到不同的数据库上面,微服务架构下通常会对数据库进行垂直分为,不同业务数据放在单独的数据库中,比如:客户信息数据库、订单数据库等。

水平分库是把同一个表的数据按一定规则拆到不同的数据库中,每个库可以放在不同的服务器上,比如:单数订单在db_orders_0数据库,偶数订单在db_orders_1数据库。

水平分表是在同一个数据库内,把同一个表的数据按一定规则拆到多个表中,比如:0到500万的订单在orders_0数据、500万到1000万的订单在orders_1数据表。

项目的分库分表是怎么做的?

对订单数据库进行分库:

分为三个数据库

分库键:用户id

分库策略:用户id除以3求余

参考历史经验,前期设计三个数据库,每个数据库使用主从结构部署,足以支撑项目几年运行,虽然哈希存在数据迁移问题,在很长一段时间也不用考虑这个问题。

分表方案:

分表键:订单号

分表策略:根据订单范围分表,以1500万为单位进行分表,0—1500万落到table_0,1500万—3000万落到table_1,依次类推。

根据范围分库不存在数据库迁移问题,方便系统扩容。

订单查询是如何优化的?

对于根据订单id查询订单详情优化:

查询数据库改为查询订单快照缓存,状态机组件提供快照缓存的查询方法,将快照信息缓存到 redis提供查询效率。

对于C端订单列表的查询优化:

分页查询改为滚动查询,避免count查询。

首先使用覆盖索引查出符合条件的订单ID(主键),再根据订单ID查询订单信息。

将订单信息缓存到Redis的Hash结构中。

缓存同步:当订单状态变更则删除Hash结构中的对应的订单信息。

对于运营端订单列表的查询优化:

使用覆盖索引查出符合条件的订单ID(主键),再根据订单ID查询订单信息。

订单快照是怎么实现的?

在订单状态变更时会记录订单的快照信息,在订单状态机的方法中实现了保存订单快照,订单快照保存到数据库的订单的快照。

提供快照查询接口,根据订单id查询订单详情是查询订单快照的信息。

快照查询接口实现Redis缓存,根据订单Id查询缓存信息,先从缓存查询如果缓存没有则查询快照表的数据然后保存到缓存中。缓存设置了过期时间是30分钟。

当订单状态变更,此时订单最新状态的快照有变更,会删除快照缓存,当再次查询快照时从数据库查询最新的快照信息进行缓存。

什么是聚集索引和非聚集索引?

聚集索引就是主键的索引,聚集索引的叶子结点存储了一行的数据。

非聚集索引是普通字段的索引,可以是联合索引,非聚集索引的叶子结点存储的是主键值。

什么是回表查询?

当从非聚集索引无法查询所需要的数据时会根据非聚集索引查询到主键再查询聚集索引拿到一行数据。

什么是覆盖索引?

覆盖索引(covering index)是一种优化手段,指一个查询语句的执行只需要从非聚集索引中就可以得到查询记录,而不需要回表去查询聚集索引,可以称之为实现了索引覆盖。

如何知道一个SQL有没有使用索引?

通过获取该SQL的执行计划判断是否使用索引。

使用explain关键字,后边跟上SQL语句即可查询该SQL的执行计划。

在执行计划中,通过key、key_len可以知道是否使用索引及使用的哪个索引。

通过Extra可以判断是否实现覆盖索引。

当Extra的值是Using index时表示使用了覆盖索引。

第八章 秒杀抢购

秒杀抢购常用的技术方案有哪些?

\1. 缓存方案

使用缓存技术(如Redis)来存储热点数据,例如商品信息和库存数量。这样可以减轻数据库的压力,提高读取数据的速度。

\2. 异步处理方案

当用户成功秒杀后,将抢购信息发送到队列,然后由消费者多线程异步处理订单,减轻系统的实时压力,使用Redis、RabbitMQ等技术都可以实现队列。

\3. 防止超卖方案

超卖是最终下单购买数量大于库存数量,比如:库存100个用户最终购买了101个,多出这一个就是超卖了,在秒杀抢购业务中这也是需要解决的问题,可以使用分布式锁、Redis等技术都可以防止超卖。

\4. 限流与防刷方案

使用限流算法(如令牌桶、漏桶算法)来控制请求的并发数,防止服务器被过多请求压垮。同时,可以在服务端实现防刷机制,例如限制同一IP的请求频率,以及使用验证码等技术。

\5. 数据库优化方案

对数据库进行优化,包括索引的设计、SQL语句的优化、数据库连接池的使用等,以提高数据库的查询和更新速度。

\6. 数据库分库分表方案

在数据库层面进行分库分表,将数据分散存储在不同的数据库实例或表中,提高数据库的读写性能。

\7. 负载均衡

使用负载均衡技术,例如Nginx、负载均衡器等,将请求分发到多个服务器上,增加系统的处理能力。

\8. CDN加速

使用内容分发网络(CDN)来加速静态资源的访问,例如商品图片、CSS和JavaScript文件等,提高网页加载速度。

\9. 安全性处理

确保系统的安全性,防止SQL注入、XSS攻击(跨站脚本攻击)等,同时在后端实现防刷、验证码等安全措施,保护系统免受恶意攻击。

抢券查询的缓存是怎么做的?

我们使用Redis缓存活动信息。

活动信息缓存方案如下:

redis结构:String类型(存储的json串)

value: 符合条件的优惠券活动列表JSON数据,我们将一个月以内即将开始的活动和进行中的活动进行缓存。

过期时间:永不过期

缓存一致性方案:通过定时预热程序保证缓存一致性

活动定时预热程序是怎么做的?

\1. 首先说明缓存方案 (参考问题:抢券查询的缓存是怎么做的?)

\2. 使用xxl-job定时执行活动预热程序,从数据库查询出符合条件的活动信息存储到redis中。

解决超卖问题有哪些方案?

使用数据库行锁实现悲观锁和乐观锁解决超卖问题,适用并发量不大的场景。

使用Redis分布式锁解决超卖问题,控制多个Jvm进程去争抢同一个锁,将并发操作库存改为同步执行。

使用Redis原子操作解决超卖问题,Redis命令具有原子性,将库存放在Redis中,使用decr命令去扣减库存。

什么是悲观锁和乐观锁?

悲观锁是一种悲观思想,总认为会有其它线程修改数据,为了保证线程安全所以在操作前总是先加锁,操作完成后释放锁,其它线程只有当锁释放后才可以获取锁继续操作数据。synchronized和ReentrantLock都可以实现悲观锁。

乐观锁则是一种乐观思想,认为不会有太多线程去并发修改数据所以谁都可以去执行代码。

Java提供的CAS机制可以实现乐观锁。CAS(Compare And Swap )即比较并交换,在修改数据前先比较版本号,如果数据的版本号没有变化说明数据没有修改,此时再去更改数据。

数据库的怎么实现悲观锁和乐观锁?

数据库的行级锁可以实现悲观锁也可以实现乐观锁。

执行select … for update 实现悲观锁,select … for update 会锁住符合条件的行的数据

数据库的行级锁也可以实现乐观锁,通用的做法是在表中添加一个version版本字段,在更新时对比版本号,更新成功将版本号加1,SQL示例如下:

针对扣减库存业务扣减库存示例SQL:

项目中保证Redis原子操作用什么方案?

Redis命令都具有原子性。

通过 MULTI 事务命令可以实现多个Redis命令具有原子性。

Redis+Lua也可以实现多个Redis命令具有原子性,此方式可以使用Lua语言添加执行逻辑,相对MULTI 命令较灵活,本项目使用Redis+Lua。

抢券是怎么做的?或方案是什么?

1、由预热程序将待生效库存同步到redis(活动开始将不允许更改库存)

2、活动开始后,抢券程序请求Redis扣减库存,扣减库存成功向抢券成功队列和抢券同步队列写入记录

Redis中两个队列的作用如下:

抢券成功队列:为了校验用户是否抢过该优惠券。

抢券同步队列:将抢券结果同步到数据库

3、通过定时任务程序根据Redis中同步队列记录的用户抢券结果信息将数据同步到MySQL,具体操作如下:

向优惠券表插入用户抢券记录。

更新优惠券活动表的库存。

写入数据库完成后删除Redis中同步队列的相应记录,删除后表示同步完成,如果同步过程失败将保留Redis同步队列的相应记录。

抢券业务的Redis数据结构用的什么?具体说说

\1. 活动信息

缓存结构:String类型:

key: “ACTIVITY:LIST”

value: 符合条件的优惠券活动列表JSON数据。

过期时间:永不过期

缓存一致性方案:通过预热程序保证缓存一致性

\2. 优惠券活动库存

缓存结构:Hash

RedisKey:COUPON:RESOURCE:STOCK:{活动id%10}

HashKey:活动id

HashValue: 库存

过期时间:永不过期

缓存一致性方案:通过预热程序保证缓存一致性

\3. 抢券成功队列

缓存结构:Hash

RedisKey:COUPON:SEIZE:LIST:活动id_{活动id%10}

HashKey:用户id

HashValue:1

过期时间:永不过期

缓存一致性方案:

抢券成功写入此队列。

活动结束删除队列。

\4. 抢券同步队列

缓存结构:Hash

RedisKey:QUEUE:COUPON:SEIZE:SYNC:{活动id%10}

HashKey:用户id

HashValue:活动id

过期时间:永不过期

缓存一致性方案:

抢券成功写入此队列。

每同步成功一条记录删除同步队列的相应数据。

项目中如何定义的线程池?

项目中使用线程池从Redis的多个同步队列获取数据向MySQL同步。

我们通过创建ThreadPoolExecutor对象定义线程池,这里我们关注以下参数:

核心线程数:初始为1

最大线程数:为同步队列的个数,同步队列个数控制在10到20

线程空闲时间:120秒,因为我们定时任务每隔1分钟调用线程池去处理任务,如果线程在120秒没有使用则销毁辅助线程,只留核心线程。

阻塞队列:根据需求我们不需要任务存放在队列,如果线程没有空闲将拒绝任务,所以阻塞队列使用的SynchronousQueue,此队列不存储任务。

拒绝策略:使用的是DiscardPolicy,丢弃任务不抛出异常策略,

项目分布式锁怎么实现的?

项目使用Redisson实现分布式锁。

我们在使用多线程从同步队列查询并处理数据时,同一个队列只允许一个线程去处理,这里我们用到了分布式锁,锁的粒度是每个同步队列。

由于从同步队列同步数据的时间不确定,所以我们使用了redisson的看门狗机制,当同步任务完成将分布式锁释放。

如何将Redis中的数据异步同步到MySQL?

我们项目开发了一个同步组件,用于将Redis Hash结构中的数据同步到MySQL,具体是这样做的:

1、使用线程池从多个同步队列中查询数据,每个线程处理一个同步队列。

同步队列的个数通常配置10到20即可。

在同步时为了保证一个线程只处理一个队列,这里使用的分布式锁进行控制。

2、使用redisTemplate.opsForHash().scan(H key, ScanOptions options)方法从hash表获取数据。

3、读取到数据后将数据库写入MySQL,最后将写入成功的的数据表示已经同步成功,将从Redis的Hash表中删除。

秒杀异步处理怎么实现的?

在秒杀抢购场景中,为了流量削峰可以在Redis存储秒杀结果,再通过定时任务将秒杀结果同步到数据库中。

本项目使用了一个数据同步组件,将Redis中的数据异步同步到MySQL,具体的做法是:

将Redis中的数据异步同步到MySQL参考“如何将Redis中的数据异步同步到MySQL?”

下单时如何拿到可用的优惠券列表?

下单时订单管理服务会请求优惠券服务获取可用优惠券列表。

根据用户id、订单金额去匹配优惠券的规则,符合条件的优惠券为可用优惠券,规则如下:

• 属于当前用户的优惠券

• 符合下边条件的优惠券:

订单金额大于等于满减金额

优惠金额小于订单金额

优惠券还没有过期

优惠券还没有使用

优惠券核销的交互流程是什么?

\1. 优惠券核销

用户端请求订单管理服务创建订单信息,订单管理服务远程调用优惠券服务核销优惠券,下单成功且优惠券核销成功。

优惠券核销执行以下操作:

• 根据优惠券id标记优惠券表中该优惠券已使用。

• 向优惠券核销表添加记录。

\2. 优惠券退回

用户端取消订单,订单管理服务执行取消订单逻辑,如果该订单使用了优惠券则请求优惠券服务退回优惠券。

优惠券退回执行以下操作:

• 添加优惠券退回记录。

• 如果优惠券已过期则标记该优惠券已作废,否则标记该优惠券未使用。

• 删除核销记录。

在优惠券核销和退回操作时由于是远程请求优惠券服务,这里使用seata控制分布式事务。

什么是分布式事务?

什么是本地事务?

基于本应用自己的关系型数据库的事务称为本地事务,在service方法通过添加@Transactional注解进行本地事务控制。

什么是分布式事务?

在分布式系统环境下由多个服务通过网络通信协作去完成一次事务,这称之为分布式事务。

分布式事务的场景有哪些?

多个微服务之间通过远程调用完成一次分布式事务。

单服务请求多数据库完成一次事务。

多服务请求单数据库完成一次事务。

什么是CAP原理?

CAP分别表示一致性、可用性、分区容忍性.

CAP理论要强调在分布式系统中C、A、P这三点不能全部满足,要么满足AP、要么满足CP。

能够说出本项目抢单的实现方案?

\1. 用户下单且支付成功,订单信息进入抢单池

\2. 通过Canal+MQ将抢单池的信息同步到Elasticsearch、同步到Redis中。

\3. 服务人员或机构进入抢单界面,请求Elasticsearch查询订单信息,这里可以根据地理坐标搜索附近的订单

\4. 服务人员或机构点击抢单、请求Redis执行Lua脚本完成抢单。

\5. 通过定时任务将Redis中抢单成功的结果同步到数据库。

抢单池是怎么设计的?

\1. 在数据库中创建抢单池表,存储待抢单的订单信息。

\2. 用户下单并支付成功将订单信息写入订单表和抢单池表。

\3. 通过Canal+MQ将抢单池的信息同步到Elasticsearch和Redis中。

同步到Elasticsearch是为了通过ES的地理坐标搜索功能查询订单信息。

同步到Redis是为了将抢单池库存信息同步到Redis,服务人员抢单时请求Redis完成。

\4. 服务人员抢单完成扣减Redis中抢单池的库存,并记录抢单结果。

\5. 最后通过异步任务将抢单结果同步到数据库。

订单分流的业务流程是什么

\1. 用户下单且支付成功

\2. 根据服务预约时间与当前时间的间隔判断,如果间隔小于2小时(默认)将订单信息写入派单池表和抢单池表。

\3. 如果服务预约时间与当前时间的间隔大于2小时将订单信息只写入抢单池表。

搜索附近怎么实现?

对于地理坐标不变的场景,比如:搜索附近的酒店、搜索附近银行等,可以提前将搜索目标信息同步到Elasticsearch中,再通过Elasticsearch的geo去根据地理坐标去搜索附近几公里内的酒店、银行等。

使用geo时在索引中设置geo_point类型的字段,查询时需要传入经纬度坐标及距离(公里),将查询以此经纬度坐标为中心方圆几公里的信息。

对于地理坐标变的场景,比如:搜索附近的骑手、搜索附近的出租车,这里就需要在手机定时上报坐标到系统中,系统收到上传的坐标更新至Elasticsearch中,再通过geo去搜索附近的骑手、搜索附近的出租车等。

抢单是如何防止超卖的?

\1. 首先提前将抢单池库存信息同步到Redis

\2. 抢单时请求Redis执行Lua脚本扣减库存,如果库存不足将会抢单失败

\3. 通过Lua脚本执行抢单逻辑保证了原子性。

第九章 订单管理

能说出订单模块涉及到的业务有哪些?

订单模块所包含的业务功能:

\1. **订单创建:**允许用户创建新订单,选择商品、商品数量、支付方式等。

\2. **订单查询:**提供用户查询订单的功能,可以按订单状态、时间范围等条件进行筛选。

\3. **订单详情:**显示订单的详细信息,包括商品清单、价格、运费、收货地址等。

\4. **订单支付:**提供多种支付方式,支持用户完成订单支付操作。

\5. **订单状态管理:**管理订单的不同状态,如待支付、已支付、已发货、已完成、已取消等。

\6. **价格计算:**根据用户选择的商品、优惠券、运费等信息计算订单总金额。

\7. **订单导出:**提供导出订单数据的功能,以便进一步的分析和报表生成。

\8. **订单统计分析:**对订单数据进行统计和分析,为业务决策提供参考。

\9. **历史订单:**通过冷热分离对已经完成的历史订单进行单独管理,提高热数据的处理效率。

相关的外部系统/模块:

\1. **库存管理:**在订单创建时,要检查商品库存是否充足,成功支付后要扣减库存。

\2. **优惠券管理:**允许用户使用优惠券,系统需要验证优惠券的有效性,并计算折扣金额。

\3. **订单支付:**提供多种支付方式,支持用户完成订单支付操作。

\4. **物流管理:**记录订单的物流信息,包括快递公司、快递单号等,方便用户追踪物流信息。

\5. **售后服务:**提供用户申请退款、退货、换货等售后服务,需要有相应的审核和处理流程。

\6. **评价和评论:**允许用户对已完成的订单进行评价和评论,提供用户反馈。

订单的状态是怎么流转的?

订单状态共有7种:

待支付:用户下单成功,该订单的初始状态为待支付。

派单中:用户支付成功后订单的状态由待支付变为派单中。

待服务:服务人员或机构抢单成功订单的状态由派单中变为待服务。

服务中:服务人员开始服务,订单状态变为服务中。

订单完成:服务人员完成服务,订单状态变为订单完成。

已取消:订单是待支付状态时用户取消订单,订单状态变为已取消。

已关闭:订单已支付状态下取消订单后订单状态变为已关闭。

订单模块的数据库是怎么设计的?

订单数据库进行分库分表设计:

分库方案:

设计三个数据库。

订单表:根据用户id哈希决定数据库(分库表达式为:db_用户id % 3)

服务单表:根据服务人员id哈希决定数据库,分库表达式:jzo2o-orders-${serve_provider_id % 3}

快照表:根据db_shard_id哈希决定数据库,分库表达式:jzo2o-orders-${db_shard_id % 3},db_shard_id即用户的id

每个数据库使用主从结构部署,可以支撑项目3年左右的运行,虽然哈希存在数据迁移问题,在很长一段时间也不用考虑这个问题。

分表方案:

订单表:根据订单范围分表,0—1500万落到table_0,1500万—3000万落到table_1,依次类推。根据范围分表不存在数据库迁移问题,方便系统扩容。

服务单表:同订单表,分表表达式为:orders_serve_${(int)Math.floor(id % 10000000000 / 15000000)}。

快照表:同订单表,分表表达式为:biz_snapshot_${(int)Math.floor((Long.valueOf(biz_id)) % 10000000000 / 15000000)}

状态机组件是怎么实现的?

为了对订单状态进行统一管理我们开发了状态机组件,状态机组件适合对状态较多且状态之间变化复杂进行状态信息进行统一管理。

根据状态机设计模式开发状态机组件,状态机设计模式简单理解就是通过状态变更事件去更改状态,状态变更事件包括变更前的状态和变更后的状态,通过变更事件可以将原状态更改为新状态。

\1. 首先定义状态接口,接口中包括获取状态代码和状态名称等方法。

订单状态会单独定义在枚举类中,并实现此状态接口。

\2. 定义状态变更事件接口,接口中包括获取变更前的状态和获取变更后的状态等方法。

订单状态变更事件定义在枚举类中,并实现状态变更事件接口。

\3. 定义快照抽象类

订单快照类会继承此抽象类

\4. 定义状态机抽象类

订单状态机继承状态机抽象类,实现抽象方法,指定状态机的名称,以及初始状态。

在变更订单状态时使用订单状态机的方法去变更。

在状态机中针对订单状态之间的变更定义了事件,每个事件对应一个动作类,对订单状态的变更是在动作类统一维护,这样就实现了对状态的统一管理,避免在业务代码中对订单状态进行硬编码,提高系统的可扩展性。

下单接口是怎么实现的?

从以下几个方面回答:

订单表如何设计

订单号生成规则

下单接口如何防止重复提交

如何计算价格

存在分布式事务如何解决

怎么防止订单重复提交?

重复提交通常是因为网络不稳定,用户提交后没有响应用户重复点提交按钮导致。

通过前端和后端共同完成。

前端:

在用户点击提交按钮后,立即将按钮禁用,防止用户多次点击。

后端:

使用分布式锁,以用户id+服务id作为分布式锁,锁定下单接口10秒,10秒内只会提交一次。

如何保证一个外部接口的安全性?

对于登录、支付包含敏感信息的接口一定要使用https进行加密传输。

通过签名、验签防止内容篡改,我们对接第三方支付接口都会进行签名验签的过程,使用非对称加密算法 更安全,比如:SHA256 with RSA算法。

通过加密、解密防止内容暴露,通过非对称加密算法 更安全。

项目中用到设计模式了吗,具体说说?

单例模式:controller、service、mapper三层的bean都是单例。

工厂模式:spring容器就是一个工厂,在Spring中,BeanFactory 接口就是一个典型的工厂方法模式的例子。

策略模式:我们在开发取消订单业务时使用了策略模式,因为不同的场景执行取消订单的逻辑不同,我们使用使用策略模式实现提高了系统的扩展性。

首先定义策略接口

再定义不同的策略类即不同场景下取消订单的逻辑,每个策略类定义的bean名称为“用户类型:订单状态”。

在取消订单时通过拿到用户的类型及当前订单的状态,根据用户类型和订单状态得到具体的策略对象,执行该策略对象的取消订单方法。

删除订单怎么实现?

删除订单和取消订单不同,取消订单的目的是终止订单,删除订单是不希望订单信息出现订单列表中。

用户删除订单后订单信息将不在订单列表显示。

也有平台提供订单回收站查询功能,即查询已经删除的订单。

根据需求,我们在订单表添加逻辑删除标记,删除订单相当于隐藏订单。

在订单表添加字段:display (1:展示,0:隐藏

删除订单将此字段设置为0。

在订单列表中只查询display 为1的订单信息。

服务人员现场服务的过程是如何管理的?

服务人员抢单成功将生成服务单,服务单有5个状态:

待服务:服务人员抢单成功生成服务单,服务单的初始状态为待服务。

待分配:机构抢单成功生成服务单,服务单的初始状态为待分配,机构分配服务人员此时状态改为待服务。

服务中:服务人员去现场服务,开始服务后服务单的状态由待服务变为服务中。

服务完成:服务人员完成家政服务,服务单的状态由服务中变为服务完成。

服务取消:用户取消订单或运营人员取消订单后服务单状态改为已取消。

服务人员开始服务需要上传服务前的照片进行存档,服务完成后也需要上传服务后的照片进行存档。

订单的冷热分离是怎么做的?

本项目的订单在15日后将不再变更状态,通过订单冷热分离技术将历史订单迁移到历史订单数据库,因为我们要对历史订单进行统计分析并且提供查询接口,所以历史订单数据库使用TiDB分布式数据库存储。

具体迁移的过程:

当订单完成,取消、关闭时将订单信息写入同步表。

通过Canal+MQ将同步表的订单数据同步到历史订单数据库的待迁移表中,具体过程如下:

Canal读取binlog将写入同步表的数据写入MQ。

历史订单服务监听MQ,获得同步表的订单数据。

历史订单服务将订单数据写入待迁移表。

历史订单服务启动定时任务,每天凌晨将昨天0点到昨天24点之间符合条件(订单完成15日后)的订单信息迁移到历史订单表。

每次迁移完成将迁移完成的历史订单从待迁移表删除。

TiDB数据库与MySQL有什么不同?

TiDB支持 MySQL驱动,开发中通过MySQL驱动访问TiDB,与访问MySQL基本没有区别,SQL中避免使用MySQL的一些复杂函数,TiDB可能不支持。

下边是它们的区别:

架构:

TiDB: TiDB 是一个分布式数据库系统,采用了分布式架构。它将数据水平分片,并在多个节点上存储分片数据。

MySQL: MySQL 是一个传统的单节点数据库管理系统,尽管有主从架构和分区功能,但主要是为单节点或主从复制场景设计的。

事务处理:

TiDB: TiDB 是分布式数据库支持分布式事务。

**MySQL:**MySQL不是分布式事务,是本地数据库的事务控制支持ACID特性。

存储引擎:

TiDB: TiDB 使用了自己的分布式存储引擎 TiKV(TiDB Key-Value),该存储引擎支持分布式架构,并且是 TiDB 的核心组件之一。

MySQL: MySQL 支持多种存储引擎,如 InnoDB、MyISAM 等。不同的存储引擎有不同的特性和适用场景。

水平扩展:

TiDB: TiDB 支持水平扩展,可以通过添加新的 TiDB 节点来增加集群的容量和性能。

MySQL: MySQL 在垂直方向上可以通过增加服务器的资源来进行扩展,但水平扩展需要借助第三方框架或中间件来完成。

订单统计分析是怎么实现的?

为了提高统计效率进行分层次聚合,再基于分层聚合的统计结果进行二次统计。

本项目对订单数据进行统计采用滚动统计的方式,每次统计近15天的数据(如果数据量大可减少统计时段长度),采用滚动式统计的好处是防止统计任务执行失败漏掉统计数据。

分层聚合的粒度有两种:

按天统计,将统计结果存储至按天统计表。

按小时,将统计结果存储至按小时统计表。

有了分层聚合的统计结果,根据用户需求基于分层聚合的统计结果进行二次统计,其统计效率会大大提高,并且有此需求无需进行二次统计直接查询分层聚合结果表即可。

经营看板功能怎么实现?

包括两个步骤:

1、首先根据经营看板的需求先进行统计分析,并将统计分析的结果存到数据库表中。

2、为前端提供获取看板数据的接口,并将数据进行缓存。

先说统计分析:

一种是使用大数据平台进行统计分析,大数据平台进行统计分析后将结果存储数据库表,java程序读取统计结果返回给前端。

第二种是基于数据库进行统计分析,为了提高统计效率进行分层次聚合,再基于分层聚合的统计结果进行二次统计。

本项目分层聚合的粒度有两种:

按天统计,将统计结果存储至按天统计表。

按小时,将统计结果存储至按小时统计表。

再根据需求编写获取看板数据的接口,并将数据进行缓存。

设置缓存过期时间,通常30分钟以内,根据监控数据变化的实时性去设置,本项目缓存数据为30分钟,当缓存过期重新统计最新的数据在看板展示。

数据导出功能如何实现?

首先制定excel模板。

再编写获取数据的service方法。

再通过EasyExcel的API向excel输出流中写数据,通过输出流最终将excel文件导出到客户端。

数据导入功能如何实现?

根据要导入的数据定义模型类。

编写解析数据的方法,对脏数据进行过滤,对数据进行去重。

将要导入的文件上传到服务器。

通过EasyExcel的API解析excel文件,解析一条数据处理一条数据,最终将数据进行存储。

第十章 派单调度

派单调度的整体交互是什么?

\1. 使用Canal+MQ将服务提供者(服务人员和机构)信息(经纬度坐标、接单状态、当前接单数等)同步Elasticsearch中。

\2. 将派单池同步到Redis,派单池中是待派单的订单信息。

\3. 通过定时任务定时派单,从Redis派单池查询多个订单,使用线程池对多个订单进行派单,每个线程负责一个订单。

\4. 派单过程首先根据订单信息(服务地址、服务项目)从Elasticsearch中的服务提供池中找到符合地理位置及服务项目等基础条件的服务提供者。

\5. 如果找到多个服务提供者,根据派单策略通过责任链模式对服务提供者进行规则匹配,最终找到一个服务提供者。

\6. 系统调用抢单接口进行机器抢单,最终将服务提供者与订单撮合匹配成功。

责任链模式用在哪里?怎么使用?在开发中注意什么?

责任链模式适用于那些处理逻辑可以被划分成多个独立处理器,并且这些处理器之间存在某种顺序关系组合成一个链,依次执行链中的处理器,最终拿到处理结果。

通常应用在如下场景:

事件处理系统: 在事件处理系统中,一个事件可能经过多个处理者处理,每个处理者负责一部分的事件处理逻辑。责任链模式可以用于处理事件的分发和处理。

请求传递与处理解耦: 当一个请求可能被多个处理者中的一个处理,而请求的发送者不需要知道是哪个处理者来处理时,责任链模式可以实现请求的发送者和接收者的解耦。

多级审批流程: 典型的场景是多级审批流程,例如请假审批、报销审批等。不同级别的审批人员可以构成一个责任链,每个审批人员处理请求后,可以将请求传递给下一级审批人员,直到得到最终审批结果。

日志记录与处理: 日志记录可以通过责任链模式来实现。不同级别的日志处理者可以负责记录不同级别的日志信息,形成一个级别递增的责任链。

使用责任链模式步骤:

\1. 定义责任接口或抽象类。

\2. 定义处理器类

\3. 将多个处理器组成一个链。

\4. 客户端通过链中的第一个处理器开始执行。

开发中注意以下几点:

\1. 确保每个处理者的处理逻辑清晰: 每个具体处理者应该专注于自己的处理逻辑,确保责任链的每个环节都清晰可理解。

\2. 避免形成无限循环: 在设计责任链时,要避免形成闭环,确保请求不会在责任链中无限循环。

\3. 动态修改责任链: 责任链模式允许在运行时动态修改处理者链,这样可以灵活地调整请求的处理顺序。

\4. 适时终止责任链: 某个处理者在处理请求时,如果确定无需继续传递请求,可以适时终止责任链的传递,提高效率。

派单策略是怎么定义的?

派单程序支持以下策略:

距离优先策略:谁距离近谁优先,距离相同看接单数,谁接单数少谁优先

评分优先策略:谁评分高谁优先,评分相同看接单数,谁接单数少谁优先

最少接单优先策略:谁接单数少谁优先,如果接单数一样看评分,谁的分高谁优先

每个策略会根据不同中的规则去匹配,比如:距离优先策略首先按距离排序,获取最近的服务人员,我们是通过Elasticsearch计算服务人员位置与用户下单位置之间的距离,距离值是小数的转为整数,如果最近的服务人员距离相同的继续根据接单数去判断,谁的接单数少谁优先,如果接单数也一样则随机选一个。

我们使用的责任链模式去进行规则匹配,创建了距离优先规则、评分优先规则、最少接单数优先规则。

不同的派单策略就是对三个规则的不同组装,所以我们通过策略模式定义了距离优先策略、评分优先策略、最少接单优先策略。

拿距离优先策略举例:

距离优先策略组装责任链模式的顺序是:

距离优先规则—》最少接单数优先规则

举报

相关推荐

0 条评论