Redis-内存淘汰策略

本文探讨了Redis的过期删除策略(定期与惰性)和淘汰机制(volatile、allkeys、LRU和TTL),并展示了如何用PHP实现LRU算法。还介绍了OpenResty多级缓存示例,涉及lua脚本和Redis与MySQL的配合使用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 过期删除策略

  1. 定期删除:在redis当中,默认是每隔100ms就随机抽取一些设置了过期时间的key,检查其是否过期,如果过期就删除。
  2. 惰性删除:redis在获取某个key的时候就会其进行检查,若这个key设置了过期时间,它就会进行判断是否过期,如果过期了此时就会删除,不会给你返回任何东西。

总结:定期删除是集中处理,惰性删除是零散处理。

2. 几种淘汰机制

  • volatile-random:从已设置过期时间的数据集中任意选择数据淘汰。
  • allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰。
  • volatile-lru:从已设置过期时间的数据集中挑选最近最少使用的数据淘汰。
  • allkeys-lru:从数据集中挑选最近最少使用的数据淘汰。
  • volatile-ttl:从已设置过期时间的数据集中挑选将要过期的数据淘汰。
  • no-enviction(驱逐):禁止驱逐数据,这也是默认策略。

3. PHP实现lru算法(lru淘汰策略实现)

  • ru算法是什么? 下图很好的解释了这一算法。
    在这里插入图片描述
  • 使用php实现lru策略
class LruNodeService
{
	private $key;
	//key对应的内容
	private $data;
	//结点右指针
	private $next;
	//结点左指针
	private $previous;
	
	/**
	* Node constructor.
	* @param $key
	* @param $data
	*/
	public function __construct($key, $data)
	{
		$this->key = $key;
		$this->data = $data;
	}
	
	/**
	* Sets a new value for the node data
	* @param string the new content of the node
	*/
	public function setData($data)
	{
		$this->data = $data;
	}
	
	/**
	* Sets a node as the next node
	* @param Node $next the next node
	*/
	public function setNext($next)
	{
		$this->next = $next;
	}
	
	/**
	* Sets a node as the previous node
	* @param Node $previous the previous node
	*/
	public function setPrevious($previous)
	{
		$this->previous = $previous;
	}
	
	/**
	* Returns the node key
	* @return string the key of the node
	*/
	public function getKey()
	{
		return $this->key;
	}
	
	/**
	* Returns the node data
	* @return mixed the content of the node
	*/
	public function getData()
	{
		return $this->data;
	}
	
	/**
	* Returns the next node
	* @return Node the next node of the node
	*/
	public function getNext()
	{
		return $this->next;
	}
	
	/**
	* Returns the previous node
	* @return Node the previous node of the node
	*/
	public function getPrevious()
	{
		return $this->previous;
	}
}
class LruService
{
	/*
	* 头部节点
	*/
	private $head;
	/*
	* 尾部节点
	*/
	private $tail;
	/*
	* 最大容量,大于淘汰部位节点指向上一个元素
	*/
	private $capacity;
	/*
	* 存储key对应的节点
	*/
	private $hashmap;
	
	public function __construct($capacity)
	{
		$this->capacity = $capacity;
		$this->hashmap = array();
		$this->head = new LruNodeService(null,null);
		$this->tail = new LruNodeService(null,null);
		$this->head->setNext($this->tail);
		$this->tail->setPrevious($this->head);
	}
	
	/**
	* 获取元素
	* @param $key
	* @return null
	*/
	public function get($key)
	{
		if (!isset($this->hashmap[$key])) {
			return null;
		}
		$node = $this->hashmap[$key];
		if (count($this->hashmap) == 1) {
			return $node->getData();
		}
		//先删除已经存在的结点
		$this->detach($node);
		//重新将新结点插入到头结点之后
		$this->attach($this->head, $node);
		return $node->getData();
	}
	
	/**
	* 设置key value
	* @param $key
	* @param $data
	* @return bool
	*/
	public function put($key, $data)
	{
		if ($this->capacity <= 0) {
			return false;
		}
		if (isset($this->hashmap[$key]) && !empty($this->hashmap[$key])) {
			$node = $this->hashmap[$key];
			//重置结点到头结点之后
			$this->detach($node);
			$this->attach($this->head, $node);
			$node->setData($data);
		} else {
			$node = new LruNodeService($key, $data);
			$this->hashmap[$key] = $node;
			//添加节点到头部节点之后
			$this->attach($this->head, $node);
			//检测容量是否达到最大值
			if (count($this->hashmap) > $this->capacity) {
				//如果达到最大值 删除尾节点左指针指向的元素
				$nodeToRemove = $this->tail->getPrevious();
				$this->detach($nodeToRemove);
				unset($this->hashmap[$nodeToRemove->getKey()]);
			}
		}
		return true;
	}
	
	/**
	* 删除key
	* @param $key
	* @return bool
	*/
	public function remove($key)
	{
		if (!isset($this->hashmap[$key])) {
			return false;
		}
		$nodeToRemove = $this->hashmap[$key];
		$this->detach($nodeToRemove);
		unset($this->hashmap[$nodeToRemove->getKey()]);
		return true;
	}
	
	/**
	* 添加新结点到头结点之后
	* @param $head
	* @param $node
	*/
	private function attach($head, $node)
	{
		//双向链表插入一个元素到头结点之后
		$node->setPrevious($head);
		$node->setNext($head->getNext());
		$node->getNext()->setPrevious($node);
		$node->getPrevious()->setNext($node);
	}
	
	/**
	* 删除结点
	* @param $node
	*/
	private function detach($node)
	{
		$node->getPrevious()->setNext($node->getNext());
		$node->getNext()->setPrevious($node->getPrevious());
	}
}

4. 多级缓存

4.1 Openresty安装

yum install readline-devel pcre-devel openssl-devel
wget https://openresty.org/download/ngx_openresty-1.9.7.1.tar.gz # 下载
tar xzvf ngx_openresty-1.9.7.1.tar.gz # 解压
cd ngx_openresty-1.9.7.1/
./configure
make
make install

4.2 使用Openresty创建lua脚本

ngx.header.content_type="application/json;charset=utf8"
local cjson = require("cjson") --引入模块
local mysql = require("resty.mysql") --引入mysql模块
local uri_args = ngx.req.get_uri_args()
local product_id = uri_args['product_id']
local db = mysql:new() --初始化数据库
db:set_timeout(1000) --设置超时时间
local props = {
	host = "192.168.59.100", --mysql ip地址
	port = 3306, --mysql 端口
	database = "shop", --mysql 数据库
	user = "root", password = "root" --用户名密码
}
local res = db:connect(props) --获得mysql连接
local select_sql = "select id,store from fm_products where id ='"..product_id.."'"--一条查询语句
res = db:query(select_sql) db:close() --执行
local redis = require("resty.redis") --引入redis
local red = redis:new() red:set_timeout(2000) --初始化 设置超时时间
local ip ="192.168.59.108"
local port = 6379
red:connect(ip,port)
red:auth("root")
red:set("product_"..product_id,cjson.encode(res)) --存储到redis
red:close()
ngx.say("{flag:true}")
ngx.header.content_type="application/json;charset=utf8"
local uri_args = ngx.req.get_uri_args()
local product_id = uri_args['product_id']
local cache_ngx = ngx.shared.dis_cache;
local adCache = cache_ngx:get('productcache'..product_id);
if adCache == "" or adCache == nil then
	local redis = require("resty.redis") --引入redis
	local red = redis:new() red:set_timeout(2000) --初始化 设置超时时间
	local ip ="192.168.59.108"
	local port = 6379
	red:connect(ip,port)
	red:auth("root")
	local rescontent=red:get("product_"..product_id)
	ngx.say(rescontent)
	red:close()
	cache_ngx:set('productcache'..product_id, rescontent, 10*60);
else
	ngx.say(adCache)
end
### Redis 内存淘汰策略详解 Redis 是一种基于内存的键值存储系统,其高效的内存管理和灵活的数据结构使其成为许多高并发场景下的首选解决方案。然而,在实际应用中,当 Redis 实例中的数据量超过可用内存容量时,就需要采用某种方式来释放空间以维持正常运行。这就是所谓的 **内存淘汰策略**。 #### 什么是内存淘汰策略内存淘汰策略是指在 Redis 中定义的一种机制,用于决定哪些数据可以在内存不足的情况下被移除或替换掉。这种策略的核心目标是在有限的内存资源下尽可能满足应用程序的需求并保持性能稳定性[^2]。 #### 常见的 Redis 内存淘汰策略 以下是 Redis 提供的主要内存淘汰策略及其特点: 1. **noeviction** - 当达到最大内存限制时,任何写入操作都会返回错误。 - 这是最安全的选择之一,因为它不会自动删除现有数据,适用于不允许丢失数据的应用场景[^1]。 2. **allkeys-lru (Least Recently Used)** - 删除最近最少使用的键,无论这些键是否有过期时间设置。 - 此策略适合于缓存类应用,其中较旧的数据可能不再重要[^3]。 3. **volatile-lru** - 只针对设置了过期时间的键执行 LRU 算法。 - 如果没有符合条件的候选者,则不采取行动。 4. **allkeys-random** - 随机选择一些键进行删除,而不考虑它们最后访问的时间戳或其他属性。 - 对某些特定类型的随机化处理非常有用。 5. **volatile-random** - 类似 allkeys-random ,但它仅作用于那些已经设定 TTL 的项目上。 6. **volatile-ttl** - 根据剩余存活时间优先级选取即将到期的对象予以清除。 - 特别适配那种希望尽快回收接近生命周期末端资料的情况。 7. **allkeys-lfu (Least Frequently Used)** 和 **volatile-lfu** - LFU 表示最不常使用的项会被最先驱逐出去;区别在于前者影响整个集合而后者限定范围内的元素。 #### 如何配置最佳策略? 为了实现最优效果,应该依据具体业务逻辑仔细挑选恰当的方法论,并且密切监视相关统计信息以便及时作出调整。例如可以利用可视化平台像 `RedisInsight` 或者开源监控框架如 Prometheus 来获取详细的性能报告和趋势分析图表。 ```bash # 设置 maxmemory-policy 参数指定淘汰算法 CONFIG SET maxmemory-policy volatile-lru ``` 上述命令展示了如何更改当前实例所遵循的规则为 “Volatile-LRU”。当然也可以通过修改 redis.conf 文件永久生效此改动。 #### 总结 每种淘汰方案都有各自的优势与局限性,因此需综合考量多方面要素之后再做定夺。务必记住定期审查现有的参数组合是否仍然契合最新的运营状况变化。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值