Netflix之后,如何用Spring Cloud 新组件构建微服务架构?

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"本文最初发表于作者Piotr Mińkowski的"},{"type":"link","attrs":{"href":"https:\/\/piotrminkowski.com\/2020\/05\/01\/a-new-era-of-spring-cloud\/","title":"","type":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"个人站点"}]},{"type":"text","marks":[{"type":"strong"}],"text":",经作者许可由InfoQ中文站编译分享。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2020年12月22日,"},{"type":"link","attrs":{"href":"https:\/\/spring.io\/blog\/2020\/12\/22\/spring-cloud-2020-0-0-aka-ilford-is-available","title":"","type":null},"content":[{"type":"text","text":"Spring在官方博客正式发布2020.0.0版本(即Ilford)"}]},{"type":"text","text":",这是一个采用新命名规范的版本,但是,更引人关注的是该版本移除了多个之前处于维护模式的Netflix组件,如Ribbon、Hystrix和Zuul。唯一剩余的模块是Eureka。这些变化对Spring Cloud来说是很重要的,因为从诞生之初,Spring Cloud就因为与Netflix组件的集成得到广泛认可。此外,Spring Cloud Netflix仍然是GitHub上最受欢迎的Spring Cloud项目(约有4200 star)。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在将Netflix组件转入维护模式的同时,Spring 团队已经开始着手准备替代方案了。因此,Ribbon将被Spring Cloud Load Balancer取代,Hystrix将被建立在Resilience4J库之上的Spring Cloud Circuit Breaker取代。Spring Cloud Gateway是Zuul的竞争解决方案,目前已是一个很受欢迎的项目,在Ilford版本后,它将是API网关方面的唯一方案。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"本文的主要目的是指导你使用新的Spring Cloud组件构建微服务架构,替换废弃的Netflix项目。示例应用的源码可以在"},{"type":"link","attrs":{"href":"https:\/\/github.com\/piomin\/course-spring-microservices.git","title":"","type":null},"content":[{"type":"text","text":"GitHub上的仓库中找到"}]},{"type":"text","text":"。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"架构"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下图展现了我们样例系统的架构。在这里,我们包含了微服务的特征元素,如API网关、服务发现server以及配置server。接下来,我会展示如何使用提供了这些模式的Spring Cloud组件。目前,向系统中添加API网关的主要组件是"},{"type":"text","marks":[{"type":"strong"}],"text":"Spring Cloud Gateway"},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/14\/14baa701320293074ed656cf90deaf7d.jpeg","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Spring Cloud提供了与多个可用作服务发现服务器的方案的集成,包括Netflix Eureka、HashiCorp Consul、Alibaba Nacos和Apache ZooKeeper。其中,最流行的是前两者。"},{"type":"text","marks":[{"type":"strong"}],"text":"Spring Cloud Netflix Eureka"},{"type":"text","text":"专门用于服务发现,而"},{"type":"text","marks":[{"type":"strong"}],"text":"Spring Cloud Consul"},{"type":"text","text":"可同时通过Consul Services实现服务发现,通过Consul Key\/Value引擎实现分布式跟踪特性。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Spring Cloud Config只负责提供一个配置管理的机制。但是,它也可以与第三方工具集成,如来自HashiCorp的Vault。以两个简单的Spring Boot应用"},{"type":"text","marks":[{"type":"italic"}],"text":"callme-service"},{"type":"text","text":"和"},{"type":"text","marks":[{"type":"italic"}],"text":"caller-service"},{"type":"text","text":"为例,我们阐述如何将应用与服务发现和配置服务器进行集成。我们还会在"},{"type":"text","marks":[{"type":"italic"}],"text":"caller-service"},{"type":"text","text":"上使用Spring Cloud Load Balancer启用客户端负载均衡,并使用基于Resilience4J构建的"},{"type":"text","marks":[{"type":"strong"}],"text":"Spring Cloud Circuit Breaker"},{"type":"text","text":"启用断路器功能。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"服务发现"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"借助Spring Cloud的DiscoveryClient抽象,在客户端切换不同的服务发现服务器非常容易。这种切换只需要替换Maven "},{"type":"codeinline","content":[{"type":"text","text":"pom.xml"}]},{"type":"text","text":"文件中的一个依赖项。如果你想要使用Eureka的话,只需要添加如下的starter到微服务中。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"\n org.springframework.cloud\n spring-cloud-starter-netflix-eureka-client\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"而如果你想要使用Consul的话,那么需要添加如下的starter到微服务中。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"\n org.springframework.cloud\n spring-cloud-starter-consul-discovery\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果你想要为服务发现的客户端定义一些非默认的配置的话,那情况就有点复杂了。在这种情况下,你需要使用特定于Eureka或Consul的属性。例如,如果你想要在同一个主机上运行同一个应用的多个实例,并启用动态HTTP服务器端口(选项"},{"type":"codeinline","content":[{"type":"text","text":"server.port=0"}]},{"type":"text","text":"),那么你需要为每个实例设置一个唯一的id。如下是Eureka客户端中所使用的属性。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"eureka:\n instance:\n instanceId: ${spring.cloud.client.hostname}:${spring.application.name}:${random.value}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"对Consul客户端来说,相同的配置如下所示。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"spring:\n cloud:\n consul:\n discovery:\n instanceId: ${spring.cloud.client.hostname}:${spring.application.name}:${random.value}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在微服务架构中,我们可以很容易地通过Spring Cloud Netflix Eureka Server模块配置和运行Eureka服务发现。只需要创建包含该模块的Spring Boot应用即可。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"\n org.springframework.cloud\n spring-cloud-starter-netflix-eureka-server\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我们还需要为应用启用Eureka,只需在主类上添加@EnableEurekaServer注解。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"@SpringBootApplication\n@EnableEurekaServer\nclass DiscoveryServerApplication\nfun main(args: Array) {\n runApplication(*args)\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在本地机器上运行Consul的最简便方式是使用它的Docker镜像。我们可以通过执行如下命令以开发模式在Docker容器中运行Consul:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"$ docker run -d --name=consul -e CONSUL_BIND_INTERFACE=eth0 -p 8500:8500 consul:1.7.2"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"使用Spring cloud进行分布式配置"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在我们的架构中,下一个重要的元素就是配置服务器。在Spring Cloud中,能提供分布式配置机制的最流行方案就是Spring Cloud Config。Spring Cloud Config为分布式系统中的外部化配置提供了服务器端和客户端的支持。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"借助配置服务器,我们能有一个中心化的位置管理应用在所有环境下的外部属性。有一些其他的方案可用作基于微服务的架构的配置服务器,如Consul、ZooKeeper和Alibaba Nacos。但是,从严格意义上来讲,所有的这些方案都不是专门的分布式配置,它们也可以用作服务发现的服务器。Spring Cloud Config可能会集成不同的工具来存储数据。服务器存储后端的默认实现是使用git,但是我们也能使用像HashiCorp Vault这样的工具来管理secret和保护敏感数据,也可以使用简单的文件系统。在一个配置服务器应用中,可能会将不同的后端组合在一起。我们只需要在应用属性文件中通过"},{"type":"codeinline","content":[{"type":"text","text":"spring.profiles.active"}]},{"type":"text","text":"激活对应的profile即可。我们可以覆盖一些默认值,比如修改Vault服务器的地址或设置认证token。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"spring:\n application:\n name: config-server\n profiles:\n active: native,vault\n cloud:\n config:\n server:\n native:\n searchLocations: classpath:\/config-repo\n vault:\n host: 192.168.99.100\n authentication: TOKEN\n token: spring-microservices-course"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"对Consul来讲,同样如此,在开发模式下,我们应该使用Docker来运行Vault实例。我们可以使用环境变量"},{"type":"codeinline","content":[{"type":"text","text":"VAULT_DEV_ROOT_TOKEN_ID"}]},{"type":"text","text":"设置一个静态的根token用于进行认证:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"$ docker run -d --name vault --cap-add=IPC_LOCK -e 'VAULT_DEV_ROOT_TOKEN_ID=spring-microservices-course' -p 8200:8200 vault:1.4.0"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"当Spring Cloud Config与服务发现一起使用时,我们可以在两种可用的方式间做出选择,即"},{"type":"text","marks":[{"type":"italic"}],"text":"Config First Bootstrap"},{"type":"text","text":"和"},{"type":"text","marks":[{"type":"italic"}],"text":"Discovery First Bootstrap"},{"type":"text","text":"。在"},{"type":"text","marks":[{"type":"italic"}],"text":"Discovery First Bootstrap"},{"type":"text","text":"中,配置服务器会将自身注册到发现服务中。借助这一点,每个微服务都能基于配置服务器的注册id找到它。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"因为配置是在引导阶段注入的,我们需要使用"},{"type":"codeinline","content":[{"type":"text","text":"bootstrap.yml"}]},{"type":"text","text":"在客户端设置属性。为了在客户端启用配置服务器的“发现”功能,我们需要将"},{"type":"codeinline","content":[{"type":"text","text":"spring.cloud.config.discovery.enabled"}]},{"type":"text","text":"属性设置为"},{"type":"codeinline","content":[{"type":"text","text":"true"}]},{"type":"text","text":"。如果配置服务器的注册服务id与自动配置的"},{"type":"codeinline","content":[{"type":"text","text":"configserver"}]},{"type":"text","text":"(在我们的样例中是"},{"type":"codeinline","content":[{"type":"text","text":"config-server"}]},{"type":"text","text":")不同的话,我们还应该覆盖它。当然,还可以使用Consul作为配置属性源。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"spring:\n application:\n name: callme-service\n cloud:\n config:\n discovery:\n enabled: true\n serviceId: config-server\n consul:\n host: 192.168.99.100\n config:\n format: YAML"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"服务间通信"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"目前,有三种基于HTTP的Spring组件可用于服务间的通信,它们都与服务发现进行了集成:同步的"},{"type":"codeinline","content":[{"type":"text","text":"RestTemplate"}]},{"type":"text","text":"、反应式的"},{"type":"codeinline","content":[{"type":"text","text":"WebClient"}]},{"type":"text","text":"以及声明式的REST客户端OpenFeign。"},{"type":"codeinline","content":[{"type":"text","text":"RestTemplate"}]},{"type":"text","text":"组件可以通过Spring Web模块获取,"},{"type":"codeinline","content":[{"type":"text","text":"WebClient"}]},{"type":"text","text":"能通过Spring WebFlux模块获取。要包含Spring Cloud OpenFeign的话,我们需要一个专门的starter。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"\n org.springframework.boot\n spring-boot-starter-webflux\n\n\n org.springframework.cloud\n spring-cloud-starter-openfeign\n\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"要使用"},{"type":"codeinline","content":[{"type":"text","text":"RestTemplate"}]},{"type":"text","text":"或"},{"type":"codeinline","content":[{"type":"text","text":"WebClient"}]},{"type":"text","text":"进行支持服务发现的通信,我们需要注册bean并为它们添加"},{"type":"codeinline","content":[{"type":"text","text":"@LoadBalanced"}]},{"type":"text","text":"注解。我们最好还要为这样的通信设置恰当的超时时间,当不使用断路器的时候,更应如此。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"@SpringBootApplication\n@EnableFeignClients\nclass InterCallerServiceApplication {\n @Bean\n @LoadBalanced\n fun template(): RestTemplate = RestTemplateBuilder()\n .setReadTimeout(Duration.ofMillis(100))\n .setConnectTimeout(Duration.ofMillis(100))\n .build()\n @Bean\n @LoadBalanced\n fun clientBuilder(): WebClient.Builder {\n val tcpClient: TcpClient = TcpClient.create()\n .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 100)\n .doOnConnected { conn ->\n conn.addHandlerLast(ReadTimeoutHandler(100, TimeUnit.MILLISECONDS))\n }\n val connector = ReactorClientHttpConnector(HttpClient.from(tcpClient))\n return WebClient.builder().clientConnector(connector)\n }\n}\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Spring Cloud LoadBalancer提供了自己的抽象和实现。为了实现负载均衡机制,Spring Cloud添加了"},{"type":"codeinline","content":[{"type":"text","text":"ReactiveLoadBalancer"}]},{"type":"text","text":"接口,并支持基于Round-Robin和Random的实现。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"目前,对于负载均衡的定制化并没有太多的选项。不过,选项之一就是配置客户端缓存的能力。默认情况下,每个客户端缓存目标服务的列表并且每隔30秒刷新一次。在你的场景下,这样的间隔可能会有些长。在配置中,我们可以很容易地修改它,如下面的样例,我们将其设置成1秒。如果你的负载均衡器与Eureka服务发现进行集成的话,还需要减少获取注册表的时间间隔,默认它是30秒钟。在修改配置之后,这两个对客户端的变更能几乎立即刷新当前运行的服务的列表。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"spring:\n cloud:\n loadbalancer:\n cache:\n ttl: 1s\n ribbon:\n enabled: false\neureka:\n client:\n registryFetchIntervalSeconds: 1\n"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"断路器"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"断路器是微服务架构中一个很流行的设计模式。它被设计用来探测失败并封装阻止失败不断重复出现的逻辑。Spring Cloud提供了一个使用不同断路器的实现。针对Resilience4J,有两个实现,分别用于反应式应用和非反应式应用。要启用非反应式的实现,我们要包含如下的依赖。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"\n org.springframework.cloud\n spring-cloud-starter-circuitbreaker-resilience4j\n\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如下是负责注册"},{"type":"codeinline","content":[{"type":"text","text":"Customizer"}]},{"type":"text","text":" bean的代码,这个bean配置了断路器的行为。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"@Bean\nfun defaultCustomizer(): Customizer {\n return Customizer { factory: Resilience4JCircuitBreakerFactory ->\n factory.configureDefault { id: String? ->\n Resilience4JConfigBuilder(id)\n .timeLimiterConfig(TimeLimiterConfig.custom()\n .timeoutDuration(Duration.ofMillis(500))\n .build())\n .circuitBreakerConfig(CircuitBreakerConfig.custom()\n .slidingWindowSize(10)\n .failureRateThreshold(33.3F)\n .slowCallRateThreshold(33.3F)\n .build())\n .build()\n }\n }\n}\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"断路器的设置通过如下图片进行了可视化。滑动窗口的大小设置了用于计算错误率的请求数。如果我们在大小为10的窗口中出现了3个以上的错误,那么断路器就会打开。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/81\/8167dcc5456d1651a88051d17094285d.jpeg","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下一步,我们需要使用"},{"type":"codeinline","content":[{"type":"text","text":"Resilience4JCircuitBreakerFactory"}]},{"type":"text","text":"创建一个断路器实例并为HTTP客户端启用它,如下所示。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"@RestController\n@RequestMapping(\"\/caller\")\nclass CallerController(private val template: RestTemplate, private val factory: Resilience4JCircuitBreakerFactory) {\n private var id: Int = 0\n @PostMapping(\"\/random-send\/{message}\")\n fun randomSend(@PathVariable message: String): CallmeResponse? {\n val request = CallmeRequest(++id, message)\n val circuit = factory.create(\"random-circuit\")\n return circuit.run { template.postForObject(\"http:\/\/inter-callme-service\/callme\/random-call\",\n request, CallmeResponse::class.java) }\n }\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"Spring Cloud API网关"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在我们微服务架构中,缺失的最后一个元素就是API网关。Spring Cloud Gateway能帮助我们实现这一组件。目前,它是Spring Cloud中仅次于Spring Cloud Netflix,欢迎程度排在第二名的项目。在GitHub上它有2800+star。它构建在Spring WebFlux和Reactor项目之上,它以反应式的方式运行,需要Netty作为运行时框架。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"API网关的主要目标是提供一个有效的方式路由至API,从而为外部客户端隐藏微服务系统的复杂性,但是它也能解决一些安全性和可靠性相关的问题。用来配置Spring Cloud Gateway的主要组件是路由。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"它由一个ID、一个目标URI、一个断言的集合和一个过滤器的集合组成。如果断言聚合为true的话,则会匹配路由。通过过滤器,我们则可以在发送下游请求之前或之后修改请求和响应。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通过预定义的网关过滤器集合,我们可以实现路径重写、速率限制、发现客户端、断路器、fallback或路由指标等机制。为了在网关上启用所有这些功能,我们首先需要包含以下依赖关系。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"\n org.springframework.cloud\n spring-cloud-starter-gateway\n\n\n org.springframework.boot\n spring-boot-starter-actuator\n\n\n org.jetbrains.kotlin\n kotlin-reflect\n\n\n org.jetbrains.kotlin\n kotlin-stdlib\n\n\n org.springframework.cloud\n spring-cloud-starter-netflix-eureka-client\n\n\n org.springframework.boot\n spring-boot-starter-data-redis-reactive\n\n\n org.springframework.cloud\n spring-cloud-starter-circuitbreaker-reactor-resilience4j\n\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"为实现上述所列的所有特性,我们并不需要编写太多的代码。几乎所有的内容都是通过应用属性配置的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"spring:\n application:\n name: api-gateway\n cloud:\n gateway:\n discovery:\n locator:\n enabled: true \n lowerCaseServiceId: true\n routes:\n - id: inter-callme-service\n uri: lb:\/\/inter-callme-service\n predicates:\n - Path=\/api\/callme\/**\n filters:\n - RewritePath=\/api(?\/?.*), $\\{path}\n - name: RequestRateLimiter\n args:\n redis-rate-limiter.replenishRate: 20\n redis-rate-limiter.burstCapacity: 40\n - name: CircuitBreaker\n args:\n name: sampleSlowCircuitBreaker\n fallbackUri: forward:\/fallback\/test\n - id: inter-caller-service\n uri: lb:\/\/inter-caller-service\n predicates:\n - Path=\/api\/caller\/**\n filters:\n - StripPrefix=1\n - name: RequestRateLimiter\n args:\n redis-rate-limiter.replenishRate: 20\n redis-rate-limiter.burstCapacity: 40\n loadbalancer:\n ribbon:\n enabled: false\n redis:\n host: 192.168.99.100\nmanagement:\n endpoints.web.exposure.include: '*'\n endpoint:\n health:\n show-details: always\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"有些设置依然需要在代码中进行配置,也就是断路器的配置,它基于Resilience4J项目,我们需要注册"},{"type":"codeinline","content":[{"type":"text","text":"Customizer"}]},{"type":"text","text":" bean。此外,我们还需要定义一个速率限制的key,它用来设置为限制计数选择请求的策略。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"@SpringBootApplication\nclass ApiGatewayApplication {\n @Bean\n fun keyResolver(): KeyResolver = KeyResolver { _ -> Mono.just(\"1\") }\n @Bean\n fun defaultCustomizer(): Customizer {\n return Customizer { factory: ReactiveResilience4JCircuitBreakerFactory ->\n factory.configureDefault { id: String? ->\n Resilience4JConfigBuilder(id)\n .timeLimiterConfig(TimeLimiterConfig.custom()\n .timeoutDuration(Duration.ofMillis(500))\n .build())\n .circuitBreakerConfig(CircuitBreakerConfig.custom()\n .slidingWindowSize(10)\n .failureRateThreshold(33.3F)\n .slowCallRateThreshold(33.3F)\n .build())\n .build()\n }\n }\n }\n}\nfun main(args: Array) {\n runApplication(*args)\n}\n"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"小结"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在本文中,我们快速了解了如何使用最新的Spring Cloud组件构建微服务架构。关于这些组件的更多详情,读者可以阅读"},{"type":"link","attrs":{"href":"https:\/\/docs.spring.io\/spring-cloud\/docs\/2020.0.0\/reference\/html\/","title":"","type":null},"content":[{"type":"text","text":"Spring Cloud的最新文档"}]},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"原文链接:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https:\/\/piotrminkowski.com\/2020\/05\/01\/a-new-era-of-spring-cloud\/","title":null,"type":null},"content":[{"type":"text","text":"https:\/\/piotrminkowski.com\/2020\/05\/01\/a-new-era-of-spring-cloud\/"}]}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章