宁静·致远


  • 首页

  • 分类

  • 关于

  • 归档

  • 标签

  • RSS

服务化架构-注册与发现

发表于 2016-11-23 | 阅读次数

基本功能

在分布式系统中,服务提供端和消费端彼此不知道对方的存在,因此需要有一种机制来确保消费端能够找到提供端并进行调用。一般来说,提供端需要将自己能够提供的服务信息暴露出来,这样消费端通过检索就能找到自己需要的服务并建立连接进行调用。所以,对于服务提供端来讲,需要具备以下功能:

  1. 服务注册:将服务信息暴露出来,供消费端查找和使用
  2. 取消注册:即注销服务的注册信息

同时,对于消费端来说,则需要:

  1. 服务检索:按一定条件查找服务信息,如按应用、接口、IP等检索
  2. 服务订阅:订阅特定的服务,为后面的服务调用和治理做前期准备
  3. 取消订阅:取消指定服务的订阅

结构组成

在这种情况下,注册中心的概念就被提了出来。

同时,系统间调用需要能够根据服务的运行状态(如上下线状态、负载、响应时间、网络情况等)而灵活调整调用端与服务端的组合。

服务化架构-组成

发表于 2016-11-23 | 阅读次数

功能结构

既然是服务化架构,那么肯定会包含服务提供方和调用方,即provider和consumer。那么作为分布式的服务化架构,需要有一个地方存储provider的信息,便于consumer调用,那么这个角色就是注册中心(registry)。

另外,鉴于分布式系统的复杂性和状态多样性,我们需要对provider、consumer、registry进行管理,比如服务依赖管理、权限管理、配置管理、版本管理、流量控制、服务上下线等,那么管理端(administrator)也就成了标配。

所以综上所述,一个完整的分布式服务化架构,应该包含4部分:

  1. 注册中心Registry
  2. 服务提供端Provider
  3. 服务消费端Consumer
  4. 管理端Administrator

其中,Provider将自己能够提供的服务信息登记到注册中心,同时通过心跳的方式定时更新自己的状态。Consumer从Registry获取可用服务信息后,直接与Provider建立连接进行交互。

这种架构,基于RPC的服务框架如dubbo/dubbox,JSF都是采用这种机制。

另外,一些基于REST的服务框架(比如Spring Cloud)会增加服务路由的组件,也就是说Consumer需要经过服务路由才能请求Provider,这种方式下,其架构就是由5部分组成:

  1. 注册中心Registry
  2. 服务提供端Provider
  3. 服务消费端Consumer
  4. 服务路由Router
  5. 管理端Administrator

经由服务路由调用的方式,典型的代表是Spring Cloud,如下图:

服务化架构-前言

发表于 2016-11-23 | 阅读次数

近两年,微服务的概念逐渐火了起来,而docker的出现,更是在微服务的火上浇了一桶油。纯粹地讲,我不太认同微服务的说法。说白了,你还是一个服务,只是因为你的小微,有些地方你需要特别注意,比如高内聚、低耦合,比如团队规模的“两个披萨”原则等。但其实这些原则对于“服务”的世界来讲,早就是普世价值了。

那我们再往前看5年,那时候服务化的架构已经开始在业界崭露头角,比如2011年的时候,阿里把内部的dubbo框架开源了。从当前的标准来看,dubbo并不是一个非常优秀的服务化框架,它有很多缺陷和缺失,但是在5年前,这已经难能可贵了。

上周,在朋友的公司做了一次服务化架构的内训。为了准备这个PPT,特地将服务化框架的体系做了一个梳理,因此决定趁热打铁,写个系列博客进行更进一步的总结。

这个系列的文章,将从以下几个方面进行总结:

  1. 服务化架构的组成
  2. 服务注册与发现
  3. RPC
  4. 同步与异步
  5. 服务依赖管理
  6. 服务链路跟踪
  7. 服务路由
  8. 服务负载均衡
  9. 服务降级
  10. 服务限流
  11. 服务上下线
  12. 服务授权
  13. 服务监控与告警
  14. 服务版本管理

晋级Review归来话代码有毒

发表于 2016-10-27 | 阅读次数

这几天做应届生的转正定级答辩,结束后小黑哥在朋友圈里面发了下面一幅图:

并在评论里面说了一句话:网络不超时,堆外不清理,异步不回调,异常不监控。

上面的中毒情况总结得非常不错,这让我想起了《唐伯虎点秋香》里面的一段情节:

华夫人对唐伯虎说:一日丧命散是用七种不同的毒虫再加上那鹤顶红提炼七七四十九天而成的,无色无味,杀人于无影无踪。吃了我们一日丧命散的人,一天之内会武功全失,经脉逆流,胡思乱想而至走火入魔,最后啊,会血管爆裂而死。

借着这个情节和小黑哥的中毒,来总结一下代码里面的七种毒虫吧。

1.资源不限制

这是很多码农容易中的毒,典型的症状是:

1.1 缓存不设置最大数量限制

比如我们使用map实现了一个cache,但是没有设置cache的大小,久而久之你可以想象一下这个cache最终会膨胀到什么程度。当然有同学可能会说我使用的是Guava cache,可以设置数量限制。well done,你再回去看看你的full gc是不是比较频繁,没准是因为这个值太大导致的哟~

1.2 线程池不设置队列大小

比如我们使用newFixedThreadPool,但是我们没有设置队列的大小,而悲催的是,在java中,线程池队列的默认大小是Integer.MAX_VALUE,我想你自己也能清楚这种情况下会发生什么问题。
再比如Executors.newCachedThreadPool,这个接口很多人会用到,但很多用的人都没有仔细想过会不会在某种情况下这里创建出巨多的线程。

2.网络不超时

这里说的是网络调用的场景不设置超时。

在服务化设计大行其道的今天,我们很多的服务调用都是通过网络调用进行的。设想一下,如果我们调用的某个服务性能低下或者网络异常,长时间没有返回结果,那么调用端线程就会一直阻塞在这里。如果没有使用线程池,那么应用会阻塞在这里。如果使用了线程池,那么线程就会长时间阻塞,最终导致线程池爆掉。

在review代码的时候,这种情况尤其常见,尤其是使用httpclient的时候,各位看官要多加注意哟。

3.自我不保护

这种情况尤其常见于对外提供接口的情况。对于调用方传过来的参数,我们没有做严格的有效性检查或者限制,最终导致不符合要求的数据将程序搞挂。比如我们提供了一个用户信息批量查询的接口,参数是一个数组。结果是某个调用方传了有1000个元素的数组过来,最终可能直接导致程序内存溢出……这种情况我们能去怪罪调用方吗?严格来说,我们不能,在这里可以借鉴一句经典的话:不要相信前端传来的任何数据。我想对于接口也是一样:不要相信调用端传来的任何数据。

还有一种情况就是系统处理能力的保护,最典型的就是QPS。如果我们的系统能够承受的QPS是100,那么如果QPS超过这个数值,我们该怎么办?我想很多人心里已经有了答案了。

4.连接不关闭

这里其实是有两点要强调,连接和流。

4.1 连接不关闭

说到连接,这里所说的是网络连接。我们都知道,网络资源是一种受限资源,无限制的网络连接会给自身及对方系统带来很大的压力,因此我们会使用连接池或其他连接复用技术来降低系统压力,以此提高效率。但是如果我们忘记了关闭连接的话,就会对对端系统带来隐患。比如数据库连接,如果忘记关闭,会造成数据库服务器连接资源耗尽最终导致拒绝服务。当然有同学说,现在很多服务器都有超时检查机制,长时间不活动的连接会自动清除。那么你想过没有,如果连接被服务器断开,这种情况下客户端是感知不到的。只有你下次使用的时候才发现连接已经不可用了。

4.2 流不关闭

流的问题很好理解,我们读写文件、网络传输,很多很多场景需要使用流,而在操作系统层面,对文件、网络的操作都是有限制的。在使用流的时候,我们首先需要进行open,最后close。如果我们忘记了close,那么系统就不会释放对此资源的占用,最终超出系统限制而导致后续的操作失败,最典型的错误就是too many open files。

5.异步不回调

在分布式服务架构中,很多的服务为了提高并发处理能力,使用了异步机制。异步的好处是消费端可以不必一直等待服务端的结果,而将资源交给其他的逻辑处理。当服务端有结果返回时,通过回调机制调用消费端的逻辑进行后续的处理。但是在实际的使用过程中,发现很多人还是使用同步的方式来调用异步方法,比如:

fooService.findFoo(fooId); //fooService.findFoo方法支持异步
Future<Foo> fooFuture = RpcContext.getContext().getFuture(); 

Foo foo = fooFuture.get(); // 如果foo已返回,直接拿到返回值,否则线程wait住,等待foo返回后,线程会被notify唤醒

我们知道Future对象具有如下的特性:

  1. 异步执行,可用 get 方法获取执行结果;
  2. 如果计算还没完成,get 方法是会阻塞的,如果完成了,是可以多次获取并立即得到结果的;
  3. 如果计算还没完成,是可以取消计算的;
  4. 可以查询计算的执行状态

所以上述的代码问题主要出现在第3行,如果fooService.findFoo方法还没有返回值,那么fooFuture.get()方法会被阻塞直到结果返回,这样就失去了异步调用的优势了。

那么针对上面的代码,正确的写法应该是通过回调机制做数据返回时的处理:

fooService.findFoo(fooId);  

ResponseFuture future = ((FutureAdapter)RpcContext.getContext().getFuture()).getFuture();
future.setCallback(new ResponseCallback(){
    public void done(Object response){
       //调用正常的时候执行
    }

    public void caught(Throwable exception){
      //调用异常的时候执行
    }
});

当然,上面的方式只是举了个栗子,具体情况需要根据不同的RPC框架去做处理。这里推荐Google guava concurrent包里面的接口和工具类,比如ListenableFuture、Futures等,具体实例网上有很多的说明,请大家自行安利,这里就不做赘述了。

6.异常不监控

当系统抛出异常被捕获后,很多同学只是通过日志记录了一下异常信息,但是并没有通过监控系统记录异常。这样带来的隐患就是我们没法在第一时间感知系统异常,只能是影响到其他监控了才会有所体现,但这样就只能靠运气了。如果被影响的监控比较敏感,或许很快就会有告警;但是如果被影响的监控不敏感,或者压根没开告警,那么你就呵呵了……

7.堆外不清理

在某些业务场景下,我们可能需要使用DirectByteBuffer分配字节缓冲区,或者使用MappedByteBuffer做内存映射,那么我们不可避免地需要使用堆外内存。DirectByteBuffer对象在创建过程中会先通过Unsafe接口直接通过os::malloc来分配内存,然后将内存的起始地址和大小存到DirectByteBuffer对象里,这样就可以直接操作这些内存。我们dump内存时可能会发现DirectByteBuffer对象很小,但是其实它后面可能关联了一个非常大的堆外内存,因此我们通常称之为“冰山对象”。这些内存只有在DirectByteBuffer回收掉之后才有机会被回收,因此如果这些对象大部分都移到了old,但是一直没有触发CMS GC或者Full GC,那么悲剧将会发生,因为你的物理内存被他们耗尽了,因此为了避免这种悲剧的发生,通过-XX:MaxDirectMemorySize来指定最大的堆外内存大小,当使用达到了阈值的时候将调用System.gc来做一次full gc,以此来回收掉没有被使用的堆外内存。

另外,当前我们的使用的服务框架、第三方组件,很多都是基于netty、mina这种NIO框架实现网络调用,而这些NIO框架基本都在使用堆外内存。而我们知道,堆外内存是无法通过JVM的垃圾回收器回收的,只能通过System.gc()来回收。但是很多同学在设置JVM参数的时候,往往选择拷贝已有的配置,而忽略了一个很重要的参数DisableExplicitGC,这个参数的意思是禁用显式GC,也就是使System.gc()失效。如果我们在jvm参数中配置了-XX:+DisableExplicitGC,那么带来的后果就是进入到老年代的堆外内存无法被回收掉,最终导致OOM。

至此,一日丧命散的其中毒虫就介绍完了。如果想要活得久,请远离这七条毒虫,哈哈。

让你变成优秀程序员的几个小习惯(转)

发表于 2016-10-24 | 阅读次数

译文链接:http://www.codeceo.com/article/habits-to-be-better-programmer.html

英文原文:What little habits made you a better software engineer?

翻译作者:码农网 – 小峰

作者Jeff Standen,有着21+年经验的软件开发者。

首先开发spike解决方案——这是我早期敏捷/极限编程所养成的习惯之一。spike解决方案是一次性原型,可以帮助你在投入大量时间和精力之前验证你是否走对路。

区别就在于原型,因为你遵循这样一个规则,在你完成研究之后,你最终会扔掉“spike”代码。所以允许你偷工减料,迅速行动,因为它不会出现在产品或代码审查中。

此方法有助于迅速发现设计的哪些部位尚不明确,而不必过早地尝试架构或设计决策。

致力于小而连贯代码块的版本控制——通过类似CVS/Subversion,每次提交都直接发送到服务器。做部分文件的提交并不简单。

随着Git的出现,只提交较大文件的若干行代码变得很容易,并且可以在push到远程代码仓库之前先本地rebase/merge提交。

有一次,我在工作于更大功能的时候,采用了小型增量提交,我的工作效率直线上升。这样做能够清空我的大脑以便于面对更重要的事情。

经常写代码——最近,我正工作于:一个基于Web的企业协作和自动化平台(PHP / MySQL),一个基于云的实时指标聚合器和使用循环哈希(Node.js/ Redis)的API,一个面向iOS app商店(Swift/ SpriteKit)的棋盘游戏,专门的基于URL的cronjob可替代基于web的SaaS服务(JAVA),等等。

用过大量框架和语言有助于我的抽象和算法思维。

我从工具,如Eclipse RCP、Tapestry和Hibernate中学到了很多伟大的经验教训,并用到我的PHP项目里。尤其是在2000年初,在有Java特征的企业生态系统用于PHP存在之前。我从Unity3d/C#学到了很多关于网络和面向消息的架构。

如果我只坚持单一平台和社区的话,就永远不会知道这些概念。

编写简单的代码——我以前习惯于写复杂的代码以作为对自己的挑战。而现在的挑战是要编写优雅且简单的代码——到一种每个人都觉得他们也能做到的地步(即使他们不能)。简单代码通常来自于若干次复杂代码的迭代。

引用Antoine de Saint Exupéry的话就是:“不是没有什么可添加,而是没有什么可消减的时候,才算是达到了完美。”

这也使得我们在长时间休止之后返回项目,以及鼓励其他人参与进来变得容易多了。

最后优化——我们很容易掉入试图比用户或计算机更聪明,并且预优化各种边缘情况的陷阱。关注帕累托法则(80%的效果来自于20%的工作)。写代码,运行代码,当必要的时候专注于最大的瓶颈。这也支持保持代码库的简单。

说“不要首先优化代码”并不意味着“编写粗糙的代码”。代码总是应该精益和优雅,没有必要画蛇添足,不要将一整天的时间用在挤压剩下的10%,但其实已经能够工作良好的一些东西上。不但工作效率会下降,而且还会引进更多复杂性,解决方案变得不那么可归纳,等等。

着眼于“最重要的事情优先”——总是有一些项目领域比其他的更有趣或更具挑战性。工作于那些有趣的东西总是比工作于那些必要的东西更有诱惑。

在攻克重要部分时,将有趣部分作为一种调剂,也就是说,两者都做一点也是可以。

因此,光从这一点上说,将大的问题分解成小问题的理念是不言自明的。每个人都懂。所以,我会通过计分若干“quick wins”来开启我的一天,这能让我更有冲劲和更专注(“quick wins”可以是任何东西,包括有趣又小型的挑战),然后我会首先冲向“最重要的事情”。

了解全栈——当我刚开始干这一行的时候,没有什么比等别人做完他们那部分东西,然后我才能继续我那部分工作更糟糕的了(设计师,后端人员,前端人员,数据库人员,服务器人员,等等)。

于是,当我2000年创办自己的软件开发公司的时候,我做了一个明智的决定,那就是涉猎全栈。我知道我不可能擅长所有东西,也不可能是最后唯一对所有一切负责的人,但我想要做终端到终端的原型,因为我没有耐心看过程。

每当我需要的东西触碰到我不懂的领域时,我会研究它。于是乎,我学会了服务器管理,数据库管理,设计,前端/后端开发,云架构等。

通过了解其他领域是做什么的,我才能写出包含它们需要的代码。

当然,其中的一些要点似乎并不是所谓的“小习惯”,但我向你保证,它们是小变化历经20年时间导致的结果。重要的行为变化并没有意义,更多的是关于我是如何频繁地练习这门技术(在过去10年时间中每年大概4000-5000个小时)。

所以,我的做法更像是去回答这个问题:“什么样的小习惯会导致更糟糕的软件和低效的生产力?”,然后反过来。

作者Ed Prentice,软件工程师

时间是宝贵的,所以要尽可能地节省时间。尽可能自动化。一旦时间成为一种商品,那么你可以实现下一个伟大的创新。

使用功能强大的IDE(如vim),并将其配置能为你做尽可能多的事情。力争单个命令Build/Test/Deploy/Run。

如果你发现自己常做某件事情,那么可以让它们在一个按键下发生,或者一次点击下发生。或者更好的是,让它们自动发生。

了解键盘快捷键和UNIX命令行。几乎所有的IDE都可以让你运行复杂的编译命令,甚至任意的终端命令——不但强大,并且可以为你节省大量的时间。

提问题,然后提更多的问题。如果有什么你不明白的事情发生了,那就问为什么。然后走开,研究替代方案,并提出来。一直问问题直到你可以详细地给下一个问为什么的开发人员解释。我时常感到奇怪为什么会有这么多开发人员不知道为什么,仅仅是因为觉得“它总是/曾是这样”。

通过提供更好的替代解决方案挑战现状——并且制定步骤实现。如果你的测试不完整,或每天/每周运行一次——那么成为本地的Continuous Integration大师——目的是为了有利于你的团队,并实现它。一旦你使用它并且它可以帮助你更好工作的话,那么让你的团队也使用它。

不要只是挑战别人,挑战自己。从来没有写过web应用程序?那么写一个。从未用过Python?用Python劫持无人飞行器。

拥有一些东西。创造一些东西。没有必是非要做技术项目,可以是一个事件,例如聚会或编程马拉松,也可以是一个游戏,一个网站,一个博客。

教一些东西。Java,公开演讲,写作,下棋,vim,网球。

成为一个杰出的人。拿到一个垃圾类/组件?修复好它。编写代码的正确途径。不在代码中走捷径。做出明智的决策,向你周围的人说明为什么你要做这个决定的利弊。总是改善代码。制定不需要花费1小时的待办事项表?Just Do It。

浏览你熟悉的Stack Exchange的话题——例如你喜欢的语言。当你发现什么新的东西的时候,尽快末位淘汰相关知识。知道C语言?什么是分支预测?这篇文章会告诉你——你要做的就是学习。

浏览你不熟悉的Stack Exchange的话题——好好学习,天天向上。

学会沟通。书面文字,呈现展示,解决问题,小却激烈的小项目,大型团队,小型团队。

文档化你的所有过程。你可以回头查阅你为什么做这些事情,以及依赖原先的解决方案去解决碰到的相似问题。这还有助于捕捉你可能会忘记的思维过程和关键的信息片段。我经常通过日志来回顾前几天的工作。

在你写之前文档化你的代码。使用系统图,类层次结构,流程图表,以展示说明你的代码将如何工作。如果有人提出建议——是的,他们会提出来的——那么你可以进行修改,这比已经写好了代码再去修改要容易得多。这是另一个我很少看到有人会做但却有着最负面影响的事情。

特定化。为新的东西制作图表,向大家展示。收集尽可能多的细节。确保每个人同意这个图表。如果有人提出了建议,那就补充/添加更正到图表。保持图表更新。

知道潜意思的偏见和男性特权。了解你是哪种MBTI和人格类型,并且更重要的是,要知道如何与其他性格类型更好地互动。了解情商。每个人都是不一样的,你需要知道如何与他们进行最有效和最有建设性的交互。

定期为团队做一些事情。带饼干。教魔术。培育一点文化,并鼓励其他人也这样做。赞美其他人的贡献。一支有凝聚力的团队是很难被击败的。

学习如何与人合作。我个人非常喜欢《The Pragmatic Programmer》的“stone snop”。

理解和使用别人的代码。如果你正在实现自己的XML解析器或或csv阅读器或git hook,那么你就是在重新发明轮子。

一旦你写了代码,并且它是有效的,通过测试的,那么回过头去整理一下吧。重新运行测试。再整理。每个类都应该有单一的职责,每个函数都应该只做一件事情。在大多数情况下,函数应该小于20行代码的长度。使用自文档的函数名和变量名。花时间整理你的代码以后将会10倍地回报给你和你的团队。

参与其中。承担责任。如果事情有不对的地方,那就解决它。如果最后期限临近了又想出了一个解决方案,那让其他人尽快知道。任何人都可以做到这一点,即使是最初级的开发人员。这要求对项目的蓝图,方向和截止期限有着大局观的认识——参与进来。保存好每天的工作内容!

和团队分享学到的经验教训(在适当的时候)。指出Java中finally块内部抛出异常的时候发生了什么?和大家一起分享。

接口文档参考模板

发表于 2016-08-23 | 阅读次数

虽然提供了在线接口参数的查看,但在和客户端对接过程中,我们作为后台开发,还是需要人工提供接口文档给客户端的,这里提供一个接口文档编写的模板,以供参考,并且以我们熟悉的?service=User.GetBaseInfo为例说明如何编写高效的文档。

温馨提示:斜体字表示是注释说明。

##功能说明
对接口功能的简单说明。
获取用户的基本信息。

##接口URL
请求的相对链接和当前接口级参数,通常为?service=XXX.XXX + 公共接口参数。
/demo/?service=User.GetBaseInfo

参数说明

对当前接口级参数的说明,建议使用在线接口参数查询工具,但以下的参数说明也是需要的。

参数 名字 是否必须 说明 示例
userId 用户ID 是 表示用户的ID &user_iduser_id=1

##返回参数
对当前接口级返回参数的说明,即对{“ret”:返回状态码,”data”:”应该业务数据”,”msg”:”错误提示”}中的data部分进行说明。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{
"ret": 200,
"data": {
"code": 0, //code=0表示正确获取用户信息,code=1时表示用户不存在
"msg": "", //业务提示文案
"info": { //仅当code=0的情况下非空且有用户信息
"id": "1", //用户ID
"name": "dogstar", //用户名
"from": "oschina" //用户来源
}
},
"msg": ""
}
```


##示例
_至少应包括成功示例,失败示例可选_

###成功示例
请求:

http://phalapi.oschina.mopaas.com/Public/demo/?service=User.GetBaseInfo&user_iduser_id=1

1
返回:

{
“ret”: 200,
“data”: {
“code”: 0,
“msg”: “”,
“info”: {
“id”: “1”,
“name”: “dogstar”,
“from”: “oschina”
}
},
“msg”: “”
}
`


晋级Review归来话答辩

发表于 2016-08-22 | 阅读次数

每半年一次的晋级季结束了,这次自己团队的同学有参加晋级的,需要帮他们过PPT以及预演,同时也参加了其他同学的晋级答辩。在这过程中发现了很多问题,觉得有必要写出来总结一下了,以帮助同学们写好PPT和做好答辩工作。

PPT中的常见问题

1.没有大纲

写PPT跟上学的时候写作文有点类似,我们需要先把大纲列出来,否则写出来的内容就容易有缺失,内容也不连贯。这样的PPT在讲的时候也会让你很纠结,听众更难受。

2.前后不连贯

一个好的PPT,前后之间应该有关联,不应该彼此孤立。很多时候,我们需要通过前面的片子来引出后面的内容,也就是俗话说的“抛砖引玉”。典型的场景比如:通过项目特点/难点,引出后面的挑战/遇到的问题,最后引出如何解决。这样下来,整个PPT就会顺畅很多。

3.重要内容有遗漏

此处说的重要内容是指契合公司文化的内容,比如去哪儿来说,推崇工程师文化,那么你平时的工作中,通过工具提升工作效率,或者开发一些组件这样的事情就比较值得拿出来说。但是有些人可能觉得这些事情不重要而忽略,那么就是一种损失。

其实任何工作都是这样,你如果只是做好本职工作(自扫门前雪),这本身就是一个基本要求,你做好是应该的。但是如果你对自己要求高一些,站在整个团队的角度,或者站在整个公司的角度看待问题或者做一些事情,那么这个事情的高度就会更高一些,做出的成绩可能也会更大。所以从这一点来讲,作为工程师,在做事的态度和方式上,也应该立足高远才好。

4.文字太多

一页PPT最好不要超过30字,这是一个基本原则,希望能够尽量遵守。因为人是视觉动物,当看到密密麻麻的字的时候,首先会选择放弃。我们来看一下下面这张PPT,看到的第一眼你是什么感觉?

再来看这张:

相比上一张,是不是清爽了很多?

另外,关于PPT的文字,给大家的建议是用微软雅黑,微软雅黑无论在表现汉字还是字母数字都比较圆润,看起来比较舒服。尽量不要用宋体,因为宋体边缘较窄,会给人带来视觉上的沉重感。

5.没有图表

俗话说,一图胜千言。作为工程师,我们可以通过架构图来描述对系统的理解,通过流程图来描述对业务/流程的理解,通过类图来描述代码实现。通过监控图/表格来描述结果,这样你的PPT内容就会更加丰满,形式也更加漂亮。

6.动画太过花哨

既然我们是工程师,不是视觉设计师,所以那些花哨的动画就免了吧,毕竟这不是我们的专长,简单的淡出效果就挺好了。当然,如果你想要用动画来描述一些状态流转之类的,那么你可以去花点时间。

答辩时的常见问题

1.没有事先调试好投影

经常看见有的同学到答辩的时候才去调试投影仪,出了问题之后不得不借别人的电脑。这种事情以后还是要避免,其实只要提前做一下调试就可以了,花不了几分钟时间,但最起码可以避免现场糗个大的。

2.PPT没有预演,直接开讲

这也是很多人容易犯的错误。我们在写PPT的时候,可能会觉得思如泉涌,PPT很顺畅就做完了。但其实做完了是一回事,你能顺畅地讲出来又是一回事了,可能那时候你会发现自己的PPT怎么做的那么烂,连舌头都不灵活了…… 所以最好的方式是:写完PPT之后先给自己讲两遍,自己讲顺畅了再去给别人讲。

3.没有自我介绍

陌生人见面总是要打个招呼,做个自我介绍吧,有些同学一上来就直奔主题,一来容易搞得自己紧张,二来显得有点不礼貌。

4.紧张

这是很多人都会出现的问题,有的人紧张的连声调都变了。针对这个问题,我的建议是:1.开始讲PPT前,先做一个深呼吸;2. 站着讲。站着讲的一个好处是,你可以通过肢体动作调节自己的情绪和节奏。

5.代码没好好准备

很多同学只重视PPT,却没有认真准备代码,结果review代码的时候被虐,当然最终的结局也就不乐观。

最后,给大家推荐一本书,《乔布斯的魔力演讲》,相信这本书可以帮助你提高PPT及演讲能力。

读书笔记:微服务设计-建模

发表于 2016-08-12 | 阅读次数

1.什么样的服务是好服务

1.高内聚

说到高内聚,还是得提一下单一职责原则(Single Responsibility Principle)。在软件编程中,谁也不希望因为修改了一个功能导致其他的功能发生故障,而避免出现这一问题的方法便是遵循单一职责原则。
那如何来做到服务的高内聚呢?首先要确定问题域的边界,其次将相同边界的相关行为放在一个地方。

2.低耦合

使用微服务最重要的一点是:能够独立修改及部署单个服务而不需要修改系统的其他部分。

2.限界上下文

限界上下文(bounded context)这个概念来自于Eric Evans的《领域驱动设计》一书,Eric认为:一个给定的领域包含多个限界上下文,每个限界上下文中的东西分成2个部分,一部分不需要与外界通信,另一部分则需要。每个上下文都有明确的接口,该接口决定了它会暴露哪些模型给其他的上下文。当然本文作者更喜欢这个定义:一个由显示边界限定的特定职责。如果你想要从一个限界上下文中获取信息,或者向其发起请求,需要使用模型和它的显示边界进行通信。

应该共享特定的模型,而不应该共享内部表示,这样就可以避免潜在的紧耦合风险。

一般来讲,微服务应该清晰地和限界上下文相对应。

3.业务功能

在思考组织内的限界上下文时,不应该从共享数据的角度来考虑,应该从这些上下文能够提供的功能来考虑。首先要问自己:这个上下文是做什么用的,然后再考虑它需要什么样的数据。

建模服务时,应该将这些功能作为关键操作提供给其协作者。

4.逐步划分上下文

先识别粗粒度上下文,然后当发现合适的缝隙后,再进一步划分出那些嵌套的上下文。

对于嵌套上下文,一种方式是嵌套上下文不直接对外可见,另外一种方式是限界上下文被提升到顶层上下文的层次。这两种方式都具有其合理性,具体还要看组织机构的划分。比如服务A、服务B、服务C分别由不同团队维护,那么可能大家倾向于成为顶层上下文;如果由一个团队维护,那么嵌套架构会更合理。另外,从测试的角度,嵌套式上下文更便于测试,毕竟它对外暴露的服务更少。

另外一本书推荐:Vaughn Vernon的《实现领域驱动设计》,这本书貌似卖得更好。

图片说明:稻城-亚丁

稻城—亚丁因其独特而原始的自然环境、美丽风景被誉为“最后的香格里拉”,景点分为稻城和亚丁两部分,有雪山、冰川、峡谷、森林、草甸、湖泊等景观。除了珍珠海、牛奶海等冰川湖泊,稻城最令人向往的是驰名藏区的雪域神峰——稻城神峰,它由仙乃日、央迈勇、夏诺多吉三座神山组成,均位于亚丁自然保护区。人们把仙乃日比作大佛、把央迈勇比作少女,将夏诺多吉当作少年,三座神山就像三尊圣灵,这里也就成了藏民心中朝圣的圣地。

稻城-亚丁

稻城-亚丁

稻城-亚丁

关键业务系统的JVM启动参数推荐2.0版[转]

发表于 2016-08-07 | 阅读次数

注:本文转自SpringSide的作者江南白衣的博客“关键业务系统的JVM启动参数推荐 2.0版”,如需转载,请尊重作者权利,注明原文出处,谢谢。

在关键的业务系统里,除了继续追求技术人员最爱的高吞吐与低延时之外,系统的稳定性与出现问题时排查的便捷性也很重要。

这是本文的一个原则,后面也会一次又一次的强调,所以与网上其他的文章略有不同,请调优高手和运维老大们多指引。

更新记录:

2.0版,增加 -XX:+PerfDisableSharedMem,GC日志指向/dev/shm,避免IO造成的JVM停顿。

前言1,资料

学习开源项目的启动脚本是个不错的主意,比如Cassandra家的, 附送一篇解释它的文章。

JVM调优的”标准参数”的各种陷阱 R大的文章,在JDK6时写的,期待更新。

偶然翻到Linkedin工程师的一篇文章。

更偶然翻到的一份不错的参数列表。

前言2, -XX:+PrintFlagsFinal打印参数值

当你在网上兴冲冲找到一个可优化的参数时,先用-XX: +PrintFlagsFinal看看,它可能已经默认打开了,再找到一个,还是默认打开了…

JDK7与JDK8,甚至JDK7中的不同版本,有些参数值都不一样,所以不要轻信网上任何文章,一切以生产环境同版本的JDK打出来的为准。

经常以类似下面的语句去查看参数,偷懒不起应用,用-version代替。有些参数设置后会影响其他参数,所以查看时也把它带上。

java -server -Xmx1024m -Xms1024m -XX:+UseConcMarkSweepGC -XX:+PrintFlagsFinal -version| grep ParallelGCThreads

前言3,关于默认值

JDK8会默认打开-XX:+TieredCompilation多层编译,而JDK7则不会。JDK7u40以后的版本会默认打开-XX:+OptimizeStringConcat优化字符串拼接,而之前的则不打开。

对于这些参数,我的建议是顺势而为,JDK在那个版本默认打开不打开总有它的理由。安全第一,没有很好的因由,不要随便因为网上某篇文章的推荐(包括你现在在读的这篇)就去设置。

1. 性能篇

先写一些不那么常见的,后面再来老生常谈。

1.1 取消偏向锁 -XX:-UseBiasedLocking

JDK1.6开始默认打开的偏向锁,在没有竞争的情况下,会取消线程同步的原语,比如那个所有方法都挂着synchronized关键字的StringBuffer,如果始终只有一条线程在访问它,就略过同步操作以获得性能提升。

但一旦有第二条线程访问这把锁,JVM就要撤销偏向锁恢复到未锁定线程的状态,用”-XX:+PrintSafepointStatistics -XX :P rintSafepointStatisticsCount=1” 可以看到不少RevokeBiasd的纪录,像GC一样,会Stop The World的干活,虽然只是很短很短的停顿,但对于多线程并发的应用,取消掉它反而有性能的提升和延时的极微的缩短,所以Cassandra就取消了它。

1.2 启动时访问并置零内存页面-XX:+AlwaysPreTouch

启动时就把参数里说好了的内存全部舔一遍,可能令得启动时慢上一点,但后面访问时会更流畅,比如页面会连续分配,比如不会在晋升新生代到老生代时才去申请页面使得GC停顿时间加长。不过这选项对32G之类的大堆才会更有感觉一点。

1.3 -Djava.security.egd=file:/dev/./urandom

UUID.randomUUID() 有时候会很慢,Thread Dump一看居然被锁住了,原因是里面用了SecureRandom,要等待机器产生新的噪音(比如机器里的某个文件发生了变化)才肯产生新的随机数。因此最好让熵池里没有新的噪音因子时重用当前的因子。详见 JVM上的随机数与熵池策略

1.4 -XX:AutoBoxCacheMax=20000

Integer i = 3;这语句有着 int自动装箱成Integer的过程,JDK默认只缓存 -128 ~ +127的int 和 long,超出范围的数字就要即时构建新的Integer对象。设为20000后,我们应用的QPS从48,000提升到50,000,足足4%的影响。详见Java Integer(-128~127)值的==和equals比较产生的思考

1.5 -XX:+PerfDisableSharedMem

Cassandra家的一个参数,一直没留意,直到发生高IO时的JVM停顿。原来每次进入安全点(比如GC), JVM都会默默的在/tmp/hperf 目录写上一点statistics数据,如果刚好遇到PageCache刷盘,把文件阻塞了,就不能结束这个Stop the World的安全点了。用此参数可以禁止JVM写statistics数据,代价是VisualVM和jstat用不了,只能用JMX取数据,但在生产环境本来就不需要VisaulVM。详见The Four Month Bug: JVM statistics cause garbage collection pauses

1.6 不建议的参数

-XX:+AggressiveOpts是一些还没默认打开的优化参数集合, -XX:AutoBoxCacheMax是其中的一项。但如前所述,关键系统里不建议打开。虽然通过-XX:+AggressiveOpts 与 -XX:-AggressiveOpts 的对比,目前才改变了三个参数,但为免以后某个版本的JDK里默默改变更多激进的配置,还是不要了。

Linkined那种黑科技,先要解锁VMOptions才能配置的就更不用说了,比如

-XX:+UnlockDiagnosticVMOptions -XX: ParGCCardsPerStrideChunk=32768

JIT Compile相关的参数,函数调用多少次之后开始编译的阀值,内联函数大小的阀值等等,不要乱改了。

-XX:+UseFastAccessorMethods,据说在多层编译下还慢了,所以是默认关闭的。

1.7 -server

-server 与 -client的JVM默认参数完全不一样,虽然在Linux 64位JVM里默认会被认成server模式,但还是顺手写上吧。

1.8 GC策略

为了稳健,还是8G以下的堆还是CMS好了,G1的细节实现起来难度太大,从理论提出到现在都做了六七年了。

CMS真正可设的东西也不多,详见JVM实用参数(七)CMS收集器

-XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly

因为我们的监控系统会通过JMX监控内存达到90%的状况(留点处理的时间),所以设置让它75%就开始跑了,早点开始也能避免Full GC等意外情况(概念重申,这种主动的CMS GC,和JVM的老生代、永久代、堆外内存完全不能分配内存了而强制Full GC是不同的概念)。为了让这个设置生效,还要设置-XX:+UseCMSInitiatingOccupancyOnly,否则75只被用来做开始的参考值,后面还是JVM自己算。

-XX:MaxTenuringThreshold=2,这是GC里改动效果最明显的一个参数了。对象在Survivor区熬过多少次Young GC后晋升到年老代,JDK7里看起来默认是6,跑起来好像变成了15。

Young GC是最大的应用停顿来源,而新生代里GC后存活对象的多少又直接影响停顿的时间,所以如果清楚Young GC的执行频率和应用里大部分临时对象的最长生命周期,可以把它设的更短一点,让其实不是临时对象的新生代长期对象赶紧晋升到年老代,别呆着。

用-XX:+PrintTenuringDistribution观察下,如果后面几代都差不多,就可以设小,比如JMeter里是2。而我们的两个系统里一个设了2,一个设了6。

-XX:+ExplicitGCInvokesConcurrent, 但不要-XX:+DisableExplicitGC, 比如Netty之堆外内存扫盲篇,可见禁了system.gc() 未必是好事,只要自己的代码里没有调它,也没用什么特别烂的类库,真有人调了总有调的原因。-XX+ExplicitGCInvokesConcurrent 则在full gc时,并不全程停顿,依然只在ygc和两个remark阶段停顿,详见JVM源码分析之SystemGC完全解读

-XX: ParallelRefProcEnabled , 默认为false,并行的处理Reference对象,如WeakReference,除非在GC log里出现Reference处理时间较长的日志,否则效果不会很明显,但我们总是要JVM尽量的并行,所以设了也就设了。

1.9 GC里不建议设的参数

-XX:+CMSClassUnloadingEnabled,在CMS中清理永久代中的过期的Class而不等到Full GC,JDK7默认关闭而JDK8打开。看自己情况,比如有没有运行动态语言脚本如Groovy产生大量的临时类。它会增加CMS remark的暂停时间,所以如果新类加载并不频繁,这个参数还是不开的好。

用了CMS,新生代收集默认就是-XX:+UseParNewGC,不用自己设。

并发收集线程数,ParallelGCThreads=8+( Processor - 8 ) ( 5/8 ), ConcGCThreads = (ParallelGCThreads + 3)/4,比如双CPU,六核,超线程就是24个处理器,小于8个处理器时ParallelGCThreads按处理器数量,大于时按上述公式ParallelGCThreads=18, ConcGCThreads=5。这线程数调整了变化也不大,还是别乱动了。

-XX:+CMSScavengeBeforeRemark,默认为关闭,在CMS remark前,先执行一次minor GC将新生代清掉,这样从老生代的对象引用到的新生代对象的个数就少了,停止全世界的CMS remark阶段就短一些。如果看到GC日志里remark阶段的时间超长,可以打开此项看看有没有效果,否则还是不要打开了,白白多了次GC。

-XX:CMSFullGCsBeforeCompaction,默认为0,即每次full gc都对老生代进行碎片整理压缩。Full GC 不同于 前面设置的75%老生代时触发的CMS GC,只在System.gc(),老生代达到100%,老生代碎片过大无法分配空间给新晋升的大对象这些特殊情况里发生,所以设为每次都进行碎片整理是合适的,详见此贴里R大的解释。

1.10 内存大小的设置

这些关于大小的参数,给人感觉是最踏实可控的。

其实JVM除了显式设置的-Xmx堆内存,还有一堆其他占内存的地方(堆外内存,线程栈,永久代,二进制代码cache),在容量规划的时候要留意。

关键业务系统的服务器上内存一般都是够的,所以尽管设得宽松点。

-Xmx, -Xms, 堆内存大小,2~4G均可,再大了GC时间会拖长。

-Xmn or -XX:NewSize and -XX:MaxNewSize or -XX:NewRatio, JDK默认新生代占堆大小的1/3, 个人喜欢把对半分, 增大新生代的大小,能减少GC的频率(但也会加大每次GC的停顿时间),主要是看老生代里没多少长期对象的话,占2/3太多了。可以用-Xmn 直接赋值(等于-XX:NewSize and -XX:MaxNewSize同值的缩写),或把NewRatio设为1来对半分(但如果想设置新生代比老生代大就只能用-Xmn)。

-XX: PermSize=128m -XX:MaxPermSize=512m (JDK7)现在的应用有Hibernate/Spring这些闹腾的家伙AOP之后类都比较多,可以一开始就把初始值从64M设到128M,并设一个更大的Max值以求保险。

-XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=512m(JDK8),JDK8的永生代几乎可用完机器的所有内存,同样设一个128M的初始值,512M的最大值保护一下。

1.11 其他内存大小等可选设置

-XX:SurvivorRatio 新生代中每个存活区的大小,默认为8,即1/10的新生代 1/(SurvivorRatio+2),有人喜欢设小点省点给新生代,但要避免太小使得存活区放不下临时对象而要晋升到老生代,还是从GC Log里看实际情况了。

-Xss 在堆之外,线程占用栈内存,默认每条线程为1M(以前是256K)。除了方法调用出参入参的栈,逃逸分析后也会把只在该线程里可见的对象直接分配在线程栈里,而不是公共的Heap里,也就减少了新生代的GC频率。有人喜欢设小点节约内存开更多线程,但反正内存够也就不必要设小,有人喜欢再设大点。

-XX:MaxDirectMemorySize,堆外内存/直接内存的大小,默认为Heap区总内存减去一个Survivor区的大小,详见Netty之堆外内存扫盲篇。

-XX:ReservedCodeCacheSize, JIT编译后二进制代码的存放区,满了之后就不再编译。JDK7默认不开多层编译48M,开了96M,而JDK8默认开多层编译256M。可以在JMX里看看CodeCache的大小,JDK7下的48M一般够了,也可以把它设大点,反正内存多。

2. 监控篇

JVM输出的各种日志,如果未指定路径,通常会生成到运行应用的相同目录,为了避免有时候在不同的地方执行启动脚本,一般将日志路径集中设到一个固定的地方。

2.1 -XX:+PrintCommandLineFlags

运维有时会对启动参数做一些临时的更改,将每次启动的参数输出到stdout,将来有据可查。
打印出来的是命令行里设置了的参数以及因为这些参数隐式影响的参数,比如开了CMS后,-XX:+UseParNewGC也被自动打开。

2.2 -XX:-OmitStackTraceInFastThrow

为异常设置StackTrace是个昂贵的操作,所以当应用在相同地方抛出相同的异常N次(两万?)之后,JVM会对某些特定异常如NPE,数组越界等进行优化,不再带上异常栈。此时,你可能会看到日志里一条条Null Point Exception,而真正输出完整栈的日志早被滚动到不知哪里去了,也就完全不知道这NPE发生在什么地方,欲哭无泪。 所以,将它禁止吧。

2.3 coredump与 -XX:ErrorFile

JVM crash时,hotspot 会生成一个error文件,提供JVM状态信息的细节。如前所述,将其输出到固定目录,避免到时会到处找这文件。文件名中的%p会被自动替换为应用的PID

-XX:ErrorFile=${MYLOGDIR}/hs_err_%p.log

当然,更好的做法是生成coredump,从CoreDump能够转出Heap Dump 和 Thread Dump 还有crash的地方,非常实用。

在启动脚本里加上 ulimit -c unlimited或其他的设置方式,如果有root权限,用一下一下输出目录更好

echo "/{MYLOGDIR}/coredump.%p" > /proc/sys/kernel/core_pattern

什么?你不知道这东西什么用?看来你是没遇过JVM Segment Fault的幸福人。

2.4 -XX:+HeapDumpOnOutOfMemoryError

在Out Of Memory,JVM快死快死掉的时候,输出Heap Dump到指定文件。不然开发很多时候还真不知道怎么重现错误。

路径只指向目录,JVM会保持文件名的唯一性,叫java_pid${pid}.hprof。如果指向文件,而文件已存在,反而不能写入。

-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=${LOGDIR}/

2.5 GC日志

-Xloggc:/dev/shm/gc-myapplication.log -XX:+PrintGCDateStamps -XX:+PrintGCDetails

详见JVM实用参数(八)GC日志,有人担心写GC日志会影响性能,但测试下来实在没什么影响,还是留一份用来排查好。

到后来,又发现如果遇上高IO的情况,如果GC的时候,操作系统正在flush pageCache 到磁盘,也可能导致GC log文件被锁住,从而让GC结束不了。所以把它指向了/dev/shm 这种内存中数据库,避免这种停顿,详见Eliminating Large JVM GC Pauses Caused by Background IO Traffic

另外还有一个-XX:+PrintGCApplicationStoppedTime -XX:+PrintGCApplicationConcurrentTime,它的名字没起好,它除了打印清晰的GC停顿时间外,还可以打印其他的停顿时间,比如取消偏向锁,class 被agent redefine,code deoptimization等等,有助于发现一些原来没想到的问题,建议也加上。如果真的发现了一些不知什么的停顿,再加上”-XX:+PrintSafepointStatistics -XX: PrintSafepointStatisticsCount=1” 找原因。

GC日志默认会在重启后清空,但有人担心长期运行不重启的应用会把文件弄得很大,有”-XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10 -XX:GCLogFileSize=1M”的参数可以让日志滚动起来。但重启后的文件名太混乱太让人头痛,所以还是不加。

2.6 JMX

-Dcom.sun.management.jmxremote.port=${MY_JMX_PORT} -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Djava.rmi.server.hostname=127.0.0.1

以上设置,只让本地的Zabbix之类监控软件通过JMX监控JVM,不允许远程访问。

《关键业务系统的JVM启动参数推荐》,转载请保留链接。

谢谢看到这里,还是贴个图吧。

有关的…

2016-07-30 – 从dstat理解Linux性能监控体系
2016-04-29 – Java应用调优之-目标与资料篇
2016-04-28 – 另一份Java应用调优指南之-工具篇
2016-01-01 – 另一份Java应用调优指南之-前菜

读书笔记:微服务设计-入门

发表于 2016-07-30 | 阅读次数

前言

该系列笔记的内容主要来源于人邮出版的《微服务设计》一书。

第一章 微服务

微服务的由来

微服务不是发明出来的,而是随着领域驱动设计、持续交付、按需虚拟化、基础设施自动化、小型自治团队、大型集群系统这些实践的流行,从现实世界总结出来的。

拓展知识点:Eric Evans的《领域驱动设计》、Alistair Cookburn的六边形架构理论、Netflix的构建大型反脆弱系统

什么是微服务

  1. 微服务很小
    在考虑微服务时,内聚性这一原则很重要,具体可以联想到单一职责原则(Single Responsibility Principle)。
    微服务的边界如何划分?答案是根据业务的边界来确定服务的边界。
    至于很小,多小算是小?这个事情仁者见仁智者见智,有的人说花2周的时间能够构建完就可以,有的人说你认为已经够小了就行,我的理解是:只要边界小到能够提供独立的服务就行了。

  2. 微服务是自治的
    一个微服务就是一个独立的实体,一个微服务的部署或修改应该尽量避免对消费方的修改,一个很典型的例子就是有人在设计接口返回值的时候用枚举,这其实很坑的。

微服务的好处

1.技术异构性

服务端和消费端可以采用不同的技术。如果把dubbo看做一个微服务框架,那么它在技术异构性方面做得并不好。当然也有人对dubbo进行了改良,比如当当的dubbox,它在dubbo协议基础上增加了REST接口,这样消费方就可以使用其他协议与dubbo进行交互了。

2.弹性

弹性工程学的一个关键概念就是“舱壁”,比如轮船的水密舱。当一个舱进水时,通过关闭该舱来确保其它舱不进水,进而保证整条船的安全。在微服务中,一个服务A可能会消费下游的很多服务,如果下游的某个服务有问题,那么它不能影响A服务自身的运行的。

3.扩展

对于单块服务来讲,即便是系统中某一个服务存在性能问题,那么也需要对整个系统进行扩展。如果使用较多的小服务,那么你只需要对存在性能问题的服务进行扩展就行了。

4.简化部署

对于线上服务来说,相对于代码量巨大的单块系统,微服务具有灵活的部署优势和更小的部署风险。这里引申出一个比较重要的部署原则就是:每次部署应尽量少地修改代码,这样才能减少两次部署之间的差异。

5.与组织机构更加匹配

说到系统与组织机构的关系,就不能不提康威定律。梅尔.康威在1968年4月的Datamation杂志上发表的一篇名为“How Do Committees Invent”的论文中提出:任何组织在设计一套系统时,所交付的设计方案在结构上都与该组织的沟通结构保持一致。
这里暂时不对康威定律做过多描述,但最起码有一点是肯定的,相比于单体应用,为服务能够更好地在团队之间交接,尤其那些业务、团队频繁变动的公司。

6.可组合性

相比单体应用,为服务能够更方便地组合成新的微服务,这一点的思路跟乐高倒是有点相像。

7.可替代性

一个规模比较大的单体应用,相信很少有人敢替代它。但对于一个规模比较小的微服务来讲,重新实现或者删除这个服务的可行性就大大增加了。

微服务的不足

《微服务设计》这本书里面只是提到了微服务的好处,但是并没有提到微服务有哪些不足和挑战,这里自己做一些总结。

1.维护压力

首先从服务部署的角度,无论是单机多服务,还是单机单服务,都会运维量的增加。单机多服务意味着服务维护复杂性的增加。比如服务器load高了,那到底是哪个服务引起的呢?单机单服务意味着服务器数量的增加。
另外,微服务之间存在着很强的关联关系,当系统出现异常时,很有可能是因为下游的系统异常导致的,这就需要我们做好服务的监控和治理,甚至问题排查。因此,一旦你开始引入微服务,那么你就要在最开始的时候好好规划服务的治理。

2.重复性劳动

重复性劳动是指你可能无法更多地像单体应用那样引用部分代码,毕竟服务之间是隔离的。当然你也可以通过抽取lib包这样的方式在微服务之间做到代码复用,但是那样就需要微服务的团队之间具有有效的沟通机制。

3.系统运行效率

微服务之间的交互是通过网络进行的,因此网络的效率、稳定性就变得更加重要。在单体应用中,我们不必担心模块之间交互的数据量、效率,但是在微服务中,服务与服务之间的效率将至关重要。

4.资源浪费

一般来说,微服务推荐的部署方式是单机单服务,这也意味着更多的资源浪费。毕竟我们需要为操作系统、其他基础设施预留一些内存。在一个单体应用中,这些内存就可以共用。

图片描述:人间净土-喀纳斯

喀纳斯_卧龙湾

喀纳斯_禾木

1…3456
宁静·致远

宁静·致远

60 日志
9 标签
友情链接
  • 卡拉搜索
© 2020 宁静·致远
由 Hexo 强力驱动
主题 - NexT.Gemini