腾讯组织架构整改引思考:中小团队要怎样搭建架构?

原文网址:https://www.infoq.cn/article/UoWc9uUtVIrm-azWOglu

2019 年 1 月 4 日,腾讯宣布成立技术委员会,也代表之前宣布的架构调整终于拉开序幕。那么中小团队要如何搭建自己的团队架构呢?本文将会对此展开讨论……

平时我们看技术大会上的分享大多高大上,亿级流量、超大型研发团队,虽然值得借鉴,但由于应用场景与研发资源的差异,一般企业并不容易落地。其实,中小型研发团队在 IT 行业还是占大多数,他们在技术架构方面的问题较多,技术阻碍业务、跟不上业务发展的情况非常常见。

我是一个有十多年经验的 IT 老兵,曾在两家几千人的技术团队做过架构与技术管理工作,也曾在几十人至几百人的中小研发团队做过首席架构师和 CTO。一个是定制的劳斯莱斯,一个是大众轿车。在互联网大厂做技术研发,大多只是一个螺丝钉。而在中小研发团队,则比较容易掌控全局。

笔者结合近几年的工作经验,摸索出了一套可直接落地、基于开源、成本低、可快速搭建的框架及架构方案。小团队也能构建大网站,中小研发团队架构实践更贴近于一般程序员的实际情况,更具应用参考价值。

一、框架篇:工欲善其事,必先利其器

如果说运维是地基,那么框架就是承重墙。农村建住房是一块砖一块砖地往上垒,而城市建大 House 则是先打地基,再建承重墙,最后才是垒砖,所以中间件的搭建和引进是建设高可用、高性能、易扩展可伸缩的大中型系统的前提。

框架篇中的每章主要由四部分组成:它是什么、工作原理、使用场景和可直接调试的 Demo。其中中间件及 Demo 是历经两家公司四年时间的考验,涉及几百个应用,100 多个库 1 万多张表,日订单从几万张到十几万,年 GMV 从几十亿到几百亿。

所有中间件与工具都是基于开源。早期我们也有部分自主研发如集中式日志和度量框架,后期在第二家公司时为了快速地搭建、降低成本、易于维护和扩展,全部改为开源。这样不仅利于个人的学习成长、知识重用和职业生涯,也利于团队的组建和人才的引进。

1、集中式缓存 Redis

缓存是计算机的难题之一,分布式缓存亦是如此。Redis 看起来非常简单,但它影响着系统的效率、性能、数据一致性。

用好它不容易,具体包括:缓存时长(复杂多维度的计算)、缓存失效处理(主动更新)、缓存键(Hash 和方便人工干预)、缓存内容及数据结构的选择、缓存雪崩的处理、缓存穿透的处理等。Redis 除了缓存的功能,还有其它功能如 Lua 计算能力、Limit 与 Session 时间窗口、分布式锁等。我们使用 ServiceStack.Redis 做客户端,使用方法详见 Demo。

2、消息队列 RabbitMQ

消息队列好比葛洲坝,有大量数据的堆积能力,然后再可靠地进行异步输出。它是 EDA 事件驱动架构的核心,也是 CQRS 同步数据的关键。为什么选择 RabbitMQ 而没有选择 Kafka,因为业务系统有对消息的高可靠性要求,以及对复杂功能如消息确认 Ack 的要求。

3、集中式日志 ELK

日志主要分为系统日志和应用日志两类。

试想一下,你该如何在一个具有几百台服务器的集群中定位到问题?如何追踪每天产生的几 G 甚至几 T 的数据?集中式日志就是此类问题的解决方案。

早期我们使用自主研发的 Log4Net+MongoDB 来收集和检索日志信息,但随着数据量的增加,查询速度却变得越来越慢。后期改为开源的 ELK,虽然易用性有所下降,但它支持海量数据以及与编程语言无关的特征。下图是 ELK 的架构图:

4、任务调度 Job

任务调度 Job 如同数据库作业或 Windows 计划任务,是分布式系统中异步和批处理的关键。我们的 Job 分为 WinJob 和 HttpJob:

  • WinJob 是操作系统级别的定时任务,使用开源的框架 Quartz.NET 实现;
  • 而 HttpJob 则是自主研发实现,采用 URL 方式可定时调用微服务。

HttpJob 借助集群巧妙地解决了 WinJob 的单点和发布问题,并集中管理所有的调度规则,调度规则有简单规则和 Cron 表达式。HttpJob 它简单易用,但间隔时间不能低于 1 分钟,毕竟通过 URL 方式来调度并不高效。

下图是 HttpJob 的管理后台:

5、度量工具 Metrics

“没有度量就没有提升”,度量是改进优化的基础,是做好一个系统的前置条件。

Zabbix 一般用于系统级别的监控,Metrics 则用于业务应用级别的监控。业务应用是个黑盒子,通过数据埋点来收集应用的实时状态,然后展示在大屏或看板上。它是报警系统和数字化管理的基础,还可以结合集中式日志来快速定位和查找问题。我们的业务监控系统使用 Metrics.NET+InfluxDB+Grafana。

6、微服务 MSA

微服务是细粒度业务行为的重用,需要与业务能力及业务阶段相匹配。

微服务框架是实现微服务及分布式架构的关键组件,我们的微服务框架是基于开源 ServiceStack 来实现。它简单易用、性能好,文档自动生成、方便调试测试,调试工具 Swagger UI、自动化接口测试工具 SoapUI。

微服务的接口开放采用我们自主研发的微服务网关,通过治理后台简单的配置即可。网关以 NIO、IOCP 的方式实现高并发,主要功能有鉴权、超时、限流、熔断、监控等,下图是 Swagger UI 调试工具:

7、搜索引擎 Solr

分库分表后的关联查询,大段文本的模糊查询,这些要如何实现呢?

显然传统的数据库没有很好的解决办法,这时可以借助专业的检索工具。全文检索工具 Solr 不仅简单易用性能好,而且支持海量数据高并发,只需实现系统两边数据的准实时或定时同步即可。下图是 Solr 的工作原理:

8、更多工具

  • 分布式协调器 ZooKeeper:ZK 工作原理、配置中心、Master 选举、Demo,一篇足以;
  • ORM 框架:Dapper.NET 语法简单、运行速度快,与数据库无关,SQL 自主编写可控,是一款适合于互联网系统的数据库访问工具;
  • 对象映射工具 EmitMapper 和 AutoMapper:EmitMapper 性能较高,AutoMapper 易用性较好;
  • IoC 框架:控制反转 IoC 轻量级框架 Autofac;
  • DLL 包管理:公司内部 DLL 包管理工具 NuGet,可解决 DLL 集中存储、更新、引用、依赖问题;
  • 发布工具 Jenkins:一键编译、发布、自动化测试、一键回滚,高效便捷故障低。

二、架构篇:思想提升

会使用以上框架并不一定能成为优秀的架构师,但一位优秀架构师一定会使用框架。架构师除了会使用工具外,还需要架构设计思想和性能调优技能。此部分以真实项目为背景,思想方法追求简单有效,内容包括企业总体架构、单个项目架构设计、统一应用分层、调试工具 WinDbg。

1、企业总体架构

当我们有了几百个上千个应用后,不仅仅需要单个项目的架构设计,还需要企业总体架构做顶层思考和指导。

大公司与小商贩的商业思维是一样的,但大公司比较难看到商业全貌和本质。而小公司又缺乏客户流量和中间件的应用场景,中型公司则兼而有之,所以企业总体架构也相对好落地。

企业总体架构需要在技术、业务、管理之间游刃有余地切换,它包括业务架构、应用架构、数据架构和技术架构。

附档是一份脱敏感信息后的真实案例,有参考 TOGAF 标准,但内容以解决公司系统的架构问题为导向、以时间为主线,包括企业商务模型、架构现状、架构规划和架构实施。

2、单个项目架构设计

应用架构设计如同施工图纸,能直接指导工程代码的实施。

上一环是功能需求,下一环是代码实施,这是架构设计的价值所在。从功能需求到用例,到用例活动图,到领域图、架构分层,到核心代码,它们之间环环相扣。做不好领域图可能源自没有做好用例活动图,因为用例活动图是领域图的上一环。关注职责、边界、应用关系、存储、部署是架构设计的核心,下图是具体案例参考:

3、统一应用分层

给应用分层这件事情很简单,但是让一家公司的几百个应用采用统一的分层结构,这可不是件简单的事情。它要做到可大可小、简单易用、支持多种场景,我们使用 IPO 方式:I 表示 Input、O 表示 Output、P 表示 Process,一进一出一处理。

应用系统的本质就是机器,是处理设备,也是一进一出一处理,IPO 方式相对于 DDD 而言更为简单实用。

4、诊断工具 WinDbg

生产环境偶尔会出现一些异常问题,而 WinDbg 或 GDB 就是解决此类问题的利器。调试工具 WinDbg 如同医生的听诊器,是系统生病时做问题诊断的逆向分析工具,Dump 文件类似于飞机的黑匣子,记录着生产环境程序运行的状态。

本文主要介绍了调试工具 WinDbg 和抓包工具 ProcDump 的使用,并分享一个真实的案例。

N 年前不知谁写的代码,导致每一两个月偶尔出现 CPU 飙高的现象。我们先使用 ProcDump 在生产环境中抓取异常进程的 Dump 文件,然后在不了解代码的情况下通过 WinDbg 命令进行分析,最终定位到有问题的那行代码。

三、公共应用篇:业务与技术的结合

先工具再框架,然后架构设计,最后深入公共应用。

公共应用因为与业务系统结合紧密,但又具有一定的独立性,所以一般自主开发,不使用开源也不方便开源。公共应用主要包括单点登录、企业支付网关、CTI 通讯网关(短信邮件微信),下面介绍单点登录和企业支付网关。

1、单点登录

应用拆分后总要合在一起,拆分是应用实施层面的拆分,合成是用户层面的合成,而合成必须解决认证和导航问题。单点登录 SSO 即只需要登录一次,便可到处访问,它是建立在用户系统、权限系统、认证系统和企业门户的基础上。

我们的凭证数据 Token 使用 JWT 标准,以解决不同语言、不同客户端、跨 WebAPI 的安全问题。

2、企业支付网关

企业支付网关集中和封装了公司的各大支付,例如支付宝、财付通、微信、预付款等。它统一了业务系统调用各支付接口的方式,简化了业务系统与支付系统的交互。它将各种支付接口统一为支付、代扣、分润、退款、退分润、补差、转账、冻结、解冻、预付款等,调用时只需选择支付类型即可。

企业支付网关将各大支付系统进行集中地设计、研发、部署、监控、维护,提供统一的加解密、序列化、日志记录和安全隔离。

四、进阶篇:从架构到管理

架构要落地、固化和提升,需要通过技术架构与组织架构的对齐来达成。从生产力到生产关系,从架构师到技术管理,你关注的角度也将发生变化。从关注技术到关注技术的商业价值、技术与业务的匹配与融合、技术团队的文化等等。

本部分内容包括技改之路、技术与业务的匹配与融合、研发团队文化是怎么长出来的,重点会介绍以下 3 个。

1、技改之路:从单块应用到微服务

技改是技术改造的简称,是技术的蜕变。这里指的是在公司技术发展的某个瓶颈阶段,按原有开发和组织方式已经无法玩下去,这时公司希望引进架构师或技术牛人,来破解当前困局。

技改之路少讲技术多讲路,我们不过多地关注技术细节和中间件的实现,而重点讲述技术改造的过程和思考。技改是大折腾,于公司于个人而言都是。小改怡情、大改伤身,所以真正高手下棋,应该是通盘无妙招,让正确的事情很容易发生,基于自然的演化来实现技术的演进。

2、技术与业务的匹配与融合

是什么在驱动公司的发展?

技术说“科学技术是第一生产力”,市场说“没有市场,哪来的业务”,运营说他自己。应该说他们都是正确的,但又不全面。这如同盲人摸象一样,引发了不少的争论,也直接或间接地导致了技术与业务的矛盾。

本文先抛出了一个启发性的问题,然后分析问题出在哪里,理解源于彼此的了解,如何去匹配与融合,最后正面回答了该问题。只有尊重事物的发展规律,加强技术与业务的之间合作,才能促进公司发展。

3、研发团队文化是怎么长出来的

从死气沉沉到激情活力,从固步自封到好学分享,这是一个有关团队文化的主题。寺庙文化传承千百年,舌尖上的美食流传至今,它是如何形成和生长的?是参考大公司或从管理书籍上挑选几个词语,还是脚踏实地、土里吧唧,自己一步一步埋头干?

我们要先弄清遇到的问题,然后是找到解决之道,包括管理工具、制度和行为措施,并予以贯彻并形成一种习惯,最后是总结并归纳成几个可以贴到墙上的大字,即「共治分享自视一起拼,简单有效快」,这个过程就如同花朵一般。只有这样「长」出来的文化,才能管人做事,才能成为公司或团队的执行力。

以上目录顺序不仅是架构改造的参考路径,也是架构师的成长路径。照着做,你也能够成为架构师。本文后续还会涉及框架中的其他部分,对内容进行更详细的延展分析。

我们希望能够尽量降低工具对研发人员的门槛,实现简单实用、降低成本。文章中部分 Demo 采用 C# 或 Java 语言,但到了框架与架构层面,与语言本身没有太多关系。

如 RabbitMQ、Job、Redis 和集中式日志 ELK,它们服务端的部署都是一样的,只是客户端语言版本稍有不同。所有 Demo 在一段时间内都可直接运行,服务地址和管理后台亦可直接访问。

以上这些基础工作,希望能够帮助到中小型研发团队,解决大家项目中遇到的实际问题,也愿与你一起在架构方面有所成长,谢谢!

案例参考和 Demo 下载

下载地址:

https://github.com/das2017?tab=repositories

 

中小型研发团队架构实践:集中式日志 ELK

  • 张辉清
  • 杨丽

阅读数:57482017 年 12 月 3 日

一、集中式日志

日志可分为系统日志、应用日志以及业务日志,系统日志给运维人员使用,应用日志给研发人员使用,业务日志给业务操作人员使用。我们这里主要讲解应用日志,通过应用日志来了解应用的信息和状态,以及分析应用错误发生的原因等。

随着系统的日益复杂,大数据时代的来临,需要几十甚至上百台的服务器是常有的事,因此迫切需要有一套针对日志、且能够集中式管理的产品。ELK 就实现了集中式日志管理平台,该平台统一涵盖了分布式日志收集、检索、统计、分析以及对日志信息的 Web 管理等集中化管控。

1.1、ELK 简介

ELK 是 Elasticsearch、Logstash、Kibana 的简称,这三套开源工具组合起来能搭建一套强大的集中式日志管理平台。

Elasticsearch 是个开源的分布式搜索引擎,提供搜索、分析、存储数据三大功能。它的特点有:分布式、自动发现、索引自动分片、索引副本机制、RESTful 风格接口、多数据源以及自动搜索负载等。

Logstash 是一个开源的用来收集、解析、过滤日志的工具。支持几乎任何类型的日志,包括系统日志、业务日志和安全日志。它可以从许多来源接收日志,这些来源主要包括 Syslog、消息传递(例如 RabbitMQ)和 Filebeat;能够以多种方式输出数据,这些方式主要包括电子邮件、WebSockets 和 Elasticsearch。

Kibana 是一个基于 Web 的友好图形界面,用于搜索、分析和可视化存储在 Elasticsearch 中的数据。它利用 Elasticsearch 的 RESTful 接口来检索数据,不仅允许用户定制仪表板视图,还允许他们以特殊的方式查询、汇总和过滤数据。

1.2、ELK 的架构

下图是集中式日志管理 ELK 的架构图。出于性能的考虑,选择采用了 Beats+EK 的形式来组合搭建集中式日志管理系统。



ELK 架构

二、配置方法

2.1、Elasticsearch

Elasticsearch 部署完成后,需要更改 elasticsearch.yml 配置文件中的主要属性:cluster.name、node.name、network.host、discovery.zen.ping.unicast.hosts。其中,当部署 Elasticsearch 时是以集群模式部署的,那么 discovery.zen.ping.unicast.hosts 这个属性才会需要被配置。

2.2、Logstash

通过配置 filebeat-pipeline.conf 文件中的 Input、Filter(可选)和 Output 来完成对数据的采集、过滤和输出,如下图所示:



Logstash 配置

然后以 filebeat-pipline.conf 文件启用 Logstash 服务,如下图所示:



启用 Logstash 服务

备注:由于采用的是 Beats+EK 这个方案来实现集中式日志管理,所以不需要配 Logstash。

2.3、Kibana

通过更改 kibana.yml 配置文件内容,用来连接正确的 Elasticsearch 服务地址,通常只需要配置 elasticsearch.url 属性即可,请见下图的第一个图。配置完成后,执行【bin/kibana &】命令启用 Kibana 服务,请见下图的第二个图。最后就可以在浏览器中打开 Kibana 管理页面(访问地址:http://139.198.13.12:4800/)来查看日志。



Kibana 的配置说明



启用 Kibana 服务

2.4、Filebeat

filebeat.yml 配置文件内容主要包含 Filebeat、Output、Shipper(可选)、Logging(可选)四大部分,其中 Filebeat 主要定义监控的日志文件信息,Output 主要配置日志数据的输出目标。

filebeat.yml 文件中,主要属性值的命名规范如下:

  1. fields.AppID 的命名规范是{AppID}。
  2. fields.AppName 的命名规范是{产品线英文名称}.{项目英文名称}(如果项目英文名称由 2 个或 2 个以上英文单词组成,则单词之间请用. 分隔)。
  3. 针对 index 属性需要注意的是:索引 (index) 所定义的值是{产品线英文名称},但英文字母必须全部小写,且不能以下划线开头,也不能包含逗号。

filebeat.yml 的配置示例如下图所示:



filebeat.yml 的配置示例

日志文件存放在哪台服务器中,filebeat 服务就部署在哪台服务器中。在 windows 操作系统上启用 filebeat 服务的步骤:

1、在 windows 下开启搜索,输入 powershell,打开 powershell 所在文件位置,右键 powershell.exe 以管理员身份运行,进入 PowerShell 窗口。

或者以管理员身份启动 cmd.exe,输入命令 powershell,进入 PowerShell 窗口 。

注意:

请务必确保以管理员身份打开 PowerShell 窗口,否则的话在以下第 2 步中运行.ps1 脚本时,就会报没有权限创建 filebeat 服务的错误:

2、导向到 filebeat 执行程序所在目录,例如:cd 'E:\ELK\filebeat-1.3.0-windows',然后执行命令:powershell.exe -ExecutionPolicy UnRestricted -File .\install-service-filebeat.ps1。

3、之后可以在 PowerShell 窗口中通过以下几个命令来查看、启用以及停止 filebeat 服务:

  • 查看 filebeat 服务状态:Get-Service filebeat
  • 启动 filebeat 服务:Start-Service filebeat
  • 停止 filebeat 服务:Stop-Service filebeat

三、使用方法

3.1、Log4Net 本地日志

1、日志存放路径规范:{盘符}:\Log4Net{AppID}\,其中 AppID 即为我们所做项目的六位编码。例如:D:\Log4Net\110107\。

2、log4net.config 配置内容:

复制代码

 
 

<?xml version="1.0" encoding="utf-8" ?>

 

<configuration>

 

<configSections>

 

<section name="log4net" type="System.Configuration.IgnoreSectionHandler"/>

 

</configSections>

 

<appSettings>

 

</appSettings>

 

<log4net>

 

<appender name="FileAppender" type="log4net.Appender.RollingFileAppender">

 

<!--AppID 150202,用于区分哪个应用的日志 -->

 

<file value="D:\Log4Net\150202\" />

 

<rollingStyle value="Composite" />

 

<datePattern value="yyyy-MM-dd&quot;.log&quot;" />

 

<staticLogFileName value="false" />

 

<param name="Encoding" value="utf-8" />

 

<maximumFileSize value="100MB" />

 

<countDirection value="0" />

 

<maxSizeRollBackups value="100" />

 

<appendToFile value="true" />

 

<layout type="log4net.Layout.PatternLayout">

 

<conversionPattern value="记录时间:%date 线程:[%thread] 日志级别:%-5level 记录类:%logger 日志消息:%message%newline" />

 

</layout>

 

</appender>

 

<logger name="FileLogger" additivity="false">

 

<level value="DEBUG" />

 

<appender-ref ref="FileAppender" />

 

</logger>

 

</log4net>

 

</configuration>

注意了:

  • maximumFileSize 设置为 100MB;请给 countDirection 设置为大于 -1 的整数;maxSizeRollBackups 设置为 100。
  • 日志文件内容规范:日志文件中的每条日志内容要求是“记录时间 线程 日志级别 出错类 日志消息”。

3.2、日志查询

基于 Kibana 查询日志(访问地址:http://139.198.13.12:4800/),主要通过以下几个步骤实现:

  1. 选择业务索引库。
  2. 选择日期范围。
  3. 输入待查找的内容实现精确查询,也可以通过输入 * 实现模糊查询。
  4. 点击每条日志的展开图标,可以查看该条日志的详细信息。
  5. 在 Kibana 界面的左侧区域,自上而下依次是索引库选择框、选中字段集合列表(Selected Fields)、可用字段集合列表(Available Fields)。通过添加可用字段到选中字段集合列表中来改变 Kibana 右侧的日志表格呈现。

请参考如下图所示:



Kibana 查询日志界面

四、Demo 下载及更多资料

 

中小型研发团队架构实践:生产环境诊断利器 WinDbg 帮你快速分析异常情况 Dump 文件

  • 张辉清
  • 许珍珠

阅读数:16332018 年 1 月 15 日

一、简介

生产环境偶尔会出现一些异常问题,WinDbg 或 GDB 就是解决此类问题的利器。调试工具 WinDbg 如同医生的听诊器,是系统生病时做问题诊断的逆向分析工具,Dump 文件类似于飞机的黑匣子,记录着生产环境程序运行的状态。

本文主要介绍了调试工具 WinDbg 和抓包工具 ProcDump 的使用,并分享一个真实的案例。N 年前不知谁写的代码,导致每一两个月偶尔出现 CPU 飙高的现象。我们先使用 ProcDump 在生产环境中抓取异常进程的 Dump 文件,然后在不了解代码的情况下通过 WinDbg 命令进行分析,最终定位到有问题的那行代码。

1、WinDbg

WinDbg 是在 Windows 平台下的、强大的用户态和内核态调试工具。相比较于 Visual Studio,它是一个轻量级的调试工具,所谓轻量级指的是它的安装文件大小较小,但是其调试功能,却比 VS 更为强大。

它的另外一个用途是可以用来分析 Dump 数据。WinDbg 是 Microsoft 公司免费调试器调试集合中的 GUI 的调试器,支持 Source 和 Assembly 两种模式的调试。

WinDbg 不仅可以调试应用程序,还可以进行 Kernel Debug。结合 Microsoft 的 Symbol Server,可以获取系统符号文件,便于应用程序和内核的调试。

WinDbg 支持的平台包括 x86、IA64、AMD64。虽然 WinDbg 也提供图形界面操作,但它最强大的地方还是有着强大的调试命令,一般情况会结合 GUI 和命令行进行操作,常用的视图有:局部变量、全局变量、调用栈、线程、命令、寄存器、白板等。其中“命令”视图是默认打开的。

2、DebugDiag

DebugDiag 最初是为了帮助分析 IIS 的性能问题而开发的,它同样可以用于任何其他的进程。DebugDiag 工具主要用于帮助解决如挂起、 速度慢、 内存泄漏或内存碎片,和任何用户模式进程崩溃等问题。

该工具包括附加调试脚本,侧重于互联网信息服务(IIS)应用程序、 Web 数据访问组件、 COM+ 和相关 Microsoft 技术、SharePoint 和.NET。它提供可扩展对象模型中的 COM 对象的形式,并具有一个内置的报告框架提供的脚本主机。它由 3 部分组成,包括调试服务、 调试器主机和用户界面。

3、ProcDump

ProcDump 是 System Internal 提供的一个专门用来监测程序 CPU 高使用率从而生成进程 Dump 文件的工具。ProcDump 可以根据系统的 CPU 使用率或者指定的性能计数器来针对特定进程生成一系列的 Dump 文件,以便调试者对事故原因进行分析。

二、工具下载地址

复制代码

 
 

1、WinDbg 下载

  
 

   x86 位版本下载:http://download.microsoft.com/download/A/6/A/A6AC035D-DA3F-4F0C-ADA4-37C8E5D34E3D/setup/WinSDKDebuggingTools/dbg_x86.msi

 

   x64 位版本下载:http://download.microsoft.com/download/A/6/A/A6AC035D-DA3F-4F0C-ADA4-37C8E5D34E3D/setup/WinSDKDebuggingTools_amd64/dbg_amd64.msi

  
 

2、DebugDiag v2 Update 2 下载:https://www.microsoft.com/en-us/download/details.aspx?id=49924

  
 

3、ProcDump v9.0 下载:https://download.sysinternals.com/files/Procdump.zip

三、获取异常进程的 Dump 文件

有以下四种方式获取 Dump 文件,具体如下:

1、通过【任务管理器】获取 Dump 文件,这样获取的是 MinDump

2、利用 WinDbg 的 adplus 获取 Dump 文件,这样获取的是 FullDump

3、通过 DebugDiag 创建.NET 异常转储 Dump 文件

4、通过 ProcDump 抓取异常线程 Dump 文件。

现在重点介绍通过 ProcDump 抓取异常线程 Dump 文件,使用方法如下:

4.1、命令行

复制代码

 
 

procdump [-a] [[-c|-cl CPU usage] [-u] [-s seconds]] [-n exceeds]

 

[-e [1 [-b]] [-f <filter,...>] [-g] [-h] [-l] [-m|-ml commit usage]

 

[-ma | -mp] [-o] [-p|-pl counter threshold] [-r] [-t]

 

[-d <callback DLL>] [-64] <[-w] <process name or service name or PID>

 

[dump file] | -i <dump file> | -u | -x <dump file> <image file>

 

[arguments] >] [-? [ -e]]

实例:

复制代码

 
 

procdump -c 70 -s 5 -ma -n 3 w3wp

 

 - 当系统 CPU 使用率持续 5 秒超过 70% 时,连续抓 3 个 Full Dump。

 

procdump outlook -p "\Processor(_Total)\% Processor Time" 80

 

 - 当系统 CPU 使用率超过 80%,抓取 Outlook 进程的 Mini Dump。

 

procdump -ma outlook -p "\Process(Outlook)\Handle Count" 10000

 

 - 当 Outlook 进程 Handle 数超过 10000 时抓取 Full Dump

 

procdump -ma 4572

 

 - 直接生成进程号位 4572 的 Full Dump。

下图是在 WindgbHighCpu 进程中造成 High CPU 时运行 ProcDump 命令的运行效果,可以看到在 CPU 每次持续 5 秒达到 5% 后就会生成相应的 Dump 文件,共生成了 3 份 Full Dump 文件:

注意:

  • ProcDump 需要进程已经启动,并且中途不能停止。比如需要抓取 IIS Worker Process 的 High CPU Dump,由于 IIS Worker Process 默认会配置 Idle Timeout = 20 min,即该进程在 20 分钟内没有任何请求的话就会自动结束,这种情况下 ProcDump 也会自动结束。需要重新运行命令。因此如果目标程序存在这样的配置,需要暂时将该配置取消。
  • 有些系统管理员希望能够运行该工具后退出用户 session,ProcDump 是做不到的,如果有这种需求可以考虑使用 DebugDiag。
  • 在调试 High CPU 问题的时候经常用到的一个命令是!runaway,但是有些时候!runway 在 ProcDump 抓取 Dump 文件的过程中运行不出来,报错信息如下:

复制代码

 
 

0:000> !runaway ERROR: !runaway: extension exception 0x80004002. "Unable to get thread times - dumps may not have time information"

解决方法是将 Debugging Tools for Windows (WinDbg) 安装目录下的 dbghelp.dll 拷贝到 procdump.exe 所在目录下,然后再运行命令抓取 Dump。

四、WinDbg 使用方法

操作步骤如下:

1、抓取异常程序的 Dump 文件。

2、设置符号表  

符号表是 WinDbg 关键的“数据库”,如果没有它,WinDbg 基本上就是个废物,无法分析更多问题。所以使用 WinDbg 设置符号表,是必须要走的一步。

  • a、运行 WinDbg 软件,然后按【Ctrl+S】弹出符号表设置窗。

  • b、将符号表地址:SRV*C:\Symbols*

    http://msdl.microsoft.com/download/symbols
     粘贴在输入框中,点击确定即可。点击确定之前,请先确认红色字的文件夹是否已被新建。

注:红色字表示符号表本地存储路径,建议固定路径,可避免符号表重复下载。

3、学会打开第一个 Dump 文件!

使用【Ctrl+D】快捷键,或者点击 WinDbg 界面上的【File=>Open Crash Dump...】按钮,来打开一个 Dump 文件。

当你想打开第二个 Dump 文件时,可能因为上一个分析记录未清除,导致无法直接分析 Dump 文件,此时你可以使用快捷键【Shift+F5】来关闭上一个对 Dump 文件的分析记录。  

4、通过简单的几个命令学会分析 Dump 文件

分享一个数据库连接超时的 Dump 案例的分析过程:  

当你打开一个 Dump 文件后,可能因为太多信息,让你无所适从,不过没关系,我们只需要关注几个关键信息就可以了。

a. 加载 SOS 扩展命令

加载 SOS 之前,先确定 SOS 的位置和版本,确定方法如下:

如果安装了 Visual Studio,那么先按照如下步骤打开 VS 的命令行:

然后,在打开的 VS 命令行中输入 where sos.dll,使获得 SOS 的位置和版本:

确定完 SOS 位置和版本号后,开始加载 SOS 扩展命令:

复制代码

 
 

.load C:\Windows\Microsoft.NET\Framework64\v4.0.30319\SOS.dll

如下图所示:

b. 使用!clrstack 命令来查看当前的调用堆栈信息

如下图所示:

c. 使用!dso 命令来查看堆栈上的所有对象详细信息

如下图所示:

综合以上分析可以大胆地猜测 Common.cs 中第 16 行“Data Source=***;Initial Catalog=***;Persist Security Info=True;User ID=sa;Password=***”的这个数据库连接字符串应该有问题,然后到代码中相应的地方进一步确认和修改就可以了。

五、真实案例

分享一个笔者工作过的公司的某业务系统使 CPU 飙高 90% 或以上的 Dump 案例的分析过程,步骤如下:

1、使用 ProcDump 抓包

2、加载 SOS 扩展命令:

复制代码

 
 

.load C:\Windows\Microsoft.NET\Framework\v2.0.50727\sos.dll

3、分析:

执行!runaway 命令,查看线程使用 CPU 时间情况,如下图所示。着重分析前面几个线程。

执行~22s 命令,进入到线程 22,如下图所示:

执行!clrstack 命令查看当前线程堆栈变量值的信息,从图中可以猜出大概是 ExecuteNonQuery() 这方法有点问题,如下图所示:

再执行!dso 命令可以查看堆栈上的所有对象详细信息,如下图所示:

从图中看,造成 CPU 飙高的罪魁祸首多半由 SQL Server 执行

复制代码

 
 

INSERT INTO [dbo].[tbl_Interface_ProcessLog] (IKey,Username,ClientIP,Module,OrderNo,LogType,Content) VALUES (@IKey,@Username,@ClientIP,@Module,@OrderNo,@LogType,@Content)

这条语句时产生异常引起,然后到源代码中找出相应的语句,经过进一步的确认、修改和重新发布后就解决了 CPU 飙高的问题。

至此,掌握几个简单的 WinDbg 命令之后,基本上绝大多数 Dump 大家都可以独立分析了。当然 WinDbg 是个强大的工具,同时产生 CPU 飙高和内存泄漏的原因也有很多。如果想分析得足够准确,那么就只有多学多练,多去分析。因为掌握 WinDbg 分析除了需要懂得几个命令之外,经验更加重要,最后再补充两点:

  • WinDbg 不是专门用于调试.NET 程序的工具,它更偏向于底层,可用于内核和驱动调试,特别是对于某些相当疑难的问题调试有所帮助,例如内存泄漏等问题。进行普通的.NET 程序调试还是使用微软专为.NET 开发所提供的调试工具更方便一些。
  • SOS 扩展命令中最有用的命令是!help,使用该命令可以列出所有可用的 SOS 扩展命令列表,使用!help [SOSCommandName] 可以查看每一个具体扩展命令的详细使用说明。例如!help dumpheap 就可以查看!dumpheap 这个扩展命令的具体使用方法。多多利用!help 命令可以很快上手 SOS。

六、Demo 下载及更多资料

 

中小型研发团队架构实践:应用监控怎么做?

  • 张辉清
  • 杨丽

阅读数:39972017 年 12 月 11 日

一、Metrics 简介

应用监控系统 Metrics 由 Metrics.NET+InfluxDB+Grafana 组合而成,通过客户端 Metrics.NET 在业务代码中埋点,Metrics.NET 会把收集到的数据存储在 InfluxDB 数据库中,然后通过 Grafana 来展示监控数据。

其中,InfluxDB 服务端部署的版本号是 1.3.1,Grafana 部署的版本号是 4.0.1。下面将结合这 3 个工具来介绍如何实现对应用的监控。

Metrics.NET 移植自 Java 的 metrics,它是一个给 CLR 提供度量的工具包。在业务代码中埋点 Metrics.NET 代码后,就可以方便地对各技术指标、业务指标进行度量,如:共花多长时间完成某方法的执行、某方法在被执行的过程中共出现过几次异常、某时间段内共下多少订单量。

Metrics.NET 共提供 5 种度量类型:Gauge、Counter、Meter、Histogram 以及 Timer。其中 Meter 和 Histogram 这两种度量类型目前可以完全满足笔者所在公司的度量需求,所以,下面只介绍了 Meter 和 Histogram 这两种,另外 3 个若有兴趣可自行抽空去了解。

二、埋点 Metrics.NET 的方法

首先为需要收集 Metrics.NET 监控数据的业务项目引用 Metrics.dll。

然后,在项目中的 App.config/Web.config 文件中加上如下配置信息:

复制代码

 
 

<add key="AppID" value="150106"/>  

 

<add key="Metrics.DBUri" value="http://139.198.13.12:4126/write"/>

 

<add key="Metrics.UserName" value="Arch"/>

 

<add key="Metrics.Password" value="Arch"/>

 

<add key="Metrics.Database" value="ArchDB"/>

1、Meter

Meter 用于度量 TPS(每秒处理的请求数)。

示例:模拟统计成功下单量、下单金额、失败下单量。

调用 Meter 对象的 Mark() 方法:

复制代码

 
 

static void CreateOrder()

 

{        

 

 try        

 

 {                

 

   // 省略关于下单的业务逻辑代码      

 

   //......

  
 

   // 分别统计成功下单量和下单金额,统一写到 MetrisKey 中              

 

   MetricsKey.OrderCount.Mark();

 

   if (n % 2 == 1)

 

   {

 

       MetricsKey.OrderMoneyCount.Mark("BuyerA", n);

 

   }

 

   else

 

   {

 

       MetricsKey.OrderMoneyCount.Mark("BuyerB", n);

 

   }          

 

 }        

 

 catch (Exception)        

 

 {                

 

   // 统计失败下单量,统一写到 MetrisKey 中            

 

   MetricsKey.OrderErrorCount.Mark();

  
 

   // 省略异常处理代码......        

 

 }

 

}

2、Histogram

Histogram 用于度量流数据中 Value 的分布情况,它不仅使您能像 Meter 一样测量出 TPS ,还能测量出最小值、最大值和平均值。使用场景如:统计服务器的延迟时间、统计某方法共执行多长时间。

示例:模拟统计航班查询引擎方法的耗时情况。

调用 Histogram 对象的 Update() 方法:

复制代码

 
 

private readonly Histogram searchFlightTime = MetricsHelper.Histogram("MetricsDemo.SearchFlightTime", Unit.Custom("ms"));

  
 

static void SearchFlight()

 

{          

 

 Stopwatch stopwatch = Stopwatch.StartNew();  

  
 

 // 模拟关于航班查询的业务逻辑的代码            

 

 Random random = new Random((int)DateTime.Now.Ticks & 0x0000FFFF);

 

 var n = Random.Next(100);                

 

 Thread.Sleep(n);                

  
 

 stopwatch.Stop();        

  
 

 // 统计航班搜索耗时  

 

 searchFlightTime.Update(stopwatch.ElapsedMilliseconds);

 

}

三、Grafana 配置

查阅 Metrics Dashboard Demo 的地址:http://139.198.13.12:4127/。打开这个 Metrics 地址后,如果页面显示已登录状态,那么在开始查阅前,请先确认是否把组织切换到了 Default Org.:

1、仪表盘设置

点击位于下图上方的 Home 图标,会下拉弹出 Dashboard 列表:

点击位于上图下方的 Create New 按钮,会进入到新建面板 Panel 页面,点击位于下图上方的保存图标按钮:

在弹出的 Save As... 对话框中输入 Dashboard 名称,如 Arch.OrderCountDemo,然后点击 Save 按钮进行保存:

2、面板(Panel)设置

点击上面创建的【Arch.OrderCountDemo】Dashboard 图标,进入属于这个 Dashboard 的面板(Panel)页面:

点击位于上图的 ADD ROW 按钮,进入下图。其中,Graph 表示以图表(有折线图、柱状图、散点图、梯形图)形式展示数据、Singlestat 表示单个统计、Table 表示以表格形式展示数据、PieChart 表示以饼状图形式展示数据。这几种统计类型的面板设置方式类似,本文将以 Graph 为例进行说明。

2.1、数据设置

点击上图的 Graph 图标创建图表:

点击上图的 Panel Title,在弹出菜单中单击 Edit 打开 Panel 编辑界面,即进入 Metrics 选项卡面板。关于 Meter 的查询数据语句配置一般如下:

关于 Histogram 的查询数据语句配置一般如下:

其中,fill() 一般被设为 null,但当查询时间范围很大时(如 1 天),请用 fill(0);另外,appId、appId、serverIP、$summarize 这 3 个变量是在模板(Templating)中设置,请看第 3 小节的介绍。

2.2、样式配置

2.2.1、General 选项卡用来设置 Panel 样式

主要用来设置 Panel 的标题:

2.2.2、Axes 选项卡用来设置坐标轴

Label 表示设置左侧 Y 轴旁显示什么说明文字,另外,Unit 表示设置左侧 Y 轴数字的单位:

2.2.3、Legend 选项卡用来设置显示样式

2.2.4、Display 选项卡用来设置图表样式

Draw options 子选项卡用来设置图表显示效果:

  • Draw Modes:Points 表示设置是否在图中显示散点;
  • Mode Options:Fill 表示设置填充度、Line Width 表示设置图表线的粗细、Point Radius 表示设置圆点半径的长度;
  • Stacking & Null value:Null value 选择 null as zero 时表示设置当该时间节点在 InfluxDB 中没有记录时,用 0 替代。

3、模板(Templating)设置

打开 Templating 设置页面:

新建变量:

新建 serverIP 变量:

(在 Query 文本框处,输的是:SHOW TAG VALUES WITH KEY = "ServerIP")

新建 summarize 变量,其中 Values 值可以自行添加或删除,值与值之间用英文状态的逗号隔开:

新建 adhoc 变量:

4、设置 Time Range

一个 Dashboard 中,除了需要显示实时监控数据外,有时还需要显示历史的监控数据,主要目的是要通过对历史监控数据的观察来预测未来的业务量走势,那么需要重写 Time Range,即需要在 Time range 选项卡中进行设置。

例如,在一个 Panel 中需要显示近 24 小时的历史监控数据,那么请在这个 Panel 中加上如下配置:

5、告警设置

在 Grafana 当前版本(4.0.1)中,告警目前仅支持 Graph 类型的面板,在将来版本会添加 Singlestat 和 Table 类型面板的支持。另外,由于告警查询语句不支持 template 变量,所以最好是对不使用 template 变量的 Panel 才设置告警。

5.1、设置通知规则

在左侧菜单中选择 Alerting -> Notifications 进入通知列表页:

点击 New Notification 按钮新建一个通知:

在 Name 文本输入框中,输入通知名称,类型 Type 选择 email。设置完成之后单击 Save 按钮,然后点击 Send Test 按钮测试下通知是否能够发送成功。

5.2、设置告警规则

进入需要添加告警的 Panel 的编辑界面,转到 Alert 选项卡,点击 Create Alert 按钮,进入 Alert Config 子选项卡界面进行配置,其中 Evaluate every 表示设置执行频率,Conditions 表示配置何时告警的条件(WHEN 是选择聚合函数的地方,OF 用来设置时间段,IS ABOVE 或者 IS BELOW 用来设置阈值)。对 Alert Config 子选项卡界面的配置参考如下:

然后在 Notifications 子选项卡界面中配置通知规则:

5.3、暂停告警操作

在左侧菜单中点击 Alerting -> Alert List 进入告警规则列表页,点击暂停图标按钮就可以停止该告警:

四、其它说明

  • 1、Grafana 匿名访问地址: http://139.198.13.12:4127/。建议使用 Google Chrome 浏览器打开 Grafana;
  • 2、一个 MetricsName 对应一张数据表,建议明确定义 MetricsName;
  • 3、提供的 Metrics.dll 基于 0.4.8 的版本增加了 Unit Count 的返回,且适用于.NET Framework 4.5 及其以上版本。

五、总结 Metrics 的价值

  • 1、可以实时监控线上程序运行情况,形成闭环、不断改进;
  • 2、可以预测程序未来大致走向;
  • 3、可以及时发现故障,消灭在用户反馈之前;
  • 4、Metrics.NET 出现异常不影响业务流程;
  • 5、可设置自动报警,即时发送邮件、短信、微信 (通过 API)。

六、Demo 下载及更多资料

中小型研发团队架构实践:如何用好消息队列 RabbitMQ?

  • 张辉清

阅读数:79382017 年 11 月 30 日

一、写在前面

使用过分布式中间件的人都知道,程序员使用起来并不复杂,常用的客户端 API 就那么几个,比我们日常编写程序时用到的 API 要少得多。但是分布式中间件在中小研发团队中使用得并不多,为什么会这样呢?

原因是中间件的职责相对单一,客户端的使用虽然简单,但整个环境搭起来却不容易。所以对于系列中的几篇中间件文章,我们重点放在解决门槛问题,把服务端环境搭好(后期可云或运维解决),把中间件的基本职责和功能介绍好,把客户端 Demo 写好,让程序员抬抬脚,在调试代码中即可轻松入门。

根据我们以往几年的经验,初次接触也可以自主快速学习,文章和 Demo 以实用为主,以下是消息队列 RabbitMQ 的快速入门及应用。

二、为什么要用消息队列 MQ

1、业务系统往往要求响应能力特别强,能够起到削峰填谷的作用。

2、解耦:如果一个系统挂了,则不会影响另外个系统的继续运行。

3、业务系统往往有对消息的高可靠要求,以及有对复杂功能如 Ack 的要求。

4、增强业务系统的异步处理能力,减少甚至几乎不可能出现并发现象:

使用消息队列,就好比为了防汛而建葛洲坝,有大量数据的堆积能力,然后可靠地进行异步输出。例如:

传统做法存在如下问题,请见上图:

  1. 一旦业务处理时间超过了定时器时间间隔,就会导致漏单。
  2. 如果采用新开线程的方式获取数据,那么由于大量新开线程处理,会容易造成服务器宕机。
  3. 数据库压力大,易并发。

使用 MQ 后的好处,请见上图

  1. 业务可注册、可配置。
  2. 获取数据规则可配置。
  3. 成功消费 MQ 中的消息才会被 Ack,提高可靠性。
  4. 大大增强了异步处理业务作业的能力:

定时从数据库获取数据后,存入 MQ 消息队列,然后 Job 会定期扫描 MQ 消息队列,假设 Job 扫描后先预取 5 条消息,然后异步处理这 5 条消息,也就是说这 5 条消息可能会同时被处理。

三、RabbitMQ 简介

RabbitMQ 是基于 AMQP 实现的一个开源消息组件,主要用于在分布式系统中存储转发消息,由因高性能、高可用以及高扩展而出名的 Erlang 语言写成。

其中,AMQP(Advanced Message Queuing Protocol,即高级消息队列协议),是一个异步消息传递所使用的应用层协议规范,为面向消息的中间件设计。

RabbitMQ 特点如下:

高可靠:RabbitMQ 提供了多种多样的特性让你在可靠性和性能之间做出权衡,包括持久化、发送应答、发布确认以及高可用性。

高可用队列:支持跨机器集群,支持队列安全镜像备份,消息的生产者与消费者不论哪一方出现问题,均不会影响消息的正常发出与接收。

灵活的路由:所有的消息都会通过路由器转发到各个消息队列中,RabbitMQ 内建了几个常用的路由器,并且可以通过路由器的组合以及自定义路由器插件来完成复杂的路由功能。

支持多客户端:对主流开发语言(如:Python、Ruby、.NET、Java、C、PHP、ActionScript 等)都有客户端实现。

集群:本地网络内的多个 Server 可以聚合在一起,共同组成一个逻辑上的 broker。

扩展性:支持负载均衡,动态增减服务器简单方便。

权限管理:灵活的用户角色权限管理,Virtual Host 是权限控制的最小粒度。

插件系统:支持各种丰富的插件扩展,同时也支持自定义插件,其中最常用的插件是 Web 管理工具 RabbitMQ_Management,其 Web UI 访问地址:

http://139.198.13.12:6233/

登录账号:flight,密码:yyabc123。

四、RabbitMQ 工作原理

消息从发送端到接收端的流转过程即 RabbitMQ 的消息工作机制,请见下图:

消息发送与接收的工作机制

五、RabbitMQ 基本用法

共有 6 种基本用法:单对单、单对多、发布订阅模式、按路由规则发送接收、主题、RPC(即远程存储调用)。我们将介绍单对单、单对多和主题的用法。

1、单对单:单发送、单接收。请见下图。

2、单对多:一个发送端,多个接收端,如分布式的任务派发。请见下图:

3、主题:Exchange Type 为 topic,发送消息时需要指定交换机及 Routing Key,消费者的消息队列绑定到该交换机并匹配到 Routing Key 实现消息的订阅,订阅后则可接收消息。只有消费者将队列绑定到该交换机且指定的 Routing Key 符合匹配规则,才能收到消息。

其中 Routing Key 可以设置成通配符,如:* 或 #(* 表示匹配 Routing Key 中的某个单词,# 表示任意的 Routing Key 的消息都能被收到)。如果 Routing Key 由多个单词组成,则单词之间用. 来分隔。

命名规范:

交换机名的命名建议:Ex{AppID}.{自定义 ExchangeName},队列名的命名建议:MQ{AppID}.{自定义 QueueName} 。

六、Demo 下载及更多资料

RabbitMQDemo 下载地址https://github.com/das2017/RabbitMQDemo

RabbitMQ 的官方网址http://www.rabbitmq.com

 

中小型研发团队架构实践:如何规范公司所有应用分层?

  • 张辉清

阅读数:37002018 年 1 月 7 日

一、写在前面

应用分层这件事情看起来很简单,但每个程序员都有自己的一套,哪怕是初学者。如何让一家公司的几百个应用采用统一的分层结构,并得到大部分程序员的认同呢?这可不是件简单的事情,接下来以我们真实案例与大家一起探讨,先问大家两个技术问题:

服务的调用代码你觉得放到哪一层好呢?

  • A 表现层
  • B 业务逻辑层
  • C 数据层
  • D 公共层

如何组织好 VO(View Object 视图对象)、BO(Business Object 业务对象)、DO(Data Object 数据对象)、DTO(Data Transfer Object 数据传输对象) 呢?

不同的人会有不同的答案,所以要统一公司应用分层,以减少开发维护学习成本。统一应用分层要可大可小、简单易用、支持多种场景,我们采用 IPO 方式:I 是 Input、O 是 Output、P 是 Process,一进一出一处理。应用系统的本质是机器,是处理设备,一进一出一处理。

IPO 原理图

二、统一逻辑架构

统一应用分层的逻辑架构图

职责说明:

  • 文件夹分层法:应用分层采用文件夹方式的优点是可大可小、简单易用、统一规范,可以包括 5 个项目,也可以包括 50 个项目,以满足所有业务应用的多种不同场景;
  • 调用规约:在开发过程中,需要遵循分层架构的约束,禁止跨层次的调用;
  • 下层为上层服务:以用户为中心,以目标为导向。上层(业务逻辑层)需要什么,下层(数据访问层)提供什么,而不是下层(数据访问层)有什么,就向上层(业务逻辑层)提供什么;
  • 实体层规约:DO 是数据表对象,不是数据访问层对象,不是只能给数据访问层使用;DTO 是网络传输对象,不是表现层对象,不是只能给表现层使用;BO 是内存计算逻辑对象,不是业务逻辑层对象,不是只能给业务逻辑层使用 。如果仅限定在本层访问,则导致单个应用内大量没有价值的对象转换。以用户为中心来设计实体类,可以减少无价值重复对象和无用转换;
  • U 型访问:下行时表现层是 Input,业务逻辑层是 Process,数据访问层是 Output。上行时数据访问层是 Input,业务逻辑层是 Process,  表现层就 Output。

三、我们的具体规范

此规范我们用了四年,牵涉几百个应用,200 多个研发人员,是一个成功的实践。接下来就借用本文提供下载的 TripOrderService、TripSellerMVCSite 这两个 Demo 来进行具体规范的说明,以下是截图:

3.1、项目命名规则

项目命名规则:{产品线英文名全称}.{子系统英文名全称 + 应用名}.{项目职责英文名全称},如:Trip.Seller.DTO。

3.2、业务逻辑层的项目规范

规范说明:

  • 1、项目名的命名规则:{产品线英文名全称}.{子系统英文名全称 + 应用名}.xxxBusiness,如上图的 Trip.Order.Business。
  • 2、类名以 Logic 结尾,如上图的 OrderLogic.cs。

3.3、数据操作项目规范

规范说明:

  • 1、各数据操作项目名根据使用什么数据库进行分类,然后以 DB 为结尾,具体命名规则是:{产品线英文名全称}.{子系统英文名全称 + 应用名}.{使用什么数据库}DB,如上图的 Trip.Seller.MSSQLDB。
  • 2、如果涉及到多个数据库访问的,那么数据操作项目下的类文件需要按数据库名称(以 DB 为结尾)创建文件夹分开,如上图的 TripOrderDB 文件夹。
  • 3、建议在应用中使用 SQL 语句,不使用存储过程。在数据库中不新增存储过程,但旧的存储过程可以继续使用和修改。
  • 4、分页建议使用数据库(如 SQLServer)的最新特性进行分页,并将每个分页 SQL 直接写到应用中。

3.4、实体类项目规范

数据传输对象 DTO 规范

规范说明:

  • 1、DTO 项目命名规则:{产品线英文名全称}.{子系统英文名全称 + 应用名}.DTO,如上图的 Trip.Order.DTO。
  • 2、请求参数 DTO 实体类、响应 DTO 实体类存放规范以及其命名规则:
    • a、请求参数 DTO 实体类放在 Request 文件夹下,且命名规则为:以 Request 结尾,如上图的 SearchOrderRequest.cs。
    • b、响应 DTO 实体类放在 Response 文件夹下,且命名规则为:以 Response 结尾,如上图的 SearchOrderResponse.cs。
    • c、如果请求参数 DTO 实体类或响应 DTO 实体类的属性中有对象或枚举,那么这些对象所属的类、枚举放在 DTO 项目的 Common 文件夹下。
  • 3、如果请求参数 DTO 实体类、响应 DTO 实体类有基类要继承,那么建议为基类取名为 RequestBase.cs、ResponseBase.cs。且这些基类直接放在 DTO 项目的 Common 文件夹下。

视图对象 VO 规范

规范说明:

  • 1、VO 项目命名规则:{产品线英文名全称}.{子系统英文名全称 + 应用名}.ViewModel,如上图的 Trip.Seller.ViewModel。
  • 2、各 VO 实体类,我们用 Controller 名作为文件夹名进行分开,如上图的 Order 文件夹。
  • 3、VO 实体类名的命名建议:
    • a、请求参数 VO 实体类以 Input/Form/Query 结尾,如上图的 SearchOrderInput.cs。
    • b、响应 VO 实体类以 Output/List/Result 结尾,如上图的 SearchOrderOutput.cs。

业务对象 BO 规范(可选)

BO 实体类名以 Model 为结尾:

规范说明:

  • 1、BO 项目命名规则:{产品线英文名全称}.{子系统英文名全称 + 应用名}.BO,如上图的 Trip.Order.BO;
  • 2、以 Model 结尾,如上图的 OrderModel.cs;
  • 3、为了简化设计,BO 项目为可选,可在 DO 项目里建文件夹。

数据对象 DO 规范(可选)

规范说明:

  • 1、DO 项目命名规则:{产品线英文名全称}.{子系统英文名全称 + 应用名}.Entity,如上图的 Trip.Seller.Entity;
  • 2、如果涉及到多个数据库访问的,那么需要按数据库名称(以 DB 为结尾)创建文件夹分开,如上图的 TripOrderDB 文件夹;
  • 3、表名 +Entity 结尾,如上图的 OrderEntity.cs;
  • 4、DO 是数据表对象,供单表 CURD 操作。对于多表查询请求对象和返回对象,可定义新对象或使用现有对象(DTO/BO)来完成。

3.5、数据库连接配置规范

规范说明:

  • 1、数据库连接的配置必须读写分离。
  • 2、数据库连接字符串建议加密处理。
  • 3、数据库连接配置名的命名规则:{以 DB 为结尾的数据库名称}_ 读写类型,如:TripOrderDB_SELECT、TripOrderDB_INSERT。

3.6、配置文件方面的规范

规范说明:

  • 1、所有配置文件(除 Web.config 文件外)都必须放到 Config 文件夹下。
  • 2、所有配置文件(除 Web.config 文件外)按不同环境区分开,具体命名规则是:{功能模块英文名}.{环境英文简称名}.config,其中本地环境的英文简称名是 Dev,测试环境的英文简称名是 Test,正式环境的英文简称名是 Prod,如上图的 AppSetting.Dev.config。
  • 3、保持 Web.config 配置文件的干净,只留环境设置节点。

3.7、静态资源文件方面的规范

规范说明:

  • 1、公共的静态资源文件(css、js、image 等)放在另外的静态站点中,统一由前端进行开发和维护。一般,css 文件放在 css 文件夹下,js 文件放在 js 文件夹下,image 图片文件放在 img 文件夹下。
  • 2、与某项业务有关的 js 文件可以放到各自业务项目的表现层 PresentationLayer 下,以方便开发人员调试,js 文件可放在项目的 js 文件夹下。
  • 3、静态资源文件必须使用版本号管理,以防更新后由于客户端浏览器缓存而导致站点使用的依然是旧版本的静态资源文件:

<script src="~/js/order.js?v=@AppSetting.StaticFileVersion"></script>

四、写在最后

4.1、问题回答

问:服务的调用代码应该放到哪一层呢?A 表现层、B 业务逻辑层 、C 数据层、D 公共层。

我们的规范是统一放到数据资源访问层即 C。上层提供服务,下层调用服务,中间处理业务逻辑。

问:如何组织好 VO(View Object 视图对象)、BO(Business Object 业务对象)、DO(Data Object 数据对象)、DTO(Data Transfer Object 数据传输对象) 呢?

通常有两种做法,限定访问范围和不限定访问范围,实际项目中可根据需要选择、折中或裁剪。我们使用后者,将 EntityLayer 作为通用对象放到左侧,具体可参考实体层规约:

“DO 是数据表对象,不是数据访问层对象,不是只能给数据访问层使用;DTO 是网络传输对象,不是表现层对象,不是只能给表现层使用;BO 是内存计算逻辑对象,不是业务逻辑层对象,不是只能给业务逻辑层使用 。如果仅限定在本层访问,则导致单个应用内大量没有价值的对象转换。以用户为中心来设计实体类,可以减少无价值重复对象和无用转换。”

问:应用分层范例代码的编写需要注意些什么?

应用分层范例的代码要想写好,非常不容易,很容易引起争议,很难让所有人满意。我们在具体实践时遵循以下几点:

应用分层范例的主要价值是明确层的职责和交互,每个层的职责是什么,哪些要干,哪些不要干,以及层与层之间依赖和交互;

私人定制:减少通用帮助类的编写,如果每一个应用中有大量相同的帮助类,这在架构层面上是有问题。在我们的几百个线上应用中,尽管减少通用的代码,包括分页帮助类、数据库帮助类、缓存帮助类、MQ 帮助类、日志帮助类、AOP 帮助类、线程帮助类。业务应用的重点是为业务服务,每一个应用都是特别的,都需要私人定制,极少有通用的代码,如果有,那么应该由框架或组件专门解决;

少即是多:应用的场景多,参考人员多,每个人想法不同,牵涉的时间长,所以尽量只做大家都认同的规范、正确的事情,要自底向上、要减少有争议的代码范例,否则一个错误将会放大百倍、一个有争议的规范将会很难推行。

追求简单:代码编写可分为三个层次,简单、复杂、简单。第一简单是不知道的简单,第二个复杂是知道后的复杂,第三个简单是知道后有取舍的简单。范例代码要追求简单,既可轻松扩展支持复杂场景,又要简单到初级程序员也能操作。

内聚大于解耦:内聚是什么,内聚是部门内有共同的目标,然后大家紧密合作。解耦是什么,解耦是部门间各自职责明确,然后减少不必要的连接。一个应用如同一个部门,应有一个共同的目标和职责,然后大家紧密合作。

换句话说,应用内部应减少不必要契约接口(如同公司间才签约合同),减少不必要的依赖注入实现,减少不必要且代价过大的解耦。一切以简单实用为主,以应用价值输出、应用的目标(接口或界面)为导向。

4.2、Demo 下载

LayerDemo 下载地址:https://github.com/das2017/LayerDemo

 

中小型研发团队架构实践:小工具合集

  • 张辉清
  • 杨丽

阅读数:47022018 年 1 月 1 日

一、ORM 工具

1.1、Dapper.NET 简介

Dapper.NET 是个开源的轻型 ORM。它扩展了 IDbConnection 接口的功能,所以只要某类实现 IDbConnection 接口,那么该类对象就能调用到 Dapper.NET 中的方法。提供的 Dapper.dll,支持.NET Framework 4.0 版本及其上版本。

1.2、为什么选择使用 Dapper.NET

  1. 语法十分简单,易学易用。
  2. 无须依赖于具体的数据库工具,它能和所有.NET ado 提供商一起工作,如:MS SQL Server、Oracle、MySQL、PostgreSQL、SQLite、SqlCe、Firebird 等。
  3. 运行速度十分快,接近于 IDataReader,因为它的映射工作原理是通过 Emit 反射 IDataReader 的序列队列,来快速地产生对象。如下两表显示的数据(数据由官网提供)体现了它的性能优势。

Performance of SELECT mapping over 500 iterations - POCO serialization:

Performance of SELECT mapping over 500 iterations - dynamic serialization:

1.3、如何使用 Dapper.NET

提供的 Demo 都是关于 Dapper.NET 的最最基本的用法。首先,在你需要用到 Dapper.NET 的项目中引用 Dapper.dll,请见下图。然后在需要使用 Dapper.NET 的代码文件中加上【using Dapper;】。

提供的 Demo 包括了如下主题:

  1. 单条记录的增、改、删
  2. 批量增、改、删
  3. Query() 泛型方法的使用
  4. Query() 非泛型方法的使用
  5. QueryMultiple() 方法的使用
  6. ExecuteScalar() 方法的使用
  7. 如何使用 Dapper.DynamicParameters 类。注意:当数据库表字段被设计为 char 类型时,必须给 DbType 传值,且必须赋的是 DbType.AnsiStringFixedLength,否则数据库访问速度会突然变得很慢。
  8. 如何调用存储过程

二、对象映射工具

2.1、为什么需要使用对象映射工具

比如,为了能够从数据库中获取数据,某一个基于 Windows Communication Service 的服务需要将数据库实体对象映射到数据协议对象上。对象—对象映射的一种传统做法就是创建许多数据转换对象。这些对象负责在众多数据对象之间复制数据。对于拥有大量数据对象的程序而言,开发人员需要花费大量的时间精力编写大量的数据转换对象来支持数据对象映射。这一过程非常无聊沉闷,而且容易出现 Bug。而如果你使用对象—对象映射工具,就不需要自己编写那些数据转换对象。

2.2、EmitMapper 和 AutoMapper 简介

EmitMapper 和 AutoMapper 都是支持对象—对象映射的开源工具,主要负责将一个数据对象的数据映射到另外一个数据对象上。提供的 EmitMapper.dll,支持.NET Framework 3.5 版本及其上版本;提供的 AutoMapper.dll,支持.NET Framework 4.5 版本及其上版本。

2.3、EmitMapper 的使用方法

首先,在需要使用 EmitMapper 的项目中引用 EmitMapper.dll。

基本的使用方法:采用默认的映射配置器 DefaultMapConfig 完成映射操作,不需要指定任何的映射策略。写法主要如下(完整写法请见 BasicUsageDemo.cs):

复制代码

 
 

ObjectsMapper<Source, Destination> mapper = ObjectMapperManager.DefaultInstance.GetMapper<Source, Destination>();

 

Destination destination = mapper.Map(source);

或者:

复制代码

 
 

Destination destination = new Destination();

 

ObjectMapperManager.DefaultInstance.GetMapper<Source, Destination>().Map(source, destination);

默认的映射配置器能自动转换以下几种类型:

  1. 使用 ToString() 方法转换任何类型到 string 类型。
  2. 使用 System.Convert 类可使原生类型之间互相转换。
  3. 可空类型转换为值类型或者值类型转换为可空类型。
  4. 枚举类型转换为它的基础类型或者基础类型转换为对应的枚举类型。
  5. 枚举类型转换为 string 类型或者 string 类型转换为枚举类型。
  6. 不同的集合类型之间互相转换(如:Array、ArrayList、List<>、IEnumerable)。
  7. 类转换为结构或者结构转换为类。
  8. 具有内嵌类型成员的复杂类型采用递归方式转换。

使用 DefaultMapConfig 的自定义配置方法:

如果默认的转换满足不了需求,那么可考虑调用 DefaultMapConfig 提供的配置方法。下表说明了各配置方法的作用:

2.4、AutoMapper 的使用方法

首先,在需要使用 AutoMapper 的项目中引用 AutoMapper.dll。

提供的 Demo 主要包括如下主题:

  • 最基本的用法(写了 3 种)
  • 扁平化映射
  • 前后映射
  • 空值替换
  • 忽略映射
  • 条件映射
  • 指定映射字段
  • 强类型对象映射动态对象
  • 动态对象映射动态对象
  • 自定义类型转换器
  • 自定义解析器

2.5、EmitMapper 和 AutoMapper 的优缺点

EmitMapper 和 AutoMapper 各有千秋:

EmitMapper 官网上虽然有多年的时间没有更新,但它的性能却十分高(接近硬编码)。下图显示的结果是通过笔者电脑运行出来的结果,发现 EmitMapper 的映射速度比 AutoMapper 的快很多(被比较的 AutoMapper 版本号是 5.1.1)。

AutoMapper 虽然性能比不过 EmitMapper,但官网上一直保持着更新状态。

三、IoC 工具

3.1、Autofac 简介

Autofac 是一款轻量级的开源 IoC 容器,它主要负责管理类之间的依赖关系、管理对象的生命周期等,降低应用程序组件间的耦合性,提高类、组件的扩展性、可重用性。

3.2、背景

在我们的软件系统中通常都是通过 N 多个对象(系统、模块、对象)的共同协作来最终实现我们的业务系统。N 多个对象的协作肯定会产生或多或少的耦合(依赖),降低对象之间的耦合是我们软件工程永远追求的目标之一。

3.3、依赖倒置原则

a. 上层模块不应该依赖于下层模块,它们应该共同依赖于一个抽象。b. 抽象不应该依赖于具体,具体依赖于抽象。

3.4、IoC

Inversion of Control:控制反转,反转的是对依赖对象的控制权。

如果 A 依赖 B 的话,按照之前的做法是在类 A 中需要 B 的地方主动实例化一个 B 对象。现在的做法是类 A 中需要一个 B 对象,IoC 容器初始化一个 B 对象传给类 A。创建依赖对象的职责从类 A 转移到了 IoC 容器里面。

3.5、依赖注入

可以用不同的方式实现 IoC,其中一种实现策略是依赖注入。那么依赖注入是什么?把耦合从代码中转移到配置文件中,通过一个 IoC 容器,在需要的时候再去形成这个依赖关系,即在程序中把需要的接口实现注入到需要它的类中。这就是依赖注入。

3.6、优点

  • a、可维护性好:在通过 IoC 容器创建组件之间的依赖关系之前,这些组件之间是毫不相关的,分别都是独立的单元,便于各自调试和单元测试。
  • b、分工明确、提高开发效率:各个组件都是独立的单元,可以由不同的开发团队来开发和维护,大大提高开发效率。
  • c、可重用性高:常用的模块都是一个单独的个体,实现了标准的接口,可以插接到任何支持此标准的模块中。

四、DLL 包管理工具

4.1、NuGet 简介

NuGet 是 Visual Studio 的一个扩展。在使用 Visual Studio 开发基于.NET Framework 的应用时,NuGet 能把在项目中添加、移除和更新引用的工作变得更加快捷方便。

4.2、为什么要用 NuGet

  1. 由于公司内部的公共组件越来越多,为了统一方便管理这些公共组件,所以需要搭建公司内部的 NuGet 服务器。
  2. DLL 不用上传到 SVN 上,以免造成过多的 DLL 文件被传到 SVN 上,减轻 SVN 压力。
  3. 方便了包的依赖管理。
  4. 会及时知道 DLL 是否有更新。
  5. NuGet 可以自动还原项目引用的包。

4.3、使用方法

1、设置 NuGet 服务器

右键需要添加引用的项目文件 -> 管理 NuGet 程序包,便打开了如下图所示的弹出框,然后点击【设置】按钮:

添加程序包源,即添加公司内部的 NuGet 服务器名和其地址【http://nuget.***.***/nuget】:

2、添加组件引用

在下图左侧的联机列表中,选中在上步设置的 NuGet 服务器名【***NuGet】,然后在下图中间的列表中选中要添加的引用的组件名,再通过点【安装】按钮把相应的组件引用添加到项目中:

3、更新组件引用

在下图左侧的更新列表中,选中在前面步骤中设置的 NuGet 服务器名【***NuGet】,然后在下图中间的列表中选中要重新添加引用的组件名,再通过点【更新】按钮把相应的组件引用重新添加到项目中:

4、包管理

管理包时需要用到 NuGetPackageExplorer,下载地址:位于本文的【下载资源】处。

新建包:

a、打开 NuGet Package Explorer,单击【创建一个新的组件包】:

b、将需要打包的组件引用拖放到【Package contents】区域:

c、单击位于界面左上角的【Edit Metadata】按钮后,进入如下图所示的编辑界面。 

在【Package metadata】区域中编辑好组件的相应信息,然后点绿色的勾,然后点击 FILE->Save 保存;其中,包名(即包 Id 号)的命名规范建议是:{产品线英文名全称}.{AppID}.{***}:

d、组件发布

点击 FILE->Publish 后,在弹出如下图的所示框中,发布地址输入:http://nuget.***.***/,在 Publish Key 文本框处输入密码:

更新包:

a、打开 NuGet Package Explorer,单击从【从在线源中打开一个包】:

b、在 Package source 文本框处默认显示了【http://nuget.***.***/nuget】,即公司内部的 NuGet 服务器地址,再单击【Reload】按钮;然后,在出现的包列表中选中将要编辑的包,然后双击它或者单击【open】按钮:

c、单击位于界面左上角的【Edit Metadata】按钮后,进入如下图所示的编辑界面。

在编辑界面的【Package metadata】区域中,在【Version】文本框中增大版本号。然后,在编辑界面的【Package contents】区域中,右键需要更新的引用,然后在弹出的快捷菜单中单击【Replace with...】来完成重新上传最新的包的操作:

d、编辑完成之后,单击位于上图左上角的绿色勾,然后单击 FILE->Publish 进行发布工作。

五、资源下载

六、更多资料

 

中小型研发团队架构实践:高效率、低风险,一键发布并测试的持续集成工具 Jenkins

  • 张辉清
  • 杨丽

阅读数:48282018 年 1 月 4 日

一、Jenkins 简介

当每月发布次数变得越来越多时,如超过 200 次,发布工作人员的工作量会翻倍,此时由人工发布操作失误引起的风险会变得越来越大。为了提高项目的发布效率,也为了降低由人工操作失误带来的风险,需要引进持续集成工具。

Jenkins 是一个用 Java 语言编写的开源持续集成工具,最开始被称作 Hudson。Jenkins 在持续集成领域市场份额中居于主导地位,被各种大小规模的团队用于用各种语言实现的各类项目中,语言包括.NET、Java、Ruby、Groovy、Grails、PHP 等。选择 Jenkins 的理由如下:

  • 易于使用:Jenkins 的用户界面简单、直观、友好,发布工作人员只需要通过简单的 UI 操作就可以替代原来繁琐的发布工作。
  • 拥有良好的扩展性:提供数以百计的开源插件可供使用,而且几乎每周会有新的开源插件贡献进来,这些插件的安装都十分快捷和简单。
  • 发展良好:Jenkins 开源社区的规模变得越来越大、活跃度也变得越来越高,发展速度非常快。

二、Jenkins 插件及相关工具

1、Jenkins:持续集成工具。

2、Git:源代码管理工具,是目前流行的分布式版本控制系统。需要安装的 Jenkins 插件有:

3、TFS:可选,源代码管理工具。

4、MSBuild:Visual Studio 中自带的一个程序编译组件。需要安装的 Jenkins 插件是 MSBuild Plugin 插件。

5、FTP:可选,通过 FTP 把编译好的发布文件部署到应用服务器中。需要安装的 Jenkins 插件是 Publish Over FTP 插件。

6、Jenkins 角色及权限管理:需要安装的 Jenkins 插件是 Role-based Authorization Strategy 插件。

7、Python 脚本:自写的 Python 脚本放在 Jenkins 服务器中。可以实现 Jenkins 把编译好的发布文件部署到远程应用站点服务器,以及实现回滚操作 Rollback。

8、PxExec.exe 工具:装在 Jenkins 服务器中,利用这个工具,可以在远程服务器中执行命令如 xcopy。

9、SoapUI 自动化测试:用于接口测试自动化,同时需要安装的 Jenkins 插件是 HTML Publisher plugin 插件。

10、回滚操作 Rollback:需要安装的 Jenkins 插件是 Build with Parameters,用于指定哪个项目回滚到哪个备份版本。

三、Jenkins 关键配置

3.1、邮件配置

Email 是 Jenkins 最基本的通知技术。什么情况下,需要 Jenkins 发送电子邮件通知?例如,在一个构建失败(例如因为编译错误)后。

3.2、角色及权限管理

首先设置全局角色和项目角色,其中 Pattern 是用来设置构建作业名的命名规范,例如:规定了构建作业名的命名规范是{发布环境}.{产品线英文名全称}.{项目名},那么要发到生产环境、属于 Trip 产品线的所有构建作业,其 Pattern 设置的值为【(?i)prod.trip.*】,表示构建作业名必须以 prod.trip 开头,而且不区分大小写,用于发布到生产环境。

然后,分别为 Jenkins 账号分派全局角色和项目角色:

3.3、部署到集群

Jenkins 通过运行自写的 Python 脚本把编译好的发布文件部署到远程应用站点服务器中,以及同步到集群内其他应用站点服务器,所以需要新增构建步骤配置,参考如下:

其中,.py 脚本(即 Python 脚本)内容如下:

.py 脚本实现了如下逻辑

第 1 步、备份

在远程应用站点服务器中,备份将要部署新版本的那个应用系统的所有文件。利用 PxExec.exe 工具,让 Jenkins 服务器远程连到应用站点服务器。然后在这台应用站点服务器中,利用 xcopy 命令,把将要部署新版本的这个应用系统站点目录之下的所有文件拷贝到这台应用站点服务器中的备份目录下。

第 2 步、部署

部署到这台远程应用站点服务器。先利用 xcopy 命令,把由 Jenkins 编译好的、位于 Jenkins 服务器的文件拷贝到这台应用站点服务器共享目录之下,以.config 结尾的配置文件不会被拷贝。再利用 PxExec.exe 工具,让 Jenkins 服务器远程连到这台应用站点服务器。然后在这台应用站点服务器中,利用 xcopy 命令,把临时存放目录下的文件拷贝到这个应用系统站点目录之下。

第 3 步、同步

同步发布文件到该应用系统集群内的其他应用站点服务器。利用 PxExec.exe 工具,让 Jenkins 服务器远程连到这台应用站点服务器,然后在这台应用站点服务器中,利用 xcopy 命令,把该应用系统站点目录下的所有文件拷贝到集群内的其他应用站点服务器的该应用系统站点目录之下。

3.4、SoapUI 接口自动化测试

测试用例提交到版本库(如 Git)后,通过 Jenkins 把它编译,编译后,通过 SoapUI 一键调用,开始自动化测试。一旦自动化测试完成,会生成报表,通过 HTML Report 把它给呈现出来,如下图所示。

3.5、UFT 界面自动化测试

3.6、回滚操作 Rollback

General 配置:

构建配置:

选择将要回滚哪个项目以及回滚到哪个备份版本号:

3.7、暂未解决的问题

  • 1、数据库发布与回滚。
  • 2、应用配置文件的发布与回滚。
  • 3、加入 QA 流程控制,经过测试工程师确定后,方可发布。

以上三个问题也可以借助其它工具来实现,分别是数据库发布工具、集中式配置服务、流程管理工具甚至邮件确认。

四、总结,Jenkins 使用价值

  • 减少发布工作人员的大量日常工作量,大大提高项目的发布效率。
  • 不容易出错,降低人工发布带来的风险。
  • 可 24 小时随时发布。
  • 方便紧急修复或回滚操作 Rollback。
  • 方便对发布流程进行控制、标准化。
  • 方便发布统计、历史版本可追溯。

五、更多资料

 

中小型研发团队架构实践三要点

  • 张辉清

阅读数:52472017 年 11 月 8 日

如果你正好处在中小型研发团队……

中小型研发团队很多,而社区在中小型研发团队架构实践方面的探讨却很少。中小型研发团队特别是 50 至 200 人的研发团队,在早期的业务探索阶段,更多关注业务逻辑,快速迭代以验证商业模式,很少去关注技术架构。

这时如果继续按照原有的架构及研发模式,会出现大量的问题,再也无法玩下去了。能不能有一套可直接落地、基于开源、成本低,可快速搭建的中间件及架构升级方案呢?

我是一个有十多年经验的 IT 老兵,曾主导了两家公司的技术架构升级改造,现抛砖引玉,与大家一起探讨这方面的问题。

在接下来的一段时间里,我会陆续推出此系列文章。

根据我们以往的经验,分享者主讲一个小时左右,业务研发就可以快速地进入项目实战。对于后面新加入的团队成员,也可通过 WIKI 自主快速学习。这是我们之前对自己的要求,尽量降低工具对人员的要求,简单实用、降低成本。

文章中部分 Demo 采用 C# 语言, 但到了框架或架构层面,与语言本身没有太多直接的关系。如 RabbitMQ、Job、Redis 和集中式日志,它们服务端的部署是一样的,只是客户端语言版本稍有不同。

所有 Demo 都可直接运行,服务地址及管理后台也可直接访问。因为部署在公有云,牵涉到成本费用的问题,我计划持续到明年 3 月底。

这些小小的基础工作,希望能够帮到中小型研发团队,解决大家项目中遇到的实际问题。愿与你一起成长,你的分享和点赞是我此次付出的动力,谢谢!

整个系列文章分为三个部分,包括 框架篇架构篇 和 公共应用篇

  • 框架篇 即中间件或工具的使用,如缓存、消息队列、集中式日志、度量、微服务框架等,工欲善其事,必先利其器。
  • 架构篇 主要是设计思想的提升,有企业总体架构、单个项目架构设计、统一应用分层等。
  • 公共应用篇 是业务与技术的结合,有单点登录和企业支付网关。

以下是篇章的具体介绍:

框架篇——工欲善其事,必先利其器

如果说运维是地基,那么框架就是承重墙。农村建住房是一块砖一块砖地往上垒,而城市建大 House 则是先打地基,再建承重墙,最后才是垒砖,所以中间件的搭建和引进是建设高可用、高性能、易扩展可伸缩的大中型系统的前提。

框架篇中的每篇主要由四部分组成:它是什么工作原理使用场景 和 可直接调试的 Demo。其中 Demo 及中间件历经两家公司四年时间的考验,涉及几百个应用,100 多个库 1 万多张表,日订单从几万张到十几万,年 GMV 从几十亿到几百亿。

所有中间件及工具都是基于开源,早期我们也有部分自主研发如集中式日志和度量框架。后期在第二家公司时为了快速地搭建,降低成本,易于维护和扩展,全部改为开源。这样不仅利于个人的学习成长、知识重用和职业生涯,也利于团队的组建和人才的引进。

集中式缓存 Redis

缓存是计算机的难题之一,分布式缓存亦是如此。Redis 看起来非常简单,但它影响着系统的效率、性能、数据一致性。

用好它不容易,涉及到的问题包括:缓存时长(复杂多维度的计算)、缓存失效处理(主动更新)、缓存键(Hash 和方便人工干预)、缓存内容及数据结构的选择、缓存雪崩的处理、缓存穿透的处理等。

Redis 除了缓存的功能,还有其它功能如 Lua 计算能力、Limit 与 Session 时间窗口、分布式锁等。

消息队列 RabbitMQ

消息队列好比葛洲坝,有大量数据的堆积能力,然后再可靠地进行异步输出。它是 EDA 事件驱动架构的核心,也是 CQRS 同步数据的关键。为什么选择 RabbitMQ 而没有选择 Kafka,因为业务系统有对消息的高可靠性要求,以及对复杂功能如消息确认 Ack 的要求。

集中式日志 ELK

日志主要分为系统日志应用日志两类。试想一下,你该如何在一个具有几百台服务器的集群中定位到问题?如何追踪每天产生的几 G 甚至几 T 的数据?集中式日志就是此类问题的解决方案。

早期我们使用自主研发的 Log4Net+MongoDB 来收集和检索日志信息,但随着数据量的增加,查询速度却变得越来越慢。后期改为开源的 ELK,虽然易用性有所下降,但它支持海量数据以及与编程语言无关的特征。下面是 ELK 的架构图。

任务调度 Job

任务调度 Job 如同数据库作业或 Windows 计划任务,是分布式系统中异步和批处理的关键。我们的 Job 分为 WinJob 和 HttpJob:WinJob 是操作系统级别的定时任务,使用开源的框架 Quartz.NET 实现;而 HttpJob 则是自主研发实现,采用 URL 方式可定时调用微服务。

HttpJob 借助集群巧妙地解决了 WinJob 的单点和发布问题,并集中管理所有的调度规则,调度规则有简单规则和 Cron 表达式。HttpJob 它简单易用,但间隔时间不能低于 1 分钟,毕竟通过 URL 方式来调度并不高效。下图是 HttpJob 的管理后台。

应用监控 Metrics

“没有度量就没有提升”,度量是改进优化的基础,是做好一个系统的前置条件。Zabbix 一般用于系统级别的监控,Metrics 则用于业务应用级别的监控。

业务应用是个黑盒子,通过数据埋点来收集应用的实时状态,然后展示在大屏或看板上。它是报警系统和数字化管理的基础,还可以结合集中式日志来快速定位和查找问题。我们的业务监控系统使用 Metrics.NET+InfluxDB+Grafana

微服务框架 MSA

微服务是细粒度业务行为的重用,需要与业务能力及业务阶段相匹配。微服务框架是实现微服务及分布式架构的关键组件,我们的微服务框架是基于开源 ServiceStack 来实现。

它简单易用、性能好,文档自动生成、方便调试测试,调试工具 Swagger UI、自动化接口测试工具 SoapUI。微服务的接口开放采用我们自主研发的微服务网关,通过治理后台简单的配置即可。网关以 NIO、IOCP 的方式实现高并发,主要功能有鉴权、超时、限流、熔断、监控等,下图是 Swagger UI 调试工具。

搜索利器 Solr

分库分表后的关联查询,大段文本的模糊查询,这些要如何实现呢?显然传统的数据库没有很好的解决办法,这时可以借助专业的检索工具。

全文检索工具 Solr 不仅简单易用性能好,而且支持海量数据高并发,只需实现系统两边数据的准实时或定时同步即可。下图是 Solr 的工作原理。

更多工具

  • 分布式协调器 ZooKeeper

    ZK 工作原理、配置中心、Master 选举、Demo,一篇足以。
  • ORM 框架

    Dapper.NET 语法简单、运行速度快,与数据库无关,SQL 自主编写可控,是一款适合于互联网系统的数据库访问工具。
  • 对象映射工具 EmitMapper 和 AutoMapper

    EmitMapper 性能较高,AutoMapper 易用性较好。
  • IoC 框架

    控制反转 IoC 轻量级框架 Autofac。
  • DLL 包管理

    公司内部 DLL 包管理工具 NuGet,可解决 DLL 集中存储、更新、引用、依赖问题。
  • 发布工具 Jenkins

    一键编译、发布、自动化测试、一键回滚,高效便捷故障低。

架构篇——思想提升

会使用以上框架并不一定能成为优秀的架构师,但一位优秀架构师一定会使用框架。架构师除了会使用工具外,还需要设计思想的提升和性能调优技能。

此篇以真实项目为背景,思想方法追求简单有效,主要内容包括 企业总体架构单个项目架构设计统一应用分层调试工具 WinDbg

企业总体架构

当我们有了几百个上千个应用后,不仅仅需要单个项目的架构设计,还需要企业总体架构做顶层思考和指导。大公司与小商贩的商业思维是一样的,但大公司比较难看到商业全貌和本质。而小公司又缺乏客户流量和中间件的应用场景,中型公司则兼而有之,所以企业总体架构也相对好落地。

企业总体架构需要在 技术业务管理 之间游刃有余地切换,它包括业务架构、应用架构、数据架构和技术架构。附档是一份脱敏感信息后的真实案例,有参考 TOGAF 标准。但内容以解决公司系统的架构问题为导向、以时间为主线,包括企业商务模型、架构现状、架构规划和架构实施。

单个项目架构设计

单个项目的架构设计如同施工图纸,能直接指导工程代码的实施。上一环是功能需求,下一环是代码实施,这是架构设计的价值所在。从功能需求到用例,到用例活动图,到领域图、架构分层,到核心代码,它们之间环环相扣。

做不好领域图可能源自没有做好用例活动图,因为用例活动图是领域图的上一环。关注职责、边界、应用关系、存储、部署是架构设计的核心,下图是具体案例参考。

统一应用分层

给应用分层这件事情很简单,但是让一家公司的几百个应用采用统一的分层结构,这可不是件简单的事情。它要做到可大可小、简单易用、支持多种场景,我们使用 IPO 方式:I 表示 Input、O 表示 Output、P 表示 Process,一进一出一处理。应用系统的本质就是机器,是处理设备,也是一进一出一处理,IPO 方式相对于 DDD 而言更为简单实用。

调试工具 WinDbg

生产环境偶尔会出现一些异常问题,而 WinDbg 或 GDB 就是解决此类问题的利器。调试工具 WinDbg 如同医生的听诊器,是系统生病时做问题诊断的逆向分析工具,Dump 文件类似于飞机的黑匣子,记录着生产环境程序运行的状态。

主要介绍调试工具 WinDbg 和抓包工具 ProcDump 的使用,并分享一个真实的案例。N 年前不知谁写的代码,导致每一两个月偶尔出现 CPU 飙高的现象。

我们先使用 ProcDump 在生产环境中抓取异常进程的 Dump 文件,然后在不了解代码的情况下通过 WinDbg 命令进行分析,最终定位到有问题的那行代码。

公共应用篇

先工具再框架,然后架构设计,最后深入公共应用。公共应用因为与业务系统结合紧密,但又具有一定的独立性,所以一般自主开发,不使用开源也不方便开源。公共应用主要包括单点登录、企业支付网关、CTI 通讯网关(短信邮件微信),此次分享单点登录和企业支付网关。

单点登录

应用拆分后总要合在一起,拆分是应用实施层面的拆分,合成是用户层面的合成,而合成必须解决认证和导航问题。单点登录 SSO 即只需要登录一次,便可到处访问,它是建立在用户系统、权限系统、认证系统和企业门户的基础上。我们的凭证数据 Token 使用 JWT 标准,以解决不同语言、不同客户端、跨 WebAPI 的安全问题。

企业支付网关

企业支付网关集中和封装了公司的各大支付,例如支付宝、财付通、微信、预付款等。它统一了业务系统调用各支付接口的方式,简化了业务系统与支付系统的交互。

它将各种支付接口统一为支付、代扣、分润、退款、退分润、补差、转账、冻结、解冻、预付款等,调用时只需选择支付类型即可。企业支付网关将各大支付系统进行集中的设计、研发、部署、监控、维护,提供统一的加解密、序列化、日志记录,安全隔离。

 

中小型研发团队架构实践:电商如何做企业总体架构?

  • 张辉清

阅读数:58322017 年 12 月 26 日

企业总体架构是什么?有什么用?具体怎么做?以我曾任职的公司为案例,一起来探讨这个问题。

这家公司当时有 200 位研发人员和 200 多台服务器,我刚进这家公司时,系统已经玩不下去了,总是出现各种问题,例如日常发布系统时或访问量稍微过大时,系统就会出现很多故障,而且找不到故障发生的根本原因。

我进公司后主要的任务就是对这个系统进行升级改造,花了一个半月的时间写了份企业总体架构文档,文档共有 124 页,直接指导了之后的技术改造,下图是那份文档的目录,文末有相关资料下载地址。

企业商务模型

企业商务模型的内容主要包括主营业务、商务模式、商务主体、竞品分析、组织架构、商务运作模型和业务流程

主营业务即公司做什么业务。

商业模式即公司怎么赚钱。

商务主体即哪几个人在一起做这门生意。

竞品分析即了解竞争对手的情况。

组织架构即公司部门是怎么划分的,组织架构图中标出人数,根据系统与业务之间对应关系,可以了解系统中哪些模块使用频率高,以及业务与其对应模块的复杂度。

商务运作模型即公司是如何运作的,售前做计划,找供应商把东西买进来后,经过服务和结算,再卖给我们的经销商和采购商,使我们获得利润,售后进行大数据分析最后又指导着我们的售前,整个过程形成良性循环。

可以把一家公司想象成一台机器,输进去的是钱,转一转后,又能够生出更多的钱出来。

最后是业务流程和附档资料,业务流程包括预订流程、订单处理流程、产品供应流程、财务结算流程、账户管理流程。企业商务模型的建立,指导着整个应用系统模型的建立,它是整个应用系统建设的基础和前提,毕竟应用系统是为业务服务的。

架构现状

架构现状的内容主要包括:功能架构、应用架构、数据设计物理架构。

功能架构

功能架构主要包括功能、角色权限三部分。功能是企业服务,用户使用的每一个功能,就是企业的每一个服务。角色是用户操作的归类,功能与角色的对应关系即权限。了解系统架构的现状,从功能架构开始。

应用架构

应用就是处理器,应用架构的内容包括现有架构图、Web 应用现状、作业小应用(Job)现状接口架构。其中,接口是应用层面的关键,它是一个程序与另外一个程序交互的部分。

应用架构图表列出了哪些业务逻辑没有被重用,换句话说业务逻辑被多少个应用调用,就需要被重复开发多少次,一旦改了一个地方,就要同时改多个地方,导致系统开发效率非常低下。

各业务逻辑如预订逻辑,虽然被多个应用调用,但它们与应用是没有关系的,业务逻辑可以独立的存在,也可以寄宿于多个应用。业务逻辑是一个业务操作的抽象,而业务应用与业务部门共同完成了业务操作。

数据设计

100 多个数据库,一万多张表,能否使用一张 E-R 图来表示呢?它是可以的。

数据设计依赖于企业的数据,而不是数据库的设计,对企业数据适当做归类,会直接导致数据设计,最终画出 E-R 图,数据设计完成后,数据库设计就自然而然出来了。

超越库、超越表去看这张 E-R 图,可以看出它包括产品、订单、结算、用户基础设施这五类数据。低层的 E-R 图可以变,但是高层的 E-R 图一般不会变化,因为它是根据你的业务模型而定,业务模型稳定,高层 E-R 图也是稳定的。

数据库只要早期设计得好,是可以做到易伸缩、易拆分的。下图从内往外看,一个框既可以是一个库,也可以是一个模块,还可以是一个表。

在业务发展的早期它可以是一个库,里面有 5 个模块,中期可以分为 5 个库,后期以更低级别可以分为更多的库,这与业务阶段及系统复杂度相关。在数据的设计完成后,数据库的设计也就很容易规划和调整。

以上是数据库、数据表之间的静态关系,接下来我们介绍数据的流转状态即状态图。通过数据状态图去了解现有数据流转变迁,如国内订单状态变迁图,这种图的价值不仅在于数据库层,还在于服务化。

图中的从等待支付到支付成功,中间有个支付行为,通过这个支付行为把数据状态变更为支付成功,否则继续等待,直到超时关闭订单。这个支付行为可以做成一个微服务,然后由不同的应用去调用。

物理架构

物理架构的内容主要包括 IDC 机房、机房之间访问关系、机房内服务器物理部署图、机房与业务分布、网站架构、数据库架构、集群清单域名清单。

将这些内容以列表和图形方式整理出来,就会很容易了解和发现问题,只有发现问题才能解决问题,特别是在全局体系架构方面,这也是表和图的价值所在。

当时这家公司共有 5 个地区、8 个机房,虽然只有 200 多台服务器,但分布很散,导致物理结构复杂,通讯也很复杂。技改前故障不断,其主要的一个原因就是物理架构不合理,运维要占 60%、70% 的责任,当时却把责任归咎为应用架构,这是个错误的方向。

物理架构的不合理,应用架构是很难合理的,因为物理架构是我们的基础设施,位于最底层,下层为上层服务,运维要为应用服务,应用要为业务服务,业务要为客人服务。

领域模型

领域模型关注概念,关注职责、关注边界、关注交互,只有先确定职责和边界,交互才会很清晰。领域模型是针对现有问题域提出一个系统解决方案,然后在图表上建立完整的模型,如同用 AutoCAD 画的施工图纸一样。

领域模型属于概要设计阶段,对于单个应用架构设计,首先需要了解业务和功能需求、用例图、用例活动图,然后才是领域模型。业务流程图是对业务操作的抽象,领域图是对业务逻辑代码的抽象。

建立领域词汇是建立领域模型的第一步,它能统一词汇明确概念,以减少一词多义、一义多词的情况。概念一旦确定,再扩展属性和行为,然后把它当作一个单元与其它事物构建在一起,就会很容易形成模型,领域模型与企业商务模型中的业务流程图有参考对应关系。

领域模型在实现时可大可小,在业务的早期,在系统比较小的情况下,它有可能是一个类。当系统做大了以后,它可能是个 DLL 库。再做更大一点的时候,它可能是一个服务,给不同的应用去调用。

每一个方法都有成为服务的潜质,特别是在系统中后期。领域模型是业务逻辑代码的施工图纸,它不仅有利于对现在系统业务逻辑的了解,同时也指导未来的架构改造。

架构规划

当我们了解了业务、了解了架构的现状,发现现有架构的问题,接下来就可以做中远期架构规划,以及架构的调整和具体实施。架构规划内容包括:顶层架构规划、网站功能规划、应用规划、SOA 规划、分层架构规划、数据库规划物理规划等。

顶层架构规划

上图是顶层架构的俯视图和侧视图。第一张图是俯视图,坐在飞机上看,整个顶层架构最外层的是功能,中间的是业务操作,内层的是数据。

功能对应业务系统的用户界面,操作对应业务系统里的服务,数据对应业务系统的数据存储如数据库。

第二张图是剖面图,切一刀来看,上层是应用,中层是服务和框架,下层是基础设施数据中心。从图中的服务层可以看出,服务的归类跟业务流程的归类有很大关系。

网站功能规划

网站功能规划就是功能的重新划分,对照着架构现状,未来的功能应该如何调整?如案例中的国内网站功能规划,分别画出了全局功能图、采购商功能图、平台商功能图和供应商功能图。

其实在做网站功能规划的时候,更多需要考虑现状,而不是未来调整的部分,如果没有很大问题,则不做调整,尊重历史。因为有些东西(如名称)用户已经使用很久了,调整往往比较难,合理大于准确。

应用规划

系统是什么?系统 = 元素 + 关系。应用架构是什么?应用架构 = 应用 + 架构。应用就是系统的最小单元,应用分类和应用编号则构成了应用关系即应用的架构。

如上图中的案例,应用分类新建了框架 FX 和公共业务系统 CBS,在原有的 200 多个应用中并没有这两个产品线,而是分布在了不同的业务线中,从而导致重复建设。

应用编号是给每个应用分配一个六位的数字 ID,就如同我们的身份证一样,头两位表示产品线,中间两位表示子系统,最后两位表示应用,如 100206。应用编号是应用管理、依赖和追踪的基础,集中式日志和监控框架都有使用到应用编号。

SOA 规划

SOA 规划就是接口规划,它的归类与商务模型中的业务流程有参考对应关系。上图案例有五个服务中心:预订服务、订单处理服务、产品供应服务、财务结算服务公共服务

每个服务只需要实现一套自己的逻辑,我们的前台、后台、接口、作业小应用等都可以调用,服务的逻辑跟我们的业务逻辑是一致的,修改代码的时候只需要改一个地方就可以影响到所有调用到这服务的前端应用。

分层架构

分层架构看似很简单,但保证整个研发中心都使用统一的分层架构就不容易了。那么要如何去做,以达到提高编写代码效率、保证工程统一性的目的呢?

先简单介绍下当前两种比较流行的分层架构体系,一种是领域架构:仓储层(Repository Layer)、领域层(Domain Layer)、应用服务层(Application Layer)、表现层(Presentation Layer)和基础公共层(Infrastructure Layer),见下图。

另一种是相对传统地分为三层:数据层(Data Layer)、应用逻辑层(Business Layer)和表现层(Presentation Layer),见下图。

领域架构和三层架构之间有什么区别?我们是这样认为的,在早期我们做三层架构的时候,大都以表来做驱动的,在做领域架构的时候,大都以业务逻辑来驱动的,两者的区别确实比较明显。

但到了现在,如果都以业务逻辑为中心的话,实际上两者并没有本质区别。当时,我所在公司采用了第二种分层法,我们希望把分层做得极简,也就是说哪怕刚毕业进来的员工,在分层时基本上也不会乱。

而相对第一种分层法,第二种分层法简单很多。每一个应用的代码量都不应该很大,一旦工程变得过大,我们就会把它适当拆分,而不是全部放在一个单块应用里。

总之,我认为分层越简单,整个软件结构就越清晰,代码就越容易统一。把工程做得极简,才有利于复制,有利于业务的快速构建,有利于规模化、稳定可靠。

数据库规划

数据库是整个信息系统中生命周期最长、最难修改的部分,所以要加强规划。数据库的设计至少要提前两步,具体根据高层 E-R 图和数据设计来新建数据库,早建要比晚建好。

数据库调整的代价大、周期长,长时间产生的问题,需要长时间来解决,先在新库里解决新表,再根据当前业务和应用的需求,逐步调整旧表。

物理规划

物理架构的规划内容包括集群规划域名规划。首先是集群规划。

20 倍规划、5 倍设计和 1.5 倍实施:规划和设计要大一些,但实施时小一些,这样不仅便于将来的扩展,也节省了当前的费用。

两个逻辑网络:一个内网和一个外网,两个负载均衡,两个防火墙,安全隔离内外网。

四条产品线:国际、国内、新业务以及公共业务,单点登录和企业支付网关等公共业务也属于一条产品线。

六个集群:Web 集群、SOA 集群、中间件集群、数据库集群、Job 集群和 ITD 集群。

以上横向集群与纵向产品线形成了一个矩阵结构,也基本确定了网络基础架构。对于域名规划。对内的域名该改的改,该停用的停用,该合并的合并。对外的域名要尽量少改,要改的话也要有历史继承性(如跳转),要尽量减小对用户的影响。

其它

除以上架构规划外,还有一些其它重要项,如源代码管理规划、文档管理规划、技术选型团队分工。为什么还要做这些呢?因为统一了源代码怎么放、每个部门的文档怎么放、将来要用什么工具版本,才利于团队的协作,基于统一的环境才能有更高层次地提升。

对于团队分工,需要逐步对齐组织架构与系统的架构规划。对于技术选型,需要注意中间件的引进,要有节奏性,力量要相对集中,要小规模试点,找非核心项目,试用成功后再进行大规模推广。

架构实施

做完架构规划后,就是架构实施落地了。我们的架构实施整体思路是:树目标、给地图、立榜样、抓重点、造文化、建制度、整环境、组建架构部。

架构部内招几名老程序员,外招几个架构师。内部走出去,提高眼界。外部牛人请进来,落地了解历史和业务。技术建议是:SOA 服务化、基础设施平台化、公共业务服务化、加强项目概要设计。

当研发团队达到 200 多人、有了几百个应用,且在故障不断的情况下,不能与以前一样没有设计就开始编码,而是做加强项目概要设计及评审。后面的补与前面的防,两手都要抓,两手都要硬。

具体计划是:Roadmap 分步实施,改造一期、改造二期、改造三期,近细远粗、实事求是、逐步细化、逐步完善。不断立技术改造项目,不断将技改与业务研发项目相结合,技改即是工单、工单即是技改。避免对业务过多地影响,并不断有业务价值输出,这是架构改造得以持续实施的关键!

以上简单地介绍了总体架构的编写方法,我们的编写思路是先了解业务,建立企业商务模型,主要包括静态的商务主体、组织架构和动态的商务运作模型和业务流程。

接着了解架构现状,建立现有信息系统模型,主要包括功能架构、应用架构、数据设计和物理架构。一个是商务,一个是电子,两者即是整个公司的电子商务系统。

然后在企业商务模型和现有系统模型之上建立领域模型,领域模型它相对稳定,直接指导着接下来的架构规划,最后一定要落地即架构实施

附档是去掉敏感信息后的真实案例,它的价值如下:

  • Big Picture,全局蓝图,起到方向性和指导性。
  • 将隐性知识显性化,方便传达、广而告之。
  • 对于新员工的价值,快速入门。
  • 对于老员工的价值,了解全局,过程梳理,然后专注于自己的部分。

关于企业总体架构,你可以参考标准 TOGAF(开放组体系结构框架)。其实,我们是在完成那份文档后才知道 TOGAF,它们之间有很多相似之处和不同之处。

TOGAF 的内容主要包括业务架构、应用架构、数据架构和技术架构,而我们当时只是以解决公司系统架构问题为导向、以时间为主线,内容有企业商务模型、架构现状、领域模型、架构规划和架构实施。

方法论很重要,但看到事物本身的特点,深入问题以及找到解决办法更为重要。欢迎点赞和拍砖!

案例参考:

https://github.com/das2017/TopArchDemo

 

中小型研发团队架构实践:微服务架构

  • 张辉清
  • 杨丽

阅读数:65442017 年 12 月 14 日

一、MSA 简介

1.1、MSA 是什么

微服务架构 MSA 是 Microservice Architect 的简称,它是一种架构模式,它提倡将单一应用程序划分成一组小的服务,服务之间互相通讯、互相配合,为用户提供最终价值。它与 SOA 之间的区别如下:

1.2、我们的 MSA 框架

我们的微服务框架 MsaFx.dll 是个基于 ServiceStack 4.0.60 包装实现的.NET Web Services 框架,而 ServiceStack 本身支持通用的轻量级协议和 Metadata。MsaFx 与普通 Web Services 框架如 WCF 相比,主要优势如下:

  1. 高性能:性能好、速度快。
  2. 支持跨平台运行:基于 MsaFx 开发出的 Web Services 既能够运行在 Windows 环境中,又能够运行在支持 Mono 的 Linux 环境中。
  3. 支持多协议:如 JSON 格式的也支持 XSD。
  4. 更加 Web 化:RESTful。
  5. 服务端实现与客户端实现的完全解耦:MSA 基于消息的设计,使得服务端的 API 改变并不会破坏现有的客户端,达到服务端实现与客户端实现完全解耦的目的。
  6. MSA API 可视化说明文档便于你调试
  7. 易学:使用 MSA 进行开发和维护服务所需的技术和时间投入要小很多。
  8. 易用:简化了 REST 以及 WCF SOAP 风格的 Web Services 的开发过程。

1.3、MSA 框架实现架构

MSA 服务端的架构请见下图的第一张图,MSA 的 HTTP 客户端架构请见下图的第二张图。MSA 的内部是建立在原生的 ASP.NET IHttpHandler 之上实现的,支持 JSON、XML、JSV、HTML、Message Pack、ProtoBuf、CSV 等消息格式。

MSA 服务端的架构

MSA HTTP Client 的架构

二、MSA 框架的使用

1、服务托管

服务端的服务对外提供服务前,必须先要把服务端给托管起来。MSA 提供了通过 IIS、Self-Host 等多种形式把服务端给托管起来,宿主环境可以是控制台应用或 Windows Service 或 ASP.NET Web 应用或 ASP.NET MVC 应用。提供的 MSA Demo 的宿主环境用的是 ASP.NET Web 应用。

2、 路由

A、MSA 自身提供的默认路由是:

复制代码

 
 

/[xml|json|html|jsv|csv]/[reply|oneway]/[Request DTO 名] [(?query 参数 1={值}&query 参数 2={值}&......&query 参数 n={值})]。

B、创建自定义路由,其创建方法是:使用 RouteAttribute 或在宿主环境中配置。提供的 MSA Demo 采用的是在宿主环境中配置路由这种方式来创建自定义路由。

3、如何验证请求参数的合法性

如果你需要在提交请求参数前,验证请求参数是否必填或是否合法,那么验证逻辑必须写在继承自 MSA 的 AbstractValidator 的类里(参考例子请见 MSA Demo 的 OrderValidator.cs),然后在宿主环境中进行开启验证的配置:

复制代码

 
 

Plugins.Add(new ValidationFeature());

 

container.RegisterValidator(typeof(OrderValidator));

4、服务

创建 MSA 服务时,必须继承来自 MSA 的 Service 类。

5、MSA 内置的客户端

5.1、MSA 内置了一些便捷访问的客户端,这些对象都实现了 IServiceClient 接口,其中支持 REST 的客户端还都实现了 IRestClient 接口。

这些客户端对象包括:JsonServiceClient、JsvServiceClient、XmlServiceClient、MsgPackServiceClient、ProtoBufServiceClient、Soap11ServiceClient、Soap12ServiceClient 等。

从名称可以看出,这几种不同之处在于支持的序列化和反序列化格式不同。因为它们实现的是相同的接口,所以它们的用法相同,也可以相互替换。

MSA Demo 中用到了 JsonServiceClient 和 ProtoBufServiceClient 这两种客户端,其中当用到 ProtoBufServiceClient 客户端时,你还需要完成如下工作:

a、除了需要引用 MSA.dll 外,还需要引用 protobuf-net.dll。

b、需要在宿主环境中进行如下配置:

复制代码

 
 

Plugins.Add(new ProtoBufFormat());

c、必须分别给 Request DTO 对象和 Response DTO 对象的各属性标上 [DataMember(Order = {0})] 特性,具体写法请见 MSA Demo 的 ProductRequestDTO.cs 和 ProductResponseDTO.cs。

5.2、MSA 内置的客户端提供 Get、Send、Post、Put、Delete 等方法。查询数据一般用 Get 方法,新增操作一般用 Post 方法,更新操作一般用 Put 方法,删除操作一般用 Delete 方法。这些方法都有重载。

以下是 Get 方法的其中一个签名:

复制代码

 
 

TResponse Get<TResponse>(IReturn<TResponse> requestDto);

6、MSA API 可视化说明文档自动生成的实现

在宿主环境中加如下配置:

复制代码

 
 

Plugins.Add(new SwaggerFeature());

如果需要在 MSA API 可视化说明文档中能够看到各请求参数、响应的含义说明,那么需要为 Request DTO、Response DTO 对象的各属性标上 ApiMember,代码参考如下:

复制代码

 
 

public class OrderRequest : IReturn<OrderResponse>

 

{

 

  [ApiMember(Name = "Id", Description = "订单 ID 号", IsRequired = false)]

 

  public int Id { get; set; }

 

  [ApiMember(Name = "CustomerName", Description = "客户名", IsRequired = false)]

 

  public string CustomerName { get; set; }

 

  //......

 

  [ApiMember(Name = "OrderItemList", Description = "订购的产品列表", IsRequired = false)]

 

  public List<OrderItem> OrderItemList { get; set; }

 

}

运行结果如下图所示:

在 MSA API 可视化说明文档中显示各请求参数、响应的含义说明

7、运行结果

先运行托管应用(如 MSA Demo 中 ServiceHost 项目),出现下图所示的 Metadata 页。然后再运行客户端来调用微服务;也可通过浏览器查看数据,网址输入格式如:

复制代码

 
 

http://localhost:34833/orders/1.html?CustomerName= 客户 _1&IsTakeAway=true&StatusCode=1&CreatedDate=2017-08-21 10:58:48.230

或:

复制代码

 
 

http://localhost:34833/html/reply/GetOrderRequest?Id=1&CustomerName= 客户 _1&IsTakeAway=true&StatusCode=1&CreatedDate=2017-08-21 10:58:48.230

其中,第 1 个网址格式规则就是 MSA Demo 中在宿主环境中所配的自定义路由规则,第 2 个网址格式规则就是由 MSA 提供的默认路由规则。

单击下图所示 Metadata 页中的【MSA API UI】后,进入下图所示的 MSA API 可视化说明文档界面,开发人员可以通过这份由 MSA 自动生成的说明文档进行调试,十分方便。

Metadata 页

MSA API 可视化说明文档界面

三、微服务治理

在我们自主开发的框架管理系统中,进行接口注册,请见下图。其中,规定内部服务访问名的命名规范是:/{***Service}/ 方法名,如 /OrderService/CreateOrder;规定外部服务访问名 OpenApiName 的命名规范是:{各产品线的缩写英文名}方法名,如 FltCreateOrder,其中 Flt 表示国内机票业务的缩写英文名。

MSA 接口注册页

四、微服务网关 API Gateway

4.1、API Gateway 的简介

API Gateway 风格的核心理念是使用一个轻量级的消息网关作为所有客户端的主入口,并且在 API Gateway 层面上实现通用的非功能性需求。如下图所示:所有的服务通过 API 网关来暴露,这是所有客户端访问的唯一入口;如果一个服务要访问另一个服务,也要通过这个网关。

所有服务通过一个 API 网关来暴露

一旦 API 网关允许客户端消费一个受管理的 API,那么我们就可以以受管理的 API 形式使用它来暴露这个微服务所实现的业务逻辑。API 网关以 NIO、IOCP 来连接内部受管理的 API,以实现 API 网关的高并发。

4.2、API Gateway 的优点

  • 网络隔离:微服务部署在了内网,通过 API Gateway 开放给 PartnerAPI、WebAPI 或 MobileAPI。
  • 在网关层面的轻量级消息路由和转换。
  • 在网关层面对存在的微服务提供必要的抽象。例如,网关可以选择对不同的用户暴露不同的 API。
  • 一个中心的地方提供非功能性的能力,这些能力可复用, 比如超时、限流、熔断、监控、日志记录等。
  • 通过适用 API 网关模式,微服务可以变得更加轻量,因为非功能性需求都在网关上实现了。
  • 统一安全管控。

4.3、API Gateway 的架构

4.4、API Gateway 的功能

API Gateway 主要实现以下功能:

  1. 路由映射:外部服务访问名映射到对应的内部服务访问名。
  2. 权限验证:包括针对客户角色的访问授权验证、针对客户的访问授权验证、IP 黑名单验证。
  3. 超时处理:当 API 网关调用的内部服务响应时间超过了在自主开发的 API 网关后台管理子系统中所设置的允许最长的超时时间时,API 网关会立即停止调用,并返回相关消息给你。
  4. 限流控制:当你通过 API 网关调用内部服务的频率达到在某个阈值时,API 网关会立即做断开链路处理。过了时间后,链路会自动闭合回去。
  5. 熔断处理:熔断处理对避免无谓的资源消耗特别有用,当通过 API 网关调用的内部服务出现异常的频率达到某个阈值时,那么 API 网关会做临时熔断处理即临时断开链路,暂时停止你对那个内部服务的调用。临时熔断后,过了一段时间后,链路会自动闭合回去。
  6. 日志信息记录:会记录客户 IP、客户请求参数、返回结果、异常信息等信息。

4.5、API Gateway 的使用

在使用 API Gateway 之前,需要先配置网关参数。网关参数的配置是在自主开发的 API 网关后台管理子系统中进行:

在自主开发的 API 网关后台管理子系统中配置网关参数

五、Demo 下载及更多资料

 

中小型研发团队架构实践:Redis 快速入门及应用

  • 张辉清
  • 杨丽

阅读数:79832017 年 11 月 29 日

Redis 的使用难吗?不难,Redis 用好容易吗?不容易。Redis 的使用虽然不难,但与业务结合的应用场景特别多、特别紧,用好并不容易。我们希望通过一篇文章及 Demo,即可轻松、快速入门并学会应用。

一、Redis 简介

Redis 是一个开源的 Key-Value 存储,但又不仅仅是 Key-Value 存储,用官网上的话来说,Redis 是一个数据结构存储,可用作数据库、缓存和消息中间件。相对于传统的 Key-Value 存储 Memcached 来说,Redis 具有如下特点:

  • 速度快
  • 丰富的数据结构,除 String 之外,还有 List、Hash、Set、Sorted Set
  • 单线程,避免了线程切换和锁的性能消耗
  • 原子操作
  • 可持久化(RDB 与 AOF)
  • 发布 / 订阅
  • 支持 Lua 脚本
  • 分布式锁
  • 事务
  • 主从复制与高可用(Redis Sentinel)
  • 集群(3.0 版本以上)

二、Redis 数据结构

1、String

这是最简单的 Redis 类型。如果只使用这种类型,Redis 就像一个可持久化的 Memcached 服务器。

2、List

Redis 的 List 是基于双向链表实现的,可以支持反向查找和遍历。

常用案例:聊天系统、社交网络中获取用户最新发表的帖子、简单的消息队列、新闻的分页列表、博客的评论系统。

3、Hash

Hash 是一个 String 类型的 field 和 value 之间的映射表,请见下图,类似于.NET 中的 Hashtable 和 Dictionary。主要用来存储对象,可以避免序列化的开销和并发修改控制的问题。

4、Set

Set 也是一个列表,不过它的特殊之处在于它是可以自动排重的:当需要存储一个列表数据,而又不希望出现重复的时候,Set 是一个很好的选择(比如 ID 的集合)。并且 Set 提供了判断某个成员是否在一个 Set 集合内的接口,这也是 List 所没有的。

5、Sorted Set

Sorted Set 和 Set 的使用场景类似,区别是 Sorted Set 会根据提供的 score 参数来进行自动排序。当你需要一个有序的并且不重复的集合列表,那么就可以选择 Sorted Set 数据结构。常用案例:游戏中的排行榜。

三、 Redis 重要特性

以下特性请重点看管道和事务。

1、管道

Redis 管道是指客户端可以将多个命令一次性发送到服务器,然后由服务器一次性返回所有结果。管道技术在批量执行命令的时候可以大大减少网络传输的开销,提高性能。

2、事务

Redis 事务是一组命令的集合。一个事务中的命令要么都执行,要么都不执行。如果命令在运行期间出现错误,不会自动回滚。

管道与事务的区别:管道主要是网络上的优化,客户端缓冲一组命令,一次性发送到服务器端执行,但是并不能保证命令是在同一个事务里面执行;而事务是原子性的,可以确保命令执行的时候不会有来自其他客户端的命令插入到命令序列中。

3、分布式锁

分布式锁是控制分布式系统之间同步访问共享资源的一种方式。在分布式系统中,常常需要协调他们的动作,如果不同的系统或是同一个系统的不同主机之间共享了一个或一组资源,那么访问这些资源的时候,往往需要互斥来防止彼此干扰来保证一致性,在这种情况下,便需要使用到分布式锁。

4、地理信息

从 Redis 3.2 版本开始,新增了地理信息相关的命令,可以将用户给定的地理位置信息(经纬度)存储起来,并对这些信息进行操作。

四、 使用方法

步骤 1、在需要使用 Redis 的项目中引用 FxCommon.dll 和 Redis.dll

步骤 2、在 App.config 或 Web.config 文件中添加如下配置

复制代码

 
 

<add key="RedisServerIP" value="redis:uuid845tylabc123@139.198.13.12:4125"/>

 

<!-- 提供的 Redis 环境是单机版配置。如果 Redis 是主从配置,则还需设置 RedisSlaveServerIP-->

 

<!--<add key="RedisSlaveServerIP" value="redis:uuid845tylabc123@139.198.13.13:4125"/>-->

  
 

<!--Redis 数据库。如果不需要指定 Redis 数据库,就配置默认值 0-->    

 

<add key="RedisDefaultDb" value="0"/>

步骤 3、使用 PooledRedisClientManager 类创建 Redis 连接池:

复制代码

 
 

// 读取 Redis 主机 IP 配置信息

 

string[] redisMasterHosts = ConfigurationManager.ConnectionStrings["RedisServerIP"].ConnectionString.Split(',');

  
 

// 如果 Redis 服务器是主从配置,那么还需要读取 Redis Slave 机的 IP 配置信息

 

string[] redisSlaveHosts = null;

 

var slaveConnection = ConfigurationManager.ConnectionStrings["RedisSlaveServerIP"];

 

if (slaveConnection != null && !string.IsNullOrWhiteSpace(slaveConnection.ConnectionString))

 

{    

 

   string redisSlaveHostConfig = slaveConnection.ConnectionString;    

 

   redisSlaveHosts = redisSlaveHostConfig.Split(',');

 

}

  
 

// 读取 RedisDefaultDb 配置

 

int defaultDb = 0;

 

string defaultDbSetting = ConfigurationManager.AppSettings["RedisDefaultDb"];

 

if (!string.IsNullOrWhiteSpace(defaultDbSetting))

 

{    

 

   int.TryParse(defaultDbSetting, out defaultDb);

 

}

  
 

var redisClientManagerConfig = new RedisClientManagerConfig

 

{    

 

   MaxReadPoolSize = 50,    

 

   MaxWritePoolSize = 50,    

 

   DefaultDb = defaultDb

 

};

  
 

// 创建 Redis 连接池

 

Manager = new PooledRedisClientManager(redisMasterHosts, redisSlaveHosts, redisClientManagerConfig)

 

{    

 

   PoolTimeout = 2000,    

 

   ConnectTimeout = 500                

 

};

步骤 4、通过 PooledRedisClientManager 的实例获取 Redis 客户端,然后就可以开始通过 Redis 客户端的 API 进行操作。

五、其它

5.1、 Redis Key 命名规范

Redis Key 命名规范:AppID:KeyName。

可能有很多人习惯用英文状态的点号来作为 AppID 和 KeyName 的分隔符,而笔者建议使用冒号作为 AppID 和 KeyName 的分隔符,其原因是:这么写会使 Redis Key 会以 AppID 作为分类显示在 Redis Desktop Manager 中,方便你能够快速查到要查阅的 Redis Key 对应的 Redis Value 值,请见下图:

但如果使用英文状态的点号来作为分隔符的话,那么在 Redis Desktop Manager 中,Redis Key 就不会被分类了,请见下图:

5.2、常见应用问题

  • 缓存穿透处理:什么是缓存穿透?当根据 Redis key 在缓存中查询后,不存在对应 Value,就应该会在后端系统如 DB 中去查找,该 Key 的并发请求量一旦变大,那么就会对 DB 造成很大的压力。解决办法有:a. 前端风险控制,将恶意穿透情况排除在外;b. 对查询结果为空的情况依然进行缓存,但缓存时间会设置得很短,一般是几分钟。
  • 缓存雪崩处理:什么是缓存雪崩?当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候,也会给后端系统 (比如 DB) 带来很大压力。解决办法有:后端连接数限制,错误阈值限制,超时处理,缓存失效时间均匀分布,前端永不失效及后端主动更新。
  • 缓存时长:策略定位复杂,需要多维度的计算。
  • 缓存失效:按时失效,事件失效,后端主动更新。
  • 缓存 Key:Hash、规则、前缀 +Hash,异常情况可人工干预。
  • Lua 脚本:服务端批量处理及事务能力,有条件逻辑的可扩展脚本。使用它的好处有:减少网络开销、原子操作、可复用。
  • Limit:可滑动时间窗口,如应用于 Session,Memcached 需每次传 Key 和 Value。

六、Demo 下载及更多资料

 

中小型研发团队架构实践:任务调度 Job

  • 张辉清
  • 杨丽

阅读数:41482017 年 12 月 5 日

一、Job 简介

Job 类似于数据库中的作业,多用于实现定时执行任务。适用场景主要包括定时轮询数据库同步、定时处理数据、定时邮件通知等。

我们的 Job 分为操作系统级别定时任务 WinJob 和 HttpJob,其中,WinJob 使用开源的任务调度框架 Quartz.NET+ ZooKeeper 实现,HttpJob 的服务端是自主开发实现的,可以直接定时调用你的计划任务如微服务。下面分别予以介绍。

二、WinJob

WinJob 使用 Quartz.NET+ZooKeeper 来实现,Quartz.NET 实现调度,ZooKeeper 使用 MasterElection 来实现高可用,解决单点问题。ZooKeeper 后继有文章单独介绍,这里重点介绍 Quartz.NET 框架的使用。

Quartz.NET 是一个全功能的开源任务调度框架,通过简单的配置就可以实现强大的任务调度功能,使得开发人员不用过多关注任务的调度,只用关注项目的业务逻辑。使用任务调度框架的价值:

  1. 提高开发效率:开发人员只需要编写业务代码,而具体的任务调度只需要通过配置就可以实现。
  2. 提高软件的可靠性:同一应用多个任务之间可以很好的隔离起来,互不影响。
  3. 降低开发人员成本和开发复杂度:开发人员不需要对线程、Timer 很了解,就能实现一个强大的执行计划应用。
  4. 容易迁移:只需实现 Quartz.IJob 接口即可,调用一次业务逻辑的入口即可。
  5. 容易扩展:新业务只需增加配置即可。

基于 Quartz.NET 实现 Job 调度的方法:

在后端服务声明实例化一个调度器,在启动服务的时候启动调度器,相应的代码如下所示:

复制代码

 
 

/// <summary>        

 

/// 当前调度服务的调度器        

 

/// </summary>        

 

public IScheduler CurrentSched

 

{

 

  get; private set;

 

}

 

public JobService()        

 

{    

 

  InitializeComponent();  

  
 

  StdSchedulerFactory schedulerFactory = new StdSchedulerFactory();                

 

  CurrentSched = schedulerFactory.GetScheduler();      

 

}        

 

protected override void OnStart(string[] args)        

 

{    

 

  CurrentSched.Start();                

 

  logger.Info("调度服务成功启动!");        

 

}

创建相应的任务和触发器,之后把任务和关联的触发器加入之前声明的调度器 CurrentSched,相应的代码如下所示:

复制代码

 
 

/// <summary>        

 

/// 演示一个任务多触发器的使用        

 

/// </summary>        

 

private static void JobWithManyTriggerDemo()         {                

 

   IJobDetail simpleJob = JobBuilder.Create<SimpleJob>().WithIdentity("任务名称", "任务组名").Build(); // 创建一个 simpleJob 任务                

 

   ITrigger simpleTrigger = TriggerBuilder.Create().WithIdentity("触发器名称 3", "触发器组名").StartNow()        

 

       .WithSimpleSchedule(x => x.WithIntervalInSeconds(5).RepeatForever()).Build();   // 创建一个简单触发器,每隔 5 秒执行一次        

  
 

   CurrentSched.ScheduleJob(simpleJob, simpleTrigger);    // 把 simpleJob 任务、简单触发器加入调度器        

  
 

   ITrigger cronTrigger = TriggerBuilder.Create().WithIdentity("触发器名称 4", "触发器组名").StartNow()        

 

       .WithCronSchedule("/10 * * ? * *").ForJob(simpleJob).Build();   // 创建一个为任务“simpleJob”服务的 Cron 触发器,每隔 10 秒执行一次      

  
 

    CurrentSched.ScheduleJob(cronTrigger);   // 把 Cron 触发器加入调度器        

 

}

在业务逻辑层继承 IJob 接口,并实现 Execute 方法,在该方法内实现需要调度的业务逻辑,相应的代码如下所示:

复制代码

 
 

/// <summary>    

 

/// 简单任务    

 

/// </summary>    

 

public class SimpleJob : IJob    

 

{            

 

   ILog logger = LogManager.GetLogger(typeof(SimpleJob));    

 

   public void Execute(IJobExecutionContext context)                

 

   {      

 

       Console.WriteLine("简单的任务演示!" + DateTime.Now.ToString("HH:mm:ss"));      

 

       logger.Info("简单的任务演示!" + DateTime.Now.ToString("HH:mm:ss"));                    

 

       // 业务逻辑处理                    

 

       Thread.Sleep(2000);            

 

    }    

 

}

三、HttpJob

通过自主开发的 JobServer,结合自主开发的 Job 集中式管理平台,可以实现满足绝大部分场景的 Job 调度。 这种 Job 调度使用方式使你只需关注实现业务系统的业务逻辑部分即可,无需在业务系统中额外关注如何使用 Quartz.NET。

3.1、HttpJob 的服务端实现

JobServer 实现的主要逻辑:

  1. 借助 Quartz,可实现多个线程(如 10 个线程)同时调用多个 HttpJob;
  2. 实现了 Get、Post、Head 三种方式的请求;
  3. 借助 ZooKeeper 的 MasterElection 来实现高可用,实现自动主备切换;
  4. 记录日志,方便追踪。

3.2、HttpJob 的后台——Job 集中式管理平台

在集中式 Job 管理平台中,配置相应的 Job 信息。配置完 Job 信息后,JobServer 获取到这些 Job 信息后,就能够定时执行这些 Job。要配置的 Job 信息包括 Job 的任务名称、任务组名、请求地址、请求类型、开始时间、触发器类型、次数、间隔时间 (s)、Cron-Like 表达式以及状态。

其中请求地址就是 JobServer 实际定时调用的任务的 http 地址,例如 HttpJobDemo 的 WebForm1.aspx 这个任务的运行地址 http://localhost:10786/WebForm1.aspx。

3.3、采用 HttpJob 进行任务调度的优势和约束

采用 HttpJob 的优势

  1. 高可用:借助网站集群巧妙地解决 Job 服务的单点问题。
  2. 方便发布:不用重启 Job 服务。
  3. 减少依赖,易学易用,不用关注线程、Windows 服务方面的知识。
  4. 数据分片,可以采用 URL 来取模 + 多个 HttpJob。

采用 HttpJob 的约束

  1. 由于请求 HttpJob 的最长响应时间是 30 秒,所以 Job 运行时间一旦超过 30 秒,则建议为 Job 先创建异步线程,立即返回。
  2. Job 调度的频率最少间隔时间是 1 分钟,因为通过 HttpJob 通知并不是件高效的事情。
  3. 为了安全应建立专业的 Job 集群,一般两台即可,外部不可访问,SLB 采用简单轮询方案。
  4. 新增及修改 Job 配置,10 分钟生效。

四、Cron 表达式

Cron 表达式格式:秒 分 时 日 月 周 年(可选)。要遵守的规范请见下表:

五、Demo 下载及更多资料

中小型研发团队架构实践:搜索服务器 Solr

  • 张辉清
  • 杨丽

阅读数:20472017 年 12 月 17 日

一、Solr 是什么

Apache Solr 是一个开源的搜索服务器,Solr 使用 Java 语言开发,主要基于 HTTP 和 Apache Lucene 实现。 Apache Lucene 是一个高效的、基于 Java 的全文检索库。

二、为什么要用 Solr

  • 在公司后台历史订单查询的应用中,模糊查询的实现方式为 LIKE '%something%',性能很差。
  • 基于关键字的日志内容需要快速检索。
  • 其他数据库模糊查询的优化方案。

三、Solr 的特性

  • 具备高级全文搜索的能力
  • 高容量
  • 基于标准的开放接口(XML、JSON、HTTP):Document 通过 HTTP 利用 XML 加到一个搜索集合中,查询该集合时也是通过 HTTP 收到一个 XML/JSON 响应来实现
  • 提供功能全面的管理界面,使你能够容易地控制你的 Solr 实例
  • 易监控
  • 高稳定性和容错性
  • 易配置,且不失灵活和适配性
  • 准实时索引,确保你能够实时看到更新后的内容
  • 可扩展插件架构:新功能能够以插件的形式非常方便地添加到 Solr 服务器上

四、Solr 怎样工作

4.1、Web 管理 UI

URL 为:http://139.198.13.12:7000/solr/admin.html。请注意:Solr5.5 的,一定要加 admin.html,如果不加的话,则按回车后将返回 404(表示找不到页面)。

4.2、Solr 服务端的安装与配置

4.2.1、安装 Solr 服务:安装的版本号是 5.5.4。

4.2.2、建立 Core

要使用 Solr,需要建立类似于数据库实例的 Core。每个 Core 对应一个文件夹,此文件夹建立在 Solr Home 路径下,且其名字要和 Core 的名字一致:

4.2.3、配置 Core

以 Demo 中使用于 Solr 服务器上的 PolicyCore 为例,修改以下 3 个配置文件:

solrconfig.xml、managed-schema 是从位于【{Solr Home 路径}/configsets/basic_configs/conf】路径下的同名配置文件拷贝而来,而 data-config.xml 来自:对 Solr 服务端安装文件 solr-5.5.4.tgz 解压后,得到 solr-5.5.4 的文件夹名,然后把位于【solr-5.5.4/example/example-DIH/solr/db/conf】路径下的 db-data-config.xml 文件拷贝到【{Solr Home 路径}/configsets/basic_configs/conf】路径下,并重命名为 data-config.xml。

在 solrconfig.xml 配置文件中增加如下内容:

复制代码

 
 

<lib dir="../contrib/extraction/lib" regex=".*\.jar" />

 

<lib dir="../dist/" regex="solr-cell-\d.*\.jar" />

 

<lib dir="../contrib/clustering/lib/" regex=".*\.jar" />

 

<lib dir="../dist/" regex="solr-clustering-\d.*\.jar" />

 

<lib dir="../contrib/langid/lib/" regex=".*\.jar" />

 

<lib dir="../dist/" regex="solr-langid-\d.*\.jar" />

 

<lib dir="../contrib/velocity/lib" regex=".*\.jar" />

 

<lib dir="../dist/" regex="solr-velocity-\d.*\.jar" />

 

<lib dir="../dist/" regex="solr-dataimporthandler-\d.*\.jar" />

以上内容加在【5.5.4】节点之后、【${solr.data.dir:}】节点之前。

复制代码

 
 

<requestHandler name="/dataimport" class="solr.DataImportHandler">

 

   <lst name="defaults">

 

       <str name="config">data-config.xml</str>

 

   </lst>

 

</requestHandler>

以上内容加的位置请见如下图所示:

对 managed-schema 文件进行修改:以下内容加在节点内:

复制代码

 
 

<fieldType name="textPolicy_ik" class="solr.TextField">

 

   <analyzer type="index" useSmart="false" class="org.wltea.analyzer.lucene.IKAnalyzer" />

 

   <analyzer type="query" useSmart="true" class="org.wltea.analyzer.lucene.IKAnalyzer" />

 

</fieldType>

注释掉以下配置:

复制代码

 
 

<field name="id" type="string" indexed="true" stored="true" required="true" multiValued="false" />

然后在其下增加如下配置:

复制代码

 
 

<field name="PolicyID" type="string" indexed="true" stored="true" required="true" multiValued="false" />

 

<field name="PolicyGroupID" type="long" indexed="true" stored="true" />

 

<field name="PolicyOperatorID" type="long" indexed="true" stored="true" />

 

<field name="PolicyOperatorName" type="textPolicy_ik" indexed="true" stored="true" omitNorms="true" />

 

<field name="PolicyCode" type="textPolicy_ik" indexed="true" stored="true" omitNorms="true" />

 

<field name="PolicyName" type="textPolicy_ik" indexed="true" stored="true" omitNorms="true" />

 

<field name="PolicyType" type="string" indexed="true" stored="true" />

 

<field name="TicketType" type="int" indexed="true" stored="true" />

 

<field name="FlightType" type="int" indexed="true" stored="true" />

 

<field name="DepartureDate" type="tdate" indexed="true" stored="true" default="NOW+8HOUR" />

 

<field name="ArrivalDate" type="tdate" indexed="true" stored="true" default="NOW+8HOUR" />

 

<field name="ReturnDepartureDate" type="tdate" indexed="true" stored="true" default="NOW+8HOUR" />

 

<field name="ReturnArrivalDate" type="tdate" indexed="true" stored="true" default="NOW+8HOUR" />

 

<field name="DepartureCityCodes" type="textPolicy_ik" indexed="true" stored="true" omitNorms="true" />

 

<field name="TransitCityCodes" type="textPolicy_ik" indexed="true" stored="true" omitNorms="true" />

 

<field name="ArrivalCityCodes" type="textPolicy_ik" indexed="true" stored="true" omitNorms="true" />

 

<field name="OutTicketType" type="int" indexed="true" stored="true" />

 

<field name="OutTicketStart" type="tdate" indexed="true" stored="true" default="NOW+8HOUR" />

 

<field name="OutTicketEnd" type="tdate" indexed="true" stored="true" default="NOW+8HOUR" />

 

<field name="OutTicketPreDays" type="int" indexed="true" stored="true" />

 

<field name="Remark" type="textPolicy_ik" indexed="true" stored="true" omitNorms="true" />

 

<field name="Status" type="int" indexed="true" stored="true" />

 

<field name="SolrUpdatedTime" type="tdate" indexed="true" stored="true" default="NOW+8HOUR" />

  
 

<uniqueKey>PolicyID</uniqueKey>

属性说明:

  • name:表示域名。
  • type:表示域的类型,必须匹配类型,不然会报错。如果需要分词,那么就传分词器名如 textPolicy_ik;另外,日期建议传 tdate,因为可以加快范围查找速度。
  • indexed:是否要做索引。
  • stored:是否要存储。
  • required:是否必填。
  • multiValued:是否有多个值。如果设置为多值,里面的值就采用数组的方式来存储。

对 data-config.xml 文件进行修改:先注释掉默认有的 dataConfig,然后在被注释内容的后面增加如下配置内容:

复制代码

 
 

<dataConfig>

 

   <dataSource driver="com.microsoft.sqlserver.jdbc.SQLServerDriver" url="jdbc:sqlserver://{SQLServer 服务器 IP 地址}:{端口号,如果端口号是默认的 1433,则可不写};DatabaseName=SolrDB" user="sa" password="{登录 SQL Server 的密码}"/>

 

   <document name="Info">        

 

      <entity name="Policy" dataSource="SolrDB" transformer="ClobTransformer" pk="PolicyID"

 

          query="SELECT [PolicyID], [PolicyGroupID], [PolicyOperatorID], [PolicyOperatorName], [PolicyCode], [PolicyName], [PolicyType], [TicketType], [FlightType],  DATEADD(HOUR, 8, CAST([DepartureDate] AS DATETIME)) [DepartureDate], DATEADD(HOUR, 8, CAST([ArrivalDate] AS DATETIME)) [ArrivalDate], DATEADD(HOUR, 8, CAST([ReturnDepartureDate] AS DATETIME)) [ReturnDepartureDate], DATEADD(HOUR, 8, CAST([ReturnArrivalDate] AS DATETIME)) [ReturnArrivalDate], [DepartureCityCodes], [TransitCityCodes], [ArrivalCityCodes], [OutTicketType], [OutTicketStart], [OutTicketEnd], [OutTicketPreDays], [Remark], [Status], DATEADD(HOUR, 8, CAST([SolrUpdatedTime] AS DATETIME)) [SolrUpdatedTime] FROM [Policy]"

 

          deltaImportQuery="SELECT [PolicyID], [PolicyGroupID], [PolicyOperatorID], [PolicyOperatorName], [PolicyCode], [PolicyName], [PolicyType], [TicketType], [FlightType], DATEADD(HOUR, 8, CAST([DepartureDate] AS DATETIME)) [DepartureDate], DATEADD(HOUR, 8, CAST([ArrivalDate] AS DATETIME)) [ArrivalDate], DATEADD(HOUR, 8, CAST([ReturnDepartureDate] AS DATETIME)) [ReturnDepartureDate], DATEADD(HOUR, 8, CAST([ReturnArrivalDate] AS DATETIME)) [ReturnArrivalDate], [DepartureCityCodes], [TransitCityCodes], [ArrivalCityCodes], [OutTicketType], [OutTicketStart], [OutTicketEnd], [OutTicketPreDays], [Remark], [Status], DATEADD(HOUR, 8, CAST([SolrUpdatedTime] AS DATETIME)) [SolrUpdatedTime] FROM [Policy] WHERE PolicyID = '${dataimporter.delta.PolicyID}'"

 

      deltaQuery="SELECT [PolicyID] FROM [Policy] WHERE [SolrUpdatedTime] > '${dataimporter.last_index_time}'">

 

    <field column="PolicyID" name="PolicyID"/>  

 

    <field column="PolicyGroupID" name="PolicyGroupID"/>  

 

    <field column="PolicyOperatorID" name="PolicyOperatorID"/>

 

    <field column="PolicyOperatorName" name="PolicyOperatorName"/>

 

    <field column="PolicyCode" name="PolicyCode"/>

 

    <field column="PolicyName" name="PolicyName"/>

 

    <field column="PolicyType" name="PolicyType"/>

 

    <field column="TicketType" name="TicketType"/>

 

    <field column="FlightType" name="FlightType"/>

 

    <field column="DepartureDate" name="DepartureDate"/>

 

    <field column="ArrivalDate" name="ArrivalDate"/>

 

    <field column="ReturnDepartureDate" name="ReturnDepartureDate"/>

 

    <field column="ReturnArrivalDate" name="ReturnArrivalDate"/>

 

    <field column="DepartureCityCodes" name="DepartureCityCodes"/>

 

    <field column="TransitCityCodes" name="TransitCityCodes"/>

 

    <field column="ArrivalCityCodes" name="ArrivalCityCodes"/>

 

    <field column="OutTicketType" name="OutTicketType"/>

 

    <field column="OutTicketStart" name="OutTicketStart"/>

 

    <field column="OutTicketEnd" name="OutTicketEnd"/>

 

    <field column="OutTicketPreDays" name="OutTicketPreDays"/>

 

    <field column="Remark" name="Remark"/>

 

    <field column="Status" name="Status"/>

 

    <field column="SolrUpdatedTime" name="SolrUpdatedTime"/>

 

   </entity>

 

 </document>

 

</dataConfig>

属性说明:

  • query:查询数据库表中符合的记录数据。
  • deltaImportQuery:表示次查询。次查询是获取以上步骤的 ID,然后把其全部数据获取,根据获取的数据,对索引库进行更新操作,可能是删除、添加或修改。此查询只对增量导入起作用,可以返回多个字段的值,一般情况下,都是返回所有字段的列。
  • deltaQuery:查询出需要增量索引的数据,所有经过修改的记录的 ID,可能是修改操作、添加操作或删除操作产生的。此查询只对增量导入起作用,而且只能返回 ID 值。

4.3、为 SolrDB 数据库的 Policy 表增加字段和触发器

复制代码

 
 

USE [SolrDB]

 

GO

  
 

CREATE TRIGGER [dbo].[TR_Solr_UPDATE_Policy] ON [dbo].[Policy]

 

       FOR UPDATE, INSERT

 

AS

 

BEGIN

 

  IF UPDATE(PolicyID)

 

       OR UPDATE(PolicyGroupID)

 

       OR UPDATE(PolicyOperatorID)

 

       OR UPDATE(PolicyOperatorName)

 

       OR UPDATE(PolicyCode)

 

       OR UPDATE(PolicyName)

 

       OR UPDATE(PolicyType)

 

       OR UPDATE(TicketType)

 

       OR UPDATE(FlightType)

 

       OR UPDATE(DepartureDate) OR UPDATE(ArrivalDate)

 

       OR UPDATE(ReturnDepartureDate) OR UPDATE(ReturnArrivalDate)

 

       OR UPDATE(DepartureCityCodes)

 

       OR UPDATE(TransitCityCodes)

 

       OR UPDATE(ArrivalCityCodes)

 

       OR UPDATE(OutTicketType)

 

       OR UPDATE(OutTicketStart) OR UPDATE(OutTicketEnd)

 

       OR UPDATE(OutTicketPreDays)

 

       OR UPDATE(Remark)

 

       OR UPDATE(Status)

 

  BEGIN

 

               UPDATE dbo.Policy

 

               SET SolrUpdatedTime = GETDATE()

 

               FROM dbo.Policy p, inserted i

 

               WHERE p.PolicyID = i.PolicyID

 

  END

 

END

 

GO

4.4、SolrNet

SolrNet 是 Solr 的开源.NET 客户端之一。

4.5、定时从数据库中全量、增量数据导入到 Solr

Solr 自身提供有定时增量导入功能,但经测试 apache-solr-dataimportscheduler1.0 版本在 Solr5.5 上已经不能使用,除非修改 apache-solr-dataimportscheduler 的源码。于是,我们采用了如下方式:

首先,开发 Job 任务调度 RESTful 服务,这种方式不仅可以实现定时增量数据导入,也能够实现定时全量数据导入。

然后,在自主研发的【Job 集中式管理平台】中把相关内容都配置好,如下图所示。

这样,我们的 JobServer 就会定时地以 HTTP GET 或 HTTP POST 或 HTTP HEAD 方式请求全量 / 增量导入链接,从而实现了定时全量、增量数据导入功能。另外,如果你想要知道如何利用 SolrNet 实现全量导入、增量导入,请分别参考 Demo 代码中的 FullDataImport() 和 DeltaDataImport() 这两个示例。

4.6、准实时数据导入、删除以及查询

用 SolrNet 的 CURD API 实现,示例请见 Demo 的 Add()、Delete() 和 Query()。准实时数据导入较定时增量数据导入更近于实时,在实际应用中如通过消息队列对数据库和 Solr 同时更新,则更好。

五、Demo 下载及更多资料

 

技改之路:从单块应用到微服务,我的血泪总结

  • 张辉清

阅读数:46722016 年 8 月 30 日

技改是技术改造的简称,是技术的蜕变。本文指的是在公司技术发展的某个瓶颈阶段,按原有开发和组织方式已经无法玩下去,这时公司希望引进架构师或技术牛人,来破解当前困局。技术改造,对于公司和技术人员而言都非常难得,参与者多,主导者少。我有幸前后主导过 3 次 OTA 系统的技改,规模有大有小,每次环境和问题虽不一样,但还是有套路可循。

《技改之路》少讲技术多讲路,我们不过多的关注技术细节和中间件的实现,而重点讲述技术改造的过程和思考,以下是本次分享的 Topic:

  • 系统背景
  • 前期工作
  • 技改实施
  • 总结

一、系统背景

1、技术规模

公司

  • 国内领先的 B2B 机票分销平台
  • 资本原始积累,财务良好,一直盈利

系统规模

  • 200+ 应用
  • 100+ 库,1 万 + 表

研发规模

  • 开发人员 200 人左右
  • 服务器有 200 台左右

此案例是一个中等规模的电子商务公司,老板白手起家,资本原始积累,现在赚钱的互联网公司很少哦。公司从 2006 年的几个研发人员,到技改前的 200 个左右研发人员,业务发展良好,是国内领先的 B2B 机票分销平台,互联网名声虽不大,但处于闷声发大财的状态。

公司之前尝试过 2 次系统重建,请了一批批的牛人,前后经历过 4 年。公司消耗大,但都以失败告终。此案例是我本人的第 2 次技改,效果不错,整体进展顺利,团队技术水平也有 1~2 个档次的提升,算是比较成功的实践。另外,因为案例过于真实,有些 UML 会打上马赛克,请多谅解。

2、单块应用

3、主要问题

  • 单块应用,该合并的没有合并,该拆开的没有拆开,单个体量不合理,主平台体量太大,其它又过小;
  • 技术过旧:使用 7 年以前的技术,主平台采用单块应用,且体量过大,无法整体更新维护;
  • 多版本共存:版本混乱,只敢添加,不敢修改;
  • 整个系统非常脆弱,问题多,访问量一大就挂;
  • 管理问题:发布困难、测试困难、修改困难、排错困难。

二、前期工作

1、架构部组建

成立架构部:

  • 内招几名老程序员,外招几个架构师

培养:

  • 内部走出去,提高眼界;外部牛人请进来,落地了解历史和业务

制度:

  • 项目管理 + 知识分享: JIRA+WIKI
  • 团队建设、技术分享、工程师文化

2、总体规划

架构是演化出来的还是设计出来的?对于创业场景,创业本身就是在未知中寻找机会,将不清楚变为清楚,系统的架构自然是演化出来的,而对于技术改造或 Google 搜索等复杂工程场景,系统的架构当然要精心策划。

公司电商系统总体架构,我们整整花了 1 个多月的时间,对项目做了总体的规划,然后对内宣讲推广,让每一个参与者了解自已的目标和价值。不手握地图,你怎知站对了位置!

3、中间件构建

我们构建的中间件有:

job/redis/center Log/ 业务监控 metrics/dashboad/ 调试工具 windbg/rabbitMQ

ORM 工具 dapper/MongoDB/jetermclient/ 公共类库 jFX/zookeeper/openTSDB

HBase/searcher 工具 solr/ 元数据管理 DDM/DLL 管理 nuget/ 自动发布 Jenkins/ 微服务架构 JSOA

中间件是应用系统的基础设施,是应用的装备和工具。农村建住房是一块砖一块砖的往上垒,城市建大 house 则是先打地基,然后再建主框架,最后才是垒砖,所以中间件的建设是大中型系统建设的前提

以上中件间的构建过程贯穿于整个技改的生命周期,每一个中间件可能需要花 1~2 个月,它们大部分都基于开源。请关注上面的顺序,直面当前的问题,按需快速构建和推动。虽然使用开源,但中件间的引进和改造有自已的一套流程:调查 => 试用 => 选型 => 深入研究 => demo => wiki => 分享推广 => 业务系统试用 => 改进完善 => 大规模推广。

中间件的构建和增加,不仅对当前业务系统影响较小,还可以解决一部分业务难题,减轻数据库的压力。同时它还有利于建立技术氛围和分享机制。一支有激情、爱技术的研发团队,对技改的具体实施是非常重要的。

三、技改实施

1、数据库改造

当面对 100 个多库时,我认为系统架构师关注到数据库级别即可,建库拆库。数据库按模块整体迁移,其实并没有想象中那么难,理想情况下只需修改数据库的链接,而对于表和字段的优化,可由应用架构师或技术主管,以 SOA 收口或应用重构来实现。

数据库如何做到可伸缩,可大可小方便拆分呢,思考如下:

  1. 上图从内往外看,一个框即可以是一个库,也可以是一个模块,还可以是一个表,根据当前业务规模和系统复杂度来实现;

  2. 我们的大实体关系图具体为:产品、用户、订单、结算、基础设施。它们早期可以是一个库,里面有 5 个模块,中期可以分为 5 个库,后期则可以更底级别分为更多的库;

  3. 命名规范:数据库名:业务线缩写 + 库名;模块名:参考大 E-R 图 + 专业词汇缩写;表名:模块缩写 + 表名;自增编号:表名 +ID;

  4. 模块内可多表联接,模块间减少联接,数据库间不允许联接;

  5. 每一个数据库有且仅有一个 Owner 组,原则上只允许一个团队才能 Create,其它团队访问需要分级控制,L1 为接口,L2 为只读库,L3 为直接读写“写库”。

数据库规划

数据库是整个信息系统中生命周期最长、最难修改的部分。所以让时间来解决时间的问题,要加强设计,具体实施过程如下:

  1. 在地图即总体架构文档推广后,我们就新建立了一批库,这在早期还遭到 DBA 的抱怨;

  2. 新增相关库后,新表按新规则创建,特殊情况走特殊审批;

  3. 去 SP 去关联,让数据库减少计算,回归存储本质;

  4. 数据库拆分,改表改字段,采用模块整体迁移或应用重构;

  5. 一年后,再去看数据库,发现在没有特别立项和驱动的情况下,已接近一半的表在新库中。

数据变迁

状态图是数据的变迁,是数据与行为的互动,数据的变化会引起行为的变化,行为的变化会产生数据的不同。上图是国内的订单状态变迁图,它的价值不仅属于数据库层,还在于 SOA 服务化和核心业务流程。

2、服务改造

服务是动词,是行为或活动的抽象,它的价值在于业务逻辑或行为的重用,具体实施过程如下:

  1. 服务列表和服务协议,在设计阶段使用 Excel 表格;

  2. 统一 Request/Response 规范;

  3. 服务实现,因没有直接可见的业务价值输出,最好以工单或项目来落地;

  4. 服务治理,早期没有工具时,使用 WIKI 做简单管理,后期使用专业的服务治理工具。

领域模型

(点击放大图像)

没有领域图的架构设计都是耍流氓,我们画领域图的架构师是 2 位老员工,没有多少高大上,甚至于他们之前没有画过 UML,但我们的状态图和领域模型都是出自他们之手。其实画领域图的关键是懂事物本身,并知道它们的关系。我们的领域图与业务模型中的 5 大业务流程一一对应,包括:预订流程,订单处理流程,产品供应流程,财务结算流程,账户管理流程。

微服务

我们的微服务 JSOA V2.0 是基于 ServiceStack 当时最新的版本号 4.0.50 实现的,它本身支持轻量级协议和 Metadata,以及 Swagger,是微服务的一种架构实现。另外,它还可以再扩展以 API Gateway 的方式实现 Open API。

微服务 MSA 与我们之前的 SOA、ESB 有什么区别呢?

  • ESB 有总线和聪明的管道管理能力;

  • SOA 弱化了中间的管道和总线,强化了两端;

  • 微服务 MSA 使用通用的轻量级协议和更加 web 化(RESTFUL)。

3、应用架构改造

系统是什么?系统 = 元素 + 关系。应用架构是什么?应用架构 = 应用 + 架构。应用就是系统的最小单元,应用分级和应用编号则构成了应用关系即应用的架构,它有利于应用的管理、交互和追踪。应用分为产品线,子系统和应用 3 级,每一级编号为 2 位,如 100206。应用要从用户的视角出发,先有用户,然后有应用功能,这样才是以用户为中心去构建系统。

4、组织架构微调

组织架构没有最佳实践,只有适合于自已当前的选择,以下是组织架构与技术架构对齐方面的思考:

  1. 艺术与工程相关分离:UED;

  2. 软件开发与硬件相分离:运维;

  3. 技术研发与业务研发相分离:架构部;

  4. 需求,实施,验收相分离:每业务线分产品组、开发组、测试组;

  5. 开发按业务职责相分离:预订组、产品组、订单组;

  6. 专业技术委员会制:测试、产品、开发、轮流主持,设委员长;

四、总结

1、过程总结

第一步总体规划:手握地图,明确路线;

第二步数据库:建库拆库,去 join 去 SP;

第三步中间件:按需构建,先增加常用;

第四步服务:技改 = 工单,有业务价值输出;

第五步应用:拆应用,建门户 Portal,重构应用;

第六步组织架构微调:组架技术与组织架构对齐,技改之后调整;

第七步固化:框架化,自动化,管理过程工具化如 DevOps。

2、经验感悟

  • 从服务入手是错的,从数据库或中间件入手是正确的。服务属于高级阶段,方便行为的重用,是深层次优化,但太慢了;

  • 从当前问题或故障入手,要先灭火,逆向分析 dump 工具很重要;

  • 历史要尊重,早期不可做大的改动,不能过多地影响现有业务。建议只做加法,建新库和新中间件,这样就不会有太多阻力和负担;

  • 一般不能全部重建,除非系统较小,系统规模大时只能拆分后分步重构;

  • 技术并不是技改过程中最复杂的,人和事及关系才是麻烦的部分,历史问题的后面是人;

  • 每次环境和问题都不一样,要有准备脱一层皮的心态。

3、通盘无妙招

技改是大折腾,于公司于个人而言都是,小改怡情,大改伤身,我们应该避免大的技术改造,但此现象又比较常见,特别是业务发展快的创业公司。所以真正高手下棋,应该是通盘无妙招,让正确的事情很容易发生,基于自然的演化来实现技术的演进。

怎样才能通盘无妙招,系统良性长久的发展?我们需要两个力量,一个是技术,一个是业务,如果只重视业务,而很容易在技术上积劳成疾,如果完全技术驱动,则又容易忘记业务目标。所以它们应该相伴相生,共同发展,在大的技术改造实施之后,在框架和流程相对固化后,小的技术重构项目应该长期存在,这样才能良性循环,让系统进入自然演进的状态。

互动问答

问题:请问张老师如果再来一次技改你会怎么做?在你做过的技改过程中你觉得你最大的收获是什么?觉得做的不好的又是什么?

这个问题非常好,为了更好回答您的问题,我简单介绍一下本人的 3 次技改经历。我的第 1 次技改是重建,项目从 10 月份到第二年 8 月,历史 10 个月,可以说是技术成功项目失败。第 2 次技改是重构,只管技术,少管人和业务,整体效果好,可以归结为成功。第 3 次技改还是重构,既管技术又管人,且业务处于高速发展期,资源少,可以总结为技术与业务相伴相生,技术效果一般。

如果以后还有机会,自已就不再直接负责了,实在是太累,从内外各招几个架构师,并按上面的工作流程和方式,然后把握好技术与业务的关系和资源占用即可。回到具体问题:

  • 如果再来一次,我会多参考第 2 次技改经验,即 PPT 分享的过程总结;

  • 技改后的收获是脱胎换骨,技术和管理都有很大提升;

  • 不好的地方:要更多的关注业务,以及平衡好业务与技术的关系。

问题:单块应用向微服务迁移时,平滑过渡有什么技巧?如何解决分布式事务一致性呢?还有关于微服务持续交付、测试、监控(语义监控)方面有落地工具吗?

  1. 平滑过渡的技术:直面当前系统的问题,不断有价值输出,然后参考上面的过程总结,先规划,然后中间件和数据库,最后是服务和应用;

  2. 分布式事务一致性:使用替代方案,如最终一致;

  3. 落地工具:MSA 与 SOA 的治理没有本质差别,还是 DevOps/Trace/Metrics/,我们使用的是 ServiceStack/JMetrics/CenterLog/Jenkins/。

问题:如果旧有模块关联复杂,又影响现有系统性能,相关开发人员流失,不好梳理,改造有风险,重写老板不答应,该如何取舍呢?

  • 旧有模块关联复杂,又影响现有系统性能:先灭火解决当前故障, 逆向分析 dump 工具;

  • 相关开发人员流失:引进中件间,建立分享机制和学习型团队,将技改总体规划,让每个人了解自已的价值和目标;

  • 不好梳理,改造有风险:内招几个老员工成为应用架构师;

  • 重写老板不答应:有业务价值输入,技改 == 工单或项目,借助业务项目来实现技改。

  • 0
    点赞
  • 0
    评论
  • 2
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值