socket选项 SO_REUSEPORT

2023-11-07

摘要 多核与网络IO

目录[-]

前言

本篇用于记录学习SO_REUSEPORT的笔记和心得,末尾还会提供一个bindp小工具也能为已有的程序享受这个新的特性。

当前Linux网络应用程序问题

运行在Linux系统上网络应用程序,为了利用多核的优势,一般使用以下比较典型的多进程/多线程服务器模型:

  1. 单线程listen/accept,多个工作线程接收任务分发,虽CPU的工作负载不再是问题,但会存在:

    • 单线程listener,在处理高速率海量连接时,一样会成为瓶颈

    • CPU缓存行丢失套接字结构(socket structure)现象严重

  2. 所有工作线程都accept()在同一个服务器套接字上呢,一样存在问题:

    • 多线程访问server socket锁竞争严重

    • 高负载下,线程之间处理不均衡,有时高达3:1不均衡比例

    • 导致CPU缓存行跳跃(cache line bouncing)

    • 在繁忙CPU上存在较大延迟

上面模型虽然可以做到线程和CPU核绑定,但都会存在:

  • 单一listener工作线程在高速的连接接入处理时会成为瓶颈

  • 缓存行跳跃

  • 很难做到CPU之间的负载均衡

  • 随着核数的扩展,性能并没有随着提升

比如HTTP CPS(Connection Per Second)吞吐量并没有随着CPU核数增加呈现线性增长: 

Linux kernel 3.9带来了SO_REUSEPORT特性,可以解决以上大部分问题。

SO_REUSEPORT解决了什么问题

linux man文档中一段文字描述其作用:

The new socket option allows multiple sockets on the same host to bind to the same port, and is intended to improve the performance of multithreaded network server applications running on top of multicore systems.

SO_REUSEPORT支持多个进程或者线程绑定到同一端口,提高服务器程序的性能,解决的问题:

  • 允许多个套接字 bind()/listen() 同一个TCP/UDP端口

    • 每一个线程拥有自己的服务器套接字

    • 在服务器套接字上没有了锁的竞争

  • 内核层面实现负载均衡

  • 安全层面,监听同一个端口的套接字只能位于同一个用户下面

其核心的实现主要有三点:

  • 扩展 socket option,增加 SO_REUSEPORT 选项,用来设置 reuseport。

  • 修改 bind 系统调用实现,以便支持可以绑定到相同的 IP 和端口

  • 修改处理新建连接的实现,查找 listener 的时候,能够支持在监听相同 IP 和端口的多个 sock 之间均衡选择。

代码分析,可以参考引用资料 [多个进程绑定相同端口的实现分析[Google Patch]]。

CPU之间平衡处理,水平扩展

以前通过fork形式创建多个子进程,现在有了SO_REUSEPORT,可以不用通过fork的形式,让多进程监听同一个端口,各个进程中accept socket fd不一样,有新连接建立时,内核只会唤醒一个进程来accept,并且保证唤醒的均衡性。

模型简单,维护方便了,进程的管理和应用逻辑解耦,进程的管理水平扩展权限下放给程序员/管理员,可以根据实际进行控制进程启动/关闭,增加了灵活性。

这带来了一个较为微观的水平扩展思路,线程多少是否合适,状态是否存在共享,降低单个进程的资源依赖,针对无状态的服务器架构最为适合了。

新特性测试或多个版本共存

可以很方便的测试新特性,同一个程序,不同版本同时运行中,根据运行结果决定新老版本更迭与否。

针对对客户端而言,表面上感受不到其变动,因为这些工作完全在服务器端进行。

服务器无缝重启/切换

想法是,我们迭代了一版本,需要部署到线上,为之启动一个新的进程后,稍后关闭旧版本进程程序,服务一直在运行中不间断,需要平衡过度。这就像Erlang语言层面所提供的热更新一样。

想法不错,但是实际操作起来,就不是那么平滑了,还好有一个hubtime开源工具,原理为SIGHUP信号处理器+SO_REUSEPORT+LD_RELOAD,可以帮助我们轻松做到,有需要的同学可以检出试用一下。

SO_REUSEPORT已知问题

SO_REUSEPORT根据数据包的四元组{src ip, src port, dst ip, dst port}和当前绑定同一个端口的服务器套接字数量进行数据包分发。若服务器套接字数量产生变化,内核会把本该上一个服务器套接字所处理的客户端连接所发送的数据包(比如三次握手期间的半连接,以及已经完成握手但在队列中排队的连接)分发到其它的服务器套接字上面,可能会导致客户端请求失败,一般可以使用:

  • 使用固定的服务器套接字数量,不要在负载繁忙期间轻易变化

  • 允许多个服务器套接字共享TCP请求表(Tcp request table)

  • 不使用四元组作为Hash值进行选择本地套接字处理,挑选隶属于同一个CPU的套接字

与RFS/RPS/XPS-mq协作,可以获得进一步的性能:

  • 服务器线程绑定到CPUs

  • RPS分发TCP SYN包到对应CPU核上

  • TCP连接被已绑定到CPU上的线程accept()

  • XPS-mq(Transmit Packet Steering for multiqueue),传输队列和CPU绑定,发送数据

  • RFS/RPS保证同一个连接后续数据包都会被分发到同一个CPU上

  • 网卡接收队列已经绑定到CPU,则RFS/RPS则无须设置

  • 需要注意硬件支持与否

目的嘛,数据包的软硬中断、接收、处理等在一个CPU核上,并行化处理,尽可能做到资源利用最大化。

SO_REUSEPORT不是一贴万能膏药

虽然SO_REUSEPORT解决了多个进程共同绑定/监听同一端口的问题,但根据新浪林晓峰同学测试结果来看,在多核扩展层面也未能够做到理想的线性扩展:

可以参考Fastsocket在其基础之上的改进,链接地址

支持SO_REUSEPORT的Tengine

淘宝的Tengine已经支持了SO_REUSEPORT特性,在其测试报告中,有一个简单测试,可以看出来相对比SO_REUSEPORT所带来的性能提升:

使用SO_REUSEPORT以后,最明显的效果是在压力下不容易出现丢请求的情况,CPU均衡性平稳。

Java支持否?

JDK 1.6语言层面不支持,至于以后的版本,由于暂时没有使用到,不多说。

Netty 3/4版本默认都不支持SO_REUSEPORT特性,但Netty 4.0.19以及之后版本才真正提供了JNI方式单独包装的epoll native transport版本(在Linux系统下运行),可以配置类似于SO_REUSEPORT等(JAVA NIIO没有提供)选项,这部分是在io.netty.channel.epoll.EpollChannelOption中定义(在线代码部分)。

在linux环境下使用epoll native transport,可以获得内核层面网络堆栈增强的红利,如何使用可参考Native transports文档。

使用epoll native transport倒也简单,类名稍作替换:

NioEventLoopGroup → EpollEventLoopGroup
NioEventLoop → EpollEventLoop
NioServerSocketChannel → EpollServerSocketChannel
NioSocketChannel → EpollSocketChannel

比如写一个PING-PONG应用服务器程序,类似代码:

public void run() throws Exception {
    EventLoopGroup bossGroup = new EpollEventLoopGroup();
    EventLoopGroup workerGroup = new EpollEventLoopGroup();
    try {
        ServerBootstrap b = new ServerBootstrap();
        ChannelFuture f = b
                .group(bossGroup, workerGroup)
                .channel(EpollServerSocketChannel.class)
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    public void initChannel(SocketChannel ch)
                            throws Exception {
                        ch.pipeline().addLast(
                                new StringDecoder(CharsetUtil.UTF_8),
                                new StringEncoder(CharsetUtil.UTF_8),
                                new PingPongServerHandler());
                    }
                }).option(ChannelOption.SO_REUSEADDR, true)
                .option(EpollChannelOption.SO_REUSEPORT, true)
                .childOption(ChannelOption.SO_KEEPALIVE, true).bind(port)
                .sync();
        f.channel().closeFuture().sync();
    } finally {
        workerGroup.shutdownGracefully();
        bossGroup.shutdownGracefully();
    }
}

若不要这么折腾,还想让以往Java/Netty应用程序在不做任何改动的前提下顺利在Linux kernel >= 3.9下同样享受到SO_REUSEPORT带来的好处,不妨尝试一下bindp,更为经济,这一部分下面会讲到。

bindp,为已有应用添加SO_REUSEPORT特性

以前所写bindp小程序,可以为已有程序绑定指定的IP地址和端口,一方面可以省去硬编码,另一方面也为测试提供了一些方便。

另外,为了让以前没有硬编码SO_REUSEPORT的应用程序可以在Linux内核3.9以及之后Linux系统上也能够得到内核增强支持,稍做修改,添加支持。

但要求如下:

  1. Linux内核(>= 3.9)支持SO_REUSEPORT特性

  2. 需要配置REUSE_PORT=1

不满足以上条件,此特性将无法生效。

使用示范:

REUSE_PORT=1 BIND_PORT=9999 LD_PRELOAD=./libbindp.so java -server -jar pingpongserver.jar &

当然,你可以根据需要运行命令多次,多个进程监听同一个端口,单机进程水平扩展。

使用示范

使用python脚本快速构建一个小的示范原型,两个进程,都监听同一个端口10000,客户端请求返回不同内容,仅供娱乐。

server_v1.py,简单PING-PONG:

# -*- coding:UTF-8 -*-

import socket
import os

PORT = 10000
BUFSIZE = 1024

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('', PORT))
s.listen(1)

while True:
    conn, addr = s.accept()
    data = conn.recv(PORT)
    conn.send('Connected to server[%s] from client[%s]\n' % (os.getpid(), addr))
    conn.close()

s.close()

server_v2.py,输出当前时间:

# -*- coding:UTF-8 -*-

import socket
import time
import os

PORT = 10000
BUFSIZE = 1024

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('', PORT))
s.listen(1)

while True:
    conn, addr = s.accept()
    data = conn.recv(PORT)
    conn.send('server[%s] time %s\n' % (os.getpid(), time.ctime()))
    conn.close()

s.close()

借助于bindp运行两个版本的程序:

REUSE_PORT=1 LD_PRELOAD=/opt/bindp/libindp.so python server_v1.py &
REUSE_PORT=1 LD_PRELOAD=/opt/bindp/libindp.so python server_v2.py &

模拟客户端请求10次:

for i in {1..10};do echo "hello" | nc 127.0.0.1 10000;done

看看结果吧:

Connected to server[3139] from client[('127.0.0.1', 48858)]
server[3140] time Thu Feb 12 16:39:12 2015
server[3140] time Thu Feb 12 16:39:12 2015
server[3140] time Thu Feb 12 16:39:12 2015
Connected to server[3139] from client[('127.0.0.1', 48862)]
server[3140] time Thu Feb 12 16:39:12 2015
Connected to server[3139] from client[('127.0.0.1', 48864)]
server[3140] time Thu Feb 12 16:39:12 2015
Connected to server[3139] from client[('127.0.0.1', 48866)]
Connected to server[3139] from client[('127.0.0.1', 48867)]

可以看出来,CPU分配很均衡,各自分配50%的请求量。

嗯,虽是小玩具,有些意思 :))



SO_REUSADDR VS SO_REUSEPORT

因为能力有限,还是有很多东西(SO_REUSEADDR和SO_REUSEPORT的区别等)没有能够在一篇文字中表达清楚,作为补遗,也方便以后自己回过头来复习。

两者不是一码事,没有可比性。有时也会被其搞晕,自己总结的不好,推荐StackOverflow的Socket options SO_REUSEADDR and SO_REUSEPORT, how do they differ?资料,总结的很全面。

简单来说:

  • 设置了SO_REUSADDR的应用可以避免TCP 的 TIME_WAIT 状态 时间过长无法复用端口,尤其表现在应用程序关闭-重启交替的瞬间

  • SO_REUSEPORT更强大,隶属于同一个用户(防止端口劫持)的多个进程/线程共享一个端口,同时在内核层面替上层应用做数据包进程/线程的处理均衡

若有困惑,推荐两者都设置,不会有冲突。

Netty多线程使用SO_REUSEPORT

上一篇讲到SO_REUSEPORT,多个程绑定同一个端口,可以根据需要控制进程的数量。这里讲讲基于Netty 4.0.25+Epoll navtie transport在单个进程内多个线程绑定同一个端口的情况,也是比较实用的。

TCP服务器,同一个进程多线程绑定同一个端口

这是一个PING-PONG示范应用:

     public void run() throws Exception {
            final EventLoopGroup bossGroup = new EpollEventLoopGroup();
            final EventLoopGroup workerGroup = new EpollEventLoopGroup();
            ServerBootstrap b = new ServerBootstrap();

           b.group(bossGroup, workerGroup)
                     .channel(EpollServerSocketChannel. class)
                     .childHandler( new ChannelInitializer<SocketChannel>() {
                            @Override
                            public void initChannel(SocketChannel ch) throws Exception {
                                ch.pipeline().addLast(
                                            new StringDecoder(CharsetUtil.UTF_8 ),
                                            new StringEncoder(CharsetUtil.UTF_8 ),
                                            new PingPongServerHandler());
                           }
                     }).option(ChannelOption. SO_REUSEADDR, true)
                     .option(EpollChannelOption. SO_REUSEPORT, true)
                     .childOption(ChannelOption. SO_KEEPALIVE, true);

            int workerThreads = Runtime.getRuntime().availableProcessors();
           ChannelFuture future;

            //new  thread            for ( int i = 0; i < workerThreads; ++i) {
                future = b.bind( port).await();
                 if (!future.isSuccess())
                      throw new Exception(String. format("fail to bind on port = %d.",
                                 port), future.cause());
           }
           Runtime. getRuntime().addShutdownHook (new Thread(){
                 @Override
                 public void run(){
                     workerGroup.shutdownGracefully();
                     bossGroup.shutdownGracefully();
                }
           });
     }

打成jar包,在CentOS 7下面运行,检查同一个端口所打开的文件句柄。

# lsof -i:8000
COMMAND  PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
java    3515 root   42u  IPv6  29040      0t0  TCP *:irdmi (LISTEN)
java    3515 root   43u  IPv6  29087      0t0  TCP *:irdmi (LISTEN)
java    3515 root   44u  IPv6  29088      0t0  TCP *:irdmi (LISTEN)
java    3515 root   45u  IPv6  29089      0t0  TCP *:irdmi (LISTEN)

同一进程,但打开的文件句柄是不一样的。

UDP服务器,多个线程绑同一个端口
/**
 * UDP谚语服务器,单进程多线程绑定同一端口示范
 */
public final class QuoteOfTheMomentServer {

       private static final int PORT = Integer.parseInt(System. getProperty("port" ,
                   "9000" ));

       public static void main(String[] args) throws Exception {
             final EventLoopGroup group = new EpollEventLoopGroup();

            Bootstrap b = new Bootstrap();
            b.group(group).channel(EpollDatagramChannel. class)
                        .option(EpollChannelOption. SO_REUSEPORT, true )
                        .handler( new QuoteOfTheMomentServerHandler());             int workerThreads = Runtime.getRuntime().availableProcessors();
             for (int i = 0; i < workerThreads; ++i) {
                  ChannelFuture future = b.bind( PORT).await();
                   if (!future.isSuccess())
                         throw new Exception(String.format ("Fail to bind on port = %d.",
                                     PORT), future.cause());
            }

            Runtime. getRuntime().addShutdownHook(new Thread() {
                   @Override
                   public void run() {
                        group.shutdownGracefully();
                  }
            });
      }
}
}

@Sharable
class QuoteOfTheMomentServerHandler extends
            SimpleChannelInboundHandler<DatagramPacket> {

       private static final String[] quotes = {
                   "Where there is love there is life." ,
                   "First they ignore you, then they laugh at you, then they fight you, then you win.",
                   "Be the change you want to see in the world." ,
                   "The weak can never forgive. Forgiveness is the attribute of the strong.", };

       private static String nextQuote() {
             int quoteId = ThreadLocalRandom.current().nextInt( quotes .length );
             return quotes [quoteId];
      }

       @Override
       public void channelRead0(ChannelHandlerContext ctx, DatagramPacket packet)
                   throws Exception {
             if ("QOTM?" .equals(packet.content().toString(CharsetUtil. UTF_8))) {
                  ctx.write( new DatagramPacket(Unpooled.copiedBuffer( "QOTM: "
                              + nextQuote(), CharsetUtil. UTF_8), packet.sender()));
            }
      }

       @Override
       public void channelReadComplete(ChannelHandlerContext ctx) {
            ctx.flush();
      }

       @Override
       public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
            cause.printStackTrace();
      }
}

同样也要检测一下端口文件句柄打开情况:

# lsof -i:9000
COMMAND  PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
java    3181 root   26u  IPv6  27188      0t0  UDP *:cslistener
java    3181 root   27u  IPv6  27217      0t0  UDP *:cslistener
java    3181 root   28u  IPv6  27218      0t0  UDP *:cslistener
java    3181 root   29u  IPv6  27219      0t0  UDP *:cslistener

小结

以上为Netty+SO_REUSEPORT多线程绑定同一端口的一些情况,是为记载。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

socket选项 SO_REUSEPORT 的相关文章

  • Micronaut ReadTimeoutException 异常

    我有一个提供 REST API 的 Grails 4 应用程序 端点之一有时会失败 但会出现以下异常 io micronaut http client exceptions ReadTimeoutException Read Timeout
  • netty源码:(24)EventExecutorChooserFactory类

    该类定义了一个内部接口EventExecutorChooser 该接口有一个方法 next EventExecutorChooserFactory类有个默认实现类 DefaultEventExecutorChooserFactory 该类有
  • netty源码:(28)ChannelPromise

    ChannelPromise是ChannelFuture的子接口 它是可写入的 其父接口Promise定义如下 ChannelPromise有个默认的实现类 DefaultChannelPromise 它的setSuccess方法用来调用所
  • 如何在请求写入代理 Netty 服务器中的 outboundChannel 时在同一处理程序中获取响应 byteBuf

    我正在实现 netty 代理服务器 如下所示 一个http请求进来 如果本地缓存有数据 则写入channel并flush 如果没有 则从远程服务器获取数据 将其添加到缓存并刷新 我在与写入客户端的处理程序相同的处理程序中从响应中提取 byt
  • OOM 使用 320 x 16MB Netty DirectByteBuffer 对象杀死了 JVM

    我在 7 5GB RAM 服务器 无交换 中运行一个应用程序 参数如下 Xmx3g Xms3g Xlog gc XX UseG1GC XX MaxGCPauseMillis 1000 XX MaxDirectMemorySize 500m
  • 如何在 Netty 通道处理程序中安全地执行阻塞操作?

    我正在构建一个基于 Netty 的小型应用程序 该应用程序通过套接字连接 即 telnet ssh 执行 I O 操作 我正在使用 Netty 启动我的套接字服务器ServerBootstrap类 给出 类型的事件循环NioEventLoo
  • 使用 Netty 的 UDP 服务器中丢失大量 UDP 请求

    我用 Netty 编写了一个简单的 UDP 服务器 它只是在日志中打印出收到的消息 帧 为此 我创建了一个简单的帧解码器解码器和一个简单的消息处理程序 我还有一个可以顺序和 或并行发送多个请求的客户端 当我配置我的客户端测试器以顺序发送数百
  • Netty Channel.write 线程安全吗?

    我有一个 Netty 应用程序 我希望有多个线程写入通道 我只是想知道 Channel write 是否线程安全 从代码中可以看出 ChannelOutboundBuffer addMessage 方法本身不是线程安全的 然而 写入通道是
  • 出站 ChannelHandler 的捕获所有异常处理

    在 Netty 中 您有入站和出站处理程序的概念 只需在管道的末尾 尾部 添加一个通道处理程序并实现一个捕获所有入站异常处理程序即可实现exceptionCaught覆盖 如果未沿途处理 沿入站管道发生的异常将沿着处理程序传播 直到遇到最后
  • 每个连接的 Netty 多线程

    我是 Netty 新手 我想开发一个服务器 旨在接收来自可能少数 假设最多有 2 个 客户端的请求 但是每个客户端都会不断地向服务器发送许多请求 服务器必须处理此类请求并响应客户端 因此 在这里我假设即使我配置了多个工作线程 它也可能没有用
  • 泄漏:ByteBuf.release() 在被垃圾收集之前没有被调用。 Spring Reactor TcpServer

    我正在使用reactor core 1 1 0 RELEASE reactor net 1 1 0 RELEASE 正在使用netty all 4 0 18 Final reactor spring context 1 1 0 RELEAS
  • 每个 UDP 数据报的 Netty 不同管道

    我们有一个已经在 TCP IP 中实现的服务器 但现在我们要求该协议也支持 UDP 发送的每个 UDP 数据报都包含我需要解码的所有内容 因此这是一个非常简单的回复和响应系统 数据报中的数据由换行符分隔 服务器启动时的引导代码如下所示 SE
  • netty中非阻塞通道中的SO_TIMEOUT

    如果通道在超时毫秒内未收到读取 响应 SO TIMEOUT 是否会使非阻塞通道过期 bootstrap group workerGroup channel NioSocketChannel class handler channelInit
  • 如何阻止netty在服务器套接字上监听和接受

    有没有办法告诉 netty 停止侦听和接受套接字上的新连接 但完成当前连接上任何正在进行的工作 你可以直接关闭ServerSocketChannel由创建的ChannelFactory 通常 ServerSocketChannel由返回Se
  • Netty 4.0多端口,每个端口有不同的协议

    我想 netty 是我所知道的最好的 java 网络框架 在阅读并尝试一些示例后我有疑问 1 使用netty 4 0创建具有不同协议的多端口网络服务器的最佳方法是什么 每个服务器创建 EventLoopGroup bossGroup new
  • Netty:如何处理从 ChunkedFile 接收到的块

    我是 netty 新手 我正在尝试将分块文件从服务器传输到客户端 发送块工作得很好 问题在于如何处理接收到的块并将它们写入文件 我尝试的两种方法都会给我带来直接缓冲区错误 任何帮助将不胜感激 Thanks Override protecte
  • Spring WebClient:SSLEngine 已关闭

    我们使用 Spring boot 版本 2 3 1 也使用 WebClient 我的网络客户端配置 private val client WebClient init val sslCtx SslContextBuilder forClie
  • Netty通道读取混乱

    我三个月前开始使用 Netty 最初 它看起来非常简单且易于使用 因为我遵循了 4 x 系列主页中给出的示例 当我更深入地探索它时 我无法理解某些事件或回调名称 例如 我无法理解以下内容之间的区别 ChannelRead ChannelHa
  • 了解 netty 通道缓冲区和水印

    我正在尝试了解网络缓冲区和水印 作为一个测试用例 我有一个 netty 服务器 它向客户端写入数据 客户端被阻止 基本上每次读取之间有 10 秒的睡眠时间 在正常 I O 下 如果接收方被阻塞 TCP 发送方将受到限制 由于流量控制 发送速
  • 如何查找以下 netty 错误的根本原因:io.netty.util.internal.OutOfDirectMemoryError: failed to allocate 16777216 byte(s)

    我经历过从负责将数据发送到 TCP 客户端的管道引发的以下异常 2017 03 02T18 00 53 749 epollEventLoopGroup 3 1 ERROR ExceptionHandler null Unknown exce

随机推荐

  • 什么副业可以月赚1万元?做什么副业可以月入上万?

    在当前经济形势下 对于一个普通上班族而言 指望工资生活 日子肯定是过得紧巴巴的 许多人想谋求一份副业收入很正常 那么 在当前社会上 有哪些副业项目 能一个月收入一万多呢 我这里给大家推荐几个 仅供用于调研参考 1 自媒体 也许很多人会说这个
  • 决策树详解(一)

    1 决策树的概念 决策树算法以树状结构表示数据分类的结果 每个决策点实现一个具有离散输出的测试函数 记为分支 决策树的元素有 根节点 非叶子节点 分支 叶节点四种元素 其代表的含义如下图所示 决策树的工作分为两个阶段 1 训练阶段 给定训练
  • 生成以太坊系地址

    代码解析 要首先生成一个新的钱包地址 我们需要导入go ethereum crypto包 该包提供用于生成随机私钥的GenerateKey方法 privateKey err crypto GenerateKey if err nil log
  • 再论KVM超量使用

    转载自 http www sohu com a 111248295 251444 KVM超量使用一直是热门话题 前段时间发的文章 群讨论 虚拟机能否使用32个CPU 又引去了群友的激烈讨论 本文为群友根据自己的经验总结投稿 感谢这位热心的群
  • 华为 ospf 报文及邻居状态有限机

    华为 IP 路由基础 ospf 报文及邻居状态有限机 ospf协议邻居建立 一 ospf的工作机制 建立邻居 发送数据库信息 计算出最短路径 hello报文 用来建立邻居和维护邻居 邻居发现 是自动发现邻居路由器 使用的组播的地址224 0
  • docker 安装

    前提 开发环境为虚拟机Ubuntu 16 04 更换国内镜像 虚拟机是刚刚安装的 需要先更换成国内镜像源 1 首先备份原始源文件 sudo cp etc apt sources list etc apt sources list bak 2
  • RS推荐系统-LSH最近邻查找+MiniHash

    什么是最近邻查找 在推荐系统中 主要分为召回跟排序两个阶段 召回阶段 基于用户画像及场景数据从海量的视频库 百万级别 中将相关度最高的资源检索出来 作为候选集 召回阶段可以通过 粗糙 的方式召回候选item 排序阶段 基于更加精细的特征对候
  • Aspose实现word、excel、ppt转pdf

    1 工具类 AsposeUtil Component Slf4j public class AsposeUtil private static final String WORD doc docx wps wpt txt private s
  • 布隆过滤器及其实现

    简介 Bloom Filter 是由布隆 Burton Howard Bloom 在1970年提出的 loom Filter是一种空间效率很高的随机数据结构 它实际上是由一个很长的二进制向量和一系列随机映射函数组成 布隆过滤器可以用于可以快
  • kubernetes高可用集群安装(二进制安装、v1.20.2版)

    1 前言 之前文章安装 kubernetes 集群 都是使用 kubeadm 安装 然鹅很多公司也采用二进制方式搭建集群 这篇文章主要讲解 如何采用二进制包来搭建完整的高可用集群 相比使用 kubeadm 搭建 二进制搭建要繁琐很多 需要自
  • 修复Unity空白报错问题

    修复Unity空白报错问题 在升级Unity Hub之后 偶然发现Console里有几行空白的报错 看不到任何信息 由于有报错 导致修改代码无法生效 尝试重启项目 重装Unity都完全没效果 而且就算新建一个空白项目 只要添加代码就会立刻报
  • Linux操作系统原理—内核网络协议栈

    前言 本文主要记录 Linux 内核网络协议栈的运行原理 数据报文的封装与分用 封装 当应用程序用 TCP 协议传送数据时 数据首先进入内核网络协议栈中 然后逐一通过 TCP IP 协议族的每层直到被当作一串比特流送入网络 对于每一层而言
  • CASE WHEN 及 SELECT CASE WHEN的用法

    Case具有两种格式 简单Case函数和Case搜索函数 简单Case函数 CASE sex WHEN 1 THEN 男 WHEN 2 THEN 女 ELSE 其他 END Case搜索函数 CASE WHEN sex 1 THEN 男 W
  • 批量保存数据

    批量保存 批量保存 参数为对象集合 br 条件 对象属性与数据库字段完全对应 允许使用大骆驼拼写法 br 2018年4月28日下午5 30 53 throws Exception private String insert00000 Lis
  • BandZIP无广告版(v6.25)安装及禁止联网设置

    安装和设置 1 下载和安装 bandzip版本v6 25以前的都是免费且无广告的 我们从网上找到6 25版本的进行下载安装 双击 如下图进行设置 点击同意并安装 安装过程中取消自动更新 其他设置默认 安装完成后 虽然不会自动更新 但每次使用
  • C# EasyModbus xktComm Modbus 例子

    转载请注明出处 联系我 田工 15118249062 微信同号 当然先要在NuGet按照相应的dll 不是ModbusRTU报文 在RTU报文前面加了4个字节 transactionIdentifier protocolIdentifier
  • 什么是Scrum?Scrum的核心要点和精髓

    有点长 期望你能通过本文彻底了解 Scrum 我们介绍了一个非常有意思且高效的组织模式 特性团队 我们首先介绍了为什么需要特性团队 特性团队的定义 核心价值 优势 可能存在的问题以及带来的成本 接着讲述了特性团队的适用范围 开发新产品 拓展
  • alertdialog 自定义样式回调选手_把 Node.js 中的回调转换为 Promise

    每日前端夜话 第431篇 正文共 2300 字 预计阅读时间 7 分钟 介绍 在几年前 回调是 JavaScript 中实现执行异步代码的唯一方法 回调本身几乎没有什么问题 最值得注意的是 回调地狱 在 ES6 中引入了 Promise 作
  • C++中的单例模式

    单例模式也称为单件模式 单子模式 可能是使用最广泛的设计模式 其意图是保证一个类仅有一个实例 并提供一个访问它的全局访问点 该实例被所有程序模块共享 有很多地方需要这样的功能模块 如系统的日志输出 GUI应用必须是单鼠标 MODEM的联接需
  • socket选项 SO_REUSEPORT

    摘要 多核与网络IO 目录 前言 本篇用于记录学习SO REUSEPORT的笔记和心得 末尾还会提供一个bindp小工具也能为已有的程序享受这个新的特性 当前Linux网络应用程序问题 运行在Linux系统上网络应用程序 为了利用多核的优势