Intellij IDEA中搜索yaml中的key

因yaml文件相比于properties文件来结构更加清晰,所以现在无论是公司项目中还是开源的项目中,yaml文件越来越常见。
曾经的特别讨厌使用yaml文件主要是因为搜索yaml文件中的key的时候太麻烦:

比如有下面的yaml文件:

1
2
3
wsearch:
zk:
address: xxxxxxxx

在Idea中使用Command+Shitf+F搜索wsearch.zk.address的时候是搜索不到的

就因为这个原因,导致我特别的厌烦yaml的配置。

今天才发现原来在IDEA可以使用如下的方式搜索到。那就是使用Idea的Search everywhere功能。按2下shift,然后在搜索就好了:

发现在Stack Overflow也有人问这个问题How to find specific property key in a yaml file using intellij idea?

正向代理和反向代理

经常会听到正向代理和反向代理的说法,那么具体怎么区分呢?

正向代理

正向代理,是在用户端的。

比如需要访问某些国外网站,我们可能需要购买vpn。并且vpn是在我们的用户浏览器端设置的(并不是在远端的服务器设置)。浏览器先访问vpn地址,vpn地址转发请求,并最后将请求结果原路返回来。

正向代理示意图

反向代理

有正向代理,就有反向代理,反向代理是作用在服务器端的,是一个虚拟ip(VIP)。对于用户的一个请求,会转发到多个后端处理器中的一台来处理该具体请求。

反向代理

区别

  • 正向代理:客户端 <一> 代理 一>服务端
  • 反向代理:客户端 一>代理 <一> 服务端

正向代理和反向代理的区别在于 代理的对象不一样,正向代理的代理对象是客户端,反向代理的代理对象是服务端

参考资料

数据库MHA架构介绍

MHA(Master High Availability)目前在MySQL高可用方面是一个相对成熟的解决方案,它由日本人youshimaton开发,是一套优秀的作为MySQL高可用性环境下故障切换和主从提升的高可用软件。在MySQL故障切换过程中,MHA能做到0~30秒之内自动完成数据库的故障切换操作,并且在进行故障切换的过程中,MHA能最大程度上保证数据库的一致性。

适用场景

目前MHA主要支持一主多从的架构,要搭建MHA,要求一个复制集群必须最少有3台数据库服务器,一主二从,即一台充当Master,一台充当备用Master,另一台充当从库。出于成本考虑,淘宝在此基础上进行了改造,目前淘宝开发的TMHA已经支持一主一从。

MAH组件

MHA由两部分组成:

  • MHA Manager(管理节点)
  • MHA Node(数据节点)。

MHA Manager

MHA Manager可以单独部署在一台独立的机器上管理多个master-slave集群,也可以部署在一台slave节点上。主要用来运行一些工具,比如masterha_manager工具实现自动监控MySQL Master和实现master故障切换,其它工具实现手动实现master故障切换、在线mater转移、连接检查等等。一个Manager可以管理多 个master-slave集群

MHA Manager会定时探测集群中的master节点,当master出现故障时,它可以自动将最新的slave提升为新的master,然后将所有其他的slave重新指向新的master。整个故障转移过程对应用程序完全透明。

MHA Node

MHA Node运行在每台MySQL服务器上,类似于Agent。主要作用有:

  • 保存二进制日志
    如果能够访问故障master,会拷贝master的二进制日志
  • 应用差异中继日志
    从拥有最新数据的slave上生成差异中继日志,然后应用差异日志。
  • 清除中继日志
    在不停止SQL线程的情况下删除中继日志

工作原理简述

因为MHA Manager会不断的去探测master节点,因此当master出现故障时,通过对比slave之间I/O线程读取masterbinlog的位置,选取最接近的slave做为latestslave。 其它slave通过与latest slave对比生成差异中继日志。在latest slave上应用从master保存的binlog,同时将latest slave提升为master。最后在其它slave上应用相应的差异中继日志并开始从新的master开始复制。

在MHA实现Master故障切换过程中,MHA Node会试图访问故障的master(通过SSH),如果可以访问(不是硬件故障,比如InnoDB数据文件损坏等),会保存二进制文件,以最大程度保 证数据不丢失。MHA和半同步复制一起使用会大大降低数据丢失的危险。流程如下:

  • 从宕机崩溃的master保存二进制日志事件(binlog events)。
  • 识别含有最新更新的slave。
  • 应用差异的中继日志(relay log)到其它slave。
  • 应用从master保存的二进制日志事件(binlog events)。
  • 提升一个slave为新master并记录binlog file和position。
  • 使其它的slave连接新的master进行复制。
  • 完成切换manager主进程OFFLINE

pt-fingerprint 安装和基本使用

1
2
3
4
5
6
7
8
9
sudo wget percona.com/get/percona-toolkit.tar.gz

## 安装相关依赖
sudo yum install perl -y
sudo yum install perl-DBI -y
sudo yum install perl-DBD-MySQL -y
sudo yum install perl-Time-HiRes -y
sudo yum install perl-IO-Socket-SSL -y
sudo yum install perl-Digest-MD5.x86_64 -y

初步使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[$ /home/w/percona/percona-toolkit-3.0.13/bin]$ ./pt-fingerprint --query "select a, b, c from users where id = 500"
select a, b, c from users where id = ?
[$ /home/w/percona/percona-toolkit-3.0.13/bin]$ ./pt-fingerprint --query "update test set a =1 , b=2 where id = 3"
update test set a =? , b=? where id = ?
[$ /home/w/percona/percona-toolkit-3.0.13/bin]$ ./pt-fingerprint --query "update test set a =1 , b=2 where id = 5"
update test set a =? , b=? where id = ?
[$ /home/w/percona/percona-toolkit-3.0.13/bin]$
[$ /home/w/percona/percona-toolkit-3.0.13/bin]$ ./pt-fingerprint --query "insert into a(id) values (1)"
insert into a(id) values(?+)
[$ /home/w/percona/percona-toolkit-3.0.13/bin]$ ./pt-fingerprint --query "insert into a(id) values (2)"
insert into a(id) values(?+)
[$ /home/w/percona/percona-toolkit-3.0.13/bin]$ ./pt-fingerprint --query "delete from test where id = 2"
delete from test where id = ?
[$ /home/w/percona/percona-toolkit-3.0.13/bin]$ ./pt-fingerprint --query "delete from test where id = 3"
delete from test where id = ?
[$ /home/w/percona/percona-toolkit-3.0.13/bin]$

不过发现pt-fingerprint执行速度有点慢,我自己写了一个java程序,程序中调用命令行来执行pt-fingerprint命令,执行20批次,没个批次执行100次命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Test
public void execute() {
ShellCommandExecutor shellCommandExecutor = new ShellCommandExecutor();

for (int j = 0; j < 20; j++) {
long start = System.currentTimeMillis();
for (int i = 0; i < 100; i++) {
ShellCommandExecInputer commandExecInputer = new ShellCommandExecInputer();
commandExecInputer.setCommand("./pt-fingerprint --query \"select a, b, c from users where id = 500\"");
commandExecInputer.setCommandExecDir("/Users/rollenholt/Downloads/percona/percona-toolkit-3.0.13/bin");
ShellCommandExecResult execResult = shellCommandExecutor.execute(commandExecInputer);
Assert.assertTrue(execResult.isOk());
}
long end = System.currentTimeMillis();
System.out.println((end - start) );
}
}

输出如下(单位毫秒,每执行100次pt-fingerprint命令耗时)
5520
5477
4898
4714
4708
4742
4729
4710
4714
5127
4801
4733
5472
5429
4870
5077
4935
5226
5461
5008

为了排查程序写的搓的影响,在机器上尝试执行了一下,发现耗时也比较长

1
2
3
4
5
6
[$ /home/w/percona/percona-toolkit-3.0.13/bin]$ time ./pt-fingerprint --query "delete from test where id = 3"
delete from test where id = ?

real 0m0.042s
user 0m0.038s
sys 0m0.004s

在我们的业务常见下基本不可用,太慢了。

gc Roots对象有哪些

JVM的垃圾自动回收是我们经常说的一个话题,这里的垃圾的含义是:

内存中已经不再被使用到的对象就是垃圾

要进行垃圾回收,如何判断一个对象是否可以被回收?

一般有两种办法:

  • 引用计数法
    • 实现简单,但是没法解决对象之间的循环引用问题
  • 枚举根节点做可达性分析
    • 通过一系列名为“GC Roots”的对象作为起始点,从“GC Roots”对象开始向下搜索,如果一个对象到“GC Roots”没有任何引用链相连,说明此对象可以被回收

常见的常见的GC Root有如下:

  • 通过System Class Loader或者Boot Class Loader加载的class对象,通过自定义类加载器加载的class不一定是GC Root
  • 处于激活状态的线程
  • 栈中的对象
  • 本地方法栈中 JNI (Native方法)的对象
  • JNI中的全局对象
  • 正在被用于同步的各种锁对象
  • JVM自身持有的对象,比如系统类加载器等。

Synchronized的一些东西

synchronized是Java中解决并发问题的一种最常用的方法,从语法上讲synchronized总共有三种用法:

  • 修饰普通方法
  • 修饰静态方法
  • 修饰代码块

synchronized 原理

为了查看synchronized的原理,我们首先反编译一下下面的代码, 这是一个synchronized修饰代码块的demo

1
2
3
4
5
6
7
public class SynchronizedDemo {
public void method() {
synchronized (this) {
System.out.println("Method 1 start");
}
}
}

http://7niucdn.wenchao.ren/20190902124248.png

从上面截图可以看到,synchronized的实现依赖2个指令:

  • monitorenter
  • monitorexit

但是从上面的截图可以看到有一个monitorenter和2个monitorexit,这里之所以有2个monitorexit是因为synchronized的锁释放有2种情况:

  • 方法正常执行完毕synchronized的范围,也就是正常情况下的锁释放
  • synchronized圈起来的范围内的代码执行抛出异常,导致锁释放

monitorenter

关于这个指令,jvm中的描述为:

Each object is associated with a monitor. A monitor is locked if and only if it has an owner. The thread that executes monitorenter attempts to gain ownership of the monitor associated with objectref, as follows:

• If the entry count of the monitor associated with objectref is zero, the thread enters the monitor and sets its entry count to one. The thread is then the owner of the monitor.
• If the thread already owns the monitor associated with objectref, it reenters the monitor, incrementing its entry count.
• If another thread already owns the monitor associated with objectref, the thread blocks until the monitor’s entry count is zero, then tries again to gain ownership.

翻译一下大概为:

每个对象有一个监视器锁(monitor)。当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的所有权,过程如下:

  • 如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者。
  • 如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1.
  • 如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权。

monitorexit

The thread that executes monitorexit must be the owner of the monitor associated with the instance referenced by objectref.
The thread decrements the entry count of the monitor associated with objectref. If as a result the value of the entry count is zero, the thread exits the monitor and is no longer its owner. Other threads that are blocking to enter the monitor are allowed to attempt to do so.

翻译一下为:

执行monitorexit的线程必须是objectref所对应的monitor的所有者。
指令执行时,monitor的进入数减1,如果减1后进入数为0,那线程退出monitor,不再是这个monitor的所有者。其他被这个monitor阻塞的线程可以尝试去获取这个 monitor 的所有权。

通过这两段描述,我们应该能很清楚的看出Synchronized的实现原理,Synchronized的语义底层是通过一个monitor的对象来完成,其实wait/notify等方法也依赖于monitor对象,这就是为什么只有在同步的块或者方法中才能调用wait/notify等方法,否则会抛出java.lang.IllegalMonitorStateException的异常的原因。

上面的demo是使用synchronized修饰代码块的demo,下面我们看一个使用synchronized修饰方法的demo:

1
2
3
4
5
public class SynchronizedMethod {
public synchronized void method() {
System.out.println("Hello World!");
}
}

我们继续对这个类进行反编译:

http://7niucdn.wenchao.ren/20190902125437.png

从反编译的结果来看,方法的同步并没有通过指令monitorentermonitorexit来完成(虽然理论上其实也可以通过这两条指令来实现),不过相对于普通方法,其常量池中多了ACC_SYNCHRONIZED标示符。JVM就是根据该标示符来实现方法的同步的:

  • 当方法调用时,调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程将先获取monitor,获取成功之后才能执行方法体,方法执行完后再释放monitor。
  • 在方法执行期间,其他任何线程都无法再获得同一个monitor对象。 其实本质上没有区别,只是方法的同步是一种隐式的方式来实现,无需通过字节码来完成。

观城模型

Synchronized 锁升级

这四种锁是指锁的状态,专门针对synchronized的。在介绍这四种锁状态之前还需要介绍一些额外的知识。

首先为什么Synchronized能实现线程同步?在回答这个问题之前我们需要了解两个重要的概念:Java对象头Monitor

Java对象头

synchronized是悲观锁,在操作同步资源之前需要给同步资源先加锁,这把锁就是存在Java对象头里的,而Java对象头又是什么呢?
我们以Hotspot虚拟机为例,Hotspot的对象头主要包括两部分数据:Mark Word(标记字段)、Klass Pointer(类型指针)。如下面的一个示例:

java object示意图

我们以64位虚拟机来说,object header的结构为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|------------------------------------------------------------------------------------------------------------|--------------------|
| Object Header (128 bits) | State |
|------------------------------------------------------------------------------|-----------------------------|--------------------|
| Mark Word (64 bits) | Klass Word (64 bits) | |
|------------------------------------------------------------------------------|-----------------------------|--------------------|
| unused:25 | identity_hashcode:31 | unused:1 | age:4 | biased_lock:1 | lock:2 | OOP to metadata object | Normal |
|------------------------------------------------------------------------------|-----------------------------|--------------------|
| thread:54 | epoch:2 | unused:1 | age:4 | biased_lock:1 | lock:2 | OOP to metadata object | Biased |
|------------------------------------------------------------------------------|-----------------------------|--------------------|
| ptr_to_lock_record:62 | lock:2 | OOP to metadata object | Lightweight Locked |
|------------------------------------------------------------------------------|-----------------------------|--------------------|
| ptr_to_heavyweight_monitor:62 | lock:2 | OOP to metadata object | Heavyweight Locked |
|------------------------------------------------------------------------------|-----------------------------|--------------------|
| | lock:2 | OOP to metadata object | Marked for GC |
|------------------------------------------------------------------------------|-----------------------------|--------------------|

Mark Word

Mark Word默认存储:

  • 对象的HashCode
  • 分代年龄
  • 锁标志位信息

这些信息都是与对象自身定义无关的数据,所以Mark Word被设计成一个非固定的数据结构以便在极小的空间内存存储尽量多的数据。它会根据对象的状态复用自己的存储空间,也就是说在运行期间Mark Word里存储的数据会随着锁标志位的变化而变化。

Klass Point

Klass Point:对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。

Monitor

Monitor可以理解为一个同步工具或一种同步机制,通常被描述为一个对象。每一个Java对象就有一把看不见的锁,称为内部锁或者Monitor锁。
Monitor是线程私有的数据结构,每一个线程都有一个可用monitor record列表,同时还有一个全局的可用列表。每一个被锁住的对象都会和一个monitor关联,同时monitor中有一个Owner字段存放拥有该锁的线程的唯一标识,表示该锁被这个线程占用。

synchronized通过Monitor来实现线程同步,Monitor是依赖于底层的操作系统的Mutex Lock(互斥锁)来实现的线程同步。

synchronized最初实现同步的方式是阻塞或唤醒一个Java线程需要操作系统切换CPU状态来完成,这种状态转换需要耗费处理器时间。如果同步代码块中的内容过于简单,状态转换消耗的时间有可能比用户代码执行的时间还要长,这就是JDK 6之前synchronized效率低的原因。这种依赖于操作系统Mutex Lock所实现的锁我们称之为“重量级锁”,JDK 6中为了减少获得锁和释放锁带来的性能消耗,引入了“偏向锁”和“轻量级锁”。

所以目前 锁一共有4种状态,级别从低到高依次是:无锁、偏向锁、轻量级锁和重量级锁。锁状态只能升级不能降级

下面是出四种锁状态对应的的Mark Word内容,然后再分别讲解四种锁状态的思路以及特点:

上下两幅图对照着看,我们就能够清楚的知道在不同的锁状态下,mack word区域存储的内容的不同了。

无锁

无锁没有对资源进行锁定,所有的线程都能访问并修改同一个资源,但同时只有一个线程能修改成功。

无锁的特点就是修改操作在循环内进行,线程会不断的尝试修改共享资源。如果没有冲突就修改成功并退出,否则就会继续循环尝试。如果有多个线程修改同一个值,必定会有一个线程能修改成功,而其他修改失败的线程会不断重试直到修改成功。CAS原理及应用即是无锁的实现。无锁无法全面代替有锁,但无锁在某些场合下的性能是非常高的。

偏向锁

偏向锁是指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁,降低获取锁的代价。

在大多数情况下,锁总是由同一线程多次获得,不存在多线程竞争,所以出现了偏向锁。其目标就是在只有一个线程执行同步代码块时能够提高性能。,也就是说偏向锁一般是一个线程的事情

当一个线程访问同步代码块并获取锁时,会在Mark Word里存储锁偏向的线程ID。在线程进入和退出同步块时不再通过CAS操作来加锁和解锁,而是检测Mark Word里是否存储着指向当前线程的偏向锁。引入偏向锁是为了在无多线程竞争的情况下尽量减少不必要的轻量级锁执行路径,因为轻量级锁的获取及释放依赖多次CAS原子指令,而偏向锁只需要在置换ThreadID的时候依赖一次CAS原子指令即可。

偏向锁只有遇到其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁,线程不会主动释放偏向锁。偏向锁的撤销,需要等待全局安全点(在这个时间点上没有字节码正在执行),它会首先暂停拥有偏向锁的线程,判断锁对象是否处于被锁定状态。撤销偏向锁后恢复到无锁(标志位为“01”)或轻量级锁(标志位为“00”)的状态。

偏向锁在JDK 6及以后的JVM里是默认启用的。可以通过JVM参数关闭偏向锁:-XX:-UseBiasedLocking=false,关闭之后程序默认会进入轻量级锁状态。

轻量级锁

轻量级锁是指当锁是偏向锁的时候,被另外的线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,从而提高性能。因此一般情况下轻量级锁大多数是2个线程的事情。

在代码进入同步块的时候,如果同步对象锁状态为无锁状态(锁标志位为“01”状态,是否为偏向锁为“0”),虚拟机首先将在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,用于存储锁对象目前的Mark Word的拷贝,然后拷贝对象头中的Mark Word复制到锁记录中。

拷贝成功后,虚拟机将使用CAS操作尝试将对象的Mark Word更新为指向Lock Record的指针,并将Lock Record里的owner指针指向对象的Mark Word。

  • 如果这个更新动作成功了,那么这个线程就拥有了该对象的锁,并且对象Mark Word的锁标志位设置为“00”,表示此对象处于轻量级锁定状态。
  • 如果轻量级锁的更新操作失败了,虚拟机首先会检查对象的Mark Word是否指向当前线程的栈帧,如果是就说明当前线程已经拥有了这个对象的锁,那就可以直接进入同步块继续执行,否则说明多个线程竞争锁。

若当前只有一个等待线程,则该线程通过自旋进行等待。但是当自旋超过一定的次数,或者一个线程在持有锁,一个在自旋,又有第三个来访时,轻量级锁升级为重量级锁。

重量级锁

升级为重量级锁时,锁标志的状态值变为“10”,此时Mark Word中存储的是指向重量级锁的指针,此时等待锁的线程都会进入阻塞状态。

整体的锁状态升级流程如下:

无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁

综上,偏向锁通过对比Mark Word解决加锁问题,避免执行CAS操作。而轻量级锁是通过用CAS操作和自旋来解决加锁问题,避免线程阻塞和唤醒而影响性能。重量级锁是将除了拥有锁的线程以外的线程都阻塞。

参考资料

ThreadPoolExecutor相关

java中的线程池相关的东西抛不开ThreadPoolExecutor,本文就简单的说说这个ThreadPoolExecutor

先看一个ThreadPoolExecutor的demo,然后我们说说它的相关参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class Test {
private static ThreadPoolExecutor threadPoolExecutor;

public static void main(String[] args) {
threadPoolExecutor = new ThreadPoolExecutor(
4, 8, 0, TimeUnit.MICROSECONDS,
new LinkedBlockingQueue<>(100), new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
}
});
System.out.println(threadPoolExecutor.getCorePoolSize()); //4
System.out.println(threadPoolExecutor.getMaximumPoolSize()); //8
System.out.println(threadPoolExecutor.getPoolSize());//0
boolean b = threadPoolExecutor.prestartCoreThread();
System.out.println(threadPoolExecutor.getCorePoolSize());//4
System.out.println(threadPoolExecutor.getMaximumPoolSize());//8
System.out.println(threadPoolExecutor.getPoolSize());//1
int i = threadPoolExecutor.prestartAllCoreThreads();
System.out.println(threadPoolExecutor.getCorePoolSize());//4
System.out.println(threadPoolExecutor.getMaximumPoolSize());//8
System.out.println(threadPoolExecutor.getPoolSize());//4
}
}

参数介绍

ThreadPoolExecutor的几个参数是必须要清楚的:

  • corePoolSize
    • 线程池中的核心线程数
  • maximumPoolSize
    • 线程池最大线程数,它表示在线程池中最多能创建多少个线程
  • keepAliveTime
    • 线程池中非核心线程闲置超时时长(准确来说应该是没有任务执行时的回收时间)
    • 一个非核心线程,如果不干活(闲置状态)的时长超过这个参数所设定的时长,就会被销毁掉
    • 如果设置allowCoreThreadTimeOut(boolean value),则会作用于核心线程, 也就是说当设置allowCoreThreadTimeOut(true)时,线程池中corePoolSize范围内的线程空闲时间达到keepAliveTime也将回收
  • TimeUnit
    • 时间单位。可选的单位有分钟(MINUTES),秒(SECONDS),毫秒(MILLISECONDS) 等
  • BlockingQueue
    • 任务的阻塞队列,缓存将要执行的Runnable任务,由各线程轮询该任务队列获取任务执行。可以选择以下几个阻塞队列
      • ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序。
      • LinkedBlockingQueue:一个基于链表结构的阻塞队列,此队列按FIFO (先进先出) 排序元素,静态工厂方法Executors.newFixedThreadPool()使用了这个队列。
      • SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,静态工厂方法Executors.newCachedThreadPool使用了这个队列。
      • PriorityBlockingQueue:一个具有优先级的无限阻塞队列。
  • ThreadFactory
    • 线程创建的工厂。可以进行一些属性设置,比如线程名,优先级等等,有默认实现。
  • RejectedExecutionHandler
    • 任务拒绝策略,当运行线程数已达到maximumPoolSize,并且队列也已经装满时会调用该参数拒绝任务,默认情况下是AbortPolicy,表示无法处理新任务时抛出异常。以下是JDK1.5提供的四种策略。
      • AbortPolicy:直接抛出异常, 这个是默认的拒绝策略。
      • CallerRunsPolicy:只用调用者所在线程来运行任务。
      • DiscardOldestPolicy:丢弃队列里最早的一个任务,并执行当前任务。
      • DiscardPolicy:不处理,丢弃掉。

运行原理

  • 初始时,线程池中的线程数为0,这一点从上面的demo输出可以看到
  • 当线程池中线程数小于corePoolSize时,新提交任务将创建一个新线程执行任务,即使此时线程池中存在空闲线程。
  • 当线程池中线程数达到corePoolSize时,新提交任务将被放入workQueue中,等待线程池中任务调度执行 。
  • workQueue已满,且maximumPoolSize > corePoolSize时,新提交任务会创建新线程执行任务。注意,新手容易犯的一个错是使用的是无界的workQueue,导致workQueue一直满不了,进而无法继续创建线程
  • workQueue已满,且提交任务数超过maximumPoolSize,任务由RejectedExecutionHandler处理。
  • 当线程池中线程数超过corePoolSize,且超过这部分的空闲时间达到keepAliveTime时,回收这些线程。
  • 当设置allowCoreThreadTimeOut(true)时,线程池中corePoolSize范围内的线程空闲时间达到keepAliveTime也将回收。

一般流程图

线程池的一般流程图

newFixedThreadPool 流程图

1
2
3
4
5
6
7
8
9
public static ExecutorService newFixedThreadPool(int nThreads){
return new ThreadPoolExecutor(
nThreads, // corePoolSize
nThreads, // maximumPoolSize == corePoolSize
0L, // 空闲时间限制是 0
TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>() // 无界阻塞队列
);
}

newFixedThreadPool 流程图

newCacheThreadPool 流程图

1
2
3
4
5
6
7
8
9
10
public static ExecutorService newCachedThreadPool(){
return new ThreadPoolExecutor(
0, // corePoolSoze == 0
Integer.MAX_VALUE, // maximumPoolSize 非常大
60L, // 空闲判定是60 秒
TimeUnit.SECONDS,
// 神奇的无存储空间阻塞队列,每个 put 必须要等待一个 take
new SynchronousQueue<Runnable>()
);
}

newCacheThreadPool 流程图

newSingleThreadPool

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static ExecutorService newSingleThreadExecutor() {
return
new FinalizableDelegatedExecutorService
(
new ThreadPoolExecutor
(
1,
1,
0L,
TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory
)
);
}

可以看到除了多了个FinalizableDelegatedExecutorService 代理,其初始化和newFiexdThreadPoolnThreads = 1的时候是一样的。
区别就在于:

  • newSingleThreadExecutor返回的ExcutorService在析构函数finalize()处会调用shutdown()
  • 如果我们没有对它调用shutdown(),那么可以确保它在被回收时调用shutdown()来终止线程。
    流程图略,请参考 newFiexdThreadPool,这里不再累赘。

js避免快速点击

通过JavaScript避免按钮快速被点击,有一般都是通过修改状态,然后延迟恢复状态来弄的,demo如下:

1
2
3
4
5
6
7
8
onRefresh: function () {
$("button[name='refresh']").attr('disabled', true);
$('#jar-debug-table').bootstrapTable('removeAll');
getAllClass();
setTimeout(function () {
$("button[name='refresh']").attr('disabled', false);
}, 3000);
}

关于elasticsearch的access log

今天才知道原来elasticsearch其实并没有传统意义上的access log。我这里说的传统意义
上的access log是指类似于tomcat,nginx等的access log。
我们公司在访问elasticsearch的时候,java语言相关的使用的公司封装的(其实是我封装的)
elasticsearch client,在这个封装的client里面,实现了传统意义上的类似于dubbo-consumer-access.log
一样的东西,记录了请求时间,traceId,请求的参数信息,请求是否成功的状态,响应时间,响应的内容等等
的信息。但是在elasticsearch的server端其实是并没有这些内容的。
Google了一下,发现elasticsearch其实并没有access log这个功能。但是elasticsearch缺提供了slow log的这个功能。
虽然说slow log和access log其实是两种不同的东西,但是在一定程度上slow log其实可以做到access log的一部分功能。

elasticsearch的slow log

这个日志的目的是捕获那些超过指定时间阈值的查询和索引请求。这个日志用来追踪由用户产生的很慢的请求很有用。
默认情况,慢日志是不开启的。要开启它,需要定义具体动作(query,fetch 还是 index),你期望的事件记录等级( WARN 、 DEBUG 等),以及时间阈值。

query

query阶段的配置,日志记录在_index_isearch_slowlog.log结尾的文件中,下面的日志级别可以根据实际
的需求来选择,如果想关闭某个配置的话,使用#号注释,或者设置值为-1就行。当然需要重启elasticsearch
以使得配置修改生效。

  • index.search.slowlog.threshold.query.warn: 10s #超过10秒的query产生1个warn日志
  • index.search.slowlog.threshold.query.info: 5s #超过5秒的query产生1个info日志
  • index.search.slowlog.threshold.query.debug: 2s #超过2秒的query产生1个debug日志
  • index.search.slowlog.threshold.query.trace: 500ms #超过500毫秒的query产生1个trace日志

    fetch

    fetch阶段的配置
  • index.search.slowlog.threshold.fetch.warn: 1s
  • index.search.slowlog.threshold.fetch.info: 800ms
  • index.search.slowlog.threshold.fetch.debug: 500ms
  • index.search.slowlog.threshold.fetch.trace: 200ms

    index

    索引阶段的配置
  • index.indexing.slowlog.threshold.index.warn: 10s ##索引数据超过10秒产生一个warn日志
  • index.indexing.slowlog.threshold.index.info: 5s ##索引数据超过5秒产生一个info日志
  • index.indexing.slowlog.threshold.index.debug: 2s ##索引数据超过2秒产生一个ddebug日志
  • index.indexing.slowlog.threshold.index.trace: 500ms ##索引数据超过500毫秒产生一个trace日志
    上面的例子说的是手动修改elasticsearch.yml文件,当然这些配置也可通过api来动态的修改,这是一个索引级别的设置,也就是说可以独立应用给单个索引:
    1
    2
    3
    4
    5
    6
    PUT /my_index/_settings
    {
    "index.search.slowlog.threshold.query.warn" : "10s",
    "index.search.slowlog.threshold.fetch.debug": "500ms",
    "index.indexing.slowlog.threshold.index.info": "5s"
    }

你也可以和上面一样在 elasticsearch.yml 文件里定义这些阈值。没有阈值设置的索引会自动继承在静态配置文件里配置的参数。
一旦阈值设置过了,你可以和其他日志器一样切换日志记录等级:

1
2
3
4
5
6
7
PUT /_cluster/settings
{
"transient" : {
"logger.index.search.slowlog" : "DEBUG",
"logger.index.indexing.slowlog" : "WARN"
}
}

我们为了能够在server段有类似的access log功能,我们修改了elasticsearch.yml中的:

  • index.search.slowlog.threshold.query.trace
  • index.indexing.slowlog.threshold.index.trace
    这2个参数的值为1ms。这样基本使得所有的query和index请求可以有一个类access log的slow log。
    你也看到了,这其实是一个很low的办法。也会影响es的性能,但是目前确实在server段没有什么比较好的办法。虽然我们封装了elasticsearch的client,但是也存在一些其他语言的应用或者部分应用直连elasticsearch。

dubbo xml解析

在之前的文章《如何在spring中自定义xml标签并解析》中我用实际的例子展示了,如何在spring中自定义xml标签,同时如何解析这个xml标签。

本篇文章主要来看看dubbo中对应的源代码

dubbo xml解析相关的类

如上图所示,这些类就是dubbo解析xml的相关实现的核心类,核心原理已经在之前的文章中详细描述了,如果大家在阅读dubbo代码,想看某个element的解析类的话,可以在DubboNamespaceHandler中来找:

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 DubboNamespaceHandler extends NamespaceHandlerSupport {

static {
Version.checkDuplicate(DubboNamespaceHandler.class);
}

@Override
public void init() {
registerBeanDefinitionParser("application", new DubboBeanDefinitionParser(ApplicationConfig.class, true));
registerBeanDefinitionParser("module", new DubboBeanDefinitionParser(ModuleConfig.class, true));
registerBeanDefinitionParser("registry", new DubboBeanDefinitionParser(RegistryConfig.class, true));
registerBeanDefinitionParser("config-center", new DubboBeanDefinitionParser(ConfigCenterBean.class, true));
registerBeanDefinitionParser("metadata-report", new DubboBeanDefinitionParser(MetadataReportConfig.class, true));
registerBeanDefinitionParser("monitor", new DubboBeanDefinitionParser(MonitorConfig.class, true));
registerBeanDefinitionParser("metrics", new DubboBeanDefinitionParser(MetricsConfig.class, true));
registerBeanDefinitionParser("provider", new DubboBeanDefinitionParser(ProviderConfig.class, true));
registerBeanDefinitionParser("consumer", new DubboBeanDefinitionParser(ConsumerConfig.class, true));
registerBeanDefinitionParser("protocol", new DubboBeanDefinitionParser(ProtocolConfig.class, true));
registerBeanDefinitionParser("service", new DubboBeanDefinitionParser(ServiceBean.class, true));
registerBeanDefinitionParser("reference", new DubboBeanDefinitionParser(ReferenceBean.class, false));
registerBeanDefinitionParser("annotation", new AnnotationBeanDefinitionParser());
}

}

基本上就是一个xml element对应一个BeanDefinitionParser

推荐阅读

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×