2020年11月26日

from:https://www.infoq.cn/article/orvjbfycnrito5qiyfhf
前言


学习一个知识之前,我觉得比较好的方式是先理解它的来龙去脉:即这个知识产生的过程,它解决了什么问题,它是怎么样解决的,还有它引入了哪些新的问题(没有银弹),这样我们才能比较好的抓到它的脉络和关键点,不会一开始就迷失在细节中。


所以,在学习分布式系统之前,我们需要解决的第一个问题是:分布式系统解决了什么问题?


分布式系统解决了什么问题?


第一个是单机性能瓶颈导致的成本问题,由于摩尔定律失效,廉价 PC 机性能的瓶颈无法继续突破,小型机和大型机能提高更高的单机性能,但是成本太大高,一般的公司很难承受;


第二个是用户量和数据量爆炸性的增大导致的成本问题,进入互联网时代,用户量爆炸性的增大,用户产生的数据量也在爆炸性的增大,但是单个用户或者单条数据的价值其实比软件时代(比如银行用户)的价值是只低不高,所以必须寻找更经济的方案;


第三个是业务高可用的要求,对于互联网的产品来说,都要求 7 * 24 小时提供服务,无法容忍停止服务等故障,而要提供高可用的服务,唯一的方式就是增加冗余来完成,这样就算单机系统可以支撑的服务,因为高可用的要求,也会变成一个分布式系统。


基于上面的三个原因可以看出,在互联网时代,单机系统是无法解决成本和高可用问题的,但是这两个问题对几乎对所有的公司来说都是非常关键的问题,所以,从单机系统到分布式系统是无法避免的技术大潮流。


分布式系统是怎么来解决问题的?


那么,分布式系统是怎么来解决单机系统面临的成本和高可用问题呢?


其实思路很简单,就是将一些廉价的 PC 机通过网络连接起来,共同完成工作,并且在系统中提供冗余来解决高可用的问题。


分布式系统引入了哪些新的问题?


我们来看分布式系统的定义:分布式系统是由一组通过网络进行通信、为了完成共同的任务而协调工作的计算机节点组成的系统。在定义中,我们可用看出,分布式系统它通过多工作节点来解决单机系统面临的成本和可用性问题,但是它引入了对分布式系统内部工作节点的协调问题。


我们经常说掌握一个知识需要理解它的前因后果,对于分布式系统来说,前因是「分布式系统解决了什么问题」,后果是「它是怎么做内部工作节点的协调」,所以我们要解决的第二个问题是:分布式系统是怎么做内部工作节点协调的?


分布式计算引入了哪些新的问题?


先从简单的情况入手,对于分布式计算(无状态)的情况,系统内部的协调需要做哪些工作:


1.怎么样找到服务?


在分布式系统内部,会有不同的服务(角色),服务 A 怎么找到服务 B 是需要解决的问题,一般来说服务注册与发现机制是常用的思路,所以可以了解一下服务注册发现机制实现原理,并且可以思考服务注册发现是选择做成 AP 还是 CP 系统更合理(严格按 CAP 理论说,我们目前使用的大部分系统很难满足 C 或者 A 的,所以这里只是通常意义上的 AP 或者 CP);


2.怎么样找到实例?


找到服务后,当前的请求应该选择发往服务的哪一个实例呢?一般来说,如果同一个服务的实例都是完全对等的(无状态),那么按负载均衡策略来处理就足够(轮询、权重、hash、一致性 hash,fair 等各种策略的适用场景);如果同一个服务的实例不是对等的(有状态),那么需要通过路由服务(元数据服务等)先确定当前要访问的请求数据做哪一个实例上,然后再进行访问。


3.怎么样避免雪崩?


系统雪崩是指故障的由于正反馈循序导致不断扩大规则的故障。一次雪崩通常是由于整个系统中一个很小的部分出现故障于引发,进而导致系统其它部分也出现故障。比如系统中某一个服务的一个实例出现故障,导致负载均衡将该实例摘除而引起其它实例负载升高,最终导致该服务的所有实例像多米诺骨牌一样一个一个全部出现故障。


避免雪崩总体的策略比较简单,只要是两个思路,一个是快速失败和降级机制(熔断、降级、限流等),通过快速减少系统负载来避免雪崩的发生;另一个为弹性扩容机制,通过快速增加系统的服务能力来避免雪崩的发生。这个根据不同的场景可以做不同的选择,或者两个策略都使用。


一般来说,快速失败会导致部分的请求失败,如果分布式系统内部对一致性要求很高的话,快速失败会带来系统数据不一致的问题,弹性扩容会是一个比较好的选择,但是弹性扩容的实现成本和响应时间比快速失败要大得多。


4.怎么样监控告警?


对于一个分布式系统,如果我们不能很清楚地了解内部的状态,那么高可用是没有办法完全保障的,所以对分布式系统的监控(比如接口的时延和可用性等信息),分布式追踪 Trace,模拟故障的混沌工程,以及相关的告警等机制是一定要完善的;


分布式存储引入了哪些新的问题?


接下来我们再来看分布式存储(有状态)的内部的协调是怎么做的,同时,前面介绍的分布式计算的协调方式在分布式存储中同样适用,就不再重复了:


1.分布式系统的理论与衡权


ACID、BASE 和 CAP 理论,了解这三个主题,推荐这一篇文章以及文章后面相关的参考文献:


英文版本:https://www.infoq.com/articles/cap-twelve-years-later-how-the-rules-have-changed/


中文版本:https://www.infoq.cn/article/cap-twelve-years-later-how-the-rules-have-changed/


2.怎么样做数据分片?


单机的存储能力是不可能存储所有的数据的,所以需要解决怎么将数据按一定的规则分别存储到不同的机器上,目前使用比较多的方案为:Hash、Consistent Hash 和 Range Based 分片策略,可以了解一下它们的优缺点和各自的应用场景;


3.怎么样做数据复制?


为什么满足系统的高可用要求,需要对数据做冗余处理,目前的方案主要为:中心化方案(主从复制、一致性协议比如 Raft 和 Paxos 等)和 去中心化的方案(Quorum 和 Vector Clock)了解一下它们的优缺点和各自的应用场景,以及对系统外部表现出来的数据一致性级别(线性一致性、顺序一致性、最终一致性等);


4.怎么样做分布式事务?


对于分布式系统来说,要实现事务,首先需要有对并发事务进行排序的能力,这样在事务冲突的时候,确认哪个事务提供成功,哪个事务提交失败。对于单机系统来说这个完全不是问题,简单通过时间戳加序号的方式就可以实现,但是对于分布式系统来说,系统中机器的时间不能完全同步,并且单台机器序号也没用全局意义,按上面的方式说行不通的。不过整个系统选一台机器按单机的模式生产事务 ID 是可以的,同城多中心和短距离的异地多中心都没有问题,不过想做成全球分布式系统的话,那么每一次事务都要去一个节点去获取事务 ID 的成本太高(比如中国杭州到美国东部的 RTT 为 200 + ms ),Google 的 Spanner 是通过 GPS 和原子钟实现 TrueTime API 来解决这个问题从而实现全球分布式数据库的。


有了事务 ID 后,通过 2PC 或者 3PC 协议来实现分布式事务的原子性,其他部分和单机事务差别不大,就不再细说来。


进阶学习阶段


到这里,对分布式系统脉络上有了基本的概念,接下来开始进入细节学习阶段,这也是非常幸苦的阶段,对于分布式系统的理解深入与否,对细节的深入度是很重要的评价指标,毕竟魔鬼在细节。这里可以往两个方面进行系统的学习:


1.从实践出发


研究目前比较常用的分布式系统的设计,HDFS 或者 GFS(分布式文件系统)、Kafka 和 Pulsar(分布式消息队列),Redis Cluster 和 Codis(分布式缓存),MySQL 的分库分表(传统关系型数据库的分布式方案),MongoDB 的 Replica Set 和 Sharing 机制集以及去中心化的 Cassandra(NoSQL 数据库),中心化的 TiDB 和去中心化的 CockroachDB(NewSQL),以及一些微服务框架等;


2.从理论出发


从理论出发,研究分布式相关的论文,这里推荐一本书「Designing Data-Intensive Applications」(中文版本:数据密集型应用系统设计),先整体看书,对比较感兴趣的章节,再读一读该章节中涉及到的相关参考文献。


总结


本文从分布式系统解决的问题开始,再讨论它是怎么样来解决问题的,最后讨论了它引入了哪些新的问题,并且讨论这些新问题的解决办法,这个就是分布式系统大概的知识脉络。掌握这个知识脉络后,那么就可以从实践和理论两个角度结合起来深入细节研究分布式系统了。


参考


知乎 | 如何系统性的学习分布式系统


Martin Kleppmann.Designing Data-Intensive Applications


CAP Twelve Years Later: How the “Rules” Have Changed

posted @ 2020-11-26 16:20 小马歌 阅读(181) | 评论 (0)编辑 收藏

2020年11月24日

     摘要: from:https://www.huxiu.com/article/351920.html本文来自微信公众号: 鲜枣课堂(ID:xzclasscom),作者:小枣君,题图来自:视觉中国大家好,我是小枣君。今天我们来聊聊基带和射频。说起基带和射频,相信大家都不陌生。它们是通信行业里的两个常见概念,经常出现在我们面前。不过,越是常见的概念,网上的资料就越混乱,错误也就越多。这些错误给很多初...  阅读全文
posted @ 2020-11-24 10:41 小马歌 阅读(421) | 评论 (0)编辑 收藏

2020年11月6日

from:https://mp.weixin.qq.com/s/s-AuC_IDS5GEamRwX286MQ


随着紫光展锐、ASR 等芯片厂商发布性价比更高的 Cat.1 芯片之后,Cat.1 模组厂商扎堆发布了自家的模组,
使得市场上的 Cat.1 模组价格已经迅速降至 45-60 元,玩家众多,竞争惨烈,基本重走 NB-IOT 的老路 —— 量未起,价已跌。

Cat.1 芯片原厂:

  • 高通 MDM9207-1(2016 年发布)

  • 紫光展锐春藤 8910DM(28nm工艺,集成蓝牙和WiFi 室内定位)

  • 翱捷 ASR3601

Cat.1 模组厂商(不完全统计):

  • 中移物联网

  • 移远通信

  • 合宙电子

  • 移柯通信

  • 域格信息

  • 广和通

  • 芯讯通

  • 高新兴物联

  • 美格智能

  • 有方科技

  • 有人信息

  • 信位通讯

  • 锐骐(厦门)电子

  • 深圳信可通讯

Cat.1 优势

  • 相对 NB-IOT,其通信速率优势明显

  • 相对 eMTC,其网络成本低

  • 相对 Cat.4,其具有一定的成本优势

Cat.1 劣势:

  • 现阶段芯片厂家少

    国外以高通为主,辅以 Sequans、Altair。

    国内主要是展锐和翱捷。

  • 现阶段价格偏高

    NB-IoT、Cat.1、Cat.4 模组价格:


cat1 的主要市场和应用场景:

Cat.1 仍处于商用初期,落地的应用场景和案例还较少,一些明确的场景包括了共享、金融支付、工业控制、车载支付、公网对讲、POS 等等。

总结

工信部办公厅发布了《关于深入推进移动物联网全面发展的通知》(以下简称《通知》)同时为 NB-IOT 和 Cat.1 站台,未来 NB-IOT 依旧很香,Cat.1 则前途大好。

随着新基建的启动,5G 打头,未来将是 NB-IOT、4G(包括 Cat.1)、5G 共同承载蜂窝物联网的连接,以应对不同层次的物联网业务需求。

posted @ 2020-11-06 14:36 小马歌 阅读(213) | 评论 (0)编辑 收藏

2020年10月12日

from:https://www.cnblogs.com/schips/p/12262587.html


背景

QoS 等级 与 通信的流程有关,直接影响了整个通信。而且篇幅比较长,所以我觉得应该单独拎出来讲一下。

概念

QoS 代表了 服务质量等级。 设置上,由2 位 的二进制控制,且值不允许为 3(0x11)。

QoS值Bit 2Bit 1描述
000最多分发一次
101至少分发一次
210只分发一次
-11保留位

要注意的是,QoS 是 Sender 和 Receiver 之间达成的协议,不是 Publisher 和 Subscriber 之间达成的协议。

也就是说 Publisher 发布一条 QoS1 的消息,只能保证 Broker 能至少收到一次这个消息;至于对应的 Subscriber 能否至少收到一次这个消息,还要取决于 Subscriber 在 Subscribe 的时候和 Broker 协商的 QoS 等级。

这里又牵扯出一个概念:"QoS 降级":在 MQTT 协议中,从 Broker 到 Subscriber 这段消息传递的实际 QoS 等于 "Publisher 发布消息时指定的 QoS 等级和 Subscriber 在订阅时与 Broker 协商的 QoS 等级,这两个 QoS 等级中的最小那一个。"

QoS 0 的通信时序图

此时,整个过程中的 Sender 不关心 Receiver 是否收到消息,它"尽力"发完消息,至于是否有人收到,它不在乎。

发布者服务器订阅者PUBLISH (QoS0,Msg-A)PUBLISH(QoS0,Msg-A)Delete Msg-A发布者服务器订阅者QoS 0:At most one(Fire and forget)

QoS1 的通信时序图

此时,Sender 发送的一条消息,Receiver 至少能收到一次,也就是说 Sender 向 Receiver 发送消息,如果发送失败,会继续重试,直到 Receiver 收到消息为止,但是因为重传的原因,Receiver 有可能会收到重复的消息;

发布者服务器订阅者Store (Msg-A)PUBLISH (QoS1,Msg-A)Store (Msg-A)PUBLISH (QoS1,Msg-A)PUBACK (QoS1)Delete (Msg-A)PUBACK (QoS1,Msg-A)Delete (Msg-A)发布者服务器订阅者QoS 1:At least one

1)Sender 向 Receiver 发送一个带有消息数据的 PUBLISH 包, 并在本地保存这个 PUBLISH 包。

2)Receiver 收到 PUBLISH 包以后,向 Sender 发送一个 PUBACK 数据包,PUBACK 数据包没有消息体(Payload),在可变头中(Variable header)中有一个包标识(Packet Identifier),和它收到的 PUBLISH 包中的 Packet Identifier 一致。

3)Sender 收到 PUBACK 之后,根据 PUBACK 包中的 Packet Identifier 找到本地保存的 PUBLISH 包,然后丢弃掉,一次消息的发送完成。

4)如果 Sender 在一段时间内没有收到 PUBLISH 包对应的 PUBACK,它将该 PUBLISH 包的 DUP 标识设为 1(代表是重新发送的 PUBLISH 包),然后重新发送该 PUBLISH 包。重复这个流程,直到收到 PUBACK,然后执行第 3 步。

QoS 2 的通信时序图

QoS2 不仅要确保 Receiver 能收到 Sender 发送的消息,还要保证消息不重复。它的重传和应答机制就要复杂一些,同时开销也是最大的。

Sender 发送的一条消息,Receiver 确保能收到而且只收到一次,也就是说 Sender 尽力向 Receiver 发送消息,如果发送失败,会继续重试,直到 Receiver 收到消息为止,同时保证 Receiver 不会因为消息重传而收到重复的消息。

发布者服务器订阅者Store (Msg-A)PUBLISH (QoS2,Msg-A,DUP=0)Store (Msg-A)PUBREC (QoS2,Msg-A)PUBREL (QoS2,Msg-A)PUBLISH (QoS2,Msg-A,DUP=0)PUBCOMP (QoS2,Msg-A)Delete (Msg-A)Store (Msg-A)PUBREC (QoS2,Msg-A)PUBREL (QoS2,Msg-A)Notify (Msg-A)PUBCOMP (QoS2,Msg-A)Delete (Msg-A)Delete (Msg-A)发布者服务器订阅者QoS 2:Exactly one

QoS 使用 2 套请求/应答流程(一个 4 段的握手)来确保 Receiver 收到来自 Sender 的消息,且不重复:

1)Sender 发送 QoS 为 2 的 PUBLISH 数据包,数据包 Packet Identifier 为 P,并在本地保存该 PUBLISH 包;

2)Receiver 收到 PUBLISH 数据包以后,在本地保存 PUBLISH 包的 Packet Identifier P,并回复 Sender 一个 PUBREC 数据包,PUBREC 数据包可变头中的 Packet Identifier 为 P,没有消息体(Payload);

3)当 Sender 收到 PUBREC,它就可以安全地丢弃掉初始的 Packet Identifier 为 P 的 PUBLISH 数据包,同时保存该 PUBREC 数据包,同时回复 Receiver 一个 PUBREL 数据包,PUBREL 数据包可变头中的 Packet Identifier 为 P,没有消息体;如果 Sender 在一定时间内没有收到 PUBREC,它会把 PUBLISH 包的 DUP 标识设为 1,重新发送该 PUBLISH 数据包(Payload);

4)当 Receiver 收到 PUBREL 数据包,它可以丢弃掉保存的 PUBLISH 包的 Packet Identifier P,并回复 Sender 一个 PUBCOMP 数据包,PUBCOMP 数据包可变头中的 Packet Identifier 为 P,没有消息体(Payload);

5)当 Sender 收到 PUBCOMP 包,那么它认为数据包传输已完成,它会丢弃掉对应的 PUBREC 包。如果 Sender 在一定时间内没有收到 PUBCOMP 包,它会重新发送 PUBREL 数据包。

我们可以看到在 QoS2 中,完成一次消息的传递,Sender 和 Reciever 之间至少要发送四个数据包,QoS2 是最安全也是最慢的一种 QoS 等级了。

QoS 和会话(Session)

客户端的会话状态包括:

  • 已经发送给服务端,但是还没有完成确认的QoS 1和QoS 2级别的消息
  • 已从服务端接收,但是还没有完成确认的QoS 2级别的消息。

服务端的会话状态包括:

  • 会话是否存在,即使会话状态的其它部分都是空。
  • 客户端的订阅信息。
  • 已经发送给客户端,但是还没有完成确认的QoS 1和QoS 2级别的消息。
  • 即将传输给客户端的QoS 1和QoS 2级别的消息。
  • 已从客户端接收,但是还没有完成确认的QoS 2级别的消息。
  • 可选,准备发送给客户端的QoS 0级别的消息。

保留消息不是服务端会话状态的一部分,会话终止时不能删除保留消息。

如果 Client 想接收离线消息,必须使用持久化的会话(CONNECT报文中可变头(byte8[1])Clean Session = 0)连接到 Broker,这样 Broker 才会存储 Client 在离线期间没有确认接收的 QoS 大于 1 的消息。

QoS 等级的选择

在以下情况下你可以选择 QoS0

  • Client 和 Broker 之间的网络连接非常稳定,例如一个通过有线网络连接到 Broker 的测试用 Client;
  • 可以接受丢失部分消息,比如你有一个传感器以非常短的间隔发布状态数据,所以丢一些也可以接受;
  • 你不需要离线消息。

在以下情况下你应该选择 QoS1:

  • 你需要接收所有的消息,而且你的应用可以接受并处理重复的消息;
  • 你无法接受 QoS2 带来的额外开销,QoS1 发送消息的速度比 QoS2 快很多。

在以下情况下你应该选择 QoS2:

  • 你的应用必须接收到所有的消息,而且你的应用在重复的消息下无法正常工作,同时你也能接受 QoS2 带来的额外开销。

实际上,QoS1 是应用最广泛的 QoS 等级,QoS1 发送消息的速度很快,而且能够保证消息的可靠性。虽然使用 QoS1 可能会收到重复的消息,但是在应用程序里面处理重复消息,通常并不是件难事。

posted @ 2020-10-12 14:19 小马歌 阅读(226) | 评论 (0)编辑 收藏

2019年4月19日

from:http://zhulongchao.com/blog/performance-trace.html

1.网速测试

安装iperf

yum install epel-release 从epel源中安装 yum install -y  iperf 

带宽检测

iperf -s 开启服务端  iperf -c ip 

丢包问题

tcpdump进行抓包  tcpdump -i eth0 -s 3000 port 8080 -w /home/tomcat.pcap  对于抓包文件采用wireshark进行分析  丢包(TCP DUP ACK) 重传(retransmission),超时重传, 

2.cdn性能测试

cdn 缓存,回源问题    304请求,浏览器是否使用本地缓存。比较last_modified 和if_modified_since  通过实践戳来判断,浏览器缓存和cdn缓存 

3.DNS基础

路由解析

泛域名解析

4.分布式服务链路追踪

http入口产生一个traceId  分发到rpc调用,cache,db,jms调用链路中  google的著名论文dapper和zipkin  日志聚合,绑定链路日志和业务日志  采样采集,慢请求,异常服务。  日志量大。日志异步写入,环状数组,日志组件自研  共享信息放在ThreadLocal中。比如traceId 

5.网卡性能问题定位

tsar -l  -i 1 --traffic 查看网卡的进出流量 

6.CPU性能问题定位

tsar -l  -i 1 --cpu  软件问题定位,perf 采样所有进程数据  perf record -F 99 -a -g -- sleep 30  java进程的函数map:java -cp attach-main.jar:$JAVA_HOME/lib/tools.jar net.virtualvoid.perf.AttachOnce PID  输出函数和地址的map  输出火焰图 perf script | stackcollapse-perf.pl | flamegraph.pl --color=java --hash > flamegraph.svg 

7.内存性能问题定位

-堆内内存问题,

采用jmap dump内存,采用离线工具分析  jprofile、mat 

-堆外内存问题

a.google-perftools

yum install -y google-perftools graphviz  export LD_PRELOAD=/usr/lib64/libtcmalloc.so.4  export HEAPPROFILE=/home/testGperf.prof  执行程序,结束程序,生成prof  分析prof  生成svg, pdf,text pprof --svg $JAVA_HOME/bin/java testGperf.prof.0001.heap > test.svg  pprof --pdf $JAVA_HOME/bin/java testGperf.prof.0001.heap > test.pdf  pprof --text $JAVA_HOME/bin/java testGperf.prof.0001.heap > test.txt 

b.jemalloc定位(优势,适合长时间trace)

sudo apt-get install graphviz 编译安装 ./configure –enable-prof –enable-stats –enable-debug –enable-fill make make install

运行配置 export MALLOC_CONF=”prof:true,prof_gdump:true,prof_prefix:/home/jedump/jez,lg_prof_interval:30,lg_prof_sample:17”

export LD_PRELOAD=/usr/local/lib/libjemalloc.so.2 运行 java -jar target/spring-boot-jemalloc-example-0.0.1-SNAPSHOT.jar

jeprof –show_bytes –svg jez.*.heap > app-profiling.svg

注明:如果在docker容器中,推荐用pprof,jemalloc只显示函数地址,不显示函数名

8.机器资源配额问题

/etc/security/limits.conf

  • soft nofile 65536
  • hard nofile 65536

控制该用户文件句柄数

9.磁盘性能问题定位

tsar -l -i 1 –io

posted @ 2019-04-19 11:04 小马歌 阅读(469) | 评论 (0)编辑 收藏

2019年3月30日

from:https://blog.csdn.net/lycyingO/article/details/80854669


 版权声明:微信公众号《小姐姐味道》,转载注明出处 https://blog.csdn.net/lycyingO/article/details/80854669
简介
JVM堆外内存难排查但经常会出现问题,这可能是目前最全的JVM堆外内存排查思路。
通过本文,你应该了解:
pmap 命令
gdb 命令
perf 命令
内存 RSS、VSZ的区别
java NMT
起因
这几天遇到一个比较奇怪的问题,觉得有必要和大家分享一下。我们的一个服务,运行在docker上,在某个版本之后,占用的内存开始增长,直到docker分配的内存上限,但是并不会OOM。版本的更改如下:
升级了基础软件的版本
将docker的内存上限由4GB扩展到8GB
上上个版本的一项变动是使用了EhCache的Heap缓存
没有读文件,也没有mmap操作
使用jps 查看启动参数,发现分配了大约3GB的堆内存
[root]$ jps -v
75 Bootstrap -Xmx3000m -Xms3000m  -verbose:gc -Xloggc:/home/logs/gc.log -XX:CMSInitiatingOccupancyFraction=80 -XX:+UseCMSCompactAtFullCollection -XX:MaxTenuringThreshold=10 -XX:MaxPermSize=128M -XX:SurvivorRatio=3 -XX:NewRatio=2 -XX:+PrintGCDateStamps -XX:+PrintGCDetails -XX:+UseParNewGC -XX:+UseConcMarkSweepGC
使用ps查看进程使用的内存和虚拟内存 ( Linux内存管理 )。除了虚拟内存比较高达到17GB以外,实际使用的内存RSS也夸张的达到了7GB,远远超过了-Xmx的设定。
[root]$ ps -p 75 -o rss,vsz  
 
RSS    VSZ 7152568 17485844
原创文章,转载注明出处 (http://sayhiai.com)
排查过程
明显的,是有堆外内存的使用,不太可能是由于EhCache引起的(因为我们使用了heap方式)。了解到基础软件的升级涉及到netty版本升级,netty会用到一些DirectByteBuffer,第一轮排查我们采用如下方式:
jmap -dump:format=b,file=75.dump 75 通过分析堆内存找到DirectByteBuffer的引用和大小
部署一个升级基础软件之前的版本,持续观察
部署另一个版本,更改EhCache限制其大小到1024M
考虑到可能由Docker的内存分配机制引起,部署一实例到实体机
结果4个环境中的服务,无一例外的都出现了内存超用的问题。问题很奇怪,宝宝睡不着觉。
pmap
为了进一步分析问题,我们使用pmap查看进程的内存分配,通过RSS升序序排列。结果发现除了地址000000073c800000上分配的3GB堆以外,还有数量非常多的64M一块的内存段,还有巨量小的物理内存块映射到不同的虚拟内存段上。但到现在为止,我们不知道里面的内容是什么,是通过什么产生的。
[root]$ pmap -x 75  | sort -n -k3
 
.....省略N行
 
0000000040626000   55488   55484   55484 rwx--    [ anon ]
 
00007fa07c000000   65536   55820   55820 rwx--    [ anon ]
 
00007fa044000000   65536   55896   55896 rwx--    [ anon ]
 
00007fa0c0000000   65536   56304   56304 rwx--    [ anon ]
 
00007f9db8000000   65536   56360   56360 rwx--    [ anon ]
 
00007fa0b8000000   65536   56836   56836 rwx--    [ anon ]
 
00007fa084000000   65536   57916   57916 rwx--    [ anon ]
 
00007f9ec4000000   65532   59752   59752 rwx--    [ anon ]
 
00007fa008000000   65536   60012   60012 rwx--    [ anon ]
 
00007f9e58000000   65536   61608   61608 rwx--    [ anon ]
 
00007f9f18000000   65532   61732   61732 rwx--    [ anon ]
 
00007fa018000000   65532   61928   61928 rwx--    [ anon ]
 
00007fa088000000   65536   62336   62336 rwx--    [ anon ]
 
00007fa020000000   65536   62428   62428 rwx--    [ anon ]
 
00007f9e44000000   65536   64352   64352 rwx--    [ anon ]
 
00007f9ec0000000   65528   64928   64928 rwx--    [ anon ]
 
00007fa050000000   65532   65424   65424 rwx--    [ anon ]
 
00007f9e08000000   65512   65472   65472 rwx--    [ anon ]
 
00007f9de0000000   65524   65512   65512 rwx--    [ anon ]
 
00007f9dec000000   65532   65532   65532 rwx--    [ anon ]
 
00007f9dac000000   65536   65536   65536 rwx--    [ anon ]
 
00007f9dc8000000   65536   65536   65536 rwx--    [ anon ]
 
00007f9e30000000   65536   65536   65536 rwx--    [ anon ]
 
00007f9eb4000000   65536   65536   65536 rwx--    [ anon ]
 
00007fa030000000   65536   65536   65536 rwx--    [ anon ]
 
00007fa0b0000000   65536   65536   65536 rwx--    [ anon ]
 
000000073c800000 3119140 2488596 2487228 rwx--    [ anon ]
 
total kB        17629516 7384476 7377520
通过google,找到以下资料 Linux glibc >= 2.10 (RHEL 6) malloc may show excessive virtual memory usage)
文章指出造成应用程序大量申请64M大内存块的原因是由Glibc的一个版本升级引起的,通过export MALLOC_ARENA_MAX=4可以解决VSZ占用过高的问题。虽然这也是一个问题,但却不是我们想要的,因为我们增长的是物理内存,而不是虚拟内存。
NMT
幸运的是 JDK1.8有Native Memory Tracker可以帮助定位。通过在启动参数上加入-XX:NativeMemoryTracking=detail就可以启用。在命令行执行jcmd可查看内存分配。
#jcmd 75 VM.native_memory summary
 
Native Memory Tracking: Total: reserved=5074027KB, committed=3798707KB -                 Java Heap (reserved=3072000KB, committed=3072000KB)                            (mmap: reserved=3072000KB, committed=3072000KB) -                     Class (reserved=1075949KB, committed=28973KB)                            (classes #4819)                            (malloc=749KB #13158)                            (mmap: reserved=1075200KB, committed=28224KB) -                    Thread (reserved=484222KB, committed=484222KB)                            (thread #470)                            (stack: reserved=482132KB, committed=482132KB)                            (malloc=1541KB #2371)                            (arena=550KB #938) -                      Code (reserved=253414KB, committed=25070KB)                            (malloc=3814KB #5593)                            (mmap: reserved=249600KB, committed=21256KB) -                        GC (reserved=64102KB, committed=64102KB)                            (malloc=54094KB #255)                            (mmap: reserved=10008KB, committed=10008KB) -                  Compiler (reserved=542KB, committed=542KB)                            (malloc=411KB #543)                            (arena=131KB #3) -                  Internal (reserved=50582KB, committed=50582KB)                            (malloc=50550KB #13713)                            (mmap: reserved=32KB, committed=32KB) -                    Symbol (reserved=6384KB, committed=6384KB)                            (malloc=4266KB #31727)                            (arena=2118KB #1) -    Native Memory Tracking (reserved=1325KB, committed=1325KB)                            (malloc=208KB #3083)                            (tracking overhead=1117KB) -               Arena Chunk (reserved=231KB, committed=231KB)                            (malloc=231KB) -                   Unknown (reserved=65276KB, committed=65276KB)                            (mmap: reserved=65276KB, committed=65276KB)
虽然pmap得到的内存地址和NMT大体能对的上,但仍然有不少内存去向成谜。虽然是个好工具但问题并不能解决。
gdb
非常好奇64M或者其他小内存块中是什么内容,接下来通过gdbdump出来。读取/proc目录下的maps文件,能精准的知晓目前进程的内存分布。
以下脚本通过传入进程id,能够将所关联的内存全部dump到文件中(会影响服务,慎用)。
grep rw-p /proc/$1/maps | sed -n 's/^\([0-9a-f]*\)-\([0-9a-f]*\) .*$/\1 \2/p' | while read start stop; do gdb --batch --pid $1 -ex "dump memory $1-$start-$stop.dump 0x$start 0x$stop"; done
更多时候,推荐之dump一部分内存。(再次提醒操作会影响服务,注意dump的内存块大小,慎用)。
gdb --batch --pid 75 -ex "dump memory a.dump 0x7f2bceda1000 0x7f2bcef2b000
[root]$ du -h *
dump 4.0K
55-00600000-00601000.dump 400K
55-00eb7000-00f1b000.dump 0
55-704800000-7c0352000.dump 47M
55-7f2840000000-7f2842eb8000.dump 53M
55-7f2848000000-7f284b467000.dump 64M
55-7f284c000000-7f284fffa000.dump 64M
55-7f2854000000-7f2857fff000.dump 64M
55-7f285c000000-7f2860000000.dump 64M
55-7f2864000000-7f2867ffd000.dump 1016K
55-7f286a024000-7f286a122000.dump 1016K
55-7f286a62a000-7f286a728000.dump 1016K
55-7f286d559000-7f286d657000.dump
是时候查看里面的内容了
[root]$ view 55-7f284c000000-7f284fffa000.dump
^@^@X+^?^@^@^@^@^@d(^?^@^@^@ ÿ^C^@^@^@^@^@ ÿ^C^@^@^@^@^@^@^@^@^@^@^@^@±<97>p^C^@^@^@^@ 8^^Z+^?^@^@ ^@^@d(^?^@^@ 8^^Z+^?^@^@ ^@^@d(^?^@^@
achine":524993642,"timeSecond":1460272569,"inc":2145712868,"new":false},"device":{"client":"android","uid":"xxxxx","version":881},"
device_android":{"BootSerialno":"xxxxx","CpuInfo":"0-7","MacInfo":"2c:5b:b8:b0:d5:10","RAMSize":"4027212","SdcardInfo":"xxxx","Serialno":"xxxx",
"android_id":"488aedba19097476","buildnumber":"KTU84P/1416486236","device_ip":"0.0.0.0","mac":"2c:5b:b8:b0:d5:10","market_source":"12","model":"OPPO ...more
纳尼?这些内容不应该在堆里面么?为何还会使用额外的内存进行分配?上面已经排查netty申请directbuffer的原因了,那么还有什么地方在分配堆外内存呢?
perf
传统工具失灵,快到了黔驴技穷的时候了,是时候祭出神器perf了。
使用 perf record -g -p 55 开启监控栈函数调用。运行一段时间后Ctrl+C结束,会生成一个文件perf.data。
执行perf report -i perf.data查看报告。
如图,进程大量执行bzip相关函数。搜索zip,结果如下:
-.-!
进程调用了Java_java_util_zip_Inflater_inflatBytes() 申请了内存,仅有一小部分调用Deflater释放内存。与pmap内存地址相比对,确实是bzip在搞鬼。
原创文章,转载注明出处 (http://sayhiai.com)
解决
java项目搜索zip定位到代码,发现确实有相关bzip压缩解压操作,而且GZIPInputStream有个地方没有close。
GZIPInputStream使用Inflater申请堆外内存,Deflater释放内存,调用close()方法来主动释放。如果忘记关闭,Inflater对象的生命会延续到下一次GC。在此过程中,堆外内存会一直增长。
原代码:
public byte[] decompress ( byte[] input) throws IOException {
                ByteArrayOutputStream out = new ByteArrayOutputStream();
                IOUtils.copy(new GZIPInputStream(new ByteArrayInputStream(input)), out);
                return out.toByteArray();
            }
修改后:
 public byte[] decompress(byte[] input) throws IOException {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        GZIPInputStream gzip = new GZIPInputStream(new ByteArrayInputStream(input));
        IOUtils.copy(gzip, out);
        gzip.close();
        return out.toByteArray();
    }
经观察,问题解决。
--------------------- 
作者:lycyingO 
来源:CSDN 
原文:https://blog.csdn.net/lycyingO/article/details/80854669 
版权声明:本文为博主原创文章,转载请附上博文链接!
posted @ 2019-03-30 11:44 小马歌 阅读(2361) | 评论 (0)编辑 收藏

2019年2月16日

     摘要: from:https://www.ibm.com/developerworks/cn/linux/thread/posix_thread1/index.htmlhttps://www.ibm.com/developerworks/cn/linux/thread/posix_thread2/index.htmlhttps://www.ibm.com/developerworks/cn/linux/t...  阅读全文
posted @ 2019-02-16 11:37 小马歌 阅读(206) | 评论 (0)编辑 收藏

2019年2月13日

     摘要: from:http://www.fanyilun.me/2017/04/20/MySQL%E5%8A%A0%E9%94%81%E5%88%86%E6%9E%90/MySQL加锁分析目录前言MySQL的锁如何查看事务的加锁情况不同语句的加锁情况1. 查询命中聚簇索引(主键索引)2. 查询命中唯一索引3. 查询命中二级索引(非唯一索引)4. 查询没有命中索引5. 对索引键值有修改6. 插入数据隐式锁一...  阅读全文
posted @ 2019-02-13 17:07 小马歌 阅读(644) | 评论 (0)编辑 收藏

2018年12月25日

摘要: MySQL replace into 错误案例 背景 * MySQL5.7 * ROW模式 * 表结构 CREATE TABLE `test` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `col_1` varc

MySQL replace into 错误案例

背景

* MySQL5.7  * ROW模式   * 表结构 CREATE TABLE `test` (   `id` int(10) unsigned NOT NULL AUTO_INCREMENT,   `col_1` varchar(100) DEFAULT NULL,   `col_2` varchar(100) DEFAULT NULL,   `col_3` varchar(100) DEFAULT NULL,   PRIMARY KEY (`id`),   UNIQUE KEY `col_1` (`col_1`) ) ENGINE=InnoDB  DEFAULT CHARSET=utf8 

错误场景一

其他字段value莫名其妙的没了

  • step1 初始化记录
mater:lc> REPLACE INTO test (col_1,col_2,col_3) values('a','a','a'); Query OK, 1 row affected (0.00 sec) --注意,这里是影响了1条记录  master:lc> REPLACE INTO test (col_1,col_2,col_3) values('b','b','b'); Query OK, 1 row affected (0.00 sec) --注意,这里是影响了1条记录  master:lc> REPLACE INTO test (col_1,col_2,col_3) values('c','c','c'); Query OK, 1 row affected (0.00 sec) --注意,这里是影响了1条记录   master > show create table test  | test  | CREATE TABLE `test` (   `id` int(10) unsigned NOT NULL AUTO_INCREMENT,   `col_1` varchar(100) DEFAULT NULL,   `col_2` varchar(100) DEFAULT NULL,   `col_3` varchar(100) DEFAULT NULL,   PRIMARY KEY (`id`),   UNIQUE KEY `col_1` (`col_1`) ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 |   mater > select * from test; +----+-------+-------+-------+ | id | col_1 | col_2 | col_3 | +----+-------+-------+-------+ |  1 | a     | a     | a     | |  2 | b     | b     | b     | |  3 | c     | c     | c     | +----+-------+-------+-------+ 3 rows in set (0.00 sec)  
  • step2 构造错误场景
master:lc> replace into test(col_1,col_2) values('c','cc'); Query OK, 2 rows affected (0.00 sec)  dba:lc> select * from test; +----+-------+-------+-------+ | id | col_1 | col_2 | col_3 | +----+-------+-------+-------+ |  1 | a     | a     | a     | |  2 | b     | b     | b     | |  4 | c     | cc    | NULL  | +----+-------+-------+-------+ 3 rows in set (0.00 sec)  
  • 总结
  1. col_3 的值,从原来的c,变成了NULL,天呐,数据不见了。 id 也变了。
  2. 用户原本的需求,应该是如果col_1='c' 存在,那么就改变col_2='cc',其余的记录保持不变,结果id,col_3都变化了
  3. 解决方案就是:将replace into 改成 INSERT INTO … ON DUPLICATE KEY UPDATE

但是你以为这样就完美的解决了吗? 马上就会带来另外一场灾难,请看下面的错误场景

错误场景二

ERROR 1062 (23000): Duplicate entry 'x' for key 'PRIMARY'

  • step1 初始化记录
 mater:lc> REPLACE INTO test (col_1,col_2) values('a','a'); Query OK, 1 row affected (0.00 sec) --注意,这里是影响了1条记录  master:lc> REPLACE INTO test (col_1,col_2) values('b','b'); Query OK, 1 row affected (0.00 sec) --注意,这里是影响了1条记录  master:lc> REPLACE INTO test (col_1,col_2) values('c','c'); Query OK, 1 row affected (0.00 sec) --注意,这里是影响了1条记录   master > show create table test  | test  | CREATE TABLE `test` (   `id` int(10) unsigned NOT NULL AUTO_INCREMENT,   `col_1` varchar(100) DEFAULT NULL,   `col_2` varchar(100) DEFAULT NULL,   PRIMARY KEY (`id`),   UNIQUE KEY `col_1` (`col_1`) ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 |   slave > show create table test  | test  | CREATE TABLE `test` (   `id` int(10) unsigned NOT NULL AUTO_INCREMENT,   `col_1` varchar(100) DEFAULT NULL,   `col_2` varchar(100) DEFAULT NULL,   PRIMARY KEY (`id`),   UNIQUE KEY `col_1` (`col_1`) ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 |
  • step2 构造错误场景
* master  mater:lc> REPLACE INTO test (col_1,col_2) values('c','cc'); Query OK, 2 rows affected (0.00 sec)  --注意,这里是影响了两条记录  mater:lc> show create table test  | test  | CREATE TABLE `test` (   `id` int(10) unsigned NOT NULL AUTO_INCREMENT,   `col_1` varchar(100) DEFAULT NULL,   `col_2` varchar(100) DEFAULT NULL,   PRIMARY KEY (`id`),   UNIQUE KEY `col_1` (`col_1`) ) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8 |  master:lc> select * from test +----+-------+-------+ | id | col_1 | col_2 | +----+-------+-------+ |  1 | a     | a     | |  2 | b     | b     | |  4 | c     | cc    | +----+-------+-------+ 3 rows in set (0.00 sec)  * slave  slave:lc> show create table test  | test  | CREATE TABLE `test` (   `id` int(10) unsigned NOT NULL AUTO_INCREMENT,   `col_1` varchar(100) DEFAULT NULL,   `col_2` varchar(100) DEFAULT NULL,   PRIMARY KEY (`id`),   UNIQUE KEY `col_1` (`col_1`) ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 |  slave:lc> select * from test +----+-------+-------+ | id | col_1 | col_2 | +----+-------+-------+ |  1 | a     | a     | |  2 | b     | b     | |  4 | c     | cc    | +----+-------+-------+ 3 rows in set (0.00 sec) 
  • step3 错误案例产生
* 假设有一天,master 挂了, 由slave 提升为 new mater  原slave:lc> show create table test  | test  | CREATE TABLE `test` (   `id` int(10) unsigned NOT NULL AUTO_INCREMENT,   `col_1` varchar(100) DEFAULT NULL,   `col_2` varchar(100) DEFAULT NULL,   PRIMARY KEY (`id`),   UNIQUE KEY `col_1` (`col_1`) ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 |  原slave:lc> select * from test +----+-------+-------+ | id | col_1 | col_2 | +----+-------+-------+ |  1 | a     | a     | |  2 | b     | b     | |  4 | c     | cc    | +----+-------+-------+ 3 rows in set (0.00 sec)   ===注意==  root:lc> REPLACE INTO test (col_1,col_2) values('d','d'); ERROR 1062 (23000): Duplicate entry '4' for key 'PRIMARY'  
  • 总结
* Row 模式,主从情况下,replace into 和 INSERT INTO … ON DUPLICATE KEY UPDATE 都会导致以上问题的发生 * 解决方案: 最后可以通过alter table auto_increment值解决,但是这样已经造成mater的表很长时间没有写入了。。。

最后总结

  • replace with unique key
1. 禁止 replace into (错误一,错误二 都会发生) 2. 禁止 INSERT INTOON DUPLICATE KEY UPDATE (错误二 会发生)
  • replace with primary key
1. 禁止 replace into (会发生错误场景一的案例,丢失部分字段数据) 2. 可以使用INSERT INTOON DUPLICATE KEY UPDATE 代替 replace into
posted @ 2018-12-25 19:19 小马歌 阅读(479) | 评论 (0)编辑 收藏

2018年12月3日

     摘要: from:https://cloud.tencent.com/developer/article/1004475最近研发的项目对 DB 依赖比较重,梳理了这段时间使用MySQL遇到的8个比较具有代表性的问题,答案也比较偏自己的开发实践,没有 DBA专业和深入,有出入的请使劲拍砖!MySQL读写性能是多少,有哪些性能相关的配置参数?MySQL负载高时,如何找到是由哪些SQL引起的?如何针对具体的SQ...  阅读全文
posted @ 2018-12-03 15:55 小马歌 阅读(293) | 评论 (0)编辑 收藏
仅列出标题  下一页