plantegg

java tcp mysql performance network docker Linux


  • 首页

  • 分类

  • 归档

  • 标签

  • 站点地图

  • 关于

就是要你懂TCP--半连接队列和全连接队列

发表于 2020-04-07 | 分类于 TCP

关于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

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

发表于 2020-03-01 | 分类于 others

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

这是一次奇特的读书经历,因为黄奇帆这本书的内容主要是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。

2022 黄奇帆谈贫富差距和共同富裕:

差距的三个原因:

  1. 地域差异,胡焕庸线导致东西部差距,西部地理天气条件不如东部;2000年收入是3.5比1,2010年是3比1,2020年是2 比1
  2. 城乡差距,农民没有财产性收入(比如房屋、宅基地升值;比如贷款通货膨胀后变相升值);
  3. 行业差距,金融业息差大躺着赚钱,互联网、房地产法律法规不完善赚钱快

解决办法:

  1. 地域:通过大资本大投入在西部搞能源、资源开发,从西气东送到西电东送,比如青海700平方公里的太阳能基地年产生2万亿度电,2毛钱一度就是4000亿人民币,有可能能成为中国的阿拉伯能源基地;发展生产力
  2. 2020年土地法,可以直接拍卖宅基地?获得土地收入以及增值。落户城市享受城市工作机会尤其是社保体系;要素生产
  3. 更多地开放金融牌照,降低息差;完善相关法律法规,让竞争尽可能在公平公正的环境下良性发生;行业边际效应、结构性均衡、资源优化配置

目前间接税过高,过高的间接税虽然有一部分用来转移支付但是效果不是最优,以后希望降低来改善营商环境藏富于民。以后可能会收取房产税、遗产税(都有免征门槛)等

TCP相关参数解释

发表于 2020-01-26 | 分类于 TCP

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

存储原理

发表于 2020-01-25 | 分类于 CPU

存储原理

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

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 年硬盘死亡報告

如何制作本地软件仓库

发表于 2020-01-24 | 分类于 Linux

如何制作软件仓库

某些情况下在没有外网的环境需要安装一些软件,但是软件依赖比较多,那么可以提前将所有依赖下载到本地,然后将他们制作成一个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
#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的作用在于,大多数命令(比如rm、mkdir、ls)与管道一起使用时,都需要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,…,9,a,b,.., 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 命令教程

如何制作本地yum repository

发表于 2020-01-24 | 分类于 Linux

如何制作本地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
#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的作用在于,大多数命令(比如rm、mkdir、ls)与管道一起使用时,都需要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,…,9,a,b,.., 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 命令教程

kubernetes service

发表于 2020-01-22 | 分类于 docker

kubernetes service 和 kube-proxy详解

service 模式

根据创建Service的type类型不同,可分成4种模式:

  • ClusterIP: 默认方式。根据是否生成ClusterIP又可分为普通Service和Headless Service两类:
    • 普通Service:通过为Kubernetes的Service分配一个集群内部可访问的固定虚拟IP(Cluster IP),实现集群内的访问。为最常见的方式。
    • Headless Service:该服务不会分配Cluster IP,也不通过kube-proxy做反向代理和负载均衡。而是通过DNS提供稳定的网络ID来访问,DNS会将headless service的后端直接解析为podIP列表。主要供StatefulSet中对应POD的序列用。
  • NodePort:除了使用Cluster IP之外,还通过将service的port映射到集群内每个节点的相同一个端口,实现通过nodeIP:nodePort从集群外访问服务。NodePort会RR转发给后端的任意一个POD,跟ClusterIP类似
  • LoadBalancer:和nodePort类似,不过除了使用一个Cluster IP和nodePort之外,还会向所使用的公有云申请一个负载均衡器,实现从集群外通过LB访问服务。在公有云提供的 Kubernetes 服务里,都使用了一个叫作 CloudProvider 的转接层,来跟公有云本身的 API 进行对接。所以,在上述 LoadBalancer 类型的 Service 被提交后,Kubernetes 就会调用 CloudProvider 在公有云上为你创建一个负载均衡服务,并且把被代理的 Pod 的 IP 地址配置给负载均衡服务做后端。
  • ExternalName:是 Service 的特例。此模式主要面向运行在集群外部的服务,通过它可以将外部服务映射进k8s集群,且具备k8s内服务的一些特征(如具备namespace等属性),来为集群内部提供服务。此模式要求kube-dns的版本为1.7或以上。这种模式和前三种模式(除headless service)最大的不同是重定向依赖的是dns层次,而不是通过kube-proxy。

service yaml案例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
apiVersion: v1
kind: Service
metadata:
name: nginx-ren
labels:
app: web
spec:
type: NodePort
# clusterIP: None
ports:
- port: 8080
targetPort: 80
nodePort: 30080
selector:
app: ren

ports 字段指定服务的端口信息:

  • port:虚拟 ip 要绑定的 port,每个 service 会创建出来一个虚拟 ip,通过访问 vip:port 就能获取服务的内容。这个 port 可以用户随机选取,因为每个服务都有自己的 vip,也不用担心冲突的情况
  • targetPort:pod 中暴露出来的 port,这是运行的容器中具体暴露出来的端口,一定不能写错–一般用name来代替具体的port
  • protocol:提供服务的协议类型,可以是 TCP 或者 UDP
  • nodePort: 仅在type为nodePort模式下有用,宿主机暴露端口

但是nodePort和loadbalancer可以被外部访问,loadbalancer需要一个外部ip,流量走外部ip进出

NodePort向外部暴露了多个宿主机的端口,外部可以部署负载均衡将这些地址配置进去。

默认情况下,服务会rr转发到可用的后端。如果希望保持会话(同一个 client 永远都转发到相同的 pod),可以把 service.spec.sessionAffinity 设置为 ClientIP。

1
2
3
4
5
6
7
8
9
10
iptables-save | grep 3306

iptables-save | grep KUBE-SERVICES

#iptables-save |grep KUBE-SVC-RVEVH2XMONK6VC5O
:KUBE-SVC-RVEVH2XMONK6VC5O - [0:0]
-A KUBE-SERVICES -d 10.10.70.95/32 -p tcp -m comment --comment "drds/mysql-read:mysql cluster IP" -m tcp --dport 3306 -j KUBE-SVC-RVEVH2XMONK6VC5O
-A KUBE-SVC-RVEVH2XMONK6VC5O -m comment --comment "drds/mysql-read:mysql" -m statistic --mode random --probability 0.33333333349 -j KUBE-SEP-XC4TZYIZFYB653VI
-A KUBE-SVC-RVEVH2XMONK6VC5O -m comment --comment "drds/mysql-read:mysql" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-MK4XPBZUIJGFXKED
-A KUBE-SVC-RVEVH2XMONK6VC5O -m comment --comment "drds/mysql-read:mysql" -j KUBE-SEP-AAYXWGQJBDHUJUQ3

看起来 service 是个完美的方案,可以解决服务访问的所有问题,但是 service 这个方案(iptables 模式)也有自己的缺点。

首先,如果转发的 pod 不能正常提供服务,它不会自动尝试另一个 pod,当然这个可以通过 readiness probes 来解决。每个 pod 都有一个健康检查的机制,当有 pod 健康状况有问题时,kube-proxy 会删除对应的转发规则。

另外,nodePort 类型的服务也无法添加 TLS 或者更复杂的报文路由机制。因为只做了NAT

NodePort 的一些问题

  • 首先endpoint回复不能走node 1给client,因为会被client reset(如果在node1上将src ip替换成node2的ip可能会路由不通)。回复包在 node1上要snat给node2
  • 经过snat后endpoint没法拿到client ip(slb之类是通过option带过来)
1
2
3
4
5
6
7
8
9
          client
\ ^
\ \
v \
node 1 <--- node 2
| ^ SNAT
| | --->
v |
endpoint

可以将 Service 的 spec.externalTrafficPolicy 字段设置为 local,这就保证了所有 Pod 通过 Service 收到请求之后,一定可以看到真正的、外部 client 的源地址。

而这个机制的实现原理也非常简单:这时候,一台宿主机上的 iptables 规则,会设置为只将 IP 包转发给运行在这台宿主机上的 Pod。所以这时候,Pod 就可以直接使用源地址将回复包发出,不需要事先进行 SNAT 了。这个流程,如下所示:

1
2
3
4
5
6
7
8
9
      client
^ / \
/ / \
/ v X
node 1 node 2
^ |
| |
| v
endpoint

当然,这也就意味着如果在一台宿主机上,没有任何一个被代理的 Pod 存在,比如上图中的 node 2,那么你使用 node 2 的 IP 地址访问这个 Service,就是无效的。此时,你的请求会直接被 DROP 掉。

Service和kube-proxy的工作原理

kube-proxy有两种主要的实现(userspace基本没有使用了):

  • [[iptables使用]]来做NAT以及负载均衡
  • ipvs来做NAT以及负载均衡

Service 是由 kube-proxy 组件通过监听 Pod 的变化事件,在宿主机上维护iptables规则或者ipvs规则。

Kube-proxy 主要监听两个对象,一个是 Service,一个是 Endpoint,监听他们启停。以及通过selector将他们绑定。

IPVS 是专门为LB设计的。它用hash table管理service,对service的增删查找都是*O(1)*的时间复杂度。不过IPVS内核模块没有SNAT功能,因此借用了iptables的SNAT功能。IPVS 针对报文做DNAT后,将连接信息保存在nf_conntrack中,iptables据此接力做SNAT。该模式是目前Kubernetes网络性能最好的选择。但是由于nf_conntrack的复杂性,带来了很大的性能损耗。

iptables 实现负载均衡的工作流程

如果kube-proxy不是用的ipvs模式,那么主要靠iptables来做DNAT和SNAT以及负载均衡

iptables+clusterIP工作流程:

  1. 集群内访问svc 10.10.35.224:3306 命中 kube-services iptables
  2. iptables 规则:KUBE-SEP-F4QDAAVSZYZMFXZQ 对应到 KUBE-SEP-F4QDAAVSZYZMFXZQ
  3. KUBE-SEP-F4QDAAVSZYZMFXZQ 指示 DNAT到 宿主机:192.168.0.83:10379(在内核中将包改写了ip port)
  4. 从svc description中可以看到这个endpoint的地址 192.168.0.83:10379(pod使用Host network)

image.png

在对应的宿主机上可以清楚地看到容器中的mysqld进程正好监听着 10379端口

1
2
3
4
5
6
7
8
[root@az1-drds-83 ~]# ss -lntp |grep 10379
LISTEN 0 128 :::10379 :::* users:(("mysqld",pid=17707,fd=18))
[root@az1-drds-83 ~]# ps auxff | grep 17707 -B2
root 13606 0.0 0.0 10720 3764 ? Sl 17:09 0:00 \_ containerd-shim -namespace moby -workdir /var/lib/containerd/io.containerd.runtime.v1.linux/moby/ead57b52b11902b9b5004db0b72abb060b56a1af7ee7ad7066bd09c946abcb97 -address /run/containerd/containerd.sock -containerd-binary /usr/bin/containerd -runtime-root /var/run/docker/runtime-runc

root 13624 0.0 0.0 103044 10424 ? Ss 17:09 0:00 | \_ python /entrypoint.py
root 14835 0.0 0.0 11768 1636 ? S 17:10 0:00 | \_ /bin/sh /u01/xcluster/bin/mysqld_safe --defaults-file=/home/mysql/my10379.cnf
alidb 17707 0.6 0.0 1269128 67452 ? Sl 17:10 0:25 | \_ /u01/xcluster_20200303/bin/mysqld --defaults-file=/home/mysql/my10379.cnf --basedir=/u01/xcluster_20200303 --datadir=/home/mysql/data10379/dbs10379 --plugin-dir=/u01/xcluster_20200303/lib/plugin --user=mysql --log-error=/home/mysql/data10379/mysql/master-error.log --open-files-limit=8192 --pid-file=/home/mysql/data10379/dbs10379/az1-drds-83.pid --socket=/home/mysql/data10379/tmp/mysql.sock --port=10379

对应的这个pod的description:

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
#kubectl describe pod apsaradbcluster010-cv6w
Name: apsaradbcluster010-cv6w
Namespace: default
Priority: 0
Node: az1-drds-83/192.168.0.83
Start Time: Thu, 10 Sep 2020 17:09:33 +0800
Labels: alisql.clusterName=apsaradbcluster010
alisql.pod_name=apsaradbcluster010-cv6w
alisql.pod_role=leader
Annotations: apsara.metric.pod_name: apsaradbcluster010-cv6w
Status: Running
IP: 192.168.0.83
IPs:
IP: 192.168.0.83
Controlled By: ApsaradbCluster/apsaradbcluster010
Containers:
engine:
Container ID: docker://ead57b52b11902b9b5004db0b72abb060b56a1af7ee7ad7066bd09c946abcb97
Image: reg.docker.alibaba-inc.com/apsaradb/alisqlcluster-engine:develop-20200910140415
Image ID: docker://sha256:7ad5cc53c87b34806eefec829d70f5f0192f4127c7ee4e867cb3da3bb6c2d709
Ports: 10379/TCP, 20383/TCP, 46846/TCP
Host Ports: 10379/TCP, 20383/TCP, 46846/TCP
State: Running
Started: Thu, 10 Sep 2020 17:09:35 +0800
Ready: True
Restart Count: 0
Environment:
ALISQL_POD_NAME: apsaradbcluster010-cv6w (v1:metadata.name)
ALISQL_POD_PORT: 10379
Mounts:
/dev/shm from devshm (rw)
/etc/localtime from etclocaltime (rw)
/home/mysql/data from data-dir (rw)
/var/run/secrets/kubernetes.io/serviceaccount from default-token-n2bmn (ro)
exporter:
Container ID: docker://b49865b7798f9036b431203d54994ac8fdfcadacb01a2ab4494b13b2681c482d
Image: reg.docker.alibaba-inc.com/apsaradb/alisqlcluster-exporter:latest
Image ID: docker://sha256:432cdd0a0e7c74c6eb66551b6f6af9e4013f60fb07a871445755f6577b44da19
Port: 47272/TCP
Host Port: 47272/TCP
Args:
--web.listen-address=:47272
--collect.binlog_size
--collect.engine_innodb_status
--collect.info_schema.innodb_metrics
--collect.info_schema.processlist
--collect.info_schema.tables
--collect.info_schema.tablestats
--collect.slave_hosts
State: Running
Started: Thu, 10 Sep 2020 17:09:35 +0800
Ready: True
Restart Count: 0
Environment:
ALISQL_POD_NAME: apsaradbcluster010-cv6w (v1:metadata.name)
DATA_SOURCE_NAME: root:@(127.0.0.1:10379)/
Mounts:
/dev/shm from devshm (rw)
/etc/localtime from etclocaltime (rw)
/home/mysql/data from data-dir (rw)
/var/run/secrets/kubernetes.io/serviceaccount from default-token-n2bmn (ro)

DNAT 规则的作用,就是在 PREROUTING 检查点之前,也就是在路由之前,将流入 IP 包的目的地址和端口,改成–to-destination 所指定的新的目的地址和端口。可以看到,这个目的地址和端口,正是被代理 Pod 的 IP 地址和端口。

哪些组件会修改iptables

image.png

ipvs 实现负载均衡的原理

ipvs模式下,kube-proxy会先创建虚拟网卡,kube-ipvs0下面的每个ip都对应着svc的一个clusterIP:

1
2
3
4
5
6
# ip addr
...
5: kube-ipvs0: <BROADCAST,NOARP> mtu 1500 qdisc noop state DOWN group default
link/ether de:29:17:2a:8d:79 brd ff:ff:ff:ff:ff:ff
inet 10.68.70.130/32 scope global kube-ipvs0
valid_lft forever preferred_lft forever

kube-ipvs0下面绑的这些ip就是在发包的时候让内核知道如果目标ip是这些地址的话,这些地址是自身的所以包不会发出去,而是给INPUT链,这样ipvs内核模块有机会改写包做完NAT后再发走。

ipvs会放置DNAT钩子在INPUT链上,因此必须要让内核识别 VIP 是本机的 IP。这样才会过INPUT 链,要不然就通过OUTPUT链出去了。k8s 通过kube-proxy将service cluster ip 绑定到虚拟网卡kube-ipvs0。

同时在路由表中增加一些ipvs 的路由条目:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# ip route show table local
local 10.68.0.1 dev kube-ipvs0 proto kernel scope host src 10.68.0.1
local 10.68.0.2 dev kube-ipvs0 proto kernel scope host src 10.68.0.2
local 10.68.70.130 dev kube-ipvs0 proto kernel scope host src 10.68.70.130 -- ipvs
broadcast 127.0.0.0 dev lo proto kernel scope link src 127.0.0.1
local 127.0.0.0/8 dev lo proto kernel scope host src 127.0.0.1
local 127.0.0.1 dev lo proto kernel scope host src 127.0.0.1
broadcast 127.255.255.255 dev lo proto kernel scope link src 127.0.0.1
broadcast 172.17.0.0 dev docker0 proto kernel scope link src 172.17.0.1
local 172.17.0.1 dev docker0 proto kernel scope host src 172.17.0.1
broadcast 172.17.255.255 dev docker0 proto kernel scope link src 172.17.0.1
local 172.20.185.192 dev tunl0 proto kernel scope host src 172.20.185.192
broadcast 172.20.185.192 dev tunl0 proto kernel scope link src 172.20.185.192
broadcast 172.26.128.0 dev eth0 proto kernel scope link src 172.26.137.117
local 172.26.137.117 dev eth0 proto kernel scope host src 172.26.137.117
broadcast 172.26.143.255 dev eth0 proto kernel scope link src 172.26.137.117

而接下来,kube-proxy 就会通过 Linux 的 IPVS 模块,为这个 IP 地址设置三个 IPVS 虚拟主机,并设置这三个虚拟主机之间使用轮询模式 (rr) 来作为负载均衡策略。我们可以通过 ipvsadm 查看到这个设置,如下所示:

1
2
3
4
5
ipvsadm -ln |grep 10.68.114.131 -A5
TCP 10.68.114.131:3306 rr
-> 172.20.120.143:3306 Masq 1 0 0
-> 172.20.185.209:3306 Masq 1 0 0
-> 172.20.248.143:3306 Masq 1 0 0

172.20.. 是后端真正pod的ip, 10.68.114.131 是cluster-ip.

完整的工作流程如下:

  1. 因为service cluster ip 绑定到虚拟网卡kube-ipvs0上,内核可以识别访问的 VIP 是本机的 IP.
  2. 数据包到达INPUT链.
  3. ipvs监听到达input链的数据包,比对数据包请求的服务是为集群服务,修改数据包的目标IP地址为对应pod的IP,然后将数据包发至POSTROUTING链.
  4. 数据包经过POSTROUTING链选路由后,将数据包通过tunl0网卡(calico网络模型)发送出去。从tunl0虚拟网卡获得源IP.
  5. 经过tunl0后进行ipip封包,丢到物理网络,路由到目标node(目标pod所在的node)
  6. 目标node进行ipip解包后给pod对应的网卡
  7. pod接收到请求之后,构建响应报文,改变源地址和目的地址,返回给客户端。

image.png

ipvs实际案例

ipvs负载均衡下一次完整的syn握手抓包。

宿主机上访问 curl clusterip+port 后因为这个ip绑定在kube-ipvs0上,本来是应该发出去的包(prerouting)但是内核认为这个包是访问自己,于是给INPUT链,接着被ipvs放置在INPUT中的DNAT钩子勾住,将dest ip根据负载均衡逻辑改成pod-ip,然后将数据包再发至POSTROUTING链。这时因为目标ip是POD-IP了,根据ip route 选择到出口网卡是tunl0。

可以看下内核中的路由规则:

1
2
3
4
5
# ip route get 10.68.70.130
local 10.68.70.130 dev lo src 10.68.70.130 //这条规则指示了clusterIP是发给自己的
cache <local>
# ip route get 172.20.185.217
172.20.185.217 via 172.26.137.117 dev tunl0 src 172.20.22.192 //这条规则指示clusterIP替换成POD IP后发给本地tunl0做ipip封包

于是cip变成了tunl0的IP,这个tunl0是ipip模式,于是将这个包打包成ipip,也就是外层sip、dip都是宿主机ip,再将这个包丢入到物理网络

网络收包到达内核后的处理流程如下,核心都是查路由表,出包也会查路由表(判断是否本机内部通信,或者外部通信的话需要选用哪个网卡)

补两张内核netfilter框架的图:

packet filtering in IPTables

image.png

完整版:

image.png

ipvs的一些分析

ipvs是一个内核态的四层负载均衡,支持NAT以及IPIP隧道模式,但LB和RS不能跨子网,IPIP性能次之,通过ipip隧道解决跨网段传输问题,因此能够支持跨子网。而NAT模式没有限制,这也是唯一一种支持端口映射的模式。

但是ipvs只有NAT(也就是DNAT),NAT也俗称三角模式,要求RS和LVS 在一个二层网络,并且LVS是RS的网关,这样回包一定会到网关,网关再次做SNAT,这样client看到SNAT后的src ip是LVS ip而不是RS-ip。默认实现不支持ful-NAT,所以像公有云厂商为了适应公有云场景基本都会定制实现ful-NAT模式的lvs。

我们不难猜想,由于Kubernetes Service需要使用端口映射功能,因此kube-proxy必然只能使用ipvs的NAT模式。

如下Masq表示MASQUERADE(也就是SNAT),跟iptables里面的 MASQUERADE 是一个意思

1
2
3
# ipvsadm -L -n  |grep 70.130 -A12
TCP 10.68.70.130:12380 rr
-> 172.20.185.217:9376 Masq 1 0 0

kuberletes对iptables的修改(图中黄色部分):

image.png

kube-proxy

在 Kubernetes v1.0 版本,代理完全在 userspace 实现。Kubernetes v1.1 版本新增了 iptables 代理模式,但并不是默认的运行模式。从 Kubernetes v1.2 起,默认使用 iptables 代理。在 Kubernetes v1.8.0-beta.0 中,添加了 ipvs 代理模式

kube-proxy相当于service的管控方,业务流量不会走到kube-proxy,业务流量的负载均衡都是由内核层面的iptables或者ipvs来分发。

kube-proxy的三种模式:

image.png

一直以来,基于 iptables 的 Service 实现,都是制约 Kubernetes 项目承载更多量级的 Pod 的主要障碍。

ipvs 就是用于解决在大量 Service 时,iptables 规则同步变得不可用的性能问题。与 iptables 比较像的是,ipvs 的实现虽然也基于 netfilter 的钩子函数,但是它却使用哈希表作为底层的数据结构并且工作在内核态,这也就是说 ipvs 在重定向流量和同步代理规则有着更好的性能。

除了能够提升性能之外,ipvs 也提供了多种类型的负载均衡算法,除了最常见的 Round-Robin 之外,还支持最小连接、目标哈希、最小延迟等算法,能够很好地提升负载均衡的效率。

而相比于 iptables,IPVS 在内核中的实现其实也是基于 Netfilter 的 NAT 模式,所以在转发这一层上,理论上 IPVS 并没有显著的性能提升。但是,IPVS 并不需要在宿主机上为每个 Pod 设置 iptables 规则,而是把对这些“规则”的处理放到了内核态,从而极大地降低了维护这些规则的代价,“将重要操作放入内核态”是提高性能的重要手段。

IPVS 模块只负责上述的负载均衡和代理功能。而一个完整的 Service 流程正常工作所需要的包过滤、SNAT 等操作,还是要靠 iptables 来实现。只不过,这些辅助性的 iptables 规则数量有限,也不会随着 Pod 数量的增加而增加。

ipvs 和 iptables 都是基于 Netfilter 实现的。

Kubernetes 中已经使用 ipvs 作为 kube-proxy 的默认代理模式。

1
/opt/kube/bin/kube-proxy --bind-address=172.26.137.117 --cluster-cidr=172.20.0.0/16 --hostname-override=172.26.137.117 --kubeconfig=/etc/kubernetes/kube-proxy.kubeconfig --logtostderr=true --proxy-mode=ipvs

image.png

为什么clusterIP不能ping通

集群内访问cluster ip(不能ping,只能cluster ip+port)就是在到达网卡之前被内核iptalbes做了dnat/snat, cluster IP是一个虚拟ip,可以针对具体的服务固定下来,这样服务后面的pod可以随便变化。

iptables模式的svc会ping不通clusterIP,可以看如下iptables和route(留意:–reject-with icmp-port-unreachable):

1
2
3
4
5
6
7
8
9
10
11
12
13
#ping 10.96.229.40
PING 10.96.229.40 (10.96.229.40) 56(84) bytes of data.
^C
--- 10.96.229.40 ping statistics ---
2 packets transmitted, 0 received, 100% packet loss, time 999ms


#iptables-save |grep 10.96.229.40
-A KUBE-SERVICES -d 10.96.229.40/32 -p tcp -m comment --comment "***-service:https has no endpoints" -m tcp --dport 8443 -j REJECT --reject-with icmp-port-unreachable

#ip route get 10.96.229.40
10.96.229.40 via 11.164.219.253 dev eth0 src 11.164.219.119
cache

准确来说如果用ipvs实现的clusterIP是可以ping通的:

  • 如果用iptables 来做转发是ping不通的,因为iptables里面这条规则只处理tcp包,reject了icmp
  • ipvs实现的clusterIP都能ping通
  • ipvs下的clusterIP ping通了也不是转发到pod,ipvs负载均衡只转发tcp协议的包
  • ipvs 的clusterIP在本地配置了route路由到回环网卡,这个包是lo网卡回复的

ipvs实现的clusterIP,在本地有添加路由到lo网卡

image.png

然后在本机抓包(到ipvs后端的pod上抓不到icmp包):

image.png

从上面可以看出显然ipvs只会转发tcp包到后端pod,所以icmp包不会通过ipvs转发到pod上,同时在本地回环网卡lo上抓到了进去的icmp包。因为本地添加了一条路由规则,目标clusterIP被指示发到lo网卡上,lo网卡回复了这个ping包,所以通了。

port-forward

port-forward后外部也能够像nodePort一样访问到,但是port-forward不适合大流量,一般用于管理端口,启动的时候port-forward会固定转发到一个具体的Pod上,也没有负载均衡的能力。

1
2
#在本机监听1080端口,并转发给后端的svc/nginx-ren(总是给发给svc中的一个pod)
kubectl port-forward --address 0.0.0.0 svc/nginx-ren 1080:80

kubectl looks up a Pod from the service information provided on the command line and forwards directly to a Pod rather than forwarding to the ClusterIP/Service port and allowing the cluster to load balance the service like regular service traffic.

The portforward.go Complete function is where kubectl portforward does the first look up for a pod from options via AttachablePodForObjectFn:

The AttachablePodForObjectFn is defined as attachablePodForObject in this interface, then here is the attachablePodForObject function.

To my (inexperienced) Go eyes, it appears the attachablePodForObject is the thing kubectl uses to look up a Pod to from a Service defined on the command line.

Then from there on everything deals with filling in the Pod specific PortForwardOptions (which doesn’t include a service) and is passed to the kubernetes API.

Service 和 DNS 的关系

Service 和 Pod 都会被分配对应的 DNS A 记录(从域名解析 IP 的记录)。

对于 ClusterIP 模式的 Service 来说(比如我们上面的例子),它的 A 记录的格式是:..svc.cluster.local。当你访问这条 A 记录的时候,它解析到的就是该 Service 的 VIP 地址。

而对于指定了 clusterIP=None 的 Headless Service 来说,它的 A 记录的格式也是:..svc.cluster.local。但是,当你访问这条 A 记录的时候,它返回的是所有被代理的 Pod 的 IP 地址的集合。当然,如果你的客户端没办法解析这个集合的话,它可能会只会拿到第一个 Pod 的 IP 地址。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#kubectl get pod -l app=mysql-r -o wide
NAME READY STATUS RESTARTS IP NODE
mysql-r-0 2/2 Running 0 172.20.120.143 172.26.137.118
mysql-r-1 2/2 Running 4 172.20.248.143 172.26.137.116
mysql-r-2 2/2 Running 0 172.20.185.209 172.26.137.117

/ # nslookup mysql-r-1.mysql-r
Server: 10.68.0.2
Address 1: 10.68.0.2 kube-dns.kube-system.svc.cluster.local

Name: mysql-r-1.mysql-r
Address 1: 172.20.248.143 mysql-r-1.mysql-r.default.svc.cluster.local
/ #
/ # nslookup mysql-r-2.mysql-r
Server: 10.68.0.2
Address 1: 10.68.0.2 kube-dns.kube-system.svc.cluster.local

Name: mysql-r-2.mysql-r
Address 1: 172.20.185.209 mysql-r-2.mysql-r.default.svc.cluster.local

#如果service是headless(也就是明确指定了 clusterIP: None)
/ # nslookup mysql-r
Server: 10.68.0.2
Address 1: 10.68.0.2 kube-dns.kube-system.svc.cluster.local

Name: mysql-r
Address 1: 172.20.185.209 mysql-r-2.mysql-r.default.svc.cluster.local
Address 2: 172.20.248.143 mysql-r-1.mysql-r.default.svc.cluster.local
Address 3: 172.20.120.143 mysql-r-0.mysql-r.default.svc.cluster.local

#如果service 没有指定 clusterIP: None,也就是会分配一个clusterIP给集群
/ # nslookup mysql-r
Server: 10.68.0.2
Address 1: 10.68.0.2 kube-dns.kube-system.svc.cluster.local

Name: mysql-r
Address 1: 10.68.90.172 mysql-r.default.svc.cluster.local

不是每个pod都会向DNS注册,只有:

  • StatefulSet中的POD会向dns注册,因为他们要保证顺序行
  • POD显式指定了hostname和subdomain,说明要靠hostname/subdomain来解析
  • Headless Service代理的POD也会注册

Ingress

kube-proxy 只能路由 Kubernetes 集群内部的流量,而我们知道 Kubernetes 集群的 Pod 位于 CNI 创建的外网络中,集群外部是无法直接与其通信的,因此 Kubernetes 中创建了 ingress 这个资源对象,它由位于 Kubernetes 边缘节点(这样的节点可以是很多个也可以是一组)的 Ingress controller 驱动,负责管理南北向流量,Ingress 必须对接各种 Ingress Controller 才能使用,比如 nginx ingress controller、traefik。Ingress 只适用于 HTTP 流量,使用方式也很简单,只能对 service、port、HTTP 路径等有限字段匹配来路由流量,这导致它无法路由如 MySQL、Redis 和各种私有 RPC 等 TCP 流量。要想直接路由南北向的流量,只能使用 Service 的 LoadBalancer 或 NodePort,前者需要云厂商支持,后者需要进行额外的端口管理。有些 Ingress controller 支持暴露 TCP 和 UDP 服务,但是只能使用 Service 来暴露,Ingress 本身是不支持的,例如 nginx ingress controller,服务暴露的端口是通过创建 ConfigMap 的方式来配置的。

Ingress是授权入站连接到达集群服务的规则集合。 你可以给Ingress配置提供外部可访问的URL、负载均衡、SSL、基于名称的虚拟主机等。 用户通过POST Ingress资源到API server的方式来请求ingress。

1
2
3
4
5
 internet
|
[ Ingress ]
--|-----|--
[ Services ]

可以将 Ingress 配置为服务提供外部可访问的 URL、负载均衡流量、终止 SSL/TLS,以及提供基于名称的虚拟主机等能力。 Ingress 控制器 通常负责通过负载均衡器来实现 Ingress,尽管它也可以配置边缘路由器或其他前端来帮助处理流量。

Ingress 不会公开任意端口或协议。 将 HTTP 和 HTTPS 以外的服务公开到 Internet 时,通常使用 Service.Type=NodePort 或 Service.Type=LoadBalancer 类型的服务。

Ingress 其实不是Service的一个类型,但是它可以作用于多个Service,作为集群内部服务的入口。Ingress 能做许多不同的事,比如根据不同的路由,将请求转发到不同的Service上等等。

image.png

Ingress 对象,其实就是 Kubernetes 项目对“反向代理”的一种抽象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: cafe-ingress
spec:
tls:
- hosts:
- cafe.example.com
secretName: cafe-secret
rules:
- host: cafe.example.com
http:
paths:
- path: /tea --入口url路径
backend:
serviceName: tea-svc --对应的service
servicePort: 80
- path: /coffee
backend:
serviceName: coffee-svc
servicePort: 80

在实际的使用中,你只需要从社区里选择一个具体的 Ingress Controller,把它部署在 Kubernetes 集群里即可。然后,这个 Ingress Controller 会根据你定义的 Ingress 对象,提供对应的代理能力。

目前,业界常用的各种反向代理项目,比如 Nginx、HAProxy、Envoy、Traefik 等,都已经为 Kubernetes 专门维护了对应的 Ingress Controller。

一个 Ingress Controller 可以根据 Ingress 对象和被代理后端 Service 的变化,来自动进行更新的 Nginx 负载均衡器。

对service未来的一些探索

eBPF(extended Berkeley Packet Filter)和网络

eBPF 最早出现在 3.18 内核中,此后原来的 BPF 就被称为 “经典” BPF(classic BPF, cBPF),cBPF 现在基本已经废弃了。很多人知道 cBPF 是因为它是 tcpdump 的包过滤语言。现在,Linux 内核只运行 eBPF,内核会将加载的 cBPF 字节码 透明地转换成 eBPF 再执行。如无特殊说明,本文中所说的 BPF 都是泛指 BPF 技术。

2015年eBPF 添加了一个新 fast path:XDP,XDP 是 eXpress DataPath 的缩写,支持在网卡驱动中运行 eBPF 代码,而无需将包送 到复杂的协议栈进行处理,因此处理代价很小,速度极快。

BPF 当时用于 tcpdump,在内核中尽量前面的位置抓包,它不会 crash 内核;

bcc 是 tracing frontend for eBPF。

内核添加了一个新 socket 类型 AF_XDP。它提供的能力是:在零拷贝( zero-copy)的前提下将包从网卡驱动送到用户空间。

AF_XDP 提供的能力与 DPDK 有点类似,不过:

  • DPDK 需要重写网卡驱动,需要额外维护用户空间的驱动代码。
  • AF_XDP 在复用内核网卡驱动的情况下,能达到与 DPDK 一样的性能。

而且由于复用了内核基础设施,所有的网络管理工具还都是可以用的,因此非常方便, 而 DPDK 这种 bypass 内核的方案导致绝大大部分现有工具都用不了了。

由于所有这些操作都是发生在 XDP 层的,因此它称为 AF_XDP。插入到这里的 BPF 代码 能直接将包送到 socket。

Facebook 公布了生产环境 XDP+eBPF 使用案例(DDoS & LB)

  • 用 XDP/eBPF 重写了原来基于 IPVS 的 L4LB,性能 10x。
  • eBPF 经受住了严苛的考验:从 2017 开始,每个进入 facebook.com 的包,都是经过了 XDP & eBPF 处理的。

Cilium 1.6 发布 第一次支持完全干掉基于 iptables 的 kube-proxy,全部功能基于 eBPF。Cilium 1.8 支持基于 XDP 的 Service 负载均衡和 host network policies。

传统的 kube-proxy 处理 Kubernetes Service 时,包在内核中的 转发路径是怎样的?如下图所示:

image.png

步骤:

  1. 网卡收到一个包(通过 DMA 放到 ring-buffer)。
  2. 包经过 XDP hook 点。
  3. 内核给包分配内存,此时才有了大家熟悉的 skb(包的内核结构体表示),然后 送到内核协议栈。
  4. 包经过 GRO 处理,对分片包进行重组。
  5. 包进入 tc(traffic control)的 ingress hook。接下来,所有橙色的框都是 Netfilter 处理点。
  6. Netfilter:在 PREROUTING hook 点处理 raw table 里的 iptables 规则。
  7. 包经过内核的连接跟踪(conntrack)模块。
  8. Netfilter:在 PREROUTING hook 点处理 mangle table 的 iptables 规则。
  9. Netfilter:在 PREROUTING hook 点处理 nat table 的 iptables 规则。
  10. 进行路由判断(FIB:Forwarding Information Base,路由条目的内核表示,译者注) 。接下来又是四个 Netfilter 处理点。
  11. Netfilter:在 FORWARD hook 点处理 mangle table 里的iptables 规则。
  12. Netfilter:在 FORWARD hook 点处理 filter table 里的iptables 规则。
  13. Netfilter:在 POSTROUTING hook 点处理 mangle table 里的iptables 规则。
  14. Netfilter:在 POSTROUTING hook 点处理 nat table 里的iptables 规则。
  15. 包到达 TC egress hook 点,会进行出方向(egress)的判断,例如判断这个包是到本 地设备,还是到主机外。
  16. 对大包进行分片。根据 step 15 判断的结果,这个包接下来可能会:发送到一个本机 veth 设备,或者一个本机 service endpoint, 或者,如果目的 IP 是主机外,就通过网卡发出去。

Cilium 如何处理POD之间的流量(东西向流量)

image.png

如上图所示,Socket 层的 BPF 程序主要处理 Cilium 节点的东西向流量(E-W)。

  • 将 Service 的 IP:Port 映射到具体的 backend pods,并做负载均衡。
  • 当应用发起 connect、sendmsg、recvmsg 等请求(系统调用)时,拦截这些请求, 并根据请求的IP:Port 映射到后端 pod,直接发送过去。反向进行相反的变换。

这里实现的好处:性能更高。

  • 不需要包级别(packet leve)的地址转换(NAT)。在系统调用时,还没有创建包,因此性能更高。
  • 省去了 kube-proxy 路径中的很多中间节点(intermediate node hops) 可以看出,应用对这种拦截和重定向是无感知的(符合 Kubernetes Service 的设计)。

Cilium处理外部流量(南北向流量)

image.png

集群外来的流量到达 node 时,由 XDP 和 tc 层的 BPF 程序进行处理, 它们做的事情与 socket 层的差不多,将 Service 的 IP:Port 映射到后端的 PodIP:Port,如果 backend pod 不在本 node,就通过网络再发出去。发出去的流程我们 在前面 Cilium eBPF 包转发路径 讲过了。

这里 BPF 做的事情:执行 DNAT。这个功能可以在 XDP 层做,也可以在 TC 层做,但 在XDP 层代价更小,性能也更高。

总结起来,Cilium的核心理念就是:

  • 将东西向流量放在离 socket 层尽量近的地方做。
  • 将南北向流量放在离驱动(XDP 和 tc)层尽量近的地方做。

性能比较

测试环境:两台物理节点,一个发包,一个收包,收到的包做 Service loadbalancing 转发给后端 Pods。

image.png

可以看出:

  • Cilium XDP eBPF 模式能处理接收到的全部 10Mpps(packets per second)。
  • Cilium tc eBPF 模式能处理 3.5Mpps。
  • kube-proxy iptables 只能处理 2.3Mpps,因为它的 hook 点在收发包路径上更后面的位置。
  • kube-proxy ipvs 模式这里表现更差,它相比 iptables 的优势要在 backend 数量很多的时候才能体现出来。

cpu:

  • XDP 性能最好,是因为 XDP BPF 在驱动层执行,不需要将包 push 到内核协议栈。
  • kube-proxy 不管是 iptables 还是 ipvs 模式,都在处理软中断(softirq)上消耗了大量 CPU。

标签和选择算符

标签(Labels) 是附加到 Kubernetes 对象(比如 Pods)上的键值对。 标签旨在用于指定对用户有意义且相关的对象的标识属性,但不直接对核心系统有语义含义。 标签可以用于组织和选择对象的子集。标签可以在创建时附加到对象,随后可以随时添加和修改。 每个对象都可以定义一组键/值标签。每个键对于给定对象必须是唯一的。

标签选择符

selector要和template中的labels一致

1
2
3
4
5
6
7
8
9
10
spec:
serviceName: "nginx-test"
replicas: 2
selector:
matchLabels:
app: ren
template:
metadata:
labels:
app: web

selector就是要找别人的label和自己匹配的,label是给别人来寻找的。如下case,svc中的 Selector: app=ren 是表示这个svc要绑定到app=ren的deployment/statefulset上.

被 selector 选中的 Pod,就称为 Service 的 Endpoints

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[root@poc117 mysql-cluster]# kubectl describe svc nginx-ren 
Name: nginx-ren
Namespace: default
Labels: app=web
Annotations: <none>
Selector: app=ren
Type: NodePort
IP: 10.68.34.173
Port: <unset> 8080/TCP
TargetPort: 80/TCP
NodePort: <unset> 30080/TCP
Endpoints: 172.20.22.226:80,172.20.56.169:80
Session Affinity: None
External Traffic Policy: Cluster
Events: <none>
[root@poc117 mysql-cluster]# kubectl get svc -l app=web
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx-ren NodePort 10.68.34.173 <none> 8080:30080/TCP 13m
[root@poc117 mysql-cluster]# kubectl get svc -l app=ren
No resources found in default namespace.
[root@poc117 mysql-cluster]# kubectl get svc -l app=web
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx-ren NodePort 10.68.34.173 <none> 8080:30080/TCP 14m

service mesh

  • Kubernetes 的本质是应用的生命周期管理,具体来说就是部署和管理(扩缩容、自动恢复、发布)。
  • Kubernetes 为微服务提供了可扩展、高弹性的部署和管理平台。
  • Service Mesh 的基础是透明代理,通过 sidecar proxy 拦截到微服务间流量后再通过控制平面配置管理微服务的行为。
  • Service Mesh 将流量管理从 Kubernetes 中解耦,Service Mesh 内部的流量无需 kube-proxy 组件的支持,通过为更接近微服务应用层的抽象,管理服务间的流量、安全性和可观察性。
  • xDS 定义了 Service Mesh 配置的协议标准。
  • Service Mesh 是对 Kubernetes 中的 service 更上层的抽象,它的下一步是 serverless。

Sidecar 注入及流量劫持步骤概述

下面是从 Sidecar 注入、Pod 启动到 Sidecar proxy 拦截流量及 Envoy 处理路由的步骤概览。

1. Kubernetes 通过 Admission Controller 自动注入,或者用户使用 istioctl 命令手动注入 sidecar 容器。

2. 应用 YAML 配置部署应用,此时 Kubernetes API server 接收到的服务创建配置文件中已经包含了 Init 容器及 sidecar proxy。

3. 在 sidecar proxy 容器和应用容器启动之前,首先运行 Init 容器,Init 容器用于设置 iptables(Istio 中默认的流量拦截方式,还可以使用 BPF、IPVS 等方式) 将进入 pod 的流量劫持到 Envoy sidecar proxy。所有 TCP 流量(Envoy 目前只支持 TCP 流量)将被 sidecar 劫持,其他协议的流量将按原来的目的地请求。

4. 启动 Pod 中的 Envoy sidecar proxy 和应用程序容器。这一步的过程请参考通过管理接口获取完整配置。

5. 不论是进入还是从 Pod 发出的 TCP 请求都会被 iptables 劫持,inbound 流量被劫持后经 Inbound Handler 处理后转交给应用程序容器处理,outbound 流量被 iptables 劫持后转交给 Outbound Handler 处理,并确定转发的 upstream 和 Endpoint。

6. Sidecar proxy 请求 Pilot 使用 xDS 协议同步 Envoy 配置,其中包括 LDS、EDS、CDS 等,不过为了保证更新的顺序,Envoy 会直接使用 ADS 向 Pilot 请求配置更新

参考资料

https://imroc.io/posts/kubernetes/troubleshooting-with-kubernetes-network/ Kubernetes 网络疑难杂症排查方法

https://blog.csdn.net/qq_36183935/article/details/90734936 kube-proxy ipvs模式详解

http://arthurchiao.art/blog/ebpf-and-k8s-zh/ 大规模微服务利器:eBPF 与 Kubernetes

http://arthurchiao.art/blog/cilium-life-of-a-packet-pod-to-service-zh/ Life of a Packet in Cilium:实地探索 Pod-to-Service 转发路径及 BPF 处理逻辑

http://arthurchiao.art/blog/understanding-ebpf-datapath-in-cilium-zh/ 深入理解 Cilium 的 eBPF 收发包路径(datapath)(KubeCon, 2019)

https://jiayu0x.com/2014/12/02/iptables-essential-summary/

kubernetes 多集群管理

发表于 2020-01-21 | 分类于 docker

kubernetes 多集群管理

kubectl 管理多集群

指定config配置文件的方式访问不同的集群

1
kubectl --kubeconfig=/etc/kubernetes/admin.conf get nodes

一个kubectl可以管理多个集群,主要是 ~/.kube/config 里面的配置,比如:

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
clusters:
- cluster:
certificate-authority: /root/k8s-cluster.ca
server: https://192.168.0.80:6443
name: context-az1
- cluster:
certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCQl0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
server: https://192.168.0.97:6443
name: context-az3

- context:
cluster: context-az1
namespace: default
user: az1-admin
name: az1
- context:
cluster: context-az3
namespace: default
user: az3-read
name: az3
current-context: az3 //当前使用的集群

kind: Config
preferences: {}
users:
- name: az1-admin
user:
client-certificate: /root/k8s.crt //key放在配置文件中
client-key: /root/k8s.key
- name: az3-read
user:
client-certificate-data: LS0tLS1CRUQ0FURS0tLS0tCg==
client-key-data: LS0tLS1CRUdJThuL2VPM0YxSWpEcXBQdmRNbUdiU2c9PQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo=

多个集群中切换的话 : kubectl config use-context az3

快速合并两个cluster

简单来讲就是把两个集群的 .kube/config 文件合并,注意context、cluster name别重复了。

1
2
3
4
5
6
7
8
9
10
11
12
13
# 必须提前保证两个config文件中的cluster、context名字不能重复
export KUBECONFIG=~/.kube/config:~/someotherconfig
kubectl config view --flatten

#激活这个上下文
kubectl config use-context az1

#查看所有context
kubectl config get-contexts
CURRENT NAME CLUSTER AUTHINFO NAMESPACE
az1 context-az1 az1-admin default
* az2 kubernetes kubernetes-admin
az3 context-az3 az3-read default

背后的原理类似于这个流程:

1
2
3
4
5
6
7
8
# 添加集群 集群地址上一步有获取 ,需要指定ca文件,上一步有获取 
kubectl config set-cluster cluster-az1 --server https://192.168.146.150:6444 --certificate-authority=/usr/program/k8s-certs/k8s-cluster.ca

# 添加用户 需要指定crt,key文件,上一步有获取
kubectl config set-credentials az1-admin --client-certificate=/usr/program/k8s-certs/k8s.crt --client-key=/usr/program/k8s-certs/k8s.key

# 指定一个上下文的名字,我这里叫做 az1,随便你叫啥 关联刚才的用户
kubectl config set-context az1 --cluster=context-az1 --namespace=default --user=az1-admin

参考资料

http://coreos.com/blog/kubectl-tips-and-tricks

https://stackoverflow.com/questions/46184125/how-to-merge-kubectl-config-file-with-kube-config

Linux 内存问题汇总

发表于 2020-01-15 | 分类于 Memory

Linux 内存问题汇总

本系列有如下几篇

[Linux 内存问题汇总](/2020/01/15/Linux 内存问题汇总/)

Linux内存–PageCache

Linux内存–管理和碎片

Linux内存–HugePage

Linux内存–零拷贝

内存使用观察

# free -m
         total       used       free     shared    buffers     cached
Mem:          7515       1115       6400          0        189        492
-/+ buffers/cache:        432       7082
Swap:            0          0          0

其中,cached 列表示当前的页缓存(Page Cache)占用量,buffers 列表示当前的块缓存(buffer cache)占用量。用一句话来解释:Page Cache 用于缓存文件的页数据,buffer cache 用于缓存块设备(如磁盘)的块数据。页是逻辑上的概念,因此 Page Cache 是与文件系统同级的;块是物理上的概念,因此 buffer cache 是与块设备驱动程序同级的。

image.png

上图中-/+ buffers/cache: -是指userd去掉buffers/cached后真正使用掉的内存; +是指free加上buffers和cached后真正free的内存大小。

free

free是从 /proc/meminfo 读取数据然后展示:

buff/cache = Buffers + Cached + SReclaimable

Buffers + Cached + SwapCached = Active(file) + Inactive(file) + Shmem + SwapCached

1
2
3
4
5
6
7
8
9
10
[root@az1-drds-79 ~]# cat /proc/meminfo |egrep -i "buff|cach|SReclai"
Buffers: 817764 kB
Cached: 76629252 kB
SwapCached: 0 kB
SReclaimable: 7202264 kB
[root@az1-drds-79 ~]# free -k
total used free shared buffers cached
Mem: 97267672 95522336 1745336 0 817764 76629352
-/+ buffers/cache: 18075220 79192452
Swap: 0 0 0

在内核启动时,物理页面将加入到伙伴系统 (Buddy System)中,用户申请内存时分配,释放时回收。为了照顾慢速设备及兼顾多种 workload,Linux 将页面类型分为匿名页(Anon Page)和文件页 (Page Cache),及 swapness,使用 Page Cache 缓存文件 (慢速设备),通过 swap cache 和 swapness 交由用户根据负载特征决定内存不足时回收二者的比例。

cached过高回收

系统内存大体可分为三块,应用程序使用内存、系统Cache 使用内存(包括page cache、buffer,内核slab 等)和Free 内存。

  • 应用程序使用内存:应用使用都是虚拟内存,应用申请内存时只是分配了地址空间,并未真正分配出物理内存,等到应用真正访问内存时会触发内核的缺页中断,这时候才真正的分配出物理内存,映射到用户的地址空间,因此应用使用内存是不需要连续的,内核有机制将非连续的物理映射到连续的进程地址空间中(mmu),缺页中断申请的物理内存,内核优先给低阶碎内存。

  • 系统Cache 使用内存:使用的也是虚拟内存,申请机制与应用程序相同。

  • Free 内存,未被使用的物理内存,这部分内存以4k 页的形式被管理在内核伙伴算法结构中,相邻的2^n 个物理页会被伙伴算法组织到一起,形成一块连续物理内存,所谓的阶内存就是这里的n (0<= n <=10),高阶内存指的就是一块连续的物理内存,在OSS 的场景中,如果3阶内存个数比较小的情况下,如果系统有吞吐burst 就会触发Drop cache 情况。

cache回收:
echo 1/2/3 >/proc/sys/vm/drop_caches

查看回收后:

cat /proc/meminfo

手动回收系统Cache、Buffer,这个文件可以设置的值分别为1、2、3。它们所表示的含义为:

echo 1 > /proc/sys/vm/drop_caches:表示清除pagecache。

echo 2 > /proc/sys/vm/drop_caches:表示清除回收slab分配器中的对象(包括目录项缓存和inode缓存)。slab分配器是内核中管理内存的一种机制,其中很多缓存数据实现都是用的pagecache。

echo 3 > /proc/sys/vm/drop_caches:表示清除pagecache和slab分配器中的缓存对象。

cached无法回收

可能是正打开的文件占用了cached,比如 vim 打开了一个巨大的文件;比如 mount的 tmpfs; 比如 journald 日志等等

通过vmtouch 查看

# vmtouch -v test.x86_64.rpm 
test.x86_64.rpm
[OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO] 10988/10988

           Files: 1
     Directories: 0
  Resident Pages: 10988/10988  42M/42M  100%
         Elapsed: 0.000594 seconds

# ls -lh test.x86_64.rpm
-rw-r--r-- 1 root root 43M 10月  8 14:11 test.x86_64.rpm

如上,表示整个文件 test.x86_64.rpm 都被cached了,回收的话执行:

vmtouch -e test.x86_64.rpm // 或者: echo 3 >/proc/sys/vm/drop_cached

遍历某个目录下的所有文件被cached了多少

# vmtouch -vt /var/log/journal/
/var/log/journal/20190829214900434421844640356160/user-1000@ad408d9cb9d94f9f93f2c2396c26b542-000000000011ba49-00059979e0926f43.journal
[OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO] 4096/4096
/var/log/journal/20190829214900434421844640356160/system@782ec314565e436b900454c59655247c-0000000000152f41-00059b2c88eb4344.journal
[OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO] 14336/14336
/var/log/journal/20190829214900434421844640356160/user-1000@ad408d9cb9d94f9f93f2c2396c26b542-00000000000f2181-000598335fcd492f.journal
[OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO] 4096/4096
/var/log/journal/20190829214900434421844640356160/system@782ec314565e436b900454c59655247c-0000000000129aea-000599e83996db80.journal
[OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO] 14336/14336
/var/log/journal/20190829214900434421844640356160/user-1000@ad408d9cb9d94f9f93f2c2396c26b542-000000000009f171-000595a722ead670.journal
…………
           Files: 48
 Directories: 2
 Touched Pages: 468992 (1G)
 Elapsed: 13.274 seconds

vmtouch 清理目录

如下脚本传入一个指定目录(业务方来确认哪些目录占用 pagecache 较大, 且可以清理),然后用vmtouch 遍历排序最大的几个清理掉,可能会造成业务的卡度

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
#!/bin/bash
#
#echo "*/2 * * * * root bash /root/cron/os_pagecache_clean.sh -n 5 -e > /root/cron/os_pagecache_clean.out 2>&1" > /etc/cron.d/os_pagecache_clean

function usage(){
cat << EOF
usage:
$0 -n topN [-l|-e]
option:
-l list top n redis_dir
-e list and evict top n redis_dir
-n top n
EOF
exit 1
}

while getopts "n:leh" opt; do
case $opt in
l) list=1 ;;
e) list=1 && evict=1 ;;
n) n=${OPTARG} ;;
h) usage ;;
esac
done

[[ -z $n ]] && usage
[[ -z $list && -z $evict ]] && usage

# list must = 1
cd /root && ls | while read dirname ; do
page=$(vmtouch $dirname | grep "Resident Pages")
echo -e "$dirname\t$page"
done | tr "/" " " | sort -nr -k4 | head -n $n | awk '{print $1,$6}' | while read dirname cache_size; do
echo -e "$dirname\t$cache_size"
[[ $evict == 1 ]] && vmtouch -e $dirname
done

消失的内存

OS刚启动后就报内存不够了,什么都没跑就500G没了,cached和buffer基本没用,纯粹就是used占用高,top按内存排序没有超过0.5%的进程

参考: https://cloud.tencent.com/developer/article/1087455

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
[aliyun@uos15 18:40 /u02/backup_15/leo/benchmark/run]
$free -g
total used free shared buff/cache available
Mem: 503 501 1 0 0 1
Swap: 15 12 3

$cat /proc/meminfo
MemTotal: 528031512 kB
MemFree: 1469632 kB
MemAvailable: 0 kB
VmallocTotal: 135290290112 kB
VmallocUsed: 0 kB
VmallocChunk: 0 kB
Percpu: 81920 kB
AnonHugePages: 950272 kB
ShmemHugePages: 0 kB
ShmemPmdMapped: 0 kB
HugePages_Total: 252557 ----- 预分配太多,一个2M,加起来刚好500G了
HugePages_Free: 252557
HugePages_Rsvd: 0
HugePages_Surp: 0
Hugepagesize: 2048 kB
Hugetlb: 517236736 kB

以下是一台正常的机器对比:
Percpu: 41856 kB
AnonHugePages: 11442176 kB
ShmemHugePages: 0 kB
ShmemPmdMapped: 0 kB
HugePages_Total: 0 ----没有做预分配
HugePages_Free: 0
HugePages_Rsvd: 0
HugePages_Surp: 0
Hugepagesize: 2048 kB
Hugetlb: 0 kB

[aliyun@uos16 18:43 /home/aliyun]
$free -g
total used free shared buff/cache available
Mem: 503 20 481 0 1 480
Swap: 15 0 15

对有问题的机器执行:
# echo 1024 > /proc/sys/vm/nr_hugepages
可以看到内存恢复正常了
root@uos15:/u02/backup_15/leo/benchmark/run# free -g
total used free shared buff/cache available
Mem: 503 10 492 0 0 490
Swap: 15 12 3
root@uos15:/u02/backup_15/leo/benchmark/run# cat /proc/meminfo
MemTotal: 528031512 kB
MemFree: 516106832 kB
MemAvailable: 514454408 kB
VmallocTotal: 135290290112 kB
VmallocUsed: 0 kB
VmallocChunk: 0 kB
Percpu: 81920 kB
AnonHugePages: 313344 kB
ShmemHugePages: 0 kB
ShmemPmdMapped: 0 kB
HugePages_Total: 1024
HugePages_Free: 1024
HugePages_Rsvd: 0
HugePages_Surp: 0
Hugepagesize: 2048 kB
Hugetlb: 2097152 kB

定制内存

物理内存700多G,要求OS只能用512G:

1
2
3
4
5
6
7
8
9
10
11
12
24条32G的内存条,总内存768G
# dmidecode -t memory |grep "Size: 32 GB"
Size: 32 GB
…………
Size: 32 GB
Size: 32 GB
root@uos15:/etc# dmidecode -t memory |grep "Size: 32 GB" | wc -l
24

# cat /boot/grub/grub.cfg |grep 512
linux /vmlinuz-4.19.0-arm64-server root=UUID=dbc68010-8c36-40bf-b794-271e59ff5727 ro splash quiet console=tty video=VGA-1:1280x1024@60 mem=512G DEEPIN_GFXMODE=$DEEPIN_GFXMODE
linux /vmlinuz-4.19.0-arm64-server root=UUID=dbc68010-8c36-40bf-b794-271e59ff5727 ro splash quiet console=tty video=VGA-1:1280x1024@60 mem=512G DEEPIN_GFXMODE=$DEEPIN_GFXMODE

​

参考资料

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

https://cloud.tencent.com/developer/article/1087455

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

kubernetes volume and storage

发表于 2020-01-13 | 分类于 docker

kubernetes volume and storage

通常部署应用需要一些永久存储,kubernetes提供了PersistentVolume (PV,实际存储)、PersistentVolumeClaim (PVC,Pod访问PV的接口)、StorageClass来支持。

它为 PersistentVolume 定义了 StorageClass 名称 为 manual,StorageClass 名称用来将 PersistentVolumeClaim 请求绑定到该 PersistentVolume。

PVC是用来描述希望使用什么样的或者说是满足什么条件的存储,它的全称是Persistent Volume Claim,也就是持久化存储声明。开发人员使用这个来描述该容器需要一个什么存储。

PVC就相当于是容器和PV之间的一个接口,使用人员只需要和PVC打交道即可。另外你可能也会想到如果当前环境中没有合适的PV和我的PVC绑定,那么我创建的POD不就失败了么?的确是这样的,不过如果发现这个问题,那么就赶快创建一个合适的PV,那么这时候持久化存储循环控制器会不断的检查PVC和PV,当发现有合适的可以绑定之后它会自动给你绑定上然后被挂起的POD就会自动启动,而不需要你重建POD。

创建 PersistentVolumeClaim 之后,Kubernetes 控制平面将查找满足申领要求的 PersistentVolume。 如果控制平面找到具有相同 StorageClass 的适当的 PersistentVolume,则将 PersistentVolumeClaim 绑定到该 PersistentVolume 上。PVC的大小可以小于PV的大小。

一旦 PV 和 PVC 绑定后,PersistentVolumeClaim 绑定是排他性的,不管它们是如何绑定的。 PVC 跟 PV 绑定是一对一的映射。

注意:PV必须先于POD创建,而且只能是网络存储不能属于任何Node,虽然它支持HostPath类型但由于你不知道POD会被调度到哪个Node上,所以你要定义HostPath类型的PV就要保证所有节点都要有HostPath中指定的路径。

PV 和PVC的关系

PVC就会和PV进行绑定,绑定的一些原则:

  1. PV和PVC中的spec关键字段要匹配,比如存储(storage)大小。
  2. PV和PVC中的storageClassName字段必须一致,这个后面再说。
  3. 上面的labels中的标签只是增加一些描述,对于PVC和PV的绑定没有关系

PV的accessModes:支持三种类型

  • ReadWriteMany 多路读写,卷能被集群多个节点挂载并读写
  • ReadWriteOnce 单路读写,卷只能被单一集群节点挂载读写
  • ReadOnlyMany 多路只读,卷能被多个集群节点挂载且只能读

PV状态:

  • Available – 资源尚未被claim使用
  • Bound – 卷已经被绑定到claim了
  • Released – claim被删除,卷处于释放状态,但未被集群回收。
  • Failed – 卷自动回收失败

PV回收Recycling—pv可以设置三种回收策略:保留(Retain),回收(Recycle)和删除(Delete)。

  • 保留(Retain): 当删除与之绑定的PVC时候,这个PV被标记为released(PVC与PV解绑但还没有执行回收策略)且之前的数据依然保存在该PV上,但是该PV不可用,需要手动来处理这些数据并删除该PV。
  • 删除(Delete):当删除与之绑定的PVC时候
  • 回收(Recycle):这个在1.14版本中以及被废弃,取而代之的是推荐使用动态存储供给策略,它的功能是当删除与该PV关联的PVC时,自动删除该PV中的所有数据

更改 PersistentVolume 的回收策略

1
2
#kubectl patch pv wordpress-data -p '{"spec":{"persistentVolumeReclaimPolicy":"Delete"}}'
persistentvolume/wordpress-data patched

本地卷(hostPath)也就是LPV不支持动态供给的方式,延迟绑定,就是为了综合考虑所有因素再进行POD调度。其根本原因是动态供给是先调度POD到节点,然后动态创建PV以及绑定PVC最后运行POD;而LPV是先创建与某一节点关联的PV,然后在调度的时候综合考虑各种因素而且要包括PV在哪个节点,然后再进行调度,到达该节点后在进行PVC的绑定。也就说动态供给不考虑节点,LPV必须考虑节点。所以这两种机制有冲突导致无法在动态供给策略下使用LPV。换句话说动态供给是PV跟着POD走,而LPV是POD跟着PV走。

PV 和 PVC

创建 pv controller 和pvc

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
#cat mysql-pv.yaml 
apiVersion: v1
kind: PersistentVolume
metadata:
name: simple-pv-volume
labels:
type: local
spec:
storageClassName: manual
capacity:
storage: 20Gi
accessModes:
- ReadWriteOnce
hostPath:
path: "/mnt/simple"
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pv-claim
spec:
storageClassName: manual
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 20Gi

StorageClass

PV是运维人员来创建的,开发操作PVC,可是大规模集群中可能会有很多PV,如果这些PV都需要运维手动来处理这也是一件很繁琐的事情,所以就有了动态供给概念,也就是Dynamic Provisioning。而我们上面的创建的PV都是静态供给方式,也就是Static Provisioning。而动态供给的关键就是StorageClass,它的作用就是创建PV模板。

创建StorageClass里面需要定义PV属性比如存储类型、大小等;另外创建这种PV需要用到存储插件。最终效果是,用户提交PVC,里面指定存储类型,如果符合我们定义的StorageClass,则会为其自动创建PV并进行绑定。

简单可以把storageClass理解为名字,只是这个名字可以重复,然后pvc和pv之间通过storageClass来绑定。

如下case中两个pv和两个pvc的绑定就是通过storageClass(一致)来实现的(当然pvc要求的大小也必须和pv一致):

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
#kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
mariadb-pv 8Gi RWO Retain Bound default/data-wordpress-mariadb-0 db 3m54s
wordpress-data 10Gi RWO Retain Bound default/wordpress wordpress 3m54s

[root@az3-k8s-11 15:35 /root/charts/bitnami/wordpress]
#kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
data-wordpress-mariadb-0 Bound mariadb-pv 8Gi RWO db 4m21s
wordpress Bound wordpress-data 10Gi RWO wordpress 4m21s

#cat create-pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: mariadb-pv
spec:
capacity:
storage: 8Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: db
hostPath:
path: /mnt/mariadb-pv

---

apiVersion: v1
kind: PersistentVolume
metadata:
name: wordpress-data
spec:
capacity:
storage: 10Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: wordpress
hostPath:
path: /mnt/wordpress-pv

----对应 pvc的定义参数:
persistence:
enabled: true
storageClass: "wordpress"
accessMode: ReadWriteOnce
size: 10Gi

persistence:
enabled: true
mountPath: /bitnami/mariadb
storageClass: "db"
annotations: {}
accessModes:
- ReadWriteOnce
size: 8Gi

定义StorageClass

1
2
3
4
5
6
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
name: local-storage
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer

定义PVC

1
2
3
4
5
6
7
8
9
10
11
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: local-claim
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
storageClassName: local-storage

delete pv 卡住

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#kubectl describe pv wordpress-pv
Name: wordpress-pv
Labels: <none>
Annotations: pv.kubernetes.io/bound-by-controller: yes
Finalizers: [kubernetes.io/pv-protection] --- 问题在finalizers
StorageClass:
Status: Terminating (lasts 18h)
Claim: default/wordpress
Reclaim Policy: Retain
Access Modes: RWO
VolumeMode: Filesystem
Capacity: 10Gi
Node Affinity: <none>
Message:
Source:
Type: NFS (an NFS mount that lasts the lifetime of a pod)
Server: 192.168.0.111
Path: /mnt/wordpress-pv
ReadOnly: false
Events: <none>

先执行后就能自动删除了:
kubectl patch pv wordpress-pv -p '{"metadata":{"finalizers": []}}' --type=merge

kubernetes 集群部署

发表于 2020-01-12 | 分类于 docker

kubernetes 集群部署

部署

系统参数修改

docker部署

kubeadm install

https://www.kubernetes.org.cn/4256.html

https://github.com/opsnull/follow-me-install-kubernetes-cluster

镜像源被墙,可以用阿里云镜像源

1
2
3
4
5
6
7
8
9
10
11
12
13
# 配置源
cat <<EOF > /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64
enabled=1
gpgcheck=1
repo_gpgcheck=1
gpgkey=https://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg https://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg
EOF

# 安装
yum install -y kubelet kubeadm kubectl ipvsadm

初始化集群

多网卡情况下有必要指定网卡:–apiserver-advertise-address=192.168.0.80

1
2
3
4
5
6
7
8
9
10
11
12
13
# 使用本地 image repository
kubeadm init --kubernetes-version=1.18.0 --apiserver-advertise-address=192.168.0.110 --image-repository registry:5000/registry.aliyuncs.com/google_containers --service-cidr=10.10.0.0/16 --pod-network-cidr=10.122.0.0/16

# 给api-server 指定外网地址,在服务器有内网、外网多个ip的时候适用
kubeadm init --control-plane-endpoint 外网-ip:6443 --image-repository=registry:5000/registry.aliyuncs.com/google_containers --kubernetes-version=v1.21.0 --pod-network-cidr=172.16.0.0/16
#--apiserver-advertise-address=30.1.1.1,设置 apiserver 的 IP 地址,对于多网卡服务器来说很重要(比如 VirtualBox 虚拟机就用了两块网卡),可以指定 apiserver 在哪个网卡上对外提供服务。

# node join command
#kubeadm token create --print-join-command
kubeadm join 192.168.0.110:6443 --token 1042rl.b4qn9iuz6xv1ri7b --discovery-token-ca-cert-hash sha256:341a4bcfde9668077ef29211c2a151fe6e9334eea8955f645698706b3bf47a49

## 查看集群配置
kubectl get configmap -n kube-system kubeadm-config -o yaml

将一个node设置为不可调度,隔离出来,比如master 默认是不可调度的

1
2
kubectl cordon <node-name>
kubectl uncordon <node-name>

kubectl 管理多集群

一个kubectl可以管理多个集群,主要是 ~/.kube/config 里面的配置,比如:

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
clusters:
- cluster:
certificate-authority: /root/k8s-cluster.ca
server: https://192.168.0.80:6443
name: context-az1
- cluster:
certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCQl0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
server: https://192.168.0.97:6443
name: context-az3

- context:
cluster: context-az1
namespace: default
user: az1-admin
name: az1
- context:
cluster: context-az3
namespace: default
user: az3-read
name: az3
current-context: az3 //当前使用的集群

kind: Config
preferences: {}
users:
- name: az1-admin
user:
client-certificate: /root/k8s.crt //key放在配置文件中
client-key: /root/k8s.key
- name: az3-read
user:
client-certificate-data: LS0tLS1CRUQ0FURS0tLS0tCg==
client-key-data: LS0tLS1CRUdJThuL2VPM0YxSWpEcXBQdmRNbUdiU2c9PQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo=

多个集群中切换的话 : kubectl config use-context az3

快速合并两个cluster

简单来讲就是把两个集群的 .kube/config 文件合并,注意context、cluster name别重复了。

1
2
3
4
5
6
7
8
9
10
11
12
13
# 必须提前保证两个config文件中的cluster、context名字不能重复
export KUBECONFIG=~/.kube/config:~/someotherconfig
kubectl config view --flatten

#激活这个上下文
kubectl config use-context az1

#查看所有context
kubectl config get-contexts
CURRENT NAME CLUSTER AUTHINFO NAMESPACE
az1 context-az1 az1-admin default
* az2 kubernetes kubernetes-admin
az3 context-az3 az3-read default

背后的原理类似于这个流程:

1
2
3
4
5
6
7
8
# 添加集群 集群地址上一步有获取 ,需要指定ca文件,上一步有获取 
kubectl config set-cluster cluster-az1 --server https://192.168.146.150:6444 --certificate-authority=/usr/program/k8s-certs/k8s-cluster.ca

# 添加用户 需要指定crt,key文件,上一步有获取
kubectl config set-credentials az1-admin --client-certificate=/usr/program/k8s-certs/k8s.crt --client-key=/usr/program/k8s-certs/k8s.key

# 指定一个上下文的名字,我这里叫做 az1,随便你叫啥 关联刚才的用户
kubectl config set-context az1 --cluster=context-az1 --namespace=default --user=az1-admin

apiserver高可用

默认只有一个apiserver,可以考虑用haproxy和keepalive来做一组apiserver的负载均衡:

1
2
3
4
5
6
docker run -d --name kube-haproxy \
-v /etc/haproxy:/usr/local/etc/haproxy:ro \
-p 8443:8443 \
-p 1080:1080 \
--restart always \
haproxy:1.7.8-alpine

haproxy配置

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
#cat /etc/haproxy/haproxy.cfg 
global
log 127.0.0.1 local0 err
maxconn 50000
uid 99
gid 99
#daemon
nbproc 1
pidfile haproxy.pid

defaults
mode http
log 127.0.0.1 local0 err
maxconn 50000
retries 3
timeout connect 5s
timeout client 30s
timeout server 30s
timeout check 2s

listen admin_stats
mode http
bind 0.0.0.0:1080
log 127.0.0.1 local0 err
stats refresh 30s
stats uri /haproxy-status
stats realm Haproxy\ Statistics
stats auth will:will
stats hide-version
stats admin if TRUE

frontend k8s-https
bind 0.0.0.0:8443
mode tcp
#maxconn 50000
default_backend k8s-https

backend k8s-https
mode tcp
balance roundrobin
server lab1 192.168.1.81:6443 weight 1 maxconn 1000 check inter 2000 rise 2 fall 3
server lab2 192.168.1.82:6443 weight 1 maxconn 1000 check inter 2000 rise 2 fall 3
server lab3 192.168.1.83:6443 weight 1 maxconn 1000 check inter 2000 rise 2 fall 3

网络

1
2
3
4
kubectl apply -f https://docs.projectcalico.org/manifests/calico.yaml

#或者老版本的calico
curl https://docs.projectcalico.org/v3.15/manifests/calico.yaml -o calico.yaml

默认calico用的是ipip封包(这个性能跟原生网络差多少有待验证,本质也是overlay网络,比flannel那种要好很多吗?)

在所有node节点都在一个二层网络时候,flannel提供hostgw实现,避免vxlan实现的udp封装开销,估计是目前最高效的;calico也针对L3 Fabric,推出了IPinIP的选项,利用了GRE隧道封装;因此这些插件都能适合很多实际应用场景。

Service cluster IP尽可在集群内部访问,外部请求需要通过NodePort、LoadBalance或者Ingress来访问

网络插件由 containernetworking-plugins rpm包来提供,一般里面会有flannel、vlan等,安装在 /usr/libexec/cni/ 下(老版本没有带calico)

kubelet启动参数会配置 KUBELET_NETWORK_ARGS=–network-plugin=cni –cni-conf-dir=/etc/cni/net.d –cni-bin-dir=/usr/libexec/cni

kubectl 启动容器

1
2
kubectl run -i --tty busybox --image=registry:5000/busybox -- sh
kubectl attach busybox -c busybox -i -t

dashboard

1
2
3
4
kubectl apply -f  https://raw.githubusercontent.com/kubernetes/dashboard/v2.0.0-rc7/aio/deploy/recommented.yaml

#暴露 dashboard 服务端口 (recommended中如果已经定义了 30000这个nodeport,所以这个命令不需要了)
kubectl port-forward -n kubernetes-dashboard svc/kubernetes-dashboard 30000:443 --address 0.0.0.0

dashboard login token:

1
2
#kubectl describe secrets -n kubernetes-dashboard   | grep token | awk 'NR==3{print $2}'
eyJhbGciOiJSUzI1NiIsImtpZCI6IndRc0hiMkdpWHRwN1FObTcyeUdhOHI0eUxYLTlvODd2U0NBcU1GY0t1Sk0ifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlcm5ldGVzLWRhc2hib2FyZCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJkZWZhdWx0LXRva2VuLXRia3o5Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQubmFtZSI6ImRlZmF1bHQiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiIwYzM2MzBhOS0xMjBjLTRhNmYtYjM0ZS0zM2JhMTE1OWU1OWMiLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6a3ViZXJuZXRlcy1kYXNoYm9hcmQ6ZGVmYXVsdCJ9.SP4JEw0kGDmyxrtcUC3HALq99Xr99E-tie5fk4R8odLJBAYN6HxEx80RbTSnkeSMJNApbtwXBLrp4I_w48kTkr93HJFM-oxie3RVLK_mEpZBF2JcfMk6qhfz4RjPiqmG6mGyW47mmY4kQ4fgpYSmZYR4LPJmVMw5W2zo5CGhZT8rKtgmi5_ROmYpWcd2ZUORaexePgesjjKwY19bLEXFOwdsqekwEvj1_zaJhKAehF_dBdgW9foFXkbXOX0xAC0QNnKUwKPanuFOVZDg1fhyV-eyi6c9-KoTYqZMJTqZyIzscIwruIRw0oauJypcdgi7ykxAubMQ4sWEyyFafSEYWg

dashboard 显示为空的话(留意报错信息,一般是用户权限,重新授权即可)

1
2
kubectl delete clusterrolebinding kubernetes-dashboard
kubectl create clusterrolebinding kubernetes-dashboard --clusterrole=cluster-admin --serviceaccount=kube-system:kubernetes-dashboard --user="system:serviceaccount:kubernetes-dashboard:default"

其中:system:serviceaccount:kubernetes-dashboard:default 来自于报错信息中的用户名

默认dashboard login很快expired,可以设置不过期:

1
2
3
4
5
6
7
8
9
$ kubectl -n kubernetes-dashboard edit deployments kubernetes-dashboard
...
spec:
containers:
- args:
- --auto-generate-certificates
- --token-ttl=0 //增加这行表示不expire

--enable-skip-login //增加这行表示不需要token 就能login,不推荐

kubectl proxy –address 0.0.0.0 –accept-hosts ‘.*’

node管理调度

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//如何优雅删除node
kubectl drain my-node # 对 my-node 节点进行清空操作,为节点维护做准备
kubectl drain ky4 --ignore-daemonsets --delete-local-data # 驱逐pod
kubectl delete node ky4 # 删除node

kubectl cordon my-node # 标记 my-node 节点为不可调度
kubectl uncordon my-node # 标记 my-node 节点为可以调度
kubectl top node my-node # 显示给定节点的度量值
kubectl cluster-info # 显示主控节点和服务的地址
kubectl cluster-info dump # 将当前集群状态转储到标准输出
kubectl cluster-info dump --output-directory=/path/to/cluster-state # 将当前集群状态输出到 /path/to/cluster-state

# 如果已存在具有指定键和效果的污点,则替换其值为指定值
kubectl taint nodes foo dedicated=special-user:NoSchedule
kubectl taint nodes poc65 node-role.kubernetes.io/master:NoSchedule-

地址

这些字段的用法取决于你的云服务商或者物理机配置。

  • HostName:由节点的内核设置。可以通过 kubelet 的 --hostname-override 参数覆盖。
  • ExternalIP:通常是节点的可外部路由(从集群外可访问)的 IP 地址。
  • InternalIP:通常是节点的仅可在集群内部路由的 IP 地址。

状况

1
2
3
4
5
# kubectl get node -o wide
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
172.26.137.114 Ready master 6d1h v1.19.0 172.26.137.114 <none> CentOS Linux 7 (Core) 3.10.0-957.21.3.el7.x86_64 docker://19.3.8
172.26.137.115 Ready node 6d1h v1.19.0 172.26.137.115 <none> CentOS Linux 7 (Core) 3.10.0-957.21.3.el7.x86_64 docker://19.3.8
172.26.137.116 Ready,SchedulingDisabled node 6d1h v1.19.0 172.26.137.116 <none> CentOS Linux 7 (Core) 3.10.0-957.21.3.el7.x86_64 docker://19.3.8

如果 Ready 条件处于 Unknown 或者 False 状态的时间超过了 pod-eviction-timeout 值, (一个传递给 kube-controller-manager 的参数), 节点上的所有 Pod 都会被节点控制器计划删除。默认的逐出超时时长为 5 分钟。 某些情况下,当节点不可达时,API 服务器不能和其上的 kubelet 通信。 删除 Pod 的决定不能传达给 kubelet,直到它重新建立和 API 服务器的连接为止。 与此同时,被计划删除的 Pod 可能会继续在游离的节点上运行。

node cidr 缺失

flannel pod 运行正常,pod无法创建,检查flannel日志发现该node cidr缺失

1
2
3
4
I0818 08:06:38.951132       1 main.go:733] Defaulting external v6 address to interface address (<nil>)
I0818 08:06:38.951231 1 vxlan.go:137] VXLAN config: VNI=1 Port=0 GBP=false Learning=false DirectRouting=false
E0818 08:06:38.951550 1 main.go:325] Error registering network: failed to acquire lease: node "ky3" pod cidr not assigned
I0818 08:06:38.951604 1 main.go:439] Stopping shutdownHandler...

正常来说describe node会看到如下的cidr信息

1
2
3
 Kube-Proxy Version:         v1.15.8-beta.0
PodCIDR: 172.19.1.0/24
Non-terminated Pods: (3 in total)

可以手工给node添加cidr

1
kubectl patch node ky3 -p '{"spec":{"podCIDR":"172.19.3.0/24"}}'

prometheus

1
2
3
git clone https://github.com/coreos/kube-prometheus.git
kubectl apply -f manifests/setup
kubectl apply -f manifests/

暴露grafana端口:

1
kubectl port-forward --address 0.0.0.0 svc/grafana -n monitoring 3000:3000

部署应用

DRDS deployment

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
apiVersion: v1
kind: Namespace
metadata:
name: drds

---

apiVersion: apps/v1
kind: Deployment
metadata:
name: drds-deployment
namespace: drds
labels:
app: drds-server
spec:
# 创建2个nginx容器
replicas: 3
selector:
matchLabels:
app: drds-server
template:
metadata:
labels:
app: drds-server
spec:
containers:
- name: drds-server
image: registry:5000/drds-image:v5_wisp_5.4.5-15940932
ports:
- containerPort: 8507
- containerPort: 8607
env:
- name: diamond_server_port
value: "8100"
- name: diamond_server_list
value: "192.168.0.79,192.168.0.82"
- name: drds_server_id
value: "1"

DRDS Service

每个 drds 容器会通过8507提供服务,service通过3306来为一组8507做负载均衡,这个service的3306是在cluster-ip上,外部无法访问

1
2
3
4
5
6
7
8
9
10
11
12
apiVersion: v1
kind: Service
metadata:
name: drds-service
namespace: drds
spec:
selector:
app: drds-server
ports:
- protocol: TCP
port: 3306
targetPort: 8507

通过node port来访问 drds service(同时会有负载均衡):

1
kubectl port-forward --address 0.0.0.0 svc/drds-service -n drds 3306:3306

部署mysql statefulset应用

drds-pv-mysql-0 后面的mysql 会用来做存储,下面用到了三个mysql(需要三个pvc)

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
#cat mysql-deployment.yaml 
apiVersion: v1
kind: Service
metadata:
name: mysql
spec:
ports:
- port: 3306
selector:
app: mysql
clusterIP: None
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: mysql
spec:
selector:
matchLabels:
app: mysql
strategy:
type: Recreate
template:
metadata:
labels:
app: mysql
spec:
containers:
- image: mysql:5.7
name: mysql
env:
# Use secret in real usage
- name: MYSQL_ROOT_PASSWORD
value: "123456"
ports:
- containerPort: 3306
name: mysql
volumeMounts:
- name: mysql-persistent-storage
mountPath: /var/lib/mysql
volumes:
- name: mysql-persistent-storage
persistentVolumeClaim:
claimName: pv-claim

清理:

1
2
3
kubectl delete deployment,svc mysql
kubectl delete pvc mysql-pv-claim
kubectl delete pv mysql-pv-volume

查看所有pod ip以及node ip:

1
kubectl get pods -o wide

配置 Pod 使用 ConfigMap

ConfigMap 允许你将配置文件与镜像文件分离,以使容器化的应用程序具有可移植性。

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
# cat mysql-configmap.yaml  //mysql配置文件放入: configmap
apiVersion: v1
kind: ConfigMap
metadata:
name: mysql
labels:
app: mysql
data:
master.cnf: |
# Apply this config only on the master.
[mysqld]
log-bin

mysqld.cnf: |
[mysqld]
pid-file = /var/run/mysqld/mysqld.pid
socket = /var/run/mysqld/mysqld.sock
datadir = /var/lib/mysql
#log-error = /var/log/mysql/error.log
# By default we only accept connections from localhost
#bind-address = 127.0.0.1
# Disabling symbolic-links is recommended to prevent assorted security risks
symbolic-links=0
sql_mode='STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION'
# 慢查询阈值,查询时间超过阈值时写入到慢日志中
long_query_time = 2
innodb_buffer_pool_size = 257M


slave.cnf: |
# Apply this config only on slaves.
[mysqld]
super-read-only

786 26/08/20 15:27:00 kubectl create configmap game-config-env-file --from-env-file=configure-pod-container/configmap/game-env-file.properties
787 26/08/20 15:28:10 kubectl get configmap -n kube-system kubeadm-config -o yaml
788 26/08/20 15:28:11 kubectl get configmap game-config-env-file -o yaml

将mysql root密码放入secret并查看 secret密码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# cat mysql-secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: mysql-root-password
type: Opaque
data:
password: MTIz

# echo -n '123' | base64 //生成密码编码
# kubectl get secret mysql-root-password -o jsonpath='{.data.password}' | base64 --decode -

或者创建一个新的 secret:
kubectl create secret generic my-secret --from-literal=password="Password"

在mysql容器中使用以上configmap中的参数:

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
spec:
volumes:
- name: conf
emptyDir: {}
- name: myconf
emptyDir: {}
- name: config-map
configMap:
name: mysql
initContainers:
- name: init-mysql
image: mysql:5.7
command:
- bash
- "-c"
- |
set -ex
# Generate mysql server-id from pod ordinal index.
[[ `hostname` =~ -([0-9]+)$ ]] || exit 1
ordinal=${BASH_REMATCH[1]}
echo [mysqld] > /mnt/conf.d/server-id.cnf
# Add an offset to avoid reserved server-id=0 value.
echo server-id=$((100 + $ordinal)) >> /mnt/conf.d/server-id.cnf
#echo "innodb_buffer_pool_size=512m" > /mnt/rds.cnf
# Copy appropriate conf.d files from config-map to emptyDir.
#if [[ $ordinal -eq 0 ]]; then
cp /mnt/config-map/master.cnf /mnt/conf.d/
cp /mnt/config-map/mysqld.cnf /mnt/mysql.conf.d/
#else
# cp /mnt/config-map/slave.cnf /mnt/conf.d/
#fi
volumeMounts:
- name: conf
mountPath: /mnt/conf.d
- name: myconf
mountPath: /mnt/mysql.conf.d
- name: config-map
mountPath: /mnt/config-map
containers:
- name: mysql
image: mysql:5.7
env:
#- name: MYSQL_ALLOW_EMPTY_PASSWORD
# value: "1"
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-root-password
key: password

通过挂载方式进入到容器里的 Secret,一旦其对应的 Etcd 里的数据被更新,这些 Volume 里的文件内容,同样也会被更新。其实,这是 kubelet 组件在定时维护这些 Volume。

集群会自动创建一个 default-token-**** 的secret,然后所有pod都会自动将这个 secret通过 Porjected Volume挂载到容器,也叫 ServiceAccountToken,是一种特殊的Secret

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
    Environment:    <none>
Mounts:
/var/run/secrets/kubernetes.io/serviceaccount from default-token-ncgdl (ro)
Conditions:
Type Status
Initialized True
Ready True
ContainersReady True
PodScheduled True
Volumes:
default-token-ncgdl:
Type: Secret (a volume populated by a Secret)
SecretName: default-token-ncgdl
Optional: false
QoS Class: BestEffort

apply create操作

先 kubectl create,再 replace 的操作,我们称为命令式配置文件操作

kubectl apply 命令才是“声明式 API”

kubectl replace 的执行过程,是使用新的 YAML 文件中的 API 对象,替换原有的 API 对象;

而 kubectl apply,则是执行了一个对原有 API 对象的 PATCH 操作。

kubectl set image 和 kubectl edit 也是对已有 API 对象的修改

kube-apiserver 在响应命令式请求(比如,kubectl replace)的时候,一次只能处理一个写请求,否则会有产生冲突的可能。而对于声明式请求(比如,kubectl apply),一次能处理多个写操作,并且具备 Merge 能力

声明式 API,相当于对外界所有操作(并发接收)串行merge,才是 Kubernetes 项目编排能力“赖以生存”的核心所在

如何使用控制器模式,同 Kubernetes 里 API 对象的“增、删、改、查”进行协作,进而完成用户业务逻辑的编写过程。

label

给多个节点加标签

1
2
3
4
kubectl label  --overwrite=true nodes 10.0.0.172 10.0.1.192 10.0.2.48 topology.kubernetes.io/region=cn-hangzhou

//查看
kubectl get nodes --show-labels

helm

Helm 是 Kubernetes 的包管理器。包管理器类似于我们在 Ubuntu 中使用的apt、Centos中使用的yum 或者Python中的 pip 一样,能快速查找、下载和安装软件包。Helm 由客户端组件 helm 和服务端组件 Tiller 组成, 能够将一组K8S资源打包统一管理, 是查找、共享和使用为Kubernetes构建的软件的最佳方式。

建立local repo index:

1
helm repo index [DIR] [flags]

仓库只能index 到 helm package 发布后的tgz包,意义不大。每次index后需要 helm repo update

然后可以启动一个http服务:

1
nohup python -m SimpleHTTPServer 8089 &

将local repo加入到仓库:

1
2
3
4
5
 helm repo add local http://127.0.0.1:8089

# helm repo list
NAME URL
local http://127.0.0.1:8089

install chart:

1
2
3
4
5
6
7
8
//helm3 默认不自动创建namespace,不带参数就报没有 ame 的namespace错误
helm install -name wordpress -n test --create-namespace .

helm list -n test

{{ .Release.Name }} 这种是helm内部自带的值,都是一些内建的变量,所有人都可以访问

image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" 这种是我们从values.yaml文件中获取或者从命令行中获取的值。

quote是一个模板方法,可以将输入的参数添加双引号

模板片段

之前我们看到有个文件叫做_helpers.tpl,我们介绍是说存储模板片段的地方。

模板片段其实也可以在文件中定义,但是为了更好管理,可以在_helpers.tpl中定义,使用时直接调用即可。

自动补全

kubernetes自动补全:

1
2
3
source <(kubectl completion bash) 

echo "source <(kubectl completion bash)" >> ~/.bashrc

helm自动补全:

1
2
cd ~
helm completion bash > .helmrc && echo "source .helmrc" >> .bashrc && source .bashrc

两者都需要依赖 auto-completion,所以得先:

1
2
# yum install -y bash-completion
# source /usr/share/bash-completion/bash_completion

kubectl -s polarx-test-ackk8s-atp-3826.adbgw.alibabacloud.test exec -it bushu016polarx282bc7216f-5161 bash

启动时间排序

1
2
532  [2021-08-24 18:37:19] kubectl get po --sort-by=.status.startTime -ndrds
533 [2021-08-24 18:37:41] kubectl get pods --sort-by=.metadata.creationTimestamp -ndrds

kubeadm

初始化集群的时候第一看kubelet能否起来(cgroup配置),第二就是看kubelet静态起pod,kubelet参数指定yaml目录,然后kubelet拉起这个目录下的所有yaml。

kubeadm启动集群就是如此。kubeadm生成证书、etcd.yaml等yaml、然后拉起kubelet,kubelet拉起etcd、apiserver等pod,kubeadm init 的时候主要是在轮询等待apiserver的起来。

可以通过kubelet –v 256来看详细日志,kubeadm本身所做的事情并不多,所以日志没有太多的信息,主要是等待轮询apiserver的拉起。

Kubeadm config

Init 可以指定仓库以及版本

1
kubeadm init --image-repository=registry:5000/registry.aliyuncs.com/google_containers --kubernetes-version=v1.14.6  --pod-network-cidr=10.244.0.0/16

查看并修改配置

1
2
3
4
5
6
7
sudo kubeadm config view > kubeadm-config.yaml
edit kubeadm-config.yaml and replace k8s.gcr.io with your repo
sudo kubeadm upgrade apply --config kubeadm-config.yaml

kubeadm config images pull --config="/root/kubeadm-config.yaml"

kubectl get cm -n kube-system kubeadm-config -o yaml

pod镜像拉取不到的话可以在kebelet启动参数中写死pod镜像(pod_infra_container_image)

1
2
#cat /usr/lib/systemd/system/kubelet.service.d/10-kubeadm.conf
ExecStart=/usr/bin/kubelet $KUBELET_KUBECONFIG_ARGS $KUBELET_CONFIG_ARGS $KUBELET_KUBEADM_ARGS $KUBELET_EXTRA_ARGS --pod_infra_container_image=registry:5000/registry.aliyuncs.com/google_containers/pause:3.1

构建离线镜像库

1
2
3
kubeadm config images list >1.24.list

cat 1.24.list | awk -F / '{ print $0 " " $3}' > 1.24.aarch.list

cni 报x509: certificate signed by unknown authority

一个集群下反复部署calico/flannel插件后,在 /etc/cni/net.d/ 下会有cni 网络配置文件残留,导致 flannel 创建容器网络的时候报证书错误。其实这不只是证书错误,还可能报其它cni配置错误,总之这是因为 10-calico.conflist 不符合 flannel要求所导致的。

1
2
3
4
5
# find /etc/cni/net.d/
/etc/cni/net.d/
/etc/cni/net.d/calico-kubeconfig
/etc/cni/net.d/10-calico.conflist //默认读取了这个配置文件,不符合flannel
/etc/cni/net.d/10-flannel.conflist

因为calico 排在 flannel前面,所以即使用flannel配置文件也是用的 10-calico.conflist。每次 kubeadm reset 的时候是不会去做 cni 的reset 的:

1
2
3
4
[reset] Deleting files: [/etc/kubernetes/admin.conf /etc/kubernetes/kubelet.conf /etc/kubernetes/bootstrap-kubelet.conf /etc/kubernetes/controller-manager.conf /etc/kubernetes/scheduler.conf]
[reset] Deleting contents of stateful directories: [/var/lib/kubelet /var/lib/dockershim /var/run/kubernetes /var/lib/cni]

The reset process does not clean CNI configuration. To do so, you must remove /etc/cni/net.d

kubernetes API 案例

用kubeadm部署kubernetes集群,会生成如下证书:

1
2
3
#ls /etc/kubernetes/pki/
apiserver-etcd-client.crt apiserver-kubelet-client.crt apiserver.crt ca.crt etcd front-proxy-ca.key front-proxy-client.key sa.pub
apiserver-etcd-client.key apiserver-kubelet-client.key apiserver.key ca.key front-proxy-ca.crt front-proxy-client.crt sa.key

curl访问api必须提供证书

1
curl --cacert /etc/kubernetes/pki/ca.crt --cert /etc/kubernetes/pki/apiserver-kubelet-client.crt --key /etc/kubernetes/pki/apiserver-kubelet-client.key https://ip:6443/apis/apps/v1/deployments

/etc/kubernetes/pki/ca.crt —- CA机构

由CA机构签发:/etc/kubernetes/pki/apiserver-kubelet-client.crt

Image

获取default namespace下的deployment

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# JWT_TOKEN_DEFAULT_DEFAULT=$(kubectl get secrets \
$(kubectl get serviceaccounts/default -o jsonpath='{.secrets[0].name}') \
-o jsonpath='{.data.token}' | base64 --decode)

#curl --cacert /etc/kubernetes/pki/ca.crt --cert /etc/kubernetes/pki/apiserver-kubelet-client.crt --key /etc/kubernetes/pki/apiserver-kubelet-client.key https://11.158.239.200:6443/apis/apps/v1/namespaces/default/deployments --header "Authorization: Bearer $JWT_TOKEN_DEFAULT_DEFAULT"
{
"kind": "DeploymentList",
"apiVersion": "apps/v1",
"metadata": {
"resourceVersion": "1233307"
},
"items": [
{
"metadata": {
"name": "nginx-deployment",

//列出default namespace下所有的pod
#curl --cacert /etc/kubernetes/pki/ca.crt --cert /etc/kubernetes/pki/apiserver-kubelet-client.crt --key /etc/kubernetes/pki/apiserver-kubelet-client.key https://11.158.239.200:6443/api/v1/namespaces/default/pods --header "Authorization: Bearer $JWT_TOKEN_DEFAULT_DEFAULT"

//对应的kubectl生成的curl命令
curl --cacert /etc/kubernetes/pki/ca.crt --cert /etc/kubernetes/pki/apiserver-kubelet-client.crt --key /etc/kubernetes/pki/apiserver-kubelet-client.key -v -XGET -H "Accept: application/json;as=Table;v=v1;g=meta.k8s.io,application/json;as=Table;v=v1beta1;g=meta.k8s.io,application/json" -H "User-Agent: kubectl/v1.23.3 (linux/arm64) kubernetes/816c97a" 'https://11.158.239.200:6443/api/v1/namespaces/default/pods?limit=500'

对应地可以通过 kubectl -v 256 get pods 来看kubectl的处理过程,以及具体访问的api、参数、返回结果等。实际kubectl最终也是通过libcurl来访问的这些api。这样也不用对api-server抓包分析了。

或者将kube api-server 代理成普通http服务

# Make Kubernetes API available on localhost:8080
# to bypass the auth step in subsequent queries:
$ kubectl proxy –port=8080

然后

curl http://localhost:8080/api/v1/namespaces

Image

抓包

用curl调用kubernetes api-server来调试,需要抓包,先在执行curl的服务器上配置环境变量

1
export SSLKEYLOGFILE=/root/ssllog/apiserver-ssl.log

然后执行tcpdump对api-server的6443端口抓包,然后将/root/ssllog/apiserver-ssl.log和抓包文件下载到本地,wireshark打开抓包文件,同时配置tls。

以下是个完整case(技巧指定curl的本地端口为12345,然后tcpdump只抓12345,所得的请求、response结果都会解密–如果抓api-server的6443则只能看到请求被解密)

1
2
3
curl --local-port 12345 --cacert /etc/kubernetes/pki/ca.crt --cert /etc/kubernetes/pki/apiserver-kubelet-client.crt --key /etc/kubernetes/pki/apiserver-kubelet-client.key https://11.158.239.200:6443/apis/apps/v1/namespaces/default/deployments --header "Authorization: Bearer $JWT_TOKEN_DEFAULT_DEFAULT"

#cat $JWT_TOKEN_DEFAULT_DEFAULT eyJhbGciOiJSUzI1NiIsImtpZCI6ImlNVVFVNmxUM2t4c3Y2Q3IyT1BzV2hDZGRVSmVxTHc5RV8wUXZ4RVM5REEifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJkZWZhdWx0Iiwia3ViZXJ: File name too long

image-20220223170008311

参考资料

https://kubernetes.io/zh/docs/reference/kubectl/cheatsheet/

Java 技巧合集

发表于 2020-01-04 | 分类于 Java

Java 技巧合集

获取一直FullGC下的java进程HeapDump的小技巧

就是小技巧,操作步骤需要查询,随手记录

  • 找到java进程,gdb attach上去, 例如 gdb -p 12345
  • 找到这个HeapDumpBeforeFullGC的地址(这个flag如果为true,会在FullGC之前做HeapDump,默认是false)
1
2
(gdb) p &HeapDumpBeforeFullGC
$2 = (<data variable, no debug info> *) 0x7f7d50fc660f <HeapDumpBeforeFullGC>
  • Copy 地址:0x7f7d50fc660f
  • 然后把他设置为true,这样下次FGC之前就会生成一份dump文件
1
2
(gdb) set *0x7f7d50fc660f = 1
(gdb) quit
  • 最后,等一会,等下次FullGC触发,你就有HeapDump了!
    (如果没有指定heapdump的名字,默认是 java_pidxxx.hprof)

(PS. jstat -gcutil pid 可以查看gc的概况)

(操作完成后记得gdb上去再设置回去,不然可能一直fullgc,导致把磁盘打满).

其它

在jvm还有响应的时候可以: jinfo -flag +HeapDumpBeforeFullGC pid 设置HeapDumpBeforeFullGC 为true(- 为false,+-都不要为只打印值)

kill -3 产生coredump 存放在 kernel.core_pattern=/root/core (/etc/sysctl.conf , 先 ulimit -c unlimited;或者 gcore id 获取coredump)

得到core文件后,采用 gdb -c 执行文件 core文件 进入调试模式,对于java,有以下2个技巧:

进入gdb调试模式后,输入如下命令: info threads,观察异常的线程,定位到异常的线程后,则可以输入如下命令:thread 线程编号,则会打印出当前java代码的工作流程。

而对于这个core,亦可以用jstack jmap打印出堆信息,线程信息,具体命令:

jmap -heap 执行文件 core文件 jstack -F -l 执行文件 core文件

容器中的进程的话需要到宿主机操作,并且将容器中的 jdk文件夹复制到宿主机对应的位置。

ps auxff |grep 容器id -A10 找到JVM在宿主机上的进程id

coredump

Coredump叫做核心转储,它是进程运行时在突然崩溃的那一刻的一个内存快照。操作系统在程序发生异常而异常在进程内部又没有被捕获的情况下,会把进程此刻内存、寄存器状态、运行堆栈等信息转储保存在一个文件里。

kill -3 产生coredump 存放在 kernel.core_pattern=/root/core (/etc/sysctl.conf , 先 ulimit -c unlimited;)

或者 gcore id 获取coredump

coredump 所在位置:

1
2
$cat /proc/sys/kernel/core_pattern
/home/admin/

coredump 分析

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
//打开 coredump
$gdb /opt/taobao/java/bin/java core.24086
[New LWP 27184]
[New LWP 27186]
[New LWP 24086]
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
Core was generated by `/opt/tt/java_coroutine/bin/java'.
#0 0x00007f2fa4fada35 in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
Missing separate debuginfos, use: debuginfo-install jdk-8.9.14-20200203164153.alios7.x86_64
(gdb) info threads //查看所有thread
Id Target Id Frame
583 Thread 0x7f2fa56177c0 (LWP 24086) 0x00007f2fa4fab017 in pthread_join () from /lib64/libpthread.so.0
582 Thread 0x7f2f695f3700 (LWP 27186) 0x00007f2fa4fada35 in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
581 Thread 0x7f2f6cbfb700 (LWP 27184) 0x00007f2fa4fada35 in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
580 Thread 0x7f2f691ef700 (LWP 27176) 0x00007f2fa4fada35 in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
579 Thread 0x7f2f698f6700 (LWP 27174) 0x00007f2fa4fada35 in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0


(gdb) thread apply all bt //查看所有线程堆栈
Thread 583 (Thread 0x7f2fa56177c0 (LWP 24086)):
#0 0x00007f2fa4fab017 in pthread_join () from /lib64/libpthread.so.0
#1 0x00007f2fa4b85085 in ContinueInNewThread0 (continuation=continuation@entry=0x7f2fa4b7fd70 <JavaMain>, stack_size=1048576, args=args@entry=0x7ffe529432d0)
at /ssd1/jenkins_home/workspace/ajdk.8.build.master/jdk/src/solaris/bin/java_md_solinux.c:1044
#2 0x00007f2fa4b81877 in ContinueInNewThread (ifn=ifn@entry=0x7ffe529433d0, threadStackSize=<optimized out>, argc=<optimized out>, argv=0x7f2fa3c163a8, mode=mode@entry=1,
what=what@entry=0x7ffe5294be17 "com.taobao.tddl.server.TddlLauncher", ret=0) at /ssd1/jenkins_home/workspace/ajdk.8.build.master/jdk/src/share/bin/java.c:2033
#3 0x00007f2fa4b8513b in JVMInit (ifn=ifn@entry=0x7ffe529433d0, threadStackSize=<optimized out>, argc=<optimized out>, argv=<optimized out>, mode=mode@entry=1,
what=what@entry=0x7ffe5294be17 "com.taobao.tddl.server.TddlLauncher", ret=ret@entry=0) at /ssd1/jenkins_home/workspace/ajdk.8.build.master/jdk/src/solaris/bin/java_md_solinux.c:1091
#4 0x00007f2fa4b8254d in JLI_Launch (argc=0, argv=0x7f2fa3c163a8, jargc=<optimized out>, jargv=<optimized out>, appclassc=1, appclassv=0x0, fullversion=0x400885 "1.8.0_232-b604",
dotversion=0x400881 "1.8", pname=0x40087c "java", lname=0x40087c "java", javaargs=0 '\000', cpwildcard=1 '\001', javaw=0 '\000', ergo=0)
at /ssd1/jenkins_home/workspace/ajdk.8.build.master/jdk/src/share/bin/java.c:304
#5 0x0000000000400635 in main ()

Thread 582 (Thread 0x7f2f695f3700 (LWP 27186)):
#0 0x00007f2fa4fada35 in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
#1 0x00007f2fa342d863 in Parker::park(bool, long) () from /opt/taobao/install/ajdk-8_9_14-b604/jre/lib/amd64/server/libjvm.so
#2 0x00007f2fa35ba3c3 in Unsafe_Park () from /opt/taobao/install/ajdk-8_9_14-b604/jre/lib/amd64/server/libjvm.so
#3 0x00007f2f9343b44a in ?? ()
#4 0x000000008082e778 in ?? ()
#5 0x0000000000000003 in ?? ()
#6 0x00007f2f88e32758 in ?? ()
#7 0x00007f2f6f532800 in ?? ()

(gdb) thread apply 582 bt //查看582这个线程堆栈,LWP 27186(0x6a32)对应jstack 线程10进程id

Thread 582 (Thread 0x7f2f695f3700 (LWP 27186)):
#0 0x00007f2fa4fada35 in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
#1 0x00007f2fa342d863 in Parker::park(bool, long) () from /opt/taobao/install/ajdk-8_9_14-b604/jre/lib/amd64/server/libjvm.so
#2 0x00007f2fa35ba3c3 in Unsafe_Park () from /opt/taobao/install/ajdk-8_9_14-b604/jre/lib/amd64/server/libjvm.so
#3 0x00007f2f9343b44a in ?? ()
#35 0x00000000f26fc738 in ?? ()
#36 0x00007f2fa51cec5b in arena_run_split_remove (arena=0x7f2f6ab09c34, chunk=0x80, run_ind=0, flag_dirty=0, flag_decommitted=<optimized out>, need_pages=0) at src/arena.c:398
#37 0x00007f2f695f2980 in ?? ()
#38 0x0000000000000001 in ?? ()
#39 0x00007f2f88e32758 in ?? ()
#40 0x00007f2f695f2920 in ?? ()
#41 0x00007f2fa32f46b8 in CallInfo::set_common(KlassHandle, KlassHandle, methodHandle, methodHandle, CallInfo::CallKind, int, Thread*) ()
from /opt/taobao/install/ajdk-8_9_14-b604/jre/lib/amd64/server/libjvm.so
#42 0x00007f2f7d800000 in ?? ()

以上堆栈涉及到Java代码部分都是看不到函数,需要进一步把Java 符号替换进去

coredump 转 jmap hprof

1
jmap -dump:format=b,file=24086.hprof /opt/taobao/java/bin/java core.24086

以上命令输入是 core.24086 这个 coredump,输出是一个 jmap 的dump 24086.hprof

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
$jmap -J-d64 /opt/taobao/java/bin/java core.24086

Attaching to core core.24086 from executable /opt/taobao/java/bin/java, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.232-b604
0x0000000000400000 8K /opt/taobao/java/bin/java
0x00007f2fa51be000 6679K /opt/taobao/install/ajdk-8_9_14-b604/bin/../lib/amd64/libjemalloc.so.2
0x00007f2fa4fa2000 138K /lib64/libpthread.so.0
0x00007f2fa4d8c000 88K /lib64/libz.so.1
0x00007f2fa4b7d000 280K /opt/taobao/install/ajdk-8_9_14-b604/bin/../lib/amd64/jli/libjli.so
0x00007f2fa4979000 18K /lib64/libdl.so.2
0x00007f2fa45ab000 2105K /lib64/libc.so.6
0x00007f2fa43a3000 42K /lib64/librt.so.1
0x00007f2fa40a1000 1110K /lib64/libm.so.6
0x00007f2fa5406000 159K /lib64/ld-linux-x86-64.so.2
0x00007f2fa2af1000 17898K /opt/taobao/install/ajdk-8_9_14-b604/jre/lib/amd64/server/libjvm.so
0x00007f2fa25f1000 64K /opt/taobao/install/ajdk-8_9_14-b604/jre/lib/amd64/libverify.so
0x00007f2fa23c2000 228K /opt/taobao/install/ajdk-8_9_14-b604/jre/lib/amd64/libjava.so
0x00007f2fa21af000 60K /lib64/libnss_files.so.2
0x00007f2fa1fa5000 47K /opt/taobao/install/ajdk-8_9_14-b604/jre/lib/amd64/libzip.so
0x00007f2f80ded000 96K /opt/taobao/install/ajdk-8_9_14-b604/jre/lib/amd64/libnio.so
0x00007f2f80bd4000 119K /opt/taobao/install/ajdk-8_9_14-b604/jre/lib/amd64/libnet.so
0x00007f2f7e1f6000 50K /opt/taobao/install/ajdk-8_9_14-b604/jre/lib/amd64/libmanagement.so
0x00007f2f75dc8000 209K /home/admin/drds-server/lib/native/libsigar-amd64-linux.so
0x00007f2f6d8ad000 293K /opt/taobao/install/ajdk-8_9_14-b604/jre/lib/amd64/libsunec.so
0x00007f2f6d697000 86K /lib64/libgcc_s.so.1
0x00007f2f6bdf9000 30K /lib64/libnss_dns.so.2
0x00007f2f6bbdf000 107K /lib64/libresolv.so.2

coredump 生成 java stack

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
jstack -J-d64 /opt/taobao/java/bin/java core.24086 

Attaching to core core.24086 from executable /opt/taobao/java/bin/java, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.232-b604
Deadlock Detection:

No deadlocks found.

Thread 27186: (state = BLOCKED)
- sun.misc.Unsafe.park0(boolean, long) @bci=0 (Compiled frame; information may be imprecise)
- sun.misc.Unsafe.park(boolean, long) @bci=63, line=1038 (Compiled frame)
- java.util.concurrent.locks.LockSupport.park(java.lang.Object) @bci=14, line=176 (Compiled frame)
- java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await() @bci=42, line=2047 (Compiled frame)
- java.util.concurrent.LinkedBlockingQueue.take() @bci=29, line=446 (Compiled frame)
- java.util.concurrent.ThreadPoolExecutor.getTask() @bci=149, line=1074 (Compiled frame)
- java.util.concurrent.ThreadPoolExecutor.runWorker(java.util.concurrent.ThreadPoolExecutor$Worker) @bci=26, line=1134 (Compiled frame)
- java.util.concurrent.ThreadPoolExecutor$Worker.run() @bci=5, line=624 (Compiled frame)
- java.lang.Thread.run() @bci=11, line=858 (Compiled frame)

gdb coredump with java symbol

需要安装JVM debug info包,同时要求gdb版本在7.10以上

设置:

home 目录下创建 .gdbinit 然后放入如下内容,libjvm.so-gdb.py 就是 dbg.py 脚本,gdb启动的时候会自动加载这个脚本

1
2
$cat ~/.gdbinit
add-auto-load-safe-path /opt/install/jdk-8_9_14-b604/jre/lib/amd64/server/libjvm.so-gdb.py

使用:

1
gdb -iex "set auto-load safe-path /" /opt/install/java/bin/java ./core.24086

G1 GC为什么快

https://ata.alibaba-inc.com/articles/199497

G1比CMS GC 暂停短、更稳定,但是最终吞吐大概率是CMS要好,这是因为G1编译后代码更大

-XX:InlineSmallCode=3000告诉编译器, 汇编3000字节以内的函数需要被inline, 这个值默认是2000

另外CMS用的是Dirty Card,而G1 为了降低GC时间在Remeber Set(类似Dirty Card)的维护上花了更多的代价

Dirty Card维护代价:

  • 会影响code size
    Code size影响了inline机会
    Code size增大则instruction cache miss几率变大 (几十倍的执行时间差距)
  • 本身执行mark dirty动作耗时, 这是一个写内存+GC/mutator线程同步的操作, 可以很复杂, 也可以很简单

比如G1为了降低暂停时间,就要尽量控制Remeber Set的更新,所以还需要判断write动作是否真的有必要更新Remeber Set(类似old.ref = null这种写操作是不需要更新Remeber Set的)

简单说CMS的每次 Dirty Card维护只需要3条汇编,而G1的Remember Set维护需要十多条、几十条汇编

获取一直FullGC下的java进程HeapDump的小技巧

发表于 2020-01-04 | 分类于 Java

获取一直FullGC下的java进程HeapDump的小技巧

就是小技巧,操作步骤需要查询,随手记录

  • 找到java进程,gdb attach上去, 例如 gdb -p 12345
  • 找到这个HeapDumpBeforeFullGC的地址(这个flag如果为true,会在FullGC之前做HeapDump,默认是false)
1
2
(gdb) p &HeapDumpBeforeFullGC
$2 = (<data variable, no debug info> *) 0x7f7d50fc660f <HeapDumpBeforeFullGC>
  • Copy 地址:0x7f7d50fc660f
  • 然后把他设置为true,这样下次FGC之前就会生成一份dump文件
1
2
(gdb) set *0x7f7d50fc660f = 1
(gdb) quit
  • 最后,等一会,等下次FullGC触发,你就有HeapDump了!
    (如果没有指定heapdump的名字,默认是 java_pidxxx.hprof)

(PS. jstat -gcutil pid 可以查看gc的概况)

(操作完成后记得gdb上去再设置回去,不然可能一直fullgc,导致把磁盘打满).

其它

在jvm还有响应的时候可以: jinfo -flag +HeapDumpBeforeFullGC pid 设置HeapDumpBeforeFullGC 为true(- 为false,+-都不要为只打印值)

kill -3 产生coredump 存放在 kernel.core_pattern=/root/core (/etc/sysctl.conf , 先 ulimit -c unlimited;或者 gcore id 获取coredump)

得到core文件后,采用 gdb -c 执行文件 core文件 进入调试模式,对于java,有以下2个技巧:

进入gdb调试模式后,输入如下命令: info threads,观察异常的线程,定位到异常的线程后,则可以输入如下命令:thread 线程编号,则会打印出当前java代码的工作流程。

而对于这个core,亦可以用jstack jmap打印出堆信息,线程信息,具体命令:

jmap -heap 执行文件 core文件 jstack -F -l 执行文件 core文件

容器中的进程的话需要到宿主机操作,并且将容器中的 jdk文件夹复制到宿主机对应的位置。

ps auxff |grep 容器id -A10 找到JVM在宿主机上的进程id

coredump

kill -3 产生coredump 存放在 kernel.core_pattern=/root/core (/etc/sysctl.conf , 先 ulimit -c unlimited;)

或者 gcore id 获取coredump

coredump 所在位置:

1
2
$cat /proc/sys/kernel/core_pattern
/home/admin/

coredump 分析

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
//打开 coredump
$gdb /opt/taobao/java/bin/java core.24086
[New LWP 27184]
[New LWP 27186]
[New LWP 24086]
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
Core was generated by `/opt/tt/java_coroutine/bin/java'.
#0 0x00007f2fa4fada35 in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
Missing separate debuginfos, use: debuginfo-install jdk-8.9.14-20200203164153.alios7.x86_64
(gdb) info threads //查看所有thread
Id Target Id Frame
583 Thread 0x7f2fa56177c0 (LWP 24086) 0x00007f2fa4fab017 in pthread_join () from /lib64/libpthread.so.0
582 Thread 0x7f2f695f3700 (LWP 27186) 0x00007f2fa4fada35 in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
581 Thread 0x7f2f6cbfb700 (LWP 27184) 0x00007f2fa4fada35 in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
580 Thread 0x7f2f691ef700 (LWP 27176) 0x00007f2fa4fada35 in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
579 Thread 0x7f2f698f6700 (LWP 27174) 0x00007f2fa4fada35 in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0


(gdb) thread apply all bt //查看所有线程堆栈
Thread 583 (Thread 0x7f2fa56177c0 (LWP 24086)):
#0 0x00007f2fa4fab017 in pthread_join () from /lib64/libpthread.so.0
#1 0x00007f2fa4b85085 in ContinueInNewThread0 (continuation=continuation@entry=0x7f2fa4b7fd70 <JavaMain>, stack_size=1048576, args=args@entry=0x7ffe529432d0)
at /ssd1/jenkins_home/workspace/ajdk.8.build.master/jdk/src/solaris/bin/java_md_solinux.c:1044
#2 0x00007f2fa4b81877 in ContinueInNewThread (ifn=ifn@entry=0x7ffe529433d0, threadStackSize=<optimized out>, argc=<optimized out>, argv=0x7f2fa3c163a8, mode=mode@entry=1,
what=what@entry=0x7ffe5294be17 "com.taobao.tddl.server.TddlLauncher", ret=0) at /ssd1/jenkins_home/workspace/ajdk.8.build.master/jdk/src/share/bin/java.c:2033
#3 0x00007f2fa4b8513b in JVMInit (ifn=ifn@entry=0x7ffe529433d0, threadStackSize=<optimized out>, argc=<optimized out>, argv=<optimized out>, mode=mode@entry=1,
what=what@entry=0x7ffe5294be17 "com.taobao.tddl.server.TddlLauncher", ret=ret@entry=0) at /ssd1/jenkins_home/workspace/ajdk.8.build.master/jdk/src/solaris/bin/java_md_solinux.c:1091
#4 0x00007f2fa4b8254d in JLI_Launch (argc=0, argv=0x7f2fa3c163a8, jargc=<optimized out>, jargv=<optimized out>, appclassc=1, appclassv=0x0, fullversion=0x400885 "1.8.0_232-b604",
dotversion=0x400881 "1.8", pname=0x40087c "java", lname=0x40087c "java", javaargs=0 '\000', cpwildcard=1 '\001', javaw=0 '\000', ergo=0)
at /ssd1/jenkins_home/workspace/ajdk.8.build.master/jdk/src/share/bin/java.c:304
#5 0x0000000000400635 in main ()

Thread 582 (Thread 0x7f2f695f3700 (LWP 27186)):
#0 0x00007f2fa4fada35 in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
#1 0x00007f2fa342d863 in Parker::park(bool, long) () from /opt/taobao/install/ajdk-8_9_14-b604/jre/lib/amd64/server/libjvm.so
#2 0x00007f2fa35ba3c3 in Unsafe_Park () from /opt/taobao/install/ajdk-8_9_14-b604/jre/lib/amd64/server/libjvm.so
#3 0x00007f2f9343b44a in ?? ()
#4 0x000000008082e778 in ?? ()
#5 0x0000000000000003 in ?? ()
#6 0x00007f2f88e32758 in ?? ()
#7 0x00007f2f6f532800 in ?? ()

(gdb) thread apply 582 bt //查看582这个线程堆栈,LWP 27186(0x6a32)对应jstack 线程10进程id

Thread 582 (Thread 0x7f2f695f3700 (LWP 27186)):
#0 0x00007f2fa4fada35 in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
#1 0x00007f2fa342d863 in Parker::park(bool, long) () from /opt/taobao/install/ajdk-8_9_14-b604/jre/lib/amd64/server/libjvm.so
#2 0x00007f2fa35ba3c3 in Unsafe_Park () from /opt/taobao/install/ajdk-8_9_14-b604/jre/lib/amd64/server/libjvm.so
#3 0x00007f2f9343b44a in ?? ()
#35 0x00000000f26fc738 in ?? ()
#36 0x00007f2fa51cec5b in arena_run_split_remove (arena=0x7f2f6ab09c34, chunk=0x80, run_ind=0, flag_dirty=0, flag_decommitted=<optimized out>, need_pages=0) at src/arena.c:398
#37 0x00007f2f695f2980 in ?? ()
#38 0x0000000000000001 in ?? ()
#39 0x00007f2f88e32758 in ?? ()
#40 0x00007f2f695f2920 in ?? ()
#41 0x00007f2fa32f46b8 in CallInfo::set_common(KlassHandle, KlassHandle, methodHandle, methodHandle, CallInfo::CallKind, int, Thread*) ()
from /opt/taobao/install/ajdk-8_9_14-b604/jre/lib/amd64/server/libjvm.so
#42 0x00007f2f7d800000 in ?? ()

coredump 转 jmap hprof

1
jmap -dump:format=b,file=24086.hprof /opt/taobao/java/bin/java core.24086

以上命令输入是 core.24086 这个 coredump,输出是一个 jmap 的dump 24086.hprof

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
$jmap -J-d64 /opt/taobao/java/bin/java core.24086

Attaching to core core.24086 from executable /opt/taobao/java/bin/java, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.232-b604
0x0000000000400000 8K /opt/taobao/java/bin/java
0x00007f2fa51be000 6679K /opt/taobao/install/ajdk-8_9_14-b604/bin/../lib/amd64/libjemalloc.so.2
0x00007f2fa4fa2000 138K /lib64/libpthread.so.0
0x00007f2fa4d8c000 88K /lib64/libz.so.1
0x00007f2fa4b7d000 280K /opt/taobao/install/ajdk-8_9_14-b604/bin/../lib/amd64/jli/libjli.so
0x00007f2fa4979000 18K /lib64/libdl.so.2
0x00007f2fa45ab000 2105K /lib64/libc.so.6
0x00007f2fa43a3000 42K /lib64/librt.so.1
0x00007f2fa40a1000 1110K /lib64/libm.so.6
0x00007f2fa5406000 159K /lib64/ld-linux-x86-64.so.2
0x00007f2fa2af1000 17898K /opt/taobao/install/ajdk-8_9_14-b604/jre/lib/amd64/server/libjvm.so
0x00007f2fa25f1000 64K /opt/taobao/install/ajdk-8_9_14-b604/jre/lib/amd64/libverify.so
0x00007f2fa23c2000 228K /opt/taobao/install/ajdk-8_9_14-b604/jre/lib/amd64/libjava.so
0x00007f2fa21af000 60K /lib64/libnss_files.so.2
0x00007f2fa1fa5000 47K /opt/taobao/install/ajdk-8_9_14-b604/jre/lib/amd64/libzip.so
0x00007f2f80ded000 96K /opt/taobao/install/ajdk-8_9_14-b604/jre/lib/amd64/libnio.so
0x00007f2f80bd4000 119K /opt/taobao/install/ajdk-8_9_14-b604/jre/lib/amd64/libnet.so
0x00007f2f7e1f6000 50K /opt/taobao/install/ajdk-8_9_14-b604/jre/lib/amd64/libmanagement.so
0x00007f2f75dc8000 209K /home/admin/drds-server/lib/native/libsigar-amd64-linux.so
0x00007f2f6d8ad000 293K /opt/taobao/install/ajdk-8_9_14-b604/jre/lib/amd64/libsunec.so
0x00007f2f6d697000 86K /lib64/libgcc_s.so.1
0x00007f2f6bdf9000 30K /lib64/libnss_dns.so.2
0x00007f2f6bbdf000 107K /lib64/libresolv.so.2

coredump 生成 java stack

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
jstack -J-d64 /opt/taobao/java/bin/java core.24086 

Attaching to core core.24086 from executable /opt/taobao/java/bin/java, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.232-b604
Deadlock Detection:

No deadlocks found.

Thread 27186: (state = BLOCKED)
- sun.misc.Unsafe.park0(boolean, long) @bci=0 (Compiled frame; information may be imprecise)
- sun.misc.Unsafe.park(boolean, long) @bci=63, line=1038 (Compiled frame)
- java.util.concurrent.locks.LockSupport.park(java.lang.Object) @bci=14, line=176 (Compiled frame)
- java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await() @bci=42, line=2047 (Compiled frame)
- java.util.concurrent.LinkedBlockingQueue.take() @bci=29, line=446 (Compiled frame)
- java.util.concurrent.ThreadPoolExecutor.getTask() @bci=149, line=1074 (Compiled frame)
- java.util.concurrent.ThreadPoolExecutor.runWorker(java.util.concurrent.ThreadPoolExecutor$Worker) @bci=26, line=1134 (Compiled frame)
- java.util.concurrent.ThreadPoolExecutor$Worker.run() @bci=5, line=624 (Compiled frame)
- java.lang.Thread.run() @bci=11, line=858 (Compiled frame)

gdb coredump with java symbol

Linux 问题总结

发表于 2020-01-02 | 分类于 Linux

Linux 问题总结

crond文件权限的坑

crond第一次加载的时候(刚启动)会去检查文件属性,不是644的话以后都不会执行了,即使后面chmod改成了644.

手工随便修改一下该文件的内容就能触发自动执行了,或者重启crond, 或者 sudo service crond reload, 或者 /etc/cron.d/下有任何修改都会触发crond reload配置(包含 touch )。

总之 crond会每分钟去检查job有没有change,有的话才触发reload,这个change看的时候change time有没有变化,不看权限的变化,仅仅是权限的变化不会触发crond reload。

crond会每分钟去检查一下job有没有修改,有修改的话会reload,但是这个修改不包含权限的修改。可以简单地理解这个修改是指文件的change time。

cgroup目录报No space left on device

可能是因为某个规则下的 cpuset.cpus 文件是空导致的

容器中root用户执行 su - admin 切换失败

问题原因:https://access.redhat.com/solutions/30316

image.png

如上图去掉 admin nproc限制就可以了

这是因为root用户的nproc是unlimited,但是admin的是65535,所以切不过去

1
2
3
[root@i22h08323 /home/admin]
#ulimit -u
unlimited

容器中ulimit限制了sudo的执行

容器启动的时候默认nofile为65535(可以通过 docker run –ulimit nofile=655360 来设置),如果容器中的 /etc/security/limits.conf 中设置的nofile大于 65535就会报错,因为容器的1号进程就是65535了,比如在容器中用root用户执行sudo ls报错:

1
2
3
#sudo ls
sudo: pam_open_session: Permission denied
sudo: policy plugin failed session initialization

可以修改容器中的 ulimit 不要超过默认的65535或者修改容器的启动参数来解决。

子进程都会继承父进程的一些环境变量,比如 limits.conf, sudo/su/crond/passwd等都会触发重新加载limits,

1
grep -rin pam_limit /etc/pam.d //可以看到触发重新加载的场景

systemd limits

/etc/security/limits.conf 的配置,只适用于通过PAM 认证登录用户的资源限制,它对systemd 的service 的资源限制不生效。

因此登录用户的限制,通过/etc/security/limits.conf 与/etc/security/limits.d 下的文件设置即可。

对于systemd service 的资源设置,则需修改全局配置,全局配置文件放在/etc/systemd/system.conf 和/etc/systemd/user.conf,同时也会加载两个对应目录中的所有.conf 文件/etc/systemd/system.conf.d/.conf 和/etc/systemd/user.conf.d/.conf。

open files 限制在1024

docker 容器内 nofile只有1024,检查:

1
2
3
4
5
cat /etc/sysconfig/docker
或者
cat /usr/lib/systemd/system/docker.service
LimitNOFILE=1048576
LimitNPROC=1048576

关于ulimit的一些知识点

参考 Ulimit http://blog.yufeng.info/archives/2568

  • limit的设定值是 per-process 的
  • 在 Linux 中,每个普通进程可以调用 getrlimit() 来查看自己的 limits,也可以调用 setrlimit() 来改变自身的 soft limits
  • 要改变 hard limit, 则需要进程有 CAP_SYS_RESOURCE 权限
  • 进程 fork() 出来的子进程,会继承父进程的 limits 设定
  • ulimit 是 shell 的内置命令。在执行ulimit命令时,其实是 shell 自身调用 getrlimit()/setrlimit() 来获取/改变自身的 limits. 当我们在 shell 中执行应用程序时,相应的进程就会继承当前 shell 的 limits 设定
  • shell 的初始 limits 通常是 pam_limits 设定的。顾名思义,pam_limits 是一个 PAM 模块,用户登录后,pam_limits 会给用户的 shell 设定在 limits.conf 定义的值

ulimit, limits.conf 和 pam_limits模块 的关系,大致是这样的:

  1. 用户进行登录,触发 pam_limits;
  2. pam_limits 读取 limits.conf,相应地设定用户所获得的 shell 的 limits;
  3. 用户在 shell 中,可以通过 ulimit 命令,查看或者修改当前 shell 的 limits;
  4. 当用户在 shell 中执行程序时,该程序进程会继承 shell 的 limits 值。于是,limits 在进程中生效了

判断要分配的句柄号是不是超过了 limits.conf 中 nofile 的限制。fd 是当前进程相关的,是一个从 0 开始的整数
结论1:soft nofile 和 fs.nr_open的作用一样,它两都是限制的单个进程的最大文件数量。区别是 soft nofile 可以按用户来配置,而 fs.nr_open 所有用户只能配一个。注意 hard nofile 一定要比 fs.nr_open 要小,否则可能导致用户无法登陆。
结论2:fs.file-max: 整个系统上可打开的最大文件数,但不限制 root 用户

pam 权限报错

image.png

从debug信息看如果是pam权限报错的话,需要将 required 改成 sufficient

1
2
3
4
5
6
7
8
9
10
11
$cat /etc/pam.d/crond 
#
# The PAM configuration file for the cron daemon
#
#
# No PAM authentication called, auth modules not needed
account required pam_access.so
account include system-auth
session required pam_loginuid.so //required 改成 sufficient
session include system-auth
auth include system-auth

PAM 提供四个安全领域的特性,但是应用程序不太可能同时需要所有这些方面。例如,passwd 命令只需要下面列表中的第三组:

  • account 处理账户限制。对于有效的用户,允许他做什么?
  • auth 处理用户识别 — 例如,通过输入用户名和密码。
  • password 只处理与密码相关的问题,比如设置新密码。
  • session 处理连接管理,包括日志记录。

在 /etc/pam.d 目录中为将使用 PAM 的每个应用程序创建一个配置文件,文件名与应用程序名相同。例如,login 命令的配置文件是 /etc/pam.d/login。

必须定义将应用哪些模块,创建一个动作 “堆”。PAM 运行堆中的所有模块,根据它们的结果允许或拒绝用户的请求。还必须定义检查是否是必需的。最后,other 文件为没有特殊规则的所有应用程序提供默认规则。

  • optional 模块可以成功,也可以失败;PAM 根据模块是否最终成功返回 success 或 failure。
  • required 模块必须成功。如果失败,PAM 返回 failure,但是会在运行堆中的其他模块之后返回。
  • requisite 模块也必须成功。但是,如果失败,PAM 立即返回 failure,不再运行其他模块。
  • sufficient 模块在成功时导致 PAM 立即返回 success,不再运行其他模块。

当pam安装之后有两大部分:在/lib64/security目录下的各种pam模块以及/etc/pam.d和/etc/pam.d目录下的针对各种服务和应用已经定义好的pam配置文件。当某一个有认证需求的应用程序需要验证的时候,一般在应用程序中就会定义负责对其认证的PAM配置文件。以vsftpd为例,在它的配置文件/etc/vsftpd/vsftpd.conf中就有这样一行定义:

pam_service_name=vsftpd

表示登录FTP服务器的时候进行认证是根据/etc/pam.d/vsftpd文件定义的内容进行。

PAM 认证过程

当程序需要认证的时候已经找到相关的pam配置文件,认证过程是如何进行的?下面我们将通过解读/etc/pam.d/system-auth文件予以说明。

首先要声明一点的是:system-auth是一个非常重要的pam配置文件,主要负责用户登录系统的认证工作。而且该文件不仅仅只是负责用户登录系统认证,其它的程序和服务通过include接口也可以调用到它,从而节省了很多重新自定义配置的工作。所以应该说该文件是系统安全的总开关和核心的pam配置文件。

下面是/etc/pam.d/system-auth文件的全部内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
$cat /etc/pam.d/system-auth
#%PAM-1.0
# This file is auto-generated.
# User changes will be destroyed the next time authconfig is run.
auth required pam_env.so
auth required pam_faildelay.so delay=2000000
auth sufficient pam_unix.so nullok try_first_pass
auth requisite pam_succeed_if.so uid >= 1000 quiet_success
auth required pam_deny.so

account required pam_unix.so
account sufficient pam_localuser.so
account sufficient pam_succeed_if.so uid < 1000 quiet
account required pam_permit.so

password requisite pam_pwquality.so try_first_pass local_users_only retry=3 authtok_type=
password sufficient pam_unix.so sha512 shadow nullok try_first_pass use_authtok
password required pam_deny.so

session optional pam_keyinit.so revoke
session required pam_limits.so
-session optional pam_systemd.so
session [success=1 default=ignore] pam_succeed_if.so service in crond quiet use_uid
session required pam_unix.so

第一部分

当用户登录的时候,首先会通过auth类接口对用户身份进行识别和密码认证。所以在该过程中验证会经过几个带auth的配置项。

其中的第一步是通过pam_env.so模块来定义用户登录之后的环境变量, pam_env.so允许设置和更改用户登录时候的环境变量,默认情况下,若没有特别指定配置文件,将依据/etc/security/pam_env.conf进行用户登录之后环境变量的设置。

然后通过pam_unix.so模块来提示用户输入密码,并将用户密码与/etc/shadow中记录的密码信息进行对比,如果密码比对结果正确则允许用户登录,而且该配置项的使用的是“sufficient”控制位,即表示只要该配置项的验证通过,用户即可完全通过认证而不用再去走下面的认证项。不过在特殊情况下,用户允许使用空密码登录系统,例如当将某个用户在/etc/shadow中的密码字段删除之后,该用户可以只输入用户名直接登录系统。

下面的配置项中,通过pam_succeed_if.so对用户的登录条件做一些限制,表示允许uid大于500的用户在通过密码验证的情况下登录,在Linux系统中,一般系统用户的uid都在500之内,所以该项即表示允许使用useradd命令以及默认选项建立的普通用户直接由本地控制台登录系统。

最后通过pam_deny.so模块对所有不满足上述任意条件的登录请求直接拒绝,pam_deny.so是一个特殊的模块,该模块返回值永远为否,类似于大多数安全机制的配置准则,在所有认证规则走完之后,对不匹配任何规则的请求直接拒绝。

第二部分

三个配置项主要表示通过account账户类接口来识别账户的合法性以及登录权限。

第一行仍然使用pam_unix.so模块来声明用户需要通过密码认证。第二行承认了系统中uid小于500的系统用户的合法性。之后对所有类型的用户登录请求都开放控制台。

第三部分

会通过password口令类接口来确认用户使用的密码或者口令的合法性。第一行配置项表示需要的情况下将调用pam_cracklib来验证用户密码复杂度。如果用户输入密码不满足复杂度要求或者密码错,最多将在三次这种错误之后直接返回密码错误的提示,否则期间任何一次正确的密码验证都允许登录。需要指出的是,pam_cracklib.so是一个常用的控制密码复杂度的pam模块,关于其用法举例我们会在之后详细介绍。之后带pam_unix.so和pam_deny.so的两行配置项的意思与之前类似。都表示需要通过密码认证并对不符合上述任何配置项要求的登录请求直接予以拒绝。不过用户如果执行的操作是单纯的登录,则这部分配置是不起作用的。

第四部分

主要将通过session会话类接口为用户初始化会话连接。其中几个比较重要的地方包括,使用pam_keyinit.so表示当用户登录的时候为其建立相应的密钥环,并在用户登出的时候予以撤销。不过该行配置的控制位使用的是optional,表示这并非必要条件。之后通过pam_limits.so限制用户登录时的会话连接资源,相关pam_limit.so配置文件是/etc/security/limits.conf,默认情况下对每个登录用户都没有限制。关于该模块的配置方法在后面也会详细介绍。

常用的PAM模块介绍

PAM模块 结合管理类型 说明
pam_unix.so auth 提示用户输入密码,并与/etc/shadow文件相比对.匹配返回0
pam_unix.so account 检查用户的账号信息(包括是否过期等).帐号可用时,返回0.
pam_unix.so password 修改用户的密码. 将用户输入的密码,作为用户的新密码更新shadow文件
pam_shells.so auth、account 如果用户想登录系统,那么它的shell必须是在/etc/shells文件中之一的shell
pam_deny.so account、auth、password、session 该模块可用于拒绝访问
pam_permit.so account、auth、password、session 模块任何时候都返回成功.
pam_securetty.so auth 如果用户要以root登录时,则登录的tty必须在/etc/securetty之中.
pam_listfile.so account、auth、password、session 访问应用程的控制开关
pam_cracklib.so password 这个模块可以插入到一个程序的密码栈中,用于检查密码的强度.
pam_limits.so session 定义使用系统资源的上限,root用户也会受此限制,可以通过/etc/security/limits.conf或/etc/security/limits.d/*.conf来设定

debug crond

先停掉 crond service,然后开启debug参数

1
2
systemctl stop crond
crond -x proc //不想真正执行的话:test

或者增加更多的debug信息, debug sudo/sudoers , 在 /etc/sudo.conf 中增加了:

1
2
Debug sudo /var/log/sudo_debug all@warn
Debug sudoers.so /var/log/sudoers_debug all@debug

crond ERROR (getpwnam() failed)

报错信息

1
crond[246590]: (/usr/bin/ssh) ERROR (getpwnam() failed)

要特别注意crond格式是 时间 用户 命令

有时候我们可以省略用户,但是在 /etc/cron.d/ 中省略用户后报错如上

进程和线程

把进程看做是资源分配的单位,把线程才看成一个具体的执行实体。

deleted 文件

lsof +L1 或者 lsof | grep delete 发现有被删除的文件,且占用大量磁盘空间

更多 lsof 用法:https://mp.weixin.qq.com/s?__biz=MzAwNTM5Njk3Mw==&mid=2247518966&idx=1&sn=6ebf794b9743abb04c9ed20d30c90746

lsof /path/file 列出打开文件的进程,也可以是路径,还可以通过参数 “+D” 来递归路径

清理:

1
2
3
4
先通过 lsof +L1 找到 deleted 文件以及 pid
cd /proc/{上一步的 pid}/fd
ll # 在列出的文件名中找到 lsof +L1 看到的文件名,记录对应的 fd 值
cat /dev/null > {上一步找到的 fd }

No route to host

如果ping ip能通,但是curl/telnet 访问 ip+port 报not route to host 错误,这肯定不是route问题(因为ping能通), 一般都是目标机器防火墙的问题

可以停掉防火墙验证,或者添加端口到防火墙:

1
2
3
#firewall-cmd --permanent --add-port=8090/tcp
success
#firewall-cmd --reload

强制重启系统

image.png

hostname

hostname -i 是根据机器的hostname去解析ip,如果 /etc/hosts里面没有指定hostname对应的ip就会走dns 流程然后libnss_myhostname 返回所有ip

getHostName获取的机器名如果对应的ip不是127.0.0.1,那么就用这个ip,否则就需要通过getHostByName获取所有网卡选择一个

tsar Floating point execption

image.png

因为 /etc/localtime 是deleted状态

奇怪的文件大小 sparse file

img

如上图 gc.log 实际为5.6M,但是通过 ls -lh 就变成74G了,但实际上总文件夹才63M。因为写文件的时候lseek了74G的地方写入5.6M的内容就看到是这个样子了,而前面lseek的74G是不需要从磁盘上分配出来的.

而 ls -s 中的 -s就是只看实际大小

img

图片来源

回收文件中的空洞:sudo fallocate -c –length 70G gc.log

如果文件一直打开写入中是没法回收的,因为一回收又被重新lseek到之前的末尾重新写入了!

增加dmesg buffer

If dmesg does not show any information about NUMA, then increase the Ring Buffer size:
Boot with ‘log_buf_len=16M’ (or some other big value). Refer the following kbase article How do I increase the kernel log ring buffer size? for steps on how to increase the ring buffer

yum 源问题处理

Yum commands error “pycurl.so: undefined symbol”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# yum check update
There was a problem importing one of the Python modules
required to run yum. The error leading to this problem was:

/usr/lib64/python2.6/site-packages/pycurl.so: undefined symbol: CRYPTO_set_locking_callback

Please install a package which provides this module, or
verify that the module is installed correctly.

It's possible that the above module doesn't match the
current version of Python, which is:
2.6.6 (r266:84292, Sep 4 2013, 07:46:00)
[GCC 4.4.7 20120313 (Red Hat 4.4.7-3)]

If you cannot solve this problem yourself, please go to
the yum faq at:
http://yum.baseurl.org/wiki/Faq
  • Check and fix the related library paths or remove 3rd party libraries, usually libcurl or libssh2. On a x86_64 system, the standard paths for those libraries are /usr/lib64/libcurl.so.4 and /usr/lib64/libssh2.so.1

软中断、系统调用和上下文切换

“你可以把内核看做是不断对请求进行响应的服务器,这些请求可能来自在CPU上执行的进程,也可能来自发出中断的外部设备。老板的请求相当于中断,而顾客的请求相当于用户态进程发出的系统调用”。

软中断和系统调用一样,都是CPU停止掉当前用户态上下文,保存工作现场,然后陷入到内核态继续工作。二者的唯一区别是系统调用是切换到同进程的内核态上下文,而软中断是则是切换到了另外一个内核进程ksoftirqd上。

系统调用开销是200ns起步

从实验数据来看,一次软中断CPU开销大约3.4us左右

实验结果显示进程上下文切换平均耗时 3.5us,lmbench工具显示的进程上下文切换耗时从2.7us到5.48之间

大约每次线程切换开销大约是3.8us左右。从上下文切换的耗时上来看,Linux线程(轻量级进程)其实和进程差别不太大。

软中断和进程上下文切换比较起来,进程上下文切换是从用户进程A切换到了用户进程B。而软中断切换是从用户进程A切换到了内核线程ksoftirqd上。而ksoftirqd作为一个内核控制路径,其处理程序比一个用户进程要轻量,所以上下文切换开销相对比进程切换要少一些(实际数据基本差不多)。

系统调用只是在进程内将用户态切换到内核态,然后再切回来,而上下文切换可是直接从进程A切换到了进程B。显然这个上下文切换需要完成的工作量更大。

软中断开销计算

  • 查看软中断总耗时, 首先用top命令可以看出每个核上软中断的开销占比,是在si列(1.2%–1秒[1000ms]中的1.2%)
  • 查看软中断次数,再用vmstat命令可以看到软中断的次数(in列 56000)
  • 计算每次软中断的耗时,该机器是16核的物理实机,故可以得出每个软中断需要的CPU时间是=12ms/(56000/16)次=3.428us。从实验数据来看,一次软中断CPU开销大约3.4us左右

Linux 启动进入紧急模式

可能是因为磁盘挂载不上,检查 /etc/fstab 中需要挂载的磁盘,尝试 mount -a 是否能全部挂载,麒麟下容易出现弄丢磁盘的标签和uuid

否则的话debug为啥,比如检查设备标签(e2label)是否冲突之类的

进程状态

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

1
2
3
4
5
6
7
8
9
10
11
PROCESS STATE CODES
Here are the different values that the s, stat and state output specifiers(header "STAT" or "S") will display to describe the state of a process:

D uninterruptible sleep (usually IO) #不可中断睡眠 不接受任何信号,因此kill对它无效,一般是磁盘io,网络io读写时出现
R running or runnable (on run queue) #可运行状态或者运行中,可运行状态表明进程所需要的资源准备就绪,待内核调度
S interruptible sleep (waiting for an event to complete) #可中断睡眠,等待某事件到来而进入睡眠状态
T stopped by job control signal #进程暂停状态 平常按下的ctrl+z,实际上是给进程发了SIGTSTP 信号 (kill -l可查看系统所有的信号量)
t stopped by debugger during the tracing #进程被ltrace、strace attach后就是这种状态
W paging (not valid since the 2.6.xx kernel) #没有用了
X dead (should never be seen) #进程退出时的状态
Z defunct ("zombie") process, terminated but not reaped by its parent #进程退出后父进程没有正常回收,俗称僵尸进程

D状态的进程

Process D是指进程处于不可中断状态。即uninterruptible sleep,通常我们比较常遇到的就是进程自旋等到进入竞争区等,刷脏页,进程同步等

D: Disk sleep(task_uninterruptible)–比如,磁盘满,导致进程D,无法kill

相关设置

1
2
3
4
5
6
7
8
9
10
11
12
13
echo 1 >  /proc/sys/kernel/hung_task_panic  

----- 处于D状态的超时时间,默认是120s
$ cat /proc/sys/kernel/hung_task_timeout_secs

----- 发现hung task之后是否触发panic操作
$ cat /proc/sys/kernel/hung_task_panic

----- 每次检查的进程数
$ cat /proc/sys/kernel/hung_task_check_count

----- 为了防止日志被刷爆,设置最多的打印次数
$ cat /proc/sys/kernel/hung_task_warnings

这个参数可以用来处理 D 状态进程

内核在 3.10.0 版本之后提供了 hung task 机制,用来检测系统中长期处于 D 状体的进程,如果存在,则打印相关警告和进程堆栈。

如果配置了 hung_task_panic ,则会直接发起 panic 操作,然后结合 kdump 可以搜集到相关的 vmcore 文件,用于定位分析。

其基本原理也很简单,系统启动时会创建一个内核线程 khungtaskd,定期遍历系统中的所有进程,检查是否存在处于 D 状态且超过 120s 的进程,如果存在,则打印相关警告和进程堆栈,并根据参数配置决定是否发起 panic 操作。

查看D进程出现的原因:

image-20230814112500971

这个堆栈能看到进程在哪里 D 住了,但不一定是根本原因,有可能是被动进入 D 状态的

image-20230814112742429

T 状态进程

kill -CONT pid 来恢复

jmap -heap/histo和大家使用-F参数是一样的,底层都是通过serviceability agent来实现的,并不是jvm attach的方式,通过sa连上去之后会挂起进程,在serviceability agent里存在bug可能导致detach的动作不会被执行,从而会让进程一直挂着,可以通过top命令验证进程是否处于T状态,如果是说明进程被挂起了,如果进程被挂起了,可以通过kill -CONT [pid]来恢复。

路由

『你所规划的路由必须要是你的网卡 (如 eth0) 或 IP 可以直接沟通 (broadcast) 的情况』才行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$sudo route add -net 11.164.191.0  gw 11.164.191.247 netmask 255.255.255.0 bond0
SIOCDELRT: No such process // 从bond0没法广播到 11.164.191.247

$sudo route add -net 11.164.191.0 gw 100.81.183.247 netmask 255.255.255.0 bond0.700
SIOCADDRT: Network is unreachable //从bond0.700 没法广播到 100.81.183.247,实际目前从bond0.700没法广播到任何地方

$sudo route add** **11.164.191.247** **dev** **bond0.700

$sudo route add -net 11.164.191.0 **gw 100.81.183.247** netmask 255.255.255.0 bond0.700
SIOCADDRT: Network is unreachable //从bond0.700 没法广播到 100.81.183.247

$sudo route add -net 11.164.191.0 gw 11.164.191.247 netmask 255.255.255.0 bond0
SIOCADDRT: Network is unreachable//从bond0没法广播到 11.164.191.247但是从bond0.700可以

$sudo route add -net 11.164.191.0 **gw 11.164.191.247** netmask 255.255.255.0 bond0.700

https://serverfault.com/questions/581159/unable-to-add-a-static-route-sioaddrt-network-is-unreachable

linux 2.6.32内核高精度定时器带来的cpu sy暴涨的“问题”

在 2.6.32 以前的内核里,即使你在java里写queue.await(1ns)之类的代码,其实都是需要1ms左右才会执行的,但.32以后则可以支持ns级的调度,对于实时性要求非常非常高的性能而言,这本来是个好特性。

1
cat /proc/timer_list | grep .resolution

可以通过在 /boot/grub2/grub.cfg 中相应的kernel行的最后增加highres=off nohz=off来关闭高精度(不建议这样做,最好还是程序本身做相应的修改)

后台执行

将任务放到后台,断开ssh后还能运行:

  1. “ctrl-Z”将当前任务挂起;
  2. “disown -h”让该任务忽略 SIGHUP 信号(不会因为掉线而终止执行);
  3. “bg”让该任务在后台恢复运行。

Linux 进程调度

Linux的进程调度有一个不太为人熟知的特性,叫做wakeup affinity,它的初衷是这样的:如果两个进程频繁互动,那么它们很有可能共享同样的数据,把它们放到亲缘性更近的scheduling domain有助于提高缓存和内存的访问性能,所以当一个进程唤醒另一个的时候,被唤醒的进程可能会被放到相同的CPU core或者相同的NUMA节点上。

这个特性缺省是打开的,它有时候很有用,但有时候却对性能有伤害作用。设想这样一个应用场景:一个主进程给成百上千个辅进程派发任务,这成百上千个辅进程被唤醒后被安排到与主进程相同的CPU core或者NUMA节点上,就会导致负载严重失衡,CPU忙的忙死、闲的闲死,造成性能下降。https://mp.weixin.qq.com/s/DG1v8cUjcXpa0x2uvrRytA

tty

tty(teletype–最早的一种终端设备,远程打字机) stty 设置tty的相关参数

tty都在 /dev 下,通过 ps -ax 可以看到进程的tty;通过tty 可以看到本次的终端

/dev/pty(Pseudo Terminal) 伪终端

/dev/tty 控制终端

远古时代tty是物理形态的存在

img

PC时代,物理上的terminal已经没有了(用虚拟的伪终端代替,pseudo tty, 简称pty),相对kernel增加了shell,这是terminal和shell容易混淆,他们的含义

img

实际像如下图的工作协作:

Diagram

rsync

1
2
3
4
5
6
7
将本地yum备份到150上的/data/yum/ 下
rsync -arv ./yum/ root@11.167.60.150:/data/yum/

走ssh的8022端口把目录备份到本地
rsync -e 'ssh -p 8022' -arv gcsql@10.2.3.4:/home/gcsql/doc/ ./

rsync -arv -e "ssh -i /home/admin/.ssh/id_dsa.per " root@1.1.20.24:/home/xijun.rxj/ /home/admin/bak/

-a、--archive参数表示存档模式,保存所有的元数据,比如修改时间(modification time)、权限、所有者等,并且软链接也会同步过去。

--delete参数删除只存在于目标目录、不存在于源目标的文件,即保证目标目录是源目标的镜像。

-i参数表示输出源目录与目标目录之间文件差异的详细情况。

--link-dest参数指定增量备份的基准目录。

-n参数或--dry-run参数模拟将要执行的操作,而并不真的执行。配合-v参数使用,可以看到哪些内容会被同步过去。

--partial参数允许恢复中断的传输。不使用该参数时,rsync会删除传输到一半被打断的文件;使用该参数后,传输到一半的文件也会同步到目标目录,下次同步时再恢复中断的传输。一般需要与--append或--append-verify配合使用。

--progress参数表示显示进展。

-r参数表示递归,即包含子目录。

-v参数表示输出细节。-vv表示输出更详细的信息,-vvv表示输出最详细的信息。

Shebang

Shebang 的东西 #!/bin/bash

对 Shebang 的处理是内核在进行。当内核加载一个文件时,会首先读取文件的前 128 个字节,根据这 128 个字节判断文件的类型,然后调用相应的加载器来加载。

ELF(Executable and Linkable Format)

对应windows下的exe

修改启动参数

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
$cat change_kernel_parameter.sh 
#cat /sys/devices/system/cpu/vulnerabilities/*
#grep '' /sys/devices/system/cpu/vulnerabilities/*
#https://help.aliyun.com/document_detail/102087.html?spm=a2c4g.11186623.6.721.4a732223pEfyNC

#cat /sys/kernel/mm/transparent_hugepage/enabled
#transparent_hugepage=always
#noibrs noibpb nopti nospectre_v2 nospectre_v1 l1tf=off nospec_store_bypass_disable no_stf_barrier mds=off mitigations=off
#追加nopti nospectre_v2到内核启动参数中
sudo sed -i 's/\(GRUB_CMDLINE_LINUX=".*\)"/\1 nopti nospectre_v2 nospectre_v1 l1tf=off nospec_store_bypass_disable no_stf_barrier mds=off mitigations=off transparent_hugepage=always"/' /etc/default/grub

//从修改的 /etc/default/grub 生成 /boot/grub2/grub.cfg 配置
sudo grub2-mkconfig -o /boot/grub2/grub.cfg

#limit the journald log to 500M
sed -i 's/^#SystemMaxUse=$/SystemMaxUse=500M/g' /etc/systemd/journald.conf
#重启系统
#sudo reboot

## 选择不同的kernel启动
#sudo grep "menuentry " /boot/grub2/grub.cfg | grep -n menu
##grub认的index从0开始数的
#sudo grub2-reboot 0; sudo reboot

$cat /sys/kernel/mm/transparent_hugepage/enabled
always [madvise] never

制作启动盘

Windows 上用 UltraISO 烧制,Mac 上就比较简单了,直接用 dd 就可以搞

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ diskutil list
/dev/disk6 (external, physical):
#: TYPE NAME SIZE IDENTIFIER
0: *31.5 GB disk6

# 找到 U 盘的那个设备,umount
$ diskutil unmountDisk /dev/disk3

# 用 dd 把 ISO 文件写进设备,注意这里是 rdisk3 而不是 disk3,在 BSD 中 r(IDENTIFIER)
# 代表了 raw device,会快很多
$ sudo dd if=/path/image.iso of=/dev/rdisk3 bs=1m

# 弹出 U 盘
$ sudo diskutil eject /dev/disk3

Linux 下制作步骤

1
2
3
umount /dev/sdn1
sudo mkfs.vfat /dev/sdn1
dd if=/polarx/uniontechos-server-20-1040d-amd64.iso of=/dev/sdn1 status=progress

性能

为保证服务性能应选用 performance 模式,将 CPU 频率固定工作在其支持的最高运行频率上,不进行动态调节,操作命令为 cpupower frequency-set --governor performance。

常用命令

  • dmesg | tail
  • vmstat 1
  • mpstat -P ALL 1
  • pidstat 1
  • iostat -xz 1
  • free -m
  • sar -n DEV 1
  • sar -n TCP,ETCP 1

案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//检查sda磁盘中哪个应用程序占用的io比较高
pidstat -d 1

//分析应用程序中哪一个线程占用的io比较高
pidstat -dt -p 73739 1 执行两三秒即可,得到74770线程io高

//分析74770这个线程在干什么
perf trace -t 74770 -o /tmp/tmp_aa.pstrace
cat /tmp/tmp_aa.pstrace
2850.656 ( 1.915 ms): futex(uaddr: 0x653ae9c4, op: WAIT|PRIVATE_FLAG, val: 1) = 0
2852.572 ( 0.001 ms): futex(uaddr: 0x653ae990, op: WAKE|PRIVATE_FLAG, val: 1) = 0
2852.601 ( 0.001 ms): clock_gettime(which_clock: MONOTONIC, tp: 0xfff7bd470f68) = 0
2852.690 ( 0.040 ms): write(fd: 159, buf: 0xd7a30020, count: 65536) = 65536
2852.796 ( 0.001 ms): clock_gettime(which_clock: MONOTONIC, tp: 0xfff7bd470f68) = 0
2852.798 ( 0.001 ms): clock_gettime(which_clock: MONOTONIC, tp: 0xfff7bd470f58) = 0
2852.939 ( 0.001 ms): clock_gettime(which_clock: MONOTONIC, tp: 0xfff7bd470f38) = 0
2852.950 ( 0.001 ms): clock_gettime(which_clock: MONOTONIC, tp: 0xfff7bd470f68) = 0
2852.977 ( 0.001 ms): clock_gettime(which_clock: MONOTONIC, tp: 0xfff7bd470f68) = 0
2853.029 ( 0.035 ms): write(fd: 64, buf: 0xcd51e020, count: 65536) = 65536
2853.164 ( 0.001 ms): clock_gettime(which_clock: MONOTONIC, tp: 0xfff7bd470f68) = 0
2853.167 ( 0.001 ms): clock_gettime(which_clock: MONOTONIC, tp: 0xfff7bd470f58) = 0
2853.302 ( 0.001 ms): clock_gettime(which_clock: MONOTONIC, tp: 0xfff7bd470f38) = 0

内存——虚拟内存参数

  • dirty_ratio 百分比值。当脏的 page cache 总量达到系统内存总量的这一百分比后,系统将开始使用 pdflush 操作将脏的 page cache 写入磁盘。默认值为 20%,通常不需调整。对于高性能 SSD,比如 NVMe 设备来说,降低其值有利于提高内存回收时的效率。
  • dirty_background_ratio 百分比值。当脏的 page cache 总量达到系统内存总量的这一百分比后,系统开始在后台将脏的 page cache 写入磁盘。默认值为 10%,通常不需调整。对于高性能 SSD,比如 NVMe 设备来说,设置较低的值有利于提高内存回收时的效率。

I/O 调度器

I/O 调度程序确定 I/O 操作何时在存储设备上运行以及持续多长时间。也称为 I/O 升降机。对于 SSD 设备,宜设置为 noop。

1
echo noop > /sys/block/${SSD_DEV_NAME}/queue/scheduler

磁盘挂载参数

noatime 读取文件时,将禁用对元数据的更新。它还启用了 nodiratime 行为,该行为会在读取目录时禁用对元数据的更新。

Unix Linux关系

image-20211210085124387

img

linux 发行版关系

细数各家linux之间的区别_软件应用_什么值得买

Fedora:基于Red Hat Linux,在Red Hat Linux终止发行后,红帽公司计划以Fedora来取代Red Hat Linux在个人领域的应用,而另外发行的Red Hat Enterprise Linux取代Red Hat Linux在商业应用的领域。Fedora的功能对于用户而言,它是一套功能完备、更新快速的免费操作系统,而对赞助者Red Hat公司而言,它是许多新技术的测试平台,被认为可用的技术最终会加入到Red Hat Enterprise Linux中。Fedora大约每六个月发布新版本。

不同发行版几乎采用了不同包管理器(SLES、Fedora、openSUSE、centos、RHEL使用rmp包管理系统,包文件以RPM为扩展名;Ubuntu系列,Debian系列使用基于DPKG包管理系统,包文件以deb为扩展名。)

69年Unix诞生在贝尔实验室,80年 DARPA(国防部高级计划局)请人在Unix实现全新的TCP、IP协议栈。ARPANET最先搞出以太网

Linux 从91年到95年处于成长期,真正大规模应用是Linux+Apache提供的WEB服务被大家大规模采用

rpm: centos/fedora/suse

deb: debian/ubuntu/uos(早期基于ubuntu定制,后来基于debian定制,再到最近开始直接基于kernel定制)

ARPANET:高等研究計劃署網路(英語:Advanced Research Projects Agency Network),通称阿帕网(英語:ARPANET)是美國國防高等研究計劃署开发的世界上第一个运营的封包交换网络,是全球互联网的鼻祖。

TCP/IP:1974年,卡恩和瑟夫带着研究成果,在IEEE期刊上,发表了一篇题为《关于分组交换的网络通信协议》的论文,正式提出TCP/IP,用以实现计算机网络之间的互联。

在1983年,美国国防部高级研究计划局决定淘汰NCP协议(ARPANET最早使用的协议),TCP/IP取而代之。

Deepin UOS

****Deepin 与统信 UOS 类似于红帽的 Fedora 与 RHEL 的上下游关系,Deepin 依然保持着原来的社区运营模式,而统信 UOS 则是基于社区版 Deepin 构建的商业发行版,为 Deepin 挖掘更多的商业机会和更大的商业价值,进而反哺社区,形成良性循环****。

参考文章

https://www.cnblogs.com/kevingrace/p/8671964.html

https://www.jianshu.com/p/ac3e7009a764

B 站哈工大操作系统视频地址:https://www.bilibili.com/video/BV1d4411v7u7?from=search&seid=2361361014547524697

B 站清华大学操作系统视频地址:https://www.bilibili.com/video/BV1js411b7vg?from=search&seid=2361361014547524697

Linux 工具:点的含义 英文版

linux cp实现强制覆盖

https://wangdoc.com/bash/startup.html

编写一个最小的 64 位 Hello World

ipmitool 和 BIOS

发表于 2020-01-01 | 分类于 Linux

ipmitool 和 BIOS

什么是 IPMI

IPMI(智能平台管理接口),Intelligent Platform Management Interface 的缩写。原本是一种Intel架构的企业系统的周边设备所采用的一种工业标准。IPMI亦是一个开放的免费标准,用户无需支付额外的费用即可使用此标准。

IPMI 能够横跨不同的操作系统、固件和硬件平台,可以智能的监视、控制和自动回报大量服务器的运作状况,以降低服务器系统成本。

1998年Intel、DELL、HP及NEC共同提出IPMI规格,可以透过网路远端控制温度、电压。

2001年IPMI从1.0版改版至1.5版,新增 PCI Management Bus等功能。

2004年Intel发表了IPMI 2.0的规格,能够向下相容IPMI 1.0及1.5的规格。新增了Console Redirection,并可以通过Port、Modem以及Lan远端管理伺服器,并加强了安全、VLAN 和刀锋伺服器的支援性。

Intel/amd/hygon 基本都支持 ipmitool,看起来ARM 支持的接口也许不一样

BMC(Baseboard Management Controller)即我们常说的带外系统,是在机器上电时即完成自身初始化,开始运行。其系统可在standby电模式下工作。所以,通过带外监控服务器硬件故障,不受OS存活状态影响,可实现7*24小时无间断监控,甚至我们可以通过带外方式,精确感知带内存活,实现OS存活监控。

BMC在物理形态上,由一主嵌入式芯片+系列总线+末端芯片组成的一个硬件监控&控制系统,嵌入式芯片中运行嵌入式Linux操作系统,负责整个BMC系统的资源协调及用户交互,核心进程是IPMImain进程,实现了全部IPMI2.0协议的消息传递&处理工作。

ipmitool 用法

基本步骤:

  1. 查看当前值:ipmitool raw 0x3e 0x5f 0x00 0x11 (非必要,列出目前BIOS中的值)
  2. 打开配置开关(让BIOS进入可配置,默认不可配置):ipmitool raw 0x3e 0x5c 0x00 0x01 0x81
  3. 修改某个值,比如将numa 设置为on:ipmitool raw 0x3e 0x5c 0x05 0x01 0x81
  4. 查看修改后的值:ipmitool raw 0x3e 0x5d 0x05 0x01 (必须要)
  5. 最后reboot机器新的值就会apply到BIOS中

ipmitool使用基本语法

1
2
         固定-不变 0x5c修改   要修改的项    长度(一搬都是01)    新的值(0x81 表示on、0x80表示off)
ipmitool raw 0x3e 0x5c index 0x01 value

第1/2个参数raw 0x3e 固定不变

第三个参数表示操作可以是:

  • 0x5c 修改
  • 0x5f 查看BIOS中的当前值(海光是这样,intel不是)
  • 0x5d 查询即将写入的值(修改后没有写入 0x5f 看到的是老值)

第四个参数Index表示需要修改的配置项(具体见后表)

第五个参数 0x01 表示值的长度,一般固定不需要改

value 表示值,0x81表示enable; 0x80表示disable

ipmitool带外设置步骤

1)设置valid flag:

ipmitool -I lan -U admin -P admin -H 192.168.1.10 raw 0x3e 0x5c 0x00 0x01 0x81

2) 设置对应的选项:

ipmitool -I lan -U admin -P admin -H 192.168.1.10 raw 0x3e 0x5c index 0x01 Data —- index 和Data参考下述表格;

3)重启CN:

ipmitool -I lan -U admin -P admin -H 192.168.1.10 power reset

4)读取当前值:

ipmitool -I lan -U admin -P admin -H 192.168.1.10 raw 0x3e 0x5f index 0x01

如moc机型,读取CN的 Numa值:

ipmitool -I lan -U admin -P admin -H 192.168.1.10 raw 0x3e 0x5f 0x05 0x01

确认是否设置成功

查询要写入的新值:ipmitool 0x3e 0x5d 0x00 0x11

返回值,如:11 81 81 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

​ 第一个byte 表示查询数量,表示查询0x11个设置项;

  第二个byte 表示index=0的值,即Configuration,必须保证是0x81,才能进行重启,否则设置不生效;

​ 第三个byte 表示index=1的值,即Turbo,表示要设置为0x81;

​ 剩余byte依次类推……….

​ 未设置新值的index对应值是00,要设置的index其对应值为Data(步骤3的设置值);

海光服务器修改案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//海光+alios下 第二列为:0x5c 修改、0x5f BIOS中的查询、0x5d 查询即将写入的值 
//0x5f 查询BIOS中的值

#ipmitool raw 0x3e 0x5f 0x00 0x11
11 81 81 81 81 80 81 80 81 81 80 81 81 00 00 81
80 81

//还没有写入任何新值
#ipmitool raw 0x3e 0x5d 0x00 0x11
11 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00

//enable numa
#ipmitool raw 0x3e 0x5c 0x00 0x01 0x81
#ipmitool raw 0x3e 0x5c 0x05 0x01 0x81

//enable boost
#ipmitool raw 0x3e 0x5c 0x01 0x01 0x81

#ipmitool raw 0x3e 0x5d 0x01 0x01
11 81
//关闭 SMT
#ipmitool raw 0x3e 0x5c 0x02 0x01 0x80

#ipmitool raw 0x3e 0x5d 0x02 0x01
11 81

BIOS中选项的对应关系

Intel服务器

Name Index Data Length(Bytes) Data (不在列表中则为无效值)
Configuration 0x00 1 0x81 – valid Flag 0x82 – Restore Default Value (恢复为PRD定义的默认值)0x00 BIOS在读取设定后,会发送index=0x00,data=0x00的命令给BMC,BMC应清零所有参数值。
Turbo 0x01 1 0x80 – disable0x81 – enable
HT 0x02 1 0x80 – disable0x81 – enable
VT 0x03 1 0x80 – disable0x81 – enable
EIST 0x04 1 0x80 – disable0x81 – enable
Numa 0X05 1 0x80 – disable 0x81 - enable
Vendor Change 0x06 1 0x80 – disable0x81 – enable
VT-d 0x07 1 0x80 – disable0x81 – enable
SRIOV 0x08 1 0x80 – disable0x81 – enable
Active Video 0x09 1 0x80 – Onboard0x81 – PCIe
Local HDD Boot 0x0A 1 0x80 – disable0x81 – enable
Hotkey support 0x0B 1 0x80 – disable0x81 – enable
Intel Speed Select 0x0C 1 0x80-Disable0x81-Config 10x82-Config 2
IMS 0x0D 1 0x80-Disable0x81-Enable
TPM 0x0E 1 0x80-Disable0x81-Enabled0x83-Enable&Clear TPM
Power off remove 0x0F 1 0x80 – disable – 响应命令0x81 – enable –不响应命令
BIOS BOOT MODE 0x10 1 0x80 – Legacy0x81 – UEFI
Active Cores 0x11 1 0x80 - Default Core Number0x81 - Active 1 Core0x82 - Active 2 Cores0x83 - Active 3 Cores…0xFE - Active 126 Cores
C State 0x12 1 0x80-Disable0x81-Enable
HWPM 0x13 1 0x80-Disable0x81-Native mode0x82-OOB Mode0x83-Native mode Without Legacy support
Intel SgxSW Guard Extensions (SGX) 0x14 1 0x80-Disable0x81-Enable
SGX PRMRR Size 0x15 1 0X80-[00]No valid PRMRR size 0X81-[40000000]1G0X82-[80000000]2G0X83-[100000000]4G0X84-[200000000]8G0X85-[400000000]16G0X86-[800000000]32G0X87-[1000000000]64G0X88-[2000000000]128G0X89-[4000000000]256G0X8A-[8000000000]512G
SGX Factory Reset 0x16 0x80-Disable0x81-Enable
0x17 预留
CPU0_IOU0 (IIO PCIe Br1) 0x18 1 0x80 – x4x4x4x4 0x81 – x4x4x8 0x82 – x8x4x4 0x83 – x8x8 0x84 – x16 0x85 - Auto
CPU0_IOU1 (IIO PCIe Br2) 0x19 1 同上
CPU0_IOU2 (IIO PCIe Br3) 0x1a 1 同上
CPU0_IOU3 (IIO PCIe Br4) 0x1b 1 同上
CPU0_IOU4 (IIO PCIe Br5) 0x1c 1 同上
CPU1_IOU0 (IIO PCIe Br1) 0x1d 1 同上
CPU1_IOU1 (IIO PCIe Br2) 0x1e 1 同上
CPU1_IOU2 (IIO PCIe Br3) 0x1f 1 同上
CPU1_IOU3 (IIO PCIe Br4) 0x20 1 同上
CPU1_IOU4 (IIO PCIe Br5) 0x21 1 同上
CPU2_IOU0 (IIO PCIe Br1) 0x22 1 同上
CPU2_IOU1 (IIO PCIe Br2) 0x23 1 同上
CPU2_IOU2 (IIO PCIe Br3) 0x24 1 同上
CPU2_IOU3 (IIO PCIe Br4) 0x25 1 同上
CPU2_IOU4 (IIO PCIe Br5) 0x26 1 同上
CPU3_IOU0 (IIO PCIe Br1) 0x27 1 同上
CPU3_IOU1 (IIO PCIe Br2) 0x28 1 同上
CPU3_IOU2 (IIO PCIe Br3) 0x29 1 同上
CPU3_IOU3 (IIO PCIe Br4) 0x2a 1 同上
CPU3_IOU4 (IIO PCIe Br5) 0x2b 1 同上
SGXLEPUBKEYHASHx Write Enable 0x2C 1 0x80-Disable0x81-Enable
SubNuma 0x2D 1 0x80-Disabled0x81-SN2
VirtualNuma 0x2E 1 0x80-Disabled0x81-Enabled
TPM Priority 0x2F 1
TDX 0x30 1 0x80 - Disabled0x81 - Enabled
Select Owner EPOCH input type 0x31 1 0x81-Change to New Random Owner EPOCHs0x82-Manual User Defined Owner EPOCHs
Software Guard Extensions Epoch 0 0x32 1
Software Guard Extensions Epoch 1 0x33 1

AMD服务器

Name Index Data Length(Bytes) Data (不在列表中则为无效值) 支持项目
Configuration 0x00 1 0x81 – valid Flag 0x82 – Restore Default Value (恢复为PRD定义的默认值) RomeMilan
Core Performance Boost 0x01 1 0x80 – disable0x81 – enable RomeMilan
SMT Mode 0x02 1 0x80 – disable0x81 – enable RomeMilan
SVM Mode 0x03 1 0x80 – disable0x81 – enable RomeMilan
EIST 0x04 1 0x80 (AMD默认支持智能调频但无此选项) RomeMilan
NUMA nodes per socket 0X05 1 0x80 – NPS0 0x81 – NPS1 0x82 – NPS2 0x83 – NPS4 (开)0x87 – Auto(Auto为NPS1) RomeMilan
Vendor Change 0x06 1 0x80 – disable0x81 – enable RomeMilan
IOMMU 0x07 1 0x80 – disable0x81 – enable0x8F – Auto RomeMilan
SRIOV 0x08 1 0x80 – disable0x81 – enable RomeMilan
Active Video 0x09 1 0x80 – Onboard0x81 – PCIe RomeMilan
Local HDD Boot 0x0A 1 0x80 – disable0x81 – enable RomeMilan
Hotkey support 0x0B 1 0x80 – disable0x81 – enable RomeMilan
Intel Speed Select 0x0C 1 0x80 (AMD无此选项) RomeMilan
IMS 0x0D 1 0x80 (AMD暂未做IMS功能) RomeMilan
TPM 0x0E 1 0x80 – disable0x81 – enable0x83 – enable & TPM clear RomeMilan
Power off remove 0x0F 1 0x80 (AMD暂未做此功能) RomeMilan
BIOS BOOT MODE 0x10 1 0x80 – Legacy0x81 – UEFI RomeMilan

海光服务器

Name Index Data Length(Bytes) Data (不在列表中则为无效值) 支持项目
Configuration 0x00 1 0x81 – valid Flag 0x82 – Restore Default Value (恢复为PRD定义的默认值) 海光2
Core Performance Boost 0x01 1 0x80 – disable0x81 – enable 海光2
SMT 0x02 1 0x80 – disable0x81 – enable 海光2
SVM 0x03 1 0x80 – disable0x81 – enable 海光2
P-State Control 0x04 1 0x80 – Performance0x81 – Normal 海光2
Memory Interleaving 0X05 1 0x80 – Socket (关numa) 0x81 - channel(8 node) 海光2
Vendor Change 0x06 1 0x80 – disable0x81 – enable 海光2
IOMMU 0x07 1 0x80 – disable0x81 – enable 海光2
SRIOV 0x08 1 0x80 – disable0x81 – enable 海光2
Onboard VGA 0x09 1 0x80 – Onboard0x81 – PCIe 海光2
Local HDD Boot 0x0A 1 0x80 – disable0x81 – enable 海光2
Hotkey support 0x0B 1 0x80 – disable0x81 – enable 海光2
Hygon平台没有此选项 0x0C 1 0x80-Disable0x81-Config 10x82-Config 2 不支持
Hygon平台没有此选项 0x0D 1 0x80-Disable0x81-Enable 不支持
TPM 0x0E 1 0x80-Disable0x81-Enabled0x83-Enable&Clear TPM 海光2
Power off remove 0x0F 1 0x80 – disable – 响应命令0x81 – enable –不响应命令 海光2
Boot option Filter 0x10 1 0x80 – Legacy0x81 – UEFI 海光2

2010到2020这10年的碎碎念念

发表于 2020-01-01 | 分类于 others

2010到2020这10年的碎碎念念

来自网络的一些数据

这十年,中国的人均GDP从大约3300美金干到了9800美金。这意味着:更多的中国人脱贫,更多的中国人变成了中产。这是这一轮消费升级的核心原动力,没有之一。

这十年,中国的进出口的总额从2009年占GDP的44.86%,降至34.35%。

互联网从美国copy开始变成创新、走在前列,因为有庞大的存量市场

2010年,一个数据发生了逆势波动。那就是中国的适龄小学人口增速。在此之前从1997年后,基本呈负增长。这是因为中国80后家长开始登上历史舞台。这带动了诸多产业的蓬勃发展,比如互联网教育,当然还有学区房。

10年吉利收购沃尔沃,18年吉利收购戴姆勒10%的股份。

微信崛起、头条崛起、百度走下神坛。美团、pdd崛起

12年2月6号的王护士长外逃美国大使馆也让大家兴奋了,11年的郭美美红十字会事件快要被忘记了,但是也让大家对慈善事件更加警惕,倒是谅解了汶川地震的王石10块捐款事件,不过老王很快因为娶了年轻的影星田朴珺一下子人设坍塌,大家更热衷老王的负面言论了。

温州动车事件让高铁降速了

我爸是李刚、药家鑫、李天一、邓玉娇(09年),陈冠希艳照门、三鹿奶粉、汶川地震、奥运会(08年)

2018年:中美贸易站、问题疫苗、个税改革、中兴被美制裁,北京驱赶低端人口,鸿茅药酒,p2p暴雷,昆山反杀案,相互宝

2015年:雾霾、柴静纪录片《穹顶之下》,屠呦呦诺贝尔奖,放开二胎

2014年:东莞扫黄、马航370事件;周师傅被查、占中

2013年:劳教正式被废除,想起2003年的孙志刚事件废除收容制度

2012年:方韩之争,韩寒走下神坛

2011年:日本海啸地震,中国抢盐事件;郭美美,温州动车

2010年:google退出中国,上海世博会开幕,富士康N连跳楼事件;我爸是李刚,腾讯大战360

自我记忆

刚看到有人在说乐清钱云会事件,一晃10年了,10年前微博开始流行改变了好多新闻、热点事件的引爆方式。

这十年BBS、门户慢慢在消亡,10年前大家都知道三大门户网站和天涯,现在的新网民应该知道的不多了。

影响最大的还是移动网络的崛起,这也取决于4G和山寨机以及后来的小米手机,真正给中国的移动互联网带来巨大的红利,注入的巨大的增长。
我自己对移动互联网的判断是极端错误的,即使09年我就开始用上了iphone手机,那又怎么样,看问题还是用静态的视觉观点。手机没有键盘、手机屏幕狭小,这些确实是限制,到2014年我还想不明白为什么要在手机上购物,比较、看物品图片太不方便了,结果便利性秒杀了这些不方便;只有手机的群体秒杀了办公室里的白领,最后大家都很高兴地用手机购物了,甚至PC端bug更多,更甚至有些网站不提供PC端。

移动网络的崛起和微信的成功也相辅相成的,在移动网络时代每个人都有自己的手机,所以账号系统的打通不再是问题,尤其是都被微信这个移动航母在吞噬,其它公司都活在微信的阴影里。

当然移动支付的崛起就理所当然了。

即使今天网上购物还是PC上要方便,那又怎么样,很多时候网上购物都是不在电脑前的零碎时间。

10多年前第一次看到智能手机是室友购买的多普达,20年前也是这个室友半夜里很兴奋地播报台湾大选,让我知道了台湾大选这个事情。

基本的价值观、世界观,没怎么改变,不应该是年龄大了僵化了,应该是掌握信息的手段和能力增强了,翻墙获取信息也很容易,基本的逻辑还在也没那么容易跑偏了。可能就是别人看到的年纪大了脑子僵化了吧,自我感觉不一定对。

最近10年经济发展的非常好,政府对言论的控制越来越精准,舆论引导也非常”成功”,所以网络上看到这5年和5年前基本差别很大,5年前公知是个褒义词,5年后居然成了贬义词。

房价自然是这10年最火的话题,07年大家开始感觉到房价上涨快、房价高,08年金融危机本来是最好的机会,结果4万亿刺激下09年年底房价开始翻倍,到10年面对翻倍了的房价政府、媒体、老百姓都在喊高,实际也只是横盘,13-14年小拉一波,16年涨价去库存再大拉一波。基本让很多人绝望了

这十年做的最错的事情除了没有早点买房外就是想搞点投资收入投了制造外加炒股,踩点能力太差了,虽然前5年像任志强一样一直看多房价的不多,这个5年都被现实教育了,房价也基本到头了。

工作上应该更早地、坚定地进入互联网、移动互联网,这10年互联网对人才的需求实在太大了,虽然最终能伴随公司成长的太少,毕竟活下来长大的公司不多。

Google退出中国、看着小杨同学和一些同事移民、360大战QQ、诺贝尔和平奖、华为251事件都算是自己在一些公众事情上投入比较多的。非常不舍google的离开,这些年也基本还是只用google,既是无奈中用下百度也还是觉得搜不到什么有效信息;好奇移民的想法和他们出去后的各种生活;360跟QQ大战的时候觉得腾讯的垄断太牛叉了,同时认为可能360有这种资源的话会更作恶和垄断的更厉害,至少腾讯还是在乎外面的看法和要面子的;LXB到现在也是敏感词,直到病死在软禁中,这些年敏感词越来越多,言论的控制更严厉了;华为251也是个奇葩事件了,暴露了资本家的粗野和枉法。

自己工作上跳槽一次,继续做一个北漂。公司对自己的方法论改变确实比较大,近距离看到了一些成功因素方面的逻辑(更有效的激励和企业文化)。

经历了从外企到私企,从小公司到大公司的不同,外企英语是天花板,也看到了华为所谓的狼性、在金钱激励下的狼性,和对企业文化的维护,不能否认90%以上的人工作是为了钱

这几年也开始习惯写技术文章来总结了,这得益于Markdown+截图表述的便利,也深刻感受到深抠,然后总结分享的方法真的很好(高斯学习方法),也体会到了抓手、触类旁通的运用。10年前在搜狐blog写过一两年的博客放弃的很快,很难一直有持续的高水平总结和输出。

10年前还在比较MSN和QQ谁更好(我是坚定站在QQ这边的),10年后MSN再也看不见了,QQ也有了更好的替代工具微信。用处不大的地方倒是站对了,对自己最有用的关键地方都站错了。

10年前差点要去豆瓣,10年后豆瓣还活着,依然倔强地保持自己的品味,这太难得了。相反十年前好用得不得了的RSS订阅,从抓虾转到google reader再到feedly好东西就是活得这么艰难。反过来公众号起来了、贴吧式微了,公众号运作新闻类是没问题的(看完就过),但是对技术类深度一点的就很不合适了,你看看一篇文章24小时内的阅读量占据了98%以上,再到后面就存亡了!但是公众号有流量,流量可以让大家跪在地上。

10年前啃老是被看不起的,10年后早结婚、多啃老也基本成了这10年更对的事情,结婚得买房,啃老买得更早,不对的事情变对了(结婚早没错)。

很成功地组织了一次同学20周年的聚会,也看到了远则亲、近则仇的现实情况,自己组织统筹能力还可以。

情绪控制能力太差、容易失眠。这十年爱上了羽毛球和滑雪,虽然最近几年滑雪少了。

体会到自小贫穷带来的一些抠门的坏习惯。

2015年的股票大跌让自己很痛苦,这个过程反馈出来的不愿意撒手、在股市上的鸵鸟方式,股市上总是踩不到正确的点。割肉太难,割掉的总是错误的。

15、16年我认为云计算不怎么样,觉得无非就是新瓶装旧酒,现在云计算不再有人质疑了,即使现在都还是亏钱。

当然我也质疑过外卖就是一跑腿的,确实撑不起那么大的盘子,虽然没有像团购一样消亡,基本跟共享单车一样了,主要因为我是共享单车的重度用户,而我极端不喜欢外卖,所以要站出来看问题、屁股坐在哪边会严重影响看法,也就是不够客观。

网约车和移动支付一起在硝烟中混战

电动车开始起来,主要受政府弯道超车的刺激,目前看取决于自有充电位(适合三四线城市),可是三四线城市用户舍不得花这个溢价,汽油车都还没爽够呢。

对世界杯不再那么关注,对AlphaGo的新闻倒是很在意了。魏则西事件牢牢地把百度钉死在耻辱柱上。

随着12306的发展和高铁的起来,终于过年回家的火车票不用再靠半夜排队了。

2019年年末行政强制安装ETC,让我想起20年前物理老师在课堂上跟我们描述的将来小汽车走高速公路再也不用停下来收费了,会自动感应,开过去就自动扣钱了。我一直对这个未来场景念念不忘,最近10年我经常问别人为什么不办ETC,这个年底看到的是行政命令下的各种抱怨。

看到:

  • 老人、家人更不愿意听身边亲近人员的建议;

  • 老人思维为什么固化、怎么样在自己老后不是那样固化;

Linux内核版本升级,性能到底提升多少?拿数据说话

发表于 2019-12-24 | 分类于 Linux

Linux内核版本升级,性能到底提升多少?

背景

X 产品在公有云售卖一直使用的2.6.32的内核,有点老并且有些内核配套工具不能用,于是想升级一下内核版本。预期新内核的性能不能比2.6.32差

以下不作特殊说明的话都是在相同核数的Intel(R) Xeon(R) Platinum 8163 CPU @ 2.50GHz下得到的数据,最后还会比较相同内核下不同机型/CPU型号的性能差异。

场景都是用sysbench 100个并发跑点查。

结论

先说大家关心的数据,最终4.19内核性能比2.6.32好将近30%,建议大家升级新内核,不需要做任何改动,尤其是Java应用(不同场景会有差异)

本次比较的场景是Java应用的Proxy类服务,主要瓶颈是网络消耗,类似于MaxScale。后面有一个简单的MySQL Server场景下2.6.32和4.19的比较,性能也有33%的提升。

2.6.32性能数据

升级前先看看目前的性能数据好对比(以下各个场景都是CPU基本跑到85%)

image.png

一波N折的4.19

阿里云上默认买到的ALinux2 OS(4.19),同样配置跑起来后,tps只有16000,比2.6.32的22000差了不少,心里只能暗暗骂几句坑爹的货,看了下各项指标,看不出来什么问题,就像是CPU能力不行一样。如果这个时候直接找内核同学,估计他们心里会说 X 是个什么东西?是不是你们测试有问题,是不是你们配置的问题,不要来坑我,内核性能我们每次发布都在实验室里跑过了,肯定是你们的应用问题。

所以要找到一个公认的场景下的性能差异。幸好通过qperf发现了一些性能差异。

通过qperf来比较差异

大包的情况下性能基本差不多,小包上差别还是很明显

qperf -t 40 -oo msg_size:1  4.19 tcp_bw tcp_lat
tcp_bw:
    bw  =  2.13 MB/sec
tcp_lat:
    latency  =  224 us
tcp_bw:
    bw  =  2.15 MB/sec
tcp_lat:
    latency  =  226 us

qperf -t 40 -oo msg_size:1  2.6.32 tcp_bw tcp_lat
tcp_bw:
    bw  =  82 MB/sec
tcp_lat:
    latency  =  188 us
tcp_bw:
    bw  =  90.4 MB/sec
tcp_lat:
    latency  =  229 us

这下不用担心内核同学怼回来了,拿着这个数据直接找他们,可以稳定重现。

经过内核同学排查后,发现默认镜像做了一些安全加固,简而言之就是CPU拿出一部分资源做了其它事情,比如旁路攻击的补丁之类的,需要关掉(因为 X 的OS只给我们自己用,上面部署的代码都是X 产品自己的代码,没有客户代码,客户也不能够ssh连上X 产品节点)

去掉 melt、spec 能到20000, 去掉sonypatch能到21000 

关闭的办法在 /etc/default/grub 里 GRUB_CMDLINE_LINUX 配置中增加这些参数:

nopti nospectre_v2 nospectre_v1 l1tf=off nospec_store_bypass_disable no_stf_barrier mds=off mitigations=off

关掉之后的状态看起来是这样的:

$sudo cat /sys/devices/system/cpu/vulnerabilities/*
Mitigation: PTE Inversion
Vulnerable; SMT Host state unknown
Vulnerable
Vulnerable
Vulnerable: __user pointer sanitization and usercopy barriers only; no swapgs barriers
Vulnerable, STIBP: disabled

这块参考阿里云文档 和这个

开启漏洞补丁(性能差):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# uname -r
4.19.91-24.8.an8.x86_64

# cat /proc/cmdline
BOOT_IMAGE=(hd0,gpt2)/vmlinuz-4.19.91-24.8.an8.x86_64 root=UUID=ac9faf02-89c6-44d8-80b2-0f8ea1084fc3 ro console=tty0 crashkernel=auto console=ttyS0,115200 crashkernel=0M-2G:0M,2G-8G:192M,8G-:256M
[root@Anolis82 ~]# sudo cat /sys/devices/system/cpu/vulnerabilities/*
KVM: Mitigation: Split huge pages
Mitigation: PTE Inversion; VMX: conditional cache flushes, SMT vulnerable
Mitigation: Clear CPU buffers; SMT vulnerable
Mitigation: PTI
Mitigation: Speculative Store Bypass disabled via prctl and seccomp
Mitigation: usercopy/swapgs barriers and __user pointer sanitization
Mitigation: Full generic retpoline, IBPB: conditional, IBRS_FW, STIBP: conditional, RSB filling
Not affected
Mitigation: Clear CPU buffers; SMT vulnerable

关闭(性能好):

1
2
3
4
5
6
7
8
9
10
11
12
[root@Anolis82 ~]# sudo cat /sys/devices/system/cpu/vulnerabilities/*
KVM: Vulnerable
Mitigation: PTE Inversion; VMX: vulnerable
Vulnerable; SMT vulnerable
Vulnerable
Vulnerable
Vulnerable: __user pointer sanitization and usercopy barriers only; no swapgs barriers
Vulnerable, IBPB: disabled, STIBP: disabled
Not affected
Vulnerable
[root@Anolis82 ~]# cat /proc/cmdline
BOOT_IMAGE=(hd0,gpt2)/vmlinuz-4.19.91-24.8.an8.x86_64 root=UUID=ac9faf02-89c6-44d8-80b2-0f8ea1084fc3 ro console=tty0 crashkernel=auto console=ttyS0,115200 nopti nospectre_v2 nospectre_v1 l1tf=off nospec_store_bypass_disable no_stf_barrier mds=off mitigations=off crashkernel=0M-2G:0M,2G-8G:192M,8G-:256M

4.9版本的内核性能

但是性能还是不符合预期,总是比2.6.32差点。在中间经过几个星期排查不能解决问题,陷入僵局的过程中,尝试了一下4.9内核,果然有惊喜。

下图中对4.9的内核版本验证发现,tps能到24000,明显比2.6.32要好,所以传说中的新内核版本性能要好看来是真的,这下坚定了升级的念头,同时也看到了兜底的方案–最差就升级到4.9

image.png

多队列是指网卡多队列功能,也是这次升级的一个动力。看起来在没达到单核瓶颈前,网卡多队列性能反而差点,这也符合预期

继续分析为什么4.19比4.9差了这么多

4.9和4.19这两个内核版本隔的近,比较好对比分析内核参数差异,4.19跟2.6.32差太多,比较起来很困难。

最终仔细对比了两者配置的差异,发现ALinux的4.19中 transparent_hugepage 是 madvise ,这对Java应用来说可不是太友好:

$cat /sys/kernel/mm/transparent_hugepage/enabled
always [madvise] never

将其改到 always 后4.19的tps终于稳定在了28300

image.png

这个过程中花了两个月的一些其他折腾就不多说了,主要是内核补丁和transparent_hugepage导致了性能差异。

transparent_hugepage,在redis、mongodb、memcache等场景(很多小内存分配)是推荐关闭的,所以要根据不同的业务场景来选择开关。

透明大页打开后在内存紧张的时候会触发sys飙高对业务会导致不可预期的抖动,同时存在已知内存泄漏的问题,我们建议是关掉的,如果需要使用,建议使用madvise方式或者hugetlbpage

一些内核版本、机型和CPU的总结

到此终于看到不需要应用做什么改变,整体性能将近有30%的提升。 在这个测试过程中发现不同CPU对性能影响很明显,相同机型也有不同的CPU型号(性能差异在20%以上–这个太坑了)

性能方面 4.19>4.9>2.6.32

没有做3.10内核版本的比较

以下仅作为大家选择ECS的时候做参考。

不同机型/CPU对性能的影响

还是先说结论:

  • CPU:内存为1:2机型的性能排序:c6->c5->sn1ne->hfc5->s1
  • CPU:内存为1:4机型的性能排序:g6->g5->sn2ne->hfg5->sn2

性能差异主要来源于CPU型号的不同

c6/g6:                  Intel(R) Xeon(R) Platinum 8269CY CPU @ 2.50GHz
c5/g5/sn1ne/sn2ne:      Intel(R) Xeon(R) Platinum 8163 CPU @ 2.50GHz

8269比8163大概好5-10%,价格便宜一点点,8163比E5-2682好20%以上,价格便宜10%(该买什么机型你懂了吧,价格是指整个ECS,而不是单指CPU)

要特别注意sn1ne/sn2ne 是8163和E5-2682 两种CPU型号随机的,如果买到的是E5-2682就自认倒霉吧

C5的CPU都是8163,相比sn1ne价格便宜10%,网卡性能也一样。但是8核以上的sn1ne机型就把网络性能拉开了(价格还是维持c5便宜10%),从点查场景的测试来看网络不会成为瓶颈,到16核机型网卡多队列才会需要打开。

顺便给一下部分机型的包月价格比较:

image.png

官方给出的CPU数据:

image.png

4.19内核在MySQL Server场景下的性能比较

这只是sysbench点查场景粗略比较,因为本次的目标是对X 产品性能的改进

image.png

(以上表格数据主要由 内核团队和我一起测试得到)

**重点注意2.6.32不但tps差30%,并发能力也差的比较多,如果同样用100个并发压2.6.32上的MySQL,TPS在30000左右。只有在减少并发到20个的时候压测才能达到图中最好的tps峰值:45000. **

新内核除了性能提升外带来的便利性

升级内核带来的性能提升只是在极端场景下才会需要,大部分时候我们希望节省开发人员的时间,提升工作效率。于是X 产品在新内核的基础上定制如下一些便利的工具。

麻烦的网络重传率

通过tsar或者其它方式发现网络重传率有点高,有可能是别的管理端口重传率高,有可能是往外连其它服务端口重传率高等,尤其是在整体流量小的情况下一点点管理端口的重传包拉升了整个机器的重传率,严重干扰了问题排查,所以需要进一步确认重传发生在哪个进程的哪个端口上,是否真正影响了我们的业务。

在2.6.32内核下的排查过程是:抓包,然后写脚本分析(或者下载到本地通过wireshark分析),整个过程比较麻烦,需要的时间也比较长。那么在新镜像中我们可以利用内核自带的bcc来快速得到这些信息

sudo /usr/share/bcc/tools/tcpretrans -l

image.png

从截图可以看到重传时间、pid、tcp四元组、状态,针对重传发生的端口和阶段(SYN_SENT握手、ESTABLISHED)可以快速推断导致重传的不同原因。

再也不需要像以前一样抓包、下载、写脚本分析了。

通过perf top直接看Java函数的CPU消耗

这个大家都比较了解,不多说,主要是top的时候能够把java函数给关联上,直接看截图:

sh ~/tools/perf-map-agent/bin/create-java-perf-map.sh pid
sudo perf top

快速定位Java中的锁等待

如果CPU跑不起来,可能会存在锁瓶颈,需要快速找到它们

如下测试中上面的11万tps是解决掉锁后得到的,下面的4万tps是没解决锁等待前的tps:

#[ 210s] threads: 400, tps: 0.00, reads/s: 115845.43, writes/s: 0.00, response time: 7.57ms (95%)
#[ 220s] threads: 400, tps: 0.00, reads/s: 116453.12, writes/s: 0.00, response time: 7.28ms (95%)
#[ 230s] threads: 400, tps: 0.00, reads/s: 116400.31, writes/s: 0.00, response time: 7.33ms (95%)
#[ 240s] threads: 400, tps: 0.00, reads/s: 116025.35, writes/s: 0.00, response time: 7.48ms (95%)

#[ 250s] threads: 400, tps: 0.00, reads/s: 45260.97, writes/s: 0.00, response time: 29.57ms (95%)
#[ 260s] threads: 400, tps: 0.00, reads/s: 41598.41, writes/s: 0.00, response time: 29.07ms (95%)
#[ 270s] threads: 400, tps: 0.00, reads/s: 41939.98, writes/s: 0.00, response time: 28.96ms (95%)
#[ 280s] threads: 400, tps: 0.00, reads/s: 40875.48, writes/s: 0.00, response time: 29.16ms (95%)
#[ 290s] threads: 400, tps: 0.00, reads/s: 41053.73, writes/s: 0.00, response time: 29.07ms (95%)

下面这行命令得到如下等锁的top 10堆栈(async-profiler):

$~/tools/async-profiler/profiler.sh -e lock -d 5 1560

--- 1687260767618 ns (100.00%), 91083 samples
 [ 0] ch.qos.logback.classic.sift.SiftingAppender
 [ 1] ch.qos.logback.core.AppenderBase.doAppend
 [ 2] ch.qos.logback.core.spi.AppenderAttachableImpl.appendLoopOnAppenders
 [ 3] ch.qos.logback.classic.Logger.appendLoopOnAppenders
 [ 4] ch.qos.logback.classic.Logger.callAppenders
 [ 5] ch.qos.logback.classic.Logger.buildLoggingEventAndAppend
 [ 6] ch.qos.logback.classic.Logger.filterAndLog_0_Or3Plus
 [ 7] ch.qos.logback.classic.Logger.info
 [ 8] com.*****.logger.slf4j.Slf4jLogger.info
 [ 9] com.*****.utils.logger.support.FailsafeLogger.info
 [10] com.*****.util.LogUtils.recordSql



"ServerExecutor-3-thread-480" #753 daemon prio=5 os_prio=0 tid=0x00007f8265842000 nid=0x26f1 waiting for monitor entry [0x00007f82270bf000]
  java.lang.Thread.State: BLOCKED (on object monitor)
    at ch.qos.logback.core.AppenderBase.doAppend(AppenderBase.java:64)
    - waiting to lock <0x00007f866dcec208> (a ch.qos.logback.classic.sift.SiftingAppender)
    at ch.qos.logback.core.spi.AppenderAttachableImpl.appendLoopOnAppenders(AppenderAttachableImpl.java:48)
    at ch.qos.logback.classic.Logger.appendLoopOnAppenders(Logger.java:282)
    at ch.qos.logback.classic.Logger.callAppenders(Logger.java:269)
    at ch.qos.logback.classic.Logger.buildLoggingEventAndAppend(Logger.java:470)
    at ch.qos.logback.classic.Logger.filterAndLog_0_Or3Plus(Logger.java:424)
    at ch.qos.logback.classic.Logger.info(Logger.java:628)
    at com.****.utils.logger.slf4j.Slf4jLogger.info(Slf4jLogger.java:42)
    at com.****.utils.logger.support.FailsafeLogger.info(FailsafeLogger.java:102)
    at com.****.util.LogUtils.recordSql(LogUtils.java:115)

          ns  percent  samples  top
  ----------  -------  -------  ---
160442633302   99.99%    38366  ch.qos.logback.classic.sift.SiftingAppender
    12480081    0.01%       19  java.util.Properties
     3059572    0.00%        9  com.***.$$$.common.IdGenerator
      244394    0.00%        1  java.lang.Object

堆栈中也可以看到大量的:

  - waiting to lock <0x00007f866dcec208> (a ch.qos.logback.classic.sift.SiftingAppender)
    - waiting to lock <0x00007f866dcec208> (a ch.qos.logback.classic.sift.SiftingAppender)
    - waiting to lock <0x00007f866dcec208> (a ch.qos.logback.classic.sift.SiftingAppender)
    - waiting to lock <0x00007f866dcec208> (a ch.qos.logback.classic.sift.SiftingAppender)
    - waiting to lock <0x00007f866dcec208> (a ch.qos.logback.classic.sift.SiftingAppender)
    - waiting to lock <0x00007f866dcec208> (a ch.qos.logback.classic.sift.SiftingAppender)
    - waiting to lock <0x00007f866dcec208> (a ch.qos.logback.classic.sift.SiftingAppender)
    - locked <0x00007f866dcec208> (a ch.qos.logback.classic.sift.SiftingAppender)
    - waiting to lock <0x00007f866dcec208> (a ch.qos.logback.classic.sift.SiftingAppender)
    - waiting to lock <0x00007f866dcec208> (a ch.qos.logback.classic.sift.SiftingAppender)
    - waiting to lock <0x00007f866dcec208> (a ch.qos.logback.classic.sift.SiftingAppender)
    - waiting to lock <0x00007f866dcec208> (a ch.qos.logback.classic.sift.SiftingAppender)
    - waiting to lock <0x00007f866dcec208> (a ch.qos.logback.classic.sift.SiftingAppender)
    - waiting to lock <0x00007f866dcec208> (a ch.qos.logback.classic.sift.SiftingAppender)
    - waiting to lock <0x00007f866dcec208> (a ch.qos.logback.classic.sift.SiftingAppender)
    - waiting to lock <0x00007f866dcec208> (a ch.qos.logback.classic.sift.SiftingAppender)

当然还有很多其他爽得要死的命令,比如一键生成火焰图等,不再一一列举,可以从业务层面的需要从这次镜像升级的便利中将他们固化到镜像中,以后排查问题不再需要繁琐的安装、配置、调试过程了。

跟内核无关的应用层的优化

到此我们基本不用任何改动得到了30%的性能提升,但是对整个应用来说,通过以上工具让我们看到了一些明显的问题,还可以从应用层面继续提升性能。

如上描述通过锁排序定位到logback确实会出现锁瓶颈,同时在一些客户场景中,因为网盘的抖动也带来了灾难性的影响,所以日志需要异步处理,经过异步化后tps 达到了32000,关键的是rt 95线下降明显,这个rt下降对X 产品这种Proxy类型的应用是非常重要的(经常被客户指责多了一层转发,rt增加了)。

日志异步化和使用协程后的性能数据:

image.png

Wisp2 协程带来的红利

在整个测试过程中都很顺利,只是发现Wisp2在阻塞不明显的场景下,抖的厉害。简单来说就是压力比较大的话Wisp2表现很稳定,一旦压力一般(这是大部分应用场景),Wisp2表现像是一会是协程状态,一会是没开携程状态,系统的CS也变化很大。

比如同一测试过程中tps抖动明显,从15000到50000:

image.png

100个并发的时候cs很小,40个并发的时候cs反而要大很多:

image.png

最终在 @梁希 同学的攻关下发布了新的jdk版本,问题基本都解决了。不但tps提升明显,rt也有很大的下降。

致谢

感谢 @夷则 团队对这次内核版本升级的支持,感谢 @雏雁 @飞绪 @李靖轩(无牙) @齐江(窅默) @梁希 等大佬的支持。

最终应用不需要任何改动可以得到 30%的性能提升,经过开启协程等优化后应用有将近80%的性能提升,同时平均rt下降了到原来的60%,rt 95线下降到原来的40%。

快点升级你们的内核,用上协程吧。同时考虑下在你们的应用中用上X 产品。

参考文章

https://help.aliyun.com/document_detail/25378.html

https://help.aliyun.com/document_detail/55263.html

https://help.aliyun.com/document_detail/52559.html (网卡)

Intel PAUSE指令变化是如何影响自旋锁以及MySQL的性能的

发表于 2019-12-16 | 分类于 CPU

Intel PAUSE指令变化如何影响MySQL的性能

导读

x86、arm指令都很多,无论是应用程序员还是数据库内核研发大多时候都不需要对这些指令深入理解,但是 Pause 指令和数据库操作太紧密了,本文通过一次非常有趣的性能优化来引入对 Pause 指令的理解,期望可以事半功倍地搞清楚 CPU指令集是如何影响你的程序的。

文章分成两大部分,第一部分是 MySQL 集群的一次全表扫描性能优化过程; 第二部分是问题解决后的原理分析以及Pause指令的来龙去脉和优缺点以及应用场景分析。

业务结构

为理解方便做了部分简化:

client -> Tomcat -> LVS -> MySQL(32 个 MySQLD实例集群,每个实例8Core)

场景描述

通过 client 压 Tomcat 和 MySQL 集群(对数据做分库分表),MySQL 集群是32个实例,每个业务 SQL 都需要经过 Tomcat 拆分成 256 个 SQL 发送给 32 个MySQL(每个MySQL上有8个分库),这 256 条下发给 MySQL 的 SQL 不是完全串行,但也不是完全并行,有一定的并行性。

业务 SQL 如下是一个简单的select sum求和,这个 SQL在每个MySQL上都很快(有索引)

SELECT SUM(emp_arr_amt) FROM table_c WHERE INSUTYPE='310' AND Revs_Flag='Z' AND accrym='201910' AND emp_no='1050457';

监控指标说明

  • 后述或者截图中的逻辑RT/QPS是指 client 上看到的Tomcat的 RT 和 QPS;
  • RT :response time 请求响应时间,判断性能瓶颈的唯一指标;
  • 物理RT/QPS是指Tomcat看到的MySQL RT 和QPS(这里的 RT 是指到达Tomcat节点网卡的 RT ,所以还包含了网络消耗)

问题描述:

通过client压一个Tomcat节点+32个MySQL,QPS大概是430,Tomcat节点CPU跑满,MySQL RT 是0.5ms,增加一个Tomcat节点,QPS大概是700,Tomcat CPU接近跑满,MySQL RT 是0.6ms,到这里性能基本随着扩容线性增加,是符合预期的。

继续增加Tomcat节点来横向扩容性能,通过client压三个Tomcat节点+32个MySQL,QPS还是700,Tomcat节点CPU跑不满,MySQL RT 是0.8ms,这就严重不符合预期了。

性能压测原则:

加并发QPS不再上升说明到了某个瓶颈,哪个环节RT增加最多瓶颈就在哪里

image-20221026145848312

到这里一切都还是符合我们的经验的,看起来就是 MySQL 有瓶颈(RT 增加明显)。

排查 MySQL

现场DBA通过监控看到MySQL CPU不到20%,没有慢查询,并且尝试用client越过所有中间环节直接压其中一个MySQL,可以将 MySQL CPU 跑满,这时的QPS大概是38000(对应上面的场景client QPS为700的时候,单个MySQL上的QPS才跑到6000) 所以排除了MySQL的嫌疑(这个推理不够严谨为后面排查埋下了大坑)。

那么接下来的嫌疑在网络、LVS 等中间环节上。

LVS和网络的嫌疑

首先通过大查询排除了带宽的问题,因为这里都是小包,pps到了72万,很自然想到了网关、LVS的限流之类的

pps监控,这台物理机有4个MySQL实例上,pps 9万左右,9*32/4=72万
image.png

…………(省略巨长的分析、拉人、扯皮过程)

最终所有网络因素都被排除,核心证据是:做压测的时候反复从 Tomcat 上 ping 后面的MySQL,RT 跟没有压力的时候一样,也说明了网络没有问题(请思考这个 ping 的作用)。

问题的确认

尝试在Tomcat上打开日志,并将慢 SQL 阈值设置为100ms,这个时候确实能从日志中看到大量MySQL上的慢查询,因为这个SQL需要在Tomcat上做拆分成256个SQL,同时下发,一旦有一个SQL返回慢,整个请求就因为这个短板被拖累了。平均 RT 0.8ms,但是经常有超过100ms的话对整体影响还是很大的。

将Tomcat记录下来的慢查询(Tomcat增加了一个唯一id下发给MySQL)到MySQL日志中查找,果然发现MySQL上确实慢了,所以到这里基本确认是MySQL的问题,终于不用再纠结是否是网络问题了。

同时在Tomcat进行抓包,对网卡上的 RT 进行统计分析:

image.png

上是Tomcat上抓到的每个sql的物理RT 平均值,上面是QPS 430的时候, RT 0.6ms,下面是3个server,QPS为700,但是 RT 上升到了0.9ms,基本跟Tomcat监控记录到的物理RT一致。如果MySQL上也有类似抓包计算 RT 时间的话可以快速排除网络问题。

网络抓包得到的 RT 数据更容易被所有人接受。尝试过在MySQL上抓包,但是因为LVS模块的原因,进出端口、ip都被修改过,所以没法分析一个流的响应时间。

重心再次转向MySQL

这个时候因为问题点基本确认,再去查看MySQL是否有问题的重心都不一样了,不再只是看看CPU和慢查询,这个问题明显更复杂一些。

教训:CPU只是影响性能的一个因素,RT 才是结果,要追着 RT 跑,而不是只看 CPU

通过监控发现MySQL CPU虽然一直不高,但是经常看到running thread飙到100多,很快又降下去了,看起来像是突发性的并发查询请求太多导致了排队等待,每个MySQL实例是8Core的CPU,尝试将MySQL实例扩容到16Core(只是为了验证这个问题),QPS确实可以上升到1000(没有到达理想的1400)。

这是Tomcat上监控到的MySQL状态:
image.png

同时在MySQL机器上通过vmstat也可以看到这种飙升:
image.png

以上分析可以清晰看到虽然 MySQL 整体压力不大,但是似乎会偶尔来一波卡顿、running 任务飙升。

像这种短暂突发性的并发流量似乎监控都很难看到(基本都被平均掉了),只有一些实时性监控偶尔会采集到这种短暂突发性飙升,这也导致了一开始忽视了MySQL。

所以接下来的核心问题就是MySQL为什么会有这种飙升、这种飙升的影响到底是什么?

perf top

直接用 perf 看下 MySQLD 进程,发现 ut_delay 高得不符合逻辑:

image.png

展开看一下,基本是在优化器中做索引命中行数的选择:

image.png

跟直接在 MySQL 命令行中通过 show processlist看到的基本一致:

image.png

这是 MySQL 的优化器在对索引进行统计,统计的时候要加锁,thread running 抖动的时候通过 show processlist 看到很多 thread处于 statistics 状态。也就是高并发下加锁影响了 CPU 压不上去同时 RT 剧烈增加。

这里ut_delay 消耗了 28% 的 CPU 肯定太不正常了,于是将 innodb_spin_wait_delay 从 30 改成 6 后性能立即上去了,继续增加 Tomcat 节点,QPS也可以线性增加。

耗CPU最高的调用函数栈是…mutex_spin_wait->ut_delay,属于锁等待的逻辑。InnoDB在这里用的是自旋锁,锁等待是通过调用 ut_delay 让 CPU做空循环在等锁的时候不释放CPU从而避免上下文切换,会消耗比较高的CPU。

最终的性能

调整参数 innodb_spin_wait_delay=6 后在4个Tomcat节点下,并发40时,QPS跑到了1700,物理RT:0.7,逻辑RT:19.6,cpu:90%,这个时候只需要继续扩容 Tomcat 节点的数量就可以增加QPS
image.png

再跟调整前比较一下,innodb_spin_wait_delay=30,并发40时,QPS 500+,物理RT:2.6ms 逻辑RT:72.1ms cpu:37%
image.png

再看看调整前压测的时候的vmstat和tsar –cpu,可以看到process running抖动明显
image.png

对比修改delay后的process running就很稳定了,即使QPS大了3倍
image.png

事后思考和分析

到这里问题得到了完美解决,但是不禁要问为什么?ut_delay 是怎么工作的? 和 innodb_spin_wait_delay 以及自旋锁的关系?

原理解析

既然调整 innodb_spin_wait_delay 就能解决这个问题,那就要先分析一下 innodb_spin_wait_delay 的作用

关于 innodb_spin_wait_delay

innodb通过大量的自旋锁(比如 InnoDB mutexes and rw-locks)来用高CPU消耗避免上下文切换,这是自旋锁的正确使用方式,在多核场景下,它们一起自旋抢同一个锁,容易造成cache ping-pong,进而多个CPU核之间会互相使对方缓存部分无效。所以这里innodb通过增加 innodb_spin_wait_delay 和 Pause 配合来缓解cache ping-pong,也就是本来通过CPU 高速自旋抢锁,换成了抢锁失败后 delay一下(Pause)但是不释放CPU,delay 时间到后继续抢锁,也就是把连续的自旋抢锁转换成了更稀疏的点状的抢锁(间隔的 delay是个随机数),这样不但避免了上下文切换也大大减少了cache ping-pong。

自旋锁如何减少了cache ping-pong

多线程竞争锁的时候,加锁失败的线程会“忙等待”,直到它拿到锁。什么叫“忙等待”呢?它并不意味着一直执行 CAS 函数,而是会与 CPU 紧密配合 ,它通过 CPU 提供的 PAUSE 指令,减少循环等待时的cache ping-pong和耗电量;对于单核 CPU,忙等待并没有意义,此时它会主动把线程休眠。

X86 PAUSE 指令

X86设计了Pause指令,也就是调用 Pause 指令的代码会抢着 CPU 不释放,但是CPU 会打个盹,比如 10个时钟周期,相对一次上下文切换是大几千个时钟周期。

这样应用一旦自旋抢锁失败可以先 Pause 一下,只是这个Pause 时间对于 MySQL 来说还不够久,所以需要增加参数 innodb_spin_wait_delay 来将休息时间放大一些。

在我们的这个场景下对每个 SQL的 RT 抖动非常敏感(放大256倍),所以过高的 delay 会导致部分SQL RT 变高。

函数 ut_delay(ut_rnd_interval(0, srv_spin_wait_delay)) 用来执行这个delay:

/***************************MySQL代码****************************//**
Runs an idle loop on CPU. The argument gives the desired delay
in microseconds on 100 MHz Pentium + Visual C++.
@return dummy value */
UNIV_INTERN
ulint
ut_delay(ulint delay)  //delay 是[0,innodb_spin_wait_delay)之间的一个随机数
{
        ulint   i, j;

        UT_LOW_PRIORITY_CPU();

        j = 0;

        for (i = 0; i < delay * 50; i++) {  //delay 放大50倍
                j += i;
                UT_RELAX_CPU();             //调用 CPU Pause
        }

        UT_RESUME_PRIORITY_CPU();

        return(j);
}

innodb_spin_wait_delay的默认值为6. spin 等待延迟是一个动态全局参数,您可以在MySQL选项文件(my.cnf或my.ini)中指定该参数,或者在运行时使用SET GLOBAL 来修改。在我们的MySQL配置中默认改成了30,导致了这个问题。

CPU 为什么要有Pause

首先可以看到 Pause 指令的作用:

  • 避免上下文切换,应用层想要休息可能会用yield、sleep,这两操作对于CPU来说太重了(伴随上下文切换)
  • 能给超线程腾出计算能力(HT共享核,但是有单独的寄存器等存储单元,CPU Pause的时候,对应的HT可以占用计算资源),比如同一个core上先跑多个Pause,同时再跑 nop 指令,这时 nop指令的 IPC基本不受Pause的影响
  • 节能(CPU可以休息、但是不让出来),CPU Pause 的时候你从 top 能看到 CPU 100%,但是不耗能。

所以有了 Pause 指令后能够提高超线程的利用率,节能,减少上下文切换提高自旋锁的效率。

The PAUSE instruction is first introduced for Intel Pentium 4 processor to improve the performance of “spin-wait loop”. The PAUSE instruction is typically used with software threads executing on two logical processors located in the same processor core, waiting for a lock to be released. Such short wait loops tend to last between tens and a few hundreds of cycles. When the wait loop is expected to last for thousands of cycles or more, it is preferable to yield to the operating system by calling one of the OS synchronization API functions, such as WaitForSingleObject on Windows OS.

An Intel® processor suffers a severe performance penalty when exiting the loop because it detects a possible memory order violation. The PAUSE instruction provides a hint to the processor that the code sequence is a spin-wait loop. The processor uses this hint to avoid the memory order violation in most situations. The PAUSE instruction can improve the performance of the processors supporting Intel Hyper-Threading Technology when executing “spin-wait loops”. With Pause instruction, processors are able to avoid the memory order violation and pipeline flush, and reduce power consumption through pipeline stall.

从intel sdm手册以及实际测试验证来看,Pause 指令在执行过程中,基本不占用流水线执行资源。

Skylake 架构的8163 和 Broadwell架构 E5-2682 CPU型号的不同

为什么用得好好的 innodb_spin_wait_delay 参数这次就不行了呢?

这是因为以前业务一直使用的是 E5-2682 CPU,这次用的是新一代架构的 Skylake 8163,那这两款CPU在这里的核心差别是?

在Intel 64-ia-32-architectures-optimization-manual手册中提到:

The latency of the PAUSE instruction in prior generation microarchitectures is about 10 cycles, whereas in Skylake microarchitecture it has been extended to as many as 140 cycles.

The PAUSE instruction can improves the performance of processors supporting Intel Hyper-Threading Technology when executing “spin-wait loops” and other routines where one thread is accessing a shared lock or semaphore in a tight polling loop. When executing a spin-wait loop, the processor can suffer a severe performance penalty when exiting the loop because it detects a possible memory order violation and flushes the core processor’s pipeline. The PAUSE instruction provides a hint to the processor that the code sequence is a spin-wait loop. The processor uses this hint to avoid the memory order violation and prevent the pipeline flush. In addition, the PAUSE instruction de-
pipelines the spin-wait loop to prevent it from consuming execution resources excessively and consume power needlessly. (See Section 8.10.6.1, “Use the PAUSE Instruction in Spin-Wait Loops,” for more information about using the PAUSE instruction with IA-32 processors supporting Intel Hyper-Threading Technology.)

也就是Skylake架构的CPU的PAUSE指令从之前的10 cycles 改成了 140 cycles。这可是14倍的变化呀。

MySQL 使用 innodb_spin_wait_delay 控制 spin lock等待时间,等待时间时间从0*50个Pause到innodb_spin_wait_delay*50个Pause。
以前 innodb_spin_wait_delay 默认配置30,对于E5-2682 CPU,等待的最长时间为:
30 * 50 * 10=15000 cycles,对于2.5GHz的CPU,等待时间为6us。
对应计算 Skylake CPU的等待时间:30 *50 *140=210000 cycles,CPU主频也是2.5GHz,等待时间84us。

E5-2682 CPU型号在不同的delay参数和不同并发压力下的写入性能数据:

image-20221026153750159

Skylake 8163 CPU型号在不同的delay参数和不同并发压力下的写入性能数据:

image-20221026153813774

==因为8163的cycles从10改到了140,所以可以看到delay参数对性能的影响更加陡峻。==

总结分析

Intel CPU 架构不同使得 Pause 指令的CPU Cycles不同导致了 MySQL innodb_spin_wait_delay 在 spin lock 失败的时候(此时需要 Pause* innodb_spin_wait_delay*N)delay更久,使得调用方看到了MySQL更大的 RT ,进而导致 Tomcat Server上业务并发跑不起来,所以最终压力上不去。

在长链路的排查中,细化定位是哪个节点出了问题是最难的,要盯住 RT 而不是 CPU。

欲速则不达,做压测的时候还是要老老实实地从一个并发开始观察QPS、 RT ,然后一直增加压力到压不上去了,再看QPS、 RT 变化,然后确认瓶颈点。

参考文章

https://cloud.tencent.com/developer/article/1005284

mysql doc

Cache Line 伪共享发现与优化

intel spec

Intel PAUSE指令变化影响到MySQL的性能,该如何解决?

ARM软硬件协同设计:锁优化, arm不同于x86,用的是yield来代替Pause

http://cr.openjdk.java.net/~dchuyko/8186670/yield/spinwait.html

https://aloiskraus.wordpress.com/2018/06/16/why-skylakex-cpus-are-sometimes-50-slower-how-intel-has-broken-existing-code/ Windows+.NET 平台下的分析过程及修复方案

Intel PAUSE指令变化是如何影响自旋锁以及MySQL的性能的

发表于 2019-12-16 | 分类于 CPU

Intel PAUSE指令变化如何影响MySQL的性能

导读

x86、arm指令都很多,无论是应用程序员还是数据库内核研发大多时候都不需要对这些指令深入理解,但是 Pause 指令和数据库操作太紧密了,本文通过一次非常有趣的性能优化来引入对 Pause 指令的理解,期望可以事半功倍地搞清楚 CPU指令集是如何影响你的程序的。

文章分成两大部分,第一部分是 MySQL 集群的一次全表扫描性能优化过程; 第二部分是问题解决后的原理分析以及Pause指令的来龙去脉和优缺点以及应用场景分析。

业务结构

为理解方便做了部分简化:

client -> Tomcat -> LVS -> MySQL(32 个 MySQLD实例集群,每个实例8Core)

场景描述

业务按照 个人分库+单位分表: 32个RDS * 8个分库 * 4张分表=1024分表, 也就是 256个分库,每个分库4张表

通过 client 压 Tomcat 和 MySQL 集群(对数据做分库分表),MySQL 集群是32个实例,每个业务 SQL 都需要经过 Tomcat 拆分成 256 个 SQL 发送给 32 个MySQL 实例(每个MySQL 实例上有8个分库),这 256 条下发给 MySQL 的 SQL 不是完全串行,但也不是完全并行,有一定的并行性。

业务 SQL 如下是一个简单的select sum求和,这个 SQL在每个MySQL上都很快(有索引)

SELECT SUM(emp_arr_amt) FROM table_c WHERE INSUTYPE='310' AND Revs_Flag='Z' AND accrym='201910' AND emp_no='1050457';

监控指标说明

  • 后述或者截图中的逻辑RT/QPS是指 client 上看到的Tomcat的 RT 和 QPS;
  • RT :response time 请求响应时间,判断性能瓶颈的唯一指标;
  • 物理RT/QPS是指Tomcat看到的MySQL RT 和QPS(这里的 RT 是指到达Tomcat节点网卡的 RT ,所以还包含了网络消耗)

问题描述:

通过client压一个Tomcat节点+32个MySQL,QPS大概是430,Tomcat节点CPU跑满,MySQL RT 是0.5ms,增加一个Tomcat节点,QPS大概是700,Tomcat CPU接近跑满,MySQL RT 是0.6ms,到这里性能基本随着扩容线性增加,是符合预期的。

继续增加Tomcat节点来横向扩容性能,通过client压三个Tomcat节点+32个MySQL,QPS还是700,Tomcat节点CPU跑不满,MySQL RT 是0.8ms,这就严重不符合预期了。

性能压测原则:

加并发QPS不再上升说明到了某个瓶颈,哪个环节RT增加最多瓶颈就在哪里

image-20221026145848312

到这里一切都还是符合我们的经验的,看起来就是 MySQL 有瓶颈(RT 增加明显)。

排查 MySQL

现场DBA通过监控看到MySQL CPU不到20%,没有慢查询,并且尝试用client越过所有中间环节直接压其中一个MySQL,可以将 MySQL CPU 跑满,这时的QPS大概是38000(对应上面的场景client QPS为700的时候,单个MySQL上的QPS才跑到6000) 所以排除了MySQL的嫌疑(这个推理不够严谨为后面排查埋下了大坑)。

那么接下来的嫌疑在网络、LVS 等中间环节上。

LVS和网络的嫌疑

首先通过大查询排除了带宽的问题,因为这里都是小包,pps到了72万,很自然想到了网关、LVS的限流之类的

pps监控,这台物理机有4个MySQL实例上,pps 9万左右,9*32/4=72万
image.png

…………(省略巨长的分析、拉人、扯皮过程)

最终所有网络因素都被排除,核心证据是:做压测的时候反复从 Tomcat 上 ping 后面的MySQL,RT 跟没有压力的时候一样,也说明了网络没有问题(请思考这个 ping 的作用)。

问题的确认

尝试在Tomcat上打开日志,并将慢 SQL 阈值设置为100ms,这个时候确实能从日志中看到大量MySQL上的慢查询,因为这个SQL需要在Tomcat上做拆分成256个SQL,同时下发,一旦有一个SQL返回慢,整个请求就因为这个短板被拖累了。平均 RT 0.8ms,但是经常有超过100ms的话对整体影响还是很大的。

将Tomcat记录下来的慢查询(Tomcat增加了一个唯一id下发给MySQL)到MySQL日志中查找,果然发现MySQL上确实慢了,所以到这里基本确认是MySQL的问题,终于不用再纠结是否是网络问题了。

同时在Tomcat进行抓包,对网卡上的 RT 进行统计分析:

image.png

上是Tomcat上抓到的每个sql的物理RT 平均值,上面是QPS 430的时候, RT 0.6ms,下面是3个server,QPS为700,但是 RT 上升到了0.9ms,基本跟Tomcat监控记录到的物理RT一致。如果MySQL上也有类似抓包计算 RT 时间的话可以快速排除网络问题。

网络抓包得到的 RT 数据更容易被所有人接受。尝试过在MySQL上抓包,但是因为LVS模块的原因,进出端口、ip都被修改过,所以没法分析一个流的响应时间。

重心再次转向MySQL

这个时候因为问题点基本确认,再去查看MySQL是否有问题的重心都不一样了,不再只是看看CPU和慢查询,这个问题明显更复杂一些。

教训:CPU只是影响性能的一个因素,RT 才是结果,要追着 RT 跑,而不是只看 CPU

通过监控发现MySQL CPU虽然一直不高,但是经常看到running thread飙到100多,很快又降下去了,看起来像是突发性的并发查询请求太多导致了排队等待,每个MySQL实例是8Core的CPU,尝试将MySQL实例扩容到16Core(只是为了验证这个问题),QPS确实可以上升到1000(没有到达理想的1400)。

这是Tomcat上监控到的MySQL状态:
image.png

同时在MySQL机器上通过vmstat也可以看到这种飙升:
image.png

以上分析可以清晰看到虽然 MySQL 整体压力不大,但是似乎会偶尔来一波卡顿、running 任务飙升。

像这种短暂突发性的并发流量似乎监控都很难看到(基本都被平均掉了),只有一些实时性监控偶尔会采集到这种短暂突发性飙升,这也导致了一开始忽视了MySQL。

所以接下来的核心问题就是MySQL为什么会有这种飙升、这种飙升的影响到底是什么?

perf top

直接用 perf 看下 MySQLD 进程,发现 ut_delay 高得不符合逻辑:

image.png

展开看一下,基本是在优化器中做索引命中行数的选择:

image.png

跟直接在 MySQL 命令行中通过 show processlist看到的基本一致:

image.png

这是 MySQL 的优化器在对索引进行统计,统计的时候要加锁,thread running 抖动的时候通过 show processlist 看到很多 thread处于 statistics 状态。也就是高并发下加锁影响了 CPU 压不上去同时 RT 剧烈增加。

这里ut_delay 消耗了 28% 的 CPU 肯定太不正常了,于是将 innodb_spin_wait_delay 从 30 改成 6 后性能立即上去了,继续增加 Tomcat 节点,QPS也可以线性增加。

耗CPU最高的调用函数栈是…mutex_spin_wait->ut_delay,属于锁等待的逻辑。InnoDB在这里用的是自旋锁,锁等待是通过调用 ut_delay 让 CPU做空循环在等锁的时候不释放CPU从而避免上下文切换,会消耗比较高的CPU。

最终的性能

调整参数 innodb_spin_wait_delay=6 后在4个Tomcat节点下,并发40时,QPS跑到了1700,物理RT:0.7,逻辑RT:19.6,cpu:90%,这个时候只需要继续扩容 Tomcat 节点的数量就可以增加QPS
image.png

再跟调整前比较一下,innodb_spin_wait_delay=30,并发40时,QPS 500+,物理RT:2.6ms 逻辑RT:72.1ms cpu:37%
image.png

再看看调整前压测的时候的vmstat和tsar –cpu,可以看到process running抖动明显
image.png

对比修改delay后的process running就很稳定了,即使QPS大了3倍
image.png

事后思考和分析

到这里问题得到了完美解决,但是不禁要问为什么?ut_delay 是怎么工作的? 和 innodb_spin_wait_delay 以及自旋锁的关系?

原理解析

既然调整 innodb_spin_wait_delay 就能解决这个问题,那就要先分析一下 innodb_spin_wait_delay 的作用

关于 innodb_spin_wait_delay

innodb通过大量的自旋锁(比如 InnoDB mutexes and rw-locks)来用高CPU消耗避免上下文切换,这是自旋锁的正确使用方式,在多核场景下,它们一起自旋抢同一个锁,容易造成cache ping-pong,进而多个CPU核之间会互相使对方缓存部分无效。所以这里innodb通过增加 innodb_spin_wait_delay 和 Pause 配合来缓解cache ping-pong,也就是本来通过CPU 高速自旋抢锁,换成了抢锁失败后 delay一下(Pause)但是不释放CPU,delay 时间到后继续抢锁,也就是把连续的自旋抢锁转换成了更稀疏的点状的抢锁(间隔的 delay是个随机数),这样不但避免了上下文切换也大大减少了cache ping-pong。

自旋锁如何减少了cache ping-pong

多线程竞争锁的时候,加锁失败的线程会“忙等待”,直到它拿到锁。什么叫“忙等待”呢?它并不意味着一直执行 CAS 函数,而是会与 CPU 紧密配合 ,它通过 CPU 提供的 PAUSE 指令,减少循环等待时的cache ping-pong和耗电量;对于单核 CPU,忙等待并没有意义,此时它会主动把线程休眠。

X86 PAUSE 指令

X86设计了Pause指令,也就是调用 Pause 指令的代码会抢着 CPU 不释放,但是CPU 会打个盹,比如 10个时钟周期,相对一次上下文切换是大几千个时钟周期。

这样应用一旦自旋抢锁失败可以先 Pause 一下,只是这个Pause 时间对于 MySQL 来说还不够久,所以需要增加参数 innodb_spin_wait_delay 来将休息时间放大一些。

在我们的这个场景下对每个 SQL的 RT 抖动非常敏感(放大256倍),所以过高的 delay 会导致部分SQL RT 变高。

函数 ut_delay(ut_rnd_interval(0, srv_spin_wait_delay)) 用来执行这个delay:

/***************************MySQL代码****************************/
/**Runs an idle loop on CPU. The argument gives the desired delay
in microseconds on 100 MHz Pentium + Visual C++.
@return dummy value */
UNIV_INTERN
ulint
ut_delay(ulint delay)  //delay 是[0,innodb_spin_wait_delay)之间的一个随机数
{
        ulint   i, j;
        UT_LOW_PRIORITY_CPU();
        j = 0;

        for (i = 0; i < delay * 50; i++) {  //delay 放大50倍
                j += i;
                UT_RELAX_CPU();             //调用 CPU Pause
        }

        UT_RESUME_PRIORITY_CPU();
        return(j);
}

innodb_spin_wait_delay的默认值为6. spin 等待延迟是一个动态全局参数,可以在MySQL选项文件(my.cnf或my.ini)中指定该参数,或者在运行时使用SET GLOBAL 来修改。在我们的MySQL配置中默认改成了30,导致了这个问题。

CPU 为什么要有Pause

首先可以看到 Pause 指令的作用:

  • 避免上下文切换,应用层想要休息可能会用yield、sleep,这两操作对于CPU来说太重了(伴随上下文切换)
  • 能给超线程腾出计算能力(HT共享核,但是有单独的寄存器等存储单元,CPU Pause的时候,对应的HT可以占用计算资源),比如同一个core上先跑多个Pause,同时再跑 nop 指令,这时 nop指令的 IPC基本不受Pause的影响
  • 节能(CPU可以休息、但是不让出来),CPU Pause 的时候你从 top 能看到 CPU 100%,但是不耗能。

所以有了 Pause 指令后能够提高超线程的利用率,节能,减少上下文切换提高自旋锁的效率。

The PAUSE instruction is first introduced for Intel Pentium 4 processor to improve the performance of “spin-wait loop”. The PAUSE instruction is typically used with software threads executing on two logical processors located in the same processor core, waiting for a lock to be released. Such short wait loops tend to last between tens and a few hundreds of cycles. When the wait loop is expected to last for thousands of cycles or more, it is preferable to yield to the operating system by calling one of the OS synchronization API functions, such as WaitForSingleObject on Windows OS.

An Intel® processor suffers a severe performance penalty when exiting the loop because it detects a possible memory order violation. The PAUSE instruction provides a hint to the processor that the code sequence is a spin-wait loop. The processor uses this hint to avoid the memory order violation in most situations. The PAUSE instruction can improve the performance of the processors supporting Intel Hyper-Threading Technology when executing “spin-wait loops”. With Pause instruction, processors are able to avoid the memory order violation and pipeline flush, and reduce power consumption through pipeline stall.

从intel sdm手册以及实际测试验证来看,Pause 指令在执行过程中,基本不占用流水线执行资源。

Skylake 架构的8163 和 Broadwell架构 E5-2682 CPU型号的不同

为什么用得好好的 innodb_spin_wait_delay 参数这次就不行了呢?

这是因为以前业务一直使用的是 E5-2682 CPU,这次用的是新一代架构的 Skylake 8163,那这两款CPU在这里的核心差别是?

在Intel 64-ia-32-architectures-optimization-manual手册中提到:

The latency of the PAUSE instruction in prior generation microarchitectures is about 10 cycles, whereas in Skylake microarchitecture it has been extended to as many as 140 cycles.

The PAUSE instruction can improves the performance of processors supporting Intel Hyper-Threading Technology when executing “spin-wait loops” and other routines where one thread is accessing a shared lock or semaphore in a tight polling loop. When executing a spin-wait loop, the processor can suffer a severe performance penalty when exiting the loop because it detects a possible memory order violation and flushes the core processor’s pipeline. The PAUSE instruction provides a hint to the processor that the code sequence is a spin-wait loop. The processor uses this hint to avoid the memory order violation and prevent the pipeline flush. In addition, the PAUSE instruction de-
pipelines the spin-wait loop to prevent it from consuming execution resources excessively and consume power needlessly. (See Section 8.10.6.1, “Use the PAUSE Instruction in Spin-Wait Loops,” for more information about using the PAUSE instruction with IA-32 processors supporting Intel Hyper-Threading Technology.)

也就是Skylake架构的CPU的PAUSE指令从之前的10 cycles 改成了 140 cycles。这可是14倍的变化呀。

MySQL 使用 innodb_spin_wait_delay 控制 spin lock等待时间,等待时间时间从0*50个Pause到innodb_spin_wait_delay*50个Pause。
以前 innodb_spin_wait_delay 默认配置30,对于E5-2682 CPU,等待的最长时间为:
30 * 50 * 10=15000 cycles,对于2.5GHz的CPU,等待时间为6us。
对应计算 Skylake CPU的等待时间:30 *50 *140=210000 cycles,CPU主频也是2.5GHz,等待时间84us。

E5-2682 CPU型号在不同的delay参数和不同并发压力下的写入性能数据:

image-20221026153750159

Skylake 8163 CPU型号在不同的delay参数和不同并发压力下的写入性能数据:

image-20221026153813774

==因为8163的cycles从10改到了140,所以可以看到delay参数对性能的影响更加陡峻。==

总结分析

Intel CPU 架构不同使得 Pause 指令的CPU Cycles不同导致了 MySQL innodb_spin_wait_delay 在 spin lock 失败的时候(此时需要 Pause* innodb_spin_wait_delay*N)delay更久,使得调用方看到了MySQL更大的 RT ,进而导致 Tomcat Server上业务并发跑不起来,所以最终压力上不去。

在长链路的排查中,细化定位是哪个节点出了问题是最难的,要盯住 RT 而不是 CPU。

欲速则不达,做压测的时候还是要老老实实地从一个并发开始观察QPS、 RT ,然后一直增加压力到压不上去了,再看QPS、 RT 变化,然后确认瓶颈点。

我想追加几个问题帮大家理解

为什么要有自旋锁(内核里面的spinlock)?

等锁的时候可以释放CPU进入等待,这叫悲观锁,代价是释放CPU必然导致上下文切换,一次上下文切换至少需要几千个时钟周期,也就是CPU需要几千个时钟周期来完成上下文切换的工作(几千个时钟周期没有产出–真浪费)

或者等锁的时候不释放CPU,赌很快能等到锁,这叫乐观锁,Linux OS用的是spinlock(类似大家看到的CAS),也就是CPU不释放一直不停地检查能否拿到锁,一个时钟周期检查一次太快了(想想你去银行柜台办业务,每秒钟问一次柜员轮到你了没有!),所以CPU工程师就在想能不能提供一条指令一直占着CPU很久,这条指令就是pause,每一个spinlock就会调用pause休息几十、几百个时钟周期后再去看看能否抢到所(柜台给你提供了沙发茶水,你坐一会再去问柜员)

执行Pause指令的时候CPU真的休息了吗?

如果没有事情做的话,CPU是会停下来(省电),如果开了超线程,如果一个核执行了Pause,那么对这个核的另一个超线程来说,白捡了100%的CPU,别人休息(Pause、stall)的时候正好给我用(这是超线程的本质)!

这也是为什么有些场景2个超线程能发挥2倍能力,有些场景2个超线程只能跑出1倍能力。

相对比上下文切换浪费的几千个时钟周期,Pause(spinlock)真是一点都没浪费。但如果你一直spinlock 几万、几十万个时钟周期都没等到锁还不释放也不对,这会导致其他线程调度不到CPU而饿死。一般自旋一段时间后都会放弃CPU转为上下文切换,所以MySQL 加了参数 innodb_spin_wait_delay 来控制spinlock的长短。

并发高导致自旋锁效率低?

如果并发高,都抢同一个锁,这里的效率会随着并发的增加而降低,不展开了,记住这个结论,类似太多人在柜台问轮到自己没有,留给柜员办业务的时间反而少了!

ARM

ARM 指令集中有 nop 来让流水线空转一个时钟周期,汇编里面的 yield 命令底层就是执行 nop 来达到目的,但是这还不够好,在64位的ARM 指令集里面增加了 ISB (instruction synchronization barrier) 来实现类似 Pause 的作用 :

On arm64 we have seen on several databases that ISB (instruction synchronization barrier) is better to use than yield in a spin loop. The yield instruction is a nop. The isb instruction puts the processor to sleep for some short time. isb is a good equivalent to the pause instruction on x86.

对比

Below is an experiment that shows the effects of yield and isb on Arm64 and the
time of a pause instruction on x86 Intel processors. The micro-benchmarks use
https://github.com/google/benchmark.git

测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ cat a.cc
static void BM_scalar_increment(benchmark::State& state) {
int i = 0;
for (auto _ : state)
benchmark::DoNotOptimize(i++);
}
BENCHMARK(BM_scalar_increment);
static void BM_yield(benchmark::State& state) {
for (auto _ : state)
asm volatile("yield"::);
}
BENCHMARK(BM_yield);
static void BM_isb(benchmark::State& state) {
for (auto _ : state)
asm volatile("isb"::);
}
BENCHMARK(BM_isb);
BENCHMARK_MAIN();

测试结果

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
$ g++ -o run a.cc -O2 -lbenchmark -lpthread
$ ./run
--------------------------------------------------------------
Benchmark Time CPU Iterations
--------------------------------------------------------------

AWS Graviton2 (Neoverse-N1) processor:
BM_scalar_increment 0.485 ns 0.485 ns 1000000000
BM_yield 0.400 ns 0.400 ns 1000000000
BM_isb 13.2 ns 13.2 ns 52993304

AWS Graviton (A-72) processor:
BM_scalar_increment 0.897 ns 0.874 ns 801558633
BM_yield 0.877 ns 0.875 ns 800002377
BM_isb 13.0 ns 12.7 ns 55169412

Apple Arm64 M1 processor:
BM_scalar_increment 0.315 ns 0.315 ns 1000000000
BM_yield 0.313 ns 0.313 ns 1000000000
BM_isb 9.06 ns 9.06 ns 77259282

static void BM_pause(benchmark::State& state) {
for (auto _ : state)
asm volatile("pause"::);
}
BENCHMARK(BM_pause);

Intel Skylake processor:
BM_scalar_increment 0.295 ns 0.295 ns 1000000000
BM_pause 41.7 ns 41.7 ns 16780553

Tested on Graviton2 aarch64-linux with `./x.py test`.

依照如上测试结果可以看出在 ARM 指令集下一次 yield 基本耗费一个时钟周期,但是一次 isb 需要 20-30 个时钟周期,而在intel Skylate 下一次Pause 需要140个时钟周期

所以MySQL 的 aarch64 版本在2020年也终于进行了改进

参考文章

https://cloud.tencent.com/developer/article/1005284

mysql doc

Cache Line 伪共享发现与优化

intel spec

Intel PAUSE指令变化影响到MySQL的性能,该如何解决?

ARM软硬件协同设计:锁优化, arm不同于x86,用的是yield、ISB 来代替Pause

http://cr.openjdk.java.net/~dchuyko/8186670/yield/spinwait.html

https://aloiskraus.wordpress.com/2018/06/16/why-skylakex-cpus-are-sometimes-50-slower-how-intel-has-broken-existing-code/ Windows+.NET 平台下的分析过程及修复方案

epoll的LT和ET

发表于 2019-12-09 | 分类于 Linux

epoll的LT和ET

  • LT水平触发(翻译为 条件触发 更合适)

    如果事件来了,不管来了几个,只要仍然有未处理的事件,epoll都会通知你。比如来了,打印一行通知,但是不去处理事件,那么会不停滴打印通知。水平触发模式的 epoll 的扩展性很差。

  • ET边沿触发

    如果事件来了,不管来了几个,你若不处理或者没有处理完,除非下一个事件到来,否则epoll将不会再通知你。 比如事件来了,打印一行通知,但是不去处理事件,那么不再通知,除非下个事件来了

LT比ET会多一次重新加入就绪队列的动作,也就是意味着一定有一次poll不到东西,效率是有影响但是队列长度有限所以基本可以不用考虑。但是LT编程方式上要简单多了,所以LT也是默认的。

举个 🌰:你有急事打电话找人,如果对方一直不接,那你只有一直打,直到他接电话为止,这就是 lt 模式;如果不急,电话打过去对方不接,那就等有空再打,这就是 et 模式。

img

条件触发的问题:不必要的唤醒

  1. 内核:收到一个新建连接的请求
  2. 内核:由于 “惊群效应” ,唤醒两个正在 epoll_wait() 的线程 A 和线程 B
  3. 线程A:epoll_wait() 返回
  4. 线程B:epoll_wait() 返回
  5. 线程A:执行 accept() 并且成功
  6. 线程B:执行 accept() 失败,accept() 返回 EAGAIN

边缘触发的问题:不必要的唤醒以及饥饿

不必要的唤醒:

  1. 内核:收到第一个连接请求。线程 A 和 线程 B 两个线程都在 epoll_wait() 上等待。由于采用边缘触发模式,所以只有一个线程会收到通知。这里假定线程 A 收到通知
  2. 线程A:epoll_wait() 返回
  3. 线程A:调用 accpet() 并且成功
  4. 内核:此时 accept queue 为空,所以将边缘触发的 socket 的状态从可读置成不可读
  5. 内核:收到第二个建连请求
  6. 内核:此时,由于线程 A 还在执行 accept() 处理,只剩下线程 B 在等待 epoll_wait(),于是唤醒线程 B
  7. 线程A:继续执行 accept() 直到返回 EAGAIN
  8. 线程B:执行 accept(),并返回 EAGAIN,此时线程 B 可能有点困惑(“明明通知我有事件,结果却返回 EAGAIN”)
  9. 线程A:再次执行 accept(),这次终于返回 EAGAIN

饥饿:

  1. 内核:接收到两个建连请求。线程 A 和 线程 B 两个线程都在等在 epoll_wait()。由于采用边缘触发模式,只有一个线程会被唤醒,我们这里假定线程 A 先被唤醒
  2. 线程A:epoll_wait() 返回
  3. 线程A:调用 accpet() 并且成功
  4. 内核:收到第三个建连请求。由于线程 A 还没有处理完(没有返回 EAGAIN),当前 socket 还处于可读的状态,由于是边缘触发模式,所有不会产生新的事件
  5. 线程A:继续执行 accept() 希望返回 EAGAIN 再进入 epoll_wait() 等待,然而它又 accept() 成功并处理了一个新连接
  6. 内核:又收到了第四个建连请求
  7. 线程A:又继续执行 accept(),结果又返回成功

ET的话会要求应用一直要把消息处理完毕,比如nginx用ET模式,来了一个上传大文件并压缩的任务,会造成这么一个循环:

nginx读数据(未读完)->Gzip(需要时间,套接字又有数据过来)->读数据(未读完)->Gzip …..

新的accpt进来,因为前一个nginx worker已经被唤醒并且还在read(这个时候内核因为accept queue为空所以已经将socket设置成不可读),所以即使其它worker 被唤醒,看到的也是一个不可读的socket,所以很快因为EAGAIN返回了。

这样就会造成nginx的这个worker假死了一样。如果上传速度慢,这个循环无法持续存在,也就是一旦读完nginx切走(再有数据进来等待下次触发),不会造成假死。

JDK中的NIO是条件触发(level-triggered),不支持ET。netty,nginx和redis默认是边缘触发(edge-triggered),netty因为JDK不支持ET,所以自己实现了Netty-native的抽象,不依赖JDK来提供ET。

边缘触发会比条件触发更高效一些,因为边缘触发不会让同一个文件描述符多次被处理,比如有些文件描述符已经不需要再读写了,但是在条件触发下每次都会返回,而边缘触发只会返回一次。

如果设置边缘触发,则必须将对应的文件描述符设置为非阻塞模式并且循环读取数据。否则会导致程序的效率大大下降或丢消息。

poll和epoll默认采用的都是条件触发,只是epoll可以修改成边缘触发。条件触发同时支持block和non-block,使用更简单一些。

epoll LT惊群的发生

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 否则会阻塞在IO系统调用,导致没有机会再epoll
set_socket_nonblocking(sd);
epfd = epoll_create(64);
event.data.fd = sd;
epoll_ctl(epfd, EPOLL_CTL_ADD, sd, &event);
while (1) {
epoll_wait(epfd, events, 64, xx);
... // 危险区域!如果有共享同一个epfd的进程/线程调用epoll_wait,它们也将会被唤醒!
// 这个accept将会有多个进程/线程调用,如果并发请求数很少,那么将仅有几个进程会成功:
// 1. 假设accept队列中有n个请求,则仅有n个进程能成功,其它将全部返回EAGAIN (Resource temporarily unavailable)
// 2. 如果n很大(即增加请求负载),虽然返回EAGAIN的比率会降低,但这些进程也并不一定取到了epoll_wait返回当下的那个预期的请求。
csd = accept(sd, &in_addr, &in_len);
...
}

再看一遍LT的描述“如果事件来了,不管来了几个,只要仍然有未处理的事件,epoll都会通知你。”,显然,epoll_wait刚刚取到事件的时候的时候,不可能马上就调用accept去处理,事实上,逻辑在epoll_wait函数调用的ep_poll中还没返回的,这个时候,显然符合“仍然有未处理的事件”这个条件,显然这个时候为了实现这个语义,需要做的就是通知别的同样阻塞在同一个epoll句柄睡眠队列上的进程!在实现上,这个语义由两点来保证:

保证1:在LT模式下,“就绪链表”上取出的epi上报完事件后会重新加回“就绪链表”;
保证2:如果“就绪链表”不为空,且此时有进程阻塞在同一个epoll句柄的睡眠队列上,则唤醒它。

epoll LT模式下有进程被不必要唤醒,这一点并不是内核无意而为之的,内核肯定是知道这件事的,这个并不像之前accept惊群那样算是内核的一个缺陷。epoll LT模式只是提供了一种模式,误用这种模式将会造成类似惊群那样的效应。但是不管怎么说,为了讨论上的方便,后面我们姑且将这种效应称作epoll LT惊群吧。

epoll ET模式可以解决上面的问题,但是带来了新的麻烦

由于epi entry的callback即ep_poll_callback所做的事情仅仅是将该epi自身加入到epoll句柄的“就绪链表”,同时唤醒在epoll句柄睡眠队列上的task,所以这里并不对事件的细节进行计数,比如说,如果ep_poll_callback在将一个epi加入“就绪链表”之前发现它已经在“就绪链表”了,那么就不会再次添加,因此可以说,一个epi可能pending了多个事件,注意到这点非常重要!

一个epi上pending多个事件,这个在LT模式下没有任何问题,因为获取事件的epi总是会被重新添加回“就绪链表”,那么如果还有事件,在下次check的时候总会取到。然而对于ET模式,仅仅将epi从“就绪链表”删除并将事件本身上报后就返回了,因此如果该epi里还有事件,则只能等待再次发生事件,进而调用ep_poll_callback时将该epi加入“就绪队列”。这意味着什么?

这意味着,应用程序,即epoll_wait的调用进程必须自己在获取事件后将其处理干净后方可再次调用epoll_wait,否则epoll_wait不会返回,而是必须等到下次产生事件的时候方可返回。这会导致事件堆积,所以一般会死循环一直拉取事件,直到拉取不到了再返回。

参考资料

Epoll is fundamentally broken

Epoll is fundamentally broken 1/2

https://wenfh2020.com/2021/11/21/question-nginx-epoll-et/

1…567…10
twitter @plantegg

twitter @plantegg

191 日志
18 分类
282 标签
RSS
© 2025 twitter @plantegg
由 Hexo 强力驱动
主题 - NexT.Mist
本站总访问量次 本站访客数人次
访问人数 人 次