当前位置: 首页 > news >正文

Redis缓存策略以及bigkey的学习(九)

一、Redis内存

1.1、通过redis.conf配置文件查看内存

在这里插入图片描述

1.2、通过命令查看内存

config get maxmemory

在这里插入图片描述

为0?那这些key存哪去了?

原来不设置最大内存大小或者设置内存大小为0,在64位系统下不限制内存大小,在32位系统下最多使用大约3GB的内存。

1.3、一般设置内存大小多少合适?

一般设置为物理内存的四分之三左右。

1.4、如何设置redis内存大小?

    1. 修改配置文件,重启redis服务。
maxmemory 1024          # 注意单位是bytes
    1. 通过命令动态设置内存大小。
config set maxmemory 1024

1.5、查看内存使用情况命令

除了之前的config get maxmemory命令,还可以通过info命令查看内存使用情况。

info memory

1.6、如果Redis内存超出最大内存大小会发生什么?

会报OOM错误。
在这里插入图片描述

小结: 如果key不加上过期时间,随着时间推移,内存会越来越大,然后写满maxmemory大小,就会报OOM错误;为避免这个错误,需要了解内存淘汰策略

二、内存淘汰策略

2.1、redis过期键是如何删除的?

    1. 立即删除策略:保证过期键在过期后马上被删除,所占用的内存立即被释放,但对CPU不友好,需要时刻遍历设置生存时间的key,会产生大量的CPU消耗,影响性能。
    1. 惰性删除策略:数据达到过期时间,不做处理,只有在访问过期键时才会检查其是否过期,如果过期则删除。这种方式对CPU友好,但内存占用时间长,可能导致大量过期key没有被及时清理,造成内存溢出。

      支持惰性删除,需要在redis.conf配置文件中开启:lazyfree-lazy-eviction yes
    1. 定时删除策略(折中,推荐):每隔一段时间,遍历设置生存时间的key,随机检查是否有过期键,如果有则删除。这种方式介于前两种方式之间,既保证了内存及时释放,也减少了CPU消耗。

      这种方式难点在于:把握好删除时长和频率,不然容易退化成为上面两种策略的极端情况。

      小结:

      这三种方案还是存在一些问题,比如定期删除时,有些key从没有被访问过,导致没有被删除;惰性删除时,也有一些key从没有被访问过,导致没有被删除,就会使得redis内存空间紧张。

2.2、redis缓存策略

八大策略:

  • noeviction:不删除任何key,即使内存达到上限也不进行删除,此时增加key会返回error。
  • allkeys-lru:对所有key使用LRU算法淘汰key。
  • volatile-lru:对设置了过期时间的key使用LRU算法淘汰。
  • allkeys-lfu: 对所有key使用LFU算法淘汰。
  • volatile-lfu: 对设置了过期时间的key使用LFU算法淘汰。
  • allkeys-random:对所有key随机淘汰。
  • volatile-random:对设置了过期时间的key随机淘汰。
  • volatile-ttl:对设置了过期时间的key,根据过期时间立即淘汰
    在这里插入图片描述

2.3、LRU和LFU的区别

  • LRU(Least Recently Used):最近 最少使用 ,淘汰最长时间未被使用的页面,看页面最后一次被使用到发生调度的时间长短,首先淘汰最长时间未被使用的页面。
  • LFU(Least Frequently Used):最近 最不常使用 ,淘汰一定时期内访问次数最少的页面,看一定时间段内页面被访问的频率高低,淘汰一定时期内被访问次数最少的页面。
特性LRULFU
淘汰依据淘汰最长时间未被使用的页面淘汰一定时间内访问次数最少的页面
数据结构双向链表 + 哈希表双哈希表 + 频率链表
时间复杂度O(1)O(1)
/**     LRU缓存实现     **/
template<typename K, typename V>
class LRUCache
{public:LRUCache(size_t capacity) : m_capacity(capacity){}V get(K key){auto it = m_cache.find(key);if (it == m_cache.end()) throw out_of_range("Key not found");m_lru.splice(m_lru.begin(), m_lru, it->second); // 将节点移动到链表头部return it->second->second;}void put(K key, V value){auto it = m_cache.find(key);if (it != m_cache.end()){it->second->second = value; // 更新值m_lru.splice(m_lru.begin(), m_lru, it->second); // 更新节点位置return;}// 检查容量是否超出,如果超出则删除最旧的元素(尾部)if (m_lru.size() >= m_capacity){// 淘汰尾部元素auto last = m_lru.back();m_cache.erase(last->first); // 从哈希表中删除该元素m_lru.pop_back(); // 移除链表中的最后一个节点}// 插入新元素到头部m_lru.emplace_front(key, value);m_cache[key] = m_lru.begin();}void print(){for (auto& item : m_lru)cout << "Key: " << item.first << ", Value: " << item.second << "-> ";cout<<"nullptr\n";}private:size_t m_capacity;list<pair<K, V>> m_lru;      //存储键值对,头部最新,尾部最旧unordered_map<K, typename list<pair<K, V>>::iterator> m_cache;
};

在这里插入图片描述

/**     LFU缓存实现     **/
template <typename K, typename V>
class LFUCache
{
public:struct Node {K key;V value;int freq;       // 频率Node(K key, V val, int f) :key(key), value(val), freq(f) {}};LFUCache(size_t capacity) :m_capacity(capacity), m_minFreq(0) {}V get(K key){if (m_cache.find(key) == m_cache.end()) throw out_of_range("Key not found");// 获取节点,并更新频率auto node = m_cache[key];updateFreq(node);// 重新使迭代器指向keynode = m_cache[key];return node->value;}void put(K key, V value){if (m_capacity <= 0) return;// 如果键已存在, 更新值和频率if (m_cache.find(key) != m_cache.end()) {auto node = m_cache[key];node->value = value;updateFreq(node);return;}// 如果缓存已满,则移除最少使用的节点if (m_cache.size() >= m_capacity){// 移除最少使用的节点auto& minFreqList = m_freqList[m_minFreq];auto del_node = minFreqList.back();m_cache.erase(del_node.key);minFreqList.pop_back();// 如果链表为空,清除频率if (minFreqList.empty()) {m_freqList.erase(m_minFreq);if (m_minFreq > 1) --m_minFreq; // 更新最小频率值}}// 插入新节点,频率为1m_minFreq = 1; // 新节点频率为1,更新最小频率值m_freqList[m_minFreq].emplace_front(key, value, m_minFreq);m_cache[key] = m_freqList[1].begin();}void print(){cout << "==============================\n";cout << "Print\n";for (auto& val : m_freqList) {cout << "Frequency: " << val.first;for (auto& node : val.second) {cout << "  Key: " << node.key << " value: " << node.value << endl;}}cout << "==============================\n";}
private:size_t m_capacity;int m_minFreq;      // 记录当前最小的频率unordered_map<K, typename list<Node>::iterator> m_cache;unordered_map<int, list<Node>> m_freqList; // 频率列表,key为频率值,value为该频率下所有节点构成的链表void updateFreq(typename list<Node>::iterator node){// 保存节点的键和值K key = node->key;V value = node->value;// 从原频率链表移除int old_freq = node->freq;auto& old_list = m_freqList[old_freq];old_list.erase(node);// 如果频率链表为空,则更新最小频率值if (old_list.empty()) {m_freqList.erase(old_freq); // 移除空链表if (old_freq == m_minFreq)++m_minFreq;}// 插入到新频率链表中int new_freq = old_freq + 1;m_freqList[new_freq].emplace_front(key, value, new_freq);m_cache[key] = m_freqList[new_freq].begin();}
};

在这里插入图片描述

2.4、使用哪种策略好?

根据使用场景来定:

    1. 在所有的key都是最近经常使用,那么就需要选择allkeys-lru策略,替换掉最近最不常使用的key,如果不确定,也推荐使用allkeys-lru策略。
    1. 如果所有key访问频率都差不多,那么可以选择allkeys-random策略,随机淘汰。
    1. 如果对数据有足够了解,能够为key指定命中,那么可以选择volatile-ttl进行置换。

2.5、修改缓存配置

config set maxmemory-policy allkeys-lru

或者修改配置文件,maxmemory-policy allkeys-lru

三、BigKey

3.1、插入100W条记录

for i in {1..1000000}
do echo "set key$i $i" >> file.txt;
donecat file.txt | redis-cli -a 18302679697 --pipe

3.2、查看是否插入成功

keys * #别使用该命令,数据量太大,会导致阻塞
DBSIZE 

*实际使用中,禁用keys ,flushdb,flushall等命令

通过配置设置禁用这些命令,redis.conf中:SECURITY中

rename command keys ""
rename command flushdb ""
rename command flushall ""

3.3、使用SCAN之类命令

SCAN,SSCAN,HSCAN,ZSCAN一组,分别针对不同的数据类型

SCAN是基于游标的迭代器,每次调用之后,都会返回一个新的游标,直到返回0为止。

SCAN cursor [MATCH pattern] [COUNT count]    #扫描游标,匹配模式,计数器,类似MySQL中的Limit#上一步的返回,是下一次扫描的游标,0表示结束,也代表开始
SCAN 0
20
scan 20
  • 返回值有两个
    • 下次遍历的新游标
    • 数组,包含被迭代的元素
  • SCAN的遍历顺序
    并不是从第一位开始遍历,而是采用高位进位加法的方式遍历,之所以使用这样的方式进行遍历,是考虑字典的扩容和缩容时避免槽位的遍历重复和遗漏。
    在这里插入图片描述

3.4、多大算BigKey?

首先大的不是key本身,而是value.

  • string类型控制在10KB以内
  • 哈希类型控制在5000个字段以内
  • 列表类型控制在5000个元素以内
  • 集合类型控制在5000个元素以内
  • 排序类型控制在5000个元素以内

3.5、BigKey的危害

  • 内存不均,集群迁移困难
  • 超时删除,大key阻塞
  • 网络流量阻塞

3.6、如何会产生BigKey?

  • 社交类:
    • 粉丝列表,某明星的粉丝,逐步递增
  • 汇总统计:
    • 某个报表,经年累月积累

3.7、如何发现BigKey?

  • redis-cli --bigkeys

    • 好处:给出每种数据结构Top1 BigKey,同时给出每种数据结构的键值个数 + 平均大小
    • 不足:想查询大于10kb的所有key,该命令无能为力
      在这里插入图片描述
  • MEMORY USAGE key
    给出一个key和它的值在RAM中所占用的字节数,返回的值时key的值以及为管理该key分配的内存总字节数

    语法:

MEMORY USAGE key [SAMPLES count]

在这里插入图片描述

3.8、如何删除BigKey?

非字符串的bigkey,不要使用del删除,使用hscan,sscan,zscan等命令逐步删除;同时也要注意防止bigkey过期时间自动删除。

  • string类型: 一般可用del,如果过于庞大unlink
  • hash类型:使用hscan每次获取少量field-value,再使用hdel删除每个field,游标从0开始,直到返回游标为0,do-while循环
  • list类型:使用LTRIM类似
  • set类型:使用sscan类似
  • zset类型:使用zsacn类似,再使用ZREMRANGERBYSCORE删除命令删除每个元素

3.9、BigKey调优

redis.conf配置文件中LAZY FREEING相关说明

lazyfree-lazy-eviction no  #默认关闭,当内存达到上限时,释放对象采用延迟策略
lazyfree-lazy-expire no   #默认关闭,过期删除采用延迟策略
lazyfree-lazy-server-del yes  #默认关闭,删除键值对采用延迟策略
replacer-lazy yes  #默认关闭,删除键值对采用延迟策略
lazyfree-lazy-user-del yes  #默认关闭,用户主动删除键值对采用延迟策略

四、总结

4.1、Redis内存淘汰策略

  • 2个维度:过期键中筛选,所有键中筛选
  • 4个方面:LRU,LFU,随机,TTL
    总计8种策略。

4.2、redis有这些缓存策略,是不是可以随意存储bigkey?

虽然redis对过期键有3三种删除方式,又增添了8种策略来淘汰内存,但不代表就可以在redis中存储bigkey数据,会极大影响redis的性能,甚至导致服务不可用。

Code
0vice·GitHub

http://www.dtcms.com/a/299428.html

相关文章:

  • C语言——学习笔记
  • 数据结构(4)单链表算法题(上)
  • Linux DNS 服务器正反向解析
  • 深入分析计算机网络传输层和应用层面试题
  • 从压缩到加水印,如何实现一站式图片处理
  • 编程语言Java——核心技术篇(四)集合类详解
  • 从0开始学linux韦东山教程Linux驱动入门实验班(5)
  • C语言中:形参与实参的那些事
  • 分类预测 | MATLAB实现CPO-SVM冠豪猪算法优化支持向量机分类预测
  • 分类预测 | MATLAB实现DBO-SVM蜣螂算法优化支持向量机分类预测
  • pyskl-Windows系统使用自己的数据集训练(一)
  • 《C++ list 完全指南:从基础到高效使用》
  • 【洛谷】单向链表、队列安排、约瑟夫问题(list相关算法题)
  • 扣子(Coze)宣布开源两大核心项目——Coze Studio(扣子开发平台)和Coze Loop(扣子罗盘),附安装步骤
  • ubuntu下docker安装thingsboard物联网平台详细记录(附每张图)
  • 如何在 Ubuntu 24.04 或 22.04 中创建自定义 Bash 命令
  • 商汤InternLM发布最先进的开源多模态推理模型——Intern-S1
  • 【机器学习深度学习】LLamaFactory微调效果与vllm部署效果不一致如何解决
  • 开源智能体框架(Agent Zero)
  • VLAN的划分(基于华为eNSP)
  • Android 蓝牙学习
  • 使用Netty搭建一个网络聊天室
  • ​P1103 书本整理 - 洛谷​
  • 方正小标宋简3.0,可编辑
  • 暑期算法训练.9
  • ArcGIS 2024软件下载及安装教程|ArcGIS软件安装附下载地址|详细安装说明
  • Linux中的线程(Lightweight Processes - LWP)
  • Flowable 实战落地核心:选型决策与坑点破解
  • MGER实验
  • 数字化转型 - 企业数字化建设的几点思考