分类 PHP 下的文章

高并发下,获取最新评论

加入直播行业四个半月之后,终于接到一个有挑战活。

我司评论系统设计要求观众打开页面时,获取若干条最新历史评论。但是获取评论的地方有bug,获取评论会重复。

在解决这一Bug之后,我发现我们原有的评论逻辑会每隔5s缓存一次最新评论。这样做能减少服务器压力,但是在高并发的情况下,会造成大量丢失。请示大佬之后,大佬表示要达到数据一条不丢失的标准。

在简单研究之后,先将缓存逻辑改为:只要有新发评论,那么就让最新缓存失效,并让第一个请求数据的用户生成缓存。这样的话,用户取到的缓存永远都是最新的。

But!Naive!测试、分析之后发现,在我们现有前后端交互逻辑(下基本做不到数据不丢失(也可能是我太渣了)。下面是我画的我们现有逻辑的时序图(图1),图为高并发情况下,缓存失效时观众获取最新评论过程,图中省略的cache。在3~4步中,如果新插入数据超出了本页范围,那么新的数据将不会被取到。在5~7步消耗的时间中,用户有可能丢失上百条评论。

图1

分析上述交互逻辑,发现主要冲突在于获取最新数据和不断插入新数据之间的矛盾。那么调整下评论加载顺序,问题不就引刃而解了么!下图(图2)是调整后的交互逻辑:

图2

这里也有一点尴尬的地方:万一mqtt并没有收到消息,那么就需要使用上图1中 1~6的逻辑来获取评论了。。。

而更尴尬的是:咨询产品的大佬之后,产品表示这么高并发情况下,用户根本感受不到评论丢失,完全不用管,所以上面都是我在YY... T_T

PHP 变量存储初探

前几天在排查一个系统BUG的时候发现一段很有意思的代码

$obj=Obj()::where()->get();//这里是eloquent方法,大概15K条数据
foreach($obj as $k=>$v){
        bar();
    if($v->user){//eloquent 的has one
        foo();
    }
}

这段代码撑爆了单线程128M的内存限制。原因就在循环中$v->user没有释放内存。根据PHP手册对foreach的描述,我们可以得知这里的$v是拷贝赋值,并不是引用赋值。那么为什么每次循环结束的时候,这些内存没有被回收?

答案在于php存储变量的方式中。PHP中的变量存在一个叫zval的结构体中,我们所用的$a,$b...都只是一个变量的标签,变量真正的值都存在zval容器中。其实每次循环结束$v所占用的内存都已经被回收,但是销毁的 $v只是一个符号,对象真正使用的空间并没有被回收。看完下面这段代码,就应该能很容易的了解到底发生了什么。

echo "<br/>----------- Init a -------------<br/>";
$a=new stdClass();
$a->foo=1;
xdebug_debug_zval('a');

echo "<br/>----------- Init b ------------<br/>";
$b=$a;
$b->foo="bar";
xdebug_debug_zval('a');
xdebug_debug_zval('b');

echo "<br/>----------- Unset b ------------<br/>";
unset($b);
xdebug_debug_zval('a');

这段代码将返回:

----------- Init a -------------
a:
(refcount=1, is_ref=0),
object(stdClass)[1]
  public 'foo' => (refcount=1, is_ref=0),int 1

----------- Init b ------------
a:
(refcount=2, is_ref=0),
object(stdClass)[1]
  public 'foo' => (refcount=1, is_ref=0),string 'bar' (length=3)
b:
(refcount=2, is_ref=0),
object(stdClass)[1]
  public 'foo' => (refcount=1, is_ref=0),string 'bar' (length=3)

----------- Unset b ------------
a:
(refcount=1, is_ref=0),
object(stdClass)[1]
  public 'foo' => (refcount=1, is_ref=0),string 'bar' (length=3)

这里销毁了$b,但是却并没有将对象销毁掉。所以,在上面的的foreach中,orm对象不停的叠加user中的属性,而一直没有被回收,最终撑爆了内存。

解决方案是我司zkq指点我的,他让我尝试下深拷贝,完美解决。

$obj=Obj()::where()->get();
foreach($obj as $k=>$v){
        $value = clone $v;//克隆一个对象
        bar();
    if($value->user){//使用克隆的对象,循环结束立刻销毁
        foo();
    }
}

参考:

PHP 中的单双引号

php 果然很有趣

<?php
    $a1 = null;
    $a2 = 'null';
    $a3 = "null";
    $a4 = 0;
    $a5 = '0';
    $a6 = "0";
    $a7 = array();
    $a8 = array(array());
    echo empty($a1) ? 'true' : 'false';
    echo empty($a2) ? 'true' : 'false';
    echo empty($a3) ? 'true' : 'false';
    echo empty($a4) ? 'true' : 'false';
    echo empty($a5) ? 'true' : 'false';
    echo empty($a6) ? 'true' : 'false';
    echo empty($a7) ? 'true' : 'false';
    echo empty($a8) ? 'true' : 'false';
?>

得到以下奇怪的结果

文件 是以 php -f test.php执行
cli 是以php -r '$a=null;echo empty($a) ? 'true' : 'false';'执行
参数 | 文件执行 | cli执行

nulltrue1
'null'false1
"null"falsenull(这里只是占位,php什么都没有输出)
0true1
'0'true1
"0"true1
array()true1
array(array())falsenull(这里只是占位,php什么都没有输出)

看起来是cli 将 ‘null’ ‘true’ ‘false’ 都当作了参数,没有按照字符串来处理。但是文件模式中却按照字符串处理

Walle,我可能的救星

Walle 一个web部署系统工具,配置简单、功能完善、界面流畅、开箱即用!支持git、svn版本管理,支持各种web代码发布,PHP,Python,JAVA等代码的发布、回滚,可以通过web来一键完成。

3月中旬换工作到现在的公司。进来之后简直被震惊到了!!!代码写的各种乱,到处都是漏洞,MySQL玩出了 NoSQL 的感觉。非常庞大的PHP项目,测试和DEV是同一个环境!!!上线居然还是FTP手动上传!!!

记得某次上线一些新功能。之前测试已经在测试环境测试通过了,但是当我们开始ftp上传的时候,噩梦开始了。

首先是上线的文件不明确,根本记不住自己一周之前到底改过那些文件,也不知到那些别人修改的文件会影响到项目,然后最严重的问题就是:根本不知道线上环境和测试环境到底差多少个版本!

文件上传之后各种BUG不断,要么就是文件漏传,要么就是某些修改过的文件影响线上环境。我们不得不在线上直接DEBUG,测试同事不得不一遍又一遍的验证线上环境。那天晚上从晚上七点折腾到凌晨两点多。

这种上线方式,除了带来许多毫无意义的重复劳动和不明确的风险之外,对大家的士气和工作积极性也是不小的打击。现在每次提上线,我都有一种要去上刑的感觉。其实这种局面只要作出一些简单的改变即可扭转:部署工具。之前有推荐过现在比较流行的jenkins来做持续集成和部署(我司也有JAVA团队),但是不知道为什么后来没下文了。。。

最近在V2EX上看到Walle,然后到他们的官网了解一下。觉得非常适合我们的团队:结构简洁、使用简单、所用技术栈我司也能hold住(Yii,Composer,Bash,Git...)。

周六抽空尝试了一下,简直High的不行,我仿佛看到了将来的光明!But!我得先在我司推广Git...

路漫漫其修远兮 吾将上下而求索

laravel homestead 下载

百度半天没找到homestead的下载地址

文档中只找到了这个vagrant地址 https://atlas.hashicorp.com/laravel/boxes/homestead

进去之后才发现,居然没有下载链接!!

以上都是废话,请看下文

打开

https://atlas.hashicorp.com/laravel/boxes/homestead

找到右上角的版本号,比如当前版本是:V0.2.7

https://atlas.hashicorp.com/laravel/boxes/homestead/versions/******/providers/virtualbox.box

将上面‘*’替换为版本号,例如

https://atlas.hashicorp.com/laravel/boxes/homestead/versions/0.2.7/providers/virtualbox.box

好了,可以直接迅雷了。我这边移动网,大概2M/S ~~