项目笔记

4 篇文章 0 订阅
3 篇文章 0 订阅

分类模块

一、缓存一致性
1、本地锁

本地锁只能锁住自己的服务线程,在集群是不行的,因为锁是通过除synchronized之外就是通过AQS来实现线程同步的。

什么是AQS?

推荐博客:AQS详解

AQS全称[AbstractQueuedSynchronizer]抽象的队列式同步器,它提供了一种实现阻塞锁和一系列依赖FIFO等待队列的同步器的框架,ReentrantLock、Semaphore、CountDownLatch、CyclicBarrier等并发类均是基于AQS来实现的,具体用法是通过继承AQS实现其模板方法,然后将子类作为同步组件的内部类。

能够保证锁是被哪个线程获得的,是通过volatile修饰的变量state,一个线程获得锁之后会改变这个state,然后这个获得锁的线程会先进入CLH队列中,而后面要获得锁的线程就会进入队列中等待,直到先进的线程执行完成。

2、Redis的SetNX分布式锁

image-20200831144351248

3、分布式锁

所以为了解决本地锁的问题我们可以使用分布式锁:Redisson。

变化的地方就是用Redis代替了AQS,在我们进行写入操作时可以上锁,与JUC下的锁相似。

如果不用Redisson只用redis自带的原子锁那么需要你自己去实现,很麻烦。

4、读多写少,一致性不高的缓存可以使用SpringCache
  • 双写失效

    先删缓存,再更新数据库,之后如果有用户访问就去查数据库,再把数据放入缓存中。

    SpringCache底层使用了sychronized锁,虽然不是分布式锁,但是在高并发情况下get缓存可以保证只有几个人访问数据库,之后的人全部访问redis。

  • 双写更新

    修改完数据库就直接更新缓存。

子域不共享问题

@Configuration
public class GulimallSessionConfig {
    /**
     * cookie的作用域
     *
     * @return
     */
    @Bean
    public CookieSerializer cookieSerializer() {
        DefaultCookieSerializer serializer = new DefaultCookieSerializer();
        serializer.setDomainName("gulimall.com");
        serializer.setCookieName("GULISESSION");
        return serializer;
    }

    /**
     * 序列化方式
     *
     * @return
     */
    @Bean
    public RedisSerializer<Object> sessionRedisOperations() {
        return new GenericJackson2JsonRedisSerializer();
    }
}

订单模块

1、提交订单可能出现的情况
  1. 订单重复提交

    使用原子令牌进行验证防止用户重复提交,保证接口的幂等性,每次进入订单确认页都会生成一个防重令牌,当提交订单后会使用lua脚本进行原子验证,如果令牌在redis中存在就会删除,否则返回0代表提交失败。

  2. 库存不足

    提交订单会进行库存验证,库存不足会进行提示。

  3. 验价失败

    提交订单会对商品进行一次验价,如果出现与加入购物车时的价格不同会进行一次提示。

2、订单关单功能

1、如果支付宝支付失败掉单了,那么订单的死信队列会进行订单状态修该,以及库存的解锁

3、订单支付成功

只有当订单满住可退款支付成功或者不可退款支付成功才算支付成功。

image-20200831202442383

高并发场景问题

保证接口的幂等性

幂等性也就是请求多次,只处理一次请求。

方法:

1、使用redis进行一个原子令牌,请求的令牌与redis的令牌不一样就重定向回去。

2、各种锁

image-20200624194916581

3、各种唯一约束

4、防重表

5、全局请求的唯一id

分布式事务
为什么要有分布式事务?

分布式系统经常出现的异常

机器宕机、网络异常、消息丢失、消息乱序、数据错误、不可靠的TCP、存储数据丢失…

如果远程调用的服务成功了,结果遇到上面的问题导致成功消息没发出去,对方就服务不知道是否成功了。

远程调用本地事务不能回滚其他服务的服务,所以需要分布式事务

image-20200624194554221

CAP

一致性: 所有微服务节点关联的数据库中都有对方的一个相同值。比如A服务保存数字1,B和C服务也必须要有数字1。

可用性: 一直可以正常的做读写操作。简单而言就是不管服务器是否出现错误,客户端一直可以正常访问并得到系统的正常响应。

分区容错性: 一个服务节点因为网络问题没有把消息传递到其他服务节点,说明这个节点出现错误,出现错误后我们该怎么办。

CAP只能实现俩点,不能三者兼顾,并且分布式系统一定会出现网络问题,所以分区容错性一定需要,那就只能在CA之间二选一。

推荐CAP定理含义文章:http://www.ruanyifeng.com/blog/2018/07/cap.html

为什么不能三者兼顾?

如果使用的了一致性,那必须保证所有的节点数据都是一致的。

如果使用可用性,那因为网络波动导致数据不同步,使得一致性失效,但节点不影响用户的读写请求,依然会响应用户请求。

所以二者有冲突,只能二选一。

**保证一致性的算法:**raft算法 或者 paxos算法等 ,raft算法不能保证可用性,因为投票必须要一半以上,如果有6台节点,因为网络原因,被分成2组一组3台节点,而因为选举需要4台节点,就会出现一直不可用状态,用户也无法收到响应信息。

图形化演示raft算法流程: http://thesecretlivesofdata.com/raft/ 或者 http://raft.github.io

BASE理论

image-20200624221752904

强一致,弱一致与最终一致

image-20200624222044160

本地事务

事务的4大特性

原子性:在事务里的操作,要么都成功,要么都失败

一致性:事务前后的数据是一致的,比如A转100 ,B必须加100,A少100

隔离性:事务之间不能影响对方的操作

持久性:事务完成后会马上保存到数据库中

隔离级别

读未提交:可以读到事务没有完成时的数据 ,也就是脏读

读提交:只能读到事务完成的数据

可重复度:在事务里,第一次从数据库获得的数据,不管之后是否删除,在事务里不管读多少次都是第一次的数据。

事务的传播行为

image-20200624200526806

事务设置失效问题

spring的事务默认required : 同一个对象的方法互相调用,用的都是调用方法一样事务设置。被调用的方法不管怎么设置都不会生效,就和之间复制代码到调用方法里一样。

**原因:**因为事务是用代理实现的,在一个对象方法互相调用不会经过代理对象。可以重新注入一下代理对象来调用方法,这样就会经过代理。

**注:**不能在一个类中注入自己,会循环注入;

解决方法:

1、导入aop依赖,我们需要它里面的AspectJ

 <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
 </dependency>

2、主类开启AspectJ

@EnableAspectJAutoProxy(exposeProxy = true) //为true对外暴露代理对象

优点: JDK需要有接口才能进行代理,AspectJ不需要代理接口

3、用代理对象来获得对象

        OrderServiceImpl o = (OrderServiceImpl) AopContext.currentProxy();
分布式事务的几种方案
1、2PC模式 了解即可

image-20200625035633701

2、柔性事务-TCC事务补偿型方案

​ 【】2PC与TCC不适合高并发场景,seata的AT模式也就是TCC的优化版,但因为用锁来进行控制的,只适合少量并发情况,比如管理系统的CURD,在一定时间就一起成功或者一起失败,可以弄一个加载动画给用户观看。

image-20200625035654455

3、柔性事务-最大努力通知

】先通知对方,后干事,如果对方一直没干事,就全部回滚。

image-20200625035749576

4、柔性事务-可靠消息+最终一致性(异步确保型)

】通过MQ发送消息通知其他服务,就算有一个服务没有保存一致的数据,可以允许它30分钟或者一天才进行数据同步,这就是最终一致性。适合高并发场景。

实现方式:

1、使用定时任务

定时1小时扫描超时的订单,如果超时就回滚所有事务。

**缺点:**会有时间差。比如0:00点扫描完,一个订单在0:59点下成功,1:00点扫描一次没发现有订单失效,结果订单在1:01失效,最后在2点才被扫描失效。最后失效时间差了1小时订单才被取消。

2、RabbitMq延迟队列

1、订单下成功就调用锁库存服务,并且订单服务发送消息给延迟队列,在30分钟之后关闭订单方法会收到延迟队列发来的消息,判断订单是否超时,超时就取消订单。

2、库存锁定成功后,在40分钟通过延迟队列,也发送消息给解锁库存方法,判断锁定库存的这个订单是否被取消,如果取消就解锁库存。 因为订单是30分钟被关闭,所以库存要确保关闭订单执行成功,多加了10分钟。

有一个记录表,它记录了需要最终一致性的表,在这些表进行事务之前的数据都会保存在记录表里,当需要回滚时,调用这个记录表数据,进行回滚。

image-20200625035817043

5、怎么保证消息可靠性?

1、创建mq的消息表

2、发送者使用确认回调机制改变消息表的状态,只要确认到达队列被持久化保存就改变为抵达状态

3、手动ACk

image-20200626231624744

6、什么是消息重复消费

在使用手动ACK确认机制,消息以及消费完了,但是还没运行到手动确认代码那就宕机了。

这个消息就会从unack状态保存read状态,然后发给其他集群,这时就会出现重复消费问题

解决: 在消费消息时,接口时幂等的,只要消息被消费就改变表中状态。

秒杀(高并发)系统关注的问题
项目秒杀的并发量

1个请求12ms,一秒钟大约可以处理100个请求,tomcat1秒支持500个请求,一个服务器就可以有5万的并发量,3个集群服务器就是15万并发量了

秒杀方案二:小米商城这种并发量小的秒杀

image-20200831193337684

image-20200831193513758

秒杀方案一:京东、淘宝秒杀

流程:

1、用户点击秒杀会判断是否登录,如果登录成功了还会去验证这个秒杀商品是否合法(如:秒杀时间、token),如果该用户已经秒杀过就不能再进行秒杀,这会使用占位的方式判断(当前秒杀商品id+用户id)保存再redis中,对这个占位使用过期机制时间也就是这次秒杀场次的过期时间。

2、然后扣减信号量,如果扣减失败就提示秒杀失败。

3、之后快速创建秒杀订单,然后使用MQ异步通知订单服务进行流量削峰来保存订单、之后让用户确认收获地址等

4、最终到支付确认页、确认支付,支付完成后支付宝会同时向你服务器发起两次请求,一个请求是跳转到支付成功页,另一个请求是异步的,请求到你的订单状态修改链接。

image-20200831193303894

1、服务单一职责+独立部署

秒杀应该有一个单一的服务

2、秒杀链接加密

定时任务会扫描开始场次的秒杀商品进行上架,上架时这个商品会生成一个商品id+tokenId的存入reids中。这时因为秒杀已经开始了前端直接拿到了json里的token,用户下单会验证秒杀时间和商品id+token,如果没有就下单失败。只会显示秒杀开始的,秒杀预告的不是同一个方法。
每个商品有一个token加密,在秒杀开始前这个token不会被传输到页面,只有在秒杀开始这个token被用户传输过来,之后再后端对这个token判断是否为这个商品的token值。

3、库存预热+快速扣减

把库存数量变成redisson的信号量存入redis中,库存请求一次就信号量就减1

4、动静分离

所有静态资源都放入nginx中

5、恶意请求的拦截

识别非法恶意拦截,比如每秒发送1000次请求或者不使用token进行请求,就说明有问题

所以需要拦截这些恶意请求,一般在网关层拦截。

6、流量错峰

可以用验证码或者加入购物车,分流到其他服务中。

7、流量限流&熔断&降级

限流
前端限流:设置每次的点击次数

后端限流:设置请求的最大数,超过了这个数就等待

熔断

秒杀场景需要快速的返回用户结果,当秒杀的调用链有一个远程调用服务不可用或者超时不应该让用户等待,应该快速返回失败的结果给用户。

降级

当服务压力顶不住了,可以使用降级功能让用户跳转到其他页面,减轻压力。

异同

image-20200706085335378

8、队列削峰

使用redisson的信号量保证原子操作,只要信号量扣减就给队列发送消息,订单服务监听这个队列。

**如:**有100万请求,只有100个库存(信号量),只要有人购买成功了就给信号量减1,减完之后就把这个扣减成功用户信息发送消息给队列,订单服务监听这个队列,消费消息 创建这个用户的商品订单。

image-20200704223102077

链有一个远程调用服务不可用或者超时不应该让用户等待,应该快速返回失败的结果给用户。

降级

当服务压力顶不住了,可以使用降级功能让用户跳转到其他页面,减轻压力。

异同

[外链图片转存中…(img-mwhHfi22-1598876827076)]

8、队列削峰

使用redisson的信号量保证原子操作,只要信号量扣减就给队列发送消息,订单服务监听这个队列。

**如:**有100万请求,只有100个库存(信号量),只要有人购买成功了就给信号量减1,减完之后就把这个扣减成功用户信息发送消息给队列,订单服务监听这个队列,消费消息 创建这个用户的商品订单。

[外链图片转存中…(img-okn2m8Pl-1598876827077)]

image-20200704224623764

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值