Jack Jiang

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

2018年7月23日

     摘要: 一、引言要实现一整套能用于大用户量、高并发场景下的IM群聊,技术难度远超IM系统中的其它功能,原因在于:IM群聊消息的实时写扩散特性带来了一系列技术难题。举个例子:如一个2000人群里,一条普通消息的发出问题,将瞬间写扩散为2000条消息的接收问题,如何保证这些消息的及时、有序、高效地送达,涉及到的技术问题点实在太多,更别说个别场景下万人大群里的炸群消息难题了更别说个别场景下万人大群里的炸群消息难...  阅读全文

posted @ 2018-10-17 14:31 Jack Jiang 阅读(20) | 评论 (0)编辑 收藏

本文引用了阿豪的微信公众号文章分享,感谢原作者的分享。

1、前言

随着社会的发展、互联网技术的进步,以前的大型机服务端架构很显然由于高成本、难维护等原因渐渐地变得不再那么主流了,替代它的就是当下最火的互联网分布式架构。

从若干年前大行其道的传统大型机到如今的分布式架构,技术发展已经经历了好几个阶段,我们只有弄明白典型互联网架构在各个阶段的演进,才能更好地理解和体会分布式架构的好处,从而有助于我们序设计适合于自已公司、产品或项目的架构(也包括设计即时通讯网专注的IM和消息推送这类系统,因为技术思路的原理都是一脉相承的)。那么本文我们就来聊聊分布式架构的演进过程,希望能给大家带来眼前一亮的感觉。

点评:即时通讯网作为IM和推送技术研究、学习和分享的社区,整理了大量的跟IM和推广技术有关的基础技术资料(比如网络基础、通信理论、架构基础等),本文内容虽然看起来跟IM和推送技术没有直接的关联性,但因为设计IM和推送系统的技术思路和原理跟典型大型互联网分布式架构都是一脉相承的,因而读懂本文内容对于IM和推送系统的架构设计同样大有裨益。

学习交流:

- 即时通讯开发交流3群:185926912[推荐]

- 移动端IM开发入门文章:《新手入门一篇就够:从零开发移动端IM

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

2、相关文章

如果你已完全掌握本文的相关知识,请移步继续阅读即时通讯网整理的另一篇:《腾讯资深架构师干货总结:一文读懂大型分布式系统设计的方方面面》,该文适合对互联网架构知识有一定了解的程序员阅读和学习,都是不可能多得的技术干货。

3、技术背景说明

我们都知道一个成熟的大型网站的系统架构并非一开始就设计的非常完美,也没有一开始就具备高性能、高并发、高可用、安全性等特性,而是随着用户量的增加、业务功能的扩展逐步演变过来的,慢慢的完善的。 在这个过程中,开发模式、技术架构等都会随着迭代发生非常大的变化。 而针对不同业务特征的系统,各自都会有自己的侧重点,例如像淘宝这类的网站,要解决的重点问题就是海量商品搜索、下单、支付等问题; 像腾讯这类的网站,要解决的是数亿级别用户的实时消息传输;而像百度这类的公司所要解决的又是海量数据的搜索。每一个种类的业务都有自己不同的系统架构。

为了方便展开本文要讲解的内容,我们来简单模拟一个架构演变过程: 我们以 javaweb 为例,来搭建一个简单的电商系统,从这个系统中来看系统的演变过程。要注意的是接下来的演示模型, 关注的是数据量、访问量提升,网站结构的变化, 而不关注具体业务的功能点。其次,这个过程是为了让大家能更好的了解网站演进过程中的一些问题和应对策略。

假如我们要设计的互联网系统需要具备以下功能:

1)用户模块:用户注册和管理;

2)商品模块:商品展示和管理;

3)交易模块:创建交易及支付结算。

请带着上述3个技术点,继续深入阅读本文的正文内容。干货马上开始了。。。

4、架构演进阶段一:单应用架构

如上图所示,这个阶段是网站的初期,也可以认为是互联网发展的早期,系统架构如上图所示。我们经常会在单台服务器上运行我们所有的程序和软件。 把所有软件和应用都部署在一台机器上,这样就完成一个简单系统的搭建,这个阶段的讲究的是效率。效率决定生死。

5、架构演进阶段二:应用服务器和数据库服务器分离

随着网站的上线,访问量逐步上升,服务器的负载慢慢提高,我们应该在服务器还没有超载的时候就做好规划、提升网站的负载能力。假若此时已经没办法在代码层面继续优化提高,那么在单台机器的性能遇到瓶颈的时候,增加机器是一个比较简单好用的方式,投入产出比相当高。这个阶段增加机器的主要目的是将 web 服务器和 数据库服务器拆分开来,这样做的话不仅提高了单机的负载能力,也提高了整个系统的容灾能力。

这个阶段的系统架构如上图所示,应用服务器和数据库服务器完全隔离开来,相互互不影响,大大减少了网站宕机的风险,此阶段我们已经开始关注到应用服务器的管理了。 

6、架构演进阶段三:应用服务器集群

这个阶段,随着访问量的继续不断增加,单台应用服务器已经无法满足我们的需求。 假设我的数据库服务器还没有遇到性能问题,那我们可以通过增加应用服务器的方式来将应用服务器集群化,这样就可以将用户请求分流到各个服务器中,从而达到继续提升系统负载能力的目的。此时各个应用服务器之间没有直接的交互,他们都是依赖数据库各自对外提供服务。

系统架构发展到这个阶段,各种问题也会接踵而至:

1)用户请求交由谁来转发到具体的应用服务器上(谁来负责负载均衡);

2)用户如果每次访问到的服务器不一样,那么如何维护session,达到session共享的目的。

那么此时,系统架构又会变成如下方式:

负载均衡又可以分为软负载和硬负载。软负载我们可以选择Nginx、Apache等,硬负载我们可以选择F5等。而session共享问题我们可以通过配置tomcat的session共享解决。

7、架构演进阶段四:数据库压力变大,数据库读写分离

架构演变到上面的阶段,并不是终点。通过上面的设计,应用层的性能被我们拉上来了, 但数据库的负载也在逐渐增大,那如何去提高数据库层面的性能呢?有了前面的设计思路以后,我们自然也会想到通过增加服务器来提高性能。但假如我们单纯的把数据库一分为二,然后对于数据库的请求,分别负载到两台数据库服务器上,那必定会造成数据库数据不统一的问题。 

所以我们一般先考虑将数据库读写分离,如下图所示。

这个架构设计的变化会带来如下几个问题:

1)主从数据库之间的数据需要同步(可以使用 mysql 自带的 master-slave 方式实现主从复制 );

2)应用中需要根据业务进行对应数据源的选择( 采用第三方数据库中间件,例如 mycat )。

8、架构演进阶段五:使用搜索引擎缓解读库的压力

我们都知道数据库常常对模糊查找效率不是很高,像电商类的网站,搜索是非常核心的功能,即使是做了读写分离,这个问题也不能得到有效解决。那么这个时候我们就需要引入搜索引擎了,使用搜索引擎能够大大提升我们系统的查询速度,但同时也会带来一 些附加的问题,比如维护索引的构建、数据同步到搜索引擎等。

9、架构演进阶段六:引入缓存机制缓解数据库的压力

然后,随着访问量的持续不断增加,逐渐会出现许多用户访问同一内容的情况,那么对于这些热点数据,没必要每次都从数据库重读取,这时我们可以使用到缓存技术,比如 redis、memcache 来作为我们应用层的缓存。

另外在某些场景下,如我们对用户的某些 IP 的访问频率做限制, 那这个放内存中就又不合适,放数据库又太麻烦了,那这个时候可以使用 Nosql 的方式比如 mongDB 来代替传统的关系型数据库。

10、架构演进阶段七:数据库的水平/垂直拆分

我们的网站演进的变化过程,交易、商品、用户的数据都还在同一 个数据库中,尽管采取了增加缓存,读写分离的方式,但是随着数 据库的压力持续增加,数据库的瓶颈仍然是个最大的问题。因此我 们可以考虑对数据的垂直拆分和水平拆分。

垂直拆分:把数据库中不同业务数据拆分到不同的数据库;

水平拆分:把同一个表中的数据拆分到两个甚至更多的数据库中,水平拆分的原因是某些业务数据量已经达到了单个数据库的瓶颈,这时可以采取将表拆分到多个数据库中。

11、架构演进阶段八:应用的拆分

随着业务的发展,业务量越来越大,应用的压力越来越大。工程规模也越来越庞大。这个时候就可以考虑将应用拆分,按照领域模型将我们的用户、商品、交易拆分成多个子系统。

这样拆分以后,可能会有一些相同的代码,比如用户操作,在商品和交易都需要查询,所以会导致每个系统都会有用户查询访问相关操作。这些相同的操作一定是要抽象出来,否则就是一个坑。所以通过走服务化路线的方式来解决。

那么服务拆分以后,各个服务之间如何进行远程通信呢? 通过 RPC 技术,比较典型的有:dubbo、webservice、hessian、http、RMI 等等。前期通过这些技术能够很好的解决各个服务之间通信问题,但是, 互联网的发展是持续的,所以架构的演变和优化也还在持续。

12、本文小结

通过本文,我们通过一个电商的案例,就了解到了分布式架构的演进过程,一环套一环,环环紧密相扣。都是通过业务量和访问量的提升来考虑重构架构设计,以便能够适应当前的环境。不可一蹴而就,也急不来,初创企业必须稳扎稳打,一步一个脚印的走出一条专属自己的路。

本文主要针对的是零基础初学者,如果您想深入了解相关知识,请继续阅读《腾讯资深架构师干货总结:一文读懂大型分布式系统设计的方方面面》。

附录:更多架构方面的技术文章

浅谈IM系统的架构设计

简述移动端IM开发的那些坑:架构设计、通信协议和客户端

一套海量在线用户的移动端IM架构设计实践分享(含详细图文)

一套原创分布式即时通讯(IM)系统理论架构方案

从零到卓越:京东客服即时通讯系统的技术架构演进历程

蘑菇街即时通讯/IM服务器开发之架构选择

腾讯QQ1.4亿在线用户的技术挑战和架构演进之路PPT

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

微信技术总监谈架构:微信之道——大道至简(演讲全文)

如何解读《微信技术总监谈架构:微信之道——大道至简》

快速裂变:见证微信强大后台架构从0到1的演进历程(一)

17年的实践:腾讯海量产品的技术方法论

移动端IM中大规模群消息的推送如何保证效率、实时性?

现代IM系统中聊天消息的同步和存储方案探讨

IM开发基础知识补课(二):如何设计大量图片文件的服务端存储架构?

IM开发基础知识补课(三):快速理解服务端数据库读写分离原理及实践建议

IM开发基础知识补课(四):正确理解HTTP短连接中的Cookie、Session和Token

WhatsApp技术实践分享:32人工程团队创造的技术神话

微信朋友圈千亿访问量背后的技术挑战和实践总结

王者荣耀2亿用户量的背后:产品定位、技术架构、网络方案等

IM系统的MQ消息中间件选型:Kafka还是RabbitMQ?

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

以微博类应用场景为例,总结海量社交系统的架构设计步骤

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

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

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

IM开发基础知识补课(五):通俗易懂,正确理解并用好MQ消息队列

微信技术分享:微信的海量IM聊天消息序列号生成实践(算法原理篇)

微信技术分享:微信的海量IM聊天消息序列号生成实践(容灾方案篇)

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

>> 更多同类文章 ……

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

posted @ 2018-10-15 10:57 Jack Jiang 阅读(29) | 评论 (0)编辑 收藏

     摘要: 1、点评对于IM系统来说,如何做到IM聊天消息离线差异拉取(差异拉取是为了节省流量)、消息多端同步、消息顺序保证等,是典型的IM技术难点。就像即时通讯网整理的以下IM开发干货系列一样:《IM消息送达保证机制实现(一):保证在线实时消息的可靠投递》《IM消息送达保证机制实现(二):保证离线消息的可靠投递》《如何保证IM实时消息的“时序性”与“一致性”?...  阅读全文

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

     摘要: 1、引言特别说明:本文内容仅用于即时通讯技术研究和学习之用,请勿用于非法用途。如本文内容有不妥之处,请联系JackJiang进行处理!我司有关部门为了获取黑产群的动态,有同事潜伏在大量的黑产群(QQ群、微信群)中,干起了无间道的工作。随着黑产群数量的激增,同事希望能自动获取黑产群的聊天信息,并交付风控引擎进行风险评估。于是,这个工作就交给我了,是时候表现一波了……针对同事的...  阅读全文

posted @ 2018-10-08 11:33 Jack Jiang 阅读(37) | 评论 (0)编辑 收藏

     摘要: 1、概述本文来自腾讯视频云终端技术总监rexchang(常青)技术分享,内容分别介绍了微信小程序视音视频和WebRTC的技术特征、差异等,并针对两者的技术差异分享和总结了微信小程序视音视频和WebRTC互通的实现思路以及技术方案。希望能带给你启发。学习交流:- 即时通讯开发交流3群:185926912[推荐]- 移动端IM开发入门文章:《新手入门一篇就够:从零开发移动端IM》(本文同步发布于:ht...  阅读全文

posted @ 2018-09-29 11:05 Jack Jiang 阅读(19) | 评论 (0)编辑 收藏

     摘要: 1、引言消息是互联网信息的一种表现形式,是人利用计算机进行信息传递的有效载体,比如即时通讯网坛友最熟悉的即时通讯消息就是其具体的表现形式之一。消息从发送者到接收者的典型传递方式有两种:1)一种我们可以称为即时消息:即消息从一端发出后(消息发送者)立即就可以达到另一端(消息接收者),这种方式的具体实现就是平时最常见的IM聊天消息;2)另一种称为延迟消息:即消息从某端发出后,首先进入一个容器进行临时存...  阅读全文

posted @ 2018-09-26 14:52 Jack Jiang 阅读(44) | 评论 (0)编辑 收藏

1、MMKV简介

腾讯微信团队于2018年9月底宣布开源 MMKV ,这是基于 mmap 内存映射的 key-value 组件,底层序列化/反序列化使用 protobuf 实现,主打高性能和稳定性。近期也已移植到 Android 平台,一并对外开源。

MMKV 是基于 mmap 内存映射的 key-value 组件,底层序列化/反序列化使用 protobuf 实现,性能高,稳定性强。从 2015 年中至今,在 iOS 微信上使用已有近 3 年,其性能和稳定性经过了时间的验证。近期也已移植到 Android 平台,一并开源。

MMKV最新源码托管地址:https://github.com/Tencent/MMKV

2、MMKV 源起

在微信客户端的日常运营中,时不时就会爆发特殊文字引起系统的 crash(请参见文章:《微信团队分享:iOS版微信是如何防止特殊字符导致的炸群、APP崩溃的?》、《微信团队分享:iOS版微信的高性能通用key-value组件技术实践》),文章里面设计的技术方案是在关键代码前后进行计数器的加减,通过检查计数器的异常,来发现引起闪退的异常文字。在会话列表、会话界面等有大量 cell 的地方,希望新加的计时器不会影响滑动性能;另外这些计数器还要永久存储下来——因为闪退随时可能发生。

这就需要一个性能非常高的通用 key-value 存储组件,我们考察了 SharedPreferences、NSUserDefaults、SQLite 等常见组件,发现都没能满足如此苛刻的性能要求。考虑到这个防 crash 方案最主要的诉求还是实时写入,而 mmap 内存映射文件刚好满足这种需求,我们尝试通过它来实现一套 key-value 组件。

3、MMKV 原理

内存准备:

通过 mmap 内存映射文件,提供一段可供随时写入的内存块,App 只管往里面写数据,由操作系统负责将内存回写到文件,不必担心 crash 导致数据丢失。

数据组织:

数据序列化方面我们选用 protobuf 协议,pb 在性能和空间占用上都有不错的表现。

写入优化:

考虑到主要使用场景是频繁地进行写入更新,我们需要有增量更新的能力。我们考虑将增量 kv 对象序列化后,append 到内存末尾。

空间增长:

使用 append 实现增量更新带来了一个新的问题,就是不断 append 的话,文件大小会增长得不可控。我们需要在性能和空间上做个折中。

更详细的设计原理参考MMKV 原理

4、iOS 指南

安装引入(推荐使用 CocoaPods):

安装CocoaPods

打开命令行,cd到你的项目工程目录, 输入pod repo update让 CocoaPods 感知最新的 MMKV 版本;

打开 Podfile, 添加pod 'MMKV'到你的 app target 里面;

在命令行输入pod install;

用 Xcode 打开由 CocoaPods 自动生成的.xcworkspace文件;

添加头文件#import <MMKV/MMKV.h>,就可以愉快地开始你的 MMKV 之旅了。

更多安装指引参考iOS Setup

快速上手:

MMKV 的使用非常简单,无需任何配置,所有变更立马生效,无需调用synchronize:

MMKV *mmkv = [MMKV defaultMMKV];    [mmkvsetBool:YESforKey:@"bool"];BOOL bValue = [mmkvgetBoolForKey:@"bool"];    [mmkvsetInt32:-1024forKey:@"int32"];int32_t iValue = [mmkvgetInt32ForKey:@"int32"];    [mmkvsetObject:@"hello, mmkv"forKey:@"string"];NSString *str = [mmkvgetObjectOfClass:NSString.classforKey:@"string"];

更详细的使用教程参考iOS Tutorial

性能对比:

循环写入随机的int1w 次,我们有如下性能对比:

更详细的性能对比参考iOS Benchmark

5、Android 指南

安装引入:

推荐使用 Maven:

dependencies{implementation'com.tencent:mmkv:1.0.10'// replace"1.0.10"with any available version}

更多安装指引参考Android Setup

快速上手:

MMKV 的使用非常简单,所有变更立马生效,无需调用sync、apply。 在 App 启动时初始化 MMKV,设定 MMKV 的根目录(files/mmkv/),例如在 MainActivity 里:

protectedvoidonCreate(Bundle savedInstanceState){super.onCreate(savedInstanceState);    String rootDir = MMKV.initialize(this);    System.out.println("mmkv root: "+ rootDir);//……}

MMKV 提供一个全局的实例,可以直接使用:

importcom.tencent.mmkv.MMKV;//……MMKV kv = MMKV.defaultMMKV();kv.encode("bool",true);booleanbValue = kv.decodeBool("bool");kv.encode("int", Integer.MIN_VALUE);intiValue = kv.decodeInt("int");kv.encode("string","Hello from mmkv");String str = kv.decodeString("string");

MMKV 支持多进程访问,更详细的用法参考Android Tutorial

性能对比:

循环写入随机的int1k 次,我们有如下性能对比:

更详细的性能对比参考Android Benchmark

posted @ 2018-09-22 11:20 Jack Jiang 阅读(33) | 评论 (0)编辑 收藏

     摘要: 本文引用了公众号纯洁的微笑作者奎哥的技术文章,感谢原作者的分享。1、前言老于网络编程熟手来说,在测试和部署网络通信应用(比如IM聊天、实时音视频等)时,如果发现网络连接超时,第一时间想到的就是使用Ping命令Ping一下服务器看看通不通。甚至在有些情况下通过图形化的Ping命令工具对目标网络进行长测(比如:《两款增强型Ping工具:持续统计、图形化展式网络状况 [附件下载]》、《网络测试:Andr...  阅读全文

posted @ 2018-09-21 18:12 Jack Jiang 阅读(46) | 评论 (0)编辑 收藏

     摘要: 本文来自知乎官方技术团队的“知乎技术专栏”,感谢原作者陈鹏的无私分享。1、引言知乎存储平台团队基于开源Redis 组件打造的知乎 Redis 平台,经过不断的研发迭代,目前已经形成了一整套完整自动化运维服务体系,提供很多强大的功能。本文作者陈鹏是该系统的负责人,本次文章深入介绍了该系统的方方面面,值得互联网后端程序员仔细研究。(本文同步发布于:http://www.52im...  阅读全文

posted @ 2018-09-18 12:31 Jack Jiang 阅读(42) | 评论 (0)编辑 收藏

     摘要: 1、前言网络通信一直是Android项目里比较重要的一个模块,Android开源项目上出现过很多优秀的网络框架,从一开始只是一些对HttpClient和HttpUrlConnection简易封装使用的工具类,到后来Google开源的比较完善丰富的Volley,再到如今比较流行的Okhttp、Retrofit。要想理解他们之间存在的异同(或者具体点说,要想更深入地掌握Android开发中的网络通信技...  阅读全文

posted @ 2018-09-17 10:44 Jack Jiang 阅读(35) | 评论 (0)编辑 收藏

     摘要: 本文原文内容来自InfoQ的技术分享,本次有修订、勘误和加工,感谢原作者的分享。1、前言自从2018年8月20日子弹短信在锤子发布会露面之后(详见《老罗最新发布了“子弹短信”这款IM,主打熟人社交能否对标微信?》),关于它的讨论不绝于耳,7 天融资 1.5 亿的传闻更是将它推到了风口浪尖(请见《[资讯] “子弹短信”发布一周即融得1.5亿资金》)。&...  阅读全文

posted @ 2018-09-14 13:50 Jack Jiang 阅读(36) | 评论 (0)编辑 收藏

     摘要: 本文来自“人人都是产品经理”公众号作者栗栗粥的原创分享。1、前言移动端的时代里,微信占据了社交领域的半壁江山,不得不让人想起曾经PC时代里的王者“QQ”,微信的爆发和QQ的停滞让很多人认为微信已经彻底将QQ打败,QQ已经不再适合这个时代了。前不久看到一句有意思的分享说:“与其说微信为什么能打败QQ,不如说QQ为什么没有被微信打败。R...  阅读全文

posted @ 2018-09-11 14:58 Jack Jiang 阅读(53) | 评论 (0)编辑 收藏

     摘要: 本文原作者:李越,由银杏财经原创发布,本次内容改动。1、前言上线一周完成1.5亿元融资,上线10天总激活用户数超400万,8月29日单日新增用户超100万,这是子弹短信交出的最新成绩单(详见《[资讯] “子弹短信”发布一周即融得1.5亿资金》)。▲ 老罗的“子弹短信”这个牛逼,又可以吹很久了这样的数据,几乎就要接近移动互联网时代APP最快...  阅读全文

posted @ 2018-09-09 21:02 Jack Jiang 阅读(24) | 评论 (0)编辑 收藏

     摘要: 1、前言随着互联网的发展,面对海量用户高并发业务,传统的阻塞式的服务端架构模式已经无能为力。本文(和下篇《高性能网络编程(六):一文读懂高性能网络编程中的线程模型》)旨在为大家提供有用的高性能网络编程的I/O模型概览以及网络服务进程模型的比较,以揭开设计和实现高性能网络架构的神秘面纱。限于篇幅原因,请将本文与《高性能网络编程(六):一文读懂高性能网络编程中的线程模型》连起来读,这样会让知识更连贯。...  阅读全文

posted @ 2018-09-06 21:09 Jack Jiang 阅读(113) | 评论 (0)编辑 收藏

     摘要: 本文来自公众号“傅老师”(ID:fustory)的原创分享,感谢作者。1、引言如果QQ是一个人,看似风光,其实从出生到成长,过程饱经错荡,堪算坎坷。它的人生历程确实也够励志的了。学习交流:- 即时通讯开发交流3群:185926912 [推荐]- 移动端IM开发入门文章:《新手入门一篇就够:从零开发移动端IM》(本文同步发布于:http://www.52im.net...  阅读全文

posted @ 2018-09-05 17:07 Jack Jiang 阅读(41) | 评论 (0)编辑 收藏

本文由达达京东到家Java工程师季炳坤原创分享。

1、前言

达达-京东到家作为优秀的即时配送物流平台,实现了多渠道的订单配送,包括外卖平台的餐饮订单、新零售的生鲜订单、知名商户的优质订单等。为了提升平台的用户粘性,我们需要兼顾商户和骑士的各自愿景:商户希望订单能够准时送达,骑士希望可以高效抢单。那么在合适的时候提升订单定制化的曝光率,是及时送物流平台的核心竞争力之一。

本文将描述“达达-京东到家”的订单即时派发系统从无到有的系统演进过程,以及方案设计的关键要点,希望能为大家在解决相关业务场景上提供一个案例参考。

关于“达达-京东到家”:

达达-京东到家,是同城速递信息服务平台和无界零售即时消费平台。达达-京东到家创始人兼首席执行官蒯佳祺;

公司旗下,目前已覆盖全国400 多个主要城市,服务超过120万商家用户和超 5000万个人用户;

2018年8月,达达-京东到家正式宣布完成最新一轮5亿美元融资,投资方分别为沃尔玛和京东。

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

2、关于作者

季炳坤:“达达-京东到家”Java工程师,负责“达达-京东到家”的订单派发、订单权限、合并订单等相关技术工作的实现。

3、订单即时派发架构的演进

在公司发展的初期,我们的外卖订单从商户发单之后直接出现在抢单池中,3公里之内的骑士能够看到订单,并且从订单卡片中获取配送地址、配送时效等关键信息。这种暴力的显示模式,很容易造成骑士挑选有利于自身的订单进行配送,从而导致部分订单超时未被配送。这样的模式,在一定程度上导致了商户的流失,同时也浪费了骑士的配送时间。

从上面的场景可以看出来,我们系统中缺少一个订单核心调度者。有一种方案是选择区域订单的订单调度员,由调度员根据骑士的接单情况、配送时间、订单挤压等实时情况来进行订单调度。这种模式,看似可行,但是人力成本投入太高,且比较依赖个人的经验总结。

核心问题已经出来了:个人的经验总结会是什么呢?

1) 骑士正在配送的订单的数量,是否已经饱和;

2) 骑士的配送习惯是什么;

3) 某一阶段的订单是否顺路,骑士是否可以一起配送;

4) 骑士到店驻留时间的预估;

5) ...

理清核心问题的答案,我们的系统派单便成为了可能。

基于以上的原理,订单派发模式就可以逐渐从抢单池的订单显示演变成系统派单:

我们将会:

1)记录商户发单行为;

2)骑士配送日志及运行轨迹等信息。

并且经过数据挖掘和数据分析:

1)获取骑士的画像;

2)骑士配送时间的预估;

3)骑士到店驻留时间的预估等基础信息;

4)使用遗传算法规划出最优的配送路径;

5)...

经过上述一系列算法,我们将在骑士池中匹配出最合适的骑士,进而使用长连接(Netty)不间断的通知到骑士。

随着达达业务的不断迭代,订单配送逐渐孵化出基于大商户的驻店模式:基于商户维护一批固定的专属骑士,订单只会在运力不足的时候才会外发到抢单池中,正常情况使用派单模式通知骑士。

4、订单派发模型的方案选型

订单派发可以浅显的认为是一种信息流的推荐。在订单进入抢单池之前,我们会根据每个城市的调度情况,先进行轮询N次的派单。

大概的表现形式如下图:

举例:有笔订单需要进行推送,在推送过程中,我们暂且假设一直没有骑士接单,那么这笔订单会每间隔N秒便会进行一次普通推荐,然后进入抢单池。

从订单派发的流程周期上可以看出来,派发模型充斥着大量的延迟任务,只要能解决订单在什么时候可以进行派发,那么整个系统 50% 的功能点就能迎刃而解。

我们先了解一下经典的延迟方案,请继续往下读。。。

4.1 方案1:数据库轮询

通过一个线程定时的扫描数据库,获取到需要派单的订单信息。

优点:开发简单,结合quartz即可以满足分布式扫描;

缺点:对数据库服务器压力大,不利于项目后续发展。

4.2 方案2:JDK的延迟队列 - DelayQueue

DelayQueue是Delayed元素的一个无界阻塞队列,只有在延迟期满时才能从中提取元素。队列中对象的顺序按到期时间进行排序。

优点:开发简单,效率高,任务触发时间延迟低;

缺点:服务器重启后,数据会丢失,要满足高可用场景,需要hook线程二次开发;宕机的担忧;如果数据量暴增,也会引起OOM的情况产生。

4.3 方案3:时间轮 - TimingWheel

时间轮的结构原理很简单,它是一个存储定时任务的环形队列,底层是由数组实现,而数组中的每个元素都可以存放一个定时任务列表。列表中的每一项都表示一个事件操作单元,当时间指针指向对应的时间格的时候,该列表中的所有任务都会被执行。 时间轮由多个时间格组成,每个时间格代表着当前实践论的跨度,用tickMs代表;时间轮的个数是固定的,用wheelSize代表。

整个时间轮的跨度用interval代表,那么指针转了一圈的时间为:

interval = tickMs * wheelSize

如果tickMs=1ms,wheelSize=20,那么便能计算出此时的时间是以20ms为一转动周期,时间指针(currentTime)指向wheelSize=0的数据槽,此时有5ms延迟的任务插入了wheelSize=5的时间格。随着时间的不断推移,指针currentTime不断向前推进,过了5ms之后,当到达时间格5时,就需要将时间格5所对应的任务做相应的到期操作。

如果此时有个定时为180ms的任务该如何处理?很直观的思路是直接扩充wheelSize?这样会导致wheelSize的扩充会随着业务的发展而不断扩张,这样会使时间轮占用很大的内存空间,导致效率低下,因此便衍生出了层级时间轮的数据结构。

180ms的任务会升级到第二层时间轮中,最终被插入到第二层时间轮中时间格#8所对应的TimerTaskList中。如果此时又有一个定时为600ms的任务,那么显然第二层时间轮也无法满足条件,所以又升级到第三层时间轮中,最终被插入到第三层时间轮中时间格#1的TimerTaskList中。注意到在到期时间在[400ms,800ms)区间的多个任务(比如446ms、455ms以及473ms的定时任务)都会被放入到第三层时间轮的时间格#1中,时间格#1对应的TimerTaskList的超时时间为400ms。

随着时间轮的转动,当TimerTaskList到期时,原本定时为450ms的任务还剩下50ms的时间,还不能执行这个任务的到期操作。便会有个时间轮降级的操作,会将这个剩余时间50ms的定时任务重新提交到下一层级的时间轮中,所以该任务被放到第二层时间轮到期时间为 [40ms,60ms) 的时间格中。再经历了40ms之后,此时这个任务又被触发到,不过还剩余10ms,还是不能立即执行到期操作。所以还要再一次的降级,此任务会被添加到第一层时间轮到期时间为[10ms,11ms)的时间格中,之后再经历10ms后,此任务真正到期,最终执行相应的到期操作。

优点:效率高,可靠性高(Netty,Kafka,Akka均有使用),便于开发;

缺点:数据存储在内存中,需要自己实现持久化的方案来实现高可用。

5、订单派发方案的具体实现

结合了上述的三种方案,最后决定使用redis作为数据存储,使用timingWhell作为时间的推动者。这样便可以将定时任务的存储和时间推动进行解耦,依赖Redis的AOF机制,也不用过于担心订单数据的丢失。

kafka中为了处理成千上万的延时任务选择了多层时间轮的设计,我们从业务角度和开发难度上做了取舍,只选择设计单层的时间轮便可以满足需求。

1)时间格和缓存的映射维护:

假设当前时间currentTime为11:49:50,订单派发时间dispatchTime为11:49:57,那么时间轮的时间格#7中会设置一个哨兵节点(作为是否有数据存储在redis的依据 )用来表示该时间段是否会时间事件触发,同时会将这份数据放入到缓存中(key=dispatchTime+ip), 当7秒过后,触发了该时间段的数据,便会从redis中获取数据,异步执行相应的业务逻辑。最后,防止由于重启等一些操作导致数据的丢失,哨兵节点的维护也会在缓存中维护一份数据,在重启的时候重新读取。

2)缓存的key统一加上IP标识:

由于我们的时间调度器是依附于自身系统的,通过将缓存的key统一加上IP的标识,这样就可以保证各台服务器消费属于自身的数据,从而防止分布式环境下的并发问题,也可以减轻遍历整个列表带来的时间损耗(时间复杂度为O(N))。

3)使用异步线程处理时间格中对应的数据:

使用异步线程,是考虑到如果上一个节点发生异常或者超时等情况,会延误下一秒的操作,如果使用异常可以改善调度的即时性问题。

我们在设计系统的时候,系统的完善度和业务的满足度是互相关联影响的,单从上述的设计看,是会有些问题的,比如使用IP作为缓存的key,如果集群发生变更便会导致数据不会被消费;使用线程池异步处理也有概率导致数据不会被消费。这些不会被消费的数据会进入到抢单池中。从派单场景的需求来看,这些场景是可以被接受的,当然了,我们系统会有脚本来进行定期的筛选,将那些进入抢单池的订单进行再次派单。

* 思考:为什么不使用ScheduledThreadPoolExecutor来定时轮询redis?

原因是即便这样可以完成业务上的需求,获取定时触发的任务,但是带来的空查询不但会拉高服务的CPU,redis的QPS也会被拉高,可能会导致redis的慢查询会显著增多。

6、结语

我们在完成一个功能的时候,往往需要一些可视化的数据来确定业务发展的正确性。因此我们在开发的时候,也相应的记录了一些订单与骑士的交互动作。从每天的报表数据可以看出来,90% 以上的订单是通过派单发出并且被骑士认可接单。

订单派发的模式是提升订单曝光率有效的技术手段,我们一直结合大数据、人工智能等技术手段希望能更好的做好订单派发,能提供更加多元化的功能,将达达打造成更加一流的配送平台。

附录:更多相关技术文章

伪即时通讯:分享滴滴出行iOS客户端的演进过程

iOS的推送服务APNs详解:设计思路、技术原理及缺陷等

信鸽团队原创:一起走过 iOS10 上消息推送(APNS)的坑

Android端消息推送总结:实现原理、心跳保活、遇到的问题等

扫盲贴:认识MQTT通信协议

一个基于MQTT通信协议的完整Android推送Demo

IBM技术经理访谈:MQTT协议的制定历程、发展现状等

求教android消息推送:GCM、XMPP、MQTT三种方案的优劣

移动端实时消息推送技术浅析

扫盲贴:浅谈iOS和Android后台实时消息推送的原理和区别

绝对干货:基于Netty实现海量接入的推送服务技术要点

移动端IM实践:谷歌消息推送服务(GCM)研究(来自微信)

为何微信、QQ这样的IM工具不使用GCM服务推送消息?

极光推送系统大规模高并发架构的技术实践分享

从HTTP到MQTT:一个基于位置服务的APP数据通信实践概述

魅族2500万长连接的实时消息推送架构的技术实践分享

专访魅族架构师:海量长连接的实时消息推送系统的心得体会

深入的聊聊Android消息推送这件小事

基于WebSocket实现Hybrid移动应用的消息推送实践(含代码示例)

一个基于长连接的安全可扩展的订阅/推送服务实现思路

实践分享:如何构建一套高可用的移动端消息推送系统?

Go语言构建千万级在线的高并发消息推送系统实践(来自360公司)

腾讯信鸽技术分享:百亿级实时消息推送的实战经验

百万在线的美拍直播弹幕系统的实时推送技术实践之路

京东京麦商家开放平台的消息推送架构演进之路

了解iOS消息推送一文就够:史上最全iOS Push技术详解

基于APNs最新HTTP/2接口实现iOS的高性能消息推送(服务端篇)

解密“达达-京东到家”的订单即时派发技术原理和实践》

>> 更多同类文章 ……

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

posted @ 2018-09-04 10:20 Jack Jiang 阅读(42) | 评论 (0)编辑 收藏

     摘要: 本文参考并引用了部分腾讯游戏学院的相关技术文章内容,感谢原作者的分享。1、前言以现在主流的即时通讯应用形态来讲,一个完整的即时通讯IM应用其实是即时通信(英文简写:IM=Instant messaging)和实时通信(英文简写:RTC=Real-time communication)2种技术组合在一起的一整套网络通信系统。之所以以IM这个简写代称整个即时通讯软件,其实是历史原因了(因为早期的诸如I...  阅读全文

posted @ 2018-08-29 18:21 Jack Jiang 阅读(28) | 评论 (0)编辑 收藏

1、引言

2018年8月20日,锤子科技在北京召开了夏季新品发布会。除了新手机,发布会上还正式推出了主打语音功能的即时通讯IM聊天工具:子弹短信。这款工具此前今年早些时候在「鸟巢」发布会上初次亮相,在经历了几个月的测试后,如今终于正式上线了(想要尝鲜的可以去官网下载:https://im.smartisan.com/,细节上坑还比较多,请自行体验)。

▲ 锤子科技2018夏季新品发布会
▲ “子弹短信”的多端效果图

从“子弹短信”官网上的效果图来看,这款IM目前至少支持iOS、Android、Web PC 3个端,还算是比较主流。在IM这片被巨头们早已稳固的红海,已经很久没有出现足够引起关注的产品了,老罗真是勇气可佳。自从2013年阿里的来往和网易的易信发布以来,这个市场鲜有触碰者。

▲ 2013年有两款IM新品问市(本图来自《史上最全即时通讯软件简史(精编大图版)[附件下载]》)

那么,老罗的“子弹短信”到底有什么特色?能否对标熟人社交的标杆产品微信呢?我们继续往下看。。。

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

2、「语音转文字」是“子弹短信”的核心特色

与其他同类工具最大的一点区别是,子弹短信把「语音转文字」放在了最重要的位置。进入聊天界面,按下蓝色的麦克风发送语音,子弹短信会自动将语音转换成文字。默认设置下,子弹短信会同时发送语音和文字消息,你也可以根据需要进行调整。

这样的好处是发送信息的一方可以根据自己的习惯来输入信息,但接受信息的一方在收到通知时可以直接看到文字,而不用打开应用来查看。相信有不少微信的用户会遇到收到一堆通知显示「语音」的情况,这种问题在子弹短信上就得到了解决。 

当然,要想实现好这一点,「语音转文字」必须要有足够高的成功率。在我们的测试中,子弹短信大部分情况下都能很好地完成转换。虽然偶尔也会出现识别的问题,好在你还可以通过听语音的方式再次确认。

另外,如果你向通讯录里的好友发送子弹短信,但对方当前没有下载子弹短信的话,信息会自动以手机短信的形式发送,这样即便对方不是子弹短信的用户也能收到信息。

3、“子弹短信”的原则:一切都为了「更快一步」

「快如闪电」子弹短信的广告语。为了达成这个目的,子弹短信做了很多工作。首先是全局的悬浮球功能。打开后你可以直接通过按住悬浮球来录入语音,然后选择联系人即可发送。

进入 App 后,点击消息列表的右侧的麦克风按钮可以直接回复消息,消息列表可同时查看多条未读消息,这些功能降低了用户点击进入对话的频率。 

如果你正在使用 Smartisan 手机的话,你还可以配合「闪念胶囊」来直接把胶囊当作文字信息进行发送。

总之,这些设计都是为了能让用户「更快一步」地发送和回复消息。「效率」一直是锤子科技产品的主打特色,而子弹短信在功能上的侧重也应证了这一点。

随着 Android 和 iOS 系统支持锁屏界面通知回复,越来越多的用户开始习惯不进入 App 直接回复消息。如果子弹短信将来能实现直接在锁屏界面录入语音发送,相信回复效率还能再提升一步。

4、「人性化」的小功能

锤子科技的产品从来都不缺乏一些有趣又实用的小功能,子弹短信这次也不例外。例如,你可以将任何信息设置为稍后处理,方便你标记出那些你需要回复和处理的信息。如果你平时习惯在聊天工具里处理工作的话,这样一个随手可用的「暂存箱」是非常有必要的。 

另外子弹短信还支持「引用回复」功能,在多人聊天的情况下很实用。长按某一条消息点击「引用并回复」,你就可以针对这一条消息进行回复,避免意义不明的问题。

还有一个有趣的功能叫「这是谁来着?」。我们有时会遇到因为跟对方不经常联系导致一换头像就不认识了的尴尬。在子弹短信里,点击联系人信息可以看到好友的历史头像。如果你觉得还是记不起来的话,可以点击底部的「这是谁来着?」,应用会显示与该好友第一次的对话记录,帮你回想起来这是谁。

更多子弹短信的功能,可以看看这篇《有点特别的聊天工具——子弹短信》。

5、小结一下

子弹短信是一款追求「快」的IM聊天工具。从语音出发,在功能设计的各个节点上想办法给用户带来「更快一步」的体验,从这个方面来说,它有着自己很鲜明的特色。

不过,在目前这个大环境下,想要找到自己的位置,子弹短信还需要回答一个核心问题:已经有微信这样强大IM,我们为什么还需要另一款聊天工具?

聊天工具的本质是用来连接人的社交关系,而子弹短信的各种功能相比于微信来说更适合于工作场景。如果你觉得微信在工作交流上不够好用,想尝试一下把自己的工作和生活进行区分,并且有能力自己选择工具,或许子弹短信是一个值得一试的选择。

不过,要想跟微信对标,哪有那么容易,你以为微信的成功是个偶然吗?请看看下面的文章:

微信七年回顾:历经多少质疑和差评,才配拥有今天的强大

前创始团队成员分享:盘点微信的前世今生——微信成功的必然和偶然

即时通讯创业必读:解密微信的产品定位、创新思维、设计法则等

好了,即时通讯产品真的没有那么容易成功:《为什么说即时通讯社交APP创业就是一个坑?》。不过,但愿“子弹短信”是个例外。

附录:更多文章

技术往事:微信估值已超5千亿,雷军曾有机会收编张小龙及其Foxmail

QQ和微信凶猛成长的背后:腾讯网络基础架构的这些年

闲话即时通讯:腾讯的成长史本质就是一部QQ成长史

2017微信数据报告:日活跃用户达9亿、日发消息380亿条

腾讯开发微信花了多少钱?技术难度真这么大?难在哪?

技术往事:创业初期的腾讯——16年前的冬天,谁动了马化腾的代码》 

技术往事:史上最全QQ图标变迁过程,追寻IM巨人的演进历史》 

技术往事:“QQ群”和“微信红包”是怎么来的?》 

开发往事:深度讲述2010到2015,微信一路风雨的背后》 

开发往事:微信千年不变的那张闪屏图片的由来》 

开发往事:记录微信3.0版背后的故事(距微信1.0发布9个月时)》 

一个微信实习生自述:我眼中的微信开发团队

首次揭秘:QQ实时视频聊天背后的神秘组织

为什么说即时通讯社交APP创业就是一个坑?

微信七年回顾:历经多少质疑和差评,才配拥有今天的强大

前创始团队成员分享:盘点微信的前世今生——微信成功的必然和偶然

即时通讯创业必读:解密微信的产品定位、创新思维、设计法则等

>> 更多同类文章 ……

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

posted @ 2018-08-22 19:47 Jack Jiang 阅读(59) | 评论 (0)编辑 收藏

     摘要: 1、前言可能有初学者会问,即时通讯应用的通信安全,不就是对Socket长连接进行SSL/TLS加密这些知识吗,干吗要理解HTTPS协议呢。这其实是个误解:当今主流的移动端IM数据通信,总结下来无外乎就是长连接+短连接的方式,长连接就是众所周之的TCP、UDP、WebSocket(WebSocket的本质还是TCP),而短连接就是HTTP/HTTPS了。即时通讯IM应用中,短连接的安全跟长连接相比,...  阅读全文

posted @ 2018-08-20 18:24 Jack Jiang 阅读(56) | 评论 (0)编辑 收藏

     摘要: 1、前言跨平台一直是老生常谈的话题,cordova、ionic、react-native、weex、kotlin-native、flutter等跨平台框架的百花齐放,颇有一股推倒原生开发者的势头。为什么我们需要跨平台开发? 本质上,跨平台开发是为了增加代码复用,减少开发者对多个平台差异适配的工作量,降低开发成本,提高业务专注的同时,提供比web更好的体验。嗯~通俗了说就是:省钱、偷懒。目...  阅读全文

posted @ 2018-08-13 10:51 Jack Jiang 阅读(141) | 评论 (0)编辑 收藏

     摘要: 本文内容由公众号“格友”原创分享。1、引言(不羁的大神,连竖中指都这么帅)因为LINUX操作系统的流行,Linus 已经成为地球人都知道的名人。虽然大家可能都听过钱钟书先生的名言:“假如你吃个鸡蛋觉得味道不错,又何必认识那个下蛋的母鸡呢?” 但是如果真是遇到一个“特别显赫”的鸡蛋,很多人还是想看看能生出这颗神蛋的母鸡的,或者想...  阅读全文

posted @ 2018-08-09 16:32 Jack Jiang 阅读(70) | 评论 (0)编辑 收藏

     摘要: 1、引言本文来自新浪微博视频转码平台技术负责人李成亚在LiveVideoStackCon 2017上的分享,由LiveVideoStack整理成文。李成亚分享了微博短视频如何提升用户体验、降低成本的思路与实践,包括提升短视频发布速度,降低长视频转码时间,通过新的Codec减少带宽成本等。本文的短视频技术跟IM的单聊、群聊、朋友圈里的小视频是类似的东西,文中针对短视频的相关优化实践可以为您的IM小视...  阅读全文

posted @ 2018-08-06 16:34 Jack Jiang 阅读(28) | 评论 (0)编辑 收藏

     摘要: 1、前言对于广大Android开发者来说,Android O(即Android 8.0)还没玩热,Andriod P(即Andriod 9.0)又要来了。下图上谷歌官方公布的Android P发布路线图:Android P的最后一个开发者预览版(即DP5)已如期发布于2018年7月26日,根据上面这张发布路线图,相信Android P的正式版将很快到来。对于Andriod开发者来说,不管Andri...  阅读全文

posted @ 2018-08-02 15:27 Jack Jiang 阅读(138) | 评论 (0)编辑 收藏

     摘要: 本文内容由“微信多媒体团队”整理发布。1、引言广州TIT创意园,这里是腾讯在广州的研发团队所在地,LiveVideoStack采访了微信多媒体内核中心音视频算法高级工程师梁俊斌(Denny)。从华为2012实验室到腾讯,过去十余年梁俊斌一直专注在音频技术。他告诉LiveVideoStack:音频技术还有许多难点需要解决,而作为技术人也延展到应用场景,关注用户需求。本文整理了...  阅读全文

posted @ 2018-07-31 15:25 Jack Jiang 阅读(37) | 评论 (0)编辑 收藏

     摘要: 1、前言本文要分享的消息推送指的是当iOS端APP被关闭或者处于后台时,还能收到消息/信息/指令的能力。这种在APP处于后台或关闭情况下的消息推送能力,通常在以下场景下非常有用:1)IM即时通讯聊天应用:聊天消息通知、音视频聊天呼叫等,典型代表有:微信、QQ、易信、米聊、钉钉、Whatsup、Line;2)新闻资讯应用:最新资讯通知等,典型代码有:网易新闻客户端、腾讯新闻客户端;3)SNS社交应用...  阅读全文

posted @ 2018-07-30 10:51 Jack Jiang 阅读(48) | 评论 (0)编辑 收藏

     摘要: 1、关于作者余果:腾讯社交用户体验设计部(ISUX)高级UI工程师,前端开发组负责人,熟悉前端开发、iOS开发、PHP开发和Ruby开发等;曾独立开发iOS APP(撸大师)和CMS(33PU);翻译有《众妙之门: 网站重新设计之道》和《响应式Web设计全流程解析》,著有《Web全栈工程师的自我修养》;平时喜欢编程、写作、演讲、摄影和英语等,希望自己能做一个终生学习者。关于腾讯ISUX:腾讯ISU...  阅读全文

posted @ 2018-07-27 12:32 Jack Jiang 阅读(40) | 评论 (0)编辑 收藏

     摘要: 1、引言我们常常会听说,某个互联网应用的服务器端系统多么牛逼,比如QQ、微信、淘宝。那么,一个大型互联网应用的服务器端系统,到底牛逼在什么地方?为什么海量的用户访问,会让一个服务器端系统变得更复杂?本文结合作者多年的互联网系统设计实践经验,从最基本的技术概念开始,带你探寻服务器端系统架构的方方面面。本文适合有过几年工作经验、正处于技术上升期的程序员阅读,内容少有浮夸,多为实践经验总结,希望能为您的...  阅读全文

posted @ 2018-07-25 17:13 Jack Jiang 阅读(64) | 评论 (0)编辑 收藏

     摘要: 1、引言图片通常是移动端应用流量耗费最多的部分,并且占据着重要的视觉空间。以大家最常用的即时通讯IM应用为例,应用中存在大量的图片数据往来(比如图片消息、用户相册、用户头像等等)。合理的图片格式选用和优化不仅能减小图片传递过程中的数据量、提升视觉效果,还能显著降低服务端的带宽、计算资源等基础设施成本,一举多得。本文我们一起全面分析学习目前主流和新兴的几种图片格式的特点、性能、调优等,以及相关开源库...  阅读全文

posted @ 2018-07-23 15:36 Jack Jiang 阅读(38) | 评论 (0)编辑 收藏

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