温故而知新-秒杀项目篇【面试复习】

温故而知新-秒杀项目篇【面试复习】

  • 前言
  • 版权
  • 推荐
  • 温故而知新-论坛项目篇【面试】
    • 秒杀项目中注册模块怎么实现的?
    • 秒杀项目中登录模块怎么实现的?
    • 秒杀项目中显示登录用户信息怎么实现的?
    • SessionStorage是什么?
    • 为什么不用session而用token
    • 什么是序列化和反序列化
    • 秒杀接口限流防刷
    • 秒杀项目中为什么使用rocketmq
    • 异步化扣减库存是怎么实现的
    • 下单的流程
    • 如何解决超卖
      • 使用redis的原子命令:decrement
      • 改成一个Lua脚本
      • redis分布式锁实现
      • 怎么合理地设置分布式锁的过期时间
      • Redisson
    • redisson分布式锁的原理
    • redisson分布式锁比setnx有什么优势?
  • 优化
    • 安全优化
    • 功能优化
    • 性能优化
  • 设计
    • 高并发下如何设计秒杀系统?
      • 页面静态化
      • 按钮至灰控制
      • 服务单一职责
      • 秒杀链接加盐
      • 限流
      • 分布式锁
      • MQ 异步处理
      • 限流&降级&熔断
    • 在秒杀场景中,常用的限流算法有哪些?
      • 1、什么是限流?
      • 2、限流算法
  • 最后

前言

2023-7-31 16:01:39

以下内容源自《【面试复习】》
仅供学习交流使用

版权

禁止其他平台发布时删除以下此话
本文首次发布于CSDN平台
作者是CSDN@日星月云
博客主页是https://blog.csdn.net/qq_51625007
禁止其他平台发布时删除以上此话

推荐

温故而知新-论坛项目篇【面试】

秒杀项目中注册模块怎么实现的?

用户:

  • 点击个人注册
  • 输入手机号
  • 点击获取验证码

前端请求后端:获取验证码

// 生成OTP 
UUID4位的随机数(0~9)
// 绑定OTP
2用户注册与登录:后端:把验证码存入到session中
6分布式状态管理:存入到redis中的<phone,otp,5mins>
// 发送OTP
输出到控制台里改为短信发送 
短信验证码【java提高】 https://jsss-1.blog.csdn.net/article/details/125790714

用户填写信息,点击立即注册

前端请求后端:注册

UserController:调用register()

// 验证OTP 
2用户注册与登录:session.getAttribute()
6分布式状态管理:取到phone对应的otp
// 加密处理
// 注册用户

秒杀项目中登录模块怎么实现的?

用户输入手机号和密码

前端请求后端:登录
后端调用login()

校验用户名和密码

2用户注册与登录:存入到session中
6分布式状态管理:存入到redis中的token中 返回token给前端
前端:把返回的token存入到浏览器sessionStorage
每次ajax异步请求都会带上token,为了得到当前登录用户

秒杀项目中显示登录用户信息怎么实现的?

调用UserController:getUser()

2用户注册与登录:从session中取出
6分布式状态管理:在redis中取出

SessionStorage是什么?

https://juejin.cn/post/6844903975800537096

本质就是存在于浏览器上的 hash(哈希表)。
localStorage生命周期是永久,这意味着除非用户显示在浏览器提供的UI上清除localStorage信息,否则这些信息将永远存在。存放数据大小为一般为5MB,而且它仅在客户端(即浏览器)中保存,不参与和服务器的通信。

sessionStorage 的所有性质基本上与 localStorage 一致,唯一的不同区别在于:
sessionStorage 的有效期是页面会话持续,如果页面会话(session)结束(关闭窗口或标签页),sessionStorage 就会消失。而 localStorage 则会一直存在。

为什么不用session而用token

cookie和session及token,为什么选择使用token?JWT使用

「揭秘」Token与Session到底有何异同?
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

什么是序列化和反序列化

https://blog.csdn.net/weixin_44211968/article/details/128112325

序列化:把对象转化为可传输的字节序列过程称为序列化。
反序列化:把字节序列还原为对象的过程称为反序列化。

秒杀接口限流防刷

Java项目笔记之秒杀接口防刷限流
https://www.shangyexinzhi.com/article/2741037.html

秒杀项目中为什么使用rocketmq

RocketMQ和其他消息中间件最大的一个区别是支持了事务消息,
这也是分布式事务里面的基于消息的最终一致性方案。

【RocketMQ】高级使用:四个问题详解事务消息

在redis中预减缓存,防止超卖
mq保证mysql中库存最终被扣减

异步化扣减库存是怎么实现的

redis存储库存,mq保证最终一致性

OrderController.create()
抛出:下单失败

OrderServiceImpl.createOrderAsync()

如果已经售罄了:hasKey()
抛出:已经售罄

生成库存流水:createItemStockLog()
输出:生成库存流水完成

输出:投递扣减库存事务消息
发送事务消息:sendMessageInTransaction
抛出:创建订单失败

LocalTransactionListenerImpl.executeLocalTransaction()

执行本地事务:createOrder()

抛出:执行MQ本地事务时发生错误

LocalTransactionListenerImpl.createOrder()

输出:本地事务提交完成
return RocketMQLocalTransactionState.COMMIT;

抛出:创建订单失败&更新流水完成
return RocketMQLocalTransactionState.ROLLBACK;

OrderServiceImpl.createOrder()

预扣库存:decreaseStockInCache()

**ItemServiceImpl.decreaseStockInCache()**
在redis中实现:decrement
库存为0是添加售罄标识 `<"item:stock:over:" + itemId, 1>`输出:回补库存完成|售罄标识完成
return result>=0;

输出:预扣减库存完成
抛出:库存不足

生成订单
输出:生成订单完成

更新销量,发送异步消息
输出:投递增加商品销量消息成功|输出:投递增加商品销量消息失败

{**IncreaseSalesConsumer.onMessage()**itemService.increaseSales(itemId, amount);输出:更新销量完成|更新销量失败
}

更新库存流水的状态
输出:更新流水完成

DecreaseStockConsumer.onMessage()

itemService.decreaseStock(itemId, amount)
输出:最终扣减库存完成|输出:从DB扣减库存失败

下单的流程

点击商品之后,查询缓存(会初始化的)

点击购买输入验证码(redis验证码 1min)

redis验证码 1 min

提交验证码

判断验证码是否正确

限流
获取秒杀凭证(先判断有没有售罄标识 redis decrement)
获取不到,下单失败

防刷
RateLimiter;一个限制访问速率的工具

判断售罄标识

生成库存流水

执行本地事务
预减库存(redis decrement)
生成订单


检查本地事务(查看库存流水表)


执行本地事务
扣减库存

下单成功

如何解决超卖

使用redis的原子命令:decrement

//预扣库存//redisTemplate.opsForValue().decrement(key, amount);//售罄标识//redisTemplate.opsForValue().set("item:stock:over:" + itemId, 1);@Overridepublic boolean decreaseStockInCache(int itemId, int amount) {if (itemId <= 0 || amount <= 0) {throw new BusinessException(PARAMETER_ERROR, "参数不合法!");}String key = "item:stock:" + itemId;long result = redisTemplate.opsForValue().decrement(key, amount);if (result < 0) {// 回补库存this.increaseStockInCache(itemId, amount);logger.debug("回补库存完成 [" + itemId + "]");} else if (result == 0) {// 售罄标识redisTemplate.opsForValue().set("item:stock:over:" + itemId, 1);logger.debug("售罄标识完成 [" + itemId + "]");}return result >= 0;}

在上述代码中,使用Redis的decrement操作来预扣库存。如果库存数量减为负数(result < 0),则说明库存不足,需要回补库存。如果库存数量减至0(result == 0),则设置售罄标识。

这段代码可以一定程度上缓解超卖问题,但并不能完全解决。在高并发场景下,多个线程可能会同时进行decrement操作,导致库存数量减为负数。

可以通过以下方式来进一步解决超卖问题:

使用悲观锁或乐观锁来保证同一时间只有一个线程能够进行decrement操作,避免超卖问题。

在decrement操作前,通过GET命令获取当前库存数量,然后进行判断。如果库存数量小于等于请求的数量,则不进行decrement操作,避免超卖问题。

使用Lua脚本来确保decrement操作的原子性,同时进行判断和减少库存数量,避免并发问题。

综上所述,单纯使用Redis的decrement操作无法完全解决超卖问题,需要结合其他策略来确保库存的准确性和一致性。


改成一个Lua脚本

    //使用Lua脚本//得到stock//判断stock>amount//DECRBY stockKey, amount//SET stockOverKey, 1@Overridepublic boolean decreaseStockInCache(int itemId, int amount) {if (itemId <= 0 || amount <= 0) {throw new BusinessException(PARAMETER_ERROR, "参数不合法!");}String script = "local itemId = ARGV[1]\n" +"local amount = tonumber(ARGV[2])\n" +"local stockKey = \"item:stock:\" .. itemId\n" +"local stockOverKey = \"item:stock:over:\" .. itemId\n" +"local stock = tonumber(redis.call(\"GET\", stockKey))\n" +"if stock == nil or stock < amount then\n" +"    return 0\n" +"else\n" +"    local result = redis.call(\"DECRBY\", stockKey, amount)\n" +"    if result == 0 then\n" +"        redis.call(\"SET\", stockOverKey, 1)\n" +"    end\n" +"    return 1\n" +"end";List<String> keys = new ArrayList<>();keys.add("item:stock:" + itemId);keys.add("item:stock:over:" + itemId);Object result = redisTemplate.execute(new DefaultRedisScript<>(script, Long.class), keys, itemId, amount);return result != null && (Long) result == 1;}

使用Lua脚本可以解决一些并发操作的问题,特别是在Redis中。由于Redis的单线程特性,执行Lua脚本可以保证原子性操作,并避免了多个命令的竞态条件。

在预扣库存的场景中,使用Lua脚本可以确保以下几点:

原子性操作:Lua脚本在Redis中被作为一个整体进行执行,中间不会被其他操作打断,从而保证了预扣库存操作的原子性。

避免超卖:通过在Lua脚本中检查库存数量,并在预扣成功后减少库存,可以避免超卖问题的发生。

但是需要注意的是,即使使用Lua脚本也不能完全解决所有并发问题。在高并发场景下,可能仍然存在一些问题,比如竞争条件和网络延迟等。因此,在实际应用中,还需要结合其他技术和策略来提高系统的并发性和可靠性,例如分布式锁、分布式事务等。



上述代码是一个使用Lua脚本来实现分布式锁和解决超卖问题的例子。具体步骤如下:

首先,定义了一个Lua脚本,脚本中包含了获取库存、检查库存是否足够、扣减库存和标记库存是否售罄的逻辑。

在Java代码中,通过RedisTemplate的execute方法来执行Lua脚本。在执行过程中,需要传入脚本、键列表和参数。

脚本:使用DefaultRedisScript指定脚本和返回值类型。
键列表:包含了需要操作的键,这里是"item:stock:“+itemId和"item:stock:over:”+itemId。
参数:itemId和amount,即商品ID和购买数量。
执行结果返回一个Object对象,需要根据具体业务逻辑进行判断。这里判断如果返回的结果不为空,且结果等于1,表示扣减库存成功;否则表示库存不足或者其他错误。

需要注意的是,在实际应用中,还需要考虑到异常处理、分布式锁的释放、并发情况下的处理等问题。此处的代码只是一个简化的示例,具体的实现方式需要根据具体业务需求和实际情况进行调整和完善。


redis分布式锁实现

    //1.定义一个公共的方法来获取分布式锁。private boolean acquireLock(String lockKey, String requestId, int expireTime) {boolean lockAcquired = false;try {lockAcquired = redisTemplate.opsForValue().setIfAbsent(lockKey, requestId, expireTime, TimeUnit.MILLISECONDS);logger.info("获取分布式锁成功");} catch (Exception e) {// 锁获取失败的处理逻辑logger.info("获取分布式锁失败");return false;}return lockAcquired;}//2.在方法中添加获取分布式锁的逻辑。@Overridepublic boolean decreaseStockInCache(int itemId, int amount) {if (itemId <= 0 || amount <= 0) {throw new BusinessException(PARAMETER_ERROR, "参数不合法!");}String lockKey = "item:lock:" + itemId;String requestId = UUID.randomUUID().toString();int expireTime = 10000; // 锁的过期时间,单位为毫秒boolean lockAcquired = acquireLock(lockKey, requestId, expireTime);if (!lockAcquired) {// 获取锁失败的处理逻辑logger.info("获取分布式锁失败");return false;}try {// 执行库存扣减的逻辑String script = "local itemId = ARGV[1]\n" +"local amount = tonumber(ARGV[2])\n" +"local stockKey = \"item:stock:\" .. itemId\n" +"local stockOverKey = \"item:stock:over:\" .. itemId\n" +"local stock = tonumber(redis.call(\"GET\", stockKey))\n" +"if stock == nil or stock < amount then\n" +"    return 0\n" +"else\n" +"    local result = redis.call(\"DECRBY\", stockKey, amount)\n" +"    if result == 0 then\n" +"        redis.call(\"SET\", stockOverKey, 1)\n" +"    end\n" +"    return 1\n" +"end";List<String> keys = new ArrayList<>();keys.add("item:stock:" + itemId);keys.add("item:stock:over:" + itemId);Object result = redisTemplate.execute(new DefaultRedisScript<>(script, Long.class), keys, itemId, amount);return result != null && (Long) result == 1;} finally {// 释放锁boolean releaseLock = releaseLock(lockKey, requestId);if (releaseLock){logger.info("释放分布式锁成功");}else {logger.info("释放分布式锁失败");}}}//3.定义一个公共的方法来释放分布式锁。private boolean releaseLock(String lockKey, String requestId) {String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " +"   return redis.call('del', KEYS[1]) " +"else " +"   return 1 " +"end";List<String> keys = new ArrayList<>();keys.add(lockKey);Object result = redisTemplate.execute(new DefaultRedisScript<>(script, Long.class), keys, requestId);return result != null && (Long) result == 1;}

怎么合理地设置分布式锁的过期时间

上面设置了10s

需要确保RedisLock过期时间大于业务执行时间的问题


设置分布式锁的过期时间是否合理取决于你的具体需求和业务场景。一般来说,过期时间需要根据以下因素进行考虑:

业务处理时间:你需要根据业务处理的预估时间来设置过期时间。如果你的业务处理时间通常在几秒钟内完成,可以设置一个稍微长一点的过期时间,以免在处理期间锁已经过期失效。
并发访问量:如果同时对某个资源进行大量的并发操作,你可能需要更短的过期时间,以便尽快释放锁,让其他请求能够获得锁。
锁争用频率:如果对某个资源的锁争用频率很高,建议设置较短的过期时间,以便其他请求能够更快地获取到锁。
根据你的描述,如果你的业务处理时间在几秒钟内,而且对资源的锁争用频率不是特别高,10秒的过期时间可能是一个合理的选择。但请注意,在实际应用中,你可能需要根据实际情况进行调试和优化,以找到最适合你的业务场景的过期时间设置。


Redisson

        <!-- redisson --><dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.13.4</version></dependency>
@Configuration
@PropertySource("classpath:application.properties")
public class RedisConfiguration {@Autowiredprivate Environment env;@Beanpublic Redisson redisson() {Config config = new Config();String redisHost = env.getProperty("spring.redis.host");String redisPort = env.getProperty("spring.redis.port");String redisPassword = env.getProperty("spring.redis.password");int redisDatabaseNumber = env.getProperty("spring.redis.database", Integer.class, 0);String redisAddress = "redis://" + redisHost + ":" + redisPort;config.useSingleServer().setAddress(redisAddress).setPassword(redisPassword).setDatabase(redisDatabaseNumber);return (Redisson)Redisson.create(config);}
}
    @Overridepublic boolean decreaseStockInCache(int itemId, int amount) {if (itemId <= 0 || amount <= 0) {throw new BusinessException(PARAMETER_ERROR, "参数不合法!");}String lockKey = "item:lock:" + itemId;String REDIS_LOCK = lockKey+":"+UUID.randomUUID().toString();RLock redissonLock = redisson.getLock(REDIS_LOCK);redissonLock.lock();logger.debug("redissonLock加锁成功");try {// 执行库存扣减的逻辑String script = "local itemId = ARGV[1]\n" +"local amount = tonumber(ARGV[2])\n" +"local stockKey = \"item:stock:\" .. itemId\n" +"local stockOverKey = \"item:stock:over:\" .. itemId\n" +"local stock = tonumber(redis.call(\"GET\", stockKey))\n" +"if stock == nil or stock < amount then\n" +"    return 0\n" +"else\n" +"    local result = redis.call(\"DECRBY\", stockKey, amount)\n" +"    if result == 0 then\n" +"        redis.call(\"SET\", stockOverKey, 1)\n" +"    end\n" +"    return 1\n" +"end";List<String> keys = new ArrayList<>();keys.add("item:stock:" + itemId);keys.add("item:stock:over:" + itemId);Object result = redisTemplate.execute(new DefaultRedisScript<>(script, Long.class), keys, itemId, amount);logger.debug("执行库存扣减的逻辑成功");return result != null && (Long) result == 1;} finally {// 释放锁//避免IllegalMonitorStateException: attempt to unlock lock,not locked by current thread by node idif (redissonLock.isLocked()){if (redissonLock.isHeldByCurrentThread()){redissonLock.unlock();logger.debug("redissonLock释放成功");}}}}

在上述代码中,通过使用分布式锁确保了库存扣减的原子性操作,从而避免了多线程环境下的超卖问题。当多个线程同时执行库存扣减操作时,只有一个线程能够获取到锁,其他线程会在redissonLock.lock()处阻塞等待。

在获取到锁后,通过Lua脚本执行库存扣减的逻辑,并且使用DECRBY命令原子性地减少库存数量。如果库存不足,返回0表示扣减失败,否则返回1表示扣减成功。在扣减成功后,通过SET命令将标识库存已售罄的键设置为1。

这种方式可以有效地解决超卖问题。但是需要注意的是,使用分布式锁会增加系统的复杂性和延迟,并且需要保证Redis服务器的可用性和性能。因此,在设计系统时需要权衡分布式锁的使用场景和影响,确保在高并发环境下依然能够保持系统的稳定性和性能。

另外,除了使用分布式锁来解决超卖问题,还可以考虑其他方案,如使用乐观锁或悲观锁、使用消息队列等。具体选择哪种方案取决于系统的需求和特点。


2023-8-17 17:14:14

redisson分布式锁的原理

Redisson是一个基于Redis的分布式Java对象和服务库,它提供了多种分布式功能,包括分布式锁。Redisson的分布式锁机制主要基于Redis的特性和命令实现。

Redisson分布式锁的原理如下:

客户端通过Redisson框架的API请求获取锁。
Redisson将客户端的请求转化为Lua脚本,并通过eval命令发送给Redis服务器。
Redis服务器在单线程下执行Lua脚本,保证了脚本的原子性和线程安全性。
Lua脚本在Redis服务器中执行以下操作:
首先,通过SET命令尝试将锁对应的键设置为指定值(通常是一个唯一标识符)。
如果设置成功,则表示获取锁成功,返回成功标识。
如果设置失败,则表示锁已被其他客户端占用,返回失败标识。
同时,设置锁的过期时间,防止锁无限占用。
Redisson客户端接收到Redis服务器返回的结果,根据结果判断锁的获取是否成功,如果成功则继续执行业务逻辑,否则等待或进行其他处理。
Redisson的分布式锁实现利用了Redis的原子操作,通过设置指定的值来实现锁的获取和释放。同时,使用Lua脚本可以保证获取锁的操作是原子的,避免了竞争条件和并发问题。

需要注意的是,Redisson的分布式锁还具备可重入性和红锁特性,使得锁的使用更加灵活和可靠。

redisson分布式锁比setnx有什么优势?

Redisson分布式锁相对于直接使用SETNX命令实现分布式锁具有以下几个优势:

  1. 可重入性:Redisson分布式锁支持可重入,也就是同一线程可以多次获取同一个锁而不会被阻塞。这种可重入性能够解决一些特定场景下的并发问题。

  2. 锁的自动续期:Redisson分布式锁支持锁的自动续期,即在获取锁后,锁的过期时间会不断更新,防止持有锁的客户端在执行业务逻辑时由于各种原因导致锁过期而被其他客户端抢占。

  3. 防止死锁的解锁机制:Redisson分布式锁通过监听锁的失效事件来实现解锁操作,避免了因为客户端崩溃或其他原因导致的锁无法释放的情况。

  4. 红锁特性:Redisson分布式锁支持红锁特性,即在多个Redis节点之间实现锁的互斥。当多个Redis节点之间进行通信时,可以使用分布式锁的红锁特性保证在大多数节点上加锁成功才算真正获取到锁,避免了分布式环境下的数据不一致问题。

  5. 强大的扩展性:Redisson分布式锁是基于Redisson框架的分布式Java对象和服务库实现的,提供了很多其他功能和特性,如分布式服务、分布式集合等。使用Redisson分布式锁可以很方便地与其他分布式功能进行集成和扩展。

综上所述,Redisson分布式锁相比于直接使用SETNX命令实现分布式锁,具有更多的功能和特性,可以更方便地应对分布式环境中的并发和竞争问题。

优化

安全优化

对用户注册登录的密码使用公私钥加密进行前后端传输
对密码进行MD5+盐值存入数据库

功能优化

sms验证码登录

秒杀链接动态化

订单超时未支付,取消订单,回补库存

性能优化

超卖:分布式锁

订单号生成使用雪花算法

设计

高并发下如何设计秒杀系统?

设计一个秒杀系统,需要考虑这些问题:

在这里插入图片描述

如何解决这些问题呢?

  • 页面静态化
  • 按钮至灰控制
  • 服务单一职责
  • 秒杀链接加盐
  • 限流
  • 分布式锁
  • MQ 异步处理
  • 限流&降级&熔断

页面静态化

秒杀活动的页面,大多数内容都固定不变,如商品名称,商品图片等等,可以对活动页面做静态化处理,减少访问服务端的请求。秒杀用户会分布在全国各地,有在上海,有在深圳,地域相差很远,网速也各不相同。为了让用户最快访问到活动页面,可以使用 CDN(Content Delivery Network,内容分发网络)。CDN 可以让用户就近获取所需内容。

按钮至灰控制

秒杀活动开始前,按钮一般需要置灰的。只有时间到了,才能变得可以点击。这样防止,秒杀用户在时间快到时前几秒,疯狂请求服务器,然后秒杀时间点还没到,服务器就自己挂了。

服务单一职责

我们都知道微服务设计思想,也就是把各个功能模块拆分,功能那个类似的放
一起,再用分布式的部署方式。

如用户登录相关的,就设计个用户服务,订单相关就搞个订单服务,再到礼物相关的就搞个礼物服务等等。那么,秒杀相关业务逻辑也可以放到一起,搞个秒杀务,单独给它搞个秒杀数据库。

服务单一职责有个好处:如果秒杀没抗住高并发的压力,秒杀库崩了,服务挂了,也不会影响到系统的其他服务。

秒杀链接加盐

链接如果明文暴露的话,会有人获取到请求 Url,提前秒杀了。因此,需要给秒杀链接加盐。可以使URL 动态化,如通过 MD5 加密算法加密随机字符串去做 url。

限流

一般有两种方式限流:nginx 限流和redis 限流。

  • 为了防止某个用户请求过于频繁,我们可以对同一用户限流;
  • 为了防止黄牛模拟几个用户请求,我们可以对某个 IP 进行限流;
  • 为了防止有人使用代理,每次请求都更换 IP 请求,我们可以对接口进行限流。
  • 为了防止瞬时过大✁流量压垮系统,还可以使用阿里Sentinel、Hystrix 组件进行限流。

分布式锁

可以使用 redis 分布式锁解决超卖问题。
使用 Redis 使用 SET EX PX NX + 校验唯一随机值,再删除释放锁。

if(jedis.set(key_resource_id, uni_request_id, "NX", "EX", 100s) == 1{ //加锁try {do something / / 业务处理}catch(){}finally {//判断是不是当前线程加锁,才释放if (uni_request_id.equals(jedis.get(key_resource_id))) {jedis.del(lockKey); //释放锁}}
}

在这里,判断是不是当前线程加锁和释放锁不是一个原子操作。如果调用
jedis.del()释放锁时候,可能这锁已经不属于当前客户端,会解除他人加
锁。
在这里插入图片描述
为了更严谨,一般也✁用 lua 脚本代替。lua 脚本如下:

if redis.call('get',KEYS[1]) == ARGV[1] thenreturn redis.call('del',KEYS[1])
elsereturn 0
end;

MQ 异步处理

如果瞬间流量特别大,可以使用消息队列削峰,异步处理。用户请求过来✁时
候,先放到消息队列,再拿出来消费。

限流&降级&熔断

  • 限流,就是限制请求,防止过大请求压垮服务器;
  • 降级,就是秒杀服务有问题了,就降级处理,不要影响别的服务;
  • 熔断,服务有问题就熔断,一般熔断降级是一起出现。

在秒杀场景中,常用的限流算法有哪些?

1、什么是限流?

所谓限流,就是指限制流量请求的频次。它主要是在高并发情况下,用于保护系统的一种策略,主要是避免在流量高峰导致系统崩溃,造成系统不可用的问题。

实现限流常见的算法4种,分别是计数器限流算法、滑动窗口限流算法、漏桶限流算法、令牌桶限流算法。下面,我给大家详细介绍每种算法的基本原理。

2、限流算法

1、计数器限流算法。
在这里插入图片描述

一般用在单一维度的访问频率限制上,比如短信验证码每隔 60s只能发送一次,或者接口调用
次数等。它的实现方法很简单,就是每调用一次就加 1,处理结束以后减1。

2、滑动窗口限流算法,
在这里插入图片描述

本质上也是一种计数器,只是通过以时间为维度的可滑动窗口设计,来减少了临界值带来的并发超过阈值的问题。每次进行数据统计的时候,只需要统计这 个窗口内每个时间刻度的访问量就可以了。

Spring Cloud 中的熔断框架 Hystrix,以及 Spring Cloud Alibaba 中的Sentinel 都采用滑动窗口来做数据统计。

3、漏桶限流算法。
在这里插入图片描述

它是一种恒定速率的限流算法,不管请求量是多少,服务端的处理效率是恒定的。基于 MQ 来实现
的生产者消费者模型,其实算是一种漏桶限流算法。

4、令牌桶限流算法。
在这里插入图片描述

相对漏桶算法来说,它可以处理突发流量的问题。它的核心思想是,令牌桶以恒定速率去生成令牌保存到令牌桶里面,桶的大小是固定的,令牌桶满了以后就不再生成令牌。

每个客户端请求进来的时候,必须要从令牌桶获得一个令牌才能访问,否则排队等待。在流量低峰的时候,令牌桶会出现堆积,因此当出现瞬时高峰的时候,有足够多的令牌可以获取,因此令牌桶能够允许瞬时流量的处理。

网关层面的限流、或者接口调用的限流,都可以使用令牌桶算法,像 Google 的Guava,和Redisson 的限流,都用到了令牌桶算法。

我认为,限流的本质是实现系统保护,最终选择什么样的算法,一方面取决于统计的精准度,另一方面考虑限流维度和场景的需求。

最后

2023-7-31 16:02:02

我们都有光明的未来

祝大家考研上岸
祝大家工作顺利
祝大家得偿所愿
祝大家如愿以偿
点赞收藏关注哦

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/14878.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

骨位深间距小模具镶件如何走水路?3D打印让一切简单

在模具制造领域&#xff0c;骨位深且间距小的模具镶件由于结构复杂&#xff0c;传统加工方法难以制造出符合要求的冷却水路&#xff0c;导致模具在注塑过程中容易产生热量积聚&#xff0c;进而引发烫伤、缩孔等不良。然而&#xff0c;随着3D打印技术的飞速发展&#xff0c;这些…

0基础学习Mybatis系列数据库操作框架——Mysql的Geometry数据处理之WKT方案

大纲 几何结构构建点点集合线线集合面面集合几何信息集合 TypeHandlerSQL操作写入操作读取操作完整XML Mapper测试代码建表SQL总结代码参考资料 WKT全称是Well-Known Text。它是一种表达几何信息的字符串内容。比如点可以用WKT表示为POINT (3 3)&#xff1b;线可以用WKT表示为L…

Playwright教程

Playwright简介 支持多数浏览器 在Chromium&#xff0c;Firefox和WebKit上进行测试。Playwright拥有适用于所有现代浏览器的完整API覆盖&#xff0c;包括Google Chrome和Microsoft Edge&#xff08;带有Chromium&#xff09;&#xff0c;Apple Safari&#xff08;带有WebKit&a…

【哈希映射 字符串 乘法原理】2227. 加密解密字符串

本文涉及知识点 哈希映射 字符串 乘法原理 LeetCode 2227. 加密解密字符串 给你一个字符数组 keys &#xff0c;由若干 互不相同 的字符组成。还有一个字符串数组 values &#xff0c;内含若干长度为 2 的字符串。另给你一个字符串数组 dictionary &#xff0c;包含解密后所…

SpringCloud微服务03-微服务保护-分布式事务-MQ基础-MQ高级

一、微服务保护 1.雪崩问题 如何做好后备方案就是后续&#xff1a; 2.雪崩解决方案 某一个服务的线程是固定的&#xff0c;出现故障线程占满后&#xff0c;就不会让取调用这个服务&#xff0c;对其他服务就没有影响。 3.Sentinel ①初识Sentinel 配置过程&#xff1a;day05-服…

C++之多态详解

1. 多态的概念 1.1 概念 多态的概念&#xff1a;通俗来说&#xff0c;就是多种形态&#xff0c;具体点就是去完成某个行为&#xff0c;当不同的对象去完成时会 产生出不同的状态。 举个栗子&#xff1a;比如买票这个行为&#xff0c;当普通人买票时&#xff0c;是全价买票&a…

源码编译安装LAMP

目录 1.LAMP概述 2.编译安装Apache httpd服务 1.关闭防火墙&#xff0c;将安装Apache所需软件包传到/opt目录下 2.安装环境依赖包 3.配置软件模块 4.编译及安装 5.优化配置文件路径&#xff0c;并把httpd服务的可执行程序文件放入路径环境变量的目录中便于系统识别 6.添…

数据清洗(ETL)案例实操

文章目录 数据清洗&#xff08;ETL&#xff09;概述案例需求和分析代码实现和结果分析 数据清洗&#xff08;ETL&#xff09;概述 “ETL&#xff0c;是英文Extract-Transform-Load的缩写&#xff0c;用来描述将数据从来源端经过抽取&#xff08;Extract&#xff09;、转换&…

查询一个字符串在另一个字符串中出现的次数(java)

查询一个字符串在另一个字符串中出现的次数 例&#xff1a; String str1“helloworld,java,python,hellokafka,world big table helloteacher”; String str2“hello”; 字符串str2在str1中出现3次 代码 package exercise.test8;public class Demo8 {public static void mai…

【网络安全】2030年十大新兴网络安全威胁

欧盟网络安全局&#xff08;ENISA&#xff09;已发布了一份全面的清单&#xff0c;列出了预计到2030年将影响数字领域的十大新兴网络安全威胁。 该预测是为期八个月的广泛研究的成果&#xff0c;融合了ENISA前瞻专家小组、CSIRTs网络以及欧盟CyCLONe专家的见解。 这项研究突显…

[技术报告]InternLM2 Technical Report

摘要 像ChatGPT和GPT-4这样的大型语言模型&#xff08;llm&#xff09;的进化引发了人们对人工通用智能&#xff08;AGI&#xff09;出现的讨论。然而&#xff0c;在开源模型中复制这种进步一直是一个挑战。本文介绍了InternLM2&#xff0c;这是一个开源的大语言模型&#xff…

【面试干货】矩阵对角线元素之和

【面试干货】矩阵对角线元素之和 1、实现思想2、代码实现 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; 1、实现思想 创建一个3x3的二维数组来表示输入的矩阵。通过嵌套循环读取输入的矩阵元素&#xff0c;并将其保存到数组中。再次嵌套循…

揭秘齿轮加工工艺的选用原则:精准打造高效传动的秘密武器

在机械制造领域&#xff0c;齿轮作为传动系统中的重要组成部分&#xff0c;其加工工艺的选择至关重要。不同的齿轮加工工艺会影响齿轮的精度、耐用性和效率。本文将通过递进式结构&#xff0c;深入探讨齿轮加工工艺的选用原则&#xff0c;带您了解如何精准打造高效传动的秘密武…

Web应用防火墙的重要性

网络安全是一个永恒的话题&#xff0c;尤其是在未知威胁不断涌现的情况下。企业网络安全是保障业务稳定运行的基础&#xff0c;Web应用防火墙(WAF)是企业网络安全的重要屏障&#xff0c;其性能直接影响到网络服务的质量和安全。 Web应用防火墙是什么&#xff1f; Web应用防火墙…

试试这个自动备份工具!

需要定时备份数据&#xff01; 在这个科技迅速发展的时代&#xff0c;数据安全变得极为重要。作为普通用户&#xff0c;我们需要了解数据备份的重要性。数据备份就像一把保护我们的伞&#xff0c;让我们不用担心重要文件意外丢失带来的困扰。如果我们忽视数据备份&#xff0c;…

SpringIOCDI—第一讲

文章目录 什么是IOC什么是控制&#xff0c;谁控制谁什么是反转&#xff0c;从什么反转到什么了 IOC的注解五大类注解Controller注解&#xff08;控制器存储&#xff09;Service&#xff08;服务存储&#xff09;Repository&#xff08;仓库存储&#xff09;Componet(组件存储)C…

云端漫步:搭建个人博客的移动云之旅

&#x1f482;作者简介&#xff1a; Thunder Wang&#xff0c;阿里云社区专家博主&#xff0c;华为云云享专家&#xff0c;腾讯云社区认证作者&#xff0c;CSDN SAP应用技术领域优质创作者。在学习工作中&#xff0c;我通常使用偏后端的开发语言ABAP&#xff0c;SQL进行任务的完…

每日一题(3)——统计合格率(不会哦)

我们来看一个案例&#xff1a; 如何理解 pass【j】 ? 为什么pass[0]3,pass[1]4? 我一直没有想通&#xff0c;自己重新测试了一些数据&#xff0c;还是没有想明白&#xff0c;希望大家能够集思广益&#xff0c;点拨点拨&#xff1a; 下面的数组我随便使用的数据&#xff0c; …

基于EBAZ4205矿板的图像处理:09基于sobel边缘检测的图像锐化

基于EBAZ4205矿板的图像处理&#xff1a;09基于sobel边缘检测的图像锐化 项目全部文件 随后会上传项目全部文件 先看效果 锐化的有点过头了&#xff0c;不过我也懒得改了&#xff0c;想要改也很简单&#xff0c;无非就是给卷积运算后的结果加个系数&#xff0c;通过改系数调…

【数据分析】Numpy和Pandas库基本用法及实例--基于Japyter notebook实现

各位大佬好 &#xff0c;这里是阿川的博客 &#xff0c; 祝您变得更强 个人主页&#xff1a;在线OJ的阿川 大佬的支持和鼓励&#xff0c;将是我成长路上最大的动力 阿川水平有限&#xff0c;如有错误&#xff0c;欢迎大佬指正 承接上篇的博客 数据分析—技术栈和开发环境搭…