搜狗商业平台Java技术实践
自从1995年露面以来, Java已经经历了二十多年的时间。在这二十年里, IT技术变化很大, Java一直凭借它的可移植性、跨平台性、生态系统完备性等特性, 成为极其主流的开发语言当中的一个。实际上, Java到处都有, 已经渗透到人们的日常生活里面, 从你的每一回购物到每一项支付, 都有着Java技术呈现出来或隐藏后面另外、国内外的主流网站基本大多借助着Java技术来进行各项工作的支撑连接。搜狗商业平台承担着搜狗广告业务, 它覆盖了搜索、网盟、无线、品牌等业务线, 针对几十万广告主以及广告代理商, 给予十亿级以上在线广告管理还有相关支持, 而且提供近百亿的在线报告。其中, 70%以上基于Java的业务系统进行运作。从底层缓存、会话、调度、通信交互等方面, 到给客户提供的API接口, 从数据库访问、离线大规模数据处理直至实时计算, 均依赖于Java技术。在我们内部经历的长期实践进程中Java技术已经渐渐自发地创立了一个生态系统。Java的生态圈极为庞大且丰富, 实践历经较长时间, 不是自主进行二次开发与优化, 便是基于Java开源组件开展二次开发与优化, 构建起了涉及搜狗商业平台的完整Java技术框架, 像图1展示的那样。图1搜狗商业平台Java技术栈于基础组件层, 我们径直运用了一些业界颇负盛名的框架与类库, 诸如 IoC 框架、日志 Log4J 等与此同时, 还基于一些框架展开了二次开发, 比如基于 Redis 供给了分布式会话以及分布式缓存, 切实地解决了单机内存及 I/O 瓶颈之问题。于数据存储层, 我们数据存储主要采用关系数据库 MySQL、文档数据库、分布式存储 HDFS 以及自行研发的 DFS 文件系统。在数据访问层面, 基于MySQL数据库以及数据库, 各自给出了一套分库分表框架, 致使其能支持海量数据存储, 同时又各自给出了ORM框架, 从而能够较为轻易地达成数据库里的数据到对象的映射。在数据计算层面, 离线计算方案主要运用了框架, 流式计算方案主要运用了Kafka和Storm。在接口交互层面, 给出了三种框架, 分别对、和HTTP/JSON三种交互方式予以支持。在基础服务层, 有认证服务, 有授权服务, 有配置服务, 有分布式任务调度服务, 有消息服务, 有图片服务, 有短信服务, 还有邮件服务, 提供了多种基础服务。下面, 给大家分别介绍一下, 有关我们在数据库分库分表框架方面的实践, 以及在分布式任务调度平台上的实践, 还有在分布式 RPC 框架上面的实践。数据库分库分表框架针对互联网领域大数据存储, 基于NoSQL的数据库数量日益增多, 可是, 在一致性、事务性、和可靠性等方面, 特别是在较为复杂的业务场景当中, 关系数据库依旧起着不可替代的作用。在关系数据库范畴内, MySQL数据库大体上主导了市场。然而, 互联网面临着用户数量众多、数据容量庞大等挑战, 一组MySQL数据库集群没办法支撑如此巨大的PV及I/O, 所以对于单表的数据量以及每台机器的数据分布需要进行一定的预估。于我们的实践当中, 针对单表的数据量存在一些约束条件。一般来讲, 对于定长的记录而言, 单表数据量最好不要超过800W, 其极限不超过1000W对于不定长记录来说, 单表数据量最好不超过500W, 达到极限不超过800W, 不然在较为复杂的业务场景里面, 有可能会致使性能下降。大规模数据的存储, 此方面, 一般会采用分库分表的方案来予以解决, 在这些当中。主流互联网公司纷纷提供了各自的解决方案, 就像淘宝的分布式数据层TDDL, 还有百度等等。我们这边, 也研发出了数据库分库分表框架, 它能够支持以及满足内部的一些需求。寻常来讲, 数据库分布分表框架的设计存有两种方案, 其一为独立中间件层方式, 其二是嵌入式应用框架方式。独立中间件层方式运用独立的部署, 后端数据库面向应用程序是透明的, 它的扩容对于应用的可用性不会产生显著的影响。嵌入式应用框架方式属于独立的类库, 应用需要明确地配置后端的单个或者多个数据库。独立中间件层方式, 开发成本高, 维护成本也高, 还多了一层网络开销, 嵌入式应用框架方式, 数据库配置较为复杂些, 不过其开发维护成本低, 对单元化架构的支持也不错, 所以选用了嵌入式应用框架方式。图2分布分表框架架构图如图2所示为系统架构, 基础数据源层可选用开源的数据库连接池方案, 像包含C3P0、、、DBCP等, 在我们实践里主要用C3P0, 而代理数据源层主要负责数据库分库分表的处理, 涵盖分库数据源、主从数据源、心跳监控以及管理接口四大模块。分库数据源实现了Java中标准的javax.sql.接口, 提供了注解形式的配置方式, 对应用屏蔽了底层数据源差别, 简化应用配置和在不同数据源迁迕, 包括从普通数据源迁到主从数据源, 从主从数据源迁到分库数据源, 降低拓展容量成本。主从数据源同样做到这些, 实现相关标准接口, 提供配置方式, 简化迁移, 降低成本。因采用标准接口, 在支持JDBC封装层方面, 能兼容多数业界ORM框架, 像、、和JDBC等。与此同时, 鉴于运用了对层施行分区路由的方式, 所以, 对于那些依赖javax.sql.接口的框架, 或者应用而言, 全都不会产生影响。分库数据源里有路由选择器, 路由选择器能够借助自定义路由选择策略选出恰当的底层数据源, 还会把获取数据库连接的请求代理给此底层数据源。路由选择策略能依多种方式达成: 一种是取模, 一种是分段, 一种是Hash, 还有特殊路由策略等。在我们的实践当中主要是管理客户的物料, 路由选择策略是靠客户ID取模达成的, 并且针对个别大客户采用特殊路由策略的方式, 以此保证表中数据的均衡性。对于每次请求获取数据库连接, 主从数据源依据当前事务属性等上下文相关信息, 以及自身可用的底层数据库连接池信息, 返回底层数据源的连接, 这里面主要涵盖主从选择和代理连接, 主从选择能够支持读写分离, 支持多从单主模式, 并且支持自定义反主从延时策略, 借此更好地确保主从库数据的一致性, 在负载均衡这方面, 我们不但支持内置的随机、权重、轮询、响应时间等负载均衡策略, 还支持自定义扩展负载均衡策略。代理连接着重于给出一系列策略, 用以对SQL加以拦截, 从而达成分表等功能。在SQL解析跟替换这方面, 当下我们设立了诸如按某种方式基于SQL语法树解析进行SQL结构替换、对如上述这般ORM框架的SQL占位符予以替换等, 此外还准许自定义扩展的解析以及替换策略。于主从选择当中, 反主从延时策略乃是用以解决主从延迟的可选插件, 其借由把一段访问时间里的读请求路由至主库来达成, 当下能够基于常见的分布式缓存以及内存方式予以反主从延迟标记的管理, 且支持自定义扩展。心跳监控着重针对底层数据源的健康状况予以监控, 一般而已, 底层数据源连接池中都已然提供了心跳监控, 像C3P0就能够监控数据库连接是否有用, 探测数据库空闲连接以及检查数据库是否正常, 并且提供了一系列的处理对策, 用来应对上述情况, 然而, 主从数据源当中的分库数据源特别有这一种需求, 即开展对底层数据源的数据库连接以及数据库是否能够正常使用的测试, 并在底层数据源不行的时候, 提供策略予以处理, 比如对主从选择策略里的参数作出改良。所以, 数据源代理层依旧得对底层数据源展开监控, 依据底层数据源是不是可用来进行选库, 或者在负载均衡策略方面予以调整。与此同时, 心跳监控里也具备故障移除、故障恢复等功能, 还能够针对不同的数据库类型实施自定义监控扩展, 并且支持一定的JMX监控接口来监控数据库连接池C3P0/DBCP/等, 支持自定义扩展, 以此可以更优地监控其运行状态。管理接口主要用以提供相应监控管理, 具体体现为诸如访问次数涵盖读写方面, 还有执行时间, 以及数据库连接数包括活跃以及空闲等系列状态上呈现的监控管理, 并且借助JMX等系列手段呈现相应服务外露的情况。数据聚合层给出了数据聚合可供选择的插件, 当下支持去继承已经实现聚合的当作全库聚合以及操作, 再者还能够采用多线程的方式用以提升数据访问的效率。主从选择的标识, 反延时标识, 路由选择标识, 这些由一系列工具收集而来, 这些收集工作由元信息收集层负责, 收集后提供给代理数据源层的路由选择器, 用于进行路由选择, 也提供给主从选择器, 用于进行主从选择。当然了, 在分布式数据库事务当中, 也就是在事务管理这块来说, 我们持续沿用事务管理机制, 用以保证同库的事务性, 然而中间件在这方面依旧不存在很好的处理方式, 对于跨节点 join 问题亦是如此。并且, 在跨节点 count 以及等等聚合问题上的实现, 也有待于更为优雅的给予支持。尤其是针对全库聚合或者查询这个情况而言的话, 它所占用的线程资源以及内存资源都相对来讲是比较高的。这同样是任何数据库中间层解决方案都没有办法完全解决掉的事情, 只能依靠于在业务方面进行精巧的设计, 尽可能地去避开这些问题。此外分库分表框架也大大提升了易用性业务发展过程中, 凌云的各开发团队, 常常要编写大量定时任务, 用以开展相关业务处理, 像导入导出数据、做报表计算、进行汇总统计等。最常见的定时任务, 可借助Linux系统, 在指定时间点触发任务执行。可随着任务量渐渐增多, 基于现有情况的任务管理方式情形下, 存在诸多弊端:任务配置管理所需成本偏高, 每增添或者修改一项任务, 便都得进行修改, 进而增添人力管理方面的成本, 无法对任务依赖予以支持, 也就是不支持任务之间存在依赖关系, 手工配置时出错的风险较大, 因为手动修改缺乏正确性校验机制, 存在一定的出错可能性, 不支持任务以可视化方式呈现, 任务仅能借助Linux终端加以查看, 不存在统一的管理界面来查看任务详情以及历史运行记录, 存在单点风险, 即任务每次触发后仅仅能在一台机器上执行, 要是机器发生故障, 任务便无法得以执行, 监控并不完善。任务失败后需要为任务单独增加相应的监控。基于此, 我们开发出了搜狗分布式任务调度平台, 也就是凌云, 把各类不同的任务都整合到这个平台之上, 开展配置以及管理的相关操作, 并且还对以下这些特性予以支持:图3凌云分布式任务调度平台其架构呈现为图3所示的样子。任务执行节点所指的, 乃是执行任务活动的服务器系列, 这些服务器具备被划分作多个不同领域的可能性, 而域的主要功能在于保障不同业务之间的隔离属性。每一项任务都必须明确指定其所属的域, 当任务被触发之后, 便会转而配给其所属域当中的某一台服务器去执行。任务执行节点承担着具体任务的执行职责, 在每个任务执行节点之上, 都配备部署着一个任务代理, 此任务代理是基于HTTP服务的存在, 其功用在于同调度集群展开交互, 进而启动子进程去执行具体的任务。任务有Java任务跟Shell脚本任务这两种类型的区分, 其中存在这样一种情况就是任务属于一种Shell脚本任务, 这两者都是通过任务代理以子进程的形式来启动的。任务代理同任务调度集群的交互, 主要涵盖接收任务分发请求, 处理任务状态轮询请求, 在任务结束之后回调任务调度集群等。任务代理凭借获取任务子进程的退出码, 来判定任务是不是正常结束, 退出码0意味着正常把任务完成, 非0表明出现异常, 任务代理会把退出码, 返回给任务调度集群。任务调度集群底部基于集群模式, 这是一个由Java撰写的强大的企业级任务调度框架, 提供了相当不错的伸缩性、还有高可用性以及负载均衡机制, 我们在此基础之上做了扩展, 支持任务的定时触发, 支持任务的依赖执行, 支持任务的失败重试以及支持任务的异常报警等。任务调度集群给出基于的HTTP服务, 涵盖两方面功能, 一方面, 针对任务管理中心, 开放任务管理以及调度的相关接口, 在此处, 管理员针对任务的调度配置会被更新至任务调度集群之中另一方面, 面向任务代理开放任务状态回调接口这般一种对象, 自任务于执行节点上运行完毕之后, 任务代理会借由这个接口回调调度器, 告之以任务的运行结果。任务依赖执行, 能极为便利地达成任务前后顺序衔接, 特别是针对有跨组数据依赖的状况, 借助配置任务间依赖关系能搞定, 可以实现数据获取的及时性与有效性。在任务管理中心, 用户能对任务相互间的依赖关系加以维护, 倘若任务的所有那些起着引导作用的前驱任务都执行完了, 就会自动促使该任务开展执行。假定如同图4有那样的任务依赖关系, 任务C存在两个起着引导作用的前驱, 分别是任务A与任务B, 任务D的起着引导作用的前驱任务是任务B, 任务E、F的起着引导作用的前驱任务是任务C。那么顺着时间轴的方向, 任务B先完成执行, 就会自动促使它后续的任务D开始执行当任务A执行结束后, 会自动促使任务C开始执行任务C执行结束后, 会自动促使E和F执行的。图4任务依赖关系因为分离了任务调度与执行, 调度器得清楚任务状态? 在必要之际触发重试或者报警。我们采用了任务结果回调跟任务处于运行中间轮候反复查问这两种办法。通常情形当中, 任务履行做完完毕之后怎么样, 任务委托人按照任务下属的分成所得到者退出态势数码裁定任务可不可以实施成功顺利顺遂接着呢此其时接着呢那么就将任务实施怎么样根据回调样式传送回安排集群。考虑到存在这样的情况, 就是任务成功完成了, 然而任务代理回调却失败了, 所以我们增加了调度器对任务状态的轮询策略, 对于运行中的任务而言, 调度器去轮询处理节点该任务的状态, 做到能及时把任务成功还是失败反馈给调度器。通过进行轮询, 倘若碰到耗时已经超出预期执行时间的任务, 调度器就会发出任务超时报警。任务管理中心属于一个针对任务的可视化管理平台, 用户能够登录这个系统去查看以及管理自身的任务。具体涵盖任务管理、触发器管理、参数管理、依赖管理, 任务配置项包含任务的基本属性、执行时间的触发器、任务的参数等。还能够直接于管理中心马上启动执行指定的任务。系统依据角色划分权限, 不同角色的用户具备不同的权限。除此之外任务管理中心运用分布式会话, 能够进行集群部署, 提升了容灾能力。目前, 搜狗分布式任务调度平台担当着绝大多部分任务调度工作, 经把任务的管理、调度以及执行进行解耦合, 削减了三者间的相互侵入, 通过给予丰富的任务配置属性以及任务可视化陈列, 可以极大地促使任务管理效率得到提升, 凭借提供任务远程调度、任务分隔执行、任务平稳上线、任务多从属等特征, 很棒地维持了各产品线事务的发展。分布式RPC框架沿着长期的业务发展进程, 于系统之间的交互情形里, 我们运用了RMI, 运用了JSON等技术, 针对每一种技术, 均要去维护一组对应的故障转移办法, 维护一组对应的故障恢复手段, 维护一组对应的追踪框架, 面向商业平台多条业务线的诸多系统交互而言, 常常会遭遇硬件故障事端, 这样的交互方式所引发的维护成本以及故障迁移成本均庞大无比。此外, 形形色色的多种接口技术, 都面临着接口兼容性这一问题, 比如说对于RMI来讲, 我们碰到了从2.5.6升级到3.1.0所引发的, 因RMI接口不兼容致使的接口调用错误状况。最终, 我们内部存在一些跨语言的调用需求, 像利用C查询广告物料信息、借助脚本做统计的情况, 这些均会涉及接口调用, 而采用无法跨语言的技术, 比如RMI和成本会极其高昂。所以, 我们要统一内部系统接口交互方式, 还要降低接口的管理以及维护成本。在实践进程当中, 我们对、、HTTP/JSON、Dubbo以及等技术做了比较。当中, 基于XML, 兼容性不错, 但是其序列化与反序列化性能不佳和HTTP/JSON没有服务接口与地址描述Dubbo的框架太过庞大在接口定义文档以及数据类型支持和跨语言等方面具备优势, 可是对服务地址以及服务管理方面支持强度不足, 而且存在类型侵入问题。经历分析比较之后, 我们予以选定, 进而在此根基之上开发出服务注册中心, 用以解决服务地址以及服务管理方面的问题。针对类型侵入问题, 从描述语言着手, 从长远视角来看, 它所生成的POJO代码理应能够如同那般, 消除类型侵入举例而言设有开源的Swift框架, 已然朝着此方向前进了一步。我们目前提供了一系列的方法以及转换类, 自主开展类型转换适配工作。我们基于构建的分布式RPC框架如图5所示。图 5 分布式RPC框架我们把服务端予以了增强, 同样也对客户端进行了增强, 在服务器端, 我们给出了标准的HTTP服务器, 一个服务依据此能轻易发布到一个URL上。而且, 标准的认证跟授权框架被集成了, 业务的安全性得以有效保障此外关于运维监控方面我们做了诸多增强, 每个方法的执行时间其及执行参数由此能被监控, 如此一来面向整个商业平台, 标准的监控统计架构能够被建立。我们提供的服务是以HTTP为基础的, 所以部分统计能够直接沿用线上现有的监控统计软件, 这也切实有效地降低了重新构建监控框架所需的成本, 而且。在客户端, 主要做了这些事, 添加了失败重试条件, 提供了类似于RPC那类的调用方式, 还提供了一种可简化测试成本的抽象机制, 通过依赖注入, 能支持在仅仅调整配置, 客户端代码却不做改变之际, 实现本地调用与远程HTTP调用的切换, 使用本地调用时能更迅速地开展单元测试, 在单元测试完成后, 能直接经由调整配置切换到远程HTTP调用, 此过程中不需要作出任何代码层面的调整, 这个方案对接口迁移, 像从RMI接口迁移到其他接口而言, 起到了甚大的助力作用。通常的状况之下, 体型庞大且复杂相互依赖的系统, 其内部各个接口间所存在的依赖关联都极为繁杂, 当开展接口梳理工作以及迁移工作之际, 成本以及风险都会相当之高, 尤其是当处于系统的服务化改造进程之中, 也就是把内部接口升格为API接口的那一时候在这样的情形当中, 可以选用引入一个模块的方式, 让此模块去依赖于那些得提升为API接口的模块, 然而其他的模块仅仅只是依赖于这一个模块罢了。在这个调整进程里, 能伴随业务版本的开发一同开展, 并且能够重用大部分单元测试, 稍加调整便可运用, 如此一来能够把成本以及风险都降低到最低限度。实际上, 存在一种具备标准性的定义语言, 借助这一语言能够针对服务接口、异常情况、接口参数以及返回值展开描述, 在此过程中还一并对Set、Map这类的数据结构持有支撑。然而, 该语言不曾对服务地址以及服务依赖关系等予以描述。所以, 我们搭建起了服务中心Aura, 其概念架构呈图6所示。图6 服务中心Aura概念架构图图6里的通信框架服务端, 以及通信框架客户端, 已在前面讲过了。服务中心的主要作用是, 管在理接口描述语言文件, 管理其发布或者订阅的关系。事实上, 它确定了一种团队之间相互协作的办法, 该协作依照面向服务体系结构。在开发实践当中, 我们不提倡依赖经由接口描述语言直接生成的代码而是提倡依赖于接口描述语言生成代码, 如此可以保证耦合性, 降低代码升级所造成的成本。再者, 借助管理服务的所有者, 以及服务的订阅者, 可快速知晓服务间的依赖关系, 还有服务演化之际所引发的相关影响。商业平台内部存在多个业务系统, 这些业务系统会运用分布式 RPC 框架, 其中涵盖资金、计费、客户、财务、合同、物料、报告等方面, 并且还覆盖了 Java、C等语言, 当前大部分接口的迁移已经完成, 基于服务中心 Aura, 构建了完备的服务发布、发现以及使用的流程, 同时针对其安全性、易用性以及可维护性开展了诸多工作, 鉴于有了统一的技术集, 就连如何去使用都具备了最佳实践, 迁移后的服务接口明显降低了重复开发成本, 切实有效地提升了沟通效率, 还降低了风险。总结在长时间的实践里头, 我们始终依据Java技术, 专心去解决因分布式、高并发、大数据量、强一致性等状况所带来的各类技术难题以及挑战。与此同时, 我们还专心于基础组件与框架的统一化, 搭建并持续优化基础架构、基础服务, 确保具备高可靠、高性能、高可扩展性、低成本的特性, 能够快速支撑各项新业务, 进而保证业务得以高速发展。其过程之中, Java以及其生态系统得以具备较高实效性地实行了支撑之举, 从而构筑成为搜狗商业平台里面Java生态系统占据的基石地位所在, 大幅削减了开发还有维持成本, 提升了可维持的性能以及系统自身具备的健壮程度呢标点符号。作者简介刘建, 身为搜狗架构师, 担任商业平台基础平台负责人, 拥有十年Java相关研发经验, 于互联网软件体系结构、分布式计算、面向服务体系结构、用户身份安全等方面存有浓厚兴趣并有实践经验, 现主要关注分布式高可用软件架构以及大数据基础架构。郭理勇是谁, 乃是搜狗的一位高级研发工程师, 同时还是商业平台那边网盟广告技术的负责人, 其主要所专注关心的, 是Java以及J2EE, 还有跟它们相关联的技术, 另外就是分布式高可用软件架构。毛宏, 身为搜狗的高级研发工程师, 还是商业平台基础服务技术的负责人, 其主要关注的是Java开发以及与之相关联的技术。选自程序员电子版2015年5月B刊的本文, 要查看该期更多文章可看这里。创刊于2000年至今的所有文章目录要查看程序员封面秀。欢迎订阅程序员电子版, 这里面包含iPad版、版、PDF版。