SpringCloud集成NetflixRibbon实现负载均衡

NetflixRibbon

  在Spring Cloud中有一个专门实现负载均衡的组件,该组件为Spring Cloud Netflix Ribbon

负载均衡的分类

  负载均衡一共分为两种,其一是服务器端负载均衡;其二是客户端负载均衡

服务器端负载均衡

  提供服务器端负载均衡的工具有很多,比如说Apache、Nginx、HAProxy等都实现了基于HTTP协议或TCP协议的负载均衡模块。

  基于服务器端的负载均衡机制实现比较简单,只需要在客户端与各个微服务实例之间架设集中式的负载均衡器即可。负载均衡器与各个微服务实例之间需要实现服务诊断以及状态监控,通过动态获取各个微服务的运行时信息决定负载均衡的目标服务。如果负载均衡器检测到某个服务不可用时就会自动移除该服务。

  通过上述分析,可以看到负载均衡器运行在一台独立的服务器上并充当代理(Proxy)的作用。所有的请求都需要通过负载均衡器的转发才能实现服务调用,这可能会是一个问题,因为当服务请求量越来越大时,负载均衡器将会成为系统的瓶颈。同时,一旦负载均衡器自身发生失败,整个微服务的调用过程都将发生失败。因此,为了避免集中式负载均衡所带来的这种问题,客户端负载均衡同样也是一种常见的方式。

客户端负载均衡

  客户端负载均衡的主要优势在于其不会出现集中式负载均衡所产生的性能瓶颈问题,因为每个客户端都有自己的负载均衡器,该负载均衡器的失败不会造成严重的后果。由于所有运行时信息都需要在多个负载均衡器之间进行传递,因而客户端负载均衡器会在一定程度上加重网络流量负载。

  客户端负载均衡是在客户端内部设定一个调度算法,通过收集各个微服务的实例信息,在向服务器发起请求时,先执行调度算法计算出目标服务器地址,然后通过负载均衡计算出目标服务器地址实现负载均衡效果。

  与服务器端负载均衡相比,客户端负载均衡由于不需要架设专门的服务器端代理,因而不会出现集中式负载均衡所产生的那种性能瓶颈问题。在微服务架构中,客户端负载均衡是常见的负载均衡实现方案,包括Dubbo和Spring Cloud在内的很多框架都采用的客户端负载均衡实现机制。

负载均衡的算法

  负载均衡算法一共分为两大类,分别是静态负载均衡算法动态负载均衡算法

静态负载均衡算法

  静态随机算法的代表是随机(Random)算法和轮询(Round Robin)算法。

  • 随机算法

  随机算法实现起来比较简单,在JDK中自带了Random随机算法,通过该算法,我们可以指定服务器提供者的地址。随机算法的一种改进是加权随机(Weight Random)算法,在集群中可能存在部分性能比较优的服务器,为了使这些服务器响应更多的请求,可以通过加权随机算法提升这些服务器的权重。

  • 轮询算法

  轮询算法就是按照一定的顺序依次访问各个微服务。加权轮询(Weighted Round Robin)算法同样使用权重,一般的流程是顺序循环遍历服务提供者列表,到达上限之后重新归零,继续顺序循环直到指定某一台服务器作为服务器的提供者。普通的轮询算法实际上就是权重为1的加权轮询算法。

动态负载均衡算法

  所有涉及权重的静态算法都可以转变为动态算法,因为权重可以在运行过程中动态更新。典型的动态算法包括最少连接数算法服务调用延时算法源IP哈希算法

  • 最少连接数算法

  最少连接数算法对传入的请求根据每台服务器当前所打开的连接数来分配。

  • 服务调用延时算法

  在服务调用延时算法中,服务消费者缓存并计算所有服务提供者的服务调用时延,根据服务调用和平均时延的差值动态调整权重。

  • 源IP哈希算法

  源IP哈希算法实现请求IP粘滞连接,尽可能让消费者总是向同一提供者发起调用服务。这是一种有状态机制,也可以归为动态负载均衡算法。

源码

  GoodsOrderApplication

package com.lyc.goodsOrder;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

/**
 * @author: zhangzhenyi
 * @date: 2019/3/14 17:33
 * @description: GoodsOrder Bootstrap启动类
 **/
@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker
public class GoodsOrderApplication {

    @LoadBalanced
    @Bean
    public RestTemplate getRestTemplate(){
        return new RestTemplate();
    }

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

}

  GoodsOrderController

package com.lyc.goodsOrder.controller;

import com.lyc.goodsOrder.entity.Orders;
import com.lyc.goodsOrder.service.GoodsOrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

/**
 * @author: zhangzhenyi
 * @date: 2019/3/15 21:38
 * @description: GoodsOrder Controller
 **/
@RestController
@RequestMapping("/v1/orders")
public class GoodsOrderController {

    @Autowired
    private GoodsOrderService goodsOrderService;

    /**
     * 保存订单信息
     * @param productCode 产品编号
     * @param accountId 用户id
     * @return
     */
    @PostMapping("/{productCode}/{accountId}")
    public Orders saveOrder(@PathVariable("productCode") String productCode,@PathVariable("accountId") Long accountId){
        return goodsOrderService.addOrder(accountId,productCode);
    }

    /**
     * 分页获取订单
     * @param pageIndex 当前页索引值
     * @param pageSize 每页显示的信息条数
     * @return
     */
    @GetMapping("/{pageIndex}/{pageSize}")
    public List<Orders> getOrders(@PathVariable("pageIndex") int pageIndex,@PathVariable("pageSize") int pageSize){
        return goodsOrderService.getOrders(pageIndex,pageSize);
    }

}

  GoodsOrderService

package com.lyc.goodsOrder.service;

import com.lyc.goodsOrder.component.AccountClient;
import com.lyc.goodsOrder.component.ProductClient;
import com.lyc.goodsOrder.dao.GoodsOrderRepository;
import com.lyc.goodsOrder.entity.Account;
import com.lyc.goodsOrder.entity.Orders;
import com.lyc.goodsOrder.entity.Product;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * @author: zhangzhenyi
 * @date: 2019/3/16 16:07
 * @description: GoodsOrder服务类
 **/
@Service
public class GoodsOrderService {

    @Autowired
    private GoodsOrderRepository goodsOrderRepository;

    @Autowired
    private ProductClient productClient;

    @Autowired
    private AccountClient accountClient;

    @Autowired
    RestTemplate restTemplate;

    /**
     * 添加订单
     * @param productCode 商品条目编号
     * @return
     */
    public Orders addOrder(Long accountId,String productCode){
        Orders orders = Orders.builder()
                .id(1L)
                .build();
        Product product = getProduct(productCode);
        if(null == product){
            return orders;
        }
        Account account = getAccount(accountId);
        if(null == account){
            return orders;
        }
        orders.setAccountId(account.getId());
        orders.setName(product.getName());
        orders.setCreateTime(new Date());
        orders.setItem(product.getName());
        orders.setProductCode(product.getProductCode());
        goodsOrderRepository.save(orders);
        return orders;
    }

    /**
     * 给账户微服务的调用增加服务熔断机制
     * @param accountId
     * @return
     */
    @HystrixCommand(commandProperties = {
            @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "3000")  // 设置超时时间为3秒(默认是1秒)
    })
    private Account getAccount(Long accountId){
        return accountClient.getAccount(accountId);
    }

    /**
     * 给产品微服务的调用增加服务熔断机制
     * @param productCode
     * @return
     */
    @HystrixCommand
    private Product getProduct(String productCode){
        return productClient.getProduct(productCode);
    }

    /**
     * 获取第一页10条订单信息
     * @param pageIndex 当前页索引值
     * @param pageSize 每页的信息条数
     * @return
     */
    @HystrixCommand(
            fallbackMethod = "getOrdersFallback"   // 失败时执行的方法
            ,threadPoolKey = "orderThreadPool"
            ,threadPoolProperties = {
                    @HystrixProperty(name = "coreSize",value = "30")
                    ,@HystrixProperty(name = "maxQueueSize",value = "10")
            }
            ,commandProperties = {
                    @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "3000")
    }
    )
    public List<Orders> getOrders(int pageIndex,int pageSize){
        return goodsOrderRepository.findAll(new PageRequest(pageIndex - 1,pageSize)).getContent();
    }

    /**
     * Hystrix 失败回退方法(其中包含超时等情况)
     * @param pageIndex
     * @param pageSize
     * @return
     */
    private List<Orders> getOrdersFallback(int pageIndex,int pageSize){
        List<Orders> fallbackList = new ArrayList<>();
        Orders orders = Orders.builder()
                .id(1L)
                .accountId(0L)
                .item("Order list is not available")
                .createTime(new Date())
                .build();
        fallbackList.add(orders);
        return fallbackList;
    }

}

  GoodsOrderRepository

package com.lyc.goodsOrder.dao;

import com.lyc.goodsOrder.entity.Orders;
import org.springframework.data.jpa.repository.JpaRepository;

/**
 * @author: zhangzhenyi
 * @date: 2019/3/16 16:08
 * @description: GoodsOrder Repository
 **/
public interface GoodsOrderRepository extends JpaRepository<Orders,Long> {

}

  Account

package com.lyc.goodsOrder.entity;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

/**
 * @author: zhangzhenyi
 * @date: 2019/3/17 15:23
 * @description: Account 实体类
 **/
@Entity
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Account {

    @Id
    @GeneratedValue
    private Long id;
    private String username;
    private String password;

}

  Orders

package com.lyc.goodsOrder.entity;

import lombok.*;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import java.util.Date;

/**
 * @author: zhangzhenyi
 * @date: 2019/3/16 16:10
 * @description: 订单实体类
 * 这里需要注意的是,实体类的名称不能为Order,因为此时Orders已经相当于内部的一个关键字,
 * 所以说应该是避开,避开之后就能够正常使用了
 **/
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Entity
public class Orders {

    @Id
    @GeneratedValue
    private Long id;
    // 用户id
    private Long accountId;
    // 订单名称
    private String name;
    // 产品编号
    private String productCode;
    private String item;
    // 创建时间
    private Date createTime;

}

  Product

package com.lyc.goodsOrder.entity;

import lombok.*;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

/**
 * @author: zhangzhenyi
 * @date: 2019/3/15 20:27
 * @description: 商品条目实体类
 **/
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Entity
public class Product {

    @Id
    @GeneratedValue
    private Long id;
    private String name;
    private String productCode;

    public Product(String name, String productCode) {
        this.name = name;
        this.productCode = productCode;
    }

}

  AccountClient

package com.lyc.goodsOrder.component;

import com.lyc.goodsOrder.entity.Account;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;

/**
 * @author: zhangzhenyi
 * @date: 2019/3/17 15:25
 * @description: AccountClient 组件
 **/
@Slf4j
@Component
public class AccountClient {

    @Autowired
    RestTemplate restTemplate;

    /**
     * 发起远程调用账户微服务
     * @param accountId 账户id
     * @return
     */
    public Account getAccount(Long accountId){
        String url = "http://account-service/v1/account/{accountId}";
        log.info(url);
        ResponseEntity<Account> result = restTemplate.exchange(url,HttpMethod.GET,null,Account.class,accountId);
        return result.getBody();
    }

}

  ProductClient

package com.lyc.goodsOrder.component;

import com.lyc.goodsOrder.entity.Product;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;

/**
 * @author: zhangzhenyi
 * @date: 2019/3/16 16:01
 * @description: Product Client
 **/
@Slf4j
@Component
public class ProductClient {

    @Autowired
    RestTemplate restTemplate;

    /**
     * 发起远程调用产品微服务
     * @param productCode 产品编号
     * @return
     */
    public Product getProduct(String productCode){
        /*String url = "http://127.0.0.1:8081/v1/products/{productCode}";*/   // 虽然直接在浏览器中使用该方法可以使用,但是此处却不可使,因为Ribbon负载均衡不能使
        String url = "http://product-service/v1/products/{productCode}";
        log.info(url);
        ResponseEntity<Product> result = restTemplate.exchange(url,HttpMethod.GET,null,Product.class,productCode);
        return result.getBody();
    }

}

  application.yml

# 服务名称
spring:
  application:
    name: order-service
  # spring连接数据库驱动
  datasource:
      driverClassName: com.mysql.jdbc.Driver
      url: jdbc:mysql://localhost:3306/spring_data?useUnicode=true&characterEncoding=UTF-8&useSSL=false
      username: root
      password: zhangzhenyi

  # jpa自动创建不存在的数据表
  jpa:
    show-sql: true
    hibernate:
      ddl-auto: update
      use-new-id-generator-mappings: true
  jackson:
    serialization:
      indent_output: false
# 服务端口号
server:
  port: 8085
eureka:
  instance:
    # 指明使用IP而不是服务名称来注册自身服务。因为Eureka默认是使用域名进行服务注册和解析
    prefer-ip-address: true
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://localhost:8761/eureka/,http://localhost:8762/eureka/

  pom.xml中的部分依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<!-- mysql驱动 -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- jpa -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-ribbon</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>

  需要注意的是,上面的代码中含有hystrix功能。

分析

  负载均衡的调用是通过AccountClient中的RestTemplate来实现的,在RestTemplate中,通过exchange()方法发起的对Product服务的远程调用,但是这里需要注意的是此时的RestTemplate已经具有了负载均衡的功能,因为我们在GoodsOrderApplication中创建RestTemplate时就已经添加了@LoadBalanced注解。

  通过访问下面的路径

http://127.0.0.1:8083/v1/orders/1001/21

  我们就可以在控制台打印的日志信息中清楚的看到Product服务被调用时所打印的日志信息了。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值