qiubo's life

Stay Hungry, Stay Foolish


  • 首页

  • 关于

  • 标签

  • 分类

  • 归档

  • 搜索

2014年10月Reading Notes

发表于 2014-10-15 | 分类于 java

Why does my Java process consume more memory than Xmx?

https://plumbr.eu/blog/why-does-my-java-process-consume-more-memory-than-xmx

java进程内存消耗会大于我们在-Xmx中指定的值.-Xmx仅仅限制了应用程序使用的heap大小.java进的内存消耗主要包括下面的东东:

Max memory = [-Xmx] + [-XX:MaxPermSize] + number_of_threads * [-Xss]+Other

Other:

  • Garbage collection(GC自己需要消耗内存来记录数据)
  • JIT optimization(JIT优化需要记录代码执行行为,Code cache-JIT编译完成后存放机器码)
  • Off-heap allocations(NIO/DirectMemory之类的东东)
  • JNI code(程序使用第三方JNI代码占用的内存)
  • Metaspace(jdk8使用它取代了permgen)

Understanding the OutOfMemoryError

https://plumbr.eu/blog/understanding-java-lang-outofmemoryerror

  • java.lang.OutOfMemoryError: Java heap space

    heap空间不足.一般加大-Xmx.如果还不足就有可能是内存泄漏了.

  • java.lang.OutOfMemoryError: PermGen space

    permgen空间不足,默认的jvm配置得比较小,需要通过-XX:MaxPermSize加大.动态代码生成技术和容器redeploy资源泄漏也会导致permgen不足

  • java.lang.OutOfMemoryError: GC overhead limit exceeded

    jvm gc行为中超过98%以上的时间去释放小于2%的堆空间时会报这个错误

  • java.lang.OutOfMemoryError: unable to create new native thread

    java没创建一个线程,会占用-Xss大小空间.这个异常有可能是系统内存不足导致.如果系统内存充足,往往是ulimit -u限制了一个用户创建最大线程数造成的.

  • java.lang.OutOfMemoryError: Requested array size exceeds VM limit

    申请的数组大小超过jvm定义的阀值.

  • java.lang.OutOfMemoryError: request bytes for . Out of swap space?

  • java.lang.OutOfMemoryError: (Native method)

20 Obstacles to Scalability

本文罗列了20个影响伸缩性的瓶颈

10 Obstacles to Scaling Performance

  1. Two-Phase Commit

    两阶段提交需要等待参与方确认,延时太大.所以我们基本上使用best-effort 1pc+业务上的重试.

  2. Insufficient Caching

    各个层次都需要引入缓存机制,现在我们对browser cache/page cache还做得比较少.

  3. Slow Disk I/O, RAID 5, Multitenant Storage

    数据库服务起I/O很关键.如果只做raid建议做raid10.当然加上fushion io之类的加速卡更好.

  4. Serial Processing

    服务并行处理我们也还思考得比较少.比如对远程服务进行合理并行处理(可以考虑下java8中的CompletableFuture).对于缓存数据的获取,可以考虑批量获取.

  5. Missing Feature Flags

    特性开关,说大点就是服务降级,我们需要却分不同服务的重要等级,适当时候关闭某些服务,保证核心业务正常运行.

  6. Single Copy of the Database

  7. Using Your Database for Queuing
  8. Using a Database for Full-Text Searching
  9. Object Relational Models(orm很好用,但是缺少能hold住他的人)
  10. Missing Instrumentation(需要监控/profile工具)

10 Obstacles to Scaling Beyond Optimization Speed

  1. Lack of a Code Repository and Version Control
  2. Single Points of Failure
  3. Lack of Browse-Only Mode(对于内容型的网站,此功能非常重要)
  4. Weak Communication
  5. Lack of Documentation
  6. Lack of Fire Drills(却分演练,特别是大的运维调整,此项非常必要)
  7. Insufficient Monitoring and Metrics
  8. Cowboy Operations
  9. Growing Technical Debt
  10. Insufficient Logging

2014年09月Reading Notes

发表于 2014-09-28 | 分类于 java

Mcrouter:基于 Memcached协议的缓存层流量管理工具

http://www.infoq.com/cn/news/2014/09/mcrouter-memcached

https://code.facebook.com/posts/296442737213493/introducing-mcrouter-a-memcached-protocol-router-for-scaling-memcached-deployments/

memcache不支持服务端路由,facebook开发了mcrouter(能够处理每秒50亿次的请求).它和memcached之间通过文本协议通信.扮演者memcached服务器的客户端,应用的服务端.他的特性很多,基本上都是我们需要的.我们现在使用的是二进制协议,需要修改为文本协议.

dubbo

发表于 2014-09-23 | 分类于 java

dubbo的一些分析和优化

dubbo应用线程分析

dubbo-remoting-client-heartbeat-thread

dubbo客户端心跳线程

线程启动类:HeaderExchangeClient

任务执行类:HeartBeatTask

此任务会定时向服务端发送心跳消息,每个连接一个任务,执行周期默认为60s,执行时会判断此Channel是否在心跳周期类有读写,如果没有,给服务端发送心跳信号.

改进措施:可以给执行线程合理命名。

xxxx-EventThread

zookeeper事件处理线程

线程启动类:org.apache.zookeeper.ClientCnxn

任务执行类:org.apache.zookeeper.ClientCnxn.EventThread

此线程的命名格式为启动线程名+EventThread,其中的localhost-startStop-1tomcat启动线程名。

在CuratorFrameworkImpl启动时,会向zookeeper服务器建立连接,此时会创建此线程。

我们必须保证一个应用只有一个CuratorFrameworkImpl实例。

localhost-startStop-1-SendThread

zookeeper 心跳、发送请求线程

线程启动类:org.apache.zookeeper.ClientCnxn

任务执行类:org.apache.zookeeper.ClientCnxn.SendThread

DubboClientReconnectTimer-thread

消费者连接服务提供者的定时重连任务,默认执行周期2s,检查是否连接,没有连接重新连接。

线程启动类:com.alibaba.dubbo.remoting.transport.AbstractClient

任务执行类:com.alibaba.dubbo.remoting.transport.AbstractClient$1

DubboServerHandler-

dubbo服务端,任务处理线程,处理客户端请求

线程池启动类:AllChannelHandler的构造器中初始化线程池

线程池实际创建类:FixedThreadPool

任务处理类:com.alibaba.dubbo.remoting.transport.DecodeHandler

DubboClientHandler-

此线程主要用于dubbo客户端处理服务端的响应/连接相关事件,netty在接受到消息后,交给此线程池来处理。

线程启动类:com.alibaba.dubbo.remoting.transport.AbstractClient#wrapChannelHandler

线程池实际创建类:CachedThreadPool

任务处理类:com.alibaba.dubbo.remoting.transport.DecodeHandler

DubboRegistryFailedRetryTimer

当dubbo和注册中心的相关请求(注册、取消注册、订阅、取消订阅)处理失败时,会暂时放在缓存中,此定时任务会周期性的来处理这些失败的请求

线程启动类:com.alibaba.dubbo.registry.support.FailbackRegistry

处理任务:com.alibaba.dubbo.registry.support.FailbackRegistry#retry

DubboSaveRegistryCache

异步保存服务提供者地址

线程启动类:com.alibaba.dubbo.registry.support.AbstractRegistry

任务执行类:com.alibaba.dubbo.registry.support.AbstractRegistry.SaveProperties

DubboResponseTimeoutScanTimer

扫描等待返回的DefaultFuture对象,如果DefaultFuture超时,则抛出超时异常。

任务类:DefaultFuture.RemotingInvocationTimeoutScan

优化意见:目前的方式是间隔30ms就去扫描一次,建议重写ConcurrentHashMap缓存,加入队列等待机制。

telnet

com.alibaba.dubbo.remoting.transport.dispatcher.ChannelEventRunnable处理netty的事件,他会把telnet请求(decode后为文本)代理给com.alibaba.dubbo.remoting.telnet.support.TelnetHandlerAdapter来处理。

dubbo线程池任务不均衡问题分析

dubbo应用使用的线程池为com.alibaba.dubbo.common.threadpool.support.fixed.FixedThreadPool,如果当queue设置为0时,会使用SynchronousQueue,这个东东导致了任务线程执行”不均衡”(满足了大家的心理预期,其实这种不均衡方式减少了上下文切换,但是SynchronousQueue没有大小,不能起到任务缓冲的作用).

请在dubbo:protocol上加上queues大小(参考tomcat默认配置).

<dubbo:protocol name="dubbo" port="${dubbo.provider.port}" threads="200" queues="100"/>

测试:

修改前:

grep DubboServerHandler dubbo-demo.log |awk  -F '-'  '{print $6}' |awk  -F ']'  '{print $1}' |sort -n |uniq -c

  1 150
  1 168
  1 169
  1 170
  ...
  117 171
  5386 172
  714 173
      2646 174

修改后:

grep DubboServerHandler dubbo-demo.log |awk  -F '-'  '{print $6}' |awk  -F ']'  '{print $1}' |sort -n |uniq -c
507 1
498 2
 ...
493 199
489 200

线程池优化

jdk默认线程池实现策略如下:

  1. 当线程数少于corePoolSize,总是新建线程
  2. 当线程数=corePoolSize时,所有线程都忙,就会把线程放入队列;当队列满时,才会继续创建线程
  3. 当线程数量=maxPoolSize并且队列也满时,RejectedExecutionHandler生效。

易极付内部对线程池做了些优化:

  1. 我们希望能充分利用资源,仅当线程数量=maxPoolSize时,才放入队列。当线程空闲时,线程池收缩到corePoolSize。
  2. 当队列设置得比较小的时候,即便线程数量<maxPoolSize,在高并发下也会触发RejectedExecutionHandler
  3. 线程池没有传递MDC或者应用请求gid。

对于1,2,可以参考tomcat内部的线程池实现org.apache.tomcat.util.threads.ThreadPoolExecutor和org.apache.tomcat.util.threads.TaskQueue来改造。

对于3,wrap原任务即可,大致代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private static class MDCGidCallable<T> implements Callable<T> {
private final Callable<T> task;
private final String gid;

public MDCGidCallable(Callable<T> task, String gid) {
this.task = task;
this.gid = gid;
}

@Override
public T call() throws Exception {
try {
MDC.put(GID_KEY, gid);
return task.call();
} finally {
MDC.remove(GID_KEY);
}
}
}

缓存扩展

下面是我们自己实现的cache机制,个人感觉比dubbo原生的清爽,通过dubbo filter实现。

源代码见dubbo-cache

blog 参考@DubboCache

dubbo mock

mock最好是有mock server。由于懒,把mock server的client实现了(拦截请求,转换为http+json调用到mock server),后面就没时间做mock server了。前段时间有个项目紧急需要,做了个简单的mock。

原理如下:

对于使用者:

  1. 用户配置需要mock的dubbo服务。

    比如: dubbo.consumer.mockInterfaces[0]=com.acooly.core.test.dubbo.mock.XXFacade

  2. 增加mock实现。

1
2
3
4
5
6
7
@Service
public class XXFacadeMock implements XXFacade {
@Override
public SingleResult<String> echo(SingleOrder<String> msg) {
return SingleResult.from("mocked");
}
}

组件提供的能力

  1. 自定义实现BeanPostProcessor,扫描所有标注@Reference注解的属性,如果被配置了要mock掉,设置属性为mock实现。

最后

最后附带一个在易极付写的dubbo分享。

使用零拷贝提高数据传输效率

发表于 2014-08-16 | 分类于 java

本文讲了如何用zero copy技术来提高I/O性能.

静态文件服务器需要把磁盘上的数据发送给客户端.这里cpu消耗比较少,但是效率不高:内核从磁盘读数据,内核/用户空间交换数据,最后写到socket.数据在内核/用户空间转换时,需要拷贝数据,消耗cpu和内存带宽.对于java应用来说,还需要合理的使用缓冲区来减少gc的压力.

java提供了transferTo方法来使用zero copy技术.他可以让数据直接从一个channel到另外一个channel.避免上面说到的一些问题.

1.传统的解决办法

过程类似于下面的代码:

File.read(fileDesc, buf, len);
Socket.send(socket, buf, len);

1.1数据拷贝

这种方式会有四次内存拷贝

1.2 上下文切换

这种方式会有四次上下文切换.

1.3 过程说明

  1. read方法导致一次从user mode到kernel mode的上下文切换.系统调用sys_read从文件读取数据,通过DMA,把磁盘上的数据读到kernel address space buffer.
  2. 数据从kernel address space buffer拷贝到user buffer.read返回,导致从kernel mode到user mode的上下文切换.现在数据被读到了user address space buffer.
  3. send方法导致一次user mode到kernel mode的上下文切换.第三次拷贝把拷贝到kernel address space buffer.此buffer关联着destination socket
  4. 系统send调用返回时导致第四次上下文切换,DMA把kernel address space buffer中的数据发送到协议引擎导致第四次数据拷贝.

1.4 intermediate kernel buffer

使用intermediate kernel buffer主要为了提高性能,读的时候扮演缓存的角色,写的时候可以让应用程序实现异步(应用程序写到kernel buffer就返回).不幸的是,当我们处理的数据大于内核缓冲大小时,这样的拷贝是完全没有任何意义的.

2.零拷贝的方式

使用如下的代码来完成零拷贝

java方法:

public void transferTo(long position, long count, WritableByteChannel target);

系统调用:

#include <sys/socket.h>
ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);

2.1 数据拷贝

涉及到3次数据拷贝.

2.2 上下文切换

涉及到2次上下文切换

2.3 过程说明

  1. transferTo方法让DMA把磁盘文件读到kernel read buffer.然后内核把kernel read buffer中的数据拷贝到socket buffer.
  2. DMA把socket buffer中的数据拷贝到协议引擎.

3 更好的方式

通过上面使用这种方式,上下文切换从4次变为了2次.数据拷贝减少了一次.如果网卡支持gather operations,linux 2.4内核就开始提供更好的解决方案.

  1. transferTo方法让DMA engine把磁盘文件内容拷贝到内核缓冲区.
  2. 数据不需要拷贝到socket buffer.socket buffer里只需写入数据的地址和长度.DMA engine从内核缓冲区把数据读到协议引擎.

通过内核带来的特性,数据拷贝变为了2次(这两次拷贝都是DMA在做).cpu copy变为了0.

4 写在最后

文章地址http://www.ibm.com/developerworks/library/j-zerocopy/,里面有性能测试结果.后面附带有性能测试程序.不过这个测试程序不太恰当,应该都用nio的api来测试tansferTo和非tansferTo.

静态文件服务器一般都有静态资源缓存(apache可以配置,其他的服务器不了解).如果使用内存缓存,减少了读的过程.内存拷贝变为cpu copy application buffer -> socket buffer,DMA copy socket buffer ->NIC buffer,磁盘io大大降低了.

NIO不是很熟悉,不知道通过ByteBuffer.allocateDirect()+transferTo+gather operations能不能让copy变为一次.

如何跟踪数据库结构变动

发表于 2014-08-15 | 分类于 java

昨天和澎湃聊了这个事情,当时的想法是数据库中有表记录版本,项目代码中存储变更脚本.无意中看到数据库版本控制工具liquibase.最开始还是有点担心,怕这东西把数据库玩坏了.看了看官方文档,再粗略看了下主流程的源代码,发掘下我想要的功能,这个工具已经足够强大了,我们用好就行.

下面以cs为例,讲讲整个过程.

1. 在cs-dal中添加maven依赖.

    <build>
        <plugin>
            <groupId>org.liquibase</groupId>
            <artifactId>liquibase-maven-plugin</artifactId>
            <version>3.2.2</version>
            <configuration>
                <!--数据库变更主文件-->
                <changeLogFile>src/main/resources/db/changelog/db.master.xml</changeLogFile>
                <!--数据库相关配置文件-->
                <propertyFile>src/main/resources/db/config/dal-${spring.profiles.active}.properties</propertyFile>
            </configuration>
            <executions>
                <execution>
                    <phase>process-resources</phase>
                    <goals>
                        <goal>update</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

2. 编写数据库变更脚本

2.1 数据库变更主文件

db.master.xml:

<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog
    xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
     http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.2.xsd">
    <!--数据库变更文件.注意:保证顺序,脚本执行顺序和include的先后有关系.includeAll可以加载所有脚本,加载顺序和文件命名有关系,容易犯错误,建议不使用.-->
    <include relativeToChangelogFile="true" file="db.1.0.sql"/>
       <include relativeToChangelogFile="true" file="db.2.0.sql"/>
</databaseChangeLog>

2.2 变更脚本

db.1.0.sql:

--liquibase formatted sql

--changeset qiubo:1
create table test1 (
    id int primary key,
    name varchar(255)
);
--rollback drop table test1;

db.2.0.sql:

--liquibase formatted sql

--changeset qiubo:2
insert into test1 (id, name) values (1, 'name 1');
insert into test1 (id, name) values (2, 'name 2');

语法参考:http://www.liquibase.org/documentation/sql_format.html

2.3 数据库配置文件

dal-local.properties

driver=com.mysql.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/yjf_cs?useUnicode=true&characterEncoding=UTF8&zeroDateTimeBehavior=convertToNull
username=root
password=root
#数据库schema名
changelogSchemaName=yjf_cs

建议大家把原来的数据库配置文件中的key改为和这个一致,没必要搞多份配置.

3.路径结构

确保xxx-dal如下的路径结构,请大家统一:

|---pom.xml
|---src
|    |---main
|    |    |---java
|    |    |---resources
|    |    |        |---db
|    |    |        |    |---changelog
|    |    |        |    |        |---db.1.0.sql
|    |    |        |    |        |---db.2.0.sql
|    |    |        |    |        |---db.master.xml
|    |    |        |    |---config
|    |    |        |    |     |---dal-dev.properties
|    |    |        |    |     |---dal-local.properties
|    |    |        |    |     |---dal-net.properties
|    |    |        |    |     |---dal-online.properties
|    |    |        |    |     |---dal-pnet.properties
|    |    |        |    |     |---dal-sdev.properties
|    |    |        |    |     |---dal-snet.properties
|    |    |        |    |     |---dal-stest.properties
|    |    |        |    |     |---dal-test.properties

4.执行

在cs-dal目录执行:

mvn liquibase:update -Dspring.profiles.active=local

上面的脚本会以local环境执行,读取dal-local.properties中的数据库配置,执行数据库变更脚本.执行成功后,会在数据库中新建两个表.DATABASECHANGELOG会记录数据库变更信息,DATABASECHANGELOGLOCK用于防止多台服务器同时部署时的并发问题.

5.注意事项

  1. 先不要搞线上.
  2. 变更脚本命名:我现在做得简单db.1.0.sql,最好版本号为项目版本号,便于跟踪.
  3. 新项目建议采用此方案,跟踪数据库所有的开发变动.
  4. 老项目可以采用全量的方式使用.全量,先根据数据库的基础数据生成变更脚本generating_changelogs,然后同步版本(changeLogSync)到数据库中.这样做的好处是,以后可以从无到有的创建当前版本的数据库了.参考Adding Liquibase on an Existing project
  5. 老项目也可以采用增量的方式使用,增量的方式不会管以前的数据版本.如果采用这种方式,在新环境搭建数据库,你需要先用数据库工具还原到没有版本之前的状态,然后再执行变更脚本.参考Adding Liquibase on an Existing project
  6. 请不要修改(脚本内容/脚本路径)之前的数据库变更脚本,liquibase会对每个Changesets生成摘要,执行时会去对比,如果你修改了以前的Changesets,会报错(所有的变更在事务中执行,出错了会回滚,不用担心会影响到数据库).
  7. 官方文档很全,想深入的同学请阅读FAQ/BEST PRACTICES/Maven Liquibase Plugin.遇到问题之前先检查配置是否正确,有bug可以找我^_^.

webservice优化

发表于 2014-08-14 | 分类于 java

很早之前写的一篇文字,一直没有搬上blog,以后会慢慢把有些东西放到blog上来.

webservice的性能实在是敢恭维。曾经因为webservice吞吐量上不去,对webservice进行了一些性能方面的优化:

1.分析

1.1 FastInfoset

采用了FastInfoset(简称FI),效果很明显,极端条件下的大数据量传输,性能提高60%,他可以减少传输成本,序列化成本和xml解析成本。cxf基于http协商机制(检查请求header中Accept: application/fastinfoset)来启用FI。

1.2 Gzip

客户端和服务器端是否使用Gzip压缩,也是基于http协议协商的(检查请求header 中是否有Accept-encoding:gzip)。但是这里需要仔细权衡下。对于小数据量,启用gzip压缩支持是吃力不讨好的行为,数据量很小的时候,gzip压缩结果不明显,还浪费cpu。

1.3 unexpected element异常

见:http://bohr.me/cxf-unexpected-element/

1.4 处理过程分析

cxf 中通过一些列interceptor来完成数据解析处理操作,每个interceptor绑定到特定的阶段,下面是GZIP 和FI interceptor所处的阶段

类型 Direction Phase
Gzip IN Phase.RECEIVE
Out Phase.PREPARE_SEND
FI IN Phase.POST_STREAM
Out Phase.PRE_STREAM

数据进来时,先RECEIVE阶段适配InputStream对象为GZIPInputStream,然后在POST_STREAM阶段解析数据。完成gzip解压缩,FI解析数据过程。

数据出去时,在PREPARE_SEND阶段适配OutputStream对象为GZipThresholdOutputStream,在PRE_STREAM阶段再序列化为二进制数据传输出去。完成FI序列化数据,GZIP压缩数据过程。

测试发送20250byte数据,仅仅启用FI时,发送数据量为20181byte,再启用Gzip压缩后,发送数据量为258byte。

2.操作步骤

2.1添加依赖

cxf版本修改为2.7.0并加入FastInfoset

<dependency>
    <groupId>com.sun.xml.fastinfoset</groupId>
    <artifactId>FastInfoset</artifactId>
    <version>1.2.9</version>
</dependency>

2.2 修改cxf配置

2.2.1 删除引入的cxf配置

<import resource="classpath:META-INF/cxf/cxf.xml" />
<import resource="classpath:META-INF/cxf/cxf-extension-soap.xml" />
<import resource="classpath:META-INF/cxf/cxf-servlet.xml" />

我们项目中很多spring配置文件都加入了上面的东东,这个不是必须的,不删除这东东会导致配置不生效。

2.2.2 配置gzip和FI

Spring配置文件中引入cxf namespace
xmlns:cxf=http://cxf.apache.org/core和xsi:schemaLocation http://cxf.apache.org/core http://cxf.apache.org/schemas/core.xsd

然后加入配置

<cxf:bus>
    <cxf:features>
        <cxf:fastinfoset force="false" />
        <bean class="org.apache.cxf.transport.common.gzip.GZIPFeature">
            <property name="threshold">
                <value>2048</value>
            </property>
        </bean>
    </cxf:features>
</cxf:bus>

注意这些特性client和server端都要配置。

3.写在最后

启用gzip和FastInfoset,性能基本上也到达webservice的极致了.通过IgnoreUnexpectedElementValidationEventHandler再解决易用性问题,基本完美.

java开发工程师可以了解的常用命令

发表于 2014-08-06 | 分类于 java

先总结下常用的一些监控工具:

linux命令

  • w

    系统负载

  • lsof -p pid

    进程打开的文件

  • lsof -i:port

    端口的运行情况

  • free -m

    内存情况

  • vmstat

    进程、内存、内存分页、堵塞IO、traps及CPU活动的信息

  • iostat

    磁盘io情况

  • top -n 1

    cpu/负载/内存等使用情况.

  • iotop

    磁盘io

  • ps aux | sort -k6nr | head -n 10

    查看linux 实际内存占用最多的10个

  • ps aux | sort -k5nr | head -n 10

    查看linux 虚拟内存占用最多的10个

  • dstat -lamps

    查看系统整体状况

  • pstree -al pid|head -n 1

    查看进程启动命令

  • strace -T -p pid

    查看进程系统调用.开销很大,使用时要小心.

  • netstat

    netstat -an |grep port 查看端口连接情况

    netstat -alnp |grep pid 通过pid查看进程所有端口情况

  • ss -lntp |grep port

    通过端口查看进程

  • nmon

    强大的监控工具.也可以方便的出报表.我一般用来在压力测试时监控系统性能.

  • latencytop

    用于查看系统内部慢.以前做mysql性能优化,多亏有这东东.

  • cat /proc/pid/status |grep Threads

    查看进程内线程个数

java工具

  • jvisualvm

    jvm的运行情况/各种dump的分析都可以干,没有JRMC牛.oracle承诺会把JRockit的特性迁移到HotSpot上面来.现在jdk下已经有jmc了.

  • jps -lv

    查看所有java进程.

  • jinfo -sysprops pid

    查看java进程系统参数

  • jinfo -flag jvmflag pid

    查看jvm flag.比如查看xss,jinfo -flag ThreadStackSize pid

  • jstack pid

    查看线程栈信息

  • jmap -dump:live,format=b,file=xxx.hprof pid

    生成heap dump

  • jmap -histo pid

    查看java堆中对象统计信息

  • java -XX:+UnlockDiagnosticVMOptions -XX:+PrintFlagsFinal

    查看jvm flag

    The first column appears to reflect the data type of the option (intx, uintx, uint64_t, bool, double, ccstr, ccstrlist). 
    The second column is the name of the flag and the third column is the value, if any, that the flag is set to.
    The fourth column appears to indicate the type of flag and has values such as {product},{pd product}, {C1 product} for client or {C2 product} for server, {C1 pd product} for client or {C2 pd product} for server, {product rw}, {diagnostic} 
    (only if -XX:+UnlockDiagnosticVMOptions was specified), {experimental}, and {manageable}. See Eugene Kuleshov's The most complete list of -XX options for Java 6 JVM for a brief description of most of these categories as well as a listing of most of these options themselves.
    
  • tda

    线程栈分析器,这个是jvisualvm的插件.

  • mat

    基于eclipse的heap dump分析工具,这个工具是比jvisualvm在heap分析这块专业.不过jvisualvm能cover住大多数场景,基本上我都只用jvisualvm了.

  • jmap -heap pid

    检查heap情况

  • GCViewer

    GC日志分析

  • jstat -gcutil pid

    查看gc总体情况

    S0  — Heap上的 Survivor space 0 区已使用空间的百分比
    S1  — Heap上的 Survivor space 1 区已使用空间的百分比
    E   — Heap上的 Eden space 区已使用空间的百分比
    O   — Heap上的 Old space 区已使用空间的百分比
    P   — Perm space 区已使用空间的百分比
    YGC — 从应用程序启动到采样时发生 Young GC 的次数
    YGCT– 从应用程序启动到采样时 Young GC 所用的时间(单位秒)
    FGC — 从应用程序启动到采样时发生 Full GC 的次数
    FGCT– 从应用程序启动到采样时 Full GC 所用的时间(单位秒)
    GCT — 从应用程序启动到采样时用于垃圾回收的总时间(单位秒)
    
  • btrace

    神器,线上出问题了,想知道某个方法的调用情况,入参之类的,就靠btrace了.
    此工具大致原理如下:

    1. btrace-client attach 目标进程(com.sun.tools.attach.VirtualMachine#attach)
    2. 加载agent btrace-agent (com.sun.tools.attach.VirtualMachine#loadAgent)
    3. agent启动服务端,开启监听端口
    4. brace-client 把编译好的用户btrace代码发送到服务端,并等待服务端响应
    5. btrace-agent 通过asm修改运行时代码,织入用户btrace代码逻辑.监控到信息后,发给btrace-client
  • jmc

    生成记录

    #检查特性是否开启
    jcmd 23385 VM.check_commercial_features
    #开启商业特性
    jcmd 23385 VM.unlock_commercial_features
    #检查JFR状态
    jcmd 23385 JFR.check
    #执行180sJFR收集
    jcmd 23385 JFR.start name=recording filename=/root/recording.jfr duration=180s
    
  • vjtools

    https://github.com/vipshop/vjtools 主要工具vjtop非常有用,打印JVM概况及繁忙线程

2014年08月Reading Notes

发表于 2014-08-03 | 分类于 java

My WordPress Development Toolbox

http://tommcfarlin.com/wordpress-developer-toolbox/

本来是准备找git的客户端,看到这篇文章.不喜欢tower,大爱SoureTree.

browserstack也挺好的,适合做浏览器兼容性测试.

JVM plus Docker: Better together

http://www.javaworld.com/article/2456960/java-app-dev/jvm-plus-docker-better-together.html

docker刚好弥补jvm对资源管理(CPU/IO)的不足.

SharedHashMap vs Redis

http://vanillajava.blogspot.jp/2014/05/sharedhashmap-vs-redis.html

这位哥异常牛掰,java低延迟方面的专家.把性能做到极致啊!!!It was designed to be used in Java in a pause less, garbage free manner.狂赞!!!先留着,有时间了看看源代码.

高性能服务器架构

http://blog.csdn.net/zhoudaxia/article/details/14223755

这些经验可以参考下:

  • 数据拷贝

    特别是java,很多数据拷贝的代码埋得深,比如StringBuilder扩容,集合扩容等等.java中的数据拷贝除了带来cpu的压力,也会给gc带来压力.

    参考:使用零拷贝提高数据传输效率

  • 上下文切换

    线程越多,上下文切换就会越多.需要合理评估处理模型和系统情况.按照SEDA的方式把一个请求划分为多个阶段,但是多个阶段的独立线程池真的会增加上下文的切换,但这样可能会让系统利用率最高.

  • 内存分配

    采用类似于Buddy memory allocation的策略来减少开销.

  • 锁竞争

    一定要控制好锁的粒度.某些场景用map来存放锁对象,而不要使用一把大锁.

数据库版本控制工具liquibase

http://www.liquibase.org/quickstart.html

今天和勇哥讨论了如何来控制数据库版本.我们想的方案是,数据库里面有张versions表,里面记录当前的版本是多少.然后数据库更新文件存在项目中,并以目录来区分.这样就可以在项目启动时,来对比是否有新版本,是否需要升级.这样可以做到全自动化,需要规范现在的开发同学的行为,更重要的一点是,没有人来做这个事情.

liquibase正好在做这个事情,他也支持sql格式的版本,学习成本相当低.而且有内置的数据库版本和集群场景的检测,给力,先试试.

参考:如何跟踪数据库结构变动

可伸缩性最佳实践:来自eBay的经验

(http://www.infoq.com/cn/articles/ebay-scalability-best-practices)[http://www.infoq.com/cn/articles/ebay-scalability-best-practices]

手里有本2011年的架构师特刊,翻开看到的第一篇文章.虽然有点老了,但是经验还是值得我们借鉴.

  • 按功能分割

咱们现在的架构体系基本上遵循这条最佳实践.借助于dubbo/cxf实现功能服务化.应用层可以实现水平线性扩展.

  • 水平切分

应用层面的无状态很重要,会话之类的东西可以放在缓存服务器上,尽量让LB来实现水平切分.

数据库层面读写分离/分区/分库/分表.

  • 避免分布式事务

分布式第一定律,不要使用分布式.特别是两阶段提交,对系统的吞吐影响很大.ebay通过周密调整数据库操作的次序、异步恢复事件,以及数据核对(reconciliation)或者集中决算(settlement batches)来实现最终一致性.

  • 用异步策略解耦程序

组件之间的异步带来的好处是解耦/缓冲压力.组件内的异步能提供跟灵活的资源管理策略(当然带来了上下文切换的开销).我们还需要异步任务管理/确保机制.

  • 将过程转变为异步的流
  • 虚拟化所有层次

虚拟化所有层次我们还做的不够好.硬件资源层面的虚拟化可以通过docker来实现.目前docker最缺少的是资源的管理/发现/注册能力.通用资源服务层面的虚拟化也可以通过注册中心来实现.结合配置管理系统/框架组件化,可以做到对应用的透明.

  • 适当地使用缓存

缓存组件很多,分布式/集中式/进程内,不要选花了眼.同类型的我们只需要一种缓存组件,他必须要能支持丰富的数据结构,如果能提供持久话的能力最好(前提是在down掉的情况下要保证数据的一直.).

Why Choose Jetty?

https://webtide.com/why-choose-jetty/

一直有想法把jetty嵌入到我们的程序中来运行,jetty自身的体系结构优势便于我们去裁剪或者新增功能.

jetty的设计哲学很酷:

Don’t put your application into Jetty, put Jetty into your application.

http proxy

http://rehorn.github.io/livepool/

http://mitmproxy.org/

两个都是好东东.可以看下手机里面在干啥,吐槽下某些粗制滥造的app.也可以用来模拟http请求.

jvm flag

http://stas-blogspot.blogspot.jp/2011/07/most-complete-list-of-xx-options-for.html

最全的jvm flag.

hibernate应用报could not initialize proxy - no Session分析

发表于 2014-07-17 | 分类于 java

1.场景描述:

某项目使用hibernate,在切换到dubbo后,在构造结果对象时从延迟加载对象中获取数据时,报org.hibernate.LazyInitializationException: could not initialize proxy - no Session

构造结果对象的操作没有在事务环境下执行.

2.原因分析:

cxf不报错是因为在web.xml中配置了org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter,在请求到达web filter后,创建了EntityManager,请求结束后关闭EntityManager.在请求线程处理过程中,都可以拿到EntityManager,所以不会报错(至少可以从ThreadLocal中拿到).

切换为dubbo后,请求不会经过web filter,在事务模版代码中执行业务操作,可以正确的拿到EntityManager,不会报错.但是执行到构造结果对象时,就悲剧了.

3.解决办法:

1.修改模版方法,把构造结果对象部分的代码也放到事务中执行.

2.编写支持dubbo的OpenEntityManagerInViewFilter

可以通过TransactionSynchronizationManager做到如果EntityManagerFactory在线程变量中不存在则创建EntityManager,服务处理结束时,关闭EntityManager.

4.优劣分析

  1. 性能考虑

    open session in veiw模式还是不怎么优雅,事务执行链路太长了,会影响性能.而且对于我们提供的服务接口来说,构造结果对象已经是最后一步了,后面再也不需要延迟加载对象,不需要在filter里面来做此操作.

    web应用有在渲染模版时读取延迟加载对象的场景,这种场景使用还有意义.

  2. 功能角度

    如果遇到应用内的两个dubbo服务调用,dubbo会走injvm协议.此时请求不会经过io栈,但是会执行所有的dubbo filter.

    比如外部请求调用服务A,服务A调用内部服务B.

    外部请求调A时,filter创建EntityManager,然后调用服务B时,filter不创建EntityManager,但是在请求B结束时,filter关闭了EntityManager.在请求A中处理剩下的业务逻辑,如果遇到要操作数据库,就只有哭了.

    为什么web请求就不怕这种filter重入呢?web请求在forward时,你必须把request对象带进去,所以可以在request对象的attribute里面记录是否进过了这个filter.可以参考org.springframework.web.filter.OncePerRequestFilter.但是调用dubbo时,你只需要拿到服务代理对象就ok了,没有办法来知道整个请求链的情况.

5.最后结论

还是修改下我们自己的代码,把构造结果对象部分的代码也放到事务中执行.

使用vagrant

发表于 2014-07-07 | 分类于 java

vagrant挺火的,用于快速搭建开发环境.官方网站一行大字Development environments made easy.很惹眼.他可以实现可分发的环境搭建.

我们现在要快速搭建开发测试环境的需求很强烈,我们希望使用TA来快速搭建我们的开发测试环境.so,begin…

1.Centos上安装Virtaul Box[^1]

1.1 安装问题unable to find the sources of your current Linux kernel. Specify KERN_DIR=<directory> and run Make again.[^2]

Virtaul Box原因是uname -r和ls /usr/src/kernels/版本不一致,需要执行yum update,可以把国内的yum镜像用起来,会快点.完了重启下.

参考:

http://rationallyparanoid.com/articles/virtualbox-centos-6.2.html
https://www.centos.org/forums/viewtopic.php?t=5603

2.Centos上安装vagrant[^3]

2.1 static IP on a bridged interface

由于是公用的环境,会有很多个童鞋去访问,所以需要固定ip,并且上面的服务大家也可以自由访问,所以需要桥接网络.
但是官方网站上没有这样的配置,最后在github[^4]上发现了解决方案,测试了N遍,终于对头了:

config.vm.network "public_network", :bridge => "eth0", :ip => "192.168.46.51"

如果是mac上,就用:

config.vm.network "public_network", :bridge => "en0: Wi-Fi (AirPort)", :ip   => "192.168.1.222"

这样也需要注意下,如果要搭建多套环境,最好还是开一个新的网段,别和其他系统的ip冲突了.

如果只是你自己一个人玩,使用host-only吧,很简单.

2.2 ext4 file system inconsistency系统稳定性问题

不知道什么原因,过段时间就启动不了,最后通过ssh tunneling打开Virtaul Box图形化界面才发现了这个ext4 file system inconsistency问题.

错误日志如下:

There is a known Linux kernel bug which can lead to the corruption of the virtual disk image under these conditions.
Either enable the host I/O cache permanently in the VM settings or put the disk image and the snapshot folder onto a different file system.
The host I/O cache will now be enabled for this medium.

这是一个kernel的bug,centos forum上遇到这个问题[^5],其他的虚拟化vmware也有同样的问题.

如果enable host I/O cache,又会遇到各种问题[^6].比如data loss,I/O errors,I/O requests time out,Physical memory waste都是童鞋们不能接受的.

只有选择使用不同的文件系统,fdisk -l看下/home还比较大,有上T的空间.

#卸载home分区
umount /dev/mapper/VolGroup-lv_home
#格式化
mkfs.ext3 /dev/mapper/VolGroup-lv_home
#装载home分区
mount /dev/mapper/VolGroup-lv_home /home

最后需要修改 /etc/fstab,改变挂载分区为ext3,重启后sudo parted -l看生效没有.现在可以在/home目录启动vagrant.

2.3 guest分配多核反而更慢

如果开启多核(比如设置为20核),又遇到启动很慢的问题[^7].原因是:

VMs with multiple vCPUs require that all allocated cores be free before processing can begin. This means, if you have a 2 vCPU machine,2 physical cores must be available, and a 4 vCPU requires 4 physical cores

我开启20核,等了半个小时实在等不下去了.

查看cpu个数grep 'physical id' /proc/cpuinfo | sort -u,2个物理cpu.查看每个cpu核心数,grep 'core id' /proc/cpuinfo | sort -u | wc -l,每个cpu6个核心. 按照

One point to note is that if you assign many more vCPUs than you have physical CPUs the system may run slower because the host spends more time scheduling threads than actually running them.

,理论上12个应该是最优的,但是感觉还是不太靠谱,测试某app启动性能:

cpus 启动费时1 启动费时2
1 34664 34291
2 29040 29104
4 26205 26495
6 27207 28566
8 48087 44483

根据上面的测试,给vm配置4 cpus是最优的.卧槽,咱这服务两个物理cpu,每个cpu6 个核心,在加上Hyperthreading,processor都有24个了.如果这台服务器上有多个vm,咱这个测试最优的cpu数还会更少.

3.制作package

3.1 初始化vagrant环境

下载一个官方提供的base box[^8],用于初始化环境.这里我们选择CentOS 6.4 x86_64[^9].
在前面提到的ext3分区上进行:

#添加镜像到 Vagrant
vagrant box add yiji package.box
#初始化环境
vagrant init yiji

当前目录会有一个Vagrantfile文件,加上前面测试的东东:

config.vm.network "public_network", :bridge => "eth0", :ip => "192.168.46.51"
config.vm.provider :virtualbox do |vb|
    vb.gui = false
    #设置内存
    vb.customize ["modifyvm", :id, "--memory", "5120"]
    #设置虚拟机ip
    vb.customize ["modifyvm", :id, "--cpus", "4"]
    #设置ioapic,启用多个cpu时,必须设置.如果就一个cpu就不要设置,影响性能
    vb.customize ["modifyvm", :id, "--ioapic", "on"]
    #vb.customize ["modifyvm", :id, "--cpuexecutioncap", "50"]
end

启动虚拟机并ssh登陆:

#启动虚拟机
vagrant up
#ssh登陆
vagrant ssh

3.2 初始化VM环境

ssh登陆后,此时是用vagrant用户登陆的,这个时候神马事情都干不了,切换到root用户

#修改root密码
sudo passwd root
#切换到root账户
su - root

配置路由和dns服务器:

sudo route del default
sudo route add default gw 192.168.46.254
echo "nameserver 192.168.45.10" > /etc/resolv.conf
echo "nameserver 8.8.8.8" >> /etc/resolv.conf

添加定时常用定时任务:

#每12小时时间服务同步
0 */12 * * * rdate -s time.nist.gov
#每天清理日志
0 0 * * * /script/deletelog.sh

关闭防火墙:

chkconfig ip6tables off
chkconfig iptables off

为了集中控制jvm的启动参数,定义java应用依赖环境变量:

export APP_JAVA_OPTS="-Xms256m -Xmx512m"

所有的java应用启动脚本中把APP_JAVA_OPTS加在启动参数的最后,它的优先级最高,就很方便的控制所有的jvm进程内存大小了.

上面有些东西可以脚本化的,尽量就脚本化,比如在/etc/rc.d/rc.local增加启动脚本init.sh .其他脚本分为 init_network.sh init_env.sh init_common_app.sh init_app.sh

3.3 安装memcache

安装:

yum install memcached

配置文件:

/etc/sysconfig/memcached

命令:

service memcached start/stop/restart/status

设置开机启动:

chkconfig memcached on

修改/etc/init.d/memcached可以修改memcache启动参数

3.4 安装rabbitmq

安装:

yum install rabbitmq
 #安装webui
 rabbitmq-plugins enable rabbitmq_management
 #启用guest账户 访问web ui
 echo "[{rabbit, [{loopback_users, []}]}]." >/etc/rabbitmq/rabbitmq.config

常用命令:

service rabbitmq-server stop/start/etc.

web ui访问地址,账号密码guest/guest:

http://192.168.46.51:15672/

设置开机启动:

chkconfig rabbitmq-server on

3.5 安装其他软件

jdk/maven/memcache/zookeeper/rabbitmq/dubbo-monitor-simple/dubbo-admin

3.6 服务列表说明

服务 服务端口 web ui 端口
memcache 11211 无
zookeeper 2181 无
rabbitmq 5672 15672
dubbo-monitor 7070 7071
dubbo-admin 无 7073

3.7 制作分发包

vagrant package

上面命令会在当前目录生成一个package.box文件,此文件拷贝到其他服务器,就可以快速搭建系统了.

3.8 常用vagrant 命令

#初始化环境,此命令会生成Vagrantfile配置文件,如果当前目录有Vagrantfile,不要执行此命令,直接up吧
vagrant init

#启动虚拟机  
vagrant up

#关闭虚拟机  
vagrant halt

# 重新启动虚拟机,如果Vagrantfile被修改后,执行此命令才能生效.
#但是修改cpu相关参数,此命令也不能重新加载配置,这个时候把虚拟机先停下来,
#通过ssh tunneling在gui界面里调整
vagrant reload 

#SSH至虚拟机
vagrant ssh

#查看虚拟机运行状态
vagrant status

# 销毁当前虚拟机
vagrant destroy  

#add box
vagrant box add boxname xxx.box

#remove box

vagrant box remove boxname

#list box
vagrant box list

4.写在最后

virtual box的性能让人担忧,如果部署应用太多需要仔细权衡下,如果只是搭建单机环境,使用vagrant还是很ok的.

[^1]: Virtual Box下载地址
[^2]: unable to find the sources of your current Linux kernel
[^3]: vagrant下载地址
[^4]: Static ip addresses on public networks
[^5]: ext4 file system inconsistency
[^6]: Host I/O caching
[^7]: Adding CPUs to Virtualbox guests makes guests boot SLOWER
[^8]: vagrant base box
[^9]: vagrant base box CentOS 6.4 x86_64

1…345…7
qiubo

qiubo

Keep running!

66 日志
2 分类
76 标签
Links
  • qiuduo
  • daidai
  • bingwei
© 2013 — 2019 qiubo
由 Hexo 强力驱动
|
主题 — NexT.Gemini v5.1.4