HLEN:获取散列包含的字段数量
用户可以通过使用 HLEN 命令获取给定散列包含的字段数量:
HLEN hash
例如,对于图 3-15 中展示的 article::10086 散列和 account::54321 散列来说,我们可以通过执行以下命令来获取 article::10086 散列包含的字段数量:
redis> HLEN article::10086
(integer) 4 -- 这个散列包含4个字段

或者,通过执行以下命令来获取 account::54321 散列包含的字段数量:
redis> HLEN account::54321
(integer) 2 -- 这个散列包含2个字段
如果用户给定的散列并不存在,那么 HLEN 命令将返回 0 作为结果:
redis> HLEN not-exists-hash
(integer) 0
示例:实现用户登录会话
为了方便用户,网站一般都会为已登录的用户生成一个加密令牌,然后把这个令牌分别存储在服务器端和客户端,之后每当用户再次访问该网站的时候,网站就可以通过验证客户端提交的令牌来确认用户的身份,从而使得用户不必重复地执行登录操作。
另外,为了防止用户因为长时间不输入密码而遗忘密码,以及为了保证令牌的安全性,网站一般都会为令牌设置一个过期期限(比如一个月),当期限到达之后,用户的会话就会过时,而网站则会要求用户重新登录。
上面描述的这种使用令牌来避免重复登录的机制一般称为登录会话(login session),通过使用 Redis 的散列,我们可以构建出代码清单3-4所示的登录会话程序。
import random
from time import time # 获取浮点数格式的 unix 时间戳
from hashlib import sha256
# 会话的默认过期时间
DEFAULT_TIMEOUT = 3600*24*30 # 一个月
# 储存会话令牌以及会话过期时间戳的散列
SESSION_TOKEN_HASH = "session::token"
SESSION_EXPIRE_TS_HASH = "session::expire_timestamp"
# 会话状态
SESSION_NOT_LOGIN = "SESSION_NOT_LOGIN"
SESSION_EXPIRED = "SESSION_EXPIRED"
SESSION_TOKEN_CORRECT = "SESSION_TOKEN_CORRECT"
SESSION_TOKEN_INCORRECT = "SESSION_TOKEN_INCORRECT"
def generate_token():
"""
生成一个随机的会话令牌。
"""
random_string = str(random.getrandbits(256)).encode('utf-8')
return sha256(random_string).hexdigest()
class LoginSession:
def __init__(self, client, user_id):
self.client = client
self.user_id = user_id
def create(self, timeout=DEFAULT_TIMEOUT):
"""
创建新的登录会话并返回会话令牌,
可选的 timeout 参数用于指定会话的过期时间(以秒为单位)。
"""
# 生成会话令牌
user_token = generate_token()
# 计算会话到期时间戳
expire_timestamp = time()+timeout
# 以用户 ID 为字段,将令牌和到期时间戳分别储存到两个散列里面
self.client.hset(SESSION_TOKEN_HASH, self.user_id, user_token)
self.client.hset(SESSION_EXPIRE_TS_HASH, self.user_id, expire_timestamp)
# 将会话令牌返回给用户
return user_token
def validate(self, input_token):
"""
根据给定的令牌验证用户身份。
这个方法有四个可能的返回值,分别对应四种不同情况:
1. SESSION_NOT_LOGIN —— 用户尚未登录
2. SESSION_EXPIRED —— 会话已过期
3. SESSION_TOKEN_CORRECT —— 用户已登录,并且给定令牌与用户令牌相匹配
4. SESSION_TOKEN_INCORRECT —— 用户已登录,但给定令牌与用户令牌不匹配
"""
# 尝试从两个散列里面取出用户的会话令牌以及会话的过期时间戳
user_token = self.client.hget(SESSION_TOKEN_HASH, self.user_id)
expire_timestamp = self.client.hget(SESSION_EXPIRE_TS_HASH, self.user_id)
# 如果会话令牌或者过期时间戳不存在,那么说明用户尚未登录
if (user_token is None) or (expire_timestamp is None):
return SESSION_NOT_LOGIN
# 将当前时间戳与会话的过期时间戳进行对比,检查会话是否已过期
# 因为 HGET 命令返回的过期时间戳是字符串格式的
# 所以在进行对比之前要先将它转换成原来的浮点数格式
if time() > float(expire_timestamp):
return SESSION_EXPIRED
# 用户令牌存在并且未过期,那么检查它与给定令牌是否一致
if input_token == user_token:
return SESSION_TOKEN_CORRECT
else:
return SESSION_TOKEN_INCORRECT
def destroy(self):
"""
销毁会话。
"""
# 从两个散列里面分别删除用户的会话令牌以及会话的过期时间戳
self.client.hdel(SESSION_TOKEN_HASH, self.user_id)
self.client.hdel(SESSION_EXPIRE_TS_HASH, self.user_id)
LoginSession 的 create() 方法首先会计算出随机的会话令牌以及会话的过期时间戳,然后使用用户 ID 作为字段,将令牌和过期时间戳分别存储到两个散列里面。
在此之后,每当客户端向服务器发送请求并提交令牌的时候,程序就会使用 validate() 方法验证被提交令牌的正确性:validate() 方法会根据用户的 ID,从两个散列里面分别取出用户的会话令牌以及会话的过期时间戳,然后通过一系列检查判断令牌是否正确以及会话是否过期。
最后,destroy() 方法可以在用户手动退出(logout)时调用,它可以删除用户的会话令牌以及会话的过期时间戳,让用户重新回到未登录状态。
在拥有 LoginSession 程序之后,我们可以通过执行以下代码为用户 peter 创建相应的会话令牌:
>>> from redis import Redis
>>> from login_session import LoginSession
>>>
>>> client = Redis(decode_responses=True)
>>> session = LoginSession(client, "peter")
>>>
>>> token = session.create()
>>> token
'3b000071e59fcdcaa46b900bb5c484f653de67055fde622f34c255a65bd9a561'
通过以下代码验证给定令牌的正确性:
>>> session.validate("wrong_token")
'SESSION_TOKEN_INCORRECT'
>>>
>>> session.validate(token)
'SESSION_TOKEN_CORRECT'
在使用完会话之后,执行以下代码销毁会话:
>>> session.destroy()
>>>
>>> session.validate(token)
'SESSION_NOT_LOGIN'
图 3-16 展示了使用 LoginSession 程序在数据库中创建多个会话的示意图。
