背景
接口业务逻辑复杂又长,涉及到redis、fegin、phoenix。
测试同事对该接口进行压测,发现该接口qps在压测5分钟后qps会降低至10左右,
开发同事怀疑phoenix导致
主题
phoenix更新压测时并发低的原因
最终结论
phoenix行锁导致
对于一张有二级索引表的phoenix主表,在更新主表某条数据时, 如果需要更新索引表,则phoenix会对该行数据添加行锁,从加锁到解锁过程一般持续200毫秒到500毫秒,该过程中其它线程无法更新这条数据。
最终处理方式
压测时采用不同的入参,避免行锁竞争,压测qps就能上去
排查过程
======================2021年11月临时处理============================
刚刚遇到这个问题时,以为只是单次接口调用稍稍慢点(1s),只要压测的时候压测线程多些,qps就能上来,当后面发现并不能
压测程序用1个线程--qps为10
压测程序用2个线程--qps为13.5
压测程序用5个线程--qps为12.4
压测程序用30个线程--qps 13.2
压测程序用200个线程--qps 12.4
后面继续排查,发现接口在调用phoenix更新方法速度特别慢,phoenix更新就是性能瓶颈,但是这个更新是根据id进行更新的,并且只更新少量字符串数据,按理来说应该很快才对
此时由于时间紧张,代码要上线了,于是临时把phoenix的代码改成了连接hbase的代码,qps恢复到300+,后续再排查问题
======================遇事不决,实验排除法============================
遇事不决,改服务器配置提高性能!!
在hbase集群添加如下配置,增加服务端线程数
<property>
<name>phoenix.query.threadPoolSize</name>
<value>100</value>
</property>
<property>
<name>phoenix.query.targetConcurrency</name>
<value>100</value>
</property>
<property>
<name>phoenix.query.maxConcurrency</name>
<value>120</value>
</property>
结果没有用,经过几轮压测后发现,根据id进行修改的接口qps还是11.2,说明调节phoenix的线程数量还是不行
遇事不决,实验排除法!!
找另外一次测试环境151,采用相同的主表结构,主表数据差不多进行查询和更新压测
发现:另一个测试环境qps200左右还算可以,qps差是31这个测试环境的问题
在测试环境新建31一张主表结构一样的表,将这个新表的数据增加到80w,然后继续进行更新压测,多次压测后qps仍然是200左右
证明:qps差可能和phoenix配置无关,和表有关,
猜测:我新建立的主表没有索引表,所以可能是索引表会降低qps
行动:在151环境建立索引表,此时151该表数据2kw,建立一个二级索引表
create index IDX_COUPON_TEST7_MEMBER_ID on COUPON_TEST7(MEMBER_ID);
上面的sql运行花了300秒,
索引表建立好了,然后进行压测
结果:第一次压测qps只有29
第二次压测qps qps只有12.3左右
证明:qps低和索引表有关
行动:试图删除索引表后重试压测,2kw数据的索引表,4秒删完了,hbase中索引表也消失了
结果:压测qps恢复为200了
再次证明:qps低确实和索引表有关
=====================线程堆栈分析、源码排查===============
虽然通过实验,证明qps差的原因和索引表有关,但是根本改进措施还不清楚,更深的原因还需要继续排查
151hbase测试环境新建表COUPON_TEST6,
以及各种索引表
IDX_COUPON_TEST9_ALL
IDX_COUPON_TEST9_MEMBER_ID
IDX_COUPON_TEST9_MEMBER_NO
........
然后使用如下sql进行压力测试
upsert into COUPON_TEST9 (PK,STATUS,MODIFICATION_DATE,MEMBER_ID) values ('021000872418880','2','2021-10-30 11:06:23','123456')
压测时入参都是固定的不变的参数,即不过压多少次都是同一条sql在更新
压测2小时后,qps降低到每分钟15个请求(50线程下)
通过idea的线程堆栈导出,发现了大量springmvc的线程阻塞情况如下
可发现这些线程在请求hbase的时候被阻塞了,因此阻塞方是hbase集群
查看hbase服务器日志,发现索引表的类在报错超时
后续使用debug dump将服务端的一个regionserver3服务器(主表更新region所在服务器)的线程堆栈拉取下来
可以发现有很多负责处理hbase请求的线程卡在一些地方长达5秒。通过堆栈可以发现,在hbase修改表的时候,会触发phoenix的协处理器,然后去更新索引表,在索引表的更新方法里有一个锁
根据堆栈,通过观察源码可以发现行锁代码如下
其中Lock类的tryLock方法会尝试获取锁,如果一直获取不到锁,就会阻塞到指定的时间
waitDuration是int,为30000 (毫秒),通过上图源码发现,这个锁和rowkey有关,可能是一个rowkey一个锁,应该是行锁
至此,已确认phoenix在并发更新同一条数据时qps很低的原因,为行锁导致
测试同事压测时,使用了相同的入参,因此更新的sql也相同,对于有索引表的情况,行锁竞争很激烈,所以接口很慢
最终处理方式:压力测试的时候使用不同的入参,入参不同则可降低锁竞争,qps则会提高