在构建微服务时,最常用的就是SpringCloud,其中的Netflix-Eureka用的最多,本文主要讲讲如何使用它。
一、配置注册服务器(Registry Server/Eureka Server)
Maven配置:
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>TestCloud</groupId>
<artifactId>TestCloud-ServiceRegistry</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>TestCloud-ServiceRegistry</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.encoding>UTF-8</maven.compiler.encoding>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.3.RELEASE</version>
<relativePath />
</parent>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.SR1</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</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
application.properties配置:
server.port=8761
eureka.instance.hostname=localhost
# eureka.datacenter=Main
# eureka.environment=prodeureka.server.enableSelfPreservation=false
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false
eureka.server.enableSelfPreservation - 当service server心跳超时后,是否还要将service server保存在列表中一段时间。 当网络发生短暂分区时,不会因为大量service server心跳超时,而将service server从列表中删除,从而造成客户端调用大面积失败的情况。
eureka.client.register-with-eureka - 是否将自身也作为service server注册到Eureka Server。
eureka.client.fetch-registry - 是否将自身作为service client从Eureka Server中获取service server列表。
ServiceRegistry代码:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer
public class ServiceRegistryEurekaApp {
public static void main(String[] args) throws Exception {
SpringApplication.run(ServiceRegistryEurekaApp.class, args);
}
}
程序运行后,可以通过访问如下地址访问Eureka Server管理控制台:
http://localhost:8761 当有service server注册上来后,可以通过如下地址查看注册的service server的信息:
http://localhost:8761/eureka/apps
二、配置服务服务器(Service Server/Eureka Client)
Maven配置:
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>TestCloud</groupId>
<artifactId>TestCloud-ServiceServer1</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>TestCloud-ServiceServer1</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.3.RELEASE</version>
<relativePath />
</parent>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.SR1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
bootstrap.properties配置:
spring.profiles.active=test
spring.application.name=TestServer
# spring.main.allow-bean-definition-overriding=true
# spring.cloud.config.uri=http://localhost:8888spring.cloud.discovery.enabled=true
spring.cloud.service-registry.auto-registration.enabled=true
spring.cloud.config.uri - 表示以后需要访问的配置服务器的地址。
spring.application.name - 表示此service注册到Registry上的服务名称。
spring.cloud.discovery.enabled - 表示是否打开发现服务,默认值为true。
spring.cloud.service-registry.auto-registration.enabled - 表示是否启动后自动注册,默认值为true。
注册可以使用Netflix-Eureka,也可以使用Zookeeper,以下使用Netflix-Eureka为例进行讲解。
application.properties配置:
server.port=8081
# http://localhost:8081/actuator/refresh
management.endpoints.web.exposure.include=refresh
# management.endpoints.jmx.exposure.exclude=*
# management.context-path=/baseeureka.client.enabled=true
eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka/eureka.client.register-with-eureka=true
eureka.client.fetch-registry=true# eureka.client.healthcheck.enabled=true
# eureka.instance.statusPageUrlPath=${management.context-path}/info
# eureka.instance.healthCheckUrlPath=${management.context-path}/health# eureka.instance.instanceId=${spring.application.name}:${random.int}
# hostname为此实例提供给外部调用的域名
eureka.instance.hostname=localhost
eureka.instance.leaseRenewalIntervalInSeconds=10
eureka.instance.leaseExpirationDurationInSeconds=20eureka.instance.metadataMap.versions=v1
management.endpoints.web.exposure.include - 表示可以放开给外部访问的方法,访问方式:http://localhost:8081/actuator/XXX,其中XXX表示实际调用的方法。比如:management.endpoints.web.exposure.include=refresh表示允许通过访问http://localhost:8081/actuator/refresh,访问结果为重新加载@RefreshScope范围内的配置信息。
eureka.client.enabled - 表示是否启用eureka client
eureka.client.serviceUrl.defaultZone - 表示eureka client可以访问的eureka server的地址
eureka.instance.hostname - 为此实例提供给外部调用的域名。
eureka.instance.leaseRenewalIntervalInSeconds - 心跳间隔。如果连续3次心跳都无回复,表示心跳超时。
eureka.instance.leaseExpirationDurationInSeconds - 心跳超时后,此服务会在eureka server的列表中保持多长时间。
eureka.instance.metadataMap.versions - eureka.instance.metadataMap中可以添加用户自定义的任意属性。
此处分为application.properties和bootstrap.properties两个配置文件,主要是为以后连接Config Server做准备,因为service server启动后必须先访问Config Server获取配置信息,然后才能初始化service server。
Java代码:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.task.TaskSchedulingAutoConfiguration;
import org.springframework.context.ConfigurableApplicationContext;
@SpringBootApplication(exclude = { TaskSchedulingAutoConfiguration.class })
public class AppBoot {
public static void main(String[] args) {
ConfigurableApplicationContext ctx = SpringApplication.run(AppBoot.class, args);
ctx.registerShutdownHook();
}
}
AppBoot负责启动整个应用
import java.util.concurrent.Executor;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.serviceregistry.ServiceRegistry;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
@Configuration
@EnableDiscoveryClient
@EnableAsync
public class AppConf {
private ServiceRegistry registry;
public AppConf(ServiceRegistry registry) {
this.registry = registry;
}
public ServiceRegistry getRegistry() {
return registry;
}
@Bean(name = "taskExecutor")
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);
executor.setMaxPoolSize(20);
executor.setQueueCapacity(200);
executor.setKeepAliveSeconds(60);
executor.setThreadNamePrefix("taskExec-");
// executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
return executor;
}
}
AppConf中获取ServiceRegistry,并生成一个线程池供以后使用。
import java.util.List;
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.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/appCtrl")
public class AppController {
@Autowired
private Registration registration;
@Autowired
private DiscoveryClient discoveryClient;
@Autowired
private AppConf configuration;
@RequestMapping("/register")
public void register() {
configuration.getRegistry().register(registration);
System.out.println("Host:" + registration.getHost());
System.out.println("Port:" + registration.getPort());
System.out.println("InstanceId:" + registration.getInstanceId());
System.out.println("ServiceId:" + registration.getServiceId());
System.out.println("Uri:" + registration.getUri());
}
@RequestMapping("/deregister")
public void deregister() {
configuration.getRegistry().deregister(registration);
}
@RequestMapping("/service-instances/{applicationName}")
public List<ServiceInstance> serviceInstancesByApplicationName(@PathVariable String applicationName) {
return this.discoveryClient.getInstances(applicationName);
}
}
registration本身也是一个service instance,它保存了当前Service Server的相关信息。discoveryClient可以获取Eureka Server上已注册的service instance。AppController可以注册此服务到Eureka Server,或从Eureka Server撤销此服务,也可以获取注册的服务信息。
应用启动后,就可以从Eureka Server的控制台上看到TestServer注册成功了。
关于service server自身的一些信息,可以直接访问http://localhost:8081/actuator中的一些命令获取。
如果不知道/actuator下面有哪些命令,可以通过访问http://localhost:8081/actuator获取所有可用的命令,返回结果为:
{"_links":{
"self":{"href":"http://localhost:8081/actuator","templated":false
"archaius":{"href":"http://localhost:8081/actuator/archaius","templated":false
"auditevents":{"href":"http://localhost:8081/actuator/auditevents","templated":false
"beans":{"href":"http://localhost:8081/actuator/beans","templated":false
"caches-cache":{"href":"http://localhost:8081/actuator/caches/{cache}","templated":true
"caches":{"href":"http://localhost:8081/actuator/caches","templated":false
"health":{"href":"http://localhost:8081/actuator/health","templated":false
"health-component-instance":{"href":"http://localhost:8081/actuator/health/{component}/{instance}","templated":true
"health-component":{"href":"http://localhost:8081/actuator/health/{component}","templated":true
"conditions":{"href":"http://localhost:8081/actuator/conditions","templated":false
"configprops":{"href":"http://localhost:8081/actuator/configprops","templated":false
"env":{"href":"http://localhost:8081/actuator/env","templated":false
"env-toMatch":{"href":"http://localhost:8081/actuator/env/{toMatch}","templated":true
"info":{"href":"http://localhost:8081/actuator/info","templated":false
"loggers":{"href":"http://localhost:8081/actuator/loggers","templated":false
"loggers-name":{"href":"http://localhost:8081/actuator/loggers/{name}","templated":true
"heapdump":{"href":"http://localhost:8081/actuator/heapdump","templated":false
"threaddump":{"href":"http://localhost:8081/actuator/threaddump","templated":false
"metrics":{"href":"http://localhost:8081/actuator/metrics","templated":false
"metrics-requiredMetricName":{"href":"http://localhost:8081/actuator/metrics/{requiredMetricName}","templated":true
"scheduledtasks":{"href":"http://localhost:8081/actuator/scheduledtasks","templated":false
"httptrace":{"href":"http://localhost:8081/actuator/httptrace","templated":false
"mappings":{"href":"http://localhost:8081/actuator/mappings","templated":false
"refresh":{"href":"http://localhost:8081/actuator/refresh","templated":false
"features":{"href":"http://localhost:8081/actuator/features","templated":false
"service-registry":{"href":"http://localhost:8081/actuator/service-registry","templated":false}
}}
再写一些业务代码,用于提供其它方法供调用:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.jjhgame.test.service.TestService;
@RefreshScope
@RestController
@RequestMapping("/base")
public class MessageRestController {
private static Logger log = LoggerFactory.getLogger(MessageRestController.class);
@Autowired
private TestService testService;
@Value("${message:Hello default}")
private String message;
@RequestMapping("/message")
String getMessage() {
String ret = this.message + ":" + System.currentTimeMillis();
String result = testService.getResult();
return result;
}
}
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import com.jjhgame.test.web.MessageRestController;
@Service
public class TestService {
private static Logger log = LoggerFactory.getLogger(MessageRestController.class);
@Async("taskExecutor")
public String getResult() {
String name = "" + Thread.currentThread().getName();
log.info("==============" + name);
return name;
}
}
每次调用由@RefreshScope注释的Bean时,Bean都会重新生成。如果启动时连接了Config Server,@RefreshScope范围中的message会在调用http://localhost:8081/actuator/refresh后,重新从Config Server中获取新的值。
三、测试客户端
Maven配置:
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>TestCloud</groupId>
<artifactId>TestCloud-Client</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>TestCloud-Client</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.3.RELEASE</version>
<relativePath />
</parent>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.SR1</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.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
application.properties配置:
spring.application.name=TestClient
server.port=7001
server.serviceName=TestServer
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=true
eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka/TestServer.ribbon.eureka.enabled=true
TestServer.ribbon.ServerListRefreshInterval=5#TestServer.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.DynamicServerListLoadBalancer
#TestServer.ribbon.NIWSServerListClassName=com.netflix.niws.loadbalancer.DiscoveryEnabledNIWSServerList
eureka.client.register-with-eureka - false 表示不将自身注册成为service server。
eureka.client.fetch-registry - true表示从eureka server上获取服务列表。
eureka.client.serviceUrl.defaultZone - 设置eureka server的访问地址。
TestServer.ribbon.eureka.enabled - 表示是否对TestServer服务开启ribbon组件。
TestServer.ribbon.ServerListRefreshInterval - 表示对TestServer服务开启ribbon组件后,从eureka server上获取最新服务可访问地址的时间间隔。
下面分别以RibbonClient和FeignClient两种形式给出示例代码:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
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.cloud.netflix.ribbon.RibbonClient;
import org.springframework.context.annotation.Bean;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@EnableDiscoveryClient
@SpringBootApplication
@RestController
@RibbonClient(name = "${server.serviceName}", configuration = MyConfiguration.class)
public class ClientApp {
@Value("${server.serviceName}")
private String serviceName;
@LoadBalanced
@Bean
RestTemplate restTemplate() {
return new RestTemplate();
}
@Autowired
RestTemplate restTemplate;
@RequestMapping("/hi")
public String hi() {
String greeting = this.restTemplate.getForObject("http://" + serviceName + "/base/message", String.class);
return String.format("%s!", greeting);
}
public static void main(String[] args) {
SpringApplication.run(ClientApp.class, args);
}
}
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
@EnableDiscoveryClient
@SpringBootApplication
@RestController
@EnableFeignClients
@RibbonClient(name = "${server.serviceName}", configuration = MyConfiguration.class)
public class FeignClientApp {
@Value("${server.serviceName}")
private String serviceName;
@Autowired
private MessageClient client;
@RequestMapping("/hihi")
public String message() {
return client.message();
}
@FeignClient(name = "${server.serviceName}")
interface MessageClient {
@RequestMapping(value = "/base/message", method = RequestMethod.GET)
String message();
}
public static void main(String[] args) {
SpringApplication.run(FeignClientApp.class, args);
}
}
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AvailabilityFilteringRule;
import com.netflix.loadbalancer.IPing;
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.PingUrl;
public class MyConfiguration {
@Autowired
IClientConfig ribbonClientConfig;
@Bean
public IPing ribbonPing(IClientConfig config) {
return new PingUrl(false, "/base/home");
}
@Bean
public IRule ribbonRule(IClientConfig config) {
return new AvailabilityFilteringRule();
}
}
通过以下两个地址,就可以测试一下代码运行结果:
http://localhost:7001/hihi http://localhost:7001/hi
另外,下表为Spring Cloud Netflix提供给Ribbon的默认值:
Bean Type | Bean Name | Class Name |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|