本篇介绍如何借助spring cloud / netflix eureka实现服务发现和服务调用,具体包含以下内容
- 创建eureka server,作为注册中心
- 创建一个叫做myservice1的服务,启动三个节点并注册到eureka中
- 创建一个服务消费者,注册到eureka,并通过服务发现来轮询调用myservice1的服务实例
本篇涉及到的组件包括:eureka, Ribbon, Feign等。
创建eureka server,作为注册中心
访问https://start.spring.io,在dependencies中选择eureka server和web,然后下载示例代码。
pom.xml中包含下面两个依赖
<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-web</artifactId>
</dependency>
修改application.yml
配置文件,增加几个必要的配置项
server:
port: 8761
eureka:
client:
registerWithEureka: false
fetchRegistry: false
server:
waitTimeInMsWhenSyncEmpty: 5
在启动类中增加@EnableEurekaServer
注解
package com.example.eurekaserver;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}
启动服务,访问http://localhost:8761,就可以看到eureka的控制台了。
如何确定client是否正确地注入到eureka-server中
通过访问eureka server的 /eureka/apps
这个地址,可以看到所有的服务。
通过访问eureka server的 /eureka/apps/{appID}
这个地址,可以看到某个服务的具体信息。
其中,hostName属性决定了其它客户度如何调用该服务。也就是说,如果hostName是IP地址,客户端就会通过ip地址来访问这个服务,如果是主机名或域名,客户端就会通过主机名或域名来访问这个服务。
创建一个叫做myservice1的服务,启动三个节点并注册到eureka中
访问https://start.spring.io,在dependencies中选择eureka client和web,然后下载示例代码。
pom.xml中包含下面两个依赖
<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-web</artifactId>
</dependency>
修改applicatin.yml
配置文件,增加必要的配置项
spring:
application:
name: myservice1
server:
port: 8000
eureka:
instance:
preferIpAddress: true
client:
registerWithEureka: true
fetchRegistry: true
serviceUrl:
defaultZone: http://localhost:8761/eureka/
上面指定了eureka的实例地址,并在启动后进行注册。
接下来编写一个controller,对外提供一个接口
@RestController
public class MyService {
@GetMapping("/service1")
public String methord1(HttpServletRequest request) {
String server = request.getServerName();
int port = request.getServerPort();
String instance = MessageFormat.format("{0}:{1}", server, String.valueOf(port));
return "this is result from " + instance;
}
}
使用mvn clean package
编译项目,然后通过下面的命令来启动三个服务示例
java -Dserver.port=8000 -jar target/myservice1-0.0.1-SNAPSHOT.jar
java -Dserver.port=8001 -jar target/myservice1-0.0.1-SNAPSHOT.jar
java -Dserver.port=8002 -jar target/myservice1-0.0.1-SNAPSHOT.jar
通过-Dserver.port=8000
的方式来指定服务启动的端口号,可以将这个服务启动多个实例。
三个服务实例启动后,会注册到eureka中,在eureka的控制台页面可以看到三个实例都是UP状态。
创建一个服务消费者
上面启动了myservice1的三个实例,并注册到了eureka中,接下来再创建一个服务,作为消费者来消费myservice1
访问https://start.spring.io,在dependencies中选择eureka client和web,然后下载示例代码。
修改applicatin.yml
配置文件,增加必要的配置项
spring:
application:
name: service-consumer
server:
port: 8000
eureka:
instance:
preferIpAddress: true
client:
registerWithEureka: true
fetchRegistry: true
serviceUrl:
defaultZone: http://localhost:8761/eureka/
使用Ribbon的方式
接下来,我们要利用netflix Ribbon的服务发现和客户端负载均衡机制,对myservice1进行调用,首先,使用@LoadBalanced
注解创建一个RestTemplate
实例,并使用该实例调用myservice1
@SpringBootApplication
public class EurekaClientApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaClientApplication.class, args);
}
@LoadBalanced
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
@RestController
public class MyController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("/call")
public String call() {
String url = "http://myservice1/service1";
ResponseEntity<String> responseEntity = restTemplate.getForEntity(url, String.class);
return responseEntity.getBody();
}
}
}
这里面比较神奇的就是@LoadBalanced
,只需要加这一个注解,就完成了服务发现和客户端负载均衡。
这里需要注意的是,启用了@LoadBalanced
的RestTemplate,会对url做特殊的解析,上面代码中的地址是http://myservice1/service1
,其中myservice1是服务的名字,后面是服务的接口路径,RestTemplate在发起http调用之前,会将服务名称替换为服务的物理地址,并且会轮询替换。启动它看下效果
$ curl http://localhost:8005/call
this is result from 192.168.1.6:8000
$ curl http://localhost:8005/call
this is result from 192.168.1.6:8002
$ curl http://localhost:8005/call
this is result from 192.168.1.6:8001
$ curl http://localhost:8005/call
this is result from 192.168.1.6:8000
可以看出myservice1的三个实例是被轮询调用的。
使用Feign的方式
除了Ribbon+RestTemplate的方式以外,还有一种基于netflix Feign的机制,简单来讲就是,开发人员定义一个接口,并指定一些描述信息,然后Feign会自动生成一个动态代理,来通过RestTemplate调用服务接口,其本质是对Ribbon+RestTemplate的一个封装。下面看一下简单的用法
首先,加入一个maven的依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
<version>1.4.7.RELEASE</version>
</dependency>
然后在启动类中启用Feign
@SpringBootApplication
@EnableFeignClients
public class EurekaClientApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaClientApplication.class, args);
}
...
}
接下来定义一个客户端接口类
@FeignClient("myservice1")
public interface MyService1Client {
@GetMapping("/service1")
String service1();
}
需要制定是哪个服务的客户端代理,并且将每个方法都映射到指定服务的接口上。
把这个接口注入到代码中并调用接口的方法,动态代理会自动通过RestTemplate来轮序地调用指定服务的接口
@Autowired
MyService1Client myServicde1Client;
@GetMapping("/call2")
public String call2() {
return myServicde1Client.service1();
}
他的效果和使用Ribbon的调用效果是一样的
客户端负载均衡
由于在配置文件中开启了client.fetchRegistry
,所以使用Ribbon会获取一份完整的服务注册清单,并且缓存在本地,每次调用服务时,无需每次都从注册中心查询完整的服务注册清单,但会定期从注册中心同步注册清单,目前看来应该是每分钟同步一次。
除此之外,如果服务的某个实例连续失败三次的话,则客户端的负载均衡策略就会把这个客户端实例忽略掉,不再调用它,直到从注册中心获取到该实例重新UP以后才会再次调用它。
有了以上两个能力,服务的上线和下线就无需人为干预了。