Redis(Remote Dictionary Server)是一个开源的、基于内存的高性能键值存储系统,它支持多种数据结构,并提供了丰富的操作命令。由于其出色的性能和丰富的功能,Redis被广泛应用于缓存、消息队列、会话存储、排行榜等场景。
Redis之所以如此受欢迎,主要得益于以下几个特点:
字符串是Redis最基本的数据类型,可以存储文本、数字或二进制数据,最大能存储512MB。
常用命令:
SET key value [EX seconds] [PX milliseconds] [NX|XX]
GET key
INCR key
DECR key
APPEND key value
STRLEN key应用场景:
示例代码:
import redis
r = redis.Redis(host='localhost', port=6379, db=0)
# 设置和获取字符串
r.set('username', 'john_doe')
print(r.get('username')) # 输出: b'john_doe'
# 计数器
r.set('page_views', 0)
r.incr('page_views')
print(r.get('page_views')) # 输出: b'1'哈希是字段和值的映射表,特别适合存储对象。
常用命令:
HSET key field value
HGET key field
HGETALL key
HDEL key field
HKEYS key
HVALS key应用场景:
示例代码:
# 存储用户信息
r.hset('user:1000', 'name', 'Alice')
r.hset('user:1000', 'age', 25)
r.hset('user:1000', 'email', 'alice@example.com')
# 获取所有字段
print(r.hgetall('user:1000'))
# 输出: {b'name': b'Alice', b'age': b'25', b'email': b'alice@example.com'}
# 获取单个字段
print(r.hget('user:1000', 'name')) # 输出: b'Alice'列表是简单的字符串列表,按照插入顺序排序,可以从头部或尾部添加元素。
常用命令:
LPUSH key value [value ...]
RPUSH key value [value ...]
LPOP key
RPOP key
LRANGE key start stop
LLEN key应用场景:
示例代码:
# 作为消息队列使用
r.lpush('task_queue', 'task1')
r.lpush('task_queue', 'task2')
r.lpush('task_queue', 'task3')
# 获取队列长度
print(r.llen('task_queue')) # 输出: 3
# 消费消息
task = r.rpop('task_queue')
print(task) # 输出: b'task1'集合是字符串的无序集合,不允许重复元素。
常用命令:
SADD key member [member ...]
SREM key member [member ...]
SMEMBERS key
SISMEMBER key member
SCARD key
SINTER key [key ...] # 交集
SUNION key [key ...] # 并集
SDIFF key [key ...] # 差集应用场景:
示例代码:
# 标签系统
r.sadd('article:123:tags', 'python', 'programming', 'database')
# 检查标签是否存在
print(r.sismember('article:123:tags', 'python')) # 输出: True
# 获取所有标签
print(r.smembers('article:123:tags'))
# 输出: {b'python', b'database', b'programming'}有序集合类似于集合,但每个元素都关联一个分数(score),元素按分数排序。
常用命令:
ZADD key score member [score member ...]
ZRANGE key start stop [WITHSCORES]
ZREVRANGE key start stop [WITHSCORES]
ZRANK key member
ZSCORE key member
ZCARD key应用场景:
示例代码:
# 排行榜
r.zadd('game_scores', {'Alice': 1500, 'Bob': 1200, 'Charlie': 1800})
# 获取前三名
top_players = r.zrevrange('game_scores', 0, 2, withscores=True)
print(top_players)
# 输出: [(b'Charlie', 1800.0), (b'Alice', 1500.0), (b'Bob', 1200.0)]
# 获取某个玩家的排名(从0开始)
print(r.zrevrank('game_scores', 'Alice')) # 输出: 1Redis还支持一些特殊的数据结构:
HyperLogLog 用于基数统计,提供不精确的去重计数方案。
Geo 用于存储地理位置信息,支持半径查询等操作。
Bitmaps 通过位操作实现一些特殊功能,如用户签到统计。
在分布式系统中,当多个进程或服务需要互斥地访问共享资源时,就需要分布式锁。分布式锁需要满足以下几个基本要求:
def acquire_lock(conn, lock_name, acquire_timeout=10):
identifier = str(uuid.uuid4())
lock_key = f'lock:{lock_name}'
end = time.time() + acquire_timeout
while time.time() < end:
if conn.setnx(lock_key, identifier):
return identifier
time.sleep(0.001)
return False
def release_lock(conn, lock_name, identifier):
lock_key = f'lock:{lock_name}'
with conn.pipeline() as pipe:
while True:
try:
pipe.watch(lock_key)
if pipe.get(lock_key) == identifier.encode():
pipe.multi()
pipe.delete(lock_key)
pipe.execute()
return True
pipe.unwatch()
break
except redis.exceptions.WatchError:
pass
return False问题:
def acquire_lock(conn, lock_name, acquire_timeout=10, lock_timeout=10):
identifier = str(uuid.uuid4())
lock_key = f'lock:{lock_name}'
lock_timeout = int(lock_timeout)
end = time.time() + acquire_timeout
while time.time() < end:
if conn.set(lock_key, identifier, ex=lock_timeout, nx=True):
return identifier
time.sleep(0.001)
return False
def release_lock(conn, lock_name, identifier):
lock_key = f'lock:{lock_name}'
script = """
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end
"""
result = conn.eval(script, 1, lock_key, identifier)
return bool(result)改进点:
在Redis集群环境下,为了确保锁的高可用性,可以使用Redlock算法:
Python实现示例:
import random
import time
def acquire_redlock(conns, lock_name, ttl, retry_count=3, retry_delay=0.2):
quorum = len(conns) // 2 + 1
identifier = str(uuid.uuid4())
for _ in range(retry_count):
start_time = time.time() * 1000
acquired = 0
for conn in conns:
try:
if conn.set(lock_name, identifier, nx=True, px=ttl):
acquired += 1
except redis.RedisError:
continue
elapsed = time.time() * 1000 - start_time
if acquired >= quorum and elapsed < ttl:
return {
'validity': ttl - elapsed,
'resource': lock_name,
'identifier': identifier
}
# 释放已经获取的锁
for conn in conns:
try:
conn.eval("""
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end
""", 1, lock_name, identifier)
except redis.RedisError:
pass
time.sleep(retry_delay * (1 + random.random()))
return False
def release_redlock(conns, lock_info):
if not lock_info:
return
for conn in conns:
try:
conn.eval("""
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end
""", 1, lock_info['resource'], lock_info['identifier'])
except redis.RedisError:
pass问题1:锁提前释放
问题2:时钟漂移
问题3:锁被误释放
问题4:性能瓶颈
Redis作为一个高性能的内存数据库,提供了丰富的数据结构,可以满足各种场景下的需求。从简单的字符串到复杂的排序集合,每种数据结构都有其特定的应用场景和优势。
在分布式系统中,Redis也是实现分布式锁的理想选择。通过合理的命令组合和Lua脚本,可以实现安全可靠的分布式锁。对于更复杂的集群环境,Redlock算法提供了更高的可用性保证。
然而,分布式锁的实现需要考虑诸多细节,包括超时设置、锁续约、错误处理等。在实际应用中,也可以考虑使用现成的库如Redlock-py等,它们已经处理了大部分边缘情况。
最后,值得注意的是,分布式锁虽然强大,但并非所有场景都需要。在设计系统时,应该首先考虑是否可以通过无锁设计或其他并发控制机制来解决问题,只有在真正需要互斥访问时才使用分布式锁。