论代码级性能优化变迁之路(二)

本文是“论代码级性能优化变迁之路一”的第二篇。

在上一篇我们主要介绍了所遇到问题的五点,那么今天接下来讨论剩下的问题,我们先再回顾一下之前讨论的问题:

1、单台40TPS,加到4台服务器能到60TPS,扩展性几乎没有。

2、在实际生产环境中,经常出现数据库死锁导致整个服务中断不可用。

3、数据库事务乱用,导致事务占用时间太长。

4、在实际生产环境中,服务器经常出现内存溢出和CPU时间被占满。

5、程序开发的过程中,考虑不全面,容错很差,经常因为一个小bug而导致服务不可用。

6、程序中没有打印关键日志,或者打印了日志,信息却是无用信息没有任何参考价值。

7、配置信息和变动不大的信息依然会从数据库中频繁读取,导致数据库IO很大。

8、项目拆分不彻底,一个tomcat中会布署多个项目WAR包。

9、因为基础平台的bug,或者功能缺陷导致程序可用性降低。

10、程序接口中没有限流策略,导致很多vip商户直接拿我们的生产环境进行压测,直接影响真正的服务可用性。

11、没有故障降级策略,项目出了问题后解决的时间较长,或者直接粗暴的回滚项目,但是不一定能解决问题。

12、没有合适的监控系统,不能准实时或者提前发现项目瓶颈。

四、优化解决方案

5、缓存优化方案

针对配置信息和变动不大的信息可以放到缓存中,提高并发能力也能够降低IO缓存,具体缓存优化策略可以参考我之前写的:

http://www.jianshu.com/p/d96906140199

6、程序容错优化方案

在这一块我要先举一个程序的例子说明一下什么才是容错,先看程序:

//Service层:
public void insertOrderInfo(OrderInfo orderInfo) {
        try {
            OrderDao.insertOrderInfo(orderInfo);
        } catch (Exception e) {
            logger.error("订单信息插入数据库失败! orderId:"+orderInfo.getOrderId(), e);
        }
    }

//DAO层
 public void insertOrderInfo(OrderInfo orderInfo) {
        try {
            this.sqlMapClient.insert("Order.insertOrderInfo", orderInfo)
        } catch (Exception e) {}
    }

注:

那么如果service层的方法调用dao层的方法,一旦数据插入失败,那么这种异常处理的方式是容错吗?

把异常给吃掉了,在service层调用的时候,虽然没有打印报错信息,但是这能是容错吗?

所谓容错是指在故障存在的情况下计算机系统不失效,仍然能够正常工作的特性。

我们拿使用缓存来作为一个案例讲解,先看一个图:

这是一个最简单的图,应用服务定期从redis中获取配置信息,可能会有朋友认为这样已经很稳定了,但是如果Redis出现问题呢?可能会有朋友说,Redis会是集群,分片或者主从,确保不会出现问题。其实我是这样的认为的,虽然应用服务程序尽量的保持轻量级是不错的,但是不能因此而把希望全部寄托在中间组件上面,换句话说,如果此时的Redis是单点,那么后果会是什么样的,那么随着大量的并发请求到来的时候,程序中会报大量的错误,同时正常的流程也不能进行下去了业务也可能由此而中断。

那么在此种场景下我的解决方案是,要把缓存的使用分级别,有的缓存同步要求时效性非常高,比如支付限额配置,在后台修改完成以后前台立刻就能够获得感知,并且能够成功切换,这种情况只能实时的从Redis中获取最新数据,但是每次获取完最新的数据后都可以同步更新本地缓存,当单点的Redis挂掉后,应用程序至少还能从本地读取信息而不至于服务瞬间挂掉。有的缓存对时效性要求不高,允许有一定延迟,那么在这种情况下我采用的方案是,利用本地缓存和远程缓存相结合的方式,如下图所示:

方案一:

这种方式通过应用服务器的Ehcache定时轮询Redis缓存服务器更同步更新本地缓存,缺点是因为每台服务器定时Ehcache的时间不一样,那么不同服务器刷新最新缓存的时间也不一样,会产生数据不一致问题,对一致性要求不高可以使用。

方案二:

通过引入了MQ队列,使每台应用服务器的Ehcache同步侦听MQ消息,这样在一定程度上可以达到准同步更新数据,通过MQ推送或者拉取的方式,但是因为不同服务器之间的网络速度的原因,所以也不能完全达到强一致性。基于此原理使用Zookeeper等分布式协调通知组件也是如此。

7、部分项目拆分不彻底

  • 拆分前

    注:

    一个Tomcat中布署多个应用war包,彼此之间互相牵制在并发量非常大的情况下性能降低非常明显。

  • 拆分后

注:

拆分前的这种情况其实还是挺普遍,之前我一直认为项目中不会存在这种情况但是事实上还是存在了。解决的方法很简单,每一个应用war只布在一个tomcat中,这样应用程序之间就不会存在资源和连接数的竞争情况,性能和并发能力提交较为明显。

8、因基础平台组件功能不完善导致性能下降

先看一段代码:

public void purchase(PurchaseParam purchaseParam, long timeoutSencond) {
        Future<String> future = threadPool.submit(new TestRunnable(purchaseParam, testService));
        logger.info("超时时间="+timeoutSencond);
        if(timeoutSencond > 0){
            try {
                future.get(timeoutSencond, TimeUnit.SECONDS);
                logger.info("超时返回,超时时间="+timeoutSencond);
            } catch (InterruptedException e) {
                logger.info("",e);
            } catch (ExecutionException e) {
                logger.info("",e);
            } catch (TimeoutException e) {
                logger.info("",e);
            }
        }
    }

注:

首先我们先不说这段代码的格式如何如何,先看功能实现,使用Future来做超时控制,这是为何呢?原因其实是在我们调用的Dubbo接口上面,因为是Dubbo已经经过二次封装,结果把自带的timeout给淹沫了,程序员只能通过这种方式来控制超时,可以看到这种用法非常差劲,对程序性能造成一定的影响。

9、如何快速定位程序性能瓶颈

我相信在定位程序性能问题的时候,大家有很多种办法,比如用jdk自带的命令,如Jcmd,Jstack,jmap,jhat,jstat,iostat,vmstat等等命令,还可以用VisualVM,MAT,JRockit等可视化工具,我今天想说的是利用一个最简单的命令就能够定位到哪段程序可能存在性能问题,请看下面介绍:

一般我们会通过top命令查看各个进程的cpu和内存占用情况,获得到了我们的进程id,然后我们将会通过pstack命令查看里边的各个线程id以及对应的线程现在正在做什么事情,分析多组数据就可以获得哪些线程里有慢操作影响了服务器的性能,从而得到解决方案。示例如下:

输入命令:pstack 30222

显示如下:
Thread 9 (Thread 0x7f729adc1700 (LWP 30251)):
#0  0x00007f72a429b720 in sem_wait () from /lib64/libpthread.so.0
#1  0x0000000000ac5eb6 in Semaphore::down() ()
#2  0x0000000000ac5cac in Queue::get() ()
#3  0x00000000009a583f in DBManager::processUpdate(Queue*) ()
#4  0x00000000009a4bfb in dbUpdateThread(void*) ()
#5  0x00007f72a4295851 in start_thread () from /lib64/libpthread.so.0
#6  0x00007f72a459267d in clone () from /lib64/libc.so.6
Thread 1 (Thread 0x7f72a60ae7e0 (LWP 30222)):
#0  0x00007f72a4584c95 in _xstat () from /lib64/libc.so.6
#1  0x00007f72a45483e0 in __tzfile_read () from /lib64/libc.so.6
#2  0x00007f72a4547864 in tzset_internal () from /lib64/libc.so.6
#3  0x00007f72a4547b20 in tzset () from /lib64/libc.so.6
#4  0x00007f72a4546699 in timelocal () from /lib64/libc.so.6
#5  0x0000000000b0b08d in Achieve::GetRemainTime(AchieveTemplate*) ()
#6  0x0000000000b115ca in Achieve::update() ()
#7  0x0000000000a197ce in Player::update() ()
#8  0x0000000000b1b272 in PlayerMng::Tick() ()
#9  0x0000000000a73105 in GameServer::FrameTick(unsigned int) ()
#10 0x0000000000a6ff80 in GameServer::run() ()
#11 0x0000000000a773a1 in main ()

输入命令:ps  -eLo pid,lwp,pcpu | grep 30222
显示如下:

30222 30222 31.4
30222 30251  0.0
30222 30252  0.0
30222 30253  0.0

由此可以判断出来在LWP 30222这个线程产生了性能问题,执行时间长达31.4毫秒的时间,再观察无非就是下面的几个语句出现的问题,只需要简单排查就知道了问题瓶颈。

10、关于索引的优化

* 组合索引的原则是偏左原则,所以在使用的时候需要多加注意

  • 索引的数量不需要过多的添加,在添加的时候要考虑聚集索引和辅助索引,这二者的性能是有区别的
  • 索引不会包含有NULL值的列

    只要列中包含有NULL值都将不会被包含在索引中,复合索引中只要有一列含有NULL值,那么这一列对于此复合索引就是无效的。所以我们在数据库设计时不要让字段的默认值为NULL。

  • MySQL索引排序

    MySQL查询只使用一个索引,因此如果where子句中已经使用了索引的话,那么order by中的列是不会使用索引的。因此数据库默认排序可以符合要求的情况下不要使用排序操作;尽量不要包含多个列的排序,如果需要最好给这些列创建复合索引。

  • 使用索引的注意事项

    以下操作符可以应用索引:

    大于等于

    Between

    IN

    LIKE 不以%开头

    以下操作符不能应用索引:

    NOT IN

    LIKE %_开头

  • 索引技巧

    同样是1234567890,数值类型存储远比字符串节约存储空间。

    节约存储就是节约IO,减少IO就是提升性能

    通常对数字的索引和检索要比对字符串的索引和检索效率更高。

* 11、使用Redis需要注意的一些点*

  • 在增加key的时候尽量设置过期时间,不然Redis Server的内存使用会达到

    系统物理内存的最大值,导致Redis使用VM降低系统性能

  • Redis Key设计时应该尽可能短,Value尽量不要使用复杂对象。
  • 将对象转换成JSON对象(利用现成的JSON库)后存入Redis,
  • 将对象转换成Google开源二进制协议对象(Google Protobuf,和JSON数据

    格式类似,但是因为是二进制表现,所以性能效率以及空间占用都比JSON要小;

    缺点是Protobuf的学习曲线比JSON大得多)

  • Redis使用完以后一定要释放连接,如下图示例:

    不管是返回到连接池中还是直接释放掉,总之就是要将连接还回去。

* 12、关于长耗时方法的拆分*

我们拆分长耗时方法的一般技巧是:

* 寻找业务的冗余点,代码中有很多重复性的代码,可以适当简化。

* 检查库表索引是否合理加入。

* 利用单元测试或者压力测试长耗时的操作进行算法级别优化,比如从库中大批量读取数据,或者长时间循环操作,或者死循环操作等等。

* 寻找业务的拆分点,根据业务需求拆分同步操作为异步,比如可以使用消息队列或者多线程异步化。

经过以上几个分析后如果方法执行时间仍然非常的长,这样可能就是业务方面的需求使然,如下图:

那么我们是否可以考虑将一个长耗时方法进行拆分,拆分为多个短耗时方法由发起端分别调用,这样在高并发的情况下不会造成某一个方法的长时间阻塞,在一定程度上能够提高并发能力,如下图:

在接下来的第三篇文章中我们就介绍系统的降级,限流,还有监控的一些方案。谢谢大家

时间: 2024-10-13 21:21:58

论代码级性能优化变迁之路(二)的相关文章

论代码级性能优化变迁之路(一)

一.前言 大家好,很久没有和大家一起讨论技术了,那么今天我将和大家一起探讨我负责的某项目的性能变迁之路. 我们以前看到的很多架构变迁或者演进方面的文章大多都是针对架构方面的介绍,很少有针对代码级别的性能优化介绍,这就好比盖楼一样,楼房的基础架子搭的很好,但是盖房的工人不够专业,有很多需要注意的地方忽略了,那么在往里面填砖加瓦的时候出了问题,后果就是房子经常漏雨,墙上有裂缝等各种问题出现,虽然不至于楼房塌陷,但楼房也已经变成了危楼.那么今天我们就将针对一些代码细节方面的东西进行介绍,欢迎大家吐槽以

代码级性能优化案例(一)

一.压测服务器环境 服务器配置:4核CPU 8G内存 共4台MQ:RabbitMQ数据库:DB2SOA框架:公司内部封装的Dubbo缓存框架:Redis,Memcached统一配置管理系统:公司内部开发的系统 二.压测性能问题描述 1. 单台40TPS,加到4台服务器能到60TPS,扩展性几乎没有.2. 在实际生产环境中,经常出现数据库死锁导致整个服务中断不可用.3. 数据库事务乱用,导致事务占用时间太长.4. 在实际生产环境中,服务器经常出现内存溢出和CPU时间被占满.5. 程序开发的过程中,

PHP性能优化学习笔记--语言级性能优化--来自慕课网Pangee http://www.imooc.com/learn/205

使用ab进行压力测试 ab -n行数 -c并发数 url 重点关注下面两点: 1.Request per secend : 每秒可接收的请求数 2.Time per request : 每次请求所耗费的时间 优化1.多使用PHP自身的功能(如PHP定义的函数.常量),尽量少自己造轮子,自己写的代码冗余较多,可读性不高,且性能低下 PHP每次接受请求后,都会进行编译成底层语言,C->汇编->机器语言,同时接受大量请求,每个请求都会执行一次编译 示例代码如下: bad.php          

小蚂蚁学习PHP性能优化(2)--PHP语言级性能优化

接上篇 3.    优化点:尽可能少的使用魔法函数 情况描述:PHP提供的魔法函数,性能不佳 为什么呢?为了给PHP程序员省事,PHP语言为此做了很多 好的方法:尽可能规避使用PHP魔法函数,需要使用的时候,权衡一下利弊 4.    优化点:产生额外开销的错误抑制符@ 情况描述:PHP提供的错误抑制符只是为了方便"懒人" @符号的实际逻辑:在代码开始前,结束后,增加了opcode,忽略了报错. 好的建议:建议尽量不要使用@错误抑制符 5.    优化点:合理使用内存 情况描述:PHP有

RabbitMQ MQTT插件源码级性能优化

最近在搞物联网平台,海量级别的消息Push导致MQ处理速度下降,对MQ进行单队列性能压测,结果很不如意啊!下游设备是通过NB模块和ESP进行双链路数据采集,由于场景就是抄表,但是下游设备太多,老板也没给多少银子买云服务,所以只能自己研究一波儿了~ 抄表也就意味着单Topic,进行测试的时候单个Topic消费端TPS到1.7w/s,大量的消息处于unconfirmed未确认状态,达到了TPS上限,然后通过新增消费端仍然是无法解决,那么就将性能瓶颈的视角转向了MQ服务. 对于瞬间大量并发的数据平台来

CSS性能优化的技巧(二)

前面已经说完了实践型的4个优化技巧,下面我们介绍下建议型的4个技巧.1. 有选择地使用选择器大多数朋友应该都知道CSS选择器的匹配是从右向左进行的,这一策略导致了不同种类的选择器之间的性能也存在差异.相比于#markdown-content-h3,显然使用#markdown .content h3时,浏览器生成渲染树(render-tree)所要花费的时间更多.因为后者需要先找到DOM中的所有h3元素,再过滤掉祖先元素不是.content的,最后过滤掉.content的祖先不是#markdown

app 性能优化的那些事(二)

来源:树下的老男孩 链接:http://www.jianshu.com/p/2a01e5e2141f 这次我们来说说iOS app中滑动的那些事.iOS为了提高滑动的流畅感,特意在滑动的时候将runloop模式切换到UITrackingRunLoopMode,在这个过程中专心做跟滑动相关的工作,这也就是在滑动过程中为什么nstimer无法工作的原因,因为两个没在同一mode下面.但我们可能经常会遇到滑动不怎么流畅的情况,比如在项目中碰到在滑动tableview的时候不怎么顺畅,感觉有点不爽,即便

Android性能优化典例(二)

1.使用 Maven 依赖方案取代使用导入jar包方案 假设项目中须要用到第三方jar包.经常使用的做法是去网上下载后然后放入libs目录,再加入到项目依赖,只是,在Android Studio已经不推荐使用这套做法了,由于假设jar有更新.那么每次都要去下载最新版本号然后删除历史依赖再加入新版本号的依赖,这样做非常繁琐.而在Android Studio中,这个问题使用Maven已经非常好的攻克了,由于AS中默认的是jcenter中央库,而jcenter默认会同步Maven中央库,所以我们能够使

PHP性能优化

图示为100个并发,请求1000次目标地址 进行测试 最重要的两个参数:Requests per second :每秒接受请求数,这里每秒接收101个请求 Time per request:一个请求用多少耗时,这里是9毫秒 第一个参数越大越好,第二个参数越小越好 优化方法之语言级性能优化1 PHP代码执行流程:  PHP代码通过zend引擎逐行扫描,成为zend引擎能理解的语法,转码解析成Opcodes,执行之后输出 如果多使用内置函数的话,在扫描和理解上时间就会快很多,Opcodes也会少一些