扫二维码与项目经理沟通
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流
redis基础结构和缓存策略以及常见缓存问题是什么,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。
成都创新互联公司是网站建设专家,致力于互联网品牌建设与网络营销,专业领域包括网站建设、成都做网站、电商网站制作开发、成都微信小程序、微信营销、系统平台开发,与其他网站设计及系统开发公司不同,我们的整合解决方案结合了恒基网络品牌建设经验和互联网整合营销的理念,并将策略和执行紧密结合,且不断评估并优化我们的方案,为客户提供全方位的互联网品牌整合方案!
一、常见的缓存策略有哪些
由于不同系统的数据访问模式不同,同一种缓存策略很难在不同的数据访问模式下取得满意的性能 缓存策略的分类:
1)、基于公平原则 FIFO(先进先出 queue)
2)、基于访问的时间 LRU (最近最少使用 链表)
3)、基于访问频率 如LFU(最近最不常用)、LRU2、2Q、LIRS
4)、访问时间与频率兼顾:通过兼顾访问时间和频率。使得数据模式在变化时缓存策略仍有较好性能。如FBR、LRUF、ALRFU。多数此类算法具有一个可调或自适应参数,通过该参数的调节使缓存策略在基于访问时间与频率间取得一个平衡
5)、基于访问模式:某些应用有较明确的数据访问特点,进而产生与其相适应的缓存策略。如专用的VoD系统设计的A&L缓存策略,同时适应随机、顺序两种访问模式的SARC策略
二、Redis缓存淘汰策略
当maxmemory限制达到的时候Redis会使用的行为由 Redis的maxmemory-policy配置指令来进行配置。
以下的策略是可用的:
noeviction:返回错误,当内存限制达到并且客户端尝试执行分配更多内存的命令时(大部分的写入指令,但DEL和几个例外)
allkeys-lru: 尝试回收最少使用的键(LRU),使得新添加的数据有空间存放。
volatile-lru: 尝试回收最少使用的键(LRU),但仅限于在过期集合的键,使得新添加的数据有空间存放。
allkeys-random: 回收随机的键使得新添加的数据有空间存放。
volatile-random: 回收随机的键使得新添加的数据有空间存放,但仅限于在过期集合的键。
volatile-ttl: 回收在过期集合的键,并且优先回收存活时间(TTL)较短的键,使得新添加的数据有空间存放。
如果没有键满足回收的前提条件的话,策略volatile-lru, volatile-random以及volatile-ttl就和noeviction 差不多了。
选择正确的回收策略是非常重要的,这取决于你的应用的访问模式,不过你可以在运行时进行相关的策略调整,并且监控缓存命中率和没命中的次数,通过RedisINFO命令输出以便调优。
一般的经验规则:
使用allkeys-lru策略:当你希望你的请求符合一个幂定律分布,也就是说,你希望部分的子集元素将比其它其它元素被访问的更多。如果你不确定选择什么,这是个很好的选择。.
使用allkeys-random:如果你是循环访问,所有的键被连续的扫描,或者你希望请求分布正常(所有元素被访问的概率都差不多)。
使用volatile-ttl:如果你想要通过创建缓存对象时设置TTL值,来决定哪些对象应该被过期。
allkeys-lru 和 volatile-random策略对于当你想要单一的实例实现缓存及持久化一些键时很有用。不过一般运行两个实例是解决这个问题的更好方法。
为了键设置过期时间也是需要消耗内存的,所以使用allkeys-lru这种策略更加高效,因为没有必要为键取设置过期时间当内存有压力时。
三、如何做到缓存数据一致性
数据不一致性产生的原因
【1】、先操作删除缓存,再写数据库成功之前,如果有读请求发生,可能导致旧数据入缓存,引发数据不一致。如果不采用给缓存设置过期时间策略,该数据永远都是脏数据。
【解决办法】:
1)、可采用更新前后双删除缓存策略。
2)、可以通过“串行化”解决,保证同一个数据的读写落在同一个后端服务上。
【2】、先操作数据库,再清除缓存。如果删缓存失败了,就会出现数据不一致问题。
【解决办法】:
1)、将删除失败的key值存入队列中重复删除。
2)、方案二:通过订阅binlog获取需要重新删除的Key值数据。在应用程序中,另起一段程序,获得这个订阅程序传来的消息,进行删除缓存操作。
四、防止缓存穿透、击穿、雪崩和刷新
【1】、缓存穿透:缓存穿透是说收到一个请求,但是该请求缓存中不存在,只能去数据库中查询,然后放进缓存。但当有好多请求同时访问同一个数据时,业务系统把这些请求全发到了数据库;或者恶意构造一个逻辑上不存在的数据,然后大量发送这个请求,这样每次都会被发送到数据库,最总导致数据库挂掉。 解决的办法:对于恶意访问,一种思路是先做校验,对恶意数据直接过滤掉,不要发送至数据库层;第二种思路是缓存空结果,就是对查询不存在的数据也记录在缓存中,这样就可以有效的减少查询数据库的次数。非恶意访问,结合缓存击穿说明。
【2】、缓存击穿:上面提到的某个数据没有,然后好多请求查询数据库,可以归为缓存击穿的范畴:对于热点数据,当缓存失效的一瞬间,所有的请求都被下放到数据库去请求更新缓存 解决的办法:防范此类问题,一种思路是加全局锁,就是所有访问某个数据的请求都共享一个锁,获得锁的那个才有资格去访问数据库,其他线程必须等待。另一种思想是对即将过期的数据进行主动刷新,比如新起一个线程轮询数据,或者比如把所有的数据划分为不同的缓存区间,定期分区间刷新数据。第二个思路与缓存雪崩有点关系。
【3】、缓存雪崩:缓存雪崩是指当我们给所有的缓存设置了同样的过期时间,当某一时刻,整个缓存的数据全部过期了,然后瞬间所有的请求都被抛向了数据库,数据库就崩掉了。 解决的办法:解决思路要么是分治,划分更小的缓存区间,按区间过期;要么给每个key的过期时间加一个随机值,避免同时过期,达到错峰刷新缓存的目的。
【4】、缓存刷新:既清空缓存 ,一般在insert、update、delete操作后就需要刷新缓存,如果不执行就会出现脏数据。但当缓存请求的系统蹦掉后,返回给缓存的值为null。
五、redis与memcached区别
【1】、工作原理:
redis是单进程操作命令,memcached为多进程
【2】、效率差异:
redis对于小数据(<100K)处理较高,而memcached由于多进程对于大数据处理更有优势
【3】、支持的结构:
redis支持事务,Memcached仅支持简单的key-value结构的数据记录,Redis支持String、Hash、List、Set和Sorted Set
【4】、内存管理机制:
Redis可以设置内存满时缓存到磁盘,并有数据保存机制(RDB、AOF)
memcached把固定大小(1MB)的内存分为n小块,以1.25倍增大,存储时选择最小可放入的块存放数据,好处是不会频繁申请内存,提高IO效率,坏处是会有一定的内存浪费。
redis会按数据大小分配内存块,并将内存块大小记录下来,方便回收
六、Redis数据结构
【1】、Redis对象底层数据结构共有八种:
编码常量 | 编码所对应的底层数据结构 |
REDIS_ENCODING_INT | long 类型的整数 |
REDIS_ENCODING_EMBSTR | embstr 编码的简单动态字符串 |
REDIS_ENCODING_RAW | 简单动态字符串 |
REDIS_ENCODING_HT | 字典 |
REDIS_ENCODING_LINKEDLIST | 双端链表 |
REDIS_ENCODING_ZIPLIST | 压缩列表 |
REDIS_ENCODING_INTSET | 整数集合 |
REDIS_ENCODING_SKIPLIST | 跳跃表和字典 |
【2】、Redis对象和底层数据结构的关系
1)、String对象:
字符串对象的编码可以是int、raw 或者embstr (3.0新增) 一个字符串的内容可以转换为long就用 int结构。
如果字符串对象的长度小于39字节,就用 embstr对象。否则用传统的raw对象
embstr的好处有如下几点:
embstr的创建只需分配一次内存,而raw为两次(一次为sds分配对象,另一次为objet分配对象,embstr省去了第一次)。
相对地,释放内存的次数也由两次变为一次。
embstr的objet和sds放在一起,更好地利用缓存带来的优势。
2)、列表对象:
列表对象的编码可以是ziplist或者linkedlist
ziplist是一种压缩链表,它的好处是更能节省内存空间,因为它所存储的内容都是在连续的内存区域当中的。当列表对象元素不大,每个元素也不大的时候,就采用ziplist存储。但当数据量过大时就ziplist就不是那么好用了。因为为了保证他存储内容在内存中的连续性,插入的复杂度是O(N),即每次插入都会重新进行realloc。
linkedlist是一种双向链表。它的结构比较简单,节点中存放pre和next两个指针,还有节点相关的信息。当每增加一个node的时候,就需要重新malloc一块内存。
3)、哈希对象:
哈希对象的底层实现可以是ziplist或者hashtable
ziplist中的哈希对象是按照key1,value1,key2,value2这样的顺序存放来存储的。当对象数目不多且内容不大时,这种方式效率是很高的。
hashtable的是由dict这个结构来实现的
typedef struct dict { dictType *type; void *privdata; dictht ht[2]; long rehashidx; /* rehashing not in progress if rehashidx == -1 */ int iterators; /* number of iterators currently running */ } dict;
dict是一个字典,其中的指针dicht ht[2] 指向了两个哈希表
typedef struct dictht { dictEntry **table; unsigned long size; unsigned long sizemask; unsigned long used; } dictht;
dicht[0] 是用于真正存放数据,dicht[1]一般在哈希表元素过多进行rehash的时候用于中转数据
dictht中的table用于真正存放元素了,每个key/value对用一个dictEntry表示,放在dictEntry数组中
4)、集合对象:
集合对象的编码可以是intset或者hashtable
intset是一个整数集合,里面存的为某种同一类型的整数
intset是一个有序集合,查找元素的复杂度为O(logN),但插入时不一定为O(logN),因为有可能涉及到升级操作。比如当集合里全是int16_t型的整数,这时要插入一个int32_t,那么为了维持集合中数据类型的一致,那么所有的数据都会被转换成int32_t类型,涉及到内存的重新分配,这时插入的复杂度就为O(N)了。是intset不支持降级操作。
5)、有序集合对象:
有序集合的编码可能两种,一种是ziplist,另一种是skiplist与dict的结合。
ziplist作为集合和作为哈希对象是一样的,member和score顺序存放。按照score从小到大顺序排列。它的结构不再复述。
skiplist是一种跳跃表,它实现了有序集合中的快速查找,在大多数情况下它的速度都可以和平衡树差不多。但它的实现比较简单,可以作为平衡树的替代品
关于Redis基础结构和缓存策略以及常见缓存问题是什么问题的解答就分享到这里了,希望以上内容可以对大家有一定的帮助,如果你还有很多疑惑没有解开,可以关注创新互联行业资讯频道了解更多相关知识。
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流