个人项目中技术落地的基础入门(2)

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: 个人项目中技术落地的基础入门

秒杀项目


 技术选型


秒杀用到的基础组件,主要有框架、KV 存储、关系型数据库、MQ。
框架主要有 Web 框架和 RPC 框架。
其中,Web 框架主要用于提供 HTTP 接口给浏览器访问,所以 Web 框架的选型在秒杀服务中非常重要。在这里,我推荐Gin,它的性能和易用性都不错,在 GitHub 上的 Star 达到了 44k。对比性能最好的 fasthttp,虽然 fasthttp 在请求延迟低于 10ms 时性能优势明显,但其底层使用的对象池容易让人踩坑,导致其易用性较差,所以没必要过于追求性能而忽略了稳定性。
至于 RPC 框架,我推荐选用 gRPC,因为它的扩展性和性能都非常不错。在秒杀系统中,Redis 中的数据主要是给秒杀接口服务使用,以便将配置从管理后台同步到 Redis 缓存中。
KV 存储方面,秒杀系统中主要是用 Redis 缓存活动配置,用 etcd 存储集群信息。
关系型数据库中,MySQL 技术成熟且稳定可靠,秒杀系统用它存储活动配置数据很合适。主要 原因还是秒杀活动信息和库存数据都缓存在 Redis 中,活动过程中秒杀服务不操作数据库, 使用 MySQL 完全能够满足需求。
MQ 有很多种,其中 Kafka 在业界认可度最高,技术也非常成熟,性能很不错,非常适合用在秒杀系统中。Kafka 支持自动创建队列,秒杀服务各个节点可以用它自动创建属于自己的队列。

 方案设计


背景

  1. 秒杀业务简单,每个秒杀活动的商品是事先定义好的,商品有明确的类型和数量,卖完即止;
  2. 秒杀活动定时上架,消费者可以在活动开始后,通过秒杀入口进行抢购秒杀活动;
  3. 秒杀活动由于商品物美价廉,开始售卖后,会被快速抢购一空;


现象

  1. 秒杀活动持续时间短,访问冲击量大,秒杀系统需要应对这种爆发性的访问模型;
  2. 业务的请求量远远大于售卖量,大部分是陪跑的请求,秒杀系统需要提前规划好处理策略;
  3. 前端访问量巨大,系统对后端数据的访问量也会短时间爆增,对数据存储资源进行良好设计;
  4. 活动期间会给整个业务系统带来超大负荷,需要制定各种策略,避免系统过载而宕机;
  5. 售卖活动商品价格低廉,存在套利空间,各种非法作弊手段层出,需要提前规划预防策略;


秒杀系统设计首先,要尽力将请求拦截在系统上游,层层设阻拦截,过滤掉无效或超量的请求。因为访问量远远大于商品数量,所有的请求打到后端服务的最后一步,其实并没有必要,反而会严重拖慢真正能成交的请求,降低用户体验。秒杀系统专为秒杀活动服务,售卖商品确定,因此可以在设计秒杀商品页面时,将商品信息提前设计为静态信息,将静态的商品信息以及常规的 CSS、JS、宣传图片等静态资源,一起独立存放到 CDN 节点,加速访问,且降低系统访问压力,在访问前端也可以制定种种限制策略,比如活动没开始时,抢购按钮置灰,避免抢先访问,用户抢购一次后,也将按钮置灰,让用户排队等待,避免反复刷新。
其次,要充分利用缓存,提升系统的性能和可用性。用户所有的请求进入秒杀系统前,通过负载均衡策略均匀分发到不同 Web 服务器,避免节点过载。在 Web 服务器中,首先检查用户的访问权限,识别并发刷订单的行为。如果发现售出数量已经达到秒杀数量,则直接返回结束,要将秒杀业务系统和其他业务系统进行功能分拆,尽量将秒杀系统及依赖服务独立分拆部署,避免影响其他核心业务系统。秒杀系统需要构建访问记录缓存,记录访问 IP、用户的访问行为,发现异常访问,提前进行阻断及返回。同时还需要构建用户缓存,并针对历史数据分析,提前缓存僵尸强刷专业户,方便在秒杀期间对其进行策略限制。这些访问记录、用户数据,通过缓存进行存储,可以加速访问,另外,对用户数据还进行缓存预热,避免活动期间大量穿透。

  • 如何解决超卖?


mysql乐观锁+redis预减库存+redis缓存卖完标记 :

  1. 第一是基于数据库乐观锁的方式保证数据并发扣减的强一致性;
  2. 第二是基于数据库的事务实现批量扣减部分失败时的数据回滚。


在扣减指定数量前应先做一次前置数量校验的读请求(参考读写分离 + 全缓存方案)。

纯数据库乐观锁+事务的方式性能比较差,但是如果不计成本和考虑场景的话也完全够用,因为任何没有机器配置的指标,都是耍流氓。如果我采用 Oracle 的数据库、100 多核的刀锋服务器、SSD 的硬盘,即使是纯数据库的扣减方案,也是可以达到单机上万的 TPS 的。


单线程Redis 的 lua 脚本实现批量扣减。
当用户调用扣减接口时,将扣减的 对应数量 + 脚本标示传递至 Redis 即可,所有的扣减判断逻辑均在 Redis 中的 lua 脚本中执行,lua 脚本执行完成之后返还是否成功给客户端。




Redis 中的 lua 脚本执行时,首先会使用 get 命令查询 uuid 进行查重。当防重通过后,会批量获取对应的剩余库存状态并进行判断,如果一个扣减的数量大于剩余数量,则返回错误并提示数量不足。
Redis 的单线程模型,确保不会出现当所有扣减数量在判断均满足后,在实际扣减时却数量不够。同时,单线程保证判断数量的步骤和后续扣减步骤之间,没有其他任何线程出现并发的执行。
当 Redis 扣减成功后,扣减接口会异步的将此次扣减内容保存至数据库。异步保存数据库的目的是防止出现极端情况—— Redis 宕机后数据未持久化到磁盘,此时我们可以使用数据库恢复或者校准数据。
最后,运营后台直连数据库,是运营和商家修改库存的入口。商家在运营后台进货物进行补充。同时,运营后台的实现需要将此数量同步的增加至 Redis,因为当前方案的所有实际扣减都在 Redis 中。

纯缓存方案虽不会导致超卖,但因缓存不具备事务特性,极端情况下会存在缓存里的数据无法回滚,导致出现少卖的情况。且架构中的异步写库,也可能发生失败,导致多扣的数据丢失。


可以借助顺序写的特性,将扣减任务同步插入任务表,发现异常时,将任务表作为undolog进行回滚。
可以解决由于网络不通、调用缓存扣减超时、在扣减到一半时缓存突然宕机(故障 failover)了。针对上述请求,都有相应的异常抛出,根据异常进行数据库回滚即可,最终任务库里的数据都是准的。
更进一步:由于任务库是无状态的,可以进行水平分库,提升整体性能。

  • 如何解决重复下单?


mysql唯一索引+分布式锁

  • 如何防刷?


IP限流 | 验证码 | 单用户 | 单设备 | IMEI | 源IP |均设置规则

  • 热key问题如何解决?

redis集群+本地缓存+限流+key加随机值分布在多个实例中 :

  1. 缓存集群可以单节点进行主从复制和垂直扩容;
  2. 利用应用内的前置缓存,但是需注意需要设置上限;
  3. 延迟不敏感,定时刷新,实时感知用主动刷新;
  4. 和缓存穿透一样,限制逃逸流量,单请求进行数据回源并刷新前置;
  5. 无论如何设计,最后都要写一个兜底逻辑,千万级流量说来就来;


  • 应对高并发的读请求


使用缓存策略将请求挡在上层中的缓存中使用CDN,能静态化的数据尽量做到静态化加入限流(比如对短时间之内来自某一个用户,某一个IP、某个设备的重复请求做丢弃处理)。

资源隔离限流会将对应的资源按照指定的类型进行隔离,比如线程池和信号量。

  1. 计数器限流,例如5秒内技术1000请求,超数后限流,未超数重新计数;
  2. 滑动窗口限流,解决计数器不够精确的问题,把一个窗口拆分多滚动窗口;
  3. 令牌桶限流,类似景区售票,售票的速度是固定的,拿到令牌才能去处理请求;
  4. 漏桶限流,生产者消费者模型,实现了恒定速度处理请求,能够绝对防止突发流量;


流量控制效果从好到差依次是:漏桶限流 > 令牌桶限流 > 滑动窗口限流 > 计数器限流;

其中,只有漏桶算法真正实现了恒定速度处理请求,能够绝对防止突发流量超过下游系统承载能力。
不过,漏桶限流也有个不足,就是需要分配内存资源缓存请求,这会增加内存的使用率。而令牌桶限流算法中的“桶”可以用一个整数表示,资源占用相对较小,这也让它成为最常用的限流算法。正是因为这些特点,漏桶限流和令牌桶限流经常在一些大流量系统中结合使用。

  • 应对高并发的写请求


  1. 削峰:恶意用户拦截对于单用户多次点击、单设备、IMEI、源IP均设置规则;
  2. 采用比较成熟的漏桶算法、令牌桶算法,也可以使用guava开箱即用的限流算法;可以集群限流,但单机限流更加简洁和稳定;
  3. 当前层直接过滤一定比例的请求,最大承载值前需要加上兜底逻辑;
  4. 对于已经无货的产品,本地缓存直接返回;
  5. 单独部署,减少对系统正常服务的影响,方便扩缩容;


对于一段时间内的秒杀活动,需要保证写成功,我们可以使用 消息队列。

  1. 削去秒杀场景下的峰值写流量——流量削峰
  2. 通过异步处理简化秒杀请求中的业务流程——异步处理
  3. 解耦,实现秒杀系统模块之间松耦合——解耦


削去秒杀场景下的峰值写流量

  1. 将秒杀请求暂存于消息队列,业务服务器响应用户“秒杀结果正在处理中......”,释放系统资源去处理其它用户的请求。
  2. 削峰填谷,削平短暂的流量高峰,消息堆积会造成请求延迟处理,但秒杀用户对于短暂延迟有一定容忍度。秒杀商品有 1000 件,处理一次购买请求的时间是 500ms,那么总共就需要 500s 的时间。这时你部署 10 个队列处理程序,那么秒杀请求的处理时间就是 50s,也就是说用户需要等待 50s 才可以看到秒杀的结果,这是可以接受的。这时会并发 10 个请求到达数据库,并不会对数据库造成很大的压力。


通过异步处理简化秒杀请求中的业务流程先处理主要的业务,异步处理次要的业务。

  1. 如主要流程是生成订单、扣减库存;
  2. 次要流程比如购买成功之后会给用户发优惠券,增加用户的积****分。
  3. 此时秒杀只要处理生成订单,扣减库存的耗时,发放优惠券、增加用户积分异步去处理了。


解耦实现秒杀系统模块之间松耦合将秒杀数据同步给数据团队,有两种思路:

  1. 使用 HTTP 或者 RPC 同步调用,即提供一个接口,实时将数据推送给数据服务。系统的耦合度高,如果其中一个服务有问题,可能会导致另一个服务不可用。
  2. 使用消息队列将数据全部发送给消息队列,然后数据服务订阅这个消息队列,接收数据进行处理。


  • 如何保证数据一致性


CacheAside旁路缓存读请求不命中查询数据库,查询完成写入缓存,写请求更新数据库后删除缓存数据。


// 延迟双删,用以保证最终一致性,防止小概率旧数据读请求在第一次删除后更新数据库public void write(String key,Object data){    redis.delKey(key);    db.updateData(data);    Thread.sleep(1000);    redis.delKey(key);}

为防缓存失效这一信息丢失,可用消息队列确保。

  1. 更新数据库数据;
  2. 数据库会将操作信息写入binlog日志当中;
  3. 另起一段非业务代码,程序订阅提取出所需要的数据以及key;
  4. 尝试删除缓存操作,若删除失败,将这些信息发送至消息队列;
  5. 重新从消息队列中获得该数据,重试操作;

订阅binlog程序在mysql中有现成的中间件叫canal,重试机制,主要采用的是消息队列的方式。
终极方案:请求串行化真正靠谱非秒杀的方案:将访问操作串行化

  1. 先删缓存,将更新数据库的写操作放进有序队列中
  2. 从缓存查不到的读操作也进入有序队列

需要解决的问题:

  1. 读请求积压,大量超时,导致数据库的压力:限流、熔断
  2. 如何避免大量请求积压:将队列水平拆分,提高并行度。


  • 可靠性如何保障


由一个或多个sentinel实例组成sentinel集群可以监视一个或多个主服务器和多个从服务器。哨兵模式适合读请求远多于写请求的业务场景,比如在秒杀系统中用来缓存活动信息。如果写请求较多,当集群 Slave 节点数量多了后,Master 节点同步数据的压力会非常大。


image.png

当主服务器进入下线状态时,sentinel可以将该主服务器下的某一从服务器升级为主服务器继续提供服务,从而保证redis的高可用性。

  • 秒杀系统瓶颈-日志


秒杀服务单节点需要处理的请求 QPS 可能达到 10 万以上。一个请求从进入秒杀服务到处理失败或者成功,至少会产生两条日志。也就是说,高峰期间,一个秒杀节点每秒产生的日志可能达到 30 万条以上。


一块性能比较好的固态硬盘,每秒写的IOPS 大概在 3 万左右。也就是说,一个秒杀节点的每秒日志条数是固态硬盘 IOPS 的 10 倍,磁盘都扛不住,更别说通过网络写入到监控系统中。

  1. 每秒日志量远高于磁盘 IOPS,直接写磁盘会影响服务性能和稳定性
  2. 大量日志导致服务频繁分配,频繁释放内存,影响服务性能。
  3. 服务异常退出丢失大量日志的问题


解决方案

  1. Tmpfs,即临时文件系统,它是一种基于内存的文件系统。我们可以将秒杀服务写日志的文件放在临时文件系统中。相比直接写磁盘,在临时文件系统中写日志的性能至少能提升 100 倍,每当日志文件达到 20MB 的时候,就将日志文件转移到磁盘上,并将临时文件系统中的日志文件清空;
  2. 可以参考内存池设计,将给logger分配缓冲区,每一次的新写可以复用Logger对象;
  3. 参考kafka的缓冲池设计,当缓冲区达到大小和间隔时长临界值时,调用Flush函数,减少丢失的风险;


  • 池化技术

image.png

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore     ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库 ECS 实例和一台目标数据库 RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
3月前
|
存储 Android开发 开发者
探索安卓开发之旅:从新手到专家的必经之路
【9月更文挑战第3天】在这篇文章中,我们将踏上一场激动人心的旅程,深入探索安卓开发的广阔天地。无论你是初涉编程世界的新手,还是期望提升技能的开发者,这里都有你需要的知识与技巧。我们将从基础概念讲起,逐步引导你了解安卓应用的核心组件,并分享实用的开发建议。准备好了吗?让我们一起开启这段成长之旅吧!
|
3月前
|
存储 Java Swift
移动应用开发之旅:从新手到专家的演进之路
【9月更文挑战第26天】在这篇文章中,我们将通过一个开发者的视角,探索移动应用开发的旅程。从最初的好奇心驱使下的尝试,到不断学习和挑战自我,最终成为一名能够独立设计和实现复杂移动应用的专家。本文将不包含代码示例,而是聚焦于开发者成长过程中的思考、策略以及心态调整。
53 4
|
2月前
|
存储 小程序 Java
移动应用开发之旅:从新手到专家的演变之路
【10月更文挑战第2天】在数字时代的浪潮中,移动应用已成为我们日常生活不可或缺的一部分。本文将带你踏上一场移动应用开发的奇幻旅程,探索从基础概念到高级技巧的转变过程。我们将通过一个实际案例,逐步构建一个简单的移动应用,并深入探讨移动操作系统的核心原理。无论你是编程新手还是希望提升现有技能,这篇文章都将为你提供宝贵的知识和启示。
43 8
|
4月前
|
存储 开发工具 Android开发
移动应用开发之旅:从零到精通
【8月更文挑战第30天】本文将带领读者踏上移动应用开发的奇妙旅程,从最初的构想到最终的实现。我们将探索不同的移动操作系统,理解它们的特点,并学习如何为这些平台开发应用程序。无论你是初学者还是有经验的开发者,这篇文章都会为你提供宝贵的见解和实用的技巧。让我们开始吧!
44 3
|
4月前
|
Java Android开发 开发者
探索安卓应用开发:从基础到实践
【8月更文挑战第31天】在这篇文章中,我们将一起踏上安卓应用开发的旅程。无论你是初学者还是有一定经验的开发者,这里都有适合你的内容。文章将引导你理解安卓开发的基础知识,然后通过实际的代码示例,带你一步步构建一个简单的应用程序。我们的目标是让读者能够不仅理解安卓开发的理论基础,还能通过动手实践来巩固这些知识。所以,拿起你的设备,让我们一起开始吧!
|
4月前
|
存储 安全 Android开发
移动应用开发之旅:从理念到实践
【8月更文挑战第30天】在数字时代,移动应用已成为我们日常生活不可或缺的一部分。本文旨在引导读者了解移动应用开发的核心概念、流程以及操作系统的相关知识。我们将从基础出发,探讨如何将一个简单的想法转化为现实中可用的应用程序,并介绍一些基本的编程知识,帮助初学者入门。文章不仅提供理论知识,还将通过一个实际代码示例展示开发过程,使读者能够更直观地理解移动应用的创建过程。
|
5月前
|
缓存 NoSQL Java
个人项目中技术落地的基础入门(1)
个人项目中技术落地的基础入门
119 6
|
5月前
|
存储 缓存 物联网
个人项目中技术落地的基础入门(3)
个人项目中技术落地的基础入门
|
7月前
|
设计模式 算法 程序员
源码阅读:拓展技能的必经之路
作为程序员,想必大家经常会遇到这样的情景:我们在日常开发中遇到的问题,有时候甚至会卡住一整天,即耗时又显得我们很“菜”。其实有时候,我们遇到的问题都是可以通过查看对应的官方文档或者程序源码就能快速解决的,我们从迈进程序开发这道门之后,有多少人能够在忙碌的日常去研究程序的源码呢?其实阅读源码非常的重要,阅读源码不仅可以帮助我们熟悉所使用的框架和库,还能快速定位问题并加速项目的进展,而且深入理解源码的原理和代码风格,对于我们的编码、设计和架构能力都有着巨大的提升,尤其是刚入行不久的开发者阅读源码非常重要。那么本文就来分享一下通过阅读源码突破自己技术瓶颈的经历,并分享一些有效的源码阅读方法和建议,个
74 2
源码阅读:拓展技能的必经之路
|
JSON Kubernetes 负载均衡
硬核技能k8s初体验
Kubernetes 是一个软件系统,使你在数以万计的电脑节点上运行软件时就像所有节点是以单个大节点一样, 它将底层基础设施抽象,这样做同时简化了应用开发、部署,以及对开发和运维团队的管理。
硬核技能k8s初体验
下一篇
DataWorks