数据请求方 向 数据持有方 请求数据的过程中,如果可以有一个可存储数据的中间部分,能使得大部分请求落在该中间部分上,就可以减少数据持有方的压力;这样的中间部分要么存储介质更优,要么在位置上更靠近数据请求方,从而使得落在该中间部分的请求可以更快的得到响应。
这样的能存储数据的中间部分就称为缓存。
假设一个系统有3个节点ABC,请求方向为 A->B ->C, 则 任意相邻两个节点之间都可以使用缓存来降低后续节点的压力,并加速响应。
简单来说可以分为client端缓存和server端缓存。
假设一套系统有 前端(Web),Web Server,DB,这样三个节点,则
在 前端 向 WebServer 发送请求 的过程中,前端可以使用浏览器的LocalStorage/SessionStorage等实现数据缓存,WebServer端可以使用比如nginx的 proxy cache将数据缓存在server端,同时也可以指定http response 的 header 的 Cache Control 告诉浏览器将数据缓存在前端;Web Server 端 代码里也可以使用单例类或者MemoryCache等类似的数据结构来缓存数据。
在 WebServer 向 DB 发送请求的过程中,常用类似redis这样的基于内存的数据存储仓库来做缓存。
Web:http Cache-Control,浏览器所支持的LocalStorage/SessionStorage/Cookie等缓存数据。
桌面程序或者手机App的话,不仅可以在内存中缓存数据,还可以直接将数据写入文件。
若浏览器得到的 http response 的 Header 中有 Cache-Control 属性,则浏览器将会根据该属性的值来缓存该http请求的response,比如浏览器收到的response中的Header含有 ‘Cache-Control’: ‘max-age=20’,则浏览器将会把该response缓存20秒,20秒内的相同请求将不会到达WebServer,而是直接从浏览器缓存中读取。
浏览器会根据response的类型来判断需要使用 memory cache 还是 disk cache。
通过在web server 与 client端之间加入nginx做反向代理,则nginx也可以实现一定的数据缓存工作,来将web server 的 数据 通过文件目录存入磁盘。
通过单例类等来实现将部分数据缓存在server端内存之中。
Redis是一种基于内存的,非关系型的,键值对存储数据的 数据存储仓库。常用来做缓存以及分布式锁。
redis基于内存,相对于基于磁盘的DB来说,数据读取快的多,性能更高,使用redis做DB数据的缓存,可以明显降低DB压力,从而更好的支持高并发。
常用作缓存,分布式锁 (setnx),计数器 等。
主要5种,string, list, set, zset,hash
两种策略
RDB (redis database): 默认的持久化方式,将内存种的数据以快照的形式,保存成硬盘中的一个文件。
AOF (append-only file): 将每次直醒的命令记录到单独的日志文件中,重启redis时会根据日志来恢复数据。
我们都知道,Redis是key-value数据库,我们可以设置Redis中缓存的key的过期时间。Redis的过期策略就是指当Redis中缓存的key过期了,Redis如何处理。
过期策略通常有以下三种:
• 定时过期:每个设置过期时间的key都需要创建一个定时器,到过期时间就会立即清除。该策略可以立即清除过期的数据,对内存很友好;但是会占用大量的CPU资源去处理过期的数据,从而影响缓存的响应时间和吞吐量。
• 惰性过期:只有当访问一个key时,才会判断该key是否已过期,过期则清除。该策略可以最大化地节省CPU资源,却对内存非常不友好。极端情况可能出现大量的过期key没有再次被访问,从而不会被清除,占用大量内存。
• 定期过期:每隔一定的时间,会扫描一定数量的数据库的expires字典中一定数量的key,并清除其中已过期的key。该策略是前两者的一个折中方案。通过调整定时扫描的时间间隔和每次扫描的限定耗时,可以在不同情况下使得CPU和内存资源达到最优的平衡效果。
(expires字典会保存所有设置了过期时间的key的过期时间数据,其中,key是指向键空间中的某个键的指针,value是该键的毫秒精度的UNIX时间戳表示的过期时间。键空间是指该Redis集群中保存的所有键。)
Redis中同时使用了惰性过期和定期过期两种过期策略。
Redis的内存淘汰策略是指在Redis的用于缓存的内存不足时,怎么处理需要新写入且需要申请额外空间的数据。
全局的键空间选择性移除
• noeviction:当内存不足以容纳新写入数据时,新写入操作会报错。
• allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key。(这个是最常用的)
• allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个key。
设置过期时间的键空间选择性移除
• volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的key。
• volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个key。
• volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的key优先移除。
总结
Redis的内存淘汰策略的选取并不会影响过期的key的处理。内存淘汰策略用于处理内存不足时的需要申请额外空间的数据;过期策略用于处理过期的缓存数据。
缓存雪崩是指缓存同一时间大面积的失效,所以,后面的请求都会落到数据库上,造成数据库短时间内承受大量请求而崩掉。
解决方案
1. 缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
2. 一般并发量不是特别多的时候,使用最多的解决方案是加锁排队。
3. 给每一个缓存数据增加相应的缓存标记,记录缓存的是否失效,如果缓存标记失效,则更新数据缓存。
缓存穿透是指缓存和数据库中都没有的数据,导致所有的请求都落到数据库上,造成数据库短时间内承受大量请求而崩掉。
解决方案
1. 接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截;
2. 从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击
3. 采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的 bitmap 中,一个一定不存在的数据会被这个 bitmap 拦截掉,从而避免了对底层存储系统的查询压力
缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力。和缓存雪崩不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。
解决方案
1. 设置热点数据永远不过期。
2. 加互斥锁,互斥锁