随堂笔记
http://code.tarena.com.cn/CGBCode/cgb2108/
http://218.247.142.198/CGBCode/cgb2108/
tarenacode
code_2013
Gitee代码仓库
王海涛/CGB2108https://gitee.com/benwang6/cgb2108
一、Spring Cloud Netflix
-
注册中心
- Nacos
- Eureka
-
配置中心
- Nacos
- Spring Cloud Config / BUS
-
远程调用、负载均衡
- Feign、Ribbon
-
系统容错、限流
- Sentinel
- Hystrix
-
API网关
- Spring Cloud Gateway
- Zuul
-
数据监控
- Sentinel
- Hystrix Dashboard+Turbine
- 链路跟踪监控:Sleuth+Zipkin
业务创建结构
-
父项目
-
commons
-
业务模块
- 新建 spring 模块
- pom.xml
- application.yml
- service
- controller
1. Eureka 注册中心
1.1 搭建Eureka注册中心服务
-
新建 spring 模块: sp05-eureka
-
修改 pom.xml,添加 eureka server
-
yml
- 禁用自我保护模式
- 主机名:eureka1
- 针对单台服务器,不向自己注册,不从自己拉取注册表
-
启动类注解:
@EnableEurekaServer
触发eureka server的自动配置
1.2 Eureka的四条运行机制
-
注册
客户端一次次的反复连接注册中心进行注册,直到注册成功为止
-
拉取
客户端每30秒拉取一次注册表,刷新注册表
-
心跳
客户端每30秒发送一次心跳数据,如果服务器端连续三次收不到一个服务的心跳,会删除它的注册信息
-
自我保护模式
- 由于网络故障,15分钟内,85%服务器出现心跳异常,会自动进入自我保护模式
- 保护所有注册信息不删除
- 等待网络恢复后,可以退出保护模式,恢复正常
- 开发调试期间应该关闭保护模式,避免影响测试
1.3 Eureka客户端
-
修改 hosts 文件 win+r,输入 drivers,进入 etc 目录找到 hosts 文件
127.0.0.1 eureka1
127.0.0.1 eureka2
-
修改 2,3,4 三个模块,添加 eureka client 依赖
-
修改 2,3,4 的 yml 配置,添加 eureka 的连接地址 http://eureka1:port1/eureka
1.4 商品和eureka的高可用
客户端启动多个服务器
通过 springboot 的启动参数 --server.port
设置商品的启动端口
-
运行的下拉菜单中,选择 edit configuration
-
找到商品的启动配置
-
修改两个位置
-
启动配置名称: Sp02-item-8001、Sp02-item-8002
-
配置启动参数 program arguments:
- --server.port=8001
- --server.port=8002
-
Eureka高可用
-
添加 eureka1 和 eureka2 的 profile 配置
-
配置两个启动配置
- Sp05Eureka-2001,添加启动参数:
--spring.profiles.active=eureka1
- Sp05Eureka-2001,添加启动参数:
--spring.profiles.active=eureka2
- 修改 2,3,4,连接两台 eureka 服务器
- Sp05Eureka-2001,添加启动参数:
1.5 订单远程调用
1.5.1 调用过程
-
调用方添加 feign 依赖
-
启动类添加注解:
@EnableFeignClients
-
添加两个远程调用接口,接口上添加注解
@FeignClients
- ItemClient
- UserClient
-
OrderServiceImpl 中实现远程调用
1.5.2 Feign 集成 Ribbon
- 负载均衡 -- 默认启用了负载均衡
- 重试 -- 默认启用了重试
1.6 Ribbon 重试
调用后台服务失败(异常、服务器崩溃、超时),可以自动发起重试调用
重试参数:
- ribbon.MaxAutoRetries - 单台服务器的重试次数,默认0
- ribbon.MaxAutoRetriesNextServer - 更换服务器的次数,默认1
- ribbon.ReadTimeout - 接收响应的超时时间,默认1000
- ribbon.ConnectTimeout - 与后台服务器建立连接等待超时时间,默认1000
- ribbon.OkToRetryOnAllOperations - 是否对所有类型请求都进行重试,默认只对 GET 请求重试
2. Zuul - API网关
- 四大特征:
- 统一的调用入口
- 统一的权限校验(过滤器)
- 集成 Ribbon
- 集成 Hystrix
2.1 统一的调用入口
-
新建 spring 模块: sp06-zuul
-
pom.xml 添加依赖: zuul(server)、eureka client(注册到注册中心)
-
yml 配置路由规则
# 下面的路由规则是默认配置
# 根据注册表,可以完成自动配置
# 最好自己手动配置,防止注册表不全
zuul:
routes:
# ** 包含深层子路径
# * 只包含单层路径
item-service: /item-service/**
user-service: /user-service/**
order-service: /order-service/**
4.启动类添加注解:@EnableZuulProxy
2.2 Zuul 统一权限校验
继承 Zuul 的过滤器,在过滤器中判断用户权限
添加商品访问的权限判断(是否携带token):
- http://localhost:3001/item-service/yt3r23rt34 没有登录不允许访问
- http://localhost:3001/item-service/yt3r23rt34?token=uy4t3 已经登录可以访问
- 新建过滤器类:AccessFilter,继承 ZuulFilter
- 按照规则实现过滤器
- 添加注解:
@Component
- zuul的自动配置,会从 spring 容器自动发现过滤器实例,完成自动配置
package com.jt.sp06zuul.filter;
import com.jt.utils.JsonResult;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
/**
*通过继承自定义过滤器,类型需是前置
* */
@Component//交给spring容器扫描并创建实例
@Slf4j
public class AccessFilter extends ZuulFilter {
//设置过滤器的类型:是pre\routing\post等的哪一类
//自动配置的回调方法
@Override
public String filterType() {
log.info("自动配置的过滤器类型");
//return "pre";
return FilterConstants.PRE_TYPE;
}
//过滤器的顺序号
@Override
public int filterOrder() {
/**
* 在默认的第五个过滤器中,在上下文对象放入了serviceId
* */
return 6;
}
//针对当前请求进行判断,是否执行过滤代码
@Override
public boolean shouldFilter() {
//获取当前请求的上下文对象
RequestContext currentContext = //RequestContext以k-v结构存储
RequestContext.getCurrentContext();
//从上下文对象取出正在访问的serviceId
String serviceId =
(String)currentContext.get(FilterConstants.SERVICE_ID_KEY);
//判断
return "item-service".equalsIgnoreCase(serviceId);
}
//过滤代码
@Override
public Object run() throws ZuulException {
//1.获得上下文对象
RequestContext currentContext =
RequestContext.getCurrentContext();
//2.从上下文对象取出request对象
HttpServletRequest request = currentContext.getRequest();
//3.用request接收token参数
String token = request.getParameter("token");
//4.判断token是否存在
if (StringUtils.isBlank(token)){//此判断为空方法包括:null,空串"",空白字符" "
//4.1阻止继续访问
currentContext.setSendZuulResponse(false);
//4.2直接返回响应
String json =
JsonResult.build().code(500).msg("没有登录").toString();
//4.2.1设置响应头和响应体
currentContext.addZuulResponseHeader("Content-Type",
"application/json;charset=UTF-8");
currentContext.setResponseBody(json);
}
return null;//zuul当前版本中返回任何数据都是无效的
}
}
2.3 Zuul 集成 Ribbon
-
默认已经启动 ribbon 的负载均衡
-
默认不启用 ribbon 的重试
- 在网关重试,可能造成后台服务大面积出现压力翻倍
- 重试功能应该尽量往后放
-
启用重试
- 添加 spring-retry 依赖
- yml配置启用重试: zuul.retryable=true
2.4 zuul集成Hystrix
-
zuul默认已经启用了Hystrix
-
添加降级
-
添加降级类,实现 FallbackProvider 接口
-
添加
@Component
- zuul的自动配置会从spring容器自动发现降级类的实例,完成自动配置
-
package com.jt.sp06zuul.fb;
import com.jt.utils.JsonResult;
import org.springframework.cloud.netflix.zuul.filters.route.FallbackProvider;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.stereotype.Component;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
/**当调用后台的商品服务失败
* 执行网关中的这个降级类,向客户端返回降级结果
*/
@Component
public class OrderFB implements FallbackProvider {
//设置当前降级类
// */null:对所有服务都应用降级类
@Override
public String getRoute() {
return "order-service";
}
//发回给客户端的降级响应数据
@Override
public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
return new ClientHttpResponse() {
//@Override
public HttpStatus getStatusCode() throws IOException {
return HttpStatus.INTERNAL_SERVER_ERROR;//
}
//状态码
//@Override
public int getRawStatusCode() throws IOException {
return HttpStatus.INTERNAL_SERVER_ERROR.value();//500
}
//状态文本
//@Override
public String getStatusText() throws IOException {
return HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase();
}
public void close() {
/**
* 用来关闭下面的输入流
* ByteArrayInputStream java虚拟机中 是不占用底层系统资源
* 所有不需要写*/
}
public InputStream getBody() throws IOException {
String json =
JsonResult.build().code(500).msg("调用后台服务失败").toString();
return new ByteArrayInputStream(json.getBytes("UTF-8"));
}
public HttpHeaders getHeaders() {
HttpHeaders h = new HttpHeaders();
h.add( "Content-Type", "application/json;charset=UTF-8");
return h;
}
};
}
}
3. Hystrix
3.1 容错和限流工具
-
容错 -- 通过降级来容错
-
调用后台服务失败,执行当前模块的一段降级代码,返回降级结果
- 错误提示
- 缓存数据
- 根据具体业务逻辑,返回任何结果都可以
-
-
限流 -- 通过熔断来限制后台服务的流量
-
流量过大时,后台服务出现大量错误,会自动触发熔断
-
10秒20次请求(必须首先满足)
-
50%出错,执行了降级
-
半开状态
- 断路器打开后一段时间,会进入半开状态
- 会尝试发送一次客户端调用 调用成功,关闭断路器,恢复正常链路 调用失败,继续保持打开状态
-
3.2 Hystrix数据监控
Hystrix利用 springboot 的 Actuator 工具来暴露自己的监控数据
搭建 Hystrix Dashboard
- 新建 spring 模块:sp07-hystrix-dashboard
- 配置 pom.xml,添加 hystrix dashboard
- yml 配置允许抓取的服务器列表
- 启动类添加注解:
@EnableHystrixDashboard
访问路径: http://localhost:4001/hystrix
<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-turbine</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix-hystrix-dashboard</artifactId>
<version>2.2.6.RELEASE</version>
</dependency>
3.3 Actuator
springboot提供的项目监控指标工具,提供了多种监控数据
- 健康状态
- 环境变量、配置参数
- spring mvc 的映射路径
- JVM 虚拟机堆内存镜像
- spring 容器中所有的对象
- .....
添加 actuator
-
添加 actuator 依赖
-
yml配置暴露的监控数据
m.e.w.e.i="*" 暴露所有监控数据 m.e.w.e.i=health, env, beans, mappings m.e.w.e.i=hystris.stream
-
访问
http://localhost:3001/actuator
4. Turbine
从多台服务器抓取 Hystrix 日志,进行聚合,
Hystrix dashboard从Turbine抓取聚合后的日志数据
-
新建 spring 模块: sp08-turbine
-
pom.xml,添加 turbine、eureka client
-
yml
聚合的服务列表:zuul,a,b,c 对聚合的日志数据命名:new String("default")
-
启动类注解:
@EnableTurbine
-
访问
http://localhost:5001/turbine.stream
<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-turbine</artifactId>
</dependency>
5. Spring cloud config 配置中心
5.1集中的管理和维护配置文件
5.1.1 创建远程仓库
- 在 gitee 中,右上角点加号新建仓库
- 仓库名: springcloud1
- 设置成开源项目
5.1.2 创建本地仓库
-
vcs -- create git repository
也可以 double shift 在 action 中搜索 create git repository
-
选择 springcloud1 工程文件夹作为本地仓库目录
-
ctrl+k 向本地仓库提交文件 或者点右上角的对勾按钮 或者可以 double shift 在 action 中搜索 commit
-
选中全部文件、填写提交信息,执行提交
5.1.3 把本地仓库推送到远程仓库
- ctrl+shift+k 执行推送 或者点右上角的向上箭头按钮 或者可以 double shift 在 action 中搜索 push
- 点左上角链接 define remote
- 粘贴远程仓库 springcloud1 的地址
5.2 三个业务模块的配置文件,放到 git 仓库
-
在 springcloud1 工程中新建文件夹:config
-
复制 2,3,4 的 application.yml 到 config 目录
- item-service-dev.yml
- user-service-dev.yml
- order-service-dev.yml
-
三个文件中添加
override-none: true
防止下载的配置,覆盖本地参数设置 -
提交到本地仓库
-
向远程仓库推送
5.3 搭建配置中心
-
新建 sp09-config 模块
-
添加 config server 、eureka client 依赖
-
yml配置
git 仓库地址 仓库中存放配置文件的文件夹路径
-
启动类注解:
@EnalbeConfigServer
-
启动 05、09,检查确认
#注册表中有 config-server http://eureka1:2001/ http://localhost:6001/item-service/dev # item-service dev.yml http://localhost:6001/user-service/dev http://localhost:6001/order-service/dev
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
5.4 配置中心的客户端
-
修改 2,3,4 的 application.yml,代码全部注释
-
修改 03 用户,添加配置中心客户端设置
-
添加 config client 依赖
-
新建 bootstrap.yml (在引导配置阶段,从配置中心下载 application.yml)
-
添加三条配置:
- 连接 eureka
- 指定配置中心的服务id
- 从配置中心下载 user-service-dev.yml
-
二、VMware
16+
Nat 网络网段
使用 192.168.64.0 网段
编辑 -- 虚拟网络编辑器 -- 上面选择 vmnet8 -- 左下角修改网段 64
加载虚拟机镜像
-
课前资料/虚拟机/centos-8-2105.zip
-
课前资料/虚拟机/centos-7-1908.zip
-
能用centos-8就用centos-8,用不了的话可以换成7
-
两个虚拟机中已经做了几步基础配置:
-
阿里的yum安装源和扩展安装源
-
安装了三个工具: python、pip、ansible
-
两个脚本文件,用来设置ip地址
- ip-static:用来设置固定ip
- ip-dhcp:用来设置自动获取ip
-
- 解压 centos-8-2105.zip
- 双击 centos-8-2105.vmx,加载虚拟机
- 启动虚拟机,按提示选择 "已复制虚拟机"
- 登录的用户名密码,都是
root
测试虚拟机和宿主机之间网络是否互通
./ip-dhcp
# ping 宿主机在 nat 网络中的地址
# 宿主机 ping 不通,可能需要关闭windows防火墙
ping 192.168.64.1
# 在 windows 的cmd窗口中 ping 虚拟机
ping 192.168.64.128
解决网络设置问题
# centos 7 禁用 NetworkManager 系统服务
systemctl stop NetworkManager
systemctl disable NetworkManager
# centos 8 开启 VMware 托管
nmcli n on
systemctl restart NetworkManager
# 如果还有问题,可以重置 VMware 虚拟网络
编辑 -- 虚拟网络编辑器 -- 还原默认设置
然后再设置 nat 网络网段 64
安装 Docker 虚拟机
-
关闭 centos-8-2105
-
克隆 centos-8-2105: docker-base
-
用 mobaxterm 工具上传文件到 /root/
- 课前资料\DevOps课前资料\docker\docker-install 文件夹
-
参考 Docker离线安装笔记,从第3步开始执行(前两步下载文件不用执行)
三、Rabbitmq 消息中间件
在分布式系统中,用来在模块之间传递数据的工具
消息服务在分布式系统中,是非常重要的组件
常用的消息服务器:
- Rabbitmq
- Activemq
- Kafka
- Rocketmq
- Tubemq
- ....
1. 搭建 Rabbitmq 服务器
-
准备好docker环境
-
从 docker base 克隆: rabbitmq
-
设置ip地址
./ip-static
ip: 192.168.64.140
ifconfig #如果有问题,参考 VMware 下面的 解决网络设置问题
-
上传镜像压缩文件到/root/
DevOps课前资料\docker\rabbit-image.gz
-
执行导入
docker load -i rabbit-image.gz
-
按照 csdn rabbitmq 笔记,docker 运行 rabbitmq
2 Rabbitmq 原生API测试
- 新建 Empty project: rabbitmq
- 新建 maven 模块: rabbitmq-api
- 添加 rabbitmq 依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.4.3</version>
</dependency>
四、配置中心 + Bus 配置刷新
Bus - 消息总线
辅助完成配置刷新,向消息服务器发送刷新指令,从消息服务器接收指令,执行刷新操作
修改 2,3,4,9 添加 Bus 和 Rabbitmq
-
添加依赖
- Bus
- Rabbitmq
- binder rabbit
-
yml 配置rabbitmq 连接
- config模块的 application.yml
- 2,3,4 修改 config 文件夹的三个文件,提交推送到远程仓库
-
09项目,用actuator暴露 bus-refresh 路径
- actuator依赖
- yml配置:
m.e.w.e.i=bus-refresh
-
启动项目
-
05,等待完全启动
-
09,等待完全启动
- 查看注册表要有 config-server
- http://localhost:6001/user-service/dev
- http://localhost:6001/actuator/ 查看这里有没有 bus-refresh
-
2,3,等待完全启动
-
4,等待完全启动,检查控制台有没有连接 6001
-
6,等待完全启动
-
POST http://localhost:6001/actuator/bus-refresh
查看 2,3,4 控制台,有没有重新连接6001刷新配置
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-bus</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream-binder-rabbit</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
#config-server 暴露 bus-refresh 刷新端点
management:
endpoints:
web:
exposure:
include: bus-refresh
五、消息服务案例
- config+bus配置刷新 把刷新指令发送到 rabbitmq,其他模块接收指令执行刷新操作 主题模式
- Sleuth+zipkin 链路跟踪 各个模块中产生的链路跟踪日志,通过 rabbitmq 中转发送到zipkin进行处理 简单模式
- 订单的流量削峰 商城的订单系统,生成订单发送到 rabbitmq,后台的消费者模块接收订单存储到数据库 简单模式、工作模式
1. Sleuth + zipkin 链路跟踪
Sleuth 用来产生链路跟踪日志
A -> B -> C -> D
# 服务名,链路id,当前模块id,是否发送到zipkin(10%抽样比例) A, UYT43TRF45Y, UYT43TRF45Y, true B, UYT43TRF45Y, IUYT34T4Y56, true C, UYT43TRF45Y, I656Y45T344, true D, UYT43TRF45Y, I6456Y32T44, true
添加Sleuth
- 修改 2,3,4,6 添加 sleuth 依赖
- sleuth 自动配置,0配置
2,3,4,6中的链路跟踪日志,发送到rabbitmq,再向zipkin中转发送
-
在 2,3,4,6 中添加依赖:
zipkin client
-
在 06 添加 rabbitmq 的依赖和连接配置
-
修改 06 的 application.yml
日志发送方式: rabbit -
在 config 文件夹修改 2,3,4 ,添加发送方式,提交推送到远程仓库
2. Eureka客户端注册正确地址
选择正确的网卡
bootstrap.ym
#选择注册的网卡
#必须在应用启动之前,在引导配置阶段完成
spring:
cloud:
inetutils:
preferred-networks:
- 176\.20\.16\..+ #自己的物理网络ip网段 +表示一到多个
注册ip地址,而不是主机名
application.yml
eureka:
instance:
prefer-ip-address: true # 使用ip进行注册
instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port} # 界面列表中显示的格式也显示ip
3. 订单的流量削峰
导入项目
- 课前资料\elasticsearch\pd-商城项目案例.zip 里面的 pd-web 文件夹,解压到 rabbitmq 工程目录
- 打开 rabbitmq 工程
- 修改 pom.xml pom.xml 拖拽到 idea 编辑,修改springboot 版本:2.3.2.RELEASE
- 右键点 pom.xml 的编辑区域,add as maven project
- jdk版本1.8,编译版本8
导入数据库
-
用 sqlyog,右键点数据库连接,从sql转储文件导入,选择 pd-web 模块目录下的 pd.sql 文件
-
导入成功后,右键点数据库连接,刷新
-
如果导入失败,可以增大数据库缓存,在 sqlyog 中执行四条 sql:
set global max_allowed_packet=100000000;
set global net_buffer_length=100000;
SET GLOBAL interactive_timeout=28800000;
SET GLOBAL wait_timeout=28800000
4. 清理测试数据
# 删除测试用户
delete from pd_user
# 删除订单数据
delete from pd_order
delete from pd_order_item
启动项目
- 右键点 RunPdAPP,运行
- 在启动配置中,配置 working directory,设置成 pd-web 文件夹的路径
- 重启 RunPdAPP
- 访问
http://localhost
如果看到浏览器在下载,可以清一下浏览器缓存
订单发送到Rabbitmq
- 添加依赖: rabbitmq
- yml 配置 rabbitmq 连接
- 在启动类(或者自定义自动配置类)中配置队列的参数: orderQueue, true, false, false
- 修改 OrderServiceImpl.saveOrder() 使用 AmqpTemplate 封装工具发送订单
订单的消费者
- rabbitmq的依赖、连接配置、使用的队列的配置
- 新建消费者类: OrderConsumer
- 通过注解配置,从 orderQueue 接收订单
- 调用 OrderServiceImpl.saveOrder() 存储订单
- 调整 OrderServiceImpl
分布式事务
搭建业务案例
数据库初始化工具
- 新建 Empty project 工程: seata-at
- 新建spring模块:db-init
- 添加 spring jdbc、mysql 驱动
- yml配置 数据库连接
- 在 resources/sql/ 文件夹下添加四个sql脚本文件
- 启动类添加初始化方法,执行这四个 sql 脚本
-
mysql 5.x 版本,需要调整sql脚本
- 四个文件中, ctrl+f 查找
datetime(6)
,把(6)
删掉 - order.sql 的最后一行,NOW() 函数改成数字 0
- 四个文件中, ctrl+f 查找
eureka注册中心
-
新建 spring 模块: eureka
-
添加 eureka server 依赖
-
yml 配置:
- 用默认端口 8761
- 禁用自我保护模式
- 单台服务器,不向自己注册,不从自己拉取
-
启动类注解:
@EnableEurekaServer
父项目
- 新建 Maven 模块:order-parent
- 复制 pom.xml
- 删除它的 src 文件夹
账户,库存,订单三个业务模块
-
新建 spring 模块
-
不加依赖,从父项目继承
-
application.yml
- app.name
- port
- eureka
- 数据库连接
- mybatis
- sql语句日志
-
bootstrap.yml
- 选择注册的网卡
-
实体类
-
Mapper
-
Service
-
Controller
全局唯一id发号器
- mirrors / lookingatstarts / easyIdGenerator · GIT CODE 下载源码
- 解压到 seata-at 工程目录
- 文件夹名改成
easy-id
- pom.xml 拖拽到 idea,修改 springboot 版本
- 右键点击编辑区域,add as maven project
- 添加 eureka client 依赖
- 添加 eureka 连接配置
- 调整发号器的配置
订单远程调用 easy-id、库存、账户
-
启动类添加
@EnalbleFeignClients
-
定义远程调用接口
- EasyIdClient
- StorageClient
- AccountClient
-
修改 OrderServiceImpl 完成远程调用
Seata AT 事务
启动 TC 事务协调器
-
从Seata官方网站下载事务协调器服务(课前资料有)
-
配置:
- registry.conf -- 连接 eureka,向注册表注册
- file.conf -- 协调器运行期间记录的日志数据的存储位置
- seata-server.bat -- 设置 JVM 使用的内存大小
-
在 cmd 中运行 seata-server.bat
- 必须用 JDK 1.8
- 命令窗口不能关闭
- 命令窗口不能选中,否则应用会被挂起,暂停执行
在业务模块中添加 Seata AT 事务
-
添加 seata 依赖
-
三个配置:
- application.yml -- 事务组的组名
- registry.conf -- 设置注册中心的地址
- file.conf -- 事务组对用使用哪个协调器
-
自定义自动配置类,创建数据源代理对象
- AT事务的自动事务控制代码,都是由数据源代理对象提供的
-
排除 springboot 默认的数据源自动配置
-
业务方法上添加事务注解
@Transactional
- 控制本地事务@GlobalTransactional
- 创建 TM,启动全局事务,只在第一个模块添加
Seata TCC 事务
TCC 是一种有侵入的事务方案
- 所有事务控制代码都需要自己实现
- 底层的数据库、业务代码都需要修改
有些复杂情况下,AT事务无法实现自动事务控制,就需要手动控制事务的方案
TCC - 两个阶段的三种操作
-
第一个阶段
Try - 预留资源、冻结数据
-
第二阶段
- Confirm - 确认、提交,使用一阶段冻结的数据实现业务数据处理
- Cancel - 取消、回滚,把一阶段冻结的数据恢复回去
导入工程的无事务版本
- 新建 Empty project: seata-tcc
- seata-at/无事务版本.zip,解压到 seata-tcc 工程目录
- 导入模块
业务模块中添加 Seata TCC 事务
-
seata 依赖
-
三个配置文件
- application.yml -- 组名
- registry.conf -- eureka地址
- file.conf -- 事务组对应使用的协调器
-
修改 Mapper,添加新的 TCC 三个数据库操作
-
添加 ResultHolder 控制幂等性
-
按照 Seata TCC 的规则,添加 TccAction 接口和实现,实现三个操作的方法
-
调整业务方法,不直接完成业务,而是调用第一阶段方法冻结数据
-
在第一个模块的业务方法上添加
@GlobalTransactional
Rocketmq
安装搭建 Rocketmq 服务器
-
克隆 centos-8-2105: rocketmq
-
设置 ip
./ip-static
ip: 192.168.64.141
ifconfig
-
上传文件到 /root/
分布式事务\rocketmq\ 文件夹的三个文件 -
按照 csdn 笔记来安装,所有联网下载都不用执行
启动三个服务
cd /usr/local/rocketmq/
nohup sh bin/mqnamesrv &
nohup sh bin/mqbroker -n localhost:9876 &
如果要从配置文件启动,用这个命令:
nohup sh bin/mqbroker -n localhost:9876 -c conf/broker.conf &
cd ~/
新版本:
nohup java -jar rocketmq-dashboard-1.0.1-SNAPSHOT.jar --server.port=8080 --rocketmq.config.namesrvAddr=localhost:9876 &
旧版本:
nohup java -jar rocketmq-console-ng-1.0.1.jar --server.port=8080 --rocketmq.config.namesrvAddr=localhost:9876 &
查看是否有三个服务的进程:
jps
Rocketmq
- 新建 Empty project 工程: rocketmq-dtx
- 新建 Maven 模块: rocketmq-api
- pom.xml 复制添加依赖
可靠消息最终一致性事务
订单异步调用账户
- seata-at/无事务版本.zip,解压到 rocketmq-dtx 工程目录
- 导入模块
修改 Order 订单
-
添加 Rocketmq 依赖
-
yml配置
- name server
- 生产者组名
-
添加新的数据表 tx_table,用来存储本地事务执行状态,来应对事务回查
-
实体类 TxInfo
-
TxMapper,插入状态、查询状态
-
添加 AccountMessage 用来封装向账户发送的消息数据
-
添加 JsonUtil 工具类
-
修改 OrderServiceImpl,在业务方法中发送事务消息
-
实现监听器,执行本地事务和处理事务回查
账户接收消息,执行扣减账户金额
- yml 配置 rocketmq 连接
- 添加 AccountMessage 用来封装接收的消息数据
- 添加 JsonUtil 工具类
- 新建消费者类:AccountConsumer
- 通过注解配置接收消息
- 收到消息后调用业务方法,完成金额扣减
Docker
Elasticsearch
-
克隆 docker-base: es
-
设置ip
./ip-static ip: 192.168.64.181 ifconfig
-
上传文件到 /root/
- elasticsearch/pditems 文件夹
- elasticsearch/elasticsearch-analysis-ik-7.9.3.zip
- elasticsearch/es-img.gz
-
导入镜像
docker load -i es-img.gz
-
服务器内存设置到 2G 或以上
右键点es虚拟机---设置---修改内存大小 -
设置系统的底层参数
echo 'vm.max_map_count=262144' >>/etc/sysctl.conf 查看 cat /etc/sysctl.conf
-
重启服务器
shutdown -r now
案例:拼多商城搜索条
- pom.xml 添加 elasticsearch 依赖
- yml 配置 es 服务器地址列表
- 添加实体类: Item
- 添加 ItemRepository 接口继承ElasticsearchRepository< >,定义商品搜索方法
- SearchService
- SearchController
- search.jsp
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
spring:
elasticsearch:
rest:
uris:
- http://192.168.64.181:9200
- http://192.168.64.181:9201
- http://192.168.64.181:9202
logging:
level:
tracer: trace #REST API调用的 http 协议数据日志
package cn.tedu.springdataes.es;
import cn.tedu.springdataes.entity.Student;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.annotations.Highlight;
import org.springframework.data.elasticsearch.annotations.HighlightField;
import org.springframework.data.elasticsearch.annotations.HighlightParameters;
import org.springframework.data.elasticsearch.core.SearchHit;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import java.util.List;
/*
spring data API 的 Repository 数据访问规范,
不需要自己写代码,只需要定义抽象接口、抽象方法就可以访问数据库的数据
父接口中已经定义了基础增删改查方法
分页:
- Pageable 封装向服务器提交的分页参数
- Page (可选)封装服务器返回的这一页数据,以及所有分页属性
*/
public interface StudentRepository
extends ElasticsearchRepository<Student, Long> {
// 在 name 字段搜索关键词
List<Student> findByName(String key);
/*
SearchHit
需要使用 List<SearchHit<Student>> 返回类型来获得高亮结果,
SearchHit中封装了高亮字段的结果,Student对象中包含的是原始学生数据
*/
@Highlight(
//高亮参数,设置前后标签
parameters = @HighlightParameters(
preTags = "<em>",
postTags = "</em>"
),
//高亮的字段
fields = {@HighlightField(name = "name")}
)
// 在 name 字段,或者 birthDate 字段中搜索
List<SearchHit<Student>> findByNameOrBirthDate(String name, String birthDate, Pageable pageable);
}
@Component
public class StudentSearcher {
// 用来执行查询的工具对象
@Autowired
private ElasticsearchOperations op;
public List<Student> findByName(String key) {
Criteria c = new Criteria("name");
c.is(key); //在 name 字段搜索指定的关键词
return exec(c, null);
}
public List<Student> findByBirthDate(
String from, String to, Pageable pageable) {
Criteria c = new Criteria("birthDate");
c.between(from, to);
return exec(c, pageable);
}
private List<Student> exec(Criteria c, Pageable pageable) {
//把搜索条件和分页参数封装成查询对象
CriteriaQuery q = new CriteriaQuery(c);
if (pageable != null) {
q.setPageable(pageable);
}
//执行搜索,把搜索结果封装成Student对象
SearchHits<Student> shs = op.search(q, Student.class);
List<Student> list = new ArrayList<>();
for (SearchHit<Student> sh : shs) {
list.add(sh.getContent());
}
return list;
}
}
@Service
public class SearchServiceImpl implements SearchService {
@Autowired
private ItemRepository r;
@Override
public List<SearchHit<Item>> search(String key, Pageable pageable) {
return r.findByTitleOrSellPoint(key, key, pageable);
}
}
package com.pd.controller;
import com.pd.pojo.Item;
import com.pd.service.SearchService;
import com.sun.javafx.scene.text.HitInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.SearchHit;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import java.util.ArrayList;
import java.util.List;
@Controller
@RequestMapping("search")
public class SearchController {
@Autowired
private SearchService searchService;
//?key=手机&page=0&size=20
@GetMapping("/toSearch.html")
public String search(Model model, String key, Pageable pageable) {
// model对象用来向jsp传递数据的工具
List<SearchHit<Item>> list = searchService.search(key, pageable);
List<Item> items = zhuanHuan(list); //转换
// items 集合传递到 jsp 进行显示
// pageable 对象传递到 jsp 显示翻页
model.addAttribute("items", items);
model.addAttribute("p", pageable);
return "/search.jsp";
}
private List<Item> zhuanHuan(List<SearchHit<Item>> list) {
List<Item> items = new ArrayList<>();
for (SearchHit<Item> sh : list) {
Item item = sh.getContent(); //原始数据,没有高亮
List<String> hlTitle = sh.getHighlightField("title"); //高亮title
item.setTitle(pinJie(hlTitle));//高亮title替换原始的title数据
items.add(item);
}
return items;
}
private String pinJie(List<String> hlTitle) {
StringBuilder s = new StringBuilder();
for (String s1 : hlTitle) {
s.append(s1);
}
return s.toString();
}
}
Kubernetes
自动容器部署管理工具
谷歌的开源工具
Kubernetes 集群安装非常复杂
有辅助的开源项目,辅助安装集群
- 一键安装:GitHub - easzlab/kubeasz: 使用Ansible脚本安装K8S集群,介绍组件交互原理,方便直接,不受国内网络环境影响
- 手动一步一步安装:GitHub - opsnull/follow-me-install-kubernetes-cluster: 和我一步步部署 kubernetes 集群
K8s集群方案:
- 内存16G或以上: 三台服务器
- 内存8G: 两台服务器
小点:
Kubernetes/k8s集群方案
kubectl命令
3个核心概念:
Pod:k8s的容器,是对docker容器的封装对象
控制器RC(Replication Controller):自动控制容器部署、销毁的工具
Service: 提供一个不变的访问地址,转发请求地址到容器(即调用)
手动创建pod
Pod设置标签:根据标签来选中对应的服务器
kubectl create -f kubia-manual-with-labels.yml
kubectl get po --show-labels
新添加标签
kubectl label po kubia-manual-v2 env=debug
修改标签
kubectl label po kubia-manual-v2 env=debug --overwrite
env=prod/product
creation_method=manual
删除标签:kubectl label po kubia-22f7k env-
节点选择器:根据节点把pod部署到匹配的工作节点服务器
kubectl label node 192.168.64.191 gpu=true
namespace/ns:同一个服务器里不能重名
kubetcl delete po kubia-gpu
标签过滤删除:
kubectl delete po -l creation_method=manual
kubectl delete ns custom-namespace
全部容器删除:kubectl delete po --all
删除后控制器会自动重写创建新的容器
存活探针:
创建配置文件:
kubectl create -f kubia-liveness-probe.yml
查看日志:
kubectl logs kubia-liveness --
livenessProbe:
httpget:
path:
port: 8080
设置延迟: initialDelaySeconds: 15
避免容器内应用没有完全启动的情况下就开始探测
kubectl get svc -ns kube-system
edit编辑
ReplicaSet(新版控制器rs)-->replicas:3-->selector
增强选择器:app in(xxx,xxx)
DS监控器ssd
job控制器
schedule:
cronjob:计划任务控制器
Endpoints:pod和service之间的一种资源
是地址列表,通过双方的name来和service绑定,自动创建
kubectl get ep//查看Endpoints
kubectl describe svc kubia//查看service详情,有关联的endpoints对象
用ip地址来关联容器
手动创建endpoints对象来调用外部系统服务external-service
kubectl get svc//获取IP集群
curl http://xxxxx.com//服务地址
在external-service里面直接指定要调用的一个域名
kubectl exec -it kubia-xxx bash//进入容器执行
service对外暴露:端口暴露到服务器
ports: nodeport节点端口 port targetport
k8s支持数据挂载:
EmptyDir:临时数据文件
index-xml
共享文件夹
安装NFS文件系统:dnf install nfs-utils
mkdir /etc/nfs_data
async同步,no_root_squash:服务器端使用root权限
启动
systemctl enable nfs-server
systemctl enable rpcbind
systemctl start nfs-server
systemctl start rpcbind
挂载到另一服务器的文件夹
mount -t nfs 当前服务器:文件夹 挂载文件夹
mongodb:键值对存储
persistent volume持久数据卷(like实现类):
存储位置
存储条件:
空间:1G
访问模式:readwriteonce/readonlimany
persistent volume claim声明(like接口):存储条件
fortune: args间隔时间覆盖cmd命令参数
fortune: env通过设置环境变量interval覆盖间隔时间参数
config Maps:配置存储cm
sleep-interval: 5
停机升级(新旧版本不兼容时)&逐步替换,滚动升级
Deployment对象:自动化实现滚动升级
创建过程:deploy-->rs-->po
修改镜像image: luksa/kubia: v1-->v2--v3...版本
创建出RS控制器,并控制控制器的容器数量
配置的容器数量spec: replicas: 3
添加属性:kubectl patch deploy kubia -p xxx
通过service来回调用容器pod
kubectl rollout status deploy kubia//查看滚动升级状态
kubectl get deploy
hash值标签hash=xxxx
新版本测试失败需要回滚到上一版本:kubectl rollout undo deploy kubia
控制滚动升级速率:
maxSurge 25% 滚动过程中可以超出的容器数量
maxUnavailable 25% 最多允许多少容器不可用
kubectl get deploy -o yaml
pause暂停升级
就绪探针:readinessProbe: periodSeconds: 1
探测成功,容器变为就绪状态
工作中可以使用Deployment对象来部署容器,方便之后的升级
K8S部署springcloud项目:
构建镜像:上传文件到root
镜像jdk-->各个模块的镜像-->压缩复制到其他服务器-scp->导入load
-->部署控制器、service
docker build -t centos7-jdk8:v1 jdk/
docker build -t sp-eureka:v1 eureka/
控制器名:metadata: name: eureka1