这一次主要是接着redis服务器接着进行代码讲解,因为redis服务器中包含大量的数据库,因为redis也对每个数据库设计了结构体
redis数据库
在上面 redisServer
中,有一个数组redisDb *db,这个数组中就是存放的是该服务器所有的数据库, redisDb
就是数据库字段, redisServer
中的dbnum就是该数组的大小
// redis服务器中每一个数据库都是这样一个实例
typedef struct redisDb {
// 数据库键空间,保存着数据库中的所有键值对
dict *dict;
// 键的过期时间,字典的键为键,字典的值为过期事件 UNIX 时间戳
dict *expires;
// 正处于阻塞状态的键
dict *blocking_keys;
// 可以解除阻塞的键
dict *ready_keys;
// 正在被 WATCH 命令监视的键
dict *watched_keys;
struct evictionPoolEntry *eviction_pool; /* Eviction pool of keys */
// 数据库号码
int id;
// 数据库的键的平均 TTL ,统计信息
long long avg_ttl;
} redisDb;

过期策略
在redisDb中,有一个字段为键的过期时间,因此针对过期的键,redis有一套自己的过期策略,下面进行讲解:
- 定时删除:在设置键过期时间的同时,创建一个定时器
- 惰性删除:每次要用这个键的时候,先检查是否过期
- 定期删除:每隔一段时间,对数据库键进行扫描,删除部分过期键
redis是使用惰性删除和定期删除配合实现的过期策略
惰性删除代码为 db.c/expireIfNeeded
,每次执行命令前都执行该函数
/*
* 检查 key 是否已经过期,如果是的话,将它从数据库中删除。
*
* 返回 0 表示键没有过期时间,或者键未过期。
*
* 返回 1 表示键已经因为过期而被删除了。
*
* 惰性删除 所有读写数据库的命令在执行前都会进行检查
*
*/
int expireIfNeeded(redisDb *db, robj *key) {
// 取出键的过期时间
mstime_t when = getExpire(db,key);
mstime_t now;
// 没有过期时间
if (when < 0) return 0;
// 如果服务器正在进行载入,那么不进行任何过期检查
if (server.loading) return 0;
/* If we are in the context of a Lua script, we claim that time is
* blocked to when the Lua script started. This way a key can expire
* only the first time it is accessed and not in the middle of the
* script execution, making propagation to slaves / AOF consistent.
* See issue #1525 on Github for more information. */
now = server.lua_caller ? server.lua_time_start : mstime();
// 当服务器运行在 replication 模式时
// 附属节点并不主动删除 key
// 它只返回一个逻辑上正确的返回值
// 真正的删除操作要等待主节点发来删除命令时才执行
// 从而保证数据的同步
if (server.masterhost != NULL) return now > when;
// 运行到这里,表示键带有过期时间,并且服务器为主节点
// 如果未过期,返回 0
if (now id);
// 将过期键从数据库中删除
return dbDelete(db,key);
}
定期删除代码为 redis.c/activeExpireCycle
,每当redis周期性执行 redis.c/serverCron
时候,就会调用该函数,它在规定的时间内,遍历各个数据库,随机检查一部分键,若过期则删除
/*
* 函数尝试删除数据库中已经过期的键。
* 当带有过期时间的键比较少时,函数运行得比较保守,
* 如果带有过期时间的键比较多,那么函数会以更积极的方式来删除过期键,
* 从而可能地释放被过期键占用的内存。
*
*
* 每次循环中被测试的数据库数目不会超过 REDIS_DBCRON_DBS_PER_CALL 。
*
*
* 如果 timelimit_exit 为真,那么说明还有更多删除工作要做,
* 那么在 beforeSleep() 函数调用时,程序会再次执行这个函数。
*
*
* 过期循环的类型:
*
*
* 如果循环的类型为 ACTIVE_EXPIRE_CYCLE_FAST ,
* 那么函数会以"快速过期"模式执行,
* 执行的时间不会长过 EXPIRE_FAST_CYCLE_DURATION 毫秒,
* 并且在 EXPIRE_FAST_CYCLE_DURATION 毫秒之内不会再重新执行。
*
* 如果循环的类型为 ACTIVE_EXPIRE_CYCLE_SLOW ,
* 那么函数会以"正常过期"模式执行,
* 函数的执行时限为 REDIS_HS 常量的一个百分比,
* 这个百分比由 REDIS_EXPIRELOOKUPS_TIME_PERC 定义。
*
* 定期删除 服务器周期性执行redis.c/serverConn时候会执行该函数
*
* 函数执行大概流程:
*
* 1、函数每次运行时,都从一定数量的数据库中取出一定数量的随机键进行检查,并删除其中的过期键。
*
* 2、全局变量current_db会记录当前activeExpireCycle函数检查的进度,
* 并在下一次activeExpireCycle函数调用时,接着上一次的进度进行处理。
* 比如说,如果当前activeExpirecycle函数在遍历10号数据库时返回了,
* 那么下次activeExpirecycle函数执行时,将从11号数据库开始查找并删除过期键。
*
* 3、随着activeExpireCycle函数的不断执行,服务器中的所有数据库都会被检查一遍,
* 这时函数将current_db变量重置为0,然后再次开始新一轮的检查工作。
*/
void activeExpireCycle(int type) {
// 静态变量,用来累积函数连续执行时的数据
// 表明目前检测到哪个数据库了
static unsigned int current_db = 0;
static int timelimit_exit = 0;
static long long last_fast_cycle = 0;
unsigned int j, iteration = 0;
// 默认每次处理的数据库数量
unsigned int dbs_per_call = REDIS_DBCRON_DBS_PER_CALL;
// 函数开始的时间
long long start = ustime(), timelimit;
// 快速模式
if (type == ACTIVE_EXPIRE_CYCLE_FAST) {
// 如果上次函数没有触发 timelimit_exit ,那么不执行处理
if (!timelimit_exit) return;
// 如果距离上次执行未够一定时间,那么不执行处理
if (start < last_fast_cycle + ACTIVE_EXPIRE_CYCLE_FAST_DURATION*2) return;
// 运行到这里,说明执行快速处理,记录当前时间
last_fast_cycle = start;
}
/*
* 一般情况下,函数只处理 REDIS_DBCRON_DBS_PER_CALL 个数据库,
* 除非:
*
* 1) 当前数据库的数量小于 REDIS_DBCRON_DBS_PER_CALL
* 2) 如果上次处理遇到了时间上限,那么这次需要对所有数据库进行扫描,
* 这可以避免过多的过期键占用空间
*/
if (dbs_per_call > server.dbnum || timelimit_exit)
dbs_per_call = server.dbnum;
/* We can use at max ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC percentage of CPU time
* per iteration. Since this function gets called with a frequency of
* server.hz times per second, the following is the max amount of
* microseconds we can spend in this function. */
// 函数处理的微秒时间上限
// ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC 默认为 25 ,也即是 25 % 的 CPU 时间
timelimit = 1000000*ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC/server.hz/100;
timelimit_exit = 0;
if (timelimit expires)) == 0) {
db->avg_ttl = 0;
break;
}
// 获取数据库中键值对的数量
slots = dictSlots(db->expires);
// 当前时间
now = mstime();
// 这个数据库的使用率低于 1% ,扫描起来太费力了(大部分都会 MISS)
// 跳过,等待字典收缩程序运行
if (num && slots > DICT_HT_INITIAL_SIZE &&
(num*100/slots < 1)) break;
/* The main collection cycle. Sample random keys among keys
* with an expire set, checking for expired ones.
*
* 样本计数器
*/
// 已处理过期键计数器
expired = 0;
// 键的总 TTL 计数器
ttl_sum = 0;
// 总共处理的键计数器
ttl_samples = 0;
// 每次最多只能检查 LOOKUPS_PER_LOOP 个键
// 默认每个数据库检查的键数量
if (num > ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP)
num = ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP;
// 开始遍历数据库
while (num--) {
dictEntry *de;
long long ttl;
// 从 expires 中随机取出一个带过期时间的键
if ((de = dictGetRandomKey(db->expires)) == NULL) break;
// 计算 TTL
ttl = dictGetSignedIntegerVal(de)-now;
// 如果键已经过期,那么删除它,并将 expired 计数器增一
if (activeExpireCycleTryExpire(db,de,now)) expired++;
if (ttl < 0) ttl = 0;
// 累积键的 TTL
ttl_sum += ttl;
// 累积处理键的个数
ttl_samples++;
}
// 为这个数据库更新平均 TTL 统计数据
if (ttl_samples) {
// 计算当前平均值
long long avg_ttl = ttl_sum/ttl_samples;
// 如果这是第一次设置数据库平均 TTL ,那么进行初始化
if (db->avg_ttl == 0) db->avg_ttl = avg_ttl;
// 取数据库的上次平均 TTL 和今次平均 TTL 的平均值
db->avg_ttl = (db->avg_ttl+avg_ttl)/2;
}
// 更新遍历次数
iteration++;
// 每遍历 16 次执行一次
if ((iteration & 0xf) == 0 &&
(ustime()-start) > timelimit)
{
// 如果遍历次数正好是 16 的倍数
// 并且遍历的时间超过了 timelimit
// 那么断开 timelimit_exit
timelimit_exit = 1;
}
// 已经超时了,返回
if (timelimit_exit) return;
// 如果已删除的过期键占当前总数据库带过期时间的键数量的 25 %
// 那么不再遍历
} while (expired > ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP/4);
}
}
redis客户端
因为redis可以和多个客户端进行连接,因此为了区分每个客户端,redis内部为每个连接客户端创建一个结构体 redisClient
,然后将多个结构体用链表连接在一起
typedef struct redisClient {
// 套接字描述符
int fd;
// 当前正在使用的数据库 使用select可以切换数据库,因为服务器刚开始创建了16个
redisDb *db;
// 当前正在使用的数据库的 id (号码)
int dictid;
// 客户端的名字
robj *name;
// 查询缓冲区
sds querybuf;
// 查询缓冲区长度峰值
size_t querybuf_peak;
// 参数数量
int argc;
// 参数对象数组
robj **argv;
// 记录被客户端执行的命令
struct redisCommand *cmd, *lastcmd;
// 请求的类型:内联命令还是多条命令
int reqtype;
// 剩余未读取的命令内容数量
int multibulklen;
// 命令内容的长度
long bulklen;
// 回复链表
list *reply;
// 回复链表中对象的总大小
unsigned long reply_bytes; /
// 已发送字节,处理 short write 用
int sentlen;
// 创建客户端的时间
time_t ctime;
// 客户端最后一次和服务器互动的时间
time_t lastinteraction;
// 客户端的输出缓冲区超过软性限制的时间
time_t obuf_soft_limit_reached_time;
// 客户端状态标志
int flags;
// 当 server.requirepass 不为 NULL 时
// 代表认证的状态
// 0 代表未认证, 1 代表已认证
int authenticated;
// 复制状态
int replstate;
// 用于保存主服务器传来的 RDB 文件的文件描述符
int repldbfd;
// 读取主服务器传来的 RDB 文件的偏移量
off_t repldboff;
// 主服务器传来的 RDB 文件的大小
off_t repldbsize;
sds replpreamble;
// 主服务器的复制偏移量
long long reploff;
// 从服务器最后一次发送 REPLCONF ACK 时的偏移量
long long repl_ack_off;
// 从服务器最后一次发送 REPLCONF ACK 的时间
long long repl_ack_time;
// 主服务器的 master run ID
// 保存在客户端,用于执行部分重同步
char replrunid[REDIS_RUN_ID_SIZE+1];
// 从服务器的监听端口号
int slave_listening_port;
// 事务状态
multiState mstate;
// 阻塞类型
int btype;
// 阻塞状态
blockingState bpop;
// 最后被写入的全局复制偏移量
long long woff;
// 被监视的键
list *watched_keys;
// 这个字典记录了客户端所有订阅的频道
// 键为频道名字,值为 NULL
// 也即是,一个频道的集合
dict *pubsub_channels;
// 链表,包含多个 pubsubPattern 结构
// 记录了所有订阅频道的客户端的信息
// 新 pubsubPattern 结构总是被添加到表尾
list *pubsub_patterns;
sds peerid;
// 回复偏移量
int bufpos;
// 回复缓冲区
char buf[REDIS_REPLY_CHUNK_BYTES];
} redisClient;
自己的网址:www.shicoder.top
欢迎加群聊天 452380935本文由博客一文多发平台 OpenWrite 发布!
Original: https://www.cnblogs.com/shilinkun/p/16171281.html
Author: 小坤学习园
Title: redis数据库
相关阅读
Title: Jmeter 使用Json提取请求数据
使用Json提取器可以提取请求响应数据
Json提取器
位置: 后置处理器-》Json提取器

使用介绍
1,变量名
变量名,其他部分引用方式: ${变量名}
若提取多个变量,多个之间使用分号”;”分割

2,Json表达式
$表示响应的根对象
.表示对象的下一级子对象或者属性
取数组加[],数组索引从0开始,*表示数组所有值
若响应结构为:{“code”:200,”resultMessage”:””,”data”:2}
$.data 表示取data这个对象若响应结构为:[{},{},{}]
$[1或者*] 1,2,3表示取数组第几个元素,表示取所有若响应结构为:{“属性”:[{“sx”:1},{},{}]}
$.属性[0].sx
3,Match no
-1表示匹配所有
0表示随机取值
1表示匹配第一个,2表示匹配第二个
设置变量数量与match no个数要匹配得上,多个提取之间使用;分割
设置为-1时
会提取多个变量,单个变量的引用带具体数值,数字表示提取到的第几个变量。使用方式:${变量名_序列} 序列为1,2,3。。。。
设置为-1时,生成其他相关变量
变量名_matchNr
意思是取到的符合条件的数字个数。可以像使用普通变量一样使用这个_matchNr变量。
变量名_ALL
若是勾选了 计算串联变量(Compute concatenation var(suffix _ALL))则还会生成以_ALL结尾的变量,表示所有变量组合的变量,以分号分割


设置为0或1,2,3等具体数值
取的是单个值,变量的使用是${变量名},不用加序列号
4,默认值
匹配上没有缺省值。当取多个值时,可以设置多个缺省值,并且可以在多个缺省值之间用分号分隔。
[En]
There is no default value on the match. When multiple values are taken, multiple default values can be set, and semicolons can be separated between multiple default values.
提取变量的后续操作
调试器样本查看变量取值情况
查看json提取的数据取值情况


这里debug样本器提取了变量pi 还会生成pi_matchNr变量,表示这个变量提取了多少匹配的值。
Beanshell写变量到文件
beanshell后置处理程序
若是请求之后,提取变量,写文件使用的是beanshell后置处理程序
beanshell取样器
非请求之后写入使用的是beanshell取样器
两种beanshell元件,其中代码暂时没有发现不同

这是使用beanshell写入获取的quo_no的ALL变量quo_no_ALL ,写入文件后取值直接也是带分号的。
遍历提取到的值

变量名为quo_no
match no为-1提取数组对象所有符合值
使用ForEach遍历器遍历获取的所有quo_no

ForEach控制器,从0开始(不包括0),停止循环为 q u o n o m a t c h N r 输 出 变 量 名 设 置 好 q u o o o , 该 控 制 下 调 用 变 量 名 {quo_no_matchNr} 输出变量名设置好quooo,该控制下调用变量名 quonomatchNr输出变量名设置好quooo,该控制下调用变量名{quooo}
比如,本脚本是使用beanshell采样器去写文件

除了写文件还可以做其他处理,比如带上提取到的变量继续请求。
到此这篇关于Jmeter 使用Json提取请求数据的文章就介绍到这了,更多相关Jmeter Json提取请求数据内容请搜索【脚本之家】
Original: https://www.cnblogs.com/yaodun55/p/16125265.html
Author: 少年Dev
Title: Jmeter 使用Json提取请求数据
原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/158549/
转载文章受原作者版权保护。转载请注明原作者出处!