分类 SpringCloud 下的文章

客户端负载均衡器:Ribbon

Ribbon实现软负载均衡核心:

  1. 服务发现 :依据服务的名字,把该服务下所有的实例都找出来
  2. 服务选择规则:依据规则策略,如果从多个实例中,选出有效的服务
  3. 服务监听:检测失效的服务,做到高效剔除

Ribbon主要组件:

  1. ServerList (获取所有的服务列表)
  2. IRule(根据规则选择出有效的)
  3. ServerListFilter(过滤掉失效的)

流程:

1.首先通过ServerList获取所有的服务列表
2.然后通过ServerListFilter过滤掉一部分地址
3.最后通过IRule选择一个实例,作为最终目标结果

默认负载均衡策略

  • 轮询

如何修改负载均衡策略

  • 去SpringCloud官网搜索Ribbon策略的配置(搜索:Customizing the Ribbon Client),可以找到从配置文件配置的方法

    需要在客户端配置如下:

PRODUCT:
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

PRODUCT : 应用名

RandomRule :负载均衡策略为随机 (Ribbon的默认策略默认是RoundRobinRule

所有的负载均衡策略在IRule接口中可以看到:

1554275423045.png

应用间通信

HTTP vs RPC

  • Spring Cloud (HTTP)
  • Dubbo (RPC)

1.SpringCloud中服务间两种restful调用方式

  • RestTemplate
  • Feign

方式一、RestTemplate:是一个http客户端

RestTemplate有三种方式

1.直接写url :http://localhost:8080/msg

使用restTemplate.getForObject("http://localhost:8080/msg", String.class);方法

@RestController
@Slf4j
public class ClientController {
    @GetMapping("/getProductMsg")
    public String getProductMsg(){
        //1. 第一种方式
        RestTemplate restTemplate = new RestTemplate();
        String response = restTemplate.getForObject("http://localhost:8080/msg", String.class);
        log.info("response={}",response);
        return response;
    }
}

缺点:地址是写死的,如果对方服务有多台,这样很不好。

2.通过 ServiceInstance serviceInstance = loadBalancerClient.choose("ServerId"); 获得服务的信息。
ServerId :服务的名字

  @Autowired
  private LoadBalancerClient loadBalancerClient;
@RestController
@Slf4j
public class ClientController {
    @Autowired
    private LoadBalancerClient loadBalancerClient;
    
    @GetMapping("/getProductMsg")
    public String getProductMsg(){
        //2.第二种
        ServiceInstance serviceInstance= loadBalancerClient.choose("PRODUCT");
        String url = String.format("http://%s:%s",serviceInstance.getHost(),serviceInstance.getPort())+"/msg";
        RestTemplate restTemplate = new RestTemplate();
        String response = restTemplate.getForObject(url, String.class);
        log.info("response={}",response);
        return response;
    }
  1. 先增加@Autowired LoadBalancerClient
  2. 通过loadBalancerClient.choose("ServerId");返回一个ServiceInstance
  3. serviceInstance.getHost()serviceInstance.getPort()可以分别获得主机以及端口号
  4. 拼接主机以及端口
  5. 通过第一种方法的restTemplate.getForObject(url, String.class)请求数据
方法说明返回例子
serviceInstance.getUri()路径http://localhost:8080
serviceInstance.getHost()主机localhost
serviceInstance.getServiceId()服务idPRODUCT
serviceInstance.getPort()端口号8080

优点:我们可以不知道服务的路径,通过服务Id或者服务路径信息
缺点:编码比较繁琐

3.用@LoadBalanced 可在restTemplate里使用应用名字来访问

@Component
public class RestTemplateConfig {
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
}
@RestController
@Slf4j
public class ClientController {
    @Autowired
    private RestTemplate restTemplate;

    @GetMapping("/getProductMsg")
    public String getProductMsg(){
        //3.第三种方式 利用@LoadBalanced 可在restTemplate里使用应用名字
        String response = restTemplate.getForObject("http://PRODUCT/msg", String.class);
        log.info("response={}",response);
        return response;
    }
}
总结

RestTemplate有三种方式:

  1. url写死,用new RestTemplate () 访问url来获取消息
  2. 使用loadBalancerClient获取到url,然后使用new RestTemplate () 访问url 获取信息
  3. 利用@LoadBalanced注解restTemplate,然后restTemplate就可以使用应用名称来访问(通过ribbon依据某种规则,如简单轮询、随机连接去连接目标服务来实现负载均衡)

方式二、Feign

  • 本质还是http客户端
  • 声明式REST客户端(伪RPC)
  • 才用了基于接口的注解
  • 内部也使用Ribbon做负载均衡

通过feign我们能把http远程调用对开发者完全透明,得到与调用本地方法一样的编码体验

使用方法:
1.在pom中增加Feign依赖
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-feign</artifactId>
            <version>2.0.0.M1</version>
        </dependency>
2.在启动类增加注解@EnableFeignClients 开启Feign功能 (org.springframework.cloud.netflix.feign.EnableFeignClients;)
package com.imooc.order;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.feign.EnableFeignClients;

@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class OrderApplication {
    public static void main(String[] args) {
        SpringApplication.run(OrderApplication.class, args);
    }
}
3.在客户端定义好要调用的服务和接口
  • 接口使用@FeignClient(name="需要调用的应用名称")
  • 方法的注解是需要调用接口的地址(也可以使用@RequestMapping、@PostMapping)
    如我们想调用的接口地址为“/order/addOrder”,且为GET请求, 那就写@GetMapping("/order/addOrder")
package com.imooc.order.client;

import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;

@FeignClient(name="product")
public interface ProductClient {
    @GetMapping("/msg")
    String productMsg();
}
4.客户端依赖注入③定义好的接口,然后直接调用即可。
package com.imooc.order.controller;

import com.imooc.order.client.ProductClient;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
@Slf4j
public class ClientController {
    @Autowired
    private ProductClient productClient;

    @GetMapping("/getProductMsg")
    public String getProductMsg(){
        String response = productClient.productMsg();
        log.info("response={}",response);
        return response;
    }
}

1.两个注解:

  • @EnableEurekaServer——在启动类上添加
  • @EnableDiscoveryClient或@EnableEurekaClient——启动类加
因为Eureka支持多种注册中心,

EnableDiscoveryClient注解在使用任何Eureka支持的注册中心时都可以使用,是一个通用的注解.

而EnableEurekaClient注解只有在使用Eureka作为注册中心时才可以使用,是Eureka注册中心的专用注解,在使用其他注册中心的时候不管用.

所以,在使用Eureka作为注册中心的时候,推荐使用EnableEurekaClient注解,在使用其他注册中心的时候,使用

EnableDiscoveryClient注解

1.@EnableEurekaServer——在启动类上添加

提供服务注册的功能,各个服务节点启动后,会在EurekaServer注册,这样EurekaServer就有了所有服务节点的信息

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

2.@EnableDiscoveryClient或@EnableEurekaClient——启动类加

表示这个是一个Eureka客户端

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

2.功能

  • 心跳检测
  • 健康检查
  • 负载均衡

3.Eureka的高可用(生产建议2台以上)

配置原理:将注册中心分别指向其他的注册中心

4.分布式系统中,服务注册中心是最重要的基础部分

1.简单情况

1554196218744.png

2.为了达到Eureka的高可用,可以多个Eureka互相注册。

1554198693041.png

3.我们需要修改两处:

  • Eureka Client
  • Eureka Server

3.1 Eureka Client

在Client的配置文件中,分别配置所有的Eureka Server地址

eureka:
  client:
    service-url:
      defaultZone: http://192.168.93.1:8761/eureka/,http://192.168.93.1:8762/eureka/,http://192.168.93.1:8763/eureka/

3.2 Eureka Server

例如我们需要3个Eureka Server

在每个Server的配置文件中,修改其注册地址,使其两两注册。

如上图:

  • Eureka1 注册 http://localhost:8762/ 和 http://localhost:8763/

    eureka:
      client:
        service-url:
          defaultZone: http://192.168.93.1:8762/eureka/,http://192.168.93.1:8763/eureka/
  • Eureka2 注册 http://localhost:8761/ 和 http://localhost:8763/

    eureka:
      client:
        service-url:
          defaultZone: http://192.168.93.1:8761/eureka/,http://192.168.93.1:8763/eureka/
  • Eureka3 注册 http://localhost:8761/ 和 http://localhost:8762/

    eureka:
      client:
        service-url:
          defaultZone: http://192.168.93.1:8761/eureka/,http://192.168.93.1:8762/eureka/

Eureka是Netflix开发的服务发现框架,本身是一个基于REST的服务,主要用于定位运行在AWS域中的中间层服务,以达到负载均衡和中间层服务故障转移的目的。SpringCloud将它集成在其子项目spring-cloud-netflix中,以实现SpringCloud的服务发现功能。

Eureka包含两个组件:Eureka Server和Eureka Client。

Eureka Server提供服务注册服务,各个节点启动后,会在Eureka Server中进行注册,这样EurekaServer中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观的看到。

Eureka Client是一个java客户端,用于简化与Eureka Server的交互,客户端同时也就是一个内置的、使用轮询(round-robin)负载算法的负载均衡器。

在应用启动后,将会向Eureka Server发送心跳,默认周期为30秒,如果Eureka Server在多个心跳周期内没有接收到某个节点的心跳,Eureka Server将会从服务注册表中把这个服务节点移除(默认90秒)。

Eureka Server之间通过复制的方式完成数据的同步,Eureka还提供了客户端缓存机制,即使所有的Eureka Server都挂掉,客户端依然可以利用缓存中的信息消费其他服务的API。综上,Eureka通过心跳检查、客户端缓存等机制,确保了系统的高可用性、灵活性和可伸缩性。

[scode type="yellow"] 版本: SpringBoot 2.0.0.M3 SpringCloud Finchley.M2 JDK 8 [/scode]

SpringCloud Eureka

  • 基于Netflix Eureka 做了二次开发
  • 两个组件

    • Eureka Server 注册中心
    • Eureka Client 服务注册

Eureka Server 注册中心创建

1.新建一个Spring项目

1554192241739.png

1554192295308.png

2.修改pom文件

因为我使用的是里程碑版本,所以要增加一个

完整的pom文件:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.0.M3</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.imooc</groupId>
    <artifactId>eureka</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>eureka</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Finchley.M2</spring-cloud.version>
    </properties>

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

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

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <repositories>
        <repository>
            <id>spring-snapshots</id>
            <name>Spring Snapshots</name>
            <url>https://repo.spring.io/snapshot</url>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
        </repository>
        <repository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
    </repositories>

    <pluginRepositories>
        <pluginRepository>
            <id>spring-snapshots</id>
            <name>Spring Snapshots</name>
            <url>https://repo.spring.io/snapshot</url>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
        </pluginRepository>
        <pluginRepository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </pluginRepository>
    </pluginRepositories>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

3.修改启动类

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

@EnableEurekaServer : 表示这个项目有注册中心这个功能

4.运行项目

发现有报错,先不用管:
1554192790935.png

浏览器输入:http://localhost:8080

1554192832233.png

这就是一个注册中心。

报错的问题:

这是由于这个应用虽然是一个Server端,但是同时也是一个Client端,它也需要找一个注册中心把自己注册上去。

点进去@EnableEurekaServer发现 @EnableDiscoveryClient ,它包含了Client:

@EnableDiscoveryClient
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(EurekaServerMarkerConfiguration.class)
public @interface EnableEurekaServer {
}

解决报错:

修改配置文件,我使用yml格式的——application.yml

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/

再次启动,发现启动是的时候还会报错,是因为它自己既是客户端又是服务端,服务端还没启动好,客户端就启动,就会报错。 它采用的是一种心跳的方式,隔一段时间就OK了

登陆 http://localhost:8080

1554193493997.png

此时Application 显示出一条信息,名字为:UNKNOWN

这个应用本来就是注册中心,所以不需要在上面显示,而且为了区分显示出端口号,我们需要修改端口号。以及修改Application 的名字:

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/
    register-with-eureka: false
spring:
  application:
    name: eureka
server:
  port: 8761

Eureka Client 注册

1.新建项目

1554194992683.png

2.修改pom文件:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.0.M3</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.imooc</groupId>
    <artifactId>client</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>client</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Finchley.M2</spring-cloud.version>
    </properties>

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

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

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <repositories>
        <repository>
            <id>spring-snapshots</id>
            <name>Spring Snapshots</name>
            <url>https://repo.spring.io/snapshot</url>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
        </repository>
        <repository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
    </repositories>

    <pluginRepositories>
        <pluginRepository>
            <id>spring-snapshots</id>
            <name>Spring Snapshots</name>
            <url>https://repo.spring.io/snapshot</url>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
        </pluginRepository>
        <pluginRepository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </pluginRepository>
    </pluginRepositories>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

3.修改启动类增加注解@EnableDiscoveryClient

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

4.修改配置文件

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/
  #自定义连接
#  instance:
#    hostname: clientname

# 修改应用名称
spring:
  application:
    name: client