单体应用存在的问题

在传统应用程序中,一般都会将整个的应用程序作为一个单独的可执行文件部署到相应的服务器上执行。一般的应用程序结构可能如下图所示:

这种方式的优点很明显,比如:架构简单,服务之间调用逻辑清晰,服务部署方式也比较简单等等。

但是也有一些显而易见的缺点:

  • 随着项目的需求变多,整个项目的复杂程度会越来越高,这个时候对代码的微小改动很有可能影响到程序的正常工作
  • 由于项目很大,因此编译和部署时也会耗费更多的时间
  • 有时只是改动了一个小模块的功能,但是不得不将整个项目再次重新编译并部署,但是这通常都是不必要的
  • 不同的模块对于硬件的要求不同,因此很难针对单一的模块进行硬件的水平扩展
  • 如果希望对现有的项目进行技术选型,这需要对整个项目进行修改,这将导致工作量剧增

微服务的引入

鉴于大型项目中存在的问题,可以考虑将单独的功能模块从单体的应用程序中剥离出来,使得这些单独的功能模块称为一个单独的应用程序服务,这些服务可以单独部署,采用不同的技术选型,拥有自己的数据库等基础中间件。这些剥离出来的应用程序就被称为 “微服务”,相关的结构可能如下图所示:

微服务的引入解决了上文提到的单体应用存在的问题,但是它也引入了一些新的问题:

  • 现在管理这些应用程序服务变得相当复杂,特别是涉及到多个服务之间相互调用的时候
  • 数据的一致性很难在多个微服务应用程序的环境中保持
  • 定位问题的难度也会随之增加

尽管引入微服务还存在一些其它的问题,但是在当前的环境下,特别是互联网这种流量特别大的应用程序,单体应用程序时绝对无法达到要求的(单个计算机的性能是有限的),因此微服务的架构在现在可以说是一个大势所趋

Spring Cloud 微服务生态圈

Spring 框架提供了对微服务应用程序的支持,它提供了以下的组件用于支持微服务应用架构:

微服务技术栈 对应的组件
服务开发 Spring MVC
服务配置 Config
服务注册与发现 Eureka
服务调用(负载均衡) Ribbon、Feign
服务路由(网关) Zuul
服务熔断(熔断器) Hystrix
服务圈链路监控 Sleuth + Zipkin
服务部署 Docker

这些组件之间的关系如下图所示:

服务治理

前文提到过,将单个的应用程序拆分成多个微服务应用程序,会导致服务之间相互调用的复杂性。比如,如何确定调用的服务是否存在,如何去访问这些服务,这些问题的解决就是 “服务治理”需要解决的问题。

服务治理是微服务应用程序中最为基础的部分,主要的任务是实现各个微服务应用程序之间的自动化注册和发现,因此当某个服务程序希望调用其它模块的服务时,会首先调用 “服务治理”这一模块,以获取要调用的服务的基本信息(如 IP、端口等),然后再进行服务的调用

实际上,在传统的单体应用程序中,也存在诸如 RMI 这样的技术来实现服务的远程调用,但是这些调用都是基于固定的 IP 地址来进行调用的,缺少一定的灵活性。

服务发现

服务发现分为两种表现方式:客户端—服务发现、服务端—服务发现

  • 客户端的服务发现

    客户端通过查询服务注册中心,获取可用服务的实际网络地址(IP和端口),然后客户端通过某种负载均衡算法来选择一个实际的服务实例,将相关的请求发送到该服务实例

    如下图所示:

  • 服务端的服务发现

    客户端将氢气发送到具有“负载均衡”功能的微服务上,该 “负载均衡”服务首先查询到服务注册中心,找到可用的服务,然后将请求发送到该服务上

实际使用

由于服务治理是相对的,因此至少需要两个微服务应用程序才能够进行服务之间的调用。

配置注册中心

对于注册中心,如果是通过 spring.io 的方式来创建的项目,那么只需要添加 eureka-server 的依赖即可:

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>

然后在 Spring-Boot 的启动类上加上 @EnableEurekaServer 使得当前的程序成为注册中心。

application.yml 配置文件中做如下的配置:

server:
port: 8761 # Eureka 注册中心的端口
eureka:
instance:
# 设置当前 Eureka 实例的主机名
hostname: xhliu-eureka-server
client:
# 由于当前所在的实例为注册中心,因此不需要向该注册中心注册自己
register-with-eureka: false
# 注册中心的职责就是维护服务器实例,不需要去检索服务
fetch-registry: false
service-url:
# 暴露给其它 Eureka 客户端的注册地址,Map 结构
defaultZone: http://127.0.0.1:8761/eureka/

然后启动该 Spring-Boot 项目,此时该项目就已经是一个单独的注册中心了。如果此时访问http://127.0.0.1:8761,可能会看到类似的界面:

说明已经成功创建了一个 Eureka 注册中心

注册服务

对于注册到注册中心的应用来讲,需要添加 eureka-client 依赖:

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

在启动类上加上 @EnableDiscoveryClient 注解可以使得当前的服务能够被其它的服务所发现,由于 Eureka 默认能够发现应用服务,因此即使不加这个注解,该服务依旧能够被正常发现。

在启动之前,配置当前的应用程序,使得它能够成功注册到对应的注册中心,以及能够被其它的应用服务正常地发现

application.yml 文件中,做如下的配置:

server:
port: 8001
spring:
application:
# 当前应用服务名称
name: xhliu-producer-1 # 这里需要正确配置注册中心的 URL,否则可能会出现找不到该应用的情况
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:8761/eureka/

启动该 Spring-Boot 应用程序,然后回到上文的 Eureka 的管理界面,可以看到该服务已经注册到 Eureka 注册中心了:

或者,访问注册中心的 /apps 接口,也可以查看注册到注册中心的服务,在上文的配置中,注册中心的上下文换进为 /eureka,因此访问 http://127.0.0.1:8761/eureka/apps 即可查看已经注册的服务,该接口会返回以 XML 格式的数据载体,类似下图所示:

服务之间的访问

有了注册中心的存在,现在服务与服务之间的调用变得更加灵活了。

producer

现在在 producer 应用服务中添加如下的 REST 接口:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.client.serviceregistry.Registration;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource;
import java.util.List; @RestController
public class EurekaProducerController {
private final static Logger log = LoggerFactory.getLogger(EurekaProducerController.class); @Resource
private DiscoveryClient client; // 服务发现的客户端接口 @Resource
private Registration registration; // 用于获取注册信息 /*
该接口每次访问都会打印当前服务发现到的服务,同时将其作为返回值返回给调用对象
*/
@GetMapping(path = "/produce")
public List<ServiceInstance> produce() {
List<ServiceInstance> instances = client.getInstances(registration.getServiceId());
instances.forEach(obj -> log.info("host={} service={}", obj.getHost(), obj.getServiceId()));
return instances;
}
}

启动两个 producer(改变 application.ymlsever.port 再启动),完成这两个 producer

注册

consumer

然后,创建一个 concumer 服务程序,用于调用 producerREST 接口。consumerapplication.yml 配置如下所示:

server:
port: 8008
spring:
application:
name: xhliu-consumer-1
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:8761/eureka/

由于是 REST 接口的调用,因此需要创建一个 RestTemplate 类型的 Bean 用于访问 REST 接口:

import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate; @Component
public class ConsumerBeanConfig {
@Bean
@LoadBalanced // 通过 @LoadBalanced 注解开启负载均衡
public RestTemplate restTemplate() {
return new RestTemplate();
}
}

然后,创建一个 REST 接口用于客户端调用:

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate; import javax.annotation.Resource;
import java.util.List; @RestController
public class ConsumerController {
@Resource
private RestTemplate restTemplate; @GetMapping(path = "/consumer")
public List<?> index() {
return restTemplate.getForEntity(
/*
这里的访问地址格式为 http://{服务名}/{REST 接口}
*/
"http://xhliu-producer-1/produce",
List.class
).getBody();
}
}

然后启动 consumer ,将 concumer 注册到注册中心中

现在,对于 consoumer/concumer 的访问,都会首先向注册中心查找对应的服务,再进行相关的调用。注意,这里的负载均衡是在 consumer 这里完成的(注意 @loadblance 注解)。如果访问该 concumer/consumer 接口,可能会得到类似下图的输出:

在注册到注册中心的两个 producer 中,由于 consumer 负载均衡的存在,具体访问哪个 producer 是不确定的

注册中心集群的搭建

由于注册中心本身也是一个服务,因此只需要创建另一个注册中心,然后将它注册到现在的注册中心即可。

假设现在创建一个有两个实例的 Eureka 注册中心集群,其中一个注册中心为 master,一个为 copied

masterapplication.yml 配置文件内容如下所示:

server:
port: 8040 # Eureka 注册中心的端口 spring:
application:
# 同一个集群的应用程序名必须是一样的
name: xhliu-server eureka:
instance:
# 设置当前 Eureka 实例的主机名
hostname: xhliu-eureka-master
client:
# 由于当前所在的实例为注册中心,因此不需要向该注册中心注册自己
register-with-eureka: false
# 注册中心的职责就是维护服务器实例,不需要去检索服务
fetch-registry: false
service-url:
# 将 master 注册到 copied,使得能够被 copied 发现
defaultZone: http://127.0.0.1:8050/eureka/

copied 的注册中心的 application.yml 配置文件内容如下:

server:
port: 8050 # master 注册中心的端口 spring:
application:
# 同一个集群的应用程序名必须是一样的
name: xhliu-server eureka:
instance:
# 设置当前 Eureka 实例的主机名
hostname: xhliu-eureka-copied
client:
# 由于当前所在的实例为注册中心,因此不需要向该注册中心注册自己
register-with-eureka: false
# 注册中心的职责就是维护服务器实例,不需要去检索服务
fetch-registry: false
service-url:
# 将 copied 注册到 master,使得其能够被 master 发现
defaultZone: http://127.0.0.1:8040/eureka/

现在,分别启动这两个注册中心,访问这两个注册中心的 Eureka 管理界面,其中,可以看到,master 中已经存在 cpoied 的备用注册中心了:

Eureka 的核心功能

Eureka 主要存在以下的一些核心功能:

  • 服务注册功能

    这个最基本的注册中心的功能之一,Client 端通过 REST 请求的方式,向 Server 注册自己的服务信息(如 ip 和端口号等)。Server 收到这些信息则会将它们存储在一个双层 Map 结构中,其中,第一层 Map 会存储该请求的服务名;第二层存储具体服务的实例名

  • 服务续约功能

    服务注册之后,Client 会默认每隔 \(30\) s 来告知 Server 自己依旧存活。可以通过修改如下的配置内容来修改这个间隔时间:

    eureka.instance.lease-renewal-interval-in-seconds=30
  • 服务同步功能

    Eureka 集群中,Server 之间通过相互注册来进行同步,保证注册的服务信息的一致性

  • 服务获取功能

    Client 启动之后,会发送一个 REST 请求给 Server 端,然后从 Server 端获取已经注册的服务列表。Client 端会将这些获取到的服务缓存到本地(缓存保留时间默认为 \(30\)s),同时,处于性能的考虑,Server 端也会将这些信息缓存到本地(缓存保留时间默认为 \(30\)s)。可以通过在配置文件中进行配置:

    eureka.client.fetch-registry=true
    eureka.client.registry-fetch-interval-seconds=30
  • 服务调用功能

    Client 需要关闭或者重启时,可以发送 REST 请求给 Server 端,告知自己将要下线。此时,将该 Client 的服务状态设置为 DOWN,并将该状态同步给集群中的其它节点

  • 服务剔除功能

    如果 Client 没有发送下线请求给 Server ,但是由于某些原因(如网络故障)导致该 Client 不能继续提供服务,那么就会触发服务剔除机制。Eureka Server 在启动时,会创建一个定时任务,默认每隔 \(60\)s 从当前的服务列表中包超时 \(90\)s 没有续约的服务进行剔除

    可以通过修改相关的配置来修改这个超时时间:

    eureka.instance.lease-expiration-duration-in-seconds=90
  • 自我保护

    如果由于网络在一段时间内发生了异常,导致所有的服务都没有续约,那么为了防止 Server 端把所有的服务都进行剔除,于是就出现了自我保护机制。

    如果在 \(15\) 分钟内,出现了低于 \(85\%\) 的续约失败比例,那么将会触发 “自我保护”机制,该机制下不会对任何服务进行剔除操作,当网络正常之后,再退出自我保护机制

    通过进行如下配置以开启“自我保护”机制:

    eureka.server.enable-self-preservation=true

Eureka 的 REST API

具体可以参考:https://github.com/Netflix/eureka/wiki/Eureka-REST-operations

参考:

[1] https://mp.weixin.qq.com/s/Xfq5YCaSSc7WgOH6Yqz4zQ

本文对应的项目地址:https://github.com/LiuXianghai-coder/Spring-Study/tree/master/spring-eureka

Spring Cloud(一)Eureka的更多相关文章

  1. spring cloud之eureka简介

    最近线上的接口出了一些问题,有一些可能不是代码的问题,但是由于是测试和其他方面的同事爆出来的,所以感觉对接口的监控应该提上日程. 经过搜索发现,spring cloud的eureka就是专门做这方面工 ...

  2. Spring Cloud Security&Eureka安全认证(Greenwich版本)

    Spring Cloud Security&Eureka安全认证(Greenwich版本) 一·安全 Spring Cloud支持多种安全认证方式,比如OAuth等.而默认是可以直接添加spr ...

  3. Spring cloud系列教程第十篇- Spring cloud整合Eureka总结篇

    Spring cloud系列教程第十篇- Spring cloud整合Eureka总结篇 本文主要内容: 1:spring cloud整合Eureka总结 本文是由凯哥(凯哥Java:kagejava ...

  4. Spring Cloud与Eureka

    Spring Cloud与Eureka 一.使用SpringCloud注册中心Eureka 1.1 Eureka和Zookeeper对比 1.1.1 Zookeeper保证CP 1.1.2 Eurek ...

  5. Spring Boot,Spring Cloud,Eureka,Actuator,Spring Boot Admin,Stream,Hystrix

    Spring Boot,Spring Cloud,Eureka,Actuator,Spring Boot Admin,Stream,Hystrix 一.Spring Cloud 之 Eureka. 1 ...

  6. Spring Cloud 组件 —— eureka

    官方文档,Spring Cloud 对其封装,Spring Cloud eureka 文档

  7. Spring cloud之Eureka(二)注册中心高可用

    背景 在实际的生产环境中,注册中心如果只有一个,是很危险的,当这个注册中心由于各种原因不能提供正常服务或者挂掉时,整个系统都会崩溃,这是很致命的的,所以在Spring cloud 设计之初就考虑到了注 ...

  8. Spring Cloud 之Eureka(一)

    简介 Eureka是Spring cloud 的基本套件之一,是基于Netflix 的Eureka做的二次封装,主要是负责完成微服务架构中的服务治理功能.它是微服务架构中最为核心和基础的模块,它主要是 ...

  9. Spring cloud Greenwich Eureka

    1.父工程POM文件中: <dependencyManagement> <dependencies> <!--spring cloud--> <depende ...

  10. 从零开始学spring cloud(八) -------- Eureka 高可用机制

    一.Eureka高可用机制介绍 Eureka服务器没有后端存储,但注册表中的服务实例都必须发送心跳以使其注册保持最新(因此可以在内存中完成). 客户端还有一个Eureka注册的内存缓存(因此,他们不必 ...

随机推荐

  1. ssh-keygen无回车生成公钥私钥对

    ssh-keygen无回车生成公钥私钥对 ssh-keygen -t rsa -N '' -f /root/.ssh/id_rsa -q -t: -N:是指密码为空: -f:id_rsa是指保存文件为 ...

  2. 【题解】《PTA-Python程序设计》题目集分享

    第1章-1 从键盘输入两个数,求它们的和并输出 (30 分) 本题目要求读入2个整数A和B,然后输出它们的和. 输入格式: 在一行中给出一个被加数在另一行中给出一个加数 输出格式: 在一行中输出和值. ...

  3. ContextWrapper

    /* * Copyright (C) 2006 The Android Open Source Project * * Licensed under the Apache License, Versi ...

  4. 一些对dp突然的理解

    突然想到了一些理解,感觉有些模糊,怕忘记,就赶紧记下来就是对于状态的设计 用01背包举例子吧,我们设计状态的时候一定是要保证所有可能在最后优秀的子状态在前面的时候是能够保留下来的也就是我们的状态设计要 ...

  5. K8S 组合命令

    强制删除namespace kubectl get namespace [namespace-name] -o json | tr -d "\n" | sed "s/\& ...

  6. 聊聊如何在Java应用中发送短信

    很多业务场景里,我们都需要发送短信,比如登陆验证码.告警.营销通知.节日祝福等等. 这篇文章,我们聊聊 Java 应用中如何优雅的发送短信. 1 客户端/服务端两种模式 Java 应用中发送短信通常需 ...

  7. 【scipy 基础】--线性代数

    SciPy的linalg模块是SciPy库中的一个子模块,它提供了许多用于线性代数运算的函数和工具,如矩阵求逆.特征值.行列式.线性方程组求解等. 相比于NumPy的linalg模块,SciPy的li ...

  8. 嵌入式C编码规范

    每个程序员都有自己的编码风格,自己喜欢就好. 嵌入式C编码规范 上述博文来自转载

  9. 为什么 Django 后台管理系统那么“丑”?

    哈喽大家好,我是咸鱼 相信使用过 Django 的小伙伴都知道 Django 有一个默认的后台管理系统--Django Admin 它的 UI 很多年都没有发生过变化,现在看来显得有些"过时 ...

  10. QT实战 之翻金币游戏

    QT实战 之翻金币游戏 相较于原版的优化: 关卡数据不是用静态的config配置,而是动态生成,每次打开的关卡都生成不同的游戏数据,增加了可玩性: 关卡数据依据关卡等级的不同而生成不同难度的数据,随关 ...