構建一個文章投票網站,通常具有下面幾個功能java
發佈文章redis
文章投票評分(按投票多少進行評分)數據庫
文章排序(按發佈時間,按評分高低)
bash
文章分組(如專題)
數據庫設計
...
ide
1.關係型數據庫設計
post
其中用戶,組兩個表簡單化處理了。業務實現起來也至關簡單。再也不贅述。重點是如何使用redis實現相似的業務邏輯。性能
因爲redis是基於key-value管理,屬於列式數據庫。和關係型數據庫實現方式差別較大,值得研究。網站
redis的設計,最重要的一部分工做就是key的命名以及鍵值數據類型的選擇上。
spa
2.Redis設計
關係型數據庫屬於二維,數據關係主要經過在行和列二者說明,而redis中的數據關係,則經過key鍵值描述,因此要求redis鍵值具有層次性。
2.1文章發佈
private static final int ONE_WEEK_IN_SECONDS = 7 * 86400; private static final int VOTE_SCORE = 432; public String postArticle(Jedis conn, String user, String title, String link) { String articleId = String.valueOf(conn.incr("article:")); String voted = "voted:" + articleId; conn.sadd(voted, user); conn.expire(voted, ONE_WEEK_IN_SECONDS);//一週的有效期 long now = System.currentTimeMillis() / 1000; String article = "article:" + articleId; HashMap<String,String> articleData = new HashMap<String,String>(); articleData.put("title", title); articleData.put("link", link); articleData.put("user", user); articleData.put("now", String.valueOf(now)); articleData.put("votes", "1"); conn.hmset(article, articleData); //維護兩個排序集合,是爲了解決文章排序的兩種方式 //若是還有三種排序方式,對不起,還須要另外維護一個排序集合 conn.zadd("score:", now + VOTE_SCORE, article);//維護文章的評分信息 conn.zadd("time:", now, article);//維護文章的發佈時間信息 return articleId; }
2.2文章投票
實現代碼
public void articleVote(Jedis conn, String user, String article) { long cutoff = (System.currentTimeMillis() / 1000) - ONE_WEEK_IN_SECONDS; if (conn.zscore("time:", article) < cutoff){ return; } String articleId = article.substring(article.indexOf(':') + 1); //維護投票的一次性 if (conn.sadd("voted:" + articleId, user) == 1) { conn.zincrby("score:", VOTE_SCORE, article); conn.hincrBy(article, "votes", 1l); } }
2.3返回文章列表
兩種排序策略:按發佈時間,按文章評分。
支持分頁排序。
redis的實現排序方式和關係型數據庫中的實現方式有很大差異,這也是key-value數據庫的一大特色。
基於key操做。
public List<Map<String,String>> getArticles(Jedis conn, int page, String order) { int start = (page - 1) * ARTICLES_PER_PAGE; int end = start + ARTICLES_PER_PAGE - 1; //從排序集合中獲取id列表 Set<String> ids = conn.zrevrange(order, start, end); List<Map<String,String>> articles = new ArrayList<Map<String,String>>(); //遍歷id列表,逐條初始化 for (String id : ids){ Map<String,String> articleData = conn.hgetAll(id); articleData.put("id", id); articles.add(articleData); } //注意:返回的信息中,沒有列表總數 return articles; }
2.4 文章分組
這一塊邏輯相對獨立,僅僅是文章的一個分析維度而已,操做起來相對簡單。就是維護groups:${group}集合。
public void addGroups(Jedis conn, String articleId, String[] toAdd) { String article = "article:" + articleId; for (String group : toAdd) { conn.sadd("group:" + group, article); } } //排序麻煩些 public List<Map<String,String>> getGroupArticles(Jedis conn, String group, int page, String order) { String key = order + group; //60秒的有效期 if (!conn.exists(key)) { ZParams params = new ZParams().aggregate(ZParams.Aggregate.MAX); conn.zinterstore(key, params, "group:" + group, order); //有序集合,與group的交集,生成新的集合 conn.expire(key, 60); //60秒的有效期,性能和實時性的平衡,須要具體狀況具體分析 } return getArticles(conn, page, key); }
zinterstore API
public java.lang.Long zinterstore(java.lang.String dstkey,
redis.clients.jedis.ZParams params,
java.lang.String... sets)
參照資源
《redis in action》