redis基本操作

redis是Remote Dictionary Service的简称,也就是远程字典服务。redis 是内存数据库,KV数据库,数据结构数据库。编译安装登录这些操作在之前已经介绍过,这里不再赘述。

redis存储结构

redis有多种存储结构,主要是val的类型不同,而key都是string类型。
在这里插入图片描述
这些存储结构在不同的数据量下,内部使用的数据结构可能是不一样的。
在这里插入图片描述

string

字符数组,该字符串是动态字符串,字符串长度小于1M时,加倍扩容,超过1M每次只多扩1M,字符串最大长度为512M。
redis字符串是二进制安全字符串,可以存储图片,二进制协议等二进制数据。什么叫做二进制安全字符串呢?C中字符数组是以’\0’结尾,这样对于数据中有该特殊字符的结构而言,就会被异常截断导致不安全。redis中string是以长度界定一个字符串,类似header中包含length的做法,而不是以某个特殊字符作为分隔符,这样就不会被异常截断。

基础命令

# 设置 key 的 value 值
SET key val
# 获取 key 的 value
GET key
# 执行原子加一的操作
INCR key
# 执行原子加一个整数increment的操作
INCRBY key increment
# 执行原子减一的操作
DECR key
# 执行原子减一个整数的操作
DECRBY key decrement
# 如果key不存在,这种情况下等同SET命令。 当key存在时,什么也不做
SETNX key value
# 删除 key val 键值对
DEL key
# 设置或者清空key的value(字符串)在offset处的bit值。
SETBIT key offset value
# 返回key对应的string在offset处的bit值
GETBIT key offset
# 统计字符串被设置为1的bit数.
BITCOUNT key

存储结构

字符串长度小于等于 20 且能转成整数,则使用 int 存储,字符串长度小于等于 44,则使用 embstr 存储,字符串长度大于44,则使用 raw 存储。
这里提出一个问题,为什么redis字符串存储小于等于44字节时,是 embstr 类型,而超过44是 raw 类型?
先给出部分redis源码

typedef struct redisObject {
    unsigned type:4;
    unsigned encoding:4;
    unsigned lru:LRU_BITS; /* LRU time (relative to global lru_clock) or LFU data (least significant 8 bits frequency and most significant 16 bits access time).*/
    int refcount;
    void *ptr;
} robj;

struct __attribute__ ((__packed__)) sdshdr8 {
    uint8_t len; /* used */
    uint8_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};

#define OBJ_ENCODING_EMBSTR_SIZE_LIMIT 44

robj *createStringObject(const char *ptr, size_t len) {
    if (len <= OBJ_ENCODING_EMBSTR_SIZE_LIMIT)
        return createEmbeddedStringObject(ptr,len);
    else
        return createRawStringObject(ptr,len);
}

首先指出,redis内部使用内存池jemalloc,其中有内存分配器。对于redis内存分配器而言,64B是内存分配的基准值,小于等于64B是小字符串,大于64B是大字符串。后面还有很多64B的地方。对于存储结构而言,我们一看到64B,就要想到分配器。
这里44B是什么意思呢?redisObject 占用16个字节; sdshdr8 占用 3+x+1 个字节(后面加1是因为 charbuf[] 要预留一个 ‘\0’ )。
如之前所说,redis 内存分配器认为大于 64个字节为大字符串,所以这里留给小字符串的大小为 64-16-3-1 = 44B 。

应用

1、对象存储
string用作对象存储,要求存储对象中的字段尽量不要修改,否则需要全部删除再重写。

SET role:10001 '{["name"]:"mark",["sex"]:"male",["age"]:30}'
GET role:10001

这里指出一点,key字段用冒号分隔,可以包含多个有意义的字段。后面还会有很多这样的例子,方便与其他数据库和其他业务联合使用。
2、累加器

# 统计阅读数 累计加1
# 如果当前没有该字段,执行时会默认创建,并将值置为0再进行累加
incr reads
# 累计加100
incrby reads 100

3、分布式锁

# 加锁
setnx lock 1
# 释放锁
del lock

4、位运算

# 月签到功能 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

list是由双向链表实现,列表首尾操作(删除和增加)时间复杂度 O(1) ;查找中间元素时间复杂度为O(n)。
列表中数据是否压缩的依据:元素长度小于 48,不压缩;元素压缩前后长度差不超过 8,不压缩。数据量少的时候,list底层用的是压缩列表;数据量大的时候用的是死双向链表。
redis中有这样一个特性:redis在数据量少的时候,使用节省空间的数据结构,因为此时查询等操作耗时不大,主要以节省空间为主;数据量大的时候,就要为了访问性能,改用时间复杂度更低的数据结构。一句话总结,数据少省空间,数据多以性能为主。

基础命令

# 从队列的左侧入队一个或多个元素
LPUSH key value [value ...]
# 从队列的左侧弹出一个元素
LPOP key
# 从队列的右侧入队一个或多个元素
RPUSH key value [value ...]
# 从队列的右侧弹出一个元素
RPOP key
# 返回从队列的 start 和 end 之间的元素 0, 1 2
LRANGE key start end
# 从存于 key 的列表里移除前 count 次出现的值为 value 的元素
LREM key count value
# 它是 RPOP 的阻塞版本,因为这个命令会在给定list无法弹出任何元素的时候阻塞连接
BRPOP key timeout

应用

1、栈

LPUSH + LPOP
# 或者
RPUSH + RPOP

2、队列

LPUSH + RPOP
# 或者
RPUSH + LPOP

3、阻塞队列

LPUSH + BRPOP
# 或者
RPUSH + BLPOP

这里提醒一下,阻塞队列阻塞的是这个连接,不是redis库。
4、异步消息队列
操作与队列一样,但是在不同系统间;
在这里插入图片描述
5、获取固定窗口记录

# 在某些业务场景下,需要获取固定数量的记录;比如获取最近50条战绩;这些记录需要按照插入的先后顺序返回
# 裁剪最近5条记录
ltrim says 0 4
lrange says 0 -1

实际项目中需要保证命令的原子性,所以一般用 lua 脚本 或者使用 pipeline 命令

-- redis lua脚本
local record = KEYS[1]
redis.call("LPUSH", "says", record)
redis.call("LTRIM", "says", 0, 4)

hash

散列表,在很多高级语言当中包含这种数据结构。redis中hash类似于stl中unordered_map。

基础命令

# 获取 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 实现。还要注意,转为了dict之后,即使删去了不满足要求的节点,也转不回ziplist了。

应用

1、对象存储
与string相比,hash用作对象存储的时候,里面的字段可以修改,因为里面的字段filed修改不复杂。

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 role:10001
# 将得到的字符串调用json解密,取出字段,修改 age 值
# 再调用json加密
set role:10001 '{["name"]:"mark",["sex"]:"male",["age"]:19}'

2、购物车
不只是redis,实际应用中,通常一个功能不会只使用一个数据结构,需要多个数据结构联合使用来满足需求。这里就用到了hash存储商品的信息,list用来确保商品的显示顺序。

# 将用户id作为 key
# 商品id作为 field
# 商品数量作为 value
# 注意:这些物品是按照我们添加顺序来显示的,所以使用了list结构
# 添加商品:
hset MyCart:10001 40001 1
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

set是集合,用来存储唯一性字段,不要求有序,类似stl中unordered_set。set本身不要求有序,但是内部存储结构是有序的。另外,intset、hashtable也是有序的。

基础命令

# 添加一个或多个指定的member元素到集合的 key中
SADD key member [member ...]
# 计算集合元素个数
SCARD key
# 返回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 ...]

存储结构

元素都为整数且节点数量小于等于 512(set-max-intset-entries),则使用整数数组存储,元素当中有一个不是整数或者节点数量大于 512,则使用字典存储。

应用

1、抽奖

# 添加抽奖用户
sadd Award:1 10001 10002 10003 10004 10005 10006
sadd Award:1 10009
# 查看所有抽奖用户
smembers Award:1
# 抽取多名幸运用户
srandmember Award:1 10

2、查看共同关注
本质上就是求交集。

sadd follow:A mark king darren mole vico
sadd follow:C mark king darren
sinter follow:A follow:C

3、推荐可能认识的好友
本质上是求差集。

sadd follow:A mark king darren mole vico
sadd follow:C mark king darren
# C可能认识的人:
sdiff follow:A follow:C

zset

zset是有序集合用,是一个有序结构,score实现有序,member实现唯一。

基础命令

# 添加到键为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中的指定范围的元素
ZRANGE key start stop [WITHSCORES]
# 返回有序集key中,指定区间内的成员(逆序)
ZREVRANGE key start stop [WITHSCORES]

应用

1、热搜榜

# 点击新闻:添加过程省略,点击一次计数加1
zincrby hot:20210203 1 10001
zincrby hot:20210203 1 10002
zincrby hot:20210203 1 10003
zincrby hot:20210203 1 10004
zincrby hot:20210203 1 10005
zincrby hot:20210203 1 10006
zincrby hot:20210203 1 10007
zincrby hot:20210203 1 10008
zincrby hot:20210203 1 10009
zincrby hot:20210203 1 10010
# 获取排行榜:
zrevrange hot:20210203 0 9 withscores

延时队列

将消息序列化成一个字符串作为 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脚本原子执行这两个命令
# 解决:漏斗限流

2、分布式定时器
生产者将定时任务利用hash抛到不同的redis实体中。每一个redis实体有一个dispatcher进程,用来定时获取redis中超时事件并发布到不同的消费者中。
在这里插入图片描述
与延时队列区分,如果是在一个进程中,叫延时队列;如果是在一个进程中,叫分布式定时器。
3、时间窗口限流

# python脚本
# 指定用户 user_id 的某个行为 action 在特定时间内 period 只允许发生最多的次数max_count
def is_action_allowed(userid, action, period, max_count):
    key = 'hist:%s:%s' % (userid, action)
    now_ts = int(time.time()*1000) # 毫秒时间戳
    with client.pipeline() as pipe:
        # 记录行为
        pipe.zadd(key, now_ts, now_ts)
        # 移除时间窗口之前的行为记录,剩下的都是时间窗口内的
        pipe.zremrangebyscore(key, 0, now_ts - period * 1000)
        # 获取时间窗口内的行为数量
        pipe.zcard(key)
        # 设置过期时间,避免冷用户持续占用内存 时间窗口的长度+1秒
        pipe.expire(key, period + 1)
        _,_,current_count,_ = pipe.execute()
    return current_count <= max_count
can_reply = is_action_allowed(10001, "replay", 60, 5)

if can_reply:
    do_reply()
else:
    raise ActionThresholdOverflow()
# 维护一次时间窗口,将窗口外的记录全部清理掉,只保留窗口内的记录;
# 缺点:记录了所有时间窗口内的数据,如果这个量很大,不适合做这样的限流;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
回答: Redis数据库是一个内存数据库,通过key-value键值对的方式进行数据存储。它的优点包括访问速度快、适用于缓存系统和存储大量活跃数据,可以提高网站的响应速度。\[1\]在Java中,有两个比较出名的连接Redis的驱动包,分别是Jedis和Redisson,我们可以使用Jedis来进行程序操作Redis。\[3\]以下是一些基本的Redis操作方法: - 使用`new Jedis(host, port)`创建Jedis对象,其中host是Redis服务器地址,port是Redis服务端口。 - 使用`set(key, value)`方法设置字符串类型的数据。 - 使用`get(key)`方法获取字符串类型的数据。 - 使用`hset(key, field, value)`方法设置哈希类型的数据。 - 使用`hget(key, field)`方法获取哈希类型的数据。 - 使用`lpush(key, values)`方法设置列表类型的数据。 - 使用`lpop(key)`方法从列表左侧弹出元素。 - 使用`rpop(key)`方法从列表右侧弹出元素。 - 使用`del(key)`方法删除指定的key。\[3\] #### 引用[.reference_title] - *1* [redis基本操作](https://blog.csdn.net/m0_50782114/article/details/128119748)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* *3* [Redis基本使用!](https://blog.csdn.net/weixin_50769390/article/details/127134134)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值