PHP+Redis 实例【一】点赞 + 热度 上篇
本文内容纲要:
-前言
-直接写入Mysql
-缺点
-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
3classGood
4{
5public$redis=null;
6
7//60*60*24/20=4320,每个点赞得到的分数,反之即之。
8public$score=4320;
9
10//点赞增加数,或者点hate增加数
11public$num=1;
12
13//initredis
14public$redis_host="127.0.0.1";
15public$redis_port="6379";
16public$redis_pass="";
17
18publicfunction__construct()
19{
20$this->redis=newRedis();
21$this->redis->connect($this->redis_host,$this->redis_port);
22$this->redis->auth($this->redis_pass);
23}
24
25/**
26*@paramint$user_id用户id
27*@paramint$type点击的类型1.点like,2.点hate
28*@paramint$comment_id文章id
29*@returnstringjson;
30*/
31publicfunctionclick($user_id,$type,$comment_id)
32{
33//判断redis是否已经缓存了该文章数据
34//使用:分隔符对redis管理是友好的
35//这里使用rediszset->zscore()方法
36if($this->redis->zscore("comment:like",$comment_id))
37{
38//已经存在
39//判断点的是什么
40if($type==1)
41{
42//判断以前是否点过,点的是什么?
43//redishash->hget()
44$rel=$this->redis->hget("comment:record",$user_id.":".$comment_id);
45if(!$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}
61elseif($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}
74elseif($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}
93elseif($type==2)
94{
95//点hate和点赞的逻辑是一样的。参看上面的点赞
96$rel=$this->redis->hget("comment:record",$user_id.":".$comment_id);
97if(!$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}
113elseif($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);
126return$data;
127}
128elseif($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);
143return$data;
144}
145}
146}
147else
148{
149//未存在
150if($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}
162elseif($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
182return$data;
183}
184
185publicfunctionifUploadList($comment_id)
186{
187date_default_timezone_set("Asia/Shanghai");
188$time=strtotime(date('Y-m-dH:i:s'));
189
190if(!$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=newGood();
210$rel=$good->click($user_id,$type,$comment_id);
211var_dump($rel);
温馨提示:
1.上面代码只是一个实现的方法之一,里面的代码没精分过,适合大部分小伙伴阅读。用心看总有收获。
2.对于第三方接口,应该在外面包装多一层的,但是边幅有限,我就不做这么详细,提示,大家可以作为参考。
3.剩下的将数据返回数据的方法,等下篇再继续了。欢迎大家来交流心得。
redis手册中文版传送门:http://www.cnblogs.com/zcy_soft/archive/2012/09/21/2697006.html#string_INCR;
本文内容总结:前言,直接写入Mysql,缺点,redis存储随后批量刷回数据库,优点,缺点,
原文链接:https://www.cnblogs.com/sunshine-H/p/7922285.html