地震是由不可抗力导致的,而事故与之不同,任何大的生产事故在发生之前都有迹可循,而且事故的发生并不是偶然的,我们应该善于从现象中总结规律,找到发现、止损和避免的方法
海恩法则
每一起严重事故的背后,必然有29次轻微事故和300起未遂先兆及1000起事故隐患。
- 事故的发生是量的积累的结果。
- 再好的技术、再完美的规章,在实际操作层面也无法取代人自身的素质和责任心。
根据海恩法则,一起重大事故发生后,我们在处理事故和解决问题的同时,还要及时对同类问题的"事故征兆"和"事故苗头"进行排查处理,以防止类似问题重复发生,把问题解决在萌芽状态,这完全可以作为互联网企业线上应急的指导思想。在线上应急的过程中,不但要定位和解决问题,还要发现问题的根源,并找到发生事故之前的各种征兆,对征兆进行排查和分析,并做响应的报警处理。
墨菲定律
如果有两种或两种以上方式去做某件事情,而选择其中一种方式将导致灾难,则必定有人会做出这种选择。
- 任何事情都没有表面看起来那么简单。
- 所有事情的发展都会比你预计的时间长。
- 会出错的事情总会出错。
- 如果你担心某种情况发生,那么它更有可能发生。
墨菲定律实际上是个心理学效应,如果你担心某种情况会发生,那么它更有可能发生,久而久之就一定会发生。这警示我们,在互联网公司里,对环境发生的任何怪异现象和问题都不要轻易忽视,对于其背后的原因一定要彻查。 同样,海恩法则也强调任何严重事故的背后都是多次小问题的积累,积累到一定的量级后会导致质变,严重的问题就会浮出水面。
对于任何现象都要秉承着"为什么发生?发生了怎么应对?怎么恢复?怎么避免?"的原则,对问题要彻查,不能因为问题的现象不明显而忽略。
6.2. 线上应急的目标、原则和方法
6.2.1. 应急目标
在生产环境发生故障时快速恢复服务,避免或减少故障造成的损失,避免或减少故障对客户的影响,
6.2.2 应急原则
- 恢复系统,快速止损,而不是彻底解决问题。
- 有明显的资金损失时,升级问题快速止损。
- 不影响用户体验的前提下,保留部分现场和数据。
6.2.3 线上应急的额方法和流程
有条不紊地进行,遇事胆大心细,该做决策的时候要毫不犹豫,该升级的时候要果断。
线上应急一般分为6个阶段:发现问题、定位问题、解决问题、消除影响、回顾问题、避免措施。
在应急过程中要记住,应急只有一个总体目标:尽快恢复问题,消除影响。
定位问题
- 问题系统最近是否进行了上线?
- 依赖的基础平台和资源是否进行了上线或者升级?
- 依赖的系统最近是否进行了上线?
- 运营是否在系统里面做过运营变更?
- 网络是否有波动?
- 最近的业务量是否增加?
- 服务的使用方是否有促销活动?
回顾问题
- 类似的问题还有哪些没有想到?
- 做了哪些事情,这个事故就不会发生?
- 做了哪些事情,这个事故即使发生了,也不会产生损失?
- 做了哪些事情,这个事故即使发生了,也不会造成这么大的损失?
6.3 技术攻关的方法论
技术攻关的目标是解决问题,因此首先要从问题发生的环境和背景入手,首先考虑下面几个问题。
- 最近是否有变更、升级和上线?(回滚)
- 之前是否遇到过类似的情况?(使用历史经验)
- 是否有相关领域的专家?例如:安全、性能、数据库、大数据和业务等领域的专家(开启专家模式)【最小化复现→找到原因→提出解决方案→验证解决方案→线上实施】。
对于任何问题,我们必须收集发生这些问题的现象,考虑如下问题。
- When:什么时候出的问题?
- What:什么出了问题?
- Who:谁在什么时间里发现了问题?问题影响了谁?
- Where:哪里出现了问题?哪里又没出现问题?
- Why:为什么出现了问题?
6.5高效的服务化治理脚本
6.5.1 show-busiest-java-threads
此命令是用来查找java进程内CPU利用率最高的线程,一般适用于服务器负载较高的场景,并需要快速定位负载过高的成因。
命令格式:
./show-busiest-java-threads -p 进程号 -c 显示条数
./show-busiest-java-threads -h
脚本源码:
#!/bin/bash
PROG=`basename $0`
usage(){
cat <<EOF
Usage: ${PROG} [OPTION]...
Example: ${PROG} -c 10
Options:
-p, --pid java process
-c, --count thread count to show,default is 5
-h, --help display this help and exit
EOF
exit $1
}
# getopt:用于解析命令行参数。
# -n选项用于指定程序名称,这里使用变量$PROG的值作为程序名称
# -a选项告诉getopt允许使用短选项后面跟一个冒号的方式,即使长选项后面也跟了冒号
# -o选项用于指定短选项及其参数格式。这里的选项有:
# c: 接受一个参数。
# p: 接受一个参数。
# h: 不接受参数(帮助选项)
# -l count:,pid:,help:
# -l选项用于指定长选项及其参数格式。这里的选项有:
# `count:` 接受一个参数。
# `pid:` 接受一个参数。
# `help` 不接受参数(帮助选项)
# `--`:双连字符--表示之后的所有参数都应该被视为命令行参数,而不是getopt选项
# `$@`:引用所有传入脚本的命令行参数。
ARGS=`getopt -n "$PROG" -a -o c:p:h -l count:,pid:,help -- "$@"`
# 需要检查getopt的返回值,以确保命令行参数是有效的。如果getopt遇到无效的选项,它会返回非零值。
# [ ... ] 是一个测试命令(test command),用于进行条件判断
# $? 是一个特殊的shell变量,用来存储上一个命令的退出状态码
# -ne 是一个测试操作符,用于比较两个整数是否不相等(not equal)。
# 0 表示成功退出的状态码
# && 是一个逻辑运算符,在这里表示“如果前面的条件成立,则执行后面的命令
# 如果上一个命令的退出状态码不是0(即命令执行失败),则调用usage函数并传递参数1。
[ $? -ne 0 ] && usage 1
# 重新设置脚本的参数列表
# eval命令用于执行一个字符串中的命令序列
# set -- "${ARGS}"是set命令的一个变体,用于设置脚本的参数列表。--之后的部分会被解释为新的命令行参数。
# set -- "${ARGS}"实际上会将ARGS变量中的内容设置为脚本的参数列表。
# eval在这里的作用是执行set -- "${ARGS}"这一命令
eval set -- "${ARGS}"
while true; do
case "$1" in
-c|--count)
count="$2"
shift 2
;;
-p|--pid)
pid="$2"
shift 2
;;
-h|--help)
usage
;;
--)
shift
break
;;
# esac 是 case 语句的结束标记
esac
done
count=${count:-5}
redEcho(){
# [ -c /dev/stdout ] 是一个测试表达式,用来检查 /dev/stdout 是否是一个字符设备文件
[ -c /dev/stdout ] && {
# if sdout is console, turn on color output.
echo -ne "\033[1;31m"
echo -n "$@"
echo -e "\033[0m"
} || echo "$@"
}
# Check the existence of jstack command
# if ! which jstack &> /dev/null; then: 这一行检查jstack命令是否可用。如果不可用,则条件为真
if ! which jstack &> /dev/null; then
# -n选项测试一个字符串是否非空
# -f选项测试一个文件是否存在
# -x选项测试一个文件是否可执行
# 将$JAVA_HOME/bin添加到PATH环境变量的开始位置。这样可以确保jstack工具在系统路径中可用
# || { ... }如果前面的任何一个条件不满足,则执行大括号内的命令
[ -n "$JAVA_HOME" ] && [ -f "$JAVA_HOME/bin/jstack" ] && [ -x "$JAVA_HOME/bin/jstack" ] && {
export PATH = "$JAVA_HOME/bin:$PATH"
} || {
redEcho "Error: jstack not found on PATH and JAVA_HOME!"
exit 1
}
fi
# $$: 当前进程的进程ID
uuid=`date +%s`_${RANDOM}_$$
cleanupWhenExit() {
rm /tmp/${uuid}_* & > /dev/null
}
trap "cleanupWhenExit" EXIT
printStackOfThread(){
while read threadLine ; do
pid=`echo ${threadLine} | awk '{print $1}'`
threadId=`echo ${threadLine} | awk '{print $2}'`
threadId0x=`printf %x ${threadId}`
user=`echo ${threadLine} | awk '{print $3}'`
pcpu=`echo ${threadLine} | awk '{print $5}'`
echo "pid=${pid},threadId=${threadId},threadId0x=${threadId0x},user=${user},pcpu=${pcpu}"
jstackFile=/tmp/${uuid}_${pid}
[ ! -f "${jstackFile}" ] && {
jstack ${pid} > ${jstackFile} || {
redEcho "Fail to jstack java process ${pid}!"
rm ${jstackFile}
continue
}
}
redEcho "The stack of busy(${pcpu}%) thread (${threadId}/0x${threadId0x}) of java process(${pid}) of user(${user}):"
sed "/nid=0x${threadId0x}/,/^$/p" -n ${jstackFile}
done
}
# -z 是否为空,-n 是否不为空
[ -z "${pid}" ] && {
# -Leo: 这些选项组合在一起用于定制输出格式:
# -L: 显示线程信息。
# -eo: 指定输出格式
# pid,lwp,user,comm,pcpu: 这些字段名指定了要显示的列:
# pid: 进程ID。
# lwp: 线程ID。
# user: 运行进程的用户。
# comm: 进程的命令名称。
# pcpu: 进程使用的CPU百分比。
ps -Leo pid,lwp,user,comm,pcpu --no-headers | awk '$4=="java"{print $0}' |
sort -k5 -r -n | head --lines "${count}" | printStackOfThread
} || {
ps -Leo pid,lwp,user,comm,pcpu --no-headers | awk -v "pid=${pid}" '$1==pid,$4=="java"{print $0}' |
sort -k5 -r -n | head --lines "${count}" | printStackOfThread
}
本站资源均来自互联网,仅供研究学习,禁止违法使用和商用,产生法律纠纷本站概不负责!如果侵犯了您的权益请与我们联系!
转载请注明出处: 免费源码网-免费的源码资源网站 » 分布式服务架构[原理、设计与实践]学习笔记
发表评论 取消回复