标签分享下的文章

Jerry Bendy 发布于 01月17, 2017

【分享】HTTP 状态码 451:基于法律上的原因,我不能向你展示网页内容

几天前 GitHub 一个 repo 由于某个原因而无法从国内访问,当我打开 Chrome DevTools 时,发现了一个新的 HTTP 状态码 —— 451。

我们先看看维基百科对这个状态码的介绍:

在电脑网络领域中,HTTP 451 因法律原因不可用(英语:HTTP 451 Unavailable For Legal Reasons)是一种 HTTP 协议的错误状态代码,当用户请求访问某个经政府审核等查核方法后认定不合法的来源时,就会显示这个错误代码。

451 数字来源于 1953 年由美国作家雷·布莱伯利所著的反乌托邦小说《华氏 451 度》。

故事叙述了一个压制自由的近未来世界,禁止人们阅读、拥有书籍,所谓的消防员的工作不是灭火,而是焚书。文中的主人公,盖·蒙塔格,就是一名负责焚书的消防员。华氏 451 度(摄氏 233 度)是雷·布莱伯利给出的纸张的燃点。

当布莱伯利写小说时正是麦卡锡时代,他很担心美国的审查制度。在 1956 年的电台采访时,布莱伯利说:

我写书的时候正为这个国家担心,担心四年前发生的事情。许多人连自己的影子都害怕;书籍有被焚烧的威胁。当时,很多书都下了架。当然,四年间事情变了很多。有不少回到了非常健康的方向上。

2013 年还在 Google 公司任职的提姆·布雷(Tim Bray)正式提出此代码,2015 年 12 月 18 日,此代码由国际网络工程研究团队通过。

Tim Bray 是一位加拿大软件工程师,也是 Open Text 公司和 Antarctica Systems 的联合创始人,也是 XML 规范的主要作者之一(有“XML 之父”之称)。在 2004 年至 2010 年期间,Bray 担任 Sun 公司 Web 技术主管。此后加入 Google 担任开发者大使(Developer Advocate),专注 Android 和 Identity。

Tim Bray 认为 451 代码有助于互联网自动化搜索业务,不少搜索引擎支持 451 代码,表示 451 代码能够让蜘蛛网络来寻找带有 451 代码的网站,所以他们可以将这些网站归类到正在接受审查的网站类别。

部分互联网大公司也支持 451 代码,如 Github,Twitter,Facebook 和 Google,使用 451 代码,它们可以在某些司法管辖区被迫审查违背其意愿内容的时候,显示 451 代码。

看到这部小说,脑海中第一个想到的就是秦始皇。


分享自 justjavac(迷渡)

阅读全文 »

Jerry Bendy 发布于 11月26, 2016

【分享】几种常见的不停机发布方式

何为不停机发布?

本文所说的不停机发布,是指在不停止对外服务的前提下完成应用的更新。与热部署的区别在于,热部署关注于应用层面并且以不重启应用为前提,而不停机发布则关注于服务层面。随着摩尔定律逐渐逼近极限和多核时代的到来,分布式应用已经成为事实上的主流。下文首先给出一种通用的适用于分布式应用环境的不停机发布方式,然后再介绍Master/Worker这种常见的适用于单机应用的不停机发布方式。

Cluster模式

对于运行于集群环境的分布式应用,一般在应用之上都有一层负载均衡(LB)。如果在发布过程中,在更新任一节点(也可以是一组节点)前先关闭该节点对应的负载,更新完再打开负载,即可实现整体服务的不停机发布。在此基础上,为了保证服务的稳定性,可以加上备机的支持,即更新某一节点时,先挂上备机,更新完再卸下,依次轮换更新完所有节点后最后再升级备机,如下图所示:

hot-deployment1

  • 完整设计可以参考我(原作者)写的另一篇文章

上述发布过程其实就是一个简单的CD(Continuous Deployment)系统。作为一个参考实现,可以使用Jenkins 2.0 Pipeline特性定义整个发布流程,使用Nginx Dynamic Upstream插件操纵Nginx,然后配合脚本完成应用的启停和检测。

hot-deployment2

Master/Worker模式

对于单机应用,由于不存在LB,一般由应用容器实现不停机发布特性,最常见是Master/Worker模式。容器中常驻一个master进程和多个work进程,master进程只负责加载程序和分发请求,由fork出来的worker进程完成具体工作。当容器收到更新应用的信号时,master进程重新加载更新后的程序,然后fork新的worker进程处理新的请求,而老的worker进程在处理完当前请求后就自动销毁。Ruby的Unicorn,PHP的FPM都是采用了这套机制。

延伸阅读

不同于Master/Worker模式,erlang采用了另一种独特的方式实现了不停机发布。

erlang VM为每个模块最多保存2份代码,当前版本’current’和旧版本’old’,当模块第一次被加载时,代码就是’current’版本。如果有新的代码被加载,’current’版本代码就变成了’old’版本,新的代码就成了’current’版本。erlang用两个版本共存的方法来保证任何时候总有一个版本可用,对外服务就不会停止。 —— 引自分析erlang热更新实现机制

小结

不管是LB,还是Master/Worker,其基本思想都是在发布过程中,通过某种机制使得服务请求始终能够被系统的某个节点或者某个进程处理,从而保证了服务的可用性。


分享自:http://emacoo.cn/devops/ci-cd-hot-deployment/

阅读全文 »

Jerry Bendy 发布于 11月24, 2016

【分享】程序员提交代码的 emoji 指南——原来表情文字不能乱用

程序员都爱 github,而许多程序员喜欢在 github 提交代码时加入 emoji 表情。 并不是程序员喜欢故意卖萌,而是添加了 emoji 表情的提交记录真的能包含很多有用信息,阅读体验非常棒。 但是,emoji 表情在提交代码的时候也不能乱用,否则容易造成误解。因此开源项目 gitmoji 专门规定了在 github 提交代码时应当遵循的 emoji 规范: 🎨 - 改进结构和代码格式 ⚡️ - 优化性能 🔥 - 移除代码或文件 🐛 - 修复 bug ✨ - 引入新功能 🍎 - 修复 MacOS 下的问题 📝 - 写文档 🚀 - 部署新功能 ✅ - 添加测试用例 🔖 - 发版/版本标签 🔒 - 修复安全问题 🐧 - 修复 Linux 下的问题 🚨 - 移除 linter

阅读全文 »

Jerry Bendy 发布于 09月07, 2016

【分享】手机淘宝的flexible设计与实现

看到小黑的文章 关于webapp中的文字单位的一些捣腾 感觉很赞。尤其是,他提到了手机淘宝的meta,所以觉得要讲讲我们这方面的一些实践。

手机淘宝从2014年中开始,全面推行flexible设计。什么叫flexible呢?其实flexible就是responsive的低端形态和基础。对我们来说,最直观的感受就是,在超宽屏幕上,网页显示不会两边留白。以前pc时代大家经常讲的流体布局,其实就是一种flexible design。只不过,流体的表述角度是实现,flexible的表述角度是结果,为了跟高大上的responsive保持一致,我们这里使用了flexible这个说法。

讨论方案之前,需要先了解三个关键概念:

  • 单位英寸像素数(Pixel Per Inch,PPI):现实世界的一英寸内像素数,决定了屏幕的显示质量
  • 设备像素比率(Device Pixel Ratio,DPR):物理像素与逻辑像素(px)的对应关系
  • 分辨率(Resolution):屏幕区域的宽高所占像素数

当我们决定不同屏幕的字体和尺寸的单位时,屏幕的这几个参数非常重要。

场景1——Resolution适配

一张banner图片,当你面对不同的屏幕时你希望它的行为是怎样的?

在这个场景中,我们主要需要面对的是分辨率适配问题,考虑到多数网页都是纵向滚动的,在不同的屏幕尺寸下,banner的行为应该是总是铺满屏幕宽度以及总是保持宽高比

最自然的思路是使用百分比宽度,但是假如使用百分比宽度,即width:100%,我们又有两种思路来实现固定宽高比:一是利用img标签的特性,只设宽度等图片加载完,这种方法会导致大量的重排,并且非固定高度会导致懒加载等功能难以实现,所以果断放弃;二是使用before伪元素的margin撑开高度,这种方法是比较干净的纯css实现,但是不具备任何复用性而且要求特定html结构,所以也只好放弃了。

于是,剩下最合适的办法是使用其它相对单位,本来最合适的单位是vw,它的含义是视口宽度,但是这个单位存在严重的兼容问题,所以也只好放弃。

最后我们只好配合js来做,硬算也是一条路,但是同样不具备任何可复用性,最终我们选择了rem,我们用js给html设置一个跟屏幕宽度成正比的font-size,然后把元素宽高都用rem作为单位。

这是我们目前的线上方案了,它是一个近乎Hack的用法,已知的问题包括:

  • 某些Android机型会丢掉rem小数部分
  • 占用了rem单位
  • 不是纯css方案

场景2——PPI适配

一段文字,当你面对不同的屏幕时你希望它的行为是怎样的?

显然,我们在iPhone3G和iPhone4的Retina屏下面,希望看到的文字尺寸是相同的,也就是说,我们不希望文字在Retina屏尺寸变小,此外,我们在大屏手机上,希望看到更多文字,以及,现在绝大多数的字体文件,是自带一些点阵尺寸的,通常是16px和24px,所以我们不希望出现13px、15px这样的奇葩尺寸

这样的特征决定了,场景1中的rem方案,不适合用到段落文字上。所以段落文字应该使用px作为单位,考虑到Retina,我们利用media query来指定不同的字体,考虑到dpr判定的兼容性,我们用宽度替换来代替:

.a {
    font-size:12px
}
@media (min-width: 401px){
    .a {
        font-size:24px
    }
}

另一种场景,一些标题性文字,希望随着屏幕宽而增大的,我们可以仍然使用rem作为单位。超过35px(个人直观感受)的文字,已经不用太考虑点阵信息了,靠字体的矢量信息也能渲染的很好。

场景3——DPR匹配

一个区块,设计稿上有1像素边框,当你面对不同的屏幕时你希望它的行为是怎样的?

这个场景,需求很简单,设计师希望在任何屏幕上这条线都是1物理像素

好吧,当然这个问题的答案不是写1px那么简单。在retina屏下面,如果你写了这样的meta

<meta name="viewport" content="initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">

你将永远无法写出1px宽度的东西,除此之外,inline的SVG等元素,也会按照逻辑像素来渲染,整个页面的清晰度会打折。

所以,手机淘宝用JS来动态写meta标签,代码类似这样:

var metaEl = doc.createElement('meta');
var scale = isRetina ? 0.5:1;
metaEl.setAttribute('name', 'viewport');
metaEl.setAttribute('content', 'initial-scale=' + scale + ', maximum-scale=' + scale + ', minimum-scale=' + scale + ', user-scalable=no');
if (docEl.firstElementChild) {
    document.documentElement.firstElementChild.appendChild(metaEl);
} else {
    var wrap = doc.createElement('div');
    wrap.appendChild(metaEl);
    documen.write(wrap.innerHTML);
}

结语

总的来说,手机淘宝的flexible方案是综合运用rem和px两种单位+js设置scale和html字体。

这些JS的内容,可以在我们开源的库ml中找到:

https://github.com/amfe/lib.flexible


分享自:前端乱炖(http://www.html-js.com/article/Like-the-winter-flexible-design-and-implementation-of-the-mobile-phone-Taobao-cold

阅读全文 »

Jerry Bendy 发布于 07月30, 2016

【分享】ES6 你可能不知道的事 - 基础篇

ES6,或许应该叫 ES2015(2015 年 6 月正式发布),对于大多数前端同学都不陌生。

首先这篇文章不是工具书,不会去过多谈概念,而是想聊聊关于每个特性你可能不知道的事,希望能为各位同学正确使用ES6,提供一些指导。

对于 ES6,有些同学已经在项目中有过深入使用了,有些则刚刚开始认识他,但不论你是属于哪一类,相信这篇文章都有适合你的部分。针对文章中的问题或不同意见,欢迎随时拍砖、指正。

正文

Let + Const

这个大概是开始了解 ES6 后,我们第一个感觉自己完全明白并兴致勃勃的开始使用的特性。

以如下方式使用的同学请举下手?

// 定义常量
const REG_GET_INPUT = /^\d{1,3}$/;

// 定义配置项
let config = {
  isDev : false,
  pubDir: './admin/'
}

// 引入 gulp
let gulp    = require('gulp');

// 引入gulp相关插件
let concat  = require('gulp-concat');
let uglify  = require('gulp-uglify');
let cssnano = require('gulp-cssnano');

很多人看完概念之后,第一印象都是:“const 是表示不可变的值,而 let 则是用来替换原来的 var 的。”

所以就会出现上面代码中的样子;一段代码中出现大量的 let,只有部分常量用 const 去做定义,这样的使用方式是错误的。

你可能不知道的事

const 的定义是不可重新赋值的值,与不可变的值(immutable value)不同;const 定义的 Object,在定义之后仍可以修改其属性。

所以其实他的使用场景很广,包括常量、配置项以及引用的组件、定义的 “大部分” 中间变量等,都应该以const做定义。反之就 let 而言,他的使用场景应该是相对较少的,我们只会在 loop(for,while 循环)及少量必须重定义的变量上用到他。

猜想:就执行效率而言,const 由于不可以重新赋值的特性,所以可以做更多语法静态分析方面的优化,从而有更高的执行效率。

所以上面代码中,所有使用 let 的部分,其实都应该是用 const 的。

Template Strings(字符串模板)

字符串模板是我刚接触ES6时最喜欢的特性之一,他语法简洁,语义明确,而且很好的解决了之前字符串拼接麻烦的问题。

因为他并不是 “必须” 的,而且原有的字符串拼接思维根深蒂固,导致我们很容易忽视掉他。

使用实例

我们先来看看他的一般使用场景:

const start = 'hi all';

const getName = () => {
  return 'jelly';
};

const conf = {
  fav: 'Coding'
};

// 模板
const msg = `${start}, my name is ${getName()}, ${conf.fav} is my favourite`;

你可能不知道的事

// 1. 与引号混用
const wantToSay = `I'm a "tbfed"`;

// 2. 支持多行文本
const slogan = 
`
I have a dream today!
`;

// 比较适合写HTML
const resultTpl = 
`
  <section>
    <div>...</div>
  </section>
`;

Enhanced Object Literals(增强的对象字面量)

增强的对象字面量是 ES6 中的升华功能,他设计了很多简写,这些简写不但保留了明确的语义,还减少了我们多余的代码量。

当他的使用成为一个习惯时,我们会看到自己代码变得更为优雅。

你可能不知道的事

const _bookNum = 4;

const basicConfig = {
  level: 5
}

const config = {
  // 直接指定原型对象
  __proto__: basicConfig,

  // 属性简写
  _bookNum,

  // 方法简写
  getBookNum() {
    return this.bookNum;
  }
}

Arrows and Lexical This(箭头函数)

箭头函数是ES6中的一个新的语法特性,他的用法简单,形态优雅,备受人们青睐。

大多数同学初识这个特性时,更多的仅仅用它作为函数定义的简写,这其实就有些屈才了。

// 未使用箭头函数的写法
{
  ...

  addOptions: function (options) {

    var self = this;

    options.forEach(function(name, opts){

      self[name] = self.addChild(name, opts);

    });

  } 
}

// 使用箭头函数后的写法
{
  ...

  addOptions: function (options) {

    options.forEach((name, opts) => {

      this[name] = this.addChild(name, opts);

    });

  } 
}

可以注意到上下两段代码的区别。

在未使用箭头函数前,我们在过程函数中使用父级 this,需要将其显式缓存到另一个中间变量中,因为过程函数有独立的 this 变量,会覆盖父级;使用箭头函数后,不但简写了一个过程函数( forEach 的参数),还省略掉了 this 的中间变量的定义。

原因:箭头函数没有独立执行上下文( this ),所以其内部引用 this 对象会直接访问父级。

插播:原来我们定义这个中间变量还有一个有趣的现象,就是明明千奇百怪,例如 self, that, me, _that, _me, Self...,快站出来说说你用过哪个,还是哪几个~

当然,从这块我们也可以看出,箭头函数是无法替代全部 function 的使用场景的,例如我们需要有独立 this 的函数。

另注:作者原文的评论中有人提到 forEach 方法中是可以不用写 that = this 这样的语句的,因为 forEach 可以接受可选的第二个参数用于重新指定 callback 中的 this 指向。即,使用 options.forEach( callback, this ) 即可。具体可参见MDN上的说明

你可能不知道的事

  • 箭头函数不但没有独立 this,他也没有独立的 arguments,所以如果需要取不定参的时候,要么使用 function,要么用 ES6 的另一个新特性 rest(具体在 rest 中会有详解)。
  • 箭头函数语法很灵活,在只有一个参数或者只有一句表达式做方法体时,可以省略相应括号。
// 完整写法
const getOptions = (name, key) => {
  ...
}

// 省略参数括号
const getOptions = key => {
  ... 
}

// 省略参数和方法体括号
const getOptions = key => console.log(key);

// 无参数或方法体,括号不能省略
const noop = () => {};

有个简单小栗子,这一灵活的语法在写连续的Promise链式调用时,可以使代码更加优雅

gitPromise
  .then(() => git.add())
  .then(() => git.commit())
  .then(() => git.log())
  .then((msg) => {
      ...
  })
  .then(() => git.push())
  .catch((err) => {
      utils.error(err);
  });

Destructuring(解构)

解构这个特性可以简单解读为分别定义,用于一次定义多个变量,常常用于分解方法返回对象为多个变量,分别使用。

使用过ES6的同学应该或多或少接触过这个特性,但是你可能不知道它如下几个用法:

你可能不知道的事

const bookSet = ['UED', 'TB fed', 'Not find'];
const bookCollection = () => {
  return {book1: 'UED', book2: 'TB fed'};
};

// 1. 解构也可以设置默认值
const {book1, book3 = 'Not find'} = bookCollection();

// 2. 解构数组时候是可以跳过其中某几项的
const [book1,,book3] = bookSet;  // book1 = 'UED', book3 = 'Not find'

// 3. 解构可以取到指定对象的任何属性,包括它包含的方法
const {length: setLength} = bookSet;  // setLength = 3

Rest + Spread

Rest 和 Spread 主要是应用 ... 运算符,完成值的聚合和分解。

你可能不知道的事

// 1. rest 得到的是一个真正的数组而不是一个伪数组
const getOptions = function(...args){
  console.log(args.join); // function
};

// 2. rest 可以配合箭头函数使用,达到取得所有参数的目的
const getOptions = (...args) => {
  console.log(args); // array
};

// 3. spread 可以用于解构时,聚合所得的值
const [opt1, ...opts] = ['one', 'two', 'three', 'four'];

// 4. spread 可以用于数组定义
const opts = ['one', 'two', 'three', 'four'];
const config = ['other', ...opts];

Classes

ES6 中实现的一个语法糖,用于简化基于原型集成实现类定义的场景。

虽然有很多人不太喜欢这个特性,认为它作为一个简单增强扩展,并没有其他语言 class 应有的特点。 但是就我自己观点来看,还是感觉这样一种写法确实比原有的原型继承的写法语义更清晰、明确,而且语法更简单。

同样,可能有些用法是你之前容易忽略掉的,在此做个补充。

你可能不知道的事

// 1. 静态变量
// ES6 的类定义实现了静态方法的定义,但静态变量呢?
// 可以用如下方式实现: 
class TbFedMembers{
  static get HuaChen(){
    return 'jelly';
  }
}
TbFedMembers.HuaChen; // "化辰"

// 2. 私有属性(私有属性有多种实现方式,只谈及其中一种)
// 闭包
const TbFedMembers = (() => {
  const HuaChen = 'jelly';

  return class{
    getOneMemberName(){
      return HuaChen;
    }
  };
})();

Promises

Promise 不只是一个对象、一个语法,他更是一种异步编程方式的变化 相信使用过 ES6 的同学都已经开始尝试了 Promise,甚至在不支持ES6的时候,已经开始使用一些基于 Promise 思想的开源框架。

那么我们之前用 Promise 究竟用的对么?有什么需要注意的点呢?

你可能不知道的事


// 1. 多个异步任务同时执行用 Promise.all,顺序执行使用链式调用
// Promise.all
Promise
  .all([jsBuildPromise, cssBuildPromise])
  .then(() => {
    ...
  });

// chain
jsBuildPromise
  .then(() => cssBuildPromise)
  .then(() => {
    ...
  });


// 2. Promise 的链式调用需要每一个过程返回一个 Promise 对象才能保证顺序执行
gitPromise
  .then(() => git.add())  // 正确,箭头函数简写
  .then(() => {
    git.commit(); // 错误,函数返回 undefined,会立即执行下一过程
  })
  .then(() => {
    return git.log(); // 正确
  });


// 3. Promise 需要调用 catch 方法来捕获错误,而且过程内的错误不会阻塞后续代码执行
new Promise(() => {
  f;  // not define error !
})
.catch((err) => {
  console.log(err)  // show 'f is not define'
});
console.log('error test');  // 此行可以被正常执行

结语

基础篇主要是讲了我们最常用的一些特性,后续如果大家感兴趣,还可以再来个 “进阶篇”,最后,希望文章中的部分内容可以对大家理解和使用 ES6 有所帮助。

参考资料

文章分享自淘宝FED团队化辰的文章, 原文地址:http://taobaofed.org/blog/2016/07/22/es6-basics/

感谢作者的分享!

阅读全文 »

Jerry Bendy 发布于 07月03, 2016

【分享】如何部署软件 - 让你团队的部署像地狱一样无聊且毫无压力

图片

作者 Zach Holman

本文为 Coding 用户协作翻译,转载请注明来源。如果你对本文的翻译有建议,欢迎提交 Pull Request

让我们来聊聊部署

无论你何时对自己的代码库做出改动,总会伴随着要破坏一些东西的风险。

没有人喜欢宕机, 没有人喜欢暴躁的用户, 也没有人喜欢生气的经理,所以部署新代码到生产环境变成颇具压力的一个环节。

你完全没必要对它有压力,我将在这里重复一遍又一遍这句话:

你的部署应该尽可能单调、直接、毫无压力。

部署新功能到生产环境中应该像在 Hacker News 开始一场关于 用 spaces 还是 tabs 的口水战一样简单。它应该足够简单到让新员工理解,它应该为防止错误而生,它应该在第一个最终用户看到新代码前被很好地测试过。

这是一篇高层次谈论部署的文章,包含了:协作,安全和速度等,在底层方面也讲了很多,但这些都是很难跨语言进行概括,并且说实话,比起在高层次技术方面有很多更密切的问题要去解决,我更喜欢谈论团队如何协同工作,而部署是与其他人协作最关键的一部分。我认为你值得花时间并不时地来评估你团队的状况。

有很多来自我在 GitHub 任职 5 年的经验,和去年我与大大小小科技公司提供建议和咨询的经验,对在提高他们的部署工作流程的重点上(已经从“非常可敬”到归纳于“我觉得这服务器已经着火了”)。我特别推荐一个初创公司, Dockbit ,其产品是旨在正视部署中的合作,等等。这篇文章来源于许多关于我和它们团队的谈话,我认为写下来许多部署难题中的不同部分会大有益处的。

我很感激一些来自不同公司的朋友给予这篇文章的校队和帮忙,并提供各自在部署上不同的观点: Corey Donohoe (Heroku) , Jesse Toth (GitHub) , Aman Gupta (GitHub) , 和 Paul Betts (Slack) 。我不断的发现不同的公司可能采取很有趣的不同路径,但一般都集中在合作,风险和谨慎这种基础方面,我觉得有东西在这里具有普遍性。

不管怎么说,对于这个漫长的导语我感到很抱歉,但无论如何这篇文章将是很长的,请尽力读完吧, lol.

目录

  • 目标 难道部署不是一个已经解决的问题吗?

  • 准备 开始为部署做准备:测试,feature flags 和你的开发协作方式

  • 分支 为你的代码设置建立分支是部署最基本的部分。在部署新代码时你能把其中导致任何可能意外后果的部分分离出来。开始思考部署分支,自动部署 master 分支,蓝/绿部署。

  • 控制 部署的核心。你如何才能控制被发布的代码。处理在部署和合并中不同的权限结构,为你的部署建立一套审计跟踪,通过部署锁定和部署队列让一切有序。

  • 监视 Cool,你的代码已经在生产环境了。现在你可以关心的你的部署在不同方面的监视指标,并且最终做出是否要为你的改动回滚代码的决定。

  • 结论 "我们学到了什么, Palmer ?" "先生我不知道。" "我 TM 也不知道。我猜我们学到的就是,不要再这么做了。" "是的,先生。"

How to Deploy Software was originally published on March 1, 2016.

目标

图片

难道部署不是一个已经解决的问题吗?

如果你说的是拉取代码并将它们传送到不同的服务器上,那么事情已经解决的很漂亮并且这些事情相当无聊。你已经获得了 Ruby 中的 Capistrano(一种远程服务器自动化工具), Python 中的 Fabric(Python 的一个类库以及命令行工具),Node 中的 Shipit (Javascript 写的一个通用自动化和发布工具),以及所有的亚马逊云服务,甚至是 FTP 也貌似会存在好几个世纪,因此工具现在真的不是问题。

如果我们此时有了相当不错的工具,为什么部署会出错呢?为什么人们总是发布 bug 呢?为什么总是存在宕机的情况?我们可都是写完美代码的完美程序员啊,这真是见鬼了。

很明显,事情总是始料未及地发生,所以我认为部署应该是中小型公司应关注的有趣领域,其他领域没有如此好的投入产出比。你可以做好尽早处理和应对问题的工作流程吗?你可以使用不同的工具来更简单地进行协助部署吗?

这不是工具问题,这是处理的问题。

我对很多很多创业公司讲,过去的几年,还没有一个从组织的角度看上去“好的”部署工作流。

你无需负责部署的专人,无需特定一个部署日期,无需在每次部署时动用所有人手。你只需要采取一些聪明的办法。

准备

图片

在一个好的基础上开始。

在跑之前你必须走起来。我认为初创公司有一个很时髦的方面就是它们都用着最酷并且最新的部署工具,但是当你切进去观察他们的处理时,会发现他们花了 80% 的时间在处理基础上。如果他们从开始时就是流水化的,每件事都会恰当地处理并且更为迅速。

测试

测试是前期一个最简单的地方。在一些浅显的依赖处理中这不是一个必需的步骤,不过却对其着有着巨大的影响。

这里很多技巧取决于你的语言,平台或者框架,不过普遍的建议是测试你的代码 ,并提高测试速度。

我最喜欢引用 Ryan Tomayko 在 GitHub 的内部测试文档里写的:

我们能让好的测试变快却不能让快的测试变好。

所以以一个好的基础开始:做个好的测试,别吝啬这点,因为它影响一切的方向。

一旦你开始有一个值得依靠的质量测试套件,尽管在开始时得花钱。如果你有任何形式的收入或者你们幕后团队的资助,几乎头号你应该花钱的地方就是你应该运行测试的地方。如果你使用 Travis CI 或者 CircleCI ,如果你能运行并行编译构建那么就重复今天所做的。如果你需要在特定的硬件上运行,那就买巨大的服务器。

我见过一些公司通过迁移到更加快的测试套件上使其获得了最重要的生产力优势,而你也能赚到,因为它影响迭代反馈周期,缩短依赖时间,增加开发者的幸福感并且使其成为一种惯性。在问题解决方案上花钱吧:因为服务器很便宜,但程序员却不。

我在 Twitter上 做过一个非正式的投票 问我的 followers 他们的测试套件究竟跑得有多快。诚然,很难解释那些微小服务,语言差异上居然有惊人数目的人从来没有做过测试。不过在全栈和更快的单元测试者对决中,它表现的还是非常明显。大多数人在 push 后至少要等待5分钟才能看到构建状态。

图片

快究竟指多快呢? 当我在 GitHub 时,测试一般在 2-3 分钟内跑完。我们并没有很多集成测试,所以允许以相对较快的速度测试,但是实际上你测试的越快,你就能越快的得到开发人员的循环反馈。

有许多的项目旨在帮助你并行化构建项目。在 Ruby 里有 parallel_teststest-queue 。比如你的测试没有相互完全独立,以不同的方式编写测试是一个很好的方法,如果情况相反,你也应该好好处理它。

Feature Flags

这一切的另一个方面是开始看你的代码并且把它转化来支持多渠道部署代码路径。

重复一遍,我们的目标是你的部署应该尽可能单调,直接,无压力。部署任何新代码的自然压力是代码运行出你无法预见的问题,你最终影响到用户的行为(即他们经历的停机时间和错误)。即使你有宇宙中最好的程序员,糟糕的代码将最终被部署。不管这些坏代码是影响 100% 的用户或只是一个对你们非常重要的用户。

用一个简单的方法来处理,那就是 Feature Flag 。 Feature Flag 已经出现很久了,至少,技术上讲,是自从 if 语句发明开始的,而我记得第一次真正听到有公司的使用 Feature Flag 是 Flickr 在 2009 年的一篇文章。Flipping Out

这允许我们开启我们正在开发的功能而不影响其他开​​发人员正在开发的功能。它也可以让我们打开或关闭测试单独的功能。

只有你可以看到,或只有你的团队可以查看 flags,或所有员工都能看是两件不同的事:你可以在现实世界使用真实的数据测试代码,并确保一切工作正常;你还可以得到真正的关于该功能正式发布时得到关于性能和风险的 benchmarks。

所有这一切的对你准备部署新的功能是大大有利的,你需要做的就是修改一行代码为 true ,然后每个人都看到了新的代码。这使得通常吓人的新版本部署变为为单调,直接,且无压力。

可查验的正确的部署

作为一个额外的步骤,Feature Flag 提供了一个很好的方式来证明你的即将代码部署不会对性能和可靠性产生不利影响。最近几年一些新的工具能帮助你做到这个。

我在几年前的演讲文章 《Move Fast and Break Nothing》 中谈过,它的主要内容是在生产环境运行 Feature Flag 的两个代码路径中,并且只返回旧代码的结果,观察你引进的新代码与你即将更换的新代码的表现。一旦你有了这些数据,你可以确保你不会破坏任何事情。部署将变得单调,直接,无压力。

图片

GitHub 上一个叫做 Scientist 的开源 Ruby 库能抽象的帮助你很多。这个库已经这点上被移植到最受欢迎的语言上,所以如果你感兴趣,它可能很值得你花时间去看一看。

另一种方法是灰度发布。一旦你对要部署的代码是准确无误充满信心,首先你仍然谨慎地仅公开到一小部分用户来进行双重检查和三重检查,直到没有产生什么破坏。破坏 5% 用户的体验比破坏 100% 用户的体验要好得多。

有大量旨在帮助你的库,从 Ruby 中的 Rollout , Java 中 Togglz ,到 JavaScript 中的 fflip ,和许多其他的。还有很多初创公司在为这个问题提供解决方案,比如 LaunchDarkly 。

另外值得一提的是,这并不仅仅是 Web 的事情。本地应用也可以从中获益良多。粗略看下 GroundControl 这个 iOS 中处理表现的库就懂了。

在代码构建上自我感觉良好?赞,我们现在来跳出这点,开始讨论下部署。

分支

图片

用分支管理

很多围绕部署的组织问题被部署者与其他人缺少沟通阻碍着。你需要每个人了解了你即将上线的代码的方方面面,在做这个同时避免踩到她人的脚趾。

这里有几个可以帮到你的有趣的方式,它们都取决于部署的最简单元:分支。

代码分支

分支,我是指 Git,Mercurial 等版本控制系统的分支。先切出一个分支,在该分支上编程,然后推送代码到你喜爱的代码托管平台(如 GitLab,Bitbucket,Coding 等) 你也应该使用 Pull Requests,Merge Request,或其他 code review 工具去评审写好的代码。部署环节必须要协作,代码评审是非常重要的一部分。我们稍后会进一步说明这块。

代码评审

代码评审这个话题太大,太复杂,且依你的团队和风险状况不同。我认为这里有几个重要的问题需要所有的团队去思考:

  • 你的分支你负责。 我见过的成功型公司都有这个理念,即部署代码失败的最终负责人是写这个代码的人。他们不把部署失败的责任归结于部署上线的人然后就去起床吃饭,当然这些人应该参与代码评审,但最重要的是你要对你自己的代码负责。如果它(编译)失败了,你自己去解决.......而不是你可怜的 ops 团队。所以不要搞砸它。

  • 尽早开始并经常性进行评审。 你不需要完成一个分支后再去请求评审。如果你可以发起一个关于预期代码的评审请求,比如,花了 20 分钟在这上面然后被通知说 “不,我们不用做这个了” 远远比之后花两周时间写这个代码更好。

  • 总是需要某人来评审代码。 你可以依靠团队来做这个,但有一双专门负责评审的眼睛是非常有帮助的。对于结构化程度高的公司,你可能要明确指派人来负责代码评审,并要求他们在代码完前就开始 review。对于结构化程度较低的公司,你可以指派不用的团队看看谁最可以帮到你。在光谱的两端,你设定的期望是有人在猛冲前给你搭把手,或独自部署代码。

分支和部署节奏

这里有个关于代码评审的老段子。无论何时你开启了一个关于 6 行代码的评审请求,你总会得到很多同事关于这 6 行代码的指指点点。但当你 push 了一个花了几周时间的代码分支,你常常会得到一个很快回复的:赞,我看行!

基本上,程序员常常都是一群很讨厌的懒虫。

但你可以利用其作为你的优势,通过:使用尽快,较小的分支和 Pull Request。让代码小到可以很容易让人随时切入并进行评审。如果你写了大型的分支,这需要别人花很长时间去 review,同时拖慢了整个开发的进度。

如何让代码更小?这时之前说的 feature flags 就派上用场了。当 2014 年我团队的三个人重建 GitHub 的 Issues 时,我们向 Production 推送了大约上百数量的使用 Feature Flag 的小型 Pull Requests。我们部署了很多小单元(在其“完美”之前)。这让代码评审更简单,同时让部署更快,更早看到线上的产品状况。

你需要快速并频繁地部署。十人规模的团队可以每天无忧地部署至少 7-15 个分支。重复一遍,diff 越小,部署就越单调,越直接,越无压力。

部署分支

当你准备好部署你的新代码,你应该总是在合并代码前部署你的分支。注意"总是"。

查看整个代码库作为事实的记录。你的 master 分支(或你指定的任何的默认主分支)应该作为你的生产环境的绝对镜像。换句话说,你需要确保你的主分支是“没问题的”,就是该分支没有任何已知的问题。

分支是个大问题。如果你合并你的分支到 master 然后就部署 master 分支,你无法简单地判断代码是否正常,“没问题”分支是无需做任何恶心的代码回滚操作的分支。 这不一定是火箭科学才需要的事情,但如果你的部署搞坏了网站,最后你就需要反思下了。你需要一个简单的方法。

这就是为什么你的部署工具应该支持你部署分支是很重要的。一旦你确认你的性能没有波动,没有稳定性问题,功能可用性在预期内,你就可以 merge 它了。这么做的原因不是为了确保事情可行,而是防止事情不可行。当其出错时,你的解决方案应该是单调,直接且无压力的:重新部署 master 分支即可。就是这样。你回到了“没问题”的状态。

自动部署

重要的是要对你的“已知状态”有清晰的定义,最简单的方法就是定一个简单的不出错的规则:

除非你正在测试一个分支,所有部署到生产环境的都始终由 master 分支体现。 我见过的最简单的方法是保持在 master 分支设置自动部署。这是超简单的规则组,它鼓励大家向分支做出最无风险的提交。

这里有很多工具平台可用,如 Heroku 可以自动部署最新分支。CI 工具如 Travis CI (译者注:国内有 flow.ci )也可以帮你自动部署。或私有部署的 Heaven 和 hubot-deploy-tools (我们稍后会提到)也可以帮到你。

自动部署在你 merge 你工作的分支到 master 分支时也有帮助。你的工具应该可以选取一个新的修订并重新再次部署网站。尽管软件内容没变(你在有效的部署同一套代码),SHA-1 值变化了,这使生产环境的已知状态变得更加明确(再次重申下,master 分支是已知状态)。

蓝绿部署

Martin Fowler 曾经在 2010 年的文章推崇过 蓝绿部署(很值得一读)。在其中,Fowler 谈论到使用两种理想的生产环境的理念,即他说的“蓝”和“绿”。蓝意味着在线的生产环境,绿代表空闲的生产环境。你可以部署到绿色集群,确认一切正常运行后,通过无缝切换(如负载均衡)切换到蓝色集群。如此,生产环境收到了没有风险的代码。

自动部署的一个挑战就是切换,将软件从测试的最后环节检出到生产环境。

这是一个非常强大的想法,而日益普及的虚拟化,容器技术和(可以很容易地扔掉,被遗忘的)自有环境使它变得更加强大。除了一个简单的蓝色/绿色的部署,你也可以让生成产环境流动起来,因为一切都是虚拟的。

这里有很多解决方法,从灾备恢复到在用户看到它前附加时间测试关键功能,但我最喜欢的是附加功能使用代码。

使用新代码是在产品开发周期非常重要的。当然,很多问题应提前在代码审查或通过自动测试找到了,但如果你正在尝试做真正的产品,有时很难预测直到你已经长时间试过了真实的数据。这就是为什么蓝绿部署比有一个简单的临时服务器更重要,其数据可能过时了或完全捏造。

更重要的是,如果你需要你的代码部署到特定的环境中,你就可以在早期开始就引入不同利益相关者。不是每个人都有技术能力把你的代码拉取到他们的计算机上,并在本地安装你的代码 - 而且这是也不应该的!比如,如果你能给你的会记部门展示你的新的上线情况的屏幕,在整个公司看到它之前,他们可以给你一些关于它的现实反馈,这可以帮你在早期找到很多的错误和问题。

Heroku Pipelines

不管你用不用 Heroku,看一下他们生态系统中的“Review Apps”理念:apps 直接从一个 Pull Request 进行部署,直接上线而不是截图或大幅的关于“这就是它上线后的样子”的描述。让更多人尽早参与进来而不是你之后试图用烂的产品说服他们。

控制

图片

控制部署流程

你看,当我在谈一个创业公司的组织方式时,我是完全嬉皮自由雅痞的:我笃信开发者的自主性,用一种自下而上的方法去开发,注重人而不是管理。我认为这会让大家更快乐且让产品更好。但在部署时,嗯哼,这是非常重要的,属于 all-or-nothing 的需要做好的事情,所以我觉得这里加入管控是合理的。

幸运的是,部署工具就是加入限制,从而把大家从压力中解放出来,所以如果你做对了将大大获益,而不是常人说的这将是阻碍。换句话说,你的流程应该促使事情搞定,而不是阻碍它。

审计跟踪

我惊讶一些竟然不可以很快拿到审计日志的创业公司。尽管可能会有一些聊天记录可查,但这不应该是你需要时拿不出来的东西。

审计跟踪的好处就是你预见到的:你可以找出是何时何地何人部署的。当你之后遇到问题时,你可以回滚到某一节点,这将节省不少时间。

很多服务都提供了这类的部署日志。如 Amazon CodeDeploy 和 Dockbit,提供了广义上的部署工具并提供了很好的追踪工具。GitHub 杰出的部署 API (译者注:Coding.net 也提供了 部署 API)也是很好的从 Pull Request 部署集成到你的外部系统的好办法。

GitHub 的开发 API

如果你在专家模式,在你的部署和部署时间需要插入很多数据库和服务,如 InfluxDB,Grafana,Librato 或 Graphite。可以在给定指标和部署层指标中对比是非常强大的:起初看到一个意外的指标增加或许让你好奇,但当那是一次部署在发生时,你就不会感到意外了。

部署锁定

如果你走到了在一个代码库中有很多人的这一步,你自然会遇到有很多人在某时都准备部署各自代码的状况。当然同时部署多个分支到生产环境是可行的,但我建议,当你走到这一步时,你需要些处理这种情况的工具。部署锁定就是我们要了解的第一个东西。

部署锁定基本是你已经预料到的东西:锁定生产环境以便大家可以依次进行部署。这里有很多方法可行,但最重要的是你要让这 可见

实现这一目标的最简单办法就是通过聊天。一个常见的方式可以是设置部署命令锁定生产环境,比如:

/deploy <app>/<branch> to <environment>

i.e.,

/deploy api/new-permissions to production

这使大家都明白你在部署什么。我见过一些使用 Slack 的公司在 Slack 的部署聊天室里说:我在部署...!我觉得这是没有必要的,这只会分散你的同事。这里只要把信息扔进聊天室就够了。如果你之后忘了做也可以添加一条额外命令让系统返回目前生产环境的状态。

这里有很多简单方法可以把这套工作流插入你的聊天室。Dockbit 有一个 Slack 集成。也有一个开源解决方案叫作 SlashDeploy 可以集成 GitHub 和 Slack。(译者注:国内有 bearychat.com 提供了类似服务)

我还见过一些特制的关于这一步的网页版工具。Slack 有个自定义的内部 App 提供了可视化的部署。Pinterest 有一个开源的基于网页的部署系统。你可以将锁定的理念延伸到其他方面,这取决于如何使你的团队最高效工作。

一旦部署分支被 merge 到 master 分支,生产环境应该自动解锁以便下一个人进行操作。

这里也有一定的锁定礼仪。你当然不希望大家等待一个粗心的程序员忘记了解锁生产环境。自动解锁工具就派上用场了,比如,你也可以设置定时提醒部署人员其生产环境是否被锁定超过了 10 分钟。宗旨就是:拉完屎赶紧走。

部署队列

一旦你有很多部署要安排且你有很多人员准备部署,你显然会有一些部署争论。对于这一点,从你内心深处的英国绅士特色中选择,形成一个部署队列。

一个部署队列有几个部分:1)如果需要等待,把你的名字添加到末尾,2)允许有人插队 (有些非常重要的部署需要立即执行,你需要允许这样做)

部署队列的唯一问题就是有太多人排队部署了。GitHub 从过去一年至今都面临这个问题;在周一人人都想部署他们的变更,部署列表看起来可以持续一个小时或更久。我不是特别提倡微服务,但我认为部署队列有一个好处就是你可以从雄伟的巨石中劈东西了。

权限

有很多方法可以限制使用部署权限的人。

两步验证是一个选项。最好你的雇员聊天帐号不会被公开,最好他们在他们电脑上有其他安全措施(全盘加密,强密码等等),但是如果你要安心的话,最好要求他们开启两步验证。

或许你已经有提供两步验证服务的聊天服务商,如 Campfire 和 Slack。(译者注:Coding.net 也提供了 两步验证 ) 如果你需要在部署前进行两步验证,你可以在其流程中增加两步验证。

另一种可行的处理方法是,我称权限外的调查员为“骑猎枪”。我见过很多拥有正式或非正式的流程或工具去保证至少有一位高级开发人员参与每一步部署。这里没有理由不这么做,比如,要求你的部署人员和高级开发人员(骑猎枪)去确认代码可以部署。

监视

图片

欣赏并检验你的工作

一旦你部署完你的代码,便是时候开始验你是否真的做了你所想做的。

检查你的 Playbook

无论是更新前端、后端或其他任何代码,每次部署都必须符合同一个策略方针。你必须查看网站是否还正常运行着,是否性能突然变得更糟糕,是否产生更多误码率,亦或者有更多反馈的问题等等。所以说精简那个策略方针将对你非常有利。

对于上述的不同方面,如果多个信息来源,试着比如在最终确认部署的时候给每个仪表盘中加一个链接。这样每次都能提醒大家观察并验证这些变更是否对度量指数产生了负面影响。

理想状态下,应从一个来源里获取信息。这样更容易指引,比如一个新员工在第一次部署的时候该观察重要的度量指数。比如 Printerest 的 Teletraan 在一个界面里就包含了所有的信息。

度量指数

有很多可以收集的度量指数将有助于你判断刚刚部署得是否成功。

当然最显著的是误码率。如果它突然急速上升,意味着你可能得重新部署 master 分支并且修复这些问题。这些过程可以自动化实现,甚至可以设定一个阈值,如误码率超了就自动重新部署。如果你确定 master 是一个对你来说熟知并且可以回滚的分支,那么自动回滚将变得更容易,一旦你部署后就触发大量异常则自动回滚部署。

部署本身也是个很有意思的度量,值得放在手上。一个很好的例子就是纵览过去一年的部署情况,可以帮助你了解部署的节奏是在放大,或者让你了解它慢下来的原因。你可以进一步收集是谁在部署,谁导致了错码,并开发一种能够检测团队开发者是否可靠的方法。

部署后的清理

最后一步需要做的家务活就是清理。

Feature Toggles 是最糟糕的技术债之一 对这进行了讨论,虽然这个标题有点激进。如果你正在构建一些有 feature flags 以及人员发展的项目,你将面临长期使你代码库的变得更复杂化的风险:

用管道与脚手架逻辑支持代码分支是一种令人讨厌的技术债务,因为自此以后每个功能开关都要引入。Feature flags 使得代码更脆弱,很难测试、理解、维护、支持,也更不安全。

你不需要部署完就立刻清理;如果你有一个新功能或者 bug 修复的需求,你应该花时间在监测系统指标,而不是立刻删除代码,尽管部署后不久你还是得这样做。如果你有一个重大版本发布, 你可以在一天或一星期后回顾并删除那些已经不用的代码。我喜欢做的一件事就是准备两个 pull request:一个是切换 Feature flags (比如,开放该功能给所有人),另一个是清除所有的你引入的冗余代码。当我确保我没破坏任何事情并且看上去不错的话,我就可以合并第二个 pull request 而不需再更多的考虑或开发。

你还需要给自己庆祝一番:因为这个终极信号意味着你们已经成功地完成了这个项目。所有人都会喜欢看到 diff 几乎都变红的状态,删除代码的确是件很开心的事情。

删除分支

当你完成任务后,你同样可以删除分支,这肯定是不会错的。但如果你用的是 GitHub 的 pull request,你通常可以保留删除的分支,这样相当于从分支列表中删除了该分支,但其实不会有任何的数据丢失。这个过程同样也可以自动完成:定期执行一个脚本,检查你那些陈旧的已合并到 master 的分支并且删除它们。(译者注:Coding.net 的 Merge Request 也提供了 merge 后自动删除分支功能)

结论

图片

整个球赛

我只对两种事情感到情绪激动:一个是一张动人的照片:山顶上一只金毛寻回犬倚着它最好的朋友,面朝大海,看夕阳西下;还有就是部署工作流。我如此关心这件事是因为它是整个比赛最关键的一部分,在一天结束的时候,我只关心两件事情:同事的感觉是怎么样的,我工作的产品是怎么样的。对我来说其他一切皆源于这两方面。

部署可以造成压力和挫折,尤其当你的公司开发节奏是迟缓的,也可以减缓和阻止你添加新功能、为用户修复 BUG。

我认为思考这些是值得的,优化自己的工作流也是值得的。花一些时间让自己的部署变得尽可能单调、直接、无压力,是会得到回报的。

(完)

你可能会感兴趣的文章:

文章转自 Coding 博客,原文地址:https://blog.coding.net/blog/deploying-software

阅读全文 »

Jerry Bendy 发布于 04月07, 2016

淘宝架构发展

2003 年 4 月 7 日,马云,在杭州,成立了一个神秘的组织。他叫来十位员工,要他们签了一份协议,这份协议要求他们立刻离开阿里巴巴,去做一个神秘的项目。这个项目要求绝对保密,老马戏称“连说梦话被老婆听到都不行,谁要是透漏出去,我将追杀到天涯海角”。这份协议是英文版的,匆忙之间,大多数人根本来不及看懂,但出于对老马的信任,都卷起铺盖离开了阿里巴巴。

他们去了一个神秘的据点 —— 湖畔花园小区的一套未装修的房子里,房子的主人是马云。这伙人刚进去的时候,马云给他们布置了一个任务,就是在最短的时间内做出一个个人对个人(C2C)的商品交易的网站。现在出一个问题考考读者,看你适不适合做淘宝的创业团队。亲,要是让你来做,你怎么做?

在说出这个答案之前,容我先卖个关子,介绍一下这个创业团队的成员:三个开发工程师(虚竹、三丰、多隆)、一个UED(二当家)、三个运营(小宝、阿珂、破天)、一个经理(财神)、还有就是马云和他的秘书。当时对整个项目组来说压力最大的就是时间,怎么在最短的时间内把一个从来就没有的网站从零开始建立起来?了解淘宝历史的人知道淘宝是在 2003 年 5 月 10 日上线的,这之间只有一个月。要是你在这个团队里,你怎么做?我们的答案就是:买一个来。

买一个网站显然比做一个网站要省事一些,但是他们的梦想可不是做一个小网站而已,要做大,就不是随便买个就行的,要有比较低的维护成本,要能够方便的扩展和二次开发。那接下来就是第二个问题:买一个什么样的网站?答案是:轻量一点的,简单一点的,于是买了这样一个架构的网站:LAMP(Linux+Apache+MySQL+PHP)。这个直到现在还是一个很常用的网站架构模型。这种架构的优点是:无需编译,发布快速,PHP功能强大,能做从页面渲染到数据访问所有的事情,而且用到的技术都是开源的,免费。

当时我们是从一个美国人那里买来的一个网站系统,这个系统的名字叫做 PHPAuction(他们的官方网站 http://www.phpauction.net,这个名字很直白,一眼就看出来这个系统是用什么语言做的、是干什么用的),PHPAuction有好几个版本,我们买的是最高版的,功能比较多,而且最重要的是对方提供了源代码。最高版比较贵,花了我们 2000 美金(貌似现在降价了,只要 946 美元)。买来之后不是直接就能用的,需要很多本地化的修改,例如页面模板改的漂亮一点,页头页脚加上自己的站点简介等,其中最有技术含量的是对数据库进行了一个修改。原来是从一个数据库进行所有的读写操作,拿过来之后多隆把它给拆分成一个主库、两个从库,读写分离。这么做的好处有几点:存储容量增加了,有了备份,使得安全性增加了,读写分离使得读写效率提升了。这样整个系统的架构就如下图所示:

其中 Pear DB 是一个 PHP 模块,负责数据访问层。另外也用开源的论坛系统 PHPBB(http://www.phpbbchina.com )搭建了一个小的论坛社区,虚竹负责机器采购、配置、架设等,三丰和多隆负责编码,他们把交易系统和论坛系统的用户信息打通,给运营人员开发出后台管理(admin系统)的功能,把交易类型从只有拍卖这一种增加为拍卖、一口价、求购商品、海报商品(意思是还没推出的商品,先挂个海报出来)这四种。(PHPAuction 只有拍卖的交易,Auction 即拍卖的意思。@_行癫在微博中提到:今天 eBay 所有交易中拍卖交易仍然占了 40%,而在中国,此种模式在淘宝几乎从一开始就未能占据优势,如今在主流的交易中几乎可以忽略不计。背后的原因一直令人费解。我大致可以给出其中一种解释,eBay 基本在发达国家展开业务,制造业外包后,电子商务的基本群体大多只能表现为零散的个体间交易。)

在经历了另外一些有趣的事情之后(这些有趣的事情包括“淘宝”这个名字的由来,员工花名的由来等等,由于本书主要描述技术方面的故事,对这些有兴趣的可以去网上找),网站开始上线运行了。

在接下来的大半年时间里,这个网站迅速显示出了它的生机。这里有必要提一下当时的市场环境,非典(SARS)的肆虐使得大家都不敢出门,尤其是去商场之类人多的地方。另外在神州大地上最早出现的 C2C 网站易趣也正忙的不亦乐乎,2002 年 3 月,eBay 以 3000 万美元收购了易趣公司 33% 的股份,2003 年 6 月以 1.5 亿美元收购了易趣公司剩余 67% 的股份。当时淘宝网允许买卖双方留下联系方式,允许同城交易,整个操作过程简单轻松。而 eBay 为了收取交易佣金,是禁止这么做的,这必然增加了交易过程的难度。而且 eBay 为了全球统一,把易趣原来的系统替换成了美国 eBay 的系统,用户体验一下子全变了,操作起来非常麻烦,这等于是把积累的用户拱手送给了淘宝。为了不引起 eBay 的注意,淘宝网在 2003 年里一直声称自己是一个“个人网站”。由于这个创业团队强大的市场开拓和运营能力,淘宝网发展的非常迅猛,2003 年底就吸引了注册用户XXX,最高每日 31 万PV,从 5 月到年底成交额 4000 万。这没有引起 eBay 的注意,却引起了阿里巴巴内部很多员工的注意,他们觉得这个网站以后会成为阿里巴巴强劲的对手。甚至有人在内网发帖,忠告管理层要警惕这个刚刚起步的网站,但管理层似乎无动于衷。(这个团队的保密工作做的真好)

在市场和运营的后方,淘宝网的技术团队也在快速的做着系统的改进和创新。这里还有个有趣的故事,eBay 和易趣早期都有员工在论坛上响应用户的需求,eBay 的论坛用粉红色背景来区分员工的发言,易趣的员工在论坛上昵称都选各种豆豆,例如黄豆豆、蚕豆豆等。淘宝在讨论运营策略的时候提到这个问题,要求所有的员工都去论坛上回答用户的问题。最早回答问题的任务落在小宝头上,那我们用什么名字好呢?“淘淘”?“宝宝”?小宝都不满意,太女性化了。讨论了很久之后,小宝灵光乍现,干脆取个名字叫“小宝”吧,小宝带七个老婆来开店,迎接各位客官,很有故事性。于是很多武侠小说中的人物开始在论坛中行侠仗义,这些昵称下面标志着“淘宝店小二”,他们回答着各种各样的问题,快速响应着用户的各种需求。如果是技术上能解决的,几个人商量一下,马上就开发、测试、发布上线。反过来对比一下,易趣被 eBay 收购之后,系统更换成了全球通用的版本,响应用户的一个需求需要层层审批,反应速度自然慢了下来。

当时淘宝第一个版本的系统里面已经包含了商品发布、管理、搜索、商品详情、出价购买、评价投诉、我的淘宝这些功能(现在主流程中也是这些模块。在 2003 年 10 月增加了一个功能节点:“安全交易”,这个是支付宝的雏形)。随着用户需求和流量的不断增长,系统上面做了很多的日常改进,服务器由最初的一台变成了三台,一台负责发送 email、一台负责运行数据库、一台负责运行 Web App。过一段时间之后,商品搜索的功能占用数据库资源太大了(用like搜索的,很慢),又从阿里巴巴中文站搬过来他们的搜索引擎 iSearch,起初 iSearch 索引的文件放在硬盘上,随着数据量的增长,又采购了 NetApp 服务器放置 iSearch。

如此快节奏的工作,其实大家都累得不行,有人就提议大家随时随地的锻炼身体,可是外面 SARS 横行,在一个一百多方的房子里,怎么锻炼呢?高挑美女阿珂提议大家练习提臀操,这个建议遭到男士的一致反对,后来虚竹就教大家练习倒立,这个大家都能接受。于是这个倒立的传统一直延续至今,和花名文化、武侠文化一并传承了下来。

随着访问量和数据量的飞速上涨,问题很快就出来了,第一个问题出现在数据库上。MySQL 当时是第 4 版的,我们用的是默认的存储引擎 MyISAM,这种类型读数据的时候会把表锁住(我们知道 Oracle 在写数据的时候会有行锁,读数据的时候是没有的),尤其是主库往从库上面写数据的时候,会对主库产生大量的读操作,使得主库性能急剧下降。这样在高访问量的时候,数据库撑不住了。另外,当年的 MySQL 不比如今的 MySQL,在数据的容量和安全性方面也有很多先天的不足(和 Oracle 相比)。

二、Oracle/支付宝/旺旺

淘宝网作为个人网站发展的时间其实并不长,由于它太引人注目了,马云在 2003 年 7 月就宣布了这个是阿里巴巴旗下的网站,随后在市场上展开了很成功的运作。最著名的就是利用中小网站来做广告,突围 eBay 在门户网站上对淘宝的广告封锁。上网比较早的人应该还记得那些在右下角的弹窗和网站腰封上一闪一闪的广告。市场部那位到处花钱买广告的家伙,太能花钱了,一出手就是几百万,他被我们称为“大少爷”。

  “大少爷”们做的广告,带来的就是迅速上涨的流量和交易量。在 2003 年底,MySQL 已经撑不住了,技术的替代方案非常简单,就是换成 Oracle。换 Oracle 的原因除了它容量大、稳定、安全、性能高之外,还有人才方面的原因。在 2003 年的时候,阿里巴巴已经有一支很强大的 DBA 团队了,有冯春培、汪海(七公)这样的人物,后来还有冯大辉(@fenng)、陈吉平(拖雷)。这样的人物牛到什么程度呢?Oracle 给全球的技术专家颁发一些头衔,其中最高级别的叫 ACE(就是扑克牌的“尖儿”,够大的吧),被授予这个头衔的人目前全球也只有 300 多名(名单在这里: http://apex.oracle.com/pls/otn/f?p=19297:3 ),当年全球只有十几名。有如此强大的技术后盾,把 MySQL 换成 Oracle 是顺理成章的事情。

  但更换数据库不是只换个库就可以的,访问方式,SQL 语法都要跟着变,最重要的一点是,Oracle 并发访问能力之所以如此强大,有一个关键性的设计 —— 连接池。但对于 PHP 语言来说它是放在 Apache 上的,每一个请求都会对数据库产生一个连接,它没有连接池这种功能(Java 语言有 Servlet 容器,可以存放连接池)。那如何是好呢?这帮人打探到 eBay 在 PHP 下面用了一个连接池的工具,是 BEA 卖给他们的。我们知道 BEA 的东西都很贵,我们买不起,于是多隆在网上寻寻觅觅,找到一个开源的连接池代理服务 SQLRelay( http://sourceforge.jp/projects/freshmeat_sqlrelay ),这个东西能够提供连接池的功能,多隆对它进行了一些功能改进之后就拿来用了。这样系统的架构就变成了如下的样子:   

数据一开始是放在本地的,DBA 们对 Oracle 做调优的工作,也对 SQL 进行调优。后来数据量变大了,本地存储不行了。买了 NAS(Network Attached Storage:网络附属存储),NetApp 的 NAS 存储作为了数据库的存储设备,加上 Oracle RAC(Real Application Clusters,实时应用集群)来实现负载均衡。七公说这实际上是走了一段弯路,NAS 的 NFS(Network File System)协议传输的延迟很严重,但那时侯不懂。后来采购了 Dell 和 EMC 合作的 SAN 低端存储,性能一下子提升了 10 几倍,这才比较稳定了。再往后来数据量更大了,存储的节点一拆二、二拆四,RAC 又出问题了。这才踏上了购买小型机的道路。在那段不稳定的时间里,七公曾经在机房住了 5 天 5 夜。

替换完数据库,时间到了 2004 年春天,俗话说“春宵一刻值千金”,但这些人的春宵却不太好过了。他们在把数据的连接放在 SQLRelay 之后就噩梦不断,这个代理服务经常会死锁,如同之前的 MySQL 死锁一样。虽然多隆做了很多修改,但当时那个版本内部处理的逻辑不对,问题很多,唯一解决的办法就是“重启”它的服务。这在白天还好,连接上机房的服务器,把进程杀掉,然后开启就可以了,但是最痛苦的是它在晚上也要死掉,于是工程师们不得不 24 小时开着手机,一旦收到“ SQLRelay 进程挂起”的短信,就从春梦中醒来,打开电脑,连上机房,重启服务。后来干脆每天睡觉之前先重启一下。做这事最多的据说是三丰,他现在是淘宝网的总裁。现在我们知道,任何牛B的人物,都有一段苦B的经历。

微博上有人说“好的架构是进化来的,不是设计来的”。的确如此,其实还可以再加上一句“好的功能也是进化来的,不是设计来的”。在架构的进化过程中,业务的进化也非常迅猛。最早的时候,买家打钱给卖家都是通过银行转账汇款,有些骗子收了钱却不发货,这是一个很严重的问题。然后这伙人研究了 PayPal 的支付方式,发现也不能解决问题。后来这几个聪明的脑袋又想到了“担保交易”这种第三方托管资金的办法。于是在 2003 年 10 月,淘宝网上面上线了一个功能,叫做“安全交易”,卖家选择支持这种功能的话,买家会把钱交给淘宝网,等他收到货之后,淘宝网再把钱给卖家。这就是现在的支付宝,在前两天(2012.2.21)年会上,支付宝公布 2011 年的交易笔数已经是 PayPal 的两倍。这个划时代的创新,其实就是在不断的思索过程中的一个灵光乍现。

当时开发“安全交易”功能的是茅十八和他的徒弟苗人凤(茅十八开发到一半去上海读 MBA 去了,苗人凤现在是支付宝的首席业务架构师),开发跟银行网关对接的功能的是多隆。当时多数银行的网站已经支持在线支付了,但多隆告诉我,他们的网关五花八门,用什么技术的都有,必须一家一家去接。而且他们不保证用户付钱了就一定扣款成功、不保证扣款成功了就一定通知淘宝、不保证通知淘宝了就一定能通知到、不保证通知到了就不重复通知。这害苦了苗人凤,他必须每天手工核对账单,对不齐的话就一定是有人的钱找不到地方了,少一分钱都睡不着觉。另外他为了测试这些功能,去杭州所有的银行都办理了一张银行卡。一堆银行卡摆在桌子上,不知道的人还以为这个家伙一定很有钱,其实里面都只是十块八块的。现在我们再一次知道,任何牛B的人物,都必须有一段苦B的经历。

有人说淘宝打败易趣(eBay 中国)是靠免费,其实这只是原因之一。如果说和易趣过招第一招是免费的话,这让用户没有门槛就愿意来,那第二招就是“安全支付”,这让用户放心付款,不必担心被骗。在武侠小说中真正的高手飞花摘叶即可伤人,他们不会局限于一招两招,一旦出手,连绵不绝。而淘宝的第三招就是“旺旺”,让用户在线沟通。其实淘宝旺旺也不是自己生出来的,是从阿里巴巴的“贸易通”复制过来的。从 2004 年 3 月开始,“叮咚、叮咚”这个经典的声音就回荡在所有淘宝买家和卖家的耳边,“亲,包邮不?”,“亲,把零头去掉行不?”,这亲切的砍价声造就了后来的“淘宝体”。有人说中国人就是爱砍价,虽然笔者体会不到砍价成功后有多少成就感,但每次我去菜市场,看到大妈们砍价砍得天昏地暗,那满足的劲头堪比捡到了钱,我就深刻的理解了淘宝旺旺在交易过程中的价值。我猜 eBay 也体会不到砍价的乐趣,他们一直不允许买卖双方在线聊天,收购了 skype 之后也没有用到电子商务中去。

旺旺在推出来没多久,就惹了一个法律方面的麻烦。有个做雪饼的厂家找上门来,说我们侵权了,他们家的雪饼很好吃,牛奶也做得不错,我们都很喜欢。然后我们就在旺旺的前面加了两个字,叫做“淘宝旺旺”。在那个野蛮生长的阶段,其实很多产品都是想到什么就做什么,例如我们还搭建过一个聊天室,但似乎淘宝网不是一个闲聊的地方,这个聊天室门可罗雀,一段时间后就关闭掉了。

SQLRelay 的问题搞得三丰他们很难睡个囫囵觉,那一年开半年会的时候,公司特地给三丰颁了一个奖项,对他表示深切的安慰。但不能总这样啊,于是,2004 年的上半年开始,整个网站就开始了一个脱胎换骨的手术。

三、淘宝技术发展(Java时代:脱胎换骨)

我的师父黄裳@岳旭强曾经说过,“好的架构图充满美感”,一个架构好不好,从审美的角度就能看得出来。后来我看了很多系统的架构,发现这个言论基本成立。那么反观淘宝前面的两个版本的架构,你看哪个比较美?

显然第一个比较好看,后面那个显得头重脚轻,这也注定了它不是一个稳定的版本,只存活了不到半年的时间。2004 年初,SQL Relay 的问题解决不了,数据库必须要用 Oracle,那从哪里动刀?只有换开发语言了。换什么语言好呢?Java。Java 是当时最成熟的网站开发语言,它有比较良好的企业开发框架,被世界上主流的大规模网站普遍采用,另外有 Java 开发经验的人才也比较多,后续维护成本会比较低。  

到 2004 年上半年,淘宝网已经运行了一年的时间,这一年积累了大量的用户,也快速的开发了很多功能,当时这个网站已经很庞大了,而且新的需求还在源源不断的过来。把一个庞大的网站的开发语言换掉,无异于脱胎换骨,在换的过程中还不能拖慢业务的发展,这无异于边换边跑,对时间和技术能力的要求都非常高。做这样的手术,需要请第一流的专家来主刀。现在再考一下读者,如果你在这个创业团队里面,请什么样的人来做这事?我们的答案是请 Sun 的人。没错,就是创造 Java 语言的那家公司,世界上没有比他们更懂 Java 的了。除此之外,还有一个不为人知的原因,……(此处和谐掉 200 字,完整版见 aliway)

这帮 Sun 的工程师的确很强大,在笔者 2004 年底来淘宝的时候,他们还在,有幸跟他们共事了几个月。现在摆在他们面前的问题是用什么办法把一个庞大的网站从 PHP 语言迁移到 Java?而且要求在迁移的过程中,不停止服务,原来系统的 bugfix 和功能改进不受影响。亲,你要是架构师,你怎么做?有人的答案是写一个翻译器,如同把中文翻译成英文一样,自动翻译。我只能说你这个想法太超前了,换个说法就是“too simple, sometimes naive”。当时没有,现在也没有人能做到。他们的大致方案是给业务分模块,一个模块一个模块的替换。如用户模块,老的 member.taobao.com 继续维护,不添加新功能,新的功能先在新的模块上开发,跟老的共用一个数据库,开发完毕之后放到不同的应用集群上,另开个域名 member1.taobao.com,同时替换老的功能,替换一个,把老的模块上的功能关闭一个,逐渐的把用户引导到 member1.taobao.com,等所有功能都替换完毕之后,关闭 member.taobao.com。后来很长时间里面都是在用 member1 这样奇怪的域名,两年后有另外一家互联网公司开始做电子商务了,我们发现他们的域名也叫 member1.xx.com、auction1.xx.com……  

说了开发模式,再说说用到的 Java MVC 框架,当时的 Struts 1.x 是用的比较多的框架,但是用过 WebWork 和 Struts 2 的同学可能知道,Struts 1.x 在多人协作方面有很多致命的弱点,由于没有一个轻量框架作为基础,因此很难扩展,这样架构师对于基础功能和全局功能的控制就很难做到。而阿里巴巴的 18 个创始人之中,有个架构师,在 Jakarta Turbine 的基础上,做了很多扩展,打造了一个阿里巴巴自己用的 MVC 框架 WebX (http://www.openwebx.org/docs/Webx3_Guide_Book.html),这个框架易于扩展,方便组件化开发,它的页面模板支持 JSP 和 Velocity 等、持久层支持 iBATIS 和 Hibernate 等、控制层可以用 EJB 和 Spring(Spring 是后来才有的)。项目组选择了这个强大的框架,这个框架如果当时开源了,也许就没有 WebWork 和 Struts 2 什么事了。另外,当时 Sun 在全世界大力推广他们的 EJB,虽然淘宝的架构师认为这个东东用不到,但他们还是极力坚持。在经历了很多次的技术讨论、争论和争吵之后,这个系统的架构就变成了下图的样子:

Java 应用服务器是 Weblogic,MVC 框架是 WebX、控制层用了 EJB、持久层是 iBATIS,另外为了缓解数据库的压力,商品查询和店铺查询放在搜索引擎上面。这个架构图是不是好看了一点了,亲?  

这帮 Sun 的工程师开发完淘宝的网站之后,又做了一个很牛的网站,叫“支付宝”。  

其实在任何时候,开发语言本身都不是系统的瓶颈,业务带来的压力更多的是压到了数据和存储上。上面一篇也说到,MySQL 撑不住了之后换 Oracle,Oracle 的存储一开始在本机上,后来在 NAS 上,NAS 撑不住了用 EMC 的 SAN 存储,再然后 Oracle 的 RAC 撑不住了,数据的存储方面就不得不考虑使用小型机了。在 2004 年的夏天,DBA 七公、测试工程师郭芙和架构师行癫,踏上了去北京测试小型机的道路。他们带着小型机回来的时候,我们像欢迎领袖一样的欢迎他们,因为那个是我们最值钱的设备了,价格表上的数字吓死人。小型机买回来之后我们争相合影,然后 Oracle 就跑在了小型机上,存储方面从 EMC 低端 cx 存储到 Sun oem hds 高端存储,再到 EMC dmx 高端存储,一级一级的往上跳。  

到现在为止,我们已经用上了 IBM 的小型机、Oracle 的数据库、EMC 的存储,这些东西都是很贵的,那些年可以说是花钱如流水啊。有人说过“钱能解决的问题,就不是问题”,但随着淘宝网的发展,在不久以后,钱已经解决不了我们的问题了。花钱买豪华的配置,也许能支持 1 亿 PV 的网站,但淘宝网的发展实在是太快了,到了 10 亿怎么办?到了百亿怎么办?在 N 年以后,我们不得不创造技术,解决这些只有世界顶尖的网站才会遇到的问题。后来我们在开源软件的基础上进行自主研发,一步一步的把 IOE(IBM 小型机、Oracle、EMC 存储)这几个“神器”都去掉了。这就如同在《西游记》里面,妖怪们拿到神仙的兵器会非常厉害,连猴子都能够打败,但最牛的神仙是不用这些神器的,他们挥一挥衣袖、翻一下手掌就威力无比。去 IOE 这一部分会在最后一个章节里面讲,这里先埋个千里伏笔。  

欲知后事如何,且听下回分解。

淘宝技术发展(Java时代:坚若磐石)

已经有读者在迫不及待的问怎么去掉了 IOE,别急,在去掉 IOE 之前还有很长的路要走。行癫他们买回来小型机之后,我们用上了 Oracle,七公带着一帮 DBA 在优化 SQL 和存储,行癫带着几个架构师在研究数据库的扩展性。Oracle 本身是一个封闭的系统,用 Oracle 怎么做扩展?用现在一个时髦的说法就是做“分库分表”。

我们知道一台 Oracle 的处理能力是有上限的,它的连接池有数量限制,查询速度跟容量成反比。简单的说,在数据量上亿、查询量上亿的时候,就到它的极限了。要突破这种极限,最简单的方式就是多用几个 Oracle 数据库。但一个封闭的系统做扩展,不像分布式系统那样轻松。我们把用户的信息按照 ID 来放到两个数据库里面(DB1/DB2),把商品的信息跟着卖家放在两个对应的数据库里面,把商品类目等通用信息放在第三个库里面(DBcommon)。这么做的目的除了增加了数据库的容量之外,还有一个就是做容灾,万一一个数据库挂了,整个网站上还有一半的数据能操作。  

数据库这么分了之后,应用程序有麻烦了,如果我是一个买家,买的商品有 DB1 的也有 DB2 的,要查看“我已买到的宝贝”的时候,应用程序怎么办?必须到两个数据库里面分别查询出来对应的商品。要按时间排序怎么办?两个库里面“我已买到的宝贝”全部查出来在应用程序里面做合并。还有分页怎么处理?关键字查询怎么处理?这些东西交给程序员来做的话会很悲催,于是行癫在淘宝的第一个架构上的作品就来解决了这个问题,他写了一个数据库路由的框架 DBRoute,这个框架在淘宝的 Oracle 时代一直在使用。后来随着业务的发展,这种分库的第二个目的 —— 容灾的效果就没有达到。像评价、投诉、举报、收藏、我的淘宝等很多地方,都必须同时连接 DB1 和 DB2,哪个库挂了都会导致整个网站挂掉。  

上一篇说过,采用 EJB 其实是和 Sun 的工程师妥协的结果,在他们走了之后,EJB 也逐渐被冷落了下来。在 2005、2006年的时候,Spring 大放异彩,正好利用 Spring 的反射(IoC)模式替代了 EJB 的工厂模式,给整个系统精简了很多代码。  

上一篇还说过,为了减少数据库的压力,提高搜索的效率,我们引入了搜索引擎。随着数据量的继续增长,到了 2005 年,商品数有 1663 万,PV 有 8931 万,注册会员有 1390 万,这给数据和存储带来的压力依然山大,数据量大,性能就慢。亲,还有什么办法能提升系统的性能?一定还有招数可以用,这就是缓存和 CDN(内容分发网络)。  

你可以想象,九千万的访问量,有多少是在商品详情页面?访问这个页面的时候,数据全都是只读的(全部从数据库里面读出来,不写入数据库),如果把这些读操作从数据库里面移到内存里,数据库将会多么的感激涕零。在那个时候我们的架构师多隆大神,找到了一个基于 Berkeley DB 的开源的缓存系统,把很多不太变动的只读信息放了进去。其实最初这个缓存系统还比较弱,我们并没有把整个商品详情都放在里面,一开始把卖家的信息放里面,然后把商品属性放里面,商品详情这个字段太大,放进去受不了。说到商品详情,这个字段比较恐怖,有人统计过,淘宝商品详情打印出来平均有 5 米长,在系统里面其实放在哪里都不招人待见。笔者清楚的记得,我来淘宝之后担任项目经理做的第一个项目就是把商品详情从商品表里面给移出来。这个字段太大了,查询商品信息的时候很多都不需要查看详情,它跟商品的价格、运费这些放在一个表里面,拖慢了整个表的查询速度。在 2005 年的时候,我把商品详情放在数据库的另外一张表里面,再往后这个大字段被从数据库里面请了出来,这也让数据库再一次感激涕零。  

到现在为止,整个商品详情的页面都在缓存里面了,眼尖的读者可能会发现现在的商品详情不全是“只读”的信息了,这个页面上有个信息叫“浏览量”,这个数字每刷新一次页面就要“写入”数据库一次,这种高频度实时更新的数据能用缓存吗?如果不用缓存,一天几十亿的写入,数据库会怎么样?一定会挂掉。那怎么办?亲……先不回答你(下图不是广告,让你看看浏览量这个数据在哪里)

CDN 这个工作相对比较独立,跟别的系统一样,一开始我们也是采用的商用系统。后来随着流量的增加,商用的系统已经撑不住了,LVS 的创始人章文嵩博士带人搭建了淘宝自己的 CDN 网络。在本文的引言中我说过淘宝的 CDN 系统支撑了 800Gbps 以上的流量,作为对比我们可以看一下国内专业做 CDN 的上市公司 ChinaCache 的介绍 —— “ChinaCache……是中国第一的专业 CDN 服务提供商,向客户提供全方位网络内容快速分布解决方案。作为首家获信产部许可的 CDN 服务提供商,目前 ChinaCache 在全国 50 多个大中城市拥有近 300 个节点,全网处理能力超过 500Gbps,其 CDN 网络覆盖中国电信、中国网通、中国移动、中国联通、中国铁通和中国教育科研网等各大运营商。” —— 这样你可以看得出淘宝在 CDN 上面的实力,这在全世界都是数一数二的。另外因为 CDN 需要大量的服务器,要消耗很多能源(消耗多少?在前两年我们算过一笔帐,淘宝上产生一个交易,消耗的电足以煮熟 4 个鸡蛋)。这两年章文嵩的团队又在研究低功耗的服务器,在绿色计算领域也做了很多开创性的工作。淘宝 CDN 的发展需要专门一个章节来讲,想先睹为快的可以看一下笔者对章文嵩的专访

回想起刚用缓存那段时间,笔者还是个小菜鸟,有一个经典的错误常常犯,就是数据库的内容更新的时候,忘记通知缓存系统,结果在测试的时候就发现我改过的数据怎么在页面上没变化呢。后来做了一些页面上的代码,修改 CSS 和 JS 的时候,用户本地缓存的信息没有更新,页面上也会乱掉,在论坛上被人说的时候,我告诉他用 Ctrl+F5 刷新页面,然后赶紧修改脚本文件的名称,重新发布页面。学会用 Ctrl+F5 的会员对我佩服的五体投地,我却惭愧的无地自容。 

有些技术的发展是顺其自然的,有些却是突如其来的。到 2007 年的时候,我们已经有几百台应用服务器了,这上面的 Java 应用服务器是 WebLogic,而 WebLogic 是非常贵的,比这些服务器本身都贵。有一段时间多隆研究了一下 JBoss,说我们换掉 WebLogic 吧,于是又省下了不少银两。那一年,老马举办了第一届的“网侠大会”,会上来的大侠中有一位是上文提到的章文嵩,还有一位曾经在 JBoss 团队工作,我们也把这位大侠留下了,这样我们用起 JBoss 更加有底气了。  

这些杂七杂八的修改,我们对数据分库、放弃 EJB、引入 Spring、加入缓存、加入 CDN、采用开源的 JBoss,看起来没有章法可循,其实都是围绕着提高容量、提高性能、节约成本来做的,由于这些不算大的版本变迁,我们姑且叫它 2.1 版吧,这个版本从构图上来看有 3 只脚,是不是稳定了很多?

架构图如下: 

五、淘宝技术发展(Java时代:创造技术-TFS)

在讲淘宝文件系统 TFS 之前,先回顾一下上面几个版本。1.0 版的 PHP 系统运行了将近一年的时间(2003.05~2004.01);后来数据库变成 Oracle 之后(2004.01~2004.05,叫 1.1 版本吧),不到半年就把开发语言转换为 Java 系统了(2004.02~2005.03,叫2.0版本);进行分库、加入缓存、CDN之后我们叫它 2.1 版本(2004.10~2007.01)。这中间有些时间的重合,因为很多架构的演化并没有明显的时间点,它是逐步进化而来的。

在描述 2.1 版本的时候我写的副标题是“坚若磐石”,这个“坚若磐石”是因为这个版本终于稳定下来了,在这个版本的系统上,淘宝网运行了两年多的时间。这期间有很多优秀的人才加入,也开发了很多优秀的产品,例如支付宝认证系统、招财进宝项目、淘宝旅行、淘宝彩票、淘宝论坛等等。甚至在团购网站风起云涌之前,淘宝网在 2006 年就推出了团购的功能,只是淘宝网最初的团购功能是买家发起的,达到卖家指定的数量之后,享受比一口价更低的价格,这个功能看起来是结合了淘宝一口价和荷兰拍的另一种交易模式,但不幸没有支撑下去。

在这些产品和功能的最底层,其实还是商品的管理和交易的管理这两大功能。这两大功能在 2.1 版本里面都有很大的变化。商品的管理起初是要求卖家选择 7 天到期还是 14 天到期,到期之后就要下架,必须重新发布才能上架,上架之后就变成了新的商品信息(ID变过了)。另外如果这个期间内成交了,之后再有新货,必须发布一个新的商品信息。这么做有几个原因,一是参照拍卖商品的时间设置,要在某日期前结束挂牌;二是搜索引擎不知道同样的商品哪个排前面,那就把挂牌时间长的排前面,这样就必须在某个时间把老的商品下架掉,不然它老排在前面;第三是成交信息和商品 ID 关联,这个商品如果多次编辑还是同一个 ID 的话,成交记录里面的商品信息会变来变去;还有一个不为人知的原因,我们的存储有限,不能让所有的商品老存放在主库里面。这种处理方式简单粗暴,但还算是公平。不过这样很多需求都无法满足,例如同样的商品,我上一次销售的时候很多好评都没法在下一个商品上体现出来;再例如我买过的商品结束后只看到交易的信息,不知道卖家还有没有再卖了。后来基于这些需求,我们在 2006 年下半年把商品和交易拆开。一个商家的一种商品有个唯一的 ID,上下架都是同一个商品。那么如果卖家改价格、库存什么的话,已成交的信息怎么处理?那就在买家每交易一次的时候,都记录下商品的快照信息,有多少次交易就有多少个快照。这样买卖双方比较爽了,给系统带来了什么?存储的成本大幅度上升了!

存储的成本高到什么程度呢?数据库方面提到过用了 IOE,一套下来就是千万级别的,那几套下来就是⋯⋯。另外淘宝网还有很多文件需要存储,我们有哪些文件呢?最主要的就是图片、商品描述、交易快照,一个商品要包含几张图片和一长串的描述信息,而每一张图片都要生成几张规格不同的缩略图。在 2010 年,淘宝网的后端系统上保存着 286 亿个图片文件。图片在交易系统中非常重要,俗话说“一张好图胜千言”、“无图无真相”,淘宝网的商品照片,尤其是热门商品,图片的访问流量是非常大的。淘宝网整体流量中,图片的访问流量要占到 90% 以上。且这些图片平均大小为 17.45 KB,小于 8K 的图片占整体图片数量 61%,占整体系统容量的 11%。这么多的图片数据、这么大的访问流量,给淘宝网的系统带来了巨大的挑战。众所周知,对于大多数系统来说,最头疼的就是大规模的小文件存储与读取,因为磁头需要频繁的寻道和换道,因此在读取上容易带来较长的延时。在大量高并发访问量的情况下,简直就是系统的噩梦。我们该怎么办?

同样的套路,在某个规模以下,采用现有的商业解决方案,达到某种规模之后,商业的解决方案无法满足,只有自己创造解决方案了。对于淘宝的图片存储来说,转折点在 2007 年。这之前,一直采用的商用存储系统,应用 NetApp 公司的文件存储系统。随着淘宝网的图片文件数量以每年 2 倍(即原来 3 倍)的速度增长,淘宝网后端 NetApp 公司的存储系统也从低端到高端不断迁移,直至 2006 年,即使是 NetApp 公司最高端的产品也不能满足淘宝网存储的要求。从 2006 年开始,淘宝网决定自己开发一套针对海量小文件存储的文件系统,用于解决自身图片存储的难题。这标志着淘宝网从使用技术到了创造技术的阶段。

2007年之前的图片存储架构如下图: 

章文嵩博士总结了几点商用存储系统的局限和不足:

首先是商用的存储系统没有对小文件存储和读取的环境进行有针对性的优化;其次,文件数量大,网络存储设备无法支撑;另外,整个系统所连接的服务器也越来越多,网络连接数已经到达了网络存储设备的极限。此外,商用存储系统扩容成本高,10T的存储容量需要几百万,而且存在单点故障,容灾和安全性无法得到很好的保证。

谈到在商用系统和自主研发之间的经济效益对比,章文嵩博士列举了以下几点经验:

  1. 商用软件很难满足大规模系统的应用需求,无论存储还是 CDN 还是负载均衡,因为在厂商实验室端,很难实现如此大的数据规模测试。
  2. 研发过程中,将开源和自主开发相结合,会有更好的可控性,系统出问题了,完全可以从底层解决问题,系统扩展性也更高。
  3. 在一定规模效应基础上,研发的投入都是值得的。上图是一个自主研发和购买商用系统的投入产出比对比,实际上,在上图的交叉点左边,购买商用系统都是更加实际和经济性更好的选择,只有在规模超过交叉点的情况下,自主研发才能收到较好的经济效果。实际上,规模化达到如此程度的公司其实并不多,不过淘宝网已经远远超过了交叉点。
  4. 自主研发的系统可在软件和硬件多个层次不断的优化。

历史总是惊人的巧合,在我们准备研发文件存储系统的时候,Google 走在了前面,2007 年他们公布了 GFS( Google File System )的设计论文,这给我们带来了很多借鉴的思路。随后我们开发出了适合淘宝使用的图片存储系统TFS(Taobao File System)。3年之后,我们发现历史的巧合比我们想象中还要神奇,几乎跟我们同时,中国的另外一家互联网公司也开发了他们的文件存储系统,甚至取的名字都一样 —— TFS,太神奇了!(猜猜是哪家?)

2007 年 6 月,TFS 正式上线运营。在生产环境中应用的集群规模达到了 200 台 PC Server(146G*6 SAS 15K Raid5),文件数量达到上亿级别;系统部署存储容量:140TB;实际使用存储容量: 50TB;单台支持随机IOPS200+,流量 3MBps。

要讲 TFS 的系统架构,首先要描述清楚业务需求,淘宝对图片存储的需求大概可以描述如下:

文件比较小;并发量高;读操作远大于写操作;访问随机;没有文件修改的操作;要求存储成本低;能容灾能备份。应对这种需求,显然要用分布式存储系统;由于文件大小比较统一,可以采用专有文件系统;并发量高,读写随机性强,需要更少的 IO 操作;考虑到成本和备份,需要用廉价的存储设备;考虑到容灾,需要能平滑扩容。

参照 GFS 并做了适度的优化之后,TFS 1.0 版的架构图如下:

从上面架构图上看:集群由一对 Name Server 和多台 Data Serve r构成,Name Server 的两台服务器互为双机,就是集群文件系统中管理节点的概念。

在这个架构中:

  • 每个 Data Server 运行在一台普通的 Linux 主机上
  • 以 block 文件的形式存放数据文件(一般64M一个block )
  • block 存多份保证数据安全
  • 利用 ext3 文件系统存放数据文件
  • 磁盘 raid5 做数据冗余
  • 文件名内置元数据信息,用户自己保存 TFS 文件名与实际文件的对照关系 – 使得元数据量特别小。

淘宝 TFS 文件系统在核心设计上最大的取巧的地方就在,传统的集群系统里面元数据只有 1 份,通常由管理节点来管理,因而很容易成为瓶颈。而对于淘宝网的用户来说,图片文件究竟用什么名字来保存实际上用户并不关心,因此TFS 在设计规划上考虑在图片的保存文件名上暗藏了一些元数据信息,例如图片的大小、时间、访问频次等等信息,包括所在的逻辑块号。而在元数据上,实际上保存的信息很少,因此元数据结构非常简单。仅仅只需要一个 fileID,能够准确定位文件在什么地方。

由于大量的文件信息都隐藏在文件名中,整个系统完全抛弃了传统的目录树结构,因为目录树开销最大。拿掉后,整个集群的高可扩展性极大提高。实际上,这一设计理念和目前业界的“对象存储”较为类似,淘宝网 TFS 文件系统已经更新到 1.3 版本,在生产系统的性能已经得到验证,且不断得到了完善和优化,淘宝网目前在对象存储领域的研究已经走在前列。

在 TFS 上线之前,淘宝网每个商品只允许上传一张图片,大小限定在 120K 之内,在商品详情里面的图片必须使用外站的服务。那时侯发布一件商品确实非常麻烦,笔者曾经想卖一台二手电脑,先把照片上传到 Google 相册,在发布到淘宝网之后发现 Google 相册被墙了,我的图片别人看不到,当时郁闷的不行。TFS 上线后,商品展示图片开放到 5 张,商品描述里面的图片也可以使用淘宝的图片服务,到现在为止,淘宝网给每个用户提供了 1G 的图片空间,这下大家都满足了。技术和业务就是这么互相用力的推动着,业务满足不了的时候,技术必须创新,技术创新之后,业务有了更大的发展空间。

1.3 版本的架构见阿里味(阿里巴巴内网)⋯⋯

六、淘宝技术发展(分布式时代:服务化)

在系统发展的过程中,架构师的眼光至关重要,作为程序员,把功能实现即可,但作为架构师,要考虑系统的扩展性、重用性,这种敏锐的感觉,有人说是一种代码洁癖。淘宝早期有几个架构师具备了这种感觉。一指开发的 Webx 是一个扩展性很强的框架,行癫在这个框架上插入了数据分库路由的模块、session 框架等等。在做淘宝后台系统的时候,同样需要这几个模块,行癫指导我把这些模块单独打成了 jar 包。另外在做淘宝机票、彩票系统的时候,页面端也有很多东西需要复用,最直观的是页头和页脚,一开始我们每个系统里面复制了一份过去,但奇妙的是,那段时间页脚要经常修改,例如把“雅虎中国”改成“中国雅虎”,过一段时间又加了一个“口碑网”,再过一段时间变成了“雅虎口碑”,最后又变成了“中国雅虎”,每个系统都改一遍,折腾啊。后来我就把这部分 velocity 模版单独拿出来了,做成了公用的模块。  

上面这些都是比较小的复用模块,到 2006 年我们做了一个商品类目属性的改造,在类目里面引入属性的概念。项目的代号叫做“泰山”,如同它的名字,这是一个举足轻重的项目,这个改变是一个划时代的创新。在这之前的三年时间内,商品的分类都是按照树状的一级一级的节点来分的,随着商品数量的增长,类目也变得越来越深,越来越复杂,这带给买家的就是查找一件商品要逐级类目点开,找商品之前要懂商品的分类。而淘宝运营部门管理类目的小二也发现一个很严重的问题 —— 例如男装里面有T恤、T恤下面有耐克、耐克有纯棉的,女装里面也有T恤、T恤下面还是有耐克、耐克下面依然有纯棉的,那是先分男女装再分款式再分品牌再分材质呢?还是先分品牌再分款式再分材质再分男女呢?晕倒了。这时候,一位大侠出来了 —— 一灯,他说品牌、款式、材质这种东东可以叫做“属性”,属性是类似 tag 的一个概念,与类目相比更加离散,更加灵活,这样也缩减了类目的深度。这个思想的提出,一举解决了分类的难题!从系统的角度来看,我们建立了“属性”这样一个数据结构,由于除了类目的子节点有属性,父节点也有可能有属性,于是类目属性合起来也是一个结构化的数据对象。这个做出来之后我们把它独立出来作为一个服务,叫做 catserver(category server)。跟类目属性密切关联的商品搜索功能,独立出来,叫做 hesper(金星),catserver 和 hesper 供淘宝的前后台系统调用。  

现在淘宝的商品类目属性已经是地球上最大的了,几乎没有什么类目的商品在淘宝上找不到(除了违禁的),但最初类目属性改造完之后,我们很缺属性数据,尤其是数码类的最缺。那从哪里弄这些数据呢亲?我们跟“中关村在线”合作,拿到了很多数据,那个时候,很多商品属性信息的后边标注着:“来自中关村在线”。有了类目属性,给运营的工作带来很大的便利,我们知道淘宝的运营主要就是类目的运营,什么季节推什么商品,都要在类目属性上面做调整,让买家更容易找到。例如夏天我要用户在女装一级类目下就标出来材质是不是蕾丝的、是不是纯棉的,冬天却要把羽绒衣调到女装一级类目下,流行什么就要把什么商品往更高级的类目调整。这样类目和属性要经常调整,随之而来的问题就显现了 —— 调整到哪个类目,那类商品的卖家就要编辑一次自己的商品,随着商品量的增长,卖家的工作量越来越大,然后我们就发现卖家受不了啦。到了 2008 年,我们研究了超市里面前后台商品的分类,发现超市前台商品可以随季节和关联来调整摆放场景(例如著名的啤酒和尿布的关联),后台仓库里面要按照自然类目来存储,二者密切关联却又相互分开。然后我们就把前后台类目分开了,这样卖家发布商品选择的是自然类目和属性,淘宝前台展示的是根据运营需要而摆放的商品的类目和属性。改造后的类目属性服务取名叫做 forest(森林,跟类目属性有点神似。catserver 还在,提供卖家授权、品牌服务、关键词等相关的服务)。类目属性的服务化,是淘宝在系统服务化方面做的第一个探索。 

虽然个别架构师具备了代码洁癖,但淘宝前台系统的业务量和代码量还是爆炸式的增长了起来。业务方总在后面催,开发人员不够了就继续招人,招来的人根本看不懂原来的业务,只好摸索着在“合适的地方”加一些“合适的代码”,看看运行起来像那么回事,就发布上线了。在这样的恶性循环中,系统越来越臃肿,业务的耦合性越来越高,开发的效率越来越低。借用当时比较流行的一句话“写一段代码,编译一下能通过,半个小时就过去了;编译一下没通过,半天就过去了。”在这种情况下,系统出错的概率也逐步增长,常常是你改了商品相关的某些代码,发现交易出问题了,甚至你改了论坛上的某些代码,旺旺出问题了。这让开发人员苦不堪言,而业务方还认为这帮人干活越来越慢了。  

大概是在 2007 年底的时候,研发部空降了一位从硅谷来的高管,空闻大师。空闻是一位温厚的长者,他告诉我们一切要以稳定为中心,所有影响系统稳定的因素都要解决掉。例如每做一个日常修改,都必须整个系统回归测试一遍;多个日常修改如果放在一个版本里面,要是一个功能没有测试通过,整个系统都不能发布。我们把这个叫做“火车模型”,任何一个乘客没有上车,都不许发车。这样做的最直接后果就是火车一直晚点,新功能上线更慢了,我们能明显的感觉到业务方的不满,空闻的压力肯定非常大。当时我都不理解这种一刀切的做法,为了稳定牺牲了发展的速度,这跟某 Party 的“稳定压倒一切”有什么分别?  

但是到现在回过头来看看,其实我们并没有理解背后的思路。正是在这种要求下,我们不得不开始改变一些东西,例如把回归测试日常化,每天晚上都跑一遍整个系统的回归。还有就是在这种要求下,我们不得不对这个超级复杂的系统做肢解和重构,其中复用性最高的一个模块 —— 用户信息模块开始拆分出来了,我们叫它 UIC(user information center)。在 UIC 里面,它只处理最基础的用户信息操作,例如getUserById、getUserByName等等。  

在另外一个方面,还有两个新兴的业务,也对系统基础功能的拆分提出了要求。在那个时候,我们做了淘宝旅行(trip.taobao.com)和淘宝彩票(caipiao.taobao.com)两个新业务,这两个新业务在商品的展示和交易的流程上都跟主站的业务不一样,机票是按照航班的信息展示的,彩票是按照双色球、数字和足球的赛程来展示的。但用到的会员的功能和交易的功能是跟主站差不多的,当时做的时候就很纠结,在主站里面做的话,会有一大半跟主站无关的东西,重新做一个的话,会有很多重复建设。最终我们决定不再给主站添乱了,就另起炉灶做了两个新的业务系统。从查询商品、购买商品、评价反馈、查看订单这一整个流程都重新写了一套出来。现在在“我的淘宝”里面查看交易记录的时候,还能发现“已买到的宝贝”里面把机票和彩票另外列出来了,他们没有加入到普通的订单里面去。在当时如果已经把会员、交易、商品、评价这些模块拆分出来,就不用什么都重做一遍了。 

到 2008 年初,整个主站系统(有了机票、彩票系统之后,把原来的系统叫做主站)的容量已经到了瓶颈,商品数在一亿以上,PV 在 2.5 亿以上,会员数超过了五千万。这个时候 Oracle 的连接池数量都不够用了,数据库的容量到了极限,上层系统再增加机器也无法继续扩容了,我们只有把底层的基础服务继续拆分,从底层开始扩容,上层才能扩展,这才能容纳以后三五年的增长。

于是那一年我们专门启动了一个更大的项目,把交易这个核心业务模块也拆分出来了。原来的淘宝交易除了跟商品管理耦合在一起,也在支付宝和淘宝之间跳来跳去,跟支付宝耦合在一起,系统复杂,用户体验也很不好。我们把交易的底层业务拆出来叫交易中心TC(trade center),所谓底层业务是例如创建订单、减库存、修改订单状态等原子型的操作;交易的上层业务叫交易管理TM(trade manager),例如拍下一件普通商品要对订单、库存、物流进行操作,拍下虚拟商品不需要对物流进行操作,这些在TM里面完成。这个项目取了一个很没有创意的名字 —— “千岛湖”,这帮开发人员取这个名字的目的是想在开发完毕之后,去千岛湖玩一圈,后来他们如愿以偿了。这个时候还有一个项目也在搞,就是淘宝商城,之前拆分出来的那些基础服务,给商城的快速构建,提供了良好的基础。

类目属性、用户中心、交易中心,随着这些模块逐步的拆分和服务化改造,我们在系统架构方面也积累了不少的经验。到 2008 年底干脆做了一个更大的项目,把淘宝所有的业务都模块化,这是继 2004 年从 LAMP 架构到 Java 架构之后的第二次脱胎换骨。这个项目取了一个很霸气的名字,叫“五彩石”(女娲炼石补天,用的石头)。这个系统重构的工作非常惊险,有人称之为“给一架高速飞行的飞机换发动机”。  

五彩石项目发布之后,这帮工程师去三亚玩了几天。他们把淘宝的系统拆分成了如下架构: 

其中 UIC 和 Forest 上文说过,TC、IC、SC分别是交易中心(Trade Center)、商品中心(Item Center)、店铺中心(Shop Center),这些中心级别的服务只提供原子级的业务逻辑,如根据ID查找商品、创建交易、减少库存等操作。再往上一层是业务系统TM(Trade Manager交易业务)、IM(Item Manager商品业务)、SM(Shop Manager,因为不好听,所以后来改名叫 SS:Shop System,店铺业务)、Detail(商品详情)。  

拆分之后,系统之间的交互关系变得非常复杂,示意图如下:

系统这么拆分的话,好处显而易见,拆分之后每个系统可以单独部署,业务简单,方便扩容;有大量可重用的模块以便于开发新的业务;能够做到专人专事,让技术人员更加专注于某一个领域。这样要解决的问题也很明显,分拆之后,系统之间还是必须要打交道的,越往底层的系统,调用它的客户方越多,这就要求底层的系统必须具有超大规模的容量和非常高的可用性。另外,拆分之后的系统如何通讯?这里需要两种中间件系统,一种是实时调用的中间件(淘宝的HSF,高性能服务框架)、一种是异步消息通知的中间件(淘宝的Notify)。另外还有一个需要解决的问题是用户在A系统登录了,到B系统的时候,用户的登录信息怎么保存?这又涉及到一个 Session 框架。再者,还有一个软件工程方面的问题,这么多层的一套系统,怎么去测试它?


文章来源于网络,原文地址已不详。

阅读全文 »

Jerry Bendy 发布于 12月02, 2015

【分享】PHP中的并发

今天看到一篇讲PHP并发的文章,感觉不错,于是便Copy了下来。原文如下:

周末去北京面了两个公司,认识了几位技术牛人,面试中聊了很多,感觉收获颇丰。认识到了自己的不足之处,也坚定了自己对计算机学习的信心。本文是对其中一道面试题的总结。 面试中有一个问题没有很好的回答出来,题目为:并发3个http请求,只要其中一个请求有结果,就返回,并中断其他两个。

当时考虑的内容有些偏离题目原意, 一直在考虑如何中断http请求,大概是在 client->recv() 之前去判断结果是否已经产生,所以回答的是用 socket 去发送一个 http 请求,把 socket 加入 libevent 循环监听,在callback中判断是否已经得到结果,如果已经得到结果,就直接 return。

后来自己越说越觉得不对,既然已经recv到结果,就不能算是中断http请求。何况自己从来没用过libevent。后来说了还说了两种实现,一个是用 curl_multi_init, 另一个是用golang实现并发。 golang的版本当时忘了close的用法,结果并不太符合题意。

这题没答上来,考官也没为难我。但是心里一直在考虑,直到面试完走到楼下有点明白什么意思了,可能考的是并发,进程线程的应用。所以总结了这篇文章,来讲讲PHP中的并发。 本文大约总结了PHP编程中的五种并发方式,最后的Golang的实现纯属无聊,可以无视。如果有空,会再补充一个libevent的版本。

curl_multi_init

文档中说的是 Allows the processing of multiple cURL handles asynchronously. 确实是异步。这里需要理解的是select这个方法,文档中是这么解释的Blocks until there is activity on any of the curl_multi connections.。了解一下常见的异步模型就应该能理解,select, epoll,都很有名,这里引用一篇非常好的文章,有兴趣看下解释吧。

<?php
// build the individual requests as above, but do not execute them
$ch_1 = curl_init('http://www.baidu.com/');
$ch_2 = curl_init('http://www.baidu.com/');
curl_setopt($ch_1, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch_2, CURLOPT_RETURNTRANSFER, true);

// build the multi-curl handle, adding both $ch
$mh = curl_multi_init();
curl_multi_add_handle($mh, $ch_1);
curl_multi_add_handle($mh, $ch_2);

// execute all queries simultaneously, and continue when all are complete
$running = null;
do {
    curl_multi_exec($mh, $running);
    $ch = curl_multi_select($mh);
    if($ch !== 0){
        $info = curl_multi_info_read($mh);
        if($info){
            var_dump($info);
            $response_1 = curl_multi_getcontent($info['handle']);
            echo "$response_1 \n";
            break;
        }
    }
} while ($running > 0);

//close the handles
curl_multi_remove_handle($mh, $ch_1);
curl_multi_remove_handle($mh, $ch_2);
curl_multi_close($mh);

这里我设置的是,select得到结果,就退出循环,并且删除 curl resource, 从而达到取消http请求的目的。

swoole_client

swoole_client提供了异步模式,我竟然把这个忘了。这里的sleep方法需要swoole版本大于等于1.7.21, 我还没升到这个版本,所以直接exit也可以。

<?php
$client = new swoole_client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_ASYNC); //设置事件回调函数
$client->on("connect", function($cli) {
    $req = "GET / HTTP/1.1\r\n
    Host: www.baidu.com\r\n
    Connection: keep-alive\r\n
    Cache-Control: no-cache\r\n
    Pragma: no-cache\r\n\r\n";

    for ($i=0; $i < 3; $i++) { $cli->send($req);
    }
});
$client->on("receive", function($cli, $data){
    echo "Received: ".$data."\n";
    exit(0);
    $cli->sleep(); // swoole >= 1.7.21
});
$client->on("error", function($cli){
    echo "Connect failed\n";
});
$client->on("close", function($cli){
    echo "Connection close\n";
});
//发起网络连接
$client->connect('183.207.95.145', 80, 1);

process

哎,竟然忘了 swoole_process, 这里就不用 pcntl 模块了。但是写完发现,这其实也不算是中断请求,而是哪个先到读哪个,忽视后面的返回值。

<?php

$workers = [];
$worker_num = 3;//创建的进程数
$finished = false;
$lock = new swoole_lock(SWOOLE_MUTEX);

for($i=0;$i<$worker_num ; $i++){ $process = new swoole_process('process'); //$process->useQueue();
    $pid = $process->start();
    $workers[$pid] = $process;
}

foreach($workers as $pid => $process){
    //子进程也会包含此事件
    swoole_event_add($process->pipe, function ($pipe) use($process, $lock, &amp;$finished) {
        $lock->lock();
        if(!$finished){
            $finished = true;
            $data = $process->read();
            echo "RECV: " . $data.PHP_EOL;
        }
        $lock->unlock();
    });
}

function process(swoole_process $process){
    $response = 'http response';
    $process->write($response);
    echo $process->pid,"\t",$process->callback .PHP_EOL;
}

for($i = 0; $i < $worker_num; $i++) {
    $ret = swoole_process::wait();
    $pid = $ret['pid'];
    echo "Worker Exit, PID=".$pid.PHP_EOL;
}

pthreads

编译pthreads模块时,提示php编译时必须打开ZTS, 所以貌似必须 thread safe 版本才能使用. wamp中多php正好是TS的,直接下了个dll, 文档中的说明复制到对应目录,就在win下测试了。 还没完全理解,查到文章说 php 的 pthreads 和 POSIX pthreads是完全不一样的。代码有些烂,还需要多看看文档,体会一下。

<?php
class Foo extends Stackable {
    public $url;
    public $response = null;

    public function __construct(){
        $this->url = 'http://www.baidu.com';
    }
    public function run(){}
}

class Process extends Worker {
    private $text = "";
    public function __construct($text,$object){
        $this->text = $text;
        $this->object = $object;
    }
    public function run(){
        while (is_null($this->object->response)){
            print " Thread {$this->text} is running\n";
            $this->object->response = 'http response';
            sleep(1);
        }
    }
}

$foo = new Foo();

$a = new Process("A",$foo);
$a->start();

$b = new Process("B",$foo);
$b->start();

echo $foo->response;

yield

yield生成的generator,可以中断函数,并用send向 generator 发送消息。 稍后补充协程的版本。还在学习中。

Golang

用Go实现比较简单, 回家后查了查 close,处理一下 panic就ok了。代码如下:

package main

import (
    "fmt"
)

func main() {
    var result chan string = make(chan string, 1)
    for index := 0;  index< 3; index++ {
        go doRequest(result)
    }

    res, ok := <-result
    if ok {
        fmt.Println("received ", res)
    }

}

func doRequest(result chan string)  {
    response := "http response"
    defer func() {
        if x := recover(); x != nil {
            fmt.Println("Unable to send: %v", x)
        }
    }()
    result <- response
    close(result)
}

上面的几个方法,除了 curl_multi_* 貌似符合题意外(不确定,要看下源码),其他的方法都没有中断请求后recv()的操作, 如果得到response后还有后续操作,那么是有用的,否则并没有什么意义。想想可能是PHP操作粒度太大, 猜测用 C/C++ 应该能解决问题。

写的时候没有注意到一个问题,有些方式是返回值,有些直接打印了,这样不好,应该统一使用返回值得到请求结果。能力有限,先这样吧。

最后要做个广告,计蒜客是一家致力于计算机科学高端教育的公司,如果你对编程或者计算机底层有兴趣,不妨去他们网站学习学习。 同时,公司也一直在招人,如果你对自己的能力有信心,可以去试试。公司非常自由开放,90后为主。牛人也有不少,ACM世界冠军,知乎大牛。 公司主做教育,内部学习资料必须给力,我只看到了一些关于操作系统的测试题,涉及到的知识面很广,可见公司平均技术能力有多厉害。

如果文章中有疏漏,错误,还请大神们不吝指出,帮助菜鸟进步,谢谢。

原文地址:http://segmentfault.com/a/1190000004069411

阅读全文 »