[深入解读LRU I]分桶式LRU

什么是LRU?

LRU是Least Recently Used的缩写,即最近最少使用。它的主要思想就是,将最近最少使用的数据,置换出容器内,一般是链表。

如果实现LRU?

一般实现的方法,一个映射加一个链表。

映射:是为了对应的键快速找到List中对应的值,一般是保存值的指针,对于STL,可以保存List的迭代器。

列表:自然就是保存实际值的数据结构, 这样方便将最近访问的数据,放到列表头,如果数据超出指定大小,则将列表尾置换出去。

整个结构如下图:

如何发现热数据?

回答这个问题前,首先需要知道什么是冷数据,怎样界定冷数据。

如果同一条数据,两次访问之间请求的时间大于数据缓存的超时时间,那则可以认为是冷数据。否则,则可以认为是热数据,只是热度不一样。

如果无论什么数据,都将其加入到LRU中,对于热数据,这是没有问题的。但是如果是冷数据,这就有问题了;对于冷数据,是不会命中缓存的,或者说是,命中缓存的概率极低。所以对于这种数据,不仅不能缓解数据库的压力,而且会消耗掉机器资源,加大了延迟。而且实际的应用中,往往热数据只占少数。

这就引申回到这个问题,如何发现热数据?

下面说一个常用的方法,令牌桶。我们知道,令牌桶,一般是解决限流的问题,但是在这里如果应用到发现热数据的呢?

原理就是,如果数据的访问单位时间访问的次数,触发了令牌桶的限制,则表示这是热数据,当然令牌桶的容量可以配置,单位时间内配置的容量越小,越容易触发限制。

如何不降低命中率的情况下,并行访问?

现阶段,为了增加程序的并行并发效率,一般使用线程来实现。如果使用了线程,访问共同的资源,一般都需要加锁。当然在所有的线程中分配相同的资源,这样就不需要竞争,自然就不需要加锁。但是同时就降低了命中率。很明显,一个线程访问的数据是热数据,但是将访问分摊到N个线程,就不一定了。同时N个线程,在缓存数据超时时,需要从数据库加载N次数据。

所以在不降低命中率,只有通过加锁来访问LRU的数据结构。但是如果减少资源竞争呢?答案就是减少锁的粒度;减少锁的粒度,可以通过分桶的方式。将不同的数据哈希到不同的桶;这样,只有相同的桶会发生资源竞争,不同的桶则可以并行访问。同时,桶的数量可以控制。

作者课堂:小明哥 - 主页

核心代码


#ifndef LRU_H
#define LRU_H

#include <mutex>
#include <list>
#include <map>
#include <algorithm>

class TokenBucket
{
public:
	TokenBucket(long cap) 
		: m_lastTime(0)
		, m_currTocken(cap)
		, m_cap(cap)
	{
	}
	~TokenBucket() {
	}

	//为了动态可变,需要从外部传入
	/*
	例如:rate:1000 cap:100
	每秒可以取100个令牌
	*/
	bool getToken(long rate, long timeNow, int num = 1)
	{
		long inc = long((timeNow - m_lastTime) * rate) / 1000;
		std::lock_guard<std::mutex> g(m_mutex);
		m_currTocken = std::min(m_currTocken + inc, m_cap);
		if (num > m_currTocken) {
			return false;
		}
		m_lastTime = timeNow;
		m_currTocken -= num;
		return true;
	}
private:
	long m_lastTime;
	long m_currTocken;
	long m_cap;
	std::mutex m_mutex;
};

template <class Key, class Value>
class LRUBucket
{
public:
	LRUBucket(long cap)
		: m_token(cap)
	{

	}

	~LRUBucket()
	{

	}

	bool get(const Key &k, Value &v, long timeNow, long lastTime, long rate, int num = 1)
	{
		if (m_token.getToken(rate, timeNow, num))
		{
			return false;
		}
		std::lock_guard<std::mutex> g(m_mutex);
		auto rItr = m_listInfo.rbegin();
		while (rItr != m_listInfo.rend())
		{
			if (rItr->time >= lastTime)
			{
				break;
			}
			m_mInfo.erase(rItr->key);
			rItr = typename std::list<LRUValue>::reverse_iterator(m_listInfo.erase((++rItr).base()));
		}

		auto itr = m_mInfo.find(k);
		if (itr != m_mInfo.end())
		{
			v = itr->second->value;
			return true;
		}

		return false;
	}

	void put(const Key &k, Value &v, long timeNow)
	{
		LRUValue lv;
		lv.key = k;
		lv.value = v;
		lv.time = timeNow;
		std::lock_guard<std::mutex> g(m_mutex);
		m_listInfo.emplace_front(lv);
		m_mInfo[k] = m_listInfo.begin();
		
	}

	size_t size()
	{
		return m_mInfo.size();
	}

private:
	struct LRUValue
	{
		Key key;
		Value value;
		long time;
	};

	std::mutex m_mutex;
	std::list<LRUValue> m_listInfo;
	std::map<Key, typename std::list<LRUValue>::iterator > m_mInfo;

	TokenBucket m_token;

};


// 如果传入的值是指针,目前暂时不支持回收

template <class Key, class Value>
class LocalCacheLocker
{
public:
	LocalCacheLocker(size_t size, long tokenCap)
		: m_tokenCap(tokenCap)
	{
		resize(size);
	}

	virtual ~LocalCacheLocker()
	{
		for (size_t i = 0; i < m_vecBucket.size(); ++i)
		{
			delete m_vecBucket[i];
		}

		m_vecBucket.clear();
	}

	bool get(const Key &k, Value&v, long timeNow, long lastTime, long rate, int num = 1)
	{
		return m_vecBucket[m_hashFunc(k) % m_vecBucket.size()]->get(k, v, timeNow, lastTime, rate, num);
	}

	void put(const Key &k, Value&v, long timeNow)
	{
		m_vecBucket[m_hashFunc(k) % m_vecBucket.size()]->put(k, v, timeNow);
	}

private:
	void resize(size_t size)
	{
		if (size < 1) size = 1;

		if (size > m_vecBucket.size())
		{
			size_t num = size - m_vecBucket.size();
			for (size_t i = 0; i < num; ++i)
			{
				m_vecBucket.emplace_back(new LRUBucket<Key, Value>(m_tokenCap));
			}
		}
		else
		{
			size_t num = m_vecBucket.size() - size;
			for (size_t i = 0; i < num; ++i)
			{
				delete m_vecBucket.back();
				m_vecBucket.pop_back();
			}
		}
	}
private:
	std::vector<LRUBucket<Key, Value> *> m_vecBucket;
	long m_tokenCap;
	std::hash<Key> m_hashFunc;
};


#endif

测试代码

#ifndef TEST_LRU_H
#define TEST_LRU_H

#include <algorithm>
#include <chrono>
#include <iostream>
#include <thread>
#include "lru.h"

long getNowMs()
{
	std::chrono::system_clock::duration d = std::chrono::system_clock::now().time_since_epoch();
	std::chrono::milliseconds mil = std::chrono::duration_cast<std::chrono::milliseconds>(d);
	return mil.count();
	// cout << min.count() << "分钟" << endl;
	// cout << sec.count() << "秒" << endl;
	// cout << mil.count() << "毫秒" << endl;
	// cout << mic.count() << "微妙" << endl;
	// cout << nan.count() << "纳秒" << endl;
}

struct Context
{
	Context():lru(10, 2){}
	LocalCacheLocker<int, int> lru;
};

void *Update(void * arg)
{
	Context *c = (Context *)arg;
	int v = 0;
	int hitTimes = 0;
	for (int i = 0; i < 100; ++i)
	{
		usleep(1000);
		auto nowMs = getNowMs();
		if (!c->lru.get(123, v, nowMs, nowMs - 1000, 100))
		{
			c->lru.put(123, i, nowMs);
		}
		else
		{
			hitTimes++;
		}
	}
	std::cout << hitTimes << std::endl;
	return NULL;
}

int main()
{
	Context c;
	std::thread *t[4];
	for (int i = 0; i < 4; ++i)
	{
		t[i] = new std::thread(::Update, &c);
	}
	
	for (int i = 0; i < 4; ++i)
	{
		t[i]->join();
	}
	return 0;
}
#endif

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值