Wednesday, October 14, 2015

Performance Tips



Goal: When design application, what may restrict performance, what should be taken care of.

http://docs.oracle.com/javase/7/docs/technotes/guides/vm/performance-enhancements-7.html
Compressed oops is supported and enabled by default in Java SE 6u23 and later. In Java SE 7, use of compressed oops is the default for 64-bit JVM processes when -Xmx isn't specified and for values of -Xmxless than 32 gigabytes. For JDK 6 before the 6u23 release, use the -XX:+UseCompressedOops flag with the java command to enable the feature.
https://blogs.oracle.com/jrockit/entry/understanding_compressed_refer
By simply swapping JVMs, you gain the advantages of running as a 64-bit process (huge Java heap sizes, more CPU registers, etc) without even having to recompile your Java code.

What is -XX:+UseCompressedOops in 64 bit JVM
Though 64 bit JVM allows you to specify larger Java heap sizes it comes with a performance penalty by using 64 bit OOPSOrdinary object pointers also known as OOPS which is used to represent Java objects in Virtual Machine has an increased width of 64 bit than smaller 32 bit from earlier 32 bit JVM. because of increased size of OOPS, fewer OOPS can be stored in CPU cache registers which effectively reduced CPU cache efficiency. -XX:+UseCompressedOops enables use of compressed 32 bit OOPS in 64 bit JVM which effectively compensate performance penalty imposed by 64 bit JVM without scarifying heap size advantage offered by them. You should use -XX:+UseCompressedOops if maximum heap sizespecified by -Xmx is less than 32G. 

By using -XX:+UseCompressedOops you can avail benefit of both 64 bit JVM in terms of larger Java heap size and 32 bit JVM in terms of compressed size of OOPS which results in better performance by utilizing CPU cache better than larger, space inefficient 64 bit OOPS pointers. Since better application performance is directly proportional to better CPU cache utilization, -XX:+UseCompressedOops allows you to get most of your available CPU registers along with additional CPU registers provided by some platforms like AMD x64.

Elasticsearch: The Definitive Guide
Ensure that the min (Xms) and max (Xmx) sizes are the same to prevent the heap from resizing at runtime, a very costly process.
Don’t Cross 32 GB!
Give Half Your Memory to Lucene
Swapping Is the Death of Performance
File Descriptors and MMap
Lucene uses a very large number of files. At the same time, Elasticsearch uses a large number of sockets to communicate between nodes and HTTP clients. All of this requires available file descriptors.

many modern Linux distributions ship with a paltry 1,024 file descriptors allowed per process.
You should increase your file descriptor count to something very large, such as 64,000.

It was in previous version of Java but didn’t do very much. In Java 8, when there is enough inlining, the JVM can work out that an object doesn’t need to be created on the heap, but instead sits on the stack (or in many cases optimized away completely). This reduces work for the CPU but produces less garbage or even no garbage.
the most common causes of latency are poor measurement. You can’t fix what you can’t see. 

http://www.cnblogs.com/yanlingyin/archive/2012/02/11/2347116.html
通过上面的例子我们知道:在对向量的访问中,如果访问数序和存储顺序一致,并且是连续访问,那么这种访问具有良好的空间局部性。

1、重复引用同一个变量有良好的时间局部性

2、对于步长为k 的引用的程序,步长越小,空间局部性越小。步长为1 的引用具有良好的空间局部性。k越大,空间局部性越差。

3、对于取指令来说、循环有较好的时间和空间局部性。

http://geek.csdn.net/news/detail/90509

一、开发者的自测利器-Hprof命令
-agentlib:hprof=cpu=times,interval=10
hprof不是独立的监控工具,它只是一个java agent工具,它可以用在监控Java应用程序在运行时的CPU信息和堆内容,使用java -agentlib:hprof=help命令可以查看hprof的使用文档。

这个hprof小工具,非常方便我们在用JUnit自测代码的时候结合使用,既可以解决业务上的BUG,又能够在一定程序上解决可发现的性能问题,非常实用。
二、性能排查工具-pidstat
pidstat -p 843 1 3 -u -t /* -u:代表对cpu使用率的监控 参数1 3:表示每秒采样一次,一共三次 -t:将监控级别细化到线程

jstack -l 843 > /tmp/testlog.txt



pidstat是一个功能非常强大的性能监测工具,他是Sysstat的组件之一,可以从http://sebastien.godard.pagesperso-orange.fr/download.html 进行下载,下载后可以通过./configure等命令进行安装,这个命令的强大之处在于不仅可以监控进程的性能情况,也可以监控线程的性能情况。

系统共有8台服务器,每次随机只有一台服务器报java.lang.OutOfMemoryError: GC overhead limit exceeded错误,然后接着就报内存溢出错误java.lang.OutOfMemoryError: Java heap space
2. 理论支撑
我们先解释一下什么是GC overhead limit exceeded错误。
GC overhead limt exceed检查是Hotspot VM 1.6定义的一个策略,通过统计GC时间来预测是否要OOM了,提前抛出异常,防止OOM发生。Sun 官方对此的定义是:“并行/并发回收器在GC回收时间过长时会抛出OutOfMemroyError。过长的定义是,超过98%的时间用来做GC并且回收了不到2%的堆内存。用来避免内存过小造成应用不能正常工作。
可以看到当堆中的对象无法被收回的时候,就提前遇警报出这样的错误,此时内存并没有溢出,这个特性在JDK中是默认添加的。

1、单台40TPS,加到4台服务器能到60TPS,扩展性几乎没有。
2、在实际生产环境中,经常出现数据库死锁导致整个服务中断不可用。
3、数据库事务乱用,导致事务占用时间太长。
4、在实际生产环境中,服务器经常出现内存溢出和CPU时间被占满。
5、程序开发的过程中,考虑不全面,容错很差,经常因为一个小bug而导致服务不可用。
6、程序中没有打印关键日志,或者打印了日志,信息却是无用信息没有任何参考价值。
7、配置信息和变动不大的信息依然会从数据库中频繁读取,导致数据库IO很大。
8、项目拆分不彻底,一个tomcat中会布署多个项目WAR包。
9、因为基础平台的bug,或者功能缺陷导致程序可用性降低。
10、程序接口中没有限流策略,导致很多vip商户直接拿我们的生产环境进行压测,直接影响真正的服务可用性。
11、没有故障降级策略,项目出了问题后解决的时间较长,或者直接粗暴的回滚项目,但是不一定能解决问题。
12、没有合适的监控系统,不能准实时或者提前发现项目瓶颈。
分析:出现这种问题就是我们在项目中混杂了大量的事务+for update语句,针对数据库锁来说有下面三种基本锁:
图片描述
当for update语句和gap lock和next-key lock锁相混合使用,又没有注意用法的时候,就非常容易出现死锁的情况。
那我们用大量的锁的目的是什么,经过业务分析发现,其实就是为了防重,同一时刻有可能会有多笔支付单发到相应系统中,而防重措施是通过在某条记录上加锁的方式来进行。
针对以上问题完全没有必要使用悲观锁的方式来进行防重,不仅对数据库本身造成极大的压力,同时也会把对于项目扩展性来说也是很大的扩展瓶颈,我们采用了三种方法来解决以上问题:
  • 使用Redis来做分布式锁,Redis采用多个来进行分片,其中一个Redis挂了也没关系,重新争抢就可以了。
  • 使用主键防重方法,在方法的入口处使用防重表,能够拦截所有重复的订单,当重复插入时数据库会报一个重复错,程序直接返回。
  • 使用版本号的机制来防重。
public void test() {
    Transaction.begin  //事务开启
    try {
        dao.insert //插入一行记录
        httpClient.queryRemoteResult()  //请求访问
        dao.update //更新一行记录
        Transaction.commit()  //事务提交
    } catch(Exception e) {
          Transaction.rollFor //事务回滚
    } 
}
项目中类似这样的程序有很多,经常把类似httpClient,或者有可能会造成长时间超时的操作混在事务代码中,不仅会造成事务执行时间超长,而且也会严重降低并发能力。
那么我们在用事务的时候,遵循的原则是快进快出,事务代码要尽量小。针对以上伪代码,我们要用httpClient这一行拆分出来,避免同事务性的代码混在一起,这不是一个好习惯。
项目在压测的过程中,cpu一直居高不下,那么通过分析得出如下分析:
  • 数据库连接池影响
我们针对线上的环境进行模拟,尽量真实的在测试环境中再现,采用数据库连接池为咱们默认的C3P0。
那么当压测到二万批,100个用户同时访问的时候,并发量突然降为零!报错如下:
com.yeepay.g3.utils.common.exception.YeepayRuntimeException: Could not get JDBC Connection; nested exception is java.sql.SQLException: An attempt by a client to checkout a Connection has timed out.
那么针对以上错误跟踪C3P0源码,以及在网上搜索资料:
http://blog.sina.com.cn/s/blog_53923f940100g6as.html
发现C3P0在大并发下表现的性能不佳。
  • 线程池使用不当引起
private static final ExecutorService executorService = Executors.newCachedThreadPool();
 /**
 * 异步执行短频快的任务
 * @param task
 */
 public static void asynShortTask(Runnable task){
  executorService.submit(task);
  //task.run();
 }

           CommonUtils.asynShortTask(new Runnable() {
                @Override
                public void run() {
                    String sms = sr.getSmsContent();
                    sms = sms.replaceAll(finalCode, AES.encryptToBase64(finalCode, ConstantUtils.getDB_AES_KEY()));
                    sr.setSmsContent(sms);
                    smsManageService.addSmsRecord(sr);
                }
            });
以上代码的场景是每一次并发请求过来,都会创建一个线程,将DUMP日志导出进行分析发现,项目中启动了一万多个线程,而且每个线程都极为忙碌,彻底将资源耗尽。
那么问题到底在哪里呢???就在这一行!
private static final ExecutorService executorService = Executors.newCachedThreadPool();
在并发的情况下,无限制的申请线程资源造成性能严重下降,在图表中显抛物线形状的元凶就是它!!!那么采用这种方式最大可以产生多少个线程呢??答案是:Integer的最大值
图片描述
那么尝试修改成如下代码:
private static final ExecutorService executorService = Executors.newFixedThreadPool(50);
修改完成以后,并发量重新上升到100以上TPS,但是当并发量非常大的时候,项目GC(垃圾回收能力下降),分析原因还是因为Executors.newFixedThreadPool(50)这一行,虽然解决了产生无限线程的问题,但是当并发量非常大的时候,采用newFixedThreadPool这种方式,会造成大量对象堆积到队列中无法及时消费,看源码如下:
图片描述
可以看到采用的是无界队列,也就是说队列是可以无限的存放可执行的线程,造成大量对象无法释放和回收。
  • 最终线程池技术方案
方案一:
图片描述
注:因为服务器的CPU只有4核,有的服务器甚至只有2核,所以在应用程序中大量使用线程的话,反而会造成性能影响,针对这样的问题,我们将所有异步任务全部拆出应用项目,以任务的方式发送到专门的任务处理器处理,处理完成回调应用程序器。后端定时任务会定时扫描任务表,定时将超时未处理的异步任务再次发送到任务处理器进行处理。
方案二:
使用AKKA技术框架
4、日志打印问题
先看下面这段日志打印程序:
QuataDTO quataDTO = null;
        try {
            quataDTO = getRiskLimit(payRequest.getQueryRiskInfo(), payRequest.getMerchantNo(), payRequest.getIndustryCatalog(), cardBinResDTO.getCardType(), cardBinResDTO.getBankCode(), bizName);
        } catch (Exception e) {
            logger.info("获取风控限额异常", e);
        }
像这样的代码是严格不符合规范的,虽然每个公司都有自己的打印要求。
  • 首先日志的打印必须是以logger.error或者logger.warn的方式打印出来。
  • 日志打印格式:[系统来源] 错误描述 [关键信息],日志信息要能打印出能看懂的信息,有前因和后果。甚至有些方法的入参和出参也要考虑打印出来。
  • 在输入错误信息的时候,Exception不要以e.getMessage的方式打印出来。
合理的日志格式是:
logger.warn("[innersys] - [" + exceptionType.description + "] - [" + methodName + "] - "
                + "errorCode:[" + errorCode + "], "
                + "errorMsg:[" + errorMsg + "]", e);

logger.info("[innersys] - [入参] - [" + methodName + "] - "
                    + LogInfoEncryptUtil.getLogString(arguments) + "]");

logger.info("[innersys] - [返回结果] - [" + methodName + "] - " + LogInfoEncryptUtil.getLogString(result));
我们在程序中大量的打印日志,虽然能够打印很多有用信息帮助我们排查问题,但是更多是日志量太多不仅影响磁盘IO,更多会造成线程阻塞对程序的性能造成较大影响。
在使用Log4j1.2.14版本的时候,使用如下格式:
%d %-5p %c:%L [%t] - %m%n
那么在压测的时候会出现下面大量的线程阻塞,如下图
图片描述
原因可以根据log4j源码分析如下:
图片描述
注:Log4j源码里用了synchronized锁,然后又通过打印堆栈来获取行号,在高并发下可能就会出现上面的情况。
于是修改log4j配置文件为:
%d %-5p %c [%t] - %m%n
上面问题解决,线程阻塞的情况很少出现,极大的提高了程序的并发能力
http://geek.csdn.net/news/detail/81227
针对配置信息和变动不大的信息可以放到缓存中,提高并发能力也能够降低IO缓存

我们拿使用缓存来作为一个案例讲解,先看一个图:
图片描述
这是一个最简单的图,应用服务定期从redis中获取配置信息,可能会有朋友认为这样已经很稳定了,但是如果Redis出现问题呢?可能会有朋友说,Redis会是集群,分片或者主从,确保不会出现问题。其实我是这样的认为的,虽然应用服务程序尽量的保持轻量级是不错的,但是不能因此而把希望全部寄托在中间组件上面,换句话说,如果此时的Redis是单点,那么后果会是什么样的,那么随着大量的并发请求到来的时候,程序中会报大量的错误,同时正常的流程也不能进行下去了业务也可能由此而中断。
那么在此种场景下我的解决方案是,要把缓存的使用分级别,有的缓存同步要求时效性非常高,比如支付限额配置,在后台修改完成以后前台立刻就能够获得感知,并且能够成功切换,这种情况只能实时的从Redis中获取最新数据,但是每次获取完最新的数据后都可以同步更新本地缓存,当单点的Redis挂掉后,应用程序至少还能从本地读取信息而不至于服务瞬间挂掉。有的缓存对时效性要求不高,允许有一定延迟,那么在这种情况下我采用的方案是,利用本地缓存和远程缓存相结合的方式,如下图所示:
方案一:
图片描述
这种方式通过应用服务器的Ehcache定时轮询Redis缓存服务器更同步更新本地缓存,缺点是因为每台服务器定时Ehcache的时间不一样,那么不同服务器刷新最新缓存的时间也不一样,会产生数据不一致问题,对一致性要求不高可以使用。
方案二:
图片描述
通过引入了MQ队列,使每台应用服务器的Ehcache同步侦听MQ消息,这样在一定程度上可以达到准同步更新数据,通过MQ推送或者拉取的方式,但是因为不同服务器之间的网络速度的原因,所以也不能完全达到强一致性。基于此原理使用Zookeeper等分布式协调通知组件也是如此。
7、部分项目拆分不彻底

    注:一个Tomcat中布署多个应用war包,彼此之间互相牵制在并发量非常大的情况下性能降低非常明显。
    
    解决的方法很简单,每一个应用war只布在一个tomcat中,这样应用程序之间就不会存在资源和连接数的竞争情况,性能和并发能力提交较为明显。

    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使用完以后一定要释放连接,如下图示例:
    图片描述
    不管是返回到连接池中还是直接释放掉,总之就是要将连接还回去。
    https://blog.eood.cn/apo-latency
    • 某大型网站的 Redis 集群的机器负载每几十分钟出现一个超越平时几十倍大的高峰,结果是因为这些实例每几十分钟对所有数据进行写盘持久化。

    1. 应用本身的 BUG 和不合理的地方

    无论网站还是移动应用 API,或者是实时通讯和聊天系统,本质上都是用户通过 HTTP 和其他 TCP 协议交互信息的过程。
    所以,其中大部分模式都是相通的,并且可以重复利用。比如一些常见的思路和技术:负载均衡、缓存、索引、命名系统、连接重用等等。
    随着流量的变化,引起的延迟变化或者机器负载变化,有经验的架构师或者运维基本心里有数。通过对商业模式的分析,相对应的在线业务系统应该有的延迟、服务器压力等等数据可以从心里估算出大致的范围。结合此业务系统的监控图表,基本可以很快看出不合理的地方。然后从一个线索入手定位问题所在,进行修复。

    2. 应用业务之外的架构层次的修改

    假如不考虑程序的 BUG 或者代码本身的优化,在这之上的一个层次也可以有很多可以优化的方向。
    这里主要说明一个利用 最终一致性 的思路修改缓存系统对网页延迟的优化。
    Varnish 4.0 增加了一个新功能:
    Background (re)fetch of expired objects. On a cache miss where a stale copy is available, serve the client the stale copy while fetching an updated copy from the backend in the background.
    假如 cache miss,立刻返回给用户一个之前的旧版本缓存,然后再从应用服务器请求新的版本。这意味着如果缓存足够大,可以让用户 100% 访问到 Varnish 服务器上缓存的版本,而跳过后端服务器的延迟。这也是为什么标题写着降低到 0ms :-)

    http://www.raychase.net/3658
    Service的性能优化
    后来工作需要维护过一个几十台机器集群的service,接受get请求,从数据库里面查数据以某种xml形式返回给用户。这种请求处理的并发数、TPS和latency都非常关键,给数据库压力很大,而相对地,计算逻辑就比较简单。
    当时想了几种优化的办法。
    第一种是中心化的缓存,使用Memcached,主要是考虑总的请求重复率可能在50%左右,但是如果在单台机器上做缓存,这个cache hit比率是非常低的,但是使用一台中心缓存服务器可以提高缓存命中率。但是另一方面,如果一旦引入中心缓存,又会带来很多新问题,比如这样的缓存读写开销就不能忽略了,对于缓存没有命中的case,性能反而是下降的;比如一旦这个中心缓存服务器挂掉怎么办,一定要设置非常短的cache访问超时机制;还有,写缓存的操作,完全做成NoReply形式的,像发送UDP报文一样,只管请求,不管结果,从而尽量保证总的latency。我把Memcached调优的一些总结放在了这里
    第二种思路是把计算部分放到客户端去,让service变得很薄,以期望减小CPU的使用。不过这一点上效果也一般,主要是因为瓶颈毕竟主要还是在数据库查询上面。基于这一点,后来还出现了一种思路,就是异步算好这些用户可能需要的结果,等到需要的时候直接来取就好。
    最后,为了减小对关系数据库的压力,增加扩展性,把数据源挪到了DynamoDB这个NoSQL数据库上面。
    http://calvin1978.blogcn.com/articles/latency.html
    准备知识: 《从Apache Kafka 重温文件高效读写》中Swap 和 PageCache的部分。

    1. 禁用swap

    Linux有个很怪的癖好,当内存不足时,看心情,有很大机率不是把用作IO缓存的Page Cache收回,而是把冷的应用内存page out到磁盘上(具体算法看准备知识)。当这段内存重新要被访问时,再把它重新page in回内存(所谓的主缺页错误),这个过程进程是停顿的。增长缓慢的老生代,池化的堆外内存,都可能被认为是冷内存,用 cat /proc/[pid]/status 看看 VmSwap的大小, 再dstat里看看监控page in发生的时间。
    在 /etc/sysctl.conf 放入下面一句,基本可以杜绝swap。设成0会导致OOM,案例在此,有些同学就设成1,喜欢就好。

    vm.swappiness = 10

    2. 加快Page Cache Flush的频率

    又是一个Linux自己的奇怪设置,Linux的Page Cache机制说来话长(还是看看准备知识), 简单说IO其实都默认不是先写磁盘,而是写进Page Cache内存,inode脏了30秒,或脏数据达到了10%可用内存(Free+PageCache -mmap),才开始起flusher线程写盘。
    我们的生产机内存起码都剩20G的样子,想想要以普通硬盘100MB/s级别的速度,一次写2G文件的速度....好在一般都达不到这个条件,一般是由几个日志文件轮流30秒触发,一次写几百M,花个三秒左右的时间。
    文章里说,后台刷盘线程不会阻塞应用程序的write(2)。但是,
    应用的write 过程是这样的:
    锁inode -> 锁page -> 写入page -> 解锁page -> 解锁inode -> 锁inode page -> 写inode page -> 解锁inode page
    而flusher的过程是这样的:
    锁page -> 将page放进IO队列,等待IO调度写盘完成返回 -> 解锁page
    可见,还是有锁,IO调度器也不是绝对公平,当IO繁忙,应用还是会发生阻塞 。
    我们的做法是用一个100MB的绝对值来代替可用内存百分比来作为阀值。
    在 /etc/sysctl.conf 里加入

    vm.dirty_background_bytes = 104857600
    在第二篇举的JVM停顿的例子,全和IO相关,即使不做后面JVM的调优,光降低这个阀值,也能大大缓解。
    当然什么值是最优,必须根据机器配置,应用特性来具体分析。

    3. 网络参数

    太多可配置的地方,可以参考阿里云团队的一个很好的文章 Linux TCP队列相关参数的总结。 还是那句,不能看着文章就开始设,必须根据自己的情况。
    比如我们自己设置网卡软中断队列的CPU亲和度:
    平时网卡中断可能只用一个核来响应,在大流量下那个核会跑满。
    运行irqbalance,也只是用到了1个CPU,12个核。
    最后自己设定24条网卡中断队列对应24个核,效果最佳。。。。。。但你的情况就不一定一样啊。
    http://calvin1978.blogcn.com/articles/latency2.html
    在压测中非常偶然的超时,但CPU正常,网络正常,只有IO在刷盘时偶然写个几秒。那,就继续怀疑IO吧。

    第一步,日志都异步写了吗?而且,异步队列满时会选择丢弃吗?

    Logback在异步日志队列满时,默认并不是丢弃的而是阻塞等待的,需要自己配置。
    或者干脆自己重新写一个更高效的异步Appender。
    One of these things is not like the other. Real refers to actual elapsed time; User and Sys refer to CPU time used only by the process.
    • Real is wall clock time - time from start to finish of the call. This is all elapsed time including time slices used by other processes and time the process spends blocked (for example if it is waiting for I/O to complete).
    • User is the amount of CPU time spent in user-mode code (outside the kernel) within the process. This is only actual CPU time used in executing the process. Other processes and time the process spends blocked do not count towards this figure.
    • Sys is the amount of CPU time spent in the kernel within the process. This means executing CPU time spent in system calls within the kernel, as opposed to library code, which is still running in user-space. Like 'user', this is only CPU time used by the process. See below for a brief description of kernel mode (also known as 'supervisor' mode) and the system call mechanism.
    User+Sys will tell you how much actual CPU time your process used. Note that this is across all CPUs, so if the process has multiple threads (and this process is running on a computer with more than one processor) it could potentially exceed the wall clock time reported by Real (which usually occurs). Note that in the output these figures include the User and Sys time of all child processes (and their descendants) as well when they could have been collected, e.g. by wait(2) or waitpid(2), although the underlying system calls return the statistics for the process and its children separately.
    https://zhuanlan.zhihu.com/p/31513942
    平均400ms延时和稳定400ms延时,用户的体验是不一样的。稳定的400ms延时,(用户心里面会有预期),相比一会儿 200,一会儿 800 会有更好的体验。
    抓娃娃快速上线之后的关键问题,就是留存。除去业务模式,决定用户留存的就是体验质量。舍弃用户体验而求快,是本末倒置。回归质量、用户体验,才是长久的发展之路。


    Labels

    Review (572) System Design (334) System Design - Review (198) Java (189) Coding (75) Interview-System Design (65) Interview (63) Book Notes (59) Coding - Review (59) to-do (45) Linux (43) Knowledge (39) Interview-Java (35) Knowledge - Review (32) Database (31) Design Patterns (31) Big Data (29) Product Architecture (28) MultiThread (27) Soft Skills (27) Concurrency (26) Cracking Code Interview (26) Miscs (25) Distributed (24) OOD Design (24) Google (23) Career (22) Interview - Review (21) Java - Code (21) Operating System (21) Interview Q&A (20) System Design - Practice (20) Tips (19) Algorithm (17) Company - Facebook (17) Security (17) How to Ace Interview (16) Brain Teaser (14) Linux - Shell (14) Redis (14) Testing (14) Tools (14) Code Quality (13) Search (13) Spark (13) Spring (13) Company - LinkedIn (12) How to (12) Interview-Database (12) Interview-Operating System (12) Solr (12) Architecture Principles (11) Resource (10) Amazon (9) Cache (9) Git (9) Interview - MultiThread (9) Scalability (9) Trouble Shooting (9) Web Dev (9) Architecture Model (8) Better Programmer (8) Cassandra (8) Company - Uber (8) Java67 (8) Math (8) OO Design principles (8) SOLID (8) Design (7) Interview Corner (7) JVM (7) Java Basics (7) Kafka (7) Mac (7) Machine Learning (7) NoSQL (7) C++ (6) Chrome (6) File System (6) Highscalability (6) How to Better (6) Network (6) Restful (6) CareerCup (5) Code Review (5) Hash (5) How to Interview (5) JDK Source Code (5) JavaScript (5) Leetcode (5) Must Known (5) Python (5)

    Popular Posts