PHP+Redis 實例【一】點贊 + 熱度 上篇


這次的開篇,算是總結下這段時間來的積累吧,廢話不多說,直接干!

前言

點贊其實是一個很有意思的功能。基本的設計思路有大致兩種, 一種自然是用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;

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM