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

此次的開篇,算是總結下這段時間來的積累吧,廢話很少說,直接幹!php

前言

點贊實際上是一個頗有意思的功能。基本的設計思路有大體兩種, 一種天然是用mysql(寫了幾百行的代碼都還沒寫完,有毒)啦html

數據庫直接落地存儲, 另一種就是利用點讚的業務特徵來扔到redis(或memcache)中, 而後離線刷回mysql等。mysql

我這裏所講的功能都是基於我以前的項目去說的,因此有些地方能夠不用管的,我主要是記錄這個功能的實現思路,當你理解了,基本想用什麼鬼語言寫都同樣的。redis

直接寫入Mysql

直接寫入Mysql是最簡單的作法。sql

作三個表便可,數據庫

  • comment_infojson

    記錄文章的主要內容,主要有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;

相關文章
相關標籤/搜索