现场BES(应用服务器)连接Oracle总是很慢,特别是初始化连接时,经常出现创建连接超时。Oracle服务监听在ipv6地址上,端口1521。服务端为两台服务器组成的RAC集群。IP地址分别为2409:8018:5c00:201::208,
2409:8018:5c00:201::209。
从java的调用栈来看,只能看到hang在连接Oracle的调用栈上。目前没有其他好办法,只能抓个包试试看:
前三个包为连接x208的三次握手,接着Oracle drive发了一个Request,Connect包。我们先看1号包:
目的地址为x208,客户端将报文发送给Mac为00:0c:bd:08:46:15的设备, 看不出什么异常。继续看2号包:
信息来了,2号包却是一个MAC地址为e4:a8:b6:f6:95:12(Wireshark识别出为HuaweiTe) 的设备发出来的。因为远程协助,对方对环境等各方面不太清楚,不知道MAC地址为e4:a8:b6:f6:95:12的是个什么设备。但这里1号包与2号包走的不是一条线。而且Hop limit为61,那x208到客户端(也有可能根本没到x208)的应该经历了三(64-61)次路由转发。猜测客户端到208的拓扑应该是这样:
继续向下看,发现能够正常通信的是发送到209的报文(第一个连接不成功,RAC切换到了第二个服务器)。23,24,25号包是与x209进行三次握手。
发现23号也是发送到MAC为00:0c:bd:08:46:15的设备上,那00:0c:bd:08:46:15肯定不是208或209的MAC,这里猜测00:0c:bd:08:46:15是客户端的网关路由器。服务器208,209都在路由器后面。再看24号包:
24号包也是由设备e4:a8:b6:f6:95:12发出。以及正常通信的26,27号包:
都是:32:09:be:88:d8:67 -> 00:0c:bd:08:46:15,e4:a8:b6:f6:95:12->32:09:be:88:d8:67。说明服务器返回到客户端的报文最终都由e4:a8:b6:f6:95:12设备转发一次。猜测e4:a8:b6:f6:95:12
很可能是一个firewall设备,拓拆接近下图:
没有现场环境,只能猜到这些。已告知对方检测e4:a8:b6:f6:95:12设备对x208与x209的路由的情况.由于对网络拓扑接触的比较少,还望大家给予指正。
VMWare网络不通一个可能原因分析
今天配置VMWare网络,感觉一切都OK, 但就是Ping不通主机。发现问题的原因在于在虚拟网络编辑器
(Edit > Virtual Network Settings)里桥接网络绑定是VMnet8:
改成VMNet0,立马可以Ping通。难道桥接模式只能绑定到VMNet0? 难道VMNet(0-N)与网络
模式一一对应,这不现实呀,3种网格模式不可能对应0-N个虚拟设备。不扯了,直接上结论吧。
吧。
其实VMNet0,VMNet1,VMNet8默认分别对应桥接模式,主机模式和Nat模式。而且可以更改对应关系。
更改的位置就是上面提到虚拟网络编辑器(Virtual Network Settings)。但不通原因并不在这,而
在于虚拟机设置页面:
我们看到Bridged,Nat,Host-only,和custom四种模式,选择custom时,需要指定绑定的虚拟设备,而
选择其他模式时,不用选择虚拟设备,但是(重点来了)选择Bridged,Nat,Host-only这三种模式时,
却默认分别绑定到VMNet0,VMNet1,VMNet8三种设备上,也就是说这种选择VM workstation已经内定了。
你可以选择custom方式,使得网卡桥接到其他设备上,否则他就是桥接到VMNet0。如果你的虚拟网络编辑
器里只配了VMNet8为桥接模式(本人当时的环境即如此),则当然无法Ping通了。
所以真正决定的网卡的网络模型的,是虚拟网络编辑器里的对于虚拟设备的设置,而虚拟机设置页面对虚机网
卡的设置只起绑定作用, 不要被文字蒙蔽了。
线程模型
最近阅读<现代操作系统>时,讲到线程根据其存在的位置分为用户级线程和内核级线程。用户级线程存在于用户空间中,他的创建,销毁,调度都由用户空间的线程库来完成。操作系统内核无法感知上层线程的切换。操作系统像不存在多线程一样去作进程调度。内核级线程存在于内核空间中,由操作系统内核创建和管理。用户程序创建的内核线程通过system call创建内核级线程.
用户级线程可以在不支持内核线程的系统上实现多线程编程。现代操作系统绝大数都支持内核线程,所以实现方式集在用户级线程和内核级线程的混用方式。更多优缺点,请参考<现代操作系统>原文。
在操作系统概念到提到用户线线程与内核级线程存在三种对应关系:
1. 多对一
多个用户线程对应一个内核线程。这其实就是纯用户级多线程的实现方式。如果一个线程执行阻塞的系统调用,会导致当前进程被内核调度出去,其他线程也无法执行。任意时刻只能有一个线程访问内核,且多个线程无法调度在多个CPU上,因为他们在内核中的调度单元是进程。腾讯libco框架单个线程上实现多个协程的并发运行。https://blog.csdn.net/chdhust/article/details/53271478
2. 一对一
一个用户线程对应一个内核线程。线程的调度由内核线程的调度来完成。如果一个线程执行阻塞的系统调用,另一个线程会被切换执行,且多个线程可以运行在多个CPU上。比如Java在Linux、windows上的实现,一个java线程通过LWP与一个系统内核线程绑定。缺点,内核线程的数量限制限制了用户线程的数量。
3. 多对多
多个用户线程对应多个内核线程,一般用户线程数大于内核线程数.这种模式不但有一对一的并发性能,而且解决了用户线程数量的限制问题。这种模型又称为二级模型,HP-UX等支持。Solaris9之后采有一对一模型.
Java线程栈中的锁信息
分析运行时Java应用性能问题或死锁问题时,获取线程栈中锁信息是最基本的手段。JDK5.0之后加锁存在两种方法:1.Jvm内置的synchronized 2. Java并发包中相关同步类。本篇主要讨论线程栈中表现出来的锁的信息,对于我们分析Java性能或死锁提供哪些线索。
情况1: Object.wait()/ Object.wait(int timeout)
1 | public class InvokeWait { |
1 | "Test-Thread-1" #10 prio=5os_prio=0 tid=0x0000000058709800 nid=0x1d14 in Object.wait() [0x000000005930f000] |
这种情况下,线程处于WAITING状态,等待获取对象锁,该线程必须先锁定了该对象(即进入synchronized区域),才可以调用wait方法(画重点),并在调用wait方法后释放对象锁,并进入对象锁Wait Set队列等待被唤醒。WAITING状态的线程必须有对应的notify唤醒。这与后面遇到Blocked状态的线程不同,Blocked是不需要唤醒的。
备注:
1) 每一个Java对象与生俱来带有唯一一把对象锁(叫Intrinsic lock或Monitor)。
2) (a java.lang.Object)表示资源的类名,这里为一个Object对象。
3) 当调用带有时间参数的Wait方法,这时线程状态显示TIMED_WAITING。
4)Wait Set队列与下面的Entry Set队列的含义,可自行Google。
情况2: 资源争用
1 | public class Share { |
1 | "Test-thrad-2" #11 prio=5os_prio=0 tid=0x0000000058a09000 nid=0x1c94 waiting for monitor entry [0x00000000595af000] |
Test-thrad-2处于BLOCKED状态,说明线程Test-thrad-2进入synchronized临界区, 进入对象锁的Entry Set队列并等待对象锁0x00000000d79d8d38的释放。通过定位locked <0x00000000d79d8d38>,我们很容易发现哪个线程正在占用这把锁。在性能分析过程,如果发现大量的BLOCKED线程,我们就需要分析谁当前持有这把锁,而且长时间没有释放。 通过locked指示的代码行,我们很容易定位到加锁位置。然后进行降低锁粒度等手段,减少线程加锁的持续时间,从而优化性能。0x00000000d79d8d38>
情况3 死锁
1 | public class Main { |
1 | "Test-thrad-2" #11 prio=5 os_prio=0tid=0x00000000185c7800 nid=0x2b88 waiting for monitor entry [0x00000000196bf000] |
Test-thrad-2线程拿到了0x00000000d7ac5ea0锁,在等待x00000000d7ac5e90锁,而Test-thrad-1刚好相反,发生了死锁的条件, 而且从线程栈很容易定位这些锁是在哪一行代码获取的,且又在哪一行代码阻塞的。JVM线程栈信息的尾端提供了更详细的死锁环的信息:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21Found one Java-level deadlock:
=============================
"Test-thrad-2":
waiting to lock monitor 0x0000000017235028 (object 0x00000000d7ac5e90, a java.lang.Object),
which is held by "Test-thrad-1"
"Test-thrad-1":
waiting to lock monitor 0x00000000172350d8 (object 0x00000000d7ac5ea0, a java.lang.Object),
which is held by "Test-thrad-2"
Java stack information for the threadslisted above:
===================================================
"Test-thrad-2":
at org.guojje.DeadLock.run(DeadLock.java:21)
- waiting to lock <0x00000000d7ac5e90>(a java.lang.Object)
- locked <0x00000000d7ac5ea0>(a java.lang.Object)
at java.lang.Thread.run(Thread.java:745)
"Test-thrad-1":
at org.guojje.DeadLock.run(DeadLock.java:21)
- waiting to lock <0x00000000d7ac5ea0>(a java.lang.Object)
- locked <0x00000000d7ac5e90> (a java.lang.Object)
at java.lang.Thread.run(Thread.java:745)
Found 1 deadlock.
你会发现JDK内置的synchronized加锁方式,你会发现问题分析死锁问题非常容易。这个例子就是因为竞争线程对资源的加锁顺序不同,导致死锁。而保持相同的加锁顺序是解决这类死锁问题关健。
对于内置的synchronized的同步方式,线程栈提供了足够的信息让我们分析与定位问题。但synchronized不够灵活等,使Java并发包更受程序序员青睐,但同时也为分析这类问题代来复杂性。
情况1
同样,我们先看一个ReentrantLock(重入锁的)await方法(与Ojbect.wait区别开来)的调用情况。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 public class Main {
public static void main(String[] args){
Thread thread = new Thread(new RTLock());
thread.setName("Test-thrad-1");
thread.start();
}
}
class RTLock implements Runnable {
private ReentrantLock lock= newReentrantLock();
public Condition scarce= lock.newCondition();
public RTLock() {
}
@Override
public void run() {
lock.lock();
try {
scarce.await();
} catch (InterruptedException e) {
e.printStackTrace();
} finally{
lock.unlock();
}
}
}
1 | "Test-thrad-1" #10 prio=5os_prio=0 tid=0x00000000189d0800 nid=0x2da8 waiting on condition [0x000000001968e000] |
显示与synchronized有些不同。Synchronized显示为Wait on,这里- parking to wait for。
另外可以发现调用栈再也找不到locked字眼了,虽然你确认该线程一定locked过这把锁(否则无法调用await)。因为这个原因,为定位加锁位置带来难度。
情况2 资源争用
1 | public class Main { |
1 | "Test-thrad-2" #11 prio=5os_prio=0 tid=0x0000000018802000 nid=0x3368 waiting on condition [0x000000001965f000] |
如果是synchronized的方式,Test-thrad-2线程应该是Blocked状态,但这里为WAITING。这也是内置锁与JUL锁的不同。
注: 只要看Blocked状态的线程,代码一定是用synchronized作的同步。
在线程栈中多出了Locked ownable synchronizers条目。这是用jstack-l打印出来。(-l表示打印更详细的栈信息)。Java已经做了努力,弥补线程栈没有locked信息的缺陷。假如没有这个条目,无法确认Test-thrad-2等待的锁被哪一个线程持有。即便如此,还是无法定位加锁的代码位置,目前本人并没有找到更好的方法,只能翻代码了。
Ownable synchronizers这里的ownable的可以理解为,可被霸占的,即抢到就只属于你的。(有的邪恶哦?)
所以Locked ownable synchronizers只会列出排他锁,如ReentrantLock锁和ReentrantReadWriteLock的写锁,而对于ReentrantReadWriteLock读是不会显示的。因为他不算排他锁(ownable)。某些情况下,这一点也为定位问题加在难度。
注:- locked <0x00000000d79d8d38> (a java.io.BufferedInputStream)是BufferedInputStream.read上的锁不要搞混.0x00000000d79d8d38>
情况3 死锁
1 | public class Main { |
1 | "Test-thrad-2" #11 prio=5os_prio=0 tid=0x0000000018871000 nid=0x2588 waiting on condition [0x00000000198ef000] |
借助Locked ownable synchronizers,确认哪些线程位于死锁的环形竞争中,还是比较容易。先到这吧。。