一、 面临的挑战

\u0026#xD;
  • 发展速度快:每年近十倍的业务增长。既要及时响应业务发展的新需求,又要对系统平滑地进行技术升级重构,其难度堪比飞机空中加油。\u0026#xD;
  • 系统越来越复杂:\u0026#xD;
    1. 多渠道: 1号店、掌上商城、第三方商城、多个分站\u0026#xD;
    2. 产品多样: 既有各类百货,也有各种虚拟票劵,还有机票充值等\u0026#xD;
    3. 多种经营模式: 有自营也有代售,有自配送也有第三方配送\u0026#xD;
    4. 多部门协同:采购、市场、销售、仓储、配送、客服等多部门需高效协同。\u0026#xD;
    5. 更智能化:随着业务和系统的复杂,各部门完全人工的运营决策变得十分低效,需要系统更加智能化。\u0026#xD;
    \u0026#xD;
  • 流量越来越大:流量每年增长10倍。\u0026#xD;

二、演进的策略

\u0026#xD;
  • 新旧兼容,平滑升级:我们在实施任何技术升级时,首要强调的就是新系统要尽量兼容旧系统,以支持平滑升级。完全新的架构看似很吸引人,比较容易开发,但是不兼容原来的系统可能使得新系统风险非常大。所以1号店采用兼容的方式,滚动开发,把架构从原来的合成一团的小系统方式逐步改变为子系统独立,多数据库的模式。这好比一边高速飞行,一边加油。\u0026#xD;
  • 系统拆分:解决系统复杂性的唯一方法是分解。同时拆分后系统也使各自进行新的技术改造变得相对平滑。我们从终端渠道、业务逻辑、使用部门等多个维度进行了系统拆分。 例如:\u0026#xD;

4db46a7db5f21e42ec6be51fcdd348c6.jpg\"

\u0026#xD; 适当放弃一致性:在一些实时性要求不高的场合,我们适当放弃一致性要求。这样就可以充分利用多种手段来提高系统吞吐量,例如页面缓存、分布式数据缓存、数据库读写分离、查询数据搜索索引化。\u0026#xD; 系统共用组件Service化:随着系统的分拆,各子系统间的重复代码越来越多,增加了很多不必要的维护成本。为提高代码的复用,降低维护成本,我们将多个子系统都需要用到的业务组件,进行了Service化。例如:\u0026#xD;

22cedf99351b18cf29e79a99158d2f27.jpg\"

\u0026#xD; 中间件的研发使用:随着子系统和Service的增多,面临很多共同的技术性挑战。为降低应用系统人员的开发难度,我们在一些开源系统的基础上,研发了多个适应我们的中间件系统。例如:ESB平台(支持远程服务、异步消息)、分布式缓存、数据访问层等。大大简化了应用系统的开发。使用中间件后的总体示意图如下:\u0026#xD;

517b11eef605451db12696aa6b503841.jpg\"

\u0026#xD; 系统自动化:为进一步提高运营效率,我们采用了很多自动化的设计理念,尽量减少人工配置的需要。例如:根据比价系统和策略自动调整商品价格,根据投放效果和策略自动调整广告投放,根据库存和销售预测自动安排采购等。\u0026#xD;

三、 经典案例:数据访问层YhdDAL 1.0设计与实施

\u0026#xD;

1) 实施前旧系统状态与结构:

\u0026#xD;
  • 整体已拆分有多个子系统和多个数据库。\u0026#xD;
  • 每个应用系统与多个数据库直连,使用iBatis进行O/R Mapping。\u0026#xD;
  • 各应用系统代码中,使用MemCached服务器进行数据缓存。\u0026#xD;

562b22e535bfaef4b259b62b75d412b6.jpg\"

\u0026#xD;

2) 旧系统面临的问题:

\u0026#xD;
  • 数据库连接数大,数据库服务器负担重。\u0026#xD;
  • Cache代码繁琐: 本来一行代码的事需要写四五行。下面是典型的示例伪代码:\u0026#xD;
\u0026#xD;result = getFromMemCached(key);\u0026#xD;if (result != null) return result;\u0026#xD;result = getFromDatabase(...);\u0026#xD;writeMemCached(key, timeout);\u0026#xD;return result;\u0026#xD;
\u0026#xD; Cache 管理困难: 失效时间代码分散,不便管理。不同系统还容易key冲突。\u0026#xD; 数据分库和读写分离代码繁琐,某库故障时无法自动切换到可用库上。\u0026#xD; 系统Service化改造困难: 原Web层与Dao层代码耦合度高,不易实现Service化。拆分粒度太细连接数上升和故障概率增加,粒度太粗又效果不佳。\u0026#xD; 无法充分发挥Cache潜能:某个Cache项失效时,因前端的并发性会导致多次数据库请求。到期Cache无法延期使用,数据库故障时应用系统就立即故障。\u0026#xD;\u0026#xD;

3) 备选方案的优缺点:

\u0026#xD;
  • 基于iBatis本地扩展: 优点是工作量小。缺点是连接数和Service化等问题无法很好解决。\u0026#xD;
  • 基于jdbc驱动的DAL:优点是客户端代码兼容性好,原代码改动工作量小。缺点是实现一个完整的jdbc驱动本身代价很高。\u0026#xD;
  • DAL服务化,接口自定义: 优点是代码可控性高,各项需求特性易实现。缺点是有一定的迁移成本。\u0026#xD;

4) YhdDAL 1.0设计方案:

\u0026#xD;
  • 引入DAL服务器。应用系统仅访问DAL服务器,DAL才连接数据库。\u0026#xD;
  • 客户端与DAL采用成熟的远程调用协议,接口简化为一个:\u0026#xD;
  • Object execute(functionName, parameters...)\u0026#xD;
  • 参数和返回值一律采用Map或基本类型,必要时可在客户端转换为Java Bean\u0026#xD;
  • 支持iBatis格式的sql脚本定义和字段映射,以降低iBatis代码迁移成本。\u0026#xD;
  • DAL层支持JavaScript脚本语言编写的类似存储过程的计算代码,简化业务层数据处理代码实现。\u0026#xD;
  • 因DAL服务器数量明显小于应用服务器数量,可有效减少数据库连接总数。\u0026#xD;
  • DAL层封装Cache处理机制,一方面简化应用层代码。另一方面可采取缓存主动更新,失效延期等机制,充分发挥Cache潜能。\u0026#xD;
  • DAL层封装分库和读写分离处理机制,简化应用层代码。\u0026#xD;
  • 参考数据库存储过程的设计思想,客户端仅传入过程名称和参数,DAL层计算完成后返回结果。有利于实现Service化。\u0026#xD;
  • 大量简化应用层代码,很多简单的Sevice/Dao类可以完全省略。\u0026#xD;
  • 1.0的缓存和负载均衡采用相对成熟简单方案,以降低风险。结构图如下:\u0026#xD;

9e9f27968adec0b9839ce6b2a6164e31.jpg\"

\u0026#xD;

5) 实施与升级过渡步骤:

\u0026#xD;
  • 实现DAL核心功能。进行压力测试,评估系统性能。\u0026#xD;
  • 先对一个风险相对较小的应用系统进行改造,评估系统稳定性和改造成本。\u0026#xD;
  • 采用iBatis兼容模式,直接转换迁移旧代码。以快速将原有系统升级至新平台。\u0026#xD;
  • 采用新平台的设计理念,如读写分离、Service化,进行旧代码改造。\u0026#xD;
  • 扩展DAL附加功能,如日志记录、权限控制、缓存优化、连接分组等。\u0026#xD;

通过一系列的方法与手段,1号店基本实现了从小系统向大系统演变,使得1号店的系统能够支撑大量高并发的访问,同时满足了业务的需要。

\u0026#xD;

关于作者

\u0026#xD;

韩军,1967年出生。1989年毕业于上海交通大学计算机系, Monash MBA

\u0026#xD;
  • 1号店的第一个员工,CTO, 设计并开发了1号店所有的系统\u0026#xD;
  • 2004年,设计了 美国著名的比较购物系统 smarter.com\u0026#xD;
  • 1999年加入51job,设计并开发了中国最为成功的工作网站与系统\u0026#xD;

感谢晁晓娟对本文的审校。

\u0026#xD;

给InfoQ中文站投稿或者参与内容翻译工作,请邮件至editors@cn.infoq.com。也欢迎大家加入到InfoQ中文站用户讨论组中与我们的编辑和其他读者朋友交流。

Logo

更多推荐