J~杰's Blog

人生就一条路,走一步有一步的景观

0%

简介

CountDownLatch 允许一个或一组线程等待其他线程完成后再恢复运行。线程可通过调用await方法进入等待状态,在其他线程调用countDown方法将计数器减为0后,处于等待状态的线程即可恢复运行。

CountDownLatch 的同步功能是基于 AQS 实现的,CountDownLatch 使用 AQS 中的 state 成员变量作为计数器。在 state 不为0的情况下,凡是调用 await 方法的线程将会被阻塞,并被放入 AQS 所维护的同步队列中进行等待。大致示意图如下:

每个阻塞的线程都会被封装成节点对象,节点之间通过 prev 和 next 指针形成同步队列。初始情况下,队列的头结点是一个虚拟节点。该节点仅是一个占位符,没什么特别的意义。每当有一个线程调用 countDown 方法,就将计数器 state–。当 state 被减至0时,队列中的节点就会按照 FIFO 顺序被唤醒,被阻塞的线程即可恢复运行。

CountDownLatch 本身的原理并不难理解,不过如果大家想深入理解 CountDownLatch 的实现细节,那么需要先去学习一下 AQS 的相关原理。CountDownLatch 是基于 AQS 实现的,所以理解 AQS 是学习 CountDownLatch 的前置条件,可以读这篇文章 AbstractQueuedSynchronizer源码分析

Demo例子

该例子前几天写在这篇文章 ReentrantLock源码分析

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
/**
* reentrantloct 测试
* @author: peijiepang
* @date 2018/11/7
* @Description:
*/
public class ReentrantLockTest extends Thread{

private final static Logger LOGGER = LoggerFactory.getLogger(ReentrantLockTest.class);

private ReentrantLock reentrantLock = new ReentrantLock();

private CountDownLatch countDownLatch = null;

public static int j = 0;

public ReentrantLockTest(String threadName,CountDownLatch countDownLatch) {
super(threadName);
this.countDownLatch = countDownLatch;
}

@Override
public void run() {
for(int i=0;i<1000;i++){
//可限时加锁
//reentrantLock.tryLock(1000,TimeUnit.MILLISECONDS);

//可响应线程中断请求
//reentrantLock.lockInterruptibly();

//可指定公平锁
//ReentrantLock fairLock = new ReentrantLock(true);

reentrantLock.lock();
try{
LOGGER.info("{}:{}",Thread.currentThread().getName(),i);
j++;
}finally {
reentrantLock.unlock();
}
}
countDownLatch.countDown();
}

public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(2);
ReentrantLockTest reentrantLockTest1 = new ReentrantLockTest("thread1",countDownLatch);
ReentrantLockTest reentrantLockTest2 = new ReentrantLockTest("thread2",countDownLatch);
reentrantLockTest1.start();
reentrantLockTest2.start();
countDownLatch.await();
LOGGER.info("---------j:{}",j);
}
}
Read more »

AbstractQueuedSynchronizer介绍

AQS是一个用来构建锁和同步器的框架,使用AQS能简单且高效地构造出应用广泛的大量的同步器,比如我们提到的ReentrantLock,Semaphore,其他的诸如ReentrantReadWriteLock,SynchronousQueue,FutureTask等等皆是基于AQS的。当然,我们自己也能利用AQS非常轻松容易地构造出符合我们自己需求的同步器。

AQS核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中。

CLH(Craig,Landin,and Hagersten)队列是一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在结点之间的关联关系)。AQS是将每条请求共享资源的线程封装成一个CLH锁队列的一个结点(Node)来实现锁的分配。

AQS原理图

AQS使用一个int成员变量来表示同步状态,通过内置的FIFO队列来完成获取资源线程的排队工作。AQS使用CAS对该同步状态进行原子操作实现对其值的修改。

1
private volatile int state;//共享变量,使用volatile修饰保证线程可见性

状态信息通过procted类型的getState,setState,compareAndSetState进行操作

1
2
3
4
5
6
7
8
9
10
11
12
//返回同步状态的当前值
protected final int getState() {
return state;
}
// 设置同步状态的值
protected final void setState(int newState) {
state = newState;
}
//原子地(CAS操作)将同步状态值设置为给定值update如果当前同步状态的值等于expect(期望值)
protected final boolean compareAndSetState(int expect, int update) {
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
Read more »

ReentrantLock 定义

Condition是JUC里面提供于控制线程释放锁, 然后进行等待其他获取锁的线程发送 signal 信号来进行唤醒的工具类.

主要特点

  • Condition内部主要是由一个装载线程节点 Node 的 Condition Queue 实现
  • 对 Condition 的方法(await, signal等) 的调用必需是在本线程获取了独占锁的前提下
  • 因为操作Condition的方法的前提是获取独占锁, 所以 Condition Queue 内部是一条不支持并发安全的单向 queue (这是相对于 AQS 里面的 Sync Queue)

    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
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    package com.fly.learn.reentrantlock;

    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;

    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.ReentrantLock;

    /**
    * @author: peijiepang
    * @date 2018/11/8
    * @Description:
    */
    public class ConditionTest extends Thread{

    private final static Logger LOGGER = LoggerFactory.getLogger(ConditionTest.class);

    private ReentrantLock lock = null;
    private Condition condition = null;

    public ConditionTest(String name,ReentrantLock lock,Condition condition) {
    super(name);
    this.lock = lock;
    this.condition = condition;
    }

    @Override
    public void run() {
    lock.lock();
    try{
    LOGGER.info("thread name:{} lock success.",Thread.currentThread().getName());
    if(Thread.currentThread().getName().equals("test1")){
    condition.await();//释放锁,然后等待唤醒
    LOGGER.info("thread name:{} 被唤醒,即将unlock.",Thread.currentThread().getName());
    }else if(Thread.currentThread().getName().equals("test2")) {
    condition.signal();//唤醒等待线程
    LOGGER.info("thread name:{} 唤醒队列中的线程,即将unlock.",Thread.currentThread().getName());
    }
    }catch (InterruptedException ex){
    ex.printStackTrace();
    }finally {
    lock.unlock();
    LOGGER.info("thread name:{} unlock success.",Thread.currentThread().getName());
    }
    }

    public static void main(String[] args) {
    ReentrantLock reentrantLock = new ReentrantLock();
    Condition condition = reentrantLock.newCondition();
    ConditionTest test1 = new ConditionTest("test1",reentrantLock,condition);
    ConditionTest test2 = new ConditionTest("test2",reentrantLock,condition);
    test1.start();
    test2.start();
    }
    }
    从如下执行结果来看,线程1先await释放锁,然后线程2获取到锁,接着线程2唤醒等待锁,然后线程2释放锁,最后线程1解锁等待中的锁。
    1
    2
    3
    4
    5
    6
    2018-11-08 23:17:50.065 [test1] INFO  c.f.l.reentrantlock.ConditionTest - thread name:test1 lock success.
    2018-11-08 23:17:50.072 [test2] INFO c.f.l.reentrantlock.ConditionTest - thread name:test2 lock success.
    2018-11-08 23:17:50.072 [test2] INFO c.f.l.reentrantlock.ConditionTest - thread name:test2 唤醒队列中的线程,即将unlock.
    2018-11-08 23:17:50.072 [test2] INFO c.f.l.reentrantlock.ConditionTest - thread name:test2 unlock success.
    2018-11-08 23:17:50.072 [test1] INFO c.f.l.reentrantlock.ConditionTest - thread name:test1 被唤醒,即将unlock.
    2018-11-08 23:17:50.072 [test1] INFO c.f.l.reentrantlock.ConditionTest - thread name:test1 unlock success.
    Read more »

ReentrantLock 定义

ReentrantLock 是 JUC 中提供的可中断, 可重入获取, 支持超时, 支持尝试获取锁
它主要有一下特点:

  1. 可重入, 一个线程获取独占锁后, 可多次获取, 多次释放(synchronized也一样, 只是synchronized内的代码执行异常后会自动释放到monitor上的锁)
  2. 支持中断(synchronized不支持)
  3. 支持超时机制, 支持尝试获取lock, 支持公不公平获取lock(主要区别在 判断 AQS 中的 Sync Queue 里面是否有其他线程等待获取 lock)
  4. 支持调用 Condition 提供的 await(释放lock, 并等待), signal(将线程节点从 Condition Queue 转移到 Sync Queue 里面)
  5. 在运行 synchronized 里面的代码若抛出异常, 则会自动释放监视器上的lock, 而 ReentrantLock 是需要显示的调用 unlock方法

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
/**
* reentrantloct 测试
* @author: peijiepang
* @date 2018/11/7
* @Description:
*/
public class ReentrantLockTest extends Thread{

private final static Logger LOGGER = LoggerFactory.getLogger(ReentrantLockTest.class);

private ReentrantLock reentrantLock = new ReentrantLock();

private CountDownLatch countDownLatch = null;

public static int j = 0;

public ReentrantLockTest(String threadName,CountDownLatch countDownLatch) {
super(threadName);
this.countDownLatch = countDownLatch;
}

@Override
public void run() {
for(int i=0;i<1000;i++){
//可限时加锁
//reentrantLock.tryLock(1000,TimeUnit.MILLISECONDS);

//可响应线程中断请求
//reentrantLock.lockInterruptibly();

//可指定公平锁
//ReentrantLock fairLock = new ReentrantLock(true);

reentrantLock.lock();
try{
LOGGER.info("{}:{}",Thread.currentThread().getName(),i);
j++;
}finally {
reentrantLock.unlock();
}
}
countDownLatch.countDown();
}

public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(2);
ReentrantLockTest reentrantLockTest1 = new ReentrantLockTest("thread1",countDownLatch);
ReentrantLockTest reentrantLockTest2 = new ReentrantLockTest("thread2",countDownLatch);
reentrantLockTest1.start();
reentrantLockTest2.start();
countDownLatch.await();
LOGGER.info("---------j:{}",j);
}
}
Read more »

synchronized关键字最主要的三种使用方式的总结

  • 修饰实例方法,作用于当前对象实例加锁,进入同步代码前要获得当前对象实例的锁
  • 修饰静态方法,作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁 。也就是给当前类加锁,会作用于类的所有对象实例,因为静态成员不属于任何一个实例对象,是类成员( static 表明这是该类的一个静态资源,不管new了多少个对象,只有一份,所以对该类的所有对象都加了锁)。所以如果一个线程A调用一个实例对象的非静态 synchronized 方法,而线程B需要调用这个实例对象所属类的静态 synchronized 方法,是允许的,不会发生互斥现象,因为访问静态 synchronized 方法占用的锁是当前类的锁,而访问非静态 synchronized 方法占用的锁是当前实例对象锁
  • 修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。 和 synchronized 方法一样,synchronized(this)代码块也是锁定当前对象的。synchronized 关键字加到 static 静态方法和 synchronized(class)代码块上都是是给 Class 类上锁。这里再提一下:synchronized关键字加到非 static 静态方法上是给对象实例上锁。另外需要注意的是:尽量不要使用 synchronized(String a) 因为JVM中,字符串常量池具有缓冲功能!

下面我已一个常见的面试题为例讲解一下 synchronized 关键字的具体使用。

面试中面试官经常会说:“单例模式了解吗?来给我手写一下!给我解释一下双重检验锁方式实现单利模式的原理呗!”

双重校验锁实现对象单例(线程安全)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Singleton {

private volatile static Singleton uniqueInstance;

private Singleton() {
}

public static Singleton getUniqueInstance() {
//先判断对象是否已经实例过,没有实例化过才进入加锁代码
if (uniqueInstance == null) {
//类对象加锁
synchronized (Singleton.class) {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}

另外,需要注意 uniqueInstance 采用 volatile 关键字修饰也是很有必要。

uniqueInstance 采用 volatile 关键字修饰也是很有必要的, uniqueInstance = new Singleton(); 这段代码其实是分为三步执行:

  1. 为 uniqueInstance 分配内存空间
  2. 初始化 uniqueInstance
  3. 将 uniqueInstance 指向分配的内存地址

但是由于 JVM 具有指令重排的特性,执行顺序有可能变成 1->3->2。指令重排在单线程环境下不会出先问题,但是在多线程环境下会导致一个线程获得还没有初始化的实例。例如,线程 T1 执行了 1 和 3,此时 T2 调用 getUniqueInstance() 后发现 uniqueInstance 不为空,因此返回 uniqueInstance,但此时 uniqueInstance 还未被初始化。

使用 volatile 可以禁止 JVM 的指令重排,保证在多线程环境下也能正常运行。

Read more »

远程仓库相关命令

检出仓库:$ git clone git://github.com/jquery/jquery.git

查看远程仓库:$ git remote -v

添加远程仓库:$ git remote add [name] [url]

删除远程仓库:$ git remote rm [name]

修改远程仓库:$ git remote set-url –push [name] [newUrl]

拉取远程仓库:$ git pull [remoteName] [localBranchName]

推送远程仓库:$ git push [remoteName] [localBranchName]

*如果想把本地的某个分支test提交到远程仓库,并作为远程仓库的master分支,或者作为另外一个名叫test的分支,如下:

$git push origin test:master // 提交本地test分支作为远程的master分支

$git push origin test:test // 提交本地test分支作为远程的test分支

分支(branch)操作相关命令

查看本地分支:$ git branch

查看远程分支:$ git branch -r

创建本地分支:$ git branch [name] —-注意新分支创建后不会自动切换为当前分支

切换分支:$ git checkout [name]

创建新分支并立即切换到新分支:$ git checkout -b [name]

删除分支:$ git branch -d [name] —- -d选项只能删除已经参与了合并的分支,对于未有合并的分支是无法删除的。如果想强制删除一个分支,可以使用-D选项

合并分支:$ git merge [name] —-将名称为[name]的分支与当前分支合并

创建远程分支(本地分支push到远程):$ git push origin [name]

删除远程分支:$ git push origin :heads/[name] 或 $ gitpush origin :[name]

*创建空的分支:(执行命令之前记得先提交你当前分支的修改,否则会被强制删干净没得后悔)

$git symbolic-ref HEAD refs/heads/[name]

$rm .git/index

$git clean -fdx

版本(tag)操作相关命令

查看版本:$ git tag

创建版本:$ git tag [name]

删除版本:$ git tag -d [name]

查看远程版本:$ git tag -r

创建远程版本(本地版本push到远程):$ git push origin [name]

删除远程版本:$ git push origin :refs/tags/[name]

合并远程仓库的tag到本地:$ git pull origin –tags

上传本地tag到远程仓库:$ git push origin –tags

创建带注释的tag:$ git tag -a [name] -m ‘yourMessage’

忽略一些文件、文件夹不提交

在仓库根目录下创建名称为“.gitignore”的文件,写入不需要的文件夹名或文件,每个元素占一行即可,如
target
bin
*.db

Zookeeper使用ACL来控制访问Znode,ACL的实现和UNIX的实现非常相似:它采用权限位来控制那些操作被允许,那些操作被禁止。但是和标准的UNIX权限不同的是,Znode没有限制用户(user,即文件的所有者),组(group)和其他(world)。Zookeepr是没有所有者的概念的。

每个ZNode的ACL是独立的,且子节点不会继承父节点的ACL。例如:Znode /app对于ip为172.16.16.1只有只读权限,而/app/status是world可读,那么任何人都可以获取/app/status;所以在Zookeeper中权限是没有继承和传递关系的,每个Znode的权限都是独立存在的。

Zookeeper支持可插拔的权限认证方案,分为三个维度:scheme,user,permission。通常表示为scheme:id,permissions,其中Scheme表示使用何种方式来进行访问控制,Id代表用户,Permission表示有什么权限。下面分别说说这三个维度:

zookeeper支持权限如下(permissions):

  • CREATE:可以创建子节点
  • READ:可以获取该节点的数据,也可以读取该节点所有的子节点
  • WRITE:可以写数据到该节点
  • DELETE:可以删除子节点
  • ADMIN:可以在该节点中设置权限

内置的ACL Schemes:

  • world: 只有一个id:anyone,world:anyone表示任何人都有访问权限,Zookeeper把任何人都有权限的节点都归属于world:anyone
  • auth:不需要任何id, 只要是通过auth的user都有权限
  • digest: 使用用户名/密码的方式验证,采用username:BASE64(SHA1(password))的字符串作为ACL的ID
  • ip: 使用客户端的IP地址作为ACL的ID,设置的时候可以设置一个ip段,比如ip:192.168.1.0/16, 表示匹配前16个bit的IP段
  • sasl:sasl的对应的id,是一个通过sasl authentication用户的id,zookeeper-3.4.4中的sasl authentication是通过kerberos来实现的,也就是说用户只有通过了kerberos认证,才能访问它有权限的node.

服务管理

  • 启动ZK服务: zkServer.sh start
  • 查看ZK状态: zkServer.sh status
  • 停止ZK服务: zkServer.sh stop
  • 重启ZK服务: zkServer.sh restart

常用命令

  • 客户端登录sudo sh zkCli.sh -server zk1.host.dxy:2181
  • 查看当前节点数据 ls /
  • 查看当前节点数据并能看到更新次数等数据 ls2 /
  • 创建一个新的节点并设置关联值 create /test “test”
  • 获取节点内容 get /test
  • 修改文件内容 set /test “test1”
  • 删除文件 delete /test
  • 删除节点及子节点 rmr /test
  • 打印节点状态 stat /test
  • 退出会话 quit

ACL权限

  • 为某个节点设置ACL权限 setAcl /test world:anyone:cdwra
  • 查看节点的ACL权限 getAcl /test
  • 添加认证信息,类似于登录,如果某个节点需要认证后才能查看,需要此命令 addauth digest admin:admin

四字命令

ZooKeeper 支持某些特定的四字命令字母与其的交互,用来获取服务的当前状态及相关信息。在客户端可以通过 telnet 或 nc 向 ZooKeeper 提交相应的命令。命令行如下:

1
echo conf | nc zk1.host.dxy 2181
  • stat 查看节点是否是leader echo stat | nc 127.0.0.1 2181|grep Mode
  • conf 输出相关服务配置的详细信息
  • cons 列出所有连接到服务器的客户端的完全的连接 / 会话的详细信息。包括“接受 / 发送”的包数量、会话 id 、操作延迟、最后的操作执行等等信息
  • dump 列出未经处理的会话和临时节点
  • envi 输出关于服务环境的详细信息(区别于 conf 命令)
  • reqs 列出未经处理的请求
  • ruok 测试服务是否处于正确状态。如果确实如此,那么服务返回“ imok ”,否则不做任何相应
  • stat 输出关于性能和连接的客户端的列表
  • wchs 列出服务器 watch 的详细信息
  • wchc 通过 session 列出服务器 watch 的详细信息,它的输出是一个与 watch 相关的会话的列表
  • wchp 通过路径列出服务器 watch 的详细信息。它输出一个与 session 相关的路径

大家在初次使用spring-cloud的gateway的时候,肯定会被里面各种的Timeout搞得晕头转向。hytrix有设置,ribbon也有。我们一开始也是乱设一桶,Github上各种项目里也没几个设置正确的。对Timeout的研究源于一次log中的warning

o.s.c.n.z.f.r.s.AbstractRibbonCommand : The Hystrix timeout of 60000ms for the command pay is set lower than the combination of the Ribbon read and connect timeout, 111000ms.

hytrix超时时间

log出自AbstractRibbonCommand.java,那么索性研究一下源码

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
protected static int getHystrixTimeout(IClientConfig config, String commandKey) {
int ribbonTimeout = getRibbonTimeout(config, commandKey);
DynamicPropertyFactory dynamicPropertyFactory = DynamicPropertyFactory.getInstance();

// 获取默认的hytrix超时时间
int defaultHystrixTimeout = dynamicPropertyFactory.getIntProperty("hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds",
0).get();
// 获取具体服务的hytrix超时时间,这里应该是hystrix.command.foo.execution.isolation.thread.timeoutInMilliseconds
int commandHystrixTimeout = dynamicPropertyFactory.getIntProperty("hystrix.command." + commandKey + ".execution.isolation.thread.timeoutInMilliseconds",
0).get();
int hystrixTimeout;
// hystrixTimeout的优先级是 具体服务的hytrix超时时间 > 默认的hytrix超时时间 > ribbon超时时间
if(commandHystrixTimeout > 0) {
hystrixTimeout = commandHystrixTimeout;
}
else if(defaultHystrixTimeout > 0) {
hystrixTimeout = defaultHystrixTimeout;
} else {
hystrixTimeout = ribbonTimeout;
}
// 如果默认的或者具体服务的hytrix超时时间小于ribbon超时时间就会警告
if(hystrixTimeout < ribbonTimeout) {
LOGGER.warn("The Hystrix timeout of " + hystrixTimeout + "ms for the command " + commandKey +
" is set lower than the combination of the Ribbon read and connect timeout, " + ribbonTimeout + "ms.");
}
return hystrixTimeout;
}

紧接着,看一下我们的配置是什么

1
2
3
4
5
6
7
8
9
10
11
12
13
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 600000

ribbon:
ReadTimeout: 50000
ConnectTimeout: 500
MaxAutoRetries: 0
MaxAutoRetriesNextServer: 1

ribbon超时时间
这里ribbon的超时时间是111000ms,那么为什么log中写的ribbon时间是50000ms?

继续分析源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
protected static int getRibbonTimeout(IClientConfig config, String commandKey) {
int ribbonTimeout;
// 这是比较异常的情况,不说
if (config == null) {
ribbonTimeout = RibbonClientConfiguration.DEFAULT_READ_TIMEOUT + RibbonClientConfiguration.DEFAULT_CONNECT_TIMEOUT;
} else {
// 这里获取了四个参数,ReadTimeout,ConnectTimeout,MaxAutoRetries, MaxAutoRetriesNextServer
int ribbonReadTimeout = getTimeout(config, commandKey, "ReadTimeout",
IClientConfigKey.Keys.ReadTimeout, RibbonClientConfiguration.DEFAULT_READ_TIMEOUT);
int ribbonConnectTimeout = getTimeout(config, commandKey, "ConnectTimeout",
IClientConfigKey.Keys.ConnectTimeout, RibbonClientConfiguration.DEFAULT_CONNECT_TIMEOUT);
int maxAutoRetries = getTimeout(config, commandKey, "MaxAutoRetries",
IClientConfigKey.Keys.MaxAutoRetries, DefaultClientConfigImpl.DEFAULT_MAX_AUTO_RETRIES);
int maxAutoRetriesNextServer = getTimeout(config, commandKey, "MaxAutoRetriesNextServer",
IClientConfigKey.Keys.MaxAutoRetriesNextServer, DefaultClientConfigImpl.DEFAULT_MAX_AUTO_RETRIES_NEXT_SERVER);
// 原来ribbonTimeout的计算方法在这里,以上文的设置为例
// ribbonTimeout = (50000 + 50000) * (0 + 1) * (1 + 1) = 200000
ribbonTimeout = (ribbonReadTimeout + ribbonConnectTimeout) * (maxAutoRetries + 1) * (maxAutoRetriesNextServer + 1);
}
return ribbonTimeout;
}

可以看到ribbonTimeout是一个总时间,所以从逻辑上来讲,作者希望hystrixTimeout要大于ribbonTimeout,否则hystrix熔断了以后,ribbon的重试就都没有意义了。

ribbon单服务设置

到这里最前面的疑问已经解开了,但是hytrix可以分服务设置timeout,ribbon可不可以? 源码走起,这里看的文件是DefaultClientConfigImpl.java

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
30
31
32
// 这是获取配置的入口方法,如果是null,那么用默认值
// 所有ribbon的默认值的都在该类中设置了,可以自己看一下
public <T> T get(IClientConfigKey<T> key, T defaultValue) {
T value = get(key);
if (value == null) {
value = defaultValue;
}
return value;
}
// 这是核心方法
protected Object getProperty(String key) {
if (enableDynamicProperties) {
String dynamicValue = null;
DynamicStringProperty dynamicProperty = dynamicProperties.get(key);
// dynamicProperties其实是一个缓存,首次访问foo服务的时候会加载
if (dynamicProperty != null) {
dynamicValue = dynamicProperty.get();
}
// 如果缓存没有,那么就再获取一次,注意这里的getConfigKey(key)是生成key的方法
if (dynamicValue == null) {
dynamicValue = DynamicProperty.getInstance(getConfigKey(key)).getString();
// 如果还是没有取默认值,getDefaultPropName(key)生成key的方法
if (dynamicValue == null) {
dynamicValue = DynamicProperty.getInstance(getDefaultPropName(key)).getString();
}
}
if (dynamicValue != null) {
return dynamicValue;
}
}
return properties.get(key);
}

小结

感觉ribbon和hytrix的配置获取源码略微有点乱,所以也导致大家在设置的时候有些无所适从。spring-cloud的代码一直在迭代,无论github上还是文档可能都相对滞后,这时候阅读源码并且动手debug一下是最能接近事实真相的了。

背景

开发写代码一直想脱离鼠标操作,看起来高大上一点,最近开始idea用vim操作。以下是vim的简单快捷键。

keymap 记录

  • 跳转到指定行:{行数}g
  • 标签特切换:gt或者gT,前者顺序切换,后者逆向切换
  • 单词移动:w/W,移动到下个单词开头;b/B,倒退到上个单词开头。大写的会忽略标点。命令前加数字表示执行次数,如2W
  • 删除当前单词并进入插入模式:cw
  • 撤销:u;恢复被撤销的操作:ctrl+r
  • v进入选择字符,V进入行选择模式
  • 用y命令将文本存入寄存器,普通模式下小写p把寄存器内容复制到当前位置之后,大写P把寄存器内容复制到当前位置之前
  • 剪切操作,先v选择多行,然后d删除,最后到需要粘贴的地方p
  • 跳转到特定行,按:n 如 :23 跳转到23行
  • x(小写) -> 正向按字符单位进行删除 向右删除
  • X(大写) -> 反向按字符单位进行删除 向左删除
  • $ -> 当前行的最后一个字符
  • G -> 跳转到最后一行

组合技巧

  • 全选: ggvG
  • 调换两个字符位置: xp
  • 复制一行: yyp
  • 调换两行位置: ddp
  • 复制后,在命令模式下 np n代表数字你想要粘贴的数目,如 10p