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

相关推荐
xcjbqd01 天前
CSS如何给Bootstrap侧边菜单加图标_使用font-awesome结合CSS
jvm·数据库·python
KevinCh1 天前
Vespa:面向 AI 时代的检索与排序服务平台
数据库
Rick19931 天前
Redis查询为什么快
数据库·redis·缓存
fly spider1 天前
MySQL索引篇
android·数据库·mysql
oradh1 天前
Oracle数据库表存储基本概述
数据库·oracle·oracle基础·oracle入门·oracle表存储
为什么不问问神奇的海螺呢丶1 天前
Oracle Golden Gate 19c 微服务版 (19.1.0.0.4) 静默安装
数据库·微服务·oracle
NineData1 天前
使用NineData实现MySQL异地多活场景
运维·数据库·mysql
森叶1 天前
逻辑仲裁者:实现多事件关联匹配与事务原子化后执行逻辑的技术方案
数据库·oracle
Navicat中国1 天前
北京理工大学推荐 Navicat | 高校教育行业应用案例
数据库·navicat·高校·教育版
素玥1 天前
实训7 json文件数据用python导入数据库
数据库·python·json