SpringCloud(二):服务调用与负载均衡

一、概念部分

在分布式系统中,各个服务通过服务注册中心来实现服务注册和服务发现。
服务与服务之前会有相互调用的部分,本篇文章将介绍如何使用服务注册中心,来搭建一个服务注册与客户端服务调用的例子。
在上篇文章中我们实现了服务注册中心的集群实现了高可用的服务注册中心,本篇文章我们将使用上篇的服务注册中心来做具体的服务提供和服务调用。

本案例提供三个实例:服务注册中心、服务提供者、服务消费者


调用说明:
1.启动服务注册中心
2.启动服务提供者和服务消费者,并且都将自己注册到服务注册中心
3.服务消费者从服务注册中心获取注册中心的服务信息(获取服务提供者地址给消费者)
4.服务消费者调用服务提供者的服务

二、代码示例

服务提供者

1.创建项目producer-service,并添加依赖到 build.gradle

dependencies {
   compile('org.springframework.cloud:spring-cloud-starter-eureka')
   testCompile('org.springframework.boot:spring-boot-starter-test')
}

2.新建配置文件 application.yml

#spring配置
spring:
  application:
    #应用名称(服务提供者)
    name: producer-service

#服务器配置
server:
  #端口
  port: 8000

#服务中心发现注册配置
eureka:
  client:
    #服务注册中心地址
    service-url:
      defaultZone:  http://service-registry1:9001/eureka

3.编辑启动类 添加 @EnableDiscoveryClient 注解

/**
* 服务提供者
*/
@SpringBootApplication
@EnableDiscoveryClient
public class ProducerServiceApplication {

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

4.新建controller类,提供输出 hello信息的服务

/**
* Hello控制器
*/
@RestController
public class HelloController {

    @Value("${server.display-name}")
    private String displayName; // 注入配置文件中的服务显示名称

    /**
     * 提供输出hello信息
     *
     * @param name 名字
     * @return "hello @name,this is producer service message!"
     */
    @RequestMapping("hello")
    public String hello(@RequestParam String name) {
        return "hello " + name + ", this is " + displayName + " service message!";
    }
}

@EnableDiscoveryClient注解具有注册服务功能。
启动该项目
打开浏览器输入:http://localhost:9001/
发现该服务提供者已经注册到了配置文件中所配置的服务注册中心,实例名称为 PRODUCER-SERVICE

服务消费者

1.创建项目consumer-service,并添加依赖到 build.gradle

dependencies {
   compile('org.springframework.cloud:spring-cloud-starter-eureka')
   compile('org.springframework.cloud:spring-cloud-starter-feign')
   testCompile('org.springframework.boot:spring-boot-starter-test')
}

2.新建配置文件application.yml

#spring配置
spring:
  application:
    #应用名称(服务调用者)
    name: consumer-service

#服务器配置
server:
  #端口
  port: 7000

#服务中心发现注册配置
eureka:
  client:
    #服务注册中心地址
    service-url:
      defaultZone:  [http://service-registry1:9001/eureka](http://service-registry1:9001/eureka)

3.编辑启动类,添加@EnableDiscoveryClient和@EnableFeignClients注解

/**
* 服务调用者
*/
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class ConsumerServiceApplication {

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

@EnableDiscoveryClient:启用服务注册与发现
@EnableFeignClients: 启用Feign进行远程调用

4.新建feign调用接口类

/**
* feign远程调用接口
* FeignClient注解的name属性值要写服务提供者在注册中心注册的服务名称
*/
@FeignClient(name = "producer-service")
public interface HelloRemote {
    /**
     * 远程调用方法
     * @param name 名称
     * @return 远程调用结果
     */
    @RequestMapping(value = "/hello")
    String hello(@RequestParam(value = "name") String name);
}

5.新建controller类,将HelloRemote远程调用接口注入控制器层,然后像调用普通方法一样调用

/**
* Hello控制器
*/
@RestController
public class HelloController {

    private final HelloRemote helloRemote;  // 远程调用接口

    @Autowired
    public HelloController(HelloRemote helloRemote) {
        this.helloRemote = helloRemote;
    }

    /**
     * 输出hello方法
     * @param name 名称
     * @return 远程调用返回值
     */
    @RequestMapping(value = "/hello/{name}")
    public String hello(@PathVariable(value = "name") String name){
        return helloRemote.hello(name);
    }
}

启动项目
发现该服务消费者已经注册到了配置文件中所配置的服务注册中心,实例名称为 CONSUMER-SERVICE


服务调用测试

需要先启动服务注册中心,然后启动服务提供者注册到服务注册中心,然后启动服务消费者注册到服务注册中心。

浏览器访问:http://localhost:8000/hello?name=lanshiqin

通过直接访问服务提供者的控制器方法,返回上图信息,说明服务提供者服务正常。

浏览器访问:http://localhost:7000/hello/lanshiqin

通过服务消费者的控制器方法远程调用服务消费者的方法,发现调用成功返回上图信息。
说明服务消费者成功通过feign客户端调用了服务提供者提供的远程hello方法。

负载均衡

在一些特殊的场景下,很重要的服务提供者要保证高可用性,需要运行多个相同的服务提供者组成集群的方式来给服务消费者提供服务。

为了演示方便,我这里直接为服务提供者项目新建配置文件,设置不同的显示名字和端口。

application-pro1.yml

#spring配置
spring:
  application:
    #应用名称(服务提供者)
    name: producer-service

#服务器配置
server:
  #端口
  port: 8001
  #显示名称
  display-name: producer-service1

#服务中心发现注册配置
eureka:
  client:
    #服务注册中心地址
    service-url:
      defaultZone:  http://service-registry1:9001/eureka

application-pro2.yml

#spring配置
spring:
  application:
    #应用名称(服务提供者)
    name: producer-service

#服务器配置
server:
  #端口
  port: 8002
  #显示名称
  display-name: producer-service2

#服务中心发现注册配置
eureka:
  client:
    #服务注册中心地址
    service-url:
      defaultZone:  http://service-registry1:9001/eureka

application-pro3.yml

#spring配置
spring:
  application:
    #应用名称(服务提供者)
    name: producer-service

#服务器配置
server:
  #端口
  port: 8003
  #显示名称
  display-name: producer-service3

#服务中心发现注册配置
eureka:
  client:
    #服务注册中心地址
    service-url:
      defaultZone:  http://service-registry1:9001/eureka

3.打包

gradle build

4.运行

java -jar producer-service-0.0.1-SNAPSHOT.jar --spring.profiles.active=pro1
java -jar producer-service-0.0.1-SNAPSHOT.jar --spring.profiles.active=pro2
java -jar producer-service-0.0.1-SNAPSHOT.jar --spring.profiles.active=pro3

5.验证

打开浏览器输入:http://localhost:9001/


可以看到服务注册中心有4个PRODUCER-SERVICE的服务提供者,占用端口分别是8000、8001、8002、8003,其中8000端口的提供者是使用默认配置文件application.yml启动的。

我们可以通过服务消费者的方法来测试服务提供者的集群访问。
打开浏览器输入:http://localhost:7000/hello/lanshiqin

浏览器返回信息: producer-service
刷新:


浏览器返回信息: producer-service1

再次刷新:


浏览器返回信息: producer-service2

再次刷新:


浏览器返回信息: producer-service3

发现浏览器通过每次刷新,都会返回不同的producer-service调用结果,证明负载均衡成功调用。

如果手动停止某个服务提供者,其他服务提供者依旧会正常被服务消费者调用,从而实现了高可用的服务。

熔断

如果服务提供者服务部署在不同机器,由于网络等因素的原因导致一个或多个服务提供者无法被调用,或者全部的服务提供者节点大面积停止运行,那么服务消费者这时候就应该友好的进行熔断,并提示用户该服务调用不成功 返回优化处理后的友好信息。

熔断是服务消费者(客户端)主动与服务提供者(服务端)断开,
一个完整的服务调用应该考虑熔断。
我们直接修改服务消费者 项目consumer-service

1.在application.yml里添加配置,开启熔断

#feign远程调用配置
feign:
  hystrix:
    #开启熔断
    enabled: true

2.创建回调类

创建 HelloRemoteFallback 继承 HelloRemote远程调用接口,并实现接口方法。

/**
* 熔断回调类
* 继承feign远程调用接口,并在实现方法中输出回调的信息
*/
@Component
public class HelloRemoteFallback implements HelloRemote {

    /**
     * 远程调用失败,将会回调该方法
     * @param name 名称
     * @return 自定义返回信息
     */
    @Override
    public String hello(@RequestParam(value = "name") String name) {
        return "hello " + name + ", this message is failed";
    }
}

3.添加fallback属性, 在服务熔断的时候返回fallback指定的类 并回调对应方法。

/**
* feign远程调用接口
* FeignClient注解的name属性值要写服务提供者在注册中心注册的服务名称
* FeignClient注解的fallback属性值表示远程调用失败时的回调类
*/
@FeignClient(name = "producer-service",fallback = HelloRemoteFallback.class)
public interface HelloRemote {
    /**
     * 远程调用方法
     * @param name 名称
     * @return 远程调用结果
     */
    @RequestMapping(value = "/hello")
    String hello(@RequestParam(value = "name") String name);
}

运行消费者服务,在提供者集群都正常的时候,发现并没有触发熔断回调。
我们将提供者集群的服务全部关闭,然后再次访问服务消费者,发现触发了熔断回调。


浏览器返回上图信息,表示服务熔断成功。

当提供者服务或提供者集群重新启动并注册到服务注册中心后,消费者服务会结束熔断,并且再次成功调用提服务供者的服务。

项目地址:
https://github.com/lanshiqin/cloud-project

欢迎点赞

注意:
在使用feign远程调用的时候一定要添加依赖:

compile('org.springframework.cloud:spring-cloud-starter-feign’)

否则会出现异常信息:java.lang.NoClassDefFoundError: feign/Logger

错误解决方案与更多优秀文章,请参考其他作者的文章:
http://blog.csdn.net/rickiyeat/article/details/64581355

http://www.ityouknow.com/springcloud/2017/05/12/eureka-provider-constomer.html

推荐阅读更多精彩内容