如下摘自《SpinrgCloud微服务架构开发实践》
服务治理
SpringCloud服务治理有两种选择,Consul和Neiflix的Eureka。
Eureka是Netflix开源的一块服务治理产品,Spirng Cloud对其进行了二次封装,形成了Spirng Cloud Netflix子项目。Erueka提供了服务注册中心、服务发现客户端,以及注册服务的UI界面应用。在Eureka的视线中,节点之间相互平等,有部分注册中停止不会对整个应用造成影响,及时汲取中只剩下一个节点存活,也可以正常的治理服务。即使所有服务注册节点都宕机,Eureka客户端中所缓存的服务实例列表信息,也可以让服务消费者能够正常工作,从而保障微服务之间相互调用的健壮性和应用的弹性。
客户端负载均衡
SpringCloud通过对Netflix开源项目Ribbon封装,实现了客户端负载均衡,Ribbon默认与Eureka无缝整合,当客户端启动时,从Eureka服务器中获取一份服务注册列表并维护在本地,当服务消费者需要调用服务时,Ribbon就会根据负载均衡策略选择一个合适的服务提供者进行访问。SpringCloud通过集成Netflix的Feign项目,为开发者提供了声明式服务调用,从而简化了微服务之间的调用处理方式。并且默认Feign项目继承了Ribbon,使得声明式调用也支持客户端负责均衡功能。
微服务容错,降级
在SpirngCloud中,通过集成Netflix的子项目Hystrix,通过所提供的的@HystrixCommand注解可以为我们所开发的微服务提供容错,回退,降级等功能,Hystrix也默认集成到Feign子项目中。Hystrix是根据“断路器”模式而创建,当Hystrix监控到某服务单元发生故障之后,就会进入服务熔断chul,bing向调用方返回一个符合语气的服务降级处理(fallback),而不是长时间的等待或者抛出调用异常,从而保障服务调用方的香橙不会被长时间,不必要的占用,避免鼓掌在英语红中的蔓延造成的雪崩效应。Hystrix仪表盘炫目(Dashboard)可以监控各个服务调用所消耗的时间、请求书、成功率等。
本测试使用版本
SpringBoot: 2.6.2
SpringCloud: 2021.0.0
Eureka服务注册中心
maven配置
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.6.2</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.demo</groupId> <artifactId>service-discovery</artifactId> <version>0.0.1-SNAPSHOT</version> <name>service-discovery</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <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>2021.0.0</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> </project>
除SpringBoot和SpringCloud基础版本号配置外,必须加上eureka-server的maven依赖,web模块依赖可以不加,如果不加则会用eureka-server默认依赖的web模块
启动类
package com.demo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; @SpringBootApplication @EnableEurekaServer public class ServiceDiscoveryApplication { public static void main(String[] args) { SpringApplication.run(ServiceDiscoveryApplication.class, args); } }
必须加上@EnableEurekaServer注解
参数配置
server.port=9999 eureka.instance.appname=eureka eureka.instance.hostname=127.0.0.1 eureka.instance.prefer-ip-address=true ## 当前就是eureka注册服务中心,并且是单机版,不需要向eureka中注册当前服务,设置为false eureka.client.register-with-eureka=false ## 不需要从eureka注册服务中心拉取服务列表,设置为false eureka.client.fetch-registry=false ## eureka注册服务中心地址 eureka.client.service-url.defaultZone=http://${eureka.instance.hostname}:${server.port}/eureka/ eureka.server.wait-time-in-ms-when-sync-empty=0 eureka.server.enable-self-preservation=false
到此Eureka服务注册中心就搭建完成,启动后,访问地址http://127.0.0.1:9999/,即可访问到eureka注册管理界面,在其中可以看到注册过的服务等信息
服务提供者
maven配置
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.6.2</version> <relativePath /> <!-- lookup parent from repository --> </parent> <groupId>com.demo</groupId> <artifactId>service-product</artifactId> <version>0.0.1-SNAPSHOT</version> <name>service-product</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</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>2021.0.0</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> </project>
除SpringBoot和SpringCloud基础版本号配置外,必须加上eureka-client的maven依赖,由于提供的为web服务,也需要加上web相关starter依赖
controller
package com.demo.controller; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import com.demo.entity.ResultEntity; @RestController @RequestMapping public class ServerController { private Logger logger = LoggerFactory.getLogger(ServerController.class); @RequestMapping("/getData/{id}") public Object getData(@PathVariable String id) { logger.info("server receive id: {}", id); return new ResultEntity(true, "result: "+id); } }
返回实体类
package com.demo.entity; public class ResultEntity { private boolean success; private String msg; public ResultEntity(boolean success, String msg) { this.success = success; this.msg = msg; } public boolean isSuccess() { return success; } public void setSuccess(boolean success) { this.success = success; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } }
启动类
package com.demo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** * 高版本可以不用使用@EnableDiscoveryClient * @author admin * */ @SpringBootApplication //@EnableDiscoveryClient public class ServiceProductApplication { public static void main(String[] args) { SpringApplication.run(ServiceProductApplication.class, args); } }
参数配置
## 端口自动分配,如果自动分配会有影响,则可以指定一个端口,比如指定端口才能通过防火墙访问时,则需要指定端口 server.port=0 spring.application.name=service-provider eureka.client.register-with-eureka=true eureka.client.fetch-registry=false eureka.client.service-url.defaultZone=http://127.0.0.1:9999/eureka/
服务消费者
maven配置
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.6.2</version> <relativePath /> <!-- lookup parent from repository --> </parent> <groupId>com.demo</groupId> <artifactId>service-consumer</artifactId> <version>0.0.1-SNAPSHOT</version> <name>service-consumer</name> <description>Demo project for Spring Boot</description> <properties> <spring-cloud.version>2021.0.0</spring-cloud.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</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> </project>
除SpringBoot和SpringCloud基础版本号配置外,必须加上eureka-client的maven依赖,web项目也需要加上web相关依赖,由于使用到了fegin,因此也需要加入feign相关的依赖。
controller
package com.demo.controller; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import com.demo.entity.ResultEntity; import com.demo.service.ConsumerService; @RestController @RequestMapping public class ConsumerController { private Logger logger = LoggerFactory.getLogger(ConsumerController.class); @Autowired private ConsumerService ConsumerService; @RequestMapping("/getData/{id}") public Object getData(@PathVariable String id) { logger.info("concumer id: {}", id); ResultEntity data = ConsumerService.getData(id); return data; } }
feign调用
package com.demo.service; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import com.demo.entity.ResultEntity; import com.demo.service.callback.ConsumerServiceCallBack; @FeignClient(name = "SERVICE-PROVIDER", fallback = ConsumerServiceCallBack.class) public interface ConsumerService { /** * 此处不能返回Object,要么返回String,要么返回存在get set方法的实体对象或者map * @param id * @return */ @RequestMapping("/getData/{id}") ResultEntity getData(@PathVariable String id); }
fallback
package com.demo.service.callback; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; import org.springframework.web.bind.annotation.PathVariable; import com.demo.entity.ResultEntity; import com.demo.service.ConsumerService; @Service public class ConsumerServiceCallBack implements ConsumerService{ private Logger logger = LoggerFactory.getLogger(ConsumerServiceCallBack.class); @Override public ResultEntity getData(@PathVariable String id) { logger.error("call back id: {}", id); return new ResultEntity(false, "error"); } }
接收实体类(与服务提供者的返回实体类对应)
package com.demo.entity; public class ResultEntity { private boolean success; private String msg; public ResultEntity(boolean success, String msg) { this.success = success; this.msg = msg; } public boolean isSuccess() { return success; } public void setSuccess(boolean success) { this.success = success; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } }
启动类
package com.demo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.openfeign.EnableFeignClients; @SpringBootApplication //@EnableDiscoveryClient @EnableFeignClients public class ServiceConsumerApplication { public static void main(String[] args) { SpringApplication.run(ServiceConsumerApplication.class, args); } }
参数配置
## 端口自动分配 server.port=8081 spring.application.name=service-consumer eureka.client.register-with-eureka=false eureka.client.fetch-registry=true eureka.client.service-url.defaultZone=http://127.0.0.1:9999/eureka/
测试,先启动eureka注册中心,在启动服务提供者,最后启动服务消费者,界面访问:http://127.0.0.1:8081/getData/11,返回结果如下
其中11为id,使用其他的值也行,会在result中原样返回。
到此,eureka服务注册中,服务的注册与消费的一个完整的服务治理流程demo结束。
注意事项:
1、在低版本的SpringCloud中,服务器提供者和服务消费者都需要添加@EnableDiscoveryClient注解,在高版本中则不需要,SpringCloud则会根据引入的starter自动开启eureka客户端
2、如果服务提供者返回的为一个字符串,只是使用Object接收远程获取到的结果,可能会出现如下异常:
org.springframework.web.client.UnknownContentTypeException: Could not extract response:
no suitable HttpMessageConverter found for response type [class java.lang.Object] and content type [text/plain;charset=UTF-8]
可以改为使用实体对象或者map来接收,或者直接用字符串接收,如果使用实体对象来返回和接收数据,则需要给实体类添加上get和set方法,否则可能会出现如下异常
Resolved [org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representation]
3、@EnableEurekaClient和@EnableDiscoveryClient都能够让注册中心发现,并扫描到该服务,其中前者只对Eureka有效,后者可以对Eureka,Zookeeper、Consul等注册中心有效;
4、如果使用feign进行远程调用,在服务消费者一方一定要加上@EnableFeignClients,否则feign调用不通,另外如果去掉实现了自定义的fegin相关接口的实现类callback,则无法将fegin接口调用注入到controller中,服务启动时可能会出现如下错误
Field ConsumerService in com.demo.controller.ConsumerController required a bean of type 'com.demo.service.ConsumerService' that could not be found.