在Reddit和Stackoverflow或者知乎上都可以看见有一个Upvote功能,这种功能让用户去维护信息流动。它可以用Redis数据库实现。
##构造数据库 我们用一个zset(由权重+值两部分组成)来存储每篇文章的post时间,zset是排序好的set,它是根据值对应的权重来排序的。这样就可以让文章以时间排序的方式排列显示。
127.0.0.1:6379> zadd time: 1430104804 article:100409
(integer) 1
127.0.0.1:6379> zadd time: 1332075503.49 article:100635
(integer) 1
127.0.0.1:6379> zadd time: 1332082035.26 article:100716
(integer) 1
用另外一个zset来存储每篇文章的排名加权权重,这样就可以让文章以排名权重的方式排列显示。
127.0.0.1:6379> zadd score: 1332225027.26 article:100716
(integer) 1
127.0.0.1:6379> zadd score: 1332075503.49 article:100635
(integer) 1
127.0.0.1:6379> zadd score: 1332065417.47 article:100408
(integer) 1
用一个元素不重复的set来存储对某篇文章投票过用户的id。假设已有三个用户对article:100408投过票。
127.0.0.1:6379> sadd voted:100408 user:234487
(integer) 1
127.0.0.1:6379> sadd voted:100408 user:253378
(integer) 1
127.0.0.1:6379> sadd voted:100408 user:364680
(integer) 1
##实现
#! -*- /bin/user env python
# -*- coding: utf-8 -*-
import redis
import time
ONE_WEEK_IN_SECONDS = 7 * 86400
VOTE_SCORE = 432 # 自定义投票后增加的排序权重
conn = redis.StrictRedis(host='localhost', port=6379, db=0)
def article_vote(conn, user, article):
cutoff = time.time() - ONE_WEEK_IN_SECONDS
# 只能只对最近一个星期内的文章进行投票
if conn.zscore('time:', article) < cutoff:
return
article_id = article.partition(':')[-1]
# 在voted:article_id set中添加对这篇文章 upvote的用户,防止重复upvote
if conn.sadd('voted:' + article_id, user):
# 对这篇文章增加排序权重
conn.zincrby('score:', article, VOTE_SCORE)
# 维持一个hashtable对文章的upvote计数
conn.hincrby(article, 'votes', 1)
if __name__ == '__main__':
article_vote(conn, 'user:1', 'article:100408')
article_vote(conn, 'user:2', 'article:100408')
article_vote(conn, 'user:3', 'article:100408')
##添加文章
def post_article(conn, user, title, link):
# 从1开始自动编号生成文章id
article_id = str(conn.incr('article:'))
voted = 'voted:' + article_id
conn.sadd(voted, user)
# 设置过期时间
conn.expire(voted, ONE_WEEK_IN_SECONDS)
now = time.time()
article = 'article:' + article_id
conn.hmset(article, {
'title': title,
'link': link,
'poster': user,
'time': now,
'votes': 1,
})
# 注意参数顺序
conn.zadd('score:', now + VOTE_SCORE, article)
conn.zadd('time:', now, article)
return article_id
调用使用
post_article(conn, 'user:1000', 'the implementation of Upvote',
'http://tuzhii.com/')
##获取Top-K文章信息
如果需要在大量的文章中选出最热的K篇文章,应该怎么实现?因为zset是排序的,只需要让它们按score排序。
def get_articles(conn, page, order='score:'):
start = (page-1) * ARTICLES_PER_PAGE
end = start + ARTICLES_PER_PAGE - 1
ids = conn.zrevrange(order, start, end)
articles = []
for id in ids:
print id
article_data = conn.hgetall(id)
article_data['id'] = id # 给hmset新增一个id项
print article_data
articles.append(article_data)
return articles
调用使用get_articles(conn, 1)
即可。完整的代码实现放在Github。
##Upvote结果
查看set中voted:100408是哪些用户投票的
SMEMBERS voted:100408
127.0.0.1:6379> SMEMBERS voted:100408
1) "user:1"
2) "user:5"
3) "user:4"
4) "user:234487"
5) "user:2"
6) "user:3"
7) "user:253378"
8) "user:364680"
查看哈希表article:100408中项votes的计数,即投票数,可以得到投票数为8。
HGET article:100408 votes
##命令列表
可以在Redis客户端用这些命令验证功能
- SMEMBERS voted:article_id 获取对此文章投票的用户
- HMGET article:article_id title link user time votes 获取此文章的所有信息
- zrange score: 0 -1 withscores 获取按权重递增排名的score: zset信息
- zrevrange score: 0 size withscores 获取按权重递减排名的score: zset信息
- HGETALL article:article_id 获取article所有信息
##Reference [1].Redis in Action. Page16~18
[2].http://redis.io/commands/zscore