Spring Boot微服务示例
在本文中,我们将看到一个带有Eureka的Spring Boot Microservices示例,该示例用于服务注册和发现服务。我们将有两个分别作为微服务开发的服务"用户"和"帐户"。
当大型整体应用程序分为两个或者多个微服务时,这些微服务可能需要相互交互。为此,这些微服务需要了解彼此的存在,并且应该能够相互查找。此过程称为服务发现。 Netflix创建了一个名为Eureka的工具,可以用作发现服务器,因为我们需要在Eureka服务器上注册微服务。
因此,在这个Spring Boot Microservices示例中,我们将创建3个单独的Spring Boot应用程序,其中两个用于User和Account功能,另一个用于Eureka Server。
Eureka Server的Spring Boot应用程序
首先,我们将创建一个用于配置Eureka Server的Spring Boot项目,该应用程序充当服务注册表。
我们需要为Eureka Server添加的启动程序是spring-cloud-starter-netflix-eureka-server
Maven依赖项– pom.xml
pom.xml与启动程序的依赖关系。
<?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.2.2.RELEASE</version> <relativePath/> </parent> <groupId>com.theitroad</groupId> <artifactId>springeureka</artifactId> <version>0.0.1-SNAPSHOT</version> <name>EurekaServer</name> <description>Eureka Server project</description> <properties> <java.version>1.8</java.version> <spring-cloud.version>Hoxton.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> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </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>
应用类别
具有main方法的应用程序类。
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; @EnableEurekaServer @SpringBootApplication public class EurekaServerApplication { public static void main(String[] args) { SpringApplication.run(EurekaServerApplication.class, args); } }
在应用程序类中,除了@SpringBootApplication注释之外,还添加了另一个注释@EnableEurekaServer。
@EnableEurekaServer注释表示我们要运行Eureka服务器。通过查看对Spring Cloud Eureaka的依赖关系,Spring Boot可以自动将应用程序配置为服务注册表。
Eureka服务器配置
在application.properties中放入以下内容。
server.port=8761 eureka.instance.hostname=localhost eureka.client.register-with-eureka=false eureka.client.fetch-registry=false
server.port配置Eureka Server运行的端口。
对于独立实例,我们也不希望Eureka Server成为客户端,这就是为什么这两个条目
eureka.client.register-with-eureka = false
eureka.client.fetch-registry = false
运行尤里卡服务器
Eureka服务器应用程序已准备就绪,我们可以运行EurekaServerApplication类来启动Eureka服务器。
如果一切正常,我们应该收到以下消息
2020-03-12 14:53:16.457 INFO 14400 --- [ Thread-10] e.s.EurekaServerInitializerConfiguration : Started Eureka Server 2020-03-12 14:53:16.503 INFO 14400 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8761 (http) with context path '' 2020-03-12 14:53:16.507 INFO 14400 --- [ main] .s.c.n.e.s.EurekaAutoServiceRegistration : Updating port to 8761 2020-03-12 14:53:19.314 INFO 14400 --- [ main] o.n.s.EurekaServerApplication : Started EurekaServerApplication in 30.203 seconds (JVM running for 33.929)
我们可以通过访问URLhttp:// localhost:8761 /来查看Eureka Server控制台。
如我们所见,目前没有实例在Eureka中注册。这就是创建Spring Boot微服务并将其注册到Eureka Server的下一个任务。
Spring Boot Account应用程序
为帐户微服务创建另一个Spring Boot项目,必须添加eureka客户端的启动程序相关性才能将该微服务注册为Eureka客户端。
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>
休息控制器类
我们将添加一个控制器,该控制器具有查找传递的EmployeeId的所有帐户的功能。
import java.util.ArrayList; import java.util.List; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; @RestController public class AccountController { @GetMapping(value="/accounts/{empId}") public List<Account>getAccounts(@PathVariable String empId) { System.out.println("EmpId------" + empId); return findByEmpId(empId); } private List<Account> findByEmpId(String empId){ List<Account> accountList = getEmployees(); List<Account> empList = new ArrayList<>(); for(Account account : accountList) { if(account.getEmpId().equals(empId)) empList.add(account); } return empList; } private List<Account> getEmployees(){ List<Account> accountList = new ArrayList<>(); accountList.add(new Account("1", "AC1", "MT")); accountList.add(new Account("1", "AC2", "IN")); accountList.add(new Account("2", "AC3", "IN")); return accountList; } }
理想情况下,应该有一个用于业务逻辑的Service类,但是在这里它是在Controller类本身中完成的。我们还可以看到有一种虚拟方法来获取帐户,而不是访问数据库,以便将重点放在微服务之间的交互上。
DTO等级
还有一个Account类,它充当DTO或者模型bean。
public class Account { private String empId; private String accountId; private String branch; Account(){ } Account(String empId, String accountId, String branch){ this.empId = empId; this.accountId = accountId; this.branch = branch; } public String getEmpId() { return empId; } public void setEmpId(String empId) { this.empId = empId; } public String getAccountId() { return accountId; } public void setAccountId(String accountId) { this.accountId = accountId; } public String getBranch() { return branch; } public void setBranch(String branch) { this.branch = branch; } }
应用类别
import org.springframework.boot.SpringApplication; import org.springframework.cloud.client.SpringCloudApplication; @SpringCloudApplication public class SpringBootAccountApplication { public static void main(String[] args) { SpringApplication.run(SpringBootAccountApplication.class, args); } }
应用程序类在此处用@SpringCloudApplication进行注释,而不是常规的@SpringBootApplication注释。
@SpringCloudApplication注释扩展了@SpringBootApplication,因此它提供了自动配置,组件扫描和Bean管理,就像@SpringBootApplication一样。它还添加了@EnableDiscoveryClient注释以向Eureka和@EnableCircuitBreaker注册,以启用CircuitBreaker实现,作为回退。
Eureka客户端的配置
还将以下属性添加到application.properties文件中,以将Account Microservice注册为Eureka客户端。
eureka.client.service-url.default-zone=http://localhost:8761/eureka server.port=9000 spring.application.name=account
eureka.client.service-url.default-zone属性告诉我们的微服务在哪里寻找Eureka Server。
使用spring.application.name可以给微服务一个逻辑名称。
服务器端口配置为9000,因此此帐户应用程序在端口9000上运行。
将帐户微服务注册为Eureka客户端
运行SpringBootAccountApplication类以启动此RESTful服务。它将自动注册为Eureka客户。我们可以验证是否在控制台上看到了消息。
2020-03-12 15:23:58.585 INFO 12416 --- [ restartedMain] o.s.c.n.e.s.EurekaServiceRegistry : Registering application ACCOUNT with eureka with status UP 2020-03-12 15:23:58.588 INFO 12416 --- [ restartedMain] com.netflix.discovery.DiscoveryClient : Saw local status change event StatusChangeEvent [timestamp=1584006838588, current=UP, previous=STARTING] 2020-03-12 15:23:58.597 INFO 12416 --- [nfoReplicator-0] com.netflix.discovery.DiscoveryClient : DiscoveryClient_ACCOUNT/user:account:9000: registering service... 2020-03-12 15:23:58.940 INFO 12416 --- [ restartedMain] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 9000 (http) with context path '' 2020-03-12 15:23:58.945 INFO 12416 --- [ restartedMain] .s.c.n.e.s.EurekaAutoServiceRegistration : Updating port to 9000 2020-03-12 15:23:59.194 INFO 12416 --- [nfoReplicator-0] com.netflix.discovery.DiscoveryClient : DiscoveryClient_ACCOUNT/user:account:9000 - registration status: 204 2020-03-12 15:24:02.128 INFO 12416 --- [ restartedMain] o.n.a.SpringBootAccountApplication : Started SpringBootAccountApplication in 31.85 seconds (JVM running for 35.175)
验证Eureka服务器
如果刷新Eureka Serverhttp:// localhost:8761 /的URL,现在应该看到实例已注册。实例的名称与使用以下属性配置为逻辑名称的名称相同。
spring.application.name =帐户
Spring Boot用户应用程序
我们需要创建的另一个微服务是用户服务,因此创建另一个项目。再次添加相同的启动程序依赖关系,以将该微服务注册为Eureka客户端。
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>
休息控制器类
我们将添加一个带有showEmployees方法的UserController类,该方法又调用Account微服务以获取传递的员工ID的所有关联帐户。
@RestController public class UserController { @Autowired private RestTemplate restTemplate; @GetMapping(value="/{id}") public List<Account> showEmployees(@PathVariable("id") String id) { System.out.println(id); // List<Account> accounts = new RestTemplate().exchange( // "http://localhost:9000/accounts/{empId}", HttpMethod.GET, null, new // ParameterizedTypeReference<List<Account>>(){}, id).getBody(); List<Account> accounts = restTemplate.exchange( "http://ACCOUNT/accounts/{empId}", HttpMethod.GET, null, new ParameterizedTypeReference<List<Account>>(){}, id).getBody(); for(Account acct : accounts) { System.out.println(acct.getEmpId()); System.out.println(acct.getAccountId()); System.out.println(acct.getBranch()); } return accounts; } //public List<Account> defaultAccounts(String id) { // return Collections.emptyList(); //} }
restTemplate.exchange()是用于远程调用另一个微服务的方法。
- restTemplate.exchange()的第一个参数是帐户微服务的网址-" http:// ACCOUNT / accounts / {empId}"
- 第二个参数指定它是HTTP Get命令。
- 第三个参数指定要写入请求的实体(标题和/或者主体)。由于我们没有传递任何请求实体,因此它为null。
- 第四个参数指定响应的类型。
- 第五个参数指定要在模板中扩展的变量。我们在那里传递ID,它将替换URL中的{empId}。
使用功能区负载平衡器
在上述方法中,我们可以看到用于调用微服务的URL是http:// ACCOUNT / accounts / {empId},尽管我们也可以使用http:// localhost:9000 / accounts / {empId},但这会硬编码该位置不好
为了避免这种硬编码,我们使用了可与Eureka集成的Netflix Ribbon服务。我们需要做的是标记一个RestTemplate bean,使其配置为使用LoadBalancerClient,为此,我们可以按以下方式创建RestTemplate bean。
import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.client.RestTemplate; @Configuration public class Config { @Bean @LoadBalanced RestTemplate restTemplate() { return new RestTemplate(); } }
一旦有了此负载均衡的restTemplate实例,便可以在URL中使用该服务的逻辑名,该逻辑名用于向Eureka注册该服务。这就是我们使用此URL http:// ACCOUNT / accounts / {empId}来访问Account MicroService的方式。
应用类别
import org.springframework.boot.SpringApplication; import org.springframework.cloud.client.SpringCloudApplication; @SpringCloudApplication public class SpringBootUserApplication { public static void main(String[] args) { SpringApplication.run(SpringBootUserApplication.class, args); } }
如我们在这里再次看到的,使用@SpringCloudApplication注释。
Eureka客户端的配置
还将以下属性添加到application.properties文件中,以将User Microservice注册为Eureka客户端。
eureka.client.service-url.default-zone=http://localhost:8761/eureka spring.application.name=user
运行SpringBootUserApplication以启动User MicroService。它将自动注册为Eureka客户。我们可以通过查看控制台上的消息来验证。
2020-03-12 16:24:00.228 INFO 9844 --- [ restartedMain] o.s.c.n.e.s.EurekaServiceRegistry : Registering application USER with eureka with status UP 2020-03-12 16:24:00.231 INFO 9844 --- [ restartedMain] com.netflix.discovery.DiscoveryClient : Saw local status change event StatusChangeEvent [timestamp=1584010440231, current=UP, previous=STARTING] 2020-03-12 16:24:00.240 INFO 9844 --- [nfoReplicator-0] com.netflix.discovery.DiscoveryClient : DiscoveryClient_USER/user:user: registering service... 2020-03-12 16:24:00.402 INFO 9844 --- [nfoReplicator-0] com.netflix.discovery.DiscoveryClient : DiscoveryClient_USER/user:user - registration status: 204 2020-03-12 16:24:00.572 INFO 9844 --- [ restartedMain] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path '' 2020-03-12 16:24:00.577 INFO 9844 --- [ restartedMain] .s.c.n.e.s.EurekaAutoServiceRegistration : Updating port to 8080 2020-03-12 16:24:03.278 INFO 9844 --- [ restartedMain] com.theitroad.user.SpringBootUserApplication : Started SpringBootUserApplication in 28.889 seconds (JVM running for 33.647)
如果刷新Eureka Serverhttp:// localhost:8761 /的URL,则应该同时看到两个已注册为Eureka客户端的MicroService。
微服务之间的通信
现在,我们已经创建并运行了两个MicroSerivces。这两个微服务都已在Eureka注册,因此可以使用Eureka发现这些服务。
现在,当我们访问URL http:// localhost:8080/1时,它将由SpringBootUser应用程序中UserController的showEmployees()方法提供服务。从那里使用restTemplate.exchange()方法与帐户服务进行通信。
在交换方法中传递的URL(http:// ACCOUNT / accounts / {empId})触发负载平衡和发现Eureka客户端的整个过程,从日志消息中可以明显看出。
2020-03-12 16:36:37.733 INFO 9844 --- [nio-8080-exec-1] c.netflix.config.ChainedDynamicProperty : Flipping property: ACCOUNT.ribbon.ActiveConnectionsLimit to use NEXT property: niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit = 2147483647 2020-03-12 16:36:37.915 INFO 9844 --- [nio-8080-exec-1] c.n.u.concurrent.ShutdownEnabledTimer : Shutdown hook installed for: NFLoadBalancer-PingTimer-ACCOUNT 2020-03-12 16:36:37.916 INFO 9844 --- [nio-8080-exec-1] c.netflix.loadbalancer.BaseLoadBalancer : Client: ACCOUNT instantiated a LoadBalancer: DynamicServerListLoadBalancer:{NFLoadBalancer:name=ACCOUNT,current list of Servers=[],Load balancer stats=Zone stats: {},Server stats: []}ServerList:null 2020-03-12 16:36:37.963 INFO 9844 --- [nio-8080-exec-1] c.n.l.DynamicServerListLoadBalancer : Using serverListUpdater PollingServerListUpdater 2020-03-12 16:36:38.090 INFO 9844 --- [nio-8080-exec-1] c.netflix.config.ChainedDynamicProperty : Flipping property: ACCOUNT.ribbon.ActiveConnectionsLimit to use NEXT property: niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit = 2147483647 2020-03-12 16:36:38.098 INFO 9844 --- [nio-8080-exec-1] c.n.l.DynamicServerListLoadBalancer : DynamicServerListLoadBalancer for client ACCOUNT initialized: DynamicServerListLoadBalancer:{NFLoadBalancer:name=ACCOUNT,current list of Servers=[user:9000],Load balancer stats=Zone stats: {defaultzone=[Zone:defaultzone; Instance count:1; Active connections count: 0; Circuit breaker tripped count: 0; Active connections per server: 0.0;] },Server stats: [[Server:user:9000; Zone:defaultZone; Total Requests:0; Successive connection failure:0; Total blackout seconds:0; Last connection made:Thu Jan 01 05:30:00 IST 1970; First connection made: Thu Jan 01 05:30:00 IST 1970; Active Connections:0; total failure count in last (1000) msecs:0; average resp time:0.0; 90 percentile resp time:0.0; 95 percentile resp time:0.0; min resp time:0.0; max resp time:0.0; stddev resp time:0.0] ]}ServerList:org.springfHyman@theitroad5820f552 1 AC1 MT 1 AC2 IN 2020-03-12 16:36:38.995 INFO 9844 --- [erListUpdater-0] c.netflix.config.ChainedDynamicProperty : Flipping property: ACCOUNT.ribbon.ActiveConnectionsLimit to use NEXT property: niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit = 2147483647 favicon.ico 2020-03-12 16:38:59.147 INFO 9844 --- [trap-executor-0] c.n.d.s.r.aws.ConfigClusterResolver : Resolving eureka endpoints via configuration 2020-03-12 16:43:59.150 INFO 9844 --- [trap-executor-0] c.n.d.s.r.aws.ConfigClusterResolver : Resolving eureka endpoints via configuration