Jack Jiang

我的最新工程MobileIMSDK:http://git.oschina.net/jackjiang/MobileIMSDK
posts - 505, comments - 13, trackbacks - 0, articles - 1

2025年6月20日

     摘要: 本文由GSYTech 恋猫de小郭分享,原题“2025 跨平台框架更新和发布对比,这是你没看过的全新版本”,下文有修订和重新排版。1、前言2025 年可以说又是一个跨平台的元年,其中不妨有鸿蒙Next平台刺激的原因,也有大厂技术积累“达到瓶颈”的可能,又或者“开猿截流、降本增笑”的趋势的影响,2025 年上半年确实让跨平台框架...  阅读全文

posted @ 2025-07-16 10:28 Jack Jiang 阅读(15) | 评论 (0)编辑 收藏

1、基本情况

RainbowTalk是一套基于MobileIMSDK的产品级鸿蒙NEXT端IM系统,目前已正式发布。纯ArkTS、从零编写,无套壳、没走捷径,每一行代码都够“纯”(详见:《RainbowTalk详细介绍)。

MobileIMSDK是一整套开源IM即时通讯框架,历经10年,超轻量级、高度提炼,一套API优雅支持 UDP 、TCP 、WebSocket 三种协议,支持 iOS、Android、H5、标准Java、小程序、Uniapp、鸿蒙NEXT,服务端基于Netty编写。

MobileIMSDK工程的开源地址是:

2、功能简介

1)支持文本消息、语音留言消息、图片消息、大文件消息(支持断点上传)、短视频消息、个人名片、群名片、Emoji表情、消息撤回、消息转发、消息引用、“@”功能、“扫一扫”功能等;
2)支持一对一陌生人聊天模式;
3)支持一对一正式好友聊天模式;
4)支持多对多群聊聊天模式;
5)完善的群组信息管理:建群、退群、解散、转让、邀请、踢人、群公告等;
6)完整的注册、登陆(同时支持手机验证码登录和密码登录)、密码找回等功能闭环;
7)个人中心功能:改基本信息、改个性签名、改头像、改密码等;
8)支持个人相册查看;
9)完整的离线消息/指令拉取机制;
10)完整的本地消息/指令缓存机制,节省网络流量;
11)完整的富媒体文件(语音、大文件、图片、短视频)缓存机制,节省网络流量;
12)完整的好友关系管理:查找好友、发出请求、处理请求、删除好友、好友备注等;
13)其它未提及的功能和特性请自行下载体验

RainbowTalk线上版本目前仅作演示和研究之用,运行环境配置最小化(仅1核1G和1MB带宽),请客观评估。

3、登陆和注册等

4、首页等主要界面

5、“我的”、“个人中心”等页面

6、好友关系等

7、陌生人聊天

8、好友聊天

9、世界频道聊天

10、群聊和群管理

11、大文件消息

12、短视频消息

13、“个人名片”消息

14、“群名片”功能

15、“扫一扫”功能

16、“搜索”功能

17、“消息转发”功能

18、“消息引用”功能

19、“@”功能

20、“消息撤回”功能

 

本文内容引用自:http://www.52im.net/thread-4822-1-1.html

posted @ 2025-07-09 11:27 Jack Jiang 阅读(31) | 评论 (0)编辑 收藏

本文由字节跳动张华挺分享,原题“你不知道的前端音视频知识”,下文有修订和重新排版。

1、前言

本文回顾了Web端音视频的发展历程,同时还介绍了视频的编码、帧率、比特率等概念,提到了Canvas作为视频播放的替代方案,以及FFmpeg在音视频处理中的重要作用等知识。

 
技术交流:

2、远古时期的HTML

Web端音视频的发展史得从刀耕火种的年代——早期 HTML说起。

在早期的 HTML,由于带宽、技术等各种因素限制,网页主要以简单的静态内容为主,只支持一些文字图片内容和简单的排版,不支持在线观看音视频。

图为 1994 年的 Yahoo!:

3、 Flash的兴起与淘汰

20 世纪初,随着互联网的发展,各种 Web 应用和门户网站不断出现,人们渴望在网页上看到更加丰富多彩的内容,比如视频、动画等等,于是 Flash 进入了人们的视野。

彼时的 Flash 没有像现在大家印象中的那么臃肿,刚诞生的 Flash 小巧、高效、跨平台,同时凭借几十 K 的体积做出放大也不会失真的各种矢量彩色动画,在还是拨号上网,带宽条件受限,加载一个在线视频需要好几分钟的年代脱颖而出,甚至可以做出各种令人沉迷的 Flash 小游戏。

Flash 塑造了很多经典的小游戏角色,火柴人就是其中之一:

Flash 的兴起,得益于当时 HTML 对于媒体文件支持的匮乏。Flash 以插件的形式,干着平台才需要负担的繁重工作,并得益于 Adobe 的大力推广,Flash 先后增加了对 Javascrip、HTML、XML 的支持,并增强了影音方面的功能。同时由于 Flash 跨平台的特性,非常容易被移植,市面上稍微高端点的设备,也得乖乖地给 Adobe 交授权费。

然而 2007 年推出的 iPhone 并不买账,他们以增加续航、安全为由抛弃了 Flash,很多人一开始对此嗤之以鼻,但事实证明苹果对此确实有远见,大量低质量的 Flash 使当时续航本就有限的移动设备更加不堪重负。2012 年,Android 也宣布不再支持 Flash,Flash 在移动市场不再有立足之地。

在桌面市场上,Flash 的日子也并不好过。Chrome 从的 Chrome 42 开始,就已经强制把 Flash 装入沙箱,以 PPAPI 的形式运行;而从 Chromium 版本号 88 开始,已经彻底不再支持 Flash 技术了。微软的 Edge 浏览器也同步不支持 Flash。Chrome 的前辈 Firefox 更加激进,从 2016 年就已经默认禁止 Flash 运行了。

至于 Flash 为什么走向了淘汰,除了它的效率变低,不安全因素过多,稳定性不足外,还有一个重要原因:Web 音视频解决方案有了更好的替代品—— HTML5。

4、HTML5的到来

其实,对于 HTML5 是否可以真正替代 Flash,尤大在 2011 年已经给出了预言:

事实正如预言所预料,HTML5 在 2008 年发布后,经过不断改进完善,基本上能包办 Flash 所有能干的事情了。HTML5 引入了许多新特性和新功能,其中就包含了 video 和 audio 标签,也就是对音视频的支持。使用了支持 HTML5 标准的网络浏览器访问 HTML5 站点,用户无需在电脑上安装 Flash 插件就可以在线观看视频,摆脱了对 Flash 的依赖。

2021 年 1 月 20 日,chrome 88 正式发布,彻底的禁止使用 Flash。自此,Flash 算是彻底退出了历史舞台。

5、到底什么是视频

视频,其实就是一系列连续播放的图片,如果一秒钟播放 24 张图片,那么人眼看到的就不再是一张张独立的图片,而是动起来的画面。

其中一张图片称为一帧,1s 播放的图片数称为帧率。由于人类眼睛的特殊生理结构,如果所看画面之帧率高于每秒约 10-12 帧的时候,就会认为是连贯的;当看到帧率为 24 fps 以上时,大脑会认为这是流畅播放的视频。所以一般有声电影的拍摄及播放帧率大约为每秒 24 帧,欧美、日本那边由于电视制式不同,大约为 30 帧。

关于视频及视频编码相关的入门文章可以继续详读以下资料:

6、电影的帧率与游戏的帧率

为什么 24 帧的电影比 30 帧的游戏要流畅许多?这其中的原因就在于,电影和游戏的图像生成原理不同。

电影的 24 fps,是每 1/24 秒拍摄一副画面,如果你玩过相机的手动设置,你应该知道如果以 1/24 秒的快门速度拍摄一个运动的物体会“糊”掉,而正是这样“糊”掉的画面连起来才让我们的眼睛看上去很“流畅”。

而游戏画面不是按 1/24 秒快门拍出来的,而是每一幅画面都是独立渲染出来的,之所以跑成 24fps 是因为显卡处理能力不够而“丢弃”了其中的一些画面,这样一来每两幅画面之间就不连续了,自然看上去会“卡”。

举个例子:一个圆从左上角移动到右下角,如果是电影,第一帧与第二帧可能是类似下图这样的。

如果是游戏画面,第一帧与第二帧会类似下面这两张图:

此外,帧与帧之间间隔恒定:人眼对于动态视频的捕捉是非常敏感的,电影帧率是固定不变,肉眼很难察觉出异常。

而游戏的帧率却是很容易变化的——如果手动锁定帧数,显卡会默认渲染最高帧率。

玩家触发的很多剧情往往伴随剧烈的画面变动,这时显卡的帧率就会出现下降,前后不一致的帧率很容易被肉眼捕捉,这时我们就会觉得,游戏变“卡”了。

7、视频的编码

7.1 概述

视频是由图片构成的,图片是由像素构成的,假设尺寸为 1980*1080。每个像素由 RGB 构成,每个 8 位,共 24 位。

假设帧率是 24,那么每秒钟的视频的尺寸如下:

一分钟视频的尺寸就是 9237888000 Bytes 已经是 8.8 个 G 了。可以看到,如果是不对视频做任何处理,是非常不方便对于视频做传输与存储的,所以需要对视频进行压缩,也就是编码。

7.2 视频编码

视频图像数据有很强的相关性,也就是说有大量的冗余信息。其中冗余信息可分为空域冗余信息和时域冗余信息。压缩技术就是将数据中的冗余信息去掉(去除数据之间的相关性),压缩技术包含帧内图像数据压缩技术、帧间图像数据压缩技术和熵编码压缩技术。

经过编码之后,视频由一帧帧的图片,变成了一串串让人看不懂的二进制代码,因为编码的方式(算法)的不同,所以就有了编码格式的区分。常见的编码格式有 H.264,MPEG-4,VP8 等。

我们前端开发只需要记住一点,主流浏览器支持的视频编码格式是 H.264。

7.3 音频编码

CD 音质的音频,存放一分钟数据需要的大小为 10M,太大了,也需要压缩(编码)。

常见的编码方式有:WAV、MP3 和 AAC 格式。

音频的编码方式不像视频那样那么多,而且音频在各个浏览器基本上都可以播放。具体的每种编码格式包含的音频是怎么构成的,这里就不讲了。

关于音频及音频编码相关的入门文章可以继续详读以下资料:

7.4 封装格式

我们把视频数据、音频数据打包到一起,然后再添加一些基本信息,例如分辨率、时长、标题等,构成一个文件,这个文件称为封装格式。常见的封装格式有 MP4、AVI、RMVB 等。

可以看出:视频的封装格式和视频的编码格式往往是无关的。一个 mp4 文件里面的视频流编码可以是 h264也可以是 mpeg-4。所以就会出现,同样都是 mp4 文件,有的浏览器可以放,有的浏览器就放不了的问题,因为能不能放是由视频码流的编码格式决定的。

8、视频的码率

码率,也叫比特率,帧率是 1s 播放多少帧,类比一下,比特率就是 1s 的视频有多少 bit。这个参数直接决定了视频的大小与清晰程度。

一般网上流传的电影 MKV(BDrip-1080P)的码率是 10Mb/s 左右,蓝光原盘是 20Mb/s 左右,这两者都是 H.264 编码的。另外一些 MV、PV、演示片什么的除了 H.264 编码,可能还有 MPEG-2 编码,码率大小不等,像 youtube 那些在线的 1080P 的视频,码率可能只有 5Mb/s,而一些 MV 的码率可以高到离谱,可以达到 110Mb/s 的,3 分多钟的 MV 差不多有 3GB 大小。

而一般的视频剪辑、后期软件,在输出序列的时候,都会有码率这个选项。

9、视频播放器的原理

播放视频的基本流程是:解协议 → 解封装 → 解码 → 视音频同步。如果播放本地文件则不需要解协议。

解协议的作用,就是将流媒体协议的数据,解析为标准的相应的封装格式数据。视音频在网络上传播的时候,常常采用各种流媒体协议,例如 HTTP、RTMP或是 MMS 等等。这些协议在传输视音频数据的同时,也会传输一些信令数据。这些信令数据包括对播放的控制(播放、暂停、停止),或者对网络状态的描述等。解协议的过程中会去除掉信令数据而只保留视音频数据。

解封装的作用,就是将输入的封装格式的数据,分离成为音频流压缩编码数据和视频流压缩编码数据。封装格式种类很多,例如 MP4、MKV、RMVB、TS、FLV、AVI 等等,它的作用就是将已经压缩编码的视频数据和音频数据按照一定的格式放到一起。例如,FLV 格式的数据,经过解封装操作后,输出 H.264 编码的视频码流和 AAC 编码的音频码流。

解码的作用,就是将视频/音频压缩编码数据,解码成为非压缩的视频/音频原始数据。音频的压缩编码标准包含 AAC、MP3、AC-3 等等,视频的压缩编码标准则包含 H.264、MPEG2、VC-1 等等。解码是整个系统中最重要也是最复杂的一个环节。通过解码,压缩编码的视频数据输出成为非压缩的颜色数据,例如 YUV420P、RGB 等等;压缩编码的音频数据输出成为非压缩的音频抽样数据,例如 PCM 数据。

视音频同步的作用,就是根据解封装模块处理过程中获取到的参数信息,同步解码出来的视频和音频数据,并将视频音频数据送至系统的显卡和声卡播放出来。

10、HTML5的canvas播放视频

如果我们碰到一些特殊机型或者特殊情况 HTML5 的 video 解决方案不是很好处理,也可以采用 Canvas 去播放这个视频。

使用 Canvas 播放视频主要是利用 ctx.drawImage(video, x, y, width, height) 来对视频当前帧的图像进行绘制,其中 video 参数就是页面中的 video 对象。所以如果我们按照特定的频率不断获取 video 当前画面,并渲染到 Canvas 画布上,就可以实现使用 Canvas 播放视频的功能。

<video id="video" controls="controls" style="display: none;">

    <source src="https://xxx.com/vid_159411468092581" />

</video>

<canvas  id="myCanvas" width="460" height="270" style="border: 1px solid blue;" ></canvas>

<div>

    <button id="playBtn">播放</button>

    <button id="pauseBtn">暂停</button>

</div>

 

const video = document.querySelector("#video");

const canvas = document.querySelector("#myCanvas");

const playBtn = document.querySelector("#playBtn");

const pauseBtn = document.querySelector("#pauseBtn");

const context = canvas.getContext("2d");

let timerId = null;

function draw() {

    if (video.paused || video.ended) return;

    context.clearRect(0, 0, canvas.width, canvas.height);

    context.drawImage(video, 0, 0, canvas.width, canvas.height);

    timerId = setTimeout(draw, 0);

}

playBtn.addEventListener("click", () => {

    if (!video.paused) return;

    video.play();

    draw();

});

pauseBtn.addEventListener("click", () => {

    if (video.paused) return;

    video.pause();

    clearTimeout(timerId);

});

事实上,市面上已经有不少 Canvas 播放视频的解决方案,比较出名的是这个 JSMpeg。它和 PIXI 一样,可以选择 WebGL 渲染视频也可以直接用 Canvas 渲染视频。

JSMpeg 是没有 npm 包的,但是社区上有开发者基于 JSMpeg 封装了一个 npm 包:https://github.com/cycjimmy/jsmpeg-player

在官网上是这么介绍的:

JSMpeg is a Video Player written in JavaScript. It consists of an MPEG-TS Demuxer, WebAssembly MPEG1 Video & MP2 Audio Decoders, WebGL & Canvas2D Renderers and WebAudio Sound Output. JSMpeg can load static files via Ajax and allows low latency streaming (~50ms) via WebSocktes.

由于它所支持的编码格式不是常规的 H.264,而是比较老的 MPEG1,并且解封装器为 MPEG-TS。所以一般我们使用它去渲染视频的格式为 TS。TS 是日本高清摄像机拍摄下进行的封装格式,全称为 MPEG2-TS。它的特点就是要求从视频流的任一片段开始都是可以独立解码的。

TS 文件通常作为多个文件保存在 DVD 上,虽然它可以在高清摄像机、蓝光 DVD 中无需借助其他软件就能直接打开,但是 TS 视频文件与大多数的媒体播放器、便携式播放器或视频编辑工具都不兼容,所以这个时候,FFmpeg 就可以出场了。

11、视频操作神器——FFmpeg

FFmpeg是一个开源的软件,我们直接用 homebrew 就可以安装:

1brew install ffmpeg

如果我们想转换为 jsmpeg 所需的 ts 格式视频,可以执行:

$ ffmpeg -i input.mp4 -f mpegts \

         -codec:v mpeg1video -s 640x360 -b:v 1500k -r 25 -bf 0 \

         -codec:a mp2 -ar 44100 -ac 1 -b:a 64k \

         output.ts

  • 1)i:指定输入文件,这里指定为 input.mp4;
  • 2)f 指明输出文件的封装格式,这里为 jsmpeg 所需的 mpegts;
  • 3)codec:v 指明输出文件的视频编码,这里指明为 jsmpeg 所需的 mpeg1video;
  • 4)s 设置视频分辨率,参数格式为w*h或w×h;
  • 5)b:v 设置视频码率,一般如果想得到高清的效果,至少需要 4000k 以上,如果对视频体积有要求,可以视情况小一点;
  • 6)r 设置帧率(fps),一般都为 25;
  • 7)bf bframe 数目控制,一般为 0。

B 帧法(B frame)是双向预测的帧间压缩算法。当把一帧压缩成 B 帧时,它根据相邻的前一帧、本帧以及后一帧数据的不同点来压缩本帧,也即仅记录本帧与前后帧的差值。

  • 1)codec:a 指明输出文件的音频编码;
  • 2)ar 设置音频编码采样率,单位kHz,一般网上的音频,大多为 44100;

音频采样率是指录音设备在单位时间内对模拟信号采样的多少,采样频率越高,机械波的波形就越真实越自然;

  • 3)ac 设置音频编码声道数;
  • 4)b:a 设置音频码率;

音频码率,指一个音频流中每秒钟能通过的数据量,码率越大的话,音质越好。

  • 最后一个参数即为输出文件位置与名称和后缀格式。

FFmpeg 是一个非常强大的音视频转换工具,不仅可以视频转换,还可以视频尺寸裁剪、视频时长裁剪、视频拼接等等功能,目前很多在线视频剪辑工具基本是基于 FFmpeg 开发的。

12、音视频的一些资源推荐

国内学习音视频相关的开发,绕不过的一个大神是雷霄骅,大佬已经去世了,但是留下的文章永垂不朽。本文也是参考了雷霄骅的部分博客,如果感兴趣,可以从这篇文章看起:《视音频编解码技术零基础学习方法》。

对于直播 webrtc 感兴趣的,也可以看一下 Real time communication with WebRTC,国内慕课网上李超老师也有不错的教程。

对 ffmpeg 感兴趣的,可以看一下这里:https://github.com/leandromoreira/ffmpeg-libav-tutorial

13、参考资料

[1] 即时通讯音视频开发(十八):详解音频编解码的原理、演进和应用选型

[2] 即时通讯音视频开发(十九):零基础,史上最通俗视频编码技术入门

[3] 即时通讯音视频开发(二十):一文读懂视频的颜色模型转换和色域转换

[4] 实时语音聊天中的音频处理与编码压缩技术简述

[5] 网易视频云技术分享:音频处理与压缩技术快速入门

[6] 福利贴:最全实时音视频开发要用到的开源工程汇总

[7] 理解实时音视频聊天中的延时问题一篇就够

[8] 写给小白的实时音视频技术入门提纲

[9] 爱奇艺技术分享:轻松诙谐,讲解视频编解码技术的过去、现在和将来

[10] 零基础入门:实时音视频技术基础知识全面盘点

[11] 实时音视频面视必备:快速掌握11个视频技术相关的基础概念

[12] 实时音视频开发理论必备:如何省流量?视频高度压缩背后的预测技术

[13] 视频直播技术干货(十三):B站实时视频直播技术实践和音视频知识入门

[14] 零基础入门:基于开源WebRTC,从0到1实现实时音视频聊天功能

[15] 实时音视频入门学习:开源工程WebRTC的技术原理和使用浅析

[16] 零基础快速入门WebRTC:基本概念、关键技术、与WebSocket的区别等


(本文已同步发布于:http://www.52im.net/thread-4840-1-1.html)

posted @ 2025-06-26 15:25 Jack Jiang 阅读(45) | 评论 (0)编辑 收藏

本文由腾讯技术团队罗国佳分享,原题“微信读书后台架构演进之路”,下文有修订和重新排版。

1、前言

今年是微信读书上线10周年,后台技术架构也伴随着微信读书的成长经历了多次迭代与升级。每一次的组件升级与架构突破,在一个运行了10年的系统上落地都不是一件容易的事情,需要破釜沉舟的决心与胆大心细的业务联动。

微信读书经过了多年的发展,赢得了良好的用户口碑,后台系统的服务质量直接影响着用户的体验。团队多年来始终保持着“小而美”的基因,快速试错与迭代成为常态。后台团队在日常业务开发的同时,需要主动寻求更多架构上的突破,提升后台服务的可用性、扩展性,以不断适应业务与团队的变化。

 
 

2、整体架构设计

微信读书是独立于微信的App,且由于历史原因,开发及运维环境均存在一定的差异与隔离。因此,微信读书的后台服务实现了从接入层到存储层的一整套完整架构。

架构上分解为典型的接入层、逻辑层和存储层:

1)接入层:按业务划分为多个CGI服务,实现了资源隔离。在CGI层面还实现了如路由、频控、接入层缓存、长连接等。

2)逻辑层:采用WRMesh框架构建了多个微服务,这些微服务按业务场景进行划分,实现了不同模块间的解耦。框架也提供了如RPC、路由发现、过载保护、限流频控、监控上报等能力。

3)存储层:主要采用PaxosStore存储用户数据,分为K-V和K-Table两种类型,具备高可用、强一致的特性,针对String和Table两种类型定制了缓存中间件,以适配某些业务场景下对访问存储的性能要求。BookStore提供书籍的存储服务,满足读书场景下对书籍的拆章、修改、下载等需要。此外,也不同程度地使用了腾讯云的PaaS存储服务,以灵活满足更多场景需要。

具体的业务逻辑不再赘述,下面简单介绍下微信读书近几年在后台架构上的一些演进。

3、异构服务间调用:RPC框架

微信读书后台微服务源于Hikit框架,采用C++开发。该框架诞生于广研、QQ邮箱年代,在性能、容灾、运维、监控层面都经受了线上的考验,在微信读书上线初期作为主要框架,支撑了后台服务长达数年。

随着微信读书的发展,越来越多异构的系统发展起来。例如推荐算法系统是独立部署在TKE上的容器服务,采用GO语言开发,好处是历史负担少,运维更加方便、开发更加便捷。

两套系统同时存在带来的问题是如何做好服务治理,推荐系统需要频繁调用后台基础模块获取用户数据,必须要有一套完善的路由管理、容灾机制,且考虑到是异构服务,开发语言也不相同,如果为每种语言都定制开发一套服务治理框架,代价会非常高。

在这个阶段,我们开发了WRMesh框架,采用Sidecar+Business的方式解决这个问题。

Sidecar专注于处理网络层的逻辑,和Business业务层分开为两个进程,由WRMesh脚手架生成代码,上层业务无需感知。

Sidecar集成了Hikit框架中用于服务治理的核心逻辑:通过UnixSocket与Business进行通信,代理Business的所有网络读写。当Business进程中需要发起网络请求时,由WRMesh生成的Client代码会自动识别当前是否在mesh环境中,并转发请求给Sidecar,由Sidecar完成接下来的网络处理。

因此:Business进程可以由任意语言任意框架开发,只要遵循Sidecar的通信协议,只需要薄薄的一层网络协议转换即可接入到Hikit的服务治理框架中。

另外:对于某些有特殊路由逻辑的Client,如KV访问、Batch请求等,代理转发并不能满足要求,因此Sidecar还提供了插件能力集成这些Client逻辑,最大限度为异构Business业务提供原生C++的能力。

随着WXG容器平台P6N的建设越来越完善,许多微信的能力也是基于P6N提供,我们也在思考如何逐步迁移到P6N。由于微信读书后台运维目前依赖于企微团队,有独立于P6N的一套运维体系,我们负责业务和架构开发。

如果要一刀切把所有后台服务迁移至P6N,将会面临几个问题:

1)框架代码需要重新适配,开发环境和现网环境都有巨大的改造成本。

2)迁移不是一蹴而就,后台上百个服务在迁移过程中,会存在新旧服务互调的问题,由于运维环境不互通,微服务之间无法完成服务治理,这种互相调用最终只能通过Proxy来转发,不仅增加了网络的失败率,时延增加,最关键的是这个过程会让容灾体系大打折扣。

3)存储模块的迁移成本和风险巨大,如果不迁移存储模块只迁移了逻辑模块,那势必又会存在2中的问题,这个过程很难收尾。

考虑到人力成本及投入性价比,我们最终采用了折衷的方案:

1)一方面:我们保留了依赖于企微的运维环境,保障绝大多数现成服务的稳定运行。

2)另一面:对于微信P6N中的服务,我们搭建了比较完善的Proxy层,例如Svrkit代理、WQueue代理等,两套架构可以方便进行互通,最大限度的在原有基础上接入微信的新能力。

目前,微信读书已顺利接入如WQueue、FKVOL、SimOL、TFCC等众多微信的能力。

4、书籍数据中台的演进

4.1 技术背景

书籍是微信读书的内容根基,书籍数量的多少、书籍质量的好坏,很大程度上决定了用户是否选择微信读书作为阅读App。

过去:我们依托阅文集团提供电子书资源,免去了书籍上架前繁琐的处理流程,包括排版、审校、元信息管理、更新管理等,后台服务只需要对接阅文API即可方便获取书籍数据,我们只需要关注书籍在平台的存储管理和分发流转即可。

近几年:电子书行业的大环境发生变化,一方面,用户对书籍品类多样性、内容质量有更高的诉求,另一方面,平台对成本、版权等行业因素也更为敏感。因此,我们也在积极探索自签版权,甚至是自出品的模式,尝试走更多不一样的道路。从后台角度而言,从过去单一依赖阅文集团API的模式,慢慢转为开放更多的书籍管理接口,形成书籍数据中台模式,为上层运营同学搭建内容管理平台,让更多人可以方便参与到电子书的制作、排版、上下架、运营管理当中。

以EPUB为例,从内容产出到上架到微信读书,大致经历以下阶段:

1)排版审校:这个阶段多为人工或者部分机器自动化介入。

2)上架预处理:这个阶段需要创建书籍信息,配置各种运营策略,当这本书是重排版上架时,内容发生改变,由于现网已经存在用户的划线笔记、进度等数据,需要有完善指标评估是否适合覆盖上架,当上架时,需要对用户数据进行修复,避免发生错位情况,严重影响用户体验。

3)EPUB解析:当书籍上架后,由于EPUB是单一文件,不适合解析和管理分发,因此后台会把源文件解析成自有格式,包括EPUB拆章、图文分离、样式分离、按章生成离线包等等。

4)生成BookInfo和BookData并落盘:EPUB文件经过解析后,BookInfo和BookData会存储到自建的StoreSvr服务上,StoreSvr针对书籍存储、下载等场景进行了很多优化,具备高可用、低时延的特点,提供了书籍信息获取、按章下载等核心接口。

4.2 建设数据中台

回到最初的目标,我们希望把更多的书籍管理能力开放出来,对上层屏蔽电子书底层的后台逻辑,让运营同学可以更专注于书籍的管理。

因此,我们构建了如下书籍数据中台:

后台服务拆分开StoreAPI和StoreSvr:

1)StoreAPI:提供书籍管理的接口,由运营同学搭建的内容平台与StoreAPI交互,完成书籍的管理工作;

2)StoreSvr:一方面接受StoreAPI的请求,更新书籍数据,另一方面为现网用户提供高可用的服务。

StoreAPI提供了如下接口能力:

  • 1)书籍id分配、上下架;
  • 2)书籍信息创建、修改;
  • 3)书籍内容修改、连载更新、订阅推送;
  • 4)运营策略管理。

此外:如上所述,划线位置和阅读进度等核心UGC数据由于是按文件偏移记录,当书籍文件替换后,这些数据会发生错位,如果不能及时修复,将对用户体验造成巨大影响。尤其在一些热门书籍里,单本书里与位置相关的UGC数据往往能达到亿级别,由于文件替换后位置的偏移具有随机性,并不能采用简单的映射方式解决,在过去,我们开发了专门的修复服务来完成这个事情,针对每一个UGC内容,采用全文模糊查找的方式重新计算新的偏移,并更新的UGC正排、书籍倒排等多个存储中。

但随着用户数据越来越多,书籍替换频率越来越频繁,修复不及时或者失败的问题逐渐暴露出来:

  • 1)修复量大导致修复不及时。过去的修复服务虽然是多机部署,但处理单本书仍只是集中在一台机器上,单机性能有限;
  • 2)修复任务缺乏落盘管理,修复服务一旦重启,任务丢失。

针对上面的问题:我们重新设计了修复服务,目标是最大限度缩短修复时间,并且让整个过程是可靠的。

为此,我们先首手考虑业务流程,我们发现在书籍上架前,运营同学本来就需要依赖UGC的修复情况做前置判断是否覆盖上架,这个过程中虽然是对UGC抽样评估,如果能对这个修复映射结果进行缓存,在正式替换文件后,也能一定程度提升修复速度。

在核心修复流程中,我们进行了较大的重构,把单本书的修复任务拆解成多个子任务,存储在Chubby上,多机器抢锁共同消费这些任务,由于任务有落盘,在服务上线重启过程中,也能马上恢复。修复过程涉及大量的KV写入,并发太高时容易命中单key的限频或者版本冲突,我们为此开发了针对K-Str和K-Table的写入中间件,可以在内存中聚合一批请求进行批量合并写入,缓解KV层面的失败。

目前,微信读书已通过内容平台完成了多家版权方自签,并在探索自出品等内容创作新形式。

5、账号系统的高可用性重构

账号是微信读书后台系统的基石,承担了登录、会话密钥生成与派发、用户资料管理等核心功能,所有的用户请求都需经过账号系统进行鉴权验证用户身份,但凡有一点系统抖动都会影响到整个App的正常使用,严重者还会导致账号被踢出无法再次登录。

账号系统的架构在微信读书诞生之初便一直沿用,同一个号段的账号服务AccountSvr和MySQL部署在同一台机器上,备机采用主从同步的方式获取数据,当主机不可用时,备机承担了所有读请求。

在某些场景下,为了能使访问备机时也具备一定的写入能力,曾经魔改过主备逻辑,但一切都显得治标不治本,且引入了更复杂的系统特性,整个架构略显混乱。在机器裁撤、数据扩容过程中,曾造成过几次严重故障,导致App不可用,严重影响用户体验。究其原因,是因为当时基础设施还不完善,缺少高性能高可靠的强一致存储,MySQL也是手动搭建的,运维成本和风险都非常高。

为了彻底解决这个历史包袱,我们在2024下定决心对其进行重构。重构就意味着要抛弃现有MySQL这套臃肿的存储方案,把数据迁移到新的存储组件上。

这里涉及到的挑战点如下:

  • 1)账号鉴权服务访问量巨大,迁移过程须尽量不增加系统负担,且必须是在不停机的情况下进行;
  • 2)迁移过程中一旦有数据丢失或者错误,会导致用户资料受损,用户登录态丢失,App无法使用;
  • 3)账号系统还涉及用户id分配和回收逻辑,在切换存储时如何保证数据的一致性,不重复分配号码。

背水一战,没有退路可言。在经历了多次论证后,我们决定采用Paxosmemkv作为新的存储组件,全内存、多副本、强一致的特性,很适合作为账号系统的底层存储。

同时,我们为整个迁移过程制定了周密的方案,把每一步进行了分解,且要求每个环节可灰度可回退,同时要做好数据的一致性检查。

在完成数据迁移后,我们还需要对AccountSvr进行重构,抛弃按号段的账号分配、路由、缓存逻辑,以全新的视角设计更简洁的架构。

6、内容召回系统的架构设计

以往微信读书的搜索仅限于基于书名、作者等维度的文本召回,通过自建的全内存索引服务实现书籍的检索。全文检索则基于ES搭建,采用规则分段的方式建立索引,能满足读书大部分场景的需要。

在大语言模型迅速发展的近两年,微信读书作为一个庞大的内容知识库,具有大量的书籍原文资源。同时,用户在微信读书也留下了大量的文字内容,如书评、想法等,这些内容构成了AI问书的内容基石,也是AI问书区别于其它问答工具的核心优势。

基于微信读书构建RAG召回系统,核心挑战如下:

1)基于书籍原文构建全文检索,为了达到最好的效果,往往需要支持按语义进行段落切分,在此基础上构建embedding进行语义召回。微信读书拥有百万级书籍原文数据,此外,对于用户导入书,更是达到亿级别规模。现有架构无论从成本还是耗时上都无法解决。

2)为了支持更多维度的召回,需要对UGC内容进行召回,部分UGC内容属于私密信息,并不向全网公开,只需要满足用户个人检索即可。此时如果用常规的检索系统构建常驻索引,访问率太低,成本难以收敛。

为此,我们针对微信读书不同的RAG使用场景,设计了如下召回架构:

我们把数据划分成两类:全局公开可搜以及用户个人可搜。

1)对于全局公开可搜索的数据:如库内电子书的全文、书籍大纲、书评、人工知识库等,我们构建了一套入库流程,能对源信息进行语义分段、生成正排倒排,语义分段基于开源的chunk模型进行微调,正排基于fkv,倒排则基于ES构建,ES提供了DiskANN方案,通过设置合理的缓存和分片,能在存储成本和召回效率之间取得不错的平衡。对于 App 内主搜等低时延场景,为了满足多种定制化检索需求,我们自建了基于内存索引的Searchsvr服务,支持索引落盘,可以在毫秒级返回电子书搜索结果。

2)对于用户个人数据:如导入书全文、个人想法等,特点是数据量大但使用频率不高,不需要针对全网用户进行检索,如果采用上述方案,会带来成本灾难,性价比极低。为此,我们按用户及物料的维度,基于USearch、Xapian等方案构建了向量及文本索引,这些组件的优势在于可以把单个索引存储成文件的形式,便于落盘,配合一些量化的方法,可以把大大压缩索引大小。在构建索引阶段,按用户+类型构建出不同的索引,并存储在低成本的COS上,当用户需要检索召回时,采用读时加载的方式实时进行召回,结合CFS进行预热可以大大提升检索速度。当检索完成后,定时淘汰策略会把长期不用的索引从CFS中清理,降低存储成本。

7、写在最后

虽然微信读书已经发展了十个年头,但我们的脚步从未停止。

在日常业务开发之余,我们也从未停止思考如何让系统能走得更远、更稳健,抓住每一个可能的优化点,随时做好准备,迎接下一个精彩的十年。

8、相关资料

[1] 腾讯资深架构师干货总结:一文读懂大型分布式系统设计的方方面面

[2] 快速理解高性能HTTP服务端的负载均衡技术原理

[3] 子弹短信光鲜的背后:网易云信首席架构师分享亿级IM平台的技术实践

[4] 知乎技术分享:从单机到2000万QPS并发的Redis高性能缓存实践之路

[5] 新手入门:零基础理解大型分布式架构的演进历史、技术原理、最佳实践

[6] 阿里技术分享:深度揭秘阿里数据库技术方案的10年变迁史

[7] 阿里技术分享:阿里自研金融级数据库OceanBase的艰辛成长之路

[8] 达达O2O后台架构演进实践:从0到4000高并发请求背后的努力

[9] 优秀后端架构师必会知识:史上最全MySQL大表优化方案总结

[10] 小米技术分享:解密小米抢购系统千万高并发架构的演进和实践

[11] 一篇读懂分布式架构下的负载均衡技术:分类、原理、算法、常见方案等

[12] 通俗易懂:如何设计能支撑百万并发的数据库架构?

[13] 多维度对比5款主流分布式MQ消息队列,妈妈再也不担心我的技术选型了

[14] 从新手到架构师,一篇就够:从100到1000万高并发的架构演进之路

[15] 美团技术分享:深度解密美团的分布式ID生成算法

[16] 12306抢票带来的启示:看我如何用Go实现百万QPS的秒杀系统(含源码)

9、微信团队的其它精华文章

微信后台基于时间序的海量数据冷热分级架构设计实践

微信团队原创分享:Android版微信的臃肿之困与模块化实践之路

微信后台团队:微信后台异步消息队列的优化升级实践分享

微信异步化改造实践:8亿月活、单机千万连接背后的后台解决方案

一份微信后台技术架构的总结性笔记

社交软件红包技术解密(十三):微信团队首次揭秘微信红包算法,为何你抢到的是0.01元

微信团队分享:极致优化,iOS版微信编译速度3倍提升的实践总结

IM“扫一扫”功能很好做?看看微信“扫一扫识物”的完整技术实现

微信团队分享:微信支付代码重构带来的移动端软件架构上的思考

IM开发宝典:史上最全,微信各种功能参数和逻辑规则资料汇总

微信团队分享:微信直播聊天室单房间1500万在线的消息架构演进之路

企业微信的IM架构设计揭秘:消息模型、万人群、已读回执、消息撤回等

IM全文检索技术专题(四):微信iOS端的最新全文检索技术优化实践

微信团队分享:微信后台在海量并发请求下是如何做到不崩溃的

微信Windows端IM消息数据库的优化实践:查询慢、体积大、文件损坏等

微信技术分享:揭秘微信后台安全特征数据仓库的架构设计

企业微信针对百万级组织架构的客户端性能优化实践

揭秘企业微信是如何支持超大规模IM组织架构的——技术解读四维关系链

微信团队分享:详解iOS版微信视频号直播中因帧率异常导致的功耗问题

微信团队分享:微信后端海量数据查询从1000ms降到100ms的技术实践

大型IM工程重构实践:企业微信Android端的重构之路

IM技术干货:假如你来设计微信的群聊,你该怎么设计?

微信团队分享:来看看微信十年前的IM消息收发架构,你做到了吗

微信后团队分享:微信后台基于Ray的分布式AI计算技术实践

一年撸完百万行代码,企业微信的全新鸿蒙NEXT客户端架构演进之路

(本文已同步发布于:http://www.52im.net/thread-4839-1-1.html)

posted @ 2025-06-20 15:26 Jack Jiang 阅读(46) | 评论 (0)编辑 收藏

本文由腾讯技术团队罗国佳分享,原题“微信读书后台架构演进之路”,下文有修订和重新排版。

1、前言

今年是微信读书上线10周年,后台技术架构也伴随着微信读书的成长经历了多次迭代与升级。每一次的组件升级与架构突破,在一个运行了10年的系统上落地都不是一件容易的事情,需要破釜沉舟的决心与胆大心细的业务联动。

微信读书经过了多年的发展,赢得了良好的用户口碑,后台系统的服务质量直接影响着用户的体验。团队多年来始终保持着“小而美”的基因,快速试错与迭代成为常态。后台团队在日常业务开发的同时,需要主动寻求更多架构上的突破,提升后台服务的可用性、扩展性,以不断适应业务与团队的变化。

 
 

2、整体架构设计

微信读书是独立于微信的App,且由于历史原因,开发及运维环境均存在一定的差异与隔离。因此,微信读书的后台服务实现了从接入层到存储层的一整套完整架构。

架构上分解为典型的接入层、逻辑层和存储层:

1)接入层:按业务划分为多个CGI服务,实现了资源隔离。在CGI层面还实现了如路由、频控、接入层缓存、长连接等。

2)逻辑层:采用WRMesh框架构建了多个微服务,这些微服务按业务场景进行划分,实现了不同模块间的解耦。框架也提供了如RPC、路由发现、过载保护、限流频控、监控上报等能力。

3)存储层:主要采用PaxosStore存储用户数据,分为K-V和K-Table两种类型,具备高可用、强一致的特性,针对String和Table两种类型定制了缓存中间件,以适配某些业务场景下对访问存储的性能要求。BookStore提供书籍的存储服务,满足读书场景下对书籍的拆章、修改、下载等需要。此外,也不同程度地使用了腾讯云的PaaS存储服务,以灵活满足更多场景需要。

具体的业务逻辑不再赘述,下面简单介绍下微信读书近几年在后台架构上的一些演进。

3、异构服务间调用:RPC框架

微信读书后台微服务源于Hikit框架,采用C++开发。该框架诞生于广研、QQ邮箱年代,在性能、容灾、运维、监控层面都经受了线上的考验,在微信读书上线初期作为主要框架,支撑了后台服务长达数年。

随着微信读书的发展,越来越多异构的系统发展起来。例如推荐算法系统是独立部署在TKE上的容器服务,采用GO语言开发,好处是历史负担少,运维更加方便、开发更加便捷。

两套系统同时存在带来的问题是如何做好服务治理,推荐系统需要频繁调用后台基础模块获取用户数据,必须要有一套完善的路由管理、容灾机制,且考虑到是异构服务,开发语言也不相同,如果为每种语言都定制开发一套服务治理框架,代价会非常高。

在这个阶段,我们开发了WRMesh框架,采用Sidecar+Business的方式解决这个问题。

Sidecar专注于处理网络层的逻辑,和Business业务层分开为两个进程,由WRMesh脚手架生成代码,上层业务无需感知。

Sidecar集成了Hikit框架中用于服务治理的核心逻辑:通过UnixSocket与Business进行通信,代理Business的所有网络读写。当Business进程中需要发起网络请求时,由WRMesh生成的Client代码会自动识别当前是否在mesh环境中,并转发请求给Sidecar,由Sidecar完成接下来的网络处理。

因此:Business进程可以由任意语言任意框架开发,只要遵循Sidecar的通信协议,只需要薄薄的一层网络协议转换即可接入到Hikit的服务治理框架中。

另外:对于某些有特殊路由逻辑的Client,如KV访问、Batch请求等,代理转发并不能满足要求,因此Sidecar还提供了插件能力集成这些Client逻辑,最大限度为异构Business业务提供原生C++的能力。

随着WXG容器平台P6N的建设越来越完善,许多微信的能力也是基于P6N提供,我们也在思考如何逐步迁移到P6N。由于微信读书后台运维目前依赖于企微团队,有独立于P6N的一套运维体系,我们负责业务和架构开发。

如果要一刀切把所有后台服务迁移至P6N,将会面临几个问题:

1)框架代码需要重新适配,开发环境和现网环境都有巨大的改造成本。

2)迁移不是一蹴而就,后台上百个服务在迁移过程中,会存在新旧服务互调的问题,由于运维环境不互通,微服务之间无法完成服务治理,这种互相调用最终只能通过Proxy来转发,不仅增加了网络的失败率,时延增加,最关键的是这个过程会让容灾体系大打折扣。

3)存储模块的迁移成本和风险巨大,如果不迁移存储模块只迁移了逻辑模块,那势必又会存在2中的问题,这个过程很难收尾。

考虑到人力成本及投入性价比,我们最终采用了折衷的方案:

1)一方面:我们保留了依赖于企微的运维环境,保障绝大多数现成服务的稳定运行。

2)另一面:对于微信P6N中的服务,我们搭建了比较完善的Proxy层,例如Svrkit代理、WQueue代理等,两套架构可以方便进行互通,最大限度的在原有基础上接入微信的新能力。

目前,微信读书已顺利接入如WQueue、FKVOL、SimOL、TFCC等众多微信的能力。

4、书籍数据中台的演进

4.1 技术背景

书籍是微信读书的内容根基,书籍数量的多少、书籍质量的好坏,很大程度上决定了用户是否选择微信读书作为阅读App。

过去:我们依托阅文集团提供电子书资源,免去了书籍上架前繁琐的处理流程,包括排版、审校、元信息管理、更新管理等,后台服务只需要对接阅文API即可方便获取书籍数据,我们只需要关注书籍在平台的存储管理和分发流转即可。

近几年:电子书行业的大环境发生变化,一方面,用户对书籍品类多样性、内容质量有更高的诉求,另一方面,平台对成本、版权等行业因素也更为敏感。因此,我们也在积极探索自签版权,甚至是自出品的模式,尝试走更多不一样的道路。从后台角度而言,从过去单一依赖阅文集团API的模式,慢慢转为开放更多的书籍管理接口,形成书籍数据中台模式,为上层运营同学搭建内容管理平台,让更多人可以方便参与到电子书的制作、排版、上下架、运营管理当中。

以EPUB为例,从内容产出到上架到微信读书,大致经历以下阶段:

1)排版审校:这个阶段多为人工或者部分机器自动化介入。

2)上架预处理:这个阶段需要创建书籍信息,配置各种运营策略,当这本书是重排版上架时,内容发生改变,由于现网已经存在用户的划线笔记、进度等数据,需要有完善指标评估是否适合覆盖上架,当上架时,需要对用户数据进行修复,避免发生错位情况,严重影响用户体验。

3)EPUB解析:当书籍上架后,由于EPUB是单一文件,不适合解析和管理分发,因此后台会把源文件解析成自有格式,包括EPUB拆章、图文分离、样式分离、按章生成离线包等等。

4)生成BookInfo和BookData并落盘:EPUB文件经过解析后,BookInfo和BookData会存储到自建的StoreSvr服务上,StoreSvr针对书籍存储、下载等场景进行了很多优化,具备高可用、低时延的特点,提供了书籍信息获取、按章下载等核心接口。

4.2 建设数据中台

回到最初的目标,我们希望把更多的书籍管理能力开放出来,对上层屏蔽电子书底层的后台逻辑,让运营同学可以更专注于书籍的管理。

因此,我们构建了如下书籍数据中台:

后台服务拆分开StoreAPI和StoreSvr:

1)StoreAPI:提供书籍管理的接口,由运营同学搭建的内容平台与StoreAPI交互,完成书籍的管理工作;

2)StoreSvr:一方面接受StoreAPI的请求,更新书籍数据,另一方面为现网用户提供高可用的服务。

StoreAPI提供了如下接口能力:

  • 1)书籍id分配、上下架;
  • 2)书籍信息创建、修改;
  • 3)书籍内容修改、连载更新、订阅推送;
  • 4)运营策略管理。

此外:如上所述,划线位置和阅读进度等核心UGC数据由于是按文件偏移记录,当书籍文件替换后,这些数据会发生错位,如果不能及时修复,将对用户体验造成巨大影响。尤其在一些热门书籍里,单本书里与位置相关的UGC数据往往能达到亿级别,由于文件替换后位置的偏移具有随机性,并不能采用简单的映射方式解决,在过去,我们开发了专门的修复服务来完成这个事情,针对每一个UGC内容,采用全文模糊查找的方式重新计算新的偏移,并更新的UGC正排、书籍倒排等多个存储中。

但随着用户数据越来越多,书籍替换频率越来越频繁,修复不及时或者失败的问题逐渐暴露出来:

  • 1)修复量大导致修复不及时。过去的修复服务虽然是多机部署,但处理单本书仍只是集中在一台机器上,单机性能有限;
  • 2)修复任务缺乏落盘管理,修复服务一旦重启,任务丢失。

针对上面的问题:我们重新设计了修复服务,目标是最大限度缩短修复时间,并且让整个过程是可靠的。

为此,我们先首手考虑业务流程,我们发现在书籍上架前,运营同学本来就需要依赖UGC的修复情况做前置判断是否覆盖上架,这个过程中虽然是对UGC抽样评估,如果能对这个修复映射结果进行缓存,在正式替换文件后,也能一定程度提升修复速度。

在核心修复流程中,我们进行了较大的重构,把单本书的修复任务拆解成多个子任务,存储在Chubby上,多机器抢锁共同消费这些任务,由于任务有落盘,在服务上线重启过程中,也能马上恢复。修复过程涉及大量的KV写入,并发太高时容易命中单key的限频或者版本冲突,我们为此开发了针对K-Str和K-Table的写入中间件,可以在内存中聚合一批请求进行批量合并写入,缓解KV层面的失败。

目前,微信读书已通过内容平台完成了多家版权方自签,并在探索自出品等内容创作新形式。

5、账号系统的高可用性重构

账号是微信读书后台系统的基石,承担了登录、会话密钥生成与派发、用户资料管理等核心功能,所有的用户请求都需经过账号系统进行鉴权验证用户身份,但凡有一点系统抖动都会影响到整个App的正常使用,严重者还会导致账号被踢出无法再次登录。

账号系统的架构在微信读书诞生之初便一直沿用,同一个号段的账号服务AccountSvr和MySQL部署在同一台机器上,备机采用主从同步的方式获取数据,当主机不可用时,备机承担了所有读请求。

在某些场景下,为了能使访问备机时也具备一定的写入能力,曾经魔改过主备逻辑,但一切都显得治标不治本,且引入了更复杂的系统特性,整个架构略显混乱。在机器裁撤、数据扩容过程中,曾造成过几次严重故障,导致App不可用,严重影响用户体验。究其原因,是因为当时基础设施还不完善,缺少高性能高可靠的强一致存储,MySQL也是手动搭建的,运维成本和风险都非常高。

为了彻底解决这个历史包袱,我们在2024下定决心对其进行重构。重构就意味着要抛弃现有MySQL这套臃肿的存储方案,把数据迁移到新的存储组件上。

这里涉及到的挑战点如下:

  • 1)账号鉴权服务访问量巨大,迁移过程须尽量不增加系统负担,且必须是在不停机的情况下进行;
  • 2)迁移过程中一旦有数据丢失或者错误,会导致用户资料受损,用户登录态丢失,App无法使用;
  • 3)账号系统还涉及用户id分配和回收逻辑,在切换存储时如何保证数据的一致性,不重复分配号码。

背水一战,没有退路可言。在经历了多次论证后,我们决定采用Paxosmemkv作为新的存储组件,全内存、多副本、强一致的特性,很适合作为账号系统的底层存储。

同时,我们为整个迁移过程制定了周密的方案,把每一步进行了分解,且要求每个环节可灰度可回退,同时要做好数据的一致性检查。

在完成数据迁移后,我们还需要对AccountSvr进行重构,抛弃按号段的账号分配、路由、缓存逻辑,以全新的视角设计更简洁的架构。

6、内容召回系统的架构设计

以往微信读书的搜索仅限于基于书名、作者等维度的文本召回,通过自建的全内存索引服务实现书籍的检索。全文检索则基于ES搭建,采用规则分段的方式建立索引,能满足读书大部分场景的需要。

在大语言模型迅速发展的近两年,微信读书作为一个庞大的内容知识库,具有大量的书籍原文资源。同时,用户在微信读书也留下了大量的文字内容,如书评、想法等,这些内容构成了AI问书的内容基石,也是AI问书区别于其它问答工具的核心优势。

基于微信读书构建RAG召回系统,核心挑战如下:

1)基于书籍原文构建全文检索,为了达到最好的效果,往往需要支持按语义进行段落切分,在此基础上构建embedding进行语义召回。微信读书拥有百万级书籍原文数据,此外,对于用户导入书,更是达到亿级别规模。现有架构无论从成本还是耗时上都无法解决。

2)为了支持更多维度的召回,需要对UGC内容进行召回,部分UGC内容属于私密信息,并不向全网公开,只需要满足用户个人检索即可。此时如果用常规的检索系统构建常驻索引,访问率太低,成本难以收敛。

为此,我们针对微信读书不同的RAG使用场景,设计了如下召回架构:

我们把数据划分成两类:全局公开可搜以及用户个人可搜。

1)对于全局公开可搜索的数据:如库内电子书的全文、书籍大纲、书评、人工知识库等,我们构建了一套入库流程,能对源信息进行语义分段、生成正排倒排,语义分段基于开源的chunk模型进行微调,正排基于fkv,倒排则基于ES构建,ES提供了DiskANN方案,通过设置合理的缓存和分片,能在存储成本和召回效率之间取得不错的平衡。对于 App 内主搜等低时延场景,为了满足多种定制化检索需求,我们自建了基于内存索引的Searchsvr服务,支持索引落盘,可以在毫秒级返回电子书搜索结果。

2)对于用户个人数据:如导入书全文、个人想法等,特点是数据量大但使用频率不高,不需要针对全网用户进行检索,如果采用上述方案,会带来成本灾难,性价比极低。为此,我们按用户及物料的维度,基于USearch、Xapian等方案构建了向量及文本索引,这些组件的优势在于可以把单个索引存储成文件的形式,便于落盘,配合一些量化的方法,可以把大大压缩索引大小。在构建索引阶段,按用户+类型构建出不同的索引,并存储在低成本的COS上,当用户需要检索召回时,采用读时加载的方式实时进行召回,结合CFS进行预热可以大大提升检索速度。当检索完成后,定时淘汰策略会把长期不用的索引从CFS中清理,降低存储成本。

7、写在最后

虽然微信读书已经发展了十个年头,但我们的脚步从未停止。

在日常业务开发之余,我们也从未停止思考如何让系统能走得更远、更稳健,抓住每一个可能的优化点,随时做好准备,迎接下一个精彩的十年。

8、相关资料

[1] 腾讯资深架构师干货总结:一文读懂大型分布式系统设计的方方面面

[2] 快速理解高性能HTTP服务端的负载均衡技术原理

[3] 子弹短信光鲜的背后:网易云信首席架构师分享亿级IM平台的技术实践

[4] 知乎技术分享:从单机到2000万QPS并发的Redis高性能缓存实践之路

[5] 新手入门:零基础理解大型分布式架构的演进历史、技术原理、最佳实践

[6] 阿里技术分享:深度揭秘阿里数据库技术方案的10年变迁史

[7] 阿里技术分享:阿里自研金融级数据库OceanBase的艰辛成长之路

[8] 达达O2O后台架构演进实践:从0到4000高并发请求背后的努力

[9] 优秀后端架构师必会知识:史上最全MySQL大表优化方案总结

[10] 小米技术分享:解密小米抢购系统千万高并发架构的演进和实践

[11] 一篇读懂分布式架构下的负载均衡技术:分类、原理、算法、常见方案等

[12] 通俗易懂:如何设计能支撑百万并发的数据库架构?

[13] 多维度对比5款主流分布式MQ消息队列,妈妈再也不担心我的技术选型了

[14] 从新手到架构师,一篇就够:从100到1000万高并发的架构演进之路

[15] 美团技术分享:深度解密美团的分布式ID生成算法

[16] 12306抢票带来的启示:看我如何用Go实现百万QPS的秒杀系统(含源码)

9、微信团队的其它精华文章

微信后台基于时间序的海量数据冷热分级架构设计实践

微信团队原创分享:Android版微信的臃肿之困与模块化实践之路

微信后台团队:微信后台异步消息队列的优化升级实践分享

微信异步化改造实践:8亿月活、单机千万连接背后的后台解决方案

一份微信后台技术架构的总结性笔记

社交软件红包技术解密(十三):微信团队首次揭秘微信红包算法,为何你抢到的是0.01元

微信团队分享:极致优化,iOS版微信编译速度3倍提升的实践总结

IM“扫一扫”功能很好做?看看微信“扫一扫识物”的完整技术实现

微信团队分享:微信支付代码重构带来的移动端软件架构上的思考

IM开发宝典:史上最全,微信各种功能参数和逻辑规则资料汇总

微信团队分享:微信直播聊天室单房间1500万在线的消息架构演进之路

企业微信的IM架构设计揭秘:消息模型、万人群、已读回执、消息撤回等

IM全文检索技术专题(四):微信iOS端的最新全文检索技术优化实践

微信团队分享:微信后台在海量并发请求下是如何做到不崩溃的

微信Windows端IM消息数据库的优化实践:查询慢、体积大、文件损坏等

微信技术分享:揭秘微信后台安全特征数据仓库的架构设计

企业微信针对百万级组织架构的客户端性能优化实践

揭秘企业微信是如何支持超大规模IM组织架构的——技术解读四维关系链

微信团队分享:详解iOS版微信视频号直播中因帧率异常导致的功耗问题

微信团队分享:微信后端海量数据查询从1000ms降到100ms的技术实践

大型IM工程重构实践:企业微信Android端的重构之路

IM技术干货:假如你来设计微信的群聊,你该怎么设计?

微信团队分享:来看看微信十年前的IM消息收发架构,你做到了吗

微信后团队分享:微信后台基于Ray的分布式AI计算技术实践

一年撸完百万行代码,企业微信的全新鸿蒙NEXT客户端架构演进之路

(本文已同步发布于:http://www.52im.net/thread-4839-1-1.html)

posted @ 2025-06-20 15:26 Jack Jiang 阅读(48) | 评论 (0)编辑 收藏

本文由腾讯技术团队罗国佳分享,原题“微信读书后台架构演进之路”,下文有修订和重新排版。

1、前言

今年是微信读书上线10周年,后台技术架构也伴随着微信读书的成长经历了多次迭代与升级。每一次的组件升级与架构突破,在一个运行了10年的系统上落地都不是一件容易的事情,需要破釜沉舟的决心与胆大心细的业务联动。

微信读书经过了多年的发展,赢得了良好的用户口碑,后台系统的服务质量直接影响着用户的体验。团队多年来始终保持着“小而美”的基因,快速试错与迭代成为常态。后台团队在日常业务开发的同时,需要主动寻求更多架构上的突破,提升后台服务的可用性、扩展性,以不断适应业务与团队的变化。

 
 

2、整体架构设计

微信读书是独立于微信的App,且由于历史原因,开发及运维环境均存在一定的差异与隔离。因此,微信读书的后台服务实现了从接入层到存储层的一整套完整架构。

架构上分解为典型的接入层、逻辑层和存储层:

1)接入层:按业务划分为多个CGI服务,实现了资源隔离。在CGI层面还实现了如路由、频控、接入层缓存、长连接等。

2)逻辑层:采用WRMesh框架构建了多个微服务,这些微服务按业务场景进行划分,实现了不同模块间的解耦。框架也提供了如RPC、路由发现、过载保护、限流频控、监控上报等能力。

3)存储层:主要采用PaxosStore存储用户数据,分为K-V和K-Table两种类型,具备高可用、强一致的特性,针对String和Table两种类型定制了缓存中间件,以适配某些业务场景下对访问存储的性能要求。BookStore提供书籍的存储服务,满足读书场景下对书籍的拆章、修改、下载等需要。此外,也不同程度地使用了腾讯云的PaaS存储服务,以灵活满足更多场景需要。

具体的业务逻辑不再赘述,下面简单介绍下微信读书近几年在后台架构上的一些演进。

3、异构服务间调用:RPC框架

微信读书后台微服务源于Hikit框架,采用C++开发。该框架诞生于广研、QQ邮箱年代,在性能、容灾、运维、监控层面都经受了线上的考验,在微信读书上线初期作为主要框架,支撑了后台服务长达数年。

随着微信读书的发展,越来越多异构的系统发展起来。例如推荐算法系统是独立部署在TKE上的容器服务,采用GO语言开发,好处是历史负担少,运维更加方便、开发更加便捷。

两套系统同时存在带来的问题是如何做好服务治理,推荐系统需要频繁调用后台基础模块获取用户数据,必须要有一套完善的路由管理、容灾机制,且考虑到是异构服务,开发语言也不相同,如果为每种语言都定制开发一套服务治理框架,代价会非常高。

在这个阶段,我们开发了WRMesh框架,采用Sidecar+Business的方式解决这个问题。

Sidecar专注于处理网络层的逻辑,和Business业务层分开为两个进程,由WRMesh脚手架生成代码,上层业务无需感知。

Sidecar集成了Hikit框架中用于服务治理的核心逻辑:通过UnixSocket与Business进行通信,代理Business的所有网络读写。当Business进程中需要发起网络请求时,由WRMesh生成的Client代码会自动识别当前是否在mesh环境中,并转发请求给Sidecar,由Sidecar完成接下来的网络处理。

因此:Business进程可以由任意语言任意框架开发,只要遵循Sidecar的通信协议,只需要薄薄的一层网络协议转换即可接入到Hikit的服务治理框架中。

另外:对于某些有特殊路由逻辑的Client,如KV访问、Batch请求等,代理转发并不能满足要求,因此Sidecar还提供了插件能力集成这些Client逻辑,最大限度为异构Business业务提供原生C++的能力。

随着WXG容器平台P6N的建设越来越完善,许多微信的能力也是基于P6N提供,我们也在思考如何逐步迁移到P6N。由于微信读书后台运维目前依赖于企微团队,有独立于P6N的一套运维体系,我们负责业务和架构开发。

如果要一刀切把所有后台服务迁移至P6N,将会面临几个问题:

1)框架代码需要重新适配,开发环境和现网环境都有巨大的改造成本。

2)迁移不是一蹴而就,后台上百个服务在迁移过程中,会存在新旧服务互调的问题,由于运维环境不互通,微服务之间无法完成服务治理,这种互相调用最终只能通过Proxy来转发,不仅增加了网络的失败率,时延增加,最关键的是这个过程会让容灾体系大打折扣。

3)存储模块的迁移成本和风险巨大,如果不迁移存储模块只迁移了逻辑模块,那势必又会存在2中的问题,这个过程很难收尾。

考虑到人力成本及投入性价比,我们最终采用了折衷的方案:

1)一方面:我们保留了依赖于企微的运维环境,保障绝大多数现成服务的稳定运行。

2)另一面:对于微信P6N中的服务,我们搭建了比较完善的Proxy层,例如Svrkit代理、WQueue代理等,两套架构可以方便进行互通,最大限度的在原有基础上接入微信的新能力。

目前,微信读书已顺利接入如WQueue、FKVOL、SimOL、TFCC等众多微信的能力。

4、书籍数据中台的演进

4.1 技术背景

书籍是微信读书的内容根基,书籍数量的多少、书籍质量的好坏,很大程度上决定了用户是否选择微信读书作为阅读App。

过去:我们依托阅文集团提供电子书资源,免去了书籍上架前繁琐的处理流程,包括排版、审校、元信息管理、更新管理等,后台服务只需要对接阅文API即可方便获取书籍数据,我们只需要关注书籍在平台的存储管理和分发流转即可。

近几年:电子书行业的大环境发生变化,一方面,用户对书籍品类多样性、内容质量有更高的诉求,另一方面,平台对成本、版权等行业因素也更为敏感。因此,我们也在积极探索自签版权,甚至是自出品的模式,尝试走更多不一样的道路。从后台角度而言,从过去单一依赖阅文集团API的模式,慢慢转为开放更多的书籍管理接口,形成书籍数据中台模式,为上层运营同学搭建内容管理平台,让更多人可以方便参与到电子书的制作、排版、上下架、运营管理当中。

以EPUB为例,从内容产出到上架到微信读书,大致经历以下阶段:

1)排版审校:这个阶段多为人工或者部分机器自动化介入。

2)上架预处理:这个阶段需要创建书籍信息,配置各种运营策略,当这本书是重排版上架时,内容发生改变,由于现网已经存在用户的划线笔记、进度等数据,需要有完善指标评估是否适合覆盖上架,当上架时,需要对用户数据进行修复,避免发生错位情况,严重影响用户体验。

3)EPUB解析:当书籍上架后,由于EPUB是单一文件,不适合解析和管理分发,因此后台会把源文件解析成自有格式,包括EPUB拆章、图文分离、样式分离、按章生成离线包等等。

4)生成BookInfo和BookData并落盘:EPUB文件经过解析后,BookInfo和BookData会存储到自建的StoreSvr服务上,StoreSvr针对书籍存储、下载等场景进行了很多优化,具备高可用、低时延的特点,提供了书籍信息获取、按章下载等核心接口。

4.2 建设数据中台

回到最初的目标,我们希望把更多的书籍管理能力开放出来,对上层屏蔽电子书底层的后台逻辑,让运营同学可以更专注于书籍的管理。

因此,我们构建了如下书籍数据中台:

后台服务拆分开StoreAPI和StoreSvr:

1)StoreAPI:提供书籍管理的接口,由运营同学搭建的内容平台与StoreAPI交互,完成书籍的管理工作;

2)StoreSvr:一方面接受StoreAPI的请求,更新书籍数据,另一方面为现网用户提供高可用的服务。

StoreAPI提供了如下接口能力:

  • 1)书籍id分配、上下架;
  • 2)书籍信息创建、修改;
  • 3)书籍内容修改、连载更新、订阅推送;
  • 4)运营策略管理。

此外:如上所述,划线位置和阅读进度等核心UGC数据由于是按文件偏移记录,当书籍文件替换后,这些数据会发生错位,如果不能及时修复,将对用户体验造成巨大影响。尤其在一些热门书籍里,单本书里与位置相关的UGC数据往往能达到亿级别,由于文件替换后位置的偏移具有随机性,并不能采用简单的映射方式解决,在过去,我们开发了专门的修复服务来完成这个事情,针对每一个UGC内容,采用全文模糊查找的方式重新计算新的偏移,并更新的UGC正排、书籍倒排等多个存储中。

但随着用户数据越来越多,书籍替换频率越来越频繁,修复不及时或者失败的问题逐渐暴露出来:

  • 1)修复量大导致修复不及时。过去的修复服务虽然是多机部署,但处理单本书仍只是集中在一台机器上,单机性能有限;
  • 2)修复任务缺乏落盘管理,修复服务一旦重启,任务丢失。

针对上面的问题:我们重新设计了修复服务,目标是最大限度缩短修复时间,并且让整个过程是可靠的。

为此,我们先首手考虑业务流程,我们发现在书籍上架前,运营同学本来就需要依赖UGC的修复情况做前置判断是否覆盖上架,这个过程中虽然是对UGC抽样评估,如果能对这个修复映射结果进行缓存,在正式替换文件后,也能一定程度提升修复速度。

在核心修复流程中,我们进行了较大的重构,把单本书的修复任务拆解成多个子任务,存储在Chubby上,多机器抢锁共同消费这些任务,由于任务有落盘,在服务上线重启过程中,也能马上恢复。修复过程涉及大量的KV写入,并发太高时容易命中单key的限频或者版本冲突,我们为此开发了针对K-Str和K-Table的写入中间件,可以在内存中聚合一批请求进行批量合并写入,缓解KV层面的失败。

目前,微信读书已通过内容平台完成了多家版权方自签,并在探索自出品等内容创作新形式。

5、账号系统的高可用性重构

账号是微信读书后台系统的基石,承担了登录、会话密钥生成与派发、用户资料管理等核心功能,所有的用户请求都需经过账号系统进行鉴权验证用户身份,但凡有一点系统抖动都会影响到整个App的正常使用,严重者还会导致账号被踢出无法再次登录。

账号系统的架构在微信读书诞生之初便一直沿用,同一个号段的账号服务AccountSvr和MySQL部署在同一台机器上,备机采用主从同步的方式获取数据,当主机不可用时,备机承担了所有读请求。

在某些场景下,为了能使访问备机时也具备一定的写入能力,曾经魔改过主备逻辑,但一切都显得治标不治本,且引入了更复杂的系统特性,整个架构略显混乱。在机器裁撤、数据扩容过程中,曾造成过几次严重故障,导致App不可用,严重影响用户体验。究其原因,是因为当时基础设施还不完善,缺少高性能高可靠的强一致存储,MySQL也是手动搭建的,运维成本和风险都非常高。

为了彻底解决这个历史包袱,我们在2024下定决心对其进行重构。重构就意味着要抛弃现有MySQL这套臃肿的存储方案,把数据迁移到新的存储组件上。

这里涉及到的挑战点如下:

  • 1)账号鉴权服务访问量巨大,迁移过程须尽量不增加系统负担,且必须是在不停机的情况下进行;
  • 2)迁移过程中一旦有数据丢失或者错误,会导致用户资料受损,用户登录态丢失,App无法使用;
  • 3)账号系统还涉及用户id分配和回收逻辑,在切换存储时如何保证数据的一致性,不重复分配号码。

背水一战,没有退路可言。在经历了多次论证后,我们决定采用Paxosmemkv作为新的存储组件,全内存、多副本、强一致的特性,很适合作为账号系统的底层存储。

同时,我们为整个迁移过程制定了周密的方案,把每一步进行了分解,且要求每个环节可灰度可回退,同时要做好数据的一致性检查。

在完成数据迁移后,我们还需要对AccountSvr进行重构,抛弃按号段的账号分配、路由、缓存逻辑,以全新的视角设计更简洁的架构。

6、内容召回系统的架构设计

以往微信读书的搜索仅限于基于书名、作者等维度的文本召回,通过自建的全内存索引服务实现书籍的检索。全文检索则基于ES搭建,采用规则分段的方式建立索引,能满足读书大部分场景的需要。

在大语言模型迅速发展的近两年,微信读书作为一个庞大的内容知识库,具有大量的书籍原文资源。同时,用户在微信读书也留下了大量的文字内容,如书评、想法等,这些内容构成了AI问书的内容基石,也是AI问书区别于其它问答工具的核心优势。

基于微信读书构建RAG召回系统,核心挑战如下:

1)基于书籍原文构建全文检索,为了达到最好的效果,往往需要支持按语义进行段落切分,在此基础上构建embedding进行语义召回。微信读书拥有百万级书籍原文数据,此外,对于用户导入书,更是达到亿级别规模。现有架构无论从成本还是耗时上都无法解决。

2)为了支持更多维度的召回,需要对UGC内容进行召回,部分UGC内容属于私密信息,并不向全网公开,只需要满足用户个人检索即可。此时如果用常规的检索系统构建常驻索引,访问率太低,成本难以收敛。

为此,我们针对微信读书不同的RAG使用场景,设计了如下召回架构:

我们把数据划分成两类:全局公开可搜以及用户个人可搜。

1)对于全局公开可搜索的数据:如库内电子书的全文、书籍大纲、书评、人工知识库等,我们构建了一套入库流程,能对源信息进行语义分段、生成正排倒排,语义分段基于开源的chunk模型进行微调,正排基于fkv,倒排则基于ES构建,ES提供了DiskANN方案,通过设置合理的缓存和分片,能在存储成本和召回效率之间取得不错的平衡。对于 App 内主搜等低时延场景,为了满足多种定制化检索需求,我们自建了基于内存索引的Searchsvr服务,支持索引落盘,可以在毫秒级返回电子书搜索结果。

2)对于用户个人数据:如导入书全文、个人想法等,特点是数据量大但使用频率不高,不需要针对全网用户进行检索,如果采用上述方案,会带来成本灾难,性价比极低。为此,我们按用户及物料的维度,基于USearch、Xapian等方案构建了向量及文本索引,这些组件的优势在于可以把单个索引存储成文件的形式,便于落盘,配合一些量化的方法,可以把大大压缩索引大小。在构建索引阶段,按用户+类型构建出不同的索引,并存储在低成本的COS上,当用户需要检索召回时,采用读时加载的方式实时进行召回,结合CFS进行预热可以大大提升检索速度。当检索完成后,定时淘汰策略会把长期不用的索引从CFS中清理,降低存储成本。

7、写在最后

虽然微信读书已经发展了十个年头,但我们的脚步从未停止。

在日常业务开发之余,我们也从未停止思考如何让系统能走得更远、更稳健,抓住每一个可能的优化点,随时做好准备,迎接下一个精彩的十年。

8、相关资料

[1] 腾讯资深架构师干货总结:一文读懂大型分布式系统设计的方方面面

[2] 快速理解高性能HTTP服务端的负载均衡技术原理

[3] 子弹短信光鲜的背后:网易云信首席架构师分享亿级IM平台的技术实践

[4] 知乎技术分享:从单机到2000万QPS并发的Redis高性能缓存实践之路

[5] 新手入门:零基础理解大型分布式架构的演进历史、技术原理、最佳实践

[6] 阿里技术分享:深度揭秘阿里数据库技术方案的10年变迁史

[7] 阿里技术分享:阿里自研金融级数据库OceanBase的艰辛成长之路

[8] 达达O2O后台架构演进实践:从0到4000高并发请求背后的努力

[9] 优秀后端架构师必会知识:史上最全MySQL大表优化方案总结

[10] 小米技术分享:解密小米抢购系统千万高并发架构的演进和实践

[11] 一篇读懂分布式架构下的负载均衡技术:分类、原理、算法、常见方案等

[12] 通俗易懂:如何设计能支撑百万并发的数据库架构?

[13] 多维度对比5款主流分布式MQ消息队列,妈妈再也不担心我的技术选型了

[14] 从新手到架构师,一篇就够:从100到1000万高并发的架构演进之路

[15] 美团技术分享:深度解密美团的分布式ID生成算法

[16] 12306抢票带来的启示:看我如何用Go实现百万QPS的秒杀系统(含源码)

9、微信团队的其它精华文章

微信后台基于时间序的海量数据冷热分级架构设计实践

微信团队原创分享:Android版微信的臃肿之困与模块化实践之路

微信后台团队:微信后台异步消息队列的优化升级实践分享

微信异步化改造实践:8亿月活、单机千万连接背后的后台解决方案

一份微信后台技术架构的总结性笔记

社交软件红包技术解密(十三):微信团队首次揭秘微信红包算法,为何你抢到的是0.01元

微信团队分享:极致优化,iOS版微信编译速度3倍提升的实践总结

IM“扫一扫”功能很好做?看看微信“扫一扫识物”的完整技术实现

微信团队分享:微信支付代码重构带来的移动端软件架构上的思考

IM开发宝典:史上最全,微信各种功能参数和逻辑规则资料汇总

微信团队分享:微信直播聊天室单房间1500万在线的消息架构演进之路

企业微信的IM架构设计揭秘:消息模型、万人群、已读回执、消息撤回等

IM全文检索技术专题(四):微信iOS端的最新全文检索技术优化实践

微信团队分享:微信后台在海量并发请求下是如何做到不崩溃的

微信Windows端IM消息数据库的优化实践:查询慢、体积大、文件损坏等

微信技术分享:揭秘微信后台安全特征数据仓库的架构设计

企业微信针对百万级组织架构的客户端性能优化实践

揭秘企业微信是如何支持超大规模IM组织架构的——技术解读四维关系链

微信团队分享:详解iOS版微信视频号直播中因帧率异常导致的功耗问题

微信团队分享:微信后端海量数据查询从1000ms降到100ms的技术实践

大型IM工程重构实践:企业微信Android端的重构之路

IM技术干货:假如你来设计微信的群聊,你该怎么设计?

微信团队分享:来看看微信十年前的IM消息收发架构,你做到了吗

微信后团队分享:微信后台基于Ray的分布式AI计算技术实践

一年撸完百万行代码,企业微信的全新鸿蒙NEXT客户端架构演进之路

(本文已同步发布于:http://www.52im.net/thread-4839-1-1.html)

posted @ 2025-06-20 15:26 Jack Jiang 阅读(51) | 评论 (0)编辑 收藏

Jack Jiang的 Mail: jb2011@163.com, 联系QQ: 413980957, 微信: hellojackjiang