SpringCloud-微服务入门之Eureka(1)

x33g5p2x  于2022-05-16 转载在 Spring  
字(12.0k)|赞(0)|评价(0)|浏览(404)

读此篇前必先去了解 :

微服务介绍(史上最全)
初始SpringCloud微服务

以上都没问题了,那么接下来的学习才会轻松

介绍

微服务是一种架构方式,最终肯定需要技术架构去实施。

微服务的实现方式很多,但是最火的莫过于Spring Cloud了。为什么?

  • 后台硬:作为Spring家族的一员,有整个Spring全家桶靠山,背景十分强大。
  • 技术强:Spring作为Java领域的前辈,可以说是功力深厚。有强力的技术团队支撑,一般人还真比不了
  • 群众基础好:可以说大多数程序员的成长都伴随着Spring框架,试问:现在有几家公司开发不用Spring?SpringCloud与Spring的各个框架无缝整合,对大家来说一切都是熟悉的配方,熟悉的味道。
  • 使用方便:相信大家都体会到了SpringBoot给我们开发带来的便利,而SpringCloud完全支持SpringBoot的开发,用很少的配置就能完成微服务框架的搭建

Spring最擅长的就是集成,把世界上最好的框架拿过来,集成到自己的项目中。

SpringCloud也是一样,它将现在非常流行的一些技术整合到一起,实现了诸如:配置管理,服务发现,智能路由,负载均衡,熔断器,控制总线,集群状态等等功能。其主要涉及的组件包括:

  • Eureka:注册中心 (下面会讲)
  • Zuul:服务网关 (下面会讲)
  • Ribbon:负载均衡 (下面会讲)
  • Feign:服务调用 (下面会讲)
  • Hystix:熔断器 (下面会讲)

以上只是其中一部分

Eureka介绍

在搭建 eureka-server服务前我们 先来了解下 Eureka的机制

就好比是中间商 卖房子的(服务) 到中间商登记 买房子到 中间商哪里去买 这样买房子的人就有很多选择了

而 eureka 还能有效防止 服务宕机的问题 需要集群

注册中心服务端主要对外提供了三个功能:

服务注册
服务提供者启动时,会通过 Eureka Client 向 Eureka Server 注册信息,Eureka Server 会存储该服务的信息,Eureka Server 内部有二层缓存机制来维护整个注册表

提供注册表
服务消费者在调用服务时,如果 Eureka Client 没有缓存注册表的话,会从 Eureka Server 获取最新的注册表

Register: 服务注册
服务的提供者,将自身注册到注册中心,服务提供者也是一个 Eureka Client。当 Eureka Client 向 Eureka Server 注册时,它提供自身的元数据,比如 IP 地址、端口,运行状况指示符 URL,主页等。

Eureka Client 会每隔 30 秒发送一次心跳来续约。 通过续约来告知 Eureka Server 该 Eureka Client 运行正常,没有出现问题。 默认情况下,如果 Eureka Server 在 90 秒内没有收到 Eureka Client 的续约,Server 端会将实例从其注册表中删除,此时间可配置,一般情况不建议更改。

自我保护机制

默认情况下,如果 Eureka Server 在一定的 90s 内没有接收到某个微服务实例的心跳,会注销该实例。但是在微服务架构下服务之间通常都是跨进程调用,网络通信往往会面临着各种问题,比如微服务状态正常,网络分区故障,导致此实例被注销。

固定时间内大量实例被注销,可能会严重威胁整个微服务架构的可用性。为了解决这个问题,Eureka 开发了自我保护机制,那么什么是自我保护机制呢?

Eureka Server 在运行期间会去统计心跳失败比例在 15 分钟之内是否低于 85%,如果低于 85%,Eureka Server 即会进入自我保护机制。

Eureka Server 触发自我保护机制后,页面会出现提示:

接下来我们搭建eureka-server 服务 用于将 服务注册

Eureka Server 进入自我保护机制,会出现以下几种情况:
(1 Eureka 不再从注册列表中移除因为长时间没收到心跳而应该过期的服务
(2 Eureka 仍然能够接受新服务的注册和查询请求,但是不会被同步到其它节点上(即保证当前节点依然可用)
(3 当网络稳定时,当前实例新的注册信息会被同步到其它节点中

Eureka 自我保护机制是为了防止误杀服务而提供的一个机制。当个别客户端出现心跳失联时,则认为是客户端的问题,剔除掉客户端;当 Eureka 捕获到大量的心跳失败时,则认为可能是网络问题,进入自我保护机制;当客户端心跳恢复时,Eureka 会自动退出自我保护机制。 (推出保护机制后 刷新页面就没有报错了)

如果在保护期内刚好这个服务提供者非正常下线了,此时服务消费者就会拿到一个无效的服务实例,即会调用失败。对于这个问题需要服务消费者端要有一些容错机制,如重试,断路器等。

通过在 Eureka Server 配置如下参数,开启或者关闭保护机制,生产环境建议打开:

eureka.server.enable-self-preservation=true

微服务项目结构图

微服务搭建

搭建SpringCloud(父模块)

删除src,因为是父模块不需要写代码,只需要pom.xml文件作为依赖管理

SpringCloud模块的pom.xml

<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.4.RELEASE</version>
        <relativePath/>
    </parent>
    <properties>
        <!--        JDK-->
        <java.version>1.8</java.version>
        <spring-cloud.version>Greenwich.SR3</spring-cloud.version>
        <mapper.starter.version>2.1.5</mapper.starter.version>
        <mysql.version>8.0.21</mysql.version>
        <mybatis.version>2.1.3</mybatis.version>
    </properties>
    <dependencyManagement>
        <dependencies> <!-- springCloud -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency> <!-- 通用Mapper启动器 -->

            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>${mysql.version}</version>
            </dependency>
            <dependency>
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
                <version>${mybatis.version}</version>
            </dependency>

        </dependencies>
    </dependencyManagement>

    <dependencies>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>

搭建公共模块common(子模块)

公共模块可以用于通用代码和通用配置的提取,如果模块多的话,我们只需要修改公共模块的配置就行了

common的pom.xml

<dependencies>
        <!-- 添加OkHttp支持 -->
        <dependency>
            <groupId>com.squareup.okhttp3</groupId>
            <artifactId>okhttp</artifactId>
            <version>3.9.0</version>
        </dependency>

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.12.0</version>
        </dependency>
    </dependencies>

application-common.yml

spring:
  # 配置数据库 参数
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://192.168.42.160:3306/springcloud?useUnicode=true&characterEncoding=utf8&autoReconnect=true&failOverReadOnly=false
    username: root
    password: root

#配置 mybatis 的 实体层
mybatis:
  type-aliases-package: com.**.entity
  mapper-locations: classpath*:/mapper/**/*Mapper.xml    # mapper映射文件位置
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl   #用于控制台打印sql语句
    map-underscore-to-camel-case: true #开启将带有下划线的表字段 映射为驼峰格式的实体类属性
##向Eureka 中注册实例
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:8000/eureka

搭建注册中心Eureka(子模块)

添加Eureka模块的pom.xml

<dependencies>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

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

EurekaApplication

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

// http://127.0.0.1:8000
@SpringBootApplication
@EnableEurekaServer // 声明这个应用是一个EurekaServer (必须)
public class EurekaApplication {

    public static void main(String[] args) {
        SpringApplication.run(EurekaApplication.class, args);
    }

}

Eureka的application.yml

server:
  port: 8000


spring:
  application:
    name: eureka  # 设置服务名称

#配置Eureka
eureka:
  client:
    register-with-eureka: false  #false表示不向注册中心注册自己。
    fetch-registry: false #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
    service-url: # EurekaServer的地址,现在是自己的地址,如果是集群,需要加上其它Server的地址。
      defaultZone: http://127.0.0.1:${server.port}/eureka

然后启动EurekaApplication

访问http://127.0.0.1:8000

搭建生产者producer(子模块)

不提供子模块的创建方式了,可以参考common模块教程进行创建子模块

producer的pom.xml

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
        </dependency>

<!--        添加公共模块-->
        <dependency>
            <groupId>org.example</groupId>
            <artifactId>common</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

        <!-- Eureka客户端 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

    </dependencies>

producer的application.yml

server:
  port: 9000

spring:
  profiles:
    active: common   # 使用公共模块的yml合并到当前的yml
  application:
    name: producer  # 设置服务名称  会在Eureka中显示

ProducerApplication

@SpringBootApplication
public class ProducerApplication {
    public static void main(String[] args) {
        SpringApplication.run(ProducerApplication.class, args);
    }
}

UserController

@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserService userService;

    @GetMapping("/all")
    public ResponseEntity<List<UserEneity>> getAll(){
       return    ResponseEntity.ok(userService.getAll());

    }

}

其他的都将省略,自行补充, 我不信你学微服务连基础三层结构都不会

启动ProducerApplication

然后去eureka控制台查看

可以通过上图发现,成功将producer注册进去了

搭建消费者consumer(子模块)

不提供子模块的创建方式了,可以参考common模块教程进行创建子模块

consumer的pom.xml

<dependencies>
        <!--        添加公共模块-->
        <dependency>
            <groupId>org.example</groupId>
            <artifactId>common</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- Eureka客户端 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
    </dependencies>

consumer的application.yml

server:
  port: 9001

# 设置服务名称  会在Eureka中显示 而且其他方也要用的
spring:
  profiles:
    active: common
  application:
    name: consumer

ConsumerApplication

@SpringBootApplication
public class ConsumerApplication {

    public static void main(String[] args) {
        SpringApplication.run(ConsumerApplication.class, args);
    }
    @Bean
    public RestTemplate restTemplate() {
        // 我们使用了OkHttp客户端,只需要注入工厂即可
        return new RestTemplate(new OkHttp3ClientHttpRequestFactory());
    }
}

ConsumerUserController

import com.consumer.entity.UserEneity;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;  // 这个包不要导入错了
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;

import java.util.List;

@RestController
@RequestMapping("/user")
public class ConsumerUserController {
    @Autowired
    private RestTemplate restTemplate;
    @Autowired
    private DiscoveryClient discoveryClient;
    @GetMapping("/getUserAll")
    @ResponseBody
    public ResponseEntity<List<UserEneity>> getUserAll(){
        // 通过在application.yml中设置的name 获取在Eureka中注册的实例
        // 因为  user-service  可能不只一个 (防止宕机)   所以是集合
        List<ServiceInstance> instances =  discoveryClient.getInstances("producer");
        // 我们 只用第一个就行了  如果第一个宕机了那么 第2个自然就变成第一个了
        //如果不懂去看一下Eureka的心跳机制  上面有
        ServiceInstance instance =  instances.get(0);
        // getUri 获取producer服务的url然后拼接成http请求以及要请求的内容
        String url = instance.getUri()+"/user/all";
        // 通过http请求客户端工具 将请求发送  获取请求 对应的数据
        List<UserEneity> list = restTemplate.getForObject(url,List.class);
        return ResponseEntity.ok(list);
    }
}

其他的都将省略,自行补充
启动ConsumerApplication

然后去eureka里查看

可以通过上图发现,成功将consumer注册进去了

消费者调用生产者
访问: http://localhost:9001/user/getUserAll 如果请求成功了那么代表服务间的调用ok了

Enreka集群

如果我们只配置一个Eureka服务端,那么如果这个服务端崩盘,那么所有服务都无法获取,这肯定不是我们不期望的。所以为了保证高可用性,我们需要搭建Eureka集群。

其实很简单,我么按照上面Eureka搭建教程在搭建2套,然后只需要修下配置文件就行

eureka: 8000 ,eureka: 8001,eureka: 8002

每个Eureka配置如下:

然后启动所有的Eureka

修改application-common.yml
从以前向单eureka改为向多个eureka注册, 这样如果其中一个eureka宕机了那么其他的eureka还能提供服务

##向Eureka 中注册实例
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:8000/eureka,http://127.0.0.1:8001/eureka,http://127.0.0.1:8002/eureka

测试集群
我们将8001的eureka关闭了,然后看看消费者还能请求成功吗?

访问http://127.0.0.1:8001/ 发现已经进不去了,也就代表我们服务关闭成功了

然后我们消费者进行访问发现还是能访问成功的

Eureka额外配置(不强制)

客户端额外配置

默认注册时使用的是主机名或者localhost,如果想用ip进行注册,可以添加配置如下:

eureka:
  instance:
    ip-address: 127.0.0.1 # ip地址
    prefer-ip-address: true # 更倾向于使用ip,而不是host名
eureka:
  instance:
    lease-expiration-duration-in-seconds: 90  # 服务续约(renew)的间隔,默认为30秒
    lease-renewal-interval-in-seconds: 30  #服务失效时间,默认值90秒

也就是说,默认情况下每隔30秒服务会向注册中心发送一次心跳,证明自己还活着。如果超过90秒没有发送心跳,

EurekaServer就会认为该服务宕机,会定时(eureka.server.eviction-interval-timer-in-ms设定的时间)从服务列表中移除,这两个值在生产环境建议不要修改,默认即可。

eureka:
    registry-fetch-interval-seconds: 10

当服务消费者启动时,会检测 eureka.client.fetch-registry=true (默认) ,则会从EurekaServer服务的列表拉取只读备份,然后缓存在本地。并且 每隔30秒 会重新拉取并更新数据。可以在 consumer-demo项目中通过下面的参数来修改

服务端额外配置

服务下线

当服务进行正常关闭操作时,它会触发一个服务下线的REST请求给Eureka Server,告诉服务注册中心:“我要下线了”。服务中心接受到请求之后,将该服务置为下线状态。

这个过程死自动的无须配置

失效剔除

有时我们的服务可能由于内存溢出或网络故障等原因使得服务不能正常的工作,而服务注册中心并未收到“服务下

线”的请求。相对于服务提供者的“服务续约”操作,服务注册中心在启动时会创建一个定时任务,默认每隔一段时间(默认为60秒)将当前清单中超时(默认为90秒)没有续约的服务剔除,这个操作被称为失效剔除。我们可以手动配置 剔除时间

# 在eureka中注册
eureka:
  server:
    eviction-interval-timer-in-ms:  3   #服务关闭3秒后剔除

设置后你就能看到控制台不断在刷新Running the evict task with compensationTime xxms

自我保护

我们关停一个服务,很可能会在Eureka面板看到一条警告:

这是触发了Eureka的自我保护机制。当服务未按时进行心跳续约时,Eureka会统计服务实例最近15分钟心跳续约的比例是否低于了85%。在生产环境下,因为网络延迟等原因,心跳失败实例的比例很有可能超标,但是此时就把服务剔除列表并不妥当,因为服务可能没有宕机。Eureka在这段时间内不会剔除任何服务实例,直到网络恢复正常。生产环境下这很有效,保证了大多数服务依然可用,不过也有可能获取到失败的服务实例,因此服务调用者必须做好服务的失败容错。

可以通过下面的配置来关停自我保护:

# 在eureka中注册
eureka:
  client:
    service-url: # EurekaServer的地址,现在是自己的地址,如果是集群,需要加上其它Server的地址。
      defaultZone: ${defaultZone:http://127.0.0.1:10086/eureka}
  server:
    enable-self-preservation: false # 关闭自我保护模式(缺省为打开)

关闭后管理页面会出现

THE SELF PRESERVATION MODE IS TURNED OFF. THIS MAY NOT PROTECT INSTANCE EXPIRY IN CASE OF NETWORK/OTHER PROBLEMS.

意思是: 自保存模式被关闭。这可能不能保护实例在网络/其他问题的情况下过期。

一般情况下 开发阶段可以关掉 生产环境就别关了

点赞 -收藏-关注-便于以后复习和收到最新内容有其他问题在评论区讨论-或者私信我-收到会在第一时间回复如有侵权,请私信联系我感谢,配合,希望我的努力对你有帮助^_^

相关文章