微服务架构之容错Hystrix

文章来源:http://www.liangsonghua.me作者介绍:京东资深工程师-梁松华,长期关注稳定性保障、敏捷开发、JAVA高级、微服务架构 一、容错的必要性假设单体应用可用率为99.99%,即使拆分后每个微服务的可用率还是保持在99.99%,总体的可用率还是下降的。因为凡是依赖都可能会失败,凡是资源都是有限制的,另外网络并不可靠。有可能一个很不起眼的微服务模块高延迟最后导致整体服务不可用 二、容错的基本模块1、主动超时,一般设置成2秒或者5秒超时时间2、服务降级,一般会降级成直接跳转到静态CDN托底页或者提示活动太火爆,以免开天窗3、限流,一般使用令牌机制限制最大并发数4、隔离,对不同依赖进行隔离,容器CPU绑核就是一种隔离措施5、弹性熔断,错误数达到一定阀值后,开始拒绝请求,健康检查发现恢复后再次接受请求三、Hystrix主要概念Hystrix流程 想要使用Hystrix,只需要继承HystrixCommand或者HystrixObservableCommand并重写业务逻辑方法即可,区别在于HystrixCommand.run()返回一个结果或者异常,HystrixObservableCommand.construct()返回一个Observable对象 编者按:关于反应式编程可参考文章Flux反应式编程结合多线程实现任务编排 Hystrix真正执行命令逻辑是通过execute()、queue()、observe()、toObservable()的其中一种,区别在于execute是同步阻塞的,queue通过myObservable.toList().toBlocking().toFuture()实现异步非阻塞,observe是事件注册前执行,toObservable是事件注册后执行,后两者是基于发布和订阅响应式的调用 每个熔断器默认维护10个bucket,每秒一个bucket,每个bucket记录成功,失败,超时,拒绝的状态,默认错误超过50%且10秒内超过20个请求才进行中断拦截。当断路器打开时,维护一个窗口,每过一个窗口时间,会放过一个请求以探测后端服务健康状态,如果已经恢复则断路器会恢复到关闭状态 当断路器打开、线程池提交任务被拒绝、信号量获得被拒绝、执行异常、执行超时任一情况发生都会触发降级fallBack,Hystrix提供两种fallBack方式,HystrixCommand.getFallback()和HystrixObservableCommand.resumeWithFallback() 四、线程和信号量隔离 1、线程隔离,针对不同的服务依赖创建线程池2、信号量隔离,本质是一个共享锁。当信号量中有可用的许可时,线程能获取该许可(seaphore.acquire()),否则线程必须等待,直到有可用的许可为止。线程用完必须释放(seaphore.release())否则其他线程永久等待 五、Hystrix主要配置项 六、使用1、请求上下文,下面将要提到的请求缓存、请求合并都依赖请求上下文,我们可以在拦截器中进行管理 public class HystrixRequestContextServletFilter implements Filter { public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HystrixRequestContext context = HystrixRequestContext.initializeContext(); try { chain.doFilter(request, response); } finally { context.shutdown(); } }}2、请求缓存,减少相同参数请求后端服务的开销,需要重写getCacheKey方法返回缓存key public class CommandUsingRequestCache extends HystrixCommand<Boolean> { private final int value; protected CommandUsingRequestCache(int value) { super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup")); this.value = value; } @Override protected Boolean run() { return value == 0 || value % 2 == 0; } @Override protected String getCacheKey() { return String.valueOf(value); }}3、请求合并。请求合并在Nginx静态资源加载中也很常见,Nginx使用的是nginx-http-concat扩展模块。但是在Hystric中请求合并会导致延迟增加,所以要求两者启动执行间隔时长足够小,减少等待合并的时间,超过10ms间隔不会自动合并 ...

July 12, 2019 · 2 min · jiezi

浅析分布式事务中的2PC和3PC

分布式事务大型的分布式系统架构都会涉及到事务的分布式处理的问题,基本来说,分布式事务和普通事务都有一个共同原则: A(Atomic) 原子性,事务要么一起完成,要么一起回滚C(Consistent) 一致性,提交数据前后数据库的完整性不变I(Isolation) 隔离性,不同的事务相互独立,不互相影响D(Duration)持久性,数据状态是永久的另外一个设计原则,分布式系统要符合以下原则: C(Consistent)一致性,分布式系统中存在的多个副本,在数据和内容上要一模一样A(Availability)高可用性,在有限时间内保证系统响应P(Partition tolerance)分区容错性,一个副本或一个分区出现宕机或其他错误时,不影响系统整体运行由于在设计分布式系统时,难以保证CAP全部符合,最多保证其中两个,例如在一个分布式系统中:要保证系统数据库的强一致性,需要跨表跨库占用数据库资源,在复杂度比较高的情况下,耗时难以保证,就会出现可用性无法保证的情况。 故而又出现了BASE理论 Basic Available基本可用,分布式系统在出现故障时,允许损失部分可用功能,保证核心功能可用Soft state柔性状态,允许系统存在中间状态,这个状态不影响系统可用性Eventually Consistent 最终一致性 指经过一段时间之后,所有数据状态保持一致的结果实际上BASE理论是基于AP的一个扩展,一个分布式事务不需要强一致性,只需要达到一个最终一致性就可以了。 分布式事务就是处理分布式中各个节点的数据操作,使之能够在节点相互隔离的情况下完成多个节点数据的一致性操作。 分步式事务 - 2PC2PC即二阶段提交,在这里需要了解两个概念 参与者:参与者即各个实际参与事务的节点,这些节点相互隔离,彼此不知道对方是否提交事务。协调者:收集和管理参与者的事务信息,负责统一管理节点信息,也就是分布式事务中的第三方。准备阶段,也称投票阶段准备阶段中,协调者向参与者发送precommit的消息,参与者在本地执行事务逻辑,如果没有超时以及运行无误,那么会记录redo和undo日志,将ack信息发送给协调者,当协调者收到所有节点参与者发送的ack信息时,准备进入下一阶段,否则会发送回滚信息给各个节点,各个节点根据之前记录好的undo信息回滚,本次事务提交失败。 提交阶段提交阶段中,协调者已经收到所有节点的应答信息,接下来发送commit消息给各个节点,通知各个节点参与者可以提交事务,各个参与者提交完毕后一一发送完成信息给协调者,并释放本地占用的资源,协调者收到所有完成消息后,完成事务。 二阶段提交的图示如下: 二阶段提交的问题二阶段提交虽然能够解决大多数的分布式事务的问题,且发生数据错误的概率极小,但仍然有以下几个问题: 单点故障:如果协调者宕机,那么参与者会一直阻塞下去,如果在参与者收到提交消息之前协调者宕机,那么参与者一直保持未成功提交的状态,会一直占用资源,导致同时间其他事务可能要一直等待下去。同步阻塞问题:因为参与者是在本地处理的事务,是阻塞型的,在最终commit之前,一直占用锁资源,如果遇到时间较长而协调者未发回commit的情况,那么会导致锁等待。数据一致性问题:假如协调者在第二阶段中,发送commit给某些参与者失败,那么就导致某些参与者提交了事务,而某些没有提交,这就导致了数据不一致。二阶段根本无法解决的问题:假如协调者宕机的同时,最后一个参与者也宕机了,当协调者通过重新选举再参与到事务管理中时,它是没有办法知道事务执行到哪一步的。针对于以上提出的一些问题,衍生出了3PC。 分布式事务 - 3PC3PC即三阶段提交协议。 3PC相对于2PC,有了以下变化: 引入超时机制,在参与者和协调者中都加入了各自的超时策略。加入了一个新的阶段,canCommit,所以阶段分成了三个:canCommit、PreCommit、DoCommit。CanCommit这是一个准备阶段,在这一阶段中,协调者向参与者发送CanCommit消息,参与者接收后,根据自身资源情况判断是否可以执行事务操作,如果可以并且未超时,则发送yes给协调者,反之,协调者会中断事务。 PreCommit这是预备阶段,在这一阶段中,协调者向参与者发送PreCommit消息,参与者接收后,会执行事务操作,记录undo和redo日志,事务执行完毕后会将Ack消息发送给协调者,协调者在未超时的情况下收集所有参与者的信息,否则中断事务,通知所有参与者Abort消息。 DoCommit当所有参与者Ack消息完毕之后,协调者会确认发送DoCommit消息给每一个参与者,执行提交事务。原则上所有参与者执行提交完毕之后,需要发送Committed给协调者,协调者完成事务。否则协调者中断事务,参与者接收abort消息会根据之前记录的undo回滚。但也要注意,此阶段中如果参与者无法及时收到协调者发来的Docommit消息时,也会自行提交事务,因为从概率上来讲,PreCommit这个阶段能够不被abort说明全部节点都可以正常执行事务提交,所以一般来讲单个节点提交不影响数据一致性,除非极端情况。 2PC 和 3PC的区别相对于2PC,3PC主要解决的单点故障问题,并减少阻塞,因为一旦参与者无法及时收到来自协调者的信息之后,他会默认执行commit。而不会一直持有事务资源并处于阻塞状态。但是这种机制也会导致数据一致性问题,因为,由于网络原因,协调者发送的abort响应没有及时被参与者接收到,那么参与者在等待超时之后执行了commit操作。这样就和其他接到abort命令并执行回滚的参与者之间存在数据不一致的情况。 消息中间件上面提到的关于协调者在实践过程中由谁来担任是个问题,一般来讲这个协调者是异步的,可以在参与者和协调者之间双向通信的,有良好的消息传递机制,能够保证消息稳定投递。故而一般协调者的担任者是高性能的消息中间件例如RocketMq、Kafka、RabbitMq等。 如图就是一个2PC的实践:

July 12, 2019 · 1 min · jiezi

微服务架构之网关层Zuul剖析

文章来源:http://www.liangsonghua.me作者介绍:京东资深工程师-梁松华,长期关注稳定性保障、敏捷开发、微服务架构 一、Zuul简介Zuul相当于是第三方调用和服务提供方之间的防护门,其中最大的亮点就是可动态发布过滤器 二、Zuul可以为我们提供什么1、权限控制2、预警和监控3、红绿部署、(粘性)金丝雀部署,流量调度支持4、流量复制转发,方便分支测试、埋点测试、压力测试5、跨区域高可用,异常感知6、防爬防攻击7、负载均衡、健康检查和屏蔽坏节点8、静态资源处理9、重试容错服务三、Zuul网关架构 可以看到其架构主要分为发布模块、控制管理加载模块、运行时模块、线程安全的请求上下文模块。在Spring Cloud中,Zuul每个后端都称为一个Route,为了避免资源抢占,整合了Hystrix进行隔离和限流,基于线程的隔离机制,另外一种机制是信号量,后面文章会提到。Zuul默认使用ThreadLocal protected static final ThreadLocal<? extends RequestContext> threadLocal = new ThreadLocal<RequestContext>() { @Override protected RequestContext initialValue() { try { return contextClass.newInstance(); } catch (Throwable e) { e.printStackTrace(); throw new RuntimeException(e); } }}; 请求处理生命周期,”pre” filters(认证、路由、请求日记)->”routing filters”(将请求发送到后端)->”post” filters(增加HTTP头、收集统计和度量、客户端响应) 四、过滤器一些概念 1、类型Type,定义被运行的阶段,也就是preroutingposterror阶段2、顺序Execution Order,定义同类型链执行中顺序3、条件Criteria,定义过滤器执行的前提条件4、动作Action,定义过滤器执行的业务下面是一个DEMO class DebugFilter extends ZuulFilter { static final DynamicBooleanProperty routingDebug = DynamicPropertyFactory.getInstance().getBooleanProperty(ZuulConstants.ZUUL_DEBUG_REQUEST, true) static final DynamicStringProperty debugParameter = DynamicPropertyFactory.getInstance().getStringProperty(ZuulConstants.ZUUL_DEBUG_PARAMETER, "d") @Override String filterType() { return 'pre' } @Override int filterOrder() { return 1 } boolean shouldFilter() { if ("true".equals(RequestContext.getCurrentContext().getRequest().getParameter(debugParameter.get()))) { return true } return routingDebug.get(); } Object run() { RequestContext ctx = RequestContext.getCurrentContext() ctx.setDebugRouting(true) ctx.setDebugRequest(true) ctx.setChunkedRequestBody() return null; }五、代码剖析在Servlet API 中有一个ServletContextListener接口,它能够监听 ServletContext 对象的生命周期,实际上就是监听 Web 应用的生命周期。接口中定义了两个方法 ...

July 11, 2019 · 3 min · jiezi

9012年论数字技术核聚变下打开中台的正确姿势

9012年了,不搞点数字化转型升级,似乎KPI没有亮点。但你说,服务拆分成几百个,遇到问题反而不好排查,还搞个锤子微服务;你又说,Hadoop那么难用,三大豪门(Cloudera、Hortonworks、MapR)都凉了,还搞个锤子大数据。然而,618、双11你买买买已经没有系统响应压力,个性化音乐推荐带给你惊喜不断,“大数据杀熟”也曾“精准”得让你咬牙切齿……这就是数字化如火如荼的背景下,实践层面的一些悖论。究竟如何作为,如何才能驯服数字技术,掌舵业务创新?关注技术核聚变,做好业务中台与数据中台,这就是2019网易云创峰会(yc.163yun.com)即将给出的答案。 技术核聚变引领变革以史为鉴,在历史长河中,人类经历了漫长的农业社会,多数人食不果腹。从人类发明计算机试图超越人脑计算的极限,到AI已经开始跟你抢饭碗,不过短短的几十年,但这正是科技大爆炸的几十年,云计算、微服务、大数据、人工智能、物联网……层出不穷的新名词,无怪乎有人将IT界与时尚圈相提并论。但不可否认,当前的新模式、新业态,靠的就是这些黑科技。 商业成功需要的,并不是将这些新技术简单堆叠。电影《国产007》中,文西研制的“集合十种杀人武器于一身”的超级武器——“要你命3000”,是导演给我们的一个笑料,但007最终解决问题的刀子,其实可以视为“要你命3000”的一个部件,只是这个部件在文西的平台上不能发威。同样,新技术只有以正确的方式融合,才能真正释放产业的潜能,助推产业升级。 成功应用各种高新技术的企业中,网易非常值得研究。据网易副总裁、网易杭州研究院执行院长汪源此前透露,网易从2006年就开始研究分布式存储、分布式计算,从2012年开始就将人工智能技术用于音乐推荐。如今,网易的技术体系支撑着电商、游戏、传媒、文创、音乐等多个领域的不同业务。 因为经历过,所以懂得。因为业务广泛,所以接地气。创新、匠心是网易给自己贴的两个标签。创新,使网易得以紧跟先进技术的步伐;匠心,使技术研发让贴近应用,做成先驱而不是先烈。而汪源本人,一手打造网易基础技术体系,掌握最新技术演变;一手掌舵网易云,倾听客户声音,时刻理解业务诉求。 汪源认为,正是数字技术核聚变产生的能量,驱动着网易互联网业务的快速发展。网易云推出的产品,从场景化云服务,到轻舟微服务、网易大数据,到工业智能平台,到业务中台、数据中台,依稀可以见到技术核聚变的影子。当然,技术核聚变具体如何发挥作用,还要等待网易官方的解读。 全链路中台是集大成者从网易云创峰会官网推测,中台技术应该是技术核聚变的核心元素。 以Hadoop为例,谈到Hadoop的窘境,汪源表示:Hadoop本该只解决计算的问题,但因为基础设施的不完善,只得把存储和资源调度的问题也解决了,确实导致系统整体复杂度很高,而这些额外的复杂性价值又比较低。 所以,企业需要的,根本不是复杂的Hadoop本身,但若没有Hadoop,或者说只解决计算问题而没解决存储问题,没有形成聚变能量,搞不定大数据,怎么办?数据中台就是网易的答案。 据了解,此次云创峰会,网易大数据将推出全链路大数据产品,从指标系统、数据地图、全链路监控、数据资产管理等多个方面支撑企业数据中台建设,为企业大数据应用提供统一的数据和服务,强力支撑前台业务。这个数据中台,就让企业规避了低价值的复杂性,轻松利用技术核聚变的力量挖掘大数据的价值。 如果某些能力缺失,中台可能转不起来,所以“全链路”的定语值得关注。其实,这与去年云创大会提的“完整的微服务解决方案”(轻舟微服务)的理念如出一辙。 互联网公司正在经历从“轻中台”到“大中台”的转变,所以强调“中台”,而跨区域、多板块的大型的集团企业,本身的业务架构特点就是“小前台、强中台、大后台”,是否还需要强化“中台”的建设?答案是一定要看本质,中台的真正意义,是让技术核聚变的能量(可能还包括行业的力量)低成本地传递到业务层,促成产业的革新。正如之前的云计算,有人纠结于技术上没新意(这里不杠规模),却忽视了云的本质在于模式创新。 7月26日,杭州钱江新城万豪酒店,2019网易云创峰会( yc.163yun.com)将如约而至。届时群贤毕至,论道数字技术核聚变,演绎数据中台及业务中台革新,汪源也将披露来自网易的一线经验与成熟实践。热衷数字化创新的,你是否要预定一张门票,锁定中台的正确打开方式呢?

July 11, 2019 · 1 min · jiezi

分布式事务中间件Seata的设计原理

微信公众号「后端进阶」,专注后端技术分享:Java、Golang、WEB框架、分布式中间件、服务治理等等。在微服务架构体系下,我们可以按照业务模块分层设计,单独部署,减轻了服务部署压力,也解耦了业务的耦合,避免了应用逐渐变成一个庞然怪物,从而可以轻松扩展,在某些服务出现故障时也不会影响其它服务的正常运行。总之,微服务在业务的高速发展中带给我们越来越多的优势,但是微服务并不是十全十美,因此不能盲目过度滥用,它有很多不足,而且会给系统带来一定的复杂度,其中伴随而来的分布式事务问题,是微服务架构体系下必然需要处理的一个痛点,也是业界一直关注的一个领域,因此也出现了诸如 CAP 和 BASE 等理论。 在今年年初,阿里开源了一个分布式事务中间件,起初起名为 Fescar,后改名为 Seata,在它开源之初,我就知道它肯定要火,因为这是一个解决痛点的开源项目,Seata 一开始就是冲着对业务无侵入与高性能方向走,这正是我们对解决分布式事务问题迫切的需求。因为待过的几家公司,用的都是微服务架构,但是在解决分布式事务的问题上都不太优雅,所以我也在一直关注 Seata 的发展,今天就简要说说它的一些设计上的原理,后续我将会对它的各个模块进行深入源码分析,感兴趣的可以持续关注我的公众号或者博客,不要跟丢。 分布式事务解决的方案有哪些?目前分布式事务解决的方案主要有对业务无入侵和有入侵的方案,无入侵方案主要有基于数据库 XA 协议的两段式提交(2PC)方案,它的优点是对业务代码无入侵,但是它的缺点也是很明显:必须要求数据库对 XA 协议的支持,且由于 XA 协议自身的特点,它会造成事务资源长时间得不到释放,锁定周期长,而且在应用层上面无法干预,因此它性能很差,它的存在相当于七伤拳那样“伤人七分,损己三分”,因此在互联网项目中并不是很流行这种解决方案。 为了这个弥补这种方案带来性能低的问题,大佬们又想出了很多种方案来解决,但这无一例外都需要通过在应用层做手脚,即入侵业务的方式,比如很出名的 TCC 方案,基于 TCC 也有很多成熟的框架,如 ByteTCC、tcc-transaction 等。以及基于可靠消息的最终一致性来实现,如 RocketMQ 的事务消息。 入侵代码的方案是基于现有情形“迫不得已”才推出的解决方案,实际上它们实现起来非常不优雅,一个事务的调用通常伴随而来的是对该事务接口增加一系列的反向操作,比如 TCC 三段式提交,提交逻辑必然伴随着回滚的逻辑,这样的代码会使得项目非常臃肿,维护成本高。 Seata 各模块之间的关系针对上面所说的分布式事务解决方案的痛点,那很显然,我们理想的分布式事务解决方案肯定是性能要好而且要对业务无入侵,业务层上无需关心分布式事务机制的约束,Seata 正是往这个方向发展的,因此它非常值得期待,它将给我们的微服务架构带来质的提升。 那 Seata 是怎么做到的呢?下面说说它的各个模块之间的关系。 Seata 的设计思路是将一个分布式事务可以理解成一个全局事务,下面挂了若干个分支事务,而一个分支事务是一个满足 ACID 的本地事务,因此我们可以操作分布式事务像操作本地事务一样。 Seata 内部定义了 3个模块来处理全局事务和分支事务的关系和处理过程,这三个组件分别是: Transaction Coordinator (TC): 事务协调器,维护全局事务的运行状态,负责协调并驱动全局事务的提交或回滚。Transaction Manager (TM): 控制全局事务的边界,负责开启一个全局事务,并最终发起全局提交或全局回滚的决议。Resource Manager (RM): 控制分支事务,负责分支注册、状态汇报,并接收事务协调器的指令,驱动分支(本地)事务的提交和回滚。 简要说说整个全局事务的执行步骤: TM 向 TC 申请开启一个全局事务,TC 创建全局事务后返回全局唯一的 XID,XID 会在全局事务的上下文中传播;RM 向 TC 注册分支事务,该分支事务归属于拥有相同 XID 的全局事务;TM 向 TC 发起全局提交或回滚;TC 调度 XID 下的分支事务完成提交或者回滚。与 XA 方案有什么不同?Seata 的事务提交方式跟 XA 协议的两段式提交在总体上来说基本是一致的,那它们之间有什么不同呢? ...

July 11, 2019 · 2 min · jiezi

Swoft-203-重大更新发布优雅的微服务治理

什么是 Swoft ?Swoft 是一款基于 Swoole 扩展实现的 PHP 微服务协程框架。Swoft 能像 Go 一样,内置协程网络服务器及常用的协程客户端且常驻内存,不依赖传统的 PHP-FPM。有类似 Go 语言的协程操作方式,有类似 Spring Cloud 框架灵活的注解、强大的全局依赖注入容器、完善的服务治理、灵活强大的 AOP、标准的 PSR 规范实现等等。 Swoft 通过长达三年的积累和方向的探索,把 Swoft 打造成 PHP 界的 Spring Cloud, 它是 PHP 高性能框架和微服务治理的最佳选择。 优雅的服务治理Swoft 官方建议开发者使用 Service mesh 模式,比如 Istio/Envoy 框架,把业务和服务治理分开,但是 Swoft 也为中小型企业快速构建微服务提供了一套微服务组件。 服务注册与发现服务熔断服务限流配置中心服务注册与发现服务注册与发现,需要用到 Swoft 官方提供的 swoft-consul 组件,如果其它第三方也类似。 注册与取消服务监听 SwooleEvent::START 事件,注册服务 /** * Class RegisterServiceListener * * @since 2.0 * * @Listener(event=SwooleEvent::START) */class RegisterServiceListener implements EventHandlerInterface{ /** * @Inject() * * @var Agent */ private $agent; /** * @param EventInterface $event */ public function handle(EventInterface $event): void { /* @var HttpServer $httpServer */ $httpServer = $event->getTarget(); $service = [ // .... ]; $scheduler = Swoole\Coroutine\Scheduler(); $scheduler->add(function () use ($service) { // Register $this->agent->registerService($service); CLog::info('Swoft http register service success by consul!'); }); $scheduler->start(); }}监听 SwooleEvent::SHUTDOWN 事件,取消服务 ...

July 9, 2019 · 3 min · jiezi

如何带领团队攻城略地优秀的架构师这样做

阿里妹导读:架构师是一个既能掌控整体又能洞悉局部瓶颈并依据具体的业务场景给出解决方案的团队领导型人物。看似完美的“人格模型”背后,是艰辛的探索。今天,阿里巴巴技术专家九摩将多年经验,进行系统性地总结,帮助更多架构师在进阶这条路上走得更“顺畅”,姿态更“优雅”。架构师职责架构师不是一个人,他需要建立高效卓越的体系,带领团队去攻城略地,在规定的时间内完成项目。 架构师需要能够识别定义并确认需求,能够进行系统分解形成整体架构,能够正确地技术选型,能够制定技术规格说明并有效推动实施落地。 按 TOGAF 的定义,架构师的职责是了解并关注实际上关系重大但未变得过载的一些关键细节和界面,架构师的角色有:理解并解析需求,创建有用的模型,确认、细化并扩展模型,管理架构。 从业界来看对于架构师的理解可以大概区分为: 企业架构师:专注于企业总体 IT 架构的设计。IT 架构师-软件产品架构师:专注于软件产品的研发。IT 架构师-应用架构师:专注于结合企业需求,定制化 IT 解决方案;大部分需要交付的工作包括总体架构、应用架构、数据架构,甚至部署架构。IT 架构师-技术架构师:专注于基础设施,某种软硬件体系,甚至云平台,提交:产品建议、产品选型、部署架构、网络方案,甚至数据中心建设方案等。阿里内部没有在职位 title 上专门设置架构师了,架构师更多是以角色而存在,现在还留下可见的 title 有两个:首席架构师和解决方案架构师,其中解决方案架构师目前在大部分 BU 都有设置,特别是在阿里云和电商体系。 解决方案架构师 工作方式理解 了解和挖掘客户痛点,项目定义,现有环境管理;梳理明确高阶需求和非功能性需求;客户有什么资产,星环(阿里电商操作系统)/阿里云等有什么解决方案;沟通,方案建议,多次迭代,交付总体架构;架构决策。职责 1.从客户视图来看: 坚定客户高层信心:利用架构和解决方案能力,帮忙客户选择星环/阿里云平台的信心。解决客户中层问题:利用星环/阿里云平台服务+结合应用架构设计/解决方案能力,帮忙客户解决业务问题,获得业务价值。引领客户 IT 员工和阿里生态同学:技术引领、方法引领、产品引领。2.从项目视图看: 对接管理部门:汇报技术方案,进度;技术沟通。对接客户 PM,项目 PM:协助项目计划,人员管理等。负责所有技术交付物的指导。对接业务部门和需求人员:了解和挖掘痛点,帮忙梳理高级业务需求,指导需求工艺。对接开发:产品支持、技术指导、架构指导。对接测试:配合测试计划和工艺制定。配合性能测试或者非功能性测试。对接运维:产品支持,运维支持。对接配置&环境:产品支持。其他:阿里技术资源聚合。3.从阿里内部看: 销售方案支持;市场宣贯;客户需求Facade;解决方案沉淀。架构师职责明确了,那么有什么架构思维可以指导架构设计呢?请看下述的架构思维。 架构思维自顶向下构建架构 要点主要如下: 1.首先定义问题,而定义问题中最重要的是定义客户的问题。定义问题,特别是识别出关键问题,关键问题是对客户有体感,能够解决客户痛点,通过一定的数据化来衡量识别出来,关键问题要优先给出解决方案。 2.问题定义务必加入时间维度,把手段/方案和问题定义区分开来。 3.问题定义中,需要对问题进行升层思考后再进行升维思考,从而真正抓到问题的本质,理清和挖掘清楚需求;要善用第一性原理思维进行分析思考问题。 4.问题解决原则:先解决客户的问题(使命),然后才能解决自己的问题(愿景);务必记住不是强调我们怎么样,而是我们能为客户具体解决什么问题,然后才是我们变成什么,从而怎么样去更好得服务客户。 5.善用多种方法对客户问题进行分析,转换成我们产品或者平台需要提供的能力,比如仓储系统 WMS 可以提供哪些商业能力。 6.对我们的现有的流程和能力模型进行梳理,找到需要提升的地方,升层思考和升维思考真正明确提升部分。 7.定义指标,并能够对指标进行拆解,然后进行数学建模。 8.将抽象出来的能力诉求转换成技术挑战,此步对于技术人员来说相当于找到了靶子,可以进行方案的设计了,需要结合自底向上的架构推导方式。 9.创新可以是业务创新,也可以是产品创新,也可以是技术创新,也可以是运营创新,升层思考、升维思考,使用第一性原理思维、生物学(进化论--进化=变异+选择+隔离、熵增定律、分形和涌现)思维等哲科思维可以帮助我们在业务,产品,技术上发现不同的创新可能。可以说哲科思维是架构师的灵魂思维。 自底向上推导应用架构 先根据业务流程,分解出系统时序图,根据时序图开始对模块进行归纳,从而得到粒度更大的模块,模块的组合/聚合构建整个系统架构。 基本上应用逻辑架构的推导有4个子路径,他们分别是: 业务概念架构:业务概念架构来自于业务概念模型和业务流程;系统模型:来自于业务概念模型;系统流程:来自业务流程;非功能性的系统支撑:来自对性能、稳定性、成本的需要。效率、稳定性、性能是最影响逻辑架构落地成物理架构的三大主要因素,所以从逻辑架构到物理架构,一定需要先对效率、稳定性和性能做出明确的量化要求。 自底向上重度依赖于演绎和归纳。 如果是产品方案已经明确,程序员需要理解这个业务需求,并根据产品方案推导出架构,此时一般使用自底向上的方法,而领域建模就是这种自底向上的分析方法。 对于自底向上的分析方法,如果提炼一下关键词,会得到如下两个关键词: 1.演绎:演绎就是逻辑推导,越是底层的,越需要演绎: 从用例到业务模型就属于演绎;从业务模型到系统模型也属于演绎;根据目前的问题,推导出要实施某种稳定性措施,这是也是演绎。2.归纳:这里的归纳是根据事物的某个维度来进行归类,越是高层的,越需要归纳: 问题空间模块划分属于归纳;逻辑架构中有部分也属于归纳;根据一堆稳定性问题,归纳出,事前,事中,事后都需要做对应的操作,是就是根据时间维度来进行归纳。 领域驱动设计架构 大部分传统架构都是基于领域模型分析架构,典型的领域实现模型设计可以参考DDD(领域驱动设计),详细可以参考《实现领域驱动设计》这本书,另外《UML和模式应用》在领域建模实操方面比较好,前者偏理论了解,后者便于落地实践。 领域划分设计步骤: 1.对用户需求场景分析,识别出业务全维度 Use Case; 2.分析模型鲁棒图,识别出业务场景中所有的实体对象。鲁棒图 —— 是需求设计过程中使用的一种方法(鲁棒性分析),通过鲁棒分析法可以让设计人员更清晰,更全面地了解需求。它通常使用在需求分析后及需求设计前做软件架构分析之用,它主要注重于功能需求的设计分析工作。需求规格说明书为其输入信息,设计模型为其输出信息。它是从功能需求向设计方案过渡的第一步,重点是识别组成软件系统的高级职责模块、规划模块之间的关系。鲁棒图包含三种图形:边界、控制、实体,三个图形如下: ...

July 4, 2019 · 3 min · jiezi

腾讯-Tars-Web-管理端用户体系对接

背景这段时间一直在基于 Tars 作开发。最近的文章也多是针对 Tars 的一些学习笔记。前面我们搭建了 Tars 基础框架,打开了 Tars web 管理界面进行服务的运维操作。不过读者肯定很快就会发现:这好像不用登录啊,那怎么保证只有有权限的用户才能更改服务呢? 显然 Tars web 是支持用户鉴权的。官方文档在这里。本文记录一下我的用户体系对接实验中的一些笔记,便于其他 Tars 的用户参阅。(特别是像我这样对 Node.js 不熟悉的小白……) 本系列文章: 腾讯 Tars 基础框架手动搭建——填掉官方 Guide 的坑腾讯 Tars-Go 服务 Hello World——从 HTTP 开始腾讯 Tars-Go 服务 Hello World——RPC 通信腾讯 Tars-Go 服务获取自定义模版(配置)值腾讯 Tars Web 管理端用户体系对接(本文)本文地址:https://segmentfault.com/a/1190000019657656 Tars 用户鉴权流程准备如果要启用 Tars web 的用户功能,那么首先开发者需要设计一个自己的用户登录服务。该服务是 http 服务,有独立的用户登录、登出功能。Tars Web 本身实现了一个简单的用户功能,不过本文我们重新设计一个。为便于说明,我们假设这个 Tars web 和用户服务 web 环境如下: Tars Web URL:https://tars.amc.com用户 Web URL:https://user.amc.com基本流程从用户通过浏览器访问 Tars web 管理平台开始,如果启用了用户功能,那么基本流程如下: 一言以蔽之:每当浏览器向 Tars web 发起一个请求时,Tars web 均向用户服务器发起请求,判断用户是否有权限;如果鉴权通过,则正常操作 Tars;如果没有,则重定向至用户登录页面。 ...

July 3, 2019 · 2 min · jiezi

Spring-Cloud-Alibaba-Nacos源码篇

在看这篇文章之前,最好对NACOS相关功能有所了解,推荐看完Spring Cloud Alibaba Nacos(功能篇)。 针对功能,有目的的去找相对应的源代码,进一步了解功能是如何被实现出来的。 本文针对有一定源代码阅读经验的人群,不会深入太多的细节,还需要读者打开源码跟踪,自行领会。 一、引子进入GitHub对应的页面,将NACOS工程clone下来。目录和文件看起来很冗长,但是对于看源代码真正有帮助的部分并不多。 有了这三张图,就能顺利找到突破口了,核心内容就集中在nacos-console,nacos-naming,nacos-config,顺藤摸瓜,就能看到不少内容了。 如果还是感觉无从下手的话,那就移步nacos-example,里面有主要业务的调用入口,一看便知。 二、配置服务首先从一个工厂类说起:com.alibaba.nacos.api.NacosFactory。 里面的静态方法用于创建ConfigService和NamingService,代码类似,以创建ConfigService为例: public static ConfigService createConfigService(Properties properties) throws NacosException { try { Class<?> driverImplClass = Class.forName("com.alibaba.nacos.client.config.NacosConfigService"); Constructor constructor = driverImplClass.getConstructor(Properties.class); ConfigService vendorImpl = (ConfigService) constructor.newInstance(properties); return vendorImpl; } catch (Throwable e) { throw new NacosException(-400, e.getMessage()); }}没有什么复杂的逻辑,使用的是基本的反射原理。构造参数传入了properties,这些属性可以通过bootstrap.yml中指定,对应的是NacosConfigProperties。 需要细看的是构造函数中对于namespace初始化的那部分内容。 private void initNamespace(Properties properties) { String namespaceTmp = null; String isUseCloudNamespaceParsing = properties.getProperty(PropertyKeyConst.IS_USE_CLOUD_NAMESPACE_PARSING, System.getProperty(SystemPropertyKeyConst.IS_USE_CLOUD_NAMESPACE_PARSING, String.valueOf(Constants.DEFAULT_USE_CLOUD_NAMESPACE_PARSING))); if (Boolean.valueOf(isUseCloudNamespaceParsing)) { namespaceTmp = TemplateUtils.stringBlankAndThenExecute(namespaceTmp, new Callable<String>() { @Override public String call() { return TenantUtil.getUserTenantForAcm(); } }); namespaceTmp = TemplateUtils.stringBlankAndThenExecute(namespaceTmp, new Callable<String>() { @Override public String call() { String namespace = System.getenv(PropertyKeyConst.SystemEnv.ALIBABA_ALIWARE_NAMESPACE); return StringUtils.isNotBlank(namespace) ? namespace : EMPTY; } }); } if (StringUtils.isBlank(namespaceTmp)) { namespaceTmp = properties.getProperty(PropertyKeyConst.NAMESPACE); } namespace = StringUtils.isNotBlank(namespaceTmp) ? namespaceTmp.trim() : EMPTY; properties.put(PropertyKeyConst.NAMESPACE, namespace);}传入的properties会指定是否解析云环境中的namespace参数,如果是的,就是去读取阿里云环境的系统变量;如果不是,那么就读取properties中指定的namespace,没有指定的话,最终解析出来的是空字符串。从代码上看出来,获取云环境的namespace做成了异步化的形式,但是目前版本还是使用的同步调用。 ...

July 3, 2019 · 4 min · jiezi

Spring-Cloud-微服务系统-分布式食物解决方案

一、微服务系统最大的挑战数据的并发访问、修改不同请求之间的数据隔离多个服务共同完成一个业务请求,保证都完成或者失败发生异常时的数据回滚二、事务事务本地事务的原则,实现原理Spring事务- Spring事务机制、事务抽象- 内部事务,外部事务,几种事务管理实现- Spring事务管理的实例(标签,代码方式)- JPA,JMS,JTA事务管理的实例分布式系统- 分布式系统的原则,实现,形式- SpringCloud微服务系统&实例分布式事务- 实现原则,用Spring实现分布式事务,微服务架构下的实现- 分布式事务管理实例:JTA、事务同步、链式- 分布式事务实现的几种模式:消息驱动、事件溯源、TCC- 消息驱动、事件溯源实现的详细实例 Event Sourcing(事件溯源架构)目标理解事务的原则,实现原理掌握Spring事务机制、实现,以及分布式事务实现了解分布式事务,掌握使用Spring Cloud实现微服务掌握分布式事务的实现原理、方法、几种实现模式掌握Event Sourcing架构,原理和实现方法基于消息驱动的Spring Cloud微服务系统基于Event Sourcing(事件溯源)微服务系统基于Event Sourcing和Spring Cloud微服务系统

July 2, 2019 · 1 min · jiezi

Spring-Cloud-Sleuth-链路监控-服务追踪-zipkin

链路监控依赖如下<!--<dependency> <group>org.springframework.cloud</group> <artifactId>spring-cloud-starter-sleuth</artifactId></dependency><dependency> <group>org.springframework.cloud</group> <artifactId>spring-cloud-sleuth-zipkin</artifactId></dependency>--><!--如下包含以上两个依赖--><dependency> <group>org.springframework.cloud</group> <artifactId>spring-cloud-starter-zipkin</artifactId></dependency>spring: zipkin: base-url: http://localhost:9411/ sleuth: sampler: percentage:1 // 1 表示监控100%的请求,默认是0.1 - 10%![图片上传中...]

July 1, 2019 · 1 min · jiezi

Spring-Cloud服务熔断降级

一、服务熔断@HystrixCommand注解 name="circuitBreaker.enabled",value="true" name="circuitBreaker.requestVolumeThreshold",value="10" // 设置在一个滚动窗口中,打开断路器的最少请求数 name="circuitBreaker.sleepWindowInMilliseconds",value="10000" // 设置在回路被打开,拒绝请求到再次尝试请求并决定回路是否继续打开的时间 name="circuitBreaker.errorThresholdPercentage",value="60" // 错误率达到60%触发降级参考:https://www.jianshu.com/p/397...配置 hystrix: command: default: execution: isolation: thread: timeoutInMilliseconds: 2000

July 1, 2019 · 1 min · jiezi

Spring-Cloud-服务网关Zuul

服务网关的要素稳定性安全性性能,并发性扩展性Spring Cloud Zuul - 路由+过滤器 - 核心是一系列的过滤器 Zuul路由配置management: security: enabled: false // 权限设置zuul: routes: # myProduct: // 这个名称可以随便填 # path: /myProduct/** # serviceId: product # sensitiveHeader: //敏感头过滤 # 简洁写法 product: /myProduct/** ignored-patterns: - /**/product/listForOrder // 不对外部访问(-代表set集合)查看所有的路由规则:localhost:port/application/routes{ /myProduct/**: "product", /config/**: "config", /product/**: "product",} Zuul配置的动态注入(也可以写入启动类中)@Compoentpublic class ZuulConfig{ @ConfigurationProperties("zuul") @RefreshScope public ZuulProperties zuulProperties(){ return new ZuulProperties(); }}典型应用场景前置过滤器 - 限流 - 鉴权 - 参数校验调整后置过滤器 - 统计- 日志要想实现Filter,需要以下几个步骤: ...

June 30, 2019 · 1 min · jiezi

Spring-Cloud微服务配置中心

格式/{name}-{profiles}.yml/{label}/{name}-{profiles}.yml注释: 1. name - 服务名2. profiles 环境3. label 分支{branch}

June 27, 2019 · 1 min · jiezi

SpringCloud微服务架构升级总结

一、背景1.1 应用系统的架构历史 1.2 什么是微服务?起源:微服务的概念源于 2014 年 3 月 Martin Fowler 所写的一篇文章“Microservices”。文中内容提到:微服务架构是一种架构模式,它提倡将单一应用程序划分成一组小的服务,服务之间互相协调、互相配合,为用户提供最终价值。 通信方式:每个服务运行在其独立的进程中,服务与服务间采用轻量级的通信机制互相沟通(通常是基于 HTTP 的 RESTful API)。 微服务的常规定义:微服务是一种架构风格,一个大型复杂软件应用由一个或多个微服务组成。系统中的各个微服务可被独立部署,各个微服务之间是松耦合的。每个微服务仅关注于完成一件任务。把原来的一个完整的进程服务,拆分成两个或两个以上的进程服务,且互相之间存在调用关系,与原先单一的进程服务相比,就是“微服务”。(微服务是一个比较级的概念,而不是单一的概念) 1.3 微服务架构的优势可扩展性:在增加业务功能时,单一应用架构需要在原先架构的代码基础上做比较大的调整,而微服务架构只需要增加新的微服务节点,并调整与之有关联的微服务节点即可。在增加业务响应能力时,单一架构需要进行整体扩容,而微服务架构仅需要扩容响应能力不足的微服务节点。容错性:在系统发生故障时,单一应用架构需要进行整个系统的修复,涉及到代码的变更和应用的启停,而微服务架构仅仅需要针对有问题的服务进行代码的变更和服务的启停。其他服务可通过重试、熔断等机制实现应用层面的容错。技术选型灵活:微服务架构下,每个微服务节点可以根据完成需求功能的不同,自由选择最适合的技术栈,即使对单一的微服务节点进行重构,成本也非常低。开发运维效率更高:每个微服务节点都是一个单一进程,都专注于单一功能,并通过定义良好的接口清晰表述服务边界。由于体积小、复杂度低,每个微服务可由一个小规模团队或者个人完全掌控,易于保持高可维护性和开发效率。Spring Cloud作为目前最流行的微服务开发框架,不是采用了Spring Cloud框架就实现了微服务架构,具备了微服务架构的优势。正确的理解是使用Spring Cloud框架开发微服务架构的系统,使系统具备微服务架构的优势(Spring Cloud就像工具,还需要“做”的过程)。 1.4 什么是Spring Boot?什么是Spring Cloud?Spring Boot框架是由Pivotal团队提供的全新框架,其设计目的是用来简化基于Spring应用的初始搭建以及开发过程。SpringBoot框架使用了特定的方式来进行应用系统的配置,从而使开发人员不再需要耗费大量精力去定义模板化的配置文件。 Spring Cloud是一个基于Spring Boot实现的云应用开发工具,它为基于JVM的云应用开发中的配置管理、服务注册,服务发现、断路器、智能路由、微代理、控制总线、全局锁、决策竞选、分布式会话和集群状态管理等操作提供了一种简单的开发方式。 1.5 微服务、Spring Boot、Spring Cloud三者之间的关系思想:微服务是一种架构的理念,提出了微服务的设计原则,从理论为具体的技术落地提供了指导思想。脚手架:Spring Boot是一套快速配置脚手架,可以基于Spring Boot快速开发单个微服务。多个组件的集合:Spring Cloud是一个基于Spring Boot实现的服务治理工具包;Spring Boot专注于快速、方便集成的单个微服务个体;Spring Cloud关注全局的服务治理框架。二、技术解析2.1 Everything is jar, Everything is httpSpring Boot通过@SpringBootApplication注解标识为Spring Boot应用程序。所有的应用都通过jar包方式编译,部署和运行。 @SpringBootApplication public class Application { private static final Logger LOGGER = LoggerFactory.getLogger(Application.class); public static void main(String[] args) { SpringApplication.run(Application.class, args); LOGGER.info(”启动成功!"); } }每个Spring Boot的应用都可以通过内嵌web容器的方式提供http服务,仅仅需要在pom文件中依赖spring-boot-start-web即可,原则上微服务架构希望每个独立节点都提供http服务。 ...

June 26, 2019 · 3 min · jiezi

一文读懂微服务与服务网格WHAT-WHY-and-HOW-TO-DO

作者注:联系方式 leontian1024@gmail.com || github.com/XinyaoTian新人入行,非常期待能与各位大牛们讨论,感谢各位的阅读,希望对您有所帮助。一切都要从云计算和容器技术的出现说起...相信每一位老道的开发者和软件工程师们都会有过,曾经( 也许现在仍然 )被庞大且复杂的软件系统所支配的恐惧。随着软件版本的迭代和开发团队人员规模的扩大,曾经那个小巧别致、设计精良的软件或应用一去不返,如今已经变得遍地狼藉,惨不忍睹——混乱的接口、不规范的调用、像贴狗皮膏药一般贴上去的新功能组件……曾经那个赏心悦目的应用,如今看了就反胃。这一切都预示着一个问题:开发软件的方式需要改变。 纵使有那么多种软件开发模式,但如果不能从底层技术上实现对设计的约束,问题将会随着时间的推移,最终暴露出来。譬如“高内聚,低耦合”的设计理念,如果不能从底层就实现模块间的隔离,而依靠开发时的技巧和经验,那么最终这个软件依旧会变得一团糟( 因为团队会有新人的加入、即使经验老道的开发者也会有头脑发昏的时候…… )。因此,“不这么写就无法运行”这样的硬性要求就很有必要了。 云计算和容器技术的出现,非常及时地帮助我们解决了这一难题。云计算的高弹性和按需分配、容器技术的快速启停和隔离,都很好的帮助了我们减少运维开销,且对于不同模块实现操作系统级别的隔离。在这两种关键技术出现前后,软件架构的差别如下图所示: ( 上图: 单体应用 与 微服务应用 )以 Web 应用为例,按照之前的开发方式,我们往往会选用一个功能齐全但相当复杂的开发框架( 比如JAVA开发常用的 SpringBoot ),然后在这个框架的基础上,根据开发经验和框架的功能将整个应用分层( 比如最常用的表示层、业务逻辑层和数据资源层的分层方法 ),之后根据需求分析得出的各个功能,通过不同目录、文件和函数的方式分别开发,最终一个完整的 Web 应用被开发出来。其过程如下图所示。 ( 上图: 基于框架的软件开发架构网格 )通过使用框架,我们把软件在层次上分为三层,而之后的每个功能,就相当于纵向地添加一个新列。如果以这种方式看待一个应用,那么我们就可以将任何基于这种开发方法的应用看作一个3行n列的表格或矩阵了。 然而,这种非常普及的开发方式仍然有一些问题...虽然这种开发方式已经非常普及,非常成熟,但其仍有许多有待改进的地方。比如: 依赖和库过于庞杂。理想情况下,我们希望针对每一个模块,单独管理其相应的依赖和库,而不是以整个应用作为单位来管理。 无法改变层次结构。某种层次结构对于某些业务需求来说很棒,但对于另外一些也许就显得不那么合适了。使用这种方式开发,几乎所有功能都要遵从这种既定的层次开发( 比如写 UI、写业务逻辑、写数据库层 )。而对于目前日渐快速的迭代和敏捷的开发思想,我们需要一种更加灵活轻便的方式进行开发。 无法实现跨语言开发。我们知道,几乎每种编程语言都有自己最擅长的领域。也许某些功能选用其他编程语言的开发效率更高,运行效果更好。然而,使用这种方式我们往往只能使用选定的框架所支持的语言。 模块的独立性不足。单纯通过函数和文件来进行隔离,隔离性仍然不够。一个疏忽或是加急上线就会让之前良好的高内聚低耦合的良好软件结构灰飞烟灭。因此,我们需要更加底层的机制来硬性约束我们实现“高内聚低耦合”,把“应该这么做”变为“必须这么做”。 而伴随着云计算和容器技术的发展,微服务的出现,恰巧将这些问题迎刃而解。 ( 上图: 容器技术的基本层次结构 )先来说说容器技术的代表 —— DockerDocker 可以看作是轻量级的虚拟机——它可以通过“容器镜像”快速启动和停止预先配置好的相应运行环境。每一个容器都可以被看作一个独立的操作系统,相互隔离,互不干扰。因此,借助docker 我们就可以针对一个应用中不同的功能,为其独立定制运行环境,独立装载依赖和工具库,独立运行独立停止,独立升级,每个功能可以使用其最适合的编程语言进行开发,也将整个应用拘泥于一种框架了。利用 docker 将每个模块在操作系统层面进行隔离,对于每个模块都可以独立管理其生命周期了,这就是“微服务”中“微”字的具体含义。 微服务开发的重点基于这种开发方式,每个功能模块可以被独立开发,独立部署,独立运行,独立进行版本控制,等等。而对于规模比较庞大的系统来说,这种利用微服务架构所开发的应用,其天然的优势就更能体现出来了——即每个模块可以独立的团队由单独负责。因此,微服务开发中的第一个重点,就是要有非常明确的需求,以及一个经验丰富的架构师,在设计之初就对各个功能模块进行合理的规划和拆分。 在整个应用设计之初由总设计师或架构师设计好各个功能模块后,第二个重点就来了:设计微服务中各个模块间的调用接口——通常由 Rest API 或者 gRPC 组成——来负责模块之间的交互,这就是微服务的第二个重点。良好的接口设计将会使你的应用结构清晰,开发起来事半功倍。而且每个独立团队在开发时都能感受到明显的模块边界,且可以放心利用模拟数据和测试数据进行开发( 只要符合接口规则的数据就能用,不用操心其他模块是如何实现的 ),从而真正实现每个团队富有效率的并行开发。 利用微服务架构开发除了上述好处之外,在运维方面的优势也非常直观——我们可以清晰地观测到整个系统的资源瓶颈在何处( 哪个容器的资源开销最大 ),从而实现有针对性的“定向扩缩容”。利用微服务架构前后的扩缩容机制如下图所示意。 ( 上图: 单体应用和微服务应用最直观的差别:定向扩缩容 示意图 )将庞大的单体应用逐步改造成微服务应用在看完上面的介绍后,相信饱受单体应用折磨的各位读者已经对微服务开发已经跃跃欲试了。但是,对于一个正在运行并使用的应用来说,完完全全从零开始开发并不现实。对于一个已经成熟并正在使用的单体应用系统来说,我们可以通过自己的努力,将一个单体应用在几次迭代过程中,逐渐改变为微服务应用。如何办到呢?下面放一张图片来帮助您激发灵感: ...

June 25, 2019 · 2 min · jiezi

点评CAT在Spring-Cloud中的实践

作者在基于Spring Cloud微服务的架构时,一直苦于寻找一个可靠的性能监控平台,后在大神的推荐下,详细研究了点评CAT,其满足对应用性能监控的需求(包含SQL性能,URL响应性能等),将踩过的坑进行分享一下。 下载cat 3.0并启动由于微服务集群并不是很庞大,且服务器资源有限,所以暂时只采用了单点部署的CAT,集群部署和使用请参考CAT的github,这里暂不做分享 配置系统的JDK,以及下载对应的tomcat,本人使用的是JDK8和tomcat8.5.x版本(JDK的配置和tomcat的下载不做详细说明)CAT下载地址http://unidal.org/nexus/servi...修改tomcat的server.xml使其支持中文的URL <Connector port="8080" protocol="HTTP/1.1" URIEncoding="utf-8" connectionTimeout="20000" redirectPort="8443" /><!-- 增加 URIEncoding="utf-8" -->创建CAT使用的文件夹,并修改其配置 mkdir /datachmod -R 777 /data/ 修改CAT配置文件,创建/data/appdatas/cat/client.xml并修改如下 <?xml version="1.0" encoding="utf-8"?><config mode="client"> <servers> <server ip="127.0.0.1" port="2280" http-port="8080"/> </servers></config>修改cat的数据库配置文件/data/appdatas/cat/datasource.xml <?xml version="1.0" encoding="utf-8"?><data-sources> <data-source id="cat"> <maximum-pool-size>3</maximum-pool-size> <connection-timeout>1s</connection-timeout> <idle-timeout>10m</idle-timeout> <statement-cache-size>1000</statement-cache-size> <properties> <driver>com.mysql.jdbc.Driver</driver> <url><![CDATA[jdbc:mysql://127.0.0.1:3306/cat]]></url> <!-- 请替换为真实数据库URL及Port --> <user>root</user> <!-- 请替换为真实数据库用户名 --> <password>root</password> <!-- 请替换为真实数据库密码 --> <connectionProperties><![CDATA[useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&socketTimeout=120000]]></connectionProperties> </properties> </data-source></data-sources>运行CAT的SQL脚本初始化数据库 将CAT的war重命名为cat.war放到tomcat的webapps下,并启动tomcat(默认用户名密码admin:admin),即可通过服务器IP:8080/cat进行访问springboot集成cat clientmaven引入cat client <dependency> <groupId>com.dianping.cat</groupId> <artifactId>cat-client</artifactId> <version>3.0.0</version></dependency>使用SPI方式配置cat client 其中com.dianping.cat.configuration.ClientConfigProvider文件中填写完成实现类名称,实现类代码如下: public class CatClientConfigProvider implements ClientConfigProvider { @Override public ClientConfig getClientConfig() { List<Server> servers = Lists.newArrayList(); //cat 服务器地址,多个则需要使用,分割 String catServersStr = SpringUtils.getProperties("cat.servers"); if (catServersStr != null) { String[] catServers = catServersStr.split(","); for (String catServer : catServers) { servers.add(new Server(catServer)); } //domain直接去springboot的application name String domain = SpringUtils.getProperties("spring.application.name"); ClientConfig config = new ClientConfig(); config.setServers(servers); config.setDomain(domain); return config; } return null; }}app.properties文件中填写app.name=应用名称 ...

June 25, 2019 · 1 min · jiezi

Spring-Cloud-Alibaba-Nacos

越来越多的读者在和我交流关于Spring Cloud Alibaba的种种事宜,甚至于在一次面试中,一半时间都在聊这个话题。所以,本着对技术钻研的热情,对Spring Cloud Alibaba进行了一番研究。 在这里,并不想高谈阔论,也不想预言未来,只是挑选了几个从阿里巴巴中间件团队内脱胎出来的开源组件,对于解决实际业务问题有所裨益。 一、背景先来说说大背景。 现在,很明显的一个趋势就是:微服务。 这个趋势的底层驱动力就来源于分布式系统的普及,而微服务的各个特性是如今大大小小的企业无法拒绝的诱惑。 然后,用上了微服务的架构风格,用Spring Cloud,或者Dubbo搭了一套脚手架,就开始干起来了。 接下来,一众小公司画完了大饼之后,发现自己根本吃不下。这就是典型的落后劳动力与先进生产力的尖锐矛盾。这个时候,返璞归真的想法是不能有了,重构代价太大。 当然,哪里有问题,哪里就有商机。各大XX云厂商经过一系列包装之后,用“云原生(Cloud Native)”的新概念粉墨登场。 Spring Cloud Alibaba就是其中之一。 这个概念的一个核心价值就是:平滑上云,赋能运维。最明显的业务表象就是,会提供一套Open API,甚至是贴心的提供一个可视化控制台,傻瓜式的那种。 二、从NACOS说起这是一颗耀眼的掌上明珠,迅速引起了我的注意。 按照套路,分为两讲。其一讲述NACOS的功能特性,及其使用,再者就是更深入一步,看看大厂的攻城狮们写的代码。 本文所使用的版本是NACOS 1.0.0,由于此版本还是第一个NACOS正式版,NACOS正处在飞速发展阶段,本文的一些内容可能会不适用于以后的版本,请读者自行辨别。 NACOS解决两个核心问题:动态配置管理,服务注册发现。 兼容性方面,除了支持自家的Dubbo,还对Spring Cloud,Kubernetes,Istio有所兼容。 对照以上的全景图,现在的NACOS还有一段距离,但是并不遥远。 至此,不说道说道Eureka,都有点过意不去了。 我用下来的体验是:NACOS完全可以替代Eureka了。 江山代有才人出,这是必然的结果。 在“云原生”的大背景之下,NACOS顺利成章的推出了Console,将触角进一步延伸至服务的精细化管理。 当然,不排除Eureka也在憋大招。 再说说动态配置的特性。 当然,NACOS略胜一筹,可替代Spring Cloud Config了。 原先在Git/SVN上托管的配置项,都可以在Console上统一管理了。 如果想先睹为快,可以接下着往下读。如果想再多了解一些,可以直接跳过这部分,阅读下一个小节。 可以把NACOS理解成是一个中心化的服务,这在阿里系的架构中屡见不鲜。所以,必须得先启动这个服务。 有两个办法:其一是直接clone源码,使用maven打包。第二个办法是直接下载GitHub release出来的压缩包。 推荐后者。 方法1:主要运行以下命令: git clone https://github.com/alibaba/nacos.git cd nacos/ mvn -Prelease-nacos clean install -U经过一段时间的构建过程,在./distribution/target目录下有我们想要的压缩包。 方法2:进入https://github.com/alibaba/nacos/releases,找到压缩包,下载。 为了演示,我们先用单机模式启动。 Windows环境下: startup.cmd -m standalone一切就绪的话,访问http://127.0.0.1:8848/nacos/index.html,使用nacos/nacos登录。 接下来,随便逛逛。 三、重要的概念为了避免在Console中迷失自我,有必要先阐述几个重要的概念。 这张图很重要。表述了namespace、group和service/dataId的包含关系。 NACOS给的最佳实践表明,最外层的namespace是可以用于区分部署环境的,比如test,uat,product等。同时,也有一个商业利用价值:多租户。以namespace为单位,给用户开辟使用空间。 其它两个领域模型不用多解释了,见名知意。其目的也非常明显,就是为了能够逻辑上区分两个目标对象。 默认情况下,namespace=public,group=DEFAULT_GROUP。 明白了这个数据模型后,可以稍微玩转一下Console了,比如新建若干个namespace: ...

June 23, 2019 · 2 min · jiezi

故障注入-Sidecar自己设计并实现的故障注入微服务非常欢迎各位大佬批评指正

“故障注入 Sidecar“——为您的微服务注入故障以验证集群性能! 由于导师和实验室师兄们的科研需要,本人专门以 Sidecar的模式设计了一个用于错误注入的微服务模块。该模块可以与任何微服务应用共同部署运行,为其模拟cpu、内存等错误。 本项目的 Github地址: https://github.com/iscas-micr...我的联系方式: leontian1024@gmail.com || 或直接留言 欢迎您提出问题批评指点!项目背景目前,本人正在中科院软件所的微服务研究组从事部分研究工作。由于本人所在科研小组的研究内容( 微服务自动扩缩容相关 ),需要经常使微服务应用处于"高 CPU 利用率" 和 "高内存使用"的状态。因此,为了方便导师和实验室的各位师兄进行实验,本人特地开发了一个可以注入进 Pod 中的错误注入容器,来模拟上述的高负载状态。 导师和师兄们使用后对我的工作给予了肯定,因此我准备将开发过程和简单使用方法写成文章做个记录( 也就是本文 ),一来方便自己日后工作学习,二来也方便有类似实验需求的其他同仁们使用这个小项目,为大家的研究节省时间。更具体的安装和使用方法,可以移步本项目 Github 的代码仓库,其中有非常详细的说明。 知识储备什么是微服务中的"Sidecar 运行模式?" 上图: 以 Sidecar 模式部署并运行的微服务单元Sidecar 运行模式是最近两年比较火的一种微服务部署和运行方法,它由目前流行的 ServiceMesh(服务网格) 架构推广而来。 具体而言,Sidecar 运行模式是一种"将不属于业务应用的功能以独立的容器运行于业务容器旁边",在 K8s 中表现出的样子就是将具有不同功能的模块封装成不同的镜像,并以不同的容器运行在同一个 Pod 中。这种说法非常形象,因为 Sidecar 这个单词的本意就是三轮摩托侧面的"跨斗",这里形容独立于业务应用但又与业务应用部署在一起非常合适。 上图: Sidecar ,中文意思为摩托车的跨斗,不由赞叹命名的非常生动主要设计思想架构设计本项目的错误注入模块也采用了 Sidecar 这种设计思想,将用于模拟 CPU、内存等故障的模块独立封装成一个镜像,并在 Pod 启动时以 Sidecar 的形式运行在主业务容器旁边。这样,不用它时他就会安安静静地当个美男子,完全不用担心它会影响到正常业务的运行;一旦需要它模拟错误产生,由于与业务容器同处于一个 Pod 之中(而 K8s 又以 Pod 为基本单元),因此他模拟出的错误亦被 K8s 集群视为业务应用所在 Pod 产生而被监测到。 上图: Pod 中的每个容器都有自己的端口映射到外部主机,因此不会相互影响注入方式设计本项目在设计之初是采用“在容器内修改环境变量”的方式对容器注入故障的,但事实证明这种方法太low,而且非常麻烦。因此在后续设计和实现中采用了目前较为流行的通过 REST API 传递 POST 请求的方式使容器模拟错误,这样就极大地方便了师兄们展开实验,而且也可以模拟出“微服务间调用而产生错误”的场景( 上游服务调用错误注入的 API 而模拟下游服务产生错误 )。 ...

June 22, 2019 · 3 min · jiezi

albin微服务2Eureka-Server-高可用

分别配置eureka: client: service-url: defaultZone: http://localhost:8761/eureka/eureka: client: service-url: defaultZone: http://localhost:8762/eureka/http://localhost:8761/如下: http://localhost:8762/如下: Eureka Client配置eureka: client: service-url: defaultZone: http://localhost:8761/eureka/,http://localhost:8762/eureka/

June 21, 2019 · 1 min · jiezi

albin微服务1Eureka-Server

一、启动项配置@EnableEurekaServer@SpringBootApplication@EnableEurekaServerpublic class EurekaApplication { public static void main(String[] args) { SpringApplication.run(EurekaApplication.class, args); }}二、Maven依赖<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId></dependency>三、Eureka配置eureka: client: service-url: defaultZone: http://localhost:8761/eureka/ register-with-eureka: false server: enable-self-preservation: falsespring: application: name: eurekaserver: port: 8761三、Eureka注册中心界面

June 21, 2019 · 1 min · jiezi

蚂蚁金服轻量级监控分析系统-SOFALookout-服务端开源

SOFAStack Scalable Open Financial  Architecture Stack 是蚂蚁金服自主研发的金融级分布式架构,包含了构建金融级云原生架构所需的各个组件,是在金融场景里锤炼出来的最佳实践。SOFALookout 是蚂蚁金服在 SOFAStack 体系内研发开源的一款解决系统的度量和监控问题的轻量级中间件服务。本文给大家介绍下 SOFALookout 服务器端主要提供的特性以及使用方式。SOFALookout:https://github.com/sofastack/sofa-lookout 前言容器,K8S,微服务,Mesh 以及 Serverless 这些新技术方向正在根本的变革我们运行软件的方式。我们构建的系统更加分布式化,另外由于容器,系统的生命周期更加短,变得易逝。针对这些变化,SOFALookout 希望提供一套轻量级解决方案。之前 SOFALookout 已经开源客户端的能力。今天,SOFALookout 服务器端 Metrics 部分的代码终于正式开源啦!本文给大家介绍下 SOFALookout 服务器端的主要特性以及使用方法。 什么是 SOFALookoutSOFALookout 是蚂蚁金服开源的一款解决系统的度量和监控问题的轻量级中间件服务。它提供的服务包括:Metrics 的埋点、收集、加工、存储与查询等。该开源项目包括了两个独立部分,分别是客户端与服务器端服务。 SOFALookout 目标是打造一套轻量级 Observability 实时工具平台,帮助用户解决基础设施、应用和服务等的监控和分析的问题。SOFALookout(目前已开源部分) 是一个利用多维度的 metrics 对目标系统进行度量和监控的项目。SOFALookout 的多维度 metrics 参考 Metrics2.0 标准。 SOFALookout :https://github.com/sofastack/sofa-lookoutSOFALookout 安装文档:https://www.sofastack.tech/sofa-lookout/docs/quickstart-metrics-server  SOFALookout 服务器端的主要特性: 适配社区主要 Metrics 数据源协议写入(比如: Prometheus,Metricbeat 等);数据的存储支持扩展,暂时开源版默认支持 Elasticsearch, 并且透明和自动化了相关运维操作;遵循 Prometheus 查询 API 的标准以及支持 PromQL,并进行了适当改进;自带数据查询的控制台,并支持 Grafana 进行数据可视化;使用简单,支持单一进程运行整个服务器端模块。随着 SOFALookout (metrics)服务器端代码开源,metrics 数据的处理已经形成闭环。后续我们将会进一步开源 Trace 和 Event 相关的服务能力,敬请期待。 SOFALookout 项目结构服务器端代码分别包括两部分:Gateway 模块和 Server 模块。如下图所示(展示了 SOFALookout 源码项目的模块概要结构) ├── boot├── client├── gateway└── server项目中的 boot 模块作用是方便集成和运行服务端的模块,既可以单独运行 Gateway 和 Server 的服务,也可以借助 SOFAArk 完成(Gateway 和 Server)的 All in One 的合并为单一进程运行。 ...

June 20, 2019 · 1 min · jiezi

php中使用protobuffer

Protobuf 简介protobuf(Protocol buffers)是谷歌出品的跨平台、跨语言、可扩展的数据传输及存储的协议,是高效的数据压缩编码方式之一。 Protocol buffers 在序列化数据方面,它是灵活的,高效的。相比于 XML 来说,Protocol buffers 更加小巧,更加快速,更加简单。一旦定义了要处理的数据的数据结构之后,就可以利用 Protocol buffers 的代码生成工具生成相关的代码。甚至可以在无需重新部署程序的情况下更新数据结构。只需使用 Protobuf 对数据结构进行一次描述,即可利用各种不同语言或从各种不同数据流中对你的结构化数据轻松读写。 Protocol buffers 很适合做数据存储或 RPC 数据交换格式。可用于通讯协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式。 此外,Protobuf由于其在内网高效的数据交换效率,是被广泛应用于微服务的,在谷歌的开源框架grpc即是基于此构建起来的。 php-protobuf安装由于protobuf原生并不支持php,所以php如果使用pb则需要安装相应扩展。 pecl install protobuf环境中需要有protoc编译器,下载安装方式: $ wget https://github.com/google/protobuf/releases/download/v2.5.0/protobuf-2.5.0.tar.gz$ tar zxvf protobuf-2.5.0.tar.gz$ cd protobuf-2.5.0$ ./configure --prefix=/usr/local/protobuf$ sudo make $ sudo make install验证安装成功: $ /usr/local/protobuf/bin/protoc --versionlibprotoc 2.5.0php-protobuf安装成功 php --ri protobuf安装lumen和google/protobuf依赖lumen new rpclumen new rpc命令相当于composer create-project laravel/lumen rpccomposer require google/protobuf在composer.json下添加classmap: { "classmap": [ "protobuf/" ]}ok,准备工作都已做好了。 自己做一个demo在代码目录下创建一个protobuf文件夹mkdir protobuf 进入该目录,创建一个文件searchRequest.proto syntax = "proto3";message SearchRequest { string query = 1; int32 page_number = 2; int32 result_per_page = 3; enum Corpus { UNIVERSAL = 0; WEB = 1; IMAGES = 2; LOCAL = 3; NEWS = 4; PRODUCTS = 5; VIDEO = 6; } Corpus corpus = 4;}????此处很重要????在composer.json下添加classmap,否则将无法侦测到对应class{ "classmap": [ "protobuf/" ]}在命令行下运行:protoc --proto_path=protobuf/ --php_out=protobuf/ protobuf/searchRequest.proto && composer dump-autoload ...

June 18, 2019 · 2 min · jiezi

规模化落地云原生阿里云即将重磅亮相-KubeCon-China

2019 年 6 月 24 日至 26 日, 由 Cloud Native Computing Foundation (CNCF) 主办的云原生技术大会 KubeCon + CloudNativeCon + Open Source Summit(上海 )即将在中国上海盛装启幕。 继 2018 年 KubeCon 首次成功登陆中国,本届 KubeCon 将吸引来自全世界数千名技术人员将会参加此次盛会,参与CNCF 全部项目和话题的深度探讨和案例分析,聆听 CNCF 项目的运维者和最终用户的分享。本届 KubeCon + CloudNativeCon + Open Source Summit 大会项目委员会由 75 名专家组成,审阅 KubeCon + CloudNativeCon 的 618 项提案,在本次 KubeCon China 2019 上,阿里巴巴共有 26 个技术演讲入选。 在本次 KubeCon 上,阿里云智能容器平台负责人丁宇(叔同)、 CNCF TOC、etcd 项目作者、阿里云容器平台资深技术专家李响,CNCF 大使、Kubernetes 项目维护者、阿里云高级技术专家张磊等众多云原生技术大咖都会悉数到场并做技术分享,同时会为您带来包括开源 Virtual Cluster 强多租户设计、 OpenKruise 开源项目、开放云原生应用中心(Cloud Native App Hub)等众多云原生先进技术的最新动态与进展。我们非常期待您能够在 KubeCon China 上与阿里容器平台团队见面、进行交流或者开展技术合作。 ...

June 18, 2019 · 2 min · jiezi

宜信开源微服务任务调度平台SIATASK入手实践

引言最近宜信开源微服务任务调度平台SIA-TASK,SIA-TASK属于分布式的任务调度平台,使用起来简单方便,非常容易入手,部署搭建好SIA-TASK任务调度平台之后,编写TASK后配置JOB进行调度,进而实现整个调度流程。本文新建了JOB示例,该JOB关联了前后级联的两个TASK,TASKONE(前置TASK)和TASKTWO(后置TASK),主要阐述一个JOB怎样关联配置两个级联TASK,以及该JOB是如何通过SIA-TASK实现任务调度,最终实现对两个TASK执行器的调用。 拓展阅读:宜信开源|宜信开源微服务任务调度平台SIA—TASK宜信开源|分布式任务调度平台SIA-TASK的架构设计与运行流程 首先,根据部署文档来搭建任务调度平台。源码地址:https://github.com/siaorg/sia... 官方文档:https://github.com/siaorg/sia... 任务调度平台主要由任务编排中心、任务调度中心以及ZK和DB等第三方服务构成,搭建SIA-TASK任务调度平台需要的主要工作包括: 1.MySQL的搭建及根据建表语句建表 2.zookeeper安装 3.SIA-TASK前端项目打包及部署 4.任务编排中心(sia-task-config)部署 5.任务调度中心(sia-task-scheduler)部署 从github上clone代码仓库并下载源码后,可根据SIA-TASK部署指南,搭建SIA-TASK任务调度平台并启动,详见SIA-TASK部署指南 搭建好SIA-TASK任务调度平台后,下一步就是TASK执行器实例的编写啦。 其次,根据开发文档来编写TASK执行器实例并启动。根据SIA-TASK开发指南,编写了两个TASK示例,TASKONE(前置TASK)和TASKTWO(后置TASK),具体开发规则见SIA-TASK开发指南,TASK示例关键配置即代码在下文有详细展示与介绍。 该示例为springboot项目,并且需要通过POM文件引入SIA-TASK的执行器关键依赖包sia-task-hunter来实现task执行器的自动抓取,首先需要将SIA-TASK源码中的sia-task-hunter包用mvn install命令打包为jar包安装至本地仓库,SIA-TASK源码中的sia-task-hunter包如下图示: 然后就可以进行示例的编写,示例主要包括以下几部分: 配置POM文件关键依赖 <!-- 此处添加个性化依赖(sia-task-hunter) --> <dependency> <groupId>com.sia</groupId> <artifactId>sia-task-hunter</artifactId> <version>1.0.0</version> </dependency>配置文件主要配置项 # 项目名称(必须) spring.application.name: onlinetask-demo # 应用端口号(必须) server.port: 10086 # zookeeper地址(必须) zooKeeperHosts: *.*.*.*:2181,*.*.*.*:2181,*.*.*.*:2181 # 是否开启 AOP 切面功能(默认为true) spring.aop.auto: true # 是否开启 @OnlineTask 串行控制(如果使用则必须开启AOP功能)(默认为true)(可选) spring.onlinetask.serial: true编写TASK执行器主要代码@Controllerpublic class OpenTestController { @OnlineTask(description = "success,无入参",enableSerial=true) @RequestMapping(value = "/success-noparam", method = { RequestMethod.POST }, produces = "application/json;charset=UTF-8") @CrossOrigin(methods = { RequestMethod.POST }, origins = "*") @ResponseBody public String taskOne() { Map<String, String> info = new HashMap<String, String>(); info.put("result", "success-noparam"); info.put("status", "success"); System.out.println("调用taskOne任务成功"); return JSONHelper.toString(info); } @OnlineTask(description = "success,有入参",enableSerial=true) @RequestMapping(value = "/success-param", method = { RequestMethod.POST }, produces = "application/json;charset=UTF-8") @CrossOrigin(methods = { RequestMethod.POST }, origins = "*") @ResponseBody public String taskTwo(@RequestBody String json) { Map<String, String> info = new HashMap<String, String>(); info.put("result", "success-param"+"入参是:"+json); info.put("status", "success"); System.out.println("调用taskTwo任务成功"); return JSONHelper.toString(info); }}当编写完TASK执行器实例后,启动该执行器所在进程启动日志如下图: ...

June 18, 2019 · 1 min · jiezi

聊聊微服务集群当中的自动化工具

本篇博客主要介绍了自动化工具这个概念,在微服务集群当中的作用,算抛砖引玉,欢迎大家提出自己的见解。 写在前面在了解自动化工具的概念之前,我们先了解一下微服务和集群的概念。 什么是微服务这个概念其实有些广泛,而我的知识广度也有限,我会尽量用通俗的语言来描述什么是微服务,什么是集群,以及为什么我们需要微服务集群 。为什么需要集群可以去看看《小强开饭店-从单体应用到微服务》,这篇文章用非常通俗的语言和配图,通过一个漫画故事简单的解释了为什么我们需要微服务集群。 微服务传统的后端服务多为单体应用,例如使用Sprint Boot或者Node又或者Gin搭建的简单的后端服务,在此基础之上,实现了基本的业务之后再部署到服务器上运行起来,这就成为了一个单体应用。 随着业务需求的增加、业务代码慢慢的累加,单体应用变的也越来越大。同时各个模块的大量业务代码相互纠缠在一起,开发以及维护变得尤其困难。想象一下一个刚刚加入项目的新人看到相互纠缠的、逻辑复杂的业务代码的绝望。 这个时候我们就需要了解微服务的概念了。如果想要讲这个庞大的单体应用可维护、可扩展以及高可用,我们就需要对单体应用按照模块进行业务拆分 。 例如将用户相关的所有逻辑单独搞成一个服务,又例如订单、库存可以搞成一个单独的服务。这样一来,业务代码被分散到几个单独的服务中,每个服务只需要关心、处理自己这个模块的业务逻辑。这样一来,业务代码的逻辑清晰,对开发人员来说,条理以及思路都很清晰。即使是后加入的项目开发人员,面对业务逻辑清晰的代码也十分容易上手。 微服务的拆分其实我看到很多的文章关于微服务的介绍就基本到这了,但是还有个值得提的概念。首先,微服务怎么拆分其实是没有一个标准的。 你按照什么样的粒度去拆分你的服务其实是跟业务强相关的。并不是说一个服务的代码一定就很少,根据你的业务的量度,例如你的系统用户量特比的大,那么一个用户服务的代码量上千上万行我觉得都很正常。 当然我也见过用户不是很多,只是为了高可用和快速定位,而将系统拆分的非常细的系统,有好几十个服务。那么问题来了,有这么多服务,前端需要去维护的后端API的地址就相当的庞大了。 我们暂且先不讨论所有拆分的服务是否运行在同一个服务器上,就算是,那也得是不同的端口。前端也需要根据后端拆分的服务模块,去维护这样一张API的映射表。所以我们需要提出一个BFF,AKA Backend For Frontend. BFF其实BFF层最初被提出来,其实不是为了微服务拆分模块中提到的目的。其设计的目的是为了给不同的设备提供不同的API。例如一个系统的后端服务,同时需要支持不同的终端,例如移动端的iOS和Android,PC端。 这样一来,可以根据不同设备上的需求来提供对应的API,而且不需要更改我们现有的微服务。 这样一来,我们的底层服务群就具有了很强的扩展性,我们不需要为一个新增的客户端来更改底层的服务代码,而是新增一层BFF层,来专门针对该终端类型去做适配。 大家从上面的图可以看出来,客户端都没有直接访问我们的底层服务。而是都先经过BFF层提供的接口,再由BFF层来根据不同的路由来调用不同的底层服务。总结一下,加了BFF层的优点如下。 扩展性强,可以适应不同的客户端统一的API管理,客户端无须再维护API的映射表可做集中鉴权,所有的请求都会先经过BFF,可在这一层对调用接口的合法性进行验证当然,BFF也有缺点。 处理不当会有大量的代码冗余因需要调用不同底层的服务而增大开发的工作量当然在实际的生产环境下,我们也很少会将BFF层直接暴露给客户端。我们通常会在BFF层上再加一层网关。网关可以在请求还没有到BFF的时候,实现权限认证,限流熔断等等其他的功能。 集群上面简单的聊了一下什么是微服务,现在我们来聊聊什么是集群。我们知道,当一个单体应用大的已经很难维护的时候,最好的办法就是将其拆分成微服务。这样有什么好处呢? 便于维护。每个微服务专注于自己这个模块的业务逻辑,不会存在各个模块的业务逻辑缠在一起的状况。提高可用性。当单体应用挂掉的时候,我们系统的所有模块都将不可用。而拆分成微服务就可以尽量的避免这个问题。单个服务挂掉了,不会影响到其他服务的正常运行。便于运维。单体应用重新部署的时候,会使整个系统不可用。而在微服务中,单个服务重新部署的代价明显要小的多。概念说了这么多,我们来给集群一个概念吧。集群就是将同一套服务部署在不同的服务器上,对外提供服务。 例子我举个具体的例子。例如我们使用Docker Swarm来提供容器的集群服务。 在Docker Swarm中有节点这样一个概念,凡是运行了Docker的主机都可以主动的创建一个Swarm集群或者加入一个已经存在的集群,一旦加入,这个主机就成为了这个集群中的一个节点。在集群中节点分为两类,分别是管理节点(manager)和工作节点(worker)。我们可以用Portainer来管理Docker主机和Swarm集群。 我们以一个集群中的请求来举个例子。 首先进入系统之后会先进入一个统一鉴权的系统去鉴权,鉴权成功之后就会到我们的微服务网关,如果这个地方还有系统自己的特殊鉴权的话,再次进行鉴权。之后网关这边会将我们的请求根据配置的路由来分发到具体的某个服务器上的某个容器中。 自动化工具自动化工具的都包含了哪些技术呢? 其中的Java只是一个类比,代表你的编程语言。微服务中其实不是很关心具体用的什么语言,甚至每个服务都用不同的技术栈都行。 那么自动化工具是什么呢?其作用是什么?在集群中扮演了什么样的角色呢?我们通过一张图来简单的了解一下。 构建简单的梳理一下逻辑。 首先自动化工具将Jenkins构建所需要的参数组织好,调用Jenkins的构建API,并记录构建操作到自动化工具的数据库然后Jenkins用配置好的凭证去Gitlab的对应的项目的分支拉取代码,根据配置好的构建脚本开始构建,记录构建记录到自动化工具的数据库构建好后再推送到docker的仓库中,并记录到自动化工具的数据库到此构建的逻辑结束。 其他的功能自动化工具还可以直接在项目列表中,选择查看当前项目的日志,而不需要每次重新打开Kibana然后再加筛选filter。 自动化工具的项目设置中,我们还可以更改docker容器的配置,而不需要再去portainer中或者通过命令行去修改;如果想要命令行进入容器,首先我们得找到对应的service,然后找到对应运行的service实例,然后才能进入,而如果我们直接使用portainer的Api,在endpoint已知的情况下,可以直接将这个功能做到自动化工具中,直接使用webshell一键连接。 其好处是什么呢? 对大部分开发屏蔽Swarm集群。对项目中非管理员的开发屏蔽Portainer,因为这个权限非常大,一旦不熟悉导致了误操作,那么有可能直接影响到线上的服务统一权限控制。在自动化工具里做权限以及环境的统一控制上手成本低。比起直接操作portainer和Jenkins和Kibana,自己搭建的自动化工具十分容易上手功能总结总结一下,其功能主要为以下几个。 构建部署回滚查看elk日志更改docker配置管理集群的环境、项目和容器命令行连接具体项目的容器…...看到这大家可能会有疑问。 构建?你的意思是我Jenkins是摆设咯?部署?更改 docker配置?命令行连接具体项目的容器?我的Iterm2也是个摆设?回滚?等于是我之前的docker镜像的tag白打了?elk日志?我的Kibana是拿来看新闻的吗?功能详解构建其实在构建这块,我个人认为自动化工具和Jenkins都很方便。而且自动化工具本身就是用的Jenkins,只不过是调用了Jenkins的API,传递了构建的参数,最终真正去构建的还是Jenkins。 只不过对于刚刚加入项目的测试来说,自己开发的Web UI对新人更加的友好,而且可以在自动化工具中做到权限控制。 部署和回滚部署在自动化工具的后端通过docker-client实现。首先我们根据配置,创建docker client。然后如果已经有在运行的服务了,就调用update service更新服务,否则就创建服务。 回滚与其本质相同,只不过是用了之前的参数和不同的tag。 elk日志首先,每个环境的配置中,会配置上kibana_host以及kibana_index,然后根据系统的projectKey,拼接成相应的Kibana日志的url,然后使用iframe嵌入到自动化工具中。这样一来就不用再手动的打开Kibana再去设置对应的filter了。特别是当你系统特别多的时候,添加和删除filter是很废时间的。 更新容器配置这里也同样是调用对应的API更新对应服务的配置,而不用登录portainer去修改。 同时,在自动化工具中还可以针对不同的环境配置不同的Base Setting。后续在该环境下添加的应用不用再单独配置,直接继承环境的Docker Setting即可。 管理集群的环境、项目和容器可以通过自动化工具统一的来创建和管理环境,同样有三种环境,研发、测试、生产环境。然后可以在自动化工具中创建角色和用户,分配给不同的角色不同的权限来达到控制权限的目的。 命令行连接具体项目的容器通常我们因为某个需求,需要进入到容器中查看,然而此时我们就面临两种选择。 通过portainer进入对应service,找个某个具体的container,点击连接命令行到容器具体运行的某个服务器上,然后再通过命令行连接但是有了自动化工具,我们就有了第三种选择。 点击连接怎么实现的呢?实际上就是通过endpointId去获取到所有的container的信息,然后遍历所有的container,找到与当前选中的containerId相同的容器,获取到其NodeName,这样一来我们就知道当前这个容器到底运行在哪个节点上的了。 然后通过已有的信息,构建WebSocket的url,最后前端通过xterm来建立ws连接,就这样直接连接了正在运行的容器实例。 总结自动化工具只是一种思路,一种解决方案,它的好处在上面也列出了很多。当然,它肯定也有坏处,那就是需要专门投入人力和资源去开发。 ...

June 17, 2019 · 1 min · jiezi

从遇见到信任-Apache-Dubbo-的毕业之旅

所谓信任,就是多一次机会。 2018年2月16日,Apache Dubbo 加入 Apache 基金会孵化器。 ... 2019年5月16日,Apache 软件基金会董事会决议通过了 Apache Dubbo 的毕业申请,这意味着 Apache Dubbo 正式成为 Apache 的顶级项目。5月21日,Apache 官方发布了这一消息。这也是 阿里巴巴微服务 继 Apache RocketMQ 后的又一个 Apache 顶级项目。 What is Dubbo ?Apache Dubbo 起初的定位是一款轻量级、高性能的服务框架,自 2012 年开源以来,深受国内开发者的喜爱,并被国内许多企业选型作为服务化改造的方案首选和微服务架构的基石之一。其主要功能是: 提供基于RPC的高性能接口,对用户透明。智能负载均衡:支持多种开箱即用的负载均衡策略,可以感知下游服务状态,从而减少总体延迟并提高系统吞吐量。自动服务注册和发现:支持多个服务注册表,可以立即在线/离线检测服务。高可扩展性:微内核和插件设计确保可以通过协议,传输和序列化等核心功能轻松扩展第三方实施。运行时流量路由:可以在运行时配置,以便根据不同的规则路由流量,这样可以轻松支持蓝绿部署,数据中心感知路由等功能。可视化服务治理:为服务治理和维护提供丰富的工具,例如查询服务元数据,运行状况和统计信息。Dubbo meets Apache2018 年 2 月,阿里巴巴将 Apache Dubbo 捐献给 Apache 软件基金会,得到了社区广泛的好评。 在这1年多的孵化过程中,Dubbo 社区: 持续迭代,共计发布11个版本;多元化治理,新增了6位 PPMC Member (孵化项目管理管理会成员),他们来自阿里巴巴、京东、美团点评、去哪儿、网易、微店、有赞等企业;并发展了15位项目提交者(对 Dubbo 项目具有提交权限),他们来自阿里巴巴、曹操科技、滴滴出行、国美金融、韩都衣舍、华为、京东、Keep、科大讯飞、美团点评、去哪儿、融贯电商、网联清算、网易、微店、亚信安全等10多家公司;构建多元化社区,Dubbo 主项目的贡献者从70+提升到目前的200位;用户多元化,阿里巴巴、当当、滴滴、海尔、去哪儿、网联清算、网易考拉、微店、中国电信、中国工商银行、中国人寿、中国银联等140多家公司在 GitHub 上报告了已将 Apache Dubbo 运用于生产环境中 ;GitHub 上的 star 数从入住孵化器前的17520增加到26400+,fork 数更是达到了17500+,fork 数排在所有Java 项目中的第三位;孵化过程中,Dubbo 社区的多样性得到了极大的发展,并不断演进核心和丰富生态,旨在为开发者们构建微服务和云原生支撑的基石。 ...

June 14, 2019 · 2 min · jiezi

关于分布式集群负载均衡微服务的关系说明

https://www.cnblogs.com/wmqia...

June 13, 2019 · 1 min · jiezi

技术选型之Docker容器引擎

题外话 最近对Docker和Kubernetes进行了一番学习,前两天做了一次技术分享,回去听了一遍自己演讲的录音,发现单单PPT做好还是远远不够的,没有提前准备好逻辑严谨的讲稿,在讲的时候出现了卡壳、漏掉技术点、逻辑矛盾的问题。为了解决这个问题,我打算以后在做技术分享前,都按着PPT的内容先写成博客,理顺表达逻辑。另外,我觉得每次技术分享使用的PPT都应该尽可能的做好,因为你不知道未来会不会还要拿来再讲几遍。本文以PPT+讲稿的方式编写,权当对自己这次技术分享做个记录,欢迎大家拍砖。 1. Docker出现的背景 在平常的研发和项目场景中,以下情况普遍存在: 个人开发环境 为了做大数据相关项目,需要安装一套CDH集群,常见的做法是在自己电脑里搭建3台与CDH版本对应的虚拟机,把CDH集群装起来后,考虑到以后很有可能还要使用一个干净的CDH集群,为了避免以后重复安装环境,通常会对整套CDH集群做一个备份,这样电脑里就有6个虚拟机镜像了。另外,后面在学习其他技术时,比如学习Ambari大数据集群,那么为了不破坏已有的虚拟机环境,又要重新搭建3台虚拟机,本机磁盘很快被一大堆的虚拟机镜像占满。公司内部开发环境 公司里往往会以小团队的方式来做项目,一般由运维部门从他们管理的服务器资源中分配出虚拟机供团队内部开发测试使用。比如做一个与机器学习相关的项目: 小明在运维部门分配的虚拟机上搭建了一套Ambari集群,拿来跑大数据相关业务小刚用python3写了一个机器学习算法,放到虚拟机上运行发现虚拟机里是python2,算法不兼容,于是把虚拟机里的python版本升级了,算法跑通了,但Ambari用到python的部分功能可能就报错了小李开发了应用,放到虚拟机上启动tomcat,发现虚拟机里的是OpenJDK,导致tomcat起不来,于是又安装了一个JDK,这时候可能Ambari里的Java代码可能就报错了小赵想利用服务器资源做性能测试,发现虚拟机严重削减了性能,最终还是要直接找物理机来跑测试,破坏了物理机原来的环境做完项目后,这些虚拟机上安装的东西往往变得没用了,下个项目组来还是得新申请虚拟机重新部署软件开发/测试/现场环境研发人员在开发环境里写好了代码做好测试后,提交给测试部门,测试人员在测试环境跑起来发现有BUG,研发人员说在开发环境没这个BUG,和测试人员多次扯皮解决BUG后发布版本,发到现场在生产环境部署后,又发现有BUG,这下轮到工程人员和测试人员扯皮。有时候为了兼容特殊的现场环境,还需要对代码进行定制化修改,拉出分支,这样导致了每次到现场升级都是一场噩梦升级或迁移项目 在每次发版本要升级到现场时,如果现场起了多个tomcat应用,那么需要对每个tomcat都先停掉,替换war包,然后再起起来,轮流着做,不仅繁琐而且很容易出错,如果遇到升级后出现严重BUG,还要手工做回退。另外,如果项目想上云,那么在云上部署后要重新进行一轮测试,如果后面考虑还云厂商,可能相同的测试还要再进行一次(比如更换了数据存储组件),费时费力。 总结以上列举的所有场景,他们存在的一个共同的问题是:没有一种既能够屏蔽操作系统差异,又能够以不降低性能的方式来运行应用的技术,来解决环境依赖的问题。Docker应运而生。 2. Docker是什么 Docker是一种应用容器引擎。首先说一下何为容器,Linux系统提供了Namespace和CGroup技术实现环境隔离和资源控制,其中Namespace是Linux提供的一种内核级别环境隔离的方法,能使一个进程和该进程创建的子进程的运行空间都与Linux的超级父进程相隔离,注意Namespace只能实现运行空间的隔离,物理资源还是所有进程共用的,为了实现资源隔离,Linux系统提供了CGroup技术来控制一个进程组群可使用的资源(如CPU、内存、磁盘IO等),把这两种技术结合起来,就能构造一个用户空间独立且限定了资源的对象,这样的对象称为容器。Linux Container是Linux系统提供的容器化技术,简称LXC,它结合Namespace和CGroup技术为用户提供了更易用的接口来实现容器化。LXC仅为一种轻量级的容器化技术,它仅能对部分资源进行限制,无法做到诸如网络限制、磁盘空间占用限制等。dotCloud公司结合LXC和以下列出的技术实现了Docker容器引擎,相比于LXC,Docker具备更加全面的资源控制能力,是一种应用级别的容器引擎。 Chroot:该技术能在container里构造完整的Linux文件系统;Veth:该技术能够在主机上虚拟出一张网卡与container里的eth0网卡进行桥接,实现容器与主机、容器之间的网络通信;UnionFS:联合文件系统,Docker利用该技术“Copy on Write”的特点实现容器的快速启动和极少的资源占用,后面会专门介绍该文件系统;Iptables/netfilter:通过这两个技术实现控制container网络访问策略;TC:该技术主要用来做流量隔离,限制带宽;Quota:该技术用来限制磁盘读写空间的大小;Setrlimit:该技术用来限制container中打开的进程数,限制打开的文件个数等也正是因为Docker依赖Linux内核的这些技术,至少使用3.8或更高版本的内核才能运行Docker容器,官方建议使用3.10以上的内核版本。3. 与传统虚拟化技术的区别 传统的虚拟化技术在虚拟机(VM)和硬件之间加了一个软件层Hypervisor,或者叫做虚拟机管理程序。Hypervisor的运行方式分为两类: 直接运行在物理硬件之上。如基于内核的KVM虚拟机,这种虚拟化需要CPU支持虚拟化技术;运行在另一个操作系统。如VMWare和VitrualBox等虚拟机。 因为运行在虚拟机上的操作系统是通过Hypervisor来最终分享硬件,所以虚拟机Guest OS发出的指令都需要被Hypervisor捕获,然后翻译为物理硬件或宿主机操作系统能够识别的指令。VMWare和VirtualBox等虚拟机在性能方面远不如裸机,但基于硬件虚拟机的KVM约能发挥裸机80%的性能。这种虚拟化的优点是不同虚拟机之间实现了完全隔离,安全性很高,并且能够在一台物理机上运行多种内核的操作系统(如Linux和Window),但每个虚拟机都很笨重,占用资源多而且启动很慢。 Docker引擎运行在操作系统上,是基于内核的LXC、Chroot等技术实现容器的环境隔离和资源控制,在容器启动后,容器里的进程直接与内核交互,无需经过Docker引擎中转,因此几乎没有性能损耗,能发挥出裸机的全部性能。但由于Docker是基于Linux内核技术实现容器化的,因此使得容器内运行的应用只能运行在Linux内核的操作系统上。目前在Window上安装的docker引擎其实是利用了Window自带的Hyper-V虚拟化工具自动创建了一个Linux系统,容器内的操作实际上是间接使用这个虚拟系统实现的。 4. Docker基本概念 Docker主要有如下几个概念: 引擎:创建和管理容器的工具,通过读取镜像来生成容器,并负责从仓库拉取镜像或提交镜像到仓库中;镜像:类似于虚拟机镜像,一般由一个基本操作系统环境和多个应用程序打包而成,是创建容器的模板;容器:可看作一个简易版的Linxu系统环境(包括root用户权限、进程空间、用户空间和网络空间等)以及运行在其中的应用程序打包而成的盒子;仓库:集中存放镜像文件的场所,分为公共仓库和私有仓库,目前最大的公共仓库是官方提供的Docker Hub,此外国内的阿里云、腾讯云等也提供了公共仓库;宿主机:运行引擎的操作系统所在服务器。5. Docker与虚拟机、Git、JVM的类比 为了让大家对Docker有更直观的认识,下面分别进行三组类比: 上图中Docker的镜像仓库类似于传统虚拟机的镜像仓库或存放镜像的本地文件系统,Docker引擎启动容器来运行Spark集群(容器内包含基础的Linux操作系统环境),类比于虚拟机软件启动多个虚拟机,在虚拟机内分别运行Spark进程,两者区别在于Docker容器内的应用在使用物理资源时,直接与内核打交道,无需经过Docker引擎。 Docker的仓库思想与Git是相同的。 Docker的口号是“Build,Ship,and Run Any App,Anywhere”,也就是可以基于Docker构建、装载和运行应用程序,一次构建到处运行。Java的口号是“Write Once,Run Anywhere”,即一次编写到处运行。Java是基于JVM适配操作系统的特点来屏蔽系统的差异,Docker则是利用内核版本兼容性的特点来实现一次构建导出运行,只要Linux系统的内核是3.8或更高的版本,就都能把容器跑起来。 当然,正如Java中如果应用代码使用了JDK10的新特性,基于JDK8就无法运行一样,如果容器内的应用使用了4.18版本的内核特性,那么在CentOS7(内核版本为3.10)启动容器时,虽然容器能够启动,但里面应用的功能是无法正常运行的,除非把宿主机的操作系统内核升级到4.18版本。6. Docker镜像文件系统 Docker镜像采用分层存储格式,每个镜像可依赖其他镜像进行构建,每一层的镜像可被多个镜像引用,上图的镜像依赖关系,K8S镜像其实是CentOS+GCC+GO+K8S这四个软件结合的镜像。这种分层结构能充分共享镜像层,能大大减少镜像仓库占用的空间,而对用户而言,他们所看到的容器,其实是Docker利用UnionFS(联合文件系统)把相关镜像层的目录“联合”到同一个挂载点呈现出来的一个整体,这里需要简单介绍一个UnionFS是什么: UnionFS可以把多个物理位置独立的目录(也叫分支)内容联合挂载到同一个目录下,UnionFS允许控制这些目录的读写权限,此外对于只读的文件和目录,它具有“Copy on Write(写实复制)”的特点,即如果对一个只读的文件进行修改,在修改前会先把文件复制一份到可写层(可能是磁盘里的一个目录),所有的修改操作其实都是对这个文件副本进行修改,原来的只读文件并不会变化。其中一个使用UnionFS的例子是:Knoppix,一个用于Linux演示、光盘教学和商业产品演示的Linux发行版,它就是把一个CD/DVD和一个存在在可读写设备(例如U盘)联合挂载,这样在演示过程中任何对CD/DVD上文件的改动都会在被应用在U盘上,不改变原来的CD/DVD上的内容。 UnionFS有很多种,其中Docker中常用的是AUFS,这是UnionFS的升级版,除此之外还有DeviceMapper、Overlay2、ZFS和 VFS等。Docker镜像的每一层默认存放在/var/lib/docker/aufs/diff目录中,当用户启动一个容器时,Docker引擎首先在/var/lib/docker/aufs/diff中新建一个可读写层目录,然后使用UnionFS把该可读写层目录和指定镜像的各层目录联合挂载到/var/lib/docker/aufs/mnt里的一个目录中(其中指定镜像的各层目录都以只读方式挂载),通过LXC等技术进行环境隔离和资源控制,使容器里的应用仅依赖mnt目录中对应的挂载目录和文件运行起来。 利用UnionFS写实复制的特点,在启动一个容器时, Docker引擎实际上只是增加了一个可写层和构造了一个Linux容器,这两者都几乎不消耗系统资源,因此Docker容器能够做到秒级启动,一台服务器上能够启动上千个Docker容器,而传统虚拟机在一台服务器上启动几十个就已经非常吃力了,而且虚拟机启动很慢,这是Docker相比于传统虚拟机的两个巨大的优势。 当应用只是直接调用了内核功能来运作的情况下,应用本身就能直接作为最底层的层来构建镜像,但因为容器本身会隔绝环境,因此容器内部是无法访问宿主机里文件的(除非指定了某些目录或文件映射到容器内),这种情况下应用代码就只能使用内核的功能。但是Linux内核仅提供了进程管理、内存管理、文件系统管理等一些基础且底层的管理功能,在实际的场景中,几乎所有软件都是基于操作系统来开发的,因此往往都需要依赖操作系统的软件和运行库等,如果这些应用的下一层直接是内核,那么应用将无法运行。所以实际上应用镜像往往底层都是基于一个操作系统镜像来补足运行依赖的。 Docker中的操作系统镜像,与平常安装系统时用的ISO镜像不同。ISO镜像里包含了操作系统内核及该发行版系统包含的所有目录和软件,而Docker中的操作系统镜像,不包含系统内核,仅包含系统必备的一些目录(如/etc /proc等)和常用的软件和运行库等,可把操作系统镜像看作内核之上的一个应用,一个封装了内核功能,并为用户编写的应用提供运行环境的工具。应用基于这样的镜像构建,就能够利用上相应操作系统的各种软件的功能和运行库,此外,由于应用是基于操作系统镜像来构建的,就算换到另外的服务器,只要操作系统镜像中被应用使用到的功能能适配宿主机的内核,应用就能正常运行,这就是一次构建到处运行的原因。 ...

June 13, 2019 · 1 min · jiezi

规模化落地云原生阿里云即将重磅亮相-KubeCon-China

2019 年 6 月 24 日至 26 日, 由 Cloud Native Computing Foundation (CNCF) 主办的云原生技术大会 KubeCon + CloudNativeCon + Open Source Summit(上海 )即将在中国上海盛装启幕。 继 2018 年 KubeCon 首次成功登陆中国,本届 KubeCon 将吸引来自全世界数千名技术人员将会参加此次盛会,参与CNCF 全部项目和话题的深度探讨和案例分析,聆听 CNCF 项目的运维者和最终用户的分享。本届 KubeCon + CloudNativeCon + Open Source Summit 大会项目委员会由 75 名专家组成,审阅 KubeCon + CloudNativeCon 的 618 项提案,在本次 KubeCon China 2019 上,阿里巴巴共有 26 个技术演讲入选。  在本次 KubeCon 上,阿里云智能容器平台负责人丁宇(叔同)、 CNCF TOC、etcd 项目作者、阿里云容器平台资深技术专家李响,CNCF 大使、Kubernetes 项目维护者、阿里云高级技术专家张磊等众多云原生技术大咖都会悉数到场并做技术分享,同时会为您带来包括开源 Virtual Cluster 强多租户设计、 OpenKruise 开源项目、开放云原生应用中心(Cloud Native App Hub)等众多云原生先进技术的最新动态与进展。我们非常期待您能够在 KubeCon China 上与阿里容器平台团队见面、进行交流或者开展技术合作。 ...

June 12, 2019 · 2 min · jiezi

小强开饭店从单体应用到微服务

本篇博客通过小强开饭店的通俗易懂的故事,带你了解后端服务是如果从单体应用演变到微服务的。如果有说的不对的地方,欢迎各位大佬强势怼。 小强开饭店有一天,小强为了早日奔赴小康生活,打算开一个饭店来帮他快速的实现这个目标。 饭店开业了于是他盘下了一个店面,一顿装修之后,雇了一个厨师,便开业了。 饭店生意变好了刚刚开业那段时间还好,店里的人虽然多,但是都还能应付的过来。 小强请的厨师手艺很好,再加上小强经营得当,宣传的也不错,慢慢的店里的生意越来越好。 慢慢的,顾客越来越多。很多时候厨师都忙不过来,大家只有排队在外面等着。渐渐的有些顾客变得十分不耐烦,等不下去了就走了,然后给了这家店差评。这种情况愈演愈烈,小强看到这不是个办法啊,得做点什么。 招聘厨师小强下了血本,又另外聘请了几位厨艺很好的厨师。 有了这些厨师的加盟,虽然客人很多,饭店的经营也还是能够勉强的应付的来。口碑也慢慢的由差变好。随着口碑的变好,慕名而来的也随之越来越多。 生意火爆随着顾客越来越多,即使厨房的厨师已经招聘满了,都还是应付不过来。 于是厨师也变成了暴躁的厨师。有的时候因为太忙了还罢工不干了。还得小强去苦口婆心的劝。小强心想这也不是个办法,再这么下去口碑又得下去。于是小强摇身一变,变成了强老板。 强老板开了分店强老板拿着开饭店赚的钱,在城里的很多地方开了分店,十分的膨胀。这样一来,客人不用大老远的跑到那一家店去了,可以选择离自己近的店。很快,原来的那家生意也渐渐的恢复正常,各个分店的业绩也有所提高。 但是随着强老板的强势宣传,以及顾客之间的自传播,这个参考被越来越多的人知道了。但是由于顾客分散,每家店的火爆程度都不同。有的店甚至陷入了跟最开始的店一样的境地,大量的顾客排队。但是有的店的生意却又十分冷清。 强老板心想,这肯定不行啊,这样下去早晚得血亏。于是强老板摇身一变,变成了强总。 强总开了个顾客中心 所有想去餐馆用餐的顾客都来这里,由强老板统一安排的大巴再送至各个分店。每辆车轮流的送至每一家分店。这样一来,就不存在某一家分店生意十分火爆而另外的店生意惨淡的情况了。 强总已达成奔赴小康的目标 读后感其实这个想法是很久以前不知道在哪儿看博客的时候,看到一位大佬的类比,确实是忘了。而最近刚好在准备分享,所以就打算详细的以图文和故事的方式来让没有了解过这方面的人快速的了解一下。 其实我也纠结过要不要将里面类比概念的解释穿插到故事里,但是后面想了一下,这样应该会干扰到大家对故事本身的理解,从而达不到通俗易懂的效果。所以我将解释单独放在了最后面。 单个饭店最开始的单个饭店其实就是一个App或者一个网站,来给用户提供服务。可以理解为前端,或者客户端。 单个饭店的厨师而单个饭店中的厨师,其实就是后端,提供数据,提供服务。一个厨师就对应着一个后端服务的实例。 随着App的访问量越来越大,最初的单体应用已经无法扛住这么大的压力了。导致其他的用户进入系统时,系统无法正常的服务。就跟我们现在打开一个网站一样,凡是超过2-3秒没有反应就直接宣告它的死刑了,直接退出-卸载二连。 单个饭店的多个厨师多个厨师则是相应的后端服务启动了多个实例,每个实例都是完全一样的,只不过是运行在不同的机器上或者不同的端口上。 每次的请求由这些实例来均摊,这样也的确能够暂时解决访问量大的问题。但是维护起来十分的麻烦,部署的流程也很繁琐。每次部署你得更新所有的实例,万一数量多,又在不同的机器上,很有可能因为操作失误引发线上的事故。而且有可能让老版本的服务兼容新版的前端或者客户端,造成不必要的BUG。 再退一万步,就算所有的实例都在同一个服务器上,万一真的访问量到了一定的量级,你得维护多少个实例啊。人工成本巨大。而且一不小心,一觉起来,本身没有问题的服务,因为一晚上发生了事件引发了热点,导致你的应用访问量剧增,增到超过你的所有实例能够承受的极限,服务挂了。 再退一万万步,就算你自己维护没有烦死,前端的兄弟可能早就收拾你了。你没有做请求分发的话,所有的服务器地址得由前端去维护。 分店这里的分店指微服务中的一个服务的多个实例。与之前人工维护的多个实例不同,这个是由工具帮我们维护。 这里我拿Docker Swarm举个例子。在Portainer中,你新建了一个服务之后可以选择设置Replicas,也就是实例的数量,当然默认是一个。你可以起10个,20个,但是这里得考虑到你的服务是否支持这样做。如果你的服务不是无状态应用,就很难做到可以自动的做横向扩展。 分店的生意火爆其实也是一样的,即使有很多个实例,你如果不能控制请求打到哪个服务上的话,某些实例承受的压力大了一样的会挂。 强总的顾客中心顾客中心大家可以理解为网关。更具体点可以理解为Zuul。 你的服务有了网关之后,所有的请求都从网关走。根据以及配置的路由,网关可以判断到你想具体到哪个服务去。 然后就会从自己的服务集群中找到对应的服务,获取到所有的服务实例的服务器IP以及端口。前面说到有可能请求会集中到某几个实例上。而我们可以使用工具来解决这个问题。例如,使用Spring Cloud的核心组件Ribbon。 这个组件的作用是做负载均衡,它可以使所有到某个服务的请求,平均的分发到该服务的每个实例上去。从而避免某几个服务的请求超过其能承受的阙值。当然,Ribbon需要和Spring Cloud的其他核心组件相互协作的。 另外一个版本的故事小强搞了个新闻App,用Spring Boot搭了一个后端,找人用React Native写了个App,就这样上线了。因为其内容和推广都还不错,所以受到了用户的喜爱。 但是随着访问量越来越大,服务器渐渐扛不住压力。有的用户进App之后甚至要5-6秒才有反应,而且慢的出奇。于是小强开始给服务尽量的无状态化,然后在一个服务器上启动了几个实例。 一段时间之后,访问量又增大了。小强只好硬着头皮,继续加实例数量,你强任你强,加实例我在行。 有一天,小强一觉起来,发现服务炸了...啊不是,是挂了。因为发生了一些事情引发了巨大的社会舆论,App的访问量剧增。导致新加的实例也没能扛住。 就这样,小强老实的开始了重构。使用Spring Cloud搭建了一个微服务集群,把服务拆分之后,给每个服务启动了几个实例。同时使用Eureka和Feign来进行服务之间的通信,使用Ribbon来做负载均衡。 就这样,这个App暂时稳定了下来。不过还有很多事情可以继续去做。 参考: 拜托!面试请不要再问我Spring Cloud底层原理往期文章: 什么?你竟然还没有用这几个chrome插件?手把手教你从零开始搭建SpringBoot后端项目框架用go-module作为包管理器搭建go的web服务器WebAssembly完全入门——了解wasm的前世今身相关: 个人网站: Lunhao Hu微信公众号: SH的全栈笔记(或直接在添加公众号界面搜索微信号LunhaoHu)

June 12, 2019 · 1 min · jiezi

WebSocket不再轮询

1.前言本文先讲解WebSocket的应用场景和特点,然后通过前后端示例代码讲解,展示在实际的开发中的应用。1.1. 应用场景WebSocket是一种在单个TCP连接上进行全双工通信的协议, 是为了满足基于 Web 的日益增长的实时通信需求而产生的。我们平时接触的大多数是HTTP的接口,但是在有些业务场景中满足不了我们的需求,这时候就需要用到WebSocket。简单举两个例子: (1) 页面地图上要实时显示在线人员坐标:传统基于HTTP接口的处理方式是轮询,每次轮询更新最新的坐标信息。 (2)手机的付款码页面,在外界设备扫描付款码支付成功后,手机付款码页面提示“支付成功”并自动关闭:传统方式还是轮询,付款码页面一直调用接口,直到从服务器获取成功支付的状态后,手机提示“支付成功”并关闭付款码页面。 HTTP 协议有一个缺陷:通信只能由客户端发起。这种单向请求的特点,注定了如果服务器有连续的状态变化,客户端要获知就非常麻烦。我们只能使用"轮询":每隔一段时候,就发出一个询问,了解服务器有没有新的信息。但这种方式即浪费带宽(HTTP HEAD 是比较大的),又消耗服务器 CPU 占用(没有信息也要接受请求)。 在WebSocket API尚未被众多浏览器实现和发布的时期,开发者在开发需要接收来自服务器的实时通知应用程序时,不得不求助于一些“hacks”来模拟实时连接以实现实时通信,最流行的一种方式是长轮询 。 长轮询主要是发出一个HTTP请求到服务器,然后保持连接打开以允许服务器在稍后的时间响应(由服务器确定)。为了这个连接有效地工作,许多技术需要被用于确保消息不错过,如需要在服务器端缓存和记录多个的连接信息(每个客户)。虽然长轮询是可以解决这一问题的,但它会耗费更多的资源,如CPU、内存和带宽等,要想很好的解决实时通信问题就需要设计和发布一种新的协议 1.2. WebSocket定义WebSocket是一种协议,是一种与HTTP 同等的网络协议,两者都是应用层协议,都基于 TCP 协议。但是 WebSocket 是一种双向通信协议,在建立连接之后,WebSocket 的 server 与 client 都能主动向对方发送或接收数据。同时,WebSocket在建立连接时需要借助 HTTP 协议,连接建立好了之后 client 与 server 之间的双向通信就与 HTTP 无关了。 相比于传统HTTP 的每次“请求-应答”都要client 与 server 建立连接的模式,WebSocket 是一种长连接的模式。就是一旦WebSocket 连接建立后,除非client 或者 server 中有一端主动断开连接,否则每次数据传输之前都不需要HTTP 那样请求数据。 WebSocket 对象提供了一组 API,用于创建和管理 WebSocket 连接,以及通过连接发送和接收数据。浏览器提供的WebSocket API很简洁,调用示例如下:HTTP、WebSocket 等应用层协议,都是基于 TCP 协议来传输数据的。我们可以把这些高级协议理解成对 TCP 的封装。既然大家都使用 TCP 协议,那么大家的连接和断开,都要遵循 TCP 协议中的三次握手和四次握手 ,只是在连接之后发送的内容不同,或者是断开的时间不同。对于 WebSocket 来说,它必须依赖 HTTP 协议进行一次握手 ,握手成功后,数据就直接从 TCP 通道传输,与 HTTP 无关了。 ...

June 10, 2019 · 1 min · jiezi

API网关如何实现对服务下线实时感知

上篇文章《Eureka 缓存机制》介绍了Eureka的缓存机制,相信大家对Eureka 有了进一步的了解,本文将详细介绍API网关如何实现服务下线的实时感知。 一、前言在基于云的微服务应用中,服务实例的网络位置都是动态分配的。而且由于自动伸缩、故障和升级,服务实例会经常动态改变。因此,客户端代码需要使用更加复杂的服务发现机制。 目前服务发现主要有两种模式:客户端发现和服务端发现。 服务端发现:客户端通过负载均衡器向服务注册中心发起请求,负载均衡器查询服务注册中心,将每个请求路由到可用的服务实例上。客户端发现:客户端负责决定可用服务实例的网络地址,并且在集群中对请求负载均衡, 客户端访问服务登记表,也就是一个可用服务的数据库,然后客户端使用一种负载均衡算法选择一个可用的服务实例然后发起请求。客户端发现相对于服务端发现最大的区别是:客户端知道(缓存)可用服务注册表信息。如果Client端缓存没能从服务端及时更新的话,可能出现Client 与 服务端缓存数据不一致的情况。 二、网关与Eureka结合使用Netflix OSS 提供了一个客户端服务发现的好例子。Eureka Server 为注册中心,Zuul 相对于Eureka Server来说是Eureka Client,Zuul 会把 Eureka Server 端服务列表缓存到本地,并以定时任务的形式更新服务列表,同时zuul通过本地列表发现其它服务,使用Ribbon实现客户端负载均衡。 正常情况下,调用方对网关发起请求即刻能得到响应。但是当对生产者做缩容、下线、升级的情况下,由于Eureka这种多级缓存的设计结构和定时更新的机制,LoadBalance 端的服务列表B存在更新不及时的情况(由上篇文章《Eureka 缓存机制》可知,服务消费者最长感知时间将无限趋近240s),如果这时消费者对网关发起请求,LoadBalance 会对一个已经不存在的服务发起请求,请求是会超时的。 三、解决方案3.1 实现思路生产者下线后,最先得到感知的是 Eureka Server 中的 readWriteCacheMap,最后得到感知的是网关核心中的 LoadBalance。但是 loadBalance 对生产者的发现是在 loadBalance 本地维护的列表中。 所以要想达到网关对生产者下线的实时感知,可以这样做:首先生产者或者部署平台主动通知 Eureka Server, 然后跳过 Eureka 多级缓存之间的更新时间,直接通知 Zuul 中的 Eureka Client,最后将 Eureka Client 中的服务列表更新到 Ribbon 中。 但是如果下线通知的逻辑代码放在生产者中,会造成代码污染、语言差异等问题。 借用一句名言: “计算机科学领域的任何问题都可以通过增加一个间接的中间层来解决” Gateway-SynchSpeed 相当于一个代理服务,它对外提供REST API来负责响应调用方的下线请求,同时会将生产者的状态同步到 Eureka Server 和 网关核心,起着 状态同步 和 软事物 的作用。 ...

June 10, 2019 · 2 min · jiezi

2019企业IT现状和趋势调研报告707的企业有云原生相关计划

2019年第一季度,灵雀云发起了“企业IT应用现状和云原生技术落地情况”的调研,通过定向邀请,3个月内共收集了400余份有效调研问卷,这些调研问卷80%以上都来自于国内政府、金融、能源、制造、汽车等传统行业的IT从业者。 发起本次调研的初衷,是我们希望了解当前企业,尤其是传统企业目前IT应用开发现状、以及以DevOps、Kubernetes、微服务等为代表的云原生技术在企业的应用情况,从而勾勒出传统行业IT发展趋势,并对于判断国内用户对云原生相关技术的认知度提供一个有价值的参考。 核心要点解读: 1、 约70%的参与调研者所在企业2019年IT预算有上浮; 2、 24.4%的参与调研者表示公司IT系统基本全靠自研,企业开始自建软件研发团队,主导IT应用的研发; 3、 70.7%的参与调研者所在企业表示在2019年有容器、DevOps和微服务方面的规划; 4、 11.4%的参与调研者所在企业已经试点了具有标杆意义的云原生实践,如精英团队的DevOps实践,小范围非核心应用的微服务拆分改造实践等。 本次调研的400多位调研对象中,80%以上来自金融、能源、制造、汽车等传统行业,其中17.3%来自基础架构部门, 22.5%来自运维部门,34.1%来自研发部门,还有约10%的被调研对象为企业的CIO/CTO等高级IT管理者。 被调研企业中,服务器规模在100-500台的比例为26.8%,500-1000台的企业占比22%,1000台服务器以上规模的14.6%。 IT系统自研还是外包 在数字化转型的背景下,传统外包的做法在被逐渐改变。在此次调查中,70.7%的参与调研者表示目前IT系统是自研外包兼而有之,其中核心业务系统以自己开发为主,24.4%的参与调研者表示公司IT系统基本全靠自研,只有4.9%的参与调研者选择了纯外包选项。这表明,企业开始不再将大部分业务系统,尤其是核心业务需求开发外包,开始自建软件研发团队,主导IT应用的研发。只有企业自己主导IT研发,才能够打造IT核心竞争力。 软件能力成为企业的核心竞争力,这恰好是数字化转型的本质之一。何谓成功的数字化转型?灵雀云认为,有三大衡量标志:IT部门由成本中心转为收入中心;企业自己主导IT产品的研发;改进工具、流程、文化来提高交付速度和质量。最终,实现客户满意度的提升、打造差异化竞争优势、加速产品上市。 IT系统更新频率 在IT系统更新频率方面,每月都要更新、升级的比例达到了51.2%的高占比。同时,每3-6个月更新一次的比例达22%。每个传统领域,都受到了来自Fintech金融科技、车联网、物联网、新零售等新技术驱动的创新业务的挑战,传统企业只有借助IT手段才能实现持续发展,在速度和规模上保持竞争力。 IT系统和研发团队TOP 3挑战 本次参与调研的企业以中大型企业为主,其中研发团队规模达到100人以上的比例高达44.3%,20-100人规模的占32.4%。 今天,许多企业都经过了大量IT建设,从分散到集中,造成IT系统越来越复杂,信息孤岛林立,架构臃肿等问题突出。调研中企业IT系统支撑所面临的压力位列前三的挑战分别是:系统复杂性越来越高(65.9%);应用交付压力大,交付速度无法满足业务需求(61.4%);运维管理复杂度提升,IT部门很难构建一支全功能团队(53.7%)。 同时,研发团队所面临的挑战前三甲分别是:部署和运维复杂,运维成本高(74.6%);研发、测试、运维等角色之间相互孤立(62.3%);升级和变更流程复杂,IT服务和应用交付周期长(45.7%)。此外,比较突出的挑战还有,工具链无法完整集成,工具使用困难(32.3%),单体应用过于庞大,迭代效率低下(20.4%)。 上述结果充分表明,面对高度创新、快速变化和充满不确定性的新型业务需求,传统开发模式和IT架构已经成为掣肘。70.7%的参与调研企业表示2019年有容器、DevOps和微服务方面的规划和实施计划。 只有朝着持续交付、敏捷部署、快速迭代,通过敏捷IT赋予业务足够的敏捷,才能够满足不断变化的业务需求,重塑自身的生产力,形成竞争优势,带来更好的用户体验,这最终落到以Kubernetes/容器、DevOps和微服务为核心的云原生技术的落地上。云原生架构和理念与数字化转型一脉相承,帮助企业更加顺畅地实施数字化转型。 业务上云需求最强烈,开源、数字化转型受追捧 在企业最关注的新兴技术趋势方面,云计算占比82.9%,企业将业务上云,提升IT资源效率作为首要关注对象。大数据和人工智能紧随其后,占比分别为73.2%和46.3%。其中开源解决方案在调研对象中的关注程度达到24.4%。 当前开源技术正在进入快速发展阶段,向着企业应用的方方面面深入。开源及开源社区不断将新的工具、方法和最佳实践用于云原生的实际业务用例,解决云原生用户的关键问题。借助许多开源解决方案,云原生部署的复杂性和难度也在得以降低。 此外,数字化转型的关注度为33.6%。如今每位IT从业者言必称数字化转型,IT能力也直接指向助力数字化转型。CIO和其他IT管理者已将企业的数字化计划置于新的高度,希望通过数字化来改变企业的商业和业务模式,数字化业务将从初步试验走向大规模应用。伴随企业数字化业务的不断成熟,预计未来几年,数字化转型将进入爆发阶段。 传统企业2019年IT预算稳中有升 本次调研中,被调研企业今年IT工作的重点包括业务上云(56.1%),云原生、大数据、人工智能等新技术采用(53.7%),打造数字化团队,引领企业的数字化创新(43.9%),选择传统业务应用的比例不足20%。越来越多的企业将工作负载放在云端,将正在开发的应用或服务托管在云平台上,云市场不断增长。 在IT预算方面,比客观经济形势略显乐观,和2018年IT预算相比,接近70%参与调研企业2019年的IT预算略有上浮,其中增长5%以内的企业占比37.5%,增长5-10%的企业占比21.2%,增长10%以上的企业达到12.7%。 此外,调研结果显示,数字化转型是一项需要通盘考虑的工作,需要项目管理部门、技术管理部门、开发部门、运维部门共同参与,制定统一的数字化转型方案和决策并推进。有些参与调研的企业特别强调2018年已经在全公司范围内试点了具有标杆意义的云原生实践,如精英团队的DevOps实践,小范围非核心应用的微服务拆分改造实践等,并且这些都将在2019年进行大范围推广。

June 3, 2019 · 1 min · jiezi

微服务网关实战Spring-Cloud-Gateway

作为Netflix Zuul的替代者,Spring Cloud Gateway是一款非常实用的微服务网关,在Spring Cloud微服务架构体系中发挥非常大的作用。本文对Spring Cloud Gateway常见使用场景进行了梳理,希望对微服务开发人员提供一些帮助。 微服务网关SpringCloudGateway 1.概述 Spring cloud gateway是spring官方基于Spring 5.0、Spring Boot2.0和Project Reactor等技术开发的网关,Spring Cloud Gateway旨在为微服务架构提供简单、有效和统一的API路由管理方式,Spring Cloud Gateway作为Spring Cloud生态系统中的网关,目标是替代Netflix Zuul,其不仅提供统一的路由方式,并且还基于Filer链的方式提供了网关基本的功能,例如:安全、监控/埋点、限流等。 2.核心概念 网关提供API全托管服务,丰富的API管理功能,辅助企业管理大规模的API,以降低管理成本和安全风险,包括协议适配、协议转发、安全策略、防刷、流量、监控日志等贡呢。一般来说网关对外暴露的URL或者接口信息,我们统称为路由信息。如果研发过网关中间件或者使用过Zuul的人,会知道网关的核心是Filter以及Filter Chain(Filter责任链)。Sprig Cloud Gateway也具有路由和Filter的概念。下面介绍一下Spring Cloud Gateway中几个重要的概念。 路由。路由是网关最基础的部分,路由信息有一个ID、一个目的URL、一组断言和一组Filter组成。如果断言路由为真,则说明请求的URL和配置匹配断言。Java8中的断言函数。Spring Cloud Gateway中的断言函数输入类型是Spring5.0框架中的ServerWebExchange。Spring Cloud Gateway中的断言函数允许开发者去定义匹配来自于http request中的任何信息,比如请求头和参数等。过滤器。一个标准的Spring webFilter。Spring cloud gateway中的filter分为两种类型的Filter,分别是Gateway Filter和Global Filter。过滤器Filter将会对请求和响应进行修改处理 如上图所示,Spring cloudGateway发出请求。然后再由Gateway Handler Mapping中找到与请求相匹配的路由,将其发送到Gateway web handler。Handler再通过指定的过滤器链将请求发送到我们实际的服务执行业务逻辑,然后返回。 快速入门 以Spring Boot框架开发为例,启动一个Gateway服务模块(以Consul作为注册中心),一个后端服务模块。client端请求经gateway服务把请求路由到后端服务。 前提条件: Consul:版本1.5.0。Spring bot:版本2.1.5。Spring cloud:版本Greenwich.SR1。Redis:版本5.0.5。1.微服务开发 这里以使用Spring Boot框架开发微服务为例,启动一个服务并注册到Consul。 引入依赖: <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-consul-discovery</artifactId></dependency>注册服务到Consul,配置文件配置如下: spring: application: name: service-consumer cloud: consul: host: 127.0.0.1 port: 8500 discovery: service-name: service-consumer如下定义RestController,发布HTTP接口。 ...

May 24, 2019 · 4 min · jiezi

宜信开源微服务任务调度平台SIATASK

背景无论是互联网应用或者企业级应用,都充斥着大量的批处理任务。常常需要一些任务调度系统帮助开发者解决问题。随着微服务化架构的逐步演进,单体架构逐渐演变为分布式、微服务架构。在此的背景下,很多原先的任务调度平台已经不能满足业务系统的需求。于是出现了一些基于分布式的任务调度平台。这些平台各有其特点,但各有不足之处,比如不支持任务编排、与业务高耦合、不支持跨平台等问题。非常不符合新一代微服务架构的需求,因此宜信公司开发了微服务任务调度平台(SIA-TASK)。 SIA是宜信公司基础开发平台Simple is Awesome的简称,SIA-TASK(微服务任务调度平台)是其中的一项重要产品,SIA-TASK契合当前微服务架构模式,具有跨平台,可编排,高可用,无侵入,一致性,异步并行,动态扩展,实时监控等特点。 项目简介SIA-TASK是任务调度的一体式解决方案。对任务进行元数据采集,然后进行任务可视化编排,最终进行任务调度,并且对任务采取全流程监控,简单易用。对业务完全无侵入,通过简单灵活的配置即可生成符合预期的任务调度模型。 SIA-TASK借鉴微服务的设计思想,获取分布在每个任务执行器上的任务元数据,上传到任务注册中心。利用在线方式进行任务编排,可动态修改任务时钟,采用HTTP作为任务调度协议,统一使用JSON数据格式,由调度中心进行时钟解析,执行任务流程,进行任务通知。 关键术语任务(Task): 基本执行单元,执行器对外暴露的一个HTTP调用接口;作业(Job): 由一个或者多个存在相互逻辑关系(串行/并行)的任务组成,任务调度中心调度的最小单位;计划(Plan): 由若干个顺序执行的作业组成,每个作业都有自己的执行周期,计划没有执行周期;任务调度中心(Scheduler): 根据每个的作业的执行周期进行调度,即按照计划、作业、任务的逻辑进行HTTP请求;任务编排中心(Config): 编排中心使用任务来创建计划和作业;任务执行器(Executer): 接收HTTP请求进行业务逻辑的执行;Hunter:Spring项目扩展包,负责执行器中的任务抓取,上传注册中心,业务可依赖该组件进行Task编写。微服务任务调度平台的特性基于注解自动抓取任务,在暴露成HTTP服务的方法上加入@OnlineTask注解,@OnlineTask会自动抓取方法所在的IP地址,端口,请求路径,请求方法,请求参数格式等信息上传到任务注册中心(zookeeper),并同步写入持久化存储中,此方法即任务;基于注解无侵入多线程控制,单一任务实例必须保持单线程运行,任务调度框架自动拦截@OnlineTask注解进行单线程运行控制,保持在一个任务运行时不会被再次调度。而且整个控制过程对开发者完全无感知。调度器自适应任务分配,任务执行过程中出现失败,异常时。可以根据任务定制的策略进行多点重新唤醒任务,保证任务的不间断执行。高度灵活任务编排模式,SIA-TASK的设计思想是以任务为原子,把多个任务按照执行的关系组合起来形成一个作业。同时运行时分为任务调度中心和任务编排中心,使得作业的调度和作业的编排分隔开来,互不影响。在我们需要调整作业的流程时,只需要在编排中心进行处理即可。同时编排中心支持任务按照串行,并行,分支等方式组织关系。在相同任务不同任务实例时,也支持多种调度方式进行处理。微服务任务调度平台设计SIA-TASK主要分为五个部分: 任务执行器任务调度中心任务编排中心任务注册中心(zookeeper)持久存储(Mysql) SIA-TASK的主要运行逻辑: 通过注解抓取任务执行器中的任务上报到任务注册中心任务编排中心从任务注册中心获取数据进行编排保存入持久化存储任务调度中心从持久化存储获取调度信息任务调度中心按照调度逻辑访问任务执行器 UI预览首页提供多维度监控 调度器信息:展示调度器信息(负载能力,预警值),以及作业分布情况。调度信息:展示调度中心触发的调度次数,作业、任务多维度调度统计。对接项目统计:对使用项目的系统进行统计,作业个数,任务个数等等。 调度监控提供对已提交的作业进行实时监控展示。 作业状态实时监控:以项目组为单位面板,展示作业运行时状态。实时日志关联:可以通过涂色状态图标进行日志实时关联展示。 任务管理:提供任务元数据的相关操作 任务元数据录入:手动模式的任务,可在此进行录入。任务连通性测试:提供任务连通性功能测试。任务元数据其他操作:修改,删除。 Job管理:提供作业相关操作 任务编排:进行作业的编排。发布作业: 作业的创建,修改,以及发布。级联设置:提供存在时间依赖的作业设置。 日志管理 本地日志:日志界面简洁,查询快速;日志提供7天的调度日志,以供快速查询。开源地址https://github.com/siaorg/sia-task作者:宜信开发平台负责人/资深架构师梁鑫

May 24, 2019 · 1 min · jiezi

简单才是王道刚开源的微服务任务调度平台SIATASK初探

导读:无论是互联网应用或者企业级应用,都充斥着大量的批处理任务。随着微服务架构的逐步推进,很多原先的任务调度平台已经不能满足业务系统的需求。宜信公司开源了微服务任务调度平台,其具有支持任务编排、支持跨平台等优秀特性,本文对该平台进行了说明。 背景 无论是互联网应用或者企业级应用,都充斥着大量的批处理任务。常常需要一些任务调度系统帮助开发者解决问题。随着微服务化架构的逐步演进,单体架构逐渐演变为分布式、微服务架构。在此的背景下,很多原先的任务调度平台已经不能满足业务系统的需求。于是出现了一些基于分布式的任务调度平台。这些平台各有其特点,但各有不足之处,比如不支持任务编排、与业务高耦合、不支持跨平台等问题。非常不符合新一代微服务架构的需求,因此宜信公司开发了微服务任务调度平台(SIA-TASK)。 SIA是宜信公司基础开发平台Simple is Awesome的简称,SIA-TASK(微服务任务调度平台)是其中的一项重要产品,SIA-TASK契合当前微服务架构模式,具有跨平台,可编排,高可用,无侵入,一致性,异步并行,动态扩展,实时监控等特点。 项目简介 SIA-TASK是任务调度的一体式解决方案。对任务进行元数据采集,然后进行任务可视化编排,最终进行任务调度,并且对任务采取全流程监控,简单易用。对业务完全无侵入,通过简单灵活的配置即可生成符合预期的任务调度模型。 SIA-TASK借鉴微服务的设计思想,获取分布在每个任务执行器上的任务元数据,上传到任务注册中心。利用在线方式进行任务编排,可动态修改任务时钟,采用HTTP作为任务调度协议,统一使用JSON数据格式,由调度中心进行时钟解析,执行任务流程,进行任务通知。 关键术语 任务(Task): 基本执行单元,执行器对外暴露的一个HTTP调用接口; 作业(Job): 由一个或者多个存在相互逻辑关系(串行/并行)的任务组成,任务调度中心调度的最小单位; 计划(Plan): 由若干个顺序执行的作业组成,每个作业都有自己的执行周期,计划没有执行周期; 任务调度中心(Scheduler): 根据每个的作业的执行周期进行调度,即按照计划、作业、任务的逻辑进行HTTP请求; 任务编排中心(Config): 编排中心使用任务来创建计划和作业; 任务执行器(Executer): 接收HTTP请求进行业务逻辑的执行; Hunter:Spring项目扩展包,负责执行器中的任务抓取,上传注册中心,业务可依赖该组件进行Task编写。 微服务任务调度平台的特性 基于注解自动抓取任务,在暴露成HTTP服务的方法上加入@OnlineTask注解,@OnlineTask会自动抓取方法所在的IP地址,端口,请求路径,请求方法,请求参数格式等信息上传到任务注册中心(zookeeper),并同步写入持久化存储中,此方法即任务; 基于注解无侵入多线程控制,单一任务实例必须保持单线程运行,任务调度框架自动拦截@OnlineTask注解进行单线程运行控制,保持在一个任务运行时不会被再次调度。而且整个控制过程对开发者完全无感知。 调度器自适应任务分配,任务执行过程中出现失败,异常时。可以根据任务定制的策略进行多点重新唤醒任务,保证任务的不间断执行。 高度灵活任务编排模式,SIA-TASK的设计思想是以任务为原子,把多个任务按照执行的关系组合起来形成一个作业。同时运行时分为任务调度中心和任务编排中心,使得作业的调度和作业的编排分隔开来,互不影响。在我们需要调整作业的流程时,只需要在编排中心进行处理即可。同时编排中心支持任务按照串行,并行,分支等方式组织关系。在相同任务不同任务实例时,也支持多种调度方式进行处理。 微服务任务调度平台设计 SIA-TASK主要分为五个部分: 任务执行器 任务调度中心 任务编排中心 任务注册中心(zookeeper) 持久存储(Mysql) SIA-TASK的主要运行逻辑: 通过注解抓取任务执行器中的任务上报到任务注册中心 任务编排中心从任务注册中心获取数据进行编排保存入持久化存储 任务调度中心从持久化存储获取调度信息 任务调度中心按照调度逻辑访问任务执行器 UI预览 首页提供多维度监控 调度器信息:展示调度器信息(负载能力,预警值),以及作业分布情况。 调度信息:展示调度中心触发的调度次数,作业、任务多维度调度统计。 对接项目统计:对使用项目的系统进行统计,作业个数,任务个数等。 调度监控提供对已提交的作业进行实时监控展示。 作业状态实时监控:以项目组为单位面板,展示作业运行时状态。 实时日志关联:可以通过涂色状态图标进行日志实时关联展示。 任务管理:提供任务元数据的相关操作 任务元数据录入:手动模式的任务,可在此进行录入。 任务连通性测试:提供任务连通性功能测试。 任务元数据其他操作:修改,删除。 Job管理:提供作业相关操作 任务编排:进行作业的编排。 发布作业: 作业的创建,修改,以及发布。 级联设置:提供存在时间依赖的作业设置。 日志管理 本地日志:日志界面简洁,查询快速;日志提供7天的调度日志,以供快速查询。 ...

May 24, 2019 · 1 min · jiezi

RabbitMQ高级特性消费端限流策略实现

应用范围为服务访问量突然剧增,原因可能有多种外部的调用或内部的一些问题导致消息积压,对服务的访问超过服务所能处理的最大峰值,导致系统超时负载从而崩溃。业务场景举一些我们平常生活中的消费场景,例如:火车票、机票、门票等,通常来说这些服务在下单之后,后续的出票结果都是异步通知的,如果服务本身只支持每秒1000访问量,由于外部服务的原因突然访问量增加到每秒2000并发,这个时候服务接收者因为流量的剧增,超过了自己系统本身所能处理的最大峰值,如果没有对消息做限流措施,系统在这段时间内就会造成不可用,在生产环境这是一个很严重的问题,实际应用场景不止于这些,本文通过RabbitMQ来讲解如果对消费端做限流措施。 消费端限流机制RabbitMQ提供了服务质量保证 (QOS) 功能,对channel(通道)预先设置一定的消息数目,每次发送的消息条数都是基于预先设置的数目,如果消费端一旦有未确认的消息,这时服务端将不会再发送新的消费消息,直到消费端将消息进行完全确认,注意:此时消费端不能设置自动签收,否则会无效。 在 RabbitMQ v3.3.0 之后,放宽了限制,除了对channel设置之外,还可以对每个消费者进行设置。 以下为 Node.js 开发语言 amqplib 库对于限流实现提供的接口方法 prefetch export interface Channel extends events.EventEmitter { prefetch(count: number, global?: boolean): Promise<Replies.Empty>; ...}prefetch 参数说明: number:每次推送给消费端 N 条消息数目,如果这 N 条消息没有被ack,生产端将不会再次推送直到这 N 条消息被消费。global:在哪个级别上做限制,ture 为 channel 上做限制,false 为消费端上做限制,默认为 false。建立生产端生产端没什么变化,和正常声明一样,关于源码参见rabbitmq-prefetch(Node.js客户端版Demo) const amqp = require('amqplib');async function producer() { // 1. 创建链接对象 const connection = await amqp.connect('amqp://localhost:5672'); // 2. 获取通道 const channel = await connection.createChannel(); // 3. 声明参数 const exchangeName = 'qosEx'; const routingKey = 'qos.test001'; const msg = 'Producer:'; // 4. 声明交换机 await channel.assertExchange(exchangeName, 'topic', { durable: true }); for (let i=0; i<5; i++) { // 5. 发送消息 await channel.publish(exchangeName, routingKey, Buffer.from(`${msg} 第${i}条消息`)); } await channel.close();}producer();建立消费端const amqp = require('amqplib');async function consumer() { // 1. 创建链接对象 const connection = await amqp.connect('amqp://localhost:5672'); // 2. 获取通道 const channel = await connection.createChannel(); // 3. 声明参数 const exchangeName = 'qosEx'; const queueName = 'qosQueue'; const routingKey = 'qos.#'; // 4. 声明交换机、对列进行绑定 await channel.assertExchange(exchangeName, 'topic', { durable: true }); await channel.assertQueue(queueName); await channel.bindQueue(queueName, exchangeName, routingKey); // 5. 限流参数设置 await channel.prefetch(1, false); // 6. 限流,noAck参数必须设置为false await channel.consume(queueName, msg => { console.log('Consumer:', msg.content.toString()); // channel.ack(msg); }, { noAck: false });}consumer();未确认消息情况测试在 consumer 中我们暂且将 channel.ack(msg) 注释掉,分别启动生产者和消费者,看看是什么情况? ...

May 23, 2019 · 2 min · jiezi

微服务注册中心注册表与hashcode实现golang版

背景基于负载均衡的服务调用基于负载均衡的服务相互调用指的是通过基于Lvs、Haproxy、Nginx等负载均衡软件来构建一个负载均衡服务,所有的服务调用都通过负载均衡器 从负载均衡的这种模式下其实有两个主要的问题: 一是中心化,整个系统都基于负载均衡器,负载均衡就相当于整个业务的中心,虽然我们可以通过一些高可用手段来保证,但其实内部流量通常是巨大的,很容易出现性能瓶颈二是增加了一次TCP交互 当然也有很多好处,比如可以做一些负载均衡、长链接维护、分布式跟踪等,这不是本文重点 基于注册中心的服务调用所有的服务都启动后都通过注册中心来注册自己,同时把注册中心里面的服务信息拉回本地,后续调用,就直接检查本地的服务和节点信息来进行服务节点的调用 注册中心中的注册表每个服务节点都会来注册中心进行服务注册,那数据如何在服务端进行保存呢,其实就是注册表,其实等同于windows 里面的注册表,每个服务都来注册,把自己的信息上报上来,然后注册中心吧注册表,返回给client端,那服务之间就知道要调用服务的节点啦 注册中心事件队列微服务注册注册中心通常会大量的服务注册, 那不能每次客户端来请求的时候,服务端都返回全量的数据,在数据传输的设计中,通常会有一种增量同步,其实在注册中心中也类似注册中心通过将最近的服务变更事件保存在一个事件队列中,后续每次客户端拉取只返回增量数据,这样服务端的忘了压力就会小很多 注册中心hashcode增量数据有一个问题就是,如果客户端错过啦某些事件,比如事件队列满了,则客户端与注册中心的注册表就会不一致, 所以eureka里面引入了一个hashcode的概念,通过比对hashcode是否相同, 如果不同则客户端需要重新全量拉取 代码实现系统架构系统整体上分为两个端:客户端(Client)和注册中心(Server)Server: 提供服务注册和获取注册表的接口, 同时本地把保存服务和节点的对应信息,变更事件写入eventQueueClient: 调用server接口进行服务注册, 同时调用注册表拉取接口进行注册表拉取,保存懂啊LocalRegistry 应用与节点信息Server端的服务注册表里面的服务和节点的信息,我通过Application和lease来维护Application: 代表一个应用,里面会包含服务对应的节点信息Lease: 维护一个节点的信息,比如心跳信息 服务端注册表注册表结构体服务端注册表结构体Registry主要包含三部分信息: lock(读写锁)、apps(应用对应信息)、eventQueue(事件队列)Lock: 注册中心是典型的读多写少的应用,server端注册表可能同时提供给N个服务进行读取,所以这里采用读写锁apps: 保存应用对应的信息, 其实后面写完发现,没必要使用,只使用基础的map就可以搞定eventQueue: 每次注册表变更都写入事件到里面 // Registry 注册表type Registry struct { lock sync.RWMutex apps sync.Map duration time.Duration eventQueue *EventQueue}注册表服务注册注册流程主要分为下面几部分: 从注册表获取对应的应用Application调用Application的add接口添加节点为节点创建一个Lease保存节点信息到Application.Node里将事件写入到eventQueue// Registr 注册服务func (r *Registry) Registr(name, node string) bool { r.lock.Lock() defer r.lock.Unlock() app := r.getApp(name) if app == nil { app = NewApplication(name) r.apps.Store(name, app) } if lease, ok := app.add(node, r.duration); ok { r.eventQueue.Push(&Event{lease: lease, action: ADD}) return true } return false}注册表拉取全量拉取通过all接口拉取全量的返回的是服务对应的节点切片增量拉取通过details接口返回增量的变更事件和服务端注册表的hashcode ...

May 23, 2019 · 2 min · jiezi

Kafka两级调度实现分布式协调微服务任务分配Golang版

背景基于Kafka消息队列的两级协调调度架构 Kafka内部为了协调内部的consumer和kafka connector的工作实现了一个复制协议, 主要工作分为两个步骤: 通过worker(consumer或connect)获取自身的topic offset等元数据信息,交给kafka的broker完成Leader/Follower选举worker Leader节点获取到kafka存储的partation和member信息,来进行二级分配,实现结合具体业务的负载均衡分配从功能实现上两级调度,一级调度负责将Leader选举,二级调度则是worker节点完成每个成员的任务的分配 主要是学习这种架构设计思想,虽然这种方案场景非常有限 基于消息队列实现分布式协调设计 一级协调器设计:一级协调器主要是指的Coordinator部分,通过记录成员的元数据信息,来进行Leader选举,比如根据offset的大小来决定谁是Leader二级协调器设计:二级协调器主要是指的Leader任务分配部分, worker节点获取到所有的任务和节点信息,就可以根据合适的算法来进行任务的分配,最终广播到消息队列 值得我们学习的地方, 通常在kafka这种场景下,如果要针对不同的业务实现统一调度,还是蛮麻烦的, 所以比如将具体任务的分配工作从架构中迁移出去, 在broker端只负责通用层的Leader选举即可, 将具体业务的分配工作,从主业务架构分离出去,由具体业务去实现 代码实现核心设计 根据设计,我们抽象出: MemoryQueue、Worker、 Coordinator、GroupRequest、GroupResponse、Task、Assignment集合核心组件 MemoryQueue: 模拟消息队列实现消息的分发,充当kafka broker角色Worker: 任务执行和具体业务二级协调算法Coordinator: 位于消息队列内部的一个协调器,用于Leader/Follower选举 Task: 任务Assignment: Coordnator根据任务信息和节点信息构建的任务分配结果GroupRequest: 加入集群请求GroupResponse: 响应信息 MemoryQueue核心数据结构// MemoryQueue 内存消息队列type MemoryQueue struct { done chan struct{} queue chan interface{} wg sync.WaitGroup coordinator map[string]*Coordinator worker map[string]*Worker}其中coordinator用于标识每个Group组的协调器,为每个组都建立一个分配器 节点加入集群请求处理 MemoryQueue 接收事件类型,然后根据事件类型进行分发,如果是GroupRequest事件,则分发给handleGroupRequest进行处理handleGroupRequest内部先获取对应group的coordinator,然后根据当前信息buildGroupResponse发回消息队列 事件分发处理func (mq *MemoryQueue) handleEvent(event interface{}) { switch event.(type) { case GroupRequest: request := event.(GroupRequest) mq.handleGroupRequest(&request) case Task: task := event.(Task) mq.handleTask(&task) default: mq.Notify(event) } mq.wg.Done()}加入Group组请求处理 ...

May 21, 2019 · 3 min · jiezi

微服务建设相关

本篇只讲下仅仅由于微服务后,需要改变增加的东西。可见:服务发现。拓扑。问题定位。监控。可控:全链路故障演练/压测。配置中心/全流程部署(部署会频繁,扩缩容频繁)本文服务发现问题定位/拓扑:https://segmentfault.com/a/11...全流程部署/配置中心/监控:https://segmentfault.com/a/11...全链路故障演练/压测:https://segmentfault.com/a/11...更多工程:https://segmentfault.com/a/11... 服务发现过程服务发现和注册文章:https://www.nginx.com/blog/se...。这里只讲下公司的应用方案 服务发现背景:替换原有Thrift要配置所有IP一个新服务,人工服务注册:创建一个服务发现节点,路由规则配置;流量调度;人工服务摘除;1.调用方每台机器上有agent.sdk.兜底文件,实时文件,访问disf先本地实时文件,兜底ip 兜底文件的产生:1.1代码扫描 与平台配置结果比对,校验1.2.编译后生成output/__naming__/__self.json1.3.odin构建到disf平台取配置作为兜底文件2.部署包根据部署集群,部署自己集群的__naming__/兜底ps:打包时会额外做:第一次打包生成__naming__目录,兜底包含所有ip,更新推方式更改扫描检查配置,第二次生成.deploy (部署节点信息,odin根据不同节点打包生成每个集群的ip)实时文件产生agent启动时。odin部署系统上线中,启动前会上报集群。到disf-agent。取配置。生成到/home/xiaoju/.service/disf中配置变更时。在平台操作,文件直接推送到agent。常态运行时,agent定期扫描文件版本上报平台,平台校验后下发版本异常文件到agentagent与conf长连接(grpc)交互过自动摘除/发现摘除:php类点多服务公用一个端口,一个unregester会导致所有都。上线时会大量的unregester或者exist出现抖动,机器挂掉不会自动摘除,只是返回一个ip列表(只有服务发现。dirpc负责负载均衡,健康检查,自动摘除)发现:不自动发现,起了服务不一定要正式到线上xrpc功能内置服务发现 (直接获取endpoint等) 支持多语言多协议 标准化日志输出&监控,标准化服务调用规范(统一提供sdk) 容错机制(故障摘除,过载保护,负载均衡) 代码1.获取endpoint信息,生成sign等初始2.server管理服务器,负载均衡,过载保护以逻辑机房为管理力度,minHealthyRatio等配置,ip/port列表,状态,balancer filter 白名单,黑名单,是否跨机房故障摘除 对节点采取投票,访问失败+1,访问成功-1,票数最低为0;每个节点的票数代表了其健康度,值越大说明越不健康,越小越健康; 投票数超过阈值即被认定为不健康,默认指导配置为10,即一个节点的投票数大于10则被认定为不健康。 流量调度只在健康节点进行选择,即非健康节点则认为被故障摘除。 另外节点被标记为非健康的时间超过冷却时长后,下次重新生成健康列表时,则将其当作健康节点对待,即故障恢复。默认指导值为60s。过载保护(只是防止可用节点太少,可用节点被打挂) 防止大量节点被标记为非健康后,流量打到集中的下游上,设置一个最低健康比例,当健康节点数的占比低于最低比例时,按最低比例挑选健康度最好的节点,构建健康列表,即最小可用度。随着业务的不断变迁,理想情况下最小可用度要能根据全链路容量压测进行自动适应。但根据滴滴目前的现状,资源管理还比较粗放,前期这个值可以设置的相对保守。当前最小可用度默认指导配置为0.67,即保证至少有2/3的节点可用,也即最多可剔除对1/3故障节点的访问。 负载均衡只支持在健康节点之间挑选,当最小可用度不足时,通过非健康节点补充。目前已支持Random、Hash两种调度策略,RR暂不支持。3.send4.根据是否成功 vote php是记到apcu(缓存中),不支持就无法投票。这个各自调用方投票记录在不同地方,各自判断,各自摘除。无法相互获取到其他的投票结果。5.记录log servicemesh概述架构上分为数据平面和控制平面两个部分,其中数据平面负责代理微服务之间的通信,具体包含RPC通信、服务发现、负载均衡、降级熔断、限流容错等,数据平面可以认为是微服务框架的通信和服务治理能力独立而成的一个单独的语言无关的进程,并且更注重通用性和扩展性,在Service Mesh中,不再将数据平面代理视为一个个孤立的组件,而是将这些代理连接在一起形成一个全局的分布式网络;控制平面负责对数据平面进行管理,定义服务发现、路由、流量控制、遥测统计等策略,这些策略可以是全局的,也可以通过配置某个数据平面节点单独指定,控制平面通过一定的机制将策略下发到各个数据平面节点,数据平面节点在通信时会使用这些策略。 Istio以Envoy为基础,将Envoy作为默认的数据平面,同时提供强大的控制平面能力。 控制:Pilot、Mixer和Security是Istio 的3大核心组件,分别负责Istio配置管理、策略和遥测以及通信安全的实现。pilot:提供通用的配置管理能力,保证不同平台、不同环境下的配置均能够通过一致的方式对Envoy进行配置和下方,负责Envoy的生命周期管理,启动Envoy,然后监控Envoy的运行状态.启动,调度到其他节点等mixer: 收集遥测统计相关的数据和指标,可以对服务进行全方位的监控,策略控制:对于一些核心资源,需要通过一定的策略,在不同消费服务以及服务的不同实例中进行分配,保证资源能够按照预期进行管理. 数据envoy是一个用 C++ 编写的云原生高性能边缘代理、中间代理和服务代理,作为专门为微服务架构设计的通信总线。Envoy作为一个独立进程,设计为和每个应用程序一块运行,所有的 Envoy 形成一个透明的通信网格,每个应用程序发送消息到本地Envoy代理或从本地Envoy代理接收消息,但不需要知道具体的网络拓扑。

May 14, 2019 · 1 min · jiezi

fong-纯typescript的node-gRPC微服务框架

简介fong: A service framework of node gRPC. github: https://github.com/xiaozhongliu/fong fong是一个完全用typescript编写的node gRPC框架, 可以基于它很方便地编写gRPC微服务应用. 一般是用来编写service层应用, 以供bff层或前端层等调用. 优点1.纯typescript编写, typescript的好处不用多说了. 并且用户使用这个框架框架时, 查看定义都是ts源码, 用户使用框架感受不到type definition文件. 2.效仿egg.js的『约定优于配置』原则, 按照统一的约定进行应用开发, 项目风格一致, 开发模式简单, 上手速度极快. 如果用过egg, 就会发现一切都是那么熟悉. 对比目前能找到的开源node gRPC框架很少, 跟其中star稍微多点的mali简单对比一下: 对比方面malifong项目风格约定 √定义查看跳转definition源代码编写语言javascripttypescriptproto文件加载仅能加载一个按目录加载多个代码生成 √中间件√√配置 √日志 √controller加载 √service加载 即将支持, 目前可以自己import即可util加载 即将支持, 目前可以自己import即可入参校验 即将支持插件机制 打算支持更多功能 TBD示例示例项目github: https://github.com/xiaozhongliu/ts-rpc-seed 运行服务使用vscode的话直接进F5调试typescript. 或者: npm start测试请求ts-node tester# 或者:npm run tscnode dist/tester.js使用目录约定不同类型文件只要按以下目录放到相应的文件夹即可自动加载. root├── proto| └── greeter.proto├── config| ├── config.default.ts| ├── config.dev.ts| ├── config.test.ts| ├── config.stage.ts| └── config.prod.ts├── midware| └── logger.ts├── controller| └── greeter.ts├── service| └── sample.ts├── util| └── sample.ts└── typings| ├── enum.ts| └── indexed.d.ts├── log| ├── common.20190512.log| ├── common.20190513.log| ├── request.20190512.log| └── request.20190513.log├── app├── packagen├── tsconfign└── tslintn入口文件import App from 'fong'new App().start()配置示例默认配置config.default.ts与环境配置config.<NODE_ENV>.ts是必须的, 运行时会合并. 配置可从ctx.config和app.config获取. ...

May 13, 2019 · 2 min · jiezi

springcloud二springcloudalibaba集成sentinel入门

Sentinel 介绍随着微服务的流行,服务和服务之间的稳定性变得越来越重要。 Sentinel 以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。 Sentinel 具有以下特征: 丰富的应用场景: Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、实时熔断下游不可用应用等。完备的实时监控: Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,甚至 500 台以下规模的集群的汇总运行情况。广泛的开源生态: Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring Cloud、Dubbo、gRPC 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel。完善的 SPI 扩展点: Sentinel 提供简单易用、完善的 SPI 扩展点。您可以通过实现扩展点,快速的定制逻辑。例如定制规则管理、适配数据源等。<!--more--> springcloud如何使用 Sentinel第一步:引入pom <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <version>0.2.1.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>第二步:新建一个启动类和controller @SpringBootApplicationpublic class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); }}@RestControllerpublic class TestController { @GetMapping(value = "/hello") @SentinelResource("hello") public String hello() { return "Hello Sentinel"; }}第三步:引入dashboard可以直接下载sentinel-dashboard的jar包,也可以自己编译,我这边这里clone了代码自己编译,代码地址:https://github.com/alibaba/Se...,执行命令 ...

May 11, 2019 · 1 min · jiezi

微服务的那些事

如何发布和引用服务服务提供者如何发布一个服务,服务消费者如何引用这个服务。具体来说,就是这个服务的接口名是什么?调用这个服务需要传递哪些参数?接口的返回值是什么类型?以及一些其他接口描述信息最常见的服务发布和引用的方式有三种:RESTful API (一般对外)XML配置 (对内)IDL文件(跨语言,Thrift, gRPC) 如何注册和发现服务在微服务架构下,主要有三种角色:服务提供者(RPC Server)、服务消费者(RPC Client)和服务注册中心(Registry)。 RPC Server提供服务,在启动时,根据服务发布文件server.xml中的配置的信息,向Registry注册自身服务,并向Registry定期发送心跳汇报存活状态。 RPC Client调用服务,在启动时,根据服务引用文件client.xml中配置的信息,向Registry订阅服务,把Registry返回的服务节点列表缓存在本地内存中,并与RPC Sever建立连接。 当RPC Server节点发生变更时,Registry会同步变更,RPC Client感知后会刷新本地内存中缓存的服务节点列表。 RPC Client从本地缓存的服务节点列表中,基于负载均衡算法选择一台RPC Sever发起调用。 注册中心实现方式注册中心的实现主要涉及几个问题:注册中心需要提供哪些接口,该如何部署;如何存储服务信息;如何监控服务提供者节点的存活;如果服务提供者节点有变化如何通知服务消费者,以及如何控制注册中心的访问权限。 注册中心必须提供以下最基本的API 服务注册接口:服务提供者通过调用服务注册接口来完成服务注册。服务反注册接口:服务提供者通过调用服务反注册接口来完成服务注销。心跳汇报接口:服务提供者通过调用心跳汇报接口完成节点存活状态上报。服务订阅接口:服务消费者通过调用服务订阅接口完成服务订阅,获取可用的服务提供者节点列表。服务变更查询接口:服务消费者通过调用服务变更查询接口,获取最新的可用服务节点列表。除此之外,为了便于管理,注册中心还必须提供一些后台管理的API,例如: 服务查询接口:查询注册中心当前注册了哪些服务信息。服务修改接口:修改注册中心中某一服务的信息。如何实现RPC远程服务调用在进行服务化拆分之后,服务提供者和服务消费者运行在两台不同物理机上的不同进程内,它们之间的调用相比于本地方法调用,可称之为远程方法调用,简称RPC。 想要完成远程调用,你需要解决四个问题: 客户端和服务端如何建立网络连接?服务端如何处理请求?数据传输采用什么协议?数据该如何序列化和反序列化?客户端和服务端如何建立网络连接客户端和服务端之间基于TCP协议建立网络连接最常用的途径有两种 HTTP通信Socket通信服务端如何处理请求同步阻塞方式 (适用于连接数比较小的业务场景)同步非阻塞方式 (用于连接数比较多并且请求消耗比较轻的业务场景,比如聊天服务器)异步非阻塞方式 (用于连接数比较多而且请求消耗比较重的业务场景)数据传输采用什么协议最常用的有HTTP协议,它是一种开放的协议,各大网站的服务器和浏览器之间的数据传输大都采用了这种协议。还有一些定制的私有协议,比如阿里巴巴开源的Dubbo协议等。 数据该如何序列化和反序列化常用的序列化方式分为两类: 文本类如XML/JSON等二进制类如PB/Thrift等如何监控微服务调用监控对象用户端监控。提供给用户的功能监控接口监控。依赖的具体RPC接口的监控资源监控。依赖的redis,mysql等基础监控。服务器本身的健康状况的监控监控指标请求量,请求量一般有二个维度,一个是实时请求量即QPS,还一个是统计请求量即PV。响应时间,可以用一段时间内所有调用的平均耗时来反映请求的响应时间错误率,通常用一段时间内调用失败的次数占调用总次数的比率来衡量监控维度全局维度分机房维度单机维度时间维度核心维度监控系统的步骤1.数据采集通常有两种数据收集方式: 服务主动上报,这种处理方式通过在业务代码或者服务框架里加入数据收集代码逻辑,在每一次服务调用完成后,主动上报服务的调用信息。代理收集,这种处理方式通过服务调用后把调用的详细信息记录到本地日志文件中,然后再通过代理去解析本地日志文件,然后再上报服务的调用信息。2. 数据传输数据传输最常用的方式有两种: UDP传输,这种处理方式是数据处理单元提供服务器的请求地址,数据采集后通过UDP协议与服务器建立连接,然后把数据发送过去。Kafka传输,这种处理方式是数据采集后发送到指定的Topic,然后数据处理单元再订阅对应的Topic,就可以从Kafka消息队列中读取到对应的数据。3. 数据处理数据处理是对收集来的原始数据进行聚合并存储。数据聚合通常有两个维度: 接口维度聚合,这个维度是把实时收到的数据按照接口名维度实时聚合在一起,这样就可以得到每个接口的实时请求量、平均耗时等信息。机器维度聚合,这个维度是把实时收到的数据按照调用的节点维度聚合在一起,这样就可以从单机维度去查看每个接口的实时请求量、平均耗时等信息。4. 数据展示数据展示是把处理后的数据以Dashboard的方式展示给用户。数据展示有多种方式,比如曲线图、饼状图、格子图展示等。 如何追踪微服务调用在微服务架构下,由于进行了服务拆分,一次请求涉及到多个服务调用,如果请求失败,就很难定位是在哪个环节出错,所以我们需要服务调用链的追踪。 服务追踪的作用优化系统瓶颈优化链路调用生成网络拓扑透明传输数据服务追踪系统原理它的核心理念就是调用链:通过一个全局唯一的ID将分布在各个服务节点上的同一次请求串联起来,从而还原原有的调用关系,可以追踪系统问题、分析调用数据并统计各种系统指标。 服务追踪系统可以分为三层 数据采集层,负责各个节点数据埋点并上报数据处理层。数据处理层,负责数据的存储与计算。数据展示层,负责数据的图形化展示。微服务治理的手段有哪些一次服务调用,服务提供者、注册中心、网络这三者都可能会有问题,此时服务消费者应该如何处理才能确保调用成功呢?这就是服务治理要解决的问题。 常用的服务治理手段节点管理服务调用失败一般是由两类原因引起的,一类是服务提供者自身出现问题,如服务器宕机、进程意外退出等;一类是网络问题,如服务提供者、注册中心、服务消费者这三者任意两者之间的网络出现问题。 无论是服务提供者自身出现问题还是网络发生问题,都有两种节点管理手段。 注册中心主动摘除机制这种机制要求服务提供者定时的主动向注册中心汇报心跳,注册中心根据服务提供者节点最近一次汇报心跳的时间与上一次汇报心跳时间做比较,如果超出一定时间,就认为服务提供者出现问题,继而把节点从服务列表中摘除,并把最近的可用服务节点列表推送给服务消费者。 服务消费者摘除机制虽然注册中心主动摘除机制可以解决服务提供者节点异常的问题,但如果是因为注册中心与服务提供者之间的网络出现异常,最坏的情况是注册中心会把服务节点全部摘除,导致服务消费者没有可用的服务节点调用,但其实这时候服务提供者本身是正常的。所以,将存活探测机制用在服务消费者这一端更合理,如果服务消费者调用服务提供者节点失败,就将这个节点从内存中保存的可用服务提供者节点列表中移除。 负载均衡常用的负载均衡算法主要包括以下几种。 随机算法轮训算法最少活跃调用算法一致性Hash算法服务路由对于服务消费者而言,在内存中的可用服务节点列表中选择哪个节点不仅由负载均衡算法决定,还由路由规则确定。所谓的路由规则,就是通过一定的规则如条件表达式或者正则表达式来限定服务节点的选择范围。 为什么要制定路由规则呢?主要有两个原因。 业务存在灰度发布的需求多机房就近访问的需求服务容错服务调用并不总是一定成功的,对于服务调用失败的情况,需要有手段自动恢复,来保证调用成功。服务容错常用的手段主要有以下几种。 FailOver:失败自动切换。就是服务消费者发现调用失败或者超时后,自动从可用的服务节点列表总选择下一个节点重新发起调用,也可以设置重试的次数。FailBack:失败通知。就是服务消费者调用失败或者超时后,不再重试,而是根据失败的详细信息,来决定后续的执行策略。FailCache:失败缓存。就是服务消费者调用失败或者超时后,不立即发起重试,而是隔一段时间后再次尝试发起调用。FailFast:快速失败。就是服务消费者调用一次失败后,不再重试。一般情况下对于幂等的调用,可以选择FailOver或者FailCache,非幂等的调用可以选择FailBack或者FailFast。 参考资料极客专栏:从0开始学微服务 本文亦在微信公众号【小道资讯】发布,欢迎扫码关注!

May 8, 2019 · 1 min · jiezi

后端好书阅读与推荐续七

后端好书阅读与推荐系列文章: 后端好书阅读与推荐后端好书阅读与推荐(续)后端好书阅读与推荐(续二)后端好书阅读与推荐(续三)后端好书阅读与推荐(续四)后端好书阅读与推荐(续五)后端好书阅读与推荐(续六)后端好书阅读与推荐(续七) Spring微服务实战Spring微服务实战 (豆瓣): https://book.douban.com/subje... Spring体系用来做微服务在当下可谓是风头正劲,这本书就用一个实例来手把手教我们实现微服务。实战系列的口碑一直不错,这本也不例外,看完就可以对微服务的概念有一个完整的理解,并且想上手也有路可寻。 亮点: 编码就像艺术,是一种创造性活动。构建分布式应用就像指挥一个管弦乐队演奏音乐,是一件令人惊奇的事情微服务是一个小的、松耦合的分布式服务,分解和分离大型复杂应用程序的功能,使它们独立,这样负责每个功能的团队都拥有自己的代码库和基础设施,技术不限,能灵活地独立开发部署,职责分离,降低团队协作成本。随着云的普及,微服务单元的增减(每个服务单元都是完整的)变得非常容易,使得整个应用更具弹性伸缩能力。Spring勇于自我革新,当初出场取代了沉重的J2EE,后面的Spring Boot使用简单注解去掉了自己原本繁重的配置,后来的Spring Cloud更是为分布式服务提供了一套完整的解决方案,所以Spring体系是我们构建微服务的绝佳选择微服务构建的一个关键是划分,而划分的一个关键是粒度,这个没有绝对标准,但是有几个原则:开始可以让服务设计范围大一些,后续可以重构至更小的服务;重点关注服务之间如何交互;随着对问题域的理解变化,服务的职责也要变化(演化思维)。需要注意微服务的几个坏味道(太粗;太细):职责过多,跨表超过5个,URL太长,测试用例太多;数量巨大、彼此依赖严重、成为简单的curd、只在一个表操作等微服务没有标准,但是作者提出了12种最佳实践:独立代码库、显式依赖、配置存储、后端可切换、构建发布运行必须完整、进程无状态、端口命令行制定、横向扩展并发、可down可up、环境一致、日志流式处理、管理脚本化。微服务的生命周期:装配、引导、发现、服务和监控、关闭少量程序可以使用低层级文件(YAML、json、XML)来存储配置,将其与代码分开,但是到了数百单元(每个单元可能有多个实例)时就不行了。手动管理既慢又复杂还可能配置漂移,这时应该引入配置管理工具(etcd、eureka、consul、zookeeper、spring cloud config等),服务启动时通过环境变量注入配置或者从集中式存储中获取服务发现提供了快速水平伸缩的能力,且当服务不健康时可以快速删除,还能取代传统的手动管理负载均衡。主要涉及服务注册、客户端服务地址查找、信息共享、健康监测4个方面一般大家关注高可用都是某个组件彻底不可用(容易检测)的情况,但是一个性能不佳而没有完全垮掉(不易检测)的服务也可以彻底拖垮整个应用,因此也需要保护这些不佳服务,避免连锁效应,导致彻底不可用。一般有客户端负载均衡(Ribbon)、断路器、后备、舱壁(Hystrix)等四个弹性模式来实现保护缓冲区AOP的思想帮我们分离关注点,那么要在微服务中实现靠啥?答案就是网关(比如Zuul,核心就是反向代理)了,我们可以在网关中实现验证授权、数据搜集与日志等关注点,但是要注意,避免网关成为单点要注意使其轻量且无状态(无状态就可以很容易扩展,而服务发现必须有状态,所以要扩展还要用Gossip等协议来同步状态数据,保障一致性和可用性)安全注意事项:都要使用HTTPSSSL、所有服务都要经过网关、将服务划分为公共API和私有API、封锁不必要的端口来限制微服务的攻击面微服务不是单体,其好处是灵活,代价就是解决问题时难以定位,所以需要追踪并聚合日志,最终定位问题。spring cloud 给每一个事务开启之前注入关联ID(一般由网关完成),每个服务都可以传播关联ID并将其添加进日志中,这些日志被统一发送到日志聚合点中,就可以供我们统一检索了,常见实现有ELK、Splunk等。光能检索还不够,一张直观的事务流图抵过1万条日志,Sleuth和ZipKin一起可以实现该功能微服务强调灵活迅速,所以一个成熟的构建与部署管道(引入CI/CD)必不可少:提交代码、自动构建(钩子实现)、构建期间进行单元测试与集成测试后获得一个jar(自包含tomcat)、用jar构建并提交机器镜像、在新环境中拉取机器镜像并进行平台测试后启动镜像、每个新环境都要重复前面一个步骤书很厚,所以很多具体工具可以跳过,尝试几个即可,将来使用的时候知道这本书里有就行了。 持续交付持续交付 (豆瓣): https://book.douban.com/subje... 微服务离不开CI/CD,而CI/CD核心就是几点:自动化、连续、小范围、快速、可靠。我们通过这本书来了解CI/CD,也看看持续交付是如何解决“Last Mile”问题,让软件交付不再令人紧张,成为一件简单平常的事情。 亮点: 从决定修改到结果上线的时间为周期时间,CI/CD技术能让周期从传统手段的周月单位变成小时甚至分钟级别(产品快速落地,降低机会成本),发布过程可靠可重复(减少错误与人力资源),核心技术就是部署流水线(一个应用从构建、部署、测试到发布这整个过程的自动化实现,过程中需要的所有东西包括需求、源码、配置、脚本、文档、运行环境等都要纳入VCS的管理,但是结果性的东西比如二进制镜像就不用,因为它可以随时构建得到,作者罗列了一些相应的工具)提出了一些反模式,让我们避免:手工部署软件(复杂 不可重复和审计 易出错)、开发完成之后才向类生产环境部署(不确定性很大 发布有风险)、生产环境手工配置管理(不能完全掌握 不可重复)。同时也提出了一些应该遵循的正面原则持续集成的前提是版本控制、自动化构建、团队共识,需要做到频繁提交、自动化测试、保持构建和测试过程较短、管理开发工作区、构建失败之后修复成功之前不能提交新代码、提交之前自己运行测试、提交测试通过之后再继续工作、时刻准备回滚(回滚之前要有一个修复时间)、为自己的问题负责、测试驱动等等持续集成能提高团队对复杂系统的自信心与控制力,其主要关注是开发团队,并不能解决软件部署、交付过程的低效,所以需要一个端到端的自动化构建、部署、测试和发布流程,也就是部署流水线(关注的是软件从CVS到用户手中这个过程),它有一些相关实践:只生成一次二进制包、不同环境统一部署、对部署进行冒烟测试、向生产环境的副本部署、每次变更都要立即在流水线中传递、只要有环节失败停止整个流水线。CI/CD的关键都是记录变更,为尽早发现问题提供信息,消除低效环节部署流水线的几个要点:构建与部署脚本化(配置初始化数据、基础设施、中间件等)、提交阶段快速反馈与及时修复、自动化验收测试(验收测试是验证客户的期待,单元测试是验证开发人员的期待)、注意非功能测试(主要指性能)、部署与发布要有策略并且可重复执行(文本化)且可回滚(不同版本文件不删除,用符号链接到当前版本)作者说无论项目大小都应使用CI/CD,这个我感觉有点偏激了,所谓磨刀不误砍柴工,前提是这个柴要么很多,要么很大,如果只是几根细柴,有那个磨刀的功夫柴都砍完了。但是实际工作中这么小的项目应该很少,所以大多数项目我们还是都还是应该搭建部署流水线,用上CI/CD。书很厚,其实好多地方可以跳过,你只需要看标题就能抓住主旨而无需多看。PS:可以先看看这本持续集成。 敏捷革命敏捷革命:提升个人创造力与企业效率的全新协作模式 (豆瓣): https://book.douban.com/subje... CI/CD 实际上正是敏捷开发的最佳实践,有了前面的铺垫,我们可以通过这本书我们来真正了解敏捷开发的全貌。 亮点: 2005年之前,大多数软件开发项目都是采用“瀑布法”:整个项目被划分为多个阶段,每个阶段都要经过严格的评审,以期为客户或软件使用者提供完美的产品(甘特图表现),每一阶段的工作做得足够好时才允许进入下一阶段。这种工作方式看似完美,实际全凭想象和猜测、华而不实,往往导致开发进度缓慢,常常超出预算,且现实和规划差异巨大,Scrum(敏捷开发流程)的出现就是解决这个问题的(不凭猜测,而是PDCA:计划、执行、检查、行动)任何项目的管理都需要实现两个目标:可控性与可预测性管理层的职责在于制定战略目标,团队的工作则是决定如何完成目标。无论任何人,只要不在现场,都不可能及时跟上事态变化的步调,所以团队要有自主决策权,此外一个团队需要包含完成一个项目的所有技能,同时要小而精(7人左右)。团队成员之间不要互相指责,而是尽量改善制度“冲刺”(一般以星期为周期)可以让团队成员集中精力快速做出成果并得到反馈,“每日立会”(15分钟以内)能让成员清楚地知道冲刺进度如何。Scrum的核心就是节奏确定懂项目、懂市场、懂顾客的产品负责人,拟定待办事项清单并检测两遍,重要的事情优先做这本书细看的话真的很洗脑,看完感觉自己迫不及待地想要冲进一家公司试试Scrum了。 DevOps实践指南DevOps实践指南 (豆瓣): https://book.douban.com/subje... DevOps是软件开发、运维和质量保证三个部门之间的沟通、协作和集成所采用的流程、方法和体系的一个集合(所以也要基于CI/CD,前4本书可以看做一个连续的专题,核心都是敏捷)。它取代了传统开发、测试、运维职责大分离的思想,填平了部门之间的鸿沟,让大家更有效的工作。我们可以通过这本书来对DevOps有一个全面的了解。 亮点: 开发部通常负责对市场变化做出响应,以最快的速度将新功能或者变更上线。而运维部则要以提供稳定、可靠和安全的服务为已任,让任何人都很难甚至无法引入可能会危害生产环境的变更。这种配置方式让开发部门和IT运维部门的目标和动因之间存在“根本的、长期的冲突”——公司对不同部门的考核和激励不同,这种冲突造成了一种恶性循环,阻碍了公司全局目标的实现。DevOps能够提高公司业绩,实现开发、QA、IT运维、信息安全等各职能技术角色的目标,同时改善人们的境遇DevOps是精益、约束理论、丰田生产系统、柔性工程、学习型组织、康威定律等知识体系的集大成者DevOps“三步工作法”:流动、反馈、持续学习与实验,并阐述了DevOps实施需遵守的原则与最佳实践(流动:它加速了从开发、运维到交付给客户的正向流程;反馈:它使组织构建安全可靠的工作体系,并获得反馈;持续学习与实验:它打造出一种高度信任的文化,并将改进和创新融入日常工作中)为了能识别工作在哪里流动、排队或停滞,需要将工作尽可能地可视化,如在看板或Sprint计划板上,使用纸质或电子卡片将各项工作展示出来,通过这种方式,不仅能将工作内容可视化,还能有效地管理工作,加速其从左至右的流动,还可以通过卡片从在看板上创建到移动至“完成”这一列,度量出工作的前置时间。此外,看板还能控制在制品数量(队列长度)文中关于小批量和大批量的差异,我以前在博客中也提到过。如此看来,两种方式各有优劣,关键看能分配的资源是什么?更追求总体效率还是效果出现的等待时间?对返工的要求是什么?再来决定使用方法第一步描述的原则,使得工作能够在价值流中从左向右快速地流动。第二步描述的原则,使得在从右向左的每个阶段中能够快速、持续地获得工作反馈。快速发现问题、群策群力解决问题,可以避免把问题带入下游环节,避免修复成本指数增加。根据精益原则,我们最重要的客户是我们的下游(不一定是最终付费用户),为他们而优化我们的工作,在源头保障质量。第三步描述的原则可以持续提升个人技能,进而转化为团队的财富......感觉历史的天平总是左右摇摆,一开始职责混乱、一个人干所有的事,后来职责分离、分工明确,现在又提倡填平鸿沟、部门融合。随着时代的发展,适用于时代的技术也总是不停变更,要想不被淘汰就得终身学习呀。 Web容量规划的艺术Web容量规划的艺术 (豆瓣): https://book.douban.com/subje... 容量规划(很早就有了,如道路规划、电力运营)是一门省钱的艺术,保证用合理的资源来实现最大化需求,通过这本书我们来敲开容量规划在互联网世界中实际运用的大门。 亮点: 容量规划整个过程:首先要明确定义响应时间、可供消耗容量以及峰值驱动处理等明确指标来定义总体负载和容量需求,然后了解当前基础设施的负荷特征,预测需要的资源来达到这种性能,然后如何管理资源,最后不断迭代,最终目标介于“没有买足够资源”和“浪费太多资源”之间有几个方法:预测系统何时失败、用统计图表(比数字更直观)呈现问题、性能调优与容量规划要同步进行、搜集数据驱动未来的规划测量是规划的前提,要有坚实的数据支撑而不是猜测,有许多工具可以测量,他们应该可以随着时间记录和保存数据、自定义度量、比较指标、导入和导出指标,当然测量工具本身要轻量,尽量对资源本身影响较小。如果说测量是对已有情况的了解,那么估计就是根据数据趋势预测未来。预测部分靠直觉,部分靠数学。我们可以做曲线拟合,注意到趋势和变更,并进行迭代和校准(看来基于机器学习或者说AI的运维是未来啊)文章除了基于传统模式的容量规划,还涉及到了基于虚拟化和云计算的模式,所以我们学习也要注意趋势和变化。 领域驱动设计领域驱动设计 (豆瓣): https://book.douban.com/subje... 构建程序之前,我们都要对业务进行梳理和理解,然后是领域划分与建模等一系列重要步骤,最后才是编码实现,这就是一本讲解这些步骤的好书。而且本书会告诉你,设计和实现可以交错进行和演化,来达到最优。还提出了专业术语,你在和别人交流时可以使用。我在读到假同源这个词语时真是犹如醍醐灌顶,因为之前开发项目就有过:同一个对象,这个模块改吧改吧,那个模块改吧改吧,最后导致对两个模块而言,这个对象都不完全属于它,要修改都得小心翼翼怕影响对方,本书告诉我,遇上假同源,要么重新理解和建模,统一该对象表示,要么果断分开这两个模块,用两个对象分别服务这两个模块。 亮点: 模型是一种简化,是对现实的解释,把与解决问题密切相关的方面抽象出来,而忽略无关的细节(所以需要我们消化和提炼已有知识,包括深层次探索)。用户应用软件的问题区域就是软件的领域(有形的如机票系统,无形的如会计系统)。成功的项目有一个共同特征:都有一个丰富的领域模型,这个模型在迭代设计过程中不断演变(我们要持续学习),与实现绑定,成为项目不可分割的一部分很多因素可能会导致项目偏离轨道,但真正决定软件复杂性的是设计方法。当复杂性失去控制时,开发人员就无法很好地理解软件,因此无法轻易、安全地更改和扩展它,而好的设计则可以为开发复杂特性创造更多机会。一些设计因素是技术上的,很多技术人员都能轻易注意到并改进,但是很多程序主要的复杂性并不在技术上,而是来自领域本身、用户的活动或业务,这部分往往被许多人忽略要避免不设计和过度设计(极限编程)模型、设计的核心、实现互相影响和关联;模型是团队所有人员沟通的中枢,使得开发人员和领域专家无需翻译就能沟通,高效简洁;模型是浓缩的知识技术人才更愿意从事精细的框架工作,试图用技术来解决领域问题,把学习领域知识和领域建模的工作留给别人去做。软件核心的复杂性需要我们直接去面对和解决,如果不这样做,则可能导致工作重点的偏离——软件的初衷以及核心就是为了解决领域问题对于比较重要的业务规则(这个知识点需要我们自己去理解)比如货船超订110%,应该单独抽象成1个实体(具体就可以是1个方法),而不是简单的用一个guard clause来实现,这样既能明确这个知识点本身,又利于代码的扩展性。当然,把不重要的细节也单独抽象就是典型的过度设计了以文本为主,简洁的小图为辅(大而全的图反而失去了解释能力)来阐释模型最好。文档是代码和口头交流的补充,为团队提供稳定和共享的交流。只用代码做文档容易使人陷入细节,不能把控全局,所以应该文档和代码互补,文档不再重复阐述代码能表现的内容而是着重核心元素,阐明设计意图,文档还要注意和代码保持同步不脱节(不再适用的文档要进行历史归档),不然就失去了意义。模型与实现也要同步,通过模型驱动设计MDD实现,保证模型既利于实现,也利于前期的分析要想创建出能处理复杂任务的程序,需要做到关注点分离,使设计中的每个部分都得到单独的关注,行业普遍采用分层架构,分层的价值在于每一层都只代表程序中的某一特定方面,每个方面的设计都更具内聚性,更容易解释。分层设计大都是“用户层界面-应用层-领域层-基础设施层”这种四层架构的变体,其中领域层是软件的核心,将其分离才是MDD的关键,也是领域驱动设计DDD的前提。领域层与应用层的区分关键在于领域层包含实际业务规则(如转账操作),而应用层是为了实现业务的辅助功能(如导入转账文本记录)DDD适用于大型项目,小项目用“Smart UI”更合适,还有其他的架构模式都有自己的使用场景和局限领域中用来表示某种具有连续性和标识(比如银行账户)的事物是ENTITY,用于描述某种状态的属性是VALUE OBJECT(不可变,无标识,比如数字3,尽量设计为不可变,便于复制和共享),动作或操作最好用SERVICE来表示(在大型系统中,中等粒度、无状态的SERVICE更容易被复用),对象之间的关联可以通过限定条件进行简化,MODULE是一种更粗粒度的建模和设计单元(提供了两种观察模型的方式,一是可以在MODULE中查看细节,而不会被整个模型淹没,二是观察MODULE之间的关系,而不考虑其内部细节)。领域模型中的每个概念都应该在实现元素中反映出来由于汽车的装配和驾驶永远不会同时发生,因此将这两种功能合并到同一个机制中是毫无价值的。同理,装配复杂的复合对象的工作也最好与对象要执行的工作分开。应该将创建复杂对象(比如依赖其他对象B的对象A就是复杂对象,不要直接在A构造函数中调用B的构造函数)的实例和AGGREGATE(一组相关对象的集合,比如车辆与发动机)的职责转移给单独的对象:FACTORY初始模型通常都是基于对领域的浅显认知而构建的,不够成熟也不够深入,通过重构(不仅是一般的代码细节的重构,还有领域的重构,后者通常风险很高,但是回报也很高,需要在前者的不断积累下寻找突破)最终开发出能够捕捉到领域深层含义的模型,这也是管理项目的规模和复杂性的要素,加上柔性设计(软件易于修改)就能让项目稳步发展。持续重构渐渐被认为是一种“最佳实践”,但大部分项目团队仍然对它抱有很大的戒心。人们虽然看到了修改代码会有风险,还要花费开发时间,但却不容易看到维持一个拙劣设计也有风险,而且迁就这种设计也要付出代价代码除了要能准确得到结果外,还要能显式的表达出领域的规则,易于理解和预测修改代码的影响。所以有一些原则:揭示意图的接口,能避免用户去研究它的实现(失去了封装的价值);无副作用的函数,安全地预见函数的作用,避免组合爆炸;断言可以帮助展示和理解副作用技术角度的设计模式中的一些也适用于领域设计,比如Strategy和Composite模式,把设计模式用作领域模式的唯一要求是这些模式能够描述关于概念领域的一些事情,而不仅是作为解决技术问题的技术解决方案大型系统的模型很复杂,需要注意三个要素:上下文(不要强制在大型系统中统一模型,可以在不同的上下文使用不同的模型(注意重复概念和假同源),划定好边界即可)、精炼和大型结构,才能操纵和理解这个模型......DDD我们可能都用过,但是很可能没把它当成一项正经学问,都是大概过一下需求,稍微捋一捋逻辑然后就开始编码了,实际上,在我们这个过程我们已经经历了ddd,看完本书以后希望能把这个过程正规化,流程化,高效化。 Go语言实战Go语言实战 (豆瓣): https://book.douban.com/subje... 上本书给我们讲了go的基础知识和原理,这本书就带领我们用go的各种库和工具进行实战。 亮点: 计算机一直在演化,但是编程语言并没有以同样的速度演化。现在的高性能服务器拥有 64 核、128 核,甚至更多核。但是我们依旧在使用为单核设计的技术在编程。Go语言对传统的面向对象开发进行了重新思考,并且提供了更高效的复用代码的手段。Go语言还让用户能更高效地利用昂贵服务器上的所有核心,而且它编译大型项目的速度也很快经验,如果需要声明初始为零值的变量,应该使用 var 关键字声明变量;如果提供确切的非零值初始化变量或者使用函数返回值创建变量,应该使用简化变量声明运算符 :=go vet工具不能让开发者避免严重的逻辑错误,或者避免编写充满小错的代码。不过可以很好地捕获一部分常见错误。每次对代码先执行 go vet 再将其签入源代码库是一个很好的习惯;在保存文件或者提交到代码库前执行 go fmt可以统一代码风格go在函数之间传递变量时,总是以值的方式传递的。函数间传递数组可能涉及大量数据拷贝,最好传递数组的指针,就只用拷贝8字节的指针而非拷贝数组本身。相反,与切片关联的数据包含在底层数组里,不属于切片本身,所函数间直接传递切片没有性能影响,映射也是;在创建切片时设置切片的容量和长度一样,就可以强制让新切片的第一个 append 操作创建新的底层数组,与原有的底层数组分离,可以安全地进行修改而不影响原切片,同时也保持了为切片申请新的底层数组的代码简洁性关键字 func 和函数名之间的参数被称作接收者,将函数与接收者的类型绑在一起。如果一个函数有接收者,这个函数就被称为方法。值接收者使用值的拷贝副本来调用方法,而指针接受者使用实际值来调用方法。Go语言会调整传入的参数,无论是指针接受者还是值接受者都可以接受指针或者值两种类型,说是方便开发,但我觉得这反而是一个不必要的歧义,比如到了接口的方法集中,如果使用指针接收者来实现一个接口,那么只有指向那个类型的指针才能够实现对应的接口。如果使用值接收者来实现一个接口,那么那个类型的值和指针都能够实现对应的接口,主要原因是编译器并不是总能自动获得一个值的地址通过嵌入类型,与内部类型相关的标识符会提升到外部类型上。这些被提升的标识符就像直接声明在外部类型里的标识符一样,也是外部类型的一部分,可以无缝实现对象组合,需要注意嵌入类型不需要声明字段。如 ...

May 7, 2019 · 1 min · jiezi

程序员笔记详解Eureka-缓存机制

引言Eureka是Netflix开源的、用于实现服务注册和发现的服务。Spring Cloud Eureka基于Eureka进行二次封装,增加了更人性化的UI,使用更为方便。但是由于Eureka本身存在较多缓存,服务状态更新滞后,最常见的状况是:服务下线后状态没有及时更新,服务消费者调用到已下线的服务导致请求失败。本文基于Spring Cloud Eureka 1.4.4.RELEASE,在默认region和zone的前提下,介绍Eureka的缓存机制。 一、AP特性从CAP理论看,Eureka是一个AP系统,优先保证可用性(A)和分区容错性(P),不保证强一致性(C),只保证最终一致性,因此在架构中设计了较多缓存。 二、服务状态Eureka服务状态enum类:com.netflix.appinfo.InstanceInfo.InstanceStatus 状态说明状态说明UP在线OUT_OF_SERVICE失效DOWN下线UNKNOWN未知STARTING正在启动三、Eureka Server在Eureka高可用架构中,Eureka Server也可以作为Client向其他server注册,多节点相互注册组成Eureka集群,集群间相互视为peer。Eureka Client向Server注册、续约、更新状态时,接受节点更新自己的服务注册信息后,逐个同步至其他peer节点。 【注意】如果server-A向server-B节点单向注册,则server-A视server-B为peer节点,server-A接受的数据会同步给server-B,但server-B接受的数据不会同步给server-A。 3.1 缓存机制Eureka Server存在三个变量:(registry、readWriteCacheMap、readOnlyCacheMap)保存服务注册信息,默认情况下定时任务每30s将readWriteCacheMap同步至readOnlyCacheMap,每60s清理超过90s未续约的节点,Eureka Client每30s从readOnlyCacheMap更新服务注册信息,而UI则从registry更新服务注册信息。 三级缓存 缓存类型说明registryConcurrentHashMap实时更新,类AbstractInstanceRegistry成员变量,UI端请求的是这里的服务注册信息readWriteCacheMapGuava Cache/LoadingCache实时更新,类ResponseCacheImpl成员变量,缓存时间180秒readOnlyCacheMapConcurrentHashMap周期更新,类ResponseCacheImpl成员变量,默认每30s从readWriteCacheMap更新,Eureka client默认从这里更新服务注册信息,可配置直接从readWriteCacheMap更新缓存相关配置 配置默认说明eureka.server.useReadOnlyResponseCachetrueClient从readOnlyCacheMap更新数据,false则跳过readOnlyCacheMap直接从readWriteCacheMap更新eureka.server.responsecCacheUpdateIntervalMs30000readWriteCacheMap更新至readOnlyCacheMap周期,默认30seureka.server.evictionIntervalTimerInMs60000清理未续约节点(evict)周期,默认60seureka.instance.leaseExpirationDurationInSeconds90清理未续约节点超时时间,默认90s关键类 类名说明com.netflix.eureka.registry.AbstractInstanceRegistry保存服务注册信息,持有registry和responseCache成员变量com.netflix.eureka.registry.ResponseCacheImpl持有readWriteCacheMap和readOnlyCacheMap成员变量四、Eureka ClientEureka Client存在两种角色:服务提供者和服务消费者,作为服务消费者一般配合Ribbon或Feign(Feign内部使用Ribbon)使用。Eureka Client启动后,作为服务提供者立即向Server注册,默认情况下每30s续约(renew);作为服务消费者立即向Server全量更新服务注册信息,默认情况下每30s增量更新服务注册信息;Ribbon延时1s向Client获取使用的服务注册信息,默认每30s更新使用的服务注册信息,只保存状态为UP的服务。 二级缓存 缓存类型说明localRegionAppsAtomicReference周期更新,类DiscoveryClient成员变量,Eureka Client保存服务注册信息,启动后立即向Server全量更新,默认每30s增量更新upServerListZoneMapConcurrentHashMap周期更新,类LoadBalancerStats成员变量,Ribbon保存使用且状态为UP的服务注册信息,启动后延时1s向Client更新,默认每30s更新缓存相关配置 配置默认说明eureka.instance.leaseRenewalIntervalInSeconds30Eureka Client 续约周期,默认30seureka.client.registryFetchIntervalSeconds30Eureka Client 增量更新周期,默认30s(正常情况下增量更新,超时或与Server端不一致等情况则全量更新)ribbon.ServerListRefreshInterval30000Ribbon 更新周期,默认30s关键类 类名说明com.netflix.discovery.DiscoveryClientEureka Client 负责注册、续约和更新,方法initScheduledTasks()分别初始化续约和更新定时任务com.netflix.loadbalancer.PollingServerListUpdaterRibbon 更新使用的服务注册信息,start初始化更新定时任务com.netflix.loadbalancer.LoadBalancerStatsRibbon,保存使用且状态为UP的服务注册信息五、默认配置下服务消费者最长感知时间Eureka Client时间说明上线30(readOnly)+30(Client)+30(Ribbon)=90sreadWrite -> readOnly -> Client -> Ribbon 各30s正常下线30(readonly)+30(Client)+30(Ribbon)=90s服务正常下线(kill或kill -15杀死进程)会给进程善后机会,DiscoveryClient.shutdown()将向Server更新自身状态为DOWN,然后发送DELETE请求注销自己,registry和readWriteCacheMap实时更新,故UI将不再显示该服务实例非正常下线30+60(evict)*2+30+30+30= 240s服务非正常下线(kill -9杀死进程或进程崩溃)不会触发DiscoveryClient.shutdown()方法,Eureka Server将依赖每60s清理超过90s未续约服务从registry和readWriteCacheMap中删除该服务实例考虑如下情况 0s时服务未通知Eureka Client直接下线;29s时第一次过期检查evict未超过90s;89s时第二次过期检查evict未超过90s;149s时第三次过期检查evict未续约时间超过了90s,故将该服务实例从registry和readWriteCacheMap中删除;179s时定时任务从readWriteCacheMap更新至readOnlyCacheMap;209s时Eureka Client从Eureka Server的readOnlyCacheMap更新;239s时Ribbon从Eureka Client更新。因此,极限情况下服务消费者最长感知时间将无限趋近240s。 六、应对措施服务注册中心在选择使用Eureka时说明已经接受了其优先保证可用性(A)和分区容错性(P)、不保证强一致性(C)的特点。如果需要优先保证强一致性(C),则应该考虑使用ZooKeeper等CP系统作为服务注册中心。分布式系统中一般配置多节点,单个节点服务上线的状态更新滞后并没有什么影响,这里主要考虑服务下线后状态更新滞后的应对措施。 6.1 Eureka Server1.缩短readOnlyCacheMap更新周期。缩短该定时任务周期可减少滞后时间。 eureka.server.responsecCacheUpdateIntervalMs: 10000 # Eureka Server readOnlyCacheMap更新周期2.关闭readOnlyCacheMap。中小型系统可以考虑该方案,Eureka Client直接从readWriteCacheMap更新服务注册信息。 ...

May 7, 2019 · 1 min · jiezi

服务治理Spring-Cloud-Eureka上

服务治理:Spring Cloud Eureka(上)Netflix Eureka是由Netflix开源的一款基于REST的服务治理组件,包括Eureka Server及Eureka Client。由于种种原因,Eureka 2.x版本已经冻结开发,目前最新版本是2018年8月份发布的1.9.4版本。Spring Cloud Eureka是Pivotal公司为Netflix Eureka整合于Spring Cloud生态系统提供的版本。1. 服务发现1.1 Eureka简介Eureka是Netflix公司提供的开源服务发现组件(现已闭源),最新版本是1.9.4,该组件提供的服务发现可以为负载均衡、failover等提供支持。Eureka包括Eureka Server和Eureka Client。Eureka Server提供REST服务,Eureka Clinet多数是使用Java编写的客户端(Eureka Client可以使用其他语言编写,比如Node.js或.NET),用于简化和Eureka Server的交互。1.2 Eureka Server简单案例所有工程使用Spring Cloud的新版Greenwich.SR1和Maven构建。 1.2.1 创建Spring Cloud Eureka Server工程pom.xml内容如下: <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.4.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>watermelon.cloud</groupId> <artifactId>eureka-server</artifactId> <version>0.0.1-SNAPSHOT</version> <name>eureka-server</name> <description>Spring Cloud Eureka Server</description> <properties> <java.version>1.8</java.version> <spring-cloud.version>Greenwich.SR1</spring-cloud.version> </properties> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>Finchley版本之后,Eureka的depenecy片段稍微有点不同 ...

May 6, 2019 · 2 min · jiezi

微服务与Spring-Cloud概述

微服务与Spring Cloud随着互联网的快速发展, 云计算近十年也得到蓬勃发展, 企业的IT环境和IT架构也逐渐在发生变革,从过去的单体应用架构发展为至今广泛流行的微服务架构。 微服务是一种架构风格, 能给软件应用开发带来很大的便利,但是微服务的实施和落地会面临很大的挑战, 因此需要一套完整的微服务解决方案。 在Java领域,Spring框架的出现给Java企业级软件开发带来 了福音, 提高了开发效率。 在2014年底,Spring团队推出Spring Cloud, 目标使其成为Java 领域微服务架构落地的标准,发展至今,Spring Cloud已经成为Java领域落地微服务架构的完整解决方案, 为企业IT架构变革保驾护航。微服务架构概述1.应用架构的发展应用是可独立运行的程序代码, 提供相对完善的业务功能。 目前软件架构有三种架构类型, 分别是业务架构、应用架构、技术架构。 它们之间的关系是业务架构决定应用架构, 技术架构支撑应用架构。 架构的发展历程是从单体架构、分布式架构、SOA架构再到微服务架构。 1.1 单体架构单体架构在Java领域可以理解为一个Java Web应用程序,包含表现层、业务层、数据访问层,从controller到service再到dao,就像一条单行道,从头一路走到底,没有任何业务的拆分,开发完毕之后就是一个超级大型的War包部署。简单的单体架构示例图如下: 这种开发方式对于大型应用来说非常复杂,也有“单体地狱”的称号。我们来说说单体架构的优缺点:单体架构的优点: 易于开发:开发人员使用当前开发工具在短时间内就可以开发出单体应用。易于测试:因为不需要依赖其他接口,测试可以节约很多时间。易于部署:你只需要将目录部署在运行环境中即可。单体架构的缺点: 灵活度不够:如果程序有任何修改, 修改的不只是一个点, 而是自上而下地去修改,测试时必须等到整个程序部署完后才能看出效果。 在开发过程可能需要等待其他开发 人员开发完成后才能完成部署,降低了团队的灵活性。降低系统的性能:原本可以直接访问数据库但是现在多了一层。 即使只包含一个功能点, 也需要在各个层写上代码。系统启动慢:一个进程包含了所有业务逻辑, 涉及的启动模块过多, 导致系统的启动 时间延长。系统扩展性比较差:增加新东西的时候不能针对单个点增加, 要全局性地增加。 牵一 发而动全身。1.2 分布式架构分布式架构就是在传统的单体架构的基础上,按照业务垂直切分,每个应用都是单体架构,通过API相互调用。 分布式架构的优缺点:优点: 依赖解耦理解清晰缺点: 进程间调用的可靠性低实现技术复杂1.3 SOA架构SOA(Service-Oriented Architecture)是指面向服务的架构,面向服务的架构是一种软件体系结构, 其应用程序的不同组件通过网络上的通信协议向其他组件提供服务或消费服务,所以也是一种分布式架构。简单来说,SOA是不同业务建立不同 的服务, 服务之间的数据交互粗粒度可以通过服务接口分级, 这样松散耦合提高服务的可重用性,也让业务逻辑变得可组合, 并且每个服务可以根据使用情况做出合理的分布式部署,从而让服务变得规范,高性能,高可用。 SOA架构中有两个主要角色:服务提供者(Provider)和服务消费者(Consumer)。 阿里开源的Dubbo是SOA的典型实现。SOA架构的优缺点:优点: 把模块拆分,使用接口通信,降低模块之间的耦合度把项目拆分成若干子项目,不同团队负责不同的子项目增加功能时只需要增加一个子项目,调用其他系统的接口即可可灵活地进行分布式部署缺点: 系统之间交互需要远程通信接口开发增加工作量1.4 微服务架构微服务架构在某种程度上是SOA架构继续发展的下一步,微服务的概念最早源千Martin Flower的《Microservice》。总体来讲,微服务是一种架构风格,对于一个大型复杂的业务系统,它的业务功能可以拆分为多个相互独立的微服务,各个服务之间是松耦合的,通过各种远程协议进行同步/异步通信,各微服务均可被独立部署、扩/缩容以及服务升/降级。 2. 微服务解决方案现今微服务架构十分火爆,而采用微服务构建系统也会带来更清晰的业务划分和可扩展性。支持微服务的技术栈也是多种多样。这里主要介绍两种实现微服务的解决方案: 2.1 基于Spring Cloud的微服务解决方案基于Spring Cloud的微服务解决方案也有人称为“Spring系微服务”,Spring Cloud的技术选型是中立的,Spring Cloud框架提供微服务落地方案主要有以下三种: ...

May 5, 2019 · 1 min · jiezi

适合新手的spring-cloud入门教程

就和 springboot 是 web 应用的脚手架一样, springcloud 是分布式和集群应用的脚手架。 但是并不是所有的同学都有接触过分布式和集群,所以为了让学习曲线变得缓和,站长按照如下顺序展开 springcloud 教程的讲解: 先来个单体架构的应用,里面既没有分布式,也没有集群。 基于这个单体架构,分析其弊端,引入微服务,集群和分布式的概念。 一般说来做一个springcloud项目都会有多个子项目,这里就涉及到使用 maven 创建父子(聚合)项目的概念。很多同学之前也没有接触过这个,为了让后面学习更顺滑,也在这里做了 maven 父子项目教程,分别提供了 eclipse 版本 和 idea 版本。 springcloud 是由一个一个的微服务组成, 而这些微服务都是在注册中心管理起来的。所以这里我们就会做注册中心的开发。 有了注册中心,我们就可以发布真正提供服务的微服务了。 springcloud 里面的一个核心内容是微服务之间的彼此调用,所以我们会先演示 ribbon 方式的视图微服务调用数据微服务。 7. 然后再学习主流的 Feign 方式  微服务之间的调用关系是需要被掌握的,于是我们学习服务链路追踪 集群里有多个实例,当发生改变的时候,必须重新部署,这样维护成本比较高。为了降低维护成本,我们引入了分布式配置服务的概念。 被调用的服务不一定100% 可用,当发生不可用的时候怎么办呢?我们会使用断路器。 断路器什么时候起作用了?微服务的可用度如何?这些都应该被纳入监控,所以我们会学习对单个微服务的短路监控以及集群里多个微服务的聚合监控。 微服务有很多个,分别处于不同的ip地址,使用不同的端口。这让访问者难以记忆,为了方便访问,我们引入了网关,这样访问者似乎就意识不到微服务的存在了一般。 在这个系列教材里,微服务有很多个,端口也有很多个,担心学员被端口号搞混淆了,于是把这些端口号都做了整理,方便梳理思路。 教程地址:http://how2j.cn/p/1628

May 4, 2019 · 1 min · jiezi

springcloud一springcloudalibaba集成rocketmq

前言在之前的工作中,微服务框架使用的是springcloud,消息中间件使用的rocketmq,这段时间看到阿里出了spring cloud alibaba集成了rocketmq,出于好奇,写了个demo 一些概念官方对 Spring Cloud Stream 的一段介绍:Spring Cloud Stream 是一个用于构建基于消息的微服务应用框架。基于 SpringBoot 创建具有生产级别的单机 Spring 应用,并且使用 Spring Integration 与 Broker 进行连接。Binder :Components responsible to provide integration with the external messaging systems.【与外部消息中间件进行集成】Binding:Bridge between the external messaging systems and application provided Producers and Consumers of messages (created by the Destination Binders).【在消息中间件与应用程序提供的 Provider 和 Consumer 之间提供了一个桥梁,开发者只需使用应用程序的 生产者或消费者生产或消费数据即可,屏蔽了开发者与底层消息中间件的接触。】Message: The canonical data structure used by producers and consumers to communicate with Destination Binders (and thus other applications via external messaging systems).【生产者和消费者用于与目标绑定器通信的规范数据结构。】快速在本地启动rocketmq第一步:下载:https://www.apache.org/dyn/cl...第二步:解压第三步:修改三个配置文件:runbroker.sh,runserver.sh,tools.sh,将其中JAVA_HOME改成自己电脑的环境配置,修改完如下 ...

April 30, 2019 · 2 min · jiezi

35条建议让你对Java-代码性能优化彻底理解

代码优化,一个很重要的课题。可能有些人觉得没用,一些细小的地方有什么好修改的,改与不改对于代码的运行效率有什么影响呢?这个问题我是这么考虑的,就像大海里面的鲸鱼一样,它吃一条小虾米有用吗?没用,但是,吃的小虾米一多之后,鲸鱼就被喂饱了。 代码优化也是一样,如果项目着眼于尽快无BUG上线,那么此时可以抓大放小,代码的细节可以不精打细磨;但是如果有足够的时间开发、维护代码,这时候就必须考虑每个可以优化的细节了,一个一个细小的优化点累积起来,对于代码的运行效率绝对是有提升的。 代码优化的目标是 减小代码的体积提高代码运行的效率代码优化细节 1、尽量指定类、方法的final修饰符带有final修饰符的类是不可派生的。在Java核心API中,有许多应用final的例子,例如java.lang.String,整个类都是final的。为类指定final修饰符可以让类不可以被继承,为方法指定final修饰符可以让方法不可以被重写。如果指定了一个类为final,则该类所有的方法都是final的。Java编译器会寻找机会内联所有的final方法,内联对于提升Java运行效率作用重大,具体参见Java运行期优化。此举能够使性能平均提高50%。 2、尽量重用对象特别是String对象的使用,出现字符串连接时应该使用StringBuilder/StringBuffer代替。由于Java虚拟机不仅要花时间生成对象,以后可能还需要花时间对这些对象进行垃圾回收和处理,因此,生成过多的对象将会给程序的性能带来很大的影响。 3、尽可能使用局部变量调用方法时传递的参数以及在调用中创建的临时变量都保存在栈中速度较快,其他变量,如静态变量、实例变量等,都在堆中创建,速度较慢。另外,栈中创建的变量,随着方法的运行结束,这些内容就没了,不需要额外的垃圾回收。 4、及时关闭流Java编程过程中,进行数据库连接、I/O流操作时务必小心,在使用完毕后,及时关闭以释放资源。因为对这些大对象的操作会造成系统大的开销,稍有不慎,将会导致严重的后果。 5、尽量减少对变量的重复计算明确一个概念,对方法的调用,即使方法中只有一句语句,也是有消耗的,包括创建栈帧、调用方法时保护现场、调用方法完毕时恢复现场等。所以例如下面的操作: for (int i = 0; i < list.size(); i++){...}建议替换为: for (int i = 0, int length = list.size(); i < length; i++){...}这样,在list.size()很大的时候,就减少了很多的消耗 6、尽量采用懒加载的策略,即在需要的时候才创建例如: String str = "aaa";if (i == 1){list.add(str);}建议替换为: if (i == 1){String str = "aaa";list.add(str);}慎用异常 异常对性能不利。抛出异常首先要创建一个新的对象,Throwable接口的构造函数调用名为fillInStackTrace()的本地同步方法,fillInStackTrace()方法检查堆栈,收集调用跟踪信息。只要有异常被抛出,Java虚拟机就必须调整调用堆栈,因为在处理过程中创建了一个新的对象。异常只能用于错误处理,不应该用来控制程序流程。 8、不要在循环中使用try…catch…,应该把其放在最外层除非不得已。如果毫无理由地这么写了,只要你的领导资深一点、有强迫症一点,八成就要骂你为什么写出这种垃圾代码来了。 9、如果能估计到待添加的内容长度,为底层以数组方式实现的集合、工具类指定初始长度比如ArrayList、LinkedLlist、StringBuilder、StringBuffer、HashMap、HashSet等等,以StringBuilder为例: (1)StringBuilder() // 默认分配16个字符的空间(2)StringBuilder(int size) // 默认分配size个字符的空间(3)StringBuilder(String str) // 默认分配16个字符+str.length()个字符空间可以通过类(这里指的不仅仅是上面的StringBuilder)的来设定它的初始化容量,这样可以明显地提升性能。比如StringBuilder吧,length表示当前的StringBuilder能保持的字符数量。因为当StringBuilder达到最大容量的时候,它会将自身容量增加到当前的2倍再加2,无论何时只要StringBuilder达到它的最大容量,它就不得不创建一个新的字符数组然后将旧的字符数组内容拷贝到新字符数组中—-这是十分耗费性能的一个操作。试想,如果能预估到字符数组中大概要存放5000个字符而不指定长度,最接近5000的2次幂是4096,每次扩容加的2不管,那么: (1)在4096 的基础上,再申请8194个大小的字符数组,加起来相当于一次申请了12290个大小的字符数组,如果一开始能指定5000个大小的字符数组,就节省了一倍以上的空间; (2)把原来的4096个字符拷贝到新的的字符数组中去。 这样,既浪费内存空间又降低代码运行效率。所以,给底层以数组实现的集合、工具类设置一个合理的初始化容量是错不了的,这会带来立竿见影的效果。但是,注意,像HashMap这种是以数组+链表实现的集合,别把初始大小和你估计的大小设置得一样,因为一个table上只连接一个对象的可能性几乎为0。初始大小建议设置为2的N次幂,如果能估计到有2000个元素,设置成new HashMap(128)、new HashMap(256)都可以。 10、当复制大量数据时,使用System.arraycopy()命令11、乘法和除法使用移位操作例如: for (val = 0; val < 100000; val += 5){a = val * 8;b = val / 2;}用移位操作可以极大地提高性能,因为在计算机底层,对位的操作是最方便、最快的,因此建议修改为: ...

April 29, 2019 · 3 min · jiezi

Sentinel-成为-Spring-Cloud-官方推荐的主流熔断降级方案

近日,Sentinel 贡献的 spring-cloud-circuitbreaker-sentinel  模块正式被Spring Cloud社区合并至 Spring Cloud Circuit Breaker,由此,Sentinel 加入了 Spring Cloud Circuit Breaker 俱乐部,成为 Spring Cloud 官方的主流推荐选择之一。这意味着,Spring Cloud 微服务的开发者在熔断降级领域有了更多的选择,可以更方便地利用 Sentinel 来保障微服务的稳定性。 一、什么是 Spring Cloud Circuit Breaker?Spring Cloud Circuit Breaker是 Spring Cloud 官方的熔断器组件库,提供了一套统一的熔断器抽象API接口,允许开发者自由选择合适的熔断器实现。这个官方的熔断器组件库,截至目前,官方推荐的熔断器组件有: HystrixResilience4JSentinelSpring Retry当前,Spring Cloud Circuit Breaker 处于孵化阶段中,未来将合并到 Spring Cloud 主干版本正式发布。 Spring Cloud Circuit Breaker https://github.com/spring-cloud-incubator/spring-cloud-circuitbreaker 二、Sentinel 发展历程2012 年,Sentinel 诞生于阿里巴巴集团内部,主要功能为入口流量控制; 2013 - 2018 年,Sentinel 在阿里巴巴集团内部迅速发展,成为基础技术模块,覆盖了所有的核心场景。Sentinel 也因此积累了大量的流量控制场景以及生产实践; 2018年7月,阿里巴巴宣布限流降级框架组件 Sentinel 正式开源,在此之前,Sentinel 作为阿里巴巴“大中台、小前台”架构中的基础模块,已经覆盖了阿里的所有核心场景,因此积累了大量的流量归整场景以及生产实践; 2018年9月,Sentinel 发布 v0.2.0版本,释放异步调用支持、热点参数限流等多个重要特性; 2018年10月,Sentinel 发布首个 GA 版本 v1.3.0,该版本包括 Sentinel 控制台功能的完善和一些 bug 修复,以及其它的产品改进; ...

April 29, 2019 · 1 min · jiezi

企业应用架构演化探讨从微服务到Service-Mesh

导读当下微服务的实践方案中,Spring Cloud,Dubbo作为主流的落地方案,在企业应用架构中发挥越来越重要的作用。本文探讨企业应用架构如何从微服务架构向Service Mesh架构演化,并形成落地方案。需要特别说明:本文讨论的架构目前适用于普通的企业级应用,其他行业(例如互联网)需要进一步扩展。 在讨论之前,我们需要明确一个事实:企业应用一定是围绕业务进行的。无论采用什么的架构落地,都是为了更好的为应用业务进行服务。从企业应用的特性考虑,主要包括:稳定性,安全性,扩展性,容错性。 围绕着企业应用的这些特点,我们来看一个典型的微服务企业架构模型,如图所示: 服务接入层:企业暴露到外部访问的入口,一般通过防火墙等。网关层:服务网关是介于客户端和服务端的中间层,所有的外部请求会先经过服务网关,为企业应用提供统一的访问控制入口。服务网关是微服务架构下的服务拆分,聚合,路由,认证以及流控综合体现。支撑服务层:为企业应用提供运行所需的支撑环境,包括注册发现,集中配置,容错限流,认证授权,日志聚合,监测告警,消息服务等业务服务层:业务服务是企业应用的核心所在,为企业领域应用的具体实现,一般进一步拆分为基础服务(基础功能)和聚合服务(综合场景)。平台服务层:为企业应用提供运行所需的软件资源,包括应用服务器,应用发布管理,应用镜像包管理,服务治理。基础设施层:为企业应用提供运行所需的硬件资源,包括计算资源,网络资源,存储资源,基本的安全策略控制等。从这个典型的服务架构体系中,能够清晰的表明层级架构以及各层涵盖的职责说明。我们暂不考虑基础设施层和平台服务两层,重点关注网关服务,业务服务,支撑服务,突出其中的一些基础支撑功能组件,这也是我们本篇探讨的重点内容。如下图所示: 根据图中红色标识,我们会发现这样一个事实:在微服务架构下,无论是哪种落地实现方式,都集中在网关服务、支撑服务两个层面。无论是Spring Cloud“套装组件”,Dubbo“套件”还是其他开源组件,都为支撑服务的实现提供了数量众多的选择。功能完整、选择性多这是业内喜闻乐见的事情,但是也无形中增加了开发,测试,运维人员的压力。大家需要掌握越来越多的“使用工具”以更“方便”、“快捷”地应对业务服务。有时候,可能为了实现单一功能,而必须引入一堆组件,这时候我们希望能够有一个完整的平台来为应用业务提供一体化的支撑服务,而不是一系列“套装组件”与业务的集成。 那么如何基于一个平台来实现这些企业应用需要的能力呢?经过一定阶段的技术调研,我们认为Service Mesh能够帮助我们初步达到这个目标。 我们都知道Service Mesh以解决“服务通信”的问题作为其设计初衷,聚焦基础设施“网络层”,并以此做技术基础,解决业务通信场景面临的问题。那么如何把它应用在企业应用架构中来取代“微服务套装组件”呢?那接下来让我们针对网关服务,业务服务,支撑服务分别来看一下,如何从原来的微服务“套装组件”中抽离出来,实现Service Mesh方向的转变。 网关服务前面提到过:服务网关是介于客户端和服务端的中间层。从功能上不难理解,对内屏蔽内部细节,对外提供统一服务接口。从场景聚焦角度考虑,网关根据不同的场景承载不同的职责,包括认证,授权,路由,流控,负载等。(之前我们也聊过网关组件的对比及具体实现,感兴趣的同学可点击微服务五种开源API网关实现组件对比)。 由此可见,服务网关是企业应用架构下一些列功能的综合体现。那么在Service Mesh情况下如何处理网关服务呢?在展开之前首先需要说明一个前提:目前为止Service Mesh跟真正企业网关相比还存在一定的不足之处,例如“协议转化”,“安全策略”,“性能要求”等方面。在这里我们也是探讨这样的可能性。下面以Istio为例,我们来看一下,如何提供网关层面的服务。 Istio在网关层面提供两种类型的网关服务:Ingress Gateway,Egress。 Ingress GatewayIngress Gateway用于接收传入的HTTP/TCP连接,它配置暴露端口,协议供外部统一接入,但是自身不提供任何的路由配置,而是完全依赖 Istio 的控制规则来进行流量路由。从而与内部服务请求统一到同一个控制层面上。 Egress在企业应用与外部应用之间,有时候为了业务需要会出现内部服务调用外部服务的情况,此时一般会从企业内部接入第三方网关来获取服务数据。在 Isito 中你同样可以基于Egress来达到目的。Isito中提供两种方式:一种基于ServiceEntry + VirtualService的配置,实现第三方服务的访问,一种扩大sidecar的访问地址列表。(参考文档:https://preliminary.istio.io/...)。 基于上述两种场景,我们可以看出,在 Service Mesh 的体系下,网关层面变成一个可以动态生成和销毁的组件,能够通过控制层面实现统一规则管理,并且实时生效。 基于Service Mesh的网关服务如下图所示: 从实现原理上分析,传统的网关实现基于 Servlet 的 filter 的模式,实现服务请求转移过程中的层层过滤和处理。区别在于采用同步或者异步处理机制,用来解决网关的性能瓶颈。而Service Mesh的网关完全是基于网络代理的请求转发与控制,本质上作用在服务的 Iptables 上,通过对 Iptables 的规则控制达到同样的效果。 业务服务业务是企业应用的“重中之重”,无论哪种企业架构,最终都是为了更好地为业务提供服务,那么我们如何在Service Mesh的体系下,重构业务服务呢?我们以两个简化的服务调用来说明整个架构的转变过程。 假如要实现服务A,服务B的相互调用,最原始的方式是服务A基于协议层直接调用服务B(这里暂时忽略高可用,多副本,负载均衡的方式),如图所示: 由图可见,服务A基于某种协议完成对服务B的请求,相对比较简单。但是我们知道这样虽然能够快速完成业务关联,但是无法确保业务正常稳定的运行,因此我们需要引入更多的服务来保证业务的稳定,可靠,可控。此时我们最容易想到的是引入微服务的支撑组件来达到目标。 以Spring Cloud方案为例,我们来说明当前微服务架构的实现方式。为了满足企业应用对服务A,服务B的管理控制,需要额外引入“注册中心”,“网关”,“配置中心”,“服务监测”,“事件消息”,“链路跟踪”,“日志服务”等众多与直接业务无关的“旁路保障服务”,简化一下,如下图所示: 从图中可以看出,每个服务都引入了大量与业务无关的“保障服务”,这些“旁路保障服务”消耗的资源,与比业务本身消耗的资源成“倍数关系”。随着服务数目的增多,业务服务本身占用的资源比会越来越少,此时开发人员会把大量的精力花费在维护这些“旁路保障服务”上,而忽略业务本身。这对于企业应用而言,有些本末倒置的意思。 我们再来看一下 Service Mesh 体系下,我们如何解决上述问题。Service Mesh为了解决企业应用的“通信问题”重点做了四个方面的工作,以 Istio 为代表,提供了包括流量管理,安全配置,策略控制以及外围组件支撑的遥测功能(需要的朋友,可以参考官方文档:https://preliminary.istio.io/...),在Service Mesh的架构下,服务A调用服务B的架构会变成下图所示: ...

April 28, 2019 · 1 min · jiezi

云原生的新思考为什么容器已经无处不在了

4月24日,中国信息通信研究院主办的首届云原生产业大会在北京举行,在《云原生数字引领未来》的主题演讲中,阿里云容器服务总监易立表示:“云原生不但可以很好的支持互联网应用,也在深刻影响着新的计算架构、新的智能数据应用。以容器、服务网格、微服务、Serverless为代表的云原生技术,带来一种全新的方式来构建应用。”本文根据易立演讲内容整理而成。 拥抱云原生技术,解耦系统复杂度如今,大多数企业开始全面拥抱云计算,在All-in-Cloud全面到来的时代,三个重要转变:基础设施的云化、核心技术的互联网化、业务的数据化和智能化。在各行各业中,都有很多业务应用从诞生之初就生长在云端,各个企业也因此越来越像互联网公司,而技术能力被视为不可或缺的核心竞争力。在2019阿里云峰会·北京站上,阿里云智能总裁张建锋在谈及‘核心技术的互联网化’时,也提到了大力投资云原生。 为什么要拥抱云原生?一方面,云计算已经重塑了软件的整个生命周期,从架构设计到开发,再到构建、交付和运维等所有环节;另一方面,企业IT架构也随之发生巨大变化,而业务又深度依赖IT能力。这带来了一定程度的复杂性和挑战性。 正如人类社会发展伴随着技术革命与社会大分工一样,云原生技术的出现解耦了很多复杂性,这是IT技术的进步。 首先,Docker实现了应用与运行环境的解耦,众多业务应用负载都可以被容器化,而且应用容器化满足了敏捷、可迁移、标准化的诉求;其次,Kubernetes的出现让资源编排调度与底层基础设施解耦,应用和资源的管控也开始得心应手,容器编排实现资源编排、高效调度;随后,Istio为代表的服务网格技术解耦了服务实现与服务治理能力。此外,阿里云还提供了Open API、SDK等丰富的开发工具,实现第三方被集成,为云的生态伙伴提供广阔的可能性。这样的技术分层推动了社会分工,极大促进了技术和业务创新。 在阿里云看来,云原生首先可以支持互联网规模应用,可以更加快速地创新、和低成本试错;其次,屏蔽了底层基础架构的差异和复杂性;同时,服务网格、无服务计算等新的计算范型的不断涌现,给整体IT架构能力带来了极致弹性,从而更好地服务于业务。用户可以基于阿里云容器服务构建面向领域的云原生框架,如面向机器学习的Kubeflow,和面向无服务器的Knative等等。 方兴未艾,容器应用的新思考容器已经无处不在了, 作为容器服务的提供者,我们认为容器技术会继续发展,被应用于“新的计算形态”,“新的应用负载”和“新的物理边界”,在此将相关观察和新思考分享给大家。 1 新的计算形态:云原生的Serverless Runtime已来 云原生技术理念,是使企业用户及开发者只关注应用开发,无需关注基础设施及基础服务。与之相似的Serverless计算,将应用服务资源化并以API接口的方式提供出来,使用者只需从客户端发起调用请求即可,更重要的是,pay as you go 能够真正为用户节省成本。 Serverless Runtime 分为面向基础架构容器的实现,面向应用服务封装的实现,和事件驱动面向函数计算的实现。 云原生Serverless Runtime形态包含多种方式。业界各个厂商也相应地设计出了不同服务解决方案: 面向函数的Function as a Service(FaaS) - 比如AWS Lambda,阿里云的函数计算,提供了事件驱动的编程方式,用户只需提供函数实现响应触发实践,开发效率很高。阿里云函数计算按照调用量计费,可以根据业务流量平滑调整计算资源,在典型场景下,会有10%~90%的成本下降。客户码隆科技做模型预测,利用函数计算降低了40%的计算成本。面向应用 - 比如Google App Engine、新发布的Cloud Run和阿里云EDAS Serverless,用户只需提供应用实现而平台负责应用弹性、自动化运维,这主要面向互联网类型应用。相比于FaaS,面向应用的Serverless形态无需改造现有应用,阿里云EDAS Serverless为流行的开源微服务框架提供了无服务器应用托管平台,支持Spring Cloud,Apache Dubbo,或者阿里云HSF框架。面向容器 – 比如AWS fargate,或者是阿里云的Serverless Kubernetes 应用的载体是容器镜像,灵活性很好,配合调度系统可以支持各种类型应用,而无需管理底层基础架构。针对容器化应用,阿里云在去年5月推出了Serverless Kubernetes容器服务,无需节点管理和容量规划,按应用所需资源付费,弹性扩容。针对阿里云基础能力优化,安全,高效。极大降低了管理Kubernetes集群的。Serverless Kubernetes的底层是构建在阿里云针对容器优化的轻量虚拟化弹性容器实例之上,提供了轻量、高效、安全的容器应用执行环境。Serverless Kubernetes无需修改即可部署容器类型应用。 2 新的应用负载:容器正被用于越来越多类型应用 最早容器被认为不适合传统的已有应用,但是现在状况已大为改观。容器已经开启了对Windows生态的支持,新发布的1.14版本中Kubernetes 的Pod,Service,应用编排,CNI 网络等绝大多数核心能力都已经在 Windows 节点上得到了支持。当今Windows系统依然占有60%的份额,比如企业的ERP软件、基于ASP的应用、大量的Windows的数据库等,这些传统的基于虚拟化的应用,都可以在代码不用重写的情况下实现容器化。 基于容器技术构建的新架构,会催生新的应用业务价值。云原生AI是非常重要的应用场景,快速搭建AI环境,高效利用底层资源,无缝配合深度学习的全生命周期。对于AI工程,云原生系统可以在四个维度上为提效: 优化异构资源调度弹性、高效、细粒度(支持GPU共享)简化异构资源管理复杂性,提升可观测性和使用效率可移植、可组装、可重现的AI流程以深度学习分布式训练为例,通过阿里云容器服务可以获得三重加强。资源优化:统一调度CPU/GPU等异构资源,使用VPC/RDMA网络加速;性能提升:GPU 64卡P100,加速比提升90%,相比原生Tensorflow有45%提升;算法优化:MPI代替gRPC通信、ring-allreduce环形通信、计算和通信重叠、梯度融合等。 还有其他高性能计算的场景,以基因数据处理为例,阿里云某用户在5小时内完成WGS 100GB数据处理,支持5000+步骤的复杂流程, 90秒实现500节点扩容充分发挥容器极致弹性。 3 新的物理边界:云-边-端,容器不止运行在IDC服务器中 容器最被熟知的基础环境是数据中心,在业务流量高峰与低谷之时,凭借容器极致弹性可以实现应用与资源伸缩,有效地保证高利用率与高性价比。 随着5G和物联网时代的到来,传统云计算中心集中存储、计算的模式已经无法满足终端设备对于时效、容量、算力的需求。将云计算的能力下沉到边缘侧、设备侧,并通过中心进行统一交付、运维、管控,将是云计算的重要发展趋势。以Kubernetes为基础的云原生技术,在任何基础设施上提供与云一致的功能和体验,实现云-边-端一体化的应用分发, 支持不同系统架构和网络状况下,应用的分发和生命周期管理,并且针对边缘及设备进行如访问协议、同步机制、安全机制的种种优化。 如前所述,应用容器化实现了标准化的可移植性,促成了敏捷弹性的云原生应用架构。不仅大大简化了多云/混合云的部署,而且优化成本,同时提供更多的选择,比如满足安全合规的要求、提升业务敏捷性、提升地域覆盖性等等。 容器可以适用于多种基础环境,比如数据中心、边缘云、和多云/混合云,使得开发者关注回归到应用本身。 写在最后云原生时代,是开发者最好的时代。 云原生不但可以很好的支持互联网应用,也在深刻影响着新的计算架构、新的智能数据应用。以容器、服务网格、微服务、Serverless为代表的云原生技术,带来一种全新的方式来构建应用。此外,云原生也在拓展云计算的边界,一方面是多云、混合云推动无边界云计算,一方面云边端的协同。 ...

April 26, 2019 · 1 min · jiezi

Dubbo-Spring-Cloud-重塑微服务治理

原文链接:Dubbo Spring Cloud 重塑微服务治理,来自于微信公众号:次灵均阁摘要在 Java 微服务生态中,Spring Cloud1 成为了开发人员的首选技术栈,然而随着实践的深入和运用规模的扩大,大家逐渐意识到 Spring Cloud 的局限性。在服务治理方面,相较于 Dubbo2 而言,Spring Cloud 并不成熟。遗憾的是,Dubbo 往往被部分开发者片面地视作服务治理的 PRC 框架,而非微服务基础设施。即使是那些有意将 Spring Cloud 迁移至 Dubbo 的小伙伴,当面对其中迁移和改造的成本时,难免望而却步。庆幸的是,Dubbo 生态体系已发生巨大变化,Dubbo Spring Cloud 作为 Spring Cloud Alibaba3 的最核心组件,完全地拥抱 Spring Cloud 技术栈,不但无缝地整合 Spring Cloud 注册中心,包括 Nacos4、Eureka5、Zookeeper6 以及 Consul7,而且完全地兼容 Spring Cloud Open Feign8 以及 @LoadBalanced RestTemplate,本文将讨论 Dubbo Spring Cloud 对 Spring Cloud 技术栈所带来的革命性变化。 注:由于 Spring Cloud 技术栈涵盖的特性众多,因此本文讨论的范围仅限于服务治理部分。简介Dubbo Spring Cloud 基于 Dubbo Spring Boot 2.7.19 和 Spring Cloud 2.x 开发,无论开发人员是 Dubbo 用户还是 Spring Cloud 用户,都能轻松地驾驭,并以接近“零”成本的代价使应用向上迁移。Dubbo Spring Cloud 致力于简化 Cloud Native 开发成本,提高研发效能以及提升应用性能等目的。 ...

April 26, 2019 · 7 min · jiezi

源码|详解分布式事务之 Seata-Client 原理及流程

摘要: 本文主要基于 spring cloud + spring jpa + spring cloud alibaba fescar + mysql + seata 的结构,搭建一个分布式系统的 demo,通过 seata 的 debug 日志和源代码,从 client 端(RM、TM)的角度分析其工作流程及原理。前言在分布式系统中,分布式事务是一个必须要解决的问题,目前使用较多的是最终一致性方案。自年初阿里开源了Fescar(四月初更名为Seata)后,该项目受到了极大的关注,目前已接近 8000 Star。Seata以高性能和零侵入的特性为目标解决微服务领域的分布式事务难题,目前正处于快速迭代中,近期小目标是生产可用的 Mysql 版本。 本文主要基于 spring cloud + spring jpa + spring cloud alibaba fescar + mysql + seata 的结构,搭建一个分布式系统的 demo,通过 seata 的 debug 日志和源代码,从 client 端(RM、TM)的角度分析其工作流程及原理。(示例项目:https://github.com/fescar-group/fescar-samples/tree/master/springcloud-jpa-seata) 为了更好地理解全文,我们来熟悉一下相关概念: XID:全局事务的唯一标识,由 ip:port:sequence 组成;Transaction Coordinator (TC):事务协调器,维护全局事务的运行状态,负责协调并驱动全局事务的提交或回滚;Transaction Manager (TM ):控制全局事务的边界,负责开启一个全局事务,并最终发起全局提交或全局回滚的决议;Resource Manager (RM):控制分支事务,负责分支注册、状态汇报,并接收事务协调器的指令,驱动分支(本地)事务的提交和回滚;提示:文中代码是基于 fescar-0.4.1 版本,由于项目刚更名为 seata 不久,其中一些包名、类名、jar包等名称还没统一更换过来,故下文中仍使用 fescar 进行表述。分布式框架支持Fescar 使用 XID 表示一个分布式事务,XID 需要在一次分布式事务请求所涉的系统中进行传递,从而向 feacar-server 发送分支事务的处理情况,以及接收 feacar-server 的 commit、rollback 指令。 Fescar 官方已支持全版本的 dubbo 协议,而对于 spring cloud(spring-boot)的分布式项目社区也提供了相应的实现 ...

April 23, 2019 · 11 min · jiezi

springboot(一)——搭建自己的springboot项目(附带日志配置)

idea使用spring Initalizr 快速构建spring boot点击新建项目,选择如图所示 点击next后 点击next,之后按照图中所示选择 选择路径 点击完成,如图所示,删除自己不想要的,项目构建完成 构建一个controller,启动项目就可以看到返回结果了 在自己的服务器搭建自己的springboot项目使用idea向远程服务传递项目设置idea 配置相关信息 上传到指定机器 配置启动脚本,基于java -jar命令start.sh#!/bin/bashnohup java -jar target/zplxjj.jar &stop.sh#!/bin/bashPID=$(ps -ef | grep target/zplxjj.jar | grep -v grep | awk '{ print $2 }')if [ -z "$PID" ]then echo Application is already stoppedelse echo kill $PID kill $PIDfi~run.sh#!/bin/bashecho stop applicationsource stop.shecho start applicationsource start.sh启动自己的项目只需要执行run.sh就行,一个自己的spring boot就搭建起来了 logback配置实际项目中,我们希望日志可以记录在服务器上面,这边用的是logback,是springboot自带的,我这边集成方式是加入logback-spring.xml文件,加入后启动项目即可,文件内容如下: <?xml version="1.0" encoding="UTF-8"?><configuration> <!--用来定义变量值的标签--> <property name="LOG_HOME" value="./logs"/> <property name="encoding" value="UTF-8"/> <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度;%M:%L是方法和行号;%msg是日志消息;%n是换行符--> <property name="normal-pattern" value="%d{yyyy-MM-dd/HH:mm:ss.SSS}|%X{localIp}|%X{requestId}|%X{requestSeq}|%X{country}|%X{deviceType}|%X{deviceId}|%X{userId}|^_^|[%t] %-5level %logger{50} %line - %m%n"/> <property name="plain-pattern" value="%d{yyyy-MM-dd.HH:mm:ss} %msg%n"/> <!-- 按照每天生成日志文件 --> <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <!--日志文件输出的文件名--> <file>${LOG_HOME}/zplxjj.log</file> <Append>true</Append> <prudent>false</prudent> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <pattern>${normal-pattern}</pattern> <charset>${encoding}</charset> </encoder> <!--按时间分割--> <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> <fileNamePattern>${LOG_HOME}/zplxjj.log.%d{yyyy-MM-dd}.%i</fileNamePattern> <maxFileSize>128MB</maxFileSize> <maxHistory>15</maxHistory> <totalSizeCap>32GB</totalSizeCap> </rollingPolicy> </appender> <!--控制台输出--> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符--> <pattern>${normal-pattern}</pattern> </encoder> </appender> <!-- log file error --> <appender name="ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender"> <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> <level>ERROR</level> </filter> <file>${LOG_HOME}/zplxjj-error.log</file> <prudent>false</prudent> <Append>true</Append> <encoder> <pattern>${normal-pattern}</pattern> <charset>${encoding}</charset> </encoder> <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> <fileNamePattern>${LOG_HOME}/zplxjj-error.log.%d{yyyy-MM-dd}.%i</fileNamePattern> <maxFileSize>128MB</maxFileSize> <maxHistory>15</maxHistory> <totalSizeCap>32GB</totalSizeCap> </rollingPolicy> </appender> <root level="INFO"> <appender-ref ref="STDOUT"/> <appender-ref ref="FILE"/> <appender-ref ref="ERROR"/> </root></configuration>效果如图: ...

April 23, 2019 · 1 min · jiezi

3分钟干货之微服务架构的局限性

虽然微服务是降低整体结构的最佳方式。然而,它有其自身的一些缺点。但在得出任何结论之前,让我们来看看其中的一些。1.开发环境超载随着应用程序及其数据库的增长,代码库也在不断扩展。随着针对每个微服务的代码扩展,它会使每个加载的应用程序的开发环境过载。这可能导致生产力的重大延迟。 DevOps复杂性单功能微服务的开发和部署并非易事。使用多种技术并创建API来集中系统是一项挑战。这需要一个经验丰富的DevOps团队。采购这样一个经验丰富的DevOps团队对于维护基于微服务的应用程序的复杂性至关重要。3.增加资源和网络使用由于多个组件协同工作,因此在某种程度上彼此进行通信非常重要。此通信将导致网络使用量增加。这需要高速可靠的网络连接。此外,运行这些应用程序的费用也会增加。所有服务都单独运行,增加了运营成本。4.测试测试应用程序可能具有挑战性,因为有单独的组件。与单片应用程序相比,微服务需要更长的时间进行测试,并且在出现任何错误时更加复杂。有时,由于测试最终会影响整个应用程序,可能会导致延迟。5.安全在Web应用程序方面,安全性至关重要。使用微服务,实现这一点很困难。当存在独立模块的集群时,每个模块都需要遵守为整个系统定义的认证和授权规范。除此之外,每个模块可能与其他模块通信,跟踪数据流变得非常困难。需要其他措施,例如具有负载平衡的API网关,以确保行为一致。这些额外的步骤导致每个微服务的开销。6.应用程序的复杂性由于微服务是独立组件,因此每个微服务通常都有一个最适合其需求的技术堆栈。例如,机器学习模块可能使用python堆栈,而计量服务可能使用Java堆栈,UI服务可能使用MEAN堆栈。这会导致复杂性,因为资源池和管理和构建新功能所需的技能将非常高。7.高初始投资微服务独立运行,它们需要独立的容器或资源来运行它们。每个项目可能有很多微服务一起工作,需要更高的投资来设置包括微服务,安全容器,负载平衡器,API网关等的所有集群。

April 22, 2019 · 1 min · jiezi

主流微服务注册中心浅析和对比

开源产品受开发者热捧,是因为其代码透明、可以参与共建、有社区进行交流和学习,当然更重要的是开源产品的接入成本低。个人开发者或者中小型公司往往会将开源产品作为选型首选。 开发者通过阅读源代码,理解产品的功能设计和架构设计,同时也可以通过本地部署来测试性能,随之而来的是对各类开源产品的对比,用以选型。不过当前关于微服务注册中心的对比,大多聚焦在功能上的对比,对架构或者性能的深入探讨,比较少见。 另一方面,作为默默支持的产品,服务注册中心往往隐藏在服务框架背后。优秀的服务框架往往会支持多种配置中心,但是注册中心的选择依然与服务框架强关联,普遍的情况是一种服务框架会带一个默认的服务注册中心。这样虽然免去了用户在选型上的烦恼,但是单个注册中心的局限性,导致用户使用多个服务框架时,必须部署多套完全不同的注册中心,这些注册中心之间的数据协同是一个问题。 本文来自Nacos社区,作者是 Nacos PMC 朱鹏飞,作者力求公正和客观的去看待主流微服务注册中心的各个维度。本文不仅仅包含常见服务注册中心产品的对比,也试图从Nacos的经验和调研中总结并阐述服务注册中心产品设计上应该去遵循和考虑的要点,文章篇幅较长,若您有不同的看法,欢迎在文末留言,或到Nacos @GitHub 提issue。 前言服务发现是一个古老的话题,当应用开始脱离单机运行和访问时,服务发现就诞生了。目前的网络架构是每个主机都有一个独立的IP地址,那么服务发现基本上都是通过某种方式获取到服务所部署的IP地址。DNS协议是最早将一个网络名称翻译为网络IP的协议,在最初的架构选型中,DNS+LVS+Nginx基本可以满足所有的RESTful服务的发现,此时服务的IP列表通常配置在Nginx或者LVS。后来出现了RPC服务,服务的上下线更加频繁,人们开始寻求一种能够支持动态上下线并且推送IP列表变化的注册中心产品。 ZooKeeper是一款经典的服务注册中心产品(虽然它最初的定位并不在于此),在很长一段时间里,它是国人在提起RPC服务注册中心时心里想到的唯一选择,这很大程度上与Dubbo在中国的普及程度有关。Consul和Eureka都出现于2014年,Consul在设计上把很多分布式服务治理上要用到的功能都包含在内,可以支持服务注册、健康检查、配置管理、Service Mesh等。而Eureka则借着微服务概念的流行,与SpringCloud生态的深度结合,也获取了大量的用户。去年开源的Nacos,则携带着阿里巴巴大规模服务生产经验,试图在服务注册和配置管理这个市场上,提供给用户一个新的选择。 当市面上有多种相似功能的产品出现时,人们往往希望知道这些产品相比较的优劣。产品本身的定位会决定它包含了哪些功能,而产品架构的设计,则会影响产品的性能和可用性等。开源产品的一个优势是开发人员可以去阅读源代码,理解产品的功能设计和架构设计,同时也可以通过本地部署来测试性能,随之而来的是各种产品的对比文章。不过当前关于注册中心的对比,往往停留在表面的功能对比上,对架构或者性能并没有非常深入的探讨。 另一个现象是服务注册中心往往隐藏在服务框架背后,作为默默支持的产品。优秀的服务框架往往会支持多种配置中心,但是注册中心的选择依然强关联与服务框架,一种普遍的情况是一种服务框架会带一个默认的服务注册中心。这样虽然免去了用户在选型上的烦恼,但是单个注册中心的局限性,导致用户使用多个服务框架时,必须部署多套完全不同的注册中心,这些注册中心之间的数据协同也是一个问题。 本文是一篇来自Nacos项目组的文章,虽然是来自Nacos,我们依然力求公正和客观的去看待服务发现所有产品的各个维度。本文不仅仅包含常见服务注册中心产品的对比,还试图从我们的经验和调研中总结和阐述服务注册中心产品设计上应该去遵循和考虑的要点。 数据模型注册中心的核心数据是服务的名字和它对应的网络地址,当服务注册了多个实例时,我们需要对不健康的实例进行过滤或者针对实例的一些特征进行流量的分配,那么就需要在实例上存储一些例如健康状态、权重等属性。随着服务规模的扩大,渐渐的又需要在整个服务级别设定一些权限规则、以及对所有实例都生效的一些开关,于是在服务级别又会设立一些属性。再往后,我们又发现单个服务的实例又会有划分为多个子集的需求,例如一个服务是多机房部署的,那么可能需要对每个机房的实例做不同的配置,这样又需要在服务和实例之间再设定一个数据级别。 Zookeeper没有针对服务发现设计数据模型,它的数据是以一种更加抽象的树形K-V组织的,因此理论上可以存储任何语义的数据。而Eureka或者Consul都是做到了实例级别的数据扩展,这可以满足大部分的场景,不过无法满足大规模和多环境的服务数据存储。Nacos在经过内部多年生产经验后提炼出的数据模型,则是一种服务-集群-实例的三层模型。如上文所说,这样基本可以满足服务在所有场景下的数据存储和管理。 Nacos的数据模型虽然相对复杂,但是它并不强制你使用它里面的所有数据,在大多数场景下,你可以选择忽略这些数据属性,此时可以降维成和Eureka和Consul一样的数据模型。 另外一个需要考虑的是数据的隔离模型,作为一个共享服务型的组件,需要能够在多个用户或者业务方使用的情况下,保证数据的隔离和安全,这在稍微大一点的业务场景中非常常见。另一方面服务注册中心往往会支持云上部署,此时就要求服务注册中心的数据模型能够适配云上的通用模型。Zookeeper、Consul和Eureka在开源层面都没有很明确的针对服务隔离的模型,Nacos则在一开始就考虑到如何让用户能够以多种维度进行数据隔离,同时能够平滑的迁移到阿里云上对应的商业化产品。 Nacos提供了四层的数据逻辑隔离模型,用户账号对应的可能是一个企业或者独立的个体,这个数据一般情况下不会透传到服务注册中心。一个用户账号可以新建多个命名空间,每个命名空间对应一个客户端实例,这个命名空间对应的注册中心物理集群是可以根据规则进行路由的,这样可以让注册中心内部的升级和迁移对用户是无感知的,同时可以根据用户的级别,为用户提供不同服务级别的物理集群。再往下是服务分组和服务名组成的二维服务标识,可以满足接口级别的服务隔离。 Nacos 1.0.0介绍的另外一个新特性是:临时实例和持久化实例。在定义上区分临时实例和持久化实例的关键是健康检查的方式。临时实例使用客户端上报模式,而持久化实例使用服务端反向探测模式。临时实例需要能够自动摘除不健康实例,而且无需持久化存储实例,那么这种实例就适用于类Gossip的协议。右边的持久化实例使用服务端探测的健康检查方式,因为客户端不会上报心跳,那么自然就不能去自动摘除下线的实例。 在大中型的公司里,这两种类型的服务往往都有。一些基础的组件例如数据库、缓存等,这些往往不能上报心跳,这种类型的服务在注册时,就需要作为持久化实例注册。而上层的业务服务,例如微服务或者Dubbo服务,服务的Provider端支持添加汇报心跳的逻辑,此时就可以使用动态服务的注册方式。 数据一致性数据一致性是分布式系统永恒的话题,Paxos协议的艰深更让数据一致性成为程序员大牛们吹水的常见话题。不过从协议层面上看,一致性的选型已经很长时间没有新的成员加入了。目前来看基本可以归为两家:一种是基于Leader的非对等部署的单点写一致性,一种是对等部署的多写一致性。当我们选用服务注册中心的时候,并没有一种协议能够覆盖所有场景,例如当注册的服务节点不会定时发送心跳到注册中心时,强一致协议看起来是唯一的选择,因为无法通过心跳来进行数据的补偿注册,第一次注册就必须保证数据不会丢失。而当客户端会定时发送心跳来汇报健康状态时,第一次的注册的成功率并不是非常关键(当然也很关键,只是相对来说我们容忍数据的少量写失败),因为后续还可以通过心跳再把数据补偿上来,此时Paxos协议的单点瓶颈就会不太划算了,这也是Eureka为什么不采用Paxos协议而采用自定义的Renew机制的原因。 这两种数据一致性协议有各自的使用场景,对服务注册的需求不同,就会导致使用不同的协议。在这里可以发现,Zookeeper在Dubbo体系下表现出的行为,其实采用Eureka的Renew机制更加合适,因为Dubbo服务往Zookeeper注册的就是临时节点,需要定时发心跳到Zookeeper来续约节点,并允许服务下线时,将Zookeeper上相应的节点摘除。Zookeeper使用ZAB协议虽然保证了数据的强一致,但是它的机房容灾能力的缺乏,无法适应一些大型场景。 Nacos因为要支持多种服务类型的注册,并能够具有机房容灾、集群扩展等必不可少的能力,在1.0.0正式支持AP和CP两种一致性协议并存。1.0.0重构了数据的读写和同步逻辑,将与业务相关的CRUD与底层的一致性同步逻辑进行了分层隔离。然后将业务的读写(主要是写,因为读会直接使用业务层的缓存)抽象为Nacos定义的数据类型,调用一致性服务进行数据同步。在决定使用CP还是AP一致性时,使用一个代理,通过可控制的规则进行转发。 目前的一致性协议实现,一个是基于简化的Raft的CP一致性,一个是基于自研协议Distro的AP一致性。Raft协议不必多言,基于Leader进行写入,其CP也并不是严格的,只是能保证一半所见一致,以及数据的丢失概率较小。Distro协议则是参考了内部ConfigServer和开源Eureka,在不借助第三方存储的情况下,实现基本大同小异。Distro重点是做了一些逻辑的优化和性能的调优。 负载均衡负载均衡严格的来说,并不算是传统注册中心的功能。一般来说服务发现的完整流程应该是先从注册中心获取到服务的实例列表,然后再根据自身的需求,来选择其中的部分实例或者按照一定的流量分配机制来访问不同的服务提供者,因此注册中心本身一般不限定服务消费者的访问策略。Eureka、Zookeeper包括Consul,本身都没有去实现可配置及可扩展的负载均衡机制,Eureka的负载均衡是由ribbon来完成的,而Consul则是由Fabio做负载均衡。 在阿里巴巴集团内部,却是使用的相反的思路。服务消费者往往并不关心所访问的服务提供者的负载均衡,它们只关心以最高效和正确的访问服务提供者的服务。而服务提供者,则非常关注自身被访问的流量的调配,这其中的第一个原因是,阿里巴巴集团内部服务访问流量巨大,稍有不慎就会导致流量异常压垮服务提供者的服务。因此服务提供者需要能够完全掌控服务的流量调配,并可以动态调整。 服务端的负载均衡,给服务提供者更强的流量控制权,但是无法满足不同的消费者希望使用不同负载均衡策略的需求。而不同负载均衡策略的场景,确实是存在的。而客户端的负载均衡则提供了这种灵活性,并对用户扩展提供更加友好的支持。但是客户端负载均衡策略如果配置不当,可能会导致服务提供者出现热点,或者压根就拿不到任何服务提供者。 抛开负载均衡到底是在服务提供者实现还是在服务消费者实现,我们看到目前的负载均衡有基于权重、服务提供者负载、响应时间、标签等策略。其中Ribbon设计的客户端负载均衡机制,主要是选择合适现有的IRule、ServerListFilter等接口实现,或者自己继承这些接口,实现自己的过滤逻辑。这里Ribbon采用的是两步负载均衡,第一步是先过滤掉不会采用的服务提供者实例,第二步是在过滤后的服务提供者实例里,实施负载均衡策略。Ribbon内置的几种负载均衡策略功能还是比较强大的,同时又因为允许用户去扩展,这可以说是一种比较好的设计。 基于标签的负载均衡策略可以做到非常灵活,Kubernetes和Fabio都已经将标签运用到了对资源的过滤中,使用标签几乎可以实现任意比例和权重的服务流量调配。但是标签本身需要单独的存储以及读写功能,不管是放在注册中心本身或者对接第三方的CMDB。 在Nacos 0.7.0版本中,我们除了提供基于健康检查和权重的负载均衡方式外,还新提供了基于第三方CMDB的标签负载均衡器,具体可以参考CMDB功能介绍文章。使用基于标签的负载均衡器,目前可以实现同标签优先访问的流量调度策略,实际的应用场景中,可以用来实现服务的就近访问,当您的服务部署在多个地域时,这非常有用。使用这个标签负载均衡器,可以支持非常多的场景,这不是本文要详细介绍的。虽然目前Nacos里支持的标签表达式并不丰富,不过我们会逐步扩展它支持的语法。除此以外,Nacos定义了Selector,作为负载均衡的统一抽象。关于Selector,由于篇幅关系,我们会有单独的文章进行介绍。 理想的负载均衡实现应该是什么样的呢?不同的人会有不同的答案。Nacos试图做的是将服务端负载均衡与客户端负载均衡通过某种机制结合起来,提供用户扩展性,并给予用户充分的自主选择权和轻便的使用方式。负载均衡是一个很大的话题,当我们在关注注册中心提供的负载均衡策略时,需要注意该注册中心是否有我需要的负载均衡方式,使用方式是否复杂。如果没有,那么是否允许我方便的扩展来实现我需求的负载均衡策略。 健康检查Zookeeper和Eureka都实现了一种TTL的机制,就是如果客户端在一定时间内没有向注册中心发送心跳,则会将这个客户端摘除。Eureka做的更好的一点在于它允许在注册服务的时候,自定义检查自身状态的健康检查方法。这在服务实例能够保持心跳上报的场景下,是一种比较好的体验,在Dubbo和SpringCloud这两大体系内,也被培养成用户心智上的默认行为。Nacos也支持这种TTL机制,不过这与ConfigServer在阿里巴巴内部的机制又有一些区别。Nacos目前支持临时实例使用心跳上报方式维持活性,发送心跳的周期默认是5秒,Nacos服务端会在15秒没收到心跳后将实例设置为不健康,在30秒没收到心跳时将这个临时实例摘除。 不过正如前文所说,有一些服务无法上报心跳,但是可以提供一个检测接口,由外部去探测。这样的服务也是广泛存在的,而且以我们的经验,这些服务对服务发现和负载均衡的需求同样强烈。服务端健康检查最常见的方式是TCP端口探测和HTTP接口返回码探测,这两种探测方式因为其协议的通用性可以支持绝大多数的健康检查场景。在其他一些特殊的场景中,可能还需要执行特殊的接口才能判断服务是否可用。例如部署了数据库的主备,数据库的主备可能会在某些情况下切换,需要通过服务名对外提供访问,保证当前访问的库是主库。此时的健康检查接口,可能就是一个检查数据库是否是主库的MYSQL命令了。 客户端健康检查和服务端健康检查有一些不同的关注点。客户端健康检查主要关注客户端上报心跳的方式、服务端摘除不健康客户端的机制。而服务端健康检查,则关注探测客户端的方式、灵敏度及设置客户端健康状态的机制。从实现复杂性来说,服务端探测肯定是要更加复杂的,因为需要服务端根据注册服务配置的健康检查方式,去执行相应的接口,判断相应的返回结果,并做好重试机制和线程池的管理。这与客户端探测,只需要等待心跳,然后刷新TTL是不一样的。同时服务端健康检查无法摘除不健康实例,这意味着只要注册过的服务实例,如果不调用接口主动注销,这些服务实例都需要去维持健康检查的探测任务,而客户端则可以随时摘除不健康实例,减轻服务端的压力。 Nacos既支持客户端的健康检查,也支持服务端的健康检查,同一个服务可以切换健康检查模式。我们认为这种健康检查方式的多样性非常重要,这样可以支持各种类型的服务,让这些服务都可以使用到Nacos的负载均衡能力。Nacos下一步要做的是实现健康检查方式的用户扩展机制,不管是服务端探测还是客户端探测。这样可以支持用户传入一条业务语义的请求,然后由Nacos去执行,做到健康检查的定制。 性能与容量虽然大部分用户用到的性能不高,但是他们仍然希望选用的产品的性能越高越好。影响读写性能的因素很多:一致性协议、机器的配置、集群的规模、存量数据的规模、数据结构及读写逻辑的设计等等。在服务发现的场景中,我们认为读写性能都是非常关键的,但是并非性能越高就越好,因为追求性能往往需要其他方面做出牺牲。Zookeeper在写性能上似乎能达到上万的TPS,这得益于Zookeeper精巧的设计,不过这显然是因为有一系列的前提存在。首先Zookeeper的写逻辑就是进行K-V的写入,内部没有聚合;其次Zookeeper舍弃了服务发现的基本功能如健康检查、友好的查询接口,它在支持这些功能的时候,显然需要增加一些逻辑,甚至弃用现有的数据结构;最后,Paxos协议本身就限制了Zookeeper集群的规模,3、5个节点是不能应对大规模的服务订阅和查询的。 在对容量的评估时,不仅要针对企业现有的服务规模进行评估,也要对未来3到5年的扩展规模进行预测。阿里巴巴的中间件在内部支撑着集团百万级别服务实例,在容量上遇到的挑战可以说不会小于任何互联网公司。这个容量不仅仅意味着整体注册的实例数,也同时包含单个服务的实例数、整体的订阅者的数目以及查询的QPS等。Nacos在内部淘汰Zookeeper和Eureka的过程中,容量是一个非常重要的因素。 Zookeeper的容量,从存储节点数来说,可以达到百万级别。不过如上面所说,这并不代表容量的全部,当大量的实例上下线时,Zookeeper的表现并不稳定,同时在推送机制上的缺陷,会引起客户端的资源占用上升,从而性能急剧下降。 Eureka在服务实例规模在5000左右的时候,就已经出现服务不可用的问题,甚至在压测的过程中,如果并发的线程数过高,就会造成Eureka crash。不过如果服务规模在1000上下,几乎目前所有的注册中心都可以满足。毕竟我们看到Eureka作为SpringCloud的注册中心,在国内也没有看到很广泛的对于容量或者性能的问题报告。 Nacos在开源版本中,服务实例注册的支撑量约为100万,服务的数量可以达到10万以上。在实际的部署环境中,这个数字还会因为机器、网络的配置与JVM参数的不同,可能会有所差别。图9展示了Nacos在使用1.0.0版本进行压力测试后的结果总结,针对容量、并发、扩展性和延时等进行了测试和统计。 完整的测试报告可以参考Nacos官网:https://nacos.io/en-us/docs/nacos-naming-benchmark.htmlhttps://nacos.io/en-us/docs/nacos-config-benchmark.html 易用性易用性也是用户比较关注的一块内容。产品虽然可以在功能特性或者性能上做到非常先进,但是如果用户的使用成本极高,也会让用户望而却步。易用性包括多方面的工作,例如API和客户端的接入是否简单,文档是否齐全易懂,控制台界面是否完善等。对于开源产品来说,还有一块是社区是否活跃。在比较Nacos、Eureka和Zookeeper在易用性上的表现时,我们诚邀社区的用户进行全方位的反馈,因为毕竟在阿里巴巴集团内部,我们对Eureka、Zookeeper的使用场景是有限的。从我们使用的经验和调研来看,Zookeeper的易用性是比较差的,Zookeeper的客户端使用比较复杂,没有针对服务发现的模型设计以及相应的API封装,需要依赖方自己处理。对多语言的支持也不太好,同时没有比较好用的控制台进行运维管理。 Eureka和Nacos相比较Zookeeper而言,已经改善很多,这两个产品有针对服务注册与发现的客户端,也有基于SpringCloud体系的starter,帮助用户以非常低的成本无感知的做到服务注册与发现。同时还暴露标准的HTTP接口,支持多语言和跨平台访问。Eureka和Nacos都提供官方的控制台来查询服务注册情况。不过随着Eureka 2.0宣布停止开发,Eureka在针对用户使用上的优化后续应该不会再有比较大的投入,而Nacos目前依然在建设中,除了目前支持的易用性特性以外,后续还会继续增强控制台的能力,增加控制台登录和权限的管控,监控体系和Metrics的暴露,持续通过官网等渠道完善使用文档,多语言SDK的开发等。 从社区活跃度的角度来看,目前由于Zookeeper和Eureka的存量用户较多,很多教程以及问题排查都可以在社区搜索到,这方面新开源的Nacos还需要随着时间继续沉淀。 集群扩展性集群扩展性和集群容量以及读写性能关系紧密。当使用一个比较小的集群规模就可以支撑远高于现有数量的服务注册及访问时,集群的扩展能力暂时就不会那么重要。从协议的层面上来说,Zookeeper使用的ZAB协议,由于是单点写,在集群扩展性上不具备优势。Eureka在协议上来说理论上可以扩展到很大规模,因为都是点对点的数据同步,但是从我们对Eureka的运维经验来看,Eureka集群在扩容之后,性能上有很大问题。 集群扩展性的另一个方面是多地域部署和容灾的支持。当讲究集群的高可用和稳定性以及网络上的跨地域延迟要求能够在每个地域都部署集群的时候,我们现有的方案有多机房容灾、异地多活、多数据中心等。 首先是双机房容灾,基于Leader写的协议不做改造是无法支持的,这意味着Zookeeper不能在没有人工干预的情况下做到双机房容灾。在单机房断网情况下,使机房内服务可用并不难,难的是如何在断网恢复后做数据聚合,Zookeeper的单点写模式就会有断网恢复后的数据对账问题。Eureka的部署模式天然支持多机房容灾,因为Eureka采用的是纯临时实例的注册模式:不持久化、所有数据都可以通过客户端心跳上报进行补偿。上面说到,临时实例和持久化实例都有它的应用场景,为了能够兼容这两种场景,Nacos支持两种模式的部署,一种是和Eureka一样的AP协议的部署,这种模式只支持临时实例,可以完美替代当前的Zookeeper、Eureka,并支持机房容灾。另一种是支持持久化实例的CP模式,这种情况下不支持双机房容灾。 在谈到异地多活时,很巧的是,很多业务组件的异地多活正是依靠服务注册中心和配置中心来实现的,这其中包含流量的调度和集群的访问规则的修改等。机房容灾是异地多活的一部分,但是要让业务能够在访问服务注册中心时,动态调整访问的集群节点,这需要第三方的组件来做路由。异地多活往往是一个包含所有产品线的总体方案,很难说单个产品是否支持异地多活。 多数据中心其实也算是异地多活的一部分。从单个产品的维度上,Zookeeper和Eureka没有给出官方的多数据中心方案。Nacos基于阿里巴巴内部的使用经验,提供的解决方案是才有Nacos-Sync组件来做数据中心之间的数据同步,这意味着每个数据中心的Nacos集群都会有多个数据中心的全量数据。Nacos-Sync是Nacos生态组件里的重要一环,不仅会承担Nacos集群与Nacos集群之间的数据同步,也会承担Nacos集群与Eureka、Zookeeper、Kubernetes及Consul之间的数据同步。 ...

April 22, 2019 · 1 min · jiezi

为什么说流处理即未来?

作者|Stephan Ewen整理|秦江杰本文整理自 Flink 创始公司 Ververica 联合创始人兼 CTO - Stephan Ewen 在 Flink Forward China 2018 上的演讲《Stream Processing takes on Everything》。这个演讲主题看似比较激进:流处理解决所有问题。很多人对于 Flink 可能还停留在最初的认知,觉得 Flink 是一个流处理引擎,实际上 Flink 可以做很多其他的工作,比如批处理、应用程序。在这个演讲中,Stephan 首先会简单说明他对 Flink 功能的观点,然后深入介绍一个特定领域的应用和事件处理场景。这个场景乍看起来不是一个流处理的使用场景,但是在 Stephan 看来,它实际上就是一个很有趣的流处理使用场景。Flink社区专刊下载地址第一期:不仅仅是流计算第二期:重新定义计算上图对为什么流处理可以处理一切作出诠释,将数据看做流是一个自然而又十分强大的想法。大部分数据的产生过程都是随时间生成的流,比如一个 Petabyte 的数据不会凭空产生。这些数据通常都是一些事件的积累,比如支付、将商品放入购物车,网页浏览,传感器采样输出。基于数据是流的想法,我们对数据处理可以有相应的理解。比如将过去的历史数据看做是一个截止到某一时刻的有限的流,或是将一个实时处理应用看成是从某一个时刻开始处理未来到达的数据。可能在未来某个时刻它会停止,那么它就变成了处理从开始时刻到停止时刻的有限数据的批处理。当然,它也有可能一直运行下去,不断处理新到达的数据。这个对数据的重要理解方式非常强大,基于这一理解,Flink 可以支持整个数据处理范畴内的所有场景。最广为人知的 Flink 使用场景是流分析、连续处理(或者说渐进式处理),这些场景中 Flink 实时或者近实时的处理数据,或者采集之前提到的历史数据并且连续的对这些事件进行计算。晓伟在之前的演讲中提到一个非常好的例子来说明怎么样通过对 Flink 进行一些优化,进而可以针对有限数据集做一些特别的处理,这使得 Flink 能够很好的支持批处理的场景,从性能上来说能够与最先进的批处理引擎相媲美。而在这根轴的另一头,是我今天的演讲将要说明的场景 – 事件驱动的应用。这类应用普遍存在于任何服务或者微服务的架构中。这类应用接收各类事件(可能是 RPC 调用、HTTP 请求),并且对这些事件作出一些响应,比如把商品放进购物车,或者加入社交网络中的某个群组。在我进一步展开今天的演讲之前,我想先对社区在 Flink 的传统领域(实时分析、连续处理)近期所做的工作做一个介绍。Flink 1.7 在 2018 年 11 月 30 日已经发布。在 Flink 1.7 中为典型的流处理场景加入了一些非常有趣的功能。比如我个人非常感兴趣的在流式 SQL 中带时间版本的 Join。一个基本想法是有两个不同的流,其中一个流被定义为随时间变化的参照表,另一个是与参照表进行 Join 的事件流。比如事件流是一个订单流,参照表是不断被更新的汇率,而每个订单需要使用最新的汇率来进行换算,并将换算的结果输出到结果表。这个例子在标准的 SQL 当中实际上并不容易表达,但在我们对 Streaming SQL 做了一点小的扩展以后,这个逻辑表达变得非常简单,我们发现这样的表达有非常多的应用场景。另一个在流处理领域十分强大的新功能是将复杂事件处理(CEP)和 SQL 相结合。CEP 应用观察事件模式。比如某个 CEP 应用观察股市,当有两个上涨后紧跟一个下跌时,这个应用可能做些交易。再比如一个观察温度计的应用,当它发现有温度计在两个超过 90 摄氏度的读数之后的两分钟里没有任何操作,可能会进行一些操作。与 SQL 的结合使这类逻辑的表达也变得非常简单。第三个 Flink 1.7 中做了很多工作的功能是 Schema 升级。这个功能和基于流的应用紧密相关。就像你可以对数据库进行数据 Schema 升级一样,你可以修改 Flink 表中列的类型或者重新写一个列。另外我想简单介绍的是流处理技术不仅仅是简单对数据进行计算,这还包括了很多与外部系统进行事务交互。流处理引擎需要在采用不同协议的系统之间以事务的方式移动数据,并保证计算过程和数据的一致性。这一部分功能也是在 Flink 1.7 中得到了增强。以上我对 Flink 1.7 的新功能向大家做了简单总结。下面让我们来看看今天我演讲的主要部分,也就是利用 Flink 来搭建应用和服务。我将说明为什么流处理是一个搭建应用和服务或者微服务的有趣技术。我将从左边这个高度简化的图说起,我们一会儿将聊一些其中的细节。首先我们来看一个理解应用简单的视角。如左图所示,一个应用可以是一个 Container,一个 Spring 应用,或者 Java 应用、Ruby 应用,等等。这个应用从诸如 RPC,HTTP 等渠道接收请求,然后依据请求进行数据库变更。这个应用也可能调用另一个微服务并进行下一步的处理。我们可以非常自然的想到进入到应用的这些请求可以看做是个事件组成的序列,所以我们可以把它们看做是事件流。可能这些事件被缓存在消息队列中,而应用会从消息队列中消费这些事件进行处理,当应用需要响应一个请求时,它将结果输出到另一个消息队列,而请求发送方可以从这个消息队列中消费得到所发送请求的响应。在这张图中我们已经可以看到一些有趣的不同。第一个不同是在这张图中应用和数据库不再是分开的两个实体,而是被一个有状态的流处理应用所代替。所以在流处理应用的架构中,不再有应用和数据库的连接了,它们被放到了一起。这个做法有利有弊,但其中有些好处是非常重要的。首先是性能上的好处是明显的,因为应用不再需要和数据库进行交互,处理可以基于内存中的变量进行。其次这种做法有很好并且很简单的一致性。这张图被简化了很多,实际上我们通常会有很多个应用,而不是一个被隔离的应用,很多情况下你的应用会更符合这张图。系统中有个接收请求的接口,然后请求被发送到第一个应用,可能会再被发到另一个应用,然后得到相应。在图中有些应用会消费中间结果的流。这张图已经展示了为什么流处理是更适合比较复杂的微服务场景的技术。因为很多时候系统中不会有一个直接接收用户请求并直接响应的服务,通常来说一个微服务需要跟其他微服务通信。这正如在流处理的架构中不同应用在创建输出流,同时基于衍生出的流再创建并输出新的流。到目前为止,我们看到的内容多少还比较直观。而对基于流处理技术的微服务架构而言,人们最常问的一个问题是如何保证事务性?如果系统中使用的是数据库,通常来说都会有非常成熟复杂的数据校验和事务模型。这也是数据库在过去许多年中十分成功的原因。开始一个事务,对数据做一些操作,提交或者撤销一个事务。这个机制使得数据完整性得到了保证(一致性,持久性等等)。那么在流处理中我们怎么做到同样的事情呢?作为一个优秀的流处理引擎,Flink 支持了恰好一次语义,保证了每个事件只会被处理一遍。但是这依然对某些操作有限制,这也成为了使用流处理应用的一个障碍。我们通过一个非常简单流处理应用例子来看我们可以做一些什么扩展来解决这个问题。我们会看到,解决办法其实出奇的简单。让我们以这个教科书式的事务为例子来看一下事务性应用的过程。这个系统维护了账户和其中存款余额的信息。这样的信息可能是银行或者在线支付系统的场景中用到的。假设我们想要处理类似下面的事务:如果账户 A 中的余额大于 100,那么从账户 A 中转账 50 元到账户 B。这是个非常简单的两个账户之间进行转账的例子。数据库对于这样的事务已经有了一个核心的范式,也就是原子性,一致性,隔离性和持久性(ACID)。这是能够让用户放心使用事务的几个基本保证。有了他们,用户不用担心钱在转账过程中会丢失或者其他问题。让我们用这个例子来放到流处理应用中,来让流处理应用也能提供和数据相同的 ACID 支持:原子性要求一个转账要不就完全完成,也就是说转账金额从一个账户减少,并增加到另一个账户,要不就两个账户的余额都没有变化。而不会只有一个账户余额改变。否则的话钱就会凭空减少或者凭空增加。一致性和隔离性是说如果有很多用户同时想要进行转账,那么这些转账行为之间应该互不干扰,每个转账行为应该被独立的完成,并且完成后每个账户的余额应该是正确的。也就是说如果两个用户同时操作同一个账户,系统不应该出错。持久性指的是如果一个操作已经完成,那么这个操作的结果会被妥善的保存而不会丢失。我们假设持久性已经被满足。一个流处理器有状态,这个状态会被 checkpoint,所以流处理器的状态是可恢复的。也就是说只要我们完成了一个修改,并且这个修改被 checkpoint 了,那么这个修改就是持久化的。让我们来看看另外三个例子。设想一下,如果我们用流处理应用来实现这样一个转账系统会发生什么。我们先把问题简化一些,假设转账不需要有条件,仅仅是将 50 元从账户 A 转到账户,也就是说账户 A 的余额减少 50 元而账户 B 的余额增加 50 元。我们的系统是一个分布式的并行系统,而不是一个单机系统。简单起见我们假设系统中只有两台机器,这两台机器可以是不同的物理机或者是在 YARN 或者 Kubernetes 上不同的容器。总之它们是两个不同的流处理器实例,数据分布在这两个流处理器上。我们假设账户 A 的数据由其中一台机器维护,而账户 B 的数据有另一台机器维护。现在我们要做个转账,将 50 元从账户 A 转移到账户 B,我们把这个请求放进队列中,然后这个转账请求被分解为对账户 A 和 B 分别进行操作,并且根据键将这两个操作路由到维护账户 A 和维护账户 B 的这两台机器上,这两台机器分别根据要求对账户 A 和账户 B 的余额进行改动。这并不是事务操作,而只是两个独立无意义的改动。一旦我们将转账的请求改的稍微复杂一些就会发现问题。下面我们假设转账是有条件的,我们只想在账户 A 的余额足够的情况下才进行转账,这样就已经有些不太对了。如果我们还是像之前那样操作,将这个转账请求分别发送给维护账户 A 和 B 的两台机器,如果 A 没有足够的余额,那么 A 的余额不会发生变化,而 B 的余额可能已经被改动了。我们就违反了一致性的要求。我们看到我们需要首先以某种方式统一做出是否需要更改余额的决定,如果这个统一的决定中余额需要被修改,我们再进行修改余额的操作。所以我们先给维护 A 的余额的机器发送一个请求,让它查看 A 的余额。我们也可以对 B 做同样的事情,但是这个例子里面我们不关心 B 的余额。然后我们把所有这样的条件检查的请求汇总起来去检验条件是否满足。因为 Flink 这样的流处理器支持迭代,如果满足转账条件,我们可以把这个余额改动的操作放进迭代的反馈流当中来告诉对应的节点来进行余额修改。反之如果条件不满足,那么余额改动的操作将不会被放进反馈流。这个例子里面,通过这种方式我们可以正确的进行转账操作。从某种角度上来说我们实现了原子性,基于一个条件我们可以进行全部的余额修改,或者不进行任何余额修改。这部分依然还是比较直观的,更大的困难是在于如何做到并发请求的隔离性。假设我们的系统没有变,但是系统中有多个并发的请求。我们在之前的演讲中已经知道,这样的并发可能达到每秒钟几十亿条。如图,我们的系统可能从两个流中同时接受请求。如果这两个请求同时到达,我们像之前那样将每个请求拆分成多个请求,首先检查余额条件,然后进行余额操作。然而我们发现这会带来问题。管理账户 A 的机器会首先检查 A 的余额是否大于 50,然后又会检查 A 的余额是否大于 100,因为两个条件都满足,所以两笔转账操作都会进行,但实际上账户 A 上的余额可能无法同时完成两笔转账,而只能完成 50 元或者 100 元的转账中的一笔。这里我们需要进一步思考怎么样来处理并发的请求,我们不能只是简单地并发处理请求,这会违反事务的保证。从某种角度来说,这是整个数据库事务的核心。数据库的专家们花了一些时间提供了不同解决方案,有的方案比较简单,有的则很复杂。但所有的方案都不是那么容易,尤其是在分布式系统当中。在流处理中怎么解决这个问题呢?直觉上讲,如果我们能够让所有的事务都按照顺序依次发生,那么问题就解决了,这也被成为可序列化的特性。但是我们当然不希望所有的请求都被依次顺序处理,这与我们使用分布式系统的初衷相违背。所以我们需要保证这些请求最后的产生的影响看起来是按照顺序发生的,也就是一个请求产生的影响是基于前一个请求产生影响的基础之上的。换句话说也就是一个事务的修改需要在前一个事务的所有修改都完成后才能进行。这种希望一件事在另一件事之后发生的要求看起来很熟悉,这似乎是我们以前在流处理中曾经遇到过的问题。是的,这听上去像是事件时间。用高度简化的方式来解释,如果所有的请求都在不同的事件时间产生,即使由于种种原因他们到达处理器的时间是乱序的,流处理器依然会根据他们的事件时间来对他们进行处理。流处理器会使得所有的事件的影响看上去都是按顺序发生的。按事件时间处理是 Flink 已经支持的功能。那么详细说来,我们到底怎么解决这个一致性问题呢?假设我们有并行的请求输入并行的事务请求,这些请求读取某些表中的记录,然后修改某些表中的记录。我们首先需要做的是把这些事务请求根据事件时间顺序摆放。这些请求的事务时间不能够相同,但是他们之间的时间也需要足够接近,这是因为在事件时间的处理过程中会引入一定的延迟,我们需要保证所处理的事件时间在向前推进。因此第一步是定义事务执行的顺序,也就是说需要有一个聪明的算法来为每个事务制定事件时间。在图上,假设这三个事务的事件时间分别是 T+2, T 和 T+1。那么第二个事务的影响需要在第一和第三个事务之前。不同的事务所做的修改是不同的,每个事务都会产生不同的操作请求来修改状态。我们现在需要将对访问每个行和状态的事件进行排序,保证他们的访问是符合事件时间顺序的。这也意味着那些相互之间没有关系的事务之间自然也没有了任何影响。比如这里的第三个事务请求,它与前两个事务之间没有访问共同的状态,所以它的事件时间排序与前两个事务也相互独立。而当前两个事务之间的操作的到达顺序与事件时间不符时,Flink 则会依据它们的事件时间进行排序后再处理。必须承认,这样说还是进行了一些简化,我们还需要做一些事情来保证高效执行,但是总体原则上来说,这就是全部的设计。除此之外我们并不需要更多其他东西。为了实现这个设计,我们引入了一种聪明的分布式事件时间分配机制。这里的事件时间是逻辑时间,它并不需要有什么现实意义,比如它不需要是真实的时钟。使用 Flink 的乱序处理能力,并且使用 Flink 迭代计算的功能来进行某些前提条件的检查。这些就是我们构建一个支持事务的流处理器的要素。我们实际上已经完成了这个工作,称之为流式账簿(Streaming Ledger),这是个在 Apache Flink 上很小的库。它基于流处理器做到了满足 ACID 的多键事务性操作。我相信这是个非常有趣的进化。流处理器一开始基本上没有任何保障,然后类似 Storm 的系统增加了至少一次的保证。但显然至少一次依然不够好。然后我们看到了恰好一次的语义,这是一个大的进步,但这只是对于单行操作的恰好一次语义,这与键值库很类似。而支持多行恰好一次或者多行事务操作将流处理器提升到了一个可以解决传统意义上关系型数据库所应用场景的阶段。Streaming Ledger 的实现方式是允许用户定义一些表和对这些表进行修改的函数。Streaming Ledger 会运行这些函数和表,所有的这些一起编译成一个 Apache Flink 的有向无环图(DAG)。Streaming Ledger 会注入所有事务时间分配的逻辑,以此来保证所有事务的一致性。搭建这样一个库并不难,难的是让它高性能的运行。让我们来看看它的性能。这些性能测试是几个月之前的,我们并没有做什么特别的优化,我们只是想看看一些最简单的方法能够有什么样的性能表现。而实际性能表现看起来相当不错。如果你看这些性能条形成的阶梯跨度,随着流处理器数量的增长,性能的增长相当线性。在事务设计中,没有任何协同或者锁参与其中。这只是流处理,将事件流推入系统,缓存一小段时间来做一些乱序处理,然后做一些本地状态更新。在这个方案中,没有什么特别代价高昂的操作。在图中性能增长似乎超过了线性,我想这主要是因为 JAVA 的 JVM 当中 GC 的工作原因导致的。在 32 个节点的情况下我们每秒可以处理大约两百万个事务。为了与数据库性能测试进行对比,通常当你看数据库的性能测试时,你会看到类似读写操作比的说明,比如 10% 的更新操作。而我们的测试使用的是 100% 的更新操作,而每个写操作至少更新在不同分区上的 4 行数据,我们的表的大小大约是两亿行。即便没有任何优化,这个方案的性能也非常不错。另一个在事务性能中有趣的问题是当更新的操作对象是一个比较小的集合时的性能。如果事务之间没有冲突,并发的事务处理是一个容易的事情。如果所有的事务都独立进行而互不干扰,那这个不是什么难题,任何系统应该都能很好的解决这样的问题。当所有的事务都开始操作同一些行时,事情开始变得更有趣了,你需要隔离不同的修改来保证一致性。所以我们开始比较一个只读的程序、一个又读又写但是没有写冲突的程序和一个又读又写并有中等程度写冲突的程序这三者之间的性能。你可以看到性能表现相当稳定。这就像是一个乐观的并发冲突控制,表现很不错。那如果我们真的想要针对这类系统的阿喀琉斯之踵进行考验,也就是反复的更新同一个小集合中的键。在传统数据库中,这种情况下可能会出现反复重试,反复失败再重试,这是一种我们总想避免的糟糕情况。是的,我们的确需要付出性能代价,这很自然,因为如果你的表中有几行数据每个人都想更新,那么你的系统就失去了并发性,这本身就是个问题。但是这种情况下,系统并没崩溃,它仍然在稳定的处理请求,虽然失去了一些并发性,但是请求依然能够被处理。这是因为我们没有冲突重试的机制,你可以认为我们有一个基于乱序处理天然的冲突避免的机制,这是一种非常稳定和强大的技术。我们还尝试了在跨地域分布的情况下的性能表现。比如我们在美国、巴西,欧洲,日本和澳大利亚各设置了一个 Flink 集群。也就是说我们有个全球分布的系统。如果你在使用一个关系型数据库,那么你会付出相当高昂的性能代价,因为通信的延迟变得相当高。跨大洲的信息交互比在同一个数据中心甚至同一个机架上的信息交互要产生大得多的延迟。但是有趣的是,流处理的方式对延迟并不是十分敏感,延迟对性能有所影响,但是相比其它很多方案,延迟对流处理的影响要小得多。所以,在这样的全球分布式环境中执行分布式程序,的确会有更差的性能,部分原因也是因为跨大洲的通信带宽不如统一数据中心里的带宽,但是性能表现依然不差。实际上,你可以拿它当做一个跨地域的数据库,同时仍然能够在一个大概 10 个节点的集群上获得每秒几十万条事务的处理能力。在这个测试中我们只用了 10 个节点,每个大洲两个节点。所以 10 个节点可以带来全球分布的每秒 20 万事务的处理能力。我认为这是很有趣的结果,这是因为这个方案对延迟并不敏感。我已经说了很多利用流处理来实现事务性的应用。可能听起来这是个很自然的想法,从某种角度上来说的确是这样。但是它的确需要一些很复杂的机制来作为支撑。它需要一个连续处理而非微批处理的能力,需要能够做迭代,需要复杂的基于事件时间处理乱序处理。为了更好地性能,它需要灵活的状态抽象和异步 checkpoint 机制。这些是真正困难的事情。这些不是由 Ledger Streaming 库实现的,而是 Apache Flink 实现的,所以即使对这类事务性的应用而言,Apache Flink 也是真正的中流砥柱。至此,我们可以说流处理不仅仅支持连续处理、流式分析、批处理或者事件驱动的处理,你也可以用它做事务性的处理。当然,前提是你有一个足够强大的流处理引擎。这就是我演讲的全部内容。本文作者:apache_flink阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

April 18, 2019 · 2 min · jiezi

灵雀云CEO左玥被任命为信通院云原生产业联盟平台架构组副组长 推动云原生国家标准制定

4月8日-10日,中国信息通信研究院云计算标准和开源推进委员会(TC608)第三次全体会员大会在成都隆重召开。大会旨在对多项云计算标准进行讨论,进一步推动云计算技术的发展、标准的落地和产业共性问题的解决。本次会议由中国信息通信研究所云大所所长何宝宏、云计算标准和开源推进委员会常务副主席栗蔚领衔指导,灵雀云CEO左玥被任命为云原生联盟平台架构组副组长,出席了大会授牌仪式并被颁发证书。在10日上午的工作组会议中,云计算开源产业联盟(OSCAR)下设的子联盟——“云原生产业联盟(Cloud Native Industry Alliance,简称CNIA)”正式通过了TC608全体会员审议。栗蔚主任对联盟的成立背景、组织架构、联盟宗旨和愿景等进行了介绍。联盟下设技术专家委员会与平台架构组、DevOps工作组、用户工作组和生态伙伴工作组等四个工作组。其中平台架构工作组包含容器项目、微服务项目、Serverless项目及更多云原生与其他领域融合的项目组,承担了云原生平台技术与实践经验标准化制定与推广的重任。灵雀云CEO左玥被提名为联盟平台架构组副组长。与此同时,招商银行苏贝、浦发银行杨欣捷提名为用户工作组组长;东华软件王昕提名为生态伙伴工作组组长。随后,何宝宏所长颁发证书,对联盟各工作组的组长予以委任。云原生产业联盟(CNIA)前身为云原生技术实践联盟(CNBPA),系由灵雀云牵头,行业顶尖平台提供商,行业解决方案与服务商,行业云原生典型用户联合发起的机构,CNBPA旨在促进国内云原生行业间交流,加强企业和行业用户之间的沟通,推进云原生技术在国内的发展和落地,是国内首个以云原生技术应用实践为核心的组织。CNIA沿用了原CNBPA部分章程制度及工作规划,并平移了原CNBPA成员。首批吸引了腾讯云、阿里云、华为、灵雀云、金山云、浪潮、中油瑞飞、东华软件、北明软件、中科软、深信服等18家理事会员单位与数十家普通会员单位加入。作为联盟首批创始成员,以及国内云原生技术及实践落地的推动者,灵雀云积极参与了国内云原生平台标准的制定与及未来研究方向的探讨。在CNIA发起的国内首个“云原生技术实践白皮书“和首个”无服务架构技术白皮书“中,灵雀云分别承担了核心的编写任务,将自身的云原生技术能力和解决方案能力进行了输出。至此,云原生产业联盟CNIA的筹备工作全部结束。 4月24日, CNIA将在云原生产业大会上正式宣布成立。以灵雀云为代表的企业将在CNIA的带领下持续推进云原生技术产业化落地,推进行业标准化工作,推广领先解决方案,构建技术带动实践、实践反哺技术的良性生态,进一步推动我国云原生技术的成熟发展!

April 15, 2019 · 1 min · jiezi

【推荐】最新200篇:技术文章整理

作为面试官,我是如何甄别应聘者的包装程度Go语言和Java、python等其他语言的对比分析Redis和MySQLRedis:主从复制的原理详解Redis:RDB 和 AOF 持久化的原理是什么?Redis:面试中经常被问到的 Redis 持久化与恢复Redis:实现故障恢复自动化:详解Redis哨兵技术Redis:查漏补缺:最易错过的技术要点大扫盲MySQL:意外宕机不难解决,但你真的懂数据恢复吗MySQL:每秒57万的写入,带你飞MySQL:三大知识点,索引、锁、事务,原理分析MySQL:查询速度慢与性能差的解决方案MySQL:事务ACID特性的实现原理MySQL:大佬是怎么思考设计MySQL优化方案的MySQL:一份非常完整的MySQL规范面试必备面试必备:浅谈Java中15种锁的分析比较面试官问:Spring AOP 的实现机制面试官问:ZooKeeper 一致性协议 ZAB 原理面试必备:46张PPT讲述JVM、GC算法和性能调优面试必备:最新 Java 面试题汇总,附答案面试必备:基于 Zookeeper 的分布式锁实现面试必备:30 道 Dubbo 面试题及答案面试必备:数组下标为什么从0开始,而不是1面试必备:Java并发编程75道面试题及答案稳了面试必备:一次生产的JVM优化面试必备:动图+源码+总结:演示JDK8 中的数据结构面试必备:133 道 Java 面试题及答案架构架构:数据库分库分表,何时分?怎样分?架构:分库分表-垂直?水平?架构:主备+分库?主从+读写分离?架构:分库分表就能无限扩容吗架构:通过10张图介绍,分布式架构如何演进!架构:MQ消息队列应用场景比较介绍架构:架构师成长之路必备技能)架构:谈谈架构架构:秒杀系统架构优化思路架构:Web系统大规模并发:电商秒杀与抢购架构:小团队的微服务架构演进之路架构:高并发文章浏览量计数系统设计Google 出品的 Java 编码规范Java性能优化的45个细节(珍藏版)面试必备面试必备:通俗易懂,常用线程池执行的-流程图面试必备:我们来谈下高并发和分布式中的幂等处理面试必备:大型分布式系统中的缓存架构面试必备:美团面试经历,贡献出来一起学习面试必备:高性能MySQL索引与优化实践面试必备:搭建网站扫码登录的功能设计面试必备:技术变化那么快,学 Docker 看这篇就够了面试必备:一文看懂 MySQL 高性能优化技巧实践面试必备:分布式事务不理解?一次给你讲清楚!面试必备:京东到家订单中心ES集群架设演进历程面试必备:一次性能优化:吞吐量从1提升到2500面试必备:Spring 面试问题 TOP 50面试必备:看京东系统架构师如何让笨重的架构变得灵巧面试必备:日请求8亿Web流量分布式系统的高容错性实践面试必备:微服务架构:如何用十步解耦你的系统?面试必备:想了解五大MQ之间的差异?这一篇文章就够了!面试必备:学习MySQL高性能优化原理,这一篇就够了!面试必备:优雅地SpringBoot中实现消息的发送和消费实践面试必备:堪称最详细的支付系统设计面试必备:动画+原理+代码+优化,解读十大经典排序算法面试必备:设计一个成功的微服务,堪称必备的9个基础知识面试必备:掌握分布式场景下的秒杀架构与秒杀实践面试必备:互联网寒冬?进BAT等大厂 堪称必备神器面试必备:缓存穿透,缓存雪崩的四种解决方案以下是 2018年11月23日 之前的文章面试必备面试必备:深入Spring MVC DispatchServlet 源码分析面试必备:一文读懂Spring AOP面向切面编程面试必备:理解JWT鉴权的应用场景及使用建议面试必备:浅谈偏向锁、轻量级锁、重量级锁面试必备:一致性哈希算法的理解与实践面试必备:深入理解为什么要设计幂等性的服务面试必备:面试官最爱的volatile关键字面试必备:Rabbitmq延迟队列实现定时任务面试必备:HashMap源码解析JDK8面试必备:Dubbo服务消费者调用过程面试必备:Dubbo服务提供者发布过程面试必备:Dubbo中暴露服务的过程解析面试必备:Java中transient关键字的作用面试必备:JavaSQL注入危害这么大,该如何来防止呢面试必备:Java虚拟机运行时数据区面试必备:Redis之数据持久化RDB与AOF面试必备:Redis持久化RDB和AOF优缺点是什么面试必备:Redis集群下的RedLock算法(真分布式锁) 实践面试必备:Redis服务器被攻击后该如何安全加固面试必备:Zookeeper的Leader选举过程面试必备:ZooKeeper和CAP理论及一致性原则面试必备:MySQL从删库到恢复,还用跑路吗?面试必备:MySQL/InnoDB中,7中锁的理解面试必备:MySQL常用30种SQL查询语句优化方法面试必备:MySQL InnoDB B+树索引和哈希索引的区别面试必备:常用的分布式事务解决方案介绍有多少种面试必备:对称加密,非对称加密,公钥和私钥面试必备:如何加密传输和存储用户密码架构架构:应用消息中间件设计可以解决哪些实际问题?架构:搭建大众点评CAT实时应用监控平台架构:如何实现一个TCC分布式事务框架的一点思考架构:Dapper大规模分布式系统的跟踪系统架构:Dubbo 整合 Pinpoint 做分布式服务请求跟踪架构:理解大型分布式架构的演进历史、技术原理、最佳实践架构:解密 Redis 助力双十一背后电商秒杀系统架构:瞬间点击量过万,Redis热点Key的5种解决方案架构:通过案例读懂 RESTful 架构风格架构:一文读懂Apache Flink架构及特性分析架构:大数据推荐系统实时架构和离线架构架构:携程:日处理20亿数据,实时用户行为架构实践架构:什么是微服务架构架构:有赞服务化架构演进架构:微服务架构下静态数据通用缓存机制架构:小型系统如何“微服务”开发架构:Oracle推出开源轻量级 Java 微服务框架 HelidonJava并发Java并发:接口限流算法:漏桶算法&令牌桶算法Java并发:深入浅出AQS之共享锁模式源码分析Java并发:深入浅出AQS之独占锁模式源码分析Java并发:了解无锁CAS就从源码分析Java并发:分布式应用限流实践Java并发:Semaphore信号量源码分析Java并发:CAS源码分析Spring Cloud(十一)分布式配置中心 Spring Cloud Bus 消息总线集成(十)分布式配置中心 Spring Cloud Config 中使用 Refresh(九)分布式配置中心 Spring Cloud Config 集成 Eureka 服务(八)分布式配置中心 Spring Cloud Config(七)服务网关 Zuul Filter 使用(六)服务网关 zuul 快速入门(五)断路器监控(Hystrix Dashboard)(四) 服务提供者 Eureka + 服务消费者 Feign(三) 服务提供者 Eureka + 服务消费者(rest + Ribbon)(二)Consul 服务治理实现(一)服务的注册与发现(Eureka)Spring BootSpring Boot 中使用 RabbitMQSpring Boot 中使用 RocketMQSpring Boot 中使用 SolrCloudSpring Boot 中使用 MongoDB 增删改查Spring Boot 中使用 Dubbo 详解Spring Boot 中使用 LogBack 配置Spring Boot 中使用 Docker镜像push到DockerHub上Spring Boot 写的一个java开源图床搭建搭建:网站配置免费的HTTS证书搭建:Apache RocketMQ 单机环境搭建:MongoDB分片(sharding) / 分区 / 集群环境搭建:MongoDB 的安装与详细使用(二)搭建:MongoDB 的安装与详细使用(一)搭建:SolrCloud 集群服务搭建:Solr 单机服务搭建:RabbitMQ 3.6 单机服务搭建:RabbitMQ 3.6 集群服务搭建:离线部署 CDH 5.12.1 及部署Hadoop集群服务搭建:Mycat 读写分离数据库分库分表中间件及简单使用DockerDocker Compose 1.18.0 之服务编排详解Docker 部署 SpringBoot 项目整合 Redis 镜像做访问计数DemoDocker Registry 企业级私有镜像仓库Harbor管理WEB UIDocker Registry Server TLS 的私有仓库Docker Hub 仓库使用,及搭建 Docker RegistryDocker 容器常用操作命令讲解Docker 安装在之初窥 Dockerfile 部署 Nginx面试题面试题:百度、腾讯、阿里、谷歌 面试题视频详解合集面试题:2018最新JAVA算法/数据结构面试题和答案面试题:70道Spring面试题与答案面试题:2018最新Redis面试题与答案面试题:50道多线程面试题与答案(二)面试题:50道多线程面试题与答案(一)面试题:Spring常见的一些面试题与答案面试题:300道Java面试题与答案面试题:40道常见面试题与答案面试题:20道数据库面试题与答案 ...

April 13, 2019 · 1 min · jiezi

微服务API网关 vs. 传统企业级API网关

翻译 | 李守超原文 | https://www.getambassador.io/…导读企业API网关是一个很成熟的工具,市场上的相关成熟产品也很多。但是,在对轻量级、快速响应要求很高的微服务架构下,传统企业级API网关作为企业的公共基础设施,又显得有些重了。在本文中,我们将讨论业务目标(生产率与管理)的不同是如何要求我们实现一种完全不同的API网关。在过去十年中,企业组织一直致力于通过定义良好的API公开内部的业务系统。如何将数百或数千个API安全地暴露给最终用户(内部和外部),巨大的挑战促使了API网关的出现。在对外发布服务时,传统企业级API网关作为一个系统的后端总入口,承载着所有服务的组合路由转换等工作。除此之外,我们一般也会把安全,限流,缓存,日志,监控,重试,熔断等放到 API 网关来做。随着时间的推移,API网关逐渐成为核心且重要的基础架构之一。随着对云原生和微服务的概念的不断推广和使用,我们开始遇到一些新的问题。区别于传统企业级API网关,业界提出了旨在加速独立服务团队的开发工作流程的微服务API网关。微服务API网关为团队提供了独立发布,监控和更新微服务的所有功能,关注于加速开发测试部署的工作流程。微服务组织在微服务组织中,小型开发团队彼此独立工作,以快速向客户提供功能。为了使每个服务团队独立工作,通过高效的工作流程,服务团队需要能够:发布服务,以便其他人可以使用该服务监控服务,观察它的运行情况测试并更新服务,以便可以继续改进服务团队需要做到所有这些而不需要其他操作或平台团队的帮助,因为只要服务团队需要另一个团队,他们就不是所谓的独立工作,进而导致瓶颈的出现。对于服务发布,微服务API网关为消费者提供静态地址,并动态地将请求路由到适当的服务地址,这里的服务地址一般指由服务团队开发和维护的一个或多个服务的多个实例。此外,为安全性提供身份验证和TLS终止是向其他使用者公开服务的典型考虑因素。了解服务的最终用户体验对于改进服务至关重要。例如,软件更新可能会无意中影响某些请求的延迟。微服务API网关可以很好地收集最终用户流量的关键可观察性的指标,因为它可以将流量路由到终端服务。微服务API网关还支持将用户请求动态路由到不同的服务版本以进行金丝雀测试。通过将一小部分最终用户请求路由到新版本的服务,服务团队可以安全地测试本次更新对一小部分用户产生的影响。微服务API网关与企业API网关乍一看,上述用例可以通过以企业为中心的API网关来实现。虽然可以实现,但企业API网关和微服务API网关的实际重点有些不同:自服务发布团队需要能够向客户发布新服务,而无需运营或API管理团队。这种部署和发布自助服务的能力使团队能够保持较高的发布速度和频率。虽然传统的企业API网关可以提供用于发布新服务的简单机制(例如,REST API),但实际上只限于负责网关运维的团队使用。限制单个团队发布API,主要原因是为了安全考虑:错误的API调用可能会对生产环境造成灾难性影响。微服务API网关允许服务团队轻松和安全地发布新的服务,是因为在微服务场景下,我们默认服务团队对微服务有清楚的了解并承担全部的责任。一旦有问题出现可以快速解决。而且微服务网关可以提供可配置的监控以方便发现问题,并提供调试钩子,例如检查流量或流量转移/复制。监控和速率限制API的常见商业模式是计量,其中根据API使用情况向消费者收取不同的费用。传统的企业API网关在这一点上一般做的比较好:它们提供了监控每个客户端API使用的功能,并且具备当客户端超出配额时限制其使用的能力。微服务网关也需要监控和速率限制,但原因有所不同。监控用户可见的指标(如吞吐量,延迟和可用性)非常重要,它可以确保微服务的更新不会影响到最终用户。稳定可靠的监控指标对于实现快速增量更新至关重要。速率限制则用于提高服务的整体弹性。当服务未按预期响应时,API网关可以限制传入请求以允许服务恢复并防止级联故障,也即微服务设计中经常使用的熔断、降级等模式。测试和更新微服务应用程序具有多个服务,每个服务都是独立更新的。上生产环境之前的自动化测试是必要的,但对于微服务来说还是不够。金丝雀部署将一小部分生产流量路由到新服务版本,是帮助测试更新的重要工具。通过将新服务版本限制为一小部分用户,即便出现问题,服务故障的影响是有限的。当测试稳定以后逐步替换旧版本,最终实现所有服务实例的版本更新。在传统的企业API网关中,路由用于隔离或组合/聚合变化的API版本。上生产环境前的自动化测试,上生产环境后的手动验证和检查,二者都是必须的。总结传统的企业API网关旨在解决API管理的挑战。虽然它们似乎可以解决微服务架构下的一些挑战,但实际情况是微服务工作流提出了一组不同的需求。将微服务API网关集成到微服务的开发工作流程中,使服务团队能够快速,安全地自行发布,监控和更新其服务。这将使我们能够以更快的速度发布软件,并且具有前所未有的可靠性。本文由博云研究院翻译发表,转载请注明出处。

April 12, 2019 · 1 min · jiezi

各大API网关性能比较

用于实现API网关的技术有很多,大致分为这么几类:通用反向代理:Nginx、Haproxy、……网络编程框架:Netty、Servlet、……API网关框架:Spring Cloud Gateway、Zuul、Zuul2、……API网关最基本的功能就是反向代理,所以在对API网关做技术选型的时候需要着重考察其性能表现,本文对Nginx、Haproxy、Netty、Spring Cloud Gateway、Zuul2做了性能测试,测试代码可以在github获得。测试方法准备了三台2CPU 4G内存的服务器,分别运行Tomcat、API Gateway、Gatling(压测工具)先对Tomcat做压测,取Tomcat充分预热后的压测结果作为基准。压的是Tomcat自带的example:/examples/jsp/jsp2/simpletag/book.jsp在对Netty、Zuul2、Spring Cloud Gateway做压测时候也是先压个几轮做预热。被测的API网关都没有添加额外业务,只做反向代理吞吐量下图是吞吐量的情况,可以看到Netty、Nginx、Haproxy均比直压Tomcat低一点点,而Spring Cloud Gateway和Zuul2则要低得多。下面这张图可以更明显的看到吞吐量比较,Tomcat为100%因为它是基准值,Netty、Nginx、Haproxy的只比基准值低8%,而Spring Cloud Gateway和Zuul2则只是基准值的35%和34%(难兄难弟)。平均响应时间下图可以看到Netty、Nginx、Haproxy的平均响应时间与Tomcat差不多。但是Spring Cloud Gateway和Zuul2则是Tomcat的3倍多,不出所料。下图同样是以Tomcat作为基准值的比较:响应时间分布光看平均响应时间是不够的,我们还得看P50、P90、P99、P99.9以及Max响应时间(可惜Gatling只能设置4个百分位,否则我还想看看P99.99的响应时间)。为何要观察P99.9的响应时间?光看P90不够吗?理由有两个:1)观察P99、P99.9、P99.99的响应时间可以观察系统的在高压情况下的稳定性,如果这三个时间的增长比较平滑那么说明该系统在高压力情况下比较稳定,如果这个曲线非常陡峭则说明不稳定。2)观察P99、P99.9、P99.99的响应时间能够帮助你估算用户体验。假设你有一个页面会发出5次请求,那么这5次请求均落在P90以内概率是多少?90%^5=59%,至少会经历一次 > P90响应时间的概率是 100%-59%=41%,如果你的P90=10s,那么就意味着用户有41%的概率会在加载页面的时候超过10s,是不是很惊人?如果你的P99=10s,那么用户只有5%的概率会在访问页面的时候超过10s。如果P99.9=10s,则有0.4%的概率。关于如何正确测量系统可以看 “How NOT to Measure Latency” by Gil Tene下面同样是把结果与Tomcat基准值做对比:可以看到几个很有趣的现象:Haproxy、Nginx的P50、P90、P99、P99.9、Max都是逐渐递增的。Netty的P50、P90、P99、P99.9是很平坦的,Max则为基准值的207%。Spring Cloud Gateway和Zuul2则是相反的,它们的平面呈现下降趋势。Spring Cloud Gateway的Max甚至还比基准值低了一点点(94%),我相信这只是一个随机出现的数字,不要太在意。结论Nginx、Haproxy、Netty三者的表现均很不错,其对于吞吐量和响应时间的性能损耗很低,可以忽略不计。但是目前最为火热的Spring Cloud Gateway和Zuul2则表现得比较糟糕,因我没有写额外的业务逻辑这,可以推测这和它们的内置逻辑有关,那么大致有这么几种可能:内置逻辑比较多内置逻辑算法存在问题,占用了太多CPU时间内置逻辑存在阻塞内置逻辑没有用正确姿势使用Netty(两者皆基于Netty)不管是上面的哪一种都需要再后续分析。不过话说回来考虑选用那种作为API网关(的基础技术)不光要看性能,还要看:是否易于扩展自己的业务逻辑API使用的便利性代码的可维护性文档是否齐全…性能只是我们手里的一个筹码,当我们知道这个东西性能到底几何后,才可以与上面的这些做交换(trade-off)。比如Nginx和Haproxy的可扩展性很差,那么我们可以使用Netty。如果你觉得Netty的API太底层了太难用了,那么可以考虑Spring Cloud Gateway或Zuul2。前提是你知道你会失去多少性能。

April 12, 2019 · 1 min · jiezi

使用URLOS低门槛快速开发和分发docker应用,未来微服务发展大趋势

2019年微服务概念继续火爆,随着Docker容器技术的快速普及,特别在一线互联网公司。使用Docker技术可以帮助企业快速水平扩展服务,从而到达弹性部署业务的能力。在云服务概念兴起之后,Docker的使用场景和范围进一步发展,如今在微服务架构越来越流行的情况下,微服务+Docker的完美组合,更加方便微服务架构运维部署落地。如何快速入门docker,开发属于自己的容器应用?咱今天不整虚的,来点实打实的干货:利用URLOS快速开发docker应用,并可随意将应用导出给他人使用。对URLOS不了解的朋友,这里大概介绍一下,URLOS是一个容器云管理面板,基于Docker容器技术打包和运行应用,可自动识别机器和云应用的故障并将云应用转移至可用的机器上,单机故障并不影响业务开展,配合云存储便可轻松搭建7x24小时持续运行的应用环境。URLOS官网:https://www.urlos.com/URLOS安装方法:https://www.urlos.com/center-…URLOS开发交流QQ群:695164700,147882180URLOS创始人微信:安装URLOS:curl -SO https://www.urlos.com/install && chmod 544 install && ./install安装完成后,地址栏输入 http://ip:9968 即可访问。划重点:利用URLOS开发docker应用的最基本的流程:这里我们以制作一个LNP(linux+nginx+php)网站环境为例,快速制作一个可以导出给他人使用的docker应用。在开始制作之前,我们先到docker官网注册一个账号,这样我们才能将制作好的镜像上传到docker仓库,打开https://hub.docker.com/ 有了hub账号,那么我们开始制作吧!第一步:拉取镜像,启动容器,进入容器使用SSH工具连接主机,输入以下命令拉取一个php:7.3.3-fpm-stretch镜像,启动容器并进入这个容器内部:docker run -it php:7.3.3-fpm-stretch bash看到类似上图中类似的字符串时,表示已经成功进入容器内部,这个便是当前容器的ID第二步:更新镜像,安装我们要的nginx以及PHP相关扩展先更新一下镜像源,国内用阿里的会快一些set -ex \ && sed -i ’s@security.debian.org@mirrors.aliyun.com@’ /etc/apt/sources.listset -ex \ && sed -i ’s@deb.debian.org@mirrors.aliyun.com@’ /etc/apt/sources.listapt-get update更新完成之后,再来安装nginx,默认安装目录为/etc/nginxapt-get install -y nginx官方镜像默认是没有ps -ef 命令,因此需要手动安装apt-get install -y procps安装PHP扩展。安装php自带的一些扩展时,可以使用docker-php-ext-configure和docker-php-ext-install。例如我们要安装pdo_mysql:docker-php-ext-configure pdo_mysqldocker-php-ext-install pdo_mysql然后使用 php -m查看我们的扩展是否安装成功。使用这种方式安装,系统会自动生成一个配置文件,提供给php加载,使用命令查看:ls -l /usr/local/etc/php/conf.d/gd扩展安装apt-get install -y libfreetype6-dev \libjpeg62-turbo-dev \libpng-devdocker-php-ext-configure gd –with-freetype-dir=/usr/include/ –with-jpegdir=/usr/include/docker-php-ext-install gd如果需要安装memcached、redis扩展,则需要下载扩展到容器,然后手动编译安装。地址:https://pecl.php.net/package/…https://pecl.php.net/package/...memcached扩展安装:curl -O https://pecl.php.net/get/memcached-3.1.3.tgztar xf memcached-3.1.3.tgz && cd memcached-3.1.3phpize./configure编译过程中若出现以下错误提示:则执行安装命令,然后重新编译安装memcached扩展:apt-get install -y libmemcached-dev./configuremake && make install添加extension=memcached.so语句到php.ini文件。安装完成后通过命令查看扩展存放的位置ls /usr/local/lib/php/extensions/no-debug-non-zts-20170718/php安装目录:/usr/local/phpphp.ini的配置文件目录:/usr/local/etc/php/。在这个目录下有两个文件:php.ini-development和php.iniproduction。因此,我们需要将php.ini-production文件重命名为php.ini。以后手动编译安装php扩展后需要添加extension=xx.so到php.ini。启动Nginx和php,检查是否正常运行。nginx && php-fpm -D第三步:打包镜像在打包成镜像之前,我们先将nginx、php-fpm关闭,删除一些不需要的应用以及清理一些安装的缓存文件,从而减小最终打包成镜像的大小。apt-get purge vim makeapt-get autoremoveapt-get autocleanrm -f /usr/local/etc/php/conf.d/* #统一将php扩展写入到php.ini文件然后,输入exit退出容器,通过以下命令将更新过的容器重新打包成一个新镜像:docker commit -m=“php-nginx-website” -a=“yourname” 96b3f038590b yourhubid/php-nginx:v0.1.0参数说明:-m:提交的描述信息-a:指定镜像作者96b3f038590b:容器IDyourhubid/php-nginx:v0.1.0:指定要创建的目标镜像名可以使用docker images命令来查看我们的新镜像第四步:上传镜像使用docker login命令登录 hub.docker.com,按提示输入账号和密码即可使用docker push命令将打包好的新镜像上传至镜像仓库:docker push yourhubid/php-nginx:v0.1.0第五步:登录URLOS制作应用注意:需要修改将/data/urlos/master-config/config.jsonc文件的envType的值设置为dev(开发环境):vim /data/urlos/master-config/config.jsonc添加镜像浏览器地址栏输入http://主机ip:9968,登录URLOS,在左侧菜单栏选择镜像管理,然后点击右上角的添加按钮。输入镜像名称镜像地址开发者信息添加LNP应用在左侧菜单中选择应用管理。然后点击右上角的添加应用按钮:应用的基本信息如上图所示。镜像:选择上一步骤添加的镜像。URLOS最低版本号:如设置此选项则表示安装URLO的版本高于或者等于当前设置的值,才允许用户安装使用。容器端口:容器启动时对外通信的端口,即参数-p。网站的80、443等端口默认是对外开发的,在这里可以不用设置。如必须特定端口时,设置的格式{“22”:true}。标签:应用标签多用于搜索场景。选项开关注解:固定节点运行:若勾选,则表示用户在安装此应用时(创建此应用的服务)需要选择安装在某个节点(云主机)。若取消勾选,则表示此应用安装在选择的集群(单容器运行也取消勾选),可达到负载均衡,故障转移的效果。单容器运行:若勾选,则表示安装此应用时,每个服务只运行一个容器。与固定节点运行配合使用,即固定节点运行时,则单容器运行。允许特权允许:若勾选,则容器内的root拥有了真正的root权限(宿主机器的root),在容器内部就可以做任何事情(包括修改宿主机器的文件,启动宿主机器其他容器,执行mount等操作),不建议勾选。以root用户允许容器:这里的root用户是容器外部的一个普通用户,默认勾选。若容器内部的程序禁止root用户允许,则取消(如:MySQL)。挂载存储目录:如需要从宿主机器挂载文件到容器,则勾选。即参数 -v。挂载时区定义文件:容器的时间与宿主机器的时间保持一致。容器只读:禁止向容器写数据。全局网络:允许同一集群不同容器网络的容器通信。允许快照备份:勾选则允许执行快照备份(仅挂载了本地存储时有效)开启反向代理,则可以实现多容器共享端口,反之则不能。注解:插件:由PHP语言编辑的脚本文件组成。插件的使用会让用户在安装应用(创建服务)时更便捷,更智能。这里选择phpWebSite:v0_1_0 — Liu Xin —php网站环境这个插件即可。(制作插件后续会有详细说明)服务别名:创建服务时,在左上角显示的描述。应用数据别名:创建服务完成后,服务产生的数据或者用户基于创建的服务需要添加新的数据,对这些数据管理取的名字,即为应用数据别名。(如:创建MySQL数据库服务,用户可以手动添加数据库,创建网站服务时也可以新增数据库。)服务表单步骤:创建服务时,用户填写表单的步骤。(数字表示必填,其他符号表示选填)额外挂载:将宿主机器的除存储目录外的其他目录挂载到容器。额外启动参数:通过docker run运行容器时的额外参数,如:–add-host a.com:192.168.0.1注解:安装脚本:安装应用时需要执行的脚本命令。test -d /etc/nginx/conf.d/ || mkdir -p /etc/nginx/conf.d/启动脚本:需要启动程序的命令。nginxphp-fpm -D状态脚本:每隔2秒执行此脚本,用来检查程序是否正常允许。当前的脚本命令用来检查apache是否启动。status1=0 && (ps -ef|grep “php-fpm”|grep “master process”|grep -v “grep”) &&status1=1;status2=0 && (ps -ef|grep “nginx”|grep “master process”|grep -v “grep”) &&status2=1;if [ ${status1} != 0 ] && [ ${status2} != 0 ]; thenstatusScriptResult=1fi监控脚本:每隔1秒执行此脚本,检查状态脚本返回的结果判断程序是否正常允许。若异常,则执行退脚本。{w:statusScript:w}[ “$statusScriptResult” != 1 ] && exit 1退出脚本:容器关闭时之前,执行的脚本。如同,我们关闭电脑时,系统会关闭正在允许的程序。添加LNP模板在这个应用,我们需要添加模板php.ini、vhost.conf,然后在这两个模板的参数设置一些变量,这样用户在安装应用时,就可以根据自己的需要动态的调整。(如:设置php的上传大小,最大内存等)那么如何添加模板呢?我们在应用管理列表中找到上述创建的应用,然后点击右侧的更多选择管理模板。添加一个php.ini模板,然后在模板内容将php.ini文件内容复制进入,同时设置变量{w:upload_max_filesize:w}、{w:PHP_memory_limit:w}。[PHP]engine = Onshort_open_tag = Offprecision = 14output_buffering = 4096zlib.output_compression = Offimplicit_flush = Offunserialize_callback_func =serialize_precision = -1disable_functions =disable_classes =zend.enable_gc = Onexpose_php = Onmax_execution_time = 30max_input_time = 60memory_limit = 128Merror_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICTdisplay_errors = Offdisplay_startup_errors = Offlog_errors = Onlog_errors_max_len = 1024ignore_repeated_errors = Offignore_repeated_source = Offreport_memleaks = Onhtml_errors = Onvariables_order = “GPCS"request_order = “GP"register_argc_argv = Offauto_globals_jit = Onpost_max_size = {w:PHP_memory_limit:w}auto_prepend_file =auto_append_file =default_mimetype = “text/html"default_charset = “UTF-8"doc_root =user_dir =enable_dl = Offfile_uploads = Onupload_max_filesize = {w:upload_max_filesize:w}max_file_uploads = 20allow_url_fopen = Onallow_url_include = Offdefault_socket_timeout = 60extension=gd.soextension=memcached.soextension=sockets.soextension=mysqli.soextension=pdo_mysql.so[CLI Server]cli_server.color = On[Date][filter][iconv][imap][intl][sqlite3][Pcre][Pdo][Pdo_mysql]pdo_mysql.default_socket=[Phar][mail function]SMTP = localhostsmtp_port = 25mail.add_x_header = Off[ODBC]odbc.allow_persistent = Onodbc.check_persistent = Onodbc.max_persistent = -1odbc.max_links = -1odbc.defaultlrl = 4096odbc.defaultbinmode = 1[Interbase]ibase.allow_persistent = 1ibase.max_persistent = -1ibase.max_links = -1ibase.timestampformat = “%Y-%m-%d %H:%M:%S"ibase.dateformat = “%Y-%m-%d"ibase.timeformat = “%H:%M:%S”[MySQLi]mysqli.max_persistent = -1mysqli.allow_persistent = Onmysqli.max_links = -1mysqli.default_port = 3306mysqli.default_socket =mysqli.default_host =mysqli.default_user =mysqli.default_pw =mysqli.reconnect = Off[mysqlnd]mysqlnd.collect_statistics = Onmysqlnd.collect_memory_statistics = Off[OCI8][PostgreSQL]pgsql.allow_persistent = Onpgsql.auto_reset_persistent = Offpgsql.max_persistent = -1pgsql.max_links = -1pgsql.ignore_notice = 0pgsql.log_notice = 0[bcmath]bcmath.scale = 0[browscap][Session]session.save_handler = filessession.use_strict_mode = 0session.use_cookies = 1session.use_only_cookies = 1session.name = PHPSESSIDsession.auto_start = 0session.cookie_lifetime = 0session.cookie_path = /session.cookie_domain =session.cookie_httponly =session.cookie_samesite =session.serialize_handler = phpsession.gc_probability = 1session.gc_divisor = 1000session.gc_maxlifetime = 1440session.referer_check =session.cache_limiter = nocachesession.cache_expire = 180session.use_trans_sid = 0session.sid_length = 26session.trans_sid_tags = “a=href,area=href,frame=src,form=“session.sid_bits_per_character = 5[Assertion]zend.assertions = -1[COM][mbstring][gd][exif][Tidy]tidy.clean_output = Off[soap]soap.wsdl_cache_enabled=1soap.wsdl_cache_dir="/tmp"soap.wsdl_cache_ttl=86400soap.wsdl_cache_limit = 5[sysvshm][ldap]ldap.max_links = -1[dba][opcache][curl][openssl]添加Nginx的虚拟站点配置vhost.conf模板server {server_name {w:domains:w};{w:listenLines:w}set $websiteRoot “/data/www/{w:indexDirName:w}";root $websiteRoot;index index.html index.htm index.php;client_max_body_size {w:upload_max_filesize:w};client_body_buffer_size 128;location / {{w:rewriteContents:w}}location ~ .(php|phtml)$ {include fastcgi.conf;fastcgi_pass 127.0.0.1:9000;}location ~ /.ht {deny all;}}添加变量变量分为:环境变量、数据变量和扩展变量。在这里只需要添加扩展变量。环境变量:在操作系统中用来指定操作系统运行环境的一些参数,与平常使用的环境变量相同。有时容器启动需要设置一些参数提供给容器内部的程序。如:MySQL容器启动时可以设置MYSQL_ROOT和MYSQL_ROOT_PASSWORD。数据变量:添加存储数据时设置的一些参数。如:往MySQL数据服务添加数据库时,需要填写dbName,dbPassword,status,charset。具体可以使用可以创建MySQL服务,然后在管理数据库中添加数据库。扩展变量:即普通变量。如:上述在模板中设置的变量{w:upload_max_filesize:w}、{w:PHP_memory_limit:w}。变量的格式:{w:变量名:w}。添加PHP最大内存变量:PHP_memory_limit添加上传大小限制:upload_max_filesize至此,LMP应用已经制作完成,我们在应用管理列表中,选择刚才制作好的应用,点击创建服务部署完成后,地址栏输入域名访问一下,如果访问正常,说明我们制作的应用没有问题了,可以导出供他人安装!第六步:导出应用将我们制作的应用导出,可将导出的文件发布到任何地方,供他人安装使用,只要对方的主机安装了URLOS,都可以完美运行(无需考虑兼容性问题)我们制作的应用。导出的文件为txt文本。只要其他用户使用URLOS直接导即可。下面是导入方法:打开文本,全选复制其中内容登录URLOS,在左侧菜单点击导入应用,将内容粘贴进去,提交导入成功!!点击安装即可。哈哈,太爽了吧。任何服务器环境可以使用的应用都可以用这个方法来制作,比如微信小程序后端部分等等都可以这样制作,方便分发和安装。 ...

April 1, 2019 · 2 min · jiezi

六年打磨!阿里开源混沌工程工具 ChaosBlade

阿里妹导读:减少故障的最好方法就是让故障经常性的发生。通过不断重复失败过程,持续提升系统的容错和弹性能力。今天,阿里巴巴把六年来在故障演练领域的创意和实践汇浓缩而成的工具进行开源,它就是 “ChaosBlade”。如果你想要提升开发效率,不妨来了解一下。高可用架构是保障服务稳定性的核心。阿里巴巴在海量互联网服务以及历年双11场景的实践过程中,沉淀出了包括全链路压测、线上流量管控、故障演练等高可用核心技术,并通过开源和云上服务的形式对外输出,以帮助企业用户和开发者享受阿里巴巴的技术红利,提高开发效率,缩短业务的构建流程。例如,借助阿里云性能测试 PTS,高效率构建全链路压测体系,通过开源组件 Sentinel 实现限流和降级功能。这一次,经历了 6 年时间的改进和实践,累计在线上执行演练场景达数万次,我们将阿里巴巴在故障演练领域的创意和实践,浓缩成一个混沌工程工具,并将其开源,命名为 ChaosBlade。ChaosBlade 是什么?ChaosBlade 是一款遵循混沌工程实验原理,提供丰富故障场景实现,帮助分布式系统提升容错性和可恢复性的混沌工程工具,可实现底层故障的注入,特点是操作简洁、无侵入、扩展性强。ChaosBlade 基于 Apache License v2.0 开源协议,目前有 chaosblade 和 chaosblade-exe-jvm 两个仓库。chaosblade 包含 CLI 和使用 Golang 实现的基础资源、容器相关的混沌实验实施执行模块。chaosblade-exe-jvm 是对运行在 JVM 上的应用实施混沌实验的执行器。ChaosBlade 社区后续还会添加 C++、Node.js 等其他语言的混沌实验执行器。为什么要开源?很多公司已经开始关注并探索混沌工程,渐渐成为测试系统高可用,构建对系统信息不可缺少的工具。但混沌工程领域目前还处于一个快速演进的阶段,最佳实践和工具框架没有统一标准。实施混沌工程可能会带来一些潜在的业务风险,经验和工具的缺失也将进一步阻止 DevOps 人员实施混沌工程。混沌工程领域目前也有很多优秀的开源工具,分别覆盖某个领域,但这些工具的使用方式千差万别,其中有些工具上手难度大,学习成本高,混沌实验能力单一,使很多人对混沌工程领域望而却步。阿里巴巴集团在混沌工程领域已经实践多年,将混沌实验工具 ChaosBlade 开源目的,我们希望:让更多人了解并加入到混沌工程领域;缩短构建混沌工程的路径;同时依靠社区的力量,完善更多的混沌实验场景,共同推进混沌工程领域的发展。ChaosBlade 能解决哪些问题?衡量微服务的容错能力通过模拟调用延迟、服务不可用、机器资源满载等,查看发生故障的节点或实例是否被自动隔离、下线,流量调度是否正确,预案是否有效,同时观察系统整体的 QPS 或 RT 是否受影响。在此基础上可以缓慢增加故障节点范围,验证上游服务限流降级、熔断等是否有效。最终故障节点增加到请求服务超时,估算系统容错红线,衡量系统容错能力。验证容器编排配置是否合理通过模拟杀服务 Pod、杀节点、增大 Pod 资源负载,观察系统服务可用性,验证副本配置、资源限制配置以及 Pod 下部署的容器是否合理。测试 PaaS 层是否健壮通过模拟上层资源负载,验证调度系统的有效性;模拟依赖的分布式存储不可用,验证系统的容错能力;模拟调度节点不可用,测试调度任务是否自动迁移到可用节点;模拟主备节点故障,测试主备切换是否正常。验证监控告警的时效性通过对系统注入故障,验证监控指标是否准确,监控维度是否完善,告警阈值是否合理,告警是否快速,告警接收人是否正确,通知渠道是否可用等,提升监控告警的准确和时效性。定位与解决问题的应急能力通过故障突袭,随机对系统注入故障,考察相关人员对问题的应急能力,以及问题上报、处理流程是否合理,达到以战养战,锻炼人定位与解决问题的能力。功能和特点场景丰富度高ChaosBlade 支持的混沌实验场景不仅覆盖基础资源,如 CPU 满载、磁盘 IO 高、网络延迟等,还包括运行在 JVM 上的应用实验场景,如 Dubbo 调用超时和调用异常、指定方法延迟或抛异常以及返回特定值等,同时涉及容器相关的实验,如杀容器、杀 Pod。后续会持续的增加实验场景。使用简洁,易于理解ChaosBlade 通过 CLI 方式执行,具有友好的命令提示功能,可以简单快速的上手使用。命令的书写遵循阿里巴巴集团内多年故障测试和演练实践抽象出的故障注入模型,层次清晰,易于阅读和理解,降低了混沌工程实施的门槛。场景扩展方便所有的 ChaosBlade 实验执行器同样遵循上述提到的故障注入模型,使实验场景模型统一,便于开发和维护。模型本身通俗易懂,学习成本低,可以依据模型方便快捷的扩展更多的混沌实验场景。ChaosBlade 的演进史EOS(2012-2015):故障演练平台的早期版本,故障注入能力通过字节码增强方式实现,模拟常见的 RPC 故障,解决微服务的强弱依赖治理问题。MonkeyKing(2016-2018):故障演练平台的升级版本,丰富了故障场景(如:资源、容器层场景),开始在生产环境进行一些规模化的演练。AHAS(2018.9-至今):阿里云应用高可用服务,内置演练平台的全部功能,支持可编排演练、演练插件扩展等能力,并整合了架构感知和限流降级的功能。ChaosBlade(2019.3):是 MonkeyKing 平台底层故障注入的实现工具,通过对演练平台底层的故障注入能力进行抽象,定义了一套故障模型。配合用户友好的 CLI 工具进行开源,帮助云原生用户进行混沌工程测试。近期规划功能迭代:增强 JVM 演练场景,支持更多的 Java 主流框架,如 Redis,GRPC增强 Kubernetes 演练场景增加对 C++、Node.js 等应用的支持社区共建:欢迎访问 ChaosBlade@GitHub,参与社区共建,包括但不限于:架构设计模块设计代码实现Bug FixDemo样例文档、网站和翻译本文作者:中亭阅读原文本文来自云栖社区合作伙伴“ 阿里技术”,如需转载请联系原作者。 ...

March 29, 2019 · 1 min · jiezi

信用算力基于 RocketMQ 实现金融级数据服务的实践

导读:微服务架构已成为了互联网的热门话题之一,而这也是互联网技术发展的必然阶段。然而,微服务概念的提出者 Martin Fowler 却强调:分布式调用的第一原则就是不要分布式。纵观微服务实施过程中的弊端,可以推断出作者的意图,就是希望系统架构者能够谨慎地对待分布式调用,这是分布式系统自身存在的缺陷所致。但无论是 RPC 框架,还是 REST 框架,都因为驻留在不同进程空间的分布式组件,而引入了额外的复杂度。因而可能对系统的效率、可靠性、可预测性等诸多方面带来负面影响。信用算力自2016年开始实施微服务改造,通过消息队列(Message Queue),后文简称MQ,来规避微服务存在的缺陷,实现金融级数据服务。以下是一些使用场景和心得。为什么需要 MQ一、案例介绍先来看一个当前的真实业务场景。对于通过信息流获客的企业而言,当用户注册时,因业务需求会调用用户服务,然后执行一系列操作,注册 -> 初始化账户信息 -> 邀友奖励发放 -> 发放优惠券 -> … -> 信息流数据上报。用户服务的开发人员压力非常大,因为需要调用非常多的服务,业务耦合严重。如果当时账户服务正在执行发版操作,那么初始化账户动作会失败。然而平台经过不断的迭代更新,后续又新增了一个签到业务,新注册用户默认签到一次。这就需要修改用户服务,增加调用签到服务的接口。每当遇到此种情况,开发用户服务的同学就非常不爽了,为什么总是我?新增签到业务和用户服务又有什么关系?为解决此类重度依赖的问题,我们在架构层面引入了 MQ,用来规避微服务之间重度耦合调用的弊端。新架构如下图:用户完成注册动作后,只需要往 MQ 发送一个用户注册的通知消息,下游业务如需要依赖注册相关的数据,订阅注册消息的 topic 即可,从而实现了业务的解耦。看完上述真实的案例后,大家可能产生疑惑,到底什么是 MQ,使用 MQ 又有什么好处?适合使用 MQ 的场景和不适合使用 MQ 的场景有哪些不同?二、什么是 MQ?简单来说,MQ(MessageQueue)是一种跨进程的通信机制,用于上下游传递消息。适合使用 MQ 的场景有:1、上游不关心下游执行结果,例如上述案例中用户注册后,我们并不关心账户是否初始化,是否上报了信息流等;2、异步返回执行时间长:例如上述案例中,当邀友奖励发放,需要经历很多风控规则,执行时间比较长,但是用户并不关注奖励何时发放。不适合使用MQ场景调用方实时关注执行结果,例如用户发起注册动作后,需要立刻知道,注册结果是成功还是失败,这种需要实时知道最终执行结果的场景,就不适合使用MQ。三、使用MQ的好处:1、解耦2、可靠投递3、广播4、最终一致性5、流量削峰6、消息投递保证7、异步通信(支持同步)8、提高系统吞吐、健壮性MQ 的技术选型目前业内比较主流的 MQ 包括 RocketMQ、ActiveMQ、RabbitMQ、Kafka等,关于性能、存储、社区活跃度等各方面的技术对比已经很多,本文不再重复。但我们发现通过简单的选型对比,很难抉择到底选择哪款MQ产品。因为金融行业对于数据一致性以及服务可用性的要求非常高,所以任何关于技术的选项都显得尤为重要。经调研,如微众银行、民生银行、平安银行等国内知名的互联网银行和直销银行代表,都在使用 RocketMQ,且 RocketMQ 出生在阿里系,经受过各种生产压力的考验,非常稳定。并且,目前此项技术已经捐增给 Apache 社区,社区活跃度非常高。另外 RocketMQ 开发语言是Java,开发同学遇到解决不了的问题点,或者不清楚的概念,可以直接 Debug 源码。经过多方面的比较,我们选择 RocketMQ 作为规避微服务弊端的利器。MQ 在微服务下的使用场景MQ 是一种跨进程的通信机制,用于上下游传递消息,目前信用算力将 RocketMQ 应用于解耦、流量削峰、分布式事务的处理等几个场景。一、解耦通常解耦的做法是生产者发送消息到 MQ,下游订阅 MQ 的特定 topic,当下游接收到消息后开始处理业务逻辑。那么,消息发送方到底应该是由谁来承担?是服务提供者在处理完RPC请求后,根据业务需求开始发送消息吗?但此刻开发人员就会抱怨为什么总是我?为什么处理完业务后需要发送 MQ?为此,在解耦的过程中通过订阅数据库的 BinLog 日志,开发了一套 BinLog 日志解析模块,专门解析日志,然后生成 JSON 字符串后发送消息到 MQ,下游订阅 MQ 即可。流程如下:目前所有需要依赖下游服务的业务线,其数据变动都采用此方案。方案优缺点:优点:1、服务之间依赖完全解耦,任何基于注册行为的业务变更,都无需依赖上游,只需订阅MQ即可;2、系统的稳定性和吞吐量增加了,用户注册的响应时间缩短了;缺点:1、引入MQ后系统复杂性增加,维护成本增加;2、从注册开始到全部数据初始化结束的整体时间增加了;二、流量削峰每逢遇到会员日的时候,平台会发送大量的会员福利活动通知,以短信、站内信、PUSH 消息的方式通知注册用户。所有的消息会在很短的时间全部推送到消息中心,同时正常的业务通知任然有大量业务消息推送到消息中心。为保障平台的稳定性和可靠性,在消息中心前置了多种 topic,如短信、推送、站内提醒。消息中心接收到消息后会全部写入不同 topic 的 MQ,多个消费者来消费并把信息推送给终端用户。三、分布式事务用户在平台上支付他订购某种业务的时候,需要涉及到支付服务、账户服务、优惠券服务、积分服务,在单体模式下这种业务非常容易实现,通过事务即可完成,伪代码如下:然而,在微服务的情况下,原本通过简单事务处理的却变得非常复杂,若引入两阶段提交(2PC)或者补偿事务(TCC)方案,则系统的复杂程度会增加。信用算力的做法是通过本地事务 + MQ 消息的方式来解决, 虽然 RocketMQ 也支持事务消息,但是其他主流 MQ 并没有此项功能,所以综合考虑采用如下方案:消息上游:需要额外建一个tc_message表,并记录消息发送状态。消息表和业务数据在同一个数据库里面,而且要在一个事务里提交。然后消息会经过MQ发送到消息的消费方。如果消息发送失败,会进行重试发送;消息上游:开启定时任务扫描tc_message表,如果超过设置的时间内状态没有变更,会再次发送消息到MQ,如重试次数达到上限则发起告警操作;消息下游:需要处理这个消息,并完成自己的业务逻辑。此时如果本地事务处理成功,表明已经处理完成了,需要发起业务回调通知业务方;方案优缺点:优点:1、用最小的代价实现分布式事物,以达到数据最终一致性;2、方案非常灵活,任何环节都可以人为控制;缺点:1、复杂性增加了,业务操作的时候需要写入 tc_message 表以及发送 MQ,同时还需要考虑状态超时未变更的补发机制以及告警处理机制;2、用户看到的数据,存在有短暂不一致的情况;心得体会使用 RocketMQ 3年多了,总体来说运行的非常稳定,基本上没有发生过生产事故,下面说说这几年使用下来的心得体会:1、一个应用尽可能用一个 Topic,消息子类型用 tags 来标识。Topic 名称和 Tags 名称可以自行设置。Producer,Consumer都需要规范,要做到见名知意。发送消息时候必须携带 Tags,消费方在订阅消息时,才可以利用 Tags 在 Broker 做消息过滤。2、每条消息在业务层面有唯一标识码,方便在系统出现异常的情况,可以通过业务维度查询。举个栗子,当用户在平台注册成功后,会以 Topic 和 UserID 作为唯一标识码(topic_user_10011),服务器会为每个消息创建索引,该消息会持久化入库,以防将来定位消息丢失等问题。下游收到消息后会以 Topic+Key 方式来记录消费行为,包括消息日期、当前机器IP地址、处理结果等;也可以通过 Topic+Key 的方式来查询这条消息内容,包括消息被谁消费,以及这条 MQ 在每个环节的处理状态。3、消息发送成功或者失败,都需要记录 log 日志,且必须打印 sendresult、MsgID、唯一标识码。4、由于上游会做消息重试机制,所以下游消息必须要做幂等处理。5、需要封装 MQ 的 API 在封装后,API 需屏蔽底层 MQ 的特性,开发人员无需关注到底是用的哪个 MQ 来支持本地分布式事物、MQ 消息自动入库、自动打印日志,减少开发人员操作成本。总的来说,MQ 是一个互联网架构中常见的解耦利器,在这3年中,信用算力在微服务中一直使用 MQ 来为金融客户提供高质量的数据服务。虽然 MQ 不是唯一方案,但是从目前阶段来看,的确是一种非常不错的解决方案。本文作者:潘志伟(信用算力技术总监,QCon 演讲嘉宾,十多年 Java 从业经验,精通微服务架构,精通大数据。拥有亿级用户平台架构经验,万级并发的API网关经验。)本文作者:中间件小哥阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

March 29, 2019 · 1 min · jiezi

阿里巴巴的微服务开源之路

侠之大者,为国为民。在金庸小说中,郭靖和黄蓉是“侠之大者,为国为民”的典范,他们以布衣之身帮助宋军守护襄阳十余年。技术的世界里,并无大小之分。但当一群程序员由服务公司内部转变为社会的程序员,将技术以开源的方式与社区的开发者一同协作、改进和使用时,他们便被赋予了更大的责任和期待。阿里云智能中间件的程序员们正和社区的开发者们一起,用键盘敲下国内微服务开源项目的过去和未来。国内首个非 Hadoop 生态体系的 Apache 社区顶级项目2016年的那届双11,RocketMQ 团队首次将低延迟存储解决方案应用于双11的支撑,经受住了流量的大考,整个大促期间,99.996%的延迟落在了10ms以内,完成了保障交易稳定的既定目标。对于读写比例几乎均衡的分布式消息引擎来说,这一技术上的突破,即便是放在全球范围内,也绝对是值得称赞的。另一边,在历时3个月的开源重塑后,RocketMQ 团队启动了向 Apache 软件基金会的捐赠之路,但迈出这一步并不容易。“当时国内的开源氛围还没有现在那么活跃,开源之后,很多设计文档、代码质量,以及社区建设还不够理想。我们一直期待,国内的开源项目和开源社区也可以在世界的开源舞台上发挥让人瞩目的价值,希望更多“中国智造”的开源项目成为世界级的开源项目。”阿里云智能高级技术专家冯嘉回忆道。经过近一年的努力,在2017年9月25日,Apache软件基金会官方宣布,阿里巴巴捐赠给 Apache 社区的开源项目 RocketMQ 从 Apache社区正式毕业,成为 Apache 顶级项目(TLP),这是国内首个非 Hadoop 生态体系的 Apache 社区顶级项目。值得一提的是,根据项目毕业前的统计,RocketMQ 有百分八十的新特性与生态集成来自于社区的贡献。毕业一年多后,RocketMQ 已经覆盖互联网金融等领域60%以上的消息场景,并被应用到金融、电力、物流、游戏、电子商务、共享出行等十几个行业。然而,随着云计算、大数据、人工智能等技术在全球范围的深入推进,催生出了如IoT、区块链、AI、边缘计算等新的应用场景,架构上如何进一步演进以更好的适应新的场景,服务好下一个十年,这是即将到来的 RocketMQ 5.0 要解决的问题。消息领域的里程碑事件RocketMQ 从 Apache 社区正式毕业的同时,消息领域出现了另一件里程碑事件,分布式消息领域的国际标准 OpenMessaging 开源项目正式入驻Linux基金会,这是国内首个在全球范围发起的分布式计算领域的国际标准。消息通信已经成为现代数据驱动架构的关键环节,但在全球范围内,消息领域仍然存在两大问题:一是,缺乏供应商中立的行业标准,导致各种消息中间件的高复杂性和不兼容性,相应地造成了公司的产品低效、混乱和供应商锁定等问题。二是,目前已有的方案框架并不能很好地适配云架构,即非云原生架构,因此无法有效地对大数据、流计算和物联网等新兴业务需求提供技术支持。这也是 RocketMQ 开源过程中,开发者和合作伙伴经常会提到的问题:“在消息领域,市场上出现了各类不同的开源解决方案,这导致了用户更高的接入和维护成本,为了确保各个消息引擎间能正常通信,还要投入大量的精力去做兼容。”这时候,建立一套供应商中立,和语言无关的消息领域的事实标准,成为各社区成员共同的诉求。此后,阿里巴巴发起 OpenMessaging 项目,并邀请了雅虎、滴滴出行、Streamlio 共同参与,一年后,参与OpenMessaging 开源标准社区的企业达20家之多,包括阿里巴巴、Datapipeline、滴滴出行、浩鲸科技、京东商城、科大讯飞、青云QingCloud、Streamlio、VIPKID、微众银行、Yahoo、中国移动苏州研发中心等(按首字母排序),此外,还获得了 Apache RocketMQ、Apache Pulsar 等顶级消息开源厂商的支持。相比于开源一个分布式消息项目,一套开源标准能被各家厂商所接受,对整个国内开源领域而言,是更具有里程碑意义的事件。从微服务框架到微服务生态Dubbo 是阿里巴巴于2012年开源的分布式服务治理框架,是国内影响力最大、使用最广泛的开源服务框架之一。在2016年、2017、2018年开源中国发起的最受欢迎的中国开源软件评选中,连续三年进入 Top10 名单。2019年2月 Dubbo 发布了2.7.0,这一版本将用于 Apache 基金会的正式毕业。(已进入 Near Graduation 阶段)从 Apache 孵化器毕业,除了有个名誉,对项目之后的维护、发展有什么影响?“从孵化器毕业是一种荣誉,但这并不是结束,而是另一种开始。这有点像求学,毕业并不意味着学习上的中断,而是发挥更大社会价值的开始。毕业也更像是一个成人礼,意味着Dubbo 团队已经符合Apache对一个成熟开源项目的要求,并开始具备独立发展的能力。”阿里云智能高级技术专家北纬在接受媒体采访时回答道。截至目前,Dubbo 已收获 2.5w+ star,在 GitHub 所有 Java 项目中排名前十,并有越来也多的企业用户选择 Dubbo 作为自己的微服务治理框架。但是,随着微服务化的逐渐深入,Dubbo 提供的能力逐渐无法满足微服务各个方面的需求。阿里云智能技术专家望陶在一次直播中分享道:“Dubbo 是一个微服务框架,帮助开发者快速构建高性能的微服务应用。但在 API Gateway,熔断限流,分布式监控,分布式事务等方面,缺乏一套比较完整的围绕 Dubbo 的解决方案,基本上是各个公司自研,或者需要调研外面开源的各种框架进行调研选型,花费了比较大的时间和精力在这上面,却无法形成一套体系化的方案。”因此,我们做了进一步的演进,即从微服务框架演进到微服务生态。通过和成熟的开源方案做集成,形成一个完整的微服务生态,组成 Dubbo Ecosystem,开发者无需为现有的系统做出过多的修改,就能快速开发微服务应用。Dubbo Ecosystem 的概念得以提出,离不开 2018 年夏天开源的两大微服务组件。技术人的仲夏之夜2018年夏天,国内开源领域,迎来了两位新成员。作为微服务和云原生生态下的两款重要开源组件,Nacos 主打云原生应用中的动态服务发现、配置和服务管理,Sentinel 则是聚焦在限流和降级两个方面。Nacos 和 Sentinel 均是在阿里近10年的核心业务场景下沉淀所产生的,他们的开源是对微服务和元原生领域开源技术方案的有效补充,同时也非常强调融入开源生态,除了兼容 Dubbo,也支持 SpringCloud 和 Kubenetes 等生态,以增强自身的生命力。“阿里巴巴早在 2007 年进行从 IOE 集中式应用架构升级为互联网分布式服务化架构的时候,就意识到在分布式环境中,诸如分布式服务治理,数据源容灾切换、异地多活、预案和限流规则等场景下的配置变更难题,因为在一个大型的分布式系统中,你没有办法把整个分布式系统停下来,去做一个软件、硬件或者系统的升级。”阿里云智能高级技术专家坤宇在 QCon 的现场分享道。相比其他服务配置中心开源方案,Nacos 的起步虽然晚了点,但除了配置中心,他还提供了动态服务发现、服务共享与管理的功能,在大规模场景下具备更优秀的性能,在易用性上更便捷,分布式部署上更灵活。Nacos 支持多种启动模式,用户可以根据业务场景自由选择,将各个功能模块,如注册中心和配置中心,分开部署或者合并部署,从而能够完整支持小型创业公司成长到大型企业,微服务全生命周期的演进。截止到目前,已经有40多家企业将 Nacos 部署到生产环境中,例如 虎牙直播 就是最早一批将 Nacos 大规模引入到生产环境的典型用户。“虎牙关注 Nacos 是从v0.2 开始的,我们也参与了社区的建设,可以说是比较早期的企业用户。引入Nacos前,我们也对比了Spring Cloud Config Server、ZooKeeper 和 ectd ,总体评估下来,基于我们微服务体系现状以及业务场景,决定使用 Nacos 作为服务化改造中服务注册和服务发现的方案。使用过程中,我们发现,随着社区版本的不断更新和虎牙的深入实践,Nacos 的优势远比调研过程中发现的多。”虎牙基础保障部中间件团队负责人张波在一次开发者活动上分享道。巧的是,一边是 Nacos宣布开源,并被列入 CNCF 云原生全景图,另一边是 Spring Cloud 生态下的服务注册和发现组件 Netflix Eureka 宣布停止开源投入,勇敢者的游戏充满了变数,但在 Nacos 团队看来,这场游戏自己可以走到最后,因为我们并不是一个人在战斗,Nacos 只是阿里众多开源项目中的一员,随后还会有更多的开源项目反哺给社区,形成生态,例如轻量级限流降级组件 Sentinel。2018年7月29日,AliwareOpen Source•深圳站现场,只能容纳400人的场地,来了700多位开发者。阿里云智能高级技术专家子矜在现场宣布了轻量级限流降级组件 Sentinel 的开源。Sentinel 经历了10年双11的考验,覆盖了阿里的所有核心场景,也因此积累了大量的流量归整场景以及生产实践。Sentinel 的出现,离不开阿里历届高可用架构团队的共同努力。“在双11备战中,容量规划是最重要也是最具挑战的环节之一。从第一年开始,双11的0点时刻就代表了我们的历史最高业务访问量,它通常是日常流量的几十倍甚至上百倍。因此,如何让一个技术和业务持续复杂的分布式站点去更平稳支撑好这突如其来的流量冲击,是我们这10年来一直在解的题。”阿里云智能高可用架构团队资深技术专家游骥在一次双11备战结束后分享道。这10年,容量规划经历了人工估算、线下压测、线上压测、全链路压测、全链路压测和隔离环境、弹性伸缩相结合的5个阶段。2013年双11结束后,全链路压测的诞生解决了容量的确定性问题。作为一项划时代的技术,全链路压测的实现,对整个集团而言,都是一件里程碑事件。![2014年,高可用架构团队获得集团 CTO 大奖](https://upload-images.jianshu…随后,基于全链路压测为核心,打造了一系列容量规划相关的配套生态,提升能力的同时,降低了整个环节的成本、提升效率。随着容量规划技术的不断演进,2018年起,高可用架构团队希望可以把这些年在生成环境下的实践,贡献给社区,之后便有了 Sentinel 的开源。Sentinel 开源后仅两个月,便被列入云原生全景图谱,位于编排和管理模块象限中,同时被列入云原生全景图谱的还有提供应用架构自动探测、故障注入式高可用能力演练和一键应用限流降级等功能的应用高可用服务 AHAS。近期,Sentinel 贡献的spring-cloud-circuitbreaker-sentinel模块正式被社区合并至Spring Cloud Circuit Breaker,由此,Sentinel 也加入了 Spring Cloud Circuit Breaker 俱乐部,成为 Spring Cloud 官方的主流推荐选择之一。Spring Cloud 官方推荐的微服务方案不止 Sentinel 一个,还有 Spring Cloud Alibaba.2018年,中国的 Java 圈发生了一件大事。Spring Cloud 联合创始人 Spencer Gibb 在 Spring 官网的博客页面宣布:阿里巴巴开源 Spring Cloud Alibaba,并发布了首个预览版本。随后,Spring Cloud 官方 Twitter 也发布了此消息。可能是受到 Spring Cloud Netflix 减少开源投入的影响,Spring Cloud Alibaba 开源后的热度超出了阿里巴巴高级技术专家姬望的预期。在接受开源中国采访的过程中,姬望认为“Spring Cloud Alibaba 是中国 Java 开发者的福音,弥补了 Spring Cloud 原生实现在大规模集群场景上的局限性。Spring Cloud 规范的实现目前有很多,比如 Netflix 有自己的一整套体系,Consul 支持服务注册和配置管理,ZooKeeper 支持服务注册等。但每套实现或多或少都有各自的优缺点,或许大多数 Spring Cloud 用户很难体会到 Netflix OSS 等实现的局限性,无论是服务发现、分布式配置,还是服务调用和熔断都不太适合大规模集群场景,比如我们内部也遇到 Eureka 性能问题。因此,我们将自身的超大规模集群经验与强大的 SpringCloud 生态整合,实现强强联合,希望能对业界会产生一些积极的化学变化。”夏天过后,开源的热度仍在延续效率的好处在于,我们可以把自己的注意力和时间聚焦在更需要创造力的事情上,做更有成就感的事情。对于工作在工程领域的开发者们而言,他们的效率意识更强。2018年9月,阿里将内部广泛使用的 Java 线上诊断工具进行开源,取名 Arthas (阿尔萨斯)。也许是击中了开发者线上排查问题的痛点,Arthas 在距离开源后的第一个 Release 版发布仅 147 天,就获得了超过 1w 的 star 数,并有40多位 Contributors 参与开源贡献。从中,我们不仅看到 Arthas 在开发者群体中的受欢迎程度,也发现越来越多的国内开发者开始擅于使用开源技术加速业务发展,更是不禁畅想起将来会有更多国内的优质开源项目获得全球开发者的关注和喜爱。技术领域,一切 里程碑 的达成,都源于一份技术情怀。阿里云智能技术专家断岭回忆到:“Arthas 在阿里巴巴内部起源于2015年,当时微服务方兴未艾,我们团队一方面专注 Spring Boot 的落地,提高开发效率。另外一方面,希望可以提高技术团队线上排查问题的能力和效率。当时,我们经过选型讨论,选择基于 Greys (Greys 是阿里巴巴杜琨@oldmanpushcart 开发的),一款 Java 开源在线问题诊断工具来开发,以提供更好的应用诊断体验。”我们在用户体验上做了大量的改进:彩色UI、Web Console 和内网一键诊断等。慢慢的,Arthas 成为阿里巴巴很多技术同事线上诊断问题的必备工具。尽管 Arthas 在阿里内部广受好评,但只是一个自用的工具。取之开源,用之开源,因此我们在2018年9月28日,正式开源了 Arthas,希望可以帮助 Java 开发人员提升诊断效率。随着越来越多的开发者开始使用 Arthas,众多开发者效率工具将 Arthas 内置到自己的产品中,丰富了 Arthas 的接入和打开方式,例如 IDE 插件 Cloud Toolkit。时间来到2019年。阿里云智能高级开发工程师煊檍在内网分享到:分布式事式问题一直是应用开发过程中的技术痒点。不敢说是痛点,因为长久以来,大家普遍对分布式事务问题的应对策略还是:能不用就不用,尽量绕开。但在微服务架构普遍落地的今天,分布式事务问题越来越绕不开,解决方案不是没有,但要么性能差,要么侵入性高,不容易落地。总之,是有点“不爽”。而这种“不爽”集中反映在了分布式事务开源中间件 Fescar 上。当阿里云智能高级开发工程师清铭在2019年1月 RocketMQ Meetup 上宣布分布式事务中间件 Fescar 正式开源后的一周内,Fescar 便收获了3000+ star,社区讨论的 issue 达58个。随后,Fescar 项目组整理并回答了开发者们集中关心的13个问题,例如 Fescar 的诞生背景、适用场景,和其他开源分布式事务方案之间的差别等。阿里巴巴中间件团队于2014年发布 TXC(Taobao Transaction Constructor),开始为集团内应用提供分布式事务服务。2016年,TXC 经过产品化改造,以 GTS(Global TransactionService)的身份上线阿里云,成为当时业界唯一一款云上分布式事务产品,以阿里云公有云和专有云解决方案的形式,交付给众多外部客户,并得到了客户的一致认可。2019 年,基于 TXC 和 GTS 的技术积累,中间件团队发起了开源项目 Fescar(Fast & EaSy Commit And Rollback, FESCAR),和社区一起共建分布式事务解决方案。TXC/GTS/Fescar 一脉相承,为解决微服务架构下的分布式事务问题交出了一份与众不同的答卷。而Fescar 的愿景是让分布式事务的使用像本地事务的使用一样简单和高效。最终的目标是希望可以让 Fescar 适用于所有的分布式事务场景。阿里巴巴的开源之路仍在延续。恰逢其时,阿里云峰会·北京的开发者专场现场,阿里云智能资深技术专家李三红宣布,阿里开源 Open JDK 长期支持版本 Alibaba Dragonwell,作为 JCP 最高执行委员会唯一的中国企业,将更主动的参与到 Java 生态的维护工作中。Dragonwell 意为龙井,象征着中国的茶文化。本文作者:中间件小哥阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

March 25, 2019 · 2 min · jiezi

Service Mesh深度解析

本片文章不是原创,转自:https://time.geekbang.org/art…微服务方兴未艾如火如荼之际,在 spring cloud 等经典框架之外,Service Mesh 技术正在悄然兴起。到底什么是 Service Mesh,它的出现能带来什么,又能改变什么?本文整理自数人云资深架构师敖小剑在 QCon 2017 上海站上的演讲。简单回顾一下过去三年微服务的发展历程。在过去三年当中,微服务成为我们的业界技术热点,我们看到大量的互联网公司都在做微服务架构的落地。也有很多传统企业在做互联网技术转型,基本上还是以微服务和容器为核心。在这个技术转型中,我们发现有一个大的趋势,伴随着微服务的大潮,Spring Cloud 微服务开发框架非常普及。而今天讲的内容在 Spring Cloud 之外,我们发现最近新一代的微服务开发技术正在悄然兴起,就是今天要给大家带来的 Service Mesh/ 服务网格。我做一个小小的现场调查,今天在座的各位,有没有之前了解过服务网格的,请举手。(备注:调查结果,现场数百人仅有 3 个人举手) 既然大家都不了解,那我来给大家介绍。首先,什么是 Service Mesh?然后给大家讲一下 Service Mesh 的演进历程,以及为什么选择 Service Mesh 以及为什么我将它称之为 下一代的微服务,这是我们今天的内容。什么是 Service Mesh?我们首先说一下 Service Mesh 这个词,这确实是一个非常非常新的名词,像刚才调查的,大部分的同学都没听过。这个词最早使用由开发 Linkerd 的 Buoyant 公司提出,并在内部使用。2016 年 9 月 29 日第一次公开使用这个术语。2017 年的时候随着 Linkerd 的传入,Service Mesh 进入国内技术社区的视野。最早翻译为“服务啮合层”,这个词比较拗口。用了几个月之后改成了服务网格。后面我会给大家介绍为什么叫网格。先看一下 Service Mesh 的定义,这个定义是由 Linkerd 的 CEO William 给出来的。Linkerd 是业界第一个 Service Mesh,也是他们创造了 Service Mesh 这个词汇的,所以这个定义比较官方和权威。我们看一下中文翻译,首先服务网格是一个基础设施层,功能在于处理服务间通信,职责是负责实现请求的可靠传递。在实践中,服务网格通常实现为轻量级网络代理,通常与应用程序部署在一起,但是对应用程序透明。这个定义直接看文字大家可能会觉得比较空洞,不太容易理解到底是什么。我们来看点具体的东西。Service Mesh 的部署模型,先看单个的,对于一个简单请求,作为请求发起者的客户端应用实例,会首先用简单方式将请求发送到本地的 Service Mesh 实例。这是两个独立进程,他们之间是远程调用。Service Mesh 会完成完整的服务间调用流程,如服务发现负载均衡,最后将请求发送给目标服务。这表现为 Sidecar。Sidecar 这个词中文翻译为边车,或者车斗,也有一个乡土气息浓重的翻译叫做边三轮。Sidecar 这个东西出现的时间挺长的,它在原有的客户端和服务端之间加多了一个代理。多个服务调用的情况,在这个图上我们可以看到 Service Mesh 在所有的服务的下面,这一层被称之为 服务间通讯专用基础设施层。Service Mesh 会接管整个网络,把所有的请求在服务之间做转发。在这种情况下,我们会看到上面的服务不再负责传递请求的具体逻辑,只负责完成业务处理。服务间通讯的环节就从应用里面剥离出来,呈现出一个抽象层。如果有大量的服务,就会表现出来网格。图中左边绿色方格是应用,右边蓝色的方框是 Service Mesh,蓝色之间的线条是表示服务之间的调用关系。Sidecar 之间的连接就会形成一个网络,这个就是服务网格名字的由来。这个时候代理体现出来的就和前面的 sidecar 不一样了,形成网状。再来回顾前面给出的定义,大家回头看这四个关键词。首先第一个,服务网格是抽象的,实际上是抽象出了一个基础设施层,在应用之外。其次,功能是实现请求的可靠传递。部署上体现为轻量级的网络代理。最后一个关键词是,对应用程序透明。大家注意看,上面的图中,网络在这种情况下,可能不是特别明显。但是如果把左边的应用程序去掉,现在只呈现出来 Service Mesh 和他们之间的调用,这个时候关系就会特别清晰,就是一个完整的网络。这是 Service Mesh 定义当中一个非常重要的关键点,和 Sidecar 不相同的地方:不再将代理视为单独的组件,而是强调由这些代理连接而形成的网络。在 Service Mesh 里面非常强调代理连接组成的网络,而不像 sidecar 那样看待个体。现在我们基本上把 Service Mesh 的定义介绍清楚了,大家应该可以大概了解什么是 Service Mesh 了。Service Mesh 演进历程第二个部分和大家追溯一下 Service Mesh 的演进历程。要注意,虽然 Service Mesh 这个词汇直到 2016 年 9 才有,但是它表述的东西很早以前就出现了。首先看“远古时代”,第一代网络计算机系统,最早的时候开发人员需要在自己的代码里处理网络通讯的细节问题,比如说数据包顺序、流量控制等等,导致网络逻辑和业务逻辑混杂在一起,这样是不行的。接下来出现了 TCP/IP 技术,解决了流量控制问题,从右边的图上可以看到,功能其实没发生变化:所有的功能都在,代码还是要写。但是,最重要的事情,流程控制,已经从应用程序里面抽出来了。对比左右两边的图,抽出来之后被做成了操作系统网络层的一部分,这就是 TCP/IP,这样的话应用的结构就简单了。现在写应有,就不用考虑网卡到底怎么发。在 TCP/IP 之后,这是完全不需要考虑的。上面说的是非常遥远的事情,大概发生在五十年前。微服务时代也面临着类似的一些东西,比如说我们在做微服务的时候要处理一系列的比较基础的事情,比如说常见的服务注册、服务发现,在得到服务器实例之后做负载均衡,为了保护服务器要熔断 / 重试等等。这些功能所有的微服务都跑不掉,那怎么办呢?只能写代码,把所有的功能写进来。我们发现最早的微服务又和刚才一样,应用程序里面又加上了大量的非功能性的代码。为了简化开发,我们开始使用类库,比如说典型的 Netflix OSS 套件。在把这些事情做好以后,开发人员的编码问题就解决了:只需要写少量代码,就可以把这些功能实现。因为这个原因,最近这些年大家看到 Java 社区 Spring Cloud 的普及程度非常快,几乎成为了微服务的代名词。到了这个地步之后,完美了吗?当然,如果真的完美了,那我今天就不会站在这里了:)我们看这几个被称之为痛点的东西: 内容比较多,门槛比较高。调查一下,大家学 Spring Cloud,到你能熟练掌握,并且在产品当中应用,可以解决出现的问题,需要多长时间?一个星期够不够?大部分人一个星期是不够的,大部分人需要三到六个月。因为你在真实落地时会遇到各种问题,要能自己解决的话,需要的时间是比较长的。这里是 Spring Cloud 的常见子项目,只列出了最常见的部分,其中 spring cloud netflix 下还有 netflix OSS 套件的很多内容。要真正吃透 Spring Cloud,需要把这些东西全部吃透,否则遇到问题时还会非常难受。这么多东西,在座的各位相对来说学习能力比较强一点,可能一个月就搞定了,但是问题是你的开发团队,尤其是业务开发团队需要多久,这是一个很要命的事情:业务团队往往有很多比较初级的同事。然后事情并不止这么简单,所谓雪上加霜,我们还不得不面对一堆现实。• 比如说,我们的业务开发团队的强项是什么?最强的会是技术吗?不,通常来说我们的业务开发团队最强的是对业务的理解,是对整个业务体系的熟悉程度。• 第二个事情,业务应用的核心价值是什么?我们辛辛苦苦写了这么多的微服务,难道是为了实现微服务吗?微服务只是我们的手段,我们最终需要实现的是业务,这是我们真正的目标。但是这些还没完,比你写一个新的微服务系统更痛苦的事情,是你要对旧有的系统进行微服务改造。所有这些加在一起,还不够,还要再加一条,这条更要命:业务开发团队往往业务压力非常大,时间人力永远不足。说下月上线就是下月上线,说双十一促销就不会推到双十二。老板是不会管你有没有时间学习 spring cloud 的,也不会管你的业务团队能否搞得定微服务的方方面面。业务永远看的是结果。第二个痛点,功能不够,这里列出了服务治理的常见功能。而 Spring Cloud 的治理功能是不够强大的,如果把这些功能一一应对做好,靠 Spring Cloud 直接提供的功能是远远不够的。很多功能都需要你在 Spring Cloud 的基础上自己解决。问题是你打算投入多少时间人力资源来做这个事情。有些人说我大不了有些功能我不做了,比如灰度,直接上线好了,但是这样做代价蛮高的。第三个痛点,跨语言。微服务在刚开始面世的时候,承诺了一个很重要的特性:就是不同的微服务可以采用自己最擅长最喜欢的最适合的编程语言来编写,这个承诺只能说有一半是 OK 的,但是另外一半是不行的,是假的。因为你实现的时候,通常来说是基于一个类库或者框架来实现的,一旦开始用具体编程语言开始编码的时候你就会发现,好像不对了。为什么?左边是我从编程语言排行列表列出来的主流编程语言,排在前面的几种,大家比较熟悉. 后面还有几十种没有列出来,中间是新兴的编程语言,比较小众一点。现在的问题在于 我们到底要为多少种语言提供类库和框架。这个问题非常尖锐,为了解决这个问题,通常只有两条路可选:一种就是统一编程语言,全公司就用一种编程语言另外一个选择,是有多少种编程语言就写多少个类库我相信在座的如果有做基础架构的同学,就一定遇到过这个问题。但是问题还没完,框架写好了,也有能够把各个语言都写一份。但是接下来会有第四个痛点:版本升级。你的框架不可能一开始就完美无缺,所有功能都齐备,没有任何 BUG,分发出去之后就再也不需要改动,这种理想状态不存在的。必然是 1.0、2.0、3.0 慢慢升级,功能逐渐增加,BUG 逐渐被修复。但是分发给使用者之后,使用者会不会立马升级?实际上做不到的。这种情况下怎么办,会出现客户端和服务器端版本不一致,就要非常小心维护兼容性,然后尽量督促你的使用者:我都是 3.0 了,你别用 1.0 了,你赶紧升级吧。但是如果如果他不升级,你就继续忍着,然后努力解决你的版本兼容性。版本兼容性有多复杂?服务端数以百计起,客户端数以千计起,每个的版本都有可能不同。这是一个笛卡尔乘积。但是别忘了,还有一个前面说的编程语言的问题,你还得再乘个 N!设想一下框架的 Java1.0 客户端访问 node.js 的 3.0 的服务器端会发生什么事情,c++ 的 2.0 客户端访问 golang 的 1.0 服务器端会如何?你想把所有的兼容性测试都做一遍吗?这种情况下你的兼容性测试需要写多少个 case,这几乎是不可能的。那怎么办?怎么解决这些问题,这是现实存在的问题,总是要面对的。我们来想一想:第一个是这些问题的根源在哪里:我们做了这么多痛苦的事情,面临这么多问题,这些多艰巨的挑战,这些和服务本身有关系吗?比如写一个用户服务,对用户做 CRUD 操作,和刚才说的这些东西有一毛钱关系吗?发现有个地方不对,这些和服务本身没关系,而是服务间的通讯,这才是我们需要解决的问题。然后看一下我们的目标是什么。我们前面所有的努力,其实都是为了保证将客户端发出的业务请求,发去一个正确的地方。什么是正确的地方?比如说有版本上的差异,应该去 2.0 版本,还是去 1.0 版本,需要用什么样的负载均衡,要不要做灰度。最终这些考虑,都是让请求去一个你需要的正确的地方。第三个,事情的本质。整个过程当中,这个请求是从来不发生更改的。比如我们前面说的用户服务,对用户做 CRUD,不管请求怎么走,业务语义不会发生变化。这是事情的本质,是不发生变化的东西。这个问题具有一个高度的普适性:所有的语言,所有的框架,所有的组织,这些问题对于任何一个微服务都是相同的。讲到这里,大家应该有感觉了:这个问题是不是和哪个问题特别像?五十年前的前辈们,他们要解决的问题是什么?为什么会出现 TCP,TCP 解决了什么问题?又是怎么解决的?TCP 解决的问题和这个很像,都是要将请求发去一个正确的地方。所有的网络通讯,只要用到 TCP 协议,这四个点都是一致的。有了 TCP 之后会发生什么? 我们有了 TCP 之后,我们基于 TCP 来开发我们的应用,我们的应用需要做什么事情? 我们的应用需要关心 TCP 层下链路层的实现吗?不需要。同理,我们基于 HTTP 开发应用时,应用需要关心 TCP 层吗?为什么我们开发微服务应用的时候就要这么关心服务的通讯层?我们把服务通讯层所有的事情学一遍,做一遍,我们做这么多是为什么?这种情况下,自然产生了另外一个想法:既然我们可以把网络访问的技术栈向下移为 TCP,我们是可以也有类似的,把微服务的技术栈向下移?最理想的状态,就是我们在网络协议层中,增加一个微服务层来完成这个事情。但是因为标准问题,所以现在没有实现,暂时这个东西应该不太现实,当然也许未来可能出现微服务的网络层。之前有一些先驱者,尝试过使用代理的方案,常见的 nginx,haproxy,apache 等代理。这些代码和微服务关系不大,但是提供了一个思路:在服务器端和客户端之间插入了一个东西完成功能,避免两者直接通讯。当然代理的功能非常简陋,开发者一看,想法不错,但是功能不够,怎么办?这种情况下,第一代的 Sidecar 出现了,Sidecar 扮演的角色和代理很像,但是功能就齐全很多,基本上原来微服务框架在客户端实现的功能都会对应实现。第一代的 sidecar 主要是列出来的这几家公司,其中最有名气的还是 netflix。在这个地方我们额外提一下,注意第四个,前面三个功能都是国外的公司,但是其实 sidecar 这个东西并不是只有国外的人在玩,国内也有厂商和公司在做类似的事情。比如唯品会,我当年在唯品会基础架构部工作的时候,在 2015 年上半年,我们的 OSP 服务化框架做了一个重大架构调整,加入了一个名为 local proxy 的 Sidecar。注意这个时间是 2015 上半年,和国外差不多。相信国内肯定还有类似的产品存在,只是不为外界所知。这个时期的 Sidecar 是有局限性的,都是 为特定的基础设施而设计,通常是和当时开发 Sidecar 的公司自己的基础设施和框架直接绑定的,在原有体系上搭出来的。这里面会有很多限制,一个最大的麻烦是无法通用:没办法拆出来给别人用。比如 Airbnb 的一定要用到 zookeeper,netflix 的一定要用 eureka,唯品会的 local proxy 是绑死在 osp 框架和其他基础设施上的。之所以出现这些绑定,主要原因还是和这些 sidecar 出现的动机有关。比如 netflix 是为了让非 JVM 语言应用接入到 Netflix OSS 中,soundcloud 是为了让遗留的 Ruby 应用可以使用到 JVM 的基础设置。而当年我们唯品会的 OSP 框架,local proxy 是为了解决非 Java 语言接入,还有前面提到的业务部门不愿意升级的问题。这些问题都比较令人头疼的,但是又不得不解决,因为逼的憋出来 sidecar 这个一个解决方式。因为有这样的特殊的背景和需求,所以导致第一代的 Sidecar 无法通用,因为它本来就是做在原有体系之上的。虽然不能单独拿出来,但是在原有体系里面还是可以很好工作的,因此也没有动力做剥离。导致虽然之前有很多公司有 Sidecar 这个东西,但是其实一直没怎么流传出来,因为即使出来以后别人也用不上。这里提一个事情,在 2015 年年中的时候,我们当时曾经有一个想法,将 Local proxy 从 OSP 剥离,改造为通用的 Sidecar。计划支持 HTTH1.1,操作 http header 就可以,body 对我们是可以视为透明的,这样就容易实现通用了。可惜因为优先级等原因未能实现,主要是有大量的其他工作比如各种业务改造,这个事情必要性不够。所有比较遗憾,当时我们有这个想法没做实现,这是在 2015 年,时间点非常早的了。如果当时有实现,很可能我们就自己折腾出业界第一个 service mesh 出来了。现在想想挺遗憾的。但是,不只有我们会有这想法。还有有一些人想法和我们差不多,但是比较幸运的是,他们有机会把东西做出来了。这就是第一代的 Service Mesh,通用性的 sidecar。通用型的 Service Mesh 的出现,左边第一个 Linkerd 是业界第一个 Service Mesh,也就是它创造了 Service Mesh 这个词。时间点:2016 年 1 月 15 号,0.0.7 发布,这是 github 上看到的最早的一个版本,其实这个版本离我们当时的有想法的时间点非常近。然后是 1.0 版本,2017 年 4 月份发布,离现在六个月。所以说,Service Mesh 是一个非常新的名词,大家没听过非常正常。接下来是 Envoy,2016 年发布的 1.0 版本。这里面要特别强调,Linkerd 和 Envoy 都加入了 CNCF,Linkerd 在今年 1 月份,而 Envoy 进入的时间是 9 月份,离现在也才 1 个月。在座的各位应该都明白 CNCF 在 Cloud Native 领域是什么江湖地位吧?可以说 CNCF 在 Cloud Native 的地位,就跟二战后联合国在国际秩序中的地位一样。之后出现了第三个 Service Mesh,nginmesh,来自于大家熟悉的 nginx,2017 年 9 月发布了第一个版本。因为实在太新,还在刚起步,没什么可以特别介绍的。我们来看一下 Service Mesh 和 Sidecar 的差异,前面两点是已经提到了:首先 Service Mesh 不再视为单独的组件,而是强调连接形成的网络第二 Service Mesh 是一个通用组件然后要强调的是第三点,Sidecar 是可选的,容许直连。通常在开发框架中,原生语言的客户端喜欢选择直连,其他语言选择走 sidecar。比如 java 写的框架,java 客户端直连,php 客户端走 sidecar。但是也可以都选择走 sidecar,比如唯品会的 OSP 就是所有语言都走 local proxy。在 sidecar 中也是可选的。但是,Service Mesh 会要求完全掌控所有流量,也就是所有的请求都必须通过 service mesh。接下来给大家介绍 Istio,这个东西我给它的评价是 王者风范,来自于谷歌、IBM 和 Lyft,是 Service Mesh 的集大成者。大家看它的图标,就是一个帆船。Istio 是希腊语,英文语义是 “sail”, 翻译过来是起航的意思。大家看这个名字和图标有什么联想?google 在云时代的另外一个现象级产品,K8S,kubernete 也同样起源于希腊语,船长,驾驶员或者舵手,图标是一个舵。istio 名字和图标与 k8s 可以说是一脉相承的。这个东西在 2017 年 5 月份发布了 0.1,就在两周前的 10 月 4 号发布了 0.2。大家都熟悉软件开发,应该明白 0.1/0.2 在软件迭代中是什么阶段。0.1 大概相当于婴儿刚刚出世,0.2 还没断奶。但是,即使在这么早期的版本中,我对他的评价已经是集大成者,王者风范,为什么?为什么说 Istio 王者风范?最重要的是他为 Service Mesh 带来了前所未有的控制力。以 Sidecar 方式部署的 Service Mesh 控制了服务间所有的流量,只要能够控制 Service Mesh 就能够控制所有的流量,也就可以控制系统中的所有请求。为此 Istio 带来了一个集中式的控制面板,让你实现控制。左边是单个视图,在 sidecar 上增加了控制面板来控制 sidecar。这个图还不是特别明显,看右边这个图,当有大量服务时,这个服务面板的感觉就更清晰一些。在整个网络里面,所有的流量都在 Service Mesh 的控制当中,所有的 Service Mesh 都在控制面板控制当中。可以通过控制面板控制整个系统,这是 Istio他的评价 Istio 由三个公司开发,前两个比较可怕,谷歌和 IBM,而且都是云平台,谷歌的云平台,IBM 的云平台,尤其 GCP 的大名想必大家都知道。所谓出身名门,大概指的就是这个样子吧?Istio 的实力非常强,我这里给了很多的赞誉:设计理念非常新颖前卫,有创意,有魄力,有追求,有格局。Istio 的团队实力也非常惊人,大家有空可以去看看 istio 的委员会名单感受一下。Istio 也是 google 的新的重量级的产品,很有可能成为下一个现象级的产品。Google 现在的现象级产品是什么?K8s。而 Istio 很有可能成为下一个 K8S 级别的产品。说到应时而生,什么是势?我们今天所在的是什么时代?是互联网技术大规模普及的时代,是微服务容器如日中天的时代,是 Cloud Native 大势已成的时代。也是传统企业进行互联网转型的时代,今天的企业用户都想转型,这个大势非常明显,大家都在转或者准备转,但是先天不足。什么叫先天不足?没基因,没能力,没经验,没人才,而且面临我们前面说的所有的痛点。所有说 Istio 现在出现,时机非常合适。别忘了 istio 身后还有 CNCF 的背景,已经即将一统江湖的 k8s。istio 在发布之后,社区响应积极,所谓应者云集。其中作为市面上仅有的几个 Service Mesh 之一的 Envoy,甘心为 istio 做底层,而另外两个实现 Linkerd/nginmesh 也直接放弃和 istio 的对抗,选择合作,积极和 istio 做集成。社区中的一众大佬,如这里列出来的,都在第一时间响应,要和 istio 做集成或者基于 istio 做自己的产品。为什么说是第一时间?istio 出 0.1 版本,他们就直接表明态度开始站队了。Istio 面的数据面板,是给传统 service mesh 的,目前是 Envoy,但是我们前面也提到 linkerd 和 nginmesh 都在和 istio 做集成,指的就是替代 Envoy 做数据面板。另外一大块就是上面的控制面板,这是 istio 真正带来的内容。主要分成三大块,图中我列出了他们各自的职责和可以实现的功能。因为时间有限,不在今天具体展开。 ...

March 20, 2019 · 3 min · jiezi

阿里巴巴微服务开源项目盘点(持续更新)

大前端、微服务、数据库、更多精彩,尽在开发者分会场【Apache Dubbo】Apache Dubbo 是一款高性能、轻量级的开源Java RPC框架,是国内影响力最大、使用最广泛的开源服务框架之一,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。在2016年、2017、2018年开源中国发起的最受欢迎的中国开源软件评选中,连续三年进入Top10名单。2019年2月Dubbo发布了2.7.0,这一版本将用于Apache 基金会的正式毕业。项目地址:https://github.com/apache/incubator-dubbo最新动态:探秘 Dubbo 的度量统计基础设施 - Dubbo MetricsDubbo 生态添新兵,Dubbo Admin 发布 v0.1谁正在用:https://github.com/apache/incubator-dubbo/issues/1012媒体报道:Dubbo作者亲述:那些辉煌、沉寂与重生的故事【Apache RocketMQ】Apache RocketMQ 是一款分布式消息引擎,具备「低延迟」、「高性能」、「高可靠性」等特点,可满足兆级容量和可扩展性的需求。它是国内首个非 Hadoop 生态体系的Apache 社区顶级项目,根据项目毕业前的统计,RocketMQ有百分八十的新特性与生态集成来自于社区的贡献。项目地址:https://github.com/apache/rocketmq最新动态:Apache RocketMQ 发布 v4.4.0,新添权限控制和消息轨迹特性谁正在用:RocketMQ 在平安银行的实践和应用滴滴出行基于RocketMQ构建企业级消息队列服务的实践【OpenMessaging】OpenMessaging开源项目于2017年正式入驻Linux基金会,是国内首个在全球范围发起的分布式计算领域的国际标准。OpenMessaging开源标准社区的企业达10家之多,包括阿里巴巴、Datapipeline、滴滴出行、浩鲸科技、京东商城、青云QingCloud、Streamlio、微众银行、Yahoo、中国移动苏州研发中心(按首字母排序),此外,还获得了RocketMQ、RabbitMQ和Pulsar 3个顶级消息开源厂商的支持。项目地址:https://github.com/openmessaging/openmessaging-java首个由国内发起的分布式消息领域的国际标准OpenMessaging一周年回顾【Nacos】Nacos 是一个更易于帮助构建云原生应用的动态服务发现、配置和服务管理平台,提供「注册中心」、「配置中心」和「动态DNS服务」三大功能。项目地址:https://github.com/alibaba/nacos谁正在用:https://github.com/alibaba/nacos/issues/273虎牙直播在微服务改造方面的实践和总结最新动态:Nacos Committers 团队首亮相,发布 0.9.0 版本背后的故事最佳实践阿里巴巴基于 Nacos 实现环境隔离的实践如何打通CMDB,实现就近访问【Sentinel】Sentinel 承接阿里巴巴近10年双十一大促流量的核心场景,以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。其提供丰富的应用场景支持、完备的监控能力、易用的拓展点。项目地址:https://github.com/alibaba/sentinel最新动态:https://dwz.cn/faH9eKHH背后的故事媒体专访:限流熔断技术选型:从Hystrix到Sentinel最佳实践快速体验 Sentinel 集群限流功能,只需简单几步【Arthas】Arthas 是一款 Java 线上诊断工具,可有效解决开发过程中遇到的各类诊断难题。2018年9月,阿里将内部广泛使用的java线上诊断工具进行开源,取名arthas(阿尔萨斯)。也许是击中了开发者线上排查问题的痛点,Arthas在距离开源后的第一个Release 版发布仅 147 天,就获得了超过1w的star数,并有40多位contributors参与开源贡献。项目地址:https://github.com/alibaba/arthas最新动态:新的开始 | Arthas GitHub Star 破万后的回顾和展望【Spring Cloud Alibaba】Spring Cloud Alibaba 致力于提供微服务开发的一站式解决方案,提供包括「服务限流降级」、「服务注册与发现」、「分布式配置管理」和「阿里云对象存储」等主要功能。项目地址:https://github.com/spring-cloud-incubator/spring-cloud-alibaba最新动态:Spring Cloud Alibaba发布第二个版本,Spring 发来贺电媒体报道:Spring Cloud Alibaba,中国 Javaer 的福音,为微服务续上 18 年【Fescar】Fescar 是阿里巴巴2019年1月开源的一套分布式事务中间件,Fescar 的愿景是让分布式事务的使用像现在本地事务的使用一样简单、高效,最终的目标是希望可以适用于所有的分布式事务场景。项目地址:https://github.com/alibaba/Fescar• 最佳实践集成源码深度剖析:Fescar x Spring Cloud分布式事务中间件 Fescar—RM 模块源码解读*阿里热门开源项目↓↓↓↓↓海量资源点击领取更有kindle、技术图书抽奖活动,百分百中奖本文作者:中间件小哥阅读原文本文为云栖社区原创内容,未经允许不得转载。

March 19, 2019 · 1 min · jiezi

华为云:微服务架构下的性能保障最佳实践

华为云:微服务架构下的性能保障最佳实践大数据时代,数字化转型已成为全球各大企业的战略核心。基于Devops的微服务架构是云时代部署应用的一项热门技术,它把庞大的单个应用程序分解为数十个微服务,每个服务独立开发、更新和部署,使业务更快速地响应市场变化。但是每个微服务有不同的客户需求、开发周期及交付时间,并且随着微服务应用增多,生产环境规模庞大,无法做1:1验证,传统的性能测试模式已远远不能满足Devops平台的要求。2018年11月2日,华为云测试架构师在DevOps国际峰会会议·深圳站发表了演讲,以实际项目中面临的问题出发,介绍内部是如何系统开展微服务性能测试,把高质量性能保障的思路和方法融入到DevOps流程,打造成华为云上服务性能保障的超级IP。演讲内容包括:微服务架构下的性能测试挑战、微服务性能保障解决方案设计、性能测试实施策略。微服务框架下的性能测试挑战微服务架构是以更复杂的应用管理、运维环境为代价,缩短应用交付时间。企业实现微服务云化改造,面临以下性能测试挑战:应对突发流量需求,扩容能否解决问题,如何扩容?每个微服务以独立进程多个实例运行,新特性开发需要频繁部署升级,如何评估单服务变更引起的性能影响?微服务数量众多,当某个服务出现问题后如何做到快速定位、快速排障。传统性能调优往往需要维护测试工具,模拟千级并发用户发起压测,再安装多种监控工具,汇总分析数十个数据,还要安装部署安装部署profile工具,分析所有节点profile结果,调优结果呈现需要数周时间,导致工作效率低下,用户体验无法保障,影响企业营收。华为云发布一站式微服务性能保障解决方案华为云性能测试服务CPTS、应用运维管理AOS、应用性能管理APM三大运维法宝hold住全场,为微服务高效运行保驾护航。从应用上线到版本迭代到日常维护,华为云一站式微服务性能保障解决方案覆盖应用生命全周期。构筑性能保障的第一道防线——云性能测试服务CPTS云性能测试服务CPTS被称为性能瓶颈的“侦探家”,它可帮助用户省去自建性能测试环境成本,模拟海量并发测试系统瓶颈,毫秒级发起万级-百万级并发压力,定位性能瓶颈并为大促场景预测资源。华为云CPTS服务除了提供基本性能测试要求,还全面支持微服务接口测试方法以及事务调试、响应提取、一键部署、文件导入变量等高级能力。目前已上线的智能分析特性提供一站式性能分析,多维度展示了TPS、时延、资源使用情况、调用链等测试数据。构筑性能保障的第二道防线——应用运维管理AOM在基于微服务架构的分布式应用日常运作中,应用运维管理AOM、应用性能管理APM为应用提供生命周期管理。应用运维管理AOM深度对接华为云应用服务,一站式收集基础设施、中间件和应用实例的运维数据,可以实现对云主机、存储、网络、docker、kubernetes等应用运行环境的深入监控并进行集中统一管理,提供应用级故障分析、告警管理、日志采集与分析等能力,能够有效预防问题的产生及定位故障,降低运维成本。AOM并非传统监控,它通过应用的角度看业务,满足企业对业务的高效和快速迭代的需求,可帮助企业更好的达到其战略目标并实现IT资产调优。构筑性能保障的第三道防线——应用性能管理APM华为云应用性能管理APM是对AOM运维能力的补充,适用于多种Java框架的应用。它包含了强大的分析工具,通过拓扑图、调用链、事务将应用状态、调用过程、用户对应用进行的操作可视化地展现了出来,帮助运维人员快速解决应用在分布式架构下的问题定位和性能瓶颈等难题。同时华为云APM实时分析应用事务,提供Apdex(应用性能指数)打分,体验数据数字化,帮助企业全面了解用户体验状况。APM现已在互联网、电商、金融领域实现解决方案落地。想了解更多微服务技术,欢迎访问华为云学院(https://edu.huaweicloud.com/c… )

March 19, 2019 · 1 min · jiezi

开发者必看!探秘阿里云Hi购季开发者分会场:海量学习资源0元起!

摘要: 开发者分会场致力于帮助开发者学习了解阿里云最新技术,为开发者设计全方位的技术成长与进阶之路。2019阿里云云上Hi购季活动已经于2月25日正式开启,从已开放的活动页面来看,活动分为三个阶段: 2月25日-3月04日的活动报名阶段、3月04日-3月16日的新购满返+5折抢购阶段、3月16日-3月31日的续费抽豪礼+5折抢购阶段。活动核心亮点:作为阿里云-Hi购季最神秘的会场,开发者分会场在3月16日全网开放。下面,云栖社区小编就为各位开发者分享该会场的攻略:开发者分会场活动阵地关键词:学习,成长,进阶开发者分会场致力于帮助开发者学习了解阿里云最新技术,为开发者设计全方位的技术成长与进阶之路。开发者分会场包括小微项目,开发者工具,阿里开源,移动开发,企业研发效能,机器学习,学习认证交流,技术影响力&众包平台 八大技术场景。从个人开发者到企业开发者,到技术影响力打造,开发者分会场提供给开发者一站式的开发者服务及海量免费资源下载!开发者会场链接:https://www.aliyun.com/acts/product-section-2019/developer?utm_content=g_1000046885首先,先为大家送上开发者福利!Kindle、技术图书、T恤等好礼等你来抽取!100%中奖!点此进入抽奖:https://www.aliyun.com/acts/product-section-2019/developer下面小编就带着大家去逐个了解每个开发者场景:1. 小微项目小微项目提供域名注册,商标服务,网站部署等服务,帮助用户在阿里云上快速搭建应用。域名注册互联网应用始于域名,高记忆度的域名既是吸引用户访问网站的“门牌”,又是电子商务的“经营场所”。阿里云旗下品牌万网域名注册蝉联国内市场NO.1,目前有超过4000万域名在万网注册。万网为用户提供快速稳定,易用安全及高性价比的域名服务,目前可支持域名注册,域名交易,域名预定,域名转入,域名云解析等等服务。帮助用户智能查询,快速注册,通过强大的域名自助平台轻松管理域名,同时提供独具特色的隐私保护,安全锁,自检服务,以及贴心的到期续费提醒,全方位保护用户的域名。商标注册商标注册流程包括创建订单,材料审核,提交商标局,获取回执,受理通过,初审公告,注册公告,核发证书等8个步骤。阿里云商标注册服务,提供一站式标准服务,最快1分钟极速申报,实时掌握申请进度。用户可以根据自身情况选择自助注册申请,专家辅助申请及担保注册申请。同时阿里云也提供商标续展申请及商标数据监控服务,目前用户可免费体验商标数据监控服务。网站部署阿里云提供定制建站服务,1V1网站策划设计,量身打造企业互联网形象可视化管理后台便捷维护,支持软件上云。阿里云提供千套模版,涵盖物流,化工业,知识产权服务,教育机构,能源设备等等。集成41中控件,用户可随意搭配。零基础学员也可以建站,APP、小程序、电商网站、云计算架构等轻松搭建。同时根据用户需求选择个性化网站定制开发。2. 开发者工具阿里云SDK平台通过调用阿里云SDK,用户不需要复杂编程即可访问云服务器、云数据库RDS、云监控等多个阿里云服务。阿里云SDK平台提供全产品线相关sdk,包括Java SDK、Go SDK、C SDK、Python SDK、Node.js SDK、Ruby SDK、PHP SDK、Android SDK、.Net SDK、IOS SDK等。阿里云API平台阿里云提供了丰富的 API 供用户使用。用户可在线查询云产品API文档,在线接口调试功能,同步生成对应SDK Demo代码,并对API文档进行整合。除此之外,阿里云推荐行业应用API及定制API解决方案,如通用文字识别,图像人脸检测,图片文字识别API等,可支持海量全企业应用,方便用户快速调用。3. 阿里开源阿里技术架构图首次曝光,提供25个开源库。大前端层开源库有Weex,ice,vlayout,ARouler,atals,AndFix。微服务层包括服务发现,配置管理,服务管理平台Nacos,分布式数据流平台,RocketMQ,微服务开发一站式解决方案Spring Cloud Alibaba,分布式事物解决方案Fescar,微服务架构解决方案Dubbo,控制流组件Sentinel。开源的数据库有关系型数据库AliSQL,关系型数据的分布式处理系统cobar,数据传输与同步服务otter、canal、DataX,以及大数据可视化图表库BizCharts。运行时可使用p2p镜像分发工具Dragonfly和企业级富容器引擎PouchContainer。操作系统层开源产品是AliKernel。4. 移动开发阿里云移动研发平台(Enterprise Mobile Application Studio, EMAS),面向企业服务市场,期望将阿里巴巴移动互联网近十年在移动互联网行业沉淀的DevOps研发支撑能力,移动App,基础中间件能力开放给用户。EMAS平台宗旨是提供一站式移动开发解决方案,帮助传统企业快速完成业务移动化的转型升级目标。EMAS提供的服务覆盖移动APP研发全生命周期的组件服务,提供APP内反馈、业务行位分析、用户消息推动等运维服务。另外,还包括APP真机自动化测试、线上APP质量、性能实施分析服务。帮助开发者解决研发阶段碰到的流量劫持、线上问题修复等痛点。目前阿里云移动开发服务用户包括猫途鹰,头条,CCTV,中国平安,汽车之家等等国内知名企业。阿里云为开发者提供了几种不同移动开发套餐,包括免费版,基础版,标准版及高级版。开发者可按需选择。5. 企业研发效能云效,一站式企业协同研发云,源于阿里巴巴多年先进的管理理念和工程实践,提供从“需求->开发->测试->发布->运维->运营”端到端的协同服务和研发工具支撑。云效涵盖项目协作域、研发域、测试域、运维域的一站式企业研发协同云服务。目前,云效主要服务于企业管理层、业务负责人、研发工程师、测试工程师、PM/PMO、ScrumMaster等企业角色。云效致力于让协同更简单,让交付更高效。云效为用户提供项目管理,持续交付,代码托管,自动化测试及智能运维等服务。6. 机器学习阿里云机器学习和深度学习平台PAI(Platform of Artificial Intelligence),为传统机器学习提供100余种算法和大规模分布式计算的服务,覆盖回归、分类、聚类、文本分析等算法。提供企业分布式计算能力,轻松实现大规模数据处理。为深度学习客户提供单机多卡、多机多卡的高性价比资源服务,支持最新的深度学习开源框架,并提供阿里云深度优化的Tensorflow,性能与速度更佳。帮助开发者和企业客户弹性扩缩计算资源,轻松实现在线预测服务,提供可视化操作界面,通过托拉拽的方式拖动算法组件拼接实验。一站式服务包括数据清洗,特征工程,机器学习算法,评估,在线预测以及离线调度等都可以在机器学习平台(PAI)一站式体验。PAI泛推荐解决方案四部曲阿里云机器学习PAI产品为了帮助不同企业的快速实现个性化推荐的应用,推出了难度系数由浅到深的技术方案。方案一:基于Online Learning的流式算法推荐(免费邀测)方案二:基于深度学习模型Wide&Deep的推荐(限时5折)方案三:基于协同过滤算法的推荐方案四:基于对象特征的推荐以上所有方案都已经通过模型Demo的内嵌到PAI的产品中。机器学习应用实践阿里云机器学习平台PAI提供多种行业应用实践案例,帮助企业解决痛点。新浪微博MaxCompute+PAI:面对当前超大规模的数据量时,提供支持百亿特征维度的算法,提升平台算法处理能力。汇合营销DataWorks+PAI+AnalyticDB+MaxCompute:提供高效低成本的海量数据分析,数据查询分析的实时性,精准化广告营销。PING++MaxCompute+PAI:对海量数据进行数据分析与业务创新从而提高用户黏性,搭建安全、可靠、稳定的大数据平台。天弘基金MaxCompute+PAI:面对多用户场景,提升数据清算效率,降低成本。为用户推荐产品,画像分析,识别敏感数据。7. 学习认证交流云大学阿里云大学作为阿里云泛云生态人才培养的平台,旨在充分利用学院制的教学方式,结合互联网的特点与优势,为用户提供严谨的体系化课程与实验环境。资深行业专家辅导+阿里云真实环境实操的方式,认证讲师在线辅导,班级助教在线服务。目前包含专业有云计算,大数据分析,大数据开发,云安全,人工智能等。此外,还精选100余门免费课程,囊括云计算、大数据、编程语言和物联网(IoT)的行业热点技术,帮助学员简单入门,上手学习。云大学开放实验室还提供真实云环境、精品实验项目、详细实验文档,帮助用户快速上手并掌握阿里云产品 。云栖社区云栖社区是面向开发者的开放型技术平台。源自阿里云,服务于云计算技术全生态。包含博客、问答、培训、设计研发、资源下载等产品,以分享专业、优质、高效的技术为己任。提供阿里云最新技术活动,资源分享,直播课程,线下沙龙,专家分享等内容。8. 技术影响力MVP&云众包平台阿里云MVP阿里云最有价值专家,简称 MVP(Most Valuable Professional),是专注于帮助他人充分了解和使用阿里云的技术实践领袖。目前,阿里云已有来自全球26个国家的419位MVP成员。阿里云MVP等级分为初级,黄金,铂金和砖石四大级别。可享受的权益包括MVP定制礼包、专享服务权益、阿里云专家面对面、阿里云官方活动主推、全球年度峰会(定向邀约)、阿里云创客大赛提名、参观阿里巴巴总部、MVP 实验室、云栖大会城市峰会门票、云栖大会讲师邀请及全球技术游学等11种权益。初级MVP可立即享受前6种权益,逐级增加两项权益。同时,阿里云MVP成员也会通过分享自身行业数字化转型实践,技术干货的方式,手把手教用户使用阿里云,将开发者的声音反映到阿里云的技术路线图上。云众包平台阿里云众包平台是阿里云网站下设的一个子频道(zhongbao.aliyun.com),是阿里云提供的产品、技术或服务进行交易的网络平台。由需求方(买家)发布项目需求,阿里云认证服务商(卖家)接单并完成项目开发,需求方在项目验收后完成交易。众包平台目前有八大集市,包括网站建设、软件开发、云服务与咨询、办公软件、企业服务、设计类、营销类、大数据。对于众包平台的用户,仅需1分钟发布需求,从需求梳理到完成售后为您提供全套服务。阿里云有一套完整的从售前、交易到售后的保障体系,使其关注在产品及服务的选择而不是对交易的担忧。最后,欢迎大家参与我们的调研项目,提出您宝贵的意见:https://survey.aliyun.com/apps/zhiliao/UkPNoDNPC阅读原文本文为云栖社区原创内容,未经允许不得转载。

March 18, 2019 · 1 min · jiezi

服务的熔断机制 | 从0开始构建SpringCloud微服务(14)

照例附上项目github链接。本项目实现的是将一个简单的天气预报系统一步一步改造成一个SpringCloud微服务系统的过程。本章主要讲解服务的熔断机制。什么是服务的熔断机制对该服务的调用执行熔断,对于后续请求,不再继续调用该目标服务,而是直接返回,从而可以快速释放资源。有利于保护系统。熔断机制 : 当服务过载了,或者是流量激增,超过了服务的负荷,使用熔断机制,将服务掐断,保护自己不至于崩溃的机制。熔断机制是对系统的防护。当有非常大的请求数目来访问我们的服务的时候,若请求超出了我们的承受范围,或者是超过了我们预先设定的某个阈值,我们就将服务断掉,响应给用户一个默认的值。这个值不是用户所请求的值,到那时这个值可能包含了一些有价值的提示性的信息,如告诉用户我们的服务已经断掉了,请稍后再访问等。基于Hystrix的服务熔断机制原理我们可以把熔断器想象为一个保险丝,在电路系统中,一般在所有的家电系统连接外部供电的线路中间都会加一个保险丝,当外部电压过高,达到保险丝的熔点时候,保险丝就会被熔断,从而可以切断家电系统与外部电路的联通,进而保障家电系统不会因为电压过高而损坏。Hystrix提供的熔断器就有类似功能,当在一定时间段内服务调用方调用服务提供方的服务的次数达到设定的阈值,并且出错的次数也达到设置的出错阈值,就会进行服务降级,让服务调用方之间执行本地设置的降级策略,而不再发起远程调用。但是Hystrix提供的熔断器具有自我反馈,自我恢复的功能,Hystrix会根据调用接口的情况,让熔断器在closed,open,half-open三种状态之间自动切换。open状态说明打开熔断,也就是服务调用方执行本地降级策略,不进行远程调用。closed状态说明关闭了熔断,这时候服务调用方直接发起远程调用。half-open状态,则是一个中间状态,当熔断器处于这种状态时候,直接发起远程调用。三种状态的转换:closed->open:正常情况下熔断器为closed状态,当访问同一个接口次数超过设定阈值并且错误比例超过设置错误阈值时候,就会打开熔断机制,这时候熔断器状态从closed->open。open->half-open:当服务接口对应的熔断器状态为open状态时候,所有服务调用方调用该服务方法时候都是执行本地降级方法,那么什么时候才会恢复到远程调用那?Hystrix提供了一种测试策略,也就是设置了一个时间窗口,从熔断器状态变为open状态开始的一个时间窗口内,调用该服务接口时候都委托服务降级方法进行执行。如果时间超过了时间窗口,则把熔断状态从open->half-open,这时候服务调用方调用服务接口时候,就可以发起远程调用而不再使用本地降级接口,如果发起远程调用还是失败,则重新设置熔断器状态为open状态,从新记录时间窗口开始时间。half-open->closed: 当熔断器状态为half-open,这时候服务调用方调用服务接口时候,就可以发起远程调用而不再使用本地降级接口,如果发起远程调用成功,则重新设置熔断器状态为closed状态。如何集成Hystrix这里我们以之前的天气预报微服务系统为例,讲解如何在其中集成Hystrix。由于在天气预报微服务系统中,天气预报微服务将会调用多个其他微服务,所以我们让该服务集成Hystrix,当其他被调用的服务挂掉的时候,将会使熔断状态进入open模式,从而向用户返回一些默认的信息。添加依赖 <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> <version>2.1.1.RELEASE</version> </dependency>@EnableCircuitBreaker注解@SpringBootApplication@EnableDiscoveryClient@EnableFeignClients@EnableCircuitBreakerpublic class Sifoudemo02Application { public static void main(String[] args) { SpringApplication.run(Sifoudemo02Application.class, args); }}书写回调方法在方法前面加上@HystrixCommand注解,启用断路器,由于这个方法我们是依赖城市数据API微服务,与天气数据API微服务的,所以当这两个微服务异常之后,就会触发这个熔断机制。fallbackMethod为我们医用熔断机制后,请求失败时默认调用的方法,该方法的返回类型要与原方法一致。 //传入城市Id得到对应城市的天气数据 @GetMapping("/cityId/{cityId}") @HystrixCommand(fallbackMethod=“defaulGetReprotByCityId”) public Weather getReportByCityId(@PathVariable(“cityId”)String cityId)throws Exception{ //获取城市Id列表 //由城市数据API微服务来提供数据 List<City>cityList=null; try { cityList=dataClient.listCity(); }catch (Exception e) { logger.error(“Exception!",e); } Weather weather=weatherReportService.getDataByCityId(cityId); return weather; } public Weather defaulGetReprotByCityId(String cityId) { Weather weather=new Weather(); return weather; }运行测试在测试时我们将天气数据API微服务,与城市数据API微服务关闭,然后发送请求,得到的为默认返回的天气数据。也可以返回一段信息,提示用户服务不可用。

March 16, 2019 · 1 min · jiezi

微服务的集中化配置 | 从0开始构建SpringCloud微服务(13)

照例附上项目github链接。本项目实现的是将一个简单的天气预报系统一步一步改造成一个SpringCloud微服务系统的过程。本章主要讲解微服务的集中化配置。微服务为什么需要集中化配置微服务数量多,配置多手工管理配置繁琐使用Config实现Server端的配置中心集成Config Server添加依赖 <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-server</artifactId> </dependency>@EnableConfigServer注解//@ServletComponentScan(basePackages=“com.demo.web.servlet”)@SpringBootApplication@EnableDiscoveryClient@EnableConfigServerpublic class Sifoudemo02Application { public static void main(String[] args) { SpringApplication.run(Sifoudemo02Application.class, args); }}修改配置文件spring.application.name: msa-weather-config-serverserver.port= 8088eureka.client.serviceUrl.defaultZone: http://localhost:8761/eureka/spring.cloud.config.server.git.uri=https://github.com/ShimmerPig/spring-cloud-config-server-test01spring.cloud.config.server.git.searchPaths=config-repo这里指定的是我github上面的一个仓库将服务端启动后,发送请求进行访问,可以查看在云端配置中心的配置文件,当以后需要使用繁多的配置时,我们可以通过此方法对服务进行集中化的配置。

March 16, 2019 · 1 min · jiezi

探秘 Dubbo 的度量统计基础设施 - Dubbo Metrics

对服务进行实时监控,了解服务当前的运行指标和健康状态,是微服务体系中不可或缺的环节。Metrics 作为微服务的重要组件,为服务的监控提供了全面的数据基础。近日,Dubbo Metrics 发布了2.0.1版本,本文将为您探秘 Dubbo Metrics 的起源,及 7 大改进。Dubbo Metrics 的起源Dubbo Metrics(原Alibaba Metrics)是阿里巴巴集团内部广泛使用的度量埋点基础类库,有 Java 和 Node.js 两个版本,目前开源的是 Java 版本。内部版本诞生于2016年,经历过近三年的发展和双十一的考验,已经成为阿里巴巴集团内部微服务监控度量的事实标准,覆盖了从系统、JVM、中间件到应用各层的度量,并且在命名规则、数据格式、埋点方式和计算规则等方面,形成了一套统一的规范。Dubbo Metrics 的代码是基于 Dropwizard Metrics 衍生而来,版本号是3.1.0,当时决定 fork 到内部进行定制化开发的主要原因有两个。一是社区的发展非常缓慢,3.1.0之后的第3年才更新了下一个版本,我们担心社区无法及时响应业务需求;另一个更重要的原因是当时的3.1.0还不支持多维度的 Tag,只能基于 a.b.c 这样传统的指标命名方法,这就意味着 Dropwizard Metrics 只能在单维度进行度量。然后,在实际的业务过程中,很多维度并不能事先确定,而且在大规模分布式系统下,数据统计好以后,需要按照各种业务维度进行聚合,例如按机房、分组进行聚合,当时的 Dropwizard 也无法满足,种种原因使得我们做了一个决定,内部fork一个分支进行发展。Dubbo Metrics 做了哪些改进相对于 Dropwizard Metrics ,Dubbo Metrics 做的改进主要有以下几个方面:一、引入基于 Tag 的命名规范如前文所描述,多维度 Tag 在动态埋点,数据聚合等方面相对于传统的 metric 命名方法具有天然的优势,这里举一个例子,要统计一个 Dubbo 服务 DemoService 调用次数和 RT,假设这个服务叫做 DemoService,那么按照传统的命名方式,通常会命名为dubbo.provider.DemoService.qps和dubbo.provider.DemoService.rt。如果只有1个服务的话,这种方法并无太大的问题,但是如果一个微服务应用同时提供了多个 Dubbo 的 Service,那么要聚合统计所有Service 的 QPS 和 RT 就比较困难了。由于 metric 数据具有天然的时间序列属性,因此数据非常适合存储到时间序列数据库当中,要统计所有的 Dubbo 服务的 QPS,那么就需要查找所有名称为dubbo.provider..qps的指标,然后对他们进行求和。由于涉及到模糊搜索,这对于绝大部分数据库的实现都是比较费时的。如果要统计更加详细的 Dubbo 方法级别的 QPS 和 RT,那么实现上就会更加复杂了。Metric Key:用英文点号分隔的字符串,来表征这个指标的含义Metric Tag:定义了这个指标的不同切分维度,可以是单个,也可以是多个;tag key:用于描述维度的名称;tag value:用于描述维度的值;同时,考虑到一个公司所有微服务产生的所有指标,都会统一汇总到同一个平台进行处理,因此Metric Key 的命名方式为应当遵循同一套规范,避免发生命名冲突,其格式为appname.category[.subcategory].suffixappname: 应用名;category: 这个指标在该应用下的分类,多个单词用’‘连接,字母采用小写;subcategory: 这个指标在该应用下的某个分类下的子分类,多个单词用’‘连接,字母采用小写;suffix: 这个关键的后缀描述了这个指标所度量的具体类型,可以是计数,速率,或者是分布;在上述例子中,同样的指标可以命名为dubbo.provider.service.qps{service=“DemoService”},其中前面部分的名称是固定的,不会变化,括号里面的Tag,可以动态变化,甚至增加更多的维度,例如增加 method 维度dubbo.provider.service.qps{service=“DemoService”,method=“sayHello”},也可以是机器的 IP、机房信息等。这样的数据存储是时间序列数据库亲和的,基于这些数据可以轻松实现任意维度的聚合,筛选等操作。P.s. 2017年12月底,Dropwizard Metrics4.0 开始支持 Tag,Dubbo Metrics 中 ag 的实现参考了Dropwizard。spring-boot 2.0中提供的 MicroMeter 和 Prometheus 也均已引入了 Tag 的概念。二、添加精准统计功能Dubbo Metrics 的精准统计是和 Dropwizard,或者其他开源项目埋点统计库实现不太一样的地方。下面分别通过时间窗口的选择和吞吐率统计方式这两个纬度进行阐述。在统计吞吐率(如 QPS)的时候,Dropwizard的实现方式是滑动窗口+指数加权移动平均,也就是所谓的EWMA,在时间窗口上只提供1分钟、5分钟、15分钟的选择。固定窗口 vs 滑动窗口在数据统计的时候,我们需要事先定义好统计的时间窗口,通常有两种确立时间窗口的方法,分别是固定窗口和滑动窗口。固定时间窗口指的是以绝对时间为参考坐标系,在一个绝对时间窗口内进行统计,无论何时访问统计数据,时间窗口不变;而滑动窗口则是以当前时间为参考系,从当前时间往前推一个指定的窗口大小进行统计,窗口随着时间,数据的变化而发生变化。固定窗口的优点在于:一是窗口不需要移动,实现相对简单;二是由于不同的机器都是基于相同的时间窗口,集群聚合起来比较容易,只需按照相同的时间窗口聚合即可。其缺点是:如果窗口较大,实时性会受到影响,无法立即得到当前窗口的统计信息。例如,如果窗口为1分钟,则必须等到当前1分钟结束,才能得到这1分钟内的统计信息。滑动窗口的优点在于实时性更好,在任意时刻,能都看到当前时刻往前推演一个时间窗口内统计好的信息。相对而言,由于不同机器的采集时刻不同,要把不同机器上的数据聚合到一起,则需要通过所谓的 Down-Sampling 来实现。即按照固定时间窗口把窗口内收集到的数据应用到某一个聚合函数上。举个例子来说,假设集群有5台机器,按照15秒的频率按照平均值进行 Down-Sampling,若在00:00~00:15的时间窗口内,在00:01,00:03,00:06,00:09,00:11各收集到一个指标数据,则把这5个点的加权平均认为是00:00这个点的经过 Down- Sampling 之后的平均值。但在我们的实践过程中,滑动窗口仍会遇到了以下问题:很多业务场景都要求精确的时间窗口的数据,比如在双11的时候,想知道双11当天0点第1秒创建了多少订单,这种时候 Dropwizard 的滑动窗口很明显就不适用了。Dropwizard 提供的窗口仅仅是分钟级,双11的场景下需要精确到秒级。集群数据聚合的问题,每台机器上的滑动时间窗口可能都不一样,数据采集的时间也有间隔,导致聚合的结果并不准确。为了解决这些问题,Dubbo Metrics 提供了 BucketCounter 度量器,可以精确统计整分整秒的数据,时间窗口可以精确到1秒。只要每台机器上的时间是同步的,那么就能保证集群聚合后的结果是准确的。同时也支持基于滑动窗口的统计。瞬时速率(Rate) vs 指数移动加权平均(EWMA)经过多年的实践,我们逐渐发现,用户在观察监控的时候,首先关注的其实是集群数据,然后才是单机数据。然而单机上的吞吐率其实并没有太大意义。怎么理解呢?比如有一个微服务,共有2台机器,某个方法在某一段时间内产生了5次调用,所花的时间分别是机器1上的[5,17],机器2上的[6,8,8](假设单位为毫秒)。如果要统计集群范围内的平均 RT,一种方法可以先统计单机上的平均 RT,然后统计整体的平均 RT,按照这种方法,机器1上平均 RT 为11ms,机器2的平均 RT 为7.33ms,两者再次平均后,得到集群平均 RT 为9.17ms,但实际的结果是这样吗?如果我们把机器1和机器2上的数据整体拉到一起整体计算的话,会发现实际的平均 RT 为(5+17+6+8+8)/5=8.8ms,两者相差很明显。而且考虑到计算浮点数的精度丢失,以及集群规模扩大,这一误差会愈加明显。因此,我们得出一个结论:单机上的吞吐率对于集群吞吐率的计算没有意义,仅在在单机维度上看才是有意义的。而 Dropwizard 提供的指数加权移动平均其实也是一种平均,同时考虑了时间的因素,认为距离当前时间越近,则数据的权重越高,在时间拉的足够长的情况下,例如15分钟,这种方式是有意义的。而通过观察发现,其实在较短的时间窗口内,例如1s、5s,考虑时间维度的权重并没有太大的意义。因此在内部改造的过程中,Dubbo Metrics 做了如下改进:提供瞬时速率计算,反应单机维度的情况,同时去掉了加权平均,采用简单平均的方式计算;为了集群聚合需要,提供了时间窗口内的总次数和总 RT 的统计,方便精确计算集群维度的吞吐率;三、极致性能优化在大促场景下,如何提升统计性能,对于 Dubbo Metrics 来说是一个重要话题。在阿里的业务场景下,某个统计接口的 QPS 可能达到数万,例如访问 Cache 的场景,因此这种情况下 metrics 的统计逻辑很可能成为热点,我们做了一些针对性的优化:高并发场景下,数据累加表现最好的就是java.util.concurrent.atomic.LongAdder,因此几乎所有的操作最好都会归结到对这个类的操作上。避免调用 LongAdder#reset当数据过期之后,需要对数据进行清理,之前的实现里面为了重用对象,使用了LongAdder#reset进行清空,但实测发现LongAdder#reset其实是一个相当耗费cpu的操作,因此选择了用内存换 CPU,当需要清理的时候用一个新的 LongAdder 对象来代替。去除冗余累加操作某些度量器的实现里面,有些统计维度比较多,需要同时更新多个 LongAdder,例如 Dropwizard Metrics的 meter 实现里面计算1分/5分/15分移动平均,每次需要更新3个 LongAdder,但实际上这3次更新操作是重复的,只需要更新一次就行了。RT为0时避免调用Add方法大多数场景下对 RT 的统计都以毫秒为单位,有些时候当 RT 计算出来小于1ms的时候,传给metrics的 RT 为0。当我们发现 JDK 原生的 LongAdder 并没有对add(0)这个操作做优化,即使输入为0,还是把逻辑都走一遍,本质上调用的是sun.misc.Unsafe.UNSAFE.compareAndSwapLong。如果这个时候,metrics 判断 RT 为0的时候不对计数器进行 Add 操作,那么可以节省一次 Add 操作。这对于并发度较高的中间件如分布式缓存很有帮助,在我们内部某个应用实测中发现,在30%的情况下,访问分布式缓存的 RT 都是0ms。通过这个优化可以节约大量无意义的更新操作。QPS 和 RT 合并统计只需要对一个Long的更新,即可实现同时对调用次数和时间进行统计,已经逼近理论上的极限。经过观测发现,通常对于中间件的某些指标,成功率都非常高,正常情况下都在100%。为了统计成功率,需要统计成功次数和总次数,这种情况下几乎一样多,这样造成了一定的浪费,白白多做了一次加法。而如果反过来,只统计失败的次数,只有当失败的情况才会更新计数器,这样能大大降低加法操作。事实上,如果我们把每一种情况进行正交拆分,例如成功和失败,这样的话,总数可以通过各种情况的求和来实现。这样能进一步确保一次调用只更新一次计数。但别忘了,除了调用次数,还有方法执行 RT 要统计。还能再降低吗?答疑是可以的!假设 RT 以毫秒为单位进行统计,我们知道1个 Long 有64个bits(实际上Java里面的Long是有符号的,所以理论上只有63个 bits 可用),而 metrics 的一个统计周期最多只统计60s的数据,这64个 bits 无论怎样用都是用不掉的。那能不能把这63个 bits 拆开来,同时统计 count 和 RT 呢?实际上是可以的。我们把一个 Long 的63个 bits 的高25位用来表示一个统计周期内的总 count,低38位用于表示总 RT。——————————————| 1 bit | 25 bit | 38 bit || signed bit | total count | total rt |——————————————当一次调用过来来的时候,假设传过来的 RT 是n,那么每次累加的数不是1,也不是n,而是1 * 2^38 + n这么设计主要有一下几个考虑:count是每调用一次加一,RT 是每调用一次加N的操作,如果 count 在高位的话,每次加一,实际是一个固定的常数,而如果rt放在高位的话,每次都加的内容不一样,所以每次都要计算一次;25 bits 最多可以表示 2^25 = 33554432 个数,所以1分钟以内对于方法调用的统计这种场景来说肯定是不会溢出的;RT 可能会比较大,所以低位给了38bits, 2^38=274877906944 基本也是不可能超的。如果真的overflow了怎么办? 由于前面分析过,几乎不可能overflow,因此这个问题暂时没有解决,留待后面在解决。无锁 BucketCounter在之前的代码中,BucketCounter 需要确保在多线程并发访问下保证只有一个线程对 Bucket 进行更新,因此使用了一个对象锁,在最新版本中,对 BucketCounter 进行了重新实现,去掉了原来实现中的锁,仅通过 AtomicReference 和 CAS 进行操作,本地测试发现性能又提升了15%左右。四、全面的指标统计Dubbo Metrics 全面支持了从操作系统,JVM,中间件,再到应用层面的各级指标,并且对统一了各种命名指标,可以做到开箱即用,并支持通过配置随时开启和关闭某类指标的收集。目前支持的指标,主要包括:操作系统支持Linux/Windows/Mac,包含CPU/Load/Disk/Net Traffic/TCP。JVM支持classload, GC次数和时间, 文件句柄,young/old区占用,线程状态, 堆外内存,编译时间,部分指标支持自动差值计算。中间件Tomcat: 请求数,失败次数,处理时间,发送接收字节数,线程池活跃线程数等;Druid: SQL 执行次数,错误数,执行时间,影响行数等;Nginx: 接受,活跃连接数,读,写请求数,排队数,请求QPS,平均 RT 等;更详细的指标可以参见这里, 后续会陆续添加对Dubbo/Nacos/Sentinel/Fescar等的支持。五、REST支持Dubbo Metrics 提供了基于 JAX-RS 的 REST 接口暴露,可以轻松查询内部的各种指标,既可以独立启动HTTP Server提供服务(默认提供了一个基于Jersey+ sun Http server的简单实现),也可以嵌入已有的HTTP Server进行暴露指标。具体的接口可以参考这里:https://github.com/dubbo/metrics/wiki/query-from-http六、单机数据落盘数据如果完全存在内存里面,可能会出现因为拉取失败,或者应用本身抖动,导致数据丢失的可能。为了解决该问题,metrics引入了数据落盘的模块,提供了日志方式和二进制方式两种方式的落盘。日志方式默认通过JSON方式进行输出,可以通过日志组件进行拉取和聚合,文件的可读性也比较强,但是无法查询历史数据;二进制方式则提供了一种更加紧凑的存储,同时支持了对历史数据进行查询。目前内部使用的是这种方式。七、易用性和稳定性优化将埋点的API和实现进行拆分,方便对接不用的实现,而用户无需关注;支持注解方式进行埋点;借鉴了日志框架的设计,获取度量器更加方便;增加Compass/FastCompass,方便业务进行常用的埋点,例如统计qps,rt,成功率,错误数等等指标;Spring-boot-starter,即将开源,敬请期待;支持指标自动清理,防止长期不用的指标占用内存;URL 指标收敛,最大值保护,防止维度爆炸,错误统计导致的内存。如何使用使用方式很简单,和日志框架的Logger获取方式一致。Counter hello = MetricManager.getCounter(“test”, MetricName.build(“test.my.counter”));hello.inc();支持的度量器包括:Counter(计数器)Meter(吞吐率度量器)Histogram(直方分布度量器)Gauge(瞬态值度量器)Timer(吞吐率和响应时间分布度量器)Compass(吞吐率, 响应时间分布, 成功率和错误码度量器)FastCompass(一种快速高效统计吞吐率,平均响应时间,成功率和错误码的度量器)ClusterHistogram(集群分位数度量器)后续规划提供Spring-boot starter支持Prometheus,Spring MicroMeter对接Dubbo,Dubbo 中的数据统计实现替换为 Dubbo Metrics在 Dubbo Admin 上展示各种 metrics 数据对接 Dubbo 生态中的其他组件,如Nacos, Sentinel, Fescar等参考资料Dubbo Metrics @Github:https://github.com/dubbo/metricsWiki:https://github.com/dubbo/metrics/wiki (持续更新)本文作者:望陶,GitHub ID @ralf0131,Apache Dubbo PPMC Member,Apache Tomcat PMCMember,阿里巴巴技术专家。子观,GitHub ID @min,Apache Dubbo Commiter,阿里巴巴高级开发工程师,负责 Dubbo Admin 和 Dubbo Metrics 项目的开发和社区维护。本文作者:中间件小哥阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

March 15, 2019 · 2 min · jiezi

API网关 | 从0开始构建SpringCloud微服务(12)

照例附上项目github链接本项目实现的是将一个简单的天气预报系统一步一步改造成一个SpringCloud微服务系统的过程。本章主要讲解API网关。项目存在的问题在目前的项目中我们构建了许多的API微服务,当第三方服务想要调用我们的API微服务的时候是通过微服务的名称进行调用的,没有一个统一的入口。使用API网关的意义集合多个API统一API入口API网关就是为了统一服务的入口,可以方便地实现对平台的众多服务接口进行管控,对访问服务的身份进行验证,防止数据的篡改等。我们的一个微服务可能需要依赖多个API微服务,API网关可以在中间做一个api的聚集。那么我们的微服务只需要统一地经过API网关就可以了(只需要依赖于API网关就可以了),不用关心各种API微服务的细节,需要调用的API微服务由API网关来进行转发。使用API网关的利与弊API网关的利避免将内部信息泄露给外部为微服务添加额外的安全层支持混合通信协议降低构建微服务的复杂性微服务模拟与虚拟化API网关的弊在架构上需要额外考虑更多编排与管理路由逻辑配置要进行统一的管理可能引发单点故障API网关的实现Zuul:SpringCloud提供的API网关的组件Zuul是Netflix开源的微服务网关,他可以和Eureka,Ribbon,Hystrix等组件配合使用。Zuul组件的核心是一系列的过滤器,这些过滤器可以完成以下功能:身份认证和安全: 识别每一个资源的验证要求,并拒绝那些不符的请求审查与监控:动态路由:动态将请求路由到不同后端集群压力测试:逐渐增加指向集群的流量,以了解性能负载分配:为每一种负载类型分配对应容量,并弃用超出限定值的请求静态响应处理:边缘位置进行响应,避免转发到内部集群多区域弹性:跨域AWS Region进行请求路由,旨在实现ELB(ElasticLoad Balancing)使用多样化Spring Cloud对Zuul进行了整合和增强。目前,Zuul使用的默认是Apache的HTTP Client,也可以使用Rest Client,可以设置ribbon.restclient.enabled=true。集成Zuul在原来项目的基础上,创建一个msa-weather-eureka-client-zuul作为API网关。添加依赖<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-zuul</artifactId></dependency>添加注解添加@EnableZuulProxy启用Zuul的代理功能。@SpringBootApplication@EnableDiscoveryClient@EnableZuulProxypublic class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); }}添加配置这里配置的是设置路由的url。当有人访问 /city/ 路径的时候,就对访问这个路径的请求做一个转发,转发到msa-weather-city-eureka服务中去,同理,当有人访问 /data/ 路径的时候,API网关也会将这个请求转发到msa-weather-data-eureka服务中去。zuul: routes: city: path: /city/** service-id: msa-weather-city-eureka data: path: /data/** service-id: msa-weather-data-eureka微服务依赖API网关原来天气预报微服务依赖了城市数据API微服务,以及天气数据API微服务,这里我们对其进行修改让其依赖API网关,让API网关处理天气预报微服务其他两个微服务的调用。首先删去原来调用其他两个API微服务的Feign客户端——CityClient以及WeatherDataClient,创建一个新的Feign客户端——DataClient。package com.demo.service;import java.util.List;import org.springframework.cloud.netflix.feign.FeignClient;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestMethod;import org.springframework.web.bind.annotation.RequestParam;import com.demo.vo.City;import com.demo.vo.WeatherResponse;@FeignClient(“msa-weather-eureka-client-zuul”)public interface DataClient { //获取城市列表 @RequestMapping(value="/city/cities",method=RequestMethod.GET) List<City> listCity()throws Exception; //通过城市Id查询对应城市的天气数据 @RequestMapping(value="/data/weather/cityId",method=RequestMethod.GET) WeatherResponse getDataByCityId(@RequestParam(“cityId”)String cityId);}在service中使用该客户端对API网关进行调用,API网关根据我们请求的路径去调用相应的微服务。@Servicepublic class WeatherReportServiceImpl implements WeatherReportService{ //@Autowired //private WeatherDataClient weatherDataClient; @Autowired private DataClient dataClient; //根据城市Id获取天气预报的数据 @Override public Weather getDataByCityId(String cityId) { //由天气数据API微服务来提供根据城市Id查询对应城市的天气的功能 WeatherResponse resp=dataClient.getDataByCityId(cityId); Weather data=resp.getData(); return data; }}在controller也是同理。 //传入城市Id得到对应城市的天气数据 @GetMapping("/cityId/{cityId}") public Weather getReportByCityId(@PathVariable(“cityId”)String cityId,Model model)throws Exception{ //获取城市Id列表 //由城市数据API微服务来提供数据 List<City>cityList=null; try { cityList=dataClient.listCity(); }catch (Exception e) { logger.error(“Exception!",e); } Weather weather=weatherReportService.getDataByCityId(cityId); return weather; }测试结果 ...

March 14, 2019 · 1 min · jiezi

微服务的消费 | 从0开始构建SpringCloud微服务(11)

照例附上项目github链接本项目实现的是将一个简单的天气预报系统一步一步改造成一个SpringCloud微服务系统的过程。本章主要讲解微服务的消费。微服务的消费模式微服务的消费模式主要有:服务直连模式客户端发现模式服务端发现模式下面我们主要讲解客户端发现模式,以及服务端发现模式。客户端发现模式客户端发现模式的具体流程如下:1)服务实例启动后,将自己的位置信息提交到服务注册表中。2)客户端从服务注册表进行查询,来获取可用的服务实例。3)客户端自行使用负载均衡算法从多个服务实例中选择出一个。服务端发现模式服务端发现模式与客户端发现模式的区别在于:服务端发现模式的负载均衡由负载均衡器(独立的)来实现,客户端不需要关心具体调用的是哪一个服务。微服务的消费者下面我们价格介绍一些常见的微服务的消费者。HttpClientHttpClient是常见的微服务的消费者,它是Apache Jakarta Common 下的子项目,可以用来提供高效的、最新的、功能丰富的支持 HTTP 协议的客户端编程工具包,并且它支持 HTTP协议最新的版本和建议。RibbonRibbon是一个基于HTTP和TCP的客户端负载均衡工具,它基于Netflix Ribbon实现。通过Spring Cloud的封装,可以让我们轻松地将面向服务的REST模版请求自动转换成客户端负载均衡的服务调用。Ribbon常与Eureka结合使用。在典型的分布式部署中,Eureka为所有的微服务提供服务注册,Ribbon提供服务消费的客户端,含有许多负载均衡的算法。FeignFeign是一个声明式的Web Service客户端,它的目的就是让Web Service调用更加简单。Feign提供了HTTP请求的模板,通过编写简单的接口和插入注解,就可以定义好HTTP请求的参数、格式、地址等信息。而Feign则会完全代理HTTP请求,我们只需要像调用方法一样调用它就可以完成服务请求及相关处理。Feign整合了Ribbon和Hystrix(关于Hystrix我们后面再讲),可以让我们不再需要显式地使用这两个组件。总起来说,Feign具有如下特性:可插拔的注解支持,包括Feign注解和JAX-RS注解;支持可插拔的HTTP编码器和解码器;支持Hystrix和它的Fallback;支持Ribbon的负载均衡;支持HTTP请求和响应的压缩。集成Feign本节我们主要讲解如何集成Feign,实现微服务的消费功能。添加配置pom.xml配置文件,添加如下依赖。 <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-feign</artifactId> </dependency> application.yml文件,添加如下配置服务的名称与工程的名称一致,并且设置请求一个服务的connect与read的超时时间。spring: application: name: micro-weather-eureka-client-feigneureka: client: service-url: defaultZone: http://localhost:8761/eureka/feign: client: config: feignName: connectTimeout: 5000 readTimeout: 5000添加注解添加@EnableFeignClients注解,启动Feign功能。package com.demo;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.boot.web.servlet.ServletComponentScan;import org.springframework.cloud.client.discovery.EnableDiscoveryClient;import org.springframework.cloud.netflix.feign.EnableFeignClients;import org.springframework.scheduling.annotation.EnableScheduling;//@ServletComponentScan(basePackages=“com.demo.web.servlet”)@SpringBootApplication@EnableDiscoveryClient@EnableScheduling@EnableFeignClientspublic class Sifoudemo02Application { public static void main(String[] args) { SpringApplication.run(Sifoudemo02Application.class, args); }}创建Feign客户端创建一个Feign客户端,通过其CityClient的接口,可以调用城市数据API微服务mas-weather-city-eureka中的cities接口,返回城市列表。package com.demo.service;import java.util.List;import org.springframework.cloud.netflix.feign.FeignClient;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestMethod;import com.demo.vo.City;//创建一个Feign客户端 获取城市数据微服务中的城市列表信息@FeignClient(“msa-weather-city-eureka”)public interface CityClient { @RequestMapping(value="/cities",method=RequestMethod.GET) List<City> listCity()throws Exception;}使用Feign客户端@RestControllerpublic class CityController{ @Autowired private CityClient cityClient; @GetMapping("/cities") public List<City> listCity(){ List<City> cityList=cityClient.listCity(); return cityList; }}测试先启动Eureka服务端,然后启动被请求的微服务——城市数据API微服务,最后启动Feign客户端。 ...

March 11, 2019 · 1 min · jiezi

服务的高可用 | 从0开始构建SpringCloud微服务(10)

照例附上项目github链接本项目实现的是将一个简单的天气预报系统一步一步改造成一个SpringCloud微服务系统的过程。本章主要讲解实现服务的高可用。什么是高可用高可用HA(High Availability)是分布式系统架构设计中必须考虑的因素之一,它通常是指,通过设计减少系统不能提供服务的时间。假设系统一直能够提供服务,我们说系统的可用性是100%。如果系统每运行100个时间单位,会有1个时间单位无法提供服务,我们说系统的可用性是99%。很多公司的高可用目标是4个9,也就是99.99%,这就意味着,系统的年停机时间为8.76个小时。如何保障系统的高可用我们都知道,单点是系统高可用的大敌,单点往往是系统高可用最大的风险和敌人,应该尽量在系统设计的过程中避免单点。方法论上,高可用保证的原则是“集群化”,或者叫“冗余”:只有一个单点,挂了服务会受影响;如果有冗余备份,挂了还有其他backup能够顶上。保证系统高可用,架构设计的核心准则是:冗余。有了冗余之后,还不够,每次出现故障需要人工介入恢复势必会增加系统的不可服务实践。所以,又往往是通过“自动故障转移”来实现系统的高可用。下面我们重点讲解通过集成Eureka设置多节点的注册中心,从而实现服务的高可用。实现服务的高可用集成Eureka我们需要将前面拆分好的四个微服务:城市数据API微服务,天气数据采集微服务,天气数据API微服务,天气预报微服务,集成Eureka,实现服务的发现与注册功能。主要的操作步骤为:添加pom.xml配置添加application.yml配置添加注解至于如何进行添加,我在上一章进行了详细的讲述,这里就不展开了。启动服务我们将各个服务生成jar包,并通过命令行指定端口进行启动,启动命令如下:java -jar micro-weather-eureka-server-1.0.0.jar –server.port=8761测试结果从上图可以看到我们有4个微服务,每个微服务启动了两个实例。每个实例已经集成了Eureka客户端,并且在Eureka服务器中完成了注册。各个微服务之间可以通过应用的名称相互访问,每个微服务启动了多个实例,这样当我们的任何一个实例挂掉了,因为在其他节点有注册,所以还可以提供服务,如此一来我们便实现了服务的高可用!

March 11, 2019 · 1 min · jiezi

浅析阿里分布式事务组件 fescar 2pc 的设计思想

二阶段提交协议的由来X/Open 组织提出了分布式事务处理的规范 DTP 模型(Distributed Transaction Processing),该模型中主要定义了三个基本组件,分别是应用程序(Application Program ,简称AP):用于定义事务边界(即定义事务的开始和结束),并且在事务边界内对资源进行操作。资源管理器(Resource Manager,简称RM):如数据库、文件系统等,并提供访问资源的方式。事务管理器(Transaction Manager ,简称TM):负责分配事务唯一标识,监控事务的执行进度,并负责事务的提交、回滚等。一般,我们称 TM 为事务的协调者,而称 RM 为事务的参与者。TM 与 RM 之间的通信接口,则由 XA 规范来约定。在 DTP 模型的基础上,才引出了二阶段提交协议来处理分布式事务。二阶段提交基本算法前提二阶段提交协议能够正确运转,需要具备以下前提条件:存在一个协调者,与多个参与者,且协调者与参与者之间可以进行网络通信参与者节点采用预写式日志,日志保存在可靠的存储设备上,即使参与者损坏,不会导致日志数据的消失参与者节点不会永久性损坏,即使后仍然可以恢复实际上,条件2和3所要求的,现今绝大多数关系型数据库都能满足。基本算法第一阶段协调者节点向所有参与者节点询问是否可以执行提交操作,并开始等待各参与者节点的响应。参与者节点执行询问发起为止的所有事务操作,并将Undo信息和Redo信息写入日志。各参与者节点响应协调者节点发起的询问。如果参与者节点的事务操作实际执行成功,则它返回一个"同意"消息;如果参与者节点的事务操作实际执行失败,则它返回一个"中止"消息。第二阶段当协调者节点从所有参与者节点获得的相应消息都为"同意"时:协调者节点向所有参与者节点发出"正式提交"的请求。参与者节点正式完成操作,并释放在整个事务期间内占用的资源。参与者节点向协调者节点发送"完成"消息。协调者节点收到所有参与者节点反馈的"完成"消息后,完成事务。如下图所示:如果任一参与者节点在第一阶段返回的响应消息为"终止",或者 协调者节点在第一阶段的询问超时之前无法获取所有参与者节点的响应消息时:协调者节点向所有参与者节点发出"回滚操作"的请求。参与者节点利用之前写入的Undo信息执行回滚,并释放在整个事务期间内占用的资源。参与者节点向协调者节点发送"回滚完成"消息。协调者节点收到所有参与者节点反馈的"回滚完成"消息后,取消事务。如下图所示:缺陷分析二阶段提交协议除了协议本身具有的局限性之外,如果我们把以下情况也考虑在内:协调者宕机参与者宕机网络闪断(脑裂)那么二阶段提交协议实际上是存在很多问题的协议本身的缺陷协议本身的缺陷是指,在协议正常运行的情况下,无论全局事务最终是被提交还是被回滚,依然存在的问题,而暂不考虑参与者或者协调者宕机,或者脑裂的情况。性能问题参与者的本地事务开启后,直到它接收到协调者的 commit 或 rollback 命令后,它才会提交或回滚本地事务,并且释放由于事务的存在而锁定的资源。不幸的是,一个参与者收到协调者的 commit 或者 rollback 的前提是:协调者收到了所有参与者在一阶段的回复。如果说,协调者一阶段询问多个参与者采用的是顺序询问的方式,那么一个参与者最快也要等到协调者询问完所有其它的参与者后才会被通知提交或回滚,在协调者未询问完成之前,这个参与者将保持占用相关的事务资源。即使,协调者一阶段询问多个参与者采用的是并发询问的方式,那么一个参与者等待收到协调者的提交或者回滚通知的时间,将取决于在一阶段过程中,响应协调者最慢的那个参与者的响应时间。无论是哪一种情况,参与者都将在整个一阶段持续的时间里,占用住相关的资源,参与者事务的处理时间增加。若此时在参与者身上有其它事务正在进行,那么其它事务有可能因为与这个延迟的事务有冲突,而被阻塞,这些被阻塞的事务,进而会引起其它事务的阻塞。总而言之,整体事务的平均耗时增加了,整体事务的吞吐量也降低了。这会使得整个应用系统的延迟变高,吞吐量降低,可扩展性降低(当参与者变多的时候,延迟可能更严重)。总的来说,二阶段提交协议,不是一个高效的协议,会带来性能上的损失。全局事务隔离性的问题全局事务的隔离性与单机事务的隔离性是不同的。当我们在单机事务中提到不允许脏读时,那么意味着在事务未提交之前,它对数据造成的影响不应该对其它事务可见。当我们在全局事务中提到不允许脏读时,意味着,在全局事务未提交之前,它对数据造成的影响不应该对其它事务可见。在二阶段提交协议中,当在第二阶段所有的参与者都成功执行 commit 或者 rollback 之后,全局事务才算结束。但第二阶段存在这样的中间状态:即部分参与者已执行 commit 或者 rollback,而其它参与者还未执行 commit 或者 rollback。此刻,已经执行 commit 或者 rollback 的参与者,它对它本地数据的影响,对其它全局事务是可见的,即存在脏读的风险。对于这种情况,二阶段协议并没有任何机制来保证全局事务的隔离性,无法做到“读已提交”这样的隔离级别。协调者宕机如果在第一阶段,协调者发生了宕机,那么因为所有参与者无法再接收到协调者第二阶段的 commit 或者 rollback 命令,所以他们会阻塞下去,本地事务无法结束,如果协调者在第二阶段发生了宕机,那么可能存在部分参与者接收到了 commit/rollback 命令,而部分没有,因此这部分没有接收到命令的参与者也会一直阻塞下去。协调者宕机属于单点问题,可以通过另选一个协调者的方式来解决,但这只能保证后续的全局事务正常运行。而因为之前协调者宕机而造成的参与者阻塞则无法避免。如果这个新选择的协调者也宕机了,那么一样会带来阻塞的问题。参与者宕机如果在第一阶段,某个参与者发生了宕机,那么会导致协调者一直等待这个参与者的响应,进而导致其它参与者也进入阻塞状态,全局事务无法结束。如果在第二阶段,协调者发起 commit 操作时,某个参与者发生了宕机,那么全局事务已经执行了 commit 的参与者的数据已经落盘,而宕机的参与者可能还没落盘,当参与者恢复过来的时候,就会产生全局数据不一致的问题。网络问题-脑裂当网络闪断发生在第一阶段时,可能会有部分参与者进入阻塞状态,全局事务无法结束。当发生在第二阶段时,可能发生部分参与者执行了 commit 而部分参与者未执行 commit,从而导致全局数据不一致的问题。三阶段提交在二阶段提交中,当协调者宕机的时候,无论是在第一阶段还是在第二阶段发生宕机,参与者都会因为等待协调者的命令而进入阻塞状态,从而导致全局事务无法继续进行。因此,如果在参与者中引入超时机制,即,当指定时间过去之后,参与者自行提交或者回滚。但是,参与者应该进行提交还是回滚呢?悲观的做法是,统一都回滚。但事情往往没那么简单。当第一阶段,协调者宕机时,那么所有被阻塞的参与者选择超时后回滚事务是最明智的做法,因为还未进入第二阶段,所以参与者都不会接收到提交或者回滚的请求,当前这个事务是无法继续进行提交的,因为参与者不知道其它参与者的执行情况,所以统一回滚,结束分布式事务。在二阶段提交协议中的第二阶段,当协调者宕机后,由于参与者无法知道协调者在宕机前给其他参与者发了什么命令,进入了第二阶段,全局事务要么提交要么回滚,参与者如果引入超时机制,那么它应该在超时之后提交还是回滚呢,似乎怎么样都不是正确的做法。执行回滚,太保守,执行提交,太激进。如果在二阶段提交协议中,在第一阶段和第二阶段中间再引入一个阶段,如果全局事务度过了中间这个阶段,那么在第三阶段,参与者就可以认为此刻进行提交的成功率会更大。但这难道不是治标不治本吗,当进入第三阶段,全局事务需要进行回滚时候,如果协调者宕机,那么参与者超时之后自行进行提交事务,就会造成全局事务的数据不一致。再考虑参与者宕机的情况下,协调者应该在超时之后,对全局事务进行回滚。总结起来,三阶段提交主要在二阶段提交的基础上,为了解决参与者和协调者宕机的问题,而引入了超时机制,并因为超时机制,附带引入中间这一层。并且,三阶段提交并没有解决二阶段提交的存在的脑裂的问题。总而言之,二阶段和三阶段提交都无法完美地解决分布式事务的问题。关于三阶段提交更详细的算法和步骤,可以参考我的另外一篇文章《分布式事务概览》fescar二阶段提交fescar 是阿里最近开源的一个关于分布式事务的处理组件,它的商业版是阿里云上的 GTS。在其官方wiki上,我们可以看到,它对XA 二阶段提交思考与改进。在我们上面提到的参与者中,这个参与者往往是数据库本身,在 DTP 模型中,往往称之为 RM,即资源管理器。fescar 的二阶段提交模型,也是在 DTP 模型的基础上构建。RM逻辑不与数据库绑定fescar 2PC 与 XA 2PC 的第一个不同是,fescar 把 RM 这一层的逻辑放在了 SDK 层面,而传统的 XA 2PC,RM的逻辑其实就在数据库本身。fescar 这样做的好处是,把提交与回滚的逻辑放在了 SDK 层,从而不必要求底层的数据库必须对 XA 协议进行支持。对于业务来说,业务层也不需要为本地事务和分布式事务两类不同场景来适配两套不同的数据库驱动。基于我们先前对 XA 2PC 讨论,XA 2PC 存在参与者宕机的情况,而 fescar 的 2PC 模型中,参与者实际上是 SDK。参与者宕机这个问题之所以在 XA 2PC 中是个大问题,主要是因为 XA 中,分支事务是有状态的,即它是跟会话绑定在一起的,无法跨连接跨会话对一个分支事务进行操作,因此在 XA 2PC 中参与者一旦宕机,分支事务就已经无法再进行恢复。fescar 2PC 中,参与者实际上是SDK,而SDK是可以做高可用设计的。并且,在其第一阶段,分支事务实际上已经是被提交了的,后续的全局上的提交和回滚,实际上是操作数据的镜像,全局事务的提交会异步清理 undo_log,回滚则会利用保存好的数据镜像,进行恢复。fescar 的 2PC 中,实际上是利用了 TCC 规范的无状态的理念。因为全局事务的执行、提交和回滚这几个操作间不依赖于公共状态,比如说数据库连接。所以参与者实际上是可以成为无状态的集群的。也就是说,在 fescar 2PC 中,协调者如果发现参与者宕机或者超时,那么它可以委托其他的参与者去做。第二阶段非阻塞化fescar 2PC 的第一阶段中,利用 SDK 中的 JDBC 数据源代理通过对业务 SQL 的解析,把业务数据在更新前后的数据镜像组织成回滚日志,利用本地事务 的 ACID 特性,将业务数据的更新和回滚日志的写入在同一个 本地事务 中提交。这就意味着在阻塞在第一阶段过后就会结束,减少了对数据库数据和资源的锁定时间,明显效率会变更高。根据 fescar 官方的说法,一个正常运行的业务,大概率是 90% 以上的事务最终应该是成功提交的,因此可以在第一阶段就将本地事务提交呢,这样 90% 以上的情况下,可以省去第二阶段持锁的时间,整体提高效率。fescar 的这个设计,直接优化了 XA 2PC 协议本身的性能缺陷。协调者的高可用XA 2PC 中存在协调者宕机的情况,而 fescar 的整体组织上,是分为 server 层和 SDK 层的,server 层作为事务的协调者,fescar 的话术中称协调者为 TC(Transaction Coordinator ),称 SDK 为 TM(Transaction Manager)。截止至这篇文章发表前,fescar 的server层高可用还未实现,依据其官方的蓝图,它可能会采用集群内自行协商的方案,也可能直接借鉴高可用KV系统。自行实现集群内高可用方案,可能需要引进一套分布式一致性协议,例如raft,我认为这是最理想的方式。而直接利用高可用KV系统,例如 redis cluster,则会显得系统太臃肿,但实现成本低。事务的隔离性XA 2PC 是没有机制去支持全局事务隔离级别的,fescar 是提供全局事务的隔离性的,它把全局锁保存在了 server 层。全局事务隔离级别如果太高,性能会有很大的损耗。目前的隔离界别默认是读未提交,如果需要读已提交或者更高的级别,就会涉及到全局锁,则意味着事务的并发性会受影响。应用层业务层应该选择合适的事务隔离级别。脑裂的问题仍然没有完美解决无论是 XA 还是 fescar,都未解决上述提到的脑裂的问题。脑裂的问题主要影响了全局事务的最后的提交和回滚阶段。没有完美的分布式事务解决方案,即使是 fescar 或者 GTS,它们也必然需要人工介入。但脑裂问题是小概率事件,并且不是致命性错误,可以先通过重试的方法来解决,如果不行,可以收集必要的事务信息,由运维介入,以自动或者非自动的方式,恢复全局事务。参考资料 维基百科:二阶段提交《2PC之踵?是时候升级二阶段提交协议了》 by Tim Yang《分布式事务概览》by beanlam fescar wiki ...

March 11, 2019 · 1 min · jiezi

轻量级高性能PHP框架ycroute

YCRoutegithub: https://github.com/caohao-php…目录框架介绍运行环境代码结构路由配置过滤验签控制层加载器模型层数据交互dao层(可选)Redis缓存操作数据库操作配置加载公共类加载公共函数日志模块视图层RPC 介绍 - 像调用本地函数一样调用远程函数RPC ServerRPC ClientRPC 并行调用附录 - Core_Model 中的辅助极速开发函数框架介绍框架由3层架构构成,Controller、Model、View 以及1个可选的Dao层,支持PHP7,优点如下:1、框架层次分明,灵活可扩展至4层架构、使用简洁(开箱即用)、功能强大。2、基于 yaf 路由和 ycdatabase 框架,两者都是C语言扩展,保证了性能。3、ycdatabase 是强大的数据库 ORM 框架,功能强大,安全可靠,支持便捷的主从配置,支持稳定、强大的数据库连接池。具体参考 https://blog.csdn.net/caohao0...4、支持Redis代理,简便的主从配置,支持稳定的redis连接池。具体参考:https://blog.csdn.net/caohao0…5、强大的日志模块、异常捕获模块,便捷高效的类库、共用函数加载模块6、基于PHP7,代码缓存opcache。运行环境运行环境: PHP 7 依赖扩展: yaf 、 ycdatabase 扩展 创建日志目录:/data/app/logs ,目录权限为 php 项目可写。 yaf 介绍以及安装: https://github.com/laruence/yafycdatabase 介绍以及安装: https://github.com/caohao-php…代码结构———————————————— |— system //框架系统代码|— conf //yaf配置路径 |— application //业务代码 |—– config //配置目录 |—– controller //控制器目录 |—— User.php //User控制器 |—– core //框架基类目录 |—– daos //DAO层目录(可选) |—– errors //错误页目录 |—– helpers //公共函数目录 |—– library //公共类库目录 |—– models //模型层目录 |—– plugins //yaf路由插件目录,路由前后钩子,(接口验签在这里) |—– third //第三方类库 |—– views //视图层路由配置路由配置位于: framework/conf/application.ini示例: http://localhost/index.php?c=…详细参考文档: http://php.net/manual/zh/book…控制器由参数c决定,动作有 m 决定。参数方式描述cGET控制器,路由到 /application/controller/User.php 文件mGET入口方法, User.php 里面的 getUserInfoAction 方法程序将被路由到 framework/application/controllers/User.php文件的 UserController::getUserInfoAction方法,其它路由细节参考Yaf框架class UserController extends Core_Controller { public function getUserInfoAction() { } } 过滤验签framework/application/plugins/Filter.php , 在 auth 中写入验签方法,所有接口都会在这里校验, 所有GET、POST等参数放在 $this->params 里。class FilterPlugin extends Yaf_Plugin_Abstract { var $params; //路由之前调用 public function routerStartUp ( Yaf_Request_Abstract $request , Yaf_Response_Abstract $response) { $this->params = & $request->getParams(); $this->auth(); } //验签过程 protected function auth() { //在这里写你的验签逻辑 } …}控制层所有控制器位于:framework/application/controllers 目录,所有控制器继承自Core_Controller方法,里面主要获取GET/POST参数,以及返回数据的处理,Core_Controller继承自 Yaf_Controller_Abstract, init方法会被自动调用,更多细节参考 Yaf 框架控制器。class UserController extends Core_Controller { public function init() { parent::init(); //必须 $this->user_model = Loader::model(‘UserinfoModel’); //模型层 $this->util_log = Logger::get_instance(‘user_log’); //日志 Loader::helper(‘common_helper’); //公共函数 $this->sample = Loader::library(‘Sample’); //加载类库,加载的就是 framework/library/Sample.php 里的Sample类 } //获取用户信息接口 public function getUserInfoAction() { $userId = $this->params[‘userid’]; $token = $this->params[’token’]; if (empty($userId)) { $this->response_error(10000017, “user_id is empty”); } if (empty($token)) { $this->response_error(10000016, “token is empty”); } $userInfo = $this->user_model->getUserinfoByUserid($userId); if (empty($userInfo)) { $this->response_error(10000023, “未找到该用户”); } if (empty($token) || $token != $userInfo[’token’]) { $this->response_error(10000024, “token 校验失败”); } $this->response_success($userInfo); }}通过 $this->response_error(10000017, ‘user_id is empty’); 返回错误结果 { “errno”:10000017, “errmsg”:“user_id is empty”}通过 $this->response_success($result); 返回JSON格式成功结果,格式如下:{ “errno”:0, “union”:"", “amount”:0, “session_key”:“ZqwsC+Spy4C31ThvqkhOPg==”, “open_id”:“oXtwn4_mrS4zIxtSeV0yVT2sAuRo”, “nickname”:“凉之渡”, “last_login_time”:“2018-09-04 18:53:06”, “regist_time”:“2018-06-29 22:03:38”, “user_id”:6842811, “token”:“c9bea5dee1f49488e2b4b4645ff3717e”, “updatetime”:“2018-09-04 18:53:06”, “avatar_url”:“https://wx.qlogo.cn/mmopen/vi_32/xfxHib91BictV8T4ibRQAibD10DfoNpzpB1LBqZvRrz0icPkN0gdibZg62EPJL3KE1Y5wkPDRAhibibymnQCFgBM2nuiavA/132", “city”:“Guangzhou”, “province”:“Guangdong”, “country”:“China”, “appid”:“wx385863ba15f573b6”, “gender”:1, “form_id”:”"}加载器通过 Loader 加载器可以加载模型层,公共类库,公共函数,数据库,缓存等对象, Logger 为日志类。模型层framework/application/models/Userinfo.php ,模型层,你可以继承自Core_Model, 也可以不用,Core_Model 中封装了许多常用SQL操作。最后一章会介绍各个函数用法。通过 $this->user_model = Loader::model(‘UserinfoModel’) 加载模型层,模型层与数据库打交道。class UserinfoModel extends Core_Model { public function construct() { $this->db = Loader::database(‘default’); $this->util_log = Logger::get_instance(‘userinfo_log’); } function register_user($appid, $userid, $open_id, $session_key) { $data = array(); $data[‘appid’] = $appid; $data[‘user_id’] = $userid; $data[‘open_id’] = $open_id; $data[‘session_key’] = $session_key; $data[’last_login_time’] = $data[‘regist_time’] = date(‘Y-m-d H:i:s’, time()); $data[’token’] = md5(TOKEN_GENERATE_KEY . time() . $userid . $session_key); $ret = $this->db->insert(“user_info”, $data); if ($ret != -1) { return $data[’token’]; } else { $this->util_log->LogError(“error to register_user, DATA=[".json_encode($data).”]"); return false; } } …}数据交互Dao层(可选)如果你习惯了4层结构,你可以加载Dao层,作为与数据库交互的层,而model层作为业务层。这个时候 Model 最好不要继承 Core_Model,而由 Dao 层来继承。framework/application/daos/UserinfoDao.php ,数据库交互层,你可以继承自Core_Model, 也可以不用,Core_Model 中封装了许多常用SQL操作。最后一章会介绍各个函数用法。通过 $this->user_dao = Loader::dao(‘UserinfoDao’) 加载dao层,我们建议一个数据库对应一个Dao层。redis 缓存操作加载 redis 缓存: Loader::redis(‘default_master’); 参数为framework/application/config/redis.php 配置键值,如下:$redis_conf[‘default_master’][‘host’] = ‘127.0.0.1’;$redis_conf[‘default_master’][‘port’] = 6379;$redis_conf[‘default_slave’][‘host’] = ‘/tmp/redis_pool.sock’; //unix socket redis连接池,需要配置 openresty-pool/conf/nginx.conf,并开启代理,具体参考 https://blog.csdn.net/caohao0591/article/details/85679702$redis_conf['userinfo']['host'] = ‘127.0.0.1’;$redis_conf[‘userinfo’][‘port’] = 6379;return $redis_conf;使用例子:$redis = Loader::redis(“default_master”); //主写$redis->set(“pre_redis_user${userid}”, serialize($result));$redis->expire(“pre_redis_user${userid}”, 3600);$redis = Loader::redis(“default_slave”); //从读$data = $redis->get(“pre_redis_user${userid}”);连接池配置 openresty-pool/conf/nginx.conf :worker_processes 1; #nginx worker 数量error_log logs/error.log; #指定错误日志文件路径events { worker_connections 1024;}stream { lua_code_cache on; lua_check_client_abort on; server { listen unix:/tmp/redis_pool.sock; content_by_lua_block { local redis_pool = require “redis_pool” pool = redis_pool:new({ip = “127.0.0.1”, port = 6380, auth = “password”}) pool:run() } } server { listen unix:/var/run/mysql_sock/mysql_user_pool.sock; content_by_lua_block { local mysql_pool = require “mysql_pool” local config = {host = “127.0.0.1”, user = “root”, password = “test123123”, database = “userinfo”, timeout = 2000, max_idle_timeout = 10000, pool_size = 200} pool = mysql_pool:new(config) pool:run() } }}数据库操作数据库加载: Loader::database(“default”); 参数为 framework/application/config/database.php 里配置键值,如下:$db[‘default’][‘unix_socket’] = ‘/var/run/mysql_sock/mysql_user_pool.sock’; //unix socket 数据库连接池,具体使用参考 https://blog.csdn.net/caohao0591/article/details/85255704$db['default']['pconnect'] = FALSE;$db[‘default’][‘db_debug’] = TRUE;$db[‘default’][‘char_set’] = ‘utf8’;$db[‘default’][‘dbcollat’] = ‘utf8_general_ci’;$db[‘default’][‘autoinit’] = FALSE;$db[‘payinfo_master’][‘host’] = ‘127.0.0.1’; //地址$db[‘payinfo_master’][‘username’] = ‘root’; //用户名$db[‘payinfo_master’][‘password’] = ’test123123’; //密码$db[‘payinfo_master’][‘dbname’] = ‘payinfo’; //数据库名$db[‘payinfo_master’][‘pconnect’] = FALSE; //是否连接池$db[‘payinfo_master’][‘db_debug’] = TRUE; //debug标志,线上关闭,打开后,异常SQL会显示到页面,不安全,仅在测试时打开,(注意,上线一定得将 db_debug 置为 FALSE,否则一定概率可能暴露数据库配置)$db[‘payinfo_master’][‘char_set’] = ‘utf8’;$db[‘payinfo_master’][‘dbcollat’] = ‘utf8_general_ci’;$db[‘payinfo_master’][‘autoinit’] = FALSE; //自动初始化,Loader的时候就连接,建议关闭$db[‘payinfo_master’][‘port’] = 3306;$db[‘payinfo_slave’][‘host’] = ‘192.168.0.7’;$db[‘payinfo_slave’][‘username’] = ‘root’;$db[‘payinfo_slave’][‘password’] = ’test123123’;$db[‘payinfo_slave’][‘dbname’] = ‘payinfo’;$db[‘payinfo_slave’][‘pconnect’] = FALSE;$db[‘payinfo_slave’][‘db_debug’] = TRUE;$db[‘payinfo_slave’][‘char_set’] = ‘utf8’;$db[‘payinfo_slave’][‘dbcollat’] = ‘utf8_general_ci’;$db[‘payinfo_slave’][‘autoinit’] = FALSE;$db[‘payinfo_slave’][‘port’] = 3306;原生SQL:$data = $this->db->query(“select * from user_info where country=‘China’ limit 3”);查询多条记录:$data = $this->db->get(“user_info”, [‘regist_time[<]’ => ‘2018-06-30 15:48:39’, ‘gender’ => 1, ‘country’ => ‘China’, ‘city[!]’ => null, ‘ORDER’ => [ “user_id”, “regist_time” => “DESC”, “amount” => “ASC” ], ‘LIMIT’ => 10], “user_id,nickname,city”);echo json_encode($data);exit;[ { “nickname”:“芒果”, “user_id”:6818810, “city”:“Yichun” }, { “nickname”:“Smile、格调”, “user_id”:6860814, “city”:“Guangzhou” }, { “nickname”:“Yang”, “user_id”:6870818, “city”:“Hengyang” }, { “nickname”:“凉之渡”, “user_id”:7481824, “city”:“Guangzhou” }]查询单列$data = $this->db->get(“user_info”, [‘regist_time[<]’ => ‘2018-06-30 15:48:39’, ‘gender’ => 1, ‘country’ => ‘China’, ‘city[!]’ => null, ‘ORDER’ => [ “user_id”, “regist_time” => “DESC”, “amount” => “ASC” ], ‘LIMIT’ => 10], “nickname”);echo json_encode($data);exit;[ “芒果”, “Smile、格调”, “Yang”, “凉之渡”]查询单条记录$data = $this->db->get_one(“user_info”, [‘user_id’ => 6818810]);{ “union”:null, “amount”:0, “session_key”:“Et1yjxbEfRqVmCVsYf5qzA==”, “open_id”:“oXtwn4wkPO4FhHmkan097DpFobvA”, “nickname”:“芒果”, “last_login_time”:“2018-10-04 16:01:27”, “regist_time”:“2018-06-29 21:24:45”, “user_id”:6818810, “token”:“5a350bc05bbbd9556f719a0b8cf2a5ed”, “updatetime”:“2018-10-04 16:01:27”, “avatar_url”:“https://wx.qlogo.cn/mmopen/vi_32/DYAIOgq83epqg7FwyBUGd5xMXxLQXgW2TDEBhnNjPVla8GmKiccP0pFiaLK1BGpAJDMiaoyGHR9Nib2icIX9Na4Or0g/132", “city”:“Yichun”, “province”:“Jiangxi”, “country”:“China”, “appid”:“wx385863ba15f573b6”, “gender”:1, “form_id”:”" }插入数据function register_user($appid, $userid, $open_id, $session_key) { $data = array(); $data[‘appid’] = $appid; $data[‘user_id’] = $userid; $data[‘open_id’] = $open_id; $data[‘session_key’] = $session_key; $data[’last_login_time’] = $data[‘regist_time’] = date(‘Y-m-d H:i:s’, time()); $data[’token’] = md5(TOKEN_GENERATE_KEY . time() . $userid . $session_key); $ret = $this->db->insert(“user_info”, $data); if ($ret != -1) { return $data[’token’]; } else { $this->util_log->LogError(“error to register_user, DATA=[".json_encode($data).”]"); return false; }}更新数据function update_user($userid, $update_data) { $redis = Loader::redis(“userinfo”); $redis->del(“pre_redis_user_info” . $userid); $ret = $this->db->update(“user_info”, [“user_id” => $userid], $update_data); if ($ret != -1) { return true; } else { $this->util_log->LogError(“error to update_user, DATA=[".json_encode($update_data).”]"); return false; }}删除操作$ret = $this->db->delete(“user_info”, [“user_id” => 7339820]);更多操作参考通过 $this->db->get_ycdb(); 可以获取ycdb句柄进行更多数据库操作, ycdb 的使用教程如下:英文: https://github.com/caohao-php…中文: https://blog.csdn.net/caohao0…配置加载通过 Loader::config(‘xxxxx’); 加载 /application/config/xxxxx.php 的配置。例如:$config = Loader::config(‘config’);var_dump($config);公共类加载所有的公共类库位于superci/application/library目录,但是注意的是, 如果你的类位于library子目录下面,你的类必须用下划线"“分隔;$this->sample = Loader::library(‘Sample’);加载的就是 framework/application/library/Sample.php 中的 Sample类。$this->ip_location = Loader::library(‘Ip_Location’);加载的是 framework/application/library/Ip/Location.php 中的Ip_Location类公共函数所有的公共类库位于superci/application/helpers目录,通过 Loader::helper(‘common_helper’); 方法包含进来。日志日志使用方法如下:$this->util_log = Logger::get_instance(‘userinfo’);$this->util_log->LogInfo(“register success”);$this->util_log->LogError(“not find userinfo”);日志级别:const DEBUG = ‘DEBUG’; /* 级别为 1 , 调试日志, 当 DEBUG = 1 的时候才会打印调试 /const INFO = ‘INFO’; / 级别为 2 , 应用信息记录, 与业务相关, 这里可以添加统计信息 /const NOTICE = ‘NOTICE’; / 级别为 3 , 提示日志, 用户不当操作,或者恶意刷频等行为,比INFO级别高,但是不需要报告*/const WARN = ‘WARN’; /* 级别为 4 , 警告, 应该在这个时候进行一些修复性的工作,系统可以继续运行下去 /const ERROR = ‘ERROR’; / 级别为 5 , 错误, 可以进行一些修复性的工作,但无法确定系统会正常的工作下去,系统在以后的某个阶段, 很可能因为当前的这个问题,导致一个无法修复的错误(例如宕机),但也可能一直工作到停止有不出现严重问题 /const FATAL = ‘FATAL’; / 级别为 6 , 严重错误, 这种错误已经无法修复,并且如果系统继续运行下去的话,可以肯定必然会越来越乱, 这时候采取的最好的措施不是试图将系统状态恢复到正常,而是尽可能的保留有效数据并停止运行 /FATAL和ERROR级别日志文件以 .wf 结尾, DEBUG级别日志文件以.debug结尾,日志目录存放于 /data/app/localhost 下面,localhost为你的项目域名,比如:[root@gzapi: /data/app/logs/localhost]# lsuserinfo.20190211.log userinfo.20190211.log.wf日志格式: [日志级别] [时间] [错误代码] [文件|行数] [ip] [uri] [referer] [cookie] [统计信息] “内容”[INFO] [2019-02-11 18:57:01] - - [218.30.116.8] - - - [] “register success”[ERROR] [2019-02-11 18:57:01] [0] [index.php|23 => | => User.php|35 => Userinfo.php|93] [218.30.116.8] [/index.php?c=user&m=getUserInfo&userid=6842811&token=c9bea5dee1f49488e2b4b4645ff3717e] [] [] - “not find userinfo"VIEW层视图层参考yaf视图渲染那部分, 我没有写案例。RPC 介绍 - 像调用本地函数一样调用远程函数传统web应用弊端传统的Web应用, 一个应用随着业务快速增长, 开发人员的流转, 就会慢慢的进入一个恶性循环, 代码量上只有加法没有了减法. 因为随着系统变复杂, 牵一发就会动全局, 而新来的维护者, 对原有的体系并没有那么多时间给他让他全面掌握. 即使有这么多时间, 要想掌握以前那么多的维护者的思维的结合, 也不是一件容易的事情…那么, 长次以往, 这个系统将会越来越不可维护…. 到一个大型应用进入这个恶性循环, 那么等待他的只有重构了.那么, 能不能对这个系统做解耦呢? 我们已经做了很多解耦了, 数据, 中间件, 业务, 逻辑, 等等, 各种分层. 但到Web应用这块, 还能怎么分呢, MVC我们已经做过了….解决利器—微服务目前比较流行的解决方案是微服务,它可以让我们的系统尽可能快地响应变化,微服务是指开发一个单个小型的但有业务功能的服务,每个服务都有自己的处理和轻量通讯机制,可以部署在单个或多个服务器上。微服务也指一种种松耦合的、有一定的有界上下文的面向服务架构。也就是说,如果每个服务都要同时修改,那么它们就不是微服务,因为它们紧耦合在一起;如果你需要掌握一个服务太多的上下文场景使用条件,那么它就是一个有上下文边界的服务,这个定义来自DDD领域驱动设计。相对于单体架构和SOA,它的主要特点是组件化、松耦合、自治、去中心化,体现在以下几个方面:一组小的服务服务粒度要小,而每个服务是针对一个单一职责的业务能力的封装,专注做好一件事情。独立部署运行和扩展 每个服务能够独立被部署并运行在一个进程内。这种运行和部署方式能够赋予系统灵活的代码组织方式和发布节奏,使得快速交付和应对变化成为可能。独立开发和演化 技术选型灵活,不受遗留系统技术约束。合适的业务问题选择合适的技术可以独立演化。服务与服务之间采取与语言无关的API进行集成。相对单体架构,微服务架构是更面向业务创新的一种架构模式。独立团队和自治 团队对服务的整个生命周期负责,工作在独立的上下文中,自己决策自己治理,而不需要统一的指挥中心。团队和团队之间通过松散的社区部落进行衔接。我们可以看到整个微服务的思想就如我们现在面对信息爆炸、知识爆炸是一样的:通过解耦我们所做的事情,分而治之以减少不必要的损耗,使得整个复杂的系统和组织能够快速的应对变化。微服务的基石—RPC服务框架微服务包含的东西非常多,这里我们只讨论RPC服务框架,ycroute框架基于Yar扩展为我们提供了RPC跨网络的服务调用基础,Yar是一个非常轻量级的RPC框架, 使用非常简单, 对于Server端和Soap使用方法很像,而对于客户端,你可以像调用本地对象的函数一样,调用远程的函数。RPC Server安装环境 (客户端服务端都需要安装)扩展: yar.so 扩展: msgpack.so 可选,一个高效的二进制打包协议,用于客户端和服务端之间包传输,还可以选php、json, 如果要使用Msgpack做为打包协议, 就需要安装这个扩展。服务加载我们在 framework/application/controllers/Rpcserver.php 中将 Model 层作为服务,提供给远程的其它程序调用,RPC Client 便可以像调用本地函数一样,调用远程的服务,如下我们将 UserinfoModel 和 TradeModel 两个模型层提供给远程程序调用。class RpcserverController extends Core_Controller { public function init() { parent::init(); //必须 } //用户信息服务 public function userinfoModelAction() { $user_model = Loader::model(‘UserinfoModel’); //模型层 $yar_server = new Yar_server($user_model); $yar_server->handle(); exit; } //支付服务 public function tradeModelAction() { $trade_model = Loader::model(‘TradeModel’); //模型层 $yar_server = new Yar_server($trade_model); $yar_server->handle(); exit; }}上面一共提供了2个服务,UserinfoModel 和 TradeModel 分别通过http://localhost/index.php?c=… 和 http://localhost/index.php?c=… 来访问,我们来看看 UserinfoModel 一共有哪些服务:从上图可以看到,UserinfoModel 类的所有 public 方法都会被当做服务提供,包括他继承的父类 public 方法。服务校验为了安全,我们最好对客户端发起的RPC服务请求做校验。在 framework/application/plugins/Filter.php 中做校验:class FilterPlugin extends Yaf_Plugin_Abstract { var $params; //路由之前调用 public function routerStartUp ( Yaf_Request_Abstract $request , Yaf_Response_Abstract $response) { $this->params = & $request->getParams(); $this->_auth(); if(!empty($this->params[‘rpc’])) { $this->_rpc_auth(); //rpc 调用校验 } } //rpc调用校验 protected function rpc_auth() { $signature = $this->get_rpc_signature($this->params); if($signature != $this->params[‘signature’]) { $this->response_error(1, ‘check failed’); } } //rpc签名计算,不要改函数名,在RPC客户端中 system/YarClientProxy.php 我们也会用到这个函数,做签名。 public function get_rpc_signature($params) { $secret = ‘MJCISDYFYHHNKBCOVIUHFUIHCQWE’; unset($params[‘signature’]); ksort($params); reset($params); unset($auth_params[‘callback’]); unset($auth_params[’’]); $str = $secret; foreach ($params as $value) { $str = $str . trim($value); } return md5($str); } … }切记不要修改签名生成函数 get_rpc_signature 的名字和参数,因为在 RPC Client 我们也会利用这个函数做签名,如果需要修改,请在 system/YarClientProxy.php 中做相应修改,以保证客户端和服务器之间的调用正常。RPC Clientyar 除了支持 http 之外,还支持tcp, unix domain socket传输协议,不过ycroute中只用了 http ,当然 http 也可以开启 keepalive 以获得更高的传输性能,只不过相比 socket, http 协议还是多了不少的协议头部的开销。安装环境扩展: yar.so 扩展: msgpack.so 可选,一个高效的二进制打包协议,用于客户端和服务端之间包传输,还可以选php、json, 如果要使用Msgpack做为打包协议, 就需要安装这个扩展。调用逻辑例子:class UserController extends Core_Controller { … //获取用户信息(从远程) public function getUserInfoByRemoteAction() { $userId = $this->params[‘userid’]; if (empty($userId)) { $this->response_error(10000017, “user_id is empty”); } $model = Loader::remote_model(‘UserinfoModel’); $userInfo = $model->getUserinfoByUserid($userId); $this->response_success($userInfo); } …}通过 $model = Loader::remote_model(‘UserinfoModel’); 可以获取远程 UserinfoModel,参数是framework/application/config/rpc.php配置里的键值:$remote_config[‘UserinfoModel’][‘url’] = “http://localhost/index.php?c=rpcserver&m=userinfoModel&rpc=true”; //服务地址$remote_config[‘UserinfoModel’][‘packager’] = FALSE; //RPC包类型,FALSE则选择默认,可以为 “json”, “msgpack”, “php”, msgpack 需要安装扩展$remote_config[‘UserinfoModel’][‘persitent’] = FALSE; //是否长链接,需要服务端支持keepalive$remote_config[‘UserinfoModel’][‘connect_timeout’] = 1000; //连接超时(毫秒),默认 1秒 $remote_config[‘UserinfoModel’][’timeout’] = 5000; //调用超时(毫秒), 默认 5 秒$remote_config[‘UserinfoModel’][‘debug’] = TRUE; //DEBUG模式,调用异常是否会打印到屏幕,线上关闭$remote_config[‘TradeModel’][‘url’] = “http://localhost/index.php?c=rpcserver&m=tradeModel&rpc=true”;$remote_config[‘TradeModel’][‘packager’] = FALSE;$remote_config[‘TradeModel’][‘persitent’] = FALSE;$remote_config[‘TradeModel’][‘connect_timeout’] = 1000; $remote_config[‘TradeModel’][’timeout’] = 5000; $remote_config[‘TradeModel’][‘debug’] = TRUE; 这样,我们就可以把 model 当成本地对象一样调用远程 UserinfoModel 的成员方法。url签名调用远程服务的时候,system/YarClientProxy.php 会从配置中获取服务的 url, 然后调用 FilterPlugin::get_rpc_signature 方法对 URL 做签名,并将签名参数拼接到 url 结尾,发起调用。class YarClientProxy { … public static function get_signatured_url($url) { $get = array(); $t = parse_url($url, PHP_URL_QUERY); parse_str($t, $get); $get[’timestamp’] = time(); $get[‘auth’] = rand(11111111, 9999999999); $signature = FilterPlugin::get_rpc_signature($get); return $url . “&timestamp=” . $get[’timestamp’] . “&auth=” . $get[‘auth’] . “&signature=” . $signature; } …}调用异常日志日志位于 /data/app/logs/localhost 下,localhost 为项目域名。[root@gzapi: /data/app/logs/localhost]# lsyar_client_proxy.20190214.log.wf[ERROR] [2019-02-14 18:57:13] [0] [index.php|23 => | => User.php|61 => YarClientProxy.php|46] [218.30.116.3] [/index.php?c=user&m=getUserInfoByRemote&userid=6818810&token=c9bea5dee1f49488e2b4b4645ff3717e1] [] [] - “yar_client_call_error URL=[http://tr.gaoqu.site/index.ph…] , Remote_model=[UserinfoModel] Func=[getUserinfoByUserid] Exception=[server responsed non-200 code ‘500’]“RPC 并行调用yar框架支持并行调用,可以同时调用多个服务,这样可以充分利用CPU性能,避免IO等待,提升系统性能,按照yar的流程,你首先得一个个注册服务,然后发送注册的调用,然后reset 重置调用。在ycroute 中,一个函数就可以了。用 Loader::concurrent_call($call_params); 来并行调用RPC服务, 其中 call_params是调用参数数组。如下数组包含4个元素,每个调用都包含 model, method 两个必输参数,以及 parameters, callback , error_callback 三个可选参数。model : 服务名,是framework/application/config/rpc.php配置里的键值。method : 调用函数parameters : 函数的参数,是一个数组,数组的个数为参数的个数callback : 回调函数,调用成功之后回调,针对的是各自的回调。error_callback : 调用失败之后会回调这个函数,其中调用超时不会回调该方法, 针对的也是各自的回调。class UserController extends Core_Controller { //获取用户信息(并行远程调用) public function multipleGetUsersInfoByRemoteAction() { $userId = $this->params[‘userid’]; $call_params = array(); $call_params[] = [‘model’ => ‘UserinfoModel’, ‘method’ => ‘getUserinfoByUserid’, ‘parameters’ => array($userId), “callback” => array($this, ‘callback1’)]; $call_params[] = [‘model’ => ‘UserinfoModel’, ‘method’ => ‘getUserInUserids’, ‘parameters’ => array(array(6860814, 6870818)), “callback” => array($this, ‘callback2’), “error_callback” => array($this, ’error_callback’)]; $call_params[] = [‘model’ => ‘UserinfoModel’, ‘method’ => ‘getUserByName’, ‘parameters’ => array(‘CH.smallhow’)]; //不存在的方法 $call_params[] = [‘model’ => ‘UserinfoModel’, ‘method’ => ‘unknownMethod’, ‘parameters’ => array(), “error_callback” => array($this, ’error_callback’)]; Loader::concurrent_call($call_params); echo json_encode($this->retval); exit; } //回调函数1 public function callback1($retval, $callinfo) { $this->retval[‘callback1’][‘retval’] = $retval; $this->retval[‘callback1’][‘callinfo’] = $callinfo; } //回调函数2 public function callback2($retval, $callinfo) { $this->retval[‘callback2’][‘retval’] = $retval; $this->retval[‘callback2’][‘callinfo’] = $callinfo; } //错误回调 public function error_callback($type, $error, $callinfo) { $tmp[’type’] = $type; $tmp[’error’] = $error; $tmp[‘callinfo’] = $callinfo; $this->retval[’error_callback’][] = $tmp; }}我特意将第4个调用的method设置一个不存在的函数,大家可以看下上面的并行调用的结果:{ “error_callback”:[ { “type”:4, “error”:“call to undefined api ::unknownMethod()”, “callinfo”:{ “sequence”:4, “uri”:“http://tr.gaoqu.site/index.php?c=rpcserver&m=userinfoModel&rpc=true×tamp=1550142590&auth=5930400101&signature=fc0ed911c624d9176523544421a0248d”, “method”:“unknownMethod” } } ], “callback1”:{ “retval”:{ “user_id”:“6818810”, “appid”:“wx385863ba15f573b6”, “open_id”:“oXtwn4wkPO4FhHmkan097DpFobvA”, “union”:null, “session_key”:“Et1yjxbEfRqVmCVsYf5qzA==”, “nickname”:“芒果”, “city”:“Yichun”, “province”:“Jiangxi”, “country”:“China”, “avatar_url”:“https://wx.qlogo.cn/mmopen/vi_32/DYAIOgq83epqg7FwyBUGd5xMXxLQXgW2TDEBhnNjPVla8GmKiccP0pFiaLK1BGpAJDMiaoyGHR9Nib2icIX9Na4Or0g/132", “gender”:“1”, “form_id”:””, “token”:“5a350bc05bbbd9556f719a0b8cf2a5ed”, “amount”:“0”, “last_login_time”:“2018-10-04 16:01:27”, “regist_time”:“2018-06-29 21:24:45”, “updatetime”:“2018-10-04 16:01:27” }, “callinfo”:{ “sequence”:1, “uri”:“http://tr.gaoqu.site/index.php?c=rpcserver&m=userinfoModel&rpc=true×tamp=1550142590&auth=8384256613&signature=c0f9c944ae070d2eb38c8e9638723a2e”, “method”:“getUserinfoByUserid” } }, “callback2”:{ “retval”:{ “6860814”:{ “user_id”:“6860814”, “nickname”:“Smile、格调”, “avatar_url”:“https://wx.qlogo.cn/mmopen/vi_32/Q0j4TwGTfTKNE5mFLk33q690Xl1N6mrehQr0ggasgk8Y4cuaUJt4CNHORwq8rVjwET7H06F3aDjU5UiczjpD4nw/132", “city”:“Guangzhou” }, “6870818”:{ “user_id”:“6870818”, “nickname”:“Yang”, “avatar_url”:“https://wx.qlogo.cn/mmopen/vi_32/Q0j4TwGTfTLTKBoU1tdRicImnUHyr43FdMulSHRhAlsQwuYgAyOlrwQaLGRoFEHbgfVuyEV1K1VU2NMmm0slS4w/132", “city”:“Hengyang” } }, “callinfo”:{ “sequence”:2, “uri”:“http://tr.gaoqu.site/index.php?c=rpcserver&m=userinfoModel&rpc=true×tamp=1550142590&auth=7249482640&signature=26c419450bb4747ac166fbaa4a242b77”, “method”:“getUserInUserids” } }}附录 - Core_Model 中的辅助极速开发函数(不关心可以跳过)$this->redis_conf_path = ‘default_master’; //用到快速缓存时,需要在 __construct 构造函数中加上 redis 缓存配置/* * 插入表记录 * @param string table 表名 * @param array data 表数据 * @param string redis_key redis 缓存键值, 可空, 非空时清理键值缓存 /public function insert_table($table, $data, $redis_key = “”);/* * 更新表记录 * @param string table 表名 * @param array where 查询条件 * @param array data 更新数据 * @param string redis_key redis 缓存键值, 可空, 非空时清理键值缓存 /public function update_table($table, $where, $data, $redis_key = “”);/* * 替换表记录 * @param string table 表名 * @param array data 替换数据 * @param string redis_key redis 缓存键值, 可空, 非空时清理键值缓存 /public function replace_table($table, $data, $redis_key = “”);/* * 删除表记录 * @param string table 表名 * @param array where 查询条件 * @param string redis_key redis缓存键值, 可空, 非空时清理键值缓存 /public function delete_table($table, $where, $redis_key = “”);/* * 获取表数据 * @param string table 表名 * @param array where 查询条件 * @param string redis_key redis 缓存键值, 可空, 非空时清理键值缓存 * @param int redis_expire redis 缓存到期时长(秒) * @param boolean set_empty_flag 是否标注空值,如果标注空值,在表记录更新之后,一定记得清理空值标记缓存 /public function get_table_data($table, $where = array(), $redis_key = “”, $redis_expire = 600, $set_empty_flag = true);/* * 根据key获取表记录 * @param string table 表名 * @param string key 键名 * @param string value 键值 * @param string redis_key redis 缓存键值, 可空, 非空时清理键值缓存 * @param int redis_expire redis 缓存到期时长(秒) * @param boolean set_empty_flag 是否标注空值,如果标注空值,在表记录更新之后,一定记得清理空值标记缓存 /public function get_table_data_by_key($table, $key, $value, $redis_key = “”, $redis_expire = 300, $set_empty_flag = true);/* * 获取一条表数据 * @param string table 表名 * @param array where 查询条件 * @param string redis_key redis 缓存键值, 可空, 非空时清理键值缓存 * @param int redis_expire redis 缓存到期时长(秒) * @param boolean set_empty_flag 是否标注空值,如果标注空值,在表记录更新之后,一定记得清理空值标记缓存 */public function get_one_table_data($table, $where, $redis_key = “”, $redis_expire = 600, $set_empty_flag = true); ...

March 9, 2019 · 9 min · jiezi

集成源码深度剖析:Fescar x Spring Cloud

Fescar 简介常见的分布式事务方式有基于 2PC 的 XA (e.g. atomikos),从业务层入手的 TCC( e.g. byteTCC)、事务消息 ( e.g. RocketMQ Half Message) 等等。XA 是需要本地数据库支持的分布式事务的协议,资源锁在数据库层面导致性能较差,而支付宝作为布道师引入的 TCC 模式需要大量的业务代码保证,开发维护成本较高。分布式事务是业界比较关注的领域,这也是短短时间 Fescar 能收获6k Star的原因之一。Fescar 名字取自 Fast & Easy Commit And Rollback ,简单来说Fescar通过对本地 RDBMS 分支事务的协调来驱动完成全局事务,是工作在应用层的中间件。主要优点是相对于XA模式是性能较好不长时间占用连接资源,相对于 TCC 方式开发成本和业务侵入性较低。类似于 XA,Fescar 将角色分为 TC、RM、TM,事务整体过程模型如下:1. TM 向 TC 申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的 XID。2. XID 在微服务调用链路的上下文中传播。3. RM 向 TC 注册分支事务,将其纳入 XID 对应全局事务的管辖。4. TM 向 TC 发起针对 XID 的全局提交或回滚决议。5. TC 调度 XID 下管辖的全部分支事务完成提交或回滚请求。其中在目前的实现版本中 TC 是独立部署的进程,维护全局事务的操作记录和全局锁记录,负责协调并驱动全局事务的提交或回滚。TM RM 则与应用程序工作在同一应用进程。RM对 JDBC 数据源采用代理的方式对底层数据库做管理,利用语法解析,在执行事务保留快照,并生成 undo log。大概的流程和模型划分就介绍到这里,下面开始对 Fescar 事务传播机制的分析。Fescar 事务传播机制Fescar 事务传播包括应用内事务嵌套调用和跨服务调用的事务传播。Fescar 事务是怎么在微服务调用链中传播的呢?Fescar 提供了事务 API 允许用户手动绑定事务的 XID 并加入到全局事务中,所以我们根据不同的服务框架机制,将 XID 在链路中传递即可实现事务的传播。RPC 请求过程分为调用方与被调用方两部分,我们将 XID 在请求与响应时做相应的处理即可。大致过程为:调用方即请求方将当前事务上下文中的 XID 取出,通过RPC协议传递给被调用方;被调用方从请求中的将 XID 取出,并绑定到自己的事务上下文中,纳入全局事务。微服务框架一般都有相应的 Filter 和 Interceptor 机制,我们来分析下 Spring Cloud 与Fescar 的整合过程。Fescar 与 Spring Cloud Alibaba 集成部分源码解析本部分源码全部来自于 spring-cloud-alibaba-fescar. 源码解析部分主要包括AutoConfiguration、微服务被调用方和微服务调用方三大部分。对于微服务调用方方式具体分为 RestTemplate 和 Feign,对于 Feign 请求方式又进一步细分为结合 Hystrix 和 Sentinel 的使用模式。Fescar AutoConfiguration对于 AutoConfiguration 部分的解析此处只介绍与 Fescar 启动相关的部分,其他部分的解析将穿插于【微服务被调用方】和【微服务调用方】章节进行介绍。Fescar 的启动需要配置 GlobalTransactionScanner,GlobalTransactionScanner 负责初始化 Fescar 的 RM client、TM client 和 自动代理标注 GlobalTransactional 注解的类。GlobalTransactionScanner bean 的启动通过 GlobalTransactionAutoConfiguration 加载并注入FescarProperties。FescarProperties 包含了 Fescar的重要属性 txServiceGroup ,此属性的可通过 application.properties 文件中的 key: spring.cloud.alibaba.fescar.txServiceGroup 读取,默认值为 ${spring.application.name}-fescar-service-group 。txServiceGroup 表示Fescar 的逻辑事务分组名,此分组名通过配置中心(目前支持文件、Apollo)获取逻辑事务分组名对应的 TC 集群名称,进一步通过集群名称构造出 TC 集群的服务名,通过注册中心(目前支持Nacos、Redis、ZooKeeper和Eureka)和服务名找到可用的 TC 服务节点,然后 RM client、TM client 与 TC 进行 RPC 交互。微服务被调用方由于调用方的逻辑比较多一点,我们先分析被调用方的逻辑。针对于 Spring Cloud 项目,默认采用的 RPC 传输协议时 HTTP 协议,所以使用了 HandlerInterceptor 机制来对HTTP的请求做拦截。HandlerInterceptor 是 Spring 提供的接口, 它有以下三个方法可以被覆写。 /** * Intercept the execution of a handler. Called after HandlerMapping determined * an appropriate handler object, but before HandlerAdapter invokes the handler. / default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { return true; } /* * Intercept the execution of a handler. Called after HandlerAdapter actually * invoked the handler, but before the DispatcherServlet renders the view. * Can expose additional model objects to the view via the given ModelAndView. / default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception { } /* * Callback after completion of request processing, that is, after rendering * the view. Will be called on any outcome of handler execution, thus allows * for proper resource cleanup. / default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception { }根据注释,我们可以很明确的看到各个方法的作用时间和常用用途。对于 Fescar 集成来讲,它需要重写了 preHandle、afterCompletion 方法。FescarHandlerInterceptor 的作用是将服务链路传递过来的 XID,绑定到服务节点的事务上下文中,并且在请求完成后清理相关资源。FescarHandlerInterceptorConfiguration 中配置了所有的 url 均进行拦截,对所有的请求过来均会执行该拦截器,进行 XID 的转换与事务绑定。/* * @author xiaojing * * Fescar HandlerInterceptor, Convert Fescar information into * @see com.alibaba.fescar.core.context.RootContext from http request’s header in * {@link org.springframework.web.servlet.HandlerInterceptor#preHandle(HttpServletRequest , HttpServletResponse , Object )}, * And clean up Fescar information after servlet method invocation in * {@link org.springframework.web.servlet.HandlerInterceptor#afterCompletion(HttpServletRequest, HttpServletResponse, Object, Exception)} /public class FescarHandlerInterceptor implements HandlerInterceptor { private static final Logger log = LoggerFactory .getLogger(FescarHandlerInterceptor.class); @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String xid = RootContext.getXID(); String rpcXid = request.getHeader(RootContext.KEY_XID); if (log.isDebugEnabled()) { log.debug(“xid in RootContext {} xid in RpcContext {}”, xid, rpcXid); } if (xid == null && rpcXid != null) { RootContext.bind(rpcXid); if (log.isDebugEnabled()) { log.debug(“bind {} to RootContext”, rpcXid); } } return true; } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception e) throws Exception { String rpcXid = request.getHeader(RootContext.KEY_XID); if (StringUtils.isEmpty(rpcXid)) { return; } String unbindXid = RootContext.unbind(); if (log.isDebugEnabled()) { log.debug(“unbind {} from RootContext”, unbindXid); } if (!rpcXid.equalsIgnoreCase(unbindXid)) { log.warn(“xid in change during RPC from {} to {}”, rpcXid, unbindXid); if (unbindXid != null) { RootContext.bind(unbindXid); log.warn(“bind {} back to RootContext”, unbindXid); } } }}preHandle 在请求执行前被调用,xid 为当前事务上下文已经绑定的全局事务的唯一标识,rpcXid 为请求通过 HTTP Header 传递过来需要绑定的全局事务标识。preHandle 方法中判断如果当前事务上下文中没有 XID,且 rpcXid 不为空,那么就将 rpcXid 绑定到当前的事务上下文。afterCompletion 在请求完成后被调用,该方法用来执行资源的相关清理动作。Fescar 通过 RootContext.unbind() 方法对事务上下文涉及到的 XID 进行解绑。下面 if 中的逻辑是为了代码的健壮性考虑,如果遇到 rpcXid和 unbindXid 不相等的情况,再将 unbindXid 重新绑定回去。对于 Spring Cloud 来讲,默认采用的 RPC 方式是 HTTP 的方式,所以对被调用方来讲,它的请求拦截方式不用做任何区分,只需要从 Header 中将 XID 就可以取出绑定到自己的事务上下文中即可。但是对于调用方由于请求组件的多样化,包括熔断隔离机制,所以要区分不同的情况做处理,后面我们来具体分析一下。微服务调用方Fescar 将请求方式分为:RestTemplate、Feign、Feign+Hystrix 和 Feign+Sentinel 。不同的组件通过 Spring Boot 的 Auto Configuration 来完成自动的配置,具体的配置类可以看 spring.factories ,下文也会介绍相关的配置类。RestTemplate先来看下如果调用方如果是是基于 RestTemplate 的请求,Fescar 是怎么传递 XID 的。public class FescarRestTemplateInterceptor implements ClientHttpRequestInterceptor { @Override public ClientHttpResponse intercept(HttpRequest httpRequest, byte[] bytes, ClientHttpRequestExecution clientHttpRequestExecution) throws IOException { HttpRequestWrapper requestWrapper = new HttpRequestWrapper(httpRequest); String xid = RootContext.getXID(); if (!StringUtils.isEmpty(xid)) { requestWrapper.getHeaders().add(RootContext.KEY_XID, xid); } return clientHttpRequestExecution.execute(requestWrapper, bytes); }}FescarRestTemplateInterceptor 实现了 ClientHttpRequestInterceptor 接口的 intercept 方法,对调用的请求做了包装,在发送请求时若存在 Fescar 事务上下文 XID 则取出并放到 HTTP Header 中。FescarRestTemplateInterceptor 通过 FescarRestTemplateAutoConfiguration 实现将 FescarRestTemplateInterceptor 配置到 RestTemplate 中去。@Configurationpublic class FescarRestTemplateAutoConfiguration { @Bean public FescarRestTemplateInterceptor fescarRestTemplateInterceptor() { return new FescarRestTemplateInterceptor(); } @Autowired(required = false) private Collection<RestTemplate> restTemplates; @Autowired private FescarRestTemplateInterceptor fescarRestTemplateInterceptor; @PostConstruct public void init() { if (this.restTemplates != null) { for (RestTemplate restTemplate : restTemplates) { List<ClientHttpRequestInterceptor> interceptors = new ArrayList<ClientHttpRequestInterceptor>( restTemplate.getInterceptors()); interceptors.add(this.fescarRestTemplateInterceptor); restTemplate.setInterceptors(interceptors); } } }}init 方法遍历所有的 restTemplate ,并将原来 restTemplate 中的拦截器取出,增加 fescarRestTemplateInterceptor 后置入并重排序。Feign接下来看下 Feign 的相关代码,该包下面的类还是比较多的,我们先从其 AutoConfiguration 入手。@Configuration@ConditionalOnClass(Client.class)@AutoConfigureBefore(FeignAutoConfiguration.class)public class FescarFeignClientAutoConfiguration { @Bean @Scope(“prototype”) @ConditionalOnClass(name = “com.netflix.hystrix.HystrixCommand”) @ConditionalOnProperty(name = “feign.hystrix.enabled”, havingValue = “true”) Feign.Builder feignHystrixBuilder(BeanFactory beanFactory) { return FescarHystrixFeignBuilder.builder(beanFactory); } @Bean @Scope(“prototype”) @ConditionalOnClass(name = “com.alibaba.csp.sentinel.SphU”) @ConditionalOnProperty(name = “feign.sentinel.enabled”, havingValue = “true”) Feign.Builder feignSentinelBuilder(BeanFactory beanFactory) { return FescarSentinelFeignBuilder.builder(beanFactory); } @Bean @ConditionalOnMissingBean @Scope(“prototype”) Feign.Builder feignBuilder(BeanFactory beanFactory) { return FescarFeignBuilder.builder(beanFactory); } @Configuration protected static class FeignBeanPostProcessorConfiguration { @Bean FescarBeanPostProcessor fescarBeanPostProcessor( FescarFeignObjectWrapper fescarFeignObjectWrapper) { return new FescarBeanPostProcessor(fescarFeignObjectWrapper); } @Bean FescarContextBeanPostProcessor fescarContextBeanPostProcessor( BeanFactory beanFactory) { return new FescarContextBeanPostProcessor(beanFactory); } @Bean FescarFeignObjectWrapper fescarFeignObjectWrapper(BeanFactory beanFactory) { return new FescarFeignObjectWrapper(beanFactory); } }}FescarFeignClientAutoConfiguration 在存在 Client.class 时生效,且要求作用在 FeignAutoConfiguration 之前。由于FeignClientsConfiguration 是在 FeignAutoConfiguration 生成 FeignContext 生效的,所以根据依赖关系, FescarFeignClientAutoConfiguration 同样早于 FeignClientsConfiguration。FescarFeignClientAutoConfiguration 自定义了 Feign.Builder,针对于 feign.sentinel,feign.hystrix 和 feign 的情况做了适配,目的是自定义 feign 中 Client 的真正实现为 FescarFeignClient。HystrixFeign.builder().retryer(Retryer.NEVER_RETRY) .client(new FescarFeignClient(beanFactory))SentinelFeign.builder().retryer(Retryer.NEVER_RETRY) .client(new FescarFeignClient(beanFactory));Feign.builder().client(new FescarFeignClient(beanFactory));FescarFeignClient 是对原来的 Feign 客户端代理增强,具体代码见下图:public class FescarFeignClient implements Client { private final Client delegate; private final BeanFactory beanFactory; FescarFeignClient(BeanFactory beanFactory) { this.beanFactory = beanFactory; this.delegate = new Client.Default(null, null); } FescarFeignClient(BeanFactory beanFactory, Client delegate) { this.delegate = delegate; this.beanFactory = beanFactory; } @Override public Response execute(Request request, Request.Options options) throws IOException { Request modifiedRequest = getModifyRequest(request); try { return this.delegate.execute(modifiedRequest, options); } finally { } } private Request getModifyRequest(Request request) { String xid = RootContext.getXID(); if (StringUtils.isEmpty(xid)) { return request; } Map<String, Collection<String>> headers = new HashMap<>(); headers.putAll(request.headers()); List<String> fescarXid = new ArrayList<>(); fescarXid.add(xid); headers.put(RootContext.KEY_XID, fescarXid); return Request.create(request.method(), request.url(), headers, request.body(), request.charset()); }上面的过程中我们可以看到,FescarFeignClient 对原来的 Request 做了修改,它首先将 XID 从当前的事务上下文中取出,如果 XID 不为空的情况下,将 XID 放到了 Header 中。FeignBeanPostProcessorConfiguration 定义了3个bean:FescarContextBeanPostProcessor、FescarBeanPostProcessor 和 FescarFeignObjectWrapper。其中 FescarContextBeanPostProcessor FescarBeanPostProcessor 实现了Spring BeanPostProcessor 接口。以下为 FescarContextBeanPostProcessor 实现。 @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { if (bean instanceof FeignContext && !(bean instanceof FescarFeignContext)) { return new FescarFeignContext(getFescarFeignObjectWrapper(), (FeignContext) bean); } return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { return bean; }BeanPostProcessor 中的两个方法可以对 Spring 容器中的 Bean 做前后处理,postProcessBeforeInitialization 处理时机是初始化之前,postProcessAfterInitialization 的处理时机是初始化之后,这2个方法的返回值可以是原先生成的实例 bean,或者使用 wrapper 包装后的实例。FescarContextBeanPostProcessor 将 FeignContext 包装成 FescarFeignContext。FescarBeanPostProcessor 将 FeignClient 根据是否继承了LoadBalancerFeignClient 包装成 FescarLoadBalancerFeignClient 和 FescarFeignClient。FeignAutoConfiguration 中的 FeignContext 并没有加 ConditionalOnXXX 的条件,所以 Fescar 采用预置处理的方式将 FeignContext 包装成 FescarFeignContext。 @Bean public FeignContext feignContext() { FeignContext context = new FeignContext(); context.setConfigurations(this.configurations); return context; }而对于 Feign Client,FeignClientFactoryBean 中会获取 FeignContext 的实例对象。对于开发者采用 @Configuration 注解的自定义配置的 Feign Client 对象,这里会被配置到 builder,导致 FescarFeignBuilder 中增强后的 FescarFeignCliet 失效。FeignClientFactoryBean 中关键代码如下: /* * @param <T> the target type of the Feign client * @return a {@link Feign} client created with the specified data and the context information */ <T> T getTarget() { FeignContext context = applicationContext.getBean(FeignContext.class); Feign.Builder builder = feign(context); if (!StringUtils.hasText(this.url)) { if (!this.name.startsWith(“http”)) { url = “http://” + this.name; } else { url = this.name; } url += cleanPath(); return (T) loadBalance(builder, context, new HardCodedTarget<>(this.type, this.name, url)); } if (StringUtils.hasText(this.url) && !this.url.startsWith(“http”)) { this.url = “http://” + this.url; } String url = this.url + cleanPath(); Client client = getOptional(context, Client.class); if (client != null) { if (client instanceof LoadBalancerFeignClient) { // not load balancing because we have a url, // but ribbon is on the classpath, so unwrap client = ((LoadBalancerFeignClient)client).getDelegate(); } builder.client(client); } Targeter targeter = get(context, Targeter.class); return (T) targeter.target(this, builder, context, new HardCodedTarget<>( this.type, this.name, url)); }上述代码根据是否指定了注解参数中的 URL 来选择直接调用 URL 还是走负载均衡,targeter.target 通过动态代理创建对象。大致过程为:将解析出的feign方法放入map,再通过将其作为参数传入生成InvocationHandler,进而生成动态代理对象。FescarContextBeanPostProcessor 的存在,即使开发者对 FeignClient 自定义操作,依旧可以完成 Fescar 所需的全局事务的增强。对于 FescarFeignObjectWrapper,我们重点关注下Wrapper方法: Object wrap(Object bean) { if (bean instanceof Client && !(bean instanceof FescarFeignClient)) { if (bean instanceof LoadBalancerFeignClient) { LoadBalancerFeignClient client = ((LoadBalancerFeignClient) bean); return new FescarLoadBalancerFeignClient(client.getDelegate(), factory(), clientFactory(), this.beanFactory); } return new FescarFeignClient(this.beanFactory, (Client) bean); } return bean; }wrap 方法中,如果 bean 是 LoadBalancerFeignClient 的实例对象,那么首先通过 client.getDelegate() 方法将 LoadBalancerFeignClient 代理的实际 Client 对象取出后包装成 FescarFeignClient,再生成 LoadBalancerFeignClient 的子类 FescarLoadBalancerFeignClient 对象。如果 bean 是 Client 的实例对象且不是 FescarFeignClient LoadBalancerFeignClient,那么 bean 会直接包装生成 FescarFeignClient。上面的流程设计还是比较巧妙的,首先根据 Spring boot 的 Auto Configuration 控制了配置的先后顺序,同时自定义了 Feign Builder的Bean,保证了 Client 均是经过增强后的 FescarFeignClient 。再通过 BeanPostProcessor 对Spring 容器中的 Bean 做了一遍包装,保证容器内的Bean均是增强后 FescarFeignClient ,避免 FeignClientFactoryBean getTarget 方法的替换动作。Hystrix 隔离下面我们再来看下 Hystrix 部分,为什么要单独把 Hystrix 拆出来看呢,而且 Fescar 代码也单独实现了个策略类。目前事务上下文 RootContext 的默认实现是基于 ThreadLocal 方式的 ThreadLocalContextCore,也就是上下文其实是和线程绑定的。Hystrix 本身有两种隔离状态的模式,基于信号量或者基于线程池进行隔离。Hystrix 官方建议是采取线程池的方式来充分隔离,也是一般情况下在采用的模式:Thread or SemaphoreThe default, and the recommended setting, is to run HystrixCommands using thread isolation (THREAD) and HystrixObservableCommands using semaphore isolation (SEMAPHORE).Commands executed in threads have an extra layer of protection against latencies beyond what network timeouts can offer.Generally the only time you should use semaphore isolation for HystrixCommands is when the call is so high volume (hundreds per second, per instance) that the overhead of separate threads is too high; this typically only applies to non-network calls.service 层的业务代码和请求发出的线程肯定不是同一个,那么 ThreadLocal 的方式就没办法将 XID 传递给 Hystrix 的线程并传递给被调用方的。怎么处理这件事情呢,Hystrix 提供了个机制让开发者去自定义并发策略,只需要继承 HystrixConcurrencyStrategy 重写 wrapCallable 方法即可。public class FescarHystrixConcurrencyStrategy extends HystrixConcurrencyStrategy { private HystrixConcurrencyStrategy delegate; public FescarHystrixConcurrencyStrategy() { this.delegate = HystrixPlugins.getInstance().getConcurrencyStrategy(); HystrixPlugins.reset(); HystrixPlugins.getInstance().registerConcurrencyStrategy(this); } @Override public <K> Callable<K> wrapCallable(Callable<K> c) { if (c instanceof FescarContextCallable) { return c; } Callable<K> wrappedCallable; if (this.delegate != null) { wrappedCallable = this.delegate.wrapCallable(c); } else { wrappedCallable = c; } if (wrappedCallable instanceof FescarContextCallable) { return wrappedCallable; } return new FescarContextCallable<>(wrappedCallable); } private static class FescarContextCallable<K> implements Callable<K> { private final Callable<K> actual; private final String xid; FescarContextCallable(Callable<K> actual) { this.actual = actual; this.xid = RootContext.getXID(); } @Override public K call() throws Exception { try { RootContext.bind(xid); return actual.call(); } finally { RootContext.unbind(); } } }}Fescar 也提供一个 FescarHystrixAutoConfiguration,在存在 HystrixCommand 的时候生成FescarHystrixConcurrencyStrategy。@Configuration@ConditionalOnClass(HystrixCommand.class)public class FescarHystrixAutoConfiguration { @Bean FescarHystrixConcurrencyStrategy fescarHystrixConcurrencyStrategy() { return new FescarHystrixConcurrencyStrategy(); }}参考资料Fescar: https://github.com/alibaba/fescarSpring Cloud Alibaba: https://github.com/spring-cloud-incubator/spring-cloud-alibabaspring-cloud-openfeign: https://github.com/spring-cloud/spring-cloud-openfeign本文作者郭树抗,社区昵称 ywind,曾就职于华为终端云,现搜狐智能媒体中心Java工程师,目前主要负责搜狐号相关开发,对分布式事务、分布式系统和微服务架构有异常浓厚的兴趣。季敏(清铭),社区昵称 slievrly,Fescar 开源项目负责人,阿里巴巴中件间 TXC/GTS 核心研发成员,长期从事于分布式中间件核心研发工作,在分布式事务领域有着较丰富的技术积累。延伸阅读微服务架构下,解决数据一致性问题的实践本文作者:中间件小哥阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

March 8, 2019 · 7 min · jiezi

天气预报微服务 | 从0开始构建SpringCloud微服务(8)

照例附上项目github链接本项目实现的是将一个简单的天气预报系统一步一步改造成一个SpringCloud微服务系统的过程,本节主要讲的是单块架构改造成微服务架构的过程,最终将原来单块架构的天气预报服务拆分为四个微服务:城市数据API微服务,天气数据采集微服务,天气数据API微服务,天气预报微服务。本章主要讲解天气预报微服务的实现。天气预报微服务的实现配置pom文件对原来单块架构的天气预报服务进行改进,去除多余的依赖,最终的pom文件如下:<?xml version=“1.0” encoding=“UTF-8”?><project xmlns=“http://maven.apache.org/POM/4.0.0" xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=“http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.demo</groupId> <artifactId>sifoudemo02</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>sifoudemo02</name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.5.RELEASE</version> <relativePath/> <!– lookup parent from repository –> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-jdk14</artifactId> <version>1.7.7</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <fork>true</fork> </configuration> <!– <groupId>org.apache.felix</groupId> <artifactId>maven-bundle-plugin</artifactId> <extensions>true</extensions> –> </plugin> </plugins> </build> </project>提供接口在Service中提供根据城市Id获取天气数据的方法。这里的天气数据后期将会由天气数据API尾服务从缓存中获取。@Servicepublic class WeatherReportServiceImpl implements WeatherReportService { @Override public Weather getDataByCityId(String cityId) { // TODO 改为由天气数据API微服务来提供 Weather data = new Weather(); data.setAqi(“81”); data.setCity(“深圳”); data.setGanmao(“容易感冒!多穿衣”); data.setWendu(“22”); List<Forecast> forecastList = new ArrayList<>(); Forecast forecast = new Forecast(); forecast.setDate(“25日星期天”); forecast.setType(“晴”); forecast.setFengxiang(“无风”); forecast.setHigh(“高温 11度”); forecast.setLow(“低温 1度”); forecastList.add(forecast); forecast = new Forecast(); forecast.setDate(“26日星期天”); forecast.setType(“晴”); forecast.setFengxiang(“无风”); forecast.setHigh(“高温 11度”); forecast.setLow(“低温 1度”); forecastList.add(forecast); forecast = new Forecast(); forecast.setDate(“27日星期天”); forecast.setType(“晴”); forecast.setFengxiang(“无风”); forecast.setHigh(“高温 11度”); forecast.setLow(“低温 1度”); forecastList.add(forecast); forecast = new Forecast(); forecast.setDate(“28日星期天”); forecast.setType(“晴”); forecast.setFengxiang(“无风”); forecast.setHigh(“高温 11度”); forecast.setLow(“低温 1度”); forecastList.add(forecast); forecast = new Forecast(); forecast.setDate(“29日星期天”); forecast.setType(“晴”); forecast.setFengxiang(“无风”); forecast.setHigh(“高温 11度”); forecast.setLow(“低温 1度”); forecastList.add(forecast); data.setForecast(forecastList); return data; }}在Controller中提供根据城市Id获取相关天气预报数据并进行前端UI界面展示的接口。@RestController@RequestMapping("/report”)public class WeatherReportController { private final static Logger logger = LoggerFactory.getLogger(WeatherReportController.class); @Autowired private WeatherReportService weatherReportService; @GetMapping("/cityId/{cityId}”) public ModelAndView getReportByCityId(@PathVariable(“cityId”) String cityId, Model model) throws Exception { // 获取城市ID列表 // TODO 改为由城市数据API微服务来提供数据 List<City> cityList = null; try { // TODO 改为由城市数据API微服务提供数据 cityList = new ArrayList<>(); City city = new City(); city.setCityId(“101280601”); city.setCityName(“深圳”); cityList.add(city); } catch (Exception e) { logger.error(“Exception!”, e); } model.addAttribute(“title”, “猪猪的天气预报”); model.addAttribute(“cityId”, cityId); model.addAttribute(“cityList”, cityList); model.addAttribute(“report”, weatherReportService.getDataByCityId(cityId)); return new ModelAndView(“weather/report”, “reportModel”, model); }} ...

March 8, 2019 · 2 min · jiezi

天气数据API微服务 | 从0开始构建SpringCloud微服务(7)

照例附上项目github链接本项目实现的是将一个简单的天气预报系统一步一步改造成一个SpringCloud微服务系统的过程,本节主要讲的是单块架构改造成微服务架构的过程,最终将原来单块架构的天气预报服务拆分为四个微服务:城市数据API微服务,天气数据采集微服务,天气数据API微服务,天气预报微服务。本章主要讲解天气数据API微服务的实现。天气数据API微服务的实现配置pom文件对原来单块架构的天气预报服务进行改进,去除多余的依赖,最终的pom文件如下:<?xml version=“1.0” encoding=“UTF-8”?><project xmlns=“http://maven.apache.org/POM/4.0.0" xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=“http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.demo</groupId> <artifactId>sifoudemo02</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>sifoudemo02</name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.5.RELEASE</version> <relativePath/> <!– lookup parent from repository –> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-jdk14</artifactId> <version>1.7.7</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <fork>true</fork> </configuration> <!– <groupId>org.apache.felix</groupId> <artifactId>maven-bundle-plugin</artifactId> <extensions>true</extensions> –> </plugin> </plugins> </build> </project>提供接口在service中保留如下接口:(1)根据城市Id查询天气的接口getDataByCityId(2)根据城市名称查询天气的接口getDataByCityName注意:原来我们的天气数据是先从缓存中获取的,若查询不到则调用第三方接口获取天气信息,并将其保存到缓存中。在我们拆分成微服务架构之后调用第三方接口的行为由天气数据采集微服务中的定时任务进行。因此在天气数据API微服务中我们的天气数据直接从缓存中进行获取,若在缓存中获取不到对应城市的数据,则直接抛出错误。@Servicepublic class WeatherDataServiceImpl implements WeatherDataService { private final static Logger logger = LoggerFactory.getLogger(WeatherDataServiceImpl.class); private static final String WEATHER_URI = “http://wthrcdn.etouch.cn/weather_mini?"; @Autowired private StringRedisTemplate stringRedisTemplate; @Override public WeatherResponse getDataByCityId(String cityId) { String uri = WEATHER_URI + “citykey=” + cityId; return this.doGetWeahter(uri); } @Override public WeatherResponse getDataByCityName(String cityName) { String uri = WEATHER_URI + “city=” + cityName; return this.doGetWeahter(uri); } private WeatherResponse doGetWeahter(String uri) { String key = uri; String strBody = null; ObjectMapper mapper = new ObjectMapper(); WeatherResponse resp = null; ValueOperations<String, String> ops = stringRedisTemplate.opsForValue(); // 先查缓存,缓存有的取缓存中的数据 if (stringRedisTemplate.hasKey(key)) { logger.info(“Redis has data”); strBody = ops.get(key); } else { logger.info(“Redis don’t has data”); // 缓存没有,抛出异常 throw new RuntimeException(“Don’t has data!”); } try { resp = mapper.readValue(strBody, WeatherResponse.class); } catch (IOException e) { //e.printStackTrace(); logger.error(“Error!",e); } return resp; }}在controller中提供根据城市Id和名称获取天气数据的接口。@RestController@RequestMapping("/weather”)public class WeatherController { @Autowired private WeatherDataService weatherDataService; @GetMapping("/cityId/{cityId}”) public WeatherResponse getWeatherByCityId(@PathVariable(“cityId”) String cityId) { return weatherDataService.getDataByCityId(cityId); } @GetMapping("/cityName/{cityName}”) public WeatherResponse getWeatherByCityName(@PathVariable(“cityName”) String cityName) { return weatherDataService.getDataByCityName(cityName); }} ...

March 8, 2019 · 2 min · jiezi

微服务与单体架构:IT变革中企业及个体如何自处?

当下,企业越来越多地受到竞争对手和他们自己的客户的压力,既需要让应用程序更快地在线运行,同时又要最大限度地降低开发成本。这些不同的目标,迫使企业IT组织必须一刻不停地迅速发展。自20世纪60年代以来经历了一次又一次的强制演变之后,大量的企业IT已经准备从单体应用架构中走出一步,走上微服务之路。图1:传统单体应用程序和微服务之间的架构差异更高的期望和更有能力的客户那些能够接触到全球产品及服务的客户,现在愈发强势地希望他们的供应商企业能够快速响应其他供应商正在做的事情。CIO杂志在报道Ovum的研究时曾指出:在“客户之旅”中,是“客户”本身占据着上风。拥有更多的购物方式,以及开始花费更少的时间在购物上,客户们需要的不仅是快速地收集信息和完成交易。他们经常希望能无需进行冗长的对话、甚至在去往某地的途中、在移动设备上就能快速完成这项工作。处于压力之下的IT这场激烈的全球竞争,也迫使企业寻找新的方法来削减成本或找到更有效的新方法。开发人员在过去其实已经看到了这一切。这只是企业IT十多年来所面临的“少花钱多办事”这一永恒号召的当代新版本罢了。他们已经了解到,尽管IT总预算增长了,但在新IT服务以及通信服务这一块需要的投资也增多了。图2:Gartner的2018年全球IT支出增长预测企业IT组织需要对时代的发展和新的需求作出回应,这一压力也使得他们不得不重新审视他们的开发流程。传统的长达两年开发周期,或许在过去是可以接受的,但在现阶段早已不再令人满意。没有人能够等待。趋势的汇合企业IT也被迫对各种趋势作出回应,这些趋势是分歧和矛盾的。引入廉价但高性能的网络连接,让分布式功能彼此之间通过网络进行通信的速度,与之前可在单个系统内相互通信的流程一样快。推出功能强大的微处理器,可在廉价和小型封装中提供大型机级性能。在标准化X86微处理器架构之后,企业现在不得不考虑其他架构来满足他们对更高性能、更低成本、以及更低功耗和热量的需求。内部系统内存容量不断增加,因此可以在小型系统中部署大型应用程序或应用程序组件。外部存储的使用正逐渐从使用旋转介质变为固态设备,这样可以提高性能、减少延迟、降低总体成本并提供巨大的容量。开源软件和分布式计算功能的发展使企业能够在需要新功能时廉价地添加一系列系统,而不是面对昂贵且耗时的叉车升级来扩展中央主机系统。客户要求即时、轻松地访问应用程序和数据。要应对这些趋势,企业IT部门很快就会发现,他们一直依赖的方法——专注于充分利用昂贵的系统和网络——必须改变了。如今最大的成本是人员、电力和冷却。二十多年前,企业将重点从单体大型机计算转移到基于X86的分布式中端系统,诚然那是一次变革,但当下的变化趋势及变革与那时不同了。下一步发展在哪?以下是企业IT为响应所有这些趋势所做的工作。他们选择从使用传统的瀑布式开发方法转向各种形式的快速应用程序开发。他们也正在从编译语言转向解释或增量编译的语言,如Java、Python或Ruby,以提高开发人员的工作效率。例如,IDC预测:到2021年,65%的CIO将把敏捷开发/DevOps实践扩展到更广泛的业务中,以实现创新、执行和变革所需的速度。复杂的应用程序越来越多地被设计为独立的功能或“服务”,可以托管在网络上的多个地方,以提高应用的性能和可靠性。这种方法意味着可以解决不断变化的业务需求,以及可以在无需并行更改任何其他功能的前提下,向一个功能中添加新功能。NetworkWorld的Andy Patrizio在对2019年的预测中指出,他预计“微服务和无服务器计算将起飞”。另一个重要变化是这些服务托管在地理位置分散的企业数据中心、云端或两者兼有之中。此外,功能可以驻留在客户的口袋中,也可以驻留在基于云或公司系统的某种组合中。这对你意味着什么?这些趋势的到来,意味着企业开发人员和运维人员必须对其传统方法进行一些重大变革,包括:开发人员必须愿意学习更适合当今快速应用程序开发方法的技术。经验丰富的“学生”可以通过在线学校快速学习。例如,Learnpython.org提供Python免费课程,而codecademy提供Ruby、Java和其他语言的免费课程。他们还必须愿意学习如何将应用程序逻辑从单体静态设计分解为独立但协作的微服务集合,各类在线学习网站依然是不错的选择,例如IBM的微服务课程(https://www.coursera.org/lear…)、Lynda.com等等。开发人员必须采用新工具来创建和维护微服务,以支持它们之间快速可靠的通信。善于使用各种商业和开源的信息传递和管理工具,可以大大简化这一过程。例如Rancher Labs的全开源平台Rancher为用户提供Kurbernetes-as-a-service。运维专家需要学习容器和Kubernetes的编排工具,以了解它们是如何让团队快速开发和改进应用程序和服务,而又不会失去对数据和安全性的控制。长期以来,运维一直是企业数据中心的守门人。毕竟,如果应用程序变慢或失败,运维人员就要在此时负责解决这一局面。运维人员必须允许这些功能托管于他们直接控制的数据中心之外。为了说明这一点,Market Research Future的分析师最近发表了一份报告称,“2017年全球云微服务市场价值为5.844亿美元,预计到预测期结束时将达到21.467亿美元,复合年增长率为25.0%”。应用程序管理和安全问题现在必须成为开发人员思考的一部分。在线课程又一次地可以帮助个人发展这方面的专业知识。很多网站包括LinkedIn提供了如何成为IT安全专家的课程。非常重要的一点是,IT和运维人员都必须理解IT的世界正在快速发展变革,每个人都必须专注于提升自身的技能和专业知识。微服务如何使企业受益?微服务这种分布式计算的最新发展,为企业带来了许多实际和可衡量的收益。在IT组织采用这种形式的分布式计算后,开发时间和成本花费都可以大大降低。之后,可以根据需要并行开发每个服务并进行细化,而无需停止或重新设计整个应用程序。开发团队可以专注于开发人员的工作效率,并且仍然可以快速、在线地提供新的应用程序功能。运维团队则可以专注于为应用程序执行定义可接受的规则,并通过编排和管理工具强制执行它们。企业面临哪些新挑战?与任何IT方法一样,微服务架构的应用在带来益处的同时也将带来挑战。监控和管理大量“移动部件”,要比处理一些单体应用程序更具挑战性。采用企业管理框架能有助于应对这些挑战。除此之外,此类分布式计算的安全性也需要首要考虑。随着网络上独立功能的增加,每个功能都必须进行分析和保护。所有单体应用程序都应该迁移到微服务吗?一些单体应用程序很难改变。这可能是由于技术挑战或可能是由于监管限制。今天使用的一些组件可能来自于已经倒闭的供应商,使得迁移或重构变得极其困难甚至不可能。对于企业来说,完成整个审计过程既费时又费钱。通常情况下,企业继续投资旧应用程序的时间,比他们相信自己在节省资金的时间长得多。企业应该先评估清楚某个单体应用是具体做什么的,再考虑是不是要分离某些单独的功能并作为较小的独立服务运行。这些可以实现为基于云的服务或基于容器的微服务。最明智的方法,不是等待并试图解决整个旧技术,而是进行一系列渐进式的更改,使得增强或替换旧系统的计划更容易被接受。这与那句古老的谚语非常相似,“植树的最佳时机,是20年前,其次是现在。”这些变化值得吗?大量已经采用基于微服务的应用程序架构的企业已经证明了,他们的IT成本通常会降低。他们还经常表示,一旦他们的团队掌握了这种方法,当市场需求发生变化时,添加新功能和特性会更容易、更快捷。如果您的企业还完全没有踏上微服务之路,从现在开始了解更多信息也是好的。此外也应该多关注业界的一些新近流行的技术与解决方案,如Kubernetes、Rancher、Serverless等等。

March 7, 2019 · 1 min · jiezi

天气数据采集微服务 | 从0开始构建SpringCloud微服务(6)

照例附上项目github链接本项目实现的是将一个简单的天气预报系统一步一步改造成一个SpringCloud微服务系统的过程,本节主要讲的是单块架构改造成微服务架构的过程,最终将原来单块架构的天气预报服务拆分为四个微服务:城市数据API微服务,天气数据采集微服务,天气数据API微服务,天气预报微服务。本章主要讲解天气数据采集微服务的实现。各微服务的主要功能天气数据采集微服务的实现配置pom文件对原来单块架构的天气预报服务进行改进,去除多余的依赖,最终的pom文件如下:<?xml version=“1.0” encoding=“UTF-8”?><project xmlns=“http://maven.apache.org/POM/4.0.0" xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=“http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.demo</groupId> <artifactId>sifoudemo02</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>sifoudemo02</name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.5.RELEASE</version> <relativePath/> <!– lookup parent from repository –> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-jdk14</artifactId> <version>1.7.7</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.7</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz</artifactId> <version>2.3.0</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <fork>true</fork> </configuration> <!– <groupId>org.apache.felix</groupId> <artifactId>maven-bundle-plugin</artifactId> <extensions>true</extensions> –> </plugin> </plugins> </build> </project>提供接口在Service中保留根据城市的Id同步天气数据的接口。实现方式为通过城市Id调用第三方接口获取对应天气数据,并将其同步到Redis缓存中。@Servicepublic class WeatherDataCollectionServiceImpl implements WeatherDataCollectionService{ private static final String WEATHER_URL = “http://wthrcdn.etouch.cn/weather_mini?"; //设置缓存无效的时间 private static final long TIME_OUT = 1800L; // 1800s //httpClient的客户端 @Autowired private RestTemplate restTemplate; @Autowired private StringRedisTemplate stringRedisTemplate; //根据城市的Id同步天气数据 @Override public void syncDataByCityId(String cityId) { String url = WEATHER_URL + “citykey=” + cityId; this.saveWeatherData(url); } //把天气数据放在缓存中 private void saveWeatherData(String url) { //将rul作为天气数据的key进行保存 String key=url; String strBody=null; ValueOperations<String, String>ops=stringRedisTemplate.opsForValue(); //调用服务接口来获取数据 ResponseEntity<String>respString=restTemplate.getForEntity(url, String.class); //判断请求状态 if(respString.getStatusCodeValue()==200) { strBody=respString.getBody(); } ops.set(key, strBody,TIME_OUT,TimeUnit.SECONDS); } }定时任务保留根据城市列表同步全部天气数据的定时任务,这里的城市列表后期会通过调用城市数据API微服务得到。//同步天气数据public class WeatherDataSyncJob extends QuartzJobBean{ private final static Logger logger = LoggerFactory.getLogger(WeatherDataSyncJob.class); @Autowired private WeatherDataCollectionService weatherDataCollectionService; @Override protected void executeInternal(JobExecutionContext arg0) throws JobExecutionException { logger.info(“Weather Data Sync Job. Start!”); //城市ID列表 //TODO 改为由城市数据API微服务来提供城市列表的数据 List<City>cityList=null; try { //TODO 改为由城市数据API微服务来提供城市列表的数据 //获取xml中的城市ID列表 //cityList=cityDataService.listCity(); cityList=new ArrayList<>(); City city=new City(); city.setCityId(“101280601”); cityList.add(city); } catch (Exception e) {// e.printStackTrace(); logger.error(“Exception!”, e); } //遍历所有城市ID获取天气 for(City city:cityList) { String cityId=city.getCityId(); logger.info(“Weather Data Sync Job, cityId:” + cityId); //实现根据cityid定时同步天气数据到缓存中 weatherDataCollectionService.syncDataByCityId(cityId); } logger.info(“Weather Data Sync Job. End!”); } } ...

March 7, 2019 · 2 min · jiezi

容错性好、易于管理和便于观察:浅谈如何利用K8s全面拥抱微服务架构

KubeCon + CloudNativeCon 论坛,作为 CNCF 的旗舰会议,自2016年以来已经在北美和欧洲两地的旧金山、伦敦、硅丘(奥斯汀)、哥本哈根等知名城市举办。2018年11月15日,KubeCon + CloudNativeCon 论坛首次来到中国,在上海跨国采购会展中心召开并获得了圆满成功。在去年的论坛上,CNCF不仅邀请到了Liz Rice、Janet Kuo等开源技术大牛到场为KubeCon + CloudNativeCon 在中国举行的首秀呐喊助威,更吸引到了包括美国、日本、印度在内的五大洲48个国家的开源精英前来参与。期间,有954家公司派代表到场展示自己的理念或者聆听精彩的技术分享。据统计,2018年盛会的参会者超过2500人,CNCF 为参会者提供了13场次的主题演讲、97场次的分组会议、55场次的维护者会议和15场次的快闪演讲。华为云、阿里云、腾讯云、微软、GitLab、Lyft等众多知名企业纷纷登台为大家分享相关的产品和技术架构,并为在场观众解答了相关问题。在上届论坛上,大家询问最多的,就是在 K8s(Kubernetes)架构上的使用精髓,而其中,比较有代表性的问题,则是企业如何利用 K8s 作为微服务架构,并且如何通过这样的部署来提升企业的自身价值。那么,什么是微服务架构呢?其实,微服务架构是与当前常用的单体架构相比较而言的。单体架构虽然方便管理,易于规划,但是灵活性和稳定性还是有所欠缺的,甚至一旦局部架构受损则可能面临全盘崩溃的危险;而且单体架构的迭代操作,比较复杂,部署速度慢不说,还会出现阻塞持续集成的现象,这对于要求时效性且相对复杂的生产环境来说,是不言而喻的灾难。基于此,便出现了小的模块化的自治服务架构,即我们常说的微服务架构。因为它所有的节点都被连接到了API网关,所以API网关的用户都会自动连接到这个完整的系统来调用或者聚合所需资源,来完成整个工作。所以,微服务架构在目前来看,基本解决了单体架构的弊端,这如同使用并联的灯泡组,即使一个灯泡损坏,亦不会影响全局的稳定和整体的生产进度,更不会出现整个局部系统的损坏而导致整体系统全面崩塌的灾难性后果。众所周知的是,任何一种架构的运用都会面临一些问题,我们采用的微服务架构也不例外。虽然微服务架构本身具有稳定、轻量、高速的特性,但是在现实的企业生产环境部署过程中,也会出现诸如调度、负载均衡、集群管理、有状态数据的管理等问题。而作为一切以服务为中心的K8s,则为我们提供了解决上述问题的最佳方式。K8s拥抱微服务就成为大势所趋。作为上届大会的焦点 - K8s如何更好的拥抱微服务?主题演讲有这些分享:Zhenqin (Alan) Liao(华为云 PaaS 服务产品部部长)的“垂直扩展,Kubernetes 如何加速各行业的云原生移动”Vicki Cheung(Lyft 工程经理)的“Kubernetes 是架构的基础层”Brendan Burns(杰出工程师及Microsoft K8s 联合创始人)的“Kubernetes 无服务器架构的现在和未来”专题演讲也分享了不少:Tony Erwin和Jonathan Schweikhart(IBM)的“将企业微服务从Cloud Foundry迁移到Kubernetes”Xiang Li(阿里巴巴)的“日新月异的 Sigma:在阿里巴巴使用 Kubernetes”Dan Romlein和Spencer Sugarman(Google)的“用户介面:利用 Kubernetes Dashboard 并决定其未来”Peter Zhao和Yuan Ji(ZTE)的“Kubernetes:使用、贡献并享受它!“Land Lu和Zhang Lei Mao(Canonical)的“利用 MicroK8s 和 Kubeflow 达成的 Kubernetes CICD 小技巧”Hui Chi(PetroChina)和Kai Chen(Alauda)的“石油巨头与Kubernetes, Microservice & DevOps共舞”Shikha Srivastava和Erica Brown(IBM)的“通过 Kubernetes 实现从容器化应用到安全和缩放”YIN SUN(小米)的“从 Mesos 到 Kubernetes”那么,在2019年,这些议题又会有哪些大咖加以完善,并与您分享更新的实战观点呢?敬请关注,今年2019年,CNCF和LF为您即将呈现的开源技术盛宴 - KubeCon + CloudNativeCon 和 Open SourceSummit(原LC3)。KubeCon + CloudNativeCon和Open Source Summit大会日期:会议日程通告日期:2019 年 4 月 10 日会议活动举办日期:2019 年 6 月 24 至 26 日KubeCon + CloudNativeCon和Open Source Summit赞助方案KubeCon + CloudNativeCon和Open Source Summit多元化奖学金现正接受申请KubeCon + CloudNativeCon和Open Source Summit即将首次合体落地中国KubeCon + CloudNativeCon和Open Source Summit购票窗口,立即购票! ...

March 7, 2019 · 1 min · jiezi

服务的拆分 | 从0开始构建SpringCloud微服务(5)

照例附上项目github链接本项目实现的是将一个简单的天气预报系统一步一步改造成一个SpringCloud微服务系统的过程,本节主要讲的是单块架构改造成微服务架构的过程,最终将原来单块架构的天气预报服务拆分为四个微服务:城市数据API微服务,天气数据采集微服务,天气数据API微服务,天气预报微服务。本章主要讲解城市数据API微服务的实现。各微服务的主要功能服务注册机制多个微服务之间获知对方的存在并进行通信,需要通过服务注册机制。当我们的微服务启动的时候,就会将信息注册到服务注册表或者服务注册中心中。中心可以通过心跳机制等感知服务的状态,并广播给其他的微服务。一个服务调用另一个服务,调用其他服务的服务称为服务的消费者,被调用的服务称为服务的提供者。城市数据API微服务的实现配置pom文件对原来单块架构的天气预报服务进行改进,去除多余的依赖,最终的pom文件如下:<?xml version=“1.0” encoding=“UTF-8”?><project xmlns=“http://maven.apache.org/POM/4.0.0" xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=“http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.demo</groupId> <artifactId>sifoudemo02</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>sifoudemo02</name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.5.RELEASE</version> <relativePath/> <!– lookup parent from repository –> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-jdk14</artifactId> <version>1.7.7</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <fork>true</fork> </configuration> <!– <groupId>org.apache.felix</groupId> <artifactId>maven-bundle-plugin</artifactId> <extensions>true</extensions> –> </plugin> </plugins> </build> </project>提供接口在service中保留获取本地xml文件中城市列表的方法listCity。@Servicepublic class CityDataServiceImpl implements CityDataService{ @Override public List<City> listCity() throws Exception { Resource resource=new ClassPathResource(“citylist.xml”); BufferedReader br=new BufferedReader(new InputStreamReader(resource.getInputStream(), “utf-8”)); StringBuffer buffer=new StringBuffer(); String line=””; while((line=br.readLine())!=null) { buffer.append(line); } br.close(); CityList cityList=(CityList)XmlBuilder.xmlStrToOject(CityList.class, buffer.toString()); return cityList.getCityList(); }}在controller中保留获取城市列表的接口。@RestController@RequestMapping("/cities”)public class CityController { @Autowired private CityDataService cityDataService; //返回城市列表 @GetMapping public List<City>listCity()throws Exception{ return cityDataService.listCity(); }}测试结果 ...

March 7, 2019 · 1 min · jiezi

使用Grab的实验平台进行混沌实验编排

Roman Atachiants · Tharaka Wijebandara · Abeesh Thomas原文: https://engineering.grab.com/chaos-engineering译:时序背景对每个用户来说,Grab是一个可以叫车,叫外卖或付款的一个APP。对工程师来说,Grab是一个有许多服务并通过RPC交互的分布式系统,有时也可以叫做微服务架构。在数千台服务器上运行的数百个服务每天都有工程师在上面进行变更。每次复杂的配置,事情可能都会变糟。 幸运的是,很多Grab App的内部服务不像用户叫车那样的动作这么重要。例如,收藏夹可以帮用户记住之前的位置,但如果它们不工作,用户仍然可以得到较合理的用户体验。服务部分可用并不是没有风险。工程师需要对于RPC调用非核心服务时需要有有备用计划。如果应急策略没有很好地执行,非核心服务的问题也可能导致停机。所以我们如何保证Grab的用户可以使用核心功能,例如叫车,而此时非核心服务正在出问题?答案是混沌工程。在Grab,我们通过在整体业务流的内部服务或组件上引入故障来实践混沌工程。但失败的服务不是实验的关注点。我们感兴趣的是测试依赖这个失败服务的服务。照理来说,上游服务应该有弹性并且整体业务流应该可以继续工作。比如,叫车流程就算在司机地址服务上出现故障时仍应该可以工作。我们测试重试和降级是否配置正确,是否熔断器被正确的设置。为了将混沌引入我们的系统,我们使用了我们的实验平台(ExP)和Grab-Kit.混沌实验平台Exp将故障注入到处理流量服务的中间件(gRPC或HTTP服务器)。如果系统的行为与期望一致,你将对非核心服务故障时服务会平稳降级产生信心。混沌实验平台ExP在Grab的基础设施中模拟不同的混沌类型,如延迟和内存泄漏。这保证了每个组件在系统的依赖不响应或响应很高时仍能返回一些东西。它能保证我们对于实例级失败有弹性,因为微服务级别的中断对于可用性也是一个威胁。配置混沌实验为了构建我们的混沌工程系统,我们认为需要在两个主要领域引入混沌:基础设置:随机关闭基础设施的实例和其他部分应用: 在较粗粒度引入运行时故障(如endpoint/request级别)你可以稍后启用有意的或随机的混沌实验:随机的比较适合‘一次性’基础设施(如EC2实例)测试冗余的基础设施对最终用户的影响当影响面已经十分确定实验精确度量影响使用实验参数控制对最终用户有限的影响适用于对于影响不十分确定的复杂故障(如延迟)最后,你可以将故障模式按以下分类:资源:CPU,内存,IO,磁盘网络:黑洞,延迟,丢包,DNS状态:关机,时间,杀进程这些模型都可以在基础设施或应用级别使用或模拟:对于Grab,进行应用级别的混沌实验并仔细度量影响面很重要。我们决定使用一个已有的实验平台来对围绕系统的应用级别混沌实验进行编排,即紫色部分,通过对下层像Grab-Kit这样的中间件进行注入来实现。为什么使用实验平台?现在有一些混沌工程工具。但是,使用它们经常需要较高级的基础设施和运维技巧,有能力设计和执行实验,以受控的方式有资源手工编排失败场景。混沌工程不是简单的在生产环境搞破坏。将混沌工程理解成受控的实验。我们的ExP SDK提供弹性和异步追踪。这样,我们可以将潜在的业务属性度量对应到混沌失败上。比如,在订车服务上进行10秒延迟的混沌故障,我们可以知道多少辆车被影响了进而知道损失了多少钱。使用ExP作为混沌工程的工具意味着我们可以基于应用或环境精确定制,让它可以像监控和部署管道一样与其他环境紧密集成。在安全上也可以获得收益。使用ExP,所有的连接都在我们的内部网络中,给我们攻击表面区域的能力。所有东西都可以掌控在手中,对外部世界没有依赖。这也潜在的使监控和控制流量变容易了。混沌故障可以点对点,编程式的,或定期执行。你可以让它们在特定日期的特定时间窗口来执行。你可以设定故障的最大数量并定制它们(比如泄漏的内存MB数量,等待的秒)。ExP的核心价值是让工程师可以启动,控制和观察系统在各种失败条件下的行为。ExP提供全面的故障原子集,用来设计实验并观察问题在复杂分布式系统发生时的表现。而且,将混沌测试集成到ExP,我们对于部署流水线或网络基础设施不需要任何改动。因此这种组合可以很容易的在各种基础设施和部署范式上使用。我们如何打造Chaos SDK和UI要开发混沌工程SDK,我们使用我们已有ExP SDK的属性 - single-digit , 不需要网络调用。你可以看这里对于ExP SDK的实现。现在我们要做两件事:一个在ExP SDK之上的很小的混沌SDK。我们将这个直接集成在我们的已有中间件,如Grab-Kit和DB层。一个专门的用来创建混沌实验的基于web的UI归功于我们与Grab-Kit的集成,Grab工程师不需要直接使用混沌SDK。当Grab-Kit处理进入的请求时,它先使用ExP SDK进行检查。如果请求“应该失败”,它将产生适合的失败类型。然后它被转发到特定endpoint的处理器。我们现在支持以下失败类型:Error - 让请求产生errorCPU Load - 在CPU上加大load内存泄漏 - 产生一些永远不能释放的内存延迟 - 在一小段随机时间内停止请求的处理磁盘空间 - 在机器上填入一些临时文件Goroutine泄漏 - 创建并泄漏goroutinesPanic -限流 - 在请求上设置一个频率限制并在超过限制时拒绝请求举个例子,如果一个叫车请求到了我们的叫车服务,我们调用GetVariable(“chaosFailure”)来决定请求是否应该成功。请求里包含所有需要用来做决定的信息(如请求ID,实例的IP地址等)。关于实验SDK的实现细节,看这篇博客。为了在我们的工程师中推广混沌工程我们围绕它建立了很好的开发者体验。在Grab不同的工程团队会有很多不同的技术和领域。所以一些人可能没有对应的知识和机能来进行合适的混沌实验。但使用我们简化过的用户界面,他们不需要担心底层实现。并且,运行混沌实验的工程师是与像产品分析师和产品经理不同的实验平台用户。所以我们使用一种简单和定制化UI配置新的混沌实验来提供一种不同的创建实验的体验。在混沌工程平台,一个实验有以下四步:定义系统正常情况下的理想状态。创建一个控制组的配置和一个对比组的配置。控制组的变量使用已有值来赋值。对比组的变量使用新值来赋值。引入真实世界的故障,例如增加CPU负载。找到区分系统正确和失败状态标志性不同。要创建一个混沌实验,标明你想要实验破坏的服务。你可以在以后通过提供环境,可用区或实例列表来更细化这个选择范围。下一步,指定一组会被破坏的服务影响的服务列表。你在试验期间需要仔细监控这些服务。尽管我们持续跟踪表示系统健康的整体度量指标,它仍能帮助你在稍后分析实验的影响。然后,我们提供UI来指定目标组和对比组的策略,失败类型,每个对比组的配置。最后一步,提供时间周期并创建实验。你已经在你的系统中加入了混沌故障并可以监控它对系统的影响了。结论在运行混沌实验后,一般会有两种可能输出。你已经确认了在引入的故障中系统保持了足够的弹性,或你发现了需要修复的问题。如果混沌实验最初被运行在预发环境那么两种都是不错的结果。在第一种场景,你对系统的行为产生了信心。在另一个场景,你在导致停机故障前发现了一个问题。混沌工程是让你工作更简单的工具。通过主动测试和验证你系统的故障模式你减轻了你的运维负担,增加了你的弹性,在晚上也能睡个好觉。本文作者:时序阅读原文本文为云栖社区原创内容,未经允许不得转载。

March 6, 2019 · 1 min · jiezi

配置管理 ACM 在高可用服务 AHAS 流控降级组件中的应用场景

应用配置管理(Application Configuration Management,简称 ACM)是一款应用配置中心产品。基于ACM您可以在微服务、DevOps、大数据等场景下极大地减轻配置管理的工作量,同时保证配置的安全合规。ACM 有着丰富的使用场景,本文将介绍其在 AHAS 流控降级 中的应用。什么是 AHAS 流控降级AHAS 流控降级 是面向分布式服务架构的专业流量控制组件,主要以流量为切入点,从流量控制、熔断降级、系统保护等多个维度帮助您保障服务的稳定性,同时提供强大的聚合监控和历史监控查询功能。在 AHAS 流控降级中,我们需要针对不同的资源(比如服务接口、方法)配置不同的规则(流控规则、降级规则、系统保护规则等)。由于流量的不确定性,我们的规则也需要根据流量的实时情况进行动态管理。AHAS 流控降级使用了 ACM 作为动态配置中心,借助其实时动态推送的能力达到规则实时推送的效果。如何使用 ACMAHAS 流控降级分为两部分:客户端(基于开源的 Sentinel)以及AHAS 控制台。用户使用时只需要引入 AHAS Sentinel 客户端相关依赖 ahas-sentinel-client 并在启动时指定相关参数即可接入到 AHAS 流控降级控制台,在 AHAS 控制台上查看监控、配置规则。Sentinel 抽象出了动态数据源接口,可以方便地对接任意配置中心。Sentinel 推荐使用 push 模式的动态规则源,推送流程为 Sentinel 控制台 → 配置中心 → Sentinel 数据源 → Sentinel,非常清晰:AHAS 流控降级客户端提供了 ACM 动态规则源适配,实现了监听远程规则变更的逻辑,而 AHAS 流控降级控制台实现了相应的规则推送逻辑。用户在 AHAS 流控降级控制台保存规则的时候,AHAS 控制台会在保存规则后将规则推送至 ACM 相应的坐标上,ACM 会实时地将规则 push 到接入端。AHAS 流控降级客户端的动态配置源会自动注册当前应用对应坐标的监听器监听规则变化,当监听到变更时就将其加载到 Sentinel 的规则管理器中,新的规则就生效了。以上就是 ACM 在 AHAS 流控降级中的应用场景,有关 ACM 的更多信息可以参考官方文档。本文作者:中间件小哥阅读原文本文为云栖社区原创内容,未经允许不得转载。

March 5, 2019 · 1 min · jiezi

使用Envoy 作Sidecar Proxy的微服务模式-5.rate limiter

本博客是深入研究Envoy Proxy和Istio.io 以及它如何实现更优雅的方式来连接和管理微服务系列文章的一部分。这是接下来几个部分的想法(将在发布时更新链接):断路器(第一部分)重试/超时(第二部分)分布式跟踪(第三部分)Prometheus的指标收集(第四部分)rate limiter(第五部分)第五部分 - rate limiterEnvoy ratelimit filtersEnvoy通过两个过滤器与Ratelimit服务集成:Network Level Filter: envoy为安装过滤器的侦听器上的每个新连接调用Ratelimit服务。这样,您可以对通过侦听器的每秒连接进行速率限制。HTTP Level Filter:Envoy为安装过滤器的侦听器上的每个新请求调用Ratelimit服务,路由表指定应调用Ratelimit服务。许多工作都在扩展HTTP过滤器的功能。envoy 配置 启用 http rate limiterhttp rate limiter 当请求的路由或虚拟主机具有与过滤器阶段设置匹配的一个或多个速率限制配置时,HTTP速率限制过滤器将调用速率限制服务。该路由可以选择包括虚拟主机速率限制配置。多个配置可以应用于请求。每个配置都会导致将描述符发送到速率限制服务。如果调用速率限制服务,并且任何描述符的响应超出限制,则返回429响应。速率限制过滤器还设置x-envoy-ratelimited标头。果在呼叫速率限制服务中出现错误或速率限制服务返回错误并且failure_mode_deny设置为true,则返回500响应。全部的配置如下: envoy.yaml: |- static_resources: listeners: - address: socket_address: address: 0.0.0.0 port_value: 8000 filter_chains: - filters: - name: envoy.http_connection_manager config: codec_type: auto stat_prefix: ingress_http access_log: - name: envoy.file_access_log config: path: “/dev/stdout” format: “[ACCESS_LOG][%START_TIME%] "%REQ(:METHOD)% %REQ(X-ENVOY-ORIGINAL-PATH?:PATH)% %PROTOCOL%" %RESPONSE_CODE% %RESPONSE_FLAGS% %BYTES_RECEIVED% %BYTES_SENT% %DURATION% %RESP(X-ENVOY-UPSTREAM-SERVICE-TIME)% "%REQ(X-FORWARDED-FOR)%" "%REQ(USER-AGENT)%" "%REQ(X-REQUEST-ID)%" "%REQ(:AUTHORITY)%" "%UPSTREAM_HOST%" "%DOWNSTREAM_REMOTE_ADDRESS_WITHOUT_PORT%"\n” route_config: name: local_route virtual_hosts: - name: gateway domains: - “*” routes: - match: prefix: “/cost” route: cluster: cost rate_limits: # enable rate limit checks for the greeter service actions: - destination_cluster: {} http_filters: - name: envoy.rate_limit # enable the Rate Limit filter config: domain: envoy - name: envoy.router config: {} clusters: - name: cost connect_timeout: 0.25s type: strict_dns lb_policy: round_robin hosts: - socket_address: address: cost.sgt port_value: 80 - name: rate_limit_cluster type: strict_dns connect_timeout: 0.25s lb_policy: round_robin http2_protocol_options: {} hosts: - socket_address: address: limiter.sgt port_value: 80 rate_limit_service: grpc_service: envoy_grpc: cluster_name: rate_limit_cluster timeout: 0.25s admin: access_log_path: “/dev/null” address: socket_address: address: 0.0.0.0 port_value: 9000 通过配置文件可以看出,本demo设置的是一个全局的http filter rate limiter。尽管分布式熔断通常在控制分布式系统中的吞吐量方面非常有效,但有时它不是非常有效并且需要全局速率限制。最常见的情况是当大量主机转发到少量主机并且平均请求延迟较低时(例如,对数据库服务器的连接/请求)。如果目标主机已备份,则下游主机将淹没上游群集。在这种情况下,在每个下游主机上配置足够严格的断路限制是非常困难的,这样系统在典型的请求模式期间将正常运行,但在系统开始出现故障时仍能防止级联故障。全局速率限制是这种情况的一个很好的解决方案。编写rate limiter 服务Envoy直接通过gRPC与速率限制服务集成。Envoy要求速率限制服务支持rls.proto中指定的gRPC IDL。有关API如何工作的更多信息,请参阅IDL文档。本身envoy 只是提供了限流的接口,没有具体的实现,所以必须自己实现一个限流器。下面只是简单实现一下,给大家一个思路。具体的代码如下:package mainimport ( “log” “net” “time” rls “github.com/envoyproxy/go-control-plane/envoy/service/ratelimit/v2” “github.com/juju/ratelimit” “golang.org/x/net/context” “google.golang.org/grpc” “google.golang.org/grpc/reflection”)// server is used to implement rls.RateLimitServicetype server struct { bucket *ratelimit.Bucket}func (s *server) ShouldRateLimit(ctx context.Context, request *rls.RateLimitRequest) (rls.RateLimitResponse, error) { // logic to rate limit every second request var overallCode rls.RateLimitResponse_Code if s.bucket.TakeAvailable(1) == 0 { overallCode = rls.RateLimitResponse_OVER_LIMIT } else { overallCode = rls.RateLimitResponse_OK } response := &rls.RateLimitResponse{OverallCode: overallCode} return response, nil}func main() { // create a TCP listener on port 8089 lis, err := net.Listen(“tcp”, “:8089”) if err != nil { log.Fatalf(“failed to listen: %v”, err) } log.Printf(“listening on %s”, lis.Addr()) // create a gRPC server and register the RateLimitService server s := grpc.NewServer() rls.RegisterRateLimitServiceServer(s, &server{ bucket: ratelimit.NewBucket(100time.Microsecond, 100), }) reflection.Register(s) if err := s.Serve(lis); err != nil { log.Fatalf(“failed to serve: %v”, err) }}具体项目,查阅github。PS:使用了令牌桶算法来限流。令牌桶算法(Token Bucket)和 Leaky Bucket 效果一样但方向相反的算法,更加容易理解.随着时间流逝,系统会按恒定1/QPS时间间隔(如果QPS=100,则间隔是10ms)往桶里加入Token(想象和漏洞漏水相反,有个水龙头在不断的加水),如果桶已经满了就不再加了.新请求来临时,会各自拿走一个Token,如果没有Token可拿了就阻塞或者拒绝服务.该实现存在单点风险。Dockerfile均在代码仓库中,大家可以构建镜像自己测试。结论本文简单讲了envoy的 rate limit功能,提供了全局限流的配置文件,并简单实现了一个基于令牌桶的限流器。希望能帮助你理解Envoy的限速过滤器如何跟gRPC协议协同工作。 ...

March 1, 2019 · 2 min · jiezi

微服务前端开发框架React-Admin

前言React-Admin是基于React16.x、Ant Design3.x的管理系统架构。 采用前后端分离,内置了许多管理系统常用功能,通过一些脚本、封装帮助开发人员快速开发管理系统,集中精力处理业务逻辑。背景一般的互联网公司内部或者对外都会有大量的管理系统,传统公司一般有后端开发人员进行管理系统开发,所用技术大多都是jsp、模版语言等。 随着公司的发展,管理系统数量和复杂度不端增加,开发人员耗费大量的精力在开发和维护管理系统上。 由于管理系统大多是公司内部使用,很多公司、团队并不重视,导致可用性差、bug多、页面丑陋。使用者怨声载道,开发者苦不堪言。。。技术迭代随着前端的技术发展迭代,传统的基于后端模版语言、jQuery的开发方式已经过时,难以应对复杂的需求、快速的项目迭代。 近几年随着React、Es6等技术的兴起,让前端可以与后端分离、可以组件化模块化,拥有了构建大型复杂项目的能力。困难WebPack开发五分钟,配置两小时,各种解决方案要结合:React、组件库、Redux、Router、ajax、国际化、主题、Less、CSS Module。。。 社区上有很多好的工具、组件,但是整合起来形成系统的解决方案的寥寥无几。要自己做一个?基础代码才刚刚写的溜,还要加班改bug,哪儿有时间搞框架React-Admin介绍React-Admin专注于解决管理系统开发过程中涉及到的一些列问题,采用最新的前端技术栈:React、ES6+、组件化、模块化等。针对于管理系统,整合了大量开源社区优秀的组件、工具库;集成了大量管理系统常用功能!项目截图这里只提供了部分页面截图,根据文档快速开始进行项目的搭建,浏览项目丰富功能!项目结构社区常用标准化目录组织。.├── config // 构建配置├── nginx-conf // 生产部署nginx配置参考├── public // 不参与构建的静态文件├── scripts // 构建脚本├── src│ ├── commons // 通用js│ ├── components // 通用组件│ ├── i18n // 国际化│ ├── layouts // 页面框架布局组件│ ├── library // 基础组件│ ├── mock // 模拟数据│ ├── models // 模块封装,基于redux,提供各组件共享数据、共享逻辑│ ├── pages // 页面组件│ ├── router // 路由│ ├── App.js // 根组件│ ├── index.css // 全局样式│ ├── index.js // 项目入口│ ├── menus.js // 菜单配置│ ├── setupProxy.js // 后端联调代理配置│ └── theme.js // 主题变量├── package.json├── README.md└── yarn.lock功能经过多年的沉淀积累、筛选迭代,系统目前集成功能如下:菜单配置:扁平化数据组织,方便编写、存库;页面配置:提供配置装饰器,页面功能配置化;系统路由:简化路由配置,一个变量搞定;Ajax请求:restful规范,自动错误提示,提示可配置;Mock模拟数据:无需等待后端,自己提供数据,简化mock写法;样式&主题:Less简化css编写,CSS Module防冲突,用户可自定义主题颜色;导航布局:多种导航方式,一键切换;Model(Redux):简化Redux写法,配置化同步LocalStorage;国际化:多种语言支持;权限控制:菜单级别、功能级别权限控制;Nginx配置:内置配置参考;开发代理:开发过程中与后端调试接口;代码生成工具:CRUD基础一键生成,提高开发效率;系统提供了一些基础的页面: * 登录 * 修改密码 * 菜单编辑 * 用户管理 * 角色管理部分功能介绍系统集成了大量功能,简单介绍几个。还有许多的功能,就不一一介绍了,感兴趣可以戳这里;菜单菜单往往涉及到了树的操作、状态选中、布局等等问题,系统内置了菜单功能:系统内置菜单权限编辑页面国际化支持权限支持菜单支持头部、左侧、头部+左侧三种布局方式;系统会基于路由path自动选中对应的菜单;无菜单对应的二级页面也可以选中相应父级菜单;左侧菜单会自动滚动到可视范围内;左侧菜单支持展开收起、拖拽改变宽度页面标题、tab标签页标题、面包屑基于菜单状态自动生成,但也提供了对应的修改方式;通过菜单配置,支持内嵌iframe打开页面、a标签方式打开页面;路由基于React-Router做系统路由,开发人员也要写配置,随着系统不断壮大,配置文件也越来越大,多人协作各种git冲突React-Admin内置路由封装,无需写配置,只写一个变量就好@config({ path: ‘/path’,})export default class SomePage extends React.Component { …}导航布局系统内置多种导航布局方式,一键切换:头部菜单左侧菜单头部+左侧菜单tab页方式页面保持列表页经过查询、翻页等操作找到一条记录,点击编辑页面跳转,再跳转回列表页,列表页初始化了,还要重新查找。 如果页面每次切换,都能保持之前的操作状态多好!React-Admin底层封装了,一键开启,无需其他特殊编码。Model(Redux)Redux很强大,也很好用,但是写法也忒复杂了吧,大量的样板代码~我就想跨组件共享个数据! React-Admin基于Redux做了封装,用Redux,只写一个函数就好!// page.model.jsexport default { initialState: { title: void 0, }, setTitle: title => ({title}),}// 使用this.props.action.page.setTitle(‘my title’);项目地址开源中国:https://gitee.com/sxfad/react-admin.gitGitHub:https://github.com/sxfad/react-admin.git文档:https://open.vbill.cn/react-admin/ ...

March 1, 2019 · 1 min · jiezi

Quartz实现数据同步 | 从0开始构建SpringCloud微服务(3)

照例附上项目github链接本项目实现的是将一个简单的天气预报系统一步一步改造成一个SpringCloud微服务系统的过程,本节主要讲的是通过引入Quartz实现天气数据的同步。存在问题当用户请求我们的数据的时候才去拉最新的数据,并将其更新到Redis缓存中,效率较低。且缓存中的数据只要存在就不再次做请求,不对数据进行更新,但是天气数据大概是每半个小时就做一次更新的,所以我们传给用户的数据可能不是较新的,数据存在一定误差。解决方案通过作业调度框架Quartz实现天气数据的自动同步。前期工作要实现定时拉取接口中的数据到Redis缓存中,需要一个城市Id的列表。通过对城市Id列表的遍历,调用weatherDataService中根据城市Id同步数据到Redis中的syncDataByCityId方法,我们就能实现所有城市数据的同步了。城市列表的构建由于在程序运行的过程中动态调用服务是有延时的,所以需要减少动态调用服务,因此我们将城市列表缓存到本地。xml文件的构建使用xml文件将列表存储到本地中,需要的时候再从本地进行读取,这样会比调用第三方的服务更快。xml文件如下:<?xml version=“1.0” encoding=“UTF-8”?><c c1=“0”><d d1=“101280101” d2=“广州” d3=“guangzhou” d4=“广东”/><d d1=“101280102” d2=“番禺” d3=“panyu” d4=“广东”/><d d1=“101280103” d2=“从化” d3=“conghua” d4=“广东”/><d d1=“101280104” d2=“增城” d3=“zengcheng” d4=“广东”/><d d1=“101280105” d2=“花都” d3=“huadu” d4=“广东”/><d d1=“101280201” d2=“韶关” d3=“shaoguan” d4=“广东”/><d d1=“101280202” d2=“乳源” d3=“ruyuan” d4=“广东”/><d d1=“101280203” d2=“始兴” d3=“shixing” d4=“广东”/><d d1=“101280204” d2=“翁源” d3=“wengyuan” d4=“广东”/><d d1=“101280205” d2=“乐昌” d3=“lechang” d4=“广东”/><d d1=“101280206” d2=“仁化” d3=“renhua” d4=“广东”/><d d1=“101280207” d2=“南雄” d3=“nanxiong” d4=“广东”/><d d1=“101280208” d2=“新丰” d3=“xinfeng” d4=“广东”/><d d1=“101280209” d2=“曲江” d3=“qujiang” d4=“广东”/><d d1=“101280210” d2=“浈江” d3=“chengjiang” d4=“广东”/><d d1=“101280211” d2=“武江” d3=“wujiang” d4=“广东”/><d d1=“101280301” d2=“惠州” d3=“huizhou” d4=“广东”/><d d1=“101280302” d2=“博罗” d3=“boluo” d4=“广东”/><d d1=“101280303” d2=“惠阳” d3=“huiyang” d4=“广东”/><d d1=“101280304” d2=“惠东” d3=“huidong” d4=“广东”/><d d1=“101280305” d2=“龙门” d3=“longmen” d4=“广东”/><d d1=“101280401” d2=“梅州” d3=“meizhou” d4=“广东”/><d d1=“101280402” d2=“兴宁” d3=“xingning” d4=“广东”/><d d1=“101280403” d2=“蕉岭” d3=“jiaoling” d4=“广东”/><d d1=“101280404” d2=“大埔” d3=“dabu” d4=“广东”/><d d1=“101280406” d2=“丰顺” d3=“fengshun” d4=“广东”/><d d1=“101280407” d2=“平远” d3=“pingyuan” d4=“广东”/><d d1=“101280408” d2=“五华” d3=“wuhua” d4=“广东”/><d d1=“101280409” d2=“梅县” d3=“meixian” d4=“广东”/><d d1=“101280501” d2=“汕头” d3=“shantou” d4=“广东”/><d d1=“101280502” d2=“潮阳” d3=“chaoyang” d4=“广东”/><d d1=“101280503” d2=“澄海” d3=“chenghai” d4=“广东”/><d d1=“101280504” d2=“南澳” d3=“nanao” d4=“广东”/><d d1=“101280601” d2=“深圳” d3=“shenzhen” d4=“广东”/><d d1=“101280701” d2=“珠海” d3=“zhuhai” d4=“广东”/><d d1=“101280702” d2=“斗门” d3=“doumen” d4=“广东”/><d d1=“101280703” d2=“金湾” d3=“jinwan” d4=“广东”/><d d1=“101280800” d2=“佛山” d3=“foshan” d4=“广东”/><d d1=“101280801” d2=“顺德” d3=“shunde” d4=“广东”/><d d1=“101280802” d2=“三水” d3=“sanshui” d4=“广东”/><d d1=“101280803” d2=“南海” d3=“nanhai” d4=“广东”/><d d1=“101280804” d2=“高明” d3=“gaoming” d4=“广东”/><d d1=“101280901” d2=“肇庆” d3=“zhaoqing” d4=“广东”/><d d1=“101280902” d2=“广宁” d3=“guangning” d4=“广东”/><d d1=“101280903” d2=“四会” d3=“sihui” d4=“广东”/><d d1=“101280905” d2=“德庆” d3=“deqing” d4=“广东”/><d d1=“101280906” d2=“怀集” d3=“huaiji” d4=“广东”/><d d1=“101280907” d2=“封开” d3=“fengkai” d4=“广东”/><d d1=“101280908” d2=“高要” d3=“gaoyao” d4=“广东”/><d d1=“101281001” d2=“湛江” d3=“zhanjiang” d4=“广东”/><d d1=“101281002” d2=“吴川” d3=“wuchuan” d4=“广东”/><d d1=“101281003” d2=“雷州” d3=“leizhou” d4=“广东”/><d d1=“101281004” d2=“徐闻” d3=“xuwen” d4=“广东”/><d d1=“101281005” d2=“廉江” d3=“lianjiang” d4=“广东”/><d d1=“101281006” d2=“赤坎” d3=“chikan” d4=“广东”/><d d1=“101281007” d2=“遂溪” d3=“suixi” d4=“广东”/><d d1=“101281008” d2=“坡头” d3=“potou” d4=“广东”/><d d1=“101281009” d2=“霞山” d3=“xiashan” d4=“广东”/><d d1=“101281010” d2=“麻章” d3=“mazhang” d4=“广东”/><d d1=“101281101” d2=“江门” d3=“jiangmen” d4=“广东”/><d d1=“101281103” d2=“开平” d3=“kaiping” d4=“广东”/><d d1=“101281104” d2=“新会” d3=“xinhui” d4=“广东”/><d d1=“101281105” d2=“恩平” d3=“enping” d4=“广东”/><d d1=“101281106” d2=“台山” d3=“taishan” d4=“广东”/><d d1=“101281107” d2=“蓬江” d3=“pengjiang” d4=“广东”/><d d1=“101281108” d2=“鹤山” d3=“heshan” d4=“广东”/><d d1=“101281109” d2=“江海” d3=“jianghai” d4=“广东”/><d d1=“101281201” d2=“河源” d3=“heyuan” d4=“广东”/><d d1=“101281202” d2=“紫金” d3=“zijin” d4=“广东”/><d d1=“101281203” d2=“连平” d3=“lianping” d4=“广东”/><d d1=“101281204” d2=“和平” d3=“heping” d4=“广东”/><d d1=“101281205” d2=“龙川” d3=“longchuan” d4=“广东”/><d d1=“101281206” d2=“东源” d3=“dongyuan” d4=“广东”/><d d1=“101281301” d2=“清远” d3=“qingyuan” d4=“广东”/><d d1=“101281302” d2=“连南” d3=“liannan” d4=“广东”/><d d1=“101281303” d2=“连州” d3=“lianzhou” d4=“广东”/><d d1=“101281304” d2=“连山” d3=“lianshan” d4=“广东”/><d d1=“101281305” d2=“阳山” d3=“yangshan” d4=“广东”/><d d1=“101281306” d2=“佛冈” d3=“fogang” d4=“广东”/><d d1=“101281307” d2=“英德” d3=“yingde” d4=“广东”/><d d1=“101281308” d2=“清新” d3=“qingxin” d4=“广东”/><d d1=“101281401” d2=“云浮” d3=“yunfu” d4=“广东”/><d d1=“101281402” d2=“罗定” d3=“luoding” d4=“广东”/><d d1=“101281403” d2=“新兴” d3=“xinxing” d4=“广东”/><d d1=“101281404” d2=“郁南” d3=“yunan” d4=“广东”/><d d1=“101281406” d2=“云安” d3=“yunan” d4=“广东”/><d d1=“101281501” d2=“潮州” d3=“chaozhou” d4=“广东”/><d d1=“101281502” d2=“饶平” d3=“raoping” d4=“广东”/><d d1=“101281503” d2=“潮安” d3=“chaoan” d4=“广东”/><d d1=“101281601” d2=“东莞” d3=“dongguan” d4=“广东”/><d d1=“101281701” d2=“中山” d3=“zhongshan” d4=“广东”/><d d1=“101281801” d2=“阳江” d3=“yangjiang” d4=“广东”/><d d1=“101281802” d2=“阳春” d3=“yangchun” d4=“广东”/><d d1=“101281803” d2=“阳东” d3=“yangdong” d4=“广东”/><d d1=“101281804” d2=“阳西” d3=“yangxi” d4=“广东”/><d d1=“101281901” d2=“揭阳” d3=“jieyang” d4=“广东”/><d d1=“101281902” d2=“揭西” d3=“jiexi” d4=“广东”/><d d1=“101281903” d2=“普宁” d3=“puning” d4=“广东”/><d d1=“101281904” d2=“惠来” d3=“huilai” d4=“广东”/><d d1=“101281905” d2=“揭东” d3=“jiedong” d4=“广东”/><d d1=“101282001” d2=“茂名” d3=“maoming” d4=“广东”/><d d1=“101282002” d2=“高州” d3=“gaozhou” d4=“广东”/><d d1=“101282003” d2=“化州” d3=“huazhou” d4=“广东”/><d d1=“101282004” d2=“电白” d3=“dianbai” d4=“广东”/><d d1=“101282005” d2=“信宜” d3=“xinyi” d4=“广东”/><d d1=“101282006” d2=“茂港” d3=“maogang” d4=“广东”/><d d1=“101282101” d2=“汕尾” d3=“shanwei” d4=“广东”/><d d1=“101282102” d2=“海丰” d3=“haifeng” d4=“广东”/><d d1=“101282103” d2=“陆丰” d3=“lufeng” d4=“广东”/><d d1=“101282104” d2=“陆河” d3=“luhe” d4=“广东”/></c>创建如下两个类,并且根据xml的内容定义其属性。package com.demo.vo;import javax.xml.bind.annotation.XmlAccessType;import javax.xml.bind.annotation.XmlAccessorType;import javax.xml.bind.annotation.XmlAttribute;import javax.xml.bind.annotation.XmlRootElement;@XmlRootElement(name=“d”)@XmlAccessorType(XmlAccessType.FIELD)public class City { @XmlAttribute(name=“d1”) private String cityId; @XmlAttribute(name=“d2”) private String cityName; @XmlAttribute(name=“d3”) private String cityCode; @XmlAttribute(name=“d4”) private String province; public String getCityId() { return cityId; } public void setCityId(String cityId) { this.cityId = cityId; } public String getCityName() { return cityName; } public void setCityName(String cityName) { this.cityName = cityName; } public String getCityCode() { return cityCode; } public void setCityCode(String cityCode) { this.cityCode = cityCode; } public String getProvince() { return province; } public void setProvince(String province) { this.province = province; } }package com.demo.vo;import java.util.List;import javax.xml.bind.annotation.XmlAccessType;import javax.xml.bind.annotation.XmlAccessorType;import javax.xml.bind.annotation.XmlElement;import javax.xml.bind.annotation.XmlRootElement;@XmlRootElement(name = “c”)@XmlAccessorType(XmlAccessType.FIELD)public class CityList { @XmlElement(name = “d”) private List<City> cityList; public List<City> getCityList() { return cityList; } public void setCityList(List<City> cityList) { this.cityList = cityList; }}引入工具类,实现将xml转换成java对象的过程。public class XmlBuilder { /** * 将XML转为指定的POJO * @param clazz * @param xmlStr * @return * @throws Exception */ public static Object xmlStrToOject(Class<?> clazz, String xmlStr) throws Exception { Object xmlObject = null; Reader reader = null; JAXBContext context = JAXBContext.newInstance(clazz); // XML 转为对象的接口 Unmarshaller unmarshaller = context.createUnmarshaller(); reader = new StringReader(xmlStr); xmlObject = unmarshaller.unmarshal(reader); if (null != reader) { reader.close(); } return xmlObject; }}获取城市列表的接口创建CityDataService,定义获取城市列表的方法。@Servicepublic class CityDataServiceImpl implements CityDataService{ @Override public List<City> listCity() throws Exception { Resource resource=new ClassPathResource(“citylist.xml”); BufferedReader br=new BufferedReader(new InputStreamReader(resource.getInputStream(), “utf-8”)); StringBuffer buffer=new StringBuffer(); String line=""; while((line=br.readLine())!=null) { buffer.append(line); } br.close(); CityList cityList=(CityList)XmlBuilder.xmlStrToOject(CityList.class, buffer.toString()); return cityList.getCityList(); }}根据城市Id同步天气数据的接口首先通过城市Id构建对应天气数据的url,然后通过restTemplate的getForEntity方法发起请求,获取返回的内容后使用set方法将其保存到Redis服务器中。 @Override public void syncDataByCityId(String cityId) { String url=WEATHER_URI+“citykey=” + cityId; this.saveWeatherData(url); } //将天气数据保存到缓存中,不管缓存中是否存在数据 private void saveWeatherData(String url) { //将url作为天气的key进行保存 String key=url; String strBody=null; ValueOperations<String, String>ops=stringRedisTemplate.opsForValue(); //通过客户端的get方法发起请求 ResponseEntity<String>respString=restTemplate.getForEntity(url, String.class); //判断请求状态 if(respString.getStatusCodeValue()==200) { strBody=respString.getBody(); } ops.set(key, strBody,TIME_OUT,TimeUnit.SECONDS); }Quartz的引入Quartz是一个Quartz是一个完全由java编写的开源作业调度框架,在这里的功能相当于一个定时器,定时执行指定的任务。创建同步天气数据的任务在Quartz中每个任务就是一个job,在这里我们创建一个同步天气数据的job。通过cityDataService的listCity方法获取xml文件中所有城市的列表,通过对城市列表的迭代得到所有城市的Id,然后通过weatherDataService的syncDataByCityId方法将对应Id的城市天气数据更新到Redis缓存中//同步天气数据public class WeatherDataSyncJob extends QuartzJobBean{ private final static Logger logger = LoggerFactory.getLogger(WeatherDataSyncJob.class); @Autowired private CityDataService cityDataService; @Autowired private WeatherDataService weatherDataService; @Override protected void executeInternal(JobExecutionContext arg0) throws JobExecutionException { logger.info(“Weather Data Sync Job. Start!”); //城市ID列表 List<City>cityList=null; try { //获取xml中的城市ID列表 cityList=cityDataService.listCity(); } catch (Exception e) {// e.printStackTrace(); logger.error(“Exception!”, e); } //遍历所有城市ID获取天气 for(City city:cityList) { String cityId=city.getCityId(); logger.info(“Weather Data Sync Job, cityId:” + cityId); //实现根据cityid定时同步天气数据到缓存中 weatherDataService.syncDataByCityId(cityId); } logger.info(“Weather Data Sync Job. End!”); } }配置QuartzTIME设置的是更新的频率,表示每隔TIME秒就执行任务一次。@Configurationpublic class QuartzConfiguration { private static final int TIME = 1800; // 更新频率 // JobDetail @Bean public JobDetail weatherDataSyncJobDetail() { return JobBuilder.newJob(WeatherDataSyncJob.class).withIdentity(“weatherDataSyncJob”) .storeDurably().build(); } // Trigger @Bean public Trigger weatherDataSyncTrigger() { SimpleScheduleBuilder schedBuilder = SimpleScheduleBuilder.simpleSchedule() .withIntervalInSeconds(TIME).repeatForever(); return TriggerBuilder.newTrigger().forJob(weatherDataSyncJobDetail()) .withIdentity(“weatherDataSyncTrigger”).withSchedule(schedBuilder).build(); }}测试结果天气数据同步结果 ...

February 28, 2019 · 4 min · jiezi

Redis提升并发能力 | 从0开始构建SpringCloud微服务(2)

照例附上项目github链接本项目实现的是将一个简单的天气预报系统一步一步改造成一个SpringCloud微服务系统的过程,本节将介绍项目中Redis的引入。Redis下载教程。若对Redis感兴趣,还可以看一下我的另一篇文章造个轮子 | 自己动手写一个Redis存在问题:数据来源于第三方的接口,依赖性太强。可能带来的不良结果:(1)延时性:用户访问我们的时候,我们需要再去访问第三方的接口,我们是数据的中间者,不是数据的产生者,有一定的延时性。(2)访问上限:免费的接口,可能会达到上限。(3)调死:可能将对方的接口给调死。解决方案:使用redis缓存系统,提高整体的并发访问能力。Redis 是一个高性能的key-value数据库,基于内存的缓存系统,对内存的操作时非常快的,所以可以做到及时响应。为什么选择Redis(1)及时响应(2)减少服务调用Redis如何引入Redis是一个key-value结构的数据存储系统,这里我们使用天气数据的uri作为它的key,通过ValueOperations<String, String>ops对象的set方法将数据写入缓存中,通过其get方法可以从缓存中获取数据,并且使用TIME_OUT设置缓存失效的时间。我们并不是每次都去调用第三方的接口,若Redis缓存中有要查找的天气数据,则从缓存中取;若缓存中没有,则请求第三方接口,然后将数据写入Redis缓存中。 private WeatherResponse doGetWeahter(String uri) { String key = uri; String strBody = null; ObjectMapper mapper = new ObjectMapper(); WeatherResponse resp = null; ValueOperations<String, String> ops = stringRedisTemplate.opsForValue(); // 先查缓存,缓存有的取缓存中的数据 if (stringRedisTemplate.hasKey(key)) { logger.info(“Redis has data”); strBody = ops.get(key); } else { logger.info(“Redis don’t has data”); // 缓存没有,再调用服务接口来获取 ResponseEntity<String> respString = restTemplate.getForEntity(uri, String.class); if (respString.getStatusCodeValue() == 200) { strBody = respString.getBody(); } // 数据写入缓存 ops.set(key, strBody, TIME_OUT, TimeUnit.SECONDS); } try { resp = mapper.readValue(strBody, WeatherResponse.class); } catch (IOException e) { //e.printStackTrace(); logger.error(“Error!",e); } return resp; } ...

February 27, 2019 · 1 min · jiezi

微服务架构下,解决数据一致性问题的实践

随着业务的快速发展,应用单体架构暴露出代码可维护性差、容错率低、测试难度大和敏捷交付能力差等诸多问题,微服务应运而生。微服务的诞生一方面解决了上述问题,但是另一方面却引入新的问题,其中主要问题之一就是:如何保证微服务间的业务数据一致性。本文将通过一个商品采购的业务,来看看在Dubbo的微服务架构下,如何通过Fescar来保障业务的数据一致性。本文所述的例子中,Dubbo 和 Fescar 的注册配置服务中心均使用 Nacos。Fescar 0.2.1+ 开始支持 Nacos 注册配置服务中心。业务描述用户采购商品的业务,包含3个微服务:库存服务: 扣减给定商品的库存数量。订单服务: 根据采购请求生成订单。账户服务: 用户账户金额扣减。业务结构图如下:库存服务(StorageService)public interface StorageService { /** * deduct storage count / void deduct(String commodityCode, int count);}订单服务(OrderService)public interface OrderService { /* * create order / Order create(String userId, String commodityCode, int orderCount);}账户服务(AccountService)public interface AccountService { /* * debit balance of user’s account */ void debit(String userId, int money);}说明: 以上三个微服务均是独立部署。8个步骤实现数据一致性Step 1:初始化 MySQL 数据库(需要InnoDB 存储引擎)在 resources/jdbc.properties 修改StorageService、OrderService、AccountService 对应的连接信息。jdbc.account.url=jdbc:mysql://xxxx/xxxxjdbc.account.username=xxxxjdbc.account.password=xxxxjdbc.account.driver=com.mysql.jdbc.Driver# storage db configjdbc.storage.url=jdbc:mysql://xxxx/xxxxjdbc.storage.username=xxxxjdbc.storage.password=xxxxjdbc.storage.driver=com.mysql.jdbc.Driver# order db configjdbc.order.url=jdbc:mysql://xxxx/xxxxjdbc.order.username=xxxxjdbc.order.password=xxxxjdbc.order.driver=com.mysql.jdbc.DriverStep 2:创建 undo_log(用于Fescar AT 模式)表和相关业务表相关建表脚本可在 resources/sql/ 下获取,在相应数据库中执行 dubbo_biz.sql 中的业务建表脚本,在每个数据库执行 undo_log.sql 建表脚本。CREATE TABLE undo_log ( id bigint(20) NOT NULL AUTO_INCREMENT, branch_id bigint(20) NOT NULL, xid varchar(100) NOT NULL, rollback_info longblob NOT NULL, log_status int(11) NOT NULL, log_created datetime NOT NULL, log_modified datetime NOT NULL, ext varchar(100) DEFAULT NULL, PRIMARY KEY (id), KEY idx_unionkey (xid,branch_id)) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;DROP TABLE IF EXISTS storage_tbl;CREATE TABLE storage_tbl ( id int(11) NOT NULL AUTO_INCREMENT, commodity_code varchar(255) DEFAULT NULL, count int(11) DEFAULT 0, PRIMARY KEY (id), UNIQUE KEY (commodity_code)) ENGINE=InnoDB DEFAULT CHARSET=utf8;DROP TABLE IF EXISTS order_tbl;CREATE TABLE order_tbl ( id int(11) NOT NULL AUTO_INCREMENT, user_id varchar(255) DEFAULT NULL, commodity_code varchar(255) DEFAULT NULL, count int(11) DEFAULT 0, money int(11) DEFAULT 0, PRIMARY KEY (id)) ENGINE=InnoDB DEFAULT CHARSET=utf8;DROP TABLE IF EXISTS account_tbl;CREATE TABLE account_tbl ( id int(11) NOT NULL AUTO_INCREMENT, user_id varchar(255) DEFAULT NULL, money int(11) DEFAULT 0, PRIMARY KEY (id)) ENGINE=InnoDB DEFAULT CHARSET=utf8;说明: 需要保证每个物理库都包含 undo_log 表,此处可使用一个物理库来表示上述三个微服务对应的独立逻辑库。Step 3:引入 Fescar、Dubbo 和 Nacos 相关 POM 依赖 <properties> <fescar.version>0.2.1</fescar.version> <dubbo.alibaba.version>2.6.5</dubbo.alibaba.version> <dubbo.registry.nacos.version>0.0.2</dubbo.registry.nacos.version> </properties> <dependency> <groupId>com.alibaba.fescar</groupId> <artifactId>fescar-spring</artifactId> <version>${fescar.version}</version> </dependency> <dependency> <groupId>com.alibaba.fescar</groupId> <artifactId>fescar-dubbo-alibaba</artifactId> <version>${fescar.version}</version> <exclusions> <exclusion> <artifactId>dubbo</artifactId> <groupId>org.apache.dubbo</groupId> </exclusion> </exclusions> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>dubbo</artifactId> <version>${dubbo.alibaba.version}</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>dubbo-registry-nacos</artifactId> <version>${dubbo.registry.nacos.version}</version> </dependency>说明: 由于当前 apache-dubbo 与 dubbo-registry-nacos jar存在兼容性问题,需要排除 fescar-dubbo 中的 apache.dubbo 依赖并手动引入 alibaba-dubbo,后续 apache-dubbo(2.7.1+) 将兼容 dubbo-registry-nacos。在Fescar 中 fescar-dubbo jar 支持 apache.dubbo,fescar-dubbo-alibaba jar 支持 alibaba-dubbo。Step 4:微服务 Provider Spring配置分别在三个微服务Spring配置文件(dubbo-account-service.xml、 dubbo-order-service 和 dubbo-storage-service.xml )进行如下配置:配置 Fescar 代理数据源<bean id=“accountDataSourceProxy” class=“com.alibaba.fescar.rm.datasource.DataSourceProxy”> <constructor-arg ref=“accountDataSource”/></bean><bean id=“jdbcTemplate” class=“org.springframework.jdbc.core.JdbcTemplate”> <property name=“dataSource” ref=“accountDataSourceProxy”/></bean>此处需要使用 com.alibaba.fescar.rm.datasource.DataSourceProxy 包装 Druid 数据源作为直接业务数据源,DataSourceProxy 用于业务 SQL 的拦截解析并与 TC 交互协调事务操作状态。配置 Dubbo 注册中心 <dubbo:registry address=“nacos://${nacos-server-ip}:8848”/>配置 Fescar GlobalTransactionScanner<bean class=“com.alibaba.fescar.spring.annotation.GlobalTransactionScanner”> <constructor-arg value=“dubbo-demo-account-service”/> <constructor-arg value=“my_test_tx_group”/></bean>此处构造方法的第一个参数为业务自定义 applicationId,若在单机部署多微服务需要保证 applicationId 唯一。构造方法的第二个参数为 Fescar 事务服务逻辑分组,此分组通过配置中心配置项 service.vgroup_mapping.my_test_tx_group 映射到相应的 Fescar-Server 集群名称,然后再根据集群名称.grouplist 获取到可用服务列表。Step 5:事务发起方配置在 dubbo-business.xml 配置以下配置:配置 Dubbo 注册中心同 Step 4配置 Fescar GlobalTransactionScanner同 Step 4在事务发起方 service 方法上添加 @GlobalTransactional 注解@GlobalTransactional(timeoutMills = 300000, name = “dubbo-demo-tx”)timeoutMills 为事务的总体超时时间默认60s,name 为事务方法签名的别名,默认为空。注解内参数均可省略。Step 6:启动 Nacos-Server下载 Nacos-Server 最新 release 包并解压运行 Nacos-serverLinux/Unix/Macsh startup.sh -m standaloneWindowscmd startup.cmd -m standalone访问 Nacos 控制台:http://localhost:8848/nacos/index.html#/configurationManagement?dataId=&group=&appName=&namespace若访问成功说明 Nacos-Server 服务运行成功(默认账号/密码: nacos/nacos)Step 7:启动 Fescar-Server下载 Fescar-Server 最新 release 包并解压初始化 Fescar 配置进入到 Fescar-Server 解压目录 conf 文件夹下,确认 nacos-config.txt 的配置值(一般不需要修改),确认完成后运行 nacos-config.sh 脚本初始化配置。sh nacos-config.sh $Nacos-Server-IPeg:sh nacos-config.sh localhost 脚本执行最后输出 “init nacos config finished, please start fescar-server.” 说明推送配置成功。若想进一步确认可登陆Nacos 控制台 配置列表 筛选 Group=FESCAR_GROUP 的配置项。修改 Fescar-server 服务注册方式为 nacos进入到 Fescar-Server 解压目录 conf 文件夹下 registry.conf 修改 type=“nacos” 并配置 Nacos 的相关属性。 registry { # file nacos type = “nacos” nacos { serverAddr = “localhost” namespace = “public” cluster = “default” } file { name = “file.conf” }}type: 可配置为 nacos 和 file,配置为 file 时无服务注册功能nacos.serverAddr: Nacos-Sever 服务地址(不含端口号)nacos.namespace: Nacos 注册和配置隔离 namespacenacos.cluster: 注册服务的集群名称file.name: type = “file” classpath 下配置文件名运行 Fescar-serverLinux/Unix/Macsh fescar-server.sh $LISTEN_PORT $PATH_FOR_PERSISTENT_DATA $IP(此参数可选)Windowscmd fescar-server.bat $LISTEN_PORT $PATH_FOR_PERSISTENT_DATA $IP(此参数可选)服务端口 PATH_FOR_PERSISTENT_DATA: 事务操作记录文件存储路径(已存在路径)$IP(可选参数): 用于多 IP 环境下指定 Fescar-Server 注册服务的IPeg: sh fescar-server.sh 8091 /home/admin/fescar/data/运行成功后可在 Nacos 控制台看到 服务名 =serverAddr 服务注册列表:Step 8:启动微服务并测试修改业务客户端发现注册方式为 nacos同Step 7 中[修改 Fescar-server 服务注册方式为 nacos] 步骤启动 DubboAccountServiceStarter启动 DubboOrderServiceStarter启动 DubboStorageServiceStarter启动完成可在 Nacos 控制台服务列表 看到启动完成的三个 provider:启动 DubboBusinessTester 进行测试注意: 在标注 @GlobalTransactional 注解方法内部显示的抛出异常才会进行事务的回滚。整个 Dubbo 服务调用链路只需要在事务最开始发起方的 service 方法标注注解即可。通过以上8个步骤,我们实现了用户采购商品的业务中库存、订单和账户3个独立微服务之间的数据一致性。参考链接:本文 sample 地址: https://github.com/fescar-group/fescar-samples/tree/master/nacosFescar: https://github.com/alibaba/fescarDubbo: https://github.com/apache/incubator-dubboNacos: https://github.com/alibaba/nacos本文作者:清铭,社区昵称 slievrly,Fescar 开源项目发起人之一,阿里巴巴中件间 TXC/GTS 核心研发成员,长期从事于分布式中间件核心研发工作,在分布式事务领域有着较丰富的技术积累。有关 Fescar 的更多信息:分布式事务中间件 Fescar - RM 模块源码解读关于开源分布式事务中间件Fescar,我们总结了开发者关心的13个问题本文作者:中间件小哥阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

February 25, 2019 · 3 min · jiezi

从0开始构建SpringCloud微服务(1)

照例附上项目github链接本项目实现的是将一个简单的天气预报系统一步一步改造成一个SpringCloud微服务系统的过程,第一节将介绍普通天气预报系统的简单实现。数据来源:数据来源1:http://wthrcdn.etouch.cn/weather_mini?city=深圳数据来源2:http://wthrcdn.etouch.cn/weather_mini?citykey=101280601数据来源3:http://mobile.weather.com.cn/js/citylist.xml数据格式根据返回的数据格式在vo包下面创建pojo。Service创建WeatherDataService在其中提供如下接口:1)根据城市Id获取城市天气数据的接口。 @Override public WeatherResponse getDataByCityId(String cityId) { String url=WEATHER_URI+ “citykey=” + cityId; return this.doGetWeather(url); }2)根据城市名称获取天气数据的接口。 @Override public WeatherResponse getDataByCityName(String cityName) { String url = WEATHER_URI + “city=” + cityName; return this.doGetWeather(url); }其中doGetWeather方法为抽离出来的请求天气数据的方法。 private WeatherResponse doGetWeahter(String uri) { ResponseEntity<String> respString = restTemplate.getForEntity(uri, String.class); ObjectMapper mapper = new ObjectMapper(); WeatherResponse resp = null; String strBody = null; if (respString.getStatusCodeValue() == 200) { strBody = respString.getBody(); } try { resp = mapper.readValue(strBody, WeatherResponse.class); } catch (IOException e) { e.printStackTrace(); } return resp; }Controller在controller中分别提供根据城市id与名称获取天气数据的接口。@RestController@RequestMapping("/weather")public class WeatherController { @Autowired private WeatherDataService weatherDataService; @GetMapping("/cityId/{cityId}") public WeatherResponse getWeatherByCityId(@PathVariable(“cityId”) String cityId) { return weatherDataService.getDataByCityId(cityId); } @GetMapping("/cityName/{cityName}") public WeatherResponse getWeatherByCityName(@PathVariable(“cityName”) String cityName) { return weatherDataService.getDataByCityName(cityName); }}配置创建Rest的配置类。@Configurationpublic class RestConfiguration { @Autowired private RestTemplateBuilder builder; @Bean public RestTemplate restTemplate() { return builder.build(); } }请求结果: ...

February 23, 2019 · 1 min · jiezi

使用Envoy 作Sidecar Proxy的微服务模式-3.分布式追踪

本博客是深入研究Envoy Proxy和Istio.io 以及它如何实现更优雅的方式来连接和管理微服务系列文章的一部分。这是接下来几个部分的想法(将在发布时更新链接):断路器(第一部分)重试/超时(第二部分)分布式跟踪(第三部分)Prometheus的指标收集(第四部分)服务发现(第五部分)第三部分 - 使用envoy proxy 实现分布式追踪第一篇博文向您介绍了Envoy Proxy的断路功能实现。在第二部分中,仔细研究了如何启用额外的弹性功能,如超时和重试。在第三部分中,我们将了解如何在服务网格中启用分布式跟踪。有意进行一些简单的演示,因此我可以单独说明模式和用法。请下载此演示的源代码并按照说明进行操作!该演示由一个客户端和一个服务组成。客户端是一个Java http应用程序,模拟对“上游”服务进行http调用(注意,我们在这里使用Envoys术语,并贯穿整个repo)。客户端打包在docker.io/ceposta/http-envoy-client:latest的Docker镜像中。除了http-client Java应用程序之外,还有Envoy Proxy的一个实例。在此部署模型中,Envoy被部署为服务的sidercar(在本例中为http客户端)。当http-client进行出站调用(到“上游”服务)时,所有调用都通过Envoy Proxy sidercar。envoy会在服务调用之间添加一些追踪headers,并发送到Zipkin(或您的跟踪提供商…… Envoy目前支持Zipkin和Lightstep)。这些示例的“上游”服务是httpbin.org。 httpbin.org允许我们轻松模拟HTTP服务行为。它很棒,所以如果你没有看到它,请查看它。这个traceing 演示有自己的envoy.json配置文件。我绝对建议您查看配置文件每个部分的参考文档,以帮助理解完整配置。 datawire.io的优秀人员也为Envoy及其配置提供了一个很好的介绍,你也应该检查一下。运行 tracing demo对于跟踪演示,我们将使用以下如下的配置来配置我们的Envoy(请参阅其余上下文的完整配置): “tracing”: { “operation_name”: “egress” }, … “tracing”: { “http”: { “driver”: { “type”: “zipkin”, “config”: { “collector_cluster”: “zipkin”, “collector_endpoint”: “/api/v1/spans” } } } }, … { “name”: “zipkin”, “connect_timeout_ms”: 1000, “type”: “strict_dns”, “lb_type”: “round_robin”, “hosts”: [ { “url”: “tcp://zipkin:9411” } ] } 这里我们配置跟踪驱动程序和跟踪集群。在这种情况下,要运行此演示,我们需要启动Zipkin服务器:首先我们停止已经存在的演示demo:./docker-stop.sh启动zipkin服务:./tracing/docker-run-zipkin.sh会将zipkin暴露在端口9411上。如果您使用minikube或类似的东西来运行这些演示,您可以直接将minikube端口导出到您的主机,如下所示:./port-forward-minikube.sh 9411一旦你启动并运行Zipkin,访问该服务(即,在minikube上,在进行端口转发后,它将只是http:// localhost:9411)。你应该看看Zipkin:现在我们已经启动了zipkin服务器,让我们启动我们的跟踪演示:/docker-run.sh -d tracing然后让我们用客户端访问服务:./curl.sh -vvvv localhost:15001/get我们将看到如下的输出:< HTTP/1.1 200 OK* Server envoy is not blacklisted< server: envoy< date: Thu, 25 May 2017 06:31:02 GMT< content-type: application/json< access-control-allow-origin: < access-control-allow-credentials: true< x-powered-by: Flask< x-processed-time: 0.000982999801636< content-length: 402< via: 1.1 vegur< x-envoy-upstream-service-time: 142< { “args”: {}, “headers”: { “Accept”: “/*”, “Connection”: “close”, “Host”: “httpbin.org”, “User-Agent”: “curl/7.35.0”, “X-B3-Sampled”: “1”, “X-B3-Spanid”: “0000b825f82b418d”, “X-B3-Traceid”: “0000b825f82b418d”, “X-Ot-Span-Context”: “0000b825f82b418d;0000b825f82b418d;0000000000000000;cs” }, “origin”: “68.3.84.124”, “url”: “http://httpbin.org/get"}现在,如果我们转到Zipkin服务器,我们应该看到此调用的单个跨度/跟踪(注意,您可能需要调整zipkin过滤器中的开始/停止时间:这里我们有一个只有一个span的跟踪(这是我们所期望的,因为我们的Envoy演示客户端直接与没有Envoy的外部服务调用……如果上游服务也有启用了zipkin的Envoy,我们将看到服务之间的全部span)。如果我们点击span以查看更多细节,我们会看到如下内容:PS请注意,服务体系结构中的每个服务都应该与Envoy一起部署并参与分布式跟踪。这种方法的优点在于跟踪是从应用程序带外进行的。但是,为了跟踪要正确传播的上下文,应用程序开发人员有责任正确传播正确的header,以便不同的span正确关联。检查zipkin以获取更多详细信息,但至少要传播这些header(如上所示):x-request-idx-b3-traceidx-b3-spanidx-b3-parentspanidx-b3-sampledx-b3-flagsx-ot-span-context ...

February 23, 2019 · 1 min · jiezi

使用Envoy 作Sidecar Proxy的微服务模式-2.超时和重试

本博客是深入研究Envoy Proxy和Istio.io 以及它如何实现更优雅的方式来连接和管理微服务系列文章的一部分。这是接下来几个部分的想法(将在发布时更新链接):断路器(第一部分)重试/超时(第二部分)分布式跟踪(第三部分)Prometheus的指标收集(第四部分)服务发现(第五部分)第一部分 - 使用envoy proxy 实现超时和重试第一篇博文向您介绍了Envoy Proxy的断路功能实现。在第二部分中,我们将详细介绍如何启用其他弹性功能,如超时和重试。有意进行一些简单的演示,因此我可以单独说明模式和用法。请下载此演示的源代码并按照说明进行操作!该演示由一个客户端和一个服务组成。客户端是一个Java http应用程序,模拟对“上游”服务进行http调用(注意,我们在这里使用Envoys术语,并贯穿整个repo)。客户端打包在docker.io/ceposta/http-envoy-client:latest的Docker镜像中。除了http-client Java应用程序之外,还有Envoy Proxy的一个实例。在此部署模型中,Envoy被部署为服务的sidercar(在本例中为http客户端)。当http-client进行出站调用(到“上游”服务)时,所有调用都通过Envoy Proxy sidercar。这些示例的“上游”服务是httpbin.org。 httpbin.org允许我们轻松模拟HTTP服务行为。它很棒,所以如果你没有看到它,请查看它。重试和超时演示有自己的envoy.json配置文件。我绝对建议您查看配置文件每个部分的参考文档,以帮助理解完整配置。 datawire.io的优秀人员也为Envoy及其配置提供了一个很好的介绍,你也应该检查一下。运行 重试 demo对于重试演示,我们将在Envoy中配置我们的路由,如下所示: “routes”: [ { “timeout_ms”: 0, “prefix”: “/”, “auto_host_rewrite”: true, “cluster”: “httpbin_service”, “retry_policy”: { “retry_on”: “5xx”, “num_retries”: 3 } }这里我们在HTTP状态为5xx时重试最多3次。如果您已经运行过以前的演示,请确保为此(或任何)演示开始一个新的初始化状态。我们为每个演示提供不同的Envoy配置,并希望确保每次都从一个新的初始化状态开始。首先停止已经存在的demo:./docker-stop.sh现在开始运行重试demo:./docker-run.sh -d retries现在让我们通过一次调用来运行客户端,该调用将触发应该返回HTTP 500错误的HTTP端点。我们将使用curl.sh脚本,该脚本设置为在我们的演示容器中调用curl。./curl.sh -vvvv localhost:15001/status/500我们将会看到类似的输出:* Hostname was NOT found in DNS cache* Trying ::1…* connect to ::1 port 15001 failed: Connection refused* Trying 127.0.0.1…* Connected to localhost (127.0.0.1) port 15001 (#0)> GET /status/500 HTTP/1.1> User-Agent: curl/7.35.0> Host: localhost:15001> Accept: /> < HTTP/1.1 500 Internal Server Error* Server envoy is not blacklisted< server: envoy< date: Thu, 25 May 2017 05:55:37 GMT< content-type: text/html; charset=utf-8< access-control-allow-origin: < access-control-allow-credentials: true< x-powered-by: Flask< x-processed-time: 0.000718116760254< content-length: 0< via: 1.1 vegur< x-envoy-upstream-service-time: 684< * Connection #0 to host localhost left intact现在我们检查一下,envoy为我们做了哪些工作:./get-envoy-stats.sh | grep retrycluster.httpbin_service.retry.upstream_rq_500: 3cluster.httpbin_service.retry.upstream_rq_5xx: 3cluster.httpbin_service.upstream_rq_retry: 3cluster.httpbin_service.upstream_rq_retry_overflow: 0cluster.httpbin_service.upstream_rq_retry_success: 0我们在这里看到由于HTTP 500错误,envoy重试了3次。如果从另外一个角度看待,重试可能会对您的服务架构产生有害影响。它们可以帮助传播故障或对可能正在挣扎的内部服务造成DDoS类型攻击。对于重试,需要注意以下几点:envoy将通过抖动进行自动指数重试。有关更多信息,请参阅文档您可以设置重试超时(每次重试超时),但总路由超时(为路由表配置;请参阅超时演示以获取确切配置)仍将保留/应用;这是为了使任何失控的重试/指数退避短路您应始终设置断路器重试配置,以便在可能具有大量连接时限制重试的配额。请参阅Envoy文档中断路器部分的有效重试运行超时 demo对于超时演示,我们将在Envoy中配置我们的路由,如下所示: “routes”: [ { “timeout_ms”: 0, “prefix”: “/”, “auto_host_rewrite”: true, “cluster”: “httpbin_service”, “timeout_ms”: 3000 }此配置为通过此路由到httpbin_service群集的任何调用设置全局(即,包括所有重试)3s超时。每当处理超时时,我们必须知道源自边缘的请求的整体全局超时。当我们深入到网络调用图中时,我们发现自己很难调试超时不会逐渐减少的情况。换句话说,当您浏览调用图时,调用图中更深层次的服务调用的服务超时应该小于先前服务的调用:envoy可以帮助传播超时信息,像gRPC这样的协议可以传播截止时间信息。随着我们继续本系列,我们将看到如何使用Istio Mesh控制Envoy代理,并且控制平面可以帮助我们进行故障注入以发现超时异常。如果您已经运行过以前的演示,请确保为此(或任何)演示开始一个新的初始化状态。我们为每个演示提供不同的Envoy配置,并希望确保每次都从一个新的初始化状态开始。首先停止已经存在的demo:./docker-stop.sh现在开始运超时demo:./docker-run.sh -d timeouts现在让我们用一个调用来运行客户端,该调用将触发HTTP端点,该端点应该将响应延迟大约5秒。此延迟应足以触发envoy超时。我们将使用curl.sh脚本,该脚本设置为在我们的演示容器中调用curl。./curl.sh -vvvv localhost:15001/delay/5我们将看到类似的输出: Hostname was NOT found in DNS cache* Trying ::1…* connect to ::1 port 15001 failed: Connection refused* Trying 127.0.0.1…* Connected to localhost (127.0.0.1) port 15001 (#0)> GET /delay/5 HTTP/1.1> User-Agent: curl/7.35.0> Host: localhost:15001> Accept: /> < HTTP/1.1 504 Gateway Timeout< content-length: 24< content-type: text/plain< date: Thu, 25 May 2017 06:13:53 GMT* Server envoy is not blacklisted< server: envoy< * Connection #0 to host localhost left intactupstream request timeout我们看到我们的请求是超时的。下面我们检查以下envoy的状态:./get-envoy-stats.sh | grep timeout在这里,我们看到1个请求(我们发送的请求!)由Envoy超时。cluster.httpbin_service.upstream_cx_connect_timeout: 0cluster.httpbin_service.upstream_rq_per_try_timeout: 0cluster.httpbin_service.upstream_rq_timeout: 1http.admin.downstream_cx_idle_timeout: 0http.egress_http.downstream_cx_idle_timeout: 0如果我们发送请求,这次延迟较小,我们应该看到调用:./curl.sh -vvvv localhost:15001/delay/2* Hostname was NOT found in DNS cache* Trying ::1…* connect to ::1 port 15001 failed: Connection refused* Trying 127.0.0.1…* Connected to localhost (127.0.0.1) port 15001 (#0)> GET /delay/2 HTTP/1.1> User-Agent: curl/7.35.0> Host: localhost:15001> Accept: /> < HTTP/1.1 200 OK* Server envoy is not blacklisted< server: envoy< date: Thu, 25 May 2017 06:15:41 GMT< content-type: application/json< access-control-allow-origin: < access-control-allow-credentials: true< x-powered-by: Flask< x-processed-time: 2.00246119499< content-length: 309< via: 1.1 vegur< x-envoy-upstream-service-time: 2145< { “args”: {}, “data”: “”, “files”: {}, “form”: {}, “headers”: { “Accept”: “/”, “Connection”: “close”, “Host”: “httpbin.org”, “User-Agent”: “curl/7.35.0”, “X-Envoy-Expected-Rq-Timeout-Ms”: “3000” }, “origin”: “68.3.84.124”, “url”: “http://httpbin.org/delay/2”} Connection #0 to host localhost left intact另请注意,Envoy会传播超时 headers,以便上游服务可以了解所期望的内容。 ...

February 22, 2019 · 2 min · jiezi

Nacos系列:Nacos的Java SDK使用

Maven依赖Nacos提供完整的Java SDK,便于配置管理和服务发现及管理,以 Nacos-0.8.0 版本为例添加Maven依赖:<dependency> <groupId>com.alibaba.nacos</groupId> <artifactId>nacos-client</artifactId> <version>0.8.0</version></dependency>仅仅引入nacos-client是不够的,否则启动时会出现如下错误:sun.misc.Launcher$AppClassLoader@18b4aac2 JM.Log:WARN Init JM logger with NopLoggerFactory, pay attention. sun.misc.Launcher$AppClassLoader@18b4aac2java.lang.ClassNotFoundException: org.apache.logging.log4j.core.Logger at java.net.URLClassLoader.findClass(URLClassLoader.java:381) at java.lang.ClassLoader.loadClass(ClassLoader.java:424) at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:335) at java.lang.ClassLoader.loadClass(ClassLoader.java:357) at java.lang.Class.forName0(Native Method) at java.lang.Class.forName(Class.java:264) at com.alibaba.nacos.client.logger.log4j2.Log4j2LoggerFactory.<init>(Log4j2LoggerFactory.java:33) at com.alibaba.nacos.client.logger.LoggerFactory.<clinit>(LoggerFactory.java:59) at com.alibaba.nacos.client.config.utils.LogUtils.<clinit>(LogUtils.java:49) at com.alibaba.nacos.client.config.NacosConfigService.<clinit>(NacosConfigService.java:55) at java.lang.Class.forName0(Native Method) at java.lang.Class.forName(Class.java:264) at com.alibaba.nacos.api.config.ConfigFactory.createConfigService(ConfigFactory.java:40) at com.alibaba.nacos.api.config.ConfigFactory.createConfigService(ConfigFactory.java:59) at com.alibaba.nacos.api.NacosFactory.createConfigService(NacosFactory.java:52) at com.learn.nacos.config.NacosConfig.main(NacosConfig.java:12)根据错误提示,应该还需要添加log4j相关依赖,官网的文档并没有对此说明,我在pom.xml添加了下面这些依赖才不报错<dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.1.11</version></dependency><dependency> <groupId>org.logback-extensions</groupId> <artifactId>logback-ext-spring</artifactId> <version>0.1.4</version></dependency><dependency> <groupId>org.slf4j</groupId> <artifactId>jcl-over-slf4j</artifactId> <version>1.7.25</version></dependency><dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version></dependency><dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.25</version></dependency>配置管理创建ConfigService,可以通过 NacosFactory.createConfigService() 或 ConfigFactory.createConfigService() 来创建,后者是前者的底层实现方式,这两种方式都包含如下两个方法:createConfigService(serverAddr)createConfigService(properties)创建示例:// 方式一String serverAddr = “127.0.0.1:8848”;ConfigService configService = ConfigFactory.createConfigService(serverAddr);// 方式二ConfigService configService = ConfigFactory.createConfigService(properties)Properties properties = new Properties();properties.put(“serverAddr”, serverAddr);查看ConfigService源码,它提供了如下方法:获取 Nacos Server 当前状态:String getServerStatus()底层源码:public String getServerStatus() { if (worker.isHealthServer()) { return “UP”; } else { return “DOWN”; }}根据源码注释,该状态应该是指 Nacos Server 的状态,我把 Nacos Server 关闭之后,再次运行示例,得到的结果仍然是UP,不知道这是不是一个BUG。发布配置:boolean publishConfig(String dataId, String group, String content) throws NacosException支持程序自动发布Nacos配置,创建和修改配置使用同一个方法,配置不存在则创建;配置已存在则更新。底层源码:try { result = agent.httpPost(url, headers, params, encode, POST_TIMEOUT);} catch (IOException ioe) { log.warn(“NACOS-0006”, LoggerHelper.getErrorCodeStr(“NACOS”, “NACOS-0006”, “环境问题”, “[publish-single] exception”)); log.warn(agent.getName(), “[publish-single] exception, dataId={}, group={}, msg={}”, dataId, group, ioe.toString()); return false;}发布配置后,如果马上用getConfig()读取配置,有时候会读不到,设置了足够的等待时长后才可保证每次正常读取,看了源码才知道Nacos的配置管理(发布、读取、移除)都是通过HTTP接口完成的,但发布配置的时延是多少,官网似乎没有说明?几秒钟的时延在一些对实时性要求很高的场景会不会存在影响呢?读取配置:String getConfig(String dataId, String group, long timeoutMs) throws NacosExceptiontimeoutMs指读取配置超时时间,官网推荐设置为3000ms底层源码:// 优先使用本地配置String content = LocalConfigInfoProcessor.getFailover(agent.getName(), dataId, group, tenant);if (content != null) { log.warn(agent.getName(), “[get-config] get failover ok, dataId={}, group={}, tenant={}, config={}”, dataId, group, tenant, ContentUtils.truncateContent(content)); cr.setContent(content); configFilterChainManager.doFilter(null, cr); content = cr.getContent(); return content;}try { content = worker.getServerConfig(dataId, group, tenant, timeoutMs); cr.setContent(content); configFilterChainManager.doFilter(null, cr); content = cr.getContent(); return content;} catch (NacosException ioe) { if (NacosException.NO_RIGHT == ioe.getErrCode()) { throw ioe; } log.warn(“NACOS-0003”, LoggerHelper.getErrorCodeStr(“NACOS”, “NACOS-0003”, “环境问题”, “get from server error”)); log.warn(agent.getName(), “[get-config] get from server error, dataId={}, group={}, tenant={}, msg={}”, dataId, group, tenant, ioe.toString());}从源码上看,配置会先从本地缓存文件读取,如果没读取到,才会去请求Nacos Server的配置,这个缓存文件在哪呢?就在当前用户的nacos目录下生成的缓存文件:nacos/config/fixed-127.0.0.1_8848_nacos/snapshot/DEFAULT_GROUP/nacos-sdk-java-config,配置内容和发布到Nacos Server的配置内容是一致的。移除配置:boolean removeConfig(String dataId, String group) throws NacosException支持程序自动发布Nacos配置,配置不存在时会直接返回成功,移除配置后,本地的缓存文件也会被删除底层源码:try { result = agent.httpDelete(url, null, params, encode, POST_TIMEOUT);} catch (IOException ioe) { log.warn("[remove] error, " + dataId + “, " + group + “, " + tenant + “, msg: " + ioe.toString()); return false;}移除配置同发布配置一样,如果移除后马上查询,有可能还能将刚移除的配置查出来,也存在一定的时延,需要设置等待时间读取。添加配置监听:void addListener(String dataId, String group, Listener listener) throws NacosException支持动态监听配置的变化,运行示例源码,在Nacos控制台把配置内容修改为sdk-java-config:change from nacos console,此时观看IDE控制台,你会看到如下打印信息:当前线程:com.alibaba.nacos.client.Worker.longPollingfixed-127.0.0.1_8848 ,监听到配置内容变化:sdk-java-config:change from nacos console移除配置监听:void removeListener(String dataId, String group, Listener listener)移除监听后,配置的变化不会再监听启动完整示例,运行结果如下,请注意配置监听线程和配置管理线程不是同一个线程当前线程:main ,服务状态:UP添加监听添加监听成功发布配置发布配置成功当前线程:com.alibaba.nacos.client.Worker.longPollingfixed-127.0.0.1_8848 ,监听到配置内容变化:nacos-sdk-java-config:init当前线程:main ,发布配置后获取配置内容:nacos-sdk-java-config:init重新发布配置重新发布配置成功当前线程:main ,重新发布配置后获取配置内容:sdk-java-config:update当前线程:com.alibaba.nacos.client.Worker.longPollingfixed-127.0.0.1_8848 ,监听到配置内容变化:sdk-java-config:update当前线程:com.alibaba.nacos.client.Worker.longPollingfixed-127.0.0.1_8848 ,监听到配置内容变化:sdk-java-config:change from nacos console移除配置移除配置成功当前线程:main ,移除配置后获取配置内容:null取消监听取消监听成功服务管理创建NamingService,可以通过 NacosFactory.createNamingService() 或 NamingFactory.createNamingService() 来创建,后者是前者的底层实现方式,这两种方式都包含如下两个方法:createNamingService(serverAddr)createNamingService(properties)创建示例:// 方式一String serverAddr = “127.0.0.1:8848”;NamingService namingService = NamingFactory.createNamingService(serverAddr);// 方式二NamingService namingService = NamingFactory.createNamingService(properties)Properties properties = new Properties();properties.put(“serverAddr”, serverAddr);查看NamingService类源码,它提供了如下方法:获取 Nacos Server 当前状态:String getServerStatus()注册服务实例:void registerInstance(多个参数)方式一:String serverIp = “127.0.0.1”;int serverPort = 8848;String serverAddr = serverIp + “:” + serverPort;String serviceName = “nacos-sdk-java-discovery”;NamingService namingService = NamingFactory.createNamingService(serverAddr);namingService.registerInstance(serviceName, serverIp, serverPort);方式二:Instance instance = new Instance();instance.setIp(serverIp);//IPinstance.setPort(serverPort);//端口instance.setServiceName(serviceName);//服务名instance.setEnabled(true);//true: 上线 false: 下线instance.setHealthy(healthy);//健康状态instance.setWeight(1.0);//权重instance.addMetadata(“nacos-sdk-java-discovery”, “true”);//元数据NamingService namingService = NamingFactory.createNamingService(serverAddr);namingService.registerInstance(serviceName, instance);注册后,本地会生成缓存文件1、在Nacos安装目录data目录下:data/naming/data/public/com.alibaba.nacos.naming.domains.meta.public##nacos-sdk-java-discovery2、当前用户的nacos目录下:/nacos/naming/public/failover/nacos-sdk-java-discovery3、当前用户的nacos目录下:/nacos/naming/public/nacos-sdk-java-discovery即使删除服务实例,上面三个缓存文件也不会被删除,Nacos控制台服务列表中该服务也还存在着(但服务实例数会变成0);删除Nacos控制台的该服务,安全目录data目录下的缓存文件会被删除,但当前用户的nacos目录下的文件不会被删除,这里面是什么机制,我暂时还没整明白,等后面整明白了再来补充。删除服务实例:void deregisterInstance(多个参数)获取所有服务实例:List<Instance> getAllInstances(多个参数)获取所有健康或不健康的服务实例:List<Instance> selectInstances(多个参数)随机获取一个健康实例(根据负载均衡算法):Instance selectOneHealthyInstance(多个参数)添加服务实例监听:void subscribe(多个参数)添加服务实例监听:void unsubscribe(多个参数)分页获取所有服务实例:ListView<String> getServicesOfServer(多个参数)获取所有监听的服务实例:List<ServiceInfo> getSubscribeServices()启动完整示例,运行结果如下,请注意服务实例监听线程和服务实例管理线程不是同一个线程当前线程:main ,服务状态:UP注册实例注册实例成功添加监听添加监听成功当前线程:main ,注册实例后获取所有实例:[{“clusterName”:“DEFAULT”,“enabled”:true,“instanceId”:“127.0.0.1#8848#DEFAULT#nacos-sdk-java-discovery”,“ip”:“127.0.0.1”,“metadata”:{},“port”:8848,“serviceName”:“nacos-sdk-java-discovery”,“valid”:true,“weight”:1.0}]当前线程:main ,注册实例后获取所有健康实例:[{“clusterName”:“DEFAULT”,“enabled”:true,“instanceId”:“127.0.0.1#8848#DEFAULT#nacos-sdk-java-discovery”,“ip”:“127.0.0.1”,“metadata”:{},“port”:8848,“serviceName”:“nacos-sdk-java-discovery”,“valid”:true,“weight”:1.0}]当前线程:com.alibaba.nacos.naming.client.listener ,监听到实例名称:nacos-sdk-java-discovery当前线程:com.alibaba.nacos.naming.client.listener ,监听到实例内容:[{“clusterName”:“DEFAULT”,“enabled”:true,“instanceId”:“127.0.0.1#8848#DEFAULT#nacos-sdk-java-discovery”,“ip”:“127.0.0.1”,“metadata”:{},“port”:8848,“serviceName”:“nacos-sdk-java-discovery”,“valid”:true,“weight”:1.0}]当前线程:main ,注册实例后获取一个健康实例:{“clusterName”:“DEFAULT”,“enabled”:true,“instanceId”:“127.0.0.1#8848#DEFAULT#nacos-sdk-java-discovery”,“ip”:“127.0.0.1”,“metadata”:{},“port”:8848,“serviceName”:“nacos-sdk-java-discovery”,“valid”:true,“weight”:1.0}当前线程:com.alibaba.nacos.naming.client.listener ,监听到实例名称:nacos-sdk-java-discovery当前线程:com.alibaba.nacos.naming.client.listener ,监听到实例内容:[{“clusterName”:“DEFAULT”,“enabled”:true,“instanceId”:“127.0.0.1#8848#DEFAULT#nacos-sdk-java-discovery”,“ip”:“127.0.0.1”,“metadata”:{“change”:“true;”},“port”:8848,“serviceName”:“nacos-sdk-java-discovery”,“valid”:true,“weight”:2.0}]取消监听取消监听成功删除实例删除实例成功Exception in thread “main” java.lang.IllegalStateException: no host to srv for serviceInfo: nacos-sdk-java-discovery at com.alibaba.nacos.client.naming.core.Balancer$RandomByWeight.selectAll(Balancer.java:45) at com.alibaba.nacos.client.naming.core.Balancer$RandomByWeight.selectHost(Balancer.java:53) at com.alibaba.nacos.client.naming.NacosNamingService.selectOneHealthyInstance(NacosNamingService.java:270) at com.alibaba.nacos.client.naming.NacosNamingService.selectOneHealthyInstance(NacosNamingService.java:263) at com.alibaba.nacos.client.naming.NacosNamingService.selectOneHealthyInstance(NacosNamingService.java:253) at com.learn.nacos.discovery.NacosDiscovery.main(NacosDiscovery.java:121)当前线程:main ,删除实例后获取所有实例:[]当前线程:main ,删除实例后获取所有健康实例:[]以上就是 Nacos Java SDK 配置管理和服务管理功能的介绍,请参考示例源码学习。示例源码项目:learn-nacos-sdk-java代码已上传至码云和Github上,欢迎下载学习GiteeGithub参考资料Nacos用户指南:Java的SDK推荐阅读Nacos系列:欢迎来到Nacos的世界!Nacos系列:基于Nacos的注册中心Nacos系列:基于Nacos的配置中心 ...

February 22, 2019 · 2 min · jiezi

微服务测试之静态代码扫描

静态代码扫描为整个发展组织增加价值。无论您在开发组织中发挥的作用如何,静态代码扫描解决方案都具有附加价值,拥有软件开发中所需要的尖端功能,最大限度地提高质量并管理软件产品中的风险。背景微服务架构模式具有服务间独立,可独立开发部署等特点,独立开发诱发了技术上的分离,HTTP通信增加了问题诊断的复杂度,对系统的功能、性能和安全方面的质量保障带来了很大的挑战。“微服务架构对测试的挑战微服务架构模式下多个独立业务服务同时开展开发工作,每个系统都有各自的业务范围和开发周期要求,这样一来,下图所示的传统流程中产品经理提供需求,需求人员进行需求分析、开发人员进行开发,最后交给测试人员进行测试的方法,就无法满足测试覆盖和测试效率的要求。 相对于传统的单体模式而言,微服务模式下对测试带来的挑战总结起来包括以下内容:1. 微服务系统模块层次化,需要保证模块内部代码的质量。这种场景下传统的端到端的测试无法满足测试要求;2. 需要保证各个微服务系统内部模块间的正确性。系统模块间以及前端和后端通常会同时开展开发工作,模块间或者前后端通过接口(通常是Restful http接口)进行连接,而模块和后端往往没有界面,为了保证各个系统单个依赖系统的正确性,因此需要借助Mock技术隔离依赖的前提下进行接口级的测试;3. 需要保证微服务系统中的接口一致性,即契约的一致性。需要通过契约测试手段保证契约的正确性,进而保证同步开发过程中的前后开发的正确性和一致性;4. 需要保障单个微服务系统的正确性。需要进行组件级的测试进行微服务系统的正确性;5. 需要保障整个系统的正确性。各个微服务系统串接之后通过端到端的测试保证整体系统的正确性;“微服务架构下如何开展测试针对上面提到的微服务对测试的挑战,一方面为了保证在服务各个层级上对微服务进行全面的测试,特别是对于分布式系统;另一方面又要确保测试执行的效率,这样才能保证持续集成/持续交付(CI/CD)。因此,总体的测试策略采用如下解决方法:1. 开展「质量」文化。让开发人员建立起代码「质量」意识,用于保障模块内部的质量;2. 采用自动化测试手段。在微服务架构中,开发分解为负责不同服务的多个小组,测试人员往往每天要花费大量的时间,了解不同团队的开发进度。如果还需要手动进行回归测试(Regression Test),最终将会不堪重负。所以自动化测试在微服务模式下是必须采取的手段。3. 分层的自动化测试策略。自动化测试分层在Mike Cohn 提出的测试金字塔(Test Pyramid)原理中进行了详细的阐述。它提倡在代码级、接口级、应用级进行不同粒度的测试来保证系统的质量。从自动化测试投入比例来看,单元测试和静态代码扫描的投入比例最大,其次是接口自动化测试,最后是UI自动化测试。同时为了提高测试效率和测试覆盖率,功能测试需要借助探索式测试手段开展测试。4. 采用流水线技术进行可视化快速反馈。由于微服务系统非常多,这样往往会增加了运维和沟通成本,为了提高沟通效率,需要借助流水线的技术,可视化查看每一个构建(Build)、测试(Test)、部署(Deploy)过程,快速做出质量反馈和处理决策。通过可视化流水线最终可以实现各个环节的监控,采用DevOps手段打通业务、开发、测试和运维的部门墙。 下面结合分层自动化测试的思想,首先对静态代码扫描进行介绍。静态代码扫描“静态代码扫描背景静态代码分析是指在不运行代码的方式下,通过词法分析、语法分析、控制流、数据流分析等技术对程序代码进行扫描的技术。它的目的是验证代码是否满足规范性、安全性、可靠性、可维护性的要求。静态代码扫描处于分层自动化测试的最底层,它和单元测试同级别。为了保证公司代码的规范性、安全性、可靠性的要求,通过定制公司级的静态代码扫描规范、扫描规则和扫描实施流程保证实施高效落地。“静态代码扫描意义为开发者软件开发人员最终负责代码质量。代码质量是非功能性需求的一部分,因此是开发人员的直接责任。代码质量不应该存在技术债务,在开发的过程中每一步都提供反馈,从IDE到发布。这使得开发人员能够尽早做出有关代码质量的决策,使他们能够做得更好,并提供质量更好的软件产品。为DevOpsDevOps需要确保软件的构建方式正确。DevOps中涉及的责任很多,其中包括支持开发流程,自动化测试,确保质量,提高生产力…..并最终实现持续部署。良好的代码质量是实现所有这些目标的必要条件,尽管不是充分条件。静态代码扫描可在任何构建/测试/部署步骤中添加的代码质量检验门槛,能够自动执行一组统一的质量标准,从而确保组织交付更好的软件。为管理者代码静态扫描可降低风险并提高团队生产力。管理人员需要能够安全地运行软件,并且需要花费合理的投资回报。我们的解决方案一目了然地显示了他们面临的技术债务以及他们缓解的成本。它还具有开箱即用的功能,可以系统地提高开发团队的可维护性和长期生产力。这使管理人员能够以最佳成本使用风险控制方法确保其组织能够交付更好的软件。“静态代码扫描介绍静态代码扫描处在特性分支开发完成之后,具体的描述如下:1. 开发人员从Master分支拉取特性分支作为开发分支;2. 开发完特性分支后、代码构建、单元测试、静态代码扫描;3. 通过后合并到Master分支,用于投产;“静态代码扫描流程随行付静态代码扫描平台的具体实现是通过集成SonarQube平台工具、Jenkins集成工具、IDE SonarLint插件和CheckStyle本地化规则模板等开源工具、插件集而成。实现本地化代码的实施检测,版本构建后的二次检测,以及邮件反馈等功能的流程闭环,保证投产前代码符合随行付代码规范的要求。具体的流程如下图所示:1.本地化IDE中通过SonarLint插件实现和SonarQube平台规则、规范的同步、实现本地代码检查;随行付定制化了java规则、XML规则257条,javascript规则86条,用于检测代码的规范性、代码缺陷、漏洞、坏味道、重复率等信息。并将定制化的规则放到了SonarQube平台上,SonrLint插件的规则比较全面,包括所有的sonajava规则和javascript规则,为了保证本地使用定制的规则,且同sonarqube中的规则一致,需要远程连接SonarQube服务器,并绑定项目。以Eclipse为例,展示SonarQube连接和项目绑定过程:2.代码提交到代码库GitLab中后,在Jenkins中测试环境构建时,自动触发Sonar扫描,并将扫描结果发布到SonarQube平台。下图为SonarQube展示的一个项目的结果:3.SonarQube平台根据质量阀的要求,不满足质量阀要求则邮件通知开发人员。质量阀要求: 1.新覆盖率大于等于80%; 2.新增Bugs为0; 3.新增漏洞为0; 4.新增坏味道为0;4.开发人员收到邮件后,进行代码处理,直到满足规范要求为止。5.以周为单位统计SonarQube平台中静态代码扫描出来的Bugs\漏洞\坏味道的数量,定时自动发送周报给相关干系人,报告中会包含问题处理情况的趋势图。 SonarQube与规则SonarQube是一个用于代码质量管理的开源平台,支持25+种编程语言的质量扫描。SonqrQube由远程机、Server端和数据库构成。远程客户机可以通过各种不同的分析机制,从而将被分析的项目代码上传到SonarQube server 并进行代码质量的管理和分析,SonarQube 还会通过Web API将分析的结果以可视化、可度量的方式展示给出来。逻辑结构如下图所示:“SonarQube的整合能力SonarQube平台中支持整合各种静态代码扫描检测工具。SonarQube中各种代码检测工具分析对象及应用技术对比:Java静态分析工具分析对象应用技术CheckStyleJava源文件缺陷模式匹配FindBugs字节码缺陷模式匹配;数据流分析PMDJava源代码缺陷模式匹配CheckStyle可以很方便的帮我们检查Java代码中的格式错误,它能够自动化代码规范检查过程,从而使得开发人员从这项重要,但是枯燥的任务中解脱出来。基本上都是根据开发规则定制规则。主要涵盖以下内容:Javadoc 注释:检查类及方法的 Javadoc 注释命名约定:检查命名是否符合命名规范标题:检查文件是否以某些行开头Import 语句:检查 Import 语句是否符合定义规范代码块大小,即检查类、方法等代码块的行数空白:检查空白符,如 tab,回车符等修饰符:修饰符号的检查,如修饰符的定义顺序块:检查是否有空块或无效块代码问题:检查重复代码,条件判断,魔数等问题类设计:检查类的定义是否符合规范,如构造函数的定义等问题FindBugsFindbugs是一个静态分析工具,它检查类或者JAR文件,将字节码与一组缺陷模式进行对比以发现可能的问题。主要涵盖以下内容:Bad practice 坏的实践:常见代码错误,用于静态代码检查时进行缺陷模式匹配Correctness 可能导致错误的代码,如空指针引用等国际化相关问题:如错误的字符串转换可能受到的恶意攻击,如访问权限修饰符的定义等多线程的正确性:如多线程编程时常见的同步,线程调度问题运行时性能问题:如由变量定义,方法调用导致的代码低效问题PMD一种开源分析Java代码错误的工具,其原理为使用JavaCC生成解析器来解析源代码并生成AST(抽象语法树)。与其他分析工具不同的是,PMD通过静态分析获知代码错误。也就是说,在不运行Java程序的情况下报告错误。PMD附带了许多可以直接使用的规则,利用这些规则可以找出Java源程序的许多问题,例如:潜在的 Bugs:检查潜在代码错误,如空的 try/catch/finally/switch 语句未使用代码(Dead code):检查未使用的变量,参数,方法等可选的代码:String/StringBuffer的滥用复杂的表达式:检查不必要的 if 语句,可被 while 替代的 for 循环重复的代码:检查重复的代码循环体创建新对象:检查在循环体内实例化新对象资源关闭:检查 Connect,Result,Statement 等资源使用之后是否被关闭掉此外,用户还可以自己定义规则,检查Java代码是否符合某些特定的编码规范。例如,你可以编写一个规则,要求PMD找出所有创建Thread和Socket对象的操作。三种工具对比由表中可以看出几种工具对于代码检查各有侧重。其中,Checkstyle 更偏重于代码编写格式,及是否符合编码规范的检验, 对代码 bug 的发现功能较弱;而 FindBugs,PMD着重于发现代码缺陷。在对代码缺陷检查中,这三种工具在针对的代码缺陷类别也各有不同,且类别之间有重叠。“规则定制考虑到Sonar Java规则已经包含了PMD和CheckStyle规则,因此我们选择了Sonar的默认规则,并对其进行了定制化。下图中SonarQube中展示了部分定制化规则内容。定制化后的规则覆盖的代码缺陷类型如下表所示(部分规则):代码缺陷分类示例引用操作空指针引用对象操作对象比较(使用==而不是equals)表达式复杂化对于的if语句数组使用数组下标越界未使用变量或代码段未使用变量资源回收I/O未关闭方法调用未使用方法返回值代码设计空的try/catch/finally块

February 22, 2019 · 1 min · jiezi

Nacos系列:基于Nacos的配置中心

前言在看正文之前,我想请你回顾一下自己待过的公司都是怎么管理配置的,我想应该会有以下几种方式:1、硬编码没有什么配置不配置的,直接写在代码里面,比如使用常量类优势:对开发友好,开发清楚地知道代码需要用到什么配置劣势:涉及秘钥等敏感配置直接暴露给开发人员,不安全;如果想修改配置必须重新发版,比较麻烦2、外部化配置文件Spring项目经常会在resoures目录下放很多配置文件,各个环境对应不同的配置文件,通过SVN管理优势:配置文件外部化,支持多环境配置管理,修改配置只需重启服务,无需发版劣势:系统庞大时,配置文件很多,多人开发,配置格式不统一,维护麻烦;敏感配置不需要暴露给开发人员,降低风险,但开发经常要和运维沟通怎么修改配置,沟通不恰当容易引发生产事故;而且,如果应用部署在多台机器,对运维来说,修改配置也是非常头疼的事情(当然也可以引入NFS系统来解决一部分问题)3、数据库配置信息存储在数据库中,灵活修改优势:可以灵活管理配置,无需重启服务劣势:界面不友好,配置没有版本管理,一旦出现问题,回滚或定位问题都比较麻烦;此外,数据库必须要保证高可用,避免因此而造成生产故障4、配置中心微服务基础架构体系中的一个不可或缺的基础组件优势:集中化管理,敏感配置可控;多版本存储,方便追溯;界面友好,修改配置一键发布;即使面对多集群也能从容应对,十分淡定劣势:引入组件,增加系统风险;如果是中途切换成配置中心,也会增加研发接入成本;配置中心也需要保证高可用,否则容易造成大面积影响以上几种管理配置文件的方式,我想都会有公司在用,不要因为配置中心有诸多优点,就盲目引进项目中,我觉得应该遵守以下两个原则:做人做事,要知道自己几斤几两释义:没深入研究过的技术,就不要随便拿到公司项目中来试水啦,恐怕到时候坑够你填的,要不然就是你有信心玩得转它。杀只鸡而已,你拿牛刀来做甚?释义:小团队小项目选择简单的配置管理方式就好了,要什么配置中心,纯属没事找事。总而言之,我们必须从实际出发,实事求是,选择适合自己的技术栈。关于为什么需要有配置中心,我推荐一篇文章给你看,讲得比较透彻:《微服务架构为什么需要配置中心?》另外,我觉得对开发本身来说,是宁愿自己管理自己代码的配置的,交给运维总是会有各种各样的问题,至于敏感配置,说实话,开发人员要真想做点“坏事”,那拦得住吗?但是,从公司的角度来讲,把服务器的配置管理交给运维同事是符合常理的,系统需要稳定且安全地运行,这是对客户的负责,从这一方面去思考,这么做是合情合理的。Okay,我就啰嗦到这里吧,下面正式介绍Nacos作为配置中心是怎么使用的。Nacos 结合 Spring添加 maven 依赖:<dependency> <groupId>com.alibaba.nacos</groupId> <artifactId>nacos-spring-context</artifactId> <version>${nacos-spring-context.version}</version></dependency>使用 @EnableNacosConfig 开启 Nacos Spring 的配置管理功能@Configuration@EnableNacosConfig(globalProperties = @NacosProperties(serverAddr = “127.0.0.1:8848”))@NacosPropertySource(dataId = “nacos.spring.config”, autoRefreshed = true)public class NacosConfig {}其中:@Configuration:Spring的注解,配置应用上下文@EnableNacosConfig:Nacos的注册,启用 Nacos Spring 的配置管理服务@NacosProperties:全局和自定义Nacos属性的统一注解@NacosPropertySource:加载数据源globalProperties:全局 Nacos 属性serverAddr:Nacos Server服务器地址dataId:配置的数据集IDautoRefreshed:是否开启配置动态更新再写一个Controller类,来验证Nacos的配置管理功能,代码如下:package com.learn.nacos;import com.alibaba.nacos.api.annotation.NacosInjected;import com.alibaba.nacos.api.config.ConfigService;import com.alibaba.nacos.api.config.annotation.NacosValue;import com.alibaba.nacos.api.exception.NacosException;import org.springframework.http.HttpStatus;import org.springframework.http.ResponseEntity;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestMethod;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.ResponseBody;@Controller@RequestMapping(value = “config”)public class NacosConfigController { @NacosInjected private ConfigService configService; @NacosValue(value = “${useLocalCache:false}”, autoRefreshed = true) private boolean useLocalCache; @RequestMapping(value = “/get”, method = RequestMethod.GET) @ResponseBody public boolean get() { return useLocalCache; } @RequestMapping(method = RequestMethod.GET) @ResponseBody public ResponseEntity<String> publish(@RequestParam String dataId, @RequestParam(defaultValue = “DEFAULT_GROUP”) String group, @RequestParam String content) throws NacosException { boolean result = configService.publishConfig(dataId, group, content); if (result) { return new ResponseEntity<String>(“Success”, HttpStatus.OK); } return new ResponseEntity<String>(“Fail”, HttpStatus.INTERNAL_SERVER_ERROR); }}该Controller类提供了两个HTTP接口读取配置:http://127.0.0.1:8080/config/get发布配置:http://127.0.0.1:8080/config?dataId=XXX&content=XXX发布配置还可以通过 Nacos Open API:curl -X POST “http://127.0.0.1:8848/nacos/v1/cs/configs?dataId=XXX&group=XXX&content=XXX 发布配置,你也可以用Postman工具模拟POST请求进行配置发布,我这里主要是为了方便验证问题,采用了这种方式。在验证之前,请先确保 Nacos Server 已经启动,Nacos Server 的安全及启动方式详见:《Nacos系列:欢迎来到Nacos的世界!》启动Tomcat,观察Console控制台20:50:13.646 [RMI TCP Connection(5)-127.0.0.1] WARN com.alibaba.nacos.spring.core.env.AnnotationNacosPropertySourceBuilder - There is no content for NacosPropertySource from dataId[nacos.spring.config] , groupId[DEFAULT_GROUP] , properties[{encode=${nacos.encode:UTF-8}, namespace=${nacos.namespace:}, contextPath=${nacos.context-path:}, endpoint=${nacos.endpoint:}, serverAddr=${nacos.server-addr:}, secretKey=${nacos.secret-key:}, accessKey=${nacos.access-key:}, clusterName=${nacos.cluster-name:}}].20:50:17.825 [RMI TCP Connection(5)-127.0.0.1] INFO com.alibaba.nacos.spring.context.event.LoggingNacosConfigMetadataEventListener - Nacos Config Metadata : dataId=‘nacos.spring.config’, groupId=‘DEFAULT_GROUP’, beanName=‘nacosConfig’, bean=‘null’, beanType=‘class com.learn.nacos.NacosConfig’, annotatedElement=‘null’, xmlResource=‘null’, nacosProperties=’{serverAddr=127.0.0.1:8848, encode=UTF-8}’, nacosPropertiesAttributes=’{encode=${nacos.encode:UTF-8}, namespace=${nacos.namespace:}, contextPath=${nacos.context-path:}, endpoint=${nacos.endpoint:}, serverAddr=${nacos.server-addr:}, secretKey=${nacos.secret-key:}, accessKey=${nacos.access-key:}, clusterName=${nacos.cluster-name:}}’, source=‘org.springframework.core.type.classreading.AnnotationMetadataReadingVisitor@66e4d430’, timestamp=‘1550753413647’我们先通过http://127.0.0.1:8080/config?dataId=nacos.spring.config&content=useLocalCache=true发布一个dataId为nacos.spring.config且配置内容为useLocalCache=true的配置集,观察Nacos控制台的变化再通过http://127.0.0.1:8080/config/get读取配置然后在Nacos控制台将useLocalCache的值改为false,并发布配置再次访问http://127.0.0.1:8080/config/getNacos 结合 Spring Boot添加 Starter 依赖:<dependency> <groupId>com.alibaba.boot</groupId> <artifactId>nacos-config-spring-boot-starter</artifactId> <version>0.2.1</version></dependency>注意:版本 0.2.x.RELEASE 对应的是 Spring Boot 2.x 版本,版本 0.1.x.RELEASE 对应的是 Spring Boot 1.x 版本。在application.properties中添加如下配置信息:nacos.config.server-addr=127.0.0.1:8848添加NacosConfigApplication启动类@SpringBootApplication@NacosPropertySource(dataId = “nacos.springboot.config”, autoRefreshed = true)public class NacosConfigApplication { public static void main(String[] args) { SpringApplication.run(NacosConfigApplication.class, args); }}如果你看过我的上一篇文章:《Nacos系列:基于Nacos的注册中心》,那么你应该知道 Spring Boot 实现方式和 Spring 的没太大差别,所以我就不再细说了,请参考我的源码示例或者官网资料学习。这里说下我在学习过程中遇到的一个问题,在application.properties添加配置文件的时候,不小心将nacos.config.server-addr写成了nacos.discovery.server-addr,结果启动项目时,一直报错:ERROR 9028 — [ main] o.s.b.d.LoggingFailureAnalysisReporter : ——APPLICATION FAILED TO START——Description:client error: invalid param. nullAction:please check your client configuration刚开始一直找不到原因,后面跟着官网代码示例复核,才发现是配置问题导致的,呵呵哒,自己给自己挖坑。后语我挺喜欢Nacos的,既然做服务发现和管理,又能做配置管理,这两者本质没多大区别,Nacos把这两者统一起来,一举两得,我觉得没什么不好,要不然你引入了Zookeeper作为注册中心,还要引入Apollo作为配置中心,无端增加学习成本。就像之前听音乐,我一般用网易云音乐就好,后面因为搞了版权的事,不得不下载了虾米和QQ音乐,我就听个歌而已,手机里装了三个APP,你说,这叫什么事儿?示例源码Nacos + Spring :learn-nacos-spring-configNacos + Spring Boot : learn-nacos-springboot-config代码已上传至码云和Github上,欢迎下载学习GiteeGithub参考资料微服务架构为什么需要配置中心?Nacos Spring 快速开始Nacos Spring Boot 快速开始SpringBoot使用Nacos配置中心Spring Cloud Alibaba基础教程:使用Nacos作为配置中心 ...

February 21, 2019 · 2 min · jiezi