Redis数据结构与连接

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

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

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

相关推荐
小安运维日记1 小时前
Linux云计算 |【第四阶段】NOSQL-DAY1
linux·运维·redis·sql·云计算·nosql
码农郁郁久居人下6 小时前
Redis的配置与优化
数据库·redis·缓存
Hsu_kk8 小时前
Redis 主从复制配置教程
数据库·redis·缓存
DieSnowK8 小时前
[Redis][环境配置]详细讲解
数据库·redis·分布式·缓存·环境配置·新手向·详细讲解
SAO&asuna18 小时前
redis基本数据结构-sorted set
数据结构·数据库·redis
一大颗萝卜18 小时前
【原创 架构设计】多级缓存的应用、常见问题与解决方式
redis·缓存·架构·caffeine·多级缓存
andrew_121920 小时前
腾讯 IEG 游戏前沿技术 一面复盘
java·redis·sql·面试
Java码农杂谈1 天前
浅谈Tair缓存的三种存储引擎MDB、LDB、RDB
java·redis·分布式·后端·阿里云·缓存
DYS_000011 天前
阿里短信服务+Redis创建定时缓存
数据库·redis·缓存
问道飞鱼2 天前
分布式中间件-redis相关概念介绍
redis·分布式·中间件