百万级 大型J2EE Push Mail 项目后记

来源:百度文库 编辑:神马文学网 时间:2024/04/25 23:08:03

第一部分


昨天一个新同事向我问起了Push Mail的解决方案的整体架构和思路,滔滔不绝的讲完以后,勾起了我不少的回忆。那是在3年前发生的事情,当时这个客户(美国人)的需求就是一句话:“我要做一个Push Mail全套解决方案。” 就是这句话我们写了厚厚的一份英文需求文档。从需求分析到最后交付我们将尽用了2年左右的时间,从 系统架构、程序发开、再到系统重构、这样的经历依然 历历在目。

项目当时就我们5个人在做,可以说这5个人都是百里挑一的人选,但我们需要面对以下挑战:
1、虽然都是搞J2EE的老手,都没有接触过Push Mail这样的国外项目,所以对业务的精髓不能完全吃透,导致理解需求比较吃力。
2、在3个月内完成演示版本,证明业务可行性;9个月内构建完全产品化的系统。
3、系统需要支持大并发,海量数据,系统必须达到很高性能指标。
4、提供7X24及时服务,在第一时间解决任何技术问题。(有时差,客户在睡觉我们在工作,客户在工作,我们不能睡觉还在工作。)
5、对J2EE以外的技术只有1-2人了解。比如:分布式计算、应用服务器,数据库集群技术 和 大型系统的数据、文件 存储方案。

但是再我们所有成员的不断努力下,这些困难都被我们一一解决。

项目里面牵涉打了J2EE 领域多项技术,也算是一个难点吧,因为下列技术跨度都比较大,例如:
1、Servlet (协议请求、回送)
2、XML (协议解析)
3、JavaMail (邮件收发)
4、EJB (分布式计算)
5、ibatis(数据库操作)
6、JMS/MDB (采用分布架构中的内部通讯方式)
7、多线程和线程池 (多任务提取邮件)
8、还有很多 J2EE规范以外的技术, 例如:httpclient 网页抓取、html 解析、Office附件处理、图片压缩 、加密/解密 等等。

客户全部选择了将服务器部署在Amazon云环境上,大约总共有40几台。其中数据库采用了MySQL服务器,我们运用分库+同步技术来解决大规模存储和查询,应用服务器我们采用了Jboss。 MQ服务器采用的是Jboss的MQ。前端采用Apache和CDN做请求压力分载。操作系统采用64位的 Ubuntu 8。

第二部分

这个 Push Mail 项目留给我最深的影响就是给我们团队来了丰厚的利润,客户他是一个美籍印度佬,就是我们常说的那种有钱人,做事爽快、干脆,也是全球移动领域具有影响的专家,因此他要求我们提供的服务必须是具有一个国际化专业水准的团队(International  Corporate)。所谓专业化的主要需要体现在 1执行规范、2执行效率、3执行细节 这3个重要的环节上。

在项目实施过程中除了搞J2EE 的开发者, TA、QA 、SA、DBA  一个也不能少。
1、TA 小组根据制定的项目执行规范监管我们每个开发成员 在每个stage和里程碑的执行力,从需求分析的文档编写规范一直到最后的项目交付,他们才算结束使命。

2、QA 测试团队在我们需求和概要设计 就开始对jboss和mysql其他几项技术学习,其中测试人员与我们一同参与需求分析,这样将来他们才知道该如何配合我们做各种测试,进行深入的测试,而不是我们在引导他们在测试,他们100%能知晓业务 并且制定出不同的测试计划。并不是依靠我们自己测试或者我们在指导他们测试,那样运动员和裁判员都是同一人,那样肯定出问题。

3、SA 的压力最大 需要对系统整体的架构进行设计这个都是分内的事情,更重要的 在真实生产的环境遇到致命性错误,可以回退或者拿出现成的备份方案,把问题在短时间内化解。

4、DBA  需要从高往低的系统架构层次进行对数据库设计与整体规划,必须根据客户需求设计出具有远景的 方案,不是一成不变,更不是所谓“一步到位”的规划设计,是根据预计的数据增长 进行实施规划的不同方案。

5、开发者们需要对每个业务模块都要详细了解。因为我们需要降低风险 开发者们如果出现有人家里有事或者生病 任何一个成员都可以替代,不会出现我今天不来上班,导致项目进度拖慢一天的现象。

在产品在设计开发到发布的过程中,我们分为四个阶段  prototype、demo、stage、producting

prototype 是一个原型,主要完成核心的部分,完成核心的功能和业务逻辑,开发团队内部评估使用。

demo 阶段是将产品的主要部分演示给客户看,让客户确认主体的方向。

stage 根据项目计划分为多个不同的stage版本,每个stage阶段的版本都会在stage服务器上由我们 测试人员先测试5-7天,再发布到真实的生产环境中。客户对这样的流程要求的非常的严格。因此 stage 服务器的配置和数量与 producting 环境是100%相同的,没有他们的邮件确认我们是不能擅自发布到 producting 环境中的,如果 producting 环境出现问题,必须在短时间内能回归到上一个稳定版本的状态 。 后期会在 stage producting 服务器上轮回 ,fix –> testing —> release。

我们当时使用SVN和Trac,值得一提的是Trac这个东西,客户有任何需求变更 通过 Trac 系统 TA,QA,DBA,SA  统统都会知道,并且知道我们会在下个版本 什么时候 会  发布在stage 服务器上,我们也能在第一时间知道他们对 当前版本的 性能情况,客户也能看见但似乎他们并不在意这个结果,因为他们需要知道我们当前的执行状态是否符合计划,其实TA的成员比他们更加关注我们的项目进度,呵呵。因此,所有人的开发进度执行情况都在trac上进行展现。客户也可以看见,也可以回复,所有信息所有人同步。

呵呵,另外那三十几台需要发布应用程序的机器,分别发布不同的应用程序或者不同的版本不可能完全依靠人工完成,我们需要一个半自动化的工具帮助我们完成,所以我们选择了hudson和自己编写的liunx脚本。这样可以提供效率,并且大大减少了发布时出错的机率。

先写这么多,有什么忘记的地方我回头补上,下一篇将开始 讲述 技术方面的那点事儿了。

第三部分

自从1998年 Google 利用廉价的互联网计算机,以最快的速度提供最精确的搜索结果, 这一创新技术成功地缩短了响应时间,提高了可扩展性,并降低了成本。是当今众多公司一直在效仿的技术。在这一项目中完全采用低廉的硬件投入,期望能通过软件的手段、利用分布式计算、集群技术 达到高效的计算,并且能加强系统的可扩展性,提高系统软硬件资源的使用率, 降低系统运行中瞬间的瓶颈。

通过这个项目让我们充分的认识到一个用户的系统 例如:单用户的 OutLook 和 一个百万级的系统的是截然不同的,系统中仅有100条数据和100w条数据的系统更是截然不同的,从100w条数据上升到上亿条数据那样的系统更会让人感受到无论从代码结构、数据库设计还是从系统架构都是完全不同的设计。

客户端环境
    MKT    SDK
    C        语言开发环境   

服务器端环境     
    Apache2            Web服务器
    mod_proxy        Web负载均衡模块   
    HAProxy             前端、数据库负载分载(后期使用)
    Java EE1.5        语言开发环境
    Jboss 4.0.5 GA    应用服务器
    Jboss MQ        JMS服务器
    MySQL  5.1.4        数据库服务器
    EJB 3.0            Java 开发API 工具
    iBATIS            Java 开发API 工具
    OsCache            缓存组件
    Gluster DFS        分布式文件系统
    Nagios            系统监控、报警工具
    Qmail              1台发送邮件服务器

压力测试工具
    Apache Jemter
    Apache AB

当前运行状态
    每月大约有4000多新注册用户
    当前注册大约用户数量200-300w
    每天大约有90-110w用户登录
    每天大约接收500-700W 封邮件
    每天大约接收附件3-5G,超过5M以上的附件将不接受
    每天数据库增加 2W条记录
    每天数据库接受 700W 次请求
    每秒并发最大18000个请求

整体架构

概览
总体上概括,系统分为 1客户端,2服务器端,3SP Mail Server 端,4 CRM 端   下面将讲述整个系统的概况。

后端发送邮件实现过程

1.手机客户端通过http网络协议发送XML数据到服务器端,

2.服务器端接收到xml数据进行解析,服务器端传输层的web请求模块将xml 协议解析,并且重新包装成pojo 对象,

3.中间有一个业务模块分发器将 获得pojo对象交给不同的业务模块进行处理,如:DAL层、消息中间件,

4.将处理完成的业务通过业务模块分发器包装成xml报文回送给客户端。

整个业务处理组成部分如图所示:

 

内部业务逻辑

1.定时器从数据库装载帐户,采用多线程实现一个线程池,我们称这个玩意儿叫做任务工厂,

2.定时向JMS服务器中不同的消息队列发送消息 ,消息接收端收到消息后驱动业务模块去 SP Mail Server  收取邮件,

3.收邮件模块 上SP Mail Server  获取邮件列表头信息,并且和数据库中已存在的邮件列表头信息进行新邮件比对,

4.收取新邮件并且将邮件信息保存到数据库或者缓存,当用户需要读取的时候将从系统的数据库或者缓存中读取,

5.保存完毕后,将通过IP Push 或者 SMS Push 技术 通知用户上服务器获得新邮件。

后端内部架构

Web层
用户的所有请求都是从web层进入,最上层是apache将请求分载到每台AP 服务器 Jboss的web容器上,web容器请求处理用户提交的各种xml请求协议,并且回送处理结果xml协议给客户端,apache经过几次优化并且开了20个进程处理用户所有请求分发,另一台apache standby 通过 heartbeat做备份,如果apache server1失效 将会自动转移到 apach server2上去。在项目后期我们采用HaProxy来替代了Apache的转发工作,也许2者进行优化后的功效相差不了多少,但是我们认为HaProxy安装、移植、部署 比Apache更加方便,最高可以处理2.7W的并发请求处理,并且可以采用leastconn 负载均衡算法,将当前连接请求数量最小的服务器进行命中。

任务调度
检测用户的新邮件功能依赖系统中的任务调度模块,我们管这个模块叫做任务工厂,他先从数据库中装载账户,再放入缓存中,每3分钟装载所有账户一次,向JMS服务器发送消息请求新邮件检查。
从缓存中读取是为了降低数据库的压力, 因为这样的操作太频繁了而且数据量越来越大,会导致数据的压力增大。任务工厂是系统中非常重要的模组,如果任务工厂停止工作将不能检测到所有用户的新邮件, 整个Push Mail系统将会瘫痪,所以必须能支持压力分载和失效转发的功能。

BTW:任务工厂模块最开始在demo的时候采用Quartz 放入Spring微容器中进行集群,因为不能从缓存中直接读取,将来系统账户会越来越多,导致数据库那边的压力越来越大,所以后期我们用 线程池+oscache集群技术 自己开发了一套类似 Quartz+spring集群的方案。另外,这个项目发生在没有Memcached的年代,如果当时使用Memcached也许设计上会更加完美些。

分布式应用
国外用户使用邮箱账号多数都是以 Gmail、AOL、Hotmail、Yahoo 4大邮箱为主,所以我们将处理邮件具体操作的机器分为4组,每组机器处理具体不同的邮箱账号的邮件存取,将整个业务中负荷最大的部分进行分散,独立计算, 每组中的服务器都支持 失效转发和压力分载。
 

EJB消息驱动Bean /JMS  消息系统
系统多线程的定时器,将系统中所有用户帐号装载后由多个JMS发送端发送Queue消息到不同的消息队列,例如:Gmail消息队列、AOL消息队列、Hotmail消息队列、Yahoo消息队列,然后接收端将接收不同的消息队列发送过来的消息,通常我们每组消息队列使用一个发送端对应多个接收端,每组接收端,例如:接收到一批Yahoo账号的JMS消息,将会由多个接收端,例如:接收到一批Yahoo账号的JMS消息,接收端将通过消息分发给不同的计算机组,JMS服务器端采用集群技术,2台JMS服务器上建有4个消息队列,不仅可以进行压力分载,还可以进行失效转发。将4大邮箱分为4个业务群组,每分组各4台消息接收端,一共16台,加上JMS MOM 消息中间件服务器一共18台,在得到上层模组传送过来的指令后,16台专门进行用户邮件解析、发送的操作。

数据库
8台 16 GB内存 、4个CPU 的机器作为 我们使用的MySQL数据库,每台MySQL数据库都安装了XtraDB MySQL插件,主要提高MySQL innodb引擎的读写性能,对于MySQL的整体性能我们主要还是通过调整MySQL的内部参数来进行优化。MySQL原本算使用 NDB引擎来做数据同步和单点失效的,经过实际情况我们反复比对还是使用传统MySQL数据同步来完成我们预想的效果,因为MySQL的集群在我们当时还不算很成熟,而且这玩儿比较消耗内存。

分片(sharding)
每个用户的请求都包含一个用户的ID,我们将判断不同的用户ID到不同的数据上进行操作,当然我们也不是每张表都做拆分,由于用户越来越多我们只把邮件相关的表进行拆分,例如:邮件列表、邮件内容、邮件附件表 3张表近拆分。每张表中都有用户ID这个字段,DAL层根据用户 ID 进行判断 数据的路由规则到具体对应范围的数据库进行存取数据,比如ID在1-10000之间的用户对应到数据库1, ID在10001-20000范围对应到数据库2,以此类推,但是不能在不停机的状态下动态添加数据库服务器节点,后来我们想了一个办法,添加节点的时候通过JMS消息通知每个机器上的DAL层重新读取数据路由配置一次,系统中的数据需要和系统中的CRM系统相结合,后期我们又建立了一个全局数据库(global),这个全局数据库只作为数据索引,为了将所有数据同步,操作所有数据库的记录通过JMS消息定时向全局数据库(global)进行批量操作,如果每笔执行同步操作,将会降低系统性能。

分布式文件系统(DFS)
用户邮件的所以邮件附件都保存在 分布文件系统 前期我们采用Gluster,后期我们采用了MooseFs搭建 使用分布式文件系统是为了能满足我们2点需求 1、由于系统用户不断的增多,导致我们需要保存的邮件附件不断增长,我们需要可以做到当磁盘容量不足时在不停机的情况下无限的添加存储的容量,并且操作方便。2、对于保存的数据可以实时同步,防止单点失效,数据丢失。

JavaMail
我们将Sun  JavaMail 的源代码进行改写,提高收邮件的效率,这样我们在代码中向POP3邮件服务器发送一个命令,立刻在瞬间可以获得上千份邮件的MessageID的头信息。可参见 《修改源码提高JavaMail比较新邮件效率》一文中提到的具体技术。发送邮件采用的是Apache 的 Commons email来简化发送邮件的操作。

 

经验与教训

1.HAProxy的扩展使用,不仅仅可以使用在Web方面,还可以使用到其他应用层,进行压力分钟, 例如,可以应用到 数据MySQL。可以参考这篇文章,http://www.alexwilliams.ca/blog/2009/08/10/using-haproxy-for-mysql-failover-and-redundancy

2.JVM的优化是任何大型J2EE项目中必不可少的话题,当然并不是你把JVM的内存使用空间设置的越大越好,SUN的官方网站已经指出,不同的操作系统,不同的应用,建议你的JVM内存分配大小是不同的。另外,对与不同的JVM也有不同的参数配置,通常的JVM配置参数可以参见这里,http://www.oracle.com/technetwork/java/javase/tech/vmoptions-jsp-140102.html

3.Linux系统上的swap 空间,4G和4G内存 以内的机器还是非常有必要使用swap空间的,有一次有一个应用服务器系统down机,花了很久的时间才发现,居然是忘记建立Swap空间,多么简单的问题啊,可是把我们折磨了3个星期,3个星期里被老板和客户骂的惨不忍睹,血的教训,让我们再也不会忘记小内存机器建立swap空间了。

4.Jboss MQ的抱怨,首先声明我不是在给Apache 的JMS做广告,因为JbossMQ实在是不好用,而且性能也不咋地,我看见有一篇文章说到JbossMQ的性能在同类产品中是最好的,不知道结果是怎么来的,还是建议大家还是使用Apache的JMS,文档全面啊,不懂就可以找到很全面的资料,jboss MQ 资料的支持不多。

5.MySQL的优化,MySQL的一些参数调优就不说了,但使用MySQL 插件以后给我们带来了不少性能上的提升,特别是在数据查询上的瓶颈可以得到不少缓解。

6.MySQL集群,这玩意儿是个不错的解决方案,可是没有足够的内存是玩不起的,因为MySQL的集群因为完全依赖内存,另外对表的字段也有特别的限制,用起来有点不太自然,不支持所有大字段的类型。如果是老系统迁移过来,估计要折腾一段时间才能合用。

7.JMS 消息的使用,不需要放入数据库,将消息内存当中,并且对JVM运行参数进行优化 ,对与客户端的代码也需要进行优化,关闭消息id (setDisableMessageID),减少消息的大小并且 省去了创建唯一ID的时间。关闭消息的时间戳。如果不需要时间戳,用MessageProducer的setDisableMessageTimeStamp()方法将其关闭。避免使用AUTO_ACKNOWLEDGE。 AUTO_ACKNOWLEDGE  使得每收到一个消息就要向服务器发送一个通知--这样增加的网络传输的负担。如果可能,尽量使用 DUPS_OK_ACKNOWLEDGE或者CLIENT_ACKNOWLEDGE。或者使用事务性会话,将通知在提交时批量完成。