LRU缓存设计-踩坑学习

        先简述下我今天遇到的问题。这边sdk登陆,并发量不小,然后用户登陆后可以进行共享登陆,主要业务描述就是,同一个公司的多个产品,一个产品登陆了,另外一个产品可以直接免登陆,这些登陆信息主要查询都是在缓存上的。

         现象是:用户在A产品登陆后,B产品点开刚开始可以查询到B的登陆信息,提示用户可以登陆,但是4个小时后,就查询不到了。刚开始以为是业务代码问题,由于是线上问题,实在无法debg什么的,当时也没打日志。在测试环境却始终模拟不出来这个问题,线上环境重现几率还挺大。

            层层排查后灵光一闪,会不会是Lru问题,一查线上memcached的free chunk,果然为0了,触发了memcached的lru,把我的缓存给干掉了,辛苦我找了半天问题,代码看了又看,望窗秋水啊。

      所以这边回顾下缓存设计当中,lru的知识。

-------------------------------------------



缓存的数据结构采用哈希表,key到value的映射。

网上有些资料采用记录数据的使用时刻 实现LRU策略,此处采用双向链表 实现LRU策略。LRU Least Recently Used,MRU Most Recently Used

双向链表,lruPtr头指向最近最少使用的元素,mruPtr头指向最近最多使用的元素。

LRUCache<int, int> tc(3);  //最大三个元素
tc.insert(1, 101);
tc.insert(2, 102);
tc.insert(3, 103);

最终存储结构如下图:

哈希表中的元素:

  • 黄色是 key域,哈希的键

  • 蓝色是value域,存储的数据值

  • 红色是newerPtr 域,指向下一个更新的 哈希项

  • 绿色是oldPtr域,指向前一个更旧的 哈希项

LRUCache缓存中 保存mruPtr和lruPtr,缓存的查找、更新元素 首先从hash_table中发起,然后同步更新到双向链表中。

LRU缓存设计-踩坑学习

lru.hpp

/*
* Implementation of an LRU cache with a maximum size.
*
* See http://code.google.com/p/lru-cache-cpp/ for usage and limitations.
*
* Licensed under the GNU LGPL: http://www.gnu.org/copyleft/lesser.html
*
* Pierre-Luc Brunelle, 2011
* [email protected]
*
* 使用stl中的map替换hash_table
* Peteryfren China, 2012
*/

#include <map>
#include <sstream>
#include <cassert>

namespace lru{

	//-------------------------------------------------------------
	// Bucket
	//-------------------------------------------------------------

	template<class K, class V>
	struct LRUCacheH4Value
	{
		typedef std::pair<const K, LRUCacheH4Value<K, V> > Val;

		LRUCacheH4Value()
			: _v(), _older(NULL), _newer(NULL) { }

		LRUCacheH4Value(const V & v, Val * older, Val * newer)
			: _v(v), _older(older), _newer(newer) { } 

		V _v;
		Val * _older;
		Val * _newer;
	};


	//-------------------------------------------------------------
	// Const Iterator
	//-------------------------------------------------------------

	template<class K, class V>
	class LRUCacheH4ConstIterator
	{
	public:
		typedef std::pair<const K, LRUCacheH4Value<K, V> > Val;
		typedef LRUCacheH4ConstIterator<K, V> const_iterator;
		typedef Val & reference;
		typedef Val * pointer;

		enum DIRECTION {
			MRU_TO_LRU = 0,
			LRU_TO_MRU
		};

		LRUCacheH4ConstIterator(const Val * ptr = NULL, DIRECTION dir = MRU_TO_LRU);

		const_iterator & operator++();
		const_iterator operator++(int);

		bool operator==(const const_iterator & other);
		bool operator!=(const const_iterator & other);

		const K & key() const;
		const V & value() const;

	private:
		const Val * _ptr;
		DIRECTION _dir;
	};


	template<class K, class V>
	LRUCacheH4ConstIterator<K, V>::LRUCacheH4ConstIterator(
		const typename LRUCacheH4ConstIterator<K, V>::Val * ptr,
		typename LRUCacheH4ConstIterator<K, V>::DIRECTION dir)
		: _ptr(ptr), _dir(dir)
	{
	}


	template<class K, class V>
	LRUCacheH4ConstIterator<K, V> & LRUCacheH4ConstIterator<K, V>::operator++()
	{
		assert(_ptr);
		_ptr = (_dir == LRUCacheH4ConstIterator<K, V>::MRU_TO_LRU ? _ptr->second._older : _ptr->second._newer);
		return *this;
	}


	template<class K, class V>
	LRUCacheH4ConstIterator<K, V> LRUCacheH4ConstIterator<K, V>::operator++(int)
	{
		const_iterator ret = *this;
		++*this;
		return ret;
	}


	template<class K, class V>
	bool LRUCacheH4ConstIterator<K, V>::operator==(const const_iterator & other)
	{
		return _ptr == other._ptr;
	}


	template<class K, class V>
	bool LRUCacheH4ConstIterator<K, V>::operator!=(const const_iterator & other)
	{
		return _ptr != other._ptr;
	}


	template<class K, class V>
	const K & LRUCacheH4ConstIterator<K, V>::key() const
	{
		assert(_ptr);
		return _ptr->first;
	}


	template<class K, class V>
	const V & LRUCacheH4ConstIterator<K, V>::value() const
	{
		assert(_ptr); 
		return _ptr->second._v;
	}


} // file scope


namespace lru {

	//-------------------------------------------------------------
	// LRU Cache
	//-------------------------------------------------------------

	template<class K, class V>
	class LRUCacheH4
	{
	public:
		typedef LRUCacheH4ConstIterator<K, V> const_iterator;

	public:
		LRUCacheH4(int maxsize);                    // Pre-condition: maxsize >= 1
		LRUCacheH4(const LRUCacheH4 & other);
		~LRUCacheH4() { _map.clear(); }

		V & operator[](const K & key);
		void insert(const K & key, const V & value);

		int size() const;
		int maxsize() const;
		bool empty() const;

		const_iterator find(const K & key);         // updates the MRU
		const_iterator find(const K & key) const;   // does not update the MRU
		const_iterator mru_begin() const;           // from MRU to LRU
		const_iterator lru_begin() const;           // from LRU to MRU
		const_iterator end() const;

		void dump_mru_to_lru(std::ostream & os) const;

	private:
		typedef std::pair<const K, LRUCacheH4Value<K, V> > Val;

		typedef std::map<K, LRUCacheH4Value<K,V> > MAP_TYPE;

	private:
		Val * _update_or_insert(const K & key);
		Val * _update(typename MAP_TYPE::iterator it);
		Val * _insert(const K & key);

	private:
		MAP_TYPE _map;
		Val * _mru;
		Val * _lru;
		int _maxsize;
	};


	// Reserve enough space to avoid resizing later on and thus invalidate iterators
	template<class K, class V>
	LRUCacheH4<K, V>::LRUCacheH4(int maxsize)
		: _mru(NULL),
		_lru(NULL),
		_maxsize(maxsize)
	{
		if (_maxsize <= 0)
			throw "LRUCacheH4: expecting cache size >= 1";
	}


	template<class K, class V>
	LRUCacheH4<K, V>::LRUCacheH4(const LRUCacheH4<K, V> & other)
		: _maxsize(other._maxsize),
		_mru(NULL),
		_lru(NULL)
	{
		for (const_iterator it = other.lru_begin();  it != other.end();  ++it)
			this->insert(it.key(), it.value());
	}


	template<class K, class V>
	V & LRUCacheH4<K, V>::operator[](const K & key)
	{
		return _update_or_insert(key)->second._v;
	}


	template<class K, class V>
	void LRUCacheH4<K, V>::insert(const K & key, const V & value)
	{
		_update_or_insert(key)->second._v = value;
	}


	template<class K, class V>
	int LRUCacheH4<K, V>::size() const
	{
		return _map.size();
	}


	template<class K, class V>
	int LRUCacheH4<K, V>::maxsize() const 
	{
		return _maxsize;
	}


	template<class K, class V>
	bool LRUCacheH4<K, V>::empty() const
	{
		return size() > 0;
	}


	// updates MRU
	template<class K, class V>
	typename LRUCacheH4<K, V>::const_iterator LRUCacheH4<K, V>::find(const K & key)
	{
		typename MAP_TYPE::iterator it = _map.find(key);

		if (it != _map.end())
			return const_iterator(_update(it), const_iterator::MRU_TO_LRU);
		else
			return end();
	}


	// does not update MRU
	template<class K, class V>
	typename LRUCacheH4<K, V>::const_iterator LRUCacheH4<K, V>::find(const K & key) const
	{
		typename MAP_TYPE::iterator it = _map.find(key);

		if (it != _map.end())
			return const_iterator(&*it, const_iterator::MRU_TO_LRU);
		else
			return end();
	}


	template<class K, class V>
	void LRUCacheH4<K, V>::dump_mru_to_lru(std::ostream & os) const
	{
		os << "LRUCacheH4(" << size() << "/" << maxsize() << "): MRU --> LRU: " << std::endl;
		for (const_iterator it = mru_begin();  it != end();  ++it)
			os << it.key() << ": " << it.value() << std::endl;
	}


	template<class K, class V>
	typename LRUCacheH4<K, V>::const_iterator LRUCacheH4<K, V>::mru_begin() const
	{
		return const_iterator(_mru, const_iterator::MRU_TO_LRU);
	}


	template<class K, class V>
	typename LRUCacheH4<K, V>::const_iterator LRUCacheH4<K, V>::lru_begin() const
	{
		return const_iterator(_lru, const_iterator::LRU_TO_MRU);
	}


	template<class K, class V>
	typename LRUCacheH4<K, V>::const_iterator LRUCacheH4<K, V>::end() const
	{
		return const_iterator();
	}


	template<class K, class V>
	typename LRUCacheH4<K, V>::Val * LRUCacheH4<K, V>::_update_or_insert(const K & key)
	{
		typename MAP_TYPE::iterator it = _map.find(key);
		if (it != _map.end())
			return _update(it);
		else
			return _insert(key);
	}


	template<class K, class V>
	typename LRUCacheH4<K, V>::Val * LRUCacheH4<K, V>::_update(typename MAP_TYPE::iterator it)
	{
		LRUCacheH4Value<K, V> & v = it->second;
		Val * older = v._older;
		Val * newer = v._newer;
		Val * moved = &*it;

		// possibly update the LRU
		if (moved == _lru && _lru->second._newer)
			_lru = _lru->second._newer;

		if (moved != _mru) {
			// "remove" key from current position
			if (older)
				older->second._newer = newer;
			if (newer)
				newer->second._older = older;

			// "insert" key to MRU position
			v._older = _mru;
			v._newer = NULL;
			_mru->second._newer = moved;
			_mru = moved;
		}

		return moved;
	}


	template<class K, class V>
	typename LRUCacheH4<K, V>::Val * LRUCacheH4<K, V>::_insert(const K & key)
	{
		// if we have grown too large, remove LRU
		if (_map.size() >= _maxsize) {
			Val * old_lru = _lru;
			if (_lru->second._newer) {
				_lru = _lru->second._newer;
				_lru->second._older = NULL;
			}
			_map.erase(old_lru->first);
		}

		// insert key to MRU position
 		std::pair<typename MAP_TYPE::iterator, bool> ret
 			= _map.insert( Val(key, LRUCacheH4Value<K, V>(V(), _mru, NULL)) );

		Val * inserted = &*ret.first;
		if (_mru)
			_mru->second._newer = inserted;
		_mru = inserted;

		// possibly update the LRU
		if (!_lru)
			_lru = _mru;
		else if (!_lru->second._newer)
			_lru->second._newer = _mru;

		return inserted;
	}


}  // namespace lru


测试代码:

#include <iostream>

#include "lru.hpp"

using namespace lru;
using namespace std;

int main()
{
	typedef LRUCacheH4<int, int> CacheType;

	CacheType tc(3);

	tc.insert(1, 101);
	tc.insert(2, 102);
	tc.insert(3, 103);
	
	tc.insert(2, 1002);

	cout << tc[1] << endl;

	cout << "================" << endl;

	for (CacheType::const_iterator it = tc.mru_begin();  it != tc.end();  ++it)
		cout << it.key() << " " << it.value() << endl;

	cout << "================" << endl;

	for (CacheType::const_iterator it = tc.lru_begin();  it != tc.end();  ++it)
		cout << it.key() << " " << it.value() << endl;

	system("PAUSE");
	return 0;
}

参考:

1. High-Throughput, Thread-Safe, LRU Caching
http://www.ebaytechblog.com/2011/08/30/high-throughput-thread-safe-lru-caching/



版权声明:本文为博主原创文章,未经博主允许不得转载。

你可能感兴趣的:(数据结构,null,table,iterator,Class,insert)