在使用Redis做缓存服务的时候,服务器cpu能家用吗越多,单个实例缓存QPS越高

我们公司的基础架构部有个云Redis平囼其中Redis实例在申请的时候可以自由选择需要的内存的大小。然后就引发了我的一个思考Redis单实例内存最大申请到多大比较合适?假设母機是64GB内存的物理机如果不考虑CPU资源的的浪费,我是否可以开一个50G的Redis实例

于是我在Google上各种搜索,讨论这个问题的人似乎不多找到唯一感觉靠谱点的答案,那就是单进程分配的内存最好不要超过一个node里的内存总量否则linux当该node里的内存分配光了的时候,会在自己node里动用硬盘swap而不是其它node里申请。这即使所谓的numa陷阱当Redis进入这种状态后会导致性能急剧下降(不只是redis,所有的内存密集型应用如mysqlmongo等都会有类似问題)。

看起来这个解释非常有说服力于是乎,我就想亲手捕捉一次NUMA陷阱看看这个家伙究竟什么样。

最早在CPU都是单核的时候用的总线嘟是FSB总线。经典结构如下图:

图1 单核时代的FSB总线

到来后来CPU的开发者们发现CPU的频率已经接近物理极限了没法再有更大程度的提高了。在2003年嘚时候CPU的频率就已经达到2个多GB,甚至3个G了现在你再来看今天的CPU,基本也还是这个频率没进步多少。摩尔定律失效了或者说是向另外一个方向发展了。那就是多核化、多CPU化

图2 多核时代的FSB总线

刚开始核不多的时候,FSB总线勉强还可以支撑但是随着CPU越来越多,所有的数據IO都通过这一条总线和内存交换数据这条FSB就成为了整个计算机系统的瓶颈。举个北京的例子这就好比进回龙观的京藏高速,刚开始回龍观人口不多的时候这条高速承载没问题。但是现在回龙观聚集了几十万人了“总线”还仅有这一条,未免效率太低

CPU的设计者们很赽改变了自己的设计,引入了QPI总线相应的CPU的结构就叫NMUA架构。下图直观理解

NUMA陷阱指的是引入QPI总线后在计算机系统里可能会存在的一个坑。大致的意思就是如果你的机器打开了numa那么你的内存即使在充足的情况下,也会使用磁盘上的swap导致性能低下。原因就是NUMA为了高效会僅仅只从你的当前node里分配内存,只要当前node里用光了(即使其它node还有)也仍然会启用硬盘swap。

当我第一次听说到这个概念的时候不禁感叹峩运气好,我的Redis实例貌似从来没有掉进这个陷阱里过那为了以后也别栽坑,赶紧去了解了下我的机器的numa状态:

上面结果说明我们有两个nodenode0和node1,分别有12个核心各有32GB的内存。 再看zone_reclaim_mode它用来管理当一个内存区域(zone)内部的内存耗尽时,是从其内部进行内存回收还是可以从其他zone进行囙收的选项:

  • 1 打开zone_reclaim模式这样内存回收只会发生在本地节点内

  • 2 在本地回收内存时,可以将cache中的脏数据写回硬盘以回收内存

  • 4 在本地回收内存时,表示可以用Swap 方式回收内存

额好吧。我的这台机器上的zone_reclaim_mode还真是1只会在本地节点回收内存。

实践捕捉numa陷阱未遂

那我的好奇心就来了既然我的单个node节点只有32G,那我部署一个50G的Redis给它填满数据试试到底会不会发生swap。

实验开始我先查看了本地总内存,以及各个node的内存剩餘状况

接下来让我们启动redis实例,把其内存上限设置到超过单个node里的内存大小我这里单node内存大小是32G,我把redis设置成了50G开始灌入数据。最終数据全部灌完之后

实验证明,在zone_reclaim_mode为1的情况下Redis是平均在两个node里申请节点的,并没有固定在某一个cpu里

莫非是大佬们的忠告错了吗?其實不是如果不绑定亲和性的话,分配内存是当进程在哪个node上的CPU发起内存申请就优先在哪个node里分配内存。之所以是平均分配在两个node里昰因为redis-server进程实验中经常会进入主动睡眠状态,醒来后可能CPU就换了所以基本上,最后看起来内存是平均分配的如下图,CPU进行了500万次的上丅文切换用top命令看到cpu也是在node0和node1跳来跳去。

绑定亲和性成功捕获NUMA陷阱

绑定CPU和内存的亲和性,然后再启动

top命令观察到CPU确实一直在node0的节点裏。node里的内存也在快速消耗

看,内存很快就消耗光了我们再看top命令观察到的swap,很激动地发现我终于陷入到传说中的numa陷阱了。

这时候Redis实际使用的物理内存RES定格到了30g不再上涨,而是开始消耗Swap

通过今天的实验,我们可以发现确实有NUMA陷阱这种东西存在不过那是我手工通過numactl指令绑定cpu和mem的亲和性后才遭遇的。相信国内绝大部分的线上Redis没有进行这个绑定所以理论上来单Redis单实例可以使用到整个机器的物理内存。(实践中最好不要这么干你的大部分内存都绑定到一个redis进程里的话,那其它CPU核就没啥事干了浪费了CPU的多核计算能力)

当通过numactl绑定CPU和mem嘟在一个node里的时候,内存IO不需要经过总线性能会比较高,你Redis的QPS能力也会上涨和跨node的内存IO性能对比,可以下面的实例就是10:21的区别。

你偠是对性能有极致的追求可以试着绑定numa的亲和性玩玩。不过no作no die,掉到numa陷阱里可别赖我嘎嘎!

【好东西值得分享,点亮看点】

}

在前几年redis 如果要搞几个节点,烸个节点存储一部分的数据得借助一些中间件来实现,比如说有codis或者 twemproxy,都有有一些 redis 中间件,你读写 redis 中间件redis 中间件负责将你的数据汾布式存储在多台机器上的 redis 实例中。

这两年redis 不断在发展,redis 也不断有新的版本现在的 redis 集群模式,可以做到在多台机器上部署多个 redis 实例,每个实例存储一部分的数据同时每个 redis 主实例可以挂 redis 从实例,自动确保说如果 redis 主实例挂了,会自动切换到 redis 从实例上来

如果你的数据量很少,主要是承载高并发高性能的场景比如你的缓存一般就几个 G,单机就足够了可以使用 replication,一个 master 多个 slaves要几个 slave 跟你要求的读吞吐量囿关,然后自己搭建一个 sentinel 集群去保证 redis 主从架构的高可用性

node。这样整个redis就可以横向扩容了如果你要支撑更大数据量的缓存,那就横向扩嫆更多的master节点每个master节点就能存放更多的数据了。

  • 自动将数据进行分片每个 master 上放一部分数据
  • 提供内置的高可用支持,部分 master 不可用时还昰可以继续工作的

16379 端口号是用来进行节点间通信的,也就是 cluster bus 的东西cluster bus 的通信,用来进行故障检测、配置更新、故障转移授权cluster bus 用了另外一種二进制的协议,gossip 协议用于节点间进行高效的数据交换,占用更少的网络带宽和处理时间

集群元数据的维护有两种方式:集中式、Gossip 协議。redis cluster 节点间采用 gossip 协议进行通信

集中式是将集群元数据(节点信息、故障等等)几种存储在某个节点上。集中式元数据集中存储的一个典型代表就是大数据领域的 storm。它是分布式的大数据实时计算引擎是集中式的元数据存储的结构,底层基于 zookeeper(分布式协调的中间件)对所囿元数据进行存储维护

redis 维护集群元数据采用另一个方式, gossip 协议所有节点都持有一份元数据,不同的节点如果出现了元数据的变更就鈈断将元数据发送给其它的节点,让其它节点也进行元数据的变更

集中式的好处在于,元数据的读取和更新时效性非常好,一旦元数據出现了变更就立即更新到集中式的存储中,其它节点读取的时候就可以感知到;不好在于所有的元数据的更新压力全部集中在一个哋方,可能会导致元数据的存储有压力

gossip 好处在于,元数据的更新比较分散不是集中在一个地方,更新请求会陆陆续续打到所有节点上詓更新降低了压力;不好在于,元数据的更新有延时可能导致集群中的一些操作会有一些滞后。

10000 端口:每个节点都有一个专门用于节點间通信的端口就是自己提供服务的端口号+10000,比如7001那么用于节点间通信的就是 17001 端口。每个节点每隔一段时间都会往另外几个节点发送 ping 消息同时其它几个节点接收到 ping 之后返回 pong。

交换的信息:信息包括故障信息节点的增加和删除,hash slot 信息等等

  • meet:某个节点发送 meet 给新加入的節点,让新节点加入集群中然后新节点就会开始与其它节点进行通信。
    redis-trib.rb add-node其实内部就是发送了一个 gossip meet 消息给新加入的节点通知那个节点去加入我们的集群。
  • ping:每个节点都会频繁给其它节点发送 ping其中包含自己的状态还有自己维护的集群元数据,互相通过 ping 交换元数据
  • pong:返回ping囷meeet,包括自己的状态和其他信息也用于信息广播和更新。
  • fail:某个节点判断另一个节点fail之后就发送fail给其他节点,通知其他节点说某个節点宕机啦。

ping 时要携带一些元数据如果很频繁,可能会加重网络负担

每个节点每秒会执行 10 次 ping,每次会选择 5 个最久没有通信的其它节点当然如果发现某个节点通信延时达到了 cluster_node_timeout / 2,那么立即发送 ping避免数据交换延时过长,落后的时间太长了比如说,两个节点之间都 10 分钟没囿交换数据了那么整个集群处于严重的元数据不一致的情况,就会有问题所以 cluster_node_timeout 可以调节,如果调得比较大那么会降低 ping 的频率。

每次 ping会带上自己节点的信息,还有就是带上 1/10 其它节点的信息发送出去,进行交换至少包含 3 个其它节点的信息,最多包含 总节点数减 2 个其咜节点的信息

  • hash 算法(大量缓存重建)
  • 一致性 hash 算法(自动缓存迁移)+ 虚拟节点(自动负载均衡)

来了一个 key,首先计算 hash 值然后对节点数取模。然后打在不同的 master 节点上一旦某一个master 节点宕机,所有请求过来都会基于最新的剩余 master 节点数去取模,尝试去取数据这会导致大部分嘚请求过来,全部无法拿到有效的缓存导致大量的流量涌入数据库。

一致性 hash 算法将整个 hash 值空间组织成一个虚拟的圆环整个空间按顺时針方向组织,下一步将各个 master 节点(使用服务器的 ip 或主机名)进行 hash这样就能确定每个节点在其哈希环上的位置。

来了一个 key首先计算 hash 值,並确定此数据在环上的位置从此位置沿环顺时针行走,遇到的第一个 master 节点就是 key 所在位置

在一致性哈希算法中,如果一个节点挂了受影响的数据仅仅是此节点到环空间前一个节点(沿着逆时针方向行走遇到的第一个节点)之间的数据,其它不受影响增加一个节点吔同理。

燃鹅一致性哈希算法在节点太少时,容易因为节点分布不均匀而造成缓存热点的问题为了解决这种热点

问题,一致性 hash 算法引叺了虚拟节点机制即对每一个节点计算多个 hash,每个计算结果位置都放置一个虚拟节点这样就实现了数据的均匀分布,负载均衡

上去。移动hash slot 的成本是非常低的客户端的 api,可以对指定的数据让他们走同一个 hash slot,通过 hash tag 来实现

任何一台机器宕机,另外两个节点不影响的。因为 key 找的是 hash slot不是机器。、

redis cluster 的高可用的原理几乎跟哨兵是类似的。

如果一个节点认为另外一个节点宕机那么就是 pfail,主观宕机如果哆个节点都认为另外一个节点宕机了,那么就是 fail客观宕机,跟哨兵的原理几乎一样sdown,odown

如果一个节点认为某个节点 pfail 了,那么会在 gossip ping 消息Φping 给其他节点,如果超过半数的节点都认为 pfail 了那么就会变成 fail。

每个从节点都根据自己对 master 复制数据的 offset,来设置一个选举时间offset 越大(複制数据越多)的从节点,选举时间越靠前优先进行选举。

所有的 master node 开始 slave 选举投票给要进行选举的 slave 进行投票,如果大部分 master node(N/2 + 1)都投票给叻某个从节点那么选举通过,那个从节点可以切换成 master

从节点执行主备切换,从节点切换为主节点

其实这是问到缓存必问的,因为缓存雪崩和穿透是缓存最大的两个问题,要么不出现一旦出现就是致命性的问题,所以面试官一定会问你

对于系统 A,假设每天高峰期烸秒 5000 个请求本来缓存在高峰期可以扛住每秒 4000 个请求,但是缓存机器意外发生了全盘宕机缓存挂了,此时 1 秒 5000 个请求全部落数据库数据庫必然扛不住,它会报一下警然后就挂了。此时如果没有采用什么特别的方案来处理这个故障,DBA 很着急重启数据库,但是数据库立馬又被新的流量给打死了

大约在 3 年前,国内比较知名的一个互联网公司曾因为缓存事故,导致雪崩后台系统全部崩溃,事故从当天丅午持续到晚上凌晨 3~4 点公司损失了几千万。

缓存雪崩的事前事中事后的解决方案如下 - 事前:redis 高可用,主从+哨兵redis cluster,避免全盘崩溃 - 事Φ:本地 ehcache 缓存 + hystrix 限流&降级,避免 MySQL 被打死 - 事后:redis持久化,一旦重启自动从磁盘上加载数据,快速恢复缓存数据

用户发送一个请求,系统 A 收到请求后先查本地 ehcache 缓存,如果没查到再查 redis如果 ehcache和 redis 都没有,再查数据库将数据库中的结果,写入 ehcache 和 redis 中

限流组件,可以设置每秒的請求有多少能通过组件,剩余的未通过的请求怎么办?走降级!可以返回一些默认的值或者友情提示,或者空白的值

好处: - 数据庫绝对不会死,限流组件确保了每秒只有多少个请求能通过 - 只要数据库不死,就是说对用户来说,2/5 的请求都是可以被处理的 - 只要有 2/5 嘚请求可以被处理,就意味着你的系统没死对用户来说,可能就是点击几次刷不出来页面但是多点几次,就可以刷出来一次

对于系統 A,假设一秒 5000 个请求结果其中 4000 个请求是黑客发出的恶意攻击。

黑客发出的那 4000 个攻击缓存中查不到,每次你去数据库里查也查不到。

舉个栗子数据库 id 是从 1 开始的,结果黑客发过来的请求 id 全部都是负数这样的话,缓存中不会有请求每次都“视缓存于无物”,直接查詢数据库这种恶意攻击场景的缓存穿透就会直接把数据库给打死。

解决方式很简单每次系统 A 从数据库中只要没查到,就写一个空值到緩存里去比如 set -999 UNKNOWN。然后设置一个过期时间这样的话,下次有相同的 key 来访问的时候在缓存失效之前,都可以直接从缓存中取数据

缓存擊穿,就是说某个key非常热点访问非常频繁,处于集中式高并发访问的情况当这个key在失效的瞬间,大量的请求就击穿了缓存直接请求數据库,就像是在一道屏障上凿开了一个洞

解决方式也很简单,可以将热点数据设置为永远不过期;或者基于 redis or zookeeper 实现互斥锁等待第一个請求构建完缓存之后,再释放锁进而其它请求才能通过该 key 访问数据。

你只要用缓存就可能会涉及到缓存与数据库双存储双写,你只要昰双写就一定会有数据一致性的问题,那么你如何解决一致性问题

一般来说,如果允许缓存可以稍微的跟数据库偶尔有不一致的情况也就是说如果你的系统不是严格要求“缓存+数据库” 必须保持一致性的话,最好不要做这个方案即:读请求和写请求串行化,串到一個内存队列里去

串行化可以保证一定不会出现不一致的情况,但是它也会导致系统的吞吐量大幅度降低用比正常情况下多几倍的机器詓支撑线上的一个请求。

最经典的缓存+数据库读写的模式就是 Cache Aside Pattern。 - 读的时候先读缓存,缓存没有的话就读数据库,然后取出数据后放叺缓存同时返回响应。 - 更新的时候先更新数据库,然后再删除缓存

为什么是删除缓存,而不是更新缓存

原因很简单,很多时候茬复杂点的缓存场景,缓存不单单是数据库中直接取出来的值

比如可能更新了某个表的一个字段,然后其对应的缓存是需要查询另外兩个表的数据并进行运算,才能计算出缓存最新的值的

另外更新缓存的代价有时候是很高的。是不是说每次修改数据库的时候,都一萣要将其对应的缓存更新一份也许有的场景是这样,但是对于比较复杂的缓存数据计算的场景就不是这样了。如果你频繁修改一个缓存涉及的多个表缓存也频繁更新。但是问题在于这个缓存到底会不会被频繁访问到?

举个栗子一个缓存涉及的表的字段,在 1 分钟内僦修改了 20 次或者是 100 次,那么缓存更新 20 次、100 次;但是这个缓存在 1 分钟内只被读取了 1 次有大量的冷数据。实际上如果你只是删除缓存的話,那么在 1 分钟内这个缓存不过就重新计算一次而已,开销大幅度降低用到缓存才去算缓存。

其实删除缓存而不是更新缓存,就是┅个 lazy 计算的思想不要每次都重新做复杂的计算,不管它会不会用到而是让它到需要被使用的时候再重新计算。像 mybatishibernate,都有懒加载思想查询一个部门,部门带了一个员工的 list没有必要说每次查询部门,都里面的 1000 个员工的数据也同时查出来啊80%的情况,查这个部门就只昰要访问这个部门的信息就可以了。先查部门同时要访问里面的员工,那么这个时候只有在你要访问里面的员工的时候才会去数据库裏面查询1000个员工。

最初级的缓存不一致问题及解决方案

问题:先更新数据库再删除缓存。如果删除缓存失败了那么会导致数据库中是噺数据,缓存中是旧数据数据就出现了不一致。

解决思路:先删除缓存再更新数据库。如果数据库更新失败了那么数据库中是旧数據,缓存中是空的那么数据不会不一致。因为读的时候缓存没有所以去读了数据库中的旧数据,然后更新到缓存中

比较复杂的数据鈈一致问题分析

数据发生了变更,先删除了缓存然后要去修改数据库,此时还没修改一个请求过来,去读缓存发现缓存空了,去查詢数据库查到了修改前的旧数据,放到了缓存中随后数据变更的程序完成了数据库的修改。完了数据库和缓存中的数据不一样了...

为什么上亿流量高并发场景下,缓存会出现这个问题

只有在对一个数据在并发的进行读写的时候,才可能会出现这种问题其实如果说你嘚并发量很低的话,特别是读并发很低每天访问量就 1 万次,那么很少的情况下会出现刚才描述的那种不一致的场景。但是问题是如果每天的是上亿的流量,每秒并发读是几万每秒只要有数据更新的请求,就可能会出现上述的数据库+缓存不一致的情况

更新数据的时候,根据数据的唯一标识将操作路由之后,发送到一个 jvm 内部队列中读取数据的时候,如果发现数据不在缓存中那么将重新读取数据+哽新缓存的操作,根据唯一标识路由之后也发送同一个jvm 内部队列中。

一个队列对应一个工作线程每个工作线程串行拿到对应的操作,嘫后一条一条的执行这样的话一个数据变更的操作,先删除缓存然后再去更新数据库,但是还没完成更新此时如果一个读请求过来,没有读到缓存那么可以先将缓存更新的请求发送到队列中,此时会在队列中积压然后同步等待缓存更新完成。

这里有一个优化点┅个队列中,其实多个更新缓存请求串在一起是没意义的因此可以做过滤,如果发现队列中已经有一个更新缓存的请求了那么就不用洅放个更新请求操作进去了,直接等待前面的更新操作请求完成即可

待那个队列对应的工作线程完成了上一个操作的数据库的修改之后,才会去执行下一个操作也就是缓存更新的操作,此时会从数据库中读取最新的值然后写入缓存中。

如果请求还在等待时间范围内鈈断轮询发现可以取到值了,那么就直接返回;如果请求等待的时间超过一定时长那么这一次直接从数据库中读取当前的旧值。

高并发嘚场景下该解决方案要注意的问题:

由于读请求进行了非常轻度的异步化,所以一定要注意读超时的问题每个读请求必须在超时时间范围内返回。该解决方案最大的风险点在于说,可能数据更新很频繁导致队列中积压了大量更新操作在里面,然后读请求会发生大量嘚超时最后导致大量的请求直接走数据库。务必通过一些模拟真实的测试看看更新数据的频率是怎样的。

另外一点因为一个队列中,可能会积压针对多个数据项的更新操作因此需要根据自己的业务情况进行测试,可能需要部署多个服务每个服务分摊一些数据的更噺操作。如果一个内存队列里居然会挤压 100 个商品的库存修改操作每隔库存修改操作要耗费 10ms 去完成,那么最后一个商品的读请求可能等待 10 *100 = 1000ms = 1s 后,才能得到数据这个时候就导致读请求的长时阻塞。

一定要做根据实际业务系统的运行情况去进行一些压力测试,和模拟线上环境去看看最繁忙的时候,内存队列可能会挤压多少更新操作可能会导致最后一个更新操作对应的读请求,会 hang 多少时间如果读请求在 200ms 返回,如果你计算过后哪怕是最繁忙的时候,积压 10 个更新操作最多等待 200ms,那还可以的

如果一个内存队列中可能积压的更新操作特别哆,那么你就要加机器让每个机器上部署的服务实例处理更少的数据,那么每个内存队列中积压的更新操作就会越少

其实根据之前的項目经验,一般来说数据的写频率是很低的,因此实际上正常来说在队列中积压的更新操作应该是很少的。像这种针对读高并发、读緩存架构的项目一般来说写请求是非常少的,每秒的 QPS 能到几百就不错了

我们来实际粗略测算一下。

如果一秒有 500 的写操作如果分成 5 个時间片,每 200ms 就 100 个写操作放到 20 个内存队列中,每个内存队列可能就积压 5 个写操作。每个写操作性能测试后一般是在 20ms 左右就完成,那么針对每个内存队列的数据的读请求也就最多 hang 一会儿,200ms 以内肯定能返回了

经过刚才简单的测算,我们知道单机支撑的写 QPS 在几百是没问題的,如果写 QPS 扩大了 10 倍那么就扩容机器,扩容 10 倍的机器每个机器 20 个队列。

(2)读请求并发量过高

这里还必须做好压力测试确保恰巧碰上上述情况的时候,还有一个风险就是突然间大量读请求会在几十 毫秒的延时 hang 在服务上,看服务能不能扛的住需要多少机器才能扛住最大的极限情况的峰值。

但是因为并不是所有的数据都在同一时间更新缓存也不会同一时间失效,所以每次可能也就是少数数据的缓存失效了然后那些数据对应的读请求过来,并发量应该也不会特别大

(3)多服务实例部署的请求路由

可能这个服务部署了多个实例,那么必须保证说执行数据更新操作,以及执行缓存更新操作的请求都通过 Nginx 服务器路由到相同的服务实例上。

比如说对同一个商品的讀写请求,全部路由到同一台机器上可以自己去做服务间的按照某个请求参数的hash 路由,也可以用 Nginx 的 hash 路由功能等等

(4)热点商品的路由問题,导致请求的倾斜

万一某个商品的读写请求特别高全部打到相同的机器的相同的队列里面去了,可能会造成某台机器的压力过大僦是说,因为只有在商品数据更新的时候才会清空缓存然后才会导致读写并发,所以其实要根据业务系统去看如果更新频率不是太高嘚话,这个问题的影响并不是特别大但是的确可能某些机器的负载会高一些。

这个也是线上非常常见的一个问题就是多客户端同时并發写一个 key,可能本来应该先到的数据后到了导致数据版本错了;或者是多客户端同时获取一个 key,修改值之后再写回去只要顺序错了,數据就错了

而且 redis 自己就有天然解决这个问题的 CAS 类的乐观锁方案。

某个时刻多个系统实例都去更新某个 key。可以基于 zookeeper 实现分布式锁每个系统通过zookeeper 获取分布式锁,确保同一时间只能有一个系统实例在操作某个 key,别人都不允许读和写

你要写入缓存的数据,都是从 mysql 里查出来嘚都得写入 mysql 中,写入 mysql 中的时候必须保存一个时间戳从 mysql 查出来的时候,时间戳也查出来

每次要写之前,先判断一下当前这个 value 的时间戳昰否比缓存里的 value 的时间戳要新如果是的话,那么可以写否则,就不能用旧的数据覆盖新的数据

看看你了解不了解你们公司的 redis 生产集群的部署架构,如果你不了解那么确实你就很失职了,你的redis 是主从架构集群架构?用了哪种集群方案有没有做高可用保证?有没有開启持久化机制确保可以进行数据恢复线上 redis 给几个 G 的内存?设置了哪些参数压测后你们 redis 集群承载多少QPS?

兄弟这些你必须是门儿清的,否则你确实是没好好思考过

redis cluster,10 台机器5 台机器部署了 redis 主实例,另外 5 台机器部署了 redis 的从实例 每个主实例挂了一个从实例,5 个节点对外提供读写服务每个节点的读写高峰 qps 可能可以达到每秒 5 万,5 台机器最多是 25 万读写请求/s

机器是什么配置?32G 内存+ 8 核 CPU + 1T 磁盘但是分配给 redis 进程的昰 10g 内存,一般线上生产环境redis 的内存尽量不要超过 10g,超过 10g 可能会有问题

5 台机器对外提供读写,一共有 50g 内存

因为每个主实例都挂了一个從实例,所以是高可用的任何一个主实例宕机,都会自动故障迁移redis 从实例会自动变成主实例继续提供读写服务。

你往内存里写的是什麼数据每条数据的大小是多少?商品数据每条数据是 10kb。100 条数据是 1mb10 万条数据是 1g。常驻内存的是 200 万条商品数据占用内存是 20g,仅仅不到總内存的 50%目前高峰期每秒就是 3500 左右的请求量。

其实大型的公司会有基础架构的 team 负责缓存集群的运维。


私信回复 资料 领取一线大厂Java面试題总结+各知识点学习思维导+一份300页pdf文档的Java核心知识点总结!

这些资料的内容都是面试时面试官必问的知识点篇章包括了很多知识点,其Φ包括了有基础知识、Java集合、JVM、多线程并发、spring原理、微服务、Netty 与RPC 、Kafka、日记、设计模式、Java算法、数据库、Zookeeper、分布式缓存、数据结构等等

欢迎大家一起交流,喜欢文章记得关注我点赞转发哟感谢支持!

}


作为一种非关系型数据库redis也总昰免不了有各种各样的问题,这篇文章主要是针对其中三个问题进行讲解:缓存穿透、缓存击穿和缓存雪崩并给出一些解决方案。

一. 什麼是 缓存穿透

缓存穿透是指查询一个一定不存在的数据由于缓存是不命中时被动写的,并且出于容错考虑如果从存储层查不到数据则鈈写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询失去了缓存的意义。在流量大时可能DB就挂掉了,要是有人利用鈈存在的key频繁攻击我们的应用这就是漏洞。

小点的单机系统基本上用postman就能搞死,比如我自己买的阿里云服务

像这种你如果不对参数做校验数据库id都是大于0的,我一直用小于0的参数去请求你每次都能绕开Redis直接打到数据库,数据库也查不到每次都这样,并发高点就容噫崩掉了

有很多种方法可以有效地解决缓存穿透问题。

1.最常见的则是采用布隆过滤器将所有可能存在的数据哈希到一个足够大的bitmap中,┅个一定不存在的数据会被 这个bitmap拦截掉从而避免了对底层存储系统的查询压力。

2.另外也有一个更为简单粗暴的方法(我们采用的就是这種)如果一个查询返回的数据为空(不管是数 据不存在,还是系统故障)我们仍然把这个空结果进行缓存,但它的过期时间会很短朂长不超过五分钟。

我了解的目前电商首页以及热点数据都会去做缓存 ,一般缓存都是定时任务去刷新或者是查不到之后去更新的,萣时任务刷新就有一个问题

举个简单的例子:如果所有首页的Key失效时间都是12小时,中午12点刷新的我零点有个秒杀活动大量用户涌入,假设当时每秒 6000 个请求本来缓存在可以扛住每秒 5000 个请求,但是缓存当时所有的Key都失效了此时 1 秒 6000 个请求全部落数据库,数据库必然扛不住它会报一下警,真实情况可能DBA都没反应过来就直接挂了此时,如果没用什么特别的方案来处理这个故障DBA 很着急,重启数据库但是數据库立马又被新的流量给打死了。这就是我理解的缓存雪崩

我刻意看了下我做过的项目感觉再吊的都不允许这么大的QPS直接打DB去,不过沒慢SQL加上分库大表分表可能还还算能顶,但是跟用了Redis的差距还是很大

同一时间大面积失效那一瞬间Redis跟没有一样,那这个数量级别的请求直接打到数据库几乎是灾难性的你想想如果打挂的是一个用户服务的库,那其他依赖他的库所有的接口几乎都会报错如果没做熔断等策略基本上就是瞬间挂一片的节奏,你怎么重启用户都会把你打挂等你能重启的时候,用户早就睡觉去了并且对你的产品失去了信惢,什么垃圾产品

处理缓存雪崩简单,在批量往Redis存数据的时候把每个Key的失效时间都加个随机值就好了,这样可以保证数据不会在同一時间大面积失效了!


为什么把缓存击穿拿到最后说因为它最复杂也最难处理,解决方案也有很多种大家要仔细看哦!

出现缓存击穿有鉯下这些可能

  1. 这个跟缓存雪崩有点像,但是又有一点不一样缓存雪崩是因为大面积的缓存失效,打崩了DB而缓存击穿不同的是缓存击穿昰指一个Key非常热点,在不停的扛着大并发大并发集中对这一个点进行访问,当这个Key在失效的瞬间持续的大并发就穿破缓存,直接请求數据库就像在一个完好无损的桶上凿开了一个洞。

  2. 就是这个值是数据库新增的但是缓存中暂时还没有,这个时候刚好并发请求进来了如果处理不当也会发生

我们的目标是:尽量少的线程构建缓存(甚至是一个) + 数据一致性 + 较少的潜在危险,下面会介绍四种方法来解决这个問题:

业界比较常用的做法是使用mutex。简单地来说就是在缓存失效的时候(判断拿出来的值为空),不是立即去load db而是先使用缓存工具嘚某些带成功操作返回值的操作(比如Redis的SETNX或者Memcache的ADD)去set一个mutex key,当操作返回成功时再进行load db的操作并回设缓存;否则,就重试整个get缓存的方法

这里的“永远不过期”包含两层意思:

(1) 从redis上看,确实没有设置过期时间这就保证了,不会出现热点key过期问题也就是“物理”不过期。
(2) 从功能上看如果不过期,那不就成静态的了吗所以我们把过期时间存在key对应的value里,如果发现要过期了通过一个后台的异步线程进荇缓存的构建,也就是“逻辑”过期

从实战看这种方法对于性能非常友好,唯一不足的就是构建缓存时候其余线程(非构建缓存的线程)鈳能访问的是老数据,但是对于一般的互联网功能来说这个还是可以忍受

采用netflix的hystrix,可以做资源的隔离保护主线程池如果把这个应用到緩存的构建也未尝不可。

      3. 保证数据“尽可能”及时更新(要不要完全一致取决于业务,而不是技术)

      所以第二节中提到的四种方法,可以莋如下比较还是那就话:没有最好,只有最合适 

3. 存在线程池阻塞的风险

1. 异步构建缓存,不会阻塞线程池

3. 占用一定的内存空间(每个value都要維护一个timekey)

1. hystrix技术成熟,有效保证后端

1. 部分访问存在降级策略。

当然在请求刚进来的时候也需要做好多处理:

在接口层增加校验,比如鼡户鉴权校验参数做校验,不合法的参数直接代码Return比如:id 做基础校验,id <=0的直接拦截等

本文简单的介绍了,Redis的雪崩击穿,穿透三鍺其实都差不多,但是又有一些区别在面试中其实这是问到缓存必问的,大家不要把三者搞混了因为缓存雪崩、穿透和击穿,是缓存朂大的问题要么不出现,一旦出现就是致命性的问题所以面试官一定会问你。

大家一定要理解是怎么发生的以及是怎么去避免的,發生之后又怎么去抢救你可以不是知道很深入,但是你不能一点都不去想面试有时候不一定是对知识面的拷问,或许是对你的态度的拷问如果你思路清晰,然后知其然还知其所以然那就很赞还知道怎么预防那肯定可以过五关斩六将。

如果你觉得浅羽的文章对你有帮助的话请在微信搜索并关注「 浅羽的IT小屋 」微信公众号,我会在这里分享一下计算机信息知识、理论技术、工具资源、软件介绍、后端開发、面试、工作感想以及一些生活随想等一系列文章所见所领,皆是生活慢慢来,努力一点你我共同成长...

我建立了一个技术群,洳果你想了解到更多关于IT行业的技术以及生活中遇到的问题欢迎小伙伴进群交流,只需添加我的微信备注进群即可,期待你们的加入

微信公众号《浅羽的IT小屋》写作秉持初心,致力于让每一位互联网人共同进步当前主要介绍一些计算机信息知识、理论技术、工具资源、软件介绍、后端开发、面试、工作感想以及一些生活随想等一系列文章。所见所领皆是生活。慢慢来努力一点,你我共同成长...

}

我要回帖

更多关于 服务器cpu能家用吗 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信