Archive

Posts Tagged ‘lock’

J2EE/Oracle性能调优实录

February 18th, 2011 西坪 No comments

这两天,有一个新接手的项目性能表现较差,不能满足要求。这个项目在这之前已经写完很久了,但是一些关键部署点一直没有跟上进度。此系统是用Java编写的多线程程序的,主要是从Oracle中读取数据,然后根据业务进行统计运行,在将计算结果放回Oracle中。

这个系统的性能的直观指标,就是每天能处理多少数据量。这个指标很不理想,速度很慢,拖后与现场数据生成的速度。调优的标准,就是每日处理量。

第一次优化: 提高JDBC的fetchSize

首先,分析系统各个环节的时间消耗,弄清瓶颈在哪里。

在进行了一些综合分析后,得出主要时间耗费在数据库的查询上。而从v$session_wait中查询,发现有很多 SQL*Net message from client 事件的等待,时间很长,有数百秒之多。 显然是客户端慢了。但是因为应用端是有连接池的,是不是空闲连接导致的呢? 有一些怀疑。

接下来,在数据库上用 sqlplus 执行了相同的SQL,测量其时间其实是很快的,远远小于应用消耗在数据获取的时间。联系其上面提到的等待事件,事情变逐渐明朗: 网络传输慢了。

于是检查了代码,发现大批量的数据查询竟然没有提升fetchSize属性。在调整该属性为500以后,很快收到了正面反馈。处理速度提升了将近一倍。

回去在检查Oracle中的等待事件,发现已经降到200名以外了(是与其他应用共用数据库的)。

有关 sqlnet message from client 事件的分析,可以参考 Asktom 上的文章 AskTom上的另一篇文章,则描述了类似于连接池等待,或者是在sqlplus中打开会话不操作也会导致该事件。所以在见到这个事件时还是需要仔细甄别一下的。

结论: 适当调整 FetchSize是很有必要的。Oracle JDBC默认的fetchSize为10。FetchSize的提升,一方面是减少了客户端与服务器之间的来回交互次数;另一方面,也会有效减少数据库服务器逻辑读的数量(记得是《Troubleshooting Oracle Performance》中有讲到)。

修改完成后,除了观测到 sqlnet message from client 事件消失外,发现 sys cpu 也有小幅增加。

第二次优化:减小锁定(lock)范围

但是,接下来的观测,证明这个速度还是不合意,达不到要求。于是继续分析。

仍然是分析其主要的时间消耗在什么地方。这次发现时间主要是消耗在程序对数据的处理过程中。数据的处理大部分都是在Java进程中的运算,处理过程中还可能调用到Memcached。是不是网络速度比较慢呢?尝试测试了一下网速,千兆网络的吞吐率在应用并发时还能挤出三分之一左右用来做测试文件的传输,用ping发现其latency也不算高。

在此前的观测中,发现应用服务器上的 user cpu 比较好,在30%~70%之间,主要是被JVM进程消耗了,系统负载在5.0左右。觉得有些奇怪的,因为一般这样的应用瓶颈都出在IO上。于是做了一次代码复查,很快发现有一个在方法签名上的synchronize 关键字。这个类是一个服务bean, 在spring中管理的。这时问题变得清晰起来,所有的处理任务线程都要在这里排队。这样正好符合了此前观测到的cpu现象。

改掉这个关键字,缩小锁定范围,速度又上升了将近一倍。user cpu很快降下来,系统负载也下来了,稍稍大于1。

此时,速度虽能勉强应付,但还是不够快。此时更多的时间消耗在数据的写回上面。处理速度提升了,写回阶段的压力便也迅速提升,每单位数据的写回时间有20%左右的上浮。于是,需要第三次优化,在数据写回方面提升速度。

第三次优化

(Update Apr 6th):
没心思再仔细写过程了,简略如下:
1) Oracle是多个系统共享的,数据写入成为瓶颈。在内存问题解决后,在本地内存中缓存结果,最后再集中提交,减少更新次数。数度大增2.5倍左右。
2) 将从数据库中的查询和在内存中的处理划分为两个线程并发处理,速度提高50-70%。
3) 然后,Memcached集群成为瓶颈,内网延迟成为问题。但此时速度已经足够快了,因此暂时搁置下一步优化。如果要继续提速,则可能在本地建立一个Memcached实例,或者在程序中使用一个足够大的LRU缓存以减少与Memcached交互的次数。

至此,数据后台处理速度完全达到并超过预期。

Categories: $Performance Tags: , , , , ,

文件锁与进程互斥

January 8th, 2010 西坪 3 comments

有的情况下需要在进程之间实现互斥,这对于普通Java程序来说,因为是运行在虚拟机里的原因,手段远不如原生程序方便,但是也有一些简单的方法可以做到。第一个就是使用Socket监听某个端口,第二种方法是使用文件锁定。

使用Socket实现进程互斥

因为Java服务器套接字都是基于系统的socket接口实现的,而socket接口会保证一个套接字端点在整个系统中是唯一的,所以只需要在应用启动时监听一个端口,就可以让系统不同时启动。虽然方法比较土,但是也能用。

try {
	ServerSocket ss = new ServerSocket(3030);
}catch (Exception e) {
	logger.error("Cannot start", e.printStackTrace());
	System.exit();
}

使用文件锁在进程间同步

另一个实现Java进程间互斥的方法,就是文件锁。不过与上面使用套接字不同,使用文件锁还可以实现更高级的互斥/协作。完全可以用文件锁来实现不同Java进程之间的同步,基本思路与在线程之间使用读写锁同步相似。

JDK本身直接支持文件锁,在FileChannel下有几个与文件锁有关的方法,可以打开一个FileLock对象。当调用FileLock#release()方法,或者Channel关闭,或者JVM退出时都会释放该锁。文件锁是JVM全局的,与Java内部的锁/线程等无关,是进程之间的竞争关系。如果你不想发生意外你的程序挂起,那就应该用FileChannel#trylock,而不是直接使用FileChannel#lock(),这样在不能加锁时可以休眠一段时间再等等,或者超时退出。

文件锁像读写锁一样,可以是加共享锁,也可以加独占锁,还可以给锁定限制范围。这是操作系统本身提供的功能,JDK在操作系统层面上做了封装。在Linux下,其实就是调用fcntl来设置相关的状态。如下面的代码,会给文件加独占锁,如果再起新的进程,进程就会打开文件失败退出。

     char* lockfile;
      int lockfd;
      struct flock lock;
      time_t t;   
 
      if ((lockfd = open(lockfile, O_RDWR)) == -1) {
          fprintf(stdout, "open file %s fail: %s \n", lockfile, strerror(errno));
          exit(1);
      }
 
      t = time(NULL);
 
      lock.l_type = F_WRLCK;
      lock.l_start = 0;
      lock.l_len = 0;
      lock.l_whence = SEEK_SET;
      if (fcntl(lockfd, F_SETLKW, &lock) == -1) {
          fprintf(stdout, "lock file %s fail: %s \n", lockfile, strerror(errno));
      }
      fprintf(stdout, "lock file %s success(%d): %ld \n", lockfile, (int)getpid(), time(NULL)-t);
      sleep(30);
Categories: $Programming Tags: , , ,

细节背后:为什么线程协作之前必须先获得锁?

October 3rd, 2009 西坪 No comments

为什么Object.wait()/notify()/notifyAll() 之前必须获得锁? 这是JLS的规定。Wait-notify机制是围绕监控器锁进行的,获得锁是很自然的前提,自身没有拿到锁之前,怎么能够尝试去操作靠锁来调控的线程呢?不过今天偶尔有时间,就看下Sun Hotspot是怎么实现这一机制的。

当我们执行下面的代码时,线程会抛出异常java.lang.IllegalMonitorStateException: current thread not owner。

public class WaitNotifyCompilerCode {
	private String aString = "Hello World!";
 
	public static void main(String[] args) {
		System.out.println("Execute start ....");
		final WaitNotifyCompilerCode w = new WaitNotifyCompilerCode();
		w.wait1SecAndPrintString();
		System.out.println("Execute end ....");
	}
 
	public void wait1SecAndPrintString() {
		try {
			this.wait(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(aString);
	}
 
}

异常栈的信息如下:

Exception in thread "main" java.lang.IllegalMonitorStateException: current thread not owner
	at java.lang.Object.wait(Native Method)
	at com.feihoo.test.waitnotify.WaitNotifyCompilerCode.wait1SecAndPrintString(WaitNotifyCompilerCode.java:35)
	at com.feihoo.test.waitnotify.WaitNotifyCompilerCode.main(WaitNotifyCompilerCode.java:27)

深入查看 OpenJDK的源码,找到 Object.wait() 函数本地代码:

JVM_ENTRY(void, JVM_MonitorWait(JNIEnv* env, jobject handle, jlong ms))
  JVMWrapper("JVM_MonitorWait");
  Handle obj(THREAD, JNIHandles::resolve_non_null(handle));
  assert(obj->is_instance() || obj->is_array(), "JVM_MonitorWait must apply to an object");
  JavaThreadInObjectWaitState jtiows(thread, ms != 0);
  if (JvmtiExport::should_post_monitor_wait()) {
    JvmtiExport::post_monitor_wait((JavaThread *)THREAD, (oop)obj(), ms);
  }
  ObjectSynchronizer::wait(obj, ms, CHECK);
JVM_END

重点看倒数第二行,调用的函数如下面的代码:

// NOTE: must use heavy weight monitor to handle wait()
void ObjectSynchronizer::wait(Handle obj, jlong millis, TRAPS) {
  if (UseBiasedLocking) {
    BiasedLocking::revoke_and_rebias(obj, false, THREAD);
    assert(!obj->mark()->has_bias_pattern(), "biases should be revoked by now");
  }
  if (millis < 0) {
    TEVENT (wait - throw IAX) ;
    THROW_MSG(vmSymbols::java_lang_IllegalArgumentException(), "timeout value is negative");
  }
  ObjectMonitor* monitor = ObjectSynchronizer::inflate(THREAD, obj());
  DTRACE_MONITOR_WAIT_PROBE(monitor, obj(), THREAD, millis);
  monitor->wait(millis, true, THREAD);

而上面的代码中最后一行的函数里,在做实际操作之前调用了下面的宏:

// A macro is used below because there may already be a pending
// exception which should not abort the execution of the routines
// which use this (which is why we don't put this into check_slow and
// call it with a CHECK argument).
 
#define CHECK_OWNER()                                                             \
  do {                                                                            \
    if (THREAD != _owner) {                                                       \
      if (THREAD->is_lock_owned((address) _owner)) {                              \
        _owner = THREAD ;  /* Convert from basiclock addr to Thread addr */       \
        _recursions = 0;                                                          \
        OwnerIsThread = 1 ;                                                       \
      } else {                                                                    \
        TEVENT (Throw IMSX) ;                                                     \
        THROW(vmSymbols::java_lang_IllegalMonitorStateException());               \
      }                                                                           \
    }                                                                             \
  } while (false)
Categories: $Programming Tags: , ,