作者:dabing(王甜甜)
视频:尚硅谷 2020 周阳 SpringCloud(H 版 & alibaba)
代码参考:https://blog.csdn.net/qq_36903261/article/details/106551120
这个同学的笔记是真的完整得很!!!推荐大家一定要去给他点个赞哈哈哈哈。我只是为了给自己好复习,也做一份罢了。
github 上代码地址:https://github.com/dabing85/springcloud2020
gitee 上代码地址:https://gitee.com/hedabing/springcloud2020
# 一、SpringCloud 简介
Spring Cloud 官方文档:https://cloud.spring.io/spring-cloud-static/Hoxton.SR5/reference/htmlsingle/
springcloud 中文文档:https://www.springcloud.cc/
springcloud 中国社区文档:https://www.bookstack.cn/read/spring-cloud-docs/docs-index.md
Spring Cloud 是一系列框架的有序集合。它利用 Spring Boot 的开发便利性巧妙地简化了分布式系统基础设施的开发。如服务注册发现、配置中心、消息总线、负载均衡、断路器、数据监控等等。都可以用 Spring Boot 的开发风格做到一键启动和部署。
Spring Cloud 并没有重复造轮子,它知识将各家公司开发的比较成熟的、经得住实际考研的服务框架结合起来,通过 Spring Boot 风格进行再封装屏蔽掉了复杂的配置和实现原理,最终给开发者留出了一套简单易懂、易部署和易维护的分布式系统开发工具包。
Spring Cloud 专注于为典型的用例和扩展机制提供良好的开箱即用体验,以涵盖其他情况。
Spring Cloud = 分布式微服务架构的一站式解决方案,是多种微服务架构落地技术的集合体,俗称全家桶。
那这个 SpringCloud 全家桶到底有多少技术呢?看看官网的截图:
我们看看一些大厂的项目架构:
京东的促销节架构:
阿里的架构:
京东物流的架构:
各种技术栈功能介绍:
先差不多脸熟一下上面的技术栈
# 二、关于版本的说明
- Spring Boot 的版本:
Spring Boot 的 git 源码地址:https:github.com/spring-projects/spring-boot/releases/
Spring Boot 的文档说明官网:https://spring.io/projects/spring-boot
截至 2022/5/3 的最新版本:
- Spring Cloud 的版本:
Spring Cloud 的 git 源码地址:https://github.com/spring-cloud/spring-cloud-release/wiki
Spring Cloud 的官网:https://spring.io/projects/spring-cloud
Spring Cloud 是以字母做为版本声明的,现在都已经到 K 版本了,周阳课程里所用的是 H 版本。为了避免一些坑,之后的练习也会先使用 H 版本来进行练习入门。🐸
- Spring Cloud 和 Spring Boot 之间的版本依赖:
前面那串英文鸡肠的意思就是说,以你当前使用的 Spring Cloud 版本来决定你使用的 Spring boot 版本。🦤
更详细的说明:https://start.spring.io/actuator/info
一大串的,能看懂的也是个神仙了,可以用 json 串转换工具:tool.lu
如 2022.0.0-M2 版本的 SpringCloud 对应的 SpringBoot 版本要在 3.0.0-M2 和 3.1.0-M1 之间
- 阳哥练习版本:
cloud - Hoxton.SR1
boot - 2.2.RELEASE
cloud - alibaba 2.1.0.RELEASE
java - Java8
maven - 3.5 及以上
# 三、关于 Cloud 各种组件的停更 / 升级 / 替换
截至 2020 年:重点学习框出来的技术
# 四、微服务架构编码 构建
这个就跟 spring boot 一样的啦就是搭一个架子出来先,你后面增加啥服务就一样增加,服务多了注册,多了集群,有啥异常的话有相应处理等等啊,然后给调用方调用你这些服务。巴拉巴拉的~~
约定 > 配置 > 编码
因此一定要把一些选型定好,把配置配好,能通再进行编码
这里先创建一个父工程,把该调好的都调好,后面的练习都在这个父工程下建 module
父工程步骤:
- New Project
- 聚合总父工程名字
- Maven 选版本
- 工程名字
- 字符编码
- 注解生效激活
- java 编译版本选 8
- File Type 过滤
可以通过目录选择性的跳过~~~
# 1 - 创建父工程
- 新建一个父工程:
- 给他起个名字:
- 选择对应的 maven 版本和本地的仓库设置
- 修改编码格式
- 开启支持注解
- File Types 过滤,把.deal 文件过滤掉
加上之后主页就看不到.idea 文件了,看着确实清爽一些
- 把用不上的文件删掉,父工程不写代码,删掉 src
- 改 pom
将父工程打包成 pom 类型
至于为啥,可以参考下这篇文章:https://www.cnblogs.com/sensenh/p/15126715.html
<packaging>pom</packaging> |
后面那堆没啥用的可以删掉,换成我们自己需要的依赖:
<!-- 统一管理 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.18.10</lombok.version> | |
<log4j.version>1.2.17</log4j.version> | |
<mysql.version>8.0.18</mysql.version> | |
<druid.version>1.1.20</druid.version> | |
<mybatis.spring.boot.version>1.3.2</mybatis.spring.boot.version> | |
</properties> | |
<!-- 子模块继承之后,提供作用:锁定版本 + 子 module 不用写 groupId 和 version--> | |
<dependencyManagement> | |
<dependencies> | |
<!--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 alibaba--> | |
<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-spring-boot-starter</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> | |
<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> |
这里:
我的爆红了,说是找不到这个版本的依赖,我就换了一个版本,有一个换了一个版本就行了。
但这个德鲁伊好顽强,换了好多个版本都不行,我就让朋友给了一份 jar 放进去的,后来我不管他他就好了,很奇怪。😑
# 2-Maven 工程细节
- Maven 中的 DependencyManagement 和 Dependencies:
Maven 使用 dependencyManagement 元素来提供了一种管理依赖版本号的方式。
通常会在一个组织或者项目的最顶层的 父POM
中看到 dependencyManagement 元素。
这样不用在多个子项目里面声明多次一个版本号,想统一升级的话就可以直接在父 pom 那修改就行了。
(子项目指定版本就用自己的,不指定就往上找)
Maven 会沿着父子层次向上走,直到找到一个拥有 dependencyManagement 元素的项目,然后它就会使用这个 dependencyManagement 元素中指定的版本号。
像下面这样:
- maven 中跳过单元测试
点这个小闪电就可以啦
# 3 - 子项目 Rest 微服务工程搭建
创建 module 五步:
- 建 module
- 改 pom
- 写 yml
- 主启动
- 业务类(这里是我们一般平时干的活了,当然包括数据库、dao 层、server 层、controller 层等啦~~)
每一次都这么干就行了
# 1. 创建 cloud-provider-payment8001 微服务提供者 Module 模块
这个模块实现支付
第一步:建 module
第二步:改 pom
先看看父 pom 有什么改变
改 pom,添加子项目所需的依赖
spring boot、mybatis、druid、jdbc、mysql、devtools、lombok…
<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.18</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> |
第三步:写 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.cj.jdbc.Driver | |
#记得先创建数据库 | |
url: jdbc:mysql://localhost:3306/cloud2020?useUnicode=true&characterEncoding=utf-8&useSSL=false | |
username: root | |
password: root | |
#mybatis 配置 | |
mybatis: | |
mapper-locations: classpath:mapper/*.xml | |
type-aliases-package: com.dabing.springcloud.entities #所有 Entity 别名类所在包 |
第四步:主启动类
在 java 文件夹下创建类:com.dabing.springcloud.PaymentMain8001
@SpringBootApplication | |
public class PaymentMain8001 { | |
public static void main(String[] args) { | |
SpringApplication.run(PaymentMain8001.class, args); | |
} | |
} |
第五步:业务类
- 数据库
idea 上连接一下 mysql
输入账户密码,如 账号 root 密码 123456
建表:
连上之后右键
在 console 窗口写 sql 语句
drop database IF EXISTS cloud2020 ; | |
create database cloud2020; | |
use cloud2020; | |
DROP TABLE IF EXISTS payment; | |
CREATE TABLE `payment` | |
( | |
`id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '订单号', | |
`serial` VARCHAR(200) DEFAULT '' COMMENT '订单流水', | |
PRIMARY KEY(`id`) | |
)ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; | |
INSERT INTO payment(`serial`)VALUES("652916232232"); |
全部选中执行,即可
呐,执行之后就有数据啦~~
- entities 实体类
第一个表的实体类 Payment
第二个需要有返回的统一的类 CommonResult(json 封装体,传给前端的)
在 springcloud 包下新建实体类 entities.Payment 和 CommonResult
// 这三个注解是 lombok 的,除了导入依赖,idea 还需要安装插件(具体操作问度娘) | |
@Data //set/get 方法 | |
@AllArgsConstructor // 有参构造器 | |
@NoArgsConstructor // 无参构造器 | |
public class Payment implements Serializable { | |
private static final long serialVersionUID = 1L; | |
private long id; | |
private String serial; | |
} | |
// 返回给前端的通用 json 数据串 | |
@Data //set/get 方法 | |
@AllArgsConstructor // 有参构造器 | |
@NoArgsConstructor // 无参构造器 | |
public class CommonResult<T> { | |
private Integer code; | |
private String message; | |
private T data; // 泛型,对应类型的 json 数据 | |
// 自定义两个参数的构造方法 | |
// 当返回数据错误时,data 可以为空 | |
public CommonResult(Integer code, String message){ | |
this(code, message, null); | |
} | |
} |
- dao
在 springcloud 包下新建 Dao.PaymentDao 接口
@Mapper | |
public interface PaymentDao { | |
// 增 | |
int create(Payment payment); | |
// 查 加上 @Param 注解,mapper 中就可以采用 #{} 的方式把 @Param 注解括号内的参数进行引用 | |
Payment getPaymentById(@Param("id") Long id); | |
// 这里用增和查进行演示,有兴趣的可以自己加其他的方法 | |
} |
- 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.dabing.springcloud.Dao.PaymentDao"> | |
<resultMap id="BaseResultMap" type="com.angenin.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> |
- 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); | |
} | |
} |
- 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); | |
} | |
} | |
} |
- 测试
启动下主启动类:某问题
get 请求 http://localhost:8001/payment/get/1
因为浏览器一般不支持直接发送 post 请求,所以,需要使用工具进行测试。(如 jmeter、postman(图形化)、curl(命令)等)
我这里用的 postman,但我没用过,可以参考这篇文章:https://blog.csdn.net/m0_61843874/article/details/123324727
测试 GET 请求: http://localhost:8001/payment/get/1
测试 POST 请求: localhost:8001/payment/create
步骤:
1、选择请求的方式
2、填写请求的 URL 地址
3、选择 Body 面板并勾选数据格式
4、填写要发送到服务器的数据
5、点击 Send 按钮发起 POST 请求
6、查看服务器响应的结果
# 2. 热部署 Devtools
使用热部署让我们每次改动代码的时候不用重启项目。方便调试
但是这个就在开发阶段用就好了。
- Adding devtools to your project
<dependency> | |
<groupId>org.springframework.boot</groupId> | |
<artifactId>spring-boot-devtools</artifactId> | |
<scope>runtime</scope> | |
<optional>true</optional> | |
</dependency> |
- Adding plugin to your pom.xml
<build> | |
<fileName>你自己的工程名字<fileName> | |
<plugins> | |
<plugin> | |
<groupId>org.springframework.boot</groupId> | |
<artifactId>spring-boot-maven-plugin</artifactId> | |
<configuration> | |
<fork>true</fork> | |
<addResources>true</addResources> | |
</configuration> | |
</plugin> | |
</plugins> | |
</build> |
- Enabling automatic build
- Update the value of
按 ctrl+shift+Alt+/
进入 Registry
我这边找不到那个 compiler.automake… 那个,然后好像也可以正常诶。
- 重启 idea
测试一下,在代码里添加字段,不用重启,代码内容生效
# 3. 创建 cloud-consumer-order80 微服务消费者订单 Module 模块
跟上边一样的,还是那五步,再来一遍:1. 建 module 2. 改 pom 3. 写 yml 4. 主启动 5. 业务类
这里要说下为啥使用 80 端口呢?
80 端口是为 HTTP(HyperText Transport Protocol) 即超文本传输协议开放的,此为上网冲浪使用次数最多的协议,主要用于 WWW(World Wide Web)即万维网传输信息的协议。
可以通过 HTTP 地址(即常说的 “网址”)加 **“: 80” 来访问网站,因为浏览网页服务默认的端口号 ** 都是 80,因此只需输入网址即可,不用输入 “: 80” 了。
pom.xml(cloud-consumer-order80)
<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> |
application.yml
server: | |
port: 80 |
主启动类:ConsumerMain80
@SpringBootApplication | |
public class ConsumerMain80 { | |
public static void main(String[] args) { | |
SpringApplication.run(ConsumerMain80.class); | |
} | |
} |
业务类
- entities
跟前面 8001 一样的实体类,Payment 和 CommonResult 类
这里直接拷贝过来就好。
因为都是共同的类,后面工程重构,会把这两个类提取出来,防止代码冗余。
- controller
因为这个 80 是消费者,直接去调用人家 8001 端口的服务,就不用写自己的 server 层了,直接调用别人即可。
但是嘞,你去调别人的服务肯定不能直接调呀,得借助点工具,使用 RestTemplate
,这玩意的作用,就是:简化了发起 HTTP 请求以及处理响应的过程,并且支持 REST
RestTemplate 的使用可以参考这篇文章:https://blog.csdn.net/qq_41538097/article/details/123560238
要使用,得先注入 bean,这里用一个配置类进行注入:
在 springcloud 包下新建 config.ApplicationContextConfig
@Configuration | |
public class ApplicationContextConfig { | |
// 往容器中添加一个 RestTemplate | |
//RestTemplate 提供了多种便捷访问远程 http 访问的方法 | |
@Bean | |
public RestTemplate restTemplate(){ | |
return new RestTemplate(); | |
} | |
} |
restTemplate.postForObject (…) 方法里参数出现了的 @Nullable
注解,它的大概作用是提示给代码编译这里可能会出现空值
在 springcloud 包下新建 controller.OrderController
@RestController | |
@Slf4j | |
public class OrderController { | |
public final static String PAYMENT_URL="http://localhost:8001"; | |
@Resource | |
private RestTemplate restTemplate; | |
// 因为浏览器只支持 get 请求,为了方便这里就用 get | |
@GetMapping("/consumer/payment/create") | |
public CommonResult<Payment> create(Payment payment){ | |
log.info("********插入的数据:" + payment); | |
//postForObject 分别有三个参数:请求地址,请求参数,返回的对象类型 | |
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); | |
} | |
} |
测试
启动 80 和 8001 端口
测试查询: http://localhost/consumer/payment/get/3
测试插入: http://localhost/consumer/payment/create?serial=112233
Tips - 开启 run DashBoard💡
看哈,这里起了两个服务,它就是两个窗口,那很多服务咋办嘞,那岂不是很多窗口,很不方便。
这里我们可以启动 run DashBoard 工具(如果本身就有,当我没说)我一开始也是没有的。(可以到百度下,很多教程~~)
我没做什么操作,我就是打开这个 workspace.xml 文件,idea 右下角就自动弹出是否开始巴拉巴拉服务的提示了,点击了是就有了,很神奇。
开启之后长这样:
# 4. 工程重构
因为 80 端口和 8001 端口有公共的部分,Payment 类和返回给前端的 CommonResult 类。
以后服务多了,每个服务都写一次代码复用性差,所以这里把公共的类提取出来,放在一个新的服务里去,这个公共的服务达成 jar,别的服务引入 jar 包即可使用。
-
新建新工程 - cloud-api-commons
-
改 pom.xml - 我这里只加了一个 lombok 的依赖,不作展示了
-
把 80 端口的 entities 包下的 Payment 和 CommonResult 类拷贝过来
-
清除 + 打包(clean+install)---- 右侧的 mvean–找到对应的服务 — 生命周期点开 — 先清除 clean,再 install
这里提一下:💡
如果在 install 的时候报了类似这样的错误:maven 的 xx 插件:xx 版本,没有注入成功,找不到类异常。。。。
** 解决:** 这类是 maven 插件的版本问题,我这里是全把报错的版本都改成了最新的版本就好了。
即在 pom.xml 里添加:
<!-- 因为我这里三个插件都报错了,这里懒得删了都粘过来 --> | |
<build> | |
<plugins> | |
<plugin> | |
<groupId>org.apache.maven.plugins</groupId> | |
<artifactId>maven-resources-plugin</artifactId> | |
<version>3.2.0</version> | |
<configuration> | |
<encoding>UTF-8</encoding> | |
</configuration> | |
</plugin> | |
<plugin> | |
<groupId>org.apache.maven.plugins</groupId> | |
<artifactId>maven-compiler-plugin</artifactId> | |
<version>3.8.0</version> | |
</plugin> | |
<plugin> | |
<groupId>org.apache.maven.plugins</groupId> | |
<artifactId>maven-install-plugin</artifactId> | |
<version>2.5.2</version> | |
</plugin> | |
</plugins> | |
</build> |
- 引用包
在 80
端口和 8001
端口引用这个 cloud-api-commons
这个 jar 包
先删除 80 端口,8001 端口的公共类,pom.xml 引入 jar 包
<dependency> | |
<groupId>com.dabing</groupId> | |
<artifactId>cloud-api-commons</artifactId> | |
<version>1.0-SNAPSHOT</version> | |
</dependency> |
- 测试
测试查询: http://localhost/consumer/payment/get/1
测试新增数据: http://localhost/consumer/payment/create?serial=12345678
# 五、Eureka 服务注册与发现
# 1- Eureka 基础知识
文字有些多,耐心看!!🐻❄️
** 举个例子:** 就是像你要去培训机构上课,这个培训机构叫做 sgg
,他的地址在 松山湖科技园
(我瞎说的),sgg 得先在松山湖科技园的 物业管理处
(就相当于 Eureka Server 注册中心)注册登记入驻科技园,管理处登记你 sgg 的信息和入驻地址。
然后学生要去上课,就去物业管理处去查拿着这个 sgg 的名字去查地址进行教学服务调用。
什么是服务治理?
Spring Cloud 封装了 Netflix 公司开发的 Eureka 模块来实现服务治理
在传统的 rpc 远程调用
框架中,管理每个服务与服务之间依赖关系比较复杂,管理比较复杂,所以需要使用服务治理,管理服务与服务之间依赖关系,可以实现服务调用、负载均衡、容错等,实现服务发现与注册。
什么是服务注册与发现?
Eureka 采用了 CS 的设计架构,Eureka Server 作为服务注册功能的服务器,它是 服务注册中心
。而系统中的其他微服务,使用 Eureka 的客户端连接到 Eureka Server 并维持心跳连接。这样系统的维护人员就可以通过 Eureka Server 来监控系统中各个微服务是否正常运行。
在服务注册与发现中,有一个注册中心。当服务器启动的时候,会把当前自己服务器的信息比如服务地址通讯地址等以别名方式注册到注册中心上。另一方(消费者服务提供者),以该别名的方式去注册中心上获取到实际的服务通讯地址,然后再实现本地 RPC 调用,RPC 远程调用框架核心设计思想:在于注册中心,因为使用注册中心管理每个服务与服务之间的一个依赖关系(服务治理概念)。在任何 rpc
远程框架中,都会有一个注册中心(存放服务地址相关信息(接口地址))
Eureka 两组件?
Eureka 包含两个组件:Eureka Server 和 Eureka Client
Eureka Server
提供服务注册服务
各个微服务节点通过配置启动后,会在 Eureka Server 中进行注册,这样 Eureka Server 中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观看到。
Eureka Client
通过注册中心进行访问
是一个 Java 客户端,用于简化 Eureka Server 的交互,客户端同时也具备一个内置的、使用轮询 (round-robin) 负载算法的负载均衡器。在应用启动后,将会向 Eureka Server 发送心跳(默认周期为 30 秒)。如果 Eureka Server 在多个心跳周期内没有接收到某个节点的心跳,Eureka Server 将会从服务注册表中把这个服务节点移除(默认 90 秒)
# 2 - 单机 Eureka 构建步骤
# 1. EurekaServer 端服务注册中心构建 类似物业公司
还是新建 moudle 的一般步骤:
- 建 module - cloud-eureka-server7001
- 改 pom - 引入 eureka-server 服务端依赖
- 写 yml - 指定端口号、eureka 相关
- 主启动类 - @EnableEurekaServer 指明是服务注册中心
- 业务类 - 无
pom.xml
<dependencies> | |
<!--springboot--> | |
<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> | |
<!--Eureka--> | |
<dependency> | |
<groupId>org.springframework.cloud</groupId> | |
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> | |
</dependency> | |
</dependencies> |
yml
server: | |
port: 7001 | |
eureka: | |
instance: | |
hostname: localhost #eureka 服务端的实例名称 | |
client: | |
#表示是否向注册中心注册(自己本身就是注册中心,不是服务,没必要注册,这里 false) | |
register-with-eureka: false | |
#flase 表示自己端就是注册中心,职责是维护服务实例,并不需要去检索服务 | |
fetch-registry: false | |
service-url: | |
#设置与 eurekaServer 交互的地址查询服务和注册服务都需要依赖这个地址 | |
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ |
注意 yaml 格式, :
后面是有空格的,别粗心大意了
@EnableEurekaServer | |
@SpringBootApplication | |
public class EurekaMain7001 { | |
public static void main(String[] args) { | |
SpringApplication.run(EurekaMain7001.class,args); | |
} | |
} |
测试: http://localhost:7001/
# 2. EurekaClient 端 cloud-provider-payment8001 类似于教育机构
8001 端口的这个支付服务,将注册进 EurekaServer 成为服务提供者 provider, 类似于学校对外提供授课服务
# 3. EurekaClient 端 cloud-consumer-order80 类似于学生
80 端口的这个消费者,将注册进 EurekaServer 成为服务消费者 consumer, 类似于学校上课消费的各位同学
如何操作?
改 pom - 添加 spring-cloud-starter-netflix-eureka-client
依赖
改 yml - 指定 spring 应用名字(即为显示在 eureka 注册中心上的名字),eureka 配置(指定是否注册、注册地址)
改主启动类 - 添加 @EnableEurekaClient
注解
pom.xml
<dependency> | |
<groupId>org.springframework.cloud</groupId> | |
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> | |
</dependency> |
application.yml
#配置微服务名称 | |
spring: | |
application: | |
name: cloud-consumer-order | |
#eureka 配置 | |
eureka: | |
client: | |
register-with-eureka: true #true 表示自己要注册到服务中心 | |
fetch-registry: true #true 表示自己要去检索下有没有其他的服务可用 | |
service-url: | |
defaultZone: http://localhost:7001/eureka/ |
ConsumerMain80.java
@EnableEurekaClient | |
@SpringBootApplication | |
public class ConsumerMain80 { | |
public static void main(String[] args) { | |
SpringApplication.run(ConsumerMain80.class); | |
} | |
} |
** 测试:** 刷新下刚刚那个 eureka 注册中心
# 3 - 集群 Eureka 构建步骤
** 解决方法:** 搭建 Eureka 注册中心集群,实现负载均衡 + 故障容错
# 1. 再构建两个注册中心
仿照 cloud-eureka-server7001 创建一个 cloud-eureka-server7002
一样的步骤:建 module、写 pom、改 yml、主启动类、业务类
之后要修改主机的 hosts 文件,在 C:\Windows\System32\drivers\etc
路径下
在最后一行加上这三行。
修改这三个注册中心的 yml 文件:
cloud-eureka-server7001 的 yml 文件:
server: | |
port: 7001 | |
eureka: | |
instance: | |
hostname: eureka7001.com #eureka 服务端的实例名称 | |
client: | |
register-with-eureka: false | |
fetch-registry: false | |
service-url: | |
# 单机 | |
# defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ | |
#集群版 相互注册,相互守望 | |
defaultZone: http://eureka7002.com:7002/eureka/, http://eureka7003.com:7003/eureka/ | |
# defaultZone 是固定写法,如果想自定义,需要按以下写法才行: | |
# region: eureka-server | |
# availability-zones: | |
# eureka-server: server1,server2 | |
# service-url: | |
# server1: http://eureka7002.com:7002/eureka/ | |
# server2: http://eureka7003.com:7003/eureka/ |
cloud-eureka-server7002 的 yml 文件:
server: | |
port: 7002 | |
spring: | |
application: | |
name: cloud-eureka-server2 | |
eureka: | |
instance: | |
hostname: eureka7002.com #eureka 服务端的实例名称 | |
client: | |
register-with-eureka: false | |
fetch-registry: false | |
service-url: | |
#集群版 相互注册,相互守望 | |
defaultZone: http://eureka7001.com:7001/eureka/, http://eureka7003.com:7003/eureka/ #相互注册,相互守望 |
cloud-eureka-server7003 的 yml 文件:
server: | |
port: 7003 | |
spring: | |
application: | |
name: cloud-eureka-server3 | |
eureka: | |
instance: | |
hostname: eureka7003.com #eureka 服务端的实例名称 | |
client: | |
register-with-eureka: false | |
fetch-registry: false | |
service-url: | |
#集群版 相互注册,相互守望 | |
defaultZone: http://eureka7001.com:7001/eureka/, http://eureka7002.com:7002/eureka/ #相互注册,相互守望 |
启动这三个注册中心 looklook。。。。分别长这样:
# 2. 将服务发布到集群配置
EurekaClient 端 cloud-provider-payment8001 类似于教育机构
EurekaClient 端 cloud-consumer-order80 类似于学生
原本这两个服务是注册到单机 7001 注册中心的,现在要注册到三个注册中心去。
他们的配置文件中的 defaultZone 改为:
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka,http://eureka7003.com:7003/eureka |
启动 5 个项目进行测试:(先要启动 EurekaServer 集群,再启动服务提供者 provider,8001,最后启动消费者,80)
集群后台截图:
访问下消费者 80 端口的是否正常使用 8001 的功能:http://localhost/consumer/payment/get/1
# 3. 构建支付服务提供者集群环境
提供的服务也要集群,按照 8001 新建 8002 。(除了要 yml 文件中需要改端口号和主配置类,其他直接复制 8001 的,yml 文件中的应用名不需要改,因为 8001 和 8002 对外暴露的服务名是一样的,所以应用名需要一致)
80 是调用方,不需要集群。
80 去调用支付服务的时候,有 8001 和 8002,而注册中心提供的是一样的服务名,那怎么知道到底调用的是哪个服务呢?如下:
在微服务提供方,在其 controller 层打印对应的端口号:
@Value ("${server.port}") 用这个注解就可以读取到配置文件里的端口号
8001 和 8002 的 controller 文件改成下面这样,打印出对应的端口号。没有全部复制,别盲目复制粘贴
@Value("${server.port}") | |
private String serverPort; | |
@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, "查询成功,serverPort:"+serverPort, payment); | |
}else{ | |
return new CommonResult(444, "没有对应记录,查询ID:"+ id +" serverPort:"+serverPort); | |
} | |
} |
对于 80 调用方,做两件事:
- 之前的 80 调用方已经写死了服务端口名是 8001,那 8002 就没有用了,所以这里要把 controller 层的 url 改成服务名:public static final String PAYMENT_URL = “http://CLOUD-PAYMENT-SERVICE”;
- 在 restTempate 的配置类里加上 @LoadBalanced 注解赋予 RestTemplate 负载均衡的能力
默认的是轮询的负载均衡机制
具体代码:
@RestController | |
@Slf4j | |
public class OrderController { | |
//public final static String PAYMENT_URL="http://localhost:8001"; | |
public static final String PAYMENT_URL = "http://CLOUD-PAYMENT-SERVICE"; | |
// 下面的代码跟原来一样。 | |
} | |
@Configuration | |
public class ApplicationContextConfig { | |
// 往容器中添加一个 RestTemplate | |
//RestTemplate 提供了多种便捷访问远程 http 访问的方法 | |
@Bean | |
@LoadBalanced | |
public RestTemplate restTemplate(){ | |
return new RestTemplate(); | |
} | |
} |
测试:
先要启动 EurekaServer,集群注册中心,再要启动服务提供者 provider,8001/8002 服务,最后启动 80 调用方。
测试查询: http://localhost/consumer/payment/get/3
刷新会发现两个端口的服务轮流提供服务。8001/8002 端口交替出现
注册中心集群后台:
Ribbon 和 Eureka 整合后 Consumer 可以直接调用服务而不用再关心地址和端口号,且该服务还有负载功能了.
看依赖包可以看到 Eureka 包下有集成 Ribbon 的包。
对于 Ribbon 的文章:https://www.jianshu.com/p/1bd66db5dc46
[
](https://www.jianshu.com/p/1bd66db5dc46)
# 4-actuator 微服务信息完善
# 1. 主机名称:服务名称修改
** 问题提出 :** 如图,服务的 ip 也给显示出来了。希望改成只含服务名。
修改方式:
在 8001 服务的 yml 文件添加上,在 eureka 的配置上添加,不知道对齐谁可以往下滑滑,下面有完整的 eureka 的配置。
#eureka 配置 | |
eureka: | |
instance: | |
instance-id: payment8001 |
效果:
# 2. 访问信息有 IP 信息提示
问题提出:
修改成上面那个样子之后,那又看不到 IP 的提示了。
修改方式:
在 8001 服务的 yml 文件添加上
prefer-ip-address: true # 访问路径可以显示 IP 地址 |
现在 yml 的 eureka 配置的变成这样了:
#eureka 配置 | |
eureka: | |
client: | |
register-with-eureka: true #true 表示自己要注册到服务中心 | |
fetch-registry: true #true 表示自己要去检索下有没有其他的服务可用 | |
service-url: | |
#defaultZone: http://localhost:7001/eureka/ | |
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka,http://eureka7003.com:7003/eureka | |
instance: | |
instance-id: payment8001 | |
prefer-ip-address: true # 访问路径可以显示 IP 地址 |
** 效果:** 可以显示 ip
# 5- 服务发现 Discovery
对于注册 eureka 里面的微服务,可以通过服务发现来获得该服务的信息
通过服务发现 Discovery 可以获取服务的一些信息,如 ip,端口号,主机,地址等。
对服务发现的一些文章:https://juejin.cn/post/6844903937653342216
正常情况下当我们要访问服务时需要知道服务实例地址和端口,如果服务实例地址和端口都是固定的我们可以直接将其配置在文件中使用,但大多数线上生产环境尤其容器部署情况下服务实例地址都是动态分配的,只有当服务实例实际部署之后才能获得地址,服务调用者根本无法提取获取服务实例地址和端口,只能在运行时通过服务发现组件解析服务名来获取服务实例地址和端口。
方法实现:
- 在 8001 主启动类添加注解:@EnableDiscoveryClient
- 在 8001 控制层装配 bean 并使用。在 8001 的 PaymentController 类添加:
/** | |
* 服务发现 | |
* 获取服务信息 | |
*/ | |
@Resource | |
private DiscoveryClient discoveryClient; //springframework 的 DiscoveryClient(不要导错包了) | |
@GetMapping("/payment/discovery") | |
public Object discovery(){ | |
// 获取服务列表的信息 | |
List<String> services = discoveryClient.getServices(); | |
for (String element : services) { | |
log.info("*******element:" + element); | |
} | |
// 获取 CLOUD-PAYMENT-SERVICE 服务的所有具体实例 | |
List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE"); | |
for (ServiceInstance instance : instances) { | |
//getServiceId 服务器 id getHost 主机名称 getPort 端口号 getUri 地址 | |
log.info(instance.getServiceId() + "\t" + instance.getHost() + "\t" + instance.getPort() + "\t" + instance.getUri()); | |
} | |
return this.discoveryClient; | |
} |
效果:
获取了注册中心上的已有服务(有两个服务 cloud-consumer-order、cloud-payment-service),
也可以通过服务名或者对应的服务实例,如 CLOUD-PAYMENT-SERVICE 获取了 8001 和 8002 两个服务实例。
# 6-eureka 自我保护
# 1. 概述
关于的文章:Eureka 的自我保护机制
属于 CAP 里面的 AP 分支
故障现象:
为什么会产生 Eureka 自我保护机制?
为了防止误杀可以正常运行的 EurekaClient ,在与 EurekaServer 网络不通情况下,EurekaServer 不会立刻 将 EurekaClient 服务剔除
什么是自我保护模式?
默认情况下,如果 EurekaServer 在一定时间内没有接收到某个微服务实例的心跳,EurekaServer 将会注销该实例(默认 90 秒)。但是当网络分区故障发生(延时、卡顿、拥挤)时,微服务与 EurekaServer 之间无法正常通信,以上行为可能变得非常危险了一因为微服务本身其实是健康的,此时本不应该注销这个微服务。Eureka 通过 “自我保护模式” 来解决这个问题一一当 EurekaServer 节点在短时间内丢失过多客户端时(可能发生了网络分区故障),那么这个节点就会进入自我保护模式。
它的设计哲学就是宁可保留错误的服务注册信息,也不盲目注销任何可能健康的服务实例。一句话讲解:好死不如赖活着
综上,自我保护模式是一种应对网络异常的安全保护措施。它的架构哲学是宁可同时保留所有微服务(健康的微服务和不健康的微服务都会保留) 也不盲目注销任何健康的微服务。使用自我保护模式,可以让 Eurekā集群更加的健壮、稳定。
# 2. 怎么禁止自我保护?
- ** 注册中心 7001 :** 关闭自我保护机制(默认是开启的)
修改 yml 文件:逐出 - 间隔 - 计时器 - 毫秒
# client | |
# server 与 client 对齐 | |
server: | |
#关闭自我保护,默认为 true | |
enable-self-preservation: false | |
#心跳的间隔时间,单位毫秒 | |
eviction-interval-timer-in-ms: 2000 |
- 生产者客户端 8001 :
修改发送心跳包时间间隔(默认 30 秒)、修改心跳等待时间,超时剔除(默认 90 秒)
修改 yml 文件:
#instance: 的下一级 | |
#Eureka 客户端向服务端发送心跳的时间间隔,单位秒(默认 30 秒) | |
lease-renewal-interval-in-seconds: 1 | |
#Eureka 服务端在收到最后一次心跳后等待的时间上限,单位秒(默认 90 秒),超时剔除服务 | |
lease-expiration-duration-in-seconds: 2 |
- 测试
关闭 8001,稍后 7001 注册中心会把 8001 服务剔除掉。
显示自我保护机制已经关闭。
# 六、Zookeeper 服务注册与发现
Eureka 已经停止更新了,后面就出现了 Zookeeper 进行替代。
# 1 - 注册中心 Zookeeper
# 1. 安装 Zookeeper-Linux
下载路径:https://dlcdn.apache.org/zookeeper/
安装虚拟机 + Linux 系统
安装教程参考:虚拟机 linux 安装 zookeeper
但是为了方便这里使用 docker 容器:一款必须掌握的虚拟容器技术:Docker
这里就跟着这个教程一步步来就行了,
查看版本号: docker version
这下有 docker 容器了。但是有个问题是说连接失败。
问题:
** 参考链接:**https://cloud.tencent.com/developer/article/1912506
** 解决:** 就跟着参考连接来就解决了,用 systemctl start docker
和 docker run hello-world
测试一下。
接下来就一步步来:
1. 关闭 linux 防火墙
2. 在 docker 下载一个 zookeeper
关闭 Linux 服务器防火墙后启动 Zookeeper 服务器
systemctl stop firewalld | |
systemctl status firewalld |
2. 使用 docker 启动 Zookeeper:
# 启动 docker | |
systemctl start docker | |
#拉取 Zookeeper 镜像 | |
docker pull zookeeper | |
#启动 Zookeeper | |
docker run --name zk01 -p 2181:2181 --restart always -d zookeeper |
报错了,因为我第一次没成功,过了好久才又重新练习的,报的错是数容器名称冲突:
这里我没管了,就直接吧 zk01 改成了 zk02 了,也可以参考:docker 容器名称冲突问题解决
服务节点是临时节点还是持久节点?
zookeeper 也是有心跳机制,在一定时间能如果一直没心跳返回,Zookeeper 就会把服务节点剔除掉。所以在 Zookeeper 上的服务节点是临时节点
# 2 - 服务提供者
应该是数据连接问题,我下面没有成功,下面的笔记是复制参考的文章的。
- 建 module - cloud-provider-payment8004
- 改 pom - spring-cloud-starter-zookeeper-discovery
- 写 yml
- 主启动
- 业务类
-
新建工程 cloud-provider-payment8004
-
pom
<!-- 因为接下来不会用到数据库,所以不导入数据库相关的依赖(防止没配置而报错)--> | |
<!-- 替换掉 eureka 依赖,其他直接复制 8001--> | |
<!--SpringBoot 整合 Zookeeper 客户端 --> | |
<dependency> | |
<groupId>org.springframework.cloud</groupId> | |
<artifactId>spring-cloud-starter-zookeeper-discovery</artifactId> | |
<!-- <exclusions>--> | |
<!-- 先排除自带的 zookeeper3.5.3--> | |
<!-- <exclusion>--> | |
<!-- <groupId>org.apache.zookeeper</groupId>--> | |
<!-- <artifactId>zookeeper</artifactId>--> | |
<!-- </exclusion>--> | |
<!-- </exclusions>--> | |
</dependency> | |
<!-- 添加 zookeeper3.4.9 版本(引入对应版本的依赖)--> | |
<!-- <dependency>--> | |
<!-- <groupId>org.apache.zookeeper</groupId>--> | |
<!-- <artifactId>zookeeper</artifactId>--> | |
<!-- <version>3.4.9</version>--> | |
<!-- </dependency>--> |
- yml
#端口号 | |
server: | |
port: 8004 | |
spring: | |
application: | |
#服务别名 —— 注册到 zookeeper 注册中心的名称 | |
name: cloud-provider-payment | |
cloud: | |
zookeeper: | |
connect-string: 192.168.174.130:2181 #linux 的 ip 加暴露的端口号 |
- 主启动类
@EnableDiscoveryClient // 该注解用于向使用 consul 或者 Zookeeper 作为注册中心时注册服务 | |
@SpringBootApplication | |
public class PaymentMain8004 { | |
public static void main(String[] args) { | |
SpringApplication.run(PaymentMain8004.class, args); | |
} | |
} |
- controller- 新建 controller.PaymentController
@Slf4j | |
@RestController | |
public class PaymentController { | |
@Value("${server.port}") // 获取端口号 | |
private String serverPort; | |
@RequestMapping("/payment/zk") | |
public String paymentzk(){ | |
return "springcloud with zookeeper:" + serverPort + "\t" + UUID.randomUUID().toString(); | |
} | |
} |
- Zookeeper 容器
默认已经帮我们启动了服务端和客户端,如果想进去容器的话,使用下面的命令。
#查看正在运行的容器(查看 Zookeeper 容器的 id) | |
docker ps | |
#进入 zookeeper 容器 | |
docker exec -it 容器ID /bin/bash | |
#退出容器(或者按快捷键 ctrl+P+Q 退出) | |
exit | |
#启动容器 | |
docker start 容器ID | |
#关闭容器 | |
docker stop 容器ID |
启动 8004 项目,浏览器输入 http://localhost:8004/payment/zk
进入 Zookeeper 容器(成功注册进注册中心)
参考:docker 安装 zookeeper 容器进入容器命令查看节点
用以下几个命令:
#查看正在运行的容器(查看 Zookeeper 容器的 id) | |
docker ps -a | |
#进入 zookeeper 容器 | |
docker exec -it 容器ID /bin/bash | |
ls / # 查看 | |
cd bin/ # 进入容器 bin 文件夹 | |
zkcli.sh # 运行 zkcli.sh | |
ls / | |
ls /services # 查看 zookeeper 已注册服务 |
https://tool.lu/json/
# 3 - 服务消费者
- 建 module - cloud-consumerzk-order80
- 改 pom - 同 8004
- 写 yml - 同 8004 ,但改端口号和服务名
- 主启动 - 同 8004
- 业务类
-
新建消费者模块 cloud-consumerzk-order80。
pom 和 yml 直接复制 8004。(yml 中端口号改为 80,应用名改为 cloud-consumerzk-order,其他都相同)
-
主启动类。(与 8004 相同)
-
在 springcloud 包下新建 config.ApplicationContextConfig
@Configuration | |
public class ApplicationContextConfig { | |
@Bean | |
@LoadBalanced // 负载均衡 | |
public RestTemplate restTemplate(){ | |
return new RestTemplate(); | |
} | |
} |
- 新建 controller.OrderZKController
@RestController | |
@Slf4j | |
public class OrderZKController { | |
public static final String INVOKE_URL = "http://cloud-provider-payment"; | |
@Resource | |
private RestTemplate restTemplate; | |
@RequestMapping("/consumer/payment/zk") | |
public String paymentInfo(){ | |
String result = restTemplate.getForObject(INVOKE_URL + "/payment/zk", String.class); | |
return result; | |
} | |
} |
启动项目
http://localhost/consumer/payment/zk
# 七、Consul 服务注册与发现
Consul 官网:https://www.consul.io/
Consul 中文文档:https://www.springcloud.cc/spring-cloud-consul.html
# 1 - 简介
# 1. 是什么?
Consul 是一种服务网格解决方案,提供具有服务发现,配置和分段功能的全功能控制平面。这些功能中的每一个都可以根据需要单独使用,也可以一起使用以构建完整的服务网格。Consul 需要一个数据平面,并支持代理和本机集成模型。Consul 附带了一个简单的内置代理,因此一切都可以直接使用,还支持 Envoy 等第三方代理集成。
# 2. 能干嘛?
** 服务发现:** 提供 HTTP/DNS 两种发现方式。
Consul 的客户端可以注册服务,例如 api 或 mysql,其他客户端可以使用 Consul 来发现给定服务的提供者。使用 DNS 或 HTTP,应用程序可以轻松找到它们依赖的服务。
** 健康检测:** 支持多种方式,HTTP、TCP、Docker、shell 脚本定制化。
领事客户端可以提供任意数量的运行状况检查,这些检查可以与给定服务(“Web 服务器是否返回 200 OK”)或本地节点(“内存利用率低于 90%”)相关。操作员可以使用此信息来监视群集的运行状况,服务发现组件可以使用此信息将流量从不正常的主机发送出去。
**KV 存储:**Key、Value 的存储方式
应用程序可以将 Consul 的分层键 / 值存储用于多种目的,包括动态配置,功能标记,协调,领导者选举等。简单的 HTTP API 使其易于使用。
安全的服务通信:领事可以为服务生成并分发 TLS 证书,以建立相互 TLS 连接。 意图 可用于定义允许哪些服务进行通信。可以使用可以实时更改的意图轻松管理服务分段,而不必使用复杂的网络拓扑和静态防火墙规则。
** 多数据中心:**Consul 支持多数据中心
Consul 开箱即用地支持多个数据中心。这意味着 Consul 的用户不必担心会构建其他抽象层以扩展到多个区域。
Consul 旨在对 DevOps 社区和应用程序开发人员友好,使其非常适合现代,灵活的基础架构。
# 3. 哪下载?
下载地址:https://www.consul.io/downloads.html
# 4. 怎么玩?
Consul 中文文档:https://www.springcloud.cc/spring-cloud-consul.html
在 docker 上安装启动 consul
# 启动 docker | |
systemctl start docker | |
#拉取 consul 镜像 | |
docker pull consul | |
#启动 consul | |
docker run -d -p 8500:8500/tcp --name myConsul consul agent -server -ui -bootstrap-expect=1 -client=0.0.0.0 |
然后在浏览器输入 http://192.168.174.130:8500(linux 的 IP 地址加上冒号 8500)
如果是本机的话,就用 http://localhost:8500
# 2 - 服务提供者
- 建 module - cloud-provider-consul-payment8006
- 改 pom - spring-cloud-starter-consul-discovery
- 写 yml
- 主启动
- 业务类
-
新建服务提供者 cloud-provider-consul-payment8006。
-
pom 复制 8004。(用下面的依赖替换 Zookeeper 的依赖)
<!--SpringCloud consul-server--> | |
<dependency> | |
<groupId>org.springframework.cloud</groupId> | |
<artifactId>spring-cloud-starter-consul-discovery</artifactId> | |
</dependency> |
- yml
server: | |
port: 8006 | |
spring: | |
application: | |
name: consul-payment-provider | |
cloud: | |
consul: | |
host: 192.168.174.130 #用 linux 的 ip 地址(consul 在本机就填 localhost) | |
port: 8500 | |
discovery: | |
service-name: ${spring.application.name} | |
heartbeat: | |
enabled: true |
-
主启动类(与 8004 相同)
-
controller
@Slf4j | |
@RestController | |
public class PaymentController { | |
@Value("${server.port}") | |
private String serverPort; | |
@RequestMapping("/payment/consul") | |
public String paymentConsul(){ | |
return "springcloud with consul:"+serverPort+"\t"+"随机数:" + UUID.randomUUID().toString(); | |
} | |
} |
- 启动项目
http://localhost:8006/payment/consul
但是 Consul 的服务有红色❌
解决:
参考 https://blog.csdn.net/weixin_44021270/article/details/107694022
已经在上边的 application.yml 添上了
# 3 - 服务消费者
- 建 module - cloud-consumer-consul-order80
- 改 pom - spring-cloud-starter-consul-discovery
- 写 yml
- 主启动
- 业务类
-
新建模块 cloud-consumer-consul-order80
-
pom(与 8006 相同)
-
yml(端口号为 80,应用名为 consul-consumer-order,其他和 8006 相同)
-
主启动类(与 8006 相同)
-
config(和 zk 的消费者相同)
-
controller.OrderConsulController
@RestController | |
@Slf4j | |
public class OrderConsulController { | |
public static final String INVOKE_URL = "http://consul-provider-payment"; | |
@Resource | |
private RestTemplate restTemplate; | |
@RequestMapping("/consumer/payment/consul") | |
public String paymentInfo(){ | |
String result = restTemplate.getForObject(INVOKE_URL + "/payment/consul", String.class); | |
return result; | |
} | |
} |
- 启动项目
http://localhost/consumer/payment/consul
# 4 - Eureka、ZK、Consul 三者的异同点
相关文章:Eureka、ZK、Consul 三者的异同点
CAP:(只能二选一) | |
C:一致性 | |
A:可用性 | |
P:分区容错性(微服务架构必须保证有P) |
三者之间的比较:
组件名 | 语言 | CAP | 服务健康检查 | 服务健康检查对外暴露接口 | SpringCloud 集成 |
---|---|---|---|---|---|
Eureka | Java | AP | 可配支持 | HTTP | 已集成 |
Consul | Go | CP | 支持 | HTTP/DNS | 已集成 |
Zookeeper | Java | CP | 支持 | 客户端 | 已集成 |
经典案例:
AP(Eureka)
CP(Zookeeper/Consul)
当网络分区出现后,为了保证一致性,就必须拒绝请求,否则无法保证一致性
结论:违背了可用性 A 的要求,只满足一致性和分区容错,即 CP
# 八、Ribbon 负载均衡调用
文档说明:https://github.com/Netflix/ribbon/wiki/Getting-Started
中文社区:http://docs.springcloud.cn/user-guide/ribbon/
# 1 - 概述
Spring Cloud Ribbon 是基于 Netflix Ribbon,实现的一套 客户端负载均衡的工具
。
简单的说,Ribbon 是 Netflix 发布的开源项目,主要功能是提供客户端的软件负载均衡算法和服务调用。
Ribbon 客户端组件提供一系列完善的配置项如连接超时,重试等。简单的说,就是在配置文件中列出 Load Balancer (简称 LB) 后面所有的机器,Ribbon 会自动的帮助你基于某种规则(如简单轮询,随机连接等)去连接这些机器。我们很容易使用 Ribbon 实现自定义的负载均衡算法。
前面的 Eureka、Zookeeper、Consul 的也因为集成了 Ribbon 所以也能实现负载均衡。
但是它目前已进入了维护模式:
未来替换模式: SpringCloud loadBalancer
# 1. 负载均衡(LB)
- 集中式 LB:
即在服务的消费方和提供方之间使用独立的 LB 设施(可以是硬件,如 F5,也可以是软件,如 nginx), 由该设施负责把访问请求通过某种策略转发至服务的提供方。
- 进程内 LB:
将 LB 逻辑集成到消费方,消费方从服务注册中心获知有哪些地址可用,然后自己再从这些地址中选择出一个合适的服务器。
Ribbon 就属于进程内 LB,它只是一个类库,集成于消费方进程,消费方通过它来获取到服务提供方的地址。
# 2 - Ribbon 负载均衡演示
# 1. 架构说明
** 总结:**Ribbon 其实就是一个软负载均衡的客户端组件,他可以和其他所需请求的客户端结合使用,和 eureka 结合只是其中一个实例
Ribbon 在工作时分成两步:
第一步先选择 EurekaServer, 它优先选择在同一个区域内负载较少的 server。
第二步再根据用户指定的策略,在从 serverl 取到的服务注册列表中选择一个地址。
其中 Ribbon 提供了多种策略:比如轮询、随机和根据响应时间加权。
# 2. POM
依赖导入 pom.xml
<!--Ribbon 的依赖 --> | |
<dependency> | |
<groupId>org.springframework.cloud</groupId> | |
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId> | |
</dependency> |
# 3. RestTemplate
API 文档说明:https://docs.spring.io/spring-framework/docs/5.2.2.RELEASE/javadoc-api/org/springframework/web/client/RestTemplate.html
RestTemplate 的使用可以参考这篇文章:https://blog.csdn.net/qq_41538097/article/details/123560238
前面的四 - 3.3 有使用过可以回顾一下。在 Bean 注入的配置文件中加入 @LoadBalanced 就能赋予它负载均衡的能力啦~
getForObject 方法 /getForEntity 方法: