本文翻译自Josiah L Carlson的《Redis in Action》,CHAPTER 8: Building a simple social network。
本章,我们将阐述实现一个类似Twitter这样的社交网站所需的数据结构和概念。当然,这并不代表阅读完本章,你就能应付一个真正的大型网站,而是通过其中的机制让你能够更好的去理解如何用简单的方法达到我们的目标。
本章将涉及如下内容:
用户与状态消息
时间轴
关注/被关注列表
发布/删除状态更新
Streaming API
1. 用户和状态消息:
当用户与Twitter进行交互时,用户和状态消息将会聚焦很多有用的信息。用户对象是用户基本的身份信息,同时包含一个用户有多少关注者或是发布过多少条状态消息等等。它是一切用户数据的基础。而状态消息则展现出用户行为和与他人的交互,它是社交网络的内容来源,因此具有同等重要的地位。
在这一部分,我们将会讨论用户和状态消息的数据构成和存储方式,并实现一个函数用以创建新用户。
用户信息
对于众多的在线服务或社交网络来说,用户对象属于最基本的模块,其他的一切都将由此衍生开来。这里也不例外。
我们将会利用HASH存储用户的基本信息。其中的内容包括用户名、关注人数、被关注人数、注册日期以及其他一些基本信息。如下图展示了名为dr_josiah的用户的数据:
当新用户注册时,只需填入用户名和注册日期并将关注人数、被关注人数以及发布的状态消息数置为0即可。实现该注册功能的函数代码如下:
def create_user(conn, login, name): llogin = login.lower() # 获取用户名锁 lock = acquire_lock_with_timeout(conn, 'user:' + llogin, 1) if not lock: return None # 一个以小写用户名构成的HASH表,在此可以用于检测用户名是否存在以确保唯一性 if conn.hget('users:', llogin): return None # 用户ID,一个自增长的数 id = conn.incr('user:id:') pipeline = conn.pipeline(True) # 将新的用户名写入‘users:’表 pipeline.hset('users:', llogin, id) # 写入该用户的信息 pipeline.hmset('user:%s'%id, { 'login': login, 'id': id, 'name': name, 'followers': 0, 'following': 0, 'posts': 0, 'signup': time.time(), }) pipeline.execute() # 释放锁 release_lock(conn, 'user:' + llogin, lock) return id
该函数将会把新用户的信息以HASH的形式保存到Redis,同时这里对用户名加锁以保证在同一时间只能有一个人请求使用该用户名进行注册。在获取锁之后,我们还会验证是否已存在同名用户。通过后我们为其分配一个唯一的ID。最后才会实实在在的去创建这个用户。
敏感信息:用户HASH将会经常被用于渲染模板甚至在API请求中直接用作返回,因此我们不会将敏感信息保存在这里。目前我们假设将密码、Email等信息保存在另外的key或其他数据库中。
到此,用户创建与基本信息的存储就OK了。接下来是状态消息。
状态消息
之前已经说过,用户配置涵盖了个人信息,而用户的行为与交互则会存储在状态消息里。与用户对象类似,我们同样以HASH的形式将状态消息保存在Redis里。
除了消息本身,还会同时保存一些冗余信息如发布时间、用户ID、用户名(这样就不必再次查询用户信息),以及一些其他的有用信息。下图展示了一个消息实体:
下面来看一下代码实现:
def create_status(conn, uid, message, **data): pipeline = conn.pipeline(True) # 通过user id获取登录名 pipeline.hget('user:%s'%uid, 'login') # 创建消息ID pipeline.incr('status:id:') login, id = pipeline.execute() if not login: return None data.update({ 'message': message, 'posted': time.time(), 'id': id, 'uid': uid, 'login': login, }) pipeline.hmset('status:%s'%id, data) pipeline.hincrby('user:%s'%uid, 'posts') pipeline.execute() return id
函数没什么特别之处,就是先获取用户名,为消息生成一个新的ID,然后将所有信息保存到Redis即可。