在网上找了个电商项目,没全部做完,主要是做了订单服务,对于分布式系统来说,最大的问题就是:如何保证数据的一致性,当某一服务挂掉后,如何保证数据不丢失,数据同步等,这些问题的造成,很重要的原因就是网络的不可抗因素,数据在网络中传输,而网络的抖动、网速的延迟都是解决不了的,所以需要分布式事务来解决这一问题。
当然整个系统有几大模块: 商品服务(增删改查)、用户服务、订单服务、库存、支付。
订单服务: 用户点击提交订单,向服务器发送请求之后,订单服务会创建订单,并远程调用库存服务,进行锁库存,远程调用库存服务,如果库存服务挂掉了,订单服务的事务无法让其进行回滚,就会出现,订单创建成功,库存却没有减少。这样就会有问题。
1 |
|
可以作为分布式事务的框架有阿里巴巴的开源框架seata,使用AT模式,seata中有三个角色:
TC (Transaction Coordinator) - 事务协调者
维护全局和分支事务的状态,驱动全局事务提交或回滚。
TM (Transaction Manager) - 事务管理器
定义全局事务的范围:开始全局事务、提交或回滚全局事务。
RM (Resource Manager) - 资源管理器
管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。
——-来自seata官网: seata.io——————-
关于使用,只需要加上一个注解即可,开启seata分布式事务,**@GlobalTransactional**
TM会开启一个全局事务,RM会对分支事务进行处理,并且一并由TC来进行事务的提交和回滚。这样,如果库存服务挂掉了,那么全局事务就会发生回滚,从而保证数据的一致性。
可是。。。如果涉及到高并发场景,会有更好的办法嘛
使用消息队列实现分布式事务,通过消息传递来实现。
创建订单后,把订单信息放到一个延时队列中,不考虑主动支付和主动取消,假设设置延时队列的ttl为30min,并调用远程的库存服务,进行库存的锁定,库存的也可以使用一个延时队列来监听,假设ttl为35min。设置延时队列的原因,因为订单服务调用库存服务之后,库存锁定,结果订单服务挂掉了,那么库存服务会一直锁定,显然这个时候应该进行解锁,所以当35分钟后,延时队列中的消息会进入死信队列,并查询订单状态,根据订单状态来判断是否需要解锁。所以订单服务也需要向库存服务中的死信队列中发送订单详情的消息。
那如果订单创建之后,30min进入死信队列,并会查询支付状态,如果未支付,则取消订单,修改订单状态,支付成功,则修改订单状态为已支付。
消息队列保证消息的可靠性,可以通过try-catch代码块来进行异常捕捉,并通过日志的方式记录消息发送内容。并且设置确认机制为手动ack模式。以免消息的丢失。
另外,如何保证订单不被重复提交,设置幂等性,每次结果返回的都是一样的,为了防止重复提交,对每个订单请求都生成一个token,并放入到redis中,当每次提交请求时,从前端的header中获取token并与redis中进行比对,但是需要保证原子操作,redis可以使用lua脚本。这样防止多次重复提交订单。
秒杀服务。秒杀服务是常有的,双十一、双十二,并发量高达几千万,如何保证秒杀的正确性,不重复卖一间商品,不超卖。可以使用redis中的框架,Redisson,来实现分布式锁。
秒杀一般都是一个固定的时间,所以需要通过定时任务来上架商品,并放入redis缓存中,首先是存放商品的id,然后存放商品的信息,key为id+随机码,随机码是为了防止提前有人访问。
秒杀需要考虑的问题: 独立服务,因为并发较高,所以独立出来,防止影响其他服务。
通过随机码来防止恶意访问。通过网关对恶意请求拦截。防止大量恶意请求造成redis缓存穿透。
最主要的就是redis,redis中有与JUC包下很多类似的类可以使用,这里主要是 Semphare。对于库存,可以使用信号量来作为库存的扣减。