一、读写分离 Redis 封装
python
# -*- coding: utf-8 -*-
REDIS = {
'Master': {
'host': '127.0.0.1',
'port': 6379,
'db': 5
},
'Slave': {
'host': '127.0.0.1',
'port': 6379,
'db': 6
}
}
import time
from redis import Redis as _Redis
from redis.client import Pipeline
from pickle import dumps, loads, UnpicklingError
class Redis(_Redis):
'''
Redis继承类
接口与原生 Redis 保持一致,增加自动序列化、反序列化功能
'''
def __init__(self, *args, **kwargs):
_Redis.__init__(self, *args, **kwargs)
def keys(self, pattern='*'):
'Returns a list of keys matching ``pattern``'
return sorted(_Redis.keys(self, pattern))
def set(self, key, value, timeout=0):
if timeout > 0:
return self.setex(key, dumps(value, 1), timeout)
else:
return _Redis.set(self, key, dumps(value, 1))
def setnx(self, key, value, timeout=0):
res = _Redis.setnx(self, key, dumps(value, 1))
if res and timeout > 0:
_Redis.expire(self, key, timeout)
return res
def get(self, key, default=None):
value = _Redis.get(self, key)
return default if value is None else value
def mset(self, mapping):
return _Redis.mset(self, {k: dumps(v, 1) for k, v in mapping.items()})
def mget(self, keys, default=None):
values = _Redis.mget(self, keys)
return [default if v is None else v for v in values]
def hset(self, name, key, value):
return _Redis.hset(self, name, key, dumps(value, 1))
def hget(self, name, key, default=None):
value = _Redis.hget(self, name, key)
return default if value is None else value
def hmset(self, name, mapping):
return _Redis.hmset(self, name, {k: dumps(v, 1) for k, v in mapping.items()})
def hmget(self, name, keys, default=None):
values = _Redis.hmget(self, name, keys)
return [default if v is None else v for v in values]
def pop(self, key, default=None):
'''del specified key and return the corresponding value'''
pipe = self.pipeline()
pipe.get(key)
pipe.delete(key)
value, res = pipe.execute()
return default if value is None or res != 1 else value
def hpop(self, name, key, default=None):
'''del specified key and return the value of key within the hash name'''
pipe = self.pipeline()
pipe.hget(name, key)
pipe.hdel(name, key)
value, res = pipe.execute()
return default if value is None or res != 1 else value
def hscan_iter(self, name, match=None, count=None):
cursor = '0'
found = []
while cursor != 0:
cursor, data = self.hscan(name, cursor=cursor,
match=match, count=count)
for k, v in data.items():
if k not in found:
found.append(k)
yield k, v
def unpickle(self, data):
try:
if isinstance(data, bytes):
return loads(data)
elif isinstance(data, (list, tuple)):
return [self.unpickle(v) for v in data]
elif isinstance(data, dict):
return {k: self.unpickle(v) for k, v in data.items()}
else:
return data
except (UnpicklingError, TypeError, ValueError, EOFError):
return data
def parse_response(self, connection, command_name, **options):
'''Parses a response from the Redis server'''
response = _Redis.parse_response(self, connection, command_name, **options)
return self.unpickle(response)
def pipeline(self, transaction=True, shard_hint=None, origin=False):
if origin:
return _Redis.pipeline(self, transaction, shard_hint)
else:
return Pipeline(self.connection_pool, self.response_callbacks,
transaction, shard_hint)
class Pipeline(Pipeline, Redis):
'''覆盖原生Pipeline类'''
def execute(self, raise_on_error=True):
result = super(Pipeline, self).execute(raise_on_error)
return [self.unpickle(r) for r in result]
class MSRedis(object):
'''读写分离客户端 (只针对程序中用到的命令)'''
def __init__(self, conf):
self.master = Redis(**conf['Master'])
self.slave = Redis(**conf['Slave'])
self.read_commands = [
'ttl', 'exists', 'expire', 'get', 'keys',
'hget', 'hgetall', 'hkeys', 'hmget',
'sismember', 'smembers', 'sdiff', 'sinter', 'sunion'
'zrevrange', 'zrevrangebyscore', 'zrevrank', 'zscore'
]
def __getattribute__(self, name):
if name in ['master', 'slave', 'read_commands']:
return object.__getattribute__(self, name)
elif name in self.read_commands:
return self.slave.__getattribute__(name)
else:
return self.master.__getattribute__(name)
# 创建全局 Redis 连接
rds = MSRedis(REDIS)
二、Redis 集群封装
python
# redis配置
REDIS_NODES = [
{'host': '172.25.102.70', 'port': '7000'},
{'host': '172.25.102.71', 'port': '7000'},
{'host': '172.16.179.72', 'port': '7000'},
{'host': '172.16.179.75', 'port': '7000'},
{'host': '172.16.179.76', 'port': '7000'},
]
class RedisApi(object):
@classmethod
def redis_conn(cls, startup_nodes=REDIS_NODES, max_connections=1000):
redisObject = None
try:
redisObject = RedisCluster(startup_nodes=startup_nodes, max_connections=max_connections)
except Exception as e:
logger.error("redis conn error : %s" % e)
return redisObject
def read_string(self, key):
""" 读取redis key值"""
conn = self.redis_conn()
val = conn.get(key)
if not val:
return None
val = val.decode("utf8")
return val
def set_string(self, key, val, ex=REDIS_EX):
""" 设置redis key的value值"""
conn = self.redis_conn()
res = conn.set(key, val, ex=ex)
return res
三、python 实现布隆过滤器
python
# pip install redis
# pip install bloom-filter
import redis
from bloom_filter import BloomFilter
class BloomCache:
def __init__(self, host='localhots', port=6379, filter_capacity=1000000, error_rate=0.001):
self.redis_conn = redis.Redis(host=host, port=port)
self.bloom_filter = BloomFilter(max_elements=filter_capacity, error_rate=error_rate)
def add_to_bloom(self, key):
"""
将 key 添加到布隆布过滤器
:param key:
:return:
"""
self.bloom_filter.add(key)
def exists_in_bloom(self, key):
"""
检查 key 是否可能存在于 布隆过滤器中
:param key:
:return:
"""
return key in self.bloom_filter
def get_from_cache(self, key):
if not self.exists_in_bloom(key):
# 如果布隆过滤器认为 key 不存在,则直接返回 None
return None
value = self.redis_conn.get(key)
if value is not None:
return value.decode('utf-8')
# 如果 Redis 中也没有找到,则需要从数据库中获取
# get_data_from_db(key):模拟从数据库中获取数据
value = self.get_data_from_db(key)
if value is not None:
self.redis_conn.set(key, value)
self.add_to_bloom(key)
return value
@classmethod
def get_data_from_db(cls, key):
"""
模拟从数据库获取数据
:return:
"""
print("get_db")
if key == "example_key":
return 'example_value'
return None
if __name__ == '__main__':
cache = BloomCache()
print(cache.get_from_cache("example_key"))
print(cache.get_from_cache("example_key"))
print(cache.get_from_cache("nonexistent_key"))
四、python 使用 Redis 常用的方法
4.1 String 操作
set
(name, value, ex=None, px=None, nx=False, xx=False)
设置键的值
参数: ex: 秒
px: 毫秒
nx: 如果设置为 True,则只有 name 不存在时,当前 set 操作才执行
xx: 如果设置为 True,则只有 name 存在时,当前 set 操作才执行
python
result = redis_conn.set("lock", "888", 5)
print(result)
get
获取键的值
python
value = redis_conn.get("lock")
setnx
设置值,只有 name 不存在时,执行才会操作
python
result = redis_conn.setnx("lock_1", "666")
print(result)
incr
将存储在键中数字值增加 1
python
result = redis_conn.incr("counter")
print(result)
decr
将存储在键中的数字值减 1
python
result = redis_conn.decr("counter")
print(result)
4.2 Hash (哈希表)
hset
将哈希表中 key 的字段 field 的值设为 value
hget:获取存储在哈希表中指定字段的值
python
redis_conn = redis.Redis(host='localhost', port=6379)
res = redis_conn.hset("student", "name", "jack")
value = redis_conn.hget("student", "name")
hmset
同时将多个 field-value (域-值) 对 设置到哈希表 key 中
hmget
获取所有给定字段的值
python
redis_conn = redis.Redis(host='localhost', port=6379)
data = {
"name": "Jack",
"age": 25,
"city": "New York"
}
redis_conn.hset("user:2", mapping=data)
# redis_conn.hmset("user:2", data) 新版本 hmset 废弃,可以使用 hset 函数
values = redis_conn.hmget("user:2", ['name', 'age', 'city'])
# 输出字段的值
for field, value in zip(data.keys(), values):
print(f"{field}: {value.decode('utf-8')}")
hgetall
获取在哈希表中指定 key 的所有字段 和值
python
redis_conn = redis.Redis(host='localhost', port=6379)
value = redis_conn.hgetall("user:1")
print(value)
"""
{b'name': b'Jack', b'age': b'25', b'city': b'New York'}
"""
hlen:
获取哈希表中字段的数量
redis_conn = redis.Redis(host='localhost', port=6379)
value = redis_conn.hlen("user:1")
print(value)
hkeys
获取 哈希表中 所有字段
python
redis_conn = redis.Redis(host='localhost', port=6379)
value = redis_conn.hkeys("user:1")
print(value)
hvals
获取 哈希表中 所有的值
python
redis_conn = redis.Redis(host='localhost', port=6379)
value = redis_conn.hvals("user:1")
print(value)
hexists
查看 哈希表中 指定的字段是否存在
python
redis_conn = redis.Redis(host='localhost', port=6379)
value = redis_conn.hexists("user:1", "name")
print(value)
hdel
删除一个或多个哈希表字段
python
redis_conn = redis.Redis(host='localhost', port=6379)
value = redis_conn.hdel("user:1", "name")
print(value)
4.3 List 操作
lindex
通过索引获取列表中的元素
python
redis_conn = redis.Redis(host='localhost', port=6379)
value = redis_conn.lindex("student", 0)
print(value)
rpush
在列表末尾中添加元素
python
redis_conn = redis.Redis(host='localhost', port=6379)
value = redis_conn.rpush("student", 'marry')
print(value)
lpush
在列表头部插入元素
python
redis_conn = redis.Redis(host='localhost', port=6379)
value = redis_conn.lpush("student", 'tony')
print(value)
lpushx
将一个或多个值插入到已存在的列表头部
python
linsert
在列表的元素前或者后插入元素
python
lset
通过索引设置列表元素的值
python
lpushx
将一个或多个值插入到列表头部
lrange
获取列表中指定范围的元素
python
redis_conn = redis.Redis(host='localhost', port=6379)
value = redis_conn.lrange("student", 0, -1)
print(value)
lpop
移出并获取列表的第一个元素
python
rpop
移除并获取列表最后一个元素
python
bpoplpush
移除列表的最后一个元素,并将该元素添加到另一个列表返回
python
blpop
移除并获取列表的第一个元素,如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止
python
brpop
移除并获取列表的最后一个元素,如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止
python
brpoplpush
从列表中弹出一个值,将弹出的元素插入到另外一个列表中并返回它;如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止
python
lrem
移除列表元素
llen
获取列表长度
ltrim
对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除
4.4 Set 操作
Set 集合 就是不允许重复的列
sunion
返回所有给定集合的并集
scard
获取集合的成员数
srandmember
返回集合中的一个或多个随机数
smembers
返回集合中的所有成员
sinter
返回给定所有集合的交集
srem
移出集合中一个或多个成员
smove
将 member 元素从 source 集合移动到 destination 集合
sadd
向集合添加一个或多个成员
sismember
判断 member 元素是否是集合 key 的成员
sdiffstore
返回给定所有集合的差集并存储在 destination 中
sdiff
返回给定所有集合的差集
sscan
迭代集合中的元素
sinterstore
返回给定集合的交集并存储在 destination 中
sunionstore
所有给定集合的并集存储在 destination 中
spop
移除并返回集合中的一个随机元素
4.5 Sort Set 操作
有序集合,在集合的基础上,为每元素排序;元素的排序需要根据另外一个值进行比较,所以,对于有序集合,每一个元素有两个值,即:值和分数,分数专门用来做排序
zadd
向有序集合添加一个或多个成员,或者更新已存在成员的分数
python
result = r.zadd(name, mapping, nx=False, xx=False, ch=False, incr=False)
name
: 是有序集合的键。mapping
: 是一个字典,键是成员,值是分数。nx
和xx
是可选参数,nx=True
表示只添加不存在的成员,xx=True
表示只更新已存在的成员。ch
如果设置为True
,则返回被改变的元素数量。incr
如果设置为True
并且成员已经存在,则对分数进行增量操作。
python
redis_conn = redis.Redis(host='localhost', port=6379)
value = redis_conn.zadd("score", {"member1": 15, "member2": 20, "member3": 30, "member4": 40})
# value = redis_conn.zrevrange()
print(value)
zrevrange
在有序集合中计算指定字典区间内成员排名,有序集合成员按分数值递减(从大到小)排序
python
def zrevrange(name: KeyT,start: int,end: int,withscores: bool = False)
redis_conn = redis.Redis(host='localhost', port=6379)
value = redis_conn.zrevrange("score", 0, -1, withscores=True)
print(value)
zrevrank
返回有序集合中指定成员的排名,有序集合成员按分数值递减(从大到小排序)
python
redis_conn = redis.Redis(host='localhost', port=6379)
value = redis_conn.zrevrank("score", "member4")
print(value)
zunionstore
计算给定的一个或多个有序集的并集,并存储在新的 key 中
zremrangebyrank
移除有序集合中给定的排名区间的所有成员
zcard
获取有序集合的成员数
zrem
移除有序集合中的一个或多个成员
zrank
返回有序集合中指定成员的索引
zinterstore
计算给定的一个或多个有序集合的交集并将结果集存储在新的有序集合 key 中
zincrby
有序集合中对指定成员的分数加上增量 increment
zrangebyscore
通过分数返回有序集合指定区间内的成员
zrangebylex
通过字典区间返回有序集合的成员
zscore
返回有序集合中,成员的分数值
zremrangebyscore
移除有序集合中给定的分数区间的所有成员
zscan
迭代有序集合中的元素(包括元素成员和元素分值)
zrevrange
返回有序集合中指定区间内的成员,通过索引,分数从高到低
zrange
通过索引区间返回有序集合成指定区间内的成员
zcount
计算在有序集合中指定区间分数的成员数
4.6 管道
redis-py默认在执行每次请求都会创建(连接池申请连接)和断开(归还连接池)一次连接操作,如果想要在一次请求中指定多个命令,则可以使用pipline实现一次请求指定多个命令,并且默认情况下一次pipline 是原子性操作
python
import redis
redis_conn = redis.Redis(host='localhost', port=6379)
pipe = redis_conn.pipeline(transaction=True)
pipe.set('name', 'alex')
pipe.set('role', 'sb')
pipe.execute()
4.7 发布订阅
订阅者
python
import redis
redis_conn = redis.Redis(host='localhost', port=6379)
pub = redis_conn.pubsub()
pub.subscribe("fm104.5")
pub.parse_response()
while 1:
msg = pub.parse_response()
print(msg)
发布者
python
import redis
redis_conn = redis.Redis(host='localhost', port=6379)
redis_conn.publish("fm104.5", "Hi,yuan!")