maven project

Maven项目实践。

组织结构

1
2
3
4
project-parent
- project-foo
- project-bar
pom.xml

项目包含一个父项目,所有子模块parent指定为该项目。

DependencyManagement

  • 统一依赖版本
  • 方便依赖版本变更

revision

项目版本定义在父项目中的properties中,变量名为revision,子模块版本使用${revision}变量,并在父项目引用 flatten-maven-plugin 插件,更多可参考这里。这样,整个项目的版本号可通过revision变量来设置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>flatten-maven-plugin</artifactId>
<version>1.2.5</version>
<configuration>
<updatePomFile>true</updatePomFile>
<flattenMode>resolveCiFriendliesOnly</flattenMode>
</configuration>
<executions>
<execution>
<id>flatten</id>
<phase>process-resources</phase>
<goals>
<goal>flatten</goal>
</goals>
</execution>
<execution>
<id>flatten.clean</id>
<phase>clean</phase>
<goals>
<goal>clean</goal>
</goals>
</execution>
</executions>
</plugin>

maven settings

Minimal settings.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0
http://maven.apache.org/xsd/settings-1.0.0.xsd">
<localRepository/>
<interactiveMode/>
<usePluginRegistry/>
<offline/>
<pluginGroups/>
<servers/>
<mirrors/>
<proxies/>
<profiles/>
<activeProfiles/>
</settings>

aliyun

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0
http://maven.apache.org/xsd/settings-1.0.0.xsd">
<localRepository/>
<interactiveMode/>
<usePluginRegistry/>
<offline/>
<pluginGroups/>
<servers/>
<mirrors>
<mirror>
<id>aliyunmaven</id>
<mirrorOf>*</mirrorOf>
<name>阿里云公共仓库</name>
<url>https://maven.aliyun.com/repository/public</url>
</mirror>
</mirrors>
<proxies/>
<profiles/>
<activeProfiles/>
</settings>

合并依赖包内 META-INF

打包成单体 jar 包

在很多场景下,需要将程序及依赖打包为单个 jar 包,方便部署、运行。打包成单体 jar 包,有多个插件可选,比如 maven-shade-pluginmaven-assembly-plugin

合并 META-INF/services

以 gRPC 为例,我们有一个依赖包,里面自定义了 NameResolver。

1
2
# META-INF/services/io.grpc.NameResolverProvider
pub.wii.cook.governance.nameresolver.DynamicNameResolverProvider

如果我们恰巧引用了另外一个依赖包(jetcd-core),里面定义了其他的 NameResolver。

1
2
3
# META-INF/services/io.grpc.NameResolverProvider
io.etcd.jetcd.resolver.DnsSrvResolverProvider
io.etcd.jetcd.resolver.IPResolverProvider

那么,在我们打包成单体 jar 时,将两个依赖包内的 META-INF/services/io.grpc.NameResolverProvider 合并为一个,这样打包后的程序在运行时才可以通过 SPI 机制,找到所有的扩展。

1
2
3
4
# 期望的打包后 META-INF/services/io.grpc.NameResolverProvider
io.etcd.jetcd.resolver.DnsSrvResolverProvider
io.etcd.jetcd.resolver.IPResolverProvider
pub.wii.cook.governance.nameresolver.DynamicNameResolverProvider

无论 maven-shade-plugin 还是 maven-assembly-plugin,默认配置都不支持合并,需要单独配置。

对于 maven-shade-plugin ,需要添加 transformer。

1
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>

对于 maven-assembly-plugin ,需要添加组装描述文件 assembly.xml,并启用 handler metaInf-services

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<!-- assembly.xml -->
<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.1.0
http://maven.apache.org/xsd/assembly-2.1.0.xsd">
<id>jar-with-dependencies</id>
<formats>
<format>jar</format>
</formats>

<includeBaseDirectory>false</includeBaseDirectory>
<dependencySets>
<dependencySet>
<outputDirectory>/</outputDirectory>
<useProjectArtifact>true</useProjectArtifact>
<unpack>true</unpack>
<scope>runtime</scope>
</dependencySet>
</dependencySets>

<containerDescriptorHandlers>
<containerDescriptorHandler>
<handlerName>metaInf-services</handlerName>
</containerDescriptorHandler>
</containerDescriptorHandlers>
</assembly>

配置示例

maven-shade-pluginmaven-assembly-plugin 配置,二选一。

maven-shade-plugin

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.4</version>
<configuration>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>pub.wii.cook.Main</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>

maven-assembly-plugin

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.3.0</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
<configuration>
<descriptors>
<descriptor>src/assemble/assembly.xml</descriptor>
</descriptors>
<archive>
<manifest>
<mainClass>pub.wii.cook.Main</mainClass>
</manifest>
</archive>
</configuration>
</execution>
</executions>
</plugin>

assembly.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.1.0
http://maven.apache.org/xsd/assembly-2.1.0.xsd">
<id>jar-with-dependencies</id>
<formats>
<format>jar</format>
</formats>

<includeBaseDirectory>false</includeBaseDirectory>
<dependencySets>
<dependencySet>
<outputDirectory>/</outputDirectory>
<useProjectArtifact>true</useProjectArtifact>
<unpack>true</unpack>
<scope>runtime</scope>
</dependencySet>
</dependencySets>

<containerDescriptorHandlers>
<containerDescriptorHandler>
<handlerName>metaInf-services</handlerName>
</containerDescriptorHandler>
</containerDescriptorHandlers>
</assembly>

参考

typora

插入图片

image-20210818230821747

  • 选择「复制到指定路径」+ 「./${filename}
  • 勾选「优先使用相对路径」

这样,不管是复制文件还是从剪切板复制图片,都会保存到文件同目录、同文件名下,引用的时候使用相对路径,这样,在上传到 git 仓库时,可以同步上传图片,并保持引用关系。

caddy usage

文档

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
localhost {
respond "caddy server"
}

http://git.example.com {
reverse_proxy 192.168.1.2:8000
}

http://doc.example.com:85 {
reverse_proxy 192.168.1.3:8000 {
header_down Location "^(http://doc.example.com)(.*)$" "$1:85$2"
}
}

注意

  • 如果监听端口是 80 或不设置端口(默认监听 80),那么在处理请求时,看做 http 协议请求
  • 如果监听端口是非 80,那么请求被当做 https 协议请求
  • 显式指定 http 协议,那么请求看做 http 协议处理
1
2
3
example.com { ... }            # http 协议
example.com:81 { ... } # https 协议
http://example.com:81 { ... } # http 协议

问题

跳转时端口丢失

原因是服务再返回请求设置 header 的 Location 字段时,没有把端口加进去,导致的现象是访问 http://doc.example.com:85 服务跳转到 http://doc.example.com/index,由于端口丢失,导致无法访问。

从两个思路解决问题。

  • 服务端,在返回时设置 header 的 Location 把 port 加上(http://doc.example.com:85/index
  • 代理服务器处理,处理方式是替换 response header 的 Location,加上端口

下面是 Caddy 的配置示例。

1
header_down Location "^(http://doc.example.com)(.*)$" "$1:85$2"
  • header_down,修改 response 的 header。相反,header_up 是修改请求 header
  • Location,response header 字段
  • "^(http://doc.example.com)(.*)$", 匹配域名和 path
  • "$1:85$2",在域名和 path 中间加端口

charles usage

安装 & 配置

下载 & 安装 charles

点击跳转

配置

chrome

下载插件 SwitchProxy,添加 charles 代理,并启用。

image-20210817102317584

转发 localhost

添加 host

/etc/hosts 中添加 127.0.0.1 charles.prx

image-20210817102356555

charles 中添加 Map Remote

image-20210817103825440

效果

image-20210817103752293

手机抓包

Charles 设置

Proxy 设置

image-20210818223551834

  • 打开「Proxy -> SSL Proxying Settings」

image-20210818223804406

  • 勾选「Enable SSL Proxying」
  • 添加 Location
    • Host:*
    • Port:*

image-20210818223918941

证书设置

image-20210818224040600

  • 点击 「Help -> Install Charles Root Certificate」
    • 安装证书,位置选择系统(不要选择 iCloud)
  • 在密钥串访问(Keychain Access)程序中修改 Charles 证书的信任配置
    • 改为始终信任(Always Trust)

image-20210818224514316

iPhone

  • 保证手机和电脑在统一局域网,或手机可通过 ip 访问电脑

设置代理

打开 iPhone 「设置 -> 无线局域网 -> 详情(已连接Wifi后的蓝色感叹号)-> 配置代理」,配置代理,ip 为开启 charles 的电脑 ip,port 为 charles 启动的代理端口,默认为 8888。

image-20210818225004433

设置证书

打开「Charles -> Help 」

image-20210818224708417

会有如下提示。

image-20210818224739823

在 safari 中访问网址 chls.pro/ssl ,允许下载证书,成功后会提示去设置中安装描述符。

image-20210818225555306

打开「设置 -> 通用 -> 描述文件 -> Charles Proxy CA」,安装证书。

image-20210818225625748

最后一步,启用证书。打开「设置 -> 通用 -> 关于手机 -> 证书信任设置 -> Charles Proxy CA」,启用它。

image-20210818225810942

brew

安装卸载

安装

使用清华开源站仓库安装参考这里

1
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

卸载

1
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

初始化

1
brew tap homebrew/cask-versions

软件

jdk

1
2
3
4
5
6
7
8
# openjdk
brew search jdk
brew install openjdk@8
# adoptopenjdk
brew tap adoptopenjdk/openjdk
brew search adoptopenjdk
brew install adoptopenjdk8
brew install adoptopenjdk # 最新 jdk

管理多个版本 jdk 参考这里

1
2
3
4
5
6
# 安装 jenv
brew install jenv
# 配置 jenv
echo 'eval "$(jenv init -)"' >> ~/.bash_profile
# 添加需要管理的的 jdk
jenv add /Library/Java/JavaVirtualMachines/adoptopenjdk-8.jdk/Contents/Home

gapis

配置格式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
{
// Load balancing policy name.
// Currently, the only selectable client-side policy provided with gRPC
// is 'round_robin', but third parties may add their own policies.
// This field is optional; if unset, the default behavior is to pick
// the first available backend.
// If the policy name is set via the client API, that value overrides
// the value specified here.
//
// Note that if the resolver returns at least one balancer address (as
// opposed to backend addresses), gRPC will use grpclb (see
// https://github.com/grpc/grpc/blob/master/doc/load-balancing.md),
// regardless of what LB policy is requested either here or via the
// client API.
'loadBalancingPolicy': string,

// Per-method configuration. Optional.
'methodConfig': [
{
// The names of the methods to which this method config applies. There
// must be at least one name. Each name entry must be unique across the
// entire service config. If the 'method' field is empty, then this
// method config specifies the defaults for all methods for the specified
// service.
//
// For example, let's say that the service config contains the following
// method config entries:
//
// 'methodConfig': [
// { 'name': [ { 'service': 'MyService' } ] ... },
// { 'name': [ { 'service': 'MyService', 'method': 'Foo' } ] ... }
// ]
//
// For a request for MyService/Foo, we will use the second entry, because
// it exactly matches the service and method name.
// For a request for MyService/Bar, we will use the first entry, because
// it provides the default for all methods of MyService.
'name': [
{
// RPC service name. Required.
// If using gRPC with protobuf as the IDL, then this will be of
// the form "pkg.service_name", where "pkg" is the package name
// defined in the proto file.
'service': string,

// RPC method name. Optional (see above).
'method': string,
}
],

// Whether RPCs sent to this method should wait until the connection is
// ready by default. If false, the RPC will abort immediately if there
// is a transient failure connecting to the server. Otherwise, gRPC will
// attempt to connect until the deadline is exceeded.
//
// The value specified via the gRPC client API will override the value
// set here. However, note that setting the value in the client API will
// also affect transient errors encountered during name resolution,
// which cannot be caught by the value here, since the service config
// is obtained by the gRPC client via name resolution.
'waitForReady': bool,

// The default timeout in seconds for RPCs sent to this method. This can
// be overridden in code. If no reply is received in the specified amount
// of time, the request is aborted and a deadline-exceeded error status
// is returned to the caller.
//
// The actual deadline used will be the minimum of the value specified
// here and the value set by the application via the gRPC client API.
// If either one is not set, then the other will be used.
// If neither is set, then the request has no deadline.
//
// The format of the value is that of the 'Duration' type defined here:
// https://developers.google.com/protocol-buffers/docs/proto3#json
'timeout': string,

// The maximum allowed payload size for an individual request or object
// in a stream (client->server) in bytes. The size which is measured is
// the serialized, uncompressed payload in bytes. This applies both
// to streaming and non-streaming requests.
//
// The actual value used is the minimum of the value specified here and
// the value set by the application via the gRPC client API.
// If either one is not set, then the other will be used.
// If neither is set, then the built-in default is used.
//
// If a client attempts to send an object larger than this value, it
// will not be sent and the client will see an error.
// Note that 0 is a valid value, meaning that the request message must
// be empty.
'maxRequestMessageBytes': number,

// The maximum allowed payload size for an individual response or object
// in a stream (server->client) in bytes. The size which is measured is
// the serialized, uncompressed payload in bytes. This applies both
// to streaming and non-streaming requests.
//
// The actual value used is the minimum of the value specified here and
// the value set by the application via the gRPC client API.
// If either one is not set, then the other will be used.
// If neither is set, then the built-in default is used.
//
// If a server attempts to send an object larger than this value, it
// will not be sent, and the client will see an error.
// Note that 0 is a valid value, meaning that the response message must
// be empty.
'maxResponseMessageBytes': number
}
]
}

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
{
"methodConfig": [
{
"name": [
{
"service": "google.analytics.admin.v1alpha.AnalyticsAdminService"
}
],
"timeout": "60s",
"retryPolicy": {
"maxAttempts": 5,
"initialBackoff": "1s",
"maxBackoff": "60s",
"backoffMultiplier": 1.3,
"retryableStatusCodes": [
"UNAVAILABLE",
"UNKNOWN"
]
}
},
{
"name": [
{
"service": "google.analytics.admin.v1alpha.AnalyticsAdminService",
"method": "GetAccount"
},
{
"service": "google.analytics.admin.v1alpha.AnalyticsAdminService",
"method": "ListAccounts"
},
{
"service": "google.analytics.admin.v1alpha.AnalyticsAdminService",
"method": "DeleteAccount"
}
],
"timeout": "60s"
}
]
}

API - 规范

目标

接口设计应以一些目标为导向,这些目标包含且不限于,易读、易懂、易用、清晰(意义明确,不易误用)、易维护、易扩展、功能强大且满足需求。要达到如上目标,在设计 API 时应考虑如下细节。

  • 文档
  • 控制器和动作命名公约
  • 稳定性且一致性
  • 灵活性
  • 安全性
  • 有效性校验
  • HTTP 状态码
  • 帮助页面
  • 日志

从形式来讲,API 不仅有 HTTP/WEB,还有 gRPC、Thrift。

视角

对于思考设计良好的接口,可从下面两个视角着手。

  • API 实现视角
    • 这个服务需要做什么
    • 这个服务需要提供什么
    • 怎么才能使 API 更通用 (输入、输出、扩展性)
  • API 使用者视角
    • 使用者如何继承我们的 API
    • 如何让使用者更灵活地向 API 提供数据并获取输出
    • 如何让使用者花更少的时间获得他们所需要的信息

规范

分类

Errors(错误)

Errors 是指客户端向服务发送错误数据,服务正确拒绝该数据。Errors 不会影响服务可用性

Faults(故障)

Faults 是指服务对合法的请求无法正常返回结果。Faults 会影响服务可用性。

由于限流或配额失败(事先设定)引起的调用失败,不能算作 faults。如果是由于服务自我保护造成的请求失败,算作 faults,比如快速失败策略。

Latency(延迟)

Latency 是指完成特定 API 调用消耗的时间,尽可能接近客户端计量。对于长操作请求,该指标记为初始请求耗时。

Time to complete

对于暴露长操作的服务,必须对这些指标跟踪 “Time to complete” 指标。

Long running API faults

对于长操作,初始请求和检索请求都可能正常,但如果最终操作失败,必须汇总至总体的可用性指标中。

客户端规范

忽略原则

忽略服务端返回的多余字段。

可变顺序规则

忽略服务端返回数据字段的顺序。

一致性基础

URL 结构

URL 应该易读且易构造。

URL 长度

HTTP 1.1 RFC 7230 并未定义 URL 长度限制,如果服务接收到的请求 URL 长度大于其定义的限制长度,应返回 414 状态码。

所以,对于长度大于 2083 个字符的 URL ,应考虑服务是否可以接受。

支持的方法

方法 描述 是否幂等
GET 返回当前对象值
PUT 替换或创建对象
DELETE 删除对象
POST 根据提供的数据创建新对象,或提交命令
HEAD 为 GET 响应返回对象的元数据,资源支持 GET 请求,也应支持 HEAD 请求
PATCH 对对象应用重要的更新
OPTIONS 获取请求的信息

自定义 Headers

跟定 API 的基本操作,不能指定自定义 Headers。

命名公约

  • 请求和返回值参数,不应使用缩写(比如 msg)

response

返回值数据结构应分级,不应将业务数据放在第一级。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 异常
{
"status": 1000,
"errors": {
"target": "ContactInfo",
"message": "Multiple errors in ContactInfo data",
"details": [
{
"code": "NullValue",
"target": "PhoneNumber",
"message": "Phone number must not be null"
},
{
"code": "MalformedValue",
"target": "Address",
"message": "Address is not valid"
}
]
}
}

# 正常
{
"status": 0,
"data": {
"contacts": [
{
"name": "cmcc",
"phone": "10086"
}
]
}
}

对于 code,应进行细致的划分,比如。

状态码 说明 HTTP Status Code
0 请求正常返回 200
1000+ 请求错误(参数、数据不存在等) 400
2000+ 元数据读取异常(不存在、格式异常) 200
3000+ 处理时异常 500
4000+ 数据写入时异常 500
5000+ 未知服务异常 500

对于 message 格式也应进行统一(避免英文/中文混用,有的 message 为英文,有的为中文),务必保证 code 不为 0 时返回有效 message。

QA

有一些设计细节,很难判定那种方式实现比较好,这里做下讨论。

多个功能使用一个接口 VS 多个接口

对于功能相似的多个接口,是使用一个接口 + 字段标识,还是拆分成多个接口。

参考

top

操作

1
2
3
4
5
6
c  展示详细 COMMAND
m 切换内存展示
e 切换进程内存展示单位
E 切换顶部内存信息的单位
P 按 CPU 排序
W 保存当前配置到用户目录 (~/.toprc OR ~/.config/prop/toprc OR ...)

排序

指定队列排序

  • 输入命令 top
  • 输入 o
    • 再输入列名(大小写不敏感)
  • 输入 O
    • 再输入列名(大小写不敏感),设置第二排序键

指定 pid

指定单个

1
$ top -p <pid>      # macos 使用 -pid 参数

指定多个

1
$ top -p `pgrep -d ',' python3`  # 非 macos

指定用户

1
$ top -u wii       # 只显示指定用户进程, macos 使用 -U 参数