1 基本的数据结构
1.1 string
string的实现有多种
- int:字符串长度小于等于20且能转成整数
- raw:字符串长度大于44
- embstr:字符串长度小于等于44
字符串长度小于1M 时,加倍扩容;超过 1M 每次只多扩1M;字符串最大长度为 512M
string是二进制安全字符串,可以存储图片,二进制协议等二进制数据
基本命令:
- SET key val
- GET key
- INCR key
- INCRBY key increment
- DECR key
- DECRBY key decrement
- SETNX key value(set not exist)
- DEL key
- SETBIT key offset value
- GETBIT key offset
- BITCOUNT key
Redis
# 月签到功能 2021年6月份的第1天
setbit sign:10001:202106 1 1
# 计算 2021年6月份 的签到情况
bitcount sign:10001:202106
# 获取 2021年6月份 第二天的签到情况 1 已签到 0 没有签到
getbit sign:10001:202106 2
1.2 list
- 双向链表实现,列表首尾操作(删除和增加)时间复杂度 O(1) ;查找中间元素时间复杂度为O(n)
基本命令:
- LPUSH key value [value ...]
- LPOP key
- RPUSH key value [value ...]
- RPOP key
- LRANGE key start end
- LREM key count value 移除前 count 次出现的值为 value 的元素
- BRPOP key timeout RPOP 的阻塞版本,这个命令会在给定list无法弹出任何元素的时候阻塞连接
- LTRIM key start stop
实际项目中需要保证命令的原子性,所以一般用 lua 脚本 或者使用 pipeline 命令
存储结构:
- quicklist(双向链表)
- ziplist(压缩列表)
1.3 hash
散列表
基本命令
- HGET key field
- HSET key field value
- HMSET key field1 value1 field2 value2 ... fieldn valuen
- HMGET key field1 field2 ... fieldn
- HGETALL key
- HINCRBY key field increment
- HLEN key
- HDEL key field
存储结构:
- 节点数量大于 512(hash-max-ziplist-entries) 或所有字符串长度大于 64(hash-max-ziplist-value),则使用 dict 实现
- 节点数量小于等于 512 且有一个字符串长度小于 64,则使用 ziplist 实现
1.4 set
基本命令
- SADD key member [member ...]
- SCARD key
- Get the number of members in a set
- SMEMBERS key
- SISMEMBER key member
- SRANDMEMBER key [count]
- SPOP key [count]
- SDIFF key [key ...] 差集
- SINTER key [key ...] 交集
- SUNION key [key ...] 并集
存储结构:
- 元素都为整数且节点数量小于等于 512(set-max-intset-entries),则使用整数数组存储
- 元素当中有一个不是整数或者节点数量大于 512,则使用字典存储
1.5 zset
有序集合
基本命令:
- ZADD key [NX|XX] [CH] [INCR] score member [score member ...]
- ZREM key member [member ...]
- ZSCORE key member
- ZINCRBY key increment member
- ZCARD key
- ZRANK key member 返回有序集key中成员member的排名
- ZRANGE key start stop [WITHSCORES]
- ZREVRANGE key start stop [WITHSCORES]
存储结构:
- 节点数量大于 128 或者有一个字符串长度大于 64,则使用跳表(skiplist)
- 节点数量小于等于 128(zset-max-ziplist-entries)且所有字符串长度小于等于 64(zset-max-ziplist-value),则使用 ziplist 存储
2 redis pipeline
redis pipeline 是一个客户端提供的机制,而不是服务端提供的
可以一次性发多条指令,减少网络交互
3 redis事务
-
MULTI 开启事务,事务执行过程中,单个命令是入队列操作,直到调用 EXEC 才会一起执行
-
乐观锁实现,所以失败需要重试
相关指令:
- MULTI 开启事务
- EXEC 提交事务
- DISCARD 取消事务
- WATCH 检测 key 的变动,若在事务执行中,key 变动则取消事务
4 lua脚本
实际使用,并不是使用MULTI等命令,而是使用lua脚本实现原子性。
Redis中内嵌一个lua虚拟机,用来执行Redis lua 脚本,Redis lua脚本可以执行多个命令,并保证原子性
Redis
127.0.0.1:6379> set lua_test 100
OK
127.0.0.1:6379> eval 'local key = KEYS[1]; local val = redis.call("get", key); redis.call("set", key, 2*val); return 2*val;' 1 lua_test
(integer) 200
语法:
Redis
EVAL script numkeys key [key ...] arg [arg ...]
可通过script load获取lua脚本的hash字符串
Redis
127.0.0.1:6379> SCRIPT LOAD 'local key = KEYS[1]; local val = redis.call("get", key); redis.call("set", key, 2*val); return 2*val;'
"5640042b18b79e5405b722cf97c15d87768a3ce9"
127.0.0.1:6379> SCRIPT EXISTS "5640042b18b79e5405b722cf97c15d87768a3ce9"
1) (integer) 1
使用EVALSHA执行
Redis
EVALSHA sha1 numkeys key [key ...] arg [arg ...]
Redis
127.0.0.1:6379> EVALSHA 5640042b18b79e5405b722cf97c15d87768a3ce9 1 lua_test
(integer) 400
实际应用中,会在服务器启动时,将所有的lua脚本先获取到对应的哈希值,然后存在一个unordered_map中,这样后序可直接使用EVALSHA执行
Redis
# 清除所有脚本缓存
> script flush
OK
# 如果当前脚本运行时间过长(死循环),可以通过 script kill 杀死当前运行的脚本
> script kill
5 ACID特性
A(atomic):原子性,事务中的多个操作要么都成功,要么都失败。
- redis不支持回滚,即使事务队列中的某个命令在执行期间出现了错误,整个事务也会继续执行下去,直到将事务队列中的所有命令都执行完毕为止。
C(consistent):一致性
- Redis满足数据库层面的一致性,即对于string类型,不能使用lpush操作
- Redis不满足逻辑上的一致性,对于lua脚本,假如有多条指令,分别是A、B、C、D,若B指令出错,后面的指令都不会执行,但A指令仍然生效。
- 一个扣钱一个加钱;可能出现扣钱执行错误,加钱执行正确,那么最终还是会加钱成功;系统凭空多了钱
I(isolation):隔离性
- redis 是单线程执行,天然具备隔离性
D(duration):持久性
- redis 只有在 aof 持久化策略的时候,并且需要在 redis.conf 中appendfsync=always 才具备持久性;实际项目中几乎不会使用 aof 持久化策略
lua 脚本满足原子性和隔离性;一致性和持久性不满足
6 Redis同步连接
hiredis已封装了相关的接口
c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <hiredis/hiredis.h>
int main() {
unsigned int j, isunix = 0;
redisContext *c;
redisReply *reply;
const char *hostname = "127.0.0.1";
int port = 6379;
struct timeval timeout = { 1, 500000 }; // 1.5 seconds
c = redisConnectWithTimeout(hostname, port, timeout);
if (c == NULL || c->err) {
if (c) {
printf("Connection error: %s\n", c->errstr);
redisFree(c);
} else {
printf("Connection error: can't allocate redis context\n");
}
exit(1);
}
int roleid = 10001;
reply = redisCommand(c, "hgetall role:%d", roleid);
if (reply->type != REDIS_REPLY_ARRAY) {
printf("reply error: %s\n", reply->str);
} else {
printf("reply:number of elements=%lu\n", reply->elements);
for (size_t i = 0; i < reply->elements; i++) {
printf("\t %lu : %s\n", i, reply->element[i]->str);
}
}
freeReplyObject(reply);
/* Disconnects and frees the context */
redisFree(c);
return 0;
}
编译
$ gcc test.c -o test -lhiredis
7 Redis异步连接
hiredis提供了适配reactor网络模型的异步驱动方式(adapters文件夹)。
libevent.h中
可以看到有addRead(注册读事件)、delRead(注销读事件)、addWrite(注册写事件)、delWrite(注销写事件)
可替换成自己设计的接口。