共计 9656 个字符,预计需要花费 25 分钟才能阅读完成。
前言
R: 代表 redis-cli
P: 代表 python 的 redis
准备
pip install redis
pool = redis.ConnectionPool(host='39.107.86.223', port=6379, db=1)
redis = redis.Redis(connection_pool=pool)
redis. 所有命令
下面命令所有命令我都省略了,有和 Python 内置函数冲突的我会加上 redis.
全局命令
-
dbsize(返回 key 的数量)
R: dbsize P: print(redis.dbsize())
-
exists(是否存在某 key)
R: exists name P: exists('name')
-
keys(列出所有 key, 可使用通配符)
R: keys na* P: keys('na*') 注:时间复杂度为 O(n)
-
scan (对应 keys,迭代取出所有 keys)
R: scan 0 match '*' count 4 P: keys_iter = redis.scan_iter(match='*', count=4) 注:这种 scan,API 后面也会有,所以我全部放在最后的结束语中讲
-
info (查看资源信息)
R: info # 也可以填参数 info memory info cpu 等 P: redis.info() # redis.info('CPU') redis.info('MEMORY')
-
type (列出类型)
R: type name P: redis.type('name') # type 和 python 的冲突了,所以这里我写全了 redis 中类型有:none string list set zset hash
过期时间
-
expire(设置)
R: expire name 秒数 P: expire('name', 秒数)
-
ttl(查询)
R: ttl name P: ttl('name') # 返回剩余过期时间值 # 返回值为 -2 则代表 无此 key # 返回值为 -1 则代表 有此 key,但未设置过期时间
-
persist(删除)
R: persist name P: persist('name')
-
自增,自减
incr incrby 加上一个整数 R: incr age 或 incrby age 1 P: incr age 1 或 incrby age 1 # python 实现 的 incr 被 重定向为 incrby,所以用哪个都行 decr decrby 减去一个整数 同上 incrbyfloat 加减一个浮点数 同上
字符串相关操作
-
set 设置值
R: set name lin P: redis.set('name', 'lin') set 选项(原子操作)nx(设置默认值)R: set name lin nx P: redis.set('name', 'lin', nx=True) 注:nx 代表 key 不存在才会将值设置成功,类似 python dict 的 setdefault,即给 key 设置默认值 xx(更新值)R: set name Tom xx P: redis.set('name', 'lin', xx=True) 注:xx 代表 key 存在才会将值更新成功。如果 key 不存在,则更新失败。
-
get 获取值
R: get name P: redis.get('name') 注:通过 py redis 客户端的 get 取出的都是 字节二进制类型,所以需要手动转为对应类型 前面提到的 incr decr 等,操作返回结果直接就是 int,而非 字节类型
-
mset 批量设置
R: mset name lin age 18 p: redis.mset({'name': 'lin', 'age': 18} )
-
mget 批量获取
R: mget name age p: redis.mget('name', 'age') # 返回值为 字节类型的 列表
-
getset 设置新值并返回旧值
R: getset name zhang P: print(redis.getset('name', 'zhang') )
-
append 字符串追加拼接
R: append name abc P: redis.append('name', 'abc')
-
strlen 获取字符串长度
R: strlen name P: print(redis.strlen('name') ) 注:与编程语言的普遍 API 不同的是,strlen 返回的字符串 长度是 字符对应编码的长度。。。。中文 utf-8 占 3 个字节
-
getrange 字符串切片(从 0 开始,前闭后闭)
R: getrange name 1 2 P: redis.getrange('name', 1, 2)
-
setrange 字符串按索引赋值(覆盖)
R: setrange name 0 abc # 把第 0 个位置开始,逐个覆盖赋值为 abc,多余的不变 P: redis.setrange('name', 0, 'abc')
-
del 删除键值
R: del k1 k2 P: redis.delete(k1, k2)
Hash 相关操作 (可对应为 文档 - 属性 - 值)
-
hset 设置 1 条文档,1 个属性 - 值
R: hset user name lin P: redis.hset('user', 'name', 'lin')
-
hget 获取 1 条文档,1 个属性
R: hget user name P: print(redis.hget('user', 'name'))
-
hmset 设置 1 条文档,多个属性 - 值
R: hmset user name lin age 18 P: redis.hmset('user', {'user': 'lin', 'age': 18})
-
hmget 获取 1 条文档,多个属性 - 值
R: hmget user name age P: print(redis.hmget('user', 'name', 'age'))
-
hkeys 获取所有 key
R: hkeys user P: print(redis.hkeys('user'))
-
hvals 获取所有 values
R: hvals user P: print(redis.hvals('user'))
-
hgetall 获取 一条文档,所有属性值(慎用,见下一条 API)
R: hgetall user # 返回为列表,偶数索引为 key,奇数索引为 vaLue(从 0 开始) P: print(redis.hgetall('user')) # 返回为 dict 格式 注:hgetall 会将所有 key-value 取出来,所以数据量庞大可能会造成性能影响。大批量数据在 python 是怎么处理来着???????没错,就是迭代器,当然 python 的 redis 模块已为我们封装好一个 API,hscan_iter, 见一条 API
-
hscan(hash 迭代,大体上可代替 hgetall 使用)
R: hscan user 0 match * count 200 # 按游标,按数量取 # 0 代表游标从头开始 # match 是关键字 # * 是 key 的通配符 # count 是一次接待的条数 P: result_iter = redis.hscan_iter('user', match= 'na*', count=2) # python 的 cursor 参数没有,是因为源码中被固定设置为 0 了,其他参数解释同上 # 返回结果为可迭代对象,可遍历取出。
-
hexists 检测是否存在某 key
R: hexists user name1 # 存在返回 1,不存在返回 0 P: print(redis.hexists('user', 'name')) # 存在返回 True
-
hlen 统计获取一个文档,所有属性的 总数
R: hlen user P: print(redis.hlen('user'))
List 相关操作
-
lpush (左压栈)
R: lpush list1 1 2 3 P: redis.lpush('list1', 1,2,3)
-
rpush (右压栈,同左压栈,略)
-
lpop (左弹栈)
R: lpop list2 P: print(redis.lpop('list2'))
-
rpop (右弹栈,同左弹栈,略)
-
blpop (左阻塞弹栈,列表为空时,就阻塞了)
R: blpop list2 1000 # 1000 为过期时间为 1000 秒,1000 秒后自动解除阻塞,有值加入也会解除阻塞 P: redis.blpop('list2', timeout=1000)
-
brpop (右阻塞弹栈,同左阻塞弹栈,略)
-
linsert (在指定 值 的 前后 插入值)
R: linsert list2 before Tom jerry # 在 Tom 前插入 jerry, before 代表之前 P: redis.linsert('list2', 'after', 'b', 'Tom') # 在 b 的后面插入 Tom, after 代表之后
-
lset (按索引赋值, 注意索引不要越界)
R:lset list2 4 zhang P: redis.lset('list2', 4, 'zhang')
-
lindex (按索引取值, 索引可正可负)
R: lindex list2 -3 P: print(redis.lindex('list2', 3))
-
llen(获取列表元素个数)
R: llen list2 P: print(redis.llen('list2'))
-
ltrim (注意:在原数据上切片,不返回值。)
R: ltrim list2 3 10 # 保留 索引 3-10 的列表数据,其他都删除 P: print(redis.ltrim('list2', 2, -1)) # 索引前闭后闭,可正可负
-
lrem (删除指定值)
R: lrem list2 0 Tom # 0 这个位置的参数代表删除值的个数 # 0 代表全部删除,删除全部 Tom 值 # 正数代表 从左到右 删除 n 个。eg: lrem list2 5 Tom 即为 从左到右 删除 5 个 Tom 值 # 负数代表 从右到左 删除 n 个。eg: lrem list2 -5 Tom 即为 从右到左 删除 5 个 Tom 值 P: print(redis.lrem('list2', -5, 1)) # 解释同上
-
lrange(遍历,正负索引都可,前闭后闭)
R: lrange list1 0 -1 P: print(redis.lrange('list2', 0, -1))
Set 相关操作
-
sadd(插入元素)
R: sadd set1 1 2 3 P: redis.sadd('set1', *[1,2,3])
-
srem(删除指定值的元素)
R: srem set1 Tom P: redis.srem('set1', 'Tom')
-
scard(获取集合中元素个数)
R: scard set1 P: redis.scard('set1')
-
sismember (判断某元素是否在集合)
R: sismember set1 Tom P: redis.sismember('set1', 'Tom')
-
srandmember (随机取出集合指定个数元素)
“”“类似 py 的 random.choices,注意有 s”“R: srandmember set1 2 # 从集合随机中取出 2 个元素 P: redis.srandmember('set1', 2)
-
smembers (取出集合中所有元素)
R: smembers set1 P: redis.smembers('set1') 注:同 hgetall,如果一次性取出,可能会出问题,所以需要迭代获取,见下 sscan
-
sscan (游标 / 迭代取出集合所有元素)
R: sscan set1 0 match * count 200 P: result_iter = redis.sscan_iter('set1', match='*', count=200) # 遍历迭代
-
sdiff (差集)
R: sdiff sset1 sset2 P: print(redis.sdiff('sset1', 'sset2'))
-
sinter(交集)
R: sinter sset1 sset2 P: print(redis.sinter('sset1', 'sset2'))
-
sunion (并集)
R: sunion sset1 sset2 P: print(redis.sunion('sset1', 'sset2'))
zset 有序集合相关操作
-
zadd (有序插入)
R: zadd zset 100 Tom 90 Jerry # 100 是权重,Tom 是数据值,注意 redis-cli 权重在前,值在后 P: redis.zadd('zset', {'Tom': 100, 'Jerry': 90}) # 注意,py 语法,权重作为字典的 value 注特别注意:zadd 的默认机制是同值,不同权重时,会更新值的权重 eg: 上面再插入一条 Tom, 不过这次的权重是 50(zadd zset 50 Tom),则 Tom 的权重会更新为 50 这时就延申出 2 个参数,(应该还记得前面讲的 set 的 nx 和 xx 参数吧,没错 zadd 也有)nx:(不存在才更新(添加),存在则更新(添加)失败)R: zadd zset nx 1000 Tom P: redis.zadd('zset',{'Tom': 1000}, nx=True) 注:如果 Tom 这个值之前存在,则这个 1000 就不会被更新了 若不存在,则就会新创建,并把这个 1000 设置成功 nx:(存在才更新(添加),不存在则更新(添加)失败)R: zadd zset xx 1000 Tom P:redis.zadd('zset',{'Tom': 1000}, xx=True) 注:如果 Tom 这个值之前存在,则 1000 才会更新成功 如果不存在,比如 {'张三':500}, 张三本来就不存在,用了 xx, 他不会被添加进来,更何谈更新
-
zrange (遍历)
R: zrange zset 0 -1 P: print(redis.zrange('zset', 0, -1)) # 返回值为列表 withscores 参数(把权重也带出来返回):R: zrange zset 0 -1 withscores # 注意,返回时 奇数位 是值,偶数位是权重 P: print(redis.zrange('zset', 0, -1, withscores=True)) # 返回列表嵌套元组,[(值,权重)]
-
zrevrange (逆序 - 降序,遍历)
这条 API 就是多了 "rev" 三个字母, reversed 单词 熟悉把,python 内置逆序高阶函数。。就是那个意思 操作同 zrange,略
-
zrangebyscore (根据权重来遍历)
R: zrangebyscore zset 40 99 limit 1 3 # 查出权重在 40-99 之内的数据,并从第 1 条开始,返回 3 条 # 40-99 都是闭区间,要想变成开区间这样写即可 (40 (99 P: print(redis.zrangebyscore('zset', 40, 99, start=1, num=3))
-
zrevrangebyscore (根据权重来 逆序遍历)
操作同 zrangebyscore,略 这 API 设计的,还不如,直接弄成一条命令,然后加一个逆序参数,吐槽!!!!
-
zrem (删除某值)
R: zrem zset Tom # 删除 Tom 这个值 P: print(redis.zrem('zset','Tom'))
-
zremrangebyscore (删除 权重 范围内的值)
R: zremrangebyscore zset 70 90 # 把权重在 70-90 分的所有数据删除 P: redis.zremrangebyscore('zset', 70, 90)
-
zremrangebyrank (删除 索引 范围内的值)
R: zremrangebyrank zset 0 -1 # 删除所有值(0 到 - 1 的索引就代表所有值啦!)P: redis.zremrangebyrank('zset', 0, -1) # redis 的 API 风格真的。。。没办法 python 也无奈同名
-
zcard (获取有序集合的 所有 元素个数)
R: zcard zset P: print(redis.zcard('zset'))
-
zcount (统计有序集合的 某权重范围的 元素个数)
R: zcount zset 10 69 # 同样默认闭区间,( 可改为开区间 P: print(redis.zcount('zset',50, 69))
-
zrank (获取某元素的索引)
R: zrank zset Jerry # 不用猜,索引肯定从 0 开始 P: print(redis.zrank('zset', 'Jerry'))
-
zrevrank (逆序 获取某元素的索引)
逆序获取索引,比如最后一个,索引就是 0 具体操作,同 zrank,略
-
zscore (获取某元素对应的权重)
R: zscore zset Jerry P: print(redis.zscore('zset', 'Jerry'))
-
zscan (迭代方式和返回 所有元素及其权重)
""" 嗯? 似曾相识燕归来?前面说过的 scan hsacn sscan 还有接下来要说的 zscan 都是一个样子的,都是为了应对大数据来迭代处理 python 版的 redis 给了我们一个简化函数, 那就是 _iter 结尾的,eg: hscan_iter() 这种 _iter 结尾的函数,不用我们来传游标 cursor 参数,为啥呢?? 一. 因为 python 有生成器 - 迭代器机制阿!(当然 _iter 等函数的源码就是用 yield 为我们实现的)二. cursor 游标不易于管理 """ R: zscan zset 0 match * count 5 P: zset_iter = redis.zscan_iter('zset', match='*', count=5) # 同理返回可迭代对象 注:还要说明一下:match 参数: 过滤查询数据(其实过滤完了,数据量小了也没必要用 scan 了,此参数主要用在 "hscan" 之类的)"因此 match 参数可不写", "match='*'和 不传是一个效果的。" count 参数:Py 源码解释 ``count`` allows for hint the minimum number of returns 意思就是:这个参数是一次迭代 "最少" 取 5 个 ",但不管怎么说,最终还是会取出全部数据!!
Redis 两种持久化的方式
-
生成 RDB 文件(三种方法)
"""RDB 机制就是 触发生成 RDB 文件,将 Redis 数据以二进制形式写入其中,触发方式有如下三种""" RDB 基本配置:vi /etc/redis/redis.conf dbfilename dump.rdb # 配置 RDB 文件名 dir /var/lib/redis # 配置 RDB 文件存放目录(ll 命令查看 dump.rdb 是否为最新时间)appendonly no # 若为 yes, 会优先按照 aof 文件来恢复,或不恢复 上述配置,可在下面三种方法实现的时候,自动触发生成 RDB 文件。并在 redis 启动时恢复 RDB 文件
-
触发方式 1:save(阻塞)
R: save P: redis.save()
-
触发方式 2:bgsave(开 fork 进程,异步, 非阻塞)
R: bgsave P: redis.bgsave()
-
触发方式 3:自动动态生成 RDB 文件(配置文件)
在上面 RDB 基本配置基础上,追加如下配置 vi /etc/redis/redis.conf save 100 10 # 100 秒钟改变 10 条数据就会,自动生成 RDB 文件
-
RDB 缺点
大数据耗时,RDB 文件写入影响 IO 性能。宕机数据不可控
-
生成 AOF 文件(三种方法)
"""AOF 机制就是 每执行一条命令,都会记录到缓冲区,在根据某种策略刷新到 AOF 文件中,策略有如下三种""" AOF 基本配置:vi /etc/redis/redis.conf appendonly yes # 开关,先要打开 appendfilename "appendonly.aof" # AOF 文件名 dir /var/lib/redis # AOF 文件目录(和 RDB 是一样的)
-
刷新策略 1:always
always 即缓冲区有一条命令,就会刷新追加到 AOF 文件中(安全可靠,耗 IO)
-
刷新策略 2:everysec(默认)
everysec 即每过 1 秒 就会把缓冲区的命令 刷新追加到 AOF 文件中 如果就在这一秒钟宕机,那么数据就丢失了。。。(1 秒不可控)
-
刷新策略 3:no
no 即 什么时候刷新,全听操作系统自己的(完全不可控)
-
AOF 重写机制(两种方法, 异步)
-
重写清洁过程:
如上可知,越来越多的命令会追加到 AOF 中,其中可能会有一些类似 一、键值覆盖:set name tom set name jerry 二、超时时间过期 三、多条插入(可用一条命令代替)如上无用命令,会让 AOF 文件变得繁杂。可通过 AOF 重写策略优化来达到化简,提高恢复速度等。
-
重写原理(查找资料 + 个人理解):
一、开 fork 子进程 新弄一份 AOF 文件,它的任务就是把当前 redis 中的数据重新按照上面的”重写清洁过程“捋一遍,并记录到这个新 AOF 文件中 二、此时主进程可以正常接受用户的请求及修改,(这时可能子进程 AOF,和数据库内容不一致, 往下看)三、其实 --- 第一条开 fork 的时候,顺便也开了一份内存空间 A(名为重写缓冲区)用来平行记录 用户新请求的命令 四、当子进程 AOF 重写完事后,会把上面 空间 A 中 中的数据命令追加到 AOF 中(类似断点复制)五、新 AOF 替代 旧的 AOF 打个比方(针对于 二、三、四):就是,你给我一个任务,我正做着,你又给我很多任务,我当然忙不过来 那这样,你先拿个清单记录下来,一会等我忙完了,咱们对接一下就好了)
-
重写方式 1:bgrewriteaof
R: bgrewriteaof P: redis.bgrewriteaof()
-
重写方式 2:配置文件实现自动重写
在上面 AOF 基本配置的基础上,追加如下配置 vi /etc/redis/redis.conf appendfsync everysec # 就是上面说的三种策略,选一种 always no auto-aof-rewrite-min-size 64mb # 当 AOF 文件超过 64mb 就会自动重写 auto-aof-rewrite-percentage 100 # 100 为增长率,每一次的限制大小是之前的 100%, 也就是二倍 no-appendfsync-on-rewrite yes # yes 就是不把“重写缓冲区”的内容 刷新到 磁盘 注意这个参数: 这就是针对上面’重写原理‘中的第三条 中的 内存空间 A(重写缓冲区)如果这个 重写缓冲区 不刷新持久化到磁盘中,要是宕机了,那么这个缓冲区的数据就会丢失。丢失多少呢?据悉(linux 中 最多最多 会丢失 30 秒的数据)如果你将其 设置为 no,那么 重写缓冲区 就会像 前面讲的 原始 AOF 一样地 刷新持久化到硬盘中。但是你想想,如果 重写缓冲区 和 原始 AOF 都做持久化刷新 那么 它们就会 竞争 IO,性能必定大打折扣,特殊情况下,还可能 堵塞。so, 要性能(设为 yes),要数据完整安全 (设为 no),自己选....
结束语
本文主要写了关于 redis 以及 python 操作 redis 的语法对比详细解释!!python 的 redis API 也是非常够意思了,函数名几乎完全还原 原生 Redis!!语法部分印象比较深刻的就是 "redis 的 scan 家族函数" 以及 "python 的 scan_iter" 家族函数:上面陆陆续续讲了那么多数据结构,都有它们各自的 "遍历所有数据的操作"
但对于大量数据的情况下,这些遍历函数就都变成渣渣了,可能会造成 "OOM(内存溢出)等情况"
这时 redis 机智的为我们 提供了一些列 "scan 家族函数" , 当然这些函数是都需要游标控制的。"游标 cursor" 是比较头疼的东西,因此 python 本着 人性化的思想:将 "scan 家族函数" 封装为 "scan_iter 家族函数",让我们省去了游标的操作,可以愉快编程!那我就列出全部大家族 以及 对应 原始遍历函数:原始遍历 redis python
keys scan scan_iter
hgetall hscan hscan_iter
smembers sscan sscan_iter
zrange zscan zscan_iter
沿着这个对应规律,之前我发现一件事情:为什么 "list 的 lrange 没有对应的 lscan?"
我像 zz 一样还去 ov 查了一遍,居然还看到一位外国朋友和我有一样的疑问。。。解答者的一句话,我直接就清醒了,"Instead, you should use LRANGE to iterate the list"
由于顺着规律,思维定势,却忘记了 "lrange 本身就可以带索引来迭代" lrange list1 0 n
这时我突然又想起 zrange 不也是和 lrange 语法一样么???为何 zrange 单独设立了一个 zscan,而 list 却没???(查了一下好像是 list 底层性能之类的原因,我也没愿意继续看了。。。)
scan 与 iter 家族函数,各自的数据结构章节都有写,并且在 "zset" 那节的 "zscan" 那里做出了详细的分析
END
正文完