搭建注册中心

服务治理

RPC远程调用框架的核心技术思想:在于注册中心,使用注册中心管理每个服务与服务之间的依赖关系叫做服务治理

为什么需要服务治理:

如下图:

服务注册与发现

我们知道任何rpc远程框架中,都会有一个注册中心,SpringCloud支持三种注册中心:Eureka, Consul(go语言编写), ZookeeperDubbo支持常用两种注册中心:ZookeeperRedis,那么什么是注册中心呢?

注册中心

存放服务地址相关信息(接口地址),如下图

上图使用注册中心的步骤

  • 首先启动注册中心
  • 启动会员服务,下面有几个概念
    • 服务提供者:提供服务接口
    • 服务消费者:调用别人接口进行使用
  • 会员服务在启动的时候,会把当前服务的基本信息(比如服务地址和端口)以别名的形式注册到注册中心上
  • 订单服务在调用远程接口的时候,使用服务别名也就是key去注册中心获取实际的rpc远程调用地址
  • 一旦获取到rpc远程调用地址后,在本地使用HttpClient技术实现调用

总结什么是服务注册,什么是服务发现

服务注册:将服务信息注册到注册中心上

服务发现:从注册中心上获取服务信息

搭建Eureka注册中心

导入Maven依赖

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

配置application.yml

server:
  port: 8100   # 服务端口号

eureka:
  instance:
    hostname: 127.0.0.1   # 注册中心ip地址
  client:
    serviceUrl:
      # 注册中心地址
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka
    # 因为自己是注册中心,所以不需要把自己注册到注册中心(集群的时候需要注册)
    register-with-eureka: false
    # 因为自己是注册中心,所以不需要再检索服务信息
    fetch-registry: false

 在启动类中添加@EnableEurekaServer注解

@EnableEurekaServer
@SpringBootApplication
public class EurekaServerApplication {

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

然后访问127.0.0.1:8100即可

搭建服务提供者注册到Eureka

导入Maven依赖

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

配置application.yml

server:
  port: 8001
spring:
  application:
    name: jx-member   # 服务注册到服务中心的名称 serviceId

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8100/eureka/   # 当前服务注册到eureka服务中心的地址
# 无需配置,因为默认是true register-with-eureka: true # 需要将服务注册到eureka上 fetch-registry: true # 检索服务

 在启动类上配置上@EnableEurekaClient

@SpringBootApplication
@EnableEurekaClient    // 将当前的服务注册到eureka上
public class ProvideTicketApplication {

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

api.controller包下写返回的数据

一般会在controller上套一层api包,因为有些服务是没有页面的,套一层api包加以区分

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class MemberApiController {

    @GetMapping("/getMember")
    public String getMapper() {
        return "this is member";
    }
}

此时启动项目,再去注册中心就可以看到我们注册的服务了

搭建服务消费者

导入Maven依赖

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

配置application.yml

server:
  port: 8200

spring:
  application:
    name: consumer-user   # 注册到服务中心的名字

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8100/eureka   # 将服务注册到服务中心

在主配置类上加上@EnableDiscoveryClient注解

注意,如果使用了RestTemplate获取远程接口,还需要将RestTemplate加到容器里面,以便使用单例模式

@EnableDiscoveryClient    // 开启发现服务功能
@SpringBootApplication
public class ConsumerUserApplication {

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

    @LoadBalanced    // 启用负载均衡机制,如果有多个服务提供者,会轮询着访问
    @Bean
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
}

然后远程调用接口

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
public class OrderApp {
    // RestTemplate是由SpringBoot Web组件提供的,默认整合ribbon负载均衡器
    // rest方式底层采用HttpClient技术
    @Autowired
    private RestTemplate restTemplate;

    /**
     * 在SpringCloud中有两种方式调用其他服务提供的接口
     * rest(SpringBoot Web)跟fegin(SpringCloud)
     * @return
     */
    @RequestMapping("/getOrder")
    public String getMember() {
        // 有两种方式调用:一种是采用服务别名方式调用,一种是直接通过地址调用(不会走注册中心)
        String forObject = restTemplate.getForObject("http://jx-member/getMember", String.class);
        return "这是:" + forObject;
    }
}

搭建Eureka注册中心集群

首先,为什么要搭建集群,微服务rpc远程调用框架最核心的点在于服务治理,也就是注册中心,如果因为某种原因,注册中心出故障了,可能会导致整个微服务环境不可用,那解决这一问题的办法就是搭建注册中心集群

搭建eureka集群环境的思路:采用互相注册的原理,形成一组相互注册的注册中心,从而实现数据的相互同步,达到高可用效果

那么如何搭建呢?

首先需要新建一个注册中心,也就是一个项目,然后修改两个项目的配置文件

  • 端口:两个注册中心设置不同的端口
  • 名称:两个注册中心必须有名称,也就是spring.application.name
  • 服务注册:将那两个注册服务相关的属性设置为true
  • 注册中心地址:注册中心地址写上另外一个的(其实只需要改一下端口号即可),如果有多个注册中心,就用逗号隔开

eureka-server1

server:
  port: 8100   # 服务端口号

eureka:
  instance:
    hostname: 127.0.0.1   # 注册中心ip地址
  client:
    serviceUrl:
      # 注册中心地址
      defaultZone: http://${eureka.instance.hostname}:9100/eureka
    # 因为自己是注册中心,所以不需要把自己注册到注册中心(集群的时候需要注册)
    register-with-eureka: true
    # 因为自己是注册中心,所以不需要再检索服务信息
    fetch-registry: true
spring:
  application:
    name: eureka-server1

eureka-server2

server:
  port: 9100   # 服务端口号
eureka:
  instance:
    hostname: 127.0.0.1   # 注册中心ip地址
  client:
    serviceUrl:
      # 注册中心地址
      defaultZone: http://${eureka.instance.hostname}:8100/eureka
    register-with-eureka: true
    fetch-registry: true
spring:
  application:
    name: eureka-server2

然后访问81009100两个端口,可以看到两个服务都已经互相注册好了

客户端调用Eureka集群

因为此时有多个注册中心了,所以服务提供者和消费者要将这几个注册中心的地址都带上

9100

8100

可以看到只有9100上有服务信息,8100上没有,这是因为,在注册过程中只会保证有一个注册中心有相应的服务数据,当9100宕机后,数据会自动同步到8100

Eureka自我保护机制

Eureka分为两种角色

    EurekaClient(注册客户端)

    EurekaServer(注册中心服务端)

首先会有这么一个现象由于订单服务开启了两台,一台8000,后面叫它8000,一台8090,后面叫它8090,那么现在会员服务去访问订单服务,会先去到注册中心,然后基于负载均衡机制轮换着访问80008090,但是如果此时8000宕机了,那么就不能访问了,就得从注册中心剔除掉,否则仍旧会轮换着访问,而此时8000已经不能用了,再去访问就会出问题

为什么需要Eureka自我保护机制基于上面的现象,我们知道,当8000宕机后,就会从注册中心剔除,但是有这么一种情况,如果8000并没有宕机,只是跟注册中心的网络不通畅,那么为了防止这种情况下误将8000从注册中心剔除掉,就得使用Eureka的自我保护机制了

自我保护机制默认情况下EurekaClient会定时向EurekaServer端发送心跳包,来告诉EurekaServer自己是存活状态。相反,如果EurekaServer在一定时间(默认90s)内没有收到EurekaClient发送的心跳包,就会直接从注册中心中剔除该EurekaClient。但是在短时间内丢失了大量的服务实例心跳,这时候EurekaServer会开启自我保护机制,不会去剔除该服务

什么时候开启自我保护机制

建议在本地开发环境禁止自我保护机制,在生产环境下开启自我保护机制

那么怎样禁止自我保护呢?

需要在Server端的yml加上下面的配置

即如下配置

server:
  port: 8100   # 服务端口号

eureka:
  instance:
    hostname: 127.0.0.1   # 注册中心ip地址
  client:
    serviceUrl:
      # 注册中心地址
      defaultZone: http://${eureka.instance.hostname}:9100/eureka
    # 因为自己是注册中心,所以不需要把自己注册到注册中心(集群的时候需要注册)
    register-with-eureka: true
    # 因为自己是注册中心,所以不需要再检索服务信息
    fetch-registry: true
  server:
    enable-self-preservation: false   # 测试时关闭自我保护,保证不可用服务及时剔除
    eviction-interval-timer-in-ms: 2000   # 间隔两秒钟剔除一次
spring:
  application:
    name: eureka-server1

然后还要在服务端配置如下信息

即如下配置

server:
  port: 8001
spring:
  application:
    name: jx-member   # 服务注册到服务中心的名称 serviceId

eureka:
  # 心跳检测与续约时间
  # 检测时将值设置小些,保证服务关闭后注册中心能够及时剔除
  instance:
    # Eureka客户端向服务端发送心跳时间间隔,单位为秒
    lease-renewal-interval-in-seconds: 1
    # Eureka服务端在收到最后一次心跳后等待的时间上限,单位为秒,超时则剔除
    lease-expiration-duration-in-seconds: 2
    prefer-ip-address: true
  client:
    service-url:
      defaultZone: http://localhost:8100/eureka/, http://localhost:9100/eureka/   # 当前服务注册到eureka服务中心的地址
    register-with-eureka: true     # 需要将服务注册到eureka上
    fetch-registry: true    # 检索服务

使用zookeeper作为注册中心

首先配置yml文件

server:
  port: 8002
spring:
  application:
    name: zk-member
  cloud:
    zookeeper:
      # 注册到zookeeper地址
      connect-string: 192.168.253.132:2181

然后在入口类上标注上@EnableDiscoveryClient注解

@SpringBootApplication
@EnableDiscoveryClient
public class ZkpServerApplication {

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

然后启动程序即可将该服务注册到zookeeper

当注册到了zookeeper注册中心后,其余的操作----使用会员服务调用订单服务的过程和Eureka做注册中心的时候是一样的,其实只是将一个注册中心变化了而已,其他的都不变

使用Consul搭建注册中心

安装

Consul是一套开源的分布式服务发现和配置管理系统,由Go语言开发

安装Consul,只需要把consul.exe放到一个英文目录下即可

然后打开cmd来到这个目录,执行命令consul agent -dev -ui -node=cy 即可启动consul

-dev:开发服务器模式启动

-node:结点名为cy

-ui:可以用界面访问,默认能访问

测试访问地址:http://localhost:8500

上面就是consul的界面,还是很漂亮的呢

实现服务注册与发现

首先导入Maven依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>

然后配置yml

server:
  port: 8501  # 服务端口号
spring:
  application:
    name: consul-member   # 服务名称
  cloud:
    consul:
      host: localhost   # consul地址
      port: 8500    # consul端口号
      discovery:
        # 默认情况下,服务注册到注册中心的地址是随机生成的英文:PC-jinxin
        # 指定服务注册到注册中心的地址为下面的ip地址
        hostname: 192.168.10.220

然后启动项目即可在consul中找到该服务

DiscoveryClient使用

有时我们希望能够在本地服务中获取一些注册中心的相关的信息,就可以调用这个API来获取相关信息

DiscoveryClient实际上是一个接口,想要使用只需要让Spring替我们自动注入即可

@RestController
public class ConsulMemberController {
    @Autowired
    private DiscoveryClient discoveryClient;


    @RequestMapping("/discoveryClient")
    public List<ServiceInstance> discoveryClient() {
        /**
         * getInstances:里面传入想要获取的服务的serverId
         * 返回一个集合,因为有可能做集群,所以要用集合接收
         */
        List<ServiceInstance> instances = discoveryClient.getInstances("consul-member");
        instances.forEach((e) -> {
            URI uri = e.getUri();// 获取url
            System.out.println("url:" + uri);
        });
        return instances;
    }
}

其他的一些api不做赘述

Spring Cloud客户端负载均衡

本地负载均衡与服务器端负载均衡的区别

RibbonSpring Cloud本地(客户端)的负载均衡器

那么它是如何实现的?

本地负载均衡实现算法:请求数 % 服务器数量,就可以得到对应服务器位置的下标

例:

List[0] ---> 127.0.0.1:8000

List[1] ---> 127.0.0.1:8001

假设总请求数为1 (总请求数为1:订单服务调用了一次会员服务)

会员服务器集群2

那么 1 % 2 = 1,即下标是1,取List[1]

如果 2 % 2 = 0,即下标是0,取List[0]

如果 3 % 2 = 1,即下标是1,取List[1]

如果 4 % 2 = 0,。。。。。。

纯手写类似于Ribbon本地客户端负载均衡效果

@RestController
public class ExtRibbonController {
    @Autowired
    private DiscoveryClient discoveryClient;
    @Autowired
    private RestTemplate restTemplate;
    // 请求总数
    private int reqCount = 1;

    @RequestMapping("/ribbonMember")
    public String ribbonMember() {
        // 1. 获取对应服务器远程调用地址
        String instance = getInstance();
        if (null == instance) {
            return null;
        }
        String serverUri = instance + "/getOrder";
        // 2. 使用rest方式发送请求
        return restTemplate.getForObject(serverUri, String.class);
    }

    /**
     * 获取当前要调用的服务的uri
     * @return
     */
    private String getInstance() {
        List<ServiceInstance> instances = discoveryClient.getInstances("consul-order");
        if ((null == instances) || (instances.size() <= 0)) {
            return null;
        }
        // 算法:下标数 = 请求总数 % 服务集群数
        int serverCount = instances.size();
        int serverIndex = reqCount++ % serverCount;
        return instances.get(serverIndex).getUri().toString();
    }
}

Ribbon本地负载均衡与Nginx服务器端负载均衡区别

Ribbon本地负载均衡原理:在调用接口的时候,会在eureka注册中心上获取注册信息服务列表,获取到之后,缓存在jvm,然后本地实现rpc远程调用技术进行调用,就是客户端实现负载均衡

Nginx实现服务器端负载均衡原理:客户端的请求都会交给Nginx,然后由nginx实现转发请求,即负载均衡在服务器端实现

应用场景:本地负载均衡适合在微服务rpc远程调用,比如dubbospringcloud

服务器端负载均衡适合于针对于服务器端的,比如Tomcatjetty

posted @ 2018-12-06 15:07  Jin同学  阅读(847)  评论(0)    收藏  举报