# pip install redis
from redis import Redis
from redis import ConnectionPool
def init01():
"""通过redis对象连接redis"""
# decode_responses=False 存入的是字节数据
# decode_responses=True 存的是字符串
r = Redis(host="redis-master.operation.svc.cluster.local", port=6379,
db=10, password="xxxxx", socket_timeout=300, decode_responses=True)
print(r.ping()) # True 表示连接成功
print(r.info()['redis_version'])
def init02():
"""推荐: 通过连接池获取redis对象连接服务器"""
redis_pool = ConnectionPool(host="redis-master.operation.svc.cluster.local",
port=6379, db=10, password="xxxxx", socket_timeout=300, decode_responses=True)
r = Redis(connection_pool=redis_pool)
print(r.ping())
print(r.info()['redis_version'])
r.set("init02", "redis", ex=None, px=None, nx=False, xx=False)
# ex 失效时间 秒
# px 失效时间 毫秒
# nx True 则只有在name不存在时,当前set操作才执行
# xx True 则只有name存在时,当前set操作才执行
print(r.get("init02"))
# redis默认在执行每次请求都会创建和断开一次连接操作,redis-server会关闭超时空闲的连接,使用连接池不需要手动关闭连接
# 不推荐手动释放连接池资源
# r.connection_pool.disconnect()
# Python操作redis五种数据
redis_pool = ConnectionPool(host="redis-master.operation.svc.cluster.local",
port=6379, db=10, password="xxxxx", socket_timeout=300, decode_responses=True)
r = Redis(connection_pool=redis_pool)
# 操作字符串
def string_test():
"""操作string"""
r.set("name", "key", ex=5)
r.set("username", "张三", nx=True)
r.set("username", "李四", nx=True) # username 已存在,不会执行
print(r.get("username")) # 张三
r.set("username", "李四", xx=True) # username 已存在,执行
print(r.get("username")) # 李四
# setnx 设置值 只有name不存在时,执行设置操作
print(r.setnx("username", "张三")) # False
print(r.setnx("age", "18")) # True
# setex 设置值 参数time,过期时间,数字秒或timedelta对象
print(r.setex("age", 10, "19")) # True 10秒过期
# psetex 毫秒
print(r.psetex("age", 5000, "20")) # True 5000毫秒过期
# mset 批量设置
r.mset({"k1": "v1", "k2": "v2", "k3": "v3"})
print(r.mget("k1", "k2", "k3")) # ['v1', 'v2', 'v3']
print(r.mget(["k1", "k2", "k3"])) # ['v1', 'v2', 'v3']
# getset(name, value) 设置新值并获取原来的值
print(r.getset("k1", "v111")) # v1
print(r.get("k1")) # v111
# getrange(key, start, end) 字节位置
r.set("cn_test", "哈哈哈哈哈哈")
print(r.getrange("cn_test", 0, 2)) # 哈 取索引前2位的字节,一个汉字3个字节,一个字母一个字节
# print(r.getrange("cn_test", 0, 4)) # UnicodeDecodeError: 'utf-8' codec can't decode bytes in position 3-4: unexpected end of data
print(r.getrange("cn_test", 0, 5)) # 哈哈
print(r.getrange("cn_test", 0, -1)) # 哈哈哈哈哈哈 切片操作,取所有
r.set("en_test", "hahahahahaha")
print(r.getrange("en_test", 0, 5)) # hahaha
print(r.getrange("en_test", 0, -1)) # hahahahahaha 切片操作,取所有
# setrange(name, offset, value) 修改字符串内容,从指定字符串索引开始向后替换(新值太长时,则向后添加)
r.setrange("cn_test", 3, "嘿嘿") # 一个汉字3个字节
# r.setrange("cn_test", 2, "嘿嘿") # UnicodeDecodeError: 'utf-8' codec can't decode bytes in position 0-1: invalid continuation byte
print(r.get("cn_test")) # 哈嘿嘿哈哈哈
r.setrange("en_test", 2, "oo")
print(r.get("en_test")) # haoohahahaha
r.setrange("en_test", 2, "oooooooooooooooooooooooo")
print(r.get("en_test")) # haoooooooooooooooooooooooo
# strlen(name) 返回name对应值的字节长度,一个汉字三个字节
print(r.strlen("cn_test")) # 18
print(r.strlen("en_test")) # 26
# incr(self, name, amount=1) 自增name对应的值,当name不存在时,创建name=amount,否则自增加
print(r.get("age")) # 20
r.incr("age", amount=2)
print(r.get("age")) # 22
r.incr("age", amount=2)
print(r.get("age")) # 24
r.incr("age_test", amount=2)
print(r.get("age_test")) # 2
r.incrby("age_test", amount=2)
# incrbyfloat(self, name, amount=1.0) 自增name对应的值,当name不存在时,创建name=amount,否则自增加 浮点型
r.set("foo1", "123.0")
r.set("foo2", "222.2")
print(r.mget("foo1", "foo2")) # ['123.0', '222.2']
r.incrbyfloat("foo1", amount=2.1)
r.incrbyfloat("foo2", amount=3.3)
print(r.mget("foo1", "foo2")) # ['125.1', '225.5']
# decr(self, name, amount=1) 自减name对应的值,当name不存在时,创建name=amount,否则自减
r.decr("age", amount=2)
print(r.get("age")) # 22
r.decr("age", amount=2)
print(r.get("age")) # 20
# append(key, value) 在name对应的值后面追加内容
r.append("cn_test", "追加内容")
print(r.get("cn_test")) # 哈嘿嘿哈哈哈追加内容
# 操作HASH
def hash_test():
"""HASH操作"""
# hset(name, key, value) name:redis的key, key:hash的key, value:hash中的value
r.hset("hash1", "k1", "v1")
r.hset("hash1", "k2", "v2")
print(r.hkeys("hash1")) # ['k1', 'k2'] 获取hash1中所有的key
print(r.hget("hash1", "k1")) # v1 获取hash1中k1的value
print(r.hmget("hash1", "k1", "k2")) # ['v1', 'v2'] 批量获取
r.hsetnx("hash1", "k3", "v3") # 如果不存在则插入
print(r.hsetnx("hash1", "k4", "v4")) # k4不存在,则新增,输出1
print(r.hsetnx("hash1", "k4", "v5")) # k4存在则输出0,不执行
print(r.hget("hash1", "k4")) # v4
# hmset(name, mapping) 批量增加
# DeprecationWarning: Redis.hmset() is deprecated. Use Redis.hset() instead.
r.hmset("hash2", {"k1": "v1", "k2": "v2", "k3": "v3"})
print(r.hkeys("hash2")) # ['k1', 'k2', 'k3']
# hmget(name, keys,*args) 批量获取
print(r.hmget("hash1", "k1", "k2")) # ['v1', 'v2']
print(r.hmget("hash1", ["k1", "k2"])) # ['v1', 'v2']
# hgetall(name) 取出所有的键值
# {'k1': 'v1', 'k2': 'v2', 'k3': 'v3 ', 'k4': 'v4 '}
print(r.hgetall("hash1"))
# hlen(name) 获取name对应的hash中键值对的个数
print(r.hlen("hash1")) # 4
# hkeys(name) 获取所有key
print(r.hkeys("hash1")) # ['k1', 'k2', 'k3', 'k4']
# hvals(name) 获取所有values
print(r.hvals("hash1")) # ['v1', 'v2', 'v3', 'v4']
# hincrby(name, key, amount=1) 自增整数 如果name不存在时 value=amount
# hincrby(name, key, amount=-1) 自减整数 如果name不存在时 value=amount
r.hset("hash1", "k1", 100)
r.hincrby("hash1", "k1", amount=1)
print(r.hget("hash1", "k1")) # 101
r.hincrby("hash1", "k1", amount=-2)
print(r.hget("hash1", "k1")) # 99
# hincrbfloat(name, key, amount=1.0) 自增/减浮点数 如果name不存在时 value=amount
r.hset("hash1", "k2", 10.0)
r.hincrbyfloat("hash1", "k2", amount=-1.1)
print(r.hget("hash1", "k2")) # 8.9
r.hincrbyfloat("hash1", "k2", amount=2.3)
print(r.hget("hash1", "k2")) # 11.2
# hdel(name, *keys) 删除
# {'k1': '99', 'k2': '11.2', 'k3': 'v3', 'k4': 'v4'}
print(r.hgetall("hash1"))
r.hdel("hash1", "k4")
print(r.hgetall("hash1")) # {'k1': '99', 'k2': '11.2', 'k3': 'v3'}
# HSCAN(name, cursor=0, match=None, count=None) # 获取所有hash数据
print(r.hscan("hash1")) # (0, {'k1': '99', 'k2': '11.2', 'k3': 'v3'})
# hscan_iter(name, match=None, count=None) 利用yield封装hscan创建生成器,实现分批去redis中获取数据
# match 匹配指定key,默认None 表示所有key
# count 每次分片最少获取个数,默认None表示采用Redis的默认分片个数
for item in r.hscan_iter("hash1"):
print(item)
print(r.hscan_iter("hash1")) # 生成器内存地址
# 操作list
def list_test():
"""操作list"""
# lpush(name, values) 从左边增加 在name对应的list中添加元素,每个新的元素都添加到列表的最左边
if r.exists("list1"):
r.delete("list1")
if r.exists("list2"):
r.delete("list2")
r.lpush("list1", 11, 22, 33)
print(r.lrange("list1", 0, -1)) # ['33', '22', '11']
r.lpush("list1", 666)
print(r.lrange("list1", 0, -1)) # ['666', '33', '22', '11']
# rpush(name, values) 从右边增加
r.rpush("list1", 10, 9, 8)
# ['666', '33', '22', '11', '10', '9', '8']
print(r.lrange("list1", 0, -1))
# lpushx(name, value) 往已有的name的列表的左边添加元素,没有的话不创建
print(r.lpushx("list2", 777)) # 0 list2不存在。不添加
r.lpushx("list1", 777)
# ['777', '666', '33', '22', '11', '10', '9', '8']
print(r.lrange("list1", 0, -1))
# linsert(name, where, refvalue, value) 在name对应的列表的某个值前或者后插入一个新值
# where BEFORE/AFTER refvalue 标杆值,即在它的前后插入新值
r.lpush("list1", 11, 22, 33)
# ['33', '22', '11', '777', '666', '33', '22', '11', '10', '9', '8']
print(r.lrange("list1", 0, -1))
r.linsert("list1", "before", 11, 10) # 在列表从左至右匹配到的第一个11前面新增 10
# ['33', '22', '10', '11', '777', '666', '33', '22', '11', '10', '9', '8']
print(r.lrange("list1", 0, -1))
print(r.linsert("list1", "before", 88, 10)) # -1 标杆值不存在,添加失败
# lset(name, index, value) 在name对应的list中的某个索引位置重新赋值
r.lset("list1", 2, 18)
# ['33', '22', '18', '11', '777', '666', '33', '22', '11', '10', '9', '8']
print(r.lrange("list1", 0, -1))
# lrem(name: str, count: int, value: str) 指定值进行删除 count=1 从左至右,第一个出现的删除,count=-2 从右至左 删除2个匹配的值
r.lrem("list1", -1, 33)
# ['33', '22', '18', '11', '777', '666', '22', '11', '10', '9', '8']
print(r.lrange("list1", 0, -1))
r.lrem("list1", 2, 11)
# ['33', '22', '18', '777', '666', '22', '10', '9', '8']
print(r.lrange("list1", 0, -1))
# lpop(name) 从左删除并返回该值
# rpop(name) 从右删除并返回该值
print(r.lpop("list1")) # 33
print(r.lpop("list1")) # 22
print(r.rpop("list1")) # 8
# ltrim(name, start, end) # 在name对应的列表中移除没有在start-end索引之间的值
r.ltrim("list1", 0, 2) # 保留索引 0 - 2的值,其余值删除
print(r.lrange("list1", 0, -1)) # ['18', '777', '666']
# llen(name) 列表元素个数
print(r.llen("list1")) # 3
# lindex(name, index) 取值,根据索引号取值
print(r.lindex("list1", 2)) # 666
print(r.lindex("list1", 0)) # 18
# rpoplpush(src, dst) 移动 元素从一个列表取出最右边的元素移动到另外一个列表的最左边
r.lpush("list2", 11, 22)
print(r.lrange("list2", 0, -1)) # ['22', '11']
r.rpoplpush("list1", "list2")
print(r.lrange("list1", 0, -1)) # ['18', '777']
print(r.lrange("list2", 0, -1)) # ['666', '22', '11']
# brpoplpush(src, dst, timeout=0) 从一个列表的右侧移除一个元素并将其添加到另一个列表的左侧
# timeout 当src对应的列表中没有数据时,阻塞等待其有数据的超时时间(秒),0 表示永远阻塞
r.brpoplpush("list2", "list1", timeout=2)
print(r.lrange("list2", 0, -1)) # ['666', '22']
print(r.lrange("list1", 0, -1)) # ['11', '18', '777']
# blpop(keys, timeout) 将多个列表排列,按照从左到右去pop对应列表的元素
r.blpop(["list1", "list2"], timeout=2)
print(r.lrange("list1", 0, -1)) # ['18', '777']
print(r.lrange("list2", 0, -1)) # ['666', '22']
r.blpop(["list1", "list2"], timeout=2)
print(r.lrange("list1", 0, -1)) # ['777']
print(r.lrange("list2", 0, -1)) # ['666', '22']
# 操作set
def set_test():
"""操作set集合"""
# sadd(name, values) name对应的集合中添加元素
r.sadd("set1", 11, 22, 33, 44, 55, 66)
# scard(name) 获取集合中元素个数
print(r.scard("set1")) # 6
# smembers(name) 获取集合中所有的成员
print(r.smembers("set1")) # {'22', '55', '33', '11', '66', '44'}
# scan(cursor=0, match=None, count=None) 查找数据库所有key
# sscan(key, cursor=0, match=None, count=None) 查找集合中所有数据
# (5, ['username', 'list1', 'hash2', '459/data1', 'cn_test', 'k3', 'en_test', 'age_test', 'foo1', 'k1'])
print(r.scan(0))
print(r.sscan("set1")) # (0, ['11', '22', '33', '44', '55', '66'])
# sscan_iter(name, match=None, count=None) 获取集合中所有的成员--迭代器的方式
for i in r.sscan_iter("set1"):
print(i)
# sdiff(keys, *args) 差集 第一个name对应的集合中去除相同的取不同的
r.sadd("set2", 55, 66, 77, 88, 99)
print(r.smembers("set1")) # {'66', '11', '22', '55', '44', '33'}
print(r.smembers("set2")) # {'66', '99', '88', '55', '77'}
print(r.sdiff(["set1", "set2"])) # {'11', '33', '22', '44'}
print(r.sdiff(["set2", "set1"])) # {'77', '88', '99'}
# sdiffstore(dest, keys, *args) 差集保存至一个新的集合
r.sdiffstore("set3", ["set1", "set2"])
print(r.smembers("set3")) # {'11', '44', '33', '22'}
# sinter(keys, *args) 交集,第一个name对应的集合中取相同的
print(r.sinter(["set1", "set2"])) # {'55', '66'}
# sunion(keys, *args) 并集,合并不同的
# {'44', '22', '33', '11', '88', '77', '99', '66', '55'}
print(r.sunion(["set2", "set1"]))
# sunionstore(dest, keys, *args) 并集,并集保存至一个新的集合
r.sunionstore("set4", ["set2", "set1"])
# {'22', '55', '77', '99', '44', '66', '88', '33', '11'}
print(r.smembers("set4"))
# sismember(name, value) 判断是否是集合的成员 类似in
print(r.sismember("set4", 88)) # 1
print(r.sismember("set4", 999)) # 0
# smove(src, dst, value) 将某个成员从一个集合中移动到另一个集合
r.smove("set2", "set1", 99) # 把set2中的99 移动至set1
print(r.smembers("set1")) # {'11', '44', '66', '33', '55', '22', '99'}
print(r.smembers("set2")) # {'88', '55', '77', '66'}
# srem(name, values) 删除指定的集合中的元素
print(r.srem("set1", 100)) # 0 删除的元素不存在
print(r.srem("set1", 99)) # 1 删除成功
print(r.smembers("set1")) # {'66', '44', '22', '11', '33', '55'}
# 操作sorted set
def sorted_set_test():
"""操作sorted set集合"""
# zadd(name, *args, **kwargs) 新增
r.zadd("zset1", {'one': 1, 'two': 2, 'three': 3})
# zcard(name) 获取有序集合的元素个数,类似于len
print(r.zcard("zset1")) # 3
# zrange(name, start, end, desc=False, withscores=False, score_cast_func=float)
# start,end 有序集合索引起始结束位置,desc,排序规则,默认按照分数大小到大排序
# withscores 是否获取元素的分数,默认只获取元素的值
# score_cast_func 对分数进行数据转换的函数
print(r.zrange("zset1", 0, -1)) # ['one', 'two', 'three'] 获取有序集合中所有的元素
# [('one', 1.0), ('two', 2.0), ('three', 3.0)]
print(r.zrange("zset1", 0, -1, withscores=True))
# zrevrange(name ,start, end, withscores=False, score_cast_func=float) 从大到小排序
print(r.zrevrange("zset1", 0, -1)) # ['three', 'two', 'one']
# [('three', 3.0), ('two', 2.0), ('one', 1.0)]
print(r.zrevrange("zset1", 0, -1, withscores=True))
# zrangebyscore(name, min, max, start=None, num=None, withscores=False, score_cast_func=float)
# 按照分数范围获取name对应的有序集合的元素
r.delete("zset2")
for i in range(1, 30):
element = 'n' + str(i)
r.zadd("zset2", {element: i})
# ['n10', 'n11', 'n12', 'n13', 'n14', 'n15', 'n16', 'n17', 'n18', 'n19', 'n20']
print(r.zrangebyscore("zset2", 10, 20))
# [('n5', 5.0), ('n6', 6.0), ('n7', 7.0), ('n8', 8.0), ('n9', 9.0), ('n10', 10.0), ('n11', 11.0), ('n12', 12.0), ('n13', 13.0), ('n14', 14.0), ('n15', 15.0)]
print(r.zrangebyscore("zset2", 5, 15, withscores=True))
# zrevrangebyscore(name, max, min, start=None, num=None, withscores=False, score_cast_func=float)
# 按照分数范围获取有序集合的元素并排序(默认从大到小排序)
# [('n25', 25.0), ('n24', 24.0), ('n23', 23.0), ('n22', 22.0), ('n21', 21.0), ('n20', 20.0), ('n19', 19.0), ('n18', 18.0), ('n17', 17.0), ('n16', 16.0), ('n15', 15.0)]
print(r.zrevrangebyscore("zset2", 25, 15, withscores=True))
# zscan_iter(name, match=None, count=None, score_cast_func=float)
# 获取所有元素--迭代器
for i in r.zscan_iter("zset1"):
print(i)
# zcount(name, min, max) 获取有序集合中,分数在[min,max]范围内的个数
print(r.zcount("zset2", 10, 20)) # 11
# zincrby(name, amount, value) 自增name对应的有序集合value对应的分数
# [('one', 1.0), ('two', 2.0), ('three', 3.0)]
print(r.zrange("zset1", 0, -1, withscores=True))
r.zincrby("zset1", 2, 'one')
# [('two', 2.0), ('one', 3.0), ('three', 3.0)]
print(r.zrange("zset1", 0, -1, withscores=True))
# zrank(name, value) 获取索引号
print(r.zrank("zset1", 'three')) # 2 从小到大排序
print(r.zrevrank("zset1", 'three')) # 0 从大到小排序
# zrem(name, values) 删除指定值
r.zrem("zset1", 'one')
# [('two', 2.0), ('three', 3.0)]
print(r.zrange("zset1", 0, -1, withscores=True))
# zremrangebyrank(name, min, max) 根据索引号范围来删除
r.zremrangebyrank("zset2", 0, 20)
# ['n22', 'n23', 'n24', 'n25', 'n26', 'n27', 'n28', 'n29']
print(r.zrange("zset2", 0, -1))
# zremrangebyscore(name, min, max) 根据分数范围删除
r.zremrangebyscore("zset2", 21, 25)
print(r.zrange("zset2", 0, -1)) # ['n26', 'n27', 'n28', 'n29']
# zscore(name, value) 获取值对应的分数
# [('n26', 26.0), ('n27', 27.0), ('n28', 28.0), ('n29', 29.0)]
print(r.zrange("zset2", 0, -1, withscores=True))
print(r.zscore("zset2", 'n28')) # 28.0
# 其他操作
def other_test():
"""其他操作"""
# redis中以层级关系,目录形式存储数据
r.set("user:01", 'zhangsan')
print(r.get("user:01"))
# delete(*names) 删除redis中任意数据类型
r.delete("user:01")
print(r.get("user:01")) # None
# exists(name) 检查name是否存在
print(r.exists("set1")) # 1
print(r.exists("user:01")) # 0
# keys(pathern='') 模糊匹配
# keys * 匹配所有key
# keys h?llo 匹配hello,hallo等,?为一个字符
# keys h*llo 匹配hllo 和 heeeeeello等 *为任意字符
# keys h[ae]llo 匹配hallo hello
print(r.keys('*')) # 所有
print(r.keys("f*o1")) # ['foo1']
# expire(name, time) 设置超时时间
r.lpush("list5", 11, 22)
r.expire("list5", time=3) # 三秒后list5超时清除
# rename(src, dst) 重命名
print(r.lpush("list5", 11, 22))
r.rename("list5", "list6")
print(r.lrange("list5", 0, -1)) # []
print(r.lrange("list6", 0, -1)) # ['22', '11', '22', '11']
# randomkey() 随机获取name
print(r.randomkey()) # zset1
# type(name) 获取类型
print(r.type("zset1")) # zset
# scan(cursor=0, math=None, count=None) 查看所有元素
print(r.hscan("hash1")) # (0, {'k1': '99', 'k2': '11.2', 'k3': 'v3'})
print(r.sscan("set1")) # (0, ['11', '22', '33', '44', '55', '66'])
print(r.zscan("zset1")) # (0, [('two', 2.0), ('three', 3.0)])
print(r.getrange("cn_test", 0, -1)) # 哈嘿嘿哈哈哈追加内容
print(r.lrange("list1", 0, -1)) # ['777']
print(r.smembers("set1")) # {'55', '22', '66', '44', '33', '11'}
print(r.zrange("zset2", 0, -1)) # ['n26', 'n27', 'n28', 'n29']
print(r.hgetall("hash2")) # {'k1': 'v1', 'k2': 'v2', 'k3': 'v3'}
# scan_iter(match=None, count=None) 查看所有元素--迭代器
for i in r.hscan_iter("hash1"):
pass
for i in r.sscan_iter("set1"):
pass
for i in r.zscan_iter("zset1"):
pass
# dbsize() 当前redis包含多少条数据(key)
print(r.dbsize()) # 24
# keys() 查询所有key
print(r.keys())
# flushdb() 清空redis中的所有数据
# r.flushdb()
# 操作byte
def byte_test():
"""操作byte"""
redis_pool = ConnectionPool(host="redis-master.operation.svc.cluster.local",
port=6379, db=10, password="xxxxx", socket_timeout=300, decode_responses=False)
r = Redis(connection_pool=redis_pool)
r.set("username", "tang")
print(r.get("username").decode(encoding='utf-8')) # tang
# redis 事务及操作redis事务
# 事务(Transaction)是访问并可能更新数据库中各种数据顶的一个程序执行单元(unit),就是把多件事当做一件事情来处理,全部成功或全部失败。
# Redis 实现事务的4个重要命令:multi、exec、discard、watch(乐观锁)
# Redis 实现事务:使用multi开启事务,通过exec提交事务,discard用于回滚事务,事务回滚后,数据无变化。
# 事务中,错误的操作不会被执行,已经正确的操作会被执行,且不会回滚,所以原子性不太好。
# 悲观锁:Pessimistic Lock 悲观,每次去拿数据时都会给该数据上锁,这样别人想拿这个数据就会block直到它拿到锁,传统关系型数据库用到了很多这种锁机制,如行锁、表锁、读锁、写锁等,都是在做操作之前先上锁
# 乐观锁:Optimistic Lock 乐观,每次去拿数据时不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制,乐观锁适用于多读的应用类型,这样可以提高吞吐量。
def transaction_pipeline_test():
"""使用管道操作事务"""
try:
pipe = r.pipeline(transaction=True) # 默认启用事务
pipe.multi() # 开启事务
# 可以插入多种数据类型
pipe.set("username", 'zhangsan')
pipe.hset("user", "username", "lisi")
pipe.sadd('letters', 'a', 'b')
# 管道的命令可以写在一起
# pipe.set("username", 'zhangsan').hset("user", "username", "lisi").sadd('letters', 'a', 'b').execute()
# 提交事务
pipe.execute()
print(r.get("username")) # zhangsan
print(r.hget("user", 'username')) # lisi
print(r.smembers("letters")) # {'b', 'a'}
except Exception as e:
print(e)
pipe.reset() # 回滚事务
if __name__ == "__main__":
# init01()
# init02()
# string_test()
# hash_test()
# list_test()
# set_test()
# sorted_set_test()
# other_test()
# byte_test()
transaction_pipeline_test()