首页 > 技术文章 > 微服务之服务生产与消费

slimshady 2018-11-08 15:55 原文

服务生产与消费
Eureka 服务治理体系中有 3 个核心角色:服务注册中心、服务提供者、服务消费者。服务注
册中心及服务提供者,在前面已做介绍。
本随笔主要介绍 Eureka 服务治理体系中的服务消费,以 Spring Cloud 服务调用中的
Ribbon+RestTemplate 方式为主。

什么是 Ribbon
Ribbon 是 Netflix 发布的开源项目,主要功能是提供客户端的软件负载均衡算法,将 Netflix 的
中间层服务连接在一起。Ribbon 客户端组件提供一系列完善的配置项如连接超时,重试等。
简单的说,就是在配置文件中列出 Load Balancer(简称 LB)后面所有的机器,Ribbon 会自动
的帮助你基于某种规则(如简单轮询,随即连接等)去连接这些机器。我们也很容易使用
Ribbon 实现自定义的负载均衡算法。

Ribbon 的负载均衡策略

1. 简单轮询负载均衡
  以轮询的方式依次将请求调度不同的服务器,即每次调度执行 i = (i + 1) mod n,并选出第
  i 台服务器。
2. 随机负载均衡
  随机选择状态为 UP 的 Server。
3. 加权响应时间负载均衡
  根据相应时间分配一个 weight,相应时间越长,weight 越小,被选中的可能性越低。
4. 区域感知轮询负载均衡
  复合判断 server 所在区域的性能和 server 的可用性选择 server。

实现原理

 

一个服务注册中心,eureka server,端口为 8761。
micro-service 工程跑了两个副本,端口分别为 8082、8083,分别向服务注册中心注册。
micro-client 端口为 8081,向服务注册中心注册。
当 micro-client 通过 restTemplate 调用 micro-service 的 hello 接口时,因为用 ribbon 进行了负
  载均衡,会轮流的调用 micro-service:8082 和 8083 两个端口的 hello 接口。

 

代码实践

使用之前Eureka 实践中建好的注册中心,之前我们已经建好了服务生产者,现在在服
务生产者中添加接口,以方便服务消费者进行调用。

配置服务生产者

1. 添加 ServiceController 类:

package com.example.controller;

import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
* Created by Administrator on 2018/11/8.
*/

@RestController
public class ServiceController {
@RequestMapping("hello/{name}")
public String hello(@PathVariable String name) {
return name + " say hello Eureka from port:8082";
}

}


2. 参照该服务生产者再建立一个副本,服务端口改为 8083,关键代码如下:
(application.yml)
# HTTP (Tomcat) port
server:
port: 8083
spring:
application:
name: micro-service
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/

(Controller类)
package com.example.controller;

import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
* Created by Administrator on 2018/11/8.
*/
@RestController
public class ServiceController {
@RequestMapping("hello/{name}")
public String hello(@PathVariable String name) {
return name + " say hello Eureka from port:8083";
}
}



建立服务消费者
1. 在 IDEA 中创建一个 Spring Cloud 工程(服务消费者),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>

<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>

<name>EurekaConsumer</name>
<description>Demo project for Spring Boot</description>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</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>Camden.SR7</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>


</project>


2. application.yml 配置

# HTTP (Tomcat) port
server:
port: 8081
spring:
application:
name: micro-client
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
# application parameters
app:
service-url: http://MICRO-SERVICE/


3. 编写启动类,在启动类上添加@SpringBootApplication @EnableDiscoveryClient 注解,声明
 这是一个 Eureka Client。


   ControllerApplication 类:

package com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

@SpringBootApplication
@EnableDiscoveryClient
public class EurekaConsumerApplication {

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


@LoadBalanced
@Bean
RestTemplate restTemplate() {
return new RestTemplate();
}
}


4. 通过@LoadBalanced 注解,调用 netflix 的 ILoadBalancer 实现客户端负载均衡。然后通过
  CallHelloService、CallHelloController 这两个类去调用服务提供者提供的 hello 接口,关键代码
     如下:

  

package com.example.Service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

/**
* Created by Administrator on 2018/11/8.
*/

@Service
public class CallHelloService {

@Value("${app.service-url}")
private String appServiceUrl;

@Autowired
private RestTemplate restTemplate;

public String callHello(String name) {
// 是一个 http client
ResponseEntity result = restTemplate.postForEntity(appServiceUrl + "hello/" + name, null, String.class);
return (String) result.getBody();
}
}






package com.example.Controller;


import com.example.Service.CallHelloService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
* Created by Administrator on 2018/11/8.
*/

@RestController
public class CallHelloController {
@Autowired
private CallHelloService callHelloService;

@GetMapping("hello")
public String hello(String name) {
String result = callHelloService.callHello(name);
return result;
}
}

结果验证
1. 访问 http://localhost:8761/,如下图所示:



2. 然后通过如下方式调用服务消费者可看到如下界面:

3. 刷新页面,测试负载均衡,出现如下界面表示负载均衡配置成功:



注:在整个测试过程中 ,注意所有springboot启动类在项目中的位置(如下)


否则会出现以下情况







推荐阅读