plantegg

java tcp mysql performance network docker Linux

MySQL JDBC StreamResult 和 net_write_timeout

MySQL JDBC 拉取数据的三种方式

MySQL JDBC 在从 MySQL 拉取数据的时候有三种方式

  1. 简单模式,也就是默认模式,数据都先要从MySQL Server发到client的OS TCP buffer,然后JDBC把 OS buffer读取到JVM内存中,读取到JVM内存的过程中憋着不让client读取,全部读完再通知inputStream.read(). 数据大的话容易导致JVM OOM
  2. useCursorFetch=true,配合FetchSize,也就是MySQL Server把查到的数据先缓存到本地磁盘,然后按照FetchSize挨个发给client。这需要占用MySQL很高的IOPS(先写磁盘缓存),其次每次Fetch需要一个RTT,效率不高。
  3. Stream读取,Stream读取是在执行SQL前设置FetchSize:statement.setFetchSize(Integer.MIN_VALUE),同时确保游标是只读、向前滚动的(为游标的默认值),MySQL JDBC内置的操作方法是将Statement强制转换为:com.mysql.jdbc.StatementImpl,调用其方法:enableStreamingResults(),这2者达到的效果是一致的,都是启动Stream流方式读取数据。这个时候MySQL不停地发数据,inputStream.read()不停地读取。一般来说发数据更快些,很快client的OS TCP recv buffer就满了,这时MySQL停下来等buffer有空闲就继续发数据。等待过程中如果超过 net_write_timeout MySQL就会报错,中断这次查询。

从这里的描述来看,数据小的时候第一种方式还能接受,但是数据大了容易OOM,方式三看起来不错,但是要特别注意 net_write_timeout。

1和3对MySQL Server来说处理上没有啥区别,也感知不到这两种方式的不同。只是对1来说从OS Buffer中的数据复制到JVM内存中速度快,JVM攒多了数据内存就容易爆掉;对3来说JDBC一条条将OS Buffer中的数据复制到JVM(内存复制速度快)同时返回给execute挨个处理(慢),一般来说挨个处理要慢一些,这就导致了从OS Buffer中复制数据较慢,容易导致 TCP Receive Buffer满了,那么MySQL Server感知到的就是TCP 传输窗口为0了,导致暂停传输数据。

在数据量很小的时候方式三没什么优势,因为总是多一次set net_write_tiemout,也就是多了一次RTT。

img

MySQL timeout

  1. Creates a statement by calling Connection.createStatement().
  2. Calls Statement.executeQuery().
  3. The statement transmits the Query to MySqlServer by using the internal connection.
  4. The statement creates a new timeout-execution thread for timeout process.
  5. For version 5.1.x, it changes to assign 1 thread for each connection.
  6. Registers the timeout execution to the thread.
  7. Timeout occurs.
  8. The timeout-execution thread creates a connection that has the same configurations as the statement.
  9. Transmits the cancel Query (KILL QUERY “connectionId“) by using the connection.

Figure 6: QueryTimeout Execution Process for MySQL JDBC Statement (5.0.8).

参考《揭秘JDBC超时机制》

net_read_timeout

Command-Line Format --net-read-timeout=#
System Variable net_read_timeout
Scope Global, Session
Dynamic Yes
Type Integer
Default Value 30
Minimum Value 1
Maximum Value 31536000
Unit seconds

The number of seconds to wait for more data from a connection before aborting the read. When the server is reading from the client, net_read_timeout is the timeout value controlling when to abort.

如下图,MySQL Server监听3017端口,195228号包 客户端发一个SQL 给 MySQL Server,但是似乎这个时候正好网络异常,30秒钟后(从 SQL 请求的前一个 ack 开始算,Server应该一直都没有收到),Server 端触发 net_read_timeout 超时异常(疑问:这里没有 net_read_timeout 描述的读取了一半的现象)

image-20230209155545142

解决方案:建议调大 net_read_timeout 以应对可能出现的网络丢包

net_write_timeout

show processlist 看到的State的值一直处于**“Sending to client”**,说明SQL这个语句已经执行完毕,而此时由于请求的数据太多,MySQL不停写入net buffer,而net buffer又不停的将数据写入服务端的网络棧,服务器端的网络栈(socket send buffer)被写满了,又没有被客户端读取并消化,这时读数据的流程就被MySQL暂停了。直到客户端完全读取了服务端网络棧的数据,这个状态才会消失。

先看下 net_write_timeout的解释:The number of seconds to wait for a block to be written to a connection before aborting the write. 只针对执行查询中的等待超时,网络不好,tcp buffer满了(应用迟迟不读走数据)等容易导致mysql server端报net_write_timeout错误,指的是mysql server hang在那里长时间无法发送查询结果。

Property Value
Command-Line Format --net-write-timeout=#
System Variable net_write_timeout
Scope Global, Session
Dynamic Yes
Type Integer
Default Value 60
Minimum Value 1

报这个错就是RDS等了net_write_timeout这么久没写数据,可能是客户端卡死没有读走数据,也可能是从多个分片挨个拉取,还没开始拉第7片前面6片拉取耗时就超过了net_write_timeout。

案例:DRDS 到 MySQL 多个分片拉取数据生成了许多 cursor 并发执行,但拉数据的时候是串行拉取的,如果用户端拉取数据过慢会导致最后一个 cursor 执行完成之后要等待很久.会超过 MySQL 的 net_write_timeout 配置从而引发报错. 也就是最后一个cursor打开后一直没有去读取数据,直到MySQL Server 触发 net_write_timeout 异常

首先可以尝试在 DRDS jdbcurl 配置 netTimeoutForStreamingResults 参数,设置为 0 可以使其一直等待,或设置一个合理的值(秒).

从JDBC驱动中可以看到,当调用PreparedStatement的executeQuery() 方法的时候,如果我们是去获取流式resultset的话,就会默认执行SET net_write_timeout= ? 这个命令去重新设置timeout时间。源代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
if (doStreaming && this.connection.getNetTimeoutForStreamingResults() > 0) {  
java.sql.Statement stmt = null;
try {
stmt = this.connection.createStatement(); ((com.mysql.jdbc.StatementImpl)stmt).executeSimpleNonQuery(this.connection, "SET net_write_timeout="
+ this.connection.getNetTimeoutForStreamingResults());
} finally {
if (stmt != null) {
stmt.close();
}
}
}

//另外DRDS代码 AppLoader.java 中写死了net_write_timeout 8小时
ds.putConnectionProperties(ConnectionProperties.NET_WRITE_TIMEOUT, 28800);

而 this.connection.getNetTimeoutForStreamingResults() 默认是600秒,或者在JDBC连接串种通过属性 netTimeoutForStreamingResults 来指定。

netTimeoutForStreamingResults 默认值:

What value should the driver automatically set the server setting ‘net_write_timeout’ to when the streaming result sets feature is in use? Value has unit of seconds, the value “0” means the driver will not try and adjust this value.

Default Value 600
Since Version 5.1.0

一般在数据导出场景中容易出现 net_write_timeout 这个错误,比如这个错误堆栈:

或者:

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
ErrorMessage:
Communications link failure
The last packet successfully received from the server was 7 milliseconds ago. The last packet sent successfully to the server was 709,806 milliseconds ago. - com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure
The last packet successfully received from the server was 7 milliseconds ago. The last packet sent successfully to the server was 709,806 milliseconds ago.
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at com.mysql.jdbc.Util.handleNewInstance(Util.java:377)
at com.mysql.jdbc.SQLError.createCommunicationsException(SQLError.java:1036)
at com.mysql.jdbc.MysqlIO.reuseAndReadPacket(MysqlIO.java:3427)
at com.mysql.jdbc.MysqlIO.reuseAndReadPacket(MysqlIO.java:3327)
at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3814)
at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:870)
at com.mysql.jdbc.MysqlIO.nextRow(MysqlIO.java:1928)
at com.mysql.jdbc.RowDataDynamic.nextRecord(RowDataDynamic.java:378)
at com.mysql.jdbc.RowDataDynamic.next(RowDataDynamic.java:358)
at com.mysql.jdbc.ResultSetImpl.next(ResultSetImpl.java:6337)
at com.alibaba.datax.plugin.rdbms.reader.CommonRdbmsReader$Task.startRead(CommonRdbmsReader.java:275)
at com.alibaba.datax.plugin.reader.drdsreader.DrdsReader$Task.startRead(DrdsReader.java:148)
at com.alibaba.datax.core.taskgroup.runner.ReaderRunner.run(ReaderRunner.java:62)
at java.lang.Thread.run(Thread.java:834)
Caused by: java.io.EOFException: Can not read response from server. Expected to read 258 bytes, read 54 bytes before connection was unexpectedly lost.
at com.mysql.jdbc.MysqlIO.readFully(MysqlIO.java:2914)
at com.mysql.jdbc.MysqlIO.reuseAndReadPacket(MysqlIO.java:3387)
... 11 more

特别注意

JDBC驱动报如下错误

Application was streaming results when the connection failed. Consider raising value of ‘net_write_timeout’ on the server. - com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Application was streaming results when the connection failed. Consider raising value of ‘net_write_timeout’ on the server.

不一定是 net_write_timeout 设置过小导致的,JDBC 在 streaming 流模式下只要连接异常就会报如上错误,比如:

  • 连接被 TCP reset
  • 连接因为某种原因(比如 QueryTimeOut、比如用户监控kill 慢查询) 触发 kill Query导致连接中断

比如出现内核bug,内核卡死不发包的话,客户端同样报 net_write_timeout 错误

max_allowed_packet: 单个SQL或者单条记录的最大大小

一些其他的 Timeout

connectTimeout:表示等待和MySQL数据库建立socket链接的超时时间,默认值0,表示不设置超时,单位毫秒,建议30000。 JDBC驱动连接属性

queryTimeout:超时后jdbc驱动触发新建一个连接来发送一个 kill 给DB

socketTimeout:JDBC参数,表示客户端发送请求给MySQL数据库后block在read的等待数据的超时时间,linux系统默认的socketTimeout为30分钟,可以不设置。socketTimeout 超时不会触发发kill,只会断开tcp连接

要特别注意socketTimeout仅仅是指等待socket数据时间,如果在传输数据那么这个值就没有用了。socketTimeout通过mysql-connector中的NativeProtocol最终设置在socketOptions上

image-20211024171459127

static final int SO_TIMEOUT。 Set a timeout on blocking Socket operations:

ServerSocket.accept();
SocketInputStream.read();
DatagramSocket.receive();

The option must be set prior to entering a blocking operation to take effect. If the timeout expires and the operation would continue to block, java.io.InterruptedIOException is raised. The Socket is not closed in this case.

Statement Timeout:用来限制statement的执行时长,timeout的值通过调用JDBC的java.sql.Statement.setQueryTimeout(int timeout) API进行设置。不过现在开发者已经很少直接在代码中设置,而多是通过框架来进行设置。

max_execution_time:The execution timeout for SELECT statements, in milliseconds. If the value is 0, timeouts are not enabled. MySQL 属性,可以set修改,一般用来设置一个查询最长不超过多少秒,避免一个慢查询一直在跑,跟statement timeout对应。

Property Value
Command-Line Format --max-execution-time=#
System Variable max_execution_time
Scope Global, Session
Dynamic Yes
Type Integer
Default Value 0

wait_timeout The number of seconds the server waits for activity on a noninteractive connection before closing it. MySQL 属性,一般设置tcp keepalive后这个值基本不会超时(这句话存疑 202110)。

On thread startup, the session wait_timeout value is initialized from the global wait_timeout value or from the global interactive_timeout value, depending on the type of client (as defined by the CLIENT_INTERACTIVE connect option to mysql_real_connect()). See also interactive_timeout.

Property Value
Command-Line Format --wait-timeout=#
System Variable wait_timeout
Scope Global, Session
Dynamic Yes
Type Integer
Default Value 28800
Minimum Value 1
Maximum Value (Other) 31536000
Maximum Value (Windows) 2147483

一般来说应该设置: max_execution_time/statement timeout < Tranction Timeout < socketTimeout

SocketTimeout

这个 httpclient 的bug 就是在 TCP 连接握手成功(只受ConnectTimeout影响,SocketTimeout还不起作用)后,还需要进行 SSL的数据交换(HandShake),但因为httpclient是在连接建立后(含 SSL HandShake)才设置的 SocketTimeout,导致在SSL HandShake的时候卡在了读数据,此时恰好还没设置SocketTimeout,导致连接永久卡死在SSL HandShake的读数据

所以代码的fix方案就是在建连接前就设置好 SocketTimeout。

一次 PreparedStatement 执行

useServerPrepStmts=true&cachePrepStmts=true

5.0.5版本后的驱动 useServerPrepStmts 默认值是false;另外跨Statement是没法重用PreparedStatement预编译的,还需要设置 cachePrepStmts 才可以

对于打开预编译的URL(String url = “jdbc:mysql://localhost:3306/studb?useServerPrepStmts=true&cachePrepStmts=true”)获取数据库连接之后,本质是获取预编译语句 **pstmt = conn.prepareStatement(sql)**时会向MySQL服务端发送一个RPC,发送一个预编译的SQL模板(驱动会拼接MySQL预编译语句prepare s1 from ‘select * from user where id = ?’),然后MySQL服务端会编译好收到的SQL模板,再会为此预编译模板语句分配一个 serverStatementId发送给JDBC驱动,这样以后PreparedStatement就会持有当前预编译语句的服务端的serverStatementId,并且会把此 PreparedStatement缓存在当前数据库连接中,以后对于相同SQL模板的操作 pstmt.executeUpdate(),都用相同的PreparedStatement,执行SQL时只需要发送 serverStatementId 和参数,节省一次SQL编译, 直接执行。并且对于每一个连接(驱动端及MySQL服务端)都有自己的prepare cache,具体的源码实现是在com.mysql.jdbc.ServerPreparedStatement中实现。

根据SQL模板和设置的参数,解析成一条完整的SQL语句,最后根据MySQL协议,序列化成字节流,RPC发送给MySQL服务端

1
2
// 解析封装需要发送的SQL语句,序列化成MySQL协议对应的字节流
Buffer sendPacket = fillSendPacket();

准备好需要发送的MySQL协议的字节流(sendPacket)后,就可以一路通过ConnectionImpl.execSQL –> MysqlIO.sqlQueryDirect –> MysqlIO.send – > OutPutStram.write把字节流数据通过Socket发送给MySQL服务器,然后线程阻塞等待服务端返回结果数据,拿到数据后再根据MySQL协议反序列化成我们熟悉的ResultSet对象。

image-20230802101859567

SocketTimeoutException: Read timed out

如果SQL 超过 SocketTimeout 报错如下:

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 -cp .:./mysql-connector-java-5.1.45.jar Test "jdbc:mysql://127.0.0.1:3306/test?useSSL=false&useServerPrepStmts=true&cachePrepStmts=true&connectTimeout=15000&socketTimeout=3700" root 123 "select sleep(10), id from sbtest1 where id= ?" 100
com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure

The last packet successfully received from the server was 3,705 milliseconds ago. The last packet sent successfully to the server was 3,705 milliseconds ago.
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at com.mysql.jdbc.Util.handleNewInstance(Util.java:425)
at com.mysql.jdbc.SQLError.createCommunicationsException(SQLError.java:990)
at com.mysql.jdbc.MysqlIO.reuseAndReadPacket(MysqlIO.java:3559)
at com.mysql.jdbc.MysqlIO.reuseAndReadPacket(MysqlIO.java:3459)
at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3900)
at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:2527)
at com.mysql.jdbc.ServerPreparedStatement.serverExecute(ServerPreparedStatement.java:1283)
at com.mysql.jdbc.ServerPreparedStatement.executeInternal(ServerPreparedStatement.java:783)
at com.mysql.jdbc.PreparedStatement.executeQuery(PreparedStatement.java:1966)
at Test.main(Test.java:31)
Caused by: java.net.SocketTimeoutException: Read timed out
at java.net.SocketInputStream.socketRead0(Native Method)
at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)
at java.net.SocketInputStream.read(SocketInputStream.java:171)
at java.net.SocketInputStream.read(SocketInputStream.java:141)
at com.mysql.jdbc.util.ReadAheadInputStream.fill(ReadAheadInputStream.java:101)
at com.mysql.jdbc.util.ReadAheadInputStream.readFromUnderlyingStreamIfNecessary(ReadAheadInputStream.java:144)
at com.mysql.jdbc.util.ReadAheadInputStream.read(ReadAheadInputStream.java:174)
at com.mysql.jdbc.MysqlIO.readFully(MysqlIO.java:3008)
at com.mysql.jdbc.MysqlIO.reuseAndReadPacket(MysqlIO.java:3469)
... 7 more

连接超时

在防火墙里设置了 3306 的包都 drop

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
#java -cp .:./mysql-connector-java-5.1.45.jar Test "jdbc:mysql://127.0.0.1:3306/test?useSSL=false&useServerPrepStmts=true&cachePrepStmts=true&connectTimeout=15000&socketTimeout=3700" root 123 "select sleep(10), id from sbtest1 where id= ?" 100
com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure

The last packet sent successfully to the server was 0 milliseconds ago. The driver has not received any packets from the server.
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at com.mysql.jdbc.Util.handleNewInstance(Util.java:425)
at com.mysql.jdbc.SQLError.createCommunicationsException(SQLError.java:990)
at com.mysql.jdbc.MysqlIO.<init>(MysqlIO.java:341)
at com.mysql.jdbc.ConnectionImpl.coreConnect(ConnectionImpl.java:2186)
at com.mysql.jdbc.ConnectionImpl.connectOneTryOnly(ConnectionImpl.java:2219)
at com.mysql.jdbc.ConnectionImpl.createNewIO(ConnectionImpl.java:2014)
at com.mysql.jdbc.ConnectionImpl.<init>(ConnectionImpl.java:776)
at com.mysql.jdbc.JDBC4Connection.<init>(JDBC4Connection.java:47)
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at com.mysql.jdbc.Util.handleNewInstance(Util.java:425)
at com.mysql.jdbc.ConnectionImpl.getInstance(ConnectionImpl.java:386)
at com.mysql.jdbc.NonRegisteringDriver.connect(NonRegisteringDriver.java:330)
at java.sql.DriverManager.getConnection(DriverManager.java:664)
at java.sql.DriverManager.getConnection(DriverManager.java:247)
at Test.main(Test.java:26)
Caused by: java.net.ConnectException: 连接超时 (Connection timed out)
at java.net.PlainSocketImpl.socketConnect(Native Method)
at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:350)
at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:206)
at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:188)
at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392)
at java.net.Socket.connect(Socket.java:607)
at com.mysql.jdbc.StandardSocketFactory.connect(StandardSocketFactory.java:211)
at com.mysql.jdbc.MysqlIO.<init>(MysqlIO.java:300)
... 15 more

SocketException connection timed out

image-20240522165928366

image-20240522165849942

案例

设置JDBC参数不合理(不设置的话默认值是:queryTimeout=10s,socketTimeout=10s),会导致在异常情况下,第二条get获得了第一条的结果,拿到了错误的数据,数据库则表现正常

触发:

同事设置了queryTimeout 和socketTimeout,当queryTimeout 先触发,并发送了 kill id 给 Server,此时标志链接是 Cancle 状态,在网络不太好的场景下 Server 没有收到或者收到这个 kill 晚了,这时 socketTimeout 到达触发时间,连接抛CommunicationsException(严重异常,触发后连接应该断开), 但JDBC会检查请求是否被cancle了,如果Cancle 就会抛出MySQLTimeoutException异常,这是一个普通异常,连接会被重新放回连接池重用(导致下一个获取这个连接的线程可能会得到前一个请求的response)。

解决办法:

  1. TDDL:在TDDL JDBC接口层面屏蔽掉用户所有的queryTimeout设置,全部使用socketTimeout进行设置,同时配置好正确的mysql exception sorter(断开超时链接).
  2. JDBC Driver:判断这个query是否被cancel时,同时判断当前 SQLException的errorCode,如果errorCode是ER_QUERY_INTERRUPTED且callingStatement被cancel掉,才会用MySQLTimeoutException覆盖原来的SQLexception。避免了CommunicationsException被覆盖的问题
  3. MYSQL Server:添加了 max_statement_time,来替代queryTimeout,当执行时间超过 max_statement_time 时,Server 直接 kill,不需要客户端发送 kill

queryTimeout(queryTimeoutKillsConnection=True–来强制关闭连接)会触发启动一个新的连接向server发送 kill id的命令,MySQL5.7增加了max_statement_time/max_execution_time来做到在server上直接检测到这种查询,然后结束掉

jdbc 和 RDS之间 socket_timeout

jdbc驱动设置socketTimeout=1459,如果是socketTimeout触发客户端断开后,server端的SQL会继续执行,如果是client被kill则server端的SQL会被终止

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
# java -cp /home/admin/drds-server/lib/*:. Test "jdbc:mysql://172.16.40.215:3008/bank_000000?socketTimeout=1459" "user" "pass" "select sleep(2)" "1"
com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure

The last packet successfully received from the server was 1,461 milliseconds ago. The last packet sent successfully to the server was 1,461 milliseconds ago.
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:80)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at com.mysql.jdbc.Util.handleNewInstance(Util.java:425)
at com.mysql.jdbc.SQLError.createCommunicationsException(SQLError.java:989)
at com.mysql.jdbc.MysqlIO.reuseAndReadPacket(MysqlIO.java:3749)
at com.mysql.jdbc.MysqlIO.reuseAndReadPacket(MysqlIO.java:3649)
at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:4090)
at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:2658)
at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2811)
at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2806)
at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2764)
at com.mysql.jdbc.StatementImpl.executeQuery(StatementImpl.java:1399)
at Test.main(Test.java:29)
Caused by: java.net.SocketTimeoutException: Read timed out
at java.net.SocketInputStream.socketRead0(Native Method)
at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)
at java.net.SocketInputStream.read(SocketInputStream.java:171)
at java.net.SocketInputStream.read(SocketInputStream.java:141)
at com.mysql.jdbc.util.ReadAheadInputStream.fill(ReadAheadInputStream.java:101)
at com.mysql.jdbc.util.ReadAheadInputStream.readFromUnderlyingStreamIfNecessary(ReadAheadInputStream.java:144)
at com.mysql.jdbc.util.ReadAheadInputStream.read(ReadAheadInputStream.java:174)
at com.mysql.jdbc.MysqlIO.readFully(MysqlIO.java:3183)
at com.mysql.jdbc.MysqlIO.reuseAndReadPacket(MysqlIO.java:3659)
... 8 more

或者开协程后的错误堆栈
com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure

The last packet successfully received from the server was 1,460 milliseconds ago. The last packet sent successfully to the server was 1,459 milliseconds ago.
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:80)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at com.mysql.jdbc.Util.handleNewInstance(Util.java:425)
at com.mysql.jdbc.SQLError.createCommunicationsException(SQLError.java:989)
at com.mysql.jdbc.MysqlIO.reuseAndReadPacket(MysqlIO.java:3749)
at com.mysql.jdbc.MysqlIO.reuseAndReadPacket(MysqlIO.java:3649)
at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:4090)
at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:2658)
at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2811)
at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2806)
at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2764)
at com.mysql.jdbc.StatementImpl.executeQuery(StatementImpl.java:1399)
at Test.main(Test.java:29)
Caused by: java.net.SocketTimeoutException: time out
at sun.nio.ch.WispSocketImpl$1$1.read0(WispSocketImpl.java:244)
at sun.nio.ch.WispSocketImpl$1$1.read(WispSocketImpl.java:208)
at sun.nio.ch.WispSocketImpl$1$1.read(WispSocketImpl.java:201)
at com.mysql.jdbc.util.ReadAheadInputStream.fill(ReadAheadInputStream.java:101)
at com.mysql.jdbc.util.ReadAheadInputStream.readFromUnderlyingStreamIfNecessary(ReadAheadInputStream.java:144)
at com.mysql.jdbc.util.ReadAheadInputStream.read(ReadAheadInputStream.java:174)
at com.mysql.jdbc.MysqlIO.readFully(MysqlIO.java:3183)
at com.mysql.jdbc.MysqlIO.reuseAndReadPacket(MysqlIO.java:3659)
... 8 more

对应抓包,没有 kill动作

image-20220601141709318

CN 和 DN 间socket_timeout案例

设置CN到DN的socket_timeout为2秒,然后执行一个sleep

CN上抓包分析(stream 5是客户端到CN、stream6是CN到DN)如下,首先CN会计时2秒钟后发送quit给DN,然后断开和DN的连接,并返回一个错误给client,client发送quit断开连接:

image-20220601122556415

CN完整报错堆栈:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
2022-06-01 12:10:00.178 [ServerExecutor-bucket-2-19-thread-181] ERROR com.alibaba.druid.pool.DruidPooledStatement - [user=polardbx_root,host=10.101.32.6,port=43947,schema=bank] CommunicationsException, druid version 1.1.24, jdbcUrl : jdbc:mysql://172.16.40.215:3008/bank_000000?maintainTimeStats=false&rewriteBatchedStatements=false&failOverReadOnly=false&cacheResultSetMetadata=true&allowMultiQueries=true&clobberStreamingResults=true&autoReconnect=false&usePsMemOptimize=true&useServerPrepStmts=true&netTimeoutForStreamingResults=0&useSSL=false&metadataCacheSize=256&readOnlyPropagatesToServer=false&prepStmtCacheSqlLimit=4096&connectTimeout=5000&socketTimeout=9000000&cachePrepStmts=true&characterEncoding=utf8&prepStmtCacheSize=256, testWhileIdle true, idle millis 11861, minIdle 5, poolingCount 4, timeBetweenEvictionRunsMillis 60000, lastValidIdleMillis 11861, driver com.mysql.jdbc.Driver, exceptionSorter com.alibaba.polardbx.common.jdbc.sorter.MySQLExceptionSorter
2022-06-01 12:10:00.179 [ServerExecutor-bucket-2-19-thread-181] ERROR com.alibaba.druid.pool.DruidDataSource - [user=polardbx_root,host=10.101.32.6,port=43947,schema=bank] discard connection
com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure
at sun.reflect.GeneratedConstructorAccessor72.newInstance(Unknown Source)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at com.mysql.jdbc.Util.handleNewInstance(Util.java:425)
at com.mysql.jdbc.SQLError.createCommunicationsException(SQLError.java:989)
at com.mysql.jdbc.MysqlIO.reuseAndReadPacket(MysqlIO.java:3749)
at com.mysql.jdbc.MysqlIO.reuseAndReadPacket(MysqlIO.java:3649)
at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:4090)
at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:2658)
at com.mysql.jdbc.ServerPreparedStatement.serverExecute(ServerPreparedStatement.java:1281)
at com.mysql.jdbc.ServerPreparedStatement.executeInternal(ServerPreparedStatement.java:782)
at com.mysql.jdbc.PreparedStatement.execute(PreparedStatement.java:1367)
at com.alibaba.druid.pool.DruidPooledPreparedStatement.execute(DruidPooledPreparedStatement.java:497)
at com.alibaba.polardbx.group.jdbc.TGroupDirectPreparedStatement.execute(TGroupDirectPreparedStatement.java:84)
at com.alibaba.polardbx.repo.mysql.spi.MyJdbcHandler.executeQueryInner(MyJdbcHandler.java:1133)
at com.alibaba.polardbx.repo.mysql.spi.MyJdbcHandler.executeQuery(MyJdbcHandler.java:990)
at com.alibaba.polardbx.repo.mysql.spi.MyPhyQueryCursor.doInit(MyPhyQueryCursor.java:83)
at com.alibaba.polardbx.executor.cursor.AbstractCursor.init(AbstractCursor.java:53)
at com.alibaba.polardbx.repo.mysql.spi.MyPhyQueryCursor.<init>(MyPhyQueryCursor.java:67)
at com.alibaba.polardbx.repo.mysql.spi.CursorFactoryMyImpl.repoCursor(CursorFactoryMyImpl.java:42)
at com.alibaba.polardbx.repo.mysql.handler.MyPhyQueryHandler.handle(MyPhyQueryHandler.java:24)
at com.alibaba.polardbx.executor.handler.HandlerCommon.handlePlan(HandlerCommon.java:102)
at com.alibaba.polardbx.executor.AbstractGroupExecutor.executeInner(AbstractGroupExecutor.java:58)
at com.alibaba.polardbx.executor.AbstractGroupExecutor.execByExecPlanNode(AbstractGroupExecutor.java:36)
at com.alibaba.polardbx.executor.TopologyExecutor.execByExecPlanNode(TopologyExecutor.java:34)
at com.alibaba.polardbx.transaction.TransactionExecutor.execByExecPlanNode(TransactionExecutor.java:120)
at com.alibaba.polardbx.executor.ExecutorHelper.executeByCursor(ExecutorHelper.java:155)
at com.alibaba.polardbx.executor.ExecutorHelper.execute(ExecutorHelper.java:70)
at com.alibaba.polardbx.executor.PlanExecutor.execByExecPlanNodeByOne(PlanExecutor.java:130)
at com.alibaba.polardbx.executor.PlanExecutor.execute(PlanExecutor.java:75)
at com.alibaba.polardbx.matrix.jdbc.TConnection.executeQuery(TConnection.java:682)
at com.alibaba.polardbx.matrix.jdbc.TConnection.executeSQL(TConnection.java:457)
at com.alibaba.polardbx.matrix.jdbc.TPreparedStatement.executeSQL(TPreparedStatement.java:65)
at com.alibaba.polardbx.matrix.jdbc.TStatement.executeInternal(TStatement.java:133)
at com.alibaba.polardbx.matrix.jdbc.TPreparedStatement.execute(TPreparedStatement.java:50)
at com.alibaba.polardbx.server.ServerConnection.innerExecute(ServerConnection.java:1131)
at com.alibaba.polardbx.server.ServerConnection.execute(ServerConnection.java:883)
at com.alibaba.polardbx.server.ServerConnection.execute(ServerConnection.java:850)
at com.alibaba.polardbx.server.ServerConnection.execute(ServerConnection.java:844)
at com.alibaba.polardbx.server.handler.SelectHandler.handle(SelectHandler.java:82)
at com.alibaba.polardbx.server.handler.SelectHandler.handle(SelectHandler.java:31)
at com.alibaba.polardbx.server.ServerQueryHandler.executeSql(ServerQueryHandler.java:155)
at com.alibaba.polardbx.server.ServerQueryHandler.executeStatement(ServerQueryHandler.java:133)
at com.alibaba.polardbx.server.ServerQueryHandler.queryRaw(ServerQueryHandler.java:118)
at com.alibaba.polardbx.net.FrontendConnection.query(FrontendConnection.java:460)
at com.alibaba.polardbx.net.handler.FrontendCommandHandler.handle(FrontendCommandHandler.java:49)
at com.alibaba.polardbx.net.FrontendConnection.lambda$handleData$0(FrontendConnection.java:753)
at com.alibaba.polardbx.common.utils.thread.RunnableWithCpuCollector.run(RunnableWithCpuCollector.java:36)
at com.alibaba.polardbx.common.utils.thread.ServerThreadPool$RunnableAdapter.run(ServerThreadPool.java:793)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:874)
at com.alibaba.wisp.engine.WispTask.runOutsideWisp(WispTask.java:277)
at com.alibaba.wisp.engine.WispTask.runCommand(WispTask.java:252)
at com.alibaba.wisp.engine.WispTask.access$100(WispTask.java:33)
at com.alibaba.wisp.engine.WispTask$CacheableCoroutine.run(WispTask.java:223)
at java.dyn.CoroutineBase.startInternal(CoroutineBase.java:60)
Caused by: java.net.SocketTimeoutException: time out
at sun.nio.ch.WispSocketImpl$1$1.read0(WispSocketImpl.java:244)
at sun.nio.ch.WispSocketImpl$1$1.read(WispSocketImpl.java:208)
at sun.nio.ch.WispSocketImpl$1$1.read(WispSocketImpl.java:201)
at com.mysql.jdbc.util.ReadAheadInputStream.fill(ReadAheadInputStream.java:101)
at com.mysql.jdbc.util.ReadAheadInputStream.readFromUnderlyingStreamIfNecessary(ReadAheadInputStream.java:144)
at com.mysql.jdbc.util.ReadAheadInputStream.read(ReadAheadInputStream.java:174)
at com.mysql.jdbc.MysqlIO.readFully(MysqlIO.java:3183)
at com.mysql.jdbc.MysqlIO.reuseAndReadPacket(MysqlIO.java:3659)
... 53 common frames omitted
2022-06-01 12:10:00.179 [ServerExecutor-bucket-2-19-thread-181] WARN com.alibaba.polardbx.repo.mysql.spi.MyJdbcHandler - [user=polardbx_root,host=10.101.32.6,port=43947,schema=bank] [TDDL] [1461cdf8b2809000]Execute ERROR on GROUP: BANK_000000_GROUP, ATOM: dskey_bank_000000_group#pxc-xdb-s-pxcunrcbmk4g9lcpk0f24#172.16.40.215-3008#bank_000000, MERGE_UNION_SIZE:1, SQL: /*DRDS /10.101.32.6/1461cdf8b2809000/0// */SELECT SLEEP(?) AS `sleep(236)`, PARAM: [236], ERROR: Communications link failure, tddl version: 5.4.13-16522656
com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure
at sun.reflect.GeneratedConstructorAccessor72.newInstance(Unknown Source)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at com.mysql.jdbc.Util.handleNewInstance(Util.java:425)
at com.mysql.jdbc.SQLError.createCommunicationsException(SQLError.java:989)
at com.mysql.jdbc.MysqlIO.reuseAndReadPacket(MysqlIO.java:3749)
at com.mysql.jdbc.MysqlIO.reuseAndReadPacket(MysqlIO.java:3649)
at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:4090)
at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:2658)
at com.mysql.jdbc.ServerPreparedStatement.serverExecute(ServerPreparedStatement.java:1281)
at com.mysql.jdbc.ServerPreparedStatement.executeInternal(ServerPreparedStatement.java:782)
at com.mysql.jdbc.PreparedStatement.execute(PreparedStatement.java:1367)
at com.alibaba.druid.pool.DruidPooledPreparedStatement.execute(DruidPooledPreparedStatement.java:497)
at com.alibaba.polardbx.group.jdbc.TGroupDirectPreparedStatement.execute(TGroupDirectPreparedStatement.java:84)
at com.alibaba.polardbx.repo.mysql.spi.MyJdbcHandler.executeQueryInner(MyJdbcHandler.java:1133)
at com.alibaba.polardbx.repo.mysql.spi.MyJdbcHandler.executeQuery(MyJdbcHandler.java:990)
at com.alibaba.polardbx.repo.mysql.spi.MyPhyQueryCursor.doInit(MyPhyQueryCursor.java:83)
at com.alibaba.polardbx.executor.cursor.AbstractCursor.init(AbstractCursor.java:53)
at com.alibaba.polardbx.repo.mysql.spi.MyPhyQueryCursor.<init>(MyPhyQueryCursor.java:67)
at com.alibaba.polardbx.repo.mysql.spi.CursorFactoryMyImpl.repoCursor(CursorFactoryMyImpl.java:42)
at com.alibaba.polardbx.repo.mysql.handler.MyPhyQueryHandler.handle(MyPhyQueryHandler.java:24)
at com.alibaba.polardbx.executor.handler.HandlerCommon.handlePlan(HandlerCommon.java:102)
at com.alibaba.polardbx.executor.AbstractGroupExecutor.executeInner(AbstractGroupExecutor.java:58)
at com.alibaba.polardbx.executor.AbstractGroupExecutor.execByExecPlanNode(AbstractGroupExecutor.java:36)
at com.alibaba.polardbx.executor.TopologyExecutor.execByExecPlanNode(TopologyExecutor.java:34)
at com.alibaba.polardbx.transaction.TransactionExecutor.execByExecPlanNode(TransactionExecutor.java:120)
at com.alibaba.polardbx.executor.ExecutorHelper.executeByCursor(ExecutorHelper.java:155)
at com.alibaba.polardbx.executor.ExecutorHelper.execute(ExecutorHelper.java:70)
at com.alibaba.polardbx.executor.PlanExecutor.execByExecPlanNodeByOne(PlanExecutor.java:130)
at com.alibaba.polardbx.executor.PlanExecutor.execute(PlanExecutor.java:75)
at com.alibaba.polardbx.matrix.jdbc.TConnection.executeQuery(TConnection.java:682)
at com.alibaba.polardbx.matrix.jdbc.TConnection.executeSQL(TConnection.java:457)
at com.alibaba.polardbx.matrix.jdbc.TPreparedStatement.executeSQL(TPreparedStatement.java:65)
at com.alibaba.polardbx.matrix.jdbc.TStatement.executeInternal(TStatement.java:133)
at com.alibaba.polardbx.matrix.jdbc.TPreparedStatement.execute(TPreparedStatement.java:50)
at com.alibaba.polardbx.server.ServerConnection.innerExecute(ServerConnection.java:1131)
at com.alibaba.polardbx.server.ServerConnection.execute(ServerConnection.java:883)
at com.alibaba.polardbx.server.ServerConnection.execute(ServerConnection.java:850)
at com.alibaba.polardbx.server.ServerConnection.execute(ServerConnection.java:844)
at com.alibaba.polardbx.server.handler.SelectHandler.handle(SelectHandler.java:82)
at com.alibaba.polardbx.server.handler.SelectHandler.handle(SelectHandler.java:31)
at com.alibaba.polardbx.server.ServerQueryHandler.executeSql(ServerQueryHandler.java:155)
at com.alibaba.polardbx.server.ServerQueryHandler.executeStatement(ServerQueryHandler.java:133)
at com.alibaba.polardbx.server.ServerQueryHandler.queryRaw(ServerQueryHandler.java:118)
at com.alibaba.polardbx.net.FrontendConnection.query(FrontendConnection.java:460)
at com.alibaba.polardbx.net.handler.FrontendCommandHandler.handle(FrontendCommandHandler.java:49)
at com.alibaba.polardbx.net.FrontendConnection.lambda$handleData$0(FrontendConnection.java:753)
at com.alibaba.polardbx.common.utils.thread.RunnableWithCpuCollector.run(RunnableWithCpuCollector.java:36)
at com.alibaba.polardbx.common.utils.thread.ServerThreadPool$RunnableAdapter.run(ServerThreadPool.java:793)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:874)
at com.alibaba.wisp.engine.WispTask.runOutsideWisp(WispTask.java:277)
at com.alibaba.wisp.engine.WispTask.runCommand(WispTask.java:252)
at com.alibaba.wisp.engine.WispTask.access$100(WispTask.java:33)
at com.alibaba.wisp.engine.WispTask$CacheableCoroutine.run(WispTask.java:223)
at java.dyn.CoroutineBase.startInternal(CoroutineBase.java:60)
Caused by: java.net.SocketTimeoutException: time out
at sun.nio.ch.WispSocketImpl$1$1.read0(WispSocketImpl.java:244)
at sun.nio.ch.WispSocketImpl$1$1.read(WispSocketImpl.java:208)
at sun.nio.ch.WispSocketImpl$1$1.read(WispSocketImpl.java:201)
at com.mysql.jdbc.util.ReadAheadInputStream.fill(ReadAheadInputStream.java:101)
at com.mysql.jdbc.util.ReadAheadInputStream.readFromUnderlyingStreamIfNecessary(ReadAheadInputStream.java:144)
at com.mysql.jdbc.util.ReadAheadInputStream.read(ReadAheadInputStream.java:174)
at com.mysql.jdbc.MysqlIO.readFully(MysqlIO.java:3183)
at com.mysql.jdbc.MysqlIO.reuseAndReadPacket(MysqlIO.java:3659)
... 53 common frames omitted
2022-06-01 12:10:00.179 [ServerExecutor-bucket-2-19-thread-181] WARN com.alibaba.polardbx.repo.mysql.spi.MyJdbcHandler - [user=polardbx_root,host=10.101.32.6,port=43947,schema=bank] [TDDL] Reset conn socketTimeout failed, lastSocketTimeout is 9000000, tddl version: 5.4.13-16522656
com.mysql.jdbc.exceptions.jdbc4.MySQLNonTransientConnectionException: No operations allowed after connection closed.
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:80)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at com.mysql.jdbc.Util.handleNewInstance(Util.java:425)
at com.mysql.jdbc.Util.getInstance(Util.java:408)
at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:918)
at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:897)
at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:886)
at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:860)
at com.mysql.jdbc.ConnectionImpl.throwConnectionClosedException(ConnectionImpl.java:1326)
at com.mysql.jdbc.ConnectionImpl.checkClosed(ConnectionImpl.java:1321)
at com.mysql.jdbc.ConnectionImpl.setNetworkTimeout(ConnectionImpl.java:5888)
at com.alibaba.polardbx.atom.utils.NetworkUtils.setNetworkTimeout(NetworkUtils.java:18)
at com.alibaba.polardbx.group.jdbc.TGroupDirectConnection.setNetworkTimeout(TGroupDirectConnection.java:433)
at com.alibaba.polardbx.repo.mysql.spi.MyJdbcHandler.resetPhyConnSocketTimeout(MyJdbcHandler.java:721)
at com.alibaba.polardbx.repo.mysql.spi.MyJdbcHandler.executeQueryInner(MyJdbcHandler.java:1173)
at com.alibaba.polardbx.repo.mysql.spi.MyJdbcHandler.executeQuery(MyJdbcHandler.java:990)
at com.alibaba.polardbx.repo.mysql.spi.MyPhyQueryCursor.doInit(MyPhyQueryCursor.java:83)
at com.alibaba.polardbx.executor.cursor.AbstractCursor.init(AbstractCursor.java:53)
at com.alibaba.polardbx.repo.mysql.spi.MyPhyQueryCursor.<init>(MyPhyQueryCursor.java:67)
at com.alibaba.polardbx.repo.mysql.spi.CursorFactoryMyImpl.repoCursor(CursorFactoryMyImpl.java:42)
at com.alibaba.polardbx.repo.mysql.handler.MyPhyQueryHandler.handle(MyPhyQueryHandler.java:24)
at com.alibaba.polardbx.executor.handler.HandlerCommon.handlePlan(HandlerCommon.java:102)
at com.alibaba.polardbx.executor.AbstractGroupExecutor.executeInner(AbstractGroupExecutor.java:58)
at com.alibaba.polardbx.executor.AbstractGroupExecutor.execByExecPlanNode(AbstractGroupExecutor.java:36)
at com.alibaba.polardbx.executor.TopologyExecutor.execByExecPlanNode(TopologyExecutor.java:34)
at com.alibaba.polardbx.transaction.TransactionExecutor.execByExecPlanNode(TransactionExecutor.java:120)
at com.alibaba.polardbx.executor.ExecutorHelper.executeByCursor(ExecutorHelper.java:155)
at com.alibaba.polardbx.executor.ExecutorHelper.execute(ExecutorHelper.java:70)
at com.alibaba.polardbx.executor.PlanExecutor.execByExecPlanNodeByOne(PlanExecutor.java:130)
at com.alibaba.polardbx.executor.PlanExecutor.execute(PlanExecutor.java:75)
at com.alibaba.polardbx.matrix.jdbc.TConnection.executeQuery(TConnection.java:682)
at com.alibaba.polardbx.matrix.jdbc.TConnection.executeSQL(TConnection.java:457)
at com.alibaba.polardbx.matrix.jdbc.TPreparedStatement.executeSQL(TPreparedStatement.java:65)
at com.alibaba.polardbx.matrix.jdbc.TStatement.executeInternal(TStatement.java:133)
at com.alibaba.polardbx.matrix.jdbc.TPreparedStatement.execute(TPreparedStatement.java:50)
at com.alibaba.polardbx.server.ServerConnection.innerExecute(ServerConnection.java:1131)
at com.alibaba.polardbx.server.ServerConnection.execute(ServerConnection.java:883)
at com.alibaba.polardbx.server.ServerConnection.execute(ServerConnection.java:850)
at com.alibaba.polardbx.server.ServerConnection.execute(ServerConnection.java:844)
at com.alibaba.polardbx.server.handler.SelectHandler.handle(SelectHandler.java:82)
at com.alibaba.polardbx.server.handler.SelectHandler.handle(SelectHandler.java:31)
at com.alibaba.polardbx.server.ServerQueryHandler.executeSql(ServerQueryHandler.java:155)
at com.alibaba.polardbx.server.ServerQueryHandler.executeStatement(ServerQueryHandler.java:133)
at com.alibaba.polardbx.server.ServerQueryHandler.queryRaw(ServerQueryHandler.java:118)
at com.alibaba.polardbx.net.FrontendConnection.query(FrontendConnection.java:460)
at com.alibaba.polardbx.net.handler.FrontendCommandHandler.handle(FrontendCommandHandler.java:49)
at com.alibaba.polardbx.net.FrontendConnection.lambda$handleData$0(FrontendConnection.java:753)
at com.alibaba.polardbx.common.utils.thread.RunnableWithCpuCollector.run(RunnableWithCpuCollector.java:36)
at com.alibaba.polardbx.common.utils.thread.ServerThreadPool$RunnableAdapter.run(ServerThreadPool.java:793)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:874)
at com.alibaba.wisp.engine.WispTask.runOutsideWisp(WispTask.java:277)
at com.alibaba.wisp.engine.WispTask.runCommand(WispTask.java:252)
at com.alibaba.wisp.engine.WispTask.access$100(WispTask.java:33)
at com.alibaba.wisp.engine.WispTask$CacheableCoroutine.run(WispTask.java:223)
at java.dyn.CoroutineBase.startInternal(CoroutineBase.java:60)
2022-06-01 12:10:00.179 [ServerExecutor-bucket-2-19-thread-181] WARN com.alibaba.polardbx.executor.ExecutorHelper - [user=polardbx_root,host=10.101.32.6,port=43947,schema=bank] [TDDL] PhyQuery(node="BANK_000000_GROUP", sql="SELECT SLEEP(?) AS `sleep(236)`")
, tddl version: 5.4.13-16522656
2022-06-01 12:10:00.180 [ServerExecutor-bucket-2-19-thread-181] WARN com.alibaba.polardbx.server.ServerConnection - [user=polardbx_root,host=10.101.32.6,port=43947,schema=bank] [TDDL] [ERROR-CODE: 3009][1461cdf8b2809000] SQL: /*+TDDL:node(0) and SOCKET_TIMEOUT=2000 */ select sleep(236), tddl version: 5.4.13-16522656
com.alibaba.polardbx.common.exception.TddlRuntimeException: ERR-CODE: [TDDL-4614][ERR_EXECUTE_ON_MYSQL] Error occurs when execute on GROUP 'BANK_000000_GROUP' ATOM 'dskey_bank_000000_group#pxc-xdb-s-pxcunrcbmk4g9lcpk0f24#172.16.40.215-3008#bank_000000': Communications link failure
at com.alibaba.polardbx.repo.mysql.spi.MyJdbcHandler.handleException(MyJdbcHandler.java:1935)
at com.alibaba.polardbx.repo.mysql.spi.MyJdbcHandler.generalHandlerException(MyJdbcHandler.java:1911)
at com.alibaba.polardbx.repo.mysql.spi.MyJdbcHandler.executeQueryInner(MyJdbcHandler.java:1168)
at com.alibaba.polardbx.repo.mysql.spi.MyJdbcHandler.executeQuery(MyJdbcHandler.java:990)
at com.alibaba.polardbx.repo.mysql.spi.MyPhyQueryCursor.doInit(MyPhyQueryCursor.java:83)
at com.alibaba.polardbx.executor.cursor.AbstractCursor.init(AbstractCursor.java:53)
at com.alibaba.polardbx.repo.mysql.spi.MyPhyQueryCursor.<init>(MyPhyQueryCursor.java:67)
at com.alibaba.polardbx.repo.mysql.spi.CursorFactoryMyImpl.repoCursor(CursorFactoryMyImpl.java:42)
at com.alibaba.polardbx.repo.mysql.handler.MyPhyQueryHandler.handle(MyPhyQueryHandler.java:24)
at com.alibaba.polardbx.executor.handler.HandlerCommon.handlePlan(HandlerCommon.java:102)
at com.alibaba.polardbx.executor.AbstractGroupExecutor.executeInner(AbstractGroupExecutor.java:58)
at com.alibaba.polardbx.executor.AbstractGroupExecutor.execByExecPlanNode(AbstractGroupExecutor.java:36)
at com.alibaba.polardbx.executor.TopologyExecutor.execByExecPlanNode(TopologyExecutor.java:34)
at com.alibaba.polardbx.transaction.TransactionExecutor.execByExecPlanNode(TransactionExecutor.java:120)
at com.alibaba.polardbx.executor.ExecutorHelper.executeByCursor(ExecutorHelper.java:155)
at com.alibaba.polardbx.executor.ExecutorHelper.execute(ExecutorHelper.java:70)
at com.alibaba.polardbx.executor.PlanExecutor.execByExecPlanNodeByOne(PlanExecutor.java:130)
at com.alibaba.polardbx.executor.PlanExecutor.execute(PlanExecutor.java:75)
at com.alibaba.polardbx.matrix.jdbc.TConnection.executeQuery(TConnection.java:682)
at com.alibaba.polardbx.matrix.jdbc.TConnection.executeSQL(TConnection.java:457)
at com.alibaba.polardbx.matrix.jdbc.TPreparedStatement.executeSQL(TPreparedStatement.java:65)
at com.alibaba.polardbx.matrix.jdbc.TStatement.executeInternal(TStatement.java:133)
at com.alibaba.polardbx.matrix.jdbc.TPreparedStatement.execute(TPreparedStatement.java:50)
at com.alibaba.polardbx.server.ServerConnection.innerExecute(ServerConnection.java:1131)
at com.alibaba.polardbx.server.ServerConnection.execute(ServerConnection.java:883)
at com.alibaba.polardbx.server.ServerConnection.execute(ServerConnection.java:850)
at com.alibaba.polardbx.server.ServerConnection.execute(ServerConnection.java:844)
at com.alibaba.polardbx.server.handler.SelectHandler.handle(SelectHandler.java:82)
at com.alibaba.polardbx.server.handler.SelectHandler.handle(SelectHandler.java:31)
at com.alibaba.polardbx.server.ServerQueryHandler.executeSql(ServerQueryHandler.java:155)
at com.alibaba.polardbx.server.ServerQueryHandler.executeStatement(ServerQueryHandler.java:133)
at com.alibaba.polardbx.server.ServerQueryHandler.queryRaw(ServerQueryHandler.java:118)
at com.alibaba.polardbx.net.FrontendConnection.query(FrontendConnection.java:460)
at com.alibaba.polardbx.net.handler.FrontendCommandHandler.handle(FrontendCommandHandler.java:49)
at com.alibaba.polardbx.net.FrontendConnection.lambda$handleData$0(FrontendConnection.java:753)
at com.alibaba.polardbx.common.utils.thread.RunnableWithCpuCollector.run(RunnableWithCpuCollector.java:36)
at com.alibaba.polardbx.common.utils.thread.ServerThreadPool$RunnableAdapter.run(ServerThreadPool.java:793)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:874)
at com.alibaba.wisp.engine.WispTask.runOutsideWisp(WispTask.java:277)
at com.alibaba.wisp.engine.WispTask.runCommand(WispTask.java:252)
at com.alibaba.wisp.engine.WispTask.access$100(WispTask.java:33)
at com.alibaba.wisp.engine.WispTask$CacheableCoroutine.run(WispTask.java:223)
at java.dyn.CoroutineBase.startInternal(CoroutineBase.java:60)
Caused by: com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure
at sun.reflect.GeneratedConstructorAccessor72.newInstance(Unknown Source)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at com.mysql.jdbc.Util.handleNewInstance(Util.java:425)
at com.mysql.jdbc.SQLError.createCommunicationsException(SQLError.java:989)
at com.mysql.jdbc.MysqlIO.reuseAndReadPacket(MysqlIO.java:3749)
at com.mysql.jdbc.MysqlIO.reuseAndReadPacket(MysqlIO.java:3649)
at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:4090)
at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:2658)
at com.mysql.jdbc.ServerPreparedStatement.serverExecute(ServerPreparedStatement.java:1281)
at com.mysql.jdbc.ServerPreparedStatement.executeInternal(ServerPreparedStatement.java:782)
at com.mysql.jdbc.PreparedStatement.execute(PreparedStatement.java:1367)
at com.alibaba.druid.pool.DruidPooledPreparedStatement.execute(DruidPooledPreparedStatement.java:497)
at com.alibaba.polardbx.group.jdbc.TGroupDirectPreparedStatement.execute(TGroupDirectPreparedStatement.java:84)
at com.alibaba.polardbx.repo.mysql.spi.MyJdbcHandler.executeQueryInner(MyJdbcHandler.java:1133)
... 44 common frames omitted
Caused by: java.net.SocketTimeoutException: time out
at sun.nio.ch.WispSocketImpl$1$1.read0(WispSocketImpl.java:244)
at sun.nio.ch.WispSocketImpl$1$1.read(WispSocketImpl.java:208)
at sun.nio.ch.WispSocketImpl$1$1.read(WispSocketImpl.java:201)
at com.mysql.jdbc.util.ReadAheadInputStream.fill(ReadAheadInputStream.java:101)
at com.mysql.jdbc.util.ReadAheadInputStream.readFromUnderlyingStreamIfNecessary(ReadAheadInputStream.java:144)
at com.mysql.jdbc.util.ReadAheadInputStream.read(ReadAheadInputStream.java:174)
at com.mysql.jdbc.MysqlIO.readFully(MysqlIO.java:3183)
at com.mysql.jdbc.MysqlIO.reuseAndReadPacket(MysqlIO.java:3659)
... 53 common frames omitted

应用和 DB 间丢包导致 keepalive 心跳失败

应用使用了 Druid 连接池来维护到 DB 间的所有长连接

应用和 DB 间丢包导致 keepalive 心跳失败,进而 OS会断开这个连接

image-20230322171621838

一个连接归还给Druid连接池都要做清理动作,就是第一个红框的rollback/autocommit=1

归还后OS 层面会探活TCP 连接,DB(4381端口)多次后多次不响应keepalive 后,OS 触发reset tcp断开连接,此时上层应用(比如Druid连接池、比如Tomcat)还不知道此连接在OS 层面已经断开

1
2
3
4
#sysctl -a |grep -i keepalive
net.ipv4.tcp_keepalive_intvl = 3
net.ipv4.tcp_keepalive_probes = 60
net.ipv4.tcp_keepalive_time = 20

继续过来一个新连接,业务取到这个连接执行查询就会报如下错误:

1
2
3
4
5
6
7
com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure

The last packet successfully received from the server was 162,776 milliseconds ago. The last packet sent successfully to the server was 162,776 milliseconds ago.

com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure

The last packet successfully received from the server was 162,776 milliseconds ago. The last packet sent successfully to the server was 162,776 milliseconds ago.

这个错误就是因为OS层面连接断开了,并且断开了162秒(和截图时间戳能对应上)

对应的错误堆栈:

1
2
3
4
5
6
7
8
Caused by: java.net.SocketException: Connection timed out (Write failed)
at java.net.SocketOutputStream.socketWrite0(Native Method)
at java.net.SocketOutputStream.socketWrite(SocketOutputStream.java:111)
at java.net.SocketOutputStream.write(SocketOutputStream.java:155)
at java.io.BufferedOutputStream.flushBuffer(BufferedOutputStream.java:82)
at java.io.BufferedOutputStream.flush(BufferedOutputStream.java:140)
at com.mysql.jdbc.MysqlIO.send(MysqlIO.java:3725)
... 46 common frames omitted

kill 案例

kill mysql client

mysql client连cn执行一个很慢的SQL,然后kill掉mysql client

cn报错:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
2022-06-01 11:45:59.063 [ServerExecutor-bucket-0-17-thread-158] ERROR com.alibaba.druid.pool.DruidPooledStatement - [user=polardbx_root,host=10.101.32.6,port=50684,schema=bank] CommunicationsException, druid version 1.1.24, jdbcUrl : jdbc:mysql://172.16.40.215:3008/bank_000000?maintainTimeStats=false&rewriteBatchedStatements=false&failOverReadOnly=false&cacheResultSetMetadata=true&allowMultiQueries=true&clobberStreamingResults=true&autoReconnect=false&usePsMemOptimize=true&useServerPrepStmts=true&netTimeoutForStreamingResults=0&useSSL=false&metadataCacheSize=256&readOnlyPropagatesToServer=false&prepStmtCacheSqlLimit=4096&connectTimeout=5000&socketTimeout=9000000&cachePrepStmts=true&characterEncoding=utf8&prepStmtCacheSize=256, testWhileIdle true, idle millis 72028, minIdle 5, poolingCount 4, timeBetweenEvictionRunsMillis 60000, lastValidIdleMillis 345734, driver com.mysql.jdbc.Driver, exceptionSorter com.alibaba.polardbx.common.jdbc.sorter.MySQLExceptionSorter
2022-06-01 11:45:59.064 [ServerExecutor-bucket-0-17-thread-158] ERROR com.alibaba.druid.pool.DruidDataSource - [user=polardbx_root,host=10.101.32.6,port=50684,schema=bank] discard connection
com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure
at sun.reflect.GeneratedConstructorAccessor72.newInstance(Unknown Source)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at com.mysql.jdbc.Util.handleNewInstance(Util.java:425)
…………
at com.alibaba.wisp.engine.WispTask$CacheableCoroutine.run(WispTask.java:223)
at java.dyn.CoroutineBase.startInternal(CoroutineBase.java:60)
Caused by: java.net.SocketException: Socket is closed
at java.net.Socket.getSoTimeout(Socket.java:1291)
at sun.nio.ch.WispSocketImpl$1$1.read0(WispSocketImpl.java:249)
at sun.nio.ch.WispSocketImpl$1$1.read(WispSocketImpl.java:208)
at sun.nio.ch.WispSocketImpl$1$1.read(WispSocketImpl.java:201)
at com.mysql.jdbc.util.ReadAheadInputStream.fill(ReadAheadInputStream.java:101)
at com.mysql.jdbc.util.ReadAheadInputStream.readFromUnderlyingStreamIfNecessary(ReadAheadInputStream.java:144)
at com.mysql.jdbc.util.ReadAheadInputStream.read(ReadAheadInputStream.java:174)
at com.mysql.jdbc.MysqlIO.readFully(MysqlIO.java:3183)
at com.mysql.jdbc.MysqlIO.reuseAndReadPacket(MysqlIO.java:3659)
... 53 common frames omitted
2022-06-01 11:45:59.065 [ServerExecutor-bucket-0-17-thread-158] WARN com.alibaba.polardbx.repo.mysql.spi.MyJdbcHandler - [user=polardbx_root,host=10.101.32.6,port=50684,schema=bank] [TDDL] [1461c86bbe809001]Execute ERROR on GROUP: BANK_000000_GROUP, ATOM: dskey_bank_000000_group#pxc-xdb-s-pxcunrcbmk4g9lcpk0f24#172.16.40.215-3008#bank_000000, MERGE_UNION_SIZE:1, SQL: /*DRDS /10.101.32.6/1461c86bbe809001/0// */SELECT SLEEP(?) AS `sleep(236)`, PARAM: [236], ERROR: Communications link failure, tddl version: 5.4.13-16522656
com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure
at sun.reflect.GeneratedConstructorAccessor72.newInstance(Unknown Source)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
…………
at java.dyn.CoroutineBase.startInternal(CoroutineBase.java:60)
Caused by: java.net.SocketException: Socket is closed
at java.net.Socket.getSoTimeout(Socket.java:1291)
at sun.nio.ch.WispSocketImpl$1$1.read0(WispSocketImpl.java:249)
at sun.nio.ch.WispSocketImpl$1$1.read(WispSocketImpl.java:208)
at sun.nio.ch.WispSocketImpl$1$1.read(WispSocketImpl.java:201)
at com.mysql.jdbc.util.ReadAheadInputStream.fill(ReadAheadInputStream.java:101)
at com.mysql.jdbc.util.ReadAheadInputStream.readFromUnderlyingStreamIfNecessary(ReadAheadInputStream.java:144)
at com.mysql.jdbc.util.ReadAheadInputStream.read(ReadAheadInputStream.java:174)
at com.mysql.jdbc.MysqlIO.readFully(MysqlIO.java:3183)
at com.mysql.jdbc.MysqlIO.reuseAndReadPacket(MysqlIO.java:3659)
... 53 common frames omitted
2022-06-01 11:45:59.065 [ServerExecutor-bucket-0-17-thread-158] WARN com.alibaba.polardbx.repo.mysql.spi.MyJdbcHandler - [user=polardbx_root,host=10.101.32.6,port=50684,schema=bank] [TDDL] Reset conn socketTimeout failed, lastSocketTimeout is 9000000, tddl version: 5.4.13-16522656
com.mysql.jdbc.exceptions.jdbc4.MySQLNonTransientConnectionException: No operations allowed after connection closed.
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:80)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
…………
at com.alibaba.wisp.engine.WispTask$CacheableCoroutine.run(WispTask.java:223)
at java.dyn.CoroutineBase.startInternal(CoroutineBase.java:60)
2022-06-01 11:45:59.065 [ServerExecutor-bucket-0-17-thread-158] WARN com.alibaba.polardbx.executor.ExecutorHelper - [user=polardbx_root,host=10.101.32.6,port=50684,schema=bank] [TDDL] PhyQuery(node="BANK_000000_GROUP", sql="SELECT SLEEP(?) AS `sleep(236)`")
, tddl version: 5.4.13-16522656
2022-06-01 11:45:59.066 [ServerExecutor-bucket-0-17-thread-158] ERROR com.alibaba.polardbx.server.ServerConnection - [user=polardbx_root,host=10.101.32.6,port=50684,schema=bank] [TDDL] Interrupted unexpectedly for 1461c86bbe809001, tddl version: 5.4.13-16522656
java.lang.InterruptedException: null
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireSharedInterruptibly(AbstractQueuedSynchronizer.java:1310)
at com.alibaba.polardbx.common.utils.BooleanMutex$Sync.innerGet(BooleanMutex.java:136)
at com.alibaba.polardbx.common.utils.BooleanMutex.get(BooleanMutex.java:53)
at com.alibaba.polardbx.common.utils.thread.ServerThreadPool.waitByTraceId(ServerThreadPool.java:445)
at com.alibaba.polardbx.server.ServerConnection.innerExecute(ServerConnection.java:1291)
……
at com.alibaba.wisp.engine.WispTask.access$100(WispTask.java:33)
at com.alibaba.wisp.engine.WispTask$CacheableCoroutine.run(WispTask.java:223)
at java.dyn.CoroutineBase.startInternal(CoroutineBase.java:60)
2022-06-01 11:45:59.066 [ServerExecutor-bucket-0-17-thread-158] WARN com.alibaba.polardbx.server.ServerConnection - [user=polardbx_root,host=10.101.32.6,port=50684,schema=bank] [TDDL] [ERROR-CODE: 3009][1461c86bbe809001] SQL: /*+TDDL:node(0) and SOCKET_TIMEOUT=40000 */ select sleep(236), tddl version: 5.4.13-16522656
com.alibaba.polardbx.common.exception.TddlRuntimeException: ERR-CODE: [TDDL-4614][ERR_EXECUTE_ON_MYSQL] Error occurs when execute on GROUP 'BANK_000000_GROUP' ATOM 'dskey_bank_000000_group#pxc-xdb-s-pxcunrcbmk4g9lcpk0f24#172.16.40.215-3008#bank_000000': Communications link failure
at com.alibaba.polardbx.repo.mysql.spi.MyJdbcHandler.handleException(MyJdbcHandler.java:1935)
at com.alibaba.polardbx.repo.mysql.spi.MyJdbcHandler.generalHandlerException(MyJdbcHandler.java:1911)
at com.alibaba.polardbx.repo.mysql.spi.MyJdbcHandler.executeQueryInner(MyJdbcHandler.java:1168)
at com.alibaba.polardbx.repo.mysql.spi.MyJdbcHandler.executeQuery(MyJdbcHandler.java:990)
…………
at com.alibaba.wisp.engine.WispTask$CacheableCoroutine.run(WispTask.java:223)
at java.dyn.CoroutineBase.startInternal(CoroutineBase.java:60)
Caused by: com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure
at sun.reflect.GeneratedConstructorAccessor72.newInstance(Unknown Source)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at com.mysql.jdbc.Util.handleNewInstance(Util.java:425)
at com.mysql.jdbc.SQLError.createCommunicationsException(SQLError.java:989)
at com.mysql.jdbc.MysqlIO.reuseAndReadPacket(MysqlIO.java:3749)
at com.mysql.jdbc.MysqlIO.reuseAndReadPacket(MysqlIO.java:3649)
at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:4090)
at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:2658)
at com.mysql.jdbc.ServerPreparedStatement.serverExecute(ServerPreparedStatement.java:1281)
at com.mysql.jdbc.ServerPreparedStatement.executeInternal(ServerPreparedStatement.java:782)
at com.mysql.jdbc.PreparedStatement.execute(PreparedStatement.java:1367)
at com.alibaba.druid.pool.DruidPooledPreparedStatement.execute(DruidPooledPreparedStatement.java:497)
at com.alibaba.polardbx.group.jdbc.TGroupDirectPreparedStatement.execute(TGroupDirectPreparedStatement.java:84)
at com.alibaba.polardbx.repo.mysql.spi.MyJdbcHandler.executeQueryInner(MyJdbcHandler.java:1133)
... 44 common frames omitted
Caused by: java.net.SocketException: Socket is closed
at java.net.Socket.getSoTimeout(Socket.java:1291)
at sun.nio.ch.WispSocketImpl$1$1.read0(WispSocketImpl.java:249)
at sun.nio.ch.WispSocketImpl$1$1.read(WispSocketImpl.java:208)
at sun.nio.ch.WispSocketImpl$1$1.read(WispSocketImpl.java:201)
at com.mysql.jdbc.util.ReadAheadInputStream.fill(ReadAheadInputStream.java:101)
at com.mysql.jdbc.util.ReadAheadInputStream.readFromUnderlyingStreamIfNecessary(ReadAheadInputStream.java:144)
at com.mysql.jdbc.util.ReadAheadInputStream.read(ReadAheadInputStream.java:174)
at com.mysql.jdbc.MysqlIO.readFully(MysqlIO.java:3183)
at com.mysql.jdbc.MysqlIO.reuseAndReadPacket(MysqlIO.java:3659)
... 53 common frames omitted
2022-06-01 11:45:59.071 [KillExecutor-15-thread-49] WARN com.alibaba.polardbx.server.ServerConnection - [user=polardbx_root,host=10.101.32.6,port=50684,schema=bank] [TDDL] Connection Killed, tddl version: 5.4.13-16522656

mysqld报错:

1
2022-06-01T11:45:58.915371+08:00 8218735 [Note] Aborted connection 8218735 to db: 'bank_000000' user: 'rds_polardb_x' host: '172.16.40.214' (Got an error reading communication packets)

172.16.40.214是客户端IP

抓包看到CN收到mysql client发过来的fin,CN回复fin断开连接

CN会给DN在新的连接上发Kill Query(stream 1596),同时会在原来的连接(stream 583)上发fin,然后原来的连接收到DN的response(被kill),然后CN发reset给DN

image-20220601120626629

下图是sleep 连接的收发包

image-20220601120417026

Kill jdbc client

Java jdbc client被kill后没有错误堆栈,kill后触发socket.close(对应client发送fin断开连接),kill后server端SQL也被立即中断

抓包:

image-20220601143200253

server端报错信息:

1
2022-06-01T14:33:52.204848+08:00 8288839 [Note] Aborted connection 8288839 to db: 'bank_000000' user: 'user' host: '172.16.40.214' (Got an error reading communication packets)

Statement timeout

1
2
3
4
# java -cp /home/admin/drds-server/lib/*:. Test "jdbc:mysql://172.16.40.215:3008/bank_000000?socketTimeout=5459" "user" "pass" "select sleep(180)" "1" 3
com.mysql.jdbc.exceptions.MySQLTimeoutException: Statement cancelled due to timeout or client request
at com.mysql.jdbc.StatementImpl.executeQuery(StatementImpl.java:1419)
at Test.main(Test.java:31)

statement会设置一个timer,到时间还没有返回结果就创建一个新连接发送kill query

server 端收到kill后终止SQL执行,抓包看到Server端主动提前返回了错误

image-20220601152401387

参考资料

MySQL JDBC StreamResult通信原理浅析

如何创建一个自己连自己的TCP连接

能不能建立一个tcp连接, src-ip:src-port 等于dest-ip:dest-port 呢?

最近有同时找我,说是发现了一个奇怪的问题,他的 MySQLD listen 28350 端口, Sysbench 和 MySQLD 部署在同一台机器上,然后压 MySQL,只要 MySQL 一挂掉就再也起不来,起不来是因为 28350 端口被 Sysbench 抢走了,如下图,对 Sysbench 来说他已经连上 28350 的 MySQL 了(注意 ESTABLISHED 状态):

img

所以问题就是能不能建立一个自己连自己的连接呢?建立后有什么现象和后果?

测试

执行

1
# nc 192.168.0.79 18082 -p 18082

然后就能看到

1
2
# netstat -ant |grep 18082
tcp 0 0 192.168.0.79:18082 192.168.0.79:18082 ESTABLISHED

比较神奇,这个连接的srcport等于destport,并且完全可以工作,也能收发数据。这有点颠覆大家的理解,端口能重复使用?

port range

我们都知道linux下本地端口范围由参数控制

1
2
# cat /proc/sys/net/ipv4/ip_local_port_range 
10000 65535

所以也经常看到一个误解:一台机器上最多能创建65535个TCP连接

到底一台机器上最多能创建多少个TCP连接

在内存、文件句柄足够的话可以创建的连接是没有限制的,那么/proc/sys/net/ipv4/ip_local_port_range指定的端口范围到底是什么意思呢?

一个TCP连接只要保证四元组(src-ip src-port dest-ip dest-port)唯一就可以了,而不是要求src port唯一,比如:

1
2
3
4
5
6
# netstat -ant |grep 18089
tcp 0 0 192.168.1.79:18089 192.168.1.79:22 ESTABLISHED
tcp 0 0 192.168.1.79:18089 192.168.1.79:18080 ESTABLISHED
tcp 0 0 192.168.0.79:18089 192.168.0.79:22 TIME_WAIT
tcp 0 0 192.168.1.79:22 192.168.1.79:18089 ESTABLISHED
tcp 0 0 192.168.1.79:18080 192.168.1.79:18089 ESTABLISHED

从前三行可以清楚地看到18089被用了三次,第一第二行src-ip、dest-ip也是重复的,但是dest port不一样,第三行的src-port还是18089,但是src-ip变了。

所以一台机器能创建的TCP连接是没有限制的,而ip_local_port_range是指没有bind的时候OS随机分配端口的范围,但是分配到的端口要同时满足五元组唯一,这样 ip_local_port_range 限制的是连同一个目标(dest-ip和dest-port一样)的port的数量(请忽略本地多网卡的情况,因为dest-ip为以后route只会选用一个本地ip)。

但是如果程序调用的是bind函数(bind(ip,port=0))这个时候是让系统绑定到某个网卡和自动分配的端口,此时系统没有办法确定接下来这个socket是要去connect还是listen. 如果是listen的话,那么肯定是不能出现端口冲突的,如果是connect的话,只要满足4元组唯一即可。在这种情况下,系统只能尽可能满足更强的要求,就是先要求端口不能冲突,即使之后去connect的时候4元组是唯一的。

bind()的时候内核是还不知道四元组的,只知道src_ip、src_port,所以这个时候单网卡下src_port是没法重复的,但是connect()的时候已经知道了四元组的全部信息,所以只要保证四元组唯一就可以了,那么这里的src_port完全是可以重复使用的。

自己连自己的连接

我们来看自己连自己发生了什么

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# strace nc 192.168.0.79 18084 -p 18084
execve("/usr/bin/nc", ["nc", "192.168.0.79", "18084", "-p", "18084"], [/* 31 vars */]) = 0
brk(NULL) = 0x23d4000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f213f394000
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=23295, ...}) = 0
mmap(NULL, 23295, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f213f38e000
close(3) = 0
open("/lib64/libssl.so.10", O_RDONLY|O_CLOEXEC) = 3
………………
munmap(0x7f213f393000, 4096) = 0
open("/usr/share/ncat/ca-bundle.crt", O_RDONLY) = -1 ENOENT (No such file or directory)
socket(AF_INET, SOCK_STREAM, IPPROTO_TCP) = 3
fcntl(3, F_GETFL) = 0x2 (flags O_RDWR)
fcntl(3, F_SETFL, O_RDWR|O_NONBLOCK) = 0
setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
bind(3, {sa_family=AF_INET, sin_port=htons(18084), sin_addr=inet_addr("0.0.0.0")}, 16) = 0
//注意这里bind后直接就是connect,没有listen
connect(3, {sa_family=AF_INET, sin_port=htons(18084), sin_addr=inet_addr("192.168.0.79")}, 16) = -1 EINPROGRESS (Operation now in progress)
select(4, [3], [3], [3], {10, 0}) = 1 (out [3], left {9, 999998})
getsockopt(3, SOL_SOCKET, SO_ERROR, [0], [4]) = 0
select(4, [0 3], [], [], NULL

抓包看看,正常三次握手,但是syn的seq和syn+ack的seq是一样的

image.png

这个连接算是常说的TCP simultaneous open,simultaneous open指的是两个不同port同时发syn建连接。而这里是先创建了一个socket,然后socket bind到18084端口上(作为local port,因为nc指定了local port),然后执行 connect, 连接到的目标也是192.168.0.79:18084,而这个目标正好是刚刚创建的socket,也就是自己连自己(连接双方总共只有一个socket)。因为一个socket充当了两个角色(client、server),握手的时候发syn,自己收到自己发的syn,就相当于两个角色simultaneous open了。

正常一个连接一定需要两个socket参与(这两个socket不一定要在两台机器上),而这个连接只用了一个socket就创建了,还能正常传输数据。但是仔细观察发数据的时候发放的seq增加(注意tcp_len 11那里的seq),收方的seq也增加了11,这是因为本来这就是用的同一个socket。正常两个socket通讯不是这样的。

那么这种情况为什么没有当做bug被处理呢?

TCP simultanous open

在tcp连接的定义中,通常都是一方先发起连接,假如两边同时发起连接,也就是两个socket同时给对方发 syn 呢? 这在内核中是支持的,就叫同时打开(simultaneous open)。

image.png

​ 摘自《tcp/ip卷1》

可以清楚地看到这个连接建立用了四次握手,然后连接建立了,当然也有 simultanous close(3次挥手成功关闭连接)。如下内核代码 net/ipv4/tcp_input.c 的5924行中就说明了允许这种自己连自己的连接(当然也允许simultanous open). 也就是允许一个socket本来应该收到 syn+ack(发出syn后), 结果收到了syn的情况,而一个socket自己连自己又是这种情况的特例。

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
	static int tcp_rcv_synsent_state_process(struct sock *sk, struct sk_buff *skb,
const struct tcphdr *th)
{
5916 /* PAWS check. */
//PAWS机制全称Protect Againest Wrapped Sequence numbers,
//目的是为了解决在高带宽下,TCP序号可能被重复使用而带来的问题。
5917 if (tp->rx_opt.ts_recent_stamp && tp->rx_opt.saw_tstamp &&
5918 tcp_paws_reject(&tp->rx_opt, 0))
5919 goto discard_and_undo;
5920 //在socket发送syn后收到了一个syn(正常应该收到syn+ack),这里是允许的。
5921 if (th->syn) {
5922 /* We see SYN without ACK. It is attempt of
5923 * simultaneous connect with crossed SYNs.
5924 * Particularly, it can be connect to self. //自己连自己
5925 */
5926 tcp_set_state(sk, TCP_SYN_RECV);
5927
5928 if (tp->rx_opt.saw_tstamp) {
5929 tp->rx_opt.tstamp_ok = 1;
5930 tcp_store_ts_recent(tp);
5931 tp->tcp_header_len =
5932 sizeof(struct tcphdr) + TCPOLEN_TSTAMP_ALIGNED;
5933 } else {
5934 tp->tcp_header_len = sizeof(struct tcphdr);
5935 }
5936
5937 tp->rcv_nxt = TCP_SKB_CB(skb)->seq + 1;
5938 tp->copied_seq = tp->rcv_nxt;
5939 tp->rcv_wup = TCP_SKB_CB(skb)->seq + 1;
5940
5941 /* RFC1323: The window in SYN & SYN/ACK segments is
5942 * never scaled.
5943 */

也就是在发送syn进入SYN_SENT状态之后,收到对端发来的syn包后不会RST,而是处理流程如下,调用tcp_set_state(sk, TCP_SYN_RECV)进入SYN_RECV状态,以及调用tcp_send_synack(sk)向对端发送syn+ack。

自己连自己的原理解释

第一我们要理解Kernel是支持simultaneous open(同时打开)的,也就是说socket发走syn后,本来应该收到一个syn+ack的,但是实际收到了一个syn(没有ack),这是允许的。这叫TCP连接同时打开(同时给对方发syn),四次握手然后建立连接成功。

自己连自己又是simultaneous open的一个特例,特别在这个连接只有一个socket参与,发送、接收都是同一个socket,自然也会是发syn后收到了自己的syn(自己发给自己),然后依照simultaneous open连接也能创建成功。

这个bind到18084 local port的socket又要连接到 18084 port上,而这个18084 socket已经bind到了socket(也就是自己),就形成了两个socket 的simultaneous open一样,内核又允许这种simultaneous open,所以就形成了自己连自己,也就是一个socket在自己给自己收发数据,所以看到收方和发放的seq是一样的。

可以用python来重现这个连接连自己的过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import socket
import time

connected=False
while (not connected):
try:
sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
sock.setsockopt(socket.IPPROTO_TCP,socket.TCP_NODELAY,1)
sock.bind(('', 18084)) //sock 先bind到18084
sock.connect(('127.0.0.1',18084)) //然后同一个socket连自己
connected=True
except socket.error,(value,message):
print message

if not connected:
print "reconnect"

print "tcp self connection occurs!"
print "netstat -an|grep 18084"
time.sleep(1800)

这里connect前如果没有bind那么系统就会从 local port range 分配一个可用port。

bind成功后会将ip+port放入hash表来判重,这就是我们常看到的 Bind to *** failed (IOD #1): Address already in use 异常。所以一台机器上,如果有多个ip,是可以将同一个port bind多次的,但是bind的时候如果不指定ip,也就是bind(‘0’, port) 还是会冲突。

connect成功后会将四元组放入ehash来判定连接的重复性。如果connect四元组冲突了就会报如下错误

1
2
# nc 192.168.0.82 8080 -p 29798 -s 192.168.0.79
Ncat: Cannot assign requested address.

问题解决

知道原因就好解决了,有如下两个方案

  1. 正常应该通过 port_range 限制随机端口的使用范围(就是给 Sysbench 这些客户端使用的),而 Listen 使用的端口在 port_range 之外,这样就不会出现自己连自己的连接了
  2. 将 listen 端口添加到 /proc/sys/net/ipv4/ip_local_reserved_ports 中

方案2示例(推荐该方案)如下:

1
2
3
4
# echo 3306,32768,1024-3000,32769-65535 >/proc/sys/net/ipv4/ip_local_reserved_ports
//合并连续的区间
# cat /proc/sys/net/ipv4/ip_local_reserved_ports
1024-3000,3306,32768-65535

以上两个方法都可以解决这个问题,方案2 简直是为这种情况量身打造的

bind 和 connect、listen

当对一个TCP socket调用connect函数时,如果这个socket没有bind指定的端口号,操作系统会为它选择一个当前未被使用的端口号,这个端口号被称为ephemeral port, 范围可以在/proc/sys/net/ipv4/ip_local_port_range里查看。假设30000这个端口被选为ephemeral port。

如果这个socket指定了local port那么socket创建后会执行bind将这个socket bind到这个port。比如:

1
2
3
4
5
socket(AF_INET, SOCK_STREAM, IPPROTO_TCP) = 3
fcntl(3, F_GETFL) = 0x2 (flags O_RDWR)
fcntl(3, F_SETFL, O_RDWR|O_NONBLOCK) = 0
setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
bind(3, {sa_family=AF_INET, sin_port=htons(18084), sin_addr=inet_addr("0.0.0.0")}, 16) = 0

image.png

listen

image-20200702131215819

参考资料

https://segmentfault.com/a/1190000002396411

linux中TCP的socket、bind、listen、connect和accept的实现

How Linux allows TCP introspection The inner workings of bind and listen on Linux.

https://idea.popcount.org/2014-04-03-bind-before-connect/

程序员如何学习和构建网络知识体系

大家学习网络知识的过程中经常发现当时看懂了,很快又忘了,最典型的比如TCP三次握手、为什么要握手,大家基本都看过,但是种感觉还差那么一点点。都要看是因为面试官总要问,所以不能不知道啊。

我们来看一个典型的面试问题:

问:为什么TCP是可靠的?
答:因为TCP有连接(或者回答因为TCP有握手)

追问:为什么有连接就可靠了?(面试的人估计心里在骂,你这不是傻逼么,有连接就可靠啊)

追问:这个TCP连接的本质是什么?网络上给你保留了一个带宽所以能可靠?
答:……懵了(或者因为TCP有ack,所以可靠)

追问:握手的本质是什么?为什么握手就可靠了
答:因为握手需要ack
追问:那这个ack也只是保证握手可靠,握手是怎么保证后面可靠的?握手本质做了什么事情?

追问:有了ack可靠后还会带来什么问题(比如发一个包ack一下,肯定是可行的,但是效率不行,面试官想知道的是这里TCP怎么传输的,从而引出各个buffer、拥塞窗口的概念)

基本上我发现99%的程序员会回答TCP相对UDP是可靠的,70%以上的程序员会告诉你可靠是因为有ack(其他的会告诉你可靠是因为握手或者有连接),再追问下次就开始王顾左右而言他、胡言乱语。

我的理解:

物理上没有一个连接的东西在这里,udp也类似会占用端口、ip,但是大家都没说过udp的连接。而本质上我们说tcp的握手是指tcp是协商和维护一些状态信息的,这个状态信息就包含seq、ack、窗口/buffer,tcp握手就是协商出来这些初始值。这些状态才是我们平时所说的tcp连接的本质。

这说明大部分程序员对问题的本质的理解上出了问题,或者教科书描述的过于教条不够接地气所以看完书本质没get到。

想想 费曼学习方法 中对事物本质的理解的重要性。

## 重点掌握如下两篇文章

一个网络包是如何到达目的地的 – 这篇可以帮你掌握网络如何运转,在本机上从端口、ip、mac地址如何一层层封上去,链路上每一个点拆开mac看看,拆看ip看看,然后替换mac地址继续扔到链路的下一跳,这样一跳跳到达目的。

对BDP、Buffer、各种窗口、rt的理解和运用 这一篇可以让你入门TCP

以上两篇都是站在程序员的角度来剖析关于网络我们应该掌握哪些,也许第一篇有点像网工要掌握的,实际我不这么认为,目前很流行的微服务化、云原生对网络的要求更高了,大多时候需要程序员去掌握这些,也就是在网络包从你的网卡离开你才有资格呼叫网工,否则成本很高!

我本周还碰到了网络不通的问题

我的测试机器不能连外网(公司安全策略)

走流程申请开通,开通后会在测试机器安装客户端以及安全配置文件

但仍然不通,客户端自检都能通

我的排查就是第一篇文章:ping 公网ip;ip route get 公网-ip;ping 网关;

很快就发现是路由的问题,公网ip正好命中了docker 容器添加的某个路由,以及默认路由缺失

如果我自己不会那就开工单、描述问题、call各种人、申请权限……

我碰到的程序员一看到网络连接异常就吓尿了,不关我的事,网络不通,但是在call人前你至少可以做:

  1. ping ip 通不通(也有个别禁掉了icmp)
  2. telnet ip port通不通
  3. 网络包发出去没有(抓包)
  4. 是不是都不通还是只有你的机器不通

来看一个案例

我第一次看RFC1180的时候是震惊的,觉得讲述的太好了,2000字就把一本教科书的知识阐述的无比清晰、透彻。但是实际上我发现很快就忘了,而且大部分程序员基本都是这样

RFC1180写的确实很好,清晰简洁,图文并茂,结构逻辑合理,但是对于95%的程序员没有什么用,当时看的时候很爽、也觉得自己理解了、学会了,实际上看完几周后就忘得差不多了。问题出在这种RFC偏理论多一点看起来完全没有体感无法感同身受,所以即使似乎当时看懂了,但是忘得也快,需要一篇结合实践的文章来帮助理解

在这个问题上,让我深刻地理解到:

一流的人看RFC就够了,差一些的人看《TCP/IP卷1》,再差些的人要看一个个案例带出来的具体知识的书籍了,比如《wireshark抓包艺术》,人和人的学习能力有差别必须要承认。

也就是我们要认识到每个个人的学习能力的差异,我超级认同这篇文章中的一个评论

看完深有感触,尤其是后面的知识效率和工程效率型的区别。以前总是很中二的觉得自己看一遍就理解记住了,结果一次次失败又怀疑自己的智商是不是有问题,其实就是把自己当作知识效率型来用了。一个不太恰当的形容就是,有颗公主心却没公主命!

嗯,大部分时候我们都觉得自己看一遍就理解了记住了能实用解决问题了,实际上了是马上忘了,停下来想想自己是不是这样的?在网络的相关知识上大部分看RFC、TCP卷1等东西是很难实际理解的,还是要靠实践来建立对知识的具体的理解,而网络相关的东西基本离大家有点远(大家不回去读tcp、ip源码,纯粹是靠对书本的理解),所以很难建立具体的概念,所以这里有个必杀技就是学会抓包和用wireshark看包,同时针对实际碰到的文题来抓包、看包分析。

比如这篇《从计算机知识到落地能力,你欠缺了什么?》就对上述问题最好的阐述,程序员最常碰到的网络问题就是网络为啥不通?

这是最好建立对网络知识具体理解和实践的机会,你把《从计算机知识到落地能力,你欠缺了什么?》实践完再去看RFC1180 就明白了。通过案例把RFC1180抽象的描述给它具体化、场景化了,理解起来就很轻松不容易忘记了。

经验一: 通过具体的东西(案例、抓包)来建立对网络基础的理解

image-20220221151815993

不要追求知识的广度

学习网络知识过程中,不建议每个知识点都去看,因为很快会忘记,我的方法是只看经常碰到的问题点,碰到一个点把他学透理解明白。

比如我曾经碰到过 nslookup OK but ping fail–看看老司机是如何解决问题的,解决问题的方法肯定比知识点重要多了,同时透过一个问题怎么样通篇来理解一大块知识,让这块原理真正在你的知识体系中扎根下来 , 这个问题Google上很多人在搜索,说明很普遍,但是没找到有资料能把这个问题说清楚,所以借着这个机会就把 Linux下的 NSS(name service switch)的原理搞懂了。要不然碰到问题老司机告诉你改下 /etc/hosts 或者 /etc/nsswitch 或者 /etc/resolv.conf 之类的问题就能解决,但是你一直不知道这三个文件怎么起作用的,也就是你碰到过这种问题也解决过但是下次碰到类似的问题你不一定能解决。

当然对我来说为了解决这个问题最后写了4篇跟域名解析相关的文章,从windows到linux,涉及到vpn、glibc、docker等各种场景,我把他叫做场景驱动。后来换来工作环境从windows换到mac后又补了一篇mac下的路由、dns文章。

关于场景驱动学习的方法可以看这篇总结

TCP是最复杂的,要从实用出发

比如拥塞算法基本大家不会用到,了解下就行,你想想你有碰到过因为拥塞算法导致的问题吗?极少是吧。还有拥塞窗口、慢启动,这个实际中碰到的概率不高,面试要问你基本上是属于炫技类型。

实际碰到更多的是传输效率(对BDP、Buffer、各种窗口、rt的理解和运用),还有为什么连不通、连接建立不起来、为什么收到包不回复、为什么要reset、为什么丢包了之类的问题。

关于为什么连不通,我碰到了这个问题,随后在这个问题的基础上进行了总结,得到客户端建连接的时候抛异常,可能的原因(握手失败,建不上连接):

  • 网络不通,诊断:ping ip
  • 端口不通, 诊断:telnet ip port
  • rp_filter 命中(rp_filter=1, 多网卡环境), 诊断: netstat -s | grep -i filter
  • 防火墙、命中iptables 被扔掉了,可以试试22端口起sshd 能否正常访问,能的话说明是端口被干了
  • snat/dnat的时候宿主机port冲突,内核会扔掉 syn包。诊断: sudo conntrack -S | grep insert_failed //有不为0的
  • Firewalld 或者 iptables
  • 全连接队列满的情况,诊断: netstat -s | egrep “listen|LISTEN”
  • syn flood攻击, 诊断:同上
  • 服务端的内核参数 net.ipv4.tcp_tw_recycle(4.12内核删除这个参数了) 和 net.ipv4.tcp_timestamps 的值都为 1时,服务器会检查每一个 SYN报文中的时间戳(Timestamp,跟同一ip下最近一次 FIN包时间对比),若 Timestamp 不是递增的关系,就扔掉这个SYN包(诊断:netstat -s | grep “ passive connections rejected because of time stamp”),常见触发时间戳非递增场景:
    1. 4.10 内核,一直必现大概率性丢包。4.11 改成了 per-destination host的算法
    2. tcpping 这种时间戳按连接随机的,必现大概率持续丢包
    3. 同一个客户端通过直连或者 DNAT 后两条链路到同一个服务端,客户端生成时间戳是 by dst ip,导致大概率持续丢包
    4. 经过NAT/LVS 后多个客户端被当成一个客户端,小概率偶尔出现
    5. 网路链路复杂/链路长容易导致包乱序,进而出发丢包,取决于网络会小概率出现——通过 tc qdisc 可以来构造丢包重现该场景
    6. 客户端修改 net.ipv4.tcp_timestamps
      • 1->0,触发持续60秒大概率必现的丢包,60秒后恢复
      • 0->1 持续大概率一直丢包60秒; 60秒过后如果网络延时略高且客户端并发大一直有上一次 FIN 时间戳大于后续SYN 会一直概率性丢包持续下去;如果停掉所有流量,重启客户端流量,恢复正常
      • 2->1 丢包,情况同2
      • 1->2 不触发丢包
  • 若服务器所用端口是 time_wait 状态,这时新连接刚好和 time_wait 5元组重复,一般服务器不会回复syn+ack 而是回复time_wait 前的ack
  • NAT 哈希表满导致 ECS 实例丢包 nf_conntrack full, 诊断: dmesg |grep conntrack

为什么 drop SYN 包时不去看四元组?因为tiem_wait 状态是 per-host

0->1 60秒后仍然持续丢包:

image-20240803095126448

2->1 60秒后持续丢包:(非常神奇:在310客户端改不影响自己,导致510客户端(网络延时大)一直丢包,直到510 客户端重启流量才能恢复)

image-20240803093817441

tcp_reuse 参数只对客户端有效(客户端是指主动发起 fin 的一方),启用后会回收超过 1 秒的 time_wait 状态端口重复使用:参考:https://ata.atatech.org/articles/11020082442

如果服务端是Time_wait 状态时收到 SYN 包怎么办?

https://developer.aliyun.com/article/1262180

tcp connect 的流程是这样的:

1、tcp发出SYN建链报文后,报文到ip层需要进行路由查询

2、路由查询完成后,报文到arp层查询下一跳mac地址

3、如果本地没有对应网关的arp缓存,就需要缓存住这个报文,发起arp请求

4、arp层收到arp回应报文之后,从缓存中取出SYN报文,完成mac头填写并发送给驱动。

问题在于,arp层报文缓存队列长度默认为3。如果你运气不好,刚好赶上缓存已满,这个报文就会被丢弃。

TCP层发现SYN报文发出去3s(1s+2s)还没有回应,就会重发一个SYN。这就是为什么少数连接会3s后才能建链。

幸运的是,arp层缓存队列长度是可配置的,用 sysctl -a | grep unres_qlen 就能看到,默认值为3

Time_Wait

socket.close 默认是四次挥手,但如果tw bucket 满了就直接走 reset,比如很多机器设置的是 5000 net.ipv4.tcp_max_tw_buckets = 5000

bucket 溢出对应的监控指标:TCPTimeWaitOverflow

1
2
3
4
5
6
7
#netstat -s | grep -i overflow
439 times the listen queue of a socket overflowed
TCPTimeWaitOverflow: 377310115

#netstat -s | grep -i overflow
439 times the listen queue of a socket overflowed
TCPTimeWaitOverflow: 377314175

总结

  • 一定要会用tcpdump和wireshark(纯工具,没有任何门槛,用不好只有一个原因: 懒)
  • 多实践(因为网络知识离我们有点远、有点抽象),用好各种工具,工具能帮我们看到、摸到
  • 不要追求知识面的广度,深抠几个具体的知识点然后让这些点建立体系
  • 不要为那些基本用不到的偏门知识花太多精力,天天用的都学不过来对吧。

参考资料

per-connection random offset:https://lwn.net/Articles/708021/

关于TCP 半连接队列和全连接队列

最近碰到一个client端连接服务器总是抛异常的问题,然后定位分析并查阅各种资料文章,对TCP连接队列有个深入的理解

查资料过程中发现没有文章把这两个队列以及怎么观察他们的指标说清楚,希望通过这篇文章能把他们说清楚

问题描述

场景:JAVA的client和server,使用socket通信。server使用NIO。

1.间歇性的出现client向server建立连接三次握手已经完成,但server的selector没有响应到这连接。
2.出问题的时间点,会同时有很多连接出现这个问题。
3.selector没有销毁重建,一直用的都是一个。
4.程序刚启动的时候必会出现一些,之后会间歇性出现。

分析问题

正常TCP建连接三次握手过程:

image.png

  • 第一步:client 发送 syn 到server 发起握手;
  • 第二步:server 收到 syn后回复syn+ack给client;
  • 第三步:client 收到syn+ack后,回复server一个ack表示收到了server的syn+ack(此时client的56911端口的连接已经是established)

从问题的描述来看,有点像TCP建连接的时候全连接队列(accept队列,后面具体讲)满了,尤其是症状2、4. 为了证明是这个原因,马上通过 netstat -s | egrep “listen” 去看队列的溢出统计数据:

667399 times the listen queue of a socket overflowed

反复看了几次之后发现这个overflowed 一直在增加,那么可以明确的是server上全连接队列一定溢出了

接着查看溢出后,OS怎么处理:

# cat /proc/sys/net/ipv4/tcp_abort_on_overflow
0

tcp_abort_on_overflow 为0表示如果三次握手第三步的时候全连接队列满了那么server扔掉client 发过来的ack(在server端认为连接还没建立起来)

为了证明客户端应用代码的异常跟全连接队列满有关系,我先把tcp_abort_on_overflow修改成 1,1表示第三步的时候如果全连接队列满了,server发送一个reset包给client,表示废掉这个握手过程和这个连接(本来在server端这个连接就还没建立起来)。

接着测试,这时在客户端异常中可以看到很多connection reset by peer的错误,到此证明客户端错误是这个原因导致的(逻辑严谨、快速证明问题的关键点所在)

于是开发同学翻看java 源代码发现socket 默认的backlog(这个值控制全连接队列的大小,后面再详述)是50,于是改大重新跑,经过12个小时以上的压测,这个错误一次都没出现了,同时观察到 overflowed 也不再增加了。

到此问题解决,简单来说TCP三次握手后有个accept队列,进到这个队列才能从Listen变成accept,默认backlog 值是50,很容易就满了。满了之后握手第三步的时候server就忽略了client发过来的ack包(隔一段时间server重发握手第二步的syn+ack包给client),如果这个连接一直排不上队就异常了。

但是不能只是满足问题的解决,而是要去复盘解决过程,中间涉及到了哪些知识点是我所缺失或者理解不到位的;这个问题除了上面的异常信息表现出来之外,还有没有更明确地指征来查看和确认这个问题。

深入理解TCP握手过程中建连接的流程和队列

image.png
(图片来源:http://www.cnxct.com/something-about-phpfpm-s-backlog/)

如上图所示,这里有两个队列:syns queue(半连接队列);accept queue(全连接队列)

三次握手中,在第一步server收到client的syn后,把这个连接信息放到半连接队列中,同时回复syn+ack给client(第二步);

题外话,比如syn floods 攻击就是针对半连接队列的,攻击方不停地建连接,但是建连接的时候只做第一步,第二步中攻击方收到server的syn+ack后故意扔掉什么也不做,导致server上这个队列满其它正常请求无法进来

第三步的时候server收到client的ack,如果这时全连接队列没满,那么从半连接队列拿出这个连接的信息放入到全连接队列中,否则按tcp_abort_on_overflow指示的执行。

这时如果全连接队列满了并且tcp_abort_on_overflow是0的话,server过一段时间再次发送syn+ack给client(也就是重新走握手的第二步),如果client超时等待比较短,client就很容易异常了。

在我们的os中retry 第二步的默认次数是2(centos默认是5次):

net.ipv4.tcp_synack_retries = 2

如果TCP连接队列溢出,有哪些指标可以看呢?

上述解决过程有点绕,听起来蒙逼,那么下次再出现类似问题有什么更快更明确的手段来确认这个问题呢?

通过具体的、感性的东西来强化我们对知识点的理解和吸收

netstat -s

[root@server ~]#  netstat -s | egrep "listen|LISTEN" 
667399 times the listen queue of a socket overflowed
667399 SYNs to LISTEN sockets ignored

比如上面看到的 667399 times ,表示全连接队列溢出的次数,隔几秒钟执行下,如果这个数字一直在增加的话肯定全连接队列偶尔满了。

ss 命令

[root@server ~]# ss -lnt
Recv-Q Send-Q Local Address:Port  Peer Address:Port 
0        50               *:3306             *:* 

上面看到的第二列Send-Q 值是50,表示第三列的listen端口上的全连接队列最大为50,第一列Recv-Q为全连接队列当前使用了多少

全连接队列的大小取决于:min(backlog, somaxconn) . backlog是在socket创建的时候传入的,somaxconn是一个os级别的系统参数

这个时候可以跟我们的代码建立联系了,比如Java创建ServerSocket的时候会让你传入backlog的值:

ServerSocket()
	Creates an unbound server socket.
ServerSocket(int port)
	Creates a server socket, bound to the specified port.
ServerSocket(int port, int backlog)
	Creates a server socket and binds it to the specified local port number, with the specified backlog.
ServerSocket(int port, int backlog, InetAddress bindAddr)
	Create a server with the specified port, listen backlog, and local IP address to bind to.

(来自JDK帮助文档:https://docs.oracle.com/javase/7/docs/api/java/net/ServerSocket.html)

半连接队列的大小取决于:max(64, /proc/sys/net/ipv4/tcp_max_syn_backlog)。 不同版本的os会有些差异

我们写代码的时候从来没有想过这个backlog或者说大多时候就没给他值(那么默认就是50),直接忽视了他,首先这是一个知识点的忙点;其次也许哪天你在哪篇文章中看到了这个参数,当时有点印象,但是过一阵子就忘了,这是知识之间没有建立连接,不是体系化的。但是如果你跟我一样首先经历了这个问题的痛苦,然后在压力和痛苦的驱动自己去找为什么,同时能够把为什么从代码层推理理解到OS层,那么这个知识点你才算是比较好地掌握了,也会成为你的知识体系在TCP或者性能方面成长自我生长的一个有力抓手

netstat 命令

netstat跟ss命令一样也能看到Send-Q、Recv-Q这些状态信息,不过如果这个连接不是Listen状态的话,Recv-Q就是指收到的数据还在缓存中,还没被进程读取,这个值就是还没被进程读取的 bytes;而 Send 则是发送队列中没有被远程主机确认的 bytes 数

$netstat -tn  
Active Internet connections (w/o servers)
Proto Recv-Q Send-Q Local Address   Foreign Address State  
tcp0  0 server:8182  client-1:15260 SYN_RECV   
tcp0 28 server:22    client-1:51708  ESTABLISHED
tcp0  0 server:2376  client-1:60269 ESTABLISHED

**netstat -tn 看到的 Recv-Q 跟全连接半连接没有关系,这里特意拿出来说一下是因为容易跟 ss -lnt 的 Recv-Q 搞混淆,顺便建立知识体系,巩固相关知识点 **

Recv-Q 和 Send-Q 的说明
1
2
3
4
5
6
7
Recv-Q
Established: The count of bytes not copied by the user program connected to this socket.
Listening: Since Kernel 2.6.18 this column contains the current syn backlog.

Send-Q
Established: The count of bytes not acknowledged by the remote host.
Listening: Since Kernel 2.6.18 this column contains the maximum size of the syn backlog.
通过 netstat 发现问题的案例

自身太慢,比如如下netstat -t 看到的Recv-Q有大量数据堆积,那么一般是CPU处理不过来导致的:

image.png

下面的case是接收方太慢,从应用机器的netstat统计来看,也是压力端回复太慢(本机listen 9108端口)

image.png

send-q表示回复从9108发走了,没收到对方的ack,基本可以推断PTS到9108之间有瓶颈

上面是通过一些具体的工具、指标来认识全连接队列(工程效率的手段)

实践验证一下上面的理解

把java中backlog改成10(越小越容易溢出),继续跑压力,这个时候client又开始报异常了,然后在server上通过 ss 命令观察到:

Fri May  5 13:50:23 CST 2017
Recv-Q Send-QLocal Address:Port  Peer Address:Port
11         10         *:3306               *:*

按照前面的理解,这个时候我们能看到3306这个端口上的服务全连接队列最大是10,但是现在有11个在队列中和等待进队列的,肯定有一个连接进不去队列要overflow掉,同时也确实能看到overflow的值在不断地增大。

Tomcat和Nginx中的Accept队列参数

Tomcat默认短连接,backlog(Tomcat里面的术语是Accept count)Ali-tomcat默认是200, Apache Tomcat默认100.

#ss -lnt
Recv-Q Send-Q   Local Address:Port Peer Address:Port
0       100                 *:8080            *:*

Nginx默认是511

$sudo ss -lnt
State  Recv-Q Send-Q Local Address:PortPeer Address:Port
LISTEN    0     511              *:8085           *:*
LISTEN    0     511              *:8085           *:*

因为Nginx是多进程模式,所以看到了多个8085,也就是多个进程都监听同一个端口以尽量避免上下文切换来提升性能

总结

全连接队列、半连接队列溢出这种问题很容易被忽视,但是又很关键,特别是对于一些短连接应用(比如Nginx、PHP,当然他们也是支持长连接的)更容易爆发。 一旦溢出,从cpu、线程状态看起来都比较正常,但是压力上不去,在client看来rt也比较高(rt=网络+排队+真正服务时间),但是从server日志记录的真正服务时间来看rt又很短。

jdk、netty等一些框架默认backlog比较小,可能有些情况下导致性能上不去,比如这个 《netty新建连接并发数很小的case》
都是类似原因

希望通过本文能够帮大家理解TCP连接过程中的半连接队列和全连接队列的概念、原理和作用,更关键的是有哪些指标可以明确看到这些问题(工程效率帮助强化对理论的理解)。

另外每个具体问题都是最好学习的机会,光看书理解肯定是不够深刻的,请珍惜每个具体问题,碰到后能够把来龙去脉弄清楚,每个问题都是你对具体知识点通关的好机会。

最后提出相关问题给大家思考

  1. 全连接队列满了会影响半连接队列吗?
  2. netstat -s看到的overflowed和ignored的数值有什么联系吗?
  3. 如果client走完了TCP握手的第三步,在client看来连接已经建立好了,但是server上的对应连接实际没有准备好,这个时候如果client发数据给server,server会怎么处理呢?(有同学说会reset,你觉得呢?)

提出这些问题就是以这个知识点为抓手,让你的知识体系开始自我生长


参考文章:

http://veithen.github.io/2014/01/01/how-tcp-backlog-works-in-linux.html

http://www.cnblogs.com/zengkefu/p/5606696.html

http://www.cnxct.com/something-about-phpfpm-s-backlog/

http://jaseywang.me/2014/07/20/tcp-queue-%E7%9A%84%E4%B8%80%E4%BA%9B%E9%97%AE%E9%A2%98/

http://jin-yang.github.io/blog/network-synack-queue.html#

http://blog.chinaunix.net/uid-20662820-id-4154399.html

https://www.atatech.org/articles/12919

https://www.cnblogs.com/xiaolincoding/p/12995358.html

分析与思考——黄奇帆的复旦经济课笔记

这是一次奇特的读书经历,因为黄奇帆这本书的内容主要是17-19年的一些报告内容,里面给出了他的一些看法以及各种数据,所以在3年后的2021年来读的话,我能够搜索当前的数据来印证他的判断,这种穿越的感觉很好。

黄比较厉害的是思路、逻辑清晰,然后各种数据比较丰富,所以他对问题的判断和看法比较准确。另外一个准确的是任大炮。相较另外一些教授、专家就混乱无比了,比如易宪容

比如,去杠杆比他预估的要差多了,杠杆这几年不但没去成反而加大了;政府债务占比也没有按他的预期减少,稳中有增;房地产开工面积也在增加,当然增速很慢了,占GDP比重也在缓慢增加。

以下引用内容都是从网络搜索所得,其它内容为黄书中直接复制出来的,附录内容没有读。


去杠杆

  • 国家M2。2017年中国M2已经达到170万亿元,这几个月下来到5月底已经是176万亿元,我们的GDP 2017年是82万亿元,M2与GDP之比已经是2.1∶1。美国的M2跟它的GDP之比是0.9∶1,美国GDP是20万亿美元,他们M2统统加起来,尽管已经有了三次(Q1、Q2、Q3)的宽松,现在的M2其实也就18万亿美元,所以我们这个指标就显然是非常非常的高。
  • 我们国家金融业的增加值。2017年年底占GDP总量的7.9%,2016年年底是8.4%,2017年五六月份到了8.8%,下半年开始努力地约束金融业夸张性的发展或者说太高速的发展,把这个约束了一下,所以到2017年年底是7.9%,2018年1—5月份还是在7.8%左右。这个指标也是世界最高,全世界金融增加值跟全世界GDP来比的话,平均是在4%左右。像日本尽管有泡沫危机,从20世纪80年代一直到现在,基本上在百分之五点几。美国从1980年到2000年也是百分之五点几,2000年以来,一直到次贷危机才逐渐增加。2008年崩盘之前占GDP的百分之八点几。这几年约束了以后,现在是在7%左右。它是世界金融的中心,全世界的金融资源集聚在华尔街,集聚在美国,产生的金融增加值也就是7%,我们并没有把世界的金融资源增加值、效益利润集聚到中国来,中国的金融业为何能够占中国80多万亿元GDP的百分之八点几?中国在十余年前,也就是2005年的时候,金融增加值占当时GDP的5%不到,百分之四点几,快速增长恰恰是这些年异常扩张、高速发展的结果。这说明我们金融发达吗?不对,其实是脱实就虚,许多金融GDP把实体经济的利润转移过来,使得实体经济异常辛苦,从这个意义上说,这个指标是泡沫化的表现。
  • 我们国家宏观经济的杠杆率。非银行非金融的企业负债,政府部门的负债(40%多),加上居民部门的负债(50%),三方面加起来是GDP的2.5倍,250%,在世界100多个国家里我们是前5位,是偏高的,我们跟美国相当,美国也是250%,日本是最高的,现在是440%,英国也比较高,当然欧洲一些国家,比如意大利或者西班牙,以及像希腊等一些债务财政出问题的小的国家,他们也异常的高。即使这样,我们的债务杠杆率排在世界前5位,也是异常的高。
  • 每年全社会新增的融资。我们的企业每年都要融资,除了存量借新还旧,存量之外,有个增量,我们在十年前每年全社会新增融资量是五六万亿元,五年前新增的量在10万亿—12万亿元,2017年新增融资18万亿元。每年新增的融资里面,股权资本金性质的融资只占总的融资量的10%不到一点,也就是说91%是债权,要么是银行贷款,要么是信托,要么是小贷公司,或者直接是金融的债券。大家可以想象,如果每年新增的融资总是90%以上是债权,10%是股权的话,这个数学模型推它十年,十年以后中国的债务不会缩小,只会越来越高。

2021年4月末,广义货币(M2)余额226.21万亿元,同比增长8.1%,增速分别比上月末和上年同期低1.3个和3个百分点;狭义货币(M1)余额60.54万亿元,同比增长6.2%,增速比上月末低0.9个百分点,比上年同期高0.7个百分点;流通中货币(M0)余额8.58万亿元,同比增长5.3%。当月净回笼现金740亿元。

杠杆率是250%,在全世界来说是排在前面,是比较高的。这个指标里面又分成三个方面,其中政府的债务占GDP不到50%,国家统计公布的数据是40%多,但是有些隐性债务没算进去,就算算进去也不到50%。第二个方面是老百姓的债务,十年前还只占10%,五年前到了20%,我印象中有一年中国人民银行也说了,中国居民部门的债务还可以放一点杠杆,这两年按揭贷款异常发展起来,居民债务两年就上升到50%。老百姓这一块的债务,主要是房产的债务,也包括信用卡和其他投资,总的也占GDP50%左右。两个方面加起来就等于GDP,剩下的160%是企业债务,美国企业负债是美国GDP的60%,而中国企业的负债是GDP的160%,这个指标是有问题的

中国政府的40多万亿元债务是中央政府的债务有十几万亿元,地方政府的债务有20多万亿元,加在一起40多万亿元,占GDP 50%左右,我们是把区县、地市、省级政府到国家级统算在一起的。所以,我们中国政府的债务算得是比较充分的。

人民网北京2021年4月7日电 (记者王震)国务院新闻办公室4月7日就贯彻落实“十四五”规划纲要,加快建立现代财税体制有关情况举行发布会。财政部部长助理欧文汉介绍,截至2020年末,地方政府债务余额25.66万亿元,控制在全国人大批准的限额28.81万亿元之内,加上纳入预算管理的中央政府债务余额20.89万亿元,全国政府债务余额46.55万亿元,政府债务余额与GDP的比重为45.8%,低于国际普遍认同的60%警戒线,风险总体可控。

image.png

去企业负债杠杆方法:第一是坚定不移地把没有任何前途的、过剩的企业,破产关闭,伤筋动骨、壮士断腕,该割肿瘤就要割掉一块(5%)。第二是通过收购兼并,资产重组去掉一部分坏账,去掉一部分债务,同时又保护生产力(5%)。第三是优势的企业融资,股权融资从新增融资的10%增加到30%、40%、50%,这应该是一个要通过五年、十年实现的中长期目标。第四是柔性地、柔和地通货膨胀,稀释债务,一年2个点,五年就是10个点,也很可观。第五是在基本面上保持M2增长率和GDP增长率与物价指数增长率之和大体相当。我相信通过这五方面措施,假以时日,务实推进,那么有个三五年、近十年,我们去杠杆四五十个百分点的宏观目标就会实现。

怎么把股市融资和私募投资从10%上升到40%、50%,这就是我们中国投融资体制,金融体制要发生一个坐标转换。这里最重要的,实际上是两件事。第一件事,要把证券市场、资本市场搞好。十几年前上证指数两三千点,现在还是两三千点。美国股市指数翻了两番,香港股市指数也翻了两番。我国国民经济总量翻了两番,为什么股市指数不增长?这里面要害是什么呢?可以说有散户的结构问题,有长期资本缺乏的问题,有违规运作处罚不力的问题,有注册制不到位的问题,各种问题都可以说。归根到底最重要的一个问题是什么呢?就是退市制度没搞好。

供应侧结构化改革三去一降一补

供应侧结构化改革三去一降一补:去产能、去库存、去杠杆、降成本、补短板

中国所有的货物运输量占GDP的比重是15%,美国、欧洲都在7%,日本只有百分之五点几。我们占15%就比其他国家额外多了几万亿元的运输成本。中国交通运输的物流成本高,除了基础设施很大一部分是新建投资、折旧成本较高以外,相当大的部分是管理体制造成的。由于我们的管理、软件、系统协调性、无缝对接等方面存在很多问题,造成了各种物流成本抬高。在这个问题上,各个地方,各个系统,各个行业都把这方面问题重视一下、协调一下,人家7%,我们哪怕降不到7%的GDP占比,能够降3%—4%的占比,就省了3万亿—4万亿元

按国际惯例,个人所得税率一般低于企业所得税率,我国的个人所得税采取超额累进税率与比例税率相结合的方式征收,工资薪金类为超额累进税率5%—45%。最高边际税率45%,是在1950年定的,当时我国企业所得税率是55%,个人所得税率定在45%有它的理由。现在企业所得税率已经降到25%,个人所得税率还保持在45%,明显高于前者,也高于大多数国家25%左右的水平。

到2018年底,中国个人住房贷款余额25.75万亿元,而公积金个人住房贷款余额为4.98万亿元,在整个贷款余额中不到20%,其为人们购房提供低息贷款的功能完全可以交由商业银行按揭贷款来解决。可以考虑公积金合并为年金

互联网金融平台、物联网金融平台、物联网+金融形成的平台会在这里起颠覆性的、全息性的、五个全方位信息(全产业链的信息、全流程的信息、全空间布局的信息、全场景的信息、全价值链的信息)的配置作用

“三元悖论”,即安全、廉价、便捷三者不可能同时存在

鉴于互联网商业平台公司的商业模式已经远远超出传统商业规模所能达成的社会影响力,所以,互联网商业平台公司与其说是在从事商业经营,不如说是在从事网络社会的经营和管理。正因如此,国家有必要通过立法,构建一种由网络安全、金融安全、社会安全、财政安全等相关部门参加的“互联网技术研发信息日常跟踪制度”。

货币

“二战”后建立的“布雷顿森林体系”,即“美元与黄金挂钩,其他国家货币与美元挂钩”的“双挂钩”制度,其实质也是一种“金本位”制度,而1971年美国总统尼克松宣布美元与黄金脱钩也正式标志着美元放弃了以黄金为本位的货币制度,随之采取的是“主权信用货币制”

近十余年来,美国为了摆脱金融危机,政府债务总量从2007年的9万亿美元上升到2019年的22万亿美元,已经超过美国GDP

从1970年开始,欧美、日本等大部分世界发达国家逐步采用了“主权信用货币制”,在实践中总体表现较好,货币的发行量与经济增量相匹配,保持了经济的健康增长和物价的稳定。这种货币制度通常以M3、经济增长率、通胀率、失业率等作为“间接锚”,并不是完全的无锚制货币。

从1970年到2008年,美国政府在应用主权信用货币制度的过程中基本遵守货币发行纪律,货币的增长与GDP增长、政府债务始终保持适度的比例。1970年,美国基础货币约为700亿美元,2007年底约为8200亿美元,大约增长了12倍。与此同时,美国GDP从1970年的1.1万亿美元增长到2007年的14.5万亿美元,大概增长了13倍。美元在全世界外汇储备中的占比稳定在65%以上

从2008年年底至2014年10月,美联储先后出台三轮量化宽松政策,总共购买资产约3.9万亿美元。美联储持有的资产规模占国内生产总值的比例从2007年年底的6.1%大幅升至2014年年底的25.3%,资产负债表扩张到前所未有的水平。

从2008年到2019年,美国基础货币供应量从8200亿美元飙升到4万亿美元,整体约增长了5倍,与此同时,美国GDP仅增长了1.5倍,基础货币的发行增速几乎是同期GDP增速的3倍以上。在这种货币政策的驱动下,美国股市开启了十年长牛之路,股市从6000点涨到28000点。各类资产价格开始重新走上上涨之路,美国经济沉浸在一片欣欣向荣之中。

1913年美国《联邦储备法案》规定,美元的发行权归美联储所有。美国政府没有发行货币的权力,只有发行国债的权力。但实际上,美国政府可以通过发行国债间接发行货币。美国国会批准国债发行规模,财政部将设计好的不同种类的国债债券拿到市场上进行拍卖,最后拍卖交易中没有卖出去的由美联储照单全收,并将相应的美元现金交给财政部。这个过程中,财政部把国债卖给美联储取得现金,美联储通过买进国债获得利息,两全其美,皆大欢喜。

2008年前美国以国家信用为担保发行美债,美债余额始终控制在GDP的70%比例之内,国家信用良好。美债作为全世界交易规模最大的政府债券,长久以来保持稳定的收益,成为黄金以外另一种可靠的无风险资产。美国的货币供给总体上与世界经济的需求也保持着适当的比例,进一步加强了美元的信用。

美国GDP占全球GDP的比重已经从50%下降到24%,但美元仍然是主要的国际交易结算货币。尽管近年来美元在国际储备货币中的占比逐渐从70%左右滑落到62%,但美元的地位短时间内仍然看不到动摇的迹象

布雷顿森林体系解体后,各国以美元为货币“名义锚”的强制性随之弱化,但在自由选择条件下,绝大多数发展中国家仍然选择美元为“名义锚”,实行了锚定美元,允许一定浮动的货币调控制度。另有一些发展中国家选择锚定原来的宗主国,以德国马克、法国法郎和英镑等货币为“名义锚”。而主要的发达国家在货币寻锚的过程中,经历了一些波折之后,大多选择以“利率、货币发行量、通货膨胀率”等指标作为货币发行中间目标,实际上锚定的是国内资产。总之,从当前来看,世界的货币大致形成了两类发行制度:以“其他货币”为名义锚的货币发行体制和以“本国资产”为名义锚的货币发行体系,也称为主权信用货币制度。

香港采用的货币制度很独特,被称为“联系汇率”制度,又被称为“钞票局”制度,据说是19世纪一位英国爵士的发明。其基本内容是香港以某一种国际货币为锚(20世纪70年代以前以英镑为本位,80年代后改以美元为本位),即以该货币为储备发行港币,通过中央银行吞吐储备货币来达到稳定本币汇率的目标。在这种货币制度下,不仅需要储备相当规模的锚货币,其还有一个重大缺陷是必须放弃独立的货币政策,即本位货币加降息时,其也必须跟随。因此,这种货币制度只适用于小国或者小型经济体,对大国或大型经济体则不适用。

从新中国成立到如今,人民币发行制度经历了从“物资本位制”到“汇兑本位制”两个阶段,这两种不同时期实施的货币制度在当时都有效地促进了国民经济的发展。1995年以后,面对新的形势,中国人民银行探索通过改革实行了新的货币制度——“汇兑本位制”,即通过发行人民币对流入的外汇强制结汇,人民币汇率采取盯住美元的策略,从而保持人民币的汇率基本稳定。

在“汇兑本位制”下,我国主要有两种渠道完成人民币的发行。第一种,当外资到我国投资时,需要将其持有的外币兑换成人民币,这就是被结汇。结汇以后,在中国人民银行资产负债表上,一边增加了外汇资产,另一边增加了存款准备金的资产,这实际上就是基础货币发出的过程。中央银行的资产负债表中关于金融资产分为两部分,一部分是外币资产,另一部分是基础货币。基础货币包括M0和存款准备金,而这部分的准备金就是因为外汇占款而出现的。

第二种则是贸易顺差。中国企业由于进出口业务产生贸易顺差,实际上是外汇结余。企业将多余的外汇卖给商业银行,再由央行通过发行基础货币来购买商业银行收到的外汇。商业银行收到央行用于购买外汇的基础货币,就会通过M0流入社会。长此以往,就会增加通货膨胀的风险。央行为规避通货膨胀的风险,就会通过提高准备金率将多出的基础货币回收。

在“汇兑本位制”下,外汇储备可以视作人民币发行的基础或储备,且由于实行强制结汇,外汇占款逐渐成为我国基础货币发行的主要途径,到2013年末达到83%的峰值,此后略有下降,截至2019年7月末,外汇占款占中国人民银行资产总规模达到59.35%,说明有近六成人民币仍然通过外汇占款的方式发行。

现代货币理论(缩写MMT)是一种非主流[1]宏观经济学理论,认为现代货币体系实际上是一种政府信用货币体系。[2] 现代货币理论即主权国家的货币并不与任何商品和其他外币挂钩,只与未来税收公债相对应。[3]因为主权货币具有无限法偿性质,没有名义预算约束,只存在通货膨胀的实际约束。–基本上就是:主权信用货币制度

“汇兑本位制”的实质:锚定美元

我国的人民币汇率制度基于两个环节。第一,人民币的汇率是人民币和外币之间的交换比率,是人民币与一篮子货币形成的一个比价。我国在同其他国家进行投资、贸易时,人民币按照汇率进行兑换。由于美元是目前世界上最主要的货币,所以虽然人民币与一篮子货币形成相对均衡的比价,但由于美元在一篮子货币中占有较大的比重,人民币最重要的比价货币是美元。第二,我国实行结汇制,即我国的商业银行、企业,基本上不能保存外汇,必须将收到的外汇卖给央行。因此,由于我国的货币发行的基础是外汇,而美元在我国的外汇占款、一篮子货币中占比较高,因此可以说人民币是间接锚定美元发行的。

截至2018年12月底,中国人民银行资产总规模为37.25万亿元,其中外汇占款达21.25万亿元。外汇占款在货币发行中的份额已经从2013年的83%降低至2008年初的57%左右。与此同时,央行对其他存款性公司债权迅速扩张,从2014年到2016年底扩张了2.4倍,占总资产份额从7.4%升至24.7%。这说明了随着外汇占款成为基础货币回笼而非投放的主渠道,央行主要通过公开市场操作和各类再贷款便利操作购买国内资产来投放货币,不失为在外汇占款不足的情况下供给货币的明智选择。同时,央行连续降低法定存款准备金率,提高了货币乘数,一定程度上也缓解了国内流动性不足的问题。

外汇占款在货币发行存量中的比重仍然接近60%,只是通过一些货币政策工具缓解了原来“汇兑本位制”的问题。一旦日后出现大量贸易顺差导致外汇储备增加,货币发行制度就会又回到老路上。

实施“主权信用货币制度”是大国崛起的必然选择

从根本上来说,税收是货币的信用,财政可以是货币发行的手段,而且是最高效公平的手段,央行买国债是能够自主收放的货币政策手段。一旦货币超发后,央行只需要提高利率、提高存款准备金率回收基础货币,而财政部门也可以通过增加税收、注销政府债券的方式来消除多余的货币、避免通货膨胀。

实际上,信用货币制度最大的问题在于锚的不清晰、不稳定,缺乏刚性。

特别提款权(Special Drawing Right,SDR),亦称“纸黄金”(Paper Gold),最早发行于1969年,是国际货币基金组织根据会员国认缴的份额分配的,可用于偿还国际货币基金组织债务、弥补会员国政府之间国际收支逆差的一种账面资产。 其价值由美元、欧元、人民币、日元和英镑组成的一篮子储备货币决定。

主权信用货币背景下,人民币是由国债做锚的。中央银行为了发行基础货币,需要购买财政部发行的国债,但中央银行不能购买财政部为了弥补财政亏空发行的国债。《中华人民共和国中国人民银行法》规定,中国人民银行不得直接认购、包销国债和其他政府债券。这意味着中国人民银行不能以政府的债权作为抵押发行货币,只能参与国债二级市场的交易而不能参与国债一级市场的发行,央行直接购买国债来发行基础货币的方式就被法律禁止了。因此,建立以国债为基础的人民币发行制度,必须对相关法律法规进行修改。

2017年5月 房地产

中国房地产和实体经济存在“十大失衡”——土地供求失衡、土地价格失衡、房地产投资失衡、房地产融资比例失衡、房地产税费占地方财力比重失衡、房屋销售租赁比失衡、房价收入比失衡、房地产内部结构失衡、房地产市场秩序失衡、政府房地产调控失衡

土地调控得当、法律制度到位、土地金融规范、税制结构改革和公租房制度保障,并特别强调了“地票制度”对盘活土地存量,提高耕地增量的重要意义

为国家粮食战略安全计,我国土地供应应逐步收紧,2015年供地770万亩,2016年700万亩,今年计划供应600万亩

国家每年批准供地中,约有三分之一用于农村建设性用地,比如水利基础设施、高速公路等,真正用于城市的只占三分之二,这部分又一分为三:55%左右用于各种基础设施和公共设施,30%左右给了工业,实际给房地产开发的建设用地只有15%。这是三分之二城市建设用地中的15%,摊到全部建设用地中只占到10%左右,这个比例是不平衡的

对于供过于求的商品,哪怕货币泛滥,也可能价格跌掉一半。货币膨胀只是房价上涨的必要条件而非充分条件,只是外部因素而非内部因素。内因只能是供需关系

住房作为附着在土地上的不动产,地价高房价必然会高,地价低房价自然会低,地价是决定房价的根本性因素。如果只有货币这个外因存在,地价这个内因不配合,房价想涨也是涨不起来的。控制房价的关键就是要控制地价。

拍卖机制,加上新供土地短缺,旧城改造循环,这三个因素相互叠加,地价就会不断上升—核心加大供地可解地价过高

按经济学的经验逻辑,一个城市的固定资产投资中房地产投资每年不应超过25%

正常情况下,一个家庭用于租房的支出最好不要超过月收入的六分之一,超过了就会影响正常生活。买房也如此,不能超过职工全部工作年限收入的六分之一,按每个人一生工作40年左右时间算,“6—7年的家庭年收入买一套房”是合理的。—-中国每个人体制外算20年工作时间,体制内可算35年

从均价看,一线城市北京、上海、广州、深圳、杭州等,房价收入比往往已到40年左右。这个比例在世界已经处于很高的水平了。考虑房价与居民收入比,必须高收入对高房价,低收入对低房价,均价对均价。有人说,纽约房子比上海还贵,伦敦海德公园的房价也比上海高。但伦敦城市居民的人均收入要高出上海几倍。就均价而言,伦敦房价收入比还是在10年以内。

每年固定资产投资不应超过GDP的60%。如果GDP有1万亿元,固定资产投资达到1.3万亿元甚至1.5万亿元,一年两年可以,长远就会不可持续。固定资产投资不超过GDP的60%,再按“房地产投资不超过固定资产投资的25%”,也符合“房地产投资不超过GDP六分之一”这一基本逻辑。

大陆31个省会城市和直辖市中,房地产投资连续多年占GDP 60%以上的有5个,占40%以上的有16个,显然偏高

房地产17-18w亿,大概8.5w亿是直接留给卖地的地方政府
之后3-4w亿是各种建筑商供应商的辛苦钱
还有1w亿+流向的银行贷款的利息
1w亿+是各种非银金融机构,如信托和平安保险等
之后又是一轮税收,然后才是房地产商和房地产人
搞死房地产也许容易,但是你得指条明路,让这些人找到新地方做业务活着啊..

上海易居房地产研究院3月9日发布《2019年区域房地产依赖度》。该报告显示:房地产开发投资占GDP比重可以用来衡量当地经济对房地产的依赖程度,占比越高,说明经济对房地产的依赖度越高。2019年,房地产开发投资占GDP比重排名前三位的省市分别是海南、天津和重庆,占比分别为25.2%、19.3%和18.8%。

2020年杭州市GDP总量达到16106亿元,比2019年增长3.9%

有15个城市去年房地产开发投资额超过1000亿元,其中超过2000亿元的共8个,分别是杭州、郑州、广州、武汉、成都、南京、西安和昆明,杭州、郑州、广州三城更是超过了3000亿元。杭州以3397.27亿元在26城中位居榜首

2019年,除了杭州土地出让金继续领跑外,数据显示南京的卖地收入达到了1696.8亿元,同比增长77.32%,福州增幅为63.37%,昆明为59.85%,武汉为27.89%。去年土地出让金额没有超过1000亿元的城市中,合肥土地出让收入增长了33.32%,长沙增长了33.75%,贵阳增长了52%。

2020年,GDP前10强的城市依次为:上海、北京、深圳、广州、重庆、苏州、成都、杭州、武汉、南京。

image.png

2011年,全国人民币贷款余额54.8万亿元,其中房地产贷款余额10.7万亿元,占比不到20%。这一比例逐年走高,2016年全国106万亿元的贷款余额中,房地产贷款余额26.9万亿元,占比超过25%。也就是说,房地产占用了全部金融资金量的25%,而房地产贡献的GDP只有7%左右。2016年全国贷款增量的45%来自房地产,一些国有大型银行甚至70%—80%的增量是房地产。从这个意义上讲,房地产绑架了太多的金融资源,导致众多金融“活水”没有进入到实体经济,就是“脱实就虚”的具体表现。

这些年,中央加地方的全部财政收入中,房地产税费差不多占了35%,乍一看来,这一比例感觉还不高。但考虑到房地产税费属地方税、地方费,和中央财力无关,把房地产税费与地方财力相比较,则显得比重太高。全国10万亿元地方税中,有40%也就是4万亿是与房地产关联的,再加上土地出让金3.7万亿元,全部13万亿元左右的地方财政预算收入中就有近8万亿元与房地产有关(60%)。政府的活动太依赖房地产,地方政府财力离了房地产是会断粮的,这也是失衡的。

一般中等城市每2万元GDP造1平方米就够了,再多必过剩。对大城市而言,每平方米写字楼成本高一些,其资源利用率也会高一些,大体按每平方米4万元GDP来规划。

一个城市的土地供应总量一般可按每人100平方米来控制,这应该成为一个法制化原则。100万城市人口就供应100平方千米。爬行钉住,后发制人。

人均100平方米的城市建设用地,该怎么分配呢?不能都拿来搞基础设施、公共设施,也不能都拿来搞商业住宅。大体上,应该有55平方米用于交通、市政、绿地等基础设施和学校、医院、文化等公共设施,这是城市环境塑造的基本需要。对工业用地,应该控制在20平方米以内,每平方千米要做到100亿元产值。剩下的25平用于房地产开发

房产税应包括五个要点:(1)对各种房子存量、增量一网打尽,增量、存量一起收;(2)根据房屋升值额度计税,如果1%的税率,价值100万元的房屋就征收1万元,升值到500万元税额就涨到5万元;(3)越高档的房屋持有成本越高,税率也要相对提高;(4)低端的、中端的房屋要有抵扣项,使得全社会70%—80%的中低端房屋的交税压力不大;(5)房产税实施后,已批租土地70年到期后可不再二次缴纳土地出让金,实现制度的有序接替。这五条是房产税应考虑的基本原则。

房地产调控的长效机制:一是金融;二是土地;三是财税;四是投资;五是立法。

在1990年之前,中国是没有商品房交易的,那时候一年就是1000多万平方米。在1998年和1999年的时候,中国房地产一年新建房的交易销售量实际上刚刚达到1亿平方米。从1998年到2008年,这十年里平均涨了6倍,有的城市实际上涨到8倍以上,十年翻三番。2007年,销售量本来已经到了差不多7亿平方米,2008年全球金融危机发生了,在这个冲击下,中国的房产交易量也下降了,萎缩到6亿平方米。后来又过了5年,到了2012年前后,房地产的交易量翻了一番,从6亿平方米增长到12亿平方米。从2012年到2018年,又增加了5亿平方米。总之在过去的20年,中国房地产每年的新房销售交易量差不多从1亿平方米增长到17亿平方米,翻了四番多。

今后十几年,中国每年的房地产新房的交易量不仅不会继续增长翻番,还会每年小比例地有所萎缩,或者零增长,或者负增长。十几年以后,每年房地产的新房销售交易量可能下降到10亿平方米以内,大体上减少40%的总量。

今后十几年的房地产业发展趋势,不会是17亿平方米、18亿平方米、20亿平方米、30亿平方米,而是逐渐萎缩,当然这个萎缩不会在一年里面大规模萎缩20%、30%,大体上有十几年的过程,每年往下降。十几年后产生的销售量下降到10亿平方米以下

http://www.ce.cn/cysc/fdc/fc/202101/18/t20210118_36234860.shtml#:~:text=%E5%BD%93%E6%97%A5%E5%8F%91%E5%B8%83%E7%9A%84%E6%95%B0%E6%8D%AE%E6%98%BE%E7%A4%BA,%E7%9A%8415.97%E4%B8%87%E4%BA%BF%E5%85%83%E3%80%82: 2020年,中国商品房销售面积176086万平方米,比上年增长2.6%,2019年为下降0.1%。 商品房销售额173613亿元,增长8.7%,增速比上年提高2.2个百分点。 此前,中国商品房销售面积和销售额的最高纪录分为2018年的近17.17亿平方米和2019年的15.97万亿元。

image.png

1990年,中国人均住房面积只有6平方米;到2000年,城市人均住房面积也仅十几平方米,现在城市人均住房面积已近50平方米。人均住房面积偏小,也会产生改善性的购房需求。

根据国家统计局公布的数据,1982年至2019年,我国常住人口城镇化率从21.1%上升至60.6%,上升超过39个百分点;同期,户籍人口城镇化率仅从17.6%上升至44.4%,上升不到27个百分点。

经济日报-中国经济网北京2月28日讯国家统计局网站2月28日发布我国2020年国民经济和社会发展统计公报。 公报显示,2020年末,我国常住人口城镇化率超过60%。Feb 28, 2021

image.png

官方数据显示,2020年,我国的城镇化率高达63.89%,比发达国家80%的平均水平低了16.11%,与美国82.7%的城镇化水平还有18.81%的距离。

image.png

image.png

当前我国人均住房面积已经达到近50平方米

2012年,住建部下发了一个关于住宅和写字楼等各种商品性房屋的建筑质量标准,把原来中国住宅商品房30年左右的安全标准提升到了至少70年,甚至100年。这意味着从2010年以后,新建造的各种城市商品房,理论上符合质量要求的话,可以使用70年到100年,这也就是说老城市的折旧改造量会大量减少。

实际据说12年后因为利润率的原因房子质量在下降?!待证

中国各个省的省会城市大体上发展规律都会遵循“一二三四”的逻辑。所谓“一二三四”,就是这个省会城市往往占有这个省土地面积的10%不到,一般是5%—10%;但是它的人口一般会等于这个省总人口的20%;它的GDP有可能达到这个省总GDP的30%;它的服务业,不论是学校、医院、文化等政府主导的公共服务,还是金融、商业、旅游等市场化的服务业,一般会占到这个省总量的40%。

河南省有1亿人口,郑州目前只有1000万人口。作为省会城市,应承担全省20%的人口,所以十几年、20年以后郑州发展成2000万人口一点不用惊讶。同样的道理,郑州的GDP现在到了1万亿元,整个河南5万亿元,它贡献了20%,如果要30%的话应该是1.5万亿元,还相差甚远。对于服务业,一个地方每100万人应该有一个三甲医院,如果河南省1亿人口要有100个的话,郑州就应该有40个,它现在才9个三甲医院,每造一个三甲医院投资20多亿元,产生的营业额也是20多亿元,作为服务业,营业额对增加值贡献比率在80%以上。

大家可以关注现在近十个人口超过1000万的国家级超级大城市,根据这些省总的经济人口规模去算一下,它们都有十几年以后人口增长500万以上的可能。只要人口增长了,城市住宅房地产就会跟上去。所以我刚才说的大都市、超级大城市,人口在1000万—2000万之间的有一批城市还会扩张,过了2000万的,可能上面要封顶,但是在1000万—2000万之间的不会封顶,会形成它的趋势。

在今后的十几年,房地产开发不再是四处开花,而会相对集聚在省会城市及同等级区域性中心城市、都市圈中的中小城市和城市群中的大中型城市三个热点地区。

根据住房和城乡建设部于2020年底最新公布的《2019年城市建设统计年鉴》,符合中国“超大城市”标准的共有上海北京重庆广州深圳天津

东莞、武汉、成都、杭州、南京、郑州、西安、济南、沈阳和青岛这10个城市的城区人口处于500万到1000之间,属于特大城市。Jan 12, 2021

根据住建部最新数据,2019年底,长沙城区人口384.75万人,建成区面积377.95平方公里。 与2018年的374.43万人相比,2019年底长沙城区人口增加约10万人。 注意,这个城区的统计范围包括雨花、岳麓、芙蓉、天心、开福、望城六区,应该不包括长沙县,因为长沙县目前不是设区,也不是县级。Jan 26, 2021

长沙市总人口810万

image.png

image.png

从通货膨胀看,我国M2已经到了190万亿元,会不会今后十年M2再翻两番?不可能,这两年国家去杠杆、稳金融已经做到了让M2的增长率大体上等于GDP的增长率加物价指数,这几年的GDP增长率百分之六点几,物价指数加两个点,所以M2在2017年、2018年都是八点几,2019年1—6月份是8.5%,基本上是这样。M2如果是八点几的话,今后十几年,基本上是GDP增长率加物价指数,保持均衡的增长。如果中国的GDP今后十几年平均增长率大体在5%—6%,房地产价格的增长大体上不会超过M2的增长率,也不会超过GDP的增长率,一般会小于老百姓家庭收入的增长率。

9万多个房产企业中,排名在前的15%大开发商在去年的开发,实际的施工、竣工、销售的面积,在17亿平方米里面它们可能占了85%。意思是什么呢?15%的企业解决了17亿平方米的85%,就是14亿多平方米,剩下的企业只干了那么2亿多平方米,有大量的空壳公司。

中国的房地产企业,我刚才说9万多个,9万多个房产商的总负债率2018年是84%。中国前十位的销售规模在1万亿元左右的房产商,它们的负债率也是在81%。

REITs: Real Estate Investment Trusts,译为房地产投资信托基金

地王的产生都是因为房产商背后有银行,所以政府的土地部门,只要资格审查的时候查定金从哪儿来,拍卖的时候资金从哪儿来,只要审查管住这个,就一定能管住地王现象的出现

2000年,中国房地产增加值仅为4141亿元,在当年GDP中占比4.1%。二十年后,2020年中国房地产增加值跃升至74553亿元,GDP占比7.3%。在20年时间里,房地产增加值大涨70412亿元,增长率达78%。

房地产增加值从1万亿元增加到2万亿元用了5年,从2万亿元到3万亿元用了3年,从5万亿元到7万亿年只用了4年。房地产新创造价值的增长速度越来越快。

image.png

香港公租房面积:現時屋單位的人均室內面積不得小於七平方米,惟房委會近年興建單位時,面積均僅停留在合格線,供一至二人入住的甲類單位,面積只及14平方米,二至三人的乙類單位也只有21平方米。 尤有甚者,有報章整理房委會資料,2020至2024年度的甲、乙類單位佔52%,總數3.44萬個,較跟2015至2019年度落成單位高11個百分點。Jan 19, 2021

对外开放

近40年以来世界贸易的格局,国际贸易的产品结构、企业组织和管理的方式,国家和国家之间贸易有关的政策均发生了重要的变化。货物贸易中的中间品的比重上升到70%以上,在总贸易量中服务贸易的比重从百分之几变成了30%。

产品交易和贸易格局的变化,导致跨国公司的组织管理方式发生变化,谁控制着产业链的集群、供应链的纽带、价值链的枢纽,谁就是龙头老大。由于世界贸易格局特征的变化,由于跨国公司管理世界级的产品的管理模式的变化,也就是“三链”这种特征性的发展,引出了世界贸易新格局中的一个新的国际贸易规则制度的变化,就是零关税、零壁垒和零补助“三零”原则的提出,并将是大势所趋。中国自贸试验区的核心,也就是“三零”原则在自己这个区域里先行先试,等到国家签订FTA的时候,自贸试验区就为国家签订FTA提供托底的经验。

现在一个产品,涉及几千个零部件,由上千个企业在几百个城市、几十个国家,形成一个游走的逻辑链,那么谁牵头、谁在管理、谁把众多的几百个上千个中小企业产业链中的企业组织在一起,谁就是这个世界制造业的大头、领袖、集群的灵魂。

能提出行业标准、产品标准的企业往往是产品技术最大的发明者。谁控制供应链,谁其实就是供应链的纽带。你在组织整个供应链体系,几百个、上千个企业,都跟着你的指挥棒,什么时间、什么地点、到哪儿,一天的间隙都不差,在几乎没有零部件库存的背景下,几百个工厂,非常有组织、非常高效地在世界各地形成一个组合。在这个意义上讲,供应链的纽带也十分重要。

50年前关税平均是50%—60%。到了20世纪八九十年代,关税一般都降到了WTO要求的关税水平,降到了10%以下。WTO要求中国的关税也要下降。以前我们汽车进口关税最高达到170%。后来降到50%。现在我们汽车进口关税还在20%的水平。但我们整个中国的加权平均的关税率,20世纪八九十年代是在40%—50%,到了90年代末加入WTO的时候到了百分之十几。WTO给我们一个过渡期,要求我们15年内降到10%以内。我们到2015年的确降到9.5%,到去年已经降到7.5%。现在整个世界的贸易平均关税已经降到了5%以内,美国现在是2.5%。

目前对于进口汽车收取的关税税率是25%,还有对进口车收取17%的增值税,而根据汽车的排量收取不同的消费税税率。 排量在在1.5升(含)以下3%,1.5升至2.0升(含) 5%,2.0升至2.5升(含) 9%,2.5升至3.0升(含)12%,3.0升至4.0升(含) 15%,4.0升以上20%。Jun 26, 2020

进口货物的增值税则在2018年5月1日进行了下调,由17%降为16%。

假定这辆到岸价24万的进口车为中规进口车(原厂授权,4S店销售),排气量为4.0以上,且到岸时间为5月1日前,套入相关计算公式,则其所要缴纳税费为:

  关税:24万×25% =6万

  消费税:(24万+6万)÷(1-40%)×40% =20万

  增值税:(24万+6万+20万)×17% =8.5万

  税费合计34.5万,加上24万的到岸价,总共58.5万,即这辆进口汽车的抵岸价。

  常规而言,这个价格与90万指导价间的31.5万价差,即为运输等成本费用和国内经销商的利润。

24万的进口车为何国内要卖90万?

在这七八年,FTA,双边贸易体的讨论,或者是一个地区,五六个国家、七八个国家形成一个贸易体的讨论就不断增加,成为趋势。给人感觉好像发达国家都在进行双边谈判,把WTO边缘化了

所谓自由贸易协定(Free Trade Agrement:FTA)是指两个或两个以上的国家(包括独立关税地区)根据WTO相关规则,为实现相互之间的贸易自由化所进行的地区性贸易安排。 由自由贸易协定的缔约方所形成的区域称为自由贸易区。 FTA的传统含义是缔约国之间相互取消货物贸易关税和非关税贸易壁垒。

2019年10月8日,日本驻美国大使杉山晋辅与美国贸易谈判代表莱特希泽在白宫正式签署新日美贸易协定。美国总统特朗普不仅亲自出席见证签字,还邀请多位西北部农业州农民代表参加,声称自己为美国农民赢得了巨大市场,巧妙地将国际贸易协定变成了国内拉票筹码。

中国已经形成了世界产业链里面最大的产业链集群,但是这个集群里面,我们掌控纽带的,掌控标准的,掌控结算枢纽的,掌控价值链枢纽的企业并不多。比如华为,华为的零部件,由3600多家大大小小供应链上的企业生产。这全球的3000多家企业每年都来开供应链大会。华为就是掌控标准。它的供应链企业比苹果多两倍。为什么?苹果主要做手机,华为既做手机又做服务器、通信设备。通信设备里面的零部件原材料更多。所以,它掌控产业链上中下游的集群,掌控标准,也掌控价值链中的牵制中枢。

零关税第一个好处:对进口中间品实行零关税,将降低企业成本,提高产品的国际竞争力。

当中国制造业实施零关税的时候,事实上对于整个制造业产业链的完整化、集群化和纽带、控制能力有好处,对于中国制造业的产业链、供应链、价值链在中国形成枢纽、形成纽带、形成集团的龙头等各方面会有提升作用,这是第二个好处。

关税下降,会促进中国的生产力结构的提升,促进我们企业的竞争能力的加强,使得我们工商企业的成本下降。

我们现在差不多有6.6亿吨农作物粮食是在中国的土地上生产出来的,但是我们现在每年要进口农产品1亿吨。加在一起,也就是中国14亿人,一年要吃7.6亿吨农作物。这1亿吨里面,有个基本的分类。我们现在进口的1亿吨里面,有8000多万吨进口的是大豆、300多万吨小麦、300多万吨玉米、300多万吨糖,另外就是进口的猪肉、牛肉和其他的肉类,也有几百万吨。

从2010年起步,当年人民币只有近千亿元的结算量,从这些年发展来看,2018年已经到7万亿元了。也就是说,中国进出口贸易里面有7万亿元人民币是人家收了人民币而不去收美元

自贸区

在过去的40年,我们国家的开放有五个基本特点:

第一个就是以出口导向为基础,利用国内的资源优势和劳动力的比较优势,推动出口发展,带动中国经济更好地发展;

第二个就是以引进外资为主,弥补我们中国当时还十分贫困的经济和财力;

第三个就是以沿海开放为主,各种开发区或者各种特区,包括新区、保税区,都以沿海地区先行,中西部内陆逐步跟进;

第四个就是开放的领域主要是工业、制造业、房地产业、建筑业等先行开放,至于服务业、服务贸易、金融保险业务开放的程度比较低,即以制造业、建筑业等第二产业开放为主;

第五个就是我们国家最初几十年的开放以适应国际经济规则为主,用国际经济规则、国际惯例倒逼国内营商环境改革、改善,倒逼国内的各种机制体制变化,是用开放倒逼改革的这样一个过程。

2012年以后我们每年退休的人员平均在1500万人左右,但每年能够上岗的劳动力,不管农村的、城市的,新生的劳动力是1200万左右。实际最近五年,我们每年少了300万劳动力补充。

本来GDP应该每掉1个点退出200万就业人口。为什么几年下来没有感觉到有500万、1000万下岗工人出现呢?就是因为人口出现了对冲性均衡,正好这边下降,要退出人员,跟那边补充的人员不足,形成了平衡,所以实际上我们基础性劳动力条件发生了变化,人口红利逐步退出。

如果一个国家在五到十年里,连续都是世界第一、第二、第三的进口大国,那一定成为世界经济的强国。要知道进口大国是和经济强国连在一起的,美国是世界第一大进口国,它也理所当然是世界最大的经济强国。

中美贸易战

关于中国加入WTO,莱特希泽有五个观点:一是中国入世美国吃亏论;二是中国没有兑现入世承诺;三是中国强制美国企业转让技术;四是中国的巨额外汇顺差造成了美国2008年的金融危机;五是中国买了大量美国国债,操纵了汇率。

2008年美国金融危机原因是2001年科技互联网危机后,当时股市一年里跌了50%以上,再加上“9·11”事件,美国政府一是降息,从6%降到1%,二是采取零按揭刺激房地产,三是将房地产次贷在资本市场1∶20加杠杆搞CDS,最终导致泡沫经济崩盘。2007年,美国房地产总市值24.3万亿美元、占GDP比重达到173%;股市总市值达到了20万亿美元、占GDP比重达到135%。2008年金融危机后,美国股市缩水50%,剩下10万亿美元左右;房地产总市值缩水40%,从2008年的25万亿美元下降到2009年的15万亿美元。

一个成熟的经济体,政府每年总有占GDP 20%—30%的财政收入要支出使用,通常会生成15%左右的GDP,这部分国有经济产生的GDP是通过政府的投资和消费产生的,美国和欧洲各国都是如此。比如2017年,美国的GDP中有13.5%是美国政府财力支出形成的GDP。中国政府除了财政税收以外,还有土地批租等预算外收入,所以,中国政府财力支出占GDP的比重相对高一点,占17%左右

自1971年布雷顿森林体系解体,美元脱离了金本位,形成“无锚货币”,美元的货币发行体制转化为政府发债,美联储购买发行基础货币之后,全球的基础货币总量如脱缰野马,快速增长。从1970年不到1000亿美元,到1980年的3500亿美元,到1990年的7000亿美元,到2000年的1.5万亿美元,到2008年的4万亿美元,到2017年的21万亿美元。其中,美元的基础货币也从20世纪70年代的几百亿美元发展到今天的6万亿美元。

报告还预计,截至2021财年底,美国联邦公共债务将达23万亿美元,约占美国GDP的103%;到2031财年,美国联邦公共债务GDP的比重将进一步升至106%。Jul 2, 2021

image-20210729172410379

以下资料来源:https://pdf.dfcfw.com/pdf/H3_AP202106171498408427_1.pdf?1623944100000.pdf

image-20210729172820145

image-20210729173011549

image-20210729173116617

2020-01

上市公司几千家,几十家金融企业每年利润几乎占了几千家实体经济企业利润的50%,这个比重太高,造成我们脱实向虚。三是金融企业占GDP的比重是百分之八点几,是全世界最高的。世界平均金融业GDP占全球GDP的5%左右,欧洲也好、美国也好、日本也好,只要一到7%、8%,就会冒出一场金融危机,自己消除坏账后萎缩到5%、6%,过了几年,又扩张达到7%、8%,又崩盘。

SWIFT 是 Society for Worldwide Interbank Financial Telecommunications 的缩写,翻译成中文叫做「环球银行金融电讯协会」。看名字就知道,他是个搞通讯的,还冠冕堂皇的是一个非盈利性组织。

另外,SWIFT 官网上是这么用中文介绍自己的:SWIFT 为社群提供报文传送平台和通信标准,并在连接、集成、身份识别、数据分析和合规等领域的产品和服务(我一字未改,这多语言做的……语句都不通顺,好在不影响理解);用英文则是这么介绍的:SWIFT is a global member-owned cooperative and the world’s leading provider of secure financial messaging services。

TCP相关参数解释

读懂TCP参数前得先搞清楚内核中出现的HZ、Tick、Jiffies三个值是什么意思

HZ

它可以理解为1s,所以120*HZ就是120秒,HZ/5就是200ms。

HZ表示CPU一秒种发出多少次时间中断–IRQ-0,Linux中通常用HZ来做时间片的计算(参考)。

这个值在内核编译的时候可设定100、250、300或1000,一般设置的是1000

1
2
3
#cat /boot/config-`uname -r` |grep 'CONFIG_HZ='
CONFIG_HZ=1000 //一般默认1000, Linux核心每隔固定周期会发出timer interrupt (IRQ 0),HZ是用来定义
每一秒有几次timer interrupts。举例来说,HZ为1000,代表每秒有1000次timer interrupts

HZ的设定:
#make menuconfig
processor type and features—>Timer frequency (250 HZ)—>

HZ的不同值会影响timer (节拍)中断的频率

Tick

Tick是HZ的倒数,意即timer interrupt每发生一次中断的间隔时间。如HZ为250时,tick为4毫秒(millisecond)。

Jiffies

Jiffies为Linux核心变数(32位元变数,unsigned long),它被用来记录系统自开机以来,已经过多少的tick。每发生一次timer interrupt,Jiffies变数会被加一。值得注意的是,Jiffies于系统开机时,并非初始化成零,而是被设为-300*HZ (arch/i386/kernel/time.c),即代表系统于开机五分钟后,jiffies便会溢位。那溢出怎么办?事实上,Linux核心定义几个macro(timer_after、time_after_eq、time_before与time_before_eq),即便是溢位,也能藉由这几个macro正确地取得jiffies的内容。

另外,80x86架构定义一个与jiffies相关的变数jiffies_64 ,此变数64位元,要等到此变数溢位可能要好几百万年。因此要等到溢位这刻发生应该很难吧。那如何经由jiffies_64取得jiffies呢?事实上,jiffies被对应至jiffies_64最低的32位元。因此,经由jiffies_64可以完全不理会溢位的问题便能取得jiffies。

数据取自于4.19内核代码中的 include/net/tcp.h

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
//rto的定义,不让修改,到每个ip的rt都不一样,必须通过rtt计算所得, HZ 一般是1000
#define TCP_RTO_MAX ((unsigned)(120*HZ))
#define TCP_RTO_MIN ((unsigned)(HZ/5)) //在rt很小的环境中计算下来RTO基本等于TCP_RTO_MIN

/* Maximal number of ACKs sent quickly to accelerate slow-start. */
#define TCP_MAX_QUICKACKS 16U //默认前16个ack必须quick ack来加速慢启动

//默认delay ack不能超过200ms
#define TCP_DELACK_MAX ((unsigned)(HZ/5)) /* maximal time to delay before sending an ACK */
#if HZ >= 100
//默认 delay ack 40ms,不能修改和关闭
#define TCP_DELACK_MIN ((unsigned)(HZ/25)) /* minimal time to delay before sending an ACK */
#define TCP_ATO_MIN ((unsigned)(HZ/25))
#else
#define TCP_DELACK_MIN 4U
#define TCP_ATO_MIN 4U
#endif

#define TCP_SYNQ_INTERVAL (HZ/5) /* Period of SYNACK timer */
#define TCP_KEEPALIVE_TIME (120*60*HZ) /* two hours */
#define TCP_KEEPALIVE_PROBES 9 /* Max of 9 keepalive probes */
#define TCP_KEEPALIVE_INTVL (75*HZ)

/* cwnd init 默认大小是10个拥塞窗口,也可以通过sysctl_tcp_init_cwnd来设置,要求内核编译的时候支持*/
#if IS_ENABLED(CONFIG_TCP_INIT_CWND_PROC)
extern u32 sysctl_tcp_init_cwnd;
/* TCP_INIT_CWND is rvalue */
#define TCP_INIT_CWND (sysctl_tcp_init_cwnd + 0)
#else
/* TCP initial congestion window as per rfc6928 */
#define TCP_INIT_CWND 10
#endif

/* Flags in tp->nonagle 默认nagle算法关闭的*/
#define TCP_NAGLE_OFF 1 /* Nagle's algo is disabled */
#define TCP_NAGLE_CORK 2 /* Socket is corked */
#define TCP_NAGLE_PUSH 4 /* Cork is overridden for already queued data */

//对应time_wait, alios 增加了tcp_tw_timeout 参数可以来设置这个值,当前网络质量更好了这个值可以减小一些
#define TCP_TIMEWAIT_LEN (60*HZ) /* how long to wait to destroy TIME-WAIT
* state, about 60 seconds */

#define TCP_SYN_RETRIES 6 /* This is how many retries are done
* when active opening a connection.
* RFC1122 says the minimum retry MUST
* be at least 180secs. Nevertheless
* this value is corresponding to
* 63secs of retransmission with the
* current initial RTO.
*/

#define TCP_SYNACK_RETRIES 5 /* This is how may retries are done
* when passive opening a connection.
* This is corresponding to 31secs of
* retransmission with the current
* initial RTO.
*/

rto 不能设置,而是根据到不同server的rtt计算得到,即使RTT很小(比如0.8ms),但是因为RTO有下限,最小必须是200ms,所以这是RTT再小也白搭;RTO最小值是内核编译是决定的,socket程序中无法修改,Linux TCP也没有任何参数可以改变这个值。

delay ack

正常情况下ack可以quick ack也可以delay ack,redhat在sysctl中可以设置这两个值

/proc/sys/net/ipv4/tcp_ato_min

默认都是推荐delay ack的,一定要修改成quick ack的话(3.10.0-327之后的内核版本):

1
2
3
4
5
6
7
8
9
10
11
$sudo ip route show
default via 10.0.207.253 dev eth0 proto dhcp src 10.0.200.23 metric 1024
10.0.192.0/20 dev eth0 proto kernel scope link src 10.0.200.23
10.0.207.253 dev eth0 proto dhcp scope link src 10.0.200.23 metric 1024

$sudo ip route change default via 10.0.207.253 dev eth0 proto dhcp src 10.0.200.23 metric 1024 quickack 1

$sudo ip route show
default via 10.0.207.253 dev eth0 proto dhcp src 10.0.200.23 metric 1024 quickack 1
10.0.192.0/20 dev eth0 proto kernel scope link src 10.0.200.23
10.0.207.253 dev eth0 proto dhcp scope link src 10.0.200.23 metric 1024

默认开启delay ack的抓包情况如下,可以清晰地看到有几个40ms的ack

image.png

第一个40ms 的ack对应的包, 3306收到 update请求后没有ack,而是等了40ms update也没结束,就ack了

image.png

同样的机器,执行quick ack后的抓包

sudo ip route change default via 10.0.207.253 dev eth0 proto dhcp src 10.0.200.23 metric 1024 quickack 1

image.png

同样场景下,改成quick ack后基本所有的ack都在0.02ms内发出去了。

比较奇怪的是在delay ack情况下不是每个空ack都等了40ms,这么多包只看到4个delay了40ms,其它的基本都在1ms内就以空包就行ack了。

将 quick ack去掉后再次抓包仍然抓到了很多的40ms的ack。

Java中setNoDelay是指关掉nagle算法,但是delay ack还是存在的。

C代码中关闭的话:At the application level with the TCP_QUICKACK socket option. See man 7 tcp for further details. This option needs to be set with setsockopt() after each operation of TCP on a given socket

连接刚建立前16个包一定是quick ack的,目的是加快慢启动

一旦后面进入延迟ACK模式后,如果接收的还没有回复ACK确认的报文总大小超过88bytes的时候就会立即回复ACK报文

参考资料

https://access.redhat.com/solutions/407743

https://www.cnblogs.com/lshs/p/6038635.html

存储原理

本文记录各种存储、接口等原理性东西

img

img

磁盘信息

Here are some common ones:

  • hdX — ATA hard disk, pre-libata. You’ll only see this with old distros (probably based on Linux 2.4.x or older)
  • sdX — “SCSI” hard disk. Also includes SATA and SAS. And IDE disks using libata (on any recent distro).
  • hdXY, sdXY — Partition on the hard disk hdX or sdX.
  • loopX — Loopback device, used for mounting disk images, etc.
  • loopXpY — Partitions on the loopback device loopX; used when mounting an image of a complete hard drive, etc.
  • scdX, srX — “SCSI” CD, using same weird definition of “SCSI”. Also includes DVD, Blu-ray, etc.
  • mdX — Linux MDraid
  • dm-X — Device Mapper. Use -N to see what these are, or ls -l /dev/mapper. Device Mapper underlies LVM2 and dm-crypt. If you’re using either LVM or encrypted volumes, you’ll see dm-X devices.

回顾串行磁盘技术的发展历史,从光纤通道,到SATA,再到SAS,几种技术各有所长。光纤通道最早出现的串行化存储技术,可以满足高性能、高可靠和高扩展性的存储需要,但是价格居高不下;SATA硬盘成本倒是降下来了,但主要是用于近线存储和非关键性应用,毕竟在性能等方面差强人意;SAS应该算是个全才,可以支持SAS和SATA磁盘,很方便地满足不同性价比的存储需求,是具有高性能、高可靠和高扩展性的解决方案。

SSD中,SATA、m2、PCIE和NVME各有什么意义

高速信号协议

SAS,SATA,PCIe 这三个是同一个层面上的,模拟串行高速接口。

  • SAS 对扩容比较友好,也支持双控双活。接上SAS RAID 卡,一般在阵列上用的比较多。
  • SATA 对热插拔很友好,早先台式机装机市场的 SSD基本上都是SATA的,现在的 机械硬盘也是SATA接口居多。但速率上最高只能到 6Gb/s,上限 768MB/s左右,现在已经慢慢被pcie取代。
  • PCIe 支持速率更高,也离CPU最近。很多设备 如 网卡,显卡也都走pcie接口,当然也有SSD。现在比较主流的是PCIe 3.0,8Gb/s 看起来好像也没比 SATA 高多少,但是 PCIe 支持多个LANE,每个LANE都是 8Gb/s,这样性能就倍数增加了。目前,SSD主流的是 PCIe 3.0x4 lane,性能可以做到 3500MB/s 左右。

传输层协议

SCSI,ATA,NVMe 都属于这一层。主要是定义命令集,数字逻辑层。

  • SCSI 命令集 历史悠久,应用也很广泛。U盘,SAS 盘,还有手机上 UFS 之类很多设备都走的这个命令集。
  • ATA 则只是跑在SATA 协议上
  • NVMe 协议是有特意为 NAND 进行优化。相比于上面两者,效率更高。主要是跑在 PCIe 上的。当然,也有NVMe-MI,NVMe-of之类的。是个很好的传输层协议。

NVMe(Non-Volatile Memory Express)

NVMe其实与AHCI一样都是逻辑设备接口标准(是接口标准,并不是接口),NVMe全称Non-Volatile Memory Express,非易失性存储器标准,是使用PCI-E通道的SSD一种规范,NVMe的设计之初就有充分利用到PCI-E SSD的低延时以及并行性,还有当代处理器、平台与应用的并行性。SSD的并行性可以充分被主机的硬件与软件充分利用,相比与现在的AHCI标准,NVMe标准可以带来多方面的性能提升。

NVMe标准是面向PCI-E SSD的,使用原生PCI-E通道与CPU直连可以免去SATA与SAS接口的外置控制器(PCH)与CPU通信所带来的延时。而在软件层方面,NVMe标准的延时只有AHCI的一半不到,NVMe精简了调用方式,执行命令时不需要读取寄存器;而AHCI每条命令则需要读取4次寄存器,一共会消耗8000次CPU循环,从而造成大概2.5微秒的延迟。

NVMe标准和传统的SATA/SAS相比,一个重大的差别是引入了多队列机制

主机与SSD进行数据交互采用“生产者-消费者”模型进行数据交互。在原有AHCI规范中,只定义了一个交互队列,主机与HDD之间的数据交互只能通过一个队列通信,多核处理器也只能通过一个队列与HDD进行数据交互。在传统磁盘存储时代,单队列在一个IO调度器,可以很好的保证提交请求的IO顺序最优化。

而NAND存储介质具有很高的性能,AHCI原有的规范不再适用,NVMe规范替代了原有的AHCI规范,在软件层面的处理命令也进行了重新定义,不再采用SCSI/ATA命令规范集。相比以前AHCI、SAS等协议规范,NVMe规范是一种非常简化,面向新型存储介质的协议规范。该规范将存储外设拉到了处理器局部总线上,性能大为提升。并且主机和SSD处理器之间采用多队列的设计,适应了多核的发展趋势,每个处理器核与SSD之间可以采用独立的硬件Queue Pair进行数据交互。

img

如上图从软件的角度来看,每个CPU Core都可以创建一对Queue Pair和SSD进行数据交互。Queue Pair由Submission Queue与Completion Queue构成,通过Submission queue发送数据;通过Completion queue接受完成事件。SSD硬件和主机驱动软件控制queue的Head与Tail指针完成双方的数据交互。

nvme多队列

img

img

物理接口

M.2 , U.2 , AIC, NGFF 这些属于物理接口

像 M.2 可以是 SATA SSD 也可以是 NVMe(PCIe) SSD。金手指上有一个 SATA/PCIe 的选择信号,来区分两者。很多笔记本的M.2 接口也是同时支持两种类型的盘的。

  • M.2 , 主要用在 笔记本上,优点是体积小,缺点是散热不好。
  • U.2,主要用在 数据中心或者一些企业级用户,对热插拔需求高的地方。优点热插拔,散热也不错。一般主要是pcie ssd(也有sas ssd),受限于接口,最多只能是 pcie 4lane
  • AIC,企业,行业用户用的比较多。通常会支持pcie 4lane/8lane,带宽上限更高

SSD 的性能特性和机制

SSD 的内部工作方式和 HDD 大相径庭,我们先了解几个概念。

单元(Cell)、页面(Page)、块(Block)

当今的主流 SSD 是基于 NAND 的,它将数字位存储在单元中。每个 SSD 单元可以存储一位或多位。对单元的每次擦除都会降低单元的寿命,所以单元只能承受一定数量的擦除。单元存储的位数越多,制造成本就越低,SSD 的容量也就越大,但是耐久性(擦除次数)也会降低。

一个页面包括很多单元,典型的页面大小是 4KB,页面也是要读写的最小存储单元。SSD 上没有“重写”操作,不像 HDD 可以直接对任何字节重写覆盖。一个页面一旦写入内容后就不能进行部分重写,必须和其它相邻页面一起被整体擦除重置。

多个页面组合成块。一个块(Block)的典型大小为 512KB 或 1MB,也就是大约 128 或 256 (Page–16KB)页。块是擦除的基本单位,每次擦除都是整个块内的所有页面都被重置。

image-20210915090731401

擦除速度相对很慢,通常为几毫秒。所以对同步的 IO,发出 IO 的应用程序可能会因为块的擦除,而经历很大的写入延迟。为了尽量地减少这样的场景,保持空闲块的阈值对于快速的写响应是很有必要的。SSD 的垃圾回收(GC)的目的就在于此。GC 可以回收用过的块,这样可以确保以后的页写入可以快速分配到一个全新的页。

SSD的基本结构:

image-20210915090459823

比如Intel P4510 SSD控制器内部集成了两个Cotex A15 ARM core,这两个CPU core各自处理50%物理地址空间的读写命令(不同CPU负责不同的Die,以提高并发度)。在处理IO命令的过程中,为了充分发挥两个cpu的并行处理效率,每个cpu core单次处理的最大数据块是128kB。所以P4510对于128k对齐(4k,8k,16k,32k,64k,128k)或者128k整数倍(256k,512k,1024k)的数据块的处理效率最高。因为这些数据块都能够在SSD内部被组装或者拆分为完整的128k数据块。但是,对于非128k对齐的数据块(68k,132k,260k,516k,1028k),由于每个提交给SSD的写命令都有一个非128k对齐的“尾巴”需要跨CPU来处理,这样便会导致SSD处理单个命令的效率下降,写带宽随之也下降。

imgimg

SSD内部使用写缓存。写缓存主要用来降低写延迟。当写请求发送给SSD时,写数据会被先保存在写缓存,此时SSD会直接发送确认消息通知主机端写请求已完成,实现最低的写延迟。SSD固件在后台会异步的定期把写缓存中的数据通过写操作命令刷回给NAND颗粒。为了满足写操作的持久化语义,SSD内有大容量电容保证写缓存中数据的安全。当紧急断电情况发生时,固件会及时把写缓存中的数据写回NAND颗粒. 也就是紧急断电后还能通过大电容供电来维持最后的落盘。

SSD内嵌内存容量的问题也限制了大容量NVMe SSD的发展,为了解决内存问题,目前一种可行的方法是增大sector size。标准NVMe SSD的sector size为4KB,为了进一步增大NVMe SSD的容量,有些厂商已经开始采用16KB的sector size。16KB Sector size的普及应用,会加速大容量NVMe SSD的推广。

以海康威视E200P为例,PCB上的硬件PLP掉电保护电路从D200Pro的10个钽电容+6个电感,简化为6个钽电容+6个电感。钽电容来自Panasonic,单颗47uF,6个钽电容并联可以为SSD提供几十毫秒的放电时间,让SSD把处理中的数据写入NAND中并更新映射表。这样的硬件PLP电路对比普通的家用产品要强悍很多。

img

或者如下结构:

image-20220923172817591

SSD存储持久化原理

记录一个比特很容易理解。给电容里面充上电有电压的时候就是 1,给电容放电里面没有电就是 0。采用这样方式存储数据的 SSD 硬盘,我们一般称之为使用了 SLC 的颗粒,全称是 Single-Level Cell,也就是一个存储单元中只有一位数据。

但是,这样的方式会遇到和 CPU Cache 类似的问题,那就是,同样的面积下,能够存放下的元器件是有限的。如果只用 SLC,我们就会遇到,存储容量上不去,并且价格下不来的问题。于是呢,硬件工程师们就陆续发明了 MLC(Multi-Level Cell)、TLC(Triple-Level Cell)以及 QLC(Quad-Level Cell),也就是能在一个电容里面存下 2 个、3 个乃至 4 个比特。

只有一个电容,我们怎么能够表示更多的比特呢?别忘了,这里我们还有一个电压计。4 个比特一共可以从 0000-1111 表示 16 个不同的数。那么,如果我们能往电容里面充电的时候,充上 15 个不同的电压,并且我们电压计能够区分出这 15 个不同的电压。加上电容被放空代表的 0,就能够代表从 0000-1111 这样 4 个比特了。

不过,要想表示 15 个不同的电压,充电和读取的时候,对于精度的要求就会更高。这会导致充电和读取的时候都更慢,所以 QLC 的 SSD 的读写速度,要比 SLC 的慢上好几倍。

SSD对碎片很敏感,类似JVM的内存碎片需要整理,碎片整理就带来了写入放大。也就是写入空间不够的时候需要先进行碎片整理、搬运,这样写入的数据更大了。

SSD寿命:以Intel 335为例再来算一下,BT用户可以用600TB × 1024 / 843 = 728天,普通用户可以用600TB/2 = 300年!情况十分乐观

两种逻辑门

NAND(NOT-AND) gate

NOR(NOT-OR) gate

如上两种门实现的介质都是非易失存储介质在写入前都需要擦除。实际上NOR Flash的一个bit可以从1变成0,而要从0变1就要擦除整块。NAND flash都需要擦除。

NAND Flash NOR Flash
芯片容量 <32GBit <1GBit
访问方式 块读写(顺序读写) 随机读写
接口方式 任意I/O口 特定完整存储器接口
读写性能 读取快(顺序读) 写入快 擦除快(可按块擦除) 读取快(RAM方式) 写入慢 檫除很慢
使用寿命 百万次 十万次
价格 低廉 高昂

NAND Flash更适合在各类需要大数据的设备中使用,如U盘、SSD、各种存储卡、MP3播放器等,而NOR Flash更适合用在高性能的工业产品中。

高端SSD会选取MLC(Multi-Level Cell)甚至SLC(Single-Level Cell),低端SSD则选取 TLC(Triple-Level Cell)。SD卡一般选取 TLC(Triple-Level Cell)

image-20210603161822079

image-20220105201724752

slc-mlc-tlc-buckets

umlc

image-20220105201749003

NOR FLash主要用于:Bios、机顶盒,大小一般是1-32MB

对于TLC NAND (每个NAND cell存储3 bits的信息),下面列出了每种操作的典型耗时的范围:

​ 读操作(Tread) : 50-100us,

​ 写操作(Tprog) : 500us-5ms,

​ 擦除操作(Terase) : 2-10ms。

为什么断电后SSD不丢数据

SSD的存储硬件都是NAND Flash。实现原理和通过改变电压,让电子进入绝缘层的浮栅(Floating Gate)内。断电之后,电子仍然在FG里面。但是如果长时间不通电,比如几年,仍然可能会丢数据。所以换句话说,SSD的确也不适合作为冷数据备份。比如标准要求SSD:温度在30度的情况下,数据要能保持52周。

写入放大(Write Amplification, or WA)

这是 SSD 相对于 HDD 的一个缺点,即实际写入 SSD 的物理数据量,有可能是应用层写入数据量的多倍。一方面,页级别的写入需要移动已有的数据来腾空页面。另一方面,GC 的操作也会移动用户数据来进行块级别的擦除。所以对 SSD 真正的写操作的数据可能比实际写的数据量大,这就是写入放大。一块 SSD 只能进行有限的擦除次数,也称为编程 / 擦除(P/E)周期,所以写入放大效用会缩短 SSD 的寿命。

SSD 的读取和写入的基本单位,不是一个比特(bit)或者一个字节(byte),而是一个页(Page)。SSD 的擦除单位就更夸张了,我们不仅不能按照比特或者字节来擦除,连按照页来擦除都不行,我们必须按照块来擦除。

SLC 的芯片,可以擦除的次数大概在 10 万次,MLC 就在 1 万次左右,而 TLC 和 QLC 就只在几千次了。这也是为什么,你去购买 SSD 硬盘,会看到同样的容量的价格差别很大,因为它们的芯片颗粒和寿命完全不一样。

从本质上讲,NAND Flash是一种不可靠介质,非常容易出现Bit翻转问题。SSD通过控制器和固件程序将这种不可靠的NAND Flash变成了可靠的数据存储介质。

为了在这种不可靠介质上构建可靠存储,SSD内部做了大量工作。在硬件层面,需要通过ECC单元解决经常出现的比特翻转问题。每次数据存储的时候,硬件单元需要为存储的数据计算ECC校验码;在数据读取的时候,硬件单元会根据校验码恢复被破坏的bit数据。ECC硬件单元集成在SSD控制器内部,代表了SSD控制器的能力。在MLC存储时代,BCH编解码技术可以解决问题,4KB数据中存在100bit翻转时可以纠正错误;在TLC存储时代,bit错误率大为提升,需要采用更高纠错能力的LDPC编解码技术,在4KB出现550bit翻转时,LDPC硬解码仍然可以恢复数据。对比LDPC硬解码、BCH以及LDPC软解码之间的能力,可以看出LDPC软解码具有更强的纠错能力,通常使用在硬解码失效的情况下。LDPC软解码的不足之处在于增加了IO的延迟。

在软件层面,SSD内部设计了FTL(Flash Translation Layer),该软件层的设计思想和Log-Structured File System设计思想类似。采用log追加写的方式记录数据,采用LBA至PBA的地址映射表记录数据组织方式。Log-structured系统最大的一个问题就是垃圾回收(GC)。因此,虽然NAND Flash本身具有很高的IO性能,但受限于GC的影响,SSD层面的性能会大受影响,并且存在十分严重的IO QoS问题,这也是目前标准NVMe SSD一个很重要的问题。

耗损平衡 (Wear Leveling)

对每一个块而言,一旦达到最大数量,该块就会死亡。对于 SLC 块,P/E 周期的典型数目是十万次;对于 MLC 块,P/E 周期的数目是一万;而对于 TLC 块,则可能是几千。为了确保 SSD 的容量和性能,我们需要在擦除次数上保持平衡,SSD 控制器具有这种“耗损平衡”机制可以实现这一目标。在损耗平衡期间,数据在各个块之间移动,以实现均衡的损耗,这种机制也会对前面讲的写入放大推波助澜。

non-volatile memory (NVM)

NVM是一种新型的硬件存储介质,同时具备磁盘和DRAM的一些特性。突出的NVM技术产品有:PC-RAM、STT-RAM和R-RAM。因为NVM具有设备层次上的持久性,所以不需要向DRAM一样的刷新周期以维持数据状态。因此NVM和DRAM相比,每bit耗费的能量更少。另外,NVM比硬盘有更小的延迟,读延迟甚至和DRAM相当;字节寻址;比DRAM密度更大。

1、NVM特性

数据访问延迟:NVM的读延迟比磁盘小很多。由于NVM仍处于开发阶段,来源不同延迟不同。STT-RAM的延迟1-20ns。尽管如此,他的延迟也已经非常接近DRAM了。

PC_RAM 和R-RAM的写延迟比DRAM高。但是写延迟不是很重要,因为可以通过buffer来缓解。

密度:NVM的密度比DRAM高,可以作为主存的替代品,尤其是在嵌入式系统中。例如,相对于DRAM,PC-RAM提供2到4倍的容量,便于扩展。

耐久性:即每个内存单元写的最大次数。最具竞争性的是PC-RAM和STT-RAM,提供接近DRAM的耐久性。更精确的说,NVM的耐久性是1015而DRAM是1016。另外,NVM比闪存技术的耐久性更大。

能量消耗:NVM不需要像DRAM一样周期性刷写以维护内存中数据,所以消耗的能量更少。PC-RAM比DRAM消耗能量显著的少,其他比较接近。

此外,还有字节寻址、持久性。Interl和Micron已经发起了3D XPoint技术,同时Interl开发了新的指令以支持持久内存的使用。

傲腾 PMem

Optane 的工作原理与 NAND Flash 有很大区别,NAND Flash 是基于 Floating-gate MOSFET 而 Optane 虽然因特尔没有官方资料说明但是根据国外机构用质谱法 测试的结果表明 Optane 是一种基于 PCM (相变化存储器) 原理的存储器,PCM 简单来说就是某种物质目前主要是一种或多种硫族化物的玻璃,经由加热可以改变它的状态,成为晶体或者非晶体,这些不同的状态具有相应的电阻值。

img

从以上数据来看 PCM 读接近 DRAM,比 NAND Flash 快了500倍,PCM 写比 DRAM 慢了20倍,但是仍然比 NAND Flash 快了500倍。DRAM 读写速度一致,PCM 和 NAND Flash 写都比读要慢20倍。

磁盘类型查看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$cat /sys/block/vda/queue/rotational
1 //1表示旋转,非ssd,0表示ssd
or
lsblk -d -o name,rota,size,label,uuid

SATA SSD测试数据
# cat /sys/block/sda/queue/rotational
0
# lsblk -d -o name,rota
NAME ROTA
sda 0
sfdv0n1 0

ESSD磁盘测试用一块虚拟的阿里云网络盘,不能算完整意义的SSD(承诺IOPS 4200),数据仅供参考,磁盘概况:
$df -lh
Filesystem Size Used Avail Use% Mounted on
/dev/vda1 99G 30G 65G 32% /

$cat /sys/block/vda/queue/rotational
1

fio 结果解读

slat,异步场景下才有

其中slat指的是发起IO的时间,在异步IO模式下,发起IO以后,IO会异步完成。例如调用一个异步的write,虽然write返回成功了,但是IO还未完成,slat约等于发起write的耗时;

slat (usec): min=4, max=6154, avg=48.82, stdev=56.38: The first latency metric you’ll see is the ‘slat’ or submission latency. It is pretty much what it sounds like, meaning “how long did it take to submit this IO to the kernel for processing?”

clat

clat指的是完成时间,从发起IO后到完成IO的时间,在同步IO模式下,clat是指整个写动作完成时间

lat

lat是总延迟时间,指的是IO单元创建到完成的总时间,通常这项数据关注较多。同步场景几乎等于clat,异步场景等于clat+slat
这项数据需要关注的是max,看看有没有极端的高延迟IO;另外还需要关注stdev,这项数据越大说明,IO响应时间波动越大,反之越小,波动越小

clat percentiles (usec):处于某个百分位的io操作时延

cpu : usr=9.11%, sys=57.07%, ctx=762410, majf=0, minf=1769 //用户和系统的CPU占用时间百分比,线程切换次数,major以及minor页面错误的数量。

SSD的direct和buffered似乎很奇怪,应该是direct=0性能更好,实际不是这样,这里还需要找资料求证下

  • direct``=bool

    If value is true, use non-buffered I/O. This is usually O_DIRECT. Note that OpenBSD and ZFS on Solaris don’t support direct I/O. On Windows the synchronous ioengines don’t support direct I/O. Default: false.

  • buffered``=bool

    If value is true, use buffered I/O. This is the opposite of the direct option. Defaults to true.

iostat 结果解读

Dm-0就是lvm

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
avg-cpu:  %user   %nice %system %iowait  %steal   %idle
0.32 0.00 3.34 0.13 0.00 96.21

Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await svctm %util
sda 0.00 11.40 66.00 7.20 1227.20 74.40 35.56 0.03 0.43 0.47 0.08 0.12 0.88
nvme0n1 0.00 8612.00 0.00 51749.60 0.00 241463.20 9.33 4.51 0.09 0.00 0.09 0.02 78.56
dm-0 0.00 0.00 0.00 60361.80 0.00 241463.20 8.00 152.52 2.53 0.00 2.53 0.01 78.26

avg-cpu: %user %nice %system %iowait %steal %idle
0.36 0.00 3.46 0.17 0.00 96.00

Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await svctm %util
sda 0.00 8.80 9.20 5.20 1047.20 67.20 154.78 0.01 0.36 0.46 0.19 0.33 0.48
nvme0n1 0.00 11354.20 0.00 50876.80 0.00 248944.00 9.79 5.25 0.10 0.00 0.10 0.02 80.06
dm-0 0.00 0.00 0.00 62231.00 0.00 248944.80 8.00 199.49 3.21 0.00 3.21 0.01 78.86

avgqu_sz,是iostat的一项比较重要的数据。如果队列过长,则表示有大量IO在处理或等待,但是这还不足以说明后端的存储系统达到了处理极限。例如后端存储的并发能力是4096,客户端并发发送了256个IO下去,那么队列长度就是256。即使长时间队列长度是256,也不能说明什么,仅仅表明队列长度是256,有256个IO在处理或者排队。

那么怎么判断IO是在调度队列排队等待,还是在设备上处理呢?iostat有两项数据可以给出一个大致的判断。svctime,这项数据的指的是IO在设备处理中耗费的时间。另外一项数据await,指的是IO从排队到完成的时间,包括了svctime和排队等待的时间。那么通过对比这两项数据,如果两项数据差不多,则说明IO基本没有排队等待,耗费的时间都是设备处理。如果await远大于svctime,则说明有大量的IO在排队,并没有发送给设备处理。

rq_affinity

参考aliyun测试文档 , rq_affinity增加2的commit: git show 5757a6d76c

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
function RunFio
{
numjobs=$1 # 实例中的测试线程数,例如示例中的10
iodepth=$2 # 同时发出I/O数的上限,例如示例中的64
bs=$3 # 单次I/O的块文件大小,例如示例中的4k
rw=$4 # 测试时的读写策略,例如示例中的randwrite
filename=$5 # 指定测试文件的名称,例如示例中的/dev/your_device
nr_cpus=`cat /proc/cpuinfo |grep "processor" |wc -l`
if [ $nr_cpus -lt $numjobs ];then
echo “Numjobs is more than cpu cores, exit!”
exit -1
fi
let nu=$numjobs+1
cpulist=""
for ((i=1;i<10;i++))
do
list=`cat /sys/block/your_device/mq/*/cpu_list | awk '{if(i<=NF) print $i;}' i="$i" | tr -d ',' | tr '\n' ','`
if [ -z $list ];then
break
fi
cpulist=${cpulist}${list}
done
spincpu=`echo $cpulist | cut -d ',' -f 2-${nu}`
echo $spincpu
fio --ioengine=libaio --runtime=30s --numjobs=${numjobs} --iodepth=${iodepth} --bs=${bs} --rw=${rw} --filename=${filename} --time_based=1 --direct=1 --name=test --group_reporting --cpus_allowed=$spincpu --cpus_allowed_policy=split
}
echo 2 > /sys/block/your_device/queue/rq_affinity
sleep 5
RunFio 10 64 4k randwrite filename

对NVME SSD进行测试,左边rq_affinity是2,右边rq_affinity为1,在这个测试参数下rq_affinity为1的性能要好(后许多次测试两者性能差不多)

image-20210607113709945

参考资料

http://cizixs.com/2017/01/03/how-slow-is-disk-and-network

https://tobert.github.io/post/2014-04-17-fio-output-explained.html

https://zhuanlan.zhihu.com/p/40497397

块存储NVMe云盘原型实践

机械硬盘随机IO慢的超乎你的想象

搭载固态硬盘的服务器究竟比搭机械硬盘快多少?

SSD基本工作原理

SSD原理解读

Backblaze 的 2021 年硬盘死亡報告

ssd/san/sas 磁盘 光纤性能比较

正好有机会用到一个san存储设备,跑了一把性能数据,记录一下

image.png

所使用的测试命令:

1
fio -ioengine=libaio -bs=4k -direct=1 -thread -rw=randwrite -size=1000G -filename=/data/fio.test -name="EBS 4K randwrite test" -iodepth=64 -runtime=60

ssd(Solid State Drive)和san的比较是在同一台物理机上,所以排除了其他因素的干扰。

简要的结论:

  • 本地ssd性能最好、sas机械盘(RAID10)性能最差

  • san存储走特定的光纤网络,不是走tcp的san(至少从网卡看不到san的流量),性能居中

  • 从rt来看 ssd:san:sas 大概是 1:3:15

  • san比本地sas机械盘性能要好,这也许取决于san的网络传输性能和san存储中的设备(比如用的ssd而不是机械盘)

NVMe SSD 和 HDD的性能比较

image.png

表中性能差异比上面测试还要大,SSD 的随机 IO 延迟比传统硬盘快百倍以上,一般在微妙级别;IO 带宽也高很多倍,可以达到每秒几个 GB;随机 IOPS 更是快了上千倍,可以达到几十万。

HDD只有一个磁头,并发没有意义,但是SSD支持高并发写入读取。SSD没有磁头、不需要旋转,所以随机读取和顺序读取基本没有差别。

SSD 的性能特性和机制

SSD 的内部工作方式和 HDD 大相径庭,我们先了解几个概念。

单元(Cell)、页面(Page)、块(Block)。当今的主流 SSD 是基于 NAND 的,它将数字位存储在单元中。每个 SSD 单元可以存储一位或多位。对单元的每次擦除都会降低单元的寿命,所以单元只能承受一定数量的擦除。单元存储的位数越多,制造成本就越少,SSD 的容量也就越大,但是耐久性(擦除次数)也会降低。

一个页面包括很多单元,典型的页面大小是 4KB,页面也是要读写的最小存储单元。SSD 上没有“重写”操作,不像 HDD 可以直接对任何字节重写覆盖。一个页面一旦写入内容后就不能进行部分重写,必须和其它相邻页面一起被整体擦除重置。

多个页面组合成块。一个块的典型大小为 512KB 或 1MB,也就是大约 128 或 256 页。块是擦除的基本单位,每次擦除都是整个块内的所有页面都被重置。

擦除速度相对很慢,通常为几毫秒。所以对同步的 IO,发出 IO 的应用程序可能会因为块的擦除,而经历很大的写入延迟。为了尽量地减少这样的场景,保持空闲块的阈值对于快速的写响应是很有必要的。SSD 的垃圾回收(GC)的目的就在于此。GC 可以回收用过的块,这样可以确保以后的页写入可以快速分配到一个全新的页。

SSD原理

对于 SSD 硬盘,类似SRAM(CPU cache)它是由一个电容加上一个电压计组合在一起,记录了一个或者多个比特。能够记录一个比特很容易理解。给电容里面充上电有电压的时候就是 1,给电容放电里面没有电就是 0。采用这样方式存储数据的 SSD 硬盘,我们一般称之为使用了 SLC 的颗粒,全称是 Single-Level Cell,也就是一个存储单元中只有一位数据。

但是,这样的方式会遇到和 CPU Cache 类似的问题,那就是,同样的面积下,能够存放下的元器件是有限的。如果只用 SLC,我们就会遇到,存储容量上不去,并且价格下不来的问题。于是呢,硬件工程师们就陆续发明了 MLC(Multi-Level Cell)、TLC(Triple-Level Cell)以及 QLC(Quad-Level Cell),也就是能在一个电容里面存下 2 个、3 个乃至 4 个比特。

只有一个电容,我们怎么能够表示更多的比特呢?别忘了,这里我们还有一个电压计。4 个比特一共可以从 0000-1111 表示 16 个不同的数。那么,如果我们能往电容里面充电的时候,充上 15 个不同的电压,并且我们电压计能够区分出这 15 个不同的电压。加上电容被放空代表的 0,就能够代表从 0000-1111 这样 4 个比特了。

不过,要想表示 15 个不同的电压,充电和读取的时候,对于精度的要求就会更高。这会导致充电和读取的时候都更慢,所以 QLC 的 SSD 的读写速度,要比 SLC 的慢上好几倍。

SSD对碎片很敏感,类似JVM的内存碎片需要整理,碎片整理就带来了写入放大。也就是写入空间不够的时候需要先进行碎片整理、搬运,这样写入的数据更大了。

SSD寿命:以Intel 335为例再来算一下,BT用户可以用600TB × 1024 / 843 = 728天,普通用户可以用600TB/2 = 300年!情况十分乐观

两种逻辑门

NAND(NOT-AND) gate

NOR(NOT-OR) gate

如上两种门实现的介质都是非易失存储介质在写入前都需要擦除。实际上NOR Flash的一个bit可以从1变成0,而要从0变1就要擦除整块。NAND flash都需要擦除。

NAND Flash NOR Flash
芯片容量 <32GBit <1GBit
访问方式 块读写(顺序读写) 随机读写
接口方式 任意I/O口 特定完整存储器接口
读写性能 读取快(顺序读) 写入快 擦除快(可按块擦除) 读取快(RAM方式) 写入慢 檫除很慢
使用寿命 百万次 十万次
价格 低廉 高昂

NAND Flash更适合在各类需要大数据的设备中使用,如U盘、SSD、各种存储卡、MP3播放器等,而NOR Flash更适合用在高性能的工业产品中。

高端SSD会选取MLC(Multi-Level Cell)甚至SLC(Single-Level Cell),低端SSD则选取 TLC(Triple-Level Cell)。SD卡一般选取 TLC(Triple-Level Cell)

image-20210603161822079

slc-mlc-tlc-buckets

NOR FLash主要用于:Bios、机顶盒,大小一般是1-32MB

为什么断电后SSD不丢数据

SSD的存储硬件都是NAND Flash。实现原理和通过改变电压,让电子进入绝缘层的浮栅(Floating Gate)内。断电之后,电子仍然在FG里面。但是如果长时间不通电,比如几年,仍然可能会丢数据。所以换句话说,SSD的确也不适合作为冷数据备份。

比如标准要求SSD:温度在30度的情况下,数据要能保持52周。

写入放大(Write Amplification, or WA)

这是 SSD 相对于 HDD 的一个缺点,即实际写入 SSD 的物理数据量,有可能是应用层写入数据量的多倍。一方面,页级别的写入需要移动已有的数据来腾空页面。另一方面,GC 的操作也会移动用户数据来进行块级别的擦除。所以对 SSD 真正的写操作的数据可能比实际写的数据量大,这就是写入放大。一块 SSD 只能进行有限的擦除次数,也称为编程 / 擦除(P/E)周期,所以写入放大效用会缩短 SSD 的寿命。

SSD 的读取和写入的基本单位,不是一个比特(bit)或者一个字节(byte),而是一个页(Page)。SSD 的擦除单位就更夸张了,我们不仅不能按照比特或者字节来擦除,连按照页来擦除都不行,我们必须按照块来擦除。

SLC 的芯片,可以擦除的次数大概在 10 万次,MLC 就在 1 万次左右,而 TLC 和 QLC 就只在几千次了。这也是为什么,你去购买 SSD 硬盘,会看到同样的容量的价格差别很大,因为它们的芯片颗粒和寿命完全不一样。

耗损平衡 (Wear Leveling)

对每一个块而言,一旦达到最大数量,该块就会死亡。对于 SLC 块,P/E 周期的典型数目是十万次;对于 MLC 块,P/E 周期的数目是一万;而对于 TLC 块,则可能是几千。为了确保 SSD 的容量和性能,我们需要在擦除次数上保持平衡,SSD 控制器具有这种“耗损平衡”机制可以实现这一目标。在损耗平衡期间,数据在各个块之间移动,以实现均衡的损耗,这种机制也会对前面讲的写入放大推波助澜。

磁盘类型查看

1
2
3
4
5
$cat /sys/block/vda/queue/rotational
1 //1表示旋转,非ssd,0表示ssd

或者
lsblk -d -o name,rota

fio测试

以下是两块测试的SSD磁盘测试前的基本情况

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
/dev/sda	240.06G  SSD_SATA  //sata
/dev/sfd0n1 3200G SSD_PCIE //PCIE

Filesystem Size Used Avail Use% Mounted on
/dev/sda3 49G 29G 18G 63% /
/dev/sfdv0n1p1 2.0T 803G 1.3T 40% /data

# cat /sys/block/sda/queue/rotational
0
# cat /sys/block/sfdv0n1/queue/rotational
0

#测试前的iostat状态
# iostat -d sfdv0n1 sda3 1 -x
Linux 3.10.0-957.el7.x86_64 (nu4d01142.sqa.nu8) 2021年02月23日 _x86_64_ (104 CPU)

Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await svctm %util
sda3 0.00 10.67 1.24 18.78 7.82 220.69 22.83 0.03 1.64 1.39 1.66 0.08 0.17
sfdv0n1 0.00 0.21 9.91 841.42 128.15 8237.10 19.65 0.93 0.04 0.25 0.04 1.05 89.52

Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await svctm %util
sda3 0.00 15.00 0.00 17.00 0.00 136.00 16.00 0.03 2.00 0.00 2.00 1.29 2.20
sfdv0n1 0.00 0.00 0.00 11158.00 0.00 54448.00 9.76 1.03 0.02 0.00 0.02 0.09 100.00

Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await svctm %util
sda3 0.00 5.00 0.00 18.00 0.00 104.00 11.56 0.01 0.61 0.00 0.61 0.61 1.10
sfdv0n1 0.00 0.00 0.00 10970.00 0.00 53216.00 9.70 1.02 0.03 0.00 0.03 0.09 100.10

Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await svctm %util
sda3 0.00 0.00 0.00 24.00 0.00 100.00 8.33 0.01 0.58 0.00 0.58 0.08 0.20
sfdv0n1 0.00 0.00 0.00 11206.00 0.00 54476.00 9.72 1.03 0.03 0.00 0.03 0.09 99.90

Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await svctm %util
sda3 0.00 14.00 0.00 21.00 0.00 148.00 14.10 0.01 0.48 0.00 0.48 0.33 0.70
sfdv0n1 0.00 0.00 0.00 10071.00 0.00 49028.00 9.74 1.02 0.03 0.00 0.03 0.10 99.80

NVMe SSD测试数据

对一块ssd进行如下测试(挂载在/data 目录)

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
fio -ioengine=libaio -bs=4k -direct=1 -thread -rw=randwrite -rwmixread=70 -size=16G -filename=/data/fio.test -name="EBS 4K randwrite test" -iodepth=64 -runtime=60
EBS 4K randwrite test: (g=0): rw=randwrite, bs=(R) 4096B-4096B, (W) 4096B-4096B, (T) 4096B-4096B, ioengine=libaio, iodepth=64
fio-3.7
Starting 1 thread
EBS 4K randwrite test: Laying out IO file (1 file / 16384MiB)
Jobs: 1 (f=1): [w(1)][100.0%][r=0KiB/s,w=63.8MiB/s][r=0,w=16.3k IOPS][eta 00m:00s]
EBS 4K randwrite test: (groupid=0, jobs=1): err= 0: pid=258871: Tue Feb 23 14:12:23 2021
write: IOPS=18.9k, BW=74.0MiB/s (77.6MB/s)(4441MiB/60001msec)
slat (usec): min=4, max=6154, avg=48.82, stdev=56.38
clat (nsec): min=1049, max=12360k, avg=3326362.62, stdev=920683.43
lat (usec): min=68, max=12414, avg=3375.52, stdev=928.97
clat percentiles (usec):
| 1.00th=[ 1483], 5.00th=[ 1811], 10.00th=[ 2114], 20.00th=[ 2376],
| 30.00th=[ 2704], 40.00th=[ 3130], 50.00th=[ 3523], 60.00th=[ 3785],
| 70.00th=[ 3949], 80.00th=[ 4080], 90.00th=[ 4293], 95.00th=[ 4490],
| 99.00th=[ 5604], 99.50th=[ 5997], 99.90th=[ 7111], 99.95th=[ 7832],
| 99.99th=[ 9634]
bw ( KiB/s): min=61024, max=118256, per=99.98%, avg=75779.58, stdev=12747.95, samples=120
iops : min=15256, max=29564, avg=18944.88, stdev=3186.97, samples=120
lat (usec) : 2=0.01%, 100=0.01%, 250=0.01%, 500=0.01%, 750=0.02%
lat (usec) : 1000=0.06%
lat (msec) : 2=7.40%, 4=66.19%, 10=26.32%, 20=0.01%
cpu : usr=5.23%, sys=46.71%, ctx=846953, majf=0, minf=6
IO depths : 1=0.1%, 2=0.1%, 4=0.1%, 8=0.1%, 16=0.1%, 32=0.1%, >=64=100.0%
submit : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.0%
complete : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.1%, >=64=0.0%
issued rwts: total=0,1136905,0,0 short=0,0,0,0 dropped=0,0,0,0
latency : target=0, window=0, percentile=100.00%, depth=64

Run status group 0 (all jobs):
WRITE: bw=74.0MiB/s (77.6MB/s), 74.0MiB/s-74.0MiB/s (77.6MB/s-77.6MB/s), io=4441MiB (4657MB), run=60001-60001msec

Disk stats (read/write):
sfdv0n1: ios=0/1821771, merge=0/7335, ticks=0/39708, in_queue=78295, util=100.00%

slat (usec): min=4, max=6154, avg=48.82, stdev=56.38: The first latency metric you’ll see is the ‘slat’ or submission latency. It is pretty much what it sounds like, meaning “how long did it take to submit this IO to the kernel for processing?”

如上测试iops为:18944,测试期间的iostat,测试中一直有mysql在导入数据,所以测试开始前util就已经100%了,并且w/s到了13K左右

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
# iostat -d sfdv0n1 3 -x
Linux 3.10.0-957.el7.x86_64 (nu4d01142.sqa.nu8) 2021年02月23日 _x86_64_ (104 CPU)

Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await svctm %util
sfdv0n1 0.00 0.18 3.45 769.17 102.83 7885.16 20.68 0.93 0.04 0.26 0.04 1.16 89.46

Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await svctm %util
sfdv0n1 0.00 0.00 0.00 13168.67 0.00 66244.00 10.06 1.05 0.03 0.00 0.03 0.08 100.10

Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await svctm %util
sfdv0n1 0.00 0.00 0.00 12822.67 0.00 65542.67 10.22 1.04 0.02 0.00 0.02 0.08 100.07

//增加压力
Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await svctm %util
sfdv0n1 0.00 0.00 0.00 27348.33 0.00 214928.00 15.72 1.27 0.02 0.00 0.02 0.04 100.17

Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await svctm %util
sfdv0n1 0.00 1.00 0.00 32661.67 0.00 271660.00 16.63 1.32 0.02 0.00 0.02 0.03 100.37

Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await svctm %util
sfdv0n1 0.00 0.00 0.00 31645.00 0.00 265988.00 16.81 1.33 0.02 0.00 0.02 0.03 100.37

Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await svctm %util
sfdv0n1 0.00 574.00 0.00 31961.67 0.00 271094.67 16.96 1.36 0.02 0.00 0.02 0.03 100.13

Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await svctm %util
sfdv0n1 0.00 0.00 0.00 27656.33 0.00 224586.67 16.24 1.28 0.02 0.00 0.02 0.04 100.37

从iostat看出,测试开始前util已经100%(因为ssd,util失去参考意义),w/s 13K左右,压力跑起来后w/s能到30K,svctm、await均保持稳定

SSD的direct和buffered似乎很奇怪,应该是direct=0性能更好,实际不是这样,这里还需要找资料求证下

  • direct``=bool

    If value is true, use non-buffered I/O. This is usually O_DIRECT. Note that OpenBSD and ZFS on Solaris don’t support direct I/O. On Windows the synchronous ioengines don’t support direct I/O. Default: false.

  • buffered``=bool

    If value is true, use buffered I/O. This is the opposite of the direct option. Defaults to true.

如下测试中direct=1和direct=0的write avg iops分别为42K、16K

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# fio -ioengine=libaio -bs=4k -direct=1 -buffered=0 -thread -rw=randrw -rwmixread=70 -size=16G -filename=/data/fio.test -name="EBS 4K randwrite test" -iodepth=64 -runtime=60 
EBS 4K randwrite test: (g=0): rw=randrw, bs=(R) 4096B-4096B, (W) 4096B-4096B, (T) 4096B-4096B, ioengine=libaio, iodepth=64
fio-3.7
Starting 1 thread
Jobs: 1 (f=1): [m(1)][100.0%][r=507MiB/s,w=216MiB/s][r=130k,w=55.2k IOPS][eta 00m:00s]
EBS 4K randwrite test: (groupid=0, jobs=1): err= 0: pid=415921: Tue Feb 23 14:34:33 2021
read: IOPS=99.8k, BW=390MiB/s (409MB/s)(11.2GiB/29432msec)
slat (nsec): min=1043, max=917837, avg=4273.86, stdev=3792.17
clat (usec): min=2, max=4313, avg=459.80, stdev=239.61
lat (usec): min=4, max=4328, avg=464.16, stdev=241.81
clat percentiles (usec):
| 1.00th=[ 251], 5.00th=[ 277], 10.00th=[ 289], 20.00th=[ 310],
| 30.00th=[ 326], 40.00th=[ 343], 50.00th=[ 363], 60.00th=[ 400],
| 70.00th=[ 502], 80.00th=[ 603], 90.00th=[ 750], 95.00th=[ 881],
| 99.00th=[ 1172], 99.50th=[ 1401], 99.90th=[ 3032], 99.95th=[ 3359],
| 99.99th=[ 3785]
bw ( KiB/s): min=182520, max=574856, per=99.24%, avg=395975.64, stdev=119541.78, samples=58
iops : min=45630, max=143714, avg=98993.90, stdev=29885.42, samples=58
write: IOPS=42.8k, BW=167MiB/s (175MB/s)(4915MiB/29432msec)
slat (usec): min=3, max=263, avg= 9.34, stdev= 4.35
clat (usec): min=14, max=2057, avg=402.26, stdev=140.67
lat (usec): min=19, max=2070, avg=411.72, stdev=142.67
clat percentiles (usec):
| 1.00th=[ 237], 5.00th=[ 281], 10.00th=[ 293], 20.00th=[ 314],
| 30.00th=[ 330], 40.00th=[ 343], 50.00th=[ 359], 60.00th=[ 379],
| 70.00th=[ 404], 80.00th=[ 457], 90.00th=[ 586], 95.00th=[ 717],
| 99.00th=[ 930], 99.50th=[ 1004], 99.90th=[ 1254], 99.95th=[ 1385],
| 99.99th=[ 1532]
bw ( KiB/s): min=78104, max=244408, per=99.22%, avg=169671.52, stdev=51142.10, samples=58
iops : min=19526, max=61102, avg=42417.86, stdev=12785.51, samples=58
lat (usec) : 4=0.01%, 10=0.01%, 20=0.01%, 50=0.02%, 100=0.04%
lat (usec) : 250=1.02%, 500=73.32%, 750=17.28%, 1000=6.30%
lat (msec) : 2=1.83%, 4=0.19%, 10=0.01%
cpu : usr=15.84%, sys=83.31%, ctx=13765, majf=0, minf=7
IO depths : 1=0.1%, 2=0.1%, 4=0.1%, 8=0.1%, 16=0.1%, 32=0.1%, >=64=100.0%
submit : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.0%
complete : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.1%, >=64=0.0%
issued rwts: total=2936000,1258304,0,0 short=0,0,0,0 dropped=0,0,0,0
latency : target=0, window=0, percentile=100.00%, depth=64

Run status group 0 (all jobs):
READ: bw=390MiB/s (409MB/s), 390MiB/s-390MiB/s (409MB/s-409MB/s), io=11.2GiB (12.0GB), run=29432-29432msec
WRITE: bw=167MiB/s (175MB/s), 167MiB/s-167MiB/s (175MB/s-175MB/s), io=4915MiB (5154MB), run=29432-29432msec

Disk stats (read/write):
sfdv0n1: ios=795793/1618341, merge=0/11, ticks=218710/27721, in_queue=264935, util=100.00%
[root@nu4d01142 data]#
[root@nu4d01142 data]# fio -ioengine=libaio -bs=4k -direct=0 -buffered=0 -thread -rw=randrw -rwmixread=70 -size=6G -filename=/data/fio.test -name="EBS 4K randwrite test" -iodepth=64 -runtime=60
EBS 4K randwrite test: (g=0): rw=randrw, bs=(R) 4096B-4096B, (W) 4096B-4096B, (T) 4096B-4096B, ioengine=libaio, iodepth=64
fio-3.7
Starting 1 thread
Jobs: 1 (f=1): [m(1)][100.0%][r=124MiB/s,w=53.5MiB/s][r=31.7k,w=13.7k IOPS][eta 00m:00s]
EBS 4K randwrite test: (groupid=0, jobs=1): err= 0: pid=437523: Tue Feb 23 14:37:54 2021
read: IOPS=38.6k, BW=151MiB/s (158MB/s)(4300MiB/28550msec)
slat (nsec): min=1205, max=1826.7k, avg=13253.36, stdev=17173.87
clat (nsec): min=236, max=5816.8k, avg=1135969.25, stdev=337142.34
lat (nsec): min=1977, max=5831.2k, avg=1149404.84, stdev=341232.87
clat percentiles (usec):
| 1.00th=[ 461], 5.00th=[ 627], 10.00th=[ 717], 20.00th=[ 840],
| 30.00th=[ 938], 40.00th=[ 1029], 50.00th=[ 1123], 60.00th=[ 1221],
| 70.00th=[ 1319], 80.00th=[ 1434], 90.00th=[ 1565], 95.00th=[ 1680],
| 99.00th=[ 1893], 99.50th=[ 1975], 99.90th=[ 2671], 99.95th=[ 3261],
| 99.99th=[ 3851]
bw ( KiB/s): min=119304, max=216648, per=100.00%, avg=154273.07, stdev=29925.10, samples=57
iops : min=29826, max=54162, avg=38568.25, stdev=7481.30, samples=57
write: IOPS=16.5k, BW=64.6MiB/s (67.7MB/s)(1844MiB/28550msec)
slat (usec): min=3, max=3565, avg=21.07, stdev=22.23
clat (usec): min=14, max=9983, avg=1164.21, stdev=459.66
lat (usec): min=21, max=10011, avg=1185.57, stdev=463.28
clat percentiles (usec):
| 1.00th=[ 498], 5.00th=[ 619], 10.00th=[ 709], 20.00th=[ 832],
| 30.00th=[ 930], 40.00th=[ 1020], 50.00th=[ 1123], 60.00th=[ 1237],
| 70.00th=[ 1336], 80.00th=[ 1450], 90.00th=[ 1598], 95.00th=[ 1713],
| 99.00th=[ 2311], 99.50th=[ 3851], 99.90th=[ 5932], 99.95th=[ 6456],
| 99.99th=[ 7701]
bw ( KiB/s): min=50800, max=92328, per=100.00%, avg=66128.47, stdev=12890.64, samples=57
iops : min=12700, max=23082, avg=16532.07, stdev=3222.66, samples=57
lat (nsec) : 250=0.01%, 500=0.01%, 750=0.01%, 1000=0.01%
lat (usec) : 2=0.01%, 4=0.01%, 10=0.01%, 20=0.02%, 50=0.03%
lat (usec) : 100=0.04%, 250=0.18%, 500=1.01%, 750=11.05%, 1000=25.02%
lat (msec) : 2=61.87%, 4=0.62%, 10=0.14%
cpu : usr=10.87%, sys=61.98%, ctx=218415, majf=0, minf=7
IO depths : 1=0.1%, 2=0.1%, 4=0.1%, 8=0.1%, 16=0.1%, 32=0.1%, >=64=100.0%
submit : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.0%
complete : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.1%, >=64=0.0%
issued rwts: total=1100924,471940,0,0 short=0,0,0,0 dropped=0,0,0,0
latency : target=0, window=0, percentile=100.00%, depth=64

Run status group 0 (all jobs):
READ: bw=151MiB/s (158MB/s), 151MiB/s-151MiB/s (158MB/s-158MB/s), io=4300MiB (4509MB), run=28550-28550msec
WRITE: bw=64.6MiB/s (67.7MB/s), 64.6MiB/s-64.6MiB/s (67.7MB/s-67.7MB/s), io=1844MiB (1933MB), run=28550-28550msec

Disk stats (read/write):
sfdv0n1: ios=536103/822037, merge=0/1442, ticks=66507/17141, in_queue=99429, util=100.00%

SATA SSD测试数据

1
2
3
4
5
6
# cat /sys/block/sda/queue/rotational 
0
# lsblk -d -o name,rota
NAME ROTA
sda 0
sfdv0n1 0

-direct=0 -buffered=0读写iops分别为15.8K、6.8K 比ssd差了不少(都是direct=0),如果direct、buffered都是1的话,ESSD性能很差,读写iops分别为4312、1852

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# fio -ioengine=libaio -bs=4k -direct=0 -buffered=0 -thread -rw=randrw -rwmixread=70 -size=2G -filename=/var/lib/docker/fio.test -name="EBS 4K randwrite test" -iodepth=64 -runtime=60 
EBS 4K randwrite test: (g=0): rw=randrw, bs=(R) 4096B-4096B, (W) 4096B-4096B, (T) 4096B-4096B, ioengine=libaio, iodepth=64
fio-3.7
Starting 1 thread
EBS 4K randwrite test: Laying out IO file (1 file / 2048MiB)
Jobs: 1 (f=1): [m(1)][100.0%][r=68.7MiB/s,w=29.7MiB/s][r=17.6k,w=7594 IOPS][eta 00m:00s]
EBS 4K randwrite test: (groupid=0, jobs=1): err= 0: pid=13261: Tue Feb 23 14:42:41 2021
read: IOPS=15.8k, BW=61.8MiB/s (64.8MB/s)(1432MiB/23172msec)
slat (nsec): min=1266, max=7261.0k, avg=7101.88, stdev=20655.54
clat (usec): min=167, max=27670, avg=2832.68, stdev=1786.18
lat (usec): min=175, max=27674, avg=2839.93, stdev=1784.42
clat percentiles (usec):
| 1.00th=[ 437], 5.00th=[ 668], 10.00th=[ 873], 20.00th=[ 988],
| 30.00th=[ 1401], 40.00th=[ 2442], 50.00th=[ 2835], 60.00th=[ 3195],
| 70.00th=[ 3523], 80.00th=[ 4047], 90.00th=[ 5014], 95.00th=[ 5866],
| 99.00th=[ 8160], 99.50th=[ 9372], 99.90th=[13829], 99.95th=[15008],
| 99.99th=[23725]
bw ( KiB/s): min=44183, max=149440, per=99.28%, avg=62836.17, stdev=26590.84, samples=46
iops : min=11045, max=37360, avg=15709.02, stdev=6647.72, samples=46
write: IOPS=6803, BW=26.6MiB/s (27.9MB/s)(616MiB/23172msec)
slat (nsec): min=1566, max=11474k, avg=8460.17, stdev=38221.51
clat (usec): min=77, max=24047, avg=2789.68, stdev=2042.55
lat (usec): min=80, max=24054, avg=2798.29, stdev=2040.85
clat percentiles (usec):
| 1.00th=[ 265], 5.00th=[ 433], 10.00th=[ 635], 20.00th=[ 840],
| 30.00th=[ 979], 40.00th=[ 2212], 50.00th=[ 2671], 60.00th=[ 3130],
| 70.00th=[ 3523], 80.00th=[ 4228], 90.00th=[ 5342], 95.00th=[ 6456],
| 99.00th=[ 9241], 99.50th=[10421], 99.90th=[13960], 99.95th=[15533],
| 99.99th=[23725]
bw ( KiB/s): min=18435, max=63112, per=99.26%, avg=27012.57, stdev=11299.42, samples=46
iops : min= 4608, max=15778, avg=6753.11, stdev=2824.87, samples=46
lat (usec) : 100=0.01%, 250=0.23%, 500=3.14%, 750=5.46%, 1000=15.27%
lat (msec) : 2=11.47%, 4=43.09%, 10=20.88%, 20=0.44%, 50=0.01%
cpu : usr=3.53%, sys=18.08%, ctx=47448, majf=0, minf=6
IO depths : 1=0.1%, 2=0.1%, 4=0.1%, 8=0.1%, 16=0.1%, 32=0.1%, >=64=100.0%
submit : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.0%
complete : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.1%, >=64=0.0%
issued rwts: total=366638,157650,0,0 short=0,0,0,0 dropped=0,0,0,0
latency : target=0, window=0, percentile=100.00%, depth=64

Run status group 0 (all jobs):
READ: bw=61.8MiB/s (64.8MB/s), 61.8MiB/s-61.8MiB/s (64.8MB/s-64.8MB/s), io=1432MiB (1502MB), run=23172-23172msec
WRITE: bw=26.6MiB/s (27.9MB/s), 26.6MiB/s-26.6MiB/s (27.9MB/s-27.9MB/s), io=616MiB (646MB), run=23172-23172msec

Disk stats (read/write):
sda: ios=359202/155123, merge=299/377, ticks=946305/407820, in_queue=1354596, util=99.61%

# fio -ioengine=libaio -bs=4k -direct=1 -buffered=0 -thread -rw=randrw -rwmixread=70 -size=2G -filename=/var/lib/docker/fio.test -name="EBS 4K randwrite test" -iodepth=64 -runtime=60
EBS 4K randwrite test: (g=0): rw=randrw, bs=(R) 4096B-4096B, (W) 4096B-4096B, (T) 4096B-4096B, ioengine=libaio, iodepth=64
fio-3.7
Starting 1 thread
Jobs: 1 (f=1): [m(1)][95.5%][r=57.8MiB/s,w=25.7MiB/s][r=14.8k,w=6568 IOPS][eta 00m:01s]
EBS 4K randwrite test: (groupid=0, jobs=1): err= 0: pid=26167: Tue Feb 23 14:44:40 2021
read: IOPS=16.9k, BW=65.9MiB/s (69.1MB/s)(1432MiB/21730msec)
slat (nsec): min=1312, max=4454.2k, avg=8489.99, stdev=15763.97
clat (usec): min=201, max=18856, avg=2679.38, stdev=1720.02
lat (usec): min=206, max=18860, avg=2688.03, stdev=1717.19
clat percentiles (usec):
| 1.00th=[ 635], 5.00th=[ 832], 10.00th=[ 914], 20.00th=[ 971],
| 30.00th=[ 1090], 40.00th=[ 2114], 50.00th=[ 2704], 60.00th=[ 3064],
| 70.00th=[ 3392], 80.00th=[ 3851], 90.00th=[ 4817], 95.00th=[ 5735],
| 99.00th=[ 7767], 99.50th=[ 8979], 99.90th=[13698], 99.95th=[15139],
| 99.99th=[16581]
bw ( KiB/s): min=45168, max=127528, per=100.00%, avg=67625.19, stdev=26620.82, samples=43
iops : min=11292, max=31882, avg=16906.28, stdev=6655.20, samples=43
write: IOPS=7254, BW=28.3MiB/s (29.7MB/s)(616MiB/21730msec)
slat (nsec): min=1749, max=3412.2k, avg=9816.22, stdev=14501.05
clat (usec): min=97, max=23473, avg=2556.02, stdev=1980.53
lat (usec): min=107, max=23477, avg=2566.01, stdev=1977.65
clat percentiles (usec):
| 1.00th=[ 277], 5.00th=[ 486], 10.00th=[ 693], 20.00th=[ 824],
| 30.00th=[ 881], 40.00th=[ 1205], 50.00th=[ 2442], 60.00th=[ 2868],
| 70.00th=[ 3326], 80.00th=[ 3949], 90.00th=[ 5080], 95.00th=[ 6128],
| 99.00th=[ 8717], 99.50th=[10159], 99.90th=[14484], 99.95th=[15926],
| 99.99th=[18744]
bw ( KiB/s): min=19360, max=55040, per=100.00%, avg=29064.05, stdev=11373.59, samples=43
iops : min= 4840, max=13760, avg=7266.00, stdev=2843.41, samples=43
lat (usec) : 100=0.01%, 250=0.17%, 500=1.66%, 750=3.74%, 1000=22.57%
lat (msec) : 2=12.66%, 4=40.62%, 10=18.20%, 20=0.38%, 50=0.01%
cpu : usr=4.17%, sys=22.27%, ctx=14314, majf=0, minf=7
IO depths : 1=0.1%, 2=0.1%, 4=0.1%, 8=0.1%, 16=0.1%, 32=0.1%, >=64=100.0%
submit : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.0%
complete : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.1%, >=64=0.0%
issued rwts: total=366638,157650,0,0 short=0,0,0,0 dropped=0,0,0,0
latency : target=0, window=0, percentile=100.00%, depth=64

Run status group 0 (all jobs):
READ: bw=65.9MiB/s (69.1MB/s), 65.9MiB/s-65.9MiB/s (69.1MB/s-69.1MB/s), io=1432MiB (1502MB), run=21730-21730msec
WRITE: bw=28.3MiB/s (29.7MB/s), 28.3MiB/s-28.3MiB/s (29.7MB/s-29.7MB/s), io=616MiB (646MB), run=21730-21730msec

Disk stats (read/write):
sda: ios=364744/157621, merge=779/473, ticks=851759/352008, in_queue=1204024, util=99.61%

# fio -ioengine=libaio -bs=4k -direct=1 -buffered=1 -thread -rw=randrw -rwmixread=70 -size=2G -filename=/var/lib/docker/fio.test -name="EBS 4K randwrite test" -iodepth=64 -runtime=60
EBS 4K randwrite test: (g=0): rw=randrw, bs=(R) 4096B-4096B, (W) 4096B-4096B, (T) 4096B-4096B, ioengine=libaio, iodepth=64
fio-3.7
Starting 1 thread
Jobs: 1 (f=1): [m(1)][100.0%][r=15.9MiB/s,w=7308KiB/s][r=4081,w=1827 IOPS][eta 00m:00s]
EBS 4K randwrite test: (groupid=0, jobs=1): err= 0: pid=31560: Tue Feb 23 14:46:10 2021
read: IOPS=4312, BW=16.8MiB/s (17.7MB/s)(1011MiB/60001msec)
slat (usec): min=63, max=14320, avg=216.76, stdev=430.61
clat (usec): min=5, max=778861, avg=10254.92, stdev=22345.40
lat (usec): min=1900, max=782277, avg=10472.16, stdev=22657.06
clat percentiles (msec):
| 1.00th=[ 6], 5.00th=[ 6], 10.00th=[ 6], 20.00th=[ 7],
| 30.00th=[ 7], 40.00th=[ 7], 50.00th=[ 7], 60.00th=[ 7],
| 70.00th=[ 8], 80.00th=[ 8], 90.00th=[ 8], 95.00th=[ 11],
| 99.00th=[ 107], 99.50th=[ 113], 99.90th=[ 132], 99.95th=[ 197],
| 99.99th=[ 760]
bw ( KiB/s): min= 168, max=29784, per=100.00%, avg=17390.92, stdev=10932.90, samples=119
iops : min= 42, max= 7446, avg=4347.71, stdev=2733.21, samples=119
write: IOPS=1852, BW=7410KiB/s (7588kB/s)(434MiB/60001msec)
slat (usec): min=3, max=666432, avg=23.59, stdev=2745.39
clat (msec): min=3, max=781, avg=10.14, stdev=20.50
lat (msec): min=3, max=781, avg=10.16, stdev=20.72
clat percentiles (msec):
| 1.00th=[ 6], 5.00th=[ 6], 10.00th=[ 6], 20.00th=[ 7],
| 30.00th=[ 7], 40.00th=[ 7], 50.00th=[ 7], 60.00th=[ 7],
| 70.00th=[ 7], 80.00th=[ 8], 90.00th=[ 8], 95.00th=[ 11],
| 99.00th=[ 107], 99.50th=[ 113], 99.90th=[ 131], 99.95th=[ 157],
| 99.99th=[ 760]
bw ( KiB/s): min= 80, max=12328, per=100.00%, avg=7469.53, stdev=4696.69, samples=119
iops : min= 20, max= 3082, avg=1867.34, stdev=1174.19, samples=119
lat (usec) : 10=0.01%
lat (msec) : 2=0.01%, 4=0.01%, 10=94.64%, 20=1.78%, 50=0.11%
lat (msec) : 100=1.80%, 250=1.63%, 500=0.01%, 750=0.02%, 1000=0.01%
cpu : usr=2.51%, sys=10.98%, ctx=260210, majf=0, minf=7
IO depths : 1=0.1%, 2=0.1%, 4=0.1%, 8=0.1%, 16=0.1%, 32=0.1%, >=64=100.0%
submit : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.0%
complete : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.1%, >=64=0.0%
issued rwts: total=258768,111147,0,0 short=0,0,0,0 dropped=0,0,0,0
latency : target=0, window=0, percentile=100.00%, depth=64

Run status group 0 (all jobs):
READ: bw=16.8MiB/s (17.7MB/s), 16.8MiB/s-16.8MiB/s (17.7MB/s-17.7MB/s), io=1011MiB (1060MB), run=60001-60001msec
WRITE: bw=7410KiB/s (7588kB/s), 7410KiB/s-7410KiB/s (7588kB/s-7588kB/s), io=434MiB (455MB), run=60001-60001msec

Disk stats (read/write):
sda: ios=258717/89376, merge=0/735, ticks=52540/564186, in_queue=616999, util=90.07%

ESSD磁盘测试数据

这是一块虚拟的阿里云网络盘,不能算完整意义的SSD(承诺IOPS 4200),数据仅供参考,磁盘概况:

1
2
3
4
5
6
$df -lh
Filesystem Size Used Avail Use% Mounted on
/dev/vda1 99G 30G 65G 32% /

$cat /sys/block/vda/queue/rotational
1

测试数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
$fio -ioengine=libaio -bs=4k -direct=1 -buffered=1  -thread -rw=randrw  -size=4G -filename=/home/admin/fio.test -name="EBS 4K randwrite test" -iodepth=64 -runtime=60
EBS 4K randwrite test: (g=0): rw=randrw, bs=(R) 4096B-4096B, (W) 4096B-4096B, (T) 4096B-4096B, ioengine=libaio, iodepth=64
fio-3.1
Starting 1 thread
Jobs: 1 (f=1): [m(1)][100.0%][r=10.8MiB/s,w=11.2MiB/s][r=2757,w=2876 IOPS][eta 00m:00s]
EBS 4K randwrite test: (groupid=0, jobs=1): err= 0: pid=25641: Tue Feb 23 16:35:19 2021
read: IOPS=2136, BW=8545KiB/s (8750kB/s)(501MiB/60001msec)
slat (usec): min=190, max=830992, avg=457.20, stdev=3088.80
clat (nsec): min=1792, max=1721.3M, avg=14657528.60, stdev=63188988.75
lat (usec): min=344, max=1751.1k, avg=15115.20, stdev=65165.80
clat percentiles (msec):
| 1.00th=[ 8], 5.00th=[ 9], 10.00th=[ 9], 20.00th=[ 10],
| 30.00th=[ 10], 40.00th=[ 11], 50.00th=[ 11], 60.00th=[ 11],
| 70.00th=[ 12], 80.00th=[ 12], 90.00th=[ 13], 95.00th=[ 14],
| 99.00th=[ 17], 99.50th=[ 53], 99.90th=[ 1028], 99.95th=[ 1167],
| 99.99th=[ 1653]
bw ( KiB/s): min= 56, max=12648, per=100.00%, avg=8598.92, stdev=5289.40, samples=118
iops : min= 14, max= 3162, avg=2149.73, stdev=1322.35, samples=118
write: IOPS=2137, BW=8548KiB/s (8753kB/s)(501MiB/60001msec)
slat (usec): min=2, max=181, avg= 6.67, stdev= 7.22
clat (usec): min=628, max=1721.1k, avg=14825.32, stdev=65017.66
lat (usec): min=636, max=1721.1k, avg=14832.10, stdev=65018.10
clat percentiles (msec):
| 1.00th=[ 8], 5.00th=[ 9], 10.00th=[ 9], 20.00th=[ 10],
| 30.00th=[ 10], 40.00th=[ 11], 50.00th=[ 11], 60.00th=[ 11],
| 70.00th=[ 12], 80.00th=[ 12], 90.00th=[ 13], 95.00th=[ 14],
| 99.00th=[ 17], 99.50th=[ 53], 99.90th=[ 1045], 99.95th=[ 1200],
| 99.99th=[ 1687]
bw ( KiB/s): min= 72, max=13304, per=100.00%, avg=8602.99, stdev=5296.31, samples=118
iops : min= 18, max= 3326, avg=2150.75, stdev=1324.08, samples=118
lat (usec) : 2=0.01%, 500=0.01%, 750=0.01%
lat (msec) : 2=0.01%, 4=0.01%, 10=37.85%, 20=61.53%, 50=0.10%
lat (msec) : 100=0.06%, 250=0.03%, 500=0.01%, 750=0.03%, 1000=0.25%
lat (msec) : 2000=0.14%
cpu : usr=0.70%, sys=4.01%, ctx=135029, majf=0, minf=4
IO depths : 1=0.1%, 2=0.1%, 4=0.1%, 8=0.1%, 16=0.1%, 32=0.1%, >=64=100.0%
submit : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.0%
complete : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.1%, >=64=0.0%
issued rwt: total=128180,128223,0, short=0,0,0, dropped=0,0,0
latency : target=0, window=0, percentile=100.00%, depth=64

Run status group 0 (all jobs):
READ: bw=8545KiB/s (8750kB/s), 8545KiB/s-8545KiB/s (8750kB/s-8750kB/s), io=501MiB (525MB), run=60001-60001msec
WRITE: bw=8548KiB/s (8753kB/s), 8548KiB/s-8548KiB/s (8753kB/s-8753kB/s), io=501MiB (525MB), run=60001-60001msec

Disk stats (read/write):
vda: ios=127922/87337, merge=0/237, ticks=55122/4269885, in_queue=2209125, util=94.29%

$fio -ioengine=libaio -bs=4k -direct=1 -buffered=0 -thread -rw=randrw -size=4G -filename=/home/admin/fio.test -name="EBS 4K randwrite test" -iodepth=64 -runtime=60
EBS 4K randwrite test: (g=0): rw=randrw, bs=(R) 4096B-4096B, (W) 4096B-4096B, (T) 4096B-4096B, ioengine=libaio, iodepth=64
fio-3.1
Starting 1 thread
Jobs: 1 (f=1): [m(1)][100.0%][r=9680KiB/s,w=9712KiB/s][r=2420,w=2428 IOPS][eta 00m:00s]
EBS 4K randwrite test: (groupid=0, jobs=1): err= 0: pid=25375: Tue Feb 23 16:33:03 2021
read: IOPS=2462, BW=9849KiB/s (10.1MB/s)(577MiB/60011msec)
slat (nsec): min=1558, max=10663k, avg=5900.28, stdev=46286.64
clat (usec): min=290, max=93493, avg=13054.57, stdev=4301.89
lat (usec): min=332, max=93497, avg=13060.60, stdev=4301.68
clat percentiles (usec):
| 1.00th=[ 1844], 5.00th=[10159], 10.00th=[10290], 20.00th=[10421],
| 30.00th=[10552], 40.00th=[10552], 50.00th=[10683], 60.00th=[10814],
| 70.00th=[18482], 80.00th=[19006], 90.00th=[19006], 95.00th=[19268],
| 99.00th=[19530], 99.50th=[19792], 99.90th=[29492], 99.95th=[30278],
| 99.99th=[43779]
bw ( KiB/s): min= 9128, max=30392, per=100.00%, avg=9850.12, stdev=1902.00, samples=120
iops : min= 2282, max= 7598, avg=2462.52, stdev=475.50, samples=120
write: IOPS=2465, BW=9864KiB/s (10.1MB/s)(578MiB/60011msec)
slat (usec): min=2, max=10586, avg= 6.92, stdev=67.34
clat (usec): min=240, max=69922, avg=12902.33, stdev=4307.92
lat (usec): min=244, max=69927, avg=12909.37, stdev=4307.03
clat percentiles (usec):
| 1.00th=[ 1729], 5.00th=[10159], 10.00th=[10290], 20.00th=[10290],
| 30.00th=[10421], 40.00th=[10421], 50.00th=[10552], 60.00th=[10683],
| 70.00th=[18220], 80.00th=[18744], 90.00th=[19006], 95.00th=[19006],
| 99.00th=[19268], 99.50th=[19530], 99.90th=[21103], 99.95th=[35390],
| 99.99th=[50594]
bw ( KiB/s): min= 8496, max=31352, per=100.00%, avg=9862.92, stdev=1991.48, samples=120
iops : min= 2124, max= 7838, avg=2465.72, stdev=497.87, samples=120
lat (usec) : 250=0.01%, 500=0.03%, 750=0.02%, 1000=0.02%
lat (msec) : 2=1.70%, 4=0.41%, 10=1.25%, 20=96.22%, 50=0.34%
lat (msec) : 100=0.01%
cpu : usr=0.89%, sys=4.09%, ctx=206337, majf=0, minf=4
IO depths : 1=0.1%, 2=0.1%, 4=0.1%, 8=0.1%, 16=0.1%, 32=0.1%, >=64=100.0%
submit : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.0%
complete : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.1%, >=64=0.0%
issued rwt: total=147768,147981,0, short=0,0,0, dropped=0,0,0
latency : target=0, window=0, percentile=100.00%, depth=64

Run status group 0 (all jobs):
READ: bw=9849KiB/s (10.1MB/s), 9849KiB/s-9849KiB/s (10.1MB/s-10.1MB/s), io=577MiB (605MB), run=60011-60011msec
WRITE: bw=9864KiB/s (10.1MB/s), 9864KiB/s-9864KiB/s (10.1MB/s-10.1MB/s), io=578MiB (606MB), run=60011-60011msec

Disk stats (read/write):
vda: ios=147515/148154, merge=0/231, ticks=1922378/1915751, in_queue=3780605, util=98.46%

$fio -ioengine=libaio -bs=4k -direct=0 -buffered=1 -thread -rw=randrw -size=4G -filename=/home/admin/fio.test -name="EBS 4K randwrite test" -iodepth=64 -runtime=60
EBS 4K randwrite test: (g=0): rw=randrw, bs=(R) 4096B-4096B, (W) 4096B-4096B, (T) 4096B-4096B, ioengine=libaio, iodepth=64
fio-3.1
Starting 1 thread
Jobs: 1 (f=1): [m(1)][100.0%][r=132KiB/s,w=148KiB/s][r=33,w=37 IOPS][eta 00m:00s]
EBS 4K randwrite test: (groupid=0, jobs=1): err= 0: pid=25892: Tue Feb 23 16:37:41 2021
read: IOPS=1987, BW=7949KiB/s (8140kB/s)(467MiB/60150msec)
slat (usec): min=192, max=599873, avg=479.26, stdev=2917.52
clat (usec): min=15, max=1975.6k, avg=16004.22, stdev=76024.60
lat (msec): min=5, max=2005, avg=16.48, stdev=78.00
clat percentiles (msec):
| 1.00th=[ 8], 5.00th=[ 9], 10.00th=[ 9], 20.00th=[ 10],
| 30.00th=[ 10], 40.00th=[ 11], 50.00th=[ 11], 60.00th=[ 11],
| 70.00th=[ 12], 80.00th=[ 12], 90.00th=[ 13], 95.00th=[ 14],
| 99.00th=[ 19], 99.50th=[ 317], 99.90th=[ 1133], 99.95th=[ 1435],
| 99.99th=[ 1871]
bw ( KiB/s): min= 32, max=12672, per=100.00%, avg=8034.08, stdev=5399.63, samples=119
iops : min= 8, max= 3168, avg=2008.52, stdev=1349.91, samples=119
write: IOPS=1984, BW=7937KiB/s (8127kB/s)(466MiB/60150msec)
slat (usec): min=2, max=839634, avg=18.39, stdev=2747.10
clat (msec): min=5, max=1975, avg=15.64, stdev=73.06
lat (msec): min=5, max=1975, avg=15.66, stdev=73.28
clat percentiles (msec):
| 1.00th=[ 8], 5.00th=[ 9], 10.00th=[ 9], 20.00th=[ 10],
| 30.00th=[ 10], 40.00th=[ 11], 50.00th=[ 11], 60.00th=[ 11],
| 70.00th=[ 12], 80.00th=[ 12], 90.00th=[ 13], 95.00th=[ 14],
| 99.00th=[ 18], 99.50th=[ 153], 99.90th=[ 1116], 99.95th=[ 1435],
| 99.99th=[ 1921]
bw ( KiB/s): min= 24, max=13160, per=100.00%, avg=8021.18, stdev=5405.12, samples=119
iops : min= 6, max= 3290, avg=2005.29, stdev=1351.28, samples=119
lat (usec) : 20=0.01%
lat (msec) : 10=36.51%, 20=62.63%, 50=0.21%, 100=0.12%, 250=0.05%
lat (msec) : 500=0.02%, 750=0.02%, 1000=0.19%, 2000=0.26%
cpu : usr=0.62%, sys=4.04%, ctx=125974, majf=0, minf=3
IO depths : 1=0.1%, 2=0.1%, 4=0.1%, 8=0.1%, 16=0.1%, 32=0.1%, >=64=100.0%
submit : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.0%
complete : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.1%, >=64=0.0%
issued rwt: total=119533,119347,0, short=0,0,0, dropped=0,0,0
latency : target=0, window=0, percentile=100.00%, depth=64

Run status group 0 (all jobs):
READ: bw=7949KiB/s (8140kB/s), 7949KiB/s-7949KiB/s (8140kB/s-8140kB/s), io=467MiB (490MB), run=60150-60150msec
WRITE: bw=7937KiB/s (8127kB/s), 7937KiB/s-7937KiB/s (8127kB/s-8127kB/s), io=466MiB (489MB), run=60150-60150msec

Disk stats (read/write):
vda: ios=119533/108186, merge=0/214, ticks=54093/4937255, in_queue=2525052, util=93.99%

$fio -ioengine=libaio -bs=4k -direct=0 -buffered=0 -thread -rw=randrw -size=4G -filename=/home/admin/fio.test -name="EBS 4K randwrite test" -iodepth=64 -runtime=60
EBS 4K randwrite test: (g=0): rw=randrw, bs=(R) 4096B-4096B, (W) 4096B-4096B, (T) 4096B-4096B, ioengine=libaio, iodepth=64
fio-3.1
Starting 1 thread
Jobs: 1 (f=1): [m(1)][100.0%][r=9644KiB/s,w=9792KiB/s][r=2411,w=2448 IOPS][eta 00m:00s]
EBS 4K randwrite test: (groupid=0, jobs=1): err= 0: pid=26139: Tue Feb 23 16:39:43 2021
read: IOPS=2455, BW=9823KiB/s (10.1MB/s)(576MiB/60015msec)
slat (nsec): min=1619, max=18282k, avg=5882.81, stdev=71214.52
clat (usec): min=281, max=64630, avg=13055.68, stdev=4233.17
lat (usec): min=323, max=64636, avg=13061.69, stdev=4232.79
clat percentiles (usec):
| 1.00th=[ 2040], 5.00th=[10290], 10.00th=[10421], 20.00th=[10421],
| 30.00th=[10552], 40.00th=[10552], 50.00th=[10683], 60.00th=[10814],
| 70.00th=[18220], 80.00th=[19006], 90.00th=[19006], 95.00th=[19268],
| 99.00th=[19530], 99.50th=[20055], 99.90th=[28967], 99.95th=[29754],
| 99.99th=[30540]
bw ( KiB/s): min= 8776, max=27648, per=100.00%, avg=9824.29, stdev=1655.78, samples=120
iops : min= 2194, max= 6912, avg=2456.05, stdev=413.95, samples=120
write: IOPS=2458, BW=9835KiB/s (10.1MB/s)(576MiB/60015msec)
slat (usec): min=2, max=10681, avg= 6.79, stdev=71.30
clat (usec): min=221, max=70411, avg=12909.50, stdev=4312.40
lat (usec): min=225, max=70414, avg=12916.40, stdev=4312.05
clat percentiles (usec):
| 1.00th=[ 1909], 5.00th=[10159], 10.00th=[10290], 20.00th=[10290],
| 30.00th=[10421], 40.00th=[10421], 50.00th=[10552], 60.00th=[10683],
| 70.00th=[18220], 80.00th=[18744], 90.00th=[19006], 95.00th=[19006],
| 99.00th=[19268], 99.50th=[19530], 99.90th=[28705], 99.95th=[40109],
| 99.99th=[60031]
bw ( KiB/s): min= 8568, max=28544, per=100.00%, avg=9836.03, stdev=1737.29, samples=120
iops : min= 2142, max= 7136, avg=2458.98, stdev=434.32, samples=120
lat (usec) : 250=0.01%, 500=0.03%, 750=0.02%, 1000=0.02%
lat (msec) : 2=1.03%, 4=1.10%, 10=0.98%, 20=96.43%, 50=0.38%
lat (msec) : 100=0.01%
cpu : usr=0.82%, sys=4.32%, ctx=212008, majf=0, minf=4
IO depths : 1=0.1%, 2=0.1%, 4=0.1%, 8=0.1%, 16=0.1%, 32=0.1%, >=64=100.0%
submit : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.0%
complete : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.1%, >=64=0.0%
issued rwt: total=147386,147564,0, short=0,0,0, dropped=0,0,0
latency : target=0, window=0, percentile=100.00%, depth=64

Run status group 0 (all jobs):
READ: bw=9823KiB/s (10.1MB/s), 9823KiB/s-9823KiB/s (10.1MB/s-10.1MB/s), io=576MiB (604MB), run=60015-60015msec
WRITE: bw=9835KiB/s (10.1MB/s), 9835KiB/s-9835KiB/s (10.1MB/s-10.1MB/s), io=576MiB (604MB), run=60015-60015msec

Disk stats (read/write):
vda: ios=147097/147865, merge=0/241, ticks=1916703/1915836, in_queue=3791443, util=98.68%

测试数据总结

-direct=1 -buffered=1 -direct=1 -buffered=0 -direct=0 -buffered=0 -direct=0 -buffered=1
NVMe SSD R=10.6k W=4544 R=99.8K W=42.8K R=38.6k W=16.5k R=10.8K W=4642
SATA SSD R=4312 W=1852 R=16.9k W=7254 R=15.8k W=6803 R=5389 W=2314
ESSD R=2149 W=2150 R=2462 W=2465 R=2455 W=2458 R=1987 W=1984

看起来,对于SSD如果buffered为1的话direct没啥用,如果buffered为0那么direct为1性能要好很多

SATA SSD的IOPS比NVMe性能差很多

SATA SSD当-buffered=1参数下SATA SSD的latency在7-10us之间。

NVMe SSD以及SATA SSD当buffered=0的条件下latency均为2-3us, NVMe SSD latency参考文章第一个表格, 和本次NVMe测试结果一致.

ESSD的latency基本是13-16us。

以上NVMe SSD测试数据是在测试过程中还有mysql在全力导入数据的情况下,用fio测试所得。所以空闲情况下测试结果会更好。

HDD性能测试数据

img

从上图可以看到这个磁盘的IOPS 读 935 写 400,读rt 10731nsec 大约10us, 写 17us。如果IOPS是1000的话,rt应该是1ms,实际比1ms小两个数量级,应该是cache、磁盘阵列在起作用。

SATA硬盘,10K转

万转机械硬盘组成RAID5阵列,在顺序条件最好的情况下,带宽可以达到1GB/s以上,平均延时也非常低,最低只有20多us。但是在随机IO的情况下,机械硬盘的短板就充分暴露了,零点几兆的带宽,将近5ms的延迟,IOPS只有200左右。其原因是因为

  • 随机访问直接让RAID卡缓存成了个摆设
  • 磁盘不能并行工作,因为我的机器RAID宽度Strip Size为128 KB
  • 机械轴也得在各个磁道之间跳来跳去。

理解了磁盘顺序IO时候的几十M甚至一个GB的带宽,随机IO这个真的是太可怜了。

从上面的测试数据中我们看到了机械硬盘在顺序IO和随机IO下的巨大性能差异。在顺序IO情况下,磁盘是最擅长的顺序IO,再加上Raid卡缓存命中率也高。这时带宽表现有几十、几百M,最好条件下甚至能达到1GB。IOPS这时候能有2-3W左右。到了随机IO的情形下,机械轴也被逼的跳来跳去寻道,RAID卡缓存也失效了。带宽跌到了1MB以下,最低只有100K,IOPS也只有可怜巴巴的200左右。

网上测试数据参考

我们来一起看一下具体的数据。首先来看NVMe如何减小了协议栈本身的时间消耗,我们用blktrace工具来分析一组传输在应用程序层、操作系统层、驱动层和硬件层消耗的时间和占比,来了解AHCI和NVMe协议的性能区别:

img

硬盘HDD作为一个参考基准,它的时延是非常大的,达到14ms,而AHCI SATA为125us,NVMe为111us。我们从图中可以看出,NVMe相对AHCI,协议栈及之下所占用的时间比重明显减小,应用程序层面等待的时间占比很高,这是因为SSD物理硬盘速度不够快,导致应用空转。NVMe也为将来Optane硬盘这种低延迟介质的速度提高留下了广阔的空间。

rq_affinity

参考aliyun测试文档 , rq_affinity增加2的commit: git show 5757a6d76c

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
function RunFio
{
numjobs=$1 # 实例中的测试线程数,例如示例中的10
iodepth=$2 # 同时发出I/O数的上限,例如示例中的64
bs=$3 # 单次I/O的块文件大小,例如示例中的4k
rw=$4 # 测试时的读写策略,例如示例中的randwrite
filename=$5 # 指定测试文件的名称,例如示例中的/dev/your_device
nr_cpus=`cat /proc/cpuinfo |grep "processor" |wc -l`
if [ $nr_cpus -lt $numjobs ];then
echo “Numjobs is more than cpu cores, exit!”
exit -1
fi
let nu=$numjobs+1
cpulist=""
for ((i=1;i<10;i++))
do
list=`cat /sys/block/your_device/mq/*/cpu_list | awk '{if(i<=NF) print $i;}' i="$i" | tr -d ',' | tr '\n' ','`
if [ -z $list ];then
break
fi
cpulist=${cpulist}${list}
done
spincpu=`echo $cpulist | cut -d ',' -f 2-${nu}`
echo $spincpu
fio --ioengine=libaio --runtime=30s --numjobs=${numjobs} --iodepth=${iodepth} --bs=${bs} --rw=${rw} --filename=${filename} --time_based=1 --direct=1 --name=test --group_reporting --cpus_allowed=$spincpu --cpus_allowed_policy=split
}
echo 2 > /sys/block/your_device/queue/rq_affinity
sleep 5
RunFio 10 64 4k randwrite filename

对NVME SSD进行测试,左边rq_affinity是2,右边rq_affinity为1,在这个测试参数下rq_affinity为1的性能要好(后许多次测试两者性能差不多)

image-20210607113709945

LVM性能对比

磁盘信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
sda 8:0 0 223.6G 0 disk
├─sda1 8:1 0 3M 0 part
├─sda2 8:2 0 1G 0 part /boot
├─sda3 8:3 0 96G 0 part /
├─sda4 8:4 0 10G 0 part /tmp
└─sda5 8:5 0 116.6G 0 part /home
nvme0n1 259:4 0 2.7T 0 disk
└─nvme0n1p1 259:5 0 2.7T 0 part
└─vg1-drds 252:0 0 5.4T 0 lvm /drds
nvme1n1 259:0 0 2.7T 0 disk
└─nvme1n1p1 259:2 0 2.7T 0 part /u02
nvme2n1 259:1 0 2.7T 0 disk
└─nvme2n1p1 259:3 0 2.7T 0 part
└─vg1-drds 252:0 0 5.4T 0 lvm /drds

单块nvme SSD盘跑mysql server,运行sysbench导入测试数据

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
#iostat -x nvme1n1 1
Linux 3.10.0-327.ali2017.alios7.x86_64 (k28a11352.eu95sqa) 05/13/2021 _x86_64_ (64 CPU)

avg-cpu: %user %nice %system %iowait %steal %idle
0.32 0.00 0.17 0.07 0.00 99.44

Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await svctm %util
nvme1n1 0.00 47.19 0.19 445.15 2.03 43110.89 193.62 0.31 0.70 0.03 0.70 0.06 2.85

avg-cpu: %user %nice %system %iowait %steal %idle
1.16 0.00 0.36 0.17 0.00 98.31

Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await svctm %util
nvme1n1 0.00 122.00 0.00 3290.00 0.00 271052.00 164.77 1.65 0.50 0.00 0.50 0.05 17.00

#iostat 1
Linux 3.10.0-327.ali2017.alios7.x86_64 (k28a11352.eu95sqa) 05/13/2021 _x86_64_ (64 CPU)

avg-cpu: %user %nice %system %iowait %steal %idle
0.14 0.00 0.13 0.05 0.00 99.67

Device: tps kB_read/s kB_wrtn/s kB_read kB_wrtn
sda 49.21 554.51 2315.83 1416900 5917488
nvme1n1 5.65 2.34 844.73 5989 2158468
nvme2n1 0.06 1.13 0.00 2896 0
nvme0n1 0.06 1.13 0.00 2900 0
dm-0 0.02 0.41 0.00 1036 0

avg-cpu: %user %nice %system %iowait %steal %idle
1.39 0.00 0.23 0.08 0.00 98.30

Device: tps kB_read/s kB_wrtn/s kB_read kB_wrtn
sda 8.00 0.00 60.00 0 60
nvme1n1 868.00 0.00 132100.00 0 132100
nvme2n1 0.00 0.00 0.00 0 0
nvme0n1 0.00 0.00 0.00 0 0
dm-0 0.00 0.00 0.00 0 0

avg-cpu: %user %nice %system %iowait %steal %idle
1.44 0.00 0.14 0.09 0.00 98.33

Device: tps kB_read/s kB_wrtn/s kB_read kB_wrtn
sda 0.00 0.00 0.00 0 0
nvme1n1 766.00 0.00 132780.00 0 132780
nvme2n1 0.00 0.00 0.00 0 0
nvme0n1 0.00 0.00 0.00 0 0
dm-0 0.00 0.00 0.00 0 0

avg-cpu: %user %nice %system %iowait %steal %idle
1.41 0.00 0.16 0.09 0.00 98.34

Device: tps kB_read/s kB_wrtn/s kB_read kB_wrtn
sda 105.00 0.00 532.00 0 532
nvme1n1 760.00 0.00 122236.00 0 122236
nvme2n1 0.00 0.00 0.00 0 0
nvme0n1 0.00 0.00 0.00 0 0
dm-0 0.00 0.00 0.00 0 0

如果同样写lvm,由两块nvme组成

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
Device:         rrqm/s   wrqm/s     r/s     w/s    rkB/s    wkB/s avgrq-sz avgqu-sz   await r_await w_await  svctm  %util
nvme2n1 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
nvme0n1 0.00 137.00 0.00 5730.00 0.00 421112.00 146.98 2.95 0.52 0.00 0.52 0.05 27.30

avg-cpu: %user %nice %system %iowait %steal %idle
1.17 0.00 0.34 0.19 0.00 98.30

Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await svctm %util
nvme2n1 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
nvme0n1 0.00 109.00 0.00 2533.00 0.00 271236.00 214.16 1.08 0.43 0.00 0.43 0.06 15.90

avg-cpu: %user %nice %system %iowait %steal %idle
1.38 0.00 0.42 0.20 0.00 98.00

Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await svctm %util
nvme2n1 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
nvme0n1 0.00 118.00 0.00 3336.00 0.00 320708.00 192.27 1.50 0.45 0.00 0.45 0.06 20.00

[root@k28a11352.eu95sqa /var/lib]
#iostat 1
Linux 3.10.0-327.ali2017.alios7.x86_64 (k28a11352.eu95sqa) 05/13/2021 _x86_64_ (64 CPU)

avg-cpu: %user %nice %system %iowait %steal %idle
0.40 0.00 0.20 0.07 0.00 99.33

Device: tps kB_read/s kB_wrtn/s kB_read kB_wrtn
sda 38.96 334.64 1449.68 1419236 6148304
nvme1n1 324.95 1.43 31201.30 6069 132329072
nvme2n1 0.07 0.90 0.00 3808 0
nvme0n1 256.24 1.60 22918.46 6801 97200388
dm-0 266.98 1.38 22918.46 5849 97200388

avg-cpu: %user %nice %system %iowait %steal %idle
1.20 0.00 0.42 0.25 0.00 98.12

Device: tps kB_read/s kB_wrtn/s kB_read kB_wrtn
sda 0.00 0.00 0.00 0 0
nvme1n1 0.00 0.00 0.00 0 0
nvme2n1 0.00 0.00 0.00 0 0
nvme0n1 4460.00 0.00 332288.00 0 332288
dm-0 4608.00 0.00 332288.00 0 332288

avg-cpu: %user %nice %system %iowait %steal %idle
1.35 0.00 0.38 0.22 0.00 98.06

Device: tps kB_read/s kB_wrtn/s kB_read kB_wrtn
sda 48.00 0.00 200.00 0 200
nvme1n1 0.00 0.00 0.00 0 0
nvme2n1 0.00 0.00 0.00 0 0
nvme0n1 4187.00 0.00 332368.00 0 332368
dm-0 4348.00 0.00 332368.00 0 332368

不知道为什么只有一个块ssd有流量,可能跟只写一个文件有关系

SSD中,SATA、m2、PCIE和NVME各有什么意义

高速信号协议

SAS,SATA,PCIe 这三个是同一个层面上的,模拟串行高速接口。

  • SAS 对扩容比较友好,也支持双控双活。接上SAS RAID 卡,一般在阵列上用的比较多。
  • SATA 对热插拔很友好,早先台式机装机市场的 SSD基本上都是SATA的,现在的 机械硬盘也是SATA接口居多。但速率上最高只能到 6Gb/s,上限 768MB/s左右,现在已经慢慢被pcie取代。
  • PCIe 支持速率更高,也离CPU最近。很多设备 如 网卡,显卡也都走pcie接口,当然也有SSD。现在比较主流的是PCIe 3.0,8Gb/s 看起来好像也没比 SATA 高多少,但是 PCIe 支持多个LANE,每个LANE都是 8Gb/s,这样性能就倍数增加了。目前,SSD主流的是 PCIe 3.0x4 lane,性能可以做到 3500MB/s 左右。

传输层协议

SCSI,ATA,NVMe 都属于这一层。主要是定义命令集,数字逻辑层。

  • SCSI 命令集 历史悠久,应用也很广泛。U盘,SAS 盘,还有手机上 UFS 之类很多设备都走的这个命令集。
  • ATA 则只是跑在SATA 协议上
  • NVMe 协议是有特意为 NAND 进行优化。相比于上面两者,效率更高。主要是跑在 PCIe 上的。当然,也有NVMe-MI,NVMe-of之类的。是个很好的传输层协议。

物理接口

M.2 , U.2 , AIC, NGFF 这些属于物理接口

像 M.2 可以是 SATA SSD 也可以是 NVMe(PCIe) SSD。金手指上有一个 SATA/PCIe 的选择信号,来区分两者。很多笔记本的M.2 接口也是同时支持两种类型的盘的。

  • M.2 , 主要用在 笔记本上,优点是体积小,缺点是散热不好。

  • U.2,主要用在 数据中心或者一些企业级用户,对热插拔需求高的地方。优点热插拔,散热也不错。一般主要是pcie ssd(也有sas ssd),受限于接口,最多只能是 pcie 4lane

  • AIC,企业,行业用户用的比较多。通常会支持pcie 4lane/8lane,带宽上限更高

数据总结

  • 性能排序 NVMe SSD > SATA SSD > SAN > ESSD > HDD
  • 本地ssd性能最好、sas机械盘(RAID10)性能最差
  • san存储走特定的光纤网络,不是走tcp的san(至少从网卡看不到san的流量),性能居中
  • 从rt来看 ssd:san:sas 大概是 1:3:15
  • san比本地sas机械盘性能要好,这也许取决于san的网络传输性能和san存储中的设备(比如用的ssd而不是机械盘)
  • NVMe SSD比SATA SSD快很多,latency更稳定
  • 阿里云的云盘ESSD比本地SAS RAID10阵列性能还好

参考资料

http://cizixs.com/2017/01/03/how-slow-is-disk-and-network

https://tobert.github.io/post/2014-04-17-fio-output-explained.html

https://zhuanlan.zhihu.com/p/40497397

块存储NVMe云盘原型实践

机械硬盘随机IO慢的超乎你的想象

搭载固态硬盘的服务器究竟比搭机械硬盘快多少?

如何制作本地yum repository

某些情况下在没有外网的环境需要安装一些软件,但是软件依赖比较多,那么可以提前将所有依赖下载到本地,然后将他们制作成一个yum repo,安装的时候就会自动将依赖包都安装好。

收集所有rpm包

创建一个文件夹,比如 Yum,将收集到的所有rpm包放在里面,比如安装ansible和docker需要的依赖文件:

1
2
3
4
5
6
7
8
9
10
11
12
-rwxr-xr-x 1 root root  73K 7月  12 14:22 audit-libs-python-2.8.4-4.el7.x86_64.rpm
-rwxr-xr-x 1 root root 295K 7月 12 14:22 checkpolicy-2.5-8.el7.x86_64.rpm
-rwxr-xr-x 1 root root 23M 7月 12 14:22 containerd.io-1.2.2-3.el7.x86_64.rpm
-rwxr-xr-x 1 root root 26K 7月 12 14:22 container-selinux-2.9-4.el7.noarch.rpm
-rwxr-xr-x 1 root root 37K 7月 12 14:22 container-selinux-2.74-1.el7.noarch.rpm
-rwxr-xr-x 1 root root 14M 7月 12 14:22 docker-ce-cli-18.09.0-3.el7.x86_64.rpm
-rwxr-xr-x 1 root root 29K 7月 12 14:22 docker-ce-selinux-17.03.2.ce-1.el7.centos.noarch.rpm
-r-xr-xr-x 1 root root 22K 7月 12 14:23 sshpass-1.06-2.el7.x86_64.rpm
-r-xr-xr-x 1 root root 22K 7月 12 14:23 sshpass-1.06-1.el7.x86_64.rpm
-r-xr-xr-x 1 root root 154K 7月 12 14:23 PyYAML-3.10-11.el7.x86_64.rpm
-r-xr-xr-x 1 root root 29K 7月 12 14:23 python-six-1.9.0-2.el7.noarch.rpm
-r-xr-xr-x 1 root root 397K 7月 12 14:23 python-setuptools-0.9.8-7.el7.noarch.rpm

收集方法:

1
2
3
4
5
6
7
8
//先安装yum工具
yum install yum-utils -y
//将 ansible 依赖包都下载下来
repoquery --requires --resolve --recursive ansible | xargs -r yumdownloader --destdir=/tmp/ansible
//将ansible rpm自己下载回来
yumdownloader --destdir=/tmp/ansible --resolve ansible
//验证一下依赖关系是完整的
//repotrack ansible

创建仓库索引

需要安装工具 yum install createrepo -y:

1
2
3
4
5
6
7
8
9
10
# createrepo ./public_yum/
Spawning worker 0 with 6 pkgs
Spawning worker 1 with 6 pkgs
Spawning worker 23 with 5 pkgs
Workers Finished
Saving Primary metadata
Saving file lists metadata
Saving other metadata
Generating sqlite DBs
Sqlite DBs complete

会在yum文件夹下生成一个索引文件夹 repodata

1
2
3
4
5
6
7
8
9
drwxr-xr-x 2 root root 4.0K 7月  12 14:25 repodata
[root@az1-drds-79 yum]# ls repodata/
5e15c62fec1fe43c6025ecf4d370d632f4b3f607500016e045ad94b70f87bac3-filelists.xml.gz
7a314396d6e90532c5c534567f9bd34eee94c3f8945fc2191b225b2861ace2b6-other.xml.gz
ce9dce19f6b426b8856747b01d51ceaa2e744b6bbd5fbc68733aa3195f724590-primary.xml.gz
ee33b7d79e32fe6ad813af92a778a0ec8e5cc2dfdc9b16d0be8cff6a13e80d99-filelists.sqlite.bz2
f7e8177e7207a4ff94bade329a0f6b572a72e21da106dd9144f8b1cdf0489cab-primary.sqlite.bz2
ff52e1f1859790a7b573d2708b02404eb8b29aa4b0c337bda83af75b305bfb36-other.sqlite.bz2
repomd.xml

生成iso镜像文件

非必要步骤,如果需要带到客户环境可以先生成iso,不过不够灵活。

也可以不用生成iso,直接在drds.repo中指定 createrepo 的目录也可以,记得要先执行 yum clean all和yum update

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#mkisofs -r -o docker_ansible.iso ./yum/
I: -input-charset not specified, using utf-8 (detected in locale settings)
Using PYTHO000.RPM;1 for /python-httplib2-0.7.7-3.el7.noarch.rpm (python-httplib2-0.9.1-3.el7.noarch.rpm)
Using MARIA006.RPM;1 for /mariadb-5.5.56-2.el7.x86_64.rpm (mariadb-libs-5.5.56-2.el7.x86_64.rpm)
Using LIBTO001.RPM;1 for /libtomcrypt-1.17-25.el7.x86_64.rpm (libtomcrypt-1.17-26.el7.x86_64.rpm)
6.11% done, estimate finish Sun Jul 12 14:26:47 2020
97.60% done, estimate finish Sun Jul 12 14:26:48 2020
Total translation table size: 0
Total rockridge attributes bytes: 14838
Total directory bytes: 2048
Path table size(bytes): 26
Max brk space used 21000
81981 extents written (160 MB)

将 生成的 iso挂载到目标机器上

1
2
3
# mkdir /mnt/iso
# mount ./docker_ansible.iso /mnt/iso
mount: /dev/loop0 is write-protected, mounting read-only

配置本地 yum 源

yum repository不是必须要求iso挂载,直接指向rpm文件夹(必须要有 createrepo 建立索引了)也可以

1
2
3
4
5
6
7
8
9
# cat /etc/yum.repos.d/drds.repo 
[drds]
name=drds Extra Packages for Enterprise Linux 7 - $basearch
enabled=1
failovermethod=priority
baseurl=file:///mnt/repo #baseurl=http://192.168.1.91:8000/ 本地内网
priority=1 #添加priority=1,数字越小优先级越高,也可以修改网络源的priority的值
gpgcheck=0
#gpgkey=file:///mnt/cdrom/RPM-GPG-KEY-CentOS-5 #注:这个你cd /mnt/cdrom/可以看到这个key,这里仅仅是个例子, 因为gpgcheck是0 ,所以gpgkey不需要了

到此就可以在没有网络环境的机器上直接:yum install ansible docker -y 了

测试

测试的话可以指定repo 源: yum install ansible –enablerepo=drds (drds 优先级最高)

本地会cache一些rpm的版本信息,可以执行 yum clean all 得到一个干净的测试环境

1
2
3
yum clean all
yum list
yum deplist ansible

yum 源问题处理

Yum commands error “pycurl.so: undefined symbol”

xargs 作用

xargs命令的作用,是将标准输入转为命令行参数。因为有些命令是不接受标准输入的,比如echo

xargs的作用在于,大多数命令(比如rmmkdirls)与管道一起使用时,都需要xargs将标准输入转为命令行参数。

dnf 使用

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

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

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

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

安装yum源

安装7.70版本curl yum源

1
rpm -Uvh http://www.city-fan.org/ftp/contrib/yum-repo/city-fan.org-release-2-1.rhel7.noarch.rpm

其它技巧

rpm依赖查询

1
2
3
4
rpm -q --whatprovides file-name //查询一个文件来自哪个rpm包
rpm -qf /usr/lib/systemd/libsystemd-shared-239.so // 查询一个so lib来自哪个rpm包
或者 yum whatprovides /usr/lib/systemd/libsystemd-shared-239.so
yum provides */libmysqlclient.so.18

制作debian 仓库

适合ubuntu、deepin、uos等, 参考:https://lework.github.io/2021/04/03/debian-kubeadm-install/

1
2
#添加新仓库
sudo apt-add-repository 'deb http://ftp.us.debian.org/debian stretch main contrib non-free'

img

rpm转换成 dpkg

apt-mirror

先要安装apt-mirror 工具,安装后会生成配置文件 /etc/apt/mirror.list 然后需要手工修改配置文件:

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
#cat /etc/apt/mirror.list
############# config ##################

#下载下来的仓库文件放在哪里
set base_path /polarx/debian

set mirror_path $base_path/mirror
set skel_path $base_path/skel
set var_path $base_path/var
set cleanscript $var_path/clean.sh
set defaultarch amd64
#set postmirror_script $var_path/postmirror.sh
set run_postmirror 0
set nthreads 20
set _tilde 0
#
############# end config ##############

#从哪里镜像仓库
deb http://yum.tbsite.net/mirrors/debian/ buster main non-free contrib
#deb https://mirrors.aliyun.com/kubernetes/apt kubernetes-xenial main

#deb http://ftp.us.debian.org/debian unstable main contrib non-free
#deb-src http://ftp.us.debian.org/debian unstable main contrib non-free

# mirror additional architectures
#deb-alpha http://ftp.us.debian.org/debian unstable main contrib non-free
#deb-amd64 http://ftp.us.debian.org/debian unstable main contrib non-free
#deb-armel http://ftp.us.debian.org/debian unstable main contrib non-free
#deb-hppa http://ftp.us.debian.org/debian unstable main contrib non-free
#deb-i386 http://ftp.us.debian.org/debian unstable main contrib non-free
#deb-ia64 http://ftp.us.debian.org/debian unstable main contrib non-free
#deb-m68k http://ftp.us.debian.org/debian unstable main contrib non-free
#deb-mips http://ftp.us.debian.org/debian unstable main contrib non-free
#deb-mipsel http://ftp.us.debian.org/debian unstable main contrib non-free
#deb-powerpc http://ftp.us.debian.org/debian unstable main contrib non-free
#deb-s390 http://ftp.us.debian.org/debian unstable main contrib non-free
#deb-sparc http://ftp.us.debian.org/debian unstable main contrib non-free

#clean http://ftp.us.debian.org/debian
clean http://yum.tbsite.net/mirrors/debian/

debian仓库介绍

一个Debian仓库包含多个发行版。Debian 的发行版是以 “玩具总动员 “电影中的角色命名的 (wheezy, jessie, stretch, …)。 代号有别名,叫做套件(stable, oldstable, testing, unstable)。一个发行版会被分成几个组件。在 Debian 中,这些组件被命名为 main, contrib, 和 non-free,并表并表示它们所包含的软件的授权条款。一个版本也有各种架构(amd64, i386, mips, powerpc, s390x, …)的软件包,以及源码和架构独立的软件包。

仓库的根目录下有一个dists 目录,而这个目录又有每个发行版和套件的目录,后者通常是前者的符号链接,但浏览器不会向您显示出这个区别。每个发行版子目录都包含一个加密签名的Release文件和每个组件的目录,里面是不同架构的目录,名为binary-*<架构>*和sources。而在这些文件中,Packages是文本文件,包含了软件包。嗯,那么实际的软件包在哪里?

image-20220829163817671

软件包本身在仓库根目录下的pool。在pool下面又有所有组件的目录,其中有0,…,9ab,.., z, liba, … , libz。 而在这些目录中,是以它们所包含的软件包命名的目录,这些目录最后包含实际的软件包,即.deb文件。这个名字不一定是软件包本身的名字,例如,软件包bsdutils在目录pool/main/u/util-linux 下,它是生成软件包的源码的名称。一个上游源可能会生成多个二进制软件包,而所有这些软件包最终都会在pool下面的同一个子目录中。额外的单字母目录只是一个技巧,以避免在一个目录中有太多的条目,因为这是很多系统传统上存在性能问题的原因。

pool下面的子目录中,通常会有多个版本的软件包,而每个版本的软件包属于什么发行版的信息只存在于索引中。这样一来,同一个版本的包可软件以属于多个发行版,但只使用一次磁盘空间,而且不需要求助于硬链接或符号链接,所以镜像相当简单,甚至可以在没有这些概念的系统中进行。

常用命令

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
apt install kubeadm=1.20.12-00 //指定版本安装

#查询可用版本
apt-cache policy kubeadm
apt list --all-versions kubeadm

#清理
apt clean --dry-run
apt update
apt list
apt show kubeadm

#查询安装包的所有文件
dpkg-query -L kubeadm

#列出所有依赖包
apt-cache depends ansible

#被依赖查询
apt-cache rdepends kubelet

dpkg -I kubernetes/pool/kubeadm_1.21.0-00_amd64.deb

#下载依赖包
apt-get download $(apt-rdepends kubeadm|grep -v "^ ")
aptitude --download-only -y install $(apt-rdepends kubeadm|grep -v "^ ") //不能下载已经安装了的依赖包

apt download $(apt-cache depends --recurse --no-recommends --no-suggests --no-conflicts --no-breaks --no-replaces --no-enhances kubeadm | grep "^\w" | sort -u)

简单仓库

下载所有 deb 包以及他们的依赖

1
apt download $(apt-cache depends --recurse --no-recommends --no-suggests --no-conflicts --no-breaks --no-replaces --no-enhances kubeadm | grep "^\w" | sort -u)

生成 index

1
dpkg-scanpackages -m . > Packages

apt source 指向这个目录

1
deb [trusted=yes] file:/polarx/test /

Kubernetes 仓库

debian 上通过kubeadm 安装 kubernetes 集群

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
//官方
echo "deb http://apt.kubernetes.io/ kubernetes-xenial main" | tee -a /etc/apt/sources.list.d/kubernetes.list

//阿里云仓库
echo 'deb https://mirrors.aliyun.com/kubernetes/apt kubernetes-xenial main' > /etc/apt/sources.list.d/kubernetes.list
curl -s https://mirrors.aliyun.com/kubernetes/apt/doc/apt-key.gpg | sudo apt-key add -
apt-get update

export KUBE_VERSION="1.20.5"
apt-get install -y kubeadm=$KUBE_VERSION-00 kubelet=$KUBE_VERSION-00 kubectl=$KUBE_VERSION-00
sudo apt-mark hold kubelet kubeadm kubectl

[ -d /etc/bash_completion.d ] && \
{ kubectl completion bash > /etc/bash_completion.d/kubectl; \
kubeadm completion bash > /etc/bash_completion.d/kubadm; }

[ ! -d /usr/lib/systemd/system/kubelet.service.d ] && mkdir -p /usr/lib/systemd/system/kubelet.service.d
cat << EOF > /usr/lib/systemd/system/kubelet.service.d/11-cgroup.conf
[Service]
CPUAccounting=true
MemoryAccounting=true
BlockIOAccounting=true
ExecStartPre=/usr/bin/bash -c '/usr/bin/mkdir -p /sys/fs/cgroup/{cpuset,memory,systemd,pids,"cpu,cpuacct"}/{system,kube,kubepods}.slice'
Slice=kube.slice
EOF
systemctl daemon-reload

systemctl enable kubelet.service

docker 仓库

1
2
3
4
5
6
7
apt-get install -y apt-transport-https ca-certificates curl gnupg2 lsb-release bash-completion

curl -fsSL https://mirrors.aliyun.com/docker-ce/linux/debian/gpg | sudo apt-key add -
echo "deb [arch=amd64] https://mirrors.aliyun.com/docker-ce/linux/debian $(lsb_release -cs) stable" > /etc/apt/sources.list.d/docker-ce.list
sudo apt-get update
apt-get install -y docker-ce docker-ce-cli containerd.io
apt-mark hold docker-ce docker-ce-cli containerd.io

参考资料

xargs 命令教程

如何制作软件仓库

某些情况下在没有外网的环境需要安装一些软件,但是软件依赖比较多,那么可以提前将所有依赖下载到本地,然后将他们制作成一个yum repo,安装的时候就会自动将依赖包都安装好。

centos下是 yum 仓库,Debian、ubuntu下是apt仓库,我们先讲 yum 仓库的制作,Debian apt 仓库类似

收集所有rpm包

创建一个文件夹,比如 Yum,将收集到的所有rpm包放在里面,比如安装ansible和docker需要的依赖文件:

1
2
3
4
5
6
7
8
9
10
11
12
-rwxr-xr-x 1 root root  73K 7月  12 14:22 audit-libs-python-2.8.4-4.el7.x86_64.rpm
-rwxr-xr-x 1 root root 295K 7月 12 14:22 checkpolicy-2.5-8.el7.x86_64.rpm
-rwxr-xr-x 1 root root 23M 7月 12 14:22 containerd.io-1.2.2-3.el7.x86_64.rpm
-rwxr-xr-x 1 root root 26K 7月 12 14:22 container-selinux-2.9-4.el7.noarch.rpm
-rwxr-xr-x 1 root root 37K 7月 12 14:22 container-selinux-2.74-1.el7.noarch.rpm
-rwxr-xr-x 1 root root 14M 7月 12 14:22 docker-ce-cli-18.09.0-3.el7.x86_64.rpm
-rwxr-xr-x 1 root root 29K 7月 12 14:22 docker-ce-selinux-17.03.2.ce-1.el7.centos.noarch.rpm
-r-xr-xr-x 1 root root 22K 7月 12 14:23 sshpass-1.06-2.el7.x86_64.rpm
-r-xr-xr-x 1 root root 22K 7月 12 14:23 sshpass-1.06-1.el7.x86_64.rpm
-r-xr-xr-x 1 root root 154K 7月 12 14:23 PyYAML-3.10-11.el7.x86_64.rpm
-r-xr-xr-x 1 root root 29K 7月 12 14:23 python-six-1.9.0-2.el7.noarch.rpm
-r-xr-xr-x 1 root root 397K 7月 12 14:23 python-setuptools-0.9.8-7.el7.noarch.rpm

收集方法:

1
2
3
4
5
6
7
8
//先安装yum工具
yum install yum-utils -y
//将 ansible 依赖包都下载下来
repoquery --requires --resolve --recursive ansible | xargs -r yumdownloader --destdir=/tmp/ansible
//将ansible rpm自己下载回来
yumdownloader --destdir=/tmp/ansible --resolve ansible
//验证一下依赖关系是完整的
//repotrack ansible

创建仓库索引

需要安装工具 yum install createrepo -y:

1
2
3
4
5
6
7
8
9
10
# createrepo ./public_yum/
Spawning worker 0 with 6 pkgs
Spawning worker 1 with 6 pkgs
Spawning worker 23 with 5 pkgs
Workers Finished
Saving Primary metadata
Saving file lists metadata
Saving other metadata
Generating sqlite DBs
Sqlite DBs complete

会在yum文件夹下生成一个索引文件夹 repodata

1
2
3
4
5
6
7
8
9
drwxr-xr-x 2 root root 4.0K 7月  12 14:25 repodata
[root@az1-drds-79 yum]# ls repodata/
5e15c62fec1fe43c6025ecf4d370d632f4b3f607500016e045ad94b70f87bac3-filelists.xml.gz
7a314396d6e90532c5c534567f9bd34eee94c3f8945fc2191b225b2861ace2b6-other.xml.gz
ce9dce19f6b426b8856747b01d51ceaa2e744b6bbd5fbc68733aa3195f724590-primary.xml.gz
ee33b7d79e32fe6ad813af92a778a0ec8e5cc2dfdc9b16d0be8cff6a13e80d99-filelists.sqlite.bz2
f7e8177e7207a4ff94bade329a0f6b572a72e21da106dd9144f8b1cdf0489cab-primary.sqlite.bz2
ff52e1f1859790a7b573d2708b02404eb8b29aa4b0c337bda83af75b305bfb36-other.sqlite.bz2
repomd.xml

生成iso镜像文件

非必要步骤,如果需要带到客户环境可以先生成iso,不过不够灵活。

也可以不用生成iso,直接在drds.repo中指定 createrepo 的目录也可以,记得要先执行 yum clean all和yum update

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#mkisofs -r -o docker_ansible.iso ./yum/
I: -input-charset not specified, using utf-8 (detected in locale settings)
Using PYTHO000.RPM;1 for /python-httplib2-0.7.7-3.el7.noarch.rpm (python-httplib2-0.9.1-3.el7.noarch.rpm)
Using MARIA006.RPM;1 for /mariadb-5.5.56-2.el7.x86_64.rpm (mariadb-libs-5.5.56-2.el7.x86_64.rpm)
Using LIBTO001.RPM;1 for /libtomcrypt-1.17-25.el7.x86_64.rpm (libtomcrypt-1.17-26.el7.x86_64.rpm)
6.11% done, estimate finish Sun Jul 12 14:26:47 2020
97.60% done, estimate finish Sun Jul 12 14:26:48 2020
Total translation table size: 0
Total rockridge attributes bytes: 14838
Total directory bytes: 2048
Path table size(bytes): 26
Max brk space used 21000
81981 extents written (160 MB)

将 生成的 iso挂载到目标机器上

1
2
3
# mkdir /mnt/iso
# mount ./docker_ansible.iso /mnt/iso
mount: /dev/loop0 is write-protected, mounting read-only

配置本地 yum 源

yum repository不是必须要求iso挂载,直接指向rpm文件夹(必须要有 createrepo 建立索引了)也可以

1
2
3
4
5
6
7
8
9
# cat /etc/yum.repos.d/drds.repo 
[drds]
name=drds Extra Packages for Enterprise Linux 7 - $basearch
enabled=1
failovermethod=priority
baseurl=file:///mnt/repo #baseurl=http://192.168.1.91:8000/ 本地内网
priority=1 #添加priority=1,数字越小优先级越高,也可以修改网络源的priority的值
gpgcheck=0
#gpgkey=file:///mnt/cdrom/RPM-GPG-KEY-CentOS-5 #注:这个你cd /mnt/cdrom/可以看到这个key,这里仅仅是个例子, 因为gpgcheck是0 ,所以gpgkey不需要了

到此就可以在没有网络环境的机器上直接:yum install ansible docker -y 了

yum 仓库代理

如果需要代理可以在docker.repo 最后添加:

1
proxy=socks5://127.0.0.1:12345 //socks代理 或者 proxy=http://ip:port

如果要给全局配置代理,则在 /etc/yum.conf 最后添加如上行

测试

测试的话可以指定repo 源: yum install ansible –enablerepo=drds (drds 优先级最高)

本地会cache一些rpm的版本信息,可以执行 yum clean all 得到一个干净的测试环境

1
2
3
yum clean all
yum list
yum deplist ansible

yum 源问题处理

Yum commands error “pycurl.so: undefined symbol”

xargs 作用

xargs命令的作用,是将标准输入转为命令行参数。因为有些命令是不接受标准输入的,比如echo

xargs的作用在于,大多数命令(比如rmmkdirls)与管道一起使用时,都需要xargs将标准输入转为命令行参数。

dnf 使用

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

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

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

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

安装yum源

安装7.70版本curl yum源

1
rpm -Uvh http://www.city-fan.org/ftp/contrib/yum-repo/city-fan.org-release-2-1.rhel7.noarch.rpm

其它技巧

rpm依赖查询

1
2
3
4
5
6
rpm -q --whatprovides file-name //查询一个文件来自哪个rpm包
rpm -qf /usr/lib/systemd/libsystemd-shared-239.so // 查询一个so lib来自哪个rpm包
或者 yum whatprovides /usr/lib/systemd/libsystemd-shared-239.so
yum provides */libmysqlclient.so.18

rpm -qi //查看rpm包安装的安装信息

多版本问题

1
2
3
4
5
6
7
//查看protobuf 的多个版本
yum --showduplicates list protobuf
//or
repoquery --show-duplicates protobuf

//指定版本安装,一般是安装教老的版本
yum install protobuf-3.6.1-4.el7

制作debian 仓库

适合ubuntu、deepin、uos等, 参考:https://lework.github.io/2021/04/03/debian-kubeadm-install/

1
2
#添加新仓库
sudo apt-add-repository 'deb http://ftp.us.debian.org/debian stretch main contrib non-free'

img

rpm转换成 dpkg

apt-mirror

先要安装apt-mirror 工具,安装后会生成配置文件 /etc/apt/mirror.list 然后需要手工修改配置文件:

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
#cat /etc/apt/mirror.list
############# config ##################

#下载下来的仓库文件放在哪里
set base_path /polarx/debian

set mirror_path $base_path/mirror
set skel_path $base_path/skel
set var_path $base_path/var
set cleanscript $var_path/clean.sh
set defaultarch amd64
#set postmirror_script $var_path/postmirror.sh
set run_postmirror 0
set nthreads 20
set _tilde 0
#
############# end config ##############

#从哪里镜像仓库
deb http://yum.tbsite.net/mirrors/debian/ buster main non-free contrib
#deb https://mirrors.aliyun.com/kubernetes/apt kubernetes-xenial main

#deb http://ftp.us.debian.org/debian unstable main contrib non-free
#deb-src http://ftp.us.debian.org/debian unstable main contrib non-free

# mirror additional architectures
#deb-alpha http://ftp.us.debian.org/debian unstable main contrib non-free
#deb-amd64 http://ftp.us.debian.org/debian unstable main contrib non-free
#deb-armel http://ftp.us.debian.org/debian unstable main contrib non-free
#deb-hppa http://ftp.us.debian.org/debian unstable main contrib non-free
#deb-i386 http://ftp.us.debian.org/debian unstable main contrib non-free
#deb-ia64 http://ftp.us.debian.org/debian unstable main contrib non-free
#deb-m68k http://ftp.us.debian.org/debian unstable main contrib non-free
#deb-mips http://ftp.us.debian.org/debian unstable main contrib non-free
#deb-mipsel http://ftp.us.debian.org/debian unstable main contrib non-free
#deb-powerpc http://ftp.us.debian.org/debian unstable main contrib non-free
#deb-s390 http://ftp.us.debian.org/debian unstable main contrib non-free
#deb-sparc http://ftp.us.debian.org/debian unstable main contrib non-free

#clean http://ftp.us.debian.org/debian
clean http://yum.tbsite.net/mirrors/debian/

debian仓库介绍

一个Debian仓库包含多个发行版。Debian 的发行版是以 “玩具总动员 “电影中的角色命名的 (wheezy, jessie, stretch, …)。 代号有别名,叫做套件(stable, oldstable, testing, unstable)。一个发行版会被分成几个组件。在 Debian 中,这些组件被命名为 main, contrib, 和 non-free,并表并表示它们所包含的软件的授权条款。一个版本也有各种架构(amd64, i386, mips, powerpc, s390x, …)的软件包,以及源码和架构独立的软件包。

仓库的根目录下有一个dists 目录,而这个目录又有每个发行版和套件的目录,后者通常是前者的符号链接,但浏览器不会向您显示出这个区别。每个发行版子目录都包含一个加密签名的Release文件和每个组件的目录,里面是不同架构的目录,名为binary-*<架构>*和sources。而在这些文件中,Packages是文本文件,包含了软件包。嗯,那么实际的软件包在哪里?

image-20220829163817671

软件包本身在仓库根目录下的pool。在pool下面又有所有组件的目录,其中有0,…,9ab,.., z, liba, … , libz。 而在这些目录中,是以它们所包含的软件包命名的目录,这些目录最后包含实际的软件包,即.deb文件。这个名字不一定是软件包本身的名字,例如,软件包bsdutils在目录pool/main/u/util-linux 下,它是生成软件包的源码的名称。一个上游源可能会生成多个二进制软件包,而所有这些软件包最终都会在pool下面的同一个子目录中。额外的单字母目录只是一个技巧,以避免在一个目录中有太多的条目,因为这是很多系统传统上存在性能问题的原因。

pool下面的子目录中,通常会有多个版本的软件包,而每个版本的软件包属于什么发行版的信息只存在于索引中。这样一来,同一个版本的包可软件以属于多个发行版,但只使用一次磁盘空间,而且不需要求助于硬链接或符号链接,所以镜像相当简单,甚至可以在没有这些概念的系统中进行。

常用命令

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
apt install kubeadm=1.20.12-00 //指定版本安装

#查询可用版本
apt-cache policy kubeadm
apt list --all-versions kubeadm

#清理
apt clean --dry-run
apt update
apt list/dpkg -l //列出本机所有已经安装的软件
apt show kubeadm //显示版本、作者等信息

#查询某个安装包的所有文件
dpkg-query -L kubeadm

#列出所有依赖包
apt-cache depends ansible

#被依赖查询
apt-cache rdepends kubelet

dpkg -I kubernetes/pool/kubeadm_1.21.0-00_amd64.deb

#下载依赖包
apt-get download $(apt-rdepends kubeadm|grep -v "^ ")
aptitude --download-only -y install $(apt-rdepends kubeadm|grep -v "^ ") //不能下载已经安装了的依赖包

apt download $(apt-cache depends --recurse --no-recommends --no-suggests --no-conflicts --no-breaks --no-replaces --no-enhances kubeadm | grep "^\w" | sort -u)

简单仓库

下载所有 deb 包以及他们的依赖

1
apt download $(apt-cache depends --recurse --no-recommends --no-suggests --no-conflicts --no-breaks --no-replaces --no-enhances kubeadm | grep "^\w" | sort -u)

到deb 包所在的目录下生成 index

1
dpkg-scanpackages -m . > Packages

/etc/apt/source.list 指向这个目录

1
2
3
deb [trusted=yes] file:/polarx/test /
or
deb [trusted=yes] http://kunpeng/pool/ ./

验证:

1
apt update //看是否能拉取你刚配置的仓库

Kubernetes 仓库

debian 上通过kubeadm 安装 kubernetes 集群

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
//官方
echo "deb http://apt.kubernetes.io/ kubernetes-xenial main" | tee -a /etc/apt/sources.list.d/kubernetes.list

//阿里云仓库
echo 'deb https://mirrors.aliyun.com/kubernetes/apt kubernetes-xenial main' > /etc/apt/sources.list.d/kubernetes.list
curl -s https://mirrors.aliyun.com/kubernetes/apt/doc/apt-key.gpg | sudo apt-key add -
apt-get update

export KUBE_VERSION="1.20.5"
apt-get install -y kubeadm=$KUBE_VERSION-00 kubelet=$KUBE_VERSION-00 kubectl=$KUBE_VERSION-00
sudo apt-mark hold kubelet kubeadm kubectl

[ -d /etc/bash_completion.d ] && \
{ kubectl completion bash > /etc/bash_completion.d/kubectl; \
kubeadm completion bash > /etc/bash_completion.d/kubadm; }

[ ! -d /usr/lib/systemd/system/kubelet.service.d ] && mkdir -p /usr/lib/systemd/system/kubelet.service.d
cat << EOF > /usr/lib/systemd/system/kubelet.service.d/11-cgroup.conf
[Service]
CPUAccounting=true
MemoryAccounting=true
BlockIOAccounting=true
ExecStartPre=/usr/bin/bash -c '/usr/bin/mkdir -p /sys/fs/cgroup/{cpuset,memory,systemd,pids,"cpu,cpuacct"}/{system,kube,kubepods}.slice'
Slice=kube.slice
EOF
systemctl daemon-reload

systemctl enable kubelet.service

docker 仓库

1
2
3
4
5
6
7
apt-get install -y apt-transport-https ca-certificates curl gnupg2 lsb-release bash-completion

curl -fsSL https://mirrors.aliyun.com/docker-ce/linux/debian/gpg | sudo apt-key add -
echo "deb [arch=amd64] https://mirrors.aliyun.com/docker-ce/linux/debian $(lsb_release -cs) stable" > /etc/apt/sources.list.d/docker-ce.list
sudo apt-get update
apt-get install -y docker-ce docker-ce-cli containerd.io
apt-mark hold docker-ce docker-ce-cli containerd.io

锁定已安装版本

1
2
//锁定这三个软件的版本,避免意外升级导致版本错误:
sudo apt-mark hold kubeadm kubelet kubectl

参考资料

xargs 命令教程

0%