首页  > 科技动态  > 

高并发场景下如何保证系统稳定性

  • 浏览
  • lengsiwei.com
  • 评论6条
  • 导读高并发场景下如何保证系统稳定性,据人民日报2022年11月25日最新关于高并发场景下如何保证系统稳定性的报导,除了本文提到的高并发秒杀场景外,在互联网服务的很多场景下,当系统希望实现高可用、高性能、高扩展的设计目标,都会使用到腾讯云云原生网...

    本篇为开发者朋友们详解高并发场景里限流的解决方案,欢迎大家收看。

    本篇文章将从以下四个方面为大家详解高并发场景限流解决方案。

    限流实现原理及方案选型

    在电商行业里,商家经常会做商品促销的活动,来进行品牌推广或吸引更多客户访问,在这种大促的场景下,通常会有高并发流量进入系统,也就是我们俗称的秒杀场景。在这种场景下,一般会遇到四个典型的特征。

    瞬时请求量大,商品价格低廉,吸引大量用户在活动开始时进行抢购。

    避免超卖,因商品让利较多,商家为控制成本,所以数量有限。

    不能影响其他业务,秒杀活动同时其他业务也需要正常进行。

    在遇见以上特征带来的技术难题时,要如何保证系统正常运行呢?主要有以下几个设计要点。

    秒杀子系统与主站资源隔离;

    系统需要具备限流能力,能够消化掉秒杀开始瞬间的巨大流量;

    系统需要具备快速扩展能力;

    削峰填谷,避免写流量压垮数据库;

    库存增减需要保证数据一致性。

    高并发场景下如何保证系统稳定性_第1张

    为保证活动的顺利开展,业务系统稳定,需要对承载高并发流量的架构进行合理改造。通常来讲,改造后的架构需要具备如下三个特点。

    高性能:能够承载秒杀时较高的读写流量,保证响应时长在可接受的范围内,并兼顾数据一致性。

    高可用:保证系统不宕机,即使发生故障,过载保护也能将故障控制在小范围内,不会影响核心业务运行。

    高扩展:系统具备水平 / 垂直扩展能力,避免单个服务成为性能瓶颈。

    接下来,⑦这里进行读缓存的优化:①位置,客户端对部分变化不灵敏的数据进行本地缓存,减少后端读取压力;②位置,cdn 缓存图片、css、js 等静态文件,就近加速访问,减少后端读取压力;⑦位置,redis 缓存热点数据,分担数据库查询压力,这三部分都是为了实现读优化的性能改造。

    写数据的优化,在⑤位置,常使用 mysql 进行读写分离方式部署,多实例提升读写性能。如单实例遇到性能瓶颈时,也可同时利用水平分库分表的方式提升并发能力;⑥位置,使用消息队列进行异步解耦,以削峰填谷的方式控制请求处理速度。

    最后,在④位置,应用层服务支持纵向或横向扩展,提升应用服务响应能力。微服务之间采用熔断降级的策略,实现容错处理,避免群体故障。

    以上各实践均有助于解决高并发问题,但在实际设计中,架构师需要根据请求 qps 量级,采用方案组合的形式逐步推进。具体落地进度,也要根据改造成本、资源成本、性能提升回报率等因素进行综合评估。如下图所示。

    限流实现原理及方案选型

    接下来我们会重点介绍阶段一和阶段二里的高并发限流能力。

    什么是限流呢?限流是高并发系统中,对于服务提供方的一种常见的保护手段。通过控制 qps 的方式,把后端服务无法承受的部分流量拒绝掉,只将能够稳定处理的流量放入进来,避免后端服务被瞬时的流量高峰冲垮,在南北向设置阈值,保障大后方的稳定性。

    商品秒杀:保护网站不被高并发访问击垮;

    防恶意请求:防止恶意用户发送虚假流量,影响正常业务访问;防止注入、篡改或 ddos 攻击等;

    反爬虫:保护核心数据不被获取。

    返回失败:http 429 too many requests;

    降级处理:自定义静态页面返回;

    请求排队:阻塞请求,一段时间后再继续处理,实现限速。

    在限流应用的开发中,有多种代码逻辑实现,我们最常见的就是限流计数器。

    固定窗口计数器(fixed window)

    方法:通过单位时间设置,如秒、分钟、小时,采用离散计数的方法,统计这个时间段里的流量值,一旦请求大于阈值可承受范围,就会将这个请求拒绝掉。这种实现方案简单,而且内存优化,请求会在自己所属时间单位里计算,不会出现跨时间段的 “阻塞现象”。

    问题:因时间段临界点问题,导致统计结果可能有偏差。以 1s 限定 1000 请求为例,在上一个统计时间的后 0.5s 进入了 1000 个请求,在下一个统计时间的前 0.5s 也进入了 1000 个请求,因为时间的连续性,实际 1s 内请求达到了 2000,那限流是不符合预期的。

    滑动窗口模式计数器(sliding window)

    方法:对固定窗口的一种改进,原理类似 tcp 拥塞控制。将时间单位整合为多个区间,在区间内统计计数,统计区间逐步进行窗口滑动,解决临界点问题。

    问题:内存占用较大,请求及时间戳需保留。

    限流计数器设计缺陷

    一般来讲,限流计数器适用于否决式限流,无法进行排队式限流,对流量 “整形”,实现削峰填谷无能为力。

    如果需要这样的功能,应该如何改进呢?最直观的想法,就是使用队列,将超额的流量进行暂存,延迟进行处理。实现这种能力的算法模型,就是我们熟悉的漏桶算法。

    漏桶算法 漏桶算法(leaky bucket)

    如上图所示,网络流量和水流一样,不断的进入到系统,当我们系统的可承载能力很小的情况下,我们可以将超额的水在一个桶里暂存起来。当系统处理完前面的流量以后,后面的流量就会接着进行处理,这就起到了削峰填谷、流量限速的作用。下面为示意代码。

    漏桶 golang 示意代码 requests := make( chanint, 5)

    fori := 1; i = 5; i++ {

    requests - i

    close(requests)

    limiter := time.tick( 200* time.millisecond)

    forreq := rangerequests {

    fmt.println( "request", req, time.now)

    能力:以固定的速率控制请求的访问速度;支持阻塞式限流;采用 fifo 队列,实现简单。

    问题:当短时间内有大量请求时,速率无法动态调整。即使服务器负载不高,新请求也得在队列中等待一段时间才能被响应,无法在固定时间内承诺响应,容易出现请求 “饥饿” 现象。

    那这种问题又该如何解决呢?可以用到一个叫做令牌桶的算法。

    令牌桶算法(token bucket)

    令牌桶算法和漏桶算法的最大区别,在于这个桶里装的不再是请求,而是 “通关” 的令牌。每一个请求过来以后,都在队列里排队。当我们的监控系统发现大量请求到来,可以人为的增加通关令牌,快速的消耗掉这一大波的请求,使新进来的请求不会等待太长时间,从而造成饥饿现象。可以看一下下面的示意代码。

    令牌桶 golang 示意代码 limiter := make( chantime.time, 3)

    fori := 0; i3; i++ {

    limiter - time.now

    fort := rangetime.tick( 200* time.millisecond) {

    limiter - t

    } // token depositor, dynamic rate

    requests := make( chanint, 5)

    fori := 1; i = 5; i++ {

    requests - i

    close(requests)

    forreq := rangerequests {

    fmt.println( "request", req, time.now)

    单机限流 vs 分布式限流 单机限流

    概念:针对单个实例级别的限流,流量限额只针对当前被调实例生效。每一个实例都会有一个自己的限流值,当请求到达这个实例后,会进行计数,一旦超过现在可以接受的阈值后,就会直接拒绝请求。

    问题:当我们做一个生产环境部署的时候,肯定不会只有一个网关,可能会有五个、十个,一个集群的网关。那么这个集群里面的每一个实例,它是没有全局感知的,每个实例都只能看见自己的限流值,无法达到共识,这种情况下很可能限流并不符合预期。

    概念:针对服务下所有实例级别的限流,多个服务实例共享同一个全局流量限额。

    方法:将所有服务的统计结果,存入集中式的中间件中,常用缓存实现如 redis,etcd,以实现集群实例共享流量配额;通过分布式锁、信号量或原子操作等控制方法,解决多实例并发读写问题。

    衍生问题:获取配额会增加网络开销,处理能力会有所降低,如何解决?集中式限流中间件不可用时,流量如何应对?

    分布式限流实现思路

    我们先来看看,实现一个简单的分布式限流,步骤会有哪些。

    发令牌的进程,和各个限流进程,通过统一中间件(如 redis、etcd 等)进行交互;

    发令牌进程在中间件上设置限流进程个节点;每个节点里,按阈值(如分钟)设置该节点的 “令牌” 数量,如 key = /token/1 或者 /token/2,value = ratelimit = 10;

    限流进程将节点 value,作为令牌使用,获取以后做原子性减一操作;

    一旦进程当前节点 value 不够,按照环形访问方式,使用下一个限流进程的令牌。

    问题:刚才上面提到的两个衍生问题,在这样的实现下,如何解决呢?

    各限流器对中间件,可以进行 batch 更新,通过牺牲部分准确率,换取访问压力减少,提高流控性能。

    当中间件不可用时,当前实例可配置拒绝所有请求,或让流量正常通过。也可配置选用本地配额进行短路处理。

    接下来我们看一下,在云原生网关上如何配置限流。

    演示视频:/x/page/b3364drmdji.html

    减少自建网关的运维成本;

    降低服务器资源成本;

    100% 兼容开源网关 kong 的 api。

    支持秒、分钟、小时、天、月、年等多时间维度单独或组合配置限流值;

    支持自定义返回的能力,设置返回状态码、返回内容和返回头;

    支持按 consumer、credential、ip、service、header、path 等多个维度进行限流。

    local:计数值保存在 nginx 本地内存中,性能最高,不适合集群部署模式(单节点部署时推荐);

    cluster:计数值保存在 kong 的数据库 postgresql 中,性能较差,不适合高并发场景;

    redis:计数值保存于外部 redis,适合集群部署场景,性能较高,需要额外的 redis 组件(集群部署时推荐)。

    服务治理中心限流

    对应的,在服务治理中心 — 北极星上配置限流。

    演示视频:/x/page/t3364mo3o56.html

    支持服务 / 接口 / 标签的限流能力;

    支持快速失败及匀速排队两种处理方式;

    支持秒、分钟、小时、天等时间微服务间的限流能力。

    故障熔断,基于服务调用的失败率和错误数等信息对故障资源进行剔除。

    访问限流,支持服务 / 接口 / 标签多级限流能力,提前限制超过阈值的流量。

    云书城沙盒环境演示

    我们模拟了一个在线的云书城。它具有多个微服务模块,比如收藏功能、购买功能,用户管理功能,订单查询功能等。在秒杀场景下,系统增加了一个秒杀子系统,专门为大促活动时,商品秒杀使用,先来看下架构图。

    从最北向进来的流量会首先经过云原生网关,到达商城主页。接下来流量进入业务网关层,它来做后端服务间 grpc 的调用管理,最后是各微服务功能单元,通过业务逻辑进行分割。

    接下来通过沙盒环境,演示云书城在大促期间,如何应对高并发流量的访问。

    演示视频:/x/page/b3364so1qnz.html

    读写优化及扩缩容方案 横向扩缩容 - tke/eks

    演示视频中有使用到扩容的功能,这里我们简单讲解一下服务扩缩容。

    熟悉 k8s 的同学都知道,采用 scale 命令可以将服务的副本数提升,但是在限流的场景下,或者在大促时,这样手动操作肯定不现实。一个更好的方案是采用腾讯云上 tke/eks 的 hpc 功能,它具有定时扩缩容的能力,针对系统负载评估值,提前做好准备。配合 hpa 功能,针对 qps 或系统负载进行动态的调整,将服务的承载能力,维持在一个合理的水位上。hpc 与 hpa 相配合,基本可以做到流量高峰时自动扩容,避免系统崩溃;流量低谷时自动缩容,节约成本。

    hpc 组件的作用:定时执行 pod 扩容或缩容的动作。

    选择 hpc 的原因:秒杀场景具有流量瞬间爆发式增长的特征,hpa 组件扩容需要 1 分钟左右,这段时间可能导致服务崩溃,hpc 组件可以根据秒杀开始时间设定提前扩容。hpa 组件可以作为补充,形成双重保证。

    节点池配置:无需事先购买节点,因资源不足而无法调度实例时,实现自动扩缩容,节约成本。

    异步解耦 - tdmq pulsar

    在秒杀场景里,经常会对写请求和读请求进行优化。

    写请求的优化,我们一般会想到,通过异步的方式来解耦数据层的访问,而不是直接将请求打到数据层上,因为数据层可能是系统里最薄弱的一个环节。我们将一些订单的处理或者用户购买信息的处理,放在消息队列里,这种设计逻辑和网关限流排队是一致的,目标都是以可控的方式,将系统外部的请求,维持在可承受范围内。

    腾讯云有一款产品叫做 tdmq pulsar ,它以存算分离方式实现,对于快速扩容更有优势,产品保证了上方的计算层处于一个无状态的部署模式,对于自身的扩容速度是非常快的。另外 tdmq pulsar 数据层的设计机制,使得它和 kafka 的最大区别,在于不限制 topic 分区数,这样我们可以启动更多的 consumer 来提升消费吞吐量。在秒杀场景中处理订单的消费,是会非常有帮助的。

    采用 bookkeeper 协议实现数据强一致性;

    存算分离的架构带来灵活的横向扩展能力 ;

    高性能低延迟,单集群 qps10 万;

    不同于 kafka,tdmq pulsar 的消费者数量不受限于 topic 的分区数,可启动更多的消费者提升处理能力;

    支持全局 / 局部顺序消息、定时消息、延时消息,满足各种业务需求。

    热数据缓存 - tdsql redis

    上面说了写请求的优化,接下来再说一下读请求的优化。

    读请求前面提到,可以在客户层进行缓存,也可以在 cdn 层进行缓存,但更重要的是需要在数据库前也进行一次缓存,使得读请求不会直接到达系统最薄弱的环节 —— 数据库,形成一个 “冲突缓存带”。这里我们常将一些热点数据放在 redis 里来供大促期间使用。

    超高性能;标准版 10 万 + qps;集群版支持千万级 qps。

    自动容灾切换;双机热备架构;主机故障后,访问秒级切换到备机,无需用户干预。

    在线扩容;控制台一键操作扩容;扩容过程中无需停服。

    除了本文提到的高并发秒杀场景外,在互联网服务的很多场景下,当系统希望实现高可用、高性能、高扩展的设计目标,都会使用到腾讯云云原生网关产品所提供的能力,比如灰度发布、全链路染色、多环境路由和多活容灾等架构。

    云原生网关(cloud-native gateway)是腾讯云基于开源网关 kong 推出的一款高性能高可用的网关产品,100% 完美兼容开源。同时提供 tke/eks 集群直通,nacos/consul/polaris/eureka 注册中心对接,实例弹性扩缩容等能力,并有特色能力插件增强,显著减少用户自建网关带来的开发及运维成本。另外,多可用区部署的模式,也保证了业务连续性,避免单可用区故障带来的服务中断。

    无论在微服务架构下还是传统 web 架构下,云原生网关都能以流量网关、安全网关和服务网关所需要的各种能力特性,为业务云上部署提供助力。

    企业投身开源,挂羊头卖狗肉行不通

    这里有最新开源资讯、软件更新、技术干货等内容

    广东德尚郭彬
    除了本文提到的高并发秒杀场景外,在互联网服务的很多场景下,当系统希望实现高可用、高性能、高扩展的设计目标,都会使用到腾讯云云原生网关产品所提供的能力,比如灰度发布、全链路染色、多环境路由和多活容灾等架构
    回答于 2022-11-25 01:37:21
    47
    ゞ淚戀ヾ杺傷
    无论在微服务架构下还是传统 web 架构下,云原生网关都能以流量网关、安全网关和服务网关所需要的各种能力特性,为业务云上部署提供助力
    回答于 2022-11-25 00:47:21
    47
    取个那么长的名字做啥子嘛
    我们将一些订单的处理或者用户购买信息的处理,放在消息队列里,这种设计逻辑和网关限流排队是一致的,目标都是以可控的方式,将系统外部的请求,维持在可承受范围内
    回答于 2022-11-24 23:56:44
    98
    ﹡.人生若只如初見ゞ
    参考上图,首先会在③位置,接入层网关对南北流量的超额部分限流,避免后端系统过载,保证业务正常运行
    回答于 2022-11-24 22:58:23
    20
    记忆已成回忆
    同时提供限流和熔断功能,且可对服务间流量进行细粒度治理,如就近访问等
    回答于 2022-11-24 22:48:33
    68
    超帅的人
    在电商行业里,商家经常会做商品促销的活动,来进行品牌推广或吸引更多客户访问,在这种大促的场景下,通常会有高并发流量进入系统,也就是我们俗称的秒杀场景
    回答于 2022-11-24 22:37:25
    67