springcloud-Ribbon详解(含java代码)

负载均衡原理

  • 客户端负载均衡,通过LoadBalancerclient来实现,ILoadBalancer 通过配置IRule 和IPin 来实现
  • ILoadBalancer 通过每10s 获取一次Eureka 注册地址,获取到注册列表后按照IRule 注册规则进行负载均衡

核心原理-拦截器

  • ribbon 的核心其实就是代理,通过拦截器的方式
  • 拦截器实现的功能1:通过对请求的拦截,获取url ,解析hostname ,通过hostname 再到Eureka 拿取真实的ip 端口,建立连接发送数据
  • 拦截器实现的功能2:拿到目标服务的列表后,按照Rule规则选择具体的目标服务,从而实现负载均衡

负载均衡算法

  • 预置可选择轮询算法:
    • 轮询策略(RoundRobinRule):轮询;
    • 随机策略(RandomRule):随机;
    • 可用性敏感策略(AvailabilityFilteringRule):会先过滤掉由于多次访问故障而处于断路器状态的服务,还有并发的连接数量超过阈值的服务,然后对剩余的服务列表按照轮询策略进行访问;
    • 权重策略(WeightedResponseTimeRule):根据平均响应时间计算所有服务的权重,响应时间越快的服务权重越大被选中的概率越大。刚启动时如果统计信息不足,则使用RoundRobinRule(轮询)策略,等统计信息足够,会切换到WeightedResponseTimeRule;
    • 重试策略(RetryRule):先按照RoundRobinRule(轮询)策略获取服务,如果获取服务失败则在指定时间内进行重试,获取可用的服务;
    • 最小连接数策略(BestAvailableRule):会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务;
    • 区域敏感策略(ZoneAvoidanceRule):复合判断Server所在区域的性能和Server的可用性选择服务器;
  • 自定义算法
    • 在主启动类加入@RibbonClient(name = “所要使用当前算法的微服务名称”,configuration = MySelfRule.class)代表使用自定义的java文件
    • 在其他的包下建立算法类,需要符合IRule类的规则,需要extends AbstractLoadBalancerRule
    • 复写Irule 接口的choose方法,将自己的算法写进去。

常用负载均衡策略和场景

  • 轮循均衡(Round Robin)
    • 原理:如果给服务器从 0 到 N 编号,轮询均衡策略会从 0 开始依次选择一个服务器作为处理本次请求的服务器
    • 场景:适合所有服务器都有相同的软硬件配置,且请求频率相对平衡。
  • 权重轮询均衡(Weighted Round Robin)
    • 原理:按照服务器的不同处理能力,给服务器分配不同的权重,然后请求会按照权重分配给不同的服务器
    • 场景:服务器的性能不同,充分利用高性能的服务器,同时也能照顾到低性能的服务器。
  • 随机均衡(Random)
    • 原理:将请求随机分配给不同的服务器。
    • 场景:适合客户端请求的频率比较随机的场景。
  • 响应速度均衡(Response Time)
    • 原理:负载均衡设备对每个服务器发送一个探测请求,看看哪台服务器的响应速度更快,
    • 场景:适合服务器的响应性能不断变化的场景。
      • 注意:响应速度是针对负载均衡设备和服务器之间的。

负载均衡器 LoadBalancer(核心)

  • Ribbon实现负载均衡是通过ILoadBalancer接口来实现的

  • AbstractLoadBalancer是 ILoadBalancer 的接口抽象实现类,它有三个方法,维护不同的功能:

    • chooseServer()
      • 选择合适的实例并返回
    • getServerList(ServerGroup var1)
      • 根据分组的类型来维护不同的服务列表
        • ALL 所有
        • STATUS_UP 正常服务的实例
        • STATUS_NOT_UP 停止服务的实例
    • getLoadBalancerStats()
      • 获取个个实力的服务当前属性和统计信息(这些信息是制定负载均衡策略的一句)
  • 负载均衡器的基础实现类是 BaseLoadBalancer ,BaseLoadBalancer 的属性值如下:
    在这里插入图片描述

  • 除了继承AbstractLoadBalancer类的维护实例和实例状态容器的方法并重写后,BaseLoadBalancer还有几个属性和功能值得关注

    • Pinger (服务实例状态监测机制类)
    • IPing(检查接口是否存活)
    • IRule(负载均衡规则)
    • PingerStartegy (定义策略,去ping服务实例)
  • 负载均衡器如何维护服务实例server列表

    • ILoadBalancer是和eruka结合通过服务发现的方式来维护server列表的
    • 负载均衡器扩展类DynamicServerListLoadBalancer实现服务实例清单在运行期的动态更新
      • 在这个类里定义了服务实例操作对象ServerList,serverListImpl
      • ServerList接口实现了初始化服务实例和更新服务实例的功能
        • List getInitialListOfServers();
        • List getUpdatedListOfServers();
  • 负载均衡器如何更新服务实例

    • 在 DynamicServerListLoadBalancer 类中有一个 serverListUpdater 对象实例属性,这个就是更新的 serverList,他有两个实现类来通过不同的策略实现serverList的更新
      • PollingServerListUpdater
        • 通过定时任务拉取服务列表(默认以这种策略为更新方式)
      • EurekaNotificationServerListUpdater
        • 通过eureka的Spring Event时间更新
  • 如何从ServerList中挑选一个合适的服务实例

    • 将返回的ServerList通过ServerListFilter过滤一次,返回满足条件的列表
    • 根据Rule规则,结合LoadBalancerStats信息,挑选一个合适的服务实例

Java实现Ribbon

  • 创建一个user-service模块
  • 在pom.xml中添加相关依赖
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
  • 在application.yml进行配置
server:
  port: 8201
spring:
  application:
    name: user-service
eureka:
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://localhost:8001/eureka/
  • 添加实体User
public class User {

    private Long id;
    private String username;
    private String password;

    public User() {
    }

    public User(Long id, String username, String password) {
        this.id = id;
        this.username = username;
        this.password = password;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

  • 添加统一返回对象CommonResult
public class CommonResult<T> {
    private T data;
    private String message;
    private Integer code;

    public CommonResult(){}

    public CommonResult(T data, String message, Integer code) {
        this.data = data;
        this.message = message;
        this.code = code;
    }

    public CommonResult(String message, Integer code) {
        this(null, message, code);
    }

    public CommonResult(T data) {
        this(data, "操作成功", 200);
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }
}

  • 添加UserService
public interface UserService {
    void create(User user);

    User getUser(Long id);

    void update(User user);

    void delete(Long id);

    User getByUsername(String username);

    List<User> getUserByIds(List<Long> ids);
}

  • 添加UserServiceImpl
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

@Service
public class UserServiceImpl implements UserService {
    private List<User> userList;

    @Override
    public void create(User user) {
        userList.add(user);
    }

    @Override
    public User getUser(Long id) {
        List<User> findUserList = userList.stream().filter(userItem -> userItem.getId().equals(id)).collect(Collectors.toList());
        if (!CollectionUtils.isEmpty(findUserList)) {
            return findUserList.get(0);
        }
        return null;
    }

    @Override
    public void update(User user) {
        userList.stream().filter(userItem -> userItem.getId().equals(user.getId())).forEach(userItem -> {
            userItem.setUsername(user.getUsername());
            userItem.setPassword(user.getPassword());
        });
    }

    @Override
    public void delete(Long id) {
        User user = getUser(id);
        if (user != null) {
            userList.remove(user);
        }
    }

    @Override
    public User getByUsername(String username) {
        List<User> findUserList = userList.stream().filter(userItem -> userItem.getUsername().equals(username)).collect(Collectors.toList());
        if (!CollectionUtils.isEmpty(findUserList)) {
            return findUserList.get(0);
        }
        return null;
    }

    @Override
    public List<User> getUserByIds(List<Long> ids) {
        return userList.stream().filter(userItem -> ids.contains(userItem.getId())).collect(Collectors.toList());
    }

    @PostConstruct
    public void initData() {
        userList = new ArrayList<>();
        userList.add(new User(1L, "zhangsan", "123456"));
        userList.add(new User(2L, "lisi", "123456"));
        userList.add(new User(3L, "wangwu", "123456"));
    }
}

  • 添加UserController用于提供调用接口
@RestController
@RequestMapping("/user")
public class UserController {

    private Logger LOGGER = LoggerFactory.getLogger(this.getClass());

    @Autowired
    private UserService userService;

    @PostMapping("/create")
    public CommonResult create(@RequestBody User user) {
        userService.create(user);
        return new CommonResult("操作成功", 200);
    }

    @GetMapping("/{id}")
    public CommonResult<User> getUser(@PathVariable Long id) {
        User user = userService.getUser(id);
        LOGGER.info("根据id获取用户信息,用户名称为:{}",user.getUsername());
        return new CommonResult<>(user);
    }

    @GetMapping("/getUserByIds")
    public CommonResult<List<User>> getUserByIds(@RequestParam List<Long> ids) {
        List<User> userList= userService.getUserByIds(ids);
        LOGGER.info("根据ids获取用户信息,用户列表为:{}",userList);
        return new CommonResult<>(userList);
    }

    @GetMapping("/getByUsername")
    public CommonResult<User> getByUsername(@RequestParam String username) {
        User user = userService.getByUsername(username);
        return new CommonResult<>(user);
    }

    @PostMapping("/update")
    public CommonResult update(@RequestBody User user) {
        userService.update(user);
        return new CommonResult("操作成功", 200);
    }

    @PostMapping("/delete/{id}")
    public CommonResult delete(@PathVariable Long id) {
        userService.delete(id);
        return new CommonResult("操作成功", 200);
    }
}
  • 创建一个ribbon-server模块
  • 在pom.xml中添加相关依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
  • 在application.yml进行配置
server:
  port: 8301
spring:
  application:
    name: ribbon-service
eureka:
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://localhost:8001/eureka/
service-url:
  user-service: http://user-service
  • 使用@LoadBalanced注解赋予RestTemplate负载均衡的能力
@Configuration
public class RibbonConfig {

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
}
  • 添加UserRibbonController类
    • 注入RestTemplate,使用其调用user-service中提供的相关接口
@RestController
@RequestMapping("/user")
public class UserRibbonController {
    @Autowired
    private RestTemplate restTemplate;
    @Value("${service-url.user-service}")
    private String userServiceUrl;

    @GetMapping("/{id}")
    public CommonResult getUser(@PathVariable Long id) {
        return restTemplate.getForObject(userServiceUrl + "/user/{1}", CommonResult.class, id);
    }

    @GetMapping("/getByUsername")
    public CommonResult getByUsername(@RequestParam String username) {
        return restTemplate.getForObject(userServiceUrl + "/user/getByUsername?username={1}", CommonResult.class, username);
    }

    @GetMapping("/getEntityByUsername")
    public CommonResult getEntityByUsername(@RequestParam String username) {
        ResponseEntity<CommonResult> entity = restTemplate.getForEntity(userServiceUrl + "/user/getByUsername?username={1}", CommonResult.class, username);
        if (entity.getStatusCode().is2xxSuccessful()) {
            return entity.getBody();
        } else {
            return new CommonResult("操作失败", 500);
        }
    }

    @PostMapping("/create")
    public CommonResult create(@RequestBody User user) {
        return restTemplate.postForObject(userServiceUrl + "/user/create", user, CommonResult.class);
    }

    @PostMapping("/update")
    public CommonResult update(@RequestBody User user) {
        return restTemplate.postForObject(userServiceUrl + "/user/update", user, CommonResult.class);
    }

    @PostMapping("/delete/{id}")
    public CommonResult delete(@PathVariable Long id) {
        return restTemplate.postForObject(userServiceUrl + "/user/delete/{1}", null, CommonResult.class, id);
    }
}

  • 启动eureka-server于8001端口
  • 启动user-service于8201端口和8202端口
  • 启动ribbon-server于8301端口
    在这里插入图片描述
  • 调用接口进行测试:http://localhost:8301/user/1
  • 可以发现运行在8201和8202的user-service控制台交替打印如下信息:
    在这里插入图片描述
    在这里插入图片描述

ribbon常用配置

  • 全局配置
ribbon:
  #服务请求连接超时时间(毫秒)
  ConnectTimeout: 1000 
  #服务请求处理超时时间(毫秒)
  ReadTimeout: 3000 
  #对超时请求启用重试机制
  OkToRetryOnAllOperations: true 
  #切换重试实例的最大个数
  MaxAutoRetriesNextServer: 1 
  # 切换实例后重试最大次数
  MaxAutoRetries: 1 
  #修改负载均衡算法
  NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule 
  • 指定服务进行配置
user-service:
  ribbon:
    #服务请求连接超时时间(毫秒)
    ConnectTimeout: 1000 
    #服务请求处理超时时间(毫秒)
    ReadTimeout: 3000 
    #对超时请求启用重试机制
    OkToRetryOnAllOperations: true 
     #切换重试实例的最大个数
    MaxAutoRetriesNextServer: 1
    # 切换实例后重试最大次数
    MaxAutoRetries: 1 
     #修改负载均衡算法
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值