Shell bash 脚本 set -u 命令用法详解

BASH 脚本的 set 命令有很多参数,在实际应用中有些写错的内容,反而被脚本忽略了。set -u 就可以让脚本遇到错误时停止执行,并指出错误的行数信息。本文主要介绍 set -u (set -o nounset)命令的相关用法。

执行脚本的时候,如果遇到不存在的变量,Bash 默认忽略它。

1
2
3
4
#!/usr/bin/env bash
echo $a
echo bar

上面代码中,$a是一个不存在的变量。执行结果如下。

1
2
3
bash script.sh
bar

可以看到,echo $a输出了一个空行,Bash 忽略了不存在的$a,然后继续执行echo bar。大多数情况下,这不是开发者想要的行为,遇到变量不存在,脚本应该报错,而不是一声不响地往下执行。

set -u就用来改变这种行为。脚本在头部加上它,遇到不存在的变量就会报错,并停止执行。

1
2
3
4
5
#!/usr/bin/env bash
set -u
echo $a
echo bar

运行结果如下。

1
2
bash script.sh
bash: script.sh:行4: a: 未绑定的变量

可以看到,脚本报错了,并且不再执行后面的语句。

-u还有另一种写法-o nounset,两者是等价的。

1
set -o nounset

以上就是 shell 脚本 set -u 的作用。

Unix/Linux 脚本中 “set -e” 的作用

———————————————————–
#!/bin/bash

set -e

command 1
command 2

exit 0
———————————————————-

Every script you write should include set -e at the top. This tells bash that it should exit the script if any statement returns a non-true return value. The benefit of using -e is that it prevents errors snowballing into serious issues when they could have been caught earlier. Again, for readability you may want to use set -o errexit.

你写的每个脚本都应该在文件开头加上set -e,这句语句告诉bash如果任何语句的执行结果不是true则应该退出。这样的好处是防止错误像滚雪球般变大导致一个致命的错误,而这些错误本应该在之前就被处理掉。如果要增加可读性,可以使用set -o errexit,它的作用与set -e相同。

Using -e gives you error checking for free. If you forget to check something, bash will do it for you. Unfortunately it means you can’t check $? as bash will never get to the checking code if it isn’t zero. There are other constructs you could use:

使用-e帮助你检查错误。如果你忘记检查(执行语句的结果),bash会帮你执行。不幸的是,你将无法检查$?,因为如果执行的语句不是返回0,bash将无法执行到检查的代码。你可以使用其他的结构:

command
if [ "$?"-ne 0]; then 
	echo "command failed"; 
	exit 1; 
fi 

could be replaced with

能够被代替为

command || { echo "command failed"; exit 1; } 

or

或者

if ! command; then
	 echo "command failed"; 
	exit 1; 
fi 

What if you have a command that returns non-zero or you are not interested in its return value? You can use command || true, or if you have a longer section of code, you can turn off the error checking, but I recommend you use this sparingly.

如果你有一个命令返回非0或者你对语句执行的结果不关心,那你可以使用command || true,或者你有一段很长的代码,你可以关闭错误检查(不使用set -e),但是我还是建议你保守地使用这个语句。

OSS对象存储–命令行操作

https://helpcdn.aliyun.com/document_detail/120057.html?spm=a2c4g.11186623.6.684.646c55b74lmlDS

cp命令用于上传、下载、拷贝文件。

命令格式

  • 上传文件
    ./ossutil cp file_url cloud_url  [-r] [-f] [-u] [--output-dir=odir] [--bigfile-threshold=size] [--checkpoint-dir=cdir] [--snapshot-path=sdir] [--payer requester]
  • 下载文件
    ./ossutil cp cloud_url file_url  [-r] [-f] [-u] [--output-dir=odir] [--bigfile-threshold=size] [--checkpoint-dir=cdir] [--range=x-y] [--payer requester]
  • 拷贝文件
    ./ossutil cp cloud_url cloud_url [-r] [-f] [-u] [--output-dir=odir] [--bigfile-threshold=size] [--checkpoint-dir=cdir] [--payer requester]

使用示例

  • 上传文件
    • 上传单个文件
      ./ossutil cp a.txt oss://bucket/path
    • 上传单个文件并指定--meta选项
      上传文件的同时可以使用--meta选项设置文件的meta信息,其内容格式为header:value#header:value...,如下将当前目录下文件a.txt上传并设置其meta信息:

      ./ossutil cp a.txt oss://bucket/path --meta=Cache-Control:no-cache#Content-Encoding:gzip
      说明 更多关于meta设置的信息请参见set-meta
    • 上传文件夹
      使用cp命令时增加-r选项可以将目标文件夹上传到OSS。命令如下:

      ./ossutil cp -r dir oss://bucket/path
      说明 若上传目标对象为符号链接(软链接),且指向本地文件夹,则使用cp命令上传时,应当给软链接加上正斜线(/)。

      ./ossutil cp -r symbolic_link/ oss://bucket/path
    • 上传文件/文件夹并设置限速
      上传文件时,指定--maxupspeed选项,可设置上传时的最高速度,单位为KB/s,缺省为0(不限速)。

      • 上传文件并设置限速为1MByte/s
        ./ossutil cp a.jpg oss://bucket/path --maxupspeed 1024
      • 上传文件夹并设置限速为1MByte/s
        ./ossutil cp -r dir oss://bucket/path --maxupspeed 1024
    • 批量上传符合条件的文件
      您可以使用--include/--exclude,在cpset-metaset-acl操作时批量选择对应条件的文件。

      • --include/--exclude选项支持格式:
        • *:通配符,匹配所有字符。例如:*.txt表示匹配所有txt格式的文件。
        • ?:匹配单个字符,例如:abc?.jpg表示匹配所有文件名为abc+任意单个字符的jpg格式的文件,如 abc1.jpg
        • [sequence]:匹配序列的任意字符,例如:abc[1-5].jpg表示匹配文件名为abc1.jpg~abc5.jpg的文件
        • [!sequence]:匹配不在序列的任意字符,例如:abc[!0-7].jpg,表示匹配文件名不为abc0.jpg~abc7.jpg的文件。
        说明

        • 不支持带目录的格式,例如:--include "/usr/test/.jpg"
        • --include--exclude可以出现多次,当多个规则出现时,这些规则按从左往右的顺序应用。
        • 指定--include/--exclude选项时,需同时指定--recursive(-r)选项。
      示例:

      • 上传所有文件格式为txt的文件:
        ./ossutil cp dir/ oss://my-bucket/path --include "*.txt" -r
      • 上传所有文件名包含abc且不是jpgtxt格式的文件:
        ./ossutil cp dir/ oss://my-bucket/path --include "*abc*" --exclude "*.jpg" --exclude "*.txt" -r
    • 上传文件并指定存储类型
      您可以在上传文件时,通过--meta选项修改对象存储类型。

      • 上传单个文件并指定存储类型为IA(低频访问)类型
        ./ossutil cp dir/sys.log  oss://my-bucket/path --meta X-oss-Storage-Class:IA
      • 上传文件目录并指定存储类型为IA类型:
        ./ossutil cp dir/ oss://my-bucket/path --meta X-oss-Storage-Class:IA -r
    • 上传文件夹并跳过已有文件

      批量上传时,若指定--update(可缩写为-u)选项,只有当目标文件不存在,或源文件的最后修改时间晚于目标文件时,ossutil才会执行上传操作。命令如下:

      ./ossutil cp -r dir oss://bucket1/path -u

      该选项可用于当批量上传失败重传时,跳过已经成功的文件,实现增量上传。

    • 上传文件夹并生成快照信息

      批量上传时,若指定--snapshot-path选项,ossutil在指定的目录下生成文件上传的快照信息,在下一次指定该选项上传时,ossutil会读取指定路径下的快照信息进行增量上传。命令如下:

      ./ossutil cp -r dir oss://bucket1/path --snapshot-path=path                                
      注意

      • --snapshot-path选项用于在某些场景下加速增量上传/下载批量文件(拷贝不支持该选项)。例如,文件数较多且两次上传期间没有其他用户更改OSS上对应的Object。
      • --snapshot-path命令通过在本地记录成功上传/下载的文件的本地lastModifiedTime,从而在下次上传/下载时通过比较lastModifiedTime来决定是否跳过相同文件,所以在使用该选项时,请确保两次上传/下载期间没有其他用户更改了OSS上的对应Object。当不满足该场景时,如果想要增量上传/下载批量文件,请使用--update选项。
      • ossutil不会主动删除snapshot-path下的快照信息,为了避免快照信息过多,当您确定快照信息无用时,请自行清理snapshot-path。
      • 由于读写snapshot信息需要额外开销,当要批量上传/下载的文件数比较少或网络状况比较好或有其他用户操作相同Object时,并不建议使用该选项。可以使用--update选项来增量上传/下载。
      • --update选项和--snapshot-path选项可以同时使用,ossutil 会优先根据snapshot-path信息判断是否跳过此文件,如果不满足跳过条件,再根据--update判断是否跳过此文件。
    • 上传文件到开通了请求者付费模式的Bucket
      ./ossutil cp dir/test.mp4 oss://payer/ --payer=requester
  • 下载文件
    • 下载单个文件
      ./ossutil cp oss://my-bucket/path/test1.txt /dir
    • 指定范围下载文件

      下载文件时,可以通过--range选项进行指定范围下载。

      示例:将test1.txt的第10到第20个字符作为一个文件载到本地。

      ./ossutil cp oss://my-bucket/path/test1.txt /dir  --range=10-20
      Succeed: Total num: 1, size: 11. OK num: 1(download 1 objects).
      0.290769(s) elapsed
    • 从开通了请求者付费模式的 Bucket 下载文件
      ./ossutil cp oss://payer/test.mp4 dir/ --payer=requester
    • 下载文件夹
      ./ossutil cp -r oss://my-bucket/path /dir 
    • 下载文件夹并指定--update选项
      批量下载时,若指定--update选项,只有当目标文件不存在,或源文件的最后修改时间晚于目标文件时,ossutil才会执行下载操作。命令如下:

      ./ossutil cp -r oss://bucket/path  /dir  --update                           

      该选项可用于当批量下载失败重传时,跳过已经下载成功的文件,实现增量下载。

    • 下载文件夹并生成快照信息

      批量下载时,若指定--snapshot-path选项,ossutil在指定的目录下生成文件下载的快照信息,在下一次指定该选项下载时,ossutil会读取指定路径下的快照信息进行增量下载。详情请参见–snapshot-path选项。命令如下:

      ./ossutil cp -r oss://bucket1/path dir --snapshot-path=path                                
    • 批量下载符合指定条件的文件
      您可以使用--include/--exclude,在下载时选定符合条件的文件。详情请参见过滤选项

      • 下载所有文件格式不为jpg的文件
        ./ossutil cp oss://my-bucket/path dir/ --exclude "*.jpg" -r
      • 下载所有文件名包含abc且不是jpgtxt格式的文件
        ./ossutil cp oss://my-bucket1/path dir/ --include "*abc*" --exclude "*.jpg" --exclude "*.txt" -r
  • 拷贝文件

    目前只支持拷贝文件,不支持拷贝未完成的Multipart,不支持跨region拷贝文件。

    • 拷贝单个文件
      ./ossutil cp oss://bucket/path1/a oss://bucket/path2/                                 
    • 拷贝单个文件并重命名
      ./ossutil cp oss://bucket/path1/a oss://bucket/path2/b                           
    • 批量拷贝符合指定条件的文件
      您可以使用--include/--exclude,在拷贝时选定符合条件的文件。详情请参见过滤选项

      • 拷贝所有文件格式不为jpg的文件
        ./ossutil cp oss://my-bucket1/path oss://my-bucket2/path --exclude "*.jpg" -r
      • 拷贝所有文件名包含abc且不是jpgtxt格式的文件
        ./ossutil cp oss://my-bucket1/path oss://my-bucket2/path --include "*abc*" --exclude "*.jpg" --exclude "*.txt" -r
    • 拷贝文件并修改文件存储类型
      • 修改已有文件的存储类型为Archive(归档存储)类型
        ./ossutil cp oss://my-bucket/path/0104_6.jpg oss://my-bucket/path/0104_6.jpg --meta X-oss-Storage-Class:Archive
      • 修改已有文件目录下所有文件的文件类型为Standard(标准存储)类型
        ./ossutil cp oss://my-bucket/path/ oss://my-bucket/path/ --meta X-oss-Storage-Class:Standard -r
        注意

        • 存储类型为归档类型的文件不能通过cp命令直接转换成其他类型,必须先通过restore命令将该文件解冻,之后再使用cp命令转换文件类型。
        • 使用cp覆写文件时涉及到数据覆盖操作。如果低频访问型归档型文件分别在创建后30和60天内被覆盖,则它们会产生提前删除费用。详情请参见计量项和计费项
    • 拷贝单个文件并指定--meta选项
      拷贝文件的同时可以使用 --meta 选项设置 Object 的 meta 信息,其内容格式为header:value#header:value...

      ./ossutil cp oss://bucket/path1/a oss://bucket/path2/ --meta=Cache-Control:no-cache
    • 同region不同Bucket之间的文件拷贝
      拷贝文件时,搭配 -r 选项可以实现批量文件拷贝功能。

      ./ossutil cp oss://your_src_bucket/path1/ oss://your_dest_bucket/path2/ -r                                   
    • 同region不同Bucket之间的增量文件拷贝
      批量拷贝时,若指定--update选项,只有当目标文件不存在,或源文件的最后修改时间晚于目标文件时,ossutil才会执行拷贝操作。命令如下:

      ./ossutil cp oss://your_src_bucket/path1/ oss://your_dest_bucket/path2/ -r --update

      该选项可用于当批量拷贝失败重传时,跳过已经拷贝成功的文件,实现增量拷贝。

    • 从开通了请求者付费模式的Bucket拷贝文件到普通Bucket
      ./ossutil cp oss://payer/test.mp4 oss://my-bucket/path  --payer=requester
    • 从普通Bucket拷贝数据到开通了请求者付费模式的Bucket
      ./ossutil cp oss://my-bucket/path/test.mp4 oss://payer --payer=requester
  • 上传/下载/拷贝文件的性能调优
    cp命令中,通过--jobs项和--parallel项控制并发数。当ossutil自行设置的默认并发达不到用户的性能需求时,您可以自行调整该两个选项来升降性能。

    • 使用--jobs项来控制多个文件上传/下载/拷贝时,文件间启动的并发数。
    • 使用--parallel项来控制分片上传/下载/拷贝一个大文件时,每一个大文件启动的并发数。

    默认情况下,ossutil会根据文件大小来计算parallel个数(该选项对于小文件不起作用,进行分片上传/下载/拷贝的大文件文件阈值可由--bigfile-threshold选项来控制)。当进行批量大文件的上传/下载/拷贝时,实际的并发数为jobs个数乘以parallel个数。

    警告

    • 通常情况下,当ECS虚拟机或者服务器在网络、内存、CPU等资源不是特别大的情况下,建议将并发数调整到100以下。如果网络、内存、CPU等资源没有占满,可以适当增加并发数。
    • 如果并发数调得太大,由于线程间资源切换及抢夺等,ossutil上传/下载/拷贝性能可能会下降。并发数过大可能会产生EOF错误。所以请根据实际的机器情况调整--jobs--parallel选项的数值。如果要进行压测,可在一开始时调低这两项数值,然后逐渐调大直至找到最优值。

常用选项

您可以在使用cp命令时附加如下选项:

 
选项名称 描述
-r,--recursive 递归进行操作。当指定该选项时,命令会对Bucket下所有符合条件的Object进行操作,否则只对指定的单个Object进行操作。
-f,--force 强制操作,不进行询问提示。
-u,--update 只有当目标文件不存在,或源文件的最后修改时间晚于目标文件时,ossutil才会执行上传/下载/拷贝操作。
--output-dir 指定输出文件所在的目录,输出文件目前包含:cp命令批量拷贝文件出错时所产生的report文件。默认值为:当前目录下的ossutil_output目录。
--bigfile-threshold 开启大文件断点续传的文件大小阀值,单位为Byte,默认值:100MByte,取值范围:0-9223372036854775807(Byte)。
--part-size 分片大小,单位为Byte。默认情况下ossutil根据文件大小自行计算合适的分片大小值。如果有特殊需求或者需要性能调优,可以设置该值,取值范围:1-9223372036854775807(Byte)。
--checkpoint-dir checkpoint目录的路径(默认值为:.ossutil_checkpoint),断点续传操作失败时,ossutil会自动创建该目录,并在该目录下记录checkpoint信息,操作成功会删除该目录。如果指定了该选项,请确保所指定的目录可以被删除。
--range 下载文件时,指定文件下载的范围,格式为:3-9或3-或-9。
--encoding-type 输入或者输出的文件名的编码方式,目前只支持url编码,即指定该选项时,取值为url。如果不指定该选项,则表示文件名未经过编码。Bucket名不支持url编码。
--include 包含对象匹配模式,如:*.jpg。
--exclude 不包含对象匹配模式,如:*.txt。
--meta 设置Object的meta为[header:value#header:value…],如:Cache-Control:no-cache#Content-Encoding:gzip。更多详情请参见set-meta
--acl 设置Object的访问权限,默认为default。可配置项为:

  • default:继承Bucket
  • private:私有
  • public-read:公共读
  • public-read-write:公共读写
--snapshot-path 批量上传/下载时,若指定--snapshot-path选项,ossutil在指定的目录下生成文件上传/下载的快照信息,在下一次指定该选项上传/下载时,ossutil会读取指定路径下的快照信息进行增量上传/下载。

注意

  • --snapshot-path选项用于在某些场景下加速增量上传/下载批量文件(拷贝不支持该选项)。例如,文件数较多且两次上传期间没有其他用户更改OSS上对应的Object。
  • --snapshot-path命令通过在本地记录成功上传/下载的文件的本地lastModifiedTime,从而在下次上传/下载时通过比较lastModifiedTime来决定是否跳过相同文件,所以在使用该选项时,请确保两次上传/下载期间没有其他用户更改了OSS上的对应Object。当不满足该场景时,如果想要增量上传/下载批量文件,请使用--update选项。
  • ossutil不会主动删除snapshot-path下的快照信息,为了避免快照信息过多,当您确定快照信息无用时,请自行清理snapshot-path。
  • 由于读写snapshot信息需要额外开销,当要批量上传/下载的文件数比较少或网络状况比较好或有其他用户操作相同Object时,并不建议使用该选项。可以使用--update选项来增量上传/下载。
  • --update选项和--snapshot-path选项可以同时使用,ossutil 会优先根据snapshot-path信息判断是否跳过此文件,如果不满足跳过条件,再根据--update判断是否跳过此文件。
--disable-crc64 该选项关闭CRC64,默认情况下,ossutil进行数据传输都打开CRC64校验。
--maxupspeed 最大上传速度,单位:KB/s,缺省值为0(不受限制)。
--payer 请求的支付方式,如果为请求者付费模式,可以将该值设置成requester。
--partition-download 分区下载使用。一个ossutil命令下载一个分区,其值格式为“分区编号:总分区数”,比如1:5,表示当前ossutil下载分区1,总共有5个分区,分区编号从1开始,Object的分区规则由工具内部算法决定。利用该选项,将待下载的Object分成多个区,可以由多个ossutil命令同时下载,每个ossutil命令下载各自的分区,多个ossutil命令可以并行在不同机器上执行。
-j,--jobs 多文件操作时的并发任务数,默认值:3,取值范围:1-10000。
--parallel 单文件内部操作的并发任务数,取值范围:1-10000,默认将由ossutil根据操作类型和文件大小自行决定。
--loglevel 设置日志级别,默认为空,表示不输出日志文件。可选值为:

  • info:输出提示信息日志。
  • debug:输出详细信息日志(包括http请求和响应信息)。
--retry-times 当错误发生时的重试次数,默认值:10,取值范围:1-500。

Jenkins 构建之后打包文件

在【构建后操作】中增加一个【Archive the artifacts】步骤,【用于存档的文件】中可以设置多个文档(逗号或者空格分隔),还可以使用通配符(*),其它选项看自己需求勾选。

之后再执行构建,Job首页上会显示【最后一次成功的构建结果】,每个构建任务的首页上也会显示【构建产生文件】,点进去之后可以打包下载全部文件。

PostgreSQL 单库对象过多,触发Linux系统限制 (ext4_dx_add_entry: Directory index full!) (could not create file “xx/xx/xxxxxx”: No space left on device)

背景

PostgreSQL 里面创建的表,序列,索引,物化视图等带有存储的对象,每个对象的数据文件都是独立的,较依赖文件系统的管理能力。并不像Oracle那样把对象放到表空间中管理,表空间又由若干的数据文件组成。(ASM的话则接管更多的操作。)

所以,当创建了很多个有实际存储的对象时,文件数就会很多:

通常一个表包含数据文件(若干,单个默认1G),fsm文件一个, vm文件一个,如果是unlogged table则还有_init文件一个。

文件数过多,可能触发文件系统的一些限制。

ext4_dx_add_entry: Directory index full!  

举例

某个系统,在创建新的对象时,报这样的错误。

ERROR:  could not create file "base/16392/166637613": No space left on device  
digoal=# do language plpgsql $$  
declare  
begin  
for i in 1..1000 loop  
execute 'create table test'||i||'(id int primary key, c1 int unique, c2 int unique)';  
end loop;  
end;  
$$;  
ERROR:  53100: could not create file "base/16392/166691646": No space left on device  
CONTEXT:  SQL statement "create table test43(id int primary key, c1 int unique, c2 int unique)"  
PL/pgSQL function inline_code_block line 5 at EXECUTE statement  
LOCATION:  mdcreate, md.c:304  

报错的PG源码文件如下

/*  
 *      mdcreate() -- Create a new relation on magnetic disk.  
 *  
 * If isRedo is true, it's okay for the relation to exist already.  
 */  
void  
mdcreate(SMgrRelation reln, ForkNumber forkNum, bool isRedo)  
{  
        MdfdVec    *mdfd;  
        char       *path;  
        File            fd;  
  
        if (isRedo && reln->md_num_open_segs[forkNum] > 0)  
                return;                      /* created and opened already... */  
  
        Assert(reln->md_num_open_segs[forkNum] == 0);  
  
        path = relpath(reln->smgr_rnode, forkNum);  
  
        fd = PathNameOpenFile(path, O_RDWR | O_CREAT | O_EXCL | PG_BINARY, 0600);  
  
        if (fd < 0)  
        {  
                int                     save_errno = errno;  
  
                /*  
                 * During bootstrap, there are cases where a system relation will be  
                 * accessed (by internal backend processes) before the bootstrap  
                 * script nominally creates it.  Therefore, allow the file to exist  
                 * already, even if isRedo is not set.  (See also mdopen)  
                 */  
                if (isRedo || IsBootstrapProcessingMode())  
                        fd = PathNameOpenFile(path, O_RDWR | PG_BINARY, 0600);  
                if (fd < 0)  
                {  
                        /* be sure to report the error reported by create, not open */  
                        errno = save_errno;  
                        ereport(ERROR,  
                                        (errcode_for_file_access(),  
                                         errmsg("could not create file \"%s\": %m", path)));  
                }  
        }  
  
        pfree(path);  
  
        _fdvec_resize(reln, forkNum, 1);  
        mdfd = &reln->md_seg_fds[forkNum][0];  
        mdfd->mdfd_vfd = fd;  
        mdfd->mdfd_segno = 0;  
}  

实际上就是创建文件出错,但并不是真的没有空间了”No space left on device”。

在系统中可以看到dmesg的消息如下

[14372230.975489] EXT4-fs warning (device dm-0): ext4_dx_add_entry: Directory index full!  
[14372230.984268] EXT4-fs warning (device dm-0): ext4_dx_add_entry: Directory index full!  
[14372230.992913] EXT4-fs warning (device dm-0): ext4_dx_add_entry: Directory index full!  

这个错误与某个目录下的文件数有关。文件数与INODE有关,同时EXT4中为了提高检索文件的性能,有index的优化开关。

man mkfs.ext4  
  
dir_index  
  Use hashed b-trees to speed up lookups in large directories.  

看起来像是这个超了。

和数据库进程的ulimit并无关系:

#cd /proc/17128  
  
#cat limits   
Limit                     Soft Limit           Hard Limit           Units       
Max cpu time              unlimited            unlimited            seconds     
Max file size             unlimited            unlimited            bytes       
Max data size             unlimited            unlimited            bytes       
Max stack size            unlimited            unlimited            bytes       
Max core file size        unlimited            unlimited            bytes       
Max resident set          unlimited            unlimited            bytes       
Max processes             655360               655360               processes   
Max open files            655360               655360               files       
Max locked memory         unlimited            unlimited            bytes       
Max address space         unlimited            unlimited            bytes       
Max file locks            unlimited            unlimited            locks       
Max pending signals       4133740              4133740              signals     
Max msgqueue size         819200               819200               bytes       
Max nice priority         0                    0                      
Max realtime priority     0                    0                      
Max realtime timeout      unlimited            unlimited            us    

分析原因

这个数据库居然创建了2万多个schema。

......  .....  
 repo_20171106_1034_2877694 | digoal  
 repo_20171106_1034_2877696 | digoal  
 repo_20171106_1034_2877697 | digoal  
 repo_20171106_1034_2877699 | digoal  
 repo_20171106_1034_2877700 | digoal  
 repo_20171106_1034_2877701 | digoal  
 repo_20171106_1034_2877703 | digoal  
digoal=# select count(*) from pg_namespace;  
 count   
-------  
 27151  
(1 row)  

每个schema的内容都差不多,有600多个对象。

digoal=# select count(*) from pg_class where relnamespace=(select oid from pg_namespace where nspname='repo_20171106_1034_2877737');  
 count   
-------  
   616  
(1 row)  

这个数据库中一共有2068万个对象。

digoal=# select count(*) from pg_class;  
  count     
----------  
 20680394  
(1 row)  

PG数据文件的存放规则

表空间目录/数据库目录/对象文件

目录结构

表空间/数据库/对象  
表空间/数据库/对象.1  
...  
表空间/数据库/对象.n  
表空间/数据库/对象_fsm  
表空间/数据库/对象_vm  
表空间/数据库/对象_init  

如果这些对象都在一个表空间里面,那么这个表空间下对应数据库OID的目录中,将有至少2000多万个文件。

$ cd $PGDATA/base/16392  
  
$ ll|wc -l  
  
21637521  

果然,有2163万个文件,看样子和它有莫大关系。

开启ext4的dir_index,对单个目录中文件数会有限制。

解决方法

1、删除不必要的schema

2、创建多个表空间,因为每个表空间是一个单独的目录。

3、将对象分配到不同的表空间中,这样的话就可以避免开启ext4的dir_index后,当有很多个对象时,单个目录中文件数超出限制的问题。

小结

由于目前PG的不同对象,存放在不同的数据文件中,当有多个对象时,会创建多个数据文件。

目前PG对象的数据文件存在目录结构如下:

表空间/数据库/对象  
表空间/数据库/对象.1  
...  
表空间/数据库/对象.n  
表空间/数据库/对象_fsm  
表空间/数据库/对象_vm  
表空间/数据库/对象_init  

如果将单个库的所有对象存放在一个表空间中,这个表空间/数据库目录下会有很多个文件,那么可能遇到开启ext4的dir_index后,当有很多个对象时,单个目录中文件数超出限制的问题。

比如本例,一个表空间/数据库目录下,有2000多万个文件。导致了ext4_dx_add_entry: Directory index full!的问题。

为了避免这个问题,建议在单个库的单个表空间中,不要超过500万个对象。如果有更多的对象要在一个库中创建,那么可以创建多个表空间。

当然,我们这里还没有提到文件系统的其他限制,比如一个文件系统INODE的限制。与位数,以及文件系统有关。

ext4_dx_add_entry:2024: Directory index full!

http://blog.chinaunix.net/uid-23284114-id-5749136.html

巡检时,一台oracle数据库操作系统/var/log/message日志中有大量如下warning:
Aug  4 09:18:23 your_hostname kernel: EXT4-fs warning (device sda5): ext4_dx_add_entry:2024: Directory index full!
Aug  4 09:18:23 your_hostname kernel: EXT4-fs warning (device sda5): ext4_dx_add_entry:2024: Directory index full!
Aug  4 09:18:23 your_hostname kernel: EXT4-fs warning (device sda5): ext4_dx_add_entry:2024: Directory index full!
Aug  4 09:18:25 your_hostname kernel: EXT4-fs warning (device sda5): ext4_dx_add_entry:2024: Directory index full!
Aug  4 09:18:25 your_hostname kernel: EXT4-fs warning (device sda5): ext4_dx_add_entry:2024: Directory index full!
Aug  4 09:18:25 your_hostname kernel: EXT4-fs warning (device sda5): ext4_dx_add_entry:2024: Directory index full!
Aug  4 09:18:25 your_hostname kernel: EXT4-fs warning (device sda5): ext4_dx_add_entry:2024: Directory index full!
Aug  4 09:18:25 your_hostname kernel: EXT4-fs warning (device sda5): ext4_dx_add_entry:2024: Directory index full!

产生warning原因如下:
单个目录下的子目录和文件超过64k个。

经排查,Oracle的adump目录下有9113975个.aud文件,将文件删除后,warning不再产生。

涉及命令:
1) 统计当前文件夹下文件的个数
ls -l |grep “^-“|wc -l

2) 统计当前文件夹下目录的个数
ls -l |grep “^d”|wc -l

3) 统计当前文件夹下文件的个数,包括子文件夹里的
ls -lR|grep “^-“|wc -l

4) 统计文件夹下目录的个数,包括子文件夹里的
ls -lR|grep “^d”|wc -l

segfault at xxx rip xxx rsp xxx error 4(合并整理)

通过 sudo cat /var/log/messages |grep segfault 或者 sudo dmesg|grep segfault 获得
这种信息一般都是由内存访问越界造成的,不管是用户态程序还是内核态程序访问越界都会出core, 并在系统日志里面输出一条这样的信息。这条信息的前面分别是访问越界的程序名,进程ID号,访问越界的地址以及当时进程堆栈地址等信息,比较有用的信息是 最后的error number. 在上面的信息中,error number是4 ,下面详细介绍一下error number的信息:
在上面的例子中,error number是6, 转成二进制就是110, 即bit2=1, bit1=1, bit0=0, 按照上面的解释,我们可以得出这条信息是由于用户态程序读操作访问越界造成的。
error number是由三个字位组成的,从高到底分别为bit2 bit1和bit0,所以它的取值范围是0~7.

bit2: 值为1表示是用户态程序内存访问越界,值为0表示是内核态程序内存访问越界
bit1: 值为1表示是写操作导致内存访问越界,值为0表示是读操作导致内存访问越界
bit0: 值为1表示没有足够的权限访问非法地址的内容,值为0表示访问的非法地址根本没有对应的页面,也就是无效地址
如:

Nov  3 09:27:50 ip-172-31-18-210 kernel: [12702742.866113] nginx[53350]: segfault at 1b852e2 ip 00007f9085b3b616 sp 00007ffdf15f1368 error 4 in libc-2.17.so[7f90859ec000+1b7000]

nginx[31752]: segfault at 0 ip 000000000047c0d5 sp 00007fff688cab40 error 4 in nginx[400000+845000]

Linux下打开core文件,定位segfault –http://blog.chinaunix.net/uid-24774106-id-344195.html

出现段错误,不容易定位到底是哪行代码出现了问题,segfault多次折磨的笔者死去活来,

查资料发现了定位段错误的方法。
    Linux下有核心转储文件即core文件,会把程序崩溃是的现场保存起来供gdb来调试。
打开的办法是 ulimit -c unlimited 。
    设置之前可以调用ulimit -c 查看当前的大小,如果是0,表示不生成core文件。unlimited 的含义是不论生成的core文件有多大,我都让系统生成core 文件。
    当时这个方法有个弊端是,在那个终端上设置的ulimit就在那个终端上生效,如果你在另一个终端上执行程序,你会发现,纵然有段错误,你也没生成core文件
    第二个办法是修改 /root/.bash_profile 文件添加一行  ulimit -S -c  unlimited 然后保存关闭文件。
     有很多linux系统/root 目录下并没有.bash_profile 文件,比如SUSE,这没有关系你自己vi 创建这个文件即可。
    修改完这个文件之后,执行source /root/.bash_profile,你就可以查看你的修改生效了没有。
    查看方法是 ulimit -c.你会发现,终端打印出 unlimited,表示你的配置生效了,你可以新开终端 执行 ulimit -c,发现新开终端也是unlimited 。
好,打开开关之后,你就可以跑你的有segfault的代码了。
一般core文件会生成在你的可执行文件所在的目录下。当然可以设定。
设定的方法是 修改 /proc/sys/kernel/core_pattern
这个文件不支持vi的方式修改,可以使用echo
如echo “/corefile/core-%p-%e-%t” > /proc/sys/kernel/core_pattern
这个语句的含义是将core文件生成在 /corefile/这个目录下
生成的文件名的格式是:“core”-“pid”-可执行程序名-段错误时间
%p ———段错误进程的PID
%e———–发生段错误的可执行文件名
%t——- 发生段错误的时间
还有其他配置选项。
生成了core文件,你就可以调试了,调试方法是:
gdb -c core test
注test是你的可执行文件名。你就可以想用gdb调试文件一样调试你的core文件了。

——————————–

Linux上Core Dump文件的形成和分析—http://baidutech.blog.51cto.com/4114344/904419/

Core,又称之为Core Dump文件,是Unix/Linux操作系统的一种机制,对于线上服务而言,Core令人闻之色变,因为出Core的过程意味着服务暂时不能正常响应,需要恢复,并且随着吐Core进程的内存空间越大,此过程可能持续很长一段时间(例如当进程占用60G+以上内存时,完整Core文件需要15分钟才能完全写到磁盘上),这期间产生的流量损失,不可估量。

凡事皆有两面性,OS在出Core的同时,虽然会终止掉当前进程,但是也会保留下第一手的现场数据,OS仿佛是一架被按下快门的相机,而照片就是产出的Core文件。里面含有当进程被终止时内存、CPU寄存器等信息,可以供后续开发人员进行调试。

 

关于Core产生的原因很多,比如过去一些Unix的版本不支持现代Linux上这种GDB直接附着到进程上进行调试的机制,需要先向进程发送终止信号,然后用工具阅读core文件。在Linux上,我们就可以使用kill向一个指定的进程发送信号或者使用gcore命令来使其主动出Core并退出。如果从浅层次的原因上来讲,出Core意味着当前进程存在BUG,需要程序员修复。从深层次的原因上讲,是当前进程触犯了某些OS层级的保护机制,逼迫OS向当前进程发送诸如SIGSEGV(即signal 11)之类的信号, 例如访问空指针或数组越界出Core,实际上是触犯了OS的内存管理,访问了非当前进程的内存空间,OS需要通过出Core来进行警示,这就好像一个人身体内存在病毒,免疫系统就会通过发热来警示,并导致人体发烧是一个道理(有意思的是,并不是每次数组越界都会出Core,这和OS的内存管理中虚拟页面分配大小和边界有关,即使不出Core,也很有可能读到脏数据,引起后续程序行为紊乱,这是一种很难追查的BUG)。

说了这些,似乎感觉Core很强势,让人感觉缺乏控制力,其实不然。控制Core产生的行为和方式,有两个途径:

1.修改/proc/sys/kernel/core_pattern文件,此文件用于控制Core文件产生的文件名,默认情况下,此文件内容只有一行内容:“core”,此文件支持定制,一般使用%配合不同的字符,这里罗列几种:

%p  出Core进程的PID
%u  出Core进程的UID
%s  造成Core的signal号
%t  出Core的时间,从1970-01-0100:00:00开始的秒数
%e  出Core进程对应的可执行文件名

2.Ulimit –C命令,此命令可以显示当前OS对于Core文件大小的限制,如果为0,则表示不允许产生Core文件。如果想进行修改,可以使用:

Ulimit –cn

其中n为数字,表示允许Core文件体积的最大值,单位为Kb,如果想设为无限大,可以执行:

Ulimit -cunlimited

产生了Core文件之后,就是如何查看Core文件,并确定问题所在,进行修复。为此,我们不妨先来看看Core文件的格式,多了解一些Core文件。

首先可以明确一点,Core文件的格式ELF格式,这一点可以通过使用readelf -h命令来证实,如下图:

从读出来的ELF头信息可以看到,此文件类型为Core文件,那么readelf是如何得知的呢?可以从下面的数据结构中窥得一二:

其中当值为4的时候,表示当前文件为Core文件。如此,整个过程就很清楚了。

了解了这些之后,我们来看看如何阅读Core文件,并从中追查BUG。在Linux下,一般读取Core的命令为:

gdb exec_file core_file

使用GDB,先从可执行文件中读取符号表信息,然后读取Core文件。如果不与可执行文件搅合在一起可以吗?答案是不行,因为Core文件中没有符号表信息,无法进行调试,可以使用如下命令来验证:

Objdump –x core_file | tail

我们看到如下两行信息:

SYMBOL TABLE:

no symbols

表明当前的ELF格式文件中没有符号表信息。

为了解释如何看Core中信息,我们来举一个简单的例子:

#include “stdio.h”

int main(){

int stack_of[100000000];

int b=1;

int* a;

*a=b;

}

这段程序使用gcc –g a.c –o a进行编译,运行后直接会Core掉,使用gdb a core_file查看栈信息,可见其Core在了这行代码:

int stack_of[100000000];

原因很明显,直接在栈上申请如此大的数组,导致栈空间溢出,触犯了OS对于栈空间大小的限制,所以出Core(这里是否出Core还和OS对栈空间的大小配置有关,一般为8M)。但是这里要明确一点,真正出Core的代码不是分配栈空间的int stack_of[100000000], 而是后面这句int b=1, 为何?出Core的一种原因是因为对内存的非法访问,在上面的代码中分配数组stack_of时并未访问它,但是在其后声明变量并赋值,就相当于进行了越界访问,继而出Core。为了解释得更详细些,让我们使用gdb来看一下出Core的地方,使用命令gdb a core_file可见:

可知程序出现了段错误“Segmentation fault”, 代码是int b=1这句。我们来查看一下当前的栈信息:

其中可见指令指针rip指向地址为0×400473, 我们来看下当前的指令是什么:

这条movl指令要把立即数1送到0xffffffffe8287bfc(%rbp)这个地址去,其中rbp存储的是帧指针,而0xffffffffe8287bfc很明显是一个负数,结果计算为-400000004。这就可以解释了:其中我们申请的int stack_of[100000000]占用400000000字节,b是int类型,占用4个字节,且栈空间是由高地址向低地址延伸,那么b的栈地址就是0xffffffffe8287bfc(%rbp),也就是$rbp-400000004。当我们尝试访问此地址时:

可以看到无法访问此内存地址,这是因为它已经超过了OS允许的范围。

下面我们把程序进行改进:

#include “stdio.h”

int main(){

int* stack_of = malloc(sizeof(int)*100000000);

int b=1;

int* a;

*a=b;

}

使用gcc –O3 –g a.c –o a进行编译,运行后会再次Core掉,使用gdb查看栈信息,请见下图:

可见BUG出在第7行,也就是*a=b这句,这时我们尝试打印b的值,却发现符号表中找不到b的信息。为何?原因在于gcc使用了-O3参数,此参数可以对程序进行优化,一个负面效应是优化过程中会舍弃部分局部变量,导致调试时出现困难。在我们的代码中,b声明时即赋值,随后用于为*a赋值。优化后,此变量不再需要,直接为*a赋值为1即可,如果汇编级代码上讲,此优化可以减少一条MOV语句,节省一个寄存器。

此时我们的调试信息已经出现了一些扭曲,为此我们重新编译源程序,去掉-O3参数(这就解释了为何一些大型软件都会有debug版本存在,因为debug是未经优化的版本,包含了完整的符号表信息,易于调试),并重新运行,得到新的core并查看,如下图:

这次就比较明显了,b中的值没有问题,有问题的是a,其指向的地址是非法区域,也就是a没有分配内存导致的Core。当然,本例中的问题其实非常明显,几乎一眼就能看出来,但不妨碍它成为一个例子,用来解释在看Core过程中,需要注意的一些问题。

Linux下打开core文件,定位segfault

出现段错误,不容易定位到底是哪行代码出现了问题,segfault多次折磨的笔者死去活来,

查资料发现了定位段错误的方法。
 
    Linux下有核心转储文件即core文件,会把程序崩溃是的现场保存起来供gdb来调试。
打开的办法是 ulimit -c unlimited 。
    设置之前可以调用ulimit -c 查看当前的大小,如果是0,表示不生成core文件。unlimited 的含义是不论生成的core文件有多大,我都让系统生成core 文件。
 
    当时这个方法有个弊端是,在那个终端上设置的ulimit就在那个终端上生效,如果你在另一个终端上执行程序,你会发现,纵然有段错误,你也没生成core文件
 
    第二个办法是修改 /root/.bash_profile 文件添加一行  ulimit -S -c  unlimited 然后保存关闭文件。
 
     有很多linux系统/root 目录下并没有.bash_profile 文件,比如SUSE,这没有关系你自己vi 创建这个文件即可。
 
    修改完这个文件之后,执行source /root/.bash_profile,你就可以查看你的修改生效了没有。
    
    查看方法是 ulimit -c.你会发现,终端打印出 unlimited,表示你的配置生效了,你可以新开终端 执行 ulimit -c,发现新开终端也是unlimited 。
 
 
好,打开开关之后,你就可以跑你的有segfault的代码了。
一般core文件会生成在你的可执行文件所在的目录下。当然可以设定。
设定的方法是 修改 /proc/sys/kernel/core_pattern
这个文件不支持vi的方式修改,可以使用echo
如echo “/corefile/core-%p-%e-%t” > /proc/sys/kernel/core_pattern
这个语句的含义是将core文件生成在 /corefile/这个目录下
生成的文件名的格式是:“core”-“pid”-可执行程序名-段错误时间
%p ———段错误进程的PID 
%e———–发生段错误的可执行文件名
%t——- 发生段错误的时间
 
还有其他配置选项。
生成了core文件,你就可以调试了,调试方法是:
 
gdb -c core test
注test是你的可执行文件名。你就可以想用gdb调试文件一样调试你的core文件了。