第一天
第五阶段
19天
第一部分:6天左右 微服务的框架介绍,和演示微服务项目的功能
第二部分:11天酷鲨商城的前台
第三部分:2天容器化,linux,Docker
微信答疑:
课程中知识点的问题
学习过程中节奏的问题
代码中的疑问\报错
答疑时间:课间,课后
中午12:00 -13:00
下午18:00-21:30
节假日9:00-21:30(回复时间可能会慢)
课间问题比较多可能回复会慢
课间 x:50 休息15分钟
学习方法
1.反复练习,敲完一次再敲一次,不断思考
2.理解业务,什么需求,为什么需要这么做
3.对于比较难理解的知识点,同桌可能互相给对方讲一遍
Vant
什么是Vant
Vant是一个轻量,可靠的移动端组件库,2017开源
目前 Vant 官方提供了 Vue 2 版本、Vue 3 版本和微信小程序版本,并由社区团队维护 React 版本和支付宝小程序版本。
课程中我们使用Vue2版本对应的Vant学习
https://youzan.github.io/vant/v2/#/zh-CN/
Vant的优势
ElementUI是开发计算机浏览器(非移动端)页面的组件库
而Vant是开发移动端页面的组件库
我们设计的酷鲨商城前台项目使用手机\移动设备访问的,所以使用Vant更合适
Vant特性
- 🚀性能极佳,组件平均体积小于 1KB(min+gzip)
- 🚀 65+ 个高质量组件,覆盖移动端主流场景
- 💪 使用 TypeScript 编写,提供完整的类型定义
- 💪 单元测试覆盖率超过 90%,提供稳定性保障
- 📖 提供完善的中英文文档和组件示例
- …
第一个Vant程序
创建Vue项目
参考第四阶段创建Vue项目的步骤,创建Vue项目
首先确定一个要创建项目的文件夹
例如在D盘下创建vue-home文件夹
进入文件夹,在地址栏输入cmd 打开dos命令行界面
D:\vue-home>vue create demo-vant
创建时具体选项,参照之前四阶段的笔记即可
下面我们就可以用idea打开我们创建的项目了
安装Vant支持
我们创建的Vue项目并不会默认就支持Vant
所以,我们需要安装Vant的支持到Vue项目中
在打开的idea界面最下方,找到Terminal点击
在出现的命令行中输入安装Vant的指令
运行安装Vant的npm命令:
F:\vue-home\demo-vant>npm i vant@latest-v2 -S
安装结束后可能有警告,可以忽略
最终可能看到下面的结果
added 5 packages in 4s
到此为止,我们就将Vant运行需要的文件安装在vue项目中的
git地址:
https://gitee.com/jtzhanghl/vant2204.git
添加Vant引用
如果想要在项目中使用Vant提供的组件
需要在Vue项目代码中添加Vant的引用
Vue项目的src/main.js文件中,添加引用代码如下
import Vant from 'vant'
import 'vant/lib/index.css'
Vue.use(Vant)
添加了上面的引用,当前Vue项目就可以使用Vant组件了
为了实时运行项目,我们先启动Vue项目,测试表示它正常运行
可以在idea提供的Terminal界面中编写如下代码
D:\vue-home\demo-vant>npm run serve
打开浏览器
输入localhost:8080
进入移动端页面调试模式
以Google浏览器为例按F12进入调试模式后点击移动端调试即可
按钮组件
在HomeView.vue中修改代码如下
<template>
<div class="home">
<van-button type="primary">主要按钮</van-button>
<van-button type="info">信息按钮</van-button>
<van-button type="default">默认按钮</van-button>
<van-button type="warning">警告按钮</van-button>
<van-button type="danger">危险按钮</van-button>
</div>
</template>
打开页面就能看到按钮的样式了
看到这个内容,表示当前Vant组件工作正常
如果没有看到效果
检查Vant组件的安装和引用
表单页面
登录作为移动端非常常见的界面
Vant表单是直接提供模板的,我们可以在官网找到表单链接直接使用
<template>
<div class="about">
<!--
@submit是vant组件提供的Event(事件),在表单提交成功时触发,所以这个名字不能修改
@failed是vant组件提供的Event(事件),在表单提交失败时触发,这个名字也不能修改
在事件名称后编写方法名实现调用
-->
<van-form @submit="onSubmit" @failed="myFail">
<van-field
v-model="username"
name="用户名"
label="用户名"
placeholder="用户名"
:rules="[{ required: true, message: '请填写用户名' }]"
/>
<van-field
v-model="password"
type="password"
name="密码"
label="密码"
placeholder="密码"
:rules="[{ required: true, message: '请填写密码' }]"
/>
<div style="margin: 16px;">
<van-button round block type="info" native-type="submit">提交</van-button>
</div>
</van-form>
</div>
</template>
<script>
export default {
data() {
return {
username: '',
password: '',
};
},
methods: {
onSubmit(values) {
console.log('submit', values);
},
myFail(errorInfo){
// 在表单提交验证失败时运行的方法,errorInfo是错误信息
console.log(errorInfo);
}
},
};
</script>
注意@submit和@failed这两个事件的绑定关系
area省市区选择
先在views文件夹下创建AreaView.vue文件
定义路由设置
const routes = [
{
path: '/',
name: 'home',
component: HomeView
},
{
path: '/about',
name: 'about',
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import(/* webpackChunkName: "about" */ '../views/AboutView.vue')
},
{
path: '/area',
name: 'area',
component: () => import('../views/AreaView.vue')
}
]
编写代码如下
<template>
<div>
<van-area title="标题" :area-list="areaList" />
</div>
</template>
<script>
const areaList = {
province_list: {
110000: '北京市',
120000: '天津市',
},
city_list: {
110100: '北京市',
120100: '天津市',
},
county_list: {
110101: '东城区',
110102: '西城区',
// ....
},
};
export default {
data(){
return {areaList};
}
}
</script>
通过输入路径http://localhost:8080/area
访问省市区选择页面
只有少量数据
git地址:https://gitee.com/jtzhanghl/vant2203.git
如果想要真实的数据,那么就需要在上面areaList数据中填入大量数据
但是这个工作量很大,个人实现非常困难,所以可以使用业界通用的一个省市区信息数组
先安装全国省市区数据包
D:\vue-home\demo-vant>npm i @vant/area-data
安装结果可能为
added 1 package in 2s
如果该安装成功,就可以添加全国省市区数据到项目中了
AreaView.vue代码中添加如下
<template>
<div>
<van-area title="标题" :area-list="areaList" @confirm="showArea" />
</div>
</template>
<script>
// 从全国省市区数据包中提取数据,命名为areaList
import {areaList} from '@vant/area-data'
export default {
data(){
return{areaList}
},
methods:{
showArea(area){
console.log(area);
}
}
}
</script>
课堂作业
新建一个AddressView.vue文件
完成Vant地址列表的效果
参考vant2官网的资料,创建页面,设置路由,加载正确代码
测试运行出现效果即可
商品列表
我们的电商网站一定会需要商品列表
移动端Vant直接支持了商品列表的格式
我们也不需要大范围的修改
创建ListView.vue
代码如下
<template>
<div>
<van-row>
<van-col span="8">综合</van-col>
<van-col span="8">销量</van-col>
<van-col span="8">价格</van-col>
</van-row>
<van-card
num="2"
price="2.00"
desc="【8月18日食品生鲜】潮能破壁,真香正当时!下单抽奖即有机会得荣耀手机!速戳链接查看>"
title="胡姬花 食用油 古法压榨一九一八花生油5.7L"
thumb="https://img01.yzcdn.cn/vant/ipad.jpeg"
>
<template #tags>
<van-tag plain type="danger">京东物流</van-tag>
<van-tag type="danger">自营</van-tag>
</template>
<template #footer>
<van-button size="mini">加入到购物车</van-button>
<van-button size="mini">立即购买</van-button>
</template>
</van-card>
<van-card
num="2"
price="2.00"
desc="描述信息"
title="商品标题"
thumb="https://img01.yzcdn.cn/vant/ipad.jpeg"
>
<template #tags>
<van-tag plain type="danger">京东物流</van-tag>
<van-tag type="danger">自营</van-tag>
</template>
<template #footer>
<van-button size="mini">加入到购物车</van-button>
<van-button size="mini">立即购买</van-button>
</template>
</van-card>
</div>
</template>
<script>
</script>
服务器端项目演进
阶段一:静态服务器
早期的服务器状态,安装好一些固定内容,让用户访问
功能单一,如果不修改代码,内容是不会变的,只能做信息的呈现或输出
阶段二:普通动态服务器
网页中的数据可能来自数据库,数据库中的数据可以在后台中进行修改
实现不修改页面代码,但是变化页面内容的效果
因为有了数据库的支持,动态网站开始支持登录注册,增删改查功能
阶段三:用户共享内容的互联网生态
随着互联网的普及,个人的社交需求提升
出现了很多由用户贡献内容的网站
微博,抖音,淘宝,大众点评或类似的网站
阶段四:微服务时代
随着用户的增加,各种并发的增高,要求我们的服务器在繁忙的情况下,也需要快速的做出响应
用户体验必须保证,这样就要求我们的项目有下面三个目标
“高并发,高可用,高性能”
高并发:很多人同时访问这个网站,这个网站不能失能
高可用:全年365天每天24小时随时可以访问,不能因为个别服务器的异常,导致整个项目瘫痪
高性能:当有用户访问时响应的速度要尽量的快,及时并发高,也要快速响应
微服务的"三高"
Java服务器项目分类
现在市面上常见的java开发的项目可以分为两大类
1.企业级应用
一般指一个企业或机构内部使用的网站或服务器应用程序
使用的人群比较固定,并不向全国乃至全世界开放
例如,商业,企事业单位,医疗,金融,军事,政府等
所以这个项目没有代替品,对"三高"没有强烈要求
企业级项目一般会在权限和业务流程方面设计的比较复杂
2.互联网应用
能够向全国乃至全世界开放的网站或服务器应用程序
我们手机中安装的app大部分都是互联网应用
微信,支付宝,京东,淘宝,饿了么,美团,抖音,qq音乐,爱奇艺,高德地图等
它们因为商业竞争等原因,对服务器的性能有非常高的要求,就是我们之前提到的"三高"
但是互联网应用一般没有权限和业务非常复杂的需求
综上所述,企业级应用和互联网应用的偏重点不同
在当今java开发业界中,基本规律如下
- 如果开发的是企业级应用,使用单体架构的情况比较多
- 如果开发的是互联网应用,使用微服务架构的情况比较多
微服务概述
什么是微服务
微服务的概念是由Martin Fowler(马丁·福勒)在2014年提出的
微服务是由以单一应用程序构成的小服务,自己拥有自己的行程与轻量化处理,服务依业务功能设计,以全自动的方式部署,与其他服务使用 HTTP API 通信。同时服务会使用最小的规模的集中管理能力,服务可以用不同的编程语言与数据库等组件实现。
简单来说,微服务就是将一个大型项目的各个业务代码,拆分成多个互不相干的小项目,而这些小项目专心的完成自己的功能,而且可以调用别的小项目的方法,从而完成整体功能
京东\淘宝这样的大型互联网应用程序,基本每个操作都是一个单独的微服务在支持:
- 登录服务器
- 搜索服务器
- 商品信息服务器
- 购物车服务器
- 订单服务器
- 支付服务器
- 物流服务器
- …
为什么需要微服务
左侧小餐馆就像单体项目
一旦服务器忙,所有业务都无法快速响应
即使添加了服务器,也不能很好的解决这个问题
不能很好的实现"高并发,高可用,高性能"
但是因为服务器数量少,所以成本低,适合并发访问少的项目
右侧大餐厅就是微服务项目
每个业务专门一批人来负责,业务之间互不影响
在某个模块性能不足时,针对这个模块添加服务器改善性能
万一一个服务器发生异常,并不会影响整体功能
但是完成部署的服务器数量多,成本高,需要较多投资,能够满足"高并发,高可用,高性能"的项目
怎么搭建微服务项目
在微服务概念提出之前(2014年),每个厂商都有自己的解决方案
但是Martin Fowler(马丁·福勒)提出了微服务的标准之后,为了技术统一和兼容性,很多企业开始支持这个标准
现在我们开发的微服务项目,大多数都是在马丁·福勒标准下的
如果我们自己编写支持这个标准的代码是不现实的,必须通过现成的框架或组件完成满足这个微服务标准的项目结构和格式
当今程序员要想快速开发满足上面微服务标准的项目结构,首选SpringCloud
Spring Cloud
什么是SpringCloud
SpringCloud是由Spring提供的一套能够快速搭建微服务架构程序的框架集
框架集表示SpringCloud不是一个框架,而是很多框架的统称
SpringCloud就是为了搭建微服务架构项目出现的
有人将SpringCloud称之为"Spring全家桶",广义上指代Spring的所有产品
Spring Cloud的内容
内容的提供者
- Spring自己编写的框架或软件
- Netflix(奈非):早期提供了全套的微服务架构解决方案
- alibaba(阿里巴巴):新版本的SpringCloudAlibaba正在迅速占领市场(推荐使用)
课程中使用全套的阿里巴巴组件
功能上分类
- 微服务的注册中心
- 微服务间的调用
- 微服务的分布式事务
- 微服务的限流
- 微服务的网关
- …
注册中心Nacos
什么是Nacos
Nacos是Spring Cloud Alibaba提供的一个软件
这个软件主要具有注册中心和配置中心(课程最后讲解)的功能
我们先学习它注册中心的功能
微服务中所有项目都必须注册到注册中心才能成为微服务的一部分
注册中心和企业中的人力资源管理部门有相似
当前微服务项目中所有的模块,在启动前,必须添加注册到Nacos的配置
所谓注册,就是将自己的信息,提交到Nacos来保存
Nacos的下载
https://github.com/alibaba/nacos/releases/download/1.4.3/nacos-server-1.4.3.zip
国外网站,下载困难可以多试几次
或直接向项目经理老师索取
Nacos的启动
因为Nacos是java开发的
我们要启动Nacos必须保证当前系统配置了java环境变量
简单来说就是要环境变量中,有JAVA_HOME的配置,指向安装jdk的路径
确定了支持java后,就可以启动Nacos了
mac系统同学一定要到http://doc.canglaoshi.org/查看homebrew相关知识
mac系统安装Nacos推荐
https://blog.csdn.net/gongzi_9/article/details/123359171
将压缩包解压(注意不要有中文路径或空格)
打开解压得到的文件夹后打开bin目录会有如下内容
cmd结尾的文件是windows版本的
sh结尾的文件是linux和mac版本的
startup是启动文件,shutdown是停止文件
Windows下启动Nacos不能直接双击cmd文件
需要在dos窗口运行
在当前资源管理器地址栏输入cmd
D:\tools\nacos\bin>startup.cmd -m standalone
startup.cmd:windows启动nacos的命令文件
-m 表示要设置启动参数
standalone:翻译为标准的孤独的,意思是正常的使用单机模式启动
运行成功默认占用8848端口,并且在代码中提示
如果不输入standalone运行会失败
startup.cmd -m standalone
如果报了
“please set JAVA_HOME…”
表示当前项目没有配置java环境变量(主要是没有设置JAVA_HOME)
如果运行没有报错
打开浏览器输入地址
http://localhost:8848/nacos
如果是首次访问,会出现这个界面
登录系统
用户名:nacos
密码:nacos
登录之后可以进入后台列表
不能关闭启动nacos的dos窗口
我们要让我们编写的项目注册到Nacos,才能真正是微服务项目
第二天
创建csmall项目
我们要搭建一个项目,用于学习各种微服务知识
搭建的过程有很多新的标准,需要我们掌握和学习
发给大家的3个csmall的项目
csmall-finish.zip:这个项目是当前学习过程中需要使用的项目,有些配置和固定的代码,可以从中复制
csmall-mobile-repo.zip:酷鲨商城前台前端项目
csmall-jsd2203.zip:酷鲨商城前台后端项目模板
业务概述
我们通过学习电商网站添加订单的业务来学习需要使用到的微服务组件
我们模拟用户选中了购物车中的商品后,点击"确认订单"按钮后的业务操作
1.减少用户选中商品的库存数量(sku)
2.删除用户购物车中勾选的商品
3.生成订单,将订单信息保存到数据库
我们将上面3个操作设计为3个模块完成
- 库存模块:减少库存
- 购物车模块:删除购物车信息
- 订单模块:新增订单
除此之外,我们还要创建一个项目对新增订单的业务进行触发操作
- 触发模块:business
创建csmall父项目
创建项目名称csmall
我们微服务开发过程中,一般都会使用一个Idea中包含多个项目的形式
这个形式就是先创建一个"父项目",再在这个父项目中创建多个子项目的操作
我们首先创建一个项目:csmall
注意细节的修改
首先,将当前csmall项目修剪为父项目的格式
- 删除csmall项目的src文件夹,因为父项目不写代码
- 修改pom文件
最终csmall的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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>cn.tedu</groupId>
<artifactId>csmall</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>csmall</name>
<description>Demo project for Spring Boot</description>
<!-- 当前项目会以一个pom文件的形式被子项目继承 -->
<packaging>pom</packaging>
</project>
https://gitee.com/jtzhanghl/csmall-jsd2204.git
创建子项目
创建csmall-stock项目
我们每次创建一个子项目之后
都要进行"父子相认"
在父项目的pom文件中,编写子项目的存在
<!-- 表示当前项目是一个父项目,以pom文件的形式,供子项目继承 -->
<packaging>pom</packaging>
<!-- 当前父项目包含的所有模块,module就是模块的意思 -->
<modules>
<module>csmall-stock</module>
</modules>
还需要在子项目的pom文件中对父项目进行继承操作
父项目的第11行到第13行
复制到子项目的第6行到第8行
子项目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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.tedu</groupId>
<artifactId>csmall</artifactId>
<version>0.0.1-SNAPSHOT</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>cn.tedu</groupId>
<artifactId>csmall-stock</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>csmall-stock</name>
<description>Demo project for Spring Boot</description>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
</dependencies>
</project>
父子相认完成
这样当前子项目就可以读取父项目中的pom文件信息了
删除stock模块src\test文件夹
父项目管理依赖版本
在我们现在使用maven添加依赖的认知中
有些依赖时必须添加版本号才能执行
有些依赖则不必添加版本号
原因是我们继承的SpringBoot(2.5.9)父项目中,定义了一些常用依赖的版本号
如果我们自己编写的父项目想定义我们项目中需要的依赖版本号的话,也是可以实现的
这样做可以统一所有子项目的版本,在更新版本时,只需要修改父项目中定义的版本号即可
父项目的pom文件添加如下内容
<!-- 定义父项目需要的版本号参数 -->
<properties>
<mybatis.version>2.2.2</mybatis.version>
</properties>
<!-- dependencyManagement不是添加依赖,而是确定子项目使用依赖时的版本,也称锁版本 -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
子项目中如果需要mybatis的依赖只需要添加如下内容即可,无需再指定版本号
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
上面的操作也称之为"锁版本"
前端Vant项目git地址
https://gitee.com/jtzhanghl/vant2204.git
包含今天笔记的csmall项目
https://gitee.com/jtzhanghl/csmall-jsd2204.git
加载正式项目pom文件
因为我们学习微服务的过程中需要很多微服务相关的依赖
这些依赖都需要在父项目中进行版本的管理的
所以我们直接使用分享给大家的完整版项目的父项目pom文件即可
父项目完整最终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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.9</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>cn.tedu</groupId>
<artifactId>csmall</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>csmall</name>
<description>Demo project for Spring Boot</description>
<!-- 表示当前项目是一个父项目,以pom文件的形式,供子项目继承 -->
<packaging>pom</packaging>
<!-- 当前父项目包含的所有模块,module就是模块的意思 -->
<modules>
<module>csmall-stock</module>
</modules>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>2020.0.3</spring-cloud.version>
<spring-cloud-alibaba.version>2.2.2.RELEASE</spring-cloud-alibaba.version>
<spring-boot.version>2.5.4</spring-boot.version>
<spring-boot-configuration-processor.version>2.3.0.RELEASE</spring-boot-configuration-processor.version>
<spring-security-jwt.version>1.0.10.RELEASE</spring-security-jwt.version>
<mybatis-spring-boot.version>2.2.0</mybatis-spring-boot.version>
<mybaits-plus.version>3.4.1</mybaits-plus.version>
<pagehelper-spring-boot.version>1.4.0</pagehelper-spring-boot.version>
<mysql.version>8.0.26</mysql.version>
<lombok.version>1.18.20</lombok.version>
<knife4j-spring-boot.version>2.0.9</knife4j-spring-boot.version>
<spring-rabbit-test.version>2.3.10</spring-rabbit-test.version>
<spring-security-test.version>5.5.2</spring-security-test.version>
<fastjson.version>1.2.45</fastjson.version>
<druid.version>1.1.20</druid.version>
<jjwt.version>0.9.0</jjwt.version>
<seata-server.version>1.4.2</seata-server.version>
</properties>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
<!-- 依赖管理 -->
<dependencyManagement>
<dependencies>
<!--seata-all-->
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-all</artifactId>
<version>${seata-server.version}</version>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
<!-- MySQL -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
<scope>runtime</scope>
</dependency>
<!-- Alibaba Druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>${druid.version}</version>
</dependency>
<!-- MyBatis Spring Boot:数据访问层MyBatis编程 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis-spring-boot.version}</version>
</dependency>
<!-- MyBatis Plus Spring Boot:MyBatis增强 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybaits-plus.version}</version>
</dependency>
<!-- MyBatis Plus Generator:代码生成器 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>${mybaits-plus.version}</version>
</dependency>
<!-- PageHelper Spring Boot:MyBatis分页 -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>${pagehelper-spring-boot.version}</version>
</dependency>
<!-- Spring Boot:基础框架 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<!-- Spring Boot Web:WEB应用 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<!-- Spring Boot Freemarker:MyBaits Plus Generator的辅助项 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<!-- Spring Boot Validation:验证请求参数 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<!-- Spring Boot Security:认证授权 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<!-- Spring Boot Oauth2:认证授权 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<!-- Spring Boot配置处理器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<version>${spring-boot-configuration-processor.version}</version>
</dependency>
<!-- Spring Security JWT -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-jwt</artifactId>
<version>${spring-security-jwt.version}</version>
</dependency>
<!-- Knife4j Spring Boot:在线API -->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
<version>${knife4j-spring-boot.version}</version>
</dependency>
<!-- Spring Boot Data Redis:缓存 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<!-- Spring Boot Data MongoDB:缓存 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<!-- Spring Boot Data Elasticsearch:文档搜索 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<!-- Spring Boot AMQP:消息队列 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<!-- Spring Boot Actuator:健康监测 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<!-- Spring Cloud家族 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- Spring Cloud Alibaba -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- Alibaba FastJson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
</dependency>
<!-- JJWT -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>${jjwt.version}</version>
</dependency>
<!-- Spring Boot Test:测试 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>${spring-boot.version}</version>
<scope>test</scope>
</dependency>
<!-- Spring Rabbit Test:消息队列测试 -->
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit-test</artifactId>
<version>${spring-rabbit-test.version}</version>
<scope>test</scope>
</dependency>
<!-- Spring Security Test:Security测试 -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<version>${spring-security-test.version}</version>
<scope>test</scope>
</dependency>
<!--seata整合springboot-->
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>${seata-server.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>
创建通用模块(项目)commons
创建项目
在实际开发中
经常有一个类需要在多个微服务项目中使用的情况
为了减少代码的冗余
我们在父项目中创建一个子项目commons专门保存编写这样的类
然后哪个微服务需要使用,就添加对commons的依赖即可
我们先来创建csmall-commons这个项目
父子相认
<module>csmall-commons</module>
子项目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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.tedu</groupId>
<artifactId>csmall</artifactId>
<version>0.0.1-SNAPSHOT</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>cn.tedu</groupId>
<artifactId>csmall-commons</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>csmall-commons</name>
<description>Demo project for Spring Boot</description>
<dependencies>
<!--在线api文档-->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
</dependency>
<!-- Spring Boot Web:WEB应用 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-json</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</project>
当前模块只是编写通用类和代码
实际上不需要运行
所以删除运行相关的文件
删除test测试文件夹
删除resources文件夹
删除SpringBoot启动类
创建实体类
带大家编写一个我们需要的实体类
创建cn.tedu.csmall.commons.pojo.cart.dto
包中创建CartAddDTO,代码如下
DTO:前端收集到数据发送给后端的实体
VO:后端从数据库查询出来要发送给前端的实体
@Data
// 定义knife4j在线文档解释文本的注解
@ApiModel("新增购物车商品信息的DTO")
public class CartAddDTO implements Serializable {
@ApiModelProperty(value = "商品编号", name="commodityCode",example = "PC100")
private String commodityCode; // 商品编号
@ApiModelProperty(value = "商品单价", name="price",example = "136")
private Integer price; // 商品单价
@ApiModelProperty(value = "商品数量", name="count",example = "3")
private Integer count; // 商品数量
@ApiModelProperty(value = "用户ID", name="userId",example = "UU100")
private String userId; // 用户ID
}
创建包
pojo.cart.model
包中创建类Cart
@Data
public class Cart implements Serializable {
private Integer id;
// 商品编号
private String commodityCode;
// 价格
private Integer price;
// 数量
private Integer count;
// 用户id
private Integer userId;
}
下面创建订单模块需要的类
pojo.order.dto.OrderAddDTO
@ApiModel("新增订单的DTO")
@Data
public class OrderAddDTO implements Serializable {
@ApiModelProperty(value = "用户id",name="userId",example = "UU100")
private String userId;
@ApiModelProperty(value = "商品编号",name="commodityCode",example = "PC100")
private String commodityCode;
@ApiModelProperty(value = "商品数量",name="count",example = "5")
private Integer count;
@ApiModelProperty(value = "总金额",name="money",example = "50")
private Integer money;
}
pojo.order.model.Order
@Data
public class Order implements Serializable {
private Integer id;
private String userId;
private String commodityCode;
private Integer count;
private Integer money;
}
最后是库存相关的类
pojo.stock.dto.StockReduceCountDTO
@ApiModel("商品减少库存DTO")
@Data
public class StockReduceCountDTO implements Serializable {
@ApiModelProperty(value = "商品编号",name="commodityCode",example = "PC100")
private String commodityCode;
@ApiModelProperty(value = "减库存数",name="reduceCount",example = "5")
private Integer reduceCount;
}
pojo.stock.model.Stock
@Data
public class Stock implements Serializable {
private Integer id;
private String commodityCode;
private Integer reduceCount;
}
创建异常相关类
除了实体类多个模块需要使用之外
像异常类和控制器返回的JsonResult类也是多个模块需要使用的类型
它们也要编写在commons中
创建cn.tedu.csmall.commons.restful包
在这个包中先创建异常响应码枚举
/**
* 错误代码枚举类型
*/
public enum ResponseCode {
OK(200),
BAD_REQUEST(400),
UNAUTHORIZED(401),
FORBIDDEN(403),
NOT_FOUND(404),
NOT_ACCEPTABLE(406),
CONFLICT(409),
INTERNAL_SERVER_ERROR(500);
private Integer value;
ResponseCode(Integer value) {
this.value = value;
}
public Integer getValue() {
return value;
}
}
下面创建自定义异常类
创建包cn.tedu.csmall.commons.exception
包中创建类CoolSharkServiceException
/**
* 业务异常
*/
@Data
@EqualsAndHashCode(callSuper = false)
public class CoolSharkServiceException extends RuntimeException {
private ResponseCode responseCode;
public CoolSharkServiceException(ResponseCode responseCode, String message) {
super(message);
setResponseCode(responseCode);
}
}
将restful包中用于控制器返回的JsonResult类复制
/**
* 通用响应对象
*/
@Data
public class JsonResult<T> implements Serializable {
/**
* 状态码
*/
@ApiModelProperty(value = "业务状态码", position = 1, example = "200, 400, 401, 403, 404, 409, 500")
private Integer state;
/**
* 消息
*/
@ApiModelProperty(value = "业务消息", position = 2, example = "登录失败!密码错误!")
private String message;
/**
* 数据
*/
@ApiModelProperty(value = "业务数据", position = 3)
private T data;
/**
* 创建响应结果对象,表示"成功",不封装其它任何数据
* @return 响应结果对象
*/
public static JsonResult<Void> ok() {
return ok("OK");
}
public static JsonResult ok(String message){
JsonResult jsonResult=new JsonResult();
jsonResult.setState(ResponseCode.OK.getValue());
jsonResult.setMessage(message);
jsonResult.setData(null);
return jsonResult;
}
/**
* 创建响应结果对象,表示"成功",且封装客户端期望响应的数据
* @param data 客户端期望响应的数据
* @return 响应结果对象
*/
public static <T> JsonResult<T> ok(String message,T data) {
JsonResult<T> jsonResult = new JsonResult<>();
jsonResult.setState(ResponseCode.OK.getValue());
jsonResult.setData(data);
return jsonResult;
}
/**
* 创建响应结果对象,表示"失败",且封装"失败"的描述
*
* @param e CoolSharkServiceException异常对象
* @return 响应结果对象
*/
public static JsonResult<Void> failed(CoolSharkServiceException e) {
return failed(e.getResponseCode(), e);
}
/**
* 创建响应结果对象,表示"失败",且封装"失败"的描述
*
* @param responseCode "失败"的状态码
* @param e "失败"时抛出的异常对象
* @return 响应结果对象
*/
public static JsonResult<Void> failed(ResponseCode responseCode, Throwable e) {
return failed(responseCode, e.getMessage());
}
/**
* 创建响应结果对象,表示"失败",且封装"失败"的描述
*
* @param responseCode "失败"的状态码
* @param message "失败"的描述文本
* @return 响应结果对象
*/
public static JsonResult<Void> failed(ResponseCode responseCode, String message) {
JsonResult<Void> jsonResult = new JsonResult<>();
jsonResult.setState(responseCode.getValue());
jsonResult.setMessage(message);
return jsonResult;
}
}
我们编写的所有模块控制层发送异常时,也都由SpringMvc的统一异常处理类来处理
所以commons模块编写统一异常处理类也是常规操作
/**
* 全局异常处理器
*/
@RestControllerAdvice
@Slf4j
public class GlobalControllerExceptionHandler {
/**
* 处理业务异常
*/
@ExceptionHandler({CoolSharkServiceException.class})
public JsonResult<Void> handleCoolSharkServiceException(CoolSharkServiceException e) {
log.debug("出现业务异常,业务错误码={},描述文本={}", e.getResponseCode().getValue(), e.getMessage());
e.printStackTrace();
JsonResult<Void> result = JsonResult.failed(e);
log.debug("即将返回:{}", result);
return result;
}
/**
* 处理绑定异常(通过Validation框架验证请求参数时的异常)
*/
@ExceptionHandler(BindException.class)
public JsonResult<Void> handleBindException(BindException e) {
log.debug("验证请求数据时出现异常:{}", e.getClass().getName());
e.printStackTrace();
String message = e.getBindingResult().getFieldError().getDefaultMessage();
JsonResult<Void> result = JsonResult.failed(ResponseCode.BAD_REQUEST, message);
log.debug("即将返回:{}", result);
return result;
}
/**
* 处理系统(其它)异常
*/
@ExceptionHandler({Throwable.class})
public JsonResult<Void> handleSystemError(Throwable e) {
log.debug("出现系统异常,异常类型={},描述文本={}", e.getClass().getName(), e.getMessage());
e.printStackTrace();
JsonResult<Void> result = JsonResult.failed(ResponseCode.INTERNAL_SERVER_ERROR, e);
log.debug("即将返回:{}", result);
return result;
}
}
commons模块内容编写暂时告一段落
创建business模块
business:商业\生意\业务的意思
这个模块创建出来是为了触发订单业务的
创建项目
创建子项目csmall-business
父子相认
子项目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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.tedu</groupId>
<artifactId>csmall</artifactId>
<version>0.0.1-SNAPSHOT</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>cn.tedu</groupId>
<artifactId>csmall-business</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>csmall-business</name>
<description>Demo project for Spring Boot</description>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>cn.tedu</groupId>
<artifactId>csmall-commons</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
删除测试的test文件夹
配置文件properties和yml
在今后企业中实际开发情况下,程序员更多愿意使用yml文件
原因是yml文件配置信息更加简洁
properties文件配置数据源
spring.datasource.url=jdbc:mysql://localhost:3306/csmall_db?useSSL=false&useUnicode=true&characterEncoding=utf-8
spring.datasource.username=root
spring.datasource.password=root
yml文件配置数据源
spring:
datasource:
url: jdbc:mysql://localhost:3306/csmall_db?useSSL=false&useUnicode=true&characterEncoding=utf-8
username: root
password: root
下面我们就使用yml文件配置business模块
创建application.yml文件,删除application.properties文件
yml文件内容如下
server:
port: 20000
#公共配置
mybatis:
configuration:
cache-enabled: false # 不启用mybatis缓存
map-underscore-to-camel-case: true # 映射支持驼峰命名法
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 将运行的sql输出到控制台
knife4j:
# 开启增强配置
enable: true
# 生产环境屏蔽,开启将禁止访问在线API文档
production: false
# Basic认证功能,即是否需要通过用户名、密码验证后才可以访问在线API文档
basic:
# 是否开启Basic认证
enable: false
# 用户名,如果开启Basic认证却未配置用户名与密码,默认是:admin/123321
username: root
# 密码
password: root
spring:
profiles:
active: dev
我们在配置末尾看到了
spring:
profiles:
active: dev
上面的配置含义是让SpringBoot读取额外配置文件
我们参数值编写的是dev,那么springBoot会读取application-dev.yml
dev是可以随意修改的名字,没有任何固定要求
SpringBoot相关配置
创建config包,编写一些必要配置
首先创建CommonsConfiguration
// 只有添加了@Configuration才能配置当前Spring环境
@Configuration
// 扫描commons模块的统一异常处理类,使其在项目中生效
@ComponentScan("cn.tedu.csmall.commons.exception")
public class CommonsConfiguration {
}
下面配置knife4JConfiguration
这个类直接复制即可
/**
* Knife4j(Swagger2)的配置
*/
@Configuration
@EnableSwagger2WebMvc
public class Knife4jConfiguration {
/**
* 【重要】指定Controller包路径
*/
private String basePackage = "cn.tedu.csmall.business.controller";
/**
* 分组名称
*/
private String groupName = "base-business";
/**
* 主机名
*/
private String host = "http://java.tedu.cn";
/**
* 标题
*/
private String title = "酷鲨商城项目案例在线API文档--基础business-web实例";
/**
* 简介
*/
private String description = "构建基础business-web项目,实现购买";
/**
* 服务条款URL
*/
private String termsOfServiceUrl = "http://www.apache.org/licenses/LICENSE-2.0";
/**
* 联系人
*/
private String contactName = "项目研发部";
/**
* 联系网址
*/
private String contactUrl = "http://java.tedu.cn";
/**
* 联系邮箱
*/
private String contactEmail = "java@tedu.cn";
/**
* 版本号
*/
private String version = "1.0-SNAPSHOT";
@Autowired
private OpenApiExtensionResolver openApiExtensionResolver;
@Bean
public Docket docket() {
String groupName = "1.0-SNAPSHOT";
Docket docket = new Docket(DocumentationType.SWAGGER_2)
.host(host)
.apiInfo(apiInfo())
.groupName(groupName)
.select()
.apis(RequestHandlerSelectors.basePackage(basePackage))
.paths(PathSelectors.any())
.build()
.extensions(openApiExtensionResolver.buildExtensions(groupName));
return docket;
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title(title)
.description(description)
.termsOfServiceUrl(termsOfServiceUrl)
.contact(new Contact(contactName, contactUrl, contactEmail))
.version(version)
.build();
}
}
开发business的业务代码
因为business模块是业务的触发者,所以不需要数据库操作,直接编写业务逻辑层即可
创建service包,包中创建IBusinessService接口
代码如下
public interface IBusinessService {
// 定义触发新增订单模块功能的方法
void buy();
}
新建service.impl包
包中创建BusinessServiceImpl类
代码如下
@Service
@Slf4j
public class BusinessServiceImpl implements IBusinessService {
@Override
public void buy() {
// 模拟购买业务
// 创建用于新增订单的DTO实体OrderAddDTO
OrderAddDTO orderAddDTO=new OrderAddDTO();
// 为orderAddDTO赋值
orderAddDTO.setUserId("UU100");
orderAddDTO.setCommodityCode("PC100");
orderAddDTO.setCount(5);
orderAddDTO.setMoney(500);
// 因为是模拟购买,现在还不能调用order模块,所以只是输出
log.info("新增订单的信息为:{}",orderAddDTO);
}
}
创建控制层controller
创建类BusinessController代码如下
@RestController
@RequestMapping("/base/business")
// knife4j控制器描述
@Api(tags = "新增订单业务触发")
public class BusinessController {
@Autowired
private IBusinessService businessService;
@PostMapping("/buy")
// localhost:20000/base/business/buy(必须是post请求,浏览器输入地址无效)
@ApiOperation("执行触发的方法")
public JsonResult buy(){
// 调用业务逻辑层的方法
businessService.buy();
return JsonResult.ok("购买完成!");
}
}
启动当前business项目
访问
http://localhost:20000/doc.html
点击测试,观察输出结果和控制台输出内容是否正常
将项目注册到Nacos
我们已经讲过,一个项目要想称为微服务项目体系的一部分
必须将当前项目的信息注册到Nacos
我们要添加一些配置,实现business模块启动时注册到Nacos的效果
首先business模块pom文件中添加依赖
<!-- 支持项目注册到Nacos注册中心的依赖 discovery发现(服务的发现) -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
我们在创建好的application-dev.yml中编写对nacos注册的配置信息
spring:
application:
# 为当前项目起名,这个名字会被Nacos记录并使用
name: nacos-business
cloud:
nacos:
discovery:
# 配置Nacos所在的位置,用于注册时提交信息
server-addr: localhost:8848
按照昨天学习的方式,启动一下nacos
启动之后,
重启business模块,如果启动也正常,就应该将当前项目的信息提交给Nacos
在Nacos的服务管理->服务列表中,能看到nacos-business的名称
Nacos心跳机制
常见面试题
心跳:周期性表示自己健康的机制
Nacos内部注册的服务都会有一个心跳机制
心跳机制的目的,是每个服务和Nacos保持沟通和交换信息的机制
默认情况下,服务启动后每隔5秒会向Nacos发送一个"心跳包",这个心跳包中包含了当前服务的基本信息
Nacos接收到这个心跳包,首先检查当前服务在不在注册列表中,如果不在按新服务的业务进行注册,如果在,表示当前这个服务是健康状态
如果一个服务连续3次心跳(默认15秒)没有和Nacos进行信息的交互,就会将当前服务标记为不健康的状态
如果一个服务连续6次心跳(默认30秒)没有和Nacos进行信息的交互,Nacos会将这个服务从注册列表中剔除
这些时间都是可以通过配置修改的
实例类型分类
实际上Nacos的服务类型还有分类
- 临时实例(默认)
- 持久化实例(永久实例)
默认每个服务都是临时实例
如果想标记一个服务为永久实例
cloud:
nacos:
discovery:
# ephemeral设置当前项目启动时注册到nacos的类型 true(默认):临时实例 false:永久实例
ephemeral: false
持久化实例启动时向nacos注册,nacos会对这个实例进行持久化处理
心跳包的规则和临时实例一致,只是不会将该服务从列表中剔除
一般情况下,我们创建的服务都是临时实例
只有项目的主干业务才会设置为永久实例
随笔
工作中可能遇到的含义类似的包名
controller/servlet:控制层
service/biz:业务层
mapper/dao/repository:持久层
entity/model/domain/bean/pojo:实体类包
map-underscore-to-camel-case: true # 映射支持驼峰命名法
user_id 自动映射java对应类中的userId属性
第三天
使用Idea启动Nacos
之前我们启动Nacos都是使用dos命令行
启动过程比较复杂,命令比较长
Idea实际上支持我们更加方便的启动Nacos
步骤1:
步骤2:
步骤3:
配置成功之后
Nacos就会出现在idea的启动选项中了
这种方法实际上只是为了简便大家启动nacos的操作,还是需要掌握命令行的启动方式的!
创建csmall-cart子项目
创建项目
创建csmall-cart子项目
父子相认
子项目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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.tedu</groupId>
<artifactId>csmall</artifactId>
<version>0.0.1-SNAPSHOT</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>cn.tedu</groupId>
<artifactId>csmall-cart</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>csmall-cart</name>
<description>Demo project for Spring Boot</description>
<dependencies>
<!--web实例-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--mybatis整合springboot-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<!--alibaba 数据源德鲁伊-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
</dependency>
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--all-common依赖-->
<dependency>
<groupId>cn.tedu</groupId>
<artifactId>csmall-commons</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<!--在线api文档-->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
</dependency>
<!-- Nacos注册依赖 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
</dependencies>
</project>
删除test文件夹
删除application.properties配置文件
创建application.yml文件,内容如下
server:
port: 20001
#公共配置
mybatis:
configuration:
# 禁用缓存
cache-enabled: false
# 配置映射驼峰命名法,数据库中user_name的字段,会映射在java的userName属性上
map-underscore-to-camel-case: true
# 将运行的sql语句输出到控制台
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
knife4j:
# 开启增强配置
enable: true
# 生产环境屏蔽,开启将禁止访问在线API文档
production: false
# Basic认证功能,即是否需要通过用户名、密码验证后才可以访问在线API文档
basic:
# 是否开启Basic认证
enable: false
# 用户名,如果开启Basic认证却未配置用户名与密码,默认是:admin/123321
username: root
# 密码
password: root
spring:
profiles:
active: dev
准备数据
推荐大家学习使用数据库的专用可视化工具来操作数据库
heidiSQL(安装MariaDB自带的)免费的
SqlYog Navicat是收费的
我们cart模块要操作数据库,首先保证要有数据
所以我们要创建需要的表和添加表中的数据
sql代码已经发送在csmall-finish项目的node文件夹中:csmall_db.sql
运行其中的sql代码即可
CREATE DATABASE IF NOT EXISTS `csmall_db` DEFAULT CHARACTER SET utf8mb4;
USE `csmall_db`;
DROP TABLE IF EXISTS `cart_tbl`;
CREATE TABLE `cart_tbl` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '购物车id',
`commodity_code` varchar(255) DEFAULT NULL COMMENT '商品编码',
`price` int DEFAULT '0' COMMENT '商品单价',
`count` int DEFAULT '0' COMMENT '购买数量',
`user_id` varchar(255) DEFAULT NULL COMMENT '用户id',
PRIMARY KEY (`id`),
UNIQUE KEY `commodity_code` (`commodity_code`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb3;
insert into `cart_tbl`(`id`,`commodity_code`,`price`,`count`,`user_id`) values
(1,'PU201',500,10,'UU100');
DROP TABLE IF EXISTS `order_tbl`;
CREATE TABLE `order_tbl` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '订单id',
`user_id` varchar(255) DEFAULT NULL COMMENT '用户id',
`commodity_code` varchar(255) DEFAULT NULL COMMENT '商品编码,也可以是商品id',
`count` int DEFAULT '0' COMMENT '购买这个商品的数量',
`money` int DEFAULT '0' COMMENT '订单金额',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=27 DEFAULT CHARSET=utf8mb3;
insert into `order_tbl`(`id`,`user_id`,`commodity_code`,`count`,`money`) values
(22,'UU100','PU201',10,200),
(23,'UU100','PU201',10,200),
(24,'UU100','PU201',10,200),
(25,'UU100','PU201',10,200);
DROP TABLE IF EXISTS `stock_tbl`;
CREATE TABLE `stock_tbl` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '商品id',
`commodity_code` varchar(255) DEFAULT NULL COMMENT '商品编码',
`count` int DEFAULT '0' COMMENT '商品库存',
PRIMARY KEY (`id`),
UNIQUE KEY `commodity_code` (`commodity_code`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb3;
insert into `stock_tbl`(`id`,`commodity_code`,`count`) values
(1,'PU201',990);
DROP TABLE IF EXISTS `undo_log`;
CREATE TABLE `undo_log` (
`id` bigint NOT NULL AUTO_INCREMENT,
`branch_id` bigint NOT NULL,
`xid` varchar(100) NOT NULL,
`context` varchar(128) NOT NULL,
`rollback_info` longblob NOT NULL,
`log_status` int NOT NULL,
`log_created` datetime NOT NULL,
`log_modified` datetime NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=68 DEFAULT CHARSET=utf8mb3;
配置yml文件
application-dev.yml
spring:
datasource:
url: jdbc:mysql://localhost:3306/csmall_db?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&allowMultiQueries=true
username: root
password: root
application:
# 为当前项目起名,这个名字会被Nacos记录并使用
name: nacos-cart
cloud:
nacos:
discovery:
# 配置Nacos所在的位置,用于注册时提交信息
server-addr: localhost:8848
ephemeral: false
Spring相关配置类
和business项目一样
我们需要创建config包,包中创建相关配置类
@Configuration // 所有配置Spring的配置类必须添加这个注解
@ComponentScan(basePackages = "cn.tedu.csmall.commons.exception")
public class CommonsConfiguration {
}
knife4j配置
@Configuration
@EnableSwagger2WebMvc
public class Knife4jConfiguration {
/**
* 【重要】指定Controller包路径
*/
private String basePackage = "cn.tedu.csmall.cart.controller";
/**
* 分组名称
*/
private String groupName = "base-cart";
/**
* 主机名
*/
private String host = "http://java.tedu.cn";
/**
* 标题
*/
private String title = "酷鲨商城项目案例在线API文档--基础cart-web实例";
/**
* 简介
*/
private String description = "构建基础cart-web项目,实现购物车管理";
// 略....
}
当前cart模块除了上述配置之外,还需要添加Mybatis扫描mapper包的配置
config包中再创建一个MyBatisConfiguration类,代码如下
@Configuration
// Mybatis框架扫描mapper包接口的注解
@MapperScan("cn.tedu.csmall.cart.mapper")
public class MybatisConfiguration {
}
我们尝试启动Nacos后
启动cart模块
观察nacos服务列表中是否包含当前服务名称
编写cart项目业务
cart模块能够正常启动,但是还没有任何业务
结合我们最终生成订单的业务
当前cart模块需要开发如下两个功能
1.新增购物车中商品
2.删除购物车中商品
开发持久层
创建mapper包
创建CartMapper接口,中编写两个方法
一个新增购物车,一个删除购物车
@Repository
public interface CartMapper {
// 新增购物车中商品的方法
@Insert("insert into cart_tbl(commodity_code,price,count,user_id) " +
"values(#{commodityCode},#{price},#{count},#{userId})")
void insertCart(Cart cart);
// 删除购物车
@Delete("delete from cart_tbl where user_id=#{userId} and " +
" commodity_code=#{commodityCode}")
void deleteCartByUserIdAndCommodityCode(@Param("userId") String userId,
@Param("commodityCode") String commodityCode);
}
开发业务逻辑层
创建service包
包中创建ICartService
接口中编写新增和删除购物车的两个业务逻辑层方法声明
public interface ICartService {
// 新增购物车商品的业务逻辑层方法
void cartAdd(CartAddDTO cartAddDTO);
// 删除购物车商品的业务逻辑层方法
void deleteUserCart(String userId,String commodityCode);
}
创建impl包
编写业务实现类
@Service
@Slf4j
public class CartServiceImpl implements ICartService {
@Autowired
private CartMapper cartMapper;
@Override
public void cartAdd(CartAddDTO cartAddDTO) {
// 当前方法参数是CartAddDTO类型,而mapper执行新增需要的类型是Cart
// 所以需要将现在cartAddDTO中,所有同名属性赋值给Cart类型对象才能实现mapper调用
// 先实例化一个Cart对象
Cart cart=new Cart();
// 利用BeanUtils工具类,将cartAddDTO同名属性赋值到cart
BeanUtils.copyProperties(cartAddDTO,cart);
// 调用mapper新增购物车商品的方法
cartMapper.insertCart(cart);
// 如果新增顺利,将新增的对象输出到日志
log.info("新增购物车商品成功:{}",cart);
}
@Override
public void deleteUserCart(String userId, String commodityCode) {
// 删除功能需要userId和commodityCode,直接使用参数调用即可
cartMapper.deleteCartByUserIdAndCommodityCode(userId,commodityCode);
// 一切正常,日志提示
log.info("购物车删除完成");
}
}
如果需要设置日志输出级别
dev的yml文件中添加
logging: level: cn.tedu.csmall.cart: debug
开发控制层
创建controller包
包中创建CartController类
代码如下
@RestController
@RequestMapping("/base/cart")
@Api(tags = "购物车管理")
public class CartController {
@Autowired
private ICartService cartService;
@PostMapping("/add")
@ApiOperation("新增购物车商品")
public JsonResult cartAdd(CartAddDTO cartAddDTO){
cartService.cartAdd(cartAddDTO);
return JsonResult.ok("新增购物车商品完成!");
}
@PostMapping("/delete")
@ApiOperation("删除购物车商品")
@ApiImplicitParams({
@ApiImplicitParam(value = "用户ID",name="userId",example = "UU100"),
@ApiImplicitParam(value = "商品编号",name="commodityCode",example = "PC100")
})
public JsonResult deleteUserCart(String userId,String commodityCode){
cartService.deleteUserCart(userId,commodityCode);
return JsonResult.ok("删除购物车商品完成!");
}
}
重启Cart模块
测试localhost:20001/doc.html
发送新增购物车的请求测试
然后测试删除购物车的功能
创建Order模块
创建项目
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.tedu</groupId>
<artifactId>csmall</artifactId>
<version>0.0.1-SNAPSHOT</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>cn.tedu</groupId>
<artifactId>csmall-order</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>csmall-order</name>
<description>Demo project for Spring Boot</description>
<dependencies>
<!--web实例-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--mybatis整合springboot-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<!-- alibaba 数据源德鲁伊 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
</dependency>
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--all-common依赖-->
<dependency>
<groupId>cn.tedu</groupId>
<artifactId>csmall-commons</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<!--在线api文档-->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
</dependency>
<!-- Nacos注册依赖 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
</dependencies>
</project>
删除test文件夹
删除application.properties文件
可以将cart模块的application.yml文件和application-dev.yml复制过来
但是要修改需要变化的属性
application.yml端口号修改
server:
port: 20002
application-dev.yml修改项目名称
application:
name: nacos-order
config配置
直接复制cart模块的config包到当前order模块即可
修改内容如下
knife4j配置主要修改:
/**
* 【重要】指定Controller包路径
*/
private String basePackage = "cn.tedu.csmall.order.controller";
/**
* 分组名称
*/
private String groupName = "base-order";
/**
* 主机名
*/
private String host = "http://java.tedu.cn";
/**
* 标题
*/
private String title = "酷鲨商城项目案例在线API文档--基础order-web实例";
/**
* 简介
*/
private String description = "构建基础order-web项目,实现订单管理";
Mybatis配置修改如下
@Configuration
// Mybatis框架扫描mapper包接口的注解
@MapperScan("cn.tedu.csmall.order.mapper")
public class MybatisConfiguration {
}
编写新增订单功能
开发持久层
创建mapper包
创建OrderMapper接口
添加新增订单方法
@Repository
public interface OrderMapper {
// 新增订单的方法
@Insert("insert into order_tbl(user_id,commodity_code,count,money) " +
"values(#{userId},#{commodityCode},#{count},#{money})")
void insertOrder(Order order);
}
开发业务逻辑层
创建service包
包中创建IOrderService
public interface IOrderService {
// 声明新增订单的方法
void orderAdd(OrderAddDTO orderAddDTO);
}
创建service.impl包
包中创建OrderServiceImpl
@Service
@Slf4j
public class OrderServiceImpl implements IOrderService {
@Autowired
private OrderMapper orderMapper;
@Override
public void orderAdd(OrderAddDTO orderAddDTO) {
// 1.减少订单中商品的库存数(要调用stock模块的方法)
// 2.删除订单中选中的购物车的商品(要调用cart模块的方法)
// 3.执行将orderAddDTO中的信息新增到订单表中的功能
// 实例化一个Order对象
Order order=new Order();
BeanUtils.copyProperties(orderAddDTO,order);
// 执行新增
orderMapper.insertOrder(order);
log.info("新增的订单信息为{}",order);
}
}
开发控制层
创建controller包
创建OrderController类
@RestController
@RequestMapping("/base/order")
@Api(tags = "订单管理")
public class OrderController {
@Autowired
private IOrderService orderService;
@ApiOperation("新增订单")
@PostMapping("/add")
public JsonResult orderAdd(OrderAddDTO orderAddDTO){
// 调用业务逻辑层
orderService.orderAdd(orderAddDTO);
return JsonResult.ok("新增订单完成!");
}
}
启动order项目
检查nacos注册状态和knife4j测试新增订单效果
先启动nacos再启动order
创建stock模块
项目搭建
创建csmall-stock
修改父项目pom文件,父子相认
子项目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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.tedu</groupId>
<artifactId>csmall</artifactId>
<version>0.0.1-SNAPSHOT</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>cn.tedu</groupId>
<artifactId>csmall-stock</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>csmall-stock</name>
<description>Demo project for Spring Boot</description>
<dependencies>
<!--web实例-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--mybatis整合springboot-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<!-- alibaba 数据源德鲁伊 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
</dependency>
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--all-common依赖-->
<dependency>
<groupId>cn.tedu</groupId>
<artifactId>csmall-commons</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<!--在线api文档-->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
</dependency>
<!-- Nacos注册依赖 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
</dependencies>
</project>
删除test测试文件夹
删除application.properties文件
复制order\cart模块的application.yml和application-dev.yml文件
修改端口号和项目名称
config配置
从order\cart模块复制config包
修改MybatisConfiguration\Knife4jConfiguration对应的包名
编写减少库存功能
开发持久层
UPDATE stock_tbl SET COUNT=COUNT-10
WHERE commodity_code='PC100' AND COUNT>=10
mapper包下StockMapper
@Repository
public interface StockMapper {
// 修改(减少)指定商品库存数的方法
@Update("UPDATE stock_tbl SET COUNT=COUNT-#{reduceCount} " +
" WHERE commodity_code=#{commodityCode} AND COUNT>=#{reduceCount}")
void updateStockByCommodityCode(@Param("commodityCode") String commodityCode,
@Param("reduceCount") Integer reduceCount);
}
开发业务层
在service包下创建IStockService
public interface IStockService {
// 减少库存数的业务逻辑层方法
void reduceCommodityCount(StockReduceCountDTO stockReduceCountDTO);
}
service.impl包下创建StockServiceImpl
@Service
@Slf4j
public class StockServiceImpl implements IStockService {
@Autowired
private StockMapper stockMapper;
@Override
public void reduceCommodityCount(StockReduceCountDTO stockReduceCountDTO) {
// 调用stockMapper减少库存数的方法
// 参数1:商品编号
// 参数2:减少的库存数
stockMapper.updateStockByCommodityCode(
stockReduceCountDTO.getCommodityCode(),
stockReduceCountDTO.getReduceCount());
log.info("库存减少已执行");
}
}
开发控制层
controller包
StockController类
@RestController
@RequestMapping("/base/stock")
@Api(tags = "库存管理")
public class StockController {
@Autowired
private IStockService stockService;
@PostMapping("/reduce/count")
@ApiOperation("减少商品库存数")
public JsonResult reduceCommodityCount(StockReduceCountDTO stockReduceCountDTO){
// 调用业务逻辑层
stockService.reduceCommodityCount(stockReduceCountDTO);
return JsonResult.ok("库存减少已执行!");
}
}
启动服务测试成功即可
Dubbo概述
什么是RPC
RPC是Remote Procedure Call的缩写 翻译为:远程过程调用
目标是为了实现两台(多台)计算机\服务器,相互调用方法\通信的解决方案
RPC只是实现远程调用的一套标准
该标准主要规定了两部分内容
1.通信协议
2.序列化协议
为了方便大家理解RPC,下面的图片帮助理解
上面图是老婆和老公在家的时,老婆让老公洗碗的调用流程
这个流程可以理解为项目内的功能的调用,类似面向对象编程实例化对象,调用方法的过程
但是这个调用关系如果是远程的,意思是老婆和老公现在是两个不同的项目
我们看到上图中,远程调用必须借助一个通信设备,图片中是手机
通信协议
通信协议指的就是远程调用的通信方式
实际上这个通知的方式可以有多种
例如:写信,飞鸽传书,闪送等等
在程序中,通信方式也有多种
序列化协议
序列化协议指通信内容的格式,双方都要理解这个格式
上面的图片中,老婆给老公发信息,一定是双方都能理解的信息
发送信息是序列化过程,接收信息需要反序列化
程序中,序列化的方式也是多种的
什么是Dubbo
上面对RPC有基本认识之后,再学习Dubbo就简单了
Dubbo是一套RPC框架。既然是框架,我们可以在框架结构高度,定义Dubbo中使用的通信协议,使用的序列化框架技术,而数据格式由Dubbo定义,我们负责配置之后直接通过客户端调用服务端代码。
可以说Dubbo就是RPC概念的实现
Dubbo是SpringCloudAlibaba提供的框架
能够实现微服务相互调用的功能!
Dubbo的发展历程
我们学习的Dubbo指的都是2.7之后的版本
是能够和SpringCloudAlibaba配合使用的
Dubbo历程
2012年底dubbo停止更新后到2017年dubbo继续更新之前
2015SpringCloud开始兴起,当时没有阿里的框架
国内公司要从SpringCloud和Dubbo中抉择使用哪个微服务方案
在2012年dubbo停止更新后国内的当当网在dubbo的基础上开发了dubboX框架,并进行维护
2019年后,SpringCloud和Dubbo才能共同使用
Dubbo对协议的支持
RPC框架分通信协议和序列化协议
Dubbo框架支持多种通信协议和序列化协议,可以通过配置文件进行修改
Dubbo支持的通信协议
- dubbo协议(默认)
- rmi协议
- hessian协议
- http协议
- webservice
- …
支持的序列化协议
- hessian2(默认)
- java序列化
- compactedjava
- nativejava
- fastjson
- dubbo
- fst
- kryo
Dubbo默认情况下,支持的协议有如下特征
- 采用NIO单一长链接
- 优秀的并发性能,但是处理大型文件的能力差
Dubbo方便支持高并发和高性能
Dubbo服务的注册与发现
在Dubbo的调用过程中,必须包含注册中心的支持
注册中心推荐阿里自己的Nacos,兼容性好,能够发挥最大性能
但是Dubbo也支持其它软件作为注册中心(例如Redis,zookeeper等)
服务发现,即消费端自动发现服务地址列表的能力,是微服务框架需要具备的关键能力,借助于自动化的服务发现,微服务之间可以在无需感知对端部署位置与 IP 地址的情况下实现通信。
上面RPC的示例中,老婆就是服务的消费端,她能发现老公具备的服务
如果老婆调用了老公的服务,就是完成了Dubbo调用
consumer服务的消费者,指服务的调用者(使用者)也就是老婆的位置
provider服务的提供者,指服务的拥有者(生产者)也就是老公的位置
在Dubbo中,远程调用依据是服务的提供者在Nacos中注册的服务名称
一个服务名称,可能有多个运行的实例,任何一个空闲的实例都可以提供服务
常见面试题:Dubbo的注册发现流程
1.首先服务的提供者启动服务时,将自己的具备的服务注册到注册中心,其中包括当前提供者的ip地址和端口号等信息,Dubbo会同时注册该项目提供的远程调用的方法
2.消费者(使用者)启动项目,也注册到注册中心,同时从注册中心中获得当前项目具备的所有服务列表
3.当注册中心中有新的服务出现时,会通知已经订阅发现的消费者,消费者会更新所有服务列表
4.RPC调用,消费者需要调用远程方法时,根据注册中心服务列表的信息,只需服务名称,不需要ip地址和端口号等信息,就可以利用Dubbo调用远程方法了
我们当前csmall项目的远程调用关系如下
第四天
Dubbo实现微服务调用
确定调用关系
![在这里插入图片描述](https://img-blog.csdnimg.cn/0bdfcf3890384278a9d1aea0f8f13118.png#pic_center
-
order模块调用stock模块的减少库存的功能
-
order模块调用cart模块的删除购物车的功能
-
business模块调用order新增订单的功能
要想实现Dubbo调用
必须按照Dubbo规定的配置和行业标准的结构来实现
Dubbo调用的好处是直接将要消费的目标(例如order模块中消费stock的方法)编写在当前消费者的业务逻辑层中,无需编写新的代码结构,开发流程不会因为Dubbo而变化
修改stock模块
创建csmall-stock-service项目
因为当前stock模块减少库存数业务是典型的生成者方法,需要被别的模块调用
那么其它模块就必须添加当前库存数减少的业务接口支持
为了减少添加这个给当前项目带来的负担
业界通用做法,是将生产者项目拆分为两个,
其中一个项目只有业务逻辑层接口
另一个项目包含正常的配置和所有业务代码
当消费者需要时只需要添加包含业务逻辑层接口的项目的依赖即可
创建csmall-stock-service项目
删除test\删除resources\删除SpringBoot启动类
csmall-stock升格为父项目,所以也要修改它的pom文件
<description>Demo project for Spring Boot</description>
<packaging>pom</packaging>
<modules>
<module>csmall-stock-service</module>
</modules>
csmall-stock-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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.tedu</groupId>
<artifactId>csmall-stock</artifactId>
<version>0.0.1-SNAPSHOT</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>cn.tedu</groupId>
<artifactId>csmall-stock-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>csmall-stock-service</name>
<description>Demo project for Spring Boot</description>
<dependencies>
<dependency>
<groupId>cn.tedu</groupId>
<artifactId>csmall-commons</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
然后将原有的业务逻辑层接口IStockService复制到这个项目中即可
public interface IStockService {
// 根据商品编号减少库存的方法
void reduceCommodityCount(StockReduceCountDTO stockReduceCountDTO);
}
创建csmall-stock-webapi项目
webapi项目包含stock项目原有的所有配置和业务代码
创建好项目之后删除test文件夹、删除application.properties文件
然后父子相认
最终csmall-stock项目的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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.tedu</groupId>
<artifactId>csmall</artifactId>
<version>0.0.1-SNAPSHOT</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>cn.tedu</groupId>
<artifactId>csmall-stock</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>csmall-stock</name>
<description>Demo project for Spring Boot</description>
<packaging>pom</packaging>
<modules>
<module>csmall-stock-service</module>
<module>csmall-stock-webapi</module>
</modules>
</project>
csmall-stock-webapi项目的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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.tedu</groupId>
<artifactId>csmall-stock</artifactId>
<version>0.0.1-SNAPSHOT</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>cn.tedu</groupId>
<artifactId>csmall-stock-webapi</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>csmall-stock-webapi</name>
<description>Demo project for Spring Boot</description>
<dependencies>
<!--web实例-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--mybatis整合springboot-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<!-- alibaba 数据源德鲁伊 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
</dependency>
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--all-common依赖-->
<dependency>
<groupId>cn.tedu</groupId>
<artifactId>csmall-commons</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<!--在线api文档-->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
</dependency>
<!-- Nacos注册依赖 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- Dubbo的依赖 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-dubbo</artifactId>
</dependency>
<!-- 为了实现业务逻辑层接口,需要添加业务逻辑层接口项目csmall-stock-service的依赖 -->
<dependency>
<groupId>cn.tedu</groupId>
<artifactId>csmall-stock-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
上面的pom文件不只是将原有的stock的pom文件依赖复制,而且添加了Dubbo和业务逻辑层接口的依赖
将csmall-stock项目的application.yml和application-dev.yml复制到csmall-stock-webapi项目的resources文件夹下
yml文件其它的不动,但是在dev.yml文件中要添加dubbo的配置信息
spring:
datasource:
url: jdbc:mysql://localhost:3306/csmall_db?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&allowMultiQueries=true
username: root
password: root
application:
# 为当前项目起名,这个名字会被Nacos记录并使用
name: nacos-stock
cloud:
nacos:
discovery:
# 配置Nacos所在的位置,用于注册时提交信息
server-addr: localhost:8848
dubbo:
protocol:
# port设置-1 表示当前Dubbo端口号是自动动态生成
# 会自动从20880开始寻找可用的端口号,如果被占用,就递增寻找下一个,直到找到可用为止
port: -1
# 设置连接的名称,一般固定设置为dubbo
name: dubbo
registry:
# 声明当前Dubbo注册到的注册中心类型和位置
address: nacos://localhost:8848
consumer:
# 当本项目启动时,是否检查当前项目需要的所有Dubbo服务是否是可用状态
# 我们设置它的值为false,表示项目启动时不检查,所需的服务是否可用
check: false
开始复制代码
我们先将csmall-stock模块中的IStockService接口删除
然后直接复制config\mapper\controller\service.impl四个包
粘贴到webapi项目中
业务逻辑层实现类需要重新导入Mapper的包来实现正确编译
注意impl的包名也要修改->service.impl
然后就可以删除原stock模块的src文件夹了
下面就可以调整webapi项目的中配置路径了
Knife4jConfiguration:
/**
* 【重要】指定Controller包路径
*/
private String basePackage = "cn.tedu.csmall.stock.webapi.controller";
MyBatisConfiguration
@Configuration
// MyBatis框架扫描mapper接口包的注解
@MapperScan("cn.tedu.csmall.stock.webapi.mapper")
public class MyBatisConfiguration {
}
下面就可以配置实现Dubbo方法提供的步骤了
将业务逻辑层实现类方法声明为Dubbo可调用的方法
当前stock模块是单纯的生产者
// @DubboService注解标记的业务逻辑层实现类,其中的所有方法会注册到Nacos
// 其它服务在"订阅"时,就会"发现"当前项目提供的服务(业务逻辑层方法),以便后续在需要时调用
@DubboService
@Service
@Slf4j
public class StockServiceImpl implements IStockService {
//内容略....
}
如果当前项目是服务的提供者(生产者)
还需要在SpringBoot启动类上添加@EnableDubbo的注解,才能真正让Dubbo功能生效
@SpringBootApplication
// 如果当前项目是Dubbo的生产者,必须在当前项目的SpringBoot启动类上添加下面注解
// 才能正常正确的将当前项目提供的服务注册到Nacos
@EnableDubbo
public class CsmallStockWebapiApplication {
public static void main(String[] args) {
SpringApplication.run(CsmallStockWebapiApplication.class, args);
}
}
先启动nacos在启动
再启动stockWebapi项目
作业:
按上面stock模块的操作
改写cart模块的结构,以实现Dubbo生产者功能
修改cart模块
操作步骤和stock完全一致,参考stock模块即可
修改order模块支持Dubbo
因为order模块在Dubbo的调用关系中
既是生产者又是消费者
它消费cart和stock的服务
同时又为business模块提供服务
重构的过程和stock\cart有很多相似,但是也要注意不同
创建csmall-order-service项目
这个项目创建的过程和stock\cart模块service项目的步骤和注意事项完全一致
创建csmall-order-webapi项目
创建项目后父子相认正常
子项目的pom文件依赖需要添加下面内容
<!-- Dubbo 依赖 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-dubbo</artifactId>
</dependency>
<!-- 相关的业务逻辑层接口依赖 -->
<dependency>
<groupId>cn.tedu</groupId>
<artifactId>csmall-order-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<!-- 作为消费者,order模块需要调用cart和stock模块的业务逻辑层接口 -->
<dependency>
<groupId>cn.tedu</groupId>
<artifactId>csmall-cart-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>cn.tedu</groupId>
<artifactId>csmall-stock-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
yml文件配置也和cart\stock模块一致
代码也都正常从csmall-order复制到csmall-order-webapi中
删除csmall-order的src目录
config包中的配置,修改为正确包名
在OrderServiceImpl业务逻辑层实现类中
添加生产者的注解,同时利用Dubbo消费stock和cart模块的方法
Reference:引用
// Order模块既是生产者也是消费者,所以作为生产者,还是要编写@DubboService
@DubboService
@Service
@Slf4j
public class OrderServiceImpl implements IOrderService {
@Autowired
private OrderMapper orderMapper;
// 添加@DubboReference注解,表示当前业务逻辑层代码,要消费其它模块的服务
// 可以编写当前Nacos中注册的其它模块的业务逻辑层接口
// 因为在Nacos中注册的是接口的实现类,可以实现自动装配实现类的效果
// 先添加stock模块的业务对象,有些公司要求dubbo引用的对象使用dubbo开头
@DubboReference
private IStockService dubboStockService;
@DubboReference
private ICartService dubboCartService;
@Override
public void orderAdd(OrderAddDTO orderAddDTO) {
// 1.减少订单中商品的库存数(要调用stock模块的方法)
// 实例化减少订单业务的DTO对象
StockReduceCountDTO countDTO=new StockReduceCountDTO();
countDTO.setCommodityCode(orderAddDTO.getCommodityCode());
countDTO.setReduceCount(orderAddDTO.getCount());
// dubbo调用stock模块减少库存数的方法
dubboStockService.reduceCommodityCount(countDTO);
// 2.删除订单中选中的购物车的商品(要调用cart模块的方法)
dubboCartService.deleteUserCart(orderAddDTO.getUserId(),
orderAddDTO.getCommodityCode());
// 3.执行将orderAddDTO中的信息新增到订单表中的功能
// 实例化一个Order对象
Order order=new Order();
BeanUtils.copyProperties(orderAddDTO,order);
// 执行新增
orderMapper.insertOrder(order);
log.info("新增的订单信息为{}",order);
}
}
因为order模块也是生产者@EnableDubbo注解仍然要写
@SpringBootApplication
@EnableDubbo
public class CsmallOrderWebapiApplication {
public static void main(String[] args) {
SpringApplication.run(CsmallOrderWebapiApplication.class, args);
}
}
我们可以测试这个Dubbo的功能
首先保证Nacos启动
我们项目的启动顺序,尽量保证生产者先启动
启动消费者
stock\cart最后启动order
访问
http://localhost:20002/doc.html运行测试
注意运行前,数据库的数据状态和运行后的比较一下
负载均衡
什么是负载均衡
在实际项目中,一个服务基本都是集群模式的,也就是多个功能相同的项目在运行,这样才能承受更高的并发
这时一个请求到这个服务,就需要确定访问哪一个服务器
Dubbo框架内部支持负载均衡算法,能够尽可能的让请求在相对空闲的服务器上运行
在不同的项目中,可能选用不同的负载均衡策略,以达到最好效果
Loadbalance:就是负载均衡的意思
Dubbo内置负载均衡策略算法
Dubbo内置4种负载均衡算法
- random loadbalance:随机分配策略(默认)
- round Robin Loadbalance:权重平均分配
- leastactive Loadbalance:活跃度自动感知分配
- consistanthash Loadbalance:一致性hash算法分配
实际运行过程中,每个服务器性能不同
在负载均衡时,都会有性能权重,这些策略算法都考虑权重问题
随机分配策略
假设我们当前3台服务器,经过测试它们的性能权重比值为5:3:1
下面可以生成一个权重模型
随机生成随机数
在哪个范围内让哪个服务器运行
优点:
算法简单,效率高,长时间运行下,任务分配比例准确
缺点:
偶然性高,如果连续的几个随机请求发送到性能弱的服务器,会导致异常甚至宕机
权重平滑分配
如果几个服务器权重一致,那么就是依次运行
但是服务器的性能权重一致的可能性很小
所以我们需要权重平滑分配
一个优秀的权重分配算法,应该是让每个服务器都有机会运行的
如果一个集群服务器性能比为5:3:1
1>A 2>A 3>A 4>A 5>A 6>B 7>B 8>B 9>C
10>A
上面的安排中,连续请求一个服务器肯定是不好的,我们希望所有的服务器都能够穿插在一起运行
Dubbo2.7之后更新了这个算法使用"平滑加权算法"优化权重平均分配策略
优点:
能够尽可能的在权重要求的情况下,实现请求的穿插运行(交替运行),不会发生随机策略中的偶发情况
缺点
服务器较多时,可能需要减权和复权的计算,需要消耗系统资源
活跃度自动感知
记录每个服务器处理一次请求的时间
按照时间比例来分配任务数,运行一次需要时间多的分配的请求数较少
一致性Hash算法
根据请求的参数进行hash运算
以后每次相同参数的请求都会访问固定服务器
因为根据参数选择服务器,不能平均分配到每台服务器上
使用的也不多
修改business模块
business模块是我们设计的新增订单业务的触发者,是起点
它是单纯的消费者
我们不需要像生产者一样去创建两个子项目
直接在现有项目上进行修改即可
pom文件直接添加dubbo和order业务逻辑接口的依赖
<!-- Dubbo依赖 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-dubbo</artifactId>
</dependency>
<!-- order模块业务逻辑层接口 -->
<dependency>
<groupId>cn.tedu</groupId>
<artifactId>csmall-order-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
application-dev.yml添加Dubbo的配置
spring:
application:
# 为当前项目起名,这个名字会被Nacos记录并使用
name: nacos-business
cloud:
nacos:
discovery:
# 配置Nacos所在的位置,用于注册时提交信息
server-addr: localhost:8848
dubbo:
protocol:
port: -1
name: dubbo
registry:
address: nacos://localhost:8848
consumer:
check: false
我们要在当前busindess模块的业务逻辑层实现类中
实现Dubbo调用order模块的生成订单方法
BusinessServiceImpl类中
@Service
@Slf4j
public class BusinessServiceImpl implements IBusinessService {
// Dubbo调用order模块的新增订单的方法
// 单纯的消费者,不需要在类上添加@DubboService
// ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
@DubboReference
private IOrderService dubboOrderService;
@Override
public void buy() {
// 模拟购买业务
// 创建用于新增订单的DTO实体OrderAddDTO
OrderAddDTO orderAddDTO=new OrderAddDTO();
// 为orderAddDTO赋值
orderAddDTO.setUserId("UU100");
orderAddDTO.setCommodityCode("PC100");
orderAddDTO.setCount(10);
orderAddDTO.setMoney(666);
// 因为是模拟购买,现在还不能调用order模块,所以只是输出
log.info("新增订单的信息为:{}",orderAddDTO);
// dubbo调用业务
// ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
dubboOrderService.orderAdd(orderAddDTO);
}
}
Springboot启动类不必要写@EnableDubbo因为business是单纯的消费者
启动business项目(前提是cart\stock\order正在运行)
http://localhost:20000/doc.html运行测试
Dubbo生产者消费者配置小结
Dubbo生产者消费者相同的配置
pom文件添加dubbo依赖,yml文件配置dubbo信息
生产者
-
要有service接口项目
-
提供服务的业务逻辑层实现类要添加@DubboService注解
-
SpringBoot启动类要添加@EnableDubbo注解
消费者
- pom文件添加消费模块的service依赖
- 业务逻辑层远程调用前,模块使用@DubboReference注解获取业务逻辑层实现类对象
Seata概述
下载Seata
https://github.com/seata/seata/releases
https://github.com/seata/seata/releases/download/v1.4.2/seata-server-1.4.2.zip
最好能从项目经理老师处获取
什么是Seata
Seata 是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务
也是Spring Cloud Alibaba提供的组件
Seata官方文档
https://seata.io/zh-cn/
更多信息可以通过官方文档获取
为什么需要Seata
我们之前学习了单体项目中的事务
使用的技术叫Spring声明式事务
能够保证一个业务中所有对数据库的操作要么都成功,要么都失败,来保证数据库的数据完整性
但是在微服务的项目中,业务逻辑层涉及远程调用,当前模块发生异常,无法操作远程服务器回滚
这时要想让远程调用也支持事务功能,就需要使用分布式事务组件Seata
事务的4个特性:ACID特性
- 原子性
- 一致性
- 隔离性
- 永久性
Seata保证微服务远程调用业务的原子性
Seata将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。
Seata的运行原理(AT模式)
观察下面事务模型
上面结构是比较典型的远程调用结构
如果account操作数据库失败需要让order模块和storage模块撤销(回滚)操作
声明式事务不能完成这个操作
需要使用Seata来解决
Seata构成部分包含
- 事务协调器TC
- 事务管理器TM
- 资源管理器RM
我们项目使用AT(自动)模式完成分布式事务的解决
AT模式运行过程
1.事务的发起方™会向事务协调器(TC)申请一个全局事务id,并保存
2.Seata会管理事务中所有相关的参与方的数据源,将数据操作之前和之后的镜像都保存在undo_log表中,这个表是seata组件规定的表,没有它就不能实现效果,依靠它来实现提交(commit)或回滚(roll back)的操作
3.事务的发起方™会连同全局id一起通过远程调用运行资源管理器(RM)中的方法
4.RM接收到全局id,去运行指定方法,并将运行结果的状态发送给TC
5.如果所有分支运行都正常,事务管理器™会通过事务协调器通知所有模块执行数据库操作,真正影响数据库内容,反之如果有任何一个分支模块运行异常,都会通知TC,再由TC通知所有分支将数据库操作回滚,恢复成运行之前的样子
Seata的启动
seata也是java开发的,启动方式和nacos很像
只是启动命令不同
它要求配置环境变量中Path属性值有java的bin目录路径
解压后路径不要用中文,不要用空格
也是解压之后的bin目录下
在路径上输入cmd进入dos窗口
D:\tools\seata\seata-server-1.4.2\bin>seata-server.bat -h 127.0.0.1 -m file
输入后,最后出现8091端口的提示即可!
第五天
续Seata 概述
其它模式简介
上次课我们讲解了Seata软件AT模式的运行流程
AT模式的运行有一个非常明显的前提条件,这个条件不满足,就无法使用AT模式
这个条件就是事务分支都必须是操作关系型数据库(mysql\MariaDB\Oracle)
因为关系型数据库才支持提交和回滚,其它非关系型数据库都是直接影响数据(例如Redis)
所以如果我们在业务过程中有一个节点操作的是Redis或其它非关系型数据库时,就无法使用AT模式
除了AT模式之外还有TCC、SAGA 和 XA 事务模式
TCC模式
简单来说,TCC模式就是自己编写代码完成事务的提交和回滚
在TCC模式下,我们需要为参与事务的业务逻辑编写一组共3个方法
(prepare\commit\rollback)
prepare:准备
commit:提交
rollback:回滚
- prepare方法是每个模块都会运行的方法
- 当所有模块的prepare方法运行都正常时,运行commit
- 当任意模块运行的prepare方法有异常时,运行rollback
这样的话所有提交或回滚代码都由自己编写
优点:虽然代码是自己写的,但是事务整体提交或回滚的机制仍然可用(仍然由TC来调度)
缺点:每个业务都要编写3个方法来对应,代码冗余,而且业务入侵量大
SAGA模式
SAGA模式的思想是对应每个业务逻辑层编写一个新的类,可以设置指定的业务逻辑层方法发生异常时,运行当新编写的类中的代码
这样编写代码不影响已经编写好的业务逻辑代码
一般用于修改已经编写完成的老代码
缺点是每个事务分支都要编写一个类来回滚业务,
会造成类的数量较多,开发量比较大
XA模式
支持XA协议的数据库分布式事务,使用比较少
使用Seata
配置Seata
cart\stock\order三个模块时需要Seata支持进行事务管理的模块
这三个模块都需要添加下面pom依赖和配置
<!-- Seata和SpringBoot整合依赖 -->
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
</dependency>
<!-- Seata 完成分布式事务的两个相关依赖(Seata会自动使用其中的资源) -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
下面修改cart\stock\order模块的application-dev.yml
代码如下
seata:
tx-service-group: csmall_group # 定义分组名称,为了与其它项目区分
service:
vgroup-mapping:
csmall_group: default # csmall_group分组使用Seata的默认配置完成事务
grouplist:
default: localhost:8091 # 配置seata的地址和端口号(8091是默认端口号)
注意同一个事务必须在同一个tx-service-group中
同时指定相同的seata地址和端口
business模块的配置
business模块作为当前分布式事务模型的触发者
它应该是事务的起点,但是它不连接数据库,所以配置稍有不同
pom文件seata依赖仍然需要,但是只需要seata依赖
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
</dependency>
application-dev.yml是一样的
seata:
tx-service-group: csmall_group # 定义分组名称,为了与其它项目区分
service:
vgroup-mapping:
csmall_group: default # csmall_group分组使用Seata的默认配置完成事务
grouplist:
default: localhost:8091 # 配置seata的地址和端口号(8091是默认端口号)
添加完必要的配置之后
要想启动Seata非常简单,只要在起点业务的业务逻辑方法上添加专用的注解即可
添加这个注解的模块就是模型中的TM
他调用的所有远程模块都是RM
@Service
@Slf4j
public class BusinessServiceImpl implements IBusinessService {
// Dubbo调用order模块的新增订单的方法
// 单纯的消费者,不需要在类上添加@DubboService
@DubboReference
private IOrderService dubboOrderService;
// Global全局,Transactional事务
// 一旦编写@GlobalTransactional标记这个方法
// 就相当于设置了分布式事务的起点,当前模块就是分布式事务模型中的TM
// 最终效果是由当前方法调用的所有远程服务中对数据库的操作要么都执行,要么都不执行
@GlobalTransactional
@Override
public void buy() {
// 模拟购买业务
// 代码略...
}
}
启动seata
上次课已经将启动的关键步骤寄了笔记
找到seata的解压路径的bin路径下
D:\tools\seata\seata-server-1.4.2\bin
地址栏输入cmd
输入命令如下
D:\tools\seata\seata-server-1.4.2\bin>seata-server.bat -h 127.0.0.1 -m file
然后就可以将服务启动了
先启动Nacos,在seata不关闭的前提下
再启动所有4个服务cart\stock\order\business
利用knife4j访问business模块,否则无法触发事务效果,business模块是seata事务的起点
在windows系统中运行seata可能出现不稳定的情况,重启seata即可解决
根据是否发生随机异常,来判断seata是否有效
OrderServiceImpl在新增订单方法前添加随机发送异常的方法
// 1.减少订单中商品的库存数(要调用stock模块的方法)
// 实例化减少订单业务的DTO对象
StockReduceCountDTO countDTO=new StockReduceCountDTO();
countDTO.setCommodityCode(orderAddDTO.getCommodityCode());
countDTO.setReduceCount(orderAddDTO.getCount());
// dubbo调用stock模块减少库存数的方法
dubboStockService.reduceCommodityCount(countDTO);
// 2.删除订单中选中的购物车的商品(要调用cart模块的方法)
dubboCartService.deleteUserCart(orderAddDTO.getUserId(),
orderAddDTO.getCommodityCode());
// 3.执行将orderAddDTO中的信息新增到订单表中的功能
// 实例化一个Order对象
Order order=new Order();
BeanUtils.copyProperties(orderAddDTO,order);
// ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
// 随机发生异常,触发Seata的回顾测试
if(Math.random()<0.5) {
throw new CoolSharkServiceException(ResponseCode.BAD_REQUEST, "测试异常");
}
// 执行新增
orderMapper.insertOrder(order);
log.info("新增的订单信息为{}",order);
如果seata启动时发送内存不足的错误,可以参考下面的文章解决
https://blog.csdn.net/he_lei/article/details/116229467
Sentinel
官网地址
https://sentinelguard.io/zh-cn/
下载地址
https://github.com/alibaba/Sentinel/releases
什么是Sentinel
Sentinel也是Spring Cloud Alibaba的组件
Sentinel英文翻译"哨兵\门卫"
随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。
为什么需要Sentinel
为了保证服务器运行的稳定性,在请求数到达设计最高值时,将过剩的请求限流,保证在设计的请求数内的请求能够稳定完成处理
-
丰富的应用场景
双11,秒杀,12306抢火车票
-
完备的实时状态监控
可以支持显示当前项目各个服务的运行和压力状态,分析出每台服务器处理的秒级别的数据
-
广泛的开源生态
很多技术可以和Sentinel进行整合,SpringCloud,Dubbo,而且依赖少配置简单
-
完善的SPI扩展
Sentinel支持程序设置各种自定义的规则
基本配置
我们的限流针对的是控制器方法
我们找一个简单的模块来测试和观察限流效果
在csmall-stock-webapi模块中
添加sentinel的依赖
<!-- Sentinel 依赖 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
application-dev.yml文件添加配置
cloud:
sentinel:
transport:
dashboard: localhost:8080 # 配置sentinel仪表台的位置
# 执行限流的端口号,每个项目唯一(别的项目例如cart模块,再设置的话就不能用8721了)
port: 8721
nacos:
discovery:
# 配置Nacos所在的位置,用于注册时提交信息
server-addr: localhost:8848
Sentinel启动
windows同学直接双击start-sentinel.bat文件
mac同学使用下面命令执行jar包
java -jar sentinel-dashboard-1.8.2.jar
启动之后
打开浏览器http://localhost:8080/
会看到下面的界面
用户名和密码都是
sentinel
刚开始什么都没有,是空界面
后面我们有控制器的配置就会出现信息了
限流方法
我们以stock模块为例
演示限流的效果
StockController在减少库存的方法上添加限流的注解
@PostMapping("/reduce/count")
@ApiOperation("减少商品库存数")
// @SentinelResource注解标记的控制层方法,会在运行时被Sentinel进行管理
// 在这个控制层方法第一次运行后,可以在Sentinel仪表台界面中设置限流规则
// "减少库存的方法"设置了当前方法在仪表台显示的名称
@SentinelResource("减少库存的方法")
public JsonResult reduceCommodityCount(StockReduceCountDTO stockReduceCountDTO){
// 调用业务逻辑层
stockService.reduceCommodityCount(stockReduceCountDTO);
return JsonResult.ok("库存减少已执行!");
}
nacos\seata\sentinel要启动
重启stock服务(其它服务都可以停掉)
如果不运行knife4j测试,sentinel的仪表盘不会有任何信息
在第一次运行了减少库存方法之后,sentinel的仪表盘才会出现nacos-stock的信息
选中这个信息点击"簇点链路"
找到我们编写的"减少库存的方法"点 “+流控”
设置流控规则
我们先设置QPS为1也就是每秒请求数超过1时,进行限流
然后我们可以快速双击knife4j减少库存的方法,触发它的流控效果
这样的流控没有正确的消息提示
我们需要自定义方法进行正确的提示给用户看到
自定义限流方法
对与被限流的请求,我们可以自定义限流的处理方法
默认情况下可能不能正确给用户提示,一般情况下,对被限流的请求也要有"服务器忙请重试"或类似的提示
StockController类中@SentinelResource注解中,可以自定义处理限流情况的方法
@PostMapping("/reduce/count")
@ApiOperation("减少商品库存数")
// @SentinelResource注解标记的控制层方法,会在运行时被Sentinel进行管理
// 在这个控制层方法第一次运行后,可以在Sentinel仪表台界面中设置限流规则
// "减少库存的方法"设置了当前方法在仪表台显示的名称
// blockHandler是指定限流时运行方法的配置
@SentinelResource(value = "减少库存的方法",blockHandler = "blockError")
public JsonResult reduceCommodityCount(StockReduceCountDTO stockReduceCountDTO){
// 调用业务逻辑层
stockService.reduceCommodityCount(stockReduceCountDTO);
return JsonResult.ok("库存减少已执行!");
}
// Sentinel 自定义限流方法规则
// 1.访问修饰符必须是public
// 2.返回值类型必须和控制器方法一致
// 3.方法名称必须匹配控制器方法blockHandler配置的名称
// 4.参数列表,前面必须和控制器方法一致,后面添加BlockException类型的参数,表示限流方法
public JsonResult blockError(StockReduceCountDTO stockReduceCountDTO,
BlockException e){
// 进这个方法就是被限流的请求,直接返回限流信息即可
return JsonResult.failed(ResponseCode.INTERNAL_SERVER_ERROR,
"服务器忙,请稍后再试");
}
重启stock-webapi模块
再次尝试被限流,观察被限流的提示
QPS与并发线程数
-
QPS:是每秒请求数
单纯的限制在一秒内有多少个请求访问控制器方法
-
并发线程数:是当前正在使用服务器资源请求线程的数量
限制的是使用当前服务器的线程数
自定义降级方法
所谓降级就是正常运行控制器方法的过程中
控制器方法发生了异常,Sentinel支持我们运行别的方法来处理异常,或运行别的业务流程处理
我们也学习过处理控制器异常的统一异常处理类,和我们的降级处理有类似的地方
但是Sentinel降级方法优先级高,而且针对单一控制器方法编写
StockController类中@SentinelResource注解中,可以定义处理降级情况的方法
@PostMapping("/reduce/count")
@ApiOperation("减少商品库存数")
// @SentinelResource注解标记的控制层方法,会在运行时被Sentinel进行管理
// 在这个控制层方法第一次运行后,可以在Sentinel仪表台界面中设置限流规则
// "减少库存的方法"设置了当前方法在仪表台显示的名称
// blockHandler是指定限流时运行方法的配置
// fallback 是指当控制器方法运行发生异常时,运行的降级方法的名称
@SentinelResource(value = "减少库存的方法",blockHandler = "blockError",
fallback = "fallbackError")
public JsonResult reduceCommodityCount(StockReduceCountDTO stockReduceCountDTO){
// 测试Sentinel降级
if(Math.random()<0.5){
// 随机抛出异常,抛出的异常会被降级方法处理
throw new CoolSharkServiceException(ResponseCode.BAD_REQUEST,"随机异常");
}
// 调用业务逻辑层
stockService.reduceCommodityCount(stockReduceCountDTO);
return JsonResult.ok("库存减少已执行!");
}
// 限流方法略.....
// 降级方法:上面@SentinelResource中fallback指定的降级方法
// 声明格式:基本和限流方法相同,方法参数不需要添加异常类型
// 当控制器方法运行发送异常时,Sentinel会自动调用这个方法
// 实际业务中,可以是新版的业务发生异常,然后转而运行老版代码的机制
public JsonResult fallbackError(StockReduceCountDTO stockReduceCountDTO){
// 因为没有老版本代码可用,所以也是返回错误信息
return JsonResult.failed(ResponseCode.INTERNAL_SERVER_ERROR,
"运行发生异常,服务降级!");
}
重启csmall-stock模块测试
当发生随机异常时,就运行降级方法
当没有发生随机异常时,就正常运行!
课堂作业
为business模块控制器的buy方法添加Sentinel流控和降级的功能
流控时输出"服务器忙",降级时输出"服务降级"
1.pom文件
2.yml(port属性不能和stock模块的相同8722)
3.修改控制器代码(注解,流控和降级方法)
SpringGateway网关
奈非框架简介
早期(2020年前)奈非提供的微服务组件和框架受到了很多开发者的欢迎
这些框架和SpringCloud Alibaba的对应关系我们要了解
现在还有很多旧项目维护是使用奈非框架完成的微服务架构
Nacos对应Eureka都是注册中心
Dubbo对应Ribbon+feign都是实现微服务远程RPC调用的组件
Sentinel对应Hystrix都是做项目限流熔断降级的组件
Gateway对应Zuul都是网关组件
Gateway框架不是阿里写的,是Spring提供的
什么是网关
"网"指网络,"关"指关口或关卡
网关:就是指网络中的关口\关卡
网关就是当前微服务项目的"统一入口"
程序中的网关就是当前微服务项目对外界开放的统一入口
所有外界的请求都需要先经过网关才能访问到我们的程序
提供了统一入口之后,方便对所有请求进行统一的检查和管理
网关项目git地址
https://gitee.com/jtzhanghl/gateway-demo.git
网关的主要功能有
- 将所有请求统一经过网关
- 网关可以对这些请求进行检查
- 网关方便记录所有请求的日志
- 网关可以统一将所有请求路由到正确的模块\服务上
路由的近义词就是"分配"
Spring Gateway简介
我们使用Spring Gateway作为当前项目的网关框架
Spring Gateway是Spring自己编写的,也是SpringCloud中的组件
SpringGateway官网
https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/
网关项目git地址
https://gitee.com/jtzhanghl/gateway-demo.git
简单网关演示
SpringGateway网关是一个依赖,不是一个软件
所以我们要使用它的话,必须先创建一个SpringBoot项目
这个项目也要注册到Nacos注册中心,因为网关项目也是微服务项目的一个组成部分
beijing和shanghai是编写好的两个项目
gateway项目就是网关项目,需要添加相关配置
<dependencies>
<!-- Gateway依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- Nacos依赖 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- 网关负载均衡依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
</dependencies>
我们从yml文件配置开始添加
server:
port: 9000
spring:
application:
name: gateway
cloud:
nacos:
discovery:
# 网关也是微服务项目的一部分,所以也要注册到Nacos
server-addr: localhost:8848
gateway:
# routes是一个数组,数组中的数据使用"-"开头表示数据中的一个对象
routes: # 开始编写Gateway路由配置
# 当前路由的名称,和任何其他名称没有关联,只是不能和后面再出现的路由名称重复
- id: gateway-beijing
# 当匹配当前路由设置时,访问指定的服务器名称(Nacos注册的服务器名称)
# lb是LoadBalance的缩写,是负载均衡的调用
uri: lb://beijing
# 编写断言配置,断言的意思就是满足指定条件时运行某些事情
# predicates:断言
predicates:
# 断言中我们编写当路径满足指定条件时
# 当请求路径以/bj/开头时,就会路由到上面设置好的beijing服务器运行
# ↓ P大写!!!!!!!!
- Path=/bj/**
# spring.cloud.gateway.routes[0].uri
# spring.cloud.gateway.routes[0].predicates[0]
随笔
Dubbo报错
报错信息特别长的
-
删除nacos配置列表中的所有信息(可以先选每页显示100条,在执行全删)
然后停掉所有服务重启
-
consumer: # 当本项目启动时,是否检查当前项目需要的所有Dubbo服务是否是可用状态 # 我们设置它的值为false,表示项目启动时不检查,所需的服务是否可用 check: false timeout: 50000
-
报错信息特别长,但是不影响运行的
是因为当前计算机wifi网卡配置或防火墙软件导致的,可以无视
路由规则解释
如果路径是 /base/business开头的, 就去找nacos-business服务器
如果路径是 /base/cart开头的, 就去找nacos-cart服务器
如果路径是 /base/order开头的, 就去找nacos-order服务器
如果路径是 /base/stock开头的, 就去找nacos-stock服务器
gateway项目
如果路径是 /bj开头 就去找beijing服务器
如果路径是 /sh开头 就去找shanghai服务器