思维导图
认识redis
- redis中的string类型是binary safe的,因为它不会对任何字符(包括'\0')进行特殊解释
redis测试
启动redis-server
启动redis-cli去连接redis-server
存储的value类型为string类型的
存储的value类型为hash结构
存储的value类型为set结构
存储的value类型为有序的集合
存储的value类型为链表结构
这种链表结构的数据存储类型适用于大部分的业务场景,因为大部分的业务场景都是仅仅要求插入有序。
redis存储结构KV
- 存储的时候需要对两方面进行均衡:
-
- 存储空间上进行均衡
- 存储效率上进行均衡
- redis是内存数据库,需要在时间和空间上进行均衡,而且redis会根据某个数据结构的节点数来进行均衡;换句话说就是底层的数据存储结构不是一成不变的,是会不断变化的
redis中value的编码:对象类型
string类型
从上面的实验可以看出来,string类型在redis中的存储可以是int类型,也可以是embstr类型,或者也可以是raw类型。
源码解析:string类型在redis内部的存储
注意这个sdshdrxx的结构体成员中都有个柔性数组(char buf[])。柔性数组的用法及特点是:
- 必须放在结构体中使用
- 必须放在结构体的最后面
- 结构体中还至少要包含一个其他的成员
- sizeof这个结构体返回的结构体的大小并不包含柔性数组的大小的
- 使用malloc这个接口来给结构体分配空间(注意这里malloc只需要调用一次)
//32为想要给结构体中的柔性数组分配的大小空间
struct sdshdr16* p = malloc(sizeof(struct sdshdr16) + 32);
- 使用free这个接口来给结构体释放空间(注意这里free也是只需要调用一次)
free(p);//这样就可以把结构体中的所有元素(包括柔性数组)全部都释放掉
- 柔性数组其实就是给编译器的一个标识,标识柔性数组代表的内存是动态的
应用方式
基本命令
位运算
key的设计
单个功能一个key
相同功能多个key
总结
- 如果不使用柔性数组,而是使用char* buffer的话,那么就需要malloc两次(一次是申请struct结构体的内存,还有一次是申请char* buffer的内存);同理释放也需要释放两次
- 柔性数组的优点
-
- 方便内存的分配及释放(只需要调用一次malloc)
- 减少内存碎片(只需要调用一次malloc)
- 分清楚几个概念:
-
- 对象类型:抽象的数据结构
- 数据结构、内存存储的结构:落盘的时候写到磁盘的结构
- 内存中的结构:字符串使用的是sds的数据结构来进行存储的
list类型
基础命令
清空数据库
存储list数据类型的基本操作
brpop的操作
存储结构定义
/* Node, quicklist, and Iterator are the only data structures used currently. */
/* quicklistNode is a 32 byte struct describing a listpack for a quicklist.
* We use bit fields keep the quicklistNode at 32 bytes.
* count: 16 bits, max 65536 (max lp bytes is 65k, so max count actually < 32k).
* encoding: 2 bits, RAW=1, LZF=2.
* container: 2 bits, PLAIN=1, PACKED=2.
* recompress: 1 bit, bool, true if node is temporary decompressed for usage.
* attempted_compress: 1 bit, boolean, used for verifying during testing.
* extra: 10 bits, free for future use; pads out the remainder of 32 bits */
typedef struct quicklistNode {
struct quicklistNode *prev;
struct quicklistNode *next;
unsigned char *entry;
size_t sz; /* entry size in bytes */
unsigned int count : 16; /* count of items in listpack */
unsigned int encoding : 2; /* RAW==1 or LZF==2 */
unsigned int container : 2; /* PLAIN==1 or PACKED==2 */
unsigned int recompress : 1; /* was this node previous compressed? */
unsigned int attempted_compress : 1; /* node can't compress; too small */
unsigned int extra : 10; /* more bits to steal for future usage */
} quicklistNode;
/* quicklist is a 40 byte struct (on 64-bit systems) describing a quicklist.
* 'count' is the number of total entries.
* 'len' is the number of quicklist nodes.
* 'compress' is: 0 if compression disabled, otherwise it's the number
* of quicklistNodes to leave uncompressed at ends of quicklist.
* 'fill' is the user-requested (or default) fill factor.
* 'bookmarks are an optional feature that is used by realloc this struct,
* so that they don't consume memory when not used. */
typedef struct quicklist {
quicklistNode *head;
quicklistNode *tail;
unsigned long count; /* total count of all entries in all listpacks */
unsigned long len; /* number of quicklistNodes */
signed int fill : QL_FILL_BITS; /* fill factor for individual nodes */
unsigned int compress : QL_COMP_BITS; /* depth of end nodes not to compress;0=off */
unsigned int bookmark_count: QL_BM_BITS;
quicklistBookmark bookmarks[];
} quicklist;
应用
栈
队列
阻塞队列
异步消息队列
获取固定窗口记录
hash类型
基础命令
修改hash数组中某个field的value(比如说修改age->30)
存储结构
应用
存储对象
购物车
set类型
基础命令
存储结构
应用
抽奖
共同关注
推荐好友
zset类型
基础命令
存储结构
应用
百度热榜
延时队列
分布式定时器
时间窗口限流
测试
package.cpath = package.cpath..";./luaclib/?.so;"
package.path = package.path .. ";./lualib/?.lua;"
local zv = require "zv"
local socket = require "socket"
local evloop = require "evloop"
local redis = require "db.redis"
require "util"
evloop.start()
local tab_concat = table.concat
local function is_action_allowed(red, userid, action, period, max_count)
local key = tab_concat({"hist", userid, action}, ":")
local now = zv.time()
red:init_pipeline()
red:multi()
-- 记录行为
red:zadd(key, now, now)
-- 移除时间窗口之前的行为记录,剩下的都是时间窗口内的记录
red:zremrangebyscore(key, 0, now - period *100)
-- 获取时间窗口内的行为数量
red:zcard(key)
-- 设置过期时间,避免冷用户持续占用内存 时间窗口的长度+1秒
red:expire(key, period + 1)
red:exec()
local res = red:commit_pipeline()
print(table.dump(res[6][3]))
return (tonumber(res[6][3]) or 0) <= max_count
end
local function console_loop(_)
local rds, err = redis.new("127.0.0.1", 6379)
if not rds then
print("failed to connect redis:", err)
return
end
local ok
ok, err = rds:set("name", "zero voice")
if not ok then
print("failed to set name: ", err)
return
end
local res
res, err = rds:get("name")
if not res then
print("failed to get name: ", err)
return
end
if res == null then
print("name not found.")
return
end
print("name:", res)
for i = 1, 10 do
local can_reply = is_action_allowed(rds, 10001, "replay", 5, 7)
if can_reply then
print("can reply")
else
print("cant replay")
end
zv.sleep(10)
end
end
socket.bind(0, console_loop)
evloop.run()