一、YDB升级步骤
1.停止YDB服务
通过./stop-all.sh来停止服务(注:HDP版本需要在管理页面上停止服务)
2.备份旧程序
1)备份旧版YDB整个程序目录
2)备份我们自己开发或添加的第三方的jar包和配置文件
3)备份YDB提供的Spark整个程序目录
4)备份HIVE本地元数据库
本地元数据库是指Hive元数据库derby.log和metastore_db,元数据库的位置可以从conf下的ya100_env.sh中的HIVE_METASTORE_PATH获取,如下图所示。
3.升级程序
1)替换ydb提供的Spark升级版本
u将之前旧的spark整个目录mv成待备份的目录名字,如xxx_spark_bak_yyyymmdd
u将新的spark解压后,重新命名为之前的spark目录
u检查spark目录是否正确,核对spark相关文件是否存在
2)替换YDB
u将之前旧的ya100 整个目录mv成待备份的目录名字,如xxx_ya100_bak_yyyymmdd
u将新的ya100解压后,重新命名为之前的ya100目录
u检查ya100目录是否正确,核对相关文件路径是否存在
3)元数据库文件替换
将之前备份的Hive元数据库derby.log和metastore_db ,恢复到相关目录里面。
如果没有恢复该元数据库目录,升级YDB后,会发现之前旧版创建的hive表,均不可见(数据还在)。
5)将我们我们自己开发或添加的第三方的jar包和配置文件重新覆盖过来。
jar包如:
kafka的reader包,parser包
自定义udf,udaf,udtf函数
自定义的第三方分词,自定义的词库
因为版本问题而更换的kafka相关jar包
其他jar包,如mysql相关库
配置文件如
fieldType.txt里面自定义的数据类型
dynamicField.txt里面自定义的动态数据类型
IK_ext.dic 里面定义的IK词库文件
4.更改配置
1)ya100_env.sh更改(HDP版请在配置页面上管理)
参考旧的ya100_env.sh,将旧版的ya100_env.sh 我们用户配置的部分,更新到新版。
如下图所示,红色部分是我们经常需要修改覆盖的地方。
2)ydb_site.yaml更改(HDP版请在配置页面上管理)
参考旧的ydb_site.yaml,将旧版的ydb_site.yaml 我们用户配置的部分,更新到新版。
如下图所示,红色部分是我们经常需要修改覆盖的地方。
3)log4j的配置更新(HDP版请在配置页面上管理)
log4j是用来控制程序的日志的。
常规情况下log4j我们无需配置,但是如果之前我们的旧版本改动了log4j的配置,我们本次升级记得也跟着改动过来
driver.log.properties 为我们的driver节点的log4j 配置,即master节点的log4j。
worker.log.properties 为我们的worker节点的log4j配置。
4)spark-defaults.conf的配置更新(HDP版请在配置页面上管理)
改配置为spark的配置,常规情况下用户无需配置。最近的一些版本spark-defaults.conf我们的改动也比较大,所以注意,spark-defaults.conf绝对不可以直接用旧版的配置文件直接覆盖,因为很多我们配置项的改动是为了提升稳定性。
但如果因为某种原因,我们之前更改过该文件的配置,请大家一定要在新的配置的基础上,仅仅增加或修改我们自己更改过的那些配置,为了稳定性,不要随意删掉YDB提供的默认配置。
5)init.sql里面我们的初始化函数
init.sql是ydb的启动后的初始化SQL文件,通常来说也无须配置。
如果我们在init.sql做了一些Hive函数的初始化,或者构建了一些表之类的,我们需要参考旧的init.sql对应着修改过来
6)hive-site.xml的配置(HDP版请在配置页面上管理)
hive-site.xml是跟hive表相关的配置,里面控制了jdbc连接的线程池,以及元数据存储位置等信息。
如果之前旧版我们更改过这里,记得要配置。
5.更改本次版本更新的特定配置
如果该次升级,涉及版本一些重大特性的变化,如要在这个步骤里面给予修改。这里面的更改由延云额外提供文档进行更改。
如:
操作系统发现的可以对性能或稳定性有较大影响的新的参数设置
hadoop\kafka\zookeeper等对ydb有较大影响的一些新的参数配置。
对于本地1.5版本的ydb需要添加额外如下配置
1)网络配置优化
echo " net.core.somaxconn = 32768 " >> /etc/sysctl.conf
sysctl -p
sysctl -a|grep somaxconn
2)Hadoop 配置更改
l调整dfs.datanode.max.transfer.threads的值,默认4096太小,建议调整为10240
l调整ipc.server.listen.queue.size为32768
l调整yarn.resourcemanager.am.max-attempts的值为10000000,默认的2次太小,客户测试过程反复的kill就会导致整个任务失败。
6.启动YDB并检查服务是否正常
通过start-all.sh来启动服务
1)ydb的1210页面上的monitor页面是否有异常错误上报
看这个页面是否有 紫色或者红色的异常提示
2)spark的ui页面是否能打开
3)核对业务,服务是否正常,相关SQL是否能查询到结果
4)核对业务数据是否有异常,数据条数是否正确。
一、YDB运维你必须要了解的几个UI页面
1.Hadoop HDFS UI页面
默认配置为50070端口,该UI通常用来核对,HDFS服务是否正常,存储空间,机器数量等,HDFS服务为YDB的基本依赖环境。
经常需要关注的几个地方,我在图中标记出来了。
|
2.Hadoop的YARN UI
默认配置为8088端口,该UI通常用来
1.核对机器的资源(CPU,内存)
2.检查运行的任务
经常需要关注的几个地方,我在图中标记出来了。
|
3.YDB的Spark UI
在Spark UI里可以看到 每个用户查询SQL的执行进度,响应时间等,也可以杀掉有异常的一些任务。
进入方法如下
然后点击application Master进入 |
|
如果点击后,发现域名解析不了,请在您机器本地配置好相关host,或者直接改成对应的IP。
3.点开后会看到如下的页面
|
如果有机器或某个进程拖后腿,或者有数据倾斜,如何看?
|
在这里可以看到,谁运行的时间比较长,或者还在运行,注意耗时Duration那列是可以排序的
|
通过这里,可以找到出问题的具体进程
|
4.YDB的监控UI
1.可以查看每台机器的负载、处理数据、内存等情况。
2.可以了解每个表的运行情况,每个分区的数据条数,数据量大小。
默认启动的端口号为1210,如果在 ydb_site.yaml里配置了ydb.httpserver.port,则以配置的端口号为准。
|
如果是基于ambari安装的,也可以通过这个快速链接直接打开 |
在这里可以看到每个机器的健康状态,内存使用情况等,如果有异常这里会有提示,红色表示是比较严重的错误,紫色表示警告,其他颜色可以忽略。
正常没有错误的页面是这样的 有丢失了节点的异常状态是这样的
|
kafka监控
如果启用了kafka数据消费,在monitor 页面中可以看到kafka的消费进度,如果消费延迟较大,这里面也会出现红色的预警。
YDB的工作进程详细监控
|
二、YDB的几种日志
1.Driver日志
Driver是jdbc的接口,也是任务调度的Master。
服务的启动,停止,与关闭、每次SQL的查询都通过该接口进行。
日志位置:
通常位于安装程序的logs目录下,在driver.log.properties这个配置文件里,可以更改Driver日志的记录位置。
日志位置截图示例如下
如果在首次安装时,出现启动失败(发现YDB长期启动不起来),或者查询某些SQL报错,可以分析此日志,了解具体启动失败的原因。一般是因为某些服务没有启动起来,或者Yarn的资源不够引起,要检查依赖的服务是否正常。
2.Worker日志
Worker进程是具体工作的进程,由Yarn来管理任务的调度与执行,本质上为Yarn的Application container. 对于YDB中Worker的概念与Yarn的Container是一样的,说那个都可以,都是一个进程。
该日志为Yarn的container日志,可以再YDB的UI的Worker里面找到,如下图的红色部分
|
当然了,也可以再Yarn的UI页面里找到(但是没有在YDB中查看方便)
3.Yarn的AppMaster日志
Yarn的App Master进程,由Yarn进行管理,用于控制Worker的启动与停止。
如果发生了 container发生了重启,可以从Appmaster的日志里看为什么发生了重启,是OOM还是机器宕机
查看方法参考下图:
三、一次查询的列越多,返回的记录数越多,响应时间越慢的问题
细心的用户会发现,如果是采用磁盘,一次SQL查询的列数很多,返回的记录数越多,响应时间越慢。
原因为YDB是采用按照单元格方式存储的(即按列,又按行来存储),也就是说每个单元格的数据在磁盘的物理上并不是相邻的,读取的时候磁盘需要进行跳跃读取,行数越多、列数越多,需要读取的单元格越多。而普通磁盘的IOPS都不是很高(150)左右,这样跳跃的读取,对于普通磁盘来说压力比较大,尤其在有一定并发的情况下,磁盘的IO达到100%以后,单位时间处理的单元格固定,那么单个SQL的时间会随着并发的增多而变得越来越慢。
针对这种场景,我们建议尽量采用SSD硬盘,SSD硬盘的随机读写性能是普通磁盘的千倍以上,如果实在没有SSD硬盘,我们可以考虑将经常读取的列,合并成一个比较大的列,并按行的方式存储在一个字段里(如都存储在类型为simpletextny的字段里面)。
四、使用Kafka导入数据,CPU全是满的,导致查询很慢
CPU很高 有两种原因
1.一是在分词计算上,消耗很多CPU
建议启用流量限速,具体如何使用限速我们可以查看流量控速那一节的配置。
2.另外一种特别大的索引合并占用很高的CPU
在索引合并过程中会进行数据的解压、字典的合并等,会比较消耗CPU
n可以通过调整索引的合并因子,以及调整索引的大小来解决
n调整当前分区的粒度,尽量别生成特别大的索引。
五、时间段的范围查询很慢
时间段跨度比较大时,建议用 tlong类型 别用普通的 string类型或者long类型。
tlong占用的存储比long类型或string类型多,但是由于时间的精度比较高,会导致倒排表会比较长,如果全范围的扫描一次倒排表的话,IO是个问题,但是tlong类型就不会有这个问题,tlong类型的倒排表是经过特殊处理的,在lucene层设置的precisionStep为8,比较适合大范围的扫描。
precisionStep 如何理解precisionStep呢?
如何理解precisionStep呢?需要一步一步来:
1,precisionStep是在做range search的起作用的,默认值是4
2,数值类型(int float double)在Lucene里都是以string形式存储的,当然这个string是经过编码的
3,经过编码后的string保证是顺序的,也就是说num1>num2,那么strNum1>strNum2
4,precisionStep用来分解编码后的string,例如有一个precisionStep,默认是4,也就是隔4位索引一个前缀
比如0100,0011,0001,1010会被分成下列的二进制位
0100,0011,0001,1010,
0100,0011,0001,
0100,0011,
0100。
precisionStep这个值越大,那么索引的遍历粒度就越粗,那么范围查询的性能也越好,占用的存储越多;这个值越小,索引的遍历粒度就越细,那么性能越差,但占用的存储空间越少。
5,range search的过程参见下图:
如果用户希望查找423~642的记录,如果没有precisionStep,那么就必须从最底层一个一个匹配了,性能肯定不行。有了precisionStep,那么range search可以这么做:44可以匹配445,446,448,而5可以匹配521,522;63可以匹配632,633,634;而只有423,641,642在最底层匹配。
六、如何判断Contaner不稳定,发生了重启
在Worker列表页面记录了每个Container的运行时长,如果启动时长与其他的Container不一致,则表示发生了重启,另外从下图的execid xx@xx 也可以判断出来,如果两者需要不一致则表示发生了重启
七、为什么Yarn中看到的内存使用量,比我在ya100_env.sh中配置的多?
1,关于计费
yarn的内存“计费”是按照 yarn.scheduler.minimum-allocation-mb的整数倍进行计费,默认是1G
Spark.yarn.executor.memoryOverhead 会在申请的内存的基础上,额外申请384m内存
如果我们对 spark.executor.memory配置了1G内存,外加额外分配的384m,Yarn的取整原则,最终是按照2G的内存使用量进行的“计费”
为了计费的相对准确,大家尽量要凑成 yarn.scheduler.minimum-allocation-mb的整数倍,或者将 yarn.scheduler.minimum-allocation-mb的值调小,让计费更准确
2,关于堆外内存
Spark在执行的过程中,会使用堆外内存,如果大家运行的一个Job很大(比如说要导入的数据量很大)
请将spark.yarn.executor.memoryOverhead 给予一个较大的内存,或者直接将yarn.nodemanager.pmem-check-enabled与yarn.nodemanager.vmem-check-enabled参数设置为false,以免在导出数据过程进程被kill导致导入数据失败。
3、相关参数解释,备忘
spark.yarn.executor.memoryOverhead :值为 executorMemory * 0.07, with minimum of 384
spark.yarn.driver.memoryOverhead :值为 driverMemory * 0.07, with minimum of 384
spark.yarn.am.memoryOverhead :值为 AM memory * 0.07, with minimum of 384
八、Ydb的Cache粒度以及每种粒度的控制
YDB分多种级别的Cache,每种级别的Cache均按照LRU的方式进行管理
1.表级别的Cache
控制一个表的打开与关闭状态,
默认YDB中能够打开的表的数量由ydb.schema.cache.size控制,表示在当前的container进程内能够同时打开多少张表,默认值根据分配的内存的不同,能够同时打开表的数量也不同,默认值为:64+ (int) Math.min(64*RamTotalForGb(), 1024)
2.Reader级别的Cache(非常影响性能)
在YDB内部,每个分区下,都会创建多个索引碎片,而每个碎片被打开后我们称为一个Reader.
l如何看碎片
如下图,在当前的container内有2个碎片,他们都打开的话,会有2个Reader
可以通过show partitions 查看分片
/*ydb.pushdown('->')*/
showpartions ydb_example_shu
/*('<-')pushdown.ydb*/
通过下图可以看到一个表的总的碎片数量,以及分配每台机器上分散的碎片数量(也可用于看数据是否倾斜)
接着往下看我们也可以看到每个分区的分片数量
接下来可以详细的看到,每个container内的分片
l一个分区的碎片数量由如下参数来决定
控制正在导入数据的分区期望生成的碎片个数
ydb.realtime.index.disk.maxcount: 64
超过24小时,已经没有数据导入的静态分区,期望生成的碎片个数
ydb.realtime.index.merger.final.maxcount: 2
单个碎片索引的最大大小-不能太大,太大的索引合并开销较大,会造成写入压力较大
ydb.realtime.index.maxfilesize: 1073741824
l在内存中能够打开的碎片数量的控制
默认是由ydb.index.reader.maxopen这个参数来决定,该参数如果不配置的话,会由内存大小来决定配置值,(32+ (int) Math.min(32*RamTotalForGb(), 2048))。
另外由于担心临时大请求造成的严重的影响,我们通过ydb.index.reader.min.open.secs参数来保证一个碎片的最小打开时间,默认值为300秒。
如果是实时导入阶段,我们也有内存中的索引碎片,但是由于内存数据与文件碎片不同,我们单独加了一个ydb.index.reader.ram.min.open.secs 来控制内存中的碎片打开时间,默认是60秒。
通过Worker工作进程列表,可以看到每个Container进程内的Reader碎片打开情况
quick即为由ydb.index.reader.min.open.secs 控制的当前打开的碎片数
big即为由 ydb.index.reader.maxopen控制的当前打开的碎片数
ram即为由ydb.index.reader.ram.min.open.secs控制的当前打开的内存碎片数
finallize表示当前因LRU的关系,正在处于关闭状态的碎片数
l为何打开一个碎片非常的消耗资源
打开碎片的过程就是一个加载索引的过程,需要加载BlockTree的信息,列的信息等。需要进行较多的跳跃性的IO。
l如何优化
1.要严格控制一次查询扫描的分区数量,不要创建海量的小分区。
2.优化合并因子,在导入性能允许的前提下,尽量别产生太多的碎片。
3.尽量多给内存,让一个Container能够打开更多的分片。
4.尽量使用SSD盘,让一个分片的打开速度更快。
3.列级以及单元格级别的Cache
YDB中的每个碎片其实就相当于一个微型的小表,每个小表都是有列一说的。由于YDB基本数据类型采用了按列存储,并且针对string类型的数据,我们还维护了一套字典,用于保存该列每个值的标签值与真实值的字典映射,所以打开一个列也是有时间消耗的。
默认YDB中能够同时在内存中打开的列的个数,是由ydb.docvalues.cache.size参数来决定,如果没配置,该参数的值为64+ (int) Math.min(64*RamTotalForGb(), 1024)。
但是考虑到内存的富余,我们通过ydb.docvalues.soft.cache.size参数来控制通过java软引用的方式能够打开的列数,但内存充足的时候,能够打开更多的列,当内存不足的时候,可以释放。默认情况下改ydb.docvalues.soft.cache.size配置的是ydb.docvalues.cache.size的两倍。
对于string类型,我们额外保存了一个字典,访问字典,某种意义上是一个检索,为了加快那些重复值比较多的列(如,性别列)string列的访问速度(减少跳跃查找次数),我们针对字段的访问也加了一个Cache,我们称为单元格级别的cache,单元格级别的Cache由ydb.docvalues.max.ords参数来控制,表示每个打开的列,能够缓存的值的个数,默认值是128+ (int) Math.min(128*RamTotalForGb(), 40960),每个打开的列会根据改值创建一个数组,有java的软引用进行内存管理。
通过worker工作进程列表,可以看到每个container进程内的列打开情况,以及创建的单元格级别的cache数组的个数
l为何打开一个列也会消耗资源
打开一个列的时候,会对该列进行初始化,构建字典等,有一定的消耗。
l如何优化
1.碎片很多,就直接造成打开的列数跟着多,所以要想尽一切办法优化碎片数
2.一次查询不要查询海量的列,因为是列存储并且是索引,随机读取很多,列越多,对磁盘的压力越大,查询性能越慢,如果我们的查询真的是为了返回很多列,那么建议将这些列合并,并存储在一个分词类型的按行存储的列里面。
3.尽量使用SSD硬盘,在多列的情况下,SSD硬盘的随机读的IOPS比普通磁盘高千倍以上。
4.HDFS文件的打开描述符Cache
为了减少对HDFS NameNode的压力,以及提升性能,我们不希望频繁的打开与关闭HDFS中的文件,但是又担心打开太多的文件,造成系统太大的压力,所以我们只会将频繁使用到的HDFS文件打开,不经常使用的和偶尔使用的文件关闭。
HDFS打开的个数由ydb.hdfs.files.maxopen.count参数控制,默认值为256+ (int) Math.min(512*RamTotalForGb(), 16)。
但是为了防止短时间内太频繁的打开与关闭文件,我们通过ydb.hdfs.files.opentimes.secs参数来控制一个索引最少打开的时间,默认是1800秒
在Worker的页面,可以通过这里看到,当前打开的HDFS的文件的个数。
1.基于文件级别的BlockBuffer的Cache
YDB的索引跟Solr,与ES最大的区别就是,YDB的索引存储在了HDFS上,不能像本地磁盘那样使用Map进行内存映射,NIO的方式将频繁访问的文件块映射到内存中,以加快索引的检索速度,故针对这种情况,我们通过BlockBuffer的方式对HDFS上的文件也做了一层“内存映射”,用来加快针对频繁访问的文件位置的IO读取性能。
基本原理大家可以参考下图
l默认我们采用如下配置参数控制这里的内存
ydb.directory.blockbuffer.percent: 24
##单个block的大小/20=1024k 19=512k 18=256k 17=128k; 16=1024*64 15=1024*32 14=1024*16 13=1024*8 12=1024*4 11=1024*2 10=1024 9=512 8=256 7=128 6=64##
ydb.directory.blockbuffer.blocksize.bits: 13
ydb.directory.blockbuffer.percent 表示使用整个jvm内存的 百分之多少进行BlockBuffer的Cache。默认是24
ydb.directory.blockbuffer.blocksize.bits 表示每个block的大小真多多个bits,默认13表示8kb大小一个块.
l当然我们同样也可以再worker页面上看到这块的内存使用情况,以及读写Cache命中率
一、YDB过载保护
任何系统都会存在由于使用不当,或者业务突然变化,导致集群压力超出设计值。
超出设计值有可能存在集中结果,一种是宕机,停服务,整体不可用,一种是服务响应变慢,数据延迟。
对于YDB来说更推荐采用“有损服务”的方式来应对系统的过载,而不是将整个集群跑到直接宕机,我们认为数据延迟一些,好过系统宕机。因为每次宕机,都需要运维人员去现场去重启机器。而且如果仅仅是数据延迟,系统依然可用的话,我们的损失会最小。
做个比喻来解释下什么叫有损服务,腾讯的QQ空间在晚上8点的流量最高,而其他时段流量又比较低,由于购买的带宽有限,可以在晚上8点的时候禁止用户访问原图相片,而只能查看缩微图,尽管这样不是很好,但是认为能看到图片好过流量太高将交换机跑满,整个腾讯的所有的业务均因为断网而宕机更好。
1.控速配置
我们有些老客户的机器配置特别好,但是硬盘非常一般,如果导入数据很多很大的话,不加控速处理,磁盘会长期处于100%的使用率状态,长久不响应,HDFS就非常容易挂掉。为了让服务持续稳定的运行,我们可以启用控速逻辑,下面这些控速的值要根据真实的集群配置进行调整。
•HDFS写控速
echo 5120 >writer.txt
hadoop fs -mkdir -p /data/ycloud/ydb/ydbpath/speedlimit
hadoop fs -rmr /data/ycloud/ydb/ydbpath/speedlimit/writer.txt
hadoop fs -put ./writer.txt /data/ycloud/ydb/ydbpath/speedlimit/writer.txt
•导入控速(kafka流量控制)
echo 20480 >reader.txt
hadoop fs -mkdir -p /data/ycloud/ydb/ydbpath/speedlimit
hadoop fs -rmr /data/ycloud/ydb/ydbpath/speedlimit/reader.txt
hadoop fs -put ./reader.txt /data/ycloud/ydb/ydbpath/speedlimit/reader.txt
•HDFS读控速
echo 40960 >hdfsread.txt
hadoop fs -mkdir -p /data/ycloud/ydb/ydbpath/speedlimit
hadoop fs -rmr /data/ycloud/ydb/ydbpath/speedlimit/hdfsread.txt
hadoop fs -put ./hdfsread.txt /data/ycloud/ydb/ydbpath/speedlimit/hdfsread.txt
2.过载断熔限制
YDB工作的时候会将SQL分成几个部分 “where后的条件过滤”、“分组与统计”、“排序”。
系统最先执行的是“where后的条件过滤",先检索到与where条件匹配的记录。比如说全表有10亿条记录,经过与where条件匹配后,可能还剩下500万条记录,然后对剩下的500万条进行“分组与统计”和“排序”。
由于YDB采用大索引技术,索引的匹配速度比较快,故后续的“分组与统计”、“排序”是决定计算性能的关键。
默认情况是将“where过滤后”,满足条件的全部匹配的数据都取出来,再进行“分组与统计”、“排序”,然后在抛给spark做后续的计算。如果满足条件的数据仅仅是原始数据的一小部分,不会影响性能,也能满足大部分的场景需求,但是如果有“严重的数据倾斜”,或者直接对全表数据进行“分组与统计”、“排序”会严重的消耗系统的资源,这会导致系统变慢。
如果业务模式每次检索的数据都是原始数据很小的一部分,那么可以将这个 “where过滤”改进一下,不像之前那样每次都取出全部匹配的数据,而是每次仅取其中的一小部分,比如经过“where过滤”后依然有几千万的数据,但我们只取其中的1万条,然后在这1万条的基础上在进一步的“分组与统计”、“排序”。这样除了极大的提高性能,也防止因为用户的误操作(写错了SQL),导致系统崩溃。
要做这样的配置,需要在ydb_site.yaml中配置限制的值,如下面的例子,我们限制每个索引碎片最多匹配10000条数据参与计算。
ydb.fq.max.return.docset.size:10000
ydb.export.max.return.docset.size:10000
注:这里的10000针对一个索引碎片。一个分区下会有YA100_EXECUTORS*spark.executor.max.usedcores 个task,每个task下hdfs目录里最多会有YA100_EXECUTORS*spark.executor.max.usedcores *ydb.realtime.index.disk.maxcount个索引碎片,内存区域最多会有YA100_EXECUTORS*spark.executor.max.usedcores *ydb.realtime.ram.ram.maxcount个索引碎片,所以一次查询最多参与计算的记录数是索引碎片的个数乘以10000。
如果个别的业务要求突破这个限制(比如说仅仅count计算,对系统性能没什么大的影响),只要SQL再加上如下条件即可突破。
放到SQL里的例子如下
select count(label),count(*) from ydbexample where ydbpartion='20151011' and ydbkv='max.return.docset.size:100000000'
limit 0,100
3.不怕数据不准确,某个节点宕机在恢复的这段时间依然可以查
在千亿级别的系统,如果只是一些不是很重要的数据,丢失一两条没什么关系,但系统某个节点发生了宕机,还没有来的及重启,原先的默认逻辑是要等待其从起完毕才能恢复服务,通过如下配置,可以去掉这个等待,直接返回数据(但是那个节点的数据没有返回)
ydb.partions.check.throw.exception: "false"
ydb.binlog.recover.wait.timeout.exception: "false"
ydb.binlog.recover.wait.stage1.secs: 3
ydb.binlog.recover.wait.secs: 3
ydb.request.thread.timeout.retries: 0
ydb.topoplgy.worker.allowfail.count: 12
系统启动阶段会碰到的问题
系统刚启动的时候,历史索引还存放在HDFS中,并没有打开,首次打开索引需要加载很多资源。故系统刚启动的瞬间,查询会比较慢。
如上次系统是因意外终止(硬件损坏,机器重启等),内存中的数据还没有来得及同步到HDFS中,所以在首次启动的时候需要用binlog(WAL write ahead log)恢复数据,这个恢复时间取决于恢复数据的量,所以首次查询的时候,有可能出现卡顿的现象或者出现数据变少的现象,出现这种现象说明系统正在恢复数据,等数据恢复完毕后这种现象会自行消失,所以建议系统启动后等待一段时间(10-15分钟)后再开始查询。
4.高并发请求的断熔配置
为了防止大量的意外请求导致的服务器宕机,YDB的接口采用熔断机制。
1)thrift的配置
修改hive_site.xml文件的如下配置
2)HTTP接口的断熔配置
可以用ydb.request.max.pral这个选项来控制允许多少个并发请求同时执行,如果请求数量超过并发数,则按照FIFO的机制在请求队列中进行排队处理。为了防止因为队列太长导致的内存溢出,如果请求队列的长度超过128个(可以通过ydb.request.max.queue.size修改)后续的请求会被拒绝。
二、数据导入,创建索引过程的IO控制
1.YDB的索引创建过程
2.针对这几个区域的参数配置如下
binlog | binlog对于离线导入不起作用,但是对于Kafka导入对于数据的恢复至关重要 如果您的业务不担心数据丢失,可以关闭binlog(预写日志WAL)功能。 ydb.realtime.binlog.usebinlog: "true" 如果设置成true,则binlog的flush会使用HDFS的hsync方法,否则使用hflush刷新数据 ydb.realtime.binlog.usehsync: "true" 每间隔1024条同步一次binlog到HDFS,但是意味着如果在同步前进程异常中断,1024条数据丢失, 每间隔几条记录,调用一次flush,如果在flush调用前进程意外终止,未flush的数据有可能丢失,但是频繁的调用hflush()方法会导致系统变慢建议调大此值。 ydb.realtime.binlog.sync.intervel: 1024 日志进行切割的时间 ydb.realtime.binlog.roll.timelen.ms: 120000 |
doclist | 积累1024条数据,doclist区域会刷新到buffer区 #ydb.realtime.doclist.buffsize: 1024 积累10秒,doclist区域会刷新到buffer区 ydb.realtime.doclist.flush.secs: 10 如果一直没有数据进来,间隔300秒启动线程检查一次,是否应该刷新到buffer区域 ydb.realtime.doclist.flush.thread.check.secs: 60 采用5个线程来刷新 #ydb.realtime.doclist.flush.process.threads: 5 |
buffer | 每个写入的partion分片会使用1%的内存进行buffer,如果并发写入的分区特别多,内存有可能不够 ydb.realtime.buffer.ram.writepartion.each.percent: 1 积累60秒,buffer区域的索引会刷新到ram区域 ydb.realtime.buffer.flush.secs: 20 如果一直没有数据进来,间隔60秒启动线程检查一次,是否应该刷新到ram区域 ydb.realtime.buffer.flush.thread.check.secs: 60 Buffer区域最多积累64个索引 ydb.realtime.buffer.maxcount: 64 Buffer区域每次合并索引的时候一次最多合并16个索引 ydb.realtime.buffer.merger.factor: 16 |
ram | 每个写入的partion分片会使用2%的内存进行Ram 在合并过程中如果ydb.index.ram2disk.premerger.isuse配置为true 有可能占用2个6%的内存如果并发写入的分区特别多,内存有可能不够 ydb.realtime.ram.ram.writepartion.each.percent: 2 积累120秒,Ram区域的索引会刷新到hdfs上 ydb.realtime.ram.localflush.secs: 120 Ram区域最多积累32个索引 ydb.realtime.ram.ram.maxcount: 32 Ram区域每次合并索引的时候一次最多合并8个索引 ydb.realtime.ram.merger.factor: 8 |
hdfs | Hdfs区域期望最多积累64个索引 ydb.realtime.index.disk.maxcount: 64 Hdfs区域每次合并索引的时候一次最多合并16个索引 ydb.realtime.index.disk.merger.factor: 16 在将ram区域的索引合并到磁盘上前,是否先进行预合并(当内存富余的时候推荐使用,可以节省磁盘IO) ydb.index.ram2disk.premerger.isuse: "true" 是否生成复合索引,生成复合索引会多一倍的IO,但是可以减少文件数量 ydb.index.compoundfile.hdfs: "true" 单个索引碎片超过1g就不会再继续合并了,如果索引很大会造成生成太多的索引碎片,但是如果改值设置的特别大,会造成生成的索引较大,导致导入数据的IO较大 #ydb.realtime.index.maxfilesize: 1073741824 |
final | 如果某个分区的索引在一定时间没有数据写入,则变成final状态,会就行进一步的规整,合并琐碎的索引。 final状态期望最多积累64个索引 ydb.realtime.index.merger.final.maxcount: 2 每次合并索引的时候一次最多合并6个索引 ydb.realtime.index.merger.final.factor: 6 86400秒后没有数据写入认为进入final状态 ydb.realtime.index.mergerone.timeslen.secs: 86400 允许在那个时段进行final的合并-一般我们会选择一个用的人较少的时段 ydb.realtime.index.mergerone.hourbegin: 1 ydb.realtime.index.mergerone.hourend: 23 |
3.普通索引与复合索引的区别
普通索引的文件列表 | |
使用复合索引的文件列表 | |
4.高IO情况下,值得调整的几个参数
如果我们的磁盘特别忙,每天写入的数据量很大 如超过百亿条,建议调整如下参数,缩小合并频率,降低磁盘IO,但也意味着索引数量增多,影响查询速度。
ydb.realtime.index.disk.maxcount: 128
ydb.realtime.index.disk.merger.factor: 32
ydb.realtime.index.merger.final.maxcount: 8
ydb.realtime.index.merger.final.factor: 16
ydb.realtime.index.maxfilesize: 1073741824
ydb.index.compoundfile.hdfs: "false"
如果数据允许有丢失,可以禁用wal log (慎用)
ydb.realtime.binlog.usebinlog: "true"
5.在导入数据的时候,请注意:
第一:导入数据要均匀的分配给每个分片(Task),防止出现数据倾斜的问题。
对于离线导入,要注意Map的均衡(之前有介绍)
对于Kafka导入,要注意Kakfa的num.partions的均衡,(之前也有介绍)
第二:同时导入的分区数量不要超过32个(非分片个数)
如果同时导入的分区太多,打开的写索引的writer也太多,会导致性能不好,因为每个writer都会占用一定的内存buffer,也容易导致内存用满。目前程序控制的允许同时处于写的状态的write数量为(32+ (int) Math.min(32*RamTotalForGb(), 1024))(配置项为ydb.index.max.writer)个,但是实际情况下,同时产生这么多writer内存是不够的,所以要严格控制写入的分区数量。如果同时写入的分区数量超过ydb.index.max.writer配置的值,就会有不经常使用的writer被关闭,下次有数据写入的时候会重新打开,这样就会造成索引频繁的被打开关闭,严重影响性能。
6.Kafka导入的数据延迟
数据导入到Kafka中在导YDB中可以查到数据,一般会有1~2分钟的延迟,如果配置了消费控速逻辑,在流量高峰期,限速流量跑满的情况下,估计延迟更大。
如果是因为数据提交产生的延迟,可以调节上面表的doclist与buffer的提交间隔,从而减少数据延迟,但是切记,频繁的提交也意味着频繁的索引合并,会增大IO。
三、数据倾斜-Spark性能真正的杀手
1.数据倾斜多么痛
1、关于性能调优首先谈数据倾斜,为什么?
(1)因为如果数据倾斜,其他所有的调优都是笑话,因为数据倾斜主要导致程序跑不起来或者运行状态不可用。
(2)数据倾斜最能代表Spark水平的地方,Spark是分布式的,如果理解数据倾斜说明你对Spark运行机制了如指掌。
2、数据倾斜两大直接致命性的后果:
(1)、OOM,一般OOM都是由于数据倾斜所致!
(2)、速度变慢、特别慢、非常慢、极端的慢、不可接受的慢!
何为数据倾斜如下图所示:
3、性能调优最好的方法。
数据倾斜解决掉之后最好的方法就是加内存和CPU 。
4、数据倾斜的定位:
(1)Web UI,可以清晰看见哪些个Task运行的数据量大小;
(2)Log,Log的一个好处是可以清晰的告诉是哪一行出现问题OOM,同时可以清晰的看到在具体哪个Stage出现了数据倾斜(数据倾斜一般是在Shuffle过程中产生的),从而定位具体Shuffle的代码;也有可能发现绝大多数Task非常快,但是个别Task非常慢;
(3)代码走读,重点看join、groupByKey、reduceByKey等关键代码;
(4)对数据特征分布进行分析;
2.多表关联引起的数据倾斜
在多表关联上,关联的key不均衡,最容易引起数据倾斜。最常见的场景就是key是null值或者空字符串,大家设想如果两张表90%的join key都是空字符串,他们关联在一起的笛卡尔及有多少? 是不是特别恐怖?针对这种情况我们一般都需要对空字符串进行特殊处理,排除掉或者通过case when替换成随机数。
3.Hive udaf引起的数据倾斜
Spark对Hive的 UDAF函数支持上并不友好,没有实现combine功能,所以用不好极容易出现数据倾斜问题。对该问题的项目描述大家请查看 YDB函数中的 “Spark中的聚合函数UDAF性能问题”对这个问题的详细阐述。
四、针对查询条件的FQ的Cache的调整
FQ 体现在YDB的Where部分。
用过Solr的同学都知道,Solr有FQ Cache的配置,FQ Cache能极大的提升查询的速度,但是FQ Cache在索引特别大的时候非常消耗内存,因为openbitset 是一个解压后的数据,只不过是通过bit来存储,针对这种情况,针对大表我们希望不使用这个功能,针对小表我们希望依然能起作用。
#########【FQ CACHE的配置】###################
#超过多少条数的数据我们就不在使用FQ Cache了
ydb.fq.cache.max.bits:1000000
#FQ Cache的LRU的大小
ydb.fq.cache.size: 默认值Math.min(32*RamTotalForGb(), 128)
#where查询的时候启动多少个后台线程并发查询,如果分片数很多,调整改参数可以提升查询性能
ydb.fq.cache.threads: 8
#FQ CACHE的缓存时间
ydb.fq.cache.secs: 3600
############【FQ Cache 用于判断的maxDoc的配置】#################
#FQ Cache前进行count的线程并发,用于获取一个索引的maxDoc()
ydb.fq.cache.count.threads:2
#缓存索引的maxDoc的个数
ydb.fq.cache.count.size: Math.min(64*RamTotalForGb(), 512)
五、Zookeeper的Cache时间
YDB在查询的时候,是ZooKeeper来获取工作的Container(Worker)进程列表的,但机器节点数比较多的时候访问ZooKeeper太频繁也许会对ZK造成较大的压力,并且连接ZK也会比较耗时,我们可以通过ydb.workerlist.zookeeper.cache.secs: 30 来调整cache的时间。
如果集群发生异常后,这个cache 太久不便于异常的尽快识别与恢复,我们可以通过ydb.workerlist.zookeeper.cache.miss.secs: 10 来调整异常情况下的cache时间
六、运行在一个Container上的Task个数
默认运行在一个Container上的Task数量为2个,由参数spark.executor.max.usedcores控制。
在Worker上以下图的红色部分为体现
1.Container上的默认2个Task数量意味着什么?
l每次SQL查询会使用当前Container的2个VCore资源,如下图所示,如果我们配置的7个的话,意味着每个SQL查询,将会占用2/7的资源,如果并行运行4个SQL,那么第五个SQL将会等待
l2个Task意味这个Container负责管理2个索引目录
对应关系如下图所示
如果YA100_EXECUTORS一共有12个container,那么默认配置会有24个Task,管理24个part-xxxx索引。如下图所示,无论是查询,还是导入,都会启用24个任务,操作24个part-00xxx目录
l缩小YA100_EXECUTORS带来的问题
之前说到,如果YA100_EXECUTORS配置的是12,由于spark.executor.max.usedcores默认值的2,那么一共会产生24个part-xxxx的目录。
那么如果我们将YA100_EXECUTORS调整为6,如果spark.executor.max.usedcores的默认值还是2的话,那么只会有12个目录与之对应,那么也意味着缩小YA100_EXECUTORS的值到6以后,丢失了一半的数据,为了让数据不丢失,缩小YA100_EXECUTORS就要成倍的增加spark.executor.max.usedcores,这里就是要调整为4。 当然了缩容会带来这个影响的,扩容则不会。
七、YDB的多个分区查询是否合并查询线程
由上节我们知道,对于YDB的一次SQL查询对于一个分区(ydbpartion)会启动YA100_EXECUTORS*spark.executor.max.usedcores 个线程资源,但是有时我们希望一次查询多个分区,默认配置是每个分区都会占用YA100_EXECUTORS*spark.executor.max.usedcores 个线程资源,如果是查询12个月,启动的线程数会比较多,如果集群规模较大,spark本身的调度压力会比较大,也会发送较多的broadcast广播,为了提升性能,我们期望合并这些资源。
我们可以通过spark.executor.partions.uniq: true来合并这些线程,让一个线程负责管理多个资源。
八、HDFS的读写Buffer大小配置
一般无需更改,因为索引很多时候我们需要跳跃读取,并不建议将读给太大的预读空间,以免浪费IO,但是也不建议调整的太小,造成IO太频繁,默认4K或8K就可以了。
ydb.directory.read.buffersize: 4096
ydb.directory.write.buffersize: 65536
九、脏索引的清理
进程的异常退出,机器宕机等原因,HDFS目录里面会存在哪些已经写入一半但是还没有最终完成的索引我们称为脏索引,脏索引如果没有清理的话,时间久了会造成HDFS空间的浪费。
默认配置由Kafka产生的脏数据,由ydb.hdfs.index.dirty.clean.days参数控制,默认值为7代表7天后清理。
对于通过Hive表离线insert方式导入的数据,由ydb.hdfs.index.dirty.commit.clean.nums参数控制,默认值为30,表示30天后进行清理。
十、Container的心跳超时时间
对于ydb是通过ZooKeeper来获取进程的,心跳超时时间由ydb.work.timeout.secs来控制,默认配置为900,表示15分钟。
对于spark来说,大家可以通过调整下图的timeout参数进行干预
十一、YDB的线程池调节(一般无需调节)
#lucene的索引合并,能够启动多少个进程进行索引的合并
ydb.thread.lucene.merger.threads: 16
#lucene索引合并过程中可能会遇到合并失败的情况(HDFS等响应异常),会进程重试,这里表示可以重试的次数,超过多少次还依然失败,则认为该索引合并失败
ydb.thread.lucene.merger.retry: 6
#YDB本身依赖HDFS,如果HDFS有BUG,或者底层硬件有BUG,造成死锁卡主,那么需要给一个超时时间,如果超过这个时间,索引还没有合并完成,则kill掉此线程后,重新合并。
#一个线程的最少等待时间
ydb.thread.lucene.min.timeout: 3600
#每GB索引的等待时间
ydb.thread.lucene.timeout.pergb: 7200
#没100万条数据的等待时间
ydb.thread.lucene.timeout.per100w: 60
#每次请求,在一个container内能够并发查询多少个segment碎片
ydb.threads.per.request.segment.min: 8
#每次请求,在一个Container内能够并发查询多个个Index分区
ydb.threads.per.request.index: 8
十二、Spark本身的线程池调节
十三、索引合并后的延迟清理时间
索引合并后,之前的旧的索引是需要被清理掉,并释放空间的,但是考虑到,有可能还有查询还会去读哪个索引,我们的清理是采取延迟清理的策略的。
ydb.realtime.index.lazydelete.timelen.secs: 1200 位于HDFS中的索引清理时间
ydb.realtime.index.purgerreader.timelen.secs: 4000 位于内存中的索引清理时间
十四、高并发请求的配置,如200并发
1.yarn中的如下配置,并发尽量修改的大一些,以便spark能多启动一些线程
2.ydb线程池的调整
3.YDB中的hive线程池修改
4.spark的akka线程池的调整,如调整为32
spark.akka.threads 24
5.在ya100_env.sh中调整Cleaner线程池与等待队列的大小
如下图所示,
1)添加或调整-Dydb.sql.unbroadcast.pral.size=8参数,额外注意,其他参数,如GC配置,请不要给删掉
2)ydb.sql.waiting.queue.size建议driver 每G内存分配10000,如果是12G内存建议分配12W
export YDB_SPARK_DRIVER_OPTS=" ${YDB_GC_UTILS} -Dydb.sql.waiting.queue.size=50000 -Dydb.sql.unbroadcast.pral.size=8 -Dydb.metastore.pmf.timeout.secs=3600 "