可以百分百保证MQ的消息可靠性吗?
保证消息可靠性分两个方面:保证生产消息可靠性和保证消费消息可靠性。
保证生产消息可靠性:
生产消息可靠性是通过判断MQ是否发送ack回执,如果发nack表示发送消息失败,此时会进行重发或记录到失败消息表,通过定时任务进行补偿发送。如果Java程序并没有收到回执(如jvm进程异常结束了,或断电等因素),此时将无法保证生产消息的可靠性。
保证消费消息可靠性:
保证消费消息可靠性方案首先保证发送消息设置为持久化,其次通过MQ的消费确认机制保证消费者消费成功消息后再将消息删除。
虽然设置了消息持久化,消息进入MQ首先是在缓存存在,MQ会根据一定的规则进行刷盘,比如:每隔几毫秒进行刷盘,如果在消息还没有保存到磁盘时MQ进程终止,此时将会丢失消息。虽然可以使用镜像队列(用于在 RabbitMQ 集群中复制队列的消息,这样做的目的是提高队列的可用性和容错性,以防止在单个节点故障时导致消息的丢失。)但也不能百分百保证消息不丢失。
虽然我们加了很多保证可靠性的机制,这样也只是去提高消息的可靠性,不能百分百做的可靠,所以使用MQ的场景要考虑这种问题的存在,做好补偿处理任务。
如何保证MQ幂等性?或 如何防止重复消费?
保证MQ幂等性通常是指保证消费者消费消息的幂等性。
1、使用数据库的唯一约束去控制。
比如:添加唯一索引保证添加数据的幂等性
2、使用token机制
发送消息时给消息指定一个唯一的ID
发送消息时将消息ID写入Redis
消费时根据消息ID查询Redis判断是否已经消费,如果已经消费则不再消费。
Canal是怎么伪装成 MySQL slave?
1、Canal模拟 MySQL slave 的交互协议,伪装自己为 MySQL slave ,向 MySQL master 发送dump 协议
2、MySQL master 收到 dump 请求,开始推送 binary log 给 slave (即 canal )
。一旦连接建立成功,Canal会一直等待并监听来自MySQL主服务器的binlog事件流,当有新的数据库变更发生时MySQL master主服务器发送binlog事件流给Canal。
3、Canal会及时接收并解析这些变更事件并解析 binary log
Canal数据同步异常了怎么处理?
检查MySQL主从状态: 检查MySQL主从服务器的状态,确保主从服务器之间的连接正常,MySQL主库的binlog开启,Canal连接是否正常等。如果有网络故障、MySQL主从服务器之间的通信问题,或者Canal连接问题,都可能导致数据同步异常。
查看Canal日志: Canal会生成日志文件,其中包含了关于数据同步的详细信息。
通过日志可以了解出现同步是成功的还是出了错误,如果同步错误在日志会显示读取哪个binlog文件出现了错误,然后通过show binary logs 查询是否存在该 binlog日志。
如果由于binlog日志被删除导致canal同步失败,可以将canal中的meta.dat清理,并且将master复位,重启canal。
此时为了防止数据不同步需要对表中的数据全部update,触发canal读取update产生的binlog,最终保证当前数据是全部同步的。
确认Canal配置: 检查Canal的配置文件,确保配置正确。特别是Canal的过滤规则等是否正确,即canal.instance.filter.regex的配置。
如何保证Canal+MQ同步消息的顺序性?
Canal解析binlog日志信息按顺序发到MQ的队列中。
现在是要保证消费端如何按顺序消费队列中的消息。
解决方法:
多个jvm进程监听同一个队列保证只有消费者活跃,即只有一个消费者接收消息。
队列需要增加x-single-active-consumer参数,值为true,表示否启用单一活动消费者模式。
消费队列中的数据使用单线程。
在监听队列的java代码中指定消费线程为1,如下图:
第四章 预约下单
订单的状态有哪些?
待支付:订单的初始状态。
派单中:用户支付成功后订单的状态由待支付变为派单中。
待服务:服务人员或机构抢单成功订单的状态由派单中变为待服务。
服务中:服务人员开始服务,订单状态变为服务中。
待评价:服务人员完成服务,订单状态变为待评价。
订单完成:用户完成评价,订单状态变为订单完成。
已取消:订单是待支付状态时用户取消订单,订单状态变为已取消。
已关闭:订单已支付状态下取消订单后订单状态变为已关闭。
订单表是怎么设计的?
订单表通常采用的结构是订单主表与订单明细表一对多关系结构,比如:在电商系统中,一个订单购买的多件不同的商品,设计订单表和订单明细表:
订单表:记录订单号、订单金额、下单人信息、订单状态等信息。
订单明细表:记录该订单购买商品的信息,包括:商品名称、商品价格、交易价格、购买商品数量等。
如果系统需求是一个订单只包括一种商品,此时无须记录订单明细,将购买商品的详细信息记录在订单表即可,设计字段包括:订单号、订单金额、下单人、订单状态、商品名称、购买商品数量等。
本项目订单表包括以下内容:
订单基础信息:订单号、订单状态、排序字段、是否显示标记等。
价格信息:单价、购买数量、优惠金额、订单总金额等。
下单人信息:下单人ID、联系方式、位置信息(相当于收货地址)等。
服务信息(如果有订单明细表要放在订单明细表):服务类型名称、服务项名称、服务单价、价格单位、购买数量等。
常见的订单号生成规则有哪些?
- 自增数字序列
使用数据库的自增主键、redis的INCR 命令生成序列化。
- 时间戳+随机数
将年月日时分秒和一定范围内的随机数组合起来。有重复的风险。
- 订单类型+日期+序号
将订单类型(例如"01"表示普通订单,"02"表示VIP订单等)、日期和序号组合起来。加上订单类型的好处是方便客户服务,根据订单号就可以知道订单的类型。
- 分布式唯一ID生成器
使用分布式唯一ID生成器(例如Snowflake算法)生成全局唯一的ID作为订单号。Snowflake 算法根据机器ID、时间戳、序号等因素生成,保证全局唯一性,它的优势在于生成的 ID 具有趋势递增、唯一性、高效性等特点.
Feign和OpenFeign的区别?
Feign 是 Netflix 公司开发的一个独立的项目,在使用 Spring Cloud 时,需要单独引入 Feign 的依赖。
OpenFeign 是 Spring Cloud 对 Feign 进行了集成,并提供了对 Spring Cloud 注解的支持。
Feign 使用了一套自己的注解,例如 @FeignClient 用于声明一个 Feign 客户端,@RequestMapping 用于声明请求的映射等。
OpenFeign 则直接使用了 Spring MVC 注解,例如 @GetMapping、@PostMapping 等,这使得 OpenFeign 更加和 Spring 生态集成。
从spring boot 2.0之后基本上都是使用OpenFeign 了。
微服务保护怎么做?
微服务之间相互调用,因为调用链中的一个服务故障,引起整个链路都无法访问会导致微服务雪崩。
常用的预防微服务雪崩的的方法:
超时处理:设定超时时间,请求超过一定时间没有响应就返回错误信息,不会无休止等待。
熔断降级:当服务的异常数或异常比例超过了预设的阈值时,熔断器会进入开启状态,暂时中断对该服务的请求,此时走降级方法,能够快速响应,确保系统的基本功能能够继续运行。
限流:限制对服务的请求速率,避免短时间内大量的请求导致系统崩溃。
线程池隔离:给要请求的资源分配一个线程池,通过线程池去控制请求数量
暂时无法在飞书文档外展示此内容
信号量隔离:使用计数器模式,记录请求资源的并发线程数量,达到信号量上限时,禁止新的请求。
信号量隔离适合同步请求,控制并发数,比如:对文件的下载并发数进行控制。
大多数场景都适合使用线程池隔离,对于需要同步操作控制并发数的场景可以使用信号量隔离。
你的项目怎么实现微服务保护的?怎么实现熔断降级的?
我们项目使用sentinel实现微服务保护,我们在Feign远程调用时进行熔断降级控制。
当远程调用发生异常首先走降级方法,当异常比较或异常数达到阈值将触发熔断,在熔断时间内不再走原来的方法而是走降级方法,可以快速进行响应。
当服务恢复后,熔断时间结束此时会再次尝试请求服务,如果成功请求将关闭熔断,恢复原来的链路。
具体方法:
- 在客户端使用使用FeignClient注解定义远程调用接口
- 定义专门远程调用的客户端类实现远程调用、熔断、降级逻辑
使用@SentinelResource注解定义sentinel监控的资源,@SentinelResource注解的属性具体包括。
value: 用于定义资源的名称,即 Sentinel 会对该资源进行流量控制和熔断降级。
fallback :非限流、熔断等导致的异常执行的降级方法
blockHandler :触发限流、熔断时执行的降级方法
微服务之间远程调用怎么实现的?
项目使用的Spring Cloud Alibaba框架,微服务之间远程调用使用OpenFeign,具体实现步骤如下:
在api工程定义Feign接口,使用@FeignClient注解进行定义。
服务提供方法定义Feign接口的实现类,实现具体的逻辑。
服务调用方(客户端)依赖api工程,使用@EnableFeignClients注解扫描Feign接口,生成代理对象并放在Spring容器中。
服务调用方(客户端)定义专门远程调用的客户端类,在客户端类中实现远程调用、熔断、降级逻辑,具体参考"你的项目怎么实战微服务保护的?怎么实现熔断降级的?"问题。
Service方法事务失效的原因是什么?
可能有以下原因:
1)在方法中捕获了异常没有抛出去,没有把异常抛给代理对象,代理对象捕捉不到异常没有进行事务回滚
2)非事务方法内部调用事务方法,不是通过代理对象去调用
3) @Transactional标记的方法不是public
4)抛出的异常与rollbackFor指定的异常不匹配,默认rollbackFor指定的异常为RuntimeException
5)数据库表不支持事务,比如MySQL的MyISAM
6)Spring的传播行为导致事务失效,比如:PROPAGATION_NEVER、PROPAGATION_NOT_SUPPORTED
PROPAGATION_NOT_SUPPORTED -- 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 PROPAGATION_NEVER -- 以非事务方式执行,如果当前存在事务,则抛出异常。
Spring 如何解决循环依赖?
Spring通过三级缓存对Bean延迟初始化解决循环依赖。
具体如下:
- singletonObjects缓存:这是 Spring 容器用来缓存完全初始化好的单例 bean 实例的缓存。
- earlySingletonObjects缓存:这个缓存是用来保存被实例化但还未完全初始化的 bean (半成品)的引用。
- singletonFactories缓存:这个缓存保存的是用于创建 bean 实例的 ObjectFactory,用于支持循环依赖的延迟初始化。
Spring 通过这三级缓存的组合,来确保在循环依赖情况下,能够正常初始化 bean。当一个 bean 在初始化过程中需要依赖另一个还未初始化的 bean 时,Spring 会调用相应的 对象工厂来获取对应的 bean 半成品实例,这样就实现了循环依赖的延迟初始化。一旦 bean 初始化完成,它就会被移动到正式的单例缓存中。
对于通过构造方法注入导致循环依赖的在其中一个类的构造方法中使用@Lazy注解注入一个代理对象即可解决。
你们对接的哪个支付接口?怎么对接的?
用户端是微信小程序,我们对接的小程序支付接口。
我们使用的SDK是微信支付的httpclient程序(wechatpay-apache-httpclient )。
首先用户发起支付会调用小程序的下单接口,微信返回一个下单标识。
小程序前端程序使用该下单标识调用小程序的方法唤起支付窗口,用户进行支付。
支付成功,微信通过通知接口调用服务端接口。
服务端程序也会调用微信的支付结果查询接口查询支付结果,根据支付结果更新订单的支付状态。
用户退款接口我们对接小程序的退款接口,用户发起退款申请,通过退款结果查询接口查询退款状态。
项目的支付服务有哪些接口?
项目的支付服务对接了微信、支付宝的Native、jsapi等常用支付方法。
支付服务提供以下接口:
支付接口,此接口请求第三方支付平台的下单接口,下单成功生成支付二维码返回给业务系统,如果已生成支付二维码会将二维码返回给业务系统。
支付结果查询接口,根据支付服务的交易单查询支付结果,支付服务会请求第三方的支付结果查询接口查询支付结果。
退款接口,此接口请求第三方支付平台的退款接口,如果已退款此接口会返回退款记录给业务系统。
支付通知接口,支付服务获取支付结果通过MQ通知业务系统。
支付服务设计了几张表?
核心的表有支付渠道表、交易单表、退款记录表。
支付接口:收到支付请求后请求第三方支付的下单接口,并向交易单表新增记录。
查询交易结果接口:请求第三方支付的查询支付结果并更新交易单表的支付状态。
接收第三方通过支付结果:更新交易单表的支付状态。
退款接口:新增退款记录
更新退款状态:请求第三方退款结果查询接口查询退款状态,并更新退款状态。
如何防止重复支付?
重复支付是一个订单客户支付多次,造成重复支付。
我们项目实现的是扫码支付,可能存在重复支付的问题,通过以下方式去避免重复支付:
1、同一个订单同一个支付渠道只生成一个支付二维码。
2、在请求第三方支付下单使用分布式锁控制不会重复请求第三方下单。
3、切换支付渠道时先关闭原渠道的交易单再生成新渠道的交易单。
4、使用定时任务每天扫描交易单表,如果存在多个支付成功的交易单则进行自动退款。
支付接口是怎么开发的?
项目有统一的支付服务与第三方支付平台对接,业务系统对接支付服务完成支付流程。
首先请求通过支付服务请求第三方支付平台的支付下单接口,如果是小程序支付下单成功会返回一个会话标识,前端通过该会话标识调起支付窗口,如果是扫码支付下单成功会返回二维码URL,生成二维码返回给前端。
用户支付成功,获取支付结果更新订单表的订单状态字段。
获取支付结果有两种方法:
调用 支付服务的查询支付结果接口查询支付结果。
通过监听MQ,支付服务将支付结果通知给业务系统。
项目中有用到MQ吗?怎么用的?
项目有很多地方都用到了MQ,我们用的是RabbitMQ。
- 数据同步用到了MQ
项目在数据同步中用到了MQ,我们用的是Canal加MQ将MySQL的数据同步到其它服务,比如:ES、Redis等。
具体的流程是:
Canal会定时读取数据库的binlog日志,解析出增加、修改及删除的数据内容并将其写入MQ。
同步程序监听MQ,收到增加、修改及删除的数据的消息后请求ES同步索引。
- 支付通知用到了MQ
项目在支付通知中用到了MQ,支付服务将支付结果发给MQ,订单服务监听MQ收到支付结果更新订单的支付状态。
具体的流程是:
支付服务将支付结果发送给专门传输支付通知的交换机,交换机使用的是topic类型,该交换机绑定了多个队列,因为考虑会有多个微服务对接支付通知,每个微服务监听一个队列。
当支付服务向交换机发送一条支付通知消息,所有绑定此交换机的队列会收到支付通知,业务系统收到支付结果后解析出业务系统应用标识,判断是否属于自己的支付结果通知,如果是再进行处理。
项目的退款功能怎么实现的?
取消订单执行退款操作。
1、首先使用一个事务保存以下数据
更新订单状态。
保存取消订单记录,记录取消的原因等信息。
保存退款记录。
2、事务提交后先启动一个线程请求支付服务的退款接口
3、定时任务扫描退款记录表,对未退款的记录请求支付服务进行退款,退款成功更新订单的退款状态,并删除退款记录。
说明:
第2步的作用为了第一时间申请退款,因为定时任务会有一定的延迟。
第3步的作用是由定时任务去更新退款的状态,因为调用了退款接口只是申请退款了,退款结果可能还没有拿到,通过定时任务再次请求支付服务的退款接口,拿到退款结果,更新订单的退款状态,并删除退款记录。