- VisualStudio2022插件的安装及使用-编程手把手系列文章
- pprof-在现网场景怎么用
- C#实现的下拉多选框,下拉多选树,多级节点
- 【学习笔记】基础数据结构:猫树
Redis本质上是一个Key-Value类型的内存数据库,整个数据库加载在内存当中操作,定期通过异步操作把数据库中的数据flush到硬盘上进行保存.
因为是纯内存操作,Redis的性能非常出色,每秒可以处理超过 10万次读写操作,是已知性能最快的Key-Value 数据库.
Redis的底层请见 https://www.bozhu12.cc/backend/redis2/#_1-前言 这篇文章 讲的非常详细 。
redis 内部使用文件事件处理器 file event handler,它是单线程的,所以redis才叫做单线程模型。它采用IO多路复用机制同时监听多个 socket,将产生事件的 socket 压入内存队列中,事件分派器根据 socket 上的事件类型来选择对应的事件处理器进行处理.
文件事件处理器的结构:
SET key1 value1
请求。key1
设置为 value1
。OK
结果发送回客户端 A。GET key1
请求。key1
的值,得到 value1
。value1
返回给客户端 B。SET key2 value2
请求。key2
设置为 value2
。OK
结果返回给客户端 C。SET key1 value1
后,立即将 key1
设置为 value1
,并返回 OK
。GET key1
,主线程查询后,立即将查询结果 value1
发送给客户端 B。多线程机制 。
假设有 3 个客户端同时向 Redis 发送请求:
SET key1 value1
GET key1
SET key2 value2
SET key1 value1
的请求。GET key1
的请求。SET key2 value2
的请求。SET key1 value1
,将 key1
设置为 value1
。GET key1
,读取并返回 key1
的值(value1
)。SET key2 value2
,将 key2
设置为 value2
。OK
响应返回给客户端 A。value1
返回给客户端 B。OK
返回给客户端 C。Redis 内存淘汰执行流程如下:
1.每次当 Redis 执行命令时,若设置了最大内存大小 maxmemory,并设置了淘汰策略式,则会尝试进行一次 Key 淘汰; 。
2.Redis 首先会评估已使用内存(这里不包含主从复制使用的两个缓冲区占用的内存)是否大于 maxmemory,如果没有则直接返回,否则将计算当前需要释放多少内存,随后开始根据策略淘汰符合条件的 Key;当开始进行淘汰时,将会依次对每个数据库进行抽样,抽样的数据范围由策略决定,而样本数量则由 maxmemory-samples配置决定; 。
3.完成抽样后,Redis 会尝试将样本放入提前初始化好 EvictionPoolLRU 数组中,它相当于一个临时缓冲区,当数组填满以后即将里面全部的 Key 进行删除.
4.若一次删除后内存仍然不足,则再次重复上一步骤,将样本中的剩余 Key 再次填入数组中进行删除,直到释放了足够的内存,或者本次抽样的所有 Key 都被删除完毕(如果此时内存还是不足,那么就重新执行一次淘汰流程).
在抽样这一步,涉及到从字典中随机抽样这个过程,由于哈希表的 Key 是散列分布的,因此会有很多桶都是空的,纯随机效率可能会很低。因此,Redis 采用了一个特别的做法,那就是先连续遍历数个桶,如果都是空的,再随机调到另一个位置,再连续遍历几个桶……如此循环,直到结束抽样.
你可以参照源码理解这个过程:
unsigned int dictGetSomeKeys(dict *d, dictEntry **des, unsigned int count) {
unsigned long j; /* internal hash table id, 0 or 1. */
unsigned long tables; /* 1 or 2 tables? */
unsigned long stored = 0, maxsizemask;
unsigned long maxsteps;
if (dictSize(d) < count) count = dictSize(d);
maxsteps = count*10;
// 如果字典正在迁移,则协助迁移
for (j = 0; j < count; j++) {
if (dictIsRehashing(d))
_dictRehashStep(d);
else
break;
}
tables = dictIsRehashing(d) ? 2 : 1;
maxsizemask = d->ht[0].sizemask;
if (tables > 1 && maxsizemask < d->ht[1].sizemask)
maxsizemask = d->ht[1].sizemask;
unsigned long i = random() & maxsizemask;
unsigned long emptylen = 0;
// 当已经采集到足够的样本,或者重试已达上限则结束采样
while(stored < count && maxsteps--) {
for (j = 0; j < tables; j++) {
if (tables == 2 && j == 0 && i < (unsigned long) d->rehashidx) {
if (i >= d->ht[1].size)
i = d->rehashidx;
else
continue;
}
// 如果一个库的到期字典已经处理完毕,则处理下一个库
if (i >= d->ht[j].size) continue;
dictEntry *he = d->ht[j].table[i];
// 连续遍历多个桶,如果多个桶都是空的,那么随机跳到另一个位置,然后再重复此步骤
if (he == NULL) {
emptylen++;
if (emptylen >= 5 && emptylen > count) {
i = random() & maxsizemask;
emptylen = 0;
}
} else {
emptylen = 0;
while (he) {
*des = he;
des++;
he = he->next;
stored++;
if (stored == count) return stored;
}
}
}
// 查找下一个桶
i = (i+1) & maxsizemask;
}
return stored;
}
LRU 的全称为 Least Recently Used,也就是最近最少使用。一般来说,LRU 会从一批 Key 中淘汰上次访问时间最早的 key.
它是一种非常常见的缓存回收算法,在诸如 Guava Cache、Caffeine等缓存库中都提供了类似的实现。我们自己也可以基于 JDK 的 LinkedHashMap 实现支持 LRU 算法的缓存功能。 2.1 近似 LRU 传统的 LRU 算法实现通常会维护一个链表,当访问过某个节点后就将该节点移至链表头部。如此反复后,链表的节点就会按最近一次访问时间排序。当缓存数量到达上限后,我们直接移除尾节点,即可移除最近最少访问的缓存。 不过,对于 Redis 来说,如果每个 Key 添加的时候都需要额外的维护并操作这样一条链表,要额外付出的代价显然是不可接受的,因此 Redis 中的 LRU 是近似 LRU(NearlyLRU).
当每次访问 Key 时,Redis 会在结构体中记录本次访问时间,而当需要淘汰 Key 时,将会从全部数据中进行抽样,然后再移除样本中上次访问时间最早的 key.
它的特点是:
仅当需要时再抽样,因而不需要维护全量数据组成的链表,这避免了额外内存消耗.
访问时仅在结构体上记录操作时间,而不需要操作链表节点,这避免了额外的性能消耗.
当然,有利就有弊,这种实现方式也决定 Redis 的 LRU 是并不是百分百准确的,被淘汰的 Key 未必真的就是所有 Key 中最后一次访问时间最早的.
2.2 抽样大小 根据上述的内容,我们不难理解,当抽样的数量越大,LRU 淘汰 Key 就越准确,相对的开销也更大。因此,Redis 允许我们通过 maxmemory-samples 配置采样数量(默认为 5),从而在性能和精度上取得平衡.
LFU 全称为 Least Frequently Used ,也就是最近最不常用。它的特点如下:
同样是基于抽样实现的近似算法,maxmemory-samples 对其同样有效.
比较的不是最后一次访问时间,而是数据的访问频率。当淘汰的时候,优先淘汰范围频率最低 Key.
它的实现与 LRU 基本一致,但是在计数部分则有所改进.
3.1 概率计数器 在 Redis 用来存储数据的结构体 redisObj 中,有一个 24 位的 lru数值字段:
当使用 LRU 算法时,它用于记录最后一次访问时间的时间戳.
当使用 LFU 算法时,它被分为两部分,高 16 位关于记录最近一次访问时间(Last Decrement Time),而低 8 位作为记录访问频率计数器(Logistic Counter).
LFU 的核心就在于低 8 位表示的访问频率计数器(下面我们简称为 counter),是一个介于 0 ~ 255 的特殊数值,它会每次访问 Key 时,基于时间衰减和概率递增机制动态改变.
| 这种基于概率,使用极小内存对大量事件进行计数的计数器被称为莫里斯计数器,它是一种概率计数法的实现.
3.2 时间衰减 每当访问 Key 时,根据当前实际与该 Key 的最后一次访问时间的时间差对 counter 进行衰减.
衰减值取决于 lfu_decay_time 配置,该配置表示一个衰减周期。我们可以简单的认为,每当时间间隔满足一个衰减周期时,就会对 counter 减一.
比如,我们设置 lfu_decay_time为 1 分钟,那么如果 Key 最后一次访问距离现在已有 3 分 30 秒,那么 counter 就需要减 3.
3.3 概率递增 在完成衰减后,Redis 将根据 lfu_log_factor 配置对应概率值对 counter 进行递增.
这里直接放上源码:
/* Logarithmically increment a counter. The greater is the current counter value
* the less likely is that it gets really implemented. Saturate it at 255. */
uint8_t LFULogIncr(uint8_t counter) {
// 若已达最大值 255,直接返回
if (counter == 255) return 255;
// 获取一个介于 0 到 1 之间的随机值
double r = (double)rand()/RAND_MAX;
// 根据当前 counter 减去初始值得到 baseval
double baseval = counter - LFU_INIT_VAL;
if (baseval < 0) baseval = 0;
// 使用 baseval*server.lfu_log_factor+1 得到一个概率值 p
double p = 1.0/(baseval*server.lfu_log_factor+1);
// 当 r < p 时,递增 counter
if (r < p) counter++;
return counter;
}
简而言之,直接从代码上理解,我们可以认为 counter和 lfu_log_factor 越大,则递增的概率越小: 当然,实际上也要考虑到访问次数对其的影响,Redis 官方给出了相关数据: 3.4 计数器的初始值 为了防止新的 Key 由于 counter 为 0 导致直接被淘汰,Redis 会默认将 counter设置为 5.
3.5 抽样大小的选择 值得注意的是,当数据量比较大的时候,如果抽样大小设置的过小,因为一次抽样的样本数量有限,冷热数据因为时间衰减导致的权重差异将会变得不明显,此时 LFU 算法的优势就难以体现,即使的相对较热的数据也有可能被频繁“误伤”.
所以,如果你选择了 LFU 算法作为淘汰策略,并且同时又具备比较大的数据量,那么不妨将抽样大小也设置的大一些.
最后此篇关于浅析Redis的文章就讲到这里了,如果你想了解更多关于浅析Redis的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
很多朋友或许都有这个疑问,一个网站域名和网站的排名有关系吗?今天本文就从三个方面分析网站的域名与网站的排名有没有关系,希望对大家有一定的帮助。 1、全拼双拼域名 首先,我们要知道在这一点百
什么是进程 当一个程序进入内存中运行起来它就变为一个进程。因此,进程就是一个处于运行状态的程序。同时进程具有独立功能,进程是操作系统进行资源分配和调度的独立单位。 什么是线程 线程是进
最近几年,互联网络竞争异常激烈,各个企业为了增加业绩,都在网络销售中下足了功夫。要确定网站发展的方向,必须给自己的网站制定好一个发展目标,有了目标才能更好的发展。不管
一.基础知识准备: 1.层的原则: (1)每一层以接口方式供上层调用。 (2)上层只能调用下层。 (3)依赖分为松散交互和严格交互两种。 2.业务逻辑分类: (1)应
编程时一门技术,更是一门艺术 简单工厂模式利用面向对象方式通过继承、封装、多态把程序的耦合度降低,设计模式使得程序更加灵活,容易修改,易于复用。 下面是服务器计算器代码:
对于策略模式的理解:当一个业务有多种需求时候,在某个时候需要使用不同的方式来计算结果。这时候不同的方式可以理解为不同的策略来解决同样的问题。 例如:商场收银系统计算价格,1:正常计算 2:商品打折计
随着 Kubernetes 在企业中应用的越来越广泛和普及,越来越多的公司在生产环境中运维多个集群。本文主要讲述一些关于多集群 Kubernetes 的思考,包括为什么选择多集群,多集群的好处以
Kubelet 出于对节点的保护,允许在节点资源不足的情况下,开启对节点上 Pod 进行驱逐的功能。最近对 Kubelet 的驱逐机制有所研究,发现其中有很多值得学习的地方,总结下来
以下分析不针对任何快递公司,纯属实说。 申通快递在快递行业中速度与费用都属于中等的水平,在国内也分布有很多投递点,一般地区都可以投递到;顺丰在国内是速度最快的快递公司之一,一般来说隔天就能够到,其
概述 流(streams)是PHP4.3版本引入的一个特性,主要是为了统一文件、sockets以及其他类似资源的工作方法。PHP4.3距今已经有很长时间了,但是很多程序员似乎都不能正确使用PHP中
Pre 很早在看 Jesse 的 Asp.net Core快速入门 的课程的时候就了解到了在Asp .net core中,如果添加的Json配置被更改了,是支持自动重载配置的,作为一名有着严重&q
之前对closure一知半解,在网上也找不到一篇文章能把它说清楚,今天好像第一次对它有点清晰的了解 了,写个BLOG记念一下 lua的函数是一种 First-Class Value 的东西, 到底
1、什么是默认方法,为什么要有默认方法 简单说,就是接口可以有实现方法,而且不需要实现类去实现其方法。只需在方法名前面加个default关键字即可。 为什么要有这个特性?首先,之前的接口是个双
信息数据传输的安全一直都是个很重要的话题,从刚开始当程序员时错以为MD5、SHA1这些哈希算法就是加密算法,到后来慢慢接触对称加密、非对称加密这些概念,再到对接各种大开发平台接口的时候看到他们通
前言 2021年,vanilla-extract 作为黑马登顶了 css-in-js 满意度榜首(虽然使用率仅为1%),号称是一个类型安全、高度兼容 TS 场景的库,国内相关讨论还很少,稍微看
(一)响应式数据 1. 简单例子 从最简单的数据绑定开始,在 Vue 2.0 中,我们这样将一个数据绑定到模板的指定位置: 在组件创建参数的 data 构造函数中返回一个用来绑定的数据对象,其
我是一名优秀的程序员,十分优秀!