一种心跳,两种设计

1 前言

在前一篇文章《聊聊 TCP 长连接和心跳那些事》中,我们已经聊过了 TCP 中的 KeepAlive,以及在应用层设计心跳的意义,但却对长连接心跳的设计方案没有做详细地介绍。事实上,设计一个好的心跳机制并不是一件容易的事,就我所熟知的几个 RPC 框架,它们的心跳机制可以说大相径庭,这篇文章我将探讨一下如何设计一个优雅的心跳机制,主要从 Dubbo 的现有方案以及一个改进方案来做分析

查看更多

分享到

聊聊 TCP 长连接和心跳那些事

前言

可能很多 Java 程序员对 TCP 的理解只有一个三次握手,四次握手的认识,我觉得这样的原因主要在于 TCP 协议本身稍微有点抽象(相比较于应用层的 HTTP 协议);其次,非框架开发者不太需要接触到 TCP 的一些细节。其实我个人对 TCP 的很多细节也并没有完全理解,这篇文章主要针对微信交流群里有人提出的长连接,心跳问题,做一个统一的整理。

在 Java 中,使用 TCP 通信,大概率会涉及到 Socket、Netty,本文将借用它们的一些 API 和设置参数来辅助介绍。

查看更多

分享到

Dubbo 中的 URL 统一模型

定义

在不谈及 dubbo 时,我们大多数人对 URL 这个概念并不会感到陌生。统一资源定位器 (RFC1738――Uniform Resource Locators (URL))应该是最广为人知的一个 RFC 规范,它的定义也非常简单

因特网上的可用资源可以用简单字符串来表示,该文档就是描述了这种字符串的语法和语
义。而这些字符串则被称为:“统一资源定位器”(URL)

一个标准的 URL 格式至多可以包含如下的几个部分

1
protocol://username:password@host:port/path?key=value&key=value

查看更多

分享到

PolarDB数据库性能大赛Java选手分享

1 前言

排名

国际惯例,先报成绩,熬了无数个夜晚,最后依旧被绝杀出了第一页,最终排名第 21 名。前十名的成绩分布为 413.69~416.94,我最终的耗时是 422.43。成绩虽然不是特别亮眼,但与众多参赛选手使用 C++ 作为参赛语言不同,我使用的是 Java,一方面是我 C++ 的能力早已荒废,另一方面是我想验证一下使用 Java 编写存储引擎是否与 C++ 差距巨大(当然,主要还是前者 QAQ)。所以在本文中,我除了介绍整体的架构之外,还会着重笔墨来探讨 Java 编写存储类型应用的一些最佳实践,文末会给出 github 的开源地址。

查看更多

分享到

文件IO操作的一些最佳实践

背景

已经过去的中间件性能挑战赛,和正在进行中的 第一届 PolarDB 数据性能大赛 都涉及到了文件操作,合理地设计架构以及正确地压榨机器的读写性能成了比赛中获取较好成绩的关键。正在参赛的我收到了几位公众号读者朋友的反馈,他们大多表达出了这样的烦恼:“对比赛很感兴趣,但不知道怎么入门”,“能跑出成绩,但相比前排的选手,成绩相差10倍有余”…为了能让更多的读者参与到之后相类似的比赛中来,我简单整理一些文件IO操作的最佳实践,而不涉及整体系统的架构设计,希望通过这篇文章的介绍,让你能够欢快地参与到之后类似的性能挑战赛之中来。

查看更多

分享到

八个层面比较 Java 8, RxJava, Reactor

前言

这是一篇译文,原文出处 戳这里。其实很久以前我就看完了这篇文章,只不过个人对响应式编程研究的不够深入,羞于下笔翻译,在加上这类译文加了原创还有争议性,所以一直没有动力。恰逢今天交流群里两个大佬对响应式编程的话题辩得不可开交,趁印象还算深刻,借机把这篇文章翻译一下。说道辩论的点,不妨也在这里抛出来:

响应式编程在单机环境下是否鸡肋?

结论是:没有结论,我觉得只能抱着怀疑的眼光审视这个问题了。另外还聊到了 RSocket 这个最近在 SpringOne 大会上比较火爆的响应式”新“网络协议,github 地址戳这里,为什么给”新“字打了个引号,仔细观察下 RSocket 的 commit log,其实三年前就有了。有兴趣的同学自行翻阅,说不定就是今年这最后两三个月的热点技术哦。

Java 圈子有一个怪事,那就是对 RxJava,Reactor,WebFlux 这些响应式编程的名词、框架永远处于渴望了解,感到新鲜,却又不甚了解,使用贫乏的状态。之前转载小马哥的那篇《Reactive Programming 一种技术,各自表述》时,就已经聊过这个关于名词之争的话题了,今天群里的讨论更是加深了我的映像。Java 圈子里面很多朋友一直对响应式编程处于一个了解名词,知道基本原理,而不是深度用户的状态(我也是之一)。可能真的和圈子有关,按石冲兄的说法,其实 Scala 圈子里面的那帮人,不知道比咱们高到哪里去了(就响应式编程而言)。

实在是好久没发文章了,向大家说声抱歉,以后的更新频率肯定是没有以前那么勤了(说的好像以前很勤快似的),一部分原因是在公司内网写的文章没法贴到公众号中和大家分享讨论,另一部分是目前我也处于学习公司内部框架的阶段,不太方便提炼成文章,最后,最大的一部分原因还是我这段时间需要学(tou)习(lan)其(da)他(you)东(xi)西啦。好了,废话也说完了,下面是译文的正文部分。

查看更多

分享到

关于阿里面试、学习路线、公众号的一些想法

还记得上一篇记录我心情的随笔是写在离开魔都,去往南京的时候,此时的我,又来到了杭州。工作发生了变故,心境也发生了变化,倒是有不少东西想跟各位来聊一聊,择其三汇成此文。

查看更多

分享到

Java随机数探秘

本文的前3节参考修改自微信公众号「咖啡拿铁」的文章,感谢李钊同学对这个话题热情的讨论。

1 前言

一提到 Java 中的随机数,很多人就会想到 Random,当出现生成随机数这样需求时,大多数人都会选择使用 Random 来生成随机数。Random 类是线程安全的,但其内部使用 CAS 来保证线程安全性,在多线程并发的情况下的时候它的表现是存在优化空间的。在 JDK1.7 之后,Java 提供了更好的解决方案 ThreadLocalRandom,接下来,我们一起探讨下这几个随机数生成器的实现到底有何不同。

2 Random

Random 这个类是 JDK 提供的用来生成随机数的一个类,这个类并不是真正的随机,而是伪随机,伪随机的意思是生成的随机数其实是有一定规律的,而这个规律出现的周期随着伪随机算法的优劣而不同,一般来说周期比较长,但是可以预测。通过下面的代码我们可以对 Random 进行简单的使用: img

Random原理

Random 中的方法比较多,这里就针对比较常见的 nextInt() 和 nextInt(int bound) 方法进行分析,前者会计算出 int 范围内的随机数,后者如果我们传入 10,那么他会求出 [0,10) 之间的 int 类型的随机数,左闭右开。我们首先看一下 Random() 的构造方法: img

可以发现在构造方法当中,根据当前时间的种子生成了一个 AtomicLong 类型的 seed,这也是我们后续的关键所在。

####nextInt()

nextInt() 的代码如下所示:

img

这个里面直接调用的是 next() 方法,传入的 32,代指的是 Int 类型的位数。

img

这里会根据 seed 当前的值,通过一定的规则(伪随机算法)算出下一个 seed,然后进行 CAS,如果 CAS 失败则继续循环上面的操作。最后根据我们需要的 bit 位数来进行返回。核心便是 CAS 算法。

nextInt(int bound)

nextInt(int bound) 的代码如下所示:img

这个流程比 nextInt() 多了几步,具体步骤如下:

  1. 首先获取 31 位的随机数,注意这里是 31 位,和上面 32 位不同,因为在 nextInt() 方法中可以获取到随机数可能是负数,而 nextInt(int bound) 规定只能获取到 [0,bound) 之前的随机数,也就意味着必须是正数,预留一位符号位,所以只获取了31位。(不要想着使用取绝对值这样操作,会导致性能下降)
  2. 然后进行取 bound 操作。
  3. 如果 bound 是2的幂次方,可以直接将第一步获取的值乘以 bound 然后右移31位,解释一下:如果 bound 是4,那么乘以4其实就是左移2位,其实就是变成了33位,再右移31位的话,就又会变成2位,最后,2位 int 的范围其实就是 [0,4) 了。
  4. 如果不是 2 的幂,通过模运算进行处理。

并发瓶颈

在我之前的文章中就有相关的介绍,一般而言,CAS 相比加锁有一定的优势,但并不一定意味着高效。一个立刻被想到的解决方案是每次使用 Random 时都去 new 一个新的线程私有化的 Random 对象,或者使用 ThreadLocal 来维护线程私有化对象,但除此之外还存在更高效的方案,下面便来介绍本文的主角 ThreadLocalRandom。

3 ThreadLocalRandom

在 JDK1.7 之后提供了新的类 ThreadLocalRandom 用来在并发场景下代替 Random。使用方法比较简单:

1
2
ThreadLocalRandom.current().nextInt();
ThreadLocalRandom.current().nextInt(10);

在 current 方法中有:

img可以看见如果没有初始化会对其进行初始化,而这里我们的 seed 不再是一个全局变量,在我们的Thread中有三个变量: img

  • threadLocalRandomSeed:ThreadLocalRandom 使用它来控制随机数种子。
  • threadLocalRandomProbe:ThreadLocalRandom 使用它来控制初始化。
  • threadLocalRandomSecondarySeed:二级种子。

可以看见所有的变量都加了 @sun.misc.Contended 这个注解,用来处理伪共享问题。

在 nextInt() 方法当中代码如下:

img

我们的关键代码如下:

1
UNSAFE.putLong(t = Thread.currentThread(), SEED,r=UNSAFE.getLong(t, SEED) + GAMMA);

可以看见由于我们每个线程各自都维护了种子,这个时候并不需要 CAS,直接进行 put,在这里利用线程之间隔离,减少了并发冲突;相比较 ThreadLocal<Random>,ThreadLocalRandom 不仅仅减少了对象维护的成本,其内部实现也更轻量级。所以 ThreadLocalRandom 性能很高。

4 性能测试

除了文章中详细介绍的 Random,ThreadLocalRandom,我还将 netty4 实现的 ThreadLocalRandom,以及 ThreadLocal<Random> 作为参考对象,一起参与 JMH 测评。

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
@BenchmarkMode({Mode.AverageTime})
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations = 3, time = 5)
@Measurement(iterations = 3, time = 5)
@Threads(50)
@Fork(1)
@State(Scope.Benchmark)
public class RandomBenchmark {

Random random = new Random();

ThreadLocal<Random> threadLocalRandomHolder = ThreadLocal.withInitial(Random::new);

@Benchmark
public int random() {
return random.nextInt();
}

@Benchmark
public int threadLocalRandom() {
return ThreadLocalRandom.current().nextInt();
}

@Benchmark
public int threadLocalRandomHolder() {
return threadLocalRandomHolder.get().nextInt();
}

@Benchmark
public int nettyThreadLocalRandom() {
return io.netty.util.internal.ThreadLocalRandom.current().nextInt();
}

public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder()
.include(RandomBenchmark.class.getSimpleName())
.build();

new Runner(opt).run();
}

}

测评结果如下:

1
2
3
4
5
Benchmark                                Mode  Cnt     Score     Error  Units
RandomBenchmark.nettyThreadLocalRandom avgt 3 192.202 ± 295.897 ns/op
RandomBenchmark.random avgt 3 3197.620 ± 380.981 ns/op
RandomBenchmark.threadLocalRandom avgt 3 90.731 ± 39.098 ns/op
RandomBenchmark.threadLocalRandomHolder avgt 3 229.502 ± 267.144 ns/op

从上图可以发现,JDK1.7 的 ThreadLocalRandom 取得了最好的成绩,仅仅需要 90 ns 就可以生成一次随机数,netty 实现的ThreadLocalRandom 以及使用 ThreadLocal 维护 Random 的方式差距不是很大,位列 2、3 位,共享的 Random 变量则效果最差。

可见,在并发场景下,ThreadLocalRandom 可以明显的提升性能。

5 注意点

注意,ThreadLocalRandom 切记不要调用 current 方法之后,作为共享变量使用

1
2
3
4
5
6
7
8
9
public class WrongCase {

ThreadLocalRandom threadLocalRandom = ThreadLocalRandom.current();

public int concurrentNextInt(){
return threadLocalRandom.nextInt();
}

}

这是因为 ThreadLocalRandom.current() 会使用初始化它的线程来填充随机种子,这会带来导致多个线程使用相同的 seed。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Main {

public static void main(String[] args) {
ThreadLocalRandom threadLocalRandom = ThreadLocalRandom.current();
for(int i=0;i<10;i++)
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(threadLocalRandom.nextInt());
}
}).start();

}
}

输出相同的随机数:

1
2
3
4
5
6
7
8
9
10
-1667209487
-1667209487
-1667209487
-1667209487
-1667209487
-1667209487
-1667209487
-1667209487
-1667209487
-1667209487

请在确保不同线程获取不同的 seed,最简单的方式便是每次调用都是使用 current():

1
2
3
4
5
public class RightCase {
public int concurrentNextInt(){
return ThreadLocalRandom.current().nextInt();
}
}

彩蛋1

梁飞博客中一句话常常在我脑海中萦绕:魔鬼在细节中。优秀的代码都是一个个小细节堆砌出来,今天介绍的 ThreadLocalRandom 也不例外。

dubbo

在 incubator-dubbo-2.7.0 中,随机负载均衡器的一个小改动便是将 Random 替换为了 ThreadLocalRandom,用于优化并发性能。

彩蛋2

ThreadLocalRandom 的 nextInt(int bound) 方法中,当 bound 不为 2 的幂次方时,使用了一个循环来修改 r 的值,我认为这可能不必要,你觉得呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public int nextInt(int bound) {
if (bound <= 0)
throw new IllegalArgumentException(BadBound);
int r = mix32(nextSeed());
int m = bound - 1;
if ((bound & m) == 0) // power of two
r &= m;
else { // reject over-represented candidates
for (int u = r >>> 1;
u + m - (r = u % bound) < 0;
u = mix32(nextSeed()) >>> 1)
;
}
return r;
}

欢迎关注李钊同学的微信公众号:「咖啡拿铁」

咖啡拿铁

当然,也欢迎关注我的微信公众号:「Kirito的技术分享」,关于文章的任何疑问都会得到回复,带来更多 Java 相关的技术分享。

关注微信公众号

分享到

Spring中的XML schema扩展机制

前言

很久没有写关于 Spring 的文章了,最近在系统梳理 Dubbo 代码的过程中发现了 XML schema 这个被遗漏的知识点。由于工作中使用 SpringBoot 比较多的原因,几乎很少接触 XML,此文可以算做是亡羊补牢,另一方面,也为后续的 Dubbo 源码解析做个铺垫。

XML schema 扩展机制是啥?这并不是一块很大的知识点,翻阅一下 Spring 的文档,我甚至没找到一个贯穿上下文的词来描述这个功能,XML Schema Authoring 是文档中对应的标题,简单来说:

查看更多

分享到

JCTools -- 高性能内存队列探秘(写作中...)

1
2
3
4
5
Benchmark                    (burstSize)  (qCapacity)            (queueType)  Mode  Cnt        Score        Error  Units
QueueBenchmark.offerAndPoll 100000 132000 ArrayBlockingQueue avgt 15 3746839.904 ± 214308.618 ns/op
QueueBenchmark.offerAndPoll 100000 132000 LinkedBlockingQueue avgt 15 6537598.922 ± 611003.544 ns/op
QueueBenchmark.offerAndPoll 100000 132000 ConcurrentLinkedQueue avgt 15 2944479.279 ± 41621.277 ns/op
QueueBenchmark.offerAndPoll 100000 132000 MpscArrayQueue avgt 15 1199839.760 ± 65991.348 ns/op

欢迎关注我的微信公众号:「Kirito的技术分享」,关于文章的任何疑问都会得到回复,带来更多 Java 相关的技术分享。

关注微信公众号

分享到