记一次分布式锁引发的生产问题

近期发版,要修复一个并发引起的主键冲突报警。

运维要求没有此类报警,组里同事顺手就改了。

结果第二天一早服务高峰期,服务直接卡死没有反应了。

快速定位到是这段代码的问题,回滚,解决问题。

然后分析了一下,开发、测试、架构都有问题:

原始逻辑:
1、服务A加了分布式锁L1
2、服务A调用服务B
3、服务B加了分布式锁L2
4、服务B调用数据库服务入库
由于加锁逻辑比较特殊,并发时,会造成主键冲突。

问题逻辑:
1、服务A加了分布式锁L1
2、服务A调用服务B
3、服务B加了分布式锁L1
4、服务B调用数据库服务入库
服务直接卡死,要么服务超时,要么锁超时,服务能不卡死吗

解决问题并不复杂,在这里就不罗嗦了。

问题是很简单,但暴漏的问题十分多:
1、开发对程序逻辑、分布式锁理解明显不够
2、在开发、测试、UAT环境下,开发和测试其实都发现了性能下降的问题,但都没有重视
3、架构和资深开发,代码审核工作问题也很大
其实很多时候,工具是一回事,但人员的专业性思维,其实更重要。

记一次HTTPS授权失败

我们伟大的云提供商告诉我们,他们廊坊机房要关停了,要求我们迁移到其他机房,春节前一定要迁移完毕。

和运维、架构沟通后,做了迁移计划,做了演练,虽然时间仓促,问题特别多,但还是扛过去了。

迁移后,不少用户受到了影响,比如有一个省由于网络供应商问题,整个省的用户无法解析DNS。

这其中,印象最深的是,我们一个用户,服务、配置、权限各种都是正确的。但就是无法登录。

分析了几个小时,最后发现,原来是用户机器时间不对,时差有几个小时,在HTTPS请求到达防火墙时,就被干掉了。

设好时间,一切正常。

其实,之前也遇到过两三次类似问题,频率太低了,一开始根本就没考虑这种可能。

记一次Excutor引发的生产故障

当时看到《阿里巴巴Java编码规范》时,我感觉这里面说的不是大家都该知道的事情吗?真有必要汇总个规范出来?

出来混,总要还的,最近在老项目上就遇到了。

我们有两个服务,遗留项目,代码写的实在是不敢恭维。
用量其实不大,或者说偏小,但很默契的每两周挂一次,到今天是第三次了。

第一次OOM(没定位到根本问题):
K8S网络组件异常,DNS解析失败,和架构一起排查后,发现问题如下:
1、系统原来使用过RabbitMQ,后来代码注释了
2、但POM中,还是有MQ的引用,别的不会做,但会启动MQ的监听
3、结果有一天K8S网络组件异常,DNS解析失败
4、MQ的心跳包瞬间起了几千个线程,连接找不到的MQ Server
5、服务挂了

推断的问题链路为:
DNS无法解析-》MQ心跳包不断起线程,而且没有连接池-》线程耗尽内存-》OOM-》服务挂掉

解决方法:
在POM中,干掉了MQ的引用,以为问题被修复了。

第二次OOM(大意了):
1、表现仍是K8S网络组件异常,DNS解析失败
2、架构感觉容器DNS解析不了应该运维去查
3、只好拉上运维查,运维也比较给力,很快把日志给出来了
4、但是系统OOM时,并没有生成镜像,架构建议添加JVM参数,结果和运维排查后发现JVM参数已经加了,OOM应该有Dump啊
5、最后的结论是,非常规问题,等下次发生再解决
6、这个时候,已经感觉怪怪的了,问题根源根本没有找到啊,但大家都忙着机房搬迁,就没能跟下去(我的问题)

推断的问题链路为:
DNS无法解析-》不断起新线程去连接-》线程耗尽内存-》OOM-》但没有Dump文件-》无法定位问题

解决方法:
碰运气,等下一次OOM生成Dump文件?我当时咋想的呢

第三次OOM(真的解决问题了吗?):
1、感觉不对劲啊,为什么总是两周挂,于是拉取了日志
2、日志上没看出什么来
3、仍然没有OOM的Dump
4、jstack一看线程,傻眼了,一堆线程在waiting on condition
5、赶快看相关源码,居然是用了Excutor,然后线程和队列都是默认的
6、你懂的,线程数和队列默认几乎都是无限的,参数设置错误,导致线程池根本不会服用原来的工作线程
7、服务挂掉是早晚的事情

推断的问题链路为:
DNS无法解析-》不断起新线程去连接-》线程耗尽内存-》OOM-》但没有Dump文件-》无法定位问题

解决方法:
修改Executor默认配置

咋说呢,再次认识到规范强制推行也是必要的,全靠人的个人水平,是不行的。他们的规范,恐怕也是各种坑汇总成的结晶吧。

记一次PG引发的生产故障

前几天,突然收到报修,一个文件接收服务突然宕了。
我们运维同事很快就进行了重启,此时怪异的事情发生了:
重启服务后,服务一会儿就没响应了,端口可以通,文件全部上传失败,没有任何异常日志输出。

那就排查吧:
1、PG数据库连接正常,无死锁,表空间正常,数据查询秒回
2、服务配置几个月都没人改过,CPU、内存、存储、IO、网络,全部正常
3、上传客户端日志输出,显示其工作正常
4、只有文件接收服务,没有响应,也没有日志输出
初步判断,文件接收服务出问题了。

于是,新开一个云主机,重新安装服务,仍然无响应。
然后,拷贝了一个正常工作的主机,修改配置后,仍然无响应。
来来回回折腾了几个小时,还是不行。

无奈之余,试了一下向PG数据库插入数据,我去,几十秒都没有响应。
赶快问DBA,原来是做了高可用,主从库数据同步,从库异常了,导致主库可读不可写。
众人皆表示无奈,重启从库,问题解决。

其实,一开始就排查数据库了,但由于是生产库,只试过查询,没有试过写入。
但是,我们都大意了:
1、服务日志,输出不够详细,就算DEBUG打开,也不知道数据进行到哪一步了,这个最坑
2、没有正确的设置超时时间,原因是接收文件后,写入数据库一直在等待,服务日志没有任何数据库异常
3、数据库监视软件,只做了主库监视,根本没做从库监视
4、数据库主从配置,本应该是异步,但现在配置成了同步
5、没有监视主从库同步的情况
6、生产库,不敢轻易进行写操作,只看了查询效率及死锁,没有看慢语句

就这样一个小问题,绕过了层层监控机制,让问题排查十分困难,花费了大量的人力。
反思起来,如果只是为了记录日志而记录日志,但日志不能反应服务状态,那不如不记;如果只是为了监控而监控,但监控不到位,那不如不监控。
我们日常做事情,也是同样的道理,细节是魔鬼,把该把控的把控好了,才能提升效率,得到想要的结果。

AXIS2无法从SOAP MTOM/XOP消息中获取附件

最近,同事在与其他厂商做IHE测试,测试的CASE多数都可以通过。唯独一个测试无法通过:对方需要通过MTOM/XOP消息发送一个文件到我们的服务,我们可以正常的收到消息,但无法解析消息得到附件。

经过沟通,发现另一个厂商居然是用CPP自己手动拼接的SOAP消息,也是醉了。

首先是解决了几个命名空间填写不正确的问题,调整完毕以后,AXIS2会报下面的错误:

org.apache.axiom.om.OMException: javax.xml.stream.XMLStreamException: Expected xop:Include as the sole child of an element information item (see section 3.2 of http://www.w3.org/TR/xop10/). This is due to the fact that the element (<xdsb:Document id="Document01">   </xdsb:Document>)  (OMElement obtained after axis2 processing)  that should contains the base64 value of document, is empty. But observing the same message sniffed from wireshark it seems that the Document is present.

这个问题好诡异啊。

经过一番搜索,在apache找到了答案:
由于对方发送附件时,多了一个换行符(XOP协议明确规定不允许的),导致了该错误的发生:

<xdsb:Document id="Document01"><xop:Include href="cid:1.11a262e2-bf65-1e01-28a7-000c29c7ee2b@apache.org" xmlns:xop="http://www.w3.org/2004/08/xop/include"/>
</xdsb:Document>

去掉换行符以后,就好了:

<xdsb:Document id="Document01"><xop:Include href="cid:1.11a262e2-bf65-1e01-28a7-000c29c7ee2b@apache.org" xmlns:xop="http://www.w3.org/2004/08/xop/include"/></xdsb:Document>

这个也是醉了。

大家如果收发SOAP消息,还是不要手工拼接这样写的好啊。

处理windows控制字符0x17

今天同事遇到了很诡异的问题,就是从数据库中读取一个字段时,有些数据中,会多一个0x17的控制字符。

暂时没太好的处理方法,自己写了个测试例子,仅供参考吧:

for java

    public static void main(String[] args)
    {
        byte[] b= { 0x30, 0x30, 0x1f, 0x37, 0x39, 0x33, 0x39, 0x36, 0x34, 0x31 };
        byte[] bt={0x1f};
        String s1 = new String(b);
        String st= new String(bt);
        String s2 = s1.replace(st,"");
        String s3 = s1.replace((char)0x1f,' ').replace(" ","");
        System.out.println(s1);
        System.out.println(s2);
        System.out.println(s3);
    }

for charp

        static void Main(string[] args)
        {
            Byte[] b = { 0x30, 0x30, 0x1f, 0x37, 0x39, 0x33, 0x39, 0x36, 0x34, 0x31 };
            Byte[] bt = {0x1f};
            String s1 = System.Text.Encoding.Default.GetString(b);
            String st = System.Text.Encoding.Default.GetString(bt);
            String s2 = s1.Replace(st, "");
            String s3 = s1.Replace((char) (0x1F), ' ').Replace(" ", "");
            Console.WriteLine(s1);
            Console.WriteLine(s2);
            Console.WriteLine(s3);
            Console.ReadLine();
        }

诡异的dll(2)

还是昨天,还是同一个哥们。他要实现一个语音叫号的功能,要先普通话叫号,然后再粤语叫号。

诡异的事情发生了,在调试状态下,怎么运行都是正常的,
但直接双击运行,只能叫第一种声音(粤语或普通话),另一个声音(粤语或普通话)怎么都发不出来。
悲剧的是,他语音叫号也是调用了一个dll。

只好慢慢调试了,开始以为是线程退出的太快,或者变量作用域问题,或变量中繁体中文乱码问题,或者函数退出太快,或者是release时优化掉了…

弄了一个多小时,没有任何进展。无奈之下,我翻出了Process Explorer,去查看环境变量。
结果调试的时候,第一个环境变量是

__COMPAT_LAYER=WinXpsp3

天啊,这不是兼容模式吗。
于是将exe设成兼容模式,双击运行,就好了。

原来,他用BCB6做开发,在Win7 sp1上无法正常运行,就设成了兼容模式,调试时运行的exe当然也继承了兼容模式的环境变量咯。
吐血啊……

回家,搜了一下__COMPAT_LAYER环境变量,可以设置为以下数值:

兼容性:

兼容模式
WIN95 Windows 95
WIN98 Windows 98 / Windows Me
NT4SP5 Windows NT 4.0 (Service Pack 5)
WIN2000 Windows 2000
WINXP Windows XP
WINXPSP2 Windows XP (Service Pack 2)
WINXPSP3 Windows XP (Service Pack 3)
WINSRV03SP1 Windows Server 2003 (Service Pack 1)
WINSRV08SP1 Windows Server 2008 (Service Pack 1)
VISTARTM Windows Vista
VISTASP1 Windows Vista (Service Pack 1)
VISTASP2 Windows Vista (Service Pack 2)
WIN7RTM Windows 7

权限:

权限
RUNASADMIN 以管理员权限运行
RUNASINVOKER 以调用者权限运行

显示模式:

显示模式
DISABLETHEMES 禁用视觉主题
640X480 用640×480屏幕分辨率运行
HIGHDPIAWARE 高DPI设置时禁用显示缩放
256COLOR 用256色运行
DISABLEDWM 禁用桌面元素

另外,也可以通过注册表,设置程序的兼容模式:

Windows Registry Editor Version 5.00

[HKEY_CURRENT_USER\Software\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers]
"D:\\TEST\\Hello.exe"="WINXPSP3"
    HKEY hKey;
    LPCTSTR strSubKey = "Software\\Microsoft\\Windows NT\\CurrentVersion\\AppCompatFlags\\Layers";
    long lRet = ::RegOpenKeyEx( HKEY_CURRENT_USER, strSubKey, 0, KEY_WRITE, &hKey );
    if ( lRet == ERROR_SUCCESS )
    {
        TCHAR achValue[] = { _T("WINXPSP3") };
        CString strExePath = _T("D:\\Test\\Hello.exe");
        lRet  = ::RegSetValueEx( hKey, strExePath, NULL, REG_SZ, (LPBYTE)&achValue, sizeof(achValue) );
        RegCloseKey( hKey );
    } 

诡异的dll(1)

昨天,和哥们一起调试程序。由于上线压力较大,他最近一直在狂改程序,但遇到了一个问题:

从PACS Server取影像时,怎么都取不回来。

首先排查了PACS Server设置,发现稍微有些问题,修改配置后,我以前写的一个测试程序,
就可以正常取回影像了。

但诡异的事情发生了,我写的程序,可以查,可以取,但他写的就是无法取回影像,只能查询。

吐血啊。

经过了一个小时的奋战,实在发现不了问题,他决定把无线网卡禁用后再测一下,
结果就通了~~

原来他取影像用的是一个dll,估计这个dll中,启用监听的时候,会自动选用某一块网卡,
但刚好选了无线网卡,悲剧啊。

能不能不要自作主张选网卡啊~~
能不能调试的时候,先把不需要的网卡禁用了啊~~

ANT出现“命令语法不正确”

昨天用ant编译代码时,报了一个很诡异的错误:
“命令语法不正确。”

分析了半天发现,原来是我在bat文件中多了””,悲剧啊。

set JAVA_HOME="D:\JavaJDK\jdk1.6.0_34_x86"
set ANT_HOME="D:\JavaTools\apache-ant-1.9.0"
set PATH=%ANT_HOME%\bin;%JAVA_HOME%\bin;%PATH%

将引号去掉就好了

set JAVA_HOME=D:\JavaJDK\jdk1.6.0_34_x86
set ANT_HOME=D:\JavaTools\apache-ant-1.9.0
set PATH=%ANT_HOME%\bin;%JAVA_HOME%\bin;%PATH%