在mac中通过shell弹窗或发通知

Mac中有个AppleScript,通过它可以实现弹窗或系统通知

弹窗

osascript -e 'display alert "告警!!" as critical'
  • osascript -e 是执行一段apple script
  • display alert是弹窗
  • as critcal是用来控制图标,可以as三个值(informational,warning,critical,默认是informational)

发通知

osascript -e 'display notification "通知内容" with title "标题" subtitle "子标题" sound name "Glass"'
  • display notification 是发通知的命令后面紧跟着通知内容
  • with title 可以修改通知标题(默认是“脚本编辑器”)
  • subtitle 可以添加一个子标题(默认没有)
  • sound name 可以用来选择一种通知的声音,声音存放在/System/Library/Sounds

结合PHP实战

<?php
ini_set('date.timezone','PRC');

$host = 'mysql.example.org';
$port = '3306';
$dbname = 'test';
$username = 'root';
$password = '123456';
$pdo = new PDO("mysql:host={$host};port={$port};dbname={$dbname};charset=UTF8", $username, $password);

$sql = <<<SQL
SELECT u.uid, p.privilege_id, u.updated_at 
FROM user_privilege AS p 
LEFT JOIN user AS u ON u.uid = p.uid
WHERE u.del = 1 and p.del = 0;
SQL;

$statement = $pdo->query($sql);
$result = $statement->fetchAll(PDO::FETCH_ASSOC);
$new = [];

$logDir = __DIR__ . '/log/';
if (!file_exists($logDir)) {
    mkdir($logDir, 0755, true);
}
foreach ($result as $item) {
    $fileName = $logDir . '/' . $item['uid'] . '.json';
    if (!file_exists($fileName)) {
        file_put_contents($fileName, json_encode($item, JSON_PRETTY_PRINT));
        $new[] = $item['uid'];
    }
}
if ($new) {
    $msg = sprintf('%s发现新异常数据%d条!(%s)', date('Y-m-d H:i:s'), count($new), implode(', ', $new));
    $cmd = <<<CMD
osascript -e 'tell application "System Events" to display alert "{$msg}" as critical'
CMD;
    exec($cmd);
}

这个脚本连接一个远程的数据库,检查是否出现用户被删除,但是权限没有被删除的情况,如果发现将它记录到日志并弹窗提示。这里增加了tell application "System Events" to这么个前缀,是指以“System Events”这个应用弹这个窗,这是因为好像有什么机制会阻止cron任务中直接弹窗,需要借用另一个应用的身份。也可以借用”Finder”,发弹窗时Finder会跳动一下,但是弹窗需要点Finder才能出来,而使用System Event可以直接弹出来。

接下来可以在执行crontab -e来增加个计划任务

MAILTO=""
*/1 * * * * php ~/check.php > /dev/null
  • MAILTO=”” 是执行crontab不发执行结果的系统邮件
  • */1是指每1分钟执行一次,剩下的*分别代表:每小时(0~23)/每天(1~31)/每月(1~12)/每周几(0~6)

解决git的error: cannot lock ref问题

今天更新代码时出现了这个错误信息

error: cannot lock ref 'refs/remotes/origin/xxxx/log': 'refs/remotes/origin/xxxx' exists; cannot create 'refs/remotes/origin/xxxx/log'
From ssh://ssh.gitlab.oooo.com:22/MyGroup/composer
 ! [new branch]      xxxx/log -> origin/xxxx/log  (unable to update local ref)

出现这个问题的原因是,之前远程有一个xxxx分支,后来别人删掉远端了xxxx分支,又建了一个xxxx/log分支,但是本地还有xxxx的信息。这样就出现了git分支名冲突的问题,类似于文件系统中一个路径不可能既是文件又是目录。

这时需要执行这条命令:

git update-ref -d refs/remotes/origin/xxxx

单独更新一下本地的xxxx信息

nginx+fpm+upstream调优

最近发现服务器经常出现502的问题,但是并发量并没有达到那么高(fpm设置了500个进程)。看了一下nginx的配置

upstream fastcgi_backend {
    server 127.0.0.1:9000;
    server 127.0.0.2:9000;
    server 127.0.0.3:9000;
    server 127.0.0.4:9000;
}

4个IP的问题,问了运维,说是为了增加可以支撑连接数,但实际上这四个IP都是本地的fpm(我感觉这波优化没什么卵用)fpm总共500个,端口也能即时回收,所以应该也不是端口沾满的问题。

后来看日志,发现有一些timeout的请求,而且每一波502的请求附近都会有一些504,但是504也不算太多,不会将所有的fpm进程都占满。

看了nginx的文档后发现如果一个server无法处理响应了,会使用其它的server重试,并且多次无法处理响应(默认1次),nginx会临时将这个节点摘掉(默认10秒)。而fpm超时就被nginx认为是无法处理响应了,这时理论上会重试4次(相当于放大了问题)。

这就意味着如果10秒内有4个请求造成了fpm超时就会造成500个fpm进程都被认为是不可用。

优化程序是一种方案(正常来说不应该这么慢)。另外也可以优化一下nginx的配置

upstream fastcgi_backend {
    server 127.0.0.1:9000 max_fails=0 fail_timeout=0;
}

鉴于我对4个IP的不认同,我将IP改回了一个。并且设置mx_fails=0即无论fpm是否可用都将请求转发至fpm,我觉得这样也没什么问题,如果fpm真的挂了,nginx应该也能处理。fail_timeout是一个无关紧要的参数,即如果服务挂了临时摘掉多少秒,因为mx_fails=0,所以这个参数设不设都可以。

最后/usr/local/openresty/nginx/sbin/nginx -t校验,/usr/local/openresty/nginx/sbin/nginx -s reload重新加载配置,问题解决。

启动和停止服务脚本

约定

  • /opt/start.sh :通用启动脚本,第一个参数指定应用名,如/opt/start.sh myapp
  • /opt/stop.sh :通用停止脚本,第一个参数指定应用名,如/opt/stop.sh myapp
  • /opt/<app_name>/<app_name>.jar :启动的应用jar包
  • /opt/<app_name>/<app_name>.pid :启动后的进程id,由start.sh自动生成
  • /opt/<app_name>/log/stdout.log :应用的标准输出,由start.sh自动生成
  • /opt/<app_name>/log/stderr.log :应用的错误输出,由start.sh自动生成
  • /opt/<app_name>.runuser :当以root运行start.sh时,应用的运行用户,如果没有这个文件将以root运行

启动脚本(start.sh)

#!/bin/bash
APP_NAME=$1
BASE_DIR=$(readlink -f $(dirname "$0"))
APP_DIR=${BASE_DIR}/${APP_NAME}
JAR_FILE=${APP_DIR}/${APP_NAME}.jar
PID_FILE=${APP_DIR}/${APP_NAME}.pid
LOG_DIR=${APP_DIR}/log
RUN_USER_FILE="${BASE_DIR}/${APP_NAME}.runuser"

RUN() {
    if [ ${RUN_USER} ]; then
        SCRIPT="$@"
        return $(runuser -l ${RUN_USER} -c "${SCRIPT}" 2>/dev/null)
    else
        return $@
    fi
}

if [ ! ${APP_NAME} ]; then
    echo "App name not found!";
    exit 1;
fi

if [ ! -f ${JAR_FILE} ]; then
    echo "Jar file not found: ${JAR_FILE}";
    exit 1;
fi

if [ -f ${PID_FILE} ]; then
    echo "App is running!: $(cat ${PID_FILE})";
    exit 1;
fi

RUN_USER=$(whoami)
if [ ${RUN_USER} == 'root' ]; then
    if [ -f ${RUN_USER_FILE} ]; then
        RUN_USER=$(cat ${RUN_USER_FILE});
    fi
else
    RUN_USER=''
fi

if [ ! -d ${LOG_DIR} ]; then
    if ! RUN "mkdir ${LOG_DIR}"; then
        echo "Start failed: can not create log directory.";
        exit 1;
    fi
fi

cd ${APP_DIR};
if ! RUN "nohup java -jar ${JAR_FILE} > ${LOG_DIR}/stdout.log 2>${LOG_DIR}/stderr.log & echo \$! > ${PID_FILE}" ; then
    echo "Start failed: run failed!";
    exit 1;
fi

echo "Started: ${JAR_FILE}";

停止脚本(stop.sh)

#!/bin/bash
APP_NAME=$1
BASE_DIR=$(dirname "$0")/${APP_NAME}
PID_FILE=${BASE_DIR}/${APP_NAME}.pid
if [ ! ${APP_NAME} ]; then
    echo "App name not found!";
    exit 1;
fi

if [ -f ${PID_FILE} ]; then
    PID=$(cat ${PID_FILE});
    echo kill ${PID}
    kill ${PID};
    rm -f ${PID_FILE};
else
    echo "Not running...";
fi
exit 0;

ubuntu服务器初始化脚本

最近买了几台云主机,写了个脚本初始化用户和权限

  1. 创建一个无密码的新用户
  2. 开启SSH登录
  3. 设为SUDOUser
  4. 禁用root用户
#!/bin/bash
NEWUSER='fyn'
IDRSA_PUB="*****"
useradd -m ${NEWUSER}
mkdir /home/${NEWUSER}/.ssh
echo ${IDRSA_PUB} > /home/${NEWUSER}/.ssh/authorized_keys
chown ${NEWUSER}:${NEWUSER} -R /home/${NEWUSER}
chmod 0600 /home/${NEWUSER}/.ssh/authorized_keys
echo -e "\n${NEWUSER} ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers
chsh -s /bin/bash ${NEWUSER}
echo -e "\n127.0.0.1 $(hostname)" >> /etc/hosts
passwd -l root

nginx反向代理426

nginx反向代理默认用的http1.0,如果尚有不支持http1.0,需要增加proxy_http_version 1.1选项来指定版本

location /api {
    proxy_http_version 1.1;
    proxy_pass http://192.168.1.30:8080/;
}

根据端口汇总连接ip

netstat -ant|grep -v TIME_WAIT|awk -F '[[:space:]:]+' '{print $6"\t"$4":"$5}'|sort|uniq -c|sort -rg|grep :5672|head -n 20
  • netstat -ant打印端口使用情况
    • a:全部
    • n:显示端口号
    • t: 只看tcp连接
  • |grep -v TIME_WAIT去掉TIME_WAIT状态的连接(netstat中不加-n也可以)
  • |awk -F '[[:space:]:]+' '{print $6"\t"$4":"$5}' 用空格和冒号拆分字符串,并重新拼接,只保留远程IP,本地IP和本地端口。
    • -F 设置分隔符
    • {print $6} 打印第6个参数
  • |sort|uniq -c 先用sort排序然后通过uniq -c做分类汇总
    • -c将计数加入到第一列
  • |sort -rg 重新排序
    • -r:倒序排列
    • -g:以数字的方式排序
  • |grep :5672 只找端口号为5672的
  • |head -n 20 取前20条
netstat -ant|grep -v TIME_WAIT|awk '{match($4"\t"$5, /^([0-9\.]+:[0-9]+)\t([0-9\.]+):[0-9]+$/, a); if(a[1]) print a[2]"\t"a[1]}'|sort|uniq -c|sort -rg|grep 5672|head -n 20
  • 通过match做精确匹配

docker镜像中缺少的命令

docker的镜像中可能会少很多常用命令,比如vim之类的,有些可以apt install vim 这样根据命令名安装,而有些命令是在别的包里。这里列出一些:

  • ps:apt install -y procps
  • ping:apt install -y iputils-ping
  • netstat: apt install -y net-tools
  • nslookup & dig: apt install -y dnsutils