Spring Cloud(二)服务消费者Ribbon

开篇,给大家介绍了Eureka注册中心的搭建,以及服务的注册。这篇博文主要围绕服务之间的调用,即消费者去调用注册中心的其他服务。

常用服务之间的调用方式

(一)RPC
远程过程调用协议,就像调用本地方法一样,调用服务器上的服务。
特点:

  1. RPC执行速度快。
  2. 可以基于TCP协议,也可以基于HTTP协议。
  3. 支持同步调用、异步调用。
  4. 数据包小。
  5. 通过二进制来传输。
  6. 开发成本较高。

(二)Rest(HTTP)
一种软件架构风格、设计风格,而不是标准,只是提供了一组设计原则和约束条件。
特点:

  1. 通过json和xml来传输数据。
  2. 开发方便,成本低。
  3. 数据包大。
  4. 可以基于HTTP1.1协议和HTTP2.0协议。

Spring Cloud中提供了两种调用方式:Ribbon和Fegin。因为Fegin默认集成了Ribbon,所以这篇博文主要围绕Ribbon来讲。

负载均衡

因为Ribbon是一个负载均衡客户端,所以这里给大家讲一下什么是负载均衡。

负载均衡建立在现有网络结构之上,它提供了一种廉价有效透明的方法扩展网络设备和服务器的带宽、增加吞吐量、加强网络数据处理能力、提高网络的灵活性和可用性。
负载均衡(Load Balance)其意思就是分摊到多个操作单元上进行执行,例如Web服务器、FTP服务器、企业关键应用服务器和其它关键任务服务器等,从而共同完成工作任务。

简单理解:其实就是一个服务部署到多个服务器,当访问量大时,就能分摊到多个服务器,不会发生宕机的情况。

服务消费者Ribbon

假设场景:我们需要订单服务调用用户服务,因为下单时,需要用户提供基本信息。所以,下面我会用一些伪代码为大家演示效果。

ps:以下的所有模块和代码都是基于第一篇博文,如有不明白,请查看我的第一篇博文。Spring Cloud(一)服务的注册与发现Eureka

附上链接:https://blog.csdn.net/qq_32101993/article/details/86515519

(一)用户服务

因为第一篇博文中,使用的是用户服务,作为服务注册。所以,这篇只需要对其进行修改。

首先,修改一下实体类。(User)

package com.root.project.userservice.pojo;

import lombok.Data;

import java.io.Serializable;

/**
 * @ClassName: User
 * @Author: 清风一阵吹我心
 * @Description: TODO
 * @Date: 2019/1/17 16:26
 * @Version 1.0
 **/
@Data
public class User implements Serializable {

    /**
     * 用户id
     */
    private Long uId;
    /**
     * 用户名
     */
    private String username;
    /**
     * 密码
     */
    private String password;
    /**
     * 性别
     */
    private Integer sex;
    /**
     * 是否为vip
     */
    private Boolean vip;

    public User() {

    }

    public User(Long uId, String username, String password, Integer sex, Boolean vip) {
        this.uId = uId;
        this.username = username;
        this.password = password;
        this.sex = sex;
        this.vip = vip;
    }
    //使用lombok自动生成getter、setter
}

需要在业务层添加新的方法,所以先定义接口(UserService)

package com.root.project.userservice.service;

import com.root.project.userservice.pojo.User;

import java.util.Map;

/**
 * @ClassName: UserService
 * @Author: 清风一阵吹我心
 * @Description: TODO
 * @Date: 2019/1/25 18:29
 * @Version 1.0
 **/
public interface UserService {

    /**
     * 根据用户id用户详情
     * @param uId
     * @return
     */
    User findOne(Long uId);

    /**
     * 查询所有用户
     * @return
     */
    Map<Long,User> findAll();
}

然后创建实现类,实现上面的两个方法。(UserServiceImpl)

package com.root.project.userservice.service.impl;

import com.root.project.userservice.pojo.User;
import com.root.project.userservice.service.UserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * @ClassName: UserServiceImpl
 * @Author: 清风一阵吹我心
 * @Description: TODO
 * @Date: 2019/1/25 18:30
 * @Version 1.0
 **/
@Service
public class UserServiceImpl implements UserService {

    private static final Logger LOGGER = LoggerFactory.getLogger(UserServiceImpl.class);

    /**
     * 用来存用户
     */
    private static final Map<Long, User> USER_MAP = new HashMap<>();

    //静态代码块,用来存储用户,因为不涉及数据库的交互,所以就以这种方式实现
    static {
        USER_MAP.put(1L, new User(1L, "清", "123456", 1, true));
        USER_MAP.put(2L, new User(2L, "风", "123456", 0, false));
        USER_MAP.put(3L, new User(3L, "一", "123456", 0, true));
        USER_MAP.put(4L, new User(4L, "阵", "123456", 0, false));
        USER_MAP.put(5L, new User(5L, "吹", "123456", 1, false));
        USER_MAP.put(6L, new User(6L, "我", "123456", 0, true));
        USER_MAP.put(7L, new User(7L, "心", "123456", 1, true));
    }

    @Override
    public User findOne(Long uId) {
        //使用entrySet而不是用keySet的原因是,entrySet体现了map的映射关系,遍历获取数据更快
        Set<Map.Entry<Long, User>> entries = USER_MAP.entrySet();
        for (Map.Entry<Long, User> entry : entries) {
            User user = entry.getValue();
            if (uId.equals(user.getUId())) {
                LOGGER.info("user:{}", user);
                return user;
            }
        }
        return null;
    }

    @Override
    public Map<Long, User> findAll() {
        return USER_MAP;
    }
}

这里没有通过数据库来提供真实数据,但是表达的效果是一样的。数据库的部分主要是和SpringBoot相关的操作,所以,就用静态代码块来实现了,为了在程序启动时,就往Map中添加用户实体。

最后就是控制层(UserController)

package com.root.project.userservice.controller;

import com.common.bean.Result;
import com.common.util.ResultBean;
import com.root.project.userservice.pojo.User;
import com.root.project.userservice.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
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 java.util.Map;

/**
 * @ClassName: UserController
 * @Author: 清风一阵吹我心
 * @Description: TODO
 * @Date: 2019/1/17 14:42
 * @Version 1.0
 **/
@RestController
@RequestMapping("/api/v1.0")
public class UserController {

    @Value("${server.port}")
    private String port;

    @Autowired
    private UserService userService;

    @GetMapping(value = "/user")
    public Result findAll() {
        Map<Long, User> map = userService.findAll();
        return ResultBean.success("查询全部用户", map);
    }

    @GetMapping(value = "/user/{userId}")
    public Result findOne(@PathVariable("userId") Long userId) {
        User one = userService.findOne(userId);
        if (one != null) {
            return ResultBean.success("根据id查询用户,服务来自:[" + port + "]端口", one);
        }
        return ResultBean.fail("查询失败", -1);
    }
}

因为我们要展示负载均衡的效果,所以,每次请求这个接口,都加上了当前服务的端口号。

同时,采用RestFul的风格,后端向前端返回统一的JSON数据。不明白的朋友,可以去看一下我的这一篇博文(https://blog.csdn.net/qq_32101993/article/details/84673627)。因为其他服务也会用到这个公共类,所以,这里我提取了一个实体类:Result,工具类:ResultBean。下面附上详细的操作。不熟悉的朋友可以看一下,反之,可以跳过。

(二)提取公共模块

首先,通过右击项目->New->Module->Maven,我取名为common-util。这里选择的是一个Maven项目,因为不涉及Spring Cloud的组件,修改pom的打包方式为jar。

common-util的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">
    <parent>
        <artifactId>springcloud-project</artifactId>
        <groupId>com.root.project</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <!-- 就添上这样一句话 -->
    <packaging>jar</packaging>
    <artifactId>common-util</artifactId>
</project>

然后在父工程中引用这个common-util,添加一个约束即可。

父工程的pom文件

<dependency>
 	 <groupId>com.root.project</groupId>
 	 <artifactId>common-util</artifactId>
 	 <version>1.0-SNAPSHOT</version>
</dependency>

因为每个人设置的groupId和artifactId都不一样,大家根据自己的名称来添加。

然后创建实体类(Result)

package com.common.bean;

import lombok.Data;

import java.io.Serializable;

/**
 * @ClassName: ResultBean
 * @Author: 清风一阵吹我心
 * @Description: TODO
 * @Date: 2019/1/25 16:36
 * @Version 1.0
 **/
@Data
public class Result implements Serializable {

    private Integer code;
    private String msg;
    private Object data;

    //这里使用lombok自动生成setter、getter


}

操作实体类的工具类(ResultBean)

package com.common.util;

import com.common.bean.Result;

/**
 * @ClassName: ResultBean
 * @Author: 清风一阵吹我心
 * @Description: TODO
 * @Date: 2019/1/25 16:40
 * @Version 1.0
 **/
public class ResultBean {

    public static Result success(String msg, Object data) {
        Result result = new Result();
        result.setCode(200);
        result.setMsg(msg);
        result.setData(data);
        return result;
    }

    public static Result success(String msg) {
        return success(msg, null);
    }

    public static Result fail(String msg, Integer code) {
        Result result = new Result();
        result.setCode(code);
        result.setMsg(msg);
        return result;
    }
}

公共模块的抽取就完成了,我们回归正题。

(三)服务消费者(订单服务)

首先新建一个SpringBoot模块,作为订单服务,取名为order-service。因为使用Ribbon作为服务消费者,所以,需要引入相关的约束。新建Module后,修改pom文件。

order-service的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.root.project</groupId>
    <artifactId>order-service</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>order-service</name>
    <description>Demo project for Spring Boot</description>

    <parent>
        <groupId>com.root.project</groupId>
        <artifactId>springcloud-project</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <dependencies>
        <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>
    </dependencies>


    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

然后在application.yml中引入相关配置。

spring:
  application:
    name: order-service

server:
  port: 8900

#指定注册中心
eureka:
  client:
    service-url:
      defaultzone: http://localhost:8761/eureka/

接着,修改我们的启动类(OrderServiceApplication)

package com.root.project.orderservice;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
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;

@SpringBootApplication
@EnableEurekaClient
public class OrderServiceApplication {

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

    /**
     * 标记是负载均衡
     * @return
     */
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
}

这里,向IOC容器中注入一个Bean:restTemplate。通过@LoadBalanced注解表明这个restRemplate开启负载均衡的功能。

服务消费者需要从提供者那里调用服务时,会使用RestTemplate 这个对象。同时,还能开启负载均衡策略。

这里需要写一个接口来测试服务之间的调用,所以各位不用理会代码的可读性。

创建订单服务接口(OrderService)

package com.root.project.orderservice.service;

import com.root.project.orderservice.pojo.Order;

/**
 * @ClassName: OrderService
 * @Author: 清风一阵吹我心
 * @Description: TODO  订单服务
 * @Date: 2019/1/18 16:15
 * @Version 1.0
 **/
public interface OrderService {

    /**
     * 保存订单
     * @param userId
     * @return
     */
    Order save(Long userId);
}

然后,创建实现类(OrderServiceImpl)

package com.root.project.orderservice.service.impl;

import com.root.project.orderservice.pojo.Order;
import com.root.project.orderservice.service.OrderService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

/**
 * @ClassName: OrderServiceImpl
 * @Author: 清风一阵吹我心
 * @Description: TODO  订单服务实现类
 * @Date: 2019/1/18 16:15
 * @Version 1.0
 **/
@Service
public class OrderServiceImpl implements OrderService {

    private static final Logger LOGGER = LoggerFactory.getLogger(OrderServiceImpl.class);

    /**
     * url:服务名称+接口名称
     */
    private static final String USER_REQ_URL = "http://user-service/api/v1.0/user/{userId}";

    @Autowired
    private RestTemplate restTemplate;

    @Override
    public Order save(Long userId) {
        Object object = restTemplate.getForObject(USER_REQ_URL, Object.class, userId);
        LOGGER.info("object:{}", object);
        Order order = new Order();
        order.setObject(object);
        return order;
    }
}

因为在启动类中,已经注入了RestTemplate对象。所以,直接使用@Autowired注解让它自动注入即可。注意,USER_REQ_URL 这个请求地址必须是,服务名称+接口的名称。
为了更好地展示效果,这里使用日志的方式,记录请求结果。

上面提到了负载均衡,为了测试,我们需要启动三个用户服务。那么在IDEA中,怎么启动多个服务呢?这里会给大家讲如何操作。
在这里插入图片描述
可以看到这个地方,点击下拉,可以看到Edit Configurations这个选项,然后点击。就会看到下面这个界面。
在这里插入图片描述
箭头1可以看到服务的名称,想为哪个服务启动多个端口,就选择哪个。
箭头2是一定要打勾的,它的意思是:以并行的方式运行。因为IDEA默认是单例的,其次,博主使用的IDEA版本是2018.3.1。使用旧版本的朋友,这里应该不一样。需要将默认的Single instance only(单实例)的钩去掉。
箭头3就是配置端口的地方了,根据自己的情况,配置不一样的端口。

接下来,就启动多个实例给大家看效果。
先启动注册中心(eureka-server),访问 http://localhost:8761/
在这里插入图片描述
可以看到,注册中心还没有注册相应的服务,接下来,启动三个用户服务。一个订单服务。
在这里插入图片描述
重新访问注册中心,可以看到,用户服务有三个端口,可以当作一个小的集群。然后访问三次订单接口:http://localhost:8901/api/v1.0/order?userId=1
在这里插入图片描述
这里我通过打日志的方式,给大家展示了,每一次访问,端口号都不一样。这是把三个服务循环调用了一遍。

Ribbon默认采用RoundRobinRule轮训的方式,每台机器的配置都一样,所以它都会访问一遍。那么,如何更改这种策略呢?官方文档说的是,通过修改application.yml配置文件的方式实现。

#自定义负载均衡策略 默认采用RoundRobinRule轮训的方式:每台机器的配置一样
#服务名称
user-service:
  ribbon:
   NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
#RandomRule 随机的方式
#WeightedResponseTimeRule:部分机器配置强

大家可以自己去尝试,关于负载均衡的策略怎么选择,还要根据实际业务出发。

努力到无能为力,拼搏到感动自己。

猜你喜欢

转载自blog.csdn.net/qq_32101993/article/details/86659161