2017年6月23日

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

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

我这里推荐几本MySQL的好书,应该能够有效避免学习MySQL的弯路,并且达到一个不错的水平。 我这里推荐的书或材料分为两个部分,分别是MySQL的使用和MySQL的源码学习。在介绍的过程中,我会穿插简单的评语或感想。

1.MySQL的使用

1.1 MySQL技术内幕:InnoDB存储引擎

学习MySQL的使用,首推姜承尧的《MySQL技术内幕:InnoDB存储引擎》,当然不是因为姜sir是我的经理才推荐这本书。这本书确实做到了由渐入深、深入浅出,是中国人写的最赞的MySQL技术书籍,符合国人的思维方式和阅读习惯,而且,这本书简直就是面试宝典,对于近期有求职MySQL相关岗位的朋友,可以认真阅读,对找工作有很大的帮助。当然,也有人说这本书入门难度较大,这个就自己取舍了,个人建议就以这本书入门即可,有不懂的地方可以求助官方手册和google。

MySQL技术内幕

1.2 MySQL的官方手册

我刚开始学习MySQL的时候误区就是,没有好好阅读MySQL的官方手册。例如,我刚开始很难理解InnoDB的锁,尤其是各个情况下如何加锁,这个问题在我师弟进入百度做DBA时,也困扰了他一阵子,我们两还讨论来讨论去,其实,MySQL官方手册已经写得清清楚楚,什么样的SQL语句加什么样的锁,当然,MySQL的官方手册非常庞大,一时半会很难看完,建议先看InnoDB相关的部分。

http://dev.mysql.com/doc/refman/5.7/en/innodb-storage-engine.html

1.3 MySQL排错指南

MySQL排错指南》是2015年夏天引入中国的书籍,这本书可以说是DBA速成指南,介绍的内容其实比较简单,但是也非常实用,对于DBA这个讲究经验的工种,这本书就是传授经验的,可能对有较多工作经验的DBA来说,这本书基本没有什么用,但是,对于刚入职场的新人,或学校里的学生,这本书会有较大的帮助,非常推荐。

MySQL排错指南

1.4 高性能MySQL

高性能MySQL》是MySQL领域的经典之作,拥有广泛的影响力,学习MySQL的朋友都应该有所耳闻,所以我就不作过多介绍,唯一的建议就是仔细看、认真看、多看几遍,我每次看都会有不小的收获。这就是一本虽然书很厚,但是需要一页一页、一行一行都认真看的书。

高性能MySQL

1.5 数据库索引设计与优化

如果认真学习完前面几本书,基本上都已经对MySQL掌握得不错了,但是,如果不了解如何设计一个好的索引,仍然不能成为牛逼的DBA,牛逼的DBA和不牛逼的DBA,一半就是看对索引的掌握情况,《数据库索引设计与优化》就是从普通DBA走向牛逼DBA的捷径,这本书在淘宝内部非常推崇,但是在中国名气却不是很大,很多人不了解。这本书也是今年夏天刚有中文版本的,非常值得入手以后跟着练习,虽然知道的人不多,豆瓣上也几乎没有什么评价,但是,强烈推荐、吐血推荐!

数据库索引设计与优化

1.6 Effective MySQL系列

Effective MySQL系列》是指:

  • Effective MySQL Replication Techniques in Depth
  • Effective MySQL之SQL语句最优化
  • Effective MySQL之备份与恢复

effective

这一系列并不如前面推荐的好,其中,我只看了前两本,这几本书只能算是小册子,如果有时间可以看看,对某一个”模块”进入深入了解。

2.MySQL的源码

关于MySQL源码的书非常少,还好现在市面上有两本不错的书,而且刚好一本讲server层,一本讲innodb存储引擎层,对于学习MySQL源码会很有帮助,至少能够更加快速地了解MySQL的原理和宏观结构,然后再深入细节。此外,还有一些博客或PPT将得也很不错,这里推荐最好的几份材料。

2.1 InnoDB - A journey to the core

InnoDB - A journey to the core》 是MySQL大牛Jeremy Cole写的PPT,介绍InnoDB的存储模块,即表空间、区、段、页的格式、记录的格式、槽等等。是学习Innodb存储的最好的材料。感谢Jeremy Cole!

2.2 深入MySQL源码

登博的分享《深入MySQL源码》,相信很多想了解MySQL源码的朋友已经知道这份PPT,就不过多介绍,不过,要多说一句,登博的参考资料里列出的几个博客,都要关注一下,干货满满,是学习MySQL必须关注的博客。

2.3 深入理解MySQL核心技术

深入理解MySQL核心技术》是第一本关于MySQL源码的书,着重介绍了MySQL的Server层,重点介绍了宏观架构,对于刚开始学习MySQL源码的人,相信会有很大的帮助,我在学习MySQL源码的过程中,反复的翻阅了几遍,这本书刚开始看的时候会很痛苦,但是,对于研究MySQL源码,非常有帮助,就看你是否需要,如果没有研究MySQL源码的决心,这本书应该会被唾弃。

深入理解MySQL核心技术

2.4 MySQL内核:InnoDB存储引擎

我们组的同事写的《MySQL内核:InnoDB存储引擎》,可能宇宙范围内这本书就数我学得最认真了,虽然书中有很多编辑错误,但是,平心而论,还是写得非常好的,相对于《深入理解MySQL核心技术》,可读性更强一些,建议研究Innodb存储引擎的朋友,可以了解一下,先对Innodb有一个宏观的概念,对大致原理有一个整体的了解,然后再深入细节,肯定会比自己从头开始研究会快很多,这本书可以帮助你事半功倍。

MySQL内核

2.5 MySQL Internals Manual

MySQL Internals Manual》相对于MySQL Manual来说,写的太粗糙,谁让人家是官方文档呢,研究MySQL源码的时候可以简单地参考一下,但是,还是不要指望文档能够回答你的问题,还需要看代码才行。

http://dev.mysql.com/doc/internals/en/

2.6 MariaDB原理与实现

评论里提到的《MariaDB原理与实现》我也买了一本,还不错,MariaDB讲的并不多,重点讲了Group Commit、线程池和复制的实现,都是MySQL Server层的知识,对MySQL Server层感兴趣的可以参考一下。

MariaDB

3. 后记

希望这里推荐的材料对学习MySQL的同学、朋友有所帮助,也欢迎推荐靠谱的学习材料,大家共同进步。

posted @ 2018-12-03 15:54 小马歌 阅读(304) | 评论 (0)编辑 收藏
 
     摘要: from:https://yq.aliyun.com/articles/69520?utm_content=m_10360#6摘要: # 我的问题排查工具箱 ## 前言 平时的工作中经常碰到很多疑难问题的处理,在解决问题的同时,有一些工具起到了相当大的作用,在此书写下来,一是作为笔记,可以让自己后续忘记了可快速翻阅,二是分享,希望看到此文的同学们可以拿出自己日常觉得帮助很大的工具,大家一...  阅读全文
posted @ 2018-11-23 10:47 小马歌 阅读(255) | 评论 (0)编辑 收藏
 
     摘要: from:https://www.cnblogs.com/Anker/p/3271773.html1、前言  之前在看《unix环境高级编程》第八章进程时候,提到孤儿进程和僵尸进程,一直对这两个概念比较模糊。今天被人问到什么是孤儿进程和僵尸进程,会带来什么问题,怎么解决,我只停留在概念上面,没有深入,倍感惭愧。晚上回来google了一下,再次参考APUE,认真总结一下,加深理解。2、基本概念  我...  阅读全文
posted @ 2018-09-03 19:53 小马歌 阅读(197) | 评论 (0)编辑 收藏
 
from:http://blog.csdn.net/vfush/article/details/51086274

最近做了个Web项目, 架构上使用了 Nginx +tomcat 集群, 且全站HTTPS,用nginx 做负载,nginx和tomcat 使用内网http通信,遇到http css,js静态资源被浏览器拦截问题,网上搜索到的很多文章在描述 Nginx + Tomcat 启用 HTTPS 支持的时候,都必须在 Nginx 和 Tomcat 两边同时配置 SSL 支持,今天做个总结。

遇到问题

  1. nginx强制使用https访问(http跳转到https)
  2. http的js,css 等静态资源被浏览器拦截(http不被信任)

最后的解决方案

首先解决第一个问题全站https 
参考 
三种方式,跟大家共享一下

nginx的rewrite方法

server {   listen  192.168.1.111:80;   server_name test.com;   rewrite ^(.*)$  https://$host$1 permanent; }   

nginx的497状态码,我选择了这种方式

server {       listen       192.168.1.11:443;  #ssl端口       listen       192.168.1.11:80;   #用户习惯用http访问,加上80,后面通过497状态码让它自动跳到443端口       server_name  test.com;       #为一个server{......}开启ssl支持       ssl                  on;       #指定PEM格式的证书文件        ssl_certificate      /etc/nginx/test.pem;        #指定PEM格式的私钥文件       ssl_certificate_key  /etc/nginx/test.key;        #让http请求重定向到https请求        error_page 497  https://$host$uri?$args;   }   

index.html刷新网页

<html>   <meta http-equiv="refresh" content="0;url=https://test.com/">   </html>  

当http访问到index.html时候自动跳转到https


接下来解决第二个问题 
如果tomcat 和nginx 双方没有配置X-Forwarded-Proto tomcat就不能正确区分实际用户是http 还是https,导致tomcat 里配置的静态资源被认为是http而被浏览器拦截,request.getScheme()总是 http,而不是实际的http或https

分别配置一下 Nginx 和 Tomcat ,果然好了。 
配置 Nginx 的转发选项:

 proxy_set_header       Host $host;       proxy_set_header  X-Real-IP  $remote_addr;       proxy_set_header  X-Forwarded-For $proxy_add_x_forwarded_for;       proxy_set_header X-Forwarded-Proto  $scheme;  
  • 1
  • 2
  • 3
  • 4

配置Tomcat server.xml 的 Engine 模块下配置一个 Valve:

<Valve className="org.apache.catalina.valves.RemoteIpValve"   remoteIpHeader="X-Forwarded-For"   protocolHeader="X-Forwarded-Proto"   protocolHeaderHttpsValue="https"/>  
  • 1
  • 2
  • 3
  • 4

非80端口配置 
Nginx增加以下配置 
proxy_set_header Host $host:$server_port; 非80端口 ,用80端口时 不需要$server_port 
proxy_set_header X-Real-IP $remote_addr; 
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 
proxy_set_header X-Forwarded-Proto $scheme; 

Tomcat server.xml配置 
<Engine name="Catalina" defaultHost="localhost"> 
<Valve className="org.apache.catalina.valves.RemoteIpValve" 
remoteIpHeader="X-Forwarded-For" 
protocolHeader="X-Forwarded-Proto" 
protocolHeaderHttpsValue="https" httpsServerPort="7001"/> 非80端口时,必须增加httpsServerPort配置,不然request.getServerPort()方法返回 443. 
</Engine>

关于 RemoteIpValve,可以阅读下 doc

http://tomcat.apache.org/tomcat-6.0-doc/api/org/apache/catalina/valves/RemoteIpValve.html

posted @ 2017-10-12 11:02 小马歌 阅读(387) | 评论 (0)编辑 收藏
 
gerrit还是轻易不要尝试引入,它的权限管理,真是复杂极了。对于小型团队,初期这将是个噩梦,但是对于像OpenStack,安卓这种大型team,又是一把利器。
下面尝试测试了两个用户的简单情况,很多配置都是系统默认,没有进行啥复杂配置,即使这样也是错误百出,光一个commit就要折腾半天,而且还有些机制没搞清楚。
首先要做的准备工作就是准备两个gerrit用户,user1和user2,并且分别把user1和user2的ssh pub-key通过gerrit setting添加好。
1. 首先user1创建一个叫HelloWord的project。
   如何创建project请参考前期博客或者官方文档。
2. user1在自己的工作环境中把HelloWord clone下来
[user1@jenkins ~]$ git clone ssh://user1@gerrit.example.com:29418/HelloWorld.git
Initialized empty Git repository in /home/user1/HelloWorld/.git/
remote: Counting objects: 2, done
remote: Finding sources: 100% (2/2)
remote: Total 2 (delta 0), reused 0 (delta 0)
Receiving objects: 100% (2/2), done.
加入user1没有添加ssh pubkey的话,这一步会出permission deny

clone后,创建一个README文件并add,commit
[user1@jenkins ~]$ cd HelloWorld
[user1@jenkins HelloWorld]$ ls
[user1@jenkins HelloWorld]$ touch README
[penxiao@jenkins test]$ git add README 
[penxiao@jenkins test]$ git commit -m add README
这里注意一点,在下面要push之前,一定要配置好git config的 username和email
可以通过命令行或者直接编辑 ~/.gitconfig文件实现,而且email一定要和gerrit里注册的email一致,否者push也会出错。
[user1@jenkins HelloWorld]$ git push origin master
Counting objects: 3, done.
Writing objects: 100% (3/3), 213 bytes, done.
Total 3 (delta 0), reused 0 (delta 0)
remote: Processing changes: refs: 1, done    
To ssh://user1@gerrit.example.com:29418/HelloWorld.git
 * [new branch]      master -> master
[user1@jenkins HelloWorld]$

在gerrit的gitweb链接可以查看push的文件。
3. user2加入
[user2@jenkins ~]$ git clone ssh://user1@gerrit.example.com:29418/HelloWorld.git
Initialized empty Git repository in /home/user2/HelloWorld/.git/
remote: Counting objects: 3, done
remote: Finding sources: 100% (3/3)
remote: Total 3 (delta 0), reused 3 (delta 0)
Receiving objects: 100% (3/3), done.
[user2@jenkins ~]$ cd HelloWorld
[user2@jenkins HelloWorld]$ ls
README
[user2@jenkins HelloWorld]$ 
user2对README文件进行修改,然后要commit,push
!!!也同样注意,user2的git config,username和email的配置,email要和gerrit setting里的一致。
commit完以后可以看到
[user2@jenkins HelloWorld]$ git log
commit 7959fe47bc2d2f53539a1861aa6b0d71afe0a531
Author: user2 <user2@gerrit.com>
Date:   Thu Dec 12 00:24:53 2013 -0500
    edit README
commit 98099fc0de3ba889b18cf36f9a5af267b3ddb501
Author: user1 <user@gerrit.com>
Date:   Thu Dec 12 00:15:08 2013 -0500
    add README
[user2@jenkins HelloWorld]$
现在user2要把这次的改变push到gerrit,可以么?
不行的,可以看到
[user2@jenkins HelloWorld]$ git push origin master
Counting objects: 5, done.
Writing objects: 100% (3/3), 249 bytes, done.
Total 3 (delta 0), reused 0 (delta 0)
remote: Branch refs/heads/master:
remote: You are not allowed to perform this operation.
remote: To push into this reference you need 'Push' rights.
remote: User: user2
remote: Please read the documentation and contact an administrator
remote: if you feel the configuration is incorrect
remote: Processing changes: refs: 1, done    
To ssh://user2@gerrit.example.com:29418/HelloWorld.git
 ! [remote rejected] master -> master (prohibited by Gerrit)
error: failed to push some refs to 'ssh://user2@gerrit.example.com:29418/HelloWorld.git'
[user2@jenkins HelloWorld]$ 
这就是gerrit的精髓所在了。原因是gerrit不允许直接将本地修改同步到远程仓库。客户机必须先push到远程仓库的refs/for/*分支上,等待审核。这也是为什么我们需要使用gerrit的原因。gerrit本身就是个代码审核工具。

接下来更该push的地址:  
[user2@jenkins HelloWorld]$git config remote.origin.push refs/heads/*:refs/for/*  
此命令实际是更改的是本地仓库test_project/.git/config文件。 
再次push   
[user2@jenkins HelloWorld]$git push origin  
这次不要加master
[user2@jenkins HelloWorld]$ git push origin
Counting objects: 5, done.
Writing objects: 100% (3/3), 249 bytes, done.
Total 3 (delta 0), reused 0 (delta 0)
remote: Processing changes: refs: 1, done    
remote: ERROR: missing Change-Id in commit message footer
remote: Suggestion for commit message:
remote: edit README
remote: 
remote: Change-Id: I7959fe47bc2d2f53539a1861aa6b0d71afe0a531
remote: 
remote: Hint: To automatically insert Change-Id, install the hook:
remote:   gitdir=$(git rev-parse --git-dir); scp -p -P 29418 user2@gerrit.example.com:hooks/commit-msg ${gitdir}/hooks/
remote: 
remote: 
To ssh://user2@gerrit.example.com:29418/HelloWorld.git
 ! [remote rejected] master -> refs/for/master (missing Change-Id in commit message footer)
error: failed to push some refs to 'ssh://user2@gerrit.example.com:29418/HelloWorld.git'
尼玛,还是不行,说缺change-Id,为了能让每次commit能自己insert 这个change-id,需要从gerrit server上下载一个脚本
[user2@jenkins HelloWorld] scp -p 29418 user2@gerrit.example.com:hooks/commit-msg <local path to your git>/.git/hooks/
然后重新commit
[user2@jenkins HelloWorld]$ git commit --amend
再次查看git log
[user2@jenkins HelloWorld]$ git log
commit f6b5919170875b5b4870fca2ab906c516c97006e
Author: user2 <user2@gerrit.com>
Date:   Thu Dec 12 00:24:53 2013 -0500
    edit by user2
    
    Change-Id: Ieac68bebefee7c6d4237fa5c058386bf7c4f66b7
commit 98099fc0de3ba889b18cf36f9a5af267b3ddb501
Author: user1 <user1@gerrit.com>
Date:   Thu Dec 12 00:15:08 2013 -0500
    add README
[user2@jenkins HelloWorld]$ 
这次就有了change id
然后再次push
[user2@jenkins HelloWorld]$ git push origin
Counting objects: 5, done.
Writing objects: 100% (3/3), 289 bytes, done.
Total 3 (delta 0), reused 0 (delta 0)
remote: Processing changes: new: 1, refs: 1, done    
remote: 
remote: New Changes:
remote:   http://gerrit.example.com:8080/1
remote: 
To ssh://user2@gerrit.example.com:29418/HelloWorld.git
 * [new branch]      master -> refs/for/master
[user2@jenkins HelloWorld]$ 
posted @ 2017-08-11 11:23 小马歌 阅读(230) | 评论 (0)编辑 收藏
 

from:http://blog.csdn.net/acmman/article/details/50848595

版权声明:本文为博主原创文章,未经博主允许不得转载。

目前,业内关于OSGI技术的学习资源或者技术文档还是很少的。我在某宝网搜索了一下“OSGI”的书籍,结果倒是有,但是种类少的可怜,而且几乎没有人购买。
因为工作的原因我需要学习OSGI,所以我不得不想尽办法来主动学习OSGI。我将用文字记录学习OSGI的整个过程,通过整理书籍和视频教程,来让我更加了解这门技术,同时也让需要学习这门技术的同志们有一个清晰的学习路线。

我们需要解决一下几问题:
1.如何正确的理解和认识OSGI技术?

我们从外文资料上或者从翻译过来的资料上看到OSGi解释和定义,都是直译过来的,但是OSGI的真实意义未必是中文直译过来的意思。OSGI的解释就是Open Service Gateway Initiative,直译过来就是“开放的服务入口(网关)的初始化”,听起来非常费解,什么是服务入口初始化?

所以我们不去直译这个OSGI,我们换一种说法来描述OSGI技术。

我们来回到我们以前的某些开发场景中去,假设我们使用SSH(struts+spring+hibernate)框架来开发我们的Web项目,我们做产品设计和开发的时候都是分模块的,我们分模块的目的就是实现模块之间的“解耦”,更进一步的目的是方便对一个项目的控制和管理。
我们对一个项目进行模块化分解之后,我们就可以把不同模块交给不同的开发人员来完成开发,然后项目经理把大家完成的模块集中在一起,然后拼装成一个最终的产品。一般我们开发都是这样的基本情况。

那么我们开发的时候预计的是系统的功能,根据系统的功能来进行模块的划分,也就是说,这个产品的功能或客户的需求是划分的重要依据。

但是我们在开发过程中,我们模块之间还要彼此保持联系,比如A模块要从B模块拿到一些数据,而B模块可能要调用C模块中的一些方法(除了公共底层的工具类之外)。所以这些模块只是一种逻辑意义上的划分。

最重要的一点是,我们把最终的项目要去部署到tomcat或者jBoss的服务器中去部署。那么我们启动服务器的时候,能不能关闭项目的某个模块或功能呢?很明显是做不到的,一旦服务器启动,所有模块就要一起启动,都要占用服务器资源,所以关闭不了模块,假设能强制拿掉,就会影响其它的功能。

以上就是我们传统模块式开发的一些局限性。

我们做软件开发一直在追求一个境界,就是模块之间的真正“解耦”、“分离”,这样我们在软件的管理和开发上面就会更加的灵活,甚至包括给客户部署项目的时候都可以做到更加的灵活可控。但是我们以前使用SSH框架等架构模式进行产品开发的时候我们是达不到这种要求的。

所以我们“架构师”或顶尖的技术高手都在为模块化开发努力的摸索和尝试,然后我们的OSGI的技术规范就应运而生。

现在我们的OSGI技术就可以满足我们之前所说的境界:在不同的模块中做到彻底的分离,而不是逻辑意义上的分离,是物理上的分离,也就是说在运行部署之后都可以在不停止服务器的时候直接把某些模块拿下来,其他模块的功能也不受影响。

由此,OSGI技术将来会变得非常的重要,因为它在实现模块化解耦的路上,走得比现在大家经常所用的SSH框架走的更远。这个技术在未来大规模、高访问、高并发的Java模块化开发领域,或者是项目规范化管理中,会大大超过SSH等框架的地位。

现在主流的一些应用服务器,Oracle的weblogic服务器,IBM的WebSphere,JBoss,还有Sun公司的glassfish服务器,都对OSGI提供了强大的支持,都是在OSGI的技术基础上实现的。有那么多的大型厂商支持OSGI这门技术,我们既可以看到OSGI技术的重要性。所以将来OSGI是将来非常重要的技术。

但是OSGI仍然脱离不了框架的支持,因为OSGI本身也使用了很多spring等框架的基本控件(因为要实现AOP依赖注入等功能),但是哪个项目又不去依赖第三方jar呢?



2.OSGI技术对我们项目的开发有什么帮助?

(1)项目展示
接下来我们同过项目代码来展示一下OSGI的魅力:
我们先不要去急着理解如何使用OSGI,我们通过一个项目先来看一下OSGI的效果。
(以下工程代码是网上教学视频中的样例,源码我这里是没有的)
(提前说一下:我们要学习的重点就是我们这个购物网站如何结合OSGI技术,使得项目更加的灵活可控,而购物网站本身并不是重点。)


首先在Eclipse中先打开我们的单服务器版本的项目:

启动成功:




这是一个Web项目,我们打开浏览器看一下效果:

可以看出是一个网上购物的项目。

我们来看一下我们基于OSGI技术的项目和我们一般的项目有什么区别。
首先介绍一下这个项目的模块:

1.大类展示


2.小类展示(大类的子产品)

点进去之后就是产品的具体信息



3.购物车
没买东西是空的:

买完之后:



4.商品管理(上架、下架)



可以看到,这个项目和我们平常开发的项目没有什么不同(我知道界面很简陋= =),重点是它的启动和加载过程。


(2)关于服务器
我们是通过动态加载,也就是“热部署”来启动我们的项目的。就是说,我们这个项目把它放在Web容器中之后,我们可以将某些功能给它拿下来,而且拿下来的时候不会对其他模块造成影响。

我们以前运行tomcat的时候,启动一下服务器,将Web项目一次性装载完毕,控制台会出现类似这种信息:


但是我们启动这个项目的时候并不是这样:


那么我们没有用tomcat和jBoss,那是如何部署和启动Web项目的呢?不可能没有Web服务器中间件的啊?这里告诉大家,OSGI技术里面也是内嵌了一个Web服务器的,就是jetty。


我们打开这个项目的Run Configuration配置窗口,看一下运行这个项目所需要的插件包:


可以看到,除了一些Web项目需要的jar包,还是有jetty的存在的。所以用到的服务器是jetty,不再是tomcat。


大家可能还是比较熟悉tomcat,对于jetty不是太熟悉,那么我们简单介绍一下jetty:
jetty也是一个比较优秀的Web容器,在某些性能方面要比tomcat强大的多(如高并发,长连接)。而且它的整个结构比tomcat轻巧很多(tomcat更臃肿),具体区别大家可以去网上自己看一下。


(3)运行模式和插件
我们接下来正式看一下此项目在OSGI下的运行模式:
我们在启动的时候,加载了四个模块,分别是:

按照模块化的思想他们就是四个对应的功能模块。
他们对应的四个功能模块的工程代码我们可以在Eclipse中看到:



我们看一下我们的启动配置(依然打开是Run Configuration配置窗口):


配置分为“WorkSpace”和“Target Platform”,分别是我们工作空间(我们自己写的项目模块和工具类)的插件和运行平台(一些依赖jar的配置)的插件,两者结合启动我们的项目就会正常运行。

我们启动项目之后,在控制台输入指令“ss”,就会出现我们所有加载的插件的运行情况:


一启动的时候,它会首先加载Eclipse的OSGI插件(Eclipse本身也是一种OSGI的容器):

我们打开我们的Eclipse安装目录,然后找到plugins文件夹,可以看到Eclipse所有的插件:

可以看到有文件夹形式的,有jar形式的插件。

我们怎么去理解插件呢?
插件其实就是被开发工具或OSGI容器管理和配置起来的jar包。  

我们随便打开一个文件夹类型的插件,可以看到:

可以看到里面除了lib之外还有其它东西,然后有一个“OSGI-INF”文件夹。且不管它是什么,这都足以说明我们的Eclipse就是一个OSGI容器。

(4)热部署和热启动
我们接下来回到重点,在我们启动的过程中,我们不停止运行,然后去停掉其中的一个模块:

假如我们要停掉“管理”模块:

也就是停掉id为22的插件

结果:


然后刷新我们的网站主页面:

发现我们的“管理”模块消失了!

这个模块的消失并不是javascript的技术,而是一种服务器技术,我们是通过服务器内部把它动态卸载掉的。

我们的管理模块去掉之后,网站的其它功能不受任何影响。至此我们的服务器没有进行任何的暂停或关闭。

我们再停掉“购物车”模块:

效果:

其它模块依旧不受影响。

我们关闭了两个模块,现在输入ss看一下所有插件和模块的运行情况:

可以看到我们的两个模块处于RESOLVED状态,也就是待解决状态。

当然我们也可以将我们的模块在服务器开启状态下部署上去:
如我们启动购物车模块:


发现购物车回来了:


这就是所谓的热部署,即是这个项目把它放在Web容器中之后,我们可以将某些功能给它拿下来,而且拿下来的时候不会对其他模块造成影响。


通过购物网站这个项目让大家真实的感受一下OSGI这个技术在项目开发和管理的一些强大的功能。

想进一步了解更多关于OSGI的知识可以查看以后的总结文章。

转载请注明出处:http://blog.csdn.net/acmman/article/details/50848595

20
posted @ 2017-08-10 15:57 小马歌 阅读(277) | 评论 (0)编辑 收藏
 
from:http://www.jianshu.com/p/ccadc2bdb6d7

第5章 Spring Boot自动配置原理

5.1 SpringBoot的核心组件模块

首先,我们来简单统计一下SpringBoot核心工程的源码java文件数量:

我们cd到spring-boot-autoconfigure工程根目录下。执行

$ tree | grep -c .java$
模块 java文件数
spring-boot 551
spring-boot-actuator 423
spring-boot-autoconfigure 783
spring-boot-devtools 169
spring-boot-cli 180
spring-boot-tools 355

我们可以看到有783个java文件。spring-boot核心工程有551个java文件。从上面的java文件数量大致可以看出,SpringBoot技术框架的核心组成部分:

spring-boot-autoconfigure spring-boot spring-boot-tools

我们把SpringBoot源码导入IntelliJ IDEA,查看artifact的全部依赖关系。

IDEA有个Maven Projects窗口,一般在右侧能够找到,如果没有可以从菜单栏打开:View>Tool Windows>Maven Projects;

选择要分析的maven module(idea的module相当于eclipse的project),右击show dependencies,会出来该module的全部依赖关系图,非常清晰细致。

例如,spring-boot-starter-freemarker的依赖图分析如下:


在spring-boot-build 的pom中,我们可以看到:

           <modules>                 <module>spring-boot-dependencies</module>                 <module>spring-boot-parent</module>                 <module>spring-boot-tools</module>                 <module>spring-boot</module>                 <module>spring-boot-test</module>                 <module>spring-boot-autoconfigure</module>                 <module>spring-boot-test-autoconfigure</module>                 <module>spring-boot-actuator</module>                 <module>spring-boot-devtools</module>                 <module>spring-boot-docs</module>                 <module>spring-boot-starters</module>                 <module>spring-boot-actuator-docs</module>                 <module>spring-boot-cli</module>             </modules>

其中,在spring-boot-dependencies中,SpringBoot项目维护了一份庞大依赖。这些依赖的版本都是经过实践,测试通过,不会发生依赖冲突的。就这样一个事情,就大大减少了Spring开发过程中,出现jar包冲突的概率。spring-boot-parent依赖spring-boot-dependencies。

下面我们简要介绍一下SpringBoot子modules。

spring-boot

SpringBoot核心工程。

spring-boot-starters

是SpringBoot的启动服务工程。

spring-boot-autoconfigure

是SpringBoot实现自动配置的核心工程。

spring-boot-actuator

提供SpringBoot应用的外围支撑性功能。 比如:

  • Endpoints,SpringBoot应用状态监控管理
  • HealthIndicator,SpringBoot应用健康指示表
  • 提供metrics支持
  • 提供远程shell支持

spring-boot-tools

提供了SpringBoot开发者的常用工具集。诸如,spring-boot-gradle-plugin,spring-boot-maven-plugin就是这个工程里面的。

spring-boot-cli

是Spring Boot命令行交互工具,可用于使用Spring进行快速原型搭建。你可以用它直接运行Groovy脚本。如果你不喜欢Maven或Gradle,Spring提供了CLI(Command Line Interface)来开发运行Spring应用程序。你可以使用它来运行Groovy脚本,甚至编写自定义命令。

5.2 SpringBoot Starters

Spring boot中的starter概念是非常重要的机制,能够抛弃以前繁杂的配置,统一集成进starter,应用者只需要引入starter jar包,spring boot就能自动扫描到要加载的信息。

starter让我们摆脱了各种依赖库的处理,需要配置各种信息的困扰。Spring Boot会自动通过classpath路径下的类发现需要的Bean,并织入bean。

例如,如果你想使用Spring和用JPA访问数据库,你只要依赖 spring-boot-starter-data-jpa 即可。

目前,github上spring-boot项目的最新的starter列表spring-boot/spring-boot-starters如下:

spring-boot-starter spring-boot-starter-activemq spring-boot-starter-actuator spring-boot-starter-amqp spring-boot-starter-aop spring-boot-starter-artemis spring-boot-starter-batch spring-boot-starter-cache spring-boot-starter-cloud-connectors spring-boot-starter-data-cassandra spring-boot-starter-data-couchbase spring-boot-starter-data-elasticsearch spring-boot-starter-data-jpa spring-boot-starter-data-ldap spring-boot-starter-data-mongodb spring-boot-starter-data-mongodb-reactive spring-boot-starter-data-neo4j spring-boot-starter-data-redis spring-boot-starter-data-rest spring-boot-starter-data-solr spring-boot-starter-freemarker spring-boot-starter-groovy-templates spring-boot-starter-hateoas spring-boot-starter-integration spring-boot-starter-jdbc spring-boot-starter-jersey spring-boot-starter-jetty spring-boot-starter-jooq spring-boot-starter-jta-atomikos spring-boot-starter-jta-bitronix spring-boot-starter-jta-narayana spring-boot-starter-log4j2 spring-boot-starter-logging spring-boot-starter-mail spring-boot-starter-mobile spring-boot-starter-mustache spring-boot-starter-parent spring-boot-starter-reactor-netty spring-boot-starter-security spring-boot-starter-social-facebook spring-boot-starter-social-linkedin spring-boot-starter-social-twitter spring-boot-starter-test spring-boot-starter-thymeleaf spring-boot-starter-tomcat spring-boot-starter-undertow spring-boot-starter-validation spring-boot-starter-web spring-boot-starter-web-services spring-boot-starter-webflux spring-boot-starter-websocket

(源代码目录执行shell:l|awk '{print $9}', l|awk '{print $9}'|grep -c 'starter')

共52个。每个starter工程里面的pom描述有相应的介绍。具体的说明,参考官网文档[1]。关于这些starters的使用例子,可以参考spring-boot/spring-boot-samples

比如说,spring-boot-starter是:

Core starter, including auto-configuration support, logging and YAML

这是Spring Boot的核心启动器,包含了自动配置、日志和YAML。它的项目依赖图如下:



可以看出,这些starter只是配置,真正做自动化配置的代码的是在spring-boot-autoconfigure里面。同时spring-boot-autoconfigure依赖spring-boot工程,这个spring-boot工程是SpringBoot的核心。

SpringBoot会基于你的classpath中的jar包,试图猜测和配置您可能需要的bean。

例如,如果你的classpath中有tomcat-embedded.jar,你可能会想要一个TomcatEmbeddedServletContainerFactory Bean (SpringBoot通过获取EmbeddedServletContainerFactory来启动对应的web服务器。常用的两个实现类是TomcatEmbeddedServletContainerFactory和JettyEmbeddedServletContainerFactory)。

其他的所有基于Spring Boot的starter都依赖这个spring-boot-starter。比如说spring-boot-starter-actuator的依赖树,如下图:


5.3 @EnableAutoConfiguration自动配置原理

通过@EnableAutoConfiguration启用Spring应用程序上下文的自动配置,这个注解会导入一个EnableAutoConfigurationImportSelector的类,而这个类会去读取一个spring.factories下key为EnableAutoConfiguration对应的全限定名的值。

这个spring.factories里面配置的那些类,主要作用是告诉Spring Boot这个stareter所需要加载的那些xxxAutoConfiguration类,也就是你真正的要自动注册的那些bean或功能。然后,我们实现一个spring.factories指定的类,标上@Configuration注解,一个starter就定义完了。

如果想从自己的starter种读取应用的starter工程的配置,只需要在入口类上加上如下注解即可:

@EnableConfigurationProperties(MyProperties.class)

读取spring.factories文件的实现

是通过org.springframework.core.io.support.SpringFactoriesLoader实现。

SpringFactoriesLoader的实现类似于SPI(Service Provider Interface,在java.util.ServiceLoader的文档里有比较详细的介绍。java SPI提供一种服务发现机制,为某个接口寻找服务实现的机制。有点类似IOC的思想,就是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要[3])。

SpringFactoriesLoader会加载classpath下所有JAR文件里面的META-INF/spring.factories文件。

其中加载spring.factories文件的代码在loadFactoryNames方法里:

public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";  ....      public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {         String factoryClassName = factoryClass.getName();         try {             Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :                     ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));             List<String> result = new ArrayList<>();             while (urls.hasMoreElements()) {                 URL url = urls.nextElement();                 Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));                 String factoryClassNames = properties.getProperty(factoryClassName);                 result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));             }             return result;         }         catch (IOException ex) {             throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() +                     "] factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex);         }     }

通过org.springframework.boot.autoconfigure.AutoConfigurationImportSelector里面的getCandidateConfigurations方法,获取到候选类的名字List<String>。该方法代码如下:

    protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,             AnnotationAttributes attributes) {         List<String> configurations = SpringFactoriesLoader.loadFactoryNames(                 getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());         Assert.notEmpty(configurations,                 "No auto configuration classes found in META-INF/spring.factories. If you "                         + "are using a custom packaging, make sure that file is correct.");         return configurations;     }

其中,getSpringFactoriesLoaderFactoryClass()方法直接返回的是EnableAutoConfiguration.class, 代码如下:

    protected Class<?> getSpringFactoriesLoaderFactoryClass() {         return EnableAutoConfiguration.class;     }

所以,getCandidateConfigurations方法里面的这段代码:

List<String> configurations = SpringFactoriesLoader.loadFactoryNames(                 getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());

会过滤出key为org.springframework.boot.autoconfigure.EnableAutoConfiguration的全限定名对应的值。全限定名都使用如下命名方法:

包名.外部类名 包名.外部类名$内部类名  e.g:  org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration

SpringBoot中的META-INF/spring.factories(完整路径:spring-boot/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories)中关于EnableAutoConfiguration的这段配置如下:

# Auto Configure org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\ org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\ org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\ org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\ org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\ org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\ org.springframework.boot.autoconfigure.cloud.CloudAutoConfiguration,\ org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\ org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\ org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\ org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration,\ org.springframework.boot.autoconfigure.dao.PersistenceExceptionTranslationAutoConfiguration,\ org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration,\ org.springframework.boot.autoconfigure.data.cassandra.CassandraRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.couchbase.CouchbaseDataAutoConfiguration,\ org.springframework.boot.autoconfigure.data.couchbase.CouchbaseRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchAutoConfiguration,\ org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration,\ org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.ldap.LdapDataAutoConfiguration,\ org.springframework.boot.autoconfigure.data.ldap.LdapRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration,\ org.springframework.boot.autoconfigure.data.mongo.MongoRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.mongo.ReactiveMongoDataAutoConfiguration,\ org.springframework.boot.autoconfigure.data.mongo.ReactiveMongoRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.neo4j.Neo4jDataAutoConfiguration,\ org.springframework.boot.autoconfigure.data.neo4j.Neo4jRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.solr.SolrRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\ org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.rest.RepositoryRestMvcAutoConfiguration,\ org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration,\ org.springframework.boot.autoconfigure.elasticsearch.jest.JestAutoConfiguration,\ org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration,\ org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration,\ org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration,\ org.springframework.boot.autoconfigure.h2.H2ConsoleAutoConfiguration,\ org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration,\ org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfiguration,\ org.springframework.boot.autoconfigure.hazelcast.HazelcastJpaDependencyAutoConfiguration,\ org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration,\ org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration,\ org.springframework.boot.autoconfigure.integration.IntegrationAutoConfiguration,\ org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration,\ org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\ org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration,\ org.springframework.boot.autoconfigure.jdbc.JndiDataSourceAutoConfiguration,\ org.springframework.boot.autoconfigure.jdbc.XADataSourceAutoConfiguration,\ org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration,\ org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration,\ org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration,\ org.springframework.boot.autoconfigure.jms.JndiConnectionFactoryAutoConfiguration,\ org.springframework.boot.autoconfigure.jms.activemq.ActiveMQAutoConfiguration,\ org.springframework.boot.autoconfigure.jms.artemis.ArtemisAutoConfiguration,\ org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration,\ org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration,\ org.springframework.boot.autoconfigure.jooq.JooqAutoConfiguration,\ org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration,\ org.springframework.boot.autoconfigure.ldap.embedded.EmbeddedLdapAutoConfiguration,\ org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration,\ org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration,\ org.springframework.boot.autoconfigure.mail.MailSenderAutoConfiguration,\ org.springframework.boot.autoconfigure.mail.MailSenderValidatorAutoConfiguration,\ org.springframework.boot.autoconfigure.mobile.DeviceResolverAutoConfiguration,\ org.springframework.boot.autoconfigure.mobile.DeviceDelegatingViewResolverAutoConfiguration,\ org.springframework.boot.autoconfigure.mobile.SitePreferenceAutoConfiguration,\ org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration,\ org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration,\ org.springframework.boot.autoconfigure.mongo.ReactiveMongoAutoConfiguration,\ org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration,\ org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\ org.springframework.boot.autoconfigure.reactor.core.ReactorCoreAutoConfiguration,\ org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration,\ org.springframework.boot.autoconfigure.security.SecurityFilterAutoConfiguration,\ org.springframework.boot.autoconfigure.security.FallbackWebSecurityAutoConfiguration,\ org.springframework.boot.autoconfigure.security.oauth2.OAuth2AutoConfiguration,\ org.springframework.boot.autoconfigure.sendgrid.SendGridAutoConfiguration,\ org.springframework.boot.autoconfigure.session.SessionAutoConfiguration,\ org.springframework.boot.autoconfigure.social.SocialWebAutoConfiguration,\ org.springframework.boot.autoconfigure.social.FacebookAutoConfiguration,\ org.springframework.boot.autoconfigure.social.LinkedInAutoConfiguration,\ org.springframework.boot.autoconfigure.social.TwitterAutoConfiguration,\ org.springframework.boot.autoconfigure.solr.SolrAutoConfiguration,\ org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration,\ org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration,\ org.springframework.boot.autoconfigure.transaction.jta.JtaAutoConfiguration,\ org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration,\ org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration,\ org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration,\ org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerAutoConfiguration,\ org.springframework.boot.autoconfigure.web.reactive.WebFluxAnnotationAutoConfiguration,\ org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration,\ org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration,\ org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration,\ org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration,\ org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration,\ org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,\ org.springframework.boot.autoconfigure.websocket.WebSocketAutoConfiguration,\ org.springframework.boot.autoconfigure.websocket.WebSocketMessagingAutoConfiguration,\ org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration

当然了,这些AutoConfiguration不是所有都会加载的,会根据AutoConfiguration上的@ConditionalOnClass等条件,再进一步判断是否加载。我们下文通过FreeMarkerAutoConfiguration实例来分析整个自动配置的过程。

5.4 FreeMarkerAutoConfiguration自动配置的实例分析

我们首先看spring-boot-starter-freemarker工程,目录结构如下:

. ├── pom.xml ├── spring-boot-starter-freemarker.iml └── src     └── main         └── resources             └── META-INF                 └── spring.provides  4 directories, 3 files

我们可以看出,这个工程没有任何Java代码,只有两个文件:pom.xml跟spring.provides。starter本身在你的应用程序中实际上是空的。

其中,
spring.provides文件

provides: freemarker,spring-context-support

主要是给这个starter起个好区分的名字。

Spring Boot 通过starter对项目的依赖进行统一管理. starter利用了maven的传递依赖解析机制,把常用库聚合在一起, 组成了针对特定功能而定制的依赖starter。

我们可以使用IDEA提供的maven依赖图分析的功能(如下图),得到spring-boot-starter-freemarker依赖的module。


IDEA提供的maven依赖图分析

spring-boot-starter-freemarker依赖的module

从上面的依赖图,我们可以清晰看出其间依赖关系。

当Spring Boot Application中自动配置EnableAutoConfiguration的相关类执行完毕之后,Spring Boot会进一步解析对应类的配置信息。如果我们配置了spring-boot-starter-freemarker ,maven就会通过这个starter所依赖的spring-boot-autoconfigure,自动传递到spring-boot-autoconfigure工程中。

我们来简单分析一下spring-boot-autoconfigure工程的架构。

其中,FreeMarker的自动配置类是org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration。

下面我们来简要分析一下FreeMarkerAutoConfiguration这个类。

在FreeMarkerAutoConfiguration类上面有四行注解:

@Configuration @ConditionalOnClass({ freemarker.template.Configuration.class,         FreeMarkerConfigurationFactory.class }) @AutoConfigureAfter(WebMvcAutoConfiguration.class) @EnableConfigurationProperties(FreeMarkerProperties.class) public class FreeMarkerAutoConfiguration {     ... }

其中,
(1)@Configuration,是org.springframework.context.annotation包里面的注解。这么说吧,用@Configuration注解该类,等价 与XML中配置beans;用@Bean标注方法等价于XML中配置bean。

(2)@ConditionalOnClass,org.springframework.boot.autoconfigure.condition包里面的注解。意思是当类路径下有指定的类的条件下,才会去注册被标注的类为一个bean。在上面的代码中的意思就是,当类路径中有freemarker.template.Configuration.class,FreeMarkerConfigurationFactory.class两个类的时候,才会实例化FreeMarkerAutoConfiguration这个Bean。

(3)@AutoConfigureAfter,org.springframework.boot.autoconfigure包里面的注解。这个通过注解的名字意思就可以知道,当WebMvcAutoConfiguration.class这个类实例化完毕,才能实例化FreeMarkerAutoConfiguration(有个先后顺序)。SpringBoot使用@ AutoConfigureBefore、@AutoConfigureAfter注解来定义这些配置类的载入顺序。

(4)@EnableConfigurationProperties,表示启动对FreeMarkerProperties.class的内嵌配置支持,自动将FreeMarkerProperties注册为一个bean。这个FreeMarkerProperties类里面就是关于FreeMarker属性的配置:

@ConfigurationProperties(prefix = "spring.freemarker") public class FreeMarkerProperties extends AbstractTemplateViewResolverProperties {      public static final String DEFAULT_TEMPLATE_LOADER_PATH = "classpath:/templates/";      public static final String DEFAULT_PREFIX = "";      public static final String DEFAULT_SUFFIX = ".ftl";      /**      * Well-known FreeMarker keys which will be passed to FreeMarker's Configuration.      */     private Map<String, String> settings = new HashMap<>();      /**      * Comma-separated list of template paths.      */     private String[] templateLoaderPath = new String[] { DEFAULT_TEMPLATE_LOADER_PATH };      /**      * Prefer file system access for template loading. File system access enables hot      * detection of template changes.      */     private boolean preferFileSystemAccess = true;      public FreeMarkerProperties() {         super(DEFAULT_PREFIX, DEFAULT_SUFFIX);     }      public Map<String, String> getSettings() {         return this.settings;     }      public void setSettings(Map<String, String> settings) {         this.settings = settings;     }      public String[] getTemplateLoaderPath() {         return this.templateLoaderPath;     }      public boolean isPreferFileSystemAccess() {         return this.preferFileSystemAccess;     }      public void setPreferFileSystemAccess(boolean preferFileSystemAccess) {         this.preferFileSystemAccess = preferFileSystemAccess;     }      public void setTemplateLoaderPath(String... templateLoaderPaths) {         this.templateLoaderPath = templateLoaderPaths;     }  }

综上,当(1)(2)两个条件满足时,才会继续(3)(4)的动作,同时注册FreeMarkerAutoConfiguration这个Bean。该类的结构如下图:


我们来看其内部类FreeMarkerWebConfiguration的代码:

    @Configuration     @ConditionalOnClass(Servlet.class)     @ConditionalOnWebApplication(type = Type.SERVLET)     public static class FreeMarkerWebConfiguration extends FreeMarkerConfiguration {          @Bean         @ConditionalOnMissingBean(FreeMarkerConfig.class)         public FreeMarkerConfigurer freeMarkerConfigurer() {             FreeMarkerConfigurer configurer = new FreeMarkerConfigurer();             applyProperties(configurer);             return configurer;         }          @Bean         public freemarker.template.Configuration freeMarkerConfiguration(                 FreeMarkerConfig configurer) {             return configurer.getConfiguration();         }          @Bean         @ConditionalOnMissingBean(name = "freeMarkerViewResolver")         @ConditionalOnProperty(name = "spring.freemarker.enabled", matchIfMissing = true)         public FreeMarkerViewResolver freeMarkerViewResolver() {             FreeMarkerViewResolver resolver = new FreeMarkerViewResolver();             this.properties.applyToViewResolver(resolver);             return resolver;         }          @Bean         @ConditionalOnMissingBean         @ConditionalOnEnabledResourceChain         public ResourceUrlEncodingFilter resourceUrlEncodingFilter() {             return new ResourceUrlEncodingFilter();         }      }

其中,
(1)@ConditionalOnWebApplication(type = Type.SERVLET), 是当该应用是基于Servlet的Web应用时。

(2)@ConditionalOnMissingBean(name = "freeMarkerViewResolver"),是当Spring容器中不存在freeMarkerViewResolver的Bean时。

(3)@ConditionalOnProperty(name = "spring.freemarker.enabled", matchIfMissing = true),指定的spring.freemarker.enabled属性是否有。如果没有(IfMissing),设为true。

当(1)(2)(3)三个条件都满足,则注册freeMarkerViewResolver这个Bean。

我们也可以自定义我们自己的my-starter,以及实现对应的@MyEnableAutoConfiguration。SpringBoot有很多第三方starter,其自动配置的原理基本都是这样,比如mybatis-spring-boot-starter的MybatisAutoConfiguration,阅读源码https://github.com/mybatis/spring-boot-starter[4]

上面文字描述了这么多,再用一张形象生动的图来说明[5]:


SpringBoot Autoconfigure 工作原理图

5.5 spring.factories与定义应用程序的初始化行为

上面说了这么多,讲的都是读取properties文件中key为org.springframework.boot.autoconfigure.EnableAutoConfiguration的全限定名对应的值。SpringBoot内部还有许多其他的key用于过滤得到需要加载的类。

# Initializers org.springframework.context.ApplicationContextInitializer=\ org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\ org.springframework.boot.autoconfigure.logging.AutoConfigurationReportLoggingInitializer  # Application Listeners org.springframework.context.ApplicationListener=\ org.springframework.boot.autoconfigure.BackgroundPreinitializer  # Auto Configuration Import Listeners org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\ org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener  # Auto Configuration Import Filters org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\ org.springframework.boot.autoconfigure.condition.OnClassCondition  # Failure analyzers org.springframework.boot.diagnostics.FailureAnalyzer=\ org.springframework.boot.autoconfigure.diagnostics.analyzer.NoSuchBeanDefinitionFailureAnalyzer,\ org.springframework.boot.autoconfigure.jdbc.DataSourceBeanCreationFailureAnalyzer,\ org.springframework.boot.autoconfigure.jdbc.HikariDriverConfigurationFailureAnalyzer  # Template availability providers org.springframework.boot.autoconfigure.template.TemplateAvailabilityProvider=\ org.springframework.boot.autoconfigure.freemarker.FreeMarkerTemplateAvailabilityProvider,\ org.springframework.boot.autoconfigure.mustache.MustacheTemplateAvailabilityProvider,\ org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAvailabilityProvider,\ org.springframework.boot.autoconfigure.thymeleaf.ThymeleafTemplateAvailabilityProvider,\ org.springframework.boot.autoconfigure.web.servlet.JspTemplateAvailabilityProvider

这些key仍然是定义在spring-boot/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories文件中。

还有对应的用于测试的自动配置,在
spring-boot/spring-boot-test-autoconfigure/src/main/resources/META-INF/spring.factories文件中定义。

另外,我们使用spring.factories里还可以定制应用程序的初始化行为。这样我们就可以在应用程序载入前操纵Spring的应用程序上下文ApplicationContext。

例如,可以使用ConfigurableApplicationContext类的addApplicationListener()方法,在应用上下文ApplicationContext中创建监听器。

自动配置运行日志报告功能就是这么实现的。我们来看在spring.factories中,Initializers一段的配置:

# Initializers org.springframework.context.ApplicationContextInitializer=\ org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\ org.springframework.boot.autoconfigure.logging.AutoConfigurationReportLoggingInitializer

其中,AutoConfigurationReportLoggingInitializer监听到系统事件时,比如上下文刷新ContextRefreshedEvent或应用程序启动故障ApplicationFailedEvent之类的事件,Spring Boot可以做一些事情。这里说的代码在AutoConfigurationReportLoggingInitializer.AutoConfigurationReportListener里面。关于支持的事件类型supportsEventType的如下:

    private class AutoConfigurationReportListener implements GenericApplicationListener {  ...         @Override         public boolean supportsEventType(ResolvableType resolvableType) {             Class<?> type = resolvableType.getRawClass();             if (type == null) {                 return false;             }             return ContextRefreshedEvent.class.isAssignableFrom(type)                     || ApplicationFailedEvent.class.isAssignableFrom(type);         }          @Override         public boolean supportsSourceType(Class<?> sourceType) {             return true;         }          @Override         public void onApplicationEvent(ApplicationEvent event) {     AutoConfigurationReportLoggingInitializer.this.onApplicationEvent(event);         }      }

要以调试模式启动应用程序,可以使用-Ddebug标识,或者在application.properties文件这添加属性debug= true。这样,当我们以调试模式启动应用程序时,SpringBoot就可以帮助我们创建自动配置的运行报告。对于每个自动配置,通过报告我们可以看到它启动或失败的原因。 这个报告内容格式大致如下:

========================= AUTO-CONFIGURATION REPORT =========================   Positive matches: -----------------     DataSourceAutoConfiguration matched:       - @ConditionalOnClass found required classes 'javax.sql.DataSource', 'org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType'; @ConditionalOnMissingClass did not find unwanted class (OnClassCondition)     DataSourceAutoConfiguration#dataSourceInitializer matched:       - @ConditionalOnMissingBean (types: org.springframework.boot.autoconfigure.jdbc.DataSourceInitializer; SearchStrategy: all) did not find any beans (OnBeanCondition)     DataSourceAutoConfiguration.PooledDataSourceConfiguration matched:       - AnyNestedCondition 2 matched 0 did not; NestedCondition on DataSourceAutoConfiguration.PooledDataSourceCondition.PooledDataSourceAvailable PooledDataSource found supported DataSource; NestedCondition on DataSourceAutoConfiguration.PooledDataSourceCondition.ExplicitType @ConditionalOnProperty (spring.datasource.type) matched (DataSourceAutoConfiguration.PooledDataSourceCondition)       - @ConditionalOnMissingBean (types: javax.sql.DataSource,javax.sql.XADataSource; SearchStrategy: all) did not find any beans (OnBeanCondition)     ...  Exclusions: -----------      None   Unconditional classes: ----------------------      org.springframework.boot.autoconfigure.web.WebClientAutoConfiguration      org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration      org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration      org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration

除了SpringBoot官方提供的starter外,还有社区贡献的很多常用的第三方starter,列表可参考[2]。

另外,国内很多公司使用RPC框架dubbo,关于SpringBoot集成dubbo,可参考:https://github.com/linux-china/spring-boot-dubbo。

参考资料:

1.http://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#using-boot-starter
2.https://github.com/spring-projects/spring-boot/tree/master/spring-boot-starters
3.http://www.cnblogs.com/javaee6/p/3714719.html
4.https://github.com/mybatis/spring-boot-starter
5.https://afoo.me/posts/2015-07-09-how-spring-boot-works.html



作者:华夏商周秦汉唐宋元明清中华民国
链接:http://www.jianshu.com/p/ccadc2bdb6d7
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
posted @ 2017-08-02 16:35 小马歌 阅读(1716) | 评论 (0)编辑 收藏
 
     摘要: from:http://blog.csdn.net/lylwo317/article/details/52163304序言注解在Java中到底是什么样的东西?具体是如何实现的? 本文将一层一层深入探究注解的实现原理。为了尽可能的将分析的过程呈现出来,所以文章包含了大量的截图和代码。(ps:如果图片看不清楚,请将网页放大来看,chrome可以通过ctrl+鼠标滚轮放大)前期准备知识方面开始...  阅读全文
posted @ 2017-08-02 11:15 小马歌 阅读(559) | 评论 (1)编辑 收藏
 

如果没有用来读取注解的方法和工作,那么注解也就不会比注释更有用处了。使用注解的过程中,很重要的一部分就是创建于使用注解处理器。Java SE5扩展了反射机制的API,以帮助程序员快速的构造自定义注解处理器。


注解处理器类库(java.lang.reflect.AnnotatedElement):

  Java使用Annotation接口来代表程序元素前面的注解,该接口是所有Annotation类型的父接口。除此之外,Java在java.lang.reflect 包下新增了AnnotatedElement接口,该接口代表程序中可以接受注解的程序元素,该接口主要有如下几个实现类:

  Class:类定义
  Constructor:构造器定义
  Field:累的成员变量定义
  Method:类的方法定义
  Package:类的包定义

  java.lang.reflect 包下主要包含一些实现反射功能的工具类,实际上,java.lang.reflect 包所有提供的反射API扩充了读取运行时Annotation信息的能力。当一个Annotation类型被定义为运行时的Annotation后,该注解才能是运行时可见,当class文件被装载时被保存在class文件中的Annotation才会被虚拟机读取。
  AnnotatedElement 接口是所有程序元素(Class、Method和Constructor)的父接口,所以程序通过反射获取了某个类的AnnotatedElement对象之后,程序就可以调用该对象的如下四个个方法来访问Annotation信息:

  方法1:<T extends Annotation> T getAnnotation(Class<T> annotationClass): 返回改程序元素上存在的、指定类型的注解,如果该类型注解不存在,则返回null。
  方法2:Annotation[] getAnnotations():返回该程序元素上存在的所有注解。
  方法3:boolean is AnnotationPresent(Class<?extends Annotation> annotationClass):判断该程序元素上是否包含指定类型的注解,存在则返回true,否则返回false.
  方法4:Annotation[] getDeclaredAnnotations():返回直接存在于此元素上的所有注释。与此接口中的其他方法不同,该方法将忽略继承的注释。(如果没有注释直接存在于此元素上,则返回长度为零的一个数组。)该方法的调用者可以随意修改返回的数组;这不会对其他调用者返回的数组产生任何影响。

  一个简单的注解处理器:  

复制代码
/***********注解声明***************/  /**  * 水果名称注解  * @author peida  *  */ @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface FruitName {     String value() default ""; }  /**  * 水果颜色注解  * @author peida  *  */ @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface FruitColor {     /**      * 颜色枚举      * @author peida      *      */     public enum Color{ BULE,RED,GREEN};          /**      * 颜色属性      * @return      */     Color fruitColor() default Color.GREEN;  }  /**  * 水果供应者注解  * @author peida  *  */ @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface FruitProvider {     /**      * 供应商编号      * @return      */     public int id() default -1;          /**      * 供应商名称      * @return      */     public String name() default "";          /**      * 供应商地址      * @return      */     public String address() default ""; }  /***********注解使用***************/  public class Apple {          @FruitName("Apple")     private String appleName;          @FruitColor(fruitColor=Color.RED)     private String appleColor;          @FruitProvider(id=1,name="陕西红富士集团",address="陕西省西安市延安路89号红富士大厦")     private String appleProvider;          public void setAppleColor(String appleColor) {         this.appleColor = appleColor;     }     public String getAppleColor() {         return appleColor;     }          public void setAppleName(String appleName) {         this.appleName = appleName;     }     public String getAppleName() {         return appleName;     }          public void setAppleProvider(String appleProvider) {         this.appleProvider = appleProvider;     }     public String getAppleProvider() {         return appleProvider;     }          public void displayName(){         System.out.println("水果的名字是:苹果");     } }  /***********注解处理器***************/  public class FruitInfoUtil {     public static void getFruitInfo(Class<?> clazz){                  String strFruitName=" 水果名称:";         String strFruitColor=" 水果颜色:";         String strFruitProvicer="供应商信息:";                  Field[] fields = clazz.getDeclaredFields();                  for(Field field :fields){             if(field.isAnnotationPresent(FruitName.class)){                 FruitName fruitName = (FruitName) field.getAnnotation(FruitName.class);                 strFruitName=strFruitName+fruitName.value();                 System.out.println(strFruitName);             }             else if(field.isAnnotationPresent(FruitColor.class)){                 FruitColor fruitColor= (FruitColor) field.getAnnotation(FruitColor.class);                 strFruitColor=strFruitColor+fruitColor.fruitColor().toString();                 System.out.println(strFruitColor);             }             else if(field.isAnnotationPresent(FruitProvider.class)){                 FruitProvider fruitProvider= (FruitProvider) field.getAnnotation(FruitProvider.class);                 strFruitProvicer=" 供应商编号:"+fruitProvider.id()+" 供应商名称:"+fruitProvider.name()+" 供应商地址:"+fruitProvider.address();                 System.out.println(strFruitProvicer);             }         }     } }  /***********输出结果***************/ public class FruitRun {      /**      * @param args      */     public static void main(String[] args) {                  FruitInfoUtil.getFruitInfo(Apple.class);              }  }  ====================================  水果名称:Apple  水果颜色:RED  供应商编号:1 供应商名称:陕西红富士集团 供应商地址:陕西省西安市延安路89号红富士大厦
复制代码

   Java注解的基础知识点(见下面导图)基本都过了一遍,下一篇我们通过设计一个基于注解的简单的ORM框架,来综合应用和进一步加深对注解的各个知识点的理解和运用。

 

 


分类: java
posted @ 2017-08-02 11:07 小马歌 阅读(207) | 评论 (0)编辑 收藏
 
http://blog.csdn.net/coslay/article/details/43458907
  1. 我们需要知道的是找到默认值的方法,掌握默认值的大概量级,在不同的版本下哪些是常用的默认参数,哪些是必须设置的参数,哪些是可以选择的、尽量不要去碰的设置。  
  1. 参数设置同一个类型会有多种参数,而且都有默认值,大家不要混用噢,混用的结果很多时候难以预料,虽然在某种情况下可能得到了一个测试结果,但如果你没有真正了解JVM的内核源码,是不可能知道所有细节的,即测试结果不能当成任何场景下的一个结论,只能作为一种参考。  

本篇文章基于Java 6update 21oder 21之后)版本, HotSpot JVM 提供给了两个新的参数,在JVM启动后,在命令行中可以输出所有XX参数和值。

  1. -XX:+PrintFlagsFinal and -XX:+PrintFlagsInitial  

让我们现在就了解一下新参数的输出。以 -client 作为参数的 -XX:+PrintFlagsFinal   的结果是一个按字母排序的590个参数表格(注意,每个release版本参数的数量会不一样)

  1. $ java -client -XX:+PrintFlagsFinal Benchmark  
  2. [Global flags]  
  3. uintx AdaptivePermSizeWeight               = 20               {product}  
  4. uintx AdaptiveSizeDecrementScaleFactor     = 4                {product}  
  5. uintx AdaptiveSizeMajorGCDecayTimeScale    = 10               {product}  
  6. uintx AdaptiveSizePausePolicy              = 0                {product}[...]  
  7. uintx YoungGenerationSizeSupplementDecay   = 8                {product}  
  8. uintx YoungPLABSize                        = 4096             {product}  
  9.  bool ZeroTLAB                             = false            {product}  
  10.  intx hashCode                             = 0                {product}  

(校对注:你可以尝试在命令行输入上面的命令,亲自实现下)

表格的每一行包括五列,来表示一个XX参数。第一列表示参数的数据类型,第二列是名称,第四列为值,第五列是参数的类别。第三列”=”表示第四列是参数的默认值,而”:=” 表明了参数被用户或者JVM赋值了。

注意对于这个例子我只是用了Benchmark类,因为这个系列前面的章节也是用的这个类。甚至没有一个主类的情况下你能得到相同的输出,通过运行Java 带另外的参数 -version.现在让我们检查下 server VM提供了多少个参数。我们也能指定参数-XX:+UnlockExperimentalVMOptions 和-XX:+UnlockDiagnosticVMOptions ;来解锁任何额外的隐藏参数。

  1. $ java -server -XX:+UnlockExperimentalVMOptions -XX:+UnlockDiagnosticVMOptions -XX:+PrintFlagsFinal Benchmark  
724个参数,让我们看一眼那些已经被赋值的参数。
  1. $ java -server -XX:+UnlockExperimentalVMOptions -XX:+UnlockDiagnosticVMOptions -XX:+PrintFlagsFinal Benchmark | grep ":"  
  2. uintx InitialHeapSize                     := 57505088         {product}  
  3. uintx MaxHeapSize                         := 920649728        {product}  
  4. uintx ParallelGCThreads                   := 4                {product}  
  5.  bool PrintFlagsFinal                     := true             {product}  
  6.  bool UseParallelGC                       := true             {product}  

(校对注:这个命令非常有用)我们仅设置一个自己的参数 -XX:+PrintFlagsFinal。其他参数通过server VM基于系统设置的,以便以合适的堆大小和GC设置运行。

如果我们只想看下所有XX参数的默认值,能够用一个相关的参数,-XX:+PrintFlagsInitial  。 用 -XX:+PrintFlagsInitial, 只是展示了第三列为“=”的数据(也包括那些被设置其他值的参数)。

然而,注意当与-XX:+PrintFlagsFinal 对比的时候,一些参数会丢失,大概因为这些参数是动态创建的。

研究表格的内容是很有意思的,通过比较client和server VM的行为,很明显了解哪些参数会影响其他的参数。有兴趣的读者,可以看一下这篇不错文章Inspecting HotSpot JVM Options。这个文章主要解释了第五列的参数类别。

-XX:+PrintCommandLineFlags

让我们看下另外一个参数,事实上这个参数非常有用: -XX:+PrintCommandLineFlags。这个参数让JVM打印出那些已经被用户或者JVM设置过的详细的XX参数的名称和值。

换句话说,它列举出 -XX:+PrintFlagsFinal的结果中第三列有":="的参数。以这种方式,我们可以用-XX:+PrintCommandLineFlags作为快捷方式来查看修改过的参数。看下面的例子。

  1. $ java -server -XX:+PrintCommandLineFlags Benchmark   

  1. -XX:InitialHeapSize=57505088 -XX:MaxHeapSize=920081408 -XX:ParallelGCThreads=4 -XX:+PrintCommandLineFlags -XX:+UseParallelGC  

现在如果我们每次启动java 程序的时候设置 -XX:+PrintCommandLineFlags 并且输出到日志文件上,这样会记录下我们设置的JVM 参数对应用程序性能的影响。类似于 -showversion(见 Part1),我建议 –XX:+PrintCommandLineFlags 这个参数应该总是设置在JVM启动的配置项里。因为你从不知道你什么时候会需要这些信息。

奇怪的是在这个例子中,通过 -XX:+PrintCommandLineFlags 列出堆的最大值会比通过-XX:+PrintFlagsFinal列举出的相应值小一点。如果谁知道两者之间不同的原因,请告诉我。

转载自:http://ifeve.com/useful-jvm-flags-part-3-printing-all-xx-flags-and-their-values/



   Java程序员有时候需要了解JVM相关的参数,不管是出于好奇或者工作需要。Oracle的文档中列出了一些,(点击这里),单并不是全部,而且有些参数的设置会默认启用或者关闭其他一些参数,而在某些情况下设置某个参数是不会生效的。还有些时候你想让JVM做某些事情,但是你不知道那个参数可以用。下面介绍一些办法用以列出所有参数,这样你在研究或者Google的时候也比较有明确的目标。

    如果你想查看一下线上正在运行的JVM到底设置了那些参数,生效的是那些,可能用到的方法:

    1. 在Linux下用ps命令找到启动Java应用时的参数

  1. ps -ef | grep "your java app name"    

    这个命令会打出你启动Java应用时传给java命令的所有参数,你可以看到里面的JVM参数。

    2.直接看启动脚本,或者参数配置

    你未必能找到所有设置这JVM参数的地方,容易遗漏。

    一般来讲以上两种办法都需要对JVM了如指掌或者非常熟悉,至少对特定的参数。

    

    其实JVM中有一个参数-XX:+PrintFlagsFinal,可以打印出几乎所有的JVM支持的参数以及他们的默认值。如果你想要查看你的Java应用到底使用了那些参数,只要在启动的时候加上这个参数就可以了。

    1.查看你使用的JDK支持的参数

  1. java -XX:+UnlockDiagnosticVMOptions -XX:+PrintFlagsFinal -version    

    2.打印Java应用启用的JVM参数

  1. java -XX:+PrintFlagsFinal -classpath=/path/to/your/libs MainClass    

    3.如果你的Java应用已经是运行状态了,你想查看某个JVM参数生效没有可以使用jinfo这个工具。比如说大名鼎鼎的G1垃圾回收器,在JDK7update3中不论是客户端(-client)还是服务器(-server)模式下都不是默认启动的。

    jinfo是随JDK一起发布的,使用时先用jps找到Java应用的pid。直接运行jinfo可以查看使用说明。

  1. jinfo -flag UseParallelOldGC 31072    

    3.如果你的Java应用已经是运行状态了,你想查看某个JVM参数生效没有可以使用jinfo这个工具。比如说大名鼎鼎的G1垃圾回收器,在JDK7update3中不论是客户端(-client)还是服务器(-server)模式下都不是默认启动的。

    jinfo是随JDK一起发布的,使用时先用jps找到Java应用的pid。直接运行jinfo可以查看使用说明。

  1. jinfo -flag UseParallelOldGC 31072  <span style="color: rgb(0, 204, 0); line-height: 18px; font-family: Consolas, 'Courier New', Courier, mono, serif; background-color: inherit;"> </span>  
  1. -XX:+UseParallelOldGC    
    JDK中实用的工具还很多,可以逐个的体验一下${JAVA_HOME}/bin目录中的每个命令,有惊喜。


参考:http://blog.csdn.net/redhat456/article/details/7360249

0
posted @ 2017-07-28 11:01 小马歌 阅读(393) | 评论 (0)编辑 收藏
 
from:http://www.jianshu.com/p/2750c7c202ef

上周有幸给部门的小伙伴分享了一些JVM相关的知识,在整个做PPT的过程中,也是对一个领域的碎片知识的整理,本文将针对虚拟机GC相关的一些内容进行整理,本文不会涉及到G1收集器。

在Hotspot VM实现中,主要有两大类GC

  1. Partial GC:并不会堆整个GC堆进行收集
    • young gc:只收集 young gen 的GC
    • old gc:只收集 old gen 的GC,只有CMS的 concurrent collection
    • mixed GC:收集整个 young gen 以及部分 old gen 的GC,只有G1
  2. Full GC:收集整个堆,包括young gen、old gen、perm gen(如果存在的话)等

其实在各种文章或书上还可以看到Minor GC、Major GC的字眼,其中minor GC和young gc对应,而Major GC通常是和Full GC是等价的,由于HotSpot VM发展了这么多年,外界对各种名词的解读已经完全混乱了,所以Major GC有时也可能是指old gc,在下定论之前一定要先问清楚。

单线程、并行、并发

在GC收集器实现中,分为了单线程、并行和并发。
单线程收集器:如 Serial GC,这个比较好理解,即垃圾收集过程中只有单一线程在进行收集工作,实现也最简单。

并行收集器:如Parallel GC,每次运行时,不管是YGC,还是FGC,会 stop-the-world,暂停所有的用户线程,并采用多个线程同时进行垃圾收集。

并发收集器:如CMS GC,在新生代进行垃圾收集时和并行收集器类似,都是并行收集(当然具体算法中,你也可以设置成采用单线程进行收集),而且都会stop-the-world,主要的区别在于老年代的收集上,CMS在老年代进行垃圾收集时,大部分时间可以和用户线程并发执行的,只有小部分的时间stop-the-world,这就是它的优势,可以大大降低应用的暂停时间,当然也是有劣势的。

算法组合

Hotspot VM实现的几种GC算法组合中,其中CMS GC使用最广,因为现在都是大内存时代。

1、Serial GC

Serial generational collector (-XX:+UseSerialGC)
是全局范围的Full GC,这种算法组合是最早出现的,当年的Java堆内存大小都还不大,使用Serial GC进行单线程收集,还感觉不出来GC耗时导致应用暂停的问题

2、Parallel GC

Parallel for young space, serial for old space generational collector (-XX:+UseParallelGC).
Parallel for young and old space generational collector (-XX:+UseParallelOldGC)
当Java堆慢慢变大时,发现已经无法忍受GC耗时带来的应用暂停了,出现了Parallel GC,采用多线程的方式进行垃圾收集,很明显可以提升垃圾收集效率。

3、CMS GC

Concurrent mark sweep with serial young space collector (-XX:+UseConcMarkSweepGC
–XX:-UseParNewGC)
Concurrent mark sweep with parallel young space collector (-XX:+UseConcMarkSweepGC)
当Java堆达到更大时,比如8G,使用Parallel GC带来的应用暂停已经很明显了,所有又出现了 CMS GC,这是目前我看到线上环境使用的比较多的GC策略,在参数中添加-XX:+UseConcMarkSweepGC,对于 young gen,会自动选用 ParNewGC,不需要额外添加 -XX:+UseParNewGC

CMS虽然好,因为它的特殊算法,大部分的收集过程可以和用户线程并发执行,大大降低应用的暂停时间,不过也会带来负面影响,在收集完 old gen 之后,CMS并不会做整理过程,会产生空间碎片,如果这些碎片空间得不到利用,就会造成空间的浪费,整个过程中可能发生 concurrent mode failure,导致一次真正意义的 full gc,采用单线程对整个堆(young+old+perm) 使用MSC(Mark-Sweep-Compact)进行收集,这个过程意味着很慢很慢很慢,而且这个碎片问题是无法预测的.

4、G1 GC

G1 garbage collector (-XX:+UseG1GC),本文不对G1进行介绍

触发条件

young gc

对于 young gc,触发条件似乎要简单很多,当 eden 区的内存不够时,就会触发young gc,我们看看在 eden 区给对象分配一块内存是怎样一个过程,画了一个简单的流程图,我一直觉得一个好的示意图可以让一个枯燥的过程变得更有意思。

在 eden 区分配空间内存不足时有两种情况,为对象分配内存、为TLAB分配内存,总之就是内存不够,需要进行一次 young gc 为eden区腾出空间为后续的内存申请做准备,然后由一个用户线程通知VM Thread,接下去要执行一次 young gc。

full gc

1、old gen 空间不足

当创建一个大对象、大数组时,eden 区不足以分配这么大的空间,会尝试在old gen 中分配,如果这时 old gen 空间也不足时,会触发 full gc,为了避免上述导致的 full gc,调优时应尽量让对象在 young gc 时就能够被回收,还有不要创建过大的对象和数组。

2、统计得到的 young gc 晋升到 old gen的对象平均总大小大于old gen 的剩余空间

当准备触发一次 young gc时,会判断这次 young gc 是否安全,这里所谓的安全是当前老年代的剩余空间可以容纳之前 young gc 晋升对象的平均大小,或者可以容纳 young gen 的全部对象,如果结果是不安全的,就不会执行这次 young gc,转而执行一次 full gc

3、perm gen 空间不足

如果有perm gen的话,当系统中要加载的类、反射的类和调用的方法较多,而且perm gen没有足够空间时,也会触发一次 full gc

4、ygc出现 promotion failure

promotion failure 发生在 young gc 阶段,即 cms 的 ParNewGC,当对象的gc年龄达到阈值时,或者 eden 的 to 区放不下时,会把该对象复制到 old gen,如果 old gen 空间不足时,会发生 promotion failure,并接下去触发full gc

在GC日志中,有时会看到 concurrent mode failure 关键字,这是因为什么原因导致的问题呢? 对这一块的理解,很多文章都是说因为 concurrent mode failure 导致触发full gc,其实应该反过来,是full gc 导致的 concurrent mode failure,在cms gc的算法实现中,通常说的cms是由一个后台线程定时触发的,默认每2秒检查一次old gen的内存使用率,当 old gen 的内存使用率达到-XX:CMSInitiatingOccupancyFraction设置的值时,会触发一次 cms gc,对 old gen 进行并发收集,而真正的 full gc 是通过 vm thread线程触发的,而且在判断当前ygc会失败的情况下触发full gc,如上一次ygc出现了promotion failure,如果执行 full gc 时,发现后台线程正在执行 cms gc,就会导致 concurrent mode failure。

对于以上这些情况,CMSInitiatingOccupancyFraction参数的设置就显得尤为重要,设置的太大的话,发生CMS时的剩余空间太小,在ygc的时候容易发生promotion failure,导致 concurrent mode failure 发生的概率就增大,如果设置太小的话,会导致 cms gc 的频率会增加,所以需要根据应用的需求对该参数进行调优。

5、执行 System.gc()jmap -histo:live <pid>jmap -dump ...

参考资料
Major GC和Full GC的区别是什么?触发条件呢

个人公众号

posted @ 2017-07-27 14:33 小马歌 阅读(323) | 评论 (0)编辑 收藏
 

from:http://www.cnblogs.com/foohack/p/5627163.html

Cassandra note:

依赖:需要java 8 (http://www.oracle.com/technetwork/java/javase/downloads/index.html)

数据模型: 与Hbase同样是属于列式数据库,Key-Value存储系统。(http://www.ibm.com/developerworks/cn/opensource/os-cn-cassandra/)
http://www.datastax.com/dev/blog/basic-rules-of-cassandra-data-modeling

集群中的数据是靠partion key的hash code均匀映射到不同的节点上去的。partionkey是primary key的第一个元素,所以选一个好的主
key才能使数据更好的均匀存储在不同的节点上。


Cassandra的节点实例叫Cluster,里面可以包含一个或多个键空间(KeysSpace).键空间是存放列族(Column Family)的容器,相当于
关系数据中的database,schema。列族是存放列(column)的容器,类似与关系数据库中的table。超级列(Super column)是一种
特殊的列,它的value值可以包含多个column。 columns是cassandra的最基本单位,有name,value,timestamp构成。

 

列式数据库的优点: 
1.适合存储大量数据,而不是小量数据。因为数据是是基于列存储的,所以可以忽略不需要的列的数据,提高查找效率。
与之对应的是行数据库。
2.高压缩比。节省存储空间,也节省CPU和内存。
3.高装载速度。

列式数据库的缺点:
1.不适合扫描小量数据。
2.不适合随机更新数据。
3.不适合做含有删除的更新的实时操作。


查询数据:
Cassandra有自己的一套查询语言CQL(类似SQL),在数据访问方式上亦是如此。客户端可以与集群中的任意节点相连,并访问任意的数据。

cassandra在写入数据之前需要记录日志(CommitLog),然后数据开始写入到 Column Family 对应的 Memtable 中,
Memtable 是一种按照 key 排序数据的内存结构,在满足一定条件时,再把 Memtable 的数据批量的刷新到磁盘上,存储为 SSTable 。

存储二进制大文件(不推荐存储):http://wiki.apache.org/cassandra/FAQ#large_file_and_blob_storage

Cassandra的GUI管理工具整理:http://wiki.apache.org/cassandra/FAQ#gui 也有自带的CLI工具连接Cassandra

Cassandra集群种子的概念(很重要):http://wiki.apache.org/cassandra/FAQ#seed
类似与Cassandra集群的初始化节点(集线器),各个节点通过种子节点互相学习(交换)各自的数据(状态),所以新加入的Cassandra节点都需要给它
指定种子节点,下次启动的时候就不需要了。


Cassandra 的C++ 接口:
Cassandra的各种编程语言的接口是有Thrift这个开源工具生成的,语言无关的Thrift输入文件(cassandra.thrift)Cassandra已经自带
,安装thrift运行 thrift.exe -gen cpp cassandra.thrift生成就可以了。 cpp的接口依赖thrift的核心库叫libthrift,libthrift依赖boost1.53.0
版本和openssl


Cassandra windows 安装配置:
解压,配置好CASSANDRA_HOME环境变量的路径(也就是你解压的cassandra根目录),然后运行bin下的cassandra.bat,如果发现logs
目录底下的system.log文件中有INFO - Starting up server gossip,那么恭喜你,Cassandra已经在你的本机启动起来了。

 

 

*****************************************Cassandra的基本操作************************************************

数据模型:多维的hash表,每行可以有不同的列。每行都有个键. keyspace包含若干列族
(列族和表是同一个概念:http://stackoverflow.com/questions/18824390/whats-the-difference-between-creating-a-table-and-creating-a-columnfamily-in-ca),
keyspace在逻辑上是容纳列族和某些配置属性的命名空间。列族定义了相关的数据名字和它们的排序方式。

入门必读:http://wiki.apache.org/cassandra/GettingStarted

CQLSH中运行外部创建的cql脚本文件: SOURCE '[file_path]'

键空间的创建: 
CREATE KEYSPACE [keyspace_name] WITH REPLICATION = {'class' : 'NetworkTopologyStrategy', 'datacenter1' : 3};

----键空间的创建要附带副本属性,class可以指定NetworkTopologyStrategy或SimpleStrategy。SimpleStrategy只用于测试评估Cassandra
生产环境使用NetworkTopologyStrategy. 键空间类似关系型数据库中的数据库(database)

键空间的修改:
ALTER KEYSPACE [keyspace_name] WITH REPLICATION = {};

键空间的删除:
DROP KEYSPACE [keyspace_name]

键空间的使用:
USE [keyspace_name]


列出已存在的键空间:
DESCRIBE keyspaces;

列出某个键空间下的所有表:
USE [keyspace_name];
DESCRIBE tables;

列出某个键空间下的所有列族:
USE [keyspace_name];
DESCRIBE columnfamilies;

列出特定的表的基本信息:
DESCRIBE TABLE [keyspace_name].[table_name];

建表:
http://docs.datastax.com/en/cql/3.1/cql/cql_using/create_table_t.html
表的主键可以是复合的,就是多个列组成一个主键:
CREATE TABLE emp (
empID int,
deptID int,
first_name varchar,
last_name varchar,
PRIMARY KEY (empID, deptID)); //主键的第一个键就是分区键(empID),分区键的目的就是把表中的数据均分到集群中的
各个节点中

更改表:
ALERT TABLE [table_name] [some change]
https://docs.datastax.com/en/cql/3.0/cql/cql_reference/alter_table_r.html
没办法更改主键,因为主键涉及到数据的物理储存

给表的某列建立索引:
CREATE INDEX ON [table_name] (column_name);


查表:
SELCT [column_name] FROM [keyspace].[table_name] WHERE [column_name] = [value] //其中column_name必须是主键的其中部分,
如果有多个条件必须其中有一个是分区键

更新表中的值:
UPDATE [keyspace].[table_name] SET [column_name] = [new_value] WHERE [column_name]=[value]

删除表中列或行:
删除列中值:DELETE [column_name] FROM [table_name] WHERE [column_name] = [value] # 同查表
删除一整行:DELETE FROM [table_name] WHERE [column_name] = [value] #同上

自定义数据类型(http://docs.datastax.com/en/cql/3.1/cql/cql_using/cqlUseUDT.html):
CREATE TYPE [keyspace_name].[type_name] (
street text,
city text,
zip_code int,
phones set<text>
);
自定义的数据类型的字面值是json-style的风格。

内建的数据类型(http://docs.datastax.com/en/cql/3.1/cql/cql_reference/cql_data_types_c.html):
ascii,bigint,blob,boolean,counter,double,float,inet,int,list,map,set,text,uuid,
timestamp,tuple,varchar(UTF-8 encoded string) ,varint


查看集群信息:
SELECT * FROM system.peers;

本地帮助文档的查看:
HELP [COMMAND]
比如:查看创建键空间 HELP CREATE_KEYSPACE;


CQL语句支持多语句提交(Batch):
可以减少Node之间的流量交换,类似于事务,是原子的。
http://docs.datastax.com/en/cql/3.1/cql/cql_reference/batch_r.html#reference_ds_djf_xdd_xj__batch-conditional
http://docs.datastax.com/en/cql/3.1/cql/cql_using/use-batch-static.html


给数据设置存活期:
超过存活期的数据,将被销毁。
INSERT INTO [table_name]
([column_name1], [column_name2])
VALUES ([column_value1], [column_value2]) USING TTL 86400; # 86400 sec 大概是一天的存活期
是给column_name2设置的


UPDATE [table_name] USING TTL 432000 SET [column_name] = [column_value]
WHERE user_name = 'cbrown';

posted @ 2017-07-18 09:57 小马歌 阅读(419) | 评论 (0)编辑 收藏
 
from:http://blog.csdn.net/MoreWindows/article/category/859207 

【白话经典算法系列之十七】 数组中只出现一次的数

数组A中,除了某一个数字x之外,其他数字都出现了三次,而x出现了一次。请给出最快的方法找到x。 这个题目非常有意思,在本人博客中有《位操作基础篇之位操作全面总结》这篇文章介绍了使用位操作的异或来解决——数组中其他数字出现二次,而x出现一次,找出x。有《【白话经典算法系列之十二】数组中只出现1次的两个数字(百度面试题)》这边文章介绍了分组异或的方法来解决——数组中其他数字出现二次,而x和y出现一次,找出x和y。而这个题目则是其他数字出现3次,x出现一次。...
2013-10-21 11:49 阅读(32100) 评论(34)
首先看看题目要求: 给定一个无序的整数数组,怎么找到第一个大于0,并且不在此数组的整数。比如[1,2,0]返回3,[3,4,-1,1]返回2,[1, 5, 3, 4, 2]返回6,[100, 3, 2, 1, 6,8, 5]返回4。要求使用O(1)空间和O(n)时间。 这道题目初看没有太好的思路,但是借鉴下《白话经典算法系列之十一道有趣的GOOGLE面试题》这篇文章,我们不发现使用“基数排序”正好可以用来解决这道题目...
2013-10-15 10:17 阅读(13580) 评论(11)
【白话经典算法系列之十五】“一步千里”之数组找数 有这样一个数组A,大小为n,相邻元素差的绝对值都是1。如:A={4,5,6,5,6,7,8,9,10,9}。现在,给定A和目标整数t,请找到t在A中的位置。除了依次遍历,还有更好的方法么?...
2013-09-02 12:57 阅读(25918) 评论(39)
【白话经典算法系列之十三】随机生成和为S的N个正整数——投影法      随机生成和为S的N个正整数有很多种解法。下面讲解一种比较高效且比较有趣味性的解法——投影法。    以生成和为20的4个数为例,可以先生成随机生成0到20之间的三个数字再排序,假设得到了4,7,18。然后在X-Y数轴上画出这三个数,如下图:然后将这些数值投影到Y轴上,可得下图:由图很容易看出AB,BC,CD,DE这四段的长度...
2013-01-04 13:46 阅读(15710) 评论(46)
微博http://weibo.com/MoreWindows已开通,欢迎关注。本系列文章地址:http://blog.csdn.net/MoreWindows/article/category/859207首先来看题目要求:在一个数组中除两个数字只出现1次外,其它数字都出现了2次, 要求尽快找出这两个数字。    考虑下这个题目的简化版——数组中除一个数字只出现1次外,其它数字都成对出现,要求尽快...
2012-11-27 09:17 阅读(35498) 评论(51)
微博http://weibo.com/MoreWindows已开通,欢迎关注。本系列文章地址:http://blog.csdn.net/MoreWindows/article/category/859207 上一篇《白话经典算法系列之十一道有趣的GOOGLE面试题》中对一道有趣的GOOGLE面试题进行了详细的讲解,使用了类似于基数排序的做法在O(N)的时间复杂度和O(1)的空间复杂度完成了题目的要...
2012-11-23 07:57 阅读(24806) 评论(52)
微博http://weibo.com/MoreWindows已开通,欢迎关注。最近在微博上看到一道有趣的GOOGLE面试题,见下图:文字版:一个大小为n的数组,里面的数都属于范围[0, n-1],有不确定的重复元素,找到至少一个重复元素,要求O(1)空间和O(n)时间。     这个题目要求用O(n)的时间复杂度,这意味着只能遍历数组一次。同时还要寻找重复元素,很容易想到建立哈希表来完成,遍历数组...
2012-11-21 09:03 阅读(47907) 评论(87)
首先来看看原题 微软2010年笔试题在一个排列中,如果一对数的前后位置与大小顺序相反,即前面的数大于后面的数,那么它们就称为一个逆序数对。一个排列中逆序的总数就称为这个排列的逆序数。如{2,4,3,1}中,2和1,4和3,4和1,3和1是逆序数对,因此整个数组的逆序数对个数为4,现在给定一数组,要求统计出该数组的逆序数对个数。 计算数列的逆序数对个数最简单的方便就最从前向后依次统计每个数字与它后面...
2012-10-15 09:15 阅读(30367) 评论(36)
在我的博客对冒泡排序,直接插入排序,直接选择排序,希尔排序,归并排序,快速排序和堆排序这七种常用的排序方法进行了详细的讲解,并做成了电子书以供大家下载。下载地址为:http://download.csdn.net/detail/morewindows/4443208。       有网友提议到这本《MoreWindows白话经典算法之七大排序》电子书讲解细致用来平时学习是非常好的,但是页数有22页...
2012-09-10 10:08 阅读(42997) 评论(26)
堆排序与快速排序,归并排序一样都是时间复杂度为O(N*logN)的几种常见排序方法。学习堆排序前,先讲解下什么是数据结构中的二叉堆。二叉堆的定义二叉堆是完全二叉树或者是近似完全二叉树。二叉堆满足二个特性:1.父结点的键值总是大于或等于(小于或等于)任何一个子节点的键值。2.每个结点的左子树和右子树都是一个二叉堆(都是最大堆或最小堆)。当父结点的键值总是大于或等于任何一个子节点的键值时为最大堆。当父...
2011-08-22 20:04 阅读(338481) 评论(188)
快速排序由于排序效率在同为O(N*logN)的几种排序方法中效率较高,因此经常被采用,再加上快速排序思想----分治法也确实实用,因此很多软件公司的笔试面试,包括像腾讯,微软等知名IT公司都喜欢考这个,还有大大小的程序方面的考试如软考,考研中也常常出现快速排序的身影。总的说来,要直接默写出快速排序还是有一定难度的,因为本人就自己的理解对快速排序作了下白话解释,希望对大家理解有帮助,达到快速排序,快...
2011-08-13 17:19 阅读(418202) 评论(284)
归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。首先考虑下如何将将二个有序数列合并。这个非常简单,只要从比较二个数列的第一个数,谁小就先取谁,取了后就在对应数列中删除这个数。然后再进行比较,如果有数列为空,那直接将另一个数列的数据依次取出即可。//将有序数组a[]和b[]合并到c[]中 void MemeryArra...
2011-08-11 11:01 阅读(275147) 评论(154)
直接选择排序和直接插入排序类似,都将数据分为有序区和无序区,所不同的是直接播放排序是将无序区的第一个元素直接插入到有序区以形成一个更大的有序区,而直接选择排序是从无序区选一个最小的元素直接放到有序区的最后。   设数组为a[0…n-1]。 1.      初始时,数组全为无...
2011-08-09 11:15 阅读(29055) 评论(38)
希尔排序的实质就是分组插入排序,该方法又称缩小增量排序,因DL.Shell于1959年提出而得名。   该方法的基本思想是:先将整个待排元素序列分割成若干个子序列(由相隔某个“增量”的元素组成的)分别进行直接插入排序,然后依次缩减增量再进行排序,待整个序列中的元素基本有序(增...
2011-08-08 11:41 阅读(148913) 评论(82)
直接插入排序(Insertion Sort)的基本思想是:每次将一个待排序的记录,按其关键字大小插入到前面已经排好序的子序列中的适当位置,直到全部记录插入完成为止。   设数组为a[0…n-1]。 1.      初始时,a[0]自成1个有序区,无序区为a[1..n-1]。...
2011-08-06 19:27 阅读(118661) 评论(81)
冒泡排序是非常容易理解和实现,,以从小到大排序举例: 设数组长度为N。 1.比较相邻的前后二个数据,如果前面数据大于后面的数据,就将二个数据交换。 2.这样对数组的第0个数据到N-1个数据进行一次遍历后,最大的一个数据就“沉”到数组第N-1个位置。 3.N=N-1,如果N...
2011-08-06 19:20 阅读(166505) 评论(94)
posted @ 2017-06-23 11:17 小马歌 阅读(305) | 评论 (0)编辑 收藏