前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >Netty杂记

Netty杂记

作者头像
书唐瑞
发布2022-06-02 14:52:02
发布2022-06-02 14:52:02
45700
代码可运行
举报
文章被收录于专栏:Netty历险记Netty历险记
运行总次数:0
代码可运行

Netty启动之后, IO线程便处于无限循环执行中.代码如下

代码语言:javascript
代码运行次数:0
复制
// 源码位置: io.netty.channel.nio.NioEventLoop#run
@Override
protected void run() {
    int selectCnt = 0;
    for (;;) {
        try {
            int strategy;
            try {
                strategy = selectStrategy.calculateStrategy(selectNowSupplier, hasTasks());
                // 1.轮询IO事件
                strategy = select(curDeadlineNanos);
            } catch (IOException e) {
                ...
            }
            ...
            if (ioRatio == 100) {
                ...
            } else if (strategy > 0) {
                final long ioStartTime = System.nanoTime();
                try {
                    // 2.处理IO事件
                    processSelectedKeys();
                } finally {
                    // Ensure we always run tasks.
                    final long ioTime = System.nanoTime() - ioStartTime;
                    // 3.执行任务
                    ranTasks = runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
                }
            }
        ...
        }
    }
}    
    

这里关注一下第3点执行任务

代码语言:javascript
代码运行次数:0
复制
/**
 * Poll all tasks from the task queue and run them via {@link Runnable#run()} method.  This method stops running the tasks in the task queue and returns if it ran longer than {@code timeoutNanos}.
 */
protected boolean runAllTasks(long timeoutNanos) {    
    fetchFromScheduledTaskQueue();
    Runnable task = pollTask();
    if (task == null) {
        afterRunningAllTasks();
        return false;
    }

    final long deadline = timeoutNanos > 0 ? ScheduledFutureTask.nanoTime() + timeoutNanos : 0;
    long runTasks = 0;
    long lastExecutionTime;
    for (;;) {
        safeExecute(task);

        runTasks ++;

        // 每64个任务检查一次超时,因为nanoTime()比较昂贵.
        // Check timeout every 64 tasks because nanoTime() is relatively expensive.
        // XXX: Hard-coded value - will make it configurable if it is really a problem.
        if ((runTasks & 0x3F) == 0) {
            lastExecutionTime = ScheduledFutureTask.nanoTime();
            if (lastExecutionTime >= deadline) {
                break;
            }
        }

        task = pollTask();
        if (task == null) {
            lastExecutionTime = ScheduledFutureTask.nanoTime();
            break;
        }
    }

    afterRunningAllTasks();
    this.lastExecutionTime = lastExecutionTime;
    return true;
}

入参timeoutNanos设置执行任务的超时时间. 一旦超过这个设定的时间,则停止执行任务.

按照一般的思路,每执行一个任务之后,就要调用获取时间,然后判断一下时间是否到达入参设置的超时时间,如果未到达,则继续执行下一个任务, 如果到达,则停止执行任务.

但是Netty并未这样实现. 它是每执行64个任务之后,在调用获取时间,判断是否到达设置的超时时间. 之所以这样做, 源码中也给了注释解释,因为调用获取时间是消耗性能的,应该减少调用获取时间的次数.

那我们看一下java.lang.System#nanoTime源码.

代码语言:javascript
代码运行次数:0
复制
// 源码位置: java.lang.System#nanoTime
public static native long nanoTime();

是一个native方法, 继续查看JVM中的源码.

代码语言:javascript
代码运行次数:0
复制
// 源码位置: hotspot/src/share/vm/prims/jvm.cpp
JVM_LEAF(jlong, JVM_NanoTime(JNIEnv *env, jclass ignored))
  JVMWrapper("JVM_NanoTime");
  return os::javaTimeNanos();
JVM_END
代码语言:javascript
代码运行次数:0
复制
// 源码位置: hotspot/src/os/linux/vm/os_linux.cpp
jlong os::javaTimeNanos() {
  if (Linux::supports_monotonic_clock()) {
    struct timespec tp;
    // 系统调用
    int status = Linux::clock_gettime(CLOCK_MONOTONIC, &tp);
    assert(status == 0, "gettime error");
    jlong result = jlong(tp.tv_sec) * (1000 * 1000 * 1000) + jlong(tp.tv_nsec);
    return result;
  } else {
    timeval time;
    // 调用C库函数,底层调用系统调用
    int status = gettimeofday(&time, NULL);
    assert(status != -1, "linux error");
    jlong usecs = jlong(time.tv_sec) * (1000 * 1000) + jlong(time.tv_usec);
    return 1000 * usecs;
  }
}

获取时间需要进行系统调用,进行用户态和内核态切换.

我们可以写一个小测试案例,验证下

代码语言:javascript
代码运行次数:0
复制
public class Example {

    public static void main(String[] args) {
        System.out.println("start...");
        
        long l = System.nanoTime();
        System.out.println(l);
        
        System.out.println("end...");
    }
}

编译

javac Example.java

使用strace命令查看系统调用

代码语言:javascript
代码运行次数:0
复制
[v-dev: tmp]# strace -ff -o out java Example
start...
35737962287000
end...

部分系统调用输出如下

代码语言:javascript
代码运行次数:0
复制
write(1, "start...", 8)                 = 8
gettimeofday({tv_sec=1638243469, tv_usec=142774}, NULL) = 0
write(1, "\n", 1)                       = 1
clock_gettime(CLOCK_MONOTONIC, {tv_sec=35737, tv_nsec=962287000}) = 0
gettimeofday({tv_sec=1638243469, tv_usec=143295}, NULL) = 0
gettimeofday({tv_sec=1638243469, tv_usec=143447}, NULL) = 0
gettimeofday({tv_sec=1638243469, tv_usec=143568}, NULL) = 0
write(1, "35737962287000", 14)          = 14
write(1, "\n", 1)                       = 1
write(1, "end...", 6)                   = 6
write(1, "\n", 1)                       = 1

可以看到它调用了clock_gettime系统调用.

综上, Netty为了提高任务执行的性能,减少系统调用次数,每执行64个任务之后,才会调用系统调用,获取时间,判断是否超时,如果到达了超时时间则停止执行任务,否则继续执行任务.

再说一点

代码语言:javascript
代码运行次数:0
复制
<dependency>
  <groupId>cn.hutool</groupId>
  <artifactId>hutool-all</artifactId>
  <version>5.3.10</version>
</dependency>

糊涂(Hutool)工具中的雪花算法,在获取时间的时候,并没有按照如下实现

代码语言:javascript
代码运行次数:0
复制
private long genTime() {
    return System.currentTimeMillis();
}

而是使用了下面的代码

代码语言:javascript
代码运行次数:0
复制
private long genTime() {
    return this.useSystemClock ? SystemClock.now() : System.currentTimeMillis();
}

作者使用了一个SystemClock类

作者进行了说明. 由于System.currentTimeMillis()会进行系统调用(这个我们在上面已经验证过了), 进行用户态和内核态切换,比较耗时.

这里作者使用一个SystemClock类.

在实例化SystemClock类的时候,底层会启动一个线程, 周期性的获取系统时间. 默认1毫秒获取一次. 作者这样的优化,也是为了提高性能.

综上, Netty和Hutool为了提高性能, 在获取时间的地方, 采取了对应的策略应对.

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-12-01,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Netty历险记 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档