-
Q:用Postman测试接口可以调通过,swagger-ui页面测试我的接口经常出现connect: connection timed out.A:确认下frontend跟你的应用网络是否通,servicecomb的swagger-ui现在是proxy模式,不是直连Q:准备使用servercomb,下载了center和demo项目,demo项目一直提示ERROR 9868 --- [ntloop-thread-0] o.a.s.s.client.http.RestUtils ......message: Connection timed out求指点A:1.httpaddr/httpport of your service center conf2.service registry address in your microservice.yamlQ:restschema与rpcschema两种方式在性能上有区别吗?A:@RestSchema 注解用于 SpringMVC 和 JAX-RS 两种服务开发风格,而 @RpcSchema 注解用于透明RPC开发风格,这两种注解的差异主要体现在服务端代码开发风格和契约生成模式上。Java-Chassis有一个隐式契约功能,可以让框架根据服务端接口代码自动生成服务契约。由于 SpringMVC 和 JAX-RS 开发风格会在REST接口上打上各种注解,有利于框架生成更符合REST风格的契约;而透明RPC模式由于接口上没有注解,Java-Chassis框架无法确定接口语义以及各个参数在HTTP请求中的位置,因此所有的接口都会作为 POST 接口处理,请求参数都会包装到HTTP请求的body中。这样的契约不太符合REST风格。因此,Java-Chassis更推荐用户使用 @RestSchema 注解开发服务端接口,这样用户只需要写代码,就能让框架自动生成符合REST风格的服务契约。Java-Chassis的传输模型和编程模型是解耦合的。传输模型方面,Java-Chassis支持REST和highway,其中REST又分为以Vert.x作为底层通信框架的REST over Vertx和以Tomcat之类的servlet容器为底层通信框架的REST over Servlet。Java-Chassis的性能主要受传输模型的影响。当用户选择了一个确定的传输模型后,无论他在服务端选择的是哪种编程模型性能都是差不多的。最后,Java-Chassis在客户端代码开发风格上提供了RPC开发模式和RestTemplate开发模式,在其他条件完全相同的情况下,RPC开发模式的性能比RestTemplate高,因此更推荐使用RPC模式编写consumer端代码。http://zhibo.huaweicloud.com/?referVisitorId=oiOYo0XLXIVOMO-ACc9q2cKXGOFQ&c=activity&a=live&id=151108&from=timeline&isappinstalled=0这里视频有说明性能比较数据Q:Caused by: java.lang.IllegalStateException: The schema(id=[calculatorRestEndpoint]) content held by this instance and the service center is different. You need to increment microservice version before deploying. Or you can configure service_description.environment=development to work in development environment and ignore this error 在bmi例子上做修改,报上面这个错,怎么解决啊?A:这通常是因为你修改了REST接口定义,但是既没有升级微服务版本号,也没有把环境配置成开发环境,所以ServiceComb启动实例检查契约的时候发现契约内容不一致,就报错了。可以考虑升级一下微服务版本号,或者在microservice.yaml文件里面配置一下service_description.environment=development,或者等sc里面的服务实例下线后,把微服务信息删掉重新注册。Q:有谁知道吗?除了注解方式@RequestHeader,还有什么方法可以在Controller中拿到request中的header参数?目前测试发现HttpServletRequest没法拿到A:如果这个Header参数定义在了你的接口契约里,那你的REST接口方法能直接拿到header参数。否则可以扩展一个HttpServerFilter从requestEx参数里面拿header,也可以在你的REST接口里面加上HttpServletRequest参数,从里面拿到headerQ:如何将一个服务部署多个,实现自动减压的?是在配置文件上设置,还是使用其他工具实现。给个链接谢谢。A:https://docs.servicecomb.io/java-chassis/zh_CN/references-handlers/loadbalance.htmlQ:The Service pv-web's instance 62d30daeed5011e8af2f00e081ba3ef7 has been isolated for a while, give a single test opportunity. org.apache.servicecomb.loadbalance.filter.IsolationDiscoveryFilter.allowVisit(IsolationDiscoveryFilter.java:119)?A:你贴出来的日志表示pv-web服务的某个实例被隔离了,现在已经经过了一段时间,Java-Chassis框架在尝试将一个请求路由到该实例,看看它能否被调通,如果调通了的话,这个实例就会从隔离状态恢复过来。https://docs.servicecomb.io/java-chassis/zh_CN/references-handlers/loadbalance.html 请参考这篇文档的“实例隔离功能”。Q:启动ServiceComb-java-chassis项目 sample/codefirst-sample 目录里的 CodeFirstProviderMain,例子程序怎么启动不起来?Caused by: java.lang.NoSuchMethodError: org.apache.commons.lang3.reflect.MethodUtils.getMethodsWithAnnotation(Ljava/lang/Class;Ljava/lang/Class;ZZ)[Ljava/lang/reflect/Method; at org.apache.servicecomb.foundation.common.event.SimpleEventBus.collectSubscribers(SimpleEventBus.java:41) at java.util.concurrent.ConcurrentHashMap.computeIfAbsent(ConcurrentHashMap.java:1660) at org.apache.servicecomb.foundation.common.concurrent.ConcurrentHashMapEx.computeIfAbsent(ConcurrentHashMapEx.java:56) at org.apache.servicecomb.foundation.common.event.SimpleEventBus.register(SimpleEventBus.java:51) at org.apache.servicecomb.serviceregistry.task.AbstractTask.<init>(AbstractTask.java:38) at org.apache.servicecomb.serviceregistry.task.AbstractRegisterTask.<init>(AbstractRegisterTask.java:28) at org.apache.servicecomb.serviceregistry.task.MicroserviceRegisterTask.<init>(MicroserviceRegisterTask.java:47) at org.apache.servicecomb.serviceregistry.task.MicroserviceServiceCenterTask.<init>(MicroserviceServiceCenterTask.java:28) at org.apache.servicecomb.serviceregistry.registry.AbstractServiceRegistry.createServiceCenterTask(AbstractServiceRegistry.java:201) at org.apache.servicecomb.serviceregistry.registry.AbstractServiceRegistry.init(AbstractServiceRegistry.java:106) at org.apache.servicecomb.serviceregistry.registry.RemoteServiceRegistry.init(RemoteServiceRegistry.java:54) at org.apache.servicecomb.serviceregistry.RegistryUtils.init(RegistryUtils.java:66) at org.apache.servicecomb.core.CseApplicationListener.setApplicationContext(CseApplicationListener.java:48) at org.springframework.context.support.ApplicationContextAwareProcessor.invokeAwareInterfaces(ApplicationContextAwareProcessor.java:121) at org.springframework.context.support.ApplicationContextAwareProcessor.postProcessBeforeInitialization(ApplicationContextAwareProcessor.java:97) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsBeforeInitialization(AbstractAutowireCapableBeanFactory.java:409) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1622) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.d**ateBean(AbstractAutowireCapableBeanFactory.java:555) ... 13 moreA:用户本地工程的lang3包没有更新到ServiceComb配套的版本,解决方法:1.在用户本地工程的pom.xml点击鼠标右键,选择执行 maven -> reimport2.在用户本地工程的pom.xml点击鼠标右键,选择执行 maven -> Show effective pom,确认其中的lang3是:<dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.6</version> </dependency>Q:ServiceComb配置中心支持“配置更新后自动刷新到服务”,如果服务有多个节点,是如何实现的?想来如果是API调用刷新不能确定刷到每个节点,是否cse client端有消息流和配置中心连接呢?类似于企业总线。如果是消息总线,那么业务服务里,需要配置消息总线的url等信息吗? A:微服务需要配置中心地址。定期pull配置,或者建立websocket链接采用push机制Q:在 ServiceComb-CRM-WorkShop 例子中 边缘服务afterReceiveRequest方法 在调用String userName = template.getForObject("cse://"+USER_SERVICE_NAME + "/validate?token={token}", String.class, token)时候,第一次调用正常后边调用会出现阻塞线程问题,报错:Thread Thread[transport-vert.x-eventloop-thread-4,5,main] has been blocked for 5433 ms, time limit is 2000A:ServiceComb的Edge Service默认工作于高性能的reactive模式,此模式要求工作于Edge Service转发流程中的业务代码不能有任何的阻塞操作。该问题是由于用户业务模型无法满足reactive要求,因此,可以通过修改微服务的配置文件,使用ServiceComb提供的同步线程池模式解决。使用指南可以参考:https://docs.servicecomb.io/java-chassis/zh_CN/edge/by-servicecomb-sdk.htmlQ:spring mvc项目中实现的filter,切换到微服务后,全部不起作用了,是不支持springmvc里边的filter了吗?用户工程同时添加了springboot和provider-springmvc的依赖:<dependency> <groupId>org.apache.servicecomb</groupId> <artifactId>spring-boot-starter-provider</artifactId> </dependency> <!-- springmvc支持 --> <dependency> <groupId>org.apache.servicecomb</groupId> <artifactId>provider-springmvc</artifactId> </dependency>A:问题原因是因为springmvc和ServiceComb REST是两套不同架构,用户将基于springmvc的项目切换到ServiceComb后,还需要基于ServiceComb REST规则对老的filter做微调,ServiceComb提供相关的架构迁移案例,详情可以参考:1.不同框架之间迁移改造可能工作量的评估指南:https://bbs.huaweicloud.com/blogs/ba7b62178cb811e89fc57ca23e93a89f 可以参考下2.在Spring Boot中使用ServiceComb:https://huaweicse.github.io/cse-java-chassis-doc/using-cse-in-spring-boot/using-cse-in-spring-boot.htmlQ:ServiceComb支持thymeleaf吗?A:这个是个web框架,需要在web容器里面运行,其实和servicecomb是两个完全独立的东西了。servicecomb也支持在web容器里面运行,理论上两个都可以放到一起跑,但我们没用过这个框架。 https://docs.servicecomb.io/java-chassis/zh_CN/build-provider/protocol/rest-over-servlet.htmlQ:原来的那些过滤器都失效了,怎么做才可以既保持原有的能力,又可以把需要发布的接口通过@RestSchema申明出去?A:因为是两个不同的运行时,涉及到“框架内部机制”,而不仅仅是“形式”上的东西。 结合功能,一般都有对应的解决方案的。附上一个简单的不同框架之间迁移改造可能工作量的评估指南: https://bbs.huaweicloud.com/blogs/ba7b62178cb811e89fc57ca23e93a89f 可以参考。 Q:群里有人压过 servicecomb 同步restful 和 spring boot的restful的差距吗?A:网上有很多对比分析,我们也有测试过。一般标准的虚拟机4u8g,差不多分别是5万单位和1万单位。servicecomb restful和其它rpc框架,比如grpc,dubbo,tars等在一个差不多的水准,即5万单位。和调优方式,测试方法不同,会有一些偏差。Q:蓝云上安装了mysql,启动alpha的jar包,直接跑saga源码里的saga-servicecomb-demo,能成功体验事务,照着saga源码里的saga-servicecomb-demo写了个一模一样的工程,是springboot接入servicecomb,调用却出了问题,子事务那边报空指针异常,globalTxId,parentTxId都为null, localTxId有值;自己没找出原因,谁了解这块的帮个忙看下吧,代码地址是:https://github.com/weichao666/sagademoA:saga demo的那个问题解决了,如果使用java chassis1.0.0版本,需要引入guava20.0版本,否则启动报错,注解和调用链正确配置的情况下,使用java chassis1.1.0-SNAPSHOT版本是没问题的,java chassis1.0.0版本使用的guava是19.0Q:ServiceComb中的同步 restful 需要特殊配置吗?A:除edge service之外,ServiceComb的restful默认都是同步的,不需要特殊配置。今天的FAQ精选问题总计20个,可以手动收藏一下哦~And小蜜蜂欢迎来我们社区提问笔芯关注我们~
-
调用链原理和场景正如Service Mesh的诞生是为了解决大规模分布式服务访问的治理问题,调用链的出现也是为了对应于大规模的复杂的分布式系统运行中碰到的故障定位定界问题。大量的服务调用、跨进程、跨服务器,可能还会跨多个物理机房。无论是服务自身问题还是网络环境的问题导致调用上链路上出现问题都比较复杂,如何定位就比单进程的一个服务打印一个异常栈来找出某个方法要困难的多。需要有一个类似的调用链路的跟踪,经一次请求的逻辑规矩完整的表达出来,可以观察到每个阶段的调用关系,并能看到每个阶段的耗时和调用详细情况。Dapper, a Large-Scale Distributed Systems Tracing Infrastructure 描述了其中的原理和一般性的机制。模型中包含的术语也很多,理解最主要的两个即可:Trace:一次完整的分布式调用跟踪链路。Span:跨服务的一次调用; 多个Span组合成一次Trace追踪记录。上图是Dapper论文中的经典图示,左表示一个分布式调用关系。前端(A),两个中间层(B和C),以及两个后端(D和E)。用户发起一个请求时,先到达前端,再发送两个服务B和C。B直接应答,C服务调用后端D和E交互之后给A应答,A进而返回最终应答。要使用调用链跟踪,就是给每次调用添加TraceId、SpanId这样的跟踪标识和时间戳。右表示对应Span的管理关系。每个节点是一个Span,表示一个调用。至少包含Span的名、父SpanId和SpanId。节点间的连线下表示Span和父Span的关系。所有的Span属于一个跟踪,共用一个TraceId。从图上可以看到对前端A的调用Span的两个子Span分别是对B和C调用的Span,D和E两个后端服务调用的Span则都是C的子Span。调用链系统有很多实现,用的比较多的如zipkin,还有已经加入CNCF基金会并且的用的越来越多的Jaeger,满足Opentracing语义标准的就有这么多。一个完整的调用链跟踪系统,包括调用链埋点,调用链数据收集,调用链数据存储和处理,调用链数据检索(除了提供检索的APIServer,一般还要包含一个非常酷炫的调用链前端)等若干重要组件。上图是Jaeger的一个完整实现。这里我们仅关注与应用相关的内容,即调用链埋点的部分,看下在Istio中是否能做到”无侵入“的调用链埋点。当然在最后也会看下Istio机制下提供的不同的调用链数据收集方式。Istio标准BookInfo例子简单期间,我们以Istio最经典的Bookinfo为例来说明。Bookinfo模拟在线书店的一个分类,显示一本书的信息。本身是一个异构应用,几个服务分别由不同的语言编写的。各个服务的模拟作用和调用关系是:productpage :productpage 服务会调用 details 和 reviews 两个服务,用来生成页面。details :这个微服务包含了书籍的信息。reviews :这个微服务包含了书籍相关的评论。并调用 ratings 微服务。ratings :ratings 微服务中包含了由书籍评价组成的评级信息。调用链输出在Istio上运行这个典型例子,不用做任何的代码修改,自带的Zipkin上就能看到如下的调用链输出。可以看到展示给我们的调用链和Boookinfo这个场景设计的调用关系一致:productpage 服务会调用 details 和 reviews 两个服务,reviews调用了ratings 微服务。除了显示调用关系外,还显示了每个中间调用的耗时和调用详情。基于这个视图,服务的运维人员比较直观的定界到慢的或者有问题的服务,并钻取当时的调用细节,进而定位到问题。我们就要关注下调用链埋点到底是在哪里做的,怎么做的?在Istio中,所有的治理逻辑的执行体都是和业务容器一起部署的Envoy这个Sidecar,不管是负载均衡、熔断、流量路由还是安全、可观察性的数据生成都是在Envoy上。Sidecar拦截了所有的流入和流出业务程序的流量,根据收到的规则执行执行各种动作。实际使用中一般是基于K8S提供的InitContainer机制,用于在Pod中执行一些初始化任务. InitContainer中执行了一段iptables的脚本。正是通过这些Iptables规则拦截pod中流量,并发送到Envoy上。Envoy拦截到Inbound和Outbound的流量会分别作不同操作,执行上面配置的操作,另外再把请求往下发,对于Outbound就是根据服务发现找到对应的目标服务后端上;对于Inbound流量则直接发到本地的服务实例上。我们今天的重点是看下拦截到流量后Sidecar在调用链埋点怎么做的。 Istio调用链埋点逻辑Envoy的埋点规则和在其他服务调用方和被调用方的对应埋点逻辑没有太大差别。Inbound流量:对于经过Sidecar流入应用程序的流量,如果经过Sidecar时Header中没有任何跟踪相关的信息,则会在创建一个根Span,TraceId就是这个SpanId,然后再将请求传递给业务容器的服务;如果请求中包含Trace相关的信息,则Sidecar从中提取Trace的上下文信息并发给应用程序。Outbound流量:对于经过Sidecar流出的流量,如果经过Sidecar时Header中没有任何跟踪相关的信息,则会创建根Span,并将该跟Span相关上下文信息放在请求头中传递给下一个调用的服务;当存在Trace信息时,Sidecar从Header中提取Span相关信息,并基于这个Span创建子Span,并将新的Span信息加在请求头中传递。特别是Outbound部分的调用链埋点逻辑,通过一段伪代码描述如图:调用链详细解析如图是对前面Zipkin上输出的一个Trace一个透视图,观察下每个调用的细节。可以看到每个阶段四个服务与部署在它旁边上的Sidecar是怎么配合的。在图上只标记了Sidecar生成的Span主要信息。因为Sidecar 处理 Inbound和Outbound的逻辑有所不同,在图上表也分开两个框图分开表达。如productpage,接收外部请求是一个处理,给details发出请求是一个处理,给reviews发出请求是另外一个处理,因此围绕productpage这个app有三个黑色的处理块,其实是一个Sidecar在做事。同时,为了不使的图上箭头太多,最终的Response都没有表达出来,其实图上每个请求的箭头都有一个反方向的Response。在服务发起方的Sidecar会收到Response时,会记录一个CR(client Received)表示收到响应的时间并计算整个Span的持续时间。下面通过解析下具体数据来找出埋点逻辑: 首先从调用入口的Gateway开始,Gateway作为一个独立部署在一个pod中的Envoy进程,当有请求过来时,它会将请求转给入口服务productpage。Gateway这个Envoy在发出请求时里面没有Trace信息,会生成一个根Span:SpanId和TraceId都是f79a31352fe7cae9,因为是第一个调用链上的第一个Span,也就是一般说的根Span,所有ParentId为空,在这个时候会记录CS(Client Send);请求从入口Gateway这个Envoy进入productpage的app业务进程其Inbound流量被productpage Pod内的Envoy拦截,Envoy处理请求头中带着Trace信息,记录SR(Server Received),并将请求发送给productpage业务容器处理,productpage在处理请求的业务方法中在接受调用的参数时,除了接受一般的业务参数外,同时解析请求中的调用链Header信息,并把Header中的Trace信息传递给了调用的Details和Reviews的微服务。从productpage出去的请求到达reviews服务前,其Oubtbound流量又一次通过同Pod的Envoy,Envoy埋点逻辑检查Header中包含了Trace相关信息,在将请求发出前会做客户端的调用链埋点,即以当前Span为parent Span,生成一个子Span:新的SpanId cb4c86fb667f3114,TraceId保持一致9a31352fe7cae9,ParentId就是上个Span的Id: f79a31352fe7cae9。从prodcutepage到review的请求经过productpage的Sidecar走LB后,发给一个review的实例。请求在到达Review业务容器前,同样也被Review的Envoy拦截,Envoy检查从Header中解析出Trace信息存在,则发送Trace信息给reviews。reviews处理请求的服务端代码中同样接收和解析出这些包含Trace的Header信息,发送给下一个Ratings服务。在这里我们只是理了一遍请求从入口Gateway,访问productpage服务,再访问reviews服务的流程。可以看到期间每个访问阶段,对服务的Inbound和Outbound流量都会被Envoy拦截并执行对应的调用链埋点逻辑。图示的Reviews访问Ratings和productpage访问Details逻辑与以上类似,这里不做复述。以上过程也印证了前面我们提出的Envoy的埋点逻辑。可以看到过程中除了Envoy再处理Inbound和Outbound流量时要执行对应的埋点逻辑外。每一步的调用要串起来,应用程序其实做了些事情。就是在将请求发给下一个服务时,需要将调用链相关的信息同样传下去,尽管这些Trace和Span的标识并不是它生成的。这样在出流量的proxy向下一跳服务发起请求前才能判断并生成子Span并和原Span进行关联,进而形成一个完整的调用链。否则,如果在应用容器未处理Header中的Trace,则Sidecar在处理请求时会创建根Span,最终会形成若干个割裂的Span,并不能被关联到一个Trace上,就会出现我们开始提到的问题。不断被问到两个问题来试图说明这个业务代码配合修改来实现调用链逻辑可能不必要:问题一、既然传入的请求上已经带了这些Header信息了,直接往下一直传不就好了吗?Sidecar请求APP的时候带着这些Header,APP请求Sidecar时也带着这些Header不就完了吗?问题二、既然TraceId和SpanId是Sidecar生成的,为什么要再费劲让App收到请求的时候解析下,发出请求时候再带着发出来传回给Sidecar呢?回答问题一,只需理解一点,这里的App业务代码是处理请求不是转发请求,即图上左边的Request to Productpage 到prodcutpage中请求就截止了,要怎么处理完全是productpage的服务接口的内容了,可以是调用本地处理逻辑直接返回,也可以是如示例中的场景构造新的请求调用其他的服务。右边的Request from productpage 完全是服务构造的发出的另外一个请求;回答问题二,需要理解当前Envoy是独立的Listener来处理Inbound和Outbound的请求。Inbound只会处理入的流量并将流量转发到本地的服务实例上。而Outbound就是根据服务发现找到对应的目标服务后端上。除了实在一个进程里外两个之间可以说没有任何关系。 另外如问题一描述,因为到Outbound已经是一个新构造的请求了,使得想维护一个map来记录这些Trace信息这种方案也变得不可行。这样基于一个例子来打开看一个调用链的主要过程就介绍到这里。附加productpage访问reviews的Span详细,删减掉一些数据只保留主要信息大致是这样:Productpage的Proxy上报了个CS,CR, reviews的那个proxy上报了个SS,SR。分别表示Productpage作为client什么时候发出请求,什么时候最终收到请求,reviews的proxy什么时候收到了客户端的请求,什么时候发出了response。另外还包括这次访问的其他信息如Response Code、Response Size等。根据前面的分析我们可以得到结论:埋点逻辑是在Sidecar代理中完成,应用程序不用处理复杂的埋点逻辑,但应用程序需要配合在请求头上传递生成的Trace相关信息。服务代码修改示例前面通过一个典型例子详细解析了Istio的调用链埋点过程中Envoy作为Sidecar和应用程序的配合关系。分析的结论是调用链埋点由Envoy来执行,但是业务程序要有适当修改。下面抽取服务代码来印证下。Python写的 productpage在服务端处理请求时,先从Request中提取接收到的Header。然后再构造请求调用details获取服务接口,并将Header转发出去。首先处理productpage请问的rest方法中从Request中提取Trace相关的Header。然后重新构造一个请求发出去,请求reviews服务接口。可以看到请求中包含收到的Header。reviews服务中Java的Rest代码类似,在服务端接收请求时,除了接收Request中的业务参数外,还要提取Header信息,调用Ratings服务时再传递下去。其他的productpage调用details,reviews调用ratings逻辑类似。当然这里只是个demo,示意下要在那个位置修改代码。实际项目中我们不会这样在每个业务方法上作这样的修改,这样对代码的侵入,甚至说污染太严重。根据语言的特点会尽力把这段逻辑提取成一段通用逻辑。Istio调用链数据收集:by Envoy一个完整的埋点过程,除了inject、extract这种处理Span信息,创建Span外,还要将Span report到一个调用链的服务端,进行存储并支持检索。在Isito中这些都是在Envoy这个Sidecar中处理,业务程序不用关心。在proxy自动注入到业务pod时,会自动刷这个后端地址.即Envoy会连接zipkin的服务端上报调用链数据,这些业务容器完全不用关心。当然这个调 用链收集的后端地址配置成jaeger也是ok的,因为Jaeger在接收数据是兼容zipkin格式的。Istio调用链数据收集:by Mixer除了直接从Envoy上报调用链到zipkin后端外,Istio提供了Mixer这个统一的面板来对接不同的后端来收集遥测数据,当然Trace数据也可以采用同样的方式。即如TraceSpan中描述,创建一个TraceSpan的模板,来描述从mixer的一次访问中提取哪些数据,可以看到Trace相关的几个ID从请求的Header中提取。除了基础数据外,基于Mixer和kubernetes的继承能力,有些对象的元数据,如Pod上的相关信息Mixr可以补充,背后其实是Mixer连了kubeapiserver获取对应的pod资源,从而较之直接从Envoy上收集的原始数据,可以有更多的业务上的扩张,如namespace、cluster等信息APM数据要用到,但是Envoy本身不会生成,通过这种方式就可以从Kubernetes中自动补充完整,非常方便。这也是Istio的核心组件Mixer在可观察性上的一个优秀实践。Istio官方说明更新最近一直在和社区沟通,督促在更显著的位置明确的告诉使用者用Istio作治理并不是所有场景下都不需要修改代码,比如调用链,虽然用户不用业务代码埋点,但还是需要修改些代码。尤其是避免首页“without any change”对大家的误导。得到回应是1.1中社区首页what-is-istio已经修改了这部分说明,不再是1.0中说without any changes in service code,而是改为with few or no code changes in service code。提示大家在使用Isito进行调用链埋点时,应用程序需要进行适当的修改。当然了解了其中原理,做起来也不会太麻烦。改了个程度轻一点的否定词,很少几乎不用修改,还是基本不用改的意思。这也是社区一贯的观点。结合对Istio调用链的原理的分析和一个典型例子中细节字段、流程包括代码的额解析,再加上和社区沟通的观点。得到以下结论:Istio的绝大多数治理能力都是在Sidecar而非应用程序中实现,因此是非侵入的;Istio的调用链埋点逻辑也是在Sidecar代理中完成,对应用程序非侵入,但应用程序需做适当的修改,即配合在请求头上传递生成的Trace相关信息。华为云Istio服务网格公测中在腾讯的场子上只讲干货的技术,尽量少做广告。在这里只是用一页PPT来简单介绍下华为云当前正在公测的Istio服务网格服务。华为云容器引擎CCE的深度集成,一键启用后,即可享受Istio服务网格的全部治理能力;基于应用运行的全景视图配置管理熔断、故障注入、负载均衡等多种智能流量治理功能;内置金丝雀、A/B Testing典型灰度发布流程灰度版本一键部署,流量切换一键生效;配置式基于流量比例、请求内容灰度策略配置,一站式健康、性能、流量监控,实现灰度发布过程量化、智能化、可视化;集成华为云APM,使用调用链、应用拓扑等多种手段对应用运行进行透视、诊断和管理。华为云Istio社区贡献华为作为CNCF 基金会的初创会员、白金会员,CNCF / Kubernetes TOC 成员。在Kubernetes社区贡献国内第一,全球第三,全球贡献3000+ PR,先后贡献了集群联邦、高级调度策略、IPVS负载均衡,容器存储快照等重要项目。随着Istio项目的深入产品化,团队也积极投入到Istio的社区贡献。当前社区贡献国内第一,全球第三。Approver 3席,Member 6席,Contributor 若干。通过Pilot agent转发实现HTTP协议的健康检查: 针对mTLS enabled环境,传统的kubernetes http健康检查不能工作,实现 sidecar转发功能,以及injector的自动注入。Istioctl debug功能增强:针对istioctl缺失查询sidecar中endpoint的能力,增加proxy-config endpoint、proxy-status endpoint命令,提高debug效率。HTTPRetry API增强: 增加HTTPRetry 配置项RetryOn策略,可以通过此控制sidecar重试。MCP 配置实现: Pilot支持mesh configuration, 可以与galley等多个实现了MCP协议的server端交互获取配置。以此解耦后端注册中心。Pilot CPU异常问题解决:1.0.0-snapshot.0 pilot free 状态CPU 利用率超过20%,降低到1%以下。Pilot 服务数据下发优化:缓存service,避免每次push时进行重复的转换。Pilot服务实例查询优化:根据label selector查询endpoints(涵盖95%以上的场景),避免遍历所有namespace的endpoints。Pilot 数据Push性能优化:将原有的串行顺序推送配置,更新为并行push,降低配置下发时延。
-
微服务运行很长一段时间,日志里面有大量这些信息:[2019-01-11 05:37:31,801/UTC][LoadBalancerStatsTimer][INFO]stats of instance 06b3ec5307b311e9bc86fa163ee9b7d0 removed. org.apache.servicecomb.loadbalance.ServiceCombLoadBalancerStats$2.onRemoval(ServiceCombLoadBalancerStats.java:145)[]打印这个日志的原因是微服务实例在一段时间内(缺省为5分钟)没有被访问,系统将其统计信息自动移除。 日志打印频繁通常由于业务实例很多,实例很久没有被访问。
-
最近刚好在infoq上看到下一代分布式的设计理念,感触颇深,二十年来,整个分布式系统架构的演进,从 C/S 到 B/S,再到分布式系统。今天,微服务,网格,云计算大行其道,虽然二十年前我刚刚上初中!以当前常见的服务架构举例:微服务常见微服务架构,其前端流量入口采用负载。应用层常见是采取一级网络 (通过配置推送的软负载) 或者二级网络 (通过应用网关负载隔离) 模式。阿里是使用前者,百度、新浪使用后者,主要取决于微服务的展现形式 (RPC or Rest-API),差异是是否需要一个专职配置中心。为保证请求无状态地实现迁移,所以使用共享数据节点 (存储各种形式的临时或中间数据) 的形式实现。数据节点往往采用分片的主备模式。而伴随微服务的增长,当前我们的系统作为2.0基于RPC的微服务架构,也衍生出很多问题,例如服务间调用关系由当初的设计自底向上,逐渐变成网状调用依赖。部署上资源限制,效率瓶颈,缺乏总体架构。大量的远程调用。爆炸式增长的碎片化应用。问题定位变得复。另外,远程调用中最大的问题就是性能问题。如何解决以上的问题,同时也是下一代架构目标:流式架构/反应式编程(Reactive Architecture/Programming)作为一种范式在整个业界正在逐步受到认可和落地,是对过往系统的业务需求理解梳理之后对系统技术设计/架构模式的提升总结。 作为JAVA程序员的你应该感到高兴,因为,Java作为一个成熟平台,对于趋势一向有些稳健的接纳和跟进能力,有着令人惊叹的生命活力: 1. Java 7提供了ForkJoinPool,支持了Java 8提供的Stream(Reactive Stream是RP的一个核心组件)。 2. 另外Java 8还提供了Lamda(有效地表达和使用RP需要FP的语言构件和理念)。 3. 有了前面的这些稳健但不失时机的准备,在Java 9中提供了面向RP的Flow API,为Java圈子提供了官方的RP API,标志着RP由集市式的自由探索阶段 向 教堂式的统一使用的转变。 响应式编程就是异步数据流编程 说了这么就,什么是响应式编程(Reactive Programming)?传统的顺序编程采用每条指令依次执行的方式,上一条指令没有执行结束,当前的线程就得等着,任你如何提升机器性能还是代码性能,如果本质不变,始终改变不了响应需要等待的现实。若要响应迅速,就得把顺序执行指令的方式换一换——同步换成异步,方法执行换做消息发送,于是,就有了上边的响应式的定义:响应式编程就是异步数据流编程,这其实是一种编程范式,是编程理念的一种思想转型。因为采用响应式编程,我们就不再将软件要处理的业务视为对象,又或者函数,而是直接透析到本质:数据流(Data Stream)。万事万物皆为流从函数式编程的角度来讲,一连串组合函数的调用,其实就是数据在流动。函数可以抽象地视为一种数据类型到另一种数据类型的转换。将各种形式的转换(map、flatMap、filter)穿起来,同时保证数据的不变性(Immutable),则数据就能非常可靠地在函数链条中流动,或者被分析,或者被转换,或者被过滤。当我们将编程的范式切换为“流(Stream)”时,我们欣喜地发现,这种方式可以在很大程度上确保数据是不变的。这就为并行开发创造了可能。然而,普通的数据流编程范式并不能满足“响应式Reactive”的本初定义。我们需要响应迅速。如何才能做到?那就是要做到没有阻塞,这就是我们通常所说的异步工作方式。因而,响应式编程的设计原则是:保持数据的不变性没有共享阻塞是有害的恰好,这三条特征也是Actor模型拥有的。Actor,这个诞生在1970的产物,遥遥领先于那个时间,知道很久以后,Erlang这种基于Acotr的模型设计的面向并发编程的新语言横空出世,并在并发领域树立一座丰碑,古老的Actor才重见天日,再次成为分布式计算领域的焦点技术之一,目前,几乎所有的主流平台都支持Actor模型:JAVA平台下的Scala的Actor类库jetlan。Actor的理念很简单,Actor是一等公民,一切皆Actor,之间通过消息发送通信,所有都是异步的,不同Actor可以自己处理各自的消息。整个系统获得良好的大规模并发处理能力。在《Scala并发编程》一书中,Aleksandar Prokopec形象地描述了Actor系统:Actor系统模仿了人类的组织,如公司、政府和其他大型机构。在软件公司中,有许多需要以并发方式达成的目标。为了实现这些目标,数百或数千名员工一起努力工作,而且这些员工通常会被组织成一种层次结构。许多员工会为级别比他们低的员工分派工作。为了高效地工作和决策,员工们使用电子邮件进行通信。当员工早上上班时,就会检查他的电子邮箱并对重要的消息做出回应。如果某封电子邮件非常重要,那么这个员工就必须立刻回复这封邮件。当员工忙着回复一封电子邮件时,可能会收到另一封电子邮件,而且后续的电子邮件都会进入他的电子邮箱中。只有当员工处理完成当前的电子邮件后,他才能继续处理下一封电子邮件。软件公司就相当于是一个ActorSystem,每位员工则是一个一个Actor。电子邮件是Actor之间彼此发送的消息(Message),一旦发送了消息,就不必等待收件人的回复,可以继续自己的工作,也就是说这种消息发送的方式是异步非阻塞的。Actor持有的MailBox正好借用了这里所谓的电子邮箱概念。因而对于每个Actor而言:每个Actor都拥有独立的MailBox;接收到的消息皆为不可变对象,且完全独立;不管是tell消息还是ask消息,Actor执行消息的方式都是异步非阻塞的。不得不提的Akka AKKA框架是一个实现Actors模型的Scala或Java平台,实现多线程安全应用。 Actors是一个轻量级的对象,通过发送消息实现交互。每个Actors在同一时间处理最多一个消息,可以发送消息给其他Actors。在同一时间可以于一个Java虚拟机存在数以百万计的参与者,构架是一个分层的父层(管理) - 子层,其中父层监控子层的行为。还可以很容易地扩展Actor运行在集群中各个节点之间 - 无需修改一行代码。每个演员都可以有内部状态(字段/变量) ,但通信只能通过消息传递,不会有共享数据结构(计数器,队列) 。Akka框架支持两种语言Java和Scala,** is cheap,show me the codeAkka actors是轻量,能够平均在一个系统创建数千个。线程是重量的,场景切换相当慢。横向扩展Scale out,Actor能够在代码没有任何修改时在远程运行。失败恢复和错误处理Actor的Java代码就如上面简介中所说的,AKKA把并发操作的各种复杂的东西都统一的做了封装.我们主要关心的是业务逻辑的实现,只需要少量的关心Actor模型的串联即可构建出高可用,高性能,高扩展的应用.我希望在后续的应用的开发中朝着这个方向努力,今天的分享就到这里。最近我对内存相关的内容比较感兴趣,下个blog我会在跟大家分享下关于内存的内容。谢谢! 部分资料来自 infoq & 简书https://www.jianshu.com/p/3bdb8dbaa35c
-
1 前言前两天有同事发现,通过华为云 ServiceStage 的流水线部署基于模板创建的 CSEJavaSDK demo 服务时,会在容器启动过程中报错。初步排查是由于 JVM 占用的内存超出了 docker 内存配额的上限,导致容器被 kill 掉。于是我们需要排查一下问题出在哪里,为什么以前没有这类问题,而现在却发生了。2 基本定位要确定 docker 容器内存超限问题的直接原因并不难。直接进入docker容器,执行 top 命令,我们发现宿主机是一台8核16G的机器,而且 docker 并不会屏蔽这些信息,也就是 JVM 会认为自己工作于一台 16G 内存的机器上。而查看 demo 服务的 Dockerfile,发现运行服务时并没有对 JVM 的内存进行任何限制,于是 JVM 会根据默认的设置来工作 —— 最大堆内存为物理内存的1/4(这里的描述并不完全准确,因为 JVM 的默认堆内存大小限制比例其实是根据物理内存有所变化的,具体内容请自行搜索资料),而基于模板创建的 ServiceStage 流水线,在部署应用堆栈的时候会把 docker 容器的内存配额默认设置为 512M,于是容器就会在启动的时候内存超限了。至于以前没有碰到过这种问题的原因,只是因为以前没将这么高规格的 ECS 服务器用于流水线部署应用堆栈。在查询过相关资料后,我们找到了两种问题解决方案,一个是直接在 jar 包运行命令里加上 -Xmx 参数来指定最大堆内存,不过这种方式只能将 JVM 堆内存限制为一个固定的值;另一个方法是在执行 jar 包时加上 -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap 参数,让 JVM 能够感知到docker容器所设置的 cgroup 限制,相应地调整自身的堆内存大小,不过这个特性是 JDK 8u131 以上的版本才具备的。最终,我们提醒 ServiceStage 流水线的同学将 CSEJavaSDK demo 的创建模板做了改进,在 Dockerfile 中将打包的基础镜像版本由原先的 java:8u111-jre-alpine 升级为了 openjdk:8u181-jdk-alpine,并且在运行服务 jar 包的命令中加上了 -Xmx256m参数。问题至此已经解决了。3 进一步的探究虽然问题已经解决,但是在好奇心的驱使下,我还是打算自己找个 demo 实际去触发一下问题,另外看看从网上搜到的解决方法到底好不好用 : )3.1 准备工作3.1.1 创建云上工程首先需要在华为云 ServiceStage 创建一个云上工程。在 ServiceStage -> 应用开发 -> 微服务开发 -> 工程管理 -> 创建云上工程中,选择“基于模板创建”,语言选择 Java, 框架选择 CSE-Java (SpringMVC),部署系统选择“云容器引擎CCE”,给你的云上工程取一个名字,比如test-memo-consuming,最后选择存放代码的仓库,就可以完成云上工程的创建了。之后云上工程会根据你的选项自动地生成脚手架代码,上传到你指定的代码仓库中,并且为你创建一条流水线,完成代码编译、构建、打包、归档镜像包的操作,并且使用打好的 docker 镜像包在 CCE 集群中部署一个应用堆栈。创建云上工程和流水线不是本文的重点,我就不详细讲操作了 : )。同一个应用堆栈的实例可以部署多个,在这里为了实验方便就按照默认值1个来部署。由于云上工程已经改进了脚手架代码的模板,不会再出现内存超限的问题,所以我们现在能看到 demo 服务已经正常的跑起来,微服务实例已经注册到服务中心了。登录到 demo 服务所部署的容器,使用curl命令可以调用 demo 服务的 helloworld 接口,可以看到此时服务已经可以正常工作。3.1.2 增加实验代码为了能够触发微服务实例消耗更多的内存,我在项目代码中增加了如下接口,当调用/allocateMemory接口时,微服务实例会不停申请内存,直到 JVM 抛出 OOM 错误或者容器内存超限被 kill 掉。private HashMap<String, long[]> cacheMap = new HashMap<>(); @GetMapping(value = "/allocateMemory") public String allocateMemory() { LOGGER.info("allocateMemory() is called"); try { for (long i = 0; true; ++i) { cacheMap.put("key" + i, new long[1024 * 1024]); } } catch (Throwable t) { LOGGER.info("allocateMemory() gets error", t); } return "allocated"; }此时用来打镜像包的基础镜像是openjdk:8u181-jdk-alpine,jar 包启动命令中加上了-Xmx256m参数。执行流水线,应用堆栈部署成功后,调用/allocateMemory接口触发微服务实例消耗内存,直到 JVM 抛出 OOM 错误,可以在 ServiceStage -> 应用上线 -> 应用管理中选择相应的应用,点击进入概览页面,查看应用使用内存的情况。应用使用的内存从 800M+ 陡然下降的时间点就是我重新打包部署的时间,而之后由于调用/allocateMemory接口,内存占用量上升到了接近 400M,并且在这个水平稳定了下来,显示-Xmx256m参数发挥了预期的作用。3.2 复现问题现在将 demo 工程中的 Dockerfile 修改一下,将基础镜像改为 java:8u111-jre-alpine,并且删除启动命令中的-Xmx256m参数,将其提交为noLimit_oldBase分支,推送到代码仓库中。然后编辑流水线,将 source 阶段的任务所使用的代码分支改为noLimit_oldBase分支,保存并重新运行流水线,将新的代码打包部署到应用堆栈中。在微服务实例列表中查询到新的微服务实例的 endpoint IP 后,调用`/allocateMemory`接口,观察内存情况,内存从接近 400M 突然掉下去一下,然后又上升到约 450M 的时间点就是修改代码后的微服务实例部署成功的时间点,之后内存占用量突然下跌就是因为调用`/allocateMemory`接口导致容器内存超限被 kill 掉了。如果你事先使用docker logs -f命令查看容器日志的话,那么日志大概是这个样子的2018-11-23 15:40:04,920 INFO SCBEngine:152 - receive MicroserviceInstanceRegisterTask event, check instance Id... 2018-11-23 15:40:04,920 INFO SCBEngine:154 - instance registry succeeds for the first time, will send AFTER_REGISTRY event. 2018-11-23 15:40:04,925 WARN VertxTLSBuilder:116 - keyStore [server.p12] file not exist, please check! 2018-11-23 15:40:04,925 WARN VertxTLSBuilder:136 - trustStore [trust.jks] file not exist, please check! 2018-11-23 15:40:04,928 INFO DataFactory:62 - Monitor data sender started. Configured data providers is {com.huawei.paas.cse.tcc.upload.TransactionMonitorDataProvider,com.huawei.paas.monitor.HealthMonitorDataProvider,} 2018-11-23 15:40:04,929 INFO ServiceCenterTask:51 - read MicroserviceInstanceRegisterTask status is FINISHED 2018-11-23 15:40:04,939 INFO TestmemoconsumingApplication:57 - Started TestmemoconsumingApplication in 34.81 seconds (JVM running for 38.752) 2018-11-23 15:40:14,943 INFO AbstractServiceRegistry:258 - find instances[1] from service center success. service=default/CseMonitoring/latest, old revision=null, new revision=28475010.1 2018-11-23 15:40:14,943 INFO AbstractServiceRegistry:266 - service id=8b09a7085f4011e89f130255ac10470c, instance id=8b160d485f4011e89f130255ac10470c, endpoints=[rest://100.125.0.198:30109?sslEnabled=true] 2018-11-23 15:40:34,937 INFO ServiceCenterTaskMonitor:39 - sc task interval changed from -1 to 30 2018-11-23 15:47:03,823 INFO SPIServiceUtils:76 - Found SPI service javax.ws.rs.core.Response$StatusType, count=0. 2018-11-23 15:47:04,657 INFO TestmemoconsumingImpl:39 - allocateMemory() is called Killed可以看到allocateMemory方法被调用,然后 JVM 还没来得及抛出 OOM 错误,整个容器就被 kill 掉了。这里也给大家提了一个醒:不要以为自己的服务容器能启动起来就万事大吉了,如果没有特定的限制,JVM 会在运行时继续申请堆内存,也有可能造成内存用量超过 docker 容器的配额!3.3 让 JVM 感知cgroup限制前文提到还有另外一种方法解决 JVM 内存超限的问题,这种方法可以让 JVM 自动感知 docker 容器的 cgroup 限制,从而动态的调整堆内存大小,感觉挺不错的。我们也来试一下这种方法,看看效果如何 ; )回到demo项目代码的master分支,将 Dockerfile 中启动命令参数的-Xmx256m替换为-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap,提交为useCGroupMemoryLimitForHeap分支,推送到代码仓库里。再次运行流水线进行构建部署。等 demo 服务部署成功后,再次调用`/allocateMemory`接口,容器的内存占用情况如上图所示(最右边的那一部分连续曲线),内存上升到一定程度后,JVM 抛出了 OOM 错误,没有继续申请堆内存。看来这种方式也是有效果的。不过,仔细观察容器的内存占用情况,可以发现容器所使用的内存仅为不到 300M,而我们对于这个容器的内存配额限制为 512M,也就是还有 200M+ 是闲置的,并不会被 JVM 利用。这个利用率,比起上文中直接设置-Xmx256m的内存利用率要低 : ( 。推测是因为 JVM 并不会感知到自己是部署在一个 docker 容器里的,所以它把当前的环境当成一个物理内存只有 512M 的物理机,按照比例来限制自己的最大堆内存,另一部分就被闲置了。如此看来,如果想要充分利用自己的服务器资源,还是得多花一点功夫,手动调整好-Xmx参数。4 参考资料Java and Docker, the limitationsJava inside docker: What you must know to not FAIL本文转载自Docker容器内部署Java微服务的内存限制问题
-
同一个项目,其他的微服务可以正常注册和启动,就这个注册不了,新人,求解决!!!
-
微服务引擎基础版:本质上逻辑多租,所有租户用户共用一套微服务引擎(注册中心、配置中心、治理中心),按照官方公布的计费方式,后续主要提供按需计费。微服务引擎专享版:本质上物理多租,每个租户独享微服务引擎,管理规模更大、访问性能更佳、物理级隔离,安全性更高!言归正传,到底微服务引擎基础版、专享版怎么使用呢?微服务引擎基础版正确的打开方式:最便捷的打开方式——>云上工程:基于模板自动化创建项目、自动构建、自动部署和自动注册到微服务引擎基础版统一注册中心(例如,华北区是https://cse.cn-north-1.myhuaweicloud.com)Step1:创建云上工程查看结果:流水线自动运行成功查看结果:自动创建容器应用查看结果:自动注册到微服务引擎基础版服务中心微服务引擎专享正确的打开方式:最便捷的打开方式——>仅需修改修改容器应用环境变量环境变量key环境变量value微服务引擎基础版PAAS_CSE_ENDPOINT引擎列表中获取,华北区为https://cse.cn-north-1.myhuaweicloud.com微服务引擎专享版PAAS_CSE_ENDPOINT引擎列表中获取,形如https://192.168.xxx.xxx:30100查看结果:自动注册到微服务引擎专享版版(专享版名称none1121)的服务中心
-
最新版本: 2.2.8插件简介: 服务契约,指基于OpenAPI规范的微服务接口契约,是服务端与消费端对于接口的定义。服务契约用于服务端和消费端的解耦,服务端围绕契约进行服务的实现,消费端根据契约进行服务的调用。ServiceComb使用yaml文件格式定义服务契约,可支持多种风格开发微服务。 CSE-Codegen是基于Swagger Codegen实现的代码生成工具,用户只需在微服务工程的服务端和消费端的pom文件分别引入插件依赖,就可以根据定义好的契约文件生成服务端和消费端的框架代码,轻松构建微服务应用。插件特性: l 新增契约同步功能 支持从远端的Git仓库同步一个或多个契约到微服务工程中,每次运行都可以根据最新的契约生成框架代码。 l 推荐使用SpringMVC风格开发微服务 在不指明language的情况下默认生成SpringMVC风格的框架代码。 l 支持微服务工程服务端和消费端代码生成 通过在微服务工程的pom文件中配置插件依赖,生成以下文件:服务端provider生成model + delegate + controller + impl,消费端consumer生成model + delegate + impl。 l 适应多服务多契约的场景 在配置中增加参数,可以适应多服务多契约的场景。 l 避免model路径不统一导致consumer调用provider失败 要求契约的definitions的model具备x-java-class,根据包路径优先级(x-java-class > service.packageName > packageName)生成model包路径,避免model路径不统一导致consumer调用provider失败。 l 最大程度保证显式契约和隐式契约一致 严格按照契约定义,针对契约的不同返回码,生成的框架代码会在,最大程度保证显式契约和隐式契约一致。 Step1:setting.xml增加repository和pluginRepository <mirrors> <mirror> <id>huaweicloud</id> <mirrorOf>*,!HuaweiCloudSDK</mirrorOf> <url>https://repo.huaweicloud.com/repository/maven/</url> </mirror> </mirrors> <profiles> <profile> <id>MyProfile</id> <pluginRepositories> <pluginRepository> <id>HuaweiCloudSDK</id> <url>https://repo.huaweicloud.com/repository/maven/huaweicloudsdk/</url> <releases> <enabled>true</enabled> </releases> <snapshots> <enabled>true</enabled> </snapshots> </pluginRepository> </pluginRepositories> <repositories> <repository> <id>HuaweiCloudSDK</id> <url>https://repo.huaweicloud.com/repository/maven/huaweicloudsdk/</url> <releases> <enabled>true</enabled> </releases> <snapshots> <enabled>false</enabled> </snapshots> </repository> </repositories> </profile> </profiles> <activeProfiles> <activeProfile>MyProfile</activeProfile> </activeProfiles>Step2: 使用CSE-Codegen插件开发微服务 使用CSE-Codegen插件开发微服务步骤很简单,只需要准备一个Git仓库用于存放契约,在微服务工程里面引入插件依赖,运行插件就能同步远端契约到本地并根据契约生成框架代码。可以参考项目示例代码,步骤如下。 步骤 1 Git仓库归档契约创建远端Git仓库,用于契约管控,上传契约文件到Git仓库中。可以参考契约仓库示例。 步骤 2 pom插件配置1. consumer模块引入huawei-swagger-codegen-maven-plugin,插件版本号是2.2.8。schemaType指明是consumer,参数说明如表2-1所示。<build> <plugins> <plugin> <groupId>io.swagger</groupId> <artifactId>huawei-swagger-codegen-maven-plugin</artifactId> <version>2.2.8</version> <executions> <execution> <goals> <goal>generate</goal> </goals> </execution> </executions> <configuration> <skip>false</skip> <!--<skipOverwrite>false</skipOverwrite>--> <repositories> <repository> <!--<userName></userName>--> <!--<password></password>--> <repoUrl> https://github.com/huaweicse/cse-codegen-schemas.git</repoUrl> <branch>master</branch> <services> <service> <!--<appId>lala</appId>--> <serviceName>provider</serviceName> <packageName>com.huawei.paas.consumer</packageName> <schemaType>consumer</schemaType> <schemas> <schema> <schemaPath>dir/myservice.yaml</schemaPath> </schema> </schemas> </service> </services> </repository> </repositories> <packageName>com.huawei.paas.consumer</packageName> </configuration> </plugin> </plugins></build>表1-1 参数说明参数说明skip是否跳过执行该插件功能,默认是true,所以这里需要手动将skip设为false。skipOverWrite是否跳过文件覆盖,默认是false,即每次运行插件都可以更新框架代码。repositories定义多个契约仓库。repository定义单个契约仓库,即远端契约所在的git仓库。userName契约仓库的用户名(选填)。password契约仓库密码(选填)。repoUrl契约仓库地址,http、https、ssh格式都适用。branch契约仓库分支名。services定义多个服务。service定义单个服务,每个服务可以有多个契约文件。appId应用Id(选填,只在consumer这边指定,consumer跨应用调用provider的时候可以填对应的provider的appId)。serviceName服务名packageName(service层)生成的框架代码(delegate、impl、controller)的包路径,当契约中的model里面没有x-java-class,也作为model的包路径。schemaType指定服务是consumer还是provider,根据契约生成相应的框架代码。schemas定义多个契约文件。schema定义单个契约文件。schemaPath契约文件在契约仓库的相对路径。packageName生成代码的包路径,优先级小于service里面的packageName,当两者都没有设置,插件运行回报错。 2. provider模块引入huawei-swagger-codegen-maven-plugin,插件版本号是2.2.8。schemaType指明是provider。<build> <plugins> <plugin> <groupId>io.swagger</groupId> <artifactId>huawei-swagger-codegen-maven-plugin</artifactId> <version>2.2.8</version> <executions> <execution> <goals> <goal>generate</goal> </goals> </execution> </executions> <configuration> <skip>false</skip> <skipOverwrite>false</skipOverwrite> <repositories> <repository> <!--<userName></userName>--> <!--<password></password>--> <repoUrl> https://github.com/huaweicse/cse-codegen-schemas.git</repoUrl> <branch>master</branch> <services> <service> <serviceName>provider</serviceName> <packageName>com.huawei.paas.provider</packageName> <schemaType>provider</schemaType> <schemas> <schema> <schemaPath>dir/myservice.yaml</schemaPath> </schema> </schemas> </service> </services> </repository> </repositories> <packageName>com.huawei.paas.provider</packageName> </configuration> </plugin> </plugins></build> 步骤 3 运行插件,同步契约并生成框架代码分别双击consumer和provider的插件huawei-swagger-codegen:generate或者直接编译,插件运行。同步契约:1. 首先会检查工程的gitRepo目录下是否存在同名的Git仓库,如果存在则进行删除。2. 下载整个Git仓库到工程的gitRepo目录下。3. 插件根据scchemaPath查找gitRepo里的契约文件,复制到契约文件到工程的microservices目录下,如目录中存在同名契约则替换掉。生成框架代码:1. 生成consumer端框架代码:model类、delegate接口、impl实现类(具备RPC调用接口)。2. 生成provider端框架代码:model类、controller类、delegate接口、impl实现类。 步骤 4 根据框架代码,用户实现自己的业务逻辑如果impl目录不存在或者impl目录下不存在契约对应的impl实现类,则生成impl实现类,否则不生成(避免覆盖用户已有的业务逻辑)。用户可以在impl实现类中增加自己的业务逻辑。 步骤 5 测试consumer和provider测试consumer和provider是否可以通信,可以下载本地服务中心。配置好工程里的microservice.yaml,启动服务中心,使用postman测试consumer和provider是否正常通信。
-
这边有服务A与服务B,服务A调用服务B的业务方法method1时,在服务B的method1中会去调用服务A的method2,然后就请求超时了,请问大家有没有碰到类似问题2018-11-13 09:21:04.403 [vertx-blocked-thread-checker] WARN io.vertx.core.impl.BlockedThreadChecker - Thread Thread[transport-vert.x-eventloop-thread-4,5,main] has been blocked for 29514 ms, time limit is 2000 io.vertx.core.VertxException: Thread blocked at sun.misc.Unsafe.park(Native Method) at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175) at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:836) at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireSharedInterruptibly(AbstractQueuedSynchronizer.java:997) at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireSharedInterruptibly(AbstractQueuedSynchronizer.java:1304) at java.util.concurrent.CountDownLatch.await(CountDownLatch.java:231) at org.apache.servicecomb.core.provider.consumer.SyncResponseExecutor.waitResponse(SyncResponseExecutor.java:53) at org.apache.servicecomb.core.provider.consumer.InvokerUtils.innerSyncInvoke(InvokerUtils.java:74) at org.apache.servicecomb.provider.springmvc.reference.CseClientHttpRequest.doInvoke(CseClientHttpRequest.java:200) at org.apache.servicecomb.provider.springmvc.reference.CseClientHttpRequest.invoke(CseClientHttpRequest.java:190) at org.apache.servicecomb.provider.springmvc.reference.CseClientHttpRequest.execute(CseClientHttpRequest.java:146) at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:660) at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:621) at org.springframework.web.client.RestTemplate.getForObject(RestTemplate.java:295) at org.apache.servicecomb.provider.springmvc.reference.RestTemplateWrapper.getForObject(RestTemplateWrapper.java:79) at com.server.edu.workflow.service.impl.ProcessServiceImpl.getTeacherName(ProcessServiceImpl.java:185) at com.server.edu.workflow.service.impl.ProcessServiceImpl.createProval(ProcessServiceImpl.java:143) at com.server.edu.workflow.service.impl.ProcessServiceImpl$$FastClassBySpringCGLIB$$2aa5e4e2.invoke(<generated>) at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204) at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:736) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157) at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:99) at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:282) at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:671) at com.server.edu.workflow.service.impl.ProcessServiceImpl$$EnhancerBySpringCGLIB$$7c04b7d3.createProval(<generated>) at com.server.edu.workflow.controller.ApprovalController.creatApproveFlow(ApprovalController.java:67) at com.server.edu.workflow.controller.ApprovalController$$FastClassBySpringCGLIB$$f196b998.invoke(<generated>) at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204) at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:736) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157) at org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint.proceed(MethodInvocationProceedingJoinPoint.java:84) at com.server.edu.common.aspect.ExceptionAspect.around(ExceptionAspect.java:35) at sun.reflect.GeneratedMethodAccessor218.invoke(Unknown Source) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:627) at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:616) at org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:70) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) at org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint.proceed(MethodInvocationProceedingJoinPoint.java:84) at com.server.edu.common.aspect.ExceptionAspect.around(ExceptionAspect.java:35) at sun.reflect.GeneratedMethodAccessor218.invoke(Unknown Source) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:627) at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:616) at org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:70) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:671) at com.server.edu.workflow.controller.ApprovalController$$EnhancerBySpringCGLIB$$ed39517.creatApproveFlow(<generated>) at sun.reflect.GeneratedMethodAccessor293.invoke(Unknown Source) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.apache.servicecomb.swagger.engine.SwaggerProducerOperation.doInvoke(SwaggerProducerOperation.java:160) at org.apache.servicecomb.swagger.engine.SwaggerProducerOperation.syncInvoke(SwaggerProducerOperation.java:148) at org.apache.servicecomb.swagger.engine.SwaggerProducerOperation.invoke(SwaggerProducerOperation.java:115) at org.apache.servicecomb.core.handler.impl.ProducerOperationHandler.handle(ProducerOperationHandler.java:40) at org.apache.servicecomb.core.Invocation.next(Invocation.java:151) at org.apache.servicecomb.bizkeeper.BizkeeperCommand.lambda$construct$2(BizkeeperCommand.java:79) at org.apache.servicecomb.bizkeeper.BizkeeperCommand$$Lambda$146/1151788033.call(Unknown Source) at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:50) at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30) at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:50) at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30) at rx.Observable.unsafeSubscribe(Observable.java:8666) at rx.internal.operators.OnSubscribeDefer.call(OnSubscribeDefer.java:52) at rx.internal.operators.OnSubscribeDefer.call(OnSubscribeDefer.java:36) at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:50) at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30) at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:50) at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30) at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:50) at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30) at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:50) at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30) at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:50) at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30) at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:50) at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30) at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:50) at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30) at rx.Observable.unsafeSubscribe(Observable.java:8666) at rx.internal.operators.OnSubscribeDefer.call(OnSubscribeDefer.java:52) at rx.internal.operators.OnSubscribeDefer.call(OnSubscribeDefer.java:36) at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:50) at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30) at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:50) at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30) at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:50) at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30) at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:50) at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30) at rx.Observable.unsafeSubscribe(Observable.java:8666) at rx.internal.operators.OnSubscribeDefer.call(OnSubscribeDefer.java:52) at rx.internal.operators.OnSubscribeDefer.call(OnSubscribeDefer.java:36) at rx.Observable.subscribe(Observable.java:8759) at rx.Observable.subscribe(Observable.java:8726) at rx.Observable.subscribe(Observable.java:8619) at org.apache.servicecomb.bizkeeper.BizkeeperHandler.handle(BizkeeperHandler.java:77) at org.apache.servicecomb.core.Invocation.next(Invocation.java:151) at com.huawei.paas.cse.tcc.handler.ProviderTccTransactionHandler.handle(ProviderTccTransactionHandler.java:58) at org.apache.servicecomb.core.Invocation.next(Invocation.java:151) at org.apache.servicecomb.qps.ProviderQpsFlowControlHandler.handle(ProviderQpsFlowControlHandler.java:49) at org.apache.servicecomb.core.Invocation.next(Invocation.java:151) at org.apache.servicecomb.common.rest.AbstractRestInvocation.doInvoke(AbstractRestInvocation.java:184) at org.apache.servicecomb.common.rest.AbstractRestInvocation.invoke(AbstractRestInvocation.java:161) at org.apache.servicecomb.common.rest.AbstractRestInvocation.runOnExecutor(AbstractRestInvocation.java:145) at org.apache.servicecomb.common.rest.AbstractRestInvocation.lambda$scheduleInvocation$0(AbstractRestInvocation.java:133) at org.apache.servicecomb.common.rest.AbstractRestInvocation$$Lambda$136/1303053706.run(Unknown Source) at org.apache.servicecomb.core.executor.ReactiveExecutor.execute(ReactiveExecutor.java:29) at org.apache.servicecomb.common.rest.AbstractRestInvocation.scheduleInvocation(AbstractRestInvocation.java:121) at org.apache.servicecomb.common.rest.RestProducerInvocation.invoke(RestProducerInvocation.java:53) at org.apache.servicecomb.transport.rest.vertx.VertxRestDispatcher.onRequest(VertxRestDispatcher.java:194) at org.apache.servicecomb.transport.rest.vertx.VertxRestDispatcher$$Lambda$93/161110096.handle(Unknown Source) at io.vertx.ext.web.impl.RouteImpl.handleContext(RouteImpl.java:223) at io.vertx.ext.web.impl.RoutingContextImplBase.iterateNext(RoutingContextImplBase.java:101) at io.vertx.ext.web.impl.RoutingContextImpl.next(RoutingContextImpl.java:120) at org.apache.servicecomb.transport.rest.vertx.RestBodyHandler$BHandler.doEnd(RestBodyHandler.java:248) at org.apache.servicecomb.transport.rest.vertx.RestBodyHandler$BHandler.end(RestBodyHandler.java:226) at org.apache.servicecomb.transport.rest.vertx.RestBodyHandler.lambda$handle$0(RestBodyHandler.java:86) at org.apache.servicecomb.transport.rest.vertx.RestBodyHandler$$Lambda$131/1681854573.handle(Unknown Source) at io.vertx.core.http.impl.HttpServerRequestImpl.handleEnd(HttpServerRequestImpl.java:418) at io.vertx.core.http.impl.ServerConnection.handleLastHttpContent(ServerConnection.java:475) at io.vertx.core.http.impl.ServerConnection.handleContent(ServerConnection.java:469) at io.vertx.core.http.impl.ServerConnection.processMessage(ServerConnection.java:449) at io.vertx.core.http.impl.ServerConnection.handleMessage(ServerConnection.java:156) at io.vertx.core.http.impl.HttpServerImpl$ServerHandlerWithWebSockets.handleMessage(HttpServerImpl.java:705) at io.vertx.core.http.impl.HttpServerImpl$ServerHandlerWithWebSockets.handleMessage(HttpServerImpl.java:614) at io.vertx.core.net.impl.VertxHandler.lambda$channelRead$1(VertxHandler.java:150) at io.vertx.core.net.impl.VertxHandler$$Lambda$54/1451074741.run(Unknown Source) at io.vertx.core.impl.ContextImpl.lambda$wrapTask$2(ContextImpl.java:342) at io.vertx.core.impl.ContextImpl$$Lambda$29/333022446.run(Unknown Source) at io.vertx.core.impl.ContextImpl.executeFromIO(ContextImpl.java:200) at io.vertx.core.net.impl.VertxHandler.channelRead(VertxHandler.java:148) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348) at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340) at io.netty.handler.timeout.IdleStateHandler.channelRead(IdleStateHandler.java:286) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348) at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340) at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:310) at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:284) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348) at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340) at io.vertx.core.http.impl.Http1xOrH2CHandler.end(Http1xOrH2CHandler.java:49) at io.vertx.core.http.impl.Http1xOrH2CHandler.channelRead(Http1xOrH2CHandler.java:27) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348) at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340) at io.netty.handler.timeout.IdleStateHandler.channelRead(IdleStateHandler.java:286) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348) at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340) at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1434) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348) at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:965) at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:163) at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:645) at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:580) at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:497) at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:459) at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:884) at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) at java.lang.Thread.run(Thread.java:748)
-
场景描述:一个客户登录后,始终把请求发送到某一个实例。当第一次访问这个实例的时候,需要记录会话ID对应的实例是A,这个会话下次发起同样的请求的时候,仍然访问A。CSE提供的会话粘滞:CSE并没有提供上述场景的会话粘滞策略实现。因为一个系统的会话管理是多样的,CSE框架并不知道如何标记一个请求属于哪个会话。CSE提供了一个简化的会话粘滞实现,保证对于一个接口的访问,尽可能发送到上一次访问的实例,负载均衡策略名字为SessionStickiness,修改负载均衡策略参考https://docs.servicecomb.io/java-chassis/zh_CN/references-handlers/loadbalance.html。如果用户需要实现“场景描述”里面的会话粘滞策略,需要考虑自定义负载均衡策略,实现相关接口即可。详细可以参考SessionStickiness、RoundRobin等CSE自带的负载均衡的策略实现。
-
忍不住想吐槽一下,不吐不快,昨天处理了一天SpringBoot异步问题问题描述:CSE+Springboot,请求我用DeferredResult异步封装,结果引入CSE后,异步响应还没回复就给我序列化了一个"setOrExpired": false针对这个问题,先是一顿猛搜,结果大失所望,网上用CSE的都门可罗雀,基本没任何可用信息,实在没辙,耐着性子看源码问题出在下面:CSE用反射的方式拿到controller对象,直接返回controller对象的结果,于是就出现了如果你想异步,对不起不可以,我会先反序列化你的结果,就算为null,我也给你返回去同时还有一个问题产生了,因为他要序列化,序列化的时候又没法指定类型,所以序列化方法上定义接口、Object同样也是不行的恶心的同时出现了第三个问题,它自己把这个方法try cache了,所以你想用@ExceptionHandler(Exception.class)这个全局异常捕获,对不起不可以,我在前面给你catch了,压根不会到你哪一步总结:这就是我CSE的一天,各种问题,恶心之极,老实说要不是我的北向网元用的恶心的CSE(当然它们不考虑并发、异步、代码各种if else),我打死都不会用CSE奉劝各位,赶紧远离这个垃圾微服务框架,各种恶心之极,我想这也许就是它不流行,小众的原因。了解一次的人,压根不想在它上面浪费时间
-
错误日志:ERROR 13160 --- [ntloop-thread-0] o.a.s.config.client.ConfigCenterClient : Config update from https://cse.cn-north-1.myhuaweicloud.com failed. Error message is [Search domain query failed. Original hostname: 'cse.cn-north-1.myhuaweicloud.com' failed to resolve 'cse.cn-north-1.myhuaweicloud.com' after 2 queries ].
-
很多人问题微服务名称或者版本号有什么规范,可以直接查看 服务中心的API请求参数规范https://support.huaweicloud.com/api-cse/cse_02_0102.html说明:应确保app内部唯一。微服务名支持数字、大小写字母和"-"、"_"、"."三个特殊字符,但是不能以特殊字符作为首尾字符serviceNameString是微服务名称,同一个App要保证唯一。字符长度为1~128。正则表达式为^[a-zA-Z0-9]*$|^[a-zA-Z0-9][a-zA-Z0-9_\-.]*[a-zA-Z0-9]$versionString是微服务版本号。字符长度为1~64。正则表达式为^[0-9]*$|^[0-9]+(\.[0-9]+)*$
-
前言本文将结合一个具体例子中的细节详细描述Istio调用链的原理和使用方式。并基于Istio中埋点的原理解释来说明:为了输出一个质量良好的调用链,业务程序需根据自身特点做适当的修改,即并非官方一直在说的完全无侵入的做各种治理。另外还会描述Istio当前版本中收集调用链数据可以通过Envoy和Mixer两种不同的方式。Istio一直强调其无侵入的服务治理,服务运行可观察性。即用户完全无需修改代码,就可以通过和业务容器一起部署的proxy来执行服务治理和与性能数据的收集。原文是这样描述的:Istio makes it easy to create a network of deployed services with load balancing, service-to-service authentication, monitoring, and more, without any changes in service code. You add Istio support to services by deploying a special sidecar proxy throughout your environment that intercepts all network communication between microservices, then configure and manage Istio using its control plane functionality。调用链的埋点是一个比起来记录日志,报个metric或者告警要复杂的多,根本原因是要能将在多个点上收集的关于一次调用的多个中间请求过程关联起来形成一个链。Dapper, a Large-Scale Distributed Systems Tracing Infrastructure 描述了其中的原理和一般性的机制,还是挺复杂的。也有很多实现,用的比较多的如zipkin,和已经在CNCF基金会的用的越来越多的Jaeger,满足Opentracing语义标准的就有这么多。在Istio中大段的埋点逻辑在Sidecar中已经提供,业务代码不用调用以上这些埋点方式来创建trace,维护span等这些复杂逻辑,但是为了能真正连接成一个完整的链路,业务代码还是需要做适当修改。我们来分析下细节为什么号称不用修改代码就能搞定治理、监控等高级功能的Sidecar为什么在调用链埋点的时候需要改应用代码。调用详细服务调用关系简单期间,我们以Istio最经典的Bookinfo为例来说明。Bookinfo的4个为服务的调用关系是这样:调用链输出从前端入口gateway那个envoy上进行一次调用,到四个不同语言开发的服务间完成调用,一次调用输出的调用链是这样:简单看下bookinfo 中的代码,能看到并没有任何创建维护span这种埋点的逻辑,想也是,对于python、java、ruby、nodejs四种不同的语言采用不同的埋点的库在来实现类似的埋点逻辑也是非常头痛的一件事情。那我们看到这个调用链信息是怎么输出的?答案当然是应用边上的sidecar Envoy,Envoy对于调用链相关设计参照这里。sidecar拦截应用程序所有的进和出的网络流量,跟踪到所有的网络请求,像Service mesh的设计理念中其他的路由策略、负载均衡等治理一样,只要拦截到流量Sidecar也可以实现埋点的逻辑。埋点逻辑对于经过sidecar流入应用程序的流量,如例子中流入roductpage, details、reviews和ratings的流量,如果经过Sidecar时header中没有任何跟踪相关的信息,则会在创建一个span,Traceid就是这个spanId,然后在将请求传递给通pod的业务服务;如果请求中包含trace相关的信息,则sidecar从走回归提取trace的上下文信息并发给应用程序。对于经过sidecar流出的流量,如例子中gateway调用productpage,或者productpage调用链details和reviews的请求。如果经过sidecar时header中没有任何跟踪相关的信息,则会创建根span,并将该跟span相关上下文信息放在请求头中传递给下一个调用的服务,当然调用前会被目标服务的sidecar拦截掉执行上面流入的逻辑;当存在trace信息时,sidecar从header中提取span相关信息,并基于这个span创建子span,并将新的span信息加在请求头中传递。以上是bookinfo一个实际的调用中在proxy上生成的span主要信息。可以看到,对于每个app访问都经过Sidecar代理,inbound的流量和outbound的流量都通过Sidecar。图上为了清楚表达每个将对Sidecar的每个处理都分开表示,如productpage,接收外部请求是一个处理,给details发出请求是一个处理,给reviews发出请求是另外一个处理,因此围绕Productpage这个app有三个黑色的处理块,其实是一个Sidecar。为了不使的图上太凌乱,最终的Response都没有表示。其实图上每个请求的箭头都有一个反方向的response,在服务发起方的Sidecar会收到response时,会记录一个CR(client received)表示收到响应的时间并计算整个span的持续时间。解析下具体数据,结合实际调用中生成的数据来看下前面proxy埋点的逻辑会更清楚些。1.从gateway开始,gateway作为一个独立部署在一个pod中的envoy进程,当有请求过来时,它会将请求转给入口的微服务productpage。Gateway这个Envoy在发出请求时里面没有trace信息,会生成一个根span:spanid和traceid都是f79a31352fe7cae9,parentid为空,并记录CS时间,即Client Send;2.请求从入口gateway这个envoy进入productpage前先讲过productpage pod内的envoy,envoy处理请求头中带着trace信息,则记录SR,Server received,并将请求发送给Productpage业务容器处理,productpage在处理请求的业务方法中需要接收这些header中的trace信息,然后再调用Details和Reviews的微服务。Python写的 productpage在服务端处理请求时,先从request中提取接收到的header。然后再调用details获取details服务时,将header转发出去。可以看到就是提取几个trace相关的header kv其实就是重新构造一个请求发出去,可以看到请求中包含收到的header。3.从ProductPage出的请求去请求Reviews服务前,又一次通过同Pod的envoy,envoy埋点逻辑检查header中包含了trace相关信息,在将请求发出前会做客户端的调用链埋点,即以当前span为parent span,生成一个子span:即traceid:保持一致9a31352fe7cae9, spanid重新生成cb4c86fb667f3114,parentid就是上个span: f79a31352fe7cae9。4.请求在到达Review业务容器前,先经过Review的Envoy,从Header中解析出trace信息存在,则发送Trace信息给Reviews。Reviews处理请求的服务端代码中需要解析出这些包含trace的Header信息。reviews服务中java的rest代码如下:即在服务端接收请求的时候也同样会提取header。调用Ratings服务时再传递下去。其他的productpage调用Details,Reviews调用Ratings逻辑类似。不再复述。以一实际调用的例子了解以上调用过程的细节,可以看到Envoy在处理inbound和outbound时的埋点逻辑,更重要的是看到了在这个过程中应用程序需要配合做的事情。即需要接收trace相关的header并在请求时发送出去,这样在出流量的proxy向下一跳服务发起请求前才能判断并生成子span并和原span进行关联,进而形成一个完整的调用链。否则,如果在应用容器未处理Header中的trace,则Sidecar在处理outbound的请求时会创建根span,最终会形成若干个割裂的span,并不能被关联到一个trace上。即虽然Istio一直是讲服务治理和服务的可观察性对业务代码0侵入。但是要获得一个质量良好的调用链,应用程序还是要配合做些事情。在官方的distributed-tracing 中这部分有描述:“尽管proxy可以自动生成span,但是应用程序需要在类似HTTP Header的地方传递这些span的信息,这样这些span才能被正确的链接成一个trace。因此要求应用程序必须要收集和传递这些trace相关的header并传递出去”• x-request-id• x-b3-traceid• x-b3-spanid• x-b3-parentspanid• x-b3-sampled• x-b3-flags• x-ot-span-context调用链阶段span解析:前端gateway访问productpage的proxy的这个span大致是这样:gateway上报了个cs,cr, productpage的那个proxy上报了个ss,sr。分别表示gateway作为client什么时候发出请求,什么时候最终受到请求,productpage的proxy什么时候收到了客户端的请求,什么时候发出了response。Reviews的span如下: Details的span如下:可以看到productpage这个微服务和detail和reviews这两个服务的调用。一个细节就是traceid就是第一个productpage span的id,所以第一个span也称为根span,而后面两个review和details的span的parentid是前一个productpage的span的id。Ratings 服务的span信息如下:可以看到traceid保持一样,parentid就是reviews的spanid。当然在Jaeger里上报会是这个样子:根据一个实际的例子理解原理后会发现,应用程序要修改代码根本原因就是调用发起方,在Isito里其实就是Sidecar在处理outbound的时生成span的逻辑,而这个埋点的代码和业务代码不在一个进程里,没法使用进程内的一些类似ThreadLocal的方式(threadlocal在golang中也已经不支持了,推荐显式的通过Context传递),只能显式的在进程间传递这些信息。这也能理解为什么Istio的官方文档中告诉我们为了能把每个阶段的调用,即span,串成一个串,即完整的调用链,你需要修你的代码来传递点东西。当然实例中只是对代码侵入最少的方式,就是只在协议头上机械的forward这几个trace相关的header,如果需要更多的控制,如在在span上加特定的tag,或者在应用代码中代码中根据需要构造一个span,可以使用opentracing的StartSpanFromContext 或者SetTag等方法。调用链数据上报Envoy上报一个完整的埋点过程,除了inject、extract这种处理span信息,创建span外,还要将span report到一个调用链的服务端,进行存储并支持检索。在Isito中这些都是在Envoy这个sidecar中处理,业务程序不用关心。在proxy自动注入到业务pod时,会自动刷这个后端地址。如: 即envoy会连接zipkin的服务端上报调用链数据,这些业务容器完全不用关心。当然这个调用链收集的后端地址配置成jaeger也是ok的,因为Jaeger在接收数据是兼容zipkin格式的。Mixers上报除了直接从Envoy上报调用链到zipkin后端外,和其他的Metric等遥测数据一样通过Mixer这个统一面板来收集也是可行的。即如tracespan中描述,创建一个tracespan的模板,来描述从mixer的一次访问中提取哪些数据,可以看到trace相关的几个ID从请求的header中提取,而访问的很多元数据有些从访问中提取,有些根据需要从pod中提取(背后去访问了kubeapiserver的pod资源)最后在这个文章发出前,一直在和社区沟通,督促在更明晰的位置告诉大家用Istio的调用链需要修改些代码,而不是只在一个旮旯的位置一小段描述。得到回应是1.1中社区首页第一页what-is-istio/已经修改了这部分说明,不再是1.0中说without any changes in service code,而是改为with few or no code changes in service code。提示大家在使用Isito进行调用链埋点时,应用程序需要进行适当的修改。当然了解了其中原理,做起来也不会太麻烦。参照https://thenewstack.io/distributed-tracing-istio-and-your-applications/ https://github.com/istio/old_mixer_repo/issues/797 http://www.idouba.net/opentracing-serverside-tracing/
-
Step1、进入云容器引擎CCE,选择“配置中心”->"配置项(ConfigMap)",创建配置项Step2、创建配置项,配置数据时,键填写“microservice.yaml”,值填写用于线上环境的microservice.yaml文件内容Step3、进入微服务云应用平台ServceStage,选择“应用上线”,选择需要修改的应用,点击“更新升级”页面 点击“高级设置”->数据存储,点击“挂载本地磁盘” 说明:“挂载路径”根据实际情况填写,以ServiceStage云上工程创建的应用为例,app.jar放在/home/apps/下,故填写“/home/apps/ms”,在启动Java程序时需要指定servicecomb.configurationSource.additionalUrls参数,以开启外置microservice.yaml配置。Step4、修改Java应用启动命令启动命令java -Dservicecomb.configurationSource.additionalUrls=file:///home/apps/ms/microservice.yaml -jar ./app.jarTips1:file://后拼接文件绝对路径,如/home/apps/ms/microservice.yaml,必须与Step3中容器挂载路径一致。Tips2:挂载的microservice.yaml优先级高,将会覆盖代码中的microservice.yaml附上start.sh,仅供参考#!/bin/bash CMDVAR="-Djava.security.egd=file:/dev/./urandom" SPECIFIED_YAML="`pwd`/config/microservice.yaml" if [ -e ${SPECIFIED_YAML} ]; then CMDVAR="${CMDVAR} -Dservicecomb.configurationSource.additionalUrls=file://${SPECIFIED_YAML}" fi java $CMDVAR -jar ./*.jar附上Dockerfile,仅供参考FROM java:8u111-jre-alpine WORKDIR /home/apps/ COPY target/*.jar app.jar RUN sh -c 'touch app.jar' ADD start.sh . ENTRYPOINT ["sh", "/home/apps/start.sh"]Step5、验证是否生效方式一:以本应用为例,检查应用程序日志/home/logs/cse.log 中microservice.yaml内容日志,已经加载了/home/apps/ms/microservice.yaml文件方式二:以本应用为例,挂载的yaml文件中APPLICATION_ID指定为finance,service_description.name也指定为finance,区别于代码中的名称。修改后:修改前:
上滑加载中
推荐直播
-
华为云码道Agent集成与鸿蒙实战2026/08/11 周二 19:00-21:00
王一男-华为云码道产品规划专家;李炎-华为云码道产品专家;彭江敏-华为云鸿蒙端云一体化开发专家
本次直播带你解读华为云码道7月份产品新特性、新功能。更有专家演示码道Agent Space × 钉钉机器集成实战,从0到1打通消息通道;码道鸿蒙端云一体化实战,快速搭建员工签到系统。
回顾中 -
华为云开发者AI素养直播课·第五期2026/09/04 周五 16:00-18:00
林华鼎-华为云AI开发者运营负责人;蒋春阳-华为云AI开发者案例开发专家
本期直播内容: AI工具体验营 · 第5-8课连讲。Agent-Team 多智能体协作完成毕业设计实践
回顾中 -
华为云开发者AI素养ClassRoom·第六期2026/09/08 周二 19:00-20:00
樊渊-2026华为软件挑战赛冠军
高手来了:看软挑高手解析二维排样问题—从工业难题到算法突破
回顾中
热门标签