高并发下,获取最新评论

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

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

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

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

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

图1

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

图2

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

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

群晖翻墙中转

家里的ps4是港服,一直想给ps搞个靠谱的代理方案。之前准备买个靠谱路由器刷梅林或者wrt搞,但是靠谱的并不是我这个小穷比现在承受的起的。。。

然后想起来家里还有一个群晖在跑,而且群晖还支持docker。。。

先到 docker hub 拉一个haproxy的镜像,然后还要ssh连上去写一个dockerfile,重新打包一个镜像。

群晖的docker套件不支持dockerfile build真是不人性啊。

最近也把想折腾的基本都折腾完了,搭了一个leanote,把所有该备份的都备份了一遍,ps4的翻墙也搞定了。该开开心心过年啦~

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();
    }
}

参考: