Eureka
Eureka 集群
- 使用了注册中心之后,所有的服务都要通过服务注册中心来进行信息交换。
- 那么服务注册中心的稳定性就变得非常重要了,一旦服务注册中心掉线,那么就会影响到整个系统的稳定性。
- 所以,在实际开发中,Eureka一般都是以集群的形式出现。
Eureka集群,实际上就是启动多个Eureka示例,多个Eureka实例之间,相互注册,相互同步数据,共同组成一个Eureka集群。
Eureka 集群搭建
首先需要修改电脑的 hosts文件
127.0.0.1 eureka-a eureka-b
在resources中添加两个环境模拟集群:
application-a.yml application-b.yml
打成jar包流程:跳过test测试,package打包
启动Eureka实例
打包完成后,在命令行启动实例
打开term终端
cd target/
启动实例:
1
2java -jar eureka-0.0.1-SNAPSHOT.jar --spring.profiles.active=a
java -jar eureka-0.0.1-SNAPSHOT.jar --spring.profiles.active=b
Eureka 细节
Eureka 本身可以分为两大部分
- Eureka Server
- Eureka client
Eureka Server
Eureka Server主要提供了三个功能:
- 服务注册,所有的服务都注册到Eureka Server上面
- 提供了注册表,注册表就是所有注册上来的服务的一个列表,Eureka Client在调用服务的时候,需要获取这个注册表,一般来说,这个注册表会被缓存下来,如果缓存失效了,则直接获取最新的注册表
- 同步状态,Eureka Client通过注册、心跳等机制,和Eureka Server同步当前客户端的状态
Eureka Client
- Eureka Client主要是用来简化每一个服务和Eureka Server之间的交互
- Eureka Client会自动拉取、更新以及缓存Eureka Server的信息,这样的话,即使Eureka Server所有的节点都宕机了
- Eureka Client依然可以在缓存中获取到想要调用服务的地址(但是地址有可能会发生改变,从而不准确)。
Eureka 服务注册
- 服务提供者将自己注册到服务注册中心(Eureka Server)
- 需要注意的是,所谓的服务提供者,只是一个业务上的划分,本质上它就是一个Eureka Client
- 当Eureka Client向Eureka Server注册时,他需要提供一些自身的元数据信息,例如 IP地址、端口号、名称、运行状态等等。
Eureka 服务续约
- Eureka Client注册到Eureka Server上之后,事情才刚刚开始运行
- 注册成功之后,默认的情况下,Eureka Client每隔30秒便会向Eureka Server发送一条心跳消息,来告诉Eureka Server我还在运行中
- 如果Eureka Server连续90秒都没有收到Eureka Client的续约消息(连续三次没发送回复),Eureka Server就会认为Eureka Client已经掉线了,便会将掉线的Eureka Client从当前的服务注册列表中剔除出去。
关于服务续约的两个相关属性(一般不建议修改):
1 | ## 表示服务的续约时间,默认时间为30秒 |
服务下线
当Eureka Client下线的时候,会主动发送一条信息,告诉Eureka Server,我已经下线。
获取注册表信息
- Eureka Client会从Eureka Server上获取服务的注册信息,并且将其缓存到本地
- 那么本地服务端在需要调用远程服务时,会从该缓存信息中查找远程服务所对应的 IP地址、端口号等信息
- Eureka Client上缓存的服务注册信息也会定期从Eureka Server上更新(30秒)
- 如果Eureka Server返回的注册表信息与本地缓存的注册表信息不一致的话,Eureka Client会自动更改同步处理。
这里涉及到两个属性:
1 | ## 表示是否允许从 Eureka Server中获取信息 默认为true |
Eureka 集群原理
- 在Eureka集群架构中,Eureka Server之间通过 Replicate进行数据同步,不同的Eureka Server之间不区分主从节点,所有的节点都是平等的。在节点之间,通过指定serviceUrl来相互注册,形成一个集群,从而提高节点的可用性。
- 在Eureka Server集群中,如果有某一个节点宕机,Eureka Client会自动切换到新的Eureka Server上。每一个Eureka Server节点,都会相互同步数据。
- Eureka Server的连接方式,也可以是单线的,也就是a–>b–>c,此时虽然a的数据也会和c之间互相同步,但是一般不建议这种写法。
在我们配置serviceUrl时,可以指定多个注册地址,也就是a可以注册到b上,也可以同时注册到c上,一般推荐使用这种写法。
Eureka分区:
- region: 根据地理上的不同区域分区
- zone: 根据具体的机房分区
服务注册与消费
服务注册
- 服务注册就是把一个微服务注册到Eureka Server上,这样的话,当其他服务需要调用该服务的时候,只需要从Eureka Server上查询该服务的信息即可。
以下我们创建一个 provider,作为我们的服务提供者。
- 创建一个spring boot项目
- 选择Eureka Client, Spring Web依赖
- 这样的话,当服务创建成功之后,简单的配置一下,就可以被注册到Eureka Server上了。
只需要在 application.yml配置一下项目的注册地址即可
1 | spring: |
- 先启动Eureka Server,等到服务注册中心Eureka Server启动成功之后,再启动 provider项目注册。
- 检验是否成功注册:等到两者均成功启动
- 打开 http://localhost:1111 ,就可以查看 provider的注册信息是否存在。
服务消费
- 首先在 provider中提供一个接口
- 然后创建一个新的 consumer项目
- 去消费这个接口(/hello)
- 在 provider中,提供了一个 hello接口。
- 创建一个 consumer项目,在 consumer项目中,去消费 provider提供的接口
- 因为 consumer想要能够获取到 provider这个接口的地址,它就需要去Eureka Server中查询。
- 如果直接在consumer中写死 provider的地址,便意味着这两个服务之间的耦合度太高了,所以我们需要降低他们之间的耦合度
写死的调用:
(具体代码参考 provider中HelloController、consumer中UserHelloController(/hello1))
- 首先也在 application.yml中配置一下注册信息
- 假设我们现在想在 consumer中调用 provider提供的服务,我们可以直接将调用写死,也就是说整个调用过程中不会涉及到Eureka Server。
- 写死的方法是利用了 HttpUrlConnection来发起的请求,请求中 provider的地址写死了
- 意味着 provider和 consumer高度的绑定在一起,这个不符合微服务的思想。
灵活调用方法:
(具体代码参考 provider中HelloController、consumer中UserHelloController(/hello2))
我们可以借助Eureka Client提供的 DiscoveryClient工具
利用了这个工具,我们可以根据服务名从Eureka Server上查询到一个服务的详细信息。
注意需要注入下面的工具 DiscoveryClient
1 | @Autowired |
注意:DiscoveryClient 查询到的服务列表是一个集合,因为服务在部署的过程中,有可能是集群化部署,集合中的每一项就是一个实例。
展示集群化部署
如果要同时启动多个 provider实例,多个 provider实例的端口不同,为了区分调用时到底是哪一个 provider提供的服务,这里可以在接口返回值中返回端口。
修改完成后,对项目进行打包。打包成功之后,在命令行启动两个 provider实例:
1 | java -jar provider-0.0.1-SNAPSHOT.jar --server.port=1117 |
- 启动完成后检查Eureka Server,这两个 provider实例是否注册上来。
- 这个时候注册成功之后,在 consumer中再去调用 provider,DiscoveryClient集合之中,获取到的就不再是一个实例,而是两个实例了。
手动实现线性负载均衡
(具体代码参考 provider中HelloController、consumer中UserHelloController(/hello3))
// todo 一步步简化 手动负载均衡改自动
- 在从集合中获取数据时,通过小小的改变来实现线性负载均衡。
- 当请求 http://localhost:1115/hello3 的时候,观察到 port端口号不断变化,则表示负载均衡成功启动。
升级改造集群化部署
从两个方面进行改造:
1. Http调用
- Http调用,我们可以使用 Spring提供的 RestTemplate来实现。
- 首先,在当前服务中,提供一个 RestTemplate的实例。 (具体代码参考/consumer/ConsumerApplication)方法:restTemplateOne()
- 然后在 Http调用时,不再使用 HttpUrlConnection,而是直接使用 RestTemplate。
- (具体代码参考 provider中HelloController、consumer中UserHelloController(/hello4))
- 用 RestTemplate一行代码就可以实现了 Http调用。
2. 负载均衡
- 使用 Ribbon来快速实现负载均衡。
- 在 RestTemplate上使用 @LoadBalanced注解开启负载均衡
- (具体代码参考/consumer/ConsumerApplication)方法:restTemplate)
- 此时的 RestTemplate就自动具备了负载均衡的功能。
- (具体代码参考 provider中HelloController、consumer中UserHelloController(/hello5))
RestTemplate
- RestTemplate 是从 Spring 3.0开始支持的一个 Http请求工具
- RestTemplate和 Spring Boot无关, 更加和Spring Cloud无关。
- RestTemplate 提供了常见的 REST请求方法模版,例如 GET、 POST、 PUT、 DELETE请求,以及一些通用的请求执行方法 exchange 和 execute 方法。
- RestTemplate 本身实现了 RestOperations接口,而在 RestOperations接口中,定义了常见的 RESTful操作,这些操作在 RestTemplate中都得到了很好的实现。
GET
(具体代码参考 provider中HelloController(/hello2)、consumer中UserHelloController(/hello6))
- 首先我们在 provider中定义一个 hello2 接口。
- 然后在 consumer中去访问这个接口,因为这个接口是一个 GET请求,所以访问方式就是调用 RestTemplate中的 GET请求去访问。
getForObject()、getForEntity() 区别
- 在 RestTemplate中,关于 GET请求,一共有两大类:getForObject()、getForEntity()
- 这两大类方法实际上是重载的,唯一的不同就是,返回的值类型不同。
- getForObject(): 返回的是一个对象,这个对象就是服务端返回的具体值。
- getForEntity(): 返回的是一个 ResponseEntity,这个 ResponseEntity中除了服务端返回的具体数据外,另外还保留了 Http响应头的数据。
- 通过 getForObject()可以看到直接拿到了服务的返回值,getForEntity()不仅仅拿到了服务的返回值,还拿到了 http响应的具体信息。
- 然后启动 Eureka Server、 provider 以及 consumer,访问 consumer中的 http://localhost:1115/hello6 接口,即可以查看到请求结果。
getForObject()、getForEntity() 重载方法
- getForObject()、getForEntity() 分别有三个重载方法,两者的三个重载方法基本上是一致的。
- 所以,主要弄清楚一种方法即可。三个重载方法其实代表了三种不同的传参方式。
- (具体代码参考 provider中HelloController(/hello2)、consumer中UserHelloController(/hello7))
- 重启 consumer中的 http://localhost:1115/hello7 接口,即可以查看到请求结果。
POST
- 首先我们在 provider中提供两个 POST接口
- 同时,因为 POST请求可能需要传递 JSON,所以这里我们创建一个普通的 Maven项目作为 commons模块
- 然后让这个 commons模块被 provider 和 consumer 共同引用,这样就可以方便我们传递 JSON 了。
- commons 模块创建成功后,在 commons模块中创建 User对象,分别被 provider 和 consumer 引用。
- 引入完成后,我们在 provider中提供两个 POST请求接口。
- (具体代码参考 provider中HelloController(/hello3、/hello4)、consumer中UserHelloController(/hello7))
- 定义完成后,接下来在 consumer中调用这两个接口。
- RestTemplate 中的 POST请求和 GET请求很像,只是多出来三个方法
- 就是 postForLocation,另外两个 postForObject 和 postForEntity 和前面的 get基本是一致的。
- 所以我们主要看 postForObject 和额外的 postForLocation。
postForObject()
(具体代码参考 provider中HelloController(/user、/user1)、consumer中UserHelloController(/hello8、/hello9))
postForLocation()
- 有的时候,当我们执行完一个 post请求之后,立马就要进行重定向,
- 例如一个非常常见的场景就是注册,注册是一个 post请求,注册完成之后,立马重定向到登陆页面去登陆,对于这种场景,我们就可以使用到 postForLocation。
- 首先我们在 provider上提供一个 RegisterController 用户注册接口。
- (参考代码 provider/controller/RegisterController.java)
- post 接口,响应一定要为302,否则 postForLocation无效。
- (具体代码参考 provider中RegisterController/register、consumer中UserHelloController(/hello10)
- 注意:重定向的地址,一定要写成绝对路径,不要写成相对路径,否则会在 consumer 中调用时候出问题。
- postForLocation,调用该方法返回的是一个 uri,这个 uri 就是重定向的地址(里面包含了重定向的参数),拿到了 uri 之后,就可以直接发送新的请求了。
PUT
- PUT 请求比较简单,重载的方法也比较少,一般用于做更新操作
- 我们首先在 provider 中提供一个 PUT 接口。 /provider/update1 /provider/update2
- 注意: PUT 接口传参其实和 POST 很像,也接受两种类型的参数, key-value 形式以及 JSON 形式。
- 然后,我们在 consumer 中调用该接口。 key-value:/consumer/hello11 json:/consumer/hello12
- consumer 中的写法基本和 post 类似,也是两种方式,可以传递两种不同类型的参数。
DELETE
- DELETE 也比较容易,我们有两种方式来传递参数,key-value形式,或者 PathVariable(参数放在路径中)
- 首先我们在 provider 中定义两个 DELETE 方法。
- key-value:/provider/delete1 PathVariable:/provider/delete2
- 然后在 consumer 中调用这两个删除的接口。
- (具体代码参考 consumer中UserHelloController(/hello13))
- delete 参数的传递也支持 map, 这块实际上和 get 也是一样的。
客户端负载均衡
- 客户端负载均衡就是相对服务端负载均衡而言的。
- 服务端负载均衡,就是传统的 Nginx 的方式,用 Nginx 做负载均衡,我们称之为服务端负载均衡
- 服务端负载均衡:它的一个特点是,就是调用的客户端并不知道自己具体是由哪一个 Server 提供的服务,它也不会关心,反正请求发送给 Nginx, Nginx再将请求转发给 Tomcat(上游服务器、service)。客户端只需要记住 Nginx的地址就可以了。
- 客户端负载均衡:它的特点是,调用的客户端本身它是知道所有 Server 的详细信息的,当你需要调用 Server 上的接口的时候,客户端会从自身所维护的 Server 列表中,根据提前配置好的负载均衡策略,自己挑选一个 Server 来调用,此时,客户端是知道它所调用的是哪一个 Server。
- 在 RestTemplate 中,如果想使用负载均衡功能,只需要给 RestTemplate 实例上添加一个 @LoadBalanced 注解即可,此时,RestTemplate 就会自动具备负载均衡功能,这个负载均衡就是客户端负载均衡。
负载均衡原理
- 在 Spring Cloud 中,实现负载均衡非常容易,只需要添加 @LoadBalanced 注解即可。
- 只要添加了该注解,一个原本普普通通的 Rest 请求的工具 RestTemplate 就会自动具备负载均衡功能
整体上来说的话,这个功能的实现就是三个核心点:
- 从 Eureka Client 本地缓存的服务注册信息中,选择一个可以调用的服务
- 根据 1 中所选择的服务,重构请求 URL 地址(不可以直接写域名+端口号,只能写服务名)
- 将 1、2 步的功能嵌入到 RestTemplate 中