SpringCloud(二)Eureka服务注册中心-搭建微服务框架

文章目录

  • 回顾问题
  • Eureka注册中心,认识Eureka
  • Eureka的原理及配置
    • 基础架构
    • eureka-server子服务
    • 修改user-service子服务
    • 修改consumer-service服务
    • 高可用的eureka配置、服务复制
    • 测试结果
  • eureka相关补充
    • Eureka客户端
    • 失效剔除和自我保护

回顾问题

https://blog.csdn.net/ym15229994318ym/article/details/105064094在最后提到的问题,概括一下就是分布式服务必然要面临的问题:

1、服务管理
    如何自动注册和发现
    如何实现状态监管
    如何实现动态路由
2、服务如何实现负载均衡
3、服务如何解决容灾问题
4、服务如何实现统一配置

Eureka注册中心,认识Eureka

首先我们来解决第一问题,服务的管理。

  • 问题分析
    在刚才的案例中,user-service对外提供服务,需要对外暴露自己的地址。而consumer (调用者)需要记录服务提供者的地址。将来地址出现变更,还需要及时更新。这在服务较少的时候并不觉得有什么,但是在现在日益复杂的互联网环境,- -个项目肯定会拆分出十几,甚至数十个微服务。此时如果还人为管理地址,不仅开发困难,将来测试、发布上线都会非常麻烦,这与DevOps的思想是背道而驰的。
  • 网约车
    这就好比是网约车出现以前,人们出门叫车只能叫出租车。一些私家车想做出租却没有资格,被称为黑车。而很多人想要约车,但是无奈出租车太少,不方便。私家车很多却不敢拦,而且满大街的车,谁知道哪个才是愿意载人的。一个想要,一个愿意给,就是缺少引子,缺乏管理啊。此时滴滴这样的网约车平台出现了,所有想载客的私家车全部到滴滴注册,记录你的车型(服务类型),身份信息(联系方式)。这样提供服务的私家车,在滴滴那里都能找到,一目了然。此时要叫车的人,只需要打开APP,输入你的目的地,选择车型(服务类型),滴滴自动安排一个符合需求的车到你面前,为你服务,完美!
  • Eureka做什么?
    Eureka就好比是滴滴,负责管理、记录服务提供者的信息。服务调用者无需自己寻找服务,而是把自己的需求告诉Eureka,然后Eureka会把符合你需求的服务告诉你。同时,服务提供方与Eureka之间通过“心跳”机制进行监控,当某个服务提供方出现问题,Eureka自 然会把它从服务列表中剔除。这就实现了服务的自动注册、发现、状态监控。

Eureka的原理及配置

基础架构

Eureka架构中的三个核心角色:

  • 服务注册中心:Eureka的服务端应用,提供服务注册和发现功能,就是刚刚我们建立的eureka-server
  • 服务提供者:提供服务的应用,可以是SpringBoot应用, 也可以是其它任意技术实现,只要对外提供的Rest风格服务即可。本例中就是我们实现的user-service
  • 服务消费者:消费应用从注册中心获取服务列表,从而得知每个服务方的信息,知道去哪里调用服务方。本例中就是我们实现的consumer

eureka-server子服务

增加eureka-server子服务,提供一个注册中心,让服务提供方user-service,在eureka-server中注册服务,服务消费者(调用)consumer在eureka-server中拉取服务,进行调用。eureka会为我们做好负载均衡、服务管理的工作。
SpringCloud(二)Eureka服务注册中心-搭建微服务框架_第1张图片

  1. 引入依赖
 <dependencies>
     <dependency>
         <groupId>org.springframework.cloudgroupId>
         <artifactId>spring-cloud-starter-netflix-eureka-serverartifactId>
     dependency>
 dependencies>
  1. 使用注解@EnableEurekaServer开启eureka功能
@SpringBootApplication
@EnableEurekaServer
public class EurekaServer {
    public static void main(String[] args) {
        SpringApplication.run(EurekaServer.class);
    }
}
  1. application改端口号,避免冲突
server:
  port: 10086
spring:
  application:
    name: eureka-server
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:10086/eureka
  instance:
    prefer-ip-address: true
    ip-address: 127.0.0.1

修改user-service子服务

依赖

<dependencies>
     <dependency>
         <groupId>org.springframework.bootgroupId>
         <artifactId>spring-boot-starter-webartifactId>
     dependency>
     <dependency>
         <groupId>mysqlgroupId>
         <artifactId>mysql-connector-javaartifactId>
     dependency>
     <dependency>
         <groupId>tk.mybatisgroupId>
         <artifactId>mapper-spring-boot-starterartifactId>
     dependency>

     
     <dependency>
         <groupId>org.springframework.cloudgroupId>
         <artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
     dependency>
 dependencies>

application.yaml配置

server:
  port: 8081

#eureka-client配置
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:10086/eureka
  instance:
    prefer-ip-address: true
    ip-address: 127.0.0.1
    # 30秒更新一次
    lease-renewal-interval-in-seconds: 30
    # 最小过期时长
    lease-expiration-duration-in-seconds: 90

#控制日志级别
logging:
  level:
    com.baidu: debug

# 数据库连接信息
spring:
  application:
    name: user-service
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/spring_study_db?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC
    username: root
    password: admin

#配置mybatis的信息
mybatis:
  #pojo别名扫描
  type-aliases-package: com.baidu.user.pojo
  #加载mybatis映射文件,使用通用Mapper这个就不用了
  #mapper-locations: classpath:mapper/*mapper.xml

启动类加上@EnableDiscoveryClient注解

@EnableDiscoveryClient
@SpringBootApplication
@MapperScan("com.baidu.user.mapper")
public class UserApplication {
    public static void main(String[] args) {
        SpringApplication.run(UserApplication.class);
    }
}

修改consumer-service服务

ribbon(一会就说)、依赖

<dependencies>
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-webartifactId>
    dependency>

    <dependency>
        <groupId>org.springframework.cloudgroupId>
        <artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
    dependency>

    <dependency>
        <groupId>org.springframework.cloudgroupId>
        <artifactId>spring-cloud-starter-netflix-ribbonartifactId>
    dependency>
dependencies>

application.yaml

server:
  port: 8082

spring:
  application:
    name: consumer-server

eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:10086/eureka

根据设置的eureka端口,进入页面,可以看见服务都已经注册上去了。
SpringCloud(二)Eureka服务注册中心-搭建微服务框架_第2张图片
controller
此时服务的消费方再要去调用时,就不会出现把url写死的的情况了,而是动态的向eureka服务注册中心去拉取服务进行调度。

@RestController
@RequestMapping("/consumer")
public class ConsumerController {
    @Autowired
    private RestTemplate restTemplate;
	@Autowired
	private DiscoveryClient discoveryClient;

    /**
     * 远程查询
     * @param id
     * @return
     */
    @GetMapping("{id}")
    public User findById(@PathVariable("id") Integer id) {
        //DiscoveryClient方式
        List<ServiceInstance> instances = discoveryClient.getInstances("user-service");
        //根据实例中的id取出ip和端口
        ServiceInstance instance = instances.get(0);

        String path = instance.getHost() + ":" + instance.getPort();
        String url = "http://" + path + "/user/findById/" + id;
        System.out.println("url:" + url);*/
        User user = restTemplate.getForObject(url, User.class);
        return user;
    }
}

这里的代码虽然解决了这个问题,但是再思考一下?这里的eureka服务、user-service、consumer-service都是注册了一个,如果其中一个服务宕机了,那么就会调用失败,当然这里也包括注册中心服务宕机的情况。
这里只有一个user-service服务,所以只获取第一个即可 , 所以是 .get(0);

  List<ServiceInstance> instances = discoveryClient.getInstances("user-service");
  ServiceInstance instance = instances.get(0);

如果有多个,是不是就要考虑负载均衡问题了呢?

高可用的eureka配置、服务复制

高可用的Eureka Server
Eureka Server即服我的注册中心,在上面的案例中,我们只有一个EurekaServer, 事实上EurekaServer也可以是一个集群,形成高可用的Eureka中心。
服务同步
多个Eureka Server之间也会互相注册为服务,当服务提供者注册到Eureka Server集群中的某个节点时,该节点会把服务的信息同步给集群中的每个节点,从而实现数据同步。因此,无论客户端访问到Eureka Server集群中的任意一个节点,都可以获取到完整的服务列表信息。
配置如下:

server:
# 本注册中心端口号
  port: 10086
spring:
  application:
    name: eureka-server
eureka:
  client:
    service-url:
    # 注册到注册中心端口号为10087
      defaultZone: http://127.0.0.1:10087/eureka
  instance:
    prefer-ip-address: true
    ip-address: 127.0.0.1

在idea中,我们可以快速的复制一个相同的服务,只需要通过修改端口号即可区分。(同一个服务,不同端口号,这些服务干的事同一件事,当其中一个宕机时,不至于一直处于等待,回去找其他服务)
SpringCloud(二)Eureka服务注册中心-搭建微服务框架_第3张图片
此时我们的服务就如同下面所示。当然要记得改你复制的服务的端口号。(我一般是先直接复制,再改原来的那个服务端口号,效果一样)
SpringCloud(二)Eureka服务注册中心-搭建微服务框架_第4张图片
由于现在存在两个注册中心,那么在配置服务时就要都注册进去。
配置如下:

eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:10086/eureka,http://127.0.0.1:10087/eureka

此时我们有多个服务,调用时也有了变化!因为我们要考虑负载均衡问题了,

  • 负载均衡
    在刚才的案例中,我们启动了一个user-service,然后通过DiscoveryClient来获取服务实例信息,然后获取ip和端口来访问。但是实际环境中,我们往往会开启很多个user-service的集群。此时我们获取的服务列表中就会有多个,到底该访问哪一个呢?一般这种情况下我们就需要编写负载均衡算法,在多个实例列表中进行选择。不过Eureka中已经帮我们集成了负载均衡组件: Ribbon, 简单修改代码即可使用。
  • 什么是Ribbon:
    Ribbon是Netflix发布的负载均衡器,它有助于控制HTTP和TCP客户端的行为。为Ribbon配置服务提供者地址列表后,Ribbon 就可基于某种负载均衡算法,自动地帮助服务消费者去请求。Ribbon默认为我们提供了很多的负载均衡算法,例如轮询、随机等。当然,我们也可为Ribbon实现自定义的负载均衡算法。
@RestController
@RequestMapping("/consumer")
public class ConsumerController {
    @Autowired
    private RestTemplate restTemplate;
	@Autowired
	private RibbonLoadBalancerClient client;
    /**
     * 远程查询
     *
     * @param id
     * @return
     */
    @GetMapping("{id}")
    public User findById(@PathVariable("id") Integer id) {
        //RibbonLoadBalancerClient方式
        ServiceInstance instance=client.choose("user-service");

        String path = instance.getHost() + ":" + instance.getPort();
        String url = "http://" + path + "/user/findById/" + id;
        System.out.println("url:" + url);
        User user = restTemplate.getForObject(url, User.class);
        return user;
    }
}

其实还提供了一种注解的简便方式,在方法上添加 @LoadBalanced注解,其实内部实现和上面一样。

@EnableDiscoveryClient
@SpringBootApplication
public class ConsumerApplication {
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }

    public static void main(String[] args) {
        SpringApplication.run(ConsumerApplication.class);
    }
}
//=============================================================================
@RestController
@RequestMapping("/consumer")
public class ConsumerController {
    @Autowired
    private RestTemplate restTemplate;
    /**
     * 远程查询
     * @param id
     * @return
     */
    @GetMapping("{id}")
    public User findById(@PathVariable("id") Integer id) {
        //主配置类中加了@LoadBalanced注解,底层会有拦截器解析服务id,进行负载均衡
        String url="http://user-service/user/findById/"+id;
        User user = restTemplate.getForObject(url, User.class);
        return user;
    }
}

测试结果

启动所有服务。
SpringCloud(二)Eureka服务注册中心-搭建微服务框架_第5张图片
查看端口号为10086的注册中心服务列表
SpringCloud(二)Eureka服务注册中心-搭建微服务框架_第6张图片
查看端口号为10087的注册中心服务列表
SpringCloud(二)Eureka服务注册中心-搭建微服务框架_第7张图片

eureka相关补充

Eureka客户端

服务提供者要向EurekaServer注册服务,并且完成服务续约等工作。

  • 服务注册
    服务提供者在启动时,会检测配置属性中的: eureka.cl ient . register -with-eruekawtrue参数是否正确,事实上默认就是true。如果值确实为true,则会向EurekaServer发起一个Rest请求, 并携带自己的元数据信息,Eureka Server会把这些信息保存到一个双层Map结构中。
    ●第一层Map的Key就是服务id,一般是配置中的spring.applicat ion.name属性
    ●第二层Map的key是服务的实例id。 一般host+ serviceld + port,例如: locahost :user -service:8081
    ●值则是服务的实例对象,也就是说一个服务,可以同时启动多个不同实例,形成集群。

  • 服务续约
    在注册服务完成以后,服务提供者会维持一个心跳(定时向EurekaServer发起Rest请求) ,告诉EurekaServer:“我还活着”。这个我们称为服务的续约(renew) ;有两个重要参数可以修改服务续约的行为:

cureka :
 lnstance:
     lease-expiration-durat ion- in-seconds: 90
     lease-renewal- interval- in- seconds: 30

lease-renewal-interval-in-seconds: 服务续约(renew)的间隔,默认为30秒
lease expiration-duration-in-seconds: 服务失效时间,默认值90秒
也就是说,默认情况下每个30秒服务会向注册中心发送一次心跳, 证明自己还活着。如果超过90秒没有发送心跳,EurekaServer就会认为该服务宕机, 会从服务列表中移除,这两个值在生产环境不要修改,默认即可。

  • 获取服务列表
    当服务消费者启动是,会检测eureka.client. fetch- registry-true参数的值,如果为true,则会从Eureka Server服务的列表只读备份,然后缓存在本地。并且每隔30秒会重新获取并更新数据。我们可以通过下面的参数来修改:
eureka:
    client:
        registry- fetch- interval- seconds: 30

失效剔除和自我保护

  • 服务下线
    当服务进行正常关闭操作时,它会触发一个服务下线的REST请求给Eureka Server,告诉服务注册中心:“我要下线了”。服务中心接受到请求之后,将该服务置为下线状态。
  • 失效剔除
    有时我们的服务可能由于内存溢出或网络故障等原因使得服务不能正常的工作,而服务注册中心并未收到"服务下线”的请求。相对于服务提供者的"服务续约”操作,服务注册中心在启动时会创建一个定时任务,默认每隔一段时间(默认为60秒)将当前清单中超时(默认为90秒)没有续约的服务剔除,这个操作被称为失效剔除。可以通过eureka. server. eviction- interval- timer- in-ms参数对其进行修改,单位是毫秒。
  • 自我保护
    我们关停一个服务,就会在Eureka面板看到一条警告:这是触发了Eureka的自我保护机制。当服务未按时进行心跳续约时,Eureka会 统计服务实例最近15分钟心跳续约的比例是否低于了85%。在生产环境下,因为网络延迟等原因,心跳失败实例的比例很有可能超标,但是此时就把服务剔除列表并不妥当,因为服务可能没有宕机。Eureka在这段时间内不会剔除任何服务实例,直到网络恢复正常。生产环境下这很有效,保证了大多数服务依然可用,不过也有可能获取到失败的服务实例,因此服务调用者必须做好服务的失败容错。我们可以通过下面的配置来关停自我保护:
eureka:
    server :
        enable-self-preservation: false #关闭自我保护模式(缺省为打开)

你可能感兴趣的:(springcloud,java,spring,cloud,eureka,spring,boot,微服务)