Eureka介绍
注册中心 : 每个服务都有自己的ip和端口,,一个服务调用另一个服务的时候都需要知道对方的ip,,,
Eureka类似 dubbo中的zookeeper
Eureka 是 netflix公司提供的一款服务注册中心,,,基于REST来实现服务的注册与发现,,,
Eureka两部分:
- 服务端: 注册中心,,用来接受其他服务的注册
- 客户端: 是一个java客户端,用来注册服务,并可以实现负载均衡等功能
eureka中有三个角色: eureka server,,provider,,consumer
Eureka搭建
Eureka是 使用 java开发的
引入spring-cloud-starter-netflix-eureka-server
spring cloud discovery
使用@EnableEurekaServer
@SpringBootApplication
// 开启 eureka server 功能
@EnableEurekaServer
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class, args);
}
}
配置文件:
spring:
application:
# 给当前服务取名字
name: eureka
server:
port: 1111
# 两层身份 : 1.注册中心 2.普通服务,,,, 即当前服务会 自己把自己注册进来
eureka:
client:
# 当前项目 不注册自己
register-with-eureka: false
# 是否从 eureka server 上获取注册信息
fetch-registry: false
端口: 一个后台管理的端口,,一个通讯的端口
Eureka集群
服务注册中心的稳定 非常重要,需要集群
实际开发中Eureka都是以集群的形式出现的
修改hosts 文件 C:\Windows\System32\drivers\etc
Eureka集群 : 就是启动多个 Eureka实例,,相互注册,,相互同步数据,共同组成一个Eureka集群
Eureka Server作用:
- 服务注册 : 所有的服务都注册到 Eureka server中,,Eureka client向 Eureka server 注册的时候,需要提供自身的一些元数据信息(ip地址,端口,名称,运行状态等)
- 提供注册表 : Eureka client 在调用服务时,需要获取这个注册表,,一般来说,这个注册表会缓存下来,如果缓存失效,会直接获取新的注册表
- 同步状态 :通过注册,心跳等机制,,和Eureka Server 同步客户端状态
Eureka Client作用:
-
获取注册表信息: eureka client会自动 拉取,更新以及缓存 Eureka server 的信息,,这样即使 Eureka Server 宕机,,Eureka client 依然能够获取到想要调用服务的地址(地址可能不准确),这个注册表会定期更新
# 是否允许获取注册表信息 eureka.client.fetch-registry=true
# 定期更新注册表 时间,,默认30s eureka.client.registry-fetch-interval-seconds=30
-
服务续约: Eureka client 每隔 30s 会向 Eureka Server 发送一条心跳消息,,来告诉Eureka server,我还在运行,,,如果Eureka Server 连续 90s 都没有收到 Eureka client 的续约消息,,认为这个Eureka client 已经掉线,,会将 eureka client 将
服务注册列表
中剔除 -
服务下线: 当Eureka client 下线时,,他会主动发送一条消息,告诉Eureka server
自我保护机制: 防止误杀,,,当Eureka 捕获到大量的心跳失败的时候,则认为可能是网络问题,,进入自我保护机制,,当客户端心跳恢复正常的时候,会自动退出自我保护机制
配置: application-a.yml
spring:
application:
# 给当前服务取名字
name: eureka
server:
port: 1111
# 两层身份 : 1.注册中心 2.普通服务,,,, 即当前服务会 自己把自己注册进来
eureka:
client:
# 当前项目 不注册自己
register-with-eureka: true
# 是否从 eureka server 上获取注册信息
fetch-registry: true
service-url:
defaultZone: http://eurekaB:1112/eureka
instance:
# 给服务取个别名,,注册到Eureka
hostname: eurekaA
application-b.yml
spring:
application:
# 给当前服务取名字
name: eureka
server:
port: 1112
# 两层身份 : 1.注册中心 2.普通服务,,,, 即当前服务会 自己把自己注册进来
eureka:
client:
# 当前项目 不注册自己
register-with-eureka: true
# 是否从 eureka server 上获取注册信息
fetch-registry: true
service-url:
defaultZone: http://eurekaA:1111/eureka
instance:
hostname: eurekaB
打包 java -jar xxx.jar --spring.profiles.active=a
Eureka集群:
通过replicate 进行数据同步
不同的Eureka server ,,不区分 主从节点,,所有的节点都是平等的,,通过指定 service 的url 进行相互注册,,
如果有某一个节点宕机,,Eureka client 会自动切换到 新的Eureka server 上
Eureka server 的连接方式 可以是单线的 a-->b--->c
,a连接b,b连接c,,a和c之间不用直接连,,但是一般不建议这种写法,一旦b宕机,a和c就不能同步,,,在配置时,可以配置多个注册地址,用,
分开
Eureka分区::
- region :地理上的不同区域
- zone : 具体的机房
在同一个分区里面的 client 和 同一个分区里面的 server 会优先进行 心跳同步
Eureka client 使用
简单使用
客户端starter : spring-cloud-starter-netflix-eureka-client
配置文件:
spring:
application:
# 服务名字
name: provider
server:
port: 1113
eureka:
client:
service-url:
# 注册地址
defaultZone: http://localhost:1111/eureka
使用DiscoveryClient
可以获取客户端的信息(ip,端口等)
DiscoveryClient 根据 服务名
,,查询到 服务的详细信息
DiscoveryClient查询到的服务列表是一个集合
,因为服务可能是集群化部署
@RestController
public class UseHelloController {
@GetMapping("/hello")
public String hello() throws IOException {
// java????????????? 代码写死了,,只能固定一个连接
HttpURLConnection connection = null;
URL url = new URL("http://localhost:1113/hello");
connection = (HttpURLConnection) url.openConnection();
// 如果这个连接相应200
if (connection.getResponseCode()==200){
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String s = reader.readLine();
reader.close();
return s;
}
return "error";
}
@Autowired
DiscoveryClient discoveryClient;
@GetMapping("/hello2")
public String hello2() throws IOException {
// serviceId: 你要调用服务的名字
// provider 可能是集群,,
List<ServiceInstance> providerList = discoveryClient.getInstances("provider");
// 获取服务
ServiceInstance instance = providerList.get(0);
String host = instance.getHost();
int port = instance.getPort();
HttpURLConnection con = null;
StringBuilder sb = new StringBuilder("http://");
sb.append(host).append(":").append(port).append("/hello");
URL url = new URL(sb.toString());
// 连接
con = (HttpURLConnection) url.openConnection();
if (con.getResponseCode() ==200){
BufferedReader reader = new BufferedReader(new InputStreamReader(con.getInputStream()));
return reader.readLine();
}
return "error";
}
}
provider代码: 将port写进去
@RestController
public class HelloController {
@Value("${server.port}")
Integer port;
@GetMapping("/hello")
public String hello(){
return "hello :"+port;
}
}
将provider打包,,运行两个provider实例:
java -jar provider-0.0.1-SNAPSHOT.jar --server.port=1116
设置一个count,,让consumer 线性负载均衡
int count = 0;
@GetMapping("/hello3")
public String hello3() throws IOException {
// serviceId: 你要调用服务的名字
// provider 可能是集群,,
List<ServiceInstance> providerList = discoveryClient.getInstances("provider");
// 获取服务 取余,,线性负载均衡
ServiceInstance instance = providerList.get((count++)%providerList.size());
String host = instance.getHost();
int port = instance.getPort();
HttpURLConnection con = null;
StringBuilder sb = new StringBuilder("http://");
sb.append(host).append(":").append(port).append("/hello");
URL url = new URL(sb.toString());
// 连接
con = (HttpURLConnection) url.openConnection();
if (con.getResponseCode() ==200){
BufferedReader reader = new BufferedReader(new InputStreamReader(con.getInputStream()));
return reader.readLine();
}
return "error";
}
升级改造
- 使用 restTemplate 发送请求
代码:
@Bean // 没有Ribbon的Bean。。。有Ribbon的bean会将请求拦截下来解析服务名字
RestTemplate restTemplateOne(){
return new RestTemplate();
}
@GetMapping("/hello4")
public String hello4() {
List<ServiceInstance> providerList = discoveryClient.getInstances("provider");
ServiceInstance instance = providerList.get((count++)%providerList.size());
String host = instance.getHost();
int port = instance.getPort();
StringBuilder sb = new StringBuilder("http://");
sb.append(host).append(":").append(port).append("/hello");
String result = restTemplate.getForObject(sb.toString(), String.class);
return result;
}
- 使用Ribbon负载均衡
代码:
@Bean
// 开启负载均衡 ,此时的restTemplate 就自动具备了负载均衡的功能
@LoadBalanced
RestTemplate restTemplateOne(){
return new RestTemplate();
}
@GetMapping("/hello5")
public String hello5() {
// 直接给一个模糊的地址,,provider,, 这个请求会被拦截下来,解析provider,,会利用DiscoveryClient从里面提取出来provider,,
// 查出来在根据你本地配好的负载均衡策略,,选一个服务出来,拼接出来地址
String result = restTemplate.getForObject("http://provider/hello", String.class);
return result;
}