618马上就要到了,各位亲们都准备好秒杀点什么呢?
随着网上商务的不断普及,各种购物狂欢节也层出不穷。作为DBA,面对购物狂欢节,你首先想到的是什么呢?事务风暴?商品库存表出现行锁等待?……
笔者今天和大家分享的就是和双十一秒杀系统有关的一个小故事,当然了,没有核心系统特别大的故障,否则出现大故障,大家都会知道的了。故事里,有的是对一线
DBA
工作状态、现场支持压力的描述以及
笔者
从事件中衍生出来的技术思考。
请你们吃鸡腿
好了,那就直接上图 ☺ ☺☺☺
这是从 oracle AWR 报告种看到的 top 等待事件,没错!就是秒杀期间出现严重的行锁竞争!
有经验的 DBA 已经感觉到不妙了,或许也深受其害吧 。
当行锁等待和“秒杀”这两个字联系在一起后,问题好像变得复杂了 , 秒杀期间出现热点商品出现行锁等待,好像是必然的吧,不改并发实现逻辑,似乎也没有太多办法呀,只能看着、等着、干着急 …
好想大哭一场
我们知道,如果行锁的持有者一直不变,那么我们通过 v$session.blocking_session 找到锁的持有者,确认后,杀掉就可以了,非常简单。
但是 … 如果 … 如果锁的持有者一直在变,杀一波,又上来一波,杀都杀不过来的时候,在现场的巨大压力下,您是否也会茫然不知所措了呢 …
笔者 今天为大家分享的就是这样一个看似简单但又不简单的行锁案例。
通过该案例,你可以学到和了解到 :
1) 银行业务系统的一些科普知识
2) 知识的全面性对处理复合问题的重要性,更多技能才可以让你游刃有余
3) 处理复杂问题,从来没有定式,需要熟能生巧,你可能会用自己没用过的招式
4) 梳理共性的问题,如果做到更全面的预防,避免类似问题再次出现。
更多实战分享和风险提示,请关注“中亦安图”公众号!也可以加小 y 微信,进微信群探讨技术, shadow-huang-bj 。喜欢就转发吧,您的转发是我们持续分享的动力!
作为一名长期活跃在一线的
ORACLEDBA
,
笔者
每年双十一都是在银行客户现场度过的,双十一从来没买过东西,够古董的,这次也不例外,这次,笔者值守的银行是国有大型商业银行。
该银行双十一期间支付渠道的并发量目前在国内所有银行中是最大的
,
去年峰值交易量就达到了每秒
2
万笔以上,要知道支付宝峰值是每秒
12
万笔
。
因此,能为这样高并发的系统保驾护航,对
笔者
而言,也是倍感荣幸,也深知责任重大。
这里普及点小知识,银行和双十一的关系是什么呢?
说简单点,就是支付!
过去,大家习惯使用银行的网银系统进行在线支付和转账,现在更多地是使用“快捷支付”。
那么什么是快捷支付呢?其实很简单,快捷支付就在你我身边。我们知道,支付宝或者微信支付(也叫财付通支付)是和某张银行卡绑定在一起的,当我们使用支付宝或者微信支付时,在输入交易密码后,支付宝或微信就会向银行卡所属银行的快捷支付系统发起实时扣款请求,由后者实时地将卡上的余额扣除。整个过程相比网银要快捷很多,使用手机就可以完成,因此这样的电子支付渠道简称快捷支付。
顺利
11
月
10
日,周四,
19
点,吃完晚饭后,早早就到了客户的数据中心。
运维大厅里,早就人山人海,当然门外还有各种饮料、美食和方便面,一切准备就绪。
我们知道,双十一,业务高峰期也就那么一会,如果系统不小心出现异常,并且通过重启无法快速恢复,被迫走上紧急排障的道路的话,一切都不好说了。如果排障过程不顺利,时间很容易在不经意间溜走,如果错过半个小时,那么也可以和这次双十一
say byebye
了
..
虽说
笔者
身经百战,但大仗之前,必须还是要有足够的休息,才能保证足够冷静的头脑和快速的分析,手速和体力也可以跟上,因此下午美美的睡了一觉,整体状态还不错。
客户的
DBA
们早就聚在一起,从大家的神情看,都信心满满。
笔者
也是一点都不担心。原因很简单,许多工作都做在了前面,例如压测发现的问题和调整措施,该做的工作都已经做完,该调大的参数、该设置的隐含参数和
event
也全部就绪。
笔者
坐在客户
DBA
身后
,
不断观察每秒事务数
、
log file sync
等指标趋势
,
静静等待双十一零点整点时刻的到来
。
点如期到来,交易开始疯狂地涌进来,很快并发量就上来了
,
12
套
RAC
,
每套将近
3000
的事务数,加起来总计每秒
3
万左右的事务数
。
log file sync
在
5
个毫秒以下,系统
CPU
和内存很富余,非常棒的性能。很快,在不断刷新的交易额中,时间过去了前
10
分钟,大家如释重负,因为大家知道,前十分钟过去了,就一切
OK
了
..
一切如意料之中的顺利。
当然,如果该客户出现问题,我想大家在微博微信都能看到了
…
过了凌晨
2
点,客户安排去酒店休息,看官们可能要失望了,不出事怎么体现价值呢
..
笔者
这么多年一线、二线、三线的干下来,深刻的了解到,让系统不出事,比出事后解决问题的价值要大的多。
那么对于一个可能出现像双十一这样突增事务风暴的系统,我们需要提前调整哪些参数或者做哪些事情才能保障更大的并发顺利通过呢?
我们是怎么建议的呢?这里列出来一些关键点供大家参考
…
电话响起,问题来了!
铃铃铃
…
迷迷糊糊中,电话响了。
“XX,
有一个行内自己做的电子商城,双十一期间做促销和秒杀,系统中出现大量的行锁等待,系统跑的很慢,赶紧过来一起分析下”。客户处长在电话里说到
。
看了下时间,
8
点一刻,
牙也顾不得刷了,
有
点意思,双十一期间,快捷支付、网银、借记卡、贷记卡系统由于准备充分,压测充分,经受了非常大的并发考验。没想到,疏忽了自己的电子商城,也在搞促销活动,但由于秒杀出现大量行锁等待,体验很差,业务部门该有意见了
..
开启分析之旅!
简单的沟通
进入运维大厅,看到,客户处长和好几个
DBA
已经围在了一个电脑终端前,神情严肃,看来,遇到麻烦了!
简单了解了下,这个电子商城,是一个行内重点推广的
B2C
系统。属于典型的
B/S
架构,数据库是
10.2.0.5
的
RAC
,中间件用的是
weblogic
。双十一期间
,
该电子商城也在搞促销活动,促销力度还挺大的,正品的品质,价格却非常实惠,还是吸引了不少人前来整点秒杀。系统中当前出现了大量的行锁等待,即“
enq: TX - row lock contention
”等待,已经杀了几轮会话,但是还是在不断的出,看上去,锁的持有者在发生变化,只不过比较缓慢而已。
笔者
立刻让客户帮开了个终端
,
迅速调出
AWR
报告,如下所示
:
可以看到:
排在第一位的行锁等待“
enq:TX - row lock contention
”,达到了
92.7%
,平均等待事件
484
毫秒,说明锁的持有者是一直在变化的;
排在第二位的是“
SQL*Netbreak/reset to client
”,即客户杀掉了会话出现该等待
,
接下来,
笔者
将进一步确认下锁的持有者在持续变化这一情况
。
查看信息
查看当前行锁等待的会话信息,发出下列查询,检查出现行锁的会话、等待时长和阻塞者,结果是大量的刷屏,看都看不过来,截图部分记录,结果如下
图中显示
:
Sid=4034的会话,即操作系统进程号为28864的进程,等待行锁已经20秒,
但阻塞者为空。
但是过一会,
SID=4034的会话
将从锁等待中消失。
该会话在执行
SQL_ID
为
7v4k2ypqk1994
的
SQL
语句,完整的
SQL
语句如下:
其中,商品库存表中每个商品在表中对应一条记录。该SQL实现了将库存表中某个商品的库存数量更新为用户提交订单后的库存。
经了解,商品秒杀的逻辑如下:
当
2
个或者
2
个以上的用户对同一商品同时提交订单时,则发出同一条
UPDATE
语句,见上图。
只有一个用户可以对该商品的库存记录加锁并减去订单销量作为库存
。
秒杀流程
有没有可能,提交订单后,事务还不结束,还需要等待支付后才
commit
?
这样的话,行锁也是不提交的。
虽然觉得业务不太可能这样处理,但是还是要确认下。
项目组反馈,订单提交成功即表示加锁并减去库存成功,与后续是否支付无关,如在特定时间未完成支付,则将库存加回。
于是让项目组赶紧打开
IE
,让他们演示了一下业务流程,如下所示:
1
)先点击立即抢购
2
)出现提交订单的页面
3
)提交订单后,可以选择电子券或积分等来进行支付,或者采用在线支付或网银支付
也就是说,在第二部提交提单后,在第三部显示积分、电子券等信息后,就已经完成了事务;看来这个可能也要排除了。。。。
第一次头脑风暴
ORACLE实现粒度为行级的锁。
如果会话处的等待事件是Enq:TX-row lockcontention,则表示:该会话拿不到行锁,即该会话出现了行锁等待。例如:
会话1更新或删除了某一行,已经持有了某一行的行锁,但由于某种原因,还不提交,此时,会话2也要更新/删除同一行,显然会话2拿不到该行锁,会话2的等待就是Enq:TX-row lockcontention。
(当然,插入带有唯一值字段的索引,不提交,也会导致其他插入同样唯一值的会话处于行锁等待)
那么为什么会话1持有了行锁但不提交呢?几种可能如下:
1
会话1的事务逻辑如下:
Update 库存表的某个商品的对应一行记录,
之后却没有提交事务,比如应用代码遗漏commit,
比如网络中断、丢包导致客户端的commit命令无法通过网络传输到数据库服务端。
2
会话1的事务逻辑如下:
Update库存表的某个商品的对应一行记录,
调用到其他产品如MQ、DB、大机的分布式事务,等待返回
Commit或rollback;
Update一行拿到行锁后,因为还在等待到其他产品如MQ、DB、大机的分布式事务的调用的返回
,才能提交。如果到其他产品的分布式事务的调用返回慢,则会话1持有行锁的时间较长,导致其他需要更新同一行的发生等待。
3
会话1的事务逻辑如下:
拿到行锁后,在数据库中还有其他SQL需要执行,如果其他SQL执行的慢,执行的时间长,则持有行锁的时间较长,导致其他需要更新同一行的会话发生等待。
Update库存表的某个商品的对应一行记录,
Select SQL1
Select SQL2
Other sql..
Commit;
经与开发项目组沟通,沟通结果如下:
第一种,应用遗漏代码的可能不存在。Netstata/ifconfig命令未见error异常,排除。
第二种可能性不存在,事务的处理逻辑都在本数据库中,因此无需去关注其他dblink/分布式调用
目前的实现方式就是第三种,也就是说,不是update后就立刻提交,而是后面还要执行几十条SQL语句,用来实现计算积分等功能后,才最终commit提交。
因此,我们需要把分析重点放在第三种可能。
update后commit前哪条SQL执行慢?
根据上述分析,当前的事务实现逻辑是:
Update库存表的某个商品的对应一行记录,
Select SQL1
Select SQL2
Other sql..
Commit;
也就是说,秒杀事务并不是发出一条update后就返回,而是后面还要执行几十条SQL语句,用来实现计算积分等功能后,才最终commit提交。
那么怎么知道update后commit前都执行了哪些SQL?又是哪条SQL执行慢呢?
1)因为blocking_session为空,因此通过blocking_session来梳理阻塞者,进一步检查阻塞者在执行哪条SQL的方法不适用。
2)即使找到非空的blocking_session的那个阻塞者,进一步检查阻塞者在执行哪条SQL时,也没有发现哪条SQL是慢的!
3)其中一个可选但不推荐的方法是协调开发项目组去梳理,但准确性和时效性无法得到保证。
4)抓取AWR,找到执行时间长的SQL的方法也不可取。一来无法确认哪些SQL是该事务过程中执行的,二来逻辑读高的SQL不少,短时间内全部优化,时间来不及。如下图所示:
问题陷入僵局
应用不可能没有提交,否则持有者不可能一直在变。
事务中是否有执行慢的
SQL
呢,不确认,项目组无法快速梳理,也可能梳理不完成;直接优化
AWR
报告中的所有长时间消耗的
SQL
,代价太大,时间太长,花大力气,如果和该秒杀系统无关,不见得有效果;
看着满屏的行锁等待,一直在缓慢的往前走,
笔者
陷入了沉思。
怎么打破这个僵局
..
读者朋友们,不妨在这里也停下来,思考几分钟,再继续往下看
.
如果是你,你会怎么往下查
…
希望的曙光
那么有没有办法在没有项目组帮助的情况下,获取整个秒杀事务发起的的完整
SQL
列表和效率情况呢?有的话,操作层面怎么做呢
…
在停下来思考的时间里,
笔者
的脑子一直在飞快的转动。终于,一个方法显然脑海中:这个
event
笔者
虽然经常在用,但是没有在这个场合用过,或许是一个奇兵,值得一试!!
因为阻塞者从等待到获取锁的时间不算长,因此我们选用10046 event从头跟踪某个被阻塞者的方法来获取秒杀事务相关的SQL以及慢的SQL的逻辑读和执行计划。
回顾之前被阻塞的会话信息,如下:
梳理update后commit前哪条SQL执行慢的10046跟踪命令如下:
Oradebugsetospid 28864
Oradebugevent 10046 trace name context forever,level 12;
等待几分钟
Oradebugevent 10046 trace name context off;
Oradebugtracefile_name
使用
tkprof
命令格式化
trace,
按照执行阶段的执行时间排序,命令如下:
Tkprof /home/db/oracle10/log/dbname/udump/dbname1_ora_28864.trc102.out sort=exeela
马上要揪出这个大魔王了,甚是期待!!!结果如何呢?
可以看到执行时间除了行锁等待导致的执行时间长以外,本身的执行时间并不长,
SQL
本身的逻辑读并并不大(除了第三条
SQL
语句走错驱动表,逻辑读达到
10000
可进一步稍微优化以外),因此
SQL
语句本身的优化空间不大。
失望的结果
也就是说:虽然一个事务中执行了几十个
SQL
,但是没有一个
SQL
的逻辑读是大的,崩溃。那么问题到底出在哪里呢?
为什么一个秒杀的订单事务,要执行这么多
SQL
呢。
问了下开发,很多
SQL
是需要去计算积分,方便后续的支付。
这个解释,也是醉了。。。。难道说,其实每条
SQL
都很快,只是
SQL
但是加起来的总时间就慢了呢。
不可能!
笔者
重新把一个事务中除了出现行锁等待的
SQL
执行时间计算了一下,单个事务在的影响时间是毫秒级的,据开发说,测试的结果也是毫秒级的。
那么问题出在哪里了呢
.。。。。
回到原点
当处理问题到了僵局的时候,
笔者
习惯再次回到原点,重新梳理所看到的数据,是不是遗漏了什么信息呢...
这里再来回顾下应用的秒杀事务逻辑。
Update库存表的某个商品的对应一行记录,
Select SQL1
Select SQL2
Other sql..
Commit;
这里SQL语句本身执行过程中并无性能问题,逻辑读不大。但是整个事务持有锁的时间又很长,问题到底出在哪里?
我们再次抓取被阻塞者的10046 event,获取从被阻塞,到获得行锁,到执行后面几十条SQL的完整过程。打开进程的10046 event后,同时开始查看对应的trace。命令如下所示
Tail –n 200 –f /home/db/oracle10/log/dbname/udump/dbname1_ora_28864.trc
Trace打印信息的过程如下如下:
会话执行完update 商品库存表,即更新库存表的某一商品库存后,等待事件为sql*net message fromclient,一共等了17秒。即等待应用发起的下一条SQL。
说明:10046 event中ela标识逝去的时间,单位为百万分之一秒。
在此期间,10046中并未看到commit语句,即会话未提交,连接池中的连接(会话)不可能被复用。等待期间,屏幕暂停刷屏。17秒后,开始刷屏,如下图所示:
在执行完第二条SELECT语句(绿色底线标识)后,屏幕卡住,停止54秒后,屏幕继续往前刷屏。在此期间,10046中并未看到commit语句,即会话未提交,连接池中的连接(会话)不可能被复用。
进一步确认问题
为什么秒杀事务中的一条SQL语句,返回结果(集)后,应用迟迟没有发出下一条SQL呢?
经确认,电子商城为BS结构的应用,应用WAR包部署在webLogic中间件中,因此,数据库返回结果(集)到中间件,如果中间件的线程不响应或者影响的慢,例如JVM无可用内存,正在频繁做垃圾回收,则可能会出现不响应或者响应缓慢的情况。表现出来,就是没有及时的发出秒杀事务中的下一条SQL,即等待事件就是sql*net
message fromclient。
经行内人员协调中间件工程师分析,初步得到确认:
webLogic中间件可能由于业务量较大的原因,webLogic中间件上可以看到在频繁的做FULL GC,垃圾回收;FULL GC导致应用不影响或者响应缓慢。该现象与数据库中的表现完全吻合。
问题原因总结和处理建议
原因总结:
根据上述分析,双十一电子商城秒杀的事务逻辑为:
Update库存表的某个商品的对应一行记录,
SQL1
SQL2
Other sql..
Commit;
在SQL1/SQL2/OtherSQL等环节,因为数据库要将SQL语句的结果(集)返回给中间件webLogic 处理,但由于webLogic中间件JVM在频繁做FULL GC(垃圾回收),期间不响应线程的请求,因此数据库中的事务不间断的处于sql*net message fromclient的等待,即等待应用发出的下一条SQL语句,等待视GC情况可能长达数十秒。从而导致事务本身的总体执行时间变慢,行锁持有时间变长,继而导致严重的行锁等待。
处理建议
:
请webLogic中间件工程师分析JVMFULL GC垃圾回收频繁的原因并解决中间件的FULL GC频繁调用的问题,即可解决电子商城秒杀期间行锁等待时间长的相关问题。
我们的收获
写了这么多,也不知道各位看官是否看到了这个CASE的精髓之处。通常,在我们了解ORACLE足够深的时候,往往会掌握非常多的方式方法来帮助我们定位分析问题,然而,在更多的时候,我们应该做的是,分析问题的本质,方法只是我们在解决问题过程中的辅助手段而已,在了解了问题的本质后,即使用最简单、最不起眼的方法也能帮我们定位到看似稀奇古怪的问题。
另外,不要忘了回顾一下在高并发事务的业务场景下,我们给出的参数修改建议哦,看看哪些对大家有帮助呢?
预料之中的
..
..
..
来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/31547066/viewspace-2222480/,如需转载,请注明出处,否则将追究法律责任。