目录
微服务提供者支付模块:cloud-provider-payment8001
微服务消费者订单模块cloud-consumer-order80
单机Eureka构建步骤—IDEA生成EurekaServer服务注册中心
将Client端8001注册进入EurekaServer成为服务提供者
将EurekaClient端80注册进EurekaServer成为服务消费者
概述
毫无疑问,SpringCloud是目前微服务架构领域的翘楚,无数的书籍博客都在讲解这个技术,不过大多数讲解还停留在对Cloud功能使用的层面,其底层的很多原理,很多人可能并不知晓,因此本文将通过大量的手绘图,给大家谈谈SpringCloud微服务架构的底层原理。
实际上,SpringCloud是一个全家桶式的技术栈,包含了很多的组件,本文先从其最核心的几个组件入手,来剖析一下其底层的工作原理,也就是Eureka、Ribbon、Feign、Hystrix、Zuul这几个组件,并结合实战演示,帮助大家更深的理解!
注:本文转载于:拜托!面试请不要再问我Spring Cloud底层原理 - 掘金
只是对其增加了案例演示,方便自我学习,案例来自于尚硅谷SpringCloud!
一、业务场景介绍
先来给大家说一个业务场景,假设我们现在要开发一个电商网站,要实现支付订单的功能,流程如下:
- 创建一个订单后,如果用户立刻支付了这个订单,我们需要将订单状态更新为“已支付”
- 扣减相应的商品库存
- 通知仓储中心,进行发货
- 给用户的这次购物增加相应的积分
针对上述流程,我们需要有订单服务、库存服务、仓储服务、积分服务。整个流程的大体思路如下:
- 用户针对一个订单完成支付之后,就会去找订单服务,更新订单状态
- 订单服务调用库存服务,完成相应功能
- 订单服务调用仓储服务,完成相应功能
- 订单服务调用积分服务,完成相应功能
至此,整个支付订单的业务流程结束
下图这张图,清晰表明了各服务间的调用过程:
好!有了业务场景之后,咱们就一起来看看SpringCloud微服务架构中,这几个组件是如何相互协作、各自发挥的作用的,最后刨析其背后的原理!
实战演示
微服务cloud整体聚合父工程Project
1.选择模板新建project
2.进行一些配置
3.File Type过滤
父工程的pom
删除src目录,并在pom文件中添加
<packaging>pom</packaging>
父工程整个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.IT.springcloud</groupId> <artifactId>SpringCloud01</artifactId> <version>1.0-SNAPSHOT</version> <modules> <module>cloud-provider-payment8001</module> </modules> <packaging>pom</packaging> <!--pom--> <!--统一管理jar包版本--> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <junit.version>4.12</junit.version> <lombok.version>1.16.18</lombok.version> <log4j.version>1.2.17</log4j.version> <mysql.version>5.1.47</mysql.version> <druid.version>1.1.16</druid.version> <mybatis.spring.boot.version>1.3.0</mybatis.spring.boot.version> </properties> <!--子模块继承之后,提供作用:锁定版本+子module不用写groupId和version--> <dependencyManagement> <dependencies> <dependency> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-project-info-reports-plugin</artifactId> <version>3.0.0</version> </dependency> <!--spring boot 2.2.2--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>2.2.2.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> <!--spring cloud Hoxton.SR1--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Hoxton.SR1</version> <type>pom</type> <scope>import</scope> </dependency> <!--spring cloud 阿里巴巴--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <version>2.1.0.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> <!--mysql--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>${mysql.version}</version> <scope>runtime</scope> </dependency> <!-- druid--> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>${druid.version}</version> </dependency> <!--mybatis--> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>${mybatis.spring.boot.version}</version> </dependency> <!--junit--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>${junit.version}</version> </dependency> <!--log4j--> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>${log4j.version}</version> </dependency> </dependencies> </dependencyManagement> <!--bulid:springboot默认的build方式--> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <fork>true</fork> <addResources>true</addResources> </configuration> </plugin> </plugins> </build> </project>
子模块
步骤:
- 建module
- 改pom
- 写yml
- 主启动类
- 业务类
- 测试
微服务提供者模块:cloud-provider-payment8001
1.新建模块(省略)
2.在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>SpringCloud01</artifactId> <groupId>com.IT.springcloud</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>cloud-provider-payment8001</artifactId> <properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.20</version> <!--子工程写了版本号,就使用子工程的版本号,如果没写版本,找父工程中规定的版本号--> </dependency> <!--mysql-connector-java--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <!--jdbc--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <!--热部署--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> </project>
3. 写yml
在resources目录下新建application.yml配置文件
# 微服务建议一定要写服务端口号和微服务名称 server: # 端口号 port: 8001 spring: application: # 微服务名称 name: cloud-payment-service # 数据库配置 datasource: type: com.alibaba.druid.pool.DruidDataSource # mysql5.x的没有cj driver-class-name: com.mysql.jdbc.Driver # 记得先创建数据库 url: jdbc:mysql://localhost:3306/db2021 username: root password: 123456 # mybatis配置 mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.angenin.springcloud.entities # 所有Entity实体类所在包
4. 主启动
在java包下创建主启动类com.IT.springcloud.PaymentMain8001
package com.IT.springcloud; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class PaymentMain8001 { public static void main(String[] args) { SpringApplication.run(PaymentMain8001.class,args); } }
5. 业务类
(1)建表SQL
CREATE TABLE `payment`( `id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT 'ID', `serial` VARCHAR(200) DEFAULT '', PRIMARY KEY(`id`) )ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; INSERT INTO payment(`serial`)VALUES("张三");
(2)entities
在springcloud包下新建实体类entities.Payment
// 这三个注解是lombok的,除了导入依赖,idea还需要安装插件(具体操作问度娘) @Data // set/get方法 @AllArgsConstructor // 有参构造器 @NoArgsConstructor // 无参构造器 public class Payment implements Serializable { private long id; private String serial; }
在entities包下新建CommonResult(json封装体,传给前端的)
// 返回给前端的通用json数据串 @Data // set/get方法 @AllArgsConstructor // 有参构造器 @NoArgsConstructor // 无参构造器 public class CommonResult<T> { private Integer code; private String message; private T data; // 泛型,对应类型的json数据 // 自定义两个参数的构造方法 public CommonResult(Integer code, String message){ this(code, message, null); } }
(3)dao
在springcloud包下新建dao.PaymentDao接口
@Mapper public interface PaymentDao { // 增 int create(Payment payment); // 查 加上@Param注解,mapper中就可以采用#{}的方式对@Param注解括号内的参数进行引用 Payment getPaymentById(@Param("id") Long id); // 这里用增和查进行演示,有兴趣的可以自己加其他的方法 }
(4)mapper
在resources目录下新建mapper目录,然后新建文件PaymentMapper.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.IT.springcloud.dao.PaymentDao"> <resultMap id="BaseResultMap" type="com.IT.springcloud.entities.Payment"> <id column="id" property="id" jdbcType="BIGINT"/> <id column="serial" property="serial" jdbcType="VARCHAR"/> </resultMap> <!-- 增 --> <!-- Payment标红了不用管,因为我们已经在yml文件中指定了Payment的位置了 --> <insert id="create" parameterType="Payment" useGeneratedKeys="true" keyProperty="id"> insert into payment(serial)values(#{serial}); </insert> <!-- 查 --> <!--返回用resultMap,防止命名不规范--> <select id="getPaymentById" parameterType="Long" resultMap="BaseResultMap"> select * from payment where id=#{id}; </select> </mapper>
(5)service
在springcloud包下新建service.PaymentService接口
public interface PaymentService { int create(Payment payment); Payment getPaymentById(@Param("id") Long id); }
在service包下新建impl.PaymentServiceIpml实现类
@Service public class PaymentServiceIpml implements PaymentService { @Resource // @Autowired也可以 private PaymentDao paymentDao; public int create(Payment payment){ return paymentDao.create(payment); } public Payment getPaymentById(Long id){ return paymentDao.getPaymentById(id); } }
(6)controller
在springcloud包下新建controller.PaymentController
@RestController @Slf4j // 日志 public class PaymentController { @Resource private PaymentService paymentService; // 前后端分离,所以不能直接返回对象,数据要先经过CommonResult封装再返回 @PostMapping("/payment/create") public CommonResult create(@RequestBody Payment payment){ int result = paymentService.create(payment); log.info("******插入的数据为:" + payment); log.info("******插入结果:" + result); if(result > 0){ // 插入成功 return new CommonResult(200, "插入数据库成功", result); }else{ return new CommonResult(444, "插入数据库失败"); } } @GetMapping("/payment/get/{id}") public CommonResult getPaymentById(@PathVariable("id") Long id){ Payment payment = paymentService.getPaymentById(id); log.info("******查询结果:" + payment); if(payment != null){ // 查询成功 return new CommonResult(200, "查询成功", payment); }else{ return new CommonResult(444, "没有对应记录,查询ID:" + id); } } }
6.测试
启动项目
浏览器地址栏输入
http://localhost:8001/payment/get/1
查询成功微服务消费者模块cloud-consumer-order80
1.新建模块(省略)
2.在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>SpringCloud01</artifactId> <groupId>com.IT.springcloud</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>cloud-consumer-order80</artifactId> <properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> </project>
3.写yml
在resources目录下新建application.yml文件
# 访问一个网站时,默认是80端口,如果给用户80端口,用户就可以不用加端口直接访问页面 server: port: 80
4.主启动类
@SpringBootApplication public class OrderMain80 { public static void main(String[] args) { SpringApplication.run(OrderMain80.class, args); } }
5.业务类
(1)复制cloud-provider-payment8001项目里的entities(里面2个实体类)到本项目的springcloud包下
(2)在springcloud包下新建config.ApplicationContextConfig
RestTemplate提供了多种便捷访问远程Http服务的方法,是一种简单便捷的restful服务模板类,是Spring提供的用于访问Rest服务的客户端模板工具集
@Configuration public class ApplicationContextConfig { // 往容器中添加一个RestTemplate // RestTemplate提供了多种便捷访问远程http访问的方法 @Bean public RestTemplate restTemplate(){ return new RestTemplate(); } }
(3)在springcloud包下新建controller.OrderController
package com.IT.springcloud.controller; import com.IT.springcloud.entities.CommonResult; import com.IT.springcloud.entities.Payment; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; @RestController @Slf4j public class OrderController { public static final String PAYMENT_URL = "http://localhost:8001"; @Autowired private RestTemplate restTemplate; // 因为浏览器只支持get请求,为了方便这里就用get @GetMapping("/consumer/payment/create") public CommonResult<Payment> create(Payment payment){ log.info("********插入的数据:" + payment); // 实际是请求如下地址 // postForObject分别有三个参数:请求地址,请求参数,返回的对象类型 // 此处是80调用8001 return restTemplate.postForObject(PAYMENT_URL + "/payment/create", payment, CommonResult.class); } @GetMapping("/consumer/payment/get/{id}") public CommonResult<Payment> getPayment(@PathVariable("id") Long id){ log.info("********查询的id:" + id); // getForObject两个参数:请求地址,返回的对象类型 return restTemplate.getForObject(PAYMENT_URL + "/payment/get/" + id, CommonResult.class); } }
6.测试
启动两个项目进行测试,两个都启动后,右下角会弹出个services提示,点击show,然后会把运行的项目模块合并在一起显示:
浏览器地址栏输入:localhost/consumer/payment/get/1
查询结果:
{"code":200,"message":"查询成功","data":{"id":1,"serial":"张三"}}
浏览器地址栏输入:localhost/consumer/payment/create/?serial=lisi
插入结果:
{"code":200,"message":"插入数据库成功","data":1}
工程重构
工程中有重复部分,重构工程
1.新建module:cloud-api-commons
2.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>SpringCloud01</artifactId> <groupId>com.IT.springcloud</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>cloud-api-commons</artifactId> <properties> <maven.compiler.source>8</maven