這次的開篇,算是總結下這段時間來的積累吧,廢話不多說,直接干!
前言
點贊其實是一個很有意思的功能。基本的設計思路有大致兩種, 一種自然是用mysql(寫了幾百行的代碼都還沒寫完,有毒)啦
數據庫直接落地存儲, 另外一種就是利用點贊的業務特征來扔到redis(或memcache)中, 然后離線刷回mysql等。
我這里所講的功能都是基於我之前的項目去說的,所以有些地方可以不用管的,我主要是記錄這個功能的實現思路,當你理解了,基本想用什么鬼語言寫都一樣的。
直接寫入Mysql
直接寫入Mysql是最簡單的做法。
做三個表即可,
-
comment_info
記錄文章的主要內容,主要有like_count,hate_count,score這三個字段是我們本次功能的主要字段。
-
comment_like
記錄文章被贊的次數,已有多少人贊過這種數據就可以直接從表中查到;
-
user_like_comment
記錄用戶贊過了哪些文章, 當打開文章列表時,顯示的有沒有贊過的數據就在這里面;
缺點
-
數據庫讀寫壓力大
熱門文章會有很多用戶點贊,甚至是短時間內被大量點贊, 直接操作數據庫從長久來看不是很理想的做法
redis存儲隨后批量刷回數據庫
redis主要的特點就是快, 畢竟主要數據都在內存嘛;
另外為啥我選擇redis而不是memcache的主要原因在於redis支持更多的數據類型, 例如hash, set, zset等。
下面具體的會用到這幾個類型。
優點
-
性能高
-
緩解數據庫讀寫壓力
其實我更多的在於緩解寫壓力, 真的讀壓力, 通過mysql主從甚至通過加入redis對熱點數據做緩存都可以解決,
寫壓力對於前面的方案確實是不大好使。
缺點
-
開發復雜
這個比直接寫mysql的方案要復雜很多, 需要考慮的地方也很多;
-
不能保證數據安全性
redis掛掉的時候會丟失數據, 同時不及時同步redis中的數據, 可能會在redis內存置換的時候被淘汰掉;
不過對於我們點贊而已, 稍微丟失一點數據問題不大;
其實上面第二點缺點是可以避免的,這就涉及到redis 的一些設計模式,不懂沒關系,我盡量詳細的寫,后面我會給出如何解決這個缺點。
設計功能前知識准備
1.將要用到的redis數據類型(具體的類型說明,請看底部鏈接,有詳細說明):
- zset 這個類型主要用來做排序或者數字的增減,這里被用作like 和hate的數字記錄,以及熱度的記錄。
- set 這個是無序集合,主要用來記錄今天需不需要更新,將今天被點贊(包括點討厭)過的文章id記錄下來,方便晚上或者有時間對這部分數據更新。
- hash 這個是散列,主要用來存儲數據以及索引。這里被用來記錄用戶對哪個文章點了什么,方便下次判斷(我看過一些網上的介紹使用set來記錄,那個也可以,但是本人覺得這樣做更省空間,以及方便管理,再有就是hash的速度快)。
- list 這個是隊列大佬,我們的數據能不能 安全 回到mysql就靠它了。
2.關於熱度如何去判斷:
大家都知道,文章獲得點贊數越高,文章的熱度就越高,那么怎么判斷呢?不就直接記錄點贊數就行啦,但是對於最新的文章怎么辦?例如有一篇文章一年前發布的,獲得50個贊,有篇最新文章獲得49個贊,但是按照上面所說的一年前的文章熱度還比最新的高,這就不合理了,文章都是時效性,誰都想看最新最熱的。
so!我們要換個方法去處理這個時效性,絕大部分語言都有 時間戳 生成的方法,時間戳隨着時間越新,數字越大,直接將時間戳初始化賦值給文章的score,這樣最新的文章相比以前的文章就會靠前了。接着是點贊對score的影響,我們假設一天得到20個贊算是一天最熱,一天60*60*24=86400秒,然后得到一個贊就是得到86400 / 20 = 4320分。具體數字看自己的業務需求定,我只是舉例子而已。點hate當然也會減去相應的數字。
激動時刻!直接上代碼了!里面有詳細注釋!
1 <?php 2 3 class Good 4 { 5 public $redis = null; 6 7 //60*60*24/20=4320,每個點贊得到的分數,反之即之。 8 public $score = 4320; 9 10 //點贊增加數,或者點hate增加數 11 public $num = 1; 12 13 //init redis 14 public $redis_host = "127.0.0.1"; 15 public $redis_port = "6379"; 16 public $redis_pass = ""; 17 18 public function __construct() 19 { 20 $this->redis = new Redis(); 21 $this->redis->connect($this->redis_host,$this->redis_port); 22 $this->redis->auth($this->redis_pass); 23 } 24 25 /** 26 * @param int $user_id 用戶id 27 * @param int $type 點擊的類型 1.點like,2.點hate 28 * @param int $comment_id 文章id 29 * @return string json; 30 */ 31 public function click($user_id,$type,$comment_id) 32 { 33 //判斷redis是否已經緩存了該文章數據 34 //使用:分隔符對redis管理是友好的 35 //這里使用redis zset-> zscore()方法 36 if($this->redis->zscore("comment:like",$comment_id)) 37 { 38 //已經存在 39 //判斷點的是什么 40 if($type==1) 41 { 42 //判斷以前是否點過,點的是什么? 43 //redis hash-> hget() 44 $rel = $this->redis->hget("comment:record",$user_id.":".$comment_id); 45 if(!$rel) 46 { 47 //什么都沒點過 48 //點贊加1 49 $this->redis->zincrby("comment:like",$this->num,$comment_id); 50 //增加分數 51 $this->redis->zincrby("comment:score",$this->score,$comment_id); 52 //記錄上次操作 53 $this->redis->hset("comment:record",$user_id.":".$comment_id,$type); 54 55 $data = array( 56 "state" => 1, 57 "status" => 200, 58 "msg" => "like+1", 59 ); 60 } 61 else if($rel==$type) 62 { 63 //點過贊了 64 //點贊減1 65 $this->redis->zincrby("comment:like",-($this->num),$comment_id); 66 //增加分數 67 $this->redis->zincrby("comment:score",-($this->score),$comment_id); 68 $data = array( 69 "state" => 2, 70 "status" => 200, 71 "msg" => "like-1", 72 ); 73 } 74 else if($rel==2) 75 { 76 //點過hate 77 //hate減1 78 $this->redis->zincrby("comment:hate",-($this->num),$comment_id); 79 //增加分數 80 $this->redis->zincrby("comment:score",$this->score+$this->score,$comment_id); 81 //點贊加1 82 $this->redis->zincrby("comment:like",$this->num,$comment_id); 83 //記錄上次操作 84 $this->redis->hset("comment:record",$user_id.":".$comment_id,$type); 85 86 $data = array( 87 "state" => 3, 88 "status" => 200, 89 "msg" => "like+1", 90 ); 91 } 92 } 93 else if($type==2) 94 { 95 //點hate和點贊的邏輯是一樣的。參看上面的點贊 96 $rel = $this->redis->hget("comment:record",$user_id.":".$comment_id); 97 if(!$rel) 98 { 99 //什么都沒點過 100 //點hate加1 101 $this->redis->zincrby("comment:hate",$this->num,$comment_id); 102 //減分數 103 $this->redis->zincrby("comment:score",-($this->score),$comment_id); 104 //記錄上次操作 105 $this->redis->hset("comment:record",$user_id.":".$comment_id,$type); 106 107 $data = array( 108 "state" => 4, 109 "status" => 200, 110 "msg" => "hate+1", 111 ); 112 } 113 else if($rel==$type) 114 { 115 //點過hate了 116 //點hate減1 117 $this->redis->zincrby("comment:hate",-($this->num),$comment_id); 118 //增加分數 119 $this->redis->zincrby("comment:score",$this->score,$comment_id); 120 121 $data = array( 122 "state" => 5, 123 "status" => 200, 124 "msg" => "hate-1", 125 ); 126 return $data; 127 } 128 else if($rel==2) 129 { 130 //點過like 131 //like減1 132 $this->redis->zincrby("comment:like",-($this->num),$comment_id); 133 //增加分數 134 $this->redis->zincrby("comment:score",-($this->score+$this->score),$comment_id); 135 //點hate加1 136 $this->redis->zincrby("comment:hate",$this->num,$comment_id); 137 138 $data = array( 139 "state" => 6, 140 "status" => 200, 141 "msg" => "hate+1", 142 ); 143 return $data; 144 } 145 } 146 } 147 else 148 { 149 //未存在 150 if($type==1) 151 { 152 //點贊加一 153 $this->redis->zincrby("comment:like",$this->num,$comment_id); 154 //分數增加 155 $this->redis->zincrby("comment:score",$this->score,$comment_id); 156 $data = array( 157 "state" => 7, 158 "status" => 200, 159 "msg" => "like+1", 160 ); 161 } 162 else if($type==2) 163 { 164 //點hate加一 165 $this->redis->zincrby("comment:hate",$this->num,$comment_id); 166 //分數減少 167 $this->redis->zincrby("comment:score",-($this->score),$comment_id); 168 169 $data = array( 170 "state" => 8, 171 "status" => 200, 172 "msg" => "hate+1", 173 ); 174 } 175 //記錄 176 $this->redis->hset("comment:record",$user_id.":".$comment_id,$type); 177 } 178 179 //判斷是否需要更新數據 180 $this->ifUploadList($comment_id); 181 182 return $data; 183 } 184 185 public function ifUploadList($comment_id) 186 { 187 date_default_timezone_set("Asia/Shanghai"); 188 $time = strtotime(date('Y-m-d H:i:s')); 189 190 if(!$this->redis->sismember("comment:uploadset",$comment_id)) 191 { 192 //文章不存在集合里,需要更新 193 $this->redis->sadd("comment:uploadset",$comment_id); 194 //更新到隊列 195 $data = array( 196 "id" => $comment_id, 197 "time" => $time, 198 ); 199 $json = json_encode($data); 200 $this->redis->lpush("comment:uploadlist",$json); 201 } 202 } 203 } 204 205 //調用 206 $user_id = 100; 207 $type = 1; 208 $comment_id= 99; 209 $good = new Good(); 210 $rel = $good->click($user_id,$type,$comment_id); 211 var_dump($rel);
溫馨提示:
1.上面代碼只是一個實現的方法之一,里面的代碼沒精分過,適合大部分小伙伴閱讀。用心看總有收獲。
2.對於第三方接口,應該在外面包裝多一層的,但是邊幅有限,我就不做這么詳細,提示,大家可以作為參考。
3.剩下的將數據返回數據的方法,等下篇再繼續了。歡迎大家來交流心得。
redis手冊中文版傳送門:http://www.cnblogs.com/zcy_soft/archive/2012/09/21/2697006.html#string_INCR;