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

相关推荐
黄名富2 小时前
Redis 附加功能(二)— 自动过期、流水线与事务及Lua脚本
java·数据库·redis·lua
G_whang2 小时前
centos7下docker 容器实现redis主从同步
redis·docker·容器
.生产的驴3 小时前
SpringBoot 对接第三方登录 手机号登录 手机号验证 微信小程序登录 结合Redis SaToken
java·spring boot·redis·后端·缓存·微信小程序·maven
我叫啥都行6 小时前
计算机基础复习12.22
java·jvm·redis·后端·mysql
阿乾之铭7 小时前
Redis四种模式在Spring Boot框架下的配置
redis
on the way 1238 小时前
Redisson锁简单使用
redis
科马9 小时前
【Redis】缓存
数据库·redis·spring·缓存
mxbb.11 小时前
单点Redis所面临的问题及解决方法
java·数据库·redis·缓存
weisian15121 小时前
Redis篇--常见问题篇3--缓存击穿(数据查询上锁,异步操作,熔断降级,三种缓存问题综合优化策略)
数据库·redis·缓存
HEU_firejef1 天前
redis——布隆过滤器
数据库·redis·缓存