Monthly Archives: July 2018

使用Micrometer进行业务指标上报

[TOC]

什么是指标

指标,metric,是指对事物的度量。通常是对事物的属性进行采样测量,记录下属性的值。采样结果记录为:<指标名,时间戳,值>,例如对当前大气温度的测量<weather.temperature, 2018-07-25T00:00:00Z, 30>。但上面的记录很笼统,无法反应是哪个地区的气温,无法满足按省市区进行汇总、细化的统计分析(即多维分析)的需求。因此一般地,会把记录格式拓展为<指标名,标签集,时间戳,值>,标签集是形如city: shenzhen, district: nanshan这样的代表维度的标签名值对。值可以是整数、浮点数、字符串。大多数的指标存储系统(时序数据库)只支持数值类型的值,因此本文也只讨论数值类型。

指标上报/表示格式

时序数据库是以<指标名,标签集,时间戳,值>作为一行存储一次采样的数据。类似以下:

sys.cpu.user host=webserver01        1356998400  50
sys.cpu.user host=webserver01,cpu=0  1356998400  1
sys.cpu.user host=webserver01,cpu=1  1356998400  0
sys.cpu.user host=webserver01,cpu=2  1356998400  2
sys.cpu.user host=webserver01,cpu=3  1356998400  0
...
sys.cpu.user host=webserver01,cpu=63 1356998400  1

实际时序数据库的存储并非以上述文本格式,比上面要复杂得多。

指标定义

指标定义是指指标的元数据,用于帮助对指标数据进行解读。一般包括:
* metric_name,指标名,如web.response.size
* type,指标类型, 如Gauge/Counter等
* interval,上报(采样)间隔,
* unit_name,字符串形式单位,如Byte/Bit
* description,指标的描述
* tags,指标支持的标签(及取值范围),如app.web.request支持的tag可能包括area,host,instance分表机房、主机和实例

指标类型

根据指标监控的对象个数、监视目的和含义的不同,指标类型大体可以分为Gauge、Counter、DistributionSummary、Timer四种。

Gauge 刻度

1a7f4299-8026-4197-835f-52ad109277b1
Gauge是监视单个对象属性的当前状态值,时序数据库存储Guage指标的每次采样记录(时间,值)。

sys.load host=webserver01,cpu=0  1356998400  11
sys.load host=webserver01,cpu=0  1356998401  21
sys.load host=webserver01,cpu=0  1356998402  15
...

sys.cpu.user host=webserver01,cpu=0就是webserver01的0号cpu的负载属性。

Gauge类型的指标值是随时间连续变化的。Gauge指标的时间序列图(非采样)类似下图,可以看出数值随时间上下波动,而且是连续的。
gague_unsmapled_ts
像CPU当前负载、消息队列中当前的消息数、JVM当前使用内存大小,这些都是典型的Gauge指标。Gauge是对单个对象属性的度量。

聚合指标

聚合指标是对多个对象的度量进行过统计处理的后产生的指标。

Counter 计数器

计数就是监视对象、事件发生的次数。其中一种办法就是每次检测到一个对象就上报一个记录,那么就是如下

web.request.accept host=host1 1356998400 1
web.request.accept host=host1 1356998401 1
web.request.accept host=host1 1356998401 1
web.request.accept host=host1 1356998402 1

指标值1代表一次请求,可以看出同一时间(戳)可能有多次请求发生。通过sum()就可以计算出总数。
另一种办法是利用一个计数器,记下所有请求的累计数,再对这个计数器采样上报,这就是Counter类型。
419SoKslhU

如果把Counter的值进行上报(不清零),展示出来会得到下图的效果。
counter_ts
Counter类型的指标值是随时间单调递增,而且是跳跃式增加的。像nginx启动以来一共处理的请求数、用户认证服务中认证不通过的次数,这些都是Counter指标。

Counter指标本质上是对象个数聚合后产生的Gauge指标。Gauge数值有升有降,而Counter只数值是一直上升的。

所有聚合指标都是有意义的Gauge指标。

Counter指标又可以分为两种:每次上报后清零,每次上报后不清零。

复合指标

复合指标是对一组对象\事件的属性的复合统计。在上报和存储时是以多个聚合指标联合构成的。
以web服务处理请求为例,一个请求响应就是一个事件,响应时间就是监控的指标。如果把所有请求的响应时间上报后进行展示,会得到下图这样的散点图。
QQ20180725-134113@2x
这样的散点图无法看出某一时间段的请求数量,无法看出平均响应时间,无法知晓大于1s和小于1s的响应的数量。也就是散点图无法以一个数值来说明事件发展的整体趋势。因此我们需要对这些响应先进行预处理(统计),生产成多个新的指标,逻辑上新指标都是原指标的组成部分。

DistributionSummary

DistributionSummary是用于跟踪事件的分布情况,有多个指标组成:
* count,事件的个数,聚合指标,如响应的个数
* sum,综合,聚合指标,如响应大小的综合
* histogram,分布,聚合指标,包含le标签用于区分bucket,例如web.response.size.historgram{le=512} = 99,表示响应大小不超过512(Byte)的响应个数是99个。一般有多个bucket,如le=128,le=256,le=512,le=1024,le=+Inf等。
每个bucket展示为一条时间序列,会得到类似下面的图。
QQ20180725-184823@2x

  • percentile(quantile),百分位数,聚合指标,包含percentile标签用于区分不同的百分位,例如web.response.size.percentile{p=90) = 512,表示90%的响应大小都小于512。一般有多个percentile,如p50,p75,p90,p99。
    每个百分位展示为一条时间序列,会得到类似下面的图。
    QQ20180725-191921@2x

Timer

Timer是DistributionSummary的特化,专门用于计时类的指标,可以对记录的时间值(duration)进行单位换算。

指标怎么展示?

采样什么的图标进行展示指标要根据指标的实际意义和使用场景决定。

展示最新

即只展示最新的采样点的数值。如下图展示进程的累计运行时长
QQ20180725-161758@2x

展示历史

时间序列,是指唯一指标名+唯一标签名值组合的所有采样数据点组成的序列。一般采用折线图,横坐标表示时间,纵坐标表示数值,一个图上可以展示多条时间序列,方便对比。
key-pg-classi
通过time shift,可以再同一个图展示一个时间序列的两个时间段。下图展示今日(红色)和昨日(蓝色)的接口访问量。
QQ20180725-163952@2x

Counter与Rate

Counter类型(不清零型)的时间序列,如果不做处理,展示出来就是一个单调增长的折线,如下图所示。
counter_ts
实际生产中我们希望知道某个时间段的请求速率,因此需要做上述时间序列做转换。通常是对时间段(period)第一个数据点和最后一个数据点进行delta计算,delta值作为该时间段的rate。转换后的时间序列类似下图,可以看出每天早上7点左右请求数有明显的增加。
QQ20180725-163952@2x
时序数据库提供rate()聚合函数用户上述需求的计算,rate()实现比delta要复杂很多,要处理计数器清零、反转、数据点丢失等情况。rate可以看做是原时间序列函数的一阶求导。

什么是业务指标?

按指标的来源进行分类。纵向实现了自底向上各层级的监控,包括网络、服务器、系统层、应用层、业务层,如下图所示:
836418C4-0D04-4A6F-AD92-21DF75E819A5
* 网络监控,包括机房出口VIP是否存活,流量是否正常,机房间专线流量和质量是否正常,以及网络设备及流量是否正常等
* 服务器监控,包括服务器是否宕机,服务器硬件是否有异常等。
* 系统监控,包括CPU、内存、磁盘、网卡等各项指标。
* 应用监控,包括:端口连通性,进程存活,QPS,连接数等指标。常用的开源中间件如nginx、redis、mysql等的监控数据也算是这一类的。
* 业务监控,包括业务关心的各项指标,例如登录验证成功/失败次数、某类型业务错误的次数、订单数等,业务指标通常与业务逻辑需求有关。

怎么上报业务指标?

通过在项目中使用micrometer库进行打点,由micrometer异步上报到监控系统。下面以使用spring boot 1.5的api-gatway项目为例。

micrometer简介

  • spring boot 2的默认指标库
  • 支持指标的多维,即Tag
  • 支持上报到Atlas、Graphite、Influx、Prometheus、Datadog等监控系统,可以看做Metric版的SL4J,屏蔽了各监控系统命名差异、Counter上报方式差异、percentile计算差异。
  • 支持Gauge、Counter、DistributionSummary、Timer(LongTimer)等Metric
  • 内置了一些指标,如JVM指标
  • 提供大量针对后端监控系统的spring boot starter,配置简单,文档齐全

引入micrometer包

    <properties>
        <micrometer.version>1.0.5</micrometer.version>
    </properties>

    <dependencies>
        ...
        <dependency>
            <groupId>io.micrometer</groupId>
            <artifactId>micrometer-spring-legacy</artifactId>
            <version>${micrometer.version}</version>
        </dependency>
        <dependency>
            <groupId>com.yunzhijia.devops</groupId>
            <artifactId>micrometer-registry-falcon</artifactId>
            <version>1.0.5</version>
        </dependency>
        ...
    </dependencies>

    <repositories>
        ...
        <repository>
            <id>nexus-release</id>
            <url>http://192.168.0.22/nexus/content/repositories/cloudhub-release</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
            <releases>
                <enabled>true</enabled>
            </releases>
        </repository>
        ...
    </repositories>

io.micrometer:micrometer-spring-legacy是micrometer官方提供的spring boot 1.5的stater。com.yunzhijia.devops:micrometer-registry-falcon是自定义的micrometer插件,专门用于按格式要求上报到云之家的监控系统。

配置

application.properties里配置云之家的监控系统的上报接口的地址、上报时间间隔。

management.metrics.export.falcon.url=http://localhost:6060/api/push
management.metrics.export.falcon.hostTag=instance
management.metrics.export.falcon.step=30s

新建MicrometerConfig.java

@Configuration
public class MicrometerConfig {

    private static Logger logger = LoggerFactory.getLogger(MicrometerConfig.class);

    @Value("${spring.application.name}")
    private String appName;

    private String getInstanceIp() {
        String addr = null;
        try {
            InetAddress ip = InetAddress.getLocalHost();
            addr = ip.getHostAddress();
        } catch (UnknownHostException e) {

        }
        logger.info("ip addr: {}", addr);
        return addr;
    }

    @Bean
    MeterRegistryCustomizer<MeterRegistry> metricsCommonTags() {
        return registry -> {
            registry.config().commonTags("app", appName);
            String addr = getInstanceIp();
            {
                registry.config().commonTags("instance", addr);
            }
            registry.config().meterFilter(
                    new MeterFilter() {
                        @Override
                        public DistributionStatisticConfig configure(Meter.Id id, DistributionStatisticConfig config) {

                            if (id.getType() == Meter.Type.TIMER) {
                                return DistributionStatisticConfig.builder()
                                        .percentilesHistogram(true)
                                        .percentiles(0.5, 0.75, 0.90)
                                        .sla(Duration.ofMillis(50).toNanos(),
                                                Duration.ofMillis(100).toNanos(),
                                                Duration.ofMillis(200).toNanos(),
                                                Duration.ofMillis(500).toNanos(),
                                                Duration.ofSeconds(1).toNanos(),
                                                Duration.ofSeconds(2).toNanos(),
                                                Duration.ofSeconds(5).toNanos(),
                                                Duration.ofSeconds(10).toNanos())
                                        .minimumExpectedValue(Duration.ofMillis(1).toNanos())
                                        .maximumExpectedValue(Duration.ofSeconds(10).toNanos())
                                        .build()
                                        .merge(config);
                            } else {
                                return config;
                            }
                        }
                    });
        };
    }

    @Bean(name = "requestCounter")
    Counter requestCounter() {
        return Counter.builder(MetricDef.C_REQUEST)
                .register(Metrics.globalRegistry);
    }

    @Bean(name = "methodTypeRpcCounter")
    Counter methodTypeRpcCounter() {
        return Counter.builder(MetricDef.C_METHOD_TYPE).tags("type", "rpc")
                .register(Metrics.globalRegistry);
    }

    @Bean(name = "methodTypeHttpCounter")
    Counter methodTypeHttpCounter() {
        return Counter.builder(MetricDef.C_METHOD_TYPE).tags("type", "http")
                .register(Metrics.globalRegistry);
    }

上述代码配置了
1. 所有Metric的公共标签app和instance,app表示来自哪个服务,instance表示来自服务的实例(所有服务端口不重叠,故用IP表示实例)。
2. 对Timer进行设置,percentilesHistogram(true)表示上报分布的所有bucket;统计和上报的百分位数包括0.5,0.75,0.90;sla()额外添加了[50ms,100ms,200ms,500ms,1s,2s,5s,10s]这些bucket;并限定统计的耗时范围在0至10s之间。
3. 定义了3个不同的Counter,总请求数,rpc和http类型接口的请求数。定义为bean,之后利用Spring注入的方式可以减少Counter对象的查找耗时。

打点统计

在相应的业务代码文件引入counter,并打点。

@Component
public class MethodExistedPreFilter extends ZuulFilter{
    ...

    @Autowired
    @Qualifier("methodTypeRpcCounter")
    Counter methodTypeRpcCounter;

    @Autowired
    @Qualifier("methodTypeHttpCounter")
    Counter methodTypeHttpCounter;

    ...

    @Override
    public Object run() {

        ...
        if (StringUtils.equalsIgnoreCase(routeVo.getProtocol(), "rpc")) {
            methodTypeRpcCounter.increment();

        } else {
            methodTypeHttpCounter.increment();
        }
        ...
    }

    ...

}

QQ20180725-194449@2x

什么不该/不需要上报?

  • 内部服务之间的相互调用的次数、耗时不需要上报。因为已经由PinPoint统一进行采集和上报了,指标监控系统回去PinPoint拉取这些数据。
  • 如果tag值是无限的,不能上报。比如你可能想按appId统计接口调用次数,don’t do that.

mac使用virtualbox和cloudera manager搭建大数据集群

装备集群

利用virtualbox,创建4个guest虚拟机,一个作为管理机,其他3个作为worker。虚拟机网络设置为NAT + HostOnly。NAT保证虚拟机能访问WAN,但是虚拟机之间无法通信。Host Only保证Mac和4个虚拟机之间处在一个网络192.168.56.0/24,可以相互通信。

  • mac 192.168.56.1 安装virtualbox
  • master 192.168.56.10 虚拟机,安装cm server
  • app1 192.168.56.11 虚拟机,安装cm agent
  • app2 192.168.56.12 虚拟机,安装cm agent
  • app3 192.168.56.13 虚拟机,安装cm agent

安装cloudera manager

在master上运行

# wget http://archive.cloudera.com/cm5/installer/5.15.0/cloudera-manager-installer.bin
# chmod u+x cloudera-manager-installer.bin
# ./cloudera-manager-installer.bin

有些包很大,可能会卡很久,或者下载很慢。可以手动下载到/var/cache/yum/x86_64/6/cloudera-manager/packages,然后重新运行./cloudera-manager-installer.bin

最好先手动下载
– oracle-j2sdk1.7-1.7.0+update67-1.x86_64.rpm
– cloudera-manager-agent-5.15.0-1.cm5150.p0.62.el6.x86_64.rpm
– cloudera-manager-server-5.15.0-1.cm5150.p0.62.el6.x86_64.rpm
– cloudera-manager-daemons-5.15.0-1.cm5150.p0.62.el6.x86_64.rpm
然后cp/scp到四个虚拟机的/var/cache/yum/x86_64/6/cloudera-manager/packages下。

下载
– CDH-5.15.0-1.cdh5.15.0.p0.21-el6.parcel
– CDH-5.15.0-1.cdh5.15.0.p0.21-el6.parcel.sha1

扔到master的/opt/cloudera/parcel-repo

配置集群

访问http://192.168.56.10:7180/ ,账号admin,密码admin,选择一些配置选项,加入机器,填写ssh用户名密码(所有机器使用同样账号密码,关闭iptables和selinux)。然后就开始安装jdk、cm agent。

使用maven解决微服务开发中的一些日常问题

1.多模块

微服务是按业务边界划分的,通常包含一个可供调用的服务和一些异步后台进程、定时任务,这些运行实际不同但又紧密的关联的进程一起完成这个业务的数据处理。因此可以把一个微服务拆分为多个模块,如api定义模块(以dubbo这类rpc微服务为例,通常会把服务接口构建为一个jar包)、服务实现模块、定时任务模块。而服务实现和定时任务会共用一些工具类代码,又可以提取为一个模块。
在工作经历中,见过一些人把服务api放在一个项目里,服务实现放在一个项目里,自动任务放在一个独立的项目里,且三个项目都是独立的git项目。每个模块使用独立的pom.xml,则很多共同的依赖和插件配置就需要写多次,在修改版本时难免出现不一致。
其实利用maven的「继承与聚合」功能,是一种更优雅的多模块组织方式。既可以把多个模块放在一个目录里管理(一个git项目,方便查看完整的变更历史),又解决多个模块依赖和插件管理问题,同时还能解决一条命令同时构建多个模块的问题。

以用户服务为例,项目结构可以组织成如下形式:

user
├── pom.xml
├── user-api
│   ├── pom.xml
│   └── src
│       └── main
│           └── java
└── user-service
    ├── pom.xml
    └── src
        └── main
            ├── java
            └── resources

user目录作为是一个父模块,而user-api和user-service是user模块的子模块,子模块和父模块以嵌套方式组织。
user/pom.xml的内容如下

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>wingyiu</groupId>
    <artifactId>user</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <packaging>pom</packaging>
    <name>User Parent</name>

    <modules>
        <module>user-api</module>
        <module>user-service</module>
    </modules>

    <properties>
        <java.version>1.8</java.version>
        <java.source.version>1.8</java.source.version>
        <java.target.version>1.8</java.target.version>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <spring-boot.version>1.5.14.RELEASE</spring-boot.version>
        <dubbo.version>2.6.2</dubbo.version>
        <dubbo.starter.version>0.1.1</dubbo.starter.version>
        <zkclient.version>0.2</zkclient.version>
        <zookeeper.version>3.4.9</zookeeper.version>
        <curator-framework.version>2.12.0</curator-framework.version>
        <!-- Build args -->
        <argline>-server -Xms256m -Xmx512m -XX:PermSize=64m -XX:MaxPermSize=128m -Dfile.encoding=UTF-8
-Djava.net.preferIPv4Stack=true
        </argline>
        <arguments/>

        <!-- Maven plugins -->
        <maven-jar-plugin.version>3.0.2</maven-jar-plugin.version>
        <maven-compiler-plugin.version>3.6.0</maven-compiler-plugin.version>
        <maven-source-plugin.version>3.0.1</maven-source-plugin.version>
        <maven-jacoco-plugin.version>0.8.1</maven-jacoco-plugin.version>
        <maven-gpg-plugin.version>1.5</maven-gpg-plugin.version>
        <apache-rat-plugin.version>0.12</apache-rat-plugin.version>
        <maven-release-plugin.version>2.5.3</maven-release-plugin.version>
        <maven-surefire-plugin.version>2.19.1</maven-surefire-plugin.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <!-- Spring Boot -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot</artifactId>
                <version>${spring-boot.version}</version>
            </dependency>
            ...省略
        </dependencies>
    </dependencyManagement>

    <repositories>
        <repository>
            <id>nexus-snapshot</id>
            <url>http://192.168.0.22/nexus/content/repositories/gayhub-snapshot</url>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
            <releases>
                <enabled>false</enabled>
            </releases>
        </repository>
        <repository>
            <id>nexus-release</id>
            <url>http://192.168.0.22/nexus/content/repositories/gayhub-release</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
            <releases>
                <enabled>true</enabled>
            </releases>
        </repository>
        <!-- Repositories to allow snapshot and milestone BOM imports during development.
            This section is stripped by the flatten plugin during install/deploy. -->
        <repository>
            <id>central</id>
            <url>https://repo.maven.apache.org/maven2</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
        ...省略
    </repositories>

    <pluginRepositories>
        <pluginRepository>
            <id>central</id>
            <url>https://repo.maven.apache.org/maven2</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </pluginRepository>
        ...省略
    </pluginRepositories>

</project>

其中
<packaging>pom</packaging>
表明这是一个聚合模块,

<modules>
    <module>user-api</module>
    <module>user-service</module>
</modules>

modules元素里声明了两个聚合模块。
从聚合模块运行man clean install时,maven会首先解析觉和模块的POM,分析要构建的模块,并计算出一个反应堆构建顺序,然后依次构建各个模块。即实现了一次构建多个模块。
properties原理声明了共用的一次属性,子模块可以通过${spring-boot.version}这样的形式引用。

<dependencyManagement>
    <dependencies>
    </dependencies>
</dependencyManagement>

声明了共同的依赖,子模块可以继承这些依赖配置,dependencyManagement下生命的依赖不会实际引入,不过它能约束子模块dependencies下的依赖使用。同理

<build>
    <pluginManagement>
        <plugins>

帮助子模块进行插件管理。

子模块user-api的pom.xml内容如下


<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <groupId>wingyiu</groupId>
        <artifactId>user</artifactId>
        <version>1.0.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>user-api</artifactId>
    <packaging>jar</packaging>
</project>

其中, 元素指明了其父模块为user。不难发现user的POM,既是聚合POM,又是父POM,实际项目中经常如此。子POM并没有声明groupId和version,因为子模块隐式地从父模块继承了这两个元素。由于user-api仅包含以下interface定义,DO定义,没有依赖,因此pom文件没有任何dependency。模块打包方式为默认的jar。


<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <artifactId>user</artifactId>
        <groupId>wingyiu</groupId>
        <version>1.0.0-SNAPSHOT</version>
        <relativePath>../pom.xml</relativePath>
    </parent>

    <artifactId>user-service</artifactId>
    <packaging>jar</packaging>

    <properties>
        <micrometer.version>1.0.5</micrometer.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot</artifactId>
        </dependency>

        <dependency>
            <groupId>io.micrometer</groupId>
            <artifactId>micrometer-registry-prometheus</artifactId>
            <version>${micrometer.version}</version>
        </dependency>

    </dependencies>
</project>

同理user-service的pom中parent指明了父模块为user。特别的

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot</artifactId>
        </dependency>

并没有制定version,还剩去了依赖范围。这是因为完整的依赖声明已经包含在父POM中,子模块只需配置groupId和artifactId就能获得对应的依赖信息。这种依赖管理机制似乎不能减少太多pom配置,但是能够统一项目范围的依赖版本。有父模块统一声明version,子模块不声明version,就不会发生过个子模块使用的依赖版本不一致的情况,这可以帮助降低依赖冲突的几率。

2.只构建并发布api包

为了加快项目进度,通常服务提供方和调用方会先定好接口定义(包括接口名,参数),调用方先以约定好的接口进行逻辑编写,等提供方正在实现好之后再进行联调。在这种情况下,就会要求先发布jar包。
此时可以利用maven的反应堆裁剪功能。反应堆是指所有模块组成的一个构建结构。对于多模块项目,反应堆包含了模块之间继承与依赖关系,从而能够自动计算出合理的模块构建顺序。模块间的依赖关系会将反应堆构成一个有向非循环图(DAG)。
默认情况,在聚合模块执行mvn clean install会执行整个反应堆,maven提供了很多命令行选项支持裁剪反应堆:
– -am, –also-make,同时构建所列模块的依赖模块
– -amd, –also-make-dependents,同时构建依赖于所列模块的模块
– -pl,–projects 构建指定的模块,模块间用逗号分隔
– -rf, –resume-from 从指定的模块恢复反应堆

因此要单独构建api模块,可以运行如下命令
mvn clean install -pl user-api

3.开发中的接口不断地改变

这种情形很常见,一个开发中的接口,参数不断的调整,此时调用方也要不断的修改,即调用方需要不断的更新依赖服务的api jar包。一般地,maven把依赖从远程仓库拉下来后会进行缓存。因此经常会出现要手动删除本地缓存包,或者增加版本号,但服务还在开发不断增加版本号显然不合适。
maven提供了更加简单有效的解决方法:SNAPSHOT,快照版本。当依赖版本为快照版本的时候,maven会自动找到最新的快照。即每次都会去仓库拉去元数据,计算真实的时间戳格式版本号,然后拉取对应的jar,保证本地使用到最新的jar。

注:默认情况下,maven每天检查一次更新(有仓库udatePolicy控制),用户也可以用-U参数强制检查更新,如mvn clean install -U

因此,快发中,我们可以把api模块(上例user-api)的版本号设为1.0.0-SNAPSHOT

发布过程中,maven自动为构件打上时间戳,如1.0.0-20180720.221414-13。

4.更新所有模块的版本号

当旧版本a.b.c已经发布,要进行下一轮迭代时,要把父模块版本号改为a.b.d-SNAPSHOT,测试通过后正式发布,要改为正式版本号a.b.d。由于每个子模块的<parent><version>都要于父POM一致,手动修改每个pom.xml显然很容易出错。

可以使用org.codehaus.mojo:versions-maven-plugin插件

5.使用企业内部私有maven仓库

假设以nexus搭建私有仓库,snapshot版本和正式版本的构件发布到不同仓库,可以在父POM中配置repository

<repositories>
        <repository>
            <id>nexus-snapshot</id>
            <url>http://192.168.0.22/nexus/content/repositories/gayhub-snapshot</url>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
            <releases>
                <enabled>false</enabled>
            </releases>
        </repository>
        <repository>
            <id>nexus-release</id>
            <url>http://192.168.0.22/nexus/content/repositories/gayhub-release</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
            <releases>
                <enabled>true</enabled>
            </releases>
        </repository>
</repositories>

<repository><id>是和$MAVEN/settings.xml中的<server><id>对应的,<server>配置repository的账号和密码。

出于规范考虑,开发人员可以自由提交snapshot版本构件,正式版本构件只能在测试和审核后才能由发布系统进行提交。因此release仓库的密码不宜开放给开发人员,开发人员只能得到snapshot仓库的账号密码。

6.快速开发新的微服务

可以利用maven的archtype功能。首先根据代码规范,编写一个包含基本的、可复用逻辑的微服务,以此作为模板,把模板项目代码中的一些类名、变量名、文件名使用模板变量替换,然后编写自定义archtype 插件。利用此插件,运行时输入模板变量值就可以生产新的微服务代码了。

7.接口文档编写与更新

参考swagger-maven-plugin,swagger可以利用maven插件,解析接口的类名、方法名、参数等,生成一个可以部署的接口文档站点。

进一步的,代码可以利用JSR 303 Bean Validation的注解,标注参数的要求。插件解析这些注解,生产的文档包含完整的参数要求信息,如是否必填、类型、最大最小值、长度、可选枚举值等。

不同于swagger-maven-plugin,我们也可以把解析的接口信息统一上报到接口文档管理系统,接口管理系统保留每个接口的url、参数要求、所属服务及版本、变更历史,提供分组和搜索功能。利用插件自动解析和生产接口文档,降低手动维护接口文档的痛苦和出错机会。

8.接口发布到网关

原理类似利用maven插件接口接口生成接口文档,只不过这里是把接口信息提交给网关的接口配置管理中心,降低维护网关接口配置的痛苦