dcddc

西米大人的博客

0%

如何抗住高并发

拆分

拆分演进

  • 单机部署
    • 系统所有模块部署在一台服务器,包括应用服务、缓存、数据库、文件存储等
  • 各自独立
    • 每个模块独立部署,能显著提高各自的性能
  • 微服务-集群化-分布式
    • 将系统拆分为更小粒度的微服务,每个微服务部署到集群上,由负载均衡服务器负责将流量分发到各应用服务器,提升服务的并发处理能力
    • 消息队列、缓存、数据库、应用服务都集群化部署,系统升级为分布式架构
  • 多集群-异地部署
    • 在分布式架构的基础上,通过在多个地区部署部署多个集群,当用户访问时,通过 DNS 解析并根据用户地理位置就近分派请求到特定集群

拆分维度

可从系统,功能和读写纬度将大的系统进行拆分

  • 系统维度
    • 将一个大系统拆分为多个小系统,比如将电商系统拆分为商品系统,购物车系统,订单系统,优惠券系统等
  • 功能维度
    • 微服务化。对拆分出的一个小系统根据功能再拆分,如优惠券系统,还可拆分为建券系统,领券系统,用券系统等
  • 读写维度
    • 根据读写比例进行拆分,如商品系统,读的需求量比写的多很多,若将读写揉在一起,会相互影响。因此可将商品系统拆分为商品读服务,商品写服务

缓存

缓存策略

cache aside

解决的问题:缓存和 DB 的数据一致性问题

更新数据:

  • 在更新完数据库后删除缓存,而不是更新数据到缓存
    • 为什么不更新而是删除缓存?因为在写多读少场景中,每次写入后需要额外计算缓存数据并更新,但实际查询 qps 很低,产生了很多无用的计算成本。这种设计的本质是 lazy 懒加载思想,只有在用到(读)的时候才计算

读取数据时:

  • 先读取缓存数据,如未命中缓存,则读取数据库,并写入缓存

缓存典型问题

雪崩、击穿

雪崩:大量缓存数据同时失效或 Redis 宕机造成大量请求直接访问数据库
击穿:热点数据过期,大量请求直接访问数据库。可以认为缓存击穿是缓存雪崩的一个子集。

大量缓存数据同时失效的解决方案:

  • 缓存有效期增加随机值,降低大量缓存数据同时过期的概率
  • 互斥锁降低并发。当缓存构建完成后,再释放锁。未能获取互斥锁的请求,要么等待锁释放后重新读取缓存,要么就返回空值或者默认值
    • 同样适合缓存击穿
  • 后台更新缓存。如果缓存值为空,向消息队列发条消息,消费者查询缓存,仍为空则查数据库更新缓存。本质还是降低并发。该方案也适用于缓存预热
    • 同样适合缓存击穿

Redis 宕机的解决方案:

  • 服务熔断。直接返回错误,保证数据库正常。等 Redis 恢复后再继续服务
  • 服务限流。只将少部分请求发送到数据库进行处理。等到 Redis 恢复正常并把缓存预热完后,再解除请求限流的机制

穿透

穿透:当缓存和数据库中都没有数据时,高并发下无法构建缓存数据,大量请求直接访问数据库,即认为缓存被穿透了。一般发生缓存穿透是由于数据被误删除或者被黑客恶意攻击

解决方案:

  • 缓存空值
    • 当多个客户端请求一条不存在的数据时,为防止多次无效的数据库访问,在第一个客户端访问时可以将这个空值也缓存起来,之后其他客户端请求时,会命中缓存中的空值,降低数据库压力
  • 布隆过滤器
    • 写入数据时,更新布隆过滤器。查询数据时,先查询布隆过滤器是否存在,若不存在,直接返回空,若存在,再去查询数据库
    • 布隆过滤器原理:对一个 key 进行 k 个 hash 算法获取 k 个值,在比特数组中将这 k 个值散列后设定为 1,然后查的时候如果特定的这几个位置都为 1,那么布隆过滤器判断该 key 存在
    • 布隆过滤器发生哈希冲突时可能会误判,判定为存在实际不存在。但如果判定不存在,则一定不存在
    • Redis 的 bitmap 只支持 2^32 大小,对应到内存也就是 512MB,误判率万分之一,可以放下 2 亿左右的数据,性能高,空间占用率及小,省去了大量无效的数据库连接
    • 除了用于解决缓存穿透,布隆过滤器另一个用途是用来去重。查数据库来判断 exists 的操作,在高 qps 场景中可以使用布隆过滤器,减轻 db 压力

降级

降级分类

  • 功能降级

    • 如电商平台的推荐功能,可以提升销量和转化,但不是购物的核心流程,在系统压力大时可以降级为默认的内容
  • 服务降级

    • 如在流量高峰时只更新缓存,不更新数据库,把要写入数据库的数据放到消息队列,在流量高峰过后,把消息队列中的数据更新到数据库

限流

限流也是服务降级的一种方式

限流算法请参考我的另一篇文章:系统学习流控技术。