分类:未分类


mysql命令行不再提醒警告


在Linux命令行里输入mysql 用户密码就会提示:

Warning: Using a password on the command line interface can be insecure.

即使我们用2>想给它重定向也无济于事。

 

有一个方法可以隐藏掉这个警告。

mysql_config_editor set –user=root –socket=/tmp/mysql.sock –password 回车

输入密码即可,这样就可以把密码保存到一个配置文件里 ~/.mylogin.cnf, 这个文件不能直接cat查看

 

以后再次使用mysql时,直接敲mysql命令即可完成登录。

搭建一个局域网http的yum源


1. 先创建一个目录,作为存在rpm包的目录
mkdir /data/yumdata
2. 拷贝ISO镜像文件中的rpm包到/data/yumdata/
mount /dev/cdrom /mnt/
cp /mnt/Packages/*rpm /data/yumdata
【小常识】 可以在/data/yumdata/下面创建子目录,然后把rpm包放到子目录下面,也可以被识别到
3. 创建repository
createrepo /data/yumdata/
如果rpm包有增加,需要执行
createrepo --update /data/yumdata/
4. 安装nginx,提供http服务
yum install epel-release
yum install nginx
当然,如果无法使用yum,需要下载nginx源码包,并编译安装
配置nginx.conf,使其可以通过IP地址访问到/data/yumdata
参考配置文件:
    server {
        listen       80 default_server;
        listen       [::]:80 default_server;
        server_name  _;
        root         /data/yumdata;

        location / {
            autoindex on;  //这一步必须要有,这是为了提供目录浏览
        }

        error_page 404 /404.html;
            location = /40x.html {
        }

        error_page 500 502 503 504 /50x.html;
            location = /50x.html {
        }
    }
5. 客户端上配置repo文件
cd /etc/yum.repos.d
mkdir bak
mv *.rep  bak/  //把系统自带的repo挪走
vim my.repo //内容如下
[aming]
name=myserver
baseurl=http://192.168.133.140
gpgcheck=0
enabled=1
6.  做一个镜像yum源
比如可以把系统默认的源给镜像到局域网来,思路是用rsync工具把远程的rpm包同步到这台局域网的对应目录下即可。
rsync -av rsync://mirrors.ustc.edu.cn/centos/7/os/x86_64/Packages/  /data/yumdata/
7. 当局域网的rpm库有更新时,除了服务端执行“createrepo  –update /data/yumdata/”外,客户端上也需要执行
yum  clean all   //删除缓存

Nginx代理tomcat https跳转到http


问题

网站采用了 Nginx 反向代理 Tomcat 的方式来负载均衡。

Nginx使用https,默认端口443。Tomcat使用http,端口8080

结果今天后台操作停留时间过长session超时后,跳转到登录页面时出现无法访问错误。如图:

http_error

分析

可以看到,出错的原因应该是跳转的时候加上了web默认80端口,而https默认的端口并不是80,所以导致无法访问。

后台无session时跳转代码:

  1. requestURL = HttpUtils.encodeUrl(requestURL);
  2. String path = request.getContextPath();
  3. String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + path
  4. + "/";
  5. return basePath + "user/login?goUrl=" + requestURL;

代码跳转的是绝对路径,包括schema和port,问题应该就在这里。

经过调试发现获取到的request.getScheme()总是http而非实际的https,相应的request.getServerPort()也就成了80。

端口是80而非Tomcat本身的8080说明获取到的确实是nginx转发过来的请求,那schema为什么得不到呢?

解决

通过查找资料,发现nginx请求转发时必须设置proxy_set_header才能让被代理的tomcat获取到正确的客户端ip等信息。

但是之前已经设置过nginx的proxy_set_header,而获取ip等也毫无问题:

  1. proxy_set_header X-Real-IP $remote_addr;
  2. proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  3. proxy_set_header Host $http_host;
  4. proxy_set_header X-Forwarded-Proto $scheme;
  5. proxy_redirect http:// $scheme://;
  6. proxy_pass http://localhost:8080;#转向tomcat处理

上面有一行设置了proxy_redirect,所以浏览器地址栏的schema仍然是https.

接着查找资料,发现要传递schema,在Tomcat端也需要相应配置。

  1. <Engine>
  2. <Valve className="org.apache.catalina.valves.RemoteIpValve"
  3. remoteIpHeader="X-Forwarded-For"
  4. protocolHeader="X-Forwarded-Proto"
  5. protocolHeaderHttpsValue="https"/>
  6. </Engine >

加上后,重启Tomcat,一切正常了!

你不知道的vim小秘密


大家先了解一些背景知识:

1) 给文件增加了i权限,那文件不能被更改,不能删除,也不能修改名字以及权限。

2) 给文件增加a权限,文件可以追加内容,不能删除,不能修改内容,不能修改名字以及权限。

3) vim一个文件,如果不正常退出,再次编辑时是会提示一些信息的,并且有一个隐藏的文件.xxx.swp

了解以上知识后,再来看下面的现象:

1) 如果给一个文件增加a权限,用vim编辑文件,增加内容(注意是在文件末尾增加内容,不要修改其他内容),并不会成功。

2) 如果给一个目录增加i权限或者a权限,在该目录下面vim一个文件,更改文件内容可以正常保存。

既然a权限可以追加内容,那为何vim一个文件在末尾增加内容不能成功?既然i权限不能修改,那为何在目录里面变更文件内容却可以成功?

关于这两点,你有没有疑惑?下面我们来分析原因。

先不管i或者a权限,在一个没有i或者a权限的目录下,编辑一个没有i或者a权限的文件,用strace来查看其执行过程。

mkdir /tmp/test

strace vim /tmp/test/aminglinux.txt 2>/tmp/vim.log

写入一个数字1,然后保存退出。再来查看vim.log的内容。

less /tmp/vim.log

大部分内容你不用关心,只需要看这几行:

stat(“/tmp/test/aminglinux.txt”, 0x7fff072ecb10) = -1 ENOENT (No such file or directory)

access(“/tmp/test/aminglinux.txt”, W_OK) = -1 ENOENT (No such file or directory)

open(“/tmp/test/aminglinux.txt”, O_RDONLY) = -1 ENOENT (No such file or directory)

readlink(“/tmp/test/aminglinux.txt”, 0x7fff072eb360, 4095) = -1 ENOENT (No such file or directory)

open(“/tmp/test/.aminglinux.txt.swp”, O_RDONLY) = -1 ENOENT (No such file or directory)

open(“/tmp/test/.aminglinux.txt.swp”, O_RDWR|O_CREAT|O_EXCL, 0600) = 3

open(“/tmp/test/.aminglinux.txt.swx”, O_RDONLY) = -1 ENOENT (No such file or directory)

open(“/tmp/test/.aminglinux.txt.swx”, O_RDWR|O_CREAT|O_EXCL, 0600) = 4

unlink(“/tmp/test/.aminglinux.txt.swx”) = 0

unlink(“/tmp/test/.aminglinux.txt.swp”) = 0

stat(“/tmp/test/.aminglinux.txt.swp”, 0x7fff072ec310) = -1 ENOENT (No such file or directory)

lstat(“/tmp/test/.aminglinux.txt.swp”, 0x7fff072ec3e0) = -1 ENOENT (No such file or directory)

lstat(“/tmp/test/.aminglinux.txt.swp”, 0x7fff072ec8a0) = -1 ENOENT (No such file or directory)

open(“/tmp/test/.aminglinux.txt.swp”, O_RDWR|O_CREAT|O_EXCL|O_NOFOLLOW, 0600) = 3

stat(“/tmp/test/aminglinux.txt”, 0x7fff072eac40) = -1 ENOENT (No such file or directory)

stat(“/tmp/test/aminglinux.txt”, 0x7fff072ebe20) = -1 ENOENT (No such file or directory)

stat(“/tmp/test/aminglinux.txt”, 0x7fff072eadf0) = -1 ENOENT (No such file or directory)

write(1, “\”/tmp/test/aminglinux.txt\””, 26) = 26

stat(“/tmp/test/aminglinux.txt”, 0x7fff072ec050) = -1 ENOENT (No such file or directory)

open(“/tmp/test/aminglinux.txt”, O_WRONLY|O_CREAT|O_TRUNC, 0666) = 4

stat(“/tmp/test/aminglinux.txt”, {st_mode=S_IFREG|0644, st_size=2, …}) = 0

stat(“/tmp/test/aminglinux.txt”, {st_mode=S_IFREG|0644, st_size=2, …}) = 0

unlink(“/tmp/test/.aminglinux.txt.swp”) = 0

看起来乱乱的,其实大概的过程就是vim  /tmp/test/aminglinux.txt时,先看有没有.aminglinux.txt.swp以及.aminglinux.txt.swx,因为这两个文件就是vim产生的临时文件,swp先产生,如果swp存在就产生第二个swx。写入的内容先存到swp里,当保存退出vim时,再把swp的内容存到aminglinux.txt里,最后删除掉swp文件。

有了这个认识之后,我们再来分析上面提到的现象1。如果文件给了a权限,那么在编辑该文件时,会产生swp文件,当保存退出时,swp文件内容会写入该文件,这相当于更改该文件,很线上a权限是不允许的。

再来分析现象2,按照我们的推测,如果目录给了a权限,增加文件没问题,也就是说产生swp或者swx文件没有问题,当然把swp或者swx内容写入到文件里时也不会有问题,但swp或者swx文件却不会被删除了,所以再次编辑文件时就会提示临时文件已经存在了。但这并不会影响修改文件内容。

如果给目录设置了i权限的话,vim编辑文件,要产生swp或swx肯定会出错啊,但为何依然能正常编辑文件? 下面继续用strace来分析一下。

chattr +i  /tmp/test

strace vim /tmp/test/aminglinux.txt 2> /tmp/vim.log

看vim.log里面和aminglinux.txt相关的信息

stat(“/tmp/test/aminglinux.txt”, {st_mode=S_IFREG|0644, st_size=4, …}) = 0

stat(“/tmp/test/aminglinux.txt”, {st_mode=S_IFREG|0644, st_size=4, …}) = 0

stat(“/tmp/test/aminglinux.txt”, {st_mode=S_IFREG|0644, st_size=4, …}) = 0

stat(“/tmp/test/aminglinux.txt”, {st_mode=S_IFREG|0644, st_size=4, …}) = 0

access(“/tmp/test/aminglinux.txt”, W_OK) = 0

open(“/tmp/test/aminglinux.txt”, O_RDONLY) = 3

readlink(“/tmp/test/aminglinux.txt”, 0x7fff49efc6f0, 4095) = -1 EINVAL (Invalid argument)

open(“/tmp/test/.aminglinux.txt.swp”, O_RDONLY) = -1 ENOENT (No such file or directory)

open(“/tmp/test/.aminglinux.txt.swp”, O_RDWR|O_CREAT|O_EXCL, 0600) = -1 EACCES (Permission denied)

stat(“/tmp/test/.aminglinux.txt.swp”, 0x7fff49efd6a0) = -1 ENOENT (No such file or directory)

lstat(“/tmp/test/.aminglinux.txt.swp”, 0x7fff49efd770) = -1 ENOENT (No such file or directory)

lstat(“/tmp/test/.aminglinux.txt.swp”, 0x7fff49efdc30) = -1 ENOENT (No such file or directory)

open(“/tmp/test/.aminglinux.txt.swp”, O_RDWR|O_CREAT|O_EXCL|O_NOFOLLOW, 0600) = -1 EACCES (Permission denied)

readlink(“/tmp/test/aminglinux.txt”, 0x7fff49efc6f0, 4095) = -1 EINVAL (Invalid argument)

open(“/root/tmp/aminglinux.txt.swp”, O_RDONLY) = -1 ENOTDIR (Not a directory)

open(“/root/tmp/aminglinux.txt.swp”, O_RDWR|O_CREAT|O_EXCL, 0600) = -1 ENOTDIR (Not a directory)

stat(“/root/tmp/aminglinux.txt.swp”, 0x7fff49efd6a0) = -1 ENOTDIR (Not a directory)

lstat(“/root/tmp/aminglinux.txt.swp”, 0x7fff49efd770) = -1 ENOTDIR (Not a directory)

lstat(“/root/tmp/aminglinux.txt.swp”, 0x7fff49efdc30) = -1 ENOTDIR (Not a directory)

open(“/root/tmp/aminglinux.txt.swp”, O_RDWR|O_CREAT|O_EXCL|O_NOFOLLOW, 0600) = -1 ENOTDIR (Not a directory)

readlink(“/tmp/test/aminglinux.txt”, 0x7fff49efc6f0, 4095) = -1 EINVAL (Invalid argument)

open(“/var/tmp/aminglinux.txt.swp”, O_RDONLY) = -1 ENOENT (No such file or directory)

open(“/var/tmp/aminglinux.txt.swp”, O_RDWR|O_CREAT|O_EXCL, 0600) = 4

open(“/var/tmp/aminglinux.txt.swx”, O_RDONLY) = -1 ENOENT (No such file or directory)

open(“/var/tmp/aminglinux.txt.swx”, O_RDWR|O_CREAT|O_EXCL, 0600) = 5

unlink(“/var/tmp/aminglinux.txt.swx”)   = 0

unlink(“/var/tmp/aminglinux.txt.swp”)   = 0

stat(“/var/tmp/aminglinux.txt.swp”, 0x7fff49efd6a0) = -1 ENOENT (No such file or directory)

lstat(“/var/tmp/aminglinux.txt.swp”, 0x7fff49efd770) = -1 ENOENT (No such file or directory)

lstat(“/var/tmp/aminglinux.txt.swp”, 0x7fff49efdc30) = -1 ENOENT (No such file or directory)

open(“/var/tmp/aminglinux.txt.swp”, O_RDWR|O_CREAT|O_EXCL|O_NOFOLLOW, 0600) = 4

chmod(“/var/tmp/aminglinux.txt.swp”, 0644) = 0

open(“/tmp/test/aminglinux.txt”, O_RDONLY) = 3

stat(“/tmp/test/aminglinux.txt”, {st_mode=S_IFREG|0644, st_size=4, …}) = 0

stat(“/tmp/test/aminglinux.txt”, {st_mode=S_IFREG|0644, st_size=4, …}) = 0

stat(“/tmp/test/aminglinux.txt”, {st_mode=S_IFREG|0644, st_size=4, …}) = 0

access(“/tmp/test/aminglinux.txt”, W_OK) = 0

write(1, “\”aminglinux.txt\””, 16)      = 16

stat(“aminglinux.txt”, {st_mode=S_IFREG|0644, st_size=4, …}) = 0

access(“aminglinux.txt”, W_OK)          = 0

getxattr(“aminglinux.txt”, “system.posix_acl_access”, 0x7fff49efd050, 132) = -1 ENODATA (No data available)

stat(“aminglinux.txt”, {st_mode=S_IFREG|0644, st_size=4, …}) = 0

open(“aminglinux.txt”, O_WRONLY|O_CREAT|O_TRUNC, 0644) = 3

chmod(“aminglinux.txt”, 0100644)        = 0

setxattr(“aminglinux.txt”, “system.posix_acl_access”, “\x02\x00\x00\x00\x01\x00\x06\x00\xff\xff\xff\xff\x04\x00\x04\x00\xff\xff\xff\xff \x00\x04\x00\xff\xff\xff\xff”, 28, 0) = 0

stat(“/tmp/test/aminglinux.txt”, {st_mode=S_IFREG|0644, st_size=6, …}) = 0

unlink(“/var/tmp/aminglinux.txt.swp”)   = 0

我相信你可以看到Permission denied的提示,这是因为当前目录有i权限,不能增加文件,也就不能在当前目录下生成临时文件。当然,vim如果遇到这样的问题,它还是会“曲线救国”的,于是先找/root/tmp/,但是并没有该目录,只好继续找/var/tmp/,这个目录存在,所以就在这个目录里生成了临时文件(并不是隐藏的)。后面的操作就不用多说了。

既然vim可以在/var/tmp/下生成临时文件,自然也可以在已经设置了i权限的目录里编辑文件的,这样现象2也解释通了。上面罗嗦了这么多,其实我就想表达如下观点:

vim编辑文件时,会在该文件所在目录生成临时隐藏文件.swp和.swx,如果那目录不可写就会到/root/tmp/下或者/var/tmp/下生成临时文件(非隐藏),当编辑的文件保存后,临时文件删除。

这个命令你看得懂么


直接上命令

history 1 | { read x cmd; echo “$cmd”; }

对于read命令的了解,仅限于在shell脚本中使用read -p,对于这种用法从来没用过,更不知道它的含义。

今天抽空琢磨了一下,终于搞明白了来龙去脉。先来写几个命令吧:

# read a b c

1 2 3

# echo $a

1

# echo $b

2

# echo $c

3

# read x y

1 2 3

# echo $x

1

# echo $y

2 3

到这,也许你似乎看出一些门道了。read命令,后面跟的是变量名,可以是1个,也可以是多个,用空格分隔。回车后,输入的字符就是在给这些变量赋值,输入的字符串也需要用空格分隔,如果和上面的变量名一个一个地对应,那么这样就一个一个地赋值了。

如果没有对应呢?通过上面的命令也可以发现,如果值比变量多,它只对应前面的。例如,变量只有x和y,而值是1 2 3,则它把x赋值1,后面的2 3一股脑赋值给最后面的变量y。

再来一个例子吧。

# echo 1 2 3 4 5|read a b c

# echo $a

1

# echo $b

2

# echo $c

3 4 5

再回头看这条命令:

history 1 | { read x cmd; echo “$cmd”; }

管道前面,history 1表示取最后一条命令。管道后面'{ }’内是一整体,相当于一个函数,函数里面可以有多条命令,用分号分割,而且最后一条命令也必须加分号。第一条命令和'{‘之间必须要有空格。

iptables,netfilter,firewalld关系


在centos6上,我们用的是iptables服务,而在centos7上,我们用的是firewalld服务。同样的,centos6上安装的是iptables包,而centos7上安装的是firewalld包。

不管是centos6还是centos7,核心其实都是netfilter,netfilter是linux的一个内核模块,iptables命令是linux内核自带的。

centos6上的iptables服务和centos7上的firewalld服务,其实都是用来定义防火墙规则功能的防火墙管理工具。它们都是将定义好的规则交由内核中的netfilter即网络过滤器来读取,从而真正实现防火墙功能,所以其实在配置规则的思路上是完全一致的。

MySQL延迟主从复制


世界上没有卖后悔药的,一旦做错了,后悔莫及。我们作为运维,尤其是不小心误删除数据库里的数据时,那更是损失巨大。对于MySQL来说,这里有一种方法,可以避免这种悲剧的发生。

这儿所谓的延迟,并不是经常说的网络延迟,而是我们故意把从库复制的步伐放慢,比如让从库比主库慢30分钟。这样,如果在半小时内发现数据有问题,还能补救。

MySQL 5.6 已经支持延迟复制, 可设置备节点的延迟时间, 延迟复制是有意义的,例如防止主节点数据误删,查看数据库历史状态等。

配置也不难,做完主从后,再加上这句:

CHANGE MASTER TO MASTER_DELAY = N;

这里的N单位是秒,这样从库则会比主库延时N秒。做完之后,在show slave status的时候,就可以看到SQL_Delay的值。

SQL_Delay: xxx

正则表达式非贪婪匹配


我们学习正则时,用到过贪婪匹配.*
其实,还有一个相对的概念,叫做非贪婪匹配。

如:str=”abcaxc”
  p=”a.*c”
  
贪婪匹配:正则表达式一般趋向于最大长度匹配,也就是所谓的贪婪匹配。如上面使用模式p匹配字符串str,结果就是匹配到:abcaxc。
  
非贪婪匹配:就是匹配到结果就好,最少的匹配字符。如上面使用模式p匹配字符串str,结果就是匹配到:abc(a.*c)。

shell中如何区分两种模式?

默认是贪婪模式;
在量词后面直接加上一个问号?就是非贪婪模式。

常用的量词有:

{m,n}: m到n个
*: 任意多个
+: 一个到多个
?: 0或一个

示例:

str=”123abc0axc456″
echo $str|grep ‘a.*c’
结果: 123abc0axc456

echo $str|grep -P ‘a.*?c’
结果: 123abc0axc456


elastic stack 使用redis作为日志缓冲


直接上步骤:

1、首先建立一个配置文件,将output的数据写到redis中,而不是ES了。配置文件示例:

input {
    stdin { }
}
output {
    redis {
        host => "192.168.1.100"  #redis服务器地址
        port => "6379"  #redis服务端口
        db => "5"  #使用redis第五个库,用其他库也可以
        data_type => "list"  #用list类型
        key => "demo"  #存储的key名,可自定义自己喜欢的
    }
}

 

2、指定配置文件然后运行Logstash,然后我们在标准输入中随便写点什么东西,这个时候输入的数据都会提交到redis中。登录redis后使用info命令可以查看keyspace相关信息,可以看到db5的keys数量有了变化,我们切换到db5再来看看

select 5
keys *

 

3、通过keys *命令可以看到有了一个key叫做demo,这个就是配置文件里设置的key名,使用LLEN demo可以看到该key的长度,input如果输入了100行,那么这个key就应该有100行

4、再次建立一个配置文件,这次input是从第一步里的redis里读取数据,然后output到ES中

input {
    redis {
        host => "192.168.1.100"  
        port => "6379"  
        db => "5"  
        data_type => "list"
        key => "demo" 
    }
}
output {
    elasticsearch {
        hosts => ["192.168.0.100:9200"]
        index => "redis-demo-%{+YYYY.MM.dd}"
    }
}

 

5、运行该配置文件后去redis看看,之前的队列因为被output到了ES中,所以队列会消失掉或者是迅速减少长度

下面是一个改造后的配置文件,更符合实际需求

input {
    file {
        path => "/var/log/messages"
        type => "system"
}
output {
    if [type] == "system" {
        redis {
          host => "192.168.1.100"
          port => "6379"
          db => "5" 
          data_type => "list" 
          key => "system"
    }
}

linux shell命名管道FIFO


在shell脚本中,我们想要实现多进程高并发,最简单的方法是把命令丢到后台去,如果量不大的话,没问题。 但是如果有几百个进程同一时间丢到后台去就很恐怖了,对于服务器资源的消耗非常大,甚至导致宕机。

那有没有好的解决方案呢? 当然有!

我们先来学习下面的常识。

1 文件描述符

文件描述符(缩写fd)在形式上是一个非负整数。实际上,它是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表。当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符。每一个unix进程,都会拥有三个标准的文件描述符,来对应三种不同的流:

除了上面三个标准的描述符外,我们还可以在进程中去自定义其他的数字作为文件描述符,后面例子中会出现自定义数字。每一个文件描述符会对应一个打开文件,同时,不同的文件描述符也可以对应同一个打开文件;同一个文件可以被不同的进程打开,也可以被同一个进程多次打开。

我们可以写一个测试脚本/tmp/test.sh,内容如下:

#!/bin/bash

echo “该进程的pid为$$”

exec 1>/tmp/test.log 2>&1

ls -l /proc/$$/fd/

执行该脚本 sh /tmp/test.sh,然后查看/tmp/test.log内容为:

总用量 0

lrwx—— 1 root root 64 11月 22 10:26 0 -> /dev/pts/3

l-wx—— 1 root root 64 11月 22 10:26 1 -> /tmp/test.log

l-wx—— 1 root root 64 11月 22 10:26 2 -> /tmp/test.log

lr-x—— 1 root root 64 11月 22 10:26 255 -> /tmp/test.sh

lrwx—— 1 root root 64 11月 22 10:26 3 -> socket:[196912101]

其中0为标准输入,也就是当前终端pts/3,1和2全部指向到了/tmp/test.log,另外两个数字,咱们暂时不关注。

2 命名管道

我们之前接触过的管道“1”,其实叫做匿名管道,它左边的输出作为右边命令的输入。这个匿名管道只能为两边的命令提供服务,它是无法让其他进程连接的。

实际上,这两个进程(cat和less)并不知道管道的存在,它们只是从标准文件描述符中读取数据和写入数据。

另外一种管道叫做命名管道,英文(First In First Out,简称FIFO)。

FIFO本质上和匿名管道的功能一样,只不过它有一些特点:

1)在文件系统中,FIFO拥有名称,并且是以设备特俗文件的形式存在的;

2)任何进程都可以通过FIFO共享数据;

3)除非FIFO两端同时有读与写的进程,否则FIFO的数据流通将会阻塞;

4)匿名管道是由shell自动创建的,存在于内核中;而FIFO则是由程序创建的(比如mkfifo命令),存在于文件系统中;

5)匿名管道是单向的字节流,而FIFO则是双向的字节流;

有了上面的基础知识储备后,下面我们来用FIFO来实现shell的多进程并发控制。

需求背景:

领导要求小明备份数据库服务器里面的100个库(数据量在几十到几百G),需要以最快的时间完成(5小时内),并且不能影响服务器性能。

需求分析:

由于数据量比较大,单个库备份时间少则10几分钟,多则几个小时,我们算平均每个库30分钟,若一个库一个库的去备份,则需要3000分钟,相当于50个小时。很明显不可取。但全部丢到后台去备份,100个并发,数据库服务器也无法承受。所以,需要写一个脚本,能够控制并发数就可以实现了。

控制并发的shell脚本示例:

#!/bin/sh

function a_sub {
    sleep 2;
    endtime=`date +%s`
    sumtime=$[$endtime-$starttime]
    echo "我是$i,运行了2秒,整个脚本已经执行了$sumtime秒"
}

starttime=`date +%s`
export starttime

##其中$$为该进程的pid
tmp_fifofile="/tmp/$$.fifo"

##创建命名管道
mkfifo $tmp_fifofile

##把文件描述符6和FIFO进行绑定
exec 6<>$tmp_fifofile

##绑定后,该文件就可以删除了
rm -f $tmp_fifofile

##并发量为8,用这个数字来控制并发数
thread=8

for ((i=0;i<$thread;i++));
do
    ##写一个空行到管道里,因为管道文件的读取以行为单位
    echo >&6
done

##循环100次,相当于要备份100个库
for ((i=0;i<100;i++))
do
    ##读取管道中的一行,每次读取后,管道都会少一行
    read -u6
    {
        a_sub || {echo "a_sub is failed"}
        ##每次执行完a_sub函数后,再增加一个空行,这样下面的进程才可以继续执行
        echo >&6
    } & ##这里要放入后台去,否则并发实现不了
done

##这里的wait意思是,需要等待以上所有操作(包括后台的进程)都结束后,再往下执行。
wait

##关闭文件描述符6的写
exec 6>&-