开篇,给大家介绍了Eureka注册中心的搭建,以及服务的注册。这篇博文主要围绕服务之间的调用,即消费者去调用注册中心的其他服务。
常用服务之间的调用方式
(一)RPC
远程过程调用协议,就像调用本地方法一样,调用服务器上的服务。
特点:
- RPC执行速度快。
- 可以基于TCP协议,也可以基于HTTP协议。
- 支持同步调用、异步调用。
- 数据包小。
- 通过二进制来传输。
- 开发成本较高。
(二)Rest(HTTP)
一种软件架构风格、设计风格,而不是标准,只是提供了一组设计原则和约束条件。
特点:
- 通过json和xml来传输数据。
- 开发方便,成本低。
- 数据包大。
- 可以基于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:部分机器配置强
大家可以自己去尝试,关于负载均衡的策略怎么选择,还要根据实际业务出发。
努力到无能为力,拼搏到感动自己。