2016年4月6日

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 小马歌 阅读(186) | 评论 (0)编辑 收藏
 
     摘要: from:https://www.huxiu.com/article/351920.html本文来自微信公众号: 鲜枣课堂(ID:xzclasscom),作者:小枣君,题图来自:视觉中国大家好,我是小枣君。今天我们来聊聊基带和射频。说起基带和射频,相信大家都不陌生。它们是通信行业里的两个常见概念,经常出现在我们面前。不过,越是常见的概念,网上的资料就越混乱,错误也就越多。这些错误给很多初...  阅读全文
posted @ 2020-11-24 10:41 小马歌 阅读(439) | 评论 (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 小马歌 阅读(221) | 评论 (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 小马歌 阅读(230) | 评论 (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 小马歌 阅读(473) | 评论 (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 小马歌 阅读(2364) | 评论 (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 小马歌 阅读(209) | 评论 (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 小马歌 阅读(650) | 评论 (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 小马歌 阅读(488) | 评论 (0)编辑 收藏
 
     摘要: from:https://cloud.tencent.com/developer/article/1004475最近研发的项目对 DB 依赖比较重,梳理了这段时间使用MySQL遇到的8个比较具有代表性的问题,答案也比较偏自己的开发实践,没有 DBA专业和深入,有出入的请使劲拍砖!MySQL读写性能是多少,有哪些性能相关的配置参数?MySQL负载高时,如何找到是由哪些SQL引起的?如何针对具体的SQ...  阅读全文
posted @ 2018-12-03 15:55 小马歌 阅读(298) | 评论 (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 小马歌 阅读(323) | 评论 (0)编辑 收藏
 
     摘要: from:https://yq.aliyun.com/articles/69520?utm_content=m_10360#6摘要: # 我的问题排查工具箱 ## 前言 平时的工作中经常碰到很多疑难问题的处理,在解决问题的同时,有一些工具起到了相当大的作用,在此书写下来,一是作为笔记,可以让自己后续忘记了可快速翻阅,二是分享,希望看到此文的同学们可以拿出自己日常觉得帮助很大的工具,大家一...  阅读全文
posted @ 2018-11-23 10:47 小马歌 阅读(275) | 评论 (0)编辑 收藏
 
     摘要: from:https://www.cnblogs.com/Anker/p/3271773.html1、前言  之前在看《unix环境高级编程》第八章进程时候,提到孤儿进程和僵尸进程,一直对这两个概念比较模糊。今天被人问到什么是孤儿进程和僵尸进程,会带来什么问题,怎么解决,我只停留在概念上面,没有深入,倍感惭愧。晚上回来google了一下,再次参考APUE,认真总结一下,加深理解。2、基本概念  我...  阅读全文
posted @ 2018-09-03 19:53 小马歌 阅读(211) | 评论 (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 小马歌 阅读(400) | 评论 (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 小马歌 阅读(248) | 评论 (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 小马歌 阅读(288) | 评论 (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 小马歌 阅读(1736) | 评论 (0)编辑 收藏
 
     摘要: from:http://blog.csdn.net/lylwo317/article/details/52163304序言注解在Java中到底是什么样的东西?具体是如何实现的? 本文将一层一层深入探究注解的实现原理。为了尽可能的将分析的过程呈现出来,所以文章包含了大量的截图和代码。(ps:如果图片看不清楚,请将网页放大来看,chrome可以通过ctrl+鼠标滚轮放大)前期准备知识方面开始...  阅读全文
posted @ 2017-08-02 11:15 小马歌 阅读(582) | 评论 (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 小马歌 阅读(221) | 评论 (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 小马歌 阅读(416) | 评论 (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 小马歌 阅读(336) | 评论 (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 小马歌 阅读(438) | 评论 (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 小马歌 阅读(325) | 评论 (0)编辑 收藏
 
from:http://blog.csdn.net/liaodehong/article/details/51605457

本文将深入浅出地讲解 JIT 编译器在 JVM 中的运作原理,使读者能够更好的理解 Java 底层机制并且为读者在 Java 性能优化领域打开更广的视野。

JIT 简介

JIT 是 just in time 的缩写, 也就是即时编译编译器。使用即时编译器技术,能够加速 Java 程序的执行速度。下面,就对该编译器技术做个简单的讲解。

首先,我们大家都知道,通常通过 javac 将程序源代码编译,转换成 java 字节码,JVM 通过解释字节码将其翻译成对应的机器指令,逐条读入,逐条解释翻译。很显然,经过解释执行,其执行速度必然会比可执行的二进制字节码程序慢很多。为了提高执行速度,引入了 JIT 技术。

在运行时 JIT 会把翻译过的机器码保存起来,以备下次使用,因此从理论上来说,采用该 JIT 技术可以接近以前纯编译技术。下面我们看看,JIT 的工作过程。

JIT 编译过程

当 JIT 编译启用时(默认是启用的),JVM 读入.class 文件解释后,将其发给 JIT 编译器。JIT 编译器将字节码编译成本机机器代码,下图展示了该过程。

图 1. JIT 工作原理图
图 1. JIT 工作原理图

回页首

Hot Spot 编译

当 JVM 执行代码时,它并不立即开始编译代码。这主要有两个原因:

首先,如果这段代码本身在将来只会被执行一次,那么从本质上看,编译就是在浪费精力。因为将代码翻译成 java 字节码相对于编译这段代码并执行代码来说,要快很多。

当然,如果一段代码频繁的调用方法,或是一个循环,也就是这段代码被多次执行,那么编译就非常值得了。因此,编译器具有的这种权衡能力会首先执行解释后的代码,然后再去分辨哪些方法会被频繁调用来保证其本身的编译。其实说简单点,就是 JIT 在起作用,我们知道,对于 Java 代码,刚开始都是被编译器编译成字节码文件,然后字节码文件会被交由 JVM 解释执行,所以可以说 Java 本身是一种半编译半解释执行的语言。Hot Spot VM 采用了 JIT compile 技术,将运行频率很高的字节码直接编译为机器指令执行以提高性能,所以当字节码被 JIT 编译为机器码的时候,要说它是编译执行的也可以。也就是说,运行时,部分代码可能由 JIT 翻译为目标机器指令(以 method 为翻译单位,还会保存起来,第二次执行就不用翻译了)直接执行。

第二个原因是最优化,当 JVM 执行某一方法或遍历循环的次数越多,就会更加了解代码结构,那么 JVM 在编译代码的时候就做出相应的优化。

我们将在后面讲解这些优化策略,这里,先举一个简单的例子:我们知道 equals() 这个方法存在于每一个 Java Object 中(因为是从 Object class 继承而来)而且经常被覆写。当解释器遇到 b = obj1.equals(obj2) 这样一句代码,它则会查询 obj1 的类型从而得知到底运行哪一个 equals() 方法。而这个动态查询的过程从某种程度上说是很耗时的。

寄存器和主存

其中一个最重要的优化策略是编译器可以决定何时从主存取值,何时向寄存器存值。考虑下面这段代码:

清单 1. 主存 or 寄存器测试代码
public class RegisterTest {  private int sum;   public void calculateSum(int n) {  for (int i = 0; i < n; ++i) {  sum += i;  }  } }

在某些时刻,sum 变量居于主存之中,但是从主存中检索值是开销很大的操作,需要多次循环才可以完成操作。正如上面的例子,如果循环的每一次都是从主存取值,性能是非常低的。相反,编译器加载一个寄存器给 sum 并赋予其初始值,利用寄存器里的值来执行循环,并将最终的结果从寄存器返回给主存。这样的优化策略则是非常高效的。但是线程的同步对于这种操作来说是至关重要的,因为一个线程无法得知另一个线程所使用的寄存器里变量的值,线程同步可以很好的解决这一问题,有关于线程同步的知识,我们将在后续文章中进行讲解。

寄存器的使用是编译器的一个非常普遍的优化。

回到之前的例子,JVM 注意到每次运行代码时,obj1 都是 java.lang.String 这种类型,那么 JVM 生成的被编译后的代码则是直接调用 String.equals() 方法。这样代码的执行将变得非常快,因为不仅它是被编译过的,而且它会跳过查找该调用哪个方法的步骤。

当然过程并不是上面所述这样简单,如果下次执行代码时,obj1 不再是 String 类型了,JVM 将不得不再生成新的字节码。尽管如此,之后执行的过程中,还是会变的更快,因为同样会跳过查找该调用哪个方法的步骤。这种优化只会在代码被运行和观察一段时间之后发生。这也就是为什么 JIT 编译器不会理解编译代码而是选择等待然后再去编译某些代码片段的第二个原因。

回页首

初级调优:客户模式或服务器模式

JIT 编译器在运行程序时有两种编译模式可以选择,并且其会在运行时决定使用哪一种以达到最优性能。这两种编译模式的命名源自于命令行参数(eg: -client 或者 -server)。JVM Server 模式与 client 模式启动,最主要的差别在于:-server 模式启动时,速度较慢,但是一旦运行起来后,性能将会有很大的提升。原因是:当虚拟机运行在-client 模式的时候,使用的是一个代号为 C1 的轻量级编译器,而-server 模式启动的虚拟机采用相对重量级代号为 C2 的编译器。C2 比 C1 编译器编译的相对彻底,服务起来之后,性能更高。

通过 java -version 命令行可以直接查看当前系统使用的是 client 还是 server 模式。例如:

图 2. 查看编译模式
图 2. 查看编译模式

回页首

中级编译器调优

大多数情况下,优化编译器其实只是选择合适的 JVM 以及为目标主机选择合适的编译器(-cient,-server 或是-xx:+TieredCompilation)。多层编译经常是长时运行应用程序的最佳选择,短暂应用程序则选择毫秒级性能的 client 编译器。

优化代码缓存

当 JVM 编译代码时,它会将汇编指令集保存在代码缓存。代码缓存具有固定的大小,并且一旦它被填满,JVM 则不能再编译更多的代码。

我们可以很容易地看到如果代码缓存很小所具有的潜在问题。有些热点代码将会被编译,而其他的则不会被编译,这个应用程序将会以运行大量的解释代码来结束。

这是当使用 client 编译器模式或分层编译时很频繁的一个问题。当使用普通 server 编译器模式时,编译合格的类的数量将被填入代码缓存,通常只有少量的类会被编译。但是当使用 client 编译器模式时,编译合格的类的数量将会高很多。

在 Java 7 版本,分层编译默认的代码缓存大小经常是不够的,需要经常提高代码缓存大小。大型项目若使用 client 编译器模式,则也需要提高代码缓存大小。

现在并没有一个好的机制可以确定一个特定的应用到底需要多大的代码缓存。因此,当需要提高代码缓存时,这将是一种凑巧的操作,一个通常的做法是将代码缓存变成默认大小的两倍或四倍。

可以通过 –XX:ReservedCodeCacheSize=Nflag(N 就是之前提到的默认大小)来最大化代码缓存大小。代码缓存的管理类似于 JVM 中的内存管理:有一个初始大小(用-XX:InitialCodeCacheSize=N 来声明)。代码缓存的大小从初始大小开始,随着缓存被填满而逐渐扩大。代码缓存的初始大小是基于芯片架构(例如 Intel 系列机器,client 编译器模式下代码缓存大小起始于 160KB,server 编译器模式下代码缓存大小则起始于 2496KB)以及使用的编译器的。重定义代码缓存的大小并不会真正影响性能,所以设置 ReservedCodeCacheSize 的大小一般是必要的。

再者,如果 JVM 是 32 位的,那么运行过程大小不能超过 4GB。这包括了 Java 堆,JVM 自身所有的代码空间(包括其本身的库和线程栈),应用程序分配的任何的本地内存,当然还有代码缓存。

所以说代码缓存并不是无限的,很多时候需要为大型应用程序来调优(或者甚至是使用分层编译的中型应用程序)。比如 64 位机器,为代码缓存设置一个很大的值并不会对应用程序本身造成影响,应用程序并不会内存溢出,这些额外的内存预定一般都是被操作系统所接受的。

编译阈值

在 JVM 中,编译是基于两个计数器的:一个是方法被调用的次数,另一个是方法中循环被回弹执行的次数。回弹可以有效的被认为是循环被执行完成的次数,不仅因为它是循环的结尾,也可能是因为它执行到了一个分支语句,例如 continue。

当 JVM 执行一个 Java 方法,它会检查这两个计数器的总和以决定这个方法是否有资格被编译。如果有,则这个方法将排队等待编译。这种编译形式并没有一个官方的名字,但是一般被叫做标准编译。

但是如果方法里有一个很长的循环或者是一个永远都不会退出并提供了所有逻辑的程序会怎么样呢?这种情况下,JVM 需要编译循环而并不等待方法被调用。所以每执行完一次循环,分支计数器都会自增和自检。如果分支计数器计数超出其自身阈值,那么这个循环(并不是整个方法)将具有被编译资格。

这种编译叫做栈上替换(OSR),因为即使循环被编译了,这也是不够的:JVM 必须有能力当循环正在运行时,开始执行此循环已被编译的版本。换句话说,当循环的代码被编译完成,若 JVM 替换了代码(前栈),那么循环的下个迭代执行最新的被编译版本则会更加快。

标准编译是被-XX:CompileThreshold=Nflag 的值所触发。Client 编译器模式下,N 默认的值 1500,而 Server 编译器模式下,N 默认的值则是 10000。改变 CompileThreshold 标志的值将会使编译器相对正常情况下提前(或推迟)编译代码。在性能领域,改变 CompileThreshold 标志是很被推荐且流行的方法。事实上,您可能知道 Java 基准经常使用此标志(比如:对于很多 server 编译器来说,经常在经过 8000 次迭代后改变次标志)。

我们已经知道 client 编译器和 server 编译器在最终的性能上有很大的差别,很大程度上是因为编译器在编译一个特定的方法时,对于两种编译器可用的信息并不一样。降低编译阈值,尤其是对于 server 编译器,承担着不能使应用程序运行达到最佳性能的风险,但是经过测试应用程序我们也发现,将阈值从 8000 变成 10000,其实有着非常小的区别和影响。

检查编译过程

中级优化的最后一点其实并不是优化本身,而是它们并不能提高应用程序的性能。它们是 JVM(以及其他工具)的各个标志,并可以给出编译工作的可见性。它们中最重要的就是--XX:+PrintCompilation(默认状态下是 false)。

如果 PrintCompilation 被启用,每次一个方法(或循环)被编译,JVM 都会打印出刚刚编译过的相关信息。不同的 Java 版本输出形式不一样,我们这里所说的是基于 Java 7 版本的。

编译日志中大部分的行信息都是下面的形式:

清单 2. 日志形式
timestamp compilation_id attributes (tiered_level) method_name size depot

这里 timestamp 是编译完成时的时间戳,compilation_id 是一个内部的任务 ID,且通常情况下这个数字是单调递增的,但有时候对于 server 编译器(或任何增加编译阈值的时候),您可能会看到失序的编译 ID。这表明编译线程之间有些快有些慢,但请不要随意推断认为是某个编译器任务莫名其妙的非常慢。

用 jstat 命令检查编译

要想看到编译日志,则需要程序以-XX:+PrintCompilation flag 启动。如果程序启动时没有 flag,您可以通过 jstat 命令得到有限的可见性信息。

Jstat 有两个选项可以提供编译器信息。其中,-compile 选项提供总共有多少方法被编译的总结信息(下面 6006 是要被检查的程序的进程 ID):

清单 3 进程详情
% jstat -compiler 6006 CompiledFailedInvalid TimeFailedTypeFailedMethod 206 0 0 1.97 0

注意,这里也列出了编译失败的方法的个数信息,以及编译失败的最后一个方法的名称。

另一种选择,您可以使用-printcompilation 选项得到最后一个被编译的方法的编译信息。因为 jstat 命令有一个参数选项用来重复其操作,您可以观察每一次方法被编译的情况。举个例子:

Jstat 对 6006 号 ID 进程每 1000 毫秒执行一次: %jstat –printcompilation 6006 1000,具体的输出信息在此不再描述。

回页首

高级编译器调优

这一节我们将介绍编译工作剩下的细节,并且过程中我们会探讨一些额外的调优策略。调优的存在很大程度上帮助了 JVM 工程师诊断 JVM 自身的行为。如果您对编译器的工作原理很感兴趣,这一节您一定会喜欢。

编译线程

从前文中我们知道,当一个方法(或循环)拥有编译资格时,它就会排队并等待编译。这个队列是由一个或很多个后台线程组成。这也就是说编译是一个异步的过程。它允许程序在代码正在编译时被继续执行。如果一个方法被标准编译方式所编译,那么下一个方法调用则会执行已编译的方法。如果一个循环被栈上替换方式所编译,那么下一次循环迭代则会执行新编译的代码。

这些队列并不会严格的遵守先进先出原则:哪一个方法的调用计数器计数更高,哪一个就拥有优先权。所以即使当一个程序开始执行,并且有大量的代码需要编译,这个优先权顺序将帮助并保证最重要的代码被优先编译(这也是为什么编译 ID 在 PrintComilation 的输出结果中有时会失序的另一个原因)。

当使用 client 编译器时,JVM 启动一个编译线程,而 server 编译器有两个这样的线程。当分层编译生效时,JVM 会基于某些复杂方程式默认启动多个 client 和 server 线程,涉及双日志在目标平台上的 CPU 数量。如下图所示:

分层编译下 C1 和 C2 编译器线程默认数量:

图 3. C1 和 C2 编译器默认数量
图 3. C1 C2 编译器默认数量

编译器线程的数量可以通过-XX:CICompilerCount=N flag 进行调节设置。这个数量是 JVM 将要执行队列所用的线程总数。对于分层编译,三分之一的(至少一个)线程被用于执行 client 编译器队列,剩下的(也是至少一个)被用来执行 server 编译器队列。

在何时我们应该考虑调整这个值呢?如果一个程序被运行在单 CPU 机器上,那么只有一个编译线程会更好一些:因为对于某个线程来说,其对 CPU 的使用是有限的,并且在很多情况下越少的线程竞争资源会使其运行性能更高。然而,这个优势仅仅局限于初始预热阶段,之后,这些具有编译资格的方法并不会真的引起 CPU 争用。当一个股票批处理应用程序运行在单 CPU 机器上并且编译器线程被限制成只有一个,那么最初的计算过程将比一般情况下快 10%(因为它没有被其他线程进行 CPU 争用)。迭代运行的次数越多,最初的性能收益就相对越少,直到所有的热点方法被编译完性能收益也随之终止。

回页首

结束语

本文详细介绍了 JIT 编译器的工作原理。从优化的角度讲,最简单的选择就是使用 server 编译器的分层编译技术,这将解决大约 90%左右的与编译器直接相关的性能问题。最后,请保证代码缓存的大小设置的足够大,这样编译器将会提供最高的编译性能。

转载自点击打开链接

posted @ 2017-06-08 17:27 小马歌 阅读(258) | 评论 (0)编辑 收藏
 

from:http://www.cnblogs.com/insistence/p/5901457.html

1. 什么是Just In Time编译器?

Hot Spot 编译

当 JVM 执行代码时,它并不立即开始编译代码。这主要有两个原因:

首先,如果这段代码本身在将来只会被执行一次,那么从本质上看,编译就是在浪费精力。因为将代码翻译成 java 字节码相对于编译这段代码并执行代码来说,要快很多。

当 然,如果一段代码频繁的调用方法,或是一个循环,也就是这段代码被多次执行,那么编译就非常值得了。因此,编译器具有的这种权衡能力会首先执行解释后的代 码,然后再去分辨哪些方法会被频繁调用来保证其本身的编译。其实说简单点,就是 JIT 在起作用,我们知道,对于 Java 代码,刚开始都是被编译器编译成字节码文件,然后字节码文件会被交由 JVM 解释执行,所以可以说 Java 本身是一种半编译半解释执行的语言。Hot Spot VM 采用了 JIT compile 技术,将运行频率很高的字节码直接编译为机器指令执行以提高性能,所以当字节码被 JIT 编译为机器码的时候,要说它是编译执行的也可以。也就是说,运行时,部分代码可能由 JIT 翻译为目标机器指令(以 method 为翻译单位,还会保存起来,第二次执行就不用翻译了)直接执行。

第二个原因是最优化,当 JVM 执行某一方法或遍历循环的次数越多,就会更加了解代码结构,那么 JVM 在编译代码的时候就做出相应的优化。

我 们将在后面讲解这些优化策略,这里,先举一个简单的例子:我们知道 equals() 这个方法存在于每一个 Java Object 中(因为是从 Object class 继承而来)而且经常被覆写。当解释器遇到 b = obj1.equals(obj2) 这样一句代码,它则会查询 obj1 的类型从而得知到底运行哪一个 equals() 方法。而这个动态查询的过程从某种程度上说是很耗时的。

在主流商用JVM(HotSpot、J9)中,Java程序一开始是通过解释器(Interpreter)进行解释执行的。当JVM发现某个方法或代码块运行特别频繁时,就会把这些代码认定为“热点代码(Hot Spot Code)”,然后JVM会把这些代码编译成与本地平台相关的机器码,并进行各种层次的优化,完成这个任务的编译器称为:即时编译器(Just In Time Compiler,JIT)

JIT编译器是“动态编译器”的一种,相对的“静态编译器”则是指的比如:C/C++的编译器

JIT并不是JVM的必须部分,JVM规范并没有规定JIT必须存在,更没有限定和指导JIT。但是,JIT性能的好坏、代码优化程度的高低却是衡量一款JVM是否优秀的最关键指标之一,也是虚拟机中最核心且最能体现虚拟机技术水平的部分。


2. 编译器与解释器

首先,不是所有JVM都采用编译器和解释器并存的架构,但主流商用虚拟机,都同时包含这两部分。

2.1 配合过程

  1. 当程序需要迅速启动然后执行的时候,解释器可以首先发挥作用,编译器不运行从而省去编译时间,立即执行程序

  2. 在程序运行后,随着时间的推移,编译器逐渐发挥作用,把越来越多的代码编译成本地代码之后,可以获得更高的执行效率

  3. 当程序运行环境中内存资源限制较大(如部分嵌入式系统中),可以使用解释执行来节约内存;反之,则可以使用编译执行来提升效率。

  4. 同时,解释器还可以作为编译器(C2才会激进优化)激进优化时的一个“逃生门”,让编译器根据概率选择一些大多数时候都能提升运行速度的优化手段,当激进优化假设不成立。如:加载了新类后,类型继承结构出现变化,出现“罕见陷阱(Uncommon Trap)”时,可以通过逆优化(Deoptimization)退回到解释状态继续执行 
    (部分没有解释器的虚拟机,也会采用不进行激进优化的C1编译器担任“逃生门”的角色)

    这里写图片描述

2.2 解释器 - Interpreter

Interpreter解释执行class文件,好像JavaScript执行引擎一样

特殊的例子:

  • 最早的Sun Classic VM只有Interpreter
  • BEA JRockit VM则只有Compiler,但它主要面向服务端应用,部署在其上的应用不重点关注启动时间

2.3 编译器 - Compiler

只说HotSpot JVM

1. C1和C2:

HotSpot虚拟机内置了两个即时编译器,分别称为Client Compiler和Server Compiler,习惯上将前者称为C1,后者称为C2

2. 使用C1还是C2?

HotSpot默认采用解释器和其中一个编译器直接配合的方式工作,使用那个编译器取决于虚拟机运行的模式,HotSpot会根据自身版本和宿主机器硬件性能自动选择模式,用户也可以使用“-client”或”-server”参数去指定

  1. 混合模式(Mixed Mode) 
    默认的模式,如上面描述的这种方式就是mixed mode

  2. 解释模式(Interpreted Mode) 
    可以使用参数“-Xint”,在此模式下全部代码解释执行

  3. 编译模式(Compiled Mode) 
    参数“-Xcomp”,此模式优先采用编译,但是无法编译时也会解释(在最新的HotSpot中此参数被取消)

    可以看到,我的JVM现在是mixed mode 
    这里写图片描述

重要:↓

在JDK1.7(1.7仅包括Server模式)之后,HotSpot就不是默认“采用解释器和其中一个编译器”配合的方式了,而是采用了分层编译,分层编译时C1和C2有可能同时工作


3. 分层编译

3.1 为什么要分层编译?

由于编译器compile本地代码需要占用程序时间,要编译出优化程度更高的代码所花费的时间可能更长,且此时解释器还要替编译器收集性能监控信息,这对解释执行的速度也有影响

所以,为了在程序启动响应时间与运行效率之间达到最佳平衡,HotSpot在JDK1.6中出现了分层编译(Tiered Compilation)的概念并在JDK1.7的Server模式JVM中作为默认策略被开启

3.2 编译层 tier(或者叫级别)

分层编译根据编译器编译、优化的规模与耗时,划分了不同的编译层次(不只以下3种),包括:

  • 第0层,程序解释执行(没有编译),解释器不开启性能监控功能,可触发第1层编译。

  • 第1层,也称C1编译,将字节码编译为本地代码,进行简单、可靠的优化,如有必要将加入性能监控的逻辑

  • 第2层(或2层以上),也称为C2编译,也是将字节码编译为本地代码,但是会启用一些编译耗时较长的优化,甚至会根据性能监控信息进行一些不可靠的激进优化

实施分层编译后,C1和C2将会同时工作,许多代码会被多次编译,用C1获取更高的编译速度,用C2来获取更好的编译质量,且在解释执行的时候解释器也无须再承担收集性能监控信息的任务


4. 编译对象与触发条件

1. 谁被编译了?

编译对象就是之前说的“热点代码”,它有两类:

  1. 被多次调用的方法 
    • 一个方法被多次调用,理应称为热点代码,这种编译也是虚拟机中标准的JIT编译方式
  2. 被多次执行的循环体 
    • 编译动作由循环体出发,但编译对象依然会以整个方法为对象
    • 这种编译方式由于编译发生在方法执行过程中,因此形象的称为:栈上替换(On Stack Replacement- OSR编译,即方法栈帧还在栈上,方法就被替换了)

2. 触发条件

1. 综述

上面的方法和循环体都说“多次”,那么多少算多?换个说法就是编译的触发条件。

判断一段代码是不是热点代码,是不是需要触发JIT编译,这样的行为称为:热点探测(Hot Spot Detection),有几种主流的探测方式:

  1. 基于计数器的热点探测(Counter Based Hot Spot Detection) 
    虚拟机会为每个方法(或每个代码块)建立计数器,统计执行次数,如果超过阀值那么就是热点代码。缺点是维护计数器开销。

  2. 基于采样的热点探测(Sample Based Hot Spot Detection) 
    虚拟机会周期性检查各个线程的栈顶,如果某个方法经常出现在栈顶,那么就是热点代码。缺点是不精确。

  3. 基于踪迹的热点探测(Trace Based Hot Spot Detection) 
    Dalvik中的JIT编译器使用这种方式

2. HotSpot

HotSpot使用的是第1种,因此它为每个方法准备了两类计数器:方法调用计数器(Invocation Counter)和回边计数器(Back Edge Counter)

  1. 方法计数器

    • 默认阀值,在Client模式下是1500次,Server是10000次,可以通过参数“-XX:CompileThreshold”来设定

    • 当一个方法被调用时会首先检查是否存在被JIT编译过得版本,如果存在则使用此本地代码来执行;如果不存在,则将方法计数器+1,然后判断“方法计数器和回边计数器之和”是否超过阀值,如果是则会向编译器提交一个方法编译请求

    • 默认情况下,执行引擎并不会同步等待上面的编译完成,而是会继续解释执行。当编译完成后,此方法的调用入口地址会被系统自动改写为新的本地代码地址

    • 还有一点,热度是会衰减的,也就是说不是仅仅+,也会-,热度衰减动作是在虚拟机的GC执行时顺便进行的

  2. 回边计数器

    • 回边,顾名思义,只有执行到大括号”}”时才算+1

    • 默认阀值,Client下13995,Server下10700

    • 它的调用逻辑和方法计数器差不多,只不过遇到回边指令时+1、超过阀值时会提交OSR编译请求以及这里没有热度衰减


5. 编译过程

编译过程是在后台线程(daemon)中完成的,可以通过参数“-XX:-BackgroundCompilation”来禁止后台编译,但此时执行线程就会同步等待编译完成才会执行程序

  1. Client Compiler 
    C1编译器是一个简单快速的三段式编译器,主要关注“局部性能优化”,放弃许多耗时较长的全局优化手段 
    过程:class -> 1. 高级中间代码 -> 2. 低级中间代码 -> 3. 机器代码
  2. Server Compiler 
    C2是专门面向服务器应用的编译器,是一个充分优化过的高级编译器,几乎能达到GNU C++编译器使用-O2参数时的优化强度。

使用参数“-XX:+PrintCompilation”会让虚拟机在JIT时把方法名称打印出来,如图: 
这里写图片描述


6. Java和C/C++的编译器对比

这里不是比Java和C/C++谁快这种大坑问题,只是比较编译器(我认为开发效率上Java快,执行效率上C/C++快)

这种对比代表了经典的即时编译器与静态编译期的对比,其实总体来说Java编译器有优有劣。主要就是动态编译时间压力大能做的优化少,还要做一些动态校验。而静态编译器无法实现一些开发上很有用的动态特性

posted @ 2017-06-08 17:26 小马歌 阅读(270) | 评论 (0)编辑 收藏
 
from:http://www.tuicool.com/articles/r6Z7vaj

JVM自动监控这所有方法的执行,如果某个方法是热点方法,JVM就计划把该方法的字节码代码编译成本地机器代码,编译成机器代码的过程是在独立线程中执行的,不会影响程序的执行,这个过程就是JIT(just in time)。

JIT针对下面的几种方式进行优化

  • 把bytecode编译成本地代码
  • 单态调度(monomorphic dispatch),当个对象的类和其父类间有方法重写时,JVM调用对象的方法可以通过对象的类型路径来判断应该调用父类的方法还是子类的方法,对此JIT进行优化,这种优化是C++所不具备的,C++中需要查找虚函数表。
  • 循环展开(loop unrolling)
  • 类型锐化
  • 逃逸分析(escape analysis)
  • 移除无用代码(这个现在IDE会提示我们的,比如:intellij idea)
  • Intrinsics
  • 分支预测
  • 方法内联(inlining,对性能的提升很大),默认情况,<= 35字节码的方法可以进行内联,通过这个来修改内联方法的最大值:-XX:MaxInlineSize=,通过-XX:FreqInlineSize=来设置频繁调用方法的临界值

这些优化方法通常是层层依赖的,所以当JIT优化后的代码被JVM应用,就会开始尝试进行更上一层次的优化。因此我们写代码的时候,应该尽量往这些优化方式上面靠。

输出JIT编译过的方法

在JVM启动参数中添加如下的启动参数:

-XX:+PrintCompilation 

输出内容类似这样:

31    23 s!    sun.misc.URLClassPath::getLoader (136 bytes) 
  • 第1列  31:为JVM启动后到该方法被编译相隔的时间,单位为毫秒
  • 第2列  23:编译ID,用来跟踪一个方法的编译、优化、深度优化
  • 第3列  s!:s是指该方法是synchronized,感叹号是指该方法有对异常的处理
  • 第4列  sun.misc.URLClassPath::getLoader:被编译的方法
  • 第5列  (136 bytes):方法的字节大小

输出JIT编译的细节信息

通过添加参数-XX:+PrintCompilation,可以看到的信息其实并不具体,比如:那些方法进行了内联,内联后的二进制代码是怎么样的都没有。而要输出JIT编译的细节信息,就需要在JVM启动参数中添加这个参数:

-XX:+LogCompilation -XX:+UnlockDiagnosticVMOptions -XX:+TraceClassLoading -XX:+PrintAssembly 

输出的编译信息,默认情况是在启动JVM的目录下一个名为:hotspot_pid<PID>.log的文件

如果想指定文件路径和文件名的话,可以再添加一个启动参数:

-XX:LogFile=<pathto file> 

输出的是一个很大的xml文件,可能有几百兆,内容大致如下:

<nmethodcompile_id='2' compiler='C1' level='3'  entry='0x00000001023fe240' size='1224'  address='0x00000001023fe0d0' relocation_offset='288'  insts_offset='368' stub_offset='880' scopes_data_offset='1032'  scopes_pcs_offset='1104' dependencies_offset='1200'  nul_chk_table_offset='1208'  method='java/lang/String hashCode ()I' bytes='55' count='512'  backedge_count='8218' iicount='512' stamp='0.350'/> 

而且内容很难读懂,建议使用JITWatch( https://github.com/AdoptOpenJDK/jitwatch/ )的可视化界面来查看JIT编译的细节信息。同时JITWatch还可以给出很多优化建议,给我们有效的优化代码提供参考,详见下文。

JIT编译模式

C1: 通常用于那种快速启动的GUI应用,对应启动参数:-client

C2: 通常用于长时间允许的服务端应用,对应启动参数:-server

分层编译模式(tiered compilation):这是自从Java SE 7以后的新特性,可通过添加启动参数来开启:

-XX:+TieredCompilation 

这个特性在应用启动阶段使用C1模式以达到快速启动的效果,一旦应用程序运行起来以后,C2模式将取代C1模式,以进行更深度的优化。在Java SE 8中,这个特性是默认的。

JITWatch

前面也提到了,JITWatch可以通过可视化界面来帮助我们分析JVM输出的JIT编译输出日志,还可以帮助我们静态分析jar中的代码是否符合JIT编译优化的条件,还可以以曲线图形的方式展示JIT编译的整个过程中的一些指标,非常好用的工具。

下载

JITWatch需要在github上把代码clone下来,然后用maven来运行,地址为: https://github.com/AdoptOpenJDK/jitwatch/

运行JITWwatch

在代码根目录下执行 launchUI.sh( Linux/Mac)或则 launchUI.bat(windows)

如果你使用maven,也可以在代码根目录下这样运行(其他运行方式,请参考JITWatch的github首页)

mvncleancompileexec:java 

如果你使用的是mac,而且idk版本是jdk7,且运行mvn clean compile exec:java时出现下面的错误和异常时: 

Causedby: java.lang.NullPointerException  atcom.sun.t2k.MacFontFinder.initPSFontNameToPathMap(MacFontFinder.java:339)  atcom.sun.t2k.MacFontFinder.getFontNamesOfFontFamily(MacFontFinder.java:390)  atcom.sun.t2k.T2KFontFactory.getFontResource(T2KFontFactory.java:233)  atcom.sun.t2k.LogicalFont.getSlot0Resource(LogicalFont.java:184)  atcom.sun.t2k.LogicalFont.getSlotResource(LogicalFont.java:228)  atcom.sun.t2k.CompositeStrike.getStrikeSlot(CompositeStrike.java:86)  atcom.sun.t2k.CompositeStrike.getMetrics(CompositeStrike.java:132)  atcom.sun.javafx.font.PrismFontUtils.getFontMetrics(PrismFontUtils.java:31)  atcom.sun.javafx.font.PrismFontLoader.getFontMetrics(PrismFontLoader.java:466)  atjavafx.scene.text.Text.<init>(Text.java:153)  atcom.sun.javafx.scene.control.skin.Utils.<clinit>(Utils.java:52)  ... 13 more   [ERROR] Failedto executegoalorg.codehaus.mojo:exec-maven-plugin:1.5.0:java (default-cli) onprojectjitwatch-ui: Anexceptionoccuredwhile executingtheJavaclass. null: InvocationTargetException: Exceptionin Applicationstartmethod: ExceptionInInitializerError: NullPointerException -> [Help 1] 

请在org.adoptopenjdk.jitwatch.launch.LaunchUI类的main函数开头处添加下面的代码(或者直接使用我fork修改好的 JITWatch ):

final Class<?> macFontFinderClass = Class.forName("com.sun.t2k.MacFontFinder"); final java.lang.reflect.FieldpsNameToPathMap = macFontFinderClass.getDeclaredField("psNameToPathMap"); psNameToPathMap.setAccessible(true); if (psNameToPathMap.get(null) == null) {     psNameToPathMap.set(         null, new java.util.HashMap<String, String>()); } final java.lang.reflect.FieldallAvailableFontFamilies = macFontFinderClass.getDeclaredField("allAvailableFontFamilies"); allAvailableFontFamilies.setAccessible(true); if (allAvailableFontFamilies.get(null) == null) {     allAvailableFontFamilies.set(         null, new String[] {}); } 

然后重新运行即可看到JITWatch的界面。

Reference

http://www.oracle.com/technetwork/articles/java/architect-evans-pt1-2266278.html

https://www.chrisnewland.com/images/jitwatch/HotSpot_Profiling_Using_JITWatch.pdf

posted @ 2017-06-08 16:48 小马歌 阅读(840) | 评论 (0)编辑 收藏
 
     摘要: from:http://yemengying.com/2016/05/07/threadsafe-hashmap/ 2016-05-07 JAVAHASHMAP HASHMAP, JAVA文章目录1. 为什么HashMap是线程不安全的1.1. HashMap的内部存储结构1.2. HashMap的自动扩容机制1.3. ...  阅读全文
posted @ 2017-06-02 17:57 小马歌 阅读(239) | 评论 (0)编辑 收藏
 
from:http://tech.meituan.com/java-hashmap.html

摘要

HashMap是Java程序员使用频率最高的用于映射(键值对)处理的数据类型。随着JDK(Java Developmet Kit)版本的更新,JDK1.8对HashMap底层的实现进行了优化,例如引入红黑树的数据结构和扩容的优化等。本文结合JDK1.7和JDK1.8的区别,深入探讨HashMap的结构实现和功能原理。

简介

Java为数据结构中的映射定义了一个接口java.util.Map,此接口主要有四个常用的实现类,分别是HashMap、Hashtable、LinkedHashMap和TreeMap,类继承关系如下图所示:

java.util.map类图

下面针对各个实现类的特点做一些说明:

(1) HashMap:它根据键的hashCode值存储数据,大多数情况下可以直接定位到它的值,因而具有很快的访问速度,但遍历顺序却是不确定的。 HashMap最多只允许一条记录的键为null,允许多条记录的值为null。HashMap非线程安全,即任一时刻可以有多个线程同时写HashMap,可能会导致数据的不一致。如果需要满足线程安全,可以用 Collections的synchronizedMap方法使HashMap具有线程安全的能力,或者使用ConcurrentHashMap。

(2) Hashtable:Hashtable是遗留类,很多映射的常用功能与HashMap类似,不同的是它承自Dictionary类,并且是线程安全的,任一时间只有一个线程能写Hashtable,并发性不如ConcurrentHashMap,因为ConcurrentHashMap引入了分段锁。Hashtable不建议在新代码中使用,不需要线程安全的场合可以用HashMap替换,需要线程安全的场合可以用ConcurrentHashMap替换。

(3) LinkedHashMap:LinkedHashMap是HashMap的一个子类,保存了记录的插入顺序,在用Iterator遍历LinkedHashMap时,先得到的记录肯定是先插入的,也可以在构造时带参数,按照访问次序排序。

(4) TreeMap:TreeMap实现SortedMap接口,能够把它保存的记录根据键排序,默认是按键值的升序排序,也可以指定排序的比较器,当用Iterator遍历TreeMap时,得到的记录是排过序的。如果使用排序的映射,建议使用TreeMap。在使用TreeMap时,key必须实现Comparable接口或者在构造TreeMap传入自定义的Comparator,否则会在运行时抛出java.lang.ClassCastException类型的异常。

对于上述四种Map类型的类,要求映射中的key是不可变对象。不可变对象是该对象在创建后它的哈希值不会被改变。如果对象的哈希值发生变化,Map对象很可能就定位不到映射的位置了。

通过上面的比较,我们知道了HashMap是Java的Map家族中一个普通成员,鉴于它可以满足大多数场景的使用条件,所以是使用频度最高的一个。下文我们主要结合源码,从存储结构、常用方法分析、扩容以及安全性等方面深入讲解HashMap的工作原理。

内部实现

搞清楚HashMap,首先需要知道HashMap是什么,即它的存储结构-字段;其次弄明白它能干什么,即它的功能实现-方法。下面我们针对这两个方面详细展开讲解。

存储结构-字段

从结构实现来讲,HashMap是数组+链表+红黑树(JDK1.8增加了红黑树部分)实现的,如下如所示。

hashMap内存结构图

这里需要讲明白两个问题:数据底层具体存储的是什么?这样的存储方式有什么优点呢?

(1) 从源码可知,HashMap类中有一个非常重要的字段,就是 Node[] table,即哈希桶数组,明显它是一个Node的数组。我们来看Node[JDK1.8]是何物。

static class Node<K,V> implements Map.Entry<K,V> {         final int hash;    //用来定位数组索引位置         final K key;         V value;         Node<K,V> next;   //链表的下一个node          Node(int hash, K key, V value, Node<K,V> next) { ... }         public final K getKey(){ ... }         public final V getValue() { ... }         public final String toString() { ... }         public final int hashCode() { ... }         public final V setValue(V newValue) { ... }         public final boolean equals(Object o) { ... } } 

Node是HashMap的一个内部类,实现了Map.Entry接口,本质是就是一个映射(键值对)。上图中的每个黑色圆点就是一个Node对象。

(2) HashMap就是使用哈希表来存储的。哈希表为解决冲突,可以采用开放地址法和链地址法等来解决问题,Java中HashMap采用了链地址法。链地址法,简单来说,就是数组加链表的结合。在每个数组元素上都一个链表结构,当数据被Hash后,得到数组下标,把数据放在对应下标元素的链表上。例如程序执行下面代码:

    map.put("美团","小美"); 

系统将调用"美团"这个key的hashCode()方法得到其hashCode 值(该方法适用于每个Java对象),然后再通过Hash算法的后两步运算(高位运算和取模运算,下文有介绍)来定位该键值对的存储位置,有时两个key会定位到相同的位置,表示发生了Hash碰撞。当然Hash算法计算结果越分散均匀,Hash碰撞的概率就越小,map的存取效率就会越高。

如果哈希桶数组很大,即使较差的Hash算法也会比较分散,如果哈希桶数组数组很小,即使好的Hash算法也会出现较多碰撞,所以就需要在空间成本和时间成本之间权衡,其实就是在根据实际情况确定哈希桶数组的大小,并在此基础上设计好的hash算法减少Hash碰撞。那么通过什么方式来控制map使得Hash碰撞的概率又小,哈希桶数组(Node[] table)占用空间又少呢?答案就是好的Hash算法和扩容机制。

在理解Hash和扩容流程之前,我们得先了解下HashMap的几个字段。从HashMap的默认构造函数源码可知,构造函数就是对下面几个字段进行初始化,源码如下:

     int threshold;             // 所能容纳的key-value对极限       final float loadFactor;    // 负载因子      int modCount;        int size; 

首先,Node[] table的初始化长度length(默认值是16),Load factor为负载因子(默认值是0.75),threshold是HashMap所能容纳的最大数据量的Node(键值对)个数。threshold = length * Load factor。也就是说,在数组定义好长度之后,负载因子越大,所能容纳的键值对个数越多。

结合负载因子的定义公式可知,threshold就是在此Load factor和length(数组长度)对应下允许的最大元素数目,超过这个数目就重新resize(扩容),扩容后的HashMap容量是之前容量的两倍。默认的负载因子0.75是对空间和时间效率的一个平衡选择,建议大家不要修改,除非在时间和空间比较特殊的情况下,如果内存空间很多而又对时间效率要求很高,可以降低负载因子Load factor的值;相反,如果内存空间紧张而对时间效率要求不高,可以增加负载因子loadFactor的值,这个值可以大于1。

size这个字段其实很好理解,就是HashMap中实际存在的键值对数量。注意和table的长度length、容纳最大键值对数量threshold的区别。而modCount字段主要用来记录HashMap内部结构发生变化的次数,主要用于迭代的快速失败。强调一点,内部结构发生变化指的是结构发生变化,例如put新键值对,但是某个key对应的value值被覆盖不属于结构变化。

在HashMap中,哈希桶数组table的长度length大小必须为2的n次方(一定是合数),这是一种非常规的设计,常规的设计是把桶的大小设计为素数。相对来说素数导致冲突的概率要小于合数,具体证明可以参考http://blog.csdn.net/liuqiyao_01/article/details/14475159,Hashtable初始化桶大小为11,就是桶大小设计为素数的应用(Hashtable扩容后不能保证还是素数)。HashMap采用这种非常规设计,主要是为了在取模和扩容时做优化,同时为了减少冲突,HashMap定位哈希桶索引位置时,也加入了高位参与运算的过程。

这里存在一个问题,即使负载因子和Hash算法设计的再合理,也免不了会出现拉链过长的情况,一旦出现拉链过长,则会严重影响HashMap的性能。于是,在JDK1.8版本中,对数据结构做了进一步的优化,引入了红黑树。而当链表长度太长(默认超过8)时,链表就转换为红黑树,利用红黑树快速增删改查的特点提高HashMap的性能,其中会用到红黑树的插入、删除、查找等算法。本文不再对红黑树展开讨论,想了解更多红黑树数据结构的工作原理可以参考http://blog.csdn.net/v_july_v/article/details/6105630

功能实现-方法

HashMap的内部功能实现很多,本文主要从根据key获取哈希桶数组索引位置、put方法的详细执行、扩容过程三个具有代表性的点深入展开讲解。

1. 确定哈希桶数组索引位置

不管增加、删除、查找键值对,定位到哈希桶数组的位置都是很关键的第一步。前面说过HashMap的数据结构是数组和链表的结合,所以我们当然希望这个HashMap里面的元素位置尽量分布均匀些,尽量使得每个位置上的元素数量只有一个,那么当我们用hash算法求得这个位置的时候,马上就可以知道对应位置的元素就是我们要的,不用遍历链表,大大优化了查询的效率。HashMap定位数组索引位置,直接决定了hash方法的离散性能。先看看源码的实现(方法一+方法二):

方法一: static final int hash(Object key) {   //jdk1.8 & jdk1.7      int h;      // h = key.hashCode() 为第一步 取hashCode值      // h ^ (h >>> 16)  为第二步 高位参与运算      return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); } 方法二: static int indexFor(int h, int length) {  //jdk1.7的源码,jdk1.8没有这个方法,但是实现原理一样的      return h & (length-1);  //第三步 取模运算 } 

这里的Hash算法本质上就是三步:取key的hashCode值、高位运算、取模运算

对于任意给定的对象,只要它的hashCode()返回值相同,那么程序调用方法一所计算得到的Hash码值总是相同的。我们首先想到的就是把hash值对数组长度取模运算,这样一来,元素的分布相对来说是比较均匀的。但是,模运算的消耗还是比较大的,在HashMap中是这样做的:调用方法二来计算该对象应该保存在table数组的哪个索引处。

这个方法非常巧妙,它通过h & (table.length -1)来得到该对象的保存位,而HashMap底层数组的长度总是2的n次方,这是HashMap在速度上的优化。当length总是2的n次方时,h& (length-1)运算等价于对length取模,也就是h%length,但是&比%具有更高的效率。

在JDK1.8的实现中,优化了高位运算的算法,通过hashCode()的高16位异或低16位实现的:(h = k.hashCode()) ^ (h >>> 16),主要是从速度、功效、质量来考虑的,这么做可以在数组table的length比较小的时候,也能保证考虑到高低Bit都参与到Hash的计算中,同时不会有太大的开销。

下面举例说明下,n为table的长度。

hashMap哈希算法例图

2. 分析HashMap的put方法

HashMap的put方法执行过程可以通过下图来理解,自己有兴趣可以去对比源码更清楚地研究学习。

hashMap put方法执行流程图

①.判断键值对数组table[i]是否为空或为null,否则执行resize()进行扩容;

②.根据键值key计算hash值得到插入的数组索引i,如果table[i]==null,直接新建节点添加,转向⑥,如果table[i]不为空,转向③;

③.判断table[i]的首个元素是否和key一样,如果相同直接覆盖value,否则转向④,这里的相同指的是hashCode以及equals;

④.判断table[i] 是否为treeNode,即table[i] 是否是红黑树,如果是红黑树,则直接在树中插入键值对,否则转向⑤;

⑤.遍历table[i],判断链表长度是否大于8,大于8的话把链表转换为红黑树,在红黑树中执行插入操作,否则进行链表的插入操作;遍历过程中若发现key已经存在直接覆盖value即可;

⑥.插入成功后,判断实际存在的键值对数量size是否超多了最大容量threshold,如果超过,进行扩容。

JDK1.8HashMap的put方法源码如下:

 1 public V put(K key, V value) {  2     // 对key的hashCode()做hash  3     return putVal(hash(key), key, value, false, true);  4 }  5   6 final V putVal(int hash, K key, V value, boolean onlyIfAbsent,  7                boolean evict) {  8     Node<K,V>[] tab; Node<K,V> p; int n, i;  9     // 步骤①:tab为空则创建 10     if ((tab = table) == null || (n = tab.length) == 0) 11         n = (tab = resize()).length; 12     // 步骤②:计算index,并对null做处理  13     if ((p = tab[i = (n - 1) & hash]) == null)  14         tab[i] = newNode(hash, key, value, null); 15     else { 16         Node<K,V> e; K k; 17         // 步骤③:节点key存在,直接覆盖value 18         if (p.hash == hash && 19             ((k = p.key) == key || (key != null && key.equals(k)))) 20             e = p; 21         // 步骤④:判断该链为红黑树 22         else if (p instanceof TreeNode) 23             e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); 24         // 步骤⑤:该链为链表 25         else { 26             for (int binCount = 0; ; ++binCount) { 27                 if ((e = p.next) == null) { 28                     p.next = newNode(hash, key,value,null);                         //链表长度大于8转换为红黑树进行处理 29                     if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st   30                         treeifyBin(tab, hash); 31                     break; 32                 }                     // key已经存在直接覆盖value 33                 if (e.hash == hash && 34                     ((k = e.key) == key || (key != null && key.equals(k))))  35                            break; 36                 p = e; 37             } 38         } 39          40         if (e != null) { // existing mapping for key 41             V oldValue = e.value; 42             if (!onlyIfAbsent || oldValue == null) 43                 e.value = value; 44             afterNodeAccess(e); 45             return oldValue; 46         } 47     }  48     ++modCount; 49     // 步骤⑥:超过最大容量 就扩容 50     if (++size > threshold) 51         resize(); 52     afterNodeInsertion(evict); 53     return null; 54 } 

3. 扩容机制

扩容(resize)就是重新计算容量,向HashMap对象里不停的添加元素,而HashMap对象内部的数组无法装载更多的元素时,对象就需要扩大数组的长度,以便能装入更多的元素。当然Java里的数组是无法自动扩容的,方法是使用一个新的数组代替已有的容量小的数组,就像我们用一个小桶装水,如果想装更多的水,就得换大水桶。

我们分析下resize的源码,鉴于JDK1.8融入了红黑树,较复杂,为了便于理解我们仍然使用JDK1.7的代码,好理解一些,本质上区别不大,具体区别后文再说。

 1 void resize(int newCapacity) {   //传入新的容量  2     Entry[] oldTable = table;    //引用扩容前的Entry数组  3     int oldCapacity = oldTable.length;           4     if (oldCapacity == MAXIMUM_CAPACITY) {  //扩容前的数组大小如果已经达到最大(2^30)了  5         threshold = Integer.MAX_VALUE; //修改阈值为int的最大值(2^31-1),这样以后就不会扩容了  6         return;  7     }  8    9     Entry[] newTable = new Entry[newCapacity];  //初始化一个新的Entry数组 10     transfer(newTable);                         //!!将数据转移到新的Entry数组里 11     table = newTable;                           //HashMap的table属性引用新的Entry数组 12     threshold = (int)(newCapacity * loadFactor);//修改阈值 13 } 

这里就是使用一个容量更大的数组来代替已有的容量小的数组,transfer()方法将原有Entry数组的元素拷贝到新的Entry数组里。

 1 void transfer(Entry[] newTable) {  2     Entry[] src = table;                   //src引用了旧的Entry数组  3     int newCapacity = newTable.length;  4     for (int j = 0; j < src.length; j++) { //遍历旧的Entry数组  5         Entry<K,V> e = src[j];             //取得旧Entry数组的每个元素  6         if (e != null) {  7             src[j] = null;//释放旧Entry数组的对象引用(for循环后,旧的Entry数组不再引用任何对象)  8             do {  9                 Entry<K,V> next = e.next; 10                 int i = indexFor(e.hash, newCapacity); //!!重新计算每个元素在数组中的位置 11                 e.next = newTable[i]; //标记[1] 12                 newTable[i] = e;      //将元素放在数组上 13                 e = next;             //访问下一个Entry链上的元素 14             } while (e != null); 15         } 16     } 17 } 

newTable[i]的引用赋给了e.next,也就是使用了单链表的头插入方式,同一位置上新元素总会被放在链表的头部位置;这样先放在一个索引上的元素终会被放到Entry链的尾部(如果发生了hash冲突的话),这一点和Jdk1.8有区别,下文详解。在旧数组中同一条Entry链上的元素,通过重新计算索引位置后,有可能被放到了新数组的不同位置上。

下面举个例子说明下扩容过程。假设了我们的hash算法就是简单的用key mod 一下表的大小(也就是数组的长度)。其中的哈希桶数组table的size=2, 所以key = 3、7、5,put顺序依次为 5、7、3。在mod 2以后都冲突在table[1]这里了。这里假设负载因子 loadFactor=1,即当键值对的实际大小size 大于 table的实际大小时进行扩容。接下来的三个步骤是哈希桶数组 resize成4,然后所有的Node重新rehash的过程。

jdk1.7扩容例图

下面我们讲解下JDK1.8做了哪些优化。经过观测可以发现,我们使用的是2次幂的扩展(指长度扩为原来2倍),所以,元素的位置要么是在原位置,要么是在原位置再移动2次幂的位置。看下图可以明白这句话的意思,n为table的长度,图(a)表示扩容前的key1和key2两种key确定索引位置的示例,图(b)表示扩容后key1和key2两种key确定索引位置的示例,其中hash1是key1对应的哈希与高位运算结果。

hashMap 1.8 哈希算法例图1

元素在重新计算hash之后,因为n变为2倍,那么n-1的mask范围在高位多1bit(红色),因此新的index就会发生这样的变化:

hashMap 1.8 哈希算法例图2

因此,我们在扩充HashMap的时候,不需要像JDK1.7的实现那样重新计算hash,只需要看看原来的hash值新增的那个bit是1还是0就好了,是0的话索引没变,是1的话索引变成“原索引+oldCap”,可以看看下图为16扩充为32的resize示意图:

jdk1.8 hashMap扩容例图

这个设计确实非常的巧妙,既省去了重新计算hash值的时间,而且同时,由于新增的1bit是0还是1可以认为是随机的,因此resize的过程,均匀的把之前的冲突的节点分散到新的bucket了。这一块就是JDK1.8新增的优化点。有一点注意区别,JDK1.7中rehash的时候,旧链表迁移新链表的时候,如果在新表的数组索引位置相同,则链表元素会倒置,但是从上图可以看出,JDK1.8不会倒置。有兴趣的同学可以研究下JDK1.8的resize源码,写的很赞,如下:

 1 final Node<K,V>[] resize() {  2     Node<K,V>[] oldTab = table;  3     int oldCap = (oldTab == null) ? 0 : oldTab.length;  4     int oldThr = threshold;  5     int newCap, newThr = 0;  6     if (oldCap > 0) {  7         // 超过最大值就不再扩充了,就只好随你碰撞去吧  8         if (oldCap >= MAXIMUM_CAPACITY) {  9             threshold = Integer.MAX_VALUE; 10             return oldTab; 11         } 12         // 没超过最大值,就扩充为原来的2倍 13         else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && 14                  oldCap >= DEFAULT_INITIAL_CAPACITY) 15             newThr = oldThr << 1; // double threshold 16     } 17     else if (oldThr > 0) // initial capacity was placed in threshold 18         newCap = oldThr; 19     else {               // zero initial threshold signifies using defaults 20         newCap = DEFAULT_INITIAL_CAPACITY; 21         newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); 22     } 23     // 计算新的resize上限 24     if (newThr == 0) { 25  26         float ft = (float)newCap * loadFactor; 27         newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? 28                   (int)ft : Integer.MAX_VALUE); 29     } 30     threshold = newThr; 31     @SuppressWarnings({"rawtypes","unchecked"}) 32         Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; 33     table = newTab; 34     if (oldTab != null) { 35         // 把每个bucket都移动到新的buckets中 36         for (int j = 0; j < oldCap; ++j) { 37             Node<K,V> e; 38             if ((e = oldTab[j]) != null) { 39                 oldTab[j] = null; 40                 if (e.next == null) 41                     newTab[e.hash & (newCap - 1)] = e; 42                 else if (e instanceof TreeNode) 43                     ((TreeNode<K,V>)e).split(this, newTab, j, oldCap); 44                 else { // 链表优化重hash的代码块 45                     Node<K,V> loHead = null, loTail = null; 46                     Node<K,V> hiHead = null, hiTail = null; 47                     Node<K,V> next; 48                     do { 49                         next = e.next; 50                         // 原索引 51                         if ((e.hash & oldCap) == 0) { 52                             if (loTail == null) 53                                 loHead = e; 54                             else 55                                 loTail.next = e; 56                             loTail = e; 57                         } 58                         // 原索引+oldCap 59                         else { 60                             if (hiTail == null) 61                                 hiHead = e; 62                             else 63                                 hiTail.next = e; 64                             hiTail = e; 65                         } 66                     } while ((e = next) != null); 67                     // 原索引放到bucket里 68                     if (loTail != null) { 69                         loTail.next = null; 70                         newTab[j] = loHead; 71                     } 72                     // 原索引+oldCap放到bucket里 73                     if (hiTail != null) { 74                         hiTail.next = null; 75                         newTab[j + oldCap] = hiHead; 76                     } 77                 } 78             } 79         } 80     } 81     return newTab; 82 } 

线程安全性

在多线程使用场景中,应该尽量避免使用线程不安全的HashMap,而使用线程安全的ConcurrentHashMap。那么为什么说HashMap是线程不安全的,下面举例子说明在并发的多线程使用场景中使用HashMap可能造成死循环。代码例子如下(便于理解,仍然使用JDK1.7的环境):

public class HashMapInfiniteLoop {        private static HashMap<Integer,String> map = new HashMap<Integer,String>(2,0.75f);       public static void main(String[] args) {           map.put(5, "C");            new Thread("Thread1") {               public void run() {                   map.put(7, "B");                   System.out.println(map);               };           }.start();           new Thread("Thread2") {               public void run() {                   map.put(3, "A);                   System.out.println(map);               };           }.start();             }   } 

其中,map初始化为一个长度为2的数组,loadFactor=0.75,threshold=2*0.75=1,也就是说当put第二个key的时候,map就需要进行resize。

通过设置断点让线程1和线程2同时debug到transfer方法(3.3小节代码块)的首行。注意此时两个线程已经成功添加数据。放开thread1的断点至transfer方法的“Entry next = e.next;” 这一行;然后放开线程2的的断点,让线程2进行resize。结果如下图。

jdk1.7 hashMap死循环例图1

注意,Thread1的 e 指向了key(3),而next指向了key(7),其在线程二rehash后,指向了线程二重组后的链表。

线程一被调度回来执行,先是执行 newTalbe[i] = e, 然后是e = next,导致了e指向了key(7),而下一次循环的next = e.next导致了next指向了key(3)。

jdk1.7 hashMap死循环例图2

jdk1.7 hashMap死循环例图3

e.next = newTable[i] 导致 key(3).next 指向了 key(7)。注意:此时的key(7).next 已经指向了key(3), 环形链表就这样出现了。

jdk1.7 hashMap死循环例图4

于是,当我们用线程一调用map.get(11)时,悲剧就出现了——Infinite Loop。

JDK1.8与JDK1.7的性能对比

HashMap中,如果key经过hash算法得出的数组索引位置全部不相同,即Hash算法非常好,那样的话,getKey方法的时间复杂度就是O(1),如果Hash算法技术的结果碰撞非常多,假如Hash算极其差,所有的Hash算法结果得出的索引位置一样,那样所有的键值对都集中到一个桶中,或者在一个链表中,或者在一个红黑树中,时间复杂度分别为O(n)和O(lgn)。 鉴于JDK1.8做了多方面的优化,总体性能优于JDK1.7,下面我们从两个方面用例子证明这一点。

Hash较均匀的情况

为了便于测试,我们先写一个类Key,如下:

class Key implements Comparable<Key> {      private final int value;      Key(int value) {         this.value = value;     }      @Override     public int compareTo(Key o) {         return Integer.compare(this.value, o.value);     }      @Override     public boolean equals(Object o) {         if (this == o) return true;         if (o == null || getClass() != o.getClass())             return false;         Key key = (Key) o;         return value == key.value;     }      @Override     public int hashCode() {         return value;     } } 

这个类复写了equals方法,并且提供了相当好的hashCode函数,任何一个值的hashCode都不会相同,因为直接使用value当做hashcode。为了避免频繁的GC,我将不变的Key实例缓存了起来,而不是一遍一遍的创建它们。代码如下:

public class Keys {      public static final int MAX_KEY = 10_000_000;     private static final Key[] KEYS_CACHE = new Key[MAX_KEY];      static {         for (int i = 0; i < MAX_KEY; ++i) {             KEYS_CACHE[i] = new Key(i);         }     }      public static Key of(int value) {         return KEYS_CACHE[value];     } } 

现在开始我们的试验,测试需要做的仅仅是,创建不同size的HashMap(1、10、100、......10000000),屏蔽了扩容的情况,代码如下:

   static void test(int mapSize) {          HashMap<Key, Integer> map = new HashMap<Key,Integer>(mapSize);         for (int i = 0; i < mapSize; ++i) {             map.put(Keys.of(i), i);         }          long beginTime = System.nanoTime(); //获取纳秒         for (int i = 0; i < mapSize; i++) {             map.get(Keys.of(i));         }         long endTime = System.nanoTime();         System.out.println(endTime - beginTime);     }      public static void main(String[] args) {         for(int i=10;i<= 1000 0000;i*= 10){             test(i);         }     } 

在测试中会查找不同的值,然后度量花费的时间,为了计算getKey的平均时间,我们遍历所有的get方法,计算总的时间,除以key的数量,计算一个平均值,主要用来比较,绝对值可能会受很多环境因素的影响。结果如下:

性能比较表1.png

通过观测测试结果可知,JDK1.8的性能要高于JDK1.7 15%以上,在某些size的区域上,甚至高于100%。由于Hash算法较均匀,JDK1.8引入的红黑树效果不明显,下面我们看看Hash不均匀的的情况。

Hash极不均匀的情况

假设我们又一个非常差的Key,它们所有的实例都返回相同的hashCode值。这是使用HashMap最坏的情况。代码修改如下:

class Key implements Comparable<Key> {      //...      @Override     public int hashCode() {         return 1;     } } 

仍然执行main方法,得出的结果如下表所示:

性能比较表2.png

从表中结果中可知,随着size的变大,JDK1.7的花费时间是增长的趋势,而JDK1.8是明显的降低趋势,并且呈现对数增长稳定。当一个链表太长的时候,HashMap会动态的将它替换成一个红黑树,这话的话会将时间复杂度从O(n)降为O(logn)。hash算法均匀和不均匀所花费的时间明显也不相同,这两种情况的相对比较,可以说明一个好的hash算法的重要性。

      测试环境:处理器为2.2 GHz Intel Core i7,内存为16 GB 1600 MHz DDR3,SSD硬盘,使用默认的JVM参数,运行在64位的OS X 10.10.1上。

小结

(1) 扩容是一个特别耗性能的操作,所以当程序员在使用HashMap的时候,估算map的大小,初始化的时候给一个大致的数值,避免map进行频繁的扩容。

(2) 负载因子是可以修改的,也可以大于1,但是建议不要轻易修改,除非情况非常特殊。

(3) HashMap是线程不安全的,不要在并发的环境中同时操作HashMap,建议使用ConcurrentHashMap。

(4) JDK1.8引入红黑树大程度优化了HashMap的性能。

(5) 还没升级JDK1.8的,现在开始升级吧。HashMap的性能提升仅仅是JDK1.8的冰山一角。

参考

  1. JDK1.7&JDK1.8 源码。
  2. CSDN博客频道,HashMap多线程死循环问题,2014。
  3. 红黑联盟,Java类集框架之HashMap(JDK1.8)源码剖析,2015。
  4. CSDN博客频道, 教你初步了解红黑树,2010。
  5. Java Code Geeks,HashMap performance improvements in Java 8,2014。
  6. Importnew,危险!在HashMap中将可变对象用作Key,2014。
  7. CSDN博客频道,为什么一般hashtable的桶数会取一个素数,2013。


posted @ 2017-06-02 17:56 小马歌 阅读(152) | 评论 (0)编辑 收藏
 
from:http://blog.csdn.net/hemeinvyiqiluoben/article/details/62439861

课程安排
第一天 
上午
一、机器学习基础1.线性代数
(1).矩阵运算  (2).向量运算
(3).SVD      (4).PCA
2.概率信息论
(1).概率分布  (2).期望、方差、协方差
(3). 贝叶斯   (4).结构概率模型
3.数值优化
二、深度学习基础1.深度学习介绍 
(1).发展历史  (2).主要应用
2.感知器      
3.人工神经网络
4.前馈神经网络  
5.BP算法      
6.Hessian矩阵
第一天
  下午
三、深度学习进阶---卷积神经网络1.CNN卷积神经网络
(1).卷积层(一维卷积、二维卷积)
(2).池化层(均值池化、最大池化)
(3). 全连接层
(4).激活函数层
(5).Softmax层
2.CNN卷积神经网络改进
(1).R-CNN (SPPNET)
(2).Fast-R-CNN
(3).Faster-R-CNN (YOLO、SSD)
3.深度学习的模型训练技巧
4.梯度下降的优化方法详解
第二天
上午
四、深度学习软件1.深度学习相关软件的安装配置与使用介绍
(1).Caffe
(2).Tensorflow
(3).Torch
(4).MXNet
第二天
下午
五、 CNN应用案例(1).CNN与手写数字集分类
(2).YOLO实现目标检测
(3).PixelNet原理与实现
(4).利用卷积神经网络做图像风格结合
第三天
上午
六、深度学习——循环神经网络1.RNN循环神经网络
(1).梯度计算
(2).BPTT
2.RNN循环神经网络改进
(1).LSTM
(2).GRU
(3).Bi-RNN
(4).Attention based RNN
3.RNN实际应用
(1).Seq2Seq的原理与实现   
第三天
下午
、强化学习1.强化学习的理论知识
2.经典模型DQN讲解
3.AlphaGo原理讲解
4.RL实际应用
(1).实现一个AlphaGo     
第四天
上午
八、对抗性生成网络1.GAN的理论知识
2.GAN经典模型
(1).GAN,CGAN,LAPGAN,DCGAN,
3.GAN经典模型
(1). INFOGAN,WGAN,S2-GAN
4.GAN实际应用
(1).DCGAN提高模糊图片分辨率
5.GAN实际应用
(1).InfoGAN做特定的样本生成
第四天
下午
九、迁移学习1.迁移学习的理论概述
2.迁移学习的常见方法
(1).基于特征的迁移
(2).基于实例的迁移
(3).基于数据的迁移
(4).深度迁移学习
(5).强化迁移学习
(6).迁移学习的研究案例
(7).迁移学习的应用
(8).2017年AAAI最佳论文讲解:利用物理定理的知识迁移到视频理解
posted @ 2017-05-17 11:28 小马歌 阅读(410) | 评论 (0)编辑 收藏
 
from:http://mobile.51cto.com/hot-439693.htm

背景:除去大名鼎鼎的QQ这款即时聊天工具,还有许多细分行业的IM,比如淘宝阿里旺旺、网易泡泡、YY语音......。恰巧公司产品也要开发一款基于我 们自己行业的类IM系统,很有幸我担当了这个产品的架构师,核心代码编写、实现者。下面把我近年来从技术上我对IM系统(即时消息的传输,不包括语音,视频,文件的传输)的理解和设计分享出来,浅薄之见,望大家别见笑,欢迎给出批评意见。

一.网络传输协议的选择

目前我知晓的所有IM系统传输即时消息无外乎使用UDP、TCP、基于TCP的http这几种协议中的一种或几种。比如QQ主要采用UDP协议,MSN主要采用TCP协议,而且他们也都支持HTTP协议的代理模式。更多资料,请参加这篇文章《一些常用软件的网络端口协议分类介绍》

我们该如何选择呢?

  • UDP协议实时性更好,但是如何处理安全可靠的传输并且处理不同客户端之间的消息交互是个难题,实现起来过于复杂;

  • HTTP协议属于扩展支持,我们在产品的初始阶段可以不用支持;

  • 那就非TCP协议莫属了,要考虑的同样也有很多,特别是如果有海量用户的需求。如何保证单机服务器高并发量,如何做到灵活,扩展的架构。

Tips: QQ 为什么采用 UDP 协议,而不采用 TCP 协议实现?

二.应该选择什么格式的数据协议

二进制格式?文本格式?这个话题转到我的这篇文章《网络传输数据格式的选择》,从我们当前的需求和产品周期上我觉得选择JSON形式的数据协议是最好的。

三.架构设计

首先我们来提炼一下一个IM系统的主要需求,包括账号,关系链,在线状态显示,消息交互......。

架构考量:

  • 由于采用可靠传输协议TCP,考虑到负载问题(短连接实现账号、关系链相关业务,长连接实现上线、信息推送);

  • 后台架构的灵活性、可扩展性,支持分布式部署——把网络层、业务逻辑层、数据层分离,网络层和业务层支持负载均衡策略、数据层支持分布式存储;

  • 客户端SDK的易用性:把网络层、数据层分离、业务逻辑层分离;

后台架构简化图

架构示意图

架构细化图

说明

  • 从< 架构细化图>中可以看出对于上线服务由于建立的是TCP长连接,对于单台服务器往往由于硬件资源、系统资源、网络资源的限制无法做到海量用户的同时 在线,所以设计为根据服务器负载支持多服务器上线,同时由于多服务器上线造成了对整个系统交互(不同的客户端的交互,协作部门应用服务和客户的交互)的分 割,引入消息转发服务器作为粘合点。另外对于多服务器上线造成的统一账户信息(在线状态,消息)数据的分割,引入统一的数据层(内存存储 层:session、状态信息存储、消息队列存储;数据库:账号信息存储)做到业务和数据的分离,也就做到了支持分布式部署。参见我的这篇文章《构建高性能服务的考量》

  • 对于部分业务服务:做到网络层、业务层、数据层的完全分离。首先对于TCP短连接来说不会如长连接那般消耗资源,即使后期遇到海量的并发访问请求依然可以从容的通过负载均衡策略和数据分布式部署策略进行解决。参见我的这篇文章《服务端架构中的“网关服务器”》

服务端平台及技术选型

  • 系统开发平台: CentOS——Linux发行版的一种,稳定可靠、可定制优化、支持丰富;

  • 网络支撑层: libevent——减小开发成本,增强稳定性;

  • 缓存存储层: Redis——支持丰富的存储结构,支持分布式存储;

  • 数据库: MySQL——最适合互联网的数据库,免授权、高效稳定、可控性高;

  • 开发语言: C/C++;

部分热点问题考量

  • 系统性能考量:

    • 编码角度:采用高效的网络模型,线程模型,I/O处理模型,合理的数据库设计和操作语句的优化;

    • 垂直扩展:通过提高单服务器的硬件资源或者网络资源来提高性能;

    • 水平扩展:通过合理的架构设计和运维方面的负载均衡策略将负载分担,有效提高性能;后期甚至可以考虑加入数据缓存层,突破IO瓶颈;

  • 系统的高可用性:(防止单点故障)

    • 在架构设计时做到业务处理和数据的分离,从而依赖分布式的部署使得在单点故障时能保证系统可用。

    • 对于关键独立节点可以采用双机热备技术进行切换。

    • 数据库数据的安全性可以通过磁盘阵列的冗余配置和主备数据库来解决。

主要学习资料: 请自行google。

  • 《1.4亿在线背后的故事》;

  • 《BasicDB的架构演变》;

  • 《微信之道-至简》;

本文出自51博客 “永远的朋友” ,转载请务必保留此出处http://yaocoder.blog.51cto.com/2668309/1412029

posted @ 2017-05-15 14:22 小马歌 阅读(224) | 评论 (0)编辑 收藏
 
     摘要: from:https://zhangge.net/4703.html昨天,同事告诉我发现一个诡异的问题,grep无法搜索shell中的变量,着实很惊讶。到他所说的服务器上试了下,还真是不行!大概就是这样一个要求:①、有个文本为userid.txt,里面每一行一个用户id,类似如下:Shell1234500010003000500070009②、另外还有一个文本为record...  阅读全文
posted @ 2017-03-01 16:59 小马歌 阅读(737) | 评论 (0)编辑 收藏
 

来自: http://tonybai.com/2016/02/26/deploy-a-private-docker-registry/

安装部署一个私有的Docker Registry是引入、学习和使用 Docker 这门技术的必经之路之一。尤其是当Docker被所在组织接受,更多人、项目和产品开始接触和使用Docker时,存储和分发自制的Docker image便成了刚需。Docker Registry一如既往的继承了“Docker坑多”的特点,为此这里将自己搭建”各类”Registry过程中执行的步骤、遇到的问题记录下来,为己备忘,为他参考。

Docker在2015年推出了 distribution 项目,即Docker Registry 2。相比于 old registry ,Registry 2使用Go实现,在安全性、性能方面均有大幅改进。Registry设计了全新的Rest API,并且在image存储格式等方面不再兼容于old Registry。去年8月份,docker官方hub使用Registriy 2.1替代了原先的old Registry。如果你要与Registry2交互,你的Docker版本至少要是Docker 1.6。

Docker的开发者也一直在致力于改善Registry安装和使用的体验,通过提供 官方Registry Image以及 Docker Compose工具 等来简化Registry的配置。不过在本文中,我们只是利用Docker以及Registry的官方Image来部署Registry,这样更便于全面了解Registry的部署配置细节。

Registry2在镜像存储方面不仅支持本地盘,还支持诸多主流第三方存储方案。通过分布式存储系统你还可以实现一个分布式Docker Registry服务。这里仅以本地盘以及single node registry2为例。

一、环境

这里还是复用以往文章中的Docker环境:

Docker Registry Server: 10.10.105.71 Ubuntu 14.04 3.16.0-57-generic;docker 1.9.1

其他两个工作Server:
10.10.105.72 Ubuntu 14.04 3.19.0-25-generic; docker 1.9.1
10.10.126.101 Ubuntu 12.04 3.16.7-013607-generic; docker 1.9.1

本次Registry使用当前最新stable版本:Registry 2.3.0。由于镜像采用本地磁盘存储,root分区较小,需要映射使用其他volume。

二、初次搭建

本以为Docker Registry的搭建是何其简单的,甚至简单到通过一行命令就可以完成的。比如我们在Registry Server上执行:

在~/dockerregistry下,执行:

$sudo docker run -d -p 5000:5000 -v `pwd`/data:/var/lib/registry --restart=always --name registry registry:2
Unable to find image 'registry:2' locally
2: Pulling from library/registry
f32095d4ba8a: Pull complete
9b607719a62a: Pull complete
973de4038269: Pull complete
2867140211c1: Pull complete
8da16446f5ca: Pull complete
fd8c38b8b68d: Pull complete
136640b01f02: Pull complete
e039ba1c0008: Pull complete
c457c689c328: Pull complete
Digest: sha256:339d702cf9a4b0aa665269cc36255ee7ce424412d56bee9ad8a247afe8c49ef1
Status: Downloaded newer image for registry:2
e9088ef901cb00546c59f89defa4625230f4b36b0a44b3713f38ab3d2a5a2b44

$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
registry            2                   c457c689c328        9 days ago          165.7 MB

$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED              STATUS              PORTS                    NAMES
e9088ef901cb        registry:2          "/bin/registry /etc/d"   About a minute ago   Up About a minute   0.0.0.0:5000->5000/tcp   registry

Registry container已经跑起来了,其启动日志可以通过:docker logs registry查看。

我们在71本地给busybox:latest打一个tag,并尝试将新tag下的image push到Registry中去:

$ docker tag busybox:latest 10.10.105.71:5000/tonybai/busybox:latest
$ docker images
REPOSITORY                          TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
registry                            2                   c457c689c328        9 days ago          165.7 MB
busybox                             latest              65e4158d9625        9 days ago          1.114 MB
10.10.105.71:5000/tonybai/busybox   latest              65e4158d9625        9 days ago          1.114 MB
... ...

push到Registry中:

$ docker push 10.10.105.71:5000/tonybai/busybox
The push refers to a repository [10.10.105.71:5000/tonybai/busybox] (len: 1)
unable to ping registry endpoint https://10.10.105.71:5000/v0/
v2 ping attempt failed with error: Get https://10.10.105.71:5000/v2/: Tunnel or SSL Forbidden
 v1 ping attempt failed with error: Get https://10.10.105.71:5000/v1/_ping: Tunnel or SSL Forbidden

出错了!简单分析了一下,可能是71上docker daemon配置中加了http代理的缘故,导致无法ping通registry endpoint。于是在/etc/default/docker中注释掉export http_proxy=”xxx”的设置,并重启docker daemon。

再次尝试push:

$ docker push 10.10.105.71:5000/tonybai/busybox
The push refers to a repository [10.10.105.71:5000/tonybai/busybox] (len: 1)
unable to ping registry endpoint https://10.10.105.71:5000/v0/
v2 ping attempt failed with error: Get https://10.10.105.71:5000/v2/: tls: oversized record received with length 20527
 v1 ping attempt failed with error: Get https://10.10.105.71:5000/v1/_ping: tls: oversized record received with length 20527

虽然还是失败,但错误信息已有所不同了。这次看来连接是可以建立的,但client端通过https访问server端,似乎想tls通信,但这一过程并未完成。

在其他机器上尝试push image到registry也遇到了同样的错误输出,如下:

10.10.105.72:

$ docker push 10.10.105.71:5000/tonybai/ubuntu
The push refers to a repository [10.10.105.71:5000/tonybai/ubuntu] (len: 1)
unable to ping registry endpoint https://10.10.105.71:5000/v0/
v2 ping attempt failed with error: Get https://10.10.105.71:5000/v2/: tls: oversized record received with length 20527
 v1 ping attempt failed with error: Get https://10.10.105.71:5000/v1/_ping: tls: oversized record received with length 20527

从错误信息来看,client与Registry交互,默认将采用https访问,但我们在install Registry时并未配置指定任何tls相关的key和crt文件,https访问定然失败。要想弄清这个问题,只能查看 Registry Manual 。

三、Insecure Registry

Registry的文档还是相对详尽的。在文档中,我们找到了 Insecure Registry ,即接收plain http访问的Registry的配置和使用方法,虽然这不是官方推荐的。

实际上对于我们内部网络而言,Insecure Registry基本能满足需求,部署过程也避免了secure registry的那些繁琐步骤,比如制作和部署证书等。

为了搭建一个Insecure Registry,我们需要先清理一下上面已经启动的Registry容器。

$ docker stop registry
registry
$ docker rm registry
registry

修改Registry server上的Docker daemon的配置,为DOCKER_OPTS增加–insecure-registry:

DOCKER_OPTS="--insecure-registry 10.10.105.71:5000 ....

重启Docker Daemon,启动Registry容器:

$ sudo service docker restart
docker stop/waiting
docker start/running, process 6712
$ sudo docker run -d -p 5000:5000 -v `pwd`/data:/var/lib/registry --restart=always --name registry registry:2
5966e92fce9c34705050e19368d19574e021a272ede1575385ef35ecf5cea019

尝试再次Push image:

$ docker push 10.10.105.71:5000/tonybai/busybox
The push refers to a repository [10.10.105.71:5000/tonybai/busybox] (len: 1)
65e4158d9625: Pushed
5506dda26018: Pushed
latest: digest: sha256:800f2d4558acd67f52262fbe170c9fc2e67efaa6f230a74b41b555e6fcca2892 size: 2739

这回push ok!

我们将本地的tag做untag处理,再从Registry pull相关image:

$ docker images
REPOSITORY                          TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
registry                            2                   c457c689c328        9 days ago          165.7 MB
10.10.105.71:5000/tonybai/busybox   latest              65e4158d9625        9 days ago          1.114 MB
busybox                             latest              65e4158d9625        9 days ago          1.114 MB
ubuntu                              14.04               6cc0fc2a5ee3        5 weeks ago         187.9 MB

$ docker rmi 10.10.105.71:5000/tonybai/busybox
Untagged: 10.10.105.71:5000/tonybai/busybox:latest

$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
registry            2                   c457c689c328        9 days ago          165.7 MB
busybox             latest              65e4158d9625        9 days ago          1.114 MB
ubuntu              14.04               6cc0fc2a5ee3        5 weeks ago         187.9 MB

$ docker pull 10.10.105.71:5000/tonybai/busybox
Using default tag: latest
latest: Pulling from tonybai/busybox
Digest: sha256:800f2d4558acd67f52262fbe170c9fc2e67efaa6f230a74b41b555e6fcca2892
Status: Downloaded newer image for 10.10.105.71:5000/tonybai/busybox:latest

$ docker images
REPOSITORY                          TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
registry                            2                   c457c689c328        9 days ago          165.7 MB
10.10.105.71:5000/tonybai/busybox   latest              65e4158d9625        9 days ago          1.114 MB
busybox                             latest              65e4158d9625        9 days ago          1.114 MB
ubuntu                              14.04               6cc0fc2a5ee3        5 weeks ago         187.9 MB

可以看到:Pull过程也很顺利。

在Private Registry2中查看或检索Repository或images, 将不能用docker search :

$ docker search 10.10.105.71:5000/tonybai/busybox/
Error response from daemon: Unexpected status code 404

但通过v2版本的API,我们可以实现相同目的:

$curl  http://10.10.105.71:5000/v2/_catalog
{"repositories":["tonybai/busybox"]}

$ curl  http://10.10.105.71:5000/v2/tonybai/busybox/tags/list
{"name":"tonybai/busybox","tags":["latest"]}

在其他主机上,我们尝试pull busybox:

10.10.105.72:

$docker pull 10.10.105.71:5000/tonybai/busybox
Using default tag: latest
Error response from daemon: unable to ping registry endpoint https://10.10.105.71:5000/v0/
v2 ping attempt failed with error: Get https://10.10.105.71:5000/v2/: tls: oversized record received with length 20527
 v1 ping attempt failed with error: Get https://10.10.105.71:5000/v1/_ping: tls: oversized record received with length 20527

我们发现依旧不能pull和push!在Registry手册中讲到,如果采用insecure registry的模式,那么所有与Registry交互的主机上的Docker Daemon都要配置:–insecure-registry选项。

我们按照上面的配置方法,修改105.72上的/etc/default/docker,重启Docker daemon,再执行pull/push就会得到正确的结果:

$ sudo vi /etc/default/docker
$ sudo service docker restart
docker stop/waiting
docker start/running, process 10614
$ docker pull 10.10.105.71:5000/tonybai/busybox
Using default tag: latest
latest: Pulling from tonybai/busybox
5506dda26018: Pull complete
65e4158d9625: Pull complete
Digest: sha256:800f2d4558acd67f52262fbe170c9fc2e67efaa6f230a74b41b555e6fcca2892
Status: Downloaded newer image for 10.10.105.71:5000/tonybai/busybox:latest

$ docker images
REPOSITORY                          TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
ubuntu                              14.04               36248ae4a9ac        8 days ago          187.9 MB
10.10.105.71:5000/tonybai/ubuntu    14.04               36248ae4a9ac        8 days ago          187.9 MB
10.10.105.71:5000/tonybai/busybox   latest              65e4158d9625        9 days ago          1.114 MB

$ docker push 10.10.105.71:5000/tonybai/ubuntu
The push refers to a repository [10.10.105.71:5000/tonybai/ubuntu] (len: 1)
36248ae4a9ac: Pushed
8ea5373bf5a6: Pushed
2e0188208e83: Pushed
e3c70beaa378: Pushed
14.04: digest: sha256:72e56686cb9fb38438f0fd68fecf02ef592ce2ef7069bbf97802d959d568c5cc size: 6781

四、Secure Registry

Docker官方是推荐你采用Secure Registry的工作模式的,即transport采用tls。这样我们就需要为Registry配置tls所需的key和crt文件了。

我们首先清理一下环境,将上面的Insecure Registry停掉并rm掉;将各台主机上Docker Daemon的DOCKER_OPTS配置中的–insecure-registry去掉,并重启Docker Daemon。

如果你拥有一个域名,域名下主机提供Registry服务,并且你拥有某知名CA签署的证书文件,那么你可以建立起一个Secure Registry。不过我这里没有现成的证书,只能使用自签署的证书。严格来讲,使用自签署的证书在Docker官方眼中依旧属于Insecure,不过这里只是借助自签署的证书来说明一下Secure Registry的部署步骤罢了。

1、制作自签署证书

如果你有知名CA签署的证书,那么这步可直接忽略。

$ openssl req -newkey rsa:2048 -nodes -sha256 -keyout certs/domain.key -x509 -days 365 -out certs/domain.crt
Generating a 2048 bit RSA private key
..............+++
............................................+++
writing new private key to 'certs/domain.key'
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:CN
State or Province Name (full name) [Some-State]:Liaoning
Locality Name (eg, city) []:shenyang
Organization Name (eg, company) [Internet Widgits Pty Ltd]:foo
Organizational Unit Name (eg, section) []:bar
Common Name (e.g. server FQDN or YOUR name) []:mydockerhub.com
Email Address []:bigwhite.cn@gmail.com

2、启动Secure Registry

启动带证书的Registry:

$ docker run -d -p 5000:5000 --restart=always --name registry \
  -v `pwd`/data:/var/lib/registry \
  -v `pwd`/certs:/certs \
  -e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/domain.crt \
  -e REGISTRY_HTTP_TLS_KEY=/certs/domain.key \
  registry:2
35e8ce77dd455f2bd50854e4581cd52be8a137f4aaea717239b6d676c5ea5777

由于证书的CN是mydockerhub.com,我们需要修改一下/etc/hosts文件:

10.10.105.71 mydockerhub.com

重新为busybox制作一个tag:

$docker tag busybox:latest mydockerhub.com:5000/tonybai/busybox:latest

Push到Registry:

$ docker push mydockerhub.com:5000/tonybai/busybox
The push refers to a repository [mydockerhub.com:5000/tonybai/busybox] (len: 1)
unable to ping registry endpoint https://mydockerhub.com:5000/v0/
v2 ping attempt failed with error: Get https://mydockerhub.com:5000/v2/: x509: certificate signed by unknown authority
 v1 ping attempt failed with error: Get https://mydockerhub.com:5000/v1/_ping: x509: certificate signed by unknown authority

push失败了!从错误日志来看,docker client认为server传输过来的证书的签署方是一个unknown authority(未知的CA),因此验证失败。我们需要让docker client安装我们的CA证书:

$ sudo mkdir -p /etc/docker/certs.d/mydockerhub.com:5000
$ sudo cp certs/domain.crt /etc/docker/certs.d/mydockerhub.com:5000/ca.crt
$ sudo service docker restart //安装证书后,重启Docker Daemon

再执行Push,我们看到了成功的输出日志。由于data目录下之前已经被push了tonybai/busybox repository,因此提示“已存在”:

$docker push mydockerhub.com:5000/tonybai/busybox
The push refers to a repository [mydockerhub.com:5000/tonybai/busybox] (len: 1)
65e4158d9625: Image already exists
5506dda26018: Image already exists
latest: digest: sha256:800f2d4558acd67f52262fbe170c9fc2e67efaa6f230a74b41b555e6fcca2892 size: 2739

3、外部访问Registry

我们换其他机器试试访问这个secure registry。根据之前的要求,我们照猫画虎的修改一下hosts文件,安装ca.cert,去除–insecure-registry选项,并重启Docker daemon。之后尝试从registry pull image:

$ docker pull mydockerhub.com:5000/tonybai/busybox
Using default tag: latest
latest: Pulling from tonybai/busybox

Digest: sha256:800f2d4558acd67f52262fbe170c9fc2e67efaa6f230a74b41b555e6fcca2892
Status: Downloaded newer image for mydockerhub.com:5000/tonybai/busybox:latest

$ docker images
REPOSITORY                             TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
10.10.105.71:5000/tonybai/ubuntu       14.04               36248ae4a9ac        9 days ago          187.9 MB
ubuntu                                 14.04               36248ae4a9ac        9 days ago          187.9 MB
10.10.105.71:5000/tonybai/busybox      latest              65e4158d9625        9 days ago          1.114 MB
mydockerhub.com:5000/tonybai/busybox   latest              65e4158d9625        9 days ago          1.114 MB

这样来看,如果使用自签署的证书,那么所有要与Registry交互的Docker主机都需要安装mydockerhub.com的ca.crt(domain.crt)。但如果你使用知名CA,这一步也就可以忽略。

五、Registry的鉴权管理

Registry提供了一种基础的鉴权方式。我们通过下面步骤即可为Registry加上基础鉴权:

在Register server上,为Registry增加foo用户,密码foo123:(之前需要停掉已有的Registry,并删除之)

//生成鉴权密码文件
$ mkdir auth
$ docker run --entrypoint htpasswd registry:2 -Bbn foo foo123  > auth/htpasswd
$ ls auth
htpasswd

//启动带鉴权功能的Registry:
$ docker run -d -p 5000:5000 --restart=always --name registry \
   -v `pwd`/auth:/auth \
   -e "REGISTRY_AUTH=htpasswd" \
   -e "REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm" \
   -e REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd \
   -v `pwd`/data:/var/lib/registry \
   -v `pwd`/certs:/certs \
   -e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/domain.crt \
   -e REGISTRY_HTTP_TLS_KEY=/certs/domain.key \
   registry:2
199ad0b3591fb9613b21b1c96f017267f3c39661a7025d30df636c6805e7ab50

在105.72上,我们尝试push image到Registry:

$ docker push mydockerhub.com:5000/tonybai/busybox
The push refers to a repository [mydockerhub.com:5000/tonybai/busybox] (len: 1)
65e4158d9625: Image push failed
Head https://mydockerhub.com:5000/v2/tonybai/busybox/blobs/sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4: no basic auth credentials

错误信息提示:鉴权失败。

在72上执行docker login:

$docker login mydockerhub.com:5000
Username: foo
Password:
Email: bigwhite.cn@gmail.com
WARNING: login credentials saved in /home/baiming/.docker/config.json
Login Succeeded

login成功后,再行Push:

$ docker push mydockerhub.com:5000/tonybai/busybox
The push refers to a repository [mydockerhub.com:5000/tonybai/busybox] (len: 1)
65e4158d9625: Image already exists
5506dda26018: Image already exists
latest: digest: sha256:800f2d4558acd67f52262fbe170c9fc2e67efaa6f230a74b41b555e6fcca2892 size: 2739

Push ok!

六、Registry中images的管理

前面提到过,通过V2版Rest API可以查询Repository和images:

$ curl --cacert domain.crt  --basic --user foo:foo123 https://mydockerhub.com:5000/v2/_catalog
{"repositories":["tonybai/busybox","tonybai/ubuntu"]}

但如果要删除Registry中的Repository或某个tag的Image,目前v2还不支持,原因见 Registry的roadmap中的说明 。

不过如果你的Registry的存储引擎使用的是本地盘,倒是有一些第三方脚本可供使用,比如:delete-docker-registry-image 。

七、小结

Registry2发布不到1年,目前还有许多问题待解决,就比如delete image的问题,相信在2.4以及后续版本这些问题会被逐个解决掉或能找到一个相对理想的方案。

posted @ 2016-10-25 14:24 小马歌 阅读(382) | 评论 (0)编辑 收藏
 
from:http://www.infoq.com/cn/articles/centos7-practical-kubernetes-deployment

. 前言

上一节我们阐述了Kubernetes的系统架构,让大家对Kubernetes有一定的初步了解,但是就如何使用Kubernetes, 也许大家还不知如何下手。本文作者将带领大家如何在本地部署、配置Kubernetes集群网络环境以及通过实例演示跨机器服务间的通信,主要包括如下内容:

  • 部署环境介绍
  • Kubernetes集群逻辑架构
  • 部署Open vSwitch、Kubernetes、Etcd组件
  • 演示Kubernetes管理容器

2. 部署环境

  • VMware Workstation:10.0.3
  • VMware Workstation网络模式:NAT
  • 操作系统信息:CentOS 7 64位
  • Open vSwitch版本信息:2.3.0
  • Kubernetes版本信息:0.5.2
  • Etcd版本信息:0.4.6
  • Docker版本信息:1.3.1
  • 服务器信息:

            | Role      | Hostname   | IP Address  | 	|:---------:|:----------:|:----------: | 	|APIServer  |kubernetes  |192.168.230.3| 	|Minion     | minion1    |192.168.230.4| 	|Minion     | minion2    |192.168.230.5|

3. Kubernetes集群逻辑架构

在详细介绍部署Kubernetes集群前,先给大家展示下集群的逻辑架构。从下图可知,整个系统分为两部分,第一部分是Kubernetes APIServer,是整个系统的核心,承担集群中所有容器的管理工作;第二部分是minion,运行Container Daemon,是所有容器栖息之地,同时在minion上运行Open vSwitch程序,通过GRE Tunnel负责minion之间Pod的网络通信工作。

4. 部署Open vSwitch、Kubernetes、Etcd组件

4.1 安装Open vSwitch及配置GRE

为了解决跨minion之间Pod的通信问题,我们在每个minion上安装Open vSwtich,并使用GRE或者VxLAN使得跨机器之间Pod能相互通信,本文使用GRE,而VxLAN通常用在需要隔离的大规模网络中。对于Open vSwitch的具体安装步骤,可参考这篇博客,我们在这里就不再详细介绍安装步骤了。安装完Open vSwitch后,接下来便建立minion1和minion2之间的隧道。首先在minion1和minion2上建立OVS Bridge,

[root@minion1 ~]# ovs-vsctl add-br obr0 

接下来建立gre,并将新建的gre0添加到obr0,在minion1上执行如下命令,

[root@minion1 ~]# ovs-vsctl add-port obr0 gre0 -- set Interface gre0 type=gre options:remote_ip=192.168.230.5 

在minion2上执行,

[root@minion2 ~]# ovs-vsctl add-port obr0 gre0 -- set Interface gre0 type=gre options:remote_ip=192.168.230.4 

至此,minion1和minion2之间的隧道已经建立。然后我们在minion1和minion2上创建Linux网桥kbr0替代Docker默认的docker0(我们假设minion1和minion2都已安装Docker),设置minion1的kbr0的地址为172.17.1.1/24, minion2的kbr0的地址为172.17.2.1/24,并添加obr0为kbr0的接口,以下命令在minion1和minion2上执行。

[root@minion1 ~]# brctl addbr kbr0               //创建linux bridge [root@minion1 ~]# brctl addif kbr0 obr0          //添加obr0为kbr0的接口 [root@minion1 ~]# ip link set dev docker0 down   //设置docker0为down状态 [root@minion1 ~]# ip link del dev docker0        //删除docker0 

为了使新建的kbr0在每次系统重启后任然有效,我们在/etc/sysconfig/network-scripts/目录下新建minion1的ifcfg-kbr0如下:

DEVICE=kbr0 ONBOOT=yes BOOTPROTO=static IPADDR=172.17.1.1 NETMASK=255.255.255.0 GATEWAY=172.17.1.0 USERCTL=no TYPE=Bridge IPV6INIT=no 

同样在minion2上新建ifcfg-kbr0,只需修改ipaddr为172.17.2.1和gateway为172.17.2.0即可,然后执行systemctl restart network重启系统网络服务,你能在minion1和minion2上发现kbr0都设置了相应的IP地址。为了验证我们创建的隧道是否能通信,我们在minion1和minion2上相互ping对方kbr0的IP地址,从下面的结果发现是不通的,经查找这是因为在minion1和minion2上缺少访问172.17.1.1和172.17.2.1的路由,因此我们需要添加路由保证彼此之间能通信。

[root@minion1 network-scripts]# ping 172.17.2.1 PING 172.17.2.1 (172.17.2.1) 56(84) bytes of data. ^C --- 172.17.2.1 ping statistics --- 2 packets transmitted, 0 received, 100% packet loss, time 1000ms  [root@minion2 ~]#  ping 172.17.1.1 PING 172.17.1.1 (172.17.1.1) 56(84) bytes of data. ^C --- 172.17.1.1 ping statistics --- 2 packets transmitted, 0 received, 100% packet loss, time 1000ms 

由于通过ip route add添加的路由会在下次系统重启后失效,为此我们在/etc/sysconfig/network-scripts目录下新建一个文件route-eth0存储路由,这里需要注意的是route-eth0和ifcfg-eth0的黑体部分必须保持一致,否则不能工作,这样添加的路由在下次重启后不会失效。为了保证两台minion的kbr0能相互通信,我们在minion1的route-eth0里添加路由172.17.2.0/24 via 192.168.230.5 dev eno16777736,eno16777736是minion1的网卡,同样在minion2的route-eth0里添加路由172.17.1.0/24 via 192.168.230.4 dev eno16777736。重启网络服务后再次验证,彼此kbr0的地址可以ping通,如:

[root@minion2 network-scripts]# ping 172.17.1.1 PING 172.17.1.1 (172.17.1.1) 56(84) bytes of data. 64 bytes from 172.17.1.1: icmp_seq=1 ttl=64 time=2.49 ms 64 bytes from 172.17.1.1: icmp_seq=2 ttl=64 time=0.512 ms ^C --- 172.17.1.1 ping statistics --- 2 packets transmitted, 2 received, 0% packet loss, time 1002ms rtt min/avg/max/mdev = 0.512/1.505/2.498/0.993 ms 

到现在我们已经建立了两minion之间的隧道,而且能正确的工作。下面我们将介绍如何安装Kubernetes APIServer及kubelet、proxy等服务。

4.2 安装Kubernetes APIServer

在安装APIServer之前,我们先下载Kubernetes及Etcd,做一些准备工作。在kubernetes上的具体操作如下:

[root@kubernetes ~]# mkdir /tmp/kubernetes [root@kubernetes ~]# cd /tmp/kubernetes/ [root@kubernetes kubernetes]# wget https://github.com/GoogleCloudPlatform/kubernetes/releases/download/v0.5.2/kubernetes.tar.gz [root@kubernetes kubernetes]# wget https://github.com/coreos/etcd/releases/download/v0.4.6/etcd-v0.4.6-linux-amd64.tar.gz 

然后解压下载的kubernetes和etcd包,并在kubernetes、minion1、minion2上创建目录/opt/kubernetes/bin,

[root@kubernetes kubernetes]# mkdir -p /opt/kubernetes/bin [root@kubernetes kubernetes]# tar xf kubernetes.tar.gz [root@kubernetes kubernetes]# tar xf etcd-v0.4.6-linux-amd64.tar.gz [root@kubernetes kubernetes]# cd ~/kubernetes/server [root@kubernetes server]# tar xf kubernetes-server-linux-amd64.tar.gz [root@kubernetes kubernetes]# /tmp/kubernetes/kubernetes/server/kubernetes/server/bin 

复制kube-apiserver,kube-controller-manager,kube-scheduler,kubecfg到kubernetes的/opt/kubernetes/bin目录下,而kubelet,kube-proxy则复制到minion1和minion2的/opt/kubernetes/bin,并确保都是可执行的。

[root@kubernetes amd64]# cp kube-apiserver kube-controller-manager kubecfg kube-scheduler /opt/kubernetes/bin [root@kubernetes amd64]# scp kube-proxy kubelet root@192.168.230.4:/opt/kubernetes/bin [root@kubernetes amd64]# scp kube-proxy kubelet root@192.168.230.5:/opt/kubernetes/bin 

为了简单我们只部署一台etcd服务器,如果需要部署etcd的集群,请参考官方文档,在本文中将其跟Kubernetes APIServer部署同一台机器上,而且将etcd放置在/opt/kubernetes/bin下,etcdctl跟ectd同一目录。

[root@kubernetes kubernetes]# cd /tmp/kubernetes/etcd-v0.4.6-linux-amd64 [root@kubernetes etcd-v0.4.6-linux-amd64]# cp etcd etcdctl /opt/kubernetes/bin 

需注意的是kubernetes和minion上/opt/kubernetes/bin目录下的文件都必须是可执行的。到目前,我们准备工作已经差不多,现在开始给apiserver,controller-manager,scheduler,etcd配置unit文件。首先我们用如下脚本etcd.sh配置etcd的unit文件,

#!/bin/sh  ETCD_PEER_ADDR=192.168.230.3:7001 ETCD_ADDR=192.168.230.3:4001 ETCD_DATA_DIR=/var/lib/etcd ETCD_NAME=kubernetes  ! test -d $ETCD_DATA_DIR && mkdir -p $ETCD_DATA_DIR cat <<EOF >/usr/lib/systemd/system/etcd.service [Unit] Description=Etcd Server  [Service] ExecStart=/opt/kubernetes/bin/etcd \\     -peer-addr=$ETCD_PEER_ADDR \\     -addr=$ETCD_ADDR \\     -data-dir=$ETCD_DATA_DIR \\     -name=$ETCD_NAME \\     -bind-addr=0.0.0.0  [Install] WantedBy=multi-user.target EOF  systemctl daemon-reload systemctl enable etcd systemctl start etcd 

对剩下的apiserver,controller-manager,scheduler的unit文件配置的脚本,可以在github 上GetStartingKubernetes找到,在此就不一一列举。运行相应的脚本后,在APIServer上etcd, apiserver, controller-manager, scheduler服务就能正常运行。

4.3 安装Kubernetes Kubelet及Proxy

根据Kubernetes的设计架构,需要在minion上部署docker, kubelet, kube-proxy,在4.2节部署APIServer时,我们已经将kubelet和kube-proxy已经分发到两minion上,所以只需配置docker,kubelet,proxy的unit文件,然后启动服务就即可,具体配置见GetStartingKubernetes

5. 演示Kubernetes管理容器

为了方便,我们使用Kubernetes提供的例子Guestbook来演示Kubernetes管理跨机器运行的容器,下面我们根据Guestbook的步骤创建容器及服务。在下面的过程中如果是第一次操作,可能会有一定的等待时间,状态处于pending,这是因为第一次下载images需要一段时间。

5.1 创建redis-master Pod和redis-master服务

[root@kubernetes ~]# cd /tmp/kubernetes/kubernetes/examples/guestbook [root@kubernetes guestbook]# kubecfg -h http://192.168.230.3:8080 -c redis-master.json create pods [root@kubernetes guestbook]# kubecfg -h http://192.168.230.3:8080 -c redis-master-service.json create services 

完成上面的操作后,我们可以看到如下redis-master Pod被调度到192.168.230.4。

[root@kubernetes guestbook]# kubecfg -h http://192.168.230.3:8080 list pods Name                                   Image(s)                   Host                Labels                                       Status ----------                             ----------                 ----------          ----------                                   ---------- redis-master                           dockerfile/redis           192.168.230.4/      name=redis-master                            Running 

但除了发现redis-master的服务之外,还有两个Kubernetes系统默认的服务kubernetes-ro和kubernetes。而且我们可以看到每个服务都有一个服务IP及相应的端口,对于服务IP,是一个虚拟地址,根据apiserver的portal_net选项设置的CIDR表示的IP地址段来选取,在我们的集群中设置为10.10.10.0/24。为此每新创建一个服务,apiserver都会在这个地址段中随机选择一个IP作为该服务的IP地址,而端口是事先确定的。对redis-master服务,其服务地址为10.10.10.206,端口为6379。

[root@kubernetes guestbook]# kubecfg -h http://192.168.230.3:8080 list services Name                Labels              Selector                                  IP                  Port ----------          ----------          ----------                                ----------          ---------- kubernetes-ro                           component=apiserver,provider=kubernetes   10.10.10.207        80 redis-master        name=redis-master   name=redis-master                         10.10.10.206        6379 kubernetes                              component=apiserver,provider=kubernetes   10.10.10.161        443 

5.2 创建redis-slave Pod和redis-slave服务

[root@kubernetes guestbook]# kubecfg -h http://192.168.230.3:8080 -c redis-slave-controller.json create replicationControllers [root@kubernetes guestbook]# kubecfg -h http://192.168.230.3:8080 -c redis-slave-service.json create services 

然后通过list命令可知新建的redis-slave Pod根据调度算法调度到两台minion上,服务IP为10.10.10.92,端口为6379

[root@kubernetes guestbook]# kubecfg -h http://192.168.230.3:8080 list pods Name                                   Image(s)                   Host                Labels                                       Status ----------                             ----------                 ----------          ----------                                   ---------- redis-master                           dockerfile/redis           192.168.230.4/      name=redis-master                            Running 8c0ddbda-728c-11e4-8233-000c297db206   brendanburns/redis-slave   192.168.230.5/      name=redisslave,uses=redis-master            Running 8c0e1430-728c-11e4-8233-000c297db206   brendanburns/redis-slave   192.168.230.4/      name=redisslave,uses=redis-master            Running  [root@kubernetes guestbook]# kubecfg -h http://192.168.230.3:8080 list services Name                Labels              Selector                                  IP                  Port ----------          ----------          ----------                                ----------          ---------- redisslave          name=redisslave     name=redisslave                           10.10.10.92         6379 kubernetes                              component=apiserver,provider=kubernetes   10.10.10.161        443 kubernetes-ro                           component=apiserver,provider=kubernetes   10.10.10.207        80 redis-master        name=redis-master   name=redis-master                         10.10.10.206        6379 

5.3 创建Frontend Pod和Frontend服务

在创建之前修改frontend-controller.json的Replicas数量为2,这是因为我们的集群中只有2台minion,如果按照frontend-controller.json的Replicas默认值3,那会导致有2个Pod会调度到同一台minion上,产生端口冲突,有一个Pod会一直处于pending状态,不能被调度。

[root@kubernetes guestbook]# kubecfg -h http://192.168.230.3:8080 -c frontend-controller.json create replicationControllers [root@kubernetes guestbook]# kubecfg -h http://192.168.230.3:8080 -c frontend-service.json create services 

通过查看可知Frontend Pod也被调度到两台minion,服务IP为10.10.10.220,端口是80。

[root@kubernetes guestbook]# kubecfg -h http://192.168.230.3:8080 list pods Name                                   Image(s)                   Host                Labels                                       Status ----------                             ----------                 ----------          ----------                                   ---------- redis-master                           dockerfile/redis           192.168.230.4/      name=redis-master                            Running 8c0ddbda-728c-11e4-8233-000c297db206   brendanburns/redis-slave   192.168.230.5/      name=redisslave,uses=redis-master            Running 8c0e1430-728c-11e4-8233-000c297db206   brendanburns/redis-slave   192.168.230.4/      name=redisslave,uses=redis-master            Running a880b119-7295-11e4-8233-000c297db206   brendanburns/php-redis     192.168.230.4/      name=frontend,uses=redisslave,redis-master   Running a881674d-7295-11e4-8233-000c297db206   brendanburns/php-redis     192.168.230.5/      name=frontend,uses=redisslave,redis-master   Running  [root@kubernetes guestbook]# kubecfg -h http://192.168.230.3:8080 list services Name                Labels              Selector                                  IP                  Port ----------          ----------          ----------                                ----------          ---------- kubernetes-ro                           component=apiserver,provider=kubernetes   10.10.10.207        80 redis-master        name=redis-master   name=redis-master                         10.10.10.206        6379 redisslave          name=redisslave     name=redisslave                           10.10.10.92         6379 frontend            name=frontend       name=frontend                             10.10.10.220        80 kubernetes                              component=apiserver,provider=kubernetes   10.10.10.161        443 

除此之外,你可以删除Pod、Service及更新ReplicationController的Replicas数量等操作,如删除Frontend服务:

[root@kubernetes guestbook]# kubecfg -h http://192.168.230.3:8080 delete services/frontend Status ---------- Success 

还可以更新ReplicationController的Replicas的数量,下面是更新Replicas之前ReplicationController的信息。

[root@kubernetes guestbook]# kubecfg -h http://192.168.230.3:8080 list replicationControllers Name                   Image(s)                   Selector            Replicas ----------             ----------                 ----------          ---------- redisSlaveController   brendanburns/redis-slave   name=redisslave     2 frontendController     brendanburns/php-redis     name=frontend       2 

现在我们想把frontendController的Replicas更新为1,则这行如下命令,然后再通过上面的命令查看frontendController信息,发现Replicas已变为1。

[root@kubernetes guestbook]# kubecfg -h http://192.168.230.3:8080 resize frontendController 1  [root@kubernetes guestbook]# kubecfg -h http://192.168.230.3:8080 list replicationControllers Name                   Image(s)                   Selector            Replicas ----------             ----------                 ----------          ---------- redisSlaveController   brendanburns/redis-slave   name=redisslave     2 frontendController     brendanburns/php-redis     name=frontend       1 

5.4 演示跨机器服务通信

完成上面的操作后,我们来看当前Kubernetes集群中运行着的Pod信息。

[root@kubernetes guestbook]# kubecfg -h http://192.168.230.3:8080 list pods Name                                   Image(s)                   Host                Labels                                       Status ----------                             ----------                 ----------          ----------                                   ---------- a881674d-7295-11e4-8233-000c297db206   brendanburns/php-redis     192.168.230.5/      name=frontend,uses=redisslave,redis-master   Running redis-master                           dockerfile/redis           192.168.230.4/      name=redis-master                            Running 8c0ddbda-728c-11e4-8233-000c297db206   brendanburns/redis-slave   192.168.230.5/      name=redisslave,uses=redis-master            Running 8c0e1430-728c-11e4-8233-000c297db206   brendanburns/redis-slave   192.168.230.4/      name=redisslave,uses=redis-master            Running 

通过上面的结果可知当前提供前端服务的PHP和提供数据存储的后端服务Redis master的Pod分别运行在192.168.230.5和192.168.230.4上,即容器运行在不同主机上,还有Redis slave也运行在两台不同的主机上,它会从Redis master同步前端写入Redis master的数据。下面我们从两方面验证Kubernetes能提供跨机器间容器的通信:

  • 在浏览器打开http://${IPAddress}:8000,IPAddress为PHP容器运行的minion的IP地址,其暴漏的端口为8000,这里IP_Address为192.168.230.5。打开浏览器会显示如下信息:

    你可以输入信息并提交,如"Hello Kubernetes"、"Container",然后Submit按钮下方会显示你输入的信息。

    由于前端PHP容器和后端Redis master容器分别在两台minion上,因此PHP在访问Redis master服务时一定得跨机器通信,可见Kubernetes的实现方式避免了用link只能在同一主机上实现容器间通信的缺陷,对于Kubernetes跨机器通信的实现方法,以后我会详细介绍。

     

  • 从上面的结果,可得知已经实现了跨机器的通信,现在我们从后端数据层验证不同机器容器间的通信。根据上面的输出结果发现Redis slave和Redis master分别调度到两台不同的minion上,在192.168.230.4主机上执行docker exec -ti c41711cc8971 /bin/sh,c41711cc8971是Redis master的容器ID,进入容器后通过redis-cli命令查看从浏览器输入的信息如下:

    如果我们在192.168.230.5上运行的Redis slave容器里查到跟Redis master容器里相同的信息,那说明Redis master和Redis slave之间的数据同步正常工作,下面是从192.168.230.5上运行的Redis slave容器查询到的信息:

    由此可见Redis master和Redis slave之间数据同步正常,OVS GRE隧道技术使得跨机器间容器正常通信。

6. 结论

本文主要介绍如何在本地环境部署Kubernetes集群和演示如何通过Kubernetes管理集群中运行的容器,并通过OVS管理集群不同minion的Pod之间的网络通信。接下来会对Kubernetes各个组件源码进行详细分析,阐述Kubernetes的工作原理。

7. 个人简介

杨章显,现就职于Cisco,主要从事WebEx SaaS服务运维,系统性能分析等工作。特别关注云计算,自动化运维,部署等技术,尤其是Go、OpenvSwitch、Docker及其生态圈技术,如Kubernetes、Flocker等Docker相关开源项目。Email: yangzhangxian@gmail.com

8. 参考资料

  1. https://n40lab.wordpress.com/2014/09/04/openvswitch-2-3-0-lts-and-centos-7/
  2. https://github.com/GoogleCloudPlatform/kubernetes/tree/master/examples/guestbook

感谢郭蕾对本文的策划和审校。

posted @ 2016-10-25 14:23 小马歌 阅读(218) | 评论 (0)编辑 收藏
 
from:http://blog.csdn.net/linuxgo/article/details/52121125

加快Kubernetes编译速度

除了Linux/amd64,默认还会为其他平台做交叉编译。为了减少编译时间,可以修改hack/lib/golang.sh,把KUBE_SERVER_PLATFORMS, KUBE_CLIENT_PLATFORMS和KUBE_TEST_PLATFORMS中除linux/amd64以外的其他平台注释掉

gcr.io无法访问

Kubernetes在创建Pod的时候,需要从gcr.io下载一个helper镜像(目前是 gcr.io/google_containers/pause-amd64:3.0 )。

但是目前国内无法访问gcr.io,这个问题会导致无法下载该镜像,然后Pod一直处于ContainerCreating状态。

解决办法

1) 在可以访问gcr.io的地方

docker pull gcr.io/google_containers/pause-amd64:3.0

传到私有docker registry

docker tag gcr.io/google_containers/pause-amd64:3.0 k8s-docker.mydomain.com/google_containers/pause-amd64:3.0

docker push k8s-docker.mydomain.com/google_containers/pause-amd64:3.0

2) 在所有的k8s节点

docker pull k8s-docker.mydomain.com/google_containers/pause-amd64:3.0
docker tag k8s-docker.mydomain.com/google_containers/pause-amd64:3.0 gcr.io/google_containers/pause-amd64:3.0

Note

不通过私有registry中转,而是使用Docker save/load应该也可以,只是要把save导出的文件复制到所有节点.

如何从集群外访问Service和Pod

这里说的集群外是指K8s集群以外的主机,比如使用nginx/HAProxy搭建的负载均衡主机。这些主机跟K8s集群部署在一起,到K8s网络可达。

对于不是部署在GCE以及AWS等云平台的K8s,我们一般需要自己搭建负载均衡,然后分发请求到到Service。

使用NodePort方式发布服务,那么负载均衡主机上不需要额外配置;使用ClusterIP方式,为了能够访问Service的ClusterIP, 需要在这些主机上安装Flanneld和kube-proxy

posted @ 2016-10-25 14:23 小马歌 阅读(1711) | 评论 (0)编辑 收藏
 
优化CMS(concurrent garbage collection)
   使用CMS,old代的垃圾回收执行线程会和应用程序的线程最大程度的并发执行。这个提供了一个机会来减少最坏延迟的频率和最坏延迟的时间消耗。CMS没有执行压缩,所以可以避免old代空间的stop-the-world压缩(会让整个应用暂停运行)。

   优化CMS的目标就是避开stop-the-world压缩垃圾回收,然而,这个说比做起来容易。在一些的部署情况下,这个是不可避免的,尤其是当内存分配受限的时候。

   在一些特殊的情况下,CMS比其他类型的垃圾回收需要更多优化,更需要优化young代的空间,以及潜在的优化该什么时候初始化old代的垃圾回收循环。

   当从吞吐量垃圾回收器(Throughput)迁移到CMS的时候,有可能会获得更慢的MinorGC,由于对象从young代转移到old会更慢 ,由于CMS在old代里面分配的内存是一个不连续的列表,相反,吞吐量垃圾回收器只是在本地线程的分配缓存里面指定一个指针。另外,由于old代的垃圾回收线程和应用的线程是尽可能的并发运行的,所以吞吐量会更小一些。然而,最坏的延迟的频率会少很多,由于在old代的不可获取的对象能够在应用运行的过程被垃圾回收,这样可以避免old代的空间溢出。

   使用CMS,如果old代能够使用的空间有限,单线程的stop-the-world压缩垃圾回收会执行。这种情况下,FullGC的时间会比吞吐量垃圾回收器的FullGC时间还要长,导致的结果是,CMS的绝对最差延迟会比吞吐量垃圾回收器的最差延迟严重很多。old代的空间溢出以及运行了stop-the-world垃圾回收必须被应用负责人重视,由于在响应上会有更长的中断。因此,不要让old代运行得溢出就非常重要了。对于从吞吐量垃圾回收器迁移到CMS的一个比较重要的建议就是提升old代20%到30%的容量。

   在优化CMS的时候有几个注意点,首先,对象从young代转移到old代的转移率。其次,CMS重新分配内存的概率。再次,CMS回收对象时候产生的old代的分隔,这个会在可获得的对象中间产生一些空隙,从而导致了分隔空间。

   碎片可以被下面的几种方法寻址。第一办法是压缩old代,压缩old代空间是通过stop-the-world垃圾回收压缩完成的,就像前面所说的那样,stop-the-world垃圾回收会执行很长时间,会严重影响应用的响应时间,应该避开。第二种办法是,对碎片编址,提高old代的空间,这个办法不能完全解决碎片的问题的,但是可以延迟old代压缩的时间。通常来讲,old代越多内存,由于碎片导致需要执行的压缩的时间久越长。努力把old的空间增大的目标是在应用的生命周期中,避免堆碎片导致stop-the-world压缩垃圾回收,换句话说,应用GC最大内存原则。另外一种处理碎片的办法是减少对象从young代移动到old的概率,就是减少MinorGC,应用MinorGC回收原则。

   任期阀值(tenuring threshold)控制了对象该什么时候从young代移动到old代。任期阀值会在后面详细的介绍,它是HotSpot VM基于young代的占用空间来计算的,尤其是survivor(幸存者)空间的占用量。下面详细介绍一下survivor空间以及讨论任期阀值。

survivor空间

   survivor空间是young代的一部分,如下图所示。young代被分成了一个eden区域和两个survivor空间。
   
   两个survivor空间的中一个被标记为“from”,另外一个标记为“to”。新的Java对象被分配到Eden空间。比如说,下面的一条语句:
   
[java] view plain copy
  1. <span style="font-size:14px;">   Map<String,String> map = new HashMap<String,String>();</span>  

   一个新的HashMap对象会被放到eden空间,当eden空间满了的时候,MinorGC就会执行,任何存活的对象,都从eden空间复制到“to” survivor空间,任何在“from” survivor空间里面的存活对象也会被复制到“to” survivor。MinorGC结束的时候,eden空间和“from” survivor空间都是空的,“to” survivor空间里面存储存活的对象,然后,在下次MinorGC的时候,两个survivor空间交换他们的标签,现在是空的“from” survivor标记成为“to”,“to” survivor标记为“from”。因此,在MinorGC结束的时候,eden空间是空的,两个survivor空间中的一个是空的。

   在MinorGC过程,如果“to” survivor空间不够大,不能够存储所有的从eden空间和from suvivor空间复制过来活动对象,溢出的对象会被复制到old代。溢出迁移到old代,会导致old代的空间快速增长,会导致stop-the-world压缩垃圾回收,所以,这里要使用MinorGC回收原则。

   避免survivor空间溢出可以通过指定survivor空间的大小来实现,以使得survivor有足够的空间来让对象存活足够的岁数。高效的岁数控制会导致只有长时间存活的对象转移到old代空间。

   岁数控制是指一个对象保持在young代里面直到无法获取,所以让old代只是存储长时间保存的对象。

   survivor的空间可以大小设置可以用HotSpot命令行参数:-XX:SurvivorRatio=<ratio>

   <ratio>必须是以一个大于0的值,-XX:SurvivorRatio=<ratio>表示了每一个survivor的空间和eden空间的比值。下面这个公式可以用来计算survivor空间的大小

   
[html] view plain copy
  1. survivor spave size = -Xmn<value>/(-XX:SurvivorRatio=<ratio>+2)  

   这里有一个+2的理由是有两个survivor空间,是一个调节参数。ratio设置的越大,survivor的空间越小。为了说明这个问题,假设young代的大小是-Xmn512m而且-XX:SurvivorRatio=6.那么,young代有两个survivor空间且空间大小是64M,那么eden空间的大小是384M。

   同样假如young代的大小是512M,但是修改-XX:SurvivorRatio=2,这样的配置会使得每一个survivor空间的大小是128m而eden空间的大小是256M。

   对于一个给定大小young代空间大小,减小ratio参数增加survivor空间的大小而且减少eden空间的大小。反之,增加ratio会导致survivor空间减少而且eden空间增大。减少eden空间会导致MinorGC更加频繁,相反,增加eden空间的大小会导致更小的MinorGC,越多的MinorGC,对象的岁数增长得越快。

   为了更好的优化survivor空间的大小和完善young代空间的大小,需要监控任期阀值,任期阀值决定了对象会再young代保存多久。怎么样来监控和优化任期阀值将在下一节中介绍。
   
任期阀值

   “任期”是转移的代名词,换句话说,任期阀值意味着对象移动到old代空间里面。HotSpot VM每次MinorGC的时候都会计算任期,以决定对象是否需要移动到old代去。任期阀值就是对象的岁数。对象的岁数是指他存活过的MinorGC次数。当一个对象被分配的时候,它的岁数是0。在下次MinorGC的时候之后,如果对象还是存活在young代里面,它的岁数就是1。如果再经历过一次MinorGC,它的岁数变成2,依此类推。在young代里面的岁数超过HotSpot VM指定阀值的对象会被移动到old代里面。换句话说,任期阀值决定对象在young代里面保存多久。

   任期阀值的计算依赖于young代里面能够存放的对象数以及MinorGC之后,“to” servivor的空间占用。HotSpot VM有一个选项-XX:MaxTenuringThreshold=<n>,可以用来指定当时对象的岁数超过<n>的时候,HotSpot VM会把对象移动到old代去。内部计算的任期阀值一定不会超过指定的最大任期阀值。最大任期阀值在可以被设定为0-15,不过在Java 5 update 5之前可以设置为1-31。

   不推荐把最大任期阀值设定成0或者超过15,这样会导致GC的低效率。

   如果HotSpot VM它无法保持目标survivor 空间的占用量,它会使用一个小于最大值的任期阀值来维持目标survivor空间的占用量,任何比这个任期阀值的大的对象都会被移动到old代。话句话说,当存活对象的量大于目标survivor空间能够接受的量的时候,溢出发生了,溢出会导致对象快速的移动到old代,导致不期望的FullGC。甚至会导致更频繁的stop-the-world压缩垃圾回收。哪些对象会被移动到old代是根据评估对象的岁数和任期阀值来确定的。因此,很有必要监控任期阀值以避免survivor空间溢出,接下来详细讨论。

监控任期阀值

   为了不被内部计算的任期阀值迷惑,我们可以使用命令选项-XX:MaxTenuringThreshod=<n>来指定最大的任期阀值。为了决定出最大的任期阀值,需要监控任期阀值的分布和对象岁数的分布,通过使用下面的选项实现

 
[html] view plain copy
  1. -XX:+PrintTenuringDistribution  

   -XX:+PrintTenuringDistribution的输出显示在survivor空间里面有效的对象的岁数情况。阅读-XX:+PrintTenuringDistribution输出的方式是观察在每一个岁数上面,对象的存活的数量,以及其增减情况,以及HotSpot VM计算的任期阀值是不是等于或者近似于设定的最大任期阀值。

   -XX:+PrintTenuringDistribution在MinorGC的时候产生任期分布信息。它可以同其他选项一同使用,比如-XX:+PrintGCDateStamps,-XX:+PrintGCTimeStamps以及-XX:+PringGCDetails。当调整survivor空间大小以获得有效的对象岁数分布,你应该使用-XX:+PrintTenuringDistribution。在生产环境中,它同样非常有用,可以用来判断stop-the-world的垃圾回收是否发生。

   下面是一个输出的例子:

   Desired survivor size 8388608 bytes, new threshold 1 (max 15) 
   - age 1: 16690480 bytes, 16690480 total

   在这里例子中,最大任期阀值被设置为15,(通过max 15表示)。内部计算出来的任期阀值是1,通过threshold 1表示。Desired survivor size 8388608 bytes表示一个survivor的空间大小。目标survivor的占有率是指目标survivor和两个survivor空间总和的比值。怎么样指定期望的survivor空间大小在后面会详细介绍。在第一行下面,会列出一个对象的岁数列表。每行会列出每一个岁数的字节数,在这个例子中,岁数是1的对象有16690480字节,而且每行后面有一个总的字节数,如果有多行输出的话,总字节数是前面的每行的累加数。后面举例说明。

   在前面的例子中,由于期望的survivor大小(8388608)比实际总共survivor字节数(16690480)小,也就是说,survivor空间溢出了,这次MinorGC会有一些对象移动到old代。这个就意味着survivor的空间太小了。另外,设定的最大任期阀值是15,但是实际上JVM使用的是1,也表明了survivor的空间太小了。

   如果发现survivor区域太小,就增大survivor的空间,下面详细介绍如何操作。
   
设定survivor空间

   当修改survivor空间的大小的时候,有一点需要记住。当修改survivor空间大小的时候,如果young代的大小不改变,那么eden空间会减小,进一步会导致更频繁的MinorGC。因此,增加survivor空间的时候,如果young代的空间大小违背了MinorGC频率的需求,eden空间的大小同需要需要增加。换句话说,当survivor空间增加的时候,young代的大小需要增加。

   如果有空间来增加MinorGC的频率,有两种选择,一是拿一些eden空间来增加survivor的空间,二是让young的空间更大一些。常规来讲,更好的选择是如果有可以使用的内存,增加young代的空间会比减少eden的空间更好一些。让eden空间大小保持恒定,MinorGC的频率不会改变,即使调整survivor空间的大小。

   使用-XX:+PrintTenuringDistribution选项,对象的总字节数和目标survivor空间占用可以用来计算survivor空间的大小。重复前面的例子:
   Desired survivor size 8388608 bytes, new threshold 1 (max 15) 
   - age 1: 16690480 bytes, 16690480 total

   存活对象的总字节数是1669048,这个并发垃圾回收器(CMS)的目标survivor默认使用50%的survivor空间。通过这个信息,我们可以知道survivor空间至少应该是33380960字节,大概是32M。这个计算让我们知道对survivor空间的预估值需要计算对象的岁数更高效以及防止溢出。为了更好的预估survivor的可用空间,你应该监控应用稳定运行情况下的任期分布,并且使用所有的额外总存活对象的字节数来作为survivor空间的大小。

   在这个例子,为了让应用计算岁数更加有效,survivor空间需要至少提升32M。前面使用的选项是:

 
[html] view plain copy
  1. -Xmx1536m -Xms1536m -Xmn512m -XX:SurvivorRatio=30  

   那么为了保持MinorGC的频率不发生变化,然后增加survivor空间的大小到32M,那么修改后的选项如下:

 
[html] view plain copy
  1. -Xmx1568m -Xms1568m -Xmn544m -XX:SurvivvorRatio=15  

   当时young代空间增加了,eden空间的大小保持大概相同,且survivor的空间大小增减了。需要注意的时候,-Xmx、-Xms、-Xmn都增加了32m。另外,-XX:SurvivvorRatio=15让每一个survivor空间的大小都是32m (544/(15+2) = 32)。

   如果存在不能增加young代空间大小的限制,那么增加survivor空间大小需要以减少eden空间的大小为代价。下面是一个增加survivor空间大小,每一个survivor空间从16m增减加到32m,那么会见减少eden的空间,从480m减少到448m(512-32-32=448,512-16-16=480)。

   
[html] view plain copy
  1. -Xms1536m -Xms1536m -Xmn1512m -XX:SurvivorRatio=14  

   再次强调,减少eden空间大小会增加MinorGC的频率。但是,对象会在young代里面保持更长的时间,由于提升survivor的空间。

   假如运行同样的应用,我们保持eden的空间不变,增加survivor空间的大小,如下面选项:

 
[html] view plain copy
  1. <span style="font-size:14px;"> -Xmx1568m -Xms1568m -Xmn544m -XX:SurvivorRatio=15</span>  

   可以产生如下的任期分布:
   Desired survivor size 16777216 bytes, new threshold 15 (max 15)
- age 1: 6115072 bytes, 6115072 total
- age 2: 286672 bytes, 6401744 total
- age 3: 115704 bytes, 6517448 total
- age 4: 95932 bytes, 6613380 total
- age 5: 89465 bytes, 6702845 total
- age 6: 88322 bytes, 6791167 total
- age 7: 88201 bytes, 6879368 total
- age 8: 88176 bytes, 6967544 total
- age 9: 88176 bytes, 7055720 total
- age 10: 88176 bytes, 7143896 total
- age 11: 88176 bytes, 7232072 total
- age 12: 88176 bytes, 7320248 total

   从任期分布的情况来看,survivor空间没有溢出,由于存活的总大小是7320248,但是预期的survivor空间大小是16777216以及任期阀值和最大任期阀值是相等的。这个表明,对象的老化速度是高效的,而且survivor空间没有溢出。

   在这个例子中,由于岁数超过3的对象很少,你可能像把最大任期阀值设置为3来测试一下,即设置选项-XX:MaxTenuringThreshhold=3,那么整个选项可以设置为:

 
[html] view plain copy
  1. -Xmx1568m -Xms1658m -Xmn544m -XX:SurvivorRatio=15 -XX:MaxTenuringThreshold=3  

   这个选项设置和之前的选项设置的权衡是,后面这个选择可以避免在MinorGC的时候不必要地把对象从“from” survivor复制到“to” survivor。在应用运行在稳定状态的情况下,观察多次MinorGC任期分布情况,看是否有对象最终移动到old代或者显示的结果还是和前面的结果类似。如果你观察得到和前面的任期分布情况相同,基本没有对象的岁数达到15,也没有survivor的空间溢出,你应该自己设置最大任期阀值以代替JVM默认的15。在这个例子中,没有长时间存活的对象,由于在他们的岁数没有到达15的时候就被垃圾回收了。这些对象在MinorGC中被回收了,而不是移动到old代里面。使用并发垃圾回收(CMS)的时候,对象从young代移动到old代最终会导致old的碎片增加,有可能导致stop-the-world压缩垃圾回收,这些都是不希望出现的。宁可选择让对象在“from” survivor和“to” survivor中复制,也不要太快的移动到old代。

   你可能需要重复数次监控任期分布、修改survivor空间大小或者重新配置young代的空间大小直到你对应用由于MinorGC引起的延迟满意为止。如果你发现MinorGC的时间太长,你可以通过减少young代的大小直到你满意为止。尽管,减少young代的大小,会导致更快地移动对象到old代,可能导致更多的碎片,如果CMS的并发垃圾回收能够跟上对象的转移率,这种情况就比不能满足应用的延迟需求更好。如果这步不能满足应用的MinorGC的延迟和频率需求,这个时候就有必要重新审视需求以及修改应用程序了。

   如果满足对MinorGC延迟的需求,包括延迟时间和延迟频率,你可以进入下一步,优化CMS垃圾回收周期的启动,下节详细介绍。
posted @ 2016-10-18 20:24 小马歌 阅读(320) | 评论 (0)编辑 收藏
 
     摘要: from:http://blog.csdn.net/fenglibing/article/details/6321453这是我公司同事的GC学习笔记,写得蛮详细的,由浅入深,循序渐进,让人一看就懂,特转到这里。一、GC特性以及各种GC的选择1、垃圾回收器的特性2、对垃圾回收器的选择2.1 连续 VS. 并行2.2 并发 VS. stop-...  阅读全文
posted @ 2016-10-18 15:37 小马歌 阅读(276) | 评论 (0)编辑 收藏
 
     摘要: from:http://www.tuicool.com/articles/RNjUfa原文  http://286.iteye.com/blog/1924947主题 JVM        -XX 参数被称为不稳定参数,之所以这么叫是因为此类参数的设置很容易引起JVM 性能上的差异,使JVM 存在极大...  阅读全文
posted @ 2016-10-18 11:18 小马歌 阅读(392) | 评论 (0)编辑 收藏
 
     摘要: from:http://www.cnblogs.com/redcreen/archive/2011/05/04/2037057.html不管是YGC还是Full GC,GC过程中都会对导致程序运行中中断,正确的选择不同的GC策略,调整JVM、GC的参数,可以极大的减少由于GC工作,而导致的程序运行中断方面的问题,进而适当的提高Java程序的工作效率。但是调整GC是以个极为复杂的过程,由于各个程序具...  阅读全文
posted @ 2016-10-14 15:06 小马歌 阅读(277) | 评论 (0)编辑 收藏
 
from:http://www.oschina.net/news/74775/open-source-database-situation

数据库作为业务的核心,是整个基础软件栈非常重要的一环。近几年的开源社区,新的思想和方案层出不穷,我将总结一下近几年一些主流的开源数据库方案,及其背后的设计思想以及适用场景。本人才疏学浅如有遗漏或者错误请见谅。本次分享聚焦于数据库即结构化数据存储 OLTP 及 NoSQL 领域,不会涉及 OLAP、对象存储以及分布式文件系统。

开源 RDBMS 与互联网的崛起

很长时间以来,关系型数据库一直是大公司的专利,市场被 Oracle / DB2 等企业数据库牢牢把持。但是随着互联网的崛起和开源社区的发展,上世纪九十年代 MySQL 1.0 的发布,标志着在关系型数据库的领域,社区终于有了可选择的方案。

MySQL

第一个介绍的单机 RDBMS 就是 MySQL。相信大多数朋友都已经对 MySQL 非常熟悉,基本上 MySQL 的成长史就是互联网的成长史。我接触的第一个 MySQL 版本是 MySQL 4.0,后来的 MySQL 5.5 更是经典——基本上所有的互联网公司都在使用。MySQL 也普及了「可插拔」引擎这一概念,即针对不同的业务场景选用不同的存储引擎,这也是 MySQL tuning 的一个重要方式。比如对于有事务需求的场景使用 InnoDB;对于并发读取的场景 MyISAM 可能比较合适;但是现在我推荐绝大多数情况还是使用 InnoDB,毕竟 MySQL 5.6 后它已经成为了官方的默认引擎。MySQL适用于几乎所有需要持久化结构化数据的场景, 大多数朋友应该都知道,我就不赘述了。

另外值得一提的是 MySQL 5.6 中引入了多线程复制和 GTID,使得故障恢复和主从的运维变得比较方便。另外,MySQL 5.7(目前处于 GA 版本) 发布了一个重大更新,主要是在读写性能和复制性能上有了长足的进步:在5.6版本中实现了 SCHEMA 级别的并行复制。不过意义不大,倒是 MariaDB 的多线程并行复制大放异彩,有不少人因为这个特性选择 MariaDB。另外,MySQL 5.7 MTS 支持两种模式,一种是和5.6一样,另一种则是基于 binlog group commit 实现的多线程复制,也就是MASTER上同时提交的 binlog 在 SLAVE 端也可以同时被 apply,实现并行复制。如果有单机数据库技术选型的朋友,基本上只需要考虑 MySQL 5.7 或者 MariaDB 就好了,而且 MySQL 5.6和5.7 由 Oracle 接手后,性能和稳定性都有了明显的提升。

PostgreSQL

PostgreSQL 的历史也非常悠久,其前身是 UCB 的 Ingres,主持这个项目的 Michael Stronebraker 于 2015 年获得图灵奖。后来该项目更名为 Post-Ingres,基于 BSD license 下开源。 1995 年几个 UCB 的学生为 Post-Ingres 开发了 SQL 的接口,正式发布了 PostgreSQL95,随后一步步在开源社区中成长起来。和 MySQL 一样,PostgreSQL 也是一个单机的关系型数据库,但是与 MySQL 方便用户过度扩展的 SQL 文法不一样的是,PostgreSQL 的 SQL 支持非常强大,不管是内置类型、JSON 支持、GIS 类型以及对于复杂查询的支持,PL/SQL 等都比 MySQL 强大得多,而且从代码质量上来看,PostgreSQL 的代码质量是优于 MySQL 的。另外,相对于MySQL 5.7以前的版本, PostgreSQL 的 SQL 优化器比 MySQL 强大很多,几乎所有稍微复杂的查询PostgreSQL 的表现都优于 MySQL。

从近几年的趋势上来看,PostgreSQL 的势头也很强劲,我认为 PostgreSQL 的不足之处在于没有 MySQL 那样强大的社区和群众基础。MySQL 经过那么多年的发展,积累了很多的运维工具和最佳实践,但是 PostgreSQL 作为后起之秀,拥有更优秀的设计和更丰富的功能。PostgreSQL 9 以后的版本也足够稳定,在做新项目技术选型的时候,是一个很好的选择。另外也有很多新的数据库项目是基于 PostgreSQL 源码的基础上进行二次开发,比如 Greenplum 等。

我认为,单机数据库的时代很快就会过去。摩尔定律带来的硬件红利总是有上限的,现代业务的数据规模、流量以及现代的数据科学对于数据库的要求,单机已经很难满足。比如,网卡磁盘 IO 和 CPU 总有瓶颈,线上敏感的业务系统可能还得承担 SPOF(单点故障) 的风险,主从复制模型在主挂掉时到底切还是不切?切了以后数据如何恢复?如果只是出现主从机器网络分区问题呢?甚至是监控环境出现网络分区问题呢?这些都是单机数据库面临的巨大挑战。所以我的观点是,无论单机性能多棒(很多令人乍舌的评测数据都是针对特定场景的优化,另外甚至有些都是本机不走网络,而大多数情况数据库出现的第一个瓶颈其实是网卡和并发连接……),随着互联网的蓬勃发展和移动互联网的出现,数据库系统迎来了第一次分布式的洗礼。

分布式时代:NoSQL 的复兴和模型简化的力量

在介绍 NoSQL 之前,我想提两个公司,一个是 Google,另一个是 Amazon。

Google

Google 应该是第一个将分布式存储技术应用到大规模生产环境的公司,同时也是在分布式系统上积累最深的公司,可以说目前工业界的分布式系统的工程实践及思想大都来源于 Google。比如 2003 年的 GFS 开创了分布式文件系统,2006 年的 Bigtable 论文开创了分布式键值系统,直接催生的就是 Hadoop 的生态;至于 2012 年发表论文的 Spanner 和 F1更是一个指明未来关系型数据库发展方向的里程碑式的项目,这个我们后续会说。

Amazon

另一个公司是 Amazon。2007 年发表的 Dynamo的论文 尝试引入了最终一致性的概念, WRN 的模型及向量时钟的应用,同时将一致性 HASH、merkle tree 等当时一些很新潮的技术整合起来,正式标志着 NoSQL 的诞生。NoSQL——对后来业界的影响非常也是很大,包括后来的 Cassandra、RiakDB、Voldemort 等数据库都是基于 Dynamo 的设计发展起来的。

新思潮

另外这个时期(2006 年前后持续至今)一个比较重要的思潮就是数据库(持久化)和缓存开始有明确的分离——我觉得这个趋势是从 memcached 开始的。随着业务的并发越来越高,对于低延迟的要求也越来越高;另外一个原因是随着内存越来越便宜,基于内存的存储方案渐渐开始普及。当然内存缓存方案也经历了一个从单机到分布式的过程,但是这个过程相比关系型数据库的进化要快得多。这是因为 NoSQL 的另外一个重要的标志——数据模型的变化——大多 NoSQL 都抛弃了关系模型,选择更简单的键值或者文档类型进行存储。数据结构和查询接口都相对简单,没有了 SQL 的包袱,实现的难度会降低很多。另外 NoSQL 的设计几乎都选择牺牲掉复杂 SQL 的支持及 ACID 事务换取弹性扩展能力,也是从当时互联网的实际情况出发:业务模型简单、爆发性增长带来的海量并发及数据总量爆炸、历史包袱小、工程师强悍,等等。其中最重要的还是业务模型相对简单。

嵌入式存储引擎

在开始介绍具体的开源的完整方案前,我想介绍一下嵌入式存储引擎们。

随着 NoSQL 的发展,不仅仅缓存和持久化存储开始细分,再往后的存储引擎也开始分化并走上前台。之前很难想象一个存储引擎独立于数据库直接对外提供服务,就像你不会直接拿着 InnoDB 或者 MyISAM甚至一个 B-tree 出来用一样(当然,bdb 这样鼎鼎大名的除外)。人们基于这些开源的存储引擎进行进一步的封装,比如加上网络协议层、加上复制机制等等,一步步构建出完整的风格各异的 NoSQL 产品。

这里我挑选几个比较著名的存储引擎介绍一下。

TC

我最早接触的是 Tokyo Cabinet(TC)。TC 相信很多人也都听说过,TC 是由日本最大的社交网站 Mixi 开发并开源的一个混合 Key-Value 存储引擎,其中包括 HASH Table 和 B+ Tree 的实现。但是这个引擎的一个缺陷是随着数据量的膨胀,性能的下降会非常明显,而且现在也基本不怎么维护了,所以入坑请慎重。与于 TC 配合使用的 Tokyo Tyrant(TT) 是一个网络库,为 TC 提供网络的接口使其变成一个数据库服务,TT + TC 应该是比较早的 NoSQL 的一个尝试。

LevelDB

在 2011 年,Google 开源了 Bigtable 的底层存储引擎: LevelDB。LevelDB 是一个使用 C++ 开发的嵌入式的 Key-Value 存储引擎,数据结构采用了 LSM-Tree,具体 LSM-Tree 的算法分析可以很容易在网上搜索到,我就不赘述了。其特点是,对于写入极其友好,LSM 的设计避免了大量的随机写入;对于特定的读也能达到不错的性能(热数据在内存中);另外 LSM-Tree 和 B-tree 一样是支持有序 Scan 的;而且 LevelDB 是出自 Jeff Dean 之手,他的事迹做分布式系统的朋友一定都知道,不知道的可以去 Google 搜一下。

LevelDB 拥有极好的写性能,线程安全,BatcTCh Write 和 Snapshot 等特性,使其很容易的在上层构建 MVCC 系统或者事务模型,这对于数据库来说非常重要。另外值得一说的是,Facebook 维护了一个活跃的 LevelDB 的分支,名为 RocksDB。RocksDB 在 LevelDB 上做了很多的改进,比如多线程 Compactor、分层自定义压缩、多 MemTable 等。另外 RocksDB 对外暴露了很多 ConfigurationConfigration ,可以根据不同业务的形态进行调优;同时 Facebook 在内部正在用 RocksDB 来实现一个全新的 MySQL 存储引擎:MyRocks,值得关注。RocksDB 的社区响应速度很快也很友好,实际上 PingCAP 也是 RocksDB 的社区贡献者。我建议新的项目如果在 LevelDB 和 RocksDB 之间纠结的话,请果断选择 RocksDB。

B-tree 家族

当然,除了 LSM-Tree 外,B-tree 的家族也还是有很多不错的引擎。首先大多数传统的单机数据库的存储引擎都选择了B+Tree,B+Tree 对磁盘的读比较友好,第三方存储引擎比较著名的纯 B+Tree 实现是 LMDB。首先 LMDB 选择在内存映像文件 (mmap) 实现 B+Tree,而且同时使用了 Copy-On-Write 实现了 MVCC 实现并发事务无锁读的能力,对于高并发读的场景比较友好;同时因为使用的是 mmap 所以拥有跨进程读取的能力。不过因为我并没有在生产环境中使用过 LMDB ,所以并不能给出 LMDB 的一些缺陷,见谅。

混合引擎

还有一部分的存储引擎选择了多种引擎混合,比如最著名的应该是 WiredTiger,大概是2014年去年被 MongoDB 收购,现在成为了 MongoDB 的默认存储引擎。WiredTiger 内部有 LSM-Tree 和 B-tree 两种实现,对外提供相同的一套接口,根据业务的情况可自由选择。另外一些特殊数据结构的存储引擎在某些特殊场合下非常抢眼,比如极高压缩比 TokuDB,采用了名为分形树的数据结构,在维持一个可接受的读写压力的情况下,能拥有 10 倍以上的压缩率。

NoSQL

说完了几个比较著名的存储引擎,我们来讲讲比较著名的 NoSQL。在我的定义中,NoSQL 是 Not Only SQL 的缩写,所以可能包含的范围有内存数据库,持久化数据库等。总之就是和单机的关系型数据库不一样的结构化数据存储系统。

我们先从缓存开始。

memcached

前面提到了 memcached 应该是第一个大规模在业界使用的缓存数据库,memcached 的实现极其简单,相当于将内存用作大的 HASH Table,只能在上面进行 get/set/ 计数器等操作,在此之上用 libevent 封装了一层网络层和文本协议(也有简单的二进制协议),虽然支持一些 CAS 的操作,但是总体上来看,还是非常简单的。但是 memcached 的内存利用率并不太高,这是这个因为 memcached 为了避免频繁申请内存导致的内存碎片的问题,采用了自己实现的 slab allocator 的方式。即内存的分配都是一块一块的,最终存储在固定长度的 chunk 上,内存最小的分配单元是 chunk,另外 libevent 的性能也并没有优化到极致。但是这些缺点并不妨碍 memcached 成为当时的开源缓存事实标准。(另外,八卦一下,memcached 的作者 Brad Fitzpatrick 现在在 Google,大家如果用 Golang 的话,Go 的官方 HTTP 包就是这哥们写的,是个很高产的工程师)。

Redis

如果我没记错的话,在 2009 年前后,一位意大利的工程师 Antirez ,开源了 Redis。从此彻底颠覆了缓存的市场,到现在大多数缓存的业务都已用上 Redis,memcached 基本退出了历史舞台。Redis 最大的特点是拥有丰富的数据结构支持,不仅仅是简单的 Key-Value,还包括队列、集合、Sorted Set 等等,提供了非常丰富的表达力,而且 Redis 还提供 sub/pub 等超出数据库范畴的便捷功能,使得几乎一夜之间大家纷纷投入 Redis 的怀抱。

Twemproxy

但是随着 Redis 渐渐的普及,而且越用越狠,另外内存也越来越便宜,人们开始寻求扩展单机 Redis 的方案,最早的尝试是 twitter 开源的 twemproxy。twemproxy 是一个 Redis 中间件,基本只有最简单的数据路由功能,并没有动态的伸缩能力,但是还是受到了很多公司的追捧,因为确实没其他替代方案。随后的 Redis Cluster 也是难产了好久,时隔好几年,中间出了 7 个RC 版本,最后才发布;2014 年底,我们开源了 Codis,解决了 Redis 中间件的数据弹性伸缩问题,目前广泛应用于国内各大互联网公司中,这个在网上也有很多文章介绍,我也就不展开了。所以在缓存上面,开源社区现在倒是非常统一,就是 Redis 及其极其周边的扩展方案。

MongoDB

在 NoSQL 的大家庭中,MongoDB 其实是一个异类,大多 NoSQL 舍弃掉 SQL 是为了追求更极致的性能和可扩展能力,而 MongoDB 主动选择了文档作为对外的接口,非常像 JSON 的格式。Schema-less 的特性对于很多轻量级业务和快速变更的了互联网业务意义很大,而且 MongoDB 的易用性很好,基本做到了开箱即用,开发者不需要费心研究数据的表结构,只需要往里存就好了,这确实笼络了一大批开发者。

尽管 MongoDB 早期的版本各种不稳定,性能也不太好(早期的 Mongo 并没有存储引擎,直接使用了 mmap 文件),集群模式还全是问题(比如至今还未解决的 Cluster 同步带宽占用过多的问题),但是因为确实太方便了,在早期的项目快速迭代中,Mongo 是一个不错的选择。但是这也正是它的问题,我不止一次听到当项目变得庞大或者「严肃」的时候,团队最后还是回归了关系型数据库。Anyway,在 2014 年底 MongoDB 收购了 WiredTiger 后,在 2.8 版本中正式亮相,同时 3.0 版本后更是作为默认存储引擎提供,性能和稳定性有了非常大的提升。

但是,从另一方面讲,Schema-less 到底对软件工程是好事还是坏事这个问题还是有待商榷。我个人是站在 Schema 这边的,不过在一些小项目或者需要快速开发的项目中使用 Mongo 确实能提升很多的开发效率,这是毋庸置疑的。

HBase

说到 NoSQL 不得不提的是 HBase,HBase 作为 Hadoop 旗下的重要产品,Google Bigtable 的正统开源实现,是不是有一种钦定的感觉 :)。提到 HBase 就不得不提一下 Bigtable, Bigtable 是 Google 内部广泛使用的分布式数据库,接口也不是简单的Key-Value,按照论文的说法叫:multi-dimensional sorted map,也就是 Value 是按照列划分的。Bigtable 构建在 GFS 之上,弥补了分布式文件系统对于海量、小的、结构化数据的插入、更新以及、随机读请求的缺陷。

HBase 就是这么一个系统的实现,底层依赖 HDFS。HBase 本身并不实际存储数据,持久化的日志和 SST file (HBase 也是 LSM-Tree 的结构) 直接存储在 HDFS 上,Region Server (RS) 维护了 MemTable 以提供快速的查询,写入都是写日志,后台进行 Compact,避免了直接随机读写 HDFS。数据通过 Region 在逻辑上进行分割,负载均衡通过调节各个 Region Server 负责的 Region 区间实现。当某 Region 太大时,这个 Region 会分裂,后续可能由不同的 RS 负责,但是前面提到了,HBase 本身并不存储数据,这里的 Region 仅是逻辑上的,数据还是以文件的形式存储在 HDFS 上,所以 HBase 并不关心 Replication 、水平扩展和数据的分布,统统交给 HDFS 解决。

和 Bigtable 一样,HBase 提供行级的一致性,严格来说在 CAP 理论中它是一个 CP 的系统,但遗憾的是并没有更进一步提供 ACID 的跨行事务。HBase 的好处就不用说了,显而易见,通过扩展 RS 可以几乎线性提升系统的吞吐,及 HDFS 本身就具有的水平扩展能力。

但是缺点仍然是有的。首先,Hadoop 的软件栈是 Java,JVM 的 GC Tuning 是一个非常烦人的事情,即使已经调得很好了,平均延迟也得几十毫秒;另外在架构设计上,HBase 本身并不存储数据,所以可能造成客户端请求的 RS 并不知道数据到底存在哪台 HDFS DataNode 上,凭空多了一次 RPC;第三,HBase 和 Bigtable 一样,并不支持跨行事务,在 Google 内部不停的有团队基于 Bigtable 来做分布式事务的支持,比如 MegaStore、Percolator。后来 Jeff Dean 有次接受采访也提到非常后悔没有在 Bigtable 中加入跨行事务,不过还好这个遗憾在 Spanner 中得到了弥补,这个一会儿说。总体来说,HBase 还是一个非常健壮且久经考验的系统,但是需要你有对于 Java 和 Hadoop 比较深入的了解后,才能玩转,这也是 Hadoop 生态的一个问题,易用性真是不是太好,而且社区演进速度相对缓慢,也是因为历史包袱过重的缘故吧。

Cassandra

提到 Cassandra (C*),虽然也是 Dynamo 的开源实现,但就没有这种钦定的感觉了。C* 确实命途多舛,最早 2008 由 Facebook 开发并开源,早期的 C* 几乎全是 bug,Facebook 后来索性也不再维护转过头搞 HBase 去了,一个烂摊子直接丢给社区。还好 DataStax 把这个项目捡起来商业化,搞了两年,终于渐渐开始流行起来。

C* 不能简单的归纳为读快写慢,或者读慢写快,因为采用了 qourm 的模型,调整复制的副本数以及读的数量,可以达到不同的效果,对于一致性不是特别高的场景,可以选择只从一个节点读取数据,达到最高的读性能。另外 C* 并不依赖分布式文件系统,数据直接存储在磁盘上,各个存储节点之间自己维护复制关系,减少了一层 RPC 调用,延迟上对比 HBase 还是有一定优势的。

不过即使使用 qourm 的模型也并不代表 C* 是一个强一致的系统。C* 并不帮你解决冲突,即使你 W(写的副本数) + R(读请求的副本数) > N(节点总数),C* 也没办法帮你决定哪些副本拥有更新的版本,因为每个数据的版本是一个 NTP 的时间戳或者客户端自行提供,每台机器可能都有误差,所以有可能并不准确,这也就是为什么 C* 是一个 AP 的系统。不过 C* 一个比较友好的地方是提供了 CQL,一个简单的 SQL 方言,比起 HBase 在易用性上有明显优势。

即使作为一个 AP 系统,C* 已经挺快了,但是人们追求更高性能的脚步还是不会停止。应该是今年年初,ScyllaDB 的发布就是典型的证明,ScyllaDB 是一个兼容 C* 的 NoSQL 数据库,不一样的是,ScyllaDB 完全用 C++ 开发,同时使用了类似 DPDK 这样的黑科技,具体我就不展开了,有兴趣可以到 Scylla 的官网去看看。BTW, 国内的蘑菇街第一时间使用了 ScyllaDB,同时在 Scylla 的官网上 share 了他们的方案,性能还是很不错的。

中间件与分库分表

NoSQL 就先介绍到这里,接下来我想说的是一些在基于单机关系型数据库之上的中间件和分库分表方案。

这些技术确实历史悠久,而且也是没有办法的选择。关系型数据库不比 Redis,并不是简单的写一个类似 Twemproxy 的中间件就搞定了。数据库的中间件需要考虑很多,比如解析 SQL,解析出 sharding key,然后根据 sharding key 分发请求,再合并;另外数据库有事务,在中间件这层还需要维护 Session 及事务状态,而且大多数方案并没有办法支持跨 shard 的事务。这就不可避免的导致了业务使用起来会比较麻烦,需要重写代码,而且会增加逻辑的复杂度,更别提动态的扩容缩容和自动的故障恢复了。在集群规模越来越大的情况下,运维和 DDL 的复杂度是指数级上升的。

中间件项目盘点

数据库中间件最早的项目大概是 MySQL Proxy,用于实现读写分离。后来国人在这个领域有过很多著名项目,比如阿里的 Cobar 和 TDDL(并未完全开源);后来社区基于 Cobar 改进的 MyCAT、360 开源的 Atlas 等,都属于这一类中间件产品;在中间件这个方案上基本走到头的开源项目应该是 Youtube 的 Vitess。Vitess 基本上是一个集大成的中间件产品,内置了热数据缓存、水平动态分片、读写分离等等,但是代价也是整个项目非常复杂,另外文档也不太好。大概1年多以前,我们尝试搭建起完整的 Vitess 集群,但是并未成功,可见其复杂度。

另外一个值得一提的是 Postgres-XC 这个项目,Postgres-XC 的野心还是很大的,整体的架构有点像早期版本的 OceanBase,由一个中央节点来处理协调分布式事务 / 解决冲突,数据分散在各个存储节点上,应该是目前 PostgreSQL 社区最好的分布式扩展方案。其他的就不提了。

未来在哪里?NewSQL!

一句话,NewSQL 就是未来。

2012 年 Google 在 OSDI 上发表了 Spanner 的论文,2013 年在 SIGMOD 发表了 F1 的论文。这两篇论文让业界第一次看到了关系模型和 NoSQL 的扩展性在超庞大集群规模上融合的可能性。在此之前,大家普遍认为这个是不可能的,即使是 Google 也经历了 Megastore 这样的失败。

Spanner综述

但是 Spanner 的创新之处在于通过硬件(GPS时钟+原子钟)来解决时钟同步的问题。在分布式系统里,时钟是最让人头痛的问题,刚才提到了 C* 为什么不是一个强 C 的系统,正是因为时钟的问题。而 Spanner 的厉害之处在于即使两个数据中心隔得非常远,不需要有通信(因为通信的代价太大,最快也就是光速)就能保证 TrueTime API的时钟误差在一个很小的范围内(10ms)。另外 Spanner 沿用了很多 Bigtable 的设计,比如 Tablet / Directory 等,同时在 Replica 这层使用 Paxos 复制,并未完全依赖底层的分布式文件系统。但是 Spanner 的设计底层仍然沿用了 Colossus,不过论文里也说是可以未来改进的点。

Google 的内部的数据库存储业务,大多是 3~5 副本,重要一点的 7 副本,遍布全球各大洲的数据中心,由于普遍使用了 Paxos,延迟是可以缩短到一个可以接受的范围(Google 的风格一向是追求吞吐的水平扩展而不是低延迟,从悲观锁的选择也能看得出来,因为跨数据中心复制是必选的,延迟不可能低,对于低延迟的场景,业务层自己解决或者依赖缓存)。另外由 Paxos 带来的 Auto-Failover 能力,更是能让整个集群即使数据中心瘫痪,业务层都是透明无感知的。另外 F1 构建在 Spanner 之上,对外提供了更丰富的 SQL 语法支持,F1 更像一个分布式 MPP SQL——F1 本身并不存储数据,而是将客户端的 SQL 翻译成类似 MapReduce 的任务,调用 Spanner 来完成请求。

其实 Spanner 和 F1 除了 TrueTime 整个系统并没有用什么全新的算法,其意义在于这是近些年来第一个 NewSQL 在生产环境中提供服务的分布式系统技术。

Spanner 和 F1 有以下几个重点:

1. 完整的 SQL 支持,ACID 事务;2. 弹性伸缩能力;

3. 自动的故障转移和故障恢复,多机房异地灾备。

NewSQL 特性确实非常诱人,在 Google 内部,大量的业务已经从原来的 Bigtable 切换到 Spanner 之上。我相信未来几年,整个业界的趋势也是如此,就像当年的 Hadoop 一样,Google 的基础软件的技术趋势是走在社区前面的。

社区反应

Spanner 的论文发表之后,当然也有社区的追随者开始实现(比如我们 :D ),第一个团队是在纽约的 CockroachDB。CockroachDB 的团队的组成还是非常豪华的,早期团队由是 Google 的分布式文件系统 Colossus 团队的成员组成;技术上来说,Cockroach 的设计和 Spanner 很像,不一样的地方是没有选择 TrueTime而是 HLC (Hybrid logical clock),也就是 NTP +逻辑时钟来代替 TrueTime 时间戳;另外 Cockroach 选用了 Raft 代替 Paxos 实现复制和自动容灾,底层存储依赖 RocksDB 实现,整个项目使用 Go 语言开发,对外接口选用 PostgreSQL 的 SQL 子集。

TiDB

目前从全球范围来看,另一个朝着 Spanner / F1 的开源实现这个目标上走的产品是 TiDB(终于谈到我们的产品了)。TiDB 本质上是一个更加正统的 Spanner 和 F1 实现,并不像 CockroachDB 那样选择将 SQL 和 Key-Value 融合,而是像 Spanner 和 F1 一样选择分离,这样分层的思想也是贯穿整个 TiDB 项目始终的。对于测试、滚动升级以及各层的复杂度控制会比较有优势;另外 TiDB 选择了 MySQL 协议和语法的兼容,MySQL 社区的 ORM 框架和运维工具,直接可以应用在 TiDB 上。

和 F1 一样,TiDB 是一个无状态的 MPP SQL Layer,整个系统的底层是依赖 TiKV 来提供分布式存储和分布式事务的支持。TiKV 的分布式事务模型采用的是 Google Percolator 的模型,但是在此之上做了很多优化。Percolator 的优点是去中心化程度非常高,整个集群不需要一个独立的事务管理模块,事务提交状态这些信息其实是均匀分散在系统的各个 Key 的 meta 中,整个模型唯一依赖的是一个授时服务器。在我们的系统上,极限情况这个授时服务器每秒能分配 400w 以上个单调递增的时间戳,大多数情况基本够用了(毕竟有 Google 量级的场景并不多见);同时在 TiKV中,这个授时服务本身是高可用的,也不存在单点故障的问题。

TiKV 和 CockroachDB 一样也是选择了 Raft 作为整个数据库的基础;不一样的是,TiKV 整体采用 Rust 语言开发,作为一个没有 GC 和 Runtime 的语言,在性能上可以挖掘的潜力会更大。

关于未来

我觉得未来的数据库会有几个趋势,也是 TiDB 项目追求的目标:

●数据库会随着业务云化,未来一切的业务都会跑在云端,不管是私有云、公有云还是混合云,运维团队接触的可能再也不是真实的物理机,而是一个个隔离的容器或者「计算资源」。这对数据库也是一个挑战,因为数据库天生就是有状态的,数据总是要存储在物理的磁盘上,而移动数据的代价比移动容器的代价可能大很多。

●多租户技术会成为标配,一个大数据库承载一切的业务,数据在底层打通,上层通过权限,容器等技术进行隔离;但是数据的打通和扩展会变得异常简单,结合第一点提到的云化,业务层可以再也不用关心物理机的容量和拓扑,只需要认为底层是一个无穷大的数据库平台即可,不用再担心单机容量和负载均衡等问题。

●OLAP 和 OLTP 会进一步细分,底层存储也许会共享一套,但是SQL优化器这层的实现一定是千差万别的。对于用户而言,如果能使用同一套标准的语法和规则来进行数据的读写和分析,会有更好的体验。

●在未来分布式数据库系统上,主从日志同步这样落后的备份方式会被 Multi-Paxos / Raft 这样更强的分布式一致性算法替代,人工的数据库运维在管理大规模数据库集群时是不可能的,所有的故障恢复和高可用都会是高度自动化的。


Q&A

问:HANA等内存数据库怎么保证系统掉电而处理结果不丢?传统数据库也用缓存,可是HANA用的内存太大。

黄东旭:没用过 HANA,但是直观感觉这类内存数据库的可用性可能通过集中方式保证:●写入会先写 WAL;

> - 写入可能会通过主从或者paxos 之类的算法做同步和冗余复制还有 HANA 本身就是内存数据库,会尽可能把数据放到内存里,这样查询才能快呀。 

问:对于传统创业公司如何弥补NoSQL的技术短板?快速的引入NoSQL提高效率?

黄东旭:选用 NoSQL 主要注意两点:1.做好业务的调研,估计并发量,数据量,数据的结构看看适不适合;2.对各种 NoSQL 擅长和不擅长的地方都尽可能了解。

不要盲目相信关系型数据库,也不要盲目相信 NoSQL,没有银弹的。

问:有多个条件  比如年龄20到30或年龄35到40 并且加入购物车或下单  这种数据怎么存储?

黄东旭:购物车这种场景是典型的 OLTP 的场景,可以选用关系型数据库 MySQL PostgreSQL 什么的,如果对于扩展性的数据跨机房有要求的话,可以调研一下 NewSQL,比如我们的 TiDB。

问:多纬度查询应该选择哪种数据库?

黄东旭:多纬度查询可以说是一个 OLAP 的场景,可以选用 Greenplum 或者 Vertica 之类的分析性数据库。

问:想知道为什么需要这些开源的数据库,既然已经有了MySQL、DB2、Oracle这些成熟的数据库,成本考虑,还是传统数据库满足不了需求?

黄东旭:对,传统数据库的扩展性是有问题的,在海量并发和数据量的场景下很难支持业务。所以可以看到比较大的互联网公司基本都有自己的分布式数据库方案。

问:未来可能不再需要数据仓库吗?

黄东旭:大家可以想想数据仓库的定义,如果是还需要离线的从线上库倒腾数据到数据仓库上,这样很难做到实时查询,而且空间的利用率也低,我认为是目前并没有太好的方案的情况下的折衷……如果有一个更好的数据库能解决数据仓库的场景,为什么还需要一个独立的数据仓库?


关于作者

黄东旭,PingCAP 联合创始人兼 CTO。PingCAP 是一家专注于研发下一代的开源的分布式数据库的公司,主要作品是 TiDB / TiKV,是 Google Spanner 及 F1 的开源实现。

相关链接

    想通过手机客户端(支持 Android、iPhone 和 Windows Phone)访问开源中国:请点这里

    posted @ 2016-09-08 15:05 小马歌 阅读(304) | 评论 (0)编辑 收藏
     
    from:https://linux.cn/article-2871-1.html

    如果你想在命令行界面监控网络吞吐量,nload 应用程序是个不错的选择。它是一个实时监控网络流量和带宽使用的控制台应用程序,使用两个图表可视化地展示接收和发送的流量,并提供诸如数据交换总量、最小/最大网络带宽使用量等附加信息。

    安装

    在 CentOS/RHEL/Red Hat/Fedora Linux 上安装 nload

    首先在 CentOS 或者基于 RHEL 的操作系统上启用 EPEL 仓库,然后键入 yum 命令安装 nload:

    1. # yum install nload

    在 Debian 或者 Ubuntu Linux 上安装 nload

    键入 apt-get 命令

    1. $ sudo apt-get install nload

    在 FreeBSD 操作系统上安装 nload

    通过 port 安装 nload,键入:

    1. # cd /usr/ports/net/nload/ &amp;&amp; make install clean

    或者添加包

    1. # pkg install net/nload

    在 OpenBSD 操作系统上安装 nload

    键入下列命令:

    1. $ sudo pkg_add -i nload

    在类 Unix 操作系统上从源代码安装 nload

    首先,使用 wget 或者 curl 命令获取源代码:

    1. $ cd /tmp
    2. $ wget http://www.roland-riegel.de/nload/nload-0.7.4.tar.gz

    使用 tar 命令解压缩名为 nload-0.7.4.tar.gz 的 tar 包,键入:

    1. $ tar xvf nload-0.7.4.tar.gz

    使用 cd 命令进入 nload 源代码所在目录:

    1. $ cd nload*

    然后键入 ./configure 为你的操作系统配置安装包:

    1. $ sh ./configure

    或者

    1. $ ./configure

    运行 configure 命令需要一点时间。完成后,使用 make 命令编译 nload:

    1. $ make

    最后,键入 make install 命令以 root 用户身份安装 nload 应用程序和相关文件:

    1. $ sudo make install

    或者

    1. # make install

    使用

    如何使用 nload 显示当前网络使用量呢?

    基本语法是:

    1. nload
    2. nload device
    3. nload [options] device1 device2

    键入下列命令:

    1. $ nload
    2. $ nload eth0
    3. $ nload em0 em2

    会得到输出:

    图01: 使用 nload 命令

    图01: 使用 nload 命令

    操控 nload 应用程序

    nload 命令一旦执行就会开始监控网络设备,你可以使用下列快捷键操控 nload 应用程序。

    1. 你可以按键盘上的 ← → 或者 Enter/Tab 键在设备间切换。
    2. 按 F2 显示选项窗口。
    3. 按 F5 将当前设置保存到用户配置文件。
    4. 按 F6 从配置文件重新加载设置。
    5. 按 q 或者 Ctrl+C 退出 nload。

    设置显示刷新间隔

    默认每 100 毫秒刷新一次显示数值,下面的例子将时间间隔设置成 500 毫秒:

    1. $ nload -t {interval_number_in_millisec}
    2. $ nload -t 500

    输出:

    Animated gif 01 - nload command in action

    Animated gif 01 - nload command in action

    GIF 动画 01 - 使用 nload 命令

    设置流量数值显示的单位

    语法如下:

    1. $ nload -u h|H|b|B|k|K|m|M|g|G
    2. $ nload -U h|H|b|B|k|K|m|M|g|G
    3. $ nload -u h
    4. $ nload -u G
    5. $ nload -U G

    释义:

    • 小写选项 -u: h 意为自动格式化为人类易读的单位,b 意为 Bit/s,k 意为 kBit/s,m 意为 MBit/s,g 意为 GBit/s。大写字母意为使用 Byte 替代 Bit。默认为 k。
    • 大写选项 -U 与小写选项 -u 非常相似,不同之处在于它展示的是数据量,比如 Bit, kByte, GBit 等等。(没有 "/s")。默认值是 M。

    结论

    我觉得 nload 是一个稳定可靠的应用程序,如果你喜欢 nload,你可能也想试试 Linux 和其他类 Unix 操作系统环境下的 vnstat 与 iftop 工具。


    译自: http://www.cyberciti.biz/networking/nload-linux-command-to-monitor-network-traffic-bandwidth-usage/

    posted @ 2016-09-08 15:04 小马歌 阅读(1519) | 评论 (0)编辑 收藏
     
         摘要: from:http://chuansong.me/n/474670251169作者介绍吴朱华:国内资深云计算和大数据专家,之前曾在IBM中国研究院和上海云人信息科技有限公司参与过多款云计算产和大数据产品的开发工作,同济本科,并曾在北京大学读过硕士。2011年中,发表业界最好的两本云计算书之一《云计算核心技术剖析》。2016年和上海华东理工大学的阮彤教授等合著了《大数据技术前沿》一书。最近几年一直参...  阅读全文
    posted @ 2016-09-08 15:03 小马歌 阅读(221) | 评论 (0)编辑 收藏
     

    Today, we’re excited to announce the general availability of Apache Spark 2.0 on Databricks. This release builds on what the community has learned in the past two years, doubling down on what users love and fixing the pain points. This post summarizes the three major themes—easier, faster, and smarter—that comprise Spark 2.0. We also explore many of them in more detail in our anthology of Spark 2.0 content.

    Two months ago, we launched a preview release of Apache Spark 2.0 on Databricks. As you can see in the chart below, 10% of our clusters are already using this release, as customers experiment with the new features and give us feedback. Thanks to this experience, we are excited to be the first commercial vendor to support Spark 2.0.

    Spark Usage over Time by Release Versions

    Apache Spark Usage over Time by Version

     

    Now, let’s dive into what’s new in Apache Spark 2.0.

    Easier: ANSI SQL and Streamlined APIs

    One thing we are proud of in Spark is APIs that are simple, intuitive, and expressive. Spark 2.0 continues this tradition, focusing on two areas: (1) standard SQL support and (2) unifying DataFrame/Dataset API.

    On the SQL side, we have significantly expanded Spark’s SQL support, with the introduction of a new ANSI SQL parser and subqueriesSpark 2.0 can run all the 99 TPC-DS queries, which require many of the SQL:2003 features. Because SQL has been one of the primary interfaces to Spark, these extended capabilities drastically reduce the effort of porting legacy applications.

    On the programmatic API side, we have streamlined Spark’s APIs:

    • Unifying DataFrames and Datasets in Scala/Java: Starting in Spark 2.0, DataFrame is just a type alias for Dataset of Row. Both the typed methods (e.g. mapfiltergroupByKey) and the untyped methods (e.g. selectgroupBy) are available on the Dataset class. Also, this new combined Dataset interface is the abstraction used for Structured Streaming. Since compile-time type-safety is not a feature in Python and R, the concept of Dataset does not apply to these language APIs. Instead, DataFrame remains the primary interface there, and is analogous to the single-node data frame notion in these languages. Get a peek fromthis notebook and this blog for the stories behind these APIs.
    • SparkSession: a new entry point that supersedes SQLContext and HiveContext. For users of the DataFrame API, a common source of confusion for Spark is which “context” to use. Now you can use SparkSession, which subsumes both, as a single entry point, asdemonstrated in this notebook. Note that the old SQLContext and HiveContext classes are still kept for backward compatibility.
    • Simpler, more performant Accumulator API: We have designed a new Accumulator APIthat has a simpler type hierarchy and support specialization for primitive types. The old Accumulator API has been deprecated but retained for backward compatibility
    • DataFrame-based Machine Learning API emerges as the primary ML API: With Spark 2.0, the spark.ml package, with its “pipeline” APIs, will emerge as the primary machine learning API. While the original spark.mllib package is preserved, future development will focus on the DataFrame-based API.
    • Machine learning pipeline persistence: Users can now save and load machine learning pipelines and models across all programming languages supported by Spark. See this blog post for more details and this notebook for examples.
    • Distributed algorithms in R: Added support for Generalized Linear Models (GLM), Naive Bayes, Survival Regression, and K-Means in R.
    • User-defined functions (UDFs) in R: Added support for running partition level UDFs (dapply and gapply) and hyper-parameter tuning (lapply).

    Faster: Apache Spark as a Compiler

    According to our 2015 Spark Survey, 91% of users consider performance as the most important aspect of Apache Spark. As a result, performance optimizations have always been a focus in our Spark development. Before we started planning our contributions to Spark 2.0, we asked ourselves a question: Spark is already pretty fast, but can we push the boundary and make Spark 10X faster?

    This question led us to fundamentally rethink the way we build Spark’s physical execution layer. When you look into a modern data engine (e.g. Spark or other MPP databases), majority of the CPU cycles are spent in useless work, such as making virtual function calls or reading/writing intermediate data to CPU cache or memory. Optimizing performance by reducing the amount of CPU cycles wasted in these useless work has been a long time focus of modern compilers.

    Spark 2.0 ships with the second generation Tungsten engine. This engine builds upon ideas from modern compilers and MPP databases and applies them to Spark workloads. The main idea is to emit optimized code at runtime that collapses the entire query into a single function, eliminating virtual function calls and leveraging CPU registers for intermediate data. We call this technique “whole-stage code generation.”

    To give you a teaser, we have measured the time (in nanoseconds) it takes to process a row on one core for some of the operators in Spark 1.6 vs. Spark 2.0. The table below shows the improvements in Spark 2.0. Spark 1.6 also included an expression code generation technique that is used in some state-of-the-art commercial databases, but as you can see, many operators became an order of magnitude faster with whole-stage code generation.

    You can see the power of whole-stage code generation in action in this notebook, in which we perform aggregations and joins on 1 billion records on a single machine.

    Cost per Row (single thread)
    primitiveSpark 1.6Spark 2.0
    filter15ns1.1ns
    sum w/o group14ns0.9ns
    sum w/ group79ns10.7ns
    hash join115ns4.0ns
    sort (8-bit entropy)620ns5.3ns
    sort (64-bit entropy)620ns40ns
    sort-merge join750ns700ns

    How does this new engine work on end-to-end queries? We did some preliminary analysis using TPC-DS queries to compare Spark 1.6 and Spark 2.0:


    Preliminary TPC-DS Spark 2.0 vs 1.6

    Beyond whole-stage code generation to improve performance, a lot of work has also gone into improving the Catalyst optimizer for general query optimizations such as nullability propagation, as well as a new vectorized Parquet decoder that improved Parquet scan throughput by 3X. Read this blog post for more detail on the optimizations in Spark 2.0.

    Smarter: Structured Streaming

    Spark Streaming has long led the big data space as one of the first systems unifying batch and streaming computation. When its streaming API, called DStreams, was introduced in Spark 0.7, it offered developers with several powerful properties: exactly-once semantics, fault-tolerance at scale, strong consistency guarantees and high throughput.

    However, after working with hundreds of real-world deployments of Spark Streaming, we found that applications that need to make decisions in real-time often require more than just a streaming engine. They require deep integration of the batch stack and the streaming stack, interaction with external storage systems, as well as the ability to cope with changes in business logic. As a result, enterprises want more than just a streaming engine; instead they need a full stack that enables them to develop end-to-end “continuous applications.”

    Spark 2.0 tackles these use cases through a new API called Structured Streaming. Compared to existing streaming systems, Structured Streaming makes three key improvements:

    1. Integrated API with batch jobs. To run a streaming computation, developers simply write a batch computation against the DataFrame / Dataset API, and Spark automaticallyincrementalizes the computation to run it in a streaming fashion (i.e. update the result as data comes in). This powerful design means that developers don’t have to manually manage state, failures, or keeping the application in sync with batch jobs. Instead, the streaming job always gives the same answer as a batch job on the same data.
    2. Transactional interaction with storage systems. Structured Streaming handles fault tolerance and consistency holistically across the engine and storage systems, making it easy to write applications that update a live database used for serving, join in static data, or move data reliably between storage systems.
    3. Rich integration with the rest of Spark. Structured Streaming supports interactive queries on streaming data through Spark SQL, joins against static data, and many libraries that already use DataFrames, letting developers build complete applications instead of just streaming pipelines. In the future, expect more integrations with MLlib and other libraries.

    Spark 2.0 ships with an initial, alpha version of Structured Streaming, as a (surprisingly small!) extension to the DataFrame/Dataset API. This makes it easy to adopt for existing Spark users that want to answer new questions in real-time. Other key features include support for event-time based processing, out-of-order/delayed data, interactive queries, and interaction with non-streaming data sources and sinks.

    We also updated the Databricks workspace to support Structured Streaming. For example, when launching a streaming query, the notebook UI will automatically display its status.image01

    Streaming is clearly a broad topic, so stay tuned for a series of blog posts with more details on Structured Streaming in Apache Spark 2.0.

    Conclusion

    Spark users initially came to Apache Spark for its ease-of-use and performance. Spark 2.0 doubles down on these while extending it to support an even wider range of workloads. Enjoy the new release on Databricks.

    Read More

    You can also import the following notebooks and try them on Databricks Community Editionwith Spark 2.0.

    Databricks Blog

    posted @ 2016-09-08 14:51 小马歌 阅读(175) | 评论 (0)编辑 收藏
     
         摘要: from:http://chuansong.me/n/465862351096本文整理自QCon北京Fangjin Yang的英文主题演讲。关注“大数据杂谈”公众号,点击“加群学习”,更多大牛一手技术分享等着你。演讲整理:刘继伟在QCon 2016 北京站上,Druid开源项目的负责人,同时也是一家位于旧金山的技术公司共同创始人的Fangjin Ya...  阅读全文
    posted @ 2016-09-08 14:45 小马歌 阅读(193) | 评论 (0)编辑 收藏
     

    Druid是一个用于大数据实时查询和分析的高容错、高性能开源分布式系统,旨在快速处理大规模的数据,并能够实现快速查询和分析。尤其是当发生代码部署、机器故障以及其他产品系统遇到宕机等情况时,Druid仍能够保持100%正常运行。创建Druid的最初意图主要是为了解决查询延迟问题,当时试图使用Hadoop来实现交互式查询分析,但是很难满足实时分析的需要。而Druid提供了以交互方式访问数据的能力,并权衡了查询的灵活性和性能而采取了特殊的存储格式。

    Druid功能介于PowerDrillDremel之间,它几乎实现了Dremel的所有功能,并且从PowerDrill吸收一些有趣的数据格式。Druid允许以类似Dremel和PowerDrill的方式进行单表查询,同时还增加了一些新特性,如为局部嵌套数据结构提供列式存储格式、为快速过滤做索引、实时摄取和查询、高容错的分布式体系架构等。从官方得知,Druid的具有以下主要特征:

    • 为分析而设计——Druid是为OLAP工作流的探索性分析而构建,它支持各种过滤、聚合和查询等类;
    • 快速的交互式查询——Druid的低延迟数据摄取架构允许事件在它们创建后毫秒内可被查询到;
    • 高可用性——Druid的数据在系统更新时依然可用,规模的扩大和缩小都不会造成数据丢失;
    • 可扩展——Druid已实现每天能够处理数十亿事件和TB级数据。

    Druid应用最多的是类似于广告分析创业公司Metamarkets中的应用场景,如广告分析、互联网广告系统监控以及网络监控等。当业务中出现以下情况时,Druid是一个很好的技术方案选择:

    • 需要交互式聚合和快速探究大量数据时;
    • 需要实时查询分析时;
    • 具有大量数据时,如每天数亿事件的新增、每天数10T数据的增加;
    • 对数据尤其是大数据进行实时分析时;
    • 需要一个高可用、高容错、高性能数据库时。

    一个Druid集群有各种类型的节点(Node)组成,每个节点都可以很好的处理一些的事情,这些节点包括对非实时数据进行处理存储和查询的Historical节点、实时摄取数据、监听输入数据流的Realtime节、监控Historical节点的Coordinator节点、接收来自外部客户端的查询和将查询转发到Realtime和Historical节点的Broker节点、负责索引服务的Indexer节点

    查询操作中数据流和各个节点的关系如下图所示:

    如下图是Druid集群的管理层架构,该图展示了相关节点和集群管理所依赖的其他组件(如负责服务发现的ZooKeeper集群)的关系:

    Druid已基于Apache License 2.0协议开源,代码托管在GitHub,其当前最新稳定版本是0.7.1.1。当前,Druid已有63个代码贡献者和将近2000个关注。Druid的主要贡献者包括广告分析创业公司Metamarkets、电影流媒体网站Netflix、Yahoo等公司。Druid官方还对Druid同SharkVerticaCassandraHadoopSparkElasticsearch等在容错能力、灵活性、查询性能等方便进行了对比说明。更多关于Druid的信息,大家还可以参考官方提供的入门教程白皮书设计文档等。

    posted @ 2016-09-08 14:45 小马歌 阅读(266) | 评论 (0)编辑 收藏
     
         摘要: from:https://coyee.com/article/10690-why-uber-engineering-switched-from-postgres-to-mysql介绍早期的 Uber 架构是由 Python 编写的,使用的是 Postgres 数据库存储。从那时起,Uber 的架构就一直在变化,变成微服务模型和新的数据平台。具体的说,很多我们以前使用 Postgres 的...  阅读全文
    posted @ 2016-09-08 14:32 小马歌 阅读(423) | 评论 (0)编辑 收藏
     
    from:http://www.36dsj.com/archives/55359

    作者:祝威廉

    • 工程数据,譬如工单数量,SLA可用性,基础资源,故障率,报警统计
    • 业务数据,譬如业务DashBoard,Trace调用链,业务拓扑切换,业务指标,业务基准数据,业务日志挖掘
    • 数据可视化

    当然,这篇文章谈的是运维都有哪些数据,哪些指标,以及数据呈现。并没有谈及如何和大数据相关的架构做整合,从而能让这些数据真的变得活起来。

    比较凑巧的是,原先百度的桑文峰的分享也讲到日志的多维度分析,吃完饭的时候,一位优酷的朋友也和我探讨了关于业务监控的的问题。而我之前发表在肉饼铺子里的一篇文章《 大数据给公司带来了什么 》也特地提到了大数据对于整个运维的帮助,当时因为这篇内容的主旨是罗列大数据的用处,自然没法细讲运维和大数据的整合这一块。

    上面的文字算引子,在步入正式的探讨前,有一点我觉得值得强调:

    虽然这里讲的是如何将大数据思维/架构应用于运维,平台化运维工作,但是和大数据本质上没有关系,我们只是将大数据处理的方式和思想应用在运维工作上。所以,即使你现在所在的公司没有数据团队支撑,也是完全可以通过现有团队完成这件事情的。

    1 运维监控现状

    很多公司的运维的监控具有如下特质:

    只能监控基础运维层次,通过zabbix等工具提供服务器,CPU,内存等相关的监控。这部分重要,但确实不是运维的核心。

    对业务的监控是最复杂的,而现在很多公司的要么还处于Shell脚本的刀耕火种阶段,要么开发能力较强,但是还是东一榔头西一棒子,不同的业务需要不同的监控系统,人人都可以根据的自己的想法开发一个监控的工具也好,系统也好,平台也好。总之是比较凌乱的。

    使用第三方的监控平台。这个似乎在Rails/NodeJS/Pythone相关语系开发的产品中比较常见。我不做过多评价,使用后冷暖自知。

    当然也有抽象得很好的,比如点评网的运维监控据说就做得相当好,运维很闲,天天没事就根据自己的监控找开发的茬,让开发持续改进。不过他们的指导思想主要有两个:

    运维自动化。怎么能够实现这个目标就怎么搞,这严重依赖于搞的人的规划能力和经验。

    抽象化,根据实际面临的问题做出抽象,得到对应的系统,比如需要发布,于是又发布系统,需要管理配置文件,所以有配管系统,需要日志分析所以有了有日志分析系统。然而这样是比较零散的。

    有点扯远,我们还是focus在监控上。

    如果以大数据的思维去思考,我们应该如何做好监控这件事情?

    2 罗列出你的数据源

    《大数据对于运维的意义》这篇文章也讲了,主要有工程数据,业务数据。所有的数据源都有一个共性,就是 日志 。无论文本的也好,二进制的也好。所以日志是整个信息的源头。日志包含的信息足以让我们追查到下面几件事情:

    • 系统健康状况监控
    • 查找故障根源
    • 系统瓶颈诊断和调优
    • 追踪安全相关问题
    • 从日志我们可以挖掘出什么?

    我觉得抽象起来就一个: 指标 。

    指标可以再进行分类:

    业务层面,如团购业务每秒访问数,团购券每秒验券数,每分钟支付、创建订单等

    应用层面,每个应用的错误数,调用过程,访问的平均耗时,最大耗时,95线等

    系统资源层面:如cpu、内存、swap、磁盘、load、主进程存活等

    网络层面: 如丢包、ping存活、流量、tcp连接数等

    每个分类里的每个小点其实都是一个指标。

    3 如何统一实现

    千万不要针对具体问题进行解决,大数据架构上的一个思维就是:我能够提供一个平台让大家方便解决这些问题么? 而不是,这个问题我能解决么?

    先来看看架构图:

    架构
    因为目前我负责应用层的研发,业务还比较少,主要就需要监控三个系统:

    • 推荐
    • 搜索
    • 统一查询引擎

    所以监控的架构设计略简单些。如果你希望进行日志存储以及事后批量分析,则可以采用淘宝的这套架构方式:

    架构方式
    稍微说明下,日志收集Agent可以使用Flume,鹰眼Storm集群,其实就是Storm集群,当然有可能是淘宝内部Java版的,Storm(或第一幅图的SparkStreaming)做两件事情。

    将日志过滤,格式化,或存储起来

    进行实时计算,将指标数据存储到HBase里去

    到目前为止,我们没有做任何的开发,全部使用大数据里通用的一些组件。至于这些组件需要多少服务器,就看对应的日志量规模了,三五台到几百台都是可以的。

    需要开发的地方只有两个点,有一个是一次性的,有一个则是长期。

    先说说一次性的,其实就是大盘展示系统。这个就是从HBase里取出数据做展示。这个貌似也有开源的一套,ELK。不过底层不是用的HBase存储,而是ES。这里就不详细讨论。

    长期的则是SparkStreaming(淘宝是使用Storm,我建议用SparkStreaming,因为SparkStreaming可以按时间窗口,也可以按量统一做计算),这里你需要定义日志的处理逻辑,生成我上面提到的各项指标。

    这里有一个什么好处呢,就是平台化了,对新的监控需求响应更快了,开发到上线可能只要几个小时的功夫。如果某个系统某天需要一个新的监控指标,我们只要开发个SparkStreaming程序,丢到平台里去,这事就算完了。

    第一幅图的平台我是已经实现了的。我目前在SparkStreaming上只做了三个方面比较基础的监控,不过应该够用了。

    状态码大盘。 HTTP响应码的URL(去掉query参数)排行榜。比如你打开页面就可以看到发生500错误的top100的URL,以及该URL所归属的系统。

    响应耗时大盘。 URL请求耗时排行榜。比如你打开页面就可以看到5分钟内平均响应耗时top100的URL(去掉query参数)。

    还有就是Trace系统。 类似Google的Dapper,淘宝的EagleEye。给出一个唯一的UUID,可以追踪到特定一个Request的请求链路。每个依赖服务的响应情况,比如响应时间。对于一个由几个甚至几百个服务组成的大系统,意义非常大,可以方便的定位出到底是那个系统的哪个API的问题。这个最大的难点是需要统一底层的RPC/HTTP调用框架,进行埋点。因为我使用的是自研的ServiceFramework框架,通讯埋点就比较简单。如果是在一个业务线复杂,各个系统使用不同技术开发,想要做这块就要做好心理准备了。

    现在,如果你想要监控一个系统是不是存活,你不在需要取写脚本去找他的pid看进程是不是存在,系统发现在一定的周期内没有日志,就可以认为它死了。而系统如果有异常,比如有大量的慢查询,大盘一定能展示出来。

    描述到这,我们可以看到,这套架构的优势在哪:

    基本上没有需要自己开发的系统。从日志收集,到日志存储,到结果存储等,统统都是现成的组件。

    可扩展性好。每个组件都是集群模式的,没有单点故障。每个组件都是可水平扩展的,日志量大了,加机器就好。

    开发更集中了。你只要关注日志实际的分析处理,提炼指标即可。

    4 大数据思维

    对于运维的监控,利用大数据思维,需要分三步走:

    • 找到数据
    • 分析定义从数据里中我能得到什么
    • 从大数据平台中挑选你要的组件完成搭积木式开发

    所有系统最可靠的就是日志输出,系统是不是正常,发生了什么情况,我们以前是出了问题去查日志,或者自己写个脚本定时去分析。现在这些事情都可以整合到一个已有的平台上,我们唯一要做的就是 定义处理日志的的逻辑 。

    这里有几点注意的:

    如果你拥有复杂的产品线,那么日志格式会是一个很痛苦的事情。以为这中间Storm(或者SparkStreaming)的处理环节你需要做大量的兼容适配。我个人的意见是,第一,没有其他更好的办理,去兼容适配吧,第二,推动大家统一日志格式。两件事情一起做。我一个月做不完,那我用两年时间行么?总有一天大家都会有统一的日志格式的。

    如果你的研发能力有富余,或者有大数据团队支撑,那么可以将进入到SparkStreaming中的数据存储起来,然后通过SparkSQL等做即席查询。这样,有的时候原先没有考虑的指标,你可以直接基于日志做多维度分析。分析完了,你觉得好了,需要固化下来,那再去更新你的SparkStreaming程序。

    后话

    我做上面第一幅图架构实现时,从搭建到完成SparkStreaming程序开发,到数据最后进入HBase存储,大概只花了一天多的时间。当然为了完成那个Trace的指标分析,我修改ServiceFramework框架大约改了两三天。因为Trace分析确实比较复杂。当然还有一个比较消耗工作量的,是页面可视化,我这块自己还没有能力做,等招个Web开发工程师再说了。

    End.

    posted @ 2016-09-06 16:50 小马歌 阅读(230) | 评论 (0)编辑 收藏
     

    华为宣布开源了CarbonData项目,该项目于6月3日通过Apache社区投票,成功进入Apache孵化器。CarbonData是一种低时延查询、存储和计算分离的轻量化文件存储格式。那么相比SQL on Hadoop方案、传统NoSQL或相对ElasticSearch等搜索系统,CarbonData具有什么样的优势呢?CarbonData的技术架构是什么样子的?未来有什么样的规划?我们采访了CarbonData项目的技术负责人为大家解惑。

    InfoQ:请问CarbonData是什么时候开始进行的项目?为什么现在向Apache孵化器开源呢?开源发展历程和项目目前状态是怎么样的?

    CarbonDataCarbonData项目是华为公司从多年数据处理经验和行业理解中逐步积累起来的,2015年我们对系统进行了一次架构重构,使其演化为HDFS上的一套通用的列式存储,支持和Spark引擎对接后形成一套分布式OLAP分析的解决方案。

    华为一直是面向电信、金融、IT企业等用户提供大数据平台解决方案的供应商,从众多客户场景中我们不断提炼数据特征,总结出了一些典型的对大数据分析的诉求,逐步形成了CarbonData这个架构。

    因为在IT领域,只有开源开放,才能最终让更多的客户和合作伙伴的数据连接在一起,产生更大商业价值。开源是为了构建E2E生态,CarbonData是数据存储层技术,要发挥价值,需要与计算层、查询层有效集成在一起,形成完成真正的生态发挥价值。

    又因为Apache是目前大数据领域最权威的开源组织,其中的Hadoop,Spark已成为大数据开源的事实标准,我们也非常认可Apache以Community驱动技术进步的理念,所以我们选择进入Apache,与社区一同构建能力,使CarbonData融入大数据生态。

    目前CarbonData开源项目已经在6月3日通过Apache社区投票,成功进入Apache孵化器。github地址:https://github.com/apache/incubator-carbondata。欢迎大家参与到Apache CarbonData社区: https://github.com/apache/incubator-carbondata/blob/master/docs/How-to-contribute-to-Apache-CarbonData.md。

    InfoQ:请问是什么原因或机遇促使您们产生做CarbonData这个项目的想法的?之前的项目中遇到什么样的困难?

    CarbonData我们一直面临着很多高性能数据分析诉求,在传统的做法里,一般是使用数据库加BI工具实现报表、DashBoard和交互式查询等业务,但随着企业数据日益增大,业务驱动的分析灵活性要求逐渐增大,也有部分客户希望有除SQL外更强大的分析功能,所以传统的方式渐渐满足不了客户需求,让我们产生了做CarbonData这个项目的想法。

    需求一般来源于几方面。

    第一,在部署上,区别于以往的单机系统,企业客户希望有一套分布式方案来应对日益增多的数据,随时可以通过增加通用服务器的方式scale out横向扩展。

    第二,在业务功能上,很多企业的业务都处在从传统数据库逐渐转移到大数据平台的迁移过程中,这就要求大数据平台要有较高兼容老业务的能力,这里面主要包含的是对完整的标准SQL支持,以及多种分析场景的支持。同时为了节约成本,企业希望“一份数据支持多种使用场景”,例如大规模扫描和计算的批处理场景,OLAP多维交互式分析场景,明细数据即席查询,主键低时延点查,以及对实时数据的实时查询等场景,都希望平台能给予支持,且达到秒级查询响应。

    第三,在易用性上,企业客户以往使用BI工具,业务分析的OLAP模型是需要在BI工具中建立的,这就会导致有的场景下数据模型的灵活性和分析手段受到限制,而在大数据时代,大数据开源领域已经形成了一个生态系统,社区随时都在进步,经常会冒出一些新型的分析工具,所以企业客户都希望能跟随社区不断改进自己的系统,在自己的数据里快速用上新型的分析工具,得到更大的商业价值。

    要同时达到上诉要求,无疑对大数据平台是一个很大的挑战。为了满足这些要求,我们开始不断在实际项目中积累经验,也尝试了很多不同的解决方案,但都没有发现能用一套方案解决所有问题。

    大家首先会想到的是,在涉及到低时延查询的分布式存储中,一般常用的是KV型NoSQL数据库(如HBase,Cassandra),可以解决主键低时延查询的问题,但如果业务的查询模式稍作改变,例如对多维度灵活组合的查询,就会使点查变为全表扫描,使性能急剧下降。有的场景下,这时可以通过加入二级索引来缓解该问题,但这又带来了二级索引的维护和同步等管理问题,所以KV型存储并不是解决企业问题的通用方案。

    那么,如果要解决通用的多维查询问题,有时我们会想到用多维时序数据库的方案(如Linkedin Pinot),他们的特点是数据都以时间序列的方式进入系统并经过数据预聚合和建立索引,因为是预计算,所以应对多维查询时非常快,数据也非常及时,同时具备多维分析和实时处理的优点,在性能监控、实时指标分析的场景里应用较多。但它在支持的查询类型上也有一定限制,因为做了数据预计算,所以这种架构一般无法应对明细数据查询,以及不支持Join多表关联分析,这无疑给企业使用场景带来了一定的限制。

    另外一类是搜索系统(如Apache Solr,ElasticSearch),搜索系统可以做多维汇总也可以查询明细数据,它也具备基于倒排索引的快速布尔查询,并发也较高,似乎正是我们希望寻找的方案。但在实际应用中我们发现两个问题:一是由于搜索系统一般是针对非结构化数据而设计的,系统的数据膨胀率一般都比较高,在企业关系型数据模型下数据存储不够紧凑,造成数据量较大,二是搜索系统的数据组织方式和计算引擎密切相关,这就导致了数据入库后只能用相应的搜索引擎处理,这又一定程度打破了企业客户希望应用多种社区分析工具的初衷,所以搜索系统也有他自己的适用场景。

    最后一类系统,就是目前社区里大量涌现的SQL on Hadoop方案,以Hive, SparkSQL, Flink为代表,这类系统的特点是计算和存储相分离,针对存储在HDFS上的文件提供标准SQL功能,他们在部署性和易用性上可以满足企业客户需求,业务场景上也能覆盖扫描,汇聚,详单等各类场景,可见可以将他们视为一类通用的解决方案。为了提高性能,Spark,Flink等开源项目通过不断优化自身架构提升计算性能,但提升重点都放在计算引擎和SQL优化器的增强上,在存储和数据组织上改进并不是重点。

    所以,可以看出当前的很多大数据系统虽然都能支持各类查询场景,但他们都是偏向某一类场景设计的,在不是其目标场景的情况下要么不支持要么退化为全表扫描,所以导致企业为了应对批处理,多维分析,明细数据查询等场景,客户常常需要通过复制多份数据,每种场景要维护一套数据。

    CarbonData的设计初衷正是为了打破这种限制,做到只保存一份数据,最优化地支撑多种使用场景


    InfoQ:能否具体谈谈CarbonData的技术架构?有何特征和优势呢?

    CarbonData整个大数据时代的开启,可以说是源自于Google的MapReduce论文,他引发了Hadoop开源项目以及后续一系列的生态发展。他的“伟大”之处在于计算和存储解耦的架构,使企业的部分业务(主要是批处理)从传统的垂直方案中解放出来,计算和存储可以按需扩展极大提升了业务发展的敏捷性,让众多企业普及了这一计算模式,从中受益。

    虽然MapReduce开启了大数据时代,但它是通过纯粹的暴力扫描+分布式计算来提升批处理性能,所以并不能解决客户对所有查询场景的低时延查询要求。

    在目前的生态中,最接近于客户要求的其实是搜索引擎类方案。通过良好的数据组织和索引,搜索引擎能提供多种快速的查询功能,但偏偏搜索引擎的存储层又和计算引擎是紧耦合的,并不符合企业对”一份数据,多种场景”的期望。

    这给了我们启发,我们何不为通用计算引擎打造更一个高效的数据组织来满足客户需求呢,做到既利用计算和存储解耦架构又能提供高性能查询。抱着这个想法,我们启动了CarbonData项目。针对更多的业务,使计算和存储相分离,这也成了CarbonData的架构设计理念

    确立了这个理念后,我们很自然地选择了基于HDFS+通用计算引擎的架构,因为这个架构可以很好地提供Scale out能力。下一步我们问自己这个架构里还缺什么?这个架构中,HDFS提供文件的复制和读写能力,计算引擎负责读取文件和分布式计算,分工很明确,可以说他们分别定位于解决存储管理和计算的问题。但不难看出,为了适应更多场景,HDFS做了很大的“牺牲”,它牺牲了对文件内容的理解,正是由于放弃了对文件内容的理解,导致计算只能通过全扫描的方式来进行,可以说最终导致的是存储和计算都无法很好的利用数据特征来做优化。

    所以针对这个问题,我们把CarbonData的发力重点放在对数据组织的优化上,通过数据组织最终是要提升IO性能和计算性能。为此,CarbonData做了如下工作。

    CarbonData基础特性

    1. 多维数据聚集:在入库时对数据按多个维度进行重新组织,使数据在“多维空间上更内聚”,在存储上获得更好的压缩率,在计算上获得更好的数据过滤效率。

    2. 带索引的列存文件结构:首先,CarbonData为多类场景设计了多个级别的索引,并融入了一些搜索的特性,有跨文件的多维索引,文件内的多维索引,每列的minmax索引,以及列内的倒排索引等。其次,为了适应HDFS的存储特点,CarbonData的索引和数据文件存放在一起,一部分索引本身就是数据,另一部分索引存放在文件的元数据结构中,他们都能随HDFS提供本地化的访问能力。

    3. 列组:整体上,CarbonData是一种列存结构,但相对于行存来说,列存结构在应对明细数据查询时会有数据还原代价高的问题,所以为了提升明显数据查询性能,CarbonData支持列组的存储方式,用户可以把某些不常作为过滤条件但又需要作为结果集返回的字段作为列组来存储,经过CarbonData编码后会将这些字段使用行存的方式来存储以提升查询性能。

    4. 数据类型:目前CarbonData支持所有数据库的常用基本类型,以及Array,Struct复杂嵌套类型。同时社区也有人提出支持Map数据类型,我们计划未来添加Map数据类型。

    5. 压缩:目前CarbonData支持Snappy压缩,压缩是针对每列分别进行的,因为列存的特点使得压缩非常高效。数据压缩率基于应用场景不同一般在2到8之间。

    6. Hadoop集成:通过支持InputFormat/OutputFormat接口,CarbonData可以利用Hadoop的分布式优点,也能在所有以Hadoop为基础的生态系统中使用。

    CarbonData高级特性

    1. 可计算的编码方式:除了常见的Delta,RLE,Dictionary,BitPacking等编码方式外,CarbonData还支持将多列进行联合编码,以及应用了全局字典编码来实现免解码的计算,计算框架可以直接使用经过编码的数据来做聚合,排序等计算,这对需要大量shuffle的查询来说性能提升非常明显。

    2. 与计算引擎联合优化:为了高效利用CarbonData经过优化后的数据组织,CarbonData提供了有针对性的优化策略,目前CarbonData社区首先做了和Spark的深度集成,其中基于SparkSQL框架增强了过滤下压,延迟物化,增量入库等特性,同时支持所有DataFrame API。相信未来通过社区的努力,会有更多的计算框架与CarbonData集成,发挥数据组织的价值。

    目前这些特性都已经合入Apache CarbonData主干,欢迎大家使用。

    InfoQ:在哪些场景推荐使用呢?性能测试结果如何?有没有应用案例,目前在国内的使用情况和用户规模?

    CarbonData:推荐场景:希望一份存储同时满足快速扫描,多维分析,明细数据查询的场景。在华为的客户使用案例中,对比业界已有的列存方案,CarbonData可以带来5~30倍性能提升

    性能测试数据及应用案例等更多信息,请关注微信公众号ApacheCarbonData,及社区https://github.com/apache/incubator-carbondata。

    InfoQ:CarbonData能和当前正火的Spark完美结合吗?还能兼容哪些主流框架呢?

    CarbonData目前CarbonData已与Spark做了深度集成,具体见上述高级特性。

    InfoQ:您们的项目在未来有什么样的发展规划?还会增加什么功能吗?如何保证开源之后的项目的持续维护工作呢?

    CarbonData接下来社区重点工作是,提升系统易用性、完善生态集成(如:与Flink,Kafka等集成,实现数据实时导入CarbonData)。

    CarbonData开源的第一个月,就有几百个commits提交,和20多个贡献者参与,所以后续这个项目会持续的活跃。10多个核心贡献者也将会持续参与社区建设。

    InfoQ:在CarbonData设计研发并进入Apache孵化器的过程中,经历了哪些阶段,经历过的最大困难是什么?有什么样的感受或经验可以和大家分享的吗?

    CarbonDataCarbonData团队大多数人都有参与Apache Hadoop、Spark等社区开发的经验,我们对社区流程和工作方式都很熟悉。最大的困难是进入孵化器阶段,去说服Apache社区接纳大数据生态新的高性能数据格式CarbonData。我们通过5月份在美国奥斯丁的开源盛会OSCON上,做CarbonData技术主题演讲和现场DEMO演示,展示了CarbonData优秀的架构和良好的性能效果。

    InfoQ:您们是一个团队吗?如何保证您们团队的优秀成长?

    CarbonDataCarbonData团队是一个全球化的(工程师来自中国、美国、印度)团队,这种全球化工作模式的经验积累,让我们能快速的适应Apache开源社区工作模式。

    采访嘉宾:Apache CarbonData的PMC、Committers李昆、陈亮。

    posted @ 2016-09-06 15:49 小马歌 阅读(390) | 评论 (0)编辑 收藏
     
    from:http://www.tuicool.com/articles/fMf2quA


    FlameGraph

    火焰图 ,简单通过x轴横条宽度来度量时间指标,y轴代表线程栈的层次,简单明了, 容易找出具体的可有化点,非常方便,当然前提是我们通过profiler工具获取到profiler 数据。

    java profiler

    java性能调优时,我们经常会用到profiler工具,但是很多时候你可能不知道,大部分的 profiler工具都是有问题的 , ,简单来说,profiler:增加开销;修改了你的 代码,导致java编译器的优化行为不确定;同时影响了代码的层次,层次越深自然也影响 执行效率。

    当然如果你不是通过上面方式实现,二是通过获取on-cpu线程的线程栈方式,这又会带来 一个麻烦的问题:获取系统范围的线程栈,jvm必须处于safepoint 状态,只有当线 程处于safepoint状态的时候,别的线程才能去获取它的线程栈,而这个safepoint是由jvm 控制的,这对于profiler非常不利,有可能一个很热的代码块,jvm不会在该代码块中间放 置safepoint,导致profiler无法获得该线程栈,导致错误的profiler结果。

    上面的问题几个商用的profiler工具都存在,Oracle Solaris studio利用的是jvmti的一 个非标准接口AsyncGetCallTrace来实现,不存在上面问题,Jeremy Manson也利用该接口 实现了一个简单的profiler工具: Lightweight Asynchronous Sampling Profiler ,我们 的火焰图的数据来源就是通过它来获取的。

    lightweight-java-profiler

    当然,这个工具只支持hotspot的vm,需要你自己编译,有些问题需要注意:

    • 如果你需要在rhel上编译,需要安装4.6以上版本gcc ,4.4版本不支持。
    • 如果你需要在ubunt上编译,可能会碰到编译错误 。

    编译的时候,需要主要修改BITS参数,如果你要编译64Bit,使用命令:

    make BITS=64 all

    使用方法很简单,直接在你的启动命令上添加如下参数:

    -agentpath:path/to/liblagent.so[:file=name]

    启动之后,会在启动目录下生成trace.txt文件(缺省),该文件就是我们需要的采样数据。

    另外有几个参数可在编译时修改,都在global.h文件中。首先是采样的频率,缺省是100次 每秒;另外是最大采样的线程栈,缺省3000,超过3000就忽略(对于复杂的应用明显不够) ;最后是栈的深度,缺省是128(对于调用层次深的应用调大)。当然你记录的东西越多, 也会有性能损耗,我调成30000+256,一刻钟生成200M文件。

    另外特别需要注意,trace不是实时写入,而是在应用shutdown的时候才写入的,别kill应 用,否则trace里面什么都没有。

    posted @ 2016-08-31 16:22 小马歌 阅读(1360) | 评论 (0)编辑 收藏
     
    from:http://my.oschina.net/u/1244232/blog/546900

    摘要
    经过了九个月的实习,尝试了不同的机会,在公司从来没有碰到网络问题,国外网站访问毫无压力。临近毕业,返校写毕业论文,论文必须要有实验的支持,这个时候就免不了下载各种Jar包尝试不同的方法,但是碰到的第一个门槛就是网络访问。为了能够访问网络,下面提供几个常用的国内可以快速访问的远程仓库。

    国内:如何解决Maven和SBT下载Jar包太慢

    前言

    最近由于忙着写毕业论文,博客撰写暂时停止一段时间。
    经过了九个月的实习,尝试了不同的机会,在公司从来没有碰到网络问题,国外网站访问毫无压力。临近毕业,返校写毕业论文,论文必须要有实验的支持,这个时候就免不了下载各种Jar包尝试不同的方法,但是碰到的第一个门槛就是网络访问。为了能够访问网络,下面提供几个常用的国内可以快速访问的远程仓库。

    Maven 远程仓库

        <mirror>         <id>ui</id>         <mirrorOf>central</mirrorOf>         <name>Human Readable Name for this Mirror.</name>         <url>http://uk.maven.org/maven2/</url>       </mirror>       <mirror>         <id>ibiblio</id>         <mirrorOf>central</mirrorOf>         <name>Human Readable Name for this Mirror.</name>         <url>http://mirrors.ibiblio.org/pub/mirrors/maven2/</url>       </mirror>       <mirror>         <id>jboss-public-repository-group</id>         <mirrorOf>central</mirrorOf>         <name>JBoss Public Repository Group</name>         <url>http://repository.jboss.org/nexus/content/groups/public/</url>       </mirror>     <mirror>       <id>CN</id>       <name>OSChina Central</name>                                           <url>http://maven.oschina.net/content/groups/public/</url>       <mirrorOf>central</mirrorOf>     </mirror>     <mirror>         <id>repo2</id>         <mirrorOf>central</mirrorOf>         <name>Human Readable Name for this Mirror.</name>         <url>http://repo2.maven.org/maven2/</url>       </mirror> 

    说明:

    1. 上面的地址前面三个只适合maven,sbt的ivy不适合,sbt需要的jar包在里面会找不到,从下面的配置可以看出。
    2. oschina的镜像虽然都适用,但是访问速度真是慢
    3. 最全面的仓库在校园网完全没办法访问

    SBT

    修改SBT的远程仓库地址有很多办法,这里采用直接修改sbt-lauch.jar/sbt/sbt.boot.properties的方式

    [scala]   version: ${sbt.scala.version-auto}  [app]   org: ${sbt.organization-org.scala-sbt}   name: sbt   version: ${sbt.version-read(sbt.version)[0.13.9]}   class: ${sbt.main.class-sbt.xMain}   components: xsbti,extra   cross-versioned: ${sbt.cross.versioned-false}   resources: ${sbt.extraClasspath-}  [repositories]   local     Local-Maven-Repository: file:///D:/Java/java-repositories, [organization]/[module]/[revision]/[type]s/[artifact](-[classifier]).[ext]     ibiblio-maven:http://maven.ibiblio.org/maven2/     typesafe-ivy:https://dl.bintray.com/typesafe/ivy-releases/, [organization]/[module]/(scala_[scalaVersion]/)(sbt_[sbtVersion]/)[revision]/[type]s/[artifact](-[classifier]).[ext]    maven-central     uk-repository: http://uk.maven.org/maven2/     jboss-repository: http://repository.jboss.org/nexus/content/groups/public/  [boot]   directory: ${sbt.boot.directory-${sbt.global.base-${user.home}/.sbt}/boot/}  [ivy]   ivy-home: D:/Java/java-repositories   checksums: ${sbt.checksums-sha1,md5}   override-build-repos: ${sbt.override.build.repos-false}   repository-config: ${sbt.repository.config-${sbt.global.base-${user.home}/.sbt}/repositories} 

    说明:

    1. repositories 修改远程仓库地址
    2. typesafe-ivy:目的是兼容ivy地址
    3. ivy-home:指的是本地仓库地址,就是jar存在哪里
    posted @ 2016-08-31 16:22 小马歌 阅读(1970) | 评论 (0)编辑 收藏
     
         摘要: from:http://logos.name/archives/515虽然ES提供了replicas shards的机制来保证数据的完整性不会因为几个节点的奔溃而被破坏,但是定期的数据备份以备不时之需依然重要。此外,通过备份与恢复也可实现数据在不同集群间的迁移(直接复制data目录下的索引文件的做法我尝试过,但没有成功)。备份的方式在官方文档里有清楚的交代:先创建仓库(repository),再往...  阅读全文
    posted @ 2016-08-27 10:26 小马歌 阅读(416) | 评论 (0)编辑 收藏
     

    在技术方面,我自己热衷于 Open Source,写了很多 Open Source 的东西,擅长的是 Infrastructure 领域。Infrastructure 领域现在范围很广,比如说很典型的分布式 Scheduler、Mesos、Kubernetes,另外它和 Microservices 所结合的东西也特别多。Infrastructure 领域还有比如 Database 有分 AP(分析型)和 TP(事务型),比如说很典型的大家知道的 Spark、Greenplum、Apache Phoenix 等等,这些都属于在 AP 的,它们也会去尝试支持有限的 TP。另外,还有一个比较有意思的就是 Kudu——Cloudera Open Source 的那个项目,它的目标很有意思:我不做最强的 AP 系统,也不做最强的 TP 系统,我选择一个相对折中的方案。从文化哲学上看,它比较符合中国的中庸思想。

    另外,我先后创建了 Codis、TiDB。去年12月份创建了 TiKV 这个 project,TiKV 在所有的 rust 项目里目前排名前三。

    首先我们聊聊 Database 的历史,在已经有这么多种数据库的背景下我们为什么要创建另外一个数据库;以及说一下现在方案遇到的困境,说一下 Google Spanner 和 F1、TiKV 和 TiDB,说一下架构的事情,在这里我们会重点聊一下 TiKV。因为我们产品的很多特性是 TiKV 提供的,比如说跨数据中心的复制、Transaction、auto-scale。

    接下来聊一下为什么 TiKV 用 Raft 能实现所有这些重要的特性,以及 scale、MVCC 和事务模型。东西非常多,我今天不太可能把里面的技术细节都描述得特别细,因为几乎每一个话题都可以找到一篇或者是多篇论文,所以详细的技术问题大家可以单独来找我聊。

    后面再说一下我们现在遇到的窘境,就是大家常规遇到的分布式方案有哪些问题,比如 MySQL Sharding。我们创建了无数 MySQL Proxy,比如官方的 MySQL proxy、Youtube 的 Vitess、淘宝的 Cobar、TDDL以及基于 Cobar 的 MyCAT、金山的 Kingshard、360 的 Atlas、京东的 JProxy,我在豌豆荚也写了一个。可以说,随便一个大公司都会造一个 MySQL Sharding 的方案。

    为什么我们要创建另外一个数据库?

    昨天晚上我还跟一个同学聊到,基于 MySQL 的方案它的天花板在哪里,它的天花板特别明显。有一个思路是能不能通过 MySQL 的 server 把 InnoDB 变成一个分布式数据库,听起来这个方案很完美,但是很快就会遇到天花板。因为 MySQL 生成的执行计划是个单机的,它认为整个计划的 cost 也是单机的,我读取一行和读取下一行之间的开销是很小的,比如迭代 next row 可以立刻拿到下一行。实际上在一个分布式系统里面,这是不一定的。

    另外,你把数据都拿回来计算这个太慢了,很多时候我们需要把我们的 expression 或者计算过程等等运算推下去,向上返回一个最终的计算结果,这个一定要用分布式的 plan,前面控制执行计划的节点,它必须要理解下面是分布式的东西,才能生成最好的 plan,这样才能实现最高的执行效率。

    比如说你做一个 sum,你是一条条拿回来加,还是让一堆机器一起算,最后给我一个结果。 例如我有 100 亿条数据分布在 10 台机器上,并行在这 10台机器我可能只拿到 10 个结果,如果把所有的数据每一条都拿回来,这就太慢了,完全丧失了分布式的价值。聊到 MySQL 想实现分布式,另外一个实现分布式的方案就是 Proxy。但是 Proxy 本身的天花板在那里,就是它不支持分布式的 transaction,它不支持跨节点的 join,它无法理解复杂的 plan,一个复杂的 plan 打到 Proxy 上面,Proxy 就傻了,我到底应该往哪一个节点上转发呢,如果我涉及到 subquery sql 怎么办?所以这个天花板是瞬间会到,在传统模型下面的修改,很快会达不到我们的要求。

    另外一个很重要的是,MySQL 支持的复制方式是半同步或者是异步,但是半同步可以降级成异步,也就是说任何时候数据出了问题你不敢切换,因为有可能是异步复制,有一部分数据还没有同步过来,这时候切换数据就不一致了。前一阵子出现过某公司突然不能支付了这种事件,今年有很多这种类似的 case,所以微博上大家都在说“说好的异地多活呢?”……

    为什么传统的方案在这上面解决起来特别的困难,天花板马上到了,基本上不可能解决这个问题。另外是多数据中心的复制和数据中心的容灾,MySQL 在这上面是做不好的。

    在前面三十年基本上是关系数据库的时代,那个时代创建了很多伟大的公司,比如说 IBM、Oracle、微软也有自己的数据库,早期还有一个公司叫 Sybase,有一部分特别老的程序员同学在当年的教程里面还可以找到这些东西,但是现在基本上看不到了。

    另外是 NoSQL。NoSQL 也是一度非常火,像 Cassandra、MongoDB 等等,这些都属于在互联网快速发展的时候创建这些能够 scale 的方案,但 Redis scale 出来比较晚,所以很多时候大家把 Redis 当成一个 Cache,现在慢慢大家把它当成存储不那么重要的数据的数据库。因为它有了 scale 支持以后,大家会把更多的数据放在里面。

    然后到了 2015,严格来讲是到 2014 年到 2015 年之间,Raft 论文发表以后,真正的 NewSQL 的理论基础终于完成了。我觉得 NewSQL 这个理论基础,最重要的划时代的几篇论文,一个是谷歌的 Spanner,是在 2013 年初发布的;再就是 Raft 是在 2014 年上半年发布的。这几篇相当于打下了分布式数据库 NewSQL 的理论基础,这个模型是非常重要的,如果没有模型在上面是堆不起来东西的。说到现在,大家可能对于模型还是可以理解的,但是对于它的实现难度很难想象。

    前面我大概提到了我们为什么需要另外一个数据库,说到 Scalability 数据的伸缩,然后我们讲到需要 SQL,比如你给我一个纯粹的 key-velue 系统的 API,比如我要查找年龄在 10 岁到 20 岁之间的 email 要满足一个什么要求的。如果只有 KV 的 API 这是会写死人的,要写很多代码,但是实际上用 SQL 写一句话就可以了,而且 SQL 的优化器对整个数据的分布是知道的,它可以很快理解你这个 SQL,然后会得到一个最优的 plan,他得到这个最优的 plan 基本上等价于一个真正理解 KV 每一步操作的人写出来的程序。通常情况下,SQL 的优化器是为了更加了解或者做出更好的选择。

    另外一个就是 ACID 的事务,这是传统数据库必须要提供的基础。以前你不提供 ACID 就不能叫数据库,但是近些年大家写一个内存的 map 也可以叫自己是数据库。大家写一个 append-only 文件,我们也可以叫只读数据库,数据库的概念比以前极大的泛化了。

    另外就是高可用和自动恢复,他们的概念是什么呢?有些人会有一些误解,因为今天还有朋友在现场问到,出了故障,比如说一个机房挂掉以后我应该怎么做切换,怎么操作。这个实际上相当于还是上一代的概念,还需要人去干预,这种不算是高可用。

    未来的高可用一定是系统出了问题马上可以自动恢复,马上可以变成可用。比如说一个机房挂掉了,十秒钟不能支付,十秒钟之后系统自动恢复了变得可以支付,即使这个数据中心再也不起来我整个系统仍然是可以支付的。Auto-Failover 的重要性就在这里。大家不希望在睡觉的时候被一个报警给拉起来,我相信大家以后具备这样一个能力,5 分钟以内的报警不用理会,挂掉一个机房,又挂掉一个机房,这种连续报警才会理。我们内部开玩笑说,希望大家都能睡个好觉,很重要的事情就是这个。

    说完应用层的事情,现在很有很多业务,在应用层自己去分片,比如说我按照 user ID在代码里面分片,还有一部分是更高级一点我会用到一致性哈希。问题在于它的复杂度,到一定程度之后我自动的分库,自动的分表,我觉得下一代数据库是不需要理解这些东西的,不需要了解什么叫做分库,不需要了解什么叫做分表,因为系统是全部自动搞定的。同时复杂度,如果一个应用不支持事务,那么在应用层去做,通常的做法是引入一个外部队列,引入大量的程序机制和状态转换,A 状态的时候允许转换到 B 状态,B 状态允许转换到 C 状态。

    举一个简单的例子,比如说在京东上买东西,先下订单,支付状态之后这个商品才能出库,如果不是支付状态一定不能出库,每一步都有严格的流程。

    Google Spanner / F1

    说一下 Google 的 Spanner 和 F1,这是我非常喜欢的论文,也是我最近几年看过很多遍的论文。 Google Spanner 已经强大到什么程度呢?Google Spanner 是全球分布的数据库,在国内目前普遍做法叫做同城两地三中心,它们的差别是什么呢?以 Google 的数据来讲,谷歌比较高的级别是他们有 7 个副本,通常是美国保存 3 个副本,再在另外 2 个国家可以保存 2 个副本,这样的好处是万一美国两个数据中心出了问题,那整个系统还能继续可用,这个概念就是比如美国 3 个副本全挂了,整个数据都还在,这个数据安全级别比很多国家的安全级别还要高,这是 Google 目前做到的,这是全球分布的好处。

    现在国内主流的做法是两地三中心,但现在基本上都不能自动切换。大家可以看到很多号称实现了两地三中心或者异地多活,但是一出现问题都说不好意思这段时间我不能提供服务了。大家无数次的见到这种 case, 我就不列举了。

    Spanner 现在也提供一部分 SQL 特性。在以前,大部分 SQL 特性是在 F1 里面提供的,现在 Spanner 也在逐步丰富它的功能,Google 是全球第一个做到这个规模或者是做到这个级别的数据库。事务支持里面 Google 有点黑科技(其实也没有那么黑),就是它有GPS 时钟和原子钟。大家知道在分布式系统里面,比如说数千台机器,两个事务启动先后顺序,这个顺序怎么界定(事务外部一致性)。这个时候 Google 内部使用了 GPS 时钟和原子钟,正常情况下它会使用一个GPS 时钟的一个集群,就是说我拿的一个时间戳,并不是从一个 GPS 上来拿的时间戳,因为大家知道所有的硬件都会有误差。如果这时候我从一个上拿到的 GPS 本身有点问题,那么你拿到的这个时钟是不精确的。而 Google 它实际上是在一批 GPS 时钟上去拿了能够满足 majority 的精度,再用时间的算法,得到一个比较精确的时间。大家知道 GPS 也不太安全,因为它是美国军方的,对于 Google 来讲要实现比国家安全级别更高的数据库,而 GPS 是可能受到干扰的,因为 GPS 信号是可以调整的,这在军事用途上面很典型的,大家知道导弹的制导需要依赖 GPS,如果调整了 GPS 精度,那么导弹精度就废了。所以他们还用原子钟去校正 GPS,如果 GPS 突然跳跃了,原子钟上是可以检测到 GPS 跳跃的,这部分相对有一点黑科技,但是从原理上来讲还是比较简单,比较好理解的。

    最开始它 Spanner 最大的用户就是 Google 的 Adwords,这是 Google 最赚钱的业务,Google 就是靠广告生存的,我们一直觉得 Google 是科技公司,但是他的钱是从广告那来的,所以一定程度来讲 Google 是一个广告公司。Google 内部的方向先有了 Big table ,然后有了 MegaStore ,MegaStore 的下一代是 Spanner ,F1 是在 Spanner 上面构建的。

    TiDB and TiKV

    TiKV 和 TiDB 基本上对应 Google Spanner 和 Google F1,用 Open Source 方式重建。目前这两个项目都开放在 GitHub 上面,两个项目都比较火爆,TiDB 是更早一点开源的, 目前 TiDB 在 GitHub 上 有 4300 多个 Star,每天都在增长。

    另外,对于现在的社会来讲,我们觉得 Infrastructure 领域闭源的东西是没有任何生存机会的。没有任何一家公司,愿意把自己的身家性命压在一个闭源的项目上。举一个很典型的例子,在美国有一个数据库叫 FoundationDB,去年被苹果收购了。FoundationDB 之前和用户签的合约都是一年的合约。比如说,我给你服务周期是一年,现在我被另外一个公司收购了,我今年服务到期之后,我是满足合约的。但是其他公司再也不能找它服务了,因为它现在不叫 FoundationDB 了,它叫 Apple了,你不能找 Apple 给你提供一个 Enterprise service。

    TiDB 和 TiKV 为什么是两个项目,因为它和 Google 的内部架构对比差不多是这样的:TiKV 对应的是 Spanner,TiDB 对应的是 F1 。F1 里面更强调上层的分布式的 SQL 层到底怎么做,分布式的 Plan 应该怎么做,分布式的 Plan 应该怎么去做优化。同时 TiDB 有一点做的比较好的是,它兼容了 MySQL 协议,当你出现了一个新型的数据库的时候,用户使用它是有成本的。大家都知道作为开发很讨厌的一个事情就是,我要每个语言都写一个 Driver,比如说你要支持 C++、你要支持 Java、你要支持 Go 等等,这个太累了,而且用户还得改他的程序,所以我们选择了一个更加好的东西兼容 MySQL 协议,让用户可以不用改。一会我会用一个视频来演示一下,为什么一行代码不改就可以用,用户就能体会到 TiDB 带来的所有的好处。

    这个图实际上是整个协议栈或者是整个软件栈的实现。大家可以看到整个系统是高度分层的,从最底下开始是 RocksDB ,然后再上面用 Raft 构建一层可以被复制的 RocksDB ,在这一层的时候它还没有 Transaction,但是整个系统现在的状态是所有写入的数据一定要保证它复制到了足够多的副本。也就是说只要我写进来的数据一定有足够多的副本去 cover 它,这样才比较安全,在一个比较安全的 Key-value store 上面, 再去构建它的多版本,再去构建它的分布式事务,然后在分布式事务构建完成之后,就可以轻松的加上 SQL 层,再轻松的加上MySQL 协议的支持。然后,这两天我比较好奇,自己写了 MongoDB 协议的支持,然后我们可以用 MongoDB 的客户端来玩,就是说协议这一层是高度可插拔的。TiDB 上可以在上面构建一个 MongoDB 的协议,相当于这个是构建一个 SQL 的协议,可以构建一个 NoSQL 的协议。这一点主要是用来验证 TiKV 在模型上面的支持能力。

    这是整个 TiKV 的架构图,从这个看来,整个集群里面有很多 Node,比如这里画了四个 Node ,分别对应了四个机器。每一个 Node 上可以有多个 Store,每个 Store 里面又会有很多小的 Region,就是说一小片数据,就是一个 Region 。从全局来看所有的数据被划分成很多小片,每个小片默认配置是 64M,它已经足够小,可以很轻松的从一个节点移到另外一个节点,Region 1 有三个副本,它分别在 Node1、Node 2 和 Node4 上面, 类似的Region 2,Region 3 也是有三个副本。每个 Region 的所有副本组成一个 Raft Group,整个系统可以看到很多这样的 Raft groups。

    Raft 细节我不展开了,大家有兴趣可以找我私聊或者看一下相应的资料。

    因为整个系统里面我们可以看到上一张图里面有很多 Raft group 给我们,不同 Raft group 之间的通讯都是有开销的。所以我们有一个类似于 MySQL 的 group commit 机制 ,你发消息的时候实际上可以 share 同一个 connection , 然后 pipeline + batch 发送,很大程度上可以省掉大量 syscall 的开销。

    另外,其实在一定程度上后面我们在支持压缩的时候,也有非常大的帮助,就是可以减少数据的传输。对于整个系统而言,可能有数百万的 Region,它的大小可以调整,比如说 64M、128M、256M,这个实际上依赖于整个系统里面当前的状况。

    比如说我们曾经在有一个用户的机房里面做过测试,这个测试有一个香港机房和新加坡的机房。结果我们在做复制的时候,新加坡的机房大于 256M 就复制不过去,因为机房很不稳定,必须要保证数据切的足够小,这样才能复制过去。

    如果一个 Region 太大以后我们会自动做 SPLIT,这是非常好玩的过程,有点像细胞的分裂。

    然后 TiKV 的 Raft 实现,是从 etcd 里面 port 过来的,为什么要从 etcd 里面 port 过来呢?首先 TiKV 的 Raft 实现是用 Rust 写的。作为第一个做到生产级别的 Raft 实现,所以我们从 etcd 里面把它用 Go 语言写的 port 到这边。

    这个是 Raft 官网上面列出来的 TiKV在里面的状态,大家可以看到 TiKV 把所有 Raft 的 feature 都实现了。 比如说 Leader Election、Membership Changes,这个是非常重要的,整个系统的 scale 过程高度依赖 Membership Changes,后面我用一个图来讲这个过程。后面这个是 Log Compaction,这个用户不太关心。

    这是很典型的细胞分裂的图,实际上 Region 的分裂过程和这个是类似的。

    我们看一下扩容是怎么做的。

    比如说以现在的系统假设,我们刚开始说只有三个节点,有 Region1 分别是在 1 、2、4,我用虚线连接起来代表它是一个 Raft group ,大家可以看到整个系统里面有三个 Raft group ,在每一个 Node 上面数据的分布是比较均匀的,在这个假设每一个 Region 是 64M ,相当于只有一个 Node 上面负载比其他的稍微大一点点。

    一个在线视频默认我们都是推荐 3 个副本或者 5 个副本的配置。Raft 本身有一个特点,如果一个 leader down 掉之后,其它的节点会选一个新的 leader ,那么这个新的 leader 会把它还没有 commit 但已经 reply 过去的 log 做一个 commit ,然后会再做 apply ,这个有点偏 Raft 协议,细节我不讲了。

    复制数据的小的 Region,它实际上是跨多个数据中心做的复制。这里面最重要的一点是永远不丢失数据,无论如何我保证我的复制一定是复制到 majority ,任何时候我只要对外提供服务,允许外面写入数据一定要复制到 majority 。很重要的一点就是恢复的过程一定要是自动化的,我前面已经强调过,如果不能自动化恢复,那么中间的宕机时间或者对外不可服务的时间,便不是由整个系统决定的,这是相对回到了几十年前的状态。

    MVCC

    MVCC 我稍微仔细讲一下这一块。MVCC 的好处,它很好支持 Lock-free 的 snapshot read ,一会儿我有一个图会展示 MVCC 是怎么做的。isolation level 就不讲了, MySQL 里面的级别是可以调的,我们的 TiKV 有 SI,还有 SI+lock,默认是支持 SI 的这种隔离级别,然后你写一个 select for update 语句,这个会自动的调整到 SI 加上 lock 这个隔离级别。这个隔离级别基本上和 SSI 是一致的。还有一个就是 GC 的问题,如果你的系统里面的数据产生了很多版本,你需要把这个比较老的数据给 GC 掉,比如说正常情况下我们是不删除数据的, 你写入一行,然后再写入一行,不断去 update 同一行的时候,每一次 update 会产生新的版本,新的版本就会在系统里存在,所以我们需要一个 GC 的模块把比较老的数据给 GC 掉,实际上这个 GC 不是 Go 里面的GC,不是 Java 的 GC,而是数据的 GC。

    这是一个数据版本,大家可以看到我们的数据分成两块,一个是 meta,一个是 data。meta 相对于描述我的数据当前有多少个版本。大家可以看到绿色的部分,比如说我们的 meta key 是 A ,keyA 有三个版本,是 A1 、A2、A3,我们把 key 自己和 version 拼到一起。那我们用 A1、A2、A3 分别描述 A 的三个版本,那么就是 version 1/2/3。meta 里面描述,就是我的整个 key 相对应哪个版本,我想找到那个版本。比如说我现在要读取 key A 的版本10,但显然现在版本 10 是没有的,那么小于版本 10 最大的版本是 3,所以这时我就能读取到 3,这是它的隔离级别决定的。关于 data,我刚才已经讲过了。

    分布式事务模型

    接下来是分布式事务模型,其实是基于 Google Percolator,这是 Google 在 2006 发表的一篇论文,是 Google 在做内部增量处理的时候发现了这个方法,本质上还是二阶段提交的。这使用的是一个乐观锁,比如说我提供一个 transaction ,我去改一个东西,改的时候是发布在本地的,并没有马上 commit 到数据存储那一端,这个模型就是说,我修改的东西我马上去 Lock 住,这个基本就是一个悲观锁。但如果到最后一刻我才提交出去,那么锁住的这一小段的时间,这个时候实现的是乐观锁。乐观锁的好处就是当你冲突很小的时候可以得到非常好的性能,因为冲突特别小,所以我本地修改通常都是有效的,所以我不需要去 Lock ,不需要去 roll back 。本质上分布式事务就是 2PC (两阶段提交) 或者是 2+x PC,基本上没有 1PC,除非你在别人的级别上做弱化。比如说我允许你读到当前最新的版本,也允许你读到前面的版本,书里面把这个叫做幻读。如果你调到这个程度是比较容易做 1PC 的,这个实际上还是依赖用户设定的隔离级别的,如果用户需要更高的隔离级别,这个 1PC就不太好做了。

    这是一个路由,正常来讲,大家可能会好奇一个 SQL 语句怎么最后会落到存储层,然后能很好的运行,最后怎么能映射到 KV 上面,又怎么能路由到正确的节点,因为整个系统可能有上千个节点,你怎么能正确路由到那一个的节点。我们在 TiDB 有一个 TiKV driver , 另外 TiKV 对外使用的是 Google Protocol Buffer 来作为通讯的编码格式。

    Placement Driver

    来说一下 Placement Driver 。Placement Driver 是什么呢?整个系统里面有一个节点,它会时刻知道现在整个系统的状态。比如说每个机器的负载,每个机器的容量,是否有新加的机器,新加机器的容量到底是怎么样的,是不是可以把一部分数据挪过去,是不是也是一样下线, 如果一个节点在十分钟之内无法被其他节点探测到,我认为它已经挂了,不管它实际上是不是真的挂了,但是我也认为它挂了。因为这个时候是有风险的,如果这个机器万一真的挂了,意味着你现在机器的副本数只有两个,有一部分数据的副本数只有两个。那么现在你必须马上要在系统里面重新选一台机器出来,它上面有足够的空间,让我现在只有两个副本的数据重新再做一份新的复制,系统始终维持在三个副本。整个系统里面如果机器挂掉了,副本数少了,这个时候应该会被自动发现,马上补充新的副本,这样会维持整个系统的副本数。这是很重要的 ,为了避免数据丢失,必须维持足够的副本数,因为副本数每少一个,你的风险就会再增加。这就是 Placement Driver 做的事情。

    同时,Placement Driver 还会根据性能负载,不断去 move 这个 data 。比如说你这边负载已经很高了,一个磁盘假设有 100G,现在已经用了 80G,另外一个机器上也是 100G,但是他只用了 20G,所以这上面还可以有几十 G 的数据,比如 40G 的数据,你可以 move 过去,这样可以保证系统有很好的负载,不会出现一个磁盘巨忙无比,数据已经多的装不下了,另外一个上面还没有东西,这是 Placement Driver 要做的东西。

    Raft 协议还提供一个很高级的特性叫 leader transfer。leader transfer 就是说在我不移动数据的时候,我把我的 leadership 给你,相当于从这个角度来讲,我把流量分给你,因为我是 leader,所以数据会到我这来,但我现在把 leader给你,我让你来当 leader,原来打给我的请求会被打给你,这样我的负载就降下来。这就可以很好的动态调整整个系统的负载,同时又不搬移数据。不搬移数据的好处就是,不会形成一个抖动。

    MySQL Sharding

    MySQL Sharding 我前面已经提到了它的各种天花板,MySQL Sharding 的方案很典型的就是解决基本问题以后,业务稍微复杂一点,你在 sharding 这一层根本搞不定。它永远需要一个 sharding key,你必须要告诉我的 proxy,我的数据要到哪里找,对用户来说是极不友好的,比如我现在是一个单机的,现在我要切入到一个分布式的环境,这时我必须要改我的代码,我必须要知道我这个 key ,我的 row 应该往哪里 Sharding。如果是用 ORM ,这个基本上就没法做这个事情了。有很多 ORM 它本身假设我后面只有一个 MySQL。但 TiDB 就可以很好的支持,因为我所有的角色都是对的,我不需要关注 Sharding 、分库、分表这类的事情。

    这里面有一个很重要的问题没有提,我怎么做 DDL。如果这个表非常大的话,比如说我们有一百亿吧,横跨了四台机器,这个时候你要给它做一个新的 Index,就是我要添加一个新的索引,这个时候你必须要不影响任何现有的业务,实际上这是多阶段提交的算法,这个是 Google 和 F1 一起发出来那篇论文。

    简单来讲是这样的,先把状态标记成 delete only ,delete only 是什么意思呢?因为在分布式系统里面,所有的系统对于 schema 的视野不是一致的,比如说我现在改了一个值,有一部分人发现这个值被改了,但是还有一部分人还没有开始访问这个,所以根本不知道它被改了。然后在一个分布系统里,你也不可能实时通知到所有人在同一时刻发现它改变了。比如说从有索引到没有索引,你不能一步切过去,因为有的人认为它有索引,所以他给它建了一个索引,但是另外一个机器他认为它没有索引,所以他就把数据给删了,索引就留在里面了。这样遇到一个问题,我通过索引找的时候告诉我有, 实际数据却没有了,这个时候一致性出了问题。比如说我 count 一个 email 等于多少的,我通过 email 建了一个索引,我认为它是在,但是 UID 再转过去的时候可能已经不存在了。

    比如说我先标记成 delete only,我删除它的时候不管它现在有没有索引,我都会尝试删除索引,所以我的数据是干净的。如果我删除掉的话,我不管结果是什么样的,我尝试去删一下,可能这个索引还没 build 出来,但是我仍然删除,如果数据没有了,索引一定没有了,所以这可以很好的保持它的一致性。后面再类似于前面,先标记成 write only 这种方式,连续再迭代这个状态,就可以迭代到一个最终可以对外公开的状态。比如说当我迭代到一定程度的时候,我可以从后台 build index ,比如说我一百亿,正在操作的 index 会马上 build,但是还有很多没有 build index ,这个时候后台不断的跑 map-reduce 去 build index ,直到整个都 build 完成之后,再对外 public ,就是说我这个索引已经可用了,你可以直接拿索引来找,这个是非常经典的。在这个 Online,Asynchronous Schema Change in F1 paper之前,大家都不知道这事该怎么做。

    Proxy Sharding 的方案不支持分布式事务,更不用说跨数据中心的一致性事务了。 TiKV 很好的支持 transaction,刚才提到的 Raft 除了增加副本之外,还有 leader transfer,这是一个传统的方案都无法提供的特性。以及它带来的好处,当我瞬间平衡整个系统负载的时候,对外是透明的, 做 leader transfer 的时候并不需要移动数据,只是个简单的 leader transfer 消息。

    然后说一下如果大家想参与我们项目的话是怎样的过程,因为整个系统是完全开源的,如果大家想参与其中任何一部分都可以,比如说我想参与到分布式 KV,可以直接贡献到 TiKV。TiKV 需要写 Rust,如果大家对这块特别有激情可以体验写 Rust 的感觉 。

    TiDB 是用 Go 写的,Go 在中国的群众基础是非常多的,目前也有很多人在贡献。整个 TiDB 和TiKV 是高度协作的项目,因为 TiDB 目前还用到了 etcd ,我们在和 CoreOS 在密切的合作,也特别感谢 CoreOS 帮我们做了很多的支持,我们也为 CoreOS 的 etcd 提了一些 patch。同时,TiKV 使用 RocksDB ,所以我们也为 RocksDB 提了一些 patch 和 test,我们也非常感谢 Facebook RocksDB team 对我们项目的支持。

    另外一个是 PD,就是我们前面提的 Placement Driver,它负责监控整个系统。这部分的算法比较好玩,大家如果有兴趣的话,可以去自己控制整个集群的调度,它和 Kubernetes 或者是Mesos 的调度算法是不一样的,因为它调度的维度实际上比那个要更多。比如说磁盘的容量,你的 leader 的数量,你的网络当前的使用情况,你的 IO 的负载和 CPU 的负载都可以放进去。同时你还可以让它调度不要跨一个机房里面建多个副本。

    posted @ 2016-08-23 15:12 小马歌 阅读(283) | 评论 (0)编辑 收藏
     
    from:http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0831/3395.html

    编辑推荐:稀土掘金,这是一个针对技术开发者的一个应用,你可以在掘金上获取最新最优质的技术干货,不仅仅是Android知识、前端、后端以至于产品和设计都有涉猎,想成为全栈工程师的朋友不要错过!

    原文:http://www.csdn.net/article/2015-08-31/2825579 

    JSON——大家可能都知道它是几乎所有现代服务器都使用的轻量级数据交换格式。它体量轻,可读性强,通常比老式的、不友好的XML开发起来更加便捷友好。JSON是不依赖于开发语言的数据格式,但是在解析数据并将其转换到如Java对象时,会消耗我们的时间和存储资源。

    几天前,Facebook宣布,其Android应用程序大幅提升了数据处理性能。这是由于几乎在全部应用程序中放弃了JSON数据格式,用FlatBuffers取而代之了。阅读这篇文章可以获得关于FlatBuffers的基础知识,学会如何从JSON转换到FlatBuffers。

    虽然这东西是非常有前景的,但是乍一看其实现过程不是一下子就能明白的。而且Facebook也没有说得很详细。这就是为什么我要写这篇文章,在其中展示我们是如何使用Flatbuffers开展工作的。

    FlatBuffers

    总之,FlatBuffers是Google专门为游戏开发而创建的跨平台序列化库,就像Facebook所展示的那样,它在Android平台上遵循快速响应UI的16ms规则

    但是,在把所有数据迁移到FlatBuffers之前,你要确定确实需要这样做。因为,这样做有时对性能的影响是潜移默化的,而且数据安全性要比计算速度上几十毫秒的差异更重要。

    什么使得Flatbuffers如此奏效?


    • 由于是以二进制形式缓存,访问序列化数据时也无需数据解析过程。即使对于层次化数据也不需要解析。多亏不需要初始化解析器(初始化意味着要建立复杂的字段映射)和解析数据,这些都是需要花费时间的。

    • Flatbuffers数据不需要分配比自身使用缓冲区还要多的内存。我们不必像在JSON中那样为解析数据的整个层次分配额外对象。

    要获得正宗的数据,就再读一读Facebook上关于FlatBuffers迁移问题的文章,还有Google自己的文档。


    实现

    本文将介绍在Android应用程序中使用Flatbuffers的最简单方法:


    • JSON数据在应用程序之外的某个地方被转换成FlatBuffer格式的文件(例如,将二进制数据以文件的形式提交,还可以从API直接返回FlatBuffer二进制文件)。

    • 在flatc (FlatBuffer编译器)的帮助下,手工生成数据模型(Java类)。

    • JSON文件存在一定的局限性(不能使用null字段,日期格式也被解析为字符串)。


    将来,我们或许会提出更复杂的解决方案。

    FlatBuffers编译器

    首先,我们需要flatc,即flatbuffers编译器。该编译器可以从Google所属的源代码构建,源代码位于Flatbuffers资源库中。我们下载并克隆它。整个构建过程在FlatBuffers构建文档中都做了描述。如果你是Mac用户的话,需要这样来构建:


    1. 在\{extract directory}\build\XcodeFlatBuffers.xcodeproj路径下,打开已下载的源代码。

    2. 点击Play按钮或⌘ + R,运行flatc scheme(默认情况下应该是被选中的)。

    3. flatc可执行文件就会在项目的根目录下出现。


    现在,我们可以使用schema编译器了,该编译器能够把给定的schema(在Java、C#、Python、GO和C++语言中的schema)生成为模型类,还可以把JSON转换成Flatbuffer的二进制文件。

    Schema文件

    接着,我们必须准备schema文件,该文件定义了要进行序列化和反序列化的数据结构。这个schema将用于flatc创建Java模型,把JSON转换成FlatBuffers的二进制文件。

    这里是JSON文件的一部分。

    完整版本在这里。这是略微修改后的版本,可以从Github API调用:https://api.github.com/users/google/repos 。

    Flatbuffer schema是编写得很好的文档,所以就不深入探讨这个问题了。另外,本文中的schema不会很复杂。我们所要做的仅仅是创建3张表:ReposList, Repo和User,并定义root_type。这是schema的重要组成部分。

    完整的schema文件在这里

    FlatBuffers数据文件

    真棒,我们现在要做的是把repos_json.json转换成FlatBuffers二进制文件,生成能够以Java风格表示数据的Java模型(此处操作所需的全部文件都在我们的代码库中):

    $ ./flatc -j -b repos_schema.fbs repos_json.json

    如果一切顺利,会产生下列文件:


    • repos_json.bin(要将重命名它为 repos_flat.bin)

    • Repos/Repo.java

    • Repos/ReposList.java

    • Repos/User.java


    Android应用程序

    现在来创建示例程序,在实践中来看看Flatbuffers格式是如何起作用的。这是截图:

    blob.png

    在UI部分,ProgressBar仅用于显示不恰当的数据处理对用户界面顺畅度的影响。

    应用文件看起来是这个样子:app/build.gradle

    当然,在本例中不是必须要用Rx或ButterKnife这样的视图注入利器,但是为什么不让应用更细致一些呢??

    我们把repos_flat.bin 和 repos_json.json文件放到res/raw/目录下。RawDataReader是工具类,它帮助我们读取Android应用中的原始文件。

    最后,把Repo,ReposList和User这三张表对应的模型类代码放到项目源代码中。

    FlatBuffers库

    使用Java语言编程过程中,FlatBuffers提供了可以直接处理这种数据格式的库,也这是flatbuffers-java-1.2.0-SNAPSHOT.jar文件。如果你想手工生成该文件,需要下载FlatBuffers源代码,再到目录java/下,用Maven生成该库:

    $ mvn install

    现在将.jar文件放到Android项目的app/libs/目录下。

    好了,当务之急是实现MainActivity类,这是完整源代码。

    我们最为关注的两个方法是:


    • parseReposListJson(String reposStr) - 这个方法初始化Gson解析器,并把JSON字符串转换成Java对象。

    • loadFlatBuffer(byte[] bytes)  - 这个方法将字节(repos_flat.bin文件)转换成Java对象。


    使用FlatBuffers的结果

    现在让我们把JSON和FlatBuffers在加载时间和资源消耗方面的差异形象化。测试是在带有Android M(beta版)的Nexus 5上进行的。

    加载时间

    测量的过程是将其他文件转换为Java源文件,对所有(90个)元素进行迭代。


    • 使用JSON:JSON文件(大小:478kB)平均加载时间200ms(时间区间:180ms~250ms);

    • 使用FlatBuffers:FlatBuffers二进制文件(大小:352kB)平均加载时间5ms(时间区间:3ms~10ms)。


    记得16ms规则吗?我们在UI线程中调用这些方法的原因就是要看看在这种情况下界面表现如何:

    JSON数据加载效果:

    json.gif

    FlatBuffers数据加载效果:

    flatbuffers.gif

    看出区别了吗?JSON数据的加载过程中, ProgressBar停顿了一会,界面不是那么顺畅(加载时间超过了16ms)。

    内存分配、CPU等资源

    还有什么想要测量的吗?也许应该测量一下Android Studio 1.3,还有那些新特性。例如,内存分配跟踪器(Allocation Tracker),内存状态查看器(Memory Viewer)和方法跟踪器(Method Tracer)。

    源代码

    这里所讲解项目的完整源代码都在Github代码库中。你不需要接触整个FlatBuffers项目,所需的内容全都在flatbuffers/目录下。

    ( 翻译/张挥戈  友情审校/白云鹏)

    文章来源:froger_mcs dev blog

    作者简介:

    Miroslaw Stanek,Azimo Money Transfer公司移动项目负责人,Android和iOS平台程序员,视频游戏玩家,冰雪运动爱好者。个人博客:http://frogermcs.github.io

    posted @ 2016-08-18 16:26 小马歌 阅读(603) | 评论 (0)编辑 收藏
     
    from:http://www.oschina.net/news/75092/android-flatbuffers-json

    你可能会问,既然我们已经有很标准的JSON以及转换库比如GSON和Jackson,为什么还要使用新的工具呢?

    不妨先试一下FlatBuffers,然后你就会发现它比JSON快得多。

    FlatBuffers是什么?

    FlatBuffers是一个高效的跨平台序列化类库,可以在C++、C#、C、Go、Java、JavaScript、PHP和Python中使用。是Google开发的,是为了应用在游戏开发,以及其他注重性能的应用上。

    为什么要使用FlatBuffers?

    • 不需要解析/拆包就可以访问序列化数据 — FlatBuffers与其他库不同之处就在于它使用二进制缓冲文件来表示层次数据,这样它们就可以被直接访问而不需解析与拆包,同时还支持数据结构进化(前进、后退兼容性)。

    • 内存高效速度快 — 访问数据时只需要访问内存中的缓冲区。它不需要多余的内存分配(至少在C++是这样,其他语言中可能会有变动)。FlatBuffers还适合配合 mmap或数据流使用,只需要缓冲区的一部分存储在内存中。访问时速度接近原结构访问,只有一点延迟(一种虚函数表vtable),是为了允许格式升级以 及可选字段。FlatBuffers适合那些花费了大量时间和空间(内存分配)来访问和构建序列化数据的项目,比如游戏以及其他对表现敏感的应用。可以参 考这里的基准

    • 灵活 — 由于有可选字段,你不但有很强的升级和回退兼容性(对于历史悠久的游戏尤其重要,不用为了每个版本升级所有数据),在选择要存储哪些数据以及设计数据结构时也很自由。

    • 轻量的code footprint — FlatBuffers只需要很少量的生成代码,以及一个表示最小依赖的很小的头文件,很容易集成。细节上可以看上面的基准页。

    • 强类型 — 编译时报错,而不需要自己写重复的容易出错的运行时检查。它可以自动生成有用的代码。

    • 使用方便 — 生成的C++代码允许精简访问与构建代码。还有可选的用于实现图表解析、类似JSON的运行时字符串展示等功能的方法。(后者比JSON解析库更快,内存效率更高)

    • 代码跨平台且没有依赖 — C++代码可以运行在任何近代的gcc/clang和VS2010上。同时还有用于测试和范例的构建文件(Android中.mk文件,其他平台是cmake文件)。

    都有谁使用FlatBuffers?

    • BobbleApp,印度第一贴图App。我们在BobbleApp中使用FlatBuffers后App的性能明显增强。

    • Cocos2d-x,第一开源移动游戏引擎,使用FlatBuffers来序列化所有的游戏数据。

    • Facebook使用FlatBuffers在Android App中进行客户端服务端的沟通。他们写了一篇文章来描述FlatBuffers是如何加速加载内容的。

    • Google的Fun Propulsion Labs在他们所有的库和游戏中大量使用FlatBuffers。

    App性能有多大提高?

    • 解析速度 解析一个20KB的JSON流(这差不多是BobbleApp的返回大小)需要35ms,超过了UI刷新间隔也就是16.6ms。如果解析JSON的话,我们就在滑动时就会因为要从磁盘加载缓存而导致掉帧(视觉上的卡顿)。

    • 解析器初始化 一个JSON解析器需要先构建字段映射再进行解析,这会花100ms到200ms,很明显的拖缓App启动时间。

    • 垃圾回收 在解析JSON时创建了很多小对象,在我们的试验中,解析20kb的JSON流时,要分配大约100kb的瞬时存储,对Java内存回收造成很大压力。

    FlatBuffers vs JSON

    我尝试使用FlatBuffers和JSON解析4mb的JSON文件。

    FlatBuffers花了1-5ms,JSON花了大约2000ms。在使用FlatBuffers期间Android App中没有GC,而在使用JSON时发生了很多次GC。在使用JSON时UI完全卡住,所以真实使用时只能在后台线程进行解析。

    如何使用FlatBuffer呢?

    我在我的GitHub中写了一个示例,里面手把手教你如何使用FlatBuffer。

    原文链接:https://medium.freecodecamp.com/why-consider-flatbuffer-over-json-2e4aa8d4ed07

    稿源:infoQ  作者Amit Shekhar  译者程大治

    posted @ 2016-08-18 16:17 小马歌 阅读(234) | 评论 (0)编辑 收藏
     

    from:http://www.jdon.com/46111


    本文来自Pivotal,分析了中国铁路总公司12306这个世界上最大的铁路系统的火车票预订系统。

    在这个星球上人类最大的年度运动大概算是中国农历新年,又称春节。有3488万人次通过航空和235万人次通过铁路踏上他们的旅途。从历史上看,铁路旅行意味着排长龙队伍买票,中国铁路总公司(CRC)现在开始在网上卖火车票,提供比车站售票处或通过电话购买更方便的方法。

    随着越来越多人的使用车票预订系统,12306铁路订票系统打破了其传统的RDBMS关系数据库系统,需要重新开始了一个新的项目,以改善原有系统性能和可伸缩性的问题,能够承受像春节度假旅游期间的尖峰的压力。目前该网站成为中国最受欢迎网站的之一。在这样严苛的访问条件下,系统出现以下很差用户体验:使用中断,性能差,预订错误,支付失败,票务确认出现问题等等。

    中国铁道科学研究院的副主任朱剑圣首先解决性能问题,早在2011年,朱博士确保新系统解决基于下面两个性能瓶颈:

    1.关系型数据库超负载,以至于不能处理传入的请求,无论是规模扩展性还是可靠性,都不能满足SLA要求的水平。 

    2.UNIX服务器的计算能力不足以解决容量需求。

    朱博士说:“传统关系型数据库和大型机的计算模式并不具有扩展性,系统不能基于内存扩展跨多个节点上运行。我们的网站证明了这一点,而试图扩展我们的遗留系统将变得非常昂贵”。

    使用In-memory内存数据网格解决扩展性和可靠性
    朱博士的团队开始寻找新的解决方案,大型机被发现和关系数据库有同样的瓶颈,在内存数据网格(IMDG)领域,他们发现了Pivotal GemFire,在海运货物系统 金融服务,航空,电子商务等多个行业都拥有成功解决最具挑战性的数据问题的良好记录。为了执行评估,朱博士和他的团队选择了国际综合系统公司(IISI)。IISI拥有强大为政府机构工作的跟踪记录,包括在开发交通运输解决方案,迁移遗留系统到云计算架构等方面经历,有与Pivotal GemFire合作经验。他们开始试点,相信了GemFire​​将满足性能,可扩展性和可用性的要求,包括能够在低成本硬件上运行。

    IISI创造了一个概念证明和展示了GemFire几个优点​​。售票计算速度提高50到100倍。当负载增加时,响应时间保持10-100毫秒的延迟。他们可以看到,通过增加容量,能实现近乎线性增长的可扩展性和高可用性的能力。项目组在短短两个月内建立了一个试点,四个月后,新的在线系统全面部署,跨越5700火车站。

    该小组负责铁路网上预订系统每年增长高达50%。他们的网站每天的平均水平预订250万票。

    72台UNIX系统和关系数据库换成了10台初始和10个备份的x86服务器,这是一个更具成本效益的模式,能在内存中处理2TB或一个月的的火车票数据。

    朱博士认为:“首先,Pivotal GemFire​​提供了一个在真实的测试环境的证明。然后,在生产环境面对意想不到的尖峰也是成功的,具体采取了一个迭代的方法来部署,克服了一系列大规模的挑战。在最近的2013春运期间,该系统具备了运行稳定的性能和正常运行时间。现在,我们有一个可靠,经济合理的生产体系​​,支持记录容量增长的空间。这个规模实现10-100毫秒的延迟。”

    基于高可用性,冗余和故障切换机制上的GemFire​​提供了连续正常运行,它已超出了所有在该领域的CRC校验的指标,并帮助他们维护他们的SLA。

    具体PDF下载

    posted @ 2016-08-17 16:40 小马歌 阅读(309) | 评论 (0)编辑 收藏
     
         摘要: from:http://www.infoq.com/cn/news/2016/08/Elasticsearch-5-0-Elastic大家好,非常高兴能在这里给大家分享,首先简单自我介绍一下,我叫曾勇,是Elastic的工程师。Elastic将在今年秋季的时候发布一个Elasticsearch V5.0的大版本,这次的微信分享将给大家介绍一下5.0版里面的一些新的特性和改进。5.0? 天啦噜,你是...  阅读全文
    posted @ 2016-08-13 14:56 小马歌 阅读(781) | 评论 (0)编辑 收藏
     
    from:http://www.infoq.com/cn/articles/anatomy-of-an-elasticsearch-cluster-part02

    剖析Elasticsearch集群系列涵盖了当今最流行的分布式搜索引擎Elasticsearch的底层架构和原型实例。

    本文是这个系列的第二篇,我们将讨论Elasticsearch如何处理分布式的三个C((共识(consensus)、并发(concurrency)和一致(consistency))的问题、Elasticsearch分片的内部概念,比如translog(预写日志,WAL(Write Ahead Log)),以及Lucene中的段。

    本系列已经得到原文著者Ronak Nathani的授权

    在本系列的前一篇中,我们讨论了Elasticsearch的底层存储模型及CRUD(创建、读取、更新和删除)操作的工作原理。在本文中,我将分享Elasticsearch是如何应对分布式系统中的一些基本挑战的,以及分片的内部概念。这其中包括了一些操作方面的事情,Insight Data的工程师们已经在使用Elasticsearch构建的数据平台之上成功地实践并真正理解。我将在本文中主要讲述:

    共识——裂脑问题及法定票数的重要性

    共识是分布式系统的一项基本挑战。它要求系统中的所有进程/节点必须对给定数据的值/状态达成共识。已经有很多共识算法诸如RaftPaxos等,从数学上的证明了是行得通的。但是,Elasticsearch却实现了自己的共识系统(zen discovery),Elasticsearch之父Shay Banon在这篇文章中解释了其中的原因。zen discovery模块包含两个部分:

    • Ping: 执行节点使用ping来发现彼此
    • 单播(Unicast):该模块包含一个主机名列表,用以控制哪些节点需要ping通

    Elasticsearch是端对端的系统,其中的所有节点彼此相连,有一个master节点保持活跃,它会更新和控制集群内的状态和操作。建立一个新的Elasticsearch集群要经过一次选举,选举是ping过程的一部分,在所有符合条件的节点中选取一个master,其他节点将加入这个master节点。ping间隔参数ping_interval的默认值是1秒,ping超时参数ping_timeout的默认值是3秒。因为节点要加入,它们会发送一个请求给master节点,加入超时参数join_timeout的默认值是ping_timeout值的20倍。如果master出现问题,那么群集中的其他节点开始重新ping以启动另一次选举。这个ping的过程还可以帮助一个节点在忽然失去master时,通过其他节点发现master。

    注意:默认情况下,client节点和data节点不参与这个选举过程。可以在elasticsearch.yml配置文件中,通过设置discovery.zen.master_election.filter_client属性和discovery.zen.master_election.filter_data属性为false来改变这种默认行为。

    故障检测的原理是这样的,master节点会ping所有其他节点,以检查它们是否还活着;然后所有节点ping回去,告诉master他们还活着。

    如果使用默认的设置,Elasticsearch有可能遭到裂脑问题的困扰。在网络分区的情况下,一个节点可以认为master死了,然后选自己作为master,这就导致了一个集群内出现多个master。这可能会导致数据丢失,也可能无法正确合并数据。可以按照如下公式,根据有资格参加选举的节点数,设置法定票数属性的值,来避免爆裂的发生。

    discovery.zen.minimum_master_nodes = int(# of master eligible nodes/2)+1

    这个属性要求法定票数的节点加入新当选的master节点,来完成并获得新master节点接受的master身份。对于确保群集稳定性和在群集大小变化时动态地更新,这个属性是非常重要的。图a和b演示了在网络分区的情况下,设置或不设置minimum_master_nodes属性时,分别发生的现象。

    注意:对于一个生产集群来说,建议使用3个节点专门做master,这3个节点将不服务于任何客户端请求,而且在任何给定时间内总是只有1个活跃。

    我们已经搞清楚了Elasticsearch中共识的处理,现在让我们来看看它是如何处理并发的。

    并发

    Elasticsearch是一个分布式系统,支持并发请求。当创建/更新/删除请求到达主分片时,它也会被平行地发送到分片副本上。但是,这些请求到达的顺序可能是乱序的。在这种情况下,Elasticsearch使用乐观并发控制,来确保文档的较新版本不会被旧版本覆盖。

    每个被索引的文档都拥有一个版本号,版本号在每次文档变更时递增并应用到文档中。这些版本号用来确保有序接受变更。为了确保在我们的应用中更新不会导致数据丢失,Elasticsearch的API允许我们指定文件的当前版本号,以使变更被接受。如果在请求中指定的版本号比分片上存在的版本号旧,请求失败,这意味着文档已经被另一个进程更新了。如何处理失败的请求,可以在应用层面来控制。Elasticsearch还提供了其他的锁选项,可以通过这篇来阅读。

    当我们发送并发请求到Elasticsearch后,接下来面对的问题是——如何保证这些请求的读写一致?现在,还无法清楚回答,Elasticsearch应落在CAP三角形的哪条边上,我不打算在这篇文章里解决这个素来已久的争辩。

    但是,我们要一起看下如何使用Elasticsearch实现写读一致。

    一致——确保读写一致

    对于写操作而言,Elasticsearch支持的一致性级别,与大多数其他的数据库不同,允许预检查,来查看有多少允许写入的可用分片。可选的值有quorumoneall。默认的设置为quorum,也就是说只有当大多数分片可用时才允许写操作。即使大多数分片可用,还是会因为某种原因发生写入副本失败,在这种情况下,副本被认为故障,分片将在一个不同的节点上重建。

    对于读操作而言,新的文档只有在刷新时间间隔之后,才能被搜索到。为了确保搜索请求的返回结果包含文档的最新版本,可设置replication为sync(默认),这将使操作在主分片和副本碎片都完成后才返回写请求。在这种情况下,搜索请求从任何分片得到的返回结果都包含的是文档的最新版本。即使我们的应用为了更高的索引率而设置了replication=async,我们依然可以为搜索请求设置参数_preferenceprimary。这样,搜索请求将查询主分片,并确保结果中的文档是最新版本。

    我们已经了解了Elasticsearch如何处理共识、并发和一致,让我们来看看分片内部的一些主要概念,正是这些特点让Elasticsearch成为一个分布式搜索引擎。

    Translog(预写日志)

    因为关系数据库的发展,预写日志(WAL)或者事务日志(translog)的概念早已遍及数据库领域。在发生故障的时候,translog能确保数据的完整性。translog的基本原理是,变更必须在数据实际的改变提交到磁盘上之前,被记录下来并提交。

    当新的文档被索引或者旧的文档被更新时,Lucene索引将发生变更,这些变更将被提交到磁盘以持久化。这是一个很昂贵的操作,如果在每个请求之后都被执行。因此,这个操作在多个变更持久化到磁盘时被执行一次。正如我们在上一篇文章中描述的那样,Lucene提交的冲洗(flush)操作默认每30分钟执行一次或者当translog变得太大(默认512MB)时执行。在这样的情况下,有可能失去2个Lucene提交之间的所有变更。为了避免这种问题,Elasticsearch采用了translog。所有索引/删除/更新操作被写入到translog,在每个索引/删除/更新操作执行之后(默认情况下是每5秒),translog会被同步以确保变更被持久化。translog被同步到主分片和副本之后,客户端才会收到写请求的确认。

    在两次Lucene提交之间发生硬件故障的情况下,可以通过重放translog来恢复自最后一次Lucene提交前的任何丢失的变更,所有的变更将会被索引所接受。

    注意:建议在重启Elasticsearch实例之前显式地执行冲洗translog,这样启动会更快,因为要重放的translog被清空。POST /_all/_flush命令可用于冲洗集群中的所有索引。

    使用translog的冲洗操作,在文件系统缓存中的段被提交到磁盘,使索引中的变更持久化。现在让我们来看看Lucene的段。

    Lucene的段

    Lucene索引是由多个段组成,段本身是一个功能齐全的倒排索引。段是不可变的,允许Lucene将新的文档增量地添加到索引中,而不用从头重建索引。对于每一个搜索请求而言,索引中的所有段都会被搜索,并且每个段会消耗CPU的时钟周、文件句柄和内存。这意味着段的数量越多,搜索性能会越低。

    为了解决这个问题,Elasticsearch会合并小段到一个较大的段(如下图所示),提交新的合并段到磁盘,并删除那些旧的小段。

    这会在后台自动执行而不中断索引或者搜索。由于段合并会耗尽资源,影响搜索性能,Elasticsearch会节制合并过程,为搜索提供足够的可用资源。

    接下来有什么?

    从搜索请求角度来说,一个Elasticsearch索引中给定分片内的所有Lucene段都会被搜索,但是,从Elasticsearch集群角度而言,获取所有匹配的文档或者深入有序结果文档是有害的。在本系列的后续文章中我们将揭晓原因,让我们来看一下接下来的主题,内容包括了一些在Elasticsearch中为相关性搜索结果的低延迟所做的权衡。

    • Elasticsearch准实时性方面的内容
    • 为什么搜索中的深层分页是有害的?
    • 搜索相关性计算中的权衡之道

    查看原文地址:Anatomy of an Elasticsearch Cluster: Part II

    posted @ 2016-08-13 11:26 小马歌 阅读(211) | 评论 (0)编辑 收藏
     
    from:http://www.infoq.com/cn/articles/anatomy-of-an-elasticsearch-cluster-part03


    剖析Elasticsearch集群系列涵盖了当今最流行的分布式搜索引擎Elasticsearch的底层架构和原型实例。本文是这个系列的第三篇,我们将讨论Elasticsearch是如何提供近实时搜索并权衡搜索相关性计算的。

    本系列已经得到原文著者Ronak Nathani的授权

    在本系列的前一篇中,我们讨论了Elastisearch如何解决分布式系统中的一些基本挑战。在本文中,我们将探讨Elasticsearch在近实时搜索及其权衡计算搜索相关性方面的内容,Insight Data的工程师们已经在使用Elasticsearch构建的数据平台之上,对此有所实践。我将在本文中主要讲述:

    近实时搜索

    虽然Elasticsearch中的变更不能立即可见,它还是提供了一个近实时的搜索引擎。如前一篇中所述,提交Lucene的变更到磁盘是一个代价昂贵的操作。为了避免在文档对查询依然有效的时候,提交变更到磁盘,Elasticsearch在内存缓冲和磁盘之间提供了一个文件系统缓存。内存缓存(默认情况下)每1秒刷新一次,在文件系统缓存中使用倒排索引创建一个新的段。这个段是开放的并对搜索有效。

    文件系统缓存可以拥有文件句柄,文件可以是开放的、可读的或者是关闭的,但是它存在于内存之中。因为刷新间隔默认是1秒,变更不能立即可见,所以说是近实时的。因为translog是尚未落盘的变更持久化记录,它能有助于CRUD操作方面的近实时性。对于每次请求来说,在查找相关段之前,任何最近的变更都能从translog搜索到,因此客户端可以访问到所有的近实时变更。

    你可以在创建/更新/删除操作后显式地刷新索引,使变更立即可见,但我并不推荐你这样做,因为这样会创建出来非常多的小segment而影响搜索性能。对于每次搜索请求来说,给定Elasticsearch索引分片中的全部Lucene段都会被搜索到,但是,对于Elasticsearch来说,获取全部匹配的文档或者很深结果页的文档是有害的。让我们来一起看看为什么是这样。

    为什么深层分页在分布式搜索中是有害的?

    当我们的一次搜索请求在Elasticsearch中匹配了很多的文档,默认情况下,返回的第一页只包含前10条结果。search API提供了fromsize参数,用于指定对于匹配搜索的全部文档,要返回多深的结果。举例来说,如果我们想看到匹配搜索的文档中,排名为5060之间的文档,可以设置from=50size=10。当每个分片接收到这个搜索请求后,各自会创建一个容量为from+size的优先队列来存储该分片上的搜索结果,然后将结果返回给协调节点。

    如果我们想看到排名为50,00050,010的结果,那么每个分片要创建一个容量为50,010的优先队列来存储结果,而协调节点要在内存中对数量为shards * 50,010的结果进行排序。这个级别的分页有可能得到结果,也有可以无法实现,这取决于我们的硬件资源,但是这足以说明,我们得非常小心地使用深分页,因为这非常容易使我们的集群崩溃。

    一种获取全部匹配结果文档的可行性方案是使用scroll API,它的角色更像关系数据库中的游标。使用scroll API无法进行排序,每个分片只要有匹配搜索的文档,就会持续发送结果给协调节点。

    获取大量文档的时候,对结果进行得分排序会非常昂贵。并且由于Elasticsearch是分布式系统,为每个文档计算搜索相关性得分是非常昂贵的。现在,让我们一起看看计算搜索相关性的诸多权衡中的一种。

    计算搜索相关性中的权衡

    Elasticsearch使用tf-idf来计算搜索相关性。由于其分布式的性质,计算全局的idf(inverse document frequency,逆文档频率)非常昂贵。反之可以这样,每个分片计算本地的idf并将相关性得分分配给结果文档,返回的结果只关乎该分片上的文档。同样地,所有分片使用本地idf计算的相关性得分,返回结果文档,协调节点对所有结果排序并返回前几条。这样做在大多数情况下是没有问题的,除非索引的关键字词项有倾斜或者单个分片上没有代表全局的足够数据。

    比如说,如果我们搜索“insight”这个词,但包含"insight"这个词项的大多数文档都存放在一个分片上,这样以来匹配查询的文档将不能公平地在每个分片上进行排序,因为每个分片上的本地idf的值非常不同,得到的搜索结果可能不会非常相关。同样地,如果没有足够的数据,那么对于某些搜索而言,本地idf的值可能大有不同,结果也会不如预期相关。在有足够数据的真实场景中,本地idf值一般会趋于均等,搜索结果是相关的,因为文档得到了公平的得分。

    这里有2种应对本地idf得分的办法,但都不建议真正在生产环境中使用。

    • 一种办法是一索引一分片,本地idf即是全局idf,但这没有为并行计算/水平伸缩留有余地,对于大型索引并不实用。
    • 另一种办法是在搜索请求中使用dfs_query_then_search (dfs = distributed frequency search,分布式频率搜索) 参数,这样以来,会首先计算每个分片的本地idf,然后综合这些本地idf的值来计算整个索引的全局idf值,最后使用全局idf计算相关性得分来返回结果。这种方式不为生产环境推荐,因为有足够的数据确保词项频率分布均匀。

    在本系列的过去几篇中,我们回顾了一些Elasticsearch的基本原则,对于我们理解并上手Elasticsearch,这些内容非常重要。在接下来的一篇中,我将使用Apache Spark来研究Elasticsearch中的索引数据。

    查看英文原文:Anatomy of an Elasticsearch Cluster: Part III

    posted @ 2016-08-13 11:26 小马歌 阅读(254) | 评论 (0)编辑 收藏
     
    from:http://www.infoq.com/cn/articles/analysis-of-elasticsearch-cluster-part01

    剖析Elasticsearch集群系列涵盖了当今最流行的分布式搜索引擎Elasticsearch的底层架构和原型实例。

    本文是这个系列的第一篇,在本文中,我们将讨论的Elasticsearch的底层存储模型及CRUD(创建、读取、更新和删除)操作的工作原理。

    本系列已经得到原文著者Ronak Nathani的授权

    Elasticsearch是当今最流行的分布式搜索引擎,GitHub、 SalesforceIQ、Netflix等公司将其用于全文检索和分析应用。在Insight,我们用到了Elasticsearch的诸多不同功能,比如:

    • 全文检索
      • 比如找到与搜索词项(term)最相关的维基百科文章。
    • 聚合
      • 比如在广告网络中,可视化的搜索词项的竞价直方图。
    • 地理空间API
      • 比如在顺风车平台,匹配最近的司机和乘客。

    正是因为Elasticsearch如此流行并且就在我们身边,我决定深入研究一下。本文,我将分享Elasticsearch的存储模型和CRUD操作的工作原理。

    当我在思考分布式系统是如何工作时,我脑海里的图案是这样的:

    水面以上的是API,以下的才是真正的引擎,一切魔幻般的事件都发生在水下。本文所关注的就是水下的部分,我们将关注:

    • Elasticsearch是主从架构还是无主架构
    • Elasticsearch的存储模型是什么样的
    • Elasticsearch是怎么执行写操作的
    • Elasticsearch是怎么执行读操作的
    • 如何定义搜索结果的相关性

    在我们深入这些概念之前,让我们熟悉下相关的术语。

    1 辨析Elasticsearch的索引与Lucene的索引

    Elasticsearch中的索引是组织数据的逻辑空间(就好比数据库)。1个Elasticsearch的索引有1个或者多个分片(默认是5个)。分片对应实际存储数据的Lucene的索引,分片自身就是一个搜索引擎。每个分片有0或者多个副本(默认是1个)。Elasticsearch的索引还包含"type"(就像数据库中的表),用于逻辑上隔离索引中的数据。在Elasticsearch的索引中,给定一个type,它的所有文档会拥有相同的属性(就像表的schema)。

    (点击放大图像)

    图a展示了一个包含3个分片的Elasticsearch索引,每个分片拥有1个副本。这些分片组成了一个Elasticsearch索引,每个分片自身是一个Lucene索引。图b展示了Elasticsearch索引、分片、Lucene索引和文档之间的逻辑关系。

    对应于关系数据库术语

    Elasticsearch Index == Database  Types == Tables  Properties == Schema

    现在我们熟悉了Elasticsearch世界的术语,接下来让我们看一下节点有哪些不同的角色。

    2 节点类型

    一个Elasticsearch实例是一个节点,一组节点组成了集群。Elasticsearch集群中的节点可以配置为3种不同的角色:

    • 主节点:控制Elasticsearch集群,负责集群中的操作,比如创建/删除一个索引,跟踪集群中的节点,分配分片到节点。主节点处理集群的状态并广播到其他节点,并接收其他节点的确认响应。

      每个节点都可以通过设定配置文件elasticsearch.yml中的node.master属性为true(默认)成为主节点。

      对于大型的生产集群来说,推荐使用一个专门的主节点来控制集群,该节点将不处理任何用户请求。

    • 数据节点:持有数据和倒排索引。默认情况下,每个节点都可以通过设定配置文件elasticsearch.yml中的node.data属性为true(默认)成为数据节点。如果我们要使用一个专门的主节点,应将其node.data属性设置为false

    • 客户端节点:如果我们将node.master属性和node.data属性都设置为false,那么该节点就是一个客户端节点,扮演一个负载均衡的角色,将到来的请求路由到集群中的各个节点。

    Elasticsearch集群中作为客户端接入的节点叫协调节点。协调节点会将客户端请求路由到集群中合适的分片上。对于读请求来说,协调节点每次会选择不同的分片处理请求,以实现负载均衡。

    在我们开始研究发送给协调节点的CRUD请求是如何在集群中传播并被引擎执行之前,让我们先来看一下Elasticsearch内部是如何存储数据,以支持全文检索结果的低延迟服务的。

    存储模型

    Elasticsearch使用了Apache Lucene,后者是Doug Cutting(Apache Hadoop之父)使用Java开发的全文检索工具库,其内部使用的是被称为倒排索引的数据结构,其设计是为全文检索结果的低延迟提供服务。文档是Elasticsearch的数据单位,对文档中的词项进行分词,并创建去重词项的有序列表,将词项与其在文档中出现的位置列表关联,便形成了倒排索引。

    这和一本书后面的索引非常类似,即书中包含的词汇与其出现的页码列表关联。当我们说文档被索引了,我们指的是倒排索引。我们来看下如下2个文档是如何被倒排索引的:

    文档1(Doc 1): Insight Data Engineering Fellows Program
    文档2(Doc 2): Insight Data Science Fellows Program

    如果我们想找包含词项"insight"的文档,我们可以扫描这个(单词有序的)倒排索引,找到"insight"并返回包含改词的文档ID,示例中是Doc 1和Doc 2。

    为了提高可检索性(比如希望大小写单词都返回),我们应当先分析文档再对其索引。分析包括2个部分:

    • 将句子词条化为独立的单词
    • 将单词规范化为标准形式

    默认情况下,Elasticsearch使用标准分析器,它使用了:

    • 标准分词器以单词为界来切词
    • 小写词条(token)过滤器来转换单词

    还有很多可用的分析器在此不列举,请参考相关文档。

    为了实现查询时能得到对应的结果,查询时应使用与索引时一致的分析器,对文档进行分析。

    注意:标准分析器包含了停用词过滤器,但默认情况下没有启用。

    现在,倒排索引的概念已经清楚,让我们开始CRUD操作的研究吧。我们从写操作开始。

    剖析写操作

    创建((C)reate)

    当我们发送索引一个新文档的请求到协调节点后,将发生如下一组操作:

    • Elasticsearch集群中的每个节点都包含了改节点上分片的元数据信息。协调节点(默认)使用文档ID参与计算,以便为路由提供合适的分片。Elasticsearch使用MurMurHash3函数对文档ID进行哈希,其结果再对分片数量取模,得到的结果即是索引文档的分片。

      shard = hash(document_id) % (num_of_primary_shards)
    • 当分片所在的节点接收到来自协调节点的请求后,会将该请求写入translog(我们将在本系列接下来的文章中讲到),并将文档加入内存缓冲。如果请求在主分片上成功处理,该请求会并行发送到该分片的副本上。当translog被同步(fsync)到全部的主分片及其副本上后,客户端才会收到确认通知。
    • 内存缓冲会被周期性刷新(默认是1秒),内容将被写到文件系统缓存的一个新段上。虽然这个段并没有被同步(fsync),但它是开放的,内容可以被搜索到。
    • 每30分钟,或者当translog很大的时候,translog会被清空,文件系统缓存会被同步。这个过程在Elasticsearch中称为冲洗(flush)。在冲洗过程中,内存中的缓冲将被清除,内容被写入一个新段。段的fsync将创建一个新的提交点,并将内容刷新到磁盘。旧的translog将被删除并开始一个新的translog。

    下图展示了写请求及其数据流。

    (点击放大图像)

    更新((U)pdate)和删除((D)elete)

    删除和更新也都是写操作。但是Elasticsearch中的文档是不可变的,因此不能被删除或者改动以展示其变更。那么,该如何删除和更新文档呢?

    磁盘上的每个段都有一个相应的.del文件。当删除请求发送后,文档并没有真的被删除,而是在.del文件中被标记为删除。该文档依然能匹配查询,但是会在结果中被过滤掉。当段合并(我们将在本系列接下来的文章中讲到)时,在.del文件中被标记为删除的文档将不会被写入新段。

    接下来我们看更新是如何工作的。在新的文档被创建时,Elasticsearch会为该文档指定一个版本号。当执行更新时,旧版本的文档在.del文件中被标记为删除,新版本的文档被索引到一个新段。旧版本的文档依然能匹配查询,但是会在结果中被过滤掉。

    文档被索引或者更新后,我们就可以执行查询操作了。让我们看看在Elasticsearch中是如何处理查询请求的。

    剖析读操作((R)ead)

    读操作包含2部分内容:

    • 查询阶段
    • 提取阶段

    我们来看下每个阶段是如何工作的。

    查询阶段

    在这个阶段,协调节点会将查询请求路由到索引的全部分片(主分片或者其副本)上。每个分片独立执行查询,并为查询结果创建一个优先队列,以相关性得分排序(我们将在本系列的后续文章中讲到)。全部分片都将匹配文档的ID及其相关性得分返回给协调节点。协调节点创建一个优先队列并对结果进行全局排序。会有很多文档匹配结果,但是,默认情况下,每个分片只发送前10个结果给协调节点,协调节点为全部分片上的这些结果创建优先队列并返回前10个作为hit。

    提取阶段

    当协调节点在生成的全局有序的文档列表中,为全部结果排好序后,它将向包含原始文档的分片发起请求。全部分片填充文档信息并将其返回给协调节点。

    下图展示了读请求及其数据流。

    (点击放大图像)

    如上所述,查询结果是按相关性排序的。接下来,让我们看看相关性是如何定义的。

    搜索相关性

    相关性是由搜索结果中Elasticsearch打给每个文档的得分决定的。默认使用的排序算法是tf/idf(词频/逆文档频率)。词频衡量了一个词项在文档中出现的次数 (频率越高 == 相关性越高),逆文档频率衡量了词项在全部索引中出现的频率,是一个索引中文档总数的百分比(频率越高 == 相关性越低)。最后的得分是tf-idf得分与其他因子比如(短语查询中的)词项接近度、(模糊查询中的)词项相似度等的组合。

    接下来有什么?

    这些CRUD操作由Elasticsearch内部的一些数据结构所支持,这对于理解Elasticsearch的工作机制非常重要。在接下来的系列文章中,我将带大家走进类似的那些概念并告诉大家在使用Elasticsearch中有哪些坑。

    • Elasticsearch中的脑裂问题及防治措施
    • 事务日志
    • Lucene的段
    • 为什么搜索时使用深层分页很危险
    • 计算搜索相关性中困难及权衡
    • 并发控制
    • 为什么Elasticsearch是准实时的
    • 如何确保读和写的一致性

    查看原文地址:http://insightdataengineering.com/blog/elasticsearch-crud

    posted @ 2016-08-13 11:15 小马歌 阅读(251) | 评论 (0)编辑 收藏
     
    from:http://www.infoq.com/cn/articles/what-complete-micro-service-system-should-include?utm_source=infoq&utm_medium=popular_widget&utm_campaign=popular_content_list&utm_content=homepage


    近几年,微服务架构迅速在整个技术社区窜红,它被认为是IT软件架构的未来方向,大神Martin Fowler也给微服务极高的评价。那为什么我们需要微服务,微服务的真正优势到底是什么,一个完整的微服务系统,应该包含哪些功能,本文作者刘彦夫在软件设计和开发领域有10多年工作经验,他将会从他的角度给出答案。

    对微服务的基本理解

    顾名思义,微服务要从两个方面来理解,一个是“微”,一个是“服务”。体型小到一定程度才能叫“微”,这个程度是什么呢?一个身高1米6,体重90斤的MM,我们说她苗条。微服务也一样,根据亚马逊CEO Bezos给出的有趣定义,单个微服务的设计、开发、测试和运维的所有人加在一起吃饭,只需要两个批萨就够了,这是就是著名的two pizza team rule。

    具备什么样的能力才能算是“服务”?这个话题很大,我这里按照自己的片面理解总结一下,所谓服务就一定会区别于系统的功能,服务是一个或者一组相对的较小且独立的功能单元,是用户可以感知的功能最小集,比如:购物车,订单,信用卡结算等都可以作为单个服务独立提供。

    这个理解显然不够深刻,为了进一步理解为什么微服务在近两年业界迅速窜红,理解为什么微服务会被认为是IT软件架构的未来方向,就要理解为什么我们需要微服务?它能给企业带来什么价值。传统企业的IT软件大多都是各种独立系统的堆砌,这些系统的问题总结来说就是扩展性差,可靠性不高,维护成本高。后来有了一个叫SOA的软件架构专门针对这些问题给出了一套解决方案,很多企业也因此将自身IT系统迁移到SOA架构上。

    但是,由于SOA早期均使用了总线模式,这种总线模式是与某种技术栈强绑定的,比如:J2EE。这导致很多企业的遗留系统很难对接,切换时间太长,成本太高,新系统稳定性的收敛也需要一些时间。最终SOA开起来很美,但却成为了企业级奢侈品,中小公司都望而生畏。

    依然SOA

    微服务,从本质意义上看,还是SOA架构。但内涵有所不同,微服务并不绑定某种特殊的技术,在一个微服务的系统中,可以有Java编写的服务,也可以有Python编写的服务,他们是靠Restful架构风格统一成一个系统的。

    最粗浅的理解就是将微服务之间的交互看作是各种字符串的传递,各种语言都可以很好的处理字符串,所以微服务本身与具体技术实现无关,扩展性强。另一个不同是微服务架构本身很轻,底层也有类似于SOA的总线,不过非常轻薄,现在看到的就两种方式:MQ和HTTP,而HTTP都不能完全等同于总线,而仅仅是个信息通道。

    所以,基于这种简单的的协议规范,无论是兼容老旧系统,还是上线新业务,都可以随着时代的步伐,滚动升级。比如:你去年还在使用.NET技术,今年就可以平滑的过度到Go了,而且系统已有服务不用改动。所以微服务架构,既保护用户已有投资,又很容易向新技术演进。

    微服务水下的冰山

    人月不是银弹,微服务更不是银弹,好像软件微服务化了,软件系统就能够应对各种问题了。其实微服务的水面下藏着巨大的冰山。下面是微服务提供的能力,以及背后需要付出的代价。

    1. 单个微服务代码量小,易修改和维护。但是,系统复杂度的总量是不变的,每个服务代码少了,但服务的个数肯定就多了。就跟拼图游戏一样,切的越碎,越难拼出整幅图。一个系统被拆分成零碎的微服务,最后要集成为一个完整的系统,其复杂度肯定比大块的功能集成要高很多。

    2. 单个微服务数据独立,可独立部署和运行。虽然微服务本身是可以独立部署和运行的,但仍然避免不了业务上的你来我往,这就涉及到要对外通信,当微服务的数量达到一定量级的时候,如何提供一个高效的集群通信机制成为一个问题。

    3. 单个微服务拥有自己的进程,进程本身就可以动态的启停,为无缝升级的打好了基础,但谁来启动和停止进程,什么时机,选择在哪台设备上做这件事情才是无缝升级的关键。这个能力并不是微服务本身提供的,而是需要背后强大的版本管理和部署能力。

    4. 多个相同的微服务可以做负载均衡,提高性能和可靠性。正是因为相同微服务可以有多个不同实例,让服务按需动态伸缩成为可能,在高峰期可以启动更多的相同的微服务实例为更多用户服务,以此提高响应速度。同时这种机制也提供了高可靠性,在某个微服务故障后,其他相同的微服务可以接替其工作,对外表现为某个设备故障后业务不中断。同样的道理,微服务本身是不会去关心系统负载的,那么什么时候应该启动更多的微服务,多个微服务的流量应该如何调度和分发,这背后也有一套复杂的负载监控和均衡的系统在起作用。

    5. 微服务可以独立部署和对外提供服务,微服务的业务上线和下线是动态的,当一个新的微服务上线时,用户是如何访问到这种新的服务?这就需要有一个统一的入口,新的服务可以动态的注册到这个入口上,用户每次访问时可以从这个入口拿到系统所有服务的访问地址,类似于到餐厅吃饭,新菜要写到“菜单”中,以供用户选择。这个统一的系统入口并不是微服务本身的一部分,所以这种能力需要系统单独提供。

    6. 还有一些企业级关注的系统问题,比如,安全策略如何集中管理?系统故障如何快速审计和跟踪到具体服务?整个系统状态如何监控?服务之间的依赖关系如何管理?等等这些问题都不是单个微服务考虑的范畴,而需要有一个系统性的考虑和设计,让每个微服务都能够按照系统性的要求和约束提供对应的安全性,可靠性,可维护性的能力。

    综上所述,微服务关键其实不仅仅是微服务本身,而是系统要提供一套基础的架构,这种架构使得微服务可以独立的部署、运行、升级,不仅如此,这个系统架构还让微服务与微服务之间在结构上“松耦合”,而在功能上则表现为一个统一的整体。这种所谓的“统一的整体”表现出来的是统一风格的界面,统一的权限管理,统一的安全策略,统一的上线过程,统一的日志和审计方法,统一的调度方式,统一的访问入口等等。

    这些系统性的功能也需要有一些服务来提供,这些服务不会直接呈现给最终用户,也就是微服务系统冰山下面的部分,我们可以简称它为微服务系统的“底座”。所有的微服务都像一个APP,插在这个底座的上面,享受这个底座提供的系统能力比如:元数据存放、灰度发布、蓝绿部署等等。

    微服务系统底座

    一个完整的微服务系统,它的底座最少要包含以下功能:

    • 日志和审计,主要是日志的汇总,分类和查询

    • 监控和告警,主要是监控每个服务的状态,必要时产生告警

    • 消息总线,轻量级的MQ或HTTP

    • 注册发现

    • 负载均衡

    • 部署和升级

    • 事件调度机制

    • 资源管理,如:底层的虚拟机,物理机和网络管理

    以下功能不是最小集的一部分,但也属于底座功能:

    • 认证和鉴权

    • 微服务统一代码框架,支持多种编程语言

    • 统一服务构建和打包

    • 统一服务测试

    • 微服务CI/CD流水线

    • 服务依赖关系管理

    • 统一问题跟踪调试框架,俗称调用链

    • 灰度发布

    • 蓝绿部署

      • 令人困惑的几个问题

      微服务的底座是不是必须的?

      是的,基本上是必须的。你可以不用代码实现一个资源管理服务,可以手工用Excel管理你的所有机器资源,但是不代表微服务系统没有这个功能,只不过这个功能是人工实现的。再举个例子,日志系统如果只是简单的打印文件,那么多个微服务的日志就需要手工收集,人工分类和筛选。所以,微服务的底座最小集一定会存在,问题是看怎样实现它。

      这里仅仅是总结了对微服务系统的基本理解,而实现这个架构有很多技术,这里不进行详细展开。实践方面,推荐王磊的《微服务架构与实践》,他描述了使用Ruby相关的技术实现了一整套微服务系统,特别是书中后面的实践部分讲解了如何将已有的系统演化为微服务架构,是很好的参考和指导材料。

      是不是所有软件都能做微服务?

      这个命题有些微妙,也很难说清楚,回答这个命题本身就是一种挑战,可能最终也没有正确答案。不过,我还是把我自己的理解写在这里,让大家去拍砖。在我这里,答案是否定的。我只需举出一个反例,比如:存储系统,其架构是传统的分层架构,每一层都使用下面一层的服务,并为上一层提供服务。虽然可以将这种架构调整为基于服务的架构,但没办法做成微服务。

      区别在哪里呢?核心的区别在于独立性上,微服务大多是可以独立的运行和使用的,而存储这种非常底层和基础的系统,每层部件都不能单独被使用,比如:Pool管理、CHUNK管理、VOL管理、NFS文件系统,这些功能都无法离开另外一些功能而独立运行,要对外提供可用的存储功能,一大堆功能必须一起上。这种系统做到极致,最多也就能够使其部件可以独立的部署和升级,俗称打热补丁。

      这也就是为什么这种底层传统系统架构通常是单块架构的原因。由于单块架构的各个部分调用关系紧密,做成微服务后系统集成成本会大大增加,不仅如此,这样的架构做成微服务并不能提高交付效率,因为各个部分根本就无法独立的运行和测试。

      什么样的软件做成微服务?

      能不能做成微服务,取决于四个要素:

      • 小:微服务体积小,2 pizza团队。

      • 独:能够独立的部署和运行。

      • 轻:使用轻量级的通信机制和架构。

      • 松:为服务之间是松耦合的。

      针对于小、轻、松都是可以通过某些技术手段达到目的,而独立的部署和运行,则是和业务本身有关系,如果你这个系统提供的业务是贴近最终用户的,并且这些功能之间的耦合性很小,则微服务就可以按照业务功能本身的独立性来划分,则这类系统做成微服务是非常合适的。如果系统提供的业务是非常底层的,如:操作系统内核、存储系统、网络系统、数据库系统等等,这类系统都偏底层,功能和功能之间有着紧密的配合关系,如果强制拆分为较小的服务单元,会让集成工作量急剧上升,并且这种人为的切割无法带来业务上的真正的隔离,所以无法做到独立部署和运行,也就更加无法做到真正的微服务了。


      感谢郭蕾对本文的审校。

      给InfoQ中文站投稿或者参与内容翻译工作,请邮件至editors@cn.infoq.com。也欢迎大家通过新浪微博(@InfoQ@丁晓昀),微信(微信号:InfoQChina)关注我们。

      posted @ 2016-08-13 10:53 小马歌 阅读(224) | 评论 (0)编辑 收藏
       
      作者:MagiLu
      链接:https://zhuanlan.zhihu.com/p/20308548
      来源:知乎
      著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

      作者:johnczchen

      出品:QQ空间终端开发团队

      原文发布于QQ空间终端开发团队的官方公众号,任何形式的转载之前必须与本人联系。


      1.背景

      当一个App发布之后,突然发现了一个严重bug需要进行紧急修复,这时候公司各方就会忙得焦头烂额:重新打包App、测试、向各个应用市场和渠道换包、提示用户升级、用户下载、覆盖安装。有时候仅仅是为了修改了一行代码,也要付出巨大的成本进行换包和重新发布。

      这时候就提出一个问题:有没有办法以补丁的方式动态修复紧急Bug,不再需要重新发布App,不再需要用户重新下载,覆盖安装?

      虽然Android系统并没有提供这个技术,但是很幸运的告诉大家,答案是:可以,我们QQ空间提出了热补丁动态修复技术来解决以上这些问题。

      2.实际案例

      空间Android独立版5.2发布后,收到用户反馈,结合版无法跳转到独立版的访客界面,每天都较大的反馈。在以前只能紧急换包,重新发布。成本非常高,也影响用户的口碑。最终决定使用热补丁动态修复技术,向用户下发Patch,在用户无感知的情况下,修复了外网问题,取得非常好的效果。

      3.解决方案

      该方案基于的是android dex分包方案的,关于dex分包方案,网上有几篇解释了,所以这里就不再赘述,具体可以看这里Android dex分包方案

      简单的概括一下,就是把多个dex文件塞入到app的classloader之中,但是android dex拆包方案中的类是没有重复的,如果classes.dex和classes1.dex中有重复的类,当用到这个重复的类的时候,系统会选择哪个类进行加载呢?

      让我们来看看类加载的代码:

      一个ClassLoader可以包含多个dex文件,每个dex文件是一个Element,多个dex文件排列成一个有序的数组dexElements,当找类的时候,会按顺序遍历dex文件,然后从当前遍历的dex文件中找类,如果找类则返回,如果找不到从下一个dex文件继续查找。

      理论上,如果在不同的dex中有相同的类存在,那么会优先选择排在前面的dex文件的类,如下图:

      在此基础上,我们构想了热补丁的方案,把有问题的类打包到一个dex(patch.dex)中去,然后把这个dex插入到Elements的最前面,如下图:

      好,该方案基于第二个拆分dex的方案,方案实现如果懂拆分dex的原理的话,大家应该很快就会实现该方案,如果没有拆分dex的项目的话,可以参考一下谷歌的multidex方案实现。然后在插入数组的时候,把补丁包插入到最前面去。

      好,看似问题很简单,轻松的搞定了,让我们来试验一下,修改某个类,然后打包成dex,插入到classloader,当加载类的时候出现了(本例中是QzoneActivityManager要被替换):

      为什么会出现以上问题呢?

      从log的意思上来讲,ModuleManager引用了QzoneActivityManager,但是发现这这两个类所在的dex不在一起,其中:

      1. ModuleManager在classes.dex中

      2. QzoneActivityManager在patch.dex中

      结果发生了错误。

      这里有个问题,拆分dex的很多类都不是在同一个dex内的,怎么没有问题?

      让我们搜索一下抛出错误的代码所在,嘿咻嘿咻,找到了一下代码:

      从代码上来看,如果两个相关联的类在不同的dex中就会报错,但是拆分dex没有报错这是为什么,原来这个校验的前提是:

      如果引用者(也就是ModuleManager)这个类被打上了CLASS_ISPREVERIFIED标志,那么就会进行dex的校验。那么这个标志是什么时候被打上去的?

      让我们在继续搜索一下代码,嘿咻嘿咻~~,在DexPrepare.cpp找到了一下代码:

      这段代码是dex转化成odex(dexopt)的代码中的一段,我们知道当一个apk在安装的时候,apk中的classes.dex会被虚拟机(dexopt)优化成odex文件,然后才会拿去执行。

      虚拟机在启动的时候,会有许多的启动参数,其中一项就是verify选项,当verify选项被打开的时候,上面doVerify变量为true,那么就会执行dvmVerifyClass进行类的校验,如果dvmVerifyClass校验类成功,那么这个类会被打上CLASS_ISPREVERIFIED的标志,那么具体的校验过程是什么样子的呢?

      此代码在DexVerify.cpp中,如下:

      1. 验证clazz->directMethods方法,directMethods包含了以下方法:

      1. static方法

      2. private方法

      3. 构造函数

      2. clazz->virtualMethods

      1. 虚函数=override方法?

      概括一下就是如果以上方法中直接引用到的类(第一层级关系,不会进行递归搜索)和clazz都在同一个dex中的话,那么这个类就会被打上CLASS_ISPREVERIFIED

      所以为了实现补丁方案,所以必须从这些方法中入手,防止类被打上CLASS_ISPREVERIFIED标志。

      最终空间的方案是往所有类的构造函数里面插入了一段代码,代码如下:

      if (ClassVerifier.PREVENT_VERIFY) {

      System.out.println(AntilazyLoad.class);

      }

      其中AntilazyLoad类会被打包成单独的hack.dex,这样当安装apk的时候,classes.dex内的类都会引用一个在不相同dex中的AntilazyLoad类,这样就防止了类被打上CLASS_ISPREVERIFIED的标志了,只要没被打上这个标志的类都可以进行打补丁操作。

      然后在应用启动的时候加载进来.AntilazyLoad类所在的dex包必须被先加载进来,不然AntilazyLoad类会被标记为不存在,即使后续加载了hack.dex包,那么他也是不存在的,这样屏幕就会出现茫茫多的类AntilazyLoad找不到的log。

      所以Application作为应用的入口不能插入这段代码。(因为载入hack.dex的代码是在Application中onCreate中执行的,如果在Application的构造函数里面插入了这段代码,那么就是在hack.dex加载之前就使用该类,该类一次找不到,会被永远的打上找不到的标志)

      其中:

      之所以选择构造函数是因为他不增加方法数,一个类即使没有显式的构造函数,也会有一个隐式的默认构造函数。

      空间使用的是在字节码插入代码,而不是源代码插入,使用的是javaassist库来进行字节码插入的。

      隐患:

      虚拟机在安装期间为类打上CLASS_ISPREVERIFIED标志是为了提高性能的,我们强制防止类被打上标志是否会影响性能?这里我们会做一下更加详细的性能测试.但是在大项目中拆分dex的问题已经比较严重,很多类都没有被打上这个标志。

      如何打包补丁包:

      1. 空间在正式版本发布的时候,会生成一份缓存文件,里面记录了所有class文件的md5,还有一份mapping混淆文件。

      2. 在后续的版本中使用-applymapping选项,应用正式版本的mapping文件,然后计算编译完成后的class文件的md5和正式版本进行比较,把不相同的class文件打包成补丁包。

      备注:该方案现在也应用到我们的编译过程当中,编译不需要重新打包dex,只需要把修改过的类的class文件打包成patch dex,然后放到sdcard下,那么就会让改变的代码生效。



      关于Qzone :

      Qzone 是中国最大的社交网络,是腾讯集团的核心平台之一,目前Qzone的月活跃账户数达到6.68亿,Qzone智能终端月活跃账户数达到5.68亿。从2005~2015,Qzone见证了国内互联网蓬勃发展的十年,这十年风云变幻,但我们的业务却不断向前发展,也希望更多的朋友能够加入我们,共同迎接互联网和Qzone的下一个十年。

      posted @ 2016-07-07 16:11 小马歌 阅读(228) | 评论 (0)编辑 收藏
       

      继插件化后,热补丁技术在2015年开始爆发,目前已经是非常热门的Android开发技术。其中比较著名的有淘宝的Dexposed、支付宝的AndFix以及Qzone的超级热补丁方案。微信对热补丁技术的研究并不算早,大约开始于2015年6月。经过研究与尝试现有的各个方案,我们发现它们都有着自身的一些局限性。微信最终采用不同于它们的技术方案,走出了自己的实践演进之路。

      另外一方面,技术应当只是热补丁方案中的一环。随着对热补丁的多次尝试与应用,微信建立起自身的流程规范,同时也不断的尝试拓展它的应用场景。通过本文,我希望大家不仅能够全面的了解各项热补丁技术的优缺点,同时也能对它的应用场景有着更加全面的认识。在此基础上,大家或许能更容易的决定是否在自己的项目中使用热补丁技术,以及应当如何使用它。

      为什么需要热补丁

      热补丁:让应用能够在无需重新安装的情况实现更新,帮助应用快速建立动态修复能力。

      从上面的定义来看,热补丁节省Android大量应用市场发布的时间。同时用户也无需重新安装,只要上线就能无感知的更新。看起来很美好,这是否可以意味我们可以尽量使用补丁来代替发布呢?事实上,热补丁技术当前依然存在它的局限性,主要表现在以下几点:

      1. 补丁只能针对单一客户端版本,随着版本差异变大补丁体积也会增大;

      2. 补丁不能支持所有的修改,例如AndroidManifest;

      3. 补丁无论对代码还是资源的更新成功率都无法达到100%。

      既然补丁技术无法完全代替升级,那它适合使用在哪些场景呢?

      一. 轻量而快速的升级

      热补丁技术也可以理解为一个动态修改代码与资源的通道,它适合于修改量较少的情况。以微信的多次发布为例,补丁大小均在300K以内,它相对于传统的发布有着很大的优势。

      以Android用户的升级习惯,即使是相对活跃的微信也需要10天以上的时间去覆盖50%的用户。使用补丁技术,我们能做到1天覆盖70%以上。这也是基于补丁体积较小,可以直接使用移动网络下载更新。

      正因如此,补丁技术非常适合使用在灰度阶段。在过去,我们需要在正式发布前保证所有严重的问题都已经得到修复,这通常需要我们经过三次以上的灰度过程,而且无法快速的验证这些问题在同一批用户的修复效果。利用热补丁技术,我们可以快速对同一批用户验证修复效果,这大大缩短我们的发布流程。

      若发布版本出现问题或紧急漏洞,传统方式需要单独灰度验证修改,然后重新发布新的版本。利用补丁技术,我们只需要先上线小部分用户验证修改的效果,最后再全量上线即可。但是此种发布对线上用户影响较大, 我们需要谨慎而为。本着对用户负责的态度, 发布补丁等同于发布版本 ,它也应该严格执行完整的测试与上线流程。

      总的来说,补丁技术可以降低开发成本,缩短开发周期,实现轻量而快速的升级。

      二. 远端调试

      一入Android深似海,Android开发的另外一个痛是机型的碎片化。我们也许都会遇到"本地不复现","日志查不出","联系用户不鸟你"的烦恼。所以补丁机制非常适合使用在远端调试上。即我们需要具备只特定用户发送补丁的能力,这对我们查找问题非常有帮助。

      利用补丁技术,我们避免了骚扰用户而默默的为用户解决问题。当然这也需要非常严格的权限管理,以防恶意或随意使用。

      三. 数据统计

      数据统计在微信中也占据着非常重要的位置,我们也非常希望将热补丁与数据统计结合的更好。事实上,热补丁无论在普通的数据统计还是ABTest都有着非常大的优势。例如若我想对同一批用户做两种test, 传统方式无法让这批用户去安装两个版本。使用补丁技术,我们可以方便的对同一批用户更换补丁版本。在数据统计之路,如何与补丁技术结合的更好,更加精准的控制样本人数与比例,这也是微信当前努力发展的一个方向。

      四. 其他

      事实上,Android官方也使用热补丁技术实现Instant Run。它分为Hot Swap、Warm Swap与Cold Swap三种方式,大家可以参考 英文介绍 ,也可以看参考文章中的翻译稿。最新的Instant App应该也是采用类似的原理,但是Google Play是不允许下发代码的,这个海外App需要注意一下。

      微信热补丁技术的演进之路

      在了解补丁技术可以与适合做什么之后,我们回到技术本身。由于 Dexposed 无法支持全平台,并不适合应用到商业产品中。所以这里我们只简单介绍Andfix、Qzone、微信几套方案的实现,以及它们方案面临着的问题,大家也可以参考资料中的 各大热补丁方案分析和比较 一文。

      一. AndFix

      AndFix 采用native hook的方式,这套方案直接使用 dalvik_replaceMethod 替换class中方法的实现。由于它并没有整体替换class, 而field在class中的相对地址在class加载时已确定,所以AndFix无法支持新增或者删除filed的情况(通过替换 init 与clinit 只可以修改field的数值)。

      也正因如此,Andfix可以支持的补丁场景相对有限,仅仅可以使用它来修复特定问题。结合之前的发布流程,我们更希望补丁对开发者是不感知的,即他不需要清楚这个修改是对补丁版本还是正式发布版本(事实上我们也是使用git分支管理+cherry-pick方式)。另一方面,使用native替换将会面临比较复杂的兼容性问题。

      相比其他方案,AndFix的最大优点在于立即生效。事实上,AndFix的实现与 Instant Run的热插拔 有点类似,但是由于使用场景的限制,微信在最初期已排除使用这一方案。

      二. Qzone

      Qzone方案并没有开源,但在github上的 Nuwa 采用了相同的方式。这个方案使用classloader的方式,能实现更加友好的类替换。而且这与我们加载Multidex的做法相似,能基本保证稳定性与兼容性。具体原理在这里不再细说,大家可以参考"安卓App热补丁动态修复技术介绍"这篇文章。

      本方案为了解决 unexpected DEX problem 异常而采用插桩的方式,从而规避问题的出现。事实上,Android系统的这些检查规则是非常有意义的,这会导致Qzone方案在Dalvik与Art都会产生一些问题。

      • Dalvik; 在dexopt过程,若class verify通过会写入pre-verify标志,在经过optimize之后再写入odex文件。这里的optimize主要包括inline以及quick指令优化等。

      若采用插桩导致所有类都非preverify,这导致verify与optimize操作会在加载类时触发。这会有一定的性能损耗,微信分别采用插桩与不插桩两种方式做过两种测试,一是连续加载700个50行左右的类,一是统计微信整个启动完成的耗时。

      平均每个类verify+optimize(跟类的大小有关系)的耗时并不长,而且这个耗时每个类只有一次。但由于启动时会加载大量的类,在这个情况影响还是比较大。

      • Art; Art采用了新的方式,插桩对代码的执行效率并没有什么影响。但是若补丁中的类出现修改类变量或者方法,可能会导致出现内存地址错乱的问题。为了解决这个问题我们需要将修改了变量、方法以及接口的类的父类以及调用这个类的所有类都加入到补丁包中。这可能会带来补丁包大小的急剧增加。

      这里是因为在dex2oat时 fast* 已经将类能确定的各个地址写死。如果运行时补丁包的地址出现改变,原始类去调用时就会出现地址错乱。这里说的可能不够详细,事实上微信当时为了查清这两个问题,也花费了一定的时间将Dalvik跟Art的流程基本搞透。若大家对这里感兴趣,后续在单独的文章详细论述。

      总的来说,Qzone方案好处在于开发透明,简单,这一套方案目前的应用成功率也是最高的, 但在补丁包大小与性能损耗上有一定的局限性。特别是无论我们是否真正应用补丁,都会因为插桩导致对程序运行时的性能产生影响。微信对于性能要求较高,所以我们也没有采用这套方案。

      三. 微信热补丁方案

      有没有那么一种方案,能做到开发透明,但是却没有Qzone方案的缺陷呢?Instant Run的冷插拔与buck的 exopackage 或许能给我们灵感,它们的思想都是全量替换新的Dex。即我们完全使用了新的Dex,那样既不出现Art地址错乱的问题,在Dalvik也无须插桩。当然考虑到补丁包的体积,我们不能直接将新的Dex放在里面。但我们可以将新旧两个Dex的差异放到补丁包中,最简单我们可以采用BsDiff算法。

      简单来说,在编译时通过新旧两个Dex生成差异patch.dex。在运行时,将差异patch.dex重新跟原始安装包的旧Dex还原为新的Dex。这个过程可能比较耗费时间与内存,所以我们是单独放在一个后台进程:patch中。为了补丁包尽量的小,微信自研了DexDiff算法,它深度利用Dex的格式来减少差异的大小。它的粒度是Dex格式的每一项,可以充分利用原本Dex的信息,而BsDiff的粒度是文件,AndFix/Qzone的粒度为class。

      这块后面我希望后面用单独的文章来讲述,这里先做一个铺垫,大致的效果如下图。在最极端的情况,由于利用了原本dex的信息完全替换一个13M的Dex,我们的补丁大小也仅仅只有6.6M。

      但是这套方案并非没有缺点,它带来的问题有两个:

      1. 占用Rom体积;这边大约是你所修改Dex大小的1.5倍( Dex压缩成jar的大小加上 生成的dexopt文件大小)。

      2. 一个额外的合成过程;虽然我们单独放在一个进程上处理,但是合成时间的长短与内存消耗也会影响最终的成功率(与修改Dex大小、补丁大小相关)。

      微信的热补丁方案叫做Tinker,也算缅怀一下Dota中的地精修补匠,希望能做到无限刷新。

      限于篇幅,这里对Dex、library以及资源的更多技术细节并没有详细的论述,这里希望放在后面的单独文章中。我们最后从整体比较一下这几种方案:

      若不care性能损耗与补丁包大小,Qzone方案是最简单且成功率最高的方案(没有单独的合成过程)。相对Tinker来说,它的占用Rom体积也更小。另一方面,Qzone与Tinker的成功率当前大约相差3%左右。

      事实上,一个完整的框架应该也是一个容易使用的框架。Tinker对补丁版本管理、进程管理、安全校验等都有着很好的支持。同时我们也支持gradle与命名行两种接入方式。希望在不久的将来,它可以很快的跟大家见面。

      微信的热补丁应用现状

      上一章节我们简单比较了各个热补丁的技术方案,它们解决了如何生成与加载补丁包的问题。但一个完善的热补丁系统不应该仅限于此,它还需要包括以下几个方面:

      • 网络通道;这里要解决的问题是决定补丁以何种方式推送给哪部分的用户。

      • 上线与后台管理平台;这里主要包括热补丁的上线管理,历史管理以及上报分析,报警监控等;

      一. 网络通道现状

      网络通道负责的将补丁包交付给用户,这个包括特定用户与全量用户两种情况。事实上,微信当前针对热补丁有以下三种通道更新:

      • pull通道; 在登陆/24小时等时机,通过pull方式查询后台是否有对应的补丁包更新,这也是我们最常用的方式;

      • 指定版本的push通道; 针对版本的通道,在紧急情况下,我们可以在一个小时内向所有用户下发补丁包更新。

      • 指定特定用户的push通道;对特定用户或用户组做远程调试。

      事实上,对于大部分的应用来说,假设不实现push通道,CDN+pull通道实现起来还是较为容易。

      二. 上线与管理平台现状

      上线与管理平台主要为了快速上线,管理历史记录,以及监控补丁的运行情况等(界面比较丑陋,因为我们木有美工啊)。

      事实上,微信发布热补丁是非常慎重的。它整个发布流程与升级版本是保持一致的,也必须修改版本号、经过严格的完整测试流程等。我们也会通过灰度的方式上线,同时监控补丁版本的各个指标。这里的为了完整的监控补丁的情况,我们做的工作有:

      • 1分钟粒度的每小时/每天的各版本累积用户,及时监控补丁版本的人数与活跃;

      • 3分钟粒度的Crash统计,基准版本与补丁版本的Crash每小时/每天的两个维度对照;

      • 10分钟粒度的补丁监控信息上报。

      三. 补丁成功率现状

      应用成功率= 补丁版本人数/补丁发布前该版本人数

      由于可能存在基准或补丁版本用户安装了其他版本,所以本统计结果应略为偏低,但它能现实的反应补丁的线上覆盖情况。

      使用Qzone方案,微信补丁在10天后的应用成功率大约在98.5%左右。使用Tinker大约只有95.5%左右,主要原因在于空间不足以及后台进程被杀。在这里我们也在尝试使用重试的方式以及降低合成的耗时与内存,从而提升成功率。

      热补丁技术发展的很快,Android推出的Instant App也令人期待。但是在国内,似乎我们还是指望自己更靠谱一点。每一个的应用的需求都不太一致,这里大致讲了一些微信的实践经验,希望对大家有帮助。

      未来工作

      随着微信部门内从“单APP”向“多APP”演进,微信也正在迈入开源化的开发实践。我们希望将各个功能组件化,从而做可以到快速复制与应用。微信的热补丁框架“Tinker”当前也在经历从微信分离,又合入到微信的过程。希望在不久的将来,我们也可以将“Tinker”以及微信中一些其他的组件开源出去。

      我们也希望可以找一些App作为内测,给我们提供宝贵的意见。若对微信的Tinker方案感兴趣的用户,可以单独发消息或在文章末留言注明姓名、所在公司以及负责的App,我们希望挑选部分产品作为内测。

      参考文章

      大家可以点击下方的阅读原文,即可直接点击跳转。

      1. Dexposed github ( https://github.com/alibaba/dexposed )

      2. AndFix github ( https://github.com/alibaba/AndFix )

      3. Nuwa github ( https://github.com/jasonross/Nuwa )

      4. Qzone实现原理解析 ( https://mp.weixin.qq.com/s?__biz=MzI1MTA1MzM2Nw==&mid=400118620&idx=1&sn=b4fdd5055731290eef12ad0d17f39d4a )

      5. Instant Run英文原文 ( https://medium.com/google-developers/instant-run-how-does-it-work-294a1633367f#.c088qhdxu )

      6. Instant Run工作原理及用法中文翻译稿 ( http://www.jianshu.com/p/2e23ba9ff14b )

      7. Buck exopackage 介绍 ( https://buckbuild.com/article/exopackage.html )

      8. 各大热补丁方案分析和比较 (http://blog.zhaiyifan.cn/2015/11/20/HotPatchCompare/ )

      posted @ 2016-07-07 16:10 小马歌 阅读(222) | 评论 (0)编辑 收藏
       
           摘要: from:http://mp.weixin.qq.com/s?__biz=MzAwNDY1ODY2OQ==&mid=207243549&idx=1&sn=4ebe4beb8123f1b5ab58810ac8bc5994&scene=0#rd前言:在13年11月中旬时,因为基础组件组人手紧张,Leo安排我和春哥去广州轮岗支援。刚到广州的时候,Ray让我和春哥对Line...  阅读全文
      posted @ 2016-07-07 16:09 小马歌 阅读(299) | 评论 (0)编辑 收藏
       

      from:http://www.ibm.com/developerworks/cn/web/1103_zhaoct_recommstudy1/


      “探索推荐引擎内部的秘密”系列将带领读者从浅入深的学习探索推荐引擎的机制,实现方法,其中还涉及一些基本的优化方法,例如聚类和分类的应用。同时在理论讲解的基础上,还会结合 Apache Mahout 介绍如何在大规模数据上实现各种推荐策略,进行策略优化,构建高效的推荐引擎的方法。本文作为这个系列的第一篇文章,将深入介绍推荐引擎的工作原理,和其中涉及的各种推荐机制,以及它们各自的优缺点和适用场景,帮助用户清楚的了解和快速构建适合自己的推荐引擎。

      信息发现

      如今已经进入了一个数据爆炸的时代,随着 Web 2.0 的发展, Web 已经变成数据分享的平台,那么,如何让人们在海量的数据中想要找到他们需要的信息将变得越来越难。

      在这样的情形下,搜索引擎(Google,Bing,百度等等)成为大家快速找到目标信息的最好途径。在用户对自己需求相对明确的时候,用搜索引擎很方便的通过关键字搜索很快的找到自己需要的信息。但搜索引擎并不能完全满足用户对信息发现的需求,那是因为在很多情况下,用户其实并不明确自己的需要,或者他们的需求很难用简单的关键字来表述。又或者他们需要更加符合他们个人口味和喜好的结果,因此出现了推荐系统,与搜索引擎对应,大家也习惯称它为推荐引擎。

      随着推荐引擎的出现,用户获取信息的方式从简单的目标明确的数据的搜索转换到更高级更符合人们使用习惯的信息发现。

      如今,随着推荐技术的不断发展,推荐引擎已经在电子商务 (E-commerce,例如 Amazon,当当网 ) 和一些基于 social 的社会化站点 ( 包括音乐,电影和图书分享,例如豆瓣,Mtime 等 ) 都取得很大的成功。这也进一步的说明了,Web2.0 环境下,在面对海量的数据,用户需要这种更加智能的,更加了解他们需求,口味和喜好的信息发现机制。

      回页首

      推荐引擎

      前面介绍了推荐引擎对于现在的 Web2.0 站点的重要意义,这一章我们将讲讲推荐引擎到底是怎么工作的。推荐引擎利用特殊的信息过滤技术,将不同的物品或内容推荐给可能对它们感兴趣的用户。

       


      图 1. 推荐引擎工作原理图
      图 1. 推荐引擎工作原理图 

      图 1 给出了推荐引擎的工作原理图,这里先将推荐引擎看作黑盒,它接受的输入是推荐的数据源,一般情况下,推荐引擎所需要的数据源包括:

      • 要推荐物品或内容的元数据,例如关键字,基因描述等;
      • 系统用户的基本信息,例如性别,年龄等
      • 用户对物品或者信息的偏好,根据应用本身的不同,可能包括用户对物品的评分,用户查看物品的记录,用户的购买记录等。其实这些用户的偏好信息可以分为两类:
      • 显式的用户反馈:这类是用户在网站上自然浏览或者使用网站以外,显式的提供反馈信息,例如用户对物品的评分,或者对物品的评论。
      • 隐式的用户反馈:这类是用户在使用网站是产生的数据,隐式的反应了用户对物品的喜好,例如用户购买了某物品,用户查看了某物品的信息等等。

      显式的用户反馈能准确的反应用户对物品的真实喜好,但需要用户付出额外的代价,而隐式的用户行为,通过一些分析和处理,也能反映用户的喜好,只是数据不是很精确,有些行为的分析存在较大的噪音。但只要选择正确的行为特征,隐式的用户反馈也能得到很好的效果,只是行为特征的选择可能在不同的应用中有很大的不同,例如在电子商务的网站上,购买行为其实就是一个能很好表现用户喜好的隐式反馈。

      推荐引擎根据不同的推荐机制可能用到数据源中的一部分,然后根据这些数据,分析出一定的规则或者直接对用户对其他物品的喜好进行预测计算。这样推荐引擎可以在用户进入的时候给他推荐他可能感兴趣的物品。

      推荐引擎的分类

      推荐引擎的分类可以根据很多指标,下面我们一一介绍一下:

      1. 推荐引擎是不是为不同的用户推荐不同的数据

        根据这个指标,推荐引擎可以分为基于大众行为的推荐引擎和个性化推荐引擎

        • 根据大众行为的推荐引擎,对每个用户都给出同样的推荐,这些推荐可以是静态的由系统管理员人工设定的,或者基于系统所有用户的反馈统计计算出的当下比较流行的物品。
        • 个性化推荐引擎,对不同的用户,根据他们的口味和喜好给出更加精确的推荐,这时,系统需要了解需推荐内容和用户的特质,或者基于社会化网络,通过找到与当前用户相同喜好的用户,实现推荐。

        这是一个最基本的推荐引擎分类,其实大部分人们讨论的推荐引擎都是将个性化的推荐引擎,因为从根本上说,只有个性化的推荐引擎才是更加智能的信息发现过程。

      2. 根据推荐引擎的数据源

        其实这里讲的是如何发现数据的相关性,因为大部分推荐引擎的工作原理还是基于物品或者用户的相似集进行推荐。那么参考图 1 给出的推荐系统原理图,根据不同的数据源发现数据相关性的方法可以分为以下几种:

        • 根据系统用户的基本信息发现用户的相关程度,这种被称为基于人口统计学的推荐(Demographic-based Recommendation)
        • 根据推荐物品或内容的元数据,发现物品或者内容的相关性,这种被称为基于内容的推荐(Content-based Recommendation)
        • 根据用户对物品或者信息的偏好,发现物品或者内容本身的相关性,或者是发现用户的相关性,这种被称为基于协同过滤的推荐(Collaborative Filtering-based Recommendation)。
      3. 根据推荐模型的建立方式

        可以想象在海量物品和用户的系统中,推荐引擎的计算量是相当大的,要实现实时的推荐务必需要建立一个推荐模型,关于推荐模型的建立方式可以分为以下几种:

        • 基于物品和用户本身的,这种推荐引擎将每个用户和每个物品都当作独立的实体,预测每个用户对于每个物品的喜好程度,这些信息往往是用一个二维矩阵描述的。由于用户感兴趣的物品远远小于总物品的数目,这样的模型导致大量的数据空置,即我们得到的二维矩阵往往是一个很大的稀疏矩阵。同时为了减小计算量,我们可以对物品和用户进行聚类, 然后记录和计算一类用户对一类物品的喜好程度,但这样的模型又会在推荐的准确性上有损失。
        • 基于关联规则的推荐(Rule-based Recommendation):关联规则的挖掘已经是数据挖掘中的一个经典的问题,主要是挖掘一些数据的依赖关系,典型的场景就是“购物篮问题”,通过关联规则的挖掘,我们可以找到哪些物品经常被同时购买,或者用户购买了一些物品后通常会购买哪些其他的物品,当我们挖掘出这些关联规则之后,我们可以基于这些规则给用户进行推荐。
        • 基于模型的推荐(Model-based Recommendation):这是一个典型的机器学习的问题,可以将已有的用户喜好信息作为训练样本,训练出一个预测用户喜好的模型,这样以后用户在进入系统,可以基于此模型计算推荐。这种方法的问题在于如何将用户实时或者近期的喜好信息反馈给训练好的模型,从而提高推荐的准确度。

      其实在现在的推荐系统中,很少有只使用了一个推荐策略的推荐引擎,一般都是在不同的场景下使用不同的推荐策略从而达到最好的推荐效果,例如 Amazon 的推荐,它将基于用户本身历史购买数据的推荐,和基于用户当前浏览的物品的推荐,以及基于大众喜好的当下比较流行的物品都在不同的区域推荐给用户,让用户可以从全方位的推荐中找到自己真正感兴趣的物品。


      深入推荐机制

      这一章的篇幅,将详细介绍各个推荐机制的工作原理,它们的优缺点以及应用场景。

      基于人口统计学的推荐

      基于人口统计学的推荐机制(Demographic-based Recommendation)是一种最易于实现的推荐方法,它只是简单的根据系统用户的基本信息发现用户的相关程度,然后将相似用户喜爱的其他物品推荐给当前用户,图 2 给出了这种推荐的工作原理。


      图 2. 基于人口统计学的推荐机制的工作原理
      图 2. 基于人口统计学的推荐机制的工作原理 

      从图中可以很清楚的看到,首先,系统对每个用户都有一个用户 Profile 的建模,其中包括用户的基本信息,例如用户的年龄,性别等等;然后,系统会根据用户的 Profile 计算用户的相似度,可以看到用户 A 的 Profile 和用户 C 一样,那么系统会认为用户 A 和 C 是相似用户,在推荐引擎中,可以称他们是“邻居”;最后,基于“邻居”用户群的喜好推荐给当前用户一些物品,图中将用户 A 喜欢的物品 A 推荐给用户 C。

      这种基于人口统计学的推荐机制的好处在于:

      1. 因为不使用当前用户对物品的喜好历史数据,所以对于新用户来讲没有“冷启动(Cold Start)”的问题。
      2. 这个方法不依赖于物品本身的数据,所以这个方法在不同物品的领域都可以使用,它是领域独立的(domain-independent)。

      那么这个方法的缺点和问题是什么呢?这种基于用户的基本信息对用户进行分类的方法过于粗糙,尤其是对品味要求较高的领域,比如图书,电影和音乐等领域,无法得到很好的推荐效果。可能在一些电子商务的网站中,这个方法可以给出一些简单的推荐。另外一个局限是,这个方法可能涉及到一些与信息发现问题本身无关却比较敏感的信息,比如用户的年龄等,这些用户信息不是很好获取。

      基于内容的推荐

      基于内容的推荐是在推荐引擎出现之初应用最为广泛的推荐机制,它的核心思想是根据推荐物品或内容的元数据,发现物品或者内容的相关性,然后基于用户以往的喜好记录,推荐给用户相似的物品。图 3 给出了基于内容推荐的基本原理。


      图 3. 基于内容推荐机制的基本原理
      图 3. 基于内容推荐机制的基本原理 

      图 3 中给出了基于内容推荐的一个典型的例子,电影推荐系统,首先我们需要对电影的元数据有一个建模,这里只简单的描述了一下电影的类型;然后通过电影的元数据发现电影间的相似度,因为类型都是“爱情,浪漫”电影 A 和 C 被认为是相似的电影(当然,只根据类型是不够的,要得到更好的推荐,我们还可以考虑电影的导演,演员等等);最后实现推荐,对于用户 A,他喜欢看电影 A,那么系统就可以给他推荐类似的电影 C。

      这种基于内容的推荐机制的好处在于它能很好的建模用户的口味,能提供更加精确的推荐。但它也存在以下几个问题:

      1. 需要对物品进行分析和建模,推荐的质量依赖于对物品模型的完整和全面程度。在现在的应用中我们可以观察到关键词和标签(Tag)被认为是描述物品元数据的一种简单有效的方法。
      2. 物品相似度的分析仅仅依赖于物品本身的特征,这里没有考虑人对物品的态度。
      3. 因为需要基于用户以往的喜好历史做出推荐,所以对于新用户有“冷启动”的问题。

      虽然这个方法有很多不足和问题,但他还是成功的应用在一些电影,音乐,图书的社交站点,有些站点还请专业的人员对物品进行基因编码,比如潘多拉,在一份报告中说道,在潘多拉的推荐引擎中,每首歌有超过 100 个元数据特征,包括歌曲的风格,年份,演唱者等等。

      基于协同过滤的推荐

      随着 Web2.0 的发展,Web 站点更加提倡用户参与和用户贡献,因此基于协同过滤的推荐机制因运而生。它的原理很简单,就是根据用户对物品或者信息的偏好,发现物品或者内容本身的相关性,或者是发现用户的相关性,然后再基于这些关联性进行推荐。基于协同过滤的推荐可以分为三个子类:基于用户的推荐(User-based Recommendation),基于项目的推荐(Item-based Recommendation)和基于模型的推荐(Model-based Recommendation)。下面我们一个一个详细的介绍着三种协同过滤的推荐机制。

      基于用户的协同过滤推荐

      基于用户的协同过滤推荐的基本原理是,根据所有用户对物品或者信息的偏好,发现与当前用户口味和偏好相似的“邻居”用户群,在一般的应用中是采用计算“K- 邻居”的算法;然后,基于这 K 个邻居的历史偏好信息,为当前用户进行推荐。下图 4 给出了原理图。


      图 4. 基于用户的协同过滤推荐机制的基本原理
      图 4. 基于用户的协同过滤推荐机制的基本原理 

      上图示意出基于用户的协同过滤推荐机制的基本原理,假设用户 A 喜欢物品 A,物品 C,用户 B 喜欢物品 B,用户 C 喜欢物品 A ,物品 C 和物品 D;从这些用户的历史喜好信息中,我们可以发现用户 A 和用户 C 的口味和偏好是比较类似的,同时用户 C 还喜欢物品 D,那么我们可以推断用户 A 可能也喜欢物品 D,因此可以将物品 D 推荐给用户 A。

      基于用户的协同过滤推荐机制和基于人口统计学的推荐机制都是计算用户的相似度,并基于“邻居”用户群计算推荐,但它们所不同的是如何计算用户的相似度,基于人口统计学的机制只考虑用户本身的特征,而基于用户的协同过滤机制可是在用户的历史偏好的数据上计算用户的相似度,它的基本假设是,喜欢类似物品的用户可能有相同或者相似的口味和偏好。

      基于项目的协同过滤推荐

      基于项目的协同过滤推荐的基本原理也是类似的,只是说它使用所有用户对物品或者信息的偏好,发现物品和物品之间的相似度,然后根据用户的历史偏好信息,将类似的物品推荐给用户,图 5 很好的诠释了它的基本原理。

      假设用户 A 喜欢物品 A 和物品 C,用户 B 喜欢物品 A,物品 B 和物品 C,用户 C 喜欢物品 A,从这些用户的历史喜好可以分析出物品 A 和物品 C 时比较类似的,喜欢物品 A 的人都喜欢物品 C,基于这个数据可以推断用户 C 很有可能也喜欢物品 C,所以系统会将物品 C 推荐给用户 C。

      与上面讲的类似,基于项目的协同过滤推荐和基于内容的推荐其实都是基于物品相似度预测推荐,只是相似度计算的方法不一样,前者是从用户历史的偏好推断,而后者是基于物品本身的属性特征信息。


      图 5. 基于项目的协同过滤推荐机制的基本原理
      图 5. 基于项目的协同过滤推荐机制的基本原理 

      同时协同过滤,在基于用户和基于项目两个策略中应该如何选择呢?其实基于项目的协同过滤推荐机制是 Amazon 在基于用户的机制上改良的一种策略,因为在大部分的 Web 站点中,物品的个数是远远小于用户的数量的,而且物品的个数和相似度相对比较稳定,同时基于项目的机制比基于用户的实时性更好一些。但也不是所有的场景都是这样的情况,可以设想一下在一些新闻推荐系统中,也许物品,也就是新闻的个数可能大于用户的个数,而且新闻的更新程度也有很快,所以它的形似度依然不稳定。所以,其实可以看出,推荐策略的选择其实和具体的应用场景有很大的关系。

      基于模型的协同过滤推荐

      基于模型的协同过滤推荐就是基于样本的用户喜好信息,训练一个推荐模型,然后根据实时的用户喜好的信息进行预测,计算推荐。

      基于协同过滤的推荐机制是现今应用最为广泛的推荐机制,它有以下几个显著的优点:

      1. 它不需要对物品或者用户进行严格的建模,而且不要求物品的描述是机器可理解的,所以这种方法也是领域无关的。
      2. 这种方法计算出来的推荐是开放的,可以共用他人的经验,很好的支持用户发现潜在的兴趣偏好

      而它也存在以下几个问题:

      1. 方法的核心是基于历史数据,所以对新物品和新用户都有“冷启动”的问题。
      2. 推荐的效果依赖于用户历史偏好数据的多少和准确性。
      3. 在大部分的实现中,用户历史偏好是用稀疏矩阵进行存储的,而稀疏矩阵上的计算有些明显的问题,包括可能少部分人的错误偏好会对推荐的准确度有很大的影响等等。
      4. 对于一些特殊品味的用户不能给予很好的推荐。
      5. 由于以历史数据为基础,抓取和建模用户的偏好后,很难修改或者根据用户的使用演变,从而导致这个方法不够灵活。

      混合的推荐机制

      在现行的 Web 站点上的推荐往往都不是单纯只采用了某一种推荐的机制和策略,他们往往是将多个方法混合在一起,从而达到更好的推荐效果。关于如何组合各个推荐机制,这里讲几种比较流行的组合方法。

      1. 加权的混合(Weighted Hybridization): 用线性公式(linear formula)将几种不同的推荐按照一定权重组合起来,具体权重的值需要在测试数据集上反复实验,从而达到最好的推荐效果。
      2. 切换的混合(Switching Hybridization):前面也讲到,其实对于不同的情况(数据量,系统运行状况,用户和物品的数目等),推荐策略可能有很大的不同,那么切换的混合方式,就是允许在不同的情况下,选择最为合适的推荐机制计算推荐。
      3. 分区的混合(Mixed Hybridization):采用多种推荐机制,并将不同的推荐结果分不同的区显示给用户。其实,Amazon,当当网等很多电子商务网站都是采用这样的方式,用户可以得到很全面的推荐,也更容易找到他们想要的东西。
      4. 分层的混合(Meta-Level Hybridization): 采用多种推荐机制,并将一个推荐机制的结果作为另一个的输入,从而综合各个推荐机制的优缺点,得到更加准确的推荐。

      推荐引擎的应用

      介绍完推荐引擎的基本原理,基本推荐机制,下面简要分析几个有代表性的推荐引擎的应用,这里选择两个领域:Amazon 作为电子商务的代表,豆瓣作为社交网络的代表。

      推荐在电子商务中的应用 – Amazon

      Amazon 作为推荐引擎的鼻祖,它已经将推荐的思想渗透在应用的各个角落。Amazon 推荐的核心是通过数据挖掘算法和比较用户的消费偏好于其他用户进行对比,借以预测用户可能感兴趣的商品。对应于上面介绍的各种推荐机制,Amazon 采用的是分区的混合的机制,并将不同的推荐结果分不同的区显示给用户,图 6 和图 7 展示了用户在 Amazon 上能得到的推荐。


      图 6. Amazon 的推荐机制 - 首页
      图 6. Amazon 的推荐机制 - 首页 

      图 7. Amazon 的推荐机制 - 浏览物品
      图 7. Amazon 的推荐机制 - 浏览物品 

      Amazon 利用可以记录的所有用户在站点上的行为,根据不同数据的特点对它们进行处理,并分成不同区为用户推送推荐:

      • 今日推荐 (Today's Recommendation For You): 通常是根据用户的近期的历史购买或者查看记录,并结合时下流行的物品给出一个折中的推荐。
      • 新产品的推荐 (New For You): 采用了基于内容的推荐机制 (Content-based Recommendation),将一些新到物品推荐给用户。在方法选择上由于新物品没有大量的用户喜好信息,所以基于内容的推荐能很好的解决这个“冷启动”的问题。
      • 捆绑销售 (Frequently Bought Together): 采用数据挖掘技术对用户的购买行为进行分析,找到经常被一起或同一个人购买的物品集,进行捆绑销售,这是一种典型的基于项目的协同过滤推荐机制。
      • 别人购买 / 浏览的商品 (Customers Who Bought/See This Item Also Bought/See): 这也是一个典型的基于项目的协同过滤推荐的应用,通过社会化机制用户能更快更方便的找到自己感兴趣的物品。

      值得一提的是,Amazon 在做推荐时,设计和用户体验也做得特别独到:

      Amazon 利用有它大量历史数据的优势,量化推荐原因。

      • 基于社会化的推荐,Amazon 会给你事实的数据,让用户信服,例如:购买此物品的用户百分之多少也购买了那个物品;
      • 基于物品本身的推荐,Amazon 也会列出推荐的理由,例如:因为你的购物框中有 ***,或者因为你购买过 ***,所以给你推荐类似的 ***。

      另外,Amazon 很多推荐是基于用户的 profile 计算出来的,用户的 profile 中记录了用户在 Amazon 上的行为,包括看了那些物品,买了那些物品,收藏夹和 wish list 里的物品等等,当然 Amazon 里还集成了评分等其他的用户反馈的方式,它们都是 profile 的一部分,同时,Amazon 提供了让用户自主管理自己 profile 的功能,通过这种方式用户可以更明确的告诉推荐引擎他的品味和意图是什么。

      推荐在社交网站中的应用 – 豆瓣

      豆瓣是国内做的比较成功的社交网站,它以图书,电影,音乐和同城活动为中心,形成一个多元化的社交网络平台,自然推荐的功能是必不可少的,下面我们看看豆瓣是如何推荐的。


      图 8 . 豆瓣的推荐机制 - 豆瓣电影
      图 8 . 豆瓣的推荐机制 - 豆瓣电影 

      当你在豆瓣电影中将一些你看过的或是感兴趣的电影加入你看过和想看的列表里,并为它们做相应的评分,这时豆瓣的推荐引擎已经拿到你的一些偏好信息,那么它将给你展示如图 8 的电影推荐。


      图 9 . 豆瓣的推荐机制 - 基于用户品味的推荐
      图 9 . 豆瓣的推荐机制 - 基于用户品味的推荐 

      豆瓣的推荐是通过“豆瓣猜”,为了让用户清楚这些推荐是如何来的,豆瓣还给出了“豆瓣猜”的一个简要的介绍。

      你的个人推荐是根据你的收藏和评价自动得出的,每个人的推荐清单都不同。你的收藏和评价越多,豆瓣给你的推荐会越准确和丰富。
      每天推荐的内容可能会有变化。随着豆瓣的长大,给你推荐的内容也会越来越准。

      这一点让我们可以清晰明了的知道,豆瓣必然是基于社会化的协同过滤的推荐,这样用户越多,用户的反馈越多,那么推荐的效果会越来越准确。

      相对于 Amazon 的用户行为模型,豆瓣电影的模型更加简单,就是“看过”和“想看”,这也让他们的推荐更加专注于用户的品味,毕竟买东西和看电影的动机还是有很大不同的。

      另外,豆瓣也有基于物品本身的推荐,当你查看一些电影的详细信息的时候,他会给你推荐出“喜欢这个电影的人也喜欢的电影”, 如图 10,这是一个基于协同过滤的应用。


      图 10 . 豆瓣的推荐机制 - 基于电影本身的推荐
      图 10 . 豆瓣的推荐机制 - 基于电影本身的推荐 

       

       

       

      总结

      在网络数据爆炸的年代,如何让用户更快的找到想要的数据,如何让用户发现自己潜在的兴趣和需求,无论是对于电子商务还是社会网络的应用都是至关重要的。推荐引擎的出现,使得这个问题越来越被大家关注。但对大多数人来讲,也许还在惊叹它为什么总是能猜到你到底想要些什么。推荐引擎的魔力在于你不清楚在这个推荐背后,引擎到底记录和推理了些什么。

      通过这篇综述性的文章,你可以了解,其实推荐引擎只是默默的记录和观察你的一举一动,然后再借由所有用户产生的海量数据分析和发现其中的规律,进而慢慢的了解你,你的需求,你的习惯,并默默的无声息的帮助你快速的解决你的问题,找到你想要的东西。

      其实,回头想想,很多时候,推荐引擎比你更了解你自己。

      通过第一篇文章,相信大家对推荐引擎有一个清晰的第一印象,本系列的下一篇文章将深入介绍基于协同过滤的推荐策略。在现今的推荐技术和算法中,最被大家广泛认可和采用的就是基于协同过滤的推荐方法。它以其方法模型简单,数据依赖性低,数据方便采集,推荐效果较优等多个优点成为大众眼里的推荐算法“No.1”。本文将带你深入了解协同过滤的秘密,并给出基于 Apache Mahout 的协同过滤算法的高效实现。Apache Mahout 是 ASF 的一个较新的开源项目,它源于 Lucene,构建在 Hadoop 之上,关注海量数据上的机器学习经典算法的高效实现。

      感谢大家对本系列的关注和支持。

      posted @ 2016-07-04 15:48 小马歌 阅读(206) | 评论 (0)编辑 收藏
       

      这是关于 C1000K 序列文章的第二篇, 在前一篇文章 构建C1000K的服务器(1) – 基础 中, 介绍了支持 C1000K 的 Linux 系统的内核参数调整和系统设置. 在本篇文章中, 将对一个真正的应用服务器做 C1000K 测试.

      Comet 服务器是一类逻辑相对简单, 需要高并发连接的服务器. Comet 在网站系统中的应用非常广泛, 可以见这篇日志的介绍: http://www.ideawu.net/blog/archives/737.html.

      HTTP 协议处理

      要开发一个支持百万并发连接的 Comet 服务器, 我选择 C/C++ 语言, 当然还有其它的选择如 Erlang, Java 等. 对于一个只支持 long-polling Comet 服务器, 首先要具备解析 HTTP 协议的能力, 我选择 libevent 来处理 HTTP 协议.

      通道和订阅者管理

      服务器在启动的时候, 就预先分配了 100 万个通道对象的空间, 但订阅者对象是按需分配的, 通过内存池方式. 100 万个通道对象和程序的其它数据占用了 24MB 的内存.

      Benchmark

      启动 icomet 服务器:

      ./icomet 

      服务器监听了 100 个端口, 是为了测试方便, 原因见前一篇文章的分析: 构建C1000K的服务器(1) – 基础.

      然后启动 benchmark 客户端:

      ./tools/benchmark 127.0.0.1 8100 

      benchmark 程序每创建十万个连接, 就会暂停, 按回车后继续. 通过 top/ps 查看 icomet 进程的内存占用. 最终, 得出如下数据.

      连接数进程VIRT进程RES
      039m24m
      100000302m288m
      200000579m565m
      5000001441m1427m
      10000002734m2720m

      可以看到, 每一个 Comet 连接大约占用了 2.7KB 的内存. 此时, 服务器空闲, 进程占用 CPU 为 0%.

      项目的代码在: https://github.com/ideawu/icomet, 欢迎大家试用, 并反馈你的测试数据.

      Related posts:

      1. HTTP 长连接技术 Comet
      2. 构建C1000K的服务器(1) – 基础
      3. iComet 的一个应用场景
      4. 长连接技术的应用
      posted @ 2016-05-26 11:17 小马歌 阅读(302) | 评论 (0)编辑 收藏
       
      from:http://www.ideawu.net/blog/archives/740.html?cp=2#comments

      著名的 C10K 问题提出的时候, 正是 2001 年, 到如今 12 年后的 2013 年, C10K 已经不是问题了, 任何一个普通的程序员, 都能利用手边的语言和库, 轻松地写出 C10K 的服务器. 这既得益于软件的进步, 也得益于硬件性能的提高.

      现在, 该是考虑 C1000K, 也就是百万连接的问题的时候了. 像 Twitter, weibo, Facebook 这些网站, 它们的同时在线用户有上千万, 同时又希望消息能接近实时地推送给用户, 这就需要服务器能维持和上千万用户的 TCP 网络连接, 虽然可以使用成百上千台服务器来支撑这么多用户, 但如果每台服务器能支持一百万连接(C1000K), 那么只需要十台服务器.

      有很多技术声称能解决 C1000K 问题, 例如 Erlang, Java NIO 等等, 不过, 我们应该首先弄明白, 什么因素限制了 C1000K 问题的解决. 主要是这几点:

      1. 操作系统能否支持百万连接?
      2. 操作系统维持百万连接需要多少内存?
      3. 应用程序维持百万连接需要多少内存?
      4. 百万连接的吞吐量是否超过了网络限制?

      下面来分别对这几个问题进行分析.

      1. 操作系统能否支持百万连接?

      对于绝大部分 Linux 操作系统, 默认情况下确实不支持 C1000K! 因为操作系统包含最大打开文件数(Max Open Files)限制, 分为系统全局的, 和进程级的限制.

      全局限制

      在 Linux 下执行:

      cat /proc/sys/fs/file-nr 

      会打印出类似下面的一行输出:

      5100	0	101747 

      第三个数字 101747 就是当前系统的全局最大打开文件数(Max Open Files), 可以看到, 只有 10 万, 所以, 在这台服务器上无法支持 C1000K. 很多系统的这个数值更小, 为了修改这个数值, 用 root 权限修改 /etc/sysctl.conf 文件:

      fs.file-max = 1020000 net.ipv4.ip_conntrack_max = 1020000 net.ipv4.netfilter.ip_conntrack_max = 1020000 

      需要重启系统服务生效:

      # Linux $ sudo sysctl -p /etc/sysctl.conf  # BSD $ sudo /etc/rc.d/sysctl reload 

      进程限制

      执行:

      ulimit -n 

      输出:

      1024 

      说明当前 Linux 系统的每一个进程只能最多打开 1024 个文件. 为了支持 C1000K, 你同样需要修改这个限制.

      临时修改

      ulimit -n 1020000 

      不过, 如果你不是 root, 可能不能修改超过 1024, 会报错:

      -bash: ulimit: open files: cannot modify limit: Operation not permitted 

      永久修改

      编辑 /etc/security/limits.conf 文件, 加入如下行:

      # /etc/security/limits.conf work         hard    nofile      1020000 work         soft    nofile      1020000 

      第一列的 work 表示 work 用户, 你可以填 *, 或者 root. 然后保存退出, 重新登录服务器.

      注意: Linux 内核源码中有一个常量(NR_OPEN in /usr/include/linux/fs.h), 限制了最大打开文件数, 如 RHEL 5 是 1048576(2^20), 所以, 要想支持 C1000K, 你可能还需要重新编译内核.

      2. 操作系统维持百万连接需要多少内存?

      解决了操作系统的参数限制, 接下来就要看看内存的占用情况. 首先, 是操作系统本身维护这些连接的内存占用. 对于 Linux 操作系统, socket(fd) 是一个整数, 所以, 猜想操作系统管理一百万个连接所占用的内存应该是 4M/8M, 再包括一些管理信息, 应该会是 100M 左右. 不过, 还有 socket 发送和接收缓冲区所占用的内存没有分析. 为此, 我写了最原始的 C 网络程序来验证:

      服务器

      #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #include <arpa/inet.h> #include <netinet/tcp.h> #include <sys/select.h>  #define MAX_PORTS 10  int main(int argc, char **argv){     struct sockaddr_in addr;     const char *ip = "0.0.0.0";     int opt = 1;     int bufsize;     socklen_t optlen;     int connections = 0;     int base_port = 7000;     if(argc > 2){         base_port = atoi(argv[1]);     }      int server_socks[MAX_PORTS];      for(int i=0; i<MAX_PORTS; i++){         int port = base_port + i;         bzero(&addr, sizeof(addr));         addr.sin_family = AF_INET;         addr.sin_port = htons((short)port);         inet_pton(AF_INET, ip, &addr.sin_addr);          int serv_sock;         if((serv_sock = socket(AF_INET, SOCK_STREAM, 0)) == -1){             goto sock_err;         }         if(setsockopt(serv_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) == -1){             goto sock_err;         }         if(bind(serv_sock, (struct sockaddr *)&addr, sizeof(addr)) == -1){             goto sock_err;         }         if(listen(serv_sock, 1024) == -1){             goto sock_err;         }          server_socks[i] = serv_sock;         printf("server listen on port: %d\n", port);     }      //optlen = sizeof(bufsize);     //getsockopt(serv_sock, SOL_SOCKET, SO_RCVBUF, &bufsize, &optlen);     //printf("default send/recv buf size: %d\n", bufsize);      while(1){         fd_set readset;         FD_ZERO(&readset);         int maxfd = 0;         for(int i=0; i<MAX_PORTS; i++){             FD_SET(server_socks[i], &readset);             if(server_socks[i] > maxfd){                 maxfd = server_socks[i];             }         }         int ret = select(maxfd + 1, &readset, NULL, NULL, NULL);         if(ret < 0){             if(errno == EINTR){                 continue;             }else{                 printf("select error! %s\n", strerror(errno));                 exit(0);             }         }          if(ret > 0){             for(int i=0; i<MAX_PORTS; i++){                 if(!FD_ISSET(server_socks[i], &readset)){                     continue;                 }                 socklen_t addrlen = sizeof(addr);                 int sock = accept(server_socks[i], (struct sockaddr *)&addr, &addrlen);                 if(sock == -1){                     goto sock_err;                 }                 connections ++;                 printf("connections: %d, fd: %d\n", connections, sock);             }         }     }      return 0; sock_err:     printf("error: %s\n", strerror(errno));     return 0; } 

      注意, 服务器监听了 10 个端口, 这是为了测试方便. 因为只有一台客户端测试机, 最多只能跟同一个 IP 端口创建 30000 多个连接, 所以服务器监听了 10 个端口, 这样一台测试机就可以和服务器之间创建 30 万个连接了.

      客户端

      #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #include <arpa/inet.h> #include <netinet/tcp.h>  int main(int argc, char **argv){     if(argc <=  2){         printf("Usage: %s ip port\n", argv[0]);         exit(0);     }      struct sockaddr_in addr;     const char *ip = argv[1];     int base_port = atoi(argv[2]);     int opt = 1;     int bufsize;     socklen_t optlen;     int connections = 0;      bzero(&addr, sizeof(addr));     addr.sin_family = AF_INET;     inet_pton(AF_INET, ip, &addr.sin_addr);      char tmp_data[10];     int index = 0;     while(1){         if(++index >= 10){             index = 0;         }         int port = base_port + index;         printf("connect to %s:%d\n", ip, port);          addr.sin_port = htons((short)port);          int sock;         if((sock = socket(AF_INET, SOCK_STREAM, 0)) == -1){             goto sock_err;         }         if(connect(sock, (struct sockaddr *)&addr, sizeof(addr)) == -1){             goto sock_err;         }          connections ++;         printf("connections: %d, fd: %d\n", connections, sock);          if(connections % 10000 == 9999){             printf("press Enter to continue: ");             getchar();         }         usleep(1 * 1000);         /*            bufsize = 5000;            setsockopt(serv_sock, SOL_SOCKET, SO_SNDBUF, &bufsize, sizeof(bufsize));            setsockopt(serv_sock, SOL_SOCKET, SO_RCVBUF, &bufsize, sizeof(bufsize));          */     }      return 0; sock_err:     printf("error: %s\n", strerror(errno));     return 0; } 

      我测试 10 万个连接, 这些连接是空闲的, 什么数据也不发送也不接收. 这时, 进程只占用了不到 1MB 的内存. 但是, 通过程序退出前后的 free 命令对比, 发现操作系统用了 200M(大致)内存来维护这 10 万个连接! 如果是百万连接的话, 操作系统本身就要占用 2GB 的内存! 也即 2KB 每连接.

      可以修改

      /proc/sys/net/ipv4/tcp_wmem /proc/sys/net/ipv4/tcp_rmem 

      来控制 TCP 连接的发送和接收缓冲的大小(多谢 @egmkang).

      3. 应用程序维持百万连接需要多少内存?

      通过上面的测试代码, 可以发现, 应用程序维持百万个空闲的连接, 只会占用操作系统的内存, 通过 ps 命令查看可知, 应用程序本身几乎不占用内存.

      4. 百万连接的吞吐量是否超过了网络限制?

      假设百万连接中有 20% 是活跃的, 每个连接每秒传输 1KB 的数据, 那么需要的网络带宽是 0.2M x 1KB/s x 8 = 1.6Gbps, 要求服务器至少是万兆网卡(10Gbps).

      总结

      Linux 系统需要修改内核参数和系统配置, 才能支持 C1000K. C1000K 的应用要求服务器至少需要 2GB 内存, 如果应用本身还需要内存, 这个要求应该是至少 10GB 内存. 同时, 网卡应该至少是万兆网卡.

      当然, 这仅仅是理论分析, 实际的应用需要更多的内存和 CPU 资源来处理业务数据.

      参考:

      http://www.cyberciti.biz/faq/linux-increase-the-maximum-number-of-open-files/
      http://www.lognormal.com/blog/2012/09/27/linux-tcpip-tuning/

      下一篇: 构建C1000K的服务器(2) – 实现

      Related posts:

      1. 要记得清除 sockaddr_in
      2. 构建C1000K的服务器(2) – 实现百万连接的comet服务器
      3. 数据传输中的停止等待机制的实现
      4. Libevent 2 HTTP 客户端示例
      5. 有趣的 main 函数参数
      Posted by ideawu at 2013-09-16 22:01:16 Tags: 
      posted @ 2016-05-26 11:16 小马歌 阅读(256) | 评论 (0)编辑 收藏
       
      from:http://www.ideawu.net/blog/archives/533.html

      "因为TCP端口号是16位无符号整数, 最大65535, 所以一台服务器最多支持65536个TCP socket连接." - 一个非常经典的误解! 即使是有多年网络编程经验的人, 也会持有这个错误结论.

      要戳破这个错误结论, 可以从理论和实践两方面来.

      理论

      系统通过一个四元组来唯一标识一条TCP连接. 这个四元组的结构是{local_ip, local_port, remote_ip, remote_port}, 对于IPv4, 系统理论上最多可以管理2^(32+16+32+16), 2的96次方个连接.

      因为对于同一台服务器来说, 一般只有一个 local_ip, 那么, 同一台服务器可以管理 2^(16+32+16) 个连接. 而一个服务(进程, 如 Nginx 进程)一般只监听一个 local_port, 那么, 同一台服务就可以管理 2^(32+16) 个连接. 而如果从一台远端机器(所谓的 client)来连接这台服务器上的一个服务, 那么 local_ip, local_port, remote_ip 这3个变量是固定的, 那么, 就只能建立 2^16=65536 个连接了. 这就是经典的误解的来源!

      如果不仅仅考虑TCP, 则是一个五元组, 加上协议号(TCP, UDP或者其它).

      实践

      服务器绑定一个ip:port, 然后accept连接, 所有accept的连接使用的本地地址也是同样的ip:port.

      扩展内容

      如果某个客户端向同一个TCP端点(ip:port)发起主动连接, 那么每一条连接都必须使用不同的本地TCP端点, 如果客户端只有一个IP则是使用不同的本地端口, 该端口的范围在*nix系统上的一个例子是32768到61000, 可以通过如下命令查看:

      [root@benegg.com ~]# cat /proc/sys/net/ipv4/ip_local_port_range  32768   61000 

      也就是说, 一个客户端连接同一个服务器的同一个ip:port(比如进行压力测试), 最多可以发起30000个左右的连接.

      TCP客户端(TCP的主动发起者)可以在同一ip:port上向不同的服务器发起主动连接, 只需在bind之前对socket设置SO_REUSEADDR选项.

      系统支持的最大打开文件描述符数(包括socket连接):

      [root@benegg.com ~]# cat /proc/sys/fs/file-max 580382 

      单个进程所能打开的最大文件描述符数:

      [root@benegg.com ~]# ulimit -n 1024 

      结论

      无论是对于服务器还是客户端, 认为"一台机器最多建立65536个TCP连接"是没有根据的, 理论上远远超过这个值.

      另外, 对于client端, 操作系统会自动根据不同的远端 ip:port, 决定是否重用本地端口.

      Related posts:

      1. 连连看游戏开发实践(1) – 算法
      2. 对P2P应用不友好的NAT
      3. Tomcat网站server.xml设置
      4. P2P穿透NAT的思路
      5. SSDB 的双主和多主配置
      Posted by ideawu at 2010-07-16 16:44:50
      posted @ 2016-05-26 11:15 小马歌 阅读(288) | 评论 (0)编辑 收藏
       

      在几个月前改造dubbo时,netty4已经稳定很久了,一时手痒,按照netty3-rpc的源码克隆了一套netty4,在修正了大量的包、类型不同之后,基本保持了netty3的风格,并发量小或者数据包很小时,一切都很ok, 在进行大并发测试时,结果和netty3完全不同,基本用惨不忍睹来形容。由于当时急于开发php客户端,就把netty4-rpc当做一个失败的组件存档起来, 前几天php-dubbo开发基本完成之后,返回过来思考netty4-rpc的问题,经过仔细分析数据包的解析过程,单步跟踪源码

      NettyCodecAdapter, TelnetCodec, ExchangeCoedec,发现ByteBuf的缓冲区为1024,当数据超过1024时,会调用多次Decoder.messageReceived函数,第一次分析dubbo的协议头时,是正确的,第二次之后数据就错误了,然后dubbo内部缓冲区的数据越来越长,但是仍然分析不到一个完整的dubbo数据包

      因此去看netty4的源码,发现AbstractNioByteChannel中有网络数据接收的代码时这么处理ByteBuf的

        ByteBuf byteBuf = null;
                  int messages = 0;
                  boolean close = false;
                  try {
                      int totalReadAmount = 0;
                      boolean readPendingReset = false;
                      do {
                          byteBuf = allocHandle.allocate(allocator);
                          int writable = byteBuf.writableBytes();
                          int localReadAmount = doReadBytes(byteBuf);
                          if (localReadAmount <= 0) {
                              // not was read release the buffer
                              byteBuf.release();
                              close = localReadAmount < 0;
                              break;
                          }
                          if (!readPendingReset) {
                              readPendingReset = true;
                              setReadPending(false);
                          }
                          pipeline.fireChannelRead(byteBuf);
                          byteBuf = null;

      看见没,内核是需要ByteBuf.release的,继续通过byteBuf的一个实现PooledByteBuf分析源码,原来是实现了一个基于简单计数应用计数的循环使用的缓冲区,一旦计数变为1,该缓冲区被归还到netty4内核,被后面的数据读取线程重新使用

      而我们InternalDecoder的代码为

            message = com.alibaba.dubbo.remoting.buffer.ChannelBuffers.wrappedBuffer(
                          input.toByteBuffer());

      直接引用了ByteBuf.toByteBuffer,继续查看源码UnpooledHeapByteBuf, 其toByteBuffer实际是对内部数据的

      一个nio封装而已,因此,使用上述函数时,导致dubbo的decode保存了一个某一个ByteBuffer的内部数据,但是虽有该

      buffer被归还到netty4缓冲区中被循环引用,下一次可能被其他读写线程重新改写数据,因此,高并发下当缓冲区被重复使用时,bytebuf将由于计数问题不断被使用,而解码器中缺傻傻等待。

      解决方案

      1.通过byteBuf的retain和release函数保证计数的有效性,通过程序例外或者缓冲区被使用完成时候归还ByteBuf到netty4内核

      2.拷贝数据到dubbo的缓冲区中

      思考:

      netty3 是否也有该问题呢???

      posted @ 2016-05-17 15:25 小马歌 阅读(1496) | 评论 (1)编辑 收藏
       
           摘要: from:http://hittyt.iteye.com/blog/1691772Hessian反序列化问题众所周知,Hessian框架提供的序列化方式,在性能上要优于Java自己的序列化方式。他将对象序列化,生成的字节数组的数量要相对于Java自带的序列化方式要更简洁。目前公司的一个项目中,有RPC调用的需要,这里我们使用了公司自己的开源RPC框架Dubbo作为远程调用框架,进行业务方法的调用和...  阅读全文
      posted @ 2016-05-17 15:24 小马歌 阅读(1341) | 评论 (0)编辑 收藏
       
      from:http://my.oschina.net/aruan/blog/351594

      同事刘阳使用dubbo服务器中配置mina作为网络传输层,发现大并发情况下,解码发生如下异常

      014-12-01 18:00:44,652 [DubboServerHandler-10.1.19.13:20880-thread-164] WARN  alibaba.dubbo.remoting.exchange.codec.ExchangeCodec (ExchangeCodec.java:596) -  [DUBBO] Fail to encode response: Response [id=8119, version=2.0.0, status=40, event=false, error=Fail to decode request due to: RpcInvocation [methodName=null, parameterTypes=null, arguments=null, attachments={input=242}, headers=null], result=null], send bad_response info instead, cause: null, dubbo version: 2.5.5, current host: 127.0.0.1
      java.lang.NullPointerException
          at com.alibaba.dubbo.rpc.protocol.dubbo.DubboCodec.encodeResponseData(DubboCodec.java:301)
          at com.alibaba.dubbo.remoting.exchange.codec.ExchangeCodec.encodeResponse(ExchangeCodec.java:560)
          at com.alibaba.dubbo.remoting.exchange.codec.ExchangeCodec.encode(ExchangeCodec.java:104)
          at com.alibaba.dubbo.rpc.protocol.dubbo.DubboCountCodec.encode(DubboCountCodec.java:39)
          at com.alibaba.dubbo.remoting.transport.mina.MinaCodecAdapter$InternalEncoder.encode(MinaCodecAdapter.java:79)
          at org.apache.mina.filter.codec.ProtocolCodecFilter.filterWrite(ProtocolCodecFilter.java:214)
          at org.apache.mina.common.support.AbstractIoFilterChain.callPreviousFilterWrite(AbstractIoFilterChain.java:361)
          at org.apache.mina.common.support.AbstractIoFilterChain.access$1300(AbstractIoFilterChain.java:53)
          at org.apache.mina.common.support.AbstractIoFilterChain$EntryImpl$1.filterWrite(AbstractIoFilterChain.java:659)
          at org.apache.mina.common.support.AbstractIoFilterChain$TailFilter.filterWrite(AbstractIoFilterChain.java:587)
          at org.apache.mina.common.support.AbstractIoFilterChain.callPreviousFilterWrite(AbstractIoFilterChain.java:361)
          at org.apache.mina.common.support.AbstractIoFilterChain.fireFilterWrite(AbstractIoFilterChain.java:355)
          at org.apache.mina.transport.socket.nio.SocketSessionImpl.write0(SocketSessionImpl.java:166)
          at org.apache.mina.common.support.BaseIoSession.write(BaseIoSession.java:177)
          at org.apache.mina.common.support.BaseIoSession.write(BaseIoSession.java:168)
          at com.alibaba.dubbo.remoting.transport.mina.MinaChannel.send(MinaChannel.java:95)
          at com.alibaba.dubbo.remoting.transport.AbstractPeer.send(AbstractPeer.java:51)
          at 

      经过对比netty3和netty4作为传输层,却都没有发现类似的问题。

      首先排除不是mina本身的问题,mina也没有爆出有这个问题,初步判断dubbo在使用mina时机制有问题

      经过对比发现

      1.netty是为每一个channel分配了一个NettyCodecAdapter, mina确实在服务器监听前配置了MinaCodecAdapter

      2.也就是说,netty的每一个独立的通道的Codec(encoder/decoder)是通道安全的

      3.mina的所有通道是共享相同的codec(encoder/decoder)的,因此,解码器中的实例数据时非channel安全的

      因此解码器中与netty相同的解码器的缓冲数据算法在并发情况下将会产生数据覆盖问题。

      4.解决方案

              1.配置acceptor的监听器

      codecAdapter = new MinaCodecAdapter(getCodec(), getUrl(), this);
              acceptor.getFilterChain().addLast("codec", new ProtocolCodecFilter(codecAdapter));

          acceptor.addListener(new IoServiceListener(){
                  @Override
                  public void serviceActivated(IoService service,
                          SocketAddress serviceAddress, IoHandler handler,
                          IoServiceConfig config) {
                  }

                  @Override
                  public void serviceDeactivated(IoService service,
                          SocketAddress serviceAddress, IoHandler handler,
                          IoServiceConfig config) {
                  }

                  @Override
                  public void sessionCreated(IoSession session) {
                      codecAdapter.sessionCreated(session);
                  }

                  @Override
                  public void sessionDestroyed(IoSession session) {
                      codecAdapter.sessionDestroyed(session);
                  }
              });

          2.监听session的create和destroy事件,传递到decoder中,decoder中,通过session和buffer的键值对保存对不同通道的数据的缓存,

      private Map<IoSession, ChannelBuffer> buffers = new ConcurrentHashMap<IoSession, ChannelBuffer>();

              // ChannelBuffers.EMPTY_BUFFER;

              public void sessionCreated(IoSession session) {
                  buffers.put(session, ChannelBuffers.EMPTY_BUFFER);
              }

              public void sessionDestroyed(IoSession session) {
                  buffers.remove(session);
              }

          3.解码时通过session获得当前channel的数据

      ChannelBuffer buffer = buffers.get(session);
                  if(buffer == null) return;


      经过测试,问题得以解决

      posted @ 2016-05-17 15:24 小马歌 阅读(1193) | 评论 (0)编辑 收藏
       

      其实这个不算是一个坑,阿里实现的字符串协议挺好的,但是由于我个人的强迫症和在编写php客户端过程中对字符串的输出和解析感觉很别扭,尤其是字符串数据很大时,必须一个字节一个字节的判断处理,让我很郁闷,明显和我当年编写汇编时的哪种精致不符。体现在两个方面

      1.字符串的格式定义为 字母S或者R + 两个字节的数据长度(MSB)+ utf8格式的字节数组,S表示最后一个块,上面那个长度是unicode 

      字符串的长度,不是自己数组的长度,即"中国"这个词,长度是2,utf8表示的字节数组确实6个字节

      2.我的php客户端是用c混合c++编写的php扩展,输出字符串时首先调用libmbfl库计算unicode字符串的长度,然后输出utf8数据,因为在php中,我们默认采用utf8格式,字符串zval已经是utf8格式了,并且附带一个utf8长度的整数,

      3.读取应答分析字符串时,根据上面的长度并不知道该分配多少内存来接受整个字符串,因为长度和utf8的字节长度根本没有关系,还有多个节时防止utf8字母被分配到多个不同的chunk上面

      4.因此,我们重新定义了一个字符串数据协议, E +4字节UTF8字节长度(MSB)+ utf8表示一个完整的字符串,4字节时,表示的数据有2^31-1个utf8字节,可以表示好几百兆的汉字,够用了,这样我们可以使用c语言的memcpy函数进行快速数据拷贝和数据缓冲区分配了

      5.经过测试,大量字符串的数据传输性能能提高5%

      posted @ 2016-05-17 15:24 小马歌 阅读(279) | 评论 (0)编辑 收藏
       

      from:http://pfmiles.github.io/blog/recently-hessian-deserialize-problem-and-thread-pool-executor-experience/

      最近工作中遇到一个诡异的问题:别人远程调用我们的系统暴露的服务,同步调用,底层使用hessian协议做序列化;
      调用方系统报空指针,反序列化失败:

      2013-04-18 16:52:10,308 [AvatarRuleChargeService.java:74] [com.alibaba.itbu.billing.biz.adaptor.avatar.AvatarRuleChargeService] ERROR com.alibaba.itbu.billing.biz.adaptor.crm.ChargeProxy :: avatar charge sys error com.alibaba.dubbo.rpc.RpcException: Failed to invoke the method match in the service com.alibaba.china.ruleservice.RuleService. Tried 3 times of the providers [172.22.6.83:20980, 172.22.6.80:20980, 172.22.9.76:20980] (3/3) from the registry dubbo-reg1.hst.xyi.cn.alidc.net:9090 on the consumer 172.30.118.26 using the dubbo version 2.4.9. Last error is: Failed to invoke remote method: match, provider: dubbo://172.22.6.83:20980/com.alibaba.china.ruleservice.RuleService?anyhost=true&application=billing&check=false&default.reference.filter=dragoon&dubbo=2.4.9&interface=com.alibaba.china.ruleservice.RuleService&methods=match&pid=18616&revision=1.0-SNAPSHOT&side=consumer&timeout=5000&timestamp=1366275108588&version=1.0.0, cause: com.alibaba.com.caucho.hessian.io.HessianProtocolException: 'com.alibaba.china.ruleservice.RuleServiceImpl$DynamicPluginInvocationMatchedResult' could not be instantiated com.alibaba.com.caucho.hessian.io.HessianProtocolException: 'com.alibaba.china.ruleservice.RuleServiceImpl$DynamicPluginInvocationMatchedResult' could not be instantiated     at com.alibaba.com.caucho.hessian.io.JavaDeserializer.instantiate(JavaDeserializer.java:275)     at com.alibaba.com.caucho.hessian.io.JavaDeserializer.readObject(JavaDeserializer.java:155)     at com.alibaba.com.caucho.hessian.io.SerializerFactory.readObject(SerializerFactory.java:396)     at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObjectInstance(Hessian2Input.java:2070)     at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:2005)     at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:1990)     at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:1538)     at com.alibaba.dubbo.common.serialize.support.hessian.Hessian2ObjectInput.readObject(Hessian2ObjectInput.java:94)     at com.alibaba.dubbo.common.serialize.support.hessian.Hessian2ObjectInput.readObject(Hessian2ObjectInput.java:99)     at com.alibaba.dubbo.rpc.protocol.dubbo.DecodeableRpcResult.decode(DecodeableRpcResult.java:83)     at com.alibaba.dubbo.rpc.protocol.dubbo.DecodeableRpcResult.decode(DecodeableRpcResult.java:109)     at com.alibaba.dubbo.rpc.protocol.dubbo.DubboCodec.decodeBody(DubboCodec.java:97)     at com.alibaba.dubbo.remoting.exchange.codec.ExchangeCodec.decode(ExchangeCodec.java:128)     at com.alibaba.dubbo.remoting.exchange.codec.ExchangeCodec.decode(ExchangeCodec.java:87)     at com.alibaba.dubbo.rpc.protocol.dubbo.DubboCountCodec.decode(DubboCountCodec.java:49)     at com.alibaba.dubbo.remoting.transport.netty.NettyCodecAdapter$InternalDecoder.messageReceived(NettyCodecAdapter.java:135)     at org.jboss.netty.channel.SimpleChannelUpstreamHandler.handleUpstream(SimpleChannelUpstreamHandler.java:80)     at org.jboss.netty.channel.DefaultChannelPipeline.sendUpstream(DefaultChannelPipeline.java:564)     at org.jboss.netty.channel.DefaultChannelPipeline.sendUpstream(DefaultChannelPipeline.java:559)     at org.jboss.netty.channel.Channels.fireMessageReceived(Channels.java:274)     at org.jboss.netty.channel.Channels.fireMessageReceived(Channels.java:261)     at org.jboss.netty.channel.socket.nio.NioWorker.read(NioWorker.java:349)     at org.jboss.netty.channel.socket.nio.NioWorker.processSelectedKeys(NioWorker.java:280)     at org.jboss.netty.channel.socket.nio.NioWorker.run(NioWorker.java:200)     at org.jboss.netty.util.ThreadRenamingRunnable.run(ThreadRenamingRunnable.java:108)     at org.jboss.netty.util.internal.DeadLockProofWorker$1.run(DeadLockProofWorker.java:44)     at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)     at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)     at java.lang.Thread.run(Thread.java:662) Caused by: java.lang.reflect.InvocationTargetException     at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)     at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:39)     at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:27)     at java.lang.reflect.Constructor.newInstance(Constructor.java:513)     at com.alibaba.com.caucho.hessian.io.JavaDeserializer.instantiate(JavaDeserializer.java:271)     ... 28 more Caused by: java.lang.NullPointerException     at com.alibaba.china.ruleservice.RuleServiceImpl$DynamicPluginInvocationMatchedResult.<init>(RuleServiceImpl.java:163)     ... 33 more      at com.alibaba.dubbo.rpc.cluster.support.FailoverClusterInvoker.doInvoke(FailoverClusterInvoker.java:101)     at com.alibaba.dubbo.rpc.cluster.support.AbstractClusterInvoker.invoke(AbstractClusterInvoker.java:226)     at com.alibaba.dubbo.rpc.cluster.support.wrapper.MockClusterInvoker.invoke(MockClusterInvoker.java:72)     at com.alibaba.dubbo.rpc.proxy.InvokerInvocationHandler.invoke(InvokerInvocationHandler.java:52)     at com.alibaba.dubbo.common.bytecode.proxy1.match(proxy1.java)     at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)     at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)     at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)     at java.lang.reflect.Method.invoke(Method.java:597)     at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:307)     at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:182)     at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:149)     at org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint.proceed(MethodInvocationProceedingJoinPoint.java:88)     at com.alibaba.itbu.billing.framework.aop.OpenApiLogAspect.logExecuteTime(OpenApiLogAspect.java:38)     at sun.reflect.GeneratedMethodAccessor199.invoke(Unknown Source)     at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)     at java.lang.reflect.Method.invoke(Method.java:597)     at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:627)     at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:616)     at org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:64)     at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:171)     at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:89)     at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:171)     at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:204)     at $Proxy107.match(Unknown Source)     at com.alibaba.itbu.billing.biz.adaptor.avatar.AvatarRuleChargeService.chargeByFactor(AvatarRuleChargeService.java:72)     at com.alibaba.itbu.billing.biz.charge.times.RuleChargeByTimesProcessor.getChargeResult(RuleChargeByTimesProcessor.java:62)     at com.alibaba.itbu.billing.biz.charge.times.ChargeByTimesProcessor.charge(ChargeByTimesProcessor.java:117)     at com.alibaba.itbu.billing.biz.task.ChargeByIncInstantTimesTask.charge(ChargeByIncInstantTimesTask.java:174)     at com.alibaba.itbu.billing.biz.task.ChargeByIncInstantTimesTask$1.run(ChargeByIncInstantTimesTask.java:109)     at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)     at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)     at java.lang.Thread.run(Thread.java:662) Caused by: com.alibaba.dubbo.remoting.RemotingException: com.alibaba.com.caucho.hessian.io.HessianProtocolException: 'com.alibaba.china.ruleservice.RuleServiceImpl$DynamicPluginInvocationMatchedResult' could not be instantiated com.alibaba.com.caucho.hessian.io.HessianProtocolException: 'com.alibaba.china.ruleservice.RuleServiceImpl$DynamicPluginInvocationMatchedResult' could not be instantiated     at com.alibaba.com.caucho.hessian.io.JavaDeserializer.instantiate(JavaDeserializer.java:275)     at com.alibaba.com.caucho.hessian.io.JavaDeserializer.readObject(JavaDeserializer.java:155)     at com.alibaba.com.caucho.hessian.io.SerializerFactory.readObject(SerializerFactory.java:396)     at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObjectInstance(Hessian2Input.java:2070)     at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:2005)     at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:1990)     at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:1538)     at com.alibaba.dubbo.common.serialize.support.hessian.Hessian2ObjectInput.readObject(Hessian2ObjectInput.java:94)     at com.alibaba.dubbo.common.serialize.support.hessian.Hessian2ObjectInput.readObject(Hessian2ObjectInput.java:99)     at com.alibaba.dubbo.rpc.protocol.dubbo.DecodeableRpcResult.decode(DecodeableRpcResult.java:83)     at com.alibaba.dubbo.rpc.protocol.dubbo.DecodeableRpcResult.decode(DecodeableRpcResult.java:109)     at com.alibaba.dubbo.rpc.protocol.dubbo.DubboCodec.decodeBody(DubboCodec.java:97)     at com.alibaba.dubbo.remoting.exchange.codec.ExchangeCodec.decode(ExchangeCodec.java:128)     at com.alibaba.dubbo.remoting.exchange.codec.ExchangeCodec.decode(ExchangeCodec.java:87)     at com.alibaba.dubbo.rpc.protocol.dubbo.DubboCountCodec.decode(DubboCountCodec.java:49)     at com.alibaba.dubbo.remoting.transport.netty.NettyCodecAdapter$InternalDecoder.messageReceived(NettyCodecAdapter.java:135)     at org.jboss.netty.channel.SimpleChannelUpstreamHandler.handleUpstream(SimpleChannelUpstreamHandler.java:80)     at org.jboss.netty.channel.DefaultChannelPipeline.sendUpstream(DefaultChannelPipeline.java:564)     at org.jboss.netty.channel.DefaultChannelPipeline.sendUpstream(DefaultChannelPipeline.java:559)     at org.jboss.netty.channel.Channels.fireMessageReceived(Channels.java:274)     at org.jboss.netty.channel.Channels.fireMessageReceived(Channels.java:261)     at org.jboss.netty.channel.socket.nio.NioWorker.read(NioWorker.java:349)     at org.jboss.netty.channel.socket.nio.NioWorker.processSelectedKeys(NioWorker.java:280)     at org.jboss.netty.channel.socket.nio.NioWorker.run(NioWorker.java:200)     at org.jboss.netty.util.ThreadRenamingRunnable.run(ThreadRenamingRunnable.java:108)     at org.jboss.netty.util.internal.DeadLockProofWorker$1.run(DeadLockProofWorker.java:44)     at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)     at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)     at java.lang.Thread.run(Thread.java:662) Caused by: java.lang.reflect.InvocationTargetException     at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)     at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:39)     at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:27)     at java.lang.reflect.Constructor.newInstance(Constructor.java:513)     at com.alibaba.com.caucho.hessian.io.JavaDeserializer.instantiate(JavaDeserializer.java:271)     ... 28 more Caused by: java.lang.NullPointerException     at com.alibaba.china.ruleservice.RuleServiceImpl$DynamicPluginInvocationMatchedResult.<init>(RuleServiceImpl.java:163)     ... 33 more      at com.alibaba.dubbo.remoting.exchange.support.DefaultFuture.returnFromResponse(DefaultFuture.java:190)     at com.alibaba.dubbo.remoting.exchange.support.DefaultFuture.get(DefaultFuture.java:110)     at com.alibaba.dubbo.remoting.exchange.support.DefaultFuture.get(DefaultFuture.java:84)     at com.alibaba.dubbo.rpc.protocol.dubbo.DubboInvoker.doInvoke(DubboInvoker.java:96)     at com.alibaba.dubbo.rpc.protocol.AbstractInvoker.invoke(AbstractInvoker.java:144)     at com.alibaba.dubbo.rpc.listener.ListenerInvokerWrapper.invoke(ListenerInvokerWrapper.java:74)     at com.alibaba.dubbo.monitor.dragoon.filter.DragoonFilter.invoke0(DragoonFilter.java:82)     at com.alibaba.dubbo.monitor.dragoon.filter.DragoonFilter.invoke(DragoonFilter.java:34)     at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:91)     at com.alibaba.dubbo.monitor.support.MonitorFilter.invoke(MonitorFilter.java:75)     at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:91)     at com.alibaba.dubbo.rpc.protocol.dubbo.filter.FutureFilter.invoke(FutureFilter.java:53)     at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:91)     at com.alibaba.dubbo.rpc.filter.ConsumerContextFilter.invoke(ConsumerContextFilter.java:48)     at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:91)     at com.alibaba.dubbo.rpc.protocol.InvokerWrapper.invoke(InvokerWrapper.java:53)     at com.alibaba.dubbo.rpc.cluster.support.FailoverClusterInvoker.doInvoke(FailoverClusterInvoker.java:77)     ... 32 more 

      看到这个日志第一反映是觉得RuleServiceImpl.java:163数据错误,抛空指针,但检查那块代码发现那个地方根本不可能抛空指针 —— 所有用到的变量都是new出来的;
      而且,依据调用方提供的错误日志的抛出时间,我在被调用方系统的所有机器的日志里查找了一遍,没有发现对应的服务端日志;照理说服务端抛空指针,服务端也会有对应日志,但没有任何线索…

      因此只好翻开了JavaDeserializer.java:275作检查,发现有这么一段:

        public JavaDeserializer(Class cl)   {     _type = cl;     _fieldMap = getFieldMap(cl);      _readResolve = getReadResolve(cl);      if (_readResolve != null) {       _readResolve.setAccessible(true);     }      Constructor []constructors = cl.getDeclaredConstructors();     long bestCost = Long.MAX_VALUE;      for (int i = 0; i < constructors.length; i++) {       Class []param = constructors[i].getParameterTypes();       long cost = 0;        for (int j = 0; j < param.length; j++) {     cost = 4 * cost;      if (Object.class.equals(param[j]))       cost += 1;     else if (String.class.equals(param[j]))       cost += 2;     else if (int.class.equals(param[j]))       cost += 3;     else if (long.class.equals(param[j]))       cost += 4;     else if (param[j].isPrimitive())       cost += 5;     else       cost += 6;       }        if (cost < 0 || cost > (1 << 48))     cost = 1 << 48;        cost += param.length << 48;        if (cost < bestCost) {         _constructor = constructors[i];         bestCost = cost;       }     }      if (_constructor != null) {       _constructor.setAccessible(true);       Class []params = _constructor.getParameterTypes();       _constructorArgs = new Object[params.length];       for (int i = 0; i < params.length; i++) {         _constructorArgs[i] = getParamArg(params[i]);       }     }   } 

      看完这段后,再结合远程调用的返回结果类后恍然大悟:
      上面这段代码,是hessian在反序列化的时候,用于在被反序列化的类里面找一个“得分最低”的构造函数,反序列化时会加以调用;
      构造函数的“得分”规则大致是:参数越少得分越低;参数个数相同时,参数类型越接近JDK内置类得分越低
      而我们的调用返回的类只有一个构造函数,当然只有这个构造函数会被选中
      但是,hessian反序列化调用被选中的构造函数时,是这样来创造该构造函数需要的参数的:

      protected static Object getParamArg(Class cl)   {     if (! cl.isPrimitive())       return null;     else if (boolean.class.equals(cl))       return Boolean.FALSE;     else if (byte.class.equals(cl))       return new Byte((byte) 0);     else if (short.class.equals(cl))       return new Short((short) 0);     else if (char.class.equals(cl))       return new Character((char) 0);     else if (int.class.equals(cl))       return new Integer(0);     else if (long.class.equals(cl))       return new Long(0);     else if (float.class.equals(cl))       return new Float(0);     else if (double.class.equals(cl))       return new Double(0);     else       throw new UnsupportedOperationException();   } 

      可以看到,如果参数不是primitive类型,会被null代替;但不巧的是我们的构造函数内部对该参数调用了一个方法,因此抛出了空指针…
      也就是说这个空指针是抛在客户端反序列化的时候而不是服务端内部,因此服务端当然找不到对应的错误日志了;
      解决的办法也很简单:可以新增一个无参构造函数(无参构造函数肯定“得分”最低一定会被选中);或者修改代码保证任意参数为null的时候都不会出问题


      下面这个问题更有意思,说的是ThreadPoolExecutorRejectedExecutionHandler的使用:

      threadPool = new ThreadPoolExecutor(5, maxThreadNum, 5, TimeUnit.MINUTES, new SynchronousQueue<Runnable>(), tf,             new RejectedExecutionHandler() {                 public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {                     logger.error("Ep thread pool exhausted, epId: " + id + "!!");                     r.run();                 }             }); 

      这个代码是说,当我这个ThreadPool不够用,又不能再新增线程数的时候,由调用方线程自己来执行这个Runnable任务…
      本来这看上去没什么问题,问题出在这个Runnable本身的实现上 —— 它内部将执行它的线程block到了一个blocking queue上面,当调用方主线程亲自来执行它时,使得主线程再也回不去做它自己该做的事情了,因此会出大问题…
      所以这么看来,“当线程池不够用就让调用方线程自己来干”的这个策略在实际使用时要非常谨慎

      这个问题是通过一个方便的thread dump分析工具: tda来查找的,因为出问题的这个应用是个多线程程序,有1600多个常驻线程,thread dump非常之大,肉眼直接看很不方便,但tda能将thread dump变得更友好易读,方便排查问题,在此推荐一下

      posted @ 2016-05-17 15:22 小马歌 阅读(868) | 评论 (0)编辑 收藏
       
      from:http://www.infoq.com/cn/articles/Kubernetes-system-architecture-introduction

      1. 前言

      Together we will ensure that Kubernetes is a strong and open container management framework for any application and in any environment, whether in a private, public or hybrid cloud.

      Urs Hölzle, Google

      Kubernetes作为Docker生态圈中重要一员,是Google多年大规模容器管理技术的开源版本,是产线实践经验的最佳表现[G1] 。如Urs Hölzle所说,无论是公有云还是私有云甚至混合云,Kubernetes将作为一个为任何应用,任何环境的容器管理框架无处不在。正因为如此, 目前受到各大巨头及初创公司的青睐,如Microsoft、VMWare、Red Hat、CoreOS、Mesos等,纷纷加入给Kubernetes贡献代码。随着Kubernetes社区及各大厂商的不断改进、发展,Kuberentes将成为容器管理领域的领导者。

      接下来我们会用一系列文章逐一探索Kubernetes是什么、能做什么以及怎么做。

      2. 什么是Kubernetes

      Kubernetes是Google开源的容器集群管理系统,其提供应用部署、维护、 扩展机制等功能,利用Kubernetes能方便地管理跨机器运行容器化的应用,其主要功能如下:

      1) 使用Docker对应用程序包装(package)、实例化(instantiate)、运行(run)。

      2) 以集群的方式运行、管理跨机器的容器。

      3) 解决Docker跨机器容器之间的通讯问题。

      4) Kubernetes的自我修复机制使得容器集群总是运行在用户期望的状态。

      当前Kubernetes支持GCE、vShpere、CoreOS、OpenShift、Azure等平台,除此之外,也可以直接运行在物理机上。

      接下来本文主要从以下几方面阐述Kubernetes:

      1) Kubernetes的主要概念。

      2) Kubernetes的构件,包括Master组件、Kubelet、Proxy的详细介绍。

      3. Kubernetes主要概念

      3.1. Pods

      Pod是Kubernetes的基本操作单元,把相关的一个或多个容器构成一个Pod,通常Pod里的容器运行相同的应用。Pod包含的容器运行在同一个Minion(Host)上,看作一个统一管理单元,共享相同的volumes和network namespace/IP和Port空间。

      3.2. Services

      Services也是Kubernetes的基本操作单元,是真实应用服务的抽象,每一个服务后面都有很多对应的容器来支持,通过Proxy的port和服务selector决定服务请求传递给后端提供服务的容器,对外表现为一个单一访问接口,外部不需要了解后端如何运行,这给扩展或维护后端带来很大的好处。

      3.3. Replication Controllers

      Replication Controller确保任何时候Kubernetes集群中有指定数量的pod副本(replicas)在运行, 如果少于指定数量的pod副本(replicas),Replication Controller会启动新的Container,反之会杀死多余的以保证数量不变。Replication Controller使用预先定义的pod模板创建pods,一旦创建成功,pod 模板和创建的pods没有任何关联,可以修改pod 模板而不会对已创建pods有任何影响,也可以直接更新通过Replication Controller创建的pods。对于利用pod 模板创建的pods,Replication Controller根据label selector来关联,通过修改pods的label可以删除对应的pods。Replication Controller主要有如下用法:

      1) Rescheduling

      如上所述,Replication Controller会确保Kubernetes集群中指定的pod副本(replicas)在运行, 即使在节点出错时。

      2) Scaling

      通过修改Replication Controller的副本(replicas)数量来水平扩展或者缩小运行的pods。

      3) Rolling updates

      Replication Controller的设计原则使得可以一个一个地替换pods来rolling updates服务。

      4) Multiple release tracks

      如果需要在系统中运行multiple release的服务,Replication Controller使用labels来区分multiple release tracks。

      3.4. Labels

      Labels是用于区分Pod、Service、Replication Controller的key/value键值对,Pod、Service、 Replication Controller可以有多个label,但是每个label的key只能对应一个value。Labels是Service和Replication Controller运行的基础,为了将访问Service的请求转发给后端提供服务的多个容器,正是通过标识容器的labels来选择正确的容器。同样,Replication Controller也使用labels来管理通过pod 模板创建的一组容器,这样Replication Controller可以更加容易,方便地管理多个容器,无论有多少容器。

      4. Kubernetes构件

      Kubenetes整体框架如下图3-1,主要包括kubecfg、Master API Server、Kubelet、Minion(Host)以及Proxy。

      图3-1 Kubernetes High Level构件

      4.1. Master

      Master定义了Kubernetes 集群Master/API Server的主要声明,包括Pod Registry、Controller Registry、Service Registry、Endpoint Registry、Minion Registry、Binding Registry、RESTStorage以及Client, 是client(Kubecfg)调用Kubernetes API,管理Kubernetes主要构件Pods、Services、Minions、容器的入口。Master由API Server、Scheduler以及Registry等组成。从下图3-2可知Master的工作流主要分以下步骤:

      1) Kubecfg将特定的请求,比如创建Pod,发送给Kubernetes Client。

      2) Kubernetes Client将请求发送给API server。

      3) API Server根据请求的类型,比如创建Pod时storage类型是pods,然后依此选择何种REST Storage API对请求作出处理。

      4) REST Storage API对的请求作相应的处理。

      5) 将处理的结果存入高可用键值存储系统Etcd中。

      6) 在API Server响应Kubecfg的请求后,Scheduler会根据Kubernetes Client获取集群中运行Pod及Minion信息。

      7) 依据从Kubernetes Client获取的信息,Scheduler将未分发的Pod分发到可用的Minion节点上。

      下面是Master的主要构件的详细介绍:

      图3-2 Master主要构件及工作流

      3.1.1. Minion Registry

      Minion Registry负责跟踪Kubernetes 集群中有多少Minion(Host)。Kubernetes封装Minion Registry成实现Kubernetes API Server的RESTful API接口REST,通过这些API,我们可以对Minion Registry做Create、Get、List、Delete操作,由于Minon只能被创建或删除,所以不支持Update操作,并把Minion的相关配置信息存储到etcd。除此之外,Scheduler算法根据Minion的资源容量来确定是否将新建Pod分发到该Minion节点。

      3.1.2. Pod Registry

      Pod Registry负责跟踪Kubernetes集群中有多少Pod在运行,以及这些Pod跟Minion是如何的映射关系。将Pod Registry和Cloud Provider信息及其他相关信息封装成实现Kubernetes API Server的RESTful API接口REST。通过这些API,我们可以对Pod进行Create、Get、List、Update、Delete操作,并将Pod的信息存储到etcd中,而且可以通过Watch接口监视Pod的变化情况,比如一个Pod被新建、删除或者更新。

      3.1.3. Service Registry

      Service Registry负责跟踪Kubernetes集群中运行的所有服务。根据提供的Cloud Provider及Minion Registry信息把Service Registry封装成实现Kubernetes API Server需要的RESTful API接口REST。利用这些接口,我们可以对Service进行Create、Get、List、Update、Delete操作,以及监视Service变化情况的watch操作,并把Service信息存储到etcd。

      3.1.4. Controller Registry

      Controller Registry负责跟踪Kubernetes集群中所有的Replication Controller,Replication Controller维护着指定数量的pod 副本(replicas)拷贝,如果其中的一个容器死掉,Replication Controller会自动启动一个新的容器,如果死掉的容器恢复,其会杀死多出的容器以保证指定的拷贝不变。通过封装Controller Registry为实现Kubernetes API Server的RESTful API接口REST, 利用这些接口,我们可以对Replication Controller进行Create、Get、List、Update、Delete操作,以及监视Replication Controller变化情况的watch操作,并把Replication Controller信息存储到etcd。

      3.1.5. Endpoints Registry

      Endpoints Registry负责收集Service的endpoint,比如Name:"mysql",Endpoints: ["10.10.1.1:1909","10.10.2.2:8834"],同Pod Registry,Controller Registry也实现了Kubernetes API Server的RESTful API接口,可以做Create、Get、List、Update、Delete以及watch操作。

      3.1.6. Binding Registry

      Binding包括一个需要绑定Pod的ID和Pod被绑定的Host,Scheduler写Binding Registry后,需绑定的Pod被绑定到一个host。Binding Registry也实现了Kubernetes API Server的RESTful API接口,但Binding Registry是一个write-only对象,所有只有Create操作可以使用, 否则会引起错误。

      3.1.7. Scheduler

      Scheduler收集和分析当前Kubernetes集群中所有Minion节点的资源(内存、CPU)负载情况,然后依此分发新建的Pod到Kubernetes集群中可用的节点。由于一旦Minion节点的资源被分配给Pod,那这些资源就不能再分配给其他Pod, 除非这些Pod被删除或者退出, 因此,Kubernetes需要分析集群中所有Minion的资源使用情况,保证分发的工作负载不会超出当前该Minion节点的可用资源范围。具体来说,Scheduler做以下工作:

      1) 实时监测Kubernetes集群中未分发的Pod。

      2) 实时监测Kubernetes集群中所有运行的Pod,Scheduler需要根据这些Pod的资源状况安全地将未分发的Pod分发到指定的Minion节点上。

      3) Scheduler也监测Minion节点信息,由于会频繁查找Minion节点,Scheduler会缓存一份最新的信息在本地。

      4) 最后,Scheduler在分发Pod到指定的Minion节点后,会把Pod相关的信息Binding写回API Server。

      4.2. Kubelet

      图3-3 Kubernetes详细构件

      根据上图3-3可知Kubelet是Kubernetes集群中每个Minion和Master API Server的连接点,Kubelet运行在每个Minion上,是Master API Server和Minion之间的桥梁,接收Master API Server分配给它的commands和work,与持久性键值存储etcd、file、server和http进行交互,读取配置信息。Kubelet的主要工作是管理Pod和容器的生命周期,其包括Docker Client、Root Directory、Pod Workers、Etcd Client、Cadvisor Client以及Health Checker组件,具体工作如下:

      1) 通过Worker给Pod异步运行特定的Action。

      2) 设置容器的环境变量。

      3) 给容器绑定Volume。

      4) 给容器绑定Port。

      5) 根据指定的Pod运行一个单一容器。

      6) 杀死容器。

      7) 给指定的Pod创建network 容器。

      8) 删除Pod的所有容器。

      9) 同步Pod的状态。

      10) 从Cadvisor获取container info、 pod info、root info、machine info。

      11) 检测Pod的容器健康状态信息。

      12) 在容器中运行命令。

      4.3. Proxy

      Proxy是为了解决外部网络能够访问跨机器集群中容器提供的应用服务而设计的,从上图3-3可知Proxy服务也运行在每个Minion上。Proxy提供TCP/UDP sockets的proxy,每创建一种Service,Proxy主要从etcd获取Services和Endpoints的配置信息,或者也可以从file获取,然后根据配置信息在Minion上启动一个Proxy的进程并监听相应的服务端口,当外部请求发生时,Proxy会根据Load Balancer将请求分发到后端正确的容器处理。

      5. 下篇主题

      下篇讲述在CentOS7上用Kubernetes来管理容器。

      6. 个人简介

      杨章显,现就职于Cisco,主要从事WebEx SaaS服务运维,系统性能分析等工作。特别关注云计算,自动化运维,部署等技术,尤其是Go、OpenvSwitch、Docker及其生态圈技术,如Kubernetes、Flocker等Docker相关开源项目。Email: yangzhangxian@gmail.com

      7. 参考资料

      1. https://github.com/GoogleCloudPlatform/kubernetes/tree/master/docs
      2. http://www.slideshare.net/rajdeep
      3. http://www.docker.com

      感谢郭蕾对本文的策划和审校。

      给InfoQ中文站投稿或者参与内容翻译工作,请邮件至editors@cn.infoq.com。也欢迎大家通过新浪微博(@InfoQ)或者腾讯微博(@InfoQ)关注我们,并与我们的编辑和其他读者朋友交流。

      QCon北京2016】近日,极客邦科技经常被热门大片儿《QCon的后裔》攻陷,不少Q迷们都直呼膜拜美国硅谷 Airbnb 美女工程师朱赟的“撩汉”技能,女神已放话要美美的来QCon啦!男神阿里巴巴资深总监庄卓然也将在此邂逅。4月21~23日,3天的约会,等你来见证。再不行动,神都帮不了你了。约约约
      posted @ 2016-04-23 11:14 小马歌 阅读(332) | 评论 (0)编辑 收藏
       

      Java Collections Cheat Sheet

      Java Collections cheat sheet

      Every Java program tends to have one thing in common. They’ll all use Java collections! They’re so fundamental, we could not even avoid thinking to omit them from out RebelLabs cheat sheet collection. This is a tough challenge, since there’s so much you need to know about the collections framework, the implementation details, correct use cases, how to choose the right collection type, what can they do and when to turn to the third party libraries as opposed to using the built in collections in the JDK.

      However, we never take the easy path, remember, we’ve tackled:

      Anyway, no topic as broad as Java collections framework can be fully explained in a single A4 page, but we’ve tried to incorporate the most essential information you will need to reference again and again. The corresponding explanations and details behind the decisions are right here, in this blogpost.

      GET ME A JAVA COLLECTIONS CHEAT SHEET!

      We’ve also created an interactive flow of questions that you can go through to determine which Java collection you should consider using for your data. Hit Enter to get going or Esc to go back a step:

      Continue reading to get a better understanding of the collection classes available in Java and when to use them.

      Collections interfaces and implementations in JDK

      You might think that the collections framework was always a part of the JDK. Surprise, surprise, that’s not true. Collections are one of the most fundamentals parts of the JDK, or any programming language for that matter, but it’s also one of the hardest aspects of programming to get right. On top of that it’s really challenging to make the collections library simple, coherent, and easy to use (I’m looking at you, Scala). So the Java collections library was introduced to Java in the Java 1.2 in 1998 and has stuck with us since then. Since backwards compatibility is one of the core values of the Java platform, collections haven’t changed a lot since then. However, recently in the Java 8 release, the language evolved enough to provide tools to enhance the collection interfaces without breaking backwards compatibility.

      All in all, Java collections is a comprehensive library that contains the utilities to tackle almost any container-like data structure you might need. Let’s dive into which types of collections are available and outline what makes them special and why you might want to use them.

      Short description of the most used collections

      There are two main approaches to choosing the collection type you need in your code. The first is simple enough, but not always the most effective:

      if it fits into an ArrayList, use ArrayList, otherwise you probably need a HashMap.

      The other includes having an understanding of the operations you will be needing from the collection, the performance you would expect from them and what kind of data you intend to put into the collection. While the advice above can take you quite far, it’s much better to understand the options available to help you make an informed decision.

      Collection interfaces you need to master

      There are 4 main types of Java collections available. Naturally, the implementations for each type can be found under the corresponding interfaces. Before we start digging into these, there are 2 main umbrella interfaces you want to know about: Iterable and Collection. Typically you rarely need to think about these, but it’s important to at least understand them. The iterable interface allows one to obtain an iterator and traverse the sequence of elements by calling the next() method. It also makes it possible to iterate through elements with the syntactic sugar of the “for-each” loop:

      for(E e: iterable)  

      The collection interface extends the Iterable and represents an iterable group of elements which can be added, removed, or checked for presence in the collection. However, the implementations usually implement the more specific interfaces that are designed with the collection implementation in mind. Here are the most used collection interfaces.

      List

      List is an ordered collection of elements. Some languages call that a sequence, or you can think of if it as an array of varying length. You can add elements to it, in the middle of it, access and replace the elements using an index.

      Not surprisingly, the list is one of the most common collections used. However, there’s almost no variety in the used implementations required. Yeah, in school you learned about the dreaded linked lists and how to implement them and how awesome it is to have access to the head and the tail of it. In practice, you are better using the ArrayList 99% of the time.

      The reason for that is quite simple, it consumes less memory, it is friendlier to the caches, and it is in general faster than the LinkedList from the JDK. In fact, you should try not to use a LinkedList.

      If you foresee that you’ll need to establish concurrent access to a list, you’ll need to worry about the synchronization yourself. Typically, that means that either you will use a CopyOnWriteArrayListwhich is thread-safe and immutable. If your data can be mutated by another thread, but the performance of the reading accesses is much more important. CopyOnWriteArrayList is your list of choice.

      Set

      A collection that contains no duplicate elements is a set. Just like the mathematical set, the interface denotes a collection that holds elements and basically answers a single question: is a given element contained in the set.

      Set doesn’t necessarily specify the order of elements when you iterate them, but all set implementations are created with the performance of the contains method in mind.

      There are two main Set implementations that you should know about: HashSet and TreeSet. HashSet hashes the elements and distributes them into buckets by the hash value. Tree set is backed by a balanced tree, which makes it ordered and navigable (so you can ask what for the previous and next elements by value). TreeSet operations will have worse complexity compared to the HashSet as a result, but bear in mind that the operations still take sublinear time of the set size, which means that for realistic values of the set sizes, it’s still quite a short time.

      However, typically use-cases for sets do not require navigating from element to element, so the HashSet is the goto implementation you’ll tend to use. And it’s a good one. Just remember to correctly implement the hashCode() and equals() methods for your elements.

      Map

      Perhaps the most used collection type of all time — an object that maps keys to values, an associative array, a table: the map. Maps are the most versatile collection type because the association of keys to values is really what computers are all about: a dictionary, mapping object properties to their values just like javascript does, mapping filenames to their contents and metadata, mapping usernames to password hashes, session attributes, product cart items, game high scores. Wherever you look you’ll find a use case for a map.

      Naturally there are different implementations that are tweaked to provide different performance trade-offs. But the default goto implementation of the map interface is undoubtedly the infamousHashMap.

      The Hashmap implementation depends on the Key objects implementing the hashCode() and equals() methods correctly, so always take care of these. In return the HashMap promises you the almost constant time performance which is as amazing as you can get out of any data structure and scales amazingly well.

      If you need the navigation between elements or you’d need to deal with unhashable element types, you can use a TreeMap. Using a balanced tree, just like the sets above, treemap scales appropriately and gives you a chance to iterate the contents in a sorted order.

      Now we don’t want to dive into all the API methods for maps. You can put and get values, as you’d expect. But we would love to point out one super amazing method that has been available to us since Java 8: computeIfAbsent.

      default V computeIfAbsent(K key,                           Function<? super K,? extends V> mappingFunction) 

      What computeIfAbsent does is give you an opportunity to specify how to obtain the value for the key, if it’s not in the collection yet. Essentially it checks if the given key is contained in the map. If it happens to be so, you get the corresponding value back. If not, the mapping function is executed and the resulting values is put into the map and returned to you.

      That’s more or less how caches work. Amazing, right?

      Queue

      queue is a collection designed to hold elements prior to processing, the elements wait in line to be consumed and can be added to the tail of the queue. Queues are those fundamental pieces of software that tie components together. When components are communicating in a system, there’s typically a queue of messages sitting between them.

      Queues are a special sort of collection, because are mostly operated using the Queue interface, rather than methods inherited from the Collection interface. To add an element to a queue, use:

      E e;  Queue<E> q = …  q.offer(e); // try to put the element into the queue, if the queue is full, do nothing q.poll(); // remove the head of the queue  q.peek(); // return the head of the queue without modifying it. 

      There are also corresponding methods that throw exceptions if operations don’t succeed. But all in all, there are three main operations one performs on a queue: enqueue, deque and peek.
      Note that you can get an iterator from the queue and process the elements similar to any other collection. It would even work for some implementations of the queue interface, but not for all.

      These are the typical classes that you might use that implement the queue interface:

      • ArrayDeque – a good general purpose battle-tested implementation of a queue. ArrayDeque is backed by a resizeable array that can grow to accommodate additional elements. Most ArrayDeque operations run in amortized constant time.
      • PriorityQueue – a queue of sorted elements. The sorting is achieved either naturally or determined by a provided comparator, but the head of the queue is always the minimal element. Note that a priority queue won’t change its order if you mutate the elements inside it. If you’re curious about the performance, the mutating operations run in logarithmic time of the queue size, peeking and such — in constant, and the search is linear.

      Collection utilities

      Now we’ve talked about all the most common collection types you’ll encounter, it’s time to dig into the other parts of the Java collections framework. There’s an amazing class, calledCollections, that provides handy utility methods that you should be aware of. First of all let’s talk about the collection wrappers. Any collection type can have additional requirements to its functionality, these orthogonal qualities are implemented with the wrappers. The original collection is wrapped into another one that delegates the element handling logic to it.

      The prime example of some functionality that is suitable for a wrapper is making a collection immutable. Indeed, you just need to ignore all the mutating operations like: put, insert, and so on. And all the non-mutating operations can be easily delegated to the actual collection implementations.

      The Collections class provides a set of methods that give you just that: unmodifiable collections:

      Java-collections-wrappers

      Another useful type of wrapper available is the synchronized collections wrapper. Indeed, most of the collection implementations do not implement the synchronization themselves. What would you do if you need to use HashSet concurrently from multiple threads? That’s right, you call the corresponding wrapper method and use the returned set.

      Collections.synchronizedSet(new HashSet<E>()); 

      It’s important to remember that the wrapper does not change the implementation of the underlying collection. That is if you obtain a reference to the wrapped hash set and use that concurrently there are no guarantees.

      Here’s a short list of the useful methods in the Collections class that typically are used more than others:

      • binarySearch – a fast way to find an element in a sorted List.
      • frequency – tells you how many times an element is encountered in a collection
      • min / max – returns the smallest / largest element of the collection
      • reverseOrder – provides you with a Comparator to sort elements in the descending order
      • singleton – wraps an object into the Set containing that object, the similar methods are available for the List and Map classes.

      Other libraries

      There are more questions that you can ask of your collection implementation and obviously there are more answers than just the stock implementations you can find in the JDK. Here are some libraries that we think are amazing and give you more functionality for the collection creation and manipulation and more collection types.

      • Guava – Google Core Libraries for Java 6+. Perhaps the default third party collection library for Java projects. Contains a magnitude of convenient methods for creating collection, like fluent builders, as well as advanced collection types.
      • Eclipse Collections – Features you want with the collections you need. Previously known as gs-collections, this library includes almost any collection you might need: primitive type collections, multimaps, bidirectional maps and so on.
      • Fastutil – Fast & compact type-specific collections for Java. Great default choice for collections of primitive types, like int or long. Also handles big collections with more than 2^31 elements.
      • JCTools – Java Concurrency Tools for the JVM. If you work on high throughput concurrent applications and need a way to increase your performance, check out JCTools. It contains lots of queues for all kinds of single / multiple producer / consumer environments and other useful classes.

      Sure, this is not a definitive list, but these are the essential libraries that you should know about. You’re might be using Guava in your project already; at least once you have thought about how awesome would it be to have the collections for primitives, and so on. So check these out!

      Improving JDK collections

      It’s not easy to change something as fundamental as the collections library in the JDK. However, the Java 8 release had implemented a major enhancements to the collections library and more than that, with the inclusion of the default methods it made it possible to evolve the collection interfaces without breaking all of the implementations.

      The progress doesn’t stop there. The upcoming Java 9 release contains the JEP 269: Convenience Factory Methods for Collections, the proposal to include the methods for easier collection creation. Finally, we would be able to specify the collections with a simpler syntax like:

        List.of(a, b, c); Set.of(d, e, f, g); Map.ofEntries(     entry(k1, v1),     entry(k2, v2),     entry(k3, v3)); 

      That would be really sweet.

      Conclusion

      We’ve talked a lot about Java collections and different implementations of the collection interfaces you can find in the java.util package. Sets, Lists and Maps are your friends and you’ll need to apply quite a bit of imagination to come up with a program that doesn’t need the collections API. We touched on the subject of collection wrappers and how to make collections immutable and synchronize them for concurrent access.

      Be sure to check out those libraries above with the alternative collections implementations. Hopefully you liked this post and the cheat sheet that tries to give you the information about Java collections on a single printable A4 sized piece of paper.

      SHOW ME JAVA COLLECTIONS CHEAT SHEET!

        posted @ 2016-04-22 14:46 小马歌 阅读(486) | 评论 (0)编辑 收藏
         
             摘要: 时间 2013-04-16 09:08:44  Script Ahead, Code Behind原文  http://rednaxelafx.iteye.com/blog/1847971主题 JVM(未经许可请勿转载。希望转载请与我联系。) (如果打开此页面时浏览器有点卡住的话请耐心等待片刻。大概是ItEye的代码高亮太耗时了...  阅读全文
        posted @ 2016-04-22 09:59 小马歌 阅读(578) | 评论 (0)编辑 收藏
         
        作者:曹旭东
        链接:https://www.zhihu.com/question/24401191/answer/37601385
        来源:知乎
        著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

        下文所使用的java版本信息

        $ java -version java version "1.8.0_20" Java(TM) SE Runtime Environment (build 1.8.0_20-b26) Java HotSpot(TM) 64-Bit Server VM (build 25.20-b23, mixed mode) 


        java中的注解是一种继承自接口`java.lang.annotation.Annotation`的特殊接口。参见下面的[JLS][2]和JDK文档。

        An annotation type declaration specifies a new annotation type,  a special kind of interface type. To distinguish an annotation  type declaration from a normal interface declaration, the keyword  interface is preceded by an at-sign (@).  ...  The direct superinterface of every annotation type is java.lang.annotation.Annotation. 

        /**  * The common interface extended by all annotation types.  Note that an  * interface that manually extends this one does <i>not</i> define  * an annotation type.  Also note that this interface does not itself  * define an annotation type.  *  * More information about annotation types can be found in section 9.6 of  * <cite>The Java&trade; Language Specification</cite>.  *  * The {@link java.lang.reflect.AnnotatedElement} interface discusses  * compatibility concerns when evolving an annotation type from being  * non-repeatable to being repeatable.  *  * @author  Josh Bloch  * @since   1.5  */ public interface Annotation {     ... } 

        下面看一下具体示例。

        定义一个注解:
        import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target;  /**  * Created by Administrator on 2015/1/18.  */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface TestAnnotation {      int count() default 1;  } 

        经过编译之后,注解`TestAnnotation`的字节码是这样的:
        Classfile /e:/workspace/intellij/SpringTest/target/classes/TestAnnotation.class   Last modified 2015-1-18; size 379 bytes   MD5 checksum 200dc3a75216b7a88ae17873d5dffd4f   Compiled from "TestAnnotation.java" public interface TestAnnotation extends java.lang.annotation.Annotation   minor version: 0   major version: 52   flags: ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION Constant pool:    #1 = Class              #14            // TestAnnotation    #2 = Class              #15            // java/lang/Object    #3 = Class              #16            // java/lang/annotation/Annotation    #4 = Utf8               SourceFile    #5 = Utf8               TestAnnotation.java    #6 = Utf8               RuntimeVisibleAnnotations    #7 = Utf8               Ljava/lang/annotation/Target;    #8 = Utf8               value    #9 = Utf8               Ljava/lang/annotation/ElementType;   #10 = Utf8               TYPE   #11 = Utf8               Ljava/lang/annotation/Retention;   #12 = Utf8               Ljava/lang/annotation/RetentionPolicy;   #13 = Utf8               RUNTIME   #14 = Utf8               TestAnnotation   #15 = Utf8               java/lang/Object   #16 = Utf8               java/lang/annotation/Annotation { } SourceFile: "TestAnnotation.java" RuntimeVisibleAnnotations:   0: #7(#8=[e#9.#10])   1: #11(#8=e#12.#13) 

        从反编译后的信息中可以看出,注解就是一个继承自`java.lang.annotation.Annotation`的接口。

        那么接口怎么能够设置属性呢?简单来说就是java通过动态代理的方式为你生成了一个实现了"接口"`TestAnnotation`的实例(对于当前的实体来说,例如类、方法、属性域等,这个代理对象是单例的),然后对该代理实例的属性赋值,这样就可以在程序运行时(如果将注解设置为运行时可见的话)通过反射获取到注解的配置信息。

        具体来说是怎么实现的呢?

        写一个使用该注解的类:
        import java.io.IOException;  /**  * Created by Administrator on 2015/1/18.  */ @TestAnnotation(count = 0x7fffffff) public class TestMain {      public static void main(String[] args) throws InterruptedException, NoSuchFieldException, IllegalAccessException, IOException {         TestAnnotation annotation = TestMain.class.getAnnotation(TestAnnotation.class);         System.out.println(annotation.count());         System.in.read();     }  } 

        反编译一下这段代码:
        Classfile /e:/workspace/intellij/SpringTest/target/classes/TestMain.class   Last modified 2015-1-20; size 1006 bytes   MD5 checksum a2d5367ea568240f078d5fb1de917550   Compiled from "TestMain.java" public class TestMain   minor version: 0   major version: 52   flags: ACC_PUBLIC, ACC_SUPER Constant pool:    #1 = Methodref          #10.#34        // java/lang/Object."<init>":()V    #2 = Class              #35            // TestMain    #3 = Class              #36            // TestAnnotation    #4 = Methodref          #37.#38        // java/lang/Class.getAnnotation:(Ljava/lang/Class;)Ljava/lang/annotation/Annotation;    #5 = Fieldref           #39.#40        // java/lang/System.out:Ljava/io/PrintStream;    #6 = InterfaceMethodref #3.#41         // TestAnnotation.count:()I    #7 = Methodref          #42.#43        // java/io/PrintStream.println:(I)V    #8 = Fieldref           #39.#44        // java/lang/System.in:Ljava/io/InputStream;    #9 = Methodref          #45.#46        // java/io/InputStream.read:()I   #10 = Class              #47            // java/lang/Object   #11 = Utf8               <init>   #12 = Utf8               ()V   #13 = Utf8               Code   #14 = Utf8               LineNumberTable   #15 = Utf8               LocalVariableTable   #16 = Utf8               this   #17 = Utf8               LTestMain;   #18 = Utf8               main   #19 = Utf8               ([Ljava/lang/String;)V   #20 = Utf8               args   #21 = Utf8               [Ljava/lang/String;   #22 = Utf8               annotation   #23 = Utf8               LTestAnnotation;   #24 = Utf8               Exceptions   #25 = Class              #48            // java/lang/InterruptedException   #26 = Class              #49            // java/lang/NoSuchFieldException   #27 = Class              #50            // java/lang/IllegalAccessException   #28 = Class              #51            // java/io/IOException   #29 = Utf8               SourceFile   #30 = Utf8               TestMain.java   #31 = Utf8               RuntimeVisibleAnnotations   #32 = Utf8               count   #33 = Integer            2147483647   #34 = NameAndType        #11:#12        // "<init>":()V   #35 = Utf8               TestMain   #36 = Utf8               TestAnnotation   #37 = Class              #52            // java/lang/Class   #38 = NameAndType        #53:#54        // getAnnotation:(Ljava/lang/Class;)Ljava/lang/annotation/Annotation;   #39 = Class              #55            // java/lang/System   #40 = NameAndType        #56:#57        // out:Ljava/io/PrintStream;   #41 = NameAndType        #32:#58        // count:()I   #42 = Class              #59            // java/io/PrintStream   #43 = NameAndType        #60:#61        // println:(I)V   #44 = NameAndType        #62:#63        // in:Ljava/io/InputStream;   #45 = Class              #64            // java/io/InputStream   #46 = NameAndType        #65:#58        // read:()I   #47 = Utf8               java/lang/Object   #48 = Utf8               java/lang/InterruptedException   #49 = Utf8               java/lang/NoSuchFieldException   #50 = Utf8               java/lang/IllegalAccessException   #51 = Utf8               java/io/IOException   #52 = Utf8               java/lang/Class   #53 = Utf8               getAnnotation   #54 = Utf8               (Ljava/lang/Class;)Ljava/lang/annotation/Annotation;   #55 = Utf8               java/lang/System   #56 = Utf8               out   #57 = Utf8               Ljava/io/PrintStream;   #58 = Utf8               ()I   #59 = Utf8               java/io/PrintStream   #60 = Utf8               println   #61 = Utf8               (I)V   #62 = Utf8               in   #63 = Utf8               Ljava/io/InputStream;   #64 = Utf8               java/io/InputStream   #65 = Utf8               read {   public TestMain();     descriptor: ()V     flags: ACC_PUBLIC     Code:       stack=1, locals=1, args_size=1          0: aload_0          1: invokespecial #1                  // Method java/lang/Object."<init>":()V          4: return       LineNumberTable:         line 7: 0       LocalVariableTable:         Start  Length  Slot  Name   Signature             0       5     0  this   LTestMain;    public static void main(java.lang.String[]) throws java.lang.InterruptedException, java.lang.NoSuchFieldException, java.lang.IllegalAccessException, java.io.IOException;     descriptor: ([Ljava/lang/String;)V     flags: ACC_PUBLIC, ACC_STATIC     Code:       stack=2, locals=2, args_size=1          0: ldc           #2                  // class TestMain          2: ldc           #3                  // class TestAnnotation          4: invokevirtual #4                  // Method java/lang/Class.getAnnotation:(Ljava/lang/Class;)Ljava/lang/annotation/Annotation;          7: checkcast     #3                  // class TestAnnotation         10: astore_1         11: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;         14: aload_1         15: invokeinterface #6,  1            // InterfaceMethod TestAnnotation.count:()I         20: invokevirtual #7                  // Method java/io/PrintStream.println:(I)V         23: getstatic     #8                  // Field java/lang/System.in:Ljava/io/InputStream;         26: invokevirtual #9                  // Method java/io/InputStream.read:()I         29: pop         30: return       LineNumberTable:         line 10: 0         line 11: 11         line 12: 23         line 13: 30       LocalVariableTable:         Start  Length  Slot  Name   Signature             0      31     0  args   [Ljava/lang/String;            11      20     1 annotation   LTestAnnotation;     Exceptions:       throws java.lang.InterruptedException, java.lang.NoSuchFieldException, java.lang.IllegalAccessException, java.io.IOException } SourceFile: "TestMain.java" RuntimeVisibleAnnotations:   0: #23(#32=I#33) 

        最后一行的代码说明,注解`TestAnnotation`的属性设置是在编译时就确定了的。(对属性的说明在[这里][1])。

        然后,运行上面的程序,通过CLHSDB在eden区找到注解实例,

        hsdb> scanoops 0x00000000e1b80000 0x00000000e3300000 TestAnnotation 0x00000000e1d6c360 com/sun/proxy/$Proxy1 

        类型`com/sun/proxy/$Proxy1`是jdk动态代理生成对象时的默认类型,其中`com.sun.proxy`是默认的包名,定义于`ReflectUtil`类的`PROXY_PACKAGE`字段中。代理类名`$PROXY1`包含两部分,其中前缀`$PROXY`是jdk种默认的代理类类名前缀(参见`java.lang.reflect.Proxy`类的javadoc),后的1是自增的结果。

        下面看一下这个代理类的内容。运行java程序时添加参数`-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true`可以将转储出jdk动态代理类的class文件。若是项目较大或是使用了各种框架的话,慎用此参数。

        Classfile /e:/workspace/intellij/SpringTest/target/classes/com/sun/proxy/$Proxy1.class   Last modified 2015-1-19; size 2062 bytes   MD5 checksum 7321e44402258ba9e061275e313c5c9f public final class com.sun.proxy.$Proxy1 extends java.lang.reflect.Proxy implements TestAnnotation   minor version: 0   major version: 49   flags: ACC_PUBLIC, ACC_FINAL ... 

        太长了,只截取一部分。从中可以看到,这个代理类实现了继承自`java.lang.reflect.Proxy`类,又实现了“接口”TestAnnotation。

        接下来查看一下代理对象的内容:

        hsdb> inspect 0x00000000e1d6c360 instance of Oop for com/sun/proxy/$Proxy1 @ 0x00000000e1d6c360 @ 0x00000000e1d6c360 (size = 16) _mark: 1 _metadata._compressed_klass: InstanceKlass for com/sun/proxy/$Proxy1 h: Oop for sun/reflect/annotation/AnnotationInvocationHandler @ 0x00000000e1ce7670 Oop for sun/reflect/annotation/Annota tionInvocationHandler @ 0x00000000e1ce7670 
        其中,0xe1ce74e0是成员变量h的地址(h是定义在`java.lang.reflect.Proxy`类中的),通过查看类`AnnotationInvocationHandler`的源码可以知道注解的代理实例的值就存储在它的成员变量`memberValues`中,然后继续向下挖就好了:

        hsdb> inspect 0x00000000e1ce7670 instance of Oop for sun/reflect/annotation/AnnotationInvocationHandler @ 0x00000000e1ce7670 @ 0x00000000e1ce7670 (size =  24) _mark: 1 _metadata._compressed_klass: InstanceKlass for sun/reflect/annotation/AnnotationInvocationHandler type: Oop for java/lang/Class @ 0x00000000e1ccc5f8 Oop for java/lang/Class @ 0x00000000e1ccc5f8 memberValues: Oop for java/util/LinkedHashMap @ 0x00000000e1ce7548 Oop for java/util/LinkedHashMap @ 0x00000000e1ce7548 memberMethods: null null hsdb> inspect 0x00000000e1ce7548 instance of Oop for java/util/LinkedHashMap @ 0x00000000e1ce7548 @ 0x00000000e1ce7548 (size = 56) _mark: 1 _metadata._compressed_klass: InstanceKlass for java/util/LinkedHashMap keySet: null null values: null null table: ObjArray @ 0x00000000e1ce75b8 Oop for [Ljava/util/HashMap$Node; @ 0x00000000e1ce75b8 entrySet: null null size: 1 modCount: 1 threshold: 1 loadFactor: 0.75 head: Oop for java/util/LinkedHashMap$Entry @ 0x00000000e1ce75d0 Oop for java/util/LinkedHashMap$Entry @ 0x00000000e1ce7 5d0 tail: Oop for java/util/LinkedHashMap$Entry @ 0x00000000e1ce75d0 Oop for java/util/LinkedHashMap$Entry @ 0x00000000e1ce75d0 accessOrder: false hsdb> inspect 0x00000000e1ce75d0 instance of Oop for java/util/LinkedHashMap$Entry @ 0x00000000e1ce75d0 @ 0x00000000e1ce75d0 (size = 40) _mark: 1 _metadata._compressed_klass: InstanceKlass for java/util/LinkedHashMap$Entry hash: 94852264 key: "count" @ 0x00000000e1bd7c90 Oop for java/lang/String @ 0x00000000e1bd7c90 value: Oop for java/lang/Integer @ 0x00000000e1ce7630 Oop for java/lang/Integer @ 0x00000000e1ce7630 next: null null before: null null after: null null hsdb> inspect 0x00000000e1ce7630 instance of Oop for java/lang/Integer @ 0x00000000e1ce7630 @ 0x00000000e1ce7630 (size = 16) _mark: 1 _metadata._compressed_klass: InstanceKlass for java/lang/Integer value: 2147483647 

        最后可以看到,key=“count”, value=Integer(2147483647 = 0x7fffffff),正是在TestMain中设置的值.

        嗯,就这样吧。


        [1]: Chapter 4. The class File Format
        [2]: Chapter 9. Interfaces
        posted @ 2016-04-22 09:58 小马歌 阅读(344) | 评论 (0)编辑 收藏
         
             摘要: from:http://my.oschina.net/pangyangyang/blog/361753ElasticSearch的安装http://www.elasticsearch.org/下载最新的ElastiSearch版本。解压下载文件。cd到${esroot}/bin/,执行elasticsearch启动。使用curl -XPOST localhost:9200/_shutdown关闭E...  阅读全文
        posted @ 2016-04-15 14:03 小马歌 阅读(478) | 评论 (0)编辑 收藏
         

        我安装的MySQL版本是5.7.10 。 官网最新版本下载地址是:MySQL下载地址

        1、选择一个DMG 后  下载->安装

        安装完后会提示一句话 如下

        A temporary password is generated for root@localhost: dwstkti5xJ<5

        If you lose this password, please consult the section How to Reset the Root Password in the MySQL reference manual.

        把root@localhost: dwstkti5xJ<5 复制到一个地方 后面要用。

        mysql 默认是安装到了  usr/local/mysql  下面

        2、安装完后 在偏好设置里启动MySQL服务,点击"Start MySQL Server"



        3、添加MySQL的快捷 命令 方式 。 为什么要弄这个? 为了方便的直接在终端默认打开的目录下使用mysql xxx 命令 而不用麻烦的进入mysql的安装目录下 进行操作。 有如下两个:

        alias mysql=/usr/local/mysql/bin/mysql

        alias mysqladmin=/usr/local/mysql/bin/mysqladmin

        4、重置(修改)MySQL的root密码。为什么要重置呢?MySQL安装完后会给一个临时的密码 也就是上文中的 dwstkti5xJ<5   ,如果你不修改这个密码 使用临时密码登陆mysql 后 各种命令是不能用的,会一直提示你  需要重置密码。

        mysqladmin -u root -p password hahaha      #hahaha是我要修改为的密码  回车后 输入临时密码dwstkti5xJ<5   就算是修改成功了

        5、使用 步骤4里设置的新密码来登陆。 为什么要登陆?登陆后才能在mysql里创建数据库和各种表 等等。

        mysql -u root -p

        输入新密码 后 回车

        6、设置配置文件。为什么要弄配置文件 ?  其实到第五步 就算是安装完成了,但是正常情况下mysql需要一个配置文件,里面存放了 许多属性 比如字符编码啦  连接数啦 什么的。这个配置文件默认是在 /usr/local/mysql/support-files/  下面  叫做 my-default.cnf 。 mysql启动时默认会从下面四个位置寻找my.cnf 然后使用 , 大家都使用第一种情况,所以咱也使用。

         /etc/my.cnf      /etc/mysql/my.cnf      /usr/local/mysql/etc/my.cnf       ~/.my.cnf

        所以现在要做的就是 把my-default.cnf 复制到 /etc 的下面,并且修改名字为my.cnf 。 命令如下:

        sudo cp -rv /usr/local/mysql/support-files/my-default.cnf  /etc  #复制 配置文件 到etc下面

        sudo mv my-default.cnf my.cnf    #修改名字为my.cnf

        配置文件就算是弄好了  要想使之生效 必须重启mysql ,还是到偏好设置里 先停止 再开启。

        7、修改配置文件my.cnf 。为什么要修改呢?可以不改  本步骤只是根据一个例子 说明如何修改配置文件。本步骤修改的是字符的编码。  使用新密码登陆mysql后 

        mysql> show variables like '%char%';    #回车后  会 看到如下文字

        | Variable_name            | Value                                                  |

        | character_set_client    | utf8                                                  |

        | character_set_connection | utf8                                                  |

        | character_set_database  | latin1                                                |

        | character_set_filesystem | binary                                                |

        | character_set_results    | utf8                                                  |

        | character_set_server    | latin1                                                |

        | character_set_system    | utf8                                                  |

        | character_sets_dir      | /usr/local/mysql-5.7.10-osx10.9-x86_64/share/charsets/

        可以看到character_set_server和character_set_database的字符编码是latin1  我现在就是要把它改为utf8格式的。

        sudo chmod a+w /etc/my.cnf   #修改权限为可写  因为复制过来的这个my-default.cnf文件(现在改名为my.cnf了) 是只读权限的 要想修改里面的内容当然要改为可写权限的了。

        vi  /etc/my.cnf  #进去后开始修改  找到 [mysqld]  这个标示后在它的下面粘贴上需要配置的参数  最终效果如下


        修改完后 保存退出。

        sudo chmod a-w /etc/my.cnf   #取消my.cnf 的可写权限  因为如果不取消这个配置文件的可写权限  mysql启动时就不理你修改好的这个配置文件 就是给你忽略掉了,意思就是说 这个配置文件必须是只读的。

        修改完后再在偏好设置里重启一下mysql。

        各种字符编码的意义如下 (网上抄的)

        character_set_client  为客户端使用的字符集;

        character_set_connection 为连接数据库的字符集设置类型 如果程序没有指明连接数据库使用的字符集类型 则按照服务器端默认的字符集设置。

        character_set_database  为数据库服务器中某个库使用的字符集设定,如果建库时没有指明 将使用服务器安装时指定的字符集设置。

        character_set_results  为数据库 给客户端返回时使用的字符集设定 如果没指明 使用服务端默认的字符集。

        character_set_server  为服务器安装时指定的默认字符集设定

        character_set_system    为数据库系统使用的字符集设定


        结束。



        文/大象飞(简书作者)
        原文链接:http://www.jianshu.com/p/65ee08a4a0d0
        著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。
        posted @ 2016-04-15 10:14 小马歌 阅读(324) | 评论 (0)编辑 收藏
         
             摘要: from:http://blog.csdn.net/smartsmile2012/article/details/17316351版权声明:本文为博主原创文章,未经博主允许不得转载。[html] view plain copy print?<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML&n...  阅读全文
        posted @ 2016-04-13 13:47 小马歌 阅读(333) | 评论 (0)编辑 收藏
         
             摘要: from:http://blog.csdn.net/isea533/article/details/50412212Spring Boot 静态资源处理Spring Boot 默认的处理方式就已经足够了,默认情况下Spring Boot 使用WebMvcAutoConfiguration中配置的各种属性。建议使用Spring Boot 默认处理方式,需要自己配置的地方可以通过配置文件修改。但是如果...  阅读全文
        posted @ 2016-04-12 14:21 小马歌 阅读(1027) | 评论 (0)编辑 收藏
         

        from:http://zeroturnaround.com/rebellabs/5-unexpectedly-useful-command-line-tools-you-might-overlook/

        My previous RebelLabs post showed us that there still is a nice amount of interest among the developers in neat command line tools. This is great news as it means there are still lots of geeks reaching for their maximum l33t potential! This post will, as the title suggests, jump into 5 more tools that will hopefully tickle your fancy.

        Whilst it is hard to find a set of tools relevant for every different command line professional or enthusiast, I can still wholly recommend glancing over these tools briefly. You never know when you’ll get stuck in a terminal only environment or will need to impress a friend with your exceptional command line skills. So without further ado, here are 5 more command line tools you should consider using!

        z

        1. Install it once
        2. Forget it’s even there
        3. Profit from its productivity: z.

        OK, but what’s it all about? Well, z allows you to quickly jump from folder to folder, without having to bother writing the full absolute or relative paths. To achieve this, it simply records all the folders you visit, and then ranks them based on a combination of the frequency and time of last use. Now all you need to do is type z part/of/path and hit enter. With this, z will automagically guide you to the highest ranking match.

        To install z, download z.sh and source it in ~/.zshrc or ~/.bashrc, if you still haven’t made the inevitable jump to ZSH. Now go about your regular flow, cd-ing all over the place, and you are done!

        Alternatively, if you use the oh-my-zsh framework, simply add it in the plugins listing of ~/.zshrc.

         $ vim ~/.zshrc  plugins=(brew git mercurial mvn osx sbt scala vi-mode z) 

        By default, z stores its data in ~/.z, below are my contents after I cleared them and moved around a bit. After printing the data, you can see I moved to 2 different folders by specifying only a small part of the path, irrespective of the current folder. Take note that the directory name was matched mid-word in a case insensitive way — very handy!

        All in all I highly recommend this great tool to anyone that spends any amount of time at the command line, whatever your background, interest or use case may be.

        z

        youtube-dl

        The second tool of this post is the excellent youtube-dl. A cross-platform tool allowing you to, unsuprisingly, download Youtube videos. The easiest way to install is either via Homebrew orpip as shown below. Other download options can be found via the link above.

         $ brew install youtube-dl  $ sudo pip install --upgrade youtube_dl 

        At times, for whatever reason, you may want to download a presentation for archiving’s purposes. Or better yet, imagine you spend the weekend in the Estonian countryside, devoid of internet, yet you really wanted to see the latest Virtual JUG sessions. A simple youtube-dl https://youtu.be/PQPvZkA-6bg suffices at this point.

        youtube-dl

        Other nice things to take note of:

        • You are not limited to downloading from Youtube only, Vimeo for example is also supported.
        • Simply point to a Youtube playlist and youtube-dl will be smart enough to automatically download the full list.
        • As shown in the example image, youtube-dl will happily follow shortened URLs for you.
        • By default the highest quality video will be downloaded, however a simple youtube-dl -f $FORMAT $LINK will override this. You find available formats with the -F flag.
        • A veritable host of additional options are nicely documented and easily accessible via man youtube-dl.

        shellcheck

        I will assume that anyone spending significant some time at the command line will want to automate tasks using shell scripts. Bash itself is renowned for its many pitfalls, and even advanced scripters will from time to time bump into something unexpected. Unfortunately for us, a shell script has no undo button, and “unexpected” results may well be synonymous with “catastrophic” results.

        Long intro short: we have a valuable tool on our side to guard ourselves against exactly this,shellcheck! In essence, it is a static analyser that will tell you where your script goes wrong.

        Install shellcheck via your package manager of choice: Homebrew, Pacman, APT; build it from source, or run it inside your browser.

         $ brew install shellcheck  # pacman -S shellcheck  # apt-get install shellcheck 

        This list may be of particular interest to you as it describes code samples that shellcheck can protect you against. To run, simply execute shellcheck my-script, it will read the shebang (#!) directive to decide whether to analyse as shbash or ksh.

        shellcheck

        As shown in the example output above, there is one syntax error, it’s marked in red: spaces surrounding the assignment. However, shellcheck goes beyond that. In yellow markings is a warning of a potentially catastrophic event: what if toDelete is ever empty? Granted, the example is somewhat contrived, without –no-preserve-root there will be no damage should the statement resolve to /, but my point still stands! Finally, in green you will find general warnings on potential future mishaps that are best not to be ignored.

        As a closing remark on shellcheck: use the neat Syntastic Vim plugin to integrate the tool inside Vim itself, configuring when it should run, how it should behave, etc. The same script that generated the ouput above now looks like the following, from our favorite text editor.

        shellcheck-vim

        multitail

        For the sys-admins and dev-opses amongst us: stop using tail -f, and start using, multitail, tail on steroids! This ultimate log-viewer allows you to do a couple of really cool things that make it worth mentioning. Alternatively you could always either use tmux or screen to get in-shell multiplexing, or just use a modern terminal emulator allowing you to do the splitting like that, such as iTerm2 or Terminator, however in these cases you may miss some of the features below.

        • Show multiple windows at the same time, in the same shell using ncurses.
        • Merge multiple logs in the same window, for example the stdout and the stderr log of the same application.
        • Perform filtering dynamically via in-menu editable regexes.
        • Use the predefined color highlighters to make the logs more legible, or define your own, again with regular expressions.

        You know the drill how to install:

         $ brew install multitail  # pacman -S multitail  # apt-get install multitail 

        For an actual example, here is a screenshot of a window where I’m developing JRebel by attaching it to Tomcat, running the infamous petclinic project. At that time I needed both Tomcat’s own output and the JRebel one. Take note of the -CS flag, signifying “use this color scheme for all subsequent files”, followed by the name of the scheme.
        As both logs color nicely with the Apache rules, they are set, finally followed by the actual paths of the logfiles.

         $ multitail -CS apache "$TOMCAT_HOME/logs/catalina.out" "$HOME/.jrebel/jrebel.log" 

        multitail

        tree

        Our final tool in this post is the essential tree utility. Tree prints a nice, structured, tree-view of your directories, allowing you to instantly get an idea about the structure of your data, without having to lscd or z all over the place. If it is not yet pre-installed in your favourite *NIX, then grab it via your favourite package manager.

         $ brew install tree  # pacman -S tree  # apt-get install tree 

        In its most basic form, you simply type $ tree to print the current folder’s structure.

        tree

        Personally, I prefer to spice it up just a little bit, adding some flags to print human readable filesizes, hidden files, and a nice total sizecount: $ tree -ah --du.

        tree-fancy

        And that is about all I have to tell you about tree. It’s really convenient to grep its output, it gives you an awesome representation of the filesystem, and I’m sure you’ll love it from the first moment.

        That concludes my list of command line tools that you won’t think will change you live, but sure enough after some time you won’t imagine yourself not using them. In fact, you’ll likely curse every time you use a command-line-muggle’s computer without these great tools installed. What are the hidden gems of your command-line-fu? Share your favorite command line utilities in the comments below, I really would like to learn new tricks.

        posted @ 2016-04-06 15:08 小马歌 阅读(271) | 评论 (0)编辑 收藏
         

        from:http://zeroturnaround.com/rebellabs/5-command-line-tools-you-should-be-using/

        Working on the command line will make you more productive, even on Windows!

        There’s an age-old discussion between the usability and friendliness of GUI programs, versus the simplicity and productivity of CLI ones. But this is not a holy war I intend to trigger or fuel. In the past, RebelLabs has discussed built-in JDK tools and received amazing feedback, so I feel an urge to share more non-JDK command line tools which I simply couldn’t live without.

        I do firmly believe every developer who’s worth their salt should have at least some notion of how to work with the command line, if only because some tools only exist in CLI variants. Plus, because geek++!

        All other nuances that people pour words over, like the choice of operating system (OSX of course, they have beautiful aluminum cases), your favorite shell (really it should be ZSH), or the preference of Vim over Emacs (unless you have more fingers than usual) are much less relevant. OK, that was a little flamewar-like, but I promise that will be the last of it!

        So my advice would be that you should learn how to use tools at the command line, as it will have a positive impact on your happiness and productivity at least for half a century!

        Anyway, in this post I want to share with you four-five lesser-known yet pretty awesome command line gems. As an added bonus I will also advise the proper way to use shell under Windows, which is a pretty valuable bit of knowledge in itself.

        The reason I wanted to write this post in the first place is because I really enjoy using these tools myself, and want to learn about other command line tools that I don’t yet know about. So please, awesome reader, leave me a comment with your favourite CLI tools — that’d be grand! Now, assuming we all have a nice, workable shell, let’s go over some neat command line tools that are worth hearing about.

        0. HTTPie

         

        The first on my list is a tool called HTTPie. Fear not, this tool has nothing to do with Internet Explorer, fortunately. In essence HTTPie is a cURL wrapper, the utility that performs HTTP requests from the command line. HTTPie adds nice features like auto-formatting and intelligent colour highlighting to the output making it much more readable and useful to the user. Additionally, it takes a very human-centric approach to its execution, not asking you to remember obscure flags and options. To perform an HTTP GET, you simply run http, to post you http POST, what can be easier or more beautiful?

        sample httpie output

        Almost all command line tools are conveniently packaged for installation. HTTPie is no exception, to install it, run the following command.

        • On OSX use homebrew, the best package manager to be found on OSX: brew install httpie
        • All other platforms, using Python’s pip: pip install --upgrade httpie

        I personally use HTTPie a lot when developing a REST API, as it allows me to very simply query the API, returning nicely structured, legible data. Without doubt this tool saves me serious work and frustration. Luckily the usage does not stop at just REST APIs. Generally speaking, all interactions over HTTP, whether it’s inputting or outputting data, can be done in a very human-readable format.

        I’d encourage you to take a look at the website, spend the 10 seconds it takes to install and give it a go yourself. Try to get the source of any website and be amazed by the output.

        How unstoppable you can be with proper tools

        Protip: Combine the HTTPie greatness with jq for command line JSON manipulation or pup for HTML parsing and you’ll be unstoppable!

        1. Icdiff

         

        At ZeroTurnaround I am blessed to work with Mercurial, a very nice and easy to use VCS. On OSX the excellent GUI program SourceTree makes working with Mercurial an absolute breeze, even with the more complex stuff. Unfortunately I like to keep the number of programs/tabs/windows I have open to an absolute minimum. Since I always have a terminal window opened it makes sense to use the CLI.

        All was fine and well apart from one single pitfall in my setup. This was a feature I could barely go without: side-by-side diffs. Introducing icdiff. Of all the tools I use each day, this is the one I most appreciate. Let’s take a look at a screenshot:

        example of icdiff at work

        By itself, icdiff is an intelligent Python script, smart at detecting which of the differences are modifications, additions or deletions. The excellent colour highlighting in the tool makes it easy to distinguish between the three types of differences mentioned.

        To get going with icdiff, do the following:

        • Via homebrew once again: brew install icdiff
        • Manually grab the Python script from the site above and put it in your PATH

        When you couple icdiff with a VCS such as Mercurial, you’ll see it really shine. To fully integrate it, you’ll need to complete two more configuration steps, already documented here. The gist of the instructions is to first add a wrapping script that allows the one-by-one file diff of icdiff to operate on entire directories. Secondly you need to config your VCS to actually use icdiff. The link above shows the details of configuring it for Mercurial, but porting this to Git shouldn’t be so hard.

        2. Pandoc

         

        In the spirit of “practice what you preach” I set out to write this entire blogpost via a CLI. Most of the work was done using MacVim, in iTerm2 on OSX. All of the text was written and formatted using standard MarkDown syntax. The only issue to arise here is that it’s pretty difficult sometimes to accurately guess how your eventual text will come out.

        This is where the next tool comes in: Pandoc. A program so powerful and versatile it’s a wonder it was GPL’d in the first place. Let’s take a look at how we might use it.

        pandoc -f markdown -t html blogpost.md &gt; blogpost.html 

        Think of a markup format, any markup format. The chances are, Pandoc can convert it from one format to any other. For example, I’m writing this blogpost in Vim and use Pandoc to convert it from MarkDown into HTML, to actually see the final result. It’s a nice thing, needing only my terminal and a browser, rather than being tied to a particular online platform, fully standalone and offline.

        Don’t let yourself be limited by simple formats like MarkDown though, give it some docx files, or perhaps some LaTeX. Export into PDFepub, let it handle and format your citations. The possibilities are endless.

        Once again brew install pandoc does the trick. Did I mention I really like Homebrew? Maybe that should have made my tool list! Anyway, you get the gist of what that does!

        3. Moreutils

         

        The next tool in this post is actually a collection of nifty tools that didn’t make it into coreutils:Moreutils. It should be obtainable under moreutils in about any distro you can think of. OSX users can get all this goodness by brewing it like I did throughout this post:

        brew install moreutils 

        Here are a list of the included programs with short descriptions:

        • chronic: runs a command quietly unless it fails
        • combine: combine the lines in two files using boolean operations
        • ifdata: get network interface info without parsing ifconfig output
        • ifne: run a program if the standard input is not empty
        • isutf8: check if a file or standard input is utf-8
        • lckdo: execute a program with a lock held
        • mispipe: pipe two commands, returning the exit status of the first
        • parallel: run multiple jobs at once
        • pee: tee standard input to pipes
        • sponge: soak up standard input and write to a file
        • ts: timestamp standard input
        • vidir: edit a directory in your text editor
        • vipe: insert a text editor into a pipe
        • zrun: automatically uncompress arguments to command

        As the maintainer hints himself sponge is perhaps the most useful tool, in that you can easily sponge up standard input into a file. However, it is not difficult to see the advantages of some of the other commands such as chronicparallel and pee.

        My personal favourite though, and the ultimate reason to include this collection, is without doubtvipe.

        You can literally intercept your data as it moves from command to command through the pipe. Even though this is not a useful tool in your scripts, it can be extremely helpful when running commands interactively. Instead of giving you a useful example I will leave you with a modified fortune!

        sample vipe command

        4. Babun

         

        These days the Windows OS comes packaged with two different shells: its classic command line, and PowerShell. Let’s completely ignore those and have a look at the proper way or running command line tools under Windows: Babun! The reason this project is amazingly awesome is because it actually brings all the goodness of the \*NIX command line into Windows in a completely pre-configured no-nonsense manner.

        Moreover, its default shell is my beloved ZSH, though it can very easily be changed to use Bash, if that’s your cup of tea. With ZSH it also packages the highly popular oh-my-zsh framework, which combines all the benefits of ZSH with no config whatsoever thanks to some very sane defaults and an impressive plugin system.

        By default Babun is loaded with more applications than any sane developer may ever need, and is thus a rather solid 728 MBs(!) when expanded. In return you get essentials like Vim pre-installed and ready to go!

        screenshot of babun termina;

        Under the hood Babun is basically a fancy wrapper around Cygwin. If you already have a Cygwin install you can seamlessly re-use that one. Otherwise it will default to its own packaged Cygwin binaries, and supply you with access to those.

        Some more points of interest are that Babun provides its own package manager, which again wraps around Cygwin’s, and an update mechanism both for itself and for oh-my-zsh. The best thing is that no actual installation is required, nor is the usual requirement of admin rights necessary, so for those people on a locked down PC this may be just the thing they need!


        I hope this small selection of tools gave you at least one new cool toy to play with. As for me, it seems it is time to look at command line browsers before writing a following blogpost, to fully ditch the world of the GUI!

        By all means fire up any comments or suggestions that you have, and let’s get some tool-sharing going on. If you just want to chat just ping RebelLabs on Twitter: @ZeroTurnaround, they are pretty chatty, but great smart people.

        posted @ 2016-04-06 14:49 小马歌 阅读(277) | 评论 (0)编辑 收藏
         
             摘要: 本文由 ImportNew - hejiani 翻译自 java-performance。欢迎加入翻译小组。转载请见文末要求。JMH是新的microbenchmark(微基准测试)框架(2013年首次发布)。与其他众多框架相比它的特色优势在于,它是由Oracle实现JIT的相同人员开发的。特别是我想提一下Aleksey Shipilev和他优秀的博...  阅读全文
        posted @ 2016-04-06 14:19 小马歌 阅读(413) | 评论 (0)编辑 收藏
         
             摘要: 花了一下午时间,总算全部搞定。时间主要都花费在下载jar包上,虽然开了VPN还是下载慢,没有VPN的话,真心要奔溃的。这期间有太多坑了,所以写这篇文章, 一是记录下,二是方便大家查阅。本人的系统环境为什么要说系统环境呢?不同的环境有不同的设置方法,但看了这篇文章后,可以举一反三,在其他环境设置也没什么问题。OS: OS X EI Capitan 10.11IDE: IntelliJ IDEA 14...  阅读全文
        posted @ 2016-04-06 10:11 小马歌 阅读(1958) | 评论 (0)编辑 收藏