一、 面临的挑战
- 发展速度快:每年近十倍的业务增长。既要及时响应业务发展的新需求,又要对系统平滑地进行技术升级重构,其难度堪比飞机空中加油。
- 系统越来越复杂:
- 多渠道: 1 号店、掌上商城、第三方商城、多个分站
- 产品多样: 既有各类百货,也有各种虚拟票劵,还有机票充值等
- 多种经营模式: 有自营也有代售,有自配送也有第三方配送
- 多部门协同:采购、市场、销售、仓储、配送、客服等多部门需高效协同。
- 更智能化:随着业务和系统的复杂,各部门完全人工的运营决策变得十分低效,需要系统更加智能化。
- 流量越来越大:流量每年增长 10 倍。
二、演进的策略
-
新旧兼容,平滑升级:我们在实施任何技术升级时,首要强调的就是新系统要尽量兼容旧系统,以支持平滑升级。完全新的架构看似很吸引人,比较容易开发,但是不兼容原来的系统可能使得新系统风险非常大。所以 1 号店采用兼容的方式,滚动开发,把架构从原来的合成一团的小系统方式逐步改变为子系统独立,多数据库的模式。这好比一边高速飞行,一边加油。
-
系统拆分:解决系统复杂性的唯一方法是分解。同时拆分后系统也使各自进行新的技术改造变得相对平滑。我们从终端渠道、业务逻辑、使用部门等多个维度进行了系统拆分。 例如:
-
适当放弃一致性:在一些实时性要求不高的场合,我们适当放弃一致性要求。这样就可以充分利用多种手段来提高系统吞吐量,例如页面缓存、分布式数据缓存、数据库读写分离、查询数据搜索索引化。
-
系统共用组件 Service 化:随着系统的分拆,各子系统间的重复代码越来越多,增加了很多不必要的维护成本。为提高代码的复用,降低维护成本,我们将多个子系统都需要用到的业务组件,进行了 Service 化。例如:
-
中间件的研发使用:随着子系统和 Service 的增多,面临很多共同的技术性挑战。为降低应用系统人员的开发难度,我们在一些开源系统的基础上,研发了多个适应我们的中间件系统。例如:ESB 平台 (支持远程服务、异步消息)、分布式缓存、数据访问层等。大大简化了应用系统的开发。使用中间件后的总体示意图如下:
-
系统自动化:为进一步提高运营效率,我们采用了很多自动化的设计理念,尽量减少人工配置的需要。例如:根据比价系统和策略自动调整商品价格,根据投放效果和策略自动调整广告投放,根据库存和销售预测自动安排采购等。
三、 经典案例:数据访问层 YhdDAL 1.0 设计与实施
1) 实施前旧系统状态与结构:
- 整体已拆分有多个子系统和多个数据库。
- 每个应用系统与多个数据库直连,使用 iBatis 进行 O/R Mapping。
- 各应用系统代码中,使用 MemCached 服务器进行数据缓存。
2) 旧系统面临的问题:
- 数据库连接数大,数据库服务器负担重。
- Cache 代码繁琐: 本来一行代码的事需要写四五行。下面是典型的示例伪代码:
result = getFromMemCached(key); if (result != null) return result; result = getFromDatabase(...); writeMemCached(key, timeout); return result;
- Cache 管理困难: 失效时间代码分散,不便管理。不同系统还容易 key 冲突。
- 数据分库和读写分离代码繁琐,某库故障时无法自动切换到可用库上。
- 系统 Service 化改造困难: 原 Web 层与 Dao 层代码耦合度高,不易实现 Service 化。拆分粒度太细连接数上升和故障概率增加,粒度太粗又效果不佳。
- 无法充分发挥 Cache 潜能:某个 Cache 项失效时,因前端的并发性会导致多次数据库请求。到期 Cache 无法延期使用,数据库故障时应用系统就立即故障。
3) 备选方案的优缺点:
- 基于 iBatis 本地扩展: 优点是工作量小。缺点是连接数和 Service 化等问题无法很好解决。
- 基于 jdbc 驱动的 DAL:优点是客户端代码兼容性好,原代码改动工作量小。缺点是实现一个完整的 jdbc 驱动本身代价很高。
- DAL 服务化,接口自定义: 优点是代码可控性高,各项需求特性易实现。缺点是有一定的迁移成本。
4) YhdDAL 1.0 设计方案:
- 引入 DAL 服务器。应用系统仅访问 DAL 服务器,DAL 才连接数据库。
- 客户端与 DAL 采用成熟的远程调用协议,接口简化为一个:
- Object execute(functionName, parameters…)
- 参数和返回值一律采用 Map 或基本类型,必要时可在客户端转换为 Java Bean
- 支持 iBatis 格式的 sql 脚本定义和字段映射,以降低 iBatis 代码迁移成本。
- DAL 层支持 JavaScript 脚本语言编写的类似存储过程的计算代码,简化业务层数据处理代码实现。
- 因 DAL 服务器数量明显小于应用服务器数量,可有效减少数据库连接总数。
- DAL 层封装 Cache 处理机制,一方面简化应用层代码。另一方面可采取缓存主动更新,失效延期等机制,充分发挥 Cache 潜能。
- DAL 层封装分库和读写分离处理机制,简化应用层代码。
- 参考数据库存储过程的设计思想,客户端仅传入过程名称和参数,DAL 层计算完成后返回结果。有利于实现 Service 化。
- 大量简化应用层代码,很多简单的 Sevice/Dao 类可以完全省略。
- 1.0 的缓存和负载均衡采用相对成熟简单方案,以降低风险。结构图如下:
5) 实施与升级过渡步骤:
- 实现 DAL 核心功能。进行压力测试,评估系统性能。
- 先对一个风险相对较小的应用系统进行改造,评估系统稳定性和改造成本。
- 采用 iBatis 兼容模式,直接转换迁移旧代码。以快速将原有系统升级至新平台。
- 采用新平台的设计理念,如读写分离、Service 化,进行旧代码改造。
- 扩展 DAL 附加功能,如日志记录、权限控制、缓存优化、连接分组等。
通过一系列的方法与手段,1 号店基本实现了从小系统向大系统演变,使得 1 号店的系统能够支撑大量高并发的访问,同时满足了业务的需要。
关于作者
韩军,1967 年出生。1989 年毕业于上海交通大学计算机系, Monash MBA
- 1 号店的第一个员工,CTO, 设计并开发了 1 号店所有的系统
- 2004 年,设计了 美国著名的比较购物系统 smarter.com
- 1999 年加入 51job,设计并开发了中国最为成功的工作网站与系统
感谢晁晓娟对本文的审校。
给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家加入到 InfoQ 中文站用户讨论组中与我们的编辑和其他读者朋友交流。
评论