原文地址:1.3 文章投票系统
1. 需求分析
要做一个可以对文章进行投票的系统,文章的排名跟得票数和发布时间相关,得票越多发布时间越近,排名越高。
一个简单的评分函数:score = post_time_in_seconds + votes * (1 * 24 * 60 *60) / 200
其中post_time_in_seconds是文章发布时间,取Unix time,即从1970年1月1日至发布时经过的秒数
votes为文章得票数,最后一项为每一票的权重。可以看出200票的权重等于一天的时间
效果类似于http://stackoverflow.com/
2. 存储结构设计
2.1 文章存储:
数据结构:hash
key: article:article_id
value:title: title, link: article_url, poster: user:user_id, time: post_time_in_seconds, votes:200
跟普通的关系数据库存储结构差不多
看个例子,图片源于原文:
2.2 时间排名的存储:
数据结构:zset
key: time:
value:'article:article_id': post_time_in_seconds
2.3 得分排名的存储:
数据结构:zset key: score:
value: 'article:article_id': score
类似对time和score两个字段分别做了索引
看个例子,图片源于原文:
2.4 投票者的存储
数据结构:set
key: voted:article_id
value:user:user_id
记录一篇文章的所有投票者,用MySQL要更麻烦一些
看个例子,图片源于原文:
2.5 分组信息的存储
数据结构:set
key: groups:group_name
value:article:article_id
看个例子,图片源于原文,score:programming是两个集合交叉的结果:
3. 功能实现
ONE_WEEK_IN_SECONDS = 7 * 86400
VOTE_SCORE = 86400 / 200
# 给文章投票,当前仅支持投赞成票
def article_vote(conn, user, article):
# vote time is one week
cutoff = time.time() - ONE_WEEK_IN_SECONDS
if conn.zscore('time:', article) < cutoff:
return
# if the user hasn't voted for this article before,
# add the user to the voted:
# increase this article's score
# increase this article's votes count
article_id = article.partition(':')[-1]
if conn.sadd('voted:' + article_id, user):
conn.zincrby('score:', article, VOTE_SCORE)
conn.hincrby(article, 'votes', 1)
# 发表文章
def post_article(conn, user, title, link):
# increase the article id
article_id = str(conn.incr('article:'))
voted = 'voted:' + article_id
# the poster as the first voted:
conn.sadd(voted, user)
conn.expire(voted, ONE_WEEK_IN_SECONDS)
now = time.time()
article = 'article:' + article_id
# add this article
conn.hmset(article, {
'title': title,
'link': link,
'poster': user,
'time': now,
'votes': 1,
})
# add this article to the sorted set
conn.zadd('score:', article, now + VOTE_SCORE)
conn.zadd('time:', article, now)
return article_id
# 获取文章
ARTICLE_PER_PAGE = 25
def get_articles(conn, page, order='score:'):
start = (page-1)*ARTICLE_PER_PAGE
end = start + ARTICLE_PER_PAGE - 1
# fetch the article ids
ids = conn.zrevrange(order, start, end)
# get article info from ids
articles = []
for id in ids:
article_data = conn.hgetall(id)
article_data['id'] = id
article.append(article_data)
return articles
# 对文章进行分组
def add_remove_groups(conn, article_id, to_add=[], to_remove=[]):
article = 'article:' + article_id
# add this article to the group
for group in to_add:
conn.sadd('group:' + group, article)
# remove this article fromt the group
for group in to_remove:
conn.srem('group:' + group, article)
# 获取一组文章
def get_group_articles(conn, group, page, order='score:'):
key = order + group
# add this group's sort info
if not conn.exists(key):
conn.zinterstore(key,
['group:'+group, order],
aggregate='max',
)
conn.expire(key, 60)
# get a page of articles from the key
return get_articles(conn, page, key)