解决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/;
}

logback设置自定义字段

配置文件

logback_spring.xml

<appender name="myAppender"  class="ch.qos.logback.core.ConsoleAppender">
    <encoder charset="UTF-8" class="net.logstash.logback.encoder.LogstashEncoder">
        <includeCallerInfo>false</includeCallerInfo>
        <includeMdc>false</includeMdc>
        <includeContext>false</includeContext>
        <fieldNames>
            <timestamp>time</timestamp>
            <message>info</message>
            <version>[ignore]</version>
            <logger>[ignore]</logger>
            <thread>[ignore]</thread>
            <levelValue>[ignore]</levelValue>
        </fieldNames>
    </encoder>
</appender>
<logger name="pw.fyn.test" additivity="false">
    <appender-ref ref="myAppender"/>
</logger>
  • encoder
    • includeCallerInfo:是否包含调用信息
    • includeMdc:是否包含mdc信息,如果填true,则每一个字段都会在输出的json中出现
    • includeContext:是否包含上下分,如果填true,每一个<springProperty scope="context" />都会输在json中
    • fieldNames:默认字段的映射关系,比如把@timestamp改为了time,忽略掉versionthread等四个字段
  • logger
    • name:受影响的包或类
    • additivity:填false后将不会再输出到默认的root中
    • appender-ref:关联appender

写日志的代码

Printer.java

package pw.fyn.test;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import net.logstash.logback.marker.Markers;
import org.slf4j.Marker;


@Slf4j
public class Printer {

    public void print() {
        LogData data = new LogData();
        Marker marker = Markers.append("data", data);
        log.info(marker, "test info");
    }

    @Data
    private static class LogData {
        private int version = 3;
        private String topic = "order.create";
        private String message_id = "00000000-0000-0000-0000-000000000000";
    }

}

需要额外增加的字段需要通过Marker的方式放到info、error等发方法的第一行

根据端口汇总连接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做精确匹配

php禁用xdebug的彩色html var_dump

这个功能有时会麻烦,直接看请求结果很乱,而且会忽略掉一些大的字段,可以通过这哥参数禁用

ini_set('xdebug.overload_var_dump', 0);
ini_set('html_errors', 0);

docker镜像中缺少的命令

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

  • ps:apt install -y procps
  • netstat:apt install -y iputils
  • ping:apt install -y iputils-ping
  • netstat: apt install -y net-tools