这次的开篇,算是总结下这段时间来的积累吧,废话不多说,直接干!

前言

点赞其实是一个很有意思的功能。基本的设计思路有大致两种, 一种自然是用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当然也会减去相应的数字。

激动时刻!直接上代码了!里面有详细注释!

 <?php

 class Good
{
public $redis = null; //60*60*24/20=4320,每个点赞得到的分数,反之即之。
public $score = 4320; //点赞增加数,或者点hate增加数
public $num = 1; //init redis
public $redis_host = "127.0.0.1";
public $redis_port = "6379";
public $redis_pass = ""; public function __construct()
{
$this->redis = new Redis();
$this->redis->connect($this->redis_host,$this->redis_port);
$this->redis->auth($this->redis_pass);
} /**
* @param int $user_id 用户id
* @param int $type 点击的类型 1.点like,2.点hate
* @param int $comment_id 文章id
* @return string json;
*/
public function click($user_id,$type,$comment_id)
{
//判断redis是否已经缓存了该文章数据
//使用:分隔符对redis管理是友好的
//这里使用redis zset-> zscore()方法
if($this->redis->zscore("comment:like",$comment_id))
{
//已经存在
//判断点的是什么
if($type==1)
{
//判断以前是否点过,点的是什么?
//redis hash-> hget()
$rel = $this->redis->hget("comment:record",$user_id.":".$comment_id);
if(!$rel)
{
//什么都没点过
//点赞加1
$this->redis->zincrby("comment:like",$this->num,$comment_id);
//增加分数
$this->redis->zincrby("comment:score",$this->score,$comment_id);
//记录上次操作
$this->redis->hset("comment:record",$user_id.":".$comment_id,$type); $data = array(
"state" => 1,
"status" => 200,
"msg" => "like+1",
);
}
else if($rel==$type)
{
//点过赞了
//点赞减1
$this->redis->zincrby("comment:like",-($this->num),$comment_id);
//增加分数
$this->redis->zincrby("comment:score",-($this->score),$comment_id);
$data = array(
"state" => 2,
"status" => 200,
"msg" => "like-1",
);
}
else if($rel==2)
{
//点过hate
//hate减1
$this->redis->zincrby("comment:hate",-($this->num),$comment_id);
//增加分数
$this->redis->zincrby("comment:score",$this->score+$this->score,$comment_id);
//点赞加1
$this->redis->zincrby("comment:like",$this->num,$comment_id);
//记录上次操作
$this->redis->hset("comment:record",$user_id.":".$comment_id,$type); $data = array(
"state" => 3,
"status" => 200,
"msg" => "like+1",
);
}
}
else if($type==2)
{
//点hate和点赞的逻辑是一样的。参看上面的点赞
$rel = $this->redis->hget("comment:record",$user_id.":".$comment_id);
if(!$rel)
{
//什么都没点过
//点hate加1
$this->redis->zincrby("comment:hate",$this->num,$comment_id);
//减分数
$this->redis->zincrby("comment:score",-($this->score),$comment_id);
//记录上次操作
$this->redis->hset("comment:record",$user_id.":".$comment_id,$type); $data = array(
"state" => 4,
"status" => 200,
"msg" => "hate+1",
);
}
else if($rel==$type)
{
//点过hate了
//点hate减1
$this->redis->zincrby("comment:hate",-($this->num),$comment_id);
//增加分数
$this->redis->zincrby("comment:score",$this->score,$comment_id); $data = array(
"state" => 5,
"status" => 200,
"msg" => "hate-1",
);
return $data;
}
else if($rel==2)
{
//点过like
//like减1
$this->redis->zincrby("comment:like",-($this->num),$comment_id);
//增加分数
$this->redis->zincrby("comment:score",-($this->score+$this->score),$comment_id);
//点hate加1
$this->redis->zincrby("comment:hate",$this->num,$comment_id); $data = array(
"state" => 6,
"status" => 200,
"msg" => "hate+1",
);
return $data;
}
}
}
else
{
//未存在
if($type==1)
{
//点赞加一
$this->redis->zincrby("comment:like",$this->num,$comment_id);
//分数增加
$this->redis->zincrby("comment:score",$this->score,$comment_id);
$data = array(
"state" => 7,
"status" => 200,
"msg" => "like+1",
);
}
else if($type==2)
{
//点hate加一
$this->redis->zincrby("comment:hate",$this->num,$comment_id);
//分数减少
$this->redis->zincrby("comment:score",-($this->score),$comment_id); $data = array(
"state" => 8,
"status" => 200,
"msg" => "hate+1",
);
}
//记录
$this->redis->hset("comment:record",$user_id.":".$comment_id,$type);
} //判断是否需要更新数据
$this->ifUploadList($comment_id); return $data;
} public function ifUploadList($comment_id)
{
date_default_timezone_set("Asia/Shanghai");
$time = strtotime(date('Y-m-d H:i:s')); if(!$this->redis->sismember("comment:uploadset",$comment_id))
{
//文章不存在集合里,需要更新
$this->redis->sadd("comment:uploadset",$comment_id);
//更新到队列
$data = array(
"id" => $comment_id,
"time" => $time,
);
$json = json_encode($data);
$this->redis->lpush("comment:uploadlist",$json);
}
}
} //调用
$user_id = 100;
$type = 1;
$comment_id= 99;
$good = new Good();
$rel = $good->click($user_id,$type,$comment_id);
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配置文件,如 ...

随机推荐

  1. C# 相对路径转绝对路径

    如果是路径相对路径,使用 Path 转换 System.IO.Path.Combine(文件夹, relativePath); 文件夹就是相对的文件夹. 这样就可以把相对路径转绝对. 参见:http: ...

  2. javascript面向对象的写法及jQuery面向对象的写法

    文章由来:jQuery源码学习时的总结 在JS中,一般的面向对象的写法如下: function Cao(){}//定义一个构造函数 Cao.prototype.init = function(){}/ ...

  3. Django——模板层(template)(模板语法、自定义模板过滤器及标签、模板继承)

    前言:当我们想在页面上给客户端返回一个当前时间,一些初学者可能会很自然的想到用占位符,字符串拼接来达到我们想要的效果,但是这样做会有一个问题,HTML被直接硬编码在 Python代码之中. 1 2 3 ...

  4. Yii2之AR类的坑与总结

    本文主要记录本人从学习yii2到使用yii2开发项目过程中遇到的一些问题以及解决方法和知识点总结. 1. AR类关联哪个数据表? 默认情况下,AR类会根据类名称关联和类名相关的数据表,如:类名为Cou ...

  5. vue.js快速搭建图书管理平台

      前  言 上一期简单讲解了vue的基本语法,这一次我们做一个小项目,搭建一个简单的图书管理平台,能够让我们更深刻的理解这门语言的妙用.   1.DEMO样式 首先我们需要搭建一个简单的demo样式 ...

  6. How the Intelligent Digital Mesh Will Transform Every Business Layer

    The "intelligent digital mesh" is the definitive package for tomorrow's titans of business ...

  7. 正则和grep——再做正则就去死

    grep 文本过滤工具 基本正则表达式 grep 语法 基本正则表达式的元字符 次数匹配 位置锚定 分组 扩展正则表达式 基本正则表达式的元字符 次数匹配 位置锚定 分组 或者 grep的介绍 lin ...

  8. ASP.NET中登录时记住用户名和密码(附源码下载)--ASP.NET

    必需了解的:实例需要做的是Cookie对象的创建和对Cookie对象数据的读取,通过Response对象的Cookies属性创建Cookie,通过Request对象的Cookies可以读取Cookie ...

  9. IOS 中的JS

     文章摘自: http://www.cocoachina.com/ios/20150127/11037.html  JSContext/JSValue JSContext 即JavaScript代码的 ...

  10. SIP简介,第1部分:SIP初探

    说明:以下内容来着之前下载的一份文档,现将概念部分摘录在BLog,完成文档将放在文件中. SIP简介,第1部分:SIP初探 时间:2006-04-07作者:Emmanuel Proulx浏览次数: 2 ...