简介

本文通过一个简单的demo, 介绍ribbon的使用入门. 服务间调用使用RestTemplate发送请求. 会演示两种ribbon的使用方式.

特别说明: demo只为演示功能, 与具体业务无关, 切勿强行对号入座.

demo项目结构

ribbon-demo为父级模块, 其下三个子模块, 代表三个服务.

order-service: 订单服务, 部署两台, 由于是本地演示, 分别监听8080和8081两个端口, 会被user-service调用.

pay-service: 支付服务, 部署两台, 由于是本地演示, 分别监听7070和7071两个端口, 会被order-service调用.

user-service: 用户服务, 部署一台, 监听本地9999端口, 从浏览器访问.

order-service和pay-service部署两台, 是为了演示负载均衡效果, user-service直接通过浏览器输入固定url访问, 所以只有一台.

ribbon-demo下的pom.xml文件内容

<?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.forest.xs.sfg</groupId>
    <artifactId>ribbon-demo</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>pom</packaging>

    <modules>
        <module>user-service</module>
        <module>order-service</module>
        <module>pay-service</module>
    </modules>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-parent</artifactId>
        <version>2.3.1.RELEASE</version>
    </parent>

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

    <properties>
        <maven.compiler.source>15</maven.compiler.source>
        <maven.compiler.target>15</maven.compiler.target>
    </properties>

</project>

user-service结构以及代码

pom.xml
<?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">
    <parent>
        <artifactId>ribbon-demo</artifactId>
        <groupId>com.forest.xs.sfg</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>user-service</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
            <version>2.2.2.RELEASE</version>
        </dependency>
    </dependencies>

    <properties>
        <maven.compiler.source>15</maven.compiler.source>
        <maven.compiler.target>15</maven.compiler.target>
    </properties>

</project>
application.yml
server:
  port: 9999	# 用户服务监听的端口
spring:
  application:
    name: user-service	# 用户服务服务名
order-service:	# 配置要调用的订单服务信息
  name: order-service	# 要调用的订单服务服务名
  ribbon:	# 要调用的订单服务的地址列表, 每次请求会根据负载均衡算法选择其中一个发送请求, 完整的格式为"要调用的服务名.ribbon.listOfServices: 要调用的服务列表"
    listOfServers: localhost:8080,localhost:8081

UserServiceApplication.java(用户服务启动类)
package com.forest.xs.sfg;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

/**
 * @Author:周建林
 * @Time:2021/3/8 0:19
 * @Description
 */
@SpringBootApplication
public class UserServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(UserServiceApplication.class, args);
    }

    @Bean
    public RestTemplate getRestTemplate(RestTemplateBuilder builder) {
        return builder.build();
    }
}

ProfileConfiguration.java(取配置文件的配置信息的工具类)
package com.forest.xs.sfg.configuration;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;

/**
 * @Author:周建林
 * @Time:2021/3/8 0:43
 * @Description
 */
@Configuration
public class ProfileConfiguration {
    @Value("${order-service.name}")
    private String orderServiceName;

    public String getOrderServiceName() {
        return orderServiceName;
    }
}

UserController.java(用户服务入口类, ribbon使用方式一)

这里演示的负载均衡调用方法一: 通过注入"LoadBalancerClient"客户端, 调用其"choose"方法获取一个"ServiceInstance"(服务实例), 从服务实例种取出主机和端口向其发送请求.

package com.forest.xs.sfg.userservice;

import com.forest.xs.sfg.configuration.ProfileConfiguration;
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.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

/**
 * @Author:周建林
 * @Time:2021/3/8 0:19
 * @Description
 */
@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
    private RestTemplate restTemplate;
    @Autowired
    private LoadBalancerClient loadBalancerClient;
    @Autowired
    private ProfileConfiguration cfg;

    @GetMapping("/info/{id}/{orderId}")
    public String getUserInfoWithOrderInfo(@PathVariable String id, @PathVariable String orderId) {
        String orderServiceName = cfg.getOrderServiceName();
        ServiceInstance serviceInstance = loadBalancerClient.choose(orderServiceName);
        String url = String.format("http://%s:%s/order/info/%s", serviceInstance.getHost(), serviceInstance.getPort(), orderId);
        String orderInfo = restTemplate.getForObject(url, String.class);
        return String.format("userId: %s. OrderInfo: %s", id, orderInfo);
    }
}

order-service结构以及代码

pom.xml
<?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">
    <parent>
        <artifactId>ribbon-demo</artifactId>
        <groupId>com.forest.xs.sfg</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>order-service</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
            <version>2.2.2.RELEASE</version>
        </dependency>
    </dependencies>

    <properties>
        <maven.compiler.source>15</maven.compiler.source>
        <maven.compiler.target>15</maven.compiler.target>
    </properties>

</project>
application.yml
server:
  port: 8080	# 订单服务监听的端口, 启动时后会修改端口为8081再次启动一次
spring:
  application:
    name: order-service	# 订单服务服务名
pay-service:	# 配置要调用的支付服务信息
  name: pay-service	# 要调用的支付服务服务名
  ribbon:	# 要调用的支付服务的地址列表, 每次请求会根据负载均衡算法选择其中一个发送请求, 完整的格式为"要调用的服务名.ribbon.listOfServices: 要调用的服务列表"
    listOfServers: localhost:7070,localhost:7071

OrderServiceApplication.java(订单服务启动类, ribbon使用方式二))
package com.forest.xs.sfg;

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

/**
 * @Author:周建林
 * @Time:2021/3/9 0:36
 * @Description
 */
@SpringBootApplication
public class OrderServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(OrderServiceApplication.class, args);
    }

    @Bean
    // 使用LoadBalanced注解实现负载均衡
    @LoadBalanced
    public RestTemplate getRestTemplate(RestTemplateBuilder builder) {
        return builder.build();
    }
}


ProfileConfiguration.java(取配置文件的配置信息的工具类)
package com.forest.xs.sfg.configuration;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;

/**
 * @Author:周建林
 * @Time:2021/3/8 0:43
 * @Description
 */
@Configuration
public class ProfileConfiguration {
    @Value("${server.port}")
    private String serverPort;

    @Value("${pay-service.name}")
    private String payServiceName;

    public String getServerPort() {
        return serverPort;
    }

    public String getPayServiceName() {
        return payServiceName;
    }
}


OrderController.java(订单服务入口类, ribbon使用方式二)
package com.forest.xs.sfg.orderservice;

import com.forest.xs.sfg.configuration.ProfileConfiguration;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

/**
 * @Author:周建林
 * @Time:2021/3/8 0:19
 * @Description
 */
@RestController
@RequestMapping("/order")
public class OrderController {
    @Autowired
    private ProfileConfiguration cfg;
    @Autowired
    private RestTemplate restTemplate;

    @GetMapping("/info/{id}")
    public String getUserInfoWithOrderInfo(@PathVariable String id) {
        String orderInfo = String.format("I am a order, from %s port.", cfg.getServerPort());
        Double money = 1000.02;
        String url = String.format("http://%s/pay/info/%s/%s", cfg.getPayServiceName(), id, money);
        // RestTemplate在注入时增加了"LoadBalanced"注解, 此注解会让框架自动帮忙从地址列表中选择一个目标地址并向其发送请求
        String payInfo = restTemplate.getForObject(url, String.class);
        System.out.println(orderInfo);
        return orderInfo + " PayInfo: " + payInfo;
    }
}


pay-service结构以及代码

pom.xml
<?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">
    <parent>
        <artifactId>ribbon-demo</artifactId>
        <groupId>com.forest.xs.sfg</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>pay-service</artifactId>

    <properties>
        <maven.compiler.source>15</maven.compiler.source>
        <maven.compiler.target>15</maven.compiler.target>
    </properties>

</project>

application.yml
server:
  port: 7071	# 支付服务监听的端口, 启动时后会修改端口为7071再次启动一次
spring:
  application:
    name: pay-service	# 支付服务服务名

PayServiceApplication.java(支付服务启动类)
package com.forest.xs.sfg;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * @Author:周建林
 * @Time:2021/3/9 1:03
 * @Description
 */
@SpringBootApplication
public class PayServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(PayServiceApplication.class, args);
    }
}


ProfileConfiguration.java(取配置文件的配置信息的工具类)
package com.forest.xs.sfg.configuration;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;

/**
 * @Author:周建林
 * @Time:2021/3/8 0:43
 * @Description
 */
@Configuration
public class ProfileConfiguration {
    @Value("${server.port}")
    private String serverPort;

    public String getServerPort() {
        return serverPort;
    }
}


PayController.java(支付服务入口类)
package com.forest.xs.sfg.payservice;

import com.forest.xs.sfg.configuration.ProfileConfiguration;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @Author:周建林
 * @Time:2021/3/9 1:05
 * @Description
 */
@RestController
@RequestMapping("/pay")
public class PayController {
    @Autowired
    private ProfileConfiguration cfg;

    @RequestMapping("/info/{orderId}/{money}")
    public String payOrder(@PathVariable String orderId, @PathVariable Double money) {
        String payInfo = String.format("I am pay service, from %s port. OrderId: %s, pay money: %s.", cfg.getServerPort(), orderId, money);
        System.out.println(payInfo);
        return payInfo;
    }
}


启动所有服务

  • 用户服务一台, 9999端口
  • 订单服务两台, 8080和8081端口
  • 支付服务两台, 7070和7071端口

浏览器向用户服务发起10此请求, 并查看结果

http://127.0.0.1:9999/user/info/uid1/oid1
http://127.0.0.1:9999/user/info/uid2/oid2
http://127.0.0.1:9999/user/info/uid3/oid3
http://127.0.0.1:9999/user/info/uid4/oid4
http://127.0.0.1:9999/user/info/uid5/oid5
http://127.0.0.1:9999/user/info/uid6/oid6
http://127.0.0.1:9999/user/info/uid7/oid7
http://127.0.0.1:9999/user/info/uid8/oid8
http://127.0.0.1:9999/user/info/uid9/oid9
http://127.0.0.1:9999/user/info/uid10/oid10

浏览器的响应信息

订单服务8080的被访问记录

订单服务8081的被访问记录

支付服务7071的被访问记录

支付服务7070的被访问记录

可以看出, 在10次请求中, 两个订单服务8080和8081分别被访问了5次, 两个支付服务, 7071被访问4次, 7070被访问6次, 可见负载均衡起到了作用.