Reddit的Upvote功能简单实现

在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



Previous     Next
tuzhi /
Published under (CC) BY-NC-SA in categories python  tagged with