分布式系统通信解决方案:Netty 与 Protobuf 高效应用

分布式系统通信解决方案:Netty 与 Protobuf 高效应用

一、引言

在现代网络编程中,数据的编解码是系统设计的一个核心问题,特别是在高并发和低延迟的应用场景中,如何高效地序列化和传输数据对于系统的性能至关重要。随着分布式系统和微服务架构的广泛应用,跨平台、高效的序列化方案变得愈加重要。

Protocol Buffers(简称 Protobuf)是 Google 开发的一个高效的序列化工具。它能够将结构化数据序列化为紧凑的二进制格式,具有以下显著优势:

  1. 高效紧凑:Protobuf 使用二进制格式,比传统的 JSON 和 XML 更紧凑,占用更少的存储空间,传输速度更快,适用于高负载、高频繁的网络通信。
  2. 跨语言支持:Protobuf 支持多种编程语言,包括 Java、Python、C++、Go 等,使得它在异构系统之间传输数据时具有极好的兼容性。
  3. 灵活性强:Protobuf 支持复杂的数据结构,如嵌套对象、列表以及可选和重复字段,使得开发者可以灵活定义数据格式,满足不同的业务需求。

随着 Netty 成为构建高性能网络服务的标准框架之一,如何将 Protobuf 与 Netty 高效结合,利用它们的优势实现快速、高效的网络通信,成为了很多开发者关心的课题。本文将深入探讨如何在 Netty 中使用 Protobuf 实现高效的数据编解码,并通过具体的代码示例演示其应用。


二、Protobuf 的基础知识

2.1 什么是 Protobuf?

Protobuf 是一种语言无关、平台无关的数据序列化协议。它通过定义 .proto 文件来描述数据结构,然后使用 Protobuf 编译器(protoc)生成特定语言的代码来实现数据的序列化与反序列化操作。

Protobuf 提供了一种高效、紧凑的方式来存储和交换数据,与传统的 JSON 和 XML 相比,它更加节省带宽和存储空间,特别适用于高并发、低延迟的网络通信场景。

2.2 Protobuf 的优点

  • 效率高:
    Protobuf 使用二进制格式序列化数据,比 JSON 和 XML 格式更紧凑。解析速度也更快,在大规模的数据传输和存储中具有明显的性能优势。
  • 跨平台支持:
    Protobuf 支持多种编程语言,包括 Java、Python、C++、Go 等,能够在不同平台和技术栈之间无缝传输数据。这使得它在异构系统的集成中非常有用。
  • 结构清晰:
    .proto 文件提供了一种简单、清晰的数据描述方式,所有的数据结构都可以在 .proto 文件中明确地定义。开发者只需关注业务逻辑,而不必过多关心底层的序列化和反序列化细节。
  • 易扩展性:
    Protobuf 支持向现有消息结构中添加新字段而不影响旧的消息解析,这为系统的演进和扩展提供了极大的灵活性。

2.3 Protobuf 的基本工作流程

Protobuf 的使用流程非常简单:

  1. 定义 .proto 文件:描述数据结构(如消息类型、字段名称和数据类型)。
  2. 生成代码:使用 Protobuf 编译器(protoc)将 .proto 文件编译成对应语言的代码。
  3. 序列化与反序列化:在应用程序中,使用生成的代码进行数据的序列化和反序列化。

这种工作流使得 Protobuf 成为在分布式系统、微服务架构和跨平台通信中,处理数据交换的理想选择。


三、在 Netty 中使用 Protobuf

Netty 提供了对 Protobuf 的原生支持,主要通过以下编解码器实现:

  1. ProtobufEncoder: 将消息序列化为二进制数据。
  2. ProtobufDecoder: 将二进制数据反序列化为 Protobuf 对象。

此外,为了解决 TCP 拆包与黏包问题,Netty 中通常配合使用 LengthFieldBasedFrameDecoderLengthFieldPrepender


四、Protobuf 在 Netty 中的基础应用

下面通过一个完整的示例展示如何在 Netty 中结合 Protobuf 实现客户端与服务端的数据传输。

1. Protobuf 工具与 Java 8 的兼容性

Protobuf 编译器(protoc 生成的代码与 Java 运行时兼容。由于 Java 8 是较旧的版本,需要确保以下两点:

  1. 选择的 Protobuf 编译器生成的代码与 Java 8 的字节码兼容。
  2. Protobuf 的运行时库(protobuf-java)版本与生成代码保持一致,并支持 Java 8。

2. 工具包版本推荐

对于 Java 8,可以使用以下 Protobuf 版本:

  • Protobuf 编译器(protoc): 推荐使用 3.19.x 或更早的版本。这些版本生成的代码默认是兼容 Java 8 的。
  • 运行时库(protobuf-java): 确保与 Protobuf 编译器版本一致,例如 3.19.x

Protobuf 3.20.x 及更高版本生成的代码可能默认使用 Java 11 特性(如模块化支持),因此对于 Java 8 不再完全兼容。


3. 下载 Protobuf 编译器

  1. 下载链接:

    • 从 Protobuf Releases 页面选择版本(推荐 3.19.x 或更早版本)。
    • Protobuf 3.19.4 版本

    在这里插入图片描述

    • 下载与操作系统对应的预编译二进制文件(protoc-3.x.x-[platform].zip)。
    • 解压后将 protoc 可执行文件的路径加入系统环境变量。
新建项目

在这里插入图片描述

引入依赖

在 Maven 项目中添加以下依赖:

<dependencies>
    <!-- Netty 核心依赖 -->
    <dependency>
        <groupId>io.netty</groupId>
        <artifactId>netty-all</artifactId>
        <version>4.1.97.Final</version>
    </dependency>

    <!-- Netty Protobuf 编解码支持 -->
    <dependency>
        <groupId>io.netty</groupId>
        <artifactId>netty-codec-protobuf</artifactId>
        <version>4.1.97.Final</version>
    </dependency>

    <!-- Protobuf 运行时库 -->
    <dependency>
        <groupId>com.google.protobuf</groupId>
        <artifactId>protobuf-java</artifactId>
        <version>3.19.4</version> <!-- 与 Protobuf 编译器版本匹配 -->
    </dependency>

    <!-- Protobuf 编译插件(如果需要通过 Maven 编译 .proto 文件) -->
    <dependency>
        <groupId>org.xolstice.maven.plugins</groupId>
        <artifactId>protobuf-maven-plugin</artifactId>
        <version>0.6.1</version>
    </dependency>
</dependencies>

4.2 定义 Protobuf 消息

在使用 Protocol Buffers(Protobuf)时,所有数据的定义都需要通过 .proto 文件进行描述。Protobuf 是一种语言无关的、平台无关的序列化数据结构的方式,能够在不同编程语言之间传输数据。在 Protobuf 中,数据通过消息(message)类型定义,每个字段都有类型和唯一的标识符。

什么是 .proto 文件?

.proto 文件是 Protobuf 的定义文件,其中定义了消息类型、字段的名称、数据类型以及字段的编号等信息。它是 Protobuf 数据序列化和反序列化的基础。每个消息类型可以包含多个字段,每个字段有一个编号,Protobuf 会根据这个编号在序列化时决定字段的顺序。

.proto 文件中,你需要遵循一定的语法规则来定义数据结构。Protobuf 支持多种数据类型,包括基本类型(如 int32stringbool 等)以及复合类型(如 messageenumrepeated)。

Protobuf 文件示例

以下是拆分后的两个 .proto 文件:

  1. inventory_request.proto
syntax = "proto3";

package com.example.protobuf;
option java_outer_classname = "InventoryRequestModel";
// InventoryRequest 消息定义
message InventoryRequest {
    string product_id = 1; // 产品 ID
    int32 quantity = 2;    // 请求的数量
    string operation = 3;  // 操作类型:add 或 remove
}

在此文件中,我们定义了一个名为 InventoryRequest 的消息类型:

  • product_id:表示产品的唯一标识符,类型为 string
  • quantity:表示请求的数量,类型为 int32
  • operation:表示操作类型,可能的值有 addremove,类型为 string
  1. inventory_response.proto
syntax = "proto3";

package com.example.protobuf;
option java_outer_classname = "InventoryResponseModel";
// InventoryResponse 消息定义
message InventoryResponse {
    string product_id = 1; // 产品 ID
    bool success = 2;      // 操作是否成功
    string message = 3;    // 响应消息
}

在此文件中,我们定义了一个名为 InventoryResponse 的消息类型:

  • product_id:表示产品的唯一标识符,类型为 string
  • success:表示操作是否成功,类型为 bool
  • message:表示响应消息的详细信息,类型为 string

如何生成 Java 类

通过运行 protoc 编译器,我们可以根据 .proto 文件生成对应的 Java 类。这些类将用于 Java 应用程序中与 Protobuf 消息进行交互。

使用以下命令生成对应的 Java 类:

protoc -I=D:\code\java\myproject\netty-003\src\main\java --java_out=D:\code\java\myproject\netty-003\src\main\java\ D:\code\java\myproject\netty-003\src\main\java\com\example\protobuf\*.proto

此命令做了以下几件事:

  • -I=D:\code\java\myproject\netty-003\src\main\java:指定 .proto 文件的根目录。
  • --java_out=D:\code\java\myproject\netty-003\src\main\java\:指定生成的 Java 类的输出目录。
  • D:\code\java\myproject\netty-003\src\main\java\com\example\protobuf\*.proto:指定要编译的 .proto 文件路径,可以使用通配符 *.proto 来一次性编译多个 .proto 文件。

Protobuf 官方文档

更多关于 Protobuf 文件语法、类型、字段规则等内容,可以参考 Protobuf 的官方文档:

  • Protobuf 官方文档地址:
    https://developers.google.com/protocol-buffers

在文档中,你可以深入了解如何定义不同的数据类型、字段规则以及 Protobuf 的高级用法。


4.3 服务端实现

服务端处理逻辑(Handler):
package com.example.protobuf;
import com.example.protobuf.InventoryRequestModel.InventoryRequest;
import com.example.protobuf.InventoryResponseModel.InventoryResponse;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;


public class ProtobufServerHandler extends SimpleChannelInboundHandler<InventoryRequest> {
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, InventoryRequest request) {
        System.out.println("收到客户端请求:" + request);

        // 模拟库存处理逻辑
        boolean success = "add".equals(request.getOperation()) || "remove".equals(request.getOperation());
        String message = success ? "操作成功!" : "操作失败,未知操作类型:" + request.getOperation();

        // 构造响应对象
        InventoryResponse response = InventoryResponse.newBuilder()
                .setProductId(request.getProductId())
                .setSuccess(success)
                .setMessage(message)
                .build();

        // 发送响应
        ctx.writeAndFlush(response);
    }

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

服务端启动类:
package com.example.protobuf;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.LengthFieldPrepender;
import io.netty.handler.codec.protobuf.ProtobufDecoder;
import io.netty.handler.codec.protobuf.ProtobufEncoder;


public class ProtobufServer {
    public static void main(String[] args) throws Exception {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) {
                            ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(65536, 0, 4, 0, 4));
                            ch.pipeline().addLast(new ProtobufDecoder(InventoryRequestModel.InventoryRequest.getDefaultInstance()));
                            ch.pipeline().addLast(new LengthFieldPrepender(4));
                            ch.pipeline().addLast(new ProtobufEncoder());
                            ch.pipeline().addLast(new ProtobufServerHandler());
                        }
                    });

            ChannelFuture future = bootstrap.bind(8080).sync();
            System.out.println("服务端已启动,端口:8080");
            future.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}


4.4 客户端实现

客户端处理逻辑(Handler):
package com.example.protobuf;

import io.netty.channel.ChannelHandlerContext;
import com.example.protobuf.InventoryRequestModel.InventoryRequest;
import com.example.protobuf.InventoryResponseModel.InventoryResponse;
import io.netty.channel.SimpleChannelInboundHandler;

import java.nio.charset.StandardCharsets;


public class ProtobufClientHandler extends SimpleChannelInboundHandler<InventoryResponse> {
    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        InventoryRequest request = InventoryRequest.newBuilder()
                .setProductId("P12345")
                .setQuantity(10)
                .setOperation("add")
                .build();

        ctx.writeAndFlush(request);
        System.out.println("客户端已发送请求:" + request);
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, InventoryResponse response) {
        System.out.println("收到服务端响应:" + response);
//        System.out.println(new String(response.getMessage().getBytes(StandardCharsets.UTF_8)));
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        cause.printStackTrace();
        ctx.close();
    }
}
客户端启动类:
package com.example.protobuf;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.LengthFieldPrepender;
import io.netty.handler.codec.protobuf.ProtobufDecoder;
import io.netty.handler.codec.protobuf.ProtobufEncoder;

public class ProtobufClient {
    public static void main(String[] args) throws Exception {
        EventLoopGroup group = new NioEventLoopGroup();

        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(group)
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) {
                            ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(65536, 0, 4, 0, 4));
                            ch.pipeline().addLast(new ProtobufDecoder(InventoryResponseModel.InventoryResponse.getDefaultInstance()));
                            ch.pipeline().addLast(new LengthFieldPrepender(4));
                            ch.pipeline().addLast(new ProtobufEncoder());
                            ch.pipeline().addLast(new ProtobufClientHandler());
                        }
                    });

            ChannelFuture future = bootstrap.connect("localhost", 8080).sync();
            future.channel().closeFuture().sync();
        } finally {
            group.shutdownGracefully();
        }
    }
}

五、总结

在本篇文章中,我们深入探讨了如何在 Netty 中结合 Protobuf 实现高效的数据编解码。通过详细的代码示例,我们展示了如何利用 Protobuf 轻松进行数据序列化与反序列化,并在 Netty 的高性能网络通信中应用。

1. Protobuf 的优势与应用场景

Protobuf 作为一种高效的二进制序列化协议,具有以下显著优势:

  • 高效紧凑:通过紧凑的二进制格式,Protobuf 可以显著减少带宽消耗和存储需求,在高负载、高频繁的网络通信中表现尤为突出。
  • 跨平台支持:Protobuf 支持多种编程语言,能够在不同语言和平台之间无缝传输数据,特别适合分布式系统和微服务架构中异构系统的数据交换。
  • 灵活扩展:Protobuf 提供灵活的结构扩展机制,能够在不破坏现有系统的情况下,向消息中添加新的字段,保证系统的平稳演化。

这些优势使得 Protobuf 成为高效数据传输的首选,特别是在大规模、高并发、低延迟的应用场景中,如分布式系统、实时数据传输、微服务架构等。

2. Netty 与 Protobuf 的结合

Netty 是一个高性能的网络框架,适用于处理大量并发连接和高效数据传输。通过将 NettyProtobuf 结合,开发者可以在保证高性能的同时,还能有效地进行数据序列化和反序列化。结合 ProtobufEncoderProtobufDecoder,数据的传输效率大大提升,特别是当需要传输大量结构化数据时。

Netty 提供了原生支持,简化了 Protobuf 的使用,只需通过简单的编码解码器配置,就可以实现 Protobuf 消息的高效传输。此外,Netty 的 LengthFieldBasedFrameDecoderLengthFieldPrepender 解码器,帮助我们解决了 TCP 拆包黏包的问题,确保消息完整性和传输的可靠性。

3. 实践中的建议

在实际开发中,结合 NettyProtobuf 的使用,可以进一步优化网络服务的性能:

  • 性能调优:根据业务需求,可以调整 Protobuf 的编解码策略,例如通过压缩数据来减少带宽占用;在高并发场景下,可以使用 Protobuf 的压缩选项来进一步提高传输效率。
  • 多种协议的结合:除了 Protobuf,Netty 还支持其他协议(如 JSON、XML、Thrift 等),你可以根据不同的应用场景,选择适合的数据格式进行组合。
  • 错误处理与安全:在处理实际应用时,务必考虑错误处理和安全性。例如,使用适当的验证机制来防止恶意数据注入,并确保网络连接的安全性(如使用 SSL/TLS 加密)。

4. 高效的跨平台通信

通过本篇博客,你已经学会了如何使用 ProtobufNetty 实现高效、可扩展的跨平台网络通信。无论是在微服务架构中,还是在大规模的分布式系统中,利用这两者的结合,都能够实现高效的消息传递,保证系统的高并发和低延迟特性。

5. 后续学习与扩展

如果你希望进一步优化系统的性能,或在更复杂的场景中使用 NettyProtobuf,可以从以下几个方向进行学习和扩展:

  • Protobuf 高级特性:深入学习 Protobuf 的更多高级特性,如自定义序列化和反序列化逻辑、扩展机制等。
  • Netty 高级用法:学习 Netty 更高级的特性,例如自定义协议处理、事件驱动模型的优化、流量控制等。
  • 性能优化:根据实际需求,结合负载均衡、数据压缩和缓存机制等技术,进一步提高系统的吞吐量和响应速度。

通过不断实践和优化,你将能够构建更加高效、灵活和可扩展的网络服务。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C++《AVL树》

在之前的学习当中我们已经了解了二叉搜索树&#xff0c;并且我们知道二叉搜索树的查找效率是无法满足我们的要求&#xff0c;当二叉树为左或者右斜树查找的效率就很低下了&#xff0c;那么这本篇当中我们就要来学习对二叉搜索树进行优化的二叉树——AVL树。在此会先来了解AVL树…

ToDesk设置临时密码和安全密码都可以当做连接密码使用

ToDesk 在各领域办公都已经是非常常见了 为了安全 ToDesk 设置了连接密码&#xff0c;想连接 需要输入远程码和连接密码 我们刚打开 系统默认给我们用的是临时密码&#xff0c;安全性确实很强 和定时Tokey一样&#xff0c;固定时间切换。 但是 如果我们要经常连接这个电脑&a…

LLMs(大型语言模型)的多智能体:Auto-GPT

LLMs(大型语言模型)的多智能体:Auto-GPT 是指在一个系统中集成多个具有不同能力、角色和任务的智能体,这些智能体能够相互协作、沟通和交互,以共同完成复杂的任务或解决复杂的问题。每个智能体都可以被视为一个独立的实体,具有自己的策略、目标和知识库,通过相互之间的…

【Linux环境变量与命令行参数】常见环境变量 | 环境变量的全局属性 | 命令行参数

前言 本文中主要介绍PATH、HOME、SHELL、HISTSIZE这4个环境变量&#xff0c;其中详细介绍PATH。并理解环境变量的全局属性--环境变量可以被子进程继承&#xff0c;这里要注意和C中的继承进行区分。其次&#xff0c;介绍命令行参数--mian函数的参数。 1.环境变量的基本概念 在…

【Python】函数(二)

链式调用 # 判定是否是奇数 def isOdd(num):if num % 2 0:return Falseelse:return Trueresult isOdd(10) print(result)实际上也可以简化写作 print(isOdd(10))把一个函数的返回值, 作为另一个函数的参数, 这种操作称为 链式调用 嵌套调用 函数内部还可以调用其他的函数…

【Elasticsearch 】 聚合分析:桶聚合

&#x1f9d1; 博主简介&#xff1a;CSDN博客专家&#xff0c;历代文学网&#xff08;PC端可以访问&#xff1a;https://literature.sinhy.com/#/?__c1000&#xff0c;移动端可微信小程序搜索“历代文学”&#xff09;总架构师&#xff0c;15年工作经验&#xff0c;精通Java编…

学习ASP.NET Core的身份认证(基于JwtBearer的身份认证7)

本文验证基于请求头中传递token信息的认证方式&#xff0c;webapi项目的控制器类中新建如下函数&#xff0c;仅通过验证的客户端能调用&#xff0c;需要客户端请求在Header中添加’Authorization’: Bearer token’的键值对且通过token验证后才能调用。 [Authorize] [HttpGet]…

游戏AI,让AI 玩游戏有什么作用?

让 AI 玩游戏这件事远比我们想象的要早得多。追溯到 1948 年&#xff0c;图灵和同事钱伯恩共同设计了国际象棋程序 Turochamp。之所以设计这么个程序&#xff0c;图灵是想说明&#xff0c;机器理论上能模拟人脑能做的任何事情&#xff0c;包括下棋这样复杂的智力活动。 可惜的是…

9. 神经网络(一.神经元模型)

首先&#xff0c;先看一个简化的生物神经元结构&#xff1a; 生物神经元有多种类型&#xff0c;内部也有复杂的结构&#xff0c;但是可以把单个神经元简化为3部分组成&#xff1a; 树突&#xff1a;一个神经元往往有多个树突&#xff0c;用于接收传入的信息。轴突&#xff1a;…

Docker可视化管理工具Portainer

Portainer简介 Portainer 是一个轻量级的、开源的容器管理工具&#xff0c;提供了一个直观的 Web 用户界面&#xff08;UI&#xff09;&#xff0c;用于管理 Docker 和 Kubernetes 环境。它简化了容器的部署、监控和管理&#xff0c;特别适合不熟悉命令行操作的用户或团队。 …

maven helper插件使用

在intellij idea插件市场搜索maven help并安装安装好后会多一个Dependency Analyzer 场景1&#xff1a;排除某个依赖 点击完exclude pom文件会被修改 然后刷新下maven&#xff0c;相应的依赖就会被排除了。

【VRChat · 改模】Unity2019、2022的版本选择哪个如何决策,功能有何区别;

总览 1.Unity2019、2022的版本的选择 2.Unity添加着色器教程 一、Unity2019、2022的版本的选择 1.Unity2019 和 Unity2022 的区别&#xff0c;VRChat SDK 为何要区分两个版本 我是外行&#xff0c;最开始以为的是&#xff0c;2019 和 2022 的变化是基于这个模型本身的。 也…

RHCE实验详解

目录 实验分析 环境拓扑结构 项目需求 主机环境描述 实验步骤 一、密钥互信和主机名更改 二、DNS 三、NGINX 四、MARIADB 五、NFS 六、NTP 七、论坛服务 结果展示及痛点解答 实验分析 环境拓扑结构 项目需求 1. 172.25.250.101 主机上的 Web 服务要求提供 www.ex…

学Python的人…

学Python的人… 一、Python能干什么&#xff1f; 1.爬虫&#xff1a;前几年&#xff0c;深度学习还没发展起来的时候&#xff0c;书店里Python就和爬虫挂钩&#xff0c;因为Python写爬虫确实方便。 2.数据分析&#xff1a;Python有各种的数据分析库可以方便使用&#xff0…

docker 安装 mysql 详解

在平常的开发工作中&#xff0c;我们经常需要用到 mysql 数据库。那么在docker容器中&#xff0c;应该怎么安装mysql数据库呢。简单来说&#xff0c;第一步&#xff1a;拉取镜像&#xff1b;第二步&#xff1a;创建挂载目录并设置 my.conf&#xff1b;第三步&#xff1a;启动容…

华为E9000刀箱服务器监控指标解读

美信监控易内置了数千种常见设备监测器&#xff0c;能够监测超过20万项指标。这些指标涵盖了从硬件设备到软件系统&#xff0c;从网络性能到安全状态等各个方面。如下基于美信监控易——IT基础监控模块&#xff0c;对华为E9000刀箱服务器部分监控指标进行解读。 一、华为E9000…

自动化标注平台开源,基于 yolov8标注平台可本地部署

yolov8标注平台本地部署&#xff08;docker部署&#xff09;&#xff0c;已调通yolov8模型自动预标注功能。 下面开始背景知识…… 1&#xff09;数据标注为什么在人工智能时代如此重要&#xff1f; 数据标注在人工智能时代如此重要&#xff0c;原因如下&#xff1a; 为机器…

PyTorch使用教程(6)一文讲清楚torch.nn和torch.nn.functional的区别

torch.nn 和 torch.nn.functional 在 PyTorch 中都是用于构建神经网络的重要组件&#xff0c;但它们在设计理念、使用方式和功能上存在一些显著的区别。以下是关于这两个模块的详细区别&#xff1a; 1. 继承方式与结构 torch.nn torch.nn 中的模块大多数是通过继承 torch.nn…

海思Hi3516CV610 -----芯片说明

Hi3516CV610这颗超高清智慧视觉SoC芯片是由海思技术有限公司推出的&#xff0c;其首发量产的时间是在2024年4。标志着海思正式回归安防市场&#xff0c;并在IPC SoC市场中展开竞争。 关键特性 ●4K20&#xff0c;6M30分辨率 ●双目实时接入&#xff0c;支撑枪球一体机等双目机…

iOS-支付相关

支付宝支付 #import <AlipaySDK/AlipaySDK.h> //orderStrAliPay为服务端传的订单信息 //fromScheme为应用配置的schemeUrl标识&#xff0c;用户支付包支付成功后跳转会本应用内 //callback回调需要在- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url 中调…