0%

单节点安装

一、 环境说明

  • docker: 18.09.9-ce
  • zookeeper: 3.5.6

二、 拉取 zookeeper 镜像

  • 拉取镜像
1
docker pull zookeeper

默认是摘取最新版本 zookeeper:latest

  • 查看当前镜像

img

三、 准备工作

将它部署在 /usr/local/zookeeper 目录下:

1
cd /usr/local && mkdir zookeeper && cd zookeeper

创建data目录,用于挂载容器中的数据目录:

1
mkdir data

img

四、 正式部署

  • 部署命令:
1
docker run -d -e TZ="Asia/Shanghai" -p 2181:2181 -v $PWD/data:/data --name zookeeper --restart always zookeeper
  • 命令详细说明:
1
2
3
4
5
6
-e TZ="Asia/Shanghai" # 指定上海时区 
-d # 表示在一直在后台运行容器
-p 2181:2181 # 对端口进行映射,将本地2181端口映射到容器内部的2181端口
--name # 设置创建的容器名称
-v # 将本地目录(文件)挂载到容器指定目录;
--restart always #始终重新启动zookeeper
  • 查看容器启动情况:
1
docker ps -a

img

注:状态(STATUS)为Up,说明容器已经启动成功。

五、 测试

  • 使用zk命令行客户端连接zk
1
docker run -it --rm --link zookeeper:zookeeper zookeeper zkCli.sh -server zookeeper

说明:-server zookeeper是启动zkCli.sh的参数

img

img

六、 其它命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 查看zookeeper容器实例进程信息
docker top zookeeper

# 停止zookeeper实例进程
docker stop zookeeper

# 启动zookeeper实例进程
docker start zookeeper

# 重启zookeeper实例进程
docker restart zookeeper

# 查看zookeeper进程日志
docker logs -f zookeeper

# 杀死zookeeper实例进程
docker kill -s KILL zookeeper

# 移除zookeeper实例
docker rm -f -v zookeeper

集群方式安装

集群方式选择使用docker-compose来完成。

一、安装docker-compose

1
2
curl -L https://github.com/docker/compose/releases/download/1.22.0/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose

二、配置docker-compose

编写配置文件,并将其命名为:docker-compose.ymldocker-compose默认配置文件名)
配置文件内容:

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
version: '2'
services:
zoo1:
image: zookeeper
restart: always
container_name: zoo1
ports:
- "2181:2181"
environment:
ZOO_MY_ID: 1
ZOO_SERVERS: server.1=zoo1:2888:3888 server.2=zoo2:2888:3888 server.3=zoo3:2888:3888

zoo2:
image: zookeeper
restart: always
container_name: zoo2
ports:
- "2182:2181"
environment:
ZOO_MY_ID: 2
ZOO_SERVERS: server.1=zoo1:2888:3888 server.2=zoo2:2888:3888 server.3=zoo3:2888:3888

zoo3:
image: zookeeper
restart: always
container_name: zoo3
ports:
- "2183:2181"
environment:
ZOO_MY_ID: 3
ZOO_SERVERS: server.1=zoo1:2888:3888 server.2=zoo2:2888:3888 server.3=zoo3:2888:3888

此配置文件表示,Docker需要启动三个zookeeper实例,并将2181,2182,2183三个端口号映射到容器内的2181这个端口上。
ZOO_MY_ID:表示zk服务的ID, 取值为1-255之间的整数,且必须唯一
ZOO_SERVERS:表示zk集群的主机列表

三、启动zookeeper集群

1
docker-compose up -d

该命令执行需要在docker-compose配置文件的目录下执行,结果如下:

1
2
3
4
5
[root@izbp13xko46hud9vfr5s94z conf]# docker-compose up -d 
Starting zoo1 ... done
Starting zoo2 ... done
Starting zoo3 ... done
[root@izbp13xko46hud9vfr5s94z conf]#

四、查看zookeeper集群实例

  • 通过docker ps查看

[root@izbp13xko46hud9vfr5s94z ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
e53b4c838001 zookeeper “/docker-entrypoint.…” 4 minutes ago Up 44 seconds 2888/tcp, 0.0.0.0:2181->2181/tcp, 3888/tcp zoo1
19282fb6f9b4 zookeeper “/docker-entrypoint.…” 4 minutes ago Up 44 seconds 2888/tcp, 3888/tcp, 0.0.0.0:2182->2181/tcp zoo2
099b926fa2d3 zookeeper “/docker-entrypoint.…” 4 minutes ago Up 44 seconds 2888/tcp, 3888/tcp, 0.0.0.0:2183->2181/tcp zoo3

  • 通过docker-compose ps查看
1
2
3
4
5
6
7
[root@izbp13xko46hud9vfr5s94z conf]# docker-compose ps 
Name Command State Ports
------------------------------------------------------------------------------------------
zoo1 /docker-entrypoint.sh zkSe ... Up 0.0.0.0:2181->2181/tcp, 2888/tcp, 3888/tcp
zoo2 /docker-entrypoint.sh zkSe ... Up 0.0.0.0:2182->2181/tcp, 2888/tcp, 3888/tcp
zoo3 /docker-entrypoint.sh zkSe ... Up 0.0.0.0:2183->2181/tcp, 2888/tcp, 3888/tcp
[root@izbp13xko46hud9vfr5s94z conf]#

注:这个命令需要在docker-compose配置文件下执行。

五、管理docker-compose服务

1
2
3
4
5
6
7
# 停止docker-compose服务
docker-compose stop

# 启动docker-compose服务
docker-compose start
# 重启docker-compose服务
docker-compose restart

六、查看zookeeper集群节点主从关系

使用docker exec -it zoo1 /bin/bash这个命令进入zoo1节点中,之后输入./bin/zkServer.sh statu来查看节点主从关系

1
2
3
4
5
6
[root@izbp13xko46hud9vfr5s94z conf]# docker exec -it zoo1 /bin/bash 
bash-4.4# ./bin/zkServer.sh status
ZooKeeper JMX enabled by default
Using config: /conf/zoo.cfg
Mode: follower
bash-4.4#

在这里插入图片描述
在这里插入图片描述
这个表记录的是设备的心跳数据,记录设备第一次上传心跳的时间和最后一次上传数据的时间

1
2
3
4
5
6
7
	我的sql:
insert into tb_ap_heart (ap_id, create_time, update_time)
values (#{apId,jdbcType=VARCHAR}, #{createTime,jdbcType=TIMESTAMP},
#{updateTime,jdbcType=TIMESTAMP})
ON DUPLICATE KEY UPDATE
update_time=values(update_time)
123456

根据主键只修改更新时间

通过查找资料发现on duplicate key update有一个特性就是每次是更新的情况下id也是会自增加1的,比如说现在id最大值的153889,然后进行了一次更新操作再进行一次插入操作时,id的值就变成了153891而不是153890。
再进行一次插入更新操作,id值就变成了153892
我是经过自己测试的,

我自用的解决方法: 先查询 再更新有改变的数据

简介: 答:是值传递。Java编程语言只有值传递参数。 当一个对象实例作为一个参数被传递到方法中时,参数的值就是该对象的引用一个副本。指向同一个对象,对象的内容可以在被调用的方法中改变,但对象的引用(不是引用的副本)是永远不会改变的。

答:是值传递。Java编程语言只有值传递参数

当一个对象实例作为一个参数被传递到方法中时,参数的值就是该对象的引用一个副本。指向同一个对象,对象的内容可以在被调用的方法中改变,但对象的引用(不是引用的副本)是永远不会改变的。

  1. Java中没有指针,所以也没有引用传递了,仅仅有值传递。不过,可以通过对象的方式来实现引用传递。类似java没有多继承,但可以用多次implements接口实现多继承的功能。

  2. 在Java应用程序中永远不会传递对象,而只传递对象的引用。因此是按引用传递对象。但重要的是要区分参数是如何传递的。

  3. Java应用程序按引用传递对象这一事实并不意味着 Java 应用程序按引用传递参数。参数可以是对象引用,而 Java应用程序是按值传递对象引用的。

  4. Java应用程序中的变量可以为以下两种类型之一:引用类型或基本类型。当作为参数传递给一个方法时,处理这两种类型的方式是相同的。两种类型都是按值传递的,没有一种按引用传递。

  按值传递意味着当将一个参数传递给一个函数时,函数接收的是原始值的一个副本。因此,如果函数修改了该参数,仅改变副本,而原始值保持不变。

  按引用传递意味着当将一个参数传递给一个函数时,函数接收的是原始值的内存地址,而不是值的副本。因此,如果函数修改了该参数的值,调用代码中的原始值也随之改变。如果函数修改了该参数的地址,调用代码中的原始值不会改变。

  值传递:方法调用时,实际参数把它的值传递给对应的形式参数,方法执行中,对形式参数值的改变不影响实际参数的值。

  引用传递:也称为传地址。方法调用时,实际参数的引用(地址,而不是参数的值)被传递给方法中相对应的形式参数,在方法执行中,对形式参数的操作实际上就是对实际参数的操作,方法执行中形式参数值的改变将会影响实际参数的值。

示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
// 在函数中传递的是基本数据类型
public class Test {
public static void main(String[] args) {
int a = 3;
int b = 4;
change(a, b);

System.out.println("a=" + a); // 3
System.out.println("b=" + b); // 4
}

public static void change(int i, int j) {
int temp = i;
i = j;
j = temp;
}
}

结果为:

a=3
b=4

原因:参数中传递的是基本数据类型a和b的拷贝,在函数中交换的也是那份拷贝的值,而不是数据本身。
// 在函数中传的是引用数据类型
public class Test {
public static void main(String[] args) {
int[] count = { 1, 2, 3, 4, 5 };
change(count); // 6
}

public static void change(int[] counts) {
counts[0] = 6;
System.out.println(counts[0]);
}
}

结果为:

6

原因:在方法中,传递引用数据类型int数组,实际上传递的是该数组的地址值,他们都指向数组对象,在方法中可以改变数组对象的内容。
传递的是对象的引用
class A {
int i = 0;
}

public class Test {
public static void main(String args[]) {
A a = new A();
add(a); // 1
System.out.println(a.i); // 0
}

public static void add(A a) {
a = new A(); //如果注释这行,则原值会被修改。
a.i++;
System.out.println(a.i);
}
}

结果为:

1
0

原因:在该程序中,对象的引用指向的是A,而在add方法中,传递的引用的一份副本则指向了一个新的对象,并对新的对象进行操作。而原来的A对象并没有发生任何变化。引用指向的是还是原来的A对象。
// String 不改变,数组改变
public class Example {
public static void main(String args[]) {
Example ex = new Example();
ex.change(ex.str, ex.ch);
System.out.print(ex.str + " and "); // good and
System.out.println(ex.ch); // gbc
}

String str = new String("good"); // good 是常量,不能改

char[] ch = { 'a', 'b', 'c' };

public void change(String str, char ch[]) {
str = "test ok"; // test ok 又是一个常量,也不能改   等价于 String str1 = new String("test ok");
ch[0] = 'g';
}
}

结果为:

good and gbc

原因:String类比较特别,看过String类代码的都知道,String类是final修饰的。所以值是不变的。
函数中String对象引用的副本指向了另外一个新String对象,而数组对象引用的副本没有改变,而是改变对象中数据的内容。
对于对象类型,也就是Object的子类,如果你在方法中修改了它的成员的值,那个修改是生效的,方法调用结束后,它的成员是新的值;但是如果你把它指向一个其它的对象,方法调用结束后,原来对它的引用并没用指向新的对象。

转载:https://github.com/heizemingjun

在某个module的pom.xml中添加如下配置

1
2
3
4
5
6
7
8
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.8.2</version>
<configuration>
<skip>true</skip>
</configuration>
</plugin>

然后在项目根目录执行命令:(强制更新)

1
mvn clean deploy   -U

Maven是Java项目的实时标准,掌握其使用技巧,可以提高工作效率

配置阿里巴巴源

1
2
3
4
5
6
7
8
9
settings.xml
<mirror>
<id>alimaven</id>
<name>aliyun maven</name>
<url>
http://maven.aliyun.com/nexus/content/groups/public/
</url>
<mirrorOf>central</mirrorOf>
</mirror>

配置本地存储位置

1
2
settings.xml
<localRepository>E:\m2_repository</localRepository>

只发布子模块

1
2
3
<properties>
<maven.deploy.skip>true</maven.deploy.skip>
</properties>

因为子模块会继承属性,所以需要发布的模块就要修改为true

1
2
3
<properties>
<maven.deploy.skip>false</maven.deploy.skip>
</properties>

只编译子模块

1
mvn clean package -pl mybaits-util

只发布父Pom

1
maven deploy -N(不会发布子module

分析Jar包使用情况

1
2
3
4
5
6
7
8
9
10
11
12
mvn dependency:analyze
[WARNING] Used undeclared dependencies found:
[WARNING] io.swagger:swagger-annotations:jar:1.5.20:provided
[WARNING] io.springfox:springfox-schema:jar:2.9.2:provided
[WARNING] io.springfox:springfox-swagger-common:jar:2.9.2:provided
[WARNING] io.springfox:springfox-spi:jar:2.9.2:provided
[WARNING] com.google.guava:guava:jar:27.0.1-jre:provided
[WARNING] com.fasterxml:classmate:jar:1.4.0:provided
[WARNING] io.springfox:springfox-core:jar:2.9.2:provided
[WARNING] Unused declared dependencies found:
[WARNING] io.springfox:springfox-swagger2:jar:2.9.2:provided
[WARNING] junit:junit:jar:4.12:compile

分析Jar包传递依赖

1
2
3
4
5
6
7
8
9
10
mvn dependency:tree -X
[INFO] +- org.reflections:reflections:jar:0.9.11:compile
[INFO] | +- com.google.guava:guava:jar:27.0.1-jre:compile
[INFO] | | +- com.google.guava:failureaccess:jar:1.0.1:compile
[INFO] | | +- com.google.guava:listenablefuture:jar:9999.0-empty-to-avoid-conflict-with-guava:compile
[INFO] | | +- com.google.code.findbugs:jsr305:jar:3.0.2:compile
[INFO] | | +- org.checkerframework:checker-qual:jar:2.5.2:compile
[INFO] | | +- com.google.errorprone:error_prone_annotations:jar:2.2.0:compile
[INFO] | | +- com.google.j2objc:j2objc-annotations:jar:1.1:compile
[INFO] | | \- org.codehaus.mojo:animal-sniffer-annotations:jar:1.17:compile

本地安装包

1
mvn install:install-file -Dfile=D:\projects\deep_learning\text-matching\text-matching-1.0-SNAPSHOT.jar -DgroupId=com.tenmao.dp -DartifactId=text-matching -Dversion=1.0-SNAPSHOT -Dpackaging=jar

maven helper插件

maven helperIntellij IDEA必备插件,可以很方便的定位Jar包依赖关系和Jar包冲突

img

Maven Helper插件

参考

Correctly compare float or compare double is not only Java specific problem. It can be observed in almost all the programming languages today. In computer memory, floats and doubles are stored using IEEE 754 standard format. How the actual storage and conversion works, it is out of scope of this article.

For now, just understand that during computations and conversions, minor rounding errors can be introduced in these numbers. That’s why it is not advisable to simply rely on the equality operators (==) to compare floating-point numbers.

Let’s learn how to compare float values in Java.

1
2
3
4
5
Table of Contents

1. Simple comparison [Not recommended]
2. Threshold based comparison [Recommended]
3. Compare with BigDecimal [Recommended]

First look at the simple comparison to understand what exactly is wrong with comparing double with == operator. In given program, I am creating same floating point number (i.e. 1.1) using two methods:

  1. Add .1, 11 times.
  2. Multiply .1 to 11.

In theory, both operations should produce the number 1.1. And when we compare the results of both methods, it should match.

1
private` `static` `void` `simpleFloatsComparison() ``{``  ``//Method 1``  ``double` `f1 = .``0``;``  ``for` `(``int` `i = ``1``; i <= ``11``; i++) {``    ``f1 += .``1``;``  ``}` `  ``//Method 2``  ``double` `f2 = .``1` `* ``11``;` `  ``System.out.println(``"f1 = "` `+ f1);``  ``System.out.println(``"f2 = "` `+ f2);` `  ``if` `(f1 == f2)``    ``System.out.println(``"f1 and f2 are equal\n"``);``  ``else``    ``System.out.println(``"f1 and f2 are not equal\n"``);``}

Program Output.

1
f1 = ``1.0999999999999999``f2 = ``1.1``f1 and f2 are not equal

Look at the both values printed in console. f1 is computed to 1.0999999999999999. Its exactly the problem which rounding off causes internally. That’s why, floating point comparison with '==' operator is not recommended.

Now when we know the problem with equality operator, lets solve it. Using programming, we cannot change the way these floating point numbers are stored or computed. So we have to adapt a solution where we agree that a determine the differences in both values which we can tolerate and still consider the numbers equal. This agreed upon difference in values is called the threshold or epsilon.

So now to use ‘threshold based floating point comparison‘, we can use the Math.abs() method to compute a difference between the two numbers and compare the difference to a threshold value.

1
private` `static` `void` `thresholdBasedFloatsComparison() ``{``  ``final` `double` `THRESHOLD = .``0001``;` `  ``//Method 1``  ``double` `f1 = .``0``;``  ``for` `(``int` `i = ``1``; i <= ``11``; i++) {``    ``f1 += .``1``;``  ``}` `  ``//Method 2``  ``double` `f2 = .``1` `* ``11``;` `  ``System.out.println(``"f1 = "` `+ f1);``  ``System.out.println(``"f2 = "` `+ f2);` `  ``if` `(Math.abs(f1 - f2) < THRESHOLD)``    ``System.out.println(``"f1 and f2 are equal using threshold\n"``);``  ``else``    ``System.out.println(``"f1 and f2 are not equal using threshold\n"``);``}

Program Output.

1
f1 = ``1.0999999999999999``f2 = ``1.1``f1 and f2 are equal using threshold

In BigDecimal class, you can specify the rounding mode and exact precision which you want to use. Using the exact precision limit, rounding errors are mostly solved.

Best part is that BigDecimal numbers are immutable i.e. if you create a BigDecimal BD with value "1.23", that object will remain "1.23" and can never be changed. This class provide many methods which can be used to do numerical operations on it’s value.

You can use it’s compareTo() method to compare to BigDecimal numbers. It ignore the scale while comparing.

a.compareTo(b);

Method returns:

-1 – if a < b)

0 – if a == b

1 – if a > b

Never use the equals() method to compare BigDecimal instances. That is because this equals function will compare the scale. If the scale is different, equals() will return false, even if they are the same number mathematically.

Java program to compare double with BigDecimal class.

1
private` `static` `void` `testBdEquality() ``{``   ``BigDecimal a = ``new` `BigDecimal(``"2.00"``);``   ``BigDecimal b = ``new` `BigDecimal(``"2.0"``);` `   ``System.out.println(a.equals(b));      ``// false` `   ``System.out.println(a.compareTo(b) == ``0``);  ``// true``}

Now just to verify, let’s solve out original problem using BigDecimal class.

1
private` `static` `void` `bigDecimalComparison() ``{``  ``//Method 1``  ``BigDecimal f1 = ``new` `BigDecimal(``"0.0"``);``  ``BigDecimal pointOne = ``new` `BigDecimal(``"0.1"``);``  ``for` `(``int` `i = ``1``; i <= ``11``; i++) {``    ``f1 = f1.add(pointOne);``  ``}` `  ``//Method 2``  ``BigDecimal f2 = ``new` `BigDecimal(``"0.1"``);``  ``BigDecimal eleven = ``new` `BigDecimal(``"11"``);``  ``f2 = f2.multiply(eleven);` `  ``System.out.println(``"f1 = "` `+ f1);``  ``System.out.println(``"f2 = "` `+ f2);` `  ``if` `(f1.compareTo(f2) == ``0``)``    ``System.out.println(``"f1 and f2 are equal using BigDecimal\n"``);``  ``else``    ``System.out.println(``"f1 and f2 are not equal using BigDecimal\n"``);``}

Program Output.

1
f1 = ``1.1``f2 = ``1.1``f1 and f2 are equal using BigDecimal

That’s all about comparing floating point numbers in java. Share your thoughts in comments section.

Happy Learning !!

for adding it really makes no difference, but this

1
2
3
Calendar c = Calendar.getInstance();
System.out.println(c.get(Calendar.DAY_OF_MONTH));
System.out.println(c.get(Calendar.DAY_OF_YEAR));

prints

1
2
28
363

dnf

新一代的RPM软件包管理器

补充说明

DNF 是新一代的rpm软件包管理器。他首先出现在 Fedora 18 这个发行版中。而最近,它取代了yum,正式成为 Fedora 22 的包管理器。

DNF包管理器克服了YUM包管理器的一些瓶颈,提升了包括用户体验,内存占用,依赖分析,运行速度等多方面的内容。DNF使用 RPM, libsolv 和 hawkey 库进行包管理操作。尽管它没有预装在 CentOS 和 RHEL 7 中,但你可以在使用 YUM 的同时使用 DNF 。你可以在这里获得关于 DNF 的更多知识:《 DNF 代替 YUM ,你所不知道的缘由》

DNF 的最新稳定发行版版本号是 1.0,发行日期是2015年5月11日。 这一版本的额 DNF 包管理器(包括在他之前的所有版本) 都大部分采用 Python 编写,发行许可为GPL v2.

安装 DNF 包管理器

DNF 并未默认安装在 RHEL 或 CentOS 7系统中,但是 Fedora 22 已经默认使用 DNF .

1、为了安装 DNF ,您必须先安装并启用 epel-release 依赖。

在系统中执行以下命令:

1
yum install epel-release

或者

1
yum install epel-release -y

其实这里并没有强制使用”-y”的理由,相反的,在不使用”-y”的情况下,用户可以在安装过程中查看到底有哪些东西被安装进了系统。但对于没有这个需求的用户,您可以在 YUM 中使用”-y”参数来自动安装所有东西。

2、使用 epel-release 依赖中的 YUM 命令来安装 DNF 包。在系统中执行以下命令:

1
yum install dnf

然后, DNF 包管理器就被成功的安装到你的系统中了。接下来,是时候开始我们的教程了!在这个教程中,您将会学到27个用于 DNF 包管理器的命令。使用这些命令,你可以方便有效的管理您系统中的 RPM 软件包。现在,让我们开始学习 DNF 包管理器的27条常用命令吧!

查看 DNF 包管理器版本

用处:该命令用于查看安装在您系统中的 DNF 包管理器的版本

1
dnf –version

!Check-DNF-Version

查看系统中可用的 DNF 软件库

用处:该命令用于显示系统中可用的 DNF 软件库

1
dnf repolist

查看系统中可用和不可用的所有的 DNF 软件库

用处:该命令用于显示系统中可用和不可用的所有的 DNF 软件库

1
dnf repolist all

列出所有 RPM 包

用处:该命令用于列出用户系统上的所有来自软件库的可用软件包和所有已经安装在系统上的软件包

1
dnf list

列出所有安装了的 RPM 包

用处:该命令用于列出所有安装了的 RPM 包

1
dnf list installed

列出所有可供安装的 RPM 包

用处:该命令用于列出来自所有可用软件库的可供安装的软件包

1
dnf list available

搜索软件库中的 RPM 包

用处:当你不知道你想要安装的软件的准确名称时,你可以用该命令来搜索软件包。你需要在”search”参数后面键入软件的部分名称来搜索。(在本例中我们使用”nano”)

1
dnf search nano

查找某一文件的提供者

用处:当你想要查看是哪个软件包提供了系统中的某一文件时,你可以使用这条命令。(在本例中,我们将查找”/bin/bash”这个文件的提供者)

1
dnf provides /bin/bash

查看软件包详情

用处:当你想在安装某一个软件包之前查看它的详细信息时,这条命令可以帮到你。(在本例中,我们将查看”nano”这一软件包的详细信息)

1
dnf info nano

安装软件包

用处:使用该命令,系统将会自动安装对应的软件及其所需的所有依赖(在本例中,我们将用该命令安装nano软件)

1
dnf install nano

升级软件包

用处:该命令用于升级制定软件包(在本例中,我们将用命令升级”systemd”这一软件包)

1
dnf update systemd

检查系统软件包的更新

用处:该命令用于检查系统中所有软件包的更新

1
dnf check-update

升级所有系统软件包

用处:该命令用于升级系统中所有有可用升级的软件包

1
dnf update 或 dnf upgrade

删除软件包

用处:删除系统中指定的软件包(在本例中我们将使用命令删除”nano”这一软件包)

1
dnf remove nano 或 dnf erase nano

删除无用孤立的软件包

用处:当没有软件再依赖它们时,某一些用于解决特定软件依赖的软件包将会变得没有存在的意义,该命令就是用来自动移除这些没用的孤立软件包。

1
dnf autoremove

删除缓存的无用软件包

用处:在使用 DNF 的过程中,会因为各种原因在系统中残留各种过时的文件和未完成的编译工程。我们可以使用该命令来删除这些没用的垃圾文件。

1
dnf clean all

获取有关某条命令的使用帮助

用处:该命令用于获取有关某条命令的使用帮助(包括可用于该命令的参数和该命令的用途说明)(本例中我们将使用命令获取有关命令”clean”的使用帮助)

1
dnf help clean

查看所有的 DNF 命令及其用途

用处:该命令用于列出所有的 DNF 命令及其用途

1
dnf help

查看 DNF 命令的执行历史

用处:您可以使用该命令来查看您系统上 DNF 命令的执行历史。通过这个手段您可以知道在自您使用 DNF 开始有什么软件被安装和卸载。

1
dnf history

查看所有的软件包组

用处:该命令用于列出所有的软件包组

1
dnf grouplist

安装一个软件包组

用处:该命令用于安装一个软件包组(本例中,我们将用命令安装”Educational Software”这个软件包组)

1
dnf groupinstall ‘Educational Software’

升级一个软件包组中的软件包

用处:该命令用于升级一个软件包组中的软件包(本例中,我们将用命令升级”Educational Software”这个软件包组中的软件)

1
dnf groupupdate ‘Educational Software’

删除一个软件包组

用处:该命令用于删除一个软件包组(本例中,我们将用命令删除”Educational Software”这个软件包组)

1
dnf groupremove ‘Educational Software’

从特定的软件包库安装特定的软件

用处:该命令用于从特定的软件包库安装特定的软件(本例中我们将使用命令从软件包库 epel 中安装 phpmyadmin 软件包)

1
dnf –enablerepo=epel install phpmyadmin

更新软件包到最新的稳定发行版

用处:该命令可以通过所有可用的软件源将已经安装的所有软件包更新到最新的稳定发行版

1
dnf distro-sync

重新安装特定软件包

用处:该命令用于重新安装特定软件包(本例中,我们将使用命令重新安装”nano”这个软件包)

1
dnf reinstall nano

回滚某个特定软件的版本

用处:该命令用于降低特定软件包的版本(如果可能的话)(本例中,我们将使用命令降低”acpid”这个软件包的版本)

1
dnf downgrade acpid

样例输出:

1
2
3
Using metadata from Wed May 20 12:44:59 2015
No match for available package: acpid-2.0.19-5.el7.x86_64
Error: Nothing to do.

原作者注:在执行这条命令的时候, DNF 并没有按照我期望的那样降级指定的软件(“acpid”)。该问题已经上报。

总结

DNF 包管理器作为 YUM 包管理器的升级替代品,它能自动完成更多的操作。但在我看来,正因如此,所以 DNF 包管理器不会太受那些经验老道的 Linux 系统管理者的欢迎。举例如下:

  1. 在 DNF 中没有 –skip-broken 命令,并且没有替代命令供选择。
  2. 在 DNF 中没有判断哪个包提供了指定依赖的 resolvedep 命令。
  3. 在 DNF 中没有用来列出某个软件依赖包的 deplist 命令。
  4. 当你在 DNF 中排除了某个软件库,那么该操作将会影响到你之后所有的操作,不像在 YUM 下那样,你的排除操作只会咋升级和安装软件时才起作用。

使用Python FastAPI构建Web服务

FastAPI 是一个使用 Python 编写的 Web 框架,还应用了 Python asyncio 库中最新的优化。本文将会介绍如何搭建基于容器的开发环境,还会展示如何使用 FastAPI 实现一个小型 Web 服务。

FastAPI 是一个使用 Python 编写的 Web 框架,还应用了 Python asyncio 库中最新的优化。本文将会介绍如何搭建基于容器的开发环境,还会展示如何使用 FastAPI 实现一个小型 Web 服务。

起步

我们将使用 Fedora 作为基础镜像来搭建开发环境,并使用 Dockerfile 为镜像注入 FastAPI、Uvicornaiofiles 这几个包。

1
FROM fedora:32RUN dnf install -y python-pip \    && dnf clean all \    && pip install fastapi uvicorn aiofilesWORKDIR /srvCMD ["uvicorn", "main:app", "--reload"]

在工作目录下保存 Dockerfile 之后,执行 podman 命令构建容器镜像。

1
$ podman build -t fastapi .$ podman imagesREPOSITORY TAG IMAGE ID CREATED SIZElocalhost/fastapi latest 01e974cabe8b 18 seconds ago 326 MB

下面我们可以开始创建一个简单的 FastAPI 应用程序,并通过容器镜像运行。

1
from fastapi import FastAPI app = FastAPI() @app.get("/")async def root():    return {"message": "Hello Fedora Magazine!"}

将上面的代码保存到 main.py 文件中,然后执行以下命令开始运行:

1
$ podman run --rm -v $PWD:/srv:z -p 8000:8000 --name fastapi -d fastapi$ curl http://127.0.0.1:8000{"message":"Hello Fedora Magazine!"

这样,一个基于 FastAPI 的 Web 服务就跑起来了。由于指定了 --reload 参数,一旦 main.py 文件发生了改变,整个应用都会自动重新加载。你可以尝试将返回信息 "Hello Fedora Magazine!" 修改为其它内容,然后观察效果。

可以使用以下命令停止应用程序:

1
$ podman stop fastapi

构建一个小型 Web 服务

接下来我们会构建一个需要 I/O 操作的应用程序,通过这个应用程序,我们可以看到 FastAPI 自身的特点,以及它在性能上有什么优势(可以在这里参考 FastAPI 和其它 Python Web 框架的对比)。为简单起见,我们直接使用 dnf history 命令的输出来作为这个应用程序使用的数据。

首先将 dnf history 命令的输出保存到文件。

1
$ dnf history | tail --lines=+3 > history.txt

在上面的命令中,我们使用 tail 去除了 dnf history 输出内容中无用的表头信息。剩余的每一条 dnf 事务都包括了以下信息:

  • id:事务编号(每次运行一条新事务时该编号都会递增)
  • command:事务中运行的 dnf 命令
  • date:执行事务的日期和时间

然后修改 main.py 文件将相关的数据结构添加进去。

1
from fastapi import FastAPIfrom pydantic import BaseModel app = FastAPI() class DnfTransaction(BaseModel):    id: int    command: str    date: str

FastAPI 自带的 pydantic 库让你可以轻松定义一个数据类,其中的类型注释对数据的验证也提供了方便。

再增加一个函数,用于从 history.txt 文件中读取数据。

1
import aiofiles from fastapi import FastAPIfrom pydantic import BaseModel app = FastAPI() class DnfTransaction(BaseModel):    id: int    command: str    date: str  async def read_history():    transactions = []    async with aiofiles.open("history.txt") as f:        async for line in f:            transactions.append(DnfTransaction(                id=line.split("|")[0].strip(" "),                command=line.split("|")[1].strip(" "),                date=line.split("|")[2].strip(" ")))    return transactions

这个函数中使用了 aiofiles 库,这个库提供了一个异步 API 来处理 Python 中的文件,因此打开文件或读取文件的时候不会阻塞其它对服务器的请求。

最后,修改 root 函数,让它返回事务列表中的数据。

1
@app.get("/")async def read_root():    return await read_history()

执行以下命令就可以看到应用程序的输出内容了。

1
$ curl http://127.0.0.1:8000 | python -m json.tool[{"id": 103,"command": "update","date": "2020-05-25 08:35"},{"id": 102,"command": "update","date": "2020-05-23 15:46"},{"id": 101,"command": "update","date": "2020-05-22 11:32"},....]

总结

FastAPI 提供了一种使用 asyncio 构建 Web 服务的简单方法,因此它在 Python Web 框架的生态中日趋流行。要了解 FastAPI 的更多信息,欢迎查阅 FastAPI 文档

本文中的代码可以在 GitHub 上找到。