限频和服务降级(转)

原文:

什么是限频和服务降级 ?

 

要保证一个大流量对外服务的稳定性, 通常我们很相当注意两个功能控制…  一个是请求的限流,一个是服务降级处理,他的意义在于不会让你的服务全瘫痪了,你可以适当的损失点东西利益,来保证最基础的功能, 这就是过载保护.

 

每个接口所能提供的单位时间服务能力是有限的。超过服务服务的承载能力,一般会造成整个接口服务停顿,或者应用 Crash毁掉,或者带来一系列未知已知的连锁反应,这样造成整个系统的服务能力丧失,SO 有必要在服务能力超限的情况下实时过载保护。 微信抢红包和小米抢购中,我们会遇到有人OK,有人被友好的deny了,这是因为服务过载保护了 .

为什么要做限流限频和降级?

首先假设几个有意思的场景,从用户访问的角度来看,如果设想有人想暴力碰撞网站的用户密码;或者黑客们尝试各种的sql注入;或者有人cc攻击某个很耗费资源的接口;或者有人想从某个接口大量抓取数据接口等等。  这时候标准的方法是加 应用级的防火墙,也就是咱们说的waf, waf是自带在线行为分析的.

除此之外,我们可以想象促销抢购的需求,当我们已知服务的只能应答约500qps, 但通过促销活动的力度推测可能要超过这个数,请求格外的多…. 

这时候就要做过载保护了,增加Rate limiting请求限速是个相当直接又易用的好办法。 请求增加限频后,我们后面有时间进一步解决这个问题,比如封堵来源或者特征…  也就是说 限速 是最根本的要求.  那么对于抢购的正常的请求遇到限速后,那么没办法,只能是粗暴的踢人了, 虽然损失了一部分超限的正常请求,但最少还能接客… 不至于请求都来了,后端服务因为处理不过来,直接hang住了,没得玩了…

可以想象成 村里的土鳖电闸安装了保险丝,一旦有人使用超大功率的设备,保险丝就会烧断以保护各个电器不被强电流给烧坏. 同理我们的接口也需要安装上“保险丝”,以防止非预期的请求对系统压力过大而引起的系统瘫痪,当流量过大时,可以采取拒绝或者引流等机制。 

再回溯下刚才的电商促销场景,这对于 “服务降级” 的意义又是什么? 为啥又要用服务降级 ?

当服务器压力剧增的情况下,根据当前业务情况及流量对一些服务和页面有策略的降级,以此释放服务器资源以保证核心任务的正常运行。

假定可以分为一级服务,二级服务,三级服务,比如在紧急情况下,务必保证一级服务的稳定性,然后可以牺牲二级和三级服务,这个就是服务降级. 

如果还是不懂,那么再举一些例子.  分级保护下 电商里面基本查询和支付可以用,至于评论、买家秀、聊天、收藏、评分等等可以先放放,最少保证买东西到付款这几步的api能正常.    又比如qq当带宽不够时 先砍视频通话,接着砍语音、传文件、群聊天、群图片、各信息推送.   最后最后至少能保留用户的在线状态.  以此类推,各种事例… 

服务降级方式:

分级范围拒绝:  也就是我们上面说到的路数,根据uri来区分接处理口, 对于大量的接口适合走nginx lua redis, 当然你在后端做也可以. 

增删改查接口拒绝:拒绝所有增删改动作,只允许查询, 错误页面内容可在CDN内获取。 如果是查询,那么直接走cache. 

延迟持久化:页面访问照常,但是涉及记录变更,会提示稍晚能看到结果,将数据记录到异步队列或可回溯log,服务恢复后执行。

xxx: 还有几种…. 

 

说完服务降级后,再聊下服务请求的限频问题.   

在哪里实现接口的限速限频 ?  限速限频的算法有多少? 

接入层的限流:

  • 基于nginx的limit_conn模块, 用来限制瞬时并发连接数.
  • 基于nginx的limit_req模块, 限制每秒的平均速率.
  • 基于nginx lua redis定制模块, 可实现多时间区间限速,具体uri限速、用户层限速等, 当然可分布式限速.

后端服务层限速:

  • 基于用户级别的限速.
  • 基于api的限速.
  • 基于用户加api的限速.
  • 基于流量限速
  • 等等…

后端实现的限速明显更加细致灵活,前端因为是nginx所以性能肯定是最好的. 理论上来说后端能实现的复杂限速体系,我们可以复用在前端接入层.  nginx lua可以足够我们构建复杂的接口限频体系了.    如果咱们是集群的场景,量级又不是吓人的.  用后端的限速完全可以支撑一个量级,多了不敢说,我们长期压测十几万的限频速度是没有问题, 注意 压测源和服务处理都是集群 !!! 
我们知道分布式限速讲究的是快,那么redis就足够快的了,一个实例完全可以到7w稳定的ops. 那么一致性hash + redis多实例 . 

当然共享的计数的nosql是快了,后端的性能又怎么保证?  你的后端如果是那种bio模式,比如php-fpm、uwsgi、早期apache prefork.  那就没得扯了.  
为啥bio不行?  你在google去 !!!  当时后端是golang beego…  后来因各种情况变更成渣渣python,web框架是自己用libev撸的轮子, 支持多进程那种, 服务器加到5台左右, 也是可以压到十几万的限频速度. 

接入层的限速相对来说粗暴呀.  像limit_conn、limit_req是可以用ip地址或request path限频的. 但如果你想要那种用户层的限速,可以通过cookie来控制,现存的limit_xxx模块有点粗,不能拆分cookie, 这时候只能上nginx lua了. 

我见过最复杂的限频是针对用户、uri、用户+uri、多时间区间分别来限速的… 特蛋疼…. 为了解决一次次的redis io时间消耗,我们上了redis内置lua. 但是redis lua又不支持跨多实例及机器. redis cluster也无法使用lua. 那么只能提前定义规则,尽量hash到一个redis. 

 

服务限频限流有这么几种方法:

  • 第一是 令牌桶限流算法
  • 第二是 漏桶限流算法
  • 第三个是 原子计数器的算法. 

令牌桶算法:

1. 每秒会有 r 个令牌放入桶中,或者说,每过 1/r 秒桶中增加一个令牌 

2. 桶中最多存放 b 个令牌,如果桶满了,新放入的令牌会被丢弃 

3. 当一个 n 字节的数据包到达时,消耗 n 个令牌,然后发送该数据包 

4. 如果桶中可用令牌小于 n,则该数据包将被缓存或丢弃

 

漏桶算法:

1. 数据被填充到桶中,并以固定速率注入网络中,而不管数据流的突发性 

2. 如果桶是空的,不做任何事情 

3. 主机在每一个时间片向网络注入一个数据包,因此产生一致的数据流

 

 

这两个算法是有区别的: 漏桶算法能够强行限制数据的传输速率,  而令牌桶算法在能够限制数据的平均传输速率外,还允许某种程度的突发传输.

 

 

计数器限频:

简单粗暴的累加计数, 超过就deny,没超过就pass.   我们可以用当前时间的某个单位及方法参数的md5做成key.  value是Int数值,然后累加计数就可以了.   我个人还是比较喜欢计数器的限频,可以设置多个时间区间,比如一秒钟可以10个,一分钟只能200个,一小时只能8000个…     计数限频不单单是那种类似常量和length方法对比限频,比如说,数据库的连接数,协程池,秒杀并发.  而且也是可以实现 令牌桶及漏桶的算法的.  

 

其实前两种限速限频算法更多的用于接入层,比如nginx,iptables mark tc, 网络设备qos 等等. . .

 

针对nginx limit_conn limit_req限速的配置,这里简单阐述下:

添加limit_conn 和limit_req 这个变量可以在http, server, location使用(如果你需要限制部分服务,可在nginx/conf/domains里面选择相应的server或者location添加上便可)  一般都是选择性的添加限频, 比如针对登陆、支付接口可以加 location层限频,针对普通增删改查的逻辑可共用一套限频.  针对翻页递归的查询应该独立一套限频.  类似的还有全文搜索,嵌套评论等等.

 


参数详解( 数值按具体需要和服务器承载能力设置):

 

<think>好的,我现在需要帮用户解决Java中时秒杀和优惠券的问题。首先,我得弄清楚用户具体想知道什么。时秒杀和优惠券都是电商中常见的促销功能,但它们在实现上有什么挑战呢? 对于时秒杀,可能面临高并发的问题,大量用户在同一时间抢购,系统压力大。库存超卖是一个关键点,如何保证库存扣减的准确性。另外,优惠券可能需要处理发放、使用制、叠加规则等。用户可能关心如何设计这些功能,避免出现漏洞,比如超发或者重复使用。 首先,我得分开来看这两个功能。时秒杀的核心是应对高并发和库存控制。优惠券则涉及业务规则和防止滥用。可能需要结合Redis这样的高性能缓存来处理实时库存,数据库如MySQL用于持久化,但事务处理可能成为瓶颈。 然后,针对秒杀,常用的方法包括提前预热库存到Redis,用分布式锁或者Lua脚本保证原子性操作。流和队列削峰也是必要的,比如用消息队列异步处理订单,避免直接冲击数据库。此外,前端可能需要做一些优化,比如按钮禁用防止重复提交,倒计时同步等。 优惠券方面,需要考虑生成、发放、核销的流程。唯一券码、用户领取制、使用条件(如满减、有效期)、叠加规则等。数据库设计时可能需要多张表来管理优惠券信息、用户领取记录等。并发领取时,用Redis的incr操作来防止超发,或者数据库的乐观锁。 用户可能还关心分布式环境下的一致性问题,比如如何保证扣减库存和生成订单的事务。可能需要引入分布式事务框架,或者最终一致性的方案,比如先扣库存再异步创建订单,配合补偿机制。 此外,缓存和数据库的数据同步也很重要。比如秒杀结束后,需要将Redis中的库存同步回数据库,或者处理订单失败时的库存回补。优惠券的核销同样需要保证原子性,避免同一张券被多次使用。 安全方面,防止恶意请求,比如用验证码、IP制、用户行为分析等。优惠券的防刷策略,比如制每个用户的领取次数,率控制。 可能还需要考虑系统的扩展性和容错,比如服务降级、熔断机制,确保在高并发下系统不会崩溃。测试阶段,用压测工具模拟高并发场景,验证系统的承载能力。 总结下来,需要分步骤设计:需求分析、架构设计(缓存、数据库、队列)、接口设计(幂等性)、安全措施、测试验证。每个环节都要考虑高并发和数据一致性的问题,合理利用现有的中间件和技术手段来优化性能,确保系统的稳定和用户体验。</think>在Java中实现时秒杀和优惠券功能需要结合高并发处理、分布式系统设计及业务规则控制。以下是解决方案的核心思路: ### 一、时秒杀解决方案 #### 1. 架构分层设计 ```text 客户端 → 网关层 → 服务层 → 缓存层 → 数据库层 ``` #### 2. 关键技术点 **(1)库存预热** ```java // 将商品库存预加载到Redis redisTemplate.opsForValue().set("seckill:stock:1001", 1000); ``` **(2)原子性扣减库存** ```lua -- Redis Lua脚本保证原子操作 local stock = redis.call('get', KEYS[1]) if tonumber(stock) > 0 then redis.call('decr', KEYS[1]) return 1 end return 0 ``` **(3)流量削峰** ```java // 使用消息队列(如RabbitMQ) @RabbitListener(queues = "seckill_queue") public void processSeckillOrder(SeckillMessage message) { // 异步处理订单 } ``` **(4)流策略** ```java // 使用Guava RateLimiter RateLimiter limiter = RateLimiter.create(5000); // 每秒5000个请求 if (!limiter.tryAcquire()) { throw new RuntimeException("活动太火爆,请稍后再试"); } ``` #### 3. 防超卖方案对比 | 方案 | 优点 | 缺点 | |--------------------|-----------------------|-----------------------| | 数据库乐观锁 | 实现简单 | 高并发下性能差 | | Redis原子操作 | 高性能 | 需处理缓存数据一致性 | | 分布式锁 | 强一致性 | 性能损耗较大 | ### 二、优惠券解决方案 #### 1. 系统设计要点 ```text 1. 优惠券模板管理 2. 发放规则控制 3. 核销校验逻辑 4. 防刷机制 ``` #### 2. 核心实现代码 **(1)发放优惠券** ```java // 使用Redis计数控制发放量 Long count = redisTemplate.opsForValue().increment("coupon:issue:1001"); if (count > 10000) { throw new RuntimeException("优惠券已领完"); } ``` **(2)核销校验** ```java // 校验优惠券状态(伪代码) public boolean validateCoupon(String userId, String couponId) { // 检查是否已使用 // 检查有效期 // 检查使用范围 // 检查叠加规则 return true; } ``` #### 3. 数据库设计示例 **优惠券表结构** ```sql CREATE TABLE `coupon` ( `id` BIGINT PRIMARY KEY COMMENT '券ID', `user_id` BIGINT NOT NULL COMMENT '用户ID', `template_id` BIGINT NOT NULL COMMENT '模板ID', `status` TINYINT DEFAULT 0 COMMENT '0未使用 1已使用', `expire_time` DATETIME NOT NULL COMMENT '过期时间' ); ``` ### 三、共性难题解决方案 1. **分布式锁应用** ```java // Redisson实现分布式锁 RLock lock = redissonClient.getLock("seckill_lock:" + productId); try { if (lock.tryLock(100, 10, TimeUnit.MILLISECONDS)) { // 执行核心业务逻辑 } } finally { lock.unlock(); } ``` 2. **最终一致性方案** ```text 扣减Redis库存 → 发送MQ消息 → 异步更新数据库 → 异常补偿机制 ``` 3. **防刷策略** - IP(每分钟最多5次请求) - 设备指纹识别 - 行为分析模型(识别异常请求模式) ### 四、性能优化建议 1. **缓存策略** ```java // 使用多级缓存 Caffeine本地缓存 → Redis集群 → 数据库 ``` 2. **数据库优化** ```sql -- 分库分表示例 CREATE TABLE seckill_order_2023q1 ( ... ) ENGINE=InnoDB PARTITION BY HASH(user_id) PARTITIONS 4; ``` 3. **压测指标参考** ``` 单机QPS要求: - 普通请求 ≥ 5000 - 秒杀场景 ≥ 30000(需集群部署) 响应时间 ≤ 100ms ``` 建议实际开发中结合具体业务需求,采用Sentinel进行系统保护,使用Seata处理分布式事务,并建立完善的监控告警体系。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值