集团客户体验事业群高级技术专家邱小侠,阿里花名肥侠。 2014年加入阿里巴巴,现在负责客户体验驱动及创新中心有关商家业务的开发工作。负责开发了商家维权中心和商家品控平台,同时也负责集团在线工作台和知识库的研发工作。
概述 关于微服务的介绍,可以参考微服务那点事。 微服务是最近非常火热的新概念,大家都在追,也都觉得很对,但是似乎没有很充足的理论基础说明这是正确的,给人的感觉是 不明觉厉 。前段时间看了Mike Amundsen 《远距离条件下的康威定律——分布式世界中实现团队构建》(是Design RESTful API的作者)在InfoQ上的一个分享,觉得很有帮助,结合自己的一些思考,整理了该演讲的内容。 可能出乎很多人意料之外的一个事实是,微服务很多核心理念其实在半个世纪前的一篇文章中就被阐述过了,而且这篇文章中的很多论点在软件开发飞速发展的这半个世纪中竟然一再被验证,这就是康威定律(Conway's Law)。 在康威的这篇文章中,最有名的一句话就是: Organizations which design systems are constrained to produce designs which are copies of the communication structures of these organizations. - Melvin Conway(1967) 中文直译大概的意思就是:设计系统的组织,其产生的设计等同于组织之内、组织之间的沟通结构。看看下面的图片(来源于互联网,侵删),再想想Apple的产品、微软的产品设计,就能形象生动的理解这句话。 用通俗的说法就是:组织形式等同系统设计。 这里的系统按原作者的意思并不局限于软件系统。据说这篇文章最初投的哈佛商业评论,结果程序员屌丝的文章不入商业人士的法眼,无情被拒,康威就投到了一个编程相关的杂志,所以被误解为是针对软件开发的。最初这篇文章显然不敢自称定律(law),只是描述了作者自己的发现和总结。后来,在Brooks Law著名的人月神话中,引用这个论点,并将其“吹捧”成了现在我们熟知“康威定律”。 康威定律详细介绍 Mike从他的角度归纳这篇论文中的其他一些核心观点,如下: 第一定律 Communication dictates design 组织沟通方式会通过系统设计表达出来 第二定律 There is never enough time to do something right, but there is always enough time to do it over 时间再多一件事情也不可能做的完美,但总有时间做完一件事情 第三定律 There is a homomorphism from the linear graph of a system to the linear graph of its design organization 线型系统和线型组织架构间有潜在的异质同态特性 第四定律 The structures of large systems tend to disintegrate during development, qualitatively more so than with small systems 大的系统组织总是比小系统更倾向于分解 人是复杂社会动物 第一定律 Communication dictates design 组织沟通方式决定系统设计 组织的沟通和系统设计之间的紧密联系,在很多别的领域有类似的阐述。对于复杂的系统,聊设计就离不开聊人与人的沟通,解决好人与人的沟通问题,才能有一个好的系统设计。相信几乎每个程序员都读过的《人月神话》(1975年,感觉都是老古董了,经典的就是经得起时间考验)里面许多观点都和这句话有异曲同工之妙。 比如《人月神话》中最著名的一句话就是 Adding manpower to a late software project makes it later --Fred Brooks, (1975) Boss们都听到了吗?为了赶进度加程序员就像用水去灭油锅里的火一样(无奈大家还是前赴后继)。 为什么?人月神话也给出了很简洁的答案:沟通成本 = n(n-1)/2,沟通成本随着项目或者组织的人员增加呈指数级增长。是的,项目管理这个算法的复杂度是O(n^2)。举个例子 5个人的项目组,需要沟通的渠道是 5*(5–1)/2 = 10 15个人的项目组,需要沟通的渠道是15*(15–1)/2 = 105 50个人的项目组,需要沟通的渠道是50*(50–1)/2 = 1,225 150个人的项目组,需要沟通的渠道是150*(150–1)/2 = 11,175 所以知道为什么互联网创业公司都这么小了吧,必须小啊,不然等CEO和所有人讲一遍创业的想法后,风投的钱都烧完了。 Mike还举了一个非常有意思的理论,叫“Dunbar Number”,这是一个叫Dunbar(废话)生物学家在1992年最早提出来的。最初,他发现灵长类的大脑容量和其对应的族群大小有一定关联,进而推断出人类的大脑能维系的关系的一些有趣估计。举例来说 亲密(intimate)朋友: 5 信任(trusted)朋友: 15 酒肉(close)朋友: 35 照面(casual)朋友: 150 是不是和上面的沟通成本的数字很貌似有关联?是的,我们的大脑智力只能支持我们维系这么多的关系。(大家都知道这不是程序猿擅长的领域,在开发团队里,这个值应该更小,估计和猿差不多 -_-凸 ) 沟通的问题,会带来系统设计的问题,进而影响整个系统的开发效率和最终产品结果。 一口气吃不成胖子,先搞定能搞定的 第二定律: There is never enough time to do something right, but there is always enough time to do it over 时间再多一件事情也不可能做的完美,但总有时间做完一件事情 Eric Hollnagel是敏捷开发社区的泰斗之一,在他《Efficiency-Effectiveness Trade Offs》 一书中解释了类似的论点。 Problem too complicated? Ignore details. Not enough resources?Give up features. --Eric Hollnagel (2009) 系统越做越复杂,功能越来越多,外部市场的竞争越来越剧烈,投资人的期待越来越高。但人的智力是有上限的,即使再牛逼的人,融到钱再多也不一定招到足够多合适的人。对于一个巨复杂的系统,我们永远无法考虑周全。Eric认为,这个时候最好的解决办法竟然是——“破罐子破摔”。 其实我们在日常开发中也经常碰到。产品经理的需求太复杂了?适当忽略一些细节,先抓主线。产品经理的需求太多了?放弃一些功能。 据说Eric被一家航空公司请去做安全咨询顾问,复杂保证飞机飞行系统的稳定性和安全性。Eric认为做到安全有两种方式: 常规的安全指的是尽可能多的发现并消除错误的部分,达到绝对安全,这是理想。 另一种则是弹性安全,即使发生错误,只要及时恢复,也能正常工作,这是现实。 对于飞机这样的复杂系统,再牛逼的人也无法考虑到漏洞的方方面面,所以Eric建议放弃打造完美系统的想法,而是通过不断的试飞,发现问题,确保问题发生时,系统能自动复原即可,而不追求飞行系统的绝对正确和安全。 下面的图很好的解释了这个过程:听着很耳熟不是吗?这不就是 持续集成 和敏捷开发吗?的确就是。 另一方面,这和互联网公司维护的分布式系统的弹性设计也是一个道理。对于一个分布式系统,我们几乎永远不可能找到并修复所有的bug,单元测试覆盖1000%也没有用,错误流淌在分布式系统的血液里。解决方法不是消灭这些问题,而是容忍这些问题,在问题发生时,能自动回复,微服务组成的系统,每一个微服务都可能挂掉,这是常态,我们只有有足够的冗余和备份即可。即所谓的 弹性设计(Resilience) 或者叫高可用设计(High Availability)。 种瓜得瓜,做独立自治的字系统减少沟通成本 第三定律 There is a homomorphism from the linear graph of a system to the linear graph of its design organization 线型系统和线型组织架构间有潜在的异质同态特性 这是康威第一定律组织和设计间内在关系的一个具体应用。更直白的说,你想要什么样的系统,就搭建什么样的团队。如果你的团队分成前端团队,Java后台开发团队,DBA团队,运维团队,你的系统就会长成下面的样子: 相反,如果你的系统是按照业务边界划分的,大家按照一个业务目标去把自己的模块做出小系统,小产品的话,你的大系统就会长成下面的样子,即微服务的架构 微服务的理念团队间应该是 inter-operate, not integrate 。inter-operate是定义好系统的边界和接口,在一个团队内全栈,让团队自治,原因就是因为如果团队按照这样的方式组建,将沟通的成本维持在系统内部,每个子系统就会更加内聚,彼此的依赖耦合能变弱,跨系统的沟通成本也就能降低。 合久必分,分而治之 第四定律 The structures of large systems tend to disintegrate during development, qualitatively more so than with small systems 大的系统组织总是比小系统更倾向于分解 前面说了,人是复杂的社会动物,人与人的通过非常复杂。但是当我们面对复杂系统时,又往往只能通过增加人力来解决。这时,我们的组织一般是如何解决这个沟通问题的呢?Divide and conquer,分而治之。大家看看自己的公司的组织,是不是一个一线经理一般都是管理15个人以下的?二线经理再管理更少的一线?三线再管理更少的,以此类推。(这里完全没有暗示开发经理比程序猿更难管理) 所以,一个大的组织因为沟通成本/管理问题,总为被拆分成一个个小团队。 创业的想法太好了,反正风投钱多,多招点程序猿 人多管不过来啊,找几个经理帮我管,我管经理 最后, 康威定律 告诉我们组织沟通的方式会在系统设计上有所表达,每个经理都被赋予一定的职责去做大系统的某一小部分,他们和大系统便有了沟通的边界,所以大的系统也会因此被拆分成一个个小团队负责的小系统(微服务是一种好的模式) 康威定律如何解释微服务的合理性 了解了康威定律是什么,再来看看他如何在半个世纪前就奠定了微服务架构的理论基础。 人与人的沟通是非常复杂的,一个人的沟通精力是有限的,所以当问题太复杂需要很多人解决的时候,我们需要做拆分组织来达成对沟通效率的管理 组织内人与人的沟通方式决定了他们参与的系统设计,管理者可以通过不同的拆分方式带来不同的团队间沟通方式,从而影响系统设计 如果子系统是内聚的,和外部的沟通边界是明确的,能降低沟通成本,对应的设计也会更合理高效 复杂的系统需要通过容错弹性的方式持续优化,不要指望一个大而全的设计或架构,好的架构和设计都是慢慢迭代出来的 带来的具体的实践建议是: 我们要用一切手段提升沟通效率,比如slack,github,wiki。能2个人讲清楚的事情,就不要拉更多人,每个人每个系统都有明确的分工,出了问题知道马上找谁,避免踢皮球的问题。 通过MVP的方式来设计系统,通过不断的迭代来验证优化,系统应该是弹性设计的。 你想要什么样的系统设计,就架构什么样的团队,能扁平化就扁平化。最好按业务来划分团队,这样能让团队自然的自治内聚,明确的业务边界会减少和外部的沟通成本,每个小团队都对自己的模块的整个生命周期负责,没有边界不清,没有无效的扯皮,inter-operate, not integrate。 做小而美的团队,人多会带来沟通的成本,让效率下降。亚马逊的Bezos有个逗趣的比喻,如果2个披萨不够一个团队吃的,那么这个团队就太大了。事实上一般一个互联网公司小产品的团队差不多就是7,8人左右(包含前后端测试交互用研等,可能身兼数职)。 再对应下衡量微服务的标准,我们很容易会发现他们之间的密切关系: 分布式服务组成的系统 按照业务而不是技术来划分组织 做有生命的产品而不是项目 Smart endpoints and dumb pipes(我的理解是强服务个体和弱通信) 自动化运维(DevOps) 容错 快速演化 参考资料 远距离条件下的康威定律——分布式世界中实现团队构建,本文图片来源该ppt截图 Conway‘s Law in wiki Conway's Law Homepage
WHAT - 什么是微服务 微服务简介 这次参加JavaOne2015最大的困难就是听Microservice相关的session,无论内容多么水,只要题目带microservice,必定报不上名,可见Microservice有多火。最喜欢其中一页。关于这个典故,可以参考[this](http://knowyourmeme.com/memes/you-keep-using-that-word-i-do-not-think-it-means-what-you-think-it-means),此图适用于一切高大上的名字——技术有SOA,Agile,CLOUD,DevOps等等,古代有道,气,八卦等等。此类名词的最大特点就是 一解释就懂,一问就不知,一讨论就打架。 微服务的流行,Martin功不可没,这老头也是个奇人,特别擅长抽象归纳和制造概念,我觉的这就是最牛逼的markting啊,感觉这也是目前国人欠缺的能力。 Martin Fowler是国际著名的OO专家,敏捷开发方法的创始人之一,现为ThoughtWorks公司的首席科学家.福勒(Martin Fowler),在面向对象分析设计、UML、模式、软件开发方法学、XP、重构等方面,都是世界顶级的专家,现为Thought Works公司的首席科学家。Thought Works是一家从事企业应用开发和集成的公司。早在20世纪80年代,Fowler就是使用对象技术构建多层企业应用的倡导者,他著有几本经典书籍:《企业应用架构模式》、《UML精粹》和《重构》等。—— 百度百科 先来看看传统的web开发方式,通过对比比较容易理解什么是Microservice Architecture。和Microservice相对应的,这种方式一般被称为Monolithic(比较难传神的翻译)。所有的功能打包在一个WAR包里,基本没有外部依赖(除了容器),部署在一个JEE容器(Tomcat,JBoss,WebLogic)里,包含了DO/DAO,Service,UI等所有逻辑。 Monolithic比较适合小项目,优点是: 开发简单直接,集中式管理 基本不会重复开发 功能都在本地,没有分布式的管理开销和调用开销 它的缺点也非常明显,特别对于互联网公司来说(不一一列举了): 开发效率低:所有的开发在一个项目改代码,递交代码相互等待,代码冲突不断 代码维护难:代码功能耦合在一起,新人不知道何从下手 部署不灵活:构建时间长,任何小修改必须重新构建整个项目,这个过程往往很长 稳定性不高:一个微不足道的小问题,可以导致整个应用挂掉 扩展性不够:无法满足高并发情况下的业务需求 所以,现在主流的设计一般会采用Microservice Architecture,就是基于微服务的架构。简单来说, 微服务的目的是有效的拆分应用,实现敏捷开发和部署 。 用《The art of scalability》一书里提到的scale cube比较容易理解如何拆分。你看,我们叫分库分表,别人总结成了scale cube,这就是抽象的能力啊,把复杂的东西用最简单的概念解释和总结。X轴代表运行多个负载均衡器之后运行的实例,Y轴代表将应用进一步分解为微服务(分库),数据量大时,还可以用Z轴将服务按数据分区(分表) 微服务的具体特征 先看看最官方的定义吧 The microservice architectural style is an approach to developing a single application as a suite of small services, each running in its own process and communicating with lightweight mechanisms, often an HTTP resource API. These services are built around business capabilities and independently deployable by fully automated deployment machinery. There is a bare minimum of centralized management of these services , which may be written in different programming languages and use different data storage technologies. -- James Lewis and Martin Fowler 把Martin老头的定义大概的翻译一下就是下面几条,这个定义还是太抽象是不是,那就对了,就是要务虚,都说明白了谁还找他付费咨询啊,这么贵。 一些列的独立的服务共同组成系统 单独部署,跑在自己的进程里 每个服务为独立的业务开发 分布式的管理 Martin自己也说了,每个人对微服务都可以有自己的理解,不过大概的标准还是有一些的。 分布式服务组成的系统 按照业务而不是技术来划分组织 做有生命的产品而不是项目 Smart endpoints and dumb pipes(我的理解是强服务个体和弱通信) 自动化运维(DevOps) 容错 快速演化 关于微服务的更多理论基础,可以参考康威定律。 SOA vs Microservice 除了Smart endpoints and dumb pipes都很容易理解对吗?相信很多人都会问一个问题,这是不是就是SOA换了个概念,挂羊头卖狗肉啊,有说法把Microservice叫成Lightway SOA。也有很多传统砖家跳出来说Microservice就是SOA。其实Martin也没否认SOA和Microservice的关系。 我个人理解,Microservice是SOA的传承,但一个最本质的区别就在于Smart endpoints and dumb pipes,或者说是真正的分布式的、去中心化的。Smart endpoints and dumb pipes本质就是去ESB,把所有的“思考”逻辑包括路由、消息解析等放在服务内部(Smart endpoints),去掉一个大一统的ESB,服务间轻(dumb pipes)通信,是比SOA更彻底的拆分。 HOW - 怎么具体实践微服务 听上去好像都不错,具体怎么落地啊?这需要回答下面几个问题: 客户端如何访问这些服务? 服务之间如何通信? 这么多服务,怎么找? 服务挂了怎么办? 客户端如何访问这些服务? 原来的Monolithic方式开发,所有的服务都是本地的,UI可以直接调用,现在按功能拆分成独立的服务,跑在独立的一般都在独立的虚拟机上的Java进程了。客户端UI如何访问他的?后台有N个服务,前台就需要记住管理N个服务,一个服务下线/更新/升级,前台就要重新部署,这明显不服务我们拆分的理念,特别当前台是移动应用的时候,通常业务变化的节奏更快。另外,N个小服务的调用也是一个不小的网络开销。还有一般微服务在系统内部,通常是无状态的,用户登录信息和权限管理最好有一个统一的地方维护管理(OAuth)。 所以,一般在后台N个服务和UI之间一般会一个代理或者叫API Gateway,他的作用包括 提供统一服务入口,让微服务对前台透明 聚合后台的服务,节省流量,提升性能 提供安全,过滤,流控等API管理功能 我的理解其实这个API Gateway可以有很多广义的实现办法,可以是一个软硬一体的盒子,也可以是一个简单的MVC框架,甚至是一个Node.js的服务端。他们最重要的作用是为前台(通常是移动应用)提供后台服务的聚合,提供一个统一的服务出口,解除他们之间的耦合,不过API Gateway也有可能成为单点故障点或者性能的瓶颈。 一般用过Taobao Open Platform的就能很容易的体会,TAO就是这个API Gateway。 服务之间如何通信? 因为所有的微服务都是独立的Java进程跑在独立的虚拟机上,所以服务间的通行就是IPC(inter process communication),已经有很多成熟的方案。现在基本最通用的有两种方式。这几种方式,展开来讲都可以写本书,而且大家一般都比较熟悉细节了,就不展开讲了。 同步调用 REST(JAX-RS) RPC(Dubbo) 异步消息调用(Kafka, Notify, MetaQ) 一般同步调用比较简单,一致性强,但是容易出调用问题,性能体验上也会差些,特别是调用层次多的时候。RESTful和RPC的比较也是一个很有意思的话题。一般REST基于HTTP,更容易实现,更容易被接受,服务端实现技术也更灵活些,各个语言都能支持,同时能跨客户端,对客户端没有特殊的要求,只要封装了HTTP的SDK就能调用,所以相对使用的广一些。RPC也有自己的优点,传输协议更高效,安全更可控,特别在一个公司内部,如果有统一个的开发规范和统一的服务框架时,他的开发效率优势更明显些。就看各自的技术积累实际条件,自己的选择了。 而异步消息的方式在分布式系统中有特别广泛的应用,他既能减低调用服务之间的耦合,又能成为调用之间的缓冲,确保消息积压不会冲垮被调用方,同时能保证调用方的服务体验,继续干自己该干的活,不至于被后台性能拖慢。不过需要付出的代价是一致性的减弱,需要接受数据最终一致性;还有就是后台服务一般要实现幂等性,因为消息发送出于性能的考虑一般会有重复(保证消息的被收到且仅收到一次对性能是很大的考验);最后就是必须引入一个独立的broker,如果公司内部没有技术积累,对broker分布式管理也是一个很大的挑战。 这么多服务,怎么找? 在微服务架构中,一般每一个服务都是有多个拷贝,来做负载均衡。一个服务随时可能下线,也可能应对临时访问压力增加新的服务节点。服务之间如何相互感知?服务如何管理?这就是服务发现的问题了。一般有两类做法,也各有优缺点。基本都是通过zookeeper等类似技术做服务注册信息的分布式管理。当服务上线时,服务提供者将自己的服务信息注册到ZK(或类似框架),并通过心跳维持长链接,实时更新链接信息。服务调用者通过ZK寻址,根据可定制算法,找到一个服务,还可以将服务信息缓存在本地以提高性能。当服务下线时,ZK会发通知给服务客户端。 客户端做:优点是架构简单,扩展灵活,只对服务注册器依赖。缺点是客户端要维护所有调用服务的地址,有技术难度,一般大公司都有成熟的内部框架支持,比如Dubbo。 服务端做:优点是简单,所有服务对于前台调用方透明,一般在小公司在云服务上部署的应用采用的比较多。 这么多服务,服务挂了怎么办? 前面提到,Monolithic方式开发一个很大的风险是,把所有鸡蛋放在一个篮子里,一荣俱荣,一损俱损。而分布式最大的特性就是网络是不可靠的。通过微服务拆分能降低这个风险,不过如果没有特别的保障,结局肯定是噩梦。我们刚遇到一个线上故障就是一个很不起眼的SQL计数功能,在访问量上升时,导致数据库load彪高,影响了所在应用的性能,从而影响所有调用这个应用服务的前台应用。所以当我们的系统是由一系列的服务调用链组成的时候,我们必须确保任一环节出问题都不至于影响整体链路。相应的手段有很多: 重试机制 限流 熔断机制 负载均衡 降级(本地缓存) 这些方法基本上都很明确通用,就不详细说明了。比如Netflix的Hystrix:https://github.com/Netflix/Hystrix WHY - 微服务的应用 这里有一个图非常好的总结微服务架构需要考虑的问题,包括 API Gateway 服务间调用 服务发现 服务容错 服务部署 数据调用 微服务的优点和缺点(或者说挑战)一样明显。 优点 开发简单 技术栈灵活 服务独立无依赖 独立按需扩展 可用性高 缺点(挑战) 多服务运维难度 系统部署依赖 服务间通信成本 数据一致性 系统集成测试 重复工作 性能监控 没有最好的,只有适合自己的。 对于大的互联网公司,微服务架构是血液,是习惯,每家公司都有自己的套路和架构,细节有不同,但是核心理念是通的。 对于一般的公司而言,实践微服务有非常大的技术挑战,于是乎才有了这么多IT供应商考虑这里的商机。微服务比较适合未来有一定的扩展复杂度,且有很大用户增量预期的应用,说人话就是新兴的互联网公司。创业初期,不可能买大量的机器或者很贵的机器,但是又必须考虑应对成功后的巨量的用户,微服务架构成了最好的选择。 So What - 思考 看到上面的图,不是不觉得特别的熟悉?其实我们N年前就用的滚瓜烂熟了好不好?裤子都拖了,你就给我看这个? from: https://github.com/Netflix/recipes-rss/wiki/Architecture 其实本来所谓的微服务就是对互联网在应用技术的一个总结归纳,IT厂商鼓吹所有概念无非是为了生意(business),SOA是,Cloud是,Microservice也是。下面玩笑很有意思的概括了这个情况(我加了第一条线,原图见这里) 所以微服对我们的思考我觉得更多的是思维上的,对已微服务架构, 技术上不是问题,意识比工具重要。 按照业务 或者客户需求组织资源(这是最难的) 做有生命的产品,而不是项目 头狼战队,全栈化 后台服务贯彻Single Responsibility Principle VM->Docker (to PE) DevOps (to PE) 同时,对于开发同学,有这么多的中间件和强大的PE支持固然是好事,我们也需要深入去了解这些中间件背后的原理,知其然知其所以然,设想下,如果我们是一个小公司的CTO,离开的阿里的大环境,在有限的技术资源如何通过开源技术实施微服务? 最后,一般提到微服务都离不开DevOps和Docker,理解微服务架构是核心,devops和docker是工具,是手段。下次在抽时间再学习整理下。 参考资料和推荐阅读 http://www.infoq.com/articles/microservices-intro http://martinfowler.com/articles/microservices.html http://martinfowler.com/microservices/ http://highscalability.com/blog/2014/4/8/microservices-not-a-free-lunch.html https://www.nginx.com/blog/introduction-to-microservices/ http://microservices.io/patterns/microservices.html http://www.infoq.com/presentations/migration-cloud-native https://github.com/Netflix/recipes-rss http://www.mattstine.com/microservices PS: 阿里巴巴客户体验驱动创新中心随时招人,欢迎加入,有意者联系 xiaoxia.qxx@alibaba-inc.com
该文章来自于阿里巴巴技术协会(ATA)精选文章。 Java调试概述 程序猿都调式或者debug过Java代码吧?都体会过被PM,PD,测试,业务同学们围观debug吧?说调试,先看看调试严格定义是什么。引用Wikipedia定义: 调试(De-bug),又称除错,是发现和减少计算机程序或电子仪器设备中程序错误的一个过程。调试的基本步骤: 1. 发现程序错误的存在 2. 以隔离、消除的方式对错误进行定位 3. 确定错误产生的原因 4. 提出纠正错误的解决办法 5. 对程序错误予以改正,重新测试 用调试的好处是我们就无需每次新测试都要重新编译了,不用copy-paste一堆的System.out.println(很low但很多时候很管用有没有?)。 更多时候我们调试最直接简单的办法就是IDE,Java程序员用的最多的必然是Eclipse,Netbeans和IntelliJ也有各自忠实的粉丝,各有优劣。关于用IDE如何调试可以另起一个话题再讨论。 除了IDE之外,JDK也自带了一些命令行调试工具也很方便。大家用的比较多的如下表所示: 命令 描述 jdb 命令行调试工具 jps 列出所有Java进程的PID jstack 列出虚拟机进程的所有线程运行状态 jmap 列出堆内存上的对象状态 jstat 记录虚拟机运行的状态,监控性能 jconsole 虚拟机性能/状态检查可视化工具 具体用法可以参考JDK文档,这些大家在线上调试应用的时候用的也不少,比如一般线上load高的问题排查步骤是 先用top找到耗资源的进程 ps+grep找到对应的java进程/线程 jstack分析哪些线程阻塞了,阻塞在哪里 jstat看看FullGC频率 jmap看看有没有内存泄露 但这个也不是今天的重点,那么问题来了(blue fly is the strongest):这些工具如何能获取远程Java进程的信息的?又是如何远程控制Java进程的运行的? 相信有不少人和我一样对这些工具的 实现原理 很好奇,本文就尝试介绍下各中缘由。 Java调试体系JPDA简介 Java虚拟机设计了专门的API接口供调试和监控虚拟机使用,被称为Java平台调试体系即Java Platform Debugger Architecture(JPDA)。JPDA按照抽象层次,又分为三层,分别是 JVM TI - Java VM Tool Interface 虚拟机对外暴露的接口,包括debug和profile JDWP - Java Debug Wire Protocol 调试器和应用之间通信的协议 JDI - Java Debug Interface Java库接口,实现了JDWP协议的客户端,调试器可以用来和远程被调试应用通信 用一个不是特别准确但是比较容易理解的类比,大家可以和HTTP做比较,可以推断他就是一个典型的C/S应用,所以也可以很自然的想到,JDI是用TCP Socket和虚拟机通信的,后面会详细再介绍。 IDE+JDI = 浏览器 JDWP = HTTP JVMTI = RESTful接口 Debugee虚拟机= REST服务端 和其他的Java模块一样,Java只定义了Spec规范,也提供了参考实现(Reference Implementation),但是第三方完全可以参照这个规范,按照自己的需要去实现其中任意一个组件,原则上除了规范上没有定义的功能,他们应该能正常的交互,比如Eclipse就没有用Sun/Oracle的JDI,而是自己实现了一套(由于开源license的兼容原因),因为直接用JDWP协议调用JVMTI是不会受GPL“污染”的。的确有第三方调试工具基于JVMTI做了一套调试工具,这样效率更高,功能更丰富,因为JDI出于远程调用的安全考虑,做了一些功能的限制。用户还可以不用JDI,用自己熟悉的C或者脚本语言开发客户端,远程调试Java虚拟机,所以JPDA真个架构是非常灵活的。 JVMTI JVMTI是整个JPDA中最中要的API,也是虚拟机对外暴露的接口,掌握了JVMTI,你就可以真正完全掌控你的虚拟机,因为必须通过本地加载,所以暴露的丰富功能在安全上也没有太大问题。更完整的API内容可以参考JVMTI SPEC: 虚拟机信息 堆上的对象 线程和栈信息 所有的类信息 系统属性,运行状态 调试行为 设置断点 挂起现场 调用方法 事件通知 断点发生 异步调用 在JPDA的这个图里,agent是其中很重要的一个模块,正是他把JDI,JDWP,JVMTI三部分串联成了一个整体。简单来说agent的特性有 C/C++实现的 被虚拟机以动态库的方式加载 能调用本地JVMTI提供的调试能力 实现JDWP协议服务器端 与JDI(作为客户端)通信(socket/shmem等方式) Code speak louder than words. 上个代码加注释来解释: // Agent_OnLoad必须是入口函数,类似于main函数,规范规定 JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *vm, char *options, void *reserved) { .... MethodTraceAgent* agent = new MethodTraceAgent(); agent->Init(vm); agent->AddCapability(); agent->RegisterEvent(); ... } /****** AddCapability(): init(): 初始化jvmti函数指针,所有功能的函数入口 *****/ jvmtiEnv* MethodTraceAgent::m_jvmti = 0; jint ret = (vm)->GetEnv(reinterpret_cast<void**>(&jvmti), JVMTI_VERSION_1_0); /****** AddCability(): 确认agent能访问的虚拟机接口 *****/ jvmtiCapabilities caps; memset(&caps, 0, sizeof(caps)); caps.can_generate_method_entry_events = 1; // 设置当前环境 m_jvmti->AddCapabilities(&caps); /****** RegisterEvent(): 创建一个新的回调函数 *****/ jvmtiEventCallbacks callbacks; memset(&callbacks, 0, sizeof(callbacks)); callbacks.MethodEntry = &MethodTraceAgent::HandleMethodEntry; // 设置回调函数 m_jvmti->SetEventCallbacks(&callbacks, static_cast<jint>(sizeof(callbacks))); // 开启事件监听 m_jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_METHOD_ENTRY, 0); /****** HandleMethodEntry: 注册的回调,获取对应的信息 *****/ // 获得方法对应的类 m_jvmti->GetMethodDeclaringClass(method, &clazz); // 获得类的签名 m_jvmti->GetClassSignature(clazz, &signature, 0); // 获得方法名字 m_jvmti->GetMethodName(method, &name, NULL, NULL); 写好agent后,需要编译,并在启动Java进程时指定加载路径 // 编译动态链接库 g++ -w -I${JAVA_HOME}/include/ -I${JAVA_HOME}/include/linux MethodTraceAgent.cpp Main.cpp -fPIC -shared -o libAgent.so // 拷贝到 LD_LIBRARY_PATH export LD_LIBRARY_PATH=/home/xiaoxia/lib cp libAgent.so ~/lib // 运行测试效果,记得load编译的动态库 javac MethodTraceTest.java java -agentlib:Agent=first MethodTraceTest Agent实现的动态链接库其实有两种加载方式: 虚拟机启动初期加载 这个链接库必须实现Agent_OnLoad作为函数入口。这种方式可以利用的接口和功能更多,因为他在被调式虚拟机运行的应用初始化之前就被调用了,但是限制是必须以显示的参数指定启动方式,这在线上环境上是不大现实的。 java -agentlib:<agent-lib-name>=<options> JavaClass //Linux从LD_LIBRARY_PATH找so文件, Windows从PATH找该DLL文件。 java -agentpath:<path-to-agent>=<options> JavaClass //直接从绝对路径查找 动态加载 这是更灵活的方式,Java进程可以正常启动,如果需要,通过Sun/Orale提供的私有Attach API可以连上对应的虚拟机,再通过JPDA方式控制,不过因为虚拟机已经开始运行了,所以功能上会有限制。我们比较熟悉的jstack等jdk工具就是通过这种方式做的,动态库必须实现Agent_OnAttach作为函数入口。如果有兴趣理解Attach机制细节的话,可以参考这个blog,简单来说,就是虚拟机默认起了一个线程(没错,就是jstack时看到Signal Dispatcher这货),专门接受处理进程间singal通知,当他收到SIGQUIT时,就会启动一个新的socket监听线程(就是jstack看到的Attach Listener线程)来接收命令,Attach Listener就是一个agent实现,他能处理很多dump命令,更重要的是他能再加载其他agent,比如jdwp agent。 通过Attach机制,我们能自己非常方便的实现一个jinfo或者其他jdk tools,只需通过JPS获取pid,在通过attach api去load我们提供的agent,完整的jinfo例子也在附件里。 import java.io.IOException; import com.sun.tools.attach.VirtualMachine; public class JInfo { public static void main(String[] args) throws Exception { String pid = args[0]; String agentName = "JInfoAgent"; System.out.printf("Atach to Pid %s, dynamic load agent %s \n", pid, agentName); VirtualMachine virtualMachine = com.sun.tools.attach.VirtualMachine.attach(pid); virtualMachine.loadAgentLibrary(agentName, null); virtualMachine.detach(); } } JDWP JDWP 是 Java Debug Wire Protocol 的缩写,它定义了调试器(debugger)和被调试的 Java 虚拟机(debugee)之间的通信协议。他就是同过JVMTI Agent实现的,简单来说,他就是对JVMTI调用(输入和输出,事件)的通信定义。 JDWP 有两种基本的包(packet)类型:命令包(command packet)和回复包(reply packet)。JDWP 本身是无状态的,因此对 命令出现的顺序并不受限制。而且,JDWP 可以是异步的,所以命令的发送方不需要等待接收到回复就可以继续发送下一个命令。Debugger 和 Debugee 虚拟机都有可能发送命令: Debugger 通过发送命令获取Debugee虚拟机的信息以及控制程序的执行。Debugger虚拟机通过发送 命令通知 Debugger 某些事件的发生,如到达断点或是产生异常。 回复是用来确认对应的命令是否执行成功(在包定义有一个flag字段对应),如果成功,回复还有可能包含命令请求的数据,比如当前的线程信息或者变量的值。从 Debugee虚拟机发送的事件消息是不需要回复的。 下图展示了一个可能的实现方式,再次强调下,Java的世界里只定义了规范(Spec),很多实现细节可以自己提供,比如虚拟机就有很多中实现(Sun HotSpot,IBM J9,Google Davik)。 一般我们启动远程调试时,都会看到如下参数,其实表面了JDWP Agent就是通过启动一个socket监听来接受JDWP命令和发送事件信息的,而且,这个TCP连接可以是双向的: // debugge是server先启动监听,ide是client发起连接 agentlib:jdwp=transport=dt_socket,server=y,address=8000 // debugger ide是server,通过JDI监听,JDWP Agent作为客户端发起连接 agentlib:jdwp=transport=dt_socket,address=myhost:8000 JDI JDI属于JPDA中最上层接口,也是Java程序员接触的比较多的。他用起来也比较简单,参考JDI的API Doc即可。所有的功能都和JVMTI提供的调试功能一一对应的(JVMTI还包括很多非调式接口,JDK5以前JVMTI是分为JVMDI和JVMPI的,分别对应调试debug和调优profile)。 还是用一个例子来解释最直接,大家可以看到基本的流程都是类似的,真个JPDA调试的核心就是通过JVMTI的 调用 和事件 两个方向的沟通实现的。 import java.util.List; import java.util.Map; import com.sun.jdi.*; import com.sun.jdi.connect.*; import com.sun.jdi.event.*; import com.sun.jdi.request.*; public class MethodTrace { private VirtualMachine vm; private Process process; private EventRequestManager eventRequestManager; private EventQueue eventQueue; private EventSet eventSet; private boolean vmExit = false; //write your own testclass private String className = "MethodTraceTest"; public static void main(String[] args) throws Exception { MethodTrace trace = new MethodTrace(); trace.launchDebugee(); trace.registerEvent(); trace.processDebuggeeVM(); // Enter event loop trace.eventLoop(); trace.destroyDebuggeeVM(); } public void launchDebugee() { LaunchingConnector launchingConnector = Bootstrap .virtualMachineManager().defaultConnector(); // Get arguments of the launching connector Map<String, Connector.Argument> defaultArguments = launchingConnector .defaultArguments(); Connector.Argument mainArg = defaultArguments.get("main"); Connector.Argument suspendArg = defaultArguments.get("suspend"); // Set class of main method mainArg.setValue(className); suspendArg.setValue("true"); try { vm = launchingConnector.launch(defaultArguments); } catch (Exception e) { // ignore } } public void processDebuggeeVM() { process = vm.process(); } public void destroyDebuggeeVM() { process.destroy(); } public void registerEvent() { // Register ClassPrepareRequest eventRequestManager = vm.eventRequestManager(); MethodEntryRequest entryReq = eventRequestManager.createMethodEntryRequest(); entryReq.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD); entryReq.addClassFilter(className); entryReq.enable(); MethodExitRequest exitReq = eventRequestManager.createMethodExitRequest(); exitReq.addClassFilter(className); exitReq.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD); exitReq.enable(); } private void eventLoop() throws Exception { eventQueue = vm.eventQueue(); while (true) { if (vmExit == true) { break; } eventSet = eventQueue.remove(); EventIterator eventIterator = eventSet.eventIterator(); while (eventIterator.hasNext()) { Event event = (Event) eventIterator.next(); execute(event); if (!vmExit) { eventSet.resume(); } } } } private void execute(Event event) throws Exception { if (event instanceof VMStartEvent) { System.out.println("VM started"); } else if (event instanceof MethodEntryEvent) { Method method = ((MethodEntryEvent) event).method(); System.out.printf("Enter -> Method: %s, Signature:%s\n",method.name(),method.signature()); System.out.printf("\t ReturnType:%s\n", method.returnTypeName()); } else if (event instanceof MethodExitEvent) { Method method = ((MethodExitEvent) event).method(); System.out.printf("Exit -> method: %s\n",method.name()); } else if (event instanceof VMDisconnectEvent) { vmExit = true; } } } 总结 整个JDPA有非常清晰的分层,各司其职,让整个调式过程简单可以扩展,而这一切其实都是构建在高司令巨牛逼的Java虚拟机抽象之上的,通过JVMTI将抽象良好的虚拟机控制暴露出来,让开发者可以自由的掌控被调试的虚拟机。有兴趣的同学可以运行下附近中的几个例子,应该会有更充分的了解。 而且由于规范的灵活性,如果有特殊需求,完全可以自己去重新实现和扩展,而且不限于Java,举个例子,我们可以通过agent去加密解密加载的类,保护知识产权;我们可以记录虚拟机运行过程,作为自动化测试用例; 我们还可以把线上问题的诊断实践自动化下来,做一个快速预判 ,争取最宝贵的时间。 参考文档 JPDA documentation JDWP specification JVMTI specification JDI specification Developerworks JVMTI Agent Attach API Apache Harmony JDWP Agent
创新性的web项目,时间比功能重要,需要开速做一个mvp去验证想法,争取种子用户。
先了解下基本的sevlet/jsp开发
然后看看https://book.douban.com/subject/1436131/
再到spring上找个sample学习代码
新生代用的是复制算法,是通过宝贵的内存空间来换取效率的的方法。S0和S1只能用一块。
老年代用的是标记清楚,空间利用率高了,但是相应的效率就会低一些。
本质还是算法不一样。