捉到 Edge 的渲染 BUG 一只

最近测试同学发现,用 Edge 浏览器,访问问卷的新版设置界面,部分联动锁总是显示异常,该锁的没锁,不该锁的又锁上了。

期初以为是 React 在 Edge 里面有什么 bug,一圈排查下来,发现状态、样式都是正确的,偏偏就是显示出来的很诡异,鼠标在上面晃动一下又正常了(浏览器绘制BUG,以前 IE7 也遇到过)。

这个 BUG 的特征,当使用 :disabled ~ label 这种伪类 + 相邻元素进行样式定义的时候,如果动态修改 input.disabled 的值,其对应的 label 的表现就不会更新。

Demo

继续阅读“捉到 Edge 的渲染 BUG 一只”

React Key 在非数组、队列场景下的应用

猜猜这段代码会发生什么事?

以为仅仅是切换下按钮?

其实是会执行下面的流程

  1. isEditingfalse 的时候,点击 button
  2. isEditing 变成了 true
  3. 重新 render,在 virtualDOM 进行 diff 的时候,给 button 添加了 type=submit 以及 form='form-1'
  4. #form-1 表单被意外的 submit 了

来个 DEMO 验证下 继续阅读“React Key 在非数组、队列场景下的应用”

基于 styled-components 实现一套皮肤系统

结构定义

styled-components 使用模版字符串特性,让我们可以保持原有 CSS 的书写习惯来编写 CSS,同时,利用 ${ props => props.theme.xxx } 的方式,实现皮肤系统中挖空填值的能力。

继续阅读“基于 styled-components 实现一套皮肤系统”

利用 Promise 实现任务流的自动重试

背景

微信小程序不支持 HTTP 的 cookie ,其会话机制是通过开发自己维护一个 session_id 在小程序的本地存储中,每次调用 wx.request 的时候都带上这个 session_id 来实现的会话机制。

那么,有会话机制,就会存在会话失效、更新等等问题。

传统的 HTTP cookie-session 机制,当会话失效的时候,可以在 HTTP 的返回头里面通过 setcookie 来静默返回一个新的 session_id ,小程序就比较麻烦。

传统的实现方案

1.理想的实现情况

2.为了容错,我们会添加返回判断,但错误的时候,再调用一次

上面这种方式,在接口的返回值中做一次判断,然后再执行一次,好像就解决问题了。

但如果我们业务的接口非常多,返回判断是不是要添加很多次呢?

继续阅读“利用 Promise 实现任务流的自动重试”

await 性能猜想

前言

以往我们推崇异步 I/O 来实现高并发下的高性能,如今 NodeJS 步入 8.x 时代,async await 可以用同步的写法来实现异步处理,不知道对性能是否会有影响,来做个简单的测试。

测试基准

Linode 1G 配置两台,一台用 ab 发请求,另一台跑4个测试用例。

先用 Nginx 跑一个默认服务,返回一个 html 文件,测试基准性能。

Nginx 访问默认的 html 文件,QPS 为 5162

同步访问文件

fs.readFileSync 是 fs.readFile 的同步版本

测试结果

QPS 为 3195

异步访问文件

测试结果

QPS 为 2945

使用 async 来封装异步操作

测试结果

QPS 为 2855

对比

  • 5162 Nginx
    • -1967
  • 3195 fs.readFileSync
    • -250
  • 2945 fs.readFile
    • -90
  • 2855 await promise fs.readFile

本来猜测的结果,应该是 Nginx >  fs.readFile > fs.readFIleSync > await + promise + fs.readFile

实际结果却是 Nginx > fs.readFileSync > fs.readFile > await + promise + fs.readFile,这下傻眼

 

尝鲜 Ubuntu 17.04

冲着自带 Kernel 4.10 去的

可以不用 apt-mark 来锁住内核版本,避免更新的时候又自动降级了

不知道是今晚线路波动还是其他原因,升级后,明显感觉 SSH 卡了很多,检查过 BBR 已经开启,起了怪了

wget 一个百兆文件,开 vnstat -l 进行统计

proxy_pass 的结尾要不要 “/” ?

这周有个临时需求,需要在我们的业务上面集成一个合作方的站点,这种事情用 proxy_pass 就非常方便

一开始用的第一个方法,结果一直提示 404,还以为 Location 规则没命中,反复查证,却忘了第一个配置会把 path 给透传到 proxy_pass 端,实际上我一直在访问 http://127.0.0.1:3000/3rd/ ,而这个地址显然是不存在的

后来排查后端的日志才发现,在结尾补上 “/” 后,终于把 /3rd/ 这个路径给吃掉了

Backbone源码研究 – Backbone.Model

前言

都因为 IE8 不支持 Object.defineProperty,但是业务还不能脱离 IE7 和 IE8,故研究下 Backbone.Model 的实现机制,找机会给主流的 MVVM 框架补丁

伪代码

先来看看 Model 的构造函数

很简单的代码,做了一些初始化赋值的事情。

用到了一个小技巧  attrs = _.defaults(_.extend({}, defaults, attrs), defaults);  来防止误传入的 undefined 覆盖掉默认的 defaults 值。

Backbone 的精粹都在 set(){} 这个函数里面。

整个 set 里面,实际干活的就是 unset ? delete current[attr] : current[attr] = val;  。

没看明白 this._changing 和 this._pending 的使用场景,感觉是一个当多个 set 同时执行时候的一个标记位,但是 JS 是单线程执行,里面又都是 for 语句,按理说可以不用这两个标记位。又或者是我的理解有误。

more

看到这,给各种Observer打补丁就有了可行性,支持 Object.defineProperty 就用 Object.defineProperty,不支持的则降级到走 Backbone 的这种 for in 方式。

涨姿势 – 不一样的服务端长连接方案 – 客户端代理

仔细再看一次腾讯云的小程序解决方案,发现一个新大陆。

传统的 LAMP 架构,PHP-CGI  这种方式是很难处理长连接的。要么写死循环的方式来握住请求,要么使用 swoole 这种,通过 C 拓展来支持。

而 NodeJS 由于官方 DEMO 就是支持跑一个 HTTP 服务,所以处理这些长连接会方便一些(大家好懂一些,我觉得 PHP-CLI 方式也是一样的)。

信道服务

建立连接过程

  1. 小程序请求业务服务器
  2. 业务服务器和信道服务建立连接
  3. 业务服务器告诉小程序你可以和信道服务建立 ws 了
  4. 小程序和信道服务建立 ws
  5. 信道服务请求业务小程序的 ws 建立完成

连接成功后的通信方式

  1. 小程序请求信道服务,信道服务转发请求给业务
  2. 业务请求信道服务,信道服务推送到客户端

我最开始看的时候,没注意信道服务是一个云服务,琢磨着腾讯云的 SDK 难道有什么新的黑魔法来实现 PHP 的长连接。

https://github.com/tencentyun/wafer-php-server-sdk/blob/master/lib/Tunnel/TunnelService.php

看里面的代码,各种 onConnect\onRequest,看着就很像长连接的 API,但他基于 CI 是怎么实现的长连接了?看 composer.json 里面没有用什么黑魔法,搜索代码里面没没看到任何死循环。

客户端代理

为什么信道服务的 icon 是一朵云?

原来腾讯云把这个信道服务抽象成了一个 PaaS 的云服务,这根本就是一个客户端代理。而开源出来的 PHP-SDK,里面没有任何长连接的实现方案。

  • 业务服务器 -> 小程序:业务服务器只需要请求信道服务即可,信道服务接收到业务的请求后,会将 HTTP Body 部分,转成 WS 的消息推送给小程序。
  • 小程序 -> 业务服务器:则正常通过 WS 发送消息给信道服务,信道服务转成 HTTP 请求转发到业务服务器。

对于一些使用 PHP 开发的历史业务,大规模的长连接改造是非常困难,但部署一个长连接转发服务却容易很多(比如用 NodeJS 来写一个)。

腾讯云的这个思路可以帮助各种业务快速支持包括 SSE 和 WS 这两种长连接方案。并且由于业务服务器本身是 HTTP 方式,可以很容易给低版本 IE 做轮训兼容。

最后感谢腾讯云开源了他们的 wafer 方案。

Linux Kernel 4.9 & BBR

Google 真是共享了个好东西,先来看看效果。

Kernel 4.9

Linode 自带的 Kernel 4.9 没有编译 BBR 模块进来,直接切换是不行的,得手动替换 Kernel。

虽然 Ubuntu 官网的 Kernel 还没升级到4.9,但是他们官方的 ppa 已经支持4.9了,可以手动下载一个回来安装。

http://kernel.ubuntu.com/~kernel-ppa/mainline/v4.9/

根据自己的32位或者64位环境,wget 个新的 linux-imag-4.9下来

安装好之后,去 linode 后台,把 [Boot Settings] 中的的 [Kernel] 改成 [GRUB 2] ,这样 Linode 的 vps 重启后,就会使用我们刚刚安装的内核了。

BBR

网上开启 BBR 的方式,都是 echo "net.ipv4.tcp_congestion_control=bbr" >> /etc/sysctl.conf  这种暴力又不美观的方式,我是不喜欢。

仔细看了下,发现一个 /etc/sysctl.d/ 的目录,里面还有一个 README

这是一个带权重的配置目录,新的 php-fpm 目录下面也是通过这种方式来维护模块配置的。

这里面补一个 60-bbr.conf ,把开启 BBR 的规则放在这个 60-bbr.conf 里面就 ok 了。

根据 README ,执行 service procps start 就能看到效果。

More

在 /etc/sysctl.d/目录下还无意发现一个 10-network-security.conf,里面默认开启了 tcp_syncookies 来防范常见的 SYN-flood 类型的 DDoS。

JavaScript Cookbook 2nd 之 Function

昨晚翻了一下,虽然都是一些旧知识,不过深入下去对照着其他资料一起看,还是能发现一些有意思的地方。

函数式编程

反正之前我是没搞懂函数式和命令式的区别,也很疑惑函数式编程中,如果出现分支怎么办,昨晚总算弄明白了。

传统的命令式编程,我们会这样写业务逻辑

而函数式编程,我们则可以这样写业务逻辑。

这里可能会有一个疑惑,互斥的 processA 和 processB 怎么进入了同一个处理流程,这样和需求就不符合了?

在这种情况下,我们还需要在 processA 和 processB 的内部,把退出条件补上。

调用栈

JS 在执行的时候,有一个函数调用栈,栈里面放着一个个的函数调用帧,这些帧保存着所属函数所需的所有变量信息。

函数调用会在内存形成一个“调用记录”,又称“调用帧”(call frame),保存调用位置和内部变量等信息。如果在函数A的内部调用函数B,那么在A的调用帧上方,还会形成一个B的调用帧。等到B运行结束,将结果返回到A,B的调用帧才会消失。如果函数B内部还调用函数C,那就还有一个C的调用帧,以此类推。所有的调用帧,就形成一个“调用栈”(call stack)。

浏览器拦截 window.open

我们发现有时候执行 window.open(),能正常打开新窗口或者新的标签页,而有时却又不行,会被浏览器拦截。

其原因是浏览器会根据当前调用栈,找到最初的caller,如果不是用户触发的,则拦截。

尾调用优化

由于函数调用的时候会生成新的调用帧,当递归调用的时候,调用栈中的调用帧增长会非常厉害,最终导致内存耗尽而触发 RangeError。

如果把函数调用放在函数块的最后一条语句,且不在使用外层函数的变量了,则外层函数所占用的调用帧已无存在意义。在进入内层函数的时候,可以直接用内层函数的调用帧替换掉外层函数的调用帧,从而大大减少内存占用。

其他

Partial Application 的模式,用来做 Library 和 SDK 都挺好的。一个可定制性高的底层接口,再通过类似 _.partial() 的方式,提供一个开箱即用的上层接口。就像 $.ajax()$.get() 一样。

_.defer()_.memoize() 可以用在有大运算量的业务场景。