c++ 实现 key-value缓存数据结构
c++ 实现 key-value缓存数据结构
概述
最近在阅读Memcached的源代码,今天借鉴部分设计思想简单的实现了一个keyvalue缓存。
哈希表部分使用了unordered_map,用于实现LRU算法的双向链表嵌套在缓存类中实现。
LRU 算法
LRU算法又称为last least used 算法,是用于在缓冲区内存不足的情况下进行内存替换调度的算法,出于局部性原理,我们会将缓存中上一次使用时间最久远的元素删除,在这里我的实现算法如下:
将hash表中存储的数据地址(实现形式为存储数据类型的指针)用双向链表的形式存储,在一个元素被更新或者插入的时候会将该元素从链表中取出重新添加到链表头部,在LRU调度时,只需要将链表尾部的元素删除即可。
存储元素
对存储元素类的数据结构设计如下: Data作为粒度最小的数据单位存储,然而由于
template<typename K, typename V>
struct Data {
explicit Data(const K& k, const V& v) :key(k), val(v) {
}
K key;
V val;
};
数据结构设计
- 数据的存取基于哈希表来实现
为了照顾代码可读性,在这里使用了unordered_map。
链表节点实现粒度的考虑
- 双向链表
首先链表是通过包装Data形成一个双向链表节点实现。- 为什么不能使用std::list?
在使用的粒度上std::list和此处的应用场景不同,考虑如下场合:通过get来查询哈希表中一个元素,此时由于这个元素被使用到了,应该从LRU链表中取出然后添加到链表头,如果使用std::list是难以实现的。因为它将list_node封装起来调用,我们无法通过哈希表中元素快速定位到链表中的迭代器位置。- 具体实现方式
实现一个类似list_node节点来进行存储,链表在缓存中以头节点的形式存储。
双线链表的实现 可参考:
http://www.cnblogs.com/joeylee97/p/8549835.html
链表节点结构
- 为什么通过指向const Data类型的shrared_ptr来存储数据?
- Reason 1:
Item之间的拷贝应该是轻量级的,这样能够提高存取性能- Reason 2:
在高并发情况下,const Data的智能指针便于内存管理,而且可以减小锁的粒度。
详细场景分析:在高并发情况下,此缓冲数据结构作为服务器端存储来使用,一个缓存区中数据应该怎样在读取时加锁?
如果仅仅在取数据时期加锁,那么要做大量拷贝(从数据结构中拷贝到栈或者其他变量中),然后调用socket进行发送。
如果我们在发送期间全程加锁,不仅效率极低,而且容易死锁。
在这里我给出的方案是通过shared_ptr类型在读取时加锁,在发送时直接通过指针来读取数据内容(使用const Data)来避免线程之间读写冲突。
template<typename K, typename V>
struct Item {
typedef Item<K, V>* Itemptr;
typedef shared_ptr<const Data<K, V>> MData;
//for head node
Item(){}
explicit Item(const K& k, const V& v) : nxt(nullptr), pre(nullptr) {
data = make_shared<Data<K, V>>(k, v);
}
//this should be a light weighted copy method since all its elems are ptr_type
Item(const Item& rhs) = default;
Item& operator=(const Item& rhs) = default;
//删除该元素
void detachFromList() {
Itemptr this_pre = pre, this_nxt = nxt;
this_pre->nxt = this_nxt;
this_nxt->pre = this_pre;
pre = nxt = nullptr; //In case this Item is reused
}
//加到该节点后面
void appendAftHead(Itemptr head) {
head->nxt->pre = this;
nxt = head->nxt;
head->nxt = this;
pre = head;
}
//for light copy and concurency
shared_ptr<const Data<K, V>> data;
Itemptr nxt;
Itemptr pre;
};
源码分析
哈希表接口
使用类应该通过模板偏特化来实现这两个接口
template<class T>
struct Hash {
size_t operator()(const T&) const;
};
template<class T>
struct Equal {
bool operator()(const T& lhs, const T& rhs) const;
};
Cache
template<class K, class V>
class Cache {
public:
typedef Item<K, V> MItem;
typedef shared_ptr<const Data<K, V>> MData;
typedef shared_ptr<const Data<K, V>> Dataptr;
typedef unordered_map<K, MItem, Hash<K>, Equal<K>> Table;
//对头节点初始化
explicit Cache(size_t capacity) :table(), head(), siz(0),cap(capacity) {
head.nxt = &head, head.pre = &head;
}
//禁止拷贝
Cache(const Cache&) = delete;
Cache& operator=(const Cache&) = delete;
std::pair<bool, Dataptr> get(const K& key) {
auto it = table.find(key);
if (it != table.end()) {
auto val = it->second.data->val;
it->second.detachFromList();
it->second.appendAftHead(&head);//调整到LRU首端
return { true, it->second.data };
}
else {
return { false, Dataptr() };
}
}
void put(const K& key, const V& val) {
auto it = table.find(key);
if (it != table.end()) {
it->second.detachFromList();
table.erase(it);
auto p = table.insert({ key, MItem(key, val) });
p.first->second.appendAftHead(&head);
}
else {
if (siz == cap) {
deleteLru();
}
auto p = table.insert({ key, MItem(key, val) }); //insert
p.first->second.appendAftHead(&head);
siz++;
}
}
bool del(const K& key) {
auto it = table.find(key);
if (it == table.end()) {
return false;
}
else {
it->second.detachFromList();
table.erase(it);
siz--;
return true;
}
}
private:
//delete least recently used item
void deleteLru() {
MItem* lru = head.pre;
if (lru != &head) {
del(lru->data->key);
}
}
size_t cap;
size_t siz;
Table table;
MItem head;
};
设计的缺陷以及优化方向
首先Memcached 的数据结构是C语言定制的,所以在哈希表上性能会更突出,举个例子
void deleteLru() {
MItem* lru = head.pre;
if (lru != &head) {
del(lru->data->key);
}
}
在这个删除LRU链表尾部元素的操作过程中,我们由于不能从链表直接定位到哈希表,所以要有一个 o nlogn的查询操作,在定制化的数据结构中这个是O 1 的
set/map?
细心的读者会注意到,在hash_map中我们的key被存储了两次(一次在map_pair节点,一次在Item中),可以使用unordered_set 来存储Item,不过这样每次使用key都要进行一次类型组装(从key到Item),在时间上性能会下降,但是会节省空间。
c++ 实现 key-value缓存数据结构的更多相关文章
- ATS缓存数据结构
ATS缓存数据结构 HttpTunnel类 数据传输驱动器(data transfer driver),包含一个生产者(producer)集合,每个生产者连接到一个或是多个消费者(comsumer). ...
- redis 一二事 - 设置过期时间,以文件夹形式展示key显示缓存数据
在使用redis时,有时回存在大量数据的时候,而且分类相同,ID相同 可以使用hset来设置,这样有一个大类和一个小分类和一个value组成 但是hset不能设置过期时间 过期时间只能在set上设置 ...
- 第三节:Redis缓存雪崩、击穿、穿透、双写一致性、并发竞争、热点key重建优化、BigKey的优化 等解决方案
一. 缓存雪崩 1. 含义 同一时刻,大量的缓存同时过期失效. 2. 产生原因和后果 (1). 原因:由于开发人员经验不足或失误,大量热点缓存设置了统一的过期时间. (2). 产生后果:恰逢秒杀高峰, ...
- 经典面试题:分布式缓存热点KEY问题如何解决--有赞方案
有赞透明多级缓存解决方案(TMC) 一.引子 1-1. TMC 是什么 TMC ,即"透明多级缓存( Transparent Multilevel Cache )",是有赞 Paa ...
- (转载)遍历memcache中已缓存的key
(转载)http://www.cnblogs.com/ainiaa/archive/2011/03/11/1981108.html 最近需要做一个缓存管理的功能.其中有一个需要模糊匹配memcache ...
- 缓存击穿、缓存失效及热点key的解决方案
分布式缓存是网站服务端经常用到的一种技术,在读多写少的业务场景中,通过使用缓存可以有效地支撑高并发的访问量,对后端的数据库等数据源做到很好地保护.现在市面上有很多分布式缓存,比如Redis.Memca ...
- Redis缓存雪崩,缓存穿透,热点key解决方案和分析
缓存穿透 缓存系统,按照KEY去查询VALUE,当KEY对应的VALUE一定不存在的时候并对KEY并发请求量很大的时候,就会对后端造成很大的压力. (查询一个必然不存在的数据.比如文章表,查询一个不存 ...
- Key/Value之王Memcached初探:二、Memcached在.Net中的基本操作
一.Memcached ClientLib For .Net 首先,不得不说,许多语言都实现了连接Memcached的客户端,其中以Perl.PHP为主. 仅仅memcached网站上列出的语言就有: ...
- CRL快速开发框架系列教程五(使用缓存)
本系列目录 CRL快速开发框架系列教程一(Code First数据表不需再关心) CRL快速开发框架系列教程二(基于Lambda表达式查询) CRL快速开发框架系列教程三(更新数据) CRL快速开发框 ...
随机推荐
- 《深入理解Java虚拟机》读书笔记
堆分配参数: -XX:+PrintGC 使用该参数,虚拟机启动后,只要遇到GC就会打印日志: -XX:+UseSerialGC 配置串行回收器: -XX:+PrintGCDeltails 可以查看详细 ...
- Android-Emulator使用
1.查看当前android支持的avd版本 2.创建Emulator avd: android create avd -n magicyu -t 2 -n后面接需要创建avd的名字,-t后面接需要创建 ...
- PowerBI 应用时间智能(生成日期表)
简介 Power BI Desktop -是一款由微软发布的自助式商业智能工具,功能强大.易于使用.其中还可以通过微软云连多个数据源并且使用数据源来创建可视化表盘. 但是几乎所有的BI都需要展示如何随 ...
- 备忘录:python 3在class中使用yield
之前代码都是直接在函数级别使用yield,但封装class后如何使用yield很少遇到. 经过半天的学习,总算完成示例.代码没有什么特殊地方,仅仅作为一个工作项. 与生成器合作: ########## ...
- python3 进程与线程
1.进程定义 狭义的定义: 进程是正在运行的程序的实例. 广义的定义:进程是一个具有 一定独立功能的程序关于某个数据集合的一次运行活动.它可以申请和拥有系统资源,是一个动态的概念,是一个活动的实体.它 ...
- Django 你需要了解的入门操作
创建一个django project (我的版本是1.11.11) django-admin startproject mysite cd mysite 当前目录下会生成mysite的工程,目录结 ...
- shift Alt + up(down) copy current line ! ctrl + j show the control # vscode key
shift Alt + up(down) copy current line ! ctrl + j show the control # vscode key
- tree iview treeData json数据 添加 selected 数据 要进行vue.set 进行响应式添加
tree iview treeData json数据 添加 selected 数据 要进行vue.set 进行响应式添加
- 如何快速上手基础的CSS3动画
前言 说起CSS3动画,就必须说说 transform,translate,transition,animation这4个属性,transform主要定义元素的动作,比如旋转.倾斜.位移等,trans ...
- go语言碎片整理之标准库log
log Go语言内置的log包实现了简单的日志服务.本文介绍了标准库log的基本使用. 使用Logger log包定义了Logger类型,该类型提供了一些格式化输出的方法.本包也提供了一个预定义的“标 ...