LRU-K
算法是LRU
的改进版,目的是为某个数据的访问次数超过K
次则放到热数据里面,为此做出了以下设计:
- 采用了
redis
的链表与字典 - 将部分资料中的排序换成了普通的
LRU
,因为使用排序可能实现起来存在性能问题。
redis
的链表与字典,可以参考博客。
plantUML
的流程图源码如下:
@startuml LRU-K
title LRU-K
start
:调用缓存对外读接口;
if (stHotData数据查找 ?) then(stHotData存在)
:将stHotData中的数据从stLi放到链表头;
else (stHotData不存在)
if (stAccessHistory数据查找 ?) then (stAccessHistory存在)
if (stAccessHistory的访问次数 == ulAccessThresh - 1 ?) then(访问次数 == ulAccessThresh - 1)
:将数据的访问次数置为ulAccessThresh;
:删除stAccessHistory.stLi的链表尾部元素;
:删除stAccessHistory的stKV的映射;
:将数据添加置stHotData的stLi中;
:将数据的Key-Value映射添加至stHotData的stKV;
else (访问次数 < ulAccessThresh - 1)
:访问次数加1;
endif
else (stAccessHistory不存在)
if (stAccessHistory的元素个数 >= ulHistoryMaxNum ?) then(>= ulHistoryMaxNum)
:删除stAccessHistory.stLi的链表尾部元素;
:删除stAccessHistory的stKV的映射;
else (< ulHistoryMaxNum)
:ignore;
endif
:调用业务API获取数据;
:将数据的访问次数置为1;
:将数据添加至stAccessHistory的stLi首部;
:将数据的Key-Value映射添加至stAccessHistory的stKV中;
endif
endif
:返回数据;
end
@enduml
流程图如下:
部分代码的结构体如下:
typedef struct LRU_HISTORY_VAL_S
{
void *pVal; /* Value数据地址 */
unsigned int ulAccessCnt; /* 访问次数 */
}LRU_HISTORY_VAL_S;
typedef struct LRU_ACCESS_HISTORY_S
{
dict stKV; /* 历史记录的Key-Value */
list stLi;
unsigned int ulHistoryMaxNum; /* 访问历史过多需要淘汰的阈值 */
unsigned int ulAccessThresh; /* 访问记录的阈值 */
}LRU_ACCESS_HISTORY_S;
typedef struct LRU_K_HOT_DATA_S
{
dict stKV; /* 缓存,用来查找 */
list stLi; /* 链表,用来淘汰数据 */
unsigned int ulMaxElemNum; /* 热点数据最大元素个数 */
}LRU_K_HOT_DATA_S;
typedef struct LRU_K_CACHE_S
{
pthread_rw_lock_t stLock; /* 锁,多线程 */
LRU_ACCESS_HISTORY_S stAccessHistory;
LRU_K_HOT_DATA_S stHotData;
}LRU_K_CACHE_S;
参考资料如下:
https://www.jianshu.com/p/d533d8a66795
https://www.iteye.com/blog/flychao88-1977653