Java后端/全端知识体系
2024-10-21 20:03:14 40 举报
AI智能生成
算是对几个知识脉络的总结,由于免费额度不够了,后续新增的其他模块会往这里添加。 2024年6月22日02:23:44:补充前端知识 2024年7月5日20:14:32:同步Redis和ES的更新。 2024年7月13日01:24:42: ①思维导图重新命名 ②定期同步思维导图的更新 2024年7月17日23:22:45: ①前端三剑客知识完善 ②框架部分写了个基本大纲,待补充 2024年7月18日15:56:13: ①Vue3知识框架完善、JS也基于MDN的参考内容做了进一步完善 2024年7月22日13:51:57 ①react和angular补充了一些核心概念(没有深入去写) ②vue根据官方文档进行了进一步的补充 ③Node.js内容完善 2024年7月24日20:49:26: 最近命令行的git用的比较多,IDEA用多了命令行反而感觉生疏了,所以想着补一下Git相关的一些内容 --------------------------------------------------------------------------------------- 2024年9月4日02:56:02:①不定时同步思维导图;②React内容补充;③调整思维导图样式风格; ------------------------------------------------------------------------------------2024年10月21日20:02:29:不定期不同步更新
作者其他创作
大纲/内容
前端知识
前端三剑客
HTML
HTML基础
网页的基本组成部分
结构
html
表现
css
行为
js
HTML标签(元素)
单标签
<标签名/>
双标签
<标签名>标签体</标签名>
HTML标签属性
用于给标签提供附加信息,可用在左标签或者单边标签中添加
HTML基本结构
body标签,网页呈现的内容
head标签,不会出现在网页
HTML注释
<!-- 注释内容 -->
HTML文档声明
文件第一行<! DOCTYPE HTML>
HTML字符编码
head标签里使用meta标签,使用charset数据指定
HTML设置语言
html标签里使用lang属性指定
HTML标准结构
HTML文档声明
html标签
head标签
meta标签
title标签
body标签
HTML标签
排版标签
h1~h6,标题
p,段落
div,没有任何含义,用于整体布局
块级元素与行内元素
块级元素
独占一行,排版标签都是块级
行内元素
不独占一行
使用规则
块中能写块和行
行中只能写行
常用文本标签
em,要着重阅读的内容
strong,十分重要
span,没有语义,用于包裹短语的通用容器
不常用文本标签
cite,作品标题
dfn,特殊术语
del与ins,删除的文本与插入的文本
sub与sup,下标文字与上标文字
code,代码段
abbr,缩写
图片标签
img标签
src属性
相对路径
顾名思义,相对当前文件的路径
绝对路径
全路径
width属性
height属性
alt属性
图片格式
jpg
有损压缩
平常使用
png
无损压缩
比较重要的配图使用
bmp
不压缩
特殊场景,如大型游戏的图片,网页很少用
gif
动态图片
webp
谷歌推出的,兼容性不太好
base64
特殊文本,对图片进行base64编码,网页查看,一般图片查看器无法打开
超链接
a标签
href属性
跳转网页
跳转文件
download属性,强制下载,值为文件名
跳转锚点
换起指定应用
电话
tel:10000
邮件
mailto:000@qq.com
短信
sms:10086
target
_blank
_self(默认)
列表
有序列表
ol标签
li标签
无序列表
ul标签
li标签
自定义列表
ul标签
dt标签(术语名称)
dd标签(术语描述)
表格
table标签
表格标题,caption
表格头部,thead
行,tr
单元格,th
表格主体,tbody
行,tr
单元格,td
表格脚注,tfoot
行,tr
单元格,td
表格常用属性
table标签
border属性,边框,值为像素值
width属性,宽度
height属性,高度
cellspacing,单元格间距
thead标签
align,内容水平对齐方式
left
center
right
valign,内容垂直对齐方式
bottom
top
middle
tbody标签
和thead一样
tr标签
和thead一样
tfoot标签
和thead一样
td标签
width
同列所有单元格全都受影响
height
同行所有单元格都受影响
align
valign
rowspan
指定要跨的行数(合并行)
colspan
指定要跨的列数(合并列)
th标签
和td一样
常用标签补充
换行标签,<br>
分割线,<hr>
原文显示,<pre></pre>
表单
表单的基本结构
form标签
action属性,将表单提交给谁处理
method属性,get、post等
target
_self
_blank
input标签
type属性,输入框类型
name属性,指定提交的数据的名称
butto标签
文本框
input标签
type属性为text
value属性,输入框默认值
maxlength属性,输入框最大可输出长度
name属性,指定提交的数据的名称
密码框
input标签
type属性为password
value属性,输入框默认值
maxlength属性,输入框最大可输出长度
name属性,指定提交的数据的名称
单选框
input标签
type属性为radio
name属性,指定提交的数据的名称,可以通过在多个radio标签中指定同样的name属性的值来指定一组单选框
value,当前选项的值
checked属性,默认勾选
多选框
input标签
type属性为checkbox
name属性,指定提交的数据的名称,可以通过在多个checkbox标签中指定同样的name属性的值来指定一组多选框
value,当前选项的值
checked属性,默认勾选
隐藏域,用户不可视
input标签
type属性为hidden
name属性,指定提交的数据的名称
value,当前标签的值
作用
提交表单的时候,携带一些固定的数据
提交
button标签
不能带name属性
input标签
type属性为submit
不能带name属性
重置
button标签
不能带name属性
type属性为reset
input标签
type属性为reset
不能带name属性
普通按钮
button标签
type属性为button
input标签
type属性为button
文本域
textarea标签
name
cols
rows
下拉框
select标签
name属性
option标签
value属性
selected属性,默认选中
禁用表单控件
disabled属性
label标签
①for属性,控件id的值
②把控件套在标签里
作用,文本与控件关联,点击文本,焦点聚焦到关联控件
fieldset和legend标签(了解)
fieldset为表单控件分组
legend是分组的标题
框架标签
ifram标签,嵌入网页
src属性
width属性
height属性
frameborder属性,是否显示标签,0/1
常用HTML字符实体
空格
金额符号
¥
版权
©
HTML全局属性
id
给标签指定唯一标识,不能重复
class
给标签指定类名
style
给标签设置css样式
dir
内容的方向
ltr
rtl
title
给标签设置一个文字提示,一般图片和超链接用的比较多
lang
给标签指定语言
meta元信息
字符编码,charset属性
针对IE的兼容性配置,http-equiv、content属性
针对移动端的配置,name=viewport,content=width-device-width,initial-scale=1.0
配置网页关键字,name=keywords
配置网页描述信息,name=description
针对爬虫配置,name=robots
index
noindex
follow
all
none
noarchive
nocache
网页作者,name=author
网页生成工具,name=generator
网页版权信息,name=copyright
网页自动刷新,http-equiv=refresh
HTML进阶(HTML5)
新增布局标签
header 整个页面,或部分区域的头部
footer 整个页面,或部分区域的底部
nav 导航 双
article 文章、帖子、杂志、新闻、博客、评论等。
section
页面中的某段文字,或文章中的某段文字(里面文字通常里面会包含
标题)。
aside 侧边栏
main 文档的主要内容 ( WHATWG 没有语义, IE 不支持),几乎不用。
hgroup 包裹连续的标题,如文章主标题、副标题的组合 ( W3C 将其删除)
footer 整个页面,或部分区域的底部
nav 导航 双
article 文章、帖子、杂志、新闻、博客、评论等。
section
页面中的某段文字,或文章中的某段文字(里面文字通常里面会包含
标题)。
aside 侧边栏
main 文档的主要内容 ( WHATWG 没有语义, IE 不支持),几乎不用。
hgroup 包裹连续的标题,如文章主标题、副标题的组合 ( W3C 将其删除)
新增状态标签
meter 标签
progress 标签
新增列表标签
datalist
details
summary
新增文本标签
文本注音
文本注音
新增表单功能
表单控件新增属性
placeholder
required
autofocus
autocomplete
pattern
input 新增属性值
email 邮箱类型的输入框,表单提交时会验证格式,输入为空则不验证格式。
url url 类型的输入框,表单提交时会验证格式,输入为空则不验证格式。
number 数字类型的输入框,表单提交时会验证格式,输入为空则不验证格式。
search 搜索类型的输入框,表单提交时不会验证格式。
tel
电话类型的输入框,表单提交时不会验证格式,在移动端使用时,会唤起数
字键盘。
range 范围选择框,默认值为 50 ,表单提交时不会验证格式。
color 颜色选择框,默认值为黑色,表单提交时不会验证格式。
date 日期选择框,默认值为空,表单提交时不会验证格式。
month 月份选择框,默认值为空,表单提交时不会验证格式。
week 周选择框,默认值为空,表单提交时不会验证格式。
time 时间选择框,默认值为空,表单提交时不会验证格式。
datetime-local 日期+时间选择框,默认值为空,表单提交时不会验证格式。
form 标签新增属性
novalidate
新增多媒体标签
视频标签
<video>
src URL地址 视频地址
width 像素值 设置视频播放器的宽度
height 像素值 设置视频播放器的高度
controls - 向用户显示视频控件(比如播放/暂停按钮)
muted - 视频静音
autoplay - 视频自动播放
loop - 循环播放
poster URL地址 视频封面
音频标签
<audio>
src URL地址 音频地址
controls - 向用户显示音频控件(比如播放/暂停按钮)
autoplay - 音频自动播放
muted - 音频静音
loop - 循环播放
新增全局属性
contenteditable
表示元素是否可被用户编辑,可选值如下:
true :可编辑
false :不可编辑
draggable
表示元素可以被拖动,可选值如下:
true :可拖动
false :不可拖动
hidden 隐藏元素
spellcheck
规定是否对元素进行拼写和语法检查,可选值如下:
true :检查
false :不检查
contextmenu 规定元素的上下文菜单,在用户鼠标右键点击元素时显示。
data-* 用于存储页面的私有定制数据。
HTML5兼容性处理
WebSocket
建立连接
var Socket = new WebSocket(url, [protocol] );
发送数据
send()
获取数据
onmessage()
关闭连接
close()
CSS
问题: HTML虽然可以创建网页,但控制样式和布局非常困难,代码混乱,难以维护。
解决: CSS(Cascading Style Sheets)被引入,它将内容与样式分离,使开发者能够更容易地控制网页的外观和布局,提高了代码的可维护性和重用性。
基础
样式
行内样式(内联样式)
在标签里使用style属性配置样式
内部样式
将所有css代码提前出来,写在style标签里
外部样式(推荐使用)
在head标签中使用link标签引入外部的css文件
rel属性=stylesheet
ref属性,css文件地址
样式优先级
优先级规则
行内样式 > 内部样式 = 外部样式
①优先级相同的,后面的会覆盖前面的
②同一个样式表中,优先级也和编写顺序有关,后面的会覆盖前面的
语法规范
格式: 选择器 { 声明(key: value); 声明; }
代码风格
展开风格
平时开发推荐,便于维护调试
紧凑风格
项目上线推荐,减小文件体积(打包工具会处理)
选择器
基本选择器
通配选择器
* { ...... }
作用所有的HTML元素,,一般用于清除样式
元素选择器
元素名(标签名) { ...... }
选中所有同种标签,但是不能 差异化选择
类选择器
.类名 { ...... }
选中所有特定类名的元素,使用频率很高
一个元素只能有一个class属性,但是可以有多个值,用空格间隔
id选择器
#id { ...... }
复合选择器
交集选择器
作用:选中同时符合多个条件的元素
语法:选择器1选择器2选择器3...选择器n { ...... },写法用的非常多,太灵活了
并集选择器
作用:选中多个选择器对应的元素
语法:选择器1,选择器2,选择器3,...,选择器n { ...... }
元素之间的关系
父元素
直接包裹某个元素的元素
子元素
被父元素直接包含的元素
祖先元素
一直往外找,都是祖先
后代元素
一直往里找,都是后代
兄弟元素
具有相同父元素的元素
后代选择器
作用:选中指定元素中,复合要求的后代元素
语法:选择器1 选择器2 选择器3 ...... 选择器n { ...... } (先写祖先再写后代)
子代选择器
作用:选中指定元素中,复合要求的子代元素
语法: 选择器1>选择器2>选择器3> ...... > 选择器n { ...... } (先写父在写子)
兄弟选择器
相邻兄弟选择器
作用:选中指定元素后,符合条件的相邻兄弟元素
语法: 选择器1+选择器2 { ...... }
通用兄弟选择器
作用:选中指定元素后,符合条件的所有兄弟元素
语法: 选择器1~选择器2 { ...... }
属性选择器
作用:选中属性值符合一定要求的元素
语法
[属性名]
选中具有某个属性的元素
[属性名="值"]
选中包含某个属性,且属性值等于指定值的元素
[属性名^="值"]
选中包含某个属性,且属性值以指定值开头的元素
[属性名$="值"]
选中包含某个属性,且属性值以指定值结尾的元素
[属性名*="值"]
选中包含某个属性,且属性值包含指定值的元素
伪类选择器
概念
选中特殊状态的元素
分类
动态伪类
:link
超链接未被访问的状态
:visited
超链接被访问过的状态
:hover
鼠标悬停在元素上的状态
:active
元素激活的状态
:focus
获取焦点的状态
只有表单类元素才能使用
结构伪类(了解即可)
:nth-last-child(n)
所以兄弟元素中的倒数第n个
:nth-last-of-type(n)
所以同类型兄弟元素中的倒数第n个
:only-child
没有兄弟的元素
:only-of-type
没有同类型兄弟的元素
:root
根元素
:empty
内容为空的元素
否定伪类
:not(选择器),排除满足括号中条件的元素
UI伪类
:checked
被选中的复选框或单选按钮
:enable
可用的表单元素(没有disabled属性)
:disabled
不可用的表单元素(有disabled属性)
目标伪类(了解)
:target
选择锚点指向的元素
语言伪类(了解)
:lang()
根据指定的语言选择元素
伪元素选择器
作用:选中元素中的一些特殊位置
常用伪元素
::first-letter
选中元素中的第一个文字
::first-line
选中元素中的第一行文字
::selection
选中被鼠标选中的内容
::placeholder
选中输入框的提示文字
::before
在元素最开始的位置,创建一个子元素
::after
在元素最后的位置,创建一个子元素
选择器优先级
简单描述:
行内样式 > ID选择器 > 类选择器 > 元素选择器 > 通配选择器
详细描述
1.计算方式
每个选择器,都可以计算出一组权重,格式为(a,b,c)
a
ID选择器的个数
b
类、伪类、属性选择器的个数
c
元素、伪元素选择器的个数
2.比较规则
从左到右,依次比较大小。当前位胜出后,后面的不再对比
3.特殊规则
1. 行内样式权重大于所以选择器
2. !Important的权重,大于行内样式、大于所有选择器,权重最高。
三大特性
层叠性
概念
如果发生了样式冲突,那么会根据一定的规则(选择器优先级),进行样式的叠层(覆盖)
继承性
概念
元素会自动拥有其父元素、或其祖先元素上所设置的某些样式
常见可继承
text-??
font-??
line-??
color
规则
优先继承离得近的
优先级
简单聊
!Important > 行内样式 > ID选择器 > 类选择器 > 元素选择器 > * > 继承的样式
详细聊
计算权重
注意
并集选择器的每一个部分是分开算的
像素
概念:我们的电脑屏幕是,是由一个一个“小点”组成的,每个“小点”,就是一个像素(px)。
规律:像素点越小,呈现的内容就越清晰、越细腻。
颜色
颜色的表示
颜色名
red
green
blue
......
rgb与rgba
rgb(r,g,b)
rgba(r,g,b,a),a表示透明度
表示方式
百分比,0%~100%
数字
颜色0~255
透明度是0~1
HEX与HEXA
#rrggbbaa
#rrggbb
表示方式
数字
0~f,十六进制
HSL与HSLA
常用属性
字体
字体大小
font-size
字体族
font-family
字体风格
font-style
normal(默认)
italic(√)
字体自带的倾斜
oblique
强制倾斜
字体粗细
font-weight
关键词
lighter
细
normal
正常
bold
粗
bolder
很粗
数值
100~1000
100~300=lighter
400~500=normal
600以上bold
字体复合属性
font
可以把上述字体样式合并成一个属性
文本
文本颜色
color
文本间距
字母间距
letter-spacing
单词间距
word-spacing
属性值为像素px
正值间距增大
负值间距缩小
文本修饰
text-decoration
none
无
underline
下划线
overline
上划线
line-through
删除线
文本缩进
text-indent
单位px
文本对齐
text-align
left(默认)
right
center
文本行高
line-height
nornal
像素值px
数字
百分比
文本对齐
垂直
顶部
无需配置,默认
居中
height=line-height
底部
vertical-align
作用:用于指定同一行元素之间,或表格单元格内文字的垂直对齐方式
常用值
baseline(默认)
top
middle
bottom
列表
作用在ul、ol、li元素上
list-style-type
设置列表符号
none
不显示
square
实心方块
disc
圆形
decimal
数字
lower-roman
小写罗马
upper-roman
大写罗马
lower-alpha
小写字母
upper-alpha
大写字母
list-style-position
设置列表符号的位置
inside
outside
list-style-image
自定义列表符号
url属性
list-style
复合属性
边框
border-width
边框宽度
border-color
边框颜色
border-style
边框风格
none
默认值
solid
实线
dashed
虚线
dotted
点线
double
双实线
border
边框复合属性
表格
只有table标签能用
table-layout
设置列宽度
auto :自动,列宽根据内容计算(默认
值)。
fixed :固定列宽,平均分。
border-spacing
单元格间距
CSS 中可用的长度值。
生效的前提:单元格边框不能合并。
border-collapse
合并单元格边框
collapse :合并
separate :不合并
empty-cells
隐藏没有内容的单元
格
show :显示,默认
hide :隐藏
生效前提:单元格不能合并。
caption-side
设置表格标题位置
top :上面(默认值)
bottom :在表格下面
背景
background-color
设置背景颜色
符合 CSS 中颜色规范的值
默认背景颜色是 transparent 。
background-image
设置背景图片
url(图片的地址)
background-repeat
设置背景重复方
式
repeat :重复,铺满整个元素,默认值。
repeat-x :只在水平方向重复。
repeat-y :只在垂直方向重复。
no-repeat :不重复。
background-position
设置背景图位置
通过关键字设置位置:
写两个值,用空格隔开
水平: left 、 center 、 right
垂直: top 、 center 、 bottom
如果只写一个值,另一个方向的值取 center
通过长度指定坐标位置:
以元素左上角,为坐标原点,设置图片左上角的
位置。
两个值,分别是 x 坐标和 y 坐标。
只写一个值,会被当做 x 坐标, y 坐标取
center
background
复合属性
鼠标
cursor
设置鼠标光标的样式
pointer :小手
move :移动图标
text :文字选择器
crosshair :十字架
wait :等待
help :帮助
自定义鼠标图标
cursor: url("./arrow.png"),pointer;
盒子模型
CSS 长度单位
px :像素
em :相对元素 font-size 的倍数
rem :相对根字体大小,html标签就是根
% :相对父元素计算
元素的显示模式
块元素(block)
1. 在页面中独占一行,不会与任何元素共用一行,是从上到下排列的。
2. 默认宽度:撑满父元素。
3. 默认高度:由内容撑开。
4. 可以通过 CSS 设置宽高。
行内元素(inline)
1. 在页面中不独占一行,一行中不能容纳下的行内元素,会在下一行继续从左到右排
列。
2. 默认宽度:由内容撑开。
3. 默认高度:由内容撑开。
4. 无法通过 CSS 设置宽高。
列。
2. 默认宽度:由内容撑开。
3. 默认高度:由内容撑开。
4. 无法通过 CSS 设置宽高。
行内块元素(inline-block)
1. 在页面中不独占一行,一行中不能容纳下的行内元素,会在下一行继续从左到右排
列。
2. 默认宽度:由内容撑开。
3. 默认高度:由内容撑开。
4. 可以通过 CSS 设置宽高。
总结各元素的显示模式
块元素(block)
1. 主体结构标签: <html> 、 <body>
2. 排版标签: <h1> ~ <h6> 、 <hr> 、 <p> 、 <pre> 、 <div>
3. 列表标签: <ul> 、 <ol> 、 <li> 、 <dl> 、 <dt> 、 <dd>
4. 表格相关标签: <table> 、 <tbody> 、 <thead> 、 <tfoot> 、 <tr> 、
<caption>
5. <form> 与 <option>
行内元素(inline)
1. 文本标签:
、 <em> 、 <strong> 、 <sup> 、 <sub> 、 <del> 、 <ins>
2. <a> 与 <label>
行内块元素(inline-block)
1. 图片: <img>
2. 单元格: <td> 、 <th>
3. 表单控件: <input> 、 <textarea> 、 <select> 、 <button>
4. 框架标签: <iframe>
修改元素的显示模式
通过 CSS 中的 display 属性可以修改元素的默认显示模式
常用值
none
元素会被隐藏。
block
元素将作为块级元素显示。
inline
元素将作为内联元素显示。
inline-block
元素将作为行内块元素显示。
盒子模型的组成
CSS 会把所有的 HTML 元素都看成一个盒子,所有的样式也都是基于这个盒子。
组成
1. margin(外边距): 盒子与外界的距离。
2. border(边框): 盒子的边框。
3. padding(内边距): 紧贴内容的补白区域。
4. content(内容):元素中的文本或后代元素都是它的内容。
盒子的大小 = content + 左右 padding + 左右 border 。
注意:外边距 margin 不会影响盒子的大小,但会影响盒子的位置。
盒子内容区(content)
width
设置内容区域宽度
max-width
设置内容区域的最大宽度
min-width
设置内容区域的最小宽度
height
设置内容区域的高度
max-height
设置内容区域的最大高度
min-height
设置内容区域的最小高度
注意
max-width 、 min-width 一般不与 width 一起使用。
max-height 、 min-height 一般不与 height 一起使用
关于默认宽度
所谓的默认宽度,就是不设置 width 属性时,元素所呈现出来的宽度。
总宽度 = 父的 content — 自身的左右 margin 。
内容区的宽度 = 父的 content — 自身的左右 margin — 自身的左右 border — 自身的左右
padding 。
padding 。
盒子内边距(padding)
padding-top
上内边距
padding-right
右内边距
padding-bottom
下内边距
padding-left
左内边距
padding
复合属性
padding 复合属性的使用规则:
1. padding: 10px; 四个方向内边距都是 10px 。
2. padding: 10px 20px; 上 10px ,左右 20px 。(上下、左右)
3. padding: 10px 20px 30px; 上 10px ,左右 20px ,下 30px 。(上、左右、下)
4. padding: 10px 20px 30px 40px; 上 10px ,右 20px ,下 30px ,左 40px 。(上、右、
下、左)
2. padding: 10px 20px; 上 10px ,左右 20px 。(上下、左右)
3. padding: 10px 20px 30px; 上 10px ,左右 20px ,下 30px 。(上、左右、下)
4. padding: 10px 20px 30px 40px; 上 10px ,右 20px ,下 30px ,左 40px 。(上、右、
下、左)
注意点:
1. padding 的值不能为负数。
2. 行内元素 的 左右内边距是没问题的,上下内边距不能完美的设置。
3. 块级元素、行内块元素,四个方向内边距都可以完美设置。
盒子边框(border)
border-style
边框线风格
复合了四个方向的边框风格
复合了四个方向的边框风格
none : 默认值
solid : 实线
dashed : 虚线
dotted : 点线
double : 双实线
solid : 实线
dashed : 虚线
dotted : 点线
double : 双实线
border-width
边框线宽度
复合了四个方向的边框宽度
复合了四个方向的边框宽度
长度,默认 3px
border-color
边框线颜色
复合了四个方向的边框颜色
复合了四个方向的边框颜色
颜色,默认黑色
border
复合属性
border-left
border-left-style
border-left-width
border-left-color
border-left-width
border-left-color
border-right
border-right-style
border-right-width
border-right-color
border-right-width
border-right-color
border-top
border-top-style
border-top-width
border-top-color
border-bottom
border-bottom-style
border-bottom-width
border-bottom-color
盒子外边距_margin
margin-left
左外边距
margin-right
右外边距
margin-top
上外边距
margin-bottom
下外边距
margin
复合属性,可以写 1~4 个值,规律同 padding (顺时
针)
margin 注意事项
1. 子元素的 margin ,是参考父元素的 content 计算的。(因为是父亲的 content 中承装着
子元素)
2. 上 margin 、左 margin :影响自己的位置;下 margin 、右 margin :影响后面兄弟元素
的位置。
3. 块级元素、行内块元素,均可以完美地设置四个方向的 margin ;但行内元素,左右
margin 可以完美设置,上下 margin 设置无效。
4. margin 的值也可以是 auto ,如果给一个块级元素设置左右 margin 都为 auto ,该块级
元素会在父元素中水平居中。
5. margin 的值可以是负值。
margin 塌陷问题
什么是 margin 塌陷?
第一个子元素的上 margin 会作用在父元素上,最后一个子元素的下 margin 会作用在父元素上。
如何解决 margin 塌陷?
方案一: 给父元素设置不为 0 的 padding 。
方案二: 给父元素设置宽度不为 0 的 border 。
方案三:给父元素设置 css 样式 overflow:hidden
margin 合并问题
什么是 margin 合并?
上面兄弟元素的下外边距和下面兄弟元素的上外边距会合并,取一个最大的值,而不是相加。
如何解决 margin 合并?
无需解决,布局的时候上下的兄弟元素,只给一个设置上下外边距就可以了。
处理内容溢出
overflow
溢出内容的处理方式
visible :显示,默认值
hidden :隐藏
scroll :显示滚动条,不论内容是否溢出
auto :自动显示滚动条,内容不溢出不显
示
overflow-x
水平方向溢出内容的处理方式
同 overflow
overflow-y
垂直方向溢出内容给的处理方
式
同 overflow
注意:
1. overflow-x 、 overflow-y 不能一个是 hidden ,一个是 visible ,是实验性属性,不
建议使用。
2. overflow 常用的值是 hidden 和 auto ,除了能处理溢出的显示方式,还可以解决很多
疑难杂症。
隐藏元素的方式
方式一:visibility 属性
visibility 属性默认值是 show ,如果设置为 hidden ,元素会隐藏。
元素看不见了,还占有原来的位置(元素的大小依然保持)。
方式二: display 属性
设置 display:none ,就可以让元素隐藏。
彻底地隐藏,不但看不见,也不占用任何位置,没有大小宽高。
样式的继承
会继承的 css 属性
字体属性、文本属性(除了vertical-align)、文字颜色 等。
不会继承的 css 属性
边框、背景、内边距、外边距、宽高、溢出方式 等
一个规律:能继承的属性,都是不影响布局的,简单说:都是和盒子模型没关系的。
.默认样式
元素一般都些默认的样式
1. <a> 元素:下划线、字体颜色、鼠标小手。
2. <h1> ~ <h6> 元素: 文字加粗、文字大小、上下外边距。
3. <p> 元素:上下外边距
4. <ul> 、 ol 元素:左内边距
5. body 元素: 8px 外边距(4个方向)
优先级
元素的默认样式 > 继承的样式,所以如果要重置元素的默认样式,选择器一定要直接选择器
到该元素
布局小技巧
1. 行内元素、行内块元素,可以被父元素当做文本处理
2. 如何让子元素,在父亲中 水平居中:
若子元素为块元素,给父元素加上: margin:0 auto; 。
若子元素为行内元素、行内块元素,给父元素加上: text-align:center 。
如何让子元素,在父亲中 垂直居中:
若子元素为块元素,给子元素加上: margin-top ,值为:(父元素 content -子元素盒子
总高) / 2。
总高) / 2。
若子元素为行内元素、行内块元素:
让父元素的 height = line-height ,每个子元素都加上: verticalalign:middle; 。
补充:若想绝对垂直居中,父元素 font-size 设置为 0 。
元素之间的空白问题
产生的原因:
行内元素、行内块元素,彼此之间的换行会被浏览器解析为一个空白字符。
解决方案:
1. 方案一: 去掉换行和空格(不推荐)。
2. 方案二: 给父元素设置 font-size:0 ,再给需要显示文字的元素,单独设置字体大小(推
荐)。
行内块的幽灵空白问题
产生原因:
行内块元素与文本的基线对齐,而文本的基线与文本最底端之间是有一定距离的。
解决方案:
方案一: 给行行内块设置 vertical ,值不为 baseline 即可,设置为 middel 、 bottom 、
top 均可。
方案二: 若父元素中只有一张图片,设置图片为 display:block 。
方案三: 给父元素设置 font-size: 0 。如果该行内块内部还有文本,则需单独设置 fontsize 。
top 均可。
方案二: 若父元素中只有一张图片,设置图片为 display:block 。
方案三: 给父元素设置 font-size: 0 。如果该行内块内部还有文本,则需单独设置 fontsize 。
浮动
浮动的简介
在最初,浮动是用来实现文字环绕图片效果的,现在浮动是主流的页面布局方式之一。
元素浮动后的特点
1. 🤢脱离文档流。
2. 😊不管浮动前是什么元素,浮动后:默认宽与高都是被内容撑开(尽可能小),而且可以设置宽
高。
3. 😊不会独占一行,可以与其他元素共用一行。
4. 😊不会 margin 合并,也不会 margin 塌陷,能够完美的设置四个方向的 margin 和 padding 。
5. 😊不会像行内块一样被当做文本处理(没有行内块的空白问题)。
元素浮动后会有哪些影响
对兄弟元素的影响: 后面的兄弟元素,会占据浮动元素之前的位置,在浮动元素的下面;对前面的兄弟
无影响。
对父元素的影响: 不能撑起父元素的高度,导致父元素高度塌陷;但父元素的宽度依然束缚浮动的元
素。
解决浮动产生的影响(清除浮动)
1. 方案一: 给父元素指定高度。
2. 方案二: 给父元素也设置浮动,带来其他影响。
3. 方案三: 给父元素设置 overflow:hidden 。
4. 方案四: 在所有浮动元素的最后面,添加一个块级元素,并给该块级元素设置 clear:both 。
5. 方案五: 给浮动元素的父元素,设置伪元素,通过伪元素清除浮动,原理与方案四相同。===> 推
荐使用
浮动相关属性
float
设置浮动
left : 设置左浮动
right : 设置右浮动
none :不浮动,默认值
clear
清除浮动
清除前面兄弟元素浮动元素的响应
left :清除前面左浮动的影响
right :清除前面右浮动的影响
both :清除前面左右浮动的影响
定位
相对定位(relative)
如何设置相对定位?
给元素设置 position:relative 即可实现相对定位。
可以使用 left 、 right 、 top 、 bottom 四个属性调整位置
相对定位的参考点在哪里?
相对自己原来的位置
相对定位的特点
1. 不会脱离文档流,元素位置的变化,只是视觉效果上的变化,不会对其他元素产生任何影响。
定位元素的显示层级比普通元素高,无论什么定位,显示层级都是一样的。
默认规则是:
定位的元素会盖在普通元素之上。
都发生定位的两个元素,后写的元素会盖在先写的元素之上
left 不能和 right 一起设置, top 和 bottom 不能一起设置。
相对定位的元素,也能继续浮动,但不推荐这样做。
相对行为的元素,也能通过 margin 调整位置,但不推荐这样做。
绝对定位(absolute)
如何设置绝对定位?
给元素设置 position: absolute 即可实现绝对定位。
可以使用 left 、 right 、 top 、 bottom 四个属性调整位置。
绝对定位的参考点在哪里?
参考它的包含块。
什么是包含块?
1. 对于没有脱离文档流的元素:包含块就是父元素;
2. 对于脱离文档流的元素:包含块是第一个拥有定位属性的祖先元素(如果所有祖先都
没定位,那包含块就是整个页面)。
绝对定位元素的特点
1. 脱离文档流,会对后面的兄弟元素、父元素有影响。
2. left 不能和 right 一起设置, top 和 bottom 不能一起设置。
3. 绝对定位、浮动不能同时设置,如果同时设置,浮动失效,以定位为主。
4. 绝对定位的元素,也能通过 margin 调整位置,但不推荐这样做。
5. 无论是什么元素(行内、行内块、块级)设置为绝对定位之后,都变成了定位元素。
固定定位(fixed)
如何设置为固定定位?
给元素设置 position: fixed 即可实现固定定位。
可以使用 left 、 right 、 top 、 bottom 四个属性调整位置。
固定定位的参考点在哪里?
参考它的视口
什么是视口?—— 对于 PC 浏览器来说,视口就是我们看网页的那扇“窗户”。
固定定位元素的特点
1. 脱离文档流,会对后面的兄弟元素、父元素有影响。
2. left 不能和 right 一起设置, top 和 bottom 不能一起设置。
3. 固定定位和浮动不能同时设置,如果同时设置,浮动失效,以固定定位为主。
4. 固定定位的元素,也能通过 margin 调整位置,但不推荐这样做。
5. 无论是什么元素(行内、行内块、块级)设置为固定定位之后,都变成了定位元素。
粘性定位(sticky)
如何设置为粘性定位?
给元素设置 position:sticky 即可实现粘性定位。
可以使用 left 、 right 、 top 、 bottom 四个属性调整位置,不过最常用的是 top 值。
粘性定位的参考点在哪里?
离它最近的一个拥有“滚动机制”的祖先元素,即便这个祖先不是最近的真实可滚动祖先。
粘性定位元素的特点
不会脱离文档流,它是一种专门用于窗口滚动时的新的定位方式。
最常用的值是 top 值。
粘性定位和浮动可以同时设置,但不推荐这样做。
粘性定位的元素,也能通过 margin 调整位置,但不推荐这样做。
定位层级
1. 定位元素的显示层级比普通元素高,无论什么定位,显示层级都是一样的。
2. 如果位置发生重叠,默认情况是:后面的元素,会显示在前面元素之上。
3. 可以通过 css 属性 z-index 调整元素的显示层级。
4. z-index 的属性值是数字,没有单位,值越大显示层级越高。
5. 只有定位的元素设置 z-index 才有效。
6. 如果 z-index 值大的元素,依然没有覆盖掉 z-index 值小的元素,那么请检查其包含块的层级。
定位的特殊应用
让定位元素的宽充满包含块
1. 块宽想与包含块一致,可以给定位元素同时设置 left 和 right 为 0 。
2. 高度想与包含块一致, top 和 bottom 设置为 0 。
让定位元素在包含块中居中
布局
版心
在 PC 端网页中,一般都会有一个固定宽度且水平居中的盒子,来显示网页的主要内容,这是网页
的版心。
版心的宽度一般是 960 ~ 1200 像素之间。
版心可以是一个,也可以是多个。
常用布局名词
顶部导航条
topbar
页头
header 、 page-header
导航
nav 、 navigator 、 navbar
搜索框
search 、 search-box
横幅、广告、宣传图
banner
主要内容
content 、 main
侧边栏
aside 、 sidebar
页脚
footer 、 page-footer
重置默认样式
很多元素都有默认样式
1. p 元素有默认的上下 margin 。
2. h1~h6 标题也有上下 margin ,且字体加粗。
3. body 元素有默认的 8px 外边距。
4. 超链接有默认的文字颜色和下划线。
5. ul 元素有默认的左 pading 。
6. .......
重置这些默认样式
方案一:使用全局选择器
方案二:reset.css
方案三:Normalize.css
进阶(CSS3)
CSS3 新增长度单位
1. rem 根元素字体大小的倍数,只与根元素字体大小有关。
2. vw 视口宽度的百分之多少 10vw 就是视口宽度的 10% 。
3. vh 视口高度的百分之多少 10vh 就是视口高度的 10% 。
4. vmax 视口宽高中大的那个的百分之多少。(了解即可)
5. vmin 视口宽高中小的那个的百分之多少。(了解即可)
CSS3 新增颜色设置方式
CSS3 新增了三种颜色设置方式,分别是: rgba 、 hsl 、 hsla ,由于之前已经详细讲解,此处略
过。
CSS3 新增选择器
CSS3 新增的选择器有:动态伪类、目标伪类、语言伪类、 UI 伪类、结构伪类、否定伪类、伪元素;这
些在 CSS2 中已经详细讲解,此处略过。
CSS3 新增盒模型相关属性
box-sizing 怪异盒模型
content-box
width 和 height 设置的是盒子内容区的大小。(默认值)
border-box
width 和 height 设置的是盒子总大小。(怪异盒模型)
resize 调整盒子大小
使用 resize 属性可以控制是否允许用户调节元素尺寸
none 不允许用户调整元素大小。 (默认)
both 用户可以调节元素的宽度和高度。
horizontal 用户可以调节元素的宽度 。
vertical 用户可以调节元素的高度。
box-shadow 盒子阴影
使用 box-shadow 属性为盒子添加阴影。
语法:
box-shadow: h-shadow v-shadow blur spread color inset;
默认值: box-shadow:none 表示没有阴影
opacity 不透明度
opacity 属性能为整个元素添加透明效果, 值是 0 到 1 之间的小数, 0 是完全透明, 1 表示完
全不透明。
CSS3 新增背景属性
background-origin
作用:设置背景图的原点。
语法
1. padding-box :从 padding 区域开始显示背景图像。—— 默认值
2. border-box : 从 border 区域开始显示背景图像。
3. content-box : 从 content 区域开始显示背景图像。
background-clip
作用:设置背景图的向外裁剪的区域。
语法
1. border-box : 从 border 区域开始向外裁剪背景。 —— 默认值
2. padding-box : 从 padding 区域开始向外裁剪背景。
3. content-box : 从 content 区域开始向外裁剪背景。
4. text :背景图只呈现在文字上。
background-size
作用:设置背景图的尺寸。
语法:
1. 用长度值指定背景图片大小,不允许负值。
2. 用百分比指定背景图片大小,不允许负值。
3. auto : 背景图片的真实大小。 —— 默认值
4. contain : 将背景图片等比缩放,使背景图片的宽或高,与容器的宽或高相等,再将完整
背景图片包含在容器内,但要注意:可能会造成容器里部分区域没有背景图片。
5. cover :将背景图片等比缩放,直到完全覆盖容器,图片会尽可能全的显示在元素上,但要
注意:背景图片有可能显示不完整。—— 相对比较好的选择
backgorund 复合属性
语法:
background: color url repeat position / size origin clip
多背景图
CSS3 允许元素设置多个背景图片
CSS3新增边框属性
边框圆角
border-radius
边框外轮廓(了解)
CSS3新增文本属性
文本阴影
text-shadow
默认值: text-shadow:none 表示没有阴影
文本换行
white-space
文本溢出
text-overflow
文本修饰
text-decoration
文本描边
CSS3 新增渐变
线性渐变
径向渐变
重复渐变
web 字体
2D变换
2D位移
2D缩放
2D旋转
2D扭曲(了解)
多重变换
变换原点
3D变换
开启3D空间
设置景深
透视点位置
3D 位移
3D 旋转
3D 缩放
多重变换
背部可见性
过渡
动画
多列布局
伸缩盒模型
响应式布局
BFC
CSS预处理器
sass
less
CSS常用框架
Tailwind CSS
JavaScript
问题: 静态网页无法响应用户操作,缺乏互动性。
解决: JavaScript的出现,使得网页可以在客户端执行代码,实现与用户的互动。DOM(Document Object Model)提供了一种操作HTML和CSS的方式,允许动态更新内容和样式。
语言基础
基础语法
JavaScript的实现
JavaScript包含以下实现部分:
核心ECMAScript,可以理解为对这一规范的实现。
文档对象模型DOM,对HTML文本操作的抽象,呈现树结构。提供与网页进行交互的方法和接口
浏览器对象模型BOM,对浏览器窗口操作的抽象。提供与浏览器进行交互的方法和接口
核心ECMAScript,可以理解为对这一规范的实现。
文档对象模型DOM,对HTML文本操作的抽象,呈现树结构。提供与网页进行交互的方法和接口
浏览器对象模型BOM,对浏览器窗口操作的抽象。提供与浏览器进行交互的方法和接口
JavaScript的使用
使用<script>元素。
标签中常用的属性:
async 表示应该立即开始下载脚本
defer 脚本会被延迟到整个页面都解析完毕后再运行
charset 使用 src 属性指定的代码字符集
src 表示包含要执行的代码的外部文件
type 表示代码块中脚本语言的内容类型
async 表示应该立即开始下载脚本
defer 脚本会被延迟到整个页面都解析完毕后再运行
charset 使用 src 属性指定的代码字符集
src 表示包含要执行的代码的外部文件
type 表示代码块中脚本语言的内容类型
<script>元素的位置
不管包含的是什么代码,浏览器都会按照<script>在页面中出现的顺序加载它们。也因此,现代Web应用程序通常将所有JavaScript引用放在body元素中的页面内容最后面,减少页面的空白时间,让用户感觉加载更快。
词法结构
程序的文本
区分大小写
语句分号结尾
语句分号结尾
注释
JavaScript支持两种注释:单行注释是以//开头到一行末尾的
内容;多行注释位于/*和*/之间,可以跨行,但不能嵌套。
字面量
字面量(literal)是一种直接出现在程序中的数据值。
标识符和保留字
简单来说,标识符就是一个名字。
变量/常量
常量和变量可以让我们在程序中使用名字来引用值
常量
const声明
行为基本和let相同,但是它必须声明和初始化一起,并且不支持修改,简而言之,就是用来声明常量。
变量
let声明(老版本有用var)
let
声明作用域:
作用域是块作用域 ,而var是函数作用域
重复声明:
不允许重复声明
声明提升:
let声明不会被提升。
var
var声明作用域:
function中通过var声明的是局部变量,如果不带var,则声明的是全局变量,但是不建议这么做,不易维护。
function中通过var声明的是局部变量,如果不带var,则声明的是全局变量,但是不建议这么做,不易维护。
var声明提升:
var 关键字声明的变量会自动提升到函数作用域顶部 。 所谓的“提升”(hoist),也就是把所有变量声明都拉到函数作用域的顶部 。
var重复声明:
解构赋值
声明风格及最佳实践
①不使用var,let和const带来了明确的作用域、声明位置和值,垃圾回收的性能也更好。
②优先const,其次才是let。
②优先const,其次才是let。
数据类型
基本数据类型,原始类型,不可修改的(immutable)
Number数值
整型字面量
浮点型字面量
NaN
非数值,特殊的数值
任何对NaN的操作,都会返回NaN
String文本字符串
16位值的不可修改的有序序列
字符串字面量
把字符串放到一对匹
配的单引号、双引号或者反引号中
配的单引号、双引号或者反引号中
模板字符串字面量
作用:拼接变量和字符串
语法
反引号
用${}包住变量
布尔值
Boolean值不能用0和1区分,只有true和false。
特殊值
Undefined和null
null和undefined的区别
null是程序级别的,undefined是系统级别的,null是一个特殊的对象,undefined是一种更甚层次的不存在,在用法上两者其实没有太大的区别,不同人的使用习惯也不一样。
null是程序级别的,undefined是系统级别的,null是一个特殊的对象,undefined是一种更甚层次的不存在,在用法上两者其实没有太大的区别,不同人的使用习惯也不一样。
特殊类型
Symbol
引用数据类型,对象类型,可修改的(mutable)
任何不是数值、字符串、布尔值、符号、
null和undefined的值都是对象。
null和undefined的值都是对象。
object对象
array数组
function函数
特殊对象
全局对象
数组
Set
Map
Date
Error
☆函数和类也是特殊的对象
原始类型和对象类型的区别
typeof关键字
作用:检测变量/常量的数据类型
类型转换
隐式转换
JavaScript对待自己所需值的类型非常灵活。JavaScript会自动执行很多类型的转换
规则
+ 号两边有一个是字符串,都会转为字符串
除了+以外的算术运算符,会把数据转成数字类型
显式转换
转数字
Number(数据)
保留整数
parseInt(数据)
保留小数
parseFloat(数据)
转换与相等
JavaScript有两个操作符用于测试两个值是否相等。一个是严
格相等操作符===,如果两个值不是同一种类型,那么这个操作符就
不会判定它们相等。
但由于JavaScript在类型转换上很灵活,所以
它也定义了==操作符,这个操作符判定相等的标准相当灵活。
表达式与操作符
算术运算符
加减乘除......
自增自减
前置
++i
后置
i++
赋值运算符
关系运算符
相等
==
会进行自动类型转换
===(√)
逻辑运算符
与
逻辑与
&&
或
逻辑或
||
非
逻辑非
!
条件运算符
三元
位运算符
语句
表达式和语句的区别
表达式是可以被求值的代码,它可以写在赋值语句的右侧
语句不一定有值
代码块
条件语句
循环语句
数组
数组也是对象的一种。
创建数组
var array = [1,44,33];
数组操作
查
增
push()
unshift()
删
pop()
shift()
splice()
改
arr[i]=?
函数
函数的声明
var sum = function(a,b){return a+b};
function sum(a,b){
return a+b;
}
return a+b;
}
参数的默认值
function sum(a=0,b,=0){
return a+b;
}
return a+b;
}
形参个数可以和实参个数不一致
函数也是一个对象
函数的调用
var result = sum(123,456);
函数返回值
以上面的:
function sum(a,b){
return a+b;
}
为例
sum(0,0)的调用本质是执行了sum()=0+0,return将返回值返回给调用者
function sum(a,b){
return a+b;
}
为例
sum(0,0)的调用本质是执行了sum()=0+0,return将返回值返回给调用者
作用域
全局
局部(函数作用域)
匿名函数
函数表达式
let fn = function (){}
立即执行函数
(function (){}) ()
(function (){}())
对象
什么是对象
对象是一个包含相关数据和方法的无序的数据集合
对象的创建方式
let persion = new Object();
let person = {};
使用 Object.create 方法
对象的组成
属性
对象属性的访问
对象.属性名(点表示法)
对象['属性名'](括号表示法)
属性的删除
delete 对象.属性名
方法
对象的遍历
for (let k in obj){
console.log(obj[k])
}
console.log(obj[k])
}
for...in 循环 该方法依次访问一个对象及其原型链中所有可枚举的属性
Object.keys(o) 该方法返回对象 o 自身包含(不包括原型中)的所有可枚举属性的名称的数组
Object.getOwnPropertyNames(o) 该方法返回对象 o 自身包含(不包括原型中)的所有属性 (无论是否可枚举) 的名称的数组
内置对象
Math
random,生成(0,1]之间的随机数
ceil,向上取整数
floor,向下取整
max,找最大
min,找最小
pow,幂运算
abs,绝对值
日期对象
实例化
new Date()
方法
getFullYeat
getMonth
getDate
getHours
getMinutes
getSeconds
时间戳
Web存储
localStorage
用于长久保存整个网站的数据,保存的数据没有过期时间,直到手动去除。
localStorage对象
sessionStorage
用于临时保存同一窗口(或标签页)的数据,在关闭窗口或标签页之后将会删除这些数据。
sessionStorage对象
Array
String
Number
Error
WebAPIs
操作DOM
DOM,全称Document Object Model文档对象模型。
获取元素
获取DOM对象
通过css选择器获取(√)
document.querySelector('CSS选择器')
document.querySelectorAll('CSS选择器')
其他
document.getElementById()
document.getElementByTagName()
document.getElementByClassName()
操作元素内容
对象.innerText属性
对象.innerHTML属性
操作元素属性
对象.属性=值
事件基础
元素对象.addEventListener('事件类型', 要执行的函数)
事件类型
鼠标事件
click
mouseenter
mouseleave
焦点事件
focus
blur
键盘事件
Keydown
Keyup
文本事件
input
事件对像
获取事件对象
事件绑定的回调函数的第一个参数就是事件对象
常用属性
type
clientX
clientY
offsetX
offsetY
key
环境对象
指函数内部特殊的变量this,代表当前函数运行时所处的环境
作用:
弄清this的指向,可以让我们的代码更简洁
函数的调用方式不同,this指代的对象也不同
谁调用,this就是谁是一个粗略的判断规则
直接调用函数,相当于window.函数,所以this是window
回调函数
事件进阶
事件流
事件流与两个阶段
事件捕获
事件冒泡
阻止冒泡
解绑事件
事件委托
其他事件
页面加载事件
页面滚动事件
元素尺寸与位置事件
节点操作
DOM节点
查找节点
增加节点
创建节点
追加节点
删除节点
操作BOM
BOM(浏览器对象模型)提供了很多对象,用于访问浏览器的功能,这些
功能与任何网页内容无关。
功能与任何网页内容无关。
window对象
location对象
navigator对象
history对象
screen对象
本地存储
进阶
作用域&解构&箭头函数
作用域和作用域链
局部作用域
函数作用域
在函数内部声明的变量只能在函数内部被访问,外部无法直接访问。
块作用域
在 JavaScript 中使用 `{}` 包裹的代码称为代码块,代码块内部声明的变量外部将【有可能】无法被访问。
全局作用域
作用域链
js垃圾回收机制和算法
闭包
闭包是JS一个非常重要的特性,这意味着
当前作用域总是能够访问外部作用域中的
变量。因为函数是JS中唯一拥有自身作用
域的结构,因此闭包的创建依赖于函数。
也可以将闭包的特征理解为,其相关的局
部变量在函数调用结束之后将会继续存在。
1.怎么理解闭包?
闭包 = 内层函数 + 外层函数的变量
2.闭包的作用?
封闭数据,实现数据私有,外部也可以访问函数内部的变量
闭包很有用,因为它允许将函数与其所操作的某些数据(环境)关联起来
3.闭包可能引起的问题?
内存泄漏
变量和函数提升
ES6箭头函数
箭头函数不会创建自己的this,它只会从自己的作用域链的上一层沿用this
解构赋值
数组解构
对象解构
构造函数&数据常用函数
构造函数
是用于生成对象的函数,像之前调用的Object()就是一个构
造函数。如果一个函数使用 `new` 关键字调用,那么这个函数就是构造函数
造函数。如果一个函数使用 `new` 关键字调用,那么这个函数就是构造函数
创建一个构造函数
function MyClass(x,y) {
this.x = x;
this.y = y;
}
调用构造函数
构造函数通过 new 关键字来调用,new 关键字会新创建一个对象并返回。
当你调用构造函数时,它将:
创建一个新对象
将 this 绑定到新对象,以便你可以在构造函数代码中引用 this
运行构造函数中的代码
返回新对象
属性的访问
constructor
每个对象中都有一个constructor属性,它引用了当前对象的构造函数
内置构造函数
Object
Array
包装类型
String
Number
函数内部属性
arguments
是一个数组,用于保存函数的参数
还有一个属性callee来表示当前函数
this
“this”的含义
关键字 this 指向了当前代码运行时的对象
在最外层代码中,this 引用的是全局对象
在函数内,this 根据函数调用方式的不同
而有所不同
面向对象
原型继承/原型链
JavaScript 中所有的对象都有一个内置属性,称为它的 prototype(原型)。它本身是一个对象,故原型对象也会有它自己的原型,逐渐构成了原型链。原型链终止于拥有 null 作为其原型的对象上。
最典型的原型中的属性就是toString()函数,实际上我们的对象
中并没有定义这个函数,但是却可以调用,那是因为这个函数
存在于Object对应的原型中
设置原型
使用构造函数
在 JavaScript 中,所有的函数都有一个名为 prototype 的属性。当你调用一个函数作为构造函数时,这个属性被设置为新构造对象的原型(按照惯例,在名为 __proto__ 的属性中)
使用 Object.create
Object.create() 方法创建一个新的对象,并允许你指定一个将被用作新对象原型的对象
获取原型的方法
Object.getPrototypeOf(对象)
对象.__proto__
对象. constructor.prototype
原型就是一个对象,和其他对象没有任何区别,可以通过构造
函数来获取原型对象。
构造函数. prototype
需要注意的是prototype属性只存在于函数对象中,其他对象
是没有prototype属性的。
每一个对象都有原型,包括原型对象也有原型。特殊的是
Object的原型对象没有原型。
原型链
基于原型对象的继承使得不同构造函数的原型对象关联在一起,并且这种关联的关系是一种链状的结构,我们将原型对
象的链状结构关系称为原型链
象的链状结构关系称为原型链
① 当访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有该属性。
② 如果没有就查找它的原型(也就是 __proto__指向的 prototype 原型对象)
③ 如果还没有就查找原型对象的原型(Object的原型对象)
④ 依此类推一直找到 Object 为止(null)
⑤ __proto__对象原型的意义就在于为对象成员查找机制提供一个方向,或者说一条路线
⑥ 可以使用 instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上
② 如果没有就查找它的原型(也就是 __proto__指向的 prototype 原型对象)
③ 如果还没有就查找原型对象的原型(Object的原型对象)
④ 依此类推一直找到 Object 为止(null)
⑤ __proto__对象原型的意义就在于为对象成员查找机制提供一个方向,或者说一条路线
⑥ 可以使用 instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上
高阶技巧
深浅拷贝
深拷贝
浅拷贝
异常处理
`throw`
1. throw 抛出异常信息,程序也会终止执行
2. throw 后面跟的是错误提示信息
3. Error 对象配合 throw 使用,能够设置更详细的错误信息
2. throw 后面跟的是错误提示信息
3. Error 对象配合 throw 使用,能够设置更详细的错误信息
try ... catch
1. `try...catch` 用于捕获错误信息
2. 将预估可能发生错误的代码写在 `try` 代码段中
3. 如果 `try` 代码段中出现错误后,会执行 `catch` 代码段,并截获到错误信息
处理this
`this` 是 JavaScript 最具“魅惑”的知识点,不同的应用场合 `this` 的取值可能会有意想不到的结果
普通函数
**普通函数**的调用方式决定了 `this` 的值,即【谁调用 `this` 的值指向谁】
箭头函数
**箭头函数**中的 `this` 与普通函数完全不同,也不受调用方式的影响,事实上箭头函数中并不存在 `this` !箭头函数中访问的 `this` 不过是箭头函数所在作用域的 `this` 变量。
改变this指向
call
使用 `call` 方法调用函数,同时指定函数中 `this` 的值
apply
bind
`bind` 方法并**不会调用函数**,而是创建一个指定了 `this` 值的新函数
模块
基于类、对象和闭包的模块
Node中模块
导入
require
导出
module.exports
exports
ES6中模块
导入
import { } from ...
导出
分别导出
export直接加在具体要导出的代码上
统一导出
export { }
默认导出
export default { }
迭代器与生成器
迭代器原理
生成器
异步JavaScript
Promise
Promise是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大
从语法上来说: Promise 是一个构造函数
从功能上来说: promise 对象用来封装一个异步操作并可以获取其成功/ 失败的结果值
promise 的状态
pending 未决定的
resolved / fullfilled 成功
rejected 失败
方法
then
用于处理 Promise 成功状态的回调函数。
catch
用于处理 Promise 失败状态的回调函数
finally
无论 Promise 是成功还是失败,都会执行的回调函数
promise的基本流程
async和await
异步迭代
JSON
元编程
客户端存储
Jquery
问题: 随着Web应用的复杂性增加,直接操作DOM变得繁琐且难以管理,代码结构不清晰,浏览器兼容性问题需要开发者编写大量的兼容性代码。。
解决: jQuery简化了DOM操作和事件处理,提高了开发效率。内置的浏览器兼容性处理,减少了开发者的负担。
jQuery 初识
优秀的 JS 函数库
Write Less,Do More!!
引入jQuery库
<script src="js/jquery-3.6.0.js"></script>
使用jQuery
jQuery核心函数:$/jQuery
当函数用:$(xxx)
当对象用:$.xxx()
jQuery核心对象:执行$()返回的对象
使用jQuery对象:$obj.xxx()
Ajax
问题: 页面每次需要更新内容时都要重新加载整个页面,用户体验不佳。
解决: AJAX技术允许网页在不重新加载整个页面的情况下与服务器进行异步通信,动态更新部分内容,提高了用户体验。JSON(JavaScript Object Notation)作为一种轻量级的数据交换格式,简化了客户端与服务器之间的数据传输。
原生 AJAX
通过 AJAX 可以在浏览器中向服务器发送异步请求,最大的优势:无刷新获取数据
核心对象
XMLHttpRequest,AJAX 的所有操作都是通过该对象进行的。
使用步骤
创建 XMLHttpRequest 对象
let xhr =new XMLHttpRequest();
设置请求信息
xhr.open(method, url);
发送请求
xhr.send(body)
接收响应
xhr.onreadystatechange = function (){
if(xhr.readyState == 4 && xhr.status == 200){
var text = xhr.responseText;
console.log(text);
}
}
if(xhr.readyState == 4 && xhr.status == 200){
var text = xhr.responseText;
console.log(text);
}
}
jQuery 中的 AJAX
$.get(url, [data], [callback], [type])
url:请求的 URL 地址。
data:请求携带的参数。
callback:载入成功时回调函数。
type:设置返回内容格式,xml, html, script, json, text, _default。
data:请求携带的参数。
callback:载入成功时回调函数。
type:设置返回内容格式,xml, html, script, json, text, _default。
$.post(url, [data], [callback], [type])
url:请求的 URL 地址。
data:请求携带的参数。
callback:载入成功时回调函数。
type:设置返回内容格式,xml, html, script, json, text, _default。
data:请求携带的参数。
callback:载入成功时回调函数。
type:设置返回内容格式,xml, html, script, json, text, _default。
Axios
axios 是什么?
前端最流行的 ajax 请求库
axios 特点
基于 xhr + promise 的异步 ajax 请求库
浏览器端/node 端都可以使用
支持请求/响应拦截器
支持请求取消
请求/响应数据转换
批量发送多个请求
axios 常用语法
axios(config): 通用/最本质的发任意类型请求的方式
axios(url[, config]): 可以只指定 url 发 get 请求
axios.request(config): 等同于 axios(config)
axios.get(url[, config]): 发 get 请求
axios.delete(url[, config]): 发 delete 请求
axios.post(url[, data, config]): 发 post 请求
axios.put(url[, data, config]): 发 put 请求
axios.defaults.xxx: 请求的默认全局配置
axios.interceptors.request.use(): 添加请求拦截器
axios.interceptors.response.use(): 添加响应拦截器
axios.create([config]): 创建一个新的 axios(它没有下面的功能)
axios.Cancel(): 用于创建取消请求的错误对象
axios.CancelToken(): 用于创建取消请求的 token 对象
axios.isCancel(): 是否是一个取消请求的错误
axios.all(promises): 用于批量执行多个异步请求
axios.spread(): 用来指定接收所有成功数据的回调函数的方法
原理图
难点语法的理解和使用
axios.create(config)
根据指定配置创建一个新的 axios, 也就就每个新 axios 都有自己的配置
新 axios 只是没有取消请求和批量发请求的方法, 其它所有语法都是一致的
为什么要设计这个语法?
拦截器函数/ajax 请求/请求的回调函数的调用顺序
说明: 调用 axios()并不是立即发送 ajax 请求, 而是需要经历一个较长的流程
流程: 请求拦截器2 => 请求拦截器1 => 发ajax请求 => 响应拦截器1 => 响应拦截器 2 => 请求的回调
注意: 此流程是通过 promise 串连起来的, 请求拦截器传递的是 config, 响应 拦截器传递的是 response
默认配置
//默认配置
axios.defaults.method = 'GET';//设置默认的请求类型为 GET
axios.defaults.baseURL = 'http://localhost:3000';//设置基础 URL
axios.defaults.params = {id:100};
axios.defaults.timeout = 3000;//
btns[0].onclick = function(){
axios({
url: '/posts'
}).then(response => {
console.log(response);
})
}
Axios的难点问题
axios 与 Axios 的关系
从语法上来说: axios 不是 Axios 的实例
从功能上来说: axios 是 Axios 的实例
axios 是 Axios.prototype.request 函数 bind()返回的函数
axios 作为对象有 Axios 原型对象上的所有方法, 有 Axios 对象上所有属性
instance 与 axios 的区别?
相同: (1) 都是一个能发任意请求的函数: request(config) (2) 都有发特定请求的各种方法: get()/post()/put()/delete() (3) 都有默认配置和拦截器的属性: defaults/interceptors
不同: (1) 默认配置很可能不一样 (2) instance 没有 axios 后面添加的一些方法: create()/CancelToken()/all()
axios运行的整体流程
整体流程: request(config) ==> dispatchRequest(config) ==> xhrAdapter(config)
request(config): 将请求拦截器 / dispatchRequest() / 响应拦截器 通过 promise 链串连起来, 返回 promise
dispatchRequest(config): 转换请求数据 ===> 调用 xhrAdapter()发请求 ===> 请求返回后转换响应数 据. 返回 promise
xhrAdapter(config): 创建 XHR 对象, 根据 config 进行相应设置, 发送特定请求, 并接收响应数据, 返回 promise
axios 的请求/响应拦截器是什么?
请求拦截器: Ⅰ- 在真正发送请求前执行的回调函数 Ⅱ- 可以对请求进行检查或配置进行特定处理 Ⅲ- 成功的回调函数, 传递的默认是 config(也必须是) Ⅳ- 失败的回调函数, 传递的默认是 error
响应拦截器 Ⅰ- 在请求得到响应后执行的回调函数 Ⅱ- 可以对响应数据进行特定处理 Ⅲ- 成功的回调函数, 传递的默认是 response Ⅳ- 失败的回调函数, 传递的默认是 error
axios 的请求/响应数据转换器是什么?
请求转换器: 对请求头和请求体数据进行特定处理的函数
响应转换器: 将响应体 json 字符串解析为 js 对象或数组的函数
如何取消未完成的请求?
当配置了 cancelToken 对象时, 保存 cancel 函数 (1) 创建一个用于将来中断请求的 cancelPromise (2) 并定义了一个用于取消请求的 cancel 函数 (3) 将 cancel 函数传递出来
调用 cancel()取消请求 (1) 执行 cacel 函数, 传入错误信息 message (2) 内部会让 cancelPromise 变为成功, 且成功的值为一个 Cancel 对象 (3) 在 cancelPromise 的成功回调中中断请求, 并让发请求的 proimse 失败, 失败的 reason 为 Cancel 对象
axios发送请求过程详解
整体流程: request(config) ==> dispatchRequest(config) ==> xhrAdapter(config)
request(config): 将请求拦截器 / dispatchRequest() / 响应拦截器 通过 promise 链串连起来, 返回 promise
dispatchRequest(config): 转换请求数据 ===> 调用 xhrAdapter()发请求 ===> 请求返回后转换响应数 据. 返回 promise
xhrAdapter(config): 创建 XHR 对象, 根据 config 进行相应设置, 发送特定请求, 并接收响应数据, 返回 promise
Node.js
介绍
简单的说 Node.js 就是运行在服务端的 JavaScript。是一个基于 Chrome JavaScript 运行时建立的一个平台,是一个事件驱动 I/O 服务端 JavaScript 环境,基于 Google 的 V8 引擎
Buffer
概念
Buffer 是一个类似于数组的 对象 ,用于表示固定长度的字节序列
Buffer 本质是一段内存空间,专门用来处理 二进制数据 。
特点
大小固定
性能较好
使用
创建 Buffer
Buffer.alloc
Buffer.allocUnsafe
Buffer.from
Buffer 与字符串的转化
借助 toString 方法将 Buffer 转为字符串
Buffer 的读写
Buffer 可以直接通过 [] ,即下标的方式对数据进行处理
模块
_fs
标准的文件操作API
引入
const fs = require('fs');
文件写入
fs.writeFile(file, data[, options], callback)
异步写入
fs.writeFileSync(file, data[, options])
同步写入
fs.appendFile(file, data[, options], callback)
fs.appendFileSync(file, data[, options])
fs.appendFileSync(file, data[, options])
追加写入
fs.createWriteStream(path[, options])
流式写入
文件读取
fs.readFile(path[, options], callback)
异步读取
fs.readFileSync(path[, options])
同步读取
fs.createReadStream(path[, options])
流式读取
文件移动与重命名
fs.rename(oldPath, newPath, callback)
fs.renameSync(oldPath, newPath)
文件删除
fs.unlink(path, callback)
fs.unlinkSync(path)
文件夹操作
创建文件夹
fs.mkdir(path[, options], callback)
fs.mkdirSync(path[, options])
读取文件夹
fs.readdir(path[, options], callback)
fs.readdirSync(path[, options])
删除文件夹
fs.rmdir(path[, options], callback)
fs.rmdirSync(path[, options])
查看资源状态
fs.stat(path[, options], callback)
fs.statSync(path[, options])
_path
提供了处理和转换文件路径的工具
引入
const path = require('path');
常用的几个 API
path.resolve
拼接规范的绝对路径 常用
path.sep
获取操作系统的路径分隔符
path.parse
解析路径并返回对象
path.basename
获取路径的基础名称
path.dirname
获取路径的目录名
path.extname
获得路径的扩展名
_http
http 模块主要用于搭建 HTTP 服务端和客户端
引入
const http = require('http');
创建 server 服务
const server = http.createServer((request, response) => {
response.end('Hello HTTP server');
});
启动服务
server.listen(9000, () => {
console.log('服务已经启动, 端口 9000 监听中...');
});
获取 HTTP 请求报文
想要获取请求的数据,需要通过 request 对象
_util
提供常用函数的集合,用于弥补核心 JavaScript 的功能 过于精简的不足
util.callbackify
util.inherits
util.inspect
util.isArray(object)
util.isRegExp(object)
util.isDate(object)
_url
解析 URL 中的参数
url.parse
_os
提供基本的系统操作函数
_net
用于底层的网络通信。提供了服务端和客户端的的操作
_dns
用于解析域名
_domain
简化异步代码的异常处理,可以捕捉处理try catch无法捕捉的
全局对象
__dirname
__dirname 与 require 类似,都是 Node.js 环境中的'全局'变量
__dirname 保存着 当前文件所在目录的绝对路径 ,可以使用 __dirname 与文件名拼接成绝对路径
__filename
__filename 表示当前正在执行的脚本的文件名。它将输出文件所在位置的绝对路径,且和命令行参数所指定的文件名不一定相同。 如果在模块中,返回的值是模块文件的路径。
setTimeout(cb, ms)
setTimeout(cb, ms) 全局函数在指定的毫秒(ms)数后执行指定函数(cb)。:setTimeout() 只执行一次指定函数。
返回一个代表定时器的句柄值
返回一个代表定时器的句柄值
clearTimeout(t)
clearTimeout( t ) 全局函数用于停止一个之前通过 setTimeout() 创建的定时器。 参数 t 是通过 setTimeout() 函数创建的定时器。
setInterval(cb, ms)
setInterval(cb, ms) 全局函数在指定的毫秒(ms)数后执行指定函数(cb)。
console
控制台标准输出
process
用于描述当前Node.js 进程状态的对象,提供了一个与操作系统的简单接口
模块化
模块暴露数据的方式
module.exports = value
exports.name = value
导入(引入)模块
在模块中使用 require 传入文件路径即可引入文件
包管理工具
npm
yarn
pnpm
cnpm
NVM
来管理 node 版本的工具,方便切换不同版本的
Node.js
Node.js
常用命令
nvm list available
显示所有可以下载的 Node.js 版本
nvm list
显示已安装的版本
nvm install 18.12.1
安装 18.12.1 版本的 Node.js
nvm install latest
安装最新版的 Node.js
nvm uninstall 18.12.1
删除某个版本的 Node.js
nvm use 18.12.1
切换 18.12.1 的 Node.js
Express框架
介绍
express 是一个封装好的工具包,封装了很多功能,便于我们开发 WEB 应用
使用
安装依赖
npm i express
引入
const express = require('express');
创建应用对象
const app = express();
创建路由规则
app.get('/home', (req, res) => {
res.end('hello express server');
});
监听端口 启动服务
app.listen(3000, () =>{
console.log('服务已经启动, 端口监听为 3000...');
});
启动
node <文件名>
express 路由
定义
路由确定了应用程序如何响应客户端对特定端点的请求
使用
一个路由的组成有 请求方法 , 路径 和 回调函数 组成
app.<method>(path,callback)
获取请求参数
console.log(req.query); // 『相对重要』
获取路由参数
app.get('/:id.html', (req, res) => {
res.send('商品详情, 商品 id 为' + req.params.id);
});
获取请求体
安装
npm i body-parser
导入
const bodyParser = require('body-parser');
获取中间件函数
//处理 querystring 格式的请求体
let urlParser = bodyParser.urlencoded({extended:false}));
//处理 JSON 格式的请求体
let jsonParser = bodyParser.json();
设置路由中间件,然后使用 request.body 来获取请求体数据
中间件
什么是中间件
本质是一个回调函数
中间件的作用
使用函数封装公共操作,简化代码
中间件的类型
全局中间件
路由中间件
响应设置
Router
什么是 Router
express 中的 Router 是一个完整的中间件和路由系统,可以看做是一个小型的 app 对象。
Router 作用
对路由进行模块化,更好的管理路由
Router 使用
const router = express.Router();
router.get('/', (req, res) => {
res.send('首页');
})
module.exports = router;
引入子路由文件
const homeRouter = require('./routes/homeRouter');
const homeRouter = require('./routes/homeRouter');
app.use(homeRouter);
EJS模板引擎
TypeScript
个人理解
JavaScript的超集,为JavaScript提供类型约束、代码逻辑漏洞等静态类型检查,提高程序的健壮性,TS最终会经由编译器编译成JS执行。
TypeScript的基本类型
类型声明
语法
let 变量: 类型;
let 变量: 类型 = 值;
function fn(参数: 类型, 参数: 类型): 类型{
...
}
let 变量: 类型 = 值;
function fn(参数: 类型, 参数: 类型): 类型{
...
}
联合类型
联合类型(Union Types)表示取值可以为多种类型中的一种。
let myFavoriteNumber: string | number;
类型推断
当对变量的声明和赋值是同时进行的,TS编译器会自动判断变量的类型
类型
number
boolean
string
字面量
let gender: '男'|'⼥'
let b: 100
any
表示任意类型,一旦声明为any,表示放弃对该变量的类型检查
unknown
类型安全的any,unknown会强制要求开发者在使用之前进行类型检查
void
没有值(或undefined)
never
不能是任何值。不能有值。多用于限制函数的返回值
object(没啥用)
任意的JS对象
array
任意JS数组
tuple
元组,TS新增类型,固定长度数组
let x: [string, number];
x = ["hello", 10];
x = ["hello", 10];
enum
枚举,TS中新增类型,定义一组命名常量
enum Color {
Red,
Green,
Blue,
}
Red,
Green,
Blue,
}
类型断言(手动指定一个值的类型)
语法
值 as 类型
<类型>值
示例
let someValue: unknown = "this is a string";
let strLength: number = (someValue as string).length;
let strLength: number = (someValue as string).length;
let someValue: unknown = "this is a string";
let strLength: number = (<string>someValue).length;
let strLength: number = (<string>someValue).length;
类型别名(自定义类型)
type Name = string;
let 变量: Name;
let 变量: Name;
type Ty = {
......
}
let 变量: Ty
......
}
let 变量: Ty
面向对象
类
class 类名 {
属性名: 类型;
constructor(参数: 类型){
this.属性名 = 参数;
}
方法名(){
....
}
}
属性名: 类型;
constructor(参数: 类型){
this.属性名 = 参数;
}
方法名(){
....
}
}
const p = new Person('孙悟空', 18);
p.sayHello();
p.sayHello();
构造函数
使用constructor定义一个构造器方法
在TS中只能有一个构造器方法!
子类继承父类时,必须调用父类的构造方法(如果子类中也定义了构造方法)!
封装
静态属性(static)
声明为static的属性或方法不再属于实例,而是属于类的属性;
只读属性(readonly)
如果在声明属性时添加一个readonly,则属性便成了只读属性无法修改
权限修饰符
public(默认值),可以在类、子类和对象中修改
protected ,可以在类、子类中修改
private ,可以在类中修改
属性存取器
对于一些不希望被任意修改的属性,可以将其设置为private
直接将其设置为private将导致无法再通过对象修改其中的属性
我们可以在类中定义一组读取、设置属性的方法,这种对属性读取或设置的属性被称为属性的存取器
读取属性的方法叫做setter方法,设置属性的方法叫做getter方法
直接将其设置为private将导致无法再通过对象修改其中的属性
我们可以在类中定义一组读取、设置属性的方法,这种对属性读取或设置的属性被称为属性的存取器
读取属性的方法叫做setter方法,设置属性的方法叫做getter方法
class Person{
private _name: string;
constructor(name: string){
this._name = name;
}
get name(){
return this._name;
}
set name(name: string){
this._name = name;
}
}
const p1 = new Person('孙悟空');
// 实际通过调用getter方法读取name属性
console.log(p1.name);
// 实际通过调用setter方法修改name属性
p1.name = '猪八戒';
private _name: string;
constructor(name: string){
this._name = name;
}
get name(){
return this._name;
}
set name(name: string){
this._name = name;
}
}
const p1 = new Person('孙悟空');
// 实际通过调用getter方法读取name属性
console.log(p1.name);
// 实际通过调用setter方法修改name属性
p1.name = '猪八戒';
继承
extends
重写
发生继承时,如果子类中的方法会替换掉父类中的同名方法,这就称为方法的重写
super
抽象类
抽象类是专门用来被其他类所继承的类,它只能被其他类所继承不能用来创建实例
使用abstract开头的方法叫做抽象方法,抽象方法没有方法体只能定义在抽象类中,继承抽象类时抽象方法必须要实现;
abstract class Animal{
abstract run(): void;
bark(){
console.log('动物在叫~');
}
}
class Dog extends Animals{
run(){
console.log('狗在跑~');
}
}
abstract run(): void;
bark(){
console.log('动物在叫~');
}
}
class Dog extends Animals{
run(){
console.log('狗在跑~');
}
}
接口
接口的作用类似于抽象类,不同点在于:接口中的所有方法和属性都是没有实值的,换句话说接口中的所有方法都是抽象方法;
对接口的理解
使用interface来定义一种约束,让数据的结构满足约束的格式
关键字
interface
implements
接口重名的情况
会对内部定义的属性进行合并
任意key
[propName : string] : any
效果
可选key
age?:string
效果
顾名思义,这个值你可以写也可以不写
readOnly
readonly age : string
效果
也是顾名思义,只可读
实现接口
interface Person{
name: string;
sayHello():void;
}
class Student implements Person{
constructor(public name: string) {
}
sayHello() {
console.log('大家好,我是'+this.name);
}
}
name: string;
sayHello():void;
}
class Student implements Person{
constructor(public name: string) {
}
sayHello() {
console.log('大家好,我是'+this.name);
}
}
(检查对象类型)
interface Person{
name: string;
sayHello():void;
}
function fn(per: Person){
per.sayHello();
}
fn({name:'孙悟空', sayHello() {console.log(`Hello, 我是 ${this.name}`)}});
name: string;
sayHello():void;
}
function fn(per: Person){
per.sayHello();
}
fn({name:'孙悟空', sayHello() {console.log(`Hello, 我是 ${this.name}`)}});
泛型
定义一个函数或类时,有些情况下无法确定其中要使用的具体类型(返回值、参数、属性的类型不能确定);
此时泛型便能够发挥作用;
此时泛型便能够发挥作用;
泛型函数
创建泛型函数
function test<T>(arg: T): T{
return arg;
}
return arg;
}
使用泛型函数
test(10)
test<number>(10)
函数中声明多个泛型
function test<T, K>(a: T, b: K): K{
return b;
}
test<number, string>(10, "hello");
return b;
}
test<number, string>(10, "hello");
泛型类
class MyClass<T>{
prop: T;
constructor(prop: T){
this.prop = prop;
}
}
prop: T;
constructor(prop: T){
this.prop = prop;
}
}
泛型继承
interface MyInter{
length: number;
}
function test<T extends MyInter>(arg: T): number{
return arg.length;
}
length: number;
}
function test<T extends MyInter>(arg: T): number{
return arg.length;
}
namespace命名空间
我们在工作中无法避免全局变量造成的污染,TypeScript提供了namespace 避免这个问题出现
概述
内部模块,主要用于组织代码,避免命名冲突
命名空间内的类默认私有
通过 export 暴露
通过 namespace 关键字定义
示例
namespace a {
export const Time: number = 1000
export const fn = <T>(arg: T): T => {
return arg
}
fn(Time)
}
a.Time
export const Time: number = 1000
export const fn = <T>(arg: T): T => {
return arg
}
fn(Time)
}
a.Time
声明文件 declare
当使用第三方库时,我们需要引用它的声明文件,才能获得对应的代码补全、接口提示等功能
AngularJS
问题:
● 随着Web应用的复杂性增加,直接操作DOM变得难以管理。
● 缺乏组织代码的方式,导致代码难以维护。
解决:
● 引入MVC(Model-View-Controller)架构,将应用逻辑和视图分离。
● 提供双向数据绑定,简化了数据和视图的同步。
● 提供依赖注入(Dependency Injection),提高代码的可测试性和可维护性。
特点:
● 模板语法强大,允许在HTML中直接使用JavaScript表达式。
● 内置许多常用功能,如表单验证、路由等。
Angular(Angular 2+)
问题:
● AngularJS架构过于复杂,不易于扩展和维护。
● 需要提高性能和开发体验。
解决:
● 完全重写了框架,采用现代化的开发理念和工具。
● 引入基于TypeScript的强类型支持,提高代码的可靠性。
● 提供更好的性能和更简洁的API。
特点:
● 基于组件的架构,支持依赖注入。
● 强大的CLI工具,提升开发体验。
● 丰富的官方支持库和工具,如路由、表单处理、HTTP请求等。
组件
理解组件
组件是任何 Angular 应用程序的基础构建块。每个组件包括三个部分
TypeScript 类
HTML 模板
CSS 样式
定义组件
核心属性
@Component
包含一些配置
HTML 模板 template
控制将渲染到 DOM
CSS 选择器 selector
定义组件在 HTML 中如何使用
CSS样式 styles
TypeScript 类
包含管理状态、处理用户输入或从服务器获取数据等行为
例:
// todo-list-item.component.ts
@Component({
selector: 'todo-list-item',
template: `
<li>(TODO) Read Angular Essentials Guide</li>
`,
})
export class TodoListItem {
/* Component behavior is defined in here */
}
@Component({
selector: 'todo-list-item',
template: `
<li>(TODO) Read Angular Essentials Guide</li>
`,
})
export class TodoListItem {
/* Component behavior is defined in here */
}
使用组件
将组件导入到文件中
将其添加到组件的 imports 数组中
在 template 中使用该组件的选择器
例:
// todo-list.component.ts
import {TodoListItem} from './todo-list-item.component.ts';
@Component({
standalone: true,
imports: [TodoListItem],
template: `
<ul>
<todo-list-item></todo-list-item>
</ul>
`,
})
export class TodoList {}
定义组件的数据(即状态)和行为
定义状态
组件内声明字段
更新状态
在组件类中定义方法,使用 this 关键字访问各种类字段
组件生命周期
创建
检测
渲染
销毁
组件通信
使用 @Input 进行组件通信
Angular 使用一个称为 Input 的概念。这类似于其他框架中的 props。要创建一个 Input 属性,使用 @Input 装饰器。
使用 @Output 进行组件通信
模板语法
文本插值
默认情况下,插值使用双花括号 {{ 和 }} 作为定界符
模板语句
属性绑定
要绑定到元素的属性,请将其放在方括号 [] 中,这会将此属性标识为目标属性。
事件绑定
双向绑定
Angular 的双向绑定语法是方括号和圆括号的组合 [()]。[()] 语法合并了 [] 的属性绑定和 () 的事件绑定
控制流
@if
@else
@for
@switch
管道
依赖注入
路由
添加用于路由的两个组件
ng generate component first
ng generate component second
定义路由
将路由导入 app.config.ts 并添加到 provideRouter 函数中。
export const appConfig: ApplicationConfig = {
providers: [provideRouter(routes)]
};
表单
响应式表单
[formControl] 指令将显式创建的 FormControl 实例与视图中的特定表单元素进行连接,使用内部值访问器
模板驱动表单
指令 NgModel 为给定的表单元素创建和管理一个 FormControl 实例
Angluar Cli
安装
npm install -g @angular/cli
创建工作区
ng new my-app
运行
ng serve --open
React
问题:
● 双向数据绑定在大型应用中容易导致数据流混乱。
● 操作DOM依然是性能瓶颈。
解决:
● 引入单向数据流,简化了数据管理。
● 提出虚拟DOM概念,提升了DOM操作的性能。
● 使用组件化开发模式,提高了代码的重用性和可维护性。
特点:
● JSX语法,允许在JavaScript中直接编写类似HTML的模板。
● 强调组件化,鼓励将UI拆分成可重用的组件。
● 丰富的生态系统,如React Router、Redux等。
介绍
React 是一款主要创建交互式 UI组件 js 库 [Vve 则提供一站式解决方案框架]
快速入门
基于脚手架创建
vite
框架
next.js
创建和嵌套组件
React 应用程序是由 组件 组成的。一个组件是 UI(用户界面)的一部分,它拥有自己的逻辑和外观。
简而言之,React UI组件 就是 HTML 片段的特殊形式、React 以函数封装组件且固定其格式
简而言之,React UI组件 就是 HTML 片段的特殊形式、React 以函数封装组件且固定其格式
React 组件是返回标签的 JavaScript 函数
通过函数声明组件
export default function MyButton() {
return (
<button>I'm a button</button>
);
}
return (
<button>I'm a button</button>
);
}
使用组件
export default function MyApp() {
return (
<div>
<h1>Welcome to my app</h1>
<MyButton />
</div>
);
}
return (
<div>
<h1>Welcome to my app</h1>
<MyButton />
</div>
);
}
PS:
React 组件必须以大写字母开头,而 HTML 标签则必须是小写字母
添加标签和样式
JSX标签语法
JSX是JavaScript的拓展,支持JS和HTML的混写。JSX 比 HTML 更加严格。你必须闭合标签,如 <br />。你的组件也不能返回多个 JSX 标签。你必须将它们包裹到一个共享的父级中,比如 <div>...</div> 或使用空的 <>...</> 包裹
为组件添加标签属性/样式
前情:
我们之前说过,jsx是js的拓展,所以不可避免的会存在js和html存在一些关键词重名的冲突情况,为此,jsx使用了属性别名来解决这个问题,例如下述两个属性
cclass->lassName
for->htmlFor
在 React 中,你可以使用 className 来指定一个 CSS 的 class。它与 HTML 的 class 属性的工作方式相同
<img className="avatar" />
.avatar {
border-radius: 50%;
}
border-radius: 50%;
}
在组件显示数据
React 使用 单 {} 绑定变量、当 {{}}特殊情境时、内花括号为 JS 对象
return (
<h1>
{username}
</h1>
);
<h1>
{username}
</h1>
);
拓展:
在 JSX 的大括号内调用 JavaScript 函数
在 JSX 的大括号内使用 JavaScript 对象
渲染条件和列表
条件渲染
React 没有特殊的语法来编写条件语句,因此你使用的就是普通的 JavaScript 代码
列表渲染
依赖 JavaScript 的特性,例如 for 循环 和 array 的 map() 函数 来渲染组件列表
示例
假设你有一个产品数组
const products = [
{ title: 'Cabbage', id: 1 },
{ title: 'Garlic', id: 2 },
{ title: 'Apple', id: 3 },
];
{ title: 'Cabbage', id: 1 },
{ title: 'Garlic', id: 2 },
{ title: 'Apple', id: 3 },
];
在你的组件中,使用 map() 函数将这个数组转换为 <li> 标签构成的列表
const listItems = products.map(product =>
<li key={product.id}>
{product.title}
</li>
);
return (
<ul>{listItems}</ul>
);
<li key={product.id}>
{product.title}
</li>
);
return (
<ul>{listItems}</ul>
);
样式
React 主要支持行内样式|外部样式[主要支持方式]
行内样式
①style ={{样式对象}} 属性写法同 js 操作 css 属性、将- 转为驼峰语法
②把样式对象进行提取,style ={ {...仅接收样式对象} } 使用...展开表达式时需注意{{...style0bject }}
②把样式对象进行提取,style ={ {...仅接收样式对象} } 使用...展开表达式时需注意{{...style0bject }}
外部样式
外部样式通过,ES 语法导入 外部.css|.scss 文件即可
导入外部样式分为 全局导入[main.jsx] | 组件局部导入【但样式总是全局生效的】
UI 框架通常全局导入:tailwind、bootstrap
样式冲突: 仅组件内导入、避免元素选择器、约定组件中类选择器的唯一性、模块化 CSS 技术
UI 框架通常全局导入:tailwind、bootstrap
样式冲突: 仅组件内导入、避免元素选择器、约定组件中类选择器的唯一性、模块化 CSS 技术
对事件做出响应并更新界面
响应事件
可以通过在组件中声明 事件处理 函数来响应事件
function MyButton() {
function handleClick() {
alert('You clicked me!');
}
return (
<button onClick={handleClick}>
Click me
</button>
);
}
function handleClick() {
alert('You clicked me!');
}
return (
<button onClick={handleClick}>
Click me
</button>
);
}
更新界面
通常你会希望你的组件 “记住” 一些信息并展示出来,你需要在你的组件中添加 state
引入 useState
import { useState } from 'react';
在组件中声明一个 state 变量
const [count, setCount] = useState(0);
组件间共享数据
解决方式:props(单向数据)
父组件引用子组件时 通过绑定/HTML 属性或自定义属性传递数据
子组件通过参数接收父组件绑定的属性、所有属性被绑定到 props 对象变量
自定义属性数量没有限制 一般不要太多 【特定机制库除外]
自定义属性数量没有限制 一般不要太多 【特定机制库除外]
将共享的数据向上一级的组件提取,然后使用 JSX 的大括号向 下级的组件 传递信息实现数据的共享
使用props传递特殊数据
children
jsx
function
进阶学习
描述UI
添加交互
状态管理
脱围机制
Vue
Vue的前世今生
问题:
● AngularJS的复杂性和React的学习曲线较高。
● 开发者需要一种更加轻量和易上手的解决方案。
解决:
● 提供简单易用的API,降低学习门槛。
● 结合了AngularJS的模板语法和React的组件化思想。
● 提供渐进式架构,允许开发者根据需要引入功能。
特点:
● 轻量化,核心库关注视图层,其他功能通过插件扩展。
● 简洁的模板语法,支持双向数据绑定。
● 强大的单文件组件(Single File Component),将模板、逻辑和样式集中在一个文件中。
Vue2
我不用^ ^
Vue3
根据培训机构课程排版
脚手架
Vue-Cli
Vite(√)
npm create vue@latest
声明式API与组合式API
声明式API
组合式API(√)
setup
setup 语法糖(√)
<script setup lang="ts">
</script>
</script>
setup函数
模板语法
{{ 表达式 }}
指令
文本
v-text
原始 HTML
v-html
标签属性绑定
v-bind
简写 :属性名
条件渲染
v-if
v-else
v-else-if
元素可见性
v-show
列表渲染
v-for
事件
v-on
简写 @事件名
表单输入绑定(双向绑定)
v-model(这个类似复合属性,内部本质上还是上面那些指令的使用)
单次渲染
v-once
仅渲染元素和组件一次,并跳过之后的更新
v-pre
跳过该元素及其所有子元素的编译
v-slot
用于声明具名插槽或是期望接收 props 的作用域插槽
响应式
ref
基本类型的响应式数据
`let xxx = ref(初始值)`
对象类型的响应式数据
`let xxx = ref(源对象)`
reactive
对象类型的响应式数据
let 响应式对象= reactive(源对象)
响应式的类型标注(泛型)
ref
在变量后面使用Ref<>标注
不赋值会报错
在ref函数后使用<>标注
不赋值默认undefined
reactive
reactive不推荐使用类型标注,因为处理了深层次 ref 解包的返回值与泛型参数的类型不同。
深层响应与浅层响应
深层响应性(ref和reactive都是是深层)
深层响应性意味着即使改变嵌套对象或数组时,变化也会被检测到
浅层响应(使用shallow ref、shallowReactive())
对于浅层响应只有 .value 的访问会被追踪。
ref 对比 reactive
宏观角度看
`ref`用来定义:基本类型数据、对象类型数据
`reactive`用来定义:对象类型数据
区别:
`ref`创建的变量必须使用`.value`(可以使用`volar`插件自动添加`.value`)
`reactive`重新分配一个新对象,会失去响应式(可以使用`Object.assign`去整体替换)
使用原则
理论上
若需要一个基本类型的响应式数据,必须使用`ref`
若需要一个响应式对象,层级不深,`ref`、`reactive`都可以
若需要一个响应式对象,且层级较深,推荐使用`reactive`
实际上
官网建议统一使用ref作为响应式的主要API,原因是reactive存在一些局限性,例如只支持对象、对解构不友好、不能替换响应式对象。当 ref 的值是一个对象时,ref() 也会在内部调用它。
响应式的一些工具
isRef()
检查某个值是否为 ref
unref()
如果参数是 ref,则返回内部值,否则返回参数本身。
toRef()
将值、refs 或 getters 规范化为 refs
toValue()
将值、refs 或 getters 规范化为值。
toRefs()
批量规范化为refs
isProxy()
检查一个对象是否是由 reactive()、readonly()、shallowReactive() 或 shallowReadonly() 创建的代理
isReactive()
检查一个对象是否是由 reactive() 或 shallowReactive() 创建的代理。
isReadonly()
检查传入的值是否为只读对象。
计算属性
使用方式
函数式
const val = computed(
() => {
return changeVal
}
)
() => {
return changeVal
}
)
对象形式
const val = computed({
get: () => {
return value
},
set: (newValue) => {
value = 'set' + newValue
}
})
const val = computed({
get: () => {
return value
},
set: (newValue) => {
value = 'set' + newValue
}
})
ComputedVS直接方法调用(缓存)
Computed存在缓存的概念,如果计算属性的值不被改变,无论访问多少次计算属性,都不会触发getter函数的执行;而方法则总是会在渲染时执行函数。在一般的情况下,其实两者都是可以使用的,然而当遇到一些耗性能的计算属性时,Computed的缓存会带来明显的性能提升。
可读性
Computed默认是只读的。当你尝试修改一个计算属性时,你会收到一个运行时警告。只在某些特殊场景中你可能才需要用到“可写”的属性,你可以通过同时提供 getter 和 setter 来创建,即使用对象的形式使用Computed。
监听
watch
作用:监视数据的变化
watchEffect
作用:立即运行一个函数,同时响应式地追踪其依赖,并在依赖更改时重新执行该函数
`watch`对比`watchEffect`
都能监听响应式数据的变化,不同的是监听数据变化的方式不同
`watch`:要明确指出监视的数据
`watchEffect`:不用明确指出监视的数据(函数中用到哪些属性,那就监视哪些属性)
watchPostEffect()
watchSyncEffect()
标签的 ref 属性
作用:用于注册模板引用。
用在普通`DOM`标签上,获取的是`DOM`节点
用在组件标签上,获取的是组件实例对象
依赖注入
provide()
要为组件后代提供数据,需要使用到 provide() 函数
inject()
要注入上层组件提供的数据,需使用 inject() 函数
生命周期
规律
生命周期整体分为四个阶段,分别是:创建、挂载、更新、销毁,每个阶段都有两个钩子,一前一后
生命周期钩子
创建阶段:`setup`
挂载阶段:`onBeforeMount`、`onMounted`
更新阶段:`onBeforeUpdate`、`onUpdated`
卸载阶段:`onBeforeUnmount`、`onUnmounted`
常用的钩子:`onMounted`(挂载完毕)、`onUpdated`(更新完毕)、`onBeforeUnmount`(卸载之前)
自定义hook
本质是一个函数,把`setup`函数中使用的`Composition API`进行了封装
路由
vue-router
概述
Vue Router 是 Vue 官方的客户端路由解决方案。
客户端路由
客户端路由的作用是在单页应用 (SPA) 中将浏览器的 URL 和用户看到的内容绑定起来。当用户在应用中浏览不同页面时,URL 会随之更新,但页面不需要从服务器重新加载。
路由组件通常存放在`pages` 或 `views`文件夹,一般组件通常存放在`components`文件夹
通过点击导航,视觉效果上“消失” 了的路由组件,默认是被卸载掉的,需要的时候再去挂载
组件
RouterLink
我们使用组件 RouterLink 来创建链接
RouterView
RouterView 组件可以使 Vue Router 知道你想要在哪里渲染当前 URL 路径对应的路由组件
tips:
组件 RouterView 和 RouterLink 都是全局注册的,因此它们不需要在组件模板中导入
使用
创建路由实例
路由器实例是通过调用 createRouter() 函数创建的
配置路由
注册路由器插件
app.use(router)
访问路由器和当前路由
可以通过 useRouter() 和 useRoute() 来访问路由器实例和当前路由。
const router = useRouter()
const route = useRoute()
路由器工作模式
`history`模式(HTML5 模式)
createWebHistory()
优点:`URL`更加美观,不带有`#`,更接近传统的网站`URL`。
缺点:后期项目上线,需要服务端配合处理路径问题,否则刷新会有`404`错误。
需要进行适当的服务器配置
如location / {
try_files $uri $uri/ /index.html;
}
try_files $uri $uri/ /index.html;
}
`hash`模式
hash 模式是用 createWebHashHistory() 创建的
优点:兼容性更好,因为不需要服务器端处理路径。
缺点:`URL`带有`#`不太美观,且在`SEO`优化方面相对较差。
Memory 模式
createMemoryHistory()
适合 Node 环境和 SSR
它不会有历史记录,这意味着你无法后退或前进,不推荐在浏览器应用中使用
路由传参
query参数
params参数
路由的props配置
作用:让路由组件更方便的收到参数(可以将路由参数作为`props`传给组件)
状态管理
pinia
简介
Pinia 是 Vue 的专属状态管理库,它允许你跨组件或页面共享状态。
安装
vite脚手架自带了
核心概念
Store
`Store`是一个保存:状态、业务逻辑 的实体,每个组件都可以读取、写入它。
Store的三个核心概念
它有三个概念:`state`、`getter`、`action`,相当于组件中的: `data`、 `computed` 和 `methods`
state
访问 state
重置 state
变更 state
替换 state
订阅 state
getter
Getter 完全等同于 store 的 state 的计算值。
action
Action 相当于组件中的 method。
存储+读取数据
修改数据
直接修改
批量修改
借助`action`修改
storeToRefs
借助`storeToRefs`将`store`中的数据转为`ref`对象,方便在模板中使用。
注意:`pinia`提供的`storeToRefs`只会将数据做转换,而`Vue`的`toRefs`会转换`store`中数据
getters
概念:当`state`中的数据,需要经过处理后再使用时,可以使用`getters`配置。
$subscribe
通过 store 的 `$subscribe()` 方法侦听 `state` 及其变化
pinia进阶
问题:
pinia页面刷新状态会丢失
解决:
使用localStorage写一个pinia 插件缓存他的值
服务端渲染
将组件在服务端直接渲染成 HTML 字符串,作为服务端响应返回给浏览器
为什么要用 SSR
更快的首屏加载
统一的心智模型
更好的 SEO
搜索引擎爬虫可以直接看到完全渲染的页面
在为你的应用使用 SSR 之前,你首先应该问自己是否真的需要它。这主要取决于首屏加载速度对应用的重要程度。
通用的解决方案
Nuxt.js
Quasar
Vite SSR
组件组合(通信)
props
常用与 :父 ↔ 子
若 父传子:属性值是非函数
若 子传父:属性值是函数
自定义事件emit
自定义事件常用于:子 => 父
mitt库(要install)
与消息订阅与发布(`pubsub`)功能类似,可以实现任意组件间通信
v-model
实现 父↔子 之间相互通信
$attrs
`$attrs`用于实现当前组件的父组件,向当前组件的子组件通信(祖→孙)
`$attrs`是一个对象,包含所有父组件传入的标签属性
`$attrs`会自动排除`props`中声明的属性(可以认为声明过的 `props` 被子组件自己“消费”了)
$refs、$parent
`$refs`用于 :父→子
值为对象,包含所有被`ref`属性标识的`DOM`元素或组件实例。
`$parent`用于:子→父。
值为对象,当前组件的父组件实例对象。
provide、inject
实现祖孙组件直接通信
在祖先组件中通过`provide`配置向后代组件提供数据
在后代组件中通过`inject`配置来声明接收数据
slot
默认插槽
<slot></slot>
具名插槽
<slot name="s1"></slot>
<slot name="s2"></slot>
<slot name="s2"></slot>
条件插槽
作用域插槽
数据在组件的自身,但根据数据生成的结构需要组件的使用者来决定
关系总结
父传子
props
v-model
$refs
默认插槽、具名插槽
子传父
props
自定义事件
v-model
$parent
作用域插槽
祖传孙、孙传祖
$attrs
provide、inject
兄弟间、任意组件间
mitt
pinia
其他API
shallowRef 与 shallowReactive
shallowRef
创建一个响应式数据,但只对顶层属性进行响应式处理
let myVar = shallowRef(initialValue);
只跟踪引用值的变化,不关心值内部的属性变化。
shallowReactive
创建一个浅层响应式对象,只会使对象的最顶层属性变成响应式的,对象内部的嵌套属性则不会变成响应式的
const myObj = shallowReactive({ ... });
对象的顶层属性是响应式的,但嵌套对象的属性不是。
readonly 与 shallowReadonly
readonly
用于创建一个对象的深只读副本。
const original = reactive({ ... });
const readOnlyCopy = readonly(original);
const readOnlyCopy = readonly(original);
shallowReadonly
与 `readonly` 类似,但只作用于对象的顶层属性。
const original = reactive({ ... });
const shallowReadOnlyCopy = shallowReadonly(original);
const shallowReadOnlyCopy = shallowReadonly(original);
toRaw 与 markRaw
toRaw
用于获取一个响应式对象的原始对象, `toRaw` 返回的对象不再是响应式的,不会触发视图更新。
markRaw
标记一个对象,使其**永远不会**变成响应式的。
customRef
创建一个自定义的`ref`,并对其依赖项跟踪和更新触发进行逻辑控制。
新组件
Teleport
Teleport 是一种能够将我们的**组件html结构**移动到指定位置的技术。
Suspense
等待异步组件时渲染一些额外内容,让应用有更好的用户体验
深入响应式
什么是响应性
Vue 中的响应性是如何工作的
劫持 property,追踪对象属性的读写
Vue 3 中则使用了 Proxy 来创建响应式对象,仅将 getter / setter 用于 ref
渲染机制
虚拟 DOM
虚拟 DOM是一种编程概念,意为将目标所需的 UI 通过数据结构“虚拟”地表示出来,保存在内存中,然后将真实的 DOM 与之保持同步
打包工具(TODO)
vite
webpack
通用的解决方案相关\场景题?(Doing)
单点登录
分布式会话
认证授权
安全性
幂等
分布式锁
分库分表
数据迁移/同步
支付
风控
多级缓存
多级缓存设计方案一
缓存架构
一级缓存(本地缓存)
存储介质:JVM内存(如HashMap、ConcurrentHashMap、Guava Cache、Caffeine等)。
特点:速度快,适用于频繁访问的数据。
缺点:内存容量有限,数据可能不一致。
二级缓存(分布式缓存)
存储介质:分布式缓存系统(如Redis、Memcached)。
特点:容量大,支持分布式环境,数据一致性较好。
缺点:速度相对于本地缓存慢,但仍远快于直接访问数据库。
持久化存储(数据库)
存储介质:关系型数据库(如MySQL、PostgreSQL)或NoSQL数据库(如MongoDB)。
特点:数据最终存储位置,持久性强。
缺点:访问速度最慢。
多级缓存工作流程
读取数据:
首先查询一级缓存,如果命中则返回数据。
如果一级缓存未命中,则查询二级缓存,如果命中则返回数据,并将数据写入一级缓存。
如果二级缓存也未命中,则查询数据库,返回数据,并将数据写入一级缓存和二级缓存。
如果一级缓存未命中,则查询二级缓存,如果命中则返回数据,并将数据写入一级缓存。
如果二级缓存也未命中,则查询数据库,返回数据,并将数据写入一级缓存和二级缓存。
写入数据:
更新数据库。
删除或更新二级缓存中的相关数据。
删除或更新一级缓存中的相关数据。
删除或更新二级缓存中的相关数据。
删除或更新一级缓存中的相关数据。
多级缓存存在、需要注意的问题
数据一致性
在更新数据时,需要确保缓存和数据库的一致性,可以使用消息队列或事务来保证。
缓存失效策略
根据业务需求设计缓存的失效策略,避免缓存雪崩和缓存穿透问题。
缓存预热
在系统启动时或高峰期前预先加载常用数据到缓存中,提高响应速度。
报表(最近面一些传统制造业公司遇到很多)
报表的定义
向上级报告情况的表格。简单的说:报表就是用表格、图表等格式来动态显示数据,可以用公式表示为:“报表 = 多样的格式 + 动态的数据”
报表分类
列表式报表
特点
以表格形式逐行列出数据,通常包括多个列,每列表示一个数据字段
用途
适用于展示详细的记录,如客户名单、订单明细等
示例
销售明细报表,其中每行列出一个订单的具体信息,包括订单号、客户名称、商品名称、数量、价格等。
摘要氏报表
特点
提供数据的概览和总结,通常使用聚合数据,如总和、平均值等。
用途
适用于快速了解总体情况,如月度销售总额、部门绩效等。
示例
财务总结报表,其中汇总显示每月的总收入、总支出和利润。
矩阵式报表
特点
以交叉表的形式展示数据,通常包含行和列的组合,行和列的交点处显示数据值。
用途
适用于展示多维度数据分析结果,如销售数据按地区和时间分类汇总。
示例
销售矩阵报表,其中行表示地区,列表示月份,交叉点显示该地区在该月份的销售额。
钻取式报表
特点
允许用户通过点击报表中的某个数据点,进一步查看更详细的相关数据。
用途
适用于需要层次化数据分析的场景,帮助用户逐步深入了解数据细节。
示例
公司年度销售报表,点击某个月份的销售额,可以钻取查看该月的详细销售订单
报表功能的标准开发流程
需求收集和分析
了解业务需求:与业务用户沟通,明确他们需要什么样的数据展示,报表的目的是什么
确定报表类型:根据需求确定使用哪种类型的报表,如列表式、摘要式、矩阵式或钻取式报表。
定义指标和维度:确定报表中需要展示的关键指标和维度,如销售额、利润、时间、地区等。
数据准备
数据源确定:确定报表所需的数据源,可能是数据库、数据仓库、API接口等。
数据抽取、转换和加载(ETL):进行数据的抽取、转换和加载,将数据从源系统提取出来,转换成适合报表使用的格式,并加载到目标数据库或数据仓库中。
报表设计
选择报表工具:选择适合的报表生成工具,如Tableau、Power BI、Excel、SAP Crystal Reports等。
设计报表布局:根据需求设计报表的布局,包括标题、表头、数据区域、图表等。
定义报表参数:如果需要动态生成报表,定义用户可以输入的参数,如日期范围、产品类别等。
报表开发
数据查询和处理:编写SQL查询或使用其他数据处理工具获取和处理数据,确保数据的准确性和及时性。
报表制作:在报表工具中创建报表,配置数据源、添加数据字段、设计图表和格式。
测试和验证
部署和发布
维护和优化
跨域
跨域的定义
在浏览器中,一个网页试图请求不同源(域名、协议、端口)的资源时所遇到的问题。浏览器基于同源策略(Same-Origin Policy)来防止不同源的资源访问,从而保证用户的信息安全。
跨域的解决方案
JSONP(JSON with Padding)
使用方式
通过Ajax发去Get请求的时候,type指定为JSONP
原理
利用 <script> 标签不受同源策略限制的特性,通过动态插入 <script> 标签来实现跨域请求
CORS (Cross-Origin Resource Sharing)
使用方式
后端(特指SpringBoot)使用@CrossOrigin实现局部跨域
后端(特指SpringBoot)重写 WebMvcConfigurer实现全局跨域
原理
是一种W3C标准,允许服务器明确指示允许哪些源访问其资源。服务器在响应头中设置 Access-Control-Allow-Origin
服务器代理
使用方式
Vite、Webpack配置
通过设置一个代理服务器,使得浏览器以为请求的是同一个域
Nginx反向代理
配置Nginx服务器,将请求转发到目标服务器,隐藏实际的目标服务器。
原理
原理:前端发送请求到代理服务器,再由代理服务器发送请求到目标服务器
WebSockets
使用方式
略
原理
WebSockets 不受同源策略限制,可以实现跨域通信,但需要服务端支持WebSocket协议。
TODO
其他
物联网
网络安全
大数据
AI
密码学
计算机科学
汇编语言
计算机组成原理
操作系统
计算机网络
数据结构与算法基础
全端知识
移动端
小程序
安卓
ios
桌面
windows
mac
linux
Maven
Gradle
Zookeeper
nginx
Spring Security
Shiro
ActiveMQ
RocketMQ
Kafka
Netty
......
Java知识脉络
基础语法
变量
基本数据类型(掌握)
整型(默认值0)
byte
short
int(字面量默认类型)
long
浮点型(默认值0.0)
float
double(字面量默认类型)
字符型
char
布尔
true
false(默认值)
基本数据类型变量间运算规则(掌握)
自动类型提升
小的会自动转大的
byte、short、char在运算中会转为int
强制类型转换
(强制类型)
丢失精度
基本数据类型和String的运算
只能用+号运算,结果一定是String
计算机对数据的存储(了解)
进制
二进制
十进制
八进制
等等
计算机数据的存储使用二进制补码形式存储,并且最高位是符号位。(了解)
正数:最高位是 0
负数:最高位是 1
正数的补码与反码、原码一样,称为三码合一
负数的原码:把十进制转为二进制,然后最高位设置为 1
负数的反码:在原码的基础上,最高位不变,其余位取反(0 变 1,1 变
0)
0)
负数的补码:反码+1
关于进制的转换(了解)
Java的包装类有提供对应的静态方法,实现进制转换的操作。一般情况下也用不上,除了在刷题会要求的自己写进制的转换。
引用数据类型(掌握)
Class、Interface、Array
Enum、Annotation、Record
运算符
算术运算符
不细讲了,主要是++和--两个说一下
++
直接使用i++和++i没区别
结合赋值符使用
i++,先用i,在加1
++i,先加1再用i
--
减减同上
赋值运算符
主要是说一下+=、-=这类的
这类会带有隐式的强制类型转换,转为=号左边的
比较(关系)运算符
没什么特别需要说明的说法
逻辑运算符
面试问的比较多的是|和||的区别、&和&&的区别
两个的,如果左边第一个条件不满足,就会直接返回false;一个的两边都得判断完才能得出结果
位运算符
计算效率上会比一般的算术运算符高,但是难度也会偏高,要求掌握二进制运算的知识。平时基本使用场景,不过源码里偶尔会见到。
条件运算符
三元运算符
流程控制
顺序
没啥好说的,顾名思义
分支
if-else
这个没啥好说的,有手就行
switch-case
这块涉及到面试问的比较多的一个题
switch表达式在jdk7后支持String,本质是使用String的Hash值
break和default留意一下就可以了
上面两个效率比较
网上的课程说法是能用switch的可以尽量用,效率会高一点,但是我个人实际编码过程中是没怎么用过,直接if就完事了,review的时候要领导提意见了再说
循环
for
while
do...while
至少执行一次
数组
声明+初始化
个人习惯 例:int[] arr = new int[?]; 或者 int[] arr = {?,?,?,?,};
使用
长度 arr.length
索引 arr[i]
遍历
在有IDE加持的情况下,这些基础操作都不是事儿,代码提示一把梭
多维数组
有一说一我真没见过用多维数组的,甚至一维都少见,更甚数组都少见到用的,基本都是集合框架那块用的多,数组一是定长,而是提供的方法支持没有集合那么多,用起来确实没集合框架那么顺手
对应工具类Arrays
用的比较多的就数组拼接、sort排序、二分查找、数组复制、数组比较
数组填充我还真没用过
asList
特别注意!
返回的ArrayList是Arrays类里面的一个内部类,和集合框架里的ArrayList并不是一个类。它并没有实现集合的修改方法,如果调用修改方法会抛异常。如果你确实要修改,把它作为集合ArrayList构造函数的入参,包成集合里的ArrayList用
sort
parallelSort
binarySearch
fill
copyOf
copyOfRange
compare
数组异常
下标越界
空指针
面向对象
面向对象和面向过程的区别
1.1面向对象
面向对象是将构成问题的事物分解成一个个的对象,使用对象去描述事物在解决问题过程中的行为,而不是专注于使用对象去完成一个步骤
1.2面向过程
面向过程简而言之就是将一个问题的解决划分为多个步骤,使用函数实现一个个的步骤,然后再按照顺序进行调用
1.3总结
两者在解决问题时,专注的角度不同,面向对象正如这个名称而言,它更关注问题中设计到了那些对象,有什么属性,涉及到什么行为,去将这样的对象一个个实例化,再通过对象之间的行为去解决一个问题,而,面向过程也是,关注点在于第一步要做什么、第二部要做什么,这样一个循序渐进的过程。
PS:面向对象和面向过程并不是互相对立的,面向过程作为最原始的coding范式,衍生出了面向对象编程,在编写对象编程中我们依旧能看到面向过程的影子。
面向对象的SOLID原则
单一职责
让一个类只做一种类型责任,当这个类需要承当其他类型的责任的时候,就需要分解这个类
开闭原则
对扩展是开放的,而对修改是封闭的
里氏替换
当一个子类的实例应该能够替换任何其超类的实例时,它们之间才具有is-A关系
倒置依赖
高层模块不应该依赖于低层模块,二者都应该依赖于抽象
抽象不应该依赖于细节,细节应该依赖于抽象
接口隔离
不能强迫用户去依赖那些他们不使用的接口。换句话说,使用多个专门的接口比使用单一的总接口总要好
类
属性
成员属性
变量
常量
类属性
变量
常量
行为(方法)
成员行为(方法)
类行为(方法)
构造器
默认提供无参构造,如果你自己写了构造函数,则不提供无参构造
代码块
默认代码块
静态代码块
这里面的很多细节有一说一我很想讲,但是我不知道怎么讲(声明周期、默认值、作用域相关)
像成员变量和局部变量和静态方法啥的区别,你初学的时候得死记硬背觉得很抽象,但是当你往JVM学习之后就好理解,它们的区别在于它们在运行时数据区中存储的位置不一样,导致了它们的生命周期、默认值、作用范围等的不一样,我个人不太喜欢死记硬背,别人问我的时候我也不能立刻答上来,但是我会从JVM的角度去梳理、对比它们,总而言之,知识的融会贯通很重要。
举个例子
局部变量在栈帧的局部变量表上,它在编译时就决定了栈帧的大小,所以对于局部变量,你必须进行初始化才能使用,编译器不帮你设置零值
成员变量基于对象的创建过程,这个过程有一个设置零值的操作,所以可以只进行声明,不进行初始化
静态变量在类加载时处理,它也有一个零值的设置阶段,所以也可以只声明不初始
然后就是作用范围,类先加载,然后对象才能在创建,所以在静态方法里肯定是不能用成员方法和成员变量的
总之这些东西罗里吧嗦一堆很难说清楚的,但是你从JVM的角度去推,思路就很清晰
对象
对象的创建方式
new关键字,通过构造函数创建
clone
反序列化
通过反射创建
匿名对象
例:new Person().shout();
使用场景
一个对象只需要进行一次方法调用,那么就可以使用匿名对象
将匿名对象作为实参传递给一个方法调用
举例:
通过Proxy创建代理对象时,构造参数的第三个参数,我们就可以传入匿名对象
面向对象的特点
封装
封装简而言之就是将类的内部细节隐藏,只对外提供公共的访问方法。JavaBean就是一个很好的例子,将字段私有化,只对外提供Setter和Getter方法进行访问。
封装的优点在于可以提高代码的安全性、降低耦合
继承
继承就是将多个类所拥有共同属性和行为向上抽取,形成一个父类。子类通过extends去继承这个父类就能够获得公共的行为和属性
继承的优点在于可以提高代码的复用性,但是问题也很明显,它破坏了封装性,并且是一种强耦合
PS:继承应该遵循里氏替换原则,子类对象必须能够替换掉所有父类对象。
继承注意点:
1、子类拥有父类非private的属性和方法。(另一种说法是,子类也拥有父类的私有属性和方法,但是它并没有权限去访问他们)
2、子类可以拥有自己属性和方法,即子类可以对父类进行扩展
3、子类可以用自己的方式实现父类的方法(重写)
多态
编译时多态
编译时多态指对象引用所调用的方法在编译期就确定了,主要指方法的重载
运行时多态
运行时多态指程序中定义的对象引用所指向的具体类型在运行期间才确定,主要指方法的重写
运行时多态的必要条件:继承、方法重写、父类引用指向子类对象(向上转型)
PS:重写发生在父子类之间,并有以下规范:子类重写的方法的返回值类型要小于或者等于父类,返回值也一样,而方法的权限修饰符要大于父类,这是为了符合设计模式关于类设计的五大原则之一里氏替换
多态的优点在于提高了程序的可拓展性
关于多态的原理:去看JVM篇的虚方法调用
构造器问题
(默认)无参构造
有参构造
重载和重写
重载
同一个类,方法名一样,参数列表不同
重写
@Override注解说明
条件
继承关系
子类重写的方法必须和父类被重写的方法具有相同的方法名称、参数列表
子类重写的方法的返回值类型不能大于父类被重写的方法的返回值类型
子类重写的方法使用的访问权限不能小于父类被重写的方法的访问权限
子类方法抛出的异常不能大于父类被重写方法的异常
访问权限问题
public
公共所有人都能访问
protect
仅限同一个包下的类以及它的子类能访问
默认
仅限同一个包下的类能访问
private
只有当前类能访问
修饰问题
方法、字段
都可以修饰
类
public
默认
this和super
this
本质是一个指针,在局部变量表索引0的位置
super
关键字
static关键字
修饰属性
修饰方法
final关键字
修饰属性
修饰方法
修饰类
接口和抽象类
属性
抽象声明的属性都是静态常量;抽象类没限制
方法
接口只能声明方法不能实现方法,但是jdk8有默认方法;抽象类可以声明也可以实现方法
构造函数
接口不能有构造函数,抽象类可以有
多继承
接口支持多继承,抽象类不支持
Object类的方法
反射用的
getClass
clone
集合用的
equals
hashcode
toString
并发用的
wait
notify
notify all
JVM用的
触发垃圾回收的方法,已经不用了
内部类
成员内部类
静态成员内部类
当静态变量用
非静态成员内部类
当成员变量用
局部内部类
匿名
非匿名
枚举
本质也是类,但是对象个数有限,只读不可修改
注解
常用注解
重写检查
@Override
抑制警告
@SuppressWarnings
弃用标识
@Deprecated
元注解(java.lang.annotation)
@Document
被javadoc记录
@Target
注解作用范围
可选值(比较多,标一些常见常用的)
ElementType.TYPE (Class, interface , enum, or record)
ElementType.FIELD
ElementType.METHOD
ElementType.PARAMETER
ElementType.CONSTRUCTOR
@Retention
生命周期
可选值
SOURCE
CLASS
RUNTIME
使用建议:自定义的一律RUNTIME
@Inherited
允许子类继承父类注解
自定义注解
声明自定义注解,配置元注解等信息
使用
读取(用反射)、处理逻辑
包装类
为什么要包装类
基本数据类型不符合面向对象特性
留意缓存值,面试有问
直接记,除了浮点都有缓存
拆箱和装箱看看就好了,反正也不用你手动拆或者装
包装类的API,没特别留意过
字符串、基本数据类型、和包装类间转换
大小写转换
进制转换
比较
包装类和基本数据类型的区别:默认值、行为,这里拓展一下在面试中遇到的一个问题
后端接口的方法参数你是使用基本数据类型还在包装类型?
这里我觉得主要是看对入参的约定吧,如果是基本数据类型的话,在不穿参数时,会有对应的默认值,如果使用的是包装类型,则会是null,一般来说我个人推荐使用包装类型,因为null的语义相比基本数据类型的默认值,来的更加的直观,你能够清晰的确认导入有没有入参进来。
高级
异常
Throwable类
Error类(非受检)
jvm处理不了的,出错程序直接停止
像栈溢出、堆溢出、方法区溢出这些
Exception类
可以进行处理,处理完程序可以继续正常运行,如果不处理,程序直接停止
运行时异常(非受检)
下标越界、类型转换异常、空指针、运算符异常、非法参数
非运行时异常(受检)
找不到文件
未知host
异常处理
在当前方法内捕获处理
try catch finally
有资源要释放的用try resource
不在当前方法里处理,往调用者抛出,让调用者处理
在方法声明上通过throws
手动抛异常
通过throw关键字抛出异常
自定义运行时异常
1、继承RuntimeException类
2、提供无参有参构造函数
3、通过throw关键字处理异常
PS:关于在finally中进行return操作的争议
根据Java语言规范,如果finally块中有return语句,那么try或catch块中的return将不会被执行。JVM会使用finally块中的return值作为方法的最终返回值
☆多线程、并发
引子
进程和线程的区别看操作系统去
资源分配的单位:进程
调度单位:线程
并发和并行的区别也看操作系统去
并发:同一时间段
并行:同一时刻
线程安全的定义
当多个线程同时访问一个对象时,如果不考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他协调操作,调用这个对象的行为都可以获得正确的结果,那就称这个对象是线程安全的
线程安全分类
不可变
安全性最直接、最纯粹,例如:final、数值包装类型、大数据类型
绝对线程安全
JavaAPI中标注自己是线程安全的类,大多数都不是绝对的线程安全。
相对线程安全
Vector、HashTable
线程兼容(我们通常说的线程不安全)
ArrayList、HashMap
线程对立
Java环境下,这种代码通常是有害的,尽量避免,例如:Thread类的弃用方法就是因为这个,会导致死锁
死锁
看操作系统去
互斥
请求和保持
不可抢占
循环等待
死锁的避免
避免一个线程同时获取多个锁
避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源
好文推荐(审核别卡我,真是好文):
https://www.raychase.net/698
https://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html
https://developer.jdcloud.com/article/2747
从JDK的发行版本窥探Java多线程、并发的发展历程:(这里主要是说明这些发行版本做了什么,不会对知识点进行刨根问底)
JDK1.0-1.2
简述:
1.0作为Java的第一个版本,原生的支持了多线程。提供了java.lang.Thread 类和 Runnable 接口为基础的多线程编程模型奠定了框架。此时的多线程模型比较简单,开发者需要手动处理线程的创建、管理、和同步(Synchronized、Volatile、Object.wait、Object.notify)。
1.2提供了线程池的雏形,虽然 JDK 1.2 没有提供线程池的标准实现,但开发者开始意识到手动创建和管理线程的成本较高,于是很多开发者自定义线程池模型以提升性能;另外,ThreadLocal的引入,提供了线程局部存储,使变量在每个线程中隔离,避免了多个线程访问共享数据时的冲突。
除此之外,Collections工具类的引入,为集合的线程安全提供了一种解决方案。
除此之外,Collections工具类的引入,为集合的线程安全提供了一种解决方案。
线程的创建方式
继承Thread类,重写run方法
实现Runnable接口,重写run方法
线程生命周期
NEW
RUNNABLE
start
BLOCKED
线程竞争锁产生阻塞
WAITING
Object. wait with no timeout
Thread. join with no timeout
TIMED_WAITING
Thread. sleep
Object. wait with timeout
Thread. join with timeout
TERMINATED
线程常用方法
public synchronized void start
注意和run()区别,直接调用run是在主线程里执行方法,调用start()本质是调用了native方法向操作系统申请了新的线程资源
join(synchronized修饰)
在一个线程中调用其他线程的join方法,实现同步执行的效果
本质是调用了Object里的wait方法,插队的线程通过那到这个锁,以形成同步执行的效果
join带超时时间
通过do-while来持有/释放锁
join不带时间
死循环,通过判断当前插队的线程是否存活来持有/释放锁
中断
public void interrupt
打断当前的线程对象。如果打断前线程就是被阻塞的状态,那么会清除中断标记,并抛出InterruptedException
public boolean isInterrupted
判断打断线程的状态,不会清除打断标记,因为就是简单的return了一个中断值
public static boolean interrupted,静态方法
判断线程是否已经中断,会清除中断标记。简而言之如果连续调用这个方法两次,第二次必定会返回false
native方法
public static native void yield
通知调度器当前线程可以让出cpu资源,但是是否让出实际是由调度器来决定的,这个方法源码注释里并不推荐使用该方法,它更多是在调试、测试场景下使用。
public static native void sleep
记住会不释放锁就行了
线程的同步方式
synchronized关键字
特性
可见性
原子性
某种意义上的保证有序性
它本身并不会禁止指令重排序,但是他符合了“as-if-serial”原则,在但线程中的执行结果不会改变,因为它恁重,他都直接阻塞其他线程了,其他线程在它释放之前根本就不可能去干扰他,所以它在单线程里怎么执行都是它的事。
可重入
无条件阻塞后面其他线程进入
底层
JVM基于进入和退出Monitor对象来实现方法同步和代码同步,再深入一点就是每一个Java对象都持有一个对应的Monitor对象,这个Monitor对象是底层C++实现的(在Java虚拟机(HotSpot)中,monitor是由OnjectMonitor实现的,其主要的数据结构如下(位于HotSpot虚拟机源码ObjectMonitor.hpp文件,C++实现)),Java对象通过对象头中的指针与它关联,再再深入一点,就是操作系统里说的管程Monitor(尚硅谷阳哥著名言论:天生飞的理念都有落地的实现,这个也是基于操作系统理念进行落地的实现)
代码块同步,字节码文件中会多出monitorenter和monitorexit指令
方法同步,字节码文件中的方法信息中会多一个ACC_SYNCHRONIZED同步标识
synchronize锁优化
无锁
偏向锁
轻量级锁
锁粗化
扩大加锁范围(如:在循环体中使用,导致每次循环都要加锁释放锁,性能开销太大,不如直接扩大范围)
适应性自旋
锁消除
检测到需要同步的代码段根本不存在竞争,进行优化
volatile关键字
volatile是Java虚拟机提供的最轻量级的同步机制。
特性:
可见性
强刷缓存实现
有序性
禁止指令重排序实现
不完全保证原子性
Java运算操作符并非是原子操作,导致volatile变量的运算在并发下是不安全的
比如自增自减
底层:带lock前缀的指令(内存屏障)→处理器嗅探机制,将工作线程的缓存写入内存,同时使其他线程的缓存失效,保证线程读取到的是最新值,保证可见性;插入内存屏障(lock前缀指令),禁止指令的重排序
针对浮点型的变量,除非明确可知变量存在竞争,否则不要刻意的声明为volatile
补充:
final
特性
天生不可变,天生线程安全,实现原原理是内存屏障
ThreadLocal
ThreadLocalMap(空间换时间,每个线程持有一份副本)
ThreadLocal类
空间换时间,每个线程拥有自己的一份副本变量
ThreadLocal只是一个壳子,内部使用的ThreadLocalMap类才是实质
ThreadLocalMap
当一个线程调用ThreadLocal的Set方法时,首先会从尝试直接从当前Thread的threadLocals成员变量获取,如果这个成员变量为null,则表示当前线程还没有初始化ThreadLocalMap,接着会调用createMap方法初始化当前线程的ThreadLocalMap对象,key值为当前的ThreadLocal对象,创建完成后将这个对象赋值给当前Thread的threadLocalMap对象。
InheritableThreadLocal
InheritableThreadLocal继承自ThreadLocal,重写了childValue、getMap和createMap方法,实现父子线程共享的核心是对于childValue的重写。结合Thread类的init方法来看,Thread对象在创建的时候,如果父类的inheritableThreadLocals不为空,则会调用ThreadLocal的createInheriteMap方法进行创建,接着通过childValue方法获取父线程的值,把父线程的inheritableThreadLocals的值赋值到新的ThreadLocalMap对象
线程的通信方式
Object方法(Synchronized)
wait
notify
notifyall
JDK1.4
虽然 JDK 1.4 并未对多线程进行重大改革,但它引入了NIO,允许异步和非阻塞操作,这对于可扩展的多线程网络应用程序至关重要。
JDK5-6
简述:
JDK5
这是 Java 并发模型的一个重大转折点,Java并发模型的飞跃!Doug Lea大神横空出世!Java原生提供的不好用,我自己写一个!java.util.concurrent 包的引入使得多线程编程更加易用和高效。
除了JUC包以外,还有几个比较重要的东西
JSR 133
JSR 133 重新明确了 Java 内存模型
JSR 166
JSR 166 的贡献就是引入了 java.util.concurrent 这个包
JDK6,对Synchronized底层的实现进行了优化,并提供了Fork/Join 框架的雏形。
线程的创建方式
实现Callable接口(可以有返回值、异常),重写call方法,用FutureTask执行、接收返回值
意义:
提高了线程任务的可操作性
线程池Executor框架
ExecutorServices接口×
submit和execute的区别
ThreadPoolExecutor√
线程生命周期(新增的)
NEW
RUNNABLE
start
BLOCKED
线程竞争锁产生阻塞
WAITING
LockSupport. park
TIMED_WAITING
LockSupport. parkNanos
LockSupport. parkUntil
TERMINATED
新增的线程的同步方式
基于Lock接口的阻塞同步
ReentrantLock
ReentrantReadWriteLock
并发工具类
CountDownLatch
CyclicBarrier
......
基于Unsafe类的CAS操作+Volatile的非阻塞同步
JUC包里提供的原子类
新增的线程的通信方式
Condition(Lock接口)(本质也是基于LockSupport实现)
await
signal
LockSupport
park
unpark
提供并发数据结构
如 ConcurrentHashMap,解决了传统同步集合类(如 Hashtable)性能较差的问题
JDK 7
Fork/Join 框架的引入专门用于任务递归拆分的并行处理,特别适用于 CPU 密集型任务的处理。这一框架允许将大任务分解成子任务,并通过 ForkJoinPool 并发处理,充分利用多核处理器。
提供了新的线程协调工具Phaser
这是一个用于线程协同的高级工具,比 CyclicBarrier 更加灵活,支持多个阶段的并发任务协调
对Locks框架进行了增强,使得 ReentrantLock 更加灵活和强大
JDK 8
简述:
JDK 8 引入了 CompletableFuture,为异步编程提供了强大的支持,支持非阻塞式的并发编程模型。开发者可以更容易地编写响应式和异步流式处理程序。
Stream API的引入。虽然不是直接的并发工具,Stream API 提供了便捷的并行流处理支持,开发者可以通过 parallel() 轻松实现数据的并行化处理。
引入了新的锁机制 StampedLock,它是一种更灵活和高效的读写锁,旨在解决并发编程中读写锁的性能瓶颈。
对Java多线程、并发作一个整体性的总结(截止至JDK8)
线程的创建方式
(JDK1.0)①通过继承Thread类,重写run方法创建
局限性:不支持多继承
(JDK1.0)②通过声明Runnable接口,重写run方法创建
进阶写法:函数式接口
new Thread(()-> System.out.println("Runnable")).start();
(JDK5)③通过Callable接口+FutureTask实现
实现Callable接口(可以有返回值、异常),重写call方法,用FutureTask执行、接收返回值
1、Callable接口+FutureTask
前言:
定义了操作异步任务执行的一些方法,提供一种异步并行计算的功能,如获取异步任务的执行结果、取消异步任务的执行、判断任务是否被取消、判断任务执行是否完毕等(多线程、异步、有返回、异常处理)
基础使用:
Runnable接口+Callable接口+Future接口和FutureTask实现类
进阶:函数式接口(JDK8)
FutureTask<Integer> task = new FutureTask<>(()->{
System.out.println("Callable");
return 0;
});
task.run();
task.get();
System.out.println("Callable");
return 0;
});
task.run();
task.get();
优点:
Future+线程池异步多线程任务配合,能显著提高程序的运行效率
缺点:
get()阻塞
isDone()轮询
2、优化
优化:jdk8提供的CompletableFuture类
链式操作
创建、结果传递、结果合并、任务顺序、异常处理
为什么出现
解决Future的阻塞、轮询问题
核心静态方法
CompletableFuture.runAsync
无返回值
CompletableFuture.supplyAsync
有返回值
PS:入参的线程池,若没有指定,则使用默认的ForkJoinPoolcommonPool()作为它的线程池执行异步代码
常用方法
获得结果和触发计算
获取结果
public T get()
public T get(long timeout,TimeUnit unit)
public T join() --->和get一样的作用,只是不需要抛出异常
public T getNow(T valuelfAbsent) --->计算完成就返回正常值,否则返回备胎值(传入的参数),立即获取结果不阻塞
主动触发计算
public boolean complete(T value) ---->是否打断get方法立即返回括号值
对计算结果进行处理
thenApply --->计算结果存在依赖关系,这两个线程串行化---->由于存在依赖关系(当前步错,不走下一步),当前步骤有异常的话就叫停
handle --->计算结果存在依赖关系,这两个线程串行化---->有异常也可以往下走一步
对计算结果进行消费
thenAccept
接受任务的处理结果,并消费处理,无返回结果
对比补充
thenRun(Runnable runnable) :任务A执行完执行B,并且不需要A的结果
thenAccept(Consumer action): 任务A执行完执行B,B需要A的结果,但是任务B没有返回值
thenApply(Function fn): 任务A执行完执行B,B需要A的结果,同时任务B有返回值
对计算速度进行选用
applyToEither
对计算结果进行合并
thenCombine
意义:
提高了线程任务的可操作性
(JDK5)④通过线程池创建
深度剖析线程池
使用线程池的优点
降低资源消耗
提高线程响应速度
提高线程的可管理性
JDK提供的线程池框架
Executor接口(JDK1.5)
接口方法声明
execute方法(没有返回值,没有异常抛出)
ExecutorService接口(JDK1.5)
接口方法声明
submit方法(有返回值,有异常抛出)
线程池的关闭
shutdown
比较柔性,先设置线程池状态,等待正在执行的线程执行完成在关闭
shutdownNow
强硬,直接停止正在执行的线程,关闭线程池
isShutdown
isTerminated
awaitTermination
invokeAll
invokeAny
AbstractExecutorService抽象类
ThreadPoolExecutor类(JDK1.5)√
ScheduledThreadPoolExecutor(JDK1.5)
线程池(ThreadPoolExecutor)的主要参数
核心线程数corePoolSize
线程池中的核心线程数,当提交一个任务时,线程池创建一个新线程执行任务,直到当前线程数等于corePoolSize, 即使有其他空闲线程能够执行新来的任务, 也会继续创建线程;如果当前线程数为corePoolSize,继续提交的任务被保存到阻塞队列中,等待被执行
最大线程数maximumPoolSize
线程池中允许的最大线程数。如果当前阻塞队列满了,且继续提交任务,则创建新的线程执行任务,前提是当前线程数小于maximumPoolSize;当阻塞队列是无界队列, 则maximumPoolSize则不起作用, 因为无法提交至核心线程池的线程会一直持续地放入workQueue.
空闲线程存活时间keepAliveTime
线程空闲时的存活时间,即当线程没有任务执行时,该线程继续存活的时间;默认情况下,该参数只在线程数大于corePoolSize时才有用, 超过这个时间的空闲线程将被终止;
存活时间的单位unit
keepAliveTime的单位
等待任务的阻塞队列workQueue
ArrayBlockingQueue
基于数组结构的有界阻塞队列,按FIFO排序任务
LinkedBlockingQueue
基于链表结构的阻塞队列,按FIFO排序任务,吞吐量通常要高于ArrayBlockingQueue
SynchronousQueue
一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue
PriorityBlockingQueue
具有优先级的无界阻塞队列
......
创建线程的工厂threadFactory
默认为DefaultThreadFactory
拒绝策略handler
AbortPolicy:默认,直接抛出RejectedExcutionException异常,阻止系统正常运行
CallerRunsPolicy:该策略既不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用者
DiscardOldestPolicy:抛弃队列中等待最久的任务,然后把当前任务加入队列中尝试再次提交当前任务
DiscardPolicy:直接丢弃任务,不予任何处理也不抛出异常,如果运行任务丢失,这是一种好方案
自定义:实现RejectedExecutionHandler接口
线程池的处理流程(工作原理)
提交一个任务,首先判断核心线程池是否已满
如果没满,创建线程执行任务
如果满了,接着判断阻塞队列是否已满
如果没满,将任务存储到队列里
如果满了,判断线程池是否已满
如果没满,创建线程执行任务
如果满了,按照策略处理无法执行的任务
内置实现
Executor工具类(JDK1.5)
FixedThreadPool(JDK1.5)
这种类型的池始终具有指定数量的线程正在运行
WorkStealingPool(ForkJoinPool)(JDK1.8)
ScheduledThreadPool(JDK1.5)
SingleThreadExecutor(JDK1.5)
只有一个线程
CachedThreadPool(JDK1.5)
自定义线程池
通过ThreadPoolExecutor的构造函数进行自定义
为什么实际工作不用内置线程池?
Executors返回的线程池对象所使用的阻塞队列要么是无界的,要么上限值太大,都容易引起OOM
怎么办呢?
自定义线程池
使用权威第三方的工具包
Apache commons-lang
Google Guava
如何配置线程池参数
区分任务类型
CPU密集型
CPU密集型任务配置尽可能少的线程数量:
一般公式:CPU核数 + 1个线程数
一般公式:CPU核数 + 1个线程数
IO密集型
IO密集时,大部分线程都被阻塞,故需要多配置线程数:
参考公式:CPU核数 / (1 - 阻塞系数) 阻塞系数在0.8 ~ 0.9左右
参考公式:CPU核数 / (1 - 阻塞系数) 阻塞系数在0.8 ~ 0.9左右
如何合理的配置线程池
①判断任务特性
CPU密集型、IO密集型或者是混合型
②使用有界队列
③合理监控线程池
ForkJoinPool类(JDK1.7)
fork/join 框架是 ExecutorService 接口的一种实现,它专为可以递归地分解成较小部分的工作而设计。目标是使用所有可用的处理能力来提高应用程序的性能。
fork/join 框架使用工作窃取算法。无事可做的工作线程可能会从仍然繁忙的其他线程中窃取任务。
基础使用
1、创建一个表示要完成的所有工作的任务(类),该任务继承RecursiveAction
2、创建将运行任务的 ForkJoinPool
3、通过调用invoke方法执行任务
拓展:源码中,集合部分许多带parallel前缀的方法,本质都是使用了fork/join实现,如parallelSort(),在大型任务处理的场景上会比一般的方法快。除此之外Stream部分也有使用到fork/join
线程常用方法
public synchronized void start
注意和run()区别,直接调用run是在主线程里执行方法,调用start()本质是调用了native方法向操作系统申请了新的线程资源
join(synchronized修饰)
在一个线程中调用其他线程的join方法,实现同步执行的效果
本质是调用了Object里的wait方法,插队的线程通过那到这个锁,以形成同步执行的效果
join带超时时间
通过do-while来持有/释放锁
join不带时间
死循环,通过判断当前插队的线程是否存活来持有/释放锁
中断
public void interrupt
打断当前的线程对象。如果打断前线程就是被阻塞的状态,那么会清除中断标记,并抛出InterruptedException
public boolean isInterrupted
判断打断线程的状态,不会清除打断标记,因为就是简单的return了一个中断值
public static boolean interrupted,静态方法
判断线程是否已经中断,会清除中断标记。简而言之如果连续调用这个方法两次,第二次必定会返回false
native方法
public static native void yield
通知调度器当前线程可以让出cpu资源,但是是否让出实际是由调度器来决定的,这个方法源码注释里并不推荐使用该方法,它更多是在调试、测试场景下使用。
public static native void sleep
记住会不释放锁就行了
线程属性
线程id
线程名
线程优先级
线程状态
线程组
是否守护线程
。。。
线程生命周期
NEW
RUNNABLE
start
BLOCKED
线程竞争锁产生阻塞
WAITING
Object. wait with no timeout(JDK1.0)
Thread. join with no timeout(JDK1.0)(本质也是调的wait)
LockSupport. park(JDK5)
TIMED_WAITING
Thread. sleep(JDK1.0)
Object. wait with timeout(JDK1.0)
Thread. join with timeout(JDK1.0)(本质也是调的wait)
LockSupport. parkNanos(JDK5)
LockSupport. parkUntil(JDK5)
TERMINATED
线程的同步方式
互斥
synchronized关键字(JDK1.0)
特性
可见性
原子性
某种意义上的保证有序性
它本身并不会禁止指令重排序,但是他符合了“as-if-serial”原则,在但线程中的执行结果不会改变,因为它恁重,他都直接阻塞其他线程了,其他线程在它释放之前根本就不可能去干扰他,所以它在单线程里怎么执行都是它的事。
可重入
无条件阻塞后面其他线程进入
使用
使用方式
代码块
方法
锁对象
当前的实例对象
当前类的Class对象
底层
JVM基于进入和退出Monitor对象来实现方法同步和代码同步,再深入一点就是每一个Java对象都持有一个对应的Monitor对象,这个Monitor对象是底层C++实现的(在Java虚拟机(HotSpot)中,monitor是由OnjectMonitor实现的,其主要的数据结构如下(位于HotSpot虚拟机源码ObjectMonitor.hpp文件,C++实现)),Java对象通过对象头中的指针与它关联,再再深入一点,就是操作系统里说的管程Monitor(尚硅谷阳哥著名言论:天生飞的理念都有落地的实现,这个也是基于操作系统理念进行落地的实现)
代码块同步,字节码文件中会多出monitorenter和monitorexit指令
方法同步,字节码文件中的方法信息中会多一个ACC_SYNCHRONIZED同步标识
synchronize锁优化(JDK6)
无锁
偏向锁
轻量级锁
重量级锁
锁粗化
扩大加锁范围(如:在循环体中使用,导致每次循环都要加锁释放锁,性能开销太大,不如直接扩大范围)
适应性自旋
锁消除
检测到需要同步的代码段根本不存在竞争,进行优化
Lock接口的实现类(JDK1.5)
ReentrantLock
可重入
基础使用
1、ReentrantLock reentrantLock = new ReentrantLock()
2、reentrantLock.lock();
3、reentrantLock.unlock();
PS:加锁和解锁操作要用try- catch-finally包围
相较synchronize的优点
等待可中断
可实现公平锁
可以绑定多个条件
ReentrantReadWriteLock
底层剖析(AQS)
AbstractQueuedSynchronizer抽象类
深究AQS原理(抽象的队列同步器)
AQS 是用来构建锁或者其它同步器组件的重量级基础框架及整个JUC体系的基石, 通过内置的FIFO队列来完成资源获取线程的排队工作,并通过一个int类变量(state)表示持有锁的状态
StampedLock(JDK8)
非阻塞
CAS(atomic包)(JDK1.5)
JUC包里提供的原子类(原理就是基于Unsafe类提供的CPU指令级CAS操作+Volatile关键字)
原始类型
AtomicBoolean
AtomicInteger
AtomicLong
引用类型
AtomicMarkableReference
AtomicReference
AtomicStampedReference
CAS简述
涉及到三个操作数
内存位置V
旧预期值A
准备设置的新值B
当且仅当V符合A时,处理器才会用B更新V的值
tip:
CAS的自旋问题
自旋过久消耗性能
CAS的ABA问题
版本号
其实大部分情况下ABA问题不会影响程序并发的正确性
无锁
ThreadLocal(JDK1.2)
ThreadLocalMap(空间换时间,每个线程持有一份副本)
ThreadLocal类
空间换时间,每个线程拥有自己的一份副本变量
ThreadLocal只是一个壳子,内部使用的ThreadLocalMap类才是实质
ThreadLocalMap
当一个线程调用ThreadLocal的Set方法时,首先会从尝试直接从当前Thread的threadLocals成员变量获取,如果这个成员变量为null,则表示当前线程还没有初始化ThreadLocalMap,接着会调用createMap方法初始化当前线程的ThreadLocalMap对象,key值为当前的ThreadLocal对象,创建完成后将这个对象赋值给当前Thread的threadLocalMap对象。
InheritableThreadLocal
InheritableThreadLocal继承自ThreadLocal,重写了childValue、getMap和createMap方法,实现父子线程共享的核心是对于childValue的重写。结合Thread类的init方法来看,Thread对象在创建的时候,如果父类的inheritableThreadLocals不为空,则会调用ThreadLocal的createInheriteMap方法进行创建,接着通过childValue方法获取父线程的值,把父线程的inheritableThreadLocals的值赋值到新的ThreadLocalMap对象
ThreadLocalRandom(JDK1.7)
补充:
final(JDK1.0)
特性
天生不可变,天生线程安全,实现原理是内存屏障
关于Volatile(JDK1.0)
volatile关键字
介绍:
volatile是Java虚拟机提供的最轻量级的同步机制。volatile 关键字可以确保一个变量的更新对于所有线程都是可见的,即当一个线程修改了 volatile 变量的值,其他线程立刻可以看到最新的值,而不是从缓存中读取到旧值。
特性:
可见性
强刷缓存实现
有序性
禁止指令重排序实现
不完全保证原子性
Java运算操作符并非是原子操作,导致volatile变量的运算在并发下是不安全的
比如自增自减
底层原理:
带lock前缀的指令(内存屏障)→处理器嗅探机制,将工作线程的缓存写入内存,同时使其他线程的缓存失效,保证线程读取到的是最新值,保证可见性; 插入内存屏障(lock前缀指令),禁止指令的重排序
剖析:
volatile 关键字在 Java 中不可以完全代替线程同步,它在某些情况下可以提供线程间的可见性保证,但不具备原子性。这意味着在一些复杂的场景中,volatile 不能替代 synchronized 或其他锁机制。
什么时候使用Volatile?
如果你只需要保证某个共享变量的可见性,而不涉及复杂的读写操作,例如标志位、状态指示器等场景,volatile 是合适的选择
什么时候使用Synchronized?
如果需要保证原子性操作,或涉及多个步骤的复杂操作(如递增、累加等),synchronized 或其他更高级的同步工具(如 ReentrantLock、AtomicInteger)才是合适的。
总结:
volatile 不能用于线程同步,但可以用于保证线程间的变量可见性。
这里我又问了?你既然要用Volatile,为什么不直接用原子包呢?
如果需要保证操作的原子性,应该使用 synchronized 或其他锁机制。
线程的通信方式
Object方法(Synchronized)(JDK1.0)
方法
wait
notify
notifyall
Condition(Lock)(JDK1.5)
方法
await
signal
拓展:Condition通信的实现也是基于LockSupport的原语+AQS实现的,不过Condition要基于Lock接口的实现类创建使用,而LockSupport并未任何限制,某种意义上说,LockSupport可以用在任何地方。
LockSupport(JDK1.5)
简介:LockSupport是用来创建锁和其他同步类的基本线程阻塞原语,所有的方法都是静态方法
原理:
Unsafe类提供的阻塞、唤醒线程的Native方法
方法
park
unpark
Condition和LockSupport的区别
Condition
更高层,适合在需要结合锁和条件变量时使用,它提供了一种更结构化和线程安全的等待-通知机制
依赖于锁。一个线程必须在持有锁的情况下调用 await(),并且在持有相同锁的线程中调用 signal() 或 signalAll()
使用条件等待机制来阻塞线程,通常适用于生产者-消费者模式,或者需要在特定条件下协调多个线程
LockSupport
更底层,它直接操作线程的阻塞和唤醒,通常在更复杂的并发控制场景中使用,比如自己实现锁或者并发数据结构时
不依赖锁,它可以在任何时候阻塞或唤醒线程,无需持有锁。
采用“许可”模型,每个线程都有一个隐式许可,park() 会阻塞线程,直到线程被赋予许可。unpark() 给指定线程一个许可,使其从阻塞状态恢复
JUC提供的辅助类
CountDownLatch(JDK1.5)
顾名思义,倒计时,倒计时介绍之后目标线程执行
方法
await(), 此函数将会使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断。
countDown(), 此函数将递减锁存器的计数,如果计数到达零,则释放所有等待的线程
CyclicBarrier(JDK1.5)
类似游戏里的匹配模式,大家伙都准备好了才能开游戏
Semaphore(JDK1.5)
允许n个任务同时访问某个资源
Exchanger(JDK1.5)
用于两个线程之间交换数据
Phaser(JDK1.7)
它可以实现CyclicBarrier和CountDownLatch类似的功能,而且它支持对任务的动态调整,并支持分层结构来达到更高的吞吐量
并发工具
并发数据结构
ConcurrentHashMap(JDK1.5)
ConcurrentLinkedDeque(JDK1.7)
ConcurrentLinkedQueue(JDK1.5)
ConcurrentSkipListMap(JDK1.6)
ConcurrentSkipListSet(JDK1.6)
CopyOnWriteArrayList(JDK1.5)
CopyOnWriteArraySet(JDK1.5)
阻塞队列
ArrayBlockingQueue(JDK1.5)
DelayQueue(JDK1.5)
LinkedBlockingDeque(JDK1.5)
LinkedBlockingQueue(JDK1.5)
LinkedTransferQueue(JDK1.7)
PriorityBlockingQueue(JDK1.5)
SynchronousQueue(JDK1.5)
规范性文件详解(JDK 5)
JSR 133
JSR 133是什么?
JSR 133 为 Java 语言定义了一个新的内存模型,它修复了早期内存模型的缺陷。为此,需要更改 final 和 volatile 的语义。
什么是Java内存模型?
我们先从处理器级别的内存模型了解。CPU多级缓存的出现,在提高处理速度的同时,也带来了内存可见性的问题,试想,当两个处理器同时对内存中同一地址的值进行读取,会发生什么?在什么条件下,他们会看到相同的值呢?此外,编译器对代码的重新排序使写入何时对另一个线程可见的问题变得更加复杂。在可见性方面,内存模型定义了必要和充分的条件,以知道其他处理器对内存的写入对当前处理器可见,并且当前处理器的写入对其他处理器可见。其中,一些处理器表现出强大的内存模型,其中所有处理器在任何时候都看到任何给定内存位置的完全相同的值。而有些处理器表现出较弱的内存模型,其中需要特殊指令(称为内存屏障)来刷新本地处理器高速缓存或使其无效,以便查看其他处理器的写入或使该处理器的写入对其他人可见。这些内存屏障通常在执行锁定和解锁操作时执行;它们对于高级语言的程序员来说是不可见的。
Java 内存模型描述了多线程代码中哪些行为是合法的,以及线程如何通过内存进行交互。它描述了程序中变量之间的关系,以及在实际计算机系统中的内存或寄存器中存储和检索变量的低级细节。它以一种可以使用各种硬件和各种编译器优化正确实现的方式实现这一点。Java 包括多种语言结构,包括 volatile、final 和 synchronized,它们旨在帮助程序员向编译器描述程序的并发要求。Java 内存模型定义了 volatile 和 synchronized 的行为,更重要的是,确保正确同步的 Java 程序在所有处理器体系结构上正确运行。
ChatGPT的回答:
Java内存模型 (Java Memory Model, JMM) 是Java语言规范的一部分,用于定义Java线程如何通过内存进行交互。它规定了一个线程对变量(如实例字段、静态字段和数组元素)的修改如何变得对其他线程可见,确保并发程序中的行为一致且可预测
内存模型的核心概念
主内存与工作内存
主内存: 指的是堆内存,所有对象实例、静态字段和数组元素都存储在这里。
工作内存: 每个Java线程都有自己的工作内存(类似本地缓存),其中存储了从主内存读取的变量的副本。当一个线程修改变量时,它会先修改工作内存中的副本,除非将这些修改刷新到主内存,否则其他线程可能看不到这些变化。
可见性和有序性
可见性: 如果一个线程修改了某个变量,其他线程需要能够看到这个变化。如果没有适当的同步,其他线程可能会看到过时或不一致的值。
有序性: JMM允许某些优化(如指令重排序)以提高性能,但通过同步机制(如volatile、synchronized和final)提供了操作顺序的保证。
先行发生原则 (Happens-Before)
JMM使用“先行发生”关系来定义线程之间内存可见性的规则。如果一个操作先行发生于另一个操作,则第一个操作对第二个操作是可见的。
规则包括
线程启动和终止
线程中的每个操作都发生在该线程中按程序顺序稍后出现的每个操作之前。
对 start() 线程的调用发生在启动的线程中的任何操作之前。
线程中的所有操作都发生在任何其他线程成功从该线程 join() 返回之前。
锁定
监视器上的解锁发生在同一监视器上的每个后续锁定之前。
volatile变量
对 volatile 字段的写入发生在对同一 volatile 的每次后续读取之前。
传递性
如果A先行发生于B,且B先行发生于C,则A先行发生于C
volatile关键字
被声明为volatile的字段确保对该变量的读写操作直接发生在主内存中。它还提供了有序性的保证
当一个线程写入一个volatile变量时,它会将该线程之前所有的变量修改刷入主内存
当一个线程读取volatile变量时,它会强制从主内存读取最新值
synchronized关键字
synchronized块实现了互斥锁,即同一时刻只有一个线程可以执行该代码块。它还提供了内存可见性保证,通过先行发生原则确保
final关键字
JMM对final字段提供了特殊的保证
一旦final字段在构造函数中被初始化,并且构造函数结束,其他线程将能够看到该字段的正确值,无需额外的同步。
JMM解决的常见问题
可见性
指令重排序
原子性
说人话
Java内存模型(JMM)主要是关于规范在使用volatile、synchronized和final等关键字时,线程间的可见性、顺序性以及原子性会如何表现。但JMM并不规定这些关键字的底层实现细节,只规定了它们在并发环境下应该遵守的行为和效果。具体的底层实现由Java虚拟机(JVM)和硬件层面去完成。
底层实现细节
JVM的实现
不同的JVM实现可能会根据不同的硬件平台、操作系统,采用不同的优化策略和指令来实现JMM的规范。例如,JVM在遇到volatile变量时,可能会在内存屏障(memory barriers)前后插入指令来保证顺序性和可见性,而在synchronized块中,JVM可能会使用CPU的硬件锁指令(如lock指令)来实现锁的机制
硬件层面
不同的CPU架构有不同的内存模型(如x86、ARM等),这些架构可以决定指令执行的顺序和缓存一致性。JMM保证了在这些不同平台上,Java的多线程行为是可预测的和一致的
常用API/类
String
查找
indexOf
charAt
截取
subString
split
替换
replace
大小写转换
去空
trim
开头结尾
length
getbytes
toString
StringBuilder
线程不安全
StringBuffer
线程安全
Object类
public final native Class<?> getClass()
public native int hashCode()
public boolean equals(Object obj)
public String toString()
public final native void notify()
public final native void notifyAll()
public final void wait
日期类
Date
SimpleDateFormat
LocalDate
LocalDateTime
LocalTime
System类
getProperties
createProperties
currentTimeMillis
exit
Runtime类
addShutdownHook
exec
exit
freeMemory
getRuntime
maxMemory
removeShutdownHook
totalMemory
version
Math类
比较器(引用数据类型的比较排序)
自然排序java.lang.Comparable接口
实现 compareTo(Object obj)方法
自定义排序java.util.Comparator接口
创建一个比较器类,重写 compare(Object o1,Object o2)方法
☆集合
Collection接口
接口方法
int size();
boolean isEmpty();
boolean contains(Object o);
Iterator<E> iterator();
toArray();
boolean add(E e);
boolean remove(Object o);
boolean containsAll(Collection<?> c);
boolean addAll(Collection<? extends E> c);
boolean removeAll(Collection<?> c);
boolean retainAll(Collection<?> c);
void clear();
常用实现类/接口
List接口(有序可重复)
ArrayList
底层数组、线程不安全
1.5倍扩容
未显示初始化,默认0,添加第一个元素时在扩容为10
LinkedList
底层双向链表
Vector
数组,线程安全
2倍扩容
未显示初始,默认10
Queue
Set接口(无序不可重复)
HashSet
底层是HashMap的key
集合元素可以是 null
LinkedHashSet
TreeSet
迭代器(Iterator)接口
遍历元素
PS:通过迭代器遍历元素时,不要使用集合的remove方法删除元素,会报并发修改异常,请使用迭代器提供的方法删除。
这里很容易在用foreach语法糖的时候产生疏忽,因为它本质也是通过迭代器遍历的。
这里很容易在用foreach语法糖的时候产生疏忽,因为它本质也是通过迭代器遍历的。
Map(K,V)接口
HashMap类
Node数组+链表+红黑树
默认的初始容量
16
链表树化的条件
索引位置上的链表的长度达到 8,且数组的长度超过 64时,此索引位置上的元素要从单向链表改为红黑树。如果链表长度到8但是数组没到64,会先扩容
红黑树链表化的条件
索引位置上的元素的个数低于 6 时,要从红黑树改为单向链表
细节探索:
哈希值的计算
为了降低哈希冲突的概念,用 key 的 hashCode 值高 16 位与低 16 位进行了异或的干扰运算
下标的技术
(n - 1) & hash(√)
hash 值 % table.length,效率没有上面高
为什么数组是2次幂?
可以保证每一个下标位置都有机会被用
到
到
哈希冲突问题的解决
拉链法
尾插法
拓展
关于哈希冲突
从设计上来说,理论上当应用程序中的对象达到10w数量级时,才会开始出现哈希冲突。
JDK1.7 中 HashMap 的循环链表
为什么会产生
头插法+链表+多线程
并发+扩容
并发+扩容
jdk8怎么解决
HashMap 改用尾插法,解决了链表死循环的问题
LinkedHashMap类
在 HashMap 存储结构的基础上,使用了一对
双向链表来记录添加元素的先后顺序,可以保证遍历元素时,与添加的顺序一致
双向链表来记录添加元素的先后顺序,可以保证遍历元素时,与添加的顺序一致
HashTable类(数组+链表)
Properties类
TreeMap类
红黑树
LinkedListHashMap类
ConcurrentHashMap类
拓展
关于equals和hashcode的面试题,我的建议是直接看源码注释,哪里有什么花里胡哨的,人家注释里hashcode方法就是写给map用的,还有这里方法的逻辑关系,是从源码里面推出来的,源码里hashcode结果一致还会接着用equal判断一致,equal一致,那hashcode肯定一致啦
Collections工具类
排序
查找
复制、替换
添加
同步
泛型
又名参数化类型,用于解决数据类型的安全性问题
泛型工作中偶尔会用到(封装Result类)
类
接口
方法
java的泛型是编译擦除的
常用的
泛型方法
泛型接口
上下界
上界
extend
下界
super
反射
反射能做什么
在程序运行的过程中,动态的获取类的信息、动态的调用对象的属性和方法
核心类
Class类
获取Class实例的方式
类.class
对象.getClass()
Class调用静态方法forName(String className)
ClassLoader的方法loadClass(String className)
Method类
Field类
Constructor类
Annotation接口
其他类
Array
用于声明式的创建一个数组
代理
核心类/接口
InvocationHandler接口
invoke(Object proxy, Method method, Object[] args)
Proxy类
newProxyInstance(Class<?> caller,
Constructor<?> cons,
InvocationHandler h)
Constructor<?> cons,
InvocationHandler h)
IO(java.io)
老实说我不是很想讲这个,工作中要使用基本都是调人家封装好的,像hutool和guava、apach的lang
File类
IO流
流的分类
数据的流向不同分为:输入流和输出流
输入流 :把数据从其他设备上读取到内存中的流(以 InputStream、Reader 结尾)
输出流 :把数据从内存 中写出到其他设备上的流(以 OutputStream、Writer 结尾)
按操作数据单位的不同分为:字节流(8bit)和字符流(16bit)。
字节流 :以字节为单位,读写数据的流(以 InputStream、OutputStream 结尾)
字符流 :以字符为单位,读写数据的流(以 Reader、Writer 结尾)
根据 IO 流的角色不同分为:节点流和处理流
节点流:直接从数据源或目的地读写数据
处理流:不直接连接到数据源或目的地,而是“连接”在已存在的流(节点
流或处理流)之上,通过对数据的处理为程序提供更为强大的读写功能。
流或处理流)之上,通过对数据的处理为程序提供更为强大的读写功能。
流的API
抽象基类
InputStream
OutputStream
Reader
Writer
常用节点流
文件流: FileInputStream、FileOutputStrean、FileReader、FileWriter
字节/字符数组流: ByteArrayInputStream、ByteArrayOutputStream、
CharArrayReader、CharArrayWriter
CharArrayReader、CharArrayWriter
常用处理流
缓冲流:BufferedInputStream、BufferedOutputStream、BufferedReader、
BufferedWriter
BufferedWriter
作用:增加缓冲功能,避免频繁读写硬盘,进而提升读写效率
转换流:InputStreamReader、OutputStreamReader
作用:实现字节流和字符流之间的转换
对象流:ObjectInputStream、ObjectOutputStream
作用:提供直接读写 Java 对象功能
序列化
原理
用 ObjectOutputStream 类保存基本类型数据或对象的机制
public final void writeObject (Object obj)
用 ObjectInputStream 类读取基本类型数据或对象的机制
public final Object readObject ()
实现
类必须实现 java.io.Serializable
接口
接口
如果有一个属性不需要可序列化的,则该属性必
须注明是瞬态的,使用 transient 关键字修饰
须注明是瞬态的,使用 transient 关键字修饰
静态(static)变量的值不会序列化
提供了一个序列版本号:
serialVersionUID
serialVersionUID
网络编程
老实说我这个也不想讲,写业务根本就没有地方给你用网络编程,除非你自己造轮子
计算机网络的理论知识看计网的思维导图吧
API
InetAddress 类(IP)
Inet4Address
Inet6Address
Socket 类(IP+端口)
流套接字(stream socket):使用 TCP 提供可依赖的字节流服务
ServerSocket(服务端)
Socket(客户端)
数据报套接字(datagram socket):使用 UDP 提供“尽力而为”的数据报服
务
务
DatagramSocket
URL
URL(Uniform Resource Locator):统一资源定位符,它表示 Internet 上某一资源的地
址
址
<传输协议>://<主机名>:<端口号>/<文件名>#片段名?参数列表
URL类
针对 HTTP 协议的 URLConnection 类
新特性
Lambda表达式
参数 箭头 方法体
函数式接口
@FunctionalInterface
用于检查接口是否满足函数式接口的使用条件
标注的接口只能声明一个未实现的方法
分类
消费型接口:Consumer<T> void accept(T t)
供给型接口:Supplier<T> T get()
函数型接口:Function<T,R> R apply(T t)
判断型接口:Predicate<T> boolean test(T t)
方法引用、构造器引用、数组引用
StreamAPI
Steam特点
Stream 自己不会存储元素
Stream 不会改变源对象。相反,他们会返回一个持有结果的新 Stream
Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。即
一旦执行终止操作,就执行中间操作链,并产生结果
一旦执行终止操作,就执行中间操作链,并产生结果
Stream 一旦执行了终止操作,就不能再调用其它中间操作或终止操作了
三大步骤
创建 Stream 一个数据源(如:集合、数组),获取一个流
通过集合创建Steam
通过数组创建Stream
通过 Stream 的 of()显示创建
中间操作 每次处理都会返回一个持有结果的新 Stream,即中间操作的方法
返回值仍然是 Stream 类型的对象。因此中间操作可以是个操作链,可对数据源
的数据进行 n 次处理,但是在终结操作前,并不会真正执行
返回值仍然是 Stream 类型的对象。因此中间操作可以是个操作链,可对数据源
的数据进行 n 次处理,但是在终结操作前,并不会真正执行
筛选与切片
filter(Predicatep)
接收 Lambda , 从流中排除某些元素
distinct()
筛选,通过流所生成元素的 hashCode() 和 equals() 去
除重复元素
除重复元素
limit(long maxSize)
截断流,使其元素不超过给定数量
skip(long n)
跳过元素,返回一个扔掉了前 n 个元素的流。若
流中元素不足 n 个,则返回一个空流。与 limit(n) 互补
流中元素不足 n 个,则返回一个空流。与 limit(n) 互补
映 射
map(Function f)
接收一个函数作为参数,该函数会被应
用到每个元素上,并将其映射成一个新
的元素。
用到每个元素上,并将其映射成一个新
的元素。
mapToDouble(ToDoubleFunction f)
接收一个函数作为参数,该函数会被应
用到每个元素上,产生一个新的
DoubleStream。
用到每个元素上,产生一个新的
DoubleStream。
flatMap(Function f)
接收一个函数作为参数,将流中的每个
值都换成另一个流,然后把所有流连接
成一个流
值都换成另一个流,然后把所有流连接
成一个流
mapToInt(ToIntFunction f)
mapToLong(ToLongFunction f)
排序
sorted()
产生一个新流,其中按自然顺序排序
sorted(Comparator com)
产生一个新流,其中按比较器顺序排序
终止操作(终端操作) 终止操作的方法返回值类型就不再是 Stream 了,因此
一旦执行终止操作,就结束整个 Stream 操作了。一旦执行终止操作,就执行中
间操作链,最终产生结果并结束 Stream
一旦执行终止操作,就结束整个 Stream 操作了。一旦执行终止操作,就执行中
间操作链,最终产生结果并结束 Stream
匹配与查找
allMatch(Predicate p)
检查是否匹配所有元素
anyMatch(Predicate p)
noneMatch(Predicate p)
findFirst()
findAny()
count()
max(Comparator c)
min(Comparator c)
forEach(Consumer c)
归约
reduce(T identity,
BinaryOperator b)
BinaryOperator b)
可以将流中元素反复结合起来,得到一个
值。返回 T
值。返回 T
reduce(BinaryOperator b)
可以将流中元素反复结合起来,得到一个
值。返回 Optional
值。返回 Optional
收集
collect(Collector
c)
c)
将流转换为其他形式。接收一个 Collector 接口的实现,
用于给 Stream 中元素做汇总的方法
用于给 Stream 中元素做汇总的方法
时间API
LocalDate
LocalTime
LocalDateTime
Record
Mysql
实战内容
数据库规范设计的6个阶段
需求分析:分析用户的需求,包括数据、功能和性能需求;
概念结构设计:主要采用E-R模型进行设计,包括画E-R图;
逻辑结构设计:通过将E-R图转换成表,实现从E-R模型到关系模型的转换;
数据库物理设计:主要是为所设计的数据库选择合适的存储结构和存取路径;
数据库的实施:包括编程、测试和试运行;
数据库运行与维护:系统的运行与数据库的日常维护
分库分表
类别
水平切分
垂直切分
Sharding策略
哈希取模: hash(key) % NUM_DB
范围: 可以是 ID 范围也可以是时间范围
映射表: 使用单独的一个数据库来存储映射关系
Sharding策略带来的问题及解决方案
事务问题
使用分布式事务来解决,比如 XA 接口
链接
可以将原来的 JOIN 分解成多个单表查询,然后在用户程序中进行 JOIN
ID唯一性
使用全局唯一 ID: GUID
为每个分片指定一个 ID 范围
分布式 ID 生成器 (如 Twitter 的 Snowflake 算法)
SQL进阶实战
分页\深分页
强制索引
函数使用
分组
子查询
表连接
组合查询
视图
存储过程
游标
触发器
基础
SQL
分类
DDL(表定义)
数据库三范式
第一范式
属性不可再分,几乎所有数据库都满足
第二范式
非主键字段依赖于主键字段
第三范式
在非主键字段依赖于主键字段的前提下,强制不能传递依赖,必须直接依赖
表关系
一对一
没咋见过,大都是一张表字段太多了要拆开来提高性能
一对多
两张表
在多的一方建立外键,指向一的一方的主键(这个不是真建,只是想表达这个意思,在业务层解决)
多对多
三张表,通过一张关联表,关联两个表的主键
表设计原则
数据表的个数越少越好
数据表中的字段个数越少越好
数据表中联合主键的字段个数越少越好
数据类型
数值型
整型
TINYINT、SMALLINT、MEDIUMINT、INT(或INTEGER)、BIGINT
tips:定义表结构时指定的宽度对实际的存储并没有影响,只会影响查询时显示的宽度。
浮点
FLOAT、DOUBLE
DECIMAL
tips:小数类型为 DECIMAL,禁止使用 FLOAT 和 DOUBLE
PS:建议整数用INT、小数用Decimal
字符型
char(定长)
这个性能会比较高,浪费存储空间
varchar(变长)
尾部有填充,实际存储的容量是 长度+1字节,性能一般,节省存储空间
Innodb中建议使用这个
二进制、文本
TXT的使用容易造成空间碎片,所以频繁使用的表里不要用TxT,单独拿出一张表来放TxT
时间日期型
DATETIME(8字节)
到9999年,但是没有时区概念,项目中建议使用这个,时区的问题在显示层解决
TIMESTAMP(4字节)
到2038年,有时区概念
这里还涉及到一个性能问题:如果没有在配置文件里设置时区,那每次处理timestamp会调用操作系统的底层函数获取当前时区,所以高并发下会有性能问题,如不datetime。
YEAR
TIME
DATE
Json(mysql8)
JSON对象
JSON数组
约束
Not NULL
UNIQUE
PRIMARY KEY
FOREIGN KEY
DEFAULT
CHECK
PS:
用的比较多的就这几个,对于外键,其实也没用过在数据库层面上,基本都是在业务层解决,直接在数据库上加外键太耗性能了
建表时,为了避免null的出现,可以加 not null default '' 或 default 0。null是一种特殊值效率不高。影响提高索引效果。
自增
AUTO_INCREMENT
☆删除表操作
DROP TABLE
DML
新增、修改、删除记录
这个个人没怎么记忆,项目中基本上没有自己手写过这类SQL,直接调ORM框架封装的方法得了,够用
DQL
查询记录
基本查询
select ... from ...
别名
select c1 as c from tab1 as t
select c1 c from tab t
去重
select distinct ... from ...
着重号 ``
解决命名和系统关键字冲突的情况
条件查询
没啥好说的,where
常用运算符
算术运算
比较运算
=
!=
IS NULL
IS NOT NULL
BETWEEN AND
IN
NOT IN
LIKE
“%”:匹配0个或多个字符。
“_”:只能匹配一个字符。
逻辑运算符
not
and
or
排序查询(order by)
ASC(ascend): 升序,默认升序
DESC(descend):降序
单列排序
☆多列排序
①可以使用不在SELECT列表中的列排序。
②在对多列进行排序的时候,首先排序的第一列必须有相同的列值,才会对第二列进行排序。如果第 一列数据中所有值都是唯一的,将不再对第二列进行排序。
分页查询(LIMIT [位置偏移量,] 行数)
limit a,b
网上记忆方法花里胡哨的,个人屡了一下,就是(a,a+b],第a行条记录后面的b行记录
分页显式公式:(当前页数-1)* 每页条数,每页条数
SELECT * FROM table
LIMIT(PageNo - 1) * PageSize, PageSize;
LIMIT(PageNo - 1) * PageSize, PageSize;
这里再深入说一下,分页操作不是指查询出a-b之间的数据,而是把a+b条记录查出来,再减去前面的a条,所以遇到深度分页的情况下需要进行特殊的优化(有一说一我们在项目中除了特定的需求需要,一般都会对分页上限做限制的,防止出现深度分页,b站的个人中心里很多分页操作就禁止了深度分页)
多表查询
查询分类
自连接
没用过
内连接
取交集
外连接
左外连接
整个左边
右外连接
整个右边
满外连接(MySQL不支持)
全都要
图例
SQL99新特性
自然连接()
自动查询两张连接表中 所有相同的字段 ,然后进行 等值 连接
示例
SELECT employee_id,last_name,department_name
FROM employees e NATURAL JOIN departments d;
FROM employees e NATURAL JOIN departments d;
USING连接
支持使用 USING 指定数据表里的 同名字段 进行等值连接。但是只能配 合JOIN一起使用。
示例
SELECT employee_id,last_name,department_name
FROM employees e JOIN departments d
USING (department_id);
FROM employees e JOIN departments d
USING (department_id);
联合查询(列数、对应的数据类型要一致)(UNION)
作用:合并查询结果
union
会去重
union all(尽量用这个)
不会去重
函数
单行函数
单行函数简述:
操作数据对象
接收参数,返回一个结果
只对一行进行变换
每行返回一个结果
可以嵌套
参数可以是一列或者一个值
分类
数值函数
基本函数
ABS(x)
LEAST(e1,e2,e3…)
GREATEST(e1,e2,e3…)
MOD(x,y)
RAND()
ROUND(x)
SQRT(x)
三角函数
指数对数函数
弧度函数
进制转换
字符串函数
CONCAT(s1,s2,......,sn)
连接s1,s2,......,sn为一个字符串
REPLACE(str, a, b)
用字符串b替换字符串str中所有出现的字符串a
UPPER(s) 或 UCASE(s)
LOWER(s) 或LCASE(s)
TRIM(s)
日期和时间函数
获取日期、时间
日期与时间戳的转换
函数
函数
获取月份、星期、星期数、天数等函数
日期的操作函数
时间和秒钟转换的函数
计算日期和时间的函数
日期的格式化与解析
流程控制函数
加密与解密函数
MySQL信息函数
VERSION()
CONNECTION_ID()
DATABASE(),SCHEMA()
USER(),CURRENT_USER()、SYSTEM_USER(), SESSION_USER()
CHARSET(value)
COLLATION(value)
聚合函数
什么是聚合函数
聚合函数作用于一组数据,并对一组数据返回一个值。
常用聚合函数类型
count
count(1)√
count(*)√
count(列名)×
avg
sum
min
max
PS:NULL值不参与聚合运算
分组查询(Group by)
Group by ... having...
执行顺序 where>group by >having
where不能用聚合函数,having可以
HAVING 不能单独使用,必须要跟 GROUP BY 一起使用
tips:
①SELECT中出现的非组函数的字段必须声明在GROUP BY中。
反之,GROUP BY中声明的字段可以不出现在SELECT中
反之,GROUP BY中声明的字段可以不出现在SELECT中
②GROUP BY声明在FROM后面、WHERE后面、ORDER BY前面、LIMIT前面
Having 和 Where的区别
用法
WHERE
先筛选数据再关联,执行效率高
HAVING
可以使用分组中的计算函数
缺点
WHERE
不能使用分组中的计算函数进行筛选
HAVING
在最后的结果集中进行筛选,执行效率较低
开发中的选择
子查询
tips:子查询先于主查询执行,子查询的结果被主查询(外查询)使用
单行子查询
=
>
等等
多行子查询
IN
ANY
ALL
SOME
EXISTS 与 NOT EXISTS 关键字
NOT EXISTS
关键字表示如果不存在某种条件,则返回TRUE,否则返回FALSE
EXISTS
在子查询中不存在满足条件的行
条件返回 FALSE
继续在子查询中查找
在子查询中存在满足条件的行
不在子查询中继续查找
条件返回 TRUE
查询结构总结:
#方式1:
SELECT ...,....,...
FROM ...,...,....
WHERE 多表的连接条件
AND 不包含组函数的过滤条件
GROUP BY ...,...
HAVING 包含组函数的过滤条件
ORDER BY ... ASC/DESC
LIMIT ...,...
SELECT ...,....,...
FROM ...,...,....
WHERE 多表的连接条件
AND 不包含组函数的过滤条件
GROUP BY ...,...
HAVING 包含组函数的过滤条件
ORDER BY ... ASC/DESC
LIMIT ...,...
#方式2:
SELECT ...,....,...
FROM ... JOIN ...
ON 多表的连接条件
JOIN ...
ON ...
WHERE 不包含组函数的过滤条件
AND/OR 不包含组函数的过滤条件
GROUP BY ...,...
HAVING 包含组函数的过滤条件
ORDER BY ... ASC/DESC
LIMIT ...,...
SELECT ...,....,...
FROM ... JOIN ...
ON 多表的连接条件
JOIN ...
ON ...
WHERE 不包含组函数的过滤条件
AND/OR 不包含组函数的过滤条件
GROUP BY ...,...
HAVING 包含组函数的过滤条件
ORDER BY ... ASC/DESC
LIMIT ...,...
执行顺序:
FROM -> WHERE -> GROUP BY -> HAVING -> SELECT 的字段 -> DISTINCT -> ORDER BY -> LIMIT
DCL(用户、权限控制)
用户权限控制
这个我作为开发也没确实没机会接触
创建用户
CREATE USER 用户名 [IDENTIFIED BY'密码'][,用户名 [IDENTIFIED BY '密码']];
修改用户
Update mysql.user
删除用户
delete from mysql.user
drop User(√)
视图
定义:一个或者多个数据表里的数据的逻辑显示,视图并不存储数据。视图是一种虚拟表,本身不具备数据,占用内存很少
视图的作用:可以将视图理解为存储起来的 SELECT 语句,对查询语句的复用,简化查询。可以用于数据脱敏、权限控制等场景
视图的使用:
CREATE VIEW 视图名称
AS 查询语句
AS 查询语句
PS:视图的创建和删除不会影响基本表;视图和基本表对数据的CURD会互相作用。
存储过程与存储函数
存储过程
作用:提高了sql语句的重用性
CREATE PROCEDURE 存储过程名(IN|OUT|INOUT 参数名 参数类型,...)
[characteristics ...]
BEGIN
存储过程体
END
[characteristics ...]
BEGIN
存储过程体
END
调用存储过程
call
函数
变量、流程控制、游标(略)
触发器
CREATE TRIGGER 触发器名称
{BEFORE|AFTER} {INSERT|UPDATE|DELETE} ON 表名
FOR EACH ROW
触发器执行的语句块
{BEFORE|AFTER} {INSERT|UPDATE|DELETE} ON 表名
FOR EACH ROW
触发器执行的语句块
高级
MySQL逻辑架构
架构分层
连接层
建立tcp连接、认证、授权
服务层
接收SQL指令、SQL解析、SQL优化
存储引擎层
从磁盘中提取数据
执行一条 SQL 查询语句,期间发生了什么?
连接器:建立连接,管理连接、校验用户身份
查询缓存:查询语句如果命中查询缓存则直接返回,否则继续往下执行。MySQL 8.0 已删除该模块(命中率低,维护成本高)
解析 SQL,通过解析器对 SQL 查询语句进行词法分析、语法分析,然后构建语法树,方便后续模块读取表名、字段、语句类型
执行 SQL:执行 SQL 共有三个阶段
预处理阶段:检查表或字段是否存在;将 select * 中的 * 符号扩展为表上的所有列
优化阶段:基于查询成本的考虑, 选择查询成本最小的执行计划
逻辑查询 优化
通过改变SQL语句的内容来使得SQL查询更高效
物理查询 优化
基于关系代数进行的查询重写(索引/表连接)
执行阶段:在执行之前需要判断当前用户是否具有执行该查询的权限,然后根据执行计划执行 SQL 查询语句,从存储引擎读取记录,返回给客户端
进阶:Innodb的Buffer Pool
作用
减少磁盘IO,提高操作效率
InnoDB 存储引擎是以页为单位来管理存储空间的,我们进行的增删改查操作其实本质上都是在访问页
面(包括读页面、写页面、创建新页面等操作)。而磁盘 I/O 需要消耗的时间很多,而在内存中进行操
作,效率则会高很多,为了能让数据表或者索引中的数据随时被我们所用,DBMS 会申请 占用内存来作为
数据缓冲池 ,在真正访问页面之前,需要把在磁盘上的页缓存到内存中的 Buffer Pool 之后才可以访
问。
面(包括读页面、写页面、创建新页面等操作)。而磁盘 I/O 需要消耗的时间很多,而在内存中进行操
作,效率则会高很多,为了能让数据表或者索引中的数据随时被我们所用,DBMS 会申请 占用内存来作为
数据缓冲池 ,在真正访问页面之前,需要把在磁盘上的页缓存到内存中的 Buffer Pool 之后才可以访
问。
内容
数据页、索引页、插入缓冲、锁信息、自适应 Hash 和数据字典
信息等
信息等
特性
预读
基于局部性原理,我们使用了一些数据,大概率还会使用它周围的一些数据,使用预读对数据进行提前加载,可以减少磁盘IO
checkpoint 的机制
存储引擎(9个)
InnoDB(默认)
特征
外键(重点记忆)
事务支持(重点记忆)
行锁(重点记忆)
热备份,崩溃恢复
MVCC
聚簇索引
存储方式
最小存储单元是页,一个页的大小是16KB
.ibd文件
MyISAM(没用过)
表锁
不支持事务
不支持外键
没有热备份
Memory(没用过)
内存型,不能持久化
CSV(没用过)
存储数据时,以逗号分隔数据项
ARCHIVE(没用过)
用于数据归档,仅支持插入和查询操作
行级锁
存储空间优化
BlackHold,字面意义,黑洞,所有写入都是无意义的,查询结果也都为空(没用过)
......
索引
索引是一种用于快速查询和检索数据的数据结构
好处
查询快,减少磁盘IO
缺点
需要维护,占磁盘空间
索引的数据结构
B树
①所有节点都能存数据
②叶子节点间没有双向链表
B+树
PS:问的比较多的是和B树、红黑树相比,优点在哪里
①数据存储在叶子结点
②叶子结点之间有双向的指针,提高查询效率
③层级少,高度低,查的快
Hash
不支持范围查询
不支持模糊查询
有hash冲突问题
对排序不友好
全文索引
ES
Innodb有,但是我没用过
R树
用的比较少,用于处理地理位置信息
索引类型
主键索引
PRIMARY KEY
唯一索引
UNIQUE
普通索引
INDEX
全文索引
FULLTEXT
聚集索引与非聚集索引(二级索引)(聚簇不聚簇不是索引的类型,是数据的存储方式)
聚集索引即索引结构和数据一起存放的索引。主键索引属于聚集索引(叶子结点存放的是数据)
.ibd文件
非聚集索引即索引结构和数据分开存放的索引(叶子结点存放的是数据的主键,需要通过主键回表查询)
聚集索引的选择
存在主键,主键就是聚集索引
不存在主键,将第一个使用唯一索引的作为聚集索引
没有主键或合适的唯一索引,生成隐藏id作为聚集索引
回表查询
从二级索引中获得数据的主键值,再去聚集索引中查找,应该尽量避免回表查询
索引的设计、使用原则
适合创建索引的情况
具有唯一性的字段
频繁作为where条件使用的列
经常用于group by和order by的列
Distinct去重的列
最频繁的列放到联合索引的左侧
在多个字段都要创建索引的情况下,联合索引优于单值索引
限制索引的数目
不适用索引的情况
在where中使用不到的字段,不要设置索引
数据量小的表最好不要使用索引
有大量重复数据的列上不要建立索引
.避免对经常更新的表创建过多的索引
不建议用无序的值作为索引
删除不再使用或者很少使用的索引
不要定义冗余或重复的索引
不要在索引列上进行运算
Innodb数据存储结构
页
InnoDB将数据划分为若干个页,一个页中可以存储多个行记录,InnoDB中页的大小默认为16KB。页可以不 在物理结构上相连 ,只要通过 双向链表 相关联即可。每个数据页中的记录会按照主键值从小到大的顺序组成一个 单向链表 ,每个数据页都会为存储在它里边的记录生成一个页目录 ,在通过主键查找某条记录的时候可以在页目录 中使用二分法 快速定位到对应的槽,然后再遍历该槽对应分组中的记录即可快速找到指定的记录。
内部结构
文件头
38字节
文件头,描述页的信息
页头
56字节
页头,页的状态信息
行记录
最大最小记录
26字节
最大和最小记录,这是两个虚拟的行记录
用户记录
不确定
用户记录,存储行记录内容
空闲空间
不确定
空闲记录,页中还没有被使用的空间
页目录
不确定
页目录,存储用户记录的相对位置
文件尾
8字节
文件尾,校验页是否完整
页的上层结构
行→页→区→段→表空间
区
区(Extent)是比页大一级的存储结构,在InnoD8 存储引擎中,一个区会分配64 个连续的页。因为 InnoDB 中的页大小默认是 16KB,所以一个区的大小是 64*16KB= 1MB
段
段(Segment)由一个或多个区组成,区在文件系统是一个连续分配的空间(在 InnoDB 中是连续的 64 个页)不过在段中不要求区与区之间是相邻的。段是数据库中的分配单位,不同类型的数据库对象以不同的段形式存在。当我们创建数据表、索引的时候,就会相应创建对应的段,比如创建一张表时会创建一个表段,创建一个索引时会创建一个索引段。
表空间
表空间(Tablespace)是一个逻辑容器,表空间存储的对象是段,在一个表空间中可以有一个或多个段,但是一个段只能属于一个表空间。数据库由一个或多个表空间组成,表空间从管理上可以划分为 系统表空间 、 用户表空间、撤销表空间、 临时表空间 等。
性能分析/优化
数据库整体优化思路
通过系统层面的性能参数查看
SHOW [GLOBAL|SESSION] STATUS LIKE 'Connections';
连接MySQL服务器的次数
SHOW [GLOBAL|SESSION] STATUS LIKE 'Uptime';
服务器的上线时间
SHOW [GLOBAL|SESSION] STATUS LIKE 'Slow_queries';
慢查询的次数
开启慢查询日志(实际工作不怎么用)
开启 slow_query_log
set global slow_query_log='ON';
查看日志文件
show variables like '%slow_query_log';
修改 long_query_time 阈值
set global long_query_time = 1;
通过mysqldumpslow命令分析慢查询日志(详情使用通过help参数获取)
mysqldumpslow -s t -t 5 /var/lib/mysql/test-slow.log
关闭慢查询日志
SET GLOBAL slow_query_log=off;
profile查看sql执行成本(默认关闭)
navicat执行完返回的结果分析用的这个
开启
set profiling = 'ON';
查看(默认最近15条)
show profiles;
详情查看
show profile cpu,block io for query 2
☆分析sql语句explain(DESC)
☆id
查询序列号,id相同执行顺序从上到下;不同值越大优先级越大,NULL为最后执行
select_type
simple
简单表,不使用表连接或者子查询
PRIMARY
复杂查询中最外层的 select
union
UNION 中的第二个或者后面的查询语句
SUBQUERY
SELECT/WHERE之后包含了子查询
derived
包含在 from 子句中的子查询。
......
table
涉及到的表名
partitions
如果查询的是基于分区的表,该字段显示查询将会访问的分区
☆type
结果值从最好到最坏依次是: system > const > eq_ref > ref > fulltext > ref_or_null > index_merge >
unique_subquery > index_subquery > range > index > ALL 其中比较重要的几个提取出来(见上图中的蓝
色)。SQL 性能优化的目标:至少要达到 range 级别,要求是 ref 级别,最好是 consts级别。(阿里巴巴
开发手册要求)
unique_subquery > index_subquery > range > index > ALL 其中比较重要的几个提取出来(见上图中的蓝
色)。SQL 性能优化的目标:至少要达到 range 级别,要求是 ref 级别,最好是 consts级别。(阿里巴巴
开发手册要求)
NULL
MySQL 能在优化阶段分解查询语句,在执行阶段不需访问表或索引
system
该表只有一行,是 const 的特例(多见于MyISAM)
const
该表最多有一个匹配的行,MySQL 能对查询的某部分进行优化并将其转化成一个常量,因为只有一个匹配的行,所以速度非常快。
例:根据主键或者唯一二级索引列与常数进行等值匹配
例:根据主键或者唯一二级索引列与常数进行等值匹配
eq_ref
primary key 或 unique key 索引的所有部分被连接使用,最多只会返回一条符合条件的记录
ref
相比 eq_ref,不使用唯一索引,而是使用普通索引或者唯一性索引的部分前缀,索引要和某个值比较,可能会得到多个结果。
例:通过普通的二级索引列与常量进行等值匹配
例:通过普通的二级索引列与常量进行等值匹配
range
范围扫描通常出现在 in,between,>,<,>=等操作中,使用一个索引来检查给定范围的行
index
扫描全索引就能拿到结果,一般是扫描某个二级索引,对二级索引的叶子节点进行遍历和扫描,所以速度较慢,而二级索引一般比较小,所以比 ALL 快
ALL
全表扫描,扫描聚簇索引的所有叶子节点,通常这种情况需要增加索引进行优化
☆possible_key
显示可能应用在这张表上的索引,一个或多个。有时显示的是 NULL 值,是因为 MySQL 判断表中数据不多,不需要使用索引查询,选择全表查询
☆key
实际使用的索引,如果为NULL,则没有使用索引。如果强制 MySQL 使用或忽视 possible_keys 列中的索引,在查询中使用 force index、ignore index。
☆key_len
表示索引中使用的字节数, 该值为索引字段最大可能长度,并非实际使用长
度,在不损失精确性的前提下, 长度越短越好。
度,在不损失精确性的前提下, 长度越短越好。
ref
当使用索引列等值查询时,与索引列进行等值匹配的对象信息
☆rows
MySQL认为必须要执行查询的行数,在innodb引擎的表中,是一个估计值,
可能并不总是准确的。越小越好。
可能并不总是准确的。越小越好。
filtered
表示返回结果的行数占需读取行数的百分比, filtered 的值越大越好。对于单表查询来说,这个filtered的值没有什么意义。
☆Extra
一些额外的信息
索引与查询优化
查询优化
插入数据
批量插入数据用load
主键顺序插入(自增)性能高于非顺序插入(UUID)
非顺序插入会导致页分裂
Order by优化
using filesort
不通过索引,全表扫
using index
通过索引
通过索引提高效率,遵循最左前缀
Group by 优化
通过索引提高效率,遵循最左前缀
Limit越往后查询效率越低(深分页)
通过覆盖索引+子查询优化
将limit偏移量转化为条件
该方案适用于主键自增的表,可以把Limit 查询转换成某个位置的查询 。
COUNT()统计
使用count(1)或者count(*)
update 优化
条件用索引,防止行锁升级为表锁
关联查询优化
LEFT JOIN 时,选择小表作为驱动表, 大表作为被驱动表 。减少外层循环的次数。
INNER JOIN 时,MySQL会自动将 小结果集的表选为驱动表 。选择相信MySQL优化策略。
能够直接多表关联的尽量直接关联,不用子查询。(减少查询的趟数)。不建议使用子查询,建议将子查询SQL拆开结合程序多次查询,或使用 JOIN 来代替子查询。
子查询优化
在MySQL中,可以使用连接(JOIN)查询来替代子查询。连接查询 不需要建立临时表 ,其 速度比子查询
要快 ,如果查询中使用索引的话,性能就会更好。
要快 ,如果查询中使用索引的话,性能就会更好。
给字符串添加索引
前缀索引
MySQL是支持前缀索引的。默认地,如果你创建索引的语句不指定前缀长度,那么索引就会包含整个字
符串。
符串。
使用前缀索引,定义好长度,就可以做到既节省空间,又不用额外增加太多的查询成本。
索引下推
只能用于二级索引(secondary index)当SQL使用覆盖索引时,不支持ICP优化方法。
索引优化
索引失效的情况
where没有索引列
未正确使用联合索引(不符合最左前缀)导致索引失效
计算、函数、类型转换(自动或手动)导致索引失效
范围条件右边的列索引失效
不等于(!= 或者<>)索引失效
is null可以使用索引,is not null无法使用索引
like以通配符%开头索引失效
OR 前后存在非索引的列,索引失效
数据库和表的字符集统一使用utf8mb4。。不
同的 字符集 进行比较前需要进行 转换 会造成索引失效。
同的 字符集 进行比较前需要进行 转换 会造成索引失效。
其他优化
整体优化思路
数据库选型
合理的表结果设计
字段
类型
索引
约束
存储引擎
查询优化
库级优化
读写分离
数据一致性
宕机问题
数据分片(分库分表)
水平分
垂直分
问题
事务
主键唯一性
JOIN
内部优化
可预测读
可预测读是一种隔离级别策略,主要应用于 InnoDB 存储引擎。它确保在读取数据时不需要等待行级锁,从而减少锁争用,提高并发性能。
自适应哈希
当InnoDB发现某些索引值被⾮ 常频繁地被访问时,它会在原有的B-tree索引之上,在内存中再构建⼀个哈希索引。这就让B-tree索引也 具备了⼀些哈希索引的优势, 这个过程是完全⾃动化的,⽤户⽆法进⾏控制或者配置。
插入缓冲区
插入缓冲是一种优化技术,用于加速插入操作。InnoDB 在插入数据时不会立即将记录插入到索引页,而是将其缓存在插入缓冲区中。然后在后台批量处理这些插入操作,从而减少磁盘 I/O,提高插入效率。
索引下推
是一种数据库查询优化技术。它的主要作用是将部分过滤操作从MySQL服务器层下推到存储引擎层,即在索引遍历过程中,对索引中包含的所有字段先做判断,过滤掉不符合条件的记录之后再回表,这样可以有效地减少回表次数。
事务(Innodb)
事务的定义
事务定义了一组逻辑操作单元,使数据从一种状态变换到另一种状态。
事务的使用
显示事务
START TRANSACTION 或者 BEGIN ,作用是显式开启一个事务。
COMMIT;
ROLLBACK;
隐式事务
ACID
原子性(A)
原子性保证了一个事务内的多个操作要么都生效要么都不生效,不会存在中间状态
使用 undo log(回滚日志) 来保证事务的原子性。
undo log
UNDO LOG 称为 回滚日志 ,回滚行记录到某个特定版本,用来保证事务的原子性
分类
insert undo log
update undo log
undo log是逻辑日志,对事务回滚时,只是将数据库逻辑地恢复到原来的样子。
一致性(C)
系统中所有的数据都是符合期望/完整性约束的,且相互关联的数据之间不会产生矛盾
保证了事务的持久性、原子性、隔离性之后,一致性才能得到保障。
隔离性(I)
隔离性保证了并发情况下每个事务各自读、写的数据互相独立,不会彼此影响
MySQL InnoDB 引擎通过 锁机制、MVCC 等手段来保证事务的隔离性
MySQL并发事务的情况分类
读-读情况
读取操作本身不会对记录有任何影响,并不会引起什么
问题,所以允许这种情况的发生。
问题,所以允许这种情况的发生。
写-写情况
在这种情况下会发生 脏写 的问题,任何一种隔离级别都不允许这种问题的发生。所以在多个未提交事务
相继对一条记录做改动时,需要让它们 排队执行 ,这个排队的过程其实是通过 锁 来实现的。
相继对一条记录做改动时,需要让它们 排队执行 ,这个排队的过程其实是通过 锁 来实现的。
读-写或写-读情况
读-写 或 写-读 ,即一个事务进行读取操作,另一个进行改动操作。这种情况下可能发生 脏读 、 不可重
复读 、 幻读 的问题。
各个数据库厂商对 SQL标准 的支持都可能不一样。比如MySQL在 REPEATABLE READ 隔离级别上就已经
解决了 幻读 问题(这里存疑,网络上的说法其实各执一词)。
复读 、 幻读 的问题。
各个数据库厂商对 SQL标准 的支持都可能不一样。比如MySQL在 REPEATABLE READ 隔离级别上就已经
解决了 幻读 问题(这里存疑,网络上的说法其实各执一词)。
事务并发带来的问题
脏写
两个事务的写写情况
所有的存储引擎都不允许这种情况的出现,直接用锁解决了
脏读
一个事务读到另外一个事务未提交的数据
使用读已提交解决
不可重复读
一个事务中对同一数据的多次读取结果不一致
使用可重复读解决
幻读
一个事务中对表的多次数据查询结果不一致
使用串行化解决(其实可重复读也有对幻读作解决)
不可重复读的重点是修改,比如多次读取一条记录发现其中某些列的值被修改,幻读的重点在于新增或者删除,比如多次查询同一条查询语句(DQL)时,记录发现记录增多或减少了
隔离级别
Mysql默认隔离级别为可重复读
不同隔离级别以及幻读、不可重复读、脏读等问题都只是表面现象,是各种锁在不同加锁时间上组合应用所产生的结果,以锁为手段来实现隔离性才是数据库表现出不同隔离级别的根本原因
READ-UNCOMMITTED(读取未提交)
只加写锁
最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。
READ-COMMITTED(读取已提交)
加读锁、写锁,读锁在查询操作执行完立即释放
允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。
脏读的解决原理
一个事务在执行过程中每次执行SELECT操作时都会生成一个ReadView,ReadView的存在本身就保证了 事务不可以读取到未提交的事务所做的更改 ,也就
是避免了脏读现象;
是避免了脏读现象;
REPEATABLE-READ(可重复读)
加读锁、写锁
对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生
细说这个级别下的幻读解决方案
在读写事务下可以靠mvcc实现不加锁避免幻读
采用 加锁(for update) 方式的话, 读-写 操作彼此需要 排队执行 ,影响性能
不可重复读、幻读的解决原理
一个事务在执行过程中只有 第一次执行SELECT操作 才会生成一个ReadView,之后的SELECT操作都 复用 这个ReadView,这样也就避免了不可重复读和幻读的问题。
SERIALIZABLE(可串行化)
加读锁、写锁、范围锁
最高的隔离级别,完全服从 ACID 的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读,但是性能最差。
锁
锁的分类
根据读写类型
共享锁/读锁
针对同一份数据,多个事务的读操作可以同时进行而不会
互相影响,相互不阻塞的。
互相影响,相互不阻塞的。
排他锁/写锁
当前写操作没有完成前,它会阻断其他写锁和读锁。这样
就能确保在给定的时间里,只有一个事务能执行写入,并防止其他用户读取正在写入的同一资源。
就能确保在给定的时间里,只有一个事务能执行写入,并防止其他用户读取正在写入的同一资源。
tips:对于 InnoDB 引擎来说,读锁和写锁可以加在表上,也可以加在行上。
根据粒度
全局锁
全局锁就是对 整个数据库实例 加锁。当你需要让整个库处于 只读状态 的时候,可以使用这个命令,之后
其他线程的以下语句会被阻塞:数据更新语句(数据的增删改)、数据定义语句(包括建表、修改表结
构等)和更新类事务的提交语句。全局锁的典型使用 场景 是:做 全库逻辑备份 。
其他线程的以下语句会被阻塞:数据更新语句(数据的增删改)、数据定义语句(包括建表、修改表结
构等)和更新类事务的提交语句。全局锁的典型使用 场景 是:做 全库逻辑备份 。
表级锁
表锁
表共享锁
表独占锁
意向锁
意向共享
事务有意向对表中的某些行加共享锁(S锁)
意向排他
事务有意向对表中的某些行加排他锁(X锁)
tips:InnoDB 支持 多粒度锁(multiple granularity locking) ,它允许 行级锁 与 表级锁 共存,而意向
锁就是其中的一种 表锁 。
锁就是其中的一种 表锁 。
tips:意向锁是由存储引擎 自己维护的 ,用户无法手动操作意向锁,在为数据行加共享 / 排他锁之前,
InooDB 会先获取该数据行 所在数据表的对应意向锁 。
InooDB 会先获取该数据行 所在数据表的对应意向锁 。
自增锁
数据的插入方式
简单插入
可以 预先确定要插入的行数 (当语句被初始处理时)的语句。
批量插入
事先不知道要插入的行数 (和所需自动递增值的数量)的语句。
混合模式插入
自增锁的三种模式
innodb_autoinc_lock_mode = 0(“传统”锁定模式)
所有类型的insert语句都会获得一个特殊的表级AUTO-INC锁,并发能力不强
innodb_autoinc_lock_mode = 1(“连续”锁定模式),8.0之前默认
批量插入用表锁,简单插入用轻量级锁
innodb_autoinc_lock_mode = 2(“交错”锁定模式),8.0开始默认
在此锁定模式下,自动递增值 保证 在所有并发执行的所有类型的insert语句中是 唯一 且 单调递增 的。但
是,由于多个语句可以同时生成数字(即,跨语句交叉编号),为任何给定语句插入的行生成的值可能
不是连续的。
是,由于多个语句可以同时生成数字(即,跨语句交叉编号),为任何给定语句插入的行生成的值可能
不是连续的。
元数据锁
系统自动控制,显示使用
当对一个表做增删改查操作的时候,加 MDL读锁;当要对表做结构变更操作的时候,加 MDL 写
锁。
锁。
行级锁(通过对索引项加锁实现)
记录锁(Record Locks)
锁记录
也有读写锁之分
间隙锁(Gap Locks)
MySQL 在 REPEATABLE READ 隔离级别下是可以解决幻读问题的,解决方案有两种,可以使用 MVCC 方
案解决,也可以采用 加锁 方案解决。但是在使用加锁方案解决时有个大问题,就是事务在第一次执行读
取操作时,那些幻影记录尚不存在,我们无法给这些 幻影记录 加上 记录锁 。InnoDB提出了一种称之为
Gap Locks 的锁,不允许别的事务在当前读取的记录的id值前的记录前边的间隙插入新记录 ,
案解决,也可以采用 加锁 方案解决。但是在使用加锁方案解决时有个大问题,就是事务在第一次执行读
取操作时,那些幻影记录尚不存在,我们无法给这些 幻影记录 加上 记录锁 。InnoDB提出了一种称之为
Gap Locks 的锁,不允许别的事务在当前读取的记录的id值前的记录前边的间隙插入新记录 ,
gap锁的提出仅仅是为了防止插入幻影记录而提出的
临建锁Next-key lock
有时候我们既想 锁住某条记录 ,又想 阻止 其他事务在该记录前边的 间隙插入新记录 ,所以InnoDB就提
出了一种称之为 Next-Key Locks 的锁,Next-Key Locks是在存储引擎 innodb 、事务级别在 可重复读 的情况下使用的数据库锁,
innodb默认的锁就是Next-Key locks
出了一种称之为 Next-Key Locks 的锁,Next-Key Locks是在存储引擎 innodb 、事务级别在 可重复读 的情况下使用的数据库锁,
innodb默认的锁就是Next-Key locks
插入意向锁
InnoDB规
定事务在等待的时候也需要在内存中生成一个锁结构,表明有事务想在某个 间隙 中 插入 新记录,但是
现在在等待。InnoDB就把这种类型的锁命名为插入意向锁
定事务在等待的时候也需要在内存中生成一个锁结构,表明有事务想在某个 间隙 中 插入 新记录,但是
现在在等待。InnoDB就把这种类型的锁命名为插入意向锁
插入意向锁是在插入一条记录行前,由 INSERT 操作产生的一种间隙锁 。
事实上插入意向锁并不会阻止别的事务继续获取该记录上任何类型的锁。
事实上插入意向锁并不会阻止别的事务继续获取该记录上任何类型的锁。
根据态度
悲观锁
顾名思义,就是很悲观,对数据被其他事务的修改持保守态度,会通过数据库自身
的锁机制来实现,从而保证数据操作的排它性。
的锁机制来实现,从而保证数据操作的排它性。
乐观锁
乐观锁认为对同一数据的并发操作不会总发生,属于小概率事件,不用每次都对数据上锁,但是在更新
的时候会判断一下在此期间别人有没有去更新这个数据,也就是不采用数据库自身的锁机制,而是通过
程序来实现。在程序上,我们可以采用 版本号机制 或者 CAS机制 实现。乐观锁适用于多读的应用类型,
这样可以提高吞吐量。
的时候会判断一下在此期间别人有没有去更新这个数据,也就是不采用数据库自身的锁机制,而是通过
程序来实现。在程序上,我们可以采用 版本号机制 或者 CAS机制 实现。乐观锁适用于多读的应用类型,
这样可以提高吞吐量。
加锁方式
隐式加锁
A InnoDB的每条记录中都一个隐含的trx_id字段,这个字段存在于聚簇索引的B+Tree中。
B 在操作一条记录前,首先根据记录中的trx_id检查该事务是否是活动的事务(未提交或回滚)。如果是活
动的事务,首先将 隐式锁 转换为 显式锁 (就是为该事务添加一个锁)。
动的事务,首先将 隐式锁 转换为 显式锁 (就是为该事务添加一个锁)。
C 检查是否有锁冲突,如果有冲突,创建锁,并设置为waiting状态。如果没有冲突不加锁,跳到E。
D. 等待加锁成功,被唤醒,或者超时。
E. 写数据,并将自己的trx_id写入trx_id字段。
显示加锁
显示加共享锁
select .... lock in share mode
显示加排它锁
select .... for update
其他
死锁
解决策略
直接进入等待,直到超时。这个超时时间可以通过参数
innodb_lock_wait_timeout 来设置。
innodb_lock_wait_timeout 来设置。
发起死锁检测,发现死锁后,主动回滚死锁链条中的某一个事务(将持有最少行级
排他锁的事务进行回滚),让其他事务得以继续执行。将参数 innodb_deadlock_detect 设置为
on ,表示开启这个逻辑。
排他锁的事务进行回滚),让其他事务得以继续执行。将参数 innodb_deadlock_detect 设置为
on ,表示开启这个逻辑。
暴力解决,kill掉sql
InnoDB 存储引擎中的 锁结构
MVCC
什么是MVCC
多版本并发控制。顾名思义,MVCC 是通过数据行的多个版
本管理来实现数据库的 并发控制 。这项技术使得在InnoDB的事务隔离级别下执行 一致性读 操作有了保
证。换言之,就是为了查询一些正在被另一个事务更新的行,并且可以看到它们被更新之前的值,这样
在做查询的时候就不用等待另一个事务释放锁。
本管理来实现数据库的 并发控制 。这项技术使得在InnoDB的事务隔离级别下执行 一致性读 操作有了保
证。换言之,就是为了查询一些正在被另一个事务更新的行,并且可以看到它们被更新之前的值,这样
在做查询的时候就不用等待另一个事务释放锁。
当前读与快照读
当前读
针对当前读(select ... for update 等语句),是通过 next-key lock(记录锁+间隙锁)方式解决了幻读,因为当执行 select ... for update 语句的时候,会加上 next-key lock,如果有其他事务在 next-key lock 锁范围内插入了一条记录,那么这个插入语句就会被阻塞,无法成功插入,所以就很好了避免幻读问题。
快照读(一致性读)
针对快照读(普通 select 语句),是通过 MVCC 方式解决了幻读,因为可重复读隔离级别下,事务执行过程中看到的数据,一直跟这个事务启动时看到的数据是一致的,即使中途有其他事务插入了一条数据,是查询不出来这条数据的,所以就很好了避免幻读问题。
记录隐藏字段
trx_id :每次一个事务对某条聚簇索引记录进行改动时,都会把该事务的 事务id 赋值给
trx_id 隐藏列。
trx_id 隐藏列。
roll_pointer :每次对某条聚簇索引记录进行改动时,都会把旧的版本写入到 undo日志 中,然
后这个隐藏列就相当于一个指针,可以通过它来找到该记录修改前的信息。
后这个隐藏列就相当于一个指针,可以通过它来找到该记录修改前的信息。
ROW_ID
隐藏主键,有主动设置主键则不会生成
undo log版本链
每次对记录进行改动,都会记录一条undo日志,每条undo日志也都有一个 roll_pointer 属性可以将这些 undo日志
都连起来,串成一个链表
都连起来,串成一个链表
对记录每次更新后,都会将旧值放到一条 undo日志 中,就算是该记录的一个旧版本,随着更新次数
的增多,所有的版本都会被 roll_pointer 属性连接成一个链表,我们把这个链表称之为 版本链 ,版
本链的头节点就是当前记录最新的值。
每个版本中还包含生成该版本时对应的 事务id 。
的增多,所有的版本都会被 roll_pointer 属性连接成一个链表,我们把这个链表称之为 版本链 ,版
本链的头节点就是当前记录最新的值。
每个版本中还包含生成该版本时对应的 事务id 。
Read View
在 MVCC 机制中,多个事务对同一个行记录进行更新会产生多个历史快照,这些历史快照保存在 Undo Log里。如果一个事务想要查询这个行记录,需要读取哪个版本的行记录呢?这时就需要用到 Readview 了,它帮我们解决了行的可见性问题。
ReadView 就是事务A在使用MVCC机制进行快照读操作时产生的读视图。当事务启动时,会生成数据库系统当前的一个快照,InnoDB为每个事务构造了一个数组,用来记录并维护系统当前 活跃事务 的ID(“活跃”指的就是,启动了但还没提交)。
ReadView 就是事务A在使用MVCC机制进行快照读操作时产生的读视图。当事务启动时,会生成数据库系统当前的一个快照,InnoDB为每个事务构造了一个数组,用来记录并维护系统当前 活跃事务 的ID(“活跃”指的就是,启动了但还没提交)。
Read View的设计思路
使用 READ UNCOMMITTED 隔离级别的事务,由于可以读到未提交事务修改过的记录,所以直接读取记录
的最新版本就好了。
的最新版本就好了。
使用 SERIALIZABLE 隔离级别的事务,InnoDB规定使用加锁的方式来访问记录。
使用 READ COMMITTED 和 REPEATABLE READ 隔离级别的事务,都必须保证读到 已经提交了的 事务修改
过的记录。假如另一个事务已经修改了记录但是尚未提交,是不能直接读取最新版本的记录的,核心问
题就是需要判断一下版本链中的哪个版本是当前事务可见的,这是ReadView要解决的主要问题。
过的记录。假如另一个事务已经修改了记录但是尚未提交,是不能直接读取最新版本的记录的,核心问
题就是需要判断一下版本链中的哪个版本是当前事务可见的,这是ReadView要解决的主要问题。
Read View的主要内容
creator_trx_id ,创建这个 Read View 的事务 ID。
trx_ids ,表示在生成ReadView时当前系统中活跃的读写事务的 事务id列表 。
up_limit_id ,活跃的事务中最小的事务 ID。
low_limit_id ,表示生成ReadView时系统中应该分配给下一个事务的 id 值。low_limit_id 是系
统最大的事务id值,这里要注意是系统中的事务id,需要区别于正在活跃的事务ID。
统最大的事务id值,这里要注意是系统中的事务id,需要区别于正在活跃的事务ID。
ReadView的规则
如果被访问版本的trx_id属性值与ReadView中的 creator_trx_id 值相同,意味着当前事务在访问
它自己修改过的记录,所以该版本可以被当前事务访问。
它自己修改过的记录,所以该版本可以被当前事务访问。
如果被访问版本的trx_id属性值小于ReadView中的 up_limit_id 值,表明生成该版本的事务在当前
事务生成ReadView前已经提交,所以该版本可以被当前事务访问。
事务生成ReadView前已经提交,所以该版本可以被当前事务访问。
如果被访问版本的trx_id属性值大于或等于ReadView中的 low_limit_id 值,表明生成该版本的事
务在当前事务生成ReadView后才开启,所以该版本不可以被当前事务访问。
务在当前事务生成ReadView后才开启,所以该版本不可以被当前事务访问。
如果被访问版本的trx_id属性值在ReadView的 up_limit_id 和 low_limit_id 之间,那就需要判
断一下trx_id属性值是不是在 trx_ids 列表中。
断一下trx_id属性值是不是在 trx_ids 列表中。
如果在,说明创建ReadView时生成该版本的事务还是活跃的,该版本不可以被访问。
如果不在,说明创建ReadView时生成该版本的事务已经被提交,该版本可以被访问。
MVCC整体操作流程
1. 首先获取事务自己的版本号,也就是事务 ID;
2. 获取 ReadView;
3. 查询得到的数据,然后与 ReadView 中的事务版本号进行比较;
4. 如果不符合 ReadView 规则,就需要从 Undo Log 中获取历史快照;
5. 最后返回符合规则的数据。
tips:在隔离级别为读已提交(Read Committed)时,一个事务中的每一次 SELECT 查询都会重新获取一次
Read View。此时同样的查询语句都会重新获取一次 Read View,这时如果 Read View 不同,就可能产生
不可重复读或者幻读的情况。当隔离级别为可重复读的时候,就避免了不可重复读,这是因为一个事务只在第一次 SELECT 的时候会
获取一次 Read View,而后面所有的 SELECT 都会复用这个 Read View。
Read View。此时同样的查询语句都会重新获取一次 Read View,这时如果 Read View 不同,就可能产生
不可重复读或者幻读的情况。当隔离级别为可重复读的时候,就避免了不可重复读,这是因为一个事务只在第一次 SELECT 的时候会
获取一次 Read View,而后面所有的 SELECT 都会复用这个 Read View。
总结:MVCC的实现原理就是记录的隐藏字段+Undo log版本链+Read View规则,核心点在于 ReadView 的原理, READ COMMITTD 、 REPEATABLE READ 这两个隔离级别的一个很大不同就是生成ReadView的时机不同
READ COMMITTD 在每一次进行普通SELECT操作前都会生成一个ReadView
REPEATABLE READ 只在第一次进行普通SELECT操作前生成一个ReadView,之后的查询操作都重复
使用这个ReadView就好了。
使用这个ReadView就好了。
持久性(D)
持久性保证了一旦一个事务生效,就不会因为任何原因而导致其修改的内容被撤销或者丢失
MySQL InnoDB 引擎使用 redo log(重做日志) 保证事务的持久性,好像还有服务层的binlog?
redo log
REDO LOG 称为 重做日志 ,提供再写入操作,恢复提交事务修改的页操作,用来保证事务的持
久性
久性
组成部分
重做日志的缓冲 (redo log buffer) ,保存在内存中,是易失的。
重做日志文件 (redo log file) ,保存在硬盘中,是持久的。
redo log是物理日志,记录的是数据页的物理变化,undo log不是redo log的逆过程。
补充:纠正网上对于ACID的说法:ACID这四种特征并不正交,A、I、D是手段,C是目的。前者是因后者是果,弄到一块完全是为了拼凑的单词缩写
日志
undo log(前面说过了不说)
redo log(前面说过了不说)
慢查询日志(前面说过了不说)
☆二进制日志(bin log)
定义
binlog即binary log,二进制日志文件,也叫作变更日志(update log)。它记录了数据库所有执行的
DDL 和 DML 等数据库更新事件的语句,但是不包含没有修改任何数据的语句(如数据查询语句select、
show等)。MySQL 8 默认开启
DDL 和 DML 等数据库更新事件的语句,但是不包含没有修改任何数据的语句(如数据查询语句select、
show等)。MySQL 8 默认开启
作用
数据恢复
数据复制
特点
默认是1GB
文件是伪SQL的形式
binlog的形式
ROW
5.1.5版本的MySQL才开始支持row level 的复制,它不记录sql语句上下文相关信息,仅保存哪条记录被修
改。
优点:row level 的日志内容会非常清楚的记录下每一行数据修改的细节。而且不会出现某些特定情况下
的存储过程,或function,以及trigger的调用和触发无法被正确复制的问题。
改。
优点:row level 的日志内容会非常清楚的记录下每一行数据修改的细节。而且不会出现某些特定情况下
的存储过程,或function,以及trigger的调用和触发无法被正确复制的问题。
Statement这是默认的binlog格式
每一条会修改数据的sql都会记录在binlog中。
优点:不需要记录每一行的变化,减少了binlog日志量,节约了IO,提高性能。
优点:不需要记录每一行的变化,减少了binlog日志量,节约了IO,提高性能。
Mixed
Statement与Row的结合
bin log 和 redo log的比较
redo log 它是 物理日志 ,记录内容是“在某个数据页上做了什么修改”,属于 InnoDB 存储引擎层产生
的。
的。
而 binlog 是 逻辑日志 ,记录内容是语句的原始逻辑,类似于“给 ID=2 这一行的 c 字段加 1”,属于
MySQL Server 层。
MySQL Server 层。
bin log 和 redo log之间逻辑一致性的解决
InnoDB存储引擎使用两阶段提交方案
中继日志relay log
中继日志只在主从服务器架构的从服务器上存在。从服务器为了与主服务器保持一致,要从主服务器读
取二进制日志的内容,并且把读取到的信息写入 本地的日志文件 中,这个从服务器本地的日志文件就叫
中继日志 。然后,从服务器读取中继日志,并根据中继日志的内容对从服务器的数据进行更新,完成主
从服务器的 数据同步 。
取二进制日志的内容,并且把读取到的信息写入 本地的日志文件 中,这个从服务器本地的日志文件就叫
中继日志 。然后,从服务器读取中继日志,并根据中继日志的内容对从服务器的数据进行更新,完成主
从服务器的 数据同步 。
MySQL8新特性
MySQL 8中新增了 隐藏索引 和 降序索 引
更完善的JSON支持
新增了 caching_sha2_password 授权插件
支持原子数据定义语言(DDL)
默认的字符集由 latin1 更改为 utf8mb4
开始支持窗口函数
为什么?
需要用到分组统计的结果对每一条记录进行计算的场景下,使用窗口函数更好。窗口函数的作用类似于在查询中对数据进行分组,不同的是,分组操作会把分组的结果聚合成一条记录,而窗口函数是将结果置于每一条数据记录中。
分类
静态窗口函数
介绍
静态窗口函数的窗口大小是固定的,不会因为记录的不同而不同
动态窗口函数
介绍
动态窗口函数的窗口大小会随着记录的不同而变化
细分
序号函数
ROW_NUMBER()函数
ROW_NUMBER()函数能够对数据中的序号进行顺序显示
RANK()函数
使用RANK()函数能够对序号进行并列排序,并且会跳过重复的序号,比如序号为1、1、3
DENSE_RANK()函数
DENSE_RANK()函数对序号进行并列排序,并且不会跳过重复的序号,比如序号为1、1、2。
分布函数
PERCENT_RANK()函数
PERCENT_RANK()函数是等级值百分比函数。按照如下方式进行计算。
(rank - 1) / (rows - 1)
(rank - 1) / (rows - 1)
CUME_DIST()函数
CUME_DIST()函数主要用于查询小于或等于某个值的比例。
前后函数
LAG(expr,n)函数
LAG(expr,n)函数返回当前行的前n行的expr的值。
LEAD(expr,n)函数
LEAD(expr,n)函数返回当前行的后n行的expr的值。
首尾函数
FIRST_VALUE(expr)函数
FIRST_VALUE(expr)函数返回第一个expr的值。
LAST_VALUE(expr)函数
LAST_VALUE(expr)函数返回最后一个expr的值。
其他函数
NTH_VALUE(expr,n)函数
NTH_VALUE(expr,n)函数返回第n个expr的值。
NTILE(n)函数
NTILE(n)函数将分区中的有序数据分为n个桶,记录桶编号。
语法
函数 OVER([PARTITION BY 字段名 ORDER BY 字段名 ASC|DESC])
函数 OVER 窗口名 … WINDOW 窗口名 AS ([PARTITION BY 字段名 ORDER BY 字段名 ASC|DESC])
运维
主从复制
作用
读写分离
数据备份
高可用
原理
Slave 会从 Master 读取 binlog 来进行数据同步。
三个线程
一个主库线程
二进制日志转储线程 (Binlog dump thread)是一个主库线程。当从库线程连接的时候, 主库可以将二进
制日志发送给从库,当主库读取事件(Event)的时候,会在 Binlog 上 加锁 ,读取完成之后,再将锁释
放掉。
制日志发送给从库,当主库读取事件(Event)的时候,会在 Binlog 上 加锁 ,读取完成之后,再将锁释
放掉。
两个从库线程
从库 I/O 线程 会连接到主库,向主库发送请求更新 Binlog。这时从库的 I/O 线程就可以读取到主库的
二进制日志转储线程发送的 Binlog 更新部分,并且拷贝到本地的中继日志 (Relay log)。
二进制日志转储线程发送的 Binlog 更新部分,并且拷贝到本地的中继日志 (Relay log)。
从库 SQL 线程 会读取从库中的中继日志,并且执行日志中的事件,将从库中的数据与主库保持同步。
复制步骤
步骤1: Master 将写操作记录到二进制日志( binlog )。
步骤2: Slave 将 Master 的binary log events拷贝到它的中继日志( relay log );
步骤3: Slave 重做中继日志中的事件,将改变应用到自己的数据库中。 MySQL复制是异步的且串行化
的,而且重启后从 接入点 开始复制。
的,而且重启后从 接入点 开始复制。
问题
主从延迟问题
网络原因
从库压力大
大事务
一致性问题
异步复制
无法最终保证数据的一致性问题
半同步复制
无法最终保证数据的一致性问题
组复制
(√)是基于 Paxos 协议的状态机复制
数据库中间件
MyCat
ShardingSphere
备份与恢复
备份
物理备份
物理备份恢复速度比较快,但占用空间比
较大,MySQL中可以用 xtrabackup 工具来进行物理备份
较大,MySQL中可以用 xtrabackup 工具来进行物理备份
逻辑备份
逻辑备份恢复速度慢,但占用空
间小,更灵活。MySQL 中常用的逻辑备份工具为 mysqldump 。逻辑备份就是 备份sql语句 ,在恢复的
时候执行备份的sql语句实现数据库数据的重现。
间小,更灵活。MySQL 中常用的逻辑备份工具为 mysqldump 。逻辑备份就是 备份sql语句 ,在恢复的
时候执行备份的sql语句实现数据库数据的重现。
恢复
mysql –u root –p [dbname] < backup.sql
导出
使用SELECT…INTO OUTFILE导出文本文件
使用mysqldump命令导出文本文件
导入
使用LOAD DATA INFILE方式导入文本文件
使用mysqlimport方式导入文本文件
迁移
物理迁移(包括拷贝数据文件和使用 XtraBackup 备份工具两种)
适用场景
大数据整体迁移
优点
快
缺点
要停机
逻辑迁移(mysqldump)
适用场景
各种
优点
灵活
缺点
迁移时间长
JDBC(快速过两眼得了)
JDBC是什么?
一套Java与数据库系统进行通信的接口规范,由Java提供接口,由各个数据库厂商进行实现。
核心类
Driver
MySQL驱动
5.0
com.mysql.jdbc.Driver
8.0
com.mysql.cj.jdbc.Driver
加载与注册JDBC驱动
Class.forName(“com.mysql.cj.jdbc.Driver”);
Connection
创建连接
DriverManager.getConnection(url, user, password);
Statement
用于执行静态 SQL 语句
PrepareStatemant
避免SQL注入,预编译
SQL 语句被预编译并存储在此对象中,可以使用此对象多次高效地执行该语句
CallableStatement
用于执行 SQL 存储过程
ResultSet
将查询结果映射到类
封装了执行数据库操作的结果集
ResultSetMetaData
ResultSet 对象中列的类型和属性信息的对象
数据库连接池
Druid
Hikari
事务
DBUtil工具类封装
数据库连接池
Druid
Hikari
C3P0
JavaWeb(最近应该这样称呼? Jakarta EE)
前言
Web项目主要由Jakarta EE规范 Servlet.Api 提供技术支持
What is Jarkarta Servlet
Jakarta Servlet 是一个基石 Web 框架,可以充当面向演示和面向服务的 Web 应用程序。Jakarta Servlet 意在减少将 HTTP 请求转换为 Java 对象所需的样板代码,并将 Java 对象作为 HTTP 响应提供,并管理围绕它们的所有生命周期。
What is Servlet
Servlet是实现了jakarta.servlet.Servlet接口的一个Java类。Servlet接口定义了 init、service 和 destroy 等生命周期方法。在实现 HTTP 服务时,我们可以拓展jakarta.servlet.http.HttpServlet 类,该类必须扩展并覆盖其中一个 doXxx 方法
What is Servlet Container
支持基于 Jakarta Servlet 的 Web 组件的 Web 服务器模块称为 Servlet 容器。servlet 容器可以是 Jakarta 运行时的一部分,例如应用程序服务器。在将 Web 应用程序安装或部署到 servlet 容器时,可以配置 Web 组件行为的某些方面。
容器举例
Eclipse GlassFish
IBM Open Liberty
Red Hat WildFly
Apache Tomcat
Servlet的生命周期
Servlet 的生命周期由部署了 Servlet 的 Servlet 容器控制。
步骤
1. 如果 servlet 的实例不存在
加载 servlet 类
创建 servlet 类的实例
通过调用 init 方法初始化 servlet 实例
2.
servlet 容器调用服务方法,传递请求和响应对象。
Servlet的作用域
RequestScoped
它从客户端发送请求开始存在,直到它检索到相应的响应。它不会在其他地方共享。
SessionScoped
只要客户端使用相同的浏览器实例与 Web 应用程序交互,并且会话在服务器端没有超时,它就会存在。它在同一会话中的所有请求之间共享。
ApplicationScoped
只要 Web 应用程序存在,它就会存在。它在所有会话中的所有请求之间共享。
Servlet能保证并发请求出现的安全性问题嘛?
Web 容器通常会创建一个线程来处理每个请求。当资源可以并发访问时,它们的使用方式可能会不一致。第一步是确保表示资源的变量具有正确的范围,并使用尽可能窄的范围。
如果并发访问是不可避免的,那么您可以通过使用同步对象或原子对象来防止这种情况
创建和初始化 Servlet
web.xml
注解(建议使用)
①extends HttpServlet
②使用@WebServlet
③重写doGet或者doPost方法
API
Servlet请求处理流程
1. 客户端向 Web 服务器发送 HTTP 请求
2. 支持基于 Jakarta Servlet 的 Web 组件的 Web 服务器模块称为 Servlet 容器
3. servlet 容器将 HTTP 请求转换为 HttpServletRequest 对象,并准备 HttpServletResponse 对象
4. 这些对象被传递到 Web 组件,该组件可以与 Bean 或数据库交互以生成动态内容
5. Web 组件可以使用生成的动态内容填充 HttpServletResponse 对象,也可以将对象传递给另一个 Web 组件来填充它
6. servlet 容器最终将 HttpServletResponse 对象转换为 HTTP 响应,然后 Web 服务器将其返回给客户端
三大核心组件
Servlet接口
某种意义上来说,你可以认为它是一种规范,Java提供了对应的接口供我们实现,比如Tomcat这类服务端程序,我们称之为Servlet容器
使用方式
方式一:
继承HttpServlet类,重写services方法,在web.xml文件里配置servlet映射
方式二:
继承HttpServlet类,重写services方法,使用在目标类上使用@WebServlet注解,在注解信息里配置servlet注册信息
生命周期
构造
init()
只执行一次
service()
来一次请求执行一次
destory()
只执行一次
衍生类/接口
HttpServletRequest接口(请求ֿ域)
ServletConfig接口(会话域)
为Servlet提供Ӭ始Հ数的一种对,个Servlet都有自己独立唯一的ServletConfig对象
ServletContext接口(应用域)
所有的Servlet所共享
转发与重定向
服务器转发
url不会变,请求参数会透传
客户端重定向
url会变,请求参数不会透传
JSP(JavaServer Pages)
本质就是一个Servlet
JavaBean
一种可重用的 Java 类,通常用于封装数据和业务逻辑
其他组件
Filter(过滤器)
规范之一,对请求和响应进行预处理和后处理
功能
查询请求并采取相应措施
阻止请求和响应对进一步传递
修改请求头和数据。您可以通过提供请求的自定义版本来实现此目的
修改响应头和数据。您可以通过提供响应的自定义版本来实现此目的
与外部资源交互
身份验证、日志记录、图像转换、数据压缩、加密、标记流、XML 转换
核心方法(生命周期)
init
doFilter
destroy
使用
一、
声明Filter接口,重写doFilter方法
处理完过滤逻辑,filterChain.doFilter(reSuest,response)放行
web.xml文件里配置
二、
声明Filter接口,重写doFilter方法
处理完过滤逻辑,filterChain.doFilter(reSuest,response)放行
目标类上加上,@WebFilter注解,在注解信息里配置Filter信息
Listener(监听器)
用于监视 Web 应用程序中的事件,并在事件发生时执行相应的操作
★属实没怎么用过
Web context
ServletContextAttributeListener
ServletContextAttributeEvent
ServletContextListener
ServletContextEvent
Session
HttpSessionAttributeListener
HttpSessionBindingEvent
HttpSessionListener
HttpSessionActivationListener
HttpSessionEvent
Request
ServletRequestListener
ServletRequestAttributeListener
Filter和Interceptor的区别:
Filter 是 Servlet 规范中的一部分,它是基于 Java Servlet API 实现的
Interceptor 是在 Spring 框架中使用的一种拦截器,它是基于 Spring 框架的 AOP(面向切面编程)机制实现的
Filter 是通过在 web.xml 文件中配置来指定对特定 URL 或者某些 Servlet 请求进行拦截和处理的
Interceptor 主要用于拦截 Spring MVC 控制器的方法调用,对请求进行预处理和后处理
Filter 在 Servlet 容器中工作,对所有的请求和响应进行拦截,包括静态资源
Interceptor 仅作用于 Spring MVC 的控制器方法,不会拦截对静态资源的请求
会话管理
Cookie
Cookie 存储在客户端
Cookie 的生命周期由客户端控制
Cookie 可以存储少量的数据
Cookie 存储在客户端,可能会被篡改
Session
Session 存储在服务器端
Session 的生命周期由服务器控制
Session 可以存储大量的数据
Session 更安全,因为数据存储在服务器端
ORM/半ORM框架
MyBatis
MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。
Mybatis框架解决了什么问题:
手动对象关系映射
解决方式:反射
SQL 注入攻击
解决方式:预编译
配置繁琐
xml配置
结果集获取
类型处理
连接复用
连接池配置
拓展:
这里为什么不直接将Mybatis称为ORM框架呢?
其实Mybatis也没把自己成为ORM框架。mybatis属于半orm,因为sql语句需要自己写。与其他比较标准的 ORM 框架(比如 Hibernate )不同, mybatis 并没有将 java 对象与数据库关联起来,而是将 java 方法与 sql 语句关联起来。像Hibernate这种,你对Java对象的修改会直接映射到表结构更改上的喔。
核心组件
SqlSessionFactoryBuilder
通过实例化和使用它来创建SqlSessionFactory,创建完成后就可以丢弃它。所以它的最佳作用域是作为局部方法变量来使用。
SqlSessionFactory
应用运行期间一直存在。它的最佳实践应该是单例的。最佳作用域是应用级别的作用域。
SqlSession
每一个线程都应该有自己的SqlSession实例。它是线程不安全的,不能被共享。最佳作用域是会话级别的。
映射器实例
从 SqlSession 中获得。
Mybatis核心XML文件配置(一般Spring环境下才需要写)
properties标签
用于引入外部配置,进行动态替换使用
settings标签
顾名思义Mybatis是核心配置项,不过基本上都是用默认的
typeAliases标签
为 Java 类型设置一个缩写名字,降低冗余的全限定类名书写
typeHandlers(这个要了解一下)
在设置PreparedStatement中的参数或从结果集中取出一个值时,会用类型处理器将获取到的值以合适的方式转换成 Java 类型,这块也是基本用默认的。
Mybatis XML映射文件的配置
XML文件常用的顶级元素
select
insert
delete
update
sql
sql片段复用
cache
自定义缓存
cache-ref
自定义缓存
resultmap
结果集映射
还有一个#{}和${}的区别
${}直接使用字符串替换,使用的场景也比较固定,动态表名、列名
#{}使用PreparedStatement 进行预编译,效果就是会使用 ? 替代,用户入参使用的比较多,安全性高,可用避免SQL注入。如前面的typeHandlers配置项所说,它会用类型处理器将获取到的值以合适的方式转换成 Java 类型。
结果映射
ResultMap(强大的)
ResultMap一般不用显示配置,默认会找同名的属性的类型进行转换,当然如果结果映射确实比较复杂,还是得显示配置。
ResultType
结果的类型。通常 MyBatis 可以推断出来,但是为了更加准确,写上也不会有什么问题。一般简单的映射可以用这个。它内部也是会自动创建ResultMap
tips:
ResultMap和ResultType只能二选一
在简单的场景下,MyBatis 可以为你自动映射查询结果。当自动映射查询结果时,MyBatis 会获取结果中返回的列名并在 Java 类中查找相同名字的属性(忽略大小写)。 这意味着如果发现了 ID 列和 id 属性,MyBatis 会将列 ID 的值赋给 id 属性。
缓存
一级缓存
sqlSession级别
默认开启
Select语句会被缓存;
Insert、Update、Delete语句会被刷新缓存;
使用LRU算法清除不用的缓存;
能存1024个引用
Insert、Update、Delete语句会被刷新缓存;
使用LRU算法清除不用的缓存;
能存1024个引用
二级缓存
Application级别
默认不开启
缓存配置
默认情况下,只启用了本地的会话缓存,它仅仅对一个会话中的数据进行缓存
MyBatis对会话(Session)级别的一级缓存设计的比较简单,就简单地使用了HashMap来维护,并没有对HashMap的容量和大小进行限制
一级缓存是一个粗粒度的缓存,没有更新缓存和缓存过期的概念
要启用全局的二级缓存,只需要在你的 SQL 映射文件中添加一行<cache/>
MyBatis的二级缓存是Application级别的缓存,它可以提高对数据库查询的效率,以提高应用的性能
动态SQL
if
语法<if test = " 条件 ">条件通过需要执行的sql部分</if>
choose(when、otherwise)类似Java的switch
trim(where、set)
前面两种在特殊的情况下,会导致SQL格式出现问题,执行失败,这个就是比较智能,会根据不同情况进行处理
foreach
对集合进行遍历
关于模糊查询
使用方式一,#符
LIKE "%"#{name}"%"
使用方式二,字符串拼接
AND name LIKE CONCAT(CONCAT('%',#{name},'%'))
使用方式三,方法入参里直接传
比如:param.setUsername("%CD%");
分页
分页方式
MySQK内置的limit分页
缺点:基于内存的分页,查出所有记录再按偏移量和limit取结果
Mybatis自带的RowBounds分页,内部也是用limit,只是不需要我们手动写
使用 RowBounds 进行分页时,MyBatis 会先查询出整个ResultSet结果集,然后在内存中按照 offset 和 limit 进行裁剪
自定义拦截器插件进行分页
拦截StatementHandler去做分页,不过最后本质还是用limit去做分页的
使用PageHelper插件分页(也是自定义拦截器方式)(推荐使用√)
引入分页插件
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>最新版本</version>
</dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>最新版本</version>
</dependency>
配置拦截器插件
com.github.pagehelper.PageInterceptor
在代码中使用
RowBounds方式的调用(物理分页)
List<Country> list = sqlSession.selectList("x.y.selectIf", null, new RowBounds(0, 10));
PageHelper 静态方法调用(基于Mybatis拦截器)(推荐)
PageHelper.startPage(1, 10);
List<Country> list = countryMapper.selectIf(1);
List<Country> list = countryMapper.selectIf(1);
PageHelper.offsetPage(1, 10);
List<Country> list = countryMapper.selectIf(1);
List<Country> list = countryMapper.selectIf(1);
lambda用法(推荐)
Page<Country> page = PageHelper.startPage(1, 10).doSelectPage(()-> countryMapper.selectGroupBy());
常用插件(只需实现 Interceptor 接口,并指定想要拦截的方法签名即)
● Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
● ParameterHandler (getParameterObject, setParameters)
● ResultSetHandler (handleResultSets, handleOutputParameters)
● StatementHandler (prepare, parameterize, batch, update, query)
● ParameterHandler (getParameterObject, setParameters)
● ResultSetHandler (handleResultSets, handleOutputParameters)
● StatementHandler (prepare, parameterize, batch, update, query)
注解开发(×),老老实实在XML写SQL!
官方对于Mybatis-Java注解开发的说法:
不幸的是,Java 注解的表达能力和灵活性十分有限。尽管我们花了很多时间在调查、设计和试验上,但最强大的 MyBatis 映射并不能用注解来构建——我们真没开玩笑。
虽然不建议用,但是了解一下,有一些在写XML文件的时候也有用到
静态SQL
@Insert
@Update
@Delete
@Select
动态SQL
@InsertProvider
@UpdateProvider
@DeleteProvider
@SelectProvider
参数映射
@Param (√)
结果集
@ResultMap
@ResultType
SQL 语句构建器(Java API)(客观看待,至少我在实际工作中确实没见过人用过这个)
MyBatis 在 XML 映射中具备强大的 SQL 动态生成能力。但有时,我们还是需要在 Java 代码里构建 SQL 语句。
SQL 类
new SQL()
方法(好像跟关键字也没啥差别)
SELECT
SELECT_DISTINCT
FROM
JOIN
INNER_JOIN
LEFT_OUTER_JOIN
RIGHT_OUTER_JOIN
WHERE
OR
AND
GROUP_BY
HAVING
ORDER_BY
LIMIT
OFFSET
DELETE_FROM
INSERT_INTO
UPDATE
VALUES
......
原理篇
Mybatis功能原理
动态代理
使用动态代理技术完成Mapper的代理、SQL的解析
动态字节码技术
使用动态字节码技术完成对象关系的映射、延迟装配
运行逻辑
①读取、解析配置文件
②装配数据库连接以及事务管理
③读取Mapper接口以及xml文件所在的位置,完成关联
④mapper 代理对象调用 mapper 接口方法时 mybatis 利用动态代理找到对应的标签
⑤mybatis 找到对应标签 解析 SQL 表达式及动态代理完成参数的适配
⑥mybatis 使用 JDBC 执行 SQL 操作、拿到 Resultset
⑦通过配置告知 mybatis 列和对象属性的对应关系、让 mybatis 完整封装
⑧过程中预留 拦截器执行
实操篇
Spring集成Mybatis(略,现在基本上也见不到了吧)
Spring Boot集成Mybatis
原理/效果
①自动探测存在的 DataSource
②创建并注册一个 SqlSessionFactory 的实例,并将探测到的 DataSource 作为数据源
③创建并注册一个从 SqlSessionFactory 中得到的 SqlSessionTemplate 的实例
④自动扫描你的 mapper,将它们与 SqlSessionTemplate 相关联,并注入到Spring容器
基本步骤
①添加Mybatis的核心依赖
②添加mybatis-spring-boot-starter依赖
③通过@Mapper注解写Mapper类,并与xml映射
④在业务类中注入Mapper使用
进阶用法
关于Mapper扫描
MyBatis-Spring-Boot-Starter 将默认搜寻带有 @Mapper 注解的 mapper 接口。当然,你也可以通过注解自定义扫描的路径@MapperScan
配置
mapper-locations
XML 映射文件的路径
Mybatis Plus
前言:
单纯是做一下笔记,其实也没必要看,要用到什么直接去官方文档看就行了。
是一个 MyBatis (opens new window)的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作
强大的 CRUD 操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求
支持 Lambda 形式调用:通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错
支持主键自动生成:支持多达 4 种主键策略(内含分布式唯一 ID 生成器 - Sequence),可自由配置,完美解决主键问题
内置分页插件:基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通 List 查询
内置性能分析插件:可输出 SQL 语句以及其执行时间,建议开发测试时启用该功能,能快速揪出慢查询
内置全局拦截插件:提供全表 delete 、 update 操作智能分析阻断,也可自定义拦截规则,预防误操作
用过的
MybatisX代码生成
IDEA集成插件直接使用
主键自动生成(√)
从3.3.0版本开始,默认使用雪花算法结合不含中划线的UUID作为ID生成方式
策略
nextId
ASSIGN_ID
nextUUID
ASSIGN_UUID
属性填充(√)
用于在插入或更新数据时自动填充某些字段,如创建时间、更新时间等
使用步骤
1.实体类中,使用 @TableField 注解来标记哪些字段需要自动填充
2.创建一个类来实现 MetaObjectHandler 接口,并重写 insertFill 和 updateFill 方法
3.确保你的 MyMetaObjectHandler 类被 Spring 管理,可以通过 @Component 或 @Bean 注解来实现。
SQL打印(有默认开启)
多数据源(√)
这个文档要收费,所以不在这里补充。有需要免费分享的可以尝试扒一下我的社交账号私信0.0
分页插件(√)
①添加分页插件
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); // 如果配置多个插件, 切记分页最后添加
// 如果有多数据源可以不配具体类型, 否则都建议配上具体的 DbType
return interceptor;
}
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); // 如果配置多个插件, 切记分页最后添加
// 如果有多数据源可以不配具体类型, 否则都建议配上具体的 DbType
return interceptor;
}
②在自定义方法中使用
声明方法的使用返回值用IPage去接
IPage<UserVo> selectPageVo(IPage<?> page, Integer state);
通过继承Page类或者声明IPage接口进行自定义分页使用
MyPage selectPageVo(MyPage page);
IPage入参,返回List
List<UserVo> selectPageVo(IPage<UserVo> page, Integer state);
逻辑删除(√)
条件构造器(√)
用于构建复杂的数据库查询条件。Wrapper 类允许开发者以链式调用的方式构造查询条件,无需编写繁琐的 SQL 语句,从而提高开发效率并减少 SQL 注入的风险。
QueryWrapper
UpdateWrapper
LambdaQueryWrapper
LambdaUpdateWrapper
没用过但是很期待的功能
自动维护DDL(向Hibernate靠拢?)
3.5.3+版本
批量操作
一次性执行多个数据库操作,从而减少与数据库的交互次数,提高数据处理的效率和性能
数据插入(Insert)
数据更新(Update)
数据删除(Delete)
Hibernate
Mapper(TK.Mybatis)
Mybatis-Flex
Spring Framework
Spring的起源
早期的J2EE解决方案,比如EJB不好用,开发难度高、性能差,而Spring的出现就是为了简化Java EE的开发,Spring是一个轻量级的Java EE开发框架
Spring的优势
非侵入式
IOC思想
DI是具体实现
AOP
组件化
容器化
Spring核心组件(看一看就好)
Core
Beans
提供了框架的基础部分,包括控制反转和依赖注入
core
封装了 Spring 框架的底层部分,包括资源访问、类型转换及一些常用工具类
context
建立在 Core 和 Beans 模块的基础之上,集成 Beans 模块功能并添加资源绑定、数据验证、国际化、Java EE 支持、容器生命周期、事件传播等。ApplicationContext 接口是上下文模块的焦点
spEl
提供了强大的表达式语言支持
Testing
Spring 支持 Junit 和 TestNG 测试框架
DataAccess/Integration
JDBC
提供了一个 JDBC 的样例模板
ORM
提供与流行的“对象-关系”映射框架无缝集成的 API
JMS
提供一套 “消息生产者、消息消费者”模板用于更加简单的使用 JMS
Transaction
支持编程和声明式事务管理。
OXM
提供了一个支持 Object /XML 映射的抽象层实现
Web Servlet
WebSocket
提供了简单的接口,用户只要实现响应的接口就可以快速的搭建 WebSocket Server,从而实现双向通讯。
Servlet
提供了一个 Spring MVC Web 框架实现
Web
提供了基本的 Web 开发集成特性
WebFlux
Spring WebFlux 是 Spring Framework 5.x中引入的新的响应式web框架
AOP
提供了面向切面编程实现
Aspects
提供与 AspectJ 的集成,是一个功能强大且成熟的面向切面编程(AOP)框架
Messaging
IOC(控制反转)
如何理解IOC?
控制反转是一种思想,用户管理Bean的行为转变为框架管理Bean,解放双手不用自己手动New对象,向容器“申请”。创建和查找依赖对象的控制权交给了容器,由容器进行注入组合对象,达到一个解耦的效果
什么是Bean?
Bean 是由 Spring IoC 容器实例化、组装和管理的对象。
IOC配置的(二)三种方式
基于XML配置文件形式装配
通过ClassPathXmlApplicationContext装载
常用标签
beans
context:component-scan
context:annotation-config
import
bean
constructor-arg
property
代码/注解方式装配
在配置类上添加@Configuration注解,在方法上添加@Bean注解,方法返回对象,本质上也可以认为是一个XML;同时可以在配置类上声明@ComponentScan,指定要扫描的包,然后使用注解@Component,@Controller,@Service,@Repository声明要注册的Bean。
通过AnnotationConfigApplicationContext装载
常用注解
@Component
标注这是一个Bean,搭配@ComponentScan使用
@Value
为变量赋值,可用使用字面量、spel或者引用配置文件的值进行赋值
@Configuration
标注这是一个配置Bean(等效xml文件)
@ComponentScan
指定扫描路径下的Bean
@Bean
标注这是一个Spring Bean,搭配@Configuration使用
@PropertySource
指定配置文件来源,和@Value搭配使用
@Scope
标注Bean的作用域
@Condition
根据条件实现类的装配
@Import
导入Configuration类,与 Spring XML 中的 <import/> 元素等效
@ImportResource
导入Spring XML文件,内部会 XmlBeanDefinitionReader 来解析 Spring <beans/> XML 文件
容器常用操作
获取Bean
getBean()
获取Bean名称
getBeanNamesForType()
getBeanDefinitionNames()
关闭容器
close()
判断容器中是否存在Bean
containsBean()
containsBeanDefinition()
Bean的实例化方式
通过反射调用构造方法来实例化(通常情况下都是这种)
工厂实例化(不太常用)
使用场景
如果我们使用到的类是我们自己编写的,那么可以通过依赖注入做解耦。但是,有时,我们需要依赖第三方库,需要实例化并使用第三方库中的相关类,这时,耦合性需要其他方式来避免。
XML方式
实例工厂实例化
①将 class 属性留空
②在 factory-bean 属性中指定当前容器中 Bean 的名称
③使用 factory-method 属性设置工厂方法本身的名称
代码示例:
public class DefaultServiceLocator {
private static ClientService clientService = new ClientServiceImpl();
public ClientService createClientServiceInstance() {
return clientService;
}
}
public class DefaultServiceLocator {
private static ClientService clientService = new ClientServiceImpl();
public ClientService createClientServiceInstance() {
return clientService;
}
}
静态工厂方法实例化
①使用 class 属性来指定包含静态工厂方法的类
②在标签中使用factory-method属性指定静态的工厂方法
代码示例:
public class ClientService {
private static ClientService clientService = new ClientService();
private ClientService() {}
public static ClientService createInstance() {
return clientService;
}
}
public class ClientService {
private static ClientService clientService = new ClientService();
private ClientService() {}
public static ClientService createInstance() {
return clientService;
}
}
代码/注解方式
FactoryBean接口实例化
对上面两种XML形式的简化
Bean的作用域(@Scope)
singleton单例,默认
IOC容器初始化时创建
prototype多实例
获取Bean时创建
request,web环境下
session,web环境下
application,Servlet上下文(不了解)
websocket(不了解)
依赖注入(DI)
先对DI下一个定义:
依赖注入(DI)是一个过程,在此过程中,对象仅通过构造函数参数、工厂方法参数或对象实例构造或从工厂方法返回后在其上设置的属性来定义其依赖关系(即与之协同工作的其他对象)。然后,容器会在创建 Bean 时注入这些依赖关系。从根本上说,这一过程是 bean 本身通过直接构建类或服务定位器模式来控制其依赖关系的实例化或位置的逆过程(因此被称为控制反转)。
DI的方式
XML配置的方式
构造方法注入(推荐)(XML里的<constructor-arg/>标签)
定义
基于构造函数的 DI 是通过容器调用带有多个参数的构造函数来实现的,每个参数代表一个依赖项。
优点
能避免循环依赖,项目在启动时如果存在循坏依赖,会抛异常提醒
保证对象的不变性,避免空指针异常
setter方法注入(XML里的<property/>标签)
定义
基于 Setter 的 DI 是通过容器在调用无参数构造函数或无参数静态工厂方法来实例化 Bean 后在 Bean 上调用 setter 方法来完成的。
优点
setter 注入的一个好处是 setter 方法使该类的对象以后可以重新配置或重新注入
某种方面来说,你可以用它来配置循环依赖
缺点
(注入的有可能为null)应仅用于可以在类中分配合理默认值的可选依赖项,否则,必须在代码使用依赖项的任何地方执行非 null 检查
属性注入(自动装配)(XML里,bean标签里的autowire属性)
定义
可以不通过上述的两个标签显示的指定依赖关系,而是由Spring容器自动管理、更新Bean的协作关系。
autowire可选属性
no(默认),不开启
byName
Spring 会查找与需要自动装配的属性同名的 bean。
byType
如果容器中正好存在一个属性类型的 bean,则允许自动连接属性。如果存在多个 bean,则会抛出致命异常,这表明您不能对该 Bean 使用 byType 自动连线。如果没有匹配的 bean,则不会发生任何事情(未设置属性)。
constructor
类似于 byType,但适用于构造函数参数。如果容器中没有构造函数参数类型的一个 bean,则会引发致命错误。
拓展:
byName和byType类型的自动装配模式是针对property的自动装配,而constructor类型则是针对
构造方法参数的类型而进行的自动装配,它同样是byType类型的自动装配模式。不过,constructor是匹
配构造方法的参数类型,而不是实例属性的类型。与byType模式类似,如果找到不止一个符合条件的
bean定义,那么,容器会返回错误。使用上也与byType没有太大差别。
构造方法参数的类型而进行的自动装配,它同样是byType类型的自动装配模式。不过,constructor是匹
配构造方法的参数类型,而不是实例属性的类型。与byType模式类似,如果找到不止一个符合条件的
bean定义,那么,容器会返回错误。使用上也与byType没有太大差别。
优点
可以显著减少指定属性或构造函数参数的需要
可以随着对象的发展自动更新配置
缺点
属性和构造函数-参数设置中的显式依赖关系会始终覆盖掉Autowire。
没有显示配置的依赖关系那么明确,简而言之就是,你不知道Spring内部帮你做了什么东西
注解注入
涉及到的注解
@Autowire(√)
Spring提供的,byType,结合@Qualifier注解可以byName
拓展使用:
由于在ByType的情况下可能会遇到多个候选Bean,我们可以使用@Primary进行强制指定(@Primary只能作用于方法上)
@Resource(√)
JSR250规范实现,byName
拓展:
如果未显式指定名称,则默认名称派生自字段名称或 setter 方法。如果是字段,则采用字段名称。如果是 setter 方法,它采用 bean 属性名称。
@Inject(×)
JSR330规范实现,目的是为了兼容Jakarta,默认按照Type匹配,@Inject和@Named一起使用实现byName
注解的使用位置
加在属性上
创建Bean时通过后置处理器去容器里找要注入的Bean,如果需要注入的Bean没有实例化,会转而先实例化该Bean,再通过反射进行赋值
源码:
加在setter方法上
创建Bean时通过后置处理器,使用反射调用setter方法进行注入。源码入口是和属性一样的,只是这里有多态,调用inject方法走的是AutowiredMethodElement类的方法,而属性则是直接走的InjectedElement的inject方法
源码:
加在构造函数上
创建Bean时,直接判断是否有可以解析的构造函数,有的话直接通过反射去调用构造函数,不走后置处理器,根据形参的类型装配
源码:
拓展用法:
可用注入数组或者List
ps:
从 Spring Framework 4.3 开始,如果目标 Bean 一开始只定义了一个构造函数,则不再需要对此类构造函数进行 @Autowired 注解。但是,如果有多个构造函数可用,并且没有主/默认构造函数,则必须至少用 @Autowired 注释其中一个构造函数,以便指示容器使用哪个构造函数。
使用@DependsOn注解 / 或者在xml中使用depends-on强制指定依赖关系
@DependsOn注解 / depends-on 属性可以显式地强制在初始化使用此元素的 Bean 之前初始化一个或多个 bean,它既可以指定初始化时间依赖性,也可以指定相应的销毁时间依赖性
延迟加载
xml中使用lazy-init属性
注解使用@Lazy
对延迟初始的误区纠正:
当延迟初始化的 Bean 是未延迟初始化的单例 Bean 的依赖项时,容器会在启动时创建延迟初始化的 Bean,因为它必须满足单例的依赖关系。
Bean的生命周期
1、Bean对象的创建(调用构造函数)
2、为Bean对象设置相关属性(赋值、依赖注入)
部分内置后置处理器的预执行
MergedBeanDefinitionPostProcessor
postProcessMergedBeanDefinition
InstantiationAwareBeanPostProcessor
postProcessAfterInstantiation
postProcessProperties
3、自定义Bean后置处理器(初始化前)
需要实现BeanPostProcessor后置处理器接口,重写postProcessBeforeInitialization方法,这块逻辑对所有Bean生效
4、Bean对象初始化(调用指定的初始化方法)
不同的Bean可以定制化处理,使用@PostConstruct
不同的Bean可以定制化处理,实现InitializingBean接口,重写afterPropertiesSet()方法
不同的Bean可以定制化处理,@Bean注解中通过initMethod属性指定方法签名
5、自定义Bean后置处理器(初始化后)
需要实现BeanPostProcessor后置处理器接口,重写postProcessAfterInitialization方法,这块逻辑对所有Bean生效
6、Bean对象创建完成,使用
7、触发IOC容器关闭
容器对象.close()方法
8、Bean对象销毁(执行指定销毁方法)
不同的Bean可以定制化处理,使用@PreDestroy
不同的Bean可以定制化处理,实现DisposableBean,重写destroy()方法
不同的Bean可以定制化处理,@Bean注解中通过destroyMethod属性指定方法签名
9、IOC容器关闭
ps:
特别标注,@Value对变量的负责操作在构造函数执行完之后,自定义init方法执行前,即第2步
Spring 只帮我们管理单例模式 Bean 的完整生命周期,对于 prototype 的 bean ,Spring 在创建好交给使用者之后则不会再管理后续的生命周期。
关于循环依赖
Spring只是解决了单例模式下属性依赖的循环问题;Spring为了解决单例的循环依赖问题,使用了三级缓存
第一层缓存(singletonObjects):单例对象缓存池,已经实例化并且属性赋值,这里的对象是成熟对象
第二层缓存(earlySingletonObjects):单例对象缓存池,已经实例化但尚未属性赋值,这里的对象是半成品对象(即 bean 的代理对象);当一个 bean 在创建过程中需要依赖另一个尚未完成初始化的 bean 时,Spring 会在这里提供该 bean 的代理引用,允许后续依赖注入
第三层缓存(singletonFactories): 单例工厂的缓存
Spring提早将对象暴露出来使用,解决循环依赖
细节描述
1. A 创建过程中需要 B,于是 A 将自己放到三级缓里面,去实例化B
2. B 实例化的时候发现需要 A,于是 B 先查一级缓存,没有,再查二级缓存,还是没有,再查三级缓存,找到了 A 然后把三级缓存里面的这个 A 放到二级缓存里面,并删除三级缓存里面的 A
3. B 顺利初始化完毕,将自己放到一级缓存里面(此时 B 里面的A 依然是创建中状态)。然后回来接着创建 A,此时 B 已经创建结束,直接从一级缓存里面拿到B,然后完成创建,并将 A 自己放到一级缓存里面。
问题答疑
为什么构造器注入属性无法解决循环依赖问题?
由于 spring 中的 bean 的创建过程为先实例化 再初始化(在进行对象实例化的过程中不必赋值)将实例化好的对象暴露出去,供其他对象调用,然而使用构造器注入,必须要使用构造器完成对象的初始化的操作,就会陷入死循环的状态
二级缓存能不能解决循环依赖问题?
理论上二级缓存可以解决循环依赖问题,但是需要注意,为什么需要在三级缓存中存储匿名内部类(ObjectFactory),原因在于 需要创建代理对象 eg:现有A 类,需要生成代理对象 A 是否需要进行实例化(需要) 在三级缓存中存放的是生成具体对象的一个匿名内部类,该类可能是代理类也可能是普通的对象,而使用三级缓存可以保证无论是否需要是代理对象,都可以保证使用的是同一个对象,而不会出现,一会儿使用普通bean 一会儿使用代理类
单例以外的循环依赖怎么解决?
生成代理对象产生的循环依赖
@Lazy注解,延迟加载
修改文件名称,改变循环依赖类的加载顺序
@DependsOn注解,指定加载先后关系
使用构造注入避免循环依赖,出现了循环依赖会报错
原型(Prototype)作用域的循环依赖
通过把bean改成单例的解决
通过方法注入,在需要的地方使用 setter 方法注入,允许在 bean 完全初始化后再注入依赖
使用 @Lookup 注解,允许 Spring 在运行时查找依赖,这样每次都会获取一个新的实例
lookup注解补充:@Lookup注解允许 Spring 在需要时动态获取某个 bean 实例,而不是在创建时注入。这使得可以避免直接依赖于一个尚未初始化的 bean
使用工厂方法,通过定义一个工厂类或方法,来控制 bean 的创建和依赖注入,可以避免直接的循环依赖
构造器循环依赖
使用@Lazy注解解决
将构造注入改为 setter 注入
IOC源码分析
两个顶层接口
BeanFactory: 工厂模式定义了IOC容器的基本功能规范,能够管理任何类型的对象。该接口的容器默认采用延迟初始化策略
ApplicationContext是 BeanFactory 的一个子接口,补充了额外的企业特定功能信息,比如AOP集成、消息资源处理(国际化)、事件发布、特定的容器上下文比如Web环境的容器上下文WebApplicationContext。ApplicationContext所管理的对象,在该类型容器启动之后,默认全部初始化并绑定完成。
BeanRegistry: 向IOC容器手工注册 BeanDefinition 对象的方法
IOC容器初始化流程
初始化的入口在容器实现中的 refresh()调用来完成
对 bean 定义载入 IOC 容器使用的方法是 loadBeanDefinition,其中的大致过程如下
通过 ResourceLoader 来完成资源文件位置的定位,DefaultResourceLoader 是默认的实现,同时上下文本身就给出了 ResourceLoader 的实现,可以从类路径,文件系统, URL 等方式来定为资源位置。如果是 XmlBeanFactory作为 IOC 容器,那么需要为它指定 bean 定义的资源,也就是说 bean 定义文件时通过抽象成 Resource 来被 IOC 容器处理的
通过 BeanDefinitionReader来完成定义信息的解析和 Bean 信息的注册, 往往使用的是XmlBeanDefinitionReader 来解析 bean 的 xml 定义文件 - 实际的处理过程是委托给 BeanDefinitionParserDelegate 来完成的,从而得到 bean 的定义信息,这些信息在 Spring 中使用 BeanDefinition 对象来表示 - 这个名字可以让我们想到loadBeanDefinition,RegisterBeanDefinition 这些相关的方法 - 他们都是为处理 BeanDefinitin 服务的
容器解析得到 BeanDefinition 以后,需要把它在 IOC 容器中注册,这由 IOC 实现 BeanDefinitionRegistry 接口来实现。注册过程就是在 IOC 容器内部维护的一个HashMap 来保存得到的 BeanDefinition 的过程。这个 HashMap 是 IoC 容器持有 bean 信息的场所,以后对 bean 的操作都是围绕这个HashMap 来实现的.
然后我们就可以通过 BeanFactory 和 ApplicationContext 来享受到 Spring IOC 的服务了,在使用 IOC 容器的时候,我们注意到除了少量粘合代码,绝大多数以正确 IoC 风格编写的应用程序代码完全不用关心如何到达工厂,因为容器将把这些对象与容器管理的其他对象钩在一起。基本的策略是把工厂放到已知的地方,最好是放在对预期使用的上下文有意义的地方,以及代码将实际需要访问工厂的地方。 Spring 本身提供了对声明式载入 web 应用程序用法的应用程序上下文,并将其存储在ServletContext 中的框架实现。
总结:Spring如何实现将资源配置(以xml配置为例)通过加载,解析,生成BeanDefination并注册到IoC容器中的;容器中存放的是Bean的定义即BeanDefinition放到beanDefinitionMap中,本质上是一个ConcurrentHashMap<String, Object>;并且BeanDefinition接口中包含了这个类的Class信息以及是否是单例等
AOP(面向切面编程)
AOP思想简述
AOP实现定义:通过定义切面, 通过拦截切点实现了不同业务模块的解耦,是通过预编译方式和运行期间动态代理实现程序的统一维护的一种技术
相关概念
连接点 Join point
在哪里干
切入点 pointcut
连接点的集合
通知 Advice
干什么
前置通知
Before advice
后置通知
After returning advice
环绕通知
环绕通知是最常用的通知类型,从方法参数这中拿到JoinPoint自定义通知逻辑
Around advice
异常通知
After throwing advice
最终通知
After (finally) advice
切面 Aspect
在哪干和干什么集合
其他
引入
干什么
目标对象
对谁干
织入(weaving)
怎么实现的
AOP代理
怎么实现的一种典型方式
实现
Spring AOP
动态织入
JDK动态代理(Proxy+InvocationHandler)
原理:通过生成代理对象对原对象进行增强(基于接口的实现类增强)
有接口使用
因为JDK动态代理是基于接口实现的,如果被代理的类没有实现任何接口,就无法生成对应的代理类,也就无法实现动态代理
CGLIB动态字节码增强(MethodInterceptor)
原理:为需要增强的类生成一个子类,通过子类对父类逻辑进行增强(基于继承的子类增强)
无接口使用
AspectJ
静态织入
编译期织入,在这个期间使用AspectJ的acj编译器(类似javac)把aspect类编译成class字节码后,在java目标类编译时织入,即先编译aspect类再编译目标类
PS
AOP在运行时仍旧是纯的Spring AOP,并不依赖于AspectJ的编译器或者织入器
在使用Spring AOP时,如果你需要使用注解配置AOP的话,依旧要集成AspectJ相关的依赖。因为相关的注解是来自于AspectJ的
配置方式
XML配置(×)
Java API方式(×)
注解方式(需要AspectJ依赖支持)(√)
如何开启注解支持
XML开启
<aop:aspectj-autoproxy/>
注解开启
@EnableAspectJAutoProxy(SpringBoot脚手架貌似是不用配)
常用注解
@Aspect
用来定义一个切面。
PS:注意,这个注解不会把类托管到容器,所以我们一般还要带上@Component注解
@pointcut
用于定义切入点表达式。在使用时还需要定义一个包含名字和任意参数的方法签名来表示切入点名称,这个方法签名就是一个返回值为void,且方法体为空的普通方法。
PointCut的组成部分
一个由名称和任何参数组成的签名,切入点签名由正则方法定义提供
一个切入点表达式,切入点表达式通过使用@Pointcut注解表示
execution(权限 包 . 类 . 方法())
within
this
target
args
bean
@Before
用于定义前置通知,相当于BeforeAdvice。在使用时,通常需要指定一个value属性值,该属性值用于指定一个切入点表达式(可以是已有的切入点,也可以直接定义切入点表达式)。
@AfterReturning
用于定义后置通知,相当于AfterReturningAdvice。在使用时可以指定pointcut / value和returning属性,其中pointcut / value这两个属性的作用一样,都用于指定切入点表达式。
@Around(√可操作性最强)
用于定义环绕通知,相当于MethodInterceptor。在使用时需要指定一个value属性,该属性用于指定该通知被植入的切入点。
使用细节
①将 Object 声明为其返回类型
②该方法的第一个参数必须是 ProceedingJoinPoint 类型
③将joinpoint的执行结果返回
@After-Throwing
用于定义异常通知来处理程序中未处理的异常,相当于ThrowAdvice。在使用时可指定pointcut / value和throwing属性。其中pointcut/value用于指定切入点表达式,而throwing属性值用于指定-一个形参名来表示Advice方法中可定义与此同名的形参,该形参可用于访问目标方法抛出的异常。
@After
用于定义最终final 通知,不管是否异常,该通知都会执行。使用时需要指定一个value属性,该属性用于指定该通知被植入的切入点。
增强通知的顺序
实现org.springframework.core.Ordered 接口,较低的那个有更高的优先级
用Order注解
较低的那个有更高的优先级
常用通知的顺序
①before
②afterreturning/afterthrowing
③after
Spring AOP 还是 AspectJ?
总结来说就是 Spring AOP更易用,AspectJ更强大
AOP源码分析
Spring默认在目标类实现接口时是通过JDK代理实现的,只有非接口的是通过Cglib代理实现的。当设置proxy-target-class为true时在目标类不是接口或者代理类时优先使用cglib代理实现。
Spring会为每个被切面切入的组件创建代理对象。代理对象中保存了切面类里面所有通知方法构成的增强器链。目标方法执行时,会先去执行增强器链中拿到需要提前执行的通知方法执行
事务管理(基于Spring AOP实现)
管理事务的方式
编程式事务(PlatformTransactionManager)
可以进行自定义粒度的事务管理
声明式事务(配置类上添加@EnableTransactionManagement注解开启,SpringBoot中不用,有自动装配)
这个用的比较多,好用,方便
事务@Transactional属性
只读read-only
默认false
超时timeout
默认无
回滚策略
rollbackFor属性:需要设置一个Class类型的对象,默认{}
By default, a transaction will be rolled back on {@link RuntimeException}
and {@link Error} but not on checked exceptions (business exceptions).
and {@link Error} but not on checked exceptions (business exceptions).
rollbackForClassName属性:需要设置一个字符串类型的全类名,默认{}
noRollbackFor属性:需要设置一个Class类型的对象,默认{}
rollbackFor属性:需要设置一个字符串类型的全类名,默认{}
事务的隔离级别isolation
DEFAULT
默认,根据数据库类型决定
READ_UNCOMMITTED
读未提交
READ_COMMITTED
读已提交
REPEATABLE_READ
可重复读
SERIALIZABLE
串行化
事务的传播行为propagation
当事务方法被另一个事务方法调用时,必须指定事务应该如何传播
TransactionDefinition.PROPAGATION_REQUIRED(√)默认
没有就新建,有就加入当前事务
TransactionDefinition.PROPAGATION_REQUIRES_NEW(√)
不管有没有,直接开启一个新事务,开启的新事务和之前的事务不存在嵌套关系,之前的事务被挂起
TransactionDefinition.PROPAGATION_NESTED
有事务的话,就在这个事务里再嵌套一个完全独立的事务,嵌套的事务可以独立的提交和回滚。没有事务就和REQUIRED一样
TransactionDefinition.PROPAGATION_MANDATORY
有就加入,没有就抛异
TransactionDefinition.PROPAGATION_SUPPORTS
有就加入,没有就以非事务执行
TransactionDefinition.PROPAGATION_NOT_SUPPORTED
不支持事务,存在就挂起当前事务
TransactionDefinition.PROPAGATION_NEVER
不支持事务,存在就抛异常
事务失效的场景
编程式事务
事务配置没配好
异常捕获问题
粗心
没有commit或者rollback
声明式事务
抛出的异常被try catch吞掉处理
解决方式一:catch后再手动抛一个异常
解决方式二:在catch里直接通过TransactionManager手动回滚(TransactionInterceptor.currentTransactionStatus().setRollbackOnly();)
AOP失效
方法权限不是pubilc
源码语法里写的就是得public修饰,不是的话条件判断过不去
方法被final修饰导致aop失效
见代理的内容,主要是这样修饰重写不了方法,生成不了代理对象
同一个类中的方法调用没触发AOP(调用的那个方法不会触发AOP)
Bean没被Spring托管
这是真实发生过的!小伙伴排了半天,发现切面类根本就没有托管到Spring里,给整无语了,当时还纳闷IDEA明明有通知的关联提示了,为啥不生效。。。
事务的配置没配好
异常不匹配(默认是RuntimeException.class)
传播行为设置不当
多线程调用,多线程父子线程回滚异常
这玩意没法解决,别用
方法嵌套调用(调用其他业务类的方法),注解配置不当
具体情况(require传播行为下)
都有事务注解,主方法正常,子方法异常
根据子方法的传播行为决定,require的话,因为子方法加入了主方法的事务,所以事务生效,都回滚。
都有事务注解,主方法异常,子方法正常
根据子方法的传播行为决定,require的话,因为子方法加入了主方法的事务,所以事务生效,都回滚。
只有主方法注解,主方法正常,子方法异常
因为子方法没注解,所以事务拦截器不会捕获子方法的异常进行处理,异常抛给主方法,主方法有事务拦截器处理,所以事务生效,都回滚。
只有主方法注解,主方法异常,子方法正常
因为子方法没注解,所以事务拦截器不会捕获子方法的异常进行处理,异常抛给主方法,主方法有事务拦截器处理,所以事务生效,都回滚。
只有子方法注解,主方法正常,子方法异常
子方法的异常会被事务拦截器捕获处理回滚,所以子方法会回滚,但是主方法不会。
使用建议:
@Transactional注解加外不加内
事务的实现原理
AOP+TransactionManager+ TransactionInterceptor
Spring MVC
(这本来就是Spring里的一个模块,所以还是放在Spring里吧)
(这本来就是Spring里的一个模块,所以还是放在Spring里吧)
介绍
Spring MVC是Spring在Spring Container Core和AOP等技术基础上,遵循上述Web MVC的规范推出的web开发框架,目的是为了简化Java栈的web开发。它是基于 Servlet API 构建的原始 Web 框架。可以这么认为:Spring在Web这块对Jakarta EE的MVC规范进行了兼容实现,除此之外还有自己的实现(Spring WebFlux),不过我们实际情况下还是MVC那套用的比较多一点
为我们提供了什么特性?
更简洁的Web 层的开发
强大的约定大于配置的契约式编程支持
灵活的URL 到页面控制器的映射
非常容易与其他视图技术集成,如 Velocity、FreeMarker 等等
非常灵活的数据验证、格式化和数据绑定机制,能使用任何对象进行数据绑定,不必实现特定框架的API
灵活的本地化、主题等解析
简单的异常处理
静态资源的支持
支持Restful 风格
组件/Bean(感觉可以直接去看servlet的内容)
DispatcherServlet
介绍
DispatcherServlet,又称前端控制器,为请求处理提供共享算法,而实际工作由可配置的委托组件执行
使用
Java 配置
web.xml配置
可委托的类(常见)
Controller
处理器/页面控制器,做的是 MVC 中的 C 的事情,但控制逻辑转移到前端控制器了,用于对请求进行
处理
处理
HandlerMapping
将请求映射到处理程序以及用于预处理和后处理的拦截器列表。
HandlerInterceptor
拦截HandlerMapping
preHandle(..)
postHandle(..)
afterCompletion(..)
HandlerAdapter
帮助 DispatcherServlet 调用映射到请求的处理程序,而不考虑处理程序的实际调用方式。
ViewResolver
将从处理程序返回的基于 String 的逻辑视图名称解析为要用于呈现响应的实际 View。
ThemeResovler
主题解析,通过它来实现一个页面多套风格,即常见的类似于软件皮肤效果
LocaleResolver, LocaleContextResolver
LocaleResolver、LocaleContextResolver
LocaleResolver、LocaleContextResolver
解决客户端正在使用的区域设置,并可能解决其时区,以便能够提供国际化视图。
MultipartResolver
在一些多部分解析库的帮助下,用于解析多部分请求(例如,浏览器样式文件上传)的抽象
SpringMVC实现原理之DispatcherServlet的初始化过程
init()
主要读取web.xml中servlet参数配置,并将交给子类方法initServletBean()继续初始化
initWebApplicationContext()
initWebApplicationContext用来初始化和刷新WebApplicationContext
onRefresh()
调用initStrategies(context)方法对DispatcherServlet中的组件进行初始化
initHandlerXXX
获取按照优先级排序后的HanlderMappings, 将来匹配时按照优先级最高的HanderMapping进行处理
拦截器
HandlerInterceptor
对处理器进行预处理和后处理
执行流程
图示:
时序图:
描述:
1. 用户发送请求至前端控制器 DispatcherServlet,前端控制器收到请求后自己不进行处理,而是委托给其他的解析器进行
处理,作为统一访问点,进行全局的流程控制
处理,作为统一访问点,进行全局的流程控制
2. HandlerMapping 将会把请求映射为 HandlerExecutionChain 对象(包含一
个 Handler 处理器(页面控制器)对象、多个 HandlerInterceptor 拦截器)对象,将该对象返回给DispatcherServlet
个 Handler 处理器(页面控制器)对象、多个 HandlerInterceptor 拦截器)对象,将该对象返回给DispatcherServlet
3. DispatcherServlet 将 HandlerExecutionChain对象交给HandlerAdapter
4. HandlerAdapter 将会根据适配的结果调用真正的处理器(Controller)的功能处
理方法,完成功能处理;并返回一个 ModelAndView 对象(包含模型数据、逻辑视图名)给DispatcherServlet;
理方法,完成功能处理;并返回一个 ModelAndView 对象(包含模型数据、逻辑视图名)给DispatcherServlet;
5. DispatcherServlet 将 ModelAndView 传给 ViewReslover 视图解析器,ViewResolver 将把逻辑视图名解析为具体的 View
6. DispatcherServlet 根据 View 进行渲染视图(即将模型数据填充至视图中),View 会根据传进来的 Model 模型数据进行渲染,此处的 Model 实际是一个 Map 数据结构
7. 返回控制权给 DispatcherServlet,由 DispatcherServlet 返回响应给用户,到此一个流程结束
注解使用
控制器组件声明
@Controller
输出效果
使用 HTML 模板进行视图分辨率和呈现
@RestController
等于@Controller + @ResponseBody
输出效果
直接写入响应正文
请求映射
@RequestMapping
@GetMapping
@PostMapping
@PutMapping
@DeleteMapping
@PatchMapping
最佳实践:
类上使用@RequestMapping作为共享路径
方法上使用具体的Mapping进行映射
映射的URI模式
PathPattern(√默认)
预解析模式,与 URL 路径匹配。此解决方案专为 Web 使用而设计,可有效处理编码和路径参数,并高效匹配。
AntPathMatcher
将 String 模式与 String 路径匹配,效率较低
Handler方法
方法参数
@RequestParam
将 Servlet 请求参数(即查询参数或表单数据)绑定到控制器中的方法参数
@MatrixVariable
用于访问 URI 路径段中的名称-值对
@PathVariable
捕获的 URI 变量
@RequestHeader
将请求标头绑定到控制器中的方法参数
@CookieValue
将 HTTP cookie 的值绑定到控制器中的方法参数
@RequestBody
用于访问 HTTP 请求正文。使用 HttpMessageConverter 实现将正文内容转换为声明的方法参数类型。
@RequestPart
访问 multipart/form-data 请求中的部件,使用 HttpMessageConverter 转换部件的主体。
@ModelAttribute
用于访问模型中的现有属性(如果不存在,则实例化),并应用数据绑定和验证。
@SessionAttribute
用于访问任何会话属性
@RequestAttribute
用于访问请求属性
参数校验(沿用JSR303 规范)
@NotNull
@Null
@NotBlank
验证注解的元素值不为空(不为 null、去除首位空格
后长度为 0),不同于@NotEmpty,@NotBlank 只应用
于字符串且在比较时会去除字符串的首位空格
后长度为 0),不同于@NotEmpty,@NotBlank 只应用
于字符串且在比较时会去除字符串的首位空格
@NotEmpty
验证注解的元素值不为 null 且不为空(字符串长度不
为 0、集合大小不为 0)
为 0、集合大小不为 0)
@Valid/@Validated
指定递归验证关联的对象;
如用户对象中有个地址对象属性,如果想在验证用户对象时一起验证地址对象的话,在地址对象上加
@Valid 注解即可级联验证
如用户对象中有个地址对象属性,如果想在验证用户对象时一起验证地址对象的话,在地址对象上加
@Valid 注解即可级联验证
返回值
@ResponseBody
返回值通过 HttpMessageConverter 实现进行转换为JSON,并写入响应。
@ModelAttribute
要添加到模型的属性,视图名称通过 RequestToViewNameTranslator 隐式确定。
异常处理
@ExceptionHandler
注解式声明异常处理器
@ControllerAdvice
@RestControllerAdvice
跨域
注解配置
@CrossOrigin
启用跨域请求
代码配置
①implements WebMvcConfigurer
②@Override addCorsMappings
③registry.allowedOrigins
视图技术(模板引擎)
Thymeleaf
JSP
FreeMarker
MVC配置
开启配置
配置类上添加@EnableWebMvc
集成配置类
implements WebMvcConfigurer
拦截器
@Override addInterceptors
registry.addInterceptor
消息转换
@Override configureMessageConverters
视图控制器
@Override addViewControllers
registry.addViewController
视图解析器
@Override configureViewResolvers
registry.enableContentNegotiation
其他集成(TODO)
RestClient
JMS
Task Execution and Scheduling
Email
Cache
Observability Support
WebFlux
Spring Boot(核心思想:约定大于配置)
为什么?
虽然Spring的组件代码是轻量级的,但它的配置却是重量级的。SpringBoot对上述Spring的缺点进行的改善和优化,基于约定优于配置的思想,可以让开发人员不必在配置与逻辑业务之间进行思维的切换,全身心的投入到逻辑业务的代码编写中,从而大大提高了开发的效率,一定程度上缩短了项目周期。
配置文件相关
SpringBoot配置加载顺序(后加载的配置会覆盖先加载的配置)
默认配置
位于spring-boot.jar内部的application.properties或application.yml
应用配置文件
config目录下的application.properties或application.yml文件(file:./config/)
当前目录下的application.properties或application.yml文件(file:./)
类路径根目录下的application.properties或application.yml文件(classpath:/config/)
类路径根目录下的application.properties或application.yml文件(classpath:/)
操作系统环境变量
命令行参数
多环境配置
为了支持多环境配置,Spring Boot引入了profile的概念,可以创建多个环境的配置文件,通过spring.profiles.active参数指定
application-dev.properties
application-prod.properties
配置文件加载原理
①启动创建ConfigurableEnvironment
②添加默认配置
③ConfigFileApplicationListener查找配置文件,加载到的每个配置文件会被添加到Environment中作为PropertySource,后加载的配置会覆盖先加载的配置
④根据profile.active处理Profile
⑤环境变量和系统属性,ConfigFileApplicationListener会将操作系统环境变量和JVM系统属性添加到Environment中
⑥处理命令行参数,并将其添加到Environment中
自动配置相关
自动配置原理
①每一个Starter都有一个spring-boot-autoconfigure依赖
②spring-boot-autoconfigure依赖的包里有一个META-INF/spring.factories文件,里面指定了所有启动要加载的自动配置类
③@EnableAutoConfiguration 会自动的把上面文件里面写的所有自动配置类都导入进来。xxxAutoConfiguration 是有条件注解进行按需加载的,不是无脑加载
④xxxAutoConfiguration给容器中导入一堆组件,组件都是从 xxxProperties中提取属性值
⑤xxxProperties又是和配置文件进行了绑定
常用注解
自动装配
大致原理:基于约定大于配置,本身内置了非常多的y依赖项集合,它们基于@Conditionnal注解进行注册,当我们在maven中添加了各种starter依赖后,满足装配条件的Bean会在启动时进行自动装配,实现方式看下面
@SpringBootApplication
定义在main方法入口类处,标注这是一个SpringBoot应用,用于启动sping boot应用项目
@SpringBootConfiguration
@Configuration
标注这个类这是一个配置Bean
@EnableAutoConfiguration
@AutoConfigurationPackage
@Import(AutoConfigurationPackages.Registrar.class)
把主程序所在的包的所有组件导入进来
@Import(AutoConfigurationImportSelector.class)
在src/main/resources的META-INF/spring.factories
加载所有自动配置类:加载starter导入的组件
@ComponentScan
排除前面已经扫描进来的配置类、和自动配置类
tips:注解的执行顺序的倒叙的
步骤
①每一个Starter都有一个spring-boot-autoconfigure依赖
②spring-boot-autoconfigure依赖的包里有一个META-INF/spring.factories文件,里面指定了所有启动要加载的自动配置类
③@EnableAutoConfiguration 会自动的把上面文件里面写的所有自动配置类都导入进来。xxxAutoConfiguration 是有条件注解进行按需加载的,不是无脑加载
④xxxAutoConfiguration给容器中导入一堆组件,组件都是从 xxxProperties中提取属性值
⑤xxxProperties又是和配置文件进行了绑定
常用注解(分类版本)
组件注册
@SpringBootConfiguration
@Configuration
@Bean
@Scope
@Import
@ComponentScan
@Controller、 @Service、@Repository、@Component
@ImportResource
加载xml配置,一般是放在启动main类上
条件注解
@ConditionalOnXxx
@ConditionalOnClass
@ConditionalOnMissingClass
@ConditionalOnBean
@ConditionalOnMissingBean
属性绑定
@ConfigurationProperties
声明组件的属性和配置文件哪些前缀开始项进行绑定
@EnableConfigurationProperties
快速注册组件
场景:SpringBoot默认只扫描自己主程序所在的包。如果导入第三方包,即使组件上标注了 @Component、@ConfigurationProperties 注解,也没用。因为组件都扫描不进来,此时使用这个注解就可以快速进行属性绑定并把组件注册进容器(这个还需要被标注的第三方组件类上本身标注了@configurationProperties注解)
● @EnableConfigurationProperties
● 1、开启Sheep组件的属性绑定
● 2、默认会把这个组件自己放到容器中
常用于导入第三方包
● 1、开启Sheep组件的属性绑定
● 2、默认会把这个组件自己放到容器中
常用于导入第三方包
@Value
application.properties定义属性,直接使用@Value注入即可
Web MVC
@PathVariable
用来获得请求url中的动态参数
@ResponseBody
支持将返回值放在response体内,而不是返回一个页面
@RequestParam
获取request请求的参数值
@RestController
组合@Controller和@ResponseBody
AOP
@Order
@Order(1),值越小优先级超高,越先运行
生命周期
@PostConstruct
spring容器初始化时,要执行该方法
自定义starter
①创建SpringBoot项目,添加spring-boot-starter依赖
②编写模块功能,引入模块所有需要的依赖
绑定配置的xxxProperties类,通过@ConfigurationProperties(prefix = "xxx")和配置文件进行绑定
③编写xxxAutoConfiguration自动配置类,帮其他项目导入这个模块需要的所有组件
④编写配置文件META-INF/spring.factories指定启动需要加载的自动配置
⑤打包,发布到Maven仓库
⑥其他项目引用
个人理解:starter里边写的那些虽然加了各种注解,看上去是被Spring托管了,但是基于SpringBoot的扫描规则,这些类根本就不会被扫描到。因此,为了装载这些类,我们通过创建一个Configuration类,在类中import其他要托管的类,然后把配置类的全类名加到spring.factories文件里,通过另外一种加载途径实现装载
Web MVC相关
Spring Boot 为 Spring MVC 提供自动配置,适用于大多数应用程序。它取代了对@EnableWebMvc的需要,两者不能一起使用。
自动配置
HttpMessageConverters
Spring MVC 使用 HttpMessageConverter 接口来转换 HTTP 请求和响应。合理的默认值是开箱即用的。
MessageCodesResolver
Spring MVC 有一个策略,用于生成错误代码,用于从绑定错误中呈现错误消息:MessageCodesResolver。
静态index.html支持
自动使用 ConfigurableWebBindingInitializer bean
自动注册 Converter、GenericConverter 和 Formatter bean
支持提供静态资源,包括对 WebJars 的支持
包含 ContentNegotiatingViewResolver 和 BeanNameViewResolver bean
Spring Cloud
服务注册与发现
Nacos
配置中心
Nacos
远程服务调用与负载均衡
OpenFeign
使用方式
①启动类添加@EnableFeignClients注解
②创建一个接口并添加@FeignClient("val")注解
Val的可选值
在集成了注册与发现中心的情况下,可以填写服务名,这会内置一个负载均衡效果
url
LoadBalance
服务网关
Spring Cloud GateWay
能干嘛
反向代理
鉴权
流量控制
熔断
日志监控
三大核心
Route
Predicate
就是匹配条件
Filter
能干嘛
请求鉴权
异常处理
。。。。
类型
全局默认过滤器GlobalFilters
直接实现接口即可
单一内置过滤器GatewayFilters
自定义过滤器
服务熔断降级
Sentinel
分布式事务
分布式事务的定义?
分布式事务是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器「分别位于不同的分布式系统的不同节点之上」。
一个大的操作由N多的小的操作共同完成。而这些小的操作又分布在不同的服务上。针对于这些操作,「要么全部成功执行,要么全部不执行」。
一个大的操作由N多的小的操作共同完成。而这些小的操作又分布在不同的服务上。针对于这些操作,「要么全部成功执行,要么全部不执行」。
分布式理论
CAP(强一致性)
简而言之,必须在一致性与可用性之间做出选择
BASE(最终一致性)
简而言之,我们无法做到强一致,但每个应用都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性
区别
分布式事务涉及到哪些角色?
TC(事务协调者)
seata服务端
TM(事务管理者)
seata客户端,就是sdk
RM(资源管理者)
database
XA协议是什么?
类似JDBC,是一套接口规范,各个数据库厂商基于这套接口规范进行了实现,通用了语言级的API。2PC和3PC衍生于XA协议
分布式事务的解决方案?
刚性兑付(强一致性)
2PC(二段提交)(理论)
详情
分为准备阶段和提交阶段。准备阶段:TC向所有参与者发送准备请求,询问是否可以准备提交事务。每个参与者要么准备好并锁住资源,要么拒绝准备。如果所有参与者都准备好了,TC向所有参与者发送提交请求,否则发送回滚请求。
优点
无侵入,好用
缺点
阻塞同步,性能差,不适合长事务
TC有单点故障风险
3PC(三段提交)(理论)
详情
3PC 是 2PC 的改进版本,增加了一个中间阶段来减少TC和参与者之间的等待时间。在TC、参与者之间都引入了超时机制。
准备阶段:与 2PC 类似,TC询问所有参与者是否可以准备提交事务。
预提交阶段:如果所有参与者都准备好了,TC者向所有参与者发送预提交请求,参与者预提交并继续等待最终指令。
提交阶段:TC发送提交请求,所有参与者正式提交事务。
准备阶段:与 2PC 类似,TC询问所有参与者是否可以准备提交事务。
预提交阶段:如果所有参与者都准备好了,TC者向所有参与者发送预提交请求,参与者预提交并继续等待最终指令。
提交阶段:TC发送提交请求,所有参与者正式提交事务。
优点
解决单点问题,以及通过超时机制减少阻塞
缺点
协议更复杂,实施成本较高,没有从根本上解决性能和数据一致性的问题
Seta-AT(√)(落地实现)
详情
对于3PC的落地实现
柔性事务(最终一致性)
TCC三段补偿
详情
2PC改良版,包含尝试(Try)、确认(Confirm)和取消(Cancel)三个步骤。Try 阶段:资源预留或锁定,检查并锁定需要的资源。Confirm 阶段:正式提交,执行实际的事务操作。Cancel 阶段:取消操作,释放预留的资源或撤销尝试阶段的操作。
优点
灵活性高,可以针对具体业务场景优化。TCC 完全不依赖底层数据库,能够实现跨数据库、跨应用资源管理,可以提供给业务方更细粒度的控制。
缺点
设计实现复杂,侵入性非常强,需要处理好资源预留和取消的逻辑。
本地消息表
详情
每一个服务的db下额外建立一张业务无关的表,基于对这张表记录的操作实现事务操作
优点
无锁,快
缺点
藕和度大,不可复用
独立消息微服务+消息队列+分布式定时任务轮询(√)
详情
将两个事物之间通过消息中间件进行异步解耦,类似本地消息表,但是把表放到MQ里了。
流程
1.主动者发送"待发送"消息给消息微服务
2.消息微服务收到消息,进行持久化数据库操作并返回(返回就是调主动者提供的一个回查接口,告诉主动者我持久化好消息了)
3.主动者收到消息微服务的返回后,执行本地的业务,持久化到数据库
4.主动者执行完业务,向消息微服务发送通知
5.消息微服务收到通知,将数据库里的待发送状态改为已发送
6.以上操作执行都ok,接着消息微服务把消息发到MQ
7.被动者监听MQ获取消息内容
8.被动者拿到消息内容,进行业务处理
9.被动者处理完业务,通知消息微服务把数据库里的消息记录改为已完成或者直接删除
PS:细节处理
1.保证消息投递100%,定时任务轮询待发送状态的消息,通过主动者提供的状态查询接口确认消息状态,如果业务执行成功,向MQ发送消息并发消息状态改为已发送,如果业务失败,删除消息
2.保证消息消费100%,,定时任务轮询已发送状态的消息,已过期的消息重新推到MQ,,被动者在保证幂等的情况下重新执行业务,,被动者执行完业务通知消息服务对消息进行状态修改为已完成或者删除消息
2.消息微服务收到消息,进行持久化数据库操作并返回(返回就是调主动者提供的一个回查接口,告诉主动者我持久化好消息了)
3.主动者收到消息微服务的返回后,执行本地的业务,持久化到数据库
4.主动者执行完业务,向消息微服务发送通知
5.消息微服务收到通知,将数据库里的待发送状态改为已发送
6.以上操作执行都ok,接着消息微服务把消息发到MQ
7.被动者监听MQ获取消息内容
8.被动者拿到消息内容,进行业务处理
9.被动者处理完业务,通知消息微服务把数据库里的消息记录改为已完成或者直接删除
PS:细节处理
1.保证消息投递100%,定时任务轮询待发送状态的消息,通过主动者提供的状态查询接口确认消息状态,如果业务执行成功,向MQ发送消息并发消息状态改为已发送,如果业务失败,删除消息
2.保证消息消费100%,,定时任务轮询已发送状态的消息,已过期的消息重新推到MQ,,被动者在保证幂等的情况下重新执行业务,,被动者执行完业务通知消息服务对消息进行状态修改为已完成或者删除消息
怎么用
@Translation+先处理本地,然后在通过MQ通知
分布式定时任务定时轮询消息服务,逼着上游向下游进行消息投递
优点
解耦,降低业务系统之间的耦合度
消息服务更加灵活,伸缩性强,独立维护,独立部署
相比本地消息表,降低开发成本,消息服务共用
缺点
主动者必须实现消息状态回查接口
需要分布式定时器实现两个任务
轮询待发送和已发送两个过期时间的状态进行重试操作
主动者通知被动者需要发两次消息,过于复杂
最大努力通知
详情
适合最终一致性要求较低的业务,比如短信通知
优点
确定
Seata
模式
AT模式(强一致性)
3PC的实现
AT 模式:基于 Seata 自身实现的两阶段提交,主要针对数据库操作。
TCC模式(最终一致性)
2PC改良版,不好用
Saga模式
长事务解决方案,不好用,你画内b状态机去吧
XA模式
XA 模式使用起来与 AT 模式基本一致,用法上的唯一区别在于数据源代理的替换:使用 DataSourceProxyXA 来替代 DataSourceProxy
XA 模式:基于标准的 XA 协议,适用于跨多种资源的分布式事务。
服务链路追踪
Jaeger(我用过的)
Micrometer+ZipKin
SkyWalking
其他?
SpringBoot+MDC实现本地微服务链路追踪,结合feign拦截器实现分布式链路追踪
云原生
Spring Cloud Kubernetes
Redis
1、基础和概念
是什么
Redis是一款内存高速缓存数据库。Redis全称为:Remote Dictionary Server(远程数据服务),使用C语言编写,Redis是一个key-value存储系统(键值存储系统),支持丰富的数据类型,如:String、list、set、zset、hash
为什么
基于内存,读写优异
数据类型丰富,数据结构优化
持久化
使用场景
缓存
工作中用的最多的就这个,而且主要是用String存token、权限信息、验证码、一些更新周期比较长的热点数据这些
缓存的使用方式
读取前,先去读Redis,如果没有数据,读取数据库,将数据拉入Redis
插入数据时,同时写入Redis
分布式锁
要么是RedisUtil封一下setnx方法,要么直接用redisson
这个主要利用redis的setnx命令进行,setnx:"set if not exists"就是如果不存在则成功设置缓存同时返回1,否则返回0
计数器
没用过
存储地理信息
没用过
签到打卡
没用过
统计页面访问量
没用过
消息队列
没用过,不要抢专业MQ的饭碗(RabbitMQ...)
......
安装需要注意的事项:
开启daemonize yes
注释bing 127.0.0.1
protected-mode no
指定端口
指定当前工作目录,dir
pid文件名,pidfile
log文件名,logfile
dump.rdb名字
aof文件名,appendfilename
密码requirepass
2、数据结构
5种基础数据结构
String
简述:string是redis最基本的类型,一个key对应一个value。
详情:String类型是二进制安全的,意思是 redis 的 string 可以包含任何数据。如数字,字符串,jpg图片或者序列化的对象,最大512M
使用场景:缓存、计数器、Session,分布式锁
底层数据结构
动态字符串SDS
List
简述:Redis列表是简单的字符串列表,按照插入顺序排序
详情:Redis中的List其实就是链表,底层是个双端链表,最多可以包含 2^32 - 1 个元素 (4294967295, 每个列表超过40亿个元素)
使用场景:排队功能、消息队列
底层数据结构
快表
Hash
介绍:Redis hash 是一个 string 类型的 field(字段) 和 value(值) 的映射表,hash 特别适合用于存储对象。Redis 中每个 hash 可以存储 2^32 - 1 键值对(40多亿)
使用场景:存储对象
底层数据结构
哈希表
压缩列表
Set
介绍:Redis 的 Set 是 String 类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据。集合中最大的成员数为 2^32 - 1 (4294967295, 每个集合可存储40多亿个成员)
使用场景:标签、点赞、踩、收藏
底层数据结构
整数集
哈希表
ZSet(SortSet)
介绍:Redis 有序集合和集合一样也是 string 类型元素的集合,且不允许重复的成员。不同的是每个元素都会关联一个 double 类型的分数(分数(score)可以重复)。redis 正是通过分数来为集合中的成员进行从小到大的排序。集合中最大的成员数为 2^32 - 1
使用场景:排行榜
底层数据结构
跳表
压缩列表
4种特殊数据结构
Hyperloglog
基数统计
可以非常省内存的去统计各种计数,比如注册 IP 数、每日访问 IP 数、页面实时UV、在线用户数,共同好友数等
Geospatial(GEO)
存储、操作地理位置信息
Bitmap
位图数据结构,由0和1状态表现的二进制位的bit数组。操作二进制位来进行记录
两个状态的,都可以使用 Bitmaps
Bitfield
一次性操作多个二进制位
Stream
类似Redis版本的MQ,Redis想抢饭碗,或者是怕被卡脖子(还是前面的意见,专业的事儿交给专业的做,要用MQ直接上MQ中间件)
3、常用命令
帮助命令:help @具体的数据类型
key操作
keys * 查看当前库所有的key
exists key 判断某个key是否存在
type key 查看key的类型
del key 删除指定的key(同步),大key的情况下会阻塞
unlink key 非阻塞(异步)删除
ttl key 查看还有多少秒过期 -1永不过期 -2已过期
expire key 设置过期时间
move key dbindex ,将当前数据库的key移动到指定的db
rename key,重命名key
db操作
select dbindex 切换数据库【0-15】,默认0
dbsize 当前db的key数量
flushdb 清空当前库
flushall 通杀全部库
具体到数据类型的操作
String
最常用
set key value
常用参数
NX(分布式锁)
XX
EX,秒单位的过期时间
keepttl,保留最初设置的过去时间,对key修改时,ttl不会重制
get key
同时设置、获取多个键值
mset k1 v1 k2 v2
mget k1 k2
获取指定区间的键值(字符串截取)
getrange key start end
setrange key index value
数值增减
incr key
incr key num
decr key
decr key num
获取字符串长度和内容追加
长度:strlen key
追加:append key value
分布式锁
setnx key value
setex
先get再set
getset,给定key的值设为value,并返回key的旧值
List
lpush/rpush/lrange
lpop/rpop
lindex
按照索引下标获得元素
llen
获取列表中元素的个数
lrem key 数字N 给点值V1
删除N个等于V1的元素
ltrim key startIndex endIndex
截取指定范围,然后赋值给key
rpoplpush 源列表 目的列表
lset key index value
linsert key befor/after 已有的值 新插入的值
Hash
hset/hget/hmset/hmget/hgetall/hdel
hlen
hexists key
hkeys/hvals
hincrby/hincrbyfloat
hsetnx
Set
SADD key member
SMEMBERS key
遍历所有元素
SISMEMBER key member
判断元素是否在集合中
SREM key member
删除元素
scard
获取集合里元素个数
srandmember key 数字
随机弹出元素,不删除
spop key 数字
随机弹出元素,出一个删一个
smove key1 key2
集合运算
差集A-B(属于A但不属于B)
SDIFF key [key ...]
并集
SUNION
交集
SINTER
ZSet
zset k1 score1 v1
zrange key start stop
zscore key member 获取元素的分值
zcard key 获取集合中元素的数量
zrem key score
删除元素
zincrby key num member
增加元素的分值
GEO(没用过)
Hyperloglog(没用过)
Bitmap(没用过)
setbit key offset val
getbit key offset
bitcount key start end
bitop operation destkey key
Bitfield(没用过)
Stream(没用过)
4、持久化机制
RDB(默认)(MySQL的Binlog?)
内存快照
RDB持久化是把当前进程数据生成快照保存到磁盘上的过程,由于是某一时刻的快照,那么快照中的值要早于或者等于内存中的值。
触发方式
手动触发
save
这是一个同步命令,主进程fork子进程做备份,但是会阻塞当前Redis主进程,直到RDB过程完成为止,对于内存比较大的实例会造成长时间阻塞,线上环境绝对不允许使用!!!
bgsave(默认)
这是一个异步命令,不会阻塞Redis主进程。Redis进程会执行fork操作创建子进程,RDB持久化过程由子进程负责,完成后自动结束。阻塞只发生在fork阶段,一般时间很短。
自动触发
配置文件里Save m n,基于配置文件里设置的规则m秒时间内有n次修改 触发
主从复制会触发
flushdb/flushall会触发,这是没有意义的
shutdown命令,如果没有开启aof,会触发
禁用rdb
配置文件里改成save " "
拓展:使用LASTSAVE命令查看最后一次执行快照成功的时间
返回的是一个时间戳
rdb文件检查修复
redis-check-rdb命令
恢复方式
关闭redis,把RDB文件复制到Redis的安装目录里,启动redis
建议:将RDB文件备份到不同的物理机上
优点
RDB文件是某个时间节点的快照,默认使用LZF算法进行压缩,压缩后的文件体积远远小于内存大小,适用于备份、全量复制等场景
Redis加载RDB文件恢复数据要远远快于AOF方式
缺点
实时性不够
无法做到秒级持久化
fork子进程属于重量级操作,开销大
RDB文件没有可读性,是二进制文件
配置文件优化
dbfilename 指定dump文件的名称
dir 指定dump文件的所在目录
其他的配置项建议默认,够用了
AOF(MySQL的RedoLog?)
针对RDB不适合实时持久化的问题,Redis提供了AOF持久化方式来解决
AOF开启(默认关闭)
配置文件里 appendonly yes
工作原理
AOF日志记录Redis的每个写命令,命令先到AOF缓存,再根据AOF写回策略写入AOF文件,减少磁盘IO
AOF写回策略
always
同步写回,可靠性高,数据基本不丢
everysec(默认)
每秒写回
no
躺平,操作系统自己控制时机,性能好
恢复
和rdb一样,把文件弄到redis目录,然后启动redis
正常恢复
dump和aof的文件同时存在,会用谁呢?
答案是只用aof
异常恢复
aof文件出错,Redis会无法启动,要通过修复命令修复文件才能启动
AOF文件修改
redis-check-aof --fix
优势
实时性持久化,秒级
日志不易损坏,损坏了也能修复
AOF文件过大的情况下有AOF重写机制进行瘦身
AOF重写触发机制
自动
满足配置文件的配置,默认是大于上一次的一倍且文件到达64M
手动
bgrewriteaof命令
重写原理
6.0版本以前
Redis通过创建一个新的AOF文件来替换现有的AOF,新旧两个AOF文件保存的数据相同,但新AOF文件没有了冗余命令
①主线程fork出子进程重写aof日志
②子进程重写日志完成后,主线程追加aof日志缓冲
③替换日志文件
7.0版本
不再整量替换AOF,而是将完整的AOF分为三个部分
appendonly.aof.1.base.rdb as a base file
BASE:表示基础AOF,
它一般由子进程通过重写产生,该文件最多只有一个。
它一般由子进程通过重写产生,该文件最多只有一个。
appendonly.aof.1.incr.aof, appendonly. aof.2.incr.aof as incremental files
INCR:表示增量AOF,
它一般会在AOFRW开始执行时被创建,该文件可能存在多个。HISTORY类型的AOF会被Redis自动删除,
它一般会在AOFRW开始执行时被创建,该文件可能存在多个。HISTORY类型的AOF会被Redis自动删除,
HISTORY:表示历史AOF,它由BASE和INCR AOF变化而来,每次AOFRW成功完成时
的BASE和INCR AOF都将变为HISTORY
本次AOFRW之前对应
的BASE和INCR AOF都将变为HISTORY
本次AOFRW之前对应
appendonly.aof.manifest as a manifest file
为了管理这些AOF文件,
我们引入了一个manifest(清单)文件来跟踪、管理这些AOF。
同时,为了便于AOF备份
和拷贝,我们将所有的AOF文件和manifest文件放入一个单独的文件目录中,目录名由appenddirname配置
(Redis 7.0新增配置项)决定。
我们引入了一个manifest(清单)文件来跟踪、管理这些AOF。
同时,为了便于AOF备份
和拷贝,我们将所有的AOF文件和manifest文件放入一个单独的文件目录中,目录名由appenddirname配置
(Redis 7.0新增配置项)决定。
日志内容可读
劣势
AOF文件比相同数据集的RDB文件更大
恢复速度慢于RDB
混合模式
工作原理
RDB镜像做全量持久化,AOF做增量持久化。混合持久化方式产生的aof文件,一部分是RDB格式,一部分是AOF格式。(包括了RDB头部+AOF混写)
开启方式
aof-use-rdb-preamble yes
特点
aof做增量,rdb做全量
拓展:在同时开启rdb 和aof 持久化时,重启时只会加载 aof 文件,不会加载 rdb 文件
纯缓存模式
同时关闭rdb和aof
save ""
禁用rdb(在禁用的情况下,手动命令还可以使用)
appendonly no
禁用aof(在禁用的情况下,手动命令还可以使用)
5、事务
介绍:可用一次执行多个命令,本质是一组命令的集合。一个事务中的所以命令都会序列化,按照顺序串行化执行而不会被其他命令插入
原理:本质上就是一个队列,一次性、顺序、排他性的执行一系列redis命令
Redis事务的ACID
Redis的事务是原子性的:所有的命令,要么全部执行,要么全部不执行。而不是完全成功,在MySQL的语义里它是不保证原子性的。
redis事务可以保证命令失败的情况下得以回滚,数据能恢复到没有执行之前的样子,是保证一致性的,除非redis进程意外终结。
redis事务是严格遵守隔离性的,原因是redis是单进程单线程模式(v6.0之前),可以保证命令执行过程中不会被其他客户端命令打断。
但是,Redis不像其它结构化数据库有隔离级别这种设计。
但是,Redis不像其它结构化数据库有隔离级别这种设计。
redis事务是不保证持久性的,这是因为redis持久化策略中不管是RDB还是AOF都是异步执行的,不保证持久性是出于对性能的考虑。
使用:Redis 可以通过 MULTI,EXEC,DISCARD 和 WATCH 等命令来实现事务(transaction)功能
使用过程
1开始事务(MULTI)
2、命令入队(批量操作 Redis 的命令,先进先出(FIFO)的顺序执行)。
3执行事务(EXEC)
你也可以在第二个步骤,通过 DISCARD (opens new window)命令取消一个事务,它会清空事务队列中保存的所有命令
WATCH (opens new window)命令用于监听指定的键,当调用 EXEC 命令执行事务时,如果一个被 WATCH 命令监视的键被修改的话,整个事务都不会执行,直接返回失败。
WATCH是乐观锁,基于CAS
UNWATCH:取消WATCH对所有key的监视。
6、管道
批处理命令的优化措施,类似Redis的原生批命令(多条命令打包执行),原理是使用的队列数据结构
使用
①命令集写到txt文件里
②通过管道符 | ,连接redis-cli 通过添加--pipe命令执行
拓展:
Pipeline与原生批量命令对比
原生批量命令是原子性(例如:mset,mget),pipeline是非原子性
原生批量命令一次只能执行一种命令,pipeline支持批量执行不同命令
原生批命令是服务端实现,而pipeline需要服务端与客户端共同完成
Pipeline与事务对比
事务具有原子性,管道不具有原子性
管道一次性将多条命令发送到服务器,事务是一条一条的发,事务只有在接收到exec命令后才会执行,管道不会
执行事务时会阻塞其他命令的执行,而执行管道中的命令时不会
使用Pipeline注意事项
pipeline缓冲的指令只是会依次执行,不保证原子性,如果执行中指令发生异常,将会继续执行后续的指令
使用pipeline组装的命令个数不能太多,不然数据量过大客户端阻塞的时间可能过久,同时服务端此时也被迫回复一个队列答复,占用很多内存
7、发布订阅(了解即可)
干嘛的
消息队列,实现进程间消息通信
不推荐使用,专业的事交个专业的做,市面上有更好的消息中间件
使用
基于频道(Channel)的发布/订阅
订阅
subscribe channel:1
发布
publish channel:1 hi
基于模式(pattern)的发布/订阅
订阅
psubscribe c? b* d?*
发布
publish c m1
publish c11 m1
8、高可用
主从复制replica(最少2台)
介绍:master以写为主,slave读为主,master数据变化时,会异步同步到slave
能干什么:
读写分离
容灾恢复
数据备份
水平扩容支撑高并发
怎么玩
配从库不配主库
权限细节(重要)
master需要配密码
slave需要配置masterauth来设置master的密码
基本操作命令
info replication
replicaof 主库ip 主库端口
一般会写进配置文件里
slaveof 主库ip 主库端口
每次和master端口后,都需要重新连接,除非在配置文件里配置了
可以用这个命令取消和当前主库的绑定,换绑到其他主库
slaveof no one
取消和主库的绑定,转成主库
复制的原理和工作流程
master启动
slave启动,同步初请
slave启动成功连接到master后,发送一个sync命令
slave首次全新连接master,一次完全同步(全量复制)将被执行,slave自身数据会被覆盖清除
首次连接,全量复制
master收到sync命令后,开始在后台保存快照,对于期间接受到的命令进行缓存。快照执行完后,master将rdb文件和缓存的命令一次性打包发给slave,完成全量同步
slave接收到文件后,进行加载,完成复制初始化
心跳持续,保持通信
repl-ping-replica-period 10,master发送ping包,默认10秒
进入平稳,增量复制
master继续将新收集到的命令传给slave同步
从机下线,重连续传
master检查backlog里面的offset,master和slave都会保存一个复制的offset和masterId,offset是保存在backlog里的。Master只会把offset后面的数据传给slave,类似断点续传
复制的缺点
复制延时,信号衰减
系统繁忙、slave数量过多会导致复制延时不可接受
master单点问题,挂了怎么办?
默认情况下,不会在slave里进行重新选举master,需要人工进行干预,基于此,自动选举的需求就出来了
哨兵sentinel(最少6台)
介绍
吹哨人巡查监控后台master是否故障,如果出现故障,根据投票数自动将某一个从库转换为新的主库,继续对外服务
能干什么
主从监控
监控主从redis运行是否正常
故障转移
master异常会进行选举切换
消息通知
将故障转移的结果发送给其他客户端
配置中心
客户端通过连接哨兵来获得当前redis服务的主服务地址
怎么玩
标准架构(说白了最少6台):
3个哨兵
只做监控维护集群,不放数据
1主2从
用于数据读取和存储
图示:
配置sentinel.conf文件
sentinel monitor master-name ip port quorum
quorum表示最少有几个哨兵认可客观下线,同意故障迁移的法定票数
sentinel auth-pass master-name password
运行流程和选举原理
当一个主从配置中的master失效后,sentinel可以选举出一个新的master用于自动接替原masterd 工作,主从配置中的其他slave自动指向新的master同步数据,一般建议sentinel采用奇数台
主观下线
SDOWN(主观不可用)是单个sentinel自己主观上检测到的关于master的状态,从sentinel的角度来看,如果发送了PING心跳后,在一定时间内没有收到合法的回复,就达到了SDOWN的条件。
Sentinel配置文件中的down-after-milliseconds设置判断主观下线的时间长度
客观下线
需要一定数量的sentinel达成一致,才认为master挂了
运行流程、故障切换
选出哨兵中的领导者
怎么选?
Raft算法(先到先得)
有哨兵的领导者开始推动故障切换流程并选出新的master
选出新的master
规则
配置文件中的优先级
偏移位置offset最大的
RunId最小的
其他的节点指向当前master
Sentinel发送命令,新的master解绑成为master,其他节点切换master
旧的master回来,成为slave,也指向当前master
同上
使用建议
哨兵数量多个、奇数、配置一致
哨兵+主从不能保证数据完全不丢失,承上启下引出下面的集群模式
集群cluster
为什么要集群?
数据量过大,单个master难以负重
sentinel不能保证数据完全不丢失
定义
Redis集群是一个提供在多个节点之间共享数据的程序集,redis集群可以支持多个master
能干什么
支持多个master,每个master又可以挂载多个slave
读写分离
高可用
海量数据读写
内置故障转移,支持高可用,无需哨兵
客户端连接任意一台都可以用
槽位slot负责分配到各个物理节点,由对应的集群来负责维护节点、插槽和数据直接的关系
集群算法-分片-槽位slot
分布式存储
哈希取余分区
定义:hash(key) % N个机器台数,计算出哈希值,用来决定数据映射到哪一个节点上
优点
简单粗暴,直接有效
缺点
扩容或者缩容会很麻烦
弹性扩容或故障停机的情况下,原来的取模公式就会发生变化
某个redis机器宕机了,由于台数数量变化,会导致hash取余全部数据重新洗牌
一致性哈希算法分区
定义:上面算法的优化,目的是当服务器个数发生变动时,尽量减少影响客户端到服务器的映射关系
步骤:
算法构建一致性哈希环
服务器IP节点映射
key 落到服务器的落键规则
优点
一致性哈希算法的容错性
一致性哈希算法的扩展性
缺点
一致性哈希算法的数据倾斜问题
哈希槽分区(√)
槽位slot
定义:哈希槽实质就是一个数组,数组[0,2^14 -1]形成hash slot空间
一个集群的槽位上限是16384,集群建议最大主节点数1000
Redis没有使用一致性hash,而是引入哈希槽的概念,每个key通过CRC16校验后对16384进行取模来决定防止在哪一个槽里,集群的每一个节点负责一部分Hash槽
分片
定义:使用Redis集群时我们会将存储的数据分散到多台redis机器上,这称为分片。简言之,集群中的每个Redis实例都被认为是整个数据的一个分片。
如何找到key的分片?
为了找到给定key的分片,我们对key进行CRC16(key)算法处理并通过对总分片数量取模。然后,使用确定性哈希函数,这意味着给定的key将多次始终映射到同一个分片,我们可以推断将来读取特定key的位置。
优势
方便扩缩容和数据分派查找
缺点
Redis集群不保证 强一致性,这意味着在特定的条件下,Redis集群可能会丢掉一些被系统收到的写入请求命令
补充:
为什么最大槽是16384?
槽位太大,发心跳太耗带宽
槽位越小,节点少的情况下,压缩比高,容易传输
redis作者不建议redis cluster节点数量超过1000个。 那么,对于节点数在1000以内的redis cluster集群,16384个槽位够用了。没有必要拓展到65536个
常用命令
CRC16算法分析
9、SpringBoot集成
客户端
jedis
Jedis Client是Redis官网推荐的一个面向Java客户端,库文件实现了对各类API进行封装调用。线程不安全,开销大
依赖
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>4.3.1</version>
</dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>4.3.1</version>
</dependency>
lettuce(√)SpringBoot2.0以后默认
Lettuce是一个Redis的Java驱动包,底层使用Netty减少多连接的开销,线程安全
依赖
<dependency>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
<version>6.2.1.RELEASE</version>
</dependency>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
<version>6.2.1.RELEASE</version>
</dependency>
集成RedisTemplate
单机连接
①starter集成
②配置文件写连接信息
③注入RedisTemplate使用
①直接接使用默认的(不推荐)
②自定义RedisTemplate
集群连接
大致内容同上,只是配置文件里的连接信息要改改
# 获取失败 最大重定向次数
spring.redis.cluster.max-redirects=3
spring.redis.cluster.max-redirects=3
#支持集群拓扑动态感应刷新,自适应拓扑刷新是否使用所有可用的更新,默认false关闭
spring.redis.lettuce.cluster.refresh.adaptive=true
spring.redis.lettuce.cluster.refresh.adaptive=true
#定时刷新
spring.redis.lettuce.cluster.refresh.period=2000
spring.redis.lettuce.cluster.refresh.period=2000
spring.redis.cluster.nodes=192.168.111.175:6381,192.168.111.175:6382
PS:RedisTemplate使用的是JDK序列化方式(默认),需要替换成jackson,否则存储的内容会出现乱码问题
更进一步:封装RedisTemplate
工作中大部分项目都会对RedisTemplate作进一步的工具类封装,直接注入RedisTemplate的比较少见。
封装内容这里不描述,网络上搜一搜一大堆,没啥统一的封装标准。如果你需要我的代码,可以试着找找我的bilibili0.0。
10、进阶
缓存问题
缓存穿透
缓存和数据库中都没有的数据,而用户不断发起请求。
对于恶意攻击导致的缓存穿透,使用布隆过滤器(Google的Guava有提供)
对于业务逻辑本身就不能避免的缓存穿透,将key-value对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击
缓存击穿
缓存中可能过期了,没有,请求到数据库
设置热点数据永不过时
接口限流与熔断,降级
互斥加锁
缓存雪崩
数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。和缓存击穿不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。
缓存数据的过期时间设置随机或者永不过期,防止同一时间大量数据过期现象发生
如果缓存数据库是分布式部署,将热点数据均匀分布在不同的缓存数据库中。
redis高可用
主从+哨兵
集群
做好AOF+RDB备份恢复工作
钱多上云
服务降级、限流(Sentinel)
缓存污染
缓存污染是指缓存中的数据与真实数据源中的数据不一致的现象
缓存污染多数是由开发者更新缓存不规范造成的
读数据时,先读缓存,缓存没有的话,再读数据源,然后将数据放入缓存,再响应请求
写数据时,先写数据源,然后失效(而不是更新)掉缓存
缓存预热怎么做?
结合PostConstruct注解实现
缓存双写一致性
操作细分
只读缓存
读写缓存
同步直写
写数据库后同步写redis
异步缓写
容许数据库和redis之间的数据存在一定的延时
具体实现
先读redis,没有再读数据库,读完回写redis
QPS<=1000可以用,高并发不行!
高QPS怎么解决?
双检加锁
redis没有,加锁,再查一次redis,没有,从数据库查,查完回写redis,返回
四种更新策略
先更数据库,再更缓存×
先更缓存,再更数据库×
先删缓存,在更数据库×
进阶 延迟双删
但是也会引申出其他问题
先更新数据库,在删除缓存(主流的,但是不100%权威)
认清一个现实:缓存和数据库之间的数据是不可能永远维持在一致性状态的
引申
结合消息中间件实现最终一致性
总结:
在大多数业务场景下优先使用先更新数据库,再删除缓存的方案(先更库→后删存)。
1 先删除缓存值再更新数据库,有可能导致请求因缓存缺失而访问数据库,给数据库带来压力导致打满mysql。
2 如果业务应用中读取数据库和写缓存的时间不好估算,那么,延迟双删中的等待时间就不好设置。
结合Canal实现最终一致性:
①MySQL开binlog
②授权canal连接mysql
③结合canal封装RedisTemplate操作
更多详情看官网:https://github,com/alibaba/canal/wiki/ClientExample,我也是纸上谈兵,这么严格的一致性要求没碰到过,所以压根没用过。
分布式锁实现
分布式锁是什么?
锁的种类?
单机版同一个M虚拟机内,synchronized或者Lock接日
一套代码会部署很多份,遇到这种情况,上面的解决方式就失效了
通常我们的系统都是集群部署的,
通常我们的系统都是集群部署的,
分布式多个不同IM虚拟机,单机的线程锁机制不再起作用,资源类在不同的服务器之间共享了。
一个靠谱的分布式锁需要具备什么?
独占性
高可用
防死锁
防误删
可重入
自动续期
小规模
基于set key value [EX seconds] [PX milliseconds] [NX]XX] 命令自己封装分布式锁(小公司够用,细节要自己把握,不过有一说一我见到的一些项目,要么直接调setnx的api当锁用,要么就不自己封装了,直接上Redission)
大规模
基于RedLock算法实现分布式锁(√)
上redisson
WatchDog机制
WatchDog机制,默认情况下有30秒的锁持有时间,会进行续期操作
细节:当我们加锁成功拿到redis分布式锁后,同时也异步启动一个线程,这个就是看门狗线程,它会判断对应key是否到过期时间的1/3,
如果到了,自动执行expire key命令,对key进行续期,直到任务完成。比如过期时间是30秒,到第20秒后,会自动给key续命加到
30秒,以此类推。当然,实现看门狗的任务通过lua脚本来完成的。
如果到了,自动执行expire key命令,对key进行续期,直到任务完成。比如过期时间是30秒,到第20秒后,会自动给key续命加到
30秒,以此类推。当然,实现看门狗的任务通过lua脚本来完成的。
lua
Redission解决了我们自己实现分布式锁时遇到的什么问题?
①单点失效
②锁续期
③可重入
④原子性
总结
单机上synchronized
缓存过期淘汰策略
缓存过期策略
惰性删除(√)
取出的时候才会去检查
对cpu友好,对内存不友好
定期删除
字面意思,定期扫描
对内存友好,对cpu不友好
缓存淘汰机制
volatile(设置了过期时间)
lru
最近最少使用
lfu
最不经常使用
ttl
将要过期
random
随机
allkeys(所有key)
lru
lfu
random
noeviction
不淘汰,返回错误
五大数据类型底层的数据结构
String
简单动态字符串SDS:Redis会根据不同的键值选择使用不同的编码格式
Hash
7之前
压缩链表ziplist(特殊编码的双向链表)
哈希表hashtable
7之后
listpack(紧凑列表,它的特点就是用一块连续的内存空间来紧凑地保存数据)
哈希表hashtable
List
7之前
quicklist(一种以ziplist为结点的双端链表结构. 宏观上, quicklist是一个链表, 微观上, 链表中的每个结点都是一个ziplist)
ziplist(特殊编码的双向链表)
7之后
quicklist(一种以ziplist为结点的双端链表结构. 宏观上, quicklist是一个链表, 微观上, 链表中的每个结点都是一个ziplist)
listpack(紧凑列表,它的特点就是用一块连续的内存空间来紧凑地保存数据)
Set
intset整数集合
哈希表hashtable
ZSet
7之前
ziplist(特殊编码的双向链表)
skiplist(redis跳跃表并没有在单独的类(比如skplist.c)中定义,空间换时间)
7之后
listpack(紧凑列表,它的特点就是用一块连续的内存空间来紧凑地保存数据)
skiplist(redis跳跃表并没有在单独的类(比如skplist.c)中定义,空间换时间)
布隆过滤器
定义:由一个初值都为零的bit数组和多个哈希函数构成,用来快速判断集合中是否存在某个元素
能干嘛
高效地插入和查询,占用空间少,返回的结果是不确定性+不够完美
原理
实质就是一个大型位数组和几个不同的无偏hash函数(无偏表示分布均匀)。由一个初值都为零的bit数组和多个哈希函数构成,用来快速判断某个数据是否存在。
优点
高效地插入和查询,内存中占用bit空间小
缺点
不能删除元素。因为删除元素会导致误判率增加,因为hash冲突同一个位置可能存的东西是多个共有的,你删除一个的同事可能也把其他的删除了
存在误判,不能精准过滤;有,是很有可能有;无,是肯定无
存在误判,不能精准过滤;有,是很有可能有;无,是肯定无
大数据统计(TODO)
BigKey
什么是大key?
String大于10kb
list、hash、set、zset元素个数达到5000
危害
内存不均匀,迁移困难
删除时造成阻塞
网络流量阻塞
如何产生?
日积月累
如何发现?
redis-cli --bigkeys
memory usage key
如何删除?
String
一般del,太大unlink
List
ltrim渐进删除
Set
sscan获取少量,srem删除
ZSET
使用zscan每次获取部分元素,再使用ZREMRANGEBYRANK命令删除每个元素
Hash
hscan获取少量,然后hdel
Redis单线程VS多线程
为什么当年说它快
基于内存读写快
数据结构优化的好,也快
单线程IO多路复用和非阻塞IO
当年为什么是单线程
作者认为Reds的主要瓶颈是内存或者带宽,不是CPU,而且单线程开发、调试、维护简单
即使使用单线程模型也并发的处理多客户端的请求,主要使用的是IO多路复用和非阻塞IO
为什么又要加多线程
时代变了大人,硬件发展快
3.x时代大key阻塞删除,大问题
版本
4以前是单线程
4开始有一点多线程
6开始完全支持多线程
那它为什么这么快呢?
IO多路复用+epoll函数使用,才是redis为什么这么快的直接原因,而不是仅仅单线程命令+redis安装在内存中。
总结:
Redis自身出道就是优秀,基于内存操作、数据结构简单、多路复用和非阻塞I/O、避免了不必要的线程上下文切换等特性,在单线程的环境下依然很快;
但对于大数据的 key删除还是卡顿厉害,因此在Redis 4.0引入了多线程unlink key/flushall async等命令,主要用于Redis 数捷的异步删除;
而在Redis6/7中引入了I/0多线程的读写,这样就可以更加高效的处理更多的任务了,Redis只是将I/O读写变成了多线程,而命令的执行依旧是由主线程串行执行的,因此在多线程下操作 Redis不会出现线程安全的问题。
Redis无论是当初的单线程设计,还是如今与当初设计相背的多线程,目的只有一个:让 Redis变得越来越快。
Redis自身出道就是优秀,基于内存操作、数据结构简单、多路复用和非阻塞I/O、避免了不必要的线程上下文切换等特性,在单线程的环境下依然很快;
但对于大数据的 key删除还是卡顿厉害,因此在Redis 4.0引入了多线程unlink key/flushall async等命令,主要用于Redis 数捷的异步删除;
而在Redis6/7中引入了I/0多线程的读写,这样就可以更加高效的处理更多的任务了,Redis只是将I/O读写变成了多线程,而命令的执行依旧是由主线程串行执行的,因此在多线程下操作 Redis不会出现线程安全的问题。
Redis无论是当初的单线程设计,还是如今与当初设计相背的多线程,目的只有一个:让 Redis变得越来越快。
RabbitMQ
应用场景
解耦
流量削峰
异步
AMQP协议模型
AMQP 091(当前用的)
协议模型
生产者生产消息,将消息发送到交换机,接着交换机使用成为Bindings的规则,将消息副本分发到队列,然后代理将消息传送给订阅队列的消费者,或者消费者按需从队列中提取消息。
AMQP 091的消息确认概念
当消息传递给消费者时,消费者会通知代理,通知方式可以是自动的,也可以是应用程序开发人员选择通知时立即通知。使用消息确认时,代理只有在收到该消息(或一组消息)的通知时才会从队列中完全删除该消息。
AMQP 091的消息特殊处理机制
在某些情况下,例如,当无法路由消息时,消息可能会被返回给发布者、被丢弃,或者,如果代理实施了扩展,则被放入所谓的“死信队列”。发布者通过使用某些参数发布消息来选择如何处理此类情况。
AMQP 1.0
MQTT
STOMP
核心概念(基于AMQP091)
生产者
事务(代码端,没用过)
消费者
订阅消息(推荐)
轮询消息(不推荐,效率低)
消息确认
问题:
代理何时应该从队列中删除消息?
确认模式
代理向应用程序发送消息后确认(自动确认模型)
basic.deliver 或 basic.get-ok
在应用程序发回确认后确认(手动确认模型)
basic.ack
特殊情况处理:
如果消费者在未发送确认的情况下死亡,则代理会将其重新交付给另一个消费者,或者,如果当时没有可用的消费者,则代理将等到至少一个消费者注册到同一队列中,然后再尝试重新交付。
消息拒绝
可以通过拒绝消息来向代理指示消息处理失败(或当时无法完成)。拒绝消息时,应用程序可以要求代理放弃或重新排队。
否定确认
basic.reject无法拒绝多条消息,但是RabbitMQ提供了一个 AMQP 0-9-1 扩展称为否定确认或 nack。
预取消息
对于多个消费者共享一个队列的情况,在发送下一个确认之前,能够指定每个消费者一次可以发送多少条消息是有用的。这可以用作一种简单的负载平衡技术,或者在消息倾向于批量发布的情况下提高吞吐量。例如,如果一个生产应用程序由于其所做工作的性质而每分钟发送一条消息。
connection(http)
Channel(AMQP),就是个连接池,共享单个 TCP 连接的轻量级连接
exchange交换机,将消息路由到匹配的队列中
交换机的类型
direct(默认的也是这个类型,比较特殊的是它的交换机名的空字符串,所有队列以队列名作为路由键绑定到这个交换机上)
Routing Key==Binding Key
一对一定点发送
一对多(队列)组播
fanout
无视routing key,路由到所有与它绑定的Queue中
所用于广播(队列)
topic
Routing Key模糊匹配
多用于(队列)组播
headers
不依赖于routing key与binding key的匹配规则来路由消息,而是根据发送的消息内容中的header属性进行匹配。
交换机的属性
name,交换机名称
Durability持久性,MQ重启后,交换机还在
Auto-delete
当最后一个队列和交换机解绑时,交换机会自动删除
Internal
If yes, clients cannot publish to this exchange directly. It can only be used with exchange to exchange bindings.
参数
可选的各种参数,根据插件定义
alternate-exchange
如果无法以其他方式路由到此交换器的消息,请将它们发送到此处指定的备用交换器。
默认提供的交换机
Direct exchange
(Empty string)((默认交换机)
特殊属性:
创建的每个队列都使用与队列名称相同的路由键自动绑定到它
ps:默认交换不允许绑定/取消绑定操作。绑定到默认交换的操作将导致错误
amq.direct
根据消息路由键将消息传递到队列。直接交换是消息单播路由(单队列)的理想选择。它们也可用于组播路由(多队列)。
Fanout exchange
amq.fanout
将消息路由到绑定到它的所有队列,并忽略路由键。扇出交换非常适合消息的广播路由。
Topic exchange
amq.topic
根据消息路由密钥与用于将队列绑定到交换的模式之间的匹配,将消息路由到一个或多个队列。通常用于消息的组播路由
Headers exchange
忽略路由键属性。相反,用于路由的属性取自 headers 属性。如果标头的值等于绑定时指定的值,则认为消息匹配。
amq.match
amq.headers
PS:可以使用多个header值,但是需要用x-match参数指定匹配任意还是全部header
queue队列
队列的属性
name
Durable 持久化
MQ重启后队列还在
参数
各种,如消息的TTL、队列长度
Auto expire
队列在被自动删除之前可以不使用多长时间(毫秒)
参数x-expires,数值
Max length
队列在开始从其头部删除消息之前可以包含多少(就绪)消息。
队列在开始从其头部删除消息之前可以包含多少(就绪)消息。
参数x-max-length,数值
Max length bytes
队列在开始从其头部删除消息之前可以包含的就绪消息的总正文大小。
队列在开始从其头部删除消息之前可以包含的就绪消息的总正文大小。
参数x-max-length-bytes,数值
Message TTL
发布到队列的消息在被丢弃之前可以存活多长时间(毫秒)。
发布到队列的消息在被丢弃之前可以存活多长时间(毫秒)。
参数x-message-ttl,数值
Overflow behaviour
设置队列溢出行为。这决定了当达到队列的最大长度时,消息会发生什么情况。有效值为 drop-head、reject-publish 或 reject-publish-dlx。仲裁队列类型仅支持 drop-head 和 reject-publish。
参数x-overflow,字符串
Single active consumer
如果设置,请确保一次只有一个消费者从队列中消费,并故障转移到另一个注册的消费者,以防活动消费者被取消或死亡。
参数x-single-active-consumer,布尔值
Dead letter exchange
消息被拒绝或过期时将重新发布到的交换器的可选名称。
消息被拒绝或过期时将重新发布到的交换器的可选名称。
参数x-dead-letter-exchange,,字符串
Dead letter routing key
当消息是死信时使用的可选替换路由密钥。如果未设置,则将使用消息的原始路由密钥。
当消息是死信时使用的可选替换路由密钥。如果未设置,则将使用消息的原始路由密钥。
参数x-dead-letter-routing-key,字符串
Leader locator
设置在节点集群上声明时队列领导者的定位规则。有效值为客户端本地(默认)和平衡。
参数x-queue-leader-locator,字符串
延迟队列(delayed)
①基于死信队列实现延迟队列(TTL+Dead Letter Exchanges)
①声明队列时,通过x-dead-letter-exchange去指明这个队列的死信交换机,队列中出现死信后,死信会重新投递到死信交换机中
②队列不指定消费者,设置过期时间,到期后消息变死信
②开一个队列绑到死信交换机上,消费者监听这个队列去处理死信,达到一个延迟消费的效果
②安装插件,集成新的交换机类型
rabbitmq-plugins enable rabbitmq_delayed_message_exchange
使用x-delayed-message声明交换机
消息带x-delay头
延迟时间内,消息不会进入队列,时间到了才会进入队列
懒惰队列lazy queue
通过设置队列参数实现懒惰队列,消息会先落盘
声明队列时,加上x-queue-mode=lazy参数
ps:这个和延迟队列的使用场景不同,它就要是为了减轻内存的压力,不过这个模式的磁盘IO会比较多
死信交换Dead Letter Exchanges
死信的来源?
死信怎么处理?
会回到交换机
丢弃
或者持久化处理
死信队列?
声明队列时,指定可选 x-dead-letter-exchange 参数
使用注意:
在使用队列之前,必须声明它。声明队列将导致创建它(如果它尚不存在)。如果队列已经存在并且其属性与声明中的属性相同,则声明将不起作用。当现有队列属性与声明中的属性不同时,将引发代码为 406 (PRECONDITION_FAILED) 的通道级异常。
bindings绑定
交换机和队列之间的绑定
交换机和交换机之间的绑定
ps:如果消息无法路由到任何队列(例如,由于消息发布到的 Exchange 没有绑定),则会将其丢弃或返回给发布者,具体取决于发布者设置的消息属性。
属性:mandatory
false(默认)
消息将被丢弃或重新发布到备用交换(如果有)
true
消息将返回给该队列(必须设置返回的消息处理程序才能处理返回消息)
virtual host
用于环境隔离
message消息
消息的属性
Content type
Content encoding
Routing key
Delivery mode
交付模式(持久性或非持久性)
Message priority
消息优先级
Message publishing timestamp
Expiration period
有效期
Publisher application id
死信消息
死信的产生
拒绝,直接丢弃
溢出,先进先出
超时
死信的处理
丢弃
入库
监听,进入死信队列,消费端监听死信队列
工作模式
RPC(没用过)
发布者确认(Java端好像不支持,没深究)
direct交换机
HelloWorld(direct交换机)
Basic one-to-one messaging
work queue(direct交换机)
Load balancing between multiple consumers
routing(direct交换机+routingkey)
Selective message routing to queues based on routing keys.
fanout交换机
发布订阅(fanout交换机)
Message broadcast to multiple queues
topic交换机
topic(topic交换机+正则表达式)
Messages routed to queues based on wildcard routing keys.
headers交换机
Message routed based on header values instead of routing keys.
常见场景
消息丢失
生产者生产消息到RabbitMQ Server消息丢失
生产者异步发布确认机制
配置文件publisher-confirm-type: correlated,交换机确认
①声明ConfirmCallBack接口,重写confirm方法,绑定到RabbitMQTemplate
publisher-returns: true,队列确认
①声明ReturnsCallBack接口,重写returnMessage方法,绑定到RabbitMQTemplate
PS:这个机制只确认消息成功到达交换机,不关心消息是否被消费
使用备用交换机,消息投递失败,转投递到备用交换机
声明交换机的时候带alternate-exchange=exchangename参数指定备用交换机
Java这边的Server端有一个自动恢复机制
Server存储的消息丢失
持久化
配置队列的持久化
Durable = true
配置Message的持久化
rabbitTemplate.convertAndSend(exchange, routingKey, message, msg -> {
msg.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT);
return msg;
});
msg.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT);
return msg;
});
RabbitMQ Server到消费者消息丢失
消费者使用消费成功使用ACK确认消息
自动ACK(默认)
手动ACK(√)
acknowledge-mode: manual
使用basicAck方法处理消息
如果消息消费失败,使用basicNAck拒绝消息
PS:这个机制是确认消息被消费者消费完成后,通知队列删除对应的消息副本。
消费者消费失败使用NACK,消息放回服务端,消费者重试一次
其他:
使用扩展的发布确认(Publisher Confirms)默认解决,不过Spring AMQP好像没做这方面的适配,需要自己用原生的Java API
实现队列的高可用性,并用死信队列来保证消息的处理
重复消费
消息内部会生成一个唯一id(deliverTag 64位),消费者在处理消息时检查是否已经处理过该标识的消息。可以使用数据库或分布式缓存(如Redis)来记录已处理的消息标识
确保消息的处理逻辑是幂等的,即同一消息即使被处理多次,结果也是相同的。例如,在数据库操作中,可以使用唯一约束来防止重复插入。
使用数据库的去重表来记录已经处理的消息,处理新消息前先检查去重表,防止重复处理
消息积压
监控报警,提前发现问题进行扩容
增加消费者数量
优化消费者处理能力
顺序消息
①单一消费者
@RabbitListener(queues = "myQueue", concurrency = "1")
public void receive(Message message) {
// 处理顺序消息
}
public void receive(Message message) {
// 处理顺序消息
}
②使用独占队列
③设置预取值basic_qos为1实现顺序消费
④消息分区
可以根据消息的某个属性(如用户ID、订单ID)将消息路由到特定的队列,从而确保特定分区的消息顺序被保证,且可以扩展到多个队列进行并行处理
SpringBoot整合
概要:
我们日常使用的基本都是这种方式,所以我们可以了解它给我们自动装配了什么默认配置
①默认是没有开启连接重试的,需要手动开启
②默认交换机、队列持久化
③默认ACK
依赖
starter-amqp
基础配置
连接信息
声明
交换机、队列、绑定
发送
RabbitMQTemplate
接收
@RabbitMQListener
Elastic
ES简介
什么是ES?
Elasticsearch(后称为 ES )是一个天生支持分布式的搜索、聚合分析和存储引擎
ELK
Elasticsearch
基于Json的分布式搜索和分析引擎Elastic Stack的核心
Logstash
动态数据收集管道,生态丰富
Kibana
提供数据的可视化界面
Beats
轻量级的数据采集器
特点
开源免费、分布式高性能可伸缩易扩展、、易用、跨语言
常见指标
ES支持的搜索类型
结构化搜索
非结构化搜索
文本搜索
地理位置搜索
ES擅长与不擅长
ES最擅长从海量数据中检索少量相关数据,但不擅长单次查询大量数据
ES的实时写入性
侧重海量数据的检索,实时写入实时性不是很高,默认1秒。ES的实时写入和检索性能是一个二选一的行为,生产中我们经常通过牺牲写入实时性来换取更高更快的数据检索性能
ES的事务支持性
不支持
ES的极限性能
无敌!PB级数据秒内响应
核心概念
Node节点
一个节点就是一个Elasticsearch的实例,可以理解为一个 ES 的进程
注意:
一个节点 ≠ 一台服务器
Role角色(应用角度不需要太关注这个概念)
主节点(active master)
一般指活跃的主节点,一个集群中只能有一个,主要作用是对集群的管理。
候选节点(master-eligible)
当主节点发生故障时,参与选举,也就是主节点的替代节点。
数据节点(data node)
数据节点保存包含已编入索引的文档的分片。数据节点处理数据相关操作,如 CRUD、搜索和聚合。这些操作是 I/O 密集型、内存密集型和 CPU 密集型的。监控这些资源并在它们过载时添加更多数据节点非常重要。
预处理节点(ingest node)
预处理节点有点类似于logstash的消息管道,所以也叫ingest pipeline,常用于一些数据写入之前的预处理操作。
Index索引
索引是什么?
在 ES 中,索引表述的含义等价于 MySQL 中的表
索引的组成
alias
索引别名
settings
索引设置,常见设置如分片和副本的数量等
mapping
即映射,定义了索引中包含哪些字段,以及字段的类型、长度、分词器等
Type(7.0版本后删除了这个概念)
Document文档
什么是Document?
理解为mysql里的行记录,JSON格式
Document的结构 doc struct
meta data
所有的元字段均已下划线开头,为系统字段
_index:索引名称
_id:文档 id。
_version:版本号
_seq_no:索引级别的版本号,索引中所有文档共享一个 _seq_no
_primary_term:_primary_term是一个整数,每当Primary Shard发生重新分配时,比如节点重启,Primary选举或重新分配等,_primary_term会递增1。主要作用是用来恢复数据时处理当多个文档的_seq_no 一样时的冲突,避免 Primary Shard 上的数据写入被覆盖。
source data
指业务数据,即最终写入的用户数据。
Cluster集群
单体服务的问题:
处理能力(包括吞吐量、并发能力、和算力等)有限,当业务量不断增加时,单体服务无法满足。
所有的服务依赖于同一个节点,当该节点出现故障,服务就完全不可用,风险高,可用性差
为什么是集群?
看上面
ES集群的一些概念
自动发现
核心配置
集群的健康值检查
健康状态
绿色:所有分片都可用
黄色:至少有一个副本不可用,但是所有主分片都可用,此时集群能提供完整的读写服务,但是可用性较低。
红色:至少有一个主分片不可用,数据不完整。此时集群无法提供完整的读写服务。集群不可用。
健康值检查
GET _cat/health
GET _cluster/health
Shard分片
什么是分片?
如过用一句话来概括,分片可以理解为 索引的碎片。并且所有碎片都是可以无限复制的
分片的种类
主分片(primary shard)
副本分片(replica shard)
分片的基本策略
一个索引包含一个或多个分片,在7.0之前默认五个主分片,每个主分片一个副本;在7.0之后默认一个主分片。副本可以在索引创建之后修改数量,但是主分片的数量一旦确定不可修改,只能创建索引
每个分片都是一个Lucene实例,有完整的创建索引和处理请求的能力
ES会自动在nodes上做分片均衡 shard reblance
一个doc不可能同时存在于多个主分片中,但是当每个主分片的副本数量不为一时,可以同时存在于多个副本中
主分片和其副本分片不能同时存在于同一个节点上
完全相同的副本不能同时存在于同一个节点上
分片的作用和意义
高可用性:提高分布式服务的高可用性
提高性能:提供系统服务的吞吐量和并发响应的能力
易扩展:当集群的性能不满足业务要求时,可以方便快速的扩容集群,而无需停止服务
使用
Restful API
GET
POST
PUT
DELETE
HEAD
基本操作
Search API
GET /<index_name>/_search
GET /_search
GET /_search
可选参数
size:单次查询多少条文档,默认为 10
from:起始文档偏移量。需要为非负数,默认为0
**timeout:**指定等待每个分片响应的时间段。如果在超时到期之前未收到响应,则请求失败并返回错误。默认为无超时。
Index API
索引设置
索引设置
PUT test_setting
{
"settings": {
"number_of_shards": 1, //主分片数量为 1
"number_of_replicas": 1 //每个主分片分配一个副本
}
}
{
"settings": {
"number_of_shards": 1, //主分片数量为 1
"number_of_replicas": 1 //每个主分片分配一个副本
}
}
索引修改
PUT test_setting/_settings
{
"number_of_replicas": 0
}
{
"number_of_replicas": 0
}
静态索引设置
只能在创建索引时或在关闭状态的索引上设置
重要的静态配置
index.number_of_shards:索引的主分片的个数,默认为 1,此设置只能在创建索引时设置
每个索引的分片的数量上限为 1024,这是一个安全限制,以防止意外创建索引,这些索引可能因资源分配而破坏集群的稳定性。export ES_JAVA_OPTS=“-Des.index.max_number_of_shards=128” 可以通过在属于集群的每个节点上指定系统属性来修改限制
动态索引设置
即可以使用 _setting API 在实时修改的配置
重要的动态配置
**index.number_of_replicas:**每个主分片的副本数。默认为 1,允许配置为 0。
**index.refresh_interval:**执行刷新操作的频率,默认为1s. 可以设置 -1 为禁用刷新。
**index.max_result_window:**from + size搜索此索引 的最大值。默认为 10000. 搜索请求占用堆内存和时间 from + size,这限制了内存。
创建索引
PUT <index_name>
命名规范
以小写英文字母命名索引
不要使用驼峰或者帕斯卡命名法则
如过出现多个单词的索引名称,以全小写 + 下划线分隔的方式:如test_index
删除索引
DELETE /<index_name>
判断索引是否存在
HEAD <index_name>
PS:索引的不可变性
ES 索引创建成功之后,以下属性将不可修改
索引名称
主分片数量
字段类型
Reindex
Document API
文档的操作类型
创建
创建一条 _id 为 1 的文档,并为其添加 name 和 content 两个字段
PUT goods/_create/1
{
"name":"傻妞手机",
"content":"华人牌2060款手机傻妞"
}
PUT goods/_create/1
{
"name":"傻妞手机",
"content":"华人牌2060款手机傻妞"
}
索引(创建或全量更新文档)
在 ES 中,写入操作被称为 Index,这里Index为动词,即索引数据为将数据创建在 ES 中的索引,写入数据亦可称之为“索引数据”。可以是创建,也可以是全量替换
向 goods 索引中索引一条 _id 为 1 的文档,并为其添加 name 和 content 两个字段:
PUT goods/_doc/1
{
"name":"傻妞手机",
"content":"华人牌2060款手机傻妞"
}
PUT goods/_doc/1
{
"name":"傻妞手机",
"content":"华人牌2060款手机傻妞"
}
自动生成 id
创建一个文档,并随机生成文档 id:
POST test_index/_doc
{
"test_field":"test",
"test_title":"title"
}
POST test_index/_doc
{
"test_field":"test",
"test_title":"title"
}
文档的 CRUD
Document Index API
将 JSON 文档添加到指定的数据流或索引并使其可被检索。如果目标是索引并且文档已经存在,则请求更新文档并增加其版本号。
基本语法 ★
PUT /<target>/_doc/<_id>
PUT /<target>/_create/<_id>
POST /<target>/_create/<_id>
PUT /<target>/_create/<_id>
POST /<target>/_create/<_id>
Get API
查询指定 id 的文档
GET <index>/_doc/<_id>
判断指定 id 的文档是否存在
HEAD <index>/_doc/<_id>
_source API
使用 _source API 可以打开或者关闭源数据字段,true 为打开,false 为关闭,默认为 true
GET <index>/_doc/<_id>?_source=false
Delete API
删除索引中指定 id 的文档,Document Delete API 必须指定 id
DELETE /<index>/_doc/<_id>
Update API
修改局部字段或者数据
POST /<index>/_update/<_id>
{
"doc": {
"<field_name>": "<field_value>"
}
}
{
"doc": {
"<field_name>": "<field_value>"
}
}
Multi get (mget) API
语法
支持查询同一个索引的不同 id,也可以查询不同索引的不同 id:
GET /_mget
{
"docs": [
{
"_index": "<index_name>",
"_id": "<_id>"
},
{
"_index": "<index_name>",
"_id": "<_id>"
}
]
}
GET /_mget
{
"docs": [
{
"_index": "<index_name>",
"_id": "<_id>"
},
{
"_index": "<index_name>",
"_id": "<_id>"
}
]
}
简化语法
GET <index_name>/_mget
{
"docs": [
{
"_id": "<_id>"
},
{
"_id": "<_id>"
}
]
}
{
"docs": [
{
"_id": "<_id>"
},
{
"_id": "<_id>"
}
]
}
进一步简化
GET /<index_name>/_mget
{
"ids" : ["<_id_1>", "<_id_2>"]
}
{
"ids" : ["<_id_1>", "<_id_2>"]
}
复杂语法的意义
负责的查询语法的意义在于其可编程性高,可以针对不同的文档,指定不同的查询策略
Bulk API
语法:
POST /_bulk
POST /<index>/_bulk
{"action": {"mata data"}}
{"data"}
POST /<index>/_bulk
{"action": {"mata data"}}
{"data"}
使用场景
大数据量的批量操作,比如数据从 MySQL 中一次性写入 ES,批量写入减少了对 es 的请求次数,降低了内存开销以及对线程的占用
注意和优缺点
注意
bulk api 对 json 的语法有严格的要求,除了 delete 外,每一个操作都要两个 json 串(mata data和 source field data),且每个 json 串内不能换行,非同一个 json 串必须换行,否则会报错;
bulk 操作中,任意一个操作失败,是不会影响其他的操作的,但是在返回结果里,会告诉你异常日志
优点
优点:相较于普通的Json格式的数据操作,不会产生额外的内存消耗,性能更好,常用于大数据量的批量写入
缺点
缺点:可读性差,可能会没有智能提示
DeleteByQuery
POST /<index_name>/_delete_by_query
{
"query": {
...
}
}
{
"query": {
...
}
}
UpdateByQuery
Mapping(映射)
映射 Mapping是什么?
ES 中的 mapping 有点类似与关系数据库中表结构的概念,在 MySQL 中,表结构里包含了字段名称,字段的类型还有索引信息等。在 Mapping 里也包含了一些属性,比如字段名称、类型、字段使用的分词器、是否评分、是否创建索引等属性,并且在 ES 中一个字段可以有对个类型。
如何查看索引映射
查看完整的索引 mapping
GET /<index_name>/_mappings
查看索引中指定字段的 mapping
GET /<index_name>/_mappings/field/<field_name>
自动映射:Dynamic mapping
简介:
自动映射也叫动态映射,是 ES 在索引文档写入发生时自动创建 mapping 的一种机制。ES 在创建索引之前,并不强制要求创建索引的 mapping,ES 会根据字段的值来推断字段类型,进而自动创建并指定索引类型。自动映射器会尽可能的把字段映射为宽字段类型。
Mapping 的使用禁忌
ES 没有隐式类型转换
ES 不支持类型修改
生产环境尽可能的避免使用 dynamic mapping
自动映射器的意义是什么?
官方的解释是为了照顾入门学习者,但实际上我更建议初学者尽量避免使用自动映射器,而尽可能多的显式声明 mapping,因为显式创建 mapping 是生产环境必须的,所以这是必须掌握的技能。
反而是老手,在非生产环境中,使用自动映射会比较方便。
反而是老手,在非生产环境中,使用自动映射会比较方便。
手动映射:Explicit mapping
简介:
手动映射也叫做显式映射,即:在索引文档写入之前,认为的创建索引并且指定索引中每个字段类型、分词器等参数。
创建索引的 mapping
PUT /<index_name>
{
"mappings": {
"properties": {
"field_a": {
"<parameter_name>": "<parameter_value>"
},
...
}
}
}
{
"mappings": {
"properties": {
"field_a": {
"<parameter_name>": "<parameter_value>"
},
...
}
}
}
修改 mapping 属性
PUT <index_name>/_mapping
{
"properties": {
"<field_name>": {
"type": "text",// 必须和原字段类型相同,切不许显式声明
"analyzer":"ik_max_word",// 必须和元原词器类型相同,切必须显式声明
"fielddata": false
}
}
}
{
"properties": {
"<field_name>": {
"type": "text",// 必须和原字段类型相同,切不许显式声明
"analyzer":"ik_max_word",// 必须和元原词器类型相同,切必须显式声明
"fielddata": false
}
}
}
注意:
字段类型不可修改
字段分词器不可修改
ES数据类型 field data type
概述
每个字段都有字段数据类型或字段类型。其大致分为两种
被分词的字段类型
text
match_only_text
...
不会被分词的字段类型
keyword
数值类型
...
ES 支持的数据类型
基本数据类型
Numbers:数字类型,包含很多具体的基本数据类型
binary:编码为 Base64 字符串的二进制值。
boolean:即布尔类型,接受 true 和 false。
alias:字段别名。
Keywords:包含 keyword ★、constant_keyword 和 wildcard。
Dates:日期类型,包括 data ★ 和 data_nanos,两种类型
对象关系类型(复杂类型)
object:非基本数据类型之外,默认的 json 对象为 object 类型。
flattened:单映射对象类型,其值为 json 对象。
nested ★:嵌套类型。
join:父子级关系类型。
结构化类型
Range:范围类型,比如 long_range,double_range,data_range 等
ip:ipv4 或 ipv6 地址
version:版本号
murmur3:计算和存储值的散列
聚合数据类型
aggregate_metric_double
histogram:
文本搜索字段
text ★:文本数据类型,用于全文检索。
annotated-text:
completion ★**:**
search_as_you_type:
token_count:
文档排名类型
dense_vector:记录浮点值的密集向量。
rank_feature:记录数字特征以提高查询时的命中率。
rank_features:记录数字特征以提高查询时的命中率。
空间数据类型 ★
geo_point:纬度和经度点。
geo_shape:复杂的形状,例如多边形。
point:任意笛卡尔点。
shape:任意笛卡尔几何。
其他类型
percolator:用Query DSL 编写的索引查询。
映射参数
支持的映射参数
analyzer ★
指定分析器,只有 text 类型字段支持。
coerce
是否允许强制类型转换,支持对字段段度设置或者对整个索引设置。
true: “1” => 1
false: “1” =< 1
true: “1” => 1
false: “1” =< 1
copy_to
该参数允许将多个字段的值复制到组字段中,然后可以将其作为单个字段进行查询
doc_values ★
为了提升排序和聚合效率,默认true,如果确定不需要对字段进行排序或聚合,也不需要通过脚本访问字段值,则可以禁用doc值以节省磁盘空间(不支持 text 和 annotated_text)
dynamic ★
控制是否可以动态添加新字段,支持以下四个选项:true:(默认)允许动态映射false:忽略新字段。这些字段不会被索引或搜索,但仍会出现在_source返回的命中字段中。这些字段不会添加到映射中,必须显式添加新字段。runtime:新字段作为运行时字段添加到索引中,这些字段没有索引,是_source在查询时加载的。strict:如果检测到新字段,则会抛出异常并拒绝文档。必须将新字段显式添加到映射中。
eager_global_ordinals
用于聚合的字段上,优化聚合性能。
enabled
**是否创建倒排索引,可以对字段操作,也可以对索引操作,如果不创建索引,让然可以检索并在_source元数据中展示,谨慎使用,该状态无法修改。
fielddata ★
查询时内存数据结构,在首次用当前字段聚合、排序或者在脚本中使用时,需要字段为fielddata数据结构,并且创建倒排索引保存到堆中
fields ★
给 field 创建多字段,用于不同目的(全文检索或者聚合分析排序)
format ★
用于格式化代码
ignore_above ★
超过长度将被忽略
ignore_malformed
忽略类型错误
index_options
控制将哪些信息添加到反向索引中以进行搜索和突出显示。仅用于text 字段
index_phrases
提升exact_value查询速度,但是要消耗更多磁盘空间
index_prefixes
前缀搜索:min_chars:前缀最小长度,>0,默认2(包含)max_chars:前缀最大长度,<20,默认5(包含)
index ★
是否对创建对当前字段创建倒排索引,默认 true,如果不创建索引,该字段不会通过索引被搜索到,但是仍然会在 source 元数据中展示true 新检测到的字段将添加到映射中。(默认)false 新检测到的字段将被忽略。这些字段将不会被索引,因此将无法搜索,但仍会出现在_source返回的匹配项中。这些字段不会添加到映射中,必须显式添加新字段。strict 如果检测到新字段,则会引发异常并拒绝文档。必须将新字段显式添加到映射中
meta
附加到元字段
normalizer
文档归一化器
norms ★
是否禁用评分(在filter和聚合字段上应该禁用)。
null_value ★
为 null 值设置默认值
position_increment_gap
用于数组中相邻搜索中的搜索间隙,slop 默认 100
properties ★
除了mapping还可用于object的属性设置
search_analyzer ★
设置单独的查询时分析器
similarity
为字段设置相关度算法,支持:BM25boolean注意:classic(TF-IDF)在 ES 8.x 中已不再支持!
subobjects
ES 8 新增,subobjects 设置为 false 的字段的值,其子字段的值不被扩展为对象。
store
设置字段是否仅查询
term_vector
运维参数,在运维篇会详细讲解。
Text 类型 ★★
当一个字段是要被全文搜索的,比如邮件内容、产品描述等长文本,这些字段应该使用 text 类型。设置 text 类型以后,字段内容会被分词,在生成倒排索引以前,字符串会被分析器分成一个一个词项。text 类型的字段不用于排序,很少用于聚合。
总的来说,text 用于长文本字段,可以说是 ES 体系中最重要也是最常见的数据类型。
总结:
应用的业务场景:全文检索。
Text 类型基本声明。
Text 类型会被分词。
ES 默认情况下会为 Text 类型创建倒排索引。
keyword 类型 ★
keyword使用序号映射存储它们的文档值以获得更紧凑的表示。 此映射的工作原理是根据其词典顺序为每个术语分配一个增量整数或*序数。*该字段的文档值仅存储每个文档的序数而不是原始术语,并使用单独的查找结构在序数和术语之间进行转换。
一般用于精确匹配和聚合字段,例如 ID、电子邮件地址、主机名、状态代码、邮政编码或标签。包括范围查找。和 term 查询一起使用频率是最高的。
总结:
keyword 类型字段不会被分词。
keyword 一般用于精确查找或者聚合字段
keyword 类型超过阈值长度会直接被丢弃
Date 类型 ★
在 Elasticsearch 中,时间类型是一个非常容易踩坑的数据类型
总结:
对于yyyy-MM-dd HH:mm:ss或2021-11-1T12:20:00Z,ES 的自动映射器完全无法识别,即便是事先声明日期类型,数据强行写入也会失败。
对于时间戳和yyyy-MM-dd这样的时间格式,ES 自动映射器无法识别,但是如果事先说明了日期类型是可以正常写入的。
对于标准的日期时间类型是可以正常自动识别为日期类型,并且也可以通过手工映射来实现声明字段类型。
Nested 类型 ★
自动映射模版:Dynamic Templates
介绍:
在定义字段映射的时候,往往字段不一定有具体的名称。有时希望对一类相同或者相似特征的字段定义相同的映射,此时可以考虑使用 Dynamic template。
定义映射模板
"dynamic_templates": [
{
"my_template_name": {
... match conditions ...
"mapping": { ... }
}
},
...
]
{
"my_template_name": {
... match conditions ...
"mapping": { ... }
}
},
...
]
规则判定:conditions
match_mapping_type用于匹配数据类型
match、unmatch用以匹配字段名称规则,支持通配符、正则表达式。
path_match、path_unmatch用于嵌套字段
模板变量 ☆
分词器Text Analysis
分词器入门
分词器基本概念
分词器官方称之为文本分析器,顾名思义,是对文本进行分析处理的一种手段,基本处理逻辑为按照预先制定的分词规则,把原始文档分割成若干更小粒度的词项,粒度大小取决于分词器规则。
分词发生时期
Index Time:文档写入并创建倒排索引时期,其分词逻辑取决于映射参数analyzer
Search Time:搜索发生时期,其分词仅对搜索词产生作用
分词器的组成
切词器(Tokenizer):用于定义切词(分词)逻辑
词项过滤器(Token Filter):用于对分词之后的单个词项的处理逻辑
字符过滤器(Character Filter):用于处理单个字符
文档归一化处理:Normalization
Processors
大小写统一
时态转换
停用词:如一些语气词、介词等在大多数场景下均无搜索意义
意义
增加召回率
减小匹配次数,进而提高查询性能
_analyzer API
_analyzer API可以用来查看指定分词器的分词结果。
GET _analyze
{
"text": ["What are you doing!"],
"analyzer": "english"
}
{
"text": ["What are you doing!"],
"analyzer": "english"
}
切词器:Tokenizer
tokenizer 是分词器的核心组成部分之一,其主要作用是分词,或称之为切词。主要用来对原始文本进行细粒度拆分。拆分之后的每一个部分称之为一个 Term,或称之为一个词项。
可以把切词器理解为预定义的切词规则。
官方内置了很多种切词器,默认的切词器位 standard。
可以把切词器理解为预定义的切词规则。
官方内置了很多种切词器,默认的切词器位 standard。
词项过滤器:Token Filter
简介
词项过滤器用来处理切词完成之后的词项,例如把大小写转换,删除停用词或同义词处理等。
官方同样预置了很多词项过滤器,基本可以满足日常开发的需要。当然也是支持第三方也自行开发的。
官方同样预置了很多词项过滤器,基本可以满足日常开发的需要。当然也是支持第三方也自行开发的。
案例
Lowercase 和 Uppercase
GET _analyze
{
"filter" : ["lowercase"],
"text" : "WWW ELASTIC ORG CN"
}
GET _analyze
{
"tokenizer" : "standard",
"filter" : ["uppercase"],
"text" : ["www.elastic.org.cn","www elastic org cn"]
}
{
"filter" : ["lowercase"],
"text" : "WWW ELASTIC ORG CN"
}
GET _analyze
{
"tokenizer" : "standard",
"filter" : ["uppercase"],
"text" : ["www.elastic.org.cn","www elastic org cn"]
}
停用词
GET _analyze
{
"tokenizer": "standard",
"filter": ["stop"],
"text": ["What are you doing"]
}
### 自定义 filter
DELETE test_token_filter_stop
PUT test_token_filter_stop
{
"settings": {
"analysis": {
"filter": {
"my_filter": {
"type": "stop",
"stopwords": [
"www"
],
"ignore_case": true
}
}
}
}
}
GET test_token_filter_stop/_analyze
{
"tokenizer": "standard",
"filter": ["my_filter"],
"text": ["What www WWW are you doing"]
}
{
"tokenizer": "standard",
"filter": ["stop"],
"text": ["What are you doing"]
}
### 自定义 filter
DELETE test_token_filter_stop
PUT test_token_filter_stop
{
"settings": {
"analysis": {
"filter": {
"my_filter": {
"type": "stop",
"stopwords": [
"www"
],
"ignore_case": true
}
}
}
}
}
GET test_token_filter_stop/_analyze
{
"tokenizer": "standard",
"filter": ["my_filter"],
"text": ["What www WWW are you doing"]
}
同义词
PUT test_token_filter_synonym
{
"settings": {
"analysis": {
"filter": {
"my_synonym": {
"type": "synonym",
"synonyms": [ "good, nice => excellent" ] //good, nice, excellent
}
}
}
}
}
GET test_token_filter_synonym/_analyze
{
"tokenizer": "standard",
"filter": ["my_synonym"],
"text": ["good"]
}
DELETE test_token_filter_synonym
PUT test_token_filter_synonym
{
"settings": {
"analysis": {
"filter": {
"my_synonym": {
"type": "synonym",
"synonyms_path": "analysis/synonym.txt"
}
}
}
}
}
GET test_token_filter_synonym/_analyze
{
"tokenizer": "standard",
"text": ["a"], // a b c d s; q w e r ss
"filter": ["my_synonym"]
}
{
"settings": {
"analysis": {
"filter": {
"my_synonym": {
"type": "synonym",
"synonyms": [ "good, nice => excellent" ] //good, nice, excellent
}
}
}
}
}
GET test_token_filter_synonym/_analyze
{
"tokenizer": "standard",
"filter": ["my_synonym"],
"text": ["good"]
}
DELETE test_token_filter_synonym
PUT test_token_filter_synonym
{
"settings": {
"analysis": {
"filter": {
"my_synonym": {
"type": "synonym",
"synonyms_path": "analysis/synonym.txt"
}
}
}
}
}
GET test_token_filter_synonym/_analyze
{
"tokenizer": "standard",
"text": ["a"], // a b c d s; q w e r ss
"filter": ["my_synonym"]
}
字符过滤器:Character Filter
基本概念
分词之前的预处理,过滤无用字符
基本用法
语法
PUT <index_name>
{
"settings": {
"analysis": {
"char_filter": {
"my_char_filter": {
"type": "<char_filter_type>"
}
}
}
}
}
{
"settings": {
"analysis": {
"char_filter": {
"my_char_filter": {
"type": "<char_filter_type>"
}
}
}
}
}
参数
type:使用的字符过滤器类型名称,可配置以下值
html_strip
mapping
pattern_replace
官方支持的三种 Char Filter
HTML 标签过滤器:HTML Strip Character Filter
字符过滤器会去除 HTML 标签和转义 HTML 元素,如 、&
字符映射过滤器:Mapping Character Filter
通过定义映替换为规则,把特定字符替换为指定字符
正则替换过滤器:Pattern Replace Character Filter
内置分词器
Standard ★:默认分词器,中文支持的不理想,会逐字拆分。参数值为:standard
Pattern:以正则匹配分隔符,把文本拆分成若干词项。参数值为:pattern
Simple:除了英文单词和字母,其他统统过滤掉,参数值为:simple
Whitespace ★:以空白符分隔,不会改变大小写,参数值为:whitespace
Keyword ★:可以理解为不做任何操作的分词器,会保留原有文本的所有属性,参数值为:keyword
Stop:分词规则和 Simple Analyzer 相同,但是增加了对停用词的支持。参数值为:stop
Language Analyzer:支持全球三十多种主流语言。
Fingerprint:一种特殊领域分词器,不常用
自定义分词器:Custom Analyzer
如果 ES 内置分词器无法满足需要,可以通过对切词器、词项过滤器、字符过滤器三个组件的自由组合来自定义分词器。在使用分词器时候需要注意必须满足以下要求
Tokenizer:必须包含一个并且只能指定一个切词器,即必须指定分词器的切词规则
Token Filter:可以不指定词项过滤器,也可以指定多个词项过滤器
Char Filter:可以不指定字符过滤器,也可以指定多个字符过滤器
type 参数
analyzer 和 search_analyzer
analyzer:为字段指定的分词器,仅对文本字段生效,针对的是源数据字段,也就是source data
search_analyzer:搜索时分词器,即作用于搜索词的分词器,作用对象为,用户传入的搜索词。
当 search_analyzer 未指定时,其缺省值为 analyzer,若 analyzer 未指定,search_analyzer 和 analyzer 的值都为standard
文档归一化器:Normalizers
概念
normalizer 与 analyzer 的作用类似,都是对字段进行处理,但是不同之处在于normalizer不会对字段进行分词,也就是说 normalizer 没有 tokenizer。所以 normalizer 是作用于 keyword 类型的字段的,相当于我们需要给 keyword 类型字段做一个额外的处理时,比如转换为小写时就可以用到 normalizer
注意事项
normalizer 只能用于 keyword 类型
normalizer 设置的字段不能被分词
中文分词器
ik分词器
下载
IK下载地址:https://github.com/medcl/elasticsearch-analysis-ik
安装
创建插件文件夹:cd {es-root-path}/plugins/ && mkdir ik
将插件解压缩到文件夹:{es-root-path}/plugins/ik
重新启动 ES 服务
将插件解压缩到文件夹:{es-root-path}/plugins/ik
重新启动 ES 服务
基本使用
词库文件描述
ik 提供的两种 analyzer
ik_max_word:会将文本做最细粒度的拆分,比如会将“中华人民共和国国歌”拆分为“中华人民共和国,中华人民,中华,华人,人民共和国,人民,人,民,共和国,共和,和,国国,国歌”,会穷尽各种可能的组合,适合 Term Query;
ik_smart:会做最粗粒度的拆分,比如会将“中华人民共和国国歌”拆分为“中华人民共和国,国歌”,适合 Phrase 查询。
基于本地词库扩展
基于远程词库热更新
基于 MySQL 的词库热更新
搜索:Query DSL
查询和检索
查询:有明确的搜索条件边界。比如,年龄 15~25 岁,颜色 = 红色,价格 < 3000,这里的 15、25、红色、3000 都是条件边界。即有明确的范围界定。
检索:即全文检索,无搜索条件边界,召回结果取决于相关性,其相关性计算无明确边界性条件,如同义词、谐音、别名、错别字、混淆词、网络热梗等均可成为其相关性判断依据。
上下文对象
使用 query 关键字进行检索,倾向于相关度搜索,故需要计算评分。搜索是 Elasticsearch 最关键和重要的部分。
相关度评分:_score
使用 query 关键字进行检索,倾向于相关度搜索,故需要计算评分。相关度评分为搜索结果的排序依据,默认情况下评分越高,则结果越靠前。
如果没有指定排序字段,则默认按照评分高低排序,相关度评分为搜索结果的排序依据,默认情况下评分越高,则结果越靠前。
如果没有指定排序字段,则默认按照评分高低排序,相关度评分为搜索结果的排序依据,默认情况下评分越高,则结果越靠前。
源数据:_source
数据源过滤器
Including:结果中返回哪些 field
Excluding:结果中不要返回哪些 field,不返回的field不代表不能通过该字段进行检索,因为元数据不存在不代表索引不存在
在 mapping 中定义过滤
支持通配符,但是这种方式不推荐,因为 mapping 不可变
'PUT product
{
"mappings": {
"_source": {
"includes": [
"name",
"price"
],
"excludes": [
"desc",
"tags"
]
}
}
}
{
"mappings": {
"_source": {
"includes": [
"name",
"price"
],
"excludes": [
"desc",
"tags"
]
}
}
}
在查询中过滤
禁用 _source
优点:节省存储开销
缺点:
不支持 update、update_by_query 和 reindex API。
不支持高亮。
不支持 reindex、更改 mapping 分析器和版本升级。
通过查看索引时使用的原始文档来调试查询或聚合的功能。
将来有可能自动修复索引损坏。
不支持 update、update_by_query 和 reindex API。
不支持高亮。
不支持 reindex、更改 mapping 分析器和版本升级。
通过查看索引时使用的原始文档来调试查询或聚合的功能。
将来有可能自动修复索引损坏。
总结:如果只是为了节省磁盘,可以压缩索引比禁用 _source 更好。
分页和排序
默认情况下,搜索返回前 10 个匹配命中。
分页
from:从低几个文档开始返回,需要为非负数,默认为 :0
size:定义要返回的命中数,默认值为:10
GET <index>/_search
{
"from": 0,
"size": 20
}
{
"from": 0,
"size": 20
}
注意:
from + size 必须小于等于 10000,其原因涉及深度分页问题、
max_result_window:可以解除 from + size 必须小于 10000 的限制,但是如果不清楚其原理,而盲目修改阈值,可能会造成严重后果。
track_total_hits:允许 hits.total 返回实际数值,但是会牺牲性能
排序
GET <index>/_search
{
"sort": [
{
"<sort_field>": {
"order": "desc" // or asc
}
}
]
}
{
"sort": [
{
"<sort_field>": {
"order": "desc" // or asc
}
}
]
}
Url Query
全文检索:Full Text Query ★
全文检索知识概览
字段类型:必须是可分词的字段类型
分词逻辑:不同的分词规则,对召回结果影响很大
全文检索:即 FullText Query
倒排索引:全文检索所依赖的底层数据结构
评分算法:TF-IDF、Okapi BM25
何为全文检索
全文检索是一种搜索技术,用于在大量文本数据中查找包含用户输入的关键字的文档或记录。它可以应用于许多不同的领域,如搜索引擎、电子邮件、社交媒体、知识库、文档管理系统、电子商务等垂直领域。
全文检索的核心是建立一个倒排索引(Inverted Index),即将每个词与包含这个词的文档列表进行关联。当用户输入关键词时,系统会根据这些关键词在倒排索引中查找相应的文档列表,并将匹配度高的文档排在前面返回给用户。
在全文检索中,文本数据通常被存储在数据库或文件系统中。在搜索之前,先将文本数据分词(Tokenization),将文本分解成一系列单词(词项),并将它们存储在倒排索引中。倒排索引是一种数据结构,用于存储每个单词所在的文档列表。它使得搜索引擎可以快速地找到包含用户输入的关键字的文档。
当用户输入一个或多个关键字进行搜索时,搜索引擎将查询倒排索引,找到包含这些关键字的文档,并将它们按照相关度排序后返回给用户。搜索引擎通常使用一些算法和技术来计算文档与关键字之间的相关度,例如TF-IDF(词频-逆文档频率)算法、BM25(Okapi Best Matching 25)算法等。
全文检索的核心是建立一个倒排索引(Inverted Index),即将每个词与包含这个词的文档列表进行关联。当用户输入关键词时,系统会根据这些关键词在倒排索引中查找相应的文档列表,并将匹配度高的文档排在前面返回给用户。
在全文检索中,文本数据通常被存储在数据库或文件系统中。在搜索之前,先将文本数据分词(Tokenization),将文本分解成一系列单词(词项),并将它们存储在倒排索引中。倒排索引是一种数据结构,用于存储每个单词所在的文档列表。它使得搜索引擎可以快速地找到包含用户输入的关键字的文档。
当用户输入一个或多个关键字进行搜索时,搜索引擎将查询倒排索引,找到包含这些关键字的文档,并将它们按照相关度排序后返回给用户。搜索引擎通常使用一些算法和技术来计算文档与关键字之间的相关度,例如TF-IDF(词频-逆文档频率)算法、BM25(Okapi Best Matching 25)算法等。
优缺点
全文检索有许多优点,例如:
可以非常快速地查找文本数据;
可以处理大量的文本数据;
可以处理复杂的查询,例如布尔查询、短语查询、模糊查询等;
可以根据相关度对文档进行排序,提高搜索结果的质量。
但是,全文检索也存在一些挑战和限制,例如:
分词过程可能会存在歧义,例如“香蕉奶油”可能会被分解成“香蕉”和“奶油”,也可能被分解成“香蕉奶”和“油”;
倒排索引可能会占用大量的存储空间;
搜索结果可能会受到数据质量和相关度计算算法的影响;
全文检索不适直接用于非文本数据,例如图像、音频、视频等。
可以非常快速地查找文本数据;
可以处理大量的文本数据;
可以处理复杂的查询,例如布尔查询、短语查询、模糊查询等;
可以根据相关度对文档进行排序,提高搜索结果的质量。
但是,全文检索也存在一些挑战和限制,例如:
分词过程可能会存在歧义,例如“香蕉奶油”可能会被分解成“香蕉”和“奶油”,也可能被分解成“香蕉奶”和“油”;
倒排索引可能会占用大量的存储空间;
搜索结果可能会受到数据质量和相关度计算算法的影响;
全文检索不适直接用于非文本数据,例如图像、音频、视频等。
Match
语法
GET <index>/_search
{
"query": {
"match": {
"<field_name>": "<field_value>"
}
}
}
{
"query": {
"match": {
"<field_name>": "<field_value>"
}
}
}
语义
匹配包含某个词项的文档。如果搜索词包含多个词项,那么文档只要匹配其中任意一个词项,就会被召回。
Match All
match_all:匹配所有结果的子句
GET <index>/_search
{
"query": {
"match_all": {}
}
}
GET <index>/_search
{
"query": {
"match_all": {}
}
}
Match Phrase
短语搜索概念
Match_phrase 查询是一种短语查询,它用于匹配包含指定短语的文档。与 Match 查询不同,Match_phrase 查询只匹配包含短语的文档,而不会匹配单个词条。
匹配规则
会分词:match_phrase 首先会将查询短语拆分为单个词项。例如,如果查询短语是 “elastic org cn”,则将其拆分为三个词项:。
必须全部匹配且顺序必须相同:被检索字段必须包含 match_phrase 短语中的所有词项,并且顺序必须是相同的。比如查询短语是elastic org cn,那么只有字段中的词项必须包含"elastic"、"org"和"cn"这三个短语,切顺序不能颠倒。
Slop距离:默认情况下被检索字段包含的 match_phrase 中的词项之间不能有其他词项,即 slop 默认为 0,如果人为将 slop 设置为其他数值,则多个词项将允许有 slop 规定的距离
语法
GET <index>/_search
{
"query": {
"match_phrase": {
"<field_name>": "<field_value>"
}
}
}
{
"query": {
"match_phrase": {
"<field_name>": "<field_value>"
}
}
}
Slop 参数
slop 参数用于指定允许查询短语中各个词项之间的最大间隔数。它可以用来放宽查询短语中词项的严格顺序要求,从而使查询结果能够匹配更多的文档。
精准查询:Term-Level Query
概念
官方把我们本小节所述的知识点,也就是精准查询称之为术语级查询,术语指的其实就是词项,简单来说,术语就是在全文检索中的最小粒度文本,而术语级查询,其实指的就是不可分词的查询类型。即有明确查询条件边界的查询。如:范围查询、词项查询等。
Term Query
基本逻辑
和 match query 相比,term query 不会对搜索词分词,而且会保留搜索词原有的所有属性,如大小写、标点符号等。
数据类型
通常来说,term query 和 keyword 类型一起使用,其他不分词字段亦可。
子主题
语法
GET <index>/_search
{
"query": {
"term": {
"<keyword_field_name>": "<field_value>"
}
}
}
{
"query": {
"term": {
"<keyword_field_name>": "<field_value>"
}
}
}
term 与 keyword
子主题
特殊情况
Terms Query
语法语义
Terms 和 Term 的区别就在于 Terms 是同时匹配多个词项。
GET /_search
{
"query": {
"terms": {
"<field_name>": [ "<value1>", "<value2>" ]
}
}
}
{
"query": {
"terms": {
"<field_name>": [ "<value1>", "<value2>" ]
}
}
}
布尔查询:Boolean Query
概念
bool:可以组合多个查询条件,bool 查询也是采用 more_matches_is_better 的机制,因此满足 must 和 should 子句的文档将会合并起来计算分值
bool query 多用于多条件组合查询
bool query 多用于多条件组合查询
基本语法
GET _search
{
"query": {
"bool": {
"filter": [ // 必须符合每条件
{条件一},
{条件二}
],
"must": [ // 必须符合每条件
{条件一},
{条件二}
],
"must_not": [ // 必须不满足每个条件
{条件一},
{条件二}
],
"should": [ // 可以满足其中若干条件或全部不满足
{条件一},
{条件二}
]
}
}
}
{
"query": {
"bool": {
"filter": [ // 必须符合每条件
{条件一},
{条件二}
],
"must": [ // 必须符合每条件
{条件一},
{条件二}
],
"must_not": [ // 必须不满足每个条件
{条件一},
{条件二}
],
"should": [ // 可以满足其中若干条件或全部不满足
{条件一},
{条件二}
]
}
}
}
查询子句
Must 子句
计算相关度得分
多个条件必须同时满足
多个条件必须同时满足
Filter 子句
过滤器 cache☆ 子句(查询)必须出现在匹配的文档中。但是不像 must 会计算评分, 使用 filter 查询的评分将被忽略,并且结果会被缓存。
注:
不计算相关度分数
会被缓存
注:
不计算相关度分数
会被缓存
Should 子句
可能满足 or 子句(查询)应出现在匹配的文档中。
Must_not 子句
子句中出现的每个条件都不能满足,并且不计算相关度评分
组合查询
语义关系
当多个子句同时出现时,多个子句之间的逻辑关系为 AND,即需要同时满足。如,当同时出现 must [case1, case2] 和 must_not [case3, case4] 时,其语义为:必须同时满足 case1 和 case2,且必须同时不满足 case3、case4
minimum_should_match 参数
子查询嵌套
聚合搜索(Aggregations)
基本概念
在 Elasticsearch 中,聚合查询是一种分析和统计数据的功能。聚合查询能够处理大量的数据,执行各种统计分析,如计算总数、平均值、最大值、最小值、标准差等等,并生成相应的报告。
聚合(aggs)不同于普通查询,是目前学到的第二种大的查询分类,第一种即 query,因此在代码中的第一层嵌套由 query 变为了 aggs 。
聚合(aggs)不同于普通查询,是目前学到的第二种大的查询分类,第一种即 query,因此在代码中的第一层嵌套由 query 变为了 aggs 。
使用场景
聚合查询可以用于各种场景,比如商业智能、数据挖掘、日志分析等等。
电商平台的销售分析:统计每个地区的销售额、每个用户的消费总额、每个产品的销售量等,以便更好地了解销售情况和趋势。
社交媒体的用户行为分析:统计每个用户的发布次数、转发次数、评论次数等,以便更好地了解用户行为和趋势,同时可以将数据按照地区、时间、话题等维度进行分析。
物流企业的运输分析:统计每个区域的运输量、每个车辆的运输次数、每个司机的行驶里程等,以便更好地了解运输情况和优化运输效率。
金融企业的交易分析:统计每个客户的交易总额、每个产品的销售量、每个交易员的业绩等,以便更好地了解交易情况和优化业务流程。
智能家居的设备监控分析:统计每个设备的使用次数、每个家庭的能源消耗量、每个时间段的设备使用率等,以便更好地了解用户需求和优化设备效能。
电商平台的销售分析:统计每个地区的销售额、每个用户的消费总额、每个产品的销售量等,以便更好地了解销售情况和趋势。
社交媒体的用户行为分析:统计每个用户的发布次数、转发次数、评论次数等,以便更好地了解用户行为和趋势,同时可以将数据按照地区、时间、话题等维度进行分析。
物流企业的运输分析:统计每个区域的运输量、每个车辆的运输次数、每个司机的行驶里程等,以便更好地了解运输情况和优化运输效率。
金融企业的交易分析:统计每个客户的交易总额、每个产品的销售量、每个交易员的业绩等,以便更好地了解交易情况和优化业务流程。
智能家居的设备监控分析:统计每个设备的使用次数、每个家庭的能源消耗量、每个时间段的设备使用率等,以便更好地了解用户需求和优化设备效能。
基本语法
查询条件:指定需要聚合的文档,可以使用标准的 Elasticsearch 查询语法,如 term、match、range 等等。
聚合函数:指定要执行的聚合操作,如 sum、avg、min、max、terms、date_histogram 等等。每个聚合命令都会生成一个聚合结果。
聚合嵌套:聚合命令可以嵌套,以便更细粒度地分析数据。
GET <index_name>/_search
{
"aggs": {
"<aggs_name>": { // 聚合名称需要自己定义
"<agg_type>": {
"field": "<field_name>"
}
}
}
}
aggs_name:聚合函数的名称
agg_type:聚合种类,比如是桶聚合(terms)或者是指标聚合(avg、sum、min、max等)
field_name:字段名称或者叫域名。
聚合函数:指定要执行的聚合操作,如 sum、avg、min、max、terms、date_histogram 等等。每个聚合命令都会生成一个聚合结果。
聚合嵌套:聚合命令可以嵌套,以便更细粒度地分析数据。
GET <index_name>/_search
{
"aggs": {
"<aggs_name>": { // 聚合名称需要自己定义
"<agg_type>": {
"field": "<field_name>"
}
}
}
}
aggs_name:聚合函数的名称
agg_type:聚合种类,比如是桶聚合(terms)或者是指标聚合(avg、sum、min、max等)
field_name:字段名称或者叫域名。
三种类型的聚合
桶聚合:Bucket Aggregations
概念
类比 SQL 中的 group by 的作用,主要用于统计不同类型数据的数量。
在 Elasticsearch 中,桶聚合是一种常用的聚合查询操作,它将文档分为多个“桶”,然后在每个桶上进行统计分析。
在 Elasticsearch 中,桶聚合是一种常用的聚合查询操作,它将文档分为多个“桶”,然后在每个桶上进行统计分析。
场景
指标聚合:Metrics Aggregations
概念
场景
用于统计某个指标,如最大值、最小值、平均值,可以结合桶聚合一起使用,如按照商品类型分桶,统计每个桶的平均价格。
指标函数
平均值:Avg
最大值:Max
最小值:Min
求和:Sum
详细信息:Stats
数量:Value count
最大值:Max
最小值:Min
求和:Sum
详细信息:Stats
数量:Value count
管道聚合:Pipeline Aggregations
概念
管道聚合用于对聚合的结果进行二次聚合,如要统计绑定数量最多的标签 bucket,就是要先按照标签进行分桶,再在分桶的结果上计算最大值。
场景
用于对聚合查询的二次聚合,如统计平均成绩最高的学生,即先按照学生姓名(或 id)进行桶聚合,并计算其平均成绩,然后对其平均成绩计算最大值聚合
聚合数据类型
doc values
doc values 是正排索引的基本数据结构之一,其存在是为了提升排序和聚合效率,默认true,如果确定不需要对字段进行排序或聚合,也不需要通过脚本访问字段值,则可以禁用 doc values 值以节省磁盘空间。
fielddata
查询时内存数据结构,在首次用当前字段聚合、排序或者在脚本中使用时,需要字段为 fielddata 数据结构,并且创建倒排索引保存到堆中。与 doc value 不同,当没有doc value 的字段需要聚合时,需要打开 fielddata,然后临时在内存中建立正排索引,fielddata 的构建和管理发生在 JVM Heap 中。Fielddata 默认是不启用的,因为 text 字段比较长,一般只做关键字分词和搜索,很少拿它来进行全文匹配和聚合还有排序。
嵌套聚合
分页和排序
对聚合结果排序
多字段排序
多层聚合嵌套排序
按照内层聚合排序
过滤器
Filter
Filters
全局聚合过滤
Global
Post Filter 后置过滤
对聚合结果查询:Top Hits
对聚合结果排序:Bucket Sort
常见的查询函数
histogram
date-histogram
percentile
邻接矩阵:Adjacency matrix
集成
Spring Data集成(√)
①maven依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
②配置文件添加连接信息
spring:
elasticsearch:
uris: http://192.168.111.4:9200
elasticsearch:
uris: http://192.168.111.4:9200
③创建映射的实体类,实体类使用@Document注解标注,更细致的看API文档,提供了非常多实用的注解(默认情况下,这个注解会在索引未创建的情况下知道创建索引,也会知道映射字段的添加,但是它不能映射原有字段的更新、删除操作。)
④创建DAO类
extends ElasticsearchRepository<实体类, ID类型>
⑤业务分层,或者直接注入DAO类即可进行简单的CRUD
进阶:
高级查询
高级的查询用QueryBuilders工具类构建
索引维护
新增Field
默认情况下,直接在实体类里添加就可以了,会自动创建,前提是你没有取消自动创建
修改、删除Field
这种情况只能重新索引
①创建新的索引
②调用_reindx API将旧数据索引到新索引
③将应用程序指向新索引,或者创建索引别名(Alias),让应用程序继续使用旧的索引名
④删除旧索引
easy-es集成(√)
永远的神,推荐学习一下,非常牛掰!各种高级功能都帮你封装好了,向前面那个索引维护,Spring Data要直接手动处理或者手动封装,这里直接帮你封装好了,你甚至可以直接改实体类,不用关注实现,它帮你维护!在这里你可以无忧无虑的当一个SQL BOY~
spark stream集成
flink集成
索引
正排索引
document_id
=>当前文档包含的所有词项
=>当前文档包含的所有词项
在数据库管理系统(DBMS)中,主键索引就是一个正排索引的例子
正排索引的优势在于可以快速地查找某个文档里包含哪些词项。同理,正排不适用于查找包含某个词项的文档有哪些。
倒排索引
词项
=>包含当前词项的doc_id的列表
=>包含当前词项的doc_id的列表
倒排索引的优势是可以快速查找包含某个词项的文档有哪些。如果用倒排来确定哪些文档中是否包含某个词项就很鸡肋。
Linux
常用命令
文件与目录
文件管理
cat
chmod
chown
diff
find
git
less
more
mv
rm
cp
scp
awk
touch
文档编辑
sed
文件传输
ftp
网络
磁盘
磁盘管理
cd
du
mkdir
df
pwd
tree
mount
磁盘维护
网络通讯
系统管理
adduser
useradd
date
sleep
kill
login
ps
top
reboot
shutdown
sudo
uname
who
history
free
系统设置
chroot
clear
crontab
备份压缩
gzip
tar
unzip
zip
Shell编程
变量
参数
数组
echo
printf
test
流程控制
函数
输入输出重定向
Linux零拷贝
工具
yum
vi/vim
curl
Git
三个工作区域的概念
工作目录
工作目录是对项目的某个版本独立提取出来的内容
暂存区域
暂存区域是一个文件,保存了下次将提交的文件列表信息,一般在 Git 仓库目录中。
Git 仓库
Git 仓库目录是 Git 用来保存项目的元数据和对象数据库的地方
三种状态
已提交(committed)
表示数据已经安全的保存在本地数据库中
已修改(modified)
表示修改了文件,但还没保存到数据库中
已暂存(staged)
表示对一个已修改文件的当前版本做了标记,使之包含在下次提交的快照中
Git 工作流程
1.在工作目录中修改文件
2.暂存文件,将文件的快照放入暂存区域
3.提交更新,找到暂存区域的文件,将快照永久性存储到 Git 仓库目录
Git安装/配置
安装略
配置
检查配置信息
git config --list
全局选型
--global
获取帮助
Git使用
获取 Git 仓库
现有项目或目录下导入所有文件到 Git 中
git init
git add
git commit
从一个服务器克隆一个现有的 Git 仓库
git clone [url]
远程仓库的使用
查看远程仓库
git remote -v
添加远程仓库
git remote add <shortname> <url>
从远程仓库中抓取与拉取
git fetch [remote-name]
推送到远程仓库
git push origin master
查看远程仓库
git remote show [remote-name]
远程仓库的移除与重命名
git remote rename
git remote rm
标签
列出标签
git tag
创建标签
轻量标签
git tag
附注标签
git tag -a
检出标签
git checkout -b [branchname] [tagname]
分支
分支创建
git branch [name]
分支切换
git checkout [name]
删除分支
git branch -d [name]
分支的合并
git merge [需要合并的分支]
冲突解决
通过可视化工具比较好解决
推送分支
git push (remote) (branch)
GitLab(TODO)
Docker
Docker VS 虚拟机
Docker安装
百度清华镜像源
镜像源代理
去阿里云ACR看去
概念
镜像
容器
网络
卷
卷挂载
将服务器上的目录与容器内目录关联,会覆盖容器目录内容,启动时,服务器上的目录要准备好对应的文件
示例:-v /app/nghtml:/usr/share/nginx/html
卷映射
将容器内部的某个目录和某一个volume映射,会把容器里的文件内容同步出来
示例:-v ngconf:/etc/nginx
仓库
例:Docker Hub
常用命令
通用操作
docker df
docker help
docker login
docker info
docker history
docker logout
容器
docker restart
docker start
docker logs
docker commit
正常情况下别这样玩,提交个黑盒子出来谁也不知道你做了什么,写Dockerfile build出来!
docker stats
docker ps
dockr rm
docker stop
docker exec
docker run
docker copy
docker inspect
镜像
docker build
docker push
docker images
docker pull
docker tag
docker rmi
docker search
网络
docker network
卷
docker volume
Dockerfile
网上找个模板照着改就完事了
常用指令
FROM
指定基础镜像,用于后续的指令构建。
LABEL
添加镜像的元数据,使用键值对的形式。
RUN
在构建过程中在镜像中执行命令。
CMD
指定容器创建时的默认命令。(可以被覆盖)
ENTRYPOINT
设置容器创建时的主要命令。(不可被覆盖)
EXPOSE
声明容器运行时监听的特定网络端口。
ENV
在容器内部设置环境变量。
ADD
将文件、目录或远程URL复制到镜像中。
COPY
将文件或目录复制到镜像中。
VOLUME
为容器创建挂载点或声明卷。
WORKDIR
设置后续指令的工作目录。
DockerCompose
这个基本上也是网上找个差不多的目标,根据需要改就行了
yaml文件指令参考
version
指定本 yml 依从的 compose 哪个版本制定的。
services
networks
volumes
常用命令
docker compose up -d -f xxx.yaml
docker compose scale
docker compose stop containid
docker compose start/restart containid
docker compose down
docker compose ps
docker compose build
docker compose top
docker compose logs
Docker SWARM
这个编排容器的我属实没用过,直接就跳到k8s了
网络模式
自定义
bridg
桥接默认
host
和宿主机的端口一一对应
none
不使用网络功能
container
指定新容器和某一个存在的容器共享网络
Docker实现原理
隔离文件:chroot
隔离访问:namespaces
隔离资源:cgroups
封装系统:LXC
Docker 在镜像的设计中,引入了层(layer)的概念。也就是说,用户制作镜像的每一步操作,都会生成一个层,也就是一个增量 rootfs。这叫叫作联合文件系统(Union File System)
一个“容器”,实际上是一个由 Linux Namespace、Linux Cgroups 和 rootfs 三种技术构建出来的进程的隔离环境。
Kubernetes
Kubernetes解决了什么?
为用户提供一个具有普遍意义的容器编排工具
核心组件
etcd
保存了整个集群的状态,理解成后端数据库得了
基于 RAFT 的一致性,节点数必须为奇数
apiserver
提供了资源操作的唯一入口,并提供认证、授权、访问控制、API 注册和发现等机制
controller manager
负责维护集群的状态,比如故障检测、自动扩展、滚动更新等
scheduler
负责资源的调度,按照预定的调度策略将 Pod 调度到相应的机器上
kubelet
负责维护容器的生命周期,同时也负责 Volume(CVI)和网络(CNI)的管理
Container runtime
负责镜像管理以及 Pod 和容器的真正运行(CRI)
kube-proxy
负责为 Service 提供 cluster 内部的服务发现和负载均衡
其他
kube-dns 负责为整个集群提供 DNS 服务
Ingress Controller 为服务提供外网入口
Heapster 提供资源监控
Dashboard 提供 GUI
Federation 提供跨可用区的集群
Fluentd-elasticsearch 提供集群日志采集、存储与查询
基本概念
Container
Pod
Pod 最重要的一个事实是:它只是一个逻辑概念。Pod,其实是一组共享了某些资源的容器。
共享Network Namespace
共享Volume
生命周期
Pending
Running
Succeeded
Failed
Unknown
Node
Namespace
Service
my-svc.my-namespace.svc.cluster.local-》serviceip-》podip
Service 是由 kube-proxy 组件,加上 iptables 来共同实现的
一直以来,基于 iptables 的 Service 实现,都是制约 Kubernetes 项目承载更多量级的 Pod 的主要障碍。
而 IPVS 模式的 Service,就是解决这个问题的一个行之有效的方法
而 IPVS 模式的 Service,就是解决这个问题的一个行之有效的方法
ClusterIP(默认)
集群的内部 IP 公开 Service,选择该值时 Service 只能够在集群内部访问
NodePort
通过每个节点上的 IP 和静态端口(NodePort)公开 Service。
LoadBalancer
使用云平台的负载均衡器向外部公开 Service。
ExternalName
将服务映射到 externalName 字段的内容(例如,映射到主机名 api.foo.bar.example)。 该映射将集群的 DNS 服务器配置为返回具有该外部主机名值的 CNAME 记录。 集群不会为之创建任何类型代理。
Headless
不会获得集群 IP,kube-proxy 不会处理这类 Service, 而且平台也不会为它们提供负载均衡或路由支持。
Label
Annotations
Deployment
Deployment 实际上是一个两层控制器。首先,它通过ReplicaSet 的个数来描述应用的版本;然后,它再通过ReplicaSet 的属性(比如 replicas 的值),来保证 Pod 的副本数量。
Deployment 控制 ReplicaSet(版本),ReplicaSet 控制 Pod(副本数)。这个两层控制关系一定要牢记。
Job
CronJob
DaemonSet
StatefulSet
ConfigMap
Ingress
YAML文件详解
容器网络
在默认情况下,被限制在 Network Namespace 里的容器进程,实际上是通过 Veth Pair 设备 + 宿主机网桥的方式,实现了跟同其他容器的数据交换。
容器“跨主通信”的问题
Flannel,VXLAN 模式
Calico
资源请求和限制
requests
保底分配的资源,调度是根据这个值
limits
最大资源使用上限
设计模式
408
计算机组成原理
计算机系统概论
运算方法和运算器
存储系统
指令系统
中央处理器
总线系统
外围设备
输入/输出设备
并行组织和结构
计算机网络
分类
范围
广域网WAN
城域网MAN
局域网LAN
个人区域网PAN
使用者
公用网
专用网
计算机网络性能指标
速率
吞吐量
带宽
时延
时延带宽积
往返时间RTT
利用率
分层
OSI七层协议
应用层
表示层
会话层
运输层
网络层
数据链路层
物理层
TCP/IP四层协议
应用层
运输层
网际层
网络接口层
五层协议
应用层
通过应用进程之间的交互来完成特定网络应用。应用层协议定义的是应用进程间通信和交互的规则。
举例协议
DNS
HTTP
HTTPS
SMTP
运输层
负责向两台主机中进程之间的通信提供通用的数据传输服务。
主要协议
TCP
面向连接的、可靠的数据传输服务
UDP
提供无连接的、尽最大努力的数据传输服务
网络层
负责为分组交换网上的不同主机提供通信服务
IP
数据链路层
数据链路层将网络层交下来的 IP 数据报组装成帧,在两个相邻节点间的链路上传送帧
物理层
尽可能的屏蔽掉各种计算机的传输媒体和通信手段的差异
自下而上
物理层
作用:屏蔽传输媒体和通信手段的差异
特性
机械特性
电气特性
功能特性
过程特性
信号分类
模拟信号
连续信号
数字信号
离散信号
信道
单向通信
单工通信
双向交替通信
半双工通信
双向同时通信
全双工通信
编码
不归零制
归零制
曼彻斯特编码
差分曼彻斯特编码
带通调制
调幅
调频
调相
传输媒体
双绞线
同轴电缆
光缆
子主题
数据链路层
点对点信道
数据单元:帧
封装成帧
透明传输
差错检测
循环冗余检验CRC
点对点协议PPP
广播信道
CSMA/CD协议
网络层
网络层只提供简单灵活的、无连接的、尽最大努力交付的数据报服务
网际协议IP
IP地址32位
格式
首部(固定20字节)
版本4位
首部长度4位
区分服务8位
总长度16位
标识16位
标志3位
片位移13位
生存时间8位
协议8位
首部校验和16位
源地址32位
目的地址32位
数据
三个阶段
分类IP地址
IP地址=网络号+主机号
A类
网络号8位主机号24位 0——
B类
网络号16位主机号16位 10——
C类
网络号24位主机号8位 110——
D类
1110——多播地址
E类
1111——保留使用
子网划分
IP地址=网络号+子网号+主机号,从网络的主机号借用若干位作为子网号
超网
地址解析协议ARP
根据IP地址解析出相应的硬件地址
过程
在主机ARP高速缓存中存放一个从IP地址到硬件地址的映射表,并且这个表经常动态更新
首先在ARP高速缓存中查找,如果查不到,就在局域网上广播发送一个ARP请求分组
本局域网上所有主机上运行的ARP进程都收到此分组请求
目标主机收下ARP请求分组,并向主机发送ARP响应分组
收到响应分组后就在ARP告诉缓存中写入更新
tip:ARP协议解决的是同一个局域网中的问题
网际控制报文协议ICMP
ICMP差错报告报文
ICMP询问报文
网际组管理协议IGMP
路由选择协议
内部网关协议IGP
外部网关协议EGP
内部网关协议RIP
内部网关协议OSPF
外部网关协议BGP
运输层
传输控制协议TCP
特点
面向连接
使用TCP之前必须建立连接,传输完之后必须释放连接
点对点交互通信
每条TCP连接只能一对一
提供可靠交付
无差错、不丢失、不重复且按序到达
全双工通信
双方应用程序在任何时候都能发送数据
面向字节流
TCP不知道所传送的字节流的含义
Socket是什么
TCP连接的端口,由IP地址:端口号定义
TCP报文段首部格式
源端口和目的端口
序号
表示本报文中所发送的数据的第一个字节的序号
确认号
期望收到对方下一个报文段的第一个数据字节的序号
若确认号=N,表面到序号N-1位置的所有数据都已正确收到
数据偏移
保留
紧急URG
确认ACK
当ACK=1时,确认号字段才有效,当ACK=0时,确认号无效。TCP规定,在建立连接后所有传送的报文段都必须把ACK置1
推送PSH
复位RST
同步SYN
在建立连接时用来同步序号。当SYN=1,ACK=0时,表面明是一个连接请求报文。若对方同意建立连接,则在应在响应报文段中使SYN=1和ACK=1,。因此,SYN=1就表示这是一个连接请求或连接接受报文。
终止FIN
FIN=1时,表明该报文段的发送方数据发送完毕,要求释放运输连接
窗口
检验和
紧急指针
选项
TCP如何实现可靠传输
以字节为单位的滑动窗口
超时重传机制
选择确认SACK
流量控制
拥塞控制
拥塞定义
对资源的需求超过了资源所能提供的可用部分
拥塞控制定义
防止过多的数据注入到网络中,防止路由或链路过载
PS:拥塞控制是全局性的过程,而流量控制属于端对端的
TCP拥塞控制方法
慢开始
由小到大逐渐增大拥塞窗口数值
总之就是一直乘以2
拥塞避免
到达慢开始门限就开始线性增长
每一个往返时间RTT,窗口+1
快重传
当发送方连续收到3个队同一报文段的确认ACK,说明发生了丢失问题。可以立刻进行重传,避免超时。
超时了门限会变成当前窗口的一半,窗口会重新从1开始
快恢复
慢开始门限变为当前窗口的一半,拥塞窗口变成当前门限,然后开始线性增加
TCP三次握手建立连接
1、客户端发送请求连接报文,其中首部同步位SYN=1,同时选择一个序号seq=x。TCP规定,SYN报文段=1的不能携带数据,但要消耗掉一个序号。此时客户端进行SYN-SENT状态
2、服务机接收到报文段,若同意连接,发送确认报文段。其中SYN、ACK都为1,并且确认号ack=x+1,同时也为自己选择一个属实序号seq=y。此时服务机进入SYN-RCVD状态
3、客户端接受到确认信号,向主机发送确认。其中ACK=1、确认号ack=y+1,而自己的序号seq=x+1。TCP规定,确认报文段可以携带数据。但如果不携带数据则不消耗序号,在这种情况下,下一个数据报文段的序号仍是seq=x+1。此时连接已经建立,客户端进入ESTABLISHEND状态,主机收到后也进入该状态。
问题:为什么要三次握手?两次可以吗?
防止已失效的连接请求报文段突然传送到服务机,产生错误,导致资源浪费。
举例
A发出请求报文没有丢失,但是滞留了,在连接释放后的某个时间间才到达B。B收到这个就认为A重新发起了连接请求,于是就发送给A发送确认,建立连接。假定没有第三次握手,在B发出这个确认报文后,连接就建立了,但是A实际并没有发起请求,所以不会理会B的确认,这就导致B的连接一直无意义的建立。
TCP四次挥手释放连接
1、进程A向进程B发送连接释放报文段,其中首部终止控制位FIN=1,序号seq=u,然后进入FIN-WAIT-1状态
2、进程B收到连接释放报文后发出确认报文,其中确认号ack=u+1,序号seq=v,然后B进入CLOSE-WAIT状态,A收到确认号进入FIN-WAIT-2状态,此时连接进入半关闭状态
3、若B没有要发送的数据时,B向A发出连接释放报文段,其中FIN=1,确认号ack=u+1,然后B进入LAST-ACK状态
4、A收到连接释放报文后,发出确认,其中ACK=1,然后进入TIME-WAIT状态,此时连接并没有释放掉,需要等待2MSL后才释放。
问题:TIME-WAIT在哪个阶段?实际为多长?为什么?
1、在第四次挥手
2、2MSL, 四分钟
3、保证最后一个ACK报文能够到达
4、防止已经失效的连接请求报文出现在本次连接中,导致资源浪费
用户数据报协议UDP
特点
无连接
发送数据之前不用建立连接,减少了开销和时延
尽最大努力交付
不保证可靠交付
面向报文
应用层交给UDP多长的报文,UDP就照发
无拥塞控制
支持一对一、一对多、多对多、多对一的交互通信
首部开销小
只有8个字节比TCP20个字节要短
首部格式(注意没个字段都是两个字节)
源端口
目的端口
长度
校验和
应用层
DNS
FTP
TELNET
WWW
URI
统一资源标识符
URI 包含 URL 和 URN,目前 WEB 只有 URL 比较流行,所以见到的基本都是 URL
URL
统一资源定位符
用于标志or定位在整个互联网范围内的某一资源
协议://主机:端口:/路径
HTTP
特点
无连接的
虽然建立了TCP连接,但是双方在交换HTTP报文之前不需要建立HTTP连接
无状态的
服务器并不记得曾经访问过的客户,也不记得为客户曾经服务过多少次。
HTTP1.0
缺点
每请求一个资源都要有两边RTT时间开销
短连接
每一次建立新的TCP连接都要分配缓存和变量
HTTP1.1
相较HTTP1.0
使用了持续连接(长连接),服务器在发送响应后扔在一段时间内保持这条连接
持续连接的工作方式
流水线式
客户在收到响应前就能够接着发送新的请求,效率高
非流水线式
客户在收到前一个响应后才能发出下一个请求,效率不好,浪费服务器资源
HTTP2.0
总之HTTP2.0肯定比之前的好
二进制分帧
首部压缩
多路复用
请求优先级
服务器推送
HTTPS
HTTP存在的安全问题
使用明文进行通信,内容可能会被窃听
不验证通信方的身份,通信方的身份有可能遭遇伪装
无法证明报文的完整性,报文有可能遭篡改
解决安全问题引入的HTTPS,HTTPS不是新协议,只是对HTTP进行加密
HTTPS=HTTP+加密+认证+完整性保护
加密方式
对称性加密
非对称性加密
PS:HTTPS使用混合加密的方式,使用非对称密钥加密用于传输对称密钥来保证传输过程的安全性,之后使用对称密钥加密进行通信来保证通信过程的效率
HTTPS连接过程
1、客户端发起HTTPS连接
2、服务器发送公钥和证书到客户端
3、客户端验证服务器发送的证书
验证证书
生成随机数
用公钥对随机数加密
4、服务器接受加密的随机数,用私钥进行解密,随后用这个随机数当做私钥对需要发送的数据进行对称加密
5、客户端在接受到加密后的数据使用私钥对数据进行解密并且解析数据呈现结果给用户
6、连接建立
HTTPS的缺点
要钱
加密解密需要时间开销
HTTP报文格式
请求报文
请求行
包含请求的方法、URL、版本
首部行
说明浏览器、服务器或报文主体的一些信息
请求体
请求的实体数据
响应报文
状态行
版本、状态码、短语
首部行
响应体
响应的实体数据
请求方法
get
put
post
delete
trace
connect
head
option
拓展:get和post的比较
作用
GET 用于获取资源,而 POST 用于传输实体主体
参数
GET 和 POST 的请求都能使用额外的参数,但是 GET 的参数是以查询字符串出现在 URL 中,而 POST 的参数存储在实体主体中。不能因为 POST 参数存储在实体主体中就认为它的安全性更高,因为照样可以通过一些抓包工具(Fiddler)查看
GET 和 POST 的请求都能使用额外的参数,但是 GET 的参数是以查询字符串出现在 URL 中,而 POST 的参数存储在实体主体中。不能因为 POST 参数存储在实体主体中就认为它的安全性更高,因为照样可以通过一些抓包工具(Fiddler)查看
因为 URL 只支持 ASCII 码,因此 GET 的参数中如果存在中文等字符就需要先进行编码
安全
GET 方法是安全的,而 POST 却不是,因为 POST 的目的是传送实体主体内容,这个内容可能是用户上传的表单数据,上传成功之后,服务器可能把这个数据存储到数据库中,因此状态也就发生了改变。
幂等性
幂等的 HTTP 方法,同样的请求被执行一次与连续执行多次的效果是一样的,服务器的状态也是一样的。换句话说就是,幂等方法不应该具有副作用(统计用途除外)
在正确实现的条件下,GET,HEAD,PUT 和 DELETE 等方法都是幂等的,而 POST 方法不是
缓存
请求报文的 HTTP 方法本身是可缓存的,包括 GET 和 HEAD,但是 PUT 和 DELETE 不可缓存,POST 在多数情况下不可缓存的
状态码
1XX
表示通知信息
2XX
表示成功
200 ok
204 请求已经成功处理,但是返回的响应报文不包含实体的主体部分
206 表示客户端进行了范围请求,响应报文包含由 Content-Range 指定范围的实体内容
3XX
表示重定向
301 永久重定向
302 临时重定向
303 和 302 有着相同的功能,但是 303 明确要求客户端应该采用 GET 方法获取资源
304 条件不满足
4XX
表示客户端错误
400 报文语法错误
401 认证失败
403 拒绝
404 懂的都懂
5XX
表示服务器错误
500 一般情况下 都是代码出现了问题
503 服务器暂时处于超负载或正在进行停机维护,现在无法处理请求
拓展:转发和重定向的区别
cookie
为什么要有cookie
HTTP 协议是无状态的,主要是为了让 HTTP 协议尽可能简单,使得它能够处理大量事务。HTTP/1.1 引入 Cookie 来保存状态信息
cookie是什么
Cookie 是服务器发送到用户浏览器并保存在本地的一小块数据,它会在浏览器之后向同一服务器再次发起请求时被携带上,用于告知服务端两个请求是否来自同一浏览器。由于之后每次请求都会需要携带 Cookie 数据,因此会带来额外的性能开销(尤其是在移动环境下)
用途
会话状态管理(如用户登录状态、购物车、游戏分数或其它需要记录的信息)
浏览器行为跟踪(如跟踪分析用户行为等)
个性化设置(如用户自定义设置、主题等)
分类
会话期cookie
浏览器关闭之后它会被自动删除,也就是说它仅在会话期内有效
持久型cookie
指定一个特定的过期时间(Expires)或有效期(max-age)之后就成为了持久性的 Cookie
session
除了可以将用户信息通过 Cookie 存储在用户浏览器中,也可以利用 Session 存储在服务器端,存储在服务器端的信息更加安全
cookie和session的比较和选择
比较
1、存储位置
cookie存储客户端,session存储在服务器
2、存储方式
cookie以文件的形式存储,session以类似HashMap的结构存储在服务器
3、存储容量
Cookie 只能存储 ASCII 码字符串,而 Session 则可以存取任何类型的数据
cookie存储上线为4kb,session没有限制
4、安全性
cookie不安全
session相对安全(对网络抓包可以抓到)
5、性能
cookie存储在客户端,不会影响服务器性能
session存储在服务器,会影响服务器性能
选择
因此在考虑数据复杂性时首选 Session
Cookie 存储在浏览器中,容易被恶意查看。如果非要将一些隐私数据存在 Cookie 中,可以将 Cookie 值进行加密,然后在服务器进行解密
其他问题:从网页上数据一个URL到产生渲染效果,其中发生了什么
1、DNS域名解析
2、请求和响应数据
建立TCP连接
发生HTTP请求
服务器处理请求
服务器返回HTTP响应
关闭TCP连接
3、浏览器加载渲染
解析
渲染
数据结构与算法(TODO)
操作系统
操作系统引论
操作系统的定义
操作系统是配置在计算机上的第一层软件,是对硬件系统的首次扩充
操作系统的目标和作用
目标
方便性、有效性、可扩充性、开放性
作用
1、作为用户与计算机硬件系统之间的接口,通信方式:
命令方式
系统调用方式
图标-窗口方式
2、计算机系统资源的管理者
3、实现了对计算机资源的抽象
操作系统的基本特性
并发
并行与并发
并行是指两个或多个事件在同一时刻发生。
并发是值两个或两个事件在同一时间间隔内发生
在多道程序环境下,并发是指在一段时间内宏观上有多个程序在同时运行,但在单机处理机系统中,每一时刻却仅能有一道程序执行,故微观上这些程序只能是分时地交替执行
共享
互斥共享方式
同时访问方式
虚拟
时分复用技术
空分复用技术
异步
操作系统的主要功能
处理机管理
进程控制
进程同步
进程通信
调度
存储器管理
内存分配
内存保护
地址映射
内存扩充
设备管理
缓冲管理
设备分配
设备处理
文件管理
文件存储空间管理
目录管理
读写管理和保护
操作系统与用户之间的接口
用户接口
程序接口
进程的描述和控制
进程的描述
程序并发执行的特征
间断性
失去封闭性
运行结果不可再现性
进程定义的引入
- 在多道程序环境下,程序的执行属于并发执行,此时它们将失去其封闭性,并具有间断性,以及其运行结果不可再现性的特征。由此决定的通常的程序是不能参与并发执行的,否则,程序的运行也就失去了意义。为了能使程序并发执行,并且可以对并发执行的程序加以描述和控制,引入了进程的概念。
进程的结构
程序段+相关数据段+PCB
PCB是进程存在系统中的唯一标志
进程的标准定义
进程是进程实体的运行过程,是系统进行资源分配和调度的一个独立单位
进程的特征
动态性
由创建而生,由调度而执行,有撤销而消亡
并发性
多个进程实体同时存在于内存中,且能在同一时间段内同时运行
独立性
进程实体是一个能独立运行、独立获得资源和独立接受调度的基本单位
异步性
进程的状态
创建
就绪
执行
阻塞
终止
进程的控制
进程控制一般都是由OS内核中的原语来实现的
原语:若干条指令组成,用于完成一定的功能的过程,它们是原子操作,要麽全部完成,要麽全部不完成
操作系统内核
状态划分
用户态
内核台
内核功能
支撑功能
中断处理
时钟管理
原语操作
资源管理
进程管理
存储器管理
设备管理
进程同步
进程同步的基本概念
对多个相关进程在执行次序上进行协调,使并发执行的程序之间按照一定的规则共享系统资源,使程序的执行具有可再现性
同步机制遵循的规则
空闲让进
临界区资源没被使用,可以进程使用
忙则等待
临界区资源被使用,其他进程必须等待
有限等待
保证等待的进程在有限的时间能获得资源的使用权,防止死等状态
让权等待
当进程获得不了资源使用权时,应该立即释放处理机,避免进程陷入忙等状态
硬件同步机制
关闭中断
Test and Set指令
Swap指令
信号量机制
整型信号量
P、V操作
记录型信号量
AND型信号量
信号量集
管程机制
为什么管程出现?
信号量机制需要自备同步操作,难以管理,容易引发死锁问题
管程的定义
一个管程定义了一个数据结构和能为并发进程锁执行的一组操作,这组操作能同步进程和改变管程中的数据
管程的组成
管程的名称
局部于管程的共享数据结构说明
对该数据结构进行操作的一组过程
对局部于管程的数据设置初始值的语句
进一步理解
就是一个数据结构,将表征共享资源的数据结构及对其数据结构操作的一组过程,,包括同步机制,都封装起来了
经典进程同步问题
生产者消费者问题
哲学家就餐问题
进程通信类型
共享存储器系统
共享数据结构
共享存储区
管道通信系统
消息传递系统
直接通信
间接通信
客户机-服务器系统
Socket
RPC
线程
线程的引入
如果说引入进程的目的是为了使程序能够并发执行,以提高资源利用率和系统吞吐量,那么,在操作系统中再引入线程,则是为了减少程序在并发执行时所付出的时空开销,使OS具有更好的并发性
线程的基本概念
比进程更小的基本单位,用以提高程序并发执行的程度,以进一步改善系统的服务质量
tip:一个误区:传统OS中,进程是作为独立调度和分派的基本单位,因而进程是能独立运行的基本单位。但是在引入线程的OS中,已把线程作为调度和分派的基本单位,因而线程是能独立运行的基本单位
进程和线程直接的比较角度
调度
引入线程的OS中,线程作为调度单位
资源
引入线程的OS中,进程作为资源分配单位
并发
线程之间并发
进程之间并发
独立性
系统开销
线程的状态
执行
就绪
阻塞
线程的数据
TCB
程序计数器
堆栈
寄存器
线程的实现方式
内核支持线程
用户级线程
组合方式
多对弈
一对多
多对多
线程之间的同步机制
互斥锁mutex
信号量
条件变量
线程之间的通信方式(参考juc)
共享内存
消息传递
由于多线程共享地址空间和数据空间,所以多个线程间的通信是一个线程的数据可以直接提供给其他线程使用,而不必通过操作系统(也就是内核的调度),线程间通信的目的主要是用于线程同步,所以线程没有像进程通信中的用于数据交换的通信机制
处理机调度与死锁
进程调度方式
抢占式
非抢占式
算法:
轮转调度算法
优先级调度算法
多队列调度算法
多级反馈队列调度算法
基于公平原则的调度算法
作业调度算法
先来先服务
短作业优先
优先级调度算法
高响应比优先调度算法
死锁
死锁的起因,通常是源于多个进程对资源的争夺,不仅对不可抢占资源进行争夺时会引起死锁,而且对可消耗资源进行争夺时,也会引起死锁
死锁的定义
如果一组进程中的每一个进程都在等待仅由该组进程中的其他进程才能引发的事件,那么该组进程是死锁的
死锁产生的条件
互斥
一段时间内,某资源只能被一个进程占用
请求和保持
获得资源的进程可以发起新的资源请求,若新的资源被占用,进程被阻塞,但是原本占有的资源不会释放
不可抢占
进程已经获得的资源在未使用完成之前不能被其他进程抢占,只能自己释放
循环等待
发生死锁时,必然有个进程——资源的循环链
处理死锁的方法
预防
破坏请求和保持
破坏不可抢占
破坏循环等待
避免
系统安全状态
银行家算法
检测
资源分配图
解除
抢占资源
终止进程
存储器管理
虚拟内存
输入输出系统
文件管理
0 条评论
下一页
为你推荐
查看更多