因为Spring Boot 的更迭,很多资料的不匹配,特别是依赖的问题,让我折腾了两天才正常的运行起来。深受其苦,所以,写一个完整的流程,希望可以帮助到大家。
- 搭建服务注册中心
pom.xml 的依赖内容如下,spring-cloud-dependencies 和 spring-cloud-starter-netflix-eureka-server 一定要注意版本:
<?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.5.3</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.example</groupId> <artifactId>eureka-server</artifactId> <version>0.0.1-SNAPSHOT</version> <name>eureka-server</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> <spring-cloud.version>2020.0.3</spring-cloud.version> </properties> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> <version>3.0.3</version> </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>
application.properties 配置信息: server.port=1111 eureka.instance.hostname=localhost # 让服务中心不注册自己 eureka.client.register-with-eureka=false eureka.client.fetch-registry=false eureka.client.serviceUrl.defaultZone=http://${eureka.instance.hostname}:${server.port}/eureka/
完成了上面的配置后,启动应用并访问 http://localhost:1111/
- 注册服务提供者
继续快速创建一个 spring Boot 项目,将其作为一个微服务应用向服务注册中心发布自己。因为涉及控制台输出,所以额外引入了 log4j<?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.5.3</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.hello</groupId> <artifactId>consumer</artifactId> <version>0.0.1-SNAPSHOT</version> <name>consumer</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> <spring-cloud.version>2020.0.3</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-netflix-eureka-server</artifactId> <version>3.0.3</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-log4j</artifactId> <version>1.3.7.RELEASE</version> <type>pom</type> </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>
接着,实现 /hello 请求处理接口,通过注入 DiscoveryCLient 对象,在日志中打印相关的服务。
package com.hello.consumer; import org.apache.log4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.cloud.client.serviceregistry.Registration; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import java.util.List; @RestController public class HelloController { private final Logger logger = Logger.getLogger(getClass()); @Autowired private DiscoveryClient client; @Autowired private Registration registration; // 服务注册 @RequestMapping(value = "/hello", method = RequestMethod.GET) public String index() { ServiceInstance instance = null; String serviceId = registration.getServiceId(); List<ServiceInstance> instanceList = client.getInstances(serviceId); if (instanceList != null && instanceList.size() > 0) { instance = instanceList.get(0); } System.out.println(instance.toString()); logger.info("/hello, host:" + instance.getHost() + ", service_id:" + instance.getServiceId()); return "Hello World"; } }
然后,在主类中通过加上 @EnablesDiscoveryClient 注解,激活 Eureka 中的 DiscoveryClient 实现。
package com.hello.consumer; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; @EnableDiscoveryClient @SpringBootApplication public class ConsumerApplication { public static void main(String[] args) { SpringApplication.run(ConsumerApplication.class, args); } }
application.properties 配置文件
# 服务名 spring.application.name=hello-service # 指定服务注册中心地址 eureka.client.serviceUrl.defaultZone=http://peer1:1111/eureka/
下面分别启动服务注册中心和服务提供者。Tomcat 启动之后,打印了注册信息,表示服务注册成功。
而此时,服务注册中心的控制台中,可以看到类似的输出,表示 hello-service 注册成功了!
通过访问 Eureka 的信息面板,一样可以看到服务的注册信息
通过访问 http://localhost:8080/hello,发起请求,在控制台中可以看懂如下输出:
这些输出内容就是之前在 HelloController 注入的 DiscoveryClient 接口对象,从服务注册中心获取的相关服务信息。 要配置log4j的打印级别,才会输出。
- 高可用注册中心
在 Eureka 的服务治理设计中,所有节点即是服务提供方,也是服务消费方。Eureka Server 的高可用实际上就是将自己作为服务向其他服务注册中心注册自己,这样就可以形成一组互相注册的服务中心,以实现服务清单的互相同步,达到高可用效果。
创建 application-peer1.properties,作为 peer1 服务中心的配置,并将 serviceUrl 指向 peer2
创建 application-peer2.properties,作为 peer2 服务中心的配置,并将 serviceUrl 指向 peer1spring.application.name=eureka-server server.port=1111 eureka.instance.hostname=peer1 eureka.client.serviceUrl.defaultZone=http://peer2:1112/eureka/
在/etc/hosts 文件中添加对 peer1 和 peer2 的转换,让上面配置的 host 形式 serviceUrl 能在本地正确访问到。spring.application.name=eureka-server server.port=1112 eureka.instance.hostname=peer2 eureka.client.serviceUrl.defaultZone=http://peer1:1111/eureka/
通过 spring.profiles.active 属性分别来启动 peer1 和 peer2。127.0.0.1 peer1 127.0.0.1 peer2
java -jar eureka-server-0.0.1-SNAPSHOT.jar --spring.profiles.active=peer1 java -jar eureka-server-0.0.1-SNAPSHOT.jar --spring.profiles.active=peer2
此时访问 peer1 的注册中心,发现 registere-replicas 中已经有 peer2 节点的 eureka-server 了。同样的,访问 peer2 的注册中心,发现 registere-replicas 中已经有 peer1节点的 eureka-server 了。
但是 available-replicas (可用分片)之中,没有节点。要在 application-peer1.properties 和 application-peer2.properties 添加如下配置:让服务中心注册自己
eureka.client.register-with-eureka=true eureka.client.fetch-registry=true
再进去就可以看到,可用分片之中就有了节点。
但是关闭 peer1节点,在peer2的服务注册中心,发现peer1一直都还在可用节点中。application-peer1.properties 和 application-peer2.properties添加如下配置:# 注册表:缓存过期时间,默认180s eureka.server.response-cache-auto-expiration-in-seconds=5 # 注册表:缓存更新间隔 eureka.server.response-cache-update-interval-ms=5000 # 启用主动失效,主动失效检测间隔3s eureka.server.eviction-interval-timer-in-ms=3000 # 服务过期时间,默认90s,超过这个时间没收到心跳,EurekaServer会剔除该实例 eureka.instance.lease-expiration-duration-in-seconds=15 # 服务刷新时间间隔,默认30s会主动心跳一次 eureka.instance.lease-renewal-interval-in-seconds=5 # 关闭自我保护模式 【注意】保护模式未关闭时,上面即使配置了过期,eureka还是不会剔除下线的实例 eureka.server.enable-self-preservation=false
在设置了多节点的服务注册中心之后,服务提供方还需要做一些简单的配置才能将服务注册到 Eureka Server 集群中。
# 服务名 spring.application.name=hello-service # 指定服务注册中心地址 eureka.client.serviceUrl.defaultZone=http://peer1:1111/eureka/,http://peer1:1112/eureka/
- 服务发现与消费
现在已经有了 服务注册中心 和 服务提供者,下面构建一个服务消费者,它主要完成两个目标 ,发现服务已经消费服务。其中服务发现的任务由 Eureka 的客户端完成,而服务消费的任务由 Ribbon 完成。Ribbon 是有一个基于 HTTP 和 TCP 的客户端负载均衡器,它可以通过客户端中配置的 ribbonServerList 服务端列表去轮询访问以达到负载均衡的作用。
首先,启动之前实现的服务注册中心 以及 hello-service 服务,为了实验 Ribbon 的客户端负载均衡功能,启动两个不同端口的 hello-service。java -jar consumer-0.0.1-SNAPSHOT.jar --server.port=8081 java -jar consumer-0.0.1-SNAPSHOT.jar --server.port=8082
在启动成功后,Eureka 信息面板如下所示:
创建一个 Spring Boot的基础工程来实现服务消费者,取名为 ribbon-consumer,pom.xml依赖如下。刚开始按照hello-service的pom.xml配置,服务启动,在注册中心显示都没问题,但是调用hello-serivice服务就有问题,SpringCloud、Eureka、ribbon报错No instances available for xxx!搜了一天半各种办法,最后参考这个 https://blog.csdn.net/qq_40781284/article/details/113830607 把spring-cloud-starter-netflix-ribbon依赖删除即可 ,因为pom.xml导入的jar包冲突,spring-cloud-starter-netflix-eureka-client 3.0版本的已经内置ribbon 。就没问题了!!!
<?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.4.9</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.tripodfan</groupId> <artifactId>ribbon-consumer</artifactId> <version>0.0.1-SNAPSHOT</version> <name>consumer</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> <spring-cloud.version>2020.0.3</spring-cloud.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--eureka--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> <version>3.0.1</version> </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>
创建应用主类 ConsumerApplication。
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.context.annotation.Bean; import org.springframework.web.client.RestTemplate; @EnableDiscoveryClient @SpringBootApplication public class ConsumerApplication { @Bean @LoadBalanced RestTemplate restTemplate() { return new RestTemplate(); } public static void main(String[] args) { SpringApplication.run(ConsumerApplication.class, args); } }
创建 ConsumerController 并实现 /ribbon-consumer 接口。这里的 HELLO-SERVICE 我试了大小写都没问题。
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; @RestController public class ConsumerController { @Autowired RestTemplate restTemplate; @RequestMapping(value = "/ribbon-consumer", method = RequestMethod.GET) public String helloConsumer() { String url="http://hello-service/hello"; // String url="http://HELLO-SERVICE/hello"; return restTemplate.getForEntity(url, String.class).getBody(); } }
在 application.properties 配置 Eureka 服务注册中心的位置
spring.application.name=ribbon-consumer server.port=9000 eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka/
启动 ribbon-consumer 应用。
通过访问 http://localhost:9000/ribbon-consumer,陈宫返回了 “Hello World”。
-
关闭一个 hello-service,继续测试,没有问题。两个服务的时候,8081 和 8082交替被调用。可以看到 控制台会交替打印下面的日志:
在微服务架构中,存在着那么多的服务单元,若一个单元出现故障,就很容易因依赖关系而引发故障的蔓延,最终导致整个系统的瘫痪,这样的架构相较传统架构更加不稳定。如何解决这样的问题呢?服务容错保护, Spring Cloud Hystrix https://blog.csdn.net/qq_15700115/article/details/119459686