2014 年のダブル トゥエルブの終わりにタオバオのホームページを引き継いでから、ほぼ 1 年半が経ちました。ホームページ上の関連業務の引き継ぎは、つい最近完了しました。 2 回の改訂と PHP から Node への 1 回の移行を経て、まだ多くの感想が残っています。それを以下に共有します。
淘宝網のホームページは淘宝網の顔であり、ほぼすべての淘宝網ビジネスへの入り口を担っています。トラフィックの単位は 1 億です。近年、無線端末の台頭により、ビジネスの中心が無線端末に移り始めており(現状では移行とは言えず、基本的に無線です)、タオバオのPCホームページのトラフィックも減少しており、それでも、1 日の平均 PV は依然としてかなり高いです。
タオバオのホームページは常に内部プラットフォームとテクノロジーの実験場であり、常に変化しています。最新のフレームワークとシステムはタオバオのホームページで試験的に導入されます。促進する必要のあるアップグレードや最適化策がタオバオのホームページで開始され、良好なデータと安定性が得られた場合、他の企業はなぜそれを使用しないのでしょうか。実験と反復についてはどうですか?同時に、昨年タオバオのフロントエンドに取り組んだテクニカル アーキテクチャ チームは、当然ながら率先していくつかの実験的なコンテンツをビジネスにプッシュしました。
タオバオのサイト ページには、ホームページ、他のチャンネル ページ、イベント ページなどが含まれます。これらのページは、タオバオのフロントエンドですべてが 1 行ずつコード化されているわけではありません。非常に多くの業務があり、2 倍の人数でも忙しいでしょう。この方法。実際、ほとんどのページは内部構築プラットフォーム (操作またはモジュール構築によるフロントエンド) に基づいて構築されており、フロントエンドの焦点はプラットフォーム自体の構築と、その汎用性と再利用率の保証にあります。もちろん、モジュール、そしてエンジニアリング関連のものも。
構築プラットフォームを使用して構築されたページの場合、フロントエンドはページを構成するアトミック モジュールの開発のみを考慮する必要があります。全体的なレンダリングは、構築プラットフォームによって提供される統合スクリプトに完全に責任があります。タオバオのホームページでは、膨大な数のページ モジュールと、部門間およびチーム間のコミュニケーションが少量であることを考慮して、レンダリング モデルが若干異なります。
背景で述べたように、タオバオのホームページは内部構築プラットフォームに依存しており、その変更は構築システムの変更に自然に伴います。
私がタオバオのホームページを引き継いで間もなく、年次改訂が行われました。当時、それはまだ PHP 環境で実行されていました。ここで説明する必要があるのは、タオバオのホームページ上のすべてのコードはフロントエンドによって完全に制御されており、フロントエンドはデータベースを直接扱わず、そのデータソースは2つの部分に分かれているということです。
1 つ目は、操作によって入力されたデータです。 フロントエンドディギングの形式を使用して、オペレーターが入力データを取得するためのピットを予約します。次のようなものです (擬似コード):
<?php $info = Person('name:String:姓名,age:Number:年龄', '个人信息坑位填写');?><div><?php $info.forEach(index) { ?> Name: <?= info[index].name ?>, Age: <?= info[index].age ?><?php } ?></div>
上記のコードは、情報フィールドに対応する PHP テンプレートとフォームピットを生成します。このプロセスは「穴を掘る」と呼ばれます。
オペレーターがこれらの穴を埋めると、この PHP テンプレートに対応するデータが生成され、最終的に完全な HTML フラグメントにレンダリングされます (リアルタイム レンダリング)。
.├── data.json # 运营数据的来源└── index.php # 装载运营数据的 PHP 模板
これは、古いバージョンのビルディング システムでサブモジュールが構築される方法です。非常に簡単に説明しましたが、プラットフォームとしては、データの順序制御、計画的リリース、ロールバック機構、フィルタリング機構、データの同期、データの更新、バージョン管理、権限管理など、考慮すべきことがたくさんあります。他のシステムへの参照など。
2 つ目は、バックエンドまたはパーソナライゼーション プラットフォームによって提供されるデータです。 さまざまなビジネスにはさまざまな需要があります。一部の企業は独自のバックエンドを持ち、自社で生成したデータを使用する必要があります。また、ユーザーが異なるコンテンツを閲覧することを期待し、アルゴリズムに接続されることを期待している企業もあります。投資促進データ、および一部の企業はデータプールからフィルタリングされたデータの使用を期待しています... つまり、タオバオのホームページはさまざまなシステムに接続する必要があり、多くのインターフェイスがあります。動的データソースの統合については後述します。
そして、これらのシステムに対応するドメイン名が異なるため、必然的に JSONP 形式が第一の選択肢になります。ただし、広告などの一部の特殊なシステムでは、そのレンダリングは単純な JSONP リクエストではなく、JS のロードやレンダリング制御の引き渡しなど、広告レンダリング プロセス全体に介入する場合もあります。
データのソースとサブモジュールの構造は上で紹介しましたが、ページ全体はどのように構成されているのでしょうか?モジュールの構築には 2 つのタイプがあり、1 つはビジュアル構築で、開発したモジュール (またはモジュール ライブラリから選択したモジュール) をコンテナにドラッグ アンド ドロップしてページを形成します。上の写真はあくまでモデルですが、ページレイアウト、多端末対応、モジュールの一時的な非表示、位置調整、スキンの選択、モジュールの複製など、システムとしては考慮すべき点がたくさんあります。
は、次のソース コード (疑似コード) を通じてビルドすることもできます:
<body> <?= loadModule(Mod1ID) ?> <?= loadModule(Mod2ID) ?> <?= loadModule(Mod3ID, 'lazyload') ?> <?= loadModule(Mod4ID, 'lazyload') ?> <?= loadModule(Mod5ID, 'lazyload') ?></body>
通过模块 id 将模块引入,并且添加一些类似 lazyload 的标记,方便控制渲染节奏和数据入口。源码搭建和模块搭建的区别在于,前者更易于控制模块的结构以及模块的渲染顺序。
首页面对一大堆接口和平台,对接几十个业务方,接口是个很大的问题,由于后台系统的差异,基本没有办法统一数据源的格式,一旦运营哪天心血来潮要换一个他自己觉得用的更爽的或者数据更好的系统,前后端估计又得沟通和对接几次。所以出现了下面这张图:
平台具备数据源接入的能力,也就是说我们挖的坑不仅仅可以让运营填数据,还可以从各种数据源中直接导入数据,当然,这里需要进行一次数据字段的映射转换。后端提供的接口是这样的:
{ "data": [{ "item_name": "name", "item_url": "http://xxx", "item_pic": "http://xxx" }]}
前端约定的接口形式是:
{ "info": [{ "name": "name", "url": "http://xxx" }]}
那么系统必须提供这种映射的绑定策略:
info/name -> data/item_nameinfo/url -> data/item_url
绑定之后,数据既可以同步输出,也可以异步输出,这些都是平台提供的能力。这个方案基本上解决了后端系统/接口变化的问题,并且减少了前后端之间的沟通成本。
不过这里需要注意的是,虽然页面上的接口都通过平台统一梳理了一次,这也意味着,页面所有的请求会先流经平台,然后分发到各个后端,平台的抗压能力要求很高。
淘宝首页日均请求的这个量级,不可能是十几二十台台服务器抗得住的,支撑它必须有一个服务集群。
每一个 CDN 节点上都具备 PHP 渲染的能力,当页面发布时,我们把该页面所有的模块和数据同步到全部 CDN 节点上,基本模式大概就是如此了。看起来还挺不错,但是经过一段时间的运维,很多安全、性能问题都慢慢浮现出来了:
性能问题。 每个 PHP 页面包含多个子模块,而子模块也有可能引用了其他的子模块,PHP 的 include 操作是存在消耗的,每一次引用都是一次磁盘 IO,一个渲染节点上跑了成千上万个类似淘宝首页的 PHP 页面,并发一高其效率可想而知。
推送机制问题。 文件同步(图中的 sync 动作)是一种比较恶心的机制,首先,时间上没法控制,一个文件同步到所有的节点,快则几秒钟,慢的话耗时会超过一两分钟;并且同步过程还有可能失败,健康检测的成本也是相当高的。发布比较紧凑时,需要同步的文件也很多,很容易造成队列堆积,加剧同步差的体验。
实时性强需求问题。 文件在推送之前,还可能经过一些前置系统,发布链路越长,线上生效时间越慢,慢的时候大约五分钟才生效,这样的延时对于实时性要求很高(如秒杀)的需求来说是完全不能接受的。
当然,还有很多其他问题,如运维成本增高、安全风险增高、PHP 资深人才储备不足等等。所以 PHP 渲染容器的命运,就是,被干掉。
上图改变了下玩法,服务集群为 Cache CDN,它只有静态文件处理能力,没有 PHP/Node 的渲染能力,所以处理效率高,性能也好,抗压能力相当强,并且扛不住的时候还可以花钱买服务,拓展 Cache 集群。
用户访问时,Nginx 转到 Cache CDN,如果命中缓存则直接返回,没有命中便回源到源站服务器。源站服务器是具备模块渲染能力的 Node 服务,它可以做很多事情:
它的优势有很多,这里不一一列举了。这个模式中还添加了一层容灾,源站服务器每隔一段时间将数据推送到于 Cache 同机房的备份服务器,一点源站挂了,还能够自动容灾到备份数据上。
模式的变化不仅在运维上有了突破,CDN 被攻击时的安全风险也低了很多,同时也省却了 sync 所需的各种检测机制,每年节约成本也是百万以上,优势还是相当明显。
上面 PHP 模块中,我们只说了 HTML 和数据部分,用心的读者应该已经发现,CSS 和 JS 这些静态资源都没提到,那页面是如何渲染的呢?
旧版 PHP 页面中,我们是直接引入了一个 CSS 和一个 JS,淘宝这边采用的是 git 版本迭代发布,这些静态资源都是直接放在一个 git 仓库中。也就是这样:
<body> <?= loadModule(Mod1ID) ?> <?= loadModule(Mod2ID) ?> <?= loadModule(Mod3ID, 'lazyload') ?> <?= loadModule(Mod4ID, 'lazyload') ?> <?= loadModule(Mod5ID, 'lazyload') ?></body>
每次发布完 git 文件,再修改 PHP 的版本号,然后发布 PHP 代码。当然,也做了相关的优化,比如发布 git 时自动更新版本号等。
而新版搭建平台的页面渲染模式与 PHP 的模式不太一样。
一个模块的 CSS/JS 和模板放在一起,CSS/JS 与页面其他模块的静态资源是相互独立的,目的就是希望单个模块也能够完整的跑起来,更加利于模块的复用。
而模块的挖坑,也从模板中独立了出来,采用 JSON Schema 的形式定义数据格式,
.├── index.css # 模块样式├── index.js # 模块渲染脚本├── schema.json # schema 配置└── index.xtpl # 模块的模板
搭建平台通过这个 JSON Schema 解析成 图一 的坑位。那么一个模块的渲染就编程了 index.xtpl 和挖坑数据之间的拼装了。
模块之间相互独立隔离,所以会存在一定程度的冗余,不过模块接偶带来的收益要比这点冗余要多得多。事实上,我们是通过一个仓库去管理单个模块的。页面的渲染就比较简单了,源站 Node 容器会将所有的 index.xtpl 合并成一个 page.xtpl ,为减少页面请求,css 和 js 也会 combo 成一个文件,如上图所示的 http://cdn/??mod1.css,mod2.css,mod3.css 。
任何模块的更新,页面都会有感知,下次进入系统时,就会提示是否需要升级模块和页面。
首页模块众多,如果一口气吐出来,DOM 数量必然超过 4k 个,其结果就是首屏时间极长。按照 TMS 的开发规范,每个 TMS 模块都包含一个 index.js 和 index.css ,最后展示出来两个 combo 的 js 和 css。首页加载的时候也不会一口气执行所有 index.js ,否则刚开始页面阻塞会十分严重。
首页框架的加载逻辑,大致上图所示:
部分模块即便是被执行了,也不一定渲染出来,因为它的优先级不高,在模块内部加了事件监听,比如等到 mouseover/onload 事件触发的时候再渲染这些内容。
之前写过性能优化相关的文章,复制就没必要了,直接贴地址:
代码的性能优化是一个精细活,如果你要在一个庞大的未经优化的页面上做性能优化,可能会面临一次重构代码。
上面的文章提到的是页面内部的细节优化,但是在开发流程中做的规范化、标准化,以及线上访问通路中的各个环节优化还没有提及。
在大流量下,任何小问题都会被放大成大问题,所以开发环节遇到的任何偶发性问题都需要引起重视。不过很多偶发性问题在我们的测试环境中是找不到的,比如与地域相关的问题(如上海的某个 CDN 节点挂了),用户属性问题(如 nickname 最后一个为字母 s 的用户页面天窗),浏览器插件问题,运营商广告注入问题等等。
难以在上线之前把所有问题考虑周全,但是有两点是必须做好的: 兜底容灾 + 监控预警。
兜底容灾有两个层面的考虑:
异步接口请求,主要涉及到的是后台系统,对接系统较多,各个系统的稳定性和抗压能力各不相同,这方面的保障有多种方案,下面是最常见的:
各データリクエストはローカルにキャッシュされ、各インターフェイスにハードボトムが提供されます。もう 1 つの解決策は、リクエストが 1 回失敗した場合に 2 回目のリクエストを行うことです。この点の詳細については、私が以前に書いた記事「淘宝網ホームページの災害復旧計画」をご覧ください。
同期レンダリングの場合、ページ テンプレートと同期されたデータのみが必要です。そのいずれかにエラーがある場合、元のサイトはエラーを報告します。このとき、元のサイトに返されるコンテンツは、ステータスコードは5xxです。このエラーは必ずしも開発者が原因であるとは限りません。同期異常またはシステム リンクのサーキット ブレークの問題が考えられます。この問題に対応して、タオバオのホームページ用のミラー ページを作成しました。
元のサイトに異常があると、Nginx はキャッシュ CDN と同じコンピューター ルームにあるホームページ ミラーに移動します。このミラーの内容は次のとおりです。タオバオのホームページの HTML バックアップ ソース コードです。
監視にも 2 つのレベルがあります:
モジュールレベルでの監視が非常に多く、監視ポイントが多いほど、最終的に問題を特定するのがより効率的になります。たとえば、少し複雑なモジュールでは、次の監視を埋め込みます:
一部の監視では、たとえば http 画像が https ページの下に表示される場合、明確なエラーも自動的に処理されます。問題は自動的にすぐに処理されます。
これは、淘宝網全体のエンジニアリング環境であるフロントエンド自動テストの一部です。通常、これらの問題はオンラインになる前に処理されます:
もちろん、検出などのテスト ケースを自分で追加することもできますインターフェイスのデータ形式、モジュールのスカイライトの問題など。自動検出では、スケジュールされた回帰を設定することもできますが、これは比較的安全です。
2. Interface Hub
Interface Hub は、以下の図に示すように、データ リクエストの管理ツールです。
3. クイック チャネル
スタイルの混乱やオーバーフロー、スカイライトを引き起こすインターフェイス エラーなどの緊急オンライン問題が発生した場合、クイック オペレーション チャネルを配置しました。ショートカット チャネルと JS を介してページの CSS をオンラインで 2 分以内に作成します。
ただし、このタイプのチャネルは、緊急の問題を修復する場合にのみ適しています。結局のところ、JS コードを自由に挿入するのは非常に危険です。
この文章は少しクライマックスに近い感じがします (コーディングと描画は非常に面倒です)。まだ拡張されていない部分がたくさんあります。以上でタオバオのホームページの基本を理解していただければ幸いです。