Netty优化

文章目录

    • 概述
    • 优化方法
    • 性能篇
      • 网络参数优化
      • 业务线程池的必要性
      • 共享 ChannelHandler
      • 设置高低水位线
      • GC 参数优化
      • 线程绑定
    • 高可用篇
      • 连接空闲检测
      • 流量整形
      • 堆外内存泄漏排查思路
      • Netty 自带检测工具
      • 二分排查法:笨方法解决大问题

概述

netty 是一种异步的、基于事件驱动的网络框架,它在高性能和可伸缩性方面表现出色。然而,在实际开发中,为了进一步提高 Netty 性能和可靠性,需要进行以下的优化:

  1. 适当调整 I/O 线程池大小:I/O 线程池的大小应该根据系统资源和需求来适当调整。如果设置太小,可能会导致线程饥饿或阻塞;如果设置太大,可能会浪费系统资源。
  2. 使用 Direct ByteBuffers:使用 Direct ByteBuffers 可以避免将内存从 JVM 移动到操作系统内核空间的额外复制,从而提高效率。
  3. 压缩数据传输:可以使用压缩算法(如 gzip、Snappy)来减少数据传输量,从而提高性能。但需要注意,压缩算法有时会对 CPU 和内存造成额外负担。
  4. 避免频繁 GC:频繁的垃圾回收会影响性能,因此应尽量避免创建大量临时对象。可以使用对象池技术(如 Netty 自带的 Recycler)来重用对象,从而减少 GC 的次数。
  5. 使用 ChannelPipeline:ChannelPipeline 是 Netty 中处理 I/O 事件的核心机制,应该根据具体的业务需求来构建和优化 ChannelPipeline,以提高系统性能。
  6. 使用 TCP_NODELAY 选项:TCP_NODELAY 选项可以避免发送小数据包时的延迟,从而提高吞吐量。但是,使用这个选项有时会导致网络拥塞,应该谨慎使用。
  7. 合理使用线程池:对于 CPU 密集型的操作,应该使用适当数量的线程池。对于 I/O 密集型的操作,应该使用更大的线程池,以便在等待 I/O 操作时能够处理其他任务。
    总之,为了进一步提高 Netty 性能和可靠性,需要综合考虑系统资源、业务需求、网络拓扑结构等因素,并根据具体情况进行优化。

优化方法

ChannelOption参数优化
业务线程池的必要性

性能篇

网络参数优化

Netty 提供了 ChannelOption 以便于我们优化 TCP 参数配置,为了提高网络通信的吞吐量,一些可选的网络参数我们有必要掌握。
SO_SNDBUF/SO_RCVBUF
TCP 发送缓冲区和接收缓冲区的大小。
为了能够达到最大的网络吞吐量,SO_SNDBUF 不应当小于带宽和时延的乘积。SO_RCVBUF 一直会保存数据到应用进程读取为止,如果 SO_RCVBUF 满了,接收端会通知对端 TCP 协议中的窗口关闭,保证 SO_RCVBUF 不会溢出。
SO_SNDBUF/SO_RCVBUF 大小的设置建议参考消息的平均大小,不要按照最大消息来进行设置,这样会造成额外的内存浪费。更灵活的方式是可以动态调整缓冲区的大小,这时候就体现出 ByteBuf 的优势,Netty 提供的 ByteBuf 是可以支持动态调整容量的,而且提供了开箱即用的工具,例如可动态调整容量的接收缓冲区分配器 AdaptiveRecvByteBufAllocator。
TCP_NODELAY
是否开启 Nagle 算法。
● Nagle 算法通过缓存的方式将网络数据包累积到一定量才会发送,从而避免频繁发送小的数据包。
● Nagle 算法 在海量流量的场景下非常有效,但是会造成一定的数据延迟。
● 如果对数据传输延迟敏感,那么应该禁用该参数。
SO_BACKLOG
已完成三次握手的请求队列最大长度。
同一时刻服务端可能会处理多个连接,在高并发海量连接的场景下,该参数应适当调大。
但是 SO_BACKLOG 也不能太大,否则无法防止 SYN-Flood 攻击。
SO_KEEPALIVE
连接保活。
启用了 TCP SO_KEEPALIVE 属性,TCP 会主动探测连接状态,Linux 默认设置了 2 小时的心跳频率。
TCP KEEPALIVE 机制主要用于回收死亡时间交长的连接,不适合实时性高的场景。
在海量连接的场景下,也许你会遇到类似 “too many open files” 的报错,所以 Linux 操作系统最大文件句柄数基本是必须要调优参数。可以通过 vi /etc/security/limits.conf,添加如下配置:

  • soft nofile 1000000
  • hard nofile 1000000
    修改保存以后,执行 sysctl -p 命令使配置生效,然后通过 ulimit -a 命令查看参数是否生效。

业务线程池的必要性

Netty 是基于 Reactor 线程模型实现的,I/O 线程数量固定且资源珍贵,ChannelPipeline 负责所有事件的传播,如果其中任何一个 ChannelHandler 处理器需要执行耗时的操作,其中那么 I/O 线程就会出现阻塞,甚至整个系统都会被拖垮。
所以推荐的做法是在 ChannelHandler 处理器中自定义新的业务线程池,将耗时的操作提交到业务线程池中执行。
以 RPC 框架为例,在服务提供者处理 RPC 请求调用时就是将 RPC 请求提交到自定义的业务线程池中执行,如下所示:

public class RpcRequestHandler extends SimpleChannelInboundHandler<MiniRpcProtocol<MiniRpcRequest>> {@Overrideprotected void channelRead0(ChannelHandlerContext ctx, MiniRpcProtocol<MiniRpcRequest> protocol) {RpcRequestProcessor.submitRequest(() -> {// 处理 RPC 请求});}}

共享 ChannelHandler

我们经常使用以下 new HandlerXXX() 的方式进行 Channel 初始化,在每建立一个新连接的时候会初始化新的 HandlerA 和 HandlerB,如果系统承载了 1w 个连接,那么就会初始化 2w 个处理器,造成非常大的内存浪费。

ServerBootstrap b = new ServerBootstrap();b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).localAddress(new InetSocketAddress(port)).childHandler(new ChannelInitializer<SocketChannel>() {@Overridepublic void initChannel(SocketChannel ch) {ch.pipeline().addLast(new HandlerA()).addLast(new HandlerB());}});

为了解决上述问题,Netty 提供了 @Sharable 注解用于修饰 ChannelHandler,标识该 ChannelHandler 全局只有一个实例,而且会被多个 ChannelPipeline 共享。
所以我们必须要注意的是,@Sharable 修饰的 ChannelHandler 必须都是无状态的,这样才能保证线程安全。

设置高低水位线

高低水位线 WRITE_BUFFER_HIGH_WATER_MARK 和 WRITE_BUFFER_LOW_WATER_MARK 是两个非常重要的流控参数。
Netty 每次添加数据时都会累加数据的字节数,然后判断缓存大小是否超过所设置的高水位线,如果超过了高水位,那么 Channel 会被设置为不可写状态。直到缓存的数据大小低于低水位线以后,Channel 才恢复成可写状态。
Netty 默认的高低水位线配置是 32K ~ 64K,可以根据发送端和接收端的实际情况合理设置高低水位线,如果你没有足够的测试数据作为参考依据,建议不要随意更改高低水位线。
高低水位线的设置方式如下:

// ServerServerBootstrap bootstrap = new ServerBootstrap();bootstrap.childOption(ChannelOption.WRITE_BUFFER_HIGH_WATER_MARK, 32 * 1024);bootstrap.childOption(ChannelOption.WRITE_BUFFER_LOW_WATER_MARK, 8 * 1024);// ClientBootstrap bootstrap = new Bootstrap();bootstrap.option(ChannelOption.WRITE_BUFFER_HIGH_WATER_MARK, 32 * 1024);bootstrap.option(ChannelOption.WRITE_BUFFER_LOW_WATER_MARK, 8 * 1024);

当缓存超过了高水位,Channel 会被设置为不可写状态,调用 isWritable() 方法会返回 false。
建议在 Channel 写数据之前,使用 isWritable() 方法来判断缓存水位情况,防止因为接收方处理较慢造成 OOM。
推荐的使用方式如下:

if (ctx.channel().isActive() && ctx.channel().isWritable()) {ctx.writeAndFlush(message);} else {// handle message}

GC 参数优化

对不同场景下的网络应用程序进行 JVM 参数调优,可以取得很好的性能提升,以及避免 OOM 风险。
因为不同业务系统的特性是不一样的,在此我只能给你分享一些重要的注意事项。
堆内存
-Xms 和 -Xmx 参数
● -Xmx 用于控制 JVM Heap 的最大值,必须设置其大小,合理调整 -Xmx 有助于降低 GC 开销,提升系统吞吐量。
● -Xms 表示 JVM Heap 的初始值,对于生产环境的服务端来说 -Xms 和 -Xmx 最好设置为相同值。
堆外内存
DirectByteBuffer 最容易造成 OOM 的情况,DirectByteBuffer 对象的回收需要依赖 Old GC 或者 Full GC 才能触发清理。
如果长时间没有 Old GC 或者 Full GC 执行,那么堆外内存即使不再使用,也会一直在占用内存不释放。
我们最好通过 JVM 参数 -XX:MaxDirectMemorySize 指定堆外内存的上限大小,当堆外内存的大小超过该阈值时,就会触发一次 Full GC 进行清理回收,如果在 Full GC 之后还是无法满足堆外内存的分配,那么程序将会抛出 OOM 异常。
年轻代
-Xmn
● 调整新生代大小,-XX:SurvivorRatio 设置 SurvivorRatio 和 eden 区比例。
● 我们经常遇到 YGC 频繁的情况,应该清楚程序中对象的基本分布情况,如果存在大量朝生夕灭的对象,应适当调大新生代;反之应适当调大老年代。
● 例如在类似百万长连接、推送服务等延迟敏感的场景中,老年代的内存增长缓慢,优化年轻代的空间大小以及各区的比例可以带来更大的收益。
内存池 & 对象池
从内存分配的角度来看,ByteBuf 可以分为堆内存 HeapByteBuf 和堆外内存 DirectByteBuf。DirectByteBuf 相比于 HeapByteBuf,虽然分配和回收的效率较慢,但是在 Socket 读写时可以少一次内存拷贝,性能更佳。
为了减少堆外内存的频繁创建和销毁,Netty 提供了池化类型的 PooledDirectByteBuf。Netty 提前申请一块连续内存作为 ByteBuf 内存池,如果有堆外内存申请的需求直接从内存池里获取即可,使用完之后必须重新放回内存池,否则会造成严重的内存泄漏。Netty 中启用内存池可以在创建客户端或者服务端的时候指定,示例代码如下:
bootstrap.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
bootstrap.childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
对象池与内存池的都是为了提高 Netty 的并发处理能力,通常在项目开发中我们会将一些通用的对象缓存起来,当需要该对象时,优先从对象池中获取对象实例。通过重用对象,不仅避免频繁地创建和销毁所带来的性能损耗,而且对 JVM GC 是友好的。
如果你是一个高性能的网络应用系统,不妨试下 Netty 提供的 Recycler 对象池。
Netty 内存池和 Recycler 对象池优化的核心目标都是为了减少资源分配的开销,避免大量朝生夕灭的对象造成严重的内存消耗和 GC 压力。
Native 支持
从 4.0.16 版本起,Netty 提供了用 C++ 编写 JNI 调用的 Socket Transport,相比 JDK NIO 具备更高的性能和更低的 GC 成本,并且支持更多的 TCP 参数。

<dependency><groupId>io.netty</groupId><artifactId>netty-transport-native-epoll</artifactId><version>4.1.42.Final</version></dependency>
使用 Netty Native 非常简单,只需要替换相应的类即可:

线程绑定

如果是经常关注系统性能调优,一定挖掘过 Linux 操作系统 CPU 亲和性的黑科技招数。
CPU 亲和性是指在多核 CPU 的机器上线程可以被强制运行在某个 CPU 上,而不会调度到其他 CPU,也被称为绑核。当绑定线程到某个固定的 CPU 后,不仅可以避免 CPU 切换的开销,而且可以提高 CPU Cache 命中率,对系统性能有一定提升。
在 C/C++、Golang 中实现绑核操作是非常容易的事,遗憾的是在 Java 中是比较麻烦的。目前 Java 中有一个开源 affinity 类库,GitHub 地址https://github.com/OpenHFT/Java-Thread-Affinity。如果你的项目想引入使用它,需要先引入 Maven 依赖:

<dependency><groupId>net.openhft</groupId><artifactId>affinity</artifactId><version>3.0.6</version></dependency>

affinity 类库可以和 Netty 轻松集成,比较常用的方式是创建一个 AffinityThreadFactory,然后传递给 EventLoopGroup,AffinityThreadFactory 负责创建 Worker 线程并完成绑核。代码实现如下所示:
EventLoopGroup bossGroup = new NioEventLoopGroup(1);

ThreadFactory threadFactory = new AffinityThreadFactory(“worker”, AffinityStrategies.DIFFERENT_CORE);

EventLoopGroup workerGroup = new NioEventLoopGroup(4, threadFactory);

ServerBootstrap serverBootstrap = new ServerBootstrap().group(bossGroup, workerGroup);

高可用篇

连接空闲检测

指每隔一段时间检测连接是否有数据读写,如果服务端一直能收到客户端连接发送过来的数据,说明连接处于活跃状态,对于假死的连接是收不到对端发送的数据的。如果一段时间内没收到客户端发送的数据,并不能说明连接一定处于假死状态,有可能客户端就是长时间没有数据需要发送,但是建立的连接还是健康状态,所以服务端还需要通过心跳检测的机制判断客户端是否存活。
心跳检测
客户端可以定时向服务端发送一次心跳包,如果有 N 次没收到心跳数据,可以判断当前客户端已经下线或处于不健康状态。由此可见,连接空闲检测和心跳检测是应对连接假死的一种有效手段,通常空闲检测时间间隔要大于 2 个周期的心跳检测时间间隔,主要是为了排除网络抖动的造成心跳包未能成功收到。
TCP 中已经有 SO_KEEPALIVE 参数,为什么我们还要在应用层加入心跳机制呢?
心跳机制不仅能说明应用程序是活跃状态,更重要的是可以判断应用程序是否还在正常工作。
然而 TCP KEEPALIVE 是有严重缺陷的,KEEPALIVE 设计初衷是为了清除和回收处于死亡状态的连接,实时性不高。
KEEPALIVE 只能检查连接是否活跃,但是不能判断连接是否可用,例如服务端如果处于高负载假死状态,但是连接依然是处于活跃状态的。
解码器保护
Netty 在实现数据解码时,需要等待到缓冲区有足够多的字节才能开始解码。为了避免缓冲区缓存太多数据造成内存耗尽,我们可以在解码器中设置一个最大字节的阈值,然后结合 Netty 提供的

TooLongFrameException 异常通知 ChannelPipeline 中其他 ChannelHandler。示例如下:
public class MyDecoder extends ByteToMessageDecoder {private static final int MAX_FRAME_LIMIT = 1024;@Overridepublic void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {int readable = in.readableBytes();if (readable > MAX_FRAME_LIMIT) {in.skipBytes(readable);throw new TooLongFrameException("too long frame");}// decode}}

检测缓冲区可读字节是否大于 MAX_FRAME_LIMIT,如果超过忽略这些可读字节,对于应用程序在特定的场景下是一种有效的保护措施。
线程池隔离
我们知道,如果有复杂且耗时的业务逻辑,推荐的做法是在 ChannelHandler 处理器中自定义新的业务线程池,将耗时的操作提交到业务线程池中执行。
建议根据业务逻辑的核心等级拆分出多个业务线程池,如果某类业务逻辑出现异常造成线程池资源耗尽,也不会影响到其他业务逻辑,从而提高应用程序整体可用率。
对于 Netty I/O 线程来说,每个 EventLoop 可以与某类业务线程池绑定,避免出现多线程锁竞争。如下图所示:

流量整形

流量整形(Traffic Shaping)是一种主动控制服务流量输出速率的措施,保证下游服务能够平稳处理。
流量整形和流控的区别在于,流量整形不会丢弃和拒绝消息,无论流量洪峰有多大,它都会采用令牌桶算法控制流量以恒定的速率输出,如下图所示。

Netty 通过实现流量整形的抽象类 AbstractTrafficShapingHandler,
提供了三种类型的流量整形策略:
● GlobalTrafficShapingHandler
● ChannelTrafficShapingHandler
● GlobalChannelTrafficShapingHandler
它们之间的关系如下:
GlobalTrafficShapingHandler = ChannelTrafficShapingHandler + GlobalChannelTrafficShapingHandler
● 全局流量整形 GlobalChannelTrafficShapingHandler 作用范围是所有 Channel,用户可以设置全局报文的接收速率、发送速率、整形周期。
● Channel 级流量整形 ChannelTrafficShapingHandler 作用范围是单个 Channel,可以对不同的 Channel 设置流量整形策略。
○ 举个简单的例子
■ 火爆的旅游景区不仅在大门口对游客限流(相当于 GlobalChannelTrafficShapingHandler)
■ 而且在景区内部不同的小景点也对游客有限流(相当于 ChannelTrafficShapingHandler)
■ 这两个流量整形策略加起来就是 GlobalTrafficShapingHandler。
流量整形并不能保证系统处于安全状态,当流量洪峰过大,数据会一直积压在内存中,所以流量整形和流控应该结合使用才能保证系统的高可用。

堆外内存泄漏排查思路

堆外内存泄漏问题是 Netty 应用程序的热点问题,经常遇到 Java 进程占用内存很高,但是堆内存并不高的情况。这里给你分享一些排查堆外内存泄漏的基本思路:
堆外内存回收
jmap -histo:live 手动触发 FullGC, 观察堆外内存是否被回收,如果正常回收很可能是因为堆外设置太小,可以通过 -XX:MaxDirectMemorySize 调整。当然这无法排除堆外内存缓慢泄漏的情况,需要借助其他工具进行分析。
堆外内存代码监控
JDK 默认采用 Cleaner 回收释放 DirectByteBuffer,Cleaner 继承于 PhantomReference,因为依赖 GC 进行处理,所以回收的时间是不可控的。对于 hasCleaner 的 DirectByteBuffer,Java 提供了一系列不同类型的 MXBean 用于获取 JVM 进程线程、内存等监控指标,代码实现如下:
BufferPoolMXBean directBufferPoolMXBean = ManagementFactory.getPlatformMXBeans(BufferPoolMXBean.class).get(0);

LOGGER.info(“DirectBuffer count: {}, MemoryUsed: {} K”, directBufferPoolMXBean.getCount(), directBufferPoolMXBean.getMemoryUsed()/1024);
对于 Netty 中 noCleaner 的 DirectByteBuffer,直接通过 PlatformDependent.usedDirectMemory() 读取即可。

Netty 自带检测工具

Netty 提供了自带的内存泄漏检测工具,我们可以通过以下命令启用堆外内存泄漏检测工具:
-Dio.netty.leakDetection.level=paranoid
Netty 一共提供了四种检测级别:

  1. disabled,关闭堆外内存泄漏检测;
  2. simple,以 1% 的采样率进行堆外内存泄漏检测,消耗资源较少,属于默认的检测级别;
  3. advanced,以 1% 的采样率进行堆外内存泄漏检测,并提供详细的内存泄漏报告;
  4. paranoid,追踪全部堆外内存的使用情况,并提供详细的内存泄漏报告,属于最高的检测级别,性能开销较大,常用于本地调试排查问题。
    Netty 会检测 ByteBuf 是否已经不可达且引用计数大于 0,判定内存泄漏的位置并输出到日志中,你需要关注日志中 LEAK 关键字。
    MemoryAnalyzer 内存分析
    我们可以通过传统 Dump 内存的方法排查堆外内存泄漏问题,运行如下命令:
    jmap -dump:format=b,file=heap.dump pid
    Dump 完内存堆栈之后,将其导入 MemoryAnalyzer 工具进行分析内存泄漏的可疑点,最终定位到代码源头。
    Btrace 神器
    Btrace 是一款通过字节码检测 Java 程序的排障神器,它可以获取程序在运行过程中的一切信息,与 AOP 的使用方式类似。我们可以通过如下方式追踪 DirectByteBuffer 的堆外内存申请的源头:
    @BTrace
    public class TraceDirectAlloc {
    @OnMethod(clazz = “java.nio.Bits”, method = “reserveMemory”)
    public static void printThreadStack() {
    jstack();
    }
    }

二分排查法:笨方法解决大问题

堆外内存泄漏问题有时候非常隐蔽,并不是很容易定位发现。为了提高问题排查的效率,我们最好能够在本地模拟复现出堆外内存泄漏问题,如果本地能够成功复现,那么已经成功了一半了。
我们可以根据近期代码变更的记录,通过二分法对代码进行回滚,然后再次尝试是否可以复现出堆外内存泄漏问题,最终可以定位出有问题的代码 commit。该思路虽然是一种笨方法,但是很多场景下可以有效解决问题。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/742952.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Elastic boosting的使用

boosting介绍 Boosting查询允许您降低与负面查询匹配的文档的相关性评分 boosting语法 GET /_search {"query": {"boosting": {"positive": {"term": {"text": "apple"}},"negative": {"term&q…

如何拆解技术瓶颈的难点

以大化小的思路 解决一个一个小问题从而解决最终问题 三段论&#xff1a; 抽象能力 职责领域划分 分层构建解决方案 案例&#xff1a;全局分布式事务的解决方案 抽象能力&#xff1a;全局分布式 是由一个个小的事务组合而成的&#xff0c;其中一个分布式事务出现问题&#xff…

亚马逊Bedrock引领生成式AI创新,Claude 3模型家族开启新时代

近日&#xff0c;亚马逊宣布其云计算平台亚马逊Bedrock已成为构建和扩展基于大型语言模型&#xff08;LLM&#xff09;和其他基础模型&#xff08;FMs&#xff09;的生成式AI应用的最佳平台。Anthropic公司开发的Claude模型家族&#xff0c;作为高性能FMs的代表&#xff0c;正在…

探索考古文字场景,基于YOLOv8全系列【n/s/m/l/x】参数模型开发构建文本考古场景下的甲骨文字符图像检测识别系统

甲骨文是一种非常历史悠久的古老文字&#xff0c;在前面我们基本上很少有涉及这块的内容&#xff0c;最近正好在做文字相关的项目开发研究&#xff0c;就想着基于甲骨文的场景来开发对应的检测识别系统&#xff0c;在前文中我们基于YOLOv5、YOLOv7和YOLOv9开发构建了在仿真数据…

激活函数Mish

paper&#xff1a;Mish: A Self Regularized Non-Monotonic Activation Function official implementation&#xff1a;https://github.com/digantamisra98/Mish 背景 在早期文献中&#xff0c;Sigmoid和TanH激活函数被广泛使用&#xff0c;随后在深度神经网络中失效。相比于…

Redis 创建群时报错 Node XXX is not empty

在创建 Redis 集群时报错[ERR] Node XXX is not empty. Either the node already knows other nodes (check with CLUSTER NODES) or contains some key in database 0. 主要原因是 RDB 或者 AOF 文件中有数据&#xff0c;redis集群搭建的时候需要所有节点都为 空&#xff0c;不…

【组件初始化链条】简化Unity组件的初始化

简介 在游戏脚本中我们通过借助GetComponent或TryGetComponent方法获取组件&#xff0c;所以当需要获取较多组件时&#xff0c;我们不可避免地要书写一些重复代码&#xff0c;为了提升代码简洁程度&#xff0c;简化组件初始化逻辑&#xff0c;本文以"组件初始化链条"…

Springboot的配置文件及其优先级

配置文件 内置配置文件 配置文件的作用&#xff1a;修改SpringBoot自动配置的默认值&#xff1b;SpringBoot在底层都给我们自动配置好&#xff1b;SpringBoot使用一个全局的配置文件&#xff0c;配置文件名是固定的&#xff1a; application.propertiesapplication.yml 以上…

网络建设与运维培训介绍和能力介绍

1.开过的发票 3.培训获奖的证书 4合同签署 5.实训设备

利用 boost::asio::ssl C/C++ 检查SSL/PEM证书文件的有效性

我们可以通过 boost::asio::ssl::context &#xff08;SSL上下文&#xff09;对象实例成员接口来检查SSL证书文件的有效性。 1、use_certificate_chain_file 使用证书链文件&#xff08;CA*&#xff09; 2、use_certificate_file 使用证书文件&#xff08;公钥&am…

[ThinkPHP]Arr返回1

$detailId (int)Arr::get($detail, null); var_dump($detailId); 打印结果&#xff1a;int(1) 原因&#xff1a; vendor/topthink/think-helper/src/helper/Arr.php

干洗店管理系统洗鞋店预约上门小程序洗护流程;

干洗店洗鞋店收银管理系统&#xfe63;智能线上预约洗衣店小程序软件; 闪站侠洗衣洗鞋店收银管理系统&#xff0c;一款集进销存、收衣、收银、会员管理等实用功能于一体的洗护管理软件&#xff0c;适用于各大中小型企业个体工商户&#xff0c;功能强大&#xff0c;操作简单&…

瑞_23种设计模式_命令模式

文章目录 1 命令模式&#xff08;Command Pattern&#xff09;1.1 介绍1.2 概述1.3 命令模式的结构1.4 命令模式的优缺点1.5 命令模式的使用场景 2 案例一2.1 需求2.2 代码实现 3 案例二3.1 需求3.2 代码实现 4 JDK源码解析&#xff08;Runable&#xff09; &#x1f64a; 前言…

【机器学习智能硬件开发全解】(二)—— 政安晨:嵌入式系统基本素养【处理器原理】

嵌入式系统的基本素养包括以下几个方面&#xff1a; 硬件知识&#xff1a;嵌入式系统通常由硬件和软件组成&#xff0c;了解和熟悉硬件的基本知识&#xff0c;包括微处理器、存储器、外设等&#xff0c;并了解它们的工作原理和特性。 软件编程&#xff1a;熟悉至少一种编程语言…

人工智能迷惑行为大赏——需求与科技的较量

目录 前言 一、 机器行为学 二、人工智能迷惑行为的现象 三、产生迷惑行为的技术原因 四、社会影响分析 五、解决措施 总结 前言 随着ChatGPT热度的攀升&#xff0c;越来越多的公司也相继推出了自己的AI大模型&#xff0c;如文心一言、通义千问等。各大应用也开始内置…

WPF图表库LiveCharts的使用

这个LiveCharts非常考究版本&#xff0c;它有非常多个版本&#xff0c;.net6对应的是LiveChart2 我这里的wpf项目是.net6&#xff0c;所以安装的是这三个&#xff0c;搜索的时候要将按钮“包括愈发行版”打勾 git&#xff1a;https://github.com/beto-rodriguez/LiveCharts2?…

webpack面试题

1、webpack是干什么的 Webpack是一个现代的JavaScript应用程序的静态模块打包工具。当webpack处理应用程序时&#xff0c;它会在内部构建一个依赖图&#xff0c;此依赖图对应映射到项目所需的每个模块&#xff0c;然后将所有这些模块打包成一个或多个bundle。Webpack的主要功能…

趣学前端 | 平平无奇的JavaScript函数

背景 最近睡前习惯翻会书&#xff0c;重温了《JavaScript权威指南》。这本书&#xff0c;文字小&#xff0c;内容多。两年了&#xff0c;我才翻到第十章。因为书太厚&#xff0c;平时都充当电脑支架。 JavaScript函数 读这章之前&#xff0c;我感觉我三十年开发功力&#xf…

经典卷积神经网络LeNet-5、AlexNet、VGG-16

一、LeNet-5 这里只讲一下C5&#xff0c;卷积核大小是5*5&#xff0c;通道数是120&#xff0c;所以卷积完成之后是1*1*120&#xff0c;这里形成120个卷积结果。每个都与上一层的16个图相连。所以共有(5x5x161)x120 48120个参数&#xff0c;同样有48120个连接。 其他卷积层和池…

Maven: There are test failures.(已解决)

问题解决办法 进行package打包时报错如下&#xff1a; 然后这些并不能看出是测试的哪里的问题&#xff0c;可以点击上一级进行查看更详细的错误&#xff0c;越向上日志越详细&#xff0c;可以看到是52行出了错误&#xff0c; 52对应代码如下&#xff1a; 原因是存在注册的测…