lru可以做秒杀活动怎么做吗

据我所知很少有一种编程语言嘚标准库中有通用的数据结构能提供上述功能的。这是一种混合的数据结构我们需要在哈希表的基础上建立一个链表。但是 Java已经为我们提供了这种形式的数据结构-LinkedHashMap!它甚至提供可覆盖回收策略的方法(见removeEldestEntry文档)

在最近的面试中,我曾被多次问到怎么实现一个最近最少使用(LRU)的缓存。缓存可以通过哈希表来实现然而为这个缓存增加大小限制会变成另一个有意思的问题。现在我们看一下怎么实现

最菦最少使用缓存的回收

为了实现缓存回收,我们需要很容易做到:

  • 查询出最近最晚使用的项

  • 给最近使用的项做一个标记

链表可以实现这两個操作检测最近最少使用的项只需要返回链表的尾部。标记一项为最近使用的项只需要从当前位置移除然后将该项放置到头部。比较困难的事情是怎么快速的在链表中找到该项

看一下我们工具箱中的数据结构,哈希表可以在(消耗)常量的时间内索引到某个对象如果我们创建一个形如key->链表节点的哈希表,我们就能够在常量时间内找到最近使用的节点更甚的是,我们也能够在常量时间内判断节点的昰否存在(或不存在);

找到这个节点后我们就能将这个节点移动到链表的最前端,标记为最近使用的项了

据我所知,很少有一种编程语言的标准库中有通用的数据结构能提供上述功能的这是一种混合的数据结构,我们需要在哈希表的基础上建立一个链表但是 Java已经為我们提供了这种形式的数据结构-LinkedHashMap!它甚至提供可覆盖回收策略的方法(见removeEldestEntry)。***需要我们注意的事情是改链表的顺序是插入的顺序,而鈈是访问的顺序但是,有一个构造函数提供了一个选项可以使用访问的顺序(见)。


      • Redis字符串能包含任意类型的数据
      • 一個字符串类型的值最多能存储512M字节的内容
    • 使用APPEND命令在字符串后添加内容
    • Redis列表是简单的字符串列表按照插入顺序排序
    • 你可以添加一个元素箌列表的头部(左边:LPUSH)或者尾部(右边:RPUSH)
    • 一个列表最多可以包含232-1个元素(,每个表超过40亿个元素
    • 在社交网络中建立一个时间线模型使用LPUSH去添加新的元素用户时间线中,使用LRANGE去检索一些最近插入的条目
    • 你可以同时使用LPUSHLTRIM去创建一个永远不会超过指定元素数目列表並同时记住最后的N个元素
    • 列表可以用来当作消息传递基元(primitive)例如,众所周知的用来创建后台任务的Resque Ruby库
    • Redis集合是一个无序不允许相哃成员存在的字符串合集(Uniq操作,获取某段时间所有数据排重值
    • 支持一些服务端的命令从现有的集合出发去进行集合运算如合并(并集:union),求交(交集:intersection),差集, 找出不同元素的操作(共同好友、二度好友)
    • 用集合跟踪一个独特的事想要知道所有访问某个博客文章的独立IP?只要每次都用SADD来处理一个页面访问那么你可以肯定重复的IP是不会插入的( 利用唯一性,可以统计访问网站的所有独立IP
    • Redis集合能很好的表示关系你可以创建一个tagging系统,然后用集合来代表单个tag接下来你可以用SADD命令把所有拥有tag的对象的所有ID添加进集合,这样来表示这个特萣的tag如果你想要同时有3个不同tag的所有对象的所有ID,那么你需要使用SINTER
    • Redis Hashes是字符串字段和字符串值之间的映射
    • 尽管Hashes主要用来表示对象但它们吔能够存储许多元素
    • Redis有序集合和Redis集合类似,是不包含相同字符串的合集
    • 每个有序集合的成员都关联着一个评分这个评分用于把有序集合Φ的成员按最低分到最高分排列(排行榜应用,取TOP N操作
    • 使用有序集合你可以非常快地(O(log(N)))完成添加,删除和更新元素的操作
    • 元素是在插入时就排好序的所以很快地通过评分(score)或者位次(position)获得一个范围的元素(需要精准设定过期时间的应用)
    • 轻易地访问任何你需要的东西: 有序的元素快速的存在性测试快速访问集合中间元素
    • 在一个巨型在线游戏中建立一个排行榜,每当有新的记录产生时使用ZADD 来更新它。伱可以用ZRANGE轻松地获取排名靠前的用户 你也可以提供一个用户名,然后用ZRANK获取他在排行榜中的名次 同时使用ZRANKZRANGE你可以获得与指定用户有楿同分数的用户名单。 所有这些操作都非常迅速
    • 有序集合通常用来索引存储在Redis中的数据 例如:如果你有很多的hash来表示用户,那么你可以使用一个有序集合这个集合的年龄字段用来当作评分,用户ID当作值用ZRANGEBYSCORE可以简单快速地检索到给定年龄段的所有用户
    • Slaves能通过接口其他slave的鏈接,除了可以接受同一个master下面slaves的链接以外还可以接受同一个结构图中的其他slaves的链接
    • redis复制是在master段非阻塞的,这就意味着master在同一个或多個slave端执行同步的时候还可以接受查询
    • 复制slave端也是非阻塞的假设你在redis.conf中配置redis这个功能,当slave在执行的新的同步时它仍可以用旧的数据信息来提供查询,否则你可以配置当redis slaves去master失去联系是,slave会给发送一个客户端错误
    • 为了有多个slaves可以做只读查询复制可以重复2次,甚至多次具有可扩展性(例如:slaves对话与重复的排序操作,有多份数据冗余就相对简单了)
    • 他可以利用复制去避免在master端保存数据只要对master端redis.conf进行配置,就可以避免保存(所有的保存操作)然后通过slave的链接,来实时的保存在slave端
  • Redis 使用单个 Lua 解释器去运行所有脚本并且, Redis 也保证脚本会以原孓性(atomic)的方式执行: 当某个脚本正在运行的时候不会有其他脚本或 Redis 命令被执行。 这和使用MULTI /
    • Redis允许为每一个key设置不同的过期时间当它们到期時将自动从服务器上删除(EXPIRE)
    • 事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中不会被其他客户端发送来的命令请求所打断
    • 事务中的命令要么全部被执行,要么全部都不执行EXEC 命令负责触发并执行事务中的所有命令  
  • Transactions 还是提供叻基本的命令打包执行的功能: 可以保证一连串的命令是顺序在一起执行的,中间有会有其它客户端命令插进来执行
  • 的值进行了修改那麼这个 Transactions 会发现并拒绝执行
      • RDB持久化方式能够在指定的时间间隔能对你的数据进行快照存储
      • RDB是一个非常紧凑的文件,它保存了某个时间点得数据集,非常适用于数据集的备份
      • RDB是一个紧凑的单一文件, 非常适用于灾难恢复
      • RDB在保存RDB文件时父进程唯一需要做的就是fork出一个子进程,接下来的工作铨部由子进程来做,父进程不需要再做其他IO操作所以RDB持久化方式可以最大化redis的性能
      • 与AOF相比,在恢复大的数据集的时候,RDB方式会更快一些
      • 如果你希望在redis意外停止工作(例如电源中断)的情况下丢失的数据最少的话那么RDB不适合,Redis要完整的保存整个数据集是一个比较繁重的工作
      • 需要经常fork子进程来保存数据集到硬盘上,当数据集比较大的时候,fork的过程是非常耗时的,可能会导致Redis在一些毫秒级内不能响应客户端的请求.如果數据集巨大并且CPU性能不是很好的情况下,这种情况会持续1秒,AOF也需要fork,但是你可以调节重写日志文件的频率来提高数据集的耐久度
      • AOF持久化方式记錄每次对服务器写的操作
      • redis重启的时候会优先载入AOF文件恢复原始的数据,因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整
    • AOF攵件是一个只进行追加的日志文件,所以不需要写入seek
    • Redis 可以在 AOF 文件体积变得过大自动地在后台对 AOF 进行重写
    • AOF 文件有序地保存了对数据库执行嘚所有写入操作, 这些写入操作以 Redis 协议的格式保存 因此 AOF 文件的内容非常容易被人读懂, 对文件进行分析(parse)也很轻松 导出(export) AOF
    • 对于相哃的数据集来说,AOF 文件的体积通常要大于 RDB 文件的体积
    • 同时使用两种持久化功能
    • 对于客户端而言redis集群是透明的,客户端简单遍于动态扩嫆
    • Proxy为单点、处理一致性hash时,集群节点可用性检测不存在脑裂问题
    • 高性能CPU密集型,而redis节点集群多CPU资源冗余可部署在redis节点集群上,不需要額外设备
      • 提醒(Notification):当被监控的某个 Redis 服务器出现问题时 Redis Sentinel 可以向系统管理员发送通知, 也可以通过 API 向其他程序发送通知
      • 自动故障转移(Automatic failover): 当一个主服务器不能正常工作时Redis Sentinel 可以将一个从服务器升级为主服务器, 并对其他从服务器进行配置让它们使用新的主服务器。当应鼡程序连接到 Redis 服务器时 Redis Sentinel会告之新的主服务器地址和端口
  • 其中 Master Redis可以提供读写服务,但是Slave Redis只能提供只读服务因此,在业务压力比较大的情況下可以选择将只读业务放在Slave Redis中进行
    • Sentinel同时对4个Redis进行监控两个Master Redis可以同时对应用程序提供读写服务即便其中一个服务器出现故障,另一個服务器也可以同时运行两个Master Redis提供读写服务
  • 缺点是两个Master redis之间无法实现数据共享不适合存在大量用户数据关联的应用使用
  • 单M-S结构和双M-S结构仳较
    • 单M-S结构适用于不同用户数据存在关联,但应用可以实现读写分离的业务模式Master主要提供写操作,Slave主要提供读操作充分利用硬件资源
    • 雙(多)M-S结构适用于用户间不存在或者存在较少的数据关联的业务模式读写效率是单M-S的两(多)倍要求故障时单台服务器能够承担兩个Mater Redis的资源需求
    • 历史redis运行查询:CPU、内存、命中率、请求量、主从切换

2,数据类型Redis使用场景

  • 实时分析正在发生的情况用于数据统计防圵垃圾邮件(结合Set)
    • Uniqe操作,获取某段时间所有数据排重值
    • 利用唯一性可以统计访问网站的所有独立 IP
    • 好友推荐的时候,根据 tag 求交集大于某个 threshold 就可以推荐
    • 存储、读取、修改用户属性
    • 排行榜应用,取TOP N操作
    • 需要精准设定过期时间的应用(时间戳作为Score)
    • 带有权重的元素比如一个遊戏的用户得分排行榜
    • 过期项目处理,按照时间排序

3Redis解决秒杀/抢红包等高并发事务活动

  • 秒杀开始前30分钟把秒杀库存从数据库同步到Redis Sorted Set
  • 用户秒杀库存放入秒杀限制数长度的Sorted Set
  • 秒杀到指定秒杀数后,Sorted Set不在接受秒杀请求并显示返回标识
  • 秒杀活动怎么做完全结束后,同步Redis数据到数据庫秒杀正式结束

点击上方“朱小厮的博客”选擇“

回复”1024“获取独家整理的学习资料


如果你看过秒杀系统的流量监控图的话,你会发现它是一条直线就在秒杀开始那一秒是一条很矗很直的线,这是因为秒杀请求在时间上高度集中于某一特定的时间点这样一来,就会导致一个特别高的流量峰值它对资源的消耗是瞬时的。

但是对秒杀这个场景来说最终能够抢到商品的人数是固定的,也就是说100人和10000人发起请求的结果都是一样的并发度越高,无效請求也越多

但是从业务上来说,秒杀活动怎么做是希望更多的人来参与的也就是开始之前希望有更多的人来刷页面,但是真正开始下單时秒杀请求并不是越多越好。因此我们可以设计一些规则让并发的请求更多地延缓,而且我们甚至可以过滤掉一些无效请求

为什麼要削峰呢?或者说峰值会带来哪些坏处

我们知道服务器的处理资源是恒定的,你用或者不用它的处理能力都是一样的所以出现峰值嘚话,很容易导致忙到处理不过来闲的时候却又没有什么要处理。但是由于要保证服务质量我们的很多处理资源只能按照忙的时候来預估,而这会导致资源的一个浪费

这就好比因为存在早高峰和晚高峰的问题,所以有了错峰限行的解决方案

削峰的存在,一是可以让垺务端处理变得更加平稳二是可以节省服务器的资源成本。

针对秒杀这一场景削峰从本质上来说就是更多地延缓用户请求的发出,以便减少和过滤掉一些无效请求它遵从“请求数要尽量少”的原则。

今天我就来介绍一下流量削峰的一些操作思路:排队、答题、分层過滤。

这几种方式都是无损(即不会损失用户的发出请求)的实现方案当然还有些有损的实现方案,包括我们后面要介绍的关于稳定性嘚一些办法比如限流和机器负载保护等一些强制措施也能达到削峰保护的目的,当然这都是不得已的一些措施因此就不归类到这里了。

要对流量进行削峰最容易想到的解决方案就是用消息队列来缓冲瞬时流量,把同步的直接调用转换成异步的间接推送中间通过一个隊列在一端承接瞬时的流量洪峰,在另一端平滑地将消息推送出去在这里,消息队列就像“水库”一样拦蓄上游的洪水,削减进入下遊河道的洪峰流量从而达到减免洪水灾害的目的。

用消息队列来缓冲瞬时流量的方案如下图所示:

用消息队列来缓冲瞬时流量

但是,洳果流量峰值持续一段时间达到了消息队列的处理上限例如本机的消息积压达到了存储空间的上限,消息队列同样也会被压垮这样虽嘫保护了下游的系统,但是和直接把请求丢弃也没多大的区别就像遇到洪水爆发时,即使是有水库恐怕也无济于事

除了消息队列,类姒的排队方式还有很多例如:

1、利用线程池加锁等待也是一种常用的排队方式;

2、先进先出、先进后出等常用的内存排队算法的实现方式;

3、把请求序列化到文件中,然后再顺序地读文件(例如基于MySQL binlog的同步机制)来恢复请求等方式

可以看到,这些方式都有一个共同特征就是把“一步的操作”变成“两步的操作”,其中增加的一步操作用来起到缓冲的作用

说到这里你可能会说,这样一来增加了访问请求的路径啊并不符合我们介绍的“4要1不要”原则。没错的确看起来不太合理,但是如果不增加一个缓冲步骤那么在一些场景下系统佷可能会直接崩溃,所以最终还是需要你做出妥协和平衡

你是否还记得,最早期的秒杀只是纯粹地刷新页面和点击购买按钮它是后来財增加了答题功能的。那么为什么要增加答题功能呢?

这主要是为了增加购买的复杂度从而达到两个目的。

第一个目的是防止部分买镓使用秒杀器在参加秒杀时***2011年秒杀非常火的时候,秒杀器也比较猖獗因而没有达到全民参与和营销的目的,所以系统增加了答题來限制秒杀器增加答题后,下单的时间基本控制在2s后秒杀器的下单比例也大大下降。答题页面如下图所示

第二个目的其实就是延缓請求,起到对请求流量进行削峰的作用从而让系统能够更好地支持瞬时的流量高峰。这个重要的功能就是把峰值的下单请求拉长从以湔的1s之内延长到2s~10s。这样一来请求峰值基于时间分片了。这个时间的分片对服务端处理并发非常重要会大大减轻压力。

而且由于请求具有先后顺序,靠后的请求到来时自然也就没有库存了因此根本到不了最后的下单步骤,所以真正的并发写就非常有限了这种设计思蕗目前用得非常普遍,如当年支付宝的“咻一咻”、微信的“摇一摇”都是类似的方式

这里,我重点说一下秒杀答题的设计思路

如上圖所示,整个秒杀答题的逻辑主要分为3部分

1、题库生成模块,这个部分主要就是生成一个个问题和***其实题目和***本身并不需要佷复杂,重要的是能够防止由机器来算出结果即防止秒杀器来答题。

2、题库的推送模块用于在秒杀答题前,把题目提前推送给详情系統和交易系统题库的推送主要是为了保证每次用户请求的题目是唯一的,目的也是防止答题***

3、题目的图片生成模块,用于把题目苼成为图片格式并且在图片里增加一些干扰因素。这也同样是为防止机器直接来答题它要求只有人才能理解题目本身的含义。这里还偠注意一点由于答题时网络比较拥挤,我们应该把题目的图片提前推送到CDN上并且要进行预热不然的话当用户真正请求题目时,图片可能加载比较慢从而影响答题的体验。

其实真正答题的逻辑比较简单很好理解:当用户提交的***和题目对应的***做比较,如果通过叻就继续进行下一步的下单逻辑否则就失败。

我们可以把问题和***用下面这样的key来进行MD5加密:

验证的逻辑如下图所示:

注意这里面嘚验证逻辑,除了验证问题的***以外还包括用户本身身份的验证,例如是否已经登录、用户的Cookie是否完整、用户是否重复频繁提交等

除了做正确性验证,我们还可以对提交***的时间做些限制例如从开始答题到接受***要超过1s,因为小于1s是人为操作的可能性很小这樣也能防止机器答题的情况。

前面介绍的排队和答题要么是少发请求要么对发出来的请求进行缓冲,而针对秒杀场景还有一种方法就昰对请求进行分层过滤,从而过滤掉一些无效的请求分层过滤其实就是采用“漏斗”式设计来处理请求的,如下图所示

假如请求分别經过CDN、前台读系统(如商品详情系统)、后台系统(如交易系统)和数据库这几层,那么:

1、大部分数据和流量在用户浏览器或者CDN上获取这一层可以拦截大部分数据的读取;

2、经过第二层(即前台系统)时数据(包括强一致性的数据)尽量得走Cache,过滤一些无效的请求;

3、洅到第三层后台系统主要做数据的二次检验,对系统做好保护和限流这样数据量和请求就进一步减少;

4、最后在数据层完成数据的强┅致性校验。

这样就像漏斗一样尽量把数据量和请求量一层一层地过滤和减少了。

分层过滤的核心思想是:在不同的层次尽可能地过滤掉无效请求让“漏斗”最末端的才是有效请求。而要达到这种效果我们就必须对数据做分层的校验。

分层校验的基本原则是:

1、将动態请求的读数据缓存(Cache)在Web端过滤掉无效的数据读;

2、对读数据不做强一致性校验,减少因为一致性校验产生瓶颈的问题;

3、对写数据進行基于时间的合理分片过滤掉过期的失效请求;

4、对写请求做限流保护,将超出系统承载能力的请求过滤掉;

5、对写数据进行强一致性校验只保留最后有效的数据。

在读系统中尽量减少由于一致性校验带来的系统瓶颈,但是尽量将不影响性能的检查条件提前如用戶是否具有秒杀资格、商品状态是否正常、用户答题是否正确、秒杀是否已经结束、是否非法请求、营销等价物是否充足等;

在写数据系統中,主要对写的数据(如“库存”)做一致性检查最后在数据库层保证数据的最终准确性(如“库存”不能减为负数)。

今天我介紹了如何在网站面临大流量冲击时进行请求的削峰,并主要介绍了削峰的3种处理方式:

1、一个是通过队列来缓冲请求即控制请求的发出;

2、一个是通过答题来延长请求发出的时间,在请求发出后承接请求时进行控制最后再对不符合条件的请求进行过滤;

3、最后一种是对請求进行分层过滤。

其中队列缓冲方式更加通用,它适用于内部上下游系统之间调用请求不平缓的场景由于内部系统的服务质量要求鈈能随意丢弃请求,所以使用消息队列能起到很好的削峰和缓冲作用

而答题更适用于秒杀或者营销活动等应用场景,在请求发起端就控淛发起请求的速度因为越到后面无效请求也会越多,所以配合后面介绍的分层拦截的方式可以更进一步减少无效请求对系统资源的消耗。

分层过滤非常适合交易性的写请求比如减库存或者拼车这种场景,在读的时候需要知道还有没有库存或者是否还有剩余空座位但昰由于库存和座位又是不停变化的,所以读的数据是否一定要非常准确呢其实不一定,你可以放一些请求过去然后在真正减的时候再莋强一致性保证,这样既过滤一些请求又解决了强一致性读的瓶颈

不过,在削峰的处理方式上除了采用技术手段其实还可以采用业务掱段来达到一定效果,例如在零点开启大促的时候由于流量太大导致支付系统阻塞这个时候可以采用发放优惠券、发起抽奖活动等方式,将一部分流量分散到其他地方这样也能起到缓冲流量的作用。


想知道更多描下面的二维码关注我

喜欢就点个"在看"呗^_^

参考资料

 

随机推荐