1.什么是SpringCloud
什么是微服务?
假如我们需要搭建一个网上购物系统,那么我们需要哪些功能呢?商品中心、订单中心和客户中心等。
当业务功能较少时,我们可以把这些功能塞到一个SpringBoot项目中来进行管理。但是随着业务越来越多,每个模块需要实现的功能越来越多时,每个模块需要的开发人员就越来越多,这时如果再是一个项目就不太合适了(耦合度太高,并且开发人员对同一个项目进行操作容易出现问题)。这就需要将原先的系统进行拆分,可以按照功能模块拆分成多个服务,这就引出了另一个问题,服务之间如何完成通信。
在实际的业务场景中,每个功能模块不可能是完全分离的,例如订单中心展示订单时可能需要获取商品的信息和客户的信息,这就需要调用商品中心模块和客户中心模块的接口。SpringCloud就支持我们将原有项目进行拆分,分成多个微服务,并支持微服务之间的通信。
1.1 SpringCloud简介
SpringCloud由五大组件构成:
1.注册中心Euraka(国内用的较多的是Nacos):负责服务注册和发现。
2.负载均衡Ribbon:负责服务实例的选择(同一个服务可能部署在多个服务器上)。
3.远程调用Feign:通过HTTP请求调用其他服务的接口。
4.熔断器Hystrix:容错管理工具,用于处理分布式系统中的延迟和故障。
5.API网关Zuul:用于路由、过滤和负载均衡。
这五大组件的工作流程如下(绕不开的八股):
1.服务注册:只有一个服务注册到注册中心,才可能被其他服务发现并调用;
2.服务发现:即一个服务通过注册中心发现了其他服务
3.负载均衡:客户端维护一份从注册中心获取的Provider列表清单,根据自己配置的Provider负载均衡选择算法在客户端进行请求的分发;
4.服务调用:一个服务对另一个服务进行调用;
5.隔离、熔断与降级:通过Hystrix的线程池去访问服务,不同的服务通过不同的线程池,实现了不同的服务调度隔离;如果服务出现故障,通过服务熔断,避免服务雪崩的问题 ;并且通过服务降级,保证可以手动实现服务正常功能;
6.网关路由:如果前端调用后台系统,统一从网关进入,通过网关转发请求给对应的服务;
1.2 Eureka
服务注册中心(Eureka server/Nacos server)像是整个微服务架构中的大脑,服务(Eureka client)需要注册到Eureka server以供其他服务(Eureka client)发现,如果要使用其他服务的功能也需要通过Eureka server来获得对应服务(Eureka client)的信息。
Eureka server的主要功能为服务注册表维护和服务健康检查,Eureka client的主要功能为服务注册、心跳续约与健康状况查询。(是不是觉得很熟悉,有点像Redis的哨兵机制)
1.3 Ribbon
在实际的生产环境中,某些模块访问量较高,单个服务器可能无法承载这些访问请求,需要将这个微服务同时部署到多台服务器上,当有服务请求打到Eureka server上时,Eureka server会返回该服务类型对应的所有Eureka client实例。因此客户端会获得多个服务实例,Ribbon会选择哪个实例去处理请求,选择的方式有多种,轮询、随机、权重等,主要目的是避免多次请求均打到同一个服务实例上。Ribbon的作用是负载均衡。
Ribbon可以获取Provider清单,并且通过IPing实例定期(如每10秒)向每个Provider实例发送“ping”,并且根据Provider是否有响应来判断该Provider实例是否可用。如果该Provider的可用性发生了改变,或者Provider清单中的数量和之前的不一致,就从注册中心更新或者重新拉取Provider服务实例清单。每次RPC请求到来时,由Ribbon的IRule负载均衡策略接口的某个实现类来进行负载均衡。
1.4 Feign
在 Spring Cloud 中使用 Feign,可以做到使用 HTTP 请求访问远程服务,就像调用本地方法一样的,开发者完全感知不到这是在调用远程方法,更感知不到在访问 HTTP 请求。Feign 整合了 Ribbon 和 Hystrix,具备负载均衡、隔离、熔断与降级功能。
1.5 Zuul
微服务网关是微服务架构中不可或缺的部分,它统一解决Provider路由、均衡负载、权限控制等功能。具体可以参考博客:https://blog.csdn.net/itigoitie/article/details/125895899?spm=1001.2014.3001.5502
1.6 Hystrix
隔离:通过Hystrix的线程池去访问服务,不同的服务通过不同的线程池,实现了不同的服务调度隔离;
熔断:分布式架构中的熔断器主要用于RPC接口上,为接口安装上“保险丝”,以防止RPC接口出现拥塞时导致系统压力过大而引起的系统瘫痪,当RPC接口流量过大或者目标Provider出现异常时,熔断器及时切断故障可以起到自我保护的作用。
降级:当服务不可用(服务正在等待、链接超时、网络延迟、服务器响应慢等),客户端一直等待时,调用fallback方法给客户端返回一个错误提示,不让客户端继续等待。
2.Nacos
Nacos是阿里的产品,它的功能要比Eureka更丰富,因此国内更倾向于使用Nacos,我们来简单介绍一下Nacos的安装和使用。
2.1 Nacos安装
Nacos官网:https://nacos.io/
将压缩包下载到本地并解压(避免中文路径),进入到bin目录:
接着在bin目录下打开cmd,输入如下命令来启动Nacos:
startup.cmd -m standalone
在浏览器中打开:http://192.168.239.1:8848/nacos/index.html
至此,Nacos安装就完成了,其实还挺简单的,接下来我们需要借助Nacos去搭建一个SpringCloud项目,下面将介绍如何单机搭建一个SpringCloud项目。
2.2 Nacos简单实践
2.2.1 IDEA搭建SpringCloud项目
2.2.1.1 数据库准备
CREATE TABLE USER (
id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(20) NOT NULL,
phone VARCHAR(15) NOT NULL,
address VARCHAR(50) NOT NULL
);
INSERT INTO USER VALUES (1, "ayanokoujimonki", 13299075426, "湖北省孝感市")
INSERT INTO USER(NAME, phone, address) VALUES ("二哈很六", 18834267011, "江苏省苏州市")
INSERT INTO USER(NAME, phone, address) VALUES ("陈大龟", 12481076533, "汉川市榔头村")
CREATE TABLE orders (
id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(30) NOT NULL,
price INT NOT NULL,
user_id INT NOT NULL REFERENCES USER(id)
)
INSERT INTO orders VALUES (1, "可乐鸡翅", 32, 1);
INSERT INTO orders(NAME, price, user_id) VALUES("冰镇啤酒", 12, 1);
INSERT INTO orders(NAME, price, user_id) VALUES("草莓冰激凌", 8, 2);
INSERT INTO orders(NAME, price, user_id) VALUES("狼牙土豆", 10, 3);
2.2.1.2 创建父项目
1.首先创建父项目:
2.选择Spring Web依赖:
3.等到依赖下载好后,删除src目录及mvnw目录,正常情况下一般不会直接在父项目下直接编写代码:
4.修改pom文件,添加packaging标签和SpringCloud版本,并修改SpringBoot版本:
5.引入mysql和mybatis依赖:
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.23</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.11</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
6.添加SpringCloud依赖库,后续子模块使用的时候就不需要再指定版本了
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.2.9.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
2.2.1.3 创建子项目
1.在父级项目上新建模块(user_client),并修改子项目的pom文件引入父项目的坐标
2.为了将子项目作为Nacos的client,需要引入如下依赖:
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>2021.0.1.0</version>
</dependency>
3.编写application.yml文件
#服务端口,随便起
server:
port: 8082
#服务名称
spring:
application:
name: userclient
datasource:
url: jdbc:mysql://localhost:3306/user
username: root
password: cmq123
driver-class-name: com.mysql.cj.jdbc.Driver
cloud:
nacos:
discovery:
server-addr: localhost:8848
4.同样的步骤创建另一个子项目(orders_client)
2.2.1.5 编写简单的业务代码
其实这时我们启动服务就可以看到服务注册到Nacos上了,只不过我们没有编写业务代码因此每个微服务不具备具体功能,为了将我们的服务能够被Nacos发现,需要在启动类上加上@EnableDiscoveryClient
注解:
浏览器中打开Nacos地址就可以看到我们注册的实例了:http://192.168.239.1:8848/nacos/index.html
为了进一步验证服务之间能够通信,我们编写简单的业务代码,首先是userclient,我们编写pojo、controller、service和mapper:
其中UserService代码如下:
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
public User findById(Integer id){
System.out.println("UserService: "+ id);
return userMapper.find(id);
}
}
UserController代码如下:
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/{id}")
public User findUserById(@PathVariable("id") Integer id){
return userService.findById(id);
}
}
UserMapper代码如下:
@Mapper
public interface UserMapper {
@Select("select * from user where id=#{id}")
User find(Integer id);
}
User代码如下:
public class User {
private int id;
private String name;
private String password;
private String address;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", password='" + password + '\'' +
", address='" + address + '\'' +
'}';
}
}
同理我们继续编写ordersclient的代码,项目结构保持相同:
User代码于userclient保持一致,Order代码如下:
public class Order {
private int id;
private String name;
private int price;
private int userId;
private User user;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
public int getUserId() {
return userId;
}
public void setUserId(int userId) {
this.userId = userId;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
@Override
public String toString() {
return "Order{" +
"id=" + id +
", name='" + name + '\'' +
", price=" + price +
", userId=" + userId +
", user=" + user +
'}';
}
}
OrderMapper代码如下:
@Mapper
public interface OrderMapper {
@Select("select * from orders where id= #{id}")
Order find(Integer id);
}
OrderService代码如下:
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private RestTemplate restTemplate;
public Order findById(Integer id){
Order order = orderMapper.find(id);
String url = "http://userclient/user/" + order.getUserId();
User user = restTemplate.getForObject(url, User.class);
order.setUser(user);
return order;
}
}
OrderController代码如下:
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private OrderService orderService;
@GetMapping("/{id}")
public Order findById(@PathVariable("id") Integer id){
Order byId = orderService.findById(id);
return byId;
}
}
启动类代码如下:
@EnableDiscoveryClient
@SpringBootApplication
public class OrdersClientApplication {
public static void main(String[] args) {
SpringApplication.run(OrdersClientApplication.class, args);
}
/***
* 定义一个RestTemplate Bean,用于发送HTTP请求
* @return
*/
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
为了将我们实体类和数据库中的字段对应,我们需要在application.yml文件中开启驼峰命名:
mybatis:
type-aliases-package: com.ayanokouji.ordersclient.pojo.Order
configuration:
map-underscore-to-camel-case: true
我们再次启动这两个client,并在浏览器输入:localhost:8083/order/1
可以看到这两个服务确实完成了通信(虽然不是很优雅)。
这就是一个极致简单的SpringCloud项目,可以看到代码中我们并未显示地使用负载均衡,通信也是使用RestTemplate而不是Feign,关于负载均衡和Feign会在后面的博客中介绍(如果有时间的话,最近公司的活也有点多)。
参考博客
Nacos 注册中心下载到搭建详细步骤【微服务】_nacos下载-CSDN博客
IDEA 搭建 SpringCloud 项目【超详细步骤】_idea创建springcloud工程-CSDN博客
Spring Cloud 五大组件_springcloud五大组件-CSDN博客