1、认识 Eureka
首先我们来解决第一问题,服务的管理。
问题分析
在刚才的案例中,user-service 对外提供服务,需要对外暴露自己的地址。而 consumer(调用者)需要记录服务提供者的地址。将来地址出现变更,还需要及时更新。这在服务较少的时候并不觉得有什么,但是在现在日益复杂的互联网环境,一个项目肯定会拆分出十几,甚至数十个微服务。此时如果还人为管理地址,不仅开发困难,将来测试、发布上线都会非常麻烦,这与 DevOps 的思想是背道而驰的。
网约车
这就好比是 网约车出现以前,人们出门叫车只能叫出租车。一些私家车想做出租却没有资格,被称为黑车。而很多人想要约车,但是无奈出租车太少,不方便。私家车很多却不敢拦,而且满大街的车,谁知道哪个才是愿意载人的。一个想要,一个愿意给,就是缺少引子,缺乏管理啊。
此时滴滴这样的网约车平台出现了,所有想载客的私家车全部到滴滴注册,记录你的车型(服务类型),身份信息(联系方式)。这样提供服务的私家车,在滴滴那里都能找到,一目了然。
此时要叫车的人,只需要打开APP,输入你的目的地,选择车型(服务类型),滴滴自动安排一个符合需求的车到你面前,为你服务,完美!
Eureka做什么?
Eureka 就好比是滴滴,负责管理、记录服务提供者的信息。服务调用者无需自己寻找服务,而是把自己的需求告诉 Eureka ,然后 Eureka 会把符合你需求的服务告诉你。
同时,服务提供方与 Eureka 之间通过“心跳”
机制进行监控,当某个服务提供方出现问题,Eureka 自然会把它从服务列表中剔除。
这就实现了服务的自动注册、发现、状态监控。
2、原理图
基本架构:
Eureka
:就是服务注册中心(可以是一个集群),对外暴露自己的地址提供者
:启动后向 Eureka 注册自己信息(地址,提供什么服务)消费者
:向Eureka订阅服务,Eureka 会将对应服务的所有提供者地址列表发送给消费者,并且定期更新心跳(续约)
:提供者定期通过 http 方式向 Eureka 刷新自己的状态
3、入门案例
编写EurekaServer
接下来我们创建一个项目,启动一个 EurekaServer:
完整的 pom
文件:
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>cloud-demo</artifactId>
<groupId>cn.ys.demo</groupId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>cn.ys.demo</groupId>
<artifactId>eureka-demo</artifactId>
<dependencies>
<!-- Eureka服务端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
</project>
编写启动类:
package cn.ys;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer // 声明这个应用是一个EurekaServer
public class EurekaServer {
public static void main(String[] args) {
SpringApplication.run(EurekaServer.class, args);
}
}
编写配置:
server:
port: 10086 # 端口
spring:
application:
name: eureka-server # 应用名称,会在 Eureka 中显示(例如,查看注册中心该 Application 名字就为:EUREKA-SERVER)
eureka:
client:
register-with-eureka: false # 是否注册自己的信息到 EurekaServer,默认是true
fetch-registry: false # 是否拉取其它服务的信息,默认是 true
service-url: # EurekaServer 的地址,现在是自己的地址,如果是集群,需要加上其它Server的地址。
defaultZone: http://127.0.0.1:${server.port}/eureka
instance:
prefer-ip-address: true # 希望使用 IP 地址,如果不使用 IP 地址,则,不需要加该配置
ip-address: 127.0.0.1 # 注册中心的 IP 地址
启动服务,并访问:http://127.0.0.1:10086/eureka
将 user-service
注册到 Eureka
注册服务,就是在服务上添加 Eureka 的客户端依赖,客户端代码会自动把服务注册到 EurekaServer 中。
我们在 user-service 中添加 Eureka 客户端依赖:
<!-- Eureka客户端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
在启动类上开启 Eureka 客户端功能
通过添加 @EnableDiscoveryClient
或 @EnableEurekaClient
来开启 Eureka 客户端功能
package cn.ys.user;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import tk.mybatis.spring.annotation.MapperScan;
@EnableDiscoveryClient // 开启 EurekaClient 功能
@SpringBootApplication
@MapperScan("cn.ys.user.mapper")
public class UserServiceApplication {
public static void main(String[] args) {
SpringApplication.run(UserServiceApplication.class,args);
}
}
@EnableEurekaClient
:只能使用 Eureka 作为注册中心@EnableDiscoveryClient
:不仅可以使用 Eureka 作为注册中心,可以使用 其他注册中心
编写配置
server:
port: 8081
spring:
application:
name: user-service # 应用名称
datasource:
url: jdbc:mysql://yangnanxi.cn:3306/springcloud
username: yang
password: 123456
hikari:
maximum-pool-size: 30
minimum-idle: 10
eureka:
client:
service-url: # EurekaServer地址
defaultZone: http://127.0.0.1:10086/eureka
instance:
prefer-ip-address: true # 当调用 getHostname 获取实例的 hostname 时,返回 ip 而不是 host 名称
ip-address: 127.0.0.1 # 指定自己的 ip 信息,不指定的话会自己寻找
注意:
- 这里我们添加了 spring.application.name 属性来指定应用名称,将来会作为应用的 id 使用。
- 不用指定 register-with-eureka 和 fetch-registry,因为默认是 true
重启项目,访问Eureka监控页面查看
我们发现 user-service 服务已经注册成功了
消费者从 Eureka 获取服务
接下来我们修改 consumer-demo,尝试从 EurekaServer 获取服务。
方法与消费者类似,只需要在项目中添加 EurekaClient 依赖,就可以通过服务名称来获取信息了!
1)添加依赖:
Eureka 客户端:
<!-- Eureka客户端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
2)在启动类开启 Eureka 客户端
package cn.ys;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@EnableDiscoveryClient // 开启Eureka客户端
@SpringBootApplication
public class ConsumerApplication {
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
}
3)修改配置:
server:
port: 8080
spring:
application:
name: consumer-demo # 应用名称
eureka:
client:
service-url: # EurekaServer 地址
defaultZone: http://127.0.0.1:10086/eureka
instance:
prefer-ip-address: true # 当其它服务获取地址时提供ip而不是hostname
ip-address: 127.0.0.1 # 指定自己的ip信息,不指定的话会自己寻找
4)修改 ConsumerController
代码,用 DiscoveryClient
类的方法,根据服务名称,获取服务实例:
package cn.ys.consumer.controller;
import cn.ys.consumer.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.util.List;
@RestController
@RequestMapping("consumer")
public class ConsumerController {
@Autowired
private RestTemplate restTemplate;
@Autowired
private DiscoveryClient discoveryClient;
@GetMapping("{id}")
public User queryById(@PathVariable("id") Long id ){
// String url = "http://localhost:8081/user/" + id;
// User user = restTemplate.getForObject(url, User.class);
// 根据服务名称,获取服务实例
List<ServiceInstance> instances = discoveryClient.getInstances("user-service");
// 因为只有一个UserService,因此我们直接get(0)获取
ServiceInstance instance = instances.get(0);
// 获取ip和端口信息
String url = "http://" + instance.getHost() + ":" +instance.getPort() + "/user/" + id;
User user = restTemplate.getForObject(url, User.class);
return user;
}
}
注意:
DiscoveryClient
导包为 :import org.springframework.cloud.client.discovery.DiscoveryClient;
,而不是import com.netflix.discovery.DiscoveryClient;
5)Debug 跟踪运行:
生成的 URL:
访问结果: