这里写目录标题
- 项目
- 面试
- 1.问了怎么保证幂等?
- 2.数据库扣减库存怎么实现一致性
- 3. 多级缓存的是怎么做的?为什么还要再多加一层本地缓存呢?对性能提升大吗?QPS 提升多少?
- 4.超卖问题
- 5.缓存三件套:击穿,雪崩,穿透
- 课后题
- 数据库如何水平扩展
- 如何解决缓存的脏读和失效的问题?
- 在大型的应用集群中若对Redis访问过度依赖,会否产生应用服务器到Redis之间的网络带宽产生瓶颈?若会产生瓶颈,如何解决这样的问题?![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/90aa060fcd838bf52ecd3663b6a69694.png)
- redis其他的数据结构还能适用于哪些场景
- 7-11 如何解决业务系统的热点问题
项目
采用Nginx做反向代理、以及搭建Tomcat服务器集群,实现负载均衡和动静分离
4.3
ajax前端访问miaoshaserver的域名,niginx通过动态的反向代理请求,访问不同的服务器
H5(STATIC)若访问miaoshaserver/resources的域名,nginx会直接从本地磁盘中获得静态资源
4.7
4-7:
1. 通过upstream server添加对应的服务器,weight表示权重
反向代理配置,配置一个backend server,可以用于指向后端不同的server集群,配置内容为server集群的局域网ip,以及轮训的权重值,并且配置一个location,当访问规则命中location任何一个规则的时候则可以进入反向代理规则
相关的八股
反向代理:客户端将请求发送到反向代理服务器,由反向代理服务器去选择目标服务器,获取数据后再返回给客户端。对外暴露的是反向代理服务器地址,隐藏了真实服务器 IP 地址。反向代理“代理”的是目标服务器
动静分离
将静态资源部署在Nginx上,当一个请求来的时候,如果是静态资源的请求,就直接到nginx配置的静态资源目录下面获取资源,如果是动态资源的请求,nginx利用反向代理的原理,把请求转发给后台应用去处理,从而实现动静分离。
4.12
4-12
redis:集中式缓存中间件
先存入redis,返回给前端
前端获取token再发送给后端
后端的获取对应的token,根据token从redis里获取对应的用户
第五章:本地热点缓存(JVM的缓存–堆栈信息)
5-4 Redis集中式缓存商品详情页
5-4:Redis集中式缓存商品详情页,
利用redis在controller层的时候,之间从redis中获取,减少对数据库的依赖
5-7 开启本地缓存
5-7:开启本地缓存接口:cacheService
本地缓存用的就是tomcat的堆内缓存
利用虚拟机的堆栈,清楚JVM里面的数据
利用Guava cache 实现
先去本地缓存(不存在)–再取redis(不存在)-- 最后再去数据库,最后记得反向SET到对应的库中
5-10
nginx proxy cache
需要配置niginx.conf, 指定一个cache缓存节点,添加到 直接对应动态代理localtion里面
将访问的数据直接缓存到设置的proxy cache 目录中(本地文件中)–不推荐
nginx没有转发给后端服务器,但是读的是本地的文件,并没讲内容缓存到nginx内存中
5-11 开启本地缓存
5-11:nginx 本地缓存 – 语言:nginx lua
指定一个内存128大小
2.编写一个lua脚本itemshared.lua(set 和 get)–更新机制不好
1.在nginx openresty-- redis
新建一个itemredis.lua的脚本,openresty对redis脚本的编写,(连接上面的redis slave)
修改niginx.conf里面的配置
(最开始用proxy cache 存放-- 后来设置了sharedic— 最后改进,重写了一个itemredis.lua,设置一个只读的slave redis)
==分级理解:==前端访问,先从redis slave(openresity -redis)读数据,没有 ,就去本地缓存读数据,再没有,就去redis中读数据,再没有才回去数据库中读数据。
采用RabbitMQ实现异步消息扣减数据库内库存
7-4 同步进缓存,下单减库存
7-4
1.将库存放入缓存中(这里是在cintroller方法调用下面serverce,使得库存进入缓存)
2.减去对应的Redis库存
课程中通过redis的原子性,先减库存判断剩余数量是否大于0来防止超卖。本项目中只要控制住redis 就不会超卖 redis挂了就不可交易 因此不会超卖
会出现数据库记录不一致的问题
7-5 利用roketmq,异步扣减数据库
7-5
- 分别构建MqProducer和MqConsumer类
- ItemserveImpl,redis扣减库存成功,result>0,就会调用上面的方法
如果Mq失败,redis需要回滚 - Consumer收到消息调用数据进行库存扣减
因为redis和数据库扣减 与 订单入库写在一个事务当中,如果redis和数据库都扣减成功了,但是订单入库出现了问题(买家取消支付),redis和数据库没有对应的回滚措施,就会出现少买的情况
8-2 事务性消息应用 transcationMQProducer
事务型:保证数据库数据提交了,对应的消息必定会发送成功的。数据库事务回滚,消息必定不会发送成功
1.上述首先往消息队列中投递消息,消息被维护在broker 中间件上面 2. 再去执行executelocalTransaction方法
在prepare的状态下会去执行executeLocalTransaction方法
- 在oredercontroller层调用mqproducer.transcation扣减库存的操作
如果上述订单入库执行了很久, mqproducer还有一个 c h e c k L o c a l T r a n s a c t i o n checkLocalTransaction checkLocalTransaction方法,去判断上面的方法是否入库成功,判断要返回三种情况的哪一种
如何判断,查看是否生成对应的订单流水
8-3 库存流水状态
引入库存流水,
订单入库成功之后,
后面需要重写一下executelocalTransaction和check方法
8.6库存数据最终一致性保证
少卖不可超卖
触发15分钟以上,需要释放掉,将数据回滚回去
- 加入库存售罄表示(redis)
- 在生成库存流水之前,先判断redis中是否存在售罄表示
采用Redis实现流量削峰和限流
9-1秒杀令牌
在业务层生成token,并且设置时间为五分钟
在ordercontroller判断前端送来的token是否与redis中一致
9-2 秒杀令牌
问题*
令牌数量
生成token前先判断count的数量
- 秒杀令牌:通过promoID,userID,ITEMiD生成一个token,放入redis中
- 根据商品的库存设置对应令牌的数量(先计算大闸count的数量在获取对应的token)
- 队列泄洪:依靠排队调整mq队列释放流量大小和下游拥塞窗口(设置一个线程池大小xx,以及拥塞窗口为xx的等待队列)
异步操作:调整队列释放流量的大小,在队列的消费端,一次性取多个,交给下游的多线程处理,取得大小就是一个拥塞窗口,
添加验证码:发放秒杀令牌之前,先定义一个生成验证码返回给前端。在发放秒杀令牌中,需要先验证一下验证码输入与redis存放的是否一致
校验失败,就显示”火爆,稍后再试“
限流
限制并发是限制 一共多少人同时干活
令牌桶限制一秒中可以有几个人干活
下一秒的意思是这一秒令牌没了他等待一秒后优先抢占下一秒冲回去的令牌
如果当前这一秒200个被消耗完,预置下一秒令牌,并且让当前线程睡眠,需要将线程sleep多少ms,才可继续操作,
总结:
初始化令牌数量— (验证码)–(rateLimiter令牌桶算法)— 生成令牌,验证令牌,数量减1— 队列大小和线程池
面试
1.问了怎么保证幂等?
在数据库中使用唯一索引来防止重复购买。对用户ID和商品ID进行联合唯一索引,确保同一用户不会重复购买同一件商品。
2.数据库扣减库存怎么实现一致性
更新数据库后立即删缓存,然后下一次查缓存找不到数据后会再次从数据库同步到缓存。
3. 多级缓存的是怎么做的?为什么还要再多加一层本地缓存呢?对性能提升大吗?QPS 提升多少?
4.超卖问题
库存售罄的时候,是通过Redis来读出库存数量,如果大于0,那就将库存扣减掉,然后当为0时,就在redis中打入售罄标识
在ordercontroller中,先判断redis中是否存在售罄表示
通过redis的原子性,先减库存判断剩余数量是否大于0来防止超卖。
因为同一个redis,两个不同的服务,同时执行incre和decre,同一个redis,两个不同的服务,同时执行incre和decre,
总是先扣redis 扣成功了再下单 同时发送消息 消息有一致性保障 消费成功必定数据库扣成功了 这样redis内数据永远是少的一个 不会超卖
设置一个定时器 如果下单操作不能反回 超过15分钟释放stocklog
5.缓存三件套:击穿,雪崩,穿透
缓存穿透
课后题
4-19:Nginx容器作为反向代理的中间节点是如何保证路由策略的性能高效的
使用HTTP1.1协议长链接KeepAlived,用keepalive可以解决网络建连的问题 同时nginx的进程模型可以保证非阻塞式的高性能运行
数据库如何水平扩展
如何解决缓存的脏读和失效的问题?
缓存的脏读问题:数据库更新之后,缓存内的数据没有更新,导致脏读。也就是数据的不一致性。如果业务不复杂,涉及数据表还不算多,缓存更新方面可以使用redis的订阅机制,本地专门有个redis监听订阅的线程去异步更新缓存
缓存失效:可能会带来的缓存雪崩。每一个缓存的数据加上一个失效标记,若过期则启动另外一个线程进行缓存数据的更新。
在大型的应用集群中若对Redis访问过度依赖,会否产生应用服务器到Redis之间的网络带宽产生瓶颈?若会产生瓶颈,如何解决这样的问题?
redis其他的数据结构还能适用于哪些场景
7-11 如何解决业务系统的热点问题
对一个热门的秒杀商品详情页的访问就属于是读热点问题,对一个秒杀商品的库存抢操作就是一个写热点问题。
读热点问题的解决方案:
- 需要优化数据库的读操作,我们对应的查询是否走了索引,走的是否是唯一索引甚至于主健效果最佳
- 优化了sql性能后我们可以借助于mysql innodb的buffer做一些文章,在数据库层面就提供足够的缓冲区,加速对应的性能
- 由于访问频次太高,mysql的cpu扛不住了,这个时候我们考虑到的是将对应的读热点放到例如redis的缓存中用于卸载压力,由于redis4版本以后就可以支持cluster的集群模式,其借助分片集群的效果理论上可以扩展到1000个左右的节点
- 一旦商家变更了读热点的数据,我们可以在业务应用中使用提交后异步清除缓存的方式将redis的数据清除,这样在下一次的请求中可以依靠数据库的回源更新redis数据。
- 热点数据到分配到一个redis节点上,当热度大到连redis节点都无法承受的时候,我们可以考虑将原本的一个热点做三分拷贝,比如我们的热点key叫"miaosha_item_1",我们可以考虑随机的生成三个key分别叫"miaosha_item_1_key_1",“miaosha_item_1_key_2”,“miaosha_item_1_key_3”,对应的value都是这个商品value本身,这样当用户请求过来后我们可以随机的生成1-3的数字以决定这次请求我们访问哪个key,这样人为的将一个热点的tps降到了原来的三分之一,以空间换时间,
- 另外我们还可以考虑在应用服务器上做本地的cache内存,由于应用服务器本身容量有限,内存中不能放太多数据,也不能存很长时间,我们推荐使用google研发的guava cache包,提供给我们很好的lru cache队列的能力,一般本地的缓存不要设置太长时间,一是出于内存容量考虑,二是出于清理本地缓存不像清理redis,需要我们的每台应用服务感知到数据的变更,一般可以用广播型的mq消息解决,推荐rocketmq 的广播型消息,使得订阅对应商品信息变更的所有应用服务器都有机会清理本地缓存。
写热点问题:要加锁以防并发的方式
- mysql对数据存储有比较好的优化,其基于写事务日志,也就是redo,undo log,然后等系统空闲的时候将数据刷入磁盘的,由于事务日志的存在,即使系统挂了再启动的时候也可以根据redo log恢复数据
- 因为写日志是一个顺序追加写的方式,磁盘的磁头不需要随机的移动寻找写入点,只要顺序的写下去即可,配合ssd固态硬盘,整个写入性能可以做到很高。
- 我们试着可以将写入的目标点移到缓存中,比如我们将秒杀的库存移到redis中,这么一来,点的瓶颈的天花板瞬间就提升到了很多倍,但是一旦将数据落到没有办法保证磁盘落地能力的缓存中就需要依靠一些机制去保证可靠性,不至于在缓存丢失的情况下造成超卖等灾难,我们可以依靠rocketmq异步事务型的消息保持redis和数据库之间的数据同步,
- 写同一份数据的操作不能并发,竞争锁的机制去竞争以获得线程的写入权限,是一个耗性能的,最高效的方式是什么,就是排队,大家都不要竞争,按照先到先得的方式将所有对热点的写入访问操作队列化,使用单线程的方式去队列中取得下一个写入操作,然后写完后再取下一个,这样可以避免掉写锁竞争的无谓cpu和内存消耗,单线程排队比多线程更高效,缓冲入账的方案是rocketmq做异步化之后先把相关数据放到队列里,然后再让单线程去处理。