1. Eureka 两大组件
Eureka 采用 CS(Client/Server,客户端/服务器) 架构,它包括以下两大组件:
服务注册,服务提供者将自己注册到服务注册中心
服务续约,注册成功后,默认情况下,Eureka CLient 每隔 30 秒就要向 Eureka Server 发送一条心跳消息。如果 Eureka Server 连续 90 秒都有没有收到 Eureka Client 的续约消息,它会认为 Eureka Client 已经掉线了,会从当前的服务注册列表中剔除。
# 服务续约时间 30s
eureka.instance.lease-renewal-interval-in-seconds=30
# 服务失效时间 90s
eureka.instance.lease-expiration-duration-in-seconds=90
服务下线,当 Eureka Client 下线时,它会主动发送一条消息,告诉 Eureka Server
获取注册表信息,Eureka Client 从 Eureka Server 上获取服务的注册信息,并将其缓存在本地
# 是否允许获取注册表信息
eureka.client.fetch-registry=true
# 定期更新注册表的时间间隔,默认 30 秒
eureka.client.registry-fetch-interval-seconds=30
1.创建一个普通的 Spring Boot 项目,创建时,勾选 Eureka Server依赖
>
>org.springframework.cloud >
>spring-cloud-starter-netflix-eureka-server >
>
2.在项目启动类上添加 @EnableEurekaServer 注解,标记该项目是一个 Eureka Server
@SpringBootApplication
@EnableEurekaServer
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class, args);
}
}
3.在 application.properties 中添加基本配置信息
# 设置服务名称
spring.application.name=eureka
# 设置端口号
server.port=1111
# 默认情况下,Eureka Server 也是一个普通的微服务,自己也会注册进来
# register-with-eureka 设置为 false,表示当前项目不要注册到注册中心上
eureka.client.register-with-eureka=false
# 表示是否从 Eureka Server 上获取注册信息
eureka.client.fetch-registry=false
项目启动成功后,浏览器输入 http://localhost:1111 就可以查看 Eureka 后台管理页面了:
使用了注册中心之后,所有的服务都要通过服务注册中心来进行信息交换。要保证注册中心的稳定性,Eureka 一般都是以集群的形式出现的。Eureka 集群,实际上就是启动多个 Eureka 实例,多个 Eureka 实例之间,互相注册,互相同步数据,共同组成一个 Eureka 集群。
1.修改电脑的 hosts 文件
127.0.0.1 eurekaA eurekaB
2.新增两个eureka实例,为了方便,这里新增两个配置文件,再以jar包形式分别启动
application-a.properites 内如如下:
# 设置服务名称
spring.application.name=eureka
# 设置端口号
server.port=1111
eureka.instance.hostname=eurekaA
# 默认情况下,Eureka Server 也是一个普通的微服务,自己也会注册进来
# register-with-eureka 设置为 false,表示当前项目不要注册到注册中心上
eureka.client.register-with-eureka=true
# 表示是否从 Eureka Server 上获取注册信息
eureka.client.fetch-registry=true
# A 服务要注册到 B 上面
eureka.client.service-url.defaultZone=http://eurekaB:1112/eureka
application-b.properites 内如如下:
# 设置服务名称
spring.application.name=eureka
# 设置端口号
server.port=1112
eureka.instance.hostname=eurekaB
# 默认情况下,Eureka Server 也是一个普通的微服务,自己也会注册进来
# register-with-eureka 设置为 false,表示当前项目不要注册到注册中心上
eureka.client.register-with-eureka=true
# 表示是否从 Eureka Server 上获取注册信息
eureka.client.fetch-registry=true
# B 服务要注册到 A 上面
eureka.client.service-url.defaultZone=http://eurekaA:1111/eureka
3.对当前项目打包,打成 jar 包,分别启动
java -jar eureka-0.0.1-SNAPSHOT.jar --spring.profiles.active=a
java -jar eureka-0.0.1-SNAPSHOT.jar --spring.profiles.active=b
启动成功后,浏览器访问 http://127.0.0.1:1111/,就可以看到,两个服务之间互相注册,共同给组成一个集群。
在这个集群架构中,Eureka Server 之间通过 Replicate 进行数据同步,不同的 Eureka Server 之间不区分主从节点,所有节点都是平等的。节点之间,通过置顶 serviceUrl 来互相注册,形成一个集群,进而提高节点的可用性。
在 Eureka Server 集群中,如果有某一个节点宕机,Eureka Client 会自动切换到新的 Eureka Server 上。每一个 Eureka Server 节点,都会互相同步数据。
服务注册就是把一个微服务注册到 Eureka Server 上,以传统springboot项目为例,添加Web 和 Eureka Discovery Client依赖
>
>org.springframework.cloud >
>spring-cloud-starter-netflix-eureka-client >
>
在 application.properties 中配置一下项目的注册地址
# provider 配置
spring.application.name=provider
server.port=1113
eureka.client.service-url.defaultZone=http://localhost:1111/eureka
启动 Eureka Server,待服务注册中心启动成功后,再启动 provider。
两者都启动成功后,浏览器输入 http://localhost:1111,就可以查看到 provider 的注册信息
在 provider 中,提供一个 hello 接口
@RestController
public class HelloController {
@GetMapping("/hello")
public String hello() {
return "hello javaboy";
}
}
创建一个 consumer 项目,添加Web 和 Eureka Discovery Client依赖
# 消费者 consumer 配置
spring.application.name=consumer
server.port=1115
eureka.client.service-url.defaultZone=http://localhost:1111/eureka
consumer 中消费 provider 提供的接口:
@Bean
@LoadBalanced
RestTemplate restTemplate() {
return new RestTemplate();
}
@Autowired
RestTemplate restTemplate;
@GetMapping("/hello2")
public String hello2() {
return restTemplate.getForObject("http://provider/hello", String.class);
}
注意,上述代码中,restTemplate.getForObject(“http://provider/hello”, String.class);第一个参数URL,是服务名称而不是具体的服务地址。使用RestTemplate,其实是封装了服务查询和URL拼接的过程。
// 根据服务名查询服务的详细信息
Listlist = discoveryClient.getInstances(“provider”);
// 手动实现负载均衡 (count++) % list.size()
ServiceInstance instance = list.get((count++) % list.size());
RestTemplate 是从 Spring3.0 开始支持的一个 Http 请求工具,提供了常见的 REST 请求方法模板,例如 GET、POST、PUT、DELETE 请求以及一些通用的请求执行方法 exchange 和 execute 方法。
GET 请求
POST 请求
一定要写成绝对路径
,不要写相对路径,否则在 consumer 中调用时会出问题PUT 请求
可接受两种类型的参数,key/value 形式以及 JSON 形式。
DELETE 请求
有两种方式来传递参数,key/value 形式或者 PathVariable(参数放在路径中)
restTemplate.delete("http://provider/user1?id={1}", 99);
restTemplate.delete("http://provider/user2/{1}", 99);
在 RestTemplate 中,要想使用负载均衡功能,只需要给 RestTemplate 实例上添加 @LoadBalanced注解即可,此时,RestTemplate 就会自动具备负载均衡功能,这个负载均衡就是客户端负载均衡。
整体上来说,这个功能的实现就是三个核心点:
RibbonLoadBalancerClient部分源码如下:
public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint) throws IOException {
//1.加载服务列表
ILoadBalancer loadBalancer = this.getLoadBalancer(serviceId);
//2.选择一个服务
Server server = this.getServer(loadBalancer, hint);
if (server == null) {
throw new IllegalStateException("No instances available for " + serviceId);
} else {
RibbonLoadBalancerClient.RibbonServer ribbonServer = new RibbonLoadBalancerClient.RibbonServer(serviceId, server, this.isSecure(server, serviceId), this.serverIntrospector(serviceId).getMetadata(server));
return this.execute(serviceId, (ServiceInstance)ribbonServer, (LoadBalancerRequest)request);
}
}
//3.重构URI
public URI reconstructURI(ServiceInstance instance, URI original) {
Assert.notNull(instance, "instance can not be null");
String serviceId = instance.getServiceId();
RibbonLoadBalancerContext context = this.clientFactory.getLoadBalancerContext(serviceId);
URI uri;
Server server;
if (instance instanceof RibbonLoadBalancerClient.RibbonServer) {
RibbonLoadBalancerClient.RibbonServer ribbonServer = (RibbonLoadBalancerClient.RibbonServer)instance;
server = ribbonServer.getServer();
uri = RibbonUtils.updateToSecureConnectionIfNeeded(original, ribbonServer);
} else {
server = new Server(instance.getScheme(), instance.getHost(), instance.getPort());
IClientConfig clientConfig = this.clientFactory.getClientConfig(serviceId);
ServerIntrospector serverIntrospector = this.serverIntrospector(serviceId);
uri = RibbonUtils.updateToSecureConnectionIfNeeded(original, clientConfig, serverIntrospector, server);
}
return context.reconstructURIWithServer(server, uri);
}