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
# 月签到功能 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脚本可以执行多个命令,并保证原子性

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

语法:

EVAL script numkeys key [key ...] arg [arg ...]

可通过script load获取lua脚本的hash字符串

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执行

EVALSHA sha1 numkeys key [key ...] arg [arg ...]
127.0.0.1:6379> EVALSHA 5640042b18b79e5405b722cf97c15d87768a3ce9 1 lua_test
(integer) 400

实际应用中,会在服务器启动时,将所有的lua脚本先获取到对应的哈希值,然后存在一个unordered_map中,这样后序可直接使用EVALSHA执行

# 清除所有脚本缓存
> 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已封装了相关的接口

#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(注销写事件)

可替换成自己设计的接口。

参考链接:https://xxetb.xetslk.com/s/1QH6AQ

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部