前言

点赞其实是一个很有意思的功能。基本的设计思路有大致两种, 一种自然是用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;

PHP+Redis 实例【一】点赞 + 热度的更多相关文章

  1. redis 实例2 构建文章投票网站后端

    redis 实例2 构建文章投票网站后端   1.限制条件 一.如果网站获得200张支持票,那么这篇文章被设置成有趣的文章 二.如果网站发布的文章中有一定数量被认定为有趣的文章,那么这些文章需要被设置 ...

  2. 基于redis实现的点赞功能设计思路详解

    点赞其实是一个很有意思的功能.基本的设计思路有大致两种, 一种自然是用mysql等 数据库直接落地存储, 另外一种就是利用点赞的业务特征来扔到redis(或memcache)中, 然后离线刷回mysq ...

  3. 一次线上redis实例cpu占用率过高问题优化(转)

    前情提要: 最近接了大数据项目的postgresql运维,刚接过来他们的报表系统就出现高峰期访问不了的问题,报表涉及实时数据和离线数据,离线读pg,实时读redis.然后自然而然就把redis也挪到我 ...

  4. 使用CacheCloud管理Redis实例

    转载来源:http://www.ywnds.com/?p=10610 一.CacheCloud是什么? 最近在使用CacheCloud管理Redis,所以简单说一下,这里主要说一下我碰到的问题.Cac ...

  5. python3.4学习笔记(二十五) Python 调用mysql redis实例代码

    python3.4学习笔记(二十五) Python 调用mysql redis实例代码 #coding: utf-8 __author__ = 'zdz8207' #python2.7 import ...

  6. Redis 实例排除步骤

    Redis 应用案例 - 在问题中不断成长 原创 2017-02-05 杜亦舒  本文翻译整理自 Andy Grunwald 发布的一篇文章,写的是作者所在公司使用 Redis 时遇到的问题,以及处理 ...

  7. 生产消费者模式与python+redis实例运用(中级篇)

    上一篇文章介绍了生产消费者模式与python+redis实例运用(基础篇),但是依旧遗留了一个问题,就是如果消费者消费的速度跟不上生产者,依旧会浪费我们大量的时间去等待,这时候我们就可以考虑使用多进程 ...

  8. 生产消费者模式与python+redis实例运用(基础篇)

    根据这个图,我们举个简单的例子:假如你去某个餐厅吃饭,点了很多菜,厨师要一个一个菜的做,一个厨师不可能同时做出所有你点的菜,于是你有两个选择:第一个,厨师把所有菜都上齐了,你才开始吃:还有一个选择,做 ...

  9. 一台机器上搭建多个redis实例的配置文件修改部分

    1.单个redis服务搭建请参考:redis服务搭建 2.一台Redis服务器,分成多个节点,每个节点分配一个端口(6380,6381…),默认端口是6379. 每个节点对应一个Redis配置文件,如 ...

  10. centos安装redis并开启多个redis实例

    1.下载安装包       下载地址 :  http://download.redis.io/releases/,去里面找对应的版本下载        例如  wget http://download ...

随机推荐

  1. 定了!AIRIOT新品发布会,6月6日北京见。

    随着物联网.大数据.AI技术的成熟和演进,智能物联网技术正在加速.深入渗透至各行业应用. AIRIOT物联网平台作为赋能数字经济发展和产业转型的数字基座,由航天科技控股集团股份有限公司(股票代码:00 ...

  2. 怀念中的java

    学了这门语言后一直没能做成项目,倒是安装环境,用记事本编辑的话,除了js最好做的就是java了. 以前学java的时候是一帮很有朝气的同学,在一个培训班,每天苦哈哈.从c开始学的语言,学完基础部分转入 ...

  3. 昇腾开发全流程 之 MindSpore华为云模型训练

    前言 学会如何安装配置华为云ModelArts.开发板Atlas 200I DK A2, 并打通一个训练到推理的全流程思路. 在本篇章,首先我们开始进入训练阶段! 训练阶段 A. 环境搭建 MindS ...

  4. IDEA使用——新建WEB项目及WEB项目的运行

    第一步:新建项目 1.2勾选Web Application 1.3填写项目名 第二步:项目配置 2.1在WEB-INF目录下新建 classes 和 lib 目录(过程省略) 2.2将classes目 ...

  5. HTML——select下拉选择标签

    select的基本语法: <select> <option></option> </select> 例子: <p>籍贯: <selec ...

  6. 在Rainbond上部署高可用Apollo集群

    一.背景信息 当前文档描述如何通过云原生应用管理平台 Rainbond 一键安装高可用 Apollo 集群.这种方式适合给不太了解 Kubernetes.容器化等复杂技术的用户使用,降低了在 Kube ...

  7. Aspire项目发布到远程k8s集群

    前提 你必须会创建aspire项目,不会的请先看微服务新体验之Aspire初体验 Aspirate (Aspir8) Aspirate 是将aspire项目发布到k8s集群的工具 安装aspirate ...

  8. sort awk 文本处理命令

    sort: 1.将文件的每一行作为一个单位,相互比较 2.默认升序 3.以字符来进行对比,从首字符开始往后,依次按ASCII码值排序 sort 显示文件内容 (类似cat) 选项: -u 去掉重复行 ...

  9. numpy基础--通用函数:快速的元素级数组函数

    以下代码的前提:import numpy as np 通用函数(即ufunc)是一种对narray中的数组执行元素级运算的函数.可以看作简单函数(接受一个或多个标量值,并产生一个或多个标量值)的矢量化 ...

  10. idea 使用 mvn clean package 报错 Could not create local repository at

    使用 mac 版本的 idea 打包使用打包命令 mvn clean package 总是报错: [ERROR] Could not create local repository at /Repos ...