Redis相关命令与原理

Redis相关命令与原理 文章目录概要安装和编译启动Redis的数据类型key的设计规范string基础命令应用list基础命令应用hash基础命令存储结构应用set基础命令应用zset基础命令存储结构应用概要Redis是Remote Dictionary Service的简称 远程字典服务。Redis是一种内存数据库运行redis的进程突然关闭或者服务器宕机了会导致数据的直接丢失但是其他进程可以直接在内存中访问redis进程中的数据速度非常快。和磁盘读写有10w倍的差异。Redis是一种key-value数据库通过key操作value通过unordered_map散列表的形式组织起来的。Redis支持多种数据结构value的数据类型stringlist 链表hash 哈希表set 无序集合zset 有序集合安装和编译gitclone https://github.com/redis/redis.gitcdredisgitcheckout6.2.14makemaketestsudomakeinstall# 默认安装在 /usr/local/bin# redis-server 是服务端程序# redis-cli 是客户端程序启动mkdirredis-data# 把redis文件夹下 redis.conf 拷贝到 redis-data# 修改 redis.conf# requirepass 修改密码 123456# daemonize yescdredis-data redis-server ./redis.confpsaux|grepredis-server# 通过 redis-cli 访问 redis-serverredis-cli-h127.0.0.1-a123456Redis的数据类型127.0.0.1:6379TYPE key stringstring:安全的二进制字符串 安全字符串有描述长度的信息而不是以\0作为结束符分割127.0.0.1:6379setkey value OK127.0.0.1:6379get keymarklist:双端队列插入有序127.0.0.1:6379lpush list value1 value2 value3(integer)3127.0.0.1:6379lrange list0-11)value12)value23)value3hash:对顺序不关注filed是唯一的127.0.0.1:6379hmset flame:10001 name flame age18OK127.0.0.1:6379hgetall flame:100011)name2)flame3)age4)18127.0.0.1:6379HINCRBY flame:10001 age1(integer)19set:127.0.0.1:6379sadd flame:10002 member1 member2 member3(integer)3127.0.0.1:6379SMEMBERS flame:100021)member12)member33)member2zset:对顺序不关注 集合是127.0.0.1:6379zadd ranks100value1(integer)1127.0.0.1:6379zadd ranks101value2(integer)1127.0.0.1:6379zadd ranks130value3(integer)1127.0.0.1:6379zadd ranks120value2(integer)0127.0.0.1:6379zrange ranks0-1withscores1)value12)1003)value24)1205)value36)130以上五种数据结构能起到去重作用的string hash set zsetkey的设计规范单个功能一个key多个功能多个key 层级间以 分开redis可视化操作平台基本用归类keystring安全的二进制字符串字符数组该字符串是动态字符串raw字符串长度小于1M时加倍扩容;超过1M每次只多扩1M;字符串最大长度为512M;注意:redis字符串是二进制安全字符串;可以存储图片二进制协议等二进制数据;基础命令# 设置 key 的 value 值SET key val# 获取 key 的 valueGET 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格式# 第一部分设置和获取角色数据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②累加器incr reads # 累计加100 incrby reads 100③分布式锁# 加锁、加锁和解析 redis 实现是 非公平锁 etcd zk 用来实现公平锁# 阻塞等待 阻塞连接的方式# 介绍简单的原理事务setnx lock1# 不存在才能设置 定义加锁行为 占用锁setnx lock uuid# expire 30 过期setlock uuid nx ex30# 释放锁del lockif(get(lock)uuid)del(lock);④位运算# 猜测一下 string 是用的 int 类型 还是 string 类型# 月签到功能 10001 用户id 202106 2021年6月份的签到 6月份的第1天setbit sign:10001:20210611# 计算 2021年6月份 的签到情况bitcount sign:10001:202106# 获取 2021年6月份 第二天的签到情况 1 已签到 0 没有签到getbit sign:10001:2021062list双向链表实现列表首尾操作(删除和增加)时间复杂度o(1);查找中间元素时间复杂度为o(n);列表中数据是否压缩的依据:1.元素长度小于48不压缩;2.元素压缩前后长度差不超过8不压缩;基础命令lpush在左侧插入rpush在右侧插入lpop在左侧弹出rpop在右侧弹出# 从队列的左侧入队一个或多个元素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 zsetLREM key count value# 它是 RPOP 的阻塞版本因为这个命令会在给定list无法弹出任何元素的时候阻塞连接BRPOP keytimeout# 超时时间 延时队列 为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 40003set集合;用来存储唯一性字段不要求有序;存储不需要有序操作(交并差集的时候排序)?基础命令其中交并补操作比较重要# 添加一个或多个指定的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:Czset有序集合用来实现排行榜他是一个有序唯一基础命令# 添加到键为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 获取到期的任务进行处理。defdelay(msg):msg.idstr(uuid.uuid4())# 保证 member 唯一valuejson.dumps(msg)retry_tstime.time()5# 5s后重试redis.zadd(delay-queue,retry_ts,value)# 使用连接池defloop():whileTrue:valuesredis.zrangebyscore(delay-queue,0,time.time(),start0,num1)ifnotvalues:time.sleep(1)continuevaluevalues[0]successredis.zrem(delay-queue,value)ifsuccess:msgjson.loads(value)handle_msg(msg)# 缺点loop 是多线程竞争两个线程都从zrangebyscore获取到数据但是zrem 一个成功一个失败。# 优化为了避免多余的操作可以使用lua脚本原子执行这两个命令# 解决漏斗限流③分布式定时器生产者将定时任务hash到不同的redis实体中为每一个redis实体分配一个dispatcher进程用来定时获取redis中超时事件并发布到不同的消费者中;④时间窗口限流系统限定用户的某个行为在指定的时间范围内(动态)只能发生N次;#指定用户 user_id 的某个行为 action 在特定时间内 period 只允许发生该行为最大次数 max_countlocalfunctionis_action_allowed(red,userid,action,period,max_count)localkeytab.concat({hist,userid,action},:)localnowzv.time()red:init_pipeline()-- 记录行为red:zadd(key,now,now)-- 移除时间窗口之前的行为记录剩下的都是时间窗口内的记录red:zremrangebyscore(key,0,now-period*100)-- 获取时间窗口内的行为数量red:zcard(key)-- 设置过期时间避免冷用户持续占用内存 时间窗口的长度1秒red:expire(key,period1)localresred:commit_pipeline()returnres[3]max_countend#维护一次时间窗口将窗口外的记录全部清理掉只保留窗口内的记录#缺点记录了所有时间窗口内的数据如果这个量很大不适合做这样的限流漏斗限流#注意如果用 keyexpire 操作也能实现但是实现的是熔断限流这里是时间窗口限流的功能https://github.com/0voice