经验拾忆(纯手工)=> Redis与Python操作Redis语法对比解析

0

前言

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

你可能感兴趣的

载入中...