开发者博客 – IT技术 尽在开发者博客

开发者博客 – 科技是第一生产力


  • 首页

  • 归档

  • 搜索

Python MySQL数据库交互

发表于 2021-04-27

引言

本文介绍在 Python 中如何与 MySQL 数据库交互

利用 PyMySQL 数据库驱动,实现 MySQL 数据库的增删改查及事务处理

MySQL 简介

MySQL是一个关系型数据库管理系统,由瑞典MySQL AB公司开发,后来被Sun公司收购,Sun公司后来又被Oracle公司收购,目前属于Oracle旗下产品。

特点

开源 免费 不要钱 使用范围广,跨平台支持性好,提供了多种语言调用的 API。

是学习数据库开发的首选。

环境

环境名称 版本
Python 3.7.9
PyMySQL 1.0.2
MySql-Server 5.7.32

首先我们要安装 PyMySQL 数据库驱动

1
python复制代码pip install PyMySQL

如要指定版本

1
python复制代码pip install PyMySQL==1.0.2

可能默认的源安装第三库会有点慢,可以配置一下其他的镜像源。Pip安装第三方库网速慢(解决方案)

如果只想临时安装第三库快一点,可以临时使用其他镜像源。

1
python复制代码pip install -i https://pypi.tuna.tsinghua.edu.cn/simple PyMySQL==1.0.2

PyMySQL 是在 Python3.x 版本中用于连接 MySQL 服务器的一个库,Python2 中则使用 mysqldb。

准备数据

创建数据库准备数据

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
python复制代码# 创建 testdb 数据库
create databases testdb charset=utf8;

use testdb;

# 创建 employee 表
create table employee (
id int unsigned primary key auto_increment not null,
emp_num varchar(10) not null,
name varchar(10) not null,
age int not null,
sex varchar(6) not null,
salary float not null
);

# 插入员工数据
insert into employee (emp_num, name, age, sex, salary) values
('M001', '张三', 56, '男', 10000),
('F002', '李四', 50, '女', 9000),
('M003', '王五', 47, '男', 8000),
('M004', '赵六', 46, '男', 7000),
('F005', '孙七', 36, '女', 6000),
('M006', '周八', 28, '男', 5000),
('M007', '吴九', 26, '男', 4000),
('M008', '郑十', 22, '男', 3000);

Python 访问数据库流程

Python DB API访问数据库流程

引入模块

  • 在 py文件 中引入 pymysql 模块
1
python复制代码from pymysql import *

Connection 对象

  • 用于建立与数据库的连接
  • 创建对象:调用connect()方法
1
python复制代码conn = connect(参数列表)
  • 参数 host:连接的 mysql 主机,如果本机就是 localhost
  • 参数 port:连接的 mysql 主机的端口,默认是 3306
  • 参数 database:数据库的名称
  • 参数 user:连接的用户名
  • 参数 password:连接的密码
  • 参数 charset:通信采用的编码方式,推荐使用 utf8

对象的方法

  • close() 关闭连接
  • commit() 提交
  • cursor() 返回 Cursor 对象,用于执行 sql 语句并获得结果

Cursor对象

  • 用于执行sql语句,使用频度最高的语句为select、insert、update、delete
  • 获取Cursor对象:调用Connection对象的cursor()方法
1
python复制代码cursor = conn.cursor()

对象的方法

  • close() 关闭
  • execute(operation [, parameters ]) 执行sql语句,返回受影响的行数,主要用于执行 insert、update、delete 语句,也可以执行 create、alter、drop 等语句
  • fetchone() 执行查询语句时,获取查询结果集的第一个行数据,返回一个元组
  • fetchall() 执行查询时,获取结果集的所有行,一行构成一个元组,再将这些元组装入一个元组返回

对象的属性

  • rowcount 只读属性,表示最近一次 execute() 执行后受影响的行数
  • connection 获得当前连接对象

Python操作MySQL数据库

查询 MySQL 服务版本

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
python复制代码"""
Python与MySQL数据库交互练习
"""
import pymysql


def mysql_version():
"""
查询MySQL版本信息
"""

# 获取数据库连接
conn = pymysql.connect(
host='localhost', # mysql服务主机,localhost代表本地
user='root',
password='123456',
database='testdb',
port=3306
)

# 创建游标对象 cursor
cursor = conn.cursor()

# 查询mysql版本的sql语句
sql = 'select version();'

# 执行sql语句
cursor.execute(sql)

data = cursor.fetchone()

print ("Database version : %s " % data)

# 关闭数据库连接
conn.close()


def main():

mysql_version()


if __name__ == '__main__':
main()

employee 数据表的增删改

为了方便操作数据库,我把获取数据库连接和游标对象提取到一个函数里了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
python复制代码import pymysql


def get_conn(dbname):
"""
获取本地 dbname 的数据库连接及游标对象
"""
conn = pymysql.connect(
host = 'localhost',
user = 'root',
password = '123456',
database = dbname,
port = 3306
)
return conn, conn.cursor()

新增员工信息到 employee 数据表

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
python复制代码def emp_insert():
"""
新增员工信息
"""
conn, cursor = get_conn(dbname='testdb')

# 插入数据
insert_sql = """insert into employee values (NULL, 'M009', 'hui', 21, '男', 6000);"""

# 执行sql语句,返回受响应的行数
count = cursor.execute(insert_sql)
print(count)

# 使用占位符
emp_info = ('M010', 'wang', 22, '男', 7000)
insert_sql = """insert into employee values (NULL, %s, %s, %s, %s, %s);"""
count = cursor.execute(sql, emp_info)
print(count)

# 更新数据(给每一位员工涨10%工资)
update_sql = """update employee set salary=salary * 1.1;"""
cursor.execute(update_sql)

# 删除数据(删除名字为wang的员工)
delete_sql = """delete from employee where name='wang';"""
cursor.execute(delete_sql)

# 记得提交到数据库执行
conn.commit()

cursor.close()
conn.close()

更新 employee 数据表信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
python复制代码def emp_update():
"""
更新employee数据库表数据
"""
conn, cursor = get_conn(dbname='testdb')

# 更新数据(给每一位员工涨10%工资)
update_sql = """update employee set salary=salary * 1.1;"""
cursor.execute(update_sql)

# 记得提交到数据库执行
conn.commit()

cursor.close()
conn.close()

删除 employee 数据表信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
python复制代码def emp_delete():
"""
删除employee数据库表数据
"""
conn, cursor = get_conn(dbname='testdb')

# 删除数据(删除名字为wang的员工)
delete_sql = """delete from employee where name='wang';"""
cursor.execute(delete_sql)

# 记得提交到数据库执行
conn.commit()

cursor.close()
conn.close()

原employee数据表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
python复制代码mysql> select * from employee;
+----+---------+------+-----+-----+--------+
| id | emp_num | name | age | sex | salary |
+----+---------+------+-----+-----+--------+
| 1 | M001 | 张三 | 56 | 男 | 10000 |
| 2 | F002 | 李四 | 50 | 女 | 9000 |
| 3 | M003 | 王五 | 47 | 男 | 8000 |
| 4 | M004 | 赵六 | 46 | 男 | 7000 |
| 5 | F005 | 孙七 | 36 | 女 | 6000 |
| 6 | M006 | 周八 | 28 | 男 | 5000 |
| 7 | M007 | 吴九 | 26 | 男 | 4000 |
| 8 | M008 | 郑十 | 22 | 男 | 3000 |
+----+---------+------+-----+-----+--------+
8 rows in set (0.00 sec)

操作后的

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
python复制代码新增后的
mysql> select * from employee;
+----+---------+------+-----+-----+--------+
| id | emp_num | name | age | sex | salary |
+----+---------+------+-----+-----+--------+
| 1 | M001 | 张三 | 56 | 男 | 10000 |
| 2 | F002 | 李四 | 50 | 女 | 9000 |
| 3 | M003 | 王五 | 47 | 男 | 8000 |
| 4 | M004 | 赵六 | 46 | 男 | 7000 |
| 5 | F005 | 孙七 | 36 | 女 | 6000 |
| 6 | M006 | 周八 | 28 | 男 | 5000 |
| 7 | M007 | 吴九 | 26 | 男 | 4000 |
| 8 | M008 | 郑十 | 22 | 男 | 3000 |
| 16 | M009 | hui | 21 | 男 | 6000 |
| 17 | M010 | wang | 22 | 男 | 7000 |
+----+---------+------+-----+-----+--------+
10 rows in set (0.00 sec)

更新、删除后
mysql> select * from employee;
+----+---------+------+-----+-----+--------+
| id | emp_num | name | age | sex | salary |
+----+---------+------+-----+-----+--------+
| 1 | M001 | 张三 | 56 | 男 | 11000 |
| 2 | F002 | 李四 | 50 | 女 | 9900 |
| 3 | M003 | 王五 | 47 | 男 | 8800 |
| 4 | M004 | 赵六 | 46 | 男 | 7700 |
| 5 | F005 | 孙七 | 36 | 女 | 6600 |
| 6 | M006 | 周八 | 28 | 男 | 5500 |
| 7 | M007 | 吴九 | 26 | 男 | 4400 |
| 8 | M008 | 郑十 | 22 | 男 | 3300 |
| 16 | M009 | hui | 21 | 男 | 6600 |
+----+---------+------+-----+-----+--------+
9 rows in set (0.00 sec)

employee 数据表的查询

  • fetchone() 获取查询结果集的第一个行数据,返回一个元组
  • fetchall() 获取结果集的所有行,一行构成一个元组,再将这些元组装入一个元组返回

单行查询获取数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
python复制代码def emp_fetchone():
"""
单行查询employee数据表信息
"""

# 单行查询
conn, cursor = get_conn('testdb')
sql = """select * from employee where id > 3;"""
count = cursor.execute(sql)
print("查询到%d条数据:" % count)

for i in range(count):
# 一行一行获取查询结果
result = cursor.fetchone()
print(result)

cursor.close()
conn.close()

多行查询获取数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
python复制代码def emp_fetchall():
"""
多行查询employee数据表信息
"""

# 多行查询
conn, cursor = get_conn('testdb')
sql = """select * from employee;"""
count = cursor.execute(sql)
print("查询到%d条数据:" % count)

results = cursor.fetchall()
for ret in results:
print(ret)

cursor.close()
conn.close()

事务处理

为什么要有事务

事务广泛的运用于订单系统、银行系统等多种场景

例如:

A用户和B用户是银行的储户,现在A要给B转账500元,那么需要做以下几件事:

  1. 检查A的账户余额>500元;
  2. A 账户中扣除500元;
  3. B 账户中增加500元;

正常的流程走下来,A账户扣了500,B账户加了500,皆大欢喜。

那如果A账户扣了钱之后,系统出故障了呢?A白白损失了500,而B也没有收到本该属于他的500。

以上的案例中,隐藏着一个前提条件:A扣钱和B加钱,要么同时成功,要么同时失败。事务的需求就在于此

所谓事务,它是一个操作序列,这些操作要么都执行,要么都不执行,它是一个不可分割的工作单位。

例如,银行转帐工作:从一个帐号扣款并使另一个帐号增款,这两个操作要么都执行,要么都不执行。所以,应该把他们看成一个事务。事务是数据库维护数据一致性的单位,在每个事务结束时,都能保持数据一致性

事务机制可以确保数据一致性。

事务应该具有4个属性:原子性、一致性、隔离性、持久性。这四个属性通常称为 ACID 特性。

  • 原子性(atomicity)。一个事务是一个不可分割的工作单位,事务中包括的诸操作要么都做,要么都不做。
  • 一致性(consistency)。事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。
  • 隔离性(isolation)。一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
  • 持久性(durability)。持续性也称永久性(permanence),指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。

Python DB API 2.0 的事务提供了两个方法 commit() 或 rollback()。

小实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
python复制代码def transaction_test():
"""
事务演示
"""
conn, cursor = get_conn('testdb')
sql = "delete from employee where age > %s" % (20)
try:
# 执行SQL语句
cursor.execute(sql)

# 这里以除0异常,来演示现实场景
n = 1 / 0

# 向数据库提交
conn.commit()

except:
# 发生错误时回滚
print('事务回滚')
conn.rollback()

cursor.close()
conn.close()

delete from employee where age > 20 这条sql语句并没有把员工年龄20岁以上的给删掉,说明事务回滚。

源代码

源代码已上传到 Gitee PythonKnowledge: Python知识宝库,欢迎大家来访。

✍ 码字不易,还望各位大侠多多支持❤️。

公众号

新建文件夹X

大自然用数百亿年创造出我们现实世界,而程序员用几百年创造出一个完全不同的虚拟世界。我们用键盘敲出一砖一瓦,用大脑构建一切。人们把1000视为权威,我们反其道行之,捍卫1024的地位。我们不是键盘侠,我们只是平凡世界中不凡的缔造者 。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Quarkus:谁说 Java 不能用来跑 Serverle

发表于 2021-04-27

why-not-java

想到这个标题的时候,我第一时间想到的就是星爷的《唐伯虎点秋香》的这一幕。

当讨论起世界上最好的开发语言是什么的时候,Java 的粉丝们总会遇到这种场景:

吹:“Java 语法简单,容易上手!”

黑:“Java 启动慢,性能差,耗资源!”

吹:“Java 有世界上最多的程序员!”

黑:“Java 启动慢,性能差,耗资源!”

吹:“Java 生态好!”

黑:“Java 启动慢,性能差,耗资源!”

吹:“滚!”

今天我们继续说说 Quarkus,应“云”而生的 Java 框架。今天算是第三篇了,没看过的同学可以回顾一下:

  • Hello, Quarkus
  • 应”云”而生的 Java 框架 Quarkus:构建本机可执行文件

上一篇的结尾预告:试试 Quarkus 在 ArgoCD 中的应用,看下 Serverless 上的使用体验。不过不想用 ArgoCD 了,因为这 workflow 这种场景实在体现不出 Quarkus 到底有多快。但又想做 Serverless,那就想到了 Knative Serving 了。

其实,还有一个原因是比较懒,上次的镜像还可以直接拿来用。

TL;DR

废话不多说,先上结论。Quarkus 与 Spring 首个请求的响应耗时:2.5s vs 5.7s。

注:为了忽略拉取镜像的时间差异,提前 pull 镜像。

验证

环境准备

  • Kubernetes 1.18+ via minikube
  • Istio 1.9.2
  • Knative 0.22.0
  • Knative CLI (brew 安装)
  • watch (brew 安装)

环境的安装准备参考官方的文档。

镜像

资源镜像就使用上一篇文章构建的,但需要做下调整:

1
2
shell复制代码docker tag quarkus/quarkus-getting-started:distroless dev.local/quarkus/quarkus-getting-started:distroless
docker tag spring/spring-getting-started:latest dev.local/spring/spring-getting-started:latest

注:knative 会忽略 dev.local 镜像的预加载,不会在创建 knative service 的时候拉取。

然后使用 minikube image load 加载到 minikube环境中:

1
2
shell复制代码minikube image load dev.local/quarkus/quarkus-getting-started:distroless
minikube image load dev.local/spring/spring-getting-started:latest

knative 配置(可选)

修改 istio-system namespace 下的 configmap config-domain,增加新的 domain:nip.io

注:这个操作纯属个人喜好,不喜欢那个 example.com,可跳过。

获取 Istio Ingress 地址

使用命令获取 Ingress 的访问方式,这里 http2/80 后的 http://192.168.64.2:31608 就是我们需要的,记下这个 ip 和端口。

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
shell复制代码minikube service list

|------------------|----------------------------|-------------------|---------------------------|
| NAMESPACE | NAME | TARGET PORT | URL |
|------------------|----------------------------|-------------------|---------------------------|
| default | kubernetes | No node port |
| istio-system | istio-egressgateway | No node port |
| istio-system | istio-ingressgateway | status-port/15021 | http://192.168.64.2:32431 |
| | | http2/80 | http://192.168.64.2:31608 |
| | | https/443 | http://192.168.64.2:31795 |
| | | tcp/31400 | http://192.168.64.2:31369 |
| | | tls/15443 | http://192.168.64.2:30293 |
| istio-system | istiod | No node port |
| istio-system | knative-local-gateway | No node port |
| knative-eventing | broker-filter | No node port |
| knative-eventing | broker-ingress | No node port |
| knative-eventing | eventing-webhook | No node port |
| knative-eventing | imc-dispatcher | No node port |
| knative-serving | activator-service | No node port |
| knative-serving | autoscaler | No node port |
| knative-serving | autoscaler-bucket-00-of-01 | No node port |
| knative-serving | autoscaler-hpa | No node port |
| knative-serving | controller | No node port |
| knative-serving | istio-webhook | No node port |
| knative-serving | webhook | No node port |
| kube-system | kube-dns | No node port |
|------------------|----------------------------|-------------------|---------------------------|

创建 Knative service

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
yaml复制代码#quarkus
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
name: hello-quarkus
namespace: default
spec:
template:
spec:
containers:
- image: dev.local/quarkus/quarkus-getting-started:distroless
imagePullPolicy: Never
---
#spring
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
name: hello-spring
namespace: default
spec:
template:
spec:
containers:
- image: dev.local/spring/spring-getting-started:latest
imagePullPolicy: Never

通过 cli kn 命令查看下 service 的信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
shell复制代码kn service describe hello-quarkus -n default

Name: hello-quarkus
Namespace: default
Age: 21s
URL: http://hello-quarkus.default.nip.io

Revisions:
100% @latest (hello-quarkus-00001) [1] (21s)
Image: dev.local/quarkus/quarkus-getting-started:distroless

Conditions:
OK TYPE AGE REASON
++ Ready 9s
++ ConfigurationsReady 10s
++ RoutesReady 9s
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
shell复制代码kn service describe hello-spring -n default

Name: hello-spring
Namespace: default
Age: 44s
URL: http://hello-spring.default.nip.io

Revisions:
100% @latest (hello-spring-00001) [1] (44s)
Image: dev.local/spring/spring-getting-started:latest

Conditions:
OK TYPE AGE REASON
++ Ready 31s
++ ConfigurationsReady 32s
++ RoutesReady 31s

从描述信息中可以拿到服务的访问地址,分别是 http://hello-quarkus.default.nip.io 和 http://hello-spring.default.nip.io。

接下来就需要在本地主机的 hosts 中加入解析:

1
2
arduino复制代码192.168.64.2    hello-quarkus.default.nip.io
192.168.64.2 hello-spring.default.nip.io

测试

上面操作完之后,就可以使用下面的地址访问服务了。

hello-quarkus.default.nip.io:31608/hello/greet…
hello-spring.default.nip.io:31608/hello/greet…

在测试的过程中,可以通过 watch -n 1 'kubectl get po -n default | grep hello' 命令来查看 pod 的创建和销毁。

2021-04-24 09.07.56

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

盘点 SpringBoot Factories 处理流程

发表于 2021-04-26

首先分享之前的所有文章 , 欢迎点赞收藏转发三连下次一定 >>>> 😜😜😜

文章合集 : 🎁 juejin.cn/post/694164…

Github : 👉 github.com/black-ant

CASE 备份 : 👉 gitee.com/antblack/ca…

一 . 前言

之前说了 SpringBoot 自动配置的地方 , 其中就涉及到 Factories 的加载 , 这一篇详细的把这个流程过一下.

Factories 的作用是工厂类的元数据 , Spring 会通过 .factories 文件将需要初始化的类反射出来

二 . 核心类

Factories 流程中核心的类是 SpringFactoriesLoader , 在 SpringApplication run 时 , 即会加载 Factories相关属性

  • spring.factories 配置文件 : Spring 自己的一套 SPI 机制的配置文件
  • SpringFactoriesLoader 类,用于加载 spring.factories 配置文件

先看一下 SpringFactoriesLoader 主要流程

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
java复制代码SpringFactoriesLoader
SF- FACTORIES_RESOURCE_LOCATION :
SF- Map<ClassLoader,MultivalueMap> cache
M- loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader)
|- 获得接口的类名 -> factoryClass.getName()
|- 加载 FACTORIES_RESOURCE_LOCATION 配置文件,获得接口对应的实现类名们
|-> loadSpringFactories
M- loadSpringFactories(@Nullable ClassLoader classLoader)
|- 缓存已存在 , 则直接返回
|- 获得 FACTORIES_RESOURCE_LOCATION 对应的 URL 们
|- 创建 LinkedMultiValueMap<String, String> 对象 = result
|- 遍历 URL 数组
|- 获得 URL = url
|- 创建 UrlResource 对象(url)
|- 加载 "META-INF/spring.factories" 配置文件,成为 Properties 对象
|- 遍历 Properties 对象
|- 使用逗号分隔
|- 添加到 result 中
|- 加到 cache 中
M- loadFactories(Class<T> factoryClass, @Nullable ClassLoader classLoader)
?- 获得接口对应的实现类名们,然后创建对应的对象们
|- 获得 ClassLoader -> SpringFactoriesLoader.class.getClassLoader()
|- 获得接口对应的实现类名们 -> loadFactoryNames
|- 遍历 factoryNames 数组,创建实现类的对象 -> for + result.add
|- 排序 -> AnnotationAwareOrderComparator.sort(result);
M- instantiateFactory
|- 获得 Class 类
|- 判断是否实现了指定接口
|- 创建对象

三 . 流程

3.1 起点

factories 加载的起点就在 SpringApplication 中.

1
2
3
4
5
6
7
8
9
10
java复制代码public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
this.webApplicationType = WebApplicationType.deduceFromClasspath();
//获取 ApplicationContextInitializer 的 Factories
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
//获取 ApplicationListener 的 Factories
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}

3.2 Application 的 Factories 加载流程

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
java复制代码
C1- SpringApplication
M1_01- getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args)
?- 获取 SpringFactories 的实例
- 获取一个 ClassLoader , 这里就是常规的 AppClassLoader
- 通过 SpringFactoriesLoader 加载 FactoryNames
- createSpringFactoriesInstances 创建实例 -> M1_02
- 排序后返回
M1_02- createSpringFactoriesInstances
- ClassUtils.forName 获取反射类 , BeanUtils.instantiateClass 创建实例


C2- SpringFactoriesLoader
F01- Map<ClassLoader, MultiValueMap<String, String>> cache
M2_01- loadFactoryNames
- factoryClass.getName() : 获得接口的类名 -> PS_M2_01_1
- loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList())
?- 加载 FACTORIES_RESOURCE_LOCATION 配置文件,获得接口对应的实现类名们
M2_02- loadSpringFactories(@Nullable ClassLoader classLoader)
?- 加载 FACTORIES_RESOURCE_LOCATION 配置文件,获得接口对应的实现类名们
- 首先从 cache (F01)中获取 MultiValueMap , 存在直接返回
- 如果 ClassLoader 存在 , classLoader.getResources(FACTORIES_RESOURCE_LOCATION)
- 如果 ClassLoader 不存在 , ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION)
?- PS_M2_02_1 , 获取文件资源迭代器
- 迭代加载 , 先获取 UrlResource , 再获取 Properties
FOR- 迭代 Properties 属性 , 添加到 Map 中 ,放入缓存
?- PS_M2_02_2 添加详情


// PS_M2_01_1 : 此处参数为 org.springframework.context.ApplicationContextInitializer
// PS_M2_02_1 : location
- FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories"
- location 不止一个包中有 , 每个包中都可以有 spring.factories , 他们会循环迭代


// M2_02 伪代码
Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader){
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
}
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION)
:ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
result = new LinkedMultiValueMap<>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryTypeName = ((String) entry.getKey()).trim();
for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
result.add(factoryTypeName, factoryImplementationName.trim());
}
}
}
cache.put(classLoader, result);
return result;
}

PS_M2_02_2 详情:

result.add(factoryTypeName, factoryImplementationName.trim())

image.png

spring.factories 对应结构

1
2
3
4
5
6
7
8
9
10
11
properties复制代码org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,\
org.springframework.boot.context.FileEncodingApplicationListener,\
org.springframework.boot.context.config.AnsiOutputApplicationListener,\
org.springframework.boot.context.config.ConfigFileApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,\
org.springframework.boot.context.logging.LoggingApplicationListener,\
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener

可以看到 , factoryTypeName 是第一个节点 , 地下为对应的实现

getResources 和 getSystemResources 的区别

3.3 Environment 流程 加载 loadFactories

加载的起点

加载 Environment 时加载的起点是 SpringApplication , 他的加载主要来自于 SpringListener

1
2
3
4
5
6
7
8
9
10
11
12
java复制代码C- SpringApplication
M- run(String... args)
- prepareEnvironment(listeners, applicationArguments)

// 再次调用 : listener.environmentPrepared(environment)


C3- ConfigFileApplicationListener
M3_01- loadPostProcessors() : 加载

// M3_01 伪代码
return SpringFactoriesLoader.loadFactories(EnvironmentPostProcessor.class, getClass().getClassLoader());

加载流程

专门截取出来主要是因为这里调用的方法和上一个是有区别的

loadFactories 除了获取上文的集合 ,同时会通过 instantiateFactory 加载实例 , 他从“META-INF/spring”中加载并实例化给定类型的工厂实现.

1
2
3
java复制代码
C2- SpringFactoriesLoader
M2_03- loadFactories
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
java复制代码P- FACTORIES_RESOURCE_LOCATION : 
-> 静态属性,定义了读取的是 "META-INF/spring.factories" 配置文件

P- Map<ClassLoader, MultiValueMap<String, String>> cache
-> 读取 "META-INF/spring.factories" 配置文件的缓存

M- loadFactories : 获得接口对应的实现类名们,然后创建对应的对象们
-> classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
-> loadFactoryNames(factoryClass, classLoaderToUse);
?- 获得接口对应的实现类名们
-> new ArrayList<>(factoryNames.size());
FOR-> 遍历 factoryNames 数组,创建实现类的对象
-> result.add(instantiateFactory(factoryName, factoryClass, classLoaderToUse));
-> AnnotationAwareOrderComparator.sort(result); -- 排序




M- instantiateFactory
-> ClassUtils.forName(instanceClassName, classLoader);
?- 获得 Class 类
->  if (!factoryClass.isAssignableFrom(instanceClass)) {
?- 判断是否实现了指定接口
-> throw new IllegalArgumentException
-> ReflectionUtils.accessibleConstructor(instanceClass).newInstance();
-> 创建对象

其他主要使用 factories 的位置

1
2
3
4
5
6
7
8
9
10
11
12
java复制代码
// 加载 EnvironmentPostProcessor 的 Factories
C- ConfigFileApplicationListener
M- loadPostProcessors
- SpringFactoriesLoader.loadFactories(EnvironmentPostProcessor.class, getClass().getClassLoader())

C- ConfigFileApplicationListener
- SpringFactoriesLoader.loadFactories(PropertySourceLoader.class,getClass().getClassLoader());


C- AutoConfigurationImportSelector
- SpringFactoriesLoader.loadFactories(AutoConfigurationImportListener.class, this.beanClassLoader);

附录: 2者对比

PS : 看了一下好像没什么区别 , 方式一创建的大多数是 ApplicationContextInitializer 和 ApplicationListener 的对象 , 从而被设置到 SpringApplication 中

image.png

3.4 factories 文件的扫描

核心逻辑在 loadSpringFactories 方法中

1
2
3
4
5
6
7
8
9
10
11
12
13
java复制代码
C2- SpringFactoriesLoader
M2_02- loadSpringFactories(@Nullable ClassLoader classLoader)
- PropertiesLoaderUtils.loadProperties

C- PropertiesLoaderUtils
M- loadProperties : 调用 fillProperties
M- fillProperties : 后面就是读成一个 inputStream 了 , 没有看的价值
- String key = loadConvert(lr.lineBuf, 0, keyLen, convtBuf);
- String value = loadConvert(lr.lineBuf, valueStart, limit - valueStart, convtBuf);
- put(key, value);

// PS : Properties extends Hashtable<Object,Object>

3.5 使用技巧

TODO : 今天不想写了 , 看心情补

四 . 总结

基础 : SPI 机制 + Properties 扫描能力

流程 : 启动时扫描ApplicationContextInitializer + ApplicationListener , 运行时扫描其他相关Factories , 并且实例化

总结 : Factories 比较简单 ,到最后也没看懂为什么会有2种不同的加载途径 , 其底层都是使用 构造器 + instance 实现一个对象 , 猜测可能和加载顺序优化

PS : 有清楚的欢迎在评论告知一下 感谢❤❤❤

更新记录

  • V20210804 : 优化布局

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

cesium张贴卫星云图离地30万米形成戴森球效果

发表于 2021-04-26

在平面GIS开发中,有时会装下逼,在地图上贴一张卫星云图

在这里插入图片描述

作为二维生物,没有高度的概念,卫星云图与下面的地图融为一体,没有高下之分。

但在三维GIS中,如果贴个卫星云图,还紧紧地贴在地面上,仿佛膏药似的,就体现不出立体的优势了。

在这里插入图片描述

当然啦,云层应该就是离地面不到万米而已,贴上去,也看不出什么垂直高度。但为了看出效果,不妨夸张一点,让卫星云图漂浮在地球上面的太空,形成戴森球。这样才过瘾。楚帮场,丁炸桥,孔过瘾。

在这里插入图片描述

怎么弄呢?

思路是这样的:

1、构造一个矩形图元(Cesium.Primitive),此图元范围就是卫星云图的四个角;离地30万米,怕未?

2、该图元的材质(Material)是卫星云图

3、将图元加到地图中

代码如下:

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
js复制代码var rectangle = viewer.scene.primitives.add(
new Cesium.Primitive({
geometryInstances: new Cesium.GeometryInstance({
geometry: new Cesium.RectangleGeometry({
rectangle: Cesium.Rectangle.fromDegrees(w,s,e,n),
//vertexFormat: Cesium.EllipsoidSurfaceAppearance.VERTEX_FORMAT,
height: 300000
}),
}),
appearance: new Cesium.EllipsoidSurfaceAppearance({
aboveGround: true,
}),
})
);
rectangle.appearance.material = new Cesium.Material({
fabric: {
type: "Image",
uniforms: {
image: "./images/cloud.png",
alpha:0.7 //透明度
},
components : {
diffuse : 'texture2D(image, fract(repeat * materialInput.st)).rgb',
alpha : 'texture2D(image, fract(repeat * materialInput.st)).a * alpha'//设置透明度不可或缺的一句
}
},
translucent : true
});

原理并不复杂,代码也挺简单。主要是如何设置透明度这里,例子不好找。因为一般给出的例子,都只有一个属性:translucent,半透明,但出来的效果,就是卫星云图基本上不透明,没能透视出下面的地球。这个components是做啥用的,目前我还不清楚;设置透明度的时候,alpha的值居然是一长串咒语更是匪夷所思。

谨记。

参考文章:

sandcastle.cesium.com/?src=Materi…

github.com/CesiumGS/ce…


2020.08.07

这样看效果更明显

在这里插入图片描述

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

2021 年最值得使用的 Nodejs 框架

发表于 2021-04-26
  • 原文地址:Top Node.js Frameworks to use in 2021
  • 原文作者:Ronak Patel
  • 译文出自:掘金翻译计划
  • 本文永久链接:github.com/xitu/gold-m…
  • 译者:洛竹
  • 校对者:PassionPenguin、xilihuasi

Node.js 是最敏捷的服务端 web 应用平台,因为它为应用开发公司提供了构建可扩展的单一编程语言 web 平台的便利。它是最热门的开源的 JavaScript 运行时框架之一,具有跨平台属性,让我们可以在浏览器以外的环境运行代码。

Node.js 的特别之处是什么?

  1. 它有一个干净简洁的代码库。
  2. 它最适合敏捷开发和原型设计。
  3. 它有一个庞大的生态系统以提供开源库。
  4. 它可以用于更快地开发企业级可扩展的应用程序。
  5. 它基于最常用的编程语言 —— JavaScript。

市场对 Node.js 的反应如何?

NPM 趋势

图片来源:npmtrends

下面是一些关于 Node.js 的更多信息:

  • Amazon、Netflix、LinkedIn、eBay、PayPal 和 Reddit 使用 Node.js 作为他们的后端框架。
  • 43% 的 Node.js 开发者使用这个框架创建企业级应用
  • Paypal 注意到改用 Node.js 后,平均响应时间显著缩短了 35%。
  • Node.js 可以将任何现有应用的性能提高 50%。
  • Node.js 降低了 58% 的应用开发成本。
  • Statista 提到,截至 2020 年初,Node 是开发者中使用最多的框架,占 51.4%。

使用 Node.js 框架的收益

  • 易扩展性
  • 学习曲线低
  • JavaScript 全栈开发
  • 庞大而活跃的社区
  • 使用单一的代码库,以提高整体 web 性能。

2021 年最值得用的 Node.js 框架

我们已经介绍了 Node.js,并详细地了解了它的功能,现在我们可以讨论 2021 年最值得使用的 Node.js 框架啦。

1. Hapi.js

  • GitHub
    • Stars:13.1k
    • Forks:1.3k
    • 版本:v20.1.0
    • 贡献者:208
    • 使用人数:16.5k
  • NPM 周下载量:105,065
  • License:BSD-3-Clause

Hapi.js 框架流行度

Hapi.js 是众多开发者信赖的最简单、安全、可靠的框架之一。你可以使用 Hapi.js 来创建可扩展和健壮的应用程序,它具有最小的开销和开箱即用的功能。它是开发 JSON API 的顶级Node.js框架。

Hapi.js 可以被用于:

  • 网站
  • HTTP 代理应用
  • 应用程序接口服务

Hapi.js 主要特性:

  • 输入验证
  • 日志
  • 错误处理
  • 代码可重用性
  • 缓存
  • 没有外部依赖
  • 基于配置的功能
  • 集成框架:在 Node 框架中提供全面的认证和授权 API 支持。

什么时候使用 Hapi.js:

Hapi.js 是开发安全、实时、可扩展和社交媒体应用的理想选择。大多数移动应用开发者都喜欢用 Hapi.js 来创建代理和 API 服务器。

谁在使用 Hapi.js:

  • Commercetools
  • Main Stack
  • Beam
  • Taggun
  • Artifakt

2. Express.js

  • GitHub
    • Stars:52.3k
    • Forks:8.8k
    • 版本:v4.17.1
    • 贡献者:262
    • 使用人数:9.2m
  • NPM 周下载量:17,193,915
  • License:MIT

Express.js 流行度

Express.js 是一个灵活而简约的 Node.js 应用框架。这个插件并不是围绕着特定的组件构建的,因此它并不限制你使用什么技术。这就给了开发者尝试的自由。他们还可以获得闪电般的配置和纯 JavaScript 体验,这些特性使 Express.js 成为快速原型设计和敏捷开发市场的有力竞争者。

Express.js 可以被用于:

  • 单页应用
  • 多页应用
  • 混合应用

Express.js 主要特性:

  • 更快的服务端开发
  • 赋能开发者更快地构建 RESTful API
  • Express 支持 MVC 架构,但需要开发者做一些额外工作
  • 开箱支持 NoSQL 数据库

什么时候使用 Express.js:

Express.js 是快速创建 Web 应用程序和服务的理想选择,因为它有现成的 API 生成工具。它是基于 JavaScript 的全栈方案 MEAN 的一部分。这意味着你可以使用 Express.js 来制作任何基于浏览器的企业级应用。

谁在使用 Express.js:

  • FindHotel
  • Omnipresent
  • Okay
  • SiHub
  • TheDoe

3. Nest.js

  • Github
    • Stars:35.5k
    • Forks:3.4k
    • 版本:7.5.0
    • 贡献者:226
    • 使用人数:60.4k
  • NPM 周下载量:508,214
  • License:MIT

Nest.js 流行度

Nest.js 是一个服务器端应用框架,它是为了解放开发者的生产力,让他们的生活变得更轻松而打造的。开发者通常为了更好地组织和管理代码而使用这个 Node.js 框架。

Nest.js 可以被用于:

  • 编写更清晰和可重用的代码。
  • 编写具有更高层次结构的代码,如拦截器、过滤器、管道等;
  • 编写可扩展、可测试和松散型应用程序。

Nest.js 主要特性

  • 易于扩展:可与其他库一起使用。
  • 允许开发人员完全使用纯 JavaScript 进行编码。
  • 结合了函数式编程、面向对象编程和响应式编程的特点。
  • 公开框架 API,帮助开发者使用各平台上的各种第三方模块。
  • 它有一个详细且维护良好的文档。

什么时候使用 Nest.js:

Nest.js 主要用于编写具有可扩展、可测试和松散耦合特点的应用。它将 Node.js 的扩展潜力提高到了一个全新的水平。它提供了结构和灵活性的适当平衡,可以高效地管理你的大型项目的代码,并且仍然有结构感可循。

谁在使用 Nest.js:

  • Roche
  • Adidas
  • Decathlon
  • Capgemini

4. Koa.js

  • GitHub
    • Stars:30.9k
    • Forks:3k
    • 版本:2.13.1
    • 贡献者:219
    • 使用人数:160k
  • NPM 周下载量:870,944
  • License:MIT

Koa.js 框架流行度

Koa.js 是一个开源的 Node web 框架,由 Express.js 原班人马创建。通过 Koa,他们的目标是为 Web 应用和 API 创建一个更小、更有价值、更强大的平台。它提供了多种高效的方法,以让构建服务的过程更快速。

Koa.js 可以被用于:

  • 前台系统
  • 后台系统
  • 混合系统

Koa.js 主要特性:

  • 代表现代和未来
  • 与所有 Node.js 框架相比,体积更小。
  • 有一个内置的错误捕捉器,防止网站崩溃。
  • 使用 context 对象,该对象同时拥有请求和响应对象。

什么时候使用 Koa.js:

Koa.js 最适合用于创建服务器、路由、处理响应和处理错误。

谁在使用 Koa.js:

  • Paralect
  • LetzChange
  • BrainHub
  • Bulb

5. Socket.io

  • GitHub
    • Stars:52.7k
    • Forks:9.6k
    • 版本:4.0.0
    • 贡献者:189
    • 使用人数:2.2m
  • NPM 周下载量:3,617,636
  • License:MIT

socket.io 流行度

Socket.io 是用来在客户端和服务器端之间创建实时双向通信的框架。要做到这一点,客户端需要在浏览器中安装 Socket.io,服务器也要集成 Socket.io 包。这使得数据可以在数百万种形式中共享。然而,最受欢迎的方法仍然是 JSON。

Socket.io 由以下两个部分组成:

  1. JavaScript 服务:Node.js
  2. JavaScript 客户端库:Node.js

注意: Socket.io 还兼容许多其他语言,如 Java、C+、Swift、Dart、.Net 和 Python。

Socket.io 可以被用于:

  • 各种命名空间
  • 广播
  • 事件处理
  • 错误处理
  • 日志和调试
  • 聊天应用
  • 内部

Socket.io 主要特性:

  • 将信息编码为命名的 JSON 或二进制事件。
  • 在应用程序中添加“实时”能力。
  • 支持自动重新连接
  • 出色的速度和可靠性
  • 即时通讯和聊天

什么时候使用 Socket.io:

Socket.io 是最好的基于事件的实时双向通信工具之一。任何想要在应用中添加实时分析功能的人都应该使用它。Socket.io 对于实时游戏应用也很有用。在实时游戏中使用基本的 HTTP 或 HTTPS 协议是不可行的,因为这些文件很大,建立通信需要时间。在这里,我们使用体积更小的 socket 包,几乎是实时地完成工作,以获得更流畅和更好的体验。

谁在使用 Socket.io:

  • Alibaba Travels
  • Patreon
  • Trello
  • Justmop
  • Plaid

6. Meteor.js

  • Github
    — Stars:42.3k
    • Forks:5.2k
    • 版本:4.0.0
    • 贡献者:452
  • NPM:不可用
  • License:MIT

Meteor.js 是一个开源的全栈 JavaScript 平台,JavaScript 根据意图不同运行在不同的地方。JavaScript 运行在 Web 浏览器内部;然后 JavaScript 运行在 Node.js 容器内的 Meteor 服务器上,支持 HTML 片段、静态资源和 CSS 规则。

Meteor.js 可以被用于:

  • 移动应用程序全流程
  • web 应用程序全流程

Meteor.js 主要特性:

  • 纯 JavaScript
  • 干净、稳健的数据同步
  • 互操作性
  • 智能套件
  • 代码热更新

什么时候使用 Meteor.js:

Meteor.js 具有快速原型设计的能力,并能生成跨平台(Android、iOS、Web)的代码。它也是最直接的学习框架之一,因为它不遵循任何严格的结构规则。因此,Meteor.js 应该被任何希望以最少的学习曲线为多个平台创建应用程序的初级或中级开发人员使用。

谁在使用 Meteor.js:

  • Accenture
  • NetApp
  • Rocket Chat
  • Esri
  • NordStorm

7. Adonis.js

  • Github
    • Stars:9.7k
    • Forks:498
    • 版本:5.0.13
    • 贡献者:47
    • 使用人数:754
  • 周下载数:3808
  • License:MIT

adoni.js 流行度

Adonis.js 是一个 Node.js 的 MVC 框架,可以运行在所有的操作系统上。它为编写服务器端 Web 应用程序提供了一个稳定的生态系统,以让开发者专注于业务需求,如最终确定选择或排除哪个包。对于想要换个口味,正在尝试 Node.js 框架的 Laravel 开发者来说,它是理想的选择。Adonis.js为 Node.js 提供了与Laravel自然具有的相同的功能和能力。

Adonis.js 可以被用于:

  • 构建 web 应用
  • 应用程序接口服务

Adonis.js 主要特性:

  • 强大的 ORM,帮助进行安全的 SQL 查询。
  • API 和基于会话的认证系统
  • 验证和给每一个用户的输入做卫生处理。
  • 高度强调安全问题
  • 可扩展的应用分层

什么时候使用 Adonis.js:

如果你是一个正在寻找 MVC 工具的 Node.js 开发者,Adonis.js 是你的首选 Node.js 框架。然而,如果你是一个 Laravel 开发者或任何其他移动应用框架开发者,你仍然可以给 Adonis.js 一个机会,甚至从 PHP 迁移到 Node.js 也可以尝试一下 Adonis.js。

谁在使用 Adonis.js:

  • Dotgroup
  • DORMshed
  • Nina
  • Zelo
  • FindUp

8. Sails.js

  • Github
    • Stars:21.9k
    • Forks:1.9k
    • 版本:1.4.0
    • 贡献者:229
    • 使用人数:23.3k
  • 周下载数:20,457
  • License:MIT

sails.js 框架流行度

Sails.js 是又一个实时 Node.js MVC 框架。它基于 Express 构建,其 MVC 架构与 Ruby on Rails 相似。它与 Ruby on Rails 的不同之处在于,它提供了对更现代的、以数据为中心的 API 和 Web 应用开发风格的支持。

Sails.js 可以被用于:

  • 构建企业级 Node.js 应用
  • 构建前端应用
  • 构建处理 HTTP 请求的后端应用

Sails.js 主要特性:

  • 支持自动生成 REST APIs
  • 具有简单的 WebSocket 集成
  • 兼容任何流行的前端框架:Angular、Android、React、iOS、Windows 或任何自定义硬件。
  • 它还具有实时功能支持

什么时候使用 Sails.js:

任何想要一个模拟 MVC 模式的 Node.js 框架(如 Laravel 和 Ruby on Rails)、想要实现现代应用架构,并构建以数据为中心的 API 和实时应用的开发者都应该在他们的下一个项目中使用 Sails.js。

谁在使用 Sails.js:

  • Tutor Platform
  • Redox Engine
  • Brainhub
  • Created Informed
  • People Grove

总结一下

市场上有很多新的 Node.js 框架。不同的 Node.js 框架会在不同阶段帮助你开发项目,并带来很多价值和功能。合理利用这些框架,妈妈再也不用担心你的应用开发啦。

如果发现译文存在错误或其他需要改进的地方,欢迎到 掘金翻译计划 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 本文永久链接 即为本文在 GitHub 上的 MarkDown 链接。


掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 Android、iOS、前端、后端、区块链、产品、设计、人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划、官方微博、知乎专栏。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

6 张图带你彻底搞懂分布式事务 XA 模式 两阶段提交 三阶

发表于 2021-04-26

头图.png

作者 | 朱晋君
来源 | 阿里巴巴云原生公众号

XA 协议是由 X/Open 组织提出的分布式事务处理规范,主要定义了事务管理器 TM 和局部资源管理器 RM 之间的接口。目前主流的数据库,比如 oracle、DB2 都是支持 XA 协议的。

mysql 从 5.0 版本开始,innoDB 存储引擎已经支持 XA 协议,今天的源码介绍实验环境使用的是 mysql 数据库。

两阶段提交

分布式事务的两阶段提交是把整个事务提交分为 prepare 和 commit 两个阶段。以电商系统为例,分布式系统中有订单、账户和库存三个服务,如下图:

1.png

第一阶段,事务协调者向事务参与者发送 prepare 请求,事务参与者收到请求后,如果可以提交事务,回复 yes,否则回复 no。

第二阶段,如果所有事务参与者都回复了 yes,事务协调者向所有事务参与者发送 commit 请求,否则发送 rollback 请求。

两阶段提交存在三个问题:

  • 同步阻塞,本地事务在 prepare 阶段锁定资源,如果有其他事务也要修改 xiaoming 这个账户,就必须等待前面的事务完成。这样就造成了系统性能下降。
  • 协调节点单点故障,如果第一个阶段 prepare 成功了,但是第二个阶段协调节点发出 commit 指令之前宕机了,所有服务的数据资源处于锁定状态,事务将无限期地等待。
  • 数据不一致,如果第一阶段 prepare 成功了,但是第二阶段协调节点向某个节点发送 commit 命令时失败,就会导致数据不一致。

三阶段提交

为了解决两阶段提交的问题,三阶段提交做了改进:

  • 在协调节点和事务参与者都引入了超时机制。
  • 第一阶段的 prepare 阶段分成了两步,canCommi 和 preCommit。

如下图:

2.png

引入 preCommit 阶段后,协调节点会在 commit 之前再次检查各个事务参与者的状态,保证它们的状态是一致的。但是也存在问题,那就是如果第三阶段发出 rollback 请求,有的节点没有收到,那没有收到的节点会在超时之后进行提交,造成数据不一致。

XA 事务语法介绍

xa 事务的语法如下:

  1. 三阶段的第一阶段:开启 xa 事务,这里 xid 为全局事务 id:
1
sql复制代码XA {START|BEGIN} xid [JOIN|RESUME]

结束 xa 事务:

1
css复制代码XA END xid [SUSPEND [FOR MIGRATE]]
  1. 三阶段的第二阶段,即 prepare:
1
sql复制代码XA PREPARE xid
  1. 三阶段的第三阶段,即 commit/rollback:
1
2
sql复制代码XA COMMIT xid [ONE PHASE]
XA ROLLBACK xid
  1. 查看处于 PREPARE 阶段的所有事务:
1
css复制代码XA RECOVER XA RECOVER [CONVERT XID]

seata XA 简介

seata 是阿里推出的一款开源分布式事务解决方案,目前有 AT、TCC、SAGA、XA 四种模式。

seata 的 XA 模式是利用分支事务中数据库对 XA 协议的支持来实现的。我们看一下 seata 官网的介绍:[1]

3.png

从上面的图可以看到,seata XA 模式的流程跟其他模式一样:

  1. TM 开启全局事务
  2. RM 向 TC 注册分支事务
  3. RM 向 TC 报告分支事务状态
  4. TC 向 RM 发送 commit/rollback 请求
  5. TM 结束全局事务

这里介绍一下 RM 客户端初始化关联的 UML 类图:[2]

4.png

这个图中有一个类是 AbstractNettyRemotingClient,这个类的内部类 ClientHandler 来处理 TC 发来的请求并委托给父类 AbstractNettyRemoting 的 processMessage 方法来处理。processMessage 方法调用 RmBranchCommitProcessor 类的 process 方法。

需要注意的是,「seata 的 xa 模式对传统的三阶段提交做了优化,改成了两阶段提交」:

  • 第一阶段首执行 XA 开启、执行 sql、XA 结束三个步骤,之后直接执行 XA prepare。
  • 第二阶段执行 XA commit/rollback。

mysql 目前是支持 seata xa 模式的两阶段优化的。

「但是这个优化对 oracle 不支持,因为 oracle 实现的是标准的 xa 协议,即 xa end 后,协调节点向事务参与者统一发送 prepare,最后再发送 commit/rollback。这也导致了 seata 的 xa 模式对 oracle 支持不太好。」

seata XA 源码

seata 中的 XA 模式是使用数据源代理来实现的,需要手动配置数据源代理,代码如下:

1
2
3
4
5
6
7
8
9
10
typescript复制代码@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DruidDataSource druidDataSource() {
return new DruidDataSource();
}

@Bean("dataSourceProxy")
public DataSource dataSource(DruidDataSource druidDataSource) {
return new DataSourceProxyXA(druidDataSource);
}
  • 也可以根据普通 DataSource 来创建 XAConnection,但是这种方式有兼容性问题(比如 oracle),所以 seata 使用了开发者自己配置 XADataSource。
  • seata 提供的 XA 数据源代理,要求代码框架中必须使用 druid 连接池。
  1. XA 第一阶段

当 RM 收到 DML 请求后,seata 会使用 ExecuteTemplateXA来执行,执行方法 execute 中有一个地方很关键,就是把 autocommit 属性改为了 false,而 mysql 默认 autocommit 是 true。事务提交之后,还要把 autocommit 改回默认。

下面我们看一下 XA 第一阶段提交的主要代码。

1)开启 XA

上面代码标注[1]处,调用了 ConnectionProxyXA 类的 setAutoCommit 方法,这个方法的源代码中,XA start 主要做了三件事:

  • 向 TC 注册分支事务
  • 调用数据源的 XA Start
1
kotlin复制代码xaResource.start(this.xaBranchXid, XAResource.TMNOFLAGS);
  • 把 xaActive 设置为 true

RM 并没有直接使用 TC 返回的 branchId 作为 xa 数据源的 branchId,而是使用全局事务 id(xid) 和 branchId 重新构建了一个。

2)执行 sql

调用 PreparedStatementProxyXA 的 execute 执行 sql。

3)XA end/prepare

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
csharp复制代码public void commit() throws SQLException {
//省略部分源代码
try {
// XA End: Success
xaResource.end(xaBranchXid, XAResource.TMSUCCESS);
// XA Prepare
xaResource.prepare(xaBranchXid);
// Keep the Connection if necessary
keepIfNecessary();
} catch (XAException xe) {
try {
// Branch Report to TC: Failed
DefaultResourceManager.get().branchReport(BranchType.XA, xid, xaBranchXid.getBranchId(),
BranchStatus.PhaseOne_Failed, null);
} catch (TransactionException te) {
//这儿只打印了一个warn级别的日志
}
throw new SQLException(
"Failed to end(TMSUCCESS)/prepare xa branch on " + xid + "-" + xaBranchXid.getBranchId() + " since " + xe
.getMessage(), xe);
} finally {
cleanXABranchContext();
}
}

从这个源码我们看到,commit 主要做了三件事:

  • 调用数据源的 XA end
  • 调用数据源的 XA prepare
  • 向 TC 报告分支事务状态

到这里我们就可以看到,seata 把 xa 协议的前两个阶段合成了一个阶段。

  1. XA commit

这里的调用关系用一个时序图来表示:

5.png

看一下 RmBranchCommitProcessor 类的 process 方法,代码如下:

1
2
3
4
5
6
7
8
9
scss复制代码@Override
public void process(ChannelHandlerContext ctx, RpcMessage rpcMessage) throws Exception {
String remoteAddress = NetUtil.toStringAddress(ctx.channel().remoteAddress());
Object msg = rpcMessage.getBody();
if (LOGGER.isInfoEnabled()) {
LOGGER.info("rm client handle branch commit process:" + msg);
}
handleBranchCommit(rpcMessage, remoteAddress, (BranchCommitRequest) msg);
}

从调用关系时序图可以看出,上面的 handleBranchCommit 方法最终调用了 AbstractRMHandler 的 handle 方法,最后通过 branchCommit 方法调用了 ResourceManagerXA 类的 finishBranch 方法。
ResourceManagerXA 类是 XA 模式的资源管理器,看下面这个类图,也就是 seata 中资源管理器(RM)的 UML 类图:

6.png

上面的 finishBranch 方法调用了 connectionProxyXA.xaCommit 方法,我们最后看一下 xaCommit 方法:

1
2
3
4
5
6
arduino复制代码public void xaCommit(String xid, long branchId, String applicationData) throws XAException {
XAXid xaXid = XAXidBuilder.build(xid, branchId);
//因为使用mysql,这里xaResource是MysqlXAConnection
xaResource.commit(xaXid, false);
releaseIfNecessary();
}

上面调用了数据源的 commit 方法,提交了 RM 分支事务。

到这里,整个 RM 分支事务就结束了。Rollback 的代码逻辑跟 commit 类似。

最后要说明的是,上面的 xaResource,是 mysql-connector-java.jar 包中的 MysqlXAConnection 类实例,它封装了 mysql 提供的 XA 协议接口。

总结

seata 中 XA 模式的实现是使用数据源代理完成的,底层使用了数据库对 XA 协议的原生支持。

mysql 的 java 驱动库中,MysqlXAConnection 类封装类 XA 协议的底层接口供外部调用。

跟 TCC 和 SAGA 模式需要在业务代码中实现 prepare/commit/rollback 逻辑相比,XA 模式对业务代码无侵入。

Reference

[1]:http://seata.io/zh-cn/docs/overview/what-is-seata.html
[2]:https://github.com/seata/seata

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

低代码有什么应用前景?

发表于 2021-04-26

想要研究低代码平台的应用前景,首先要了解究竟谁会使用低/无代码平台。海比研究院发布的《2021中国低代码/无代码市场研究报告》显示了低/无代码平台的使用者画像:男性、85后,高学历,技术人员,互联网行业,中高收入,一二线城市为主。

用户画像.png

从购买偏好来看,按时间段付费和定制需求付费最受欢迎,按用户数量计费方式则不受使用者欢迎;数据安全、易用性、功能丰富度、流程管理便捷和数据模型丰富是影响购买的关键要素。

购买偏好.png

从低/无代码平台的使用者角度来看,最佳价格区间在【500,5000】元/年,最高可接受2万元,若超过2万元则购买亿元较低。可见使用者的价格接受度较低。

价格接受度.png

从应用类型来看,使用低/无代码平台开发的应用类型总趋势仍是移动端为主,这与中国市场的移动互联网更为普及和发达息息相关,而国外更偏向PC端。从细分应用类型来看,小程序类是现阶段开发需求最高的类型,从未来应用开发变化来看,app类应用开发需求会继续提升,将会与小程序组成第一梯队,第二梯队则是web和iOS/Android类,第三梯队是Windows/Mac和H5类。

应用开发类型.png

从业务类型来看,**现阶段以数据、设备、协同办公和研发类为主,未来新技术方面需求显著增长。**从未来需求增长趋势来看,数据仓库类、人工智能和区块链等先进技术类、生产制造类等方面均具有较高的需求增长空间。

业务类型.png

从行业场景来看,互联网、专业服务、零售、金融、制造和教育是重点应用行业场景。从细分类型对比来看,场景应用型在专业服务和制造行业应用更多,产品研发型则在教育、文旅和政府行业较为突出,平台生态型则更擅长互联网、零售、金融、交通、餐饮和医疗。

行业场景.png

之前,低代码更多作为工具帮助研发人员降低软件开发过程中部门模块的可复用性,随着可复用性模块增加和云计算、微服务架构等技术的发展,通过平台架构设计和引擎的开发逐渐抽象出低代码平台。目前,随着RPA、AI技术能力的应用普及,将会有更多的泛自动化、智能化能力赋能到企业应用中。

发展趋势.png

企业越发重视通过数字化工具实现各类需求,而这些需求呈现出用户体验要求高、场景多元化、短生命周期、高集成要求等特点,传统的商业软件或定制开发的方式,已经显然无法满足企业客户的需求,低代码平台赋予更多开发人员低/无代码开发能力,还可以对组织内的IT团队产生积极影响,使IT团队能够专注于提升其核心竞争力,低/无代码平台几乎成为了必然选择。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

久等了,网传“字节跳动总结的设计模式”,出版纸质书了【送书】

发表于 2021-04-26

一、前言

你见过网传“字节跳动总结的设计模式”吗?

对,我就是那个被网传了很久的 字节跳动·设计模式 作者,小傅哥!

只要你关注过一些技术公众号,或者看过一些技术博客,再或者搜过设计模式类文章,那么可能你的手里就有一本《重学Java设计模式》的PDF电子版。文章留言举个手,让我看看你有没有!

后来从这本电子书加上我微信的伙伴越来越多,大家都问你是字节的吗?。无可奈何,也回复不过来了,只能做个表情挡挡。

也不知道是哪个营销天才,竟然起了个这么个文章名。也不知道另一个推广天才,在全网公众号号投放开始引流。就这么一个卧龙、一个凤雏,硬生生的把我推到字节跳动,体会了一把什么是云跳槽。当时很多技术公众号号主只是当成一次普通的互推或者广告开始推广,而一些仔细阅读文章并知道我的伙伴主动联系我是否授权了他们使用,也有大批我的真情铁粉在这些文章下留言为我正身。

虽然刚开始觉得有点生气,但后来想想算了,虽然大家没有因为这本书直接关注我的公众号:bugstack虫洞栈 下载,但也让很多人知道了我,也帮助了很多人可以学习到我分享的技术内容,想想也就平静。也就从那天开始,我动心了,只有这本书重新整理出版,我才好意思把这件事说出来,哈哈哈!

二、为什么这本书火成这样?

同样是设计模式书籍,为什么这本书火了?

因为你看过的所有设计模式书籍,几乎都是以画圆圈、涂颜色、吃披萨的简单案例为基础,重在理论讲解。这也不奇怪,因为本身设计模式就是一本来自,克里斯托佛·亚历山大的著作 《建筑模式语言》为基础将设计模式的概念应用到程序开发领域中的书籍资料,它所能解决的事情是通用共性问题的统一解决方案,但它不是最终落地实现的具体结果。所以很多人初学者在看一些设计模式时并不能理解这些设计模式要什么时候用,用怎么使用。

但《重学Java设计模式》并不是纯理论书籍,它反而是一本弱化理论介绍,采用互联网真实场景开发案例(交易、营销、秒杀、中间件、源码)为基础,通过反例用if…else实现需求,再使用正例用设计模式改造代码的过程,让读者切身实际的体会设计模式的用途。而这些例子又可以实实在在的帮助你解决一些遇到的真实场景编程问题,优化你的代码实现,让整个工程可以更加易于维护和易于扩展。

而恰巧所有你之前遇到的设计模式没学到的问题,都在这本书中找到了解决方案和落地代码。所以,它火了,我也跟着有点火了!

三、全彩纸制版书籍是啥样?

重新整理、新绘图稿、加强讲解、清晰案例,一般随时在手边查阅的解决方案书籍。

欢迎再次来到这里,很高兴你将拿到这本纸质书,如果你能坚持看完并按照书中的例子进行实践,那么在编程开发的世界里,就又多了一个可以写出良好代码的人,同时也为架构师培养储备了一个人才。

本书是一本基于互联网真实案例编写的Java设计模式实践图书。全书以解决方案为核心,从实际开发业务中抽离出交易、营销、规则引擎、中间件、框架源码等22个真实场景,对设计模式进行全面、彻底的分析。帮助读者灵活地使用各种设计模式,从容应对复杂变化的业务需求,编写出易维护、可扩展的代码结构。本书融合了生动有趣的动画插图和实践开发的类结构图,让读者不仅能体会设计模式的概念和原理,更能清楚地知晓落地方法。此外,本书还介绍了DDD四层架构、RPC中间件设计、分布式领域驱动设计和设计模式的结合使用等内容。

本书适合计算机相关行业的研发人员、高等院校计算机专业的学生阅读。无论是初学者,还是中、高级研发人员都能从本书中有所获益。

有趣的动画图

清晰的类流程

当然此书好看的地方还不止于此,绝对让你体会到什么是真正的有抓手;动画图带你走进场景、一坨坨代码的反例告诉你什么样的代码不好维护、用设计模式再重构代码告诉你该怎么实现才更好、清晰的类图给你描述出每一层关系让学习更方便,所以,真香!也希望你能有一本这样,在手边可随时查阅的编程案例工具型书籍,一决像屎山似的代码!

四、📚新书5折下单

全书彩印、动画图稿、类图添加、内容夯实,是你在手边的可当编码参考的工具型书籍,好看、好用、好香!

图书节,新书5折预售,需要的可以下单了!

  • 链接:item.jd.com/13218336.ht…
  • 搜索:jd.com 搜索:重学Java设计模式
  • 直达:公众号点击阅读原文,直接进入购买链接,快!
  • 扫码:

五、🎉回馈掘金粉丝

牛吹完了,接下来回馈粉丝一波奖品,感谢一直以来对小傅哥的支持。

1. 礼品包括

  • 1-3 名:送全彩《重学Java设计模式》书籍,给掘金粉丝伙伴。(备注:你要是我的粉丝哦)
  • 4-6 名:《SpringBoot 中间件设计和开发》 掘金小册5折券
  • 7-10 名:掘金搪瓷杯,很漂亮。备货中发的会慢些

2. 得奖说明

  • 在掘金此篇首发文章进行留言,并找你的小伙伴给你的留言点赞即可
  • 以个人留言被读者点赞数量取前10名,仅记录个人攒点最高的留言

3. 活动说明

  • 时间范围:2021-04-26 - 2021-04-30,共计5天计票
  • 公布时间:2021年05月01日,假期公布中奖名单,节后发放奖品
  • 公布方式:小傅哥的朋友圈公布,记得添加小傅哥微信:fustack
  • 领奖方式:看到小傅哥朋友圈后,主动联系小傅哥。提供;收获人、收获地址、手机号等必要信息。😄嘿…嘿,我会保密的你的信息!

六、👣 收个尾

这本书的出版算是在技术成长路上的一次打卡,了解了定稿、三审三校、书号、印刷、上架等等流程,而这些其实是我,就想知道知道我没经历过的风风雨雨后面的彩虹到底有多美!

感谢我能在这一路上遇到的人遇到的事,是粉丝伙伴的陪伴、是号主好友的支持、是各平台的服务,让我从一点点做的稍有起色,但也还好初心仍在,这条路上我扔是一直坚持的少年!


下方扫码关注 bugstack虫洞栈,与小傅哥一起学习成长、共同进步,做一个码场最贵Coder!

  • 回复【面经手册】,下载《面经手册 • 拿大厂Offer》,这是一本有深度的Java核心内容,从数据结构、算法、并发编程以及JVM系8不断深入讲解,让懂了就是真的懂。
  • 回复【字节码编程】,下载《字节码编程》,此资料主要针对字节码编程系列知识栈进行编写文章学习。在字节码编程方便有三个比较常见的框架;ASM、Javassit、Byte-buddy,他们都可以使用自己的API方式进行字节码的插装,通过这样增强方法的方式就可以和Javaagent结合起来开发非入侵的全链路监控服务,以及做反射、中间件和混淆代码等

你好,我是小傅哥。一线互联网 java 工程师、架构师,开发过交易&营销、写过运营&活动、设计过中间件也倒腾过中继器、IO板卡。不只是写Java语言,也搞过C#、PHP,是一个技术活跃的折腾者。

2021年在掘金上架一本小册 《SpringBoot 中间件设计和开发》 ,16个互联网中间件场景、30个工程,是全网唯一一次手把手教你造轮子、写中间件,因为这样的技术离P7最近、离架构师最近、离高薪资最近!

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

一文精通Docker安装部署发布 Docker

发表于 2021-04-26

Docker

hub.docker.com //镜像地址

Docker安装

1
2
3
4
5
6
7
8
9
10
11
arduino复制代码//安装yum工具包 yum-utils 数据存储驱动device-mapper-persistent-data 和lvm2
yum install -y yum-utils device-mapper-persistent-data lvm2

//修改yum安装源,默认在国外下载慢
yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo

//自动检测优质的安装源
yum makecache fast

//安装
yum -y install docker-ce

启动验证

1
2
3
4
5
6
7
8
9
10
arduino复制代码//启动命令
service docker start

//查看版本
docker version

//验证是否可用,从国外中央仓库拉到本地
docker pull hello-world
//运行一下,验证是否成功
docker run hello-world

阿里云Docker镜像加速

通过阿里云的加速服务来加速访问国外仓库的操作
需要去阿里云开通服务

1
2
3
4
5
6
7
8
9
10
11
12
bash复制代码sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json <<-'EOF'
{
"registry-mirrors": ["https://xxxxx.mirror.aliyuncs.com"] //加速器地址
}
EOF
sudo systemctl daemon-reload
sudo systemctl restart docker


//拉去tomcat验证是否成功
docker pull tomcat

Docker常用命令

  • docker pull 镜像名<:tags> - 从远程仓库抽取镜像 eg.docker pull tomcat:9.0-jdk8-adoptopenjdk-openj9
  • docker images - 查看本地镜像
  • docker run 镜像名<:tags> - 创建容器,启动应用
  • docker ps - 查看正在运行中的镜像
  • docker stop 容器id 停止容器
  • docker rm <-f(强制)> 容器id - 删除容器
  • docker rmi <-f> 镜像名: - 删除镜像

宿主机与Docker容器通讯

client不能直接访问docker,需要访问宿主机然后映射到docker服务

配置映射关系并启动docker容器

  • docker run -p 8000:8080 tomcat
  • docker run -p 8000:8080 -d tomcat 后台运行

在容器中执行命令

  • 格式:docker exec [-it] 容器id 命令
  • exec 在对应容器中执行命令
  • -it 采用交互方式执行命令
  • 实例:docker exec -it 0738ed2fe68b /bin/bash

容器生命周期

image.png

Dockerfile构建镜像

介绍

Dockerfile是一个包含用于组合镜像的命令的文本文档
Docker通过读取Dockerfile中的指令按步自动生成镜像

命令

1
2
3
4
ruby复制代码FROM tomcat:latest //设置基础景象
MAINTAINER pengasan.com //设置当前景象的拥有者
WORKDIR /usr/local/tomcat/webapps //切换工作目录
ADD docker-web ./docker-web //复制docker-web 复制到容器docker-web
  • docker build -t 机构/镜像名<:tags> Dockerfile目录 //构建自定义镜像
  • eg. docker build -t pengasan.com/mywebapp:1.0

Dockerfile基础命令

  • FROM centos #制作基准镜像(基于centos:lastest)
  • LABEL & MAINTAINER - 说明信息
    • MAINTAINER pengasan.com
    • LABEL version = “1.0” ,LABEL description = “商城的项目”
  • WORKDIR - 设置工作目录
    • WORKDIR /usr/local ,WORKDIR /usr/local/newdir #自动创建
  • ADD & COPY - 复制文件
    • ADD hello / #复制到根路径
    • ADD test.tar.gz / #添加根目录并解压
    • ADD 除了复制,还具备添加远程文件功能
  • ENV - 设置环境常量
    • ENV JAVA_HOME /usr/local/openjdk8
    • RUN ${JAVA_HOME}/bin/java -jar test.jar
  • EXPOSE - 暴露容器端口 仅仅是声明
    • EXPOSE 8080
    • docker run -p 8000:8080 tomcat

Dockerfile运行命令

  • RUN : 在Build构建时执行命令
  • ENTRYPOINT : 容器启动时执行的命令
  • CMD : 容器启动后执行默认的命令或参数
    image.png

RUN构建时运行

  • RUN yum install -y vim #Shell 命令格式
  • RUN [“yum”,”install”,”-y”,”vim”] #Exec命令格式 官方推荐用这个方式

ENTRYPOINT启动命令

  • ENTRYPOINT(入口点)用于在容器启动时执行命令
  • Dockerfile中只有最后一个ENTRYPOINT会被执行
  • ENTRYPOINT [“ps”] #推荐使用Exec格式

CMD默认命令

  • CMD用于设置默认执行的命令 (默认命令 如果启动命令中有附加命令会被覆盖)
  • 如Dockerfile中出现多个CMD,则只有最后一个被执行
  • 如容器启动时附加指令,则CMD被忽略
  • CMD [“ps” , “-ef”] #推荐使用Exec格式

容器间Link单向通信

容器每次创建会都会被赋予一个ip,这个ip是动态的,当然我们容器间使用ip是天然可用通讯的但是一旦重新启动ip就会改变,这样是我们的日常工作难度大大增加,所以我们使用link通讯优化这个问题

1
2
3
4
5
6
7
8
9
10
11
12
13
bash复制代码docker run -d --name web tomcat

# -it /bin/bash 为了让容器可见,默认创建后就退出了
docker run -d --name database -it centos /bin/bash

#查看容器IP
docker inspect 容器ID/名称




#创建容器时候我们直接使用link命令使用容器间可用使用名称通讯,这个内部是由docker管理的
docker run -d --name web --link database tomcat

Bridge网桥双向通信

介绍

image.png

实现原理

image.png

命令

  • docker network ls //查看网络服务
  • docker network create -d bridge my-bridge //创建一个自定义网桥
  • docker network connect my-bridge[网桥名称] web[容器名称] //容器绑定网桥

所有绑定一个网桥的容器之间都是能互相通讯的。

Volume容器间共享数据

背景

如果一个容器中的数据发生了改变,我们不需要挨个容器去手动更新数据,直接利用volume源数据让其他容器进行读取数据

挂载宿主机命令

第一种方式 (-v的方式)
  • 格式:docker run –name 容器名 -v 宿主机路径:容器内挂载路径 镜像名
  • 实例:docker run –name tomcat1 -v /usr/webapps:/usr/local/tomcat/webapps tomcat
第二种方式 (通过共享容器内挂载点)
  • 创建共享容器:docker create –name webpage -v /webapps:/tomcat/webapps tomcat /bin/true
  • 共享容器挂载点:docker run –volumes-from webpage –name t1 -d tomcat

Docker Compose

容器编排工具
单机多容器部署工具,集群的话需要其他工具 例如k8s
通过yml文件定义多容器部署

安装

  • sudo curl -L “github.com/docker/comp… -s)-$(uname -m)” -o /usr/local/bin/docker-compose
  • sudo chmod +x /usr/local/bin/docker-compose

部署

编写yml构建脚本

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
yaml复制代码version: '3.3'

services:
db:
image: mysql:5.7
volumes:
- db_data:/var/lib/mysql
restart: always
environment:
MYSQL_ROOT_PASSWORD: somewordpress
MYSQL_DATABASE: wordpress
MYSQL_USER: wordpress
MYSQL_PASSWORD: wordpress

wordpress:
depends_on:
- db
image: wordpress:latest
ports:
- "8000:80"
restart: always
environment:
WORDPRESS_DB_HOST: db:3306
WORDPRESS_DB_USER: wordpress
WORDPRESS_DB_PASSWORD: wordpress
WORDPRESS_DB_NAME: wordpress
volumes:
db_data: {}

编译yml文件
docker-compose up -d

实战

构建Dockerfile

构建web容器

1
2
3
4
5
6
sql复制代码FROM openjdk:8u222-jre
WORKDIR /usr/local/webapp
ADD ucas.jar .
ADD application.yml
EXPOSE 80
CMD ["java","-jar","ucas.jar"]

创建镜像
docker build -t pengasan.com/ucas-web .

运行
docker run pengasan.com/ucas-web

创建数据库容器

1
2
3
sql复制代码FROM mysql:5.7
WORKDIR /docker-entrypoint-initdb.d
ADD init-db.sql .

创建镜像
docker build -t pengasan.com/ucas-db .

运行
docker run -d -e MYSQL_ROOT_PASSWORD=root pengasan.com/ucas-db

DockerCompose 一键发布

命令格式:docker-compose.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
yaml复制代码version: '3.3'
services:
db:
build: ./ucas-db/ //利用ucas-db下的dockerfile文件build
restart: always //自动重启
environment:
MYSQL_ROOT_PASSWORD: root //增加环境变量修改了root密码
app:
build: ./ucas-web/
depends_on:
- db
ports:
- "80:80"
restart: always

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

为什么大厂服务并发高却很稳定?分布式服务熔断降级限流利器至H

发表于 2021-04-26

全文概览

[TOC]

为什么需要hystrix

hystrix官网地址github

  • Hystrix同样是netfix公司在分布式系统中的贡献。同样的也进入的不维护阶段。不维护不代表被淘汰。只能说明推陈出新技术在不断迭代。曾今的辉煌曾经的设计还是值得我们去学习的。
  • 在分布式环境中,服务调度是特色也是头疼的一块。在服务治理章节我们介绍了服务治理的功能。前一课我们也介绍了ribbon、feign进行服务调用。现在自然的到了服务监控管理了。hystrix就是对服务进行隔离保护。以实现服务不会出现连带故障。导致整个系统不可用

image-20210414145650113

  • 如上图所示,当多个客户端进行服务调用Aservice时,而在分布式系统中Aservice存在三台服务,其中Aservice某些逻辑需要Bservice处理。Bservice在分布式系统中部署了两台服务。这个时候因为网络问题导致Aservice中有一台和Bservice的通信异常。如果Bservice是做日志处理的。在整个系统看来日志丢了和系统宕机比起来应该无所谓了。但是这个时候因为网络通信问题导致Aservice整个服务不可用了。有点得不尝试。

image-20210414153046542

  • 在看上图 。 A–>B–>C–>D 。此时D服务宕机了。C因为D宕机出现处理异常。但是C的线程却还在为B响应。这样随着并发请求进来时,C服务线程池出现爆满导致CPU上涨。在这个时候C服务的其他业务也会受到CPU上涨的影响导致响应变慢。

特色功能

Hystrix是一个低延迟和容错的第三方组件库。旨在隔离远程系统、服务和第三方库的访问点。官网上已经停止维护并推荐使用resilience4j。但是国内的话我们有springcloud alibaba。

Hystrix 通过隔离服务之间的访问来实现分布式系统中延迟及容错机制来解决服务雪崩场景并且基于hystrix可以提供备选方案(fallback)。

  • 对网络延迟及故障进行容错
  • 阻断分布式系统雪崩
  • 快速失败并平缓恢复
  • 服务降级
  • 实时监控、警报

99.9930=99.7%uptime0.3%of1billionrequests=3,000,000failures2+hoursdowntime/monthevenifalldependencieshaveexcellentuptime.99.99^{30} = 99.7% \quad uptime
\
0.3% \quad of \quad 1 \quad billion \quad requests \quad = \quad 3,000,000 \quad failures
\
2+ \quad hours \quad downtime/month \quad even \quad if \quad all \quad dependencies \quad have \quad excellent \quad uptime.99.9930=99.7%uptime0.3%of1billionrequests=3,000,000failures2+hoursdowntime/monthevenifalldependencieshaveexcellentuptime.

  • 上面试官网给出的一个统计。在30台服务中每台出现异常的概览是0.01%。一亿个请求就会有300000失败。这样换算下每个月至少有2小时停机。这对于互联网系统来说是致命的。

image-20210414164135471

  • 上图是官网给出的两种情况。和我们上章节的类似。都是介绍服务雪崩的场景。

项目准备

  • 在openfeign专题中我们就探讨了基于feign实现的服务熔断当时说了内部就是基于hystrix。当时我们也看了pom内部的结构在eureka中内置ribbon的同时也内置了hystrix模块。

image-20210414165134167

  • 虽然包里面包含了hystrix 。我们还是引入对应的start开启相关配置吧。这里其实就是在openfeign专题中的列子。在那个专题我们提供了PaymentServiceFallbackImpl、PaymentServiceFallbackFactoryImpl两个类作为备选方案。不过当时我们只需指出openfeign支持设置两种方式的备选方案。今天我们
1
2
3
4
5
java复制代码<!--hystrix-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>

演示下传统企业没有备选方案的情况会发生什么灾难。

image-20210414171119461

image-20210414171529143

接口测试

  • 首先我们对payment#createByOrder接口进行测试。查看下响应情况

image-20210415103254641

  • 在测试payment#getTimeout/id方法。

image-20210415103254641

+ 现在我们用jemeter来压测payment#getTimeOut/id这个接口。一位需要4S等待会照成资源消耗殆尽问题。这个时候我们的payment#createByOrder也会被阻塞。![image-20210415103254641](https://gitee.com/songjianzaina/juejin_p15/raw/master/img/33c15c25ff9b0732f9c788ea592fbd2ed1f95c8782f485805028ac07865cc073)


+ spring中默认的tomcat的最大线程数是200.为了保护我们辛苦的笔记本。这里我们将线程数设置小点。这样我们更容易复现线程被打满的情况。线程满了就会影响到payment#createByOrder接口。![image-20210415103018785](https://gitee.com/songjianzaina/juejin_p15/raw/master/img/738549560e0cf07d4dd0ca704fa0df4ffc3ad094038ae8160c977a1c88b1383b)
  • 上面我们压测的是payment的原生接口。如果压测的是order模块。如果没有在openfeign中配置fallback。那么order服务就会因为payment#getTimeOut/id接口并发导致线程满了从而导致order模块响应缓慢。这就是雪崩效应。下面我们从两个方面来解决雪崩的发生。

业务隔离

  • 上面的场景发生是因为payment#createByOrder 和payment#getTimeOut/id同属于payment服务。一个payment服务实际上就是一个Tomcat服务。同一个tomcat服务是有一个线程池的。 每次请求落到该tomcat 服务里就会去线程池中申请线程。获取到线程了才能由线程来处理请求的业务。就是因为tomcat内共享线程池。所以当payment#getTimeOut/id并发上来后就会抢空线程池。导致别的借口甚至是毫不相关的接口都没有资源可以申请。只能干巴巴的等待资源的释放。
  • 这就好比上班高峰期乘坐电梯因为某一个公司集中上班导致一段时间电梯全部被使用了。这时候国家领导过来也没办法上电梯。
  • 我们也知道这种情况很好解决。每个园区都会有专用电梯供特殊使用。
  • 我们解决上述问题也是同样的思路。进行隔离。不同的接口有不同的线程池。这样就不会造成雪崩。

线程隔离

image-20210415112638886

  • 还记得我们上面为了演示并发将order模块的最大线程数设置为10.这里我们通过测试工具调用下order/getpayment/1这个接口看看日志打印情况

image-20210415142629374

  • 我们接口调用的地方将当前线程打印出来。我们可以看到一只都是那10个线程在来回的使用。这也是上面为什么会造成雪崩现象。
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
java复制代码	@HystrixCommand(
groupKey = "order-service-getPaymentInfo",
commandKey = "getPaymentInfo",
threadPoolKey = "orderServicePaymentInfo",
commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "1000")
},
threadPoolProperties = {
@HystrixProperty(name = "coreSize" ,value = "6"),
@HystrixProperty(name = "maxQueueSize",value = "100"),
@HystrixProperty(name = "keepAliveTimeMinutes",value = "2"),
@HystrixProperty(name = "queueSizeRejectionThreshold",value = "100")

},
fallbackMethod = "getPaymentInfoFallback"
)
@RequestMapping(value = "/getpayment/{id}",method = RequestMethod.GET)
public ResultInfo getPaymentInfo(@PathVariable("id") Long id) {
log.info(Thread.currentThread().getName());
return restTemplate.getForObject(PAYMENT_URL+"/payment/get/"+id, ResultInfo.class);
}
public ResultInfo getPaymentInfoFallback(@PathVariable("id") Long id) {
log.info("已经进入备选方案了,下面交由自由线程执行"+Thread.currentThread().getName());
return new ResultInfo();
}
@HystrixCommand(
groupKey = "order-service-getpaymentTimeout",
commandKey = "getpaymentTimeout",
threadPoolKey = "orderServicegetpaymentTimeout",
commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "10000")
},
threadPoolProperties = {
@HystrixProperty(name = "coreSize" ,value = "3"),
@HystrixProperty(name = "maxQueueSize",value = "100"),
@HystrixProperty(name = "keepAliveTimeMinutes",value = "2"),
@HystrixProperty(name = "queueSizeRejectionThreshold",value = "100")

}
)
@RequestMapping(value = "/getpaymentTimeout/{id}",method = RequestMethod.GET)
public ResultInfo getpaymentTimeout(@PathVariable("id") Long id) {
log.info(Thread.currentThread().getName());
return orderPaymentService.getTimeOut(id);
}
  • 这里演示效果不好展示,我就直接展示数据吧。
并发量在getpaymentTimeout getpaymentTimeout/{id} /getpayment/{id}
20 三个线程打满后一段时间开始报错 可以正常响应;也会慢,cpu线程切换需要时间
30 同上 同上
50 同上 也会超时,因为order调用payment服务压力会受影响
  • 如果我们将hystrix加载payment原生服务就不会出现上面第三条情况。为什么我会放在order上就是想让大家看看雪崩的场景。在并发50的时候因为payment设置的最大线程也是10,他本身也是有吞吐量的。在order#getpyament/id接口虽然在order模块因为hystrix线程隔离有自己的线程运行,但是因为原生服务不给力导致自己调用超时从而影响运行的效果。这样演示也是为了后续引出fallback解决雪崩的一次场景模拟吧。
  • 我们可以在payment服务中通过hystrix设置fallback。保证payment服务低延迟从而保证order模块不会因为payment自己缓慢导致order#getpayment这种正常接口异常。
  • 还有一点虽然通过hystrix进行线程隔离了。但是我们在运行其他接口时响应时间也会稍长点。因为CPU在进行线程切换的时候是有开销的。这一点也是痛点。我们并不能随心所欲的进行线程隔离的。这就引出我们的信号量隔离了。

信号量隔离

  • 关于信号量隔离这里也就不演示了。演示的意义不是很大
1
2
3
4
5
6
7
8
java复制代码   @HystrixCommand(
commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "1000"),
@HystrixProperty(name = HystrixPropertiesManager.EXECUTION_ISOLATION_STRATEGY,value = "SEMAPHORE"),
@HystrixProperty(name = HystrixPropertiesManager.EXECUTION_ISOLATION_SEMAPHORE_MAX_CONCURRENT_REQUESTS,value = "6")
},
fallbackMethod = "getPaymentInfoFallback"
)
  • 我们如上配置表示信号量最大为6 。 表示并发6之后就会进行等待。等待超时时间未1s。
措施 优点 缺点 超时 熔断 异步
线程隔离 一个调用一个线程池;互相不干扰;保证高可用 cpu线程切换开销 √ √ √
信号量隔离 避免CPU切换。高效 在高并发场景下需要存储信号量变大 × √ ×
  • 除了线程隔离、信号量隔离等隔离手段我们可以通过请求合并、接口数据缓存等手段加强稳定性。

服务降级

触发条件

  • 程序发生除HystrixBadRequestException异常。
  • 服务调用超时
  • 服务熔断
  • 线程池、信号量不够

image-20210415180423428

  • 在上面我们的timeout接口。不管是线程隔离还是信号量隔离在条件满足的时候就会直接拒绝后续请求。这样太粗暴了。上面我们也提到了fallback。
  • 还记的上面我们order50个并发的timeout的时候会导致getpayment接口异常,当时定位了是因为原生payment服务压力撑不住导致的。如果我们在payment上加入fallback就能保证在资源不足的时候也能快速响应。这样至少能保证order#getpayment方法的可用性。

image-20210415180423428

+ 但是这种配置属于实验性配置。在真实生产中我们不可能在每个方法上配置fallback的。这样愚蠢至极。
+ hystrix除了在方法上特殊定制的fallback以外,还有一个全局的fallback。只需要在类上通过`@DefaultProperties(defaultFallback = "globalFallback")`来实现全局的备选方案。一个方法满足触发降级的条件时如果该请求对应的`HystrixCommand`注解中没有配置fallback则使用所在类的全局fallback。如果全局也没有则抛出异常。


### 不足


    - 虽然`DefaultProperties` 可以避免每个接口都配置fallback。但是这种的全局好像还不是全局的fallback。我们还是需要每个类上配置fallback。笔者查阅了资料好像也没有
    - 但是在openfeign专题里我们说了openfeign结合hystrix实现的服务降级功能。还记的里面提到了一个`FallbackFactory`这个类吗。这个类可以理解成spring的`BeanFactory`。这个类是用来产生我们所需要的`FallBack`的。我们在这个工厂里可以生成一个通用类型的fallback的代理对象。代理对象可以根据代理方法的方法签名进行入参和出参。
    - 这样我们可以在所有的openfeign地方配置这个工厂类。这样的话就避免的生成很多个fallback。 美中不足的还是需要每个地方都指定一下。关于`FallBackFactory`感兴趣的可以下载源码查看或者进主页查看openfeign专题。

服务熔断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
java复制代码  @HystrixCommand(
commandProperties = {
@HystrixProperty(name = "circuitBreaker.enabled",value = "true"), //是否开启断路器
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "10"), //请求次数
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "10000"), //时间范围
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "60"), //失败率达到多少后跳闸
},
fallbackMethod = "getInfoFallback"
)
@RequestMapping(value = "/get", method = RequestMethod.GET)
public ResultInfo get(@RequestParam Long id) {
if (id < 0) {
int i = 1 / 0;
}
log.info(Thread.currentThread().getName());
return orderPaymentService.get(id);
}
public ResultInfo getInfoFallback(@RequestParam Long id) {

return new ResultInfo();
}
  • 首先我们通过circuitBreaker.enabled=true开启熔断器
  • circuitBreaker.requestVolumeThreshold设置统计请求次数
  • circuitBreaker.sleepWindowInMilliseconds 设置时间滑动单位 , 在触发熔断后多久进行尝试开放,及俗称的半开状态
  • circuitBreaker.errorThresholdPercentage 设置触发熔断开关的临界条件
  • 上面的配置如果最近的10次请求错误率达到60% ,则触发熔断降级 , 在10S内都处于熔断状态服务进行降级。10S后半开尝试获取服务最新状态
  • 下面我们通过jmeter进行接口http://localhost/order/get?id=-1进行20次测试。虽然这20次无一例额外都会报错。但是我们会发现一开始报错是因为我们代码里的错误。后面的错误就是hystrix熔断的错误了。一开始试by zero 错误、后面就是short-circuited and fallback failed 熔断错误了

  • 正常我们在hystrix中会配置fallback , 关于fallback两种方式我们上面降级章节已经实现了。这里是为了方便看到错误的不同特意放开了。

image-20210421163842061

  • 在HystrixCommand中配置的参数基本都是在HystrixPropertiesManager对象中。我们可以看到关于熔断器的配置有6个参数。基本就是我们上面的四个配置

服务限流

  • 服务降级我们上面提到的两种隔离就是实现限流的策略。

请求合并

  • 除了熔断、降级、限流意外hystrix还为我们提供了请求合并。顾名思义就是将多个请求合并成一个请求已达到降低并发的问题。
  • 比如说我们order有个接个是查询当个订单信息order/getId?id=1 突然有一万个请求过来。为了缓解压力我们集中一下请求每100个请求调用一次order/getIds?ids=xxxxx 。这样我们最终到payment模块则是10000/100=100个请求。下面我们通过代码配置实现下请求合并。

HystrixCollapser

1
2
3
4
5
6
7
8
9
10
11
12
java复制代码@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface HystrixCollapser {
String collapserKey() default "";

String batchMethod();

Scope scope() default Scope.REQUEST;

HystrixProperty[] collapserProperties() default {};
}
属性 含义
collapserKey 唯一标识
batchMethod 请求合并处理方法。即合并后需要调用的方法
scope 作用域;两种方式[REQUEST, GLOBAL] ; REQUEST : 在同一个用户请求中达到条件将会合并GLOBAL : 任何线程的请求都会加入到这个全局统计中
HystrixProperty[] 配置相关参数

image-20210422094851902

  • 在Hystrix中所有的properties配置都会在HystrixPropertiesManager.java中。我们在里面可以找到Collapser只有两个相关的配置。分别表示最大请求数和统计时间单元。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
java复制代码	@HystrixCollapser(
scope = com.netflix.hystrix.HystrixCollapser.Scope.GLOBAL,
batchMethod = "getIds",
collapserProperties = {
@HystrixProperty(name = HystrixPropertiesManager.MAX_REQUESTS_IN_BATCH , value = "3"),
@HystrixProperty(name = HystrixPropertiesManager.TIMER_DELAY_IN_MILLISECONDS, value = "10")
}
)
@RequestMapping(value = "/getId", method = RequestMethod.GET)
public ResultInfo getId(@RequestParam Long id) {
if (id < 0) {
int i = 1 / 0;
}
log.info(Thread.currentThread().getName());
return null;
}
@HystrixCommand
public List<ResultInfo> getIds(List<Long> ids) {
System.out.println(ids.size()+"@@@@@@@@@");
return orderPaymentService.getIds(ids);
}
  • 上面我们配置了getId会走getIds请求,最多是10S三个请求会合并在一起。然后getIds有payment服务在分别去查询最终返回多个ResultInfo。

  • 我们通过jemeter进行getId接口压测,日志中ids的长度最大是3 。 验证了我们上面getId接口的配置。这样就能保证在出现高并发的时候会进行接口合并降低TPS。
  • 上面我们是通过请求方法注解进行接口合并处理。实际上内部hystrix是通过HystrixCommand

工作流程

image-20210421171613835

  • 官网给出的流程图示,并配备流程说明一共是9部。下面我们就翻译下。
  • ①、创建HystrixCommand或者HystrixObservableCommand对象
+ HystrixCommand : 用在依赖单个服务上
+ HystrixObservableCommand : 用在依赖多个服务上
  • ②、命令执行,hystrrixCommand 执行execute、queue ; hystrixObservableCommand执行observe、toObservable
方法 作用
execute 同步执行;返回结果对象或者异常抛出
queue 异步执行;返回Future对象
observe 返回Observable对象
toObservable 返回Observable对象
  • ③、查看缓存是否开启及是否命中缓存,命中则返回缓存响应
  • ④、是否熔断, 如果已经熔断则fallback降级;如果熔断器是关闭的则放行
  • ⑤、线程池、信号量是否有资源供使用。如果没有足够资源则fallback 。 有则放行
  • ⑥、执行run或者construct方法。这两个是hystrix原生的方法,java实现hystrix会实现两个方法的逻辑,springcloud已经帮我们封装了。这里就不看这两个方法了。如果执行错误或者超时则fallback。在此期间会将日志采集到监控中心。
  • ⑦、计算熔断器数据,判断是否需要尝试放行;这里统计的数据会在hystrix.stream的dashboard中查看到。方便我们定位接口健康状态
  • ⑧、在流程图中我们也能看到④、⑤、⑥都会指向fallback。 也是我们俗称的服务降级。可见服务降级是hystrix热门业务啊。
  • ⑨、返回响应

HystrixDashboard

  • hystrix 除了服务熔断、降级、限流以外,还有一个重要的特性是实时监控。并形成报表统计接口请求信息。
  • 关于hystrix的安装也很简单,只需要在项目中配置actutor和hystrix-dashboard两个模块就行了
1
2
3
4
5
6
7
8
java复制代码		<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
  • 启动类上添加EnableHystrixDashboard 就引入了dashboard了。 我们不需要进行任何开发。这个和eureka一样主需要简单的引包就可以了。

image-20210422161743942

  • 就这样dashboard搭建完成了。dashboard主要是用来监控hystrix的请求处理的。所以我们还需要在hystrix请求出将端点暴露出来。
  • 在使用了hystrix命令的模块加入如下配置即可,我就在order模块加入
1
2
3
4
5
6
7
8
9
10
11
12
13
14
java复制代码@Component
public class HystrixConfig {
@Bean
public ServletRegistrationBean getServlet(){
HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
registrationBean.setLoadOnStartup(1);
//注意这里配置的/hystrix.stream 最终访问地址就是 localhost:port/hystrix.stream ; 如果在配置文件中配置在新版本中是需要
//加上actuator 即 localhost:port/actuator
registrationBean.addUrlMappings("/hystrix.stream");
registrationBean.setName("HystrixMetricsStreamServlet");
return registrationBean;
}
}
  • 然后我们访问order模块localhost/hystrix.stream 就会出现ping的界面。表示我们order模块安装监控成功。当然order也需要actuator模块
  • 下面我们通过jmeter来压测我们的熔断、降级、限流接口在通过dashboard来看看各个状态吧。

  • 上面的动画看起来我们的服务还是很忙的。想想如果是电商当你看着每个接口的折线图像不像就是你的心跳。太高的你就担心的。太低了就没有成就高。下面我们看看dashboard的指标详情

image-20210422163536850

  • 我们看看我们服务运行期间各个接口的现状。

image-20210422161504688

聚合监控

  • 上面我们通过新建的模块hystrix-dashboard 来对我们的order模块进行监控。但是实际应用中我们不可能只在order中配置hystrix的。
  • 我们只是在上面为了演示所以在order配置的。现在我们在payment中也对hystrix中配置。那么我们就需要在dashboard中来回切换order、payment的监控数据了。
  • 所以我们的聚合监控就来了。在进行聚合监控之前我们先将payment也引入hystrix。注意上面我们是通过bean方式注入hystrix.stream 的 。 访问前缀不需要actuator

新建hystrix-turbine

pom

1
2
3
4
5
xml复制代码<!--新增hystrix dashboard-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-turbine</artifactId>
</dependency>
  • 主要就是新增turbine坐标,其他的就是hystrix , dashboard等模块,具体查看结尾处源码

yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
xml复制代码spring:
application:
name: cloud-hystrix-turbine

eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://localhost:7001/eureka
instance:
prefer-ip-address: true

# 聚合监控

turbine:
app-config: cloud-order-service,cloud-payment-service
cluster-name-expression: "'default'"
# 该处配置和url一样。如果/actuator/hystrix.stream 的则需要配置actuator
instanceUrlSuffix: hystrix.stream

启动类

启动类上添加EnableTurbine注解

image-20210423093456668




源码

上述源码

image-20210414153359481

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

1…680681682…956

开发者博客

9558 日志
1953 标签
RSS
© 2025 开发者博客
本站总访问量次
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4
0%