关于spring-cloud:changgou

谬误整顿解决:com.sun.jersey.api.client.ClientHandlerException: java.net.ConnectException: Connection refused:解决方案此利用为注册核心,false:不向注册核心注册本人。eureka.client.register-with-eureka=false注册核心职责是保护服务实例,false:不检索服务。eureka.client.fetch-registry=false

January 5, 2021 · 1 min · jiezi

关于spring-cloud:服务链路追踪Spring-Cloud-Sleuth-zpkin-初试

sleuth引入sleuth依赖<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-sleuth</artifactId></dependency>#是否开启sleuth spring.sleuth.enabled=true@ 展现成果 zpkin增加zpkin依赖<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-zipkin</artifactId></dependency>配置# 开启Sleuthspring.sleuth.enabled=true#指定zipkin server的地址,上报追踪日志spring.zipkin.base-url=http://localhost:9411# 采样率 越大采集率越高spring.sleuth.sampler.probability=1启动zpkin-serverjava -jar ipkin-server-2.12.9-exec.jar如果要将数据存储到elasticsearch中java -DSTORAGE_TYPE=elasticsearch -DES_HOSTS=http://localhost:9200 -jar zipkin-server-2.12.9-exec.jarSTORAGE_TYPE用于指定Zipkin的存储类型,这里为elasticsearchES_HOSTS 则用于指定Elasticsearch服务器地址列表,有多个节点时应用逗号分隔 如果存在elasticsearch,想查看依赖

December 29, 2020 · 1 min · jiezi

关于spring-cloud:Spring-Cloud-202000正式发布再见了Netflix

分享、成长,回绝浅藏辄止。关注公众号【BAT的乌托邦】,回复关键字专栏有Spring技术栈、中间件等小而美的原创专栏供以收费学习。本文已被 https://www.yourbatman.cn 收录。✍前言你好,我是YourBatman。 北京工夫2020-12-22深夜,Spring Cloud 2020.0.0版本正式公布。2020.0.0是第一个应用新版本计划的Spring Cloud发行版本。 对于版本号这里啰嗦几句:在这之前,Spring Cloud的Release Train名称采纳的是伦敦地铁站命名形式,如:Hoxton、Greenwich等。 阐明:2020.0.0版本又名Ilford(地铁站名),因为此我的项目3月后才依照新规更名,预计是为了团队内沟通不便吧,你也能够了解为它仅是一个外部代号而已,不便沟通虽依照字母表顺序排列,但仍存在两个致命问题: 对非英语母语国家(比方天朝)十分不敌对,无奈疾速理清版本号关系A-Z,假使版本号到Z了呢?如何持续倒退?你品,你细品 Spring团队意识到了这确实是个问题,因而在往年3月份作出了扭转。详情参考我后面写的一篇文章(强烈建议每个进来的你都理解下这次规定变更):Spring扭转版本号命名规定:此举对非英语国家很敌对 阐明:版本号规定变更实用于所有Spring技术栈,蕴含Spring Framework、Spring Boot、Spring Cloud、Spring Data...文归正传。Spring Cloud早在年初就启动了该版本的研发工作,并在往年4月份就曾经公布了其2020.0.0-M1版本(第一个里程碑版本),直到离2020年完结不到10天了才“憋出”大招,正式RELEASE。 Spring Cloud作为构建在Spring Boot之上的云计算框架,我感觉本次难产的起因次要有二: Spring Boot 2.4.0版本2020-11-12才正式RELEASE(Spirng Framework 5.3.0版本2020-10-27才RELEASE) Spring Framework 5.3.0正式公布,在云原生路上持续发力Spring Boot 2.4.0正式公布,全新的配置文件加载机制(不向下兼容)改变的确太大,研发、测试、文档编写工作量都是微小的从Spring Framework、Spring Boot、Spring Cloud三者的发版线路图再一次验证了我的那句话:你对Spring Cloud多理解源自于你对Spring Boot有多理解,你对Spring Boot多理解源自于你对Spring Framework有多理解。这就是为何我文章花大量笔墨在Spring Framework上而非Spring Boot上的根本原因,底层通透了,下层运用自如。 版本约定Spring Framework:5.3.2Spring Boot:2.4.1Spring Cloud:2020.0.0 以上版本为SC“携带”的版本 ✍注释有个乏味的景象,截止稿前(2020-12-23 22:00:00)官网还并未同步标注好以后最新版本为2020.0.0版(如图): 其实早在24h之前官网博客就做出了发版宣告: 并且Maven地方仓库也已存在最新Jar包(证实你失常引包、应用是没问题的了): 其实,文档层面不止官网这一处没有sync最新版本,我就不一一例举,毕竟不太重要。针对此景象我yy一下,是不是Spring Cloud团队缺人人手不够用呢?请问社招吗?O(∩_∩)O哈哈~ Spring Cloud版本治理版本治理对于软件开发来说太重要,在Spring Boot呈现之前依赖关系、版本治理让人着实头大(即便有Spring BOM存在),特地是当呈现版本不适配时很容易就偷走你一下午甚至一整天的工夫。 Spring Cloud作为下层利用框架,底层版本匹配了能力失常work,其中最次要就是和Spring Boot的版本号要对齐。 与Spring Boot版本对应关系Spring Boot的呈现和风行大大缓解了上述些状况,但应用起Spring Cloud时它和Spring Boot的版本对应关系仍旧是须要特地关注的。为此我帮你总结出了这个表格: ...

December 24, 2020 · 2 min · jiezi

关于spring-cloud:zuul如果两个filter的order一样是如何排序的

最近有个网友问了一个问题,zuul中如果两个filter的order一样,是如何排序的?引起了我的趣味,特地去浏览了它的源码。 如果你有应用过springcloud应该据说过zuul,它的定位是散布式微服务中的API网关服务,当然前面可能要被gateway代替了。zuul是一个L7应用程序网关,提供了动静路由,监督,弹性,安全性等性能。zuul的大部分性能是通过filter实现的。 zuul定义了四种不同生命周期的filter 为了不便操作,zuul内置了一些filter,这些filter次要通过@EnableZuulServer和@EnableZuulProxy注解开启相干性能。@EnableZuulServer注解开启的filter性能如下: @EnableZuulProxy注解除了开启下面这些filter性能之外,还开启了如下的性能: 只需继承ZuulFilter类,实现它的filterType、filterOrder、shouldFilter 和 run办法即可,具体实现可参考如下代码: public class LogFilter extends ZuulFilter { @Override public String filterType() { return FilterConstants.PRE_TYPE; } @Override public int filterOrder() { return 1; } @Override public boolean shouldFilter() { return RequestContext.getCurrentContext().sendZuulResponse(); } @Override public Object run() throws ZuulException { RequestContext currentContext = RequestContext.getCurrentContext(); HttpServletRequest request = currentContext.getRequest(); log.info("zuul pre filter-->" + request.getRequestURL() + "-->" + request.getMethod()); return null; }}下面的四个办法有哪些作用呢? ...

November 25, 2020 · 1 min · jiezi

关于spring-cloud:wanxin-finance

开户创立mapper模板

November 8, 2020 · 1 min · jiezi

关于spring-cloud:wanxin金融

day 1day 2 用户注册增加的apollo额定配置另外数据库的名称,路由的开始门路/account

November 7, 2020 · 1 min · jiezi

关于spring-cloud:红包雨项目

概述及根底储备zookeeperzookeeper利用概述zookeeper之zkui

November 6, 2020 · 1 min · jiezi

关于spring-cloud:第五阶段1023

spring cloud介绍spring cloud 是一系列框架的汇合。它利用 spring boot 的开发便利性奇妙地简化了分布式系统基础设施的开发,如服务发现注册、配置核心、音讯总线、负载平衡、断路器、数据监控等,都能够用 spring boot 的开发格调做到一键启动和部署。spring cloud 并没有反复制作轮子,它只是将目前各家公司开发的比拟成熟、经得起理论考验的服务框架组合起来,通过 spring boot 格调进行再封装屏蔽掉了简单的配置和实现原理,最终给开发者留出了一套简略易懂、易部署和易保护的分布式系统开发工具包。 spring cloud 对于中小型互联网公司来说是一种福音,因为这类公司往往没有实力或者没有足够的资金投入去开发本人的分布式系统基础设施,应用 spring cloud 一站式解决方案能在从容应对业务倒退的同时大大减少开发成本。同时,随着近几年微服务架构和 docker 容器概念的火爆,也会让 spring cloud 在将来越来越“云”化的软件开发格调中立有一席之地,尤其是在目前形形色色的分布式解决方案中提供了标准化的、一站式的技术计划,意义可能会堪比当年 servlet 标准的诞生,无效推动服务端软件系统技术水平的提高。 spring cloud技术组成 eureka微服务治理,服务注册和发现ribbon负载平衡、申请重试hystrix断路器,服务降级、熔断feignribbon + hystrix 集成,并提供申明式客户端hystrix dashboard 和 turbinehystrix 数据监控zuulAPI 网关,提供微服务的对立入口,并提供对立的权限验证config配置核心bus音讯总线, 配置刷新sleuth+zipkin链路跟踪Spring Cloud 比照 Dubbo Dubbo Dubbo只是一个近程调用(RPC)框架默认基于长连贯,反对多种序列化格局Spring Cloud 框架集提供了一整套微服务解决方案(全家桶)基于http调用, Rest APIapplication.yml文件向注册核心注册的服务名sping: application: name: item-serviceserver: port: 8001 eureka: server: enable-self-presevation: false #1.保护模式instance: hostname: eureka1 ##2.集群服务器间用主机名辨别client: #3.单台服务器。不向本人注册 不从本人拉取 register-with-eureka: false fetch-registry: false ...

October 23, 2020 · 1 min · jiezi

关于spring-cloud:Spring-Cloud-AlibabaNacos-安装及使用

Spring Cloud Alibaba:Nacos 装置及应用Nacos 是什么? Nacos 致力于帮忙开发者发现、配置和治理微服务。Nacos 提供了一组简略易用的个性集,帮忙开发者疾速实现动静服务发现、服务配置、服务元数据及流量治理。Nacos 帮忙开发者更麻利和容易地构建、交付和治理微服务平台。 Nacos 是构建以“服务”为核心的古代利用架构 (例如微服务范式、云原生范式) 的服务基础设施。Nacos 次要蕴含两个局部,一个是配置核心,一个是服务注册与发现。本系列的文章将顺次对其进行分享介绍。本篇将着重介绍 Nacos 的单机与集群装置及一些根本的应用。 版本阐明Windows: win 10 64位零碎Linux: Centos 7 64位零碎Nacos: 1.3.2 (以后最新稳固版本)Windows 单机装置下载软件包返回 GitHub Nacos Release 下载 nacos-server-1.3.2.zip 软件包到本地。本地解压,目录构造如下图所示。 bin : 启动/敞开脚本conf : Nacos 的配置文件data : 未做长久化的时候数据会存储在此,比方配置数据(第一次运行后才会生成)logs : Nacos 日志(第一次运行后才会生成)target : nacos-server.jar 运行文件参数配置关上文件 conf/application.properties 文件,对 nacos 进行配置。 ### 配置网页端拜访端口server.port=8848### 配置数据长久化的数据库,这里应用 mysql### 这里的配置默认是正文掉的,须要手动去除正文### 如果须要启用数据库的话,须要导入 conf/nacos-mysql.sql 脚本### 如果不启用数据库,则数据将长久化到本地 data/ 目录下### If use MySQL as datasource:spring.datasource.platform=mysql### Count of DB:db.num=1### 数据库能够有多个,db.url.0=xxx db.url.1=xxx db.url.2=xxx### 此处仅应用一个### Connect URL of DB:db.url.0=jdbc:mysql://127.0.0.1:3306/nacos?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTCdb.user=usernamedb.password=password### 关上认证受权零碎,默认为 false ### 此项配置并不会影响网页端的登录,设置为 true 或是 false 网页端拜访时均须要登录### 此项配置开启的话,在代码中须要配置 nacos 的用户名及明码### 即 spring.cloud.nacos.username 和 spring.cloud.nacos.password 两个参数,对应的是网页端登录的用户名明码### If turn on auth system:nacos.core.auth.enabled=true### 其余配置依据本人的需要配置即可留神: > ...

October 9, 2020 · 3 min · jiezi

关于spring-cloud:spring-cloud

最好用的idea插件:JBLSpringBootAppGen

September 8, 2020 · 1 min · jiezi

关于spring-cloud:spring-cloud-gatewayribbon-组合指定版本权重分流简易灰度发布实现

0.参考文章1 ribbon中应用HystrixRequestVariableDefault的一个注意事项 文章2 SpringCloud灰度公布实际(附源码) zuul实现的能够参考。 1.思路革新gataway的### Weight Route Predicate Factory,在指定权重的同时指定每个对应权重的服务的版本号。次要须要改写的中央是要在分流之后,将版本号传给ribbon,ribbon在做负载平衡抉择的时候,依据传入的版本号,与服务的eureka.matedata中的version匹配,从而达到灰度公布的目标。gateway的配置相似如下,VersionWeight为重写的断言工厂。 - id: temp_old uri: lb://TEMPLATE predicates: - Path=/temp/** - VersionWeight=group1, 10, v1 filters: - StripPrefix=1 - id: temp_new uri: lb://TEMPLATE predicates: - Path=/temp/** - VersionWeight=group1, 1, v2 filters: - StripPrefix=12.考察革新点debug后晓得了整个申请程序 客户端-> gateway filter -> LoadBalancerClientFilter -> (如果应用ribbon) RibbonLoadBalancerClient-> (某个Rule) XXXAvoidanceRule -> (对应的 Predicate) XXXAvoidancePredicate所以抉择的服务的要害就是Ribbon的rule和Predicate 步骤 写一个grayContext, 基于threadlocal解决version的上下文传递。自定义负载策略:负载策略的要害是Rule 和 Predicate, Rule 继承 PredicateBasedRule, Predicate 继承 AbstractServerPredicate, 实现Predicate 中的 apply办法 此办法是负载策略外围(逻辑:从grayContext获取version,与服务的eureka.matedata中的version匹配。匹配上则为指标服务)改写权重断言策略:改写WeightRoutePredicateFactory,在Factory 分流实现后,把路由中配置的 version 放入到grayContext 中。并且要将vesion 放入到 request的head中,用来向下传递。下面是对gateway的革新,通过革新后,gateway散发的时候就依据version能指定申请的服务器,接下来是微服务间通过fegin或者restTemplate调用时的革新,目标是将version持续往下传递 ...

August 19, 2020 · 1 min · jiezi

关于spring-cloud:Spring-Cloud微服务全栈技术与案例解析

Spring Cloud微服务全栈技术与案例解析 下载地址: https://pan.baidu.com/s/1NJmgFlQmNmFG2RkrYO7Q7Q 扫码上面二维码关注公众号回复100017 获取分享码 本书目录构造如下: Spring Cloud与微服务概述… ··2 1.1 传统的单体利用………………… ··2 1.1.1 改良 利用的架构………… 1.1.2 务聚拢 ……………… ·3 1.2 什么是微服务…………………… ·4 1.2.l 用微 务架构的劣势和 劣势 ……..... ...... .. ... ... 4 1.2.2 重构前的筹备工作…………… 1.3 什么是 Spring Cloud ……………… 5 1.3.l Spring oud 块介绍……… ·5 1.3.2 Spring Cloud 版本 ……… ·6 1.4 本章小结…. ...... ..……… ·······7 章实战前的筹备工作………… 2.1 开发环境的筹备………………… 8 2.2 Spring Boot 人门 …… …………… ··9 ...

August 12, 2020 · 7 min · jiezi

关于spring-cloud:Spring-Cloud系列之微服务介绍

置信我,请认真读完,点开每一个链接,或者你能力真正理解什么是微服务?什么是分布式?什么是云计算?相对没有多余! 1 微服务微服务架构是“新常态”。构建小型的、自蕴含的、随时能够运行的应用程序能够为代码带来极大的灵活性和灵活性。spring boot的许多专门构建的个性使得在大规模生产中构建和运行微服务变得非常容易。别忘了,没有spring cloud,就没有一个微服务架构是残缺的,它简化了治理,加强了您的容错能力。 1.1 什么是微服务?微服务是一种古代的软件办法,利用程序代码能够独立于其余程序以可治理的小块模式交付。 1.2 为什么要构建微服务?它们的小规模和绝对隔离能够带来许多额定的益处,例如更容易保护、进步生产力、更大的容错能力、更好的业务协调等等。 2 Spring Cloud开发分布式系统可能具备挑战性。复杂性从应用层转移到网络层,要求服务之间进行更大的交互。使您的代码成为“本机云”意味着要解决12个因素的问题,例如内部配置、无状态状态、日志记录和连贯到反对服务。spring cloud我的项目套件蕴含许多使应用程序在云中运行所需的服务。 2.1 Service discovery——服务发现在云中,应用程序不可能总是晓得其余服务的确切地位。服务注册核心(如Netflix Eureka)或sidecar解决方案(如HashiCorp consur)都能够提供帮忙。spring cloud为风行的注册核心提供了Discovery Client实现,比方Eureka、consur、Zookeeper,甚至Kubernetes的内置零碎。还有一个spring cloud负载平衡器(Spring Cloud LoadBalancer),能够帮忙您在服务实例之间小心地调配负载。 2.2 API gateway——API 网关因为有如此多的客户机和服务器,在您的云架构中蕴含一个API网关通常是很有帮忙的。网关能够负责爱护和路由音讯、暗藏服务、限度负载以及许多其余有用的事件。spring cloud gateway为您提供了对API层的准确管制,集成了spring cloud服务发现和客户端负载平衡解决方案,以简化配置和保护。 2.3 Cloud configuration——云配置在云中,配置不能简略地嵌入到应用程序中。配置必须足够灵便,以应答多个应用程序、环境和服务实例,并在不停机的状况下解决动态变化。spring cloud config旨在加重这些累赘,并提供与Git等版本控制系统的集成,以帮忙您确保配置的平安。 2.4 Circuit breakers——断路器分布式系统可能不牢靠。申请可能会遇到超时或齐全失败。断路器能够帮忙缓解这些问题,spring cloud断路器为您提供了三种风行的抉择:Resilience4J、Sentinel或Hystrix。 2.5 Tracing——追踪调试分布式应用程序可能很简单,而且须要很长时间。对于任何给定的失败,您可能须要将来自多个独立服务的信息跟踪拼凑在一起。spring cloud sleuth能够以一种可预测和可反复的形式为您的利用程序安装工具。当与Zipkin联合应用时,您能够将注意力集中在任何可能存在的提早问题上。 2.6 Testing——测试在云计算中,领有牢靠、可信、稳固的api能够取得额定的分数,但要实现这一指标还须要一段旅程。基于契约的测试是高绩效团队常常应用的一种技术。它有助于规范化api的内容,并围绕它们构建测试,以确保代码始终处于查看状态。spring cloud contract通过应用Groovy、Java或Kotlin编写的契约,为REST和基于消息传递的api提供基于契约的测试反对。 3 流数据spring cloud stream使得应用和生成事件变得非常容易,无论您抉择哪个消息传递平台。spring cloud stream只需几行代码就能够将您的微服务与实时消息传递连接起来,以帮忙您构建高度可伸缩、事件驱动的零碎。Get started with Spring Cloud Stream 4 治理微服务spring boot的可选仪器框架microller间接将度量发送给Prometheus、Atlas等,以提供有价值的见解。spring cloud的Sleuth和Zipkin我的项目补充了这一点,它们提供了分布式跟踪,以便您可能实时跟踪正在产生的事件。Get started with Micrometer on Spring Boot ...

August 9, 2020 · 1 min · jiezi

关于spring-cloud:Spring-Cloud-Spring-Security实践二-自定义登录界面及鉴权

上一篇文章 [Spring Cloud] - Spring Security实际(一)- 基本概念及实际 中, 咱们曾经实现了根本的security鉴权和认证. 上面会在此基础上加一些自定义的配置. 摈弃自带的登陆界面, 实现自定义登陆界面 (themeleaf形式)对资源(controller)实现更细化的鉴权操作增加自定义登陆界面Spring security默认应用/login办法跳转到登录界面, 咱们替换login页面,只需将/login指向的界面换成本人的即可. 增加themeleaf依赖<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId></dependency>新建login controller@Controllerpublic class Login { @GetMapping("/login") private String loginController(){ return "login"; }}在我的项目构造的/resource/templates下新建login.html<!DOCTYPE html><html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org" xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity3"><head> <title>Spring Security Example </title></head><body><div th:if="${param.error}"> Invalid username and password.</div><div th:if="${param.logout}"> You have been logged out.</div><form th:action="@{/login}" method="post"> <div><label> User Name : <input type="text" name="username"/> </label></div> <div><label> Password: <input type="password" name="password"/> </label></div> <div><input type="submit" value="Sign In"/></div></form></body></html>Spring security框架会拦挡login的post申请 security的@configuration中,为以下配置@EnableWebSecuritypublic class SecurityConfiguration extends WebSecurityConfigurerAdapter { @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder()) .withUser("root").password(new BCryptPasswordEncoder().encode("1234")).roles("ADMIN") .and() .withUser("user").password(new BCryptPasswordEncoder().encode("123")).roles("USER"); } @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests()// .antMatchers("/", "/user").permitAll() .antMatchers("/user").hasRole("USER") .antMatchers("/admin").hasRole("ADMIN") .anyRequest().authenticated() .and() .formLogin() .loginPage("/login") .permitAll() .and() .logout() .permitAll(); }}

July 19, 2020 · 1 min · jiezi

源码剖析ApiImplicitParam对RequestParam的required属性的侵入性

问题起源应用SpringCloud构建我的项目时,应用Swagger生成相应的接口文档是举荐的选项,Swagger可能提供页面拜访,间接在网页上调试后端系统的接口, 十分不便。最近却遇到了一个有点困惑的问题,演示接口示例如下(原有性能接口带有业务实现逻辑,这里简化了接口): /** * @description: 演示类 * @author: Huang Ying **/@Api(tags = "演示类")@RestController@Slf4jpublic class DemoController { @ApiOperation(value = "测试接口") @ApiImplicitParams({ @ApiImplicitParam(name = "uid", value = "用户ID", paramType = "query", dataType = "Long") }) @RequestMapping(value = "/api/json/demo", method = RequestMethod.GET) public String auth(@RequestParam(value = "uid") Long uid) { System.out.println(uid); return "the uid: " + uid; }}问题出在接口参数uid的必填性上,@RequestParam注解里require默认为true,要求必填,但@ApiImplicitParam注解里require默认为false,要求非必填,该业务接口在进行性能联调时,uid竟然能失去一个null值,依照个别认知习惯@ApiImplicitParam注解的次要作用是生成接口文档,不应该对@RequestParam的属性有侵入性才对,目前反馈的bug,让我狐疑@ApiImplicitParam是不是会侵入@RequestParam的require属性? 框架选型、版本及次要性能我的项目搭建SpringBoot版本:2.1.6.RELEASESpringCloud版本:Greenwich.SR3 业务模块SpringCloud业务模块应用的swagger: swagger bootstrap ui 1.9.6 加强swagger ui款式spring4all-swagger 1.9.0.RELEASE 配置化swagger参数,免去代码开发 业务网关SpringCloud业务网关应用的swagger: ...

July 9, 2020 · 4 min · jiezi

Spring-Cloud-Alibaba系列四使用gateway作为服务网关

什么是网关在微服务架构里,服务的粒度被进一步细分,各个业务服务可以被独立的设计、开发、测试、部署和管理。这时,各个独立部署单元可以用不同的开发测试团队维护,可以使用不同的编程语言和技术平台进行设计,这就要求必须使用一种语言和平台无关的服务协议作为各个单元间的通讯方式。换句话说就是网关为所有的请求提供了统一的入口,方便我们对服务请求和响应做统一管理。 为什么要用网关API 网关是一个处于应用程序或服务(提供 REST API 接口服务)之前的系统,用来管理授权、访问控制和流量限制等,这样 REST API 接口服务就被 API 网关保护起来,对所有的调用者透明。 什么是gatewaySpring Cloud Gateway是Spring官方基于Spring 5.0,Spring Boot 2.0和Project Reactor等技术开发的网关,Spring Cloud Gateway旨在为微服务架构提供一种简单而有效的统一的API路由管理方式。Spring Cloud Gateway作为Spring Cloud生态系中的网关,目标是替代ZUUL,其不仅提供统一的路由方式,并且基于Filter链的方式提供了网关基本的功能,例如:安全,监控/埋点,和限流等。gateway工作原理 客户端向Spring Cloud网关发出请求。如果网关处理程序映射确定请求与路由匹配,则将其发送到网关Web处理程序。该处理程序运行通过特定于请求的过滤器链发送请求。过滤器由虚线分隔的原因是,过滤器可以在发送代理请求之前或之后执行逻辑。执行所有“前置”过滤器逻辑,然后发出代理请求。发出代理请求后,将执行“后”过滤器逻辑。路由规则路由和过滤器是gateway中非常重要的两个概念,gateway本身提供了非常丰富的路由规则和多种过滤器来适配我们的需求。gateway提供了11种路由规则,分别是: 后置路由谓词工厂该谓词匹配在当前日期时间之后发生的请求。参数名为 After 前置路由谓词工厂该谓词匹配当前日期时间之前发生的请求。参数名为 Before 时间段路由谓词工厂该谓词匹配在datetime1之后和datetime2之前发生的请求。参数名为 Between cookie路由谓词工厂该谓词匹配具有给定名称的cookie,并且值匹配正则表达式。参数名为 Cookie 标头路由谓词工厂该谓词与具有给定名称的标头匹配,并且值与正则表达式匹配。参数名为 Header 主机路由谓词工厂该谓词是指由路由进行匹配,匹配多个路由时用,隔开。参数名为 Host 方法路由谓词工厂该参数是一个或多个要匹配的HTTP方法。参数名为 Method 路径路由谓词工厂该谓词是指在请求路径上加一个前缀,以此来匹配。参数名为 Path 查询路由谓词工厂RemoteAddr路由谓词工厂重量路线谓词工厂其中,我们比较常用的就是路径路由谓词工厂,配合StripPrefix GatewayFilter工厂,实现我们的路由匹配转发。 路径路由谓词工厂配置如下: spring: cloud: gateway: discovery: locator: enabled: true # 开启从注册中心动态创建路由的功能,利用微服务名称进行路由 routes: # 路由id,建议配合服务名 - id: demo_route #匹配路由名 uri: lb://demo-provider predicates: # 断言,路径相匹配的进行路由 - Path=/demo/** 配置的含义就是,如果请求路径中是/demo/**,则转发到demo-provider服务。 ...

June 17, 2020 · 2 min · jiezi

Spring-Cloud-Zuul-实践二-负载均衡

Zuul内部通过Ribbon实现的负载均衡。初次接触,不需要了解太多,还是老办法,上demo。 本文涉及的demo是在zuul的第一课: [Spring Cloud] - Zuul 实践(一) - 基本使用 的基础上建立的,建议从第一课读起(直接看此文亦可,本文通俗易懂,老少皆宜)。 Zuul的负载均衡是建立在同一服务的多个实例的前提下实现的,通俗点说,在eureka server中连接多个相同服务,是Zuul负载均衡的前提。 我们建立两个不同的工程,其中设置相同的application.name。 创建步骤该不赘述,请参考我的另一篇文档[Spring Cloud] - Eureka 创建及使用 的创建eureka client的步骤。注意: 确定两个工程中配置文件的spring.application.name相同。 spring: application: name: service1将两个工程启动,查看eureka server中是否注册了两个相同的服务:(Availability Zones的数字为service的副本数量) 此时若使用zuul访问service1,zuul会自动分发请求到这两个相同的service。以上就是基于zuul和eureka实现的最基本的负载均衡功能,是否很简单呢?

May 27, 2020 · 1 min · jiezi

微服务架构案例04中间件集成公共服务封装

本文源码:GitHub·点这里 || GitEE·点这里 更新进度(共6节): 01:项目技术选型简介,架构图解说明02:业务架构设计,系统分层管理03:数据库选型,业务数据设计规划04:中间件集成,公共服务管理一、中间件简介中间件是基础软件的一类, 属于复用性极高的软件。处于操作系统软件与应用程序的之间。是一种独立的系统软件,也可以是公共的服务程序,分布式架构系统借助中间件,可以在不同的技术之间共享资源,或者不同的服务直接传递信息。中间件位操作系统之上,管理计算机资源和网络通讯。是连接两个独立应用程序或独立系统的软件,例如:消息队列中间件,在两个服务之间进行异步的消息传递;数据缓存中间件,缓存整合系统的热点数据,提高程序的响应速度;Nginx中间件,提供负载均衡,服务代理,等功能;二、公共服务简介公共服务,顾名思义就是系统内通用的服务,例如用户身份验证,消息发送,监控预警,网关服务等。 该案例的中间件和公共服务,都是基于Feign接口统一的方式提供服务。 三、中间件集成1、消息中间件RocketMq简介RocketMq 是一款分布式、队列模型的消息中间件,有两个核心角色:消息生产者和消息消费者。作为高并发系统的核心组件之一,能够帮助业务系统解构提高系统稳定性。 应用流程消息生产者@Componentpublic class MsgSendService { @Resource private ProducerConfig producerConfig ; public void sendMsg (MsgWrap msgWrap) { producerConfig.sendMsg(msgWrap.getGroup(),msgWrap.getTopic(), msgWrap.getTag(),msgWrap.getContent()); }}消息消费者@Component@Consumer(group = MsgRoute.husky_group_1, topic = MsgRoute.husky_topic_1 , tag = MsgRoute.husky_tag_1)public class UserSearchListener implements MsgReadService { @Resource private BookEsAnalyFeign bookEsAnalyFeign ; @Override public void readMsg(String msg) throws Exception { LOGGER.info("【用户搜索消息监听 Msg】:{}",msg) ; // 转发请求数据分析服务 bookEsAnalyFeign.sendBookEsMsg(msg); }}提供Feign接口@RestControllerpublic class UserSearchController implements UserSearchFeign { @Resource private SendMsgService sendMsgService ; @Override public void sendBookSearch(String msgContent) { MsgWrap msgWrap = new MsgWrap() ; msgWrap.setContent(msgContent); msgWrap.setGroup(MsgRoute.husky_group_1); msgWrap.setTopic(MsgRoute.husky_topic_1); msgWrap.setTag(MsgRoute.husky_tag_1); sendMsgService.sendMsg(msgWrap); }}2、缓存中间件Redis简介Redis 是一个基于内存的高性能key-value数据库。对高并发系统提供各种场景的支撑:热点数据缓存,计数器,流量削峰等。 ...

November 5, 2019 · 3 min · jiezi

基于-Spring-Cloud-GreenwichSR1-的微服务权限系统-FEBS-Cloud

FEBS Cloud 微服务权限系统FEBS Cloud是一款使用Spring Cloud Greenwich.SR1、Spring Cloud OAuth2和Spring Cloud Security构建的权限管理系统,前端(FEBS Cloud Web)采用vue element admin构建。FEBS意指:Fast,Easy use,Beautiful和Safe。该系统具有如下特点: 前后端分离架构,客户端和服务端纯Token交互;认证服务器与资源服务器分离,方便接入自己的微服务系统;微服务防护,客户端请求资源只能通过微服务网关获取;集成Spring Boot Admin,多维度监控微服务;集成Zipkin,方便跟踪Feign调用链;集成ELK,集中管理日志,便于问题分析;微服务Docker化,使用Docker Compose一键部署;提供详细的使用文档和搭建教程;前后端请求参数校验,Excel导入导出,代码生成等。文档与教程项目文档及手摸手搭建教程地址:https://www.kancloud.cn/mrbird/spring-cloud/1263679 系统架构 项目地址平台FEBS Cloud(后端)FEBS Cloud Web(前端)GitHubhttps://github.com/wuyouzhuguli/FEBS-Cloudhttps://github.com/wuyouzhuguli/FEBS-Cloud-Web演示地址http://49.234.20.223:9527 演示环境账号密码: 账号密码权限scott1234qwer注册账户,拥有查看,新增权限(新增用户除外)和导出Excel权限本地部署账号密码: 账号密码权限mrbird1234qwer超级管理员,拥有所有增删改查权限scott1234qwer注册账户,拥有查看,新增权限(新增用户除外)和导出Excel权限jane1234qwer系统监测员,负责整个系统监控模块服务模块FEBS模块: 服务名称端口描述FEBS-Register8001微服务注册中心FEBS-Auth8101微服务认证服务器FEBS-Server-System8201微服务子系统(资源服务器)FEBS-Server-Test8202微服务子系统(资源服务器)FEBS-Gateway8301微服务网关FEBS-Monitor-Admin8401微服务监控子系统Zipkin-Server8402Zipkin服务器FEBS-Config8501微服务配置子系统第三方模块: 服务名称端口描述MySQL3306MySQL数据库RabbitMQ5672RabbitMQ消息中间件Redis6379K-V缓存数据库Elasticsearch9200日志存储Logstash4560日志收集Kibana5601日志展示目录结构├─febs-auth ------ 微服务认证服务器├─febs-cloud ------ 整个项目的父模块│ └─docker compose ------ 存放docker compose文件│ ├─elk ------ ELK docker compose文件│ ├─febs-cloud ------ 聚合所有微服务子项目的docker compose文件│ └─third-part ------ 第三方服务(MySQL,Redis等)docker compose文件├─febs-common ------ 通用模块├─febs-config ------ 微服务配置中心├─febs-gateway ------ 微服务网关├─febs-monitor ------ 微服务监控父模块│ ├─febs-monitor-admin ------ 微服务监控中心│ └─zipkin-server ------ zipkin 服务├─febs-register ------ 微服务注册中心└─febs-server ------ 资源服务器 ├─febs-server-system ------- 资源服务器系统模块 └─febs-server-test ------ 资源服务器demo,演示如何整合自己的微服务系统系统截图 ...

September 9, 2019 · 1 min · jiezi

Spring-Cloud-OAuth2-实现自定义返回格式

实现效果 启用授权服务器@Configuration@EnableAuthorizationServerpublic class AuthorizationServerConfigurer extends AuthorizationServerConfigurerAdapter { //...... @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints.tokenEnhancer(new TokenEnhancer() { @Override public OAuth2AccessToken enhance(OAuth2AccessToken oAuth2AccessToken, OAuth2Authentication oAuth2Authentication) { //在此追加返回的数据 DefaultOAuth2AccessToken token = (DefaultOAuth2AccessToken) oAuth2AccessToken; CustomUser user = (CustomUser) oAuth2Authentication.getDetails(); Map<String, Object> map = new LinkedHashMap<>(); map.put("nickname", user.getNickname()); map.put("mobile", user.getMobile()); map.put("avatar",user.getAvatar()); token.setAdditionalInformation(map); return oAuth2AccessToken; } }); } }创建响应实体@Data@NoArgsConstructor@AllArgsConstructor@JsonInclude(JsonInclude.Include.NON_NULL)public class Result { //是否成功 private boolean success; //返回码 private int code; //返回信息 private String msg; //返回数据 private Object data; public static Result build(Object data) { return new Result(true, 200, "操作成功",data); } }重写令牌申请接口@RestController@RequestMapping("/oauth")public class OauthController { @Autowired private TokenEndpoint tokenEndpoint; @GetMapping("/token") public Result getAccessToken(Principal principal, @RequestParam Map<String, String> parameters) throws HttpRequestMethodNotSupportedException { return custom(tokenEndpoint.getAccessToken(principal, parameters).getBody()); } @PostMapping("/token") public Result postAccessToken(Principal principal, @RequestParam Map<String, String> parameters) throws HttpRequestMethodNotSupportedException { return custom(tokenEndpoint.postAccessToken(principal, parameters).getBody()); } //自定义返回格式 private Result custom(OAuth2AccessToken accessToken) { DefaultOAuth2AccessToken token = (DefaultOAuth2AccessToken) accessToken; Map<String, Object> data = new LinkedHashMap(token.getAdditionalInformation()); data.put("accessToken", token.getValue()); if (token.getRefreshToken() != null) { data.put("refreshToken", token.getRefreshToken().getValue()); } return Result.build(data); }}项目源码https://gitee.com/yugu/demo-o... ...

September 7, 2019 · 1 min · jiezi

SpringCloud微服务07Zipkin组件实现请求链路追踪

一、链路追踪简介1、Sleuth组件简介Sleuth是SpringCloud微服务系统中的一个组件,实现了链路追踪解决方案。可以定位一个请求到底请求了哪些具体的服务。在复杂的微服务系统中,如果请求发生了异常,可以快速捕获问题所在的服务。2、项目结构启动顺序如下* 注册中心node07-eureka-7001* 链路数据收集服务node07-zipkin-7003* 服务提供node07-provider-6001node07-provider-6002* 网关路由node07-zuul-7002二、搭建链路服务1、核心依赖<dependency> <groupId>io.zipkin.java</groupId> <artifactId>zipkin-server</artifactId></dependency><dependency> <groupId>io.zipkin.java</groupId> <artifactId>zipkin-autoconfigure-ui</artifactId></dependency>启动类注解:@EnableZipkinServer2、配置文件server: port: 7003spring: application: name: node07-zipkin-7003eureka: instance: hostname: zipkin-7003 prefer-ip-address: true client: service-url: defaultZone: http://registry01.com:7001/eureka/三、服务配置这里网关,zuul-7002,服务提供,provider-6001,provider-6002的配置相同。 1、核心依赖<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-zipkin</artifactId></dependency>2、配置文件spring: zipkin: base-url: http://localhost:7003 sleuth: sampler: # 数据 100% 上传 percentage: 1.0四、测试流程1、注册中心一次启动上述服务之后,查看注册中心: 2、请求流程访问接口 http://localhost:7002/v1/api-6001/get6001Info这个请求从网关服务进入,到达6001端口服务之后,请求6002端,最终返回结果。 6001接口@Autowiredprivate RestTemplate restTemplate ;@RequestMapping("/get6001Info")public String get6001Info (){ String server_name = "http://node07-provider-6002" ; return restTemplate.getForObject(server_name+"/get6002Info",String.class) ;}6002接口@RequestMapping(value = "/get6002Info",method = RequestMethod.GET)public String get6002Info () { LOG.info("provider-6002"); return "6002Info" ;}3、链路管理界面1)、UI界面 ...

August 20, 2019 · 1 min · jiezi

聊聊spring-cloud-netflix-ribbon的eager-load

序本文主要研究一下spring cloud netflix ribbon的eager load RibbonAutoConfigurationspring-cloud-netflix-ribbon-2.1.1.RELEASE-sources.jar!/org/springframework/cloud/netflix/ribbon/RibbonAutoConfiguration.java @Configuration@Conditional(RibbonAutoConfiguration.RibbonClassesConditions.class)@RibbonClients@AutoConfigureAfter(name = "org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration")@AutoConfigureBefore({ LoadBalancerAutoConfiguration.class, AsyncLoadBalancerAutoConfiguration.class })@EnableConfigurationProperties({ RibbonEagerLoadProperties.class, ServerIntrospectorProperties.class })public class RibbonAutoConfiguration { @Autowired(required = false) private List<RibbonClientSpecification> configurations = new ArrayList<>(); @Autowired private RibbonEagerLoadProperties ribbonEagerLoadProperties; @Bean public HasFeatures ribbonFeature() { return HasFeatures.namedFeature("Ribbon", Ribbon.class); } @Bean public SpringClientFactory springClientFactory() { SpringClientFactory factory = new SpringClientFactory(); factory.setConfigurations(this.configurations); return factory; } @Bean @ConditionalOnMissingBean(LoadBalancerClient.class) public LoadBalancerClient loadBalancerClient() { return new RibbonLoadBalancerClient(springClientFactory()); } @Bean @ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate") @ConditionalOnMissingBean public LoadBalancedRetryFactory loadBalancedRetryPolicyFactory( final SpringClientFactory clientFactory) { return new RibbonLoadBalancedRetryFactory(clientFactory); } @Bean @ConditionalOnMissingBean public PropertiesFactory propertiesFactory() { return new PropertiesFactory(); } @Bean @ConditionalOnProperty("ribbon.eager-load.enabled") public RibbonApplicationContextInitializer ribbonApplicationContextInitializer() { return new RibbonApplicationContextInitializer(springClientFactory(), ribbonEagerLoadProperties.getClients()); } @Configuration @ConditionalOnClass(HttpRequest.class) @ConditionalOnRibbonRestClient protected static class RibbonClientHttpRequestFactoryConfiguration { @Autowired private SpringClientFactory springClientFactory; @Bean public RestTemplateCustomizer restTemplateCustomizer( final RibbonClientHttpRequestFactory ribbonClientHttpRequestFactory) { return restTemplate -> restTemplate .setRequestFactory(ribbonClientHttpRequestFactory); } @Bean public RibbonClientHttpRequestFactory ribbonClientHttpRequestFactory() { return new RibbonClientHttpRequestFactory(this.springClientFactory); } } // TODO: support for autoconfiguring restemplate to use apache http client or okhttp @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @Conditional(OnRibbonRestClientCondition.class) @interface ConditionalOnRibbonRestClient { } private static class OnRibbonRestClientCondition extends AnyNestedCondition { OnRibbonRestClientCondition() { super(ConfigurationPhase.REGISTER_BEAN); } @Deprecated // remove in Edgware" @ConditionalOnProperty("ribbon.http.client.enabled") static class ZuulProperty { } @ConditionalOnProperty("ribbon.restclient.enabled") static class RibbonProperty { } } /** * {@link AllNestedConditions} that checks that either multiple classes are present. */ static class RibbonClassesConditions extends AllNestedConditions { RibbonClassesConditions() { super(ConfigurationPhase.PARSE_CONFIGURATION); } @ConditionalOnClass(IClient.class) static class IClientPresent { } @ConditionalOnClass(RestTemplate.class) static class RestTemplatePresent { } @ConditionalOnClass(AsyncRestTemplate.class) static class AsyncRestTemplatePresent { } @ConditionalOnClass(Ribbon.class) static class RibbonPresent { } }}RibbonAutoConfiguration启用了RibbonEagerLoadProperties、ServerIntrospectorProperties两个配置,这里主要是注册了SpringClientFactory,创建LoadBalancerClient、LoadBalancedRetryFactory、RibbonApplicationContextInitializer、RestTemplateCustomizer及RibbonClientHttpRequestFactoryRibbonApplicationContextInitializerspring-cloud-netflix-ribbon-2.1.1.RELEASE-sources.jar!/org/springframework/cloud/netflix/ribbon/RibbonApplicationContextInitializer.java ...

July 9, 2019 · 3 min · jiezi

这个注解一次搞定限流与熔断降级SentinelResource

在之前的《使用Sentinel实现接口限流》一文中,我们仅依靠引入Spring Cloud Alibaba对Sentinel的整合封装spring-cloud-starter-alibaba-sentinel,就完成了对所有Spring MVC接口的限流控制。然而,在实际应用过程中,我们可能需要限流的层面不仅限于接口。可能对于某个方法的调用限流,对于某个外部资源的调用限流等都希望做到控制。呢么,这个时候我们就不得不手工定义需要限流的资源点,并配置相关的限流策略等内容了。 今天这篇我们就来一起学习一下,如何使用@SentinelResource注解灵活的定义控制资源以及如何配置控制策略。 自定义资源点下面的例子基于您已经引入了Spring Cloud Alibaba Sentinel为基础,如果您还不会这些,建议优先阅读《使用Sentinel实现接口限流》。 第一步:在应用主类中增加注解支持的配置: @SpringBootApplicationpublic class TestApplication { public static void main(String[] args) { SpringApplication.run(TestApplication.class, args); } // 注解支持的配置Bean @Bean public SentinelResourceAspect sentinelResourceAspect() { return new SentinelResourceAspect(); }}第二步:在需要通过Sentinel来控制流量的地方使用@SentinelResource注解,比如下面以控制Service逻辑层的某个方法为例: @Slf4j@Servicepublic class TestService { @SentinelResource(value = "doSomeThing") public void doSomeThing(String str) { log.info(str); }}到这里一个需要被保护的方法就定义完成了。下面我们分别说说,定义了资源点之后,我们如何实现不同的保护策略,包括:限流、降级等。 如何实现限流与熔断降级在定义了资源点之后,我们就可以通过Dashboard来设置限流和降级策略来对资源点进行保护了。同时,也可以通过@SentinelResource来指定出现限流和降级时候的异常处理策略。下面,就来一起分别看看限流和降级都是如何实现的。 实现限流控制第一步:在Web层调用这个被保护的方法: @RestControllerpublic class TestController { @Autowired private TestService testService; @GetMapping("/hello") public String hello() { estService.doSomeThing("hello " + new Date()); return "didispace.com"; }}第二步:启动测试应用,启动Sentinel-Dashboard。发一个请求到/hello接口上,使得Sentinel-Dashboard上可以看到如下图所示的几个控制点: ...

July 2, 2019 · 2 min · jiezi

Spring-boot-stater开发小助手micaauto-110-发布

简介mica-auto 原理为采用Annotation Processor 注解处理技术,编译期间自动生成 Spring boot starter 的一些基础配置。在 Spring cloud 微服务核心组件 Mica(云母)以及 pig ,Springblade 中大量使用。 功能将标记有或者组合有 @Component 的注解类生成到 spring.factories 中。生成 spring-devtools.properties。组合 spring-boot-configuration-processor 和 spring-boot-autoconfigure-processor避免项目中引入过多依赖。生成 @FeignClient 的接口到 spring.factories 中,供 mica 中完成 Feign 自动化配置。 变更记录 [1.1.0] - 2019-05-24 添加添加 @AutoContextInitializer 支持 org.springframework.context.ApplicationContextInitializer。添加 @AutoListener 支持 org.springframework.context.ApplicationListener。添加 @AutoFailureAnalyzer 支持 org.springframework.boot.diagnostics.FailureAnalyzer。依赖升级升级 gradle 到 5.4.1。升级 google auto 到 1.0-rc5。升级 lombok 到 1.18.8,升级 Spring Boot 到 2.1.5.RELEASE。使用注意: 如果你项目中使用了 Lombok 请将 mica-auto 的依赖放置到 Lombok 后面。 ...

May 24, 2019 · 1 min · jiezi

Spring-Cloud-Alibaba基础教程Sentinel-Dashboard中修改规则同步到Nacos

上一篇我们介绍了如何通过改造Sentinel Dashboard来实现修改规则之后自动同步到Apollo。下面通过这篇,详细介绍当使用Nacos作为配置中心之后,如何实现Sentinel Dashboard中修改规则同步到Nacos。关于下面改造的原理和分析可以见上一篇《Sentinel Dashboard中修改规则同步到Apollo》的头两节内容,这里不重复介绍了。 代码实现下面直接来看看如何实现的具体改造步骤,这里参考了Sentinel Dashboard源码中关于Nacos实现的测试用例。但是由于考虑到与Spring Cloud Alibaba的结合使用,略作修改。 第一步:修改pom.xml中的sentinel-datasource-nacos的依赖,将<scope>test</scope>注释掉,这样才能在主程序中使用。 <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-datasource-nacos</artifactId> <!--<scope>test</scope>--></dependency>第二步:找到resources/app/scripts/directives/sidebar/sidebar.html中的这段代码: <li ui-sref-active="active"> <a ui-sref="dashboard.flowV1({app: entry.app})"> <i class="glyphicon glyphicon-filter"></i>&nbsp;&nbsp;流控规则 </a></li>修改为: <li ui-sref-active="active"> <a ui-sref="dashboard.flow({app: entry.app})"> <i class="glyphicon glyphicon-filter"></i>&nbsp;&nbsp;流控规则 </a></li>第三步:在com.alibaba.csp.sentinel.dashboard.rule包下新建一个nacos包,用来编写针对Nacos的扩展实现。 第四步:创建Nacos的配置类,具体代码如下: @Configurationpublic class NacosConfig { @Bean public Converter<List<FlowRuleEntity>, String> flowRuleEntityEncoder() { return JSON::toJSONString; } @Bean public Converter<String, List<FlowRuleEntity>> flowRuleEntityDecoder() { return s -> JSON.parseArray(s, FlowRuleEntity.class); } @Bean public ConfigService nacosConfigService() throws Exception { Properties properties = new Properties(); properties.put(PropertyKeyConst.SERVER_ADDR, "localhost"); return ConfigFactory.createConfigService(properties); }}如果用到了namespace隔离环境,可以在nacosConfigService方法中再加入配置,比如:properties.put(PropertyKeyConst.NAMESPACE, "130e71fa-97fe-467d-ad77-967456f2c16d"); ...

May 22, 2019 · 2 min · jiezi

架构必备-Spring-cloud-微服务核心组件集-mica-v111-发布

mica(云母)mica 云母,寓意为云服务的核心,mica v1.1.0 开源了原来闭源的 mica-pro 部分代码,增强 Spring cloud 功能,使得 Spring cloud 服务开发更加方便快捷。 mica 核心依赖mica 基于 java 8,没有历史包袱,支持传统 Servlet 和 Reactive(webflux)。采用 mica-auto 自动生成 spring.factories 和 spring-devtools.properties 配置,仅依赖 Spring boot、Spring cloud 全家桶,无第三方依赖。市面上鲜有的微服务核心组件。 依赖版本Spring Boot2.1.xSpring CloudGreenwich.RELEASE 更新说明 减少部分阿里巴巴规范问题。 优化日志,dev 环境日志,不按内存切分,不使用gz压缩,避免每次本地重启生成日志文件。 优化 StackTraceAsString 中 FastStringWriter 初始容量为200。 优化 ServiceException R 泛型。 添加 R.throw 系列方法,用于处理异常直接返回的情况。 Try 添加 Runnable、Callable、Comparator 的 Lambda 受检异常处理。 修复日期添加和减少的bug IW2IM。 组件图谱(新)我们来看看新的功能全景图。 ...

May 12, 2019 · 1 min · jiezi

Nacos部署中的一些常见问题汇总

开个帖子,汇总一下读者经常提到的一些问题问题一:Ubuntu下启动Nacos报错问题描述 使用命令sh startup.sh -m standalone启动报错: ./startup.sh: 78: ./startup.sh: [[: not found./startup.sh: 88: ./startup.sh: [[: not found./startup.sh: 90: ./startup.sh: [[: not found./startup.sh: 96: ./startup.sh: [[: not found/usr/lib/jvm/java-8-openjdk-amd64/bin/java -server -Xms2g -Xmx2g -Xmn1g -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=320m -XX:-OmitStackTraceInFastThrow -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/data/soft/nacos/logs/java_heapdump.hprof -XX:-UseLargePages -Djava.ext.dirs=/usr/lib/jvm/java-8-openjdk-amd64/jre/lib/ext:/usr/lib/jvm/java-8-openjdk-amd64/lib/ext:/data/soft/nacos/plugins/cmdb:/data/soft/nacos/plugins/mysql -Xloggc:/data/soft/nacos/logs/nacos_gc.log -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10 -XX:GCLogFileSize=100M -Dnacos.home=/data/soft/nacos -jar /data/soft/nacos/target/nacos-server.jar --spring.config.location=classpath:/,classpath:/config/,file:./,file:./config/,file:/data/soft/nacos/conf/ --logging.config=/data/soft/nacos/conf/nacos-logback.xml./startup.sh: 116: ./startup.sh: [[: not foundnacos is starting,you can check the /data/nacos/logs/start.out解决方法 改用命令bash -f ./startup.sh -m standalone启动 ...

May 10, 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

Sentinel-Dashboard中修改规则同步到Apollo存储

在之前的两篇教程中我们分别介绍了如何将Sentinel的限流规则存储到Nacos和Apollo中。同时,在文末的思考中,我都指出了这两套整合方案都存在一个不足之处:不论采用什么配置中心,限流规则都只能通过Nacos界面或Apollo界面来完成修改才能得到持久化存储,而在Sentinel Dashboard中修改限流规则虽然可以生效,但是不会被持久化到配置中心。而在这两个配置中心里存储的数据是一个Json格式,当存储的规则越来越多,对该Json配置的可读性与可维护性会变的越来越差。所以,下面我们就来继续探讨这个不足之处,并给出相应的解决方案。本文以Apollo存储为例,下一篇介绍Nacos的改在示例。 问题分析在实际操作之前,我们先通过下图了解一下之前我们所实现的限流规则持久化方案的配置数据流向图: 蓝色箭头代表了限流规则由配置中心发起修改的更新路径橙色箭头代表了限流规则由Sentinel Dashboard发起修改的更新路径从图中可以很明显的看到,Sentinel Dashboard与业务服务之间本身是可以互通获取最新限流规则的,这在没有整合配置中心来存储限流规则的时候就已经存在这样的机制。最主要的区别是:配置中心的修改都可以实时的刷新到业务服务,从而被Sentinel Dashboard读取到,但是对于这些规则的更新到达各个业务服务之后,并没有一个机制去同步到配置中心,作为配置中心的客户端也不会提供这样的逆向更新方法。 改造方案关于如何改造,现来解读一下官方文档中关于这部分的说明: 要通过 Sentinel 控制台配置集群流控规则,需要对控制台进行改造。我们提供了相应的接口进行适配。 从 Sentinel 1.4.0 开始,我们抽取出了接口用于向远程配置中心推送规则以及拉取规则: DynamicRuleProvider<T>: 拉取规则DynamicRulePublisher<T>: 推送规则对于集群限流的场景,由于每个集群限流规则都需要唯一的 flowId,因此我们建议所有的规则配置都通过动态规则源进行管理,并在统一的地方生成集群限流规则。 我们提供了新版的流控规则页面,可以针对应用维度推送规则,对于集群限流规则可以自动生成 flowId。用户只需实现 DynamicRuleProvider 和 DynamicRulePublisher 接口,即可实现应用维度推送(URL: /v2/flow)。 这段内容什么意思呢?简单的说就是Sentinel Dashboard通过DynamicRuleProvider和DynamicRulePublisher两个接口来获取和更新应用的动态规则。默认情况下,就如上一节中Sentinel Dashboard与各业务服务之间的两个箭头,一个接口负责获取规则,一个接口负责更新规则。 所以,只需要通过这两个接口,实现对配置中心中存储规则的读写,就能实现Sentinel Dashboard中修改规则与配置中心存储同步的效图如下: ![图片上传中...] 其中,绿色箭头为公共公共部分,即:不论从培中心修改,还是从Sentinel Dashboard修改都会触发的操作。这样的话,从上图的两处修改起点看,所有涉及的部分都能获取到一致的限流规则了。 代码实现下面继续说说具体的代码实现,这里参考了Sentinel Dashboard源码中关于Apollo实现的测试用例。但是由于考虑到与Spring Cloud Alibaba的结合使用,略作修改。 第一步:修改pom.xml中的Apollo OpenAPi的依赖,将<scope>test</scope>注释掉,这样才能在主程序中使用。 <dependency> <groupId>com.ctrip.framework.apollo</groupId> <artifactId>apollo-openapi</artifactId> <version>1.2.0</version> <!--<scope>test</scope>--></dependency>第二步:找到resources/app/scripts/directives/sidebar/sidebar.html中的这段代码: <li ui-sref-active="active"> <a ui-sref="dashboard.flowV1({app: entry.app})"> <i class="glyphicon glyphicon-filter"></i>&nbsp;&nbsp;流控规则 </a></li>修改为: <li ui-sref-active="active"> <a ui-sref="dashboard.flow({app: entry.app})"> <i class="glyphicon glyphicon-filter"></i>&nbsp;&nbsp;流控规则 </a></li>第三步:在com.alibaba.csp.sentinel.dashboard.rule包下新建一个apollo包,用来编写针对Apollo的扩展实现。 第四步:创建Apollo的配置类,定义Apollo的portal访问地址以及第三方应用访问的授权Token(通过Apollo管理员账户登录,在“开放平台授权管理”功能中创建),具体代码如下: @Configurationpublic class ApolloConfig { @Bean public Converter<List<FlowRuleEntity>, String> flowRuleEntityEncoder() { return JSON::toJSONString; } @Bean public Converter<String, List<FlowRuleEntity>> flowRuleEntityDecoder() { return s -> JSON.parseArray(s, FlowRuleEntity.class); } @Bean public ApolloOpenApiClient apolloOpenApiClient() { ApolloOpenApiClient client = ApolloOpenApiClient.newBuilder() .withPortalUrl("https://apollo.xxx.com") // TODO 根据实际情况修改 .withToken("open api token") // TODO 根据实际情况修改 .build(); return client; }}第五步:实现Apollo的配置拉取实现。 ...

May 7, 2019 · 2 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

Spring-Cloud-参考文档Spring-Cloud-Sleuth抽样

Spring Cloud Sleuth抽样采样可用于减少收集和报告的进程外数据,如果未对span进行抽样,则不会增加任何开销(noop)。 抽样是一个前期决策,这意味着报告数据的决定是在trace中的第一个操作中做出的,并且该决策是向下游传播的。 默认情况下,全局抽样器将单个速率应用于所有跟踪的操作,Tracer.Builder.sampler控制此设置,默认为跟踪每个请求。 声明性抽样某些应用程序需要根据java方法的类型或注解进行采样。 大多数用户使用框架拦截器来自动执行此类策略,以下示例显示了内部可能如何工作: @Autowired Tracer tracer;// derives a sample rate from an annotation on a java methodDeclarativeSampler<Traced> sampler = DeclarativeSampler.create(Traced::sampleRate);@Around("@annotation(traced)")public Object traceThing(ProceedingJoinPoint pjp, Traced traced) throws Throwable { // When there is no trace in progress, this decides using an annotation Sampler decideUsingAnnotation = declarativeSampler.toSampler(traced); Tracer tracer = tracer.withSampler(decideUsingAnnotation); // This code looks the same as if there was no declarative override ScopedSpan span = tracer.startScopedSpan(spanName(pjp)); try { return pjp.proceed(); } catch (RuntimeException | Error e) { span.error(e); throw e; } finally { span.finish(); }}定制抽样根据操作的不同,末可能希望应用不同的策略,例如,你可能不希望跟踪对静态资源(如图像)的请求,或者你可能希望跟踪对新api的所有请求。 ...

April 30, 2019 · 1 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

Spring-Cloud-参考文档Spring-Cloud-Sleuth特性

Spring Cloud Sleuth特性将trace和span ID添加到Slf4J MDC,因此你可以在日志聚合器中从给定的trace或span提取所有日志,如以下示例日志中所示: 2016-02-02 15:30:57.902 INFO [bar,6bfd228dc00d216b,6bfd228dc00d216b,false] 23030 --- [nio-8081-exec-3] ...2016-02-02 15:30:58.372 ERROR [bar,6bfd228dc00d216b,6bfd228dc00d216b,false] 23030 --- [nio-8081-exec-3] ...2016-02-02 15:31:01.936 INFO [bar,46ab0d418373cbc9,46ab0d418373cbc9,false] 23030 --- [nio-8081-exec-4] ...请注意MDC中的[appname,traceId,spanId,exportable]条目: spanId:发生的特定操作的ID。appname:记录span的应用程序的名称。traceId:包含span的延迟图的ID。exportable:是否应将日志导出到Zipkin,你希望什么时候span不能导出?如果要在Span中包装某些操作并将其写入日志中。提供对常见分布式追踪数据模型的抽象:trace、span(形成DAG)、annotation和键值annotation,Spring Cloud Sleuth基于HTrace,但与Zipkin(Dapper)兼容。Sleuth记录时间信息以帮助进行延迟分析,通过使用sleuth,你可以查明应用程序中的延迟原因。Sleuth不进行过多的日志记录,并且不会导致生产应用程序崩溃,为此,Sleuth: 在带内传播有关你的调用图的结构数据,在带外休息。包括层的自定义插装,比如HTTP。包括用于管理卷的采样策略。可以向Zipkin系统报告用于查询和可视化。仪器从Spring应用程序中常见的入口和出口点(servlet过滤器、异步端点、rest模板,调度操作,消息通道,Zuul过滤器和Feign客户端)。Sleuth包含默认逻辑,用于跨HTTP或消息传递边界连接trace,例如,HTTP传播适用于与Zipkin兼容的请求headers。Sleuth可以在进程之间传播上下文(也称为baggage),因此,如果你在Span上设置baggage元素,则会通过HTTP或消息传递向下发送到其他进程。提供创建或继续span以及通过annotations添加标记和日志的方法。如果spring-cloud-sleuth-zipkin位于类路径上,则应用程序会生成并收集与Zipkin兼容的trace,默认情况下,它通过HTTP将它们发送到localhost上的Zipkin服务器(端口9411),你可以通过设置spring.zipkin.baseUrl来配置服务的位置。 如果你依赖spring-rabbit,你的应用程序会将trace发送到RabbitMQ代理而不是HTTP。如果你依赖spring-kafka,并设置spring.zipkin.sender.type:kafka,你的应用程序会将trace发送到Kafka代理而不是HTTP。 spring-cloud-sleuth-stream已弃用,不应再使用。Spring Cloud Sleuth兼容OpenTracing。 如果使用Zipkin,请通过设置spring.sleuth.sampler.probability来配置导出的span概率(默认值:0.1,即10%),否则,你可能会认为Sleuth没有工作,因为它忽略了一些span。始终设置SLF4J MDC,并且logback用户可以根据前面显示的示例立即在日志中看到trace和span ID,其他日志记录系统必须配置自己的格式化程序才能获得相同的结果,默认值如下:logging.pattern.level设置为%5p [${spring.zipkin.service.name:${spring.application.name:-}},%X{X-B3-TraceId:-},%X{X-B3-SpanId:-},%X{X-Span-Export:-}](这是Logback用户的Spring Boot特性),如果你不使用SLF4J,则不会自动应用此模式。Brave介绍从版本2.0.0开始,Spring Cloud Sleuth使用Brave作为追踪库,为方便起见,在此处嵌入了Brave的部分文档。在绝大多数情况下,你只需使用Sleuth提供的Brave中的Tracer或SpanCustomizer bean,下面的文档概述了Brave是什么以及它是如何工作的。Brave是一个用于捕获和报告关于分布式操作的延迟信息到Zipkin的库,大多数用户不直接使用Brave,他们使用库或框架,而不是代表他们使用Brave。 此模块包含一个追踪器,用于创建和连接span,对潜在分布式工作的延迟进行建模,它还包括通过网络边界传播trace上下文的库(例如,使用HTTP头)。 追踪最重要的是,你需要一个brave.Tracer,配置为向Zipkin报告。 以下示例设置通过HTTP(而不是Kafka)将trace数据(spans)发送到Zipkin: class MyClass { private final Tracer tracer; // Tracer will be autowired MyClass(Tracer tracer) { this.tracer = tracer; } void doSth() { Span span = tracer.newTrace().name("encode").start(); // ... }}如果你的span包含一个名称长度超过50个字符,则该名称将被截断为50个字符,你的名称必须明确而具体,大名称会导致延迟问题,有时甚至会引发异常。追踪器创建并连接span,对潜在分布式工作的延迟进行建模,它可以采用抽样来减少进程中的开销,减少发送到Zipkin的数据量,或两者兼而有之。 ...

April 28, 2019 · 3 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

Spring-Cloud-参考文档Spring-Cloud-Sleuth介绍

Spring Cloud Sleuth介绍Spring Cloud Sleuth为Spring Cloud实现了分布式跟踪解决方案。 术语Spring Cloud Sleuth借用了Dapper的术语。 Span:基本工作单元,例如,发送RPC是一个新的span,就像向RPC发送响应一样,Span由span的唯一64位ID标识,另一个64位ID标识其所属的Trace。Span还有其他数据,例如描述、带时间戳的事件、键值annotations(标签),导致它们的span的ID以及进程ID(通常是IP地址)。 span可以启动和停止,它们可以跟踪自己的时间信息,创建span后,必须在将来的某个时刻停止它。 启动Trace的初始span称为root span,该span的ID值等于trace ID。Trace:一组span形成的树状结构,例如,如果运行分布式大数据存储,则可能由PUT请求形成trace。 Annotation:用于及时记录事件的存在,使用Brave工具,不再需要为Zipkin设置特殊的事件来了解客户端和服务器是谁、请求在哪里开始以及在哪里结束,然而,出于学习目的,标记这些事件以突出发生了什么类型的操作。 cs:Client Sent,客户端发起了一个请求,这个annotation表示span的开始。sr:Server Received,服务器端获得了请求并开始处理它,从此时间戳中减去cs时间戳会显示网络延迟。ss:Server Sent,在请求处理完成时注释(当响应被发送回客户端时),从此时间戳中减去sr时间戳会显示服务器端处理请求所需的时间。cr:Client Received,表示span的结束,客户端已成功从服务器端收到响应,从此时间戳中减去cs时间戳会显示客户端从服务器接收响应所需的全部时间。下图显示了系统中的Span和Trace,以及Zipkin annotations: 标记的每种颜色表示一个span(有七个span — 从A到G),请考虑以下标记: Trace Id = XSpan Id = DClient Sent此标记表示当前span的Trace Id设置为X,Span Id设置为D,此外,还发生了Client Sent事件。 下图显示了span的父—子关系: 用途以下部分参考上图中显示的示例。 使用Zipkin进行分布式跟踪这个例子有七个span,如果你进入Zipkin中的trace,你可以在第二个trace中看到这个数字,如下图所示: 但是,如果选择特定trace,则可以看到四个span,如下图所示: 选择特定trace时,你会看到合并的span,这意味着,如果通过Server Received和Server Sent或Client Received和Client Sent annotations向Zipkin发送了两个span,则它们将显示为单个span。在这种情况下,为什么七个和四个span之间存在差异? 一个span来自http:/start span,它具有Server Received(sr)和Server Sent(ss)annotations。两个span来自从service1到service2的http:/foo端点的RPC调用,Client Sent(cs)和Client Received(cr)事件发生在service1端,Server Received(sr)和Server Sent(ss)事件发生在service2端,这两个span形成一个与RPC调用相关的逻辑span。两个span来自从service2到service3的http:/bar端点的RPC调用,Client Sent(cs)和Client Received(cr)事件发生在service2端,Server Received(sr)和Server Sent(ss)事件发生在service3端,这两个span形成一个与RPC调用相关的逻辑span。两个span来自从service2到service4的http:/baz端点的RPC调用,Client Sent(cs)和Client Received(cr)事件发生在service2端,Server Received(sr)和Server Sent(ss)事件发生在service4端,这两个span形成一个与RPC调用相关的逻辑span。因此,如果我们计算物理span,我们有一个来自http:/start,两个来自service1调用service2,两个来自service2调用service3,两个来自service2调用service4,总之,我们总共有七个span。 从逻辑上讲,我们看到四个总Span的信息,因为我们有一个与传入请求到service1相关的span和三个与RPC调用相关的span。 ...

April 25, 2019 · 3 min · jiezi

Spring-Cloud-参考文档重试失败的请求

重试失败的请求Spring Cloud Netflix提供了多种方式来发出HTTP请求,你可以使用负载均衡的RestTemplate、Ribbon或Feign。无论你如何选择创建HTTP请求,总是有可能请求失败,请求失败时,你可能希望自动重试请求,要在使用Sping Cloud Netflix时这样做,你需要在应用程序的类路径中包含Spring Retry。当存在Spring Retry时,负载均衡的RestTemplates、Feign和Zuul会自动重试任何失败的请求(假设你的配置允许这样做)。 退避策略默认情况下,重试请求时不使用退避策略,如果要配置退避策略,则需要创建类型为LoadBalancedRetryFactory的bean并覆盖给定服务的createBackOffPolicy方法,如以下示例所示: @Configurationpublic class MyConfiguration { @Bean LoadBalancedRetryFactory retryFactory() { return new LoadBalancedRetryFactory() { @Override public BackOffPolicy createBackOffPolicy(String service) { return new ExponentialBackOffPolicy(); } }; }}配置将Ribbon与Spring Retry一起使用时,可以通过配置某些Ribbon属性来控制重试功能,为此,请设置client.ribbon.MaxAutoRetries、client.ribbon.MaxAutoRetriesNextServer和client.ribbon.OkToRetryOnAllOperations属性,有关这些属性的说明,请参阅Ribbon文档。 启用client.ribbon.OkToRetryOnAllOperations包括重试POST请求,由于请求body的缓冲,POST请求会对服务器的资源产生影响。此外,你可能希望在响应中返回某些状态码时重试请求,你可以通过设置clientName.ribbon.retryableStatusCodes属性列出你希望Ribbon客户端重试的响应码,如以下示例所示: clientName: ribbon: retryableStatusCodes: 404,502你还可以创建类型为LoadBalancedRetryPolicy的bean,并实现retryableStatusCode方法以在给定状态码的情况下重试请求。 Zuul你可以通过将zuul.retryable设置为false来关闭Zuul的重试功能,你还可以通过将zuul.routes.routename.retryable设置为false来逐个路由地禁用重试功能。 上一篇:使用Sidecar支持多语言

April 24, 2019 · 1 min · jiezi

Spring-Cloud-参考文档HTTP客户端

HTTP客户端Spring Cloud Netflix会自动为你创建Ribbon、Feign和Zuul使用的HTTP客户端,但是,你也可以根据需要自定义自己的HTTP客户端,为此,如果使用Apache Http Cient,可以创建ClosableHttpClient类型的bean;如果使用OK Http,可以创建OkHttpClient类型的bean。 创建自己的HTTP客户端时,你还负责为这些客户端实施正确的连接管理策略,不正确地执行此操作可能会导致资源管理问题。维护模式的模块将模块置于维护模式意味着Spring Cloud团队将不再向模块添加新功能,将修复bug和安全问题,还将考虑和审查来自社区的小型拉请求。 Spring Cloud团队打算继续支持这些模块至少一年的时间,从Greenwich发行版开始。 以下Spring Cloud Netflix模块和相应的启动器将进入维护模式: spring-cloud-netflix-archaiusspring-cloud-netflix-hystrix-contractspring-cloud-netflix-hystrix-dashboardspring-cloud-netflix-hystrix-streamspring-cloud-netflix-hystrixspring-cloud-netflix-ribbonspring-cloud-netflix-turbine-streamspring-cloud-netflix-turbinespring-cloud-netflix-zuul这不包括Eureka或并发限制模块。上一篇:重试失败的请求

April 24, 2019 · 1 min · jiezi

Spring-Cloud-Eureka-常用配置详解建议收藏

前几天,栈长分享了 《Spring Cloud Eureka 注册中心集群搭建,Greenwich 最新版!》,今天来分享下 Spring Cloud Eureka 常用的一些参数配置及说明。 Spring Boot 的配置参考Java技术栈微信公众号往期 Spring Boot 系列文章,在公众号后台回复:boot。这篇只针对 Spring Cloud Eureka 常用到的配置进行解释。 Spring Cloud Eureka 主要分为下面三个模块的参数: Eureka ServerEureka ClientEureka InstanceEureka ServerEureka Server 的配置参数格式:eureka.server.xxx。 enable-self-preservation表示注册中心是否开启服务的自我保护能力。 什么是自我保护?看这篇文章:SpringCloud Eureka自我保护机制,或者关注Java技术栈微信公众号,在后台回复:cloud。 renewal-percent-threshold表示 Eureka Server 开启自我保护的系数,默认:0.85。 eviction-interval-timer-in-ms表示 Eureka Server 清理无效节点的频率,默认 60000 毫秒(60 秒)。 更多 Eureka Server 参数配置可以看一下这个类: org.springframework.cloud.netflix.eureka.server.EurekaServerConfigBeanEureka InstanceEureka Instance 的配置参数格式:eureka.instance.xxx。 instance-id表示实例在注册中心注册的唯一ID。 prefer-ip-addresstrue:实例以 IP 的形式注册false:实例以机器 HOSTNAME 形式注册lease-expiration-duration-in-seconds表示 Eureka Server 在接收到上一个心跳之后等待下一个心跳的秒数(默认 90 秒),若不能在指定时间内收到心跳,则移除此实例,并禁止此实例的流量。 此值设置太长,即使实例不存在,流量也能路由到该实例此值设置太小,由于网络故障,实例会被取消流量需要设置为至少高于 lease-renewal-interval-in-seconds 的值,不然会被误移除了。 ...

April 24, 2019 · 1 min · jiezi

Spring-Cloud-参考文档使用Sidecar支持多语言

使用Sidecar支持多语言你是否有希望利用Eureka、Ribbon和Config Server的非JVM语言?Spring Cloud Netflix Sidecar的灵感来自Netflix Prana,它包含一个HTTP API,用于获取给定服务的所有实例(按主机和端口)。你还可以通过嵌入式Zuul代理代理服务调用,该代理从Eureka获取其路由条目,可以通过主机查找或Zuul代理直接访问Spring Cloud Config Server,非JVM应用程序应实现健康检查,以便Sidecar可以向Eureka报告应用程序是启动还是关闭。 要在项目中包含Sidecar,请使用组ID为org.springframework.cloud和工件IDspring-cloud-netflix-sidecar的依赖项。 要启用Sidecar,请使用@EnableSidecar创建Spring Boot应用程序,此注解包括@EnableCircuitBreaker、@EnableDiscoveryClient和@EnableZuulProxy,在与非JVM应用程序相同的主机上运行生成的应用程序。 要配置Sidecar,请将sidecar.port和sidecar.health-uri添加到application.yml,sidecar.port属性是非JVM应用程序侦听的端口,这样Sidecar可以正确地向Eureka注册应用程序,sidecar.health-uri是可在非JVM应用程序上访问的URI,它模仿Spring Boot健康指示器,它应该返回类似于以下内容的JSON文档: health-uri-document { "status":"UP"}以下application.yml示例显示了Sidecar应用程序的示例配置: server: port: 5678spring: application: name: sidecarsidecar: port: 8000 health-uri: http://localhost:8000/health.jsonDiscoveryClient.getInstances()方法的API是/hosts/{serviceId},以下针对/hosts/customers的示例响应返回在不同主机上的两个实例: /hosts/customers [ { "host": "myhost", "port": 9000, "uri": "http://myhost:9000", "serviceId": "CUSTOMERS", "secure": false }, { "host": "myhost2", "port": 9000, "uri": "http://myhost2:9000", "serviceId": "CUSTOMERS", "secure": false }]非JVM应用程序(如果sidecar在端口5678上)可以在http://localhost:5678/hosts/{serviceId}访问此API。 Zuul代理自动将Eureka中已知的每个服务的路由添加到/<serviceId>,因此customers服务可在/customers处获得,非JVM应用程序可以访问位于http://localhost:5678/customers的customer服务(假设sidecar正在侦听端口5678)。 如果配置服务器已在Eureka中注册,则非JVM应用程序可以通过Zuul代理访问它,如果ConfigServer的serviceId是configserver且Sidecar在端口5678上,则可以在http://localhost:5678/configserver上访问它。 非JVM应用程序可以利用Config Server返回YAML文档的能力,例如,调用http://sidecar.local.spring.io:5678/configserver/default-master.yml可能会导致YAML文档类似于以下内容: eureka: client: serviceUrl: defaultZone: http://localhost:8761/eureka/ password: passwordinfo: description: Spring Cloud Samples url: https://github.com/spring-cloud-samples要在使用HTTPs时将健康检查请求设置为接受所有证书,请将sidecar.accept-all-ssl-certificates设置为true。 ...

April 23, 2019 · 1 min · jiezi

Spring-Cloud-参考文档外部配置Archaius

外部配置:ArchaiusArchaius是Netflix客户端配置库,它是所有Netflix OSS组件用于配置的库。 Archaius是Apache Commons Configuration项目的扩展,它允许通过轮询源更改或允许源推送更改到客户端来更新配置,Archaius使用Dynamic<Type>Property类作为属性的句柄,如以下示例所示: Archaius Example class ArchaiusTest { DynamicStringProperty myprop = DynamicPropertyFactory .getInstance() .getStringProperty("my.prop"); void doSomething() { OtherClass.someMethod(myprop.get()); }}Archaius有自己的一组配置文件和加载优先级,Spring应用程序通常不应直接使用Archaius,但仍然需要原生配置Netflix工具。 Spring Cloud有一个Spring Environment Bridge,因此Archaius可以从Spring环境中读取属性,此桥接器允许Spring Boot项目使用常规配置工具链,同时让他们按照文档(大多数情况下)配置Netflix工具。 上一篇:客户端负载均衡器:Ribbon

April 23, 2019 · 1 min · jiezi

Spring-Cloud-参考文档客户端负载均衡器Ribbon

客户端负载均衡器:RibbonRibbon是一个客户端负载均衡器,可以让你对HTTP和TCP客户端的行为进行大量控制,Feign已经使用了Ribbon,因此,如果你使用@FeignClient,此部分也适用。 Ribbon中的一个核心概念是命名客户端,每个负载均衡器都是一组组件的一部分,这些组件一起工作以按需联系远程服务器,并且该集合具有你作为应用程序开发人员提供的名称(例如,通过使用@FeignClient注解)。根据需要,Spring Cloud通过使用RibbonClientConfiguration为每个命名客户端创建一个新的集合作为ApplicationContext,这包含(除其他外)ILoadBalancer、RestClient和ServerListFilter。 如何包含Ribbon要在项目中包含Ribbon,使用组ID为org.springframework.cloud和工件ID为spring-cloud-starter-netflix-ribbon。 自定义Ribbon客户端你可以使用<client>.ribbon.*中的外部属性配置Ribbon客户端的某些,这类似于使用原生Netflix API,但你可以使用Spring Boot配置文件,可以在CommonClientConfigKey(ribbon-core的一部分)中将原生选项作为静态字段进行检查。 Spring Cloud还允许你通过使用@RibbonClient声明其他配置(在RibbonClientConfiguration之上)来完全控制客户端,如以下示例所示: @Configuration@RibbonClient(name = "custom", configuration = CustomConfiguration.class)public class TestConfiguration {}在这种情况下,客户端由RibbonClientConfiguration中已有的组件以及CustomConfiguration(后者通常覆盖前者)中的任何组件组成。 CustomConfiguration类必须是@Configuration类,但要注意它不在@ComponentScan中用于主应用程序上下文,否则,它由所有@RibbonClients共享。如果使用@ComponentScan(或@SpringBootApplication),则需要采取措施以避免包含它(例如,你可以将其放在单独的非重叠包中,或指定要在@ComponentScan中显式扫描的包)。下表显示了Spring Cloud Netflix默认为Ribbon提供的bean: Bean类型Bean名称类名称IClientConfigribbonClientConfigDefaultClientConfigImplIRuleribbonRuleZoneAvoidanceRuleIPingribbonPingDummyPingServerList<Server>ribbonServerListConfigurationBasedServerListServerListFilter<Server>ribbonServerListFilterZonePreferenceServerListFilterILoadBalancerribbonLoadBalancerZoneAwareLoadBalancerServerListUpdaterribbonServerListUpdaterPollingServerListUpdater创建其中一种类型的bean并将其置于@RibbonClient配置(例如下面的FooConfiguration)中,可以覆盖所描述的每个bean,如以下示例所示: @Configurationprotected static class FooConfiguration { @Bean public ZonePreferenceServerListFilter serverListFilter() { ZonePreferenceServerListFilter filter = new ZonePreferenceServerListFilter(); filter.setZone("myTestZone"); return filter; } @Bean public IPing ribbonPing() { return new PingUrl(); }}前面示例中的语句将NoOpPing替换为PingUrl,并提供自定义serverListFilter。 自定义所有Ribbon客户端的默认值可以使用@RibbonClients注解并注册默认配置为所有Ribbon客户端提供默认配置,如以下示例所示: @RibbonClients(defaultConfiguration = DefaultRibbonConfig.class)public class RibbonClientDefaultConfigurationTestsConfig { public static class BazServiceList extends ConfigurationBasedServerList { public BazServiceList(IClientConfig config) { super.initWithNiwsConfig(config); } }}@Configurationclass DefaultRibbonConfig { @Bean public IRule ribbonRule() { return new BestAvailableRule(); } @Bean public IPing ribbonPing() { return new PingUrl(); } @Bean public ServerList<Server> ribbonServerList(IClientConfig config) { return new RibbonClientDefaultConfigurationTestsConfig.BazServiceList(config); } @Bean public ServerListSubsetFilter serverListFilter() { ServerListSubsetFilter filter = new ServerListSubsetFilter(); return filter; }}通过设置属性自定义Ribbon客户端从版本1.2.0开始,Spring Cloud Netflix现在支持通过将属性设置为与Ribbon文档兼容来自定义Ribbon客户端。 ...

April 23, 2019 · 1 min · jiezi

Spring-Cloud-参考文档Hystrix超时和Ribbon客户端

Hystrix超时和Ribbon客户端使用包装Ribbon客户端的Hystrix命令时,要确保将Hystrix超时配置为长于配置的Ribbon超时,包括可能进行的任何可能的重试,例如,如果你的Ribbon连接超时为一秒,并且Ribbon客户端可能会重试该请求三次,那么你的Hystrix超时应该略大于三秒。 如何包含Hystrix仪表板要在项目中包含Hystrix仪表板,请使用组ID为org.springframework.cloud和工件ID为spring-cloud-starter-netflix-hystrix-dashboard的启动器。 要运行Hystrix仪表板,请使用@EnableHystrixDashboard注解Spring Boot主类,然后访问/hystrix并将仪表板指向Hystrix客户端应用程序中的单个实例的/hystrix.stream端点。 连接到使用HTTPS的/hystrix.stream端点时,JVM必须信任服务器使用的证书,如果证书不受信任,则必须将证书导入JVM,以便Hystrix仪表板成功连接到流端点。Turbine查看单个实例的Hystrix数据在系统整体运行状况方面不是很有用,Turbine是一个应用程序,它将所有相关的/hystrix.stream端点聚合到一个组合的/turbine.stream中,以便在Hystrix仪表板中使用,从Eureka定位单个实例。运行Turbine需要使用@EnableTurbine注解来注解主类(例如,通过使用spring-cloud-starter-netflix-turbine来设置类路径),Turbine 1 wiki中所有记录的配置属性均适用。唯一的区别是turbine.instanceUrlSuffix不需要前置端口,因为除非turbine.instanceInsertPort=false,否则会自动处理。 默认情况下,Turbine在注册实例上查找/hystrix.stream端点,方法是查找它在Eureka中的hostName和port条目,然后将/hystrix.stream附加到它。如果实例的元数据包含management.port,则使用它来代替/hystrix.stream端点的port值。默认情况下,名为management.port的元数据条目等于management.port配置属性,可以通过以下配置覆盖它:eureka: instance: metadata-map: management.port: ${management.port:8081}turbine.appConfig配置键是turbine用于查找实例的Eureka serviceId列表,然后,turbine流在Hystrix仪表板中使用,其URL类似于以下内容: http://my.turbine.server:8080/turbine.stream?cluster=CLUSTERNAME如果名称是default,则可以省略cluster参数,cluster参数必须与turbine.aggregator.clusterConfig中的条目匹配,从Eureka返回的值是大写的,因此,如果有一个名为customers的应用程序在Eureka注册,则以下示例有效: turbine: aggregator: clusterConfig: CUSTOMERS appConfig: customers如果需要自定义Turbine应使用的集群名称(因为你不希望在turbine.aggregator.clusterConfig配置中存储集群名称),请提供TurbineClustersProvider类型的Bean。 clusterName可以通过turbine.clusterNameExpression中的SPEL表达式进行自定义,其中根用作InstanceInfo的实例,默认值为appName,这意味着Eureka serviceId成为群集键(即,customers的InstanceInfo的appName为CUSTOMERS)。另一个示例是turbine.clusterNameExpression=aSGName,它从AWS ASG名称获取集群名称,以下清单显示了另一个示例: turbine: aggregator: clusterConfig: SYSTEM,USER appConfig: customers,stores,ui,admin clusterNameExpression: metadata['cluster']在前面的示例中,来自四个服务的集群名称是从其元数据映射中提取的,并且应该具有包含SYSTEM和USER的值。 要为所有应用程序使用“default”群集,你需要一个字符串文字表达式(如果它在YAML中,则使用单引号并使用双引号进行转义): turbine: appConfig: customers,stores clusterNameExpression: "'default'"Spring Cloud提供了一个spring-cloud-starter-netflix-turbine,它具有运行Turbine服务器所需的所有依赖关系,要添加Turbine,请创建一个Spring Boot应用程序并使用@EnableTurbine对其进行注解。 默认情况下,Spring Cloud允许Turbine使用主机和端口来允许每个主机、每个集群有多个进程,如果你希望Turbine内置的原生Netflix行为不允许每个主机、每个群集有多个进程(实例ID的键是主机名),请设置turbine.combineHostPort=false。集群端点在某些情况下,了解Turbine中配置了哪些集群可能对其他应用程序有用,为了支持这一点,你可以使用/clusters端点,它将返回所有已配置集群的JSON数组。 GET /clusters [ { "name": "RACES", "link": "http://localhost:8383/turbine.stream?cluster=RACES" }, { "name": "WEB", "link": "http://localhost:8383/turbine.stream?cluster=WEB" }]可以通过将turbine.endpoints.clusters.enabled设置为false来禁用此端点。 Turbine流在某些环境中(例如在PaaS设置中),从所有分布式Hystrix命令中提取指标的经典Turbine模型不起作用,在这种情况下,你可能希望让Hystrix命令将指标推送到Turbine,Spring Cloud通过消息传递实现这一点。要在客户端上实现这一点,请添加依赖spring-cloud-netflix-hystrix-stream和你选择的spring-cloud-starter-stream-*。有关代理以及如何配置客户端凭据的详细信息,请参阅Spring Cloud Stream文档,对于本地代理,这应该是开箱即用的。 在服务器端,创建一个Spring Boot应用程序并使用@EnableTurbineStream注解它,Turbine Stream服务器需要使用Spring Webflux,因此spring-boot-starter-webflux需要包含在你的项目中,默认情况下,在将spring-cloud-starter-netflix-turbine-stream添加到你的应用程序时,会包含spring-boot-starter-webflux。 然后,你可以将Hystrix仪表板指向Turbine Stream Server而不是单独的Hystrix流,如果Turbine Stream在myhost上的端口8989上运行,则将http://myhost:8989放入Hystrix仪表板的流输入字段中,Circuit的前缀是各自的serviceId,后跟一个点(·),然后是Circuit名称。 ...

April 23, 2019 · 1 min · jiezi

Spring Cloud Eureka 你还在让它裸奔吗??

前些天栈长在微信公众号Java技术栈分享了 Spring Cloud Eureka 最新版 实现注册中心的实战教程:Spring Cloud Eureka 注册中心集群搭建,Greenwich 最新版!,成功进入 Eureka 控制台页面。 但控制台首页默认是没有登录认证保护的,打开就能访问,而且你的微服务也能随意注册进去,这样是不安全的,本章栈长将加入登录认证功能,把你的 Eureka 注册中心保护起来。 本文基于最新的 Spring Cloud Greenwich.SR1 以及 Spring Boot 2.1.3 版本进行分享。 1、加入 Spring Security 依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId></dependency>2、添加安全配置 在 application.yml 配置文件中添加以下配置: spring: security: user: name: javastack password: javastack配置用来登录认证的用户名和密码。 3、修改defaultZone 需要在 defaultZone 中添加用户名密码认证。 格式如下: defaultZone: http://username:password@eureka:8761/eureka/参考配置如下: defaultZone: http://javastack:javastack@eureka1:8761/eureka/, http://javastack:javastack@eureka2:8762/eureka/4、禁用CSRF 如上图所示,注册实例出现在 unavailable-replicas 里面。 这是因为加入了安全认证模块后,默认会开启 CSRF 跨站脚本攻击,需要禁用它,添加以下配置即可。 参考:https://cloud.spring.io/sprin...@EnableWebSecurityclass WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().ignoringAntMatchers("/eureka/**"); super.configure(http); }}这样配置后,打开 Eureka 控制台页面会先要跳到登录页面做登录认证才能访问,如下图所示。 ...

April 22, 2019 · 1 min · jiezi

理解Spring Cloud Gateway Filters的执行顺序

本文基于Spring Cloud Gateway 2.1.1.RELEASE。 在讲SCG的Filter的排序问题之前得先比较一下Spring Cloud Gateway在对待Filter的方面与Zuul2有着哪些不同。 Filter的ScopeSCG采用的是Global Filter和Route Filter相结合的方式Zuul2则都是Global FilterSCG所谓Route Filter就是像下面这样的: spring: cloud: gateway: routes: - id: tomcat_route uri: http://tomcat:8080 predicates: - Path=/tomcat/docs filters: - StripPrefix=1 - RemoveRequestHeader=X-Request-Foo上面的StripPrefix和RemoveRequestHeader就是Route Filter,而SCG的Global Filter则是隐式的,无需显式配置,它们会在请求过来的时候被SCG调用。 也就是说你可以配置不同的Route,然后为每个Route配置不同的Route Filter,这一切都是在配置阶段就决定下来的。 而Zuul2则都是Global Filter,因此你得运行时在每个Filter内部自己决定是否要干活,除此之外,发送到Origin(被代理的服务)的url也得你自己设置,下面是一个例子(来自Zuul2 Sample): public class Routes extends HttpInboundSyncFilter { @Override public boolean shouldFilter(HttpRequestMessage httpRequestMessage) { // ... return true; } @Override public HttpRequestMessage apply(HttpRequestMessage request) { // ... // Route healthchecks to the healthcheck endpoint.; context.setEndpoint(ZuulEndPointRunner.PROXY_ENDPOINT_FILTER_NAME); context.setRouteVIP("tomcat"); return request; }}Filter的角色在SCG概念中只有一种Filter(撇开Global和Route的区别),它用代码来区分Pre Filter、Post Filter。在文档中还提到了Routing Filter,其实也是Pre Filter。Zuul2在代码中显示得提供了InboundFilter(负责进来的请求)、OutboundFilter(负责出去的响应)、ProxyEndpoint(负责请求到Origin,串起Inbound和Outbound)。下面是SCG的Pre Filter(裁剪自官方例子12.2 Writing Custom GatewayFilter Factories): ...

April 22, 2019 · 2 min · jiezi

Spring Cloud Alibaba 新版本发布:众多期待内容整合打包加入!

在Nacos 1.0.0 Release之后,Spring Cloud Alibaba也终于发布了最新的版本。该版本距离上一次发布,过去了整整4个月!下面就随我一起看看,这个大家期待已久的版本都有哪些内容值得我们关注。 版本变化之前在《Spring Cloud Alibaba与Spring Boot、Spring Cloud之间不得不说的版本关系》一文中,我有提到过当前版本的Spring Cloud Alibaba还处于孵化器中,没有纳入Spring Cloud的主线版本。所以,我们在使用的时候需要明确Spring Boot、Spring Cloud主版本以及Spring Cloud Alibaba之间的版本关系。 这次的更新,在版本上与我之前文章中说的0.2.2来支持Greenwich有所区别。这里纠正一下,对于Greenwich版本的支持采用了0.9.x的版本号来对应,所以Spring Boot、Spring Cloud、Spring Cloud Alibaba三者之间的准确关系如下表所示: Spring BootSpring CloudSpring Cloud Alibaba2.1.xGreenwich0.9.x2.0.xFinchley0.2.x1.5.xEdgware0.1.x1.5.xDalston0.1.x模块新增这次新版本中,最值得关注的应该就是下面这几个新模块的加入了。之前我说过非常看好Spring Cloud Alibaba,下面新增的几个模块就是主要原因之一。 spring-cloud-alibaba-dubbo对于Dubbo的支持,我觉得对于国内用户来说,是非常有意义的。由于Dubbo在过去一段时间对国内行业的渗透非常广,至今依然有很多团队在使用着这个老牌RPC。但是,由于Dubbo只是一个单纯的RPC框架,它不像Spring Cloud这样,拥有不错的生态,在外围设施的对接支持上做了非常多的努力,使得我们在使用Spring Cloud的时候,可以很快的体系化我们的基础设施。 现在,Spring Cloud Alibaba将Dubbo融入Spring Cloud体系,可以让其一起享受Spring Cloud生态的各种便利。对于原来的Dubbo用户来说,该模块的加入为原Dubbo用户拥抱Spring Cloud生态提供了非常好的支持,可以大大减少用户自己融入需要做的扩展工作量。 为什么说这个模块好呢?看看下面几个与Spring Cloud生态融合的重要功能: 支持所有Spring Cloud的注册中心实现,包括Spring Cloud Alibaba中整合的Nacos。也就是说,以前我们自己在整合Spring Cloud和Dubbo来使用的时候,如果不去为Dubbo扩展注册中心,那么就不得不采用Eureka + Zookeeper同时存在的复杂架构。现在,在这个模块的帮助下,注册中心就可以得到统一。支持RestTemplate,开发者只需要在定义RestTemplate的@Bean注解上搭配使用@DubboTransported注解,就能将这个客户端直接支持Dubbo调用。支持Feign,开发者只需要在@FeignClient注解上搭配使用@DubboTransported,就能将Feign客户端变成Dubbo的客户端。这样的设计,对于传统Spring Cloud用户来说,是不是也非常容易接受呢?如果您的团队还在坚持使用Dubbo,又很想引入Spring Cloud,那么不妨移步来尝试一下Spring Cloud Alibaba的最新版本吧! 注意:该模块没有发布到0.1.2中,所以只有0.2.2和0.9.0中才拥有。也就是说,仅支持Spring Boot 2.x和Spring Cloud Finchley版和Greenwich版。 spring-cloud-alibaba-seataSeata是Alibaba与蚂蚁金融共建的一个开源分布式事务解决方案。通过该模块的加入,可以非常方便的在Spring MVC、RestTemplate、FeignClient的调用中传递事务上下文,同时也支持与Hystrix、Sentinel的联合使用。全方面的打通Spring Cloud生态的分布式事务场景。 这个框架我还没有深入的研究过,后续我也会将这部分内容的学习写到《Spring Cloud Alibaba基础教程》中与大家分享心得与经验。如果对这个感兴趣的,记得关注我哟。 spring-cloud-alibaba-sentinel-zuul在该模块中定义了一些为Zuul定制的过滤器:SentinelPreFilter、SentinelPostFilter、SentinelErrorFilter。通过它们来为Zuul实现网关层的请求流量控制。用户可以通过spring.cloud.sentinel.zuul.参数来配置这些Sentinel过滤器。 spring-cloud-alibaba-smsSMS是阿里云的商业化产品。所以该模块类之前的SchedulerX模块,OSS模块类似,主要为了更方便的整合使用而存在。如果是Spring Cloud用户,同时也是阿里云这些产品的用户,那么直接使用还是非常方便的。但是如果这两个条件都不满足,那么可以忽略之。 模块升级在该版本中对于一些重要的基础设施模块也做了大幅度的版本升级,包括: ...

April 22, 2019 · 1 min · jiezi

微服务架构必备—mica,开源 pro 全部功能

介绍mica(云母)寓意为云服务的核心,使得云服务开发更加方便快捷。在跟如梦技术的成员沟通之后我们打算开源 mica-pro 全部代码,现已经推送到 github 和 码云(gitee)。 新开源的组件mica-cloudFeign 自动降级、header 透传、版本处理,结合 mica-auto 自动化配置。RestTemplate自动配置,基于okhttp增强,添加请求日志和Header传递。hystrix 熔断器增强,支持 header 透传、当前用户获取和透传。本地开发不注册到 Eureka 服务中,避免影响联调环境。Apollo Properties 配置刷新。mica-plus-error-catch未知异常收集到 spring-cloud-stream 中,方便统一处理。mica-plus-redisredis cache name # 自动配置超时时间。mica-plus-mongomongo 复杂 tree 和 jsonNode 转换处理。组件图谱(新)我们来看看新的功能全景图。 最佳实践pigx 宇宙最强微服务(架构师必备):https://pig4cloud.combladex 完整的线上解决方案(企业生产必备):https://bladex.vip相关链接示例项目:https://github.com/lets-mica/mica-examplemica 源码 Github:https://github.com/lets-micamica 源码 Gitee(码云):https://gitee.com/596392912/mica文档地址(官网):https://www.dreamlu.net/docs/文档地址(语雀-可关注订阅):https://www.yuque.com/dreamlu/mica开源推荐Avue 一款基于vue可配置化的神奇框架:https://gitee.com/smallweigit/avuepig 宇宙最强微服务(架构师必备):https://gitee.com/log4j/pigSpringBlade 完整的线上解决方案(企业开发必备):https://gitee.com/smallc/SpringBladeIJPay 支付SDK让支付触手可及:https://gitee.com/javen205/IJPay关注我们 扫描上面二维码,更多精彩内容每天推荐!

April 22, 2019 · 1 min · jiezi

Spring Cloud Alibaba基础教程:Sentinel使用Apollo存储规则

上一篇我们介绍了如何通过Nacos的配置功能来存储限流规则。Apollo是国内用户非常多的配置中心,所以,今天我们继续说说Spring Cloud Alibaba Sentinel中如何将流控规则存储在Apollo中。 使用Apollo存储限流规则Sentinel自身就支持了多种不同的数据源来持久化规则配置,目前包括以下几种方式: 文件配置Nacos配置ZooKeeper配置Apollo配置本文我们就来一起动手尝试一下,如何使用Apollo来存储限流规则。 准备工作下面我们将同时使用到Apollo和Sentinel Dashboard,所以可以先把Apollo和Sentinel Dashboard启动起来。 如果还没入门Sentinel Dashboard可以通过文末的系列目录先学习之前的内容。Apollo的话相对复杂一些,这里不做详细介绍了,如果还没有接触过Apollo的读者可以查看其官方文档进一步学习。 应用配置第一步:在Spring Cloud应用的pom.xml中引入Spring Cloud Alibaba的Sentinel模块和Apollo存储扩展: <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-datasource-apollo</artifactId> <version>1.4.0</version> </dependency></dependencies>第二步:在Spring Cloud应用中配置的服务信息,在resource目录下,创建apollo-env.properties文件,内容样例: local.meta=http://192.168.0.201:8080dev.meta=http://192.168.0.202:8080这里需要了解Apollo对多环境的配置,这里设置的是每个环境不同的配置服务地址,读者需要根据自己的实际情况修改。 第三步:在Spring Cloud应用中添加配置信息: spring.application.name=sentinel-datasource-apolloserver.port=8002# apollo configapp.id=${spring.application.name}# sentinel dashboardspring.cloud.sentinel.transport.dashboard=localhost:8080# sentinel datasource apollospring.cloud.sentinel.datasource.ds.apollo.namespaceName=applicationspring.cloud.sentinel.datasource.ds.apollo.flowRulesKey=sentinel.flowRulesapp.id:Apollo中的创建的项目名称,这里采用spring.application.name参数的引用,从而达到服务名与配置项目名一致的效果spring.cloud.sentinel.transport.dashboard:sentinel dashboard的访问地址,根据上面准备工作中启动的实例配置spring.cloud.sentinel.datasource.ds.apollo.namespaceName:Apollo的空间名spring.cloud.sentinel.datasource.ds.apollo.flowRulesKey:配置规则的key名称关于Apollo相关配置的对应关系可见下图所示: 第四步:创建应用主类,并提供一个rest接口,比如: @EnableApolloConfig@SpringBootApplicationpublic class TestApplication { public static void main(String[] args) { SpringApplication.run(TestApplication.class, args); } @Slf4j @RestController static class TestController { @GetMapping("/hello") public String hello() { return "didispace.com"; } }}其中@EnableApolloConfig注解是开启Apollo的配置加载功能。 ...

April 21, 2019 · 1 min · jiezi

Spring Cloud Data Flow Docker Compose问题解决

研究Spring Cloud Data Flow时,启动Docker Compose的时候发现报错如下:dataflow-server | java.lang.RuntimeException: Error reading from URL [http://bit.ly/Dearborn-SR1-task-applications-maven]dataflow-server | at org.springframework.cloud.dataflow.registry.service.DefaultAppRegistryService.loadProperties(DefaultAppRegistryService.java:284)dataflow-server | at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193)dataflow-server | at java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:948)dataflow-server | at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)dataflow-server | at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)dataflow-server | at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708)dataflow-server | at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)dataflow-server | at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:499)dataflow-server | at org.springframework.cloud.dataflow.registry.service.DefaultAppRegistryService.importAll(DefaultAppRegistryService.java:297)dataflow-server | at org.springframework.cloud.dataflow.registry.service.DefaultAppRegistryService$$FastClassBySpringCGLIB$$a8bae4.invoke(<generated>)dataflow-server | at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)dataflow-server | at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:749)dataflow-server | at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)dataflow-server | at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:294)dataflow-server | at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:98)dataflow-server | at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)dataflow-server | at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:688…解决方案,修改docker-compose.yml中bit.ly相关的url为bit.ly.xiaoyou.biz, 同时手工下载相应的文件放到里边。

April 19, 2019 · 1 min · jiezi

Spring Cloud 参考文档(声明式REST客户端:Feign)

声明式REST客户端:FeignFeign是一个声明式的Web服务客户端,它使编写Web服务客户端变得更容易,要使用Feign,请创建一个接口并对其进行注解,它具有可插拔的注解支持,包括Feign注解和JAX-RS注解,Feign还支持可插拔编码器和解码器。Spring Cloud增加了对Spring MVC注解的支持,并使用了Spring Web中默认使用的相同HttpMessageConverters,Spring Cloud集成了Ribbon和Eureka,在使用Feign时提供负载均衡的http客户端。如何包含Feign要在项目中包含Feign,请使用包含组名为org.springframework.cloud和工件名为spring-cloud-starter-openfeign的启动器。spring boot应用示例@SpringBootApplication@EnableFeignClientspublic class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); }}StoreClient.java@FeignClient(“stores”)public interface StoreClient { @RequestMapping(method = RequestMethod.GET, value = “/stores”) List<Store> getStores(); @RequestMapping(method = RequestMethod.POST, value = “/stores/{storeId}”, consumes = “application/json”) Store update(@PathVariable(“storeId”) Long storeId, Store store);}在@FeignClient注解中,String值(上面的“stores”)是一个任意客户端名称,用于创建Ribbon负载均衡器,你还可以使用url属性指定URL(绝对值或仅指定主机名),应用程序上下文中bean的名称是接口的完全限定名称,要指定自己的别名值,可以使用@FeignClient注解的qualifier值。上面的Ribbon客户端将想要发现“stores”服务的物理地址,如果你的应用程序是Eureka客户端,那么它将解析Eureka服务注册表中的服务,如果你不想使用Eureka,只需在外部配置中配置服务器列表。覆盖Feign默认值Spring Cloud的Feign支持的核心概念是命名客户端,每个feign客户端都是一个组件集成的一部分,这些组件协同工作以按需联系远程服务器,并且集成有一个名称,作为使用@FeignClient注解的应用程序开发人员可以使用这个名称。Spring Cloud使用FeignClientsConfiguration按需为每个命名客户端创建一个新的集成作为ApplicationContext,这包含(除其他外)feign.Decoder、feign.Encoder和feign.Contract,可以使用@FeignClient注解的contextId属性覆盖该集成的名称。Spring Cloud允许你通过使用@FeignClient声明其他配置(在FeignClientsConfiguration之上)来完全控制feign客户端,例如:@FeignClient(name = “stores”, configuration = FooConfiguration.class)public interface StoreClient { //..}在这种情况下,客户端由FeignClientsConfiguration中已有的组件以及FooConfiguration中的任何组件组成(后者将覆盖前者)。FooConfiguration不需要使用@Configuration注解,但是,如果是,则注意将其从任何包含此配置的@ComponentScan中排除,因为它将成为feign.Decoder、feign.Encoder、feign.Contract等的默认源。这可以通过将其放在任何@ComponentScan或@SpringBootApplication的单独的非重叠包中来避免,也可以在@ComponentScan中明确排除。现在不推荐使用serviceId属性,而是使用name属性。使用@FeignClient注解的contextId属性除了更改ApplicationContext集成的名称,它将覆盖客户端名称的别名,它将用作为该客户端创建的配置bean名称的一部分。以前,使用url属性不需要name属性,现在需要使用name。name和url属性支持占位符。@FeignClient(name = “${feign.name}”, url = “${feign.url}")public interface StoreClient { //..}Spring Cloud Netflix默认为feign(BeanType beanName:ClassName)提供以下bean:Decoder feignDecoder:ResponseEntityDecoder(包装SpringDecoder)Encoder feignEncoder:SpringEncoderLogger feignLogger:Slf4jLoggerContract feignContract:SpringMvcContractFeign.Builder feignBuilder:HystrixFeign.BuilderClient feignClient:如果启用了Ribbon,则它是LoadBalancerFeignClient,否则使用默认的feign客户端。可以通过将feign.okhttp.enabled或feign.httpclient.enabled分别设置为true,并将它们放在类路径上来使用OkHttpClient和ApacheHttpClient feign客户端,你可以通过在使用Apache时提供ClosableHttpClient或在使用OK HTTP时提供OkHttpClient的bean来定制使用的HTTP客户端。Spring Cloud Netflix默认情况下不为feign提供以下bean,但仍然从应用程序上下文中查找这些类型的bean以创建feign客户端:Logger.LevelRetryerErrorDecoderRequest.OptionsCollection<RequestInterceptor>SetterFactory创建其中一种类型的bean并将其放在@FeignClient配置中(如上面的FooConfiguration)允许你覆盖所描述的每个bean,例如:@Configurationpublic class FooConfiguration { @Bean public Contract feignContract() { return new feign.Contract.Default(); } @Bean public BasicAuthRequestInterceptor basicAuthRequestInterceptor() { return new BasicAuthRequestInterceptor(“user”, “password”); }}这将使用feign.Contract.Default替换SpringMvcContract,并将RequestInterceptor添加到RequestInterceptor的集合中。@FeignClient也可以使用配置属性进行配置。application.ymlfeign: client: config: feignName: connectTimeout: 5000 readTimeout: 5000 loggerLevel: full errorDecoder: com.example.SimpleErrorDecoder retryer: com.example.SimpleRetryer requestInterceptors: - com.example.FooRequestInterceptor - com.example.BarRequestInterceptor decode404: false encoder: com.example.SimpleEncoder decoder: com.example.SimpleDecoder contract: com.example.SimpleContract可以以与上述类似的方式在@EnableFeignClients属性defaultConfiguration中指定默认配置,不同之处在于此配置将适用于所有feign客户端。如果你更喜欢使用配置属性来配置所有@FeignClient,则可以使用default feign名称创建配置属性。application.ymlfeign: client: config: default: connectTimeout: 5000 readTimeout: 5000 loggerLevel: basic如果我们同时创建@Configuration bean和配置属性,配置属性将获胜,它将覆盖@Configuration值,但是,如果要将优先级更改为@Configuration,则可以将feign.client.default-to-properties更改为false。如果需要在RequestInterceptor中使用ThreadLocal绑定变量,则需要将Hystrix的线程隔离策略设置为“SEMAPHORE”或在Feign中禁用Hystrix。application.yml# To disable Hystrix in Feignfeign: hystrix: enabled: false# To set thread isolation to SEMAPHOREhystrix: command: default: execution: isolation: strategy: SEMAPHORE如果我们想要创建具有相同名称或URL的多个feign客户端,以便它们指向同一服务器但每个都具有不同的自定义配置,那么我们必须使用@FeignClient的contextId属性,以避免这些配置bean的名称冲突。@FeignClient(contextId = “fooClient”, name = “stores”, configuration = FooConfiguration.class)public interface FooClient { //..}@FeignClient(contextId = “barClient”, name = “stores”, configuration = BarConfiguration.class)public interface BarClient { //..}手动创建Feign客户端在某些情况下,可能需要以使用上述方法无法实现的方式自定义Feign客户端,在这种情况下,你可以使用Feign Builder API创建客户端。下面是一个示例,它创建两个具有相同接口的Feign客户端,但使用单独的请求拦截器配置每个客户端。@Import(FeignClientsConfiguration.class)class FooController { private FooClient fooClient; private FooClient adminClient; @Autowired public FooController(Decoder decoder, Encoder encoder, Client client, Contract contract) { this.fooClient = Feign.builder().client(client) .encoder(encoder) .decoder(decoder) .contract(contract) .requestInterceptor(new BasicAuthRequestInterceptor(“user”, “user”)) .target(FooClient.class, “http://PROD-SVC”); this.adminClient = Feign.builder().client(client) .encoder(encoder) .decoder(decoder) .contract(contract) .requestInterceptor(new BasicAuthRequestInterceptor(“admin”, “admin”)) .target(FooClient.class, “http://PROD-SVC”); }}在上面的示例中,FeignClientsConfiguration.class是Spring Cloud Netflix提供的默认配置。PROD-SVC是客户端将向其发出请求的服务的名称。Feign Contract对象定义了哪些注解和值在接口上是有效的,自动装配的Contract bean提供对SpringMVC注解的支持,而不是默认的Feign原生注解。Feign Hystrix支持如果Hystrix位于类路径并且feign.hystrix.enabled=true,则Feign将使用断路器包装所有方法,返回com.netflix.hystrix.HystrixCommand也可用,这允许你使用反应模式(通过调用.toObservable()或.observe()或异步使用(通过调用.queue())。要在每个客户端的基础上禁用Hystrix支持,请创建一个带有“prototype”范围的vanilla Feign.Builder,例如:@Configurationpublic class FooConfiguration { @Bean @Scope(“prototype”) public Feign.Builder feignBuilder() { return Feign.builder(); }}在Spring Cloud Dalston发布之前,如果Hystrix在类路径上,Feign会默认将所有方法包装在断路器中,Spring Cloud Dalston中更改了此默认行为,转而采用了选择加入方法。Feign Hystrix FallbackHystrix支持回退的概念:在电路打开或出现错误时执行的默认代码路径,要为给定的@FeignClient启用回退,请将fallback属性设置为实现回退的类名,你还需要将实现声明为Spring bean。@FeignClient(name = “hello”, fallback = HystrixClientFallback.class)protected interface HystrixClient { @RequestMapping(method = RequestMethod.GET, value = “/hello”) Hello iFailSometimes();}static class HystrixClientFallback implements HystrixClient { @Override public Hello iFailSometimes() { return new Hello(“fallback”); }}如果需要访问产生回退触发器的原因,可以使用@FeignClient中的fallbackFactory属性。@FeignClient(name = “hello”, fallbackFactory = HystrixClientFallbackFactory.class)protected interface HystrixClient { @RequestMapping(method = RequestMethod.GET, value = “/hello”) Hello iFailSometimes();}@Componentstatic class HystrixClientFallbackFactory implements FallbackFactory<HystrixClient> { @Override public HystrixClient create(Throwable cause) { return new HystrixClient() { @Override public Hello iFailSometimes() { return new Hello(“fallback; reason was: " + cause.getMessage()); } }; }}在Feign中实现回退以及Hystrix回退如何工作都有一定的限制,返回com.netflix.hystrix.HystrixCommand和rx.Observable的方法目前不支持回退。Feign和@Primary当与Hystrix回退一起使用Feign时,ApplicationContext中有相同类型的多个bean,这将导致@Autowired无法工作,因为没有一个明确的bean或一个标记为primary的bean。为了解决这个问题,Spring Cloud Netflix将所有Feign实例标记为@Primary,因此Spring Framework将知道要注入哪个bean,在某些情况下,这可能并不理想,要关闭此行为,请将@FeignClient的primary属性设置为false。@FeignClient(name = “hello”, primary = false)public interface HelloClient { // methods here}Feign继承支持Feign通过单继承接口支持样板api,这允许将通用操作分组为方便的基本接口。UserService.javapublic interface UserService { @RequestMapping(method = RequestMethod.GET, value ="/users/{id}”) User getUser(@PathVariable(“id”) long id);}UserResource.java@RestControllerpublic class UserResource implements UserService {}UserClient.javapackage project.user;@FeignClient(“users”)public interface UserClient extends UserService {}通常不建议在服务器和客户端之间共享接口,它引入了紧耦合,并且实际上也不能以其当前形式使用Spring MVC(方法参数映射不会被继承)。Feign请求/响应压缩你可以考虑为你的Feign请求启用请求或响应GZIP压缩,你可以通过启用以下属性之一来执行此操作:feign.compression.request.enabled=truefeign.compression.response.enabled=trueFeign请求压缩为你提供类似于你为Web服务器设置的设置:feign.compression.request.enabled=truefeign.compression.request.mime-types=text/xml,application/xml,application/jsonfeign.compression.request.min-request-size=2048通过这些属性,你可以选择压缩介质类型和最小请求阈值长度。Feign记录日志为每个创建的Feign客户端创建一个记录器,默认情况下,记录器的名称是用于创建Feign客户端的接口的完整类名,Feign日志记录仅响应DEBUG级别。application.ymllogging.level.project.user.UserClient: DEBUG你可以为每个客户端配置Logger.Level对象,告诉Feign要记录多少,选择是:NONE,没有记录(DEFAULT)。BASIC,仅记录请求方法和URL以及响应状态代码和执行时间。HEADERS,记录基本信息以及请求和响应headers。FULL,记录请求和响应的headers、body和元数据。例如,以下内容将Logger.Level设置为FULL:@Configurationpublic class FooConfiguration { @Bean Logger.Level feignLoggerLevel() { return Logger.Level.FULL; }}Feign @QueryMap支持OpenFeign @QueryMap注解为POJO提供了支持,可用作GET参数映射,不幸的是,默认的OpenFeign QueryMap注解与Spring不兼容,因为它缺少value属性。Spring Cloud OpenFeign提供等效的@SpringQueryMap注解,用于将POJO或Map参数注解为查询参数映射。例如,Params类定义参数param1和param2:// Params.javapublic class Params { private String param1; private String param2; // [Getters and setters omitted for brevity]}以下feign客户端使用@SpringQueryMap注解使用Params类:@FeignClient(“demo”)public class DemoTemplate { @GetMapping(path = “/demo”) String demoEndpoint(@SpringQueryMap Params params);} ...

April 18, 2019 · 2 min · jiezi

Spring Cloud 参考文档(服务发现:Eureka客户端)

服务发现:Eureka客户端服务发现是基于微服务架构的关键原理之一,尝试手动配置每个客户端或某种形式的约定可能很难做到并且可能很脆弱,Eureka是Netflix Service Discovery服务器和客户端,服务器可以被配置和部署为高可用性,每个服务器将注册服务的状态复制到其他服务器。如何包含Eureka客户端要在项目中包含Eureka Client,请使用组ID为org.springframework.cloud和工件ID为spring-cloud-starter-netflix-eureka-client的启动器。注册Eureka当客户端向Eureka注册时,它会提供有关自身的元数据 — 例如主机、端口、健康指示器URL、主页和其他详细信息,Eureka从属于服务的每个实例接收心跳消息,如果心跳故障超过可配置的时间表,则通常会从注册表中删除该实例。以下示例显示了最小的Eureka客户端应用程序:@SpringBootApplication@RestControllerpublic class Application { @RequestMapping("/") public String home() { return “Hello world”; } public static void main(String[] args) { new SpringApplicationBuilder(Application.class).web(true).run(args); }}请注意,前面的示例显示了一个普通的Spring Boot应用程序,通过在类路径上使用spring-cloud-starter-netflix-eureka-client,你的应用程序会自动向Eureka Server注册,找到Eureka服务器需要进行配置,如以下示例所示:application.ymleureka: client: serviceUrl: defaultZone: http://localhost:8761/eureka/在前面的示例中,“defaultZone”是一个神奇的字符串回退值,它为任何不表示首选项的客户端提供服务URL(换句话说,它是一个有用的默认值)。默认应用程序名称(即服务ID)、虚拟主机和非安全端口(取自Environment)分别是${spring.application.name}、${spring.application.name}和${server.port}。在类路径上使用spring-cloud-starter-netflix-eureka-client使应用程序成为Eureka“实例”(即,它自己注册)和“客户端”(它可以查询注册表以查找其他服务),实例行为由eureka.instance.*配置键驱动,但如果你确保应用程序具有spring.application.name的值(这是Eureka服务ID或VIP的默认值),则默认值很好。有关可配置选项的更多详细信息,请参阅EurekaInstanceConfigBean和EurekaClientConfigBean。要禁用Eureka Discovery Client,可以将eureka.client.enabled设置为false。使用Eureka Server进行身份验证如果其中一个eureka.client.serviceUrl.defaultZone URL中嵌入了凭据,则会自动将HTTP基本身份验证添加到你的eureka客户端(curl样式,如下所示:http://user:password@localhost:8761/eureka)。对于更复杂的需求,你可以创建一个类型为DiscoveryClientOptionalArgs的@Bean并将ClientFilter实例注入其中,所有这些都应用于从客户端到服务器的调用。由于Eureka的限制,无法支持每个服务器基本身份验证凭据,因此仅使用找到的第一个集合。状态页面和健康指示器Eureka实例的状态页面和健康指示器分别默认为/info和/health,它们是Spring Boot Actuator应用程序中有用端点的默认位置,如果使用非默认上下文路径或servlet路径(例如server.servletPath=/custom),则需要更改这些,即使对于Actuator应用程序也是如此,以下示例显示了两个设置的默认值:application.ymleureka: instance: statusPageUrlPath: ${server.servletPath}/info healthCheckUrlPath: ${server.servletPath}/health这些链接显示在客户端使用的元数据中,并在某些情况下用于决定是否向你的应用程序发送请求,因此如果它们准确,则会很有帮助。在Dalston中,还需要在更改管理上下文路径时设置状态和健康检查URL,从Edgware开始删除此要求。注册安全应用程序如果你的应用程序想通过HTTPS联系,你可以在EurekaInstanceConfig中设置两个标志:eureka.instance.[nonSecurePortEnabled]=[false]eureka.instance.[securePortEnabled]=[true]这样做会使Eureka发布显示对安全通信明确偏好的实例信息,对于以这种方式配置的服务,Spring Cloud DiscoveryClient始终返回以https开头的URI,同样,当以这种方式配置服务时,Eureka(本机)实例信息具有安全的健康检查URL。由于Eureka在内部工作的方式,它仍然会发布状态和主页的非安全URL,除非你也明确地覆盖这些URL,你可以使用占位符来配置eureka实例URL,如以下示例所示:application.ymleureka: instance: statusPageUrl: https://${eureka.hostname}/info healthCheckUrl: https://${eureka.hostname}/health homePageUrl: https://${eureka.hostname}/请注意,${eureka.hostname}是一个原生占位符,仅在更高版本的Eureka中可用,你也可以使用Spring占位符实现相同的功能 — 例如,使用${eureka.instance.hostName}。如果你的应用程序在代理后面运行,并且SSL终止在代理中(例如,如果你在Cloud Foundry或其他平台中作为服务运行),然后,你需要确保代理“转发” headers被应用程序拦截和处理。如果嵌入在Spring Boot应用程序中的Tomcat容器具有针对X-Forwarded-* headers的显式配置,则会自动发生,应用程序呈现到自身的链接错误(错误的主机、端口或协议)表明你的配置错误。Eureka的健康检查默认情况下,Eureka使用客户端心跳来确定客户端是否已启动,除非另有说明,否则Discovery Client不会根据Spring Boot Actuator传播应用程序的当前健康检查状态,因此,在成功注册后,Eureka始终宣布应用程序处于“UP”状态,通过启用Eureka健康检查可以更改此行为,从而将应用程序状态传播到Eureka。因此,每个其他应用程序都不会向“UP”以外的状态下的应用程序发送流量,以下示例显示如何为客户端启用健康检查:application.ymleureka: client: healthcheck: enabled: trueeureka.client.healthcheck.enabled=true应该只在application.yml中设置,在bootstrap.yml中设置值会导致不良副作用,例如在Eureka中以UNKNOWN状态注册。如果你需要更多控制健康检查,请考虑实现自己的com.netflix.appinfo.HealthCheckHandler。实例和客户端的Eureka元数据值得花一些时间了解Eureka元数据的工作原理,因此你可以在平台中使用它,有用于信息的标准元数据,如主机名、IP地址、端口号、状态页和健康检查。这些发布在服务注册表中,客户端使用它们以直接的方式联系服务,可以将额外元数据添加到eureka.instance.metadataMap中的实例注册中,并且可以在远程客户端中访问此元数据。通常,除非客户端了解元数据的含义,否则额外元数据不会更改客户端的行为,本文稍后将介绍几种特殊情况,其中Spring Cloud已经为元数据映射赋予了意义。在Cloud Foundry上使用EurekaCloud Foundry有一个全局路由器,因此同一个应用程序的所有实例都具有相同的主机名(具有类似架构的其他PaaS解决方案),这不一定是使用Eureka的障碍。但是,如果你使用路由器(建议甚至强制使用,具体取决于你的平台的设置方式),你需要明确设置主机名和端口号(安全或非安全),以便他们使用路由器。你可能还希望使用实例元数据,以便区分客户端上的实例(例如,在自定义负载均衡器中),默认情况下,eureka.instance.instanceId是vcap.application.instance_id,如以下示例所示:application.ymleureka: instance: hostname: ${vcap.application.uris[0]} nonSecurePort: 80根据在Cloud Foundry实例中设置安全规则的方式,你可以注册并使用主机VM的IP地址进行直接服务到服务调用,Pivotal Web Services(PWS)尚未提供此功能。在AWS上使用Eureka如果计划将应用程序部署到AWS云,则必须将Eureka实例配置为支持AWS,你可以通过自定义EurekaInstanceConfigBean来执行此操作,如下所示:@Bean@Profile("!default")public EurekaInstanceConfigBean eurekaInstanceConfig(InetUtils inetUtils) { EurekaInstanceConfigBean b = new EurekaInstanceConfigBean(inetUtils); AmazonInfo info = AmazonInfo.Builder.newBuilder().autoBuild(“eureka”); b.setDataCenterInfo(info); return b;}更改Eureka实例ID一个vanilla Netflix Eureka实例注册的ID等于其主机名(即每个主机只有一个服务),Spring Cloud Eureka提供合理的默认值,定义如下:${spring.cloud.client.hostname}:${spring.application.name}:${spring.application.instance_id:${server.port}}}一个例子是myhost:myappname:8080。通过使用Spring Cloud,你可以通过在eureka.instance.instanceId中提供唯一标识符来覆盖此值,如以下示例所示:application.ymleureka: instance: instanceId: ${spring.application.name}:${vcap.application.instance_id:${spring.application.instance_id:${random.value}}}使用前面示例中显示的元数据和部署在localhost上的多个服务实例,将随机值插入其中以使实例唯一,在Cloud Foundry中,vcap.application.instance_id会自动填充在Spring Boot应用程序中,因此不需要随机值。使用EurekaClient一旦你拥有一个发现客户端的应用程序,就可以使用它从Eureka Server发现服务实例,一种方法是使用原生com.netflix.discovery.EurekaClient(而不是Spring Cloud DiscoveryClient),如以下示例所示:@Autowiredprivate EurekaClient discoveryClient;public String serviceUrl() { InstanceInfo instance = discoveryClient.getNextServerFromEureka(“STORES”, false); return instance.getHomePageUrl();}不要在@PostConstruct方法或@Scheduled方法中使用EurekaClient(或者可能尚未启动ApplicationContext的任何地方),它在SmartLifecycle中初始化(phase=0),因此最早可以依赖它的是另一个具有更高阶段的SmartLifecycle。没有Jersey的EurekaClient默认情况下,EurekaClient使用Jersey进行HTTP通信,如果你希望避免来自Jersey的依赖项,则可以将其从依赖项中排除,Spring Cloud基于Spring RestTemplate自动配置传输客户端,以下示例显示Jersey被排除在外:<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> <exclusions> <exclusion> <groupId>com.sun.jersey</groupId> <artifactId>jersey-client</artifactId> </exclusion> <exclusion> <groupId>com.sun.jersey</groupId> <artifactId>jersey-core</artifactId> </exclusion> <exclusion> <groupId>com.sun.jersey.contribs</groupId> <artifactId>jersey-apache-client4</artifactId> </exclusion> </exclusions></dependency>原生Netflix EurekaClient的替代品你无需使用原始Netflix EurekaClient,此外,在某种包装后面使用它通常更方便,Spring Cloud通过逻辑Eureka服务标识符(VIP)而不是物理URL支持Feign(REST客户端构建器)和Spring RestTemplate。要使用固定的物理服务器列表配置Ribbon,可以将<client>.ribbon.listOfServers设置为以逗号分隔的物理地址(或主机名)列表,其中<client>是客户端的ID。你还可以使用org.springframework.cloud.client.discovery.DiscoveryClient,它为发现客户端提供简单的API(不特定于Netflix),如以下示例所示:@Autowiredprivate DiscoveryClient discoveryClient;public String serviceUrl() { List<ServiceInstance> list = discoveryClient.getInstances(“STORES”); if (list != null && list.size() > 0 ) { return list.get(0).getUri(); } return null;}为什么注册服务这么慢?作为实例还涉及到注册表的定期心跳(通过客户端的serviceUrl),默认持续时间为30秒,在实例、服务器和客户端在其本地缓存中都具有相同的元数据之前,客户端无法发现服务(因此可能需要3个心跳)。你可以通过设置eureka.instance.leaseRenewalIntervalInSeconds来更改周期,将其设置为小于30的值会加快使客户端连接到其他服务的过程,在生产中,最好坚持使用默认值,因为服务器中的内部计算会对租约续期做出假设。Zones如果你已将Eureka客户端部署到多个区域,你可能希望这些客户端在尝试另一个区域中的服务之前使用同一区域内的服务,要进行此设置,你需要正确配置Eureka客户端。首先,你需要确保将Eureka服务器部署到每个区域,并确保它们彼此对等,有关详细信息,请参阅有关Zones和Regions的部分。接下来,你需要告诉Eureka你的服务所在的区域,你可以使用metadataMap属性执行此操作,例如,如果将service 1部署到zone 1和zone 2,则需要在service 1中设置以下Eureka属性:zone 1的service 1eureka.instance.metadataMap.zone = zone1eureka.client.preferSameZoneEureka = truezone 2的service 1eureka.instance.metadataMap.zone = zone2eureka.client.preferSameZoneEureka = true ...

April 18, 2019 · 1 min · jiezi

Spring Cloud Alibaba基础教程:Sentinel使用Nacos存储规则

通过上一篇《使用Sentinel实现接口限流》的介绍,相信大家对Sentinel已经有了初步的认识。在Spring Cloud Alibaba的整合封装之下,接口限流这件事情可以非常轻易的整合到我们的Spring Cloud应用中。但是,通过上篇的整合,依然还不能完美的满足我们日常的生产需求。其中,非常重要的一点就是限流规则的持久化问题。不少细心的读者也在留言中提出了Dashboard中设置的限流规则在应用重启之后就丢失了的问题。那么,接下来我们就来说说Sentinel的规则持久化如何实现。使用Nacos存储限流规则Sentinel自身就支持了多种不同的数据源来持久化规则配置,目前包括以下几种方式:文件配置Nacos配置ZooKeeper配置Apollo配置本文我们就来一起动手尝试一下,使用Spring Cloud Alibaba的中整合的配置中心Nacos存储限流规则。准备工作下面我们将同时使用到Nacos和Sentinel Dashboard,所以可以先把Nacos和Sentinel Dashboard启动起来。默认配置下启动后,它们的访问地址(后续会用到)为:Nacos:http://localhost:8848/Sentinel Dashboard:http://localhost:8080/如果还没入门Nacos和Sentinel Dashboard可以通过文末的系列目录先学习之前的内容。应用配置第一步:在Spring Cloud应用的pom.xml中引入Spring Cloud Alibaba的Sentinel模块和Nacos存储扩展: <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-datasource-nacos</artifactId> <version>1.4.0</version> </dependency></dependencies>第二步:在Spring Cloud应用中添加配置信息:spring.application.name=alibaba-sentinel-datasource-nacosserver.port=8003# sentinel dashboardspring.cloud.sentinel.transport.dashboard=localhost:8080# sentinel datasource nacos :http://blog.didispace.com/spring-cloud-alibaba-sentinel-2-1/spring.cloud.sentinel.datasource.ds.nacos.server-addr=localhost:8848spring.cloud.sentinel.datasource.ds.nacos.dataId=${spring.application.name}-sentinelspring.cloud.sentinel.datasource.ds.nacos.groupId=DEFAULT_GROUPspring.cloud.sentinel.transport.dashboard:sentinel dashboard的访问地址,根据上面准备工作中启动的实例配置spring.cloud.sentinel.datasource.ds.nacos.server-addr:nacos的访问地址,,根据上面准备工作中启动的实例配置spring.cloud.sentinel.datasource.ds.nacos.groupId:nacos中存储规则的groupIdspring.cloud.sentinel.datasource.ds.nacos.dataId:nacos中存储规则的dataId这里对于dataId使用了${spring.application.name}变量,这样可以根据应用名来区分不同的规则配置。注意:Spring Cloud Alibaba的Sentinel整合文档中有一些小问题,比如:并没有spring.cloud.sentinel.datasource.ds2.nacos.rule-type这个参数。可能是由于版本迭代更新,文档失修的缘故。读者在使用的时候,可以通过查看org.springframework.cloud.alibaba.sentinel.datasource.config.DataSourcePropertiesConfiguration和org.springframework.cloud.alibaba.sentinel.datasource.config.NacosDataSourceProperties两个类来分析具体的配置内容,会更为准确。比如,Nacos存储的具体配置类源码如下:public class NacosDataSourceProperties extends AbstractDataSourceProperties { private String serverAddr; private String groupId; private String dataId; // commercialized usage private String endpoint; private String namespace; private String accessKey; private String secretKey;}上面我们使用了前三个属性,后四个属性是阿里云的商业化产品使用的,这里就不具体介绍了,有使用阿里云商业产品的童鞋可以了解一下。第三步:创建应用主类,并提供一个rest接口,比如:@SpringBootApplicationpublic class TestApplication { public static void main(String[] args) { SpringApplication.run(TestApplication.class, args); } @Slf4j @RestController static class TestController { @GetMapping("/hello") public String hello() { return “didispace.com”; } }}第四步:Nacos中创建限流规则的配置,比如:其中:Data ID、Group就是上面第二步中配置的内容。配置格式选择JSON,并在配置内容中填入下面的内容:[ { “resource”: “/hello”, “limitApp”: “default”, “grade”: 1, “count”: 5, “strategy”: 0, “controlBehavior”: 0, “clusterMode”: false }]可以看到上面配置规则是一个数组类型,数组中的每个对象是针对每一个保护资源的配置对象,每个对象中的属性解释如下:resource:资源名,即限流规则的作用对象limitApp:流控针对的调用来源,若为 default 则不区分调用来源grade:限流阈值类型(QPS 或并发线程数);0代表根据并发数量来限流,1代表根据QPS来进行流量控制count:限流阈值strategy:调用关系限流策略controlBehavior:流量控制效果(直接拒绝、Warm Up、匀速排队)clusterMode:是否为集群模式这里我们只做简单的配置解释,以便于理解这里的配置作用。实际上这里还有非常多可配置选项和规则,更复杂的配置后面我们单独开一篇来深入学习。第五步:启动应用。如果一些顺利,可以看到类似下面的日志,代表已经成功从Nacos加载了一条限流规则:2019-04-16 14:24:42.919 INFO 89484 — [ main] o.s.c.a.s.c.SentinelDataSourceHandler : [Sentinel Starter] DataSource ds-sentinel-nacos-datasource start to loadConfig2019-04-16 14:24:42.938 INFO 89484 — [ main] o.s.c.a.s.c.SentinelDataSourceHandler : [Sentinel Starter] DataSource ds-sentinel-nacos-datasource load 1 FlowRule通过postman或者curl访问几下localhost:8003/hello接口:$ curl localhost:8003/hellodidispace.com此时,在Sentinel Dashboard中就可以看到当前我们启动的alibaba-sentinel-datasource-nacos服务。点击左侧菜单中的流控规则,可以看到已经存在一条记录了,具体如下:这条记录就是上面我们在Nacos中配置的限流规则。深入思考在完成了上面的整合之后,对于接口流控规则的修改就存在两个地方了:Sentinel控制台、Nacos控制台。这个时候,需要注意当前版本的Sentinel控制台不具备同步修改Nacos配置的能力,而Nacos由于可以通过在客户端中使用Listener来实现自动更新。所以,在整合了Nacos做规则存储之后,需要知道在下面两个地方修改存在不同的效果:Sentinel控制台中修改规则:仅存在于服务的内存中,不会修改Nacos中的配置值,重启后恢复原来的值。Nacos控制台中修改规则:服务的内存中规则会更新,Nacos中持久化规则也会更新,重启后依然保持。代码示例本文介绍内容的客户端代码,示例读者可以通过查看下面仓库中的alibaba-sentinel-datasource-nacos项目:Github:https://github.com/dyc87112/SpringCloud-Learning/Gitee:https://gitee.com/didispace/SpringCloud-Learning/如果您对这些感兴趣,欢迎star、follow、收藏、转发给予支持!参考资料下面是Sentinel的仓库地址与官方文档,读者也可以自己查阅文档学习:GithubSentinel官方文档:动态规则Spring Cloud Alibaba Sentinel文档专题推荐Spring Boot基础教程Spring Cloud基础教程 ...

April 17, 2019 · 1 min · jiezi

Spring Cloud 参考文档(断路器:Hystrix客户端)

断路器:Hystrix客户端Netflix创建了一个名为Hystrix的库,用于实现断路器模式,在微服务架构中,通常有多层服务调用,如以下示例所示:较低级别的服务中的服务故障可能导致级联故障一直到用户,当对特定服务的调用超过circuitBreaker.requestVolumeThreshold(默认值:20个请求)并且在metrics.rollingStats.timeInMilliseconds(默认值:10秒)定义的滚动窗口中,失败百分比大于circuitBreaker.errorThresholdPercentage(默认值:> 50%)时,电路打开,没有调用,在出现错误和开路的情况下,开发人员可以提供回退。拥有一个开放的电路可以停止级联故障,并允许过载或故障服务有时间恢复,回退可以是另一个受Hystrix保护的调用、静态数据或合理的空值,回退可以被链接,以便第一个回退执行一些其他业务调用,而这些业务调用又反过来回退到静态数据。如何包含Hystrix要在项目中包含Hystrix,请使用组ID为org.springframework.cloud和工件ID为spring-cloud-starter-netflix-hystrix的启动器。以下示例显示了具有Hystrix断路器的最小Eureka服务器:@SpringBootApplication@EnableCircuitBreakerpublic class Application { public static void main(String[] args) { new SpringApplicationBuilder(Application.class).web(true).run(args); }}@Componentpublic class StoreIntegration { @HystrixCommand(fallbackMethod = “defaultStores”) public Object getStores(Map<String, Object> parameters) { //do stuff that might fail } public Object defaultStores(Map<String, Object> parameters) { return /* something useful */; }}@HystrixCommand由名为“javanica”的Netflix contrib库提供,Spring Cloud在连接到Hystrix断路器的代理中自动包装带有该注解的Spring bean,断路器计算何时打开和关闭电路以及在发生故障时应采取的措施。要配置@HystrixCommand,可以将commandProperties属性与@HystrixProperty注解列表一起使用,有关详细信息,请参见此处,有关可用属性的详细信息,请参阅Hystrix wiki。传播安全上下文或使用Spring Scopes如果希望某些线程本地上下文传播到@HystrixCommand,则默认声明不起作用,因为它在线程池中执行该命令(在超时的情况下),你可以通过配置或直接在注解中切换Hystrix以使用与调用者相同的线程,方法是让它使用不同的“隔离策略”,以下示例演示如何在注解中设置线程:@HystrixCommand(fallbackMethod = “stubMyService”, commandProperties = { @HystrixProperty(name=“execution.isolation.strategy”, value=“SEMAPHORE”) })…如果你使用@SessionScope或@RequestScope,则同样适用,如果遇到运行时异常,表示无法找到作用域上下文,则需要使用相同的线程。你还可以选择将hystrix.shareSecurityContext属性设置为true,这样做会自动配置Hystrix并发策略插件挂钩,将SecurityContext从主线程传输到Hystrix命令使用的线程。Hystrix不会注册多个Hystrix并发策略,因此可以通过将自己的HystrixConcurrencyStrategy声明为Spring bean来实现扩展机制,Spring Cloud在Spring上下文中查找你的实现,并将其包装在自己的插件中。健康指示器连接断路器的状态也暴露在调用应用程序的/health端点中,如以下示例所示:{ “hystrix”: { “openCircuitBreakers”: [ “StoreIntegration::getStoresByLocationLink” ], “status”: “CIRCUIT_OPEN” }, “status”: “UP”}Hystrix指标流要启用Hystrix指标流,请包含对spring-boot-starter-actuator的依赖关系并设置management.endpoints.web.exposure.include: hystrix.stream,这样做会将/actuator/hystrix.stream公开为管理端点,如以下示例所示:<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>Hystrix仪表板Hystrix的主要好处之一是它收集了关于每个HystrixCommand的指标集,Hystrix仪表板以高效的方式显示每个断路器的健康状况。 ...

April 17, 2019 · 1 min · jiezi

Spring Cloud 参考文档(服务发现:Eureka Server)

服务发现:Eureka Server本节介绍如何设置Eureka服务器。如何包含Eureka服务器要在项目中包含Eureka Server,请使用组ID为org.springframework.cloud和工件ID为spring-cloud-starter-netflix-eureka-server的启动器。如果你的项目已使用Thymeleaf作为其模板引擎,则可能无法正确加载Eureka服务器的Freemarker模板,在这种情况下,有必要手动配置模板加载器:application.ymlspring: freemarker: template-loader-path: classpath:/templates/ prefer-file-system-access: false如何运行Eureka服务器以下示例显示了最小的Eureka服务器:@SpringBootApplication@EnableEurekaServerpublic class Application { public static void main(String[] args) { new SpringApplicationBuilder(Application.class).web(true).run(args); }}服务器有一个UI主页和在/eureka/*下用于正常Eureka功能的HTTP API端点。以下链接有一些Eureka背景阅读:flux capacitor和谷歌小组讨论。由于Gradle的依赖关系解析规则和缺少父bom特性,依赖spring-cloud-starter-netflix-eureka-server可能导致应用程序启动失败,要解决此问题,请添加Spring Boot Gradle插件并导入Spring cloud starter parent bom,如下所示:build.gradlebuildscript { dependencies { classpath(“org.springframework.boot:spring-boot-gradle-plugin:{spring-boot-docs-version}”) }}apply plugin: “spring-boot"dependencyManagement { imports { mavenBom “org.springframework.cloud:spring-cloud-dependencies:{spring-cloud-version}” }}高可用性、Zones和RegionsEureka服务器没有后端存储,但注册表中的服务实例都必须发送心跳以使其注册保持最新(因此可以在内存中完成),客户端还有一个Eureka注册的内存缓存(因此,它们不必对服务的每个请求都去注册表)。默认情况下,每个Eureka服务器也是Eureka客户端,并且需要(至少一个)服务URL来定位对等体,如果你不提供该服务,该服务将运行并工作,但它会在你的日志中填充很多关于无法向对等方注册的噪音。独立模式两个缓存(客户端和服务器)的组合和心跳使独立的Eureka服务器能够很好地应对故障,只要有某种监视器或弹性运行时(例如Cloud Foundry)保持活着。在独立模式下,你可能更愿意关闭客户端行为,以便它不会继续尝试并且无法访问其对等方,以下示例显示如何关闭客户端行为:application.yml(独立Eureka服务器)。server: port: 8761eureka: instance: hostname: localhost client: registerWithEureka: false fetchRegistry: false serviceUrl: defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/请注意,serviceUrl指向与本地实例相同的主机。对等感知通过运行多个实例并要求它们相互注册,可以使Eureka更具弹性和可用性,实际上,这是默认行为,因此你需要做的就是将有效的serviceUrl添加到对等体,如以下示例所示:application.yml(两个Peer Aware Eureka服务器)。—spring: profiles: peer1eureka: instance: hostname: peer1 client: serviceUrl: defaultZone: http://peer2/eureka/—spring: profiles: peer2eureka: instance: hostname: peer2 client: serviceUrl: defaultZone: http://peer1/eureka/在前面的示例中,我们有一个YAML文件,可以通过在不同的Spring配置文件中运行它来在两个主机(peer1和peer2)上运行相同的服务器,你可以使用此配置通过操作/etc/hosts来解析主机名来测试单个主机上的对等感知(在生产中执行此操作没有太大价值)。实际上,如果你在知道自己的主机名的计算机上运行,则不需要eureka.instance.hostname(默认情况下,使用java.net.InetAddress查找它)。你可以将多个对等体添加到一个系统中,并且只要它们通过至少一个边缘彼此连接,它们就会在它们之间同步注册,如果对等体在物理上是分开的(在数据中心内或在多个数据中心之间),那么系统原则上可以在“裂脑”类型故障中存活,你可以向一个系统添加多个对等体,只要它们彼此直接连接,它们就会在它们之间同步注册。application.yml(三个Peer Aware Eureka服务器)。eureka: client: serviceUrl: defaultZone: http://peer1/eureka/,http://peer2/eureka/,http://peer3/eureka/—spring: profiles: peer1eureka: instance: hostname: peer1—spring: profiles: peer2eureka: instance: hostname: peer2—spring: profiles: peer3eureka: instance: hostname: peer3何时首选IP地址在某些情况下,Eureka最好公布服务的IP地址而不是主机名,将eureka.instance.preferIpAddress设置为true,当应用程序向eureka注册时,它使用其IP地址而不是其主机名。如果Java无法确定主机名,则将IP地址发送给Eureka,设置主机名的明确方法是只有设置eureka.instance.hostname属性,你可以使用环境变量在运行时设置主机名 — 例如,eureka.instance.hostname=${HOST_NAME}。保护Eureka服务器只需将spring-boot-starter-security添加到服务器的类路径中,即可通过Spring Security保护你的Eureka服务器,默认情况下,当Spring Security位于类路径上时,它将要求应用程序的每次请求都要发送有效的CSRF令牌,Eureka客户端通常不会拥有有效的跨站点请求伪造(CSRF)令牌,你需要为/eureka/端点禁用此要求,例如:@EnableWebSecurityclass WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().ignoringAntMatchers("/eureka/”); super.configure(http); }}有关CSRF的更多信息,请参阅Spring Security文档。可以在Spring Cloud Samples存储库中找到Eureka Server demo。JDK 11支持在JDK 11中删除了Eureka服务器所依赖的JAXB模块,如果你打算在运行Eureka服务器时使用JDK 11,则必须在POM或Gradle文件中包含这些依赖项。<dependency> <groupId>javax.xml.bind</groupId> <artifactId>jaxb-api</artifactId> <version>2.3.0</version></dependency><dependency> <groupId>com.sun.xml.bind</groupId> <artifactId>jaxb-core</artifactId> <version>2.3.0</version></dependency><dependency> <groupId>com.sun.xml.bind</groupId> <artifactId>jaxb-impl</artifactId> <version>2.3.0</version></dependency> ...

April 16, 2019 · 1 min · jiezi

Spring Cloud 参考文档(Spring Cloud Config Client)

Spring Cloud Config ClientSpring Boot应用程序可以立即利用Spring Config Server(或应用程序开发人员提供的其他外部属性源),它还提取了一些与Environment变化事件相关的额外有用特性。配置优先Bootstrap在类路径上具有Spring Cloud Config Client的任何应用程序的默认行为如下:当配置客户端启动时,它会绑定到Config Server(通过spring.cloud.config.uri bootstrap配置属性)并使用远程属性源初始化Spring Environment。这种行为的最终结果是,所有想要使用Config Server的客户端应用程序都需要一个bootstrap.yml(或环境变量),其服务器地址在spring.cloud.config.uri中设置(默认为“http://localhost:8888" )。发现优先Bootstrap如果你使用DiscoveryClient实现,例如Spring Cloud Netflix和Eureka Service Discovery或Spring Cloud Consul,你可以将Config Server注册到Discovery Service,但是,在默认的“配置优先Bootstrap”模式下,客户端无法利用注册。如果你更喜欢使用DiscoveryClient来定位Config Server,可以通过设置spring.cloud.config.discovery.enabled=true(默认值为false)来实现,这样做的最终结果是客户端应用程序都需要具有适当发现配置的bootstrap.yml(或环境变量)。例如,使用Spring Cloud Netflix,你需要定义Eureka服务器地址(例如,在eureka.client.serviceUrl.defaultZone中),使用此选项的代价是启动时额外的网络往返,以查找服务注册,好处是,只要Discovery Service是固定点,Config Server就可以更改其坐标。默认服务ID是configserver,但你可以通过设置spring.cloud.config.discovery.serviceId在客户端上更改它(并在服务器上,以通常的方式提供服务,例如通过设置spring.application.name) 。发现客户端实现都支持某种元数据映射(例如,我们为Eureka提供了eureka.instance.metadataMap),可能需要在其服务注册元数据中配置Config Server的一些额外属性,以便客户端可以正确连接。如果使用HTTP Basic保护Config Server,则可以将凭据配置为user和password,此外,如果Config Server具有上下文路径,则可以设置configPath,例如,以下YAML文件用于作为Eureka客户端的Config Server:bootstrap.ymleureka: instance: … metadataMap: user: osufhalskjrtl password: lviuhlszvaorhvlo5847 configPath: /configConfig Client快速失败在某些情况下,如果服务无法连接到Config Server,你可能希望服务启动失败,如果这是所需的行为,请将bootstrap配置属性spring.cloud.config.fail-fast=true设置为使客户端停止并显示异常。Config Client重试如果你预期配置服务器在应用程序启动时偶尔可能不可用,你可以在失败后继续尝试。首先,你需要设置spring.cloud.config.fail-fast=true,然后,你需要在类路径中添加spring-retry和spring-boot-starter-aop,默认行为是重试六次,初始回退间隔为1000毫秒,后续回退的指数乘数为1.1,你可以通过设置spring.cloud.config.retry.*配置属性来配置这些属性(和其他属性)。要完全控制重试行为,请添加一个类型为RetryOperationsInterceptor的@Bean,其ID为configServerRetryInterceptor,Spring Retry有一个RetryInterceptorBuilder支持创建它。查找远程配置资源Config Service从/{name}/{profile}/{label}提供属性源,其中客户端应用程序中的默认绑定如下:“name” = ${spring.application.name}“profile” = ${spring.profiles.active}(实际上是Environment.getActiveProfiles())“label” = “master”设置属性${spring.application.name}时,不要在应用程序名称前加上保留字application-,以防止解析正确的属性源问题。你可以通过设置spring.cloud.config.来覆盖所有这些(其中是name、profile或label),该label可用于回滚到以前版本的配置,使用默认的Config Server实现,它可以是git标签,分支名称或提交ID。标签也可以以逗号分隔列表的形式提供,在这种情况下,列表中的项目将逐个尝试,直到成功为止,在处理特性分支时,此行为非常有用。例如,你可能希望将配置标签与你的分支对齐,但使其成为可选(在这种情况下,请使用spring.cloud.config.label=myfeature,develop)。为Config Server指定多个URL当你部署了多个Config Server实例并预期一个或多个实例不时不可用时,为确保高可用性,你可以指定多个URL(作为spring.cloud.config.uri属性下的逗号分隔列表),也可以让所有实例在Eureka等Service Registry中注册(如果使用发现优先Bootstrap模式)。请注意,只有在Config Server未运行时(即应用程序已退出时)或发生连接超时时,才能确保高可用性,例如,如果Config Server返回500(内部服务器错误)响应或Config Client从Config Server收到401(由于凭据错误或其他原因),则Config Client不会尝试从其他URL获取属性,这种错误表示用户问题而不是可用性问题。如果在Config Server上使用HTTP基本安全性,则仅当你在spring.cloud.config.uri属性下指定的每个URL中嵌入凭据时,才能支持每个Config Server身份验证凭据,如果使用任何其他类型的安全机制,则无法(目前)支持每个Config Server身份验证和授权。配置读取超时如果要配置读取超,可以使用属性spring.cloud.config.request-read-timeout来完成此操作。安全性如果你在服务器上使用HTTP Basic安全性,客户端需要知道密码(如果不是默认值,则需要用户名),你可以通过配置服务器URI或单独的用户名和密码属性指定用户名和密码,如以下示例所示:bootstrap.ymlspring: cloud: config: uri: https://user:secret@myconfig.mycompany.com以下示例显示了传递相同信息的另一种方法:bootstrap.ymlspring: cloud: config: uri: https://myconfig.mycompany.com username: user password: secretspring.cloud.config.password和spring.cloud.config.username值覆盖URI中提供的任何内容。如果你在Cloud Foundry上部署应用程序,提供密码的最佳方式是通过服务凭据(例如在URI中,因为它不需要在配置文件中),以下示例在本地运行,并在名为configserver的Cloud Foundry上为用户提供服务:bootstrap.ymlspring: cloud: config: uri: ${vcap.services.configserver.credentials.uri:http://user:password@localhost:8888}如果你使用其他形式的安全性,则可能需要向ConfigServicePropertySourceLocator提供一个RestTemplate(例如,通过在引导程序上下文中获取它并注入它)。健康指示器Config Client提供尝试从Config Server加载配置的Spring Boot Health Indicator,可以通过设置health.config.enabled=false来禁用健康指示器,出于性能原因,还会缓存响应,生存的默认缓存时间为5分钟,要更改该值,请设置health.config.time-to-live属性(以毫秒为单位)。提供自定义RestTemplate在某些情况下,你可能需要自定义从客户端向配置服务器发出的请求,通常,这样做涉及传递特殊的Authorization headers来验证对服务器的请求,要提供自定义RestTemplate:使用PropertySourceLocator的实现创建一个新的配置bean,如以下示例所示:CustomConfigServiceBootstrapConfiguration.java@Configurationpublic class CustomConfigServiceBootstrapConfiguration { @Bean public ConfigServicePropertySourceLocator configServicePropertySourceLocator() { ConfigClientProperties clientProperties = configClientProperties(); ConfigServicePropertySourceLocator configServicePropertySourceLocator = new ConfigServicePropertySourceLocator(clientProperties); configServicePropertySourceLocator.setRestTemplate(customRestTemplate(clientProperties)); return configServicePropertySourceLocator; }}在resources/META-INF中,创建一个名为spring.factories的文件并指定自定义配置,如以下示例所示:spring.factoriesorg.springframework.cloud.bootstrap.BootstrapConfiguration = com.my.config.client.CustomConfigServiceBootstrapConfigurationVault使用Vault作为配置服务器的后端时,客户端需要为服务器提供令牌以从Vault检索值,可以通过在bootstrap.yml中设置spring.cloud.config.token在客户端内提供此令牌,如以下示例所示:bootstrap.ymlspring: cloud: config: token: YourVaultTokenVault中的嵌套密钥Vault支持将密钥嵌套在Vault中存储的值中,如以下示例所示:echo -n ‘{“appA”: {“secret”: “appAsecret”}, “bar”: “baz”}’ | vault write secret/myapp -此命令将JSON对象写入Vault,要在Spring中访问这些值,可以使用传统的点(.)注解,如以下示例所示:@Value("${appA.secret}")String name = “World”;上面的代码会将name变量的值设置为appAsecret。上一篇:推送通知和Spring Cloud Bus ...

April 16, 2019 · 1 min · jiezi

Spring Cloud 参考文档(推送通知和Spring Cloud Bus)

推送通知和Spring Cloud Bus许多源代码存储库提供程序(例如Github、Gitlab、Gitea、Gitee、Gogs或Bitbucket)通过webhook通知你存储库中的更改,你可以通过提供程序的用户界面将webhook配置为URL以及你感兴趣的一组事件。例如,Github使用POST到webhook,其中包含一个JSON体,其中包含一个提交列表和一个header(X-Github-Event)设置为push,如果添加了对spring-cloud-config-monitor库的依赖并在Config Server中激活Spring Cloud Bus,则会启用/monitor端点。激活webhook后,Config Server会针对它认为可能已更改的应用程序发送一个RefreshRemoteApplicationEvent,变更检测可以制定策略。但是,默认情况下,它会查找与应用程序名称匹配的文件中的更改(例如,foo.properties的目标是foo应用程序,而application.properties则针对所有应用程序)。要覆盖该行为时使用的策略是PropertyPathNotificationExtractor,它接受请求headers和body作为参数,并返回已更改的文件路径列表。默认配置的开箱即用的使用Github、Gitlab、Gitea、Gitee、Gogs或Bitbucket,除了来自Github,Gitlab,Gitee或Bitbucket的JSON通知,你可以通过使用path={name}模式中的form-encoded的body参数POST到/monitor来触发更改通知,这样做会广播到匹配{name}模式(可以包含通配符)的应用程序。仅当在Config Server和客户端应用程序中激活spring-cloud-bus时,才会传输RefreshRemoteApplicationEvent。默认配置还检测本地git存储库中的文件系统更改,在这种情况下,不使用webhook,但是,只要编辑配置文件,就会广播刷新。上一篇:嵌入Config Server

April 15, 2019 · 1 min · jiezi

Spring Cloud 参考文档(嵌入Config Server)

嵌入Config ServerConfig Server作为独立应用程序运行最佳,但是,如果需要,你可以将其嵌入另一个应用程序中,为此,请使用@EnableConfigServer注解。在这种情况下,名为spring.cloud.config.server.bootstrap的可选属性非常有用,它是一个标志,指示服务器是否应从其自己的远程存储库配置自身,默认情况下,该标志处于关闭状态,因为它可能会延迟启动。但是,当嵌入到另一个应用程序中时,以与任何其他应用程序相同的方式初始化是有意义的,将spring.cloud.config.server.bootstrap设置为true时,还必须使用组合环境存储库配置,例如:spring: application: name: configserver profiles: active: composite cloud: config: server: composite: - type: native search-locations: ${HOME}/Desktop/config bootstrap: true如果使用bootstrap标志,则配置服务器需要在bootstrap.yml中配置其名称和存储库URI。要更改服务器端点的位置,你可以(可选)设置spring.cloud.config.server.prefix(例如,/config),以便在前缀下提供资源,前缀应该开始但不以/结束,它应用于Config Server中的@RequestMappings(即Spring Boot server.servletPath和server.contextPath前缀下面)。如果要直接从后端存储库(而不是从配置服务器)读取应用程序的配置,你基本上需要一个没有端点的嵌入式配置服务器,你可以通过不使用@EnableConfigServer注解完全关闭端点(设置spring.cloud.config.server.bootstrap=true)。上一篇:提供纯文本配置访问下一篇:推送通知和Spring Cloud Bus

April 15, 2019 · 1 min · jiezi

Spring Cloud 参考文档(Spring Cloud Config Server)

Spring Cloud Config ServerSpring Cloud Config Server为外部配置提供基于HTTP资源的API(名称—值对或等效的YAML内容),通过使用@EnableConfigServer注解,服务器可嵌入Spring Boot应用程序中,因此,以下应用程序是配置服务器:ConfigServer.java@SpringBootApplication@EnableConfigServerpublic class ConfigServer { public static void main(String[] args) { SpringApplication.run(ConfigServer.class, args); }}与所有Spring Boot应用程序一样,它默认在端口8080上运行,但你可以通过各种方式将其切换到更传统的端口8888。最简单的,也是设置默认配置存储库,是通过spring.config.name=configserver启动它(Config Server jar中有一个configserver.yml),另一种方法是使用你自己的application.properties,如以下示例所示:application.propertiesserver.port: 8888spring.cloud.config.server.git.uri: file://${user.home}/config-repo其中${user.home}/config-repo是一个包含YAML和属性文件的git存储库。在Windows上,如果文件URL是绝对的驱动器前缀,则需要额外的“/”(例如,file:///${user.home}/config-repo)。以下清单显示了在前面的示例中创建git存储库的步骤:$ cd $HOME$ mkdir config-repo$ cd config-repo$ git init .$ echo info.foo: bar > application.properties$ git add -A .$ git commit -m “Add application.properties"使用git存储库的本地文件系统仅用于测试,生产中你应该使用服务器托管配置存储库。如果只保留文本文件,则配置存储库的初始克隆可以快速有效,如果存储二进制文件(尤其是大型文件),则第一次请求配置可能会出现延迟或服务器中遇到内存不足错误。环境存储库应该在哪里存储配置服务器的配置数据?管理此行为的策略是EnvironmentRepository,为Environment对象提供服务,此Environment是Spring Environment中域的浅拷贝(包括propertySources作为主要功能),Environment资源由三个变量参数化:{application},它映射到客户端的spring.application.name。{profile},它映射到客户端(逗号分隔列表)的spring.profiles.active。{label},这是标记配置文件集“版本化”的服务器端特性。存储库实现通常表现得像Spring Boot应用程序,从spring.config.name等于{application}参数,spring.profiles.active等于{profiles}参数加载配置文件。配置文件的优先规则也与常规Spring Boot应用程序中的规则相同:活动配置文件优先于默认配置文件,如果有多个配置文件,则最后一个配置文件获胜(类似于向Map添加条目)。以下示例客户端应用程序具有此bootstrap配置:bootstrap.ymlspring: application: name: foo profiles: active: dev,mysql像通常一样,Spring Boot应用程序也可以通过环境变量或命令行参数来设置这些属性。如果存储库是基于文件的,则服务器从application.yml(在所有客户端之间共享)和foo.yml(以foo.yml优先)创建Environment。如果YAML文件中包含指向Spring配置文件的文档,那么这些文档将以更高的优先级应用(按列出的配置文件的顺序)。如果存在特定配置文件的YAML(或属性)文件,则这些文件的优先级也高于默认值,较高的优先级转换为Environment中先前列出的PropertySource(这些相同的规则适用于独立的Spring Boot应用程序)。你可以将spring.cloud.config.server.accept-empty设置为false,以便如果应用程序找不到,则Server返回HTTP 404状态,默认情况下,此标志设置为true。健康指示器Config Server附带一个健康指示器,用于检查配置的EnvironmentRepository是否正常工作,默认情况下,它会向EnvironmentRepository请求名为app的应用程序、default配置文件以及EnvironmentRepository实现提供的默认标签。你可以配置健康指示器以检查更多应用程序以及自定义配置文件和自定义标签,如以下示例所示:spring: cloud: config: server: health: repositories: myservice: label: mylabel myservice-dev: name: myservice profiles: development你可以通过设置spring.cloud.config.server.health.enabled=false来禁用监控指示器。安全性你可以以对你有意义的任何方式保护你的Config Server(从物理网络安全到OAuth2承载令牌),因为Spring Security和Spring Boot为许多安全安排提供支持。要使用默认的Spring Boot配置的HTTP Basic安全性,请在类路径中包含Spring Security(例如,通过spring-boot-starter-security),默认值为user的用户名和随机生成的密码,随机密码在实践中没有用,因此建议你配置密码(通过设置spring.security.user.password)并对其进行加密(有关如何执行此操作的说明,请参阅下文)。加密和解密要使用加密和解密特性,你需要在JVM中安装完整的JCE(默认情况下不包括),你可以从Oracle下载“Java Cryptography Extension(JCE)Unlimited Strength Jurisdiction Policy Files”并按照安装说明进行操作(实际上,你需要将JRE lib/security目录中的两个策略文件替换为你下载的策略文件)。如果远程属性源包含加密内容(以{cipher}开头的值),则在通过HTTP发送到客户端之前对它们进行解密,此设置的主要优点是,属性值在“静止”时不必是纯文本格式(例如,在git存储库中)。如果某个值无法解密,则会从属性源中删除该值,并添加一个附加属性,该属性具有相同的键但前缀为invalid,且值为“不适用”(通常为<n/a>),这主要是为了防止密文被用作密码并意外泄露。如果为配置客户端应用程序设置远程配置存储库,则它可能包含类似于以下内容的application.yml:spring: datasource: username: dbuser password: ‘{cipher}FKSAJDFGYOS8F7GLHAKERGFHLSAJ’.properties文件中的加密值不能用引号括起来,否则,该值不会被解密,以下示例显示了有效的值:application.propertiesspring.datasource.username: dbuserspring.datasource.password: {cipher}FKSAJDFGYOS8F7GLHAKERGFHLSAJ你可以安全地将此纯文本推送到共享的git存储库,并且密码仍然受到保护。服务器还公开/encrypt和/decrypt端点(假设这些端点是安全的并且只能由授权代理访问),如果编辑远程配置文件,则可以使用Config Server通过POST到/encrypt端点来加密值,如以下示例所示:$ curl localhost:8888/encrypt -d mysecret682bc583f4641835fa2db009355293665d2647dade3375c0ee201de2a49f7bda如果你加密的值中包含需要进行URL编码的字符,则应使用–data-urlencode选项进行curl以确保它们已正确编码。请确保不要在加密值中包含任何curl命令统计信息,将值输出到文件可以帮助避免此问题。通过/decrypt也可以使用反向操作(前提是服务器配置了对称密钥或完整密钥对),如以下示例所示:$ curl localhost:8888/decrypt -d 682bc583f4641835fa2db009355293665d2647dade3375c0ee201de2a49f7bdamysecret如果你用curl测试,那么使用–data-urlencode(替代-d)或设置一个显式的Content-Type: text/plain来确保curl在有特殊字符时正确编码数据(’+‘特别棘手)。获取加密值并添加{cipher}前缀,然后再将其放入YAML或属性文件中,然后再提交并将其推送到远程(可能不安全)存储。/encrypt和/decrypt端点也接受//{name}/{profiles}形式的路径,当客户端调用主环境资源时,可用于在每个应用程序(名称)和每个配置文件的基础上控制加密。要以这种精细的方式控制加密,你还必须提供类型为TextEncryptorLocator的@Bean,它为每个名称和配置文件创建不同的加密器,默认情况下提供的那个不会这样做(所有加密都使用相同的密钥)。spring命令行客户端(安装了Spring Cloud CLI扩展)也可用于加密和解密,如以下示例所示:$ spring encrypt mysecret –key foo682bc583f4641835fa2db009355293665d2647dade3375c0ee201de2a49f7bda$ spring decrypt –key foo 682bc583f4641835fa2db009355293665d2647dade3375c0ee201de2a49f7bdamysecret要使用文件中的密钥(例如用于加密的RSA公钥),请在密钥值前加上“@”并提供文件路径,如以下示例所示:$ spring encrypt mysecret –key @${HOME}/.ssh/id_rsa.pubAQAjPgt3eFZQXwt8tsHAVv/QHiY5sI2dRcR+…–key参数是必需的(尽管有–前缀)。密钥管理Config Server可以使用对称(共享)密钥或非对称密钥(RSA密钥对),非对称选择在安全性方面更优越,但使用对称密钥通常更方便,因为它是在bootstrap.properties中配置的单个属性值。要配置对称密钥,需要将encrypt.key设置为秘密字符串(或使用ENCRYPT_KEY环境变量将其排除在纯文本配置文件之外)。无法使用encrypt.key配置非对称密钥。要配置非对称密钥,请使用密钥库(例如,由JDK附带的keytool实用工具创建),密钥库属性是encrypt.keyStore.,*等于:属性描述encrypt.keyStore.location包含Resource的位置encrypt.keyStore.password保存解锁密钥库的密码encrypt.keyStore.alias标识要使用存储中的哪个密钥加密是使用公钥完成的,并且需要私钥进行解密,因此,原则上,如果只想加密(并准备使用私钥本地解密值),则只配置服务器中的公钥。实际上,你可能不希望在本地进行解密,因为它会围绕所有客户端传播密钥管理过程,而不是将其集中在服务器中,另一方面,如果你的配置服务器相对不安全且只有少数客户端需要加密属性,那么它可能是一个有用的选项。创建用于测试的密钥库要创建用于测试的密钥库,可以使用类似于以下内容的命令:$ keytool -genkeypair -alias mytestkey -keyalg RSA \ -dname “CN=Web Server,OU=Unit,O=Organization,L=City,S=State,C=US” \ -keypass changeme -keystore server.jks -storepass letmein将server.jks文件放在类路径中(例如),然后在bootstrap.yml中为Config Server创建以下设置:encrypt: keyStore: location: classpath:/server.jks password: letmein alias: mytestkey secret: changeme使用多个密钥和密钥轮换除了加密属性值中的{cipher}前缀之外,Config Server还会在(Base64编码)密文开头之前查找零个或多个{name:value}前缀,密钥传递给TextEncryptorLocator,它可以执行为密文定位TextEncryptor所需的任何逻辑。如果已配置密钥库(encrypt.keystore.location),则默认定位器将查找具有key前缀提供的别名的密钥,密文类似于以下内容:foo: bar: {cipher}{key:testkey}...定位器查找名为“testkey”的密钥,也可以通过在前缀中使用{secret:…}值来提供秘密,但是,如果未提供,则默认使用密钥库密码(这是你在构建密钥库时未指定秘密),如果你提供秘密,你还应该使用自定义SecretLocator加密秘密。当密钥仅用于加密几个字节的配置数据时(也就是说,它们没有在其他地方使用),在加密方面几乎不需要密钥轮换。但是,你可能偶尔需要更改密钥(例如,在发生安全漏洞时),在这种情况下,所有客户端都需要更改其源配置文件(例如,在git中)并在所有密文中使用新的{key:…}前缀,请注意,客户端需要首先检查Config Server密钥库中的密钥别名是否可用。如果你想让Config Server处理所有加密和解密,{name:value}前缀也可以作为纯文本添加发布到/encrypt端点。提供加密属性有时你希望客户端在本地解密配置,而不是在服务器中执行此操作。在这种情况下,如果你提供encrypt.*配置来定位密钥,你仍然可以拥有/encrypt和/decrypt端点,但是你需要通过在bootstrap.[yml|properties]中放置spring.cloud.config.server.encrypt.enabled=false来明确地关闭输出属性的解密,如果你不关心端点,那么如果你不配置密钥或启用标志,它应该可以工作。提供选择性的格式环境端点的默认JSON格式非常适合Spring应用程序使用,因为它直接映射到Environment抽象,如果你愿意,可以通过向资源路径添加后缀(“.yml”,“.yaml”或“.properties”)来使用与YAML或Java属性相同的数据,对于不关心JSON端点结构或它们提供的额外元数据的应用程序来说,这可能很有用(例如,不使用Spring的应用程序可能会受益于此方法的简单性)。YAML和属性表示有一个额外的标志(作为名为resolvePlaceholders的布尔查询参数提供),表示源文档中的占位符(在标准的Spring ${…}形式)应该在渲染之前在输出中解析(在可能的情况),对于不了解Spring占位符约定的消费者而言,这是一个有用的特性。使用YAML或属性格式存在限制,主要与元数据丢失有关。例如,JSON为属性源的有序列表结构,其名称与源相关,YAML和属性形式合并为单个映射,即使值的来源有多个源,并且原始源文件的名称丢失。此外,YAML表示不一定是支持存储库中YAML源的可靠表示,它由一个平面属性源列表构成,必须对键的形式进行假设。上一篇:Spring Cloud Config快速入门下一篇:提供纯文本 ...

April 15, 2019 · 1 min · jiezi

Spring Cloud 参考文档(提供纯文本配置访问)

提供纯文本你的应用程序可能需要根据其环境定制的通用纯文本配置文件,而不是使用Environment抽象(或YAML或属性格式中的其中一种替代表示)。Config Server通过/{name}/{profile}/{label}/{path}中的附加端点提供这些,其中name、profile和label与常规环境端点具有相同的含义,但path是文件名(例如log.xml)。此端点的源文件的定位方式与环境端点相同,相同的搜索路径用于属性和YAML文件,但是,不是聚合所有匹配的资源,而是仅返回要匹配的第一个。找到资源后,通过使用提供的应用程序名称、配置文件和标签的有效Environment来解析正常格式(${…})的占位符,通过这种方式,资源端点与环境端点紧密集成,请考虑以下GIT或SVN存储库示例:application.ymlnginx.conf其中nginx.conf看起来像这样:server { listen 80; server_name ${nginx.server.name};}application.yml像这样:nginx: server: name: example.com—spring: profiles: developmentnginx: server: name: develop.com/foo/default/master/nginx.conf资源可能如下:server { listen 80; server_name example.com;}/foo/development/master/nginx.conf是这样的:server { listen 80; server_name develop.com;}与环境配置的源文件一样,profile用于解析文件名,因此,如果你需要特定配置文件,//development//logback.xml可以被解析为名为logback-development.xml的文件(优先于logback.xml)。如果你不想提供label并让服务器使用默认标签,则可以提供useDefaultLabel请求参数,因此,default配置文件的前面示例可能是/foo/default/nginx.conf?useDefaultLabel。上一篇:Spring Cloud Config Server

April 15, 2019 · 1 min · jiezi

干货|Spring Cloud Bus 消息总线介绍

继上一篇 干货|Spring Cloud Stream 体系及原理介绍 之后,本期我们来了解下 Spring Cloud 体系中的另外一个组件 Spring Cloud Bus (建议先熟悉 Spring Cloud Stream,不然无法理解 Spring Cloud Bus 内部的代码)。Spring Cloud Bus 对自己的定位是 Spring Cloud 体系内的消息总线,使用 message broker 来连接分布式系统的所有节点。Bus 官方的 Reference 文档 比较简单,简单到连一张图都没有。这是最新版的 Spring Cloud Bus 代码结构(代码量比较少):Bus 实例演示在分析 Bus 的实现之前,我们先来看两个使用 Spring Cloud Bus 的简单例子。所有节点的配置新增Bus 的例子比较简单,因为 Bus 的 AutoConfiguration 层都有了默认的配置,只需要引入消息中间件对应的 Spring Cloud Stream 以及 Spring Cloud Bus 依赖即可,之后所有启动的应用都会使用同一个 Topic 进行消息的接收和发送。Bus 对应的 Demo 已经放到了 github 上: https://github.com/fangjian0423/rocketmq-binder-demo/tree/master/rocketmq-bus-demo 。 该 Demo 会模拟启动 5 个节点,只需要对其中任意的一个实例新增配置项,所有节点都会新增该配置项。访问任意节点提供的 Controller 提供的获取配置的地址(key为hangzhou):curl -X GET ‘http://localhost:10001/bus/env?key=hangzhou’所有节点返回的结果都是 unknown,因为所有节点的配置中没有 hangzhou 这个 key。Bus 内部提供了 EnvironmentBusEndpoint 这个 Endpoint 通过 message broker 用来新增/更新配置。访问任意节点该 Endpoint 对应的 url /actuator/bus-env?name=hangzhou&value=alibaba 进行配置项的新增(比如访问 node1 的url):curl -X POST ‘http://localhost:10001/actuator/bus-env?name=hangzhou&value=alibaba’ -H ‘content-type: application/json’然后再次访问所有节点 /bus/env 获取配置:$ curl -X GET ‘http://localhost:10001/bus/env?key=hangzhou’unknown%~ ⌚$ curl -X GET ‘http://localhost:10002/bus/env?key=hangzhou’unknown%~ ⌚$ curl -X GET ‘http://localhost:10003/bus/env?key=hangzhou’unknown%~ ⌚$ curl -X GET ‘http://localhost:10004/bus/env?key=hangzhou’unknown%~ ⌚$ curl -X GET ‘http://localhost:10005/bus/env?key=hangzhou’unknown%~ ⌚$ curl -X POST ‘http://localhost:10001/actuator/bus-env?name=hangzhou&value=alibaba’ -H ‘content-type: application/json’~ ⌚$ curl -X GET ‘http://localhost:10005/bus/env?key=hangzhou’alibaba%~ ⌚$ curl -X GET ‘http://localhost:10004/bus/env?key=hangzhou’alibaba%~ ⌚$ curl -X GET ‘http://localhost:10003/bus/env?key=hangzhou’alibaba%~ ⌚$ curl -X GET ‘http://localhost:10002/bus/env?key=hangzhou’alibaba%~ ⌚$ curl -X GET ‘http://localhost:10001/bus/env?key=hangzhou’alibaba%可以看到,所有节点都新增了一个 key 为 hangzhou 的配置,且对应的 value 是 alibaba。这个配置项是通过 Bus 提供的 EnvironmentBusEndpoint 完成的。这里引用 程序猿DD 画的一张图片,Spring Cloud Config 配合 Bus 完成所有节点配置的刷新来描述之前的实例(本文实例不是刷新,而是新增配置,但是流程是一样的):部分节点的配置修改比如在 node1 上指定 destination 为 rocketmq-bus-node2 (node2 配置了 spring.cloud.bus.id 为 rocketmq-bus-node2:10002,可以匹配上) 进行配置的修改:curl -X POST ‘http://localhost:10001/actuator/bus-env/rocketmq-bus-node2?name=hangzhou&value=xihu’ -H ‘content-type: application/json’访问 /bus/env 获取配置(由于在 node1 上发送消息,Bus 也会对发送方的节点 node1 进行配置修改):~ ⌚$ curl -X POST ‘http://localhost:10001/actuator/bus-env/rocketmq-bus-node2?name=hangzhou&value=xihu’ -H ‘content-type: application/json’~ ⌚$ curl -X GET ‘http://localhost:10005/bus/env?key=hangzhou’alibaba%~ ⌚$ curl -X GET ‘http://localhost:10004/bus/env?key=hangzhou’alibaba%~ ⌚$ curl -X GET ‘http://localhost:10003/bus/env?key=hangzhou’alibaba%~ ⌚$ curl -X GET ‘http://localhost:10002/bus/env?key=hangzhou’xihu%~ ⌚$ curl -X GET ‘http://localhost:10001/bus/env?key=hangzhou’xihu%可以看到,只有 node1 和 node2 修改了配置,其余的 3 个节点配置未改变。Bus 的实现Bus 概念介绍事件Bus 中定义了远程事件 RemoteApplicationEvent,该事件继承了 Spring 的事件 ApplicationEvent,而且它目前有 4 个具体的实现:EnvironmentChangeRemoteApplicationEvent: 远程环境变更事件。主要用于接收一个 Map<String, String> 类型的数据并更新到 Spring 上下文中 Environment 中的事件。文中的实例就是使用这个事件并配合 EnvironmentBusEndpoint 和 EnvironmentChangeListener 完成的。AckRemoteApplicationEvent: 远程确认事件。Bus 内部成功接收到远程事件后会发送回 AckRemoteApplicationEvent 确认事件进行确认。RefreshRemoteApplicationEvent: 远程配置刷新事件。配合 @RefreshScope 以及所有的 @ConfigurationProperties 注解修饰的配置类的动态刷新。UnknownRemoteApplicationEvent:远程未知事件。Bus 内部消息体进行转换远程事件的时候如果发生异常会统一包装成该事件。Bus 内部还存在一个非 RemoteApplicationEvent 事件 - SentApplicationEvent 消息发送事件,配合 Trace 进行远程消息发送的记录。这些事件会配合 ApplicationListener 进行操作,比如 EnvironmentChangeRemoteApplicationEvent 配了 EnvironmentChangeListener 进行配置的新增/修改:public class EnvironmentChangeListener implements ApplicationListener<EnvironmentChangeRemoteApplicationEvent> { private static Log log = LogFactory.getLog(EnvironmentChangeListener.class); @Autowired private EnvironmentManager env; @Override public void onApplicationEvent(EnvironmentChangeRemoteApplicationEvent event) { Map<String, String> values = event.getValues(); log.info(“Received remote environment change request. Keys/values to update " + values); for (Map.Entry<String, String> entry : values.entrySet()) { env.setProperty(entry.getKey(), entry.getValue()); } }}收到其它节点发送来的 EnvironmentChangeRemoteApplicationEvent 事件之后调用 EnvironmentManager#setProperty 进行配置的设置,该方法内部针对每一个配置项都会发送一个 EnvironmentChangeEvent 事件,然后被 ConfigurationPropertiesRebinder 所监听,进行 rebind 操作新增/更新配置。Actuator EndpointBus 内部暴露了 2 个 Endpoint,分别是 EnvironmentBusEndpoint 和 RefreshBusEndpoint,进行配置的新增/修改以及全局配置刷新。它们对应的 Endpoint id 即 url 是 bus-env 和 bus-refresh。配置Bus 对于消息的发送必定涉及到 Topic、Group 之类的信息,这些内容都被封装到了 BusProperties 中,其默认的配置前缀为 spring.cloud.bus,比如:spring.cloud.bus.refresh.enabled 用于开启/关闭全局刷新的 Listener。spring.cloud.bus.env.enabled 用于开启/关闭配置新增/修改的 Endpoint。spring.cloud.bus.ack.enabled 用于开启开启/关闭 AckRemoteApplicationEvent 事件的发送。spring.cloud.bus.trace.enabled 用于开启/关闭消息记录 Trace 的 Listener。消息发送涉及到的 Topic 默认用的是 springCloudBus,可以配置进行修改,Group 可以设置成广播模式或使用 UUID 配合 offset 为 lastest 的模式。每个 Bus 应用都有一个对应的 Bus id,官方取值方式较复杂:${vcap.application.name:${spring.application.name:application}}:${vcap.application.instance_index:${spring.application.index:${local.server.port:${server.port:0}}}}:${vcap.application.instance_id:${random.value}}建议手动配置 Bus id,因为 Bus 远程事件中的 destination 会根据 Bus id 进行匹配:spring.cloud.bus.id=${spring.application.name}-${server.port}Bus 底层分析Bus 的底层分析无非牵扯到这几个方面:消息是如何发送的;消息是如何接收的;destination 是如何匹配的;远程事件收到后如何触发下一个 action;BusAutoConfiguration 自动化配置类被 @EnableBinding(SpringCloudBusClient.class) 所修饰。@EnableBinding 的用法在上期文章 干货|Spring Cloud Stream 体系及原理介绍 中已经说明,且它的 value 为 SpringCloudBusClient.class,会在 SpringCloudBusClient 中基于代理创建出 input 和 output 的 DirectChannel:public interface SpringCloudBusClient { String INPUT = “springCloudBusInput”; String OUTPUT = “springCloudBusOutput”; @Output(SpringCloudBusClient.OUTPUT) MessageChannel springCloudBusOutput(); @Input(SpringCloudBusClient.INPUT) SubscribableChannel springCloudBusInput();}springCloudBusInput 和 springCloudBusOutput 这两个 Binding 的属性可以通过配置文件进行修改(比如修改 topic):spring.cloud.stream.bindings: springCloudBusInput: destination: my-bus-topic springCloudBusOutput: destination: my-bus-topic消息的接收的发送:// BusAutoConfiguration@EventListener(classes = RemoteApplicationEvent.class) // 1public void acceptLocal(RemoteApplicationEvent event) { if (this.serviceMatcher.isFromSelf(event) && !(event instanceof AckRemoteApplicationEvent)) { // 2 this.cloudBusOutboundChannel.send(MessageBuilder.withPayload(event).build()); // 3 }}@StreamListener(SpringCloudBusClient.INPUT) // 4public void acceptRemote(RemoteApplicationEvent event) { if (event instanceof AckRemoteApplicationEvent) { if (this.bus.getTrace().isEnabled() && !this.serviceMatcher.isFromSelf(event) && this.applicationEventPublisher != null) { // 5 this.applicationEventPublisher.publishEvent(event); } // If it’s an ACK we are finished processing at this point return; } if (this.serviceMatcher.isForSelf(event) && this.applicationEventPublisher != null) { // 6 if (!this.serviceMatcher.isFromSelf(event)) { // 7 this.applicationEventPublisher.publishEvent(event); } if (this.bus.getAck().isEnabled()) { // 8 AckRemoteApplicationEvent ack = new AckRemoteApplicationEvent(this, this.serviceMatcher.getServiceId(), this.bus.getAck().getDestinationService(), event.getDestinationService(), event.getId(), event.getClass()); this.cloudBusOutboundChannel .send(MessageBuilder.withPayload(ack).build()); this.applicationEventPublisher.publishEvent(ack); } } if (this.bus.getTrace().isEnabled() && this.applicationEventPublisher != null) { // 9 // We are set to register sent events so publish it for local consumption, // irrespective of the origin this.applicationEventPublisher.publishEvent(new SentApplicationEvent(this, event.getOriginService(), event.getDestinationService(), event.getId(), event.getClass())); }}利用 Spring 事件的监听机制监听本地所有的 RemoteApplicationEvent 远程事件(比如 bus-env 会在本地发送 EnvironmentChangeRemoteApplicationEvent 事件,bus-refresh 会在本地发送 RefreshRemoteApplicationEvent 事件,这些事件在这里都会被监听到)。判断本地接收到的事件不是 AckRemoteApplicationEvent 远程确认事件(不然会死循环,一直接收消息,发送消息…)以及该事件是应用自身发送出去的(事件发送方是应用自身),如果都满足执行步骤 3。构造 Message 并将该远程事件作为 payload,然后使用 Spring Cloud Stream 构造的 Binding name 为 springCloudBusOutput 的 MessageChannel 将消息发送到 broker。@StreamListener 注解消费 Spring Cloud Stream 构造的 Binding name 为 springCloudBusInput 的 MessageChannel,接收的消息为远程消息。如果该远程事件是 AckRemoteApplicationEvent 远程确认事件并且应用开启了消息追踪 trace 开关,同时该远程事件不是应用自身发送的(事件发送方不是应用自身,表示事件是其它应用发送过来的),那么本地发送 AckRemoteApplicationEvent 远程确认事件表示应用确认收到了其它应用发送过来的远程事件,流程结束。如果该远程事件是其它应用发送给应用自身的(事件的接收方是应用自身),那么进行步骤 7 和 8,否则执行步骤 9。该远程事件不是应用自身发送(事件发送方不是应用自身)的话,将该事件以本地的方式发送出去。应用自身一开始已经在本地被对应的消息接收方处理了,无需再次发送。如果开启了 AckRemoteApplicationEvent 远程确认事件的开关,构造 AckRemoteApplicationEvent 事件并在远程和本地都发送该事件(本地发送是因为步骤 5 没有进行本地 AckRemoteApplicationEvent 事件的发送,也就是自身应用对自身应用确认; 远程发送是为了告诉其它应用,自身应用收到了消息)。如果开启了消息记录 Trace 的开关,本地构造并发送 SentApplicationEvent 事件bus-env 触发后所有节点的 EnvironmentChangeListener 监听到了配置的变化,控制台都会打印出以下信息:o.s.c.b.event.EnvironmentChangeListener : Received remote environment change request. Keys/values to update {hangzhou=alibaba}如果在本地监听远程确认事件 AckRemoteApplicationEvent,都会收到所有节点的信息,比如 node5 节点的控制台监听到的 AckRemoteApplicationEvent 事件如下:ServiceId [rocketmq-bus-node5:10005] listeners on {“type”:“AckRemoteApplicationEvent”,“timestamp”:1554124670484,“originService”:“rocketmq-bus-node5:10005”,“destinationService”:”",“id”:“375f0426-c24e-4904-bce1-5e09371fc9bc”,“ackId”:“750d033f-356a-4aad-8cf0-3481ace8698c”,“ackDestinationService”:"",“event”:“org.springframework.cloud.bus.event.EnvironmentChangeRemoteApplicationEvent”}ServiceId [rocketmq-bus-node5:10005] listeners on {“type”:“AckRemoteApplicationEvent”,“timestamp”:1554124670184,“originService”:“rocketmq-bus-node1:10001”,“destinationService”:"",“id”:“91f06cf1-4bd9-4dd8-9526-9299a35bb7cc”,“ackId”:“750d033f-356a-4aad-8cf0-3481ace8698c”,“ackDestinationService”:"",“event”:“org.springframework.cloud.bus.event.EnvironmentChangeRemoteApplicationEvent”}ServiceId [rocketmq-bus-node5:10005] listeners on {“type”:“AckRemoteApplicationEvent”,“timestamp”:1554124670402,“originService”:“rocketmq-bus-node2:10002”,“destinationService”:"",“id”:“7df3963c-7c3e-4549-9a22-a23fa90a6b85”,“ackId”:“750d033f-356a-4aad-8cf0-3481ace8698c”,“ackDestinationService”:"",“event”:“org.springframework.cloud.bus.event.EnvironmentChangeRemoteApplicationEvent”}ServiceId [rocketmq-bus-node5:10005] listeners on {“type”:“AckRemoteApplicationEvent”,“timestamp”:1554124670406,“originService”:“rocketmq-bus-node3:10003”,“destinationService”:"",“id”:“728b45ee-5e26-46c2-af1a-e8d1571e5d3a”,“ackId”:“750d033f-356a-4aad-8cf0-3481ace8698c”,“ackDestinationService”:"",“event”:“org.springframework.cloud.bus.event.EnvironmentChangeRemoteApplicationEvent”}ServiceId [rocketmq-bus-node5:10005] listeners on {“type”:“AckRemoteApplicationEvent”,“timestamp”:1554124670427,“originService”:“rocketmq-bus-node4:10004”,“destinationService”:"",“id”:“1812fd6d-6f98-4e5b-a38a-4b11aee08aeb”,“ackId”:“750d033f-356a-4aad-8cf0-3481ace8698c”,“ackDestinationService”:"",“event”:“org.springframework.cloud.bus.event.EnvironmentChangeRemoteApplicationEvent”}那么回到本章节开头提到的 4 个问题,我们分别做一下解答:消息是如何发送的: 在 BusAutoConfiguration#acceptLocal 方法中通过 Spring Cloud Stream 发送事件到 springCloudBus topic 中。消息是如何接收的: 在 BusAutoConfiguration#acceptRemote 方法中通过 Spring Cloud Stream 接收 springCloudBus topic 的消息。destination 是如何匹配的: 在 BusAutoConfiguration#acceptRemote 方法中接收远程事件方法里对 destination 进行匹配。远程事件收到后如何触发下一个 action: Bus 内部通过 Spring 的事件机制接收本地的 RemoteApplicationEvent 具体的实现事件再做下一步的动作(比如 EnvironmentChangeListener 接收了 EnvironmentChangeRemoteApplicationEvent 事件, RefreshListener 接收了 RefreshRemoteApplicationEvent 事件)。总结Spring Cloud Bus 自身内容还是比较少的,不过还是需要提前了解 Spring Cloud Stream 体系以及 Spring 自身的事件机制,在此基础上,才能更好地理解 Spring Cloud Bus 对本地事件和远程事件的处理逻辑。目前 Bus 内置的远程事件较少,大多数为配置相关的事件,我们可以继承 RemoteApplicationEvent 并配合 @RemoteApplicationEventScan 注解构建自身的微服务消息体系。本文作者:中间件小哥阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

April 15, 2019 · 4 min · jiezi

Spring Cloud 参考文档(Spring Cloud Config快速入门)

Spring Cloud Config快速入门这个快速入门使用Spring Cloud Config Server的服务器和客户端。首先,启动服务器,如下所示:$ cd spring-cloud-config-server$ ../mvnw spring-boot:run服务器是一个Spring Boot应用程序,因此如果你愿意,可以从IDE运行它(主类是ConfigServerApplication)。接下来尝试一个客户端,如下所示:$ curl localhost:8888/foo/development{“name”:“foo”,“label”:“master”,“propertySources”:[ {“name”:“https://github.com/scratches/config-repo/foo-development.properties”,“source”:{“bar”:“spam”}}, {“name”:“https://github.com/scratches/config-repo/foo.properties”,“source”:{“foo”:“bar”}}]}定位属性源的默认策略是克隆git存储库(在spring.cloud.config.server.git.uri)并使用它来初始化一个微型SpringApplication,微型应用程序的Environment用于枚举属性源并在JSON端点发布它们。HTTP服务具有以下形式的资源:/{application}/{profile}[/{label}]/{application}-{profile}.yml/{label}/{application}-{profile}.yml/{application}-{profile}.properties/{label}/{application}-{profile}.propertiesapplication是SpringApplication中的spring.config.name(常规Spring Boot应用程序中的正常application),profile是一个活动的配置文件(或以逗号分隔的属性列表),label是一个可选的git标签(默认为master)。Spring Cloud Config Server从git存储库(必须提供)中提取远程客户端的配置,如以下示例所示:spring: cloud: config: server: git: uri: https://github.com/spring-cloud-samples/config-repo客户端使用要在应用程序中使用这些功能,你可以将其构建为依赖于spring-cloud-config-client的Spring Boot应用程序(例如,请参阅config-client或示例应用程序的测试用例)。添加依赖项最方便的方法是使用Spring Boot启动器org.springframework.cloud:spring-cloud-starter-config,还有一个用于Maven用户的父pom和BOM(spring-cloud-starter-parent)以及一个用于Gradle和Spring CLI用户的Spring IO版本管理属性文件,以下示例显示了典型的Maven配置:<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>{spring-boot-docs-version}</version> <relativePath /> <!– lookup parent from repository –> </parent><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><dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-config</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency></dependencies><build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins></build> <!– repositories also needed for snapshots and milestones –>现在你可以创建一个标准的Spring Boot应用程序,例如以下HTTP服务器:@SpringBootApplication@RestControllerpublic class Application { @RequestMapping("/") public String home() { return “Hello World!”; } public static void main(String[] args) { SpringApplication.run(Application.class, args); }}当此HTTP服务器运行时,它从端口8888上的默认本地配置服务器(如果它正在运行)中获取外部配置,要修改启动行为,可以使用bootstrap.properties更改配置服务器的位置(类似于application.properties但适用于应用程序上下文的bootstrap阶段),如以下示例所示:spring.cloud.config.uri: http://myconfigserver.com默认情况下,如果未设置应用程序名称,则将使用application,要修改名称,可以将以下属性添加到bootstrap.properties文件中:spring.application.name: myapp设置属性${spring.application.name}时,不要在应用程序名称前加上保留字application-,以防止解析正确属性源的问题。bootstrap属性在/env端点中显示为高优先级属性源,如以下示例所示。$ curl localhost:8080/env{ “profiles”:[], “configService:https://github.com/spring-cloud-samples/config-repo/bar.properties”:{“foo”:“bar”}, “servletContextInitParams”:{}, “systemProperties”:{…}, …}名为configService:<URL of remote repository>/<file name>的属性源包含值为bar且具有最高优先级的foo属性。属性源名称中的URL是git存储库,而不是配置服务器URL。上一篇:Spring Cloud Commons:通用的抽象 ...

April 13, 2019 · 1 min · jiezi

Spring Cloud 参考文档(Spring Cloud Commons:通用的抽象)

Spring Cloud Commons:通用的抽象服务发现、负载均衡和断路器等模式适用于所有Spring Cloud客户端都可以使用的通用抽象层,独立于实现(例如,使用Eureka或Consul发现)。@EnableDiscoveryClientSpring Cloud Commons提供@EnableDiscoveryClient注解,这将使用META-INF/spring.factories查找DiscoveryClient接口的实现。Discovery Client的实现将配置类添加到org.springframework.cloud.client.discovery.EnableDiscoveryClient键下的spring.factories,DiscoveryClient实现的示例包括Spring Cloud Netflix Eureka,Spring Cloud Consul Discovery和Spring Cloud Zookeeper Discovery。默认情况下,DiscoveryClient的实现会使用远程发现服务器自动注册本地Spring Boot服务器,通过在@EnableDiscoveryClient中设置autoRegister=false可以禁用此行为。@EnableDiscoveryClient已不再需要,你可以在类路径上放置DiscoveryClient实现,以使Spring Boot应用程序向服务发现服务器注册。健康指示器Commons创建了一个Spring Boot HealthIndicator,DiscoveryClient实现可以通过实现DiscoveryHealthIndicator来参与,要禁用混合HealthIndicator,请设置spring.cloud.discovery.client.composite-indicator.enabled=false。基于DiscoveryClient的通用HealthIndicator是自动配置的(DiscoveryClientHealthIndicator)。要禁用它,请设置spring.cloud.discovery.client.health-indicator.enabled=false,要禁用DiscoveryClientHealthIndicator的description字段,请设置spring.cloud.discovery.client.health-indicator.include-description=false,否则,它可能会像卷起的HealthIndicator的description一样冒出来。排序DiscoveryClient实例DiscoveryClient接口扩展了Ordered,这在使用多个发现客户端时很有用,因为它允许你定义返回的发现客户端的顺序,类似于你可以如何排序Spring应用程序加载的bean。默认情况下,任何DiscoveryClient的顺序都设置为0,如果要为自定义DiscoveryClient实现设置不同的顺序,只需重写getOrder()方法,以便它返回适合你的设置的值。除此之外,你还可以使用属性来设置Spring Cloud提供的DiscoveryClient实现的顺序,其中包括ConsulDiscoveryClient,EurekaDiscoveryClient和ZookeeperDiscoveryClient,为此,你只需将spring.cloud.{clientIdentifier}.discovery.order(或Eureka的eureka.client.order)属性设置为所需的值。ServiceRegistryCommons现在提供一个ServiceRegistry接口,提供register(Registration)和deregister(Registration)等方法,让你提供自定义注册服务,Registration是一个标记接口。以下示例显示ServiceRegistry的使用:@Configuration@EnableDiscoveryClient(autoRegister=false)public class MyConfiguration { private ServiceRegistry registry; public MyConfiguration(ServiceRegistry registry) { this.registry = registry; } // called through some external process, such as an event or a custom actuator endpoint public void register() { Registration registration = constructRegistration(); this.registry.register(registration); }}每个ServiceRegistry实现都有自己的Registry实现。ZookeeperRegistration与ZookeeperServiceRegistry一起使用EurekaRegistration与EurekaServiceRegistry一起使用ConsulRegistration与ConsulServiceRegistry一起使用如果你使用的是ServiceRegistry接口,则需要为正在使用的ServiceRegistry实现传递正确的Registry实现。ServiceRegistry自动注册默认情况下,ServiceRegistry实现会自动注册正在运行的服务,要禁用该行为,你可以设置: @EnableDiscoveryClient(autoRegister=false)永久禁用自动注册, spring.cloud.service-registry.auto-registration.enabled=false通过配置禁用行为。ServiceRegistry自动注册事件当服务自动注册时,将触发两个事件,第一个事件名为InstancePreRegisteredEvent,在注册服务之前触发,第二个事件名为InstanceRegisteredEvent,在注册服务后触发,你可以注册一个ApplicationListener来监听并响应这些事件。如果spring.cloud.service-registry.auto-registration.enabled设置为false,则不会触发这些事件。Service Registry Actuator端点Spring Cloud Commons提供/service-registry执行器端点,此端点依赖于Spring Application Context中的Registration bean,使用GET调用/service-registry返回Registration的状态,将POST用于具有JSON体的同一端点会将当前Registration的状态更改为新值,JSON体必须包含具有首选值的status字段。在更新状态和为状态返回的值时,请参阅用于允许值的ServiceRegistry实现的文档,例如,Eureka支持的状态是UP、DOWN、OUT_OF_SERVICE和UNKNOWN。Spring RestTemplate作为负载均衡客户端RestTemplate可以自动配置为使用ribbon,要创建负载均衡的RestTemplate,请创建RestTemplate @Bean并使用@LoadBalanced限定符,如以下示例所示:@Configurationpublic class MyConfiguration { @LoadBalanced @Bean RestTemplate restTemplate() { return new RestTemplate(); }}public class MyClass { @Autowired private RestTemplate restTemplate; public String doOtherStuff() { String results = restTemplate.getForObject(“http://stores/stores”, String.class); return results; }}不再通过自动配置创建RestTemplate bean,单个应用程序必须创建它。URI需要使用虚拟主机名(即服务名称,而不是主机名),Ribbon客户端用于创建完整的物理地址,有关如何设置RestTemplate的详细信息,请参见RibbonAutoConfiguration。Spring WebClient作为负载均衡客户端WebClient可以自动配置为使用LoadBalancerClient,要创建负载均衡的WebClient,请创建WebClient.Builder @Bean并使用@LoadBalanced限定符,如以下示例所示:@Configurationpublic class MyConfiguration { @Bean @LoadBalanced public WebClient.Builder loadBalancedWebClientBuilder() { return WebClient.builder(); }}public class MyClass { @Autowired private WebClient.Builder webClientBuilder; public Mono<String> doOtherStuff() { return webClientBuilder.build().get().uri(“http://stores/stores”) .retrieve().bodyToMono(String.class); }}URI需要使用虚拟主机名(即服务名称,而不是主机名),Ribbon客户端用于创建完整的物理地址。重试失败的请求可以将负载均衡的RestTemplate配置为重试失败的请求,默认情况下,禁用此逻辑,你可以通过将Spring Retry添加到应用程序的类路径来启用它。负载均衡的RestTemplate支持与重试失败的请求相关的一些Ribbon配置值,你可以使用client.ribbon.MaxAutoRetries、client.ribbon.MaxAutoRetriesNextServer和client.ribbon.OkToRetryOnAllOperations属性,如果要在类路径上使用Spring Retry禁用重试逻辑,可以设置spring.cloud.loadbalancer.retry.enabled=false,有关这些属性的说明,请参阅Ribbon文档。如果要在重试中实现BackOffPolicy,则需要创建LoadBalancedRetryFactory类型的bean并覆盖createBackOffPolicy方法:@Configurationpublic class MyConfiguration { @Bean LoadBalancedRetryFactory retryFactory() { return new LoadBalancedRetryFactory() { @Override public BackOffPolicy createBackOffPolicy(String service) { return new ExponentialBackOffPolicy(); } }; }}前面示例中的client应替换为你的Ribbon客户端的名称。如果要将一个或多个RetryListener实现添加到重试功能中,你需要创建一个类型为LoadBalancedRetryListenerFactory的bean并返回你要用于给定服务的RetryListener数组,如以下示例所示:@Configurationpublic class MyConfiguration { @Bean LoadBalancedRetryListenerFactory retryListenerFactory() { return new LoadBalancedRetryListenerFactory() { @Override public RetryListener[] createRetryListeners(String service) { return new RetryListener[]{new RetryListener() { @Override public <T, E extends Throwable> boolean open(RetryContext context, RetryCallback<T, E> callback) { //TODO Do you business… return true; } @Override public <T, E extends Throwable> void close(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) { //TODO Do you business… } @Override public <T, E extends Throwable> void onError(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) { //TODO Do you business… } }}; } }; }}多个RestTemplate对象如果你想要一个非负载均衡的RestTemplate,请创建一个RestTemplate bean并将其注入,要访问负载均衡的RestTemplate,请在创建@Bean时使用@LoadBalanced限定符,如以下示例所示:@Configurationpublic class MyConfiguration { @LoadBalanced @Bean RestTemplate loadBalanced() { return new RestTemplate(); } @Primary @Bean RestTemplate restTemplate() { return new RestTemplate(); }}public class MyClass { @Autowired private RestTemplate restTemplate; @Autowired @LoadBalanced private RestTemplate loadBalanced; public String doOtherStuff() { return loadBalanced.getForObject(“http://stores/stores”, String.class); } public String doStuff() { return restTemplate.getForObject(“http://example.com”, String.class); }}请注意在前面示例中的普通RestTemplate声明中使用@Primary注解来消除无条件的@Autowired注入的歧义。如果你看到java.lang.IllegalArgumentException: Can not set org.springframework.web.client.RestTemplate field com.my.app.Foo.restTemplate to com.sun.proxy.$Proxy89,尝试注入RestOperations或设置spring.aop.proxyTargetClass=true。Spring WebFlux WebClient作为负载均衡客户端可以将WebClient配置为使用LoadBalancerClient,如果spring-webflux位于类路径上,则自动配置LoadBalancerExchangeFilterFunction,以下示例显示如何配置WebClient以使用负载均衡:public class MyClass { @Autowired private LoadBalancerExchangeFilterFunction lbFunction; public Mono<String> doOtherStuff() { return WebClient.builder().baseUrl(“http://stores”) .filter(lbFunction) .build() .get() .uri("/stores") .retrieve() .bodyToMono(String.class); }}URI需要使用虚拟主机名(即服务名称,而不是主机名),LoadBalancerClient用于创建完整的物理地址。忽略网络接口有时,忽略某些命名的网络接口以便从Service Discovery注册中排除它们(例如,在Docker容器中运行时)是有用的,可以设置正则表达式列表以使所需的网络接口被忽略,以下配置忽略docker0接口和以veth开头的所有接口:application.ymlspring: cloud: inetutils: ignoredInterfaces: - docker0 - veth.你还可以使用正则表达式列表强制仅使用指定的网络地址,如以下示例所示:bootstrap.ymlspring: cloud: inetutils: preferredNetworks: - 192.168 - 10.0你还可以强制仅使用站点本地地址,如以下示例所示:application.ymlspring: cloud: inetutils: useOnlySiteLocalInterfaces: true有关构成站点本地地址的更多详细信息,请参阅Inet4Address.html.isSiteLocalAddress()。HTTP客户端工厂Spring Cloud Commons提供用于创建Apache HTTP客户端(ApacheHttpClientFactory)和OK HTTP客户端(OkHttpClientFactory)的bean,仅当OK HTTP jar位于类路径上时,才会创建OkHttpClientFactory bean。此外,Spring Cloud Commons提供了创建用于两个客户端使用的连接管理器的bean:Apache HTTP客户端的ApacheHttpClientConnectionManagerFactory和OK HTTP客户端的OkHttpClientConnectionPoolFactory。如果要自定义在下游项目中创建HTTP客户端的方式,可以提供自己的这些bean实现,此外,如果你提供类型为HttpClientBuilder或OkHttpClient.Builder的bean,则默认工厂使用这些构建器作为返回到下游项目的构建器的基础,你还可以通过将spring.cloud.httpclientfactories.apache.enabled或spring.cloud.httpclientfactories.ok.enabled设置为false来禁用这些bean的创建。启用功能Spring Cloud Commons提供/features执行器端点,此端点返回类路径上可用的功能以及它们是否已启用,返回的信息包括功能类型、名称、版本和供应商。功能类型有两种类型的’功能’:抽象和命名。抽象功能是定义接口或抽象类并创建实现(如DiscoveryClient、LoadBalancerClient或LockService)的功能,抽象类或接口用于在上下文中查找该类型的bean,显示的版本是bean.getClass().getPackage().getImplementationVersion()。命名功能是没有他们实现的特定类的功能,例如“断路器”,“API网关”,“Spring Cloud Bus”等,这些功能需要名称和bean类型。声明功能任何模块都可以声明任意数量的HasFeature bean,如以下示例所示:@Beanpublic HasFeatures commonsFeatures() { return HasFeatures.abstractFeatures(DiscoveryClient.class, LoadBalancerClient.class);}@Beanpublic HasFeatures consulFeatures() { return HasFeatures.namedFeatures( new NamedFeature(“Spring Cloud Bus”, ConsulBusAutoConfiguration.class), new NamedFeature(“Circuit Breaker”, HystrixCommandAspect.class));}@BeanHasFeatures localFeatures() { return HasFeatures.builder() .abstractFeature(Foo.class) .namedFeature(new NamedFeature(“Bar Feature”, Bar.class)) .abstractFeature(Baz.class) .build();}这些bean中的每一个都应该放在一个受到适当保护的@Configuration中。Spring Cloud兼容性验证由于某些用户在设置Spring Cloud应用程序时遇到问题,因此决定添加兼容性验证机制,如果你当前的设置与Spring Cloud要求不兼容,并且报告显示出现了什么问题,它将会中断。目前我们验证哪个版本的Spring Boot被添加到你的类路径中。报告示例APPLICATION FAILED TO START*Description:Your project setup is incompatible with our requirements due to following reasons:- Spring Boot [2.1.0.RELEASE] is not compatible with this Spring Cloud release trainAction:Consider applying the following actions:- Change Spring Boot version to one of the following versions [1.2.x, 1.3.x] .You can find the latest Spring Boot versions here [https://spring.io/projects/spring-boot#learn].If you want to learn more about the Spring Cloud Release train compatibility, you can visit this page [https://spring.io/projects/spring-cloud#overview] and check the [Release Trains] section.要禁用此功能,请将spring.cloud.compatibility-verifier.enabled设置为false,如果要覆盖兼容的Spring Boot版本,只需使用逗号分隔的兼容Spring Boot版本列表设置spring.cloud.compatibility-verifier.compatible-boot-versions属性。上一篇:Spring Cloud Context:应用程序上下文服务 ...

April 12, 2019 · 3 min · jiezi

Spring Cloud Alibaba基础教程:使用Sentinel实现接口限流

最近管点闲事浪费了不少时间,感谢网友libinwalan的留言提醒。及时纠正路线,继续跟大家一起学习Spring Cloud Alibaba。Nacos作为注册中心和配置中心的基础教程,到这里先告一段落,后续与其他结合的内容等讲到的时候再一起拿出来说,不然内容会有点跳跃。接下来我们就来一起学习一下Spring Cloud Alibaba下的另外一个重要组件:Sentinel。Sentinel是什么Sentinel的官方标题是:分布式系统的流量防卫兵。从名字上来看,很容易就能猜到它是用来作服务稳定性保障的。对于服务稳定性保障组件,如果熟悉Spring Cloud的用户,第一反应应该就是Hystrix。但是比较可惜的是Netflix已经宣布对Hystrix停止更新。那么,在未来我们还有什么更好的选择呢?除了Spring Cloud官方推荐的resilience4j之外,目前Spring Cloud Alibaba下整合的Sentinel也是用户可以重点考察和选型的目标。Sentinel的功能和细节比较多,一篇内容很难介绍完整。所以下面我会分多篇来一一介绍Sentinel的重要功能。本文就先从限流入手,说说如何把Sentinel整合到Spring Cloud应用中,以及如何使用Sentinel Dashboard来配置限流规则。通过这个简单的例子,先将这一套基础配置搭建起来。使用Sentinel实现接口限流Sentinel的使用分为两部分:sentinel-dashboard:与hystrix-dashboard类似,但是它更为强大一些。除了与hystrix-dashboard一样提供实时监控之外,还提供了流控规则、熔断规则的在线维护等功能。客户端整合:每个微服务客户端都需要整合sentinel的客户端封装与配置,才能将监控信息上报给dashboard展示以及实时的更改限流或熔断规则等。下面我们就分两部分来看看,如何使用Sentienl来实现接口限流。部署Sentinel Dashboard本文采用的spring cloud alibaba版本是0.2.1,可以查看依赖发现当前版本使用的是sentinel 1.4.0。为了顺利完成本文的内容,我们可以挑选同版本的sentinel dashboard来使用是最稳妥的。下载地址:https://github.com/alibaba/Se…其他版本:https://github.com/alibaba/Se…同以往的Spring Cloud教程一样,这里也不推荐大家跨版本使用,不然可能会出现各种各样的问题。通过命令启动:java -jar sentinel-dashboard-1.4.0.jarsentinel-dashboard不像Nacos的服务端那样提供了外置的配置文件,比较容易修改参数。不过不要紧,由于sentinel-dashboard是一个标准的spring boot应用,所以如果要自定义端口号等内容的话,可以通过在启动命令中增加参数来调整,比如:-Dserver.port=8888。默认情况下,sentinel-dashboard以8080端口启动,所以可以通过访问:localhost:8080来验证是否已经启动成功,如果一切顺利的话,可以看到如下页面:整合Sentinel第一步:在Spring Cloud应用的pom.xml中引入Spring Cloud Alibaba的Sentinel模块: <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.2</version> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency></dependencies>第二步:在Spring Cloud应用中通过spring.cloud.sentinel.transport.dashboard参数配置sentinel dashboard的访问地址,比如:spring.application.name=alibaba-sentinel-rate-limitingserver.port=8001# sentinel dashboardspring.cloud.sentinel.transport.dashboard=localhost:8080第三步:创建应用主类,并提供一个rest接口,比如:@SpringBootApplicationpublic class TestApplication { public static void main(String[] args) { SpringApplication.run(TestApplication.class, args); } @Slf4j @RestController static class TestController { @GetMapping("/hello") public String hello() { return “didispace.com”; } }}第四步:启动应用,然后通过postman或者curl访问几下localhost:8001/hello接口。$ curl localhost:8001/hellodidispace.com此时,在上一节启动的Sentinel Dashboard中就可以当前我们启动的alibaba-sentinel-rate-limiting这个服务以及接口调用的实时监控了。具体如下图所示:配置限流规则在完成了上面的两节之后,我们在alibaba-sentinel-rate-limiting服务下,点击簇点链路菜单,可以看到如下界面:其中/hello接口,就是我们上一节中实现并调用过的接口。通过点击流控按钮,来为该接口设置限流规则,比如:这里做一个最简单的配置:阈值类型选择:QPS单机阈值:2综合起来的配置效果就是,该接口的限流策略是每秒最多允许2个请求进入。点击新增按钮之后,可以看到如下界面:其实就是左侧菜单中流控规则的界面,这里可以看到当前设置的所有限流策略。验证限流规则在完成了上面所有内容之后,我们可以尝试一下快速的调用这个接口,看看是否会触发限流控制,比如:$ curl localhost:8001/hellodidispace.com$ curl localhost:8001/hellodidispace.com$ curl localhost:8001/helloBlocked by Sentinel (flow limiting)可以看到,快速的调用两次/hello接口之后,第三次调用被限流了。代码示例本文介绍内容的客户端代码,示例读者可以通过查看下面仓库中的alibaba-sentinel-rate-limiting项目:Github:https://github.com/dyc87112/SpringCloud-Learning/Gitee:https://gitee.com/didispace/SpringCloud-Learning/如果您对这些感兴趣,欢迎star、follow、收藏、转发给予支持!参考资料下面是Sentinel的仓库地址与官方文档,读者也可以自己查阅文档学习:GithubSentinel官方文档Spring Cloud Alibaba Sentinel文档专题推荐Spring Boot基础教程Spring Cloud基础教程 ...

April 11, 2019 · 1 min · jiezi

干货|Spring Cloud Stream 体系及原理介绍

Spring Cloud Stream 在 Spring Cloud 体系内用于构建高度可扩展的基于事件驱动的微服务,其目的是为了简化消息在 Spring Cloud 应用程序中的开发。Spring Cloud Stream (后面以 SCS 代替 Spring Cloud Stream) 本身内容很多,而且它还有很多外部的依赖,想要熟悉 SCS,必须要先了解 Spring Messaging 和 Spring Integration 这两个项目,接下来文章将从以下几点跟大家进行介绍:什么是 Spring Messaging;什么是 Spring Integration;什么是 SCS及其功能;Spring MessagingSpring Messaging 是 Spring Framework 中的一个模块,其作用就是统一消息的编程模型。比如消息 Messaging 对应的模型就包括一个消息体 Payload 和消息头 Header:package org.springframework.messaging;public interface Message<T> { T getPayload(); MessageHeaders getHeaders();}消息通道 MessageChannel 用于接收消息,调用 send 方法可以将消息发送至该消息通道中 :@FunctionalInterfacepublic interface MessageChannel { long INDEFINITE_TIMEOUT = -1; default boolean send(Message<?> message) { return send(message, INDEFINITE_TIMEOUT); } boolean send(Message<?> message, long timeout);}消息通道里的消息如何被消费呢?由消息通道的子接口可订阅的消息通道 SubscribableChannel 实现,被 MessageHandler 消息处理器所订阅:public interface SubscribableChannel extends MessageChannel { boolean subscribe(MessageHandler handler); boolean unsubscribe(MessageHandler handler);}由MessageHandler 真正地消费/处理消息:@FunctionalInterfacepublic interface MessageHandler { void handleMessage(Message<?> message) throws MessagingException;}Spring Messaging 内部在消息模型的基础上衍生出了其它的一些功能,如:消息接收参数及返回值处理:消息接收参数处理器 HandlerMethodArgumentResolver 配合 @Header, @Payload 等注解使用;消息接收后的返回值处理器 HandlerMethodReturnValueHandler 配合 @SendTo 注解使用;消息体内容转换器 MessageConverter;统一抽象的消息发送模板 AbstractMessageSendingTemplate;消息通道拦截器 ChannelInterceptor;Spring IntegrationSpring Integration 提供了 Spring 编程模型的扩展用来支持企业集成模式(Enterprise Integration Patterns),是对 Spring Messaging 的扩展。它提出了不少新的概念,包括消息的路由 MessageRoute、消息的分发 MessageDispatcher、消息的过滤 Filter、消息的转换 Transformer、消息的聚合 Aggregator、消息的分割 Splitter 等等。同时还提供了 MessageChannel 和MessageHandler 的实现,分别包括 DirectChannel、ExecutorChannel、PublishSubscribeChannel 和MessageFilter、ServiceActivatingHandler、MethodInvokingSplitter 等内容。首先为大家介绍几种消息的处理方式:消息的分割:消息的聚合:消息的过滤:消息的分发:接下来,我们以一个最简单的例子来尝试一下 Spring Integration:SubscribableChannel messageChannel = new DirectChannel(); // 1messageChannel.subscribe(msg -> { // 2 System.out.println(“receive: " + msg.getPayload());});messageChannel.send(MessageBuilder.withPayload(“msg from alibaba”).build()); // 3构造一个可订阅的消息通道 messageChannel;使用 MessageHandler 去消费这个消息通道里的消息;发送一条消息到这个消息通道,消息最终被消息通道里的 MessageHandler 所消费,最后控制台打印出: receive: msg from alibaba;DirectChannel 内部有个 UnicastingDispatcher 类型的消息分发器,会分发到对应的消息通道 MessageChannel 中,从名字也可以看出来,UnicastingDispatcher 是个单播的分发器,只能选择一个消息通道。那么如何选择呢? 内部提供了 LoadBalancingStrategy 负载均衡策略,默认只有轮询的实现,可以进行扩展。我们对上段代码做一点修改,使用多个 MessageHandler 去处理消息:SubscribableChannel messageChannel = new DirectChannel();messageChannel.subscribe(msg -> { System.out.println(“receive1: " + msg.getPayload());});messageChannel.subscribe(msg -> { System.out.println(“receive2: " + msg.getPayload());});messageChannel.send(MessageBuilder.withPayload(“msg from alibaba”).build());messageChannel.send(MessageBuilder.withPayload(“msg from alibaba”).build());由于 DirectChannel 内部的消息分发器是 UnicastingDispatcher 单播的方式,并且采用轮询的负载均衡策略,所以这里两次的消费分别对应这两个 MessageHandler。控制台打印出:receive1: msg from alibabareceive2: msg from alibaba既然存在单播的消息分发器 UnicastingDispatcher,必然也会存在广播的消息分发器,那就是 BroadcastingDispatcher,它被 PublishSubscribeChannel 这个消息通道所使用。广播消息分发器会把消息分发给所有的 MessageHandler:SubscribableChannel messageChannel = new PublishSubscribeChannel();messageChannel.subscribe(msg -> { System.out.println(“receive1: " + msg.getPayload());});messageChannel.subscribe(msg -> { System.out.println(“receive2: " + msg.getPayload());});messageChannel.send(MessageBuilder.withPayload(“msg from alibaba”).build());messageChannel.send(MessageBuilder.withPayload(“msg from alibaba”).build());发送两个消息,都被所有的 MessageHandler 所消费。控制台打印:receive1: msg from alibabareceive2: msg from alibabareceive1: msg from alibabareceive2: msg from alibabaSpring Cloud StreamSCS与各模块之间的关系是:SCS 在 Spring Integration 的基础上进行了封装,提出了 Binder, Binding, @EnableBinding, @StreamListener 等概念;SCS 与 Spring Boot Actuator 整合,提供了 /bindings, /channels endpoint;SCS 与 Spring Boot Externalized Configuration 整合,提供了 BindingProperties, BinderProperties 等外部化配置类;SCS 增强了消息发送失败的和消费失败情况下的处理逻辑等功能。SCS 是 Spring Integration 的加强,同时与 Spring Boot 体系进行了融合,也是 Spring Cloud Bus 的基础。它屏蔽了底层消息中间件的实现细节,希望以统一的一套 API 来进行消息的发送/消费,底层消息中间件的实现细节由各消息中间件的 Binder 完成。Binder 是提供与外部消息中间件集成的组件,为构造 Binding提供了 2 个方法,分别是 bindConsumer 和 bindProducer ,它们分别用于构造生产者和消费者。目前官方的实现有 Rabbit Binder 和 Kafka Binder, Spring Cloud Alibaba 内部已经实现了 RocketMQ Binder。从图中可以看出,Binding 是连接应用程序跟消息中间件的桥梁,用于消息的消费和生产。我们来看一个最简单的使用 RocketMQ Binder 的例子,然后分析一下它的底层处理原理:启动类及消息的发送:@SpringBootApplication@EnableBinding({ Source.class, Sink.class }) // 1public class SendAndReceiveApplication { public static void main(String[] args) { SpringApplication.run(SendAndReceiveApplication.class, args); } @Bean // 2 public CustomRunner customRunner() { return new CustomRunner(); } public static class CustomRunner implements CommandLineRunner { @Autowired private Source source; @Override public void run(String… args) throws Exception { int count = 5; for (int index = 1; index <= count; index++) { source.output().send(MessageBuilder.withPayload(“msg-” + index).build()); // 3 } } }}消息的接收:@Servicepublic class StreamListenerReceiveService { @StreamListener(Sink.INPUT) // 4 public void receiveByStreamListener1(String receiveMsg) { System.out.println(“receiveByStreamListener: " + receiveMsg); }}这段代码很简单,没有涉及到 RocketMQ 相关的代码,消息的发送和接收都是基于 SCS 体系完成的。如果想切换成 RabbitMQ 或 kafka,只需修改配置文件即可,代码无需修改。我们分析这段代码的原理:@EnableBinding 对应的两个接口属性 Source 和 Sink 是 SCS 内部提供的。SCS 内部会基于 Source 和 Sink 构造 BindableProxyFactory,且对应的 output 和 input 方法返回的 MessageChannel 是 DirectChannel。output 和 input 方法修饰的注解对应的 value 是配置文件中 binding 的 name。public interface Source { String OUTPUT = “output”; @Output(Source.OUTPUT) MessageChannel output();}public interface Sink { String INPUT = “input”; @Input(Sink.INPUT) SubscribableChannel input();}配置文件里 bindings 的 name 为 output 和 input,对应 Source 和 Sink 接口的方法上的注解里的 value:spring.cloud.stream.bindings.output.destination=test-topicspring.cloud.stream.bindings.output.content-type=text/plainspring.cloud.stream.rocketmq.bindings.output.producer.group=demo-groupspring.cloud.stream.bindings.input.destination=test-topicspring.cloud.stream.bindings.input.content-type=text/plainspring.cloud.stream.bindings.input.group=test-group1构造 CommandLineRunner,程序启动的时候会执行 CustomRunner 的 run 方法。调用 Source 接口里的 output 方法获取 DirectChannel,并发送消息到这个消息通道中。这里跟之前 Spring Integration 章节里的代码一致。Source 里的 output 发送消息到 DirectChannel 消息通道之后会被 AbstractMessageChannelBinder#SendingHandler 这个 MessageHandler 处理,然后它会委托给 AbstractMessageChannelBinder#createProducerMessageHandler 创建的 MessageHandler 处理(该方法由不同的消息中间件实现);不同的消息中间件对应的 AbstractMessageChannelBinder#createProducerMessageHandler 方法返回的 MessageHandler 内部会把 Spring Message 转换成对应中间件的 Message 模型并发送到对应中间件的 broker;使用 @StreamListener 进行消息的订阅。请注意,注解里的 Sink.input 对应的值是 “input”,会根据配置文件里 binding 对应的 name 为 input 的值进行配置:不同的消息中间件对应的 AbstractMessageChannelBinder#createConsumerEndpoint 方法会使用 Consumer 订阅消息,订阅到消息后内部会把中间件对应的 Message 模型转换成 Spring Message;消息转换之后会把 Spring Message 发送至 name 为 input 的消息通道中;@StreamListener 对应的 StreamListenerMessageHandler 订阅了 name 为 input 的消息通道,进行了消息的消费;这个过程文字描述有点啰嗦,用一张图总结一下(黄色部分涉及到各消息中间件的 Binder 实现以及 MQ 基本的订阅发布功能):SCS 章节的最后,我们来看一段 SCS 关于消息的处理方式的一段代码:@StreamListener(value = Sink.INPUT, condition = “headers[‘index’]==‘1’")public void receiveByHeader(Message msg) { System.out.println(“receive by headers[‘index’]==‘1’: " + msg);}@StreamListener(value = Sink.INPUT, condition = “headers[‘index’]==‘9999’")public void receivePerson(@Payload Person person) { System.out.println(“receive Person: " + person);}@StreamListener(value = Sink.INPUT)public void receiveAllMsg(String msg) { System.out.println(“receive allMsg by StreamListener. content: " + msg);}@StreamListener(value = Sink.INPUT)public void receiveHeaderAndMsg(@Header(“index”) String index, Message msg) { System.out.println(“receive by HeaderAndMsg by StreamListener. content: " + msg);}有没有发现这段代码跟 Spring MVC Controller 中接收请求的代码很像? 实际上他们的架构都是类似的,Spring MVC 对于 Controller 中参数和返回值的处理类分别是 org.springframework.web.method.support.HandlerMethodArgumentResolver、 org.springframework.web.method.support.HandlerMethodReturnValueHandler。Spring Messaging 中对于参数和返回值的处理类之前也提到过,分别是 org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolver、org.springframework.messaging.handler.invocation.HandlerMethodReturnValueHandler。它们的类名一模一样,甚至内部的方法名也一样。总结上图是 SCS 体系相关类说明的总结,关于 SCS 以及 RocketMQ Binder 更多相关的示例,可以参考 RocketMQ Binder Demos,包含了消息的聚合、分割、过滤;消息异常处理;消息标签、sql过滤;同步、异步消费等等。下一篇文章,我们将分析消息总线(Spring Cloud Bus) 在 Spring Cloud 体系中的作用,并逐步展开,分析 Spring Cloud Alibaba 中的 RocketMQ Binder 是如何实现 Spring Cloud Stream 标准的。本文作者:中间件小哥阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

April 9, 2019 · 4 min · jiezi

Spring Cloud Alibaba到底坑不坑?

之前我发过一篇《说说我为什么看好Spring Cloud Alibaba》,然后这两天有网友给我转了这篇文章《坑爹项目spring-cloud-alibaba,我们也来一个》,问我的看法是怎么样的,聊天时候简单说了一下。今天在家休息,抽空整理一下内容,逐点说一下我的看法,主要还是觉得这篇文章博眼球的成分高一些,因为这篇文章的解读与之前其他某些自媒体发布的《Eureka 2.0 开源工作宣告停止,继续使用风险自负》一文有异曲同工之“妙”,如果读者没有真正的理解Spring Cloud与Spring Cloud Alibaba,就很有可能会对它们有什么误解,然后产生这样的想法:感觉很有道理,这东西真垃圾标题很燃,必须转发下面具体来说说该文章中,那些我认为不太正确的解读:第一点:远程调用RPC看看这篇文章的解读:SpringCloud默认的是Feign和Ribbon,主要是提供了远程调用请求和解析,以及负载均衡的功能。客观点来说,如果不用这两个组件,就会越来越四不像,干脆也别叫SpringCloud了,所以替换不得。RPC会大量使用动态代理的功能,将你的字符串或者配置(因为网络传输方便)搞成动态的接口。你也可以写一个RPC进行集成,有很多教程教你手撸一个。爸爸版的集成了个dubbo,dubbo就是个RPC。所以你一用这玩意,其他的一些关键组件也得跟着全套的换,组件就不叫组件了!作者认为Spring Cloud的负载均衡和远程调用必须使用Feign和Ribbon,这是Spring Cloud的默认实现。如果换成Dubbo,就是四不像了。说说我的想法:第一点:Dubbo在融入Spring Cloud的时候,真的就是四不像吗?如果真正看过Spring Cloud Alibaba以及理解Spring Cloud Common中的抽象的话,这个问题根本就不用去讨论。Spring Cloud Alibaba Dubbo在实现的时候是兼容Feign的编程模型的。有兴趣的读者可以看看小马哥在该项目中的案例:Github地址:https://github.com/spring-clo…第二点:Feign和Ribbon并不是Spring Cloud的标准,它们也只是Netflix OSS中的组件。对于负载均衡,大家可以了解一下spring-cloud-loadbalancer,它现在是Spring Cloud Common的一部分,这才是真正的标准。对于Spring Cloud Alibaba在整合Dubbo的时候兼容Feign客户端,已经是非常有用户意识了。Github地址:https://github.com/spring-clo…第二点:注册中心看看这篇文章的解读:服务注册中心是微服务的另外一个必备组件,用来协调服务提供者和调用者的相互发现,SpringCloud默认的注册中心是Eureka。爸爸版的用的是Nacos。Nacos的更新目前来看还是比较活跃的,但真没有必要集成在一个Cloud中。Nacos最好的方式还是独立发布,然后维护一个starter。开发者可以按照自己公司的环境进行有选择性的集成或替换。集成一个组件的成本是比较低的,远远低于删掉一堆自以为是的功能。SpringCloud还可以选择Zookeeper,或者Consul,甚至Etcd等,进行注册中心的搭建。目前,Eureka宣布不再维护后,Consul应该是首要选择。Consul自带Dashboard和ACL,能够看到大多数你所关心的信息。为了能够集成在我们公司的体系中,你可能会开发一些后台管理功能,进行更多的控制。这部分开发简单,只需要做个界面,直接通过API读取Consul的数据就可以了。说说我的想法:第一点:注册中心的选择。对于Eureka不再更新之后,到底选择使用哪个并没有完全的最优解,存在即合理,选择适合自己团队(技术栈、使用成本)的,才是最需要考虑的点。第二点:作者建议“Nacos最好的方式还是独立发布,然后维护一个starter”。这确实是一个很好的建议,但是这点我就奇怪了,作者到底有没有看过Nacos?Nacos目前就是独立发布的,Spring Cloud Alibaba对Nacos的支持,只是Nacos在客户端应用中,针对Spring Cloud用户的一种应用方式而已。第三点:熔断、限流看看这篇文章的解读:这部分已经被炒作成微服务体系的必备组件,但扪心自问,这个功能对于中小型的应用可能就是一个摆设。但我们还是要搞的,因为这是个卖点。SpringCloud默认的组件是Hystrix,提供了多线程和信号量来控制的不同方式。可惜的是Hystrix也宣布不再维护了,官方推荐的替换版本是resilience4j。熔断限流功能其实是非常简单的,同事花了一周时间就撸了个足够用的组件。这部分的主要设计在于能够简单的应用,最好是能够通过后台配置实时生效。爸爸版的是Sentinel,虽然也带了个后台,但是并没有和注册中心进行集成,搞了个不伦不类。我要用Sentinel,我自己集成就好了,用你个大头鬼。说说我的想法:第一点:我觉得作者能碰到一个能撸出熔断、限流框架和配置管理的同事,还是非常幸运的。但是并不是所有的团队都有人可以做这些,所以我觉得有这样的开源项目不管放在什么时候,都是对行业有益的。你不用没啥问题,但是并不代表对别人没用,并不代表这个项目不够优秀。第二点:对于作者所说的,没有与注册中心集成,搞得不伦不类。这里的不伦不类,一直没能Get到作者的点。。。不知道是不是有点“为赋新词强说愁”的感觉?个人在对比Hystrix和Sentinel的时候,还是觉得有非常多要比Hystrix做得更好的地方的。当然真正应用到自己的架构体系中,通常都是需要做一些适配、自定义等工作的。但是,对于开源产品的扩展,从来都不是用来抨击开源项目的核心原因。第四点:集成自己的服务这点是我通篇觉得最可笑的,先来看看作者对于AWS和Azure对Spring Cloud整合的赞美:话说这aws,搞了个自己的SpringCloud,集成了自己的众多的服务,相辅相成,卖的很好。于是Azure等,也搞了一套,只不过只能跑在自己的云上。如果你用了,哪一天如果想换主机环境了,就会知道这些爸爸们是多么的爱你。但是到了Alibaba做这些,就成了:重要的组件不集成,反而集成了一堆类似于OSS、ANS、SMS、MQ等非必须的功能,这就是偷奸耍滑了。同样是集成自己的商业服务来做好对客户的支持,我觉得是任何一个厂商增强自身产品实力必须要做的。到底好不好,用户说了算。就拿个人而言,我们也是阿里云的客户,对于OSS、RocketMQ这些必不可少的产品,如果提供Spring Cloud的Starter,让我更好的使用它们。从用户角度来说,省去了很多自己封装的工作,有什么不好呢?总结现在技术圈有个怪现象,自从一些技术自媒体人开始分享自己如何通过分享技术来赚钱开始,催生出了越来越多的技术自媒体。然后就出现了这样的奇葩现象:没有做过面试官的人在分享如何应对面试没有做过架构师的人在分享如何成为架构师没有赚到钱的人在分享如何赚钱不是中产的人在分享如何成为中产…不可否认,做技术自媒体是可以赚钱。但是单纯为了赚钱的技术自媒体,生搬硬套那些大V们分享的赚钱方法,为了追求流量,会使用夸大表述、扭曲事实、传播侵权内容、编故事博取同情等手段来获得关注和转发。这使得很多技术内容的分享就变得不那么纯粹了,甚至会对读者造成对技术内容的误解。我没有能力去控制那些自媒体发布这些不实的内容,但是在我了解的范围内,还是尽力输出一些我的理解。希望可以给这些误读内容不同的声音,能够引起读者的注意,从而希望大家可以多一些自己的思考。当然,我的观点也不一定都是对的,所以不管读者看到什么内容,一定要保持自己的思考。当你发现网上有内容发生冲突的时候,唯一可以解决的方式不是选择一方去相信,还是要自己去深入研究,去验证哪一个观点才是正确的。最后,声明一点:我不是Spring Cloud Alibaba的成员,也不是阿里系公司的员工。对于Spring Cloud Alibaba的支持,只是我作为一名奋斗在一线的程序员的简单思考。如果您觉得我说的不对,非常欢迎可以留言讨论。欢迎关注我长期连载的《Spring Cloud基础教程》

April 9, 2019 · 1 min · jiezi

说说我为什么看好Spring Cloud Alibaba

最近对《Spring Cloud Alibaba基础教程》系列的催更比较多,说一下最近的近况:因为打算Spring Boot 2.x一起更新。所以一直在改博客Spring Boot专题页和Git仓库的组织。由于前端技术太过蹩脚,花了不少时间。大家不用担心,这个系列不会太监,因为我真心看好这个套件的未来,后续的更新也会继续赶上来。今天就水更一篇吧,跟大家聊一下平时被问的比较多的一类问题,Spring Cloud Alibaba是什么,我为什么要写Spring Cloud Alibaba基础教程?Spring Cloud Alibaba是什么简介Spring Cloud Alibaba从名字上看,就知道一定跟Spring Cloud有关,但是我们为什么在Spring Cloud官方文档中看不到它的影子呢?因为它目前还是一个孵化项目,它的仓库也位于Spring Cloud孵化器中,Github地址:https://github.com/spring-cloud-incubator/spring-cloud-alibaba。版本关系虽然它还没有纳入Spring Cloud的主版本管理(Dalston、Edgware、Finchley、Greenwich这些),但是也已经发布了几个针对目前常用Spring Cloud版本的可用内容,也有一些公司已经将其用于生产环境。了解Spring Cloud的读者肯定知道,Spring Cloud的版本与Spring Boot的版本有着密切的关系,现在又多了一个Spring Cloud Alibaba,那么它们的关系是怎么样的呢?可以看看之前写过的这篇文章:Spring Cloud Alibaba与Spring Boot、Spring Cloud之间不得不说的版本关系功能特性:Spring Cloud Alibaba不是一个简单的组件,而是一个综合套件。其中涵盖了非常多的内容,包括:服务治理、配置管理、限流降级以及对阿里开源生态(Dubbo、RocketMQ等)支持的诸多组件。更多详细详细,读者可查阅其官方文档。为什么要写Spring Cloud Alibaba基础教程首先,我们需要知道Spring Cloud Alibaba在Spring Cloud家族中的地位,它是一个套件,与Netflix OSS一样,涵盖了非常多的实用组件,其中也有不少内容存在重叠。其次,我们需要知道Netflix OSS下的诸多重要组件先后宣布停止新功能开发的大背景,而Spring Cloud Alibaba是一个新生项目,正处于高速迭代中。对于未来,相信谁都会选。再次,对于中国用户来说,Spring Cloud Alibaba还有一个非常特殊的意义:它将曾经红极一时的Dubbo,以及阿里巴巴的强力消息中间件RocketMQ融入Spring Cloud体系。还在纠结于如何让这些共存的团队,你们所面临过的各种困难与问题,马上就会迎刃而解。不用再烦恼是不是要扩展Dubbo的注册中心,还是自己为RocketMQ实现一套的Spring Cloud Stream的Binder等等问题。最后,对于Spring Cloud Alibaba的上手学习成本如何呢?如果您已经是Spring Cloud的用户,那么恭喜您,在Spring Cloud Common的抽象和Spring Cloud Alibaba团队的努力下,你会非常容易、甚至不需要改变多少编码模式,就能适应它。如果您第一次接触Spring Cloud,那么也恭喜您,因为这是有史以来,中文文档最全的一个Spring Cloud组件了,相信机智的您一定也能很快的上手使用它!如果你觉得一堆文档,不知道从何看起?那我就是我想写的这个教程的目的,在那么多内容中,带你快速入门这个最具有中国特色的Spring Cloud组件! ^_^关注我的Spring Cloud基础教程以下是当前已发布内容Spring Cloud Alibaba基础教程:使用Nacos实现服务注册与发现Spring Cloud Alibaba基础教程:Nacos 生产级版本 0.8.0Spring Cloud Alibaba基础教程:支持的几种服务消费方式Spring Cloud Alibaba基础教程:使用Nacos作为配置中心Spring Cloud Alibaba基础教程:Nacos配置的加载规则详解Spring Cloud Alibaba基础教程:Nacos配置的多环境管理Spring Cloud Alibaba基础教程:Nacos配置的多文件加载与共享配置Spring Cloud Alibaba基础教程:Nacos的数据持久化Spring Cloud Alibaba基础教程:Nacos的集群部署 ...

March 29, 2019 · 1 min · jiezi

Spring 中优雅的获取泛型信息

简介Spring 源码是个大宝库,我们能遇到的大部分工具在源码里都能找到,所以笔者开源的 mica 完全基于 Spring 进行基础增强,不重复造轮子。今天我要分享的是在 Spring 中优雅的获取泛型。获取泛型自己解析我们之前的处理方式,代码来源 vjtools(江南白衣)。/** * 通过反射, 获得Class定义中声明的父类的泛型参数的类型. * * 注意泛型必须定义在父类处. 这是唯一可以通过反射从泛型获得Class实例的地方. * * 如无法找到, 返回Object.class. * * 如public UserDao extends HibernateDao<User,Long> * * @param clazz clazz The class to introspect * @param index the Index of the generic declaration, start from 0. * @return the index generic declaration, or Object.class if cannot be determined */public static Class getClassGenericType(final Class clazz, final int index) { Type genType = clazz.getGenericSuperclass(); if (!(genType instanceof ParameterizedType)) { logger.warn(clazz.getSimpleName() + “’s superclass not ParameterizedType”); return Object.class; } Type[] params = ((ParameterizedType) genType).getActualTypeArguments(); if ((index >= params.length) || (index < 0)) { logger.warn(“Index: " + index + “, Size of " + clazz.getSimpleName() + “’s Parameterized Type: " + params.length); return Object.class; } if (!(params[index] instanceof Class)) { logger.warn(clazz.getSimpleName() + " not set the actual class on superclass generic parameter”); return Object.class; } return (Class) params[index];}ResolvableType 工具从 Spring 4.0 开始 Spring 中添加了 ResolvableType 工具,这个类可以更加方便的用来回去泛型信息。首先我们来看看官方示例:private HashMap<Integer, List<String>> myMap;public void example() { ResolvableType t = ResolvableType.forField(getClass().getDeclaredField(“myMap”)); t.getSuperType(); // AbstractMap<Integer, List<String>> t.asMap(); // Map<Integer, List<String>> t.getGeneric(0).resolve(); // Integer t.getGeneric(1).resolve(); // List t.getGeneric(1); // List<String> t.resolveGeneric(1, 0); // String}详细说明构造获取 Field 的泛型信息ResolvableType.forField(Field)构造获取 Method 的泛型信息ResolvableType.forMethodParameter(Method, int)构造获取方法返回参数的泛型信息ResolvableType.forMethodReturnType(Method)构造获取构造参数的泛型信息ResolvableType.forConstructorParameter(Constructor, int)构造获取类的泛型信息ResolvableType.forClass(Class)构造获取类型的泛型信息ResolvableType.forType(Type)构造获取实例的泛型信息ResolvableType.forInstance(Object)更多使用 Api 请查看,ResolvableType java doc: https://docs.spring.io/spring…开源推荐Spring boot 微服务高效开发 mica 工具集:https://gitee.com/596392912/micaAvue 一款基于vue可配置化的神奇框架:https://gitee.com/smallweigit/avuepig 宇宙最强微服务(架构师必备):https://gitee.com/log4j/pigSpringBlade 完整的线上解决方案(企业开发必备):https://gitee.com/smallc/SpringBladeIJPay 支付SDK让支付触手可及:https://gitee.com/javen205/IJPay加入【如梦技术】Spring QQ群:479710041,了解更多。关注我们扫描上面二维码,更多精彩内容每天推荐! ...

March 27, 2019 · 1 min · jiezi

扩展jwt解决oauth2 性能瓶颈

oauth2 性能瓶颈资源服务器的请求都会被拦截 到认证服务器校验合法性 (如下图)用户携带token 请求资源服务器资源服务器拦截器 携带token 去认证服务器 调用tokenstore 对token 合法性校验资源服务器拿到token,默认只会含有用户名信息通过用户名调用userdetailsservice.loadbyusername 查询用户全部信息如上步骤在实际使用,会造成认证中心的负载压力过大,成为造成整个系统瓶颈的关键点。check-token 过程中涉及的源码更为详细的源码讲解可以参考我上篇文章《Spring Cloud OAuth2 资源服务器CheckToken 源码解析》check-token 涉及到的核心类扩展jwt 生成携带用户详细信息为什么使用jwt 替代默认的UUID token ?通过jwt 访问资源服务器后,不再使用check-token 过程,通过对jwt 的解析即可实现身份验证,登录信息的传递。减少网络开销,提高整体微服务集群的性能spring security oauth 默认的jwttoken 只含有username,通过扩展TokenEnhancer,实现关键字段的注入到 JWT 中,方便资源服务器使用 @Bean public TokenEnhancer tokenEnhancer() { return (accessToken, authentication) -> { if (SecurityConstants.CLIENT_CREDENTIALS .equals(authentication.getOAuth2Request().getGrantType())) { return accessToken; } final Map<String, Object> additionalInfo = new HashMap<>(8); PigxUser pigxUser = (PigxUser) authentication.getUserAuthentication().getPrincipal(); additionalInfo.put(“user_id”, pigxUser.getId()); additionalInfo.put(“username”, pigxUser.getUsername()); additionalInfo.put(“dept_id”, pigxUser.getDeptId()); additionalInfo.put(“tenant_id”, pigxUser.getTenantId()); additionalInfo.put(“license”, SecurityConstants.PIGX_LICENSE); ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo); return accessToken; }; }生成的token 如下,含有关键的字段重写默认的资源服务器处理行为不再使用RemoteTokenServices 去掉用认证中心 CheckToken,自定义客户端TokenService@Slf4jpublic class PigxCustomTokenServices implements ResourceServerTokenServices { @Setter private TokenStore tokenStore; @Setter private DefaultAccessTokenConverter defaultAccessTokenConverter; @Setter private JwtAccessTokenConverter jwtAccessTokenConverter; @Override public OAuth2Authentication loadAuthentication(String accessToken) throws AuthenticationException, InvalidTokenException { OAuth2Authentication oAuth2Authentication = tokenStore.readAuthentication(accessToken); UserAuthenticationConverter userTokenConverter = new PigxUserAuthenticationConverter(); defaultAccessTokenConverter.setUserTokenConverter(userTokenConverter); Map<String, ?> map = jwtAccessTokenConverter.convertAccessToken(readAccessToken(accessToken), oAuth2Authentication); return defaultAccessTokenConverter.extractAuthentication(map); } @Override public OAuth2AccessToken readAccessToken(String accessToken) { return tokenStore.readAccessToken(accessToken); }}解析jwt 组装成Authentication/** * @author lengleng * @date 2019-03-17 * <p> * jwt 转化用户信息 */public class PigxUserAuthenticationConverter implements UserAuthenticationConverter { private static final String USER_ID = “user_id”; private static final String DEPT_ID = “dept_id”; private static final String TENANT_ID = “tenant_id”; private static final String N_A = “N/A”; @Override public Authentication extractAuthentication(Map<String, ?> map) { if (map.containsKey(USERNAME)) { Collection<? extends GrantedAuthority> authorities = getAuthorities(map); String username = (String) map.get(USERNAME); Integer id = (Integer) map.get(USER_ID); Integer deptId = (Integer) map.get(DEPT_ID); Integer tenantId = (Integer) map.get(TENANT_ID); PigxUser user = new PigxUser(id, deptId, tenantId, username, N_A, true , true, true, true, authorities); return new UsernamePasswordAuthenticationToken(user, N_A, authorities); } return null; } private Collection<? extends GrantedAuthority> getAuthorities(Map<String, ?> map) { Object authorities = map.get(AUTHORITIES); if (authorities instanceof String) { return AuthorityUtils.commaSeparatedStringToAuthorityList((String) authorities); } if (authorities instanceof Collection) { return AuthorityUtils.commaSeparatedStringToAuthorityList(StringUtils .collectionToCommaDelimitedString((Collection<?>) authorities)); } throw new IllegalArgumentException(“Authorities must be either a String or a Collection”); }}资源服务器配置中注入以上配置即可@Slf4jpublic class PigxResourceServerConfigurerAdapter extends ResourceServerConfigurerAdapter { @Override public void configure(ResourceServerSecurityConfigurer resources) { DefaultAccessTokenConverter accessTokenConverter = new DefaultAccessTokenConverter(); UserAuthenticationConverter userTokenConverter = new PigxUserAuthenticationConverter(); accessTokenConverter.setUserTokenConverter(userTokenConverter); PigxCustomTokenServices tokenServices = new PigxCustomTokenServices(); // 这里的签名key 保持和认证中心一致 JwtAccessTokenConverter converter = new JwtAccessTokenConverter(); converter.setSigningKey(“123”); converter.setVerifier(new MacSigner(“123”)); JwtTokenStore jwtTokenStore = new JwtTokenStore(converter); tokenServices.setTokenStore(jwtTokenStore); tokenServices.setJwtAccessTokenConverter(converter); tokenServices.setDefaultAccessTokenConverter(accessTokenConverter); resources .authenticationEntryPoint(resourceAuthExceptionEntryPoint) .tokenServices(tokenServices); }}使用JWT 扩展后带来的问题JWT 的最大缺点是,由于服务器不保存 session 状态,因此无法在使用过程中废止某个 token,或者更改 token 的权限。也就是说,一旦 JWT 签发了,在到期之前就会始终有效,除非服务器部署额外的逻辑。去认证服务器校验的过程就是 通过tokenstore 来控制jwt 安全性的一个方法,去掉Check-token 意味着 jwt token 安全性不可保证JWT 本身包含了认证信息,一旦泄露,任何人都可以获得该令牌的所有权限。为了减少盗用,JWT 的有效期应该设置得比较短。对于一些比较重要的权限,使用时应该再次对用户进行认证。为了减少盗用,JWT 不应该使用 HTTP 协议明码传输,要使用 HTTPS 协议传输。关注我个人项目 基于Spring Cloud、OAuth2.0开发基于Vue前后分离的开发平台QQ: 2270033969 一起来聊聊你们是咋用 spring cloud 的吧。 ...

March 18, 2019 · 2 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

Spring Cloud Alibaba与Spring Boot、Spring Cloud之间不得不说的版本关系

这篇博文是临时增加出来的内容,主要是由于最近连载《Spring Cloud Alibaba基础教程》系列的时候,碰到读者咨询的大量问题中存在一个比较普遍的问题:版本的选择。其实这类问题,在之前写Spring Cloud基础教程的时候,就已经发过一篇《聊聊Spring Cloud版本的那些事儿》,来说明Spring Boot和Spring Cloud版本之间的关系。Spring Cloud Alibaba现阶段版本的特殊性现在的Spring Cloud Alibaba由于没有纳入到Spring Cloud的主版本管理中,所以我们需要自己去引入其版本信息,比如之前教程中的例子:<dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Finchley.SR1</version> <type>pom</type> <scope>import</scope> </dependency> <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> </dependencies></dependencyManagement>而不是像以往使用Spring Cloud的时候,直接引入Spring Cloud的主版本(Dalston、Edgware、Finchley、Greenwich这些)就可以的。我们需要像上面的例子那样,单独的引入spring-cloud-alibaba-dependencies来管理Spring Cloud Alibaba下的组件版本。由于Spring Cloud基于Spring Boot构建,而Spring Cloud Alibaba又基于Spring Cloud Common的规范实现,所以当我们使用Spring Cloud Alibaba来构建微服务应用的时候,需要知道这三者之间的版本关系。下表整理了目前Spring Cloud Alibaba的版本与Spring Boot、Spring Cloud版本的兼容关系:Spring BootSpring CloudSpring Cloud Alibaba2.1.xGreenwich0.2.2(还未RELEASE)2.0.xFinchley0.2.11.5.xEdgware0.1.11.5.xDalston0.1.1所以,不论您是在读我的《Spring Boot基础教程》、《Spring Cloud基础教程》还是正在连载的《Spring Cloud Alibaba系列教程》。当您照着博子的顺序,一步步做下来,但是没有调试成功的时候,强烈建议检查一下,您使用的版本是否符合上表的关系。推荐:Spring Cloud Alibaba基础教程《Spring Cloud Alibaba基础教程:使用Nacos实现服务注册与发现》《Spring Cloud Alibaba基础教程:支持的几种服务消费方式》《Spring Cloud Alibaba基础教程:使用Nacos作为配置中心》《Spring Cloud Alibaba基础教程:Nacos配置的加载规则详解》《Spring Cloud Alibaba基础教程:Nacos配置的多环境管理》《Spring Cloud Alibaba基础教程:Nacos配置的多文件加载与共享配置》《Spring Cloud Alibaba基础教程:Nacos的数据持久化》《Spring Cloud Alibaba基础教程:Nacos的集群部署》该系列教程的代码示例:Github:https://github.com/dyc87112/SpringCloud-Learning/Gitee:https://gitee.com/didispace/SpringCloud-Learning/如果您对这些感兴趣,欢迎star、follow、收藏、转发给予支持!以下专题教程也许您会有兴趣Spring Boot基础教程【新版】Spring Cloud从入门到精通 ...

March 4, 2019 · 1 min · jiezi

Spring Cloud Alibaba迁移指南(四):零代码兼容 Api-Gateway

自 Spring Cloud 官方宣布 Spring Cloud Netflix 进入维护状态后,我们开始制作《Spring Cloud Alibaba迁移指南》系列文章,向开发者提供更多的技术选型方案,并降低迁移过程中的技术难度。第一篇:一行代码从 Hystrix 迁移到 Sentinel第二篇:零代码替换 Eureka第三篇:极简的 Config如果你为 Api-Gateway(可能是 Zuul,也可能是 spring cloud gateway) 选择了 Eureka 为注册中心, 找不到一个合适的替换方案而苦苦烦恼时,那接下来的内容将是非常值得你一读。Spring Cloud Alibaba 不管是开源的服务注册组件还是商业化,都实现了 Spring Cloud 服务注册的标准规范。这就天然的给开发者提供了一种非常便利的方式将服务注册中心的 Eureka 迁移到开源的 Nacos。兼容 Api-Gateway:零代码替换 Eureka使用 Spring Cloud Alibaba 的开源组件 spring-cloud-starter-alibaba-nacos-discovery 来替换 Eureka,兼容 Api-Gateway(注意: 这里的 Api-Gateway 是一个统称,有可能是基于 Zuul 来实现,也有能可能是基于 spring cloud gateway 来实现。)仅需要完成以下几个简单的步骤即可。环境准备工作:本地需要安装 Nacos。Nacos 的安装方式也是极其的简单,参考 Nacos 官网。假设现在已经正常启动了 Nacos 。添加 Nacos 的 pom 依赖,同时去掉 Eureka。 在需要替换的工程目录下找到 maven 的配置文件 pom.xml。添加如下的 pom 依赖:<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> <version>0.2.1.RELEASE</version> </dependency>同时将依赖的 spring-cloud-starter-netflix-eureka-client pom 给去掉。 application.properties 配置。 一些关于 Nacos 基本的配置也必须在 application.properties(也可以是application.yaml)配置,如下所示:spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848同时将与 Eureka 相关的配置删除。(可选) 更换 EnableEurekaClient 注解。 如果在你的应用启动程序类加了 EnableEurekaClient 注解,这个时候需要更符合 Spring Cloud 规范的一个注解 EnableDiscoveryClient 。注意:以上几个步骤不仅仅是在集成 Api-Gateway 网关的项目中做相应的更改,通过 Api-Gateway 网关进行转发的后端服务也都要做相应的更改。完成以上三个步骤,就已经兼容了 Api-Gateway 网关的路由转发。关于如何使用 Spring Cloud Alibaba 的商业化组件 ANS 来替换掉 Api-Gateway 的注册中心 Eureka,详细的文档可参考这里。至此,《Spring Cloud Alibaba迁移指南》系列文章的四篇已全部,若您在迁移过程遇到了其他难题,欢迎到Spring Cloud Alibaba@GitHub 提issue。本文作者:中间件小哥阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

March 1, 2019 · 1 min · jiezi

Spring Cloud Alibaba迁移指南(三):极简的 Config

自 Spring Cloud 官方宣布 Spring Cloud Netflix 进入维护状态后,我们开始制作《Spring Cloud Alibaba迁移指南》系列文章,向开发者提供更多的技术选型方案,并降低迁移过程中的技术难度。第一篇:一行代码从 Hystrix 迁移到 Sentinel第二篇:零代码替换 Eureka第三篇,我们一起来看看 Spring Cloud Alibaba 是如何使用极简的方式来做到分布式应用的外部化配置,使得应用在运行时动态更新某些配置成为可能。 目前关于 Spring Cloud Config 的标准实现开源方面有三个,分别是:Spring Cloud Alibaba Nacos ConfigSpring Cloud Consul ConfigSpring Cloud Config (Spring Cloud 官方集成的方式)那面对于这么多的实现,Spring Cloud Alibaba Nacos Config 的实现它具有哪些优势呢?大致从以下几个方面来全方位的分析。 Spring Cloud Alibaba Nacos ConfigSpring Cloud Consul ConfigSpring Cloud Config (Spring Cloud 官方集成的方式)配置存储直接依赖于 Nacos。直接依赖于 Consul。通常的组合是Config-server 和 git。配置刷新无需人工干预,自动秒级刷新。无需人工干预,自动秒级刷新。需要人工干预,手动触发/bus/refresh 接口,才能达到配置动态刷新的效果。是否集成第三方服务不需要。不需要。存储需要依赖于git,刷新依赖于 RabbitMQ 。运维组件只需要运维 Nacos 本身即可。只需要运维 Consul本身。通常是要运维 Config-erver,MQ 的服务,提供存储能力的 Git。比较重的第三方依赖无,直接引入starter 即可 。无,直接引入 starter 即可。不仅需要引入 starter,而且还需要引入配置刷新依赖的 spring-cloud-starter-bus-amqp 。推送状态支持无无更新历史查询支持无无配置回滚支持无无配置加解密支持待确认待确认多重容灾支持无无同时 Spring Cloud Alibaba 还可以基于 Spring Cloud Alibaba Nacos Config 无缝对接云上的 ACM,这给一些需要上云的用户带来了极其的方便。综上全方位的对比,Spring Cloud Alibaba Nacos Config 无疑提供了性价比最高的 Spring Cloud Config 的开源实现。下面以一个快速上手的案例体验一下 Spring Cloud Alibaba Nacos Config 的实现是如何使用的。同时也提供了简单的方式给那些想转用 Spring Cloud Alibaba Nacos Config 的同学做一些参考。第 1 步:Nacos 服务端初始化。1.1 启动 Nacos Server。启动方式可见 Nacos 官网 。1.2 添加配置。启动好 Nacos 之后,在 Nacos 控制台添加如下的配置。Data ID: ${spring.application.name}.propertiesGroup : DEFAULT_GROUP配置格式: Properties配置内容: ${key}=${value}注意:Data Id 是以 properties(默认的文件扩展名方式)为扩展名。文件名以 &dollar;{spring.application.name} 配置参数为主。配置内容:当你想从其他的存储源(例如: git) 要往 Nacos 进行迁移的话,目前只能通过手动的方式进行逐个的添加。&dollar;{key} 是根据您的业务场景需要配置的或者迁移的 key, &dollar;{value} 就是对应的具体值。第 2 步:Spring Cloud Alibaba Nacos Config 客户端使用方式。2.1 添加 maven 依赖。为了能够在应用程序中使用 Nacos 来实现应用的外部化配置,在构建应用的同时或者已经存在的应用需要引入一个 Starter,如下所示:<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> <version>0.2.2.BUILD-SNAPSHOT</version></dependency>2.2 添加相关配置。客户端需要和 Nacos 服务端进行通信,因此需要配置 Nacos 服务端的地址。在您的应用配置文件中新增如下配置,这里以 application.properties 为例。spring.cloud.nacos.config.server-addr=127.0.0.1:8848完成以上两个步骤,就已经完成了 Spring Cloud Alibaba Nacos Config 的基本使用。完整的使用可参考 Spring Cloud Alibaba 的管方 Wiki 文档。本文作者:中间件小哥阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

February 28, 2019 · 1 min · jiezi

Spring Cloud Alibaba迁移指南(二):零代码替换 Eureka

自 Spring Cloud 官方宣布 Spring Cloud Netflix 进入维护状态后,我们开始制作《Spring Cloud Alibaba迁移指南》系列文章,向开发者提供更多的技术选型方案,并降低迁移过程中的技术难度。第二篇,Spring Cloud Alibaba 实现了 Spring Cloud 服务注册的标准规范,这就天然的给开发者提供了一种非常便利的方式将服务注册中心的 Eureka 迁移到开源的 Nacos 。 第一篇回顾:一行代码从 Hystrix 迁移到 Sentinel零代码使用 Nacos 替换 Eureka如果你需要使用 Spring Cloud Alibaba 的开源组件 spring-cloud-starter-alibaba-nacos-discovery 来替换 Eureka。需要完成以下几个简单的步骤即可。1. __本地需要安装 Nacos。__Nacos 的安装方式也是极其的简单,参考 Nacos 官网。假设现在已经正常启动了 Nacos 。2. 添加 Nacos 的 pom 依赖,同时去掉 Eureka。 在需要替换的工程目录下找到 maven 的配置文件 pom.xml。添加如下的 pom 依赖:<dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> <version>0.2.1.RELEASE</version> </dependency></dependencies>同时将依赖的 spring-cloud-starter-netflix-eureka-client pom 给去掉。 3. application.properties 配置。 一些关于 Nacos 基本的配置也必须在 application.properties(也可以是application.yaml)配置,如下所示: application.properties:spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848同时将和 Eureka 相关的配置删除。4. (可选) 更换 EnableEurekaClient 注解。如果在你的应用启动程序类加了 EnableEurekaClient 注解,这个时候需要更符合 Spring Cloud 规范的一个注解EnableDiscoveryClient 。直接启动你的应用即可。到目前为止,就已经完成了 “零行代码使用 Nacos 替换 Eureka”。完整的方式可参考 Spring Cloud Alibaba 的官方 Wiki 文档。零代码使用 ANS 替换 Eureka如果你需要使用 Spring Cloud Alibaba 的商业化组件 spring-cloud-starter-alicloud-ans 来替换 Eureka。也是仅需完成以下几个简单的步骤即可。1. 本地需要安装 轻量版配置中心。 轻量版配置中心的下载和启动方式可参考 这里。假设现在已经正常启动了轻量版配置中心 。2. 添加 ANS 的 pom 依赖,同时去掉 Eureka。 在需要替换的工程目录下找到 maven 的配置文件 pom.xml。添加如下的 pom 依赖:<dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-alicloud-ans</artifactId> <version>0.2.1.RELEASE</version> </dependency></dependencies>同时将依赖的 org.springframework.cloud:spring-cloud-starter-netflix-eureka-client pom 给去掉。 3. (可选) application.properties 配置。 一些关于 ANS 基本的配置也可以在 application.properties(也可以是application.yaml)配置,如下所示: application.properties:spring.cloud.alicloud.ans.server-list=127.0.0.1spring.cloud.alicloud.ans.server-port=8080如果不配置的话,默认值就是 127.0.0.1 和 8080 ,因此这一步是可选的。同时将和 Eureka 相关的配置删除。4. (可选) 更换 EnableEurekaClient 注解。如果在你的应用启动程序类加了 EnableEurekaClient 注解,这个时候需要更符合 Spring Cloud 规范的一个注解EnableDiscoveryClient 。代码层面不需要改动任何代码,直接启动你的应用即可。到目前为止,就已经完成了 “零代码使用 ANS 替换 Eureka”。完整的使用方式可参考 Spring Cloud Alibaba 的官方 Wiki 文档。本文作者:中间件小哥阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

February 27, 2019 · 1 min · jiezi

spring cloud config将配置存储在数据库中

转载请标明出处: https://blog.csdn.net/forezp/…本文出自方志朋的博客Spring Cloud Config Server最常见是将配置文件放在本地或者远程Git仓库,放在本地是将将所有的配置文件统一写在Config Server工程目录下,如果需要修改配置,需要重启config server;放在Git仓库,是将配置统一放在Git仓库,可以利用Git仓库的版本控制。本文将介绍使用另外一种方式存放配置信息,即将配置存放在Mysql中。整个流程:Config Sever暴露Http API接口,Config Client 通过调用Config Sever的Http API接口来读取配置Config Server的配置信息,Config Server从数据中读取具体的应用的配置。流程图如下:案例实战在本案例中需要由2个工程,分为config-server和config-client,其中config-server工程需要连接Mysql数据库,读取配置;config-client则在启动的时候从config-server工程读取。本案例Spring Cloud版本为Greenwich.RELEASE,Spring Boot版本为2.1.0.RELEASE。工程描述config-server端口8769,从数据库中读取配置config-client端口8083,从config-server读取配置搭建config-server工程创建工程config-server,在工程的pom文件引入config-server的起步依赖,mysql的连接器,jdbc的起步依赖,代码如下:<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-server</artifactId></dependency><dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId></dependency><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId></dependency>在工程的配置文件application.yml下做以下的配置:spring: profiles: active: jdbc application: name: config-jdbc-server datasource: url: jdbc:mysql://127.0.0.1:3306/config-jdbc?useUnicode=true&characterEncoding=utf8&characterSetResults=utf8&serverTimezone=GMT%2B8 username: root password: 123456 driver-class-name: com.mysql.jdbc.Driver cloud: config: label: master server: jdbc: trueserver: port: 8769spring.cloud.config.server.jdbc.sql: SELECT key1, value1 from config_properties where APPLICATION=? and PROFILE=? and LABEL=?其中,spring.profiles.active为spring读取的配置文件名,从数据库中读取,必须为jdbc。spring.datasource配置了数据库相关的信息,spring.cloud.config.label读取的配置的分支,这个需要在数据库中数据对应。spring.cloud.config.server.jdbc.sql为查询数据库的sql语句,该语句的字段必须与数据库的表字段一致。在程序的启动文件ConfigServerApplication加上@EnableConfigServer注解,开启ConfigServer的功能,代码如下:@SpringBootApplication@EnableConfigServerpublic class ConfigServerApplication { public static void main(String[] args) { SpringApplication.run(ConfigServerApplication.class, args); }}初始化数据库由于Config-server需要从数据库中读取,所以读者需要先安装MySQL数据库,安装成功后,创建config-jdbc数据库,数据库编码为utf-8,然后在config-jdbc数据库下,执行以下的数据库脚本:CREATE TABLE config_properties ( id bigint(20) NOT NULL AUTO_INCREMENT, key1 varchar(50) COLLATE utf8_bin NOT NULL, value1 varchar(500) COLLATE utf8_bin DEFAULT NULL, application varchar(50) COLLATE utf8_bin NOT NULL, profile varchar(50) COLLATE utf8_bin NOT NULL, label varchar(50) COLLATE utf8_bin DEFAULT NULL, PRIMARY KEY (id)) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 COLLATE=utf8_bin其中key1字段为配置的key,value1字段为配置的值,application字段对应于应用名,profile对应于环境,label对应于读取的分支,一般为master。插入数据config-client 的2条数据,包括server.port和foo两个配置,具体数据库脚本如下:insert into config_properties (id, key1, value1, application, profile, label) values(‘1’,‘server.port’,‘8083’,‘config-client’,‘dev’,‘master’);insert into config_properties (id, key1, value1, application, profile, label) values(‘2’,‘foo’,‘bar-jdbc’,‘config-client’,‘dev’,‘master’);搭建config-client在 config-client工程的pom文件,引入web和config的起步依赖,代码如下:<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId></dependency><dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-config</artifactId></dependency>在程序的启动配置文件 bootstrap.yml做程序的相关配置,一定要是bootstrap.yml,不可以是application.yml,bootstrap.yml的读取优先级更高,配置如下:spring: application: name: config-client cloud: config: uri: http://localhost:8769 fail-fast: true profiles: active: dev其中spring.cloud.config.uri配置的config-server的地址,spring.cloud.config.fail-fast配置的是读取配置失败后,执行快速失败。spring.profiles.active配置的是spring读取配置文件的环境。在程序的启动文件ConfigClientApplication,写一个RestAPI,读取配置文件的foo配置,返回给浏览器,代码如下:@SpringBootApplication@RestControllerpublic class ConfigClientApplication { public static void main(String[] args) { SpringApplication.run(ConfigClientApplication.class, args); } @Value("${foo}") String foo; @RequestMapping(value = “/foo”) public String hi(){ return foo; }}依次启动2个工程,其中config-client的启动端口为8083,这个是在数据库中的,可见config-client从 config-server中读取了配置。在浏览器上访问http://localhost:8083/foo,浏览器显示bar-jdbc,这个是在数据库中的,可见config-client从 config-server中读取了配置。参考资料https://cloud.spring.io/sprin…源码下载https://github.com/forezp/Spr…<div><p align=“center”> <img src=“https://www.fangzhipeng.com/img/avatar.jpg" width=“258” height=“258”/> <br> 扫一扫,支持下作者吧</p><p align=“center” style=“margin-top: 15px; font-size: 11px;color: #cc0000;"> <strong>(转载本站文章请注明作者和出处 <a href=“https://www.fangzhipeng.com”>方志朋的博客</a>)</strong></p></div> ...

February 21, 2019 · 1 min · jiezi

Spring Cloud Alibaba基础教程:Nacos的集群部署

前情回顾:《Spring Cloud Alibaba基础教程:使用Nacos实现服务注册与发现》《Spring Cloud Alibaba基础教程:支持的几种服务消费方式》《Spring Cloud Alibaba基础教程:使用Nacos作为配置中心》《Spring Cloud Alibaba基础教程:Nacos配置的加载规则详解》《Spring Cloud Alibaba基础教程:Nacos配置的多环境管理》《Spring Cloud Alibaba基础教程:Nacos配置的多文件加载与共享配置》《Spring Cloud Alibaba基础教程:Nacos的数据持久化》继续说说生产环境的Nacos搭建,通过上一篇《Nacos的数据持久化》的介绍,我们已经知道Nacos对配置信息的存储原理,在集群搭建的时候,必须要使用集中化存储,比如:MySQL存储。下面顺着上一篇的内容,继续下一去。通过本文,我们将完成Nacos生产环境的搭建。集群搭建根据官方文档的介绍,Nacos的集群架构大致如下图所示(省略了集中化存储信息的MySQL):下面我们就来一步步的介绍,我们每一步的搭建细节。MySQL数据源配置对于数据源的修改,在上一篇《Nacos的数据持久》中已经说明缘由,如果还不了解的话,可以先读一下这篇再回来看这里。在进行集群配置之前,先完成对MySQL数据源的初始化和配置。主要分以下两步:第一步:初始化MySQL数据库,数据库初始化文件:nacos-mysql.sql,该文件可以在Nacos程序包下的conf目录下获得。第二步:修改conf/application.properties文件,增加支持MySQL数据源配置,添加(目前只支持mysql)数据源的url、用户名和密码。配置样例如下:spring.datasource.platform=mysqldb.num=1db.url.0=jdbc:mysql://localhost:3306/nacos?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=truedb.user=rootdb.password=更多介绍与思考,可见查看上一篇《Nacos的数据持久化》。集群配置在Nacos的conf目录下有一个cluster.conf.example,可以直接把example扩展名去掉来使用,也可以单独创建一个cluster.conf文件,然后打开将后续要部署的Nacos实例地址配置在这里。本文以在本地不同端点启动3个Nacos服务端为例,可以如下配置:127.0.0.1:8841127.0.0.1:8842127.0.0.1:8843注意:这里的例子仅用于本地学习测试使用,实际生产环境必须部署在不同的节点上,才能起到高可用的效果。另外,Nacos的集群需要3个或3个以上的节点,并且确保这三个节点之间是可以互相访问的。启动实例在完成了上面的配置之后,我们就可以开始在各个节点上启动Nacos实例,以组建Nacos集群来使用了。由于本文中我们测试学习采用了本地启动多实例的情况,与真正生产部署会有一些差异,所以下面分两种情况说一下,如何启动各个Nacos实例。本地测试本文中,在集群配置的时候,我们设定了3个Nacos的实例都在本地,只是以不同的端口区分,所以我们在启动Nacos的时候,需要修改不同的端口号。下面介绍一种方法来方便地启动Nacos的三个本地实例,我们可以将bin目录下的startup.sh脚本复制三份,分别用来启动三个不同端口的Nacos实例,为了可以方便区分不同实例的启动脚本,我们可以把端口号加入到脚本的命名中,比如:startup-8841.shstartup-8842.shstartup-8843.sh然后,分别修改这三个脚本中的参数,具体如下图的红色部分(端口号根据上面脚本命名分配):这里我们通过-Dserver.port的方式,在启动命令中,为Nacos指定具体的端口号,以实现在本机上启动三个不同的Nacos实例来组成集群。修改完3个脚本配置之后,分别执行下面的命令就可以在本地启动Nacos集群了:sh startup-8841.shsh startup-8842.shsh startup-8843.sh生产环境在实际生产环境部署的时候,由于每个实例分布在不同的节点上,我们可以直接使用默认的启动脚本(除非要调整一些JVM参数等才需要修改)。只需要在各个节点的Nacos的bin目录下执行sh startup.sh命令即可。Proxy配置在Nacos的集群启动完毕之后,根据架构图所示,我们还需要提供一个统一的入口给我们用来维护以及给Spring Cloud应用访问。简单地说,就是我们需要为上面启动的的三个Nacos实例做一个可以为它们实现负载均衡的访问点。这个实现的方式非常多,这里就举个用Nginx来实现的简单例子吧。在Nginx配置文件的http段中,我们可以加入下面的配置内容:这样,当我们访问:http://localhost:8080/nacos/的时候,就会被负载均衡的代理到之前我们启动的三个Nacos实例上了。这里我们没有配置upstream的具体策略,默认会使用线性轮训的方式,如果有需要,也可以配置上更为复杂的分发策略。这部分是Nginx的使用内容,这里就不作具体介绍了。这里提一下我在尝试搭建时候碰到的一个问题,如果您也遇到了,希望下面的说明可以帮您解决问题。错误信息如下:2019-02-20 16:20:53,216 INFO The host [nacos_server] is not valid Note: further occurrences of request parsing errors will be logged at DEBUG level.java.lang.IllegalArgumentException: The character [_] is never valid in a domain name. at org.apache.tomcat.util.http.parser.HttpParser$DomainParseState.next(HttpParser.java:926) at org.apache.tomcat.util.http.parser.HttpParser.readHostDomainName(HttpParser.java:822) at org.apache.tomcat.util.http.parser.Host.parse(Host.java:71) at org.apache.tomcat.util.http.parser.Host.parse(Host.java:45) at org.apache.coyote.AbstractProcessor.parseHost(AbstractProcessor.java:288) at org.apache.coyote.http11.Http11Processor.prepareRequest(Http11Processor.java:809) at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:384) at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66) at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:791) at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1417) at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) at java.lang.Thread.run(Thread.java:748)主要原因是,一开始在配置upstream的时候,用了nacos_server作为名称,而在Nacos使用的Tomcat版本中不支持_符号出现在域名位置,所以上面截图给出的upstream的名称是nacosserver,去掉了_符号。到这里,Nacos的集群搭建就完成了!我们可以通过Nginx配置的代理地址:http://localhost:8080/nacos/来访问Nacos,在Spring Cloud应用中也可以用这个地址来作为注册中心和配置中心的访问地址来配置。读者可以使用文末的代码示例来修改原来的Nacos地址来启动,看是否可以获取配置信息来验证集群的搭建是否成功。也可以故意的关闭某个实例,来验证Nacos集群是否还能正常服务。深入思考在Nacos官方文档的指引下,Nacos的集群搭建总体上还是非常顺畅的,没有什么太大的难度。但是值得思考的一个问题跟在上一篇中讲数据持久化的思考类似,作为一个注册中心和配置中心,Nacos的架构是否显得太过于臃肿?除了Nacos自身之外,还需要依赖更多的中间件来完成整套生产环境的搭建,相较于其他的可以用于服务发现与配置的中间件来说,就不那么有优势了。尤其对于小团队来说,这样的复杂度与成本投入,也是在选型的时候需要去考虑的。代码示例本文介绍内容的客户端代码,示例读者可以通过查看下面仓库中的alibaba-nacos-config-client项目:Github:https://github.com/dyc87112/SpringCloud-Learning/Gitee:https://gitee.com/didispace/SpringCloud-Learning/如果您对这些感兴趣,欢迎star、follow、收藏、转发给予支持!以下专题教程也许您会有兴趣Spring Boot基础教程【新版】Spring Cloud从入门到精通 ...

February 21, 2019 · 1 min · jiezi

netflix-eureka 服务注册与容错

Spring Cloud Netflix Eureka - 隐藏手册介绍在2015-2016,我们将单体应用程序重新设计为微服务,并选择Spring Cloud Netflix作为基础。(Spring Cloud Netflix)通过自动配置, Spring 环境以及其他 Spring 编程模型习惯用法为 SpringBoot 应用程序提供 Netflix OSS 开源软件集成。时间流逝,我们逐渐对框架更加熟悉,甚至还贡献了一些代码。我认为我已经知道了够多了, 便开始研究 Eureka 冗余和故障转移。一个概念的快速证明不起作用,当我挖掘更多时,我遇到了其他可怜的灵魂在互联网上搜索类似的信息。不幸的是, 找不到啥。值得赞扬的是,Spring Cloud 文档相当不错,而且还有 Netflix Wiki,但没有一个达到我想要的详细程度。这篇文章试图弥合这一差距。我假设您对 Eureka 和服务发现有一些基本的了解,所以如果您是新手,请在阅读 Spring Cloud 文档后再回来。基础架构Netflix 的高级架构,遵循 Apache License v2.0 许可。Eureka 有两个基本组件,服务器和客户端。引用 Netflix Wiki:Eureka 是一种基于 REST(Representational State Transfer)的服务,主要用于 AWS 云,用于定位服务,以实现中间层服务器的负载均衡和故障转移。我们将此服务称为 Eureka Server。Eureka 还附带了一个基于Java 的客户端组件 Eureka Client,它使与服务的交互变得更加容易。客户端还有一个内置的负载均衡器,可以进行基本的循环负载均衡。Eureka 客户端应用程序称为实例。客户端应用程序和 Eureka 客户端之间存在细微差别; 前者是您的应用程序,后者是框架提供的组件。Netflix 设计的 Eureka 具有高度动态性。有一般属性,也有一些专门属性用于定义了在一般属性的更新查询时间间隔。这是一种常见的做法,这意味着大多数这些属性可以在运行时更改,并在下一个刷新周期中被感知。例如,客户端用于向 Eureka 注册的 URL 可以更改,并在5分钟后被感知(可配置eureka.client.eurekaServiceUrlPollIntervalSeconds )。大多数用户不需要这样的动态更新,但如果你想这样做,可以找到所有配置选项,如下所示:对于 Eureka 服务器配置:org.springframework.cloud.netflix.eureka.server.EurekaServerConfigBean 实现com.netflix.eureka.EurekaServerConfig。所有属性都具有eureka.server 前缀。对于 Eureka 客户端配置:org.springframework.cloud.netflix.eureka.EurekaClientConfigBean 实现 com.netflix.discovery.EurekaClientConfig。所有属性都有 eureka.client 前缀。对于Eureka实例配置:org.springframework.cloud.netflix.eureka.EurekaInstanceConfigBean 实现(间接)com.netflix.appinfo.EurekaInstanceConfig。所有属性都有 eureka.instance 前缀。有关更多体系结构的详细信息,请参阅 Netflix Wiki。Eureka实例和客户在 Eureka 中通过 eureka.instance.instanceId 识别实例, 如果不存在则用eureka.instance.metadataMap.instanceId。实例发现彼此使用 eureka.instance.appName,该值在 Spring Cloud 默认使用 spring.application.name 或者 UNKNOWN 如果前者未定义。您需要进行设置,spring.application.name 因为具有相同名称的应用程序在 Eureka 服务器中聚集在一起。不需要设置eureka.instance.instanceId,因为它默认值设置为 CLIENT IP:PORT,但如果你想设置它,则appName必须在集群范围内是唯一的。还有一个 eureka.instance.virtualHostName,但它没有被 Spring 使用,并且设置为 spring.application.name 或 UNKNOWN 如上所述。如果 registerWithEureka 是 true,则实例使用给定的 URL 向 Eureka 服务器注册; 然后,它每30秒发送一次心跳(可配置 eureka.instance.leaseRenewalIntervalInSeconds)。如果服务器没有收到心跳,则在eureka.instance.leaseExpirationDurationInSeconds 从注册表中删除实例之前等待90秒(可配置),然后禁止该实例的流量。发送心跳是一项异步任务; 如果操作失败,则以指数方式后退2倍,直到eureka.instance.leaseRenewalIntervalInSeconds * eureka.client.heartbeatExecutorExponentialBackOffBound 达到最大延迟。注册 Eureka 的重试次数没有限制。心跳与将实例信息更新到 Eureka 服务器不同。每个实例都由 com.netflix.appinfo.InstanceInfo 表示,它是关于实例的一堆信息。它将 InstanceInfo 会定期发送到Eureka 服务器,从启动后40s开始(可配置eureka.client.initialInstanceInfoReplicationIntervalSeconds),然后每30秒开始一次(可配置eureka.client.instanceInfoReplicationIntervalSeconds)。如果 eureka.client.fetchRegistry 是 true,则客户端在启动时获取 Eureka 服务器注册表并在本地进行缓存。从那时起,它只是获取增量(这可以通过设置来关闭 eureka.client.shouldDisableDelta 到 false,尽管这会是浪费带宽)。注册表获取是每30秒调度一次的异步任务(可配置eureka.client.registryFetchIntervalSeconds)。如果操作失败,则按指数2倍后退,直到eureka.client.registryFetchIntervalSeconds * eureka.client.cacheRefreshExecutorExponentialBackOffBound 达到最大延迟。获取注册表信息的重试次数没有限制。客户端任务由 com.netflix.discovery.DiscoveryClient 安排, 这个类是对 Spring Cloud 的org.springframework.cloud.netflix.eureka.CloudEurekaClient 的扩展实现。为什么用 Eureka 实例需要这么长时间?大部分都是从spring-cloud-netflix#373复制而来的。客户注册第一次心跳在启动后发生30s(如前所述),因此实例在此间隔之前不会出现在 Eureka 注册表中。服务器响应缓存服务器维护一个每30秒更新一次的响应缓存(可配置 eureka.server.responseCacheUpdateIntervalMs)。因此,即使实例刚刚注册,它也不会出现在对 /eureka/apps REST 端点的调用结果中。但是,实例可能会在注册后出现在 Eureka Dashboard 上。这是因为仪表板绕过了 REST API 使用的响应缓存。如果您知道instanceId,您仍然可以通过调用从 Eureka 获取有关它的一些详细信息/eureka/apps/<appName>/<instanceId>。此端点不使用响应缓存。因此,其他客户端可能需要另外30秒来发现新注册的实例。客户端缓存刷新Eurekac客户端维护注册表信息的缓存。此缓存每30秒刷新一次(如前所述)。因此,在客户端决定刷新其本地缓存并发现其他新注册的实例之前,可能还需要30秒。LoadBalancer 刷新Ribbon 使用的负载均衡器从本地 Eureka 客户端获取其信息。Ribbon 还维护一份本地缓存,以避免为每个请求调用客户端。此缓存每30秒刷新一次(可配置 ribbon.ServerListRefreshInterval)。因此,在 Ribbon 可以使用新注册的实例之前可能还需要30秒。最后,在新注册的实例开始接收来自其他实例的流量之前,可能需要2分钟。Eureka 服务器Eureka 服务器具有对等感知模式,在该模式下,它跨其他 Eureka 服务器复制服务注册表,以提供负载平衡和弹性。对等感知模式是默认模式,因此 Eureka 服务器也充当 Eureka 客户端向对等体上注册给定的 URL。这就是你应该如何在生产中运行 Eureka,但是对于演示或概念验证,你可以通过设置 registerWithEureka 为 false 采用独立模式运行。当 Eureka 服务器启动时,它会尝试从对等的 Eureka 节点获取所有注册表信息。对每个对等体重试此操作5次(可配置 eureka.server.numberRegistrySyncRetries)。如果由于某种原因此操作失败,则服务器不允许客户端获取注册表信息5分钟(可配置 eureka.server.getWaitTimeInMsWhenSyncEmpty)。Eureka 的对等感知意识,通过所谓的“自我保护”的概念引入一个全新水平的复杂性(可以通过设置eureka.server.enableSelfPreservation 为 false 来关闭)。事实上,在网上看,这是我看到大多数人绊倒的地方。来自 Netflix Wiki:当 Eureka 服务器启动时,它会尝试从相邻节点获取所有实例注册表信息。如果从某个节点获取信息时出现问题,服务器会尝试所有对等体直到放弃。如果服务器能够成功获取所有实例,则会根据该信息设置应接收的续订阈值。如果有任何时间,续订低于为该值配置的百分比,则服务器会停止使用实例过期机制, 以保护当前实例注册表信息。数学运算如下:如果有两个客户端注册到 Eureka 实例,每个客户端每30秒发送一次心跳,该实例应该在一分钟内收到4次心跳。Spring 为此添加了一个较低的最小值1(可配置eureka.instance.registry.expectedNumberOfRenewsPerMin),因此实例希望每分钟接收5个心跳。然后将其乘以0.85(可配置eureka.server.renewalPercentThreshold)并四舍五入到下一个整数,这使我们再次回到 5。如果 Eureka 在15分钟内收到的心跳少于5次(可配置 eureka.server.renewalThresholdUpdateIntervalMs),它将进入自我保护模式并不再让已经注册的实例过期。Eureka 服务器隐含假设客户端以每30秒固定的速率发送心跳。如果注册了两个实例,则服务器希望(2 2 + 1 ) 0.85 = 5每分钟都接收一次心跳。如果续订率低于此值,则激活自保护模式。现在,如果客户端发送心跳的速度要快得多(例如,每隔10秒),则服务器每分钟接收12次心跳,并且即使其中一个实例发生故障,也会持续接收6次/分钟。因此,即使应该是自保护模式也不会被激活。这就是改变 eureka.client.instanceInfoReplicationIntervalSeconds 不可取的原因。如果必须的话, 你可以修改一下 eureka.server.renewalPercentThreshold 的值。Eureka 对等实例不考虑预期的续约数量,但他们的心跳被计入在最后一分钟收到的续约数量。在对等感知模式下,心跳可以转到任何 Eureka 实例; 这在运行负载均衡器或 Kubernetes 服务后很重要,其中心跳以循环模式(通常)发送到每个实例。在更新超过阈值之前,Eureka服务器不会退出自我保护模式。这可能导致客户端获取不再存在的实例。请参阅了解Eureka Peer to Peer Communication.还有一件事:在同一主机上运行多个 Eureka 服务器。Netflix 代码(com.netflix.eureka.cluster.PeerEurekaNodes.isThisMyUrl)过滤掉同一主机上的对等 URL。这可能是为了防止服务器注册为自己的对等体(我在这里猜测),但由于它们不检查端口,因此除非 Eureka 主机名eureka.client.serviceUrl.defaultZone 不同,否则对等感知不起作用。这种情况的Hack解决方法是定义唯一的主机名,然后在/etc/hosts文件(或它的Windows等价物)将它们映射到 127.0.0.1 了。Spring Cloud doc讨论了这种解决方法,但没有提到为什么需要它。现在你知道了。区域和可用区域AWS Regions 和可用 Zones。Eureka 旨在在 AWS 中运行,并使用许多特定于 AWS 的概念和术语。Regions 和 Zones 是两个这样的东西。来自 AWS doc:Amazon EC2 托管在全球多个地点。这些位置由 Regions 和可用 Zones 组成。每个地区都是一个独立的地理区域。每个区域都有多个孤立的位置,称为可用Zones…每个区域都是完全独立的。每个可用 Zones 都是隔离的,但区域中的可用 Zones 通过低延迟链接连接。Eureka 仪表板显示环境和数据中心。的值被分别设置为 test 与 default,通过使用com.netflix.config.ConfigurationManager 设置 org.springframework.cloud.netflix.eureka.server.EurekaServerBootstrap。有各种查找和回退,因此如果由于某种原因需要更改它们,请参阅上述类的源代码。Eureka 客户端默认情况下更喜欢相同的区域(可配置eureka.client.preferSameZone)。来自com.netflix.discovery.endpoint.EndpointUtils.getServiceUrlsFromDNS Java doc:从DNS获取所有 Eureka 服务 URL 的列表,以便 Eureka 客户端与之交谈。客户端从其区域中获取服务 URL,然后随机故障转移到其他区域。如果同一区域中有多个服务器,则客户端会再次随机选择一个服务器。这样,流量将在发生故障时分发。Ticket spring-cloud-netflix#203 在撰写本文时是开放的,其中有几个人谈论 Regions和 Zones。我没有验证,所以我无法评论Regions和 Zones如何与 Eureka一起使用。高可用性(HA)大部分都是从 spring-cloud-netflix#203 中复制而来的。HA 策略似乎是一个主要的 Eureka 服务器(server1)与备份(server2)。通过配置(或 DNS 或 /etc/hosts)向客户端提供 Eureka 服务器列表客户端尝试连接server1; 在这一点上,server2坐着闲着。如果server1不可用,客户端将从列表中尝试下一个。当server1重新上线时,客户端会重新使用server1。当然server1,server2可以在对等感知模式下运行,并且可以复制其注册表。但这与客户注册正交。 ...

February 19, 2019 · 2 min · jiezi

Spring Cloud Alibaba基础教程:Nacos的数据持久化

前情回顾:《Spring Cloud Alibaba基础教程:使用Nacos实现服务注册与发现》《Spring Cloud Alibaba基础教程:支持的几种服务消费方式》《Spring Cloud Alibaba基础教程:使用Nacos作为配置中心》《Spring Cloud Alibaba基础教程:Nacos配置的加载规则详解》《Spring Cloud Alibaba基础教程:Nacos配置的多环境管理》《Spring Cloud Alibaba基础教程:Nacos配置的多文件加载与共享配置》通过之前几篇关于Nacos的博文,对于Nacos分别作为服务注册中心以及配置中心时,与Spring Cloud体系结合的基础使用方法已经介绍完毕了。下面我们再用几篇博文从生产部署的角度,介绍Nacos的相关内容。本文我们将具体说说Nacos的数据存储以及生产配置的推荐。数据持久化在之前的教程中,我们对于Nacos服务端自身并没有做过什么特殊的配置,一切均以默认的单机模式运行,完成了上述所有功能的学习。但是,Nacos的单机运行模式仅适用于学习与测试环境,对于有高可用要求的生产环境显然是不合适的。那么,我们是否可以直接启动多个单机模式的Nacos,然后客户端指定多个Nacos节点就可以实现高可用吗?答案是否定的。在搭建Nacos集群之前,我们需要先修改Nacos的数据持久化配置为MySQL存储。默认情况下,Nacos使用嵌入式数据库实现数据的存储。所以,如果启动多个默认配置下的Nacos节点,数据存储是存在一致性问题的。为了解决这个问题,Nacos采用了集中式存储的方式来支持集群化部署,目前只要支持MySQL的存储。配置Nacos的MySQL存储只需要下面三步:第一步:安装数据库,版本要求:5.6.5+第二步:初始化MySQL数据库,数据库初始化文件:nacos-mysql.sql,该文件可以在Nacos程序包下的conf目录下获得。执行完成后可以得到如下图所示的表结构:第三步:修改conf/application.properties文件,增加支持MySQL数据源配置,添加(目前只支持mysql)数据源的url、用户名和密码。配置样例如下:spring.datasource.platform=mysqldb.num=1db.url.0=jdbc:mysql://localhost:3306/nacos?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=truedb.user=rootdb.password=到这里,Nacos数据存储到MySQL的配置就完成了,可以尝试继续用单机模式启动Nacos。然后再根据之前学习的Nacos配置中心的用法来做一些操作,配合MySQL工具就可以看到数据已经写入到数据库中了。下一篇,我们将继续深入思考关于Nacos数据的持久化实现,与其他的中间件相比,在实现上并没有采用分布式算法来解决一致性问题,而是采用了比较常规的集中化存储来实现。由于采用单一数据源的方式,直接解决了分布式一致性问题,所以从学习成本的角度上来说,Nacos的实现原理会更容易被理解和接受。但是,从部署的负责度和硬件投入成本上来说,与etcd、consul、zookeeper这些通过算法方式解决一致性问题的中间件相比,就显得不足了。同时,在引入MySQL的存储时,由于多了一个中间件的存在,整个Nacos系统的整体可用性一定是会所有下降的。所以为了弥补可用性的下降,在生产上MySQL的高可用部署也是必须的,成本再次提高。不论如何提高,可用性都难以达到100%,所以这种方式,不论如何提升存储的可用性,理论上都会对Nacos集群的自身可用性造成微小的下降。以上思考主要从理论上,粗略讨论的,并没有经过详细的成本评估与可用性计算。所以,对于实际应用场景下,可能这些成本的增加和可用性的降低并没有那么多大的影响。同时,Spring Cloud Alibaba下使用的各开源组件都有对应的商业产品,在没有足够运维人力的团队下,使用对应的商业产品可能从各方面都会更加划算。参考资料Nacos官方文档代码示例本文介绍内容的客户端代码,示例读者可以通过查看下面仓库中的alibaba-nacos-config-client项目:Github:https://github.com/dyc87112/SpringCloud-Learning/Gitee:https://gitee.com/didispace/SpringCloud-Learning/如果您对这些感兴趣,欢迎star、follow、收藏、转发给予支持!以下专题教程也许您会有兴趣Spring Boot基础教程【新版】Spring Cloud从入门到精通

February 18, 2019 · 1 min · jiezi

spring-cloud Sleuth

一直没弄明白sleuth的tracerContext是如何创建和传递的,闲来无事研究了一下。由于对sleuth的源码不熟悉,准备通过debug brave.Tracer的nextId()方法,查看方法调用栈来找来龙去脉。首先创建两个service A和B,记作srvA、srvB,在srvA中添加testA controller,sevB中添加testB controller,testA中通过Feign调用testB。先看当用户通过浏览器调用srvA的时候,srvA是作为server的。configuration:TraceWebServletAutoConfiguration==>TracingFilterTraceHttpAutoConfiguration==>HttpTracingTraceAutoConfiguration==>TracingSleuthLogAutoConfiguration.Slf4jConfiguration==>CurrentTraceContext配置中,TracingFilter在实例化时需要一个HttpTracing: public static Filter create(HttpTracing httpTracing) { return new TracingFilter(httpTracing); } //Servlet运行时类 final ServletRuntime servlet = ServletRuntime.get(); //Slf4jCurrentTraceContext final CurrentTraceContext currentTraceContext; final Tracer tracer; final HttpServerHandler<HttpServletRequest, HttpServletResponse> handler; //TraceContext的数据提取器 final TraceContext.Extractor<HttpServletRequest> extractor; TracingFilter(HttpTracing httpTracing) { tracer = httpTracing.tracing().tracer(); currentTraceContext = httpTracing.tracing().currentTraceContext(); handler = HttpServerHandler.create(httpTracing, ADAPTER); extractor = httpTracing.tracing().propagation().extractor(GETTER); }HttpTracing Builder模式构造时接收一个Tracing: Tracing tracing; //客户端span解析器 HttpClientParser clientParser; String serverName; //服务端span解析器 HttpServerParser serverParser; HttpSampler clientSampler, serverSampler; Builder(Tracing tracing) { if (tracing == null) throw new NullPointerException(“tracing == null”); final ErrorParser errorParser = tracing.errorParser(); this.tracing = tracing; this.serverName = “”; // override to re-use any custom error parser from the tracing component this.clientParser = new HttpClientParser() { @Override protected ErrorParser errorParser() { return errorParser; } }; this.serverParser = new HttpServerParser() { @Override protected ErrorParser errorParser() { return errorParser; } }; this.clientSampler = HttpSampler.TRACE_ID; this.serverSampler(HttpSampler.TRACE_ID); }Tracing实例化: @Bean @ConditionalOnMissingBean // NOTE: stable bean name as might be used outside sleuth Tracing tracing(@Value("${spring.zipkin.service.name:${spring.application.name:default}}") String serviceName, Propagation.Factory factory, CurrentTraceContext currentTraceContext, Reporter<zipkin2.Span> reporter, Sampler sampler, ErrorParser errorParser, SleuthProperties sleuthProperties ) { return Tracing.newBuilder() .sampler(sampler) .errorParser(errorParser) .localServiceName(serviceName) //ExtraFieldPropagation.Factory .propagationFactory(factory) .currentTraceContext(currentTraceContext) .spanReporter(adjustedReporter(reporter)) .traceId128Bit(sleuthProperties.isTraceId128()) .supportsJoin(sleuthProperties.isSupportsJoin()) .build(); }下面看TracingFilter的doFilter: @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpRequest = (HttpServletRequest) request; HttpServletResponse httpResponse = servlet.httpResponse(response); // Prevent duplicate spans for the same request TraceContext context = (TraceContext) request.getAttribute(TraceContext.class.getName()); if (context != null) { // A forwarded request might end up on another thread, so make sure it is scoped Scope scope = currentTraceContext.maybeScope(context); try { chain.doFilter(request, response); } finally { scope.close(); } return; } Span span = handler.handleReceive(extractor, httpRequest); // Add attributes for explicit access to customization or span context request.setAttribute(SpanCustomizer.class.getName(), span.customizer()); request.setAttribute(TraceContext.class.getName(), span.context()); Throwable error = null; Scope scope = currentTraceContext.newScope(span.context()); try { // any downstream code can see Tracer.currentSpan() or use Tracer.currentSpanCustomizer() chain.doFilter(httpRequest, httpResponse); } catch (IOException | ServletException | RuntimeException | Error e) { error = e; throw e; } finally { scope.close(); if (servlet.isAsync(httpRequest)) { // we don’t have the actual response, handle later servlet.handleAsync(handler, httpRequest, httpResponse, span); } else { // we have a synchronous response, so we can finish the span handler.handleSend(ADAPTER.adaptResponse(httpRequest, httpResponse), error, span); } } }在SleuthLogAutoConfiguration中如果有slfj的包,则注入CurrentTraceContext: @Configuration @ConditionalOnClass(MDC.class) @EnableConfigurationProperties(SleuthSlf4jProperties.class) protected static class Slf4jConfiguration { @Bean @ConditionalOnProperty(value = “spring.sleuth.log.slf4j.enabled”, matchIfMissing = true) @ConditionalOnMissingBean public CurrentTraceContext slf4jSpanLogger() { return Slf4jCurrentTraceContext.create(); } … }Slf4jCurrentTraceContext中,delegate就是CurrentTraceContext.Default.inheritable(): public static final class Default extends CurrentTraceContext { static final ThreadLocal<TraceContext> DEFAULT = new ThreadLocal<>(); // Inheritable as Brave 3’s ThreadLocalServerClientAndLocalSpanState was inheritable static final InheritableThreadLocal<TraceContext> INHERITABLE = new InheritableThreadLocal<>(); final ThreadLocal<TraceContext> local; //静态方法create,local对象为ThreadLocal类型 /** Uses a non-inheritable static thread local / public static CurrentTraceContext create() { return new Default(DEFAULT); } //local对象为InheritableThreadLocal类型 //官方文档指出,inheritable方法在线程池的环境中需谨慎使用,可能会取出错误的TraceContext,这样会导致Span等信息会记录并关联到错误的traceId上 /* * Uses an inheritable static thread local which allows arbitrary calls to {@link * Thread#start()} to automatically inherit this context. This feature is available as it is was * the default in Brave 3, because some users couldn’t control threads in their applications. * * <p>This can be a problem in scenarios such as thread pool expansion, leading to data being * recorded in the wrong span, or spans with the wrong parent. If you are impacted by this, * switch to {@link #create()}. */ public static CurrentTraceContext inheritable() { return new Default(INHERITABLE); } Default(ThreadLocal<TraceContext> local) { if (local == null) throw new NullPointerException(“local == null”); this.local = local; } @Override public TraceContext get() { return local.get(); } //替换当前TraceContext,close方法将之前的TraceContext设置回去 //Scope接口继承了Closeable接口,在try中使用会自动调用close方法,为了避免用户忘记close方法,还提供了Runnable,Callable,Executor,ExecutorService包装方法 @Override public Scope newScope(@Nullable TraceContext currentSpan) { final TraceContext previous = local.get(); local.set(currentSpan); class DefaultCurrentTraceContextScope implements Scope { @Override public void close() { local.set(previous); } } return new DefaultCurrentTraceContextScope(); } }Slf4jCurrentTraceContext的delegate使用的就是一个InheritableThreadLocal,InheritableThreadLocal在创建子线程的时候,会将父线程的inheritableThreadLocals继承下来。这样就实现了TraceContext在父子线程中的传递。看一下CurrentTraceContext的maybeScope: //返回一个新的scope,如果当前scope就是传入的scope,返回一个空scope public Scope maybeScope(@Nullable TraceContext currentSpan) { //获取当前TraceContext TraceContext currentScope = get(); //如果传入的TraceContext为空,且当前TraceContext为空返回空scope if (currentSpan == null) { if (currentScope == null) return Scope.NOOP; return newScope(null); } return currentSpan.equals(currentScope) ? Scope.NOOP : newScope(currentSpan); }TracingFilter中HttpServerHandler解析Request:请输入代码2.srvA请求到servB时作为Client。TraceLoadBalancerFeignClient–>LoadBalancerFeignClient–>FeignLoadBalancer–>LazyTracingFeignClient–>Client ...

February 11, 2019 · 3 min · jiezi

Spring Boot 2.1.2 & Spring Cloud Greenwich 升级记录

节前没有新业务代码,正好Greenwich刚发布,于是开始为期四天的框架代码升级。之前的版本是 spring boot 1.5.10 , spring cloud Edgware.SR3依赖升级增加依赖管理插件 apply plugin: ‘io.spring.dependency-management’spring-cloud-starter-eureka → spring-cloud-starter-netflix-eureka-clientspring-cloud-starter-feign → spring-cloud-starter-openfeigngradle版本要求4.4boot : spring-boot-starter-data-jpadelete → deleteByIdfindone → findById这个改动确实大,返回值变成了Optional,合理是合理的,只改的真多。。boot : spring-boot-starter-data-redisJedis → Lettuce还好并没有使用它的autoconfiguration,配置上有一个小坑,Jedis的redis.timeout是表示connection timeout, 而Lettuce是表示command timeout,之前配置成0的,如果set到Lettuce的commandtimeout里面那就要抛异常了。配置:可以在build.gradle中加入,启动时会检查配置是否兼容compile “org.springframework.boot:spring-boot-properties-migrator” 注意:完成迁移后需要删除警告如上图会告知最新的配置格式boot: spring-boot-starter-actuatorendpoint的暴露方式变化,management.endpoints.web.exposure.include = “*” 表示暴露所有endpoints,如果配置了security那么也需要在security的配置中开放访问/actuator路径boot: spring-boot-starter-security自动注入的AuthenticationManager可能会找不到If you want to expose Spring Security’s AuthenticationManager as a bean, override the authenticationManagerBean method on your WebSecurityConfigurerAdapter and annotate it with @Bean.cloud : eureka各个项目在注册中心里面的客户端实例IP显示不正确,需要修改每个项目的bootstarp.yml${spring.cloud.client.ipAddress} → ${spring.cloud.client.ip-address}boot: spring-boot-starter-test:org.mockito.Matchers → org.mockito.ArgumentMatchers 注意build时的warningMock方法时请使用Mocikto.doReturn(…).when(…),不使用when(…).thenReturn(…),否则@spybean的会调用实际方法其他问题版本升级后会有deprecated的类或方法,所以要注意看console中build的warning信息由于spring cloud依赖管理插件强制cuator升级到4.0.1,导致我们使用的elestic-job不能正常工作,只能强行控制版本。dependencyManagement { imports { mavenBom “org.springframework.cloud:spring-cloud-dependencies:${SPRING_CLOUD_VERSION}” } dependencies { dependency ‘org.apache.curator:curator-framework:2.10.0’ dependency ‘org.apache.curator:curator-recipes:2.10.0’ dependency ‘org.apache.curator:curator-client:2.10.0’ }}如果启用出现error,报bean重复,首先确认是不是故意覆盖,如重写spring-boot自带的bean,如是,可以在bootstrap.yml加入spring.main.allow-bean-definition-overriding=trueFeignClient注解增加了contextId属性@FeignClient(value = “foo”, contextId = “fooFeign”)此contextId即表示bean id,所有注入使用时需要@AutowriedFooFeign fooFeign如果不写contextId,当多个class都是@FeignClient(“foo”),即会认为是同一个bean而排除上一条所说的warning ...

February 2, 2019 · 1 min · jiezi

Spring Cloud Alibaba基础教程:Nacos配置的多环境管理

前情回顾:《Spring Cloud Alibaba基础教程:使用Nacos实现服务注册与发现》《Spring Cloud Alibaba基础教程:支持的几种服务消费方式》《Spring Cloud Alibaba基础教程:使用Nacos作为配置中心》《Spring Cloud Alibaba基础教程:Nacos配置的加载规则详解》通过之前两篇对Nacos配置管理功能的介绍,已经学会了在Nacos中如何加入配置以及Spring Cloud应用如何通过配置来加载到对应的内容。接下来,我们讨论一个在使用配置中心时,都需要关注的一个问题:多环境的配置如何实现与管理?多环境管理在Nacos中,本身有多个不同管理级别的概念,包括:Data ID、Group、Namespace。只要利用好这些层级概念的关系,就可以根据自己的需要来实现多环境的管理。下面,我就来介绍一下,可以使用的几种实现方式:使用Data ID与profiles实现Data ID在Nacos中,我们可以理解为就是一个Spring Cloud应用的配置文件名。通过上一篇《Spring Cloud Alibaba基础教程:Nacos配置的加载规则详解》,我们知道默认情况下Data ID的名称格式是这样的:${spring.application.name}.properties,即:以Spring Cloud应用命名的properties文件。实际上,Data ID的规则中,还包含了环境逻辑,这一点与Spring Cloud Config的设计类似。我们在应用启动时,可以通过spring.profiles.active来指定具体的环境名称,此时客户端就会把要获取配置的Data ID组织为:${spring.application.name}-${spring.profiles.active}.properties。实际上,更原始且最通用的匹配规则,是这样的:${spring.cloud.nacos.config.prefix}-${spring.profile.active}.${spring.cloud.nacos.config.file-extension}。而上面的结果是因为${spring.cloud.nacos.config.prefix}和${spring.cloud.nacos.config.file-extension}都使用了默认值。动手试一试我们可以用《Spring Cloud Alibaba基础教程:使用Nacos作为配置中心》一文中的列子(可在文末仓库中获取)为基础,体验一下这种区分环境的配置方式。第一步:先在Nacos中,根据这个规则,创建两个不同环境的配置内容。比如:如上图,我们为alibaba-nacos-config-client应用,定义了DEV和TEST的两个独立的环境配置。我们可以在里面定义不同的内容值,以便后续验证是否真实加载到了正确的配置。第二步:在alibaba-nacos-config-client应用的配置文件中,增加环境配置:spring.profiles.active=DEV第三步:启动应用,我们可以看到日志中打印了,加载的配置文件:2019-01-30 15:25:18.216 INFO 96958 — [ main] o.s.c.a.n.c.NacosPropertySourceBuilder : Loading nacos data, dataId: ‘alibaba-nacos-config-client-DEV.properties’, group: ‘DEFAULT_GROUP’使用Group实现Group在Nacos中是用来对Data ID做集合管理的重要概念。所以,如果我们把一个环境的配置视为一个集合,那么也就可以实现不同环境的配置管理。对于Group的用法并没有固定的规定,所以我们在实际使用的时候,需要根据我们的具体需求,可以是架构运维上对多环境的管理,也可以是业务上对不同模块的参数管理。为了避免冲突,我们需要在架构设计之初,做好一定的规划。这里,我们先来说说如何用Group来实现多环境配置管理的具体实现方式。动手试一试第一步:先在Nacos中,通过区分Group来创建两个不同环境的配置内容。比如:如上图,我们为alibaba-nacos-config-client应用,定义了DEV环境和TEST环境的两个独立的配置,这两个匹配与上一种方法不同,它们的Data ID是完全相同的,只是GROUP不同。第二步:在alibaba-nacos-config-client应用的配置文件中,增加Group的指定配置:spring.cloud.nacos.config.group=DEV_GROUP第三步:启动应用,我们可以看到日志中打印了,加载的配置文件:2019-01-30 15:55:23.718 INFO 3216 — [main] o.s.c.a.n.c.NacosPropertySourceBuilder : Loading nacos data, dataId: ‘alibaba-nacos-config-client.properties’, group: ‘DEV_GROUP’使用Namespace实现Namespace在本系列教程中,应该还是第一次出现。先来看看官方的概念说明:用于进行租户粒度的配置隔离。不同的命名空间下,可以存在相同的Group或Data ID的配置。Namespace的常用场景之一是不同环境的配置的区分隔离,例如:开发测试环境和生产环境的资源(如配置、服务)隔离等。在官方的介绍中,就介绍了利用其可以作为环境的隔离使用,下面我们就来试一下吧!动手试一试第一步:先在Nacos中,根据环境名称来创建多个Namespace。比如:第二步:在配置列表的最上方,可以看到除了Public之外,多了几个刚才创建的Namepsace。分别在DEV和TEST空间下为alibaba-nacos-config-client应用创建配置内容:第三步:在alibaba-nacos-config-client应用的配置文件中,增加Namespace的指定配置,比如:spring.cloud.nacos.config.namespace=83eed625-d166-4619-b923-93df2088883a。这里需要注意namespace的配置不是使用名称,而是使用Namespace的ID。第四步:启动应用,通过访问localhost:8001/test接口,验证一下返回内容是否正确。这种方式下,目前版本的日志并不会输出与Namespace相关的信息,所以还无法以此作为加载内容的判断依据。深入思考上面我们分别利用Nacos配置管理功能中的几个不同纬度来实现多环境的配置管理。从结果上而言,不论用哪一种方式,都能够胜任需求,但是哪一种最好呢?第一种:通过Data ID与profile实现。优点:这种方式与Spring Cloud Config的实现非常像,用过Spring Cloud Config的用户,可以毫无违和感的过渡过来,由于命名规则类似,所以要从Spring Cloud Config中做迁移也非常简单。缺点:这种方式在项目与环境多的时候,配置内容就会显得非常混乱。配置列表中会看到各种不同应用,不同环境的配置交织在一起,非常不利于管理。建议:项目不多时使用,或者可以结合Group对项目根据业务或者组织架构做一些拆分规划。第二种:通过Group实现。优点:通过Group按环境讲各个应用的配置隔离开。可以非常方便的利用Data ID和Group的搜索功能,分别从应用纬度和环境纬度来查看配置。缺点:由于会占用Group纬度,所以需要对Group的使用做好规划,毕竟与业务上的一些配置分组起冲突等问题。建议:这种方式虽然结构上比上一种更好一些,但是依然可能会有一些混乱,主要是在Group的管理上要做好规划和控制。第三种:通过Namespace实现。优点:官方建议的方式,通过Namespace来区分不同的环境,释放了Group的自由度,这样可以让Group的使用专注于做业务层面的分组管理。同时,Nacos控制页面上对于Namespace也做了分组展示,不需要搜索,就可以隔离开不同的环境配置,非常易用。缺点:没有啥缺点,可能就是多引入一个概念,需要用户去理解吧。建议:直接用这种方式长远上来说会比较省心。虽然可能对小团队而言,项目不多,第一第二方式也够了,但是万一后面做大了呢?注意:不论用哪一种方式实现。对于指定环境的配置(spring.profiles.active=DEV、spring.cloud.nacos.config.group=DEV_GROUP、spring.cloud.nacos.config.namespace=83eed625-d166-4619-b923-93df2088883a),都不要配置在应用的bootstrap.properties中。而是在发布脚本的启动命令中,用-Dspring.profiles.active=DEV的方式来动态指定,会更加灵活!。参考资料Nacos官方文档代码示例本文示例读者可以通过查看下面仓库的中的alibaba-nacos-config-client项目:Github:https://github.com/dyc87112/SpringCloud-Learning/Gitee:https://gitee.com/didispace/SpringCloud-Learning/如果您对这些感兴趣,欢迎star、follow、收藏、转发给予支持!以下专题教程也许您会有兴趣Spring Boot基础教程Spring Cloud基础教程

February 1, 2019 · 1 min · jiezi

Spring-cloud Feign 的理解

feign的调用流程读取注解信息:EnableFeignClients–>FeignClientsRegistrar–>FeignClientFactoryBeanfeigh流程:ReflectiveFeign–>Contract–>SynchronousMethodHandler相关configuration:FeignClientsConfiguration,FeignAutoConfiguration,DefaultFeignLoadBalancedConfiguration,FeignRibbonClientAutoConfiguration(Ribbon)在FeignClientsRegistrar中: @Override public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { //注册feign配置信息 registerDefaultConfiguration(metadata, registry); //注册feign client registerFeignClients(metadata, registry); } private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata, Map<String, Object> attributes) { String className = annotationMetadata.getClassName(); //准备注入FeignClientFactoryBean BeanDefinitionBuilder definition = BeanDefinitionBuilder .genericBeanDefinition(FeignClientFactoryBean.class); … }查看FeignClientFactoryBean: @Override public Object getObject() throws Exception { FeignContext context = applicationContext.getBean(FeignContext.class); //构建Feign.Builder Feign.Builder builder = feign(context); //如果注解没有指定URL if (!StringUtils.hasText(this.url)) { String url; if (!this.name.startsWith(“http”)) { url = “http://” + this.name; } else { url = this.name; } url += cleanPath(); return loadBalance(builder, context, new HardCodedTarget<>(this.type, this.name, url)); } //如果指定了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) { // 因为指定了URL且classpath下有Ribbon,获取client的delegate(unwrap) // 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 targeter.target(this, builder, context, new HardCodedTarget<>( this.type, this.name, url)); } protected <T> T loadBalance(Feign.Builder builder, FeignContext context, HardCodedTarget<T> target) { //获取Feign Client实例 Client client = getOptional(context, Client.class); if (client != null) { builder.client(client); //DefaultTargeter或者HystrixTargeter Targeter targeter = get(context, Targeter.class); //调用builder的target,其中就调用了Feign的newInstance return targeter.target(this, builder, context, target); } throw new IllegalStateException( “No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-netflix-ribbon?”); }在FeignClientsConfiguration配置了Feign.Builder,prototype类型: @Bean @Scope(“prototype”) @ConditionalOnMissingBean public Feign.Builder feignBuilder(Retryer retryer) { return Feign.builder().retryer(retryer); }Feign的Builder.build返回了一个ReflectiveFeign: public Feign build() { SynchronousMethodHandler.Factory synchronousMethodHandlerFactory = new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger, logLevel, decode404); ParseHandlersByName handlersByName = new ParseHandlersByName(contract, options, encoder, decoder, errorDecoder, synchronousMethodHandlerFactory); //ReflectiveFeign构造参数 //ParseHandlersByName作用是通过传入的target返回代理接口下的方法的各种信息(MethodHandler) //Contract:解析接口的方法注解规则,生成MethodMetadata //Options:Request超时配置 //Encoder:请求编码器 //Decoder:返回解码器 //ErrorDecoder:错误解码器 //SynchronousMethodHandler.Factory是构建SynchronousMethodHandler的工厂 //Client:代表真正执行HTTP的组件 //Retryer:该组决定了在http请求失败时是否需要重试 //RequestInterceptor:请求前的拦截器 //Logger:记录日志组件,包含各个阶段记录日志的方法和留给用户自己实现的log方法 //Logger.Level:日志级别 //decode404:处理404的策略,返回空还是报错 //synchronousMethodHandlerFactory通过所有的信息去包装一个synchronousMethodHandler,在调用invoke方法的时候执行HTTP return new ReflectiveFeign(handlersByName, invocationHandlerFactory); }在调用Feign.Builder的target的时候,调用了ReflectiveFeign.newInstance: /** * creates an api binding to the {@code target}. As this invokes reflection, care should be taken * to cache the result. */ @SuppressWarnings(“unchecked”) @Override //接收Target参数(包含feign代理接口的类型class,名称,http URL) public <T> T newInstance(Target<T> target) { //首先通过ParseHandlersByName解析出接口中包含的方法,包装RequestTemplate,组装成<name, MethodHandler> Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target); Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>(); //接口default方法List List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>(); for (Method method : target.type().getMethods()) { if (method.getDeclaringClass() == Object.class) { continue; } else if(Util.isDefault(method)) { DefaultMethodHandler handler = new DefaultMethodHandler(method); defaultMethodHandlers.add(handler); methodToHandler.put(method, handler); } else { methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method))); } } //InvocationHandlerFactory.Default()返回了一个ReflectiveFeign.FeignInvocationHandler对象,通过传入的methodHandler map 调用目标对象的对应方法 InvocationHandler handler = factory.create(target, methodToHandler); //生成JDK代理对象 T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(), new Class<?>[]{target.type()}, handler); //绑定接口的默认方法到代理对象 for(DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) { defaultMethodHandler.bindTo(proxy); } return proxy; }生成Feign代理对象的基本流程图:当调用接口方法时,实际上就是调用代理对象invoke方法: @Override public Object invoke(Object[] argv) throws Throwable { //工厂创建请求模版 RequestTemplate template = buildTemplateFromArgs.create(argv); //每次克隆一个新的Retryer Retryer retryer = this.retryer.clone(); while (true) { try { //这里调用实际的Feign client execute return executeAndDecode(template); } catch (RetryableException e) { //失败重试 retryer.continueOrPropagate(e); if (logLevel != Logger.Level.NONE) { logger.logRetry(metadata.configKey(), logLevel); } continue; } } }在DefaultFeignLoadBalancedConfiguration里实例化了LoadBalancerFeignClient @Override public Response execute(Request request, Request.Options options) throws IOException { try { URI asUri = URI.create(request.url()); String clientName = asUri.getHost(); URI uriWithoutHost = cleanUrl(request.url(), clientName); //delegate这里是Client.Default实例,底层调用的是java.net原生网络访问 FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest( this.delegate, request, uriWithoutHost); IClientConfig requestConfig = getClientConfig(options, clientName); //executeWithLoadBalancer会根据ribbon的负载均衡算法构建url,这里不展开 return lbClient(clientName).executeWithLoadBalancer(ribbonRequest, requestConfig).toResponse(); } catch (ClientException e) { IOException io = findIOException(e); if (io != null) { throw io; } throw new RuntimeException(e); } } ...

January 31, 2019 · 3 min · jiezi

Spring Cloud Alibaba基础教程:Nacos配置的加载规则详解

前情回顾:《Spring Cloud Alibaba基础教程:使用Nacos实现服务注册与发现》《Spring Cloud Alibaba基础教程:支持的几种服务消费方式(RestTemplate、WebClient、Feign)》《Spring Cloud Alibaba基础教程:使用Nacos作为配置中心》上一篇,我们学习了如何在Nacos中创建配置,以及如何使用Spring Cloud Alibaba的Nacos客户端模块来加载配置。在入门例子中,我们只配置了Nacos的地址信息,没有配置任何其他与配置加载相关的其他内容。所以,接下来准备分几篇说说大家问的比较多的一些实际使用的问题或疑问。加载规则在《Spring Cloud Alibaba基础教程:使用Nacos作为配置中心》一文中,我们的例子完全采用了默认配置完成。所以,一起来看看Spring Cloud Alibaba Nacos模块默认情况下是如何加载配置信息的。首先,回顾一下,我们在入门例子中,Nacos中创建的配置内容是这样的:Data ID:alibaba-nacos-config-client.propertiesGroup:DEFAULT_GROUP拆解一下,主要有三个元素,它们与具体应用的配置内容对应关系如下:Data ID中的alibaba-nacos-config-client:对应客户端的配置spring.cloud.nacos.config.prefix,默认值为${spring.application.name},即:服务名Data ID中的properties:对应客户端的配置spring.cloud.nacos.config.file-extension,默认值为propertiesGroup的值DEFAULT_GROUP:对应客户端的配置spring.cloud.nacos.config.group,默认值为DEFAULT_GROUP在采用默认值的应用要加载的配置规则就是:Data ID=${spring.application.name}.properties,Group=DEFAULT_GROUP。下面,我们做一些假设例子,方便大家理解这些配置之间的关系:例子一:如果我们不想通过服务名来加载,那么可以增加如下配置,就会加载到Data ID=example.properties,Group=DEFAULT_GROUP的配置内容了:spring.cloud.nacos.config.prefix=example例子二:如果我们想要加载yaml格式的内容,而不是Properties格式的内容,那么可以通过如下配置,实现加载Data ID=example.yaml,Group=DEFAULT_GROUP的配置内容了:spring.cloud.nacos.config.prefix=examplespring.cloud.nacos.config.file-extension=yaml例子三:如果我们对配置做了分组管理,那么可以通过如下配置,实现加载Data ID=example.yaml,Group=DEV_GROUP的配置内容了:spring.cloud.nacos.config.prefix=examplespring.cloud.nacos.config.file-extension=yamlspring.cloud.nacos.config.group=DEV_GROUP深入思考上面,我们具体介绍了在Nacos中添加的各种配置与Spring Cloud应用中客户端配置的对照关系。对于spring.cloud.nacos.config.prefix和spring.cloud.nacos.config.file-extension来说,没有太多的花样可以去揣摩,大部分用户默认配置就可以使用,或者通过spring.cloud.nacos.config.file-extension修改下配置格式的后缀。但是对于spring.cloud.nacos.config.group的配置来说,还是可以派一些特殊的作用,比如:用它来区分不同的产品组下各个应用的配置内容(解决可能应用名冲突的问题)、或者用它来区分不同用途的配置内容、再或者用它来区分不同环境的配置(Nacos下的配置纬度很多,我们可以通过不同的手段来实现多环境的配置,后面会专门写一篇如何实现多环境的配置)等。如果您对spring.cloud.nacos.config.group还有什么其他妙用,欢迎留言分享您的使用方案。参考资料Nacos官方文档代码示例本系列教程的代码案例,都可以通过下面的仓库查看:Github:https://github.com/dyc87112/SpringCloud-Learning/Gitee:https://gitee.com/didispace/SpringCloud-Learning/如果您对这些感兴趣,欢迎star、follow、收藏、转发给予支持!以下专题教程也许您会有兴趣Spring Boot基础教程Spring Cloud基础教程

January 31, 2019 · 1 min · jiezi

Spring Cloud Alibaba基础教程:使用Nacos作为配置中心

通过本教程的前两篇:《Spring Cloud Alibaba基础教程:使用Nacos实现服务注册与发现》《Spring Cloud Alibaba基础教程:支持的几种服务消费方式(RestTemplate、WebClient、Feign)》我们已经学会了,如何利用Nacos实现服务的注册与发现。同时,也介绍了在Spring Cloud中,我们可以使用的几种不同编码风格的服务消费方式。接下来,我们再来一起学习一下Nacos的另外一个重要能力:配置管理。简介Nacos除了实现了服务的注册发现之外,还将配置中心功能整合在了一起。通过Nacos的配置管理功能,我们可以将整个架构体系内的所有配置都集中在Nacos中存储。这样做的好处,在以往的教程中介绍Spring Cloud Config时也有提到,主要有以下几点:分离的多环境配置,可以更灵活的管理权限,安全性更高应用程序的打包更为纯粹,以实现一次打包,多处运行的特点(《云原声应用的12要素》之一)Nacos的配置管理模型与淘宝开源的配置中心Diamond类似,基础层面都通过DataId和Group来定位配置内容,除此之外还增加了很多其他的管理功能。快速入门下面我们通过一个简单的例子来介绍如何在Nacos中创建配置内容以及如何在Spring Cloud应用中加载Nacos的配置信息。创建配置第一步:进入Nacos的控制页面,在配置列表功能页面中,点击右上角的“+”按钮,进入“新建配置”页面,如下图填写内容:其中:Data ID:填入alibaba-nacos-config-client.propertiesGroup:不修改,使用默认值DEFAULT_GROUP配置格式:选择Properties配置内容:应用要加载的配置内容,这里仅作为示例,做简单配置,比如:didispace.title=spring-cloud-alibaba-learning创建应用第一步:创建一个Spring Boot应用,可以命名为:alibaba-nacos-config-client。第二步:编辑pom.xml,加入必要的依赖配置,比如:<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.5.RELEASE</version> <relativePath/> <!– lookup parent from repository –></parent><dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Finchley.SR1</version> <type>pom</type> <scope>import</scope> </dependency> <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> </dependencies></dependencyManagement><dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.2</version> <optional>true</optional> </dependency></dependencies>上述内容主要三部分:parent:定义spring boot的版本dependencyManagement:spring cloud的版本以及spring cloud alibaba的版本,由于spring cloud alibaba还未纳入spring cloud的主版本管理中,所以需要自己加入dependencies:当前应用要使用的依赖内容。这里主要新加入了Nacos的配置客户端模块:spring-cloud-starter-alibaba-nacos-config。由于在dependencyManagement中已经引入了版本,所以这里就不用指定具体版本了。可以看到,这个例子中并没有加入nacos的服务发现模块,所以这两个内容是完全可以独立使用的第三步:创建应用主类,并实现一个HTTP接口:@SpringBootApplicationpublic class TestApplication { public static void main(String[] args) { SpringApplication.run(TestApplication.class, args); } @Slf4j @RestController @RefreshScope static class TestController { @Value("${didispace.title:}") private String title; @GetMapping("/test") public String hello() { return title; } }}内容非常简单,@SpringBootApplication定义是个Spring Boot应用;还定义了一个Controller,其中通过@Value注解,注入了key为didispace.title的配置(默认为空字符串),这个配置会通过/test接口返回,后续我们会通过这个接口来验证Nacos中配置的加载。另外,这里还有一个比较重要的注解@RefreshScope,主要用来让这个类下的配置内容支持动态刷新,也就是当我们的应用启动之后,修改了Nacos中的配置内容之后,这里也会马上生效。第四步:创建配置文件bootstrap.properties,并配置服务名称和Nacos地址spring.application.name=alibaba-nacos-config-clientserver.port=8001spring.cloud.nacos.config.server-addr=127.0.0.1:8848注意:这里必须使用bootstrap.properties。同时,spring.application.name值必须与上一阶段Nacos中创建的配置Data Id匹配(除了.properties或者.yaml后缀)。第五步:启动上面创建的应用。2019-01-27 18:29:43.497 INFO 93597 — [ main] o.s.c.a.n.c.NacosPropertySourceBuilder : Loading nacos data, dataId: ‘alibaba-nacos-config-client.properties’, group: ‘DEFAULT_GROUP'2019-01-27 18:29:43.498 INFO 93597 — [ main] b.c.PropertySourceBootstrapConfiguration : Located property source: CompositePropertySource {name=‘NACOS’, propertySources=[NacosPropertySource {name=‘alibaba-nacos-config-client.properties’}]}在启动的时候,我们可以看到类似上面的日志信息,这里会输出应用程序要从Nacos中获取配置的dataId和group。如果在启动之后,发现配置信息没有获取到的时候,可以先从这里着手,看看配置加载的目标是否正确。第六步:验证配置获取和验证动态刷新用curl或者postman等工具,访问接口: localhost:8001/test,一切正常的话,将返回Nacos中配置的spring-cloud-alibaba-learning。然后,再通过Nacos页面,修改这个内容,点击发布之后,再访问接口,可以看到返回结果变了。同时,在应用的客户端,我们还能看到如下日志:2019-01-27 18:39:14.162 INFO 93597 — [-127.0.0.1_8848] o.s.c.e.event.RefreshEventListener : Refresh keys changed: [didispace.title]在Nacos中修改了Key,在用到这个配置的应用中,也自动刷新了这个配置信息。参考资料Nacos官方文档Nacos源码分析代码示例本文示例读者可以通过查看下面仓库的中的alibaba-nacos-config-client项目:Github:https://github.com/dyc87112/SpringCloud-Learning/Gitee:https://gitee.com/didispace/SpringCloud-Learning/如果您对这些感兴趣,欢迎star、follow、收藏、转发给予支持!以下专题教程也许您会有兴趣Spring Boot基础教程Spring Cloud基础教程 ...

January 30, 2019 · 1 min · jiezi

Spring Cloud Alibaba基础教程:支持的几种服务消费方式

通过《Spring Cloud Alibaba基础教程:使用Nacos实现服务注册与发现》一文的学习,我们已经学会如何使用Nacos来实现服务的注册与发现,同时也介绍如何通过LoadBalancerClient接口来获取某个服务的具体实例,并根据实例信息来发起服务接口消费请求。但是这样的做法需要我们手工的去编写服务选取、链接拼接等繁琐的工作,对于开发人员来说非常的不友好。所以接下来,我们再来看看除此之外,还支持哪些其他的服务消费方式。使用RestTemplate在之前的例子中,已经使用过RestTemplate来向服务的某个具体实例发起HTTP请求,但是具体的请求路径是通过拼接完成的,对于开发体验并不好。但是,实际上,在Spring Cloud中对RestTemplate做了增强,只需要稍加配置,就能简化之前的调用方式。比如:@EnableDiscoveryClient@SpringBootApplicationpublic class TestApplication { public static void main(String[] args) { SpringApplication.run(TestApplication.class, args); } @Slf4j @RestController static class TestController { @Autowired RestTemplate restTemplate; @GetMapping("/test") public String test() { String result = restTemplate.getForObject(“http://alibaba-nacos-discovery-server/hello?name=didi”, String.class); return “Return : " + result; } } @Bean @LoadBalanced public RestTemplate restTemplate() { return new RestTemplate(); }}可以看到,在定义RestTemplate的时候,增加了@LoadBalanced注解,而在真正调用服务接口的时候,原来host部分是通过手工拼接ip和端口的,直接采用服务名的时候来写请求路径即可。在真正调用的时候,Spring Cloud会将请求拦截下来,然后通过负载均衡器选出节点,并替换服务名部分为具体的ip和端口,从而实现基于服务名的负载均衡调用。关于这种方式,可在文末仓库查看完整代码示例。而对于这种方式的实现原理,可以参考我之前写的这篇文章的前半部分:Spring Cloud源码分析(二)Ribbon使用WebClientWebClient是Spring 5中最新引入的,可以将其理解为reactive版的RestTemplate。下面举个具体的例子,它将实现与上面RestTemplate一样的请求调用:@EnableDiscoveryClient@SpringBootApplicationpublic class TestApplication { public static void main(String[] args) { SpringApplication.run(TestApplication.class, args); } @Slf4j @RestController static class TestController { @Autowired private WebClient.Builder webClientBuilder; @GetMapping("/test”) public Mono<String> test() { Mono<String> result = webClientBuilder.build() .get() .uri(“http://alibaba-nacos-discovery-server/hello?name=didi”) .retrieve() .bodyToMono(String.class); return result; } } @Bean @LoadBalanced public WebClient.Builder loadBalancedWebClientBuilder() { return WebClient.builder(); }}可以看到,在定义WebClient.Builder的时候,也增加了@LoadBalanced注解,其原理与之前的RestTemplate时一样的。关于WebClient的完整例子也可以通过在文末的仓库中查看。使用Feign上面介绍的RestTemplate和WebClient都是Spring自己封装的工具,下面介绍一个Netflix OSS中的成员,通过它可以更方便的定义和使用服务消费客户端。下面也举一个具体的例子,其实现内容与上面两种方式结果一致:第一步:在pom.xml中增加openfeign的依赖:<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId></dependency>第二步:定义Feign客户端和使用Feign客户端:@EnableDiscoveryClient@SpringBootApplication@EnableFeignClientspublic class TestApplication { public static void main(String[] args) { SpringApplication.run(TestApplication.class, args); } @Slf4j @RestController static class TestController { @Autowired Client client; @GetMapping("/test") public String test() { String result = client.hello(“didi”); return “Return : " + result; } } @FeignClient(“alibaba-nacos-discovery-server”) interface Client { @GetMapping("/hello”) String hello(@RequestParam(name = “name”) String name); }}这里主要先通过@EnableFeignClients注解开启扫描Spring Cloud Feign客户端的功能;然后又创建一个Feign的客户端接口定义。使用@FeignClient注解来指定这个接口所要调用的服务名称,接口中定义的各个函数使用Spring MVC的注解就可以来绑定服务提供方的REST接口,比如下面就是绑定alibaba-nacos-discovery-server服务的/hello接口的例子。最后,在Controller中,注入了Client接口的实现,并调用hello方法来触发对服务提供方的调用。关于使用Feign的完整例子也可以通过在文末的仓库中查看。深入思考如果之前已经用过Spring Cloud的读者,肯定会这样的感受:不论我用的是RestTempalte也好、还是用的WebClient也好,还是用的Feign也好,似乎跟我用不用Nacos没啥关系?我们在之前介绍Eureka和Consul的时候,也都是用同样的方法来实现服务调用的,不是吗?确实是这样,对于Spring Cloud老手来说,就算我们更换了Nacos作为新的服务注册中心,其实对于我们应用层面的代码是没有影响的。那么为什么Spring Cloud可以带给我们这样的完美编码体验呢?实际上,这完全归功于Spring Cloud Common的封装,由于在服务注册与发现、客户端负载均衡等方面都做了很好的抽象,而上层应用方面依赖的都是这些抽象接口,而非针对某个具体中间件的实现。所以,在Spring Cloud中,我们可以很方便的去切换服务治理方面的中间件。代码示例本文示例读者可以通过查看下面仓库:Github:https://github.com/dyc87112/SpringCloud-Learning/Gitee:https://gitee.com/didispace/SpringCloud-Learning/其中,本文的几种示例可查看下面的几个项目:alibaba-nacos-discovery-server:服务提供者,必须启动alibaba-nacos-discovery-client-resttemplate:使用RestTemplate消费alibaba-nacos-discovery-client-webclient:使用WebClient消费alibaba-nacos-discovery-client-feign:使用Feign消费如果您对这些感兴趣,欢迎star、follow、收藏、转发给予支持!以下专题教程也许您会有兴趣Spring Boot基础教程Spring Cloud基础教程 ...

January 29, 2019 · 1 min · jiezi

微服务不是全部,只是特定领域的子集

大家都在学SpringCloud,貌似学会了SC就牛逼哄哄,感觉不得了的样子。但微服务,在整个企业级应用中,只占了一小部分。微服务引入的问题比解决的问题还要多,你会遇到各种各样的bottleneck。微服务解决的是计算节点的问题,然而根源却在存储节点。当业务规模变得越来越庞大,存储、编码、管理都会成为问题。接下来我们谈一些放之四海而皆准的道理,不需要贴上"XX公司最佳实践"之类的标签。下面是一张因数据扩张引出的微服务相关的图,简约但不简单。中小型公司只要有这些元素,就能玩的很好;大点的公司,因为规模太大,每个组件都会遇到瓶颈,所谓的专项的优化并不能脱离它的本质。那我们开始。注意,这张图仅是主要数据路径,一个子集,其他的包括CDN、通讯层等,不在此列。这张图并不包含某个特定领域的具体架构,属于一个整体性的概括。我们从数据库容量的瓶颈说起,看一下微服务在其中的比重。数据库用户数据要存储,就存在数据库。过去这么多年,NoSQL并不能消除开发人员的恐惧,所以,MySQL之类还是大多数公司的首选存储。假设你的业务增长的很好,这个就有意思多了。项目开始,你的sql玩的越6,那么给后人埋的坑,越多。因为sql的功能太丰富了,一不小心,就炫技了。你会发现,林子越大,对sql的规范要求越高。一些官宣的特性,在公司内是严格禁止的。市场发展很好,终于来报应了。以前的技巧变成了现在的累赘。慢查询、全文扫描,招招毙命。想要加缓存,结果发现无从下手;想要分库分表,结果发现表关系错综复杂。小表和宽表所以第一步,还是得去填坑。一个超过3个表的联合查询业务,大概率是不合理的。在加缓存和分库分表之前,还是得重新设计一下数据表。忘掉什么数据库范式,我们将存在两类表:小表和宽表。小表提供了最基本的数据,可能一个简单的KV就完成了。一些联合查询,是直接可以在程序里进行循环拼接的。程序里循环1000次10毫秒的查询,比单次查询耗费6秒要强的多。这就是分布式系统的特点,小耗时的批量查询,比hang在那里更加有生命力。宽表通过冗余的方式,提供了某个重要功能常用的分析数据。这种表的字段一般都特别多,在写入时通过拼接获取冗余数据,一般用在读多写少的场景。完成了这一步,接下来的工作才能进行。分库分表在《“分库分表" ?选型和流程要慎重,否则会失控》中,详细的说明了分库分表的选型,这里浅谈一下过程。分库分表很可能会引入某一种中间件,因为仅仅将数据库分开还不行。HA,FailOver等特性,是同时需要的。分库分为垂直分和水平分。垂直面向的是业务拆分,即将一部分表按照业务逻辑独立到其他库中;水平面向的是容量,即通过分库分表的模式使数据有一个扩张的途径。数据一定要有一个可以度量的切分维度,否则就过于分散,或者过于倾斜,影响后续的处理。数据同步有分就有合,比如某些报表业务需要全量的数据。不同业务通过共享数据库来共享数据不得不说是个非常蠢的主意。这个时候就需要一些数据同步工具。数据同步组件可以说是一个公司的必备组件。有基于最后更新时间的高延迟同步工具,也有基于binlog的低延迟同步工具。有的公司为了稳定,还会有所谓的多机房同步。 数据同步最怕异常,因为大多数同步都有顺序性要求。一切运行良好的时候,大家皆大欢喜;一旦出现异常,就需要其他手段来保证异常期间的数据同步和延迟。这都是些脏活,自动化有时候会适得其反,监控是第一位的。分层的数据存储可以预见的是,即使你分库分表了,还是能很快达到瓶颈。分库分表后,你的一些统计功能可能还用不了了,在一些传统的管理系统上,这是硬伤。一个分层的数据存储层是必要的。你的一些业务,可能一个分支走的是MySQL,换了另外一个条件就成了ES。不同的DB做不同的事情。RDBMS只做原是数据的存储和查询,是扁平快的数据通道;特定的单机高性能DB,做一些汇聚和科学计算;分布式的类RT的存储,用来存储一些中等规模的数据,并提供一些中延迟的搜索功能;海量的存储系统,存储系统所有的历史记录,并提供离线分析功能。不要想着某一类存储解决所有的问题,那是骗人的。存储部分的复杂性不是普通的微服务能够相比的。 是谁保证了分层的数据存储设计呢?除了一部分通过MQ分发数据的业务,还是得靠我们的数据同步组件。缓存但DB的压力实在是太大了,我们不得不考虑缓存。缓存不能乱用,有两个原则:一个是缓存不能侵入业务,也就是不能带有业务逻辑;一个是缓存的命中率要高,否则适得其反。缓存是对高并发、高速接口的补充,是系统稳定性的必要不充分条件。除了Redis等外置的缓存集群,jvm内缓存也是一个比较重要的场所。缓存的存在是因为I/O设备的缓慢,通常放在内存中,断电后即消失。缓存涉及到源数据库和缓存数据库之间的数据同步。通常,更新源库时,会同时删掉缓存中相关的就数据,这样在下次读取的时候,能够读取到最新的数据。缓存限制最大的就是其容量问题,而且都贵的很。假如业务模式固定,一些kv存储使用LevelDB或者HBase等方案,会显著节约成本。模块化是时候将工程模块化了,毕竟上百个程序员共享一个代码库,风险已经很大了。模块化通常会按照业务线进行拆分。比如,支付模块和报表模块的拆分。模块拆分后,相似的模块会共享数据库。但更多的是通过冗余数据来解决,这样能将业务解耦,一部分出现问题,另一部分能够运行良好。好比你隔壁出了杀人案你第二天还能正常去上班。模块之间要找到一种交互方式,比如使用HttpClient、OkHttp等。重要的是统一,统一了以后就有一个高大上的名字了:RPC。一个小模块很有可能会发展为一个大的业务线,也有可能无人问津。MQ模块化之间另一种共享数据或者数据交互的方式就是MQ。除了有削峰等功效,MQ更多改变的是一种交互模式,一种对业务的解耦。Kafka几乎每个公司都在用,最高能有几十万的吞吐量。RabbitMQ、RocketMQ等,更多用在可靠性要求非常高的场景,但比较耗机器。MQ资源一般都要求绝对的高可靠,作为基础设施,一旦出问题,将带来非常大的事故。设计的时候要考虑异常情况下的数据处理流向,以及MQ恢复后的补偿策略。MQ集群设计的比较小一些才合理,避免不同业务,不同可靠性级别的消息互相影响。MQ在业务上和功能上要相互隔离,做到最小服务集合。为了避免MQ当机对正常业务产生影响,非重要链路上的MQ不能阻塞业务的正常进行,这种消息通常通过异步线程发送。微服务我们已经使用消息和模块化,将系统拆分成了多个工程。将这些工程使用统一的方式管理起来,统一其交互模式和在上面的治理,就是微服务的范畴。微服务就是一个多模块项目规范化的过程。非规范的服务与微服务体系,是要共存一段时间的,如何保证新旧服务的替换,是一个管理上的问题。功能组件根据SpringCloud的描述,一个服务想要被发现,需要将自己注册到通用的注册中心,其他服务可以从同一个地方,获取它的实例,进而调用。而真正产生调用的功能,就是RPC的功能。RPC要考虑一系列比如超时、重拾、熔断等功能。在某些访问量非常大的节点,可能还要考虑预热。RPC要能产生一些统计性数据,比如TPS、QPS、TP值等,很显然SpringCloud是缺乏的,我们要借助外部系统进行分析。在外部请求流转到内部之前,需要经过一层网关的处理。像一些通用的操作,比如权限、限流、灰度等,就可以在网关层处理。服务治理微服务最重要的特色就是其治理功能。服务治理的依据就是监控信息。通过统计每次调用的大小、耗时、分布,能够得出服务的大体拓扑。通常以下信息最有用:1、QPS,时间序列的qps分布,最高区间qps2、平均响应时间,接口的平均响应时间,最大耗时和最小耗时3、TP值分布,90%,99%等请求是在x耗时内完成通过以上信息能够对服务进行画像。是扩容、缩容、专项治理的数据依据。微服务引出的另外一个问题就是调用链,即某个请求的真实路径。分布式环境下的问题排查,会非常的困难,调用链能够帮助研发快速定位问题,并帮助理解业务的数据流向。服务治理的目的就是找到不合理的请求和分布,比如某个接口耗时太长;某个接口请求量大,需要加缓存;某个功能依赖链条过长,需要业务优化等。服务治理要借助大量的外部分析工具,更多通用的业务模型,需要大数据平台的支持。我们把监控/报警也放在服务治理的部分,在《这么多监控组件,总有一款适合你》中,我们详细的讨论了监控部分的技术选择方案。日志微服务产生的另外一个问题就是日志太过分散。一个核心的业务可能有上百个实例,你不可能打开100个终端去看日志。这就涉及到日志的收集。日志归集功能就是把分散的日志集合到一个地方,它的主要挑战就是数据量。通常日志分为两部分,一部分是全量的,可以通过定时同步等方式,备份到日志堡垒机或者hdfs中;一部分是过滤后的日志,比如一些异常信息,集中在某一个处理平台中进行报警。很多研发喜欢将用户行为数据输出到日志文件中,这部分日志被收集后,会通过流计算或者离线计算,得到一些推荐和模型。日志信息进入了大数据处理的范畴,我们不过多描述。持续集成如果一个上点规模的公司,技术团队有什么值得一做的系统,那么发布系统算一个。《发布系统有那么难么?》中,谈了一种可能的模式。发布系统就是给一堆脚本包了一张方便的皮。一些流程性工具、发布验证、CI/CD功能,很容易能够添加到自己的发布系统中。很多微服务推广的文章中,谈到虚拟化(Docker)等,其实不是必须的。虚拟化减少了服务编排的时间,能够方便的进行扩容和缩容,但对监控、日志收集、网络拓扑等,要求比较高。建议是整个体系中的最后一步而不是第一步。你的系统是否灵活,还与公司的文化环境相关。如果上个线走审批流程就需要一两周,那么做一个敏捷的持续集成系统就不是那么必要了。基础设施基础设施更多指的是运维体系,这是支撑整个系统健康发展的基石。我倾向于基础运维和基础架构不分家,因为它们的模式和文化,是一个公司研发环境的基石。另外一些基础组件,比如配置中心、调度中心、分布式锁管理等,都对可靠性有较高的要求。END这套体系看着简单,也有固定的解决方案。但问题就在于,许多公司从成立玩到倒闭,玩了那么多年,还是没玩好。真是可怜。

January 25, 2019 · 1 min · jiezi

Spring Cloud Alibaba基础教程:Nacos 生产级版本 0.8.0

昨晚Nacos社区发布了第一个生产级版本:0.8.0。由于该版本除了Bug修复之外,还提供了几个生产管理非常重要的特性,所以觉得还是有必要写一篇讲讲这次升级,在后续的文章中也都将以0.8.0版本为基础。升级的理由如Nacos官方的发布文档中描述的那样,本版本将支持非常重要的三个特性:第一,用户登录。在过去版本的Nacos中,用户是可以直接访问Nacos的页面的,我们需要通过网络或者代理手段来增加这样的安全性控制,在该版本后就不需要了。第二:Prometheus的支持。对于一个基础中间件来说,完善的监控指标输出在生产环境是必须的,通过在/prometheus端点上暴露监控指标,以保障Nacos集群的正常服务。第三:Namespace的支持。服务发现的功能将支持Namespace的隔离,可以方便的在一套Nacos集群下,实现多环境服务发现的隔离等。发布清单可见文末参考资料。这些重要功能的具体使用,后续继续连载,敬请期待!安装与使用如果之前有看过《Spring Cloud Alibaba基础教程:使用Nacos实现服务注册与发现》的话,只需要将Nacos安装部分把安装包替换成 0.8.0 版本即可。下载地址:https://github.com/alibaba/na…下载完成之后,解压。根据不同平台,执行不同命令,启动单机版Nacos服务:Linux/Unix/Mac:sh startup.sh -m standaloneWindows:cmd startup.cmd -m standalonestartup.sh脚本位于Nacos解压后的bin目录下。启动完成之后,访问:http://127.0.0.1:8848/nacos/,可以进入Nacos的登录页面,具体如下;evernotecid://1F3482F0-828B-4B97-918F-4856C2E684DF/appyinxiangcom/113183/ENResource/p2004默认情况下,用户名与密码都为nacos,登录后进入控制台如下:evernotecid://1F3482F0-828B-4B97-918F-4856C2E684DF/appyinxiangcom/113183/ENResource/p2005对于应用端,不需要做任何改动,就能够适配新版本。如果还没有对接过Nacos,那么看看这篇吧:《Spring Cloud Alibaba基础教程:使用Nacos实现服务注册与发现》参考资料Nacos官方文档0.8.0版本说明

January 24, 2019 · 1 min · jiezi

java性能调优记录(限流)

问题spring-cloud-gateway 网关新增了一个限流功能,使用的是模块自带的限流过滤器 RequestRateLimiterGatewayFilterFactory,基于令牌桶算法,通过 redis 实现。其原理是 redis 中针对每个限流要素(比如针对接口限流),保存 2 个 key:tokenKey(令牌数量),timeKey(调用时间)。每次接口调用时,更新 tokenKey 的值为:原先的值 + (当前时间 - 原先时间)* 加入令牌的速度,如果新的 tokenKey 的值大于 1,那么允许调用,否则不允许;同时更新 redis 中 tokenKey,timeKey 的值。整个过程通过 lua 脚本实现。在加入限流功能之前,500 客户端并发访问,tps 为 6800 req/s,50% 时延为 70ms;加入限流功能之后,tps 为 2300 req/s,50% 时延为 205ms,同时,原先 cpu 占用率几乎 600%(6 核) 变成不到 400%(cpu 跑不满了)。2. 排查和解决过程2.1 单个 CPU 跑满查看单个线程的 cpu 占用:[root@auth-service imf2]# top -Hp 29360top - 15:16:27 up 102 days, 18:04, 1 user, load average: 1.61, 0.72, 0.34Threads: 122 total, 9 running, 113 sleeping, 0 stopped, 0 zombie%Cpu(s): 42.0 us, 7.0 sy, 0.0 ni, 49.0 id, 0.0 wa, 0.0 hi, 2.0 si, 0.0 stKiB Mem : 7678384 total, 126844 free, 3426148 used, 4125392 buff/cacheKiB Swap: 6291452 total, 2212552 free, 4078900 used. 3347956 avail Mem PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND29415 root 20 0 6964708 1.1g 14216 R 97.9 15.1 3:01.65 java29392 root 20 0 6964708 1.1g 14216 R 27.0 15.1 0:45.42 java29391 root 20 0 6964708 1.1g 14216 R 24.8 15.1 0:43.95 java29387 root 20 0 6964708 1.1g 14216 R 23.8 15.1 0:46.38 java29388 root 20 0 6964708 1.1g 14216 R 23.4 15.1 0:48.21 java29390 root 20 0 6964708 1.1g 14216 R 23.0 15.1 0:45.93 java29389 root 20 0 6964708 1.1g 14216 R 22.3 15.1 0:44.36 java线程 29415 几乎跑满了 cpu,查看是什么线程:[root@auth-service imf2]# printf ‘%x\n’ 2941572e7[root@auth-service imf2]# jstack 29360 | grep 72e7"lettuce-nioEventLoop-4-1" #40 daemon prio=5 os_prio=0 tid=0x00007f604cc92000 nid=0x72e7 runnable [0x00007f606ce90000]果然是操作 redis 的线程,和预期一致。查看 redis:cpu 占用率不超过 15%,没有 10ms 以上的慢查询。应该不会是 redis 的问题。查看线程栈信息:通过以下脚本每秒记录一次 jstack:[root@eureka2 jstack]# cat jstack.sh#!/bin/shi=0while [ $i -lt 30 ]; do/bin/sleep 1i=expr $i + 1jstack 29360 > “$i”.txtdone查看 lettuce 线程主要执行哪些函数:“lettuce-nioEventLoop-4-1” #36 daemon prio=5 os_prio=0 tid=0x00007f1eb07ab800 nid=0x4476 runnable [0x00007f1eec8fb000] java.lang.Thread.State: RUNNABLE at sun.misc.URLClassPath$Loader.findResource(URLClassPath.java:715) at sun.misc.URLClassPath.findResource(URLClassPath.java:215) at java.net.URLClassLoader$2.run(URLClassLoader.java:569) at java.net.URLClassLoader$2.run(URLClassLoader.java:567) at java.security.AccessController.doPrivileged(Native Method) at java.net.URLClassLoader.findResource(URLClassLoader.java:566) at org.springframework.boot.loader.LaunchedURLClassLoader.findResource(LaunchedURLClassLoader.java:57) at java.lang.ClassLoader.getResource(ClassLoader.java:1096) at org.springframework.core.io.ClassPathResource.resolveURL(ClassPathResource.java:155) at org.springframework.core.io.ClassPathResource.getURL(ClassPathResource.java:193) at org.springframework.core.io.AbstractFileResolvingResource.lastModified(AbstractFileResolvingResource.java:220) at org.springframework.scripting.support.ResourceScriptSource.retrieveLastModifiedTime(ResourceScriptSource.java:119) at org.springframework.scripting.support.ResourceScriptSource.isModified(ResourceScriptSource.java:109) - locked <0x000000008c074d00> (a java.lang.Object) at org.springframework.data.redis.core.script.DefaultRedisScript.getSha1(DefaultRedisScript.java:89) - locked <0x000000008c074c10> (a java.lang.Object) at org.springframework.data.redis.core.script.DefaultReactiveScriptExecutor.eval(DefaultReactiveScriptExecutor.java:113) at org.springframework.data.redis.core.script.DefaultReactiveScriptExecutor.lambda$execute$0(DefaultReactiveScriptExecutor.java:105) at org.springframework.data.redis.core.script.DefaultReactiveScriptExecutor$$Lambda$1268/1889039573.doInRedis(Unknown Source) at org.springframework.data.redis.core.script.DefaultReactiveScriptExecutor.lambda$execute$6(DefaultReactiveScriptExecutor.java:167) at org.springframework.data.redis.core.script.DefaultReactiveScriptExecutor$$Lambda$1269/1954779522.get(Unknown Source) at reactor.core.publisher.FluxDefer.subscribe(FluxDefer.java:46)可知该线程主要在执行 ReactiveRedisTemplate 类的 execute(RedisScript<T> script, List<K> keys, List<?> args) 方法,即运行 lua 脚本。猜想:既然是因为 lettuce-nioEventLoop 线程跑满了 cpu,那么通过创建多个 lettuce-nioEventLoop 线程,以充分利用多核的特点,是否可以解决呢?以下为源码分析阶段:// 1. RedisConnectionFactory bean 的创建依赖 ClientResources@Bean@ConditionalOnMissingBean(RedisConnectionFactory.class)public LettuceConnectionFactory redisConnectionFactory( ClientResources clientResources) throws UnknownHostException { LettuceClientConfiguration clientConfig = getLettuceClientConfiguration( clientResources, this.properties.getLettuce().getPool()); return createLettuceConnectionFactory(clientConfig);}// 2. ClientResources bean 的创建如下@Bean(destroyMethod = “shutdown”)@ConditionalOnMissingBean(ClientResources.class)public DefaultClientResources lettuceClientResources() { return DefaultClientResources.create();}public static DefaultClientResources create() { return builder().build();}// 3. 创建 EventLoopGroupProvider 对象protected DefaultClientResources(Builder builder) { this.builder = builder; // 默认为 null,执行这块代码 if (builder.eventLoopGroupProvider == null) { // 设置处理 redis 连接的线程数:默认为 // Math.max(1, // SystemPropertyUtil.getInt(“io.netty.eventLoopThreads”, // Math.max(MIN_IO_THREADS, Runtime.getRuntime().availableProcessors()))); // 针对多核处理器,该值一般等于 cpu 的核的数量 int ioThreadPoolSize = builder.ioThreadPoolSize; if (ioThreadPoolSize < MIN_IO_THREADS) { logger.info(“ioThreadPoolSize is less than {} ({}), setting to: {}”, MIN_IO_THREADS, ioThreadPoolSize, MIN_IO_THREADS); ioThreadPoolSize = MIN_IO_THREADS; } this.sharedEventLoopGroupProvider = false; // 创建 EventLoopGroupProvider 对象 this.eventLoopGroupProvider = new DefaultEventLoopGroupProvider(ioThreadPoolSize); } else { this.sharedEventLoopGroupProvider = true; this.eventLoopGroupProvider = builder.eventLoopGroupProvider; } // 以下代码省略 …}// 4. 通过 EventLoopGroupProvider 创建 EventExecutorGroup 对象public static <T extends EventExecutorGroup> EventExecutorGroup createEventLoopGroup(Class<T> type, int numberOfThreads) { if (DefaultEventExecutorGroup.class.equals(type)) { return new DefaultEventExecutorGroup(numberOfThreads, new DefaultThreadFactory(“lettuce-eventExecutorLoop”, true)); } // 我们采用的是 Nio 模式,会执行这个分支 if (NioEventLoopGroup.class.equals(type)) { return new NioEventLoopGroup(numberOfThreads, new DefaultThreadFactory(“lettuce-nioEventLoop”, true)); } if (EpollProvider.isAvailable() && EpollProvider.isEventLoopGroup(type)) { return EpollProvider.newEventLoopGroup(numberOfThreads, new DefaultThreadFactory(“lettuce-epollEventLoop”, true)); } if (KqueueProvider.isAvailable() && KqueueProvider.isEventLoopGroup(type)) { return KqueueProvider.newEventLoopGroup(numberOfThreads, new DefaultThreadFactory(“lettuce-kqueueEventLoop”, true)); } throw new IllegalArgumentException(String.format(“Type %s not supported”, type.getName()));}// 5. NioEventLoopGroup 继承了 MultithreadEventLoopGroup;// 创建了多个 NioEventLoop;// 每个 NioEventLoop 都是单线程;// 每个 NioEventLoop 都可以处理多个连接。public class NioEventLoopGroup extends MultithreadEventLoopGroup { … }public abstract class MultithreadEventLoopGroup extends MultithreadEventExecutorGroup implements EventLoopGroup { … }public final class NioEventLoop extends SingleThreadEventLoop { … }以上分析可知,默认创建的 RedisConnectionFactory bean 其实是支持多线程的,但通过 jstack 等方式查看 lettuce-nioEventLoop 线程却只有一个。[root@ ~]# ss | grep 6379tcp ESTAB 0 0 ::ffff:10.201.0.27:36184 ::ffff:10.201.0.30:6379查看 redis 连接,发现只有一个。在 Netty 中,一个 EventLoop 线程可以处理多个 Channel,但是一个 Channel 只能绑定到一个 EventLoop,这是基于线程安全和同步考虑而设计的。这解释了为什么只有一个 lettuce-nioEventLoop。下面继续分析为什么会只有一个连接呢?继续源码分析:// 1. 创建 RedisConnectionFactory bean@Bean@ConditionalOnMissingBean(RedisConnectionFactory.class)public LettuceConnectionFactory redisConnectionFactory( ClientResources clientResources) throws UnknownHostException { LettuceClientConfiguration clientConfig = getLettuceClientConfiguration( clientResources, this.properties.getLettuce().getPool()); return createLettuceConnectionFactory(clientConfig);}// 2. 查看 createLettuceConnectionFactory(clientConfig) 方法private LettuceConnectionFactory createLettuceConnectionFactory( LettuceClientConfiguration clientConfiguration) { if (getSentinelConfig() != null) { return new LettuceConnectionFactory(getSentinelConfig(), clientConfiguration); } if (getClusterConfiguration() != null) { return new LettuceConnectionFactory(getClusterConfiguration(), clientConfiguration); } // 没有哨兵模式,没有集群,执行这块代码 return new LettuceConnectionFactory(getStandaloneConfig(), clientConfiguration);}// 3. 获取 redis 连接private boolean shareNativeConnection = true;public LettuceReactiveRedisConnection getReactiveConnection() { // 默认为 true return getShareNativeConnection() ? new LettuceReactiveRedisConnection(getSharedReactiveConnection(), reactiveConnectionProvider) : new LettuceReactiveRedisConnection(reactiveConnectionProvider);}LettuceReactiveRedisConnection(StatefulConnection<ByteBuffer, ByteBuffer> sharedConnection, LettuceConnectionProvider connectionProvider) { Assert.notNull(sharedConnection, “Shared StatefulConnection must not be null!”); Assert.notNull(connectionProvider, “LettuceConnectionProvider must not be null!”); this.dedicatedConnection = new AsyncConnect(connectionProvider, StatefulConnection.class); this.pubSubConnection = new AsyncConnect(connectionProvider, StatefulRedisPubSubConnection.class); // 包装 sharedConnection this.sharedConnection = Mono.just(sharedConnection);}protected Mono<? extends StatefulConnection<ByteBuffer, ByteBuffer>> getConnection() { // 直接返回 sharedConnection if (sharedConnection != null) { return sharedConnection; } return getDedicatedConnection();}// 4. shareNativeConnection 是怎么来的protected StatefulConnection<ByteBuffer, ByteBuffer> getSharedReactiveConnection() { return shareNativeConnection ? getOrCreateSharedReactiveConnection().getConnection() : null;}private SharedConnection<ByteBuffer> getOrCreateSharedReactiveConnection() { synchronized (this.connectionMonitor) { if (this.reactiveConnection == null) { this.reactiveConnection = new SharedConnection<>(reactiveConnectionProvider, true); } return this.reactiveConnection; }}StatefulConnection<E, E> getConnection() { synchronized (this.connectionMonitor) { // 第一次通过 getNativeConnection() 获取连接;之后直接返回该连接 if (this.connection == null) { this.connection = getNativeConnection(); } if (getValidateConnection()) { validateConnection(); } return this.connection; }}分析以上源码,关键就在于 shareNativeConnection 默认为 true,导致只有一个连接。更改 shareNativeConnection 的值为 true,并开启 lettuce 连接池,最大连接数设置为 6;再次测试,[root@eureka2 jstack]# ss | grep 6379tcp ESTAB 0 0 ::ffff:10.201.0.27:48937 ::ffff:10.201.0.30:6379tcp ESTAB 0 0 ::ffff:10.201.0.27:35842 ::ffff:10.201.0.30:6379tcp ESTAB 0 0 ::ffff:10.201.0.27:48932 ::ffff:10.201.0.30:6379tcp ESTAB 0 0 ::ffff:10.201.0.27:48930 ::ffff:10.201.0.30:6379tcp ESTAB 0 0 ::ffff:10.201.0.27:48936 ::ffff:10.201.0.30:6379tcp ESTAB 0 0 ::ffff:10.201.0.27:48934 ::ffff:10.201.0.30:6379[root@eureka2 jstack]# jstack 23080 | grep lettuce-epollEventLoop"lettuce-epollEventLoop-4-6" #69 daemon prio=5 os_prio=0 tid=0x00007fcfa4012000 nid=0x5af2 runnable [0x00007fcfa81ef000]“lettuce-epollEventLoop-4-5” #67 daemon prio=5 os_prio=0 tid=0x00007fcf94003800 nid=0x5af0 runnable [0x00007fcfa83f1000]“lettuce-epollEventLoop-4-4” #60 daemon prio=5 os_prio=0 tid=0x00007fcfa0003000 nid=0x5ae9 runnable [0x00007fcfa8af8000]“lettuce-epollEventLoop-4-3” #59 daemon prio=5 os_prio=0 tid=0x00007fcfb00b8000 nid=0x5ae8 runnable [0x00007fcfa8bf9000]“lettuce-epollEventLoop-4-2” #58 daemon prio=5 os_prio=0 tid=0x00007fcf6c00f000 nid=0x5ae7 runnable [0x00007fcfa8cfa000]“lettuce-epollEventLoop-4-1” #43 daemon prio=5 os_prio=0 tid=0x00007fcfac248800 nid=0x5a64 runnable [0x00007fd00c2b9000]可以看到已经建立了 6 个 redis 连接,并且创建了 6 个 eventLoop 线程。2.2 线程阻塞再次进行压力测试,结果如下:[root@hystrix-dashboard wrk]# wrk -t 10 -c 500 -d 30s –latency -T 3s -s post-test.lua ‘http://10.201.0.27:8888/api/v1/json’Running 30s test @ http://10.201.0.27:8888/api/v1/json 10 threads and 500 connections Thread Stats Avg Stdev Max +/- Stdev Latency 215.83ms 104.38ms 1.00s 75.76% Req/Sec 234.56 49.87 434.00 71.45% Latency Distribution 50% 210.63ms 75% 281.30ms 90% 336.78ms 99% 519.51ms 69527 requests in 30.04s, 22.43MB readRequests/sec: 2314.14Transfer/sec: 764.53KB[root@eureka2 jstack]# top -Hp 23080top - 10:08:10 up 162 days, 12:31, 2 users, load average: 2.92, 1.19, 0.53Threads: 563 total, 9 running, 554 sleeping, 0 stopped, 0 zombie%Cpu(s): 50.5 us, 10.2 sy, 0.0 ni, 36.2 id, 0.1 wa, 0.0 hi, 2.9 si, 0.0 stKiB Mem : 7677696 total, 215924 free, 3308248 used, 4153524 buff/cacheKiB Swap: 6291452 total, 6291452 free, 0 used. 3468352 avail Mem PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND23280 root 20 0 7418804 1.3g 7404 R 42.7 17.8 0:54.75 java23272 root 20 0 7418804 1.3g 7404 S 31.1 17.8 0:44.63 java23273 root 20 0 7418804 1.3g 7404 S 31.1 17.8 0:44.45 java23271 root 20 0 7418804 1.3g 7404 R 30.8 17.8 0:44.63 java23282 root 20 0 7418804 1.3g 7404 S 30.5 17.8 0:44.96 java23119 root 20 0 7418804 1.3g 7404 R 24.8 17.8 1:27.30 java23133 root 20 0 7418804 1.3g 7404 R 23.8 17.8 1:29.55 java23123 root 20 0 7418804 1.3g 7404 S 23.5 17.8 1:28.98 java23138 root 20 0 7418804 1.3g 7404 S 23.5 17.8 1:44.19 java23124 root 20 0 7418804 1.3g 7404 R 22.8 17.8 1:32.21 java23139 root 20 0 7418804 1.3g 7404 R 22.5 17.8 1:29.49 java最终结果没有任何提升,cpu 利用率依然不超过 400%,tps 也还是在 2300 request/s;单个 cpu 利用率最高不超过 50%,说明这次的瓶颈不是 cpu。通过 jstack 查看线程状态,“lettuce-epollEventLoop-4-3” #59 daemon prio=5 os_prio=0 tid=0x00007fcfb00b8000 nid=0x5ae8 waiting for monitor entry [0x00007fcfa8bf8000] java.lang.Thread.State: BLOCKED (on object monitor) at org.springframework.data.redis.core.script.DefaultRedisScript.getSha1(DefaultRedisScript.java:88) - waiting to lock <0x000000008c1da690> (a java.lang.Object) at org.springframework.data.redis.core.script.DefaultReactiveScriptExecutor.eval(DefaultReactiveScriptExecutor.java:113) at org.springframework.data.redis.core.script.DefaultReactiveScriptExecutor.lambda$execute$0(DefaultReactiveScriptExecutor.java:105) at org.springframework.data.redis.core.script.DefaultReactiveScriptExecutor$$Lambda$1317/1912229933.doInRedis(Unknown Source) at org.springframework.data.redis.core.script.DefaultReactiveScriptExecutor.lambda$execute$6(DefaultReactiveScriptExecutor.java:167) at org.springframework.data.redis.core.script.DefaultReactiveScriptExecutor$$Lambda$1318/1719274268.get(Unknown Source) at reactor.core.publisher.FluxDefer.subscribe(FluxDefer.java:46) at reactor.core.publisher.FluxDoFinally.subscribe(FluxDoFinally.java:73) at reactor.core.publisher.FluxOnErrorResume.subscribe(FluxOnErrorResume.java:47) at reactor.core.publisher.MonoReduceSeed.subscribe(MonoReduceSeed.java:65) at reactor.core.publisher.MonoMapFuseable.subscribe(MonoMapFuseable.java:59) at reactor.core.publisher.MonoFlatMap.subscribe(MonoFlatMap.java:60) at reactor.core.publisher.Mono.subscribe(Mono.java:3608) at reactor.core.publisher.FluxFlatMap.trySubscribeScalarMap(FluxFlatMap.java:169) at reactor.core.publisher.MonoFlatMap.subscribe(MonoFlatMap.java:53) at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:52) at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:150) at reactor.core.publisher.FluxSwitchIfEmpty$SwitchIfEmptySubscriber.onNext(FluxSwitchIfEmpty.java:67) at reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1476) at reactor.core.publisher.MonoFlatMap$FlatMapInner.onNext(MonoFlatMap.java:241) at reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1476) at reactor.core.publisher.MonoProcessor.subscribe(MonoProcessor.java:457) at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:150) at reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1476) at reactor.core.publisher.MonoHasElement$HasElementSubscriber.onNext(MonoHasElement.java:74) at reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1476) at reactor.core.publisher.MonoProcessor.onNext(MonoProcessor.java:389) at reactor.core.publisher.MonoNext$NextSubscriber.onNext(MonoNext.java:76) at reactor.core.publisher.FluxDoFinally$DoFinallySubscriber.onNext(FluxDoFinally.java:123) at reactor.core.publisher.FluxMap$MapSubscriber.onNext(FluxMap.java:114) at reactor.core.publisher.FluxMap$MapSubscriber.onNext(FluxMap.java:114) at reactor.core.publisher.FluxFilter$FilterSubscriber.onNext(FluxFilter.java:107) at reactor.core.publisher.MonoNext$NextSubscriber.onNext(MonoNext.java:76) at reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onNext(FluxOnErrorResume.java:73) at reactor.core.publisher.MonoFlatMapMany$FlatMapManyInner.onNext(MonoFlatMapMany.java:238) at reactor.core.publisher.FluxDefaultIfEmpty$DefaultIfEmptySubscriber.onNext(FluxDefaultIfEmpty.java:92) at reactor.core.publisher.FluxMap$MapSubscriber.onNext(FluxMap.java:114) at reactor.core.publisher.MonoNext$NextSubscriber.onNext(MonoNext.java:76) at io.lettuce.core.RedisPublisher$RedisSubscription.onNext(RedisPublisher.java:270) at io.lettuce.core.RedisPublisher$SubscriptionCommand.complete(RedisPublisher.java:754) at io.lettuce.core.protocol.CommandWrapper.complete(CommandWrapper.java:59) at io.lettuce.core.protocol.CommandHandler.complete(CommandHandler.java:646) at io.lettuce.core.protocol.CommandHandler.decode(CommandHandler.java:604) at io.lettuce.core.protocol.CommandHandler.channelRead(CommandHandler.java:556) 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.ChannelInboundHandlerAdapter.channelRead(ChannelInboundHandlerAdapter.java:86) 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.ChannelInboundHandlerAdapter.channelRead(ChannelInboundHandlerAdapter.java:86) 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.epoll.AbstractEpollStreamChannel$EpollStreamUnsafe.epollInReady(AbstractEpollStreamChannel.java:799) at io.netty.channel.epoll.EpollEventLoop.processReady(EpollEventLoop.java:433) at io.netty.channel.epoll.EpollEventLoop.run(EpollEventLoop.java:330) at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:897) at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) at java.lang.Thread.run(Thread.java:748)有 4 个 lettuce-epollEventLoop 线程都处于 BLOCKED 状态,继续查看源码:public class DefaultRedisScript<T> implements RedisScript<T>, InitializingBean { private @Nullable ScriptSource scriptSource; private @Nullable String sha1; private @Nullable Class<T> resultType; public String getSha1() { // 1. 线程需要先获取 shaModifiedMonitor 锁 synchronized (shaModifiedMonitor) { // 第一次调用时或者 lua 脚本文件被修改时,需要重新计算 sha1 的值 // 否则直接返回sha1 if (sha1 == null || scriptSource.isModified()) { this.sha1 = DigestUtils.sha1DigestAsHex(getScriptAsString()); } return sha1; } } public String getScriptAsString() { try { return scriptSource.getScriptAsString(); } catch (IOException e) { throw new ScriptingException(“Error reading script text”, e); } }}public class ResourceScriptSource implements ScriptSource { // 只有第一次调用或者 lua 脚本文件被修改时,才会执行这个方法 @Override public String getScriptAsString() throws IOException { synchronized (this.lastModifiedMonitor) { this.lastModified = retrieveLastModifiedTime(); } Reader reader = this.resource.getReader(); return FileCopyUtils.copyToString(reader); } @Override public boolean isModified() { // 2. 每次都需要判断 lua 脚本是否被修改 // 线程需要再获取 lastModifiedMonitor 锁 synchronized (this.lastModifiedMonitor) { return (this.lastModified < 0 || retrieveLastModifiedTime() > this.lastModified); } }}对于限流操作,重要性并没有那么高,而且计算接口调用次数的 lua 脚本,一般也不会经常改动,所以没必要获取 sha1 的值的时候都查看下脚本是否有改动;如果偶尔改动的话,可以通过新增一个刷新接口,在改动脚本文件后通过手动刷新接口来改变 sha1 的值。所以这里,可以把同步操作去掉;我改成了这样:public class CustomRedisScript<T> extends DefaultRedisScript<T> { private @Nullable String sha1; CustomRedisScript(ScriptSource scriptSource, Class<T> resultType) { setScriptSource(scriptSource); setResultType(resultType); this.sha1 = DigestUtils.sha1DigestAsHex(getScriptAsString()); } @Override public String getSha1() { return sha1; }}2.3 cpu 出现大量软中断继续测试,结果如下:[root@hystrix-dashboard wrk]# wrk -t 10 -c 500 -d 30s -T 3s -s post-test.lua –latency ‘http://10.201.0.27:8888/api/v1/json’Running 30s test @ http://10.201.0.27:8888/api/v1/json 10 threads and 500 connections Thread Stats Avg Stdev Max +/- Stdev Latency 155.60ms 110.40ms 1.07s 67.68% Req/Sec 342.90 64.88 570.00 70.35% Latency Distribution 50% 139.14ms 75% 211.03ms 90% 299.74ms 99% 507.03ms 102462 requests in 30.02s, 33.15MB readRequests/sec: 3413.13Transfer/sec: 1.10MBcpu 利用率 500% 左右,tps 达到了 3400 req/s,性能大幅度提升。查看 cpu 状态:[root@eureka2 imf2]# top -Hp 19021top - 16:24:09 up 163 days, 18:47, 2 users, load average: 3.03, 1.08, 0.47Threads: 857 total, 7 running, 850 sleeping, 0 stopped, 0 zombie%Cpu0 : 60.2 us, 10.0 sy, 0.0 ni, 4.3 id, 0.0 wa, 0.0 hi, 25.4 si, 0.0 st%Cpu1 : 64.6 us, 16.3 sy, 0.0 ni, 19.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st%Cpu2 : 65.7 us, 15.8 sy, 0.0 ni, 18.5 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st%Cpu3 : 54.5 us, 15.8 sy, 0.0 ni, 29.5 id, 0.3 wa, 0.0 hi, 0.0 si, 0.0 st%Cpu4 : 55.0 us, 17.8 sy, 0.0 ni, 27.2 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st%Cpu5 : 53.2 us, 16.4 sy, 0.0 ni, 30.0 id, 0.3 wa, 0.0 hi, 0.0 si, 0.0 stKiB Mem : 7677696 total, 174164 free, 3061892 used, 4441640 buff/cacheKiB Swap: 6291452 total, 6291452 free, 0 used. 3687692 avail Mem PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND19075 root 20 0 7722156 1.2g 14488 S 41.4 15.9 0:55.71 java19363 root 20 0 7722156 1.2g 14488 R 40.1 15.9 0:41.33 java19071 root 20 0 7722156 1.2g 14488 R 37.1 15.9 0:56.38 java19060 root 20 0 7722156 1.2g 14488 S 35.4 15.9 0:52.74 java19073 root 20 0 7722156 1.2g 14488 R 35.1 15.9 0:55.83 javacpu0 利用率达到了 95.7%,几乎跑满。但是其中出现了 si(软中断): 25.4%。查看软中断类型:[root@eureka2 imf2]# watch -d -n 1 ‘cat /proc/softirqs’ CPU0 CPU1 CPU2 CPU3 CPU4 CPU5 HI: 0 0 0 0 0 0 TIMER: 1629142082 990710808 852299786 606344269 586896512 566624764 NET_TX: 291570 833710 9616 5295 5358 2012064 NET_RX: 2563401537 32502894 31370533 6886869 6530120 6490002 BLOCK: 18130 1681 41404591 8751054 8695636 8763338BLOCK_IOPOLL: 0 0 0 0 0 0 TASKLET: 39225643 0 0 817 17304 2516988 SCHED: 782335782 442142733 378856479 248794679 238417109 259695794 HRTIMER: 0 0 0 0 0 0 RCU: 690827224 504025610 464412234 246695846 254062933 248859132其中 NET_RX,CPU0 的中断次数远远大于其他 CPU,初步判断是网卡问题。我这边网卡是 ens32,查看网卡的中断号:[root@eureka2 imf2]# cat /proc/interrupts | grep ens 18: 2524017495 0 0 0 0 7 IO-APIC-fasteoi ens32[root@eureka2 imf2]# cat /proc/irq/18/smp_affinity01[root@eureka2 imf2]# cat /proc/irq/18/smp_affinity_list0网卡的中断配置到了 CPU0。(01:表示 cpu0,02:cpu1,04:cpu2,08:cpu3,10:cpu4,20:cpu5)smp_affinity:16 进制;smp_affinity_list:配置到了哪些 cpu。查看网卡队列模式:[root@eureka2 ~]# lspci -vvv02:00.0 Ethernet controller: Intel Corporation 82545EM Gigabit Ethernet Controller (Copper) (rev 01) Subsystem: VMware PRO/1000 MT Single Port Adapter Physical Slot: 32 Control: I/O+ Mem+ BusMaster+ SpecCycle- MemWINV+ VGASnoop- ParErr- Stepping- SERR+ FastB2B- DisINTx- Status: Cap+ 66MHz+ UDF- FastB2B- ParErr- DEVSEL=medium >TAbort- <TAbort- <MAbort- >SERR- <PERR- INTx- Latency: 0 (63750ns min), Cache Line Size: 64 bytes Interrupt: pin A routed to IRQ 18 Region 0: Memory at fd5c0000 (64-bit, non-prefetchable) [size=128K] Region 2: Memory at fdff0000 (64-bit, non-prefetchable) [size=64K] Region 4: I/O ports at 2000 [size=64] [virtual] Expansion ROM at fd500000 [disabled] [size=64K] Capabilities: [dc] Power Management version 2 Flags: PMEClk- DSI+ D1- D2- AuxCurrent=0mA PME(D0+,D1-,D2-,D3hot+,D3cold+) Status: D0 NoSoftRst- PME-Enable- DSel=0 DScale=1 PME- Capabilities: [e4] PCI-X non-bridge device Command: DPERE- ERO+ RBC=512 OST=1 Status: Dev=ff:1f.0 64bit+ 133MHz+ SCD- USC- DC=simple DMMRBC=2048 DMOST=1 DMCRS=16 RSCEM- 266MHz- 533MHz- Kernel driver in use: e1000 Kernel modules: e1000由于是单队列模式,所以通过修改 /proc/irq/18/smp_affinity 的值不能生效。可以通过 RPS/RFS 在软件层面模拟多队列网卡功能。[root@eureka2 ~]# echo 3e > /sys/class/net/ens32/queues/rx-0/rps_cpus[root@eureka2 rx-0]# sysctl net.core.rps_sock_flow_entries=32768[root@eureka2 rx-0]# echo 32768 > /sys/class/net/ens32/queues/rx-0/rps_flow_cnt/sys/class/net/ens32/queues/rx-0/rps_cpus: 1e,设置模拟网卡中断分配到 cpu1-5 上。继续测试,[root@hystrix-dashboard wrk]# wrk -t 10 -c 500 -d 30s -T 3s -s post-test.lua –latency ‘http://10.201.0.27:8888/api/v1/json’Running 30s test @ http://10.201.0.27:8888/api/v1/json 10 threads and 500 connections Thread Stats Avg Stdev Max +/- Stdev Latency 146.75ms 108.45ms 1.01s 65.53% Req/Sec 367.80 64.55 575.00 67.93% Latency Distribution 50% 130.93ms 75% 200.72ms 90% 290.32ms 99% 493.84ms 109922 requests in 30.02s, 35.56MB readRequests/sec: 3661.21Transfer/sec: 1.18MB[root@eureka2 rx-0]# top -Hp 19021top - 09:39:49 up 164 days, 12:03, 1 user, load average: 2.76, 2.02, 1.22Threads: 559 total, 9 running, 550 sleeping, 0 stopped, 0 zombie%Cpu0 : 55.1 us, 13.0 sy, 0.0 ni, 17.5 id, 0.0 wa, 0.0 hi, 14.4 si, 0.0 st%Cpu1 : 60.1 us, 14.0 sy, 0.0 ni, 22.5 id, 0.0 wa, 0.0 hi, 3.4 si, 0.0 st%Cpu2 : 59.5 us, 14.3 sy, 0.0 ni, 22.4 id, 0.0 wa, 0.0 hi, 3.7 si, 0.0 st%Cpu3 : 58.6 us, 15.2 sy, 0.0 ni, 22.2 id, 0.0 wa, 0.0 hi, 4.0 si, 0.0 st%Cpu4 : 59.1 us, 14.8 sy, 0.0 ni, 22.7 id, 0.0 wa, 0.0 hi, 3.4 si, 0.0 st%Cpu5 : 57.7 us, 16.2 sy, 0.0 ni, 23.0 id, 0.0 wa, 0.0 hi, 3.1 si, 0.0 stKiB Mem : 7677696 total, 373940 free, 3217180 used, 4086576 buff/cacheKiB Swap: 6291452 total, 6291452 free, 0 used. 3533812 avail Mem PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND19060 root 20 0 7415812 1.2g 13384 S 40.7 16.7 3:23.05 java19073 root 20 0 7415812 1.2g 13384 R 40.1 16.7 3:20.56 java19365 root 20 0 7415812 1.2g 13384 R 40.1 16.7 2:36.65 java可以看到软中断也分配到了 cpu1-5 上;至于为什么还是 cpu0 上软中断比例最高,猜测是因为还有一些其他中断并且默认配置在 cpu0 上?同时,tps 也从 3400 -> 3600,提升不大。2.4 增加 redis 连接经过以上修改,cup 利用率还是不超过 500%,说明在某些地方还是存在瓶颈。尝试修改了下 lettuce 连接池,spring: redis: database: x host: x.x.x.x port: 6379 lettuce: pool: max-active: 18 min-idle: 1 max-idle: 18主要是把 max-active 参数 6 增大到了 18,继续测试:[root@hystrix-dashboard wrk]# wrk -t 10 -c 500 -d 120s -T 3s -s post-test.lua –latency ‘http://10.201.0.27:8888/api/v1/json’Running 2m test @ http://10.201.0.27:8888/api/v1/json 10 threads and 500 connections Thread Stats Avg Stdev Max +/- Stdev Latency 117.66ms 96.72ms 1.34s 86.48% Req/Sec 485.42 90.41 790.00 70.80% Latency Distribution 50% 90.04ms 75% 156.01ms 90% 243.63ms 99% 464.04ms 578298 requests in 2.00m, 187.01MB readRequests/sec: 4815.57Transfer/sec: 1.56MB6 核 cpu 几乎跑满,同时 tps 也从 3600 -> 4800,提升明显!这说明之前的瓶颈出在 redis 连接上,那么如何判断 tcp 连接是瓶颈呢?(尝试通过 ss、netstat 等命令查看 tcp 发送缓冲区、接收缓冲区、半连接队列、全连接队列等,未发现问题。先放着,以后在研究)

January 17, 2019 · 12 min · jiezi

java性能调优记录(线程阻塞)

问题spring-cloud-gateway 作为统一的请求入口,负责转发请求到相应的微服务中去。采用的 Spring Cloud 的版本为 Finchley SR2。测试一个接口的性能,发现 tps 只有 1000 req/s 左右就上不去了。[root@hystrix-dashboard wrk]# wrk -t 10 -c 200 -d 30s –latency -s post-test.lua ‘http://10.201.0.28:8888/api/v1/json’Running 30s test @ http://10.201.0.28:8888/api/v1/json 10 threads and 200 connections Thread Stats Avg Stdev Max +/- Stdev Latency 188.34ms 110.13ms 2.00s 78.43% Req/Sec 106.95 37.19 333.00 77.38% Latency Distribution 50% 165.43ms 75% 243.48ms 90% 319.47ms 99% 472.64ms 30717 requests in 30.04s, 7.00MB read Socket errors: connect 0, read 0, write 0, timeout 75Requests/sec: 1022.62Transfer/sec: 238.68KB其中 post-test.lua 内容如下:request = function() local headers = {} headers[“Content-Type”] = “application/json” local body = [[{ “biz_code”: “1109000001”, “channel”: “7”, “param”: { “custom_id”: “ABCD”, “type”: “test”, “animals”: [“cat”, “dog”, “lion”], “retcode”: “0” } }]] return wrk.format(‘POST’, nil, headers, body)end网关的逻辑是读取请求中 body 的值,根据 biz_code 字段去内存的路由表中匹配路由,然后转发请求到对应的微服务中去。2. 排查测试接口本身的性能:[root@hystrix-dashboard wrk]# wrk -t 10 -c 200 -d 30s –latency -s post-test.lua ‘http://10.201.0.32:8776/eeams-service/api/v1/json’Running 30s test @ http://10.201.0.32:8776/eeams-service/api/v1/json 10 threads and 200 connections Thread Stats Avg Stdev Max +/- Stdev Latency 26.72ms 8.59ms 260.23ms 89.66% Req/Sec 752.18 101.46 0.94k 78.67% Latency Distribution 50% 23.52ms 75% 28.02ms 90% 35.58ms 99% 58.25ms 224693 requests in 30.02s, 50.83MB readRequests/sec: 7483.88Transfer/sec: 1.69MB发现接口的 tps 可以达到 7000+。通过 spring-boot-admin 查看网关的 cpu、内存等占用情况,发现都没有用满;查看线程状况,发现 reactor-http-nio 线程组存在阻塞情况。对于响应式编程来说,reactor-http-nio 线程出现阻塞结果是灾难性的。通过 jstack 命令分析线程状态,定位阻塞的代码(第 19 行):“reactor-http-nio-4” #19 daemon prio=5 os_prio=0 tid=0x00007fb784d7f240 nid=0x80b waiting for monitor entry [0x00007fb71befc000] java.lang.Thread.State: BLOCKED (on object monitor) at java.lang.ClassLoader.loadClass(ClassLoader.java:404) - waiting to lock <0x000000008b0cec30> (a java.lang.Object) at org.springframework.boot.loader.LaunchedURLClassLoader.loadClass(LaunchedURLClassLoader.java:93) at java.lang.ClassLoader.loadClass(ClassLoader.java:357) at org.springframework.util.ClassUtils.forName(ClassUtils.java:282) at org.springframework.http.converter.json.Jackson2ObjectMapperBuilder.registerWellKnownModulesIfAvailable(Jackson2ObjectMapperBuilder.java:753) at org.springframework.http.converter.json.Jackson2ObjectMapperBuilder.configure(Jackson2ObjectMapperBuilder.java:624) at org.springframework.http.converter.json.Jackson2ObjectMapperBuilder.build(Jackson2ObjectMapperBuilder.java:608) at org.springframework.http.codec.json.Jackson2JsonEncoder.<init>(Jackson2JsonEncoder.java:54) at org.springframework.http.codec.support.AbstractCodecConfigurer$AbstractDefaultCodecs.getJackson2JsonEncoder(AbstractCodecConfigurer.java:177) at org.springframework.http.codec.support.DefaultServerCodecConfigurer$ServerDefaultCodecsImpl.getSseEncoder(DefaultServerCodecConfigurer.java:99) at org.springframework.http.codec.support.DefaultServerCodecConfigurer$ServerDefaultCodecsImpl.getObjectWriters(DefaultServerCodecConfigurer.java:90) at org.springframework.http.codec.support.AbstractCodecConfigurer.getWriters(AbstractCodecConfigurer.java:121) at org.springframework.http.codec.support.DefaultServerCodecConfigurer.getWriters(DefaultServerCodecConfigurer.java:39) at org.springframework.web.reactive.function.server.DefaultHandlerStrategiesBuilder.build(DefaultHandlerStrategiesBuilder.java:103) at org.springframework.web.reactive.function.server.HandlerStrategies.withDefaults(HandlerStrategies.java:90) at org.springframework.cloud.gateway.support.DefaultServerRequest.<init>(DefaultServerRequest.java:81) at com.glsc.imf.dbg.route.RouteForJsonFilter.filter(RouteForJsonFilter.java:34) at org.springframework.cloud.gateway.handler.FilteringWebHandler$DefaultGatewayFilterChain.lambda$filter$0(FilteringWebHandler.java:115) at org.springframework.cloud.gateway.handler.FilteringWebHandler$DefaultGatewayFilterChain$$Lambda$800/1871561393.get(Unknown Source) at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:44)最终定位到问题代码为:DefaultServerRequest req = new DefaultServerRequest(exchange); // 这行代码存在性能问题return req.bodyToMono(JSONObject.class).flatMap(body -> { …});这里的逻辑是我需要读取请求中 body 的值,并转化为 json,之后根据其中的特定字段去匹配路由,然后进行转发。这里选择了先把 exchange 转化为 DefaultServerRequest,目的是为了使用该类的 bodyToMono 方法,可以方便的进行转换。3. 解决改写代码以实现同样的功能:return exchange.getRequest().getBody().collectList() .map(dataBuffers -> { ByteBuf byteBuf = Unpooled.buffer(); dataBuffers.forEach(buffer -> { try { byteBuf.writeBytes(IOUtils.toByteArray(buffer.asInputStream())); } catch (IOException e) { e.printStackTrace(); } }); return JSON.parseObject(new String(byteBuf.array())); }) .flatMap(body -> { … });之后进行测试,[root@hystrix-dashboard wrk]# wrk -t 10 -c 200 -d 30s –latency -s post-test.lua ‘http://10.201.0.28:8888/api/v1/json’Running 30s test @ http://10.201.0.28:8888/api/v1/json 10 threads and 200 connections Thread Stats Avg Stdev Max +/- Stdev Latency 48.47ms 45.85ms 325.87ms 88.55% Req/Sec 548.13 202.55 760.00 80.01% Latency Distribution 50% 31.18ms 75% 39.44ms 90% 112.18ms 99% 227.19ms 157593 requests in 30.02s, 35.94MB readRequests/sec: 5249.27Transfer/sec: 1.20MB发现 tps 从 1000 提升到了 5000+,问题解决。

January 17, 2019 · 2 min · jiezi

Spring Cloud Alibaba基础教程:使用Nacos实现服务注册与发现

自Spring Cloud Alibaba发布第一个Release以来,就备受国内开发者的高度关注。虽然Spring Cloud Alibaba还没能纳入Spring Cloud的主版本管理中,但是凭借阿里中间件团队的背景,还是得到不少团队的支持;同时,由于Spring Cloud Alibaba中的几项主要功能都直指Netflix OSS中的重要组件,而后者最近频繁宣布各组件不在更新新特性,这使得Spring Cloud Alibaba关注度不断飙升,不少开发者或团队也开始小范围试水。笔者对此也进行了一段时间的调研与试水,接下来计划以《Spring Cloud Alibaba基础教程》为主题,为大家完成一套快速入门的免费内容,以支持开源社区的发展! ^_^更多关于Spring Cloud Alibaba的介绍可见:《Spring Cloud 加盟重量级成员Spring Cloud Alibaba,打造更符合中国国情的微服务体系》什么是NacosNacos致力于帮助您发现、配置和管理微服务。Nacos提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据及流量管理。Nacos帮助您更敏捷和容易地构建、交付和管理微服务平台。Nacos是构建以“服务”为中心的现代应用架构 (例如微服务范式、云原生范式) 的服务基础设施。在接下里的教程中,将使用Nacos作为微服务架构中的注册中心(替代:eurekba、consul等传统方案)以及配置中心(spring cloud config)来使用。安装Nacos下载地址:https://github.com/alibaba/na…本文版本:0.7.0下载完成之后,解压。根据不同平台,执行不同命令,启动单机版Nacos服务:Linux/Unix/Mac:sh startup.sh -m standaloneWindows:cmd startup.cmd -m standalonestartup.sh脚本位于Nacos解压后的bin目录下。这里主要介绍Spring Cloud与Nacos的集成使用,对于Nacos的高级配置,后续再补充。所以,持续关注我的博客或者公众号吧!启动完成之后,访问:http://127.0.0.1:8848/nacos/,可以进入Nacos的服务管理页面,具体如下;构建应用接入Nacos注册中心在完成了Nacos服务的安装和启动之后,下面我们就可以编写两个应用(服务提供者与服务消费者)来验证服务的注册与发现了。服务提供者第一步:创建一个Spring Boot应用,可以命名为:alibaba-nacos-discovery-server。如果您还不会或者不了解Spring Boot应用,建议先学习《Spring Boot基础教程》。第二步:编辑pom.xml,加入必要的依赖配置,比如:<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.5.RELEASE</version> <relativePath/> <!– lookup parent from repository –></parent><dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Finchley.SR1</version> <type>pom</type> <scope>import</scope> </dependency> <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> </dependencies></dependencyManagement><dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.2</version> <optional>true</optional> </dependency></dependencies>上述内容主要三部分:parent:定义spring boot的版本dependencyManagement:spring cloud的版本以及spring cloud alibaba的版本,由于spring cloud alibaba还未纳入spring cloud的主版本管理中,所以需要自己加入dependencies:当前应用要使用的依赖内容。这里主要新加入了Nacos的服务注册与发现模块:spring-cloud-starter-alibaba-nacos-discovery。由于在dependencyManagement中已经引入了版本,所以这里就不用指定具体版本了。第三步:创建应用主类,并实现一个HTTP接口:@EnableDiscoveryClient@SpringBootApplicationpublic class TestApplication { public static void main(String[] args) { SpringApplication.run(TestApplication.class, args); } @Slf4j @RestController static class TestController { @GetMapping("/hello") public String hello(@RequestParam String name) { log.info(“invoked name = " + name); return “hello " + name; } }}内容非常简单,@SpringBootApplication定义是个Spring Boot应用;@EnableDiscoveryClient开启Spring Cloud的服务注册与发现,由于这里引入了spring-cloud-starter-alibaba-nacos-discovery模块,所以Spring Cloud Common中定义的那些与服务治理相关的接口将使用Nacos的实现。这点不论我们使用Eureka、Consul还是其他Spring Cloud整合的注册中心都一样,这也是Spring Cloud做了封装的好处所在,如果对Eureka、Consul这些注册中心的整合还不熟悉的可以看看以前的这篇:Spring Cloud构建微服务架构:服务注册与发现(Eureka、Consul)。第四步:配置服务名称和Nacos地址spring.application.name=alibaba-nacos-discovery-serverserver.port=8001spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848第五步:启动上面创建的应用。可以在启动时候增加-Dserver.port=8001的形式在本机的不同端口上启动多个实例。在应用启动好之后,我们可以在控制台或日志中看到如下内容,代表已经注册成功:INFO 10476 — [ main] o.s.c.a.n.registry.NacosServiceRegistry : nacos registry, alibaba-nacos-discovery-server 10.123.18.216:8001 register finished在启动都ok之后,我们可以访问Nacos的管理页面http://127.0.0.1:8848/nacos/来查看服务列表,此时可以看到如下内容:这里会显示当前注册的所有服务,以及每个服务的集群数目、实例数、健康实例数。点击详情,我们还能看到每个服务具体的实例信息,如下图所示:服务消费者接下来,实现一个应用来消费上面已经注册到Nacos的服务。第一步:创建一个Spring Boot应用,命名为:alibaba-nacos-discovery-client-common。第二步:编辑pom.xml中的依赖内容,与上面服务提供者的一样即可。第三步:创建应用主类,并实现一个HTTP接口,在该接口中调用服务提供方的接口。@EnableDiscoveryClient@SpringBootApplicationpublic class TestApplication { public static void main(String[] args) { SpringApplication.run(TestApplication.class, args); } @Slf4j @RestController static class TestController { @Autowired LoadBalancerClient loadBalancerClient; @GetMapping("/test”) public String test() { // 通过spring cloud common中的负载均衡接口选取服务提供节点实现接口调用 ServiceInstance serviceInstance = loadBalancerClient.choose(“alibaba-nacos-discovery-server”); String url = serviceInstance.getUri() + “/hello?name=” + “didi”; RestTemplate restTemplate = new RestTemplate(); String result = restTemplate.getForObject(url, String.class); return “Invoke : " + url + “, return : " + result; } }}这里使用了Spring Cloud Common中的LoadBalancerClient接口来挑选服务实例信息。然后从挑选出的实例信息中获取可访问的URI,拼接上服务提供方的接口规则来发起调用。第四步:配置服务名称和Nacos地址,让服务消费者可以发现上面已经注册到Nacos的服务。spring.application.name=alibaba-nacos-discovery-client-commonserver.port=9000spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848第五步:启动服务消费者,然后通过curl或者postman等工具发起访问,下面以curl为例:$ curl localhost:9000/testInvoke : http://10.123.18.216:8001/hello?name=didi, return : hello didi$ curl localhost:9000/testInvoke : http://10.123.18.216:8002/hello?name=didi, return : hello didi可以看到,两次不同请求的时候,真正实际调用的服务提供者实例是不同的,也就是说,通过LoadBalancerClient接口在获取服务实例的时候,已经实现了对服务提供方实例的负载均衡。但是很明显,这样的实现还是比较繁琐,预告下后面的几篇,关于服务消费的几种不同姿势。参考资料Nacos官方文档Nacos源码分析代码示例本文示例读者可以通过查看下面仓库的中的alibaba-nacos-discovery-server和alibaba-nacos-discovery-client-common项目:GithubGitee如果您对这些感兴趣,欢迎star、follow、收藏、转发给予支持!以下专题教程也许您会有兴趣Spring Boot基础教程Spring Cloud基础教程 ...

January 16, 2019 · 2 min · jiezi

Spring Cloud Stream 使用延迟消息实现定时任务(RabbitMQ)

应用场景我们在使用一些开源调度系统(比如:elastic-job等)的时候,对于任务的执行时间通常都是有规律性的,可能是每隔半小时执行一次,或者每天凌晨一点执行一次。然而实际业务中还存在另外一种定时任务,它可能需要一些触发条件才开始定时,比如:编写博文时候,设置2小时之后发送。对于这些开始时间不确定的定时任务,我们也可以通过Spring Cloud Stream来很好的处理。为了实现开始时间不确定的定时任务触发,我们将引入延迟消息的使用。RabbitMQ中提供了关于延迟消息的插件,所以本文就来具体介绍以下如何利用Spring Cloud Stream以及RabbitMQ轻松的处理上述问题。动手试试插件安装关于RabbitMQ延迟消息的插件介绍可以查看官方网站:https://www.rabbitmq.com/blog…安装方式很简单,只需要在这个页面:http://www.rabbitmq.com/commu… 中找到rabbitmq_delayed_message_exchange插件,根据您使用的RabbitMQ版本选择对应的插件版本下载即可。注意:只有RabbitMQ 3.6.x以上才支持在下载好之后,解压得到.ez结尾的插件包,将其复制到RabbitMQ安装目录下的plugins文件夹。然后通过命令行启用该插件:rabbitmq-plugins enable rabbitmq_delayed_message_exchange该插件在通过上述命令启用后就可以直接使用,不需要重启。另外,如果您没有启用该插件,您可能为遇到类似这样的错误:ERROR 156 — [ 127.0.0.1:5672] o.s.a.r.c.CachingConnectionFactory : Channel shutdown: connection error; protocol method: #method(reply-code=503, reply-text=COMMAND_INVALID - unknown exchange type ‘x-delayed-message’, class-id=40, method-id=10)应用编码下面通过编写一个简单的例子来具体体会一下这个属性的用法:@EnableBinding(TestApplication.TestTopic.class)@SpringBootApplicationpublic class TestApplication { public static void main(String[] args) { SpringApplication.run(TestApplication.class, args); } @Slf4j @RestController static class TestController { @Autowired private TestTopic testTopic; /** * 消息生产接口 * * @param message * @return / @GetMapping("/sendMessage") public String messageWithMQ(@RequestParam String message) { log.info(“Send: " + message); testTopic.output().send(MessageBuilder.withPayload(message).setHeader(“x-delay”, 5000).build()); return “ok”; } } /* * 消息消费逻辑 */ @Slf4j @Component static class TestListener { @StreamListener(TestTopic.INPUT) public void receive(String payload) { log.info(“Received: " + payload); } } interface TestTopic { String OUTPUT = “example-topic-output”; String INPUT = “example-topic-input”; @Output(OUTPUT) MessageChannel output(); @Input(INPUT) SubscribableChannel input(); }}内容很简单,既包含了消息的生产,也包含了消息消费。在/sendMessage接口的定义中,发送了一条消息,一条消息的头信息中包含了x-delay字段,该字段用来指定消息延迟的时间,单位为毫秒。所以上述代码发送的消息会在5秒之后被消费。在消息监听类TestListener中,对TestTopic.INPUT通道定义了@StreamListener,这里会对延迟消息做具体的逻辑。由于消息的消费是延迟的,从而变相实现了从消息发送那一刻起开始的定时任务。在启动应用之前,还要需要做一些必要的配置,下面分消息生产端和消费端做说明:消息生产端spring.cloud.stream.bindings.example-topic-output.destination=delay-topicspring.cloud.stream.rabbit.bindings.example-topic-output.producer.delayed-exchange=true注意这里的一个新参数spring.cloud.stream.rabbit.bindings.example-topic-output.producer.delayed-exchange,用来开启延迟消息的功能,这样在创建exchange的时候,会将其设置为具有延迟特性的exchange,也就是用到上面我们安装的延迟消息插件的功能。消息消费端spring.cloud.stream.bindings.example-topic-input.destination=delay-topicspring.cloud.stream.bindings.example-topic-input.group=testspring.cloud.stream.rabbit.bindings.example-topic-input.consumer.delayed-exchange=true在消费端也一样,需要设置spring.cloud.stream.rabbit.bindings.example-topic-output.producer.delayed-exchange=true。如果该参数不设置,将会出现类似下面的错误:ERROR 9340 — [ 127.0.0.1:5672] o.s.a.r.c.CachingConnectionFactory : Channel shutdown: channel error; protocol method: #method<channel.close>(reply-code=406, reply-text=PRECONDITION_FAILED - inequivalent arg ’type’ for exchange ‘delay-topic’ in vhost ‘/’: received ’topic’ but current is ‘‘x-delayed-message’’, class-id=40, method-id=10)完成了上面配置之后,就可以启动应用,并尝试访问localhost:8080/sendMessage?message=hello接口来发送一个消息到MQ中了。此时可以看到类似下面的日志:2019-01-02 23:28:45.318 INFO 96164 — [ctor-http-nio-3] c.d.s.TestApplication$TestController : Send: hello2019-01-02 23:28:45.328 INFO 96164 — [ctor-http-nio-3] o.s.a.r.c.CachingConnectionFactory : Attempting to connect to: [localhost:5672]2019-01-02 23:28:45.333 INFO 96164 — [ctor-http-nio-3] o.s.a.r.c.CachingConnectionFactory : Created new connection: rabbitConnectionFactory.publisher#5c5f9a03:0/SimpleConnection@3278a728 [delegate=amqp://guest@127.0.0.1:5672/, localPort= 53536]2019-01-02 23:28:50.349 INFO 96164 — [ay-topic.test-1] c.d.stream.TestApplication$TestListener : Received: hello从日志中可以看到,Send: hello和Received: hello两条输出之间间隔了5秒,符合我们上面编码设置的延迟时间。深入思考在代码层面已经完成了定时任务,那么我们如何查看延迟的消息数等信息呢?此时,我们可以打开RabbitMQ的Web控制台,首先可以进入Exchanges页面,看看这个特殊exchange,具体如下:可以看到,这个exchange的Type类型是x-delayed-message。点击该exchange的名称,进入详细页面,就可以看到更多具体信息了:代码示例本文示例读者可以通过查看下面仓库的中的stream-delayed-message项目:GithubGitee如果您对这些感兴趣,欢迎star、follow、收藏、转发给予支持!以下专题教程也许您会有兴趣Spring Boot基础教程Spring Cloud基础教程本文首发于我的独立博客:http://blog.didispace.com/spr… ...

January 4, 2019 · 1 min · jiezi

Spring Cloud Alibaba发布第二个版本,Spring 发来贺电

还是熟悉的面孔,还是熟悉的味道,不同的是,这次的配方升级了。今年10月底,Spring Cloud联合创始人Spencer Gibb在Spring官网的博客页面宣布:阿里巴巴开源 Spring Cloud Alibaba,并发布了首个预览版本。随后,Spring Cloud 官方Twitter也发布了此消息。- 传送门时隔 51天,Spencer Gibb再次在Spring官网的博客页面宣布:Spring Cloud Alibaba发布了其开源后的第二个版本0.2.1,随后,Spring Cloud 官方Twitter也转发了此消息。圣诞节的前一周,Josh Long向他的老朋友许晓斌发来贺电:小编翻译:听闻阿里巴巴官方宣布使用Spring Cloud,我开心的一晚上没睡着,下周三我会在Spring Tips小视频里介绍Spring Cloud Alibaba。圣诞快乐,老哥!爱你视频地址新版本概要增加了两个新的模块, spring-cloud-alibaba-schedulerx 和 spring-cloud-stream-binder-rocketmq 。在 spring-cloud-alibaba-nacos 和 spring-cloud-alibaba-sentinel 中增加了一些新的 feature 。修复了之前版本的一些 bug 。注意: 版本 0.2.1.RELEASE 对应的是 Spring Cloud Finchley 的版本。这次发布也包含了一个适配 Spring Cloud Edgware 的版本 0.1.1.RELEASE,版本 0.1.1.RELEASE 也包含了 0.2.1.RELEASE 中新增的组件和特性。Spring Cloud Alibaba RocketMQ适配了 spring cloud stream 对于 message 抽象的 API。支持事务消息。Consumer 端支持以 tags、SQL表达式过滤消息。支持顺序、并发以及广播消费模式。Spring Cloud Alibaba SchedulerX提供了秒级、精准、高可靠、高可用的定时任务调度服务。提供了丰富的任务执行模型,包括单机执行,广播执行,以及子任务的分布式执行。Spring Cloud Alibaba Nacos Config将Nacos Client 的版本升级到 0.6.2 版本。支持从多个 dataid 和 groupid 中获取和监听配置,并支持优先级指定。优化了 动态监听的逻辑,只有配置了动态刷新的配置项才会实时刷新。Spring Cloud Alibaba Nacos Discovery将Nacos Client 的版本升级到 0.6.2 版本。支持在 Nacos Console 端将服务实例设置成不可用状态,服务发现会自动过滤此节点。支持服务发现在初始化时不使用本地缓存。Spring Cloud Alibaba Sentinel支持 Feign,兼容了 @FeignClient 所有的属性,包括 fallback、fallbackFactory。支持 热点参数限流和集群限流。重构了 ReadableDataSource 的设计,提供了更友好的配置 Sentinel 规则持久化的方式。优化了 Sentinel 对于 RestTemplate 的降级后的处理。调整并添加了一些 Sentinel 配置信息对应的属性,如日志目录,日志文件名等。新版本背后的思考Spring Cloud Alibaba Nacos DiscoveryNacos Discovery 在这个版本最大的更新就是支持在初始化的时候不使用本地文件缓存,目前初始化的时候已经默认不使用本地文件缓存。为什么要有缓存?首先我们来了解一下本地缓存的概念,为什么需要这个本地缓存?我们都知道,服务注册与发现应该只是服务调用中的辅助性的一个环节,而不是一个关键的环节。一个良好的服务注册与发现的设计,需要保证以下两点。服务注册与发现只是旁路,不应该参与具体的调用过程。在服务的运行过程中,即使服务注册中心偶然发生异常宕机后,也尽量不影响正常的业务调用。要实现以上两点,缓存就不可或缺,而为了适应不同的场景,缓存又可以分成内存缓存和本地文件缓存,他们的概念和适用场景如下。内存中的缓存将服务提供者列表维护在内存中,每次调用时直接从内存中的列表获取节点即可。内存缓存通过定时任务更新,或者在收到服务注册中心的推送之后再更新。确保了即使在服务注册中心宕机的情况下,也能保证服务仍能正常调用。本地文件缓存将上述提到的内存中的缓存,保留在本地的某个文件中,这样在调用服务注册中心失败的时候,可以从本机的文件缓存中获取服务提供者列表。这样就保证了在服务注册中心宕机的情况下,应用在重启后也能找到服务提供者。为什么要关闭有了以上背景知识后,读者可能会有疑问,既然缓存这么好,你们为什么默认要把它置为默认关闭呢?我们在发布出第一个版本之后,很多用户反馈,为什么我服务下线之后还是有节点,仍旧可以被查询到呢?这样导致我这个监控数据完全不准,你们这个有 bug,完全不对。其实这是阿里巴巴在多年业务积累的经验,对于服务发现来说,一个即使是已经过时的节点,也比没有任何数据好。而且还有可能其实这个服务只是和服务注册中心失去了心跳,但是应用本身是正常的。当然,这也暴露了我们设计中存在的一些问题,没有把服务发现本身,以及缓存的分层给做好,两者糅合在一块。所以在这一次的版本更新中我们还是选择适配开源标准,默认关闭了本地文件缓存,留了一个开关给用户自由选择。Spring Cloud Alibaba Nacos ConfigNacos Config 在这个版本中有两个大的特性,支持了“共享配置”,修正了动态刷新的语义和行为。共享配置的实现在第一个版本还没发布到时候,社区里对配置管理中心的讨论就没停止过,其中听到最多的反馈应该就是支持多个应用共享一个配置。我们也通过 github issue 的方式,征集了很多意见,详情见 #12 , #141。后来我们将这个模型抽象了一下,认清这些需求本质是一个应用可以从多个 DataID 和 GroupID 组合中获取配置,并且这些配置还可以单独指定优先级和是否动态刷新。最后我们推荐了这种使用方式,既保证了使用场景的灵活性,又保证了业务语义的明确性。更多详情可以参考 WIKI。# config external configuration# 1、Data Id 在默认的组 DEFAULT_GROUP,不支持配置的动态刷新spring.cloud.nacos.config.ext-config[0].data-id=ext-config-common01.properties# 2、Data Id 不在默认的组,不支持动态刷新spring.cloud.nacos.config.ext-config[1].data-id=ext-config-common02.propertiesspring.cloud.nacos.config.ext-config[1].group=GLOBAL_GROUP# 3、Data Id 既不在默认的组,也支持动态刷新spring.cloud.nacos.config.ext-config[2].data-id=ext-config-common03.propertiesspring.cloud.nacos.config.ext-config[2].group=REFRESH_GROUPspring.cloud.nacos.config.ext-config[2].refresh=true#优先级关系: spring.cloud.nacos.config.ext-config[n].data-id 其中 n 的值越大,优先级越高。注意 data-id 的值必须带文件扩展名,文件扩展名支持 properties、yaml 和 yml。通过这种自定义扩展的配置项,既可以支持一个应用从多个配置项中获取数据,也解决多个应用间配置共享的问题。头脑风暴,@fudali 同学还提出了更加灵活的一种方式 #161,就是可以通过一个配置项来配置所有的 DataID 的信息,然后可以通过修改这个配置项,可以修改整体配置项的逻辑。这是一个非常好的想法,只不过这一期中我们没有实现,原因是这种方式太灵活了。我们认为配置管理其实是一件很严肃的事情,太灵活导致生产中变更比较不可控。虽然目前的逻辑也可以支持这种用法,但是我们并没有把这种模式当做推荐模式,后续如果社区有更多的反馈认为这是一个强烈的需求,欢迎提 PR。动态刷新的修正简单好用、实时可监控的动态刷新也许是 Nacos Config 相对于其他开源中间件相比最核心的优势了,不同于 Spring Cloud Config Server 必须使用 Spring Cloud Bus 才能实现动态刷新,Nacos Config 无需依赖其他任何中间件就可以实现实时动态刷新,而且推送成功与否和刷新历史还支持实时查询。但是我们发现在第一个版本中的实现发现两个问题:动态刷新的实现方式不对:不应该直接调用 Context.refresh() 方法,而是应该 publish 一个 RefreshEvent。是否动态刷新的语义有误:对于那些被标记为不动态刷新的配置项来说,只是修改他们的时候不会触发动态刷新。但是当其他支持动态刷新的配置项触发了动态刷新时,应用的 Context 仍旧会去获取那些被标记为不动态刷新的配置项的内容,也就意味着这些配置项有可能被连带刷新了。在这个版本中,我们修复了这两个问题。首先,动态刷新不再是直接去调用 ContextRefresher.refresh() 方法,而是 publish 了一个 RefreshEvent,让 spring-cloud-commons 里的 RefreshEventListener 去触发这个 ContextRefresher.refresh() 方法。其次,我们修正了动态刷新的语义后,这一次是真正做到了,只有 refresh 属性为 true 的配置项,才会在运行的过程中变更为新的值,refresh 属性为 false 的配置项再也不用担心应用在运行的过程中发生莫名其妙的变更了。更深入一点,在上个月 SpringOne Tour 中,我们和 Spring Cloud 的创始人 Spencer 聊到 Spring Cloud 的 Context.refresh() 成本太高,会刷新整个 Spring Context。他反复强调了两次 Context.refresh() 只对 @RefreshScope 和 @ConfigurationProperties 有效,成本一点也不高。之前我们接收到很多社区的反馈都是 Nacos Config 动态刷新支不支持 xxxx,支不支持 xxxx。之前我们都是回答只支持 @RefreshScope 和 @ConfigurationProperties ,如果他内置没有支持,那就得自己加上相应的注解。今天我们可以很愉快地回复,他监听了 RefreshEvent 也能直接支持。而且如果添加 @RefreshScope 和 @ConfigurationProperties 都不满足你的需求时,可以通过实现自己的 RefreshEventListener 更多高级的玩法。Spring Cloud Alibaba SentinelSentinel 在这个版本中有三个大的特性:全面支持 FeignClient ,完善了 RestTemplate 的支持,添加了热点限流、集群限流。FeignClient 集成 Sentinel其实在这之前,Sentinel 支持 FeignClient 已经设计了很久了,之前我们的想法做一个兼容性较强的方案,支持 Sentinel 和 Hystrix 在 FeignClient 中同时使用,尽量做到对原有应用的侵入性做到最小。这个方案我们也思考调研了很久,但是实现难度确实比较大,需要修改 FeignClient 的代码才能实现两者共存。正好前段时间在 Spring Cloud 届最大的新闻就是 Hystrix 宣布不在维护了,于是我们就换了一个思路,直接使用 Sentinel 替代 Hystrix,不再去追求支持两者共存。我们实现了自己的 Feign.Builder,在构建的 FeignClient 执行调用的过程中,通过 SentinelInvocationHandler 完成 Sentinel 的流量统计和保护的动作。如果想使用 Sentinel 为 FeignClient 限流降级,首先需要引入 sentinel-starter 的依赖,然后打开 Sentinel 限流降级的开关 feign.sentinel.enabled=true ,就完成了 Sentinel 的接入。如果需要使用更加定制化的功能,则需要在 @FeignClient 添加 fallback 和 configuration 这些属性的配置。注意 @FeignClient 注解中的所有属性,Sentinel 都做了兼容。RestTemplate 集成 SentinelSpring Cloud Alibaba Sentinel 支持对 RestTemplate 的服务调用使用 Sentinel 进行保护,补全了 Hystrix 这一块的空白。接入的方式也不复杂,在构造 RestTemplate bean 的时候需要加上 @SentinelRestTemplate 注解,然后在控制台配置相应的规则即可。在触发了限流降级时,默认的处理方式是返回 RestTemplate request block by sentinel 信息。如果想自定义被限流之后的处理方式,还可以添加 blockHandlerClass,blockHandler 分别定制被限流后的处理类以及对于的处理方法。如果想自定义被降级之后的处理方式,还可以添加 fallback,fallbackClass 分别定制被降级后的处理类以及对于的处理方法。RestTemplate 的限流降级 ?Sentinel 也承包了!热点参数限流和集群限流首先解释一下什么是热点参数限流和集群限流。热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效。集群流控主要解决的问题是:当我们需要控制整个集群流量总量,但是单机流量可能会不均匀,如果是单机维度去限制的话会无法精确地限制总体流量,因此需要引入集群维度的流量控制。Sentinel v1.4.0 的 新功能 ,也能第一时间愉快地在 Spring Cloud Alibaba 上使用了。新组件Spring Cloud Alibaba RocketMQSpring Cloud Stream 是一个用于构建基于消息的微服务应用框架,它基于 SpringBoot 来创建具有生产级别的单机 Spring 应用,并且使用 Spring Integration 与 Broker 进行连接。它提供了消息中间件的统一抽象,推出了 publish-subscribe、consumer groups、partition 这些统一的概念。RocketMQ 是一款开源的分布式消息系统,基于高可用分布式集群技术,提供低延时的、高可靠的消息发布与订阅服务。具有以下特点:能够保证严格的消息顺序、提供丰富的消息拉取模式、高效的订阅者水平扩展能力、实时的消息订阅机制、亿级消息堆积能力。在这次的 Spring Cloud Stream Binder RocketMQ 的实现中,我们适配了 Spring Cloud Stream 对于 message 抽象的 API,支持了 RocketMQ 的事务消息。消息订阅时支持以 tags、SQL 表达式过滤消息,同时还支持顺序、并发、延迟以及广播消费模式。Spring Cloud Alibaba SchedulerXSchedulerX 是阿里中间件团队开发的一款分布式任务调度产品,提供秒级、精准、高可靠、高可用的定时(基于 Cron 表达式)任务调度服务,同时提供分布式的任务执行模型,如网格任务,网格任务支持海量子任务均匀分配到所有 Worker(schedulerx-client)上执行。简单易用的轻量分布式任务调度您不需要关心调度逻辑,只需要在在 JobProcessor 接口的实现中添加业务逻辑即可,然后在自主运维控制台配置上一个 Job 即可完成使用。高可用的分布式任务不管是 SchedulerX 服务端还是客户端都是分布式架构设计,任务可以在多台客户端机器里的任何一台机器执行,如果客户端出现宕机的情况,服务端会自动选择正常运行的客户端去执行 Job,每个 Job 在服务端的不同机器均有备份,SchedulerX 服务端任意宕掉部分机器仍能保证 Job 正常调度。友好的用户界面SchedulerX 提供了非常友好的页面方便您创建、删除或修改 Job,提供了立即触发执行一次的功能,方便您测试以及关键时刻手动立即执行一次,还提供了历史执行记录查询的功能,您可以看到任何一个 Job 过去 100 次的历史执行记录。功能强大提供了秒级、精准的定时任务调度服务,且提供了丰富的任务执行模型,包括单机执行,广播执行,以及子任务的分布式执行。What’s Next?Spring Cloud Alibaba Cloud SLS 针对日志类数据的一站式服务,在阿⾥巴巴集团经历大量大数据场景锤炼⽽成。您⽆需开发就能快捷地完成日志数据采集、消费、投递以及查询分析等功能,提升运维、运营效率,建立 DT 时代海量日志处理能力。Spring Cloud Alibaba Dubbo Dubbo 是一款流行的开源 RPC 框架,我们会把 Dubbo 整合到 Spring Cloud Alibaba 中,让大家在开发 Dubbo 时也能享受到 Spring Cloud 带来的便利。致谢Spring Cloud Alibaba 从开源建设以来,受到了很多社区同学的关注。社区的每一个 issue ,每一个 PR,都是对整个项目的帮助,都在为建设更好用的 Spring Cloud 添砖加瓦。我们真心地感谢为这个项目提出过 Issue 和 PR 的同学,特别是这些 contributor:HaojunRen、xiejiashuai、mengxiangrui007我们希望有更多社区的同学加入进来,一起把项目做好。还要特别感谢文档团队的倾芸,她帮忙将我们所有的 Reference Doc 翻译了英文,为Spring Cloud Alibaba 的国际化进程铺平了道路。亦盏,阿里巴巴中间件高级开发工程师,长期关注微服务领域,主要负责 Spring Cloud Alibaba 项目的开发和社区维护。本文作者:中间件小哥阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

December 29, 2018 · 3 min · jiezi