Velocity 2016 参会笔记

大规模系统平衡性能最佳实践和弹性工程

  • Fire TV 的遥控器导航用例很有意思。但是太复杂了,但是值得思考。改变遥控器的输入方式,好像可以更简单的解决问题。
  • 有能跑 JavaScript 的电视棒了,是个好消息。
  • 并没有开源。但是有个 starter 项目仓库。
  • 讲师的中英文交叉的很6。

Web 组件介绍

  • Web is always fragmented。
  • ES6 class extends HTMLElement + Shadow DOM 实现 Web Component 很有意思。
  • attributeChangeCallback 可以实现自定义属性改变时的行为。
  • Template tag 可以用于代替 JSX。
  • https://webcomponents.org 有 polyfill 支持
  • 明天会讲 Polymer at YouTube。

零点之战——阿里双11技术架构演进之路

  • 很虚,PPT 做的很烂,字太多,图里面的字很小,画的也很难看。一直强调我们业务很复杂、用户量很大、流量很大,数据库怎么做一致性都没提。
  • 保障服务集群在区域内可以独立完成。(只有买家的业务会在自己的区域内独立完成。)
  • 服务集群可以快速复制。
  • 流量链条压测工具。
  • 阿里云降低双十一机器成本。
  • 处理不了的流量主动拒绝服务。
  • 动态设置任务分配不均的实例的权重。
  • 不要再线上使用没有测试过的预案。

OceanBase:蚂蚁双十一背后的关系数据库

  • 4 年落地,双十一全部使用 OceanBase。
  • 满足强一致性。
  • 分布式投票,Paxos协议强同步。但机房至少一个 raplication 做 backup。
  • 模拟断电断网。
  • 数据迁移,灰度引流,留好后路,随时回滚。
  • 查了一下,目前还没有开源。
  • 完全兼容 MySQL,优化器向 Oracle 学习。
  • 支持单个实例 10w 连接数。
  • 查询过程打断点,执行过程中可以取消执行,防止大查询占用过度资源。

滴滴弹性在线存储平台

  • 听起来很不靠谱,没有亮点,不知道为什么会做这个东西。一上来就是 redis、hbase、mongodb 都满足不了我们的需求,所以我们要自研。另一个造轮子的理由是,我们无法修改上游的代码。而且存储引擎是基于 RocksDB 和 Redis 的。尤其是数据同步的方案,竟然用 PPTP 的时间戳作为参考,呵呵哒。
  • 核心在于实现了一个可以伸缩的 Slot/Region 的路由表。

58 到家微服务架构实践

  • 比较水,其他厅也没有有价值的主题了,随便听一下吧。

DT时代的业务实时监控之道

  • 和我们的业务比较像。
  • 计算任务可以在前端编排。
  • 存储用的 hbase,用法很想 OpenTSDB。
  • 通过接口支持自定义查询。
  • 支持自定义可视化。
  • 只支持 5 个维度,但是对于维度的值不限制数量,但是遇到维度爆炸的情况会截断展开的量。
  • 没有考虑维度爆炸造成的存储资源浪费的问题。
  • Hbase 热点的问题,通过把 维度 放在 timestamp 之前解决。
  • 整体来看,整个的产品完成度非常高,我们需要一段时间才能追赶上。
  • 思考:需要做排名的指标,用我之前提出的记录用户访问习惯的思路可能会稍微做一下修改。

主题发言 阿里应用运维体系演变

  • 脚本化 => 工具化 => DevOPS => 自动化
  • 工具化
    • 工具/运维团队 => 合并各工具团队 => 工具团队同时兼 OPS 工作。
    • 工具化遇到的问题:
      • 推进落地难,思想转变,时间保障。
      • 有工具,但是工具的质量不高,运维的幸福感并没有明显提升。
      • 标准化,和国外差距比较大,刚开始做不好,就要化很多年时间去填补。
  • DevOPS
    • 将运维团队交还给研发团队。之前将运维的组织架构统一到一起,现在拆开。
    • 借助 Docker。
  • 自动化
    • 有工具但是需要人点,需要巨大的碎片化时间投入。
    • 无人值守比较难做。以发布过程为例,没有一个判断发布成功的统一条件。
  • 智能化
    • 还没开始做。
    • 说要通过机器学习判断特征,这个比较扯,比如 1000 个应用,999 个被下线了,机器学习是不是就自动下线最后一个应用呢?要是最后一个应用不能被下线怎么办?

主题发言 测量服务的可运维性 (Measure the operability of your service)

  • 讲师的开场介绍好像是在被面试一样。
  • 讲师的表达不太好。

Swarm优化:从单实例管理 1000 nodes 到 30000 nodes

  • 选择 Swarm 而不是其他方案的原因是因为对 docker 本身的协议更熟悉,方便深度定制。
  • 提到阿里采用的大部分开源软件都是经过深度定制的。
  • 问题:是否支持 airplane 或 busybox 之类的镜像。
  • 遇到的性能问题:
    • 节点多,线程太多,Swarm 进程直接崩溃。
    • TCP 连接泄露。
  • 实例太多,需要引入更多管理系统。
  • (对 golang 语言相关的不感兴趣,跳过这部分细节了。)
  • Swarm 线程最大线程数默认被限制在 50k。
    • 官方的解释是网络 IO 会创建很多线程。
    • 真正的原因是使用 conn.File() 将连接设置为了 block 模式。
      问题:查了一下已经被阿里的人提交 PR 并 merge 了。我的问题是,setTCPUserTimeout() 是 keepalive 还是应用层的心跳?删掉之后会有什么影响?如果是中间使用了代理会怎么样呢?
      回答:删掉之后会造成没有应用层心跳机制,要两个小时才能检测到另一端死掉了。Swarm 的协议并没有提供可以穿透代理的端到端的心跳机制。阿里内部的跨级房 Swarm 通信中间没有网管或代理,所以不会遇到 TCP 的心跳不能穿透代理的问题。
      
  • CPU消耗特别大,有很多低效率的算法造成的问题。
  • 问题:查了一下已经被阿里的人提交 PR 并 merge 了。我的问题是,setTCPUserTimeout() 是 keepalive 还是应用层的心跳?删掉之后会有什么影响?如果是中间使用了代理会怎么样呢?
    回答:删掉之后会造成没有应用层心跳机制,要两个小时才能检测到另一端死掉了。Swarm 的协议并没有提供可以穿透代理的端到端的心跳机制。阿里内部的跨级房 Swarm 通信中间没有网管或代理,所以不会遇到 TCP 的心跳不能穿透代理的问题。
    
  • 问题:阿里内部的 docker 容器可以支持所有的镜像吗?比如 alpine。
    回答并不可以,只能使用我们自己维护的镜像(有点儿像我们的 cargo)。而且只有 readhat 系的,想用 ubuntu 是不可能的(还不如 cargo)。社区的应用基本上没法用。
    

OWL 分布式开源监控最佳实践

  • 完全是来广告的。和我们的量级和功能相比,太小了,没什么有价值的信息。

网易蜂巢基于 kubernetes 的公有云运维实践

  • 作者的背景很靠谱,有很多容器化、虚拟化方案的经验。
  • 计算/网络/存储虚拟化
  • 从虚拟机到容器
    • 以资源为核心 => 以应用为核心
    • 有状态的容器:保留内存和硬盘可写能力
    • 容器跨主机互联
    • 云盘
  • 一板斧:去状态、可扩展(都已经喊得烂了,不记了)
  • 二板斧:容器化、可编排
    • Kuber 系技术栈实现快速编排无状态的应用,相当于分布式的 docker-compose,
  • 三板斧:DevOPS、可迭代
    • 主要讲的多个环境自动选择配置选项。基本上靠 CI、CD、kuber 系的 compose 功能实现。
  • 私有云到公有云
    • 容器的安全
      不同租户之间不共享虚拟机。
      
    • 容器的启动速度
      第一次启动比较慢,后面会比较快。
      
    • 容器的规模
      扩展到了 15k 个 kuber 节点。
      
    • 容器的租户隔离
      不同租户之间不共享虚拟机。
      
  • 编排优化
    • 支持多用户
    • 调度性能用户,默认只支持单个串行队列
    • 提高集群扩展性,通过拆分 etcd 集群实现
  • 虚拟机启动优化:提前分配 IP
  • 提供内部镜像仓库,提升下载镜像的速度
  • 整个蜂巢全部采用没有修改过的开源软件(这一点非常赞!好多公司都做不到啊!)

阿里巴巴 Aliware 十年微服务架构演进历程中的挑战与实践

  • 上百个人维护一个核心工程
    • 源代码冲突严重
    • 协同成本极高
  • 问题:工程化的方案,为什么没有采用 google、facebook 整个公司的代码放在一个仓库里的做法?放在一个仓库里还可以共享很多已经实现的逻辑看起来效率更高。
  • 错误难以隔离
  • 讲师:能写 if 就不会去重构了(谁要敢这么说,以后估计都不会找到工作了)。
  • 数据库能力达到上限
    • 连接数捉襟见肘
    • 单机 IOPS 达到瓶颈
  • 数据库容量日趋饱和
  • 长期高负荷运转,接近崩溃边缘
  • 数据孤岛
    • 数据隔离
    • 不一致
    • 无法复用
  • 无法进行全局数据分析
  • 中间件 / 基础服务
    • RPC
    • 消息队列
    • 配置中心
    • 监控(一直在讲 Java 系相关的内容,没兴趣)
    • 调用链路分析
    • 容量规划
      • 制造流量 => 压测单机性能 => 设定运行水位 => 计算机器使用量 => 机器上下线
      • 每天自动运行压测 => 部署前进行压测。
      • 亮点:
        - 使用线上流量进行压测,和 tcpcopy / goreplay 类似。
        - 根据运行水位自动伸缩,方案的完成度比较高。
        
      • 思考:性能平台达到可以扩张的时候,可以提供在线压测平台。
    • 限流降级

浅尝 Windows Subsystem for Linux

安装/卸载/重置

准备

  • 准备一台安装了 Windows 10 的 PC,并将系统升级到最新版本。
  • (可选,但是建议)安装 Fast Ring 的 Insider Preview 版本更新。
  • Update & security配置中,开启Developer mode
  • Turn Windows features on or off中,开启Windows Subsystem for Linux (Beta)
  • 运行bash命令。

其他操作

powershell 或者 cmd 中执行下面的命令,可以查看 安装/卸载/重置 WSL 相关的参数。

1
lxrun /?

目录结构

WSL 路径 Windows 路径
/ %localappdata%\Lxss\rootfs
/mnt/c C:\

新建文件默认权限 - umask

目前 WSL 默认的新建文件默认权限存在问题。例如运行下面的命令:

1
touch test && ll test

输出结果:

1
-rw-rw-rw- 1 crzidea crzidea 0 Nov 30 11:45 test

可以看到 group 和 other 用户都默认拥有了写权限。这是因为 umask 没有被正确设置:

1
umask # 输出结果为 0000

要解决这个问题,需要在 bashrc 中手动加入下面一行命令:

1
umask 022

重新打开终端后,重新执行上面的测试:

1
rm test && touch test && ll test

可以看到文件权限可以被正确设置了:

1
-rw-r--r-- 1 crzidea crzidea 0 Nov 30 11:45 test

另外,通过 /mnt/ 路径访问文件时,文件权限大部分为 rwx

1
ll /mnt/c

输出结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
drwxrwxrwx 0 root root 0 Nov 30 11:29 cygwin64
-????????? ? ? ? ? ? hiberfil.sys
drwxrwxrwx 0 root root 0 Nov 19 13:53 Intel
drwxrwxrwx 0 root root 0 Nov 30 12:40 OneDriveTemp
-????????? ? ? ? ? ? pagefile.sys
d--------- 0 root root 0 Nov 18 21:27 PerfLogs
drwxrwxrwx 0 root root 0 Nov 19 14:33 ProgramData
dr-xr-xr-x 0 root root 0 Nov 19 13:53 Program Files
dr-xr-xr-x 0 root root 0 Nov 19 13:53 Program Files (x86)
dr-xr-xr-x 0 root root 0 Nov 19 13:55 Recovery
drwxrwxrwx 0 root root 0 Nov 19 14:47 $RECYCLE.BIN
-????????? ? ? ? ? ? swapfile.sys
drwxrwxrwx 0 root root 0 Nov 18 21:33 $SysReset
d--------- 0 root root 0 Nov 30 10:39 System Volume Information
dr-xr-xr-x 0 root root 0 Nov 19 14:48 Users
dr-xr-xr-x 0 root root 0 Nov 30 10:45 Windows
dr-xr-xr-x 0 root root 0 Nov 30 12:33 $WINDOWS.~BT
drwxrwxrwx 0 root root 0 Nov 19 19:47 Windows.old

tmux 相关

目前 WSL 可以支持绝大多数的 tmux 操作,这也是 WSL 发布时拿出来秀肌肉的亮点。但是测试中发现使用快捷键(默认prefix + ")对 tmux 分屏时,无法保留当前的路径。在终端中运行tmux split不会有这个问题。

文件系统事件 - inotify

1
sudo apt install -y inotify-tools

WSL 的早期版本无法使用 inotify 工具,也无法获取文件变动的事件。但是目前 Insider Preview 版本已经实现了功能,这些这也是建议用户升级到 Insider Preview Fast Ring 的原因之一。

另外由于 WSL 文件系统的特殊性,需要注意:

  • 在 bash 环境下修改 /mnt/c 中的文件,也可使用这些事件。
  • 在 Windows 环境中修改文件,不会触发事件。

使用 bash 启动 Windows 应用程序

1
2
export PATH=$PATH:/mnt/c/Windows/System32
notepad.exe

注意:被调用的 Windows 应用程序可能无法正确识别传入的路径参数,例如:

1
explorer.exe / # 无法正确识别 `/` 路径

使用 Windows 应用程序监控 WSL 进程

通过 WSL 启动的进程可以在 Windows 中访问到。例如在powershell中执行:

1
get-process

可以从输出列表中查找到从 WSL 启动的 bash、ssh、node 之类的进程。

中文字符

默认的 bash 命令行程序可能会无法正确显示中文,建议使用 mintty/wsltty 作为日常使用的终端应用程序。

个性化终端

bash.exe

bash.exe 实际上是一个Windows Console Application,终端的样式(颜色、字体、光标)可以通过注册表修改,但是可以修改的项目有限。

mintty

如果觉得默认的 bash.exe 终端太挫了,可以用 mintty + wslbridge 的组合代替。

安装

mintty 作者建了一个 wsltty 的项目专门用于发布用于 WSL 的终端。但是亲测后发现 Windows 升级之后,之前发布的编译好的终端已经无法运行了。所以需要自己动手将下面的几个玩具组合起来。

将所有文件都解压到C:\cygwin64\bin中,运行install.bat即可。

配置文件

下载已经配置好的 Monokai 样式文件放入下面的目录,重新打开 mintty 即可:

1
%localappdata%\wsltty\home\%username%\

日常开发

这篇博客其实就是在 WSL 的 vim 中编辑的,并且使用 node 创建了一个 web 服务器。所以使用 WSL 应对日常的开发,应该是没有问题的。

参考资料

RestQL:现代化的 API 开发方式

本文已在美团点评技术博客发表,谢绝转载!

koa-restql 已经在 github 开源并在 npm 发布。感兴趣的同学可以前往围观一下。欢迎 Pull Request,同时热烈欢迎 Star。

在现代的业务系统中,后端开发工作基本上可以被拆分为三项:

  • 接口鉴权。例如拍段是不是当前系统的用户,以及该用户是否有权限访问接口。
  • 与其他系统的交互。例如调用第三方的服务,或内部搭建的其他服务。
  • 数据操作。基本上所有需要持久化存储的系统都会在这项工作上耗费大量时间。

本文将介绍如何利用 RestQL 来非常有效地减少「数据操作」相关的工作量。

现状与挑战

我们先来做个假设。

  • 假设系统中有 60 张表,每张表对应的接口都要有四种 CRUD 的 API。那么就需要后端工程师写60 * 4 = 240个API。
  • 假设上述 60 张表中,40 张表存的是资源类的数据,其余 20 张表为关系类的数据,也就是说每张表和 20 张表都要进行关联,每个关联也需要四种 CRUD 操作,那么又要增加40 * 20 * 4 = 3200个API。

所以在上述假设场景中,后端工程师要编写 3200 + 240 = 3440 个 API。而且这还不是全部。假如后端代码需要 100% 的测试覆盖,那么工程师们就要写至少 3440 个测试!

60 张表 = 3440 个 API + 3440 个单元测试

众所周知,数据操作 API 的实现过程基本上是重复的,有的同学甚至认为这是低端的,体现不出工程师价值的工作,纯粹的「体力活」。但是却没有一个能真正解放生产力的方案。

解决思路

尽管我们把数据库抽象成了「关系型」数据库,把操作数据的命令抽象成了 SQL ,同时我们也有了 MySQL 客户端,甚至是 sequelize 这种非常方便的库,也有「RESTful」API 命名规则,但是接口的实现从来都是需要工程师们自己用手敲出来的。

如果说我看得比别人远,那是因为我站在巨人的肩膀上。

所以我们在现有的技术基础上再抽象,把已有的东西重新组合起来,拼装成一个新的工具,帮助工程师从「体力活」中解脱出来,解放生产力。

什么样的工具

最开始的时候,我们最先需要明确的问题就是:「我们需要什么样的工具?」或者说「这种工具要帮我们解决什么问题?」。

实际上我们从刚才的假设中,已经可以得出结论:我们希望有一个工具可以让工程师免于编写数据操作 API,把数据库操作直接映射到 HTTP RESTful API 上

调用方式

如何请求

为了解释「如何请求」,我们先从一些公认的规则出发,举一个例子,然后再从例子中抽象出一些规则。

注意:为了更便于理解,我们把所有的命名从客户端一直穿透到数据库,所以请不要纠结于我们在定义一个 API 时名词单复数的问题。

基本用例

几乎所有的系统都会有一个用户表(user)。根据 RESTful 规则的约定,我们应该把访问 user 表的 API 路径定义为 /user,并把 CRUD 的访问方法映射到 HTTP 协议中的四种方法:GETPOSTPUTDELETE

比如:

  • GET /user:获取用户列表,应该返回一个数组。
  • GET /user/:id:获取指定的用户,应该返回一个对象。
  • POST /user:创建一个用户,应该返回被存储的对象,状态码应该为 201(Created)。
  • PUT /user:修改一个用户的信息,应该返回修改后的对象。
  • DELETE /user/:id:删除一个用户,状态码应该为 204(No Content)。

如果 user 表有一个关系表 feed,那么我们的路径就会再复杂一点:

  • GET /user/:id/feedGET /feed?user_id=:id:获取某个用户的帖子,应该返回一个数组。
  • GET /user/:id/feed/:feed_idGET /feed/:id:获取指定的帖子,应该返回一个对象。

上述的例子中还会衍生出其他的数据操作,不仅仅只有 GET,这里不一一列举了。

抽象出规则

上一节中,列举了要提供一个表的数据访问 API,大概要实现哪些路由。从这些枚举中,可以找出其中的规律,总结出一套规则。最终我们在「把能实现的路由,全部实现」的原则基础上,开发了 RestQL 的 koa 版本。

支持的 HTTP 方法:

HTTP verb CRUD
GET Read
POST Create
PUT Create/Update
DELETE Delete

支持的带有 body 的 HTTP 方法:

HTTP verb List Single
POST Array/Object ×
PUT Array/Object Object

说明

  • List 路径为返回值为数组的路径,包括:
    • /resource
    • /resource/:id/association, association 为 1:n 关系
    • /resource/:id/association, association 为 n:m 关系
  • Single 路径为返回值为单个对象的路径,包括:
    • /resource/:id
    • /resource/:id/association, association 为 1:1 关系
    • /resource/:id/association/:id, association 为 1:n 关系
    • /resource/:id/association/:id, association 为 n:m 关系

如何使用

我们已经开源了 koa-restql,koa 应用开发者可以通过 npm 安装它:

1
npm install koa-restql

然后在 koa 应用的代码中引用 RestQL:

1
2
3
4
5
6
7
const koa = require('koa')
const RestQL = require('koa-restql')
let app = koa()
// Build APIs from `sequelize.models`
let restql = new RestQL(sequelize.models)
app.use(restql.routes())

常见问题

修改参数

用户可以通过querystring来修改参数。强烈建议使用qs对 querystring 进行解析,例如:

1
qs.stringify({a: 1, b:2}) // => a=1&b=2

RestQL 中的querystring仅有 3 条规则:

  • 所有不以_开头的建,都会被放进sequelize#query()where参数中。例如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // query
    {
    name: "Li Xin"
    }
    // option for sequelize
    {
    where: {
    name: "Li Xin"
    }
    }
  • 所有以_开头的建,都会被放进sequelize#query()的参数中,和where保持平级。例如:

    1
    2
    3
    4
    5
    6
    7
    8
    // query
    {
    _limit: 10
    }
    // option for sequelize
    {
    limit: 10
    }
  • 当需要使用关系时,可以用关系名称的字符串代替关系对象传入。例如需要使用include时:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // query
    {
    _include: ['friends']
    }
    // option for sequelize
    {
    include: [
    models.user.association.friends
    ]
    }

访问控制

通常来说,我们有两种方法实现访问控制:

通过中间件

在 koa 应用挂载 RestQL 的 router 之前,可以实现一个鉴权中间件,控制用户的访问权限:

1
2
app.use(authorizeMiddleware)
app.use(restql.routes())

Authorize Middleware

通过 restql 参数

在使用sequelize定义关联时,我们可以设定restql参数,实现访问控制。例如:

  • 禁止通过restql访问关联:

    1
    2
    3
    4
    5
    6
    7
    8
    models.user.hasOne(
    models.privacy,
    {
    restql: {
    ignore: true
    }
    }
    )
  • 禁止通过restql使用指定的 HTTP 方法访问关联

    1
    2
    3
    4
    5
    6
    7
    8
    models.user.hasOne(
    models.privacy,
    {
    restql: {
    ignore: ['get']
    }
    }
    )

其他语言/框架

目前我们仅实现了基于nodekoa的版本,还没有其他语言/框架的实现版本。欢迎开发者提交其他语言/框架的实现到 RestQL 组

参考链接

从数据结构出发对比关系型数据库和文档型数据库

现在文档性数据库火的不行,动不动就听到谁家用了 MongoDB。但是当问及他们为什么选择 MongoDB 时,多数人都是一脸懵逼的表情。甚至有人给出下面一些荒唐的理由:

  • 文档型数据库特别火,所以我们用它。
    这就不用解释了。
  • 文档性数据库不需要定义表结构,开发方便。
    在一些高度工程化的项目中,即使采用文档性数据库,也是要定义文档结构的。另外,关系型数据库也有可以自动修改表结构的。
  • 文档性数据库就是 JSON,对写 JS 的同学更友好。
    「文档」不一定就是 JSON。常见的文档结构还有 XML、protobuf 等。所以文档型数据库不见得一定会对前端同学更友好。另外,一些关系型数据库也提供 JSON 格式的输出。

数据结构

关系型

关系型数据库最典型的数据结构是。通常长下面这个样子:

Table A

id content
1 test a
2 test b

Table B:

id table_a_id content
1 1 test c
2 1 test d
3 2 test e
4 2 test f

文档型

文档性数据库最典型的数据结构,当然是文档。以 JSON 为例,通常长下面这个样子:

Collection A:

1
2
3
4
5
6
7
8
9
10
[
{
"id": 1,
"content": "test a"
},
{
"id": 2,
"content": "test b"
}
]

Collection B:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[
{
"id": 1,
"collection_a_id": 1,
"content": "test c"
},
{
"id": 2,
"collection_a_id": 1,
"content": "test d"
},
{
"id": 3,
"collection_a_id": 2,
"content": "test e"
},
{
"id": 4,
"collection_a_id": 2,
"content": "test f"
}
]

使用场景

文档性

经常进行连接查询

我们要对上面的 A 和 B 进行连接查询,关系型数据库返回的是两个表的笛卡尔积:

id content table_b.id table_b.table_a_id table_b.content
1 test a 1 1 test c
1 test a 2 1 test d
2 test b 3 2 test e
2 test b 4 2 test f

上表中的第 2 行和第4 行都是冗余的。这种现象在复杂的连接查询中会被放大的很可怕。我们有的业务在一次查询中连接了 12 张表,冗余的数据可不止一个A.content字段这么简单。

这种情况下再来看看文档性数据库的返回结果,就非常合理了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
[
{
"left": {
"id": 1,
"content": "test a"
},
"right": [
{
"id": 1,
"collection_a_id": 1,
"content": "test c"
},
{
"id": 2,
"collection_a_id": 1,
"content": "test d"
}
]
},
{
"left": {
"id": 2,
"content": "test b"
},
"right": [
{
"id": 3,
"collection_a_id": 2,
"content": "test e"
},
{
"id": 4,
"collection_a_id": 2,
"content": "test f"
}
]
}
]

也有冗余,但是冗余的是字段的 key,完全是可控的。假如使用 protobuf 提前定义好结构,这种冗余甚至会被消除。

同一个表不同记录的数据结构经常不一样

假如你的表中存在很多互斥的字段,文档型数据库可能更适合你的系统。例如你又这样一张表:

id x y z
1 1 null null
2 null 2 null
3 null null 3
4 null null null

商标中xyz字段在每条记录张最多出现一次,然而null依然需要在存储或传输的时候占位。

这种时候,相比之下采用文档性数据库会更合理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[
{
"id": 1,
"x": 1
},
{
"id": 2,
"x": 1
},
{
"id": 3,
"x": 1
},
{
"id": 4
}
]

关系型

除上述几种情况外,关系型数据库往往会更适合你的系统。

在 HTTPS 站点使用 jiathis

我的反向代理服务器 IP 被屏蔽了,大家都洗洗睡吧,太恶心了,不提供 HTTPS 也不让别人提供。
我已经换成了多说的分享插件。


三步搞定:

  1. 需要一个 https 的反向代理,懒得建的话,可以直接用我的:
    //v3-jiathis-com-proxy.crzidea.com/code/jia.js
  2. 在全局配置变量中,设置do_not_track: true。可以干掉发往 id.jiathis.com 域的请求。

    1
    2
    3
    4
    5
    6
    var jiathis_config={
    summary:"",
    shortUrl:false,
    hideMore:false,
    do_not_track: true // 干掉发往 id.jiathis.com 域的请求
    }
  3. 禁用分享计数器,把类名包含jiathis_counter_style的 HTML 元素删掉即可。可以干掉发往 i.jiathis.com 域的请求。

理解 Linux 中的用户组

注意:为了避免实际操作过程中 SELinux 可能会造成的影响,建议先关闭 SELinux:

1
setenforce Permissive

基本概念

用户

创建用户:

1
useradd LOGIN

  • 查看用户所属的组:

    1
    groups LOGIN
  • 为用户设置默认组:

    1
    usermod -g GROUP1 LOGIN

    注意:执行该命令会讲指定用户$HOME目录中的文件权限重新设置。

  • 将用户添加到组:

    1
    usermod -aG GROUP2 LOGIN

    注意:将用户添加到组并不会更改用户的默认组。例如用户 A 同时属于 GROUP1、GROUP2,但是 primary group 是 GROUP1,另一个用户 B 使用 GROUP2 创建了一个文件并设置了访问权限为rw-rw----,默认情况下,用户 A 是无法访问这个文件的。

  • 使用指定的组进行操作:

    1
    sg GROUP2 [COMMAND]

    小贴士:不加COMMAND参数会默认运行一个 bash 进程。

实例

讲用户添加到一个有 sudo 权限的组中

1
2
3
4
5
# 一般系统中都会有一个名为 wheel 的组
sudo usermod -g wheel $USER
# 假如你不想在每次使用 sudo 时输入密码,就在下面的文件中添加一行
# %wheel ALL=(ALL) NOPASSWD: ALL
sudo visudo

不使用 sudo 访问 docker

1
2
3
4
5
sudo usermod -aG docker $USER
sg docker 'docker ps'
# 假如你觉得上面这个命令太长,可以不输入`COMMAND`参数
sg docker
# 现在打开了一个新的 bash 进程,你的所有操作默认会使用`docker`组的权限进行

为什么我要唱衰 React

其实对于 React 的态度,我一直是比较明确的。但是最近有不少人跟我聊这个事情,我想还是有些点需要总结的。

我们为什么需要 React?

这是一个来自知乎的问题。

实际上,大部分人在用 React 的时候,用到的是两个特性:

  1. 虚拟 DOM
  2. 组件化

至于其他的伪特性,我认为是有些同学「一瓶子不满,半瓶子咣当」,意淫出来的。这些伪特性包括:

  1. 跨平台。虽然 ReactNative 可以在多个平台上使用,但是 ReactNative 并没有封装不同系统的 API。从这方面来说,这货甚至连 weex 都不如。
  2. 更易于组织逻辑。这明显是 flux/redux 做的事情。而且 redux 已经明确说明了,不仅仅适用于 React,其他框架也可以用 redux。

我们真的需要上述的两个特性吗?

虚拟 DOM

虚拟 DOM 解决了频繁操作 DOM
产生的性能问题。那么下面几项事实必定会导致这一特性「没有前途」:

  1. 设备的硬件性能会越来越好,性能在将来不再是问题。
  2. 假如说我们要解决性能问题,相比虚拟 DOM,下面几个解决方案会更好:
    1. 浏览器实现虚拟 DOM。而且这也是虚拟 DOM 被应用的正确场景和姿势。
    2. 再操作数据的地方做 diff,而不是在虚拟 DOM 的基础上做 diff。这是才是
      cache/diff 的正确用法。

所以我认为虚拟 DOM 必定是「没有前途」的。

组件化

组件化有一个很重要的目的是为了提高开发效率。再来看一下使用 React 开发效率高吗?

民间:想加班就用 React

为了说明 React 的开发效率,不妨点开两个链接看一下代码行数。下面两个链接都实现了一个聊天应用,完全一样的功能:

你还需要啥理由丢弃 React?

感谢 React

虽然我并不认同 React 的一些解决问题的方法,甚至觉得它「没有前途」,但是 React 对行业做出的贡献是众所周知的。最后还是感谢 facebook 工程师的贡献。

使用 docker 测试 rethinkdb 的高可用和可扩展特性

rethinkdb 在高可用和扩展性方面做得出奇的好,而且非常好理解。这篇博客讲的只是如何使用 docker 对 rethinkdb 的这些特性做一些简单测试。

准备

首先确保有一台可以运行 docker 的机器。然后使用 docker 创建一个 rethinkdb 容器。

  1. 获取 rethinkdb 镜像:

    1
    docker pull rethinkdb
  2. 创建 rethinkdb 容器(为了便于理解,所有命令行参数均使用全写):

    1
    docker run --publish-all --detach rethinkdb
  3. 找出容器内 8080 端口映射到外部的端口(下面的例子中,被映射到外部的端口是 32780):

    1
    docker ps

    应该会得到类似下面的输出(此处已经把所有干扰列删除了):

    1
    2
    CONTAINER ID IMAGE PORTS
    bce34ef8fc56 rethinkdb 0.0.0.0:32780->8080/tcp, 0.0.0.0:32779->28015/tcp, 0.0.0.0:32778->29015/tcp
  4. 在你的本地浏览器中访问 rethinkdb 的 WebUI(此处的例子中,WebUI 的地址为 http://server:32780 )。

扩展性

准确的说,我们会测试它的伸缩能力,即「增加节点」和「摘除节点」。

为了完整演示整个测试过程,需要再创建一个可以交互的容器,并把将创建的两个容器设置到同一个虚拟网络中:

1
docker run --publish-all --link bce34ef8fc56 --interactive --tty rethinkdb bash

增加节点

成功创建容器之后,我们会获得了一个来自容器的可以交互的终端。要增加节点,只需要在新容器内运行下面的命令:

1
rethinkdb --join bce34ef8fc56

再次感叹一下,扩展一个数据库服务的命令行就应该是这个样子,感谢 rethinkdb,把被各种小聪明玷污的工具逻辑还原出来了本来的面容。

平衡节点上的表

终于轮到 rethinkdb 的 WebUI 登场了。

  1. 访问上文中创建好的 WebUI 服务(http://server:32780)
  2. 打开「Data Explorer」标签页,分别给两个节点设置标签:

    1
    2
    3
    var config = r.db('rethinkdb').table('server_config');
    config.filter({name: 'bce34ef8fc56_iz6'}).update({tags: ['default', 'node_a']});
    config.filter({name: 'd72b193f46d5_jp6'}).update({tags: ['default', 'node_b']});

    提示:要查询server_config表中的内容,可以使用config.filter({})
    注意:必须至少有一个tags包含default的节点,新创建的表会被分配到有default标签的节点上。

  3. 创建两个表:

    1
    2
    3
    var test = r.db('test');
    test.tableCreate('table_a');
    test.tableCreate('table_b');
  4. 查询表在各节点的分布情况:

    1
    r.db('rethinkdb').table('table_status')

    输出结果(已去除干扰信息):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    [{
    "name": "table_b",
    "shards": [{
    "primary_replicas": ["bce34ef8fc56_iz6"]
    }]
    }, {
    "name": "table_a",
    "shards": [{
    "primary_replicas": ["d72b193f46d5_jp6"]
    }]
    }]

    可以看到新创建的表已经被自动分布到了两个节点上。

  5. 测试分布在不同节点上的表的连接查询

    1. table_a插入测试数据:

      1
      r.db('test').table('table_a').insert({key_a: 1});

      假设返回的新创建的记录id7415e20c-6fab-4813-85f0-f169c70699c9

    2. table_b插入测试数据:

      1
      2
      3
      4
      r.db('test').table('table_b').insert({
      key_b: 2,
      table_a_id: '7415e20c-6fab-4813-85f0-f169c70699c9'
      });
    3. 测试连接查询:

      1
      2
      3
      4
      5
      r.db('test').table('table_b')
      .eqJoin(
      'table_a_id',
      r.db('test').table('table_a')
      )

      输出结果:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      [{
      "left": {
      "id": "2b9bebc7-bb98-45b8-9304-8a19479f4bcb",
      "key_b": 2,
      "table_a_id": "7415e20c-6fab-4813-85f0-f169c70699c9"
      },
      "right": {
      "id": "7415e20c-6fab-4813-85f0-f169c70699c9",
      "key_a": 1
      }
      }]

      由此可以得出结论,即使被查询的表分布在不同节点上,仍然可以使用连接查询。

强制摘除节点

要模拟「强制摘除节点」,只需要在容器的终端上按下Ctrl + C

摘除节点之后,WebUI 会提示下面的内容:

Table test.table_a is not available.

None of the replicas for this table are reachable (the servers for these
replicas may be down or disconnected).
No operations can be performed on this table until at least one replica is
reachable.

当然,这个时候执行上面的连接查询,也是会出错的:

e: Cannot perform read: primary replica for shard [“”, +inf) not available in:
r.db(“test”).table(“table_b”).eqJoin(“table_a_id”, r.db(“test”).table(“table_a”))

所以我们还是先恢复这个节点吧:

1
rethinkdb --join bce34ef8fc56

高可用

安全摘除节点

  1. 将所有的表都调度到 node_a 上:

    1
    2
    r.db('test').table('table_a').reconfigure({shards: 1, replicas: {node_a: 1}, primary_replica_tag: 'node_a'});
    r.db('test').table('table_b').reconfigure({shards: 1, replicas: {node_a: 1}, primary_replica_tag: 'node_a'});

    注意:切换主节点会造成指定的表短暂的无法访问。

  2. 在容器的终端上再次按下Ctrl + C
  3. 测试连接查询,现在不会出错了

替换主节点

  1. 为了能在外部访问到用于替换主节点的节点上的服务,需要在启动服务的命令行上加一个参数:

    1
    rethinkdb --join bce34ef8fc56 --bind-all
  2. 然后使用在容器的宿主机上执行docker ps来查找容器内 8080
    端口被映射到外部的端口。这里假设是 32786。

  3. 访问 http://server:32786/#dataexplorer ,将所有的表调度到 node_b 上:

    1
    2
    r.db('test').table('table_a').reconfigure({shards: 1, replicas: {node_b: 1}, primary_replica_tag: 'node_b'});
    r.db('test').table('table_b').reconfigure({shards: 1, replicas: {node_b: 1}, primary_replica_tag: 'node_b'});
  4. 在宿主机上停掉之前的主节点:

    1
    docker stop bce34ef8fc56
  5. 在新节点上执行查询,没有任何报警

总结

整个测试过程搞完之后,对 rethinkdb 基本上可以有相当的信心了。没啥好说的,用 rethinkdb 完全不用担心分布式架构的问题了,所有的运维过程完全不需要 DBA 介入。


参考:Scaling, sharding and replication

通过 systemd 设置 docker 仓库镜像

具体步骤(伸手党直接从第4步开始看):

  1. 找到一个可用的镜像服务,参考 Faster Docker pulls in China with DaoCloud
  2. 通过 yum 安装 docker 之后,检查 systemd service:

    1
    systemctl status docker

    从输出中找到对应的 .service 文件:

    1
    2
    3
    4
    5
    ● docker.service - Docker Application Container Engine
    Loaded: loaded (/usr/lib/systemd/system/docker.service; enabled; vendor preset: disabled)
    Active: active (running) since Tue 2016-07-19 15:02:49 CST; 2h 9min ago
    Docs: http://docs.docker.com
    ...
  3. docker.service中找到相应的OPTIONS环境变量:

    1
    vi /usr/lib/systemd/system/docker.service

    找到下面几行(为了更易于理解,这里把所有的干扰项都删掉了):

    1
    2
    3
    4
    EnvironmentFile=-/etc/sysconfig/docker
    ExecStart=/bin/sh -c '/usr/bin/docker-current daemon \
    $OPTIONS \
    2>&1 | /usr/bin/forward-journald -tag docker'
  4. 编辑EnvironmentFile项所指的文件,把参数加到OPTIONS环境变量里

    1
    vi /etc/sysconfig/docker

    编辑下面的内容:

    1
    2
    3
    # Modify these options if you want to change the way the docker daemon runs
    #OPTIONS='--selinux-enabled --log-driver=journald'
    OPTIONS='--selinux-enabled --log-driver=journald --registry-mirror=http://YOUR_ID.m.daocloud.io'

    注意:这个配置文件不支持使用 bash 的模板字符串。

  5. 重新启动 docker 服务:
    1
    sudo systemctl restart docker

你的洗牌算法有可能是错的

本篇实际上是下面两篇博客的读后总结。如果读得懂,就不用往下看了:

总结

为什么使用下面的排序算法洗牌,结果是错的?

  • 冒泡 先后边,后前边
  • 插入 县前边,后后边
  • 快排 先中间,后两边

后生成的随机数和先生成的随机数比较时,满足交换条件的可能性越来越小。

造成了打乱的结果变得有规律:

  • 先被比较随机数的位置随机性比较大
  • 后被比较随机数的位置保持原状的可能性比较大

怎么测试结果是随机的算法?

  • PC 机上是做不出真随机数的,只能做出伪随机数。真随机数需要硬件支持。
  • 一到概率问题,我们只有一个方法来做测试,那就是用统计的方式。
  • 测试的三个参数:样本数、最大误差、平均误差。