这个问题,互联网公司必问,要是一我的连缓存都不太清楚,那确实比较尴尬。java
只要问到缓存,上来第一个问题,确定是先问问你项目哪里用了缓存?为啥要用?不用行不行?若是用了之后可能会有什么不良的后果?mysql
用缓存,主要有两个用途:高性能、高并发。git
假设这么个场景,你有个操做,一个请求过来,吭哧吭哧你各类乱七八糟操做 mysql,半天查出来一个结果,耗时 600ms。可是这个结果可能接下来几个小时都不会变了,或者变了也能够不用当即反馈给用户。那么此时咋办?github
缓存啊,折腾 600ms 查出来的结果,扔缓存里,一个 key 对应一个 value,下次再有人查,别走 mysql 折腾 600ms 了,直接从缓存里,经过一个 key 查出来一个 value,2ms 搞定。性能提高 300 倍。面试
就是说对于一些须要复杂操做耗时查出来的结果,且肯定后面不怎么变化,可是有不少读请求,那么直接将查询出来的结果放在缓存中,后面直接读缓存就好。redis
mysql 这么重的数据库,压根儿设计不是让你玩儿高并发的,虽然也能够玩儿,可是自然支持很差。mysql 单机支撑到 2000QPS
也开始容易报警了。算法
因此要是你有个系统,高峰期一秒钟过来的请求有 1万,那一个 mysql 单机绝对会死掉。你这个时候就只能上缓存,把不少数据放缓存,别放 mysql。缓存功能简单,说白了就是 key-value
式操做,单机支撑的并发量轻松一秒几万十几万,支撑高并发 so easy。单机承载并发量是 mysql 单机的几十倍。sql
缓存是走内存的,内存自然就支撑高并发。数据库
常见的缓存问题有如下几个:编程
redis 相比 memcached 来讲,拥有更多的数据结构,能支持更丰富的数据操做。若是须要缓存可以支持更复杂的结构和操做, redis 会是不错的选择。
在 redis3.x 版本中,便能支持 cluster 模式,而 memcached 没有原生的集群模式,须要依靠客户端来实现往集群中分片写入数据。
因为 redis 只使用单核,而 memcached 可使用多核,因此平均每个核上 redis 在存储小数据时比 memcached 性能更高。而在 100k 以上的数据中,memcached 性能要高于 redis。虽然 redis 最近也在存储大数据的性能上进行优化,可是比起 memcached,仍是稍有逊色。
redis 内部使用文件事件处理器 file event handler
,这个文件事件处理器是单线程的,因此 redis 才叫作单线程的模型。它采用 IO 多路复用机制同时监听多个 socket,将产生事件的 socket 压入内存队列中,事件分派器根据 socket 上的事件类型来选择对应的事件处理器进行处理。
文件事件处理器的结构包含 4 个部分:
多个 socket 可能会并发产生不一样的操做,每一个操做对应不一样的文件事件,可是 IO 多路复用程序会监听多个 socket,会将产生事件的 socket 放入队列中排队,事件分派器每次从队列中取出一个 socket,根据 socket 的事件类型交给对应的事件处理器进行处理。
来看客户端与 redis 的一次通讯过程:
要明白,通讯是经过 socket 来完成的,不懂的同窗能够先去看一看 socket 网络编程。
首先,redis 服务端进程初始化的时候,会将 server socket 的 AE_READABLE
事件与链接应答处理器关联。
客户端 socket01 向 redis 进程的 server socket 请求创建链接,此时 server socket 会产生一个 AE_READABLE
事件,IO 多路复用程序监听到 server socket 产生的事件后,将该 socket 压入队列中。文件事件分派器从队列中获取 socket,交给链接应答处理器。链接应答处理器会建立一个能与客户端通讯的 socket01,并将该 socket01 的 AE_READABLE
事件与命令请求处理器关联。
假设此时客户端发送了一个 set key value
请求,此时 redis 中的 socket01 会产生 AE_READABLE
事件,IO 多路复用程序将 socket01 压入队列,此时事件分派器从队列中获取到 socket01 产生的 AE_READABLE
事件,因为前面 socket01 的 AE_READABLE
事件已经与命令请求处理器关联,所以事件分派器将事件交给命令请求处理器来处理。命令请求处理器读取 socket01 的 key value
并在本身内存中完成 key value
的设置。操做完成后,它会将 socket01 的 AE_WRITABLE
事件与命令回复处理器关联。
若是此时客户端准备好接收返回结果了,那么 redis 中的 socket01 会产生一个 AE_WRITABLE
事件,一样压入队列中,事件分派器找到相关联的命令回复处理器,由命令回复处理器对 socket01 输入本次操做的一个结果,好比 ok
,以后解除 socket01 的 AE_WRITABLE
事件与命令回复处理器的关联。
这样便完成了一次通讯。
除非是面试官感受看你简历,是工做 3 年之内的比较初级的同窗,可能对技术没有很深刻的研究,面试官才会问这类问题。不然,在宝贵的面试时间里,面试官实在不想多问。
其实问这个问题,主要有两个缘由:
要是你回答的很差,没说出几种数据类型,也没说什么场景,你完了,面试官对你印象确定很差,以为你平时就是作个简单的 set 和 get。
redis 主要有如下几种数据类型:
这是最简单的类型,就是普通的 set 和 get,作简单的 KV 缓存。
这个是相似 map 的一种结构,这个通常就是能够将结构化的数据,好比一个对象(前提是这个对象没嵌套其余的对象)给缓存在 redis 里,而后每次读写缓存的时候,能够就操做 hash 里的某个字段。
hset person name bingo hset person age 20 hset person id 1 hget person name
person = { "name": "bingo", "age": 20, "id": 1 }
list 是有序列表,这个能够玩儿出不少花样。
好比能够经过 list 存储一些列表型的数据结构,相似粉丝列表、文章的评论列表之类的东西。
好比能够经过 lrange 命令,读取某个闭区间内的元素,能够基于 list 实现分页查询,这个是很棒的一个功能,基于 redis 实现简单的高性能分页,能够作相似微博那种下拉不断分页的东西,性能高,就一页一页走。
# 0开始位置,-1结束位置,结束位置为-1时,表示列表的最后一个位置,即查看全部。
lrange mylist 0 -1
好比能够搞个简单的消息队列,从 list 头怼进去,从 list 尾巴那里弄出来。
lpush mylist 1 lpush mylist 2 lpush mylist 3 4 5 # 1 rpop mylist
set 是无序集合,自动去重。
直接基于 set 将系统里须要去重的数据扔进去,自动就给去重了,若是你须要对一些数据进行快速的全局去重,你固然也能够基于 jvm 内存里的 HashSet 进行去重,可是若是你的某个系统部署在多台机器上呢?得基于 redis 进行全局的 set 去重。
能够基于 set 玩儿交集、并集、差集的操做,好比交集吧,能够把两我的的粉丝列表整一个交集,看看俩人的共同好友是谁?对吧。
把两个大 V 的粉丝都放在两个 set 中,对两个 set 作交集。
#-------操做一个set------- # 添加元素 sadd mySet 1 # 查看所有元素 smembers mySet # 判断是否包含某个值 sismember mySet 3 # 删除某个/些元素 srem mySet 1 srem mySet 2 4 # 查看元素个数 scard mySet # 随机删除一个元素 spop mySet #-------操做多个set------- # 将一个set的元素移动到另一个set smove yourSet mySet 2 # 求两set的交集 sinter yourSet mySet # 求两set的并集 sunion yourSet mySet # 求在yourSet中而不在mySet中的元素 sdiff yourSet mySet
sorted set 是排序的 set,去重但能够排序,写进去的时候给一个分数,自动根据分数排序。
zadd board 85 zhangsan zadd board 72 lisi zadd board 96 wangwu zadd board 63 zhaoliu # 获取排名前三的用户(默认是升序,因此须要 rev 改成降序) zrevrange board 0 3 # 获取某用户的排名 zrank board zhaoliu
常见的有两个问题:
可能有同窗会遇到,在生产环境的 redis 常常会丢掉一些数据,写进去了,过一下子可能就没了。个人天,同窗,你问这个问题就说明 redis 你就没用对啊。redis 是缓存,你给当存储了是吧?
啥叫缓存?用内存当缓存。内存是无限的吗,内存是很宝贵并且是有限的,磁盘是廉价并且是大量的。可能一台机器就几十个 G 的内存,可是能够有几个 T 的硬盘空间。redis 主要是基于内存来进行高性能、高并发的读写操做的。
那既然内存是有限的,好比 redis 就只能用 10G,你要是往里面写了 20G 的数据,会咋办?固然会干掉 10G 的数据,而后就保留 10G 的数据了。那干掉哪些数据?保留哪些数据?固然是干掉不经常使用的数据,保留经常使用的数据了。
这是由 redis 的过时策略来决定。
redis 过时策略是:按期删除+惰性删除。
所谓按期删除,指的是 redis 默认是每隔 100ms 就随机抽取一些设置了过时时间的 key,检查其是否过时,若是过时就删除。
假设 redis 里放了 10w 个 key,都设置了过时时间,你每隔几百毫秒,就检查 10w 个 key,那 redis 基本上就死了,cpu 负载会很高的,消耗在你的检查过时 key 上了。注意,这里可不是每隔 100ms 就遍历全部的设置过时时间的 key,那样就是一场性能上的灾难。实际上 redis 是每隔 100ms 随机抽取一些 key 来检查和删除的。
可是问题是,按期删除可能会致使不少过时 key 到了时间并无被删除掉,那咋整呢?因此就是惰性删除了。这就是说,在你获取某个 key 的时候,redis 会检查一下 ,这个 key 若是设置了过时时间那么是否过时了?若是过时了此时就会删除,不会给你返回任何东西。
获取 key 的时候,若是此时 key 已通过期,就删除,不会返回任何东西。
可是实际上这仍是有问题的,若是按期删除漏掉了不少过时 key,而后你也没及时去查,也就没走惰性删除,此时会怎么样?若是大量过时 key 堆积在内存里,致使 redis 内存块耗尽了,咋整?
答案是:走内存淘汰机制。
redis 内存淘汰机制有如下几个:
你能够现场手写最原始的 LRU 算法,那个代码量太大了,彷佛不太现实。
不求本身纯手工从底层开始打造出本身的 LRU,可是起码要知道如何利用已有的 JDK 数据结构实现一个 Java 版的 LRU。
class LRUCache<K, V> extends LinkedHashMap<K, V> { private final int CACHE_SIZE; /** * 传递进来最多能缓存多少数据 * * @param cacheSize 缓存大小 */ public LRUCache(int cacheSize) { // true 表示让 linkedHashMap 按照访问顺序来进行排序,最近访问的放在头部,最老访问的放在尾部。 super((int) Math.ceil(cacheSize / 0.75) + 1, 0.75f, true); CACHE_SIZE = cacheSize; } @Override protected boolean removeEldestEntry(Map.Entry<K, V> eldest) { // 当 map中的数据量大于指定的缓存个数的时候,就自动删除最老的数据。 return size() > CACHE_SIZE; } }
redis 若是仅仅只是将数据缓存在内存里面,若是 redis 宕机了再重启,内存里的数据就所有都弄丢了啊。你必须得用 redis 的持久化机制,将数据写入内存的同时,异步的慢慢的将数据写入磁盘文件里,进行持久化。
若是 redis 宕机重启,自动从磁盘上加载以前持久化的一些数据就能够了,也许会丢失少量数据,可是至少不会将全部数据都弄丢。
这个其实同样,针对的都是 redis 的生产环境可能遇到的一些问题,就是 redis 要是挂了再重启,内存里的数据不就全丢了?能不能重启的时候把数据给恢复了?
持久化主要是作灾难恢复、数据恢复,也能够归类到高可用的一个环节中去,好比你 redis 整个挂了,而后 redis 就不可用了,你要作的事情就是让 redis 变得可用,尽快变得可用。
重启 redis,尽快让它对外提供服务,若是没作数据备份,这时候 redis 启动了,也不可用啊,数据都没了。
极可能说,大量的请求过来,缓存所有没法命中,在 redis 里根本找不到数据,这个时候就死定了,出现缓存雪崩问题。全部请求没有在 redis 命中,就会去 mysql 数据库这种数据源头中去找,一会儿 mysql 承接高并发,而后就挂了...
若是你把 redis 持久化作好,备份和恢复方案作到企业级的程度,那么即便你的 redis 故障了,也能够经过备份数据,快速恢复,一旦恢复当即对外提供服务。
append-only
的模式写入一个日志文件中,在 redis 重启的时候,能够经过回放 AOF 日志中的写入指令来从新构建整个数据集。RDB 或 AOF,均可以将 redis 内存中的数据给持久化到磁盘上面来,而后能够将这些数据备份到别的地方去,好比说阿里云等云服务。
若是 redis 挂了,服务器上的内存和磁盘上的数据都丢了,能够从云服务上拷贝回来以前的数据,放到指定的目录中,而后从新启动 redis,redis 就会自动根据持久化数据文件中的数据,去恢复内存中的数据,继续对外提供服务。
若是同时使用 RDB 和 AOF 两种持久化机制,那么在 redis 重启的时候,会使用 AOF 来从新构建数据,由于 AOF 中的数据更加完整。
RDB 会生成多个数据文件,每一个数据文件都表明了某一个时刻中 redis 的数据,这种多个数据文件的方式,很是适合作冷备,能够将这种完整的数据文件发送到一些远程的安全存储上去,好比说 Amazon 的 S3 云服务上去,在国内能够是阿里云的 ODPS 分布式存储上,以预约好的备份策略来按期备份 redis 中的数据。
RDB 对 redis 对外提供的读写服务,影响很是小,可让 redis 保持高性能,由于 redis 主进程只须要 fork 一个子进程,让子进程执行磁盘 IO 操做来进行 RDB 持久化便可。
相对于 AOF 持久化机制来讲,直接基于 RDB 数据文件来重启和恢复 redis 进程,更加快速。
若是想要在 redis 故障时,尽量少的丢失数据,那么 RDB 没有 AOF 好。通常来讲,RDB 数据快照文件,都是每隔 5 分钟,或者更长时间生成一次,这个时候就得接受一旦 redis 进程宕机,那么会丢失最近 5 分钟的数据。
RDB 每次在 fork 子进程来执行 RDB 快照数据文件生成的时候,若是数据文件特别大,可能会致使对客户端提供的服务暂停数毫秒,或者甚至数秒。
fsync
操做,最多丢失 1 秒钟的数据。append-only
模式写入,因此没有任何磁盘寻址的开销,写入性能很是高,并且文件不容易破损,即便文件尾部破损,也很容易修复。rewrite
log 的时候,会对其中的指令进行压缩,建立出一份须要恢复数据的最小日志出来。在建立新日志文件的时候,老的日志文件仍是照常写入。当新的 merge 后的日志文件 ready 的时候,再交换新老日志文件便可。flushall
命令清空了全部数据,只要这个时候后台 rewrite
尚未发生,那么就能够当即拷贝 AOF 文件,将最后一条 flushall
命令给删了,而后再将该 AOF
文件放回去,就能够经过恢复机制,自动恢复全部数据。fsync
一第二天志文件,固然,每秒一次 fsync
,性能也仍是很高的。(若是实时写入,那么 QPS 会大降,redis 性能会大大下降)对于系统 A,假设天天高峰期每秒 5000 个请求,原本缓存在高峰期能够扛住每秒 4000 个请求,可是缓存机器意外发生了全盘宕机。缓存挂了,此时 1 秒 5000 个请求所有落数据库,数据库必然扛不住,它会报一下警,而后就挂了。此时,若是没有采用什么特别的方案来处理这个故障,DBA 很着急,重启数据库,可是数据库立马又被新的流量给打死了。
这就是缓存雪崩。
大约在 3 年前,国内比较知名的一个互联网公司,曾由于缓存事故,致使雪崩,后台系统所有崩溃,事故从当天下午持续到晚上凌晨 3~4 点,公司损失了几千万。
缓存雪崩的事前事中过后的解决方案以下。
用户发送一个请求,系统 A 收到请求后,先查本地 ehcache 缓存,若是没查到再查 redis。若是 ehcache 和 redis 都没有,再查数据库,将数据库中的结果,写入 ehcache 和 redis 中。
限流组件,能够设置每秒的请求,有多少能经过组件,剩余的未经过的请求,怎么办?走降级!能够返回一些默认的值,或者友情提示,或者空白的值。
好处:
对于系统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 计算的思想,不要每次都从新作复杂的计算,无论它会不会用到,而是让它到须要被使用的时候再从新计算。像 mybatis,hibernate,都有懒加载思想。查询一个部门,部门带了一个员工的 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 个队列。
这里还必须作好压力测试,确保恰巧碰上上述状况的时候,还有一个风险,就是忽然间大量读请求会在几十毫秒的延时 hang 在服务上,看服务能不能扛的住,须要多少机器才能扛住最大的极限状况的峰值。
可是由于并非全部的数据都在同一时间更新,缓存也不会同一时间失效,因此每次可能也就是少数数据的缓存失效了,而后那些数据对应的读请求过来,并发量应该也不会特别大。
可能这个服务部署了多个实例,那么必须保证说,执行数据更新操做,以及执行缓存更新操做的请求,都经过 Nginx 服务器路由到相同的服务实例上。
好比说,对同一个商品的读写请求,所有路由到同一台机器上。能够本身去作服务间的按照某个请求参数的 hash 路由,也能够用 Nginx 的 hash 路由功能等等。
万一某个商品的读写请求特别高,所有打到相同的机器的相同的队列里面去了,可能会形成某台机器的压力过大。就是说,由于只有在商品数据更新的时候才会清空缓存,而后才会致使读写并发,因此其实要根据业务系统去看,若是更新频率不是过高的话,这个问题的影响并非特别大,可是的确可能某些机器的负载会高一些。
这个也是线上很是常见的一个问题,就是多客户端同时并发写一个 key,可能原本应该先到的数据后到了,致使数据版本错了;或者是多客户端同时获取一个 key,修改值以后再写回去,只要顺序错了,数据就错了。
并且 redis 本身就有自然解决这个问题的 CAS 类的乐观锁方案。
某个时刻,多个系统实例都去更新某个 key。能够基于 zookeeper 实现分布式锁。每一个系统经过 zookeeper 获取分布式锁,确保同一时间,只能有一个系统实例在操做某个 key,别人都不容许读和写。
你要写入缓存的数据,都是从 mysql 里查出来的,都得写入 mysql 中,写入 mysql 中的时候必须保存一个时间戳,从 mysql 查出来的时候,时间戳也查出来。
每次要写以前,先判断一下当前这个 value 的时间戳是否比缓存里的 value 的时间戳要新。若是是的话,那么能够写,不然,就不能用旧的数据覆盖新的数据。
你的 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 条数据是 1mb,10 万条数据是 1g。常驻内存的是 200 万条商品数据,占用内存是 20g,仅仅不到总内存的 50%。目前高峰期每秒就是 3500 左右的请求量。
其实大型的公司,会有基础架构的 team 负责缓存集群的运维。
RT:响应时间是指系统对请求做出响应的时间。
TPS:吞吐量是指系统在单位时间内处理请求的数量。
并发用户数 :并发用户数是指系统能够同时承载的正常使用系统功能的用户的数量。
QPS:每秒查询率QPS是对一个特定的查询服务器在规定时间内所处理流量多少的衡量标准。