微服务架构风格图

Eureka简介
注册发现中心
Eureka来源于古希腊词汇,意为“发现了”。在软件领域, Eureka是Netflix在线影片公司开源的一个服务注册与发现的组件,和其他Netflix 公司的服务组件(例如负载均衡、熔断器、网关等)一起,被Spring Cloud社区整合为Spring Cloud Netflix模块。Eureka是Netflix贡献给Spring Cloud的一个框架! Netflix给Spring cloud 贡献了很多框架。
分布式微服务-----CAP原则
一致性(Consistency):服务器中数据保持一致
可用性(Availability):当一个服务器挂掉时,集群中其他服务器可以继续使用,对外提供服务
分区容错性(Partition tolerance):允许各个原因导致的各个服务器中的数据短暂不同---必须满足
CAP原则指出以上三个要素只能同时实现两点,三者不能同时满足
Eureka为AP(数据可能不一致但是拥有高可用性)
zookeeper为CP(数据一致但是有服务器宕机时整个服务会有几分钟不能提供服务)
Eureka快速入门
两种Eureka
1.搭建一个注册中心,提供注册的服务----Eureka-server
2.创建客户端,继续注册----Eureka-client
注意:Eureka-server可以自己注册自己
搭建注册中心
创建一个Eureka依赖的SpringCloud项目
Eureka Server依赖本身提供了web服务

在pom文件中修改SpringBoot和SpringCloud依赖的版本

编辑配置文件
server:
port: 8761 #Eureka 的默认端口是8761
spring:
application:
name: eureka-server-a #自定义应用名称,一般使用 - 不要使用特殊字符在主启动文件上加上注解@EnableEurekaServer
package com.ys;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer //启动Eureka服务
public class EurekaServerAApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerAApplication.class, args);
}
}
启动项目,在网页打开

一个应用可以拥有多个实例

搭建客户端
选择依赖,Eureka Discovery Client 不包含web依赖所以要单独加入

在pom文件中修改SpringBoot和SpringCloud依赖的版本

编辑配置文件
server:
port: 8080 #客户端的端口没有要求
spring:
application:
name: eureka-client-a
#注册的含义是指将自己的信息(ip,端口(port)等等)发送到(某个地方)
eureka:
client:
service-url: #指定注册地址
defaultZone: http://localhost:8761/eureka #将信息发送到该地址 注意defaultZone没有提示
在主启动类上添加注解@EnableEurekaClient
package com.ys;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@EnableEurekaClient //启动客户端服务
public class EurekaClientAApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaClientAApplication.class, args);
}
}
打开网页,查看实例列表,查看是否注册成功

安装相同的方法再创建一个客户端
最后结果如下

同一个应用创建多个实例
因为前面创建的客户端的例子没有写入其他东西,使用进行复制修改端口即可达到目的
将任意一个客户端的启动文件进行复制
并使用 --server.port:端口号 进行端口的修改 并启动

查看网页结果

配置文件详解
注册中心应该考虑的问题


服务端(注册中心)配置文件详解
server:
port: 8761 #Eureka 的默认端口是8761
spring:
application:
name: eureka-server-a #自定义应用名称,一般使用 - 不要使用特殊字符
# eureka的三大配置 server 服务端 instance 实例 client 客户端
eureka:
server:
eviction-interval-timer-in-ms: 10000 #服务端间隔多少毫秒进行定期删除的操作,每过一定的时间客户端没有响应将被剔除
renewal-percent-threshold: 0.85 #续约(心跳机制)百分比 规定时间内超过百分之八十五的应用没有和服务端续约 那么Eureka的保护机制将启动不会剔除任何一个客户端
instance:
instance-id: ${eureka.instance.hostname}:${spring.application.name}:${server.port} #定义Eureka中实例名称(主机名:应用名称:端口号)
hostname: localhost #定义主机名称
prefer-ip-address: true #是否已ip的形式显示具体的服务信息
lease-renewal-interval-in-seconds: 5 #客户端的续约时间间隔(秒),这个时间要小于服务端的定期删除的时间
以ip显示服务信息
![]()
控制台(服务端)每十秒刷新定期删除

客户端文件配置详解
server:
port: 8080 #客户端的端口没有要求
spring:
application:
name: eureka-client-a
#注册的含义是指将自己的信息(ip,端口(port)等等)发送到(某个地方)
eureka: #客户端相关配置
client:
service-url: #指定注册地址
defaultZone: http://localhost:8761/eureka #将信息发送到该地址 注意defaultZone没有提示
register-with-eureka: true # 是否往Eureka-Server 中注册
fetch-registry: true #应用是否从注册中心拉取服务列表到本地缓存
registry-fetch-interval-seconds: 10 #为了缓解服务列表的脏读问题,设置每过多少秒就从注册中心重新拉取列表,时间越短脏读越少,但是系统资源消耗越多
instance:
hostname: localhost #应用主机名称
instance-id: ${eureka.instance.hostname}:${spring.application.name}:${server.port} #实例名称
lease-renewal-interval-in-seconds: 5 #续约时间
prefer-ip-address: true #以ip形式显示服务信息
Eureka集群
集群的方案
中心化集群(主从模式集群):有一个核心服务器,当这个服务器无法使用时,整个集群也无法使用
去中心化模式集群:没有核心服务器(没有主从概念),更加的高可用。当某一台服务器接收到数据等,该服务器会将数据扩散和广播到其他服务器中去,保持一致。 Eureka就是这种模式。
创建一个集群
利用与前面相同的方法,再次创建俩个服务器,并修改pom文件中版本

在主启动类上添加@EnableEurekaServer

并修改配置文件,使的服务端的Eureka相互注册,三个服务器的配置文件大致相同,只需要修改端口号和注册地址
# 集群的配置
server:
port: 8763 #Eureka 的默认端口是8761
spring:
application:
name: eureka-server #自定义应用名称,一般使用 - 不要使用特殊字符 一个集群的服务器应用名称相同
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka,http://localhost:8762/eureka
instance:
instance-id: ${eureka.instance.hostname}:${spring.application.name}:${server.port} #定义Eureka中实例名称(主机名:应用名称:端口号)
hostname: localhost #定义主机名称
prefer-ip-address: true #是否已ip的形式显示具体的服务信息
lease-renewal-interval-in-seconds: 5 #客户端的续约时间间隔(秒),这个时间要小于服务端的定期删除的时间
启动三个服务端,三个端口号的网页内容一致


到目前为止,这不能算是集群,网页中没有出现集群信息,系统只是认为同一个服务启动了多台,没有数据交互,不是真正意义上的集群。因为只是修改了端口,所以Eureka只会认为只有一个机器,所以这里需要修改hosts文件(网络映射文件)。
在hosts文件中添加红色方框的内容,作用是peer1,peer2,peer3 为不同名称但是代表的ip地址相同,这样就可以做到欺骗Eureka,使其认为有三台服务器,分别为peer1,peer2,peer3

再去修改配置文件,三个文件都是这样
# 集群的配置
server:
port: 8761 #Eureka 的默认端口是8761
spring:
application:
name: eureka-server #自定义应用名称,一般使用 - 不要使用特殊字符 一个集群的服务器应用名称相同
eureka:
client:
service-url:
defaultZone: http://peer2:8762/eureka,http://peer3:8763/eureka
instance:
instance-id: ${eureka.instance.hostname}:${spring.application.name}:${server.port} #定义Eureka中实例名称(主机名:应用名称:端口号)
hostname: peer1 #定义主机名称
prefer-ip-address: true #是否已ip的形式显示具体的服务信息
lease-renewal-interval-in-seconds: 5 #客户端的续约时间间隔(秒),这个时间要小于服务端的定期删除的时间
全部修改后,重新启动三个服务端
http://peer1:8761/ ----网页链接输入localhost或者peer2、peer3都是可以访问的,因为在hosts文件中,这几个名称代表的ip地址相同
再次进入网页后可以看到DS Replicas中多出了其他两个服务器,这就形成了真正意义上的集群

修改客户端的注册地址
server:
port: 8080 #客户端的端口没有要求
spring:
application:
name: eureka-client-a
#注册的含义是指将自己的信息(ip,端口(port)等等)发送到(某个地方)
eureka: #客户端相关配置
client:
service-url: #指定注册地址
defaultZone: http://peer1:8761/eureka #将信息发送到该地址 注意defaultZone没有提示
register-with-eureka: true # 是否往Eureka-Server 中注册
fetch-registry: true #应用是否从注册中心拉取服务列表到本地缓存
registry-fetch-interval-seconds: 10 #为了缓解服务列表的脏读问题,设置每过多少秒就从注册中心重新拉取列表,时间越短脏读越少,但是系统资源消耗越多
instance:
hostname: localhost #应用主机名称
instance-id: ${eureka.instance.hostname}:${spring.application.name}:${server.port} #实例名称
lease-renewal-interval-in-seconds: 5 #续约时间
prefer-ip-address: true #以ip形式显示服务信息
启动客户端,在服务端观察,此客户端只在一个服务端中注册,最后可以在三个服务端中观察到,说明了Eureka集群是去中心化模式,服务器之间是可以扩散和广播的。

当集群中某一个服务器下线,其他服务器是可以正常运行的,不会受到影响。这就是Eureka的AP原则,高可用性。
简化服务端的配置,往三个服务器中都进行注册,不需要定义hostname,且instance-id也只需要应用名称和端口号了,主机名称在注册地址中系统会根据端口号找到相应的名称。
# 集群的配置
server:
port: 8761 #Eureka 的默认端口是8761
spring:
application:
name: eureka-server #自定义应用名称,一般使用 - 不要使用特殊字符 一个集群的服务器应用名称相同
eureka:
client:
service-url:
defaultZone: http://peer2:8762/eureka,http://peer3:8763/eureka,http://peer1:8762/eureka,
instance:
instance-id:${spring.application.name}:${server.port}
# hostname: peer1 #定义主机名称
prefer-ip-address: true #是否已ip的形式显示具体的服务信息
lease-renewal-interval-in-seconds: 5 #客户端的续约时间间隔(秒),这个时间要小于服务端的定期删除的时间
此时客户端需要往三个服务器中同时注册,防止唯一进行的注册的服务端下线导致无法注册,提高了注册的成功几率
server:
port: 8080 #客户端的端口没有要求
spring:
application:
name: eureka-client-a
#注册的含义是指将自己的信息(ip,端口(port)等等)发送到(某个地方)
eureka: #客户端相关配置
client:
service-url: #指定注册地址
defaultZone: http://peer1:8761/eureka,http://peer2:8762/eureka,http://peer3:8763/eureka #将信息发送到该地址 注意defaultZone没有提示
register-with-eureka: true # 是否往Eureka-Server 中注册
fetch-registry: true #应用是否从注册中心拉取服务列表到本地缓存
registry-fetch-interval-seconds: 10 #为了缓解服务列表的脏读问题,设置每过多少秒就从注册中心重新拉取列表,时间越短脏读越少,但是系统资源消耗越多
instance:
hostname: localhost #应用主机名称
instance-id: ${eureka.instance.hostname}:${spring.application.name}:${server.port} #实例名称
lease-renewal-interval-in-seconds: 5 #续约时间
prefer-ip-address: true #以ip形式显示服务信息
分布式数据一致协议----Paxos raft
Raft 可以在网页上观看官方的动画,深入了解协议和机制
zookeeper 是Paxos
Eureka 没有分布式数据的一致性的机制,节点都是相同的
nacos 是raft
在主从模式的集群中,一般都需要遵守这样的协议 才可以稳定的对外提供服务
Eureka概念的理解
服务的注册
当项目启动时((eureka的客户端),就会向eureka-server发送自己的元数据(原始数据)(运行的ip,端口port,健康的状态监控等,因为使用的是http/ResuFul请求风格),eureka-server 会在自己内部保留这些元数据(内存中)。(有一个服务列表)(restful风格,以http.动词的请求方式,完成对url资源的操作)
服务的续约
项目启动成功了,除了向eureka-server注册自己成功,还会定时的向eureka-server汇报自己,心跳,表示自己还活着。(修改一个时间)
服务的下线(主动下线)
当项目关闭时,会给eureka-server报告,说明自己要下机了。
服务的剔除(被动下线,主动剔除)
当项目超过了指定时间没有向eureka-server汇报自己,那么eureka-server就会认为此节点死掉了,会把它剔除掉,也不会放流量和请求到此节点了。
服务发现
通过应用名找到该应用的注册信息

修改配置文件,Eureka-Server-A
server:
port: 8761 #Eureka 的默认端口是8761
spring:
application:
name: eureka-server-a #自定义应用名称,一般使用 - 不要使用特殊字符
# eureka的三大配置 server 服务端 instance 实例 client 客户端
eureka:
server:
eviction-interval-timer-in-ms: 10000 #服务端间隔多少毫秒进行定期删除的操作,每过一定的时间客户端没有响应将被剔除
renewal-percent-threshold: 0.85 #续约(心跳机制)百分比 规定时间内超过百分之八十五的应用没有和服务端续约 那么Eureka的保护机制将启动不会剔除任何一个客户端
instance:
instance-id: ${eureka.instance.hostname}:${spring.application.name}:${server.port} #定义Eureka中实例名称(主机名:应用名称:端口号)
hostname: localhost #定义主机名称
prefer-ip-address: true #是否已ip的形式显示具体的服务信息
lease-renewal-interval-in-seconds: 5 #客户端的续约时间间隔(秒),这个时间要小于服务端的定期删除的时间
client:
service-url:
defaultZone: http://localhost:8761/eureka
fetch-registry: true #拉取缓存注册中心的实例列表
register-with-eureka: false#将服务端自己注册自己默认关闭开关自由Eureka-Client-A配置文件
server:
port: 8080 #客户端的端口没有要求
spring:
application:
name: eureka-client-a
#注册的含义是指将自己的信息(ip,端口(port)等等)发送到(某个地方)
eureka: #客户端相关配置
client:
service-url: #指定注册地址
defaultZone: http://localhost:8761/eureka #将信息发送到该地址 注意defaultZone没有提示
register-with-eureka: true # 是否往Eureka-Server 中注册
fetch-registry: true #应用是否从注册中心拉取服务列表到本地缓存
registry-fetch-interval-seconds: 10 #为了缓解服务列表的脏读问题,设置每过多少秒就从注册中心重新拉取列表,时间越短脏读越少,但是系统资源消耗越多
instance:
hostname: localhost #应用主机名称
instance-id: ${eureka.instance.hostname}:${spring.application.name}:${server.port} #实例名称
lease-renewal-interval-in-seconds: 5 #续约时间
prefer-ip-address: true #以ip形式显示服务信息
Eureka-Client-B配置文件
server:
port: 8081
spring:
application:
name: eureka-client-b
eureka:
client:
service-url: #指定注册地址
defaultZone: http://localhost:8761/eureka
instance:
prefer-ip-address: true
hostname: localhost
instance-id: ${eureka.instance.hostname}:${spring.application.name}:${server.port}
lease-renewal-interval-in-seconds: 10 #续约时间
修改后启动Eureka-Client-B、Eureka-Server-A服务

在Eureka-Client-A编写代码,创建controller包下DiscoveryController.java,获取信息
package com.ys.controller;
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.RestController;
import java.util.List;
@RestController
public class DiscoveryController {
@Autowired
private DiscoveryClient discoveryClient; //SpringCloud提供的接口
@GetMapping("/test")
public String doDiscovery(String serviceName){
// 寻找Eureka-Client-B的ip和port 服务发现,找到服务的具体信息
List<ServiceInstance> instances=discoveryClient.getInstances(serviceName);
// List<ServiceInstance>是集合,一个服务可能有多个实例
// 对 List<ServiceInstance>集合进行遍历
instances.forEach(System.out::println);
// 取出第一个值
ServiceInstance serviceInstance=instances.get(0);
// 获取ip地址
String ip=serviceInstance.getHost();
// 获取端口号
int port=serviceInstance.getPort();
// 输出ip和端口
System.out.println("ip:"+ip+"port:"+port);
// 返回 List<ServiceInstance>集合中的一个值 get(元素下标)
return instances.get(0).toString();
}
}
启动Eureka-Client-A

修改网址为http://localhost:8080/test?serviceName=eureka-client-b
获取Eureka-Client-B的注册信息,并在控制台返回信息,单独返回ip和port


RestTemplate
客户端工具,可以发送不同类型的请求。
创建一个只有web依赖的springboot项目
在测试类中编辑
package com.ys;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.web.client.RestTemplate;
@SpringBootTest
class RestTemplateApplicationTests {
@Test
void contextLoads() {
// 在java代码中去发送一个请求 请求一个页面
RestTemplate restTemplate=new RestTemplate();
String url="https://www.baidu.com"; //发送请求到那个网址
// url 网址 String.class是指返回值的类型 当返回值为一个页面但是要求为字符串类型时,会返回页面的组成代码
String result=restTemplate.getForObject(url,String.class);
System.out.println(result);
}
}
运行方法,得到的返回结果

创建Controller类 进行传参
package com.ys.controller;
import com.ys.controller.domain.User;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
@org.springframework.web.bind.annotation.RestController
public class RestController {
@GetMapping("testGet")
public String testGet(String name){
System.out.println(name);
return "ok";
}
// json参数的核心 header content-type=application/json charset=utf-8
// 使用一个接收类User和@RequestBody接受json类型参数
@PostMapping("testPost")
public String testPost(@RequestBody User user){
System.out.println(user);
return "ok";
}
// 接收表单参数
/**
* <form action=/testPost(地址)
* <input name=ccc value=sss>
* 核心 header content-type=application/x-www-form-urlencoded
* 使用一个接收类User不用加@RequestBody,直接如下即可:
* public String testPost(User user){
* System.out.println(user);
* return "ok";
* }
*/
@PostMapping("testPost2")
public String testPost2(User user){
System.out.println(user);
return "ok";
}
}
编辑测试类
package com.ys;
import com.ys.controller.domain.User;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.ResponseEntity;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.client.RestTemplate;
@SpringBootTest
class RestTemplateApplicationTests {
@Test
void contextLoads() {
// 在java代码中去发送一个请求 请求一个页面
RestTemplate restTemplate=new RestTemplate();
String url="https://www.baidu.com"; //发送请求到那个网址
// url 网址 String.class是指返回值的类型 当返回值为一个页面但是要求为字符串类型时,会返回页面的组成代码
String result=restTemplate.getForObject(url,String.class);
System.out.println(result);
}
@Test
void testGet(){
RestTemplate restTemplate=new RestTemplate();
String url="http://localhost:8080/testGet?name=cxk";
String result=restTemplate.getForObject(url,String.class);
// http协议:请求头 请求参数....响应头 响应状态码 报文...
ResponseEntity<String> responseEntity=restTemplate.getForEntity(url,String.class);
System.out.println(responseEntity);
System.out.println(result);
}
@Test
void testPost(){
RestTemplate restTemplate=new RestTemplate();
String url="http://localhost:8080/testPost";
User user=new User("雷哥",18,10000D);
// 发生一个post请求,而且参数是json,因为web里面默认使用jackson,会把你的对象转化成json字符串
String result= restTemplate.postForObject(url,user,String.class);
System.out.println(result);
}
@Test
void testPost2(){
RestTemplate restTemplate=new RestTemplate();
String url="http://localhost:8080/testPost2";
// 构建表单参数
LinkedMultiValueMap<String,Object> map = new LinkedMultiValueMap<>();
// 注意这里要使用add而不是put
map.add("name","林妹");
map.add("age",15);
map.add("price",8000D);
String result= restTemplate.postForObject(url,map,String.class);
System.out.println(result);
}
}
启动springboot服务器,如果报错,就先把测试类中的方法全部注释掉之后启动服务器,之后在测试类取消注释再运行测试方法,此时查看控制台返回的数据


