项目初始化建立
This commit is contained in:
87
source/_posts/Kubernetes基础术语.md
Normal file
87
source/_posts/Kubernetes基础术语.md
Normal file
@@ -0,0 +1,87 @@
|
||||
---
|
||||
title: Kubernetes基础术语
|
||||
date: 2021-04-01 17:03:05
|
||||
tags: [DevOps, Kubernetes]
|
||||
categories:
|
||||
- [DevOps, Kubernetes]
|
||||
---
|
||||
要完成一个服务在Kubernetes上的部署,必须先了解以下关于Kubernetes中的知识点,这些知识点决定着一个应用在Kubernetes上的部署行为。
|
||||
<!-- more -->
|
||||
|
||||
@[toc]
|
||||
|
||||
## 术语集
|
||||
|
||||
* **Master**,Kubernetes中的集群控制节点,所有的集群控制命令都是在Master节点上运行。
|
||||
* **Node**,Kubernetes集群中的实体机器或者虚拟机,是Kubernetes中的工作负载节点。
|
||||
* **Namespace**,对一组资源和对象的抽象集合,通常用来在Kubernetes内部划分不同的项目组。默认创建的资源都是隶属于`default`命名空间的。
|
||||
* **Pod**,Kubernetes中最基础的服务单元,其中包含至少一个容器。
|
||||
* **Label**,标签是一个键值对,每个资源可以有任意数量的标签
|
||||
* **Replication Controller**,即**RC**,用于控制Pod部署的副本控制器,现在已经建议使用**Deployment**替代。
|
||||
* **Replica Set**,最新一代的Pod副本控制器。与RC相比提供了更新的选择器支持。
|
||||
* **Deployment**,用于控制Kubernetes集群中Pod的编排,内部使用**Replica Set**实现。
|
||||
* **StatefulSet**,运行一个或者多个能够被跟踪应用状态的Pod,适用于Pod需要把数据做永久存储的需求。StatefulSet中的Pod可以与PersistentVolume对应,来使得Pod的状态得以保存。
|
||||
* **DaemonSet**,用于在Node上提供支持设施的Pod,集群会自动在符合条件的每一个Node上运行一个Pod。
|
||||
* **Service**,定义Kubernetes上一个服务的入口,对来自客户端的访问进行请求转发和负载均衡控制。Service都拥有一个唯一的名称,拥有一个虚拟IP,并且会被映射在一组Pod上。
|
||||
* **Endpoint**,Kubernates中用来记录一个Service所对应的所有Pod的访问地址,即所有Pod的IP地址和端口。只有Service中配置了Selector,才会自动创建Endpoint。
|
||||
|
||||
## 网络IP类型
|
||||
|
||||
在Kubernetes中,Pod、Node和Cluster这三层元素就会引入以下三个IP系统。
|
||||
|
||||
* **Node IP**,这是Kubernetes集群中每个Node的物理网卡地址。
|
||||
* **Pod IP**,每个Pod都在Kubernetes集群中拥有一个独一无二的IP地址,可以用来支持Pod在集群中的相互访问。Pod IP是一个虚拟的二层网络。
|
||||
* **Cluster IP**,Kubernetes集群中的一个Service的IP地址。Cluster IP是一个完全虚拟的IP,无法被Ping到,只能用于支持Kubernetes内部服务之间的相互访问。
|
||||
|
||||
## Label
|
||||
|
||||
Label允许在Kubernetes资源上附加对于系统或者用户有意义的标示性属性,方便对Kubernetes资源进行分组管理。 这些属性对不会直接对Kubernetes核心组件产生语义,不过可能间接地被Kubernetes核心组件处理产生效果。
|
||||
|
||||
Label key由两部分组成:可选的prefix和name,它们之间通过`/`连接。下面对它们的语法规则进行对比:
|
||||
|
||||
| | Prefix | Name |
|
||||
| :------- | :-------- | :--------------- |
|
||||
| 存在性 | 可选 | 必需 |
|
||||
| 最大长度 | 253 | 63 |
|
||||
| 可用字符 | DNS子域名 | 字母与数字 |
|
||||
| 分割符 | `.` | `-`, `_`, `.` |
|
||||
| 限制 | 以`/`结尾 | 以字母开头和结尾 |
|
||||
|
||||
Label可以让用户将他们自己的有组织目的的结构以一种松耦合的方式应用到系统的对象上,且不需要客户端存放这些对应关系(mappings)。
|
||||
|
||||
服务部署和批处理管道通常是多维的实体(例如多个分区或者部署,多个发布轨道,多层,每层多微服务)。管理通常需要跨越式的切割操作,这会打破有严格层级展示关系的封装,特别对那些是由基础设施而非用户决定的很死板的层级关系。
|
||||
|
||||
在习惯上以下Label都是比较常用的。
|
||||
|
||||
* "release" : "stable", "release" : "canary"
|
||||
* "environment" : "dev","environment" : "qa","environment" : "production"
|
||||
* "tier" : "frontend","tier" : "backend","tier" : "cache"
|
||||
* "partition" : "customerA", "partition" : "customerB"
|
||||
* "track" : "daily", "track" : "weekly"
|
||||
|
||||
### 选择器
|
||||
|
||||
与Name和UID不同,标签不需要有唯一性。一般来说,我们期望许多对象具有相同的标签。通过标签选择器(Labels Selectors),客户端/用户能方便辨识出一组对象。标签选择器是Kubernetes中核心的组成部分。
|
||||
|
||||
Kubernetes API目前支持两种选择器:equality-based(基于相等)和set-based(基于集合)的。标签选择器可以由逗号分隔的多个requirements 组成。在多重需求的情况下,必须满足所有要求,因此逗号分隔符作为AND逻辑运算符。
|
||||
|
||||
* 一个为空的标签选择器(即有0个必须条件的选择器)会选择集合中的每一个对象。
|
||||
* 一个null型标签选择器(仅对于可选的选择器字段才可能)不会返回任何对象。
|
||||
|
||||
> 注意:两个控制器的标签选择器不能在命名空间中重叠。
|
||||
|
||||
基于相等的或者不相等的条件允许用标签的keys和values进行过滤。匹配的对象必须满足所有指定的标签约束,尽管他们可能也有额外的标签。有三种运算符是允许的,“=”,“==”和“!=”。
|
||||
|
||||
* `=`:被选择的资源必须带label key,并且value必须相等。
|
||||
* `==`:被选择的资源必须带label key,并且value必须相等。
|
||||
* `!=`:被选择的资源不带label key,或者带label key,但是value不相等。
|
||||
|
||||
基于集合的标签条件允许用一组value来过滤key。支持三种操作符:`in`,`notin`和 `exists`(仅针对于key符号) 。
|
||||
|
||||
* `in`:被选择的资源必须带label key,并且value在集合范围内。
|
||||
* `notin`:被选择的资源不带label key,或者带label key,但是value不在集合范围内。
|
||||
* `exists`:被选择的资源必须带label key,不检查value。Label key前加`!`表示不存在,则被选择的资源必须不带label key。
|
||||
|
||||
Deployment和Service两种对象的Label选择器是使用map定义在json或者yaml文件中的,它们只支持Equality-based的条件。Job,Deployment,Replica Set,和Daemon Set,支持Set-based条件。注意,Deployment中的`matchLabels`属性只支持Equality-based条件,但`matchExpressions`属性支持Set-based条件。
|
||||
|
||||
> 不管是在`matchLabels`还是在`matchExpressions`中的选择器条件,都必须被同时满足才能够完成匹配。
|
495
source/_posts/UML概念速查.md
Normal file
495
source/_posts/UML概念速查.md
Normal file
@@ -0,0 +1,495 @@
|
||||
---
|
||||
title: UML概念速查
|
||||
date: 2021-04-04 23:28:22
|
||||
tags: [架构知识, UML, 组件图, 类图, 对象图, 结构图, 用例图, 活动图, 状态图, 序列图]
|
||||
categories:
|
||||
- [架构知识, UML]
|
||||
---
|
||||
UML图是进行软件架构设计时使用的一套标准工具集,通过UML图的绘制和表述,可以清晰明确的表现软件的静态逻辑结构、物理结构以及动态的运行过程等。UML图很简单,并不难理解,但是组成UML图形的图例却十分的丰富,只有完全了解了各种图例所代表的含义,才能顺利读懂UML图所要表达的意思。
|
||||
<!--more-->
|
||||
|
||||
@[toc]
|
||||
|
||||
## 概览
|
||||
|
||||
UML图主要由以下这些图组成,不同的图会被应用于软件架构设计的不同时期。
|
||||
|
||||
```plantuml
|
||||
@startwbs
|
||||
* 图
|
||||
** 结构图
|
||||
***< 类图
|
||||
***> 组件图
|
||||
***< 对象图
|
||||
***> 扩展机制图
|
||||
***< 组合结构图
|
||||
***> 部署图
|
||||
***< 包图
|
||||
** 行为图
|
||||
***< 用例图
|
||||
***> 活动图
|
||||
***< 状态图
|
||||
***> 交互图
|
||||
****< 序列图
|
||||
****> 通信图
|
||||
****< 交互概述图
|
||||
****> 时间图
|
||||
@endwbs
|
||||
```
|
||||
|
||||
| 工作阶段 | 工作焦点 | 采用图形 |
|
||||
|------|--------------|------------------------------------------|
|
||||
| 业务建模 | 组织系统之间的关系和流程 | 用例图、类图、序列图、活动图 |
|
||||
| 需求 | 系统边界 | 用例图、序列图、状态图、活动图 |
|
||||
| 分析 | 系统内的核心域 | 类图、序列图、状态图、活动图、通信图、包图 |
|
||||
| 设计 | 系统内各域之间 | 类图、序列图、状态图、活动图、通信图、组件图、部署图、时间图、组合结构图、包图 |
|
||||
|
||||
## 类图
|
||||
|
||||
类图是描述类、类的特性以及类之间关系的图。
|
||||
|
||||
### 图例
|
||||
|
||||
#### 类
|
||||
|
||||
使用分为三个部分的矩形表示,第一部分为类名和类属性,第二部分为类内的属性(字段),第三部分为类内的方法。
|
||||
|
||||
对于类内属性和方法的访问性,用以下符号表示:`+`表示public,`-`表示private,`#`表示protected,`~`表示package private。静态属性和方法使用下划线标识,抽象方法使用斜体标识。
|
||||
|
||||
属性一般采用`标识名: 类型`的格式书写,方法一般采用`方法名(参数类型列表): 返回类型`的格式书写。
|
||||
|
||||
```plantuml
|
||||
class Class {
|
||||
+ teacher: Teacher
|
||||
+ students: List<Student>
|
||||
+ dismiss(): void
|
||||
}
|
||||
```
|
||||
|
||||
接口和抽象类、枚举类都是特殊的类,只是类属性标识不同,图示与结构均相同。
|
||||
|
||||
#### 包
|
||||
|
||||
包对应Java中的`package`,其中会拥有多个类或者接口等。包使用类似于文件夹形式的图形表示。
|
||||
|
||||
```plantuml
|
||||
package School {
|
||||
class Teacher
|
||||
class Student
|
||||
class ClassRoom
|
||||
}
|
||||
```
|
||||
|
||||
### 类间关系
|
||||
|
||||
#### 双向关联
|
||||
|
||||
双方知道对方的存在,可以调用对方的公共属性和方法,表现为拥有对方的指针、引用或者实例。使用实直线连接,或者使用带有双向箭头的实直线连接。
|
||||
|
||||
```plantuml
|
||||
class A
|
||||
class B
|
||||
|
||||
A - B
|
||||
```
|
||||
|
||||
```plantuml
|
||||
class A
|
||||
class B
|
||||
|
||||
A <-> B
|
||||
```
|
||||
|
||||
#### 单向关联
|
||||
|
||||
类单方知晓对方的公共属性和方法,表现为拥有对方的指针、引用或实例。使用实线箭头连接,箭头指向被知晓方。
|
||||
|
||||
```plantuml
|
||||
class A
|
||||
class B
|
||||
|
||||
A -> B
|
||||
```
|
||||
|
||||
#### 自关联
|
||||
|
||||
类在内部拥有一个自身的指针、引用或者实例。使用实线箭头连接,箭头指向自己。
|
||||
|
||||
```plantuml
|
||||
class A {
|
||||
+ a: A
|
||||
}
|
||||
|
||||
A -> A
|
||||
```
|
||||
|
||||
#### 聚合
|
||||
|
||||
表示类之间的整体与部分的关系,组成整体的组件可独立存在(在特定领域内独立时有意义)时,关系为聚合。使用空心菱形加实直线(或者单箭头实直线)连接两个类,空心菱形指向整体。
|
||||
|
||||
```plantuml
|
||||
class A {
|
||||
+ b: B
|
||||
}
|
||||
class B
|
||||
|
||||
A o- B
|
||||
```
|
||||
|
||||
使用带箭头的实直线表示单向聚合。
|
||||
|
||||
```plantuml
|
||||
class A {
|
||||
+ b: B
|
||||
}
|
||||
class B
|
||||
|
||||
A o-> B
|
||||
```
|
||||
|
||||
#### 组合
|
||||
|
||||
表示整体与部分的关系,组成整体的组件不能独立存在(在特定领域内独立无意义)、且整体与部分之间的生命周期相同时,关系为组合。使用实心菱形加实直线连接两个类,实心菱形指向整体。
|
||||
|
||||
```plantuml
|
||||
class A {
|
||||
+ b: B
|
||||
}
|
||||
class B
|
||||
|
||||
A *- B
|
||||
```
|
||||
|
||||
使用带箭头的实直线表示单向组合。
|
||||
|
||||
```plantuml
|
||||
class A {
|
||||
+ b: B
|
||||
}
|
||||
class B
|
||||
|
||||
A *-> B
|
||||
```
|
||||
|
||||
#### 依赖
|
||||
|
||||
一个类执行功能时,必须要有被依赖类的功能协助,则两个类产生依赖关系。实际使用中,依赖都是单向的,不应该产生双向依赖。使用单箭头虚直线连接两个类,箭头指向被依赖类。
|
||||
|
||||
```plantuml
|
||||
class A
|
||||
class B
|
||||
|
||||
A .> B
|
||||
```
|
||||
|
||||
#### 继承
|
||||
|
||||
当两个类存在继承(泛化)关系。使用空心箭头实直线连接两个类,箭头指向被继承的类(父类)。
|
||||
|
||||
```plantuml
|
||||
class Animal
|
||||
class Tiger
|
||||
|
||||
Tiger -|> Animal
|
||||
```
|
||||
|
||||
#### 实现
|
||||
|
||||
当一个类实现了一个接口时,关系为实现。使用空心箭头虚直线连接两个类,箭头指向接口。
|
||||
|
||||
```plantuml
|
||||
interface Person
|
||||
class Student
|
||||
|
||||
Student .|> Person
|
||||
```
|
||||
|
||||
#### 嵌套
|
||||
|
||||
当一个类中包含另一个类或者接口等时,两个类形成嵌套关系。使用内嵌加号的圆形搭配实直线连接两个类,圆形指向被嵌套类。
|
||||
|
||||
```plantuml
|
||||
class A
|
||||
class NestedB
|
||||
|
||||
A -+ NestedB
|
||||
```
|
||||
|
||||
## 数据库实体图
|
||||
|
||||
数据库实体图用来描述数据库表之间的关系,可以用来对数据库表结构以及数据表之间的关联关系进行设计。
|
||||
|
||||
### 图例
|
||||
|
||||
数据表又称为Entity(实体),表示形状与类图中的类基本一致,也是将一个矩形分为三个部分,第一部分书写数据表或者实体的名称,第二部分书写主键字段,第三部分书写其余字段。所有的非空字段需要在字段名前使用点标记。字段的类型放置在字段名后,与字段名使用冒号隔开。
|
||||
|
||||
```plantuml
|
||||
entity Person {
|
||||
* id: varchar(20)
|
||||
--
|
||||
* name: varchar(50)
|
||||
* birthday: datetime
|
||||
salary: numeric(10,2)
|
||||
}
|
||||
```
|
||||
|
||||
### 实体间关系
|
||||
|
||||
数据库实体之间主要存在的关系是一对一、一对多、多对一、多对多几种,配合单向关联和双向关联,可以形成多种数据库实体间的关联关系。在数据库实体图中主要通过不同的连接线样式来体现。
|
||||
|
||||
以下通过几个常见的关系来简单说明实体间关系图示的绘制方法。
|
||||
|
||||
#### 单向一对一关系
|
||||
|
||||
```plantuml
|
||||
entity A
|
||||
entity B
|
||||
|
||||
A -|| B
|
||||
```
|
||||
|
||||
上图表示实体A必定存在一个对应的实体B。连接线上实体B一端的符号表示必定存在一个且仅一个实体,可以使用“11”来助记。
|
||||
|
||||
```plantuml
|
||||
entity A
|
||||
entity B
|
||||
|
||||
A -o| B
|
||||
```
|
||||
|
||||
上图表示实体A可能存在一个对应的实体B。连接线上实体B一端的符号表示存在零个或者一个实体,即最多一个实体,可以使用“01”来助记。
|
||||
|
||||
#### 单向一对多关系
|
||||
|
||||
```plantuml
|
||||
entity A
|
||||
entity B
|
||||
|
||||
A -|{ B
|
||||
```
|
||||
|
||||
上图表示实体A中存在最少一个对应的实体B。连接线上实体B一端的符号表示存在一个或者多个实体,即最少一个实体,可以使用“1<”来助记。
|
||||
|
||||
```plantuml
|
||||
entity A
|
||||
entity B
|
||||
|
||||
A -o{ B
|
||||
```
|
||||
|
||||
上图表示实体A中可能存在对应的实体B。连接线上实体B一端的符号表示存在零个或者多个实体,即最少零个、多则不限的实体,可以使用“0<”来助记。
|
||||
|
||||
#### 双向对应关系
|
||||
|
||||
之前的所有示例中都是单向对应关系,均表示在实体A中包含实体B,或者称为从实体A中访问实体B。双向对应关系则是增加了从实体B中访问实体A的关系,其表示符号和方法与单向关系相同,只是在连接线的实体A一端也增加相应的标记即可。
|
||||
|
||||
```plantuml
|
||||
entity A
|
||||
entity B
|
||||
|
||||
A ||-o{ B
|
||||
```
|
||||
|
||||
例如这个示例中表示实体A中会对应零个或者多个实体B,但是每个实体B必定仅有唯一对应的实体A。
|
||||
|
||||
## 用例图
|
||||
|
||||
用例图用来描述用户与系统功能单元之间的需求关系,用来展示外部用户所看到的系统功能模型。
|
||||
|
||||
### 图例
|
||||
|
||||
#### 参与者
|
||||
|
||||
通常表示与系统进行交互的用户、组织、外部系统或设备等。用小人来表示。
|
||||
|
||||
#### 用例
|
||||
|
||||
用例表示外部可见的系统功能或服务,用椭圆表示。
|
||||
|
||||
#### 容器
|
||||
|
||||
通常代表一个系统,用矩形表示。
|
||||
|
||||
#### 关系
|
||||
|
||||
用例图中的关系通常包括关联、泛化、包含和扩展,与类图中的表示方法相同。
|
||||
|
||||
### 示例
|
||||
|
||||
```plantuml
|
||||
actor Customer as cus
|
||||
actor Manager as man
|
||||
|
||||
rectangle Store {
|
||||
man -- (Deliver)
|
||||
cus -- (Order)
|
||||
(Order) ..> (Database)
|
||||
(Deliver) ..> (Database)
|
||||
}
|
||||
```
|
||||
|
||||
## 活动图
|
||||
|
||||
活动图一般用来描述用例图中用例的活动与活动间的约束关系,强调对象间的控制流程。主要用来对业务过程和工作流进行建模,以及用来实现用例。
|
||||
|
||||
活动图着重表现一个活动到另一个活动的控制流,流的前进来自内部驱动。与流程图相比,活动图面向对象,主要表现系统行为,而流程图是面向过程的,强调处理顺序和时间关系。
|
||||
|
||||
### 图例
|
||||
|
||||
#### 起始与终止节点
|
||||
|
||||
起始节点使用实心圆表示,每张活动图只能有一个起始节点。终止节点使用包含一个同心实心圆的圆表示,每张活动图可以有多个终止节点。
|
||||
|
||||
#### 活动节点
|
||||
|
||||
活动使用圆角矩形表示,活动内容书写在矩形内部。
|
||||
|
||||
#### 转换
|
||||
|
||||
活动间的转换使用实线单箭头连接。
|
||||
|
||||
#### 分支条件
|
||||
|
||||
分支使用菱形表示,包含一个进入转换和多个离开转换,条件标记在转换上。
|
||||
|
||||
#### 并发
|
||||
|
||||
并发操作包含分叉(fork)和汇合(join)两个操作节点,分别使用两条粗实线表示。进入分叉节点表示所有分支开始并行运行,共同完成一项事务,全部分支都运行到汇合节点之后,活动才能继续向下执行。
|
||||
|
||||
#### 泳道
|
||||
|
||||
泳道表示活动转移到了其他的模块或者组件中执行了,通常用来表现组件协作。
|
||||
|
||||
### 示例
|
||||
|
||||
```plantuml
|
||||
|Order|
|
||||
start
|
||||
|User|
|
||||
:Check login;
|
||||
if (Logged in) then (yes)
|
||||
else (no)
|
||||
stop
|
||||
endif
|
||||
|Order|
|
||||
:Add Goods;
|
||||
:Calculate;
|
||||
|Finance|
|
||||
:Accounting;
|
||||
|Order|
|
||||
:Generate;
|
||||
|Database|
|
||||
:Persist;
|
||||
stop
|
||||
```
|
||||
|
||||
## 状态图
|
||||
|
||||
状态图一般用来描述一个对象的所有状态以及状态之间的转换,一般对应状态机模式。
|
||||
|
||||
状态图着重于描述一个对象内的状态转换,驱动力来自于对象外部。
|
||||
|
||||
### 图例
|
||||
|
||||
状态图所采用的图例与互动图基本一致。不同点在于对于状态的描述。
|
||||
|
||||
#### 状态
|
||||
|
||||
状态使用圆角矩形表示,在矩形中间使用文字描述状态的名称。状态之内可以继续定义更加详细的状态。
|
||||
|
||||
### 示例
|
||||
|
||||
```plantuml
|
||||
[*] --> Neutral
|
||||
Neutral --> LoginAttempt : Input
|
||||
LoginAttempt --> LoginFail : No User
|
||||
LoginAttempt --> LoginFail : Password error
|
||||
LoginAttempt --> LoggedIn : Success
|
||||
LoggedIn -> Neutral : Log out
|
||||
LoginFail --> Neutral : Reset
|
||||
```
|
||||
|
||||
## 序列图
|
||||
|
||||
序列图用于进行逻辑建模,描述整个处理过程的基本逻辑。序列图描述了参与过程的各个模块角色之间相互作用以及消息传递关系。
|
||||
|
||||
序列图由对象、生命线、激活、消息、分支等元素组成。以下通过一个示例来说明序列图的形式。
|
||||
|
||||
```plantuml
|
||||
actor Administrator as root
|
||||
actor Operator as main
|
||||
participant Business as plat
|
||||
participant Alliance as ally
|
||||
|
||||
main -> plat : Query orders
|
||||
plat -> main : Orders
|
||||
|
||||
group Delivery
|
||||
ally -> plat : Delivery notice
|
||||
plat -> main : Delivery notice
|
||||
end
|
||||
|
||||
main -> plat : Process delivery
|
||||
plat -> main : Request conform delivery
|
||||
activate plat
|
||||
main -> plat : Confirm delivery
|
||||
plat -> main : Request authenticate
|
||||
main -> plat : Authenticate
|
||||
plat -> root : Request secondary authenticate
|
||||
root -> plat : Authenticate
|
||||
deactivate plat
|
||||
|
||||
plat -> plat : Notice delivery
|
||||
plat -> ally : Notice alliance delivery
|
||||
```
|
||||
|
||||
## 组件图
|
||||
|
||||
组件图是用来描述组件与组件之间关系的,通常在宏观层面上表现系统某个方面的实现结构。参与组件图表现的,一般都是系统中的物理组件,包括库、程序包、文件等。
|
||||
|
||||
### 图例
|
||||
|
||||
组件图的图例比较简单,主要有以下几种。
|
||||
|
||||
* 组件,使用矩形表示,其中书写组件的名称。
|
||||
* 接口,组件对外提供或者依赖与外部的操作集合。
|
||||
* 提供接口,组件为其他组件提供服务的操作的集合,使用空心圆表示,接口名称放置于图形旁边。
|
||||
* 需求接口,组件向其他组件请求服务时所遵循的接口,使用半圆弧表示,通常与其他组件的提供接口对接。
|
||||
* 端口,被封装的组件的对外窗口,所有出入组件的交互都要通过端口。绘制在组件的边界上,以空心矩形表示。
|
||||
* 关系,组件与接口之间通过连线来表示关系。
|
||||
* 实现关系,组件与接口之间采用实线连接。
|
||||
* 依赖关系,组件与其他组件接口之间采用单箭头虚线连接,箭头指向接口。
|
||||
|
||||
### 示例
|
||||
|
||||
```plantuml
|
||||
() "Withdraw" as wd
|
||||
() "Transfer" as tf
|
||||
|
||||
[Account] - wd
|
||||
tf - [Account]
|
||||
[B2B] ..> tf
|
||||
wd <.. [ATM]
|
||||
```
|
||||
|
||||
## 对象图
|
||||
|
||||
对象图表示一组对象及其之间的关系,是系统某一时刻的详细状态快照,通常用来结合类图来表示一个复杂的实例。
|
||||
|
||||
对象图的构建与类图相似,但主要列出属性的具体值。对象间的关联关系基本与类图一致。
|
||||
|
||||
### 示例
|
||||
|
||||
```plantuml
|
||||
object Ming {
|
||||
name = "Ming"
|
||||
age = 12
|
||||
}
|
||||
|
||||
object Mei {
|
||||
name = "Mei"
|
||||
age = 11
|
||||
}
|
||||
|
||||
Ming - Mei : friend
|
||||
```
|
6
source/_posts/flag.md
Normal file
6
source/_posts/flag.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
title: 为这个博客立一个Flag
|
||||
date: 2021-03-31 22:42:15
|
||||
tags:
|
||||
categories:
|
||||
---
|
80
source/_posts/git-tips-tricks.md
Normal file
80
source/_posts/git-tips-tricks.md
Normal file
@@ -0,0 +1,80 @@
|
||||
---
|
||||
title: Git日常使用技巧拾遗
|
||||
date: 2021-04-06 15:46:42
|
||||
tags: [架构知识, 代码管理, Git]
|
||||
categories:
|
||||
- [架构知识, Git]
|
||||
---
|
||||
Git在日常使用的时候,有些操作十分简单,但是一旦出现失误或者没有做过的操作,就会不知所措,如果再盲目乱试,又极有可能造成版本库损坏。这里收集了一些比较常见的Git版本库操作技巧。
|
||||
<!--more-->
|
||||
@[toc]
|
||||
## 修改Git的默认编辑器
|
||||
|
||||
修改Git的默认编辑器可以使用以下命令来实现:
|
||||
|
||||
```bash
|
||||
git config --global core.editor nano
|
||||
```
|
||||
|
||||
以上命令将Git在命令行中的默认编辑器设置为了nano,如果习惯使用vim,可以直接将nano替换为vim。
|
||||
|
||||
## 修改提交信息
|
||||
|
||||
提交信息的修改分为两种情况,第一种情况是仅修改最近一次提交的信息,可以直接使用`amend`。
|
||||
|
||||
```bash
|
||||
git commit --amend
|
||||
```
|
||||
|
||||
执行以上命令会打开一个编辑器,允许你修改最近一次提交的提交信息。
|
||||
|
||||
如果要修改以往的提交信息,必须要借助于`rebase`变基。对于修改提交信息可以利用Git提供的交互式变基来完成,在使用交互式变基时,必须指定要重写多久远的历史。例如修改最近6次提交的信息,可以执行以下命令。
|
||||
|
||||
```bash
|
||||
git rebase -i HEAD~6
|
||||
```
|
||||
|
||||
已经启动的编辑器只要被保存了,Git就会开始执行变基操作。执行这个命令以后,Git就会在编辑器中列举出被选中的提交。每个提交历史的格式都是`<命令> <提交编号> <提交信息>`,都是要对这次提交做的操作,例如`pick f7f3f6d changed files`表示继续采用提交编号为`f7f3f6d`的提交。如果要对这次提交进行修改,可以直接编辑提交历史项目前的命令。常用的命令如下。
|
||||
|
||||
* `pick`,采用本次提交,并且保持提交信息不变。
|
||||
* `reword`,采用本次提交,但修改提交信息。
|
||||
* `edit`,采用本次提交,但停下来做amend操作。编辑结束后可以使用`git rebase --continue`继续变基。
|
||||
* `squash`,采用本次提交,但将本次提交与前一次提交(上面的)合并。
|
||||
* `fixup`,行为与`squash`相同,但取消提交的日志信息。
|
||||
* `exec`,执行一条shell命令。
|
||||
* `break`,在当前位置暂停rebase,可以使用`git rebase --continue`继续。
|
||||
* `drop`,丢弃本次提交。
|
||||
* `reset`,将HEAD重置到指定标签。
|
||||
* `merge`,建立一个合并提交信息,可以使用`-C`来指定新的提交信息。
|
||||
|
||||
在对提交列表进行编辑时,可以调整提交的顺序,这里只需要注意Git会按照从上到下的顺序应用变基操作。
|
||||
|
||||
## 从每一个提交中删除一个文件
|
||||
|
||||
当因为粗心向版本库中添加了不必要的文件可以使用以下指令来将其从所有的提交中删除。
|
||||
|
||||
```bash
|
||||
git filter-branch --tree-filter 'rm -f 要删除的文件名' HEAD
|
||||
```
|
||||
|
||||
> 注意,这个命令是核武器级的,能够影响整个版本库,在使用时要十分谨慎。
|
||||
|
||||
## 提交信息标准化
|
||||
|
||||
标准化的提交信息一般包含Header、Body、Footer三个部分,其中Header为提交的概要说明,Body为提交的详细描述,Footer中一般用于描述不兼容变更和被关闭的Issue信息。
|
||||
|
||||
对于提交信息标准化,主要是针对Header部分。Header一般建议写为`类型(影响范围):信息概要`的格式。其中类别主要有以下几种。
|
||||
|
||||
* `feat`,新增功能。
|
||||
* `fix`,修复issue。
|
||||
* `docs`,仅修改文档。
|
||||
* `refactor`,重构代码,未新增任何功能,也未修复任何issue。
|
||||
* `build`,改变构建流程,新增依赖库以及工具等。
|
||||
* `style`,仅仅修改了空格、缩进等代码样式,不改变代码的逻辑。
|
||||
* `pref`,改善性能和体现。
|
||||
* `chore`,对代码和测试代码以外的内容进行的修改。
|
||||
* `test`,修改了测试用例。
|
||||
* `ci`,修改了自动化流程配置。
|
||||
* `revert`,回滚到了上一个版本。
|
||||
|
||||
影响范围是可选的,如果修复的issue,可以书写issue的编号。对于其他的类型,可以书写相应的功能代号。
|
775
source/_posts/spring-boot-kafka-rpc.md
Normal file
775
source/_posts/spring-boot-kafka-rpc.md
Normal file
@@ -0,0 +1,775 @@
|
||||
---
|
||||
title: 使用Spring Boot搭配Kafka实现RPC调用
|
||||
date: 2021-04-05 11:30:14
|
||||
tags: [JVM, Spring Boot, Java, Kafka, RPC, 分布式通信]
|
||||
categories:
|
||||
- [JVM, Java]
|
||||
- [JVM, Spring]
|
||||
---
|
||||
Kafka是目前十分流行的分布式消息队列,但是如何利用Kafka搭配Spring for Apache Kafka实现一个基于消息队列的RPC基础功能呢?
|
||||
<!--more-->
|
||||
## 服务架构
|
||||
|
||||
Spring for Apache Kafka 中提供了以下几个概念来构建 Kafka 中的生产者和消费者。
|
||||
|
||||
* `ProducerFactory<K, V>`,用于构建一个生产者实例的工厂类。
|
||||
* `KafkaTemplate<K, V>`,执行发送消息的功能类。
|
||||
* `ReplyingKafkaTemplate<K, V, R>`,具备发送消息和回收消息的功能类。
|
||||
* `ConsumerFactory<K, V>`,用于构建一个消费者的工厂类。
|
||||
* `KafkaMessageListenerContainer<K, V>`,用于持有消费者的容器类。
|
||||
* `KafkaListenerContainerFactory<K, V>`,用于构建只有消费者的容器的工厂类。
|
||||
* `NewTopic`,程序运行时自动构建的 Topic,如果 Topic 已经存在则跳过构建。
|
||||
* `Message<?>`,用于承载对象的消息。
|
||||
|
||||
### 生产方架构
|
||||
|
||||
生产方的架构十分简单,只需要在生产方的类构造函数中注入 `KafkaTemplate<K, V>` Bean 即可。当不使用事务时,`ProducerFactory` 的默认实现 `DefaultKafkaProducerFactory` 会创建一个单例的生产者。
|
||||
|
||||
要创建一个 `ProducerFactory` 需要一个类型为 `Map<String, Object>` 的配置集,以及一个键序列化器和一个值序列化器。配置集中的各个配置项名称在 `ProducerConfig` 类中定义。`KafkaTemplate<K, V>` 实例中可以注入一个 `RecordMessageConverter` 实例,用来对复杂的对象进行承载传输。
|
||||
|
||||
```plantuml
|
||||
@startuml
|
||||
skinparam {
|
||||
componentStyle uml2
|
||||
monochrome false
|
||||
shadowing false
|
||||
}
|
||||
hide fields
|
||||
|
||||
class KafkaProperties {
|
||||
+ buildProducerProperties()
|
||||
}
|
||||
class KafkaTemplate {
|
||||
+ setMessageConverter(RecordMessageConverter)
|
||||
+ send(String, K, V)
|
||||
+ send(String, V)
|
||||
+ send(ProducerRecord)
|
||||
+ send(Message)
|
||||
}
|
||||
class KeySerializer {
|
||||
+ serialize(String, Header, T)
|
||||
+ serialize(String, T
|
||||
}
|
||||
class ValueSerializer {
|
||||
+ serialize(String, Header, T)
|
||||
+ serialize(String, T)
|
||||
}
|
||||
interface ProducerFactory {
|
||||
+ createProducer()
|
||||
}
|
||||
interface MessageConverter {
|
||||
+ commonHeaders()
|
||||
}
|
||||
interface RecordMessageConverter {
|
||||
+ toMessage(ConsumeRecord, Acknowledgement, Consumer, Type)
|
||||
+ fromMessage(Message, String)
|
||||
}
|
||||
|
||||
ProducerFactory --* KafkaTemplate
|
||||
KeySerializer --* ProducerFactory
|
||||
ValueSerializer --* ProducerFactory
|
||||
KafkaProperties --* ProducerFactory
|
||||
MessageConverter <|-- RecordMessageConverter
|
||||
RecordMessageConverter --* KafkaTemplate
|
||||
@enduml
|
||||
```
|
||||
|
||||
### 消费方架构
|
||||
|
||||
消费方的架构要略复杂,由于消费方需要对 Kafka 传递来的消息进行监听,所以需要将监听器(Listener)置入容器中,由容器负载并进行处理。常用的监听器接口主要有 `MessageListener<K, V>` 和 `AcknowledgingMessageListener<K, V>` 等,或者使用 `@KafkaListener` 注解标记处理方法或者处理类。容器根据功能需要,常用的则有两种 `KafkaMessageListenerContainer` 和 `ConcurrentMessageListenerContainer`,分别用于单线程监听和多线程监听。
|
||||
|
||||
与生产方相同,消费方也需要使用工厂类来创建消费方实例。消费方工厂类一般都实现了接口 `ConsumerFactory<K ,V>`,常用的是 `DefaultKafkaConsumerFactory<K ,V>`。监听容器的构建需要同时提供消费方工厂类实例和容器配置集。
|
||||
|
||||
```plantuml
|
||||
@startuml
|
||||
skinparam {
|
||||
componentStyle uml2
|
||||
monochrome false
|
||||
shadowing false
|
||||
}
|
||||
hide fields
|
||||
|
||||
class ContainerProperties {
|
||||
+ ContainerProperties(String...)
|
||||
+ setMessageListener(MessageListener)
|
||||
}
|
||||
interface MessageListener {
|
||||
+ onMessage(ConsumerRecord)
|
||||
}
|
||||
interface ConsumerFactory {
|
||||
+ createConsumer()
|
||||
}
|
||||
class KafkaProperties {
|
||||
+ buildConsumerProperties()
|
||||
}
|
||||
class KeyDeserializer {
|
||||
+ deserialize(String, Header, T)
|
||||
+ deserialize(String, T
|
||||
}
|
||||
class ValueDeserializer {
|
||||
+ deserialize(String, Header, T)
|
||||
+ deserialize(String, byte[])
|
||||
}
|
||||
class KafkaMessageListenerContainer {
|
||||
# doStart()
|
||||
}
|
||||
interface KafkaListenerContainerFactory {
|
||||
+ createContainer()
|
||||
}
|
||||
abstract class AbstractKafkaListenerContainerFactory {
|
||||
+ setConsumerFactory(ConsumerFactory)
|
||||
+ setMessageConverter(MessageConverter)
|
||||
}
|
||||
interface MessageConverter {
|
||||
+ commonHeaders()
|
||||
}
|
||||
interface RecordMessageConverter {
|
||||
+ toMessage(ConsumeRecord, Acknowledgement, Consumer, Type)
|
||||
+ fromMessage(Message, String)
|
||||
}
|
||||
|
||||
MessageListener --* ContainerProperties
|
||||
KafkaProperties --* ConsumerFactory
|
||||
KeyDeserializer --* ConsumerFactory
|
||||
ValueDeserializer --* ConsumerFactory
|
||||
KafkaListenerContainerFactory <|.right. AbstractKafkaListenerContainerFactory
|
||||
ConsumerFactory --* AbstractKafkaListenerContainerFactory
|
||||
ContainerProperties --* AbstractKafkaListenerContainerFactory
|
||||
AbstractKafkaListenerContainerFactory - KafkaMessageListenerContainer : 生成 >
|
||||
MessageConverter <|-- RecordMessageConverter
|
||||
RecordMessageConverter --* AbstractKafkaListenerContainerFactory
|
||||
@enduml
|
||||
```
|
||||
|
||||
### RPC 架构
|
||||
|
||||
在使用 Kafka 执行 RPC 调用时,被调用的消费方的建立与其他用途中没有太多区别,只是需要在 `AbstractKafkaListenerContainerFactory<C, K, V>` 中加入一个用于发送消息的 `KafkaTemplate<K, V>` 实例即可,并在使用 `@KafkaListener` 注解的监听器上增加 `@SendTo` 注解,并使监听器返回要发回的对象即可。但是生产方的配置就相应的要复杂许多了,除了要配置专用的 `ReplyingKafkaTemplate<K, V, R>` 以外,还需要配置针对返回消息的消费方设置。
|
||||
|
||||
总起来说,在使用 RPC 调用时,无论调用方还是被调用方,都是一个集成了生产方和消费方的全功能 Kafka 客户端。
|
||||
|
||||
#### 调用方架构
|
||||
|
||||
```plantuml
|
||||
@startuml
|
||||
skinparam {
|
||||
componentStyle uml2
|
||||
monochrome false
|
||||
shadowing false
|
||||
}
|
||||
hide fields
|
||||
|
||||
interface ProducerFactory {
|
||||
+ createProducer()
|
||||
}
|
||||
interface RecordMessageConverter {
|
||||
+ toMessage(ConsumeRecord, Acknowledgement, Consumer, Type)
|
||||
+ fromMessage(Message, String)
|
||||
}
|
||||
class ReplyingKafkaTemplate {
|
||||
+ sendAndReceive(ProducerRecord)
|
||||
+ sendAndReceive(ProducerRecord, Duration)
|
||||
}
|
||||
class KafkaMessageListenerContainer {
|
||||
# doStart()
|
||||
}
|
||||
interface KafkaListenerContainerFactory {
|
||||
+ createContainer()
|
||||
}
|
||||
abstract class AbstractKafkaListenerContainerFactory {
|
||||
+ setConsumerFactory(ConsumerFactory)
|
||||
+ setMessageConverter(MessageConverter)
|
||||
}
|
||||
interface ConsumerFactory {
|
||||
+ createConsumer()
|
||||
}
|
||||
interface MessageListener {
|
||||
+ onMessage(ConsumerRecord)
|
||||
}
|
||||
class ContainerProperties {
|
||||
+ ContainerProperties(String...)
|
||||
+ setMessageListener(MessageListener)
|
||||
}
|
||||
|
||||
MessageListener --* ContainerProperties
|
||||
RecordMessageConverter --* AbstractKafkaListenerContainerFactory
|
||||
ProducerFactory --* ReplyingKafkaTemplate
|
||||
ContainerProperties --* AbstractKafkaListenerContainerFactory
|
||||
KafkaListenerContainerFactory <|.. AbstractKafkaListenerContainerFactory
|
||||
KafkaMessageListenerContainer - AbstractKafkaListenerContainerFactory : 生成 <
|
||||
ConsumerFactory --* AbstractKafkaListenerContainerFactory
|
||||
KafkaMessageListenerContainer --* ReplyingKafkaTemplate
|
||||
@enduml
|
||||
```
|
||||
|
||||
#### 被调用方架构
|
||||
|
||||
```plantuml
|
||||
@startuml
|
||||
skinparam {
|
||||
componentStyle uml2
|
||||
monochrome false
|
||||
shadowing false
|
||||
}
|
||||
hide fields
|
||||
|
||||
class ContainerProperties {
|
||||
+ ContainerProperties(String...)
|
||||
+ setMessageListener(MessageListener)
|
||||
}
|
||||
interface MessageListener {
|
||||
+ onMessage(ConsumerRecord)
|
||||
}
|
||||
interface ConsumerFactory {
|
||||
+ createConsumer()
|
||||
}
|
||||
class KafkaMessageListenerContainer {
|
||||
# doStart()
|
||||
}
|
||||
interface KafkaListenerContainerFactory {
|
||||
+ createContainer()
|
||||
}
|
||||
abstract class AbstractKafkaListenerContainerFactory {
|
||||
+ setConsumerFactory(ConsumerFactory)
|
||||
+ setMessageConverter(MessageConverter)
|
||||
+ setReplyTemplate(KafkaTemplate)
|
||||
}
|
||||
interface RecordMessageConverter {
|
||||
+ toMessage(ConsumeRecord, Acknowledgement, Consumer, Type)
|
||||
+ fromMessage(Message, String)
|
||||
}
|
||||
class KafkaTemplate {
|
||||
+ setMessageConverter(RecordMessageConverter)
|
||||
+ send(String, K, V)
|
||||
+ send(String, V)
|
||||
+ send(ProducerRecord)
|
||||
+ send(Message)
|
||||
}
|
||||
|
||||
MessageListener --* ContainerProperties
|
||||
KafkaListenerContainerFactory <|.right. AbstractKafkaListenerContainerFactory
|
||||
ConsumerFactory --* AbstractKafkaListenerContainerFactory
|
||||
ContainerProperties --* AbstractKafkaListenerContainerFactory
|
||||
AbstractKafkaListenerContainerFactory - KafkaMessageListenerContainer : 生成 >
|
||||
RecordMessageConverter --* AbstractKafkaListenerContainerFactory
|
||||
RecordMessageConverter --* KafkaTemplate
|
||||
KafkaTemplate --* AbstractKafkaListenerContainerFactory
|
||||
@enduml
|
||||
```
|
||||
|
||||
## 配置项内容的获取
|
||||
|
||||
Spring Boot 所接管的配置项内容可以通过依赖注入获取,而不必像说明手册中描述的一样需要在程序中手动置入。要获取 Kafka 的配置,只需要声明并注入一个 `KafkaProperties` 类型的属性即可。
|
||||
|
||||
## 单向发送字符串
|
||||
|
||||
单向发送功能需要在发送方创建 `KafkaTemplate<K, V>` 的实例,需要注意的是, Spring Boot 已经内置提供了 `KafkaTemplate<String, String>` 的 Bean,对于字符串信息可以直接发送。
|
||||
|
||||
以下是发送方的示例。
|
||||
|
||||
```java
|
||||
@Component
|
||||
public class MessageProducer {
|
||||
private final KafkaTemplate<String, String> template;
|
||||
|
||||
@Autowired
|
||||
public MessageProducer(KafkaTemplate<String, String> kafkaTemplate) {
|
||||
this.template = kafkaTemplate;
|
||||
}
|
||||
|
||||
public void sendMessage(String message) {
|
||||
this.template.send("some-topic", message);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
以下是消费方的示例。
|
||||
|
||||
```java
|
||||
@Component
|
||||
@Slf4j
|
||||
public class MessageConsumer {
|
||||
@KafkaListener(id = "client_grp", topic = "some-topic")
|
||||
public void consumeMessage(String message) {
|
||||
log.info(message);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 双向发送字符串
|
||||
|
||||
与单向发送字符串功能相似,针对字符串的发送和接收,Spring for Kafka 已经提供了许多已经配置好的现成 Bean 可供使用,但是需要注意的是,RPC 调用方的 `ReplyingKafkaTemplate<K, V, R>` 是需要手工配置的。
|
||||
|
||||
以下是调用方的示例。
|
||||
|
||||
```java
|
||||
// 应用主类
|
||||
@SpringBootApplication
|
||||
public class RpcRequestApplication {
|
||||
private final KafkaProperties kProperties;
|
||||
|
||||
@Autowired
|
||||
public RpcRequestApplication(
|
||||
KafkaProperties properties
|
||||
) {
|
||||
this.kProperties = properties;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(RpcRequestApplication.class, args).close();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ReplyingKafkaTemplate<String, String, String> replyTemplate(
|
||||
ProducerFactory<String, String> factory,
|
||||
ConcurrentMessageListenerContainer<String, String> repliesContainer
|
||||
) {
|
||||
return new ReplyingKafkaTemplate<>(factory, repliesContainer);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ProducerFactory<String, String> producerFactory() {
|
||||
Map<String, Object> props = this.kProperties.buildProducerProperties();
|
||||
props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
|
||||
props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
|
||||
return new DefaultKafkaProducerFactory<>(props);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ConcurrentKafkaListenerContainerFactory<String, String> containerFactory(
|
||||
KafkaTemplate<String, String> kafkaTemplate
|
||||
) {
|
||||
ConcurrentKafkaListenerContainerFactory<String, String> factory =
|
||||
new ConcurrentKafkaListenerContainerFactory<>();
|
||||
factory.setConsumerFactory(consumerFactory());
|
||||
factory.setReplyTemplate(kafkaTemplate);
|
||||
return factory;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ConsumerFactory<String, String> consumerFactory() {
|
||||
return new DefaultKafkaConsumerFactory<>(
|
||||
this.kProperties.buildConsumerProperties(),
|
||||
StringDeserializer::new,
|
||||
StringDeserializer::new
|
||||
);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ConcurrentMessageListenerContainer<String, String> messageContainer(
|
||||
ConcurrentKafkaListenerContainerFactory<String, String> factory
|
||||
) {
|
||||
ConcurrentMessageListenerContainer<String, String> container =
|
||||
factory.createContainer("RPC-Response");
|
||||
container.getContainerProperties().setGroupId("replies");
|
||||
container.setAutoStartup(false);
|
||||
return container;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public NewTopic rpcRequestTopic() {
|
||||
return TopicBuilder.name("RPC-Request")
|
||||
.partitions(1)
|
||||
.replicas(3)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public NewTopic rpcReplyTopic() {
|
||||
return TopicBuilder.name("RPC-Response")
|
||||
.partitions(1)
|
||||
.replicas(3)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
// 功能类
|
||||
@Component
|
||||
@Slf4j
|
||||
public class RpcRequester implements CommandLineRunner {
|
||||
private final ReplyingKafkaTemplate<String, String> template;
|
||||
|
||||
@Autowired
|
||||
public RpcRequester(
|
||||
ReplyingKafkaTemplate<String, String> template
|
||||
) {
|
||||
this.template = template;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(String... args) throws Exception {
|
||||
try {
|
||||
RequestReplyFuture<String, String, String> reply = this.template.sendAndReceive(
|
||||
new ProducerRecord<>("RPC-Request", "greeting")
|
||||
);
|
||||
String result = reply.get().value();
|
||||
log.info("Hello from " + result);
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
log.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
以下是响应方的示例。
|
||||
|
||||
```java
|
||||
@Component
|
||||
@Slf4j
|
||||
public class RpcReplier {
|
||||
@KafkaListener(id="rpc-server", topic="RPC-Request")
|
||||
@SendTo
|
||||
public String replyGreeting(String message) {
|
||||
log.info("Requester send: " + message);
|
||||
return "Replier";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
在响应方中,与单向发送唯一的不同是添加了 `@SendTo` 注解并在监听器上增加了返回值类型。
|
||||
|
||||
## 单向发送自定义对象
|
||||
|
||||
单向发送自定义对象需要自行配置完整的业务链条,其中生产方需要配置 `ProducerFactory<K, V>`、`KafkaTemplate<K, V>`,而消费方则需要配置 `ConsumerFactory<K, V>`,以及监听器容器工厂和容器。
|
||||
|
||||
以下给出生产方的示例代码。
|
||||
|
||||
```yaml
|
||||
# 生产方配置文件
|
||||
spring:
|
||||
kafka:
|
||||
bootstrap-servers: 192.168.1.1:9092
|
||||
producer:
|
||||
key-serializer: org.apache.kafka.common.serialization.StringSerializer
|
||||
value-serializer: org.springframework.kafka.support.serializer.JsonSerializer
|
||||
```
|
||||
|
||||
```java
|
||||
// 自定义载荷类
|
||||
@Data
|
||||
@Builder
|
||||
public class Cargo {
|
||||
@NonNull private final String action;
|
||||
private Object payload;
|
||||
|
||||
public Cargo(
|
||||
@JsonProperty("action") String action,
|
||||
@JsonProperty("payload") @Nullable Object payload
|
||||
) {
|
||||
this.action = action;
|
||||
this.payload = payload;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
// 主类文件
|
||||
@SpringBootApplication
|
||||
public class SenderApplication {
|
||||
|
||||
private final KafkaProperties kProperties;
|
||||
|
||||
@Autowired
|
||||
public SenderApplication(KafkaProperties props) {
|
||||
this.kProperties = props;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(SenderApplication.class, args);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ProducerFactory<String, Cargo> producerFactory() {
|
||||
Map<String, Object> producerProps = this.kProperties.buildProducerProperties();
|
||||
// 以下两条语句与上面配置文件中的 producer 的配置功能相同
|
||||
// 择一使用即可,一般不建议在此进行硬编码
|
||||
producerProps.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
|
||||
producerProps.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, JsonSerializer.class);
|
||||
return new DefaultKafkaProducerFactory<>(producerProps);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public KafkaTemplate<String, Cargo> sendTemplate() {
|
||||
return new KafkaTemplate<>(producerFactory());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
// 功能类
|
||||
@Component
|
||||
@Slf4j
|
||||
public class Requester implements CommandLineRunner {
|
||||
|
||||
private final KafkaTemplate<String, Cargo> sendTemplate;
|
||||
|
||||
@Autowired
|
||||
public Requester(KafkaTemplate<String, Cargo> template) {
|
||||
this.sendTemplate = template;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(String... args) throws Exception {
|
||||
Cargo load = Cargo.builder().action("test").build();
|
||||
ProducerRecord<String, Cargo> request = new ProducerRecord<>("RPC-Request", load);
|
||||
this.sendTemplate.send(request);
|
||||
log.info("Custom package sent.");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
以下是消费方示例代码。
|
||||
|
||||
```yaml
|
||||
# 消费方配置文件
|
||||
spring:
|
||||
kafka:
|
||||
bootstrap-servers: 192.168.1.1:9092
|
||||
consumer:
|
||||
group-id: response-group
|
||||
key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
|
||||
value-deserializer: org.springframework.kafka.support.serializer.JsonDeserializer
|
||||
properties:
|
||||
spring.json:
|
||||
trusted.packages: '*'
|
||||
```
|
||||
|
||||
```java
|
||||
// 主类文件
|
||||
@SpringBootApplication
|
||||
public class ReceiverApplication {
|
||||
|
||||
private final KafkaProperties kProperties;
|
||||
|
||||
@Autowired
|
||||
public ReceiverApplication(KafkaProperties props) {
|
||||
this.kProperties = props;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(SenderApplication.class, args);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ConsumerFactory<String, Cargo> consumerFactory() {
|
||||
Map<String, Object> consumerProps = this.kProperties.buildConsumerProperties();
|
||||
// 以下三条语句与上面配置文件中的 consumer 的配置功能相同
|
||||
// 择一使用即可,一般不建议在此进行硬编码
|
||||
consumerProps.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringSerializer.class);
|
||||
consumerProps.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, JsonSerializer.class);
|
||||
consumerProps.put(JsonDeserializer.TRUSTED_PACKAGES, "*");
|
||||
return new DefaultKafkaProducerFactory<>(consumerProps);
|
||||
}
|
||||
|
||||
// 这里有一个小坑,如果生成容器工厂的 Bean 方法名不是 kafkaListenerContainerFactory,
|
||||
// 就必须将 Bean 的名称设置为 kafkaListenerContainerFactory,
|
||||
// 否则将提示无法找到类型为 ConsumerFactory<Object, Object> 的 Bean,
|
||||
// 但实际上是没有找到监听器容器工厂 Bean。
|
||||
@Bean
|
||||
public ConcurrentKafkaListenerContainerFactory<String, Cargo> kafkaListenerContainerFactory() {
|
||||
ConcurrentKafkaListenerContainerFactory<String, Cargo> factory =
|
||||
new ConcurrentKafkaListenerContainerFactory<>();
|
||||
factory.setConsumerFactory(consumerFactory());
|
||||
return factory;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ConcurrentMessageListenerContainer<String, Cargo> cargoListenerContainer() {
|
||||
ConcurrentMessageListenerContainer<String, Cargo> container =
|
||||
kafkaListenerContainerFactory().createContainer("RPC-Request");
|
||||
container.getContainerProperties().setGroupId("replies");
|
||||
container.setAutoStartup(false);
|
||||
return container;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
// 功能类
|
||||
@Component
|
||||
@Slf4j
|
||||
public class Receiver {
|
||||
|
||||
@KafkaListener(id = "rpc-server", topics = "RPC-Request")
|
||||
public void receive(Cargo cargo) {
|
||||
log.info("Received: {}", cargo.getAction());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 双向发送自定义对象
|
||||
|
||||
双向发送自定义对象实际上与双向发送字符串一样,需要将生产方和消费方结合起来,形成 RPC 的调用方和被调用方。在以下示例中,调用方和被调用方都采用如下的配置文件。
|
||||
|
||||
```yaml
|
||||
spring:
|
||||
kafka:
|
||||
bootstrap-servers: 192.168.1.1:9092
|
||||
producer:
|
||||
key-serializer: org.apache.kafka.common.serialization.StringSerializer
|
||||
value-serializer: org.springframework.kafka.support.serializer.JsonSerializer
|
||||
consumer:
|
||||
# 针对调用方和被调用方,group-id 可以不相同,也尽量不要相同
|
||||
group-id: response-group
|
||||
key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
|
||||
value-deserializer: org.springframework.kafka.support.serializer.JsonDeserializer
|
||||
properties:
|
||||
spring.json:
|
||||
trusted.packages: '*'
|
||||
```
|
||||
|
||||
以下是调用方的示例。
|
||||
|
||||
```java
|
||||
// 主类文件
|
||||
@SpringBootApplication
|
||||
public class SenderApplication {
|
||||
|
||||
private final KafkaProperties kProperties;
|
||||
|
||||
@Autowired
|
||||
public SenderApplication(KafkaProperties props) {
|
||||
this.kProperties = props;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(SenderApplication.class, args);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ProducerFactory<String, Cargo> producerFactory() {
|
||||
Map<String, Object> producerProps = this.kProperties.buildProducerProperties();
|
||||
return new DefaultKafkaProducerFactory<>(producerProps);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ConsumerFactory<String, Cargo> consumerFactory() {
|
||||
Map<String, Object> consumerProps = this.kProperties.buildConsumerProperties();
|
||||
return new DefaultKafkaProducerFactory<>(consumerProps);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ConcurrentKafkaListenerContainerFactory<String, Cargo> kafkaListenerContainerFactory() {
|
||||
ConcurrentKafkaListenerContainerFactory<String, Cargo> factory =
|
||||
new ConcurrentKafkaListenerContainerFactory<>();
|
||||
factory.setConsumerFactory(consumerFactory());
|
||||
return factory;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ConcurrentMessageListenerContainer<String, Cargo> cargoListenerContainer() {
|
||||
ConcurrentMessageListenerContainer<String, Cargo> container =
|
||||
kafkaListenerContainerFactory().createContainer("RPC-Response");
|
||||
container.getContainerProperties().setGroupId("requests");
|
||||
container.setAutoStartup(false);
|
||||
return container;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ReplyingKafkaTemplate<String, Cargo, Cargo> replyingTemplate(
|
||||
ProducerFactory<String, Cargo> factory,
|
||||
ConcurrentMessageListenerContainer<String, Cargo> container
|
||||
) {
|
||||
return new ReplyingKafkaTemplate<>(factory, container);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
// 功能类
|
||||
@Component
|
||||
@Slf4j
|
||||
public class Requester implements CommandLineRunner {
|
||||
|
||||
private final ReplyingKafkaTemplate<String, Cargo, Cargo> replyTemplate;
|
||||
|
||||
@Autowired
|
||||
public Requester(ReplyingKafkaTemplate<String, Cargo, Cargo> template) {
|
||||
this.replyTemplate = template;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(String... args) throws Exception {
|
||||
try {
|
||||
Cargo load = Cargo.builder().action("request").build();
|
||||
ProducerRecord<String, Cargo> request = new ProducerRecord<>("RPC-Request", load);
|
||||
RequestReplyFuture<String, Cargo, Cargo> requestFuture = this.replyTemplate.sendAndReceive(request);
|
||||
Cargo response = requestFuture.get().value();
|
||||
log.info("Received: {}", response.getAction());
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
log.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
以下是被调用方的示例。
|
||||
|
||||
```java
|
||||
// 主类文件
|
||||
@SpringBootApplication
|
||||
public class ReceiverApplication {
|
||||
|
||||
private final KafkaProperties kProperties;
|
||||
|
||||
@Autowired
|
||||
public ReceiverApplication(KafkaProperties props) {
|
||||
this.kProperties = props;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(SenderApplication.class, args);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ProducerFactory<String, Cargo> producerFactory() {
|
||||
Map<String, Object> producerProps = this.kProperties.buildProducerProperties();
|
||||
return new DefaultKafkaProducerFactory<>(producerProps);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ConsumerFactory<String, Cargo> consumerFactory() {
|
||||
Map<String, Object> consumerProps = this.kProperties.buildConsumerProperties();
|
||||
return new DefaultKafkaProducerFactory<>(consumerProps);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public KafkaTemplate<String, Cargo> sendTemplate() {
|
||||
return new KafkaTemplate<>(producerFactory());
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ConcurrentKafkaListenerContainerFactory<String, Cargo> kafkaListenerContainerFactory() {
|
||||
ConcurrentKafkaListenerContainerFactory<String, Cargo> factory =
|
||||
new ConcurrentKafkaListenerContainerFactory<>();
|
||||
factory.setConsumerFactory(consumerFactory());
|
||||
factory.setReplyTemplate(sendTemplate());
|
||||
return factory;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ConcurrentMessageListenerContainer<String, Cargo> cargoListenerContainer() {
|
||||
ConcurrentMessageListenerContainer<String, Cargo> container =
|
||||
kafkaListenerContainerFactory().createContainer("RPC-Request");
|
||||
container.getContainerProperties().setGroupId("replies");
|
||||
container.setAutoStartup(false);
|
||||
return container;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
// 功能类
|
||||
@Component
|
||||
@Slf4j
|
||||
public class Receiver {
|
||||
|
||||
@KafkaListener(id = "rpc-server", topics = "RPC-Request")
|
||||
@SendTo
|
||||
public Cargo receive(Cargo cargo) {
|
||||
log.info("Received: {}", cargo.getAction());
|
||||
return Cargo.builder().action("response").build();
|
||||
}
|
||||
}
|
||||
```
|
108
source/_posts/如何使用Kotlin协程中的suspend函数.md
Normal file
108
source/_posts/如何使用Kotlin协程中的suspend函数.md
Normal file
@@ -0,0 +1,108 @@
|
||||
---
|
||||
title: 如何使用Kotlin协程中的suspend函数
|
||||
date: 2021-04-02 16:37:14
|
||||
tags: [JVM, Kotlin, Coroutine, suspend, 协程, 并发]
|
||||
categories:
|
||||
- [JVM, Kotlin]
|
||||
---
|
||||
协程是Kotlin带来的一项明星功能。通过使用比线程更加轻量的协程,程序的性能得到了极大的提高。但是协程的运行控制有与传统的线程不尽相同,尤其是suspend函数的引入,更加使协程的使用令人迷惑。本文试图通过使用更加简单的方式对如何使用Kotlin协程进行简述。
|
||||
<!--more-->
|
||||
|
||||
> 需要注意的是,虽然Kotlin Coroutines声称其是使用的协程,但是在功能的底层实现上,依旧还是多线程,只是多线程的调度和交互已经被Kotlin Coroutines做了极大的优化。
|
||||
|
||||
@[toc]
|
||||
|
||||
## 协程的控制
|
||||
|
||||
suspend函数是发挥协程的性能威力,妥善处理异步的核心。不同于Go语言中的协程,Kotlin所提供的协程实际上是一套“线程框架”,其对任务的处理方式更加类似于Java中的Executor。所以对于协程的控制,实际上是可以借用线程的控制的。
|
||||
|
||||
在整个操作系统的概念中,是没有协程这个概念的,我们所有程序的代码都是在线程中运行的,也就是说如果没有使用多线程API专门启动其他的线程,我们的程序是以单线程的方式运行的。而线程又是依附于进程的,在一个进程中,可以存在众多的线程。协程的概念首先是Go语言提出来的,Go中的协程是一个比线程更加轻量的结构,一个线程中可以轻松的运行若干个协程,而且这个协程的结构是根植与Go语言的核心中的。
|
||||
|
||||
但是Kotlin首先是一种JVM语言,不像Go语言那样没有历史包袱。Kotlin所提供的协程,是一种可以在一个线程中运行并完成调度,然后将一些比较耗时或者需要等待的放到其他的线程中去运行,这样就可以让运行核心程序的主线程能够不被“阻塞”。所以协程的特点就是可以使用同步代码的形式写出异步的程序。
|
||||
|
||||
不同于Java中使用回调函数来处理异步,协程利用“挂起”来处理异步。suspend函数在整个协程中标记了所有耗时和需要在其他线程中处理的任务,每当协程遇到suspend函数的时候,就会把函数的执行放到其他的线程中去执行,然后将当前的协程挂起,这样程序的主线程就可以去执行其他的协程了。所以Kotlin协程的调度方法可以参考以下示意图。
|
||||
|
||||
所以,一个协程的“挂起”实际上就是让这个协程从当前的线程上脱离,去了调度器给它指定的线程去并行运行了。但是等到这个协程运行结束以后,Kotlin协程框架还会自动的把它切回来,这个操作就像是协程在之前的线程上被唤醒了一样。
|
||||
|
||||
## 协程的启动
|
||||
|
||||
在Kotlin程序中启动一个协程,可以使用`launch`、`async`、`runBlocking`。
|
||||
|
||||
```kotlin
|
||||
fun main() {
|
||||
GlobalScope.launch { // 在后台启动一个新的协程并继续
|
||||
delay(1000L)
|
||||
println("World!")
|
||||
}
|
||||
println("Hello,") // 主线程中的代码会立即执行
|
||||
runBlocking { // 但是这个表达式阻塞了主线程
|
||||
delay(2000L) // 我们延迟 2 秒来保证 JVM 的存活
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
使用`launch`和`async`的时候,需要指定协程的作用域,例如上例中的`GlobalScope`即表示程序的全局作用域,在这个作用域中是可以启动后台协程的。一个比较常见的是直接调用`launch`和`async`,这样的话新协程将会继承其父级协程的上下文,变成一个子协程。
|
||||
|
||||
启动协程的这三个函数的区别如下:
|
||||
|
||||
- `launch`,启动一个协程,但并不关心其内部的返回结果。
|
||||
- `async`,启动一个协程,可以从其中返回结果。协程的运行结果可以使用`.await()`获取。
|
||||
- `runBlocking`,启动一个协程,但是这个协程将会阻塞启动它的线程。通常会在单元测试中用到。
|
||||
|
||||
### Dispatchers
|
||||
|
||||
启动协程的函数launch、async还可以接受Dispatcher参数,用于指示即将启动的协程要如何调度,常用的调度有以下几种,并且会随着项目引入的其他依赖出现新的调度器。
|
||||
|
||||
- `Dispatchers.Main`,程序的主线程。
|
||||
- `Dispatchers.IO`,针对磁盘和网络优化的IO线程。
|
||||
- `Dispatchers.Default`,适用于CPU密集型任务的线程。
|
||||
- `Dispatchers.Swing`,Swing进行UI渲染的线程。
|
||||
- `Dispatchers.JavaFx`,JavaFx进行UI渲染的线程。
|
||||
|
||||
## suspend函数
|
||||
|
||||
其实描述到这里,suspend函数已经没有什么秘密了,`suspend`关键字实际上不执行任何挂起协程的功能,它存在的唯一意义,就是提醒开发者,这个函数是一个异步函数,需要被放到协程中去执行。所以一个suspend函数在书写起来就跟普通函数没有什么两样,例如:
|
||||
|
||||
```kotlin
|
||||
suspend fun suspendUntilDone() {
|
||||
while (!done) {
|
||||
delay(5)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
但是一般suspend函数最好还是使用`withContext`指示一下这个suspend函数需要使用哪种调度器,例如下面这个示例:
|
||||
|
||||
```kotlin
|
||||
suspend fun callAPI(id: String) = withContext(Dispatchers.IO) {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
## 合成示例
|
||||
|
||||
综合以上示例,可以组成一个附带有后台协程和IO协程以及密集计算协程的示例。
|
||||
|
||||
```kotlin
|
||||
suspend fun callApi(id: String): String = withContext(Dispatchers.IO) {
|
||||
// 执行网络访问
|
||||
return response;
|
||||
}
|
||||
|
||||
// runBlocking会将main函数转换为一个协程,否则就需要手工使用coroutineScope自行创建
|
||||
fun main(arg: Array<String>) = runBlocking {
|
||||
GlobalScope.launch {
|
||||
// 创建在GlobalScope上的协程不会继承父级作用域
|
||||
while (true) {
|
||||
// 执行一些需要在后台提供服务的功能
|
||||
...
|
||||
}
|
||||
}
|
||||
// 此处async启动的协程,其作用域与runBlocking相同,属于runBlocking的子协程
|
||||
val response = async { callApi("1") }
|
||||
response.await()
|
||||
.data.forEach { d -> launch(Dispatchers.Default) {
|
||||
// 执行需要消耗较多算力的任务
|
||||
} }
|
||||
}
|
||||
```
|
5
source/categories/index.md
Normal file
5
source/categories/index.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
title: 知识分类
|
||||
type: "categories"
|
||||
layout: "categories-list"
|
||||
---
|
5
source/tags/index.md
Normal file
5
source/tags/index.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
title: 知识标签
|
||||
type: "tags"
|
||||
layout: "tags-list"
|
||||
---
|
Reference in New Issue
Block a user