最近一次的项目体验,手机用户在训练完成之后,会有服务器超时的提示,在用户量大的时候,每晚的7-9点时间段发生的尤为频繁,所以作了一些的排查。
排查的顺序乃是这样的:
-
确定是哪个接口存在性能问题
-
确定这个接口的内部逻辑是怎样的,做了哪些事情
-
分析接口存在性能问题的根本原因
-
寻找确立优化方案
-
回归验证方案效果
首先哪个接口的话,代码当中马上就找到了,立刻开始确定里面的逻辑,这一下就发现了问题了,里面的逻辑随着业务的增加,这里面变得越来越臃肿,包括对多张表的查询、几张表的更新与新增,这一下就看到了,可是里面的一些逻辑实在太难拆出来了,上下的连接性太严重,需要前面的数据,后面的业务才能继续的走下去。没办法,就看看还有什么其他的地方需要优化的。
1.在sql日志的打印中,喜不自胜的看到了一连串了slow 慢sql语句,这下子可以从这些sql语句上着手的,这是肯定要优化的,都是一些执行时间大于1s的,更有甚者到达了5-6s。
最严重的,我发现这是一条update语句,竟然不是select查询语句,后来查了一下的资料,发现可能是因为事务的原因,线程被阻塞,锁住了要查询的表,导致sql语句过慢,接口也就相对应的慢起来,发现这个update的语句,会对播放记录play_total多次的进行+1的操作,每次当前视频video被跳一次,它的热度就作+1处理,导致这个字段被更新的过于频繁,这张表被锁死了,其他对video的查询操作进行不下去,整个接口的响应被拖慢了。
解决方案:
将这个play_total字段单独拿出来做张副表,专门对这个字段作更新+1的操作,这样在频繁也不会,对其他的查询操作有影响了,尤其是对video的影响
PS:其实还有更好的方法,对于这种频繁操作,但是却不太重要的字段,可以直接存在缓存当中,在服务器启动时,做一次查询操作,后面所有的更新操作直接在缓存里进行,这样也就不会对数据库有任何的影响了。隔段时间作同步操作,将缓存的数据同步到数据库中,这个涉及到数据的同步,可能会增加复杂度,使用的话,慎重。
2.在作了这些操作之后,当晚发布后,第二天再次到达晚上用户量较多时,发现情况稍有好转,但是问题仍旧存在,有一些其他接口的响应时长在30s以上,用户还是时常会报服务器超时,于是再次排查问题所在,又再次发现了,问题接口里的逻辑写的有些问题,很多的for循环里嵌套了一些其他的一些实现类,然后里面包含对数据库的操作,这样也就相当于循环多少次,会建立多少次的连接,严重拖慢了接口的响应。
于是作了一些的操作,在for循环外面将所有数据查询出来,sql里面用in foreach标签,这2个sql作了联表查询,
3.还有最后一个问题就是,后台会经常报“断开的管道”这个异常,实在是不经常见到的错误,所以在网上搜了一些的答案。
在这里面的文章里找到一些有用的内容,原来是因为接口响应过慢,导致前端那边设置的超时时间到了,直接主动切断了与后端的连接,但是后端还在查数据,查完以后想返给前端,发现管道已经断掉了,就只好报这个错误的
后来又找了一些其他的资料,终于找到了解决方案,
解决方法
1、调大防火墙的连接切断时长
这是一个临时解决方法,比如将防火墙的连接超时时间调整为8小时,这样可以尽量避免空闲连接的切断,但无法完全避免,因为无法预计连接会被空闲多久,如果你的系统不是总有人访问的话,那么连接迟早会因为空闲而被切断,导致一些不可预计的问题,而调大超时时间只是缓解而已
2、tcp keepalive功能
tcp的keepalive,其实就是用来保持tcp连接的,其原理简单说就是如果一个TCP连接在指定的时间内没有任何活动,会发送一个探测包到连接的对端,检测连接的对端是否仍然存在,如果对端一定时间内仍没有对探测的响应,会再次发送探测包,发送几次后,仍然没有响应,就认为连接已经失效,关闭本地连接。
tcp keepalive并不是默认开启的,在开发程序时可以设置tcp keepalive为true,这样tcp连接在一定时间内没有任何数据报文传输则启动探测,这个时间一般是操作系统规定,Linux系统中可以通过设置net.ipv4.tcp_keepalive_time
来修改,默认是7200秒,即2小时。当然在编程时也可以设置这个时间用于当前socket,但是Java的Socket API中好像只有设置keepalive=true,并没法设置tcp_keepalive_time
当设置了tcp keepalive之后,只要tcp探测包发送的时间小于防火墙的连接超时时间,防火墙就会检查到连接中仍然有数据传输,就不会断开这个连接。
使用JDBC创建的数据库tcp连接是没有设置keepalive的,这点可以通过Linux的netstat或ss命令在数据库客户端(即应用端)验证
使用命令netstat -ano 或 ss -ano,其中参数o都是显示timer计时器,timer计时器在连接建立状态下可以对连接保活计时
netstat命令对没有开启keepalive的tcp连接显示为:off (0.00/0/0)
ss命令对没有keepalive的tcp连接,不会显示timer计时器
3、程序不定时执行查询
以上几种方法要么是利用tcp连接keepalive特性,要么是采用数据库端的空闲连接检测,我们的程序中也可以主动做这种心跳检测
Druid数据库连接池从1.0.28开始,添加了druid.keepAlive属性,默认关闭
打开druid.keepAlive之后,当连接池空闲时,池中的minIdle数量以内的连接,空闲时间超过minEvictableIdleTimeMillis,则会执行keepAlive操作,即执行druid.validationQuery指定的查询SQL,一般为select * from dual,只要minEvictableIdleTimeMillis设置的小于防火墙切断连接时间,就可以保证当连接空闲时自动做保活检测,不会被防火墙切断
这就是整个问题的解决,希望可以帮到大家。