Redis相关命令与原理

文章目录

概要

Redis是Remote Dictionary Service的简称 远程字典服务。

Redis是一种内存数据库,运行redis的进程突然关闭 ,或者服务器宕机了,会导致数据的直接丢失,但是其他进程可以直接在内存中访问redis进程中的数据,速度非常快。和磁盘读写有10w倍的差异。

Redis是一种key-value数据库,通过key操作value,通过unordered_map散列表的形式组织起来的。

Redis支持多种数据结构(value的数据类型):

string

list 链表

hash 哈希表

set 无序集合

zset 有序集合

安装和编译

bash 复制代码
git clone https://github.com/redis/redis.git
cd redis
git checkout 6.2.14
make
make test
sudo make install
# 默认安装在 /usr/local/bin
# redis-server 是服务端程序
# redis-cli 是客户端程序

启动

bash 复制代码
mkdir redis-data
# 把redis文件夹下 redis.conf 拷贝到 redis-data
# 修改 redis.conf
# requirepass 修改密码 123456
# daemonize yes
cd redis-data
redis-server ./redis.conf
ps aux | grep redis-server
# 通过 redis-cli 访问 redis-server
redis-cli -h 127.0.0.1 -a 123456

Redis的数据类型

bash 复制代码
127.0.0.1:6379> TYPE key
string


string: 安全的二进制字符串 安全:字符串有描述长度的信息,而不是以\0作为结束符分割

bash 复制代码
127.0.0.1:6379> set key value
OK
127.0.0.1:6379> get key
"mark"

list: 双端队列,插入有序

bash 复制代码
127.0.0.1:6379> lpush list value1 value2 value3
(integer) 3
127.0.0.1:6379> lrange list 0 -1
1)"value1"
2)"value2"
3)"value3"

hash:对顺序不关注,filed是唯一的

bash 复制代码
127.0.0.1:6379> hmset flame:10001 name flame age 18
OK
127.0.0.1:6379> hgetall flame:10001
1) "name"
2) "flame"
3) "age"
4) "18"

127.0.0.1:6379> HINCRBY flame:10001 age 1
(integer) 19

set:

bash 复制代码
127.0.0.1:6379> sadd flame:10002 member1 member2 member3
(integer) 3
127.0.0.1:6379> SMEMBERS flame:10002
1) "member1"
2) "member3"
3) "member2"

zset:对顺序不关注 集合是

bash 复制代码
127.0.0.1:6379> zadd ranks 100 value1
(integer) 1
127.0.0.1:6379> zadd ranks 101 value2
(integer) 1
127.0.0.1:6379> zadd ranks 130 value3
(integer) 1
127.0.0.1:6379> zadd ranks 120 value2
(integer) 0
127.0.0.1:6379> zrange ranks 0 -1 withscores
1) "value1"
2) "100"
3) "value2"
4) "120"
5) "value3"
6) "130"

以上五种数据结构能起到去重 作用的:
string hash set zset

key的设计规范

单个功能一个key,多个功能多个key 层级间以 : 分开(redis可视化操作平台基本用:归类key)

string

安全的二进制字符串 ,字符数组,该字符串是动态字符串raw,字符串长度小于1M时,加倍扩容;超过1M每次只多扩1M;字符串最大长度为512M;

注意:redis字符串是二进制安全字符串;可以存储图片,二进制协议等二进制数据;

基础命令

bash 复制代码
# 设置 key 的 value 值
SET key val

# 获取 key 的 value
GET key

# 执行原子加一的操作
INCR key

# 执行原子加一个整数的操作
INCRBY key increment

# 执行原子减一的操作
DECR key

# 执行原子减一个整数的操作
DECRBY key decrement

# 如果key不存在,这种情况下等同SET命令。 当key存在时,什么都不做
# set Not exist  ok 这个命令是否执行了  0,1 是不是操作结果是不是成功
SETNX key value

# 删除 key val 键值对
DEL key

# 设置或者清空key的value(字符串)在offset处的bit值。 setbit embstr raw  int
# 动态字符串 能够节约内存
SETBIT key offset value

# 返回key对应的string在offset处的bit值
GETBIT key offset

# 统计字符串中被设置为1的bit数。
BITCOUNT key

应用

①对象存储json格式

bash 复制代码
# 第一部分:设置和获取角色数据
SET role:10001 '{"name":"mark","sex":"male","age":30}'
SET role:10002 '{"name":"darren","sex":"male","age":30}'
# 极少修改,对象属性字段很少改变的时候,否则应用hash来进行存储
GET role:10001

# 第二部分:关于 key 的命名建议
# key 如何来设置
# 1. 有意义的字段    role 有多行
# 2. role:10001  redis 客户端   role:10001:recharge  role:10001:activity:10001

②累加器

bash# 复制代码
incr reads
# 累计加100
incrby reads 100

③分布式锁

bash 复制代码
# 加锁、加锁和解析 redis 实现是 非公平锁    etcd zk 用来实现公平锁
# 阻塞等待 阻塞连接的方式
# 介绍简单的原理:事务

setnx lock 1     # 不存在才能设置 定义加锁行为 占用锁
setnx lock uuid  # expire 30 过期
set lock uuid nx ex 30

# 释放锁
del lock
if (get(lock) == uuid)
    del(lock);

④位运算

bash 复制代码
# 猜测一下 string 是用的 int 类型 还是 string 类型
# 月签到功能 10001 用户id 202106 2021年6月份的签到 6月份的第1天
setbit sign:10001:202106 1 1

# 计算 2021年6月份 的签到情况
bitcount sign:10001:202106

# 获取 2021年6月份 第二天的签到情况 1 已签到 0 没有签到
getbit sign:10001:202106 2

list

双向链表实现,列表首尾操作(删除和增加)时间复杂度o(1);查找中间元素时间复杂度为o(n);

列表中数据是否压缩的依据:

1.元素长度小于48,不压缩;

2.元素压缩前后长度差不超过8,不压缩;

基础命令

lpush在左侧插入,rpush在右侧插入

lpop在左侧弹出,rpop在右侧弹出

bash 复制代码
# 从队列的左侧入队一个或多个元素
LPUSH key value [value ...]

# 从队列的左侧弹出一个元素
LPOP key

# 从队列的右侧入队一个或多个元素
RPUSH key value [value ...]

# 从队列的右侧弹出一个元素
RPOP key

# 返回从队列的 start 和 end 之间的元素 0,1,2 负索引
LRANGE key start end

# 从存于 key 的列表里移除前 count 次出现的值为 value 的元素
# list 没有去重功能 hash set zset
LREM key count value

# 它是 RPOP 的阻塞版本,因为这个命令会在给定list无法弹出任何元素的时候阻塞连接
BRPOP key timeout  # 超时时间 + 延时队列 ,为0就是永久阻塞

应用

①栈

复制代码
LPUSH + LPOP
#或者
RPUSH + RPOP

②队列

复制代码
LPUSH + RPOP
#或者
RPUSH + LPOP

③阻塞队列

复制代码
LPUSH + BRPOP
#或者
RPUSH + BLPOP

④异步消息队列
与队列一致,但是push和pop在不同的进程中,生产者和消费者

⑤获取固定窗口记录(战绩)

复制代码
lpush says '{name1,age1}'
ltrim says 0 4
lpush says '{name2,age2}'
ltrim says 0 4
lpush says '{name3,age3}'
ltrim says 0 4
lpush says '{name4,age4}'
ltrim says 0 4
lpush says '{name5,age5}'
ltrim says 0 4
lrange says 0 -1

实际项目中需要保证指令的原子性,一般使用Lua脚本

复制代码
-- redis lua脚本
local record = KEYS[1]
redis.call("LpUsH", "says", record)
redis.call("LTRIM", "says", 0, 4)

hash

用于保存频繁改变的对象属性,散列表,在很多高级语言当中包含这种数据结构;c++ unordered_map 通过 key 快速索引I value。

基础命令

复制代码
# 获取 key 对应 hash 中的 field 对应的值
HGET key field

# 设置 key 对应 hash 中的 field 对应的值
HSET key field value

# 设置多个hash键值对
HMSET key field1 value1 field2 value2 ... fieldn valuen

# 获取多个field的值
HMGET key field1 field2 ... fieldn

# 给 key 对应 hash 中的 field 的值加一个整数值
HINCRBY key field increment

# 获取 key 对应的 hash 有多少个键值对
HLEN key

# 删除 key 对应的 hash 的键值对,该键为field
HDEL key field

存储结构

节点数量大于512(hash-max-ziplist-entries)或所有字符串长度大于64 (hash-max-ziplist-value)则使用dict 实现;

节点数量小于等于512且有一个字符串长度小于64,则使用ziplist实现;

应用

①存储对象

复制代码
hmset hash:10001 name mark age 18 sex male

# 与 string 比较
set hash:10001 '{"name":"mark","sex":"male","age":18}'

# 假设现在修改 mark的年龄为19岁

# hash:
hset hash:10001 age 19

# string:
get hash:10001
# 将得到的字符串调用json解密,取出字段,修改 age 值
# 再调用json加密
set hash:10001 '{"name":"mark","sex":"male","age":19}'

②购物车 (hash + list)

复制代码
# 将用户id作为 key
# 商品id作为 field
# 商品数量作为 value
# 注意:这些物品是按照我们添加顺序来显示的:

# 添加商品:
hmset MyCart:10001 40001 1 cost 5099 desc "戴尔笔记本14-3400"
lpush MyItem:10001 40001

# 增加数量:
hincrby MyCart:10001 40001 1
hincrby MyCart:10001 40001 -1  // 减少数量1

# 显示所有物品数量:
hlen MyCart:10001


# 删除商品:
hdel MyCart:10001 40001
lrem MyItem:10001 1 40001

# 获取所有物品:
lrange MyItem:10001
# 40001 40002 40003
hget MyCart:10001 40001
hget MyCart:10001 40002
hget MyCart:10001 40003

set

集合;用来存储唯一性字段,不要求有序;

存储不需要有序,操作(交并差集的时候排序)?

基础命令

其中交并补操作比较重要

复制代码
# 添加一个或多个指定的member元素到集合的 key中
SADD key member [member ...]

# 计算集合元素个数
SCARD key

# SMEMBERS key
SMEMBERS key

# 返回成员 member 是否是存储的集合 key的成员
SISMEMBER key member

# 随机返回key集合中的一个或者多个元素,不删除这些元素
SRANDMEMBER key [count]

# 从存储在key的集合中移除并返回一个或多个随机元素
SPOP key [count]

# 返回一个集合与给定集合的差集的元素
SDIFF key [key ...]

# 返回指定所有集合的成员的交集
SINTER key [key ...]

# 返回给定的多个集合的并集中的所有成员
SUNION key [key ...]

应用

①抽奖

复制代码
# 添加抽奖用户
sadd Award:1 10001 10002 10003 10004 10005 10006
sadd Award:1 10009

# 查看所有抽奖用户
smembers Award:1

# 抽取多名幸运用户
srandsample Award:1 10

# 如果抽取一等奖1名,二等奖2名,三等奖3名,该如何操作?

②共同关注

复制代码
sadd follow:A mark king darren mole vico
sadd follow:C mark king darren
sinter follow:A follow:C

③推荐好友

复制代码
sadd follow:A mark king darren mole vico
sadd follow:C mark king darren

# C可能认识的人:
sdiff follow:A follow:C

zset

有序集合;用来实现排行榜;他是一个有序唯一

基础命令

复制代码
# 添加到键为key有序集合 (sorted set) 里面
ZADD key [NX|XX] [CH] [INCR] score member [score member ...]

# 从键为key有序集合中删除 member 的键值对
ZREM key member [member ...]

# 返回有序集key中,成员member的score值
ZSCORE key member

# 为有序集key的成员member的score值加上增量increment
ZINCRBY key increment member

# 返回key的有序集元素个数
ZCARD key

# 返回有序集key中成员member的排名
ZRANK key member

# 返回存储在有序集合key中的指定范围的元素 order by id limit 1,100
ZRANGE key start stop [WITHSCORES]

# 返回有序集key中,指定区间内的成员(逆序)
ZREVRANGE key start stop [WITHSCORES]

存储结构

应用

①热搜

复制代码
# 点击新闻:
zincrby hot:20230612 1 10001
zincrby hot:20230612 1 10002
zincrby hot:20230612 1 10003
zincrby hot:20230612 1 10004
zincrby hot:20230612 1 10005
zincrby hot:20230612 1 10006
zincrby hot:20230612 1 10007
zincrby hot:20230612 1 10008
zincrby hot:20230612 1 10009
zincrby hot:20230612 1 10010

# 获取排行榜:
zrevrange hot:20230612 0 9 withscore

②延时队列

将消息序列化成一个字符串作为 zset 的 member;这个消息的到期处理时间作为 score,然后用多个线程轮询 zset 获取到期的任务进行处理。

python 复制代码
def delay(msg):
    msg.id = str(uuid.uuid4())  # 保证 member 唯一
    value = json.dumps(msg)
    retry_ts = time.time() + 5  # 5s后重试
    redis.zadd("delay-queue", retry_ts, value)

# 使用连接池
def loop():
    while True:
        values = redis.zrangebyscore("delay-queue", 0, time.time(), start=0, num=1)
        if not values:
            time.sleep(1)
            continue
        value = values[0]
        success = redis.zrem("delay-queue", value)
        if success:
            msg = json.loads(value)
            handle_msg(msg)

# 缺点:loop 是多线程竞争,两个线程都从zrangebyscore获取到数据,但是zrem 一个成功一个失败。
# 优化:为了避免多余的操作,可以使用lua脚本原子执行这两个命令
# 解决:漏斗限流

③分布式定时器

生产者将定时任务hash到不同的redis实体中,为每一个redis实体分配一个dispatcher进程,用来定时获取redis中超时事件并发布到不同的消费者中;

④时间窗口限流

系统限定用户的某个行为在指定的时间范围内(动态)只能发生N次;

lua 复制代码
# 指定用户 user_id 的某个行为 action 在特定时间内 period 只允许发生该行为最大次数 max_count

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:zadd(key, now, now)

    -- 移除时间窗口之前的行为记录,剩下的都是时间窗口内的记录
    red:zremrangebyscore(key, 0, now - period *100)

    -- 获取时间窗口内的行为数量
    red:zcard(key)

    -- 设置过期时间,避免冷用户持续占用内存 时间窗口的长度+1秒
    red:expire(key, period + 1)

    local res = red:commit_pipeline()
    return res[3] <= max_count
end

# 维护一次时间窗口,将窗口外的记录全部清理掉,只保留窗口内的记录;
# 缺点:记录了所有时间窗口内的数据,如果这个量很大,不适合做这样的限流;漏斗限流
# 注意:如果用 key + expire 操作也能实现,但是实现的是熔断限流,这里是时间窗口限流的功能;

小结

https://github.com/0voice

相关推荐
木易 士心2 小时前
自然语言转数据库操作语句原理架构图分析和实现
数据库·后端
TDengine (老段)2 小时前
TDengine IDMP 1-产品简介
大数据·数据库·物联网·时序数据库·tdengine·涛思数据
heimeiyingwang2 小时前
【架构实战】缓存架构 Redis 集群部署
redis·缓存·架构
娇娇yyyyyy2 小时前
QT编程(20): Qt QListWidget QTreeWidget介绍
数据库·qt·microsoft
阿里云瑶池数据库2 小时前
阿里云瑶池数据库KVCache亮相NVIDIA GTC 2026
数据库·阿里云
橙子家2 小时前
行式存储(Row-based Storage)和列式存储(Column-base Storage)简介
数据库
l1t10 小时前
DeepSeek总结的 pg_regresql插件:真正可移植的 PostgreSQL 统计信息
数据库·postgresql
oradh10 小时前
Oracle 11.2.0.1版本升级至11.2.0.4_单机环境
数据库·oracle·oracle11g·oracle升级
l1t10 小时前
用docker安装测试crate数据库
数据库·docker·容器·cratedb