学习日报 0823-0825 项目+场景题+计算机网络+代码随想录

周末学习效率不高,攒了三天一起发

项目进度:nacos实现属性动态切换、登录鉴权模块设计

nacos这里进行的比较顺利,没遇到问题直接跑通了。

场景题:

不用redis分布式锁,如何防止用户重复点击?

前端设置按钮置灰操作,当用户点击一次后按钮禁用,但是有些情况可能来不及置灰,或者被用户绕过置灰操作。

可以通过token的机制避免重复提交,当用户访问页面的时候,请求后端服务拿到一个token,然后下一次接口点击的时候把token带过来,服务端对token进行验证,验证该token是否被使用过,如果没有被使用过才可以进行点击。验证的逻辑可以放在数据库中,通过数据库的悲观锁或者乐观锁都可以实现。

滑动窗口限流:可以限制用户在一分钟或一秒钟内只能发起一次请求。

布隆过滤器:可以快速判断某个元素是否存在于集合中。可以在服务端使用布隆过滤器记录某个操作是否已经被执行过,从而防止重复执行。

参考ruoyi框架中的防重复提交的实现方案,其实就是把表单信息做校验并保存在redis中,下次再提交的时候做校验,如果和上次提交的内容一样,并且时间小于一定的时间间隔,则拒绝请求。

扩展:

滑动窗口限流:

首先需要把时间划分成多个连续的时间片段,然后定义一个时间窗口,比如10s,随着时间推移,窗口不断右移,通常有一个计数器去统计时间窗口内的请求。

当时间窗口移动时,不断减掉上一个时间片段的请求数,当有新的请求时,系统会检查窗口内计数是否已满,如果计数未满则允许执行,如果计数已满,拒绝请求或进入等待队列或执行其他限流操作。

优点是相比设置固定的请求数或速率,滑动窗口限流更加平滑,可以灵活应对突发流量或峰值流量,而不会因为固定速率的限制而浪费资源或降低系统性能。

Redis实现滑动窗口限流:

在Redis中,我们可以基于zset实现这个功能,假如我们限定login接口一分钟只能调用100次,那么,我们可以把login接口这个需要做限流的资源名作为key,在redis中进行存储。value是zset结构,把他的score设置为当前请求的时间戳,member用请求的详情的hash进行存储(或者UUID、MD5),避免在并发时时间戳一致导致幂等问题。

主要步骤:

1.定义滑动窗口的时间范围,例如,窗口大小为60秒。

2.每次收到一个请求时,我们就定义出一个zset然后存储到redis中。

3.然后再通过ZREMRANGEBYSCORE命令来删除分值小于窗口起始时间戳(当前时间戳-60s)的数据

4.最后,再使用ZCARD命令来获取有序集合中的成员数量,即在窗口内的请求量。

高并发情况下用lua脚本或者事务来保证原子性

Redission中已经给我们提供了一个限流器,不过并不是滑动窗口,而是一个令牌桶的算法

布隆过滤器:布隆过滤器是一种数据结构,用于快速检索一个元素是否可能存在于一个集合中,他的基本原理是利用多个哈希函数,将一个元素映射成多个位,然后将这些位设置为1.当查询一个元素时,如果这些位都被设置为1,则认为元素可能存在,否则肯定不存在。

所以,布隆过滤器可以准确的判断一个元素是否一定不存在,但是因为哈希冲突的存在,他没法判断一个元素一定存在,只能判断可能存在。

出现哈希冲突可能误判为存在。

布隆过滤器无法删除元素,因为删除一个元素需要将其对应的多个位设置为0,但这些位可能被其他元素共享。

有很多第三方库可以实现布隆过滤器,常见的有Google Guava、Apache Commons、以及Redis中的Redisson 或者 Jedis

设计一个秒杀系统,你会考虑哪些问题?

高并发瞬时流量

一般会做逐层的流量过滤,一次用户的秒杀请求会经过客户端、CDN、Nginx、Web应用、缓存、数据库等等。

接下来,能做的就是尽量在离用户更近的地方做流量的过滤,比如很多秒杀系统,会在前端,也就是客户端层面做一些请求的随机丢弃,这些被丢弃的请求就直接返回失败,或者系统繁忙,让用户重试。过滤掉一部分流量向服务端发送。

在服务端接受请求之前,还会先经过Nginx做统一接入,Nginx不仅可以用来做负载均衡和流量的分发,其实他也是可以做流量的过滤的,这里面可以配置一些黑白名单、可以通过IP进行限流、也可以做一些业务校验都是可以的。

再之后就是到服务器上面了,服务器层面也是配置很多限流策略的,基于sentinel,或者自己实现一些限流算法,都是可以做动态限流的。

还有就是,服务器中有一些查询操作,和一部分写操作,其实是可以用缓存来抗一下的。在缓存上,本地缓存要比分布式缓存的性能更高,近端缓存要好于远端缓存。

因为秒杀业务支持失败,所以可以通过层层过滤,让更多的流量在前面被过滤掉。

热点数据

拆分+预热

拆分就是把一个热点数据拆分开,拆成没那么热的多个数据,再通过负载均衡让不同的请求分散到不同数据上。

还有就是用缓存,一般秒杀是可以提前预知哪些数据会变成热点的,所以可以提前做一些缓存的预热,对于热点数据,不仅要在redis中做预热,还要在本地缓存也做预热,避免redis的热key问题

数据量大

秒杀系统会有高频的下单,那么就会导致最终数据量也会很多,那么最终产生的订单量可能就会很大。数据量一大就会带来查询效率低的问题。

这时就要考虑加缓存、用ES、做分库分表、做数据归档把历史数据归档掉。

库存的正确扣减

超卖:

高并发场景下,会出现多个并发线程同时下单把库存扣到负数的情况。

需要实现库存扣减过程中的原子性和有序性

原子性:库存查询、库存判断以及扣减动作,作为一个原子操作,过程中不会被打断,也不会有其他线程执行。

有序性:多个并发操作需要排队执行

数据库扣减,在扣减过程中想要保证原子性和有序性,可以采用加锁的方式,无论是悲观锁还是乐观锁都可以实现。

悲观锁会导致很多请求被迫阻塞并且排队,并发量很大会拖垮数据库

乐观锁会存在大量的失败,而且高并发场景中也不适合使用乐观锁,因为乐观锁在update的过程中也是需要加行级锁的,也会出现阻塞的情况。

借助数据库自己执行引擎的顺序执行机制,通过sql语句直接控制

也不好,缺点同乐观锁,完全依赖数据库,多个update进行时发生阻塞。

redis扣减

基于redis做库存扣减,借助redis的单线程执行的特性,再加上lua脚本执行过程中的原子性保障,我们可以在redis中通过lua脚本进行库存扣减。

先从Redis中取出当前的剩余库存,然后判断是否足够扣减,如果足够的话,就进行扣减,否则就返回库存不足

因为lua脚本在执行过程中,可以避免被打断,并且redis执行的过程也是单线程的,所以在脚本中进行判断,再扣减,这个过程是可以避免并发的。所以也就可以实现前面我们说的原子性+有序性了。

并且Redis是一个高性能的分布式缓存,使用Lua脚本扣减库存的方案也非常的高效。

这两个方案,一个是通过数据库进行库存扣减,一个是通过redis实现扣减,一般,在实际应用过程中,这两种方案会结合使用,

也就是说先在Redis中做扣减,利用Redis来抗高并发流量,然后再同步到数据库中,在数据库中做扣减并进行持久化存储,避免Redis挂了导致数据丢失。

一般的做法是,先在Redis中做扣减,然后发送一个MQ消息,消费者在接到消息之后做数据库中库存的真正扣减及业务逻辑操作。

但是这种方案可能会导致少卖:

假设,上面的流程中,第1步执行成功了,Redis中库存成功扣减了,但是后续第2步的消息没有发出去,或者后面的消费过程中消息丢了或者失败了等情况。就会出现redis扣库存但是数据库库存没扣。

解决办法是引入对账机制,做一些准实时的核对。(比如用zset在redis中添加流水记录,然后定时拉一段时间内的所有记录,和数据库比对,发现不一致,则进行补偿处理)

黄牛抢购

借助算法检测出哪些用户可能是黄牛,可以把这部分用户的ID直接加入黑名单,在Nginx中或者业务系统中都可以做过滤

还有一种防刷,是防止别人直接通过脚本的方式直接调我们的接口,这种的话,我们可以借助token的方式实现防刷,

当用户访问页面时,发放一个token,请求过来时,需要把token带过来,这时候我们做校验,token合法则接受请求,并且让token失效。token不合法或者已失效则直接拒绝请求即可。

重复下单

基于token做重复下单的检测,如果用户在一个页面上没刷新页面的话,token是一样的。

引入锁机制来保证下单的幂等

一锁:加锁,分布式锁或者悲观锁都可以,一定要是个互斥锁

二判:幂等性判断,可以基于状态机、流水表、唯一性索引等等进行重复操作的判断。

三更新:进行数据的更新,将数据持久化。

不管怎么样,数据库的唯一性约束都要加好,万一前面的锁失效了,这里也能控制不产生脏数据。

对普通交易的影响

做隔离,物理隔离和逻辑隔离。

物理隔离就是前后端服务,包括数据存储都彻底分开。

或者应用上做物理隔离,数据上做逻辑隔离。(在订单、商品上打标,方便后续查询以及数据分析)

业务手段

预约、预售、验证码等

为什么不用分布式锁来实现秒杀?

我们要实现秒杀的库存扣减,最重要的是两个点:1、抗更高的并发。2、避免超卖。尤其重要的就是防止超卖,这也是我们加锁的目的。

拿我们常用的 Redisson 分布式锁来说,如果用了 tryLock,不考虑 waitTime的合理性情况下,和 lua 脚本的执行也差不多,就是排队执行。

但是,如果我们用lua 脚本,可以直接用利用他的原子性特性,在一个脚本中实现库存的检查、扣减等动作。这样才能避免超卖!

如果使用分布式锁,在不使用 lua 脚本的情况下,每次库存扣减操作都需要多次与 Redis 服务器通信(例如,加锁、读取库存、扣减库存、释放锁等)。这不仅增加了网络延时,还增加了系统的复杂性。

而如果使用 Lua 脚本,所有操作可以在一次脚本执行中完成,这大大减少了网络传输时间和通信次数。

计算机网络:

整理了25个常见问题,对计网有更深的了解。

代码随想录:

344.反转字符串
541. 反转字符串II
替换数字
151.翻转字符串里的单词
右旋字符串

没跟上打卡,下周多花时间在项目和算法上

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值