Redis:缓存被我写满了,该怎么办?
nanshan 2024-12-10 18:56 10 浏览 0 评论
介绍
Redis是一个内存数据库,当Redis使用的内存超过物理内存的限制后,内存数据会和磁盘产生频繁的交换,交换会导致Redis性能急剧下降。所以在生产环境中我们通过配置参数maxmemoey来限制使用的内存大小。
当实际使用的内存超过maxmemoey后,Redis提供了如下几种可选策略。
noeviction:写请求返回错误
volatile-lru:使用lru算法删除设置了过期时间的键值对 volatile-lfu:使用lfu算法删除设置了过期时间的键值对 volatile-random:在设置了过期时间的键值对中随机进行删除 volatile-ttl:根据过期时间的先后进行删除,越早过期的越先被删除
allkeys-lru:在所有键值对中,使用lru算法进行删除 allkeys-lfu:在所有键值对中,使用lfu算法进行删除 allkeys-random:所有键值对中随机删除
我们来详细了解一下lru和lfu算法,这是2个常见的缓存淘汰算法。「因为计算机缓存的容量是有限的,所以我们要删除那些没用的数据,而这两种算法的区别就是判定没用的纬度不一样。」
LRU算法
「lru(Least recently used,最近最少使用)算法,即最近访问的数据,后续很大概率还会被访问到,即是有用的。而长时间未被访问的数据,应该被淘汰」
lru算法中数据会被放到一个链表中,链表的头节点为最近被访问的数据,链表的尾节点为长时间没有被访问的数据
「lru算法的核心实现就是哈希表加双向链表」。链表可以用来维护访问元素的顺序,而hash表可以帮我们在O(1)时间复杂度下访问到元素。
「至于为什么是双向链表呢」?主要是要删除元素,所以要获取前继节点。数据结构图示如下
使用双向链表+HashMap
双向链表节点定义如下
public class ListNode<K, V> {
K key;
V value;
ListNode pre;
ListNode next;
public ListNode() {}
public ListNode(K key, V value) {
this.key = key;
this.value = value;
}
}
封装双向链表的常用操作
public class DoubleList {
private ListNode head;
private ListNode tail;
public DoubleList() {
head = new ListNode();
tail = new ListNode();
head.next = tail;
tail.pre = head;
}
public void remove(ListNode node) {
node.pre.next = node.next;
node.next.pre = node.pre;
}
public void addLast(ListNode node) {
node.pre = tail.pre;
tail.pre = node;
node.pre.next = node;
node.next = tail;
}
public ListNode removeFirst() {
if (head.next == tail) {
return null;
}
ListNode first = head.next;
remove(first);
return first;
}
}
封装一个缓存类,提供最基本的get和put方法。「需要注意,这两种基本的方法都涉及到对两种数据结构的修改」。
public class MyLruCache<K, V> {
private int capacity;
private DoubleList doubleList;
private Map<K, ListNode> map;
public MyLruCache(int capacity) {
this.capacity = capacity;
map = new HashMap<>();
doubleList = new DoubleList();
}
public V get(Object key) {
ListNode<K, V> node = map.get(key);
if (node == null) {
return null;
}
// 先删除该节点,再接到尾部
doubleList.remove(node);
doubleList.addLast(node);
return node.value;
}
public void put(K key, V value) {
// 直接调用这边的get方法,如果存在,它会在get内部被移动到尾巴,不用再移动一遍,直接修改值即可
if ((get(key)) != null) {
map.get(key).value = value;
return;
}
// 如果超出容量,把头去掉
if (map.size() == capacity) {
ListNode listNode = doubleList.removeFirst();
map.remove(listNode.key);
}
// 若不存在,new一个出来
ListNode node = new ListNode(key, value);
map.put(key, node);
doubleList.addLast(node);
}
}
这里我们的实现为最近访问的放在链表的尾节点,不经常访问的放在链表的头节点
测试一波,输出为链表的正序输出(代码为了简洁没有贴toString方法)
MyLruCache<String, String> myLruCache = new MyLruCache<>(3);
// {5 : 5}
myLruCache.put("5", "5");
// {5 : 5}{3 : 3}
myLruCache.put("3", "3");
// {5 : 5}{3 : 3}{4 : 4}
myLruCache.put("4", "4");
// {3 : 3}{4 : 4}{2 : 2}
myLruCache.put("2", "2");
// {4 : 4}{2 : 2}{3 : 3}
myLruCache.get("3");
「因为LinkedHashMap的底层实现就是哈希表加双向链表,所以你可以用LinkedHashMap替换HashMap和DoubleList来改写一下上面的类」。
我来演示一下更骚的操作,只需要重写一个构造函数和removeEldestEntry方法即可。
使用LinkedHashMap实现LRU
public class LruCache<K, V> extends LinkedHashMap<K, V> {
private int cacheSize;
public LruCache(int cacheSize) {
/**
* initialCapacity: 初始容量大小
* loadFactor: 负载因子
* accessOrder: false基于插入排序(默认),true基于访问排序
*/
super(cacheSize, 0.75f, true);
this.cacheSize = cacheSize;
}
/**
* 当调用put或者putAll方法时会调用如下方法,是否删除最老的数据,默认为false
*/
@Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
return size() > cacheSize;
}
}
注意这个缓存并不是线程安全的,可以调用Collections.synchronizedMap方法返回线程安全的map
LruCache<String, String> lruCache = new LruCache(3);
Map<String, String> safeMap = Collections.synchronizedMap(lruCache);
Collections.synchronizedMap实现线程安全的方式很简单,只是返回一个代理类。代理类对Map接口的所有方法加锁
public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m) {
return new SynchronizedMap<>(m);
}
LFU算法
「LRU算法有一个问题,当一个长时间不被访问的key,偶尔被访问一下后,可能会造成一个比这个key访问更频繁的key被淘汰。」
即LRU算法对key的冷热程度的判断可能不准确。而LFU算法(Least Frequently Used,最不经常使用)则是按照访问频率来判断key的冷热程度的,每次删除的是一段时间内访问频率较低的数据,比LRU算法更准确。
使用3个hash表实现lfu算法
那么我们应该如何组织数据呢?
为了实现键值的对快速访问,用一个map来保存键值对
private HashMap<K, Integer> keyToFreq;
还需要用一个map来保存键的访问频率
private HashMap<K, Integer> keyToFreq;
「当然你也可以把值和访问频率封装到一个类中,用一个map来替代上述的2个map」
接下来就是最核心的部分,删除访问频率最低的数据。
- 为了能在O(1)时间复杂度内找到访问频率最低的数据,我们需要一个变量minFreq记录访问最低的频率
- 每个访问频率有可能对应多个键。当空间不够用时,我们要删除最早被访问的数据,所以需要如下数据结构,Map<频率, 有序集合>。每次内存不够用时,删除有序集合的第一个元素即可。并且这个有序集合要能快速删除某个key,因为某个key被访问后,需要从这个集合中删除,加入freq+1对应的集合中
- 有序集合很多,但是能满足快速删除某个key的只有set,但是set插入数据是无序的。「幸亏Java有LinkedHashSet这个类,链表和集合的结合体,链表不能快速删除元素,但是能保证插入顺序。集合内部元素无序,但是能快速删除元素,完美」
下面就是具体的实现。
public class LfuCache<K, V> {
private HashMap<K, V> keyToVal;
private HashMap<K, Integer> keyToFreq;
private HashMap<Integer, LinkedHashSet<K>> freqTokeys;
private int minFreq;
private int capacity;
public LfuCache(int capacity) {
keyToVal = new HashMap<>();
keyToFreq = new HashMap<>();
freqTokeys = new HashMap<>();
this.capacity = capacity;
this.minFreq = 0;
}
public V get(K key) {
V v = keyToVal.get(key);
if (v == null) {
return null;
}
increaseFrey(key);
return v;
}
public void put(K key, V value) {
// get方法里面会增加频次
if (get(key) != null) {
// 重新设置值
keyToVal.put(key, value);
return;
}
// 超出容量,删除频率最低的key
if (keyToVal.size() >= capacity) {
removeMinFreqKey();
}
keyToVal.put(key, value);
keyToFreq.put(key, 1);
// key对应的value存在,返回存在的key
// key对应的value不存在,添加key和value
freqTokeys.putIfAbsent(1, new LinkedHashSet<>());
freqTokeys.get(1).add(key);
this.minFreq = 1;
}
// 删除出现频率最低的key
private void removeMinFreqKey() {
LinkedHashSet<K> keyList = freqTokeys.get(minFreq);
K deleteKey = keyList.iterator().next();
keyList.remove(deleteKey);
if (keyList.isEmpty()) {
// 这里删除元素后不需要重新设置minFreq
// 因为put方法执行完会将minFreq设置为1
freqTokeys.remove(keyList);
}
keyToVal.remove(deleteKey);
keyToFreq.remove(deleteKey);
}
// 增加频率
private void increaseFrey(K key) {
int freq = keyToFreq.get(key);
keyToFreq.put(key, freq + 1);
freqTokeys.get(freq).remove(key);
freqTokeys.putIfAbsent(freq + 1, new LinkedHashSet<>());
freqTokeys.get(freq + 1).add(key);
if (freqTokeys.get(freq).isEmpty()) {
freqTokeys.remove(freq);
// 最小频率的set为空,key被移动到minFreq+1对应的set了
// 所以minFreq也要加1
if (freq == this.minFreq) {
this.minFreq++;
}
}
}
}
测试一下
LfuCache<String, String> lfuCache = new LfuCache(2);
lfuCache.put("1", "1");
lfuCache.put("2", "2");
// 1
System.out.println(lfuCache.get("1"));
lfuCache.put("3", "3");
// 1的频率为2,2和3的频率为1,但2更早之前被访问,所以被清除
// 结果为null
System.out.println(lfuCache.get("2"));
相关推荐
- 服务器温度监控--lm-sensors(服务器温度怎么看)
-
lm-sensors是一款linux的硬件监控的软件,可以帮助我们来监控主板,CPU的工作电压,风扇转速、温度等数据。这些数据我们通常在主板的BIOS也可以看到。当我们可以在机器运行的时候通过...
- MySQL版本区别及管理(mysql版本最新版本)
-
MySQL版本区别及管理一.MySQL5.6与MySQL5.7安装的区别1、cmake的时候加入了bostorg2、初始化时使用mysqld--initialize替代mysql_install...
- Linux技术问答系列-NO4(linux必知必会)
-
一.绝对路径用什么符号表示?当前目录、上层目录用什么表示?主目录用什么表示?切换目录用什么命令?绝对路径:如/etc/init.d当前目录和上层目录:./../主目录:~/切换目录:cd二...
- 猫盘原版系统开启ssh教程(猫盘原版系统怎么样)
-
猫盘是之前网上流传许久的矿渣,默认其系统不支持SSH功能,为了能打开其SSH功能,我特意制作操作教程如下:1、到网盘下载相关软件,利用猫盘系统自带功能,将assets放入个人存储目录下,并牢记对应的...
- 一探究竟——天融信网闸TopRules7000
-
网闸即:安全隔离与信息交换系统,常用作企业内外网隔离与业务互访用途。相比给服务器加多块网卡跨多个网段来说,网闸提供了更加安全的方式。探究背景:某次,网闸配置新业务,重启设备查看是否生效,结果发现刚重启...
- 操作系统加固通用Linux篇(linux系统加固常见操作)
-
1检查是否配置登陆超时时间设置编辑vi/etc/profile文件,配置TMOUT将值设置为低于300.TMOUT=3002检查是否禁止root用户登录FTP设置如下将对应配置文件中,设置roo...
- zabbix agent的安装与配置(zabbix-agent安装)
-
Agent安装rpm-ivhzabbix-agent-3.2.4-1.el6.x86_64.rpm安装完成后,zabbixagent端已经安装完成了,zabbixagent端的配置目录位于/e...
- Linux基础命令之计划任务(linux计划任务crontab)
-
一、计划任务1、at只能执行一次语法:at时间服务:atd必须开启123[root@xuegod163~]#/etc/init.d/atdstatus#查看服务状态atd(pid2...
- Secure Delivery Center (SDC)安装指南二:Delivery Hub
-
免费下载SecureDeliveryCenter2015>7月23日软件分发管理神器SecureDeliveryCenter免费技术交流会,MyEclipse原厂商倾力主讲,敬请关注!...
- OpenWrt 常用命令及用法!!(openwrt常用功能)
-
OpenWrt是一个高度可定制的嵌入式Linux操作系统,常用于路由器等网络设备。以下是一些常见的OpenWrt命令及其详细解释和示例操作:一、系统信息相关命令1.`uname-a``u...
- Linux 设置定时任务crontab命令(linux定时任务cron表达式)
-
看了同事的脚本,发现他用了cron来自检自身的那个程序是否崩溃了,这是有多大的不自信才用这种机制的?点击(此处)折叠或打开$sudocat/var/spool/cron/crontabs/ro...
- vCenter纳管ESXI主机出错(vsphere esxi)
-
vCenter纳管主机的大致步骤为:(1)vc和esxi交换证书,确立信任;(2)esxi把自己的资源信息同步到VC,VC建立清单。(3)VC在esxi建立几个操作用户;(4)然后下发...
- 从选购到安装 小白也能看懂的超全NAS经验分享
-
0.篇首语Hello大家好,我是KC,上一篇器材和工作流分享的文章里,有小伙伴问我怎么没有提到NAS?其实是因为前段时间碰巧更换了一台新NAS,折腾了一段时间很多内容还没来及整理和汇总,今天就...
- 手把手教你!如何在 Linux 服务器中搭建 Sentinel 环境?
-
你在Linux服务器上搭建Sentinel环境时,是不是也遇到过各种报错,要么是启动失败,要么是配置后无法正常访问控制台?看着同事顺利搭建好,自己却一头雾水,别提多着急了!其实,很多互联网大厂...
- 服务器被暴力破解的解决办法(二)(服务器被攻破严重吗)
-
上一次,我们说到小王公司服务器遭遇暴力破解,拿到解决方案回公司就开始部署。部署完成后的确起到了一定的效果,不过接下来的一个问题让他很头疼,原来黑客虽然攻入不进系统,但是依旧不依不饶的进行暴力破解。...
你 发表评论:
欢迎- 一周热门
-
-
极空间如何无损移机,新Z4 Pro又有哪些升级?极空间Z4 Pro深度体验
-
UOS服务器操作系统防火墙设置(uos20关闭防火墙)
-
如何修复用户配置文件服务在 WINDOWS 上登录失败的问题
-
手机如何设置与显示准确时间的详细指南
-
如何在安装前及安装后修改黑群晖的Mac地址和Sn系列号
-
日本海上自卫队的军衔制度(日本海上自卫队的军衔制度是什么)
-
爱折腾的特斯拉车主必看!手把手教你TESLAMATE的备份和恢复
-
10个免费文件中转服务站,分享文件简单方便,你知道几个?
-
FANUC 0i-TF数据备份方法(fanuc系统备份教程)
-
NAS:DS video/DS file/DS photo等群晖移动端APP远程访问的教程
-
- 最近发表
-
- 服务器温度监控--lm-sensors(服务器温度怎么看)
- MySQL版本区别及管理(mysql版本最新版本)
- Linux技术问答系列-NO4(linux必知必会)
- 猫盘原版系统开启ssh教程(猫盘原版系统怎么样)
- 一探究竟——天融信网闸TopRules7000
- 操作系统加固通用Linux篇(linux系统加固常见操作)
- zabbix agent的安装与配置(zabbix-agent安装)
- Linux基础命令之计划任务(linux计划任务crontab)
- Secure Delivery Center (SDC)安装指南二:Delivery Hub
- OpenWrt 常用命令及用法!!(openwrt常用功能)
- 标签列表
-
- linux 查询端口号 (58)
- docker映射容器目录到宿主机 (66)
- 杀端口 (60)
- yum更换阿里源 (62)
- internet explorer 增强的安全配置已启用 (65)
- linux自动挂载 (56)
- 禁用selinux (55)
- sysv-rc-conf (69)
- ubuntu防火墙状态查看 (64)
- windows server 2022激活密钥 (56)
- 无法与服务器建立安全连接是什么意思 (74)
- 443/80端口被占用怎么解决 (56)
- ping无法访问目标主机怎么解决 (58)
- fdatasync (59)
- 405 not allowed (56)
- 免备案虚拟主机zxhost (55)
- linux根据pid查看进程 (60)
- dhcp工具 (62)
- mysql 1045 (57)
- 宝塔远程工具 (56)
- ssh服务器拒绝了密码 请再试一次 (56)
- ubuntu卸载docker (56)
- linux查看nginx状态 (63)
- tomcat 乱码 (76)
- 2008r2激活序列号 (65)