个人学习笔记
2022-12-08 22:39:25 52 举报
AI智能生成
登录查看完整内容
个人笔记
作者其他创作
大纲/内容
匹配出现在行首的abc
^abc
匹配出现在行尾的abc
abc$
以abc打头和结尾,即匹配值为abc的行
^abc$
匹配Abc或abc
[Aa]bc
举例
匹配字符串的开始位置。
如:[^a]bc,表示匹配bc,但不匹配abc
在[]内,表示非。
^
匹配字符串结束位置
$
匹配一个单词的边界,也就是指单词和空格间的位置。例如,“er\\b”可以匹配“never”中的“er”,但不能匹配“verb”中的“er”;
\\b
匹配非单词边界。“er\\B”能匹配“verb”中的“er”,但不能匹配“never”中的“er”。
\\B
匹配词(word)的开始(\\<)
\\<
匹配词(word)的结束(\\>)
\\>
字符位置匹配
*
+
?
n是一个非负整数。匹配确定的n次
{n}
n是一个非负整数。至少匹配n次
m和n均为非负整数,其中n<=m。最少匹配n次且最多匹配m次
表示前面表达式循环次数
字符集合。匹配所包含的任意一个字符。例如,“[abc]”可以匹配“plain”中的“a”
[xyz]
负值字符集合。匹配未包含的任意字符。例如 ,“[^abc]”可以匹配“place”中的“ple”任一字符。
[^xyz]
字符范围。匹配指定范围内的任意字符。例如,“[a-z]”可以匹配“a”到“z”范围内的任意小写字母字符。
[a-z]
负值字符范围。匹配任何不在指定范围内的任意字符。例如,“[^a-z]”可以匹配任何不在“a”到“z”范围内的任意
[^a-z]
匹配一个数字字符。等价于[0-9]
\\d
匹配一个非数字字符。等价于[^0-9]。
\\D
匹配包括下划线的任何单词字符。类似但不等价于“[A-Za-z0-9_]”,这里的\"单词\"字符使用Unicode字符集。
\\w
匹配任何非单词字符。等价于“[^A-Za-z0-9_]”。
\\W
正负元字符(上)
匹配任何不可见字符,包括空格、制表符、换页符等等。等价于[ \\f\\\\t\\v]。
\\s
匹配任何可见字符。等价于[^ \\f\\\\t\\v]。
\\S
匹配一个换页符。等价于\\x0c和\\cL。
\\f
匹配一个换行符。等价于\\x0a和\\cJ。
\
匹配一个回车符。等价于\\x0d和\\cM。
匹配一个制表符。等价于\\x09和\\cI。
\\t
匹配一个垂直制表符。等价于\\x0b和\\cK。
\\v
不可见字符
转义字符,例如:\\\ 匹配 \换行符
\\
匹配除“\”换行符和“\”回车符之外的任何单个字符
.
或,例如,“z|food”能匹配“z”或“food”。“[z|f]ood”则匹配“zood”或“food”。
|
将( 和 ) 之间的表达式定义为“组”(group),并且将匹配这个表达式的字符保存到一个临时区域(一个正则表达式中最多可以保存9个),它们可以用 \\1 到\\9 的符号来引用。
()
其他元字符
global (g),全局匹配
g
ignoreCase(i),不区分大小写的匹配
i
multiline(m),多(more)行匹配
m
允许.匹配换行符
s
Unicode(u),将模式视为Unicode代码点的序列,必须要在正则中包含unicode才能看到效果
u
修饰符
匹配n,其中n为十六进制转义值。十六进制转义值必须为确定的两个数字长。例如,“\\x41”匹配“A”。“\\x041”则等价于“\\x04&1”。正则表达式中可以使用ASCII编码。
\\xn
匹配num,其中num是一个正整数。对所获取的匹配的引用。例如,“(.)\\1”匹配两个连续的相同字符。
\um
标识一个八进制转义值或一个向后引用。如果\之前至少n个获取的子表达式,则n为向后引用。否则,如果n为八进制数字(0-7),则n为一个八进制转义值。
标识一个八进制转义值或一个向后引用。如果\m之前至少有nm个获得子表达式,则nm为向后引用。如果\m之前至少有n个获取,则n为一个后跟文字m的向后引用。如果前面的条件都不满足,若n和m均为八进制数字(0-7),则\m将匹配八进制转义值nm。
\m
如果n为八进制数字(0-7),且m和l均为八进制数字(0-7),则匹配八进制转义值nml。
\ml
匹配n,其中n是一个用四个十六进制数字表示的Unicode字符,例如,\\u00A9匹配版权符号(©)。
\\un
进制转义值
匹配pattern并获取这一匹配。所获取的匹配可以从产生的Matches集合得到
(pattern)
获取匹配
匹配pattern但不获取匹配结果,也就是说这是一个非获取匹配,不进行存储供以后使用。这在使用或字符“(|)”来组合一个模式的各个部分是很有用。例如“industr(?:y|ies)”就是一个比“industry|industries”更简略的表达式。
(?:pattern)
先向后(即正向)探测,看看有没有符合 b style=\
逻辑
非获取匹配,正向肯定预查,在任何匹配pattern的字符串开始处匹配查找字符串,该匹配不需要获取供以后使用。例如:“Windows(?=95|98|NT|2000)”能匹配“Windows2000”中的“Windows”,但不能匹配“Windows3.1”中的“Windows”。
xxx(?=pattern)
先向后(即正向)探测,看看有没有不符合pattern的。如果有,则把左面的匹配出来; 如果没有,则光标往后移一位,继续探测。这个过程就是负正向预查:预先判断为某个值。然后匹配到的东西不包含这个元素。 注意:(?!pattern)在右边,查找返回值是左边的部分。(?!pattern) 后面不能跟任何字符
非获取匹配,正向否定预查,在任何不匹配pattern的字符串开始处匹配查找字符串,该匹配不需要获取供以后使用。例如:“Windows(?!95|98|NT|2000)”能匹配“Windows3.1”中的“Windows”,但不能匹配“Windows2000”中的“Windows”。
xxx(?!pattern)
正向(pattern在后面)预查
从右向左(即反向)探测,看看有没有符合pattern的。如果有,则把右面的匹配出来; 如果没有,则光标往后移一位,继续探测。这个过程就是反向预查:预先判断为某个值。 然后匹配到的东西不包含这个元素。
非获取匹配,反向肯定预查,与正向肯定预查类似,只是方向相反。例如:“(?<=95|98|NT|2000)Windows”能匹配“2000Windows”中的“Windows”,但不能匹配“3.1Windows”中的“Windows”。
(?<=pattern)xxx
从右向左(即反向)探测,看看有没有不符合pattern的。如果有,则把右面的匹配出来; 如果没有,则光标往后移一位,继续探测。这个过程就是负反向预查:预先判断为某个值, 然后匹配到的东西不包含这个元素。
非获取匹配,反向否定预查,与正向否定预查类似,只是方向相反。例如:“(?<!95|98|NT|2000)Windows”能匹配“3.1Windows”中的“Windows”,但不能匹配“2000Windows”中的“Windows”。
(?<!pattern)xxx
反向(pattern在前面)预查
非获取匹配
模式匹配
特殊字符
语法
尽可能多地匹配所搜索的字符串,默认为贪婪模式
贪婪模式
尽可能少地匹配所搜索的字符串
非贪婪模式:
贪婪模式、非贪婪模式
'\' 10 换行(newline)'\' 13 回车(return)在windows系统下,回车换行符号是“\ \\"在Linux等系统下是没有\"\\"符号的在解析文本或其他格式的文件内容时,常常要碰到判定回车换行的地方,这个时候就要注意既要判定\"\\\"又要判定\"\\"。
\和\ 的区别
我们使用正则表达式,在很多场景下的作用是为了查找和替换,大部分语言的正则表达式实现中,在查找时,使用后向引用来代表一个子模式,语法是\"\\数字\",而在替换中,语法是\"$数字\"。在正则表达式中,我们可以使用 \"\\数字 \" 来进行后向引用,数字 表示这里引用的是前面的第几个子模式。如下: \\1代表前面的子模式([1-6])的匹配结果 1
<h1>This is a valid header</h1> 匹配
<h2>this is a valid</h3> 不匹配
RegEx : <h([1-6])>.*?</h\\1 >
后向引用
在使用正则表达式的时候,我们经常会使用()把某个部分括起来,称为一个子模式。
指获取匹配 ,是指系统会在幕后将所有的子模式匹配结果保存起来(最多保存9个),供我们查找或者替换。如后向引用的使用;
语法:(pattern)
获取匹配怎么用?
通常用来干啥?可以用于标签校验
Capturing(获取匹配)
在子模式内部前面添加“?:” (?:),即为非获取匹配
语法:(?:pattern)
Non-Capturing(非获取匹配)
而Non-Capturing指非获取匹配 ,这时系统并不会保存子模式的匹配结果,子模式的匹配更多的只是作为一种限制条件使用,如正向预查,反向预查,负正向预查,负反向预查等。
正则:Windows (?:[\\w]+\\b)
Windows 95 and Windows 98 are the successor.Then Windows 2000 and Windows Xp appeared.Windows Vista is the Latest version of the family.
匹配文本
Windows 95Windows 98Windows 2000Windows XpWindows Vista
匹配结果(5处)
子模式有Capturing和Non-Capturing两种情况。
子模式、获取匹配、非获取匹配
[^win].*
正则
Windows2008
文本
dows2008
匹配结果
^在[]内第一个位置表示非
[w^n]
adin^dows
n^w
匹配结果(3处)
^在[]内中间位置,表示字母^本身
{、}、(、)、?、*、+在[]出现表示匹配这些字符本身
[]
分组
子主题
正向匹配、反向匹配
肯定匹配、否定匹配
正向肯定,正向否定,反向肯定,反向否定
概念
正则表达式
https://blog.csdn.net/YL3126/article/details/118141476
https://blog.csdn.net/weixin_43464964/article/details/124846086
数据结构
连接所有指定文件并将结果写到标准输出
显示文件,可以连接多个文件形成新文件
用途
cat [选项] ... [文件]...
-n 对输出内容中的所有行标注行号
-b 只对输出内容中的非空行标注行号
常用选项
会打开标准输入,用户输入信息后,按ctrl+D结束输入,之后将用户输入的内容,重定向到output.txt中
若output.txt不存在则创建该文件
1. cat > output.txt
与1类似,不同之处在于这里是将内容追加到output.txt末尾,而1是将output.txt内容完全用用户输入的信息替换
2. cat >>output.txt
先输出test.txt的内容 再输出标准输入的内容(输入完成后,以ctrl+D结束标准输入),再输出command.txt的内容
3. cat test.txt - command.txt
示例
cat
分屏显示文件内容,只可向下翻屏
more
less
more|less [选项] <文件>...
格式
-num 仅适用于more命令,每屏的行数
+num 从指定行开始显示
-c——从顶部清屏然后显示文件内容。
按Enter键向下逐行滚动按空格键向下翻一屏、按b键向上翻一屏文件末尾时more会自动退出,less 按q键退出
交互操作方法
more 和 less的区别
more & less
查看文件头部内容,默认前十行
head
查看文件尾部内容,默认后十行
tail
head | tail [选项] 文件名
-num——指定需要显示文件多少行的内容,若不指定默认只显示十行
显示aa.txt 前|后三行内容
head|tail -3 aa.txt
tail &head
相关命令
查看文件内容
从文件或标准输入中,搜索匹配指定正则pattern的文本内容。grep来自于英文词组“global search regular expression and print out the line”的缩写,意思是用于全面搜索的正则表达式,并将结果输出。人们通常会将grep命令与正则表达式搭配使用,参数作为搜索过程中的补充或对输出结果的筛选,命令模式十分灵活。与之容易混淆的是egrep命令和fgrep命令。如果把grep命令当作是标准搜索命令,那么egrep则是扩展搜索命令,等价于“grep -E”命令,支持扩展的正则表达式。而fgrep则是快速搜索命令,等价于“grep -F”命令,不支持正则表达式,直接按照字符串内容进行匹配。
grep [选项] pattern 文件
-i 忽略大小写-c 只输出匹配行的数量-l 只列出符合匹配的文件名,不列出具体的匹配行-n 列出所有的匹配行,显示行号-h 查询多文件时不显示文件名-s 不显示不存在、没有匹配文本的错误信息-v 显示不包含匹配文本的所有行-w 匹配整词-x 匹配整行-r 递归搜索-q 禁止输出任何结果,已退出状态表示搜索是否成功-b 打印匹配行距文件头部的偏移量,以字节为单位-o 与-b结合使用,打印匹配的词据文件头部的偏移量,以字节为单位-F 匹配固定字符串的内容-E 支持扩展的正则表达式
选项
[root@linuxcool ~]# grep root /etc/passwdroot:x:0:0:root:/root:/bin/bashoperator:x:11:0:operator:/root:/sbin/nologin
搜索某个文件(/etc/passwd)中,包含某个关键词(root)的内容
[root@linuxcool ~]# grep ^root /etc/passwdroot:x:0:0:root:/root:/bin/bash
搜索某个文件中,以某个关键词开头的内容
[root@linuxcool ~]# grep linuxprobe /etc/passwd /etc/shadow/etc/passwd:linuxprobe:x:1000:1000:linuxprobe:/home/linuxprobe:/bin/bash/etc/shadow:linuxprobe:$6$9Av/41hCM17T2PrT$hoggWJ3J/j6IqEOSp62elhdOYPLhQ1qDho7hANcm5fQkPCQdib8KCWGdvxbRvDmqyOarKpWGxd8NAmp3j2Ln00::0:99999:7:::
搜索多个文件中,包含某个关键词的内容
[root@linuxcool ~]# grep -c root /etc/passwd /etc/shadow/etc/passwd:2/etc/shadow:1
输出在某个(些)文件中,包含某个关键词行的数量
[root@linuxcool ~]# grep -v nologin /etc/passwdroot:x:0:0:root:/root:/bin/bashsync:x:5:0:sync:/sbin:/bin/syncshutdown:x:6:0:shutdown:/sbin:/sbin/shutdownhalt:x:7:0:halt:/sbin:/sbin/haltlinuxprobe:x:1000:1000:linuxprobe:/home/linuxprobe:/bin/bash
搜索某个文件中,不包含某个关键词的内容
[root@linuxcool ~]# grep -l root *anaconda-ks.cfggrep: Desktop: Is a directorygrep: Documents: Is a directorygrep: Downloads: Is a directoryinitial-setup-ks.cfggrep: Music: Is a directorygrep: Pictures: Is a directorygrep: Public: Is a directorygrep: Templates: Is a directorygrep: Videos: Is a directory
搜索当前工作目录中,包含某个关键词内容的文件(-l 只列文件名),未找到则提示
[root@linuxcool ~]# grep -sl root *anaconda-ks.cfginitial-setup-ks.cfg
搜索当前工作目录中,包含某个关键词内容的文件(-l 只列文件名),未找到不提示(-s)
[root@linuxcool ~]# grep -srl root /etc/etc/fstab/etc/X11/xinit/Xclients/etc/X11/xinit/xinitrc/etc/libreport/events.d/collect_dnf.conf/etc/libreport/events.d/bugzilla_anaconda_event.conf/etc/libreport/forbidden_words.conf
递归搜索,不仅搜索指定目录,还搜索其内子目录内是否有关键词文件
[root@linuxcool ~]# grep -x cd anaconda-ks.cfg [root@linuxcool ~]# grep -x cdrom anaconda-ks.cfg cdrom
搜索某个文件中,精准匹配到某个关键词的内容(搜索词应与整行内容完全一样才会显示 -x,有别于一般搜索)
[root@linuxcool ~]# grep -q linuxprobe anaconda-ks.cfg [root@linuxcool ~]# echo $?0[root@linuxcool ~]# grep -q linuxcool anaconda-ks.cfg [root@linuxcool ~]# echo $?1
[root@linuxcool ~]# grep -c ^$ anaconda-ks.cfg 6
搜索某个文件中,空行(正则表达式为^$)的数量
grep
文本内容查找
sed
替换
查找与替换
echo是用于在终端设备上输出指定字符串或变量提取后值的命令,能够给用户一些简单的提醒信息,也可以将输出的指定字符串内容同管道符一起传递给后续命令作为标准输入信息再来进行二次处理,又或者同输出重定向符一起操作,将信息直接写入到文件中。如需提取变量值,需在变量名称前加入$符号做提取,变量名称一般均为大写形式。
echo [参数] 字符串/变量
-n 不输出结尾的换行符-e “\\a” 发出警告音-e “\\b” 删除前面的一个字符-e “\\c” 结尾不加换行符-e “\\f” 换行,光标扔停留在原来的坐标位置-e “\” 换行,光标移至行首-e “\” 光标移至行首,但不换行-E 禁止反斜杠转移,与-e参数功能相反—version 查看版本信息--help 查看帮助信息
[root@linuxcool ~]# echo LinuxCoolLinuxCool
输出指定字符串到终端设备界面(默认为电脑屏幕)
[root@linuxcool ~]# echo $PATH/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/root/bin
输出某个变量值内容
[root@linuxcool ~]# echo \\$PATH$PATH
搭配转义符一起使用,输出纯字符串内容
[root@linuxcool ~]# echo \"Hello World\" > Document
搭配输出重定向符一起使用,将字符串内容直接写入文件中
搭配反引号执行命令,并将执行结果输出
[root@linuxcool ~]# echo -e \"First\Second\Third\"FirstSecondThird
输出带有换行符的内容
[root@linuxcool ~]# echo -e \"123\\b456\" 12456
指定删除字符串中某些字符,随后将内容输出
echo
cut
join
sort
fmt
wc
lpr
lprm
lpq
cancel
pr
打印
文本操作
find命令的功能是根据给定的路径和条件查找相关文件或目录,可以使用的参数很多,并且支持正则表达式,结合管道符后能够实现更加复杂的功能,是系统管理员和普通用户日常工作必须掌握的命令之一。find命令通常进行的是从根目录(/)开始的全盘搜索,有别于whereis、which、locate……等等的有条件或部分文件的搜索。对于服务器负载较高的情况,建议不要在高峰时期使用find命令的模糊搜索,会相对消耗较多的系统资源。
find path -option [ -print ] [ -exec -ok command ] {} \\;
find <指定目录> <指定条件> <指定动作>
- <指定目录>: 所要搜索的目录及其所有子目录。默认为当前目录。- <指定条件>: 所要搜索的文件的特征。- <指定动作>: 对搜索结果进行特定的处理。
-name 匹配名称-perm 匹配权限(mode为完全匹配,-mode为包含即可)-user 匹配所有者-group 匹配所有组-mtime -n +n 匹配修改内容的时间(-n指n天以内,+n指n天以前)-atime -n +n 匹配访问文件的时间(-n指n天以内,+n指n天以前)-ctime -n +n 匹配修改文件权限的时间(-n指n天以内,+n指n天以前)-nouser 匹配无所有者的文件-nogroup 匹配无所有组的文件-newer f1 !f2 匹配比文件f1新但比f2旧的文件-type b/d/c/p/l/f 匹配文件类型(后面的字幕字母依次表示块设备、目录、字符设备、管道、链接文件、文本文件)-size 匹配文件的大小(+50KB为查找超过50KB的文件,而-50KB为查找小于50KB的文件)-prune 忽略某个目录-exec …… {}\\; 后面可跟用于进一步处理搜索结果的命令
-type #根据文件类型 find /var/log -type f -name \"*.log\" ;find /var/log -type d-name #根据文件名 find /var/log -type f -name \"*.log\"-perm #根据文件权限 find /var/log -perm 600 -type f -name \"*.log\"-user #根据文件所属主 find /var/log -user XD
列出/etc及子目录下的所有文件和目录
find /etc
搜索当前目录(含子目录,以下同)中,所有文件名以my开头的文件。
find . -name 'my*'
搜索当前目录中,所有过去10分钟中更新过的普通文件。如果不加-type f参数,则搜索普通文件+特殊文件+目录。
find . -type f -mmin -10
搜索/etc目录下的.log文件
find /etc -name \"*.log\"
[root@linuxcool ~]# find / -name *.conf/run/tmpfiles.d/kmod.conf/etc/resolv.conf/etc/dnf/dnf.conf/etc/dnf/plugins/copr.conf/etc/dnf/plugins/debuginfo-install.conf/etc/dnf/plugins/product-id.conf/etc/dnf/plugins/subscription-manager.conf
全盘搜索系统中所有以.conf结尾的文件
[root@linuxcool ~]# find /etc -size +1M/etc/selinux/targeted/policy/policy.31/etc/udev/hwdb.bin
在/etc目录中搜索所有大约1M大小的文件
[root@linuxcool ~]# find /home -user linuxprobe/home/linuxprobe/home/linuxprobe/.mozilla/home/linuxprobe/.mozilla/extensions/home/linuxprobe/.mozilla/plugins/home/linuxprobe/.bash_logout/home/linuxprobe/.bash_profile/home/linuxprobe/.bashrc
在/home目录中搜索所有属于指定用户的文件
[root@linuxcool ~]# find .../.bash_logout./.bash_profile./.bashrc./.cshrc./.tcshrc./anaconda-ks.cfg
列出当前工作目录中的所有文件、目录以及子文件信息
[root@linuxcool ~]# find /var/log -iname \"*.log\"/var/log/audit/audit.log/var/log/rhsm/rhsmcertd.log/var/log/rhsm/rhsm.log/var/log/sssd/sssd.log/var/log/sssd/sssd_implicit_files.log/var/log/sssd/sssd_nss.log
在/var/log目录下搜索所有指定后缀的文件,后缀不需要大小写。
[root@linuxcool ~]# find /var/log ! -name \"*.log\"/var/log/var/log/lastlog/var/log/README
在/var/log目录下搜索所有后缀不是.log的文件
[root@linuxcool ~]# find . -mtime +7./.bash_logout./.bash_profile
搜索当前工作目录中的所有近7天被修改过的文件
[root@linuxcool ~]# find / -type d -perm 1777/dev/mqueue/dev/shm/var/tmp
全盘搜索系统中所有类型为目录,且权限为1777的目录文件
[root@linuxcool ~]# find / -type f -perm /a=x /boot/vmlinuz-4.18.0-80.el8.x86_64/boot/vmlinuz-0-rescue-c8b04558503242459d908c6c22a2d481/etc/X11/xinit/xinitrc.d/50-systemd-user.sh
全盘搜索系统中所有类型为普通文件,且可以执行的文件信息
[root@linuxcool ~]# find / -name \"*.mp4\" -exec rm -rf {} \\;
全盘搜索系统中所有后缀为.mp4的文件,并删除所有查找到的文件
find
whereis
locate
which
type
查找
文件操作
uname命令来自于英文词组”Unix name“的缩写,其功能是用于查看系统主机名、内核及硬件架构等信息。如果不加任何参数,默认仅显示系统内核名称,相当于-s参数。
uname [参数]
-a 显示系统所有相关信息-m 显示计算机硬件架构-n 显示主机名称-r 显示内核发行版本号-s 显示内核名称-v 显示内核版本-p 显示主机处理器类型-o 显示操作系统名称-i 显示硬件平台
[root@linuxcool ~]# unameLinux
显示系统内核名称
[root@linuxcool ~]# uname -aLinux linuxcool.com 4.18.0-80.el8.x86_64 #1 SMP Wed Mar 13 12:02:46 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux
显示系统所有相关信息(含内核名称、主机名、版本号及硬件架构)
[root@linuxcool ~]# uname -r 4.18.0-80.el8.x86_64
显示系统内核版本号
[root@linuxcool ~]# uname -ix86_64
现在系统硬件架构
uname 显示系统内核信息
netstat命令来自于英文词组”network statistics“的缩写,其功能是用于显示各种网络相关信息,例如网络连接状态、路由表信息、接口状态、NAT、多播成员等等。netstat命令不仅应用于Linux系统,而且在Windows XP、Windows 7、Windows 10及Windows 11中均已默认支持,并且可用参数也相同,有经验的运维人员可以直接上手
netstat [参数]
-a 显示所有连线中的Socket-p 显示正在使用Socket的程序识别码和程序名称-l 仅列出在监听的服务状态-t 显示TCP传输协议的连线状况-u 显示UDP传输协议的连线状况-i 显示网络界面信息表单-r 显示路由表信息-n 直接使用IP地址,不通过域名服务器
[root@linuxcool ~]# netstat -aActive Internet connections (servers and established)Proto Recv-Q Send-Q Local Address Foreign Address State tcp 0 0 0.0.0.0:http 0.0.0.0:* LISTEN tcp 0 0 0.0.0.0:https 0.0.0.0:* LISTEN tcp 0 0 0.0.0.0:ms-wbt-server 0.0.0.0:* LISTEN
显示系统网络状态中的所有连接信息
[root@linuxcool ~]# netstat -nuActive Internet connections (w/o servers)Proto Recv-Q Send-Q Local Address Foreign Address State udp 0 0 172.19.226.238:68 172.19.239.253:67 ESTABLISHED
显示系统网络状态中的UDP连接信息
[root@linuxcool ~]# netstat -apu Active Internet connections (servers and established)Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name udp 0 0 linuxcool:bootpc _gateway:bootps ESTABLISHED 1024/NetworkManager udp 0 0 localhost:323 0.0.0.0:* 875/chronyd udp6 0 0 localhost:323 [::]:* 875/chronyd
显示系统网络状态中的UDP连接端口号使用信息
[root@linuxcool~]# netstat -i Kernel Interface tableIface MTU RX-OK RX-ERR RX-DRP RX-OVR TX-OK TX-ERR TX-DRP TX-OVR Flgeth0 1500 31945 0 0 0 39499 0 0 0 BMRUlo 65536 0 0 0 0 0 0 0 0 LRU
显示网卡当前状态信息
[root@linuxcool ~]# netstat -rKernel IP routing tableDestination Gateway Genmask Flags MSS Window irtt Ifacedefault _gateway 0.0.0.0 UG 0 0 0 eth0172.19.224.0 0.0.0.0 255.255.240.0 U 0 0 0 eth0
显示网络路由表状态信息
[root@linuxcool ~]# netstat -ap | grep sshunix 2 [ ] STREAM CONNECTED 89121805 203890/sshd: root [ unix 3 [ ] STREAM CONNECTED 27396 1754/sshd unix 3 [ ] STREAM CONNECTED 89120965 203890/sshd: root [ unix 2 [ ] STREAM CONNECTED 89116510 203903/sshd: root@p unix 2 [ ] STREAM CONNECTED 89121803 203890/sshd: root [ unix 2 [ ] STREAM CONNECTED 29959 1754/sshd unix 2 [ ] DGRAM 89111175 203890/sshd: root [ unix 3 [ ] STREAM CONNECTED 89120964 203903/sshd: root@p
找到某个服务所对应的连接信息
etstat命令 – 显示网络状态
系统管理
linux命令参见:https://www.linuxcool.com/
JDK各版本演进及新功能
Eden
To Survivor(Survivor1,S1)
新生代 ( Young )
老年代 ( Old )
堆
VM1.8中,图中的 方法区为元数据区
用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据
方法区
所有线程共有
每个线程在创建时都会创建一个虚拟机栈,其内部保存一个个的栈帧(Stack Frame),对应着一次次的Java方法调用。
虚拟机栈的作用:主管Java程序的运行,它保存方法的局部变量、部分结果,并参与方法的调用和返回。
每个方法被调用到执行完的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
栈帧(Stack Frame) 是用于虚拟机执行时方法调用和方法执行时的数据结构,它是虚拟栈的基本元素,栈帧由局部变量区、操作数栈等组成,如下图所示:
每一个方法从调用到方法返回都对应着一个栈帧入栈出栈的过程。最顶部的栈帧称为当前栈帧,栈帧所关联的方法称为当前方法,定义这个方法的类称为当前类,该线程中,虚拟机有且也只会对当前栈帧进行操作。
虚拟机栈
栈帧的作用有存储数据,部分过程结果,处理动态链接,方法返回值和异常分派。
本地方法栈
在JVM的概念模型里,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令。
分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。
程序计数器占用的内存空间很少,也是唯一一个在JVM规范中没有规定任何OutOfMemoryError(内存不足错误)的区域。
程序计数器
线程私有
主要分为两部分、5大区域
https://blog.csdn.net/m0_71777195/article/details/125819073?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522166615991416800186577080%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=166615991416800186577080&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_ecpm_v1~rank_v31_ecpm-1-125819073-null-null.nonecase&utm_term=jvm&spm=1018.2226.3001.4450
参考
java对象内存模型:
如果两个对象互相引用,那么就意味着两个对象永久性的无法被回收?对象无法被回收,显然这种方法时不行的
行不通
引用计数法
推荐
可达性分析
如何确定对象是否可以被回收? 两种方案
1. 标记
2. 清除
思路
这种回收算法:因为需要遍历所有的对象,所以是比较耗时的
标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。(1)标记和清除两个过程都比较耗时,效率不高(2)会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连续内存 而不得不提前触发另一次垃圾收集动作。
问题
复制算法
解决方案
标记清除算法-[marked-sweep]
将内存分为大小相等的两块,每次我们只使用其中一块
当前正在使用的这一块使用完了之后,就会把LiveObject 对象移动到另外一半上,其他的被回收掉
不会产生内存碎片
好处
有一半的内存空间l浪费
分代垃圾收集
解决办法
标记过程仍然与\"标记-清除\"算法一样,但是后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动, 然后直接清理掉端边界以外的内存
标记整理算法(Mark Compact)
垃圾回收算法
在Java程序运行的过程中,会产生大量的对象
如Http请求中的Session对象、线程、Socket连接,这类是与业务信息相关的对象
有些对象生命周期比较长,短期内不会死亡
主要是程序运行过程中生成的临时变量,这些对象生命周期会比较短,比如:String对象,由于其不变类的特性,系统会产生大量的这些对象,有些对象甚至只用一次即可回收
有些对象生命周期短,很快就会死亡而希望被快速回收
但不同的对象的生命周期是不一样的
若根据对象存活时间进行分代,则每次垃圾回收都是对整个堆空间进行回收,花费时间相对会长,并且每次回收都需要遍历所有存活对象,但实际上,对于生命周期长的对象而言,这种遍历是没有意义的,因为可能进行了很多次遍历,但是他们依旧存在。
因此,分代垃圾回收采用分治的思想,进行代的划分,把不同生命周期的对象放在不同代上,不同代上采用最适合它的垃圾回收方式进行回收
为什么分代
存放生命周期短,很快就会死亡而希望被快速回收的对象
程序运行过程中,对象创建非常频繁,因此新生代内存分配非常频繁
新对象都在新生代内创建
造成新生代会在短期内生成大量的垃圾对象
新生代绝大多数为垃圾对象,这些垃圾对象希望能被快速回收,以为后续新对象腾出空间,因此内存回收也非常频繁
绝大多数对象的生命周期很短
特点
1. 能快速创建大量对象
2. 频繁创建的垃圾对象能够被快速回收
3. 回收频率和效率都要很高,并且希望每次回收后尽量减少甚至不希望有内存碎片(对象创建销毁太频繁,若碎片太多可能找不到足够连续的空间分配给新对象)
根据上述特点,对这块区域的要求
就像你有一个文件夹,里面有 1000 张图片,其中只有 3 张是有用的,你要删除余下的 997 张垃圾图片,你会怎么删除呢?你肯定不会一张一张的去删除 997 张垃圾图片,这样需要删除 997 次,你显然认为把 3 张有用的图片复制出去,然后将整个文件夹干掉来的快。这也就是为什么新生代使用复制算法合适、使用标记清除算法不合适。
新生代里绝大部分都是垃圾对象,可以使用复制算法将小部分存活对象复制到另一个区域,然后留下来的都是垃圾对象,可以一网打尽,一下子清除掉。因为存活的对象少,所以“复制”的次数少;虽然留下来的垃圾对象多,但是可以一网打尽,所以“删除”的次数也少,速度非常快,并且可以频繁执行回收操作。
另外,也不会产生碎片,满足新生代的内存要求
原因:
复制算法要求新生代内存不能太高,否则复制会比较慢
要求
回收算法
新对象在此区被创建
在Edge区经历一次Minor GC之后,仍然存活的对象就会被放入S0区
Eden (8)
复制算法的对象复制区1
复制算法的对象复制区2
To Survivor(又叫Survivor1,S1, 1)
将新生代分为三部分,比例默认为8:1:1
2.Survivor的存在意义,就是减少被送到老年代的对象,进而减少Full GC的发生,Survivor的预筛选保证,只有经历16次Minor GC还能在新生代中存活的对象,才会被送到老年代。
为什么要分为Eden和Survivor?
可能不准确,因为没有涉及From Survivor区回收。From Survivor区在Minor GC时,不存活对象也需要被回收。
参见:https://blog.csdn.net/Student_xx/article/details/121746854
新生代GC非常频繁
时刻保持两个Survivor区有一个是空闲的,以用于下次GC时作为To Survivor区
Minor GC时,会把Edge区和From Survivor区的存活对象,复制到To Survivor区,然后From Survivor和To Survivor区互换
因为这些对象大都是临时对象,生命周期非常短,在Edge区就死亡了,变成了垃圾对象
大部分 Eden 区中的对象都能被认为是垃圾,永远也不会被复制到 Survivor 区或者老年代空间
Edge区满后,对象会被复制到S0(或S1),之后指针会从0开始跟踪内存,原有对象占用的内存空间直接被覆盖即可
从S0(或S1)到S1(或S0)的对象复制同样如此,因此不会产生任何碎片
整个新生代(包括Edge区和 Survivor 区)都采用复制算法,因此不会产生任何碎片
执行 Minor GC 操作时,不会影响到永久代
出现了 Major GC,通常会伴随至少一次的 Minor GC ,MajorGC 的速度一般会比 Minor GC 慢 10倍以上
在垃圾回收过程中由于要移动对象,需要对对象引用进行更新,为了保证引用更新的正确性,Java将暂停所有其他的线程,这种情况被称为“Stop-The-World”,导致系统全局停顿。
垃圾收集后,若Survivor及old区仍然无法存放从Eden复制过来的部分对象,导致JVM无法在Eden区为新对象创建内存区域。则出现Out of memory。
这不代表着停止复制清理法很高效,其实,它也只在大部分对象存活周期很短的事实情况下高效
如果在老年代采用停止复制,则是非常不合适的
这种方式分配内存和清理内存的效率都极高
结论
新生代回收过程
设置两个Survivor区最大的好处就是解决了碎片化,刚刚新建的对象在Eden中,经历一次Minor GC,Eden中的存活对象就会被移动到第一块survivor space S0,Eden被清空;等Eden区再满 了,就再触发一次Minor GC,Eden和S0中的存活对象又会被复制送入第二块survivor space S1(这个过程非常重要,因为这种复制算法保证了S1中来自S0和Eden两部分的存活对象占用连续 的内存空间,避免了碎片化的发生)
为什么要设置两个Survivor区?
回收机制
新生代(Yong区)
存放生命周期长,很长时间都不会死亡的对象
对象在新生代被创建后,若其生命周期比较长,就会被移动到老年代,因此老年代是存放生命周期比较长的对象的区域,对象回收周期比较长,不会被频繁GC,当老年代内存不足时,会触发Full GC
当老年代满时会触发MajorGC,只有CMS收集器会有单独收集老年代的行为,其他收集器均为Full GC。而针对新生代的Minor GC,各个收集器均支持。总之,单独发生收集行为的只有新生代,除了CMS收集器,都不支持老年代单独GC。
老年代(Old区)
分代实现:将JVM分为两个区域
最终JVM采用算法:分代
垃圾回收核心算法
除了清除,还要做压缩。怎么才能标记和清除清楚上百万对象呢?答案就是STW,让全世界停止下来。
为何会有 STW
采用串行单线程方式完成GC任务,其中“Stop The World\"简称STW,即垃圾回收的某个阶段会暂停整个应用程序的执行F-GC的时间相对较长,频繁FGC会严重影响应用程序的性能。
单线程
适合Client模式
简单高效
采用\"复制\"算法
Serial
是Serial的多线程版本
ParNew由多条GC线程并行地进行垃圾清理.但清理过程font color=\"#ff0000\
默认开启的收集线程数与CPU数量相同.
多线程并行执行
适合多CPU的服务器环境
追求\"降低停顿时间\"
良好的反应速度提升用户体验.
font color=\"#ff0000\
与Serial性能对比
ParNew
CPU总时间包括 : 用户线程运行时间 和 GC线程运行的时间.
吞吐量 = 运行用户代码时间/(运行用户代码时间+垃圾收集时间)
吞吐量是指用户线程运行时间占CPU总时间的比例.
降低停顿时间的两种方式
追求用户线程CPU吞吐量
适合后台大量运算而不需要太多用户交互的场景
直接设置吞吐量大小,是一个大于0小于100的整数,也就是程序运行时间占总时间的比率,默认值是99,即垃圾收集运行最大1% =(1/(1+99))的垃圾收集时间
-XX:GCTimeRadio
新生代晋升年老代对象年龄(-XX:PretenureSizeThreshold)等细节参数,虚拟机会根据当前系统运行情况收集性能监控信息,动态调整这些参数以达到最大吞吐量,这种方式称为GC自适应调节策略,自适应调节策略也是ParallelScavenge收集器与ParNew收集器的一个重要区别。
开启GC 自适应的调节策略(区别于ParNew).
这是个开关参数,打开之后就不需要手动指定新生代大小(-Xmn)、Eden与Survivor区的比例(-XX:SurvivorRation)
-XX:+UseAdaptiveSizePolicy
Parallel Scavenge提供的参数
HotSpot VM里多个GC有部分共享的代码。有一个分代式GC框架,Serial/Serial Old/ParNew/CMS都在这个框架内;在该框架内的young collector和old collector可以任意搭配使用,所谓的“mix-and-match”。
而ParallelScavenge(与G1)则不在这个框架内,而是各自采用了自己特别的框架,所以不能跟使用了那个框架的CMS搭配使用。
不能与CMS一起使用
Parallel Scavenge
只用于新生代
使用\"标记-整理\"算法
Serial Old
使用\"标记-整理\"算法.
span style=\
Parallel Old
一种追求最短停顿时间的收集器,它在垃圾收集时使得用户线程和GC线程并发执行,因此在GC过程中用户也不会感受到明显卡顿。但用户线程和GC线程之间不停地切换会有额外的开销,因此垃圾回收总时间就会被延长。
回收停顿时间比较短,对许多应用来说,快速响应比端到端的吞吐量更为重要。
追求\"降低停顿时间\" (与新生代ParNew目标相同)
尽可能并发执行,每个 GC 周期只有2次短的停顿。
使用\"标记-清除\"算法
由于CMS在垃圾收集过程使用用户线程和GC线程并行执行,从而线程切换会有额外开销,因此CPU吞吐量就不如在GC过程中停止一切用户线程的方式来的高。
吞吐量低
由于垃圾清除过程中,用户线程和GC线程并发执行,即用户线程仍在执行,则在执行过程中会产生垃圾,这些垃圾称为\"浮动垃圾\"。如果CMS在垃圾清理过程中,用户线程需要在老年代中分配内存时发现空间不足,就需再次发起Full GC,而此时CMS正在进行清除工作,因此此时只能由Serial Old临时对老年代进行一次Full GC。
由于CMS采用的是“标记-清除算法\",因此产生大量的空间碎片,不利于空间利用率。
为了解决这个问题,CMS可以通过配置-XX:+UseCMSCompactAtFullCollection强制JVM在FGC完成后対老年代进行压缩,执行一次空间碎片整理,但空间碎片整理阶段也会引发STW。为了减少STW次数,CMS还可以通过配置-XX:+CMSFullGCsBeforeCompaction=n在执行了n次FGC后,JVM再在老年代执行空间碎片整理。在并发收集失败的情况下,Java虚拟机会使用其他两个压缩型垃圾回收器进行一次垃圾回收。
使用\"标记-清除\"算法产生碎片空间
无法解决,因此出现了G1,同时CMS被废弃
漏标问题
缺点
由于G1的出现,CMS在Java 9中已被废弃。
CMS在Java 9中已被废弃
CMS ((Concurrent Mark Sweep))
只用于老生代
是一个横跨新生代和老年代的垃圾回收器。实际上,它已经打乱了前面所说的堆结构,直接将堆分成极其多个区域。每个区域都可以充当Eden区、Survivor区或者老年代中的一个。它采用的是标记-压缩算法,而且和CMS一样都能够在应用程序运行过程中并发地进行垃圾回收。
虽然G1依然有分代内存划分,但抛弃了连续的分代,他们可以是一些不连续的Region集合,正因为这样,每一个Region区域的功能会发生变化,比如一个Region之前按是年轻代,在做完垃圾回收之后又变成了老年代。
G1能够针对每个细分的区域来进行垃圾回收。在每次GC发生时会根据「用户指定的期望停顿时间或默认的期望停顿时间」,优先从列表中选择「回收价值最大(优先回收死亡对象较多的区域)」Region区回收,这也是G1名字的由来。
和CMS相比,Gl具备压缩功能,能避免碎片向題,G1的暂停时间更加可控。性能总体还是非常不错的,G1是当今最前沿的垃圾收集器成果之一
可以非常精确控制停顿时间,在不牺牲吞吐量前提下,实现低停顿垃圾回收
概述
追求停顿时间
多线程GC
面向服务端应用
不会产生内存空间碎片.
可对整个堆进行垃圾回收
G1能充分利用CPU、多核的环境优势来缩短STW停顿时间,部分其他收集器需要STW的区域,G1也可以并发执行。
并行与并发
虽然G1去掉了连续内存空间分代的概念,也不需要其他收集器配合使用,但分代收集依然存在
分代收集
整体来看基于标记-整理,局部来看基于复制算法合并
空间整合
用户可通过-XX:MaxGCPauseMillis参数设置最大停顿时间,拥有良好的用户体验,但是这个参数不可随意设置,不能设置的太小,否则每一次minor gc时间过短,收集的垃圾太少,容易触发full gc
可预测的停顿时间
Survivor
Old
专门用于存放巨型对象,如果一个对象的大小超过Region容量的50%以上,G1 就认为这是个巨型对象。在其他垃圾收集器中,这些巨型对象默认会被分配在老年代,但如果它是一个短期存活的巨型对象,放入老年代就会对垃圾收集器造成负面影响,触发老年代频繁GC。为了解决这个问题,G1划分了一个H区专门存放巨型对象,如果一个H区装不下巨型对象,那么G1会寻找连续的H分区来存储,如果寻找不到连续的H区的话,就不得不启动 Full GC 了。
Humongous
包括四类Region
每个Region是连续的一段内存,具体大小根据堆的实际大小而定,整体被控制在 1M - 32M 之间,且为2的N次幂(1M、2M、4M、8M、16M和32M)
这样的划分方式意味着不需要一个连续的内存空间管理对象.
G1将Java堆空间分割成了许多相同大小的区域,即region
一个对象和它内部所引用的对象可能不在同一个Region中
在串行和并行收集器中,GC时是通过整堆扫描来确定对象是否处于可达路径中
G1为了避免STW式的整堆扫描,为每个分区各自分配了一个 RSet(Remembered Set),它内部类似于一个反向指针,记录了其它 Region 对当前 Region 的引用情况,这样就带来一个极大的好处:回收某个Region时,不需要执行全堆扫描,只需扫描它的 RSet 就可以找到外部引用,来确定引用本分区内的对象是否存活,进而确定本分区内的对象存活情况,而这些引用就是初始标记的根之一
如果引用源是本分区的对象,那么就不需要记录在 RSet 中;
同时 G1 每次 GC 时,由于所有的新生代都会被扫描,因此引用源是年轻代的对象,也不需要在RSet中记录;
所以最终只需要记录老年代到新生代之间的引用即可。
事实上,并非所有的引用都需要记录在RSet中
Remember Set
可达性分析快,不需要扫描整个堆内存
能够针对每个细分的区域来进行垃圾回收
优先回收垃圾最多的区域
内存模型
先STW,只启动一条初始标记线程记录下gc roots直接能引用的对象,速度很快,如果不做STW,gc root会非常多
初始标记
根据初始标记的结果,做整个的一个可达性分析,找出所有的被引用的对象,这个过程耗时比较长(大约占整个收集过程的80%左右),但是这个过程和用户线程并发执行,所以用户无感知,但是因为用户程序继续运行,可能会有导致已经标记过的对象状态发生改变(就比如在并发标记前是非垃圾,标记之后是垃圾或者并发标记前是垃圾,并发标记后变非垃圾),详情这篇文章中的三色标记处理(https://blog.csdn.net/qq_41931364/article/details/106988008)
并发标记
多线程
先STW,同时修复在并发标记里面出现状态变换的对象,主要用到三色标记里的原始快照算法(见下面详解)做重新标记
最终标记
会根据用户所指定的STW参数-XX:MaxGCPauseMillis(最大垃圾回收停顿时间)来制定回收计划,判断这轮回收需要回收多少个,所以这个阶段不一定会将所有的垃圾都回收掉,它在回收之前对堆有一个区域的回收时间估算,如果回收1/2就达到了用户指定的最大停顿时间,那么就只会回收1/2,但是这1/2如何去选具体是哪一块区域,有一个筛选算法在下面详解,剩下的在下一次的垃圾回收去回收,这也就是为什么没有重置标记,其实筛选回收是可以与用户线程并发执行的,但是由于我们指定了最大停顿时间,所以,在保证时间的情况下为了提高吞吐量,我们进行了STW,回收完成之后将旧的地址转换成新的地址。(注意:其实这个阶段可以与用户线程并发执行,但是由于G1内部算法过于复杂,没有实现并发执行,不过到了Shenandoah就实现了并发收集,Shenandoah可以看成是G1的升级版本。)
在G1收集器后台维护了一个优先列表,每次根据允许的收集时间,选择回收价值最大的Region,比如在同等大小的两个Region下,回收一个需要100ms,一个需要50ms,那么G1肯定会优先回收那个50ms的,这样就保证了在有效时间内能回收更多的堆空间,回收时间就是复制的时间,要复制的存活对象越多,回收时间就越长,回收的效益就越低,被回收的优先级就越低。
筛选算法
无论是年轻代还是老年代主要的回收算法是复制算法
它会将要回收Region的存活对象挪到相邻的空的Region,然后清空之前的Region,这样就保证了内存碎片的减少。
筛选回收
垃圾收集过程
Eden区的默认大小为5%,当Eden区放满的时候,G1不会马上做minor gc,它会先判断一下,触发一次minor gc的时间与我们用户设置的最大停顿时间的差距,如果大于或者接近最大停顿时间,则立即触发minor gc,如果回收时间比最大停顿时间小很多的话,将会扩大Eden区,继续放对象,过一段时间再次判断,依次重复,直到做了minor gc。
minor gc
不是full gc,我们有一个参数-XX:InitiatingHeapOccupancyPercent可设定当老年代的占比达到一定的大小之后触发mixed gc,mixed gc主要回收所有年轻代和部分的老年代以及大对象区域,由于底层是复制算法,对剩余空间大小的要求比较高,所以,触发mixed gc的占比必须要调整到合适大小,如果没有足够的region去复制存活的对象,将会触发full gc。
mixed gc
先STW,然后采用单线程进行标记、清理和压缩整理,好空闲出来一批Region来供下一次MixedGC使用,非常耗时。在后续有一个收集器版本Shenandoah就优化成多线程收集了。
full gc
G1垃圾收集分类
默认占整个堆的5%,可以通过设置-XX:G1NewSizePercent参数调整年轻代的初始占比,在运行过程中,JVM会动态的调整年轻代的占比,但最多不会超过整个堆的60%,最大占比也可以通过-XX:G1MaxNewSizePercent进行调整,年轻代的Eden区和Survivor区比例也是8:1:1。
年轻代比例配置
可以避免一些“短命”的巨型对象直接进入年老代,节约年老代的内存空间,可以有效避免年老代因空间不足时的GC开销。
Humongous区存在的意义
专门放大对象的区域,当一个对象的大小超过了一个Region区域的50%,则为大对象,直接放到Humongous区,在MixedGC或Full gc的时候会回收。
大对象
配置
通过设置不同的「期望停顿时间」可以使得G1在任意场景下,都能在「吞吐量」和「响应时间/低延迟」之间取得很好的平衡。
如果这个参数过于大,minor gc将很少发生,在它发生时有极大可能会有大量的存活对象进入Survivor区,如果Survivor放不下,就会进入老年代,就很容易触发mixed gc,不建议
太大
在触发minor gc时很难回收到垃圾,最后导致垃圾太多,空间被占,也很容易触发mixed gc
太小
G1的默认的「停顿时间」目标为200ms,但一般而言,实际过程中中占到[几十 ~ 三百毫秒之间]都很正常
实际应用中,通常把「期望停顿时间」设置为100ms~300ms之间会是比较合理的选择。
但这里设置的「期望停顿时间」值必须要符合实际,如果想着:如果设置成20ms岂不是超低延迟了?这是并不现实的,只能称为异想天开,因为G1回收阶段也需要发生STW的来根扫描、复制对象、清理对象的,所以这个「期望停顿时间」值也得有个度,这个参数不能太大也不能太小。
另外,在追求响应时间的时候必然会牺牲吞吐量,而追求吞吐量的同时必然会牺牲响应时间。鱼和熊掌不可兼得。
优势
当大多数对象都存活的时候,说明老年代被占用的比例也会很大,这个时候就会触发full gc,full gc是很慢的,如果我们使用G1,那么G1就会触发mixed gc,而且mixed gc的GC最大停顿时间还是可控的。
50%以上的堆被存活对象占用
说明了对象往老年代挪动的频率很频繁,一样的,可以减少full gc的发生
对象分配和晋升的速度变化非常大
可以设置停顿时间,提升用户体验。
垃圾回收时间特别长,超过1秒
内存如果在8G以下,收集的垃圾不是很多,而G1的算法相对于CMS较为复杂,还很有可能效率不如CMS,但是对于大内存,STW时间比较长,所以,在可控停顿时间这里,G1比较合适。
8GB以上的堆内存(建议值)
停顿时间可由用户控制
停顿时间是500ms以内
G1的适合场景
JDK11的默认垃圾收集器
https://blog.csdn.net/qq_41931364/article/details/107040928
https://blog.csdn.net/a745233700/article/details/121724998
参考资料
G1(Garbage First)
主要是用于测试的无操作收集器,如:性能测试、内存压力测试、VM接口测试等。在程序启动时选择装载Epsilon收集器,这样可以帮助我们过滤掉GC机制引起的性能假象。装配该款GC收集器的JVM,在运行期间不会发生任何GC相关的操作,程序所分配的堆空间一旦用完,Java程序就会因OOM原因退出
Epsilon
一款源自于JDK11的性能魔兽 - ZGC
是一款基于分区概念的内存布局GC器,这款GC器是真正意义上的不分代收集器,因为它无论是从逻辑上,还是物理上都不再保留分代的概念
ZGC也会把堆空间划分为一个个的Region区域,但ZGC中的Region区不存在分代的概念,仅仅只是简单的将所有Region区分为了大、中、小三个等级
使用读屏障、染色指针和内存多重映射等技术来实现的可并发的标记-整理算法的垃圾收集器。
G1引入Remembered Set的目的是防止整堆扫码,提高性能,而不是为了解决跨代引用
ZGC扫码所有页面,是否会有性能问题?如何解决?
由于不分代,ZGC中只存在一种GC类型,同时也不需要G1中Remembered Set这种概念存在,因为是单代的堆空间,所以每次回收都是扫描所有页面,不需要额外解决跨代引用问题。
ZGC主打的是超低延迟与吞吐量,在实现时,ZGC也会在尽可能堆吞吐量影响不大的前提下,实现在任意堆内存大小下都可以把垃圾回收的停顿时间限制在10ms以内的低延迟
ZGC与GC收集器一样,也会存在「垃圾优先」的特性,在标记完成后,整个堆中会有很多分区可以回收,ZGC也会筛选出回收价值最大的页面来作为本次GC回收的目标。
颜色指针可以说是ZGC的核心概念。因为他在指针中借了几个位出来做事情,所以它必须要求在64位的机器上才可以工作。并且因为要求64位的指针,也就不能支持压缩指针。
基于64位指针实现的染色指针技术,最大支持16TB JVM
ZGC通过多阶段的并发执行+几个短暂的STW阶段来达到低延迟的目的
低延迟
ZGC只会在处理根节点等阶段才会出现STW,而堆空间再怎么扩大,内存中的根节点数量不会出现质的增长,所以ZGC的停顿时间几乎不受限于内存大小
ZGC不会因为堆空间的扩大而增大停顿时间
ZGC与之前的收集器还有一点很大的不同在于:ZGC标记的是指针而并非对象,但最终达到的效果是等价的,因为所有对象以及所有指针都会被遍历。
ZGC的不分代其实是它的缺点,因为对象都是满足朝生夕死的特性,ZGC不分代只是因为分代比较难实现。
①奠定未来GC特性的基础。
UMA即Uniform Memory Access Architecture(统一内存访问),UMA也就是一般正常电脑的常用架构,一块内存多颗CPU,所有CPU在处理时都去访问一块内存,所以必然就会出现竞争(争夺内存主线访问权),而操作系统为了避免竞争过程中出现安全性问题,注定着也会伴随锁概念存在,有锁在的场景定然就会影响效率。同时CPU访问内存都需要通过总线和北桥,因此当CPU核数越来越多时,渐渐的总线和北桥就成为瓶颈,从而导致使用UMA/SMP架构机器CPU核数越多,竞争会越大,性能会越低。
UMA架构
NUMA即Non Uniform Memory Access Architecture(非统一内存访问),NUMA架构下,每颗CPU都会对应有一块内存,具体内存取决于处理器的内存位置,一般与CPU对应的内存都是在主板上离该CPU最近的,CPU会优先访问这块内存,每颗CPU各自访问距离自己最近的内存,效率自然而然就提高了。
但上述内容并非重点,重点是NUMA架构允许多台机器共同组成一个服务供给外部使用,NUMA技术可以使众多服务器像单一系统那样运转,该架构在中大型系统上一直非常盛行,也是高性能的解决方案,尤其在系统延迟方面表现都很优秀,因此,实际上堆空间也可以由多台机器的内存组成。
NUMA架构
ZGC是能自动感知NUMA架构并可以充分利用NUMA架构特性的一款垃圾收集器。
TB级别内存出处:NUMA架构
②为了支持超大级别堆空间(TB级别),最高支持16TB。
③在最糟糕的情况下,对吞吐量的影响也不会降低超过15%。
④GC触发产生的停顿时间不会偏差10ms。
ZGC最初是源于Azul System公司的C4(Concurrent Continuously Compacting Collector)收集器与PauselessGC,Java引入ZGC的目的主要有如下四点
大页存在两种尺度,分别为2MB以及1GB。
Linux内核引入大页的目的主要在于为了迎合硬件发展,因为在云计算、弹性调度等技术的发展,服务器硬件的配置会越来越高,如果再依照之前标准页4KB的大小划分内存,那最终反而会影响性能。
ZGC的内存结构实际上被称为分页,源于Linux Kernel 2.6中引入了标准的大页huge page
固定大小为2MB,用于分配小于256KB的对象。
小型区/页(Small)
固定大小为32MB,用于分配大于256KB小于4MB的对象
中型区/页(Medium)
因为Large区中只会存储一个对象,在GC发生时标记完成后,直接决定是否回收即可,Large区中存储的对象并非不能转移到其他区,而是没有必要,本身当前Large区中就只有一个大对象,转移还得专门准备另外一个Large区接收,但本质上转不转移都不会影响,反而会增加额外的空间开销。
为何Large区不能被重分配/转移呢?
没有固定大小,容量可以动态变化,但是大小必须为2MB的整数倍,专门用于存放>4MB的巨型对象。但值得一提的是:每个Large区只能存放一个大对象,也就代表着你的这个大对象多大,那么这个Large区就为多大,所以一般情况下,Large区的容量要小于Medium区,并且需要注意:Large区的空间是不会被重新分配的。
大型区/页(Large)
也会把堆空间划分为一个个的Region区域,但ZGC中的Region区不存在分代的概念,它仅仅只是简单的将所有Region(ZGC实际不叫Region,而是叫ZPage)区分为了大、中、小三个等级
动态创建和销毁,以及动态的区域容量大小
ZPage具有动态性
假设ZGC的一次完整GC需要八分钟,在这期间由于新对象的分配速率很高,所以堆中会产生大量的新对象,这些新对象是不会被计入本次GC的,会被直接判定为存活对象,而本轮GC回收期间可能新分配的对象会有大部分对象都成为了“垃圾”,但这些“浮动垃圾”只能等待下次GC的时候进行回收
ZGC最大的问题是浮动垃圾
ZGC问题
https://blog.csdn.net/weixin_45101064/article/details/123478022
ZGC
在JDK11中推出ZGC后,JDK12马不停蹄的推出了ShenandoahGC收集器,它与G1、ZGC收集器一样,都是基于分区结构实现的一款收集器。
ShenandoahGC也没有实现分代的架构,所以在触发GC时也不会有新生代、年老代之说,只会存在一种覆盖全局的GC类型。
它们的停顿时间都不会受到堆空间大小的影响
相同
ZGC是基于colored pointers染色指针实现的,而ShenandoahGC是基于brooks pointers转发指针实现
不同
和ZGC对比
也会将堆内存划分为一个个 大小相同的Region区域,也同样有存放大对象的Humongous区
你可以把ShenandoahGC看做G1收集器的修改版
它比G1收集器实现上来说更为激进,一味追求极致低延迟。
和G1比
在前面的ZGC中可以通过染色指针+读屏障的方案获取到最新的地址
BrooksPointers转发指针
ShenandoahGC
回收过程中,如果一个对象被复制到新的区域,用户线程通过原本指针访问时如何定位对象呢?
解决的问题
所谓的转发指针就是在每个对象的对象头前面添加了一个新的字段,也就是对象头前面多了根指针。对于未移动的对象而言,指针指向的地址是自己,但对于移动的对象而言,该指针指向的为对象新地址中的BrooksPointers转发指针,示意图如下
BrooksPointers转发指针原理:
ShenandoahGC核心-BrooksPointers转发指针
最大支持256TB JVM
在三款高性能的GC器中,就目前而言,唯一保留了分代思想的是G1,而ZGC、ShenandoahGC并非是因为不分代性能好一些而不实现的,而是因为实现难度大所以才没有实现,在之前就曾提及过:逻辑分代+物理分区的结构才是最佳的,所以不分代的结构对于ZGC、ShenandoahGC来说,其实是一个缺点,因为不分代的堆空间,每次触发GC时都会扫描整堆。
G1收集器在后续的JDK版本中一直在做优化,因为G1是打算作为全能的默认GC器来研发的,但G1收集器最大的遗憾和短板在于:回收阶段需要发生STW,所以导致了使用G1收集器的程序会出现不短的停顿。
而ZGC、ShenandoahGC两款收集器,前者采用了染色指针+读屏障技术做到了并发回收,后者通过转发指针+读写屏障也实现了并发回收。因此,使用这两款收集器的应用程序,在运行期间触发GC时,造成的停顿会非常短暂,所以如果你的项目对延迟要求非常低,那么它两个是很不错的选择。
不过ZGC由于承诺了最大不超过10ms的低延迟,所以最恶劣的情况可能会导致降低15%左右的吞吐量,因此,如果你想使用它,那么要做好扩大堆空间的准备,因为只能通过加大堆空间来做到提升吞吐量。同时,由于ZGC的染色指针使用了64位指针实现,所以也就代表着:在ZGC中指针压缩失效了,所以在32GB以下的堆空间中,相同的对象数据,ZGC会比其他的收集器占用的空间更多。
而ShenandoahGC因为额外增加了转发指针,所以也存在两个问题: ①访问对象时,速度会更慢,因为需要至少经过一次地址转发。 ②需要更多的空间存储多出来的这根指针。同时,ShenandoahGC是没有给出类似于ZGC的“最大10ms的低延迟”承诺,所以就现阶段而言,ShenandoahGC性能会比ZGC差一些,但唯一的优势在于:它可以比ZGC支持更大的堆空间(虽然没啥用)。
好像G1收集器压根比不上其他两款,但实际上并非如此,因为每款收集器都会有自己的适用场景,就好比在几百MB的堆空间中,装载ZGC就一定比G1好吗?其实是不见得的。因为G1中存在分代的逻辑,而ZGC是单代的,所以如果在分配速率较快的情况下,ZGC可能会跟不上(因为ZGC的整个GC过程很久),而G1则可以完全胜任。
高性能垃圾收集器(G1、ZGC、ShenandoahGC)总结
不区分新老生代
https://blog.51cto.com/u_11440114/5103211
CMS回收器问题
垃圾回收策略
https://blog.51cto.com/u_11812624/5462445
垃圾回收
https://blog.csdn.net/m0_71777195/article/details/126247090?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522166615991416800186577080%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=166615991416800186577080&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_ecpm_v1~rank_v31_ecpm-14-126247090-null-null.nonecase&utm_term=jvm&spm=1018.2226.3001.4450
JDK
Thrift
https://blog.csdn.net/chengzhi0371/article/details/100788739
https://blog.csdn.net/Com_ma/article/details/97134775
与thrift对比
gRPC
Motan
Dubbox
rpcx
RPC框架
https://blog.csdn.net/asdcls/article/details/121661651
各种RPC框架比较
RPC及序列化框架
基础篇
提供低延迟、高可用的内存KV数据库服务
提供中心化的服务故障发现服务
提供分布式场景下的锁、计数器、队列等协调服务
作用
通常有奇数个节点
每个节点存储的数据都相同
通过ZAB协议(由Paxos算法改进而来)保证数据的一致性
客户端连接zk集群时,只要能连到zk的任意一个节点,即可获得zk集群的所有节点信息
zk本身是一个高可用集群
持久节点
临时节点
可创建多个子节点
记录子节点创建的先后顺序
持久顺序节点
临时顺序节点
zk包含4类节点
节点内数据为树状结构,有父节点、子节点
节点
异步事件反馈机制
对应的watcher为子节点watcher
关注的事件有子节点的创建、删除等
getChildren
关注的事件有:节点数据发生更新、子节点发生创建、删除操作等
exists、getData
由客户端watch zk节点的如下操作
watch事件发生后,服务器会向客户端发送事件
客户端收到事件后,执行相应的处理(可编程)
zk客户端与服务端的连接是长连接
客户端收到watch事件后,必须再次watch才能收到后续事件
zk客户端和服务端存在一个类似HTTP服务的session机制,每次请求均会带着session标识来标记本次session
session可以设置超时时间
连接断开后,session不会自动断开,除非session超时
session机制
watcher机制
核心特性
1. 客户端在临时顺序节点上创建子节点
2.1.1 执行后续业务操作(该业务操作一般是多个客户端必须串行执行的业务逻辑,否则会导致业务不一致)
退出当前session时zk服务器会自动向所有连接它的客户端发送子节点移除事件
2.1.2 业务执行完成后,退出当前session,业务结束。
2.1 若自己创建的节点是最小节点,则认为成功获得锁,
当监控到子节点移除事件(说明之前拿到锁的客户端释放了锁)后,跳转到步骤1,直到拿到锁,执行2.1.2执行业务逻辑并退出为止
2.2 若不是最小节点,则说明没拿到锁,则watch该节点的子节点移除事件
2. 创建后查询
分布式锁
使用场景
zookeeper
块大小默认为128M
一个HDFS文件由一个个块(Block)组成
N可配
每个块会设置N个副本
数据存储
文件大小
拥有者
组
各个用户的访问权限
1. 文件属性
2. 文件的数据块的节点分布
元数据组成
存储并管理HDFS文件系统的元数据
NameNode本质上是一个独立的维护所有文件元数据的高可用KV数据库系统
EditLog即为WAL:每一次文件元数据的写入,先做一次EditLog的顺序写,然后再修改NameNode的内存状态
NameNode内部有一个线程,定期将内存持久化成FSImage
持久化成FSImage后,该时间点之前的EditLog可以被清理,这个过程叫checkpoint
采用EditLog和FSImage方式保证元数据的高效持久化
如何保证数据不丢失
HDFS会把所有文件的元数据维护在内存中
若存储大量小文件,则造成分配大量的Block,会耗尽NameNode的内存而OOM
后续版本的HDFS支持NameNode对元数据分片,解决了NameNode的扩展性问题
HDFS不适合存储大量小文件,原因
Active NameNode 一个
作为Active的备份
不对外提供服务
ZKFailoverController检测到Active NameNode发生异常时,会把StandBy NameNode切换成Active
StandBy NameNode一个
需要部署两个NameNode
部署
NameNode
当StandBy NameNode切换为Active NameNode时,必须保证新的Active NameNode的数据与之前一致
JournalNode就是用来维护EditLog一致性的Paxos组
类似zookeeper,线上一般部署奇数个,建议3个或5个
Active和Standby NameNode之间,保证数据一致性的组件(是一个服务)
JournalNode
实现NameNode的字段切换
ZKFailoverController
元数据维护类组件
组成文件的所有Block都存储在DataNode上
一个逻辑上的Block会放放在N个不同的DataNode上
主要传输数据,所以主要消耗网络带宽和磁盘资源,即I/O密集
而Hbase的RegionServer主要是计算密集型,消耗CPU和内存
因此通常将RegionSever和DataNode部署在一起
计算非密集:CPU和内存消耗很少
资源消耗
DataNode
数据存储类组件
高可用HDF集群主要由4个服务(两类作业)组成
1. create
2. write
写WAL是不是也写入操作系统Cache?这样无法保证WAL的持久性
问题:
保证WAL持久
有WAL持久,数据怎么会丢失呢?
3个副本都写入,但可能在各自的操作系统Cache中,并未持久,有丢数据的风险
hflush
hflush基础上,数据持久,不会丢失数据
hsync
hflush和hsync区别
p43,看不懂???
用hflush保证数据WAL持久性
如何保证数据不丢失(高可靠性)
3. hflush或hsync(可配))
4. close
写入流程
1. 请求NameNode,获取指定文件的块信息,以及这些块都在哪些DataNode上
2. 连接各个DataNode,读取块数据
步骤
对满足可本地读的数据,开启短路读(可设置是否开启),本地读写,不走网络,性能提升非常大
该文件的块数据在当前DFSClient所在的DataNode上的字节数/文件总的字节数
locality
短路读
Hbase读取文件性能影响因素
读取流程
文件写入/读取流程
HDFS擅长的场景是大文件顺序写、顺序读、随机读
不支持随机写以及多个客户端同时写一个文件
HDFS
GFS
MapReduce
Hbase
BigTable
Google三论文
逻辑模型:表
即rowid长度不能超过2^16=65536
2B
rowid
即列族长度不能超过2^8=256字符
1B
列族
列名(qualifier)
用于多版本控制
8B:时间戳的long值
倒序排序
值越大,排序越靠前
时间戳
PUT/DELETE/Delete Column/Delete Family
1字节
类型
Key的五大组成部分
按列族存储,即一个列族的数据存储在一起
Key比value占用更大的空间,因此key长度越少越好,即rowid、列族、列名的值越小越好
按列存储
物理模型:Map
数据模型
RDB的空值列需要存储空间,其值填充为null,造成存储大量浪费且性能低下
一行表若某列无值,则不需要存储,不占用存储空间。
节省空间,查询速度快
因此,稀疏性是Hbase的列可以无限扩展的一个重要条件,可以达到上百万列
稀疏
表分布在整个集群中,而不是集中存储在某台机器上
分布
持久
Map Key由五大部分组成
多维
先比较rowkey,字典升序
若rowkey相同,再比较列族:列名,字典升序
比较时间戳,即版本,倒序排序
根据key的五大组成部分排序
排序
Hbase五大特症
无论PUT还是DELE,写入的都并非数据本身,而是操作记录,如PUT/DELETE一条rowid为1的记录
无论PUT还是DELE,落盘时都是Append操作,没有任何随机写操作
LSM对写入是极为友好的索引结构,能将磁盘的写入贷款利用到极致
但对读取操作非常不利,需要做多路归并(compact)
LSM(Log-Structured Merge-Tree)
跳跃表(又叫跳表,SckipList)
是一个ConcurrentSckipListMap
key就是Map Key五部分
value是一个字节数组
内存部分即MemStore
写入时,先写入MemStore
数据flush到磁盘后,释放对应的内存空间
当MemStore写满后,该MemStore就会被标记为Snapshot标记,并禁止写入
在内存开辟一块新的空MemStore,存储后续写入的数据
为避免flush影响性能,flush操作是异步的
当MemStore写满(阈值可配)时,会flush到磁盘,生成一个数据块文件
内存部分
keyLen(4B)|valueLen(4B)|rowkeyLen(2B)|rowkeyBytes|familyLen(1B)|familyBytes|qualifierBytes|timestamp(8B)|type(1B)valueBytes
结构
由一个个操作日志组成
由一个个独立的文件组成
每个文件是一个LSM块
因为需要将那些key相同的数据全局综合起来,最终选择出合适的版本返回给用户
一旦用户有读取请求,则需要将大量的磁盘文件进行多路归并,之后才能读取到所需的数据
对读取操作极为不利,影响读取性能
磁盘文件数量越多,读取的时候随机读取的次数也会越多,从而影响读取性能
设置一个策略,让多个hfile进行多路归并(compact),合并成一个文件
文件个数越少,随机读写操作次数越少,读取性能越好
原理
合并后只有一个文件,读取性能最高
因此Major Compact不宜太频繁,时候周期性(如每周一次,夜间执行)地运行
合并操作时间很长,并消耗大量I/O带宽
将所有的Hfile一次性多路归并成一个文件
major compact
I/O较少,适合较高频率地执行
优点
部分comcat,性能没有major compact后性能高
无法彻底清除delete操作,无法保证数据的最小化
选中少数几个hfile进行多路归并
minor compact
两种归并类型
多路归并(compact)
随着不断写入磁盘,文件(数据块)数量越来越多
磁盘部分
LSM分两部分
设定一个数组,长度为N,每个元素的值要么为0,要么为1
数组长度为N,每个数组元素的值要么为0,要么为1
K表示一个固定整数
设定一个长度为N的数组和一个整数K
即w会在Array的K个元素位置设置为1
for (i in 0..K){ Array[hash(w)%N] =1}
对于要存储的集合c中的每一个元素w
只要有一个位置不为1,则m一定不在集合c中
这种情况下,需要进一步比对c的内容才能进一步确定m是否在c中
误判率即布隆过滤器判定m可能在集合c但实际不在c中的占比
论文证明,当N=K*|c| / ln2时,误判率最低 (|c|为集合元素个数)
全部为1,则m可能(但不=确定)在集合c中
查看数组中,对应的位置是否也为1
用于检索指定的元素m是否在集合c中
布隆过滤器占用占用极少的空间
可迅速给定某个(而非一批)元素m一定不在集合m中或可能在集合m中的判断,性能极高
若判断某个元素m一定不在集合中,就不需要进一步比对数据内容,可大大节省磁盘I/O,提高效率
每一个LSM文件(数据块)对应一个布隆过滤器
关闭布隆过滤
NONE
对指定rowkey的get操作,可充分利用布隆过滤的特点,判断元素是否存在LSM数据块中,极大提升get效率
get操作必须带rowkey,因此可以设置为默认值
按照rowkey来计算布隆过滤器的二进制串并存储
ROW
get操作需同时指定这三个字段才可提升性能
按照rowkey+family+qualifier这三个字段拼出byte[]来计算布隆过滤器的二进制串并存储
ROWCOL
在Hbase 1.x中,用好可以对某些列设置不同的布隆过滤器,共有3种类型
1. 基于ROWCOL类型设置布隆过滤,当scan操作在同一行内切换下一列数据时,由于rowid+family+column确定,此时可以通过布隆过滤器迅速判断数据是否存在,因此可以优化scan性能
如设置rowkey=userid-其它字段,scan时会以userid作为过滤条件进行扫描
设置rowkey=在scan时将会作为过滤条件的、定长业务字段作为前缀
长度与userid相同,即提取userid部分作为布隆过滤器
取rowkey的固定长度(长度与业务字段长度相同)计算布隆过滤器
用户执行scan操作时,将业务字段作为过滤条件
方法
可借助布隆过滤器,提升scan操作的性能(可提升一倍以上)
2. 前缀布隆过滤器:
但在某些特定场景下,scan操作可以借助布隆过滤器提升性能
对普通的scan操作,由于操作的是一批数据,rowkey不确定,因此无法利用布隆过滤器特性来提升扫描性能
Hbase如何利用布隆过滤器
布隆过滤器
HBase基于HDFS
LSM结构正好满足这一要求
HBase为什么选择LSM
一个列族就是一个LSM树
HBase相关数据结构
基础概念
client
active master通常只有一个,当该active master发生异常时,zk会选举出一个slave master作为active master
实现master的高可用
hbase:meta记录整个hbase集群的Region信息
管理系统核心元数据hbase:meta
表定义
表分区(Region)及所在的RegionServer
存储了所有用户业务表的元数据
zk通过心跳可感知到RS是否宕机,并在宕机后通知master进行宕机处理
RegionServer宕机异常检测
Hbase对表进行各种管理操作(如alter操作)需要先对表上锁
分布式表锁
zk集群的地址
hbase.zookeeper.quorum
默认为2181
hbase.zookeeper.property.clientPort
master收到通知后,会将该RS移出集群,并将该RS上的所有Region迁移到集群中其它RS上
一旦发生超时,zk会认为RS宕机,进而通知master
表示zk与RS之间的会话超时时间
zookeeper.session.timeout
默认值:/hbase
zookeeper.znode.parent
通过conf/hbase-site.xml配置zk
hbase容许针对不同的业务设计不同的namespace
系统表采用统一的namespace,即hbase
前缀 hbase表示namespace
meta表示表名
hbase:meta本身也是一个Hbase表,hbase:meta记录整个hbase集群的Region信息
业务表名
tableName
业务表Region区间的起始Rowkey
startRowkey
Region创建时间戳
timestamp
上面3个字段的MD5 hash值
EncodedName
Rowkey组成:
EncodeName
RegionName
StartRow
StopRow
存储4个信息
regioninfo
存储Region打开的sequenceId
seqnumDuringOpen
Region所在的RegionServer
server
存储所在RegionServer的启动时间
serverstartcode
列族:只有一个:info,包含的列有
https://blog.csdn.net/qq_44665283/article/details/125719969
hbase:meta表定义
每行记录是一个Region信息
因为hbase本质上只支持Region级别的事务
为了确保meta表多次操作的原子性
只有一个Region的原因
并且该表只存储在一个(而非多个)RegionServer上,具体在哪个Regionserver存储在zk的该节点
hbase:meta只有一个Region
为提高性能,hbase:meta表的所有数据全部保存在内存中,大小限制为128M
大量客户端连接并访问hbase:meta,所在的RegionServer会成为热点
客户端首次连接Hbase时,从Hbase服务器获取hbase:meta到客户端本地,并缓存起来(缓存到MetaCache中),之后基于本地缓存查找rowkey所在的Region
hbase:meta设计存在的问题及解决方案
1. 从zk meta-region-server节点,获知hbase:meta表存储在哪个RegionServer上
2. 连接该RegionServer,获取hbase:meta表信息到客户端本地缓存起来
说明rowkey真的不存在,返回给业务:rowkey不存在
1. 1还是找不到
1.2. 找到了,跳转到2
连接Hbase集群,从服务器端重新获取hbase:meta,再次比对
1. 找不到 可能本地MetaCache已过期
1. 还是找不到
2. 找到了,跳转到2.2
2.1 RegionServer返回rowkey不存在,说明本地MetaCache可能已经过期
找到数据,返回数据给业务
2.2
2. 找到了,连接对应的RegionServer
3. 根据业务rowkey,与本地MetaCache比对,查找当前业务rowkey所在的region及所在的regionServer
客户端根据rowkey查找业务表数据过程
即hbase:meta表存储在哪个meta-region-server上
meta-region-server
存储多个,其中一个为active,其它为slave。利用zk选举机制实现master的高可用
backup-masters
但一个Region包含哪些HFile及对应存储路径,这些存储在哪里?
问题:一个表有多少region,该Region在哪个RegionServer上存储在hbase:meta表中
集群中所有表信息
table
region-in-transition
table-block
master
balancer
namespace
hbaseid
online-snapshot
replication
split-WAL
recovering-regions
集群中所有运行的RegionServer
rs
/hbase
HLog(1)
读缓存,读取数据时,先将数据读取到BlockCache
BlockCache(1)
读写缓存
MemStore(1)
HFile(*)
Store(*)
Region(*)
RegionServer(*)
组件构成
master、RegionServer、Region均是服务,而不是数据
数据存储在HDF中,哪些Region管理哪些数据,在hbase:meta表中存储
说明
相关组件
用户执行snapshot后,相关的snapshot元数据文件存储在该目录
snapshot文件存储目录
.hbase-snapshot
表创建先在tmp目录下执行,执行成功后再将tmp目录下的表信息移动到实际表目录下
表删除操作会讲表目录移动到tmp目录下,一定时间后会再将tmp目录下的文件真正删除
主要用于hbase表的创建和删除操作
临时文件
.tmp
Master Procedure功能主要用于可恢复的分布式DDL操作
存储Master Procedure过程中的WAL文件。
MaserProcWALs
存储集群中所有RegionServer的Hlog日志文件
WALs
所有对HFile文件的删除操作都会将待删除文件临时放在该目录
进行snapshot或者升级时使用到的归档目录
Compaction删除HFile的时候,也会把旧的Hfile移动到这里
文件归档目录
archive
存储损坏的HLog或HFile文件
corrupt
/hbase/${hbaseClusterName}/data/命名空间/表名/Region名称/列蔟名/文件名
举例:hbase/hbase-nptest/data/default/usertable/1a1365282ac023d8wewe98we887/famiy/1053d223lysd23s32sd66
HFile文件在data目录下的完整路径为
集群中所有Region的HFile数据
表描述文件,记录对应表的基本schema信息
.tabledesc
以flush为例,MemStore落盘形成的HFile先生成到该目录,完成后再移动到对应的实际文件目录
用来存储flush和compaction的中间结果
Region描述信息
.regioninfo
RegionServer宕机后,该节点上还没来得及flush到磁盘的数据需要通过WAL回放恢复
WAL会先按Region进行切分,每个Region拥有对应的WAL数据片段
每个Region回放时,只需要回放自己的WAL数据切片即可
存储故障恢复时,该Region需要回放的WAL日志数据
recovered.edits
子目录
data
集群启动初始化时,创建的集群唯一ID
hbase.id
hbase软件版本号
hbase.version
WAL归档目录,一旦WAL中对应的数据持久化到HFile,那么该WAL文件会被移动到该目录
oldWALs
${hbaseClusterName}
Hbase集群的文件目录结构(HDFS文件系统)
https://blog.csdn.net/u013411339/article/details/90657429
Phoenix
插件
https://blog.csdn.net/weixin_43930865/article/details/121170645
相关代码
HBaseTestingUtility
代码调试
hbase-site.xml
core-site.xml
hdfs-site.xml
包含三个配置文件
Configuration configuration = HbaseConfiguration.create()
HBaseTestingUtility提供了API获取:Configuration configuration = hBaseTestingUtility.getConfiguration()
获取Hbase Configuration
集群的配置(对客户端来说)
admin.tableExists(TableName.valueOf(tableNameStr));
Connection conn = ConnectionFactory.createConnection(conf);Admin admin = connection.getAdmin()
通过Admin对象管表
admin.createTable
admin.tableExists
TableName table = TableName.valueOf(tableName); if (admin.tableExists(table)) { //先禁用后删除 admin.disableTable(table); admin.deleteTable(table); }
admin.deleteTable
admin.addColumnFamily
admin.deleteColumnFamily
管理操作
管理表
Connection conn = ConnectionFactory.createConnection(conf);TableName tablename = TableName.valueOf(tableNameStr);if (admin.tableExists(tablename)) { Table table = connection.getTable(tablename); }
通过table对象管理数据
table.put(List<Row> puts)
table.exists(Get get)
table.get(get)
table.getScanner
数据管理操作
管理表数据
两类操作
Hbase客户端的Scan操作应该是比较复杂的RPC操作,为了满足客户端多样化的数据库查询需求,Scan必须能设置众多维度的属性。常用的有startRow、endRow、Filter、caching、batch、reversed、maxResultSize、version、timeRange等
用户每次执行scanner.next(),都会尝试去名为cache的队列中拿result(步骤4),如果cache队列为空,则会发起一次RPC向服务端请求当前scanner的后续result数据(步骤1)。客户端收到result列表后(步骤2),通过scanResultCache把这些results内的多个Cell进行重组,最终组成用户需要的result放入到Cache中(步骤3)。其中步骤1+步骤2+步骤3统称为loadCache操作。
这是因为RS为了避免被当前RPC请求耗尽资源,实现了多个维度的资源限制(例如timeout、单次RPC响应最大字节数),一旦某个维度资源到达阈值,就马上把当前拿到的Cell返回给客户端。这样客户端拿到的result可能就不是一行完整的数据,因此在步骤3需要对result进行重组。
为什么要在步骤3对Rpc Responce中的result进行重组呢?
每次loadCache操作最多放caching个result到cache队列中;
避免出现某一次scanner.next。操作耗时极长的情况
caching
用户拿到的result中最多含有一行数据中的bacth个cell
batch
allowPartial
loadCache时,单次RPC操作最多拿到maxResultSize字节的结果集。
maxResultSize
scan中的几个重要的概念
scan操作
单独用性能低下
Scan scan = new Scan();scan.withStartRow(Bytes.toBytes(\"def\"));scan.setFilter(new PrefixFilter(Bytes.toBytes(\"def\")));
1. 用startRow+PrefixFilter
Scan scan = new Scan();scan.withStartRow(Bytes.toBytes(\"def\"));scan.withStopRow(Bytes.toBytes(\"deg\"));
2. 更好的方法:不用PrefixFilter
优化
PrefixFilter
filter
少量写和批量写
表示单次RPC请求的超时时间,一旦单次RPC超过该时间,上层将收到TimeOutException,默认时间为60000ms.
hbase.rpc.timeout
表示调用API时最多容许发生多少次RPC重试,默认为35次。
第1次 RPC重试 100ms第2次 RPC重试 200ms第3次 RPC重试 300ms第4次 RPC重试 500ms第5次 RPC重试 1000ms第6次 RPC重试 2000ms按照默认配置将会卡在休眠和重试两个步骤。
若按照默认的hbase.client.retries.number=35,客户端可能长期卡在休眠和重试两个步骤中
重试策略
表示连续两次RPC重试之间的休眠时间,默认为100ms
hbase.client.pause
注意,get/put/delete等表操作称为一次API操作,一次API可能会有多次RPC重试,这个operation.timeout限制的是API操作的总超时。
表示单次API的超时时间,默认值为1200000ms
hbase.client.operation.timeout
相关参数
根据业务指标要求,合理设置上述参数
如何设置
超时设置
客户端
客户端将写入请求进行预处理,根据集群元数据hbase:meta定位待写入数据所在的Region及Region Server,并将请求转发给对应的RegionServer
主要内容
1.客户端处理阶段 p94
Region server接收到写入请求之后,将数据解析出来,首先写入HLog(WAL),再写入对应Region列蔟的MemStore
1. Aquire Locks
2. Update Latest Timestamp timestamps
3. Build WALEdit
默认1个
hbase 1.1 可以开启MultiWAL功能,允许多个HLog
每个RegionServer有一个或多个HLog
因此一个HLog中可能包含多个表及多个列蔟的数据
每个HLog是多个Region共享的
一个WALEntry表示一次行级更新的最小追加单元
没有列蔟
sequenceid是自增序号。很好理解,就是随着时间推移不断自增,不会减小。,不同region的sequenceid相互独立。
sequenceid是一次行级事务的自增序号。行级事务是什么?简单点说,就是更新一行中的多个列族、多个列,行级事务能够保证这次更新的原子性、一致性、持久性以及设置的隔离性,HBase会为一次行级事务分配一个自增序号。
sequenceid是 region级别 的自增序号。每个region都维护属于自己的sequenceid
sequenceid是region级别一次行级事务的自增序号。每个region都维护属于自己的sequenceid,不同region的sequenceid相互独立。
参见:https://www.modb.pro/db/99815
由TableName、Region name以及sequence id组成
HLogKey
一次行级事务可以原子操作同一行中的多个列(多个列可以是不同的列蔟),一次事务用一个sequenceid表示
一个WALEdit包含多个KeyValue
表示一个事务中的更新集合
WALEdit
WALEntry组成部分
一个HLog对象由多个WALEntry组成
文件结构(p64)
文件名格式RegionServer域名%2cRegionServer端口%2c文件生成时间戳.default(命名空间?).时间戳
举例:
存储目录及文件格式(p65)
./hbase hlog
查看HLog命令
Hbase任何写入(创建、更新、删除)都会先将记录追加到HLog文件中
1. 构建
日志滚动会新疆一个新的日志文件,接收新的数据
目的主要是为了方便过期日志数据能够以文件的形式直接删除
Hbase后台启动一个线程,定期(hbase.regionserver.logroll.period参数,默认1小时)进行日志滚动。
2. 滚动
写入到HLog的数据,会同时写入内存MemStore,一旦数据从MemStore落盘,变成HFile,对应的HLog文件就会失效
一旦HLog文件失效,就会从WALs目录移动到oldWALs
3. 失效
1. 该HLog是否还在参与主从复制,若参与,先不删除
2. 该HLog是否已经在oldWALs目录中存在是否超过10分钟(通过hbase.master.logcleaner.ttl配置,默认10分钟),超过则删除
确认条件
master后台启动一个线程,定期(hbase.master.cleaner.interval参数,默认1分钟)检查一次oldWALs目录,确认是否可以删除
4. 删除
HLog生命周期(P66)
只写MemStore,不写HLog文件,相当于是否写HLOG的总开关
SKIP_WAL
异步将数据写入HLog文件
ASYNC_WAL
但执行的是flush操作,只写入文件系统中,并没有真正落盘
同步将数据写入HLog文件
SYNC_WAL
同步将数据写入HLog文件并强制落盘
FSYNC_WAL
由用户指定持久化等级,若没有指定,默认使用SYNC_WAL
USER_DEFAULT
持久化HLog等级(在可靠性与性能之间做取舍) p97
HLog说明
先将一个个的WALEntry放入队列
最后将wal.sync事件放入队列,之后工作线程阻塞,等待消费者通知解除阻塞
生产者
此时可能没有实际落盘,只是加入到文件缓存
消费WALEntry,然后执行文件的append操作追加到日志文件中
执行数据落盘操作,并唤醒生产者工作线程取消阻塞
消费wal.async事件
消费者
生产者、消费者模式
基于LMAX Disruptor框架的无锁有界队列模型实现
HLog数据写入实现机制 p97
1. 追加写入HLog(即WAL) p97-p98
4. Append WALEdit to WAL
ConcurrentSkipListMap
非常友好地支持大规模并发顺序写入
跳表的有序存储,有利于数据有序落盘,且有利于提升查找性能
跳跃表的查找、删除、插入的复杂度都是O(logN)
内存中是基于JDK提供的跳跃表ConcurrentSkipListMap(简称CSLM,该对象实际被包装在CellSet中),即LSM的内存部分
许多KeyValue对象在新生代来不及销毁进入老年代,等等FullGC时才会被销毁,会不断积累FullGC的次数,性能严重低下
当再次申请KeyValue时,找不到能容纳该对象的连续内存空间,频繁触发FullGC
KeyValue不断申请和释放,产生大量、非常小(一个KeyValue对象大小为120Byte)的内存碎片
问题:容易产生FullGC
内存碎片粗话,足够容纳KeyValue对象所需的内存(比如让空闲碎片空间>2M),即MSLAB
解决思路
1. 所有KevValue都在JVM中申请和释放
每个MemStore会实例化得到一个MemStoreLAB对象
MemStoreLAB会申请一个2M(比一个KeyValue对象所需的内存空间120Byte大的多)大小的Chunk数组,同时维护一个Chunk偏移量,该偏移量初始值为0。
当一个KeyValue值插入MemStore后,MemStoreLAB会首先通过KeyValue.getBuffer()取得data数组,并将data数组复制到Chunk数组中,之后再将Chunk偏移量往前移动data. length。
将KeyValue复制到Chunk中后,生成一个Cell对象(这个Cell对象在源码中为ByteBufferChunkKeyValue),这个Cell对象指向Chunk中的KeyValue内存区域,包含指向Chunk中对应数据块的指针、offsize以及length
将这个Cell对象作为Key和Value写入CSLM中。
原生的KeyValue对象写入到Chunk之后就没有再被引用,所以很快就会被Young GC回收掉。
当前Chunk满了之后,再调用new byte[2 1024 1024]申请一个新的Chunk。
MemStore flush之后,对应的chunk数据落盘,该Chunk被GC回收
实现思路
Chunk被GC回收,释放的空间连续性更强,碎片力度(2M)更粗,不易引发FullGC
一旦写满一个Chunk后,会在Edge区申请新内存,如果申请比较频繁就会导致JVM Edge区满,触发YGC
容易引发YGC
这个笔者查阅了很多资料都没有找到相关的说明,个人理解是因为Cell相对原生KeyValue来说占用内存小的多(一个Cell对象为40 Byte,是一个KeyValue对象(大小120 Byte)的1/3),可以一定程度上可以忽略
那Cell对象不会引起内存碎片?
MemStore flush之后,Chunk不被释放,而是交给chunk池管理,继续被复用
Chunk池化
2. MSLAB
1)系统创建一个Chunk Pool来管理所有未被引用的Chunk,这些Chunk就不会再被JVM当作垃圾回收。2)如果一个Chunk没有再被引用,将其放入Chunk Pool。3)如果当前Chunk Pool已经达到了容量最大值,就不会再接纳新的Chunk。4)如果需要申请新的Chunk来存储KeyValue,首先从Chunk Pool中获取,如果能够获取得到就重复利用,否则就重新申请一个新的Chunk。
不会产生大量碎片,较少出现YGC和FullGC
CSLM的跳跃表,用两个对象Node和Index对象构建LSM树,最下一层是Node对象,上面的几层是Index对象,除了创建Node节点,还会创建索引节点,创建了太多内部对象
创建大量对象
http://www.360doc.com/content/21/0329/21/13328254_969627458.shtml 中关于50M对象那几段内存的计算看不懂,但结论是内存占用多,对象多
128M MemStore都是一颗CLSM树,CLSM为实现LSM目的都是为了方便数据写入和查找操作,而LSM本身是内存和CPU开销都比较大的数据结构,而且创建了大量对象,这些对象占用了大量内存,大量挤占了真正有效的KeyValue内存空间,内存利用率低。
太多的内部对象,占用的内存多,内存利用率低
CLSM落盘后,内部创建的对象会被GC,随着大量Cell、Nde和Index对象的频繁创建和销毁,会产生较多的内存碎片
以上诸多因素,会引起比较严重的FullGC
CSLM的LSM实现机制,虽方便了插入和查找,但会导致许多问题
在内存中只开辟一小块CLSM写入数据,写满后转换成其它类型的只读的、方便查找、且节省内存的数据结构,如数组。
替换JDK自身的CSLM实现
解决办法,2个
3. MSLAB+ChunkPool
Hbase 2.x的实现
存入MutableSegment CSLM中的Cell对象也在chunk中申请
进一步优化
在MemStore中构造一小块基于CLSM结构的Segment,叫MutableSegment,大小2M(可配),接收写入数据
LSM结构转换成了数组结构,解决了CSLM本身生成太多无用内存(比如跳跃表最底层之上的所有节点的内存开销)、产生大量垃圾对象、内存利用率低和容易引发FullGC问题
因为CSLM的插入性能比数组好许多,数组插入会移动大量数据,因此不适合插入场景。
为什么MutableSegment内部不是数组而是CSLM
ImmutableSegment只读,数组结构同样支持二分查找,且查找性能比CSLM还要好
当MutableSegment数据写满后,将其转换为只读的数组结构(数组结构占用内存更小、内存利用率更高)的ImmutableSegment并按顺序放入FIFO队列(Pipeline对象),然后再初始化一个新的MutableSegment供后续数据写入
TTL过期数据
超过列蔟指定版本的列值
被用户删除的列值
归并过程可以做无效数据清理工作,无效数据包括
在内存中执行归并后,可以在内存中放更多有效的数据,大大提高内存的利用率,使MemStore占用的内存增长变慢,触发MemStore Flush的频率降低,大大提高MemStore内存使用率。
磁盘上的HFile 归并频率降低,无论Minor Comaction还是Major Compaction次数都会降低,节省磁盘和网络带宽
生成的HFile梳理少,读取性能提升
新写入数据在内存中停留的时间长,对那种写完立即读的场景,性能大大提升
同时,也会使得HFile数据量变大,磁盘上HFile数量会增长缓慢,进而带来以下收益
当队列中的ImmutableSegment达到一定数量后(可配,默认是2),对所有的ImmutableSegment执行Memory Compaction,将多个ImmutableSegment归并成一个ImmutableSegment,再次放入Pipeline队列,等待下次进一步归并。
如将ImmutableSegment直接编码成HFile格式,这样在flush时,直接把内存数据写盘即可,进一步提升flush的速度、吞吐量和稳定性
未来还可进一步优化:
4. CompactionMemStore (p268 15.2)
5. 利用堆外内存
阿里巴巴内部版本为了优化CSLM数据结构内存利用效率低所实现的一个新的数据结构。CCSMap数据结构的基本理念是将原生的ConcurrentSkipListMap进行压缩,压缩的直观效果如下图所示
解决MSLAB+ChunkPool的问题
解决目标
为Node对象创建一个索引数组,表示该对象在LSM树中各层的索引
1. 只有Node对象,去掉了Index对象,索引信息在Node对象上保存,节省了内存空间
既然阿里的CSMap已经将LSM中的cell放到ChunkPool了,那么是不是可以将KeyValue可以直接存入Node对象,让KeyValue随同Node对象一起放入chunk中,而无需将其单独放到ChunkPool中,也无需封装成Cell???
疑惑?
6. 阿里的CCSMap(CompactedConcurrentSkipListMap)
5. 上述几个MemStore的问题及解决办法
MemStore优化历程
减少碎片
每个chunk块默认大小2M
若池内没有可用的chunk,则从JVM申请一个chunk
MemStore申请内存时,从chunk中申请一个或多个chunk
MemStore落盘后,占用的chunk会被放入池中供后续MemStore使用,不用归还JVM,不会产生碎片,也没有GC发生
为防止因JVM内存不断申请和释放,产生过多内存碎片,而频繁导致JVM的Full GC和YGC,采用了MSLAB方式,并在内存中申请许多大的chunk块,并将这些chunk用chunk池来管理
内部会维护两个跳跃表ConcurrentSkipListMap,当需要刷盘时,先把当前ConcurrentSkipListMap冻结,生成snapshot,作为flush的输入。同时创建一个新的ConcurrentSkipListMap接收后续的写入数据
http://www.360doc.com/content/21/0329/21/13328254_969627458.shtml
MemStore数据结构 p67
检查当前chunk是否写满,若已写满,则从chunk池(若池已满,则从JVM中)申请新chunk
将当前数据(KeyValue对象)写入chunk中指定的offset处
将当前的KeyValue对象(地址)写入跳跃表ConcurrentSkipListMap中
MemStore写入流程
2. 随机写入MemStore p98中
5. write back to MemStore
6. Release Row Locks
7. sync wal
参见: http://t.zoukankan.com/dailidong-p-7571245.html
为了写数据期间实现读写一致性,需要一致性控制,采取的机制是MVCC (Multiversion Concurrency Control)
8. Advance MVCC
主要步骤(p95 2.Region写入阶段图)
2. Region写入阶段 p95
当MemStore达到触发条件,会异步执行flush操作,将内存中的数据写入文件,形成HFile
Region中的任意一个MemStore大小达到了上限(hbase.hregion.memstore.flush.size,默认128MB)
1. MemStore级别限制
当Region中所有Memstore的大小总和达到了上限(hbase.hregion.memstore.block.multiplier * hbase.hregion.memstore.flush.size,默认 4 * 128M = 512M),整个Region中的所有MemStore都会flush。 (执行更新操作前,checkresource操作)
2. Region级别限制
RegionServer分配给MemStore的JVM堆内存比例,默认是40%
举例:JVM设置为10G, 则RegionServer分配给MemStore的所有内存为:10*40%=4G
1. hbase.regionserver.global.memstore.size
只有参数1, 则当一个RegionServer里面全部的memStore内存加一起,占到当前RegionServer内存的40%,就会触发flush操作,并且阻塞所有的memStore的写操作。但这样做有些不合理,在达到阈值前应该给一个缓冲的余地,避免直接进入阻塞状态。因此设计参数2
默认95%,相当于上一个参数的95%
这个参数就会在参数1的基础上再乘0.95,得出的值为6.08G,当全部的memStore占用内存达到这个数值时,就会开始flush操作,并且不会阻塞
若写入操作频繁,达到了JVM大小*参数1, 则会阻塞并强制刷盘,直到降低到JVM大小*参数1*参数2之下
2. hbase.regionserver.global.memstore.size.lower.limit
MemStore降低到JVM大小*参数1*参数2之下,停止刷盘
停止刷盘条件
当一个Region Server中所有Memstore的大小总和达到了上限时,触发flush
3. Region Server级别限制:
当一个Region Server中HLog数量达到上限(可通过参数hbase.regionserver.maxlogs配置)时,系统会选取最早的一个 HLog对应的一个或多个Region进行flush。
因为HLog的数量太多会影响容灾后的数据恢复效率,而当memStore里面的数据都flush到磁盘后,HLog会被清空。
设计原因
HLog文件占用内存空间吗?
HLog文件占用的内存空间如何设置?
不止HLog数量过多会触发,当HLog文件占用的内存空间达到阈值时,也会触发memStore的flush操作。需要注意的是,如果增大了memStore的内存,那么也要增大HLog文件的内存占用阈值,否则HLog文件达到阈值直接触发flush,而memStore还远没有达到阈值,这样增大memStroe的内存就没有意义了。
4. Region Server中HLog数量或内存占用空间限制
默认周期为1小时,确保Memstore不会长时间没有持久化。
考虑到可能会有多个memStore同时达到了一小时的时间阈值,都需要进行flush操作,为了避免同时进行,导致资源被大量占用。定期自动刷写策略里面有一个延迟机制,memStore不会立刻开始flush,而是随机等待0到5分钟之后才会开始flush操作
5. HBase定期刷新Memstore
用户通过shell命令之下 flush 'tablename',flush 'regionname'分别对一个表或一个region进行flush
6. 手动flush
如put/delete等
7.数据更新操作引起
1. MemStore Flush的触发条件 p99下
Hbase为每一个Region而不是每一个MemStore维护了一个自增的sequenceid
每次写HLog时,都会写入数据所在Region的自增sequenceid
每次flush所生成的所有HFlie中都存储同一个sequenceId
MemStore数据落盘后,Hbase会记录并将已落盘的数据对应的最大的SequenceId(叫做oldestUnflushedSequenceId)存储到HLog中
假如某个表有两个列族,某次写入了两个列蔟的数据,那么在执行写入时,从业务上希望该行数据保持事务一致性,即两个列蔟中的列要么都写入成功,要么都写入失败
若以列蔟为单位记录sequenceid,则无法保证事务
个人理解:要保证行级事务
为什么不以MemStore(即列蔟)为单位?
由于Hbase以Region为单位,而不是以MemStore为单位维护oldestUnflushedSequenceId,因此每次flush时,必须刷新该Region下的所有MemStore,无论该MemStore是否已满
原因
参见:https://www.it1352.com/2269279.html
以Region为单位进行flush。只要Region中有一个MemStore触发了flush,则这个Region中的所有MemStore(无论是否写满)都需要flush
2. flush操作粒度
为什么要将
遍历触发了flush操作的MemStore所在的Region中的所有Memstore,将Memstore中当前数据集CellSet(内部是CSLM)做一个快照snapshot,然后再新建一个新的kvset。后期的所有写入操作都会写入新的CellSet中,而整个flush阶段读操作会首先分别遍历CellSet和snapshot,如果查找不到再会到HFile中查找。prepare阶段需要加一把updateLock对写请求阻塞,结束之后会释放该锁。因为此阶段没有任何费时操作,因此持锁时间很短。
对触发了flush操作的MemStore所在的Region中的所有Memstore生成snapshot
prepare阶段
顾名思义,表示顺序扫描HFile时所有的数据块将会被读取,包括DataBlock、Leaf Index Block和Bloom Block
Scanned block section
表示在HFile顺序扫描的时候数据不会被读取,主要包括Meta Block和Intermediate Level Data Index Blocks两部分。
Non-scanned block section
这部分数据在RegionServer打开HFile时直接加载到内存中。包括FileInfo、布隆过滤器MetaBlock、Root Data Index和Meta Index Block
Load-on-Open
大小固定
主要记录了HFile的版本信息、其它各部分的偏移量和寻址信息、文件大小(未压缩)、压缩算法、存储KV个数等信息
Trailer
HFile文件主要分为四个部分
大号的Block有利于顺序Scan,小号Block利于随机查询,因而需要权衡
Block是HFile文件读取的最小单元
HFile由不同类型的块组成,快大小可以在创建表列蔟的时候指定(blocksize=>65535),默认为32M。
HFile中各块的一对多关系见p73上
最核心的字段是BlockType字段,用来标示该block块的类型,HBase中定义了8种BlockType(p75表格),每种BlockType对应的block都存储不同的数据内容,有的存储用户数据,有的存储索引数据,有的存储meta元数据
主要存储block元数据
BlockHeader
来存储具体数据
BlockData
主要包括两部分
对于任意一种类型的HFileBlock, 都拥有相同结构的BlockHeader , 但是BlockData结构却不相同
这些块的类型不同,但却有相同的数据结构HFileBlock。
Block
写入的KV数据可以压缩,尤其是rowkey部分(p102M)
在Scanned block section区
KV数据,放到DataBlock中
HFile越大索引树层数越多
HFile越大,DataBlock越多,需要建立索引以加快文件查找速度
一条索引项指向一块DataBlock,而非一条KV
Root Data Index、Intermediate Level Data Index以及Leaf Index在不同的区
DataBlock的索引,树状索引(树状),便于从HFile中定位Block。包括Root Data Index、Intermediate Level Data Index以及Leaf Index,分别存储在对应的三种类型的Block中
Scanned block section区
存储到Bloom Block中
HFile文件越大,里面存储的KeyValue值越多,位数组就会相应越大。一旦位数组太大就不适合直接加载到内存了,因此HFile V2在设计上将位数组进行了拆分,拆成了多个独立的位数组(根据Key进行拆分,一部分连续的Key使用一个位数组)。这样,一个HFile中就会包含多个位数组,根据Key进行查询时,首先会定位到具体的位数组,只需要加载此位数组到内存进行过滤即可,从而降低了内存开销。
为什么有多个而非一个?
HFile中有多个布隆过滤器位数组
KV数据的布隆过滤器
便于查找布隆过滤器
为什么有布隆过滤器索引
在Load-on-Open区
布隆过滤器MetaBlock
存储位置
一个BloomBlock可能包含多个布隆过滤器
每一个Bloom Index Entry指向存储在BloomBlock中的某个布隆过滤器(位数组)
找到布隆过滤器维数组的位置:该索引项在哪个BloomBlock中的哪个偏移处
BlockOffset
位数组长度,从BlockOffset开始,提取BlockOndiskSize长度的数据,即为维数组
BlockOndiskSize
因为HFile 中的rowKey是按字母排序的,第一个执行布隆过滤器Hash映射的rowKey一定是这个布隆过滤器对应的KV集合中最小的那个rowKey
是一个非常关键的字段,表示该Index Entry指向的布隆过滤器位图中,第一个执行Hash映射的rowKey
BlockKey
BlockKey的长度
BlockKeyLen
Bloom Index Entry 结构 p78图
用客户请求的rowKey与Bloom Index Entry的BlockKey进行比对
继续比对该Bloom Index Entry节点的右侧子树
若rowKey>BlockKey
继续比对继续比对该Bloom Index Entry节点的左侧侧子树
若rowKey<BlockKey
某个BloomIndexEntry.BlockKey<=rowkey<该BloomIndexEntry的下一个BloomIndexEntry.BlockKey,则该BloomIndexEntry代表的布隆过滤器位数组被命中
命中条件
比对依据
1. 根据rowKey,通过二分查找法,比对rowKey布隆过滤器索引中的Bloom Index Entry,找到对应的布隆过滤器维数组
如果不是,表示布隆过滤器对应的KV集合中肯定不存在该rowKey
若找到则返回找到的数据给客户端,退出
若找不到,则说明客户请求的rowKey在Hbase中不存在,返回客户端null,退出
若是,则可能存在,需要进一步加载位图对应的KV集合逐个比对
2. 根据布隆过滤器计算规则,对rowKey做hash映射,根据映射结果在位图中查看是否都为1
get(rowKey)请求查找过程
布隆过滤器的索引
HFile包含的内容
HFile文件结构
HFile文件结构(p72-p84 5.4HFile)
遍历所有Memstore,将prepare阶段生成的snapshot持久化为临时HFile文件,临时文件会统一放到目录.tmp下。这个过程因为涉及到磁盘IO操作,因此相对比较耗时。
flush阶段
遍历所有的Memstore,将flush阶段生成的临时文件移到指定的ColumnFamily目录下,针对HFile生成对应的storefile和Reader,把storefile添加到HStore的storefiles列表中,最后再清空prepare阶段生成的snapshot。
commit阶段
为了减少flush过程对读写的影响,HBase采用了类似于两阶段提交的方式,将整个flush过程分为三个阶段
3. flush流程(p99)
触发Region级别的flush,只会阻塞对应Region上的写请求,阻塞时间较短,影响不大
触发RegionServer级别的flush,会阻塞该RegionServer上的所有写请求,直至MemStore数据量降低到配置的阈值内,耗时比较长,影响比较大
flush操作会阻塞用户的写请求,会对用户业务产生影响
flush操作的性能影响
重点问题
3. MemStore flush阶段 p98
数据写入流程(三个阶段)
数据读取流程
读写流程及相关内部组件架构
场景:比如系统上线时,有大量数据需要载入Hbase,通过API方式会使HBase承受不了巨大写入的内存和网络压力而崩溃
批量数据导入BulkLoad(p104 6.2)
Coprocessor
Compaction实现
负载均衡
复制
备份与恢复
运维
系统调优
二级索引
单行事务与跨行事务
Hbase开发与测试
高级话题
首次全量
持续增量
从非Hbase数据导入Hbase(如项目上线)
全量复制
增量复制
HBase->Hbase(如主从复制)
Hbase->非Hbase
备份容灾
Hbase数据复制或迁移
监控
数据过于离散影响性能
热点数据打到同一台机器上,造成机器过载
rowkey设计
一次scan流程走过的客户端、服务端各个组件
常见业务场景
大数据
分支主题
核心主题
0 条评论
回复 删除
下一页