前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Spring Cloud 快速上手之 Ribbon 负载均衡

Spring Cloud 快速上手之 Ribbon 负载均衡

作者头像
架构探险之道
修改2020-05-18 18:16:57
7160
修改2020-05-18 18:16:57
举报
文章被收录于专栏:架构探险之道架构探险之道

Spring Cloud 快速上手之 Ribbon 负载均衡

简介

Spring Cloud Ribbon是基于HTTP和TCP的客户端负载工具,它是基于Netflix Ribbon实现的。通过Spring Cloud的封装,可以轻松地将面向服务的REST 模板请求,自动转换成客户端负载均衡服务调用。提供云端负载均衡,有多种负载均衡策略可供选择,可配合服务发现和断路器使用。

准备工作

开发环境

  • Greenwich.SR5
  • Spring Boot 2.1.5
  • MySQL 5.7
  • JDK 1.8

依赖管理

代码语言:html
复制
<!--负载均衡 Ribbon-->
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>

需要注意的是,在spring-cloud-starter-netflix-eureka-client默认集成了spring-cloud-starter-netflix-ribbon,因此可以不引入。

Ribbon

基本配置

RestTemlate 配置

代码语言:java
复制
@Configuration
public class RpcConfig {

    @Bean
    //添加此注解后,可以直接通过 服务 ID 进行接口调用,而无需输入IP 和端口信息
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

RestController

代码语言:java
复制
@RestController
public class UserController {

    @Autowired
    private IUserService userService;

    @Autowired
    private LoadBalancerClient loadBalancerClient;

    @GetMapping("/user/{id}")
    public User findById(@PathVariable Long id) {
        return userService.findById(id);
    }

    @GetMapping("/instance/{instanceId}")
    public String instance(@PathVariable String instanceId) {
        ServiceInstance choose = loadBalancerClient.choose(instanceId);
        HashMap<String, String> instanceInfo = new HashMap<>();
        return JSON.toJSONString(choose,true);
    }
}

UserServiceImpl

代码语言:java
复制
@Service
public class UserServiceImpl implements IUserService {

    @Autowired
    private RestTemplate restTemplate;

    @Override
    public User findById(Long id) {
        return this.restTemplate.getForObject("http://ms-provider-user-v2/" + id, User.class);
    }

}

接口测试

  • http://localhost:8012/instance/ms-consumer-user-v2-ribbon
代码语言:javascript
复制
GET http://localhost:8012/instance/ms-consumer-user-v2-ribbon

HTTP/1.1 200 
Content-Type: application/json;charset=UTF-8
Content-Length: 1792
Date: Tue, 05 May 2020 06:10:12 GMT

{
  "host": "192.168.0.100",
  "instanceId": "192.168.0.100:8012",
  "metadata": {
    "management.port": "8012"
  },
  "port": 8012,
  "secure": false,
  "server": {
    "alive": true,
    "host": "192.168.0.100",
    "hostPort": "192.168.0.100:8012",
    "id": "192.168.0.100:8012",
    "instanceInfo": {
      "actionType": "ADDED",
      "appName": "MS-CONSUMER-USER-V2-RIBBON",
      "coordinatingDiscoveryServer": false,
      "countryId": 1,
      "dataCenterInfo": {
        "name": "MyOwn"
      },
      "dirty": false,
      "healthCheckUrl": "http://192.168.0.100:8012/actuator/health",
      "healthCheckUrls": [
        "http://192.168.0.100:8012/actuator/health"
      ],
      "homePageUrl": "http://192.168.0.100:8012/",
      "hostName": "192.168.0.100",
      "iPAddr": "192.168.0.100",
      "id": "192.168.0.100:ms-consumer-user-v2-ribbon:8012",
      "instanceId": "192.168.0.100:ms-consumer-user-v2-ribbon:8012",
      "lastDirtyTimestamp": 1588658754217,
      "lastUpdatedTimestamp": 1588658754758,
      "leaseInfo": {
        "durationInSecs": 90,
        "evictionTimestamp": 0,
        "registrationTimestamp": 1588658754758,
        "renewalIntervalInSecs": 30,
        "renewalTimestamp": 1588659024755,
        "serviceUpTimestamp": 1588658754254
      },
      "metadata": {
        "$ref": "$.metadata"
      },
      "overriddenStatus": "UNKNOWN",
      "port": 8012,
      "sID": "na",
      "securePort": 443,
      "secureVipAddress": "ms-consumer-user-v2-ribbon",
      "status": "UP",
      "statusPageUrl": "http://192.168.0.100:8012/actuator/info",
      "vIPAddress": "ms-consumer-user-v2-ribbon",
      "version": "unknown"
    },
    "metaInfo": {
      "appName": "MS-CONSUMER-USER-V2-RIBBON",
      "instanceId": "192.168.0.100:ms-consumer-user-v2-ribbon:8012",
      "serviceIdForDiscovery": "ms-consumer-user-v2-ribbon"
    },
    "port": 8012,
    "readyToServe": true,
    "zone": "defaultZone"
  },
  "serviceId": "ms-consumer-user-v2-ribbon",
  "uri": "http://192.168.0.100:8012"
}

Response code: 200; Time: 100ms; Content length: 1792 bytes
  • http://localhost:8012/user/16
代码语言:javascript
复制
GET http://localhost:8012/user/16

HTTP/1.1 200 
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Tue, 05 May 2020 06:54:25 GMT

{
  "id": 16,
  "account": "account5",
  "userName": "x_user_5",
  "age": 20
}

Response code: 200; Time: 27ms; Content length: 61 bytes

负载均衡

配置服务提供者多实例

两个实例的启动参数分别为--spring.profiles.active=ribbon1

--spring.profiles.active=ribbon2

代码语言:javascript
复制
server:
  port: 8011

spring:
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/db_yier?characterEncoding=UTF-8&rewriteBatchedStatements=true
    username: root
    password: Abc123++
    driver-class-name: com.mysql.jdbc.Driver
  jpa:
    hibernate:
      ddl-auto: update
    show-sql: true
    database-platform: org.hibernate.dialect.MySQL5Dialect

  application:
    name: ms-provider-user-v2

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8010/eureka/
  instance:
    prefer-ip-address: true

info:
  app:
    name: @project.artifactId@
    encoding: @project.build.sourceEncoding@
    java:
      source: @java.version@
      target: @java.version@


---
spring:
  profiles: ribbon1
server:
  port: 8013

---
spring:
  profiles: ribbon2
server:
  port: 8014

接口测试

  • http://localhost:8012/instance/ms-provider-user-v2
代码语言:javascript
复制
//第一次调用
GET http://localhost:8012/instance/ms-provider-user-v2

HTTP/1.1 200 
Content-Type: application/json;charset=UTF-8
Content-Length: 1729
Date: Tue, 05 May 2020 07:39:49 GMT

{
  "host": "192.168.0.100",
  "instanceId": "192.168.0.100:8014",
  "metadata": {
    "management.port": "8014"
  },
  "port": 8014,
  "secure": false,
  "server": {
    "alive": true,
    "host": "192.168.0.100",
    "hostPort": "192.168.0.100:8014",
    "id": "192.168.0.100:8014",
    "instanceInfo": {
      "actionType": "ADDED",
      "appName": "MS-PROVIDER-USER-V2",
      "coordinatingDiscoveryServer": false,
      "countryId": 1,
      "dataCenterInfo": {
        "name": "MyOwn"
      },
      "dirty": false,
      "healthCheckUrl": "http://192.168.0.100:8014/actuator/health",
      "healthCheckUrls": [
        "http://192.168.0.100:8014/actuator/health"
      ],
      "homePageUrl": "http://192.168.0.100:8014/",
      "hostName": "192.168.0.100",
      "iPAddr": "192.168.0.100",
      "id": "192.168.0.100:ms-provider-user-v2:8014",
      "instanceId": "192.168.0.100:ms-provider-user-v2:8014",
      "lastDirtyTimestamp": 1588663883990,
      "lastUpdatedTimestamp": 1588663884538,
      "leaseInfo": {
        "durationInSecs": 90,
        "evictionTimestamp": 0,
        "registrationTimestamp": 1588663884538,
        "renewalIntervalInSecs": 30,
        "renewalTimestamp": 1588664154534,
        "serviceUpTimestamp": 1588663884034
      },
      "metadata": {
        "$ref": "$.metadata"
      },
      "overriddenStatus": "UNKNOWN",
      "port": 8014,
      "sID": "na",
      "securePort": 443,
      "secureVipAddress": "ms-provider-user-v2",
      "status": "UP",
      "statusPageUrl": "http://192.168.0.100:8014/actuator/info",
      "vIPAddress": "ms-provider-user-v2",
      "version": "unknown"
    },
    "metaInfo": {
      "appName": "MS-PROVIDER-USER-V2",
      "instanceId": "192.168.0.100:ms-provider-user-v2:8014",
      "serviceIdForDiscovery": "ms-provider-user-v2"
    },
    "port": 8014,
    "readyToServe": true,
    "zone": "defaultZone"
  },
  "serviceId": "ms-provider-user-v2",
  "uri": "http://192.168.0.100:8014"
}

Response code: 200; Time: 12ms; Content length: 1729 bytes

//第二次调用

GET http://localhost:8012/instance/ms-provider-user-v2

HTTP/1.1 200 
Content-Type: application/json;charset=UTF-8
Content-Length: 1729
Date: Tue, 05 May 2020 07:40:26 GMT

{
  "host": "192.168.0.100",
  "instanceId": "192.168.0.100:8013",
  "metadata": {
    "management.port": "8013"
  },
  "port": 8013,
  "secure": false,
  "server": {
    "alive": true,
    "host": "192.168.0.100",
    "hostPort": "192.168.0.100:8013",
    "id": "192.168.0.100:8013",
    "instanceInfo": {
      "actionType": "ADDED",
      "appName": "MS-PROVIDER-USER-V2",
      "coordinatingDiscoveryServer": false,
      "countryId": 1,
      "dataCenterInfo": {
        "name": "MyOwn"
      },
      "dirty": false,
      "healthCheckUrl": "http://192.168.0.100:8013/actuator/health",
      "healthCheckUrls": [
        "http://192.168.0.100:8013/actuator/health"
      ],
      "homePageUrl": "http://192.168.0.100:8013/",
      "hostName": "192.168.0.100",
      "iPAddr": "192.168.0.100",
      "id": "192.168.0.100:ms-provider-user-v2:8013",
      "instanceId": "192.168.0.100:ms-provider-user-v2:8013",
      "lastDirtyTimestamp": 1588663874416,
      "lastUpdatedTimestamp": 1588663874966,
      "leaseInfo": {
        "durationInSecs": 90,
        "evictionTimestamp": 0,
        "registrationTimestamp": 1588663874966,
        "renewalIntervalInSecs": 30,
        "renewalTimestamp": 1588664144962,
        "serviceUpTimestamp": 1588663874460
      },
      "metadata": {
        "$ref": "$.metadata"
      },
      "overriddenStatus": "UNKNOWN",
      "port": 8013,
      "sID": "na",
      "securePort": 443,
      "secureVipAddress": "ms-provider-user-v2",
      "status": "UP",
      "statusPageUrl": "http://192.168.0.100:8013/actuator/info",
      "vIPAddress": "ms-provider-user-v2",
      "version": "unknown"
    },
    "metaInfo": {
      "appName": "MS-PROVIDER-USER-V2",
      "instanceId": "192.168.0.100:ms-provider-user-v2:8013",
      "serviceIdForDiscovery": "ms-provider-user-v2"
    },
    "port": 8013,
    "readyToServe": true,
    "zone": "defaultZone"
  },
  "serviceId": "ms-provider-user-v2",
  "uri": "http://192.168.0.100:8013"
}

Response code: 200; Time: 12ms; Content length: 1729 bytes

关注"instanceId": "192.168.0.100:ms-provider-user-v2:8013",

"instanceId": "192.168.0.100:ms-provider-user-v2:8014",

可以发现实现了负载均衡,两次请求被均匀的分配到2个ms-provider-user-v2服务实例上。

源码分析

LoadBalancerInterceptor

LoadBalancerInterceptor是注解@LoadBalanced的关联实现类。

代码语言:java
复制
/**
 * @author Spencer Gibb
 * @author Dave Syer
 * @author Ryan Baxter
 * @author William Tran
 */
public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {

    private LoadBalancerClient loadBalancer;

    private LoadBalancerRequestFactory requestFactory;

    public LoadBalancerInterceptor(LoadBalancerClient loadBalancer,
            LoadBalancerRequestFactory requestFactory) {
        this.loadBalancer = loadBalancer;
        this.requestFactory = requestFactory;
    }

    public LoadBalancerInterceptor(LoadBalancerClient loadBalancer) {
        // for backwards compatibility
        this(loadBalancer, new LoadBalancerRequestFactory(loadBalancer));
    }

    @Override
    public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
            final ClientHttpRequestExecution execution) throws IOException {
        final URI originalUri = request.getURI();
        String serviceName = originalUri.getHost();
        Assert.state(serviceName != null,
                "Request URI does not contain a valid hostname: " + originalUri);
        return this.loadBalancer.execute(serviceName,
                this.requestFactory.createRequest(request, body, execution));
    }

}

LoadBalancerAutoConfiguration中,会对RestTemplate进行增强处理:

代码语言:java
复制
//传入拦截器
@Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(
  final LoadBalancerInterceptor loadBalancerInterceptor) {
  return restTemplate -> {
    List<ClientHttpRequestInterceptor> list = new ArrayList<>(
      restTemplate.getInterceptors());
    list.add(loadBalancerInterceptor);
    restTemplate.setInterceptors(list);
  };
}

//加工RestTemplate
@Bean
public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
  final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
  return () -> restTemplateCustomizers.ifAvailable(customizers -> {
    for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
      for (RestTemplateCustomizer customizer : customizers) {
        customizer.customize(restTemplate);
      }
    }
  });
}

而在 Spring 容器注入单例 Bean 的时候,会在DefaultListableBeanFactory中调用如下一段代码:

代码语言:java
复制
// Trigger post-initialization callback for all applicable beans...
        for (String beanName : beanNames) {
            Object singletonInstance = getSingleton(beanName);
            if (singletonInstance instanceof SmartInitializingSingleton) {
                final SmartInitializingSingleton smartSingleton = (SmartInitializingSingleton) singletonInstance;
                if (System.getSecurityManager() != null) {
                    AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
                        smartSingleton.afterSingletonsInstantiated();
                        return null;
                    }, getAccessControlContext());
                }
                else {
                    smartSingleton.afterSingletonsInstantiated();
                }
            }
        }

负载均衡策略

改变负载均衡策略,配置形式,或者注解形式都可以(IRule)

  • 配置文件
代码语言:javascript
复制
ms-provider-user-v2:
  ribbon:
    # 配置随机策略
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
  • 代码配置
代码语言:java
复制
/**
  * 配置均衡负载策略
  * @return
  */
@Bean
public IRule ribbonRule() {
  return new RandomRule();
}
代码语言:javascript
复制
# 测试 LoadBalancerClient 返回的 实例 ms-provider-user-v2 的信息
GET http://localhost:8012/instance/ms-provider-user-v2
Accept: application/json

通过返回的结果测试,可以验证是随机的,而非默认的轮询选择机制。

单独使用

依赖配置

删除spring-cloud-starter-netflix-eureka-client,并引入配置:

代码语言:html
复制
<!--负载均衡 Ribbon-->
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>

配置

代码语言:javascript
复制
server:
  port: 8012

spring:
  application:
    name: ms-consumer-user-v2-ribbon-single

#ms-provider-user-v2:
#  ribbon:
#    # 配置随机策略
#    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

# 单独使用,不使用 Eureka
ms-provider-user-v2:
  ribbon:
    listOfServers: localhost:8013,localhost:8014

测试

代码语言:javascript
复制
GET http://localhost:8012/instance/ms-provider-user-v2

HTTP/1.1 200 
Content-Type: application/json;charset=UTF-8
Content-Length: 390
Date: Tue, 05 May 2020 08:27:49 GMT

{
  "host": "localhost",
  "instanceId": "localhost:8013",
  "metadata": {},
  "port": 8013,
  "secure": false,
  "server": {
    "alive": true,
    "host": "localhost",
    "hostPort": "localhost:8013",
    "id": "localhost:8013",
    "metaInfo": {
      "instanceId": "localhost:8013"
    },
    "port": 8013,
    "readyToServe": true,
    "zone": "UNKNOWN"
  },
  "serviceId": "ms-provider-user-v2",
  "uri": "http://localhost:8013"
}

Response code: 200; Time: 507ms; Content length: 390 bytes

结果轮询返回的端口为 8013 或 8014。

饥饿加载

默认情况下Ribbon是懒加载的。当服务起动好之后,第一次请求是非常慢的,第二次之后就快很多。其解决方式:开启饥饿加载。

代码语言:javascript
复制
ribbon:
 eager-load:
  # 开启饥饿加载
  enabled: true 
  # 为哪些服务的名称开启饥饿加载,多个用逗号分隔
  clients: server-1,server-2,server-3

Spring Cloud 会为每个名称的 Ribbon Client 维护一个子应用程序的上下文,默认是懒加载的,配置饥饿加载后,可以在启动时就加载对应子应用程序的上下文,从而提高首次请求的访问速度

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-05-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 架构探险之道 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Spring Cloud 快速上手之 Ribbon 负载均衡
    • Ribbon
      • 基本配置
      • 负载均衡
      • 源码分析
      • 负载均衡策略
      • 单独使用
      • 饥饿加载
相关产品与服务
负载均衡
负载均衡(Cloud Load Balancer,CLB)提供安全快捷的流量分发服务,访问流量经由 CLB 可以自动分配到云中的多台后端服务器上,扩展系统的服务能力并消除单点故障。负载均衡支持亿级连接和千万级并发,可轻松应对大流量访问,满足业务需求。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档