从零开始学习Netty - 学习笔记 -Netty入门【协议设计和解析】

news2024/11/24 19:43:03

2.协议设计和解析

协议
在计算机中,协议是指一组规则和约定,用于在不同的计算机系统之间进行通信和数据交换。计算机协议定义了数据传输的格式、顺序、错误检测和纠正方法,以及参与通信的各个实体的角色和责任。计算机协议可以在各种不同的层次上操作,包括物理层、数据链路层、网络层、传输层、会话层、表示层和应用层。

以下是一些常见的计算机协议:

  1. 传输层协议:例如TCP (Transmission Control Protocol) 和UDP (User Datagram Protocol),用于在网络上可靠地传输数据。
  2. 网络层协议:例如IP (Internet Protocol),负责在网络上寻址和路由数据包。
  3. 应用层协议:例如HTTP (Hypertext Transfer Protocol)、FTP (File Transfer Protocol)、SMTP (Simple Mail Transfer Protocol) 等,用于支持特定的应用程序和服务。
  4. 数据链路层协议:例如Ethernet、PPP (Point-to-Point Protocol) 等,用于在物理网络之间传输数据帧。

2.1.redis协议

Redis 使用一种简单而有效的文本协议进行通信,这种协议被称为 RESP(REdis Serialization Protocol)。RESP 是一种二进制安全的协议,它可以将多种类型的数据结构序列化为字节流进行传输,并且允许客户端和服务器之间进行高效的通信。

下面是 RESP 协议的一些基本规则:

  1. 简单字符串(Simple Strings):以 “+” 开头,后面跟着字符串内容和回车换行符 “\r\n”。例如:+OK\r\n 表示一个成功的响应。
  2. 错误消息(Errors):以 “-” 开头,后面跟着错误消息内容和回车换行符 “\r\n”。例如:-ERR unknown command 'foobar'\r\n 表示一个错误的响应。
  3. 整数(Integers):以 “:” 开头,后面跟着整数内容和回车换行符 “\r\n”。例如::1000\r\n 表示整数 1000。
  4. 批量字符串(Bulk Strings):以 “$” 开头,后面跟着字符串的长度(以字节为单位)、字符串内容和回车换行符 “\r\n”。例如:$6\r\nfoobar\r\n 表示一个长度为 6 的字符串 “foobar”。
  5. 数组(Arrays):以 “*” 开头,后面跟着数组的长度和数组的元素,每个元素都可以是任意 RESP 类型。例如:*2\r\n$3\r\nfoo\r\n$3\r\nbar\r\n 表示一个包含两个元素的数组,分别是字符串 “foo” 和字符串 “bar”。

在实际的通信中,客户端发送命令给 Redis 服务器,并等待服务器的响应。客户端发送的命令遵循 RESP 协议的格式,而服务器返回的响应也是 RESP 格式的。

这种简单而灵活的 RESP 协议使得 Redis 能够高效地处理各种数据类型和命令,并在性能和易用性之间找到了平衡。

代码

package com.hrfan.java_se_base.netty.protocol;

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.logging.LoggingHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.invoke.MethodHandles;
import java.nio.charset.Charset;

/**
 * @author 13723
 * @version 1.0
 * 2024/3/3 0:03
 */
public class TestRedisProtocol {
	private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());

	public static void main(String[] args) {
		// 测试redis协议
		NioEventLoopGroup worker = new NioEventLoopGroup();
		try {
			Bootstrap bootstrap = new Bootstrap();
			bootstrap.channel(NioSocketChannel.class);
			bootstrap.group(worker);
			bootstrap.handler(new ChannelInitializer<SocketChannel>() {
				@Override
				protected void initChannel(SocketChannel channel) throws Exception {
					channel.pipeline().addLast(new LoggingHandler());
					channel.pipeline().addLast(new ChannelInboundHandlerAdapter() {
						/**
						 * 连接一旦建立就发送命令
						 * @param ctx
						 * @throws Exception
						 */

						@Override
						public void channelActive(ChannelHandlerContext ctx) throws Exception {
							// 如果redis 有密码 需要先发送一个auth命令
							//
							// 发送 AUTH 命令进行认证
							// String authCommand = "*2\r\n$4\r\nauth\r\n$5\r\n12345\r\n";
							// ByteBuf authBuffer = ctx.alloc().buffer();
							// authBuffer.writeBytes(authCommand.getBytes());
							// ctx.writeAndFlush(authBuffer);
							// 发送一个连接建立的命令
							// redis 协议是一种文本协议 以 \r\n 作为结束符 以$开头的是长度  以*开头的是数组
							// 例如 *3\r\n$3\r\nset\r\n$4\r\nname\r\n$8\r\nhrfan\r\n
							// 表示一个数组 有三个元素  第一个元素是set 第二个元素是name 第三个元素是hrfan
							// 也就是执行 set name hrfan
							String command = "*3\r\n$3\r\nset\r\n$4\r\nname\r\n$5\r\nhrfan\r\n";
							ByteBuf buffer = ctx.alloc().buffer();
							buffer.writeBytes(command.getBytes());
							ctx.writeAndFlush(buffer);
						}

						@Override
						public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
							super.channelRead(ctx, msg);
							// redis 接收到结果  肯定会返回信息 +OK\r\n
							ByteBuf byteBuf = (ByteBuf) msg;
							String string = byteBuf.toString(Charset.defaultCharset());
							logger.info("redis 返回的结果是:{}", string);
						}
					});
				}
			});
			// 和redis建立连接
			ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 6379).sync();
			channelFuture.channel().closeFuture().sync();
		} catch (InterruptedException e) {
			logger.error("client error !",e);
		}finally {
			worker.shutdownGracefully();
		}
	}
}

image-20240303011539551

image-20240303011603698

2.2.HTTP协议

HTTP(Hypertext Transfer Protocol,超文本传输协议)是一种用于传输超媒体文档(例如 HTML)的应用层协议,是互联网上数据传输的基础。

HTTP的特点:

  1. 无连接
    • HTTP 协议是无连接的,即每个请求都是独立的,服务器处理完请求后即断开连接,因此每个请求需要单独建立连接和断开连接,无法复用连接,导致了额外的开销。
  2. 无状态
    • HTTP 协议是无状态的,即服务器不会保存客户端的请求信息,每个请求之间没有关联,服务器无法知道当前请求与之前的请求是否相关。
    • 为了实现状态保持,引入了 Cookie 和 Session 机制。
  3. 简单快速
    • HTTP 协议基于请求-响应模型,简单易懂,通信速度较快。
    • 由于 HTTP 协议的简单性,使得它被广泛应用于 Web 数据传输。
  4. 灵活性
    • HTTP 协议允许传输任意类型的数据对象,不限于文本数据,也可以传输图片、视频、音频等多媒体数据。
  5. 无安全性
    • HTTP 协议是明文传输的,数据传输过程中不对数据进行加密处理,容易被窃听、篡改或伪造,因此不适合传输敏感数据。

HTTP请求/响应的基本结构:

  1. 请求结构
    • 请求行:包括请求方法(GET、POST 等)、请求 URI 和 HTTP 版本号。
    • 请求头部:包括客户端信息、请求资源信息、支持的压缩方法等。
    • 请求正文:传输请求相关的数据。
  2. 响应结构
    • 状态行:包括 HTTP 版本号、状态码和状态描述。
    • 响应头部:包括服务器信息、响应时间、响应内容类型等。
    • 响应正文:包含响应的实际数据。

HTTP的方法(请求方式):

  1. GET:用于请求指定的资源。
  2. POST:用于提交数据,常用于提交表单数据。
  3. PUT:用于上传指定的 URI 表示的内容。
  4. DELETE:用于删除指定的资源。
  5. HEAD:与 GET 类似,但服务器只返回响应头部,不返回实际内容。
  6. OPTIONS:用于请求目标资源所支持的通信选项。
  7. TRACE:用于测试目的,回显服务器收到的请求,主要用于诊断。

HTTP状态码:

  1. 1xx(信息):请求已接收,继续处理。
  2. 2xx(成功):请求已成功被服务器接收、理解、并接受。
  3. 3xx(重定向):需要客户端采取进一步的操作才能完成请求。
  4. 4xx(客户端错误):请求包含语法错误或无法完成请求。
  5. 5xx(服务器错误):服务器在处理请求的过程中发生了错误。

HTTP持久连接:

HTTP/1.1 引入了持久连接(Persistent Connection)机制,使得可以在同一连接上发送和接收多个 HTTP 请求和响应,减少了连接建立和断开的开销,提高了性能。

/**
 * @author 13723
 * @version 1.0
 * 2024/3/3 0:03
 */
public class TestHttpProtocol {
	private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());

	public static void main(String[] args) {
		// 测试HTTP协议
		NioEventLoopGroup boss = new NioEventLoopGroup();
		NioEventLoopGroup worker = new NioEventLoopGroup();

		try {
			ServerBootstrap bootstrap = new ServerBootstrap();
			bootstrap.channel(NioServerSocketChannel.class);
			bootstrap.group(boss,worker);
			bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
				@Override
				protected void initChannel(SocketChannel channel) throws Exception {
					channel.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));
					channel.pipeline().addLast(new HttpServerCodec());
					// 对编解码的请求结果进行处理
					channel.pipeline().addLast(new ChannelInboundHandlerAdapter(){
						@Override
						public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
							super.channelRead(ctx, msg);
							// 此时打开浏览器 输入localhost:8080 会看到请求的信息
							logger.error("获取的信息:{}",msg);
						}
					});
				}
			});
			// 建立和http之间的连接
			ChannelFuture channelFuture = bootstrap.bind(9999).sync();
			channelFuture.channel().closeFuture().sync();
		} catch (InterruptedException e) {
			logger.error("client error !",e);
		}finally {
			worker.shutdownGracefully();
			boss.shutdownGracefully();
		}
	}
}

image-20240304212730612

但是这个信息是一个http请求的信息 但是http请求 会分为请求头和请求体
会默认发送两次请求 一次是请求头 一次是请求体
所以我们需要对请求头和请求体进行处理
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    super.channelRead(ctx, msg);
    // 此时打开浏览器 输入localhost:8080 会看到请求的信息
    logger.error("获取的信息:{}",msg);
    // 但是这个信息是一个http请求的信息 但是http请求 会分为请求头和请求体
    // 会默认发送两次请求 一次是请求头 一次是请求体
    // 所以我们需要对请求头和请求体进行处理
    if (msg instanceof HttpRequest){
        HttpRequest request = (HttpRequest) msg;
        logger.error("请求头:{}",request.headers());
    }else if (msg instanceof HttpContent){
        HttpContent content = (HttpContent) msg;
        ByteBuf buf = content.content();
        logger.error("请求体:{}",buf.toString(Charset.defaultCharset()));
    }
}

image-20240304213137183

还可以使用 添加指定处理器 处理特定的内容

SimpleChannelInboundHandler 它可根据消息的类型进行选择处理,例如我们只关心HttpRequest类型的消息,Netty会自动帮你进行转换 你不需要进行类型转换

// 对请求头和请求体进行处理 我们还可以使用SimpleChannelInboundHandler
// 它可根据消息的类型进行选择处理,例如我们只关心HttpRequest类型的消息
// 他会自动帮你进行转换 你不需要进行类型转换
channel.pipeline().addLast(new SimpleChannelInboundHandler<HttpRequest>() {
    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, HttpRequest httpRequest) throws Exception {
        logger.error("请求信息:{}",httpRequest);
        // 向浏览器返回响应
        // netty提供一个响应对象
        // 符合http协议的响应对象 第一个参数 时http协议的版本 第二个参数是响应的状态码
        DefaultFullHttpResponse response = new DefaultFullHttpResponse(httpRequest.protocolVersion(), HttpResponseStatus.OK);

        // 向浏览器写入一些内容
        byte[] bytes = "<h1>hello world</h1>".getBytes();
        response.content().writeBytes(bytes);
        // 设置响应头 否则浏览器会一直等待 告知箱体
        response.headers().setInt(HttpHeaderNames.CONTENT_LENGTH,bytes.length);
        // 设置响应头的类型
        response.headers().set(HttpHeaderNames.CONTENT_TYPE,"text/html;charset=utf-8");
        // 写入响应
        channelHandlerContext.writeAndFlush(response);
    }
});

image-20240304214047954

image-20240304214058259

2.3.自定义协议

自定义协议要素

  • 魔改 (Magic Number)
    • 用于第一时间判定是否是有效数据包,通常是一个固定的字节序列或者数字,用来标识该数据包是符合自定义协议的。
    • 例如,可以是一个特定的字节序列,如 0x7E 0x7E。
  • 版本号 (Protocol Version)
    • 用于支持协议的升级,可以在协议中包含一个字段来表示协议的版本号。
    • 这样可以在协议升级时识别和处理不同版本的协议。
  • 序列化算法 (Serialization Algorithm)
    • 用于消息正文的序列化和反序列化,可以支持多种序列化算法,如 JSON、Protobuf、Hessian、JDK 自带的序列化等。
    • 这样可以根据需求选择最适合的序列化算法来进行数据的编码和解码。
  • 指令类型 (Instruction Type)
    • 用来表示消息的类型,与业务相关,包括登录、注册、单聊、群聊等操作。
    • 可以使用一个字段来标识不同的指令类型,以便在接收方根据指令类型进行相应的业务处理。
  • 请求序号 (Request Sequence Number)
    • 用于实现双工通信和提供异步能力,每个请求都有一个唯一的序号。
    • 接收方在处理请求后,可以通过该序号将响应与请求进行关联。
  • 消息正文长度和消息正文
    • 消息正文长度字段用于表示消息正文的长度,以便在解析消息时可以正确地读取到消息的内容。
    • 消息正文则是实际的数据内容,根据指令类型和业务需求可以是不同格式的数据,例如文本、二进制、结构化数据等。

定义一个简单的自定义协议,协议由两部分组成:消息类型和消息内容。消息类型用一个字节表示,消息内容是一个字符串。

消息格式:消息类型字节消息类型字节 消息内容长度字节消息内容长度字节 消息内容消息内容

  1. MessageType (消息类型):一个字节,0表示心跳消息,1表示业务消息。
  2. MessageContentLength (消息内容长度):4个字节,表示消息内容的长度。
  3. MessageContent (消息内容):消息内容的字节数组。

编码器

1.自定义消息信息的枚举类型
  • MessageType 枚举定义了两种消息类型:心跳消息和业务消息,分别用 0 和 1 表示。
public enum MessageType {
    HEARTBEAT((byte) 0),
    BUSINESS((byte) 1);

    private final byte value;

    MessageType(byte value) {
        this.value = value;
    }

    public byte getValue() {
        return value;
    }

    public static MessageType valueOf(byte value) {
        for (MessageType type : values()) {
            if (type.value == value) {
                return type;
            }
        }
        throw new IllegalArgumentException("Invalid MessageType value: " + value);
    }
}
2.自定义协议消息类
  • MyProtocolMessage 类表示一个自定义协议消息,包括消息类型和消息内容。
public class MyProtocolMessage {
    private MessageType type;
    private String content;

    public MyProtocolMessage(MessageType type, String content) {
        this.type = type;
        this.content = content;
    }

    public MessageType getType() {
        return type;
    }

    public String getContent() {
        return content;
    }
}
3.自定义协议的编码器
  • 继承自 Netty 的 MessageToByteEncoder 类,负责将 MyProtocolMessage 编码成字节流。
  • 将消息类型、消息内容长度和消息内容依次写入 ByteBuf 中。
public class MyProtocolEncoder extends MessageToByteEncoder<MyProtocolMessage> {

    @Override
    protected void encode(ChannelHandlerContext ctx, MyProtocolMessage msg, ByteBuf out) throws Exception {
        // 写入消息类型
        out.writeByte(msg.getType().getValue());

        // 获取消息内容的字节数组
        byte[] contentBytes = msg.getContent().getBytes(StandardCharsets.UTF_8);

        // 写入消息内容长度
        out.writeInt(contentBytes.length);

        // 写入消息内容
        out.writeBytes(contentBytes);
    }
}
4.自定义协议的解码器
  • 继承自 Netty 的 ByteToMessageDecoder 类,负责将字节流解码成 MyProtocolMessage 对象。
  • 读取字节流中的消息类型和消息内容长度,然后读取对应长度的字节流作为消息内容。
  • 构造 MyProtocolMessage 对象并加入到解码器的输出列表中。
public class MyProtocolDecoder extends ByteToMessageDecoder {

    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        // 至少需要5个字节来解码
        if (in.readableBytes() < 5) {
            return;
        }

        // 标记当前读取位置
        in.markReaderIndex();

        // 读取消息类型
        byte messageType = in.readByte();

        // 读取消息内容长度
        int contentLength = in.readInt();

        // 如果可读字节数小于消息内容长度,说明消息不完整,重置读取位置
        if (in.readableBytes() < contentLength) {
            in.resetReaderIndex();
            return;
        }

        // 读取消息内容
        byte[] contentBytes = new byte[contentLength];
        in.readBytes(contentBytes);
        String content = new String(contentBytes, StandardCharsets.UTF_8);

        // 构造消息对象
        MyProtocolMessage message = new MyProtocolMessage(MessageType.valueOf(messageType), content);
        out.add(message);
    }
}
5.测试
  • 使用 Netty 的 EmbeddedChannel 类模拟了一个通道来进行测试。
  • 测试了编码器和解码器的正确性,包括编码后解码得到的消息与原消息是否相同。
public class MyProtocolTest {
    private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
    public static void main(String[] args) {
        // 创建一个嵌入式通道,并添加编码器和解码器
        // 指定日志级别为DEBUG,可以看到编码后的字节
        EmbeddedChannel channel = new EmbeddedChannel(new MyProtocolEncoder(), new MyProtocolDecoder(),new LoggingHandler(LogLevel.DEBUG));


        // 构造一个业务消息
        MyProtocolMessage message = new MyProtocolMessage(MessageType.BUSINESS, "Hello, Netty!");

        // 写入消息到通道
        channel.writeOutbound(message);

        // 读取通道中的字节
        ByteBuf encoded = channel.readOutbound();

        // 打印编码后的字节
        logger.error("编码后的字节:Encoded Message: {}",encoded);

        // 写入编码后的字节到通道
        channel.writeInbound(encoded.retain());

        // 读取通道中的解码后的消息
        MyProtocolMessage decodedMessage = channel.readInbound();

        // 打印解码后的消息
        logger.error("解码后的字节:Decoded Message: {}",decodedMessage.getContent());

        // 关闭通道
        channel.finish();
    }
}

image-20240304221744643

自定义协议的优点:

  1. 灵活性
    • 自定义协议可以根据实际业务需求进行设计,灵活地定义消息格式和通信规则,使得通信双方能够更好地适应特定的业务场景。
  2. 性能优化
    • 自定义协议可以针对特定的业务需求进行优化,可以选择合适的数据格式和编码方式,减少通信数据量,提高通信效率。
  3. 安全性
    • 自定义协议可以设计加密和校验机制,确保通信数据的安全性和完整性,防止数据被篡改或窃取。
  4. 版本控制
    • 自定义协议可以包含版本号,便于协议的升级和兼容,能够保证通信双方在协议更新后仍能正常通信。
  5. 易于调试和维护
    • 自定义协议通常具有明确的结构和语义,易于调试和排查问题,同时也方便日后的维护和扩展。

自定义协议的缺点和注意事项:

  1. 复杂性增加
    • 自定义协议的设计和实现需要对网络通信有深入的理解,不当的设计可能导致协议过于复杂,增加开发和维护的难度。
  2. 兼容性问题
    • 协议的升级和演化可能会导致与旧版本的不兼容,需要谨慎处理版本控制和协议演化的问题,以确保新旧版本的兼容性。
  3. 安全风险
    • 自定义协议的安全性需要开发者自行考虑和实现,不恰当的安全机制可能会导致数据泄露和安全漏洞。
  4. 性能折衷
    • 自定义协议的设计需要兼顾性能和灵活性,有时需要在性能和灵活性之间进行权衡和折衷,选择合适的方案。
  5. 协议文档和规范
    • 自定义协议需要有清晰的文档和规范,以确保通信双方都能正确理解和实现协议,避免因为误解或者实现不一致导致通信失败。

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

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

相关文章

第四十九回 吴学究双掌连环计 宋公明三打祝家庄-Python与HTTP服务交互

吴用请戴宗从梁山请来铁面孔目裴宣、圣手书生萧让、通臂猿侯健、玉臂匠金大坚来帮忙。又告诫扈家庄的扈成&#xff0c;打起来不要去帮祝家庄。 孙立把旗号改成“登州兵马提辖孙立”&#xff0c;来祝家庄找峦廷玉&#xff0c;被热情接待。 第三天&#xff0c;宋江派小李广花荣…

001 GUI编程简介

一个知识该怎么学&#xff1f; 这是什么该怎么玩能干什么 图形化程序应该包含并不限于如下组件 窗口弹窗面板文本框列表框按钮图片监听事件鼠标键盘事件 GUI介绍 核心技术&#xff1a;Swing与AWT 不流行原因&#xff1a;界面不美观、需要JRE环境 仍然学习的原因&#xf…

备战蓝桥杯---树形DP基础3

上一次我们讲了二叉苹果树&#xff0c;现在我们加一点难度&#xff0c;从二叉变成了多叉苹果树。 这样子我们就不可以直接按照上次的方法DP&#xff0c;我们其实可以发现&#xff0c;我们可以用类似背包的思想求解&#xff0c;这就是所谓的树上背包。 我们先加进第一个儿子来…

骨传导耳机哪个牌子好?六大选购窍门,帮你甩掉坑货!

很多用户对骨传导耳机的理解存在偏差&#xff0c;认为只要选择价格贵的、热度高的产品就能万事大吉&#xff0c;而实际却不是如此&#xff0c;要知道&#xff0c;随着骨传导耳机逐渐成为热门款式&#xff0c;目前的市场上的骨传导耳机品牌也变得五花八门&#xff0c;这其中就包…

springboot230基于Spring Boot在线远程考试系统的设计与实现

在线远程考试系统设计与实现 摘 要 信息数据从传统到当代&#xff0c;是一直在变革当中&#xff0c;突如其来的互联网让传统的信息管理看到了革命性的曙光&#xff0c;因为传统信息管理从时效性&#xff0c;还是安全性&#xff0c;还是可操作性等各个方面来讲&#xff0c;遇到…

数据库学习案例20240304-mysql数据库案例总结(碎片,统计信息)

1 表中的碎片 在InnoDB中删除行的时候&#xff0c;这些行只是被标记为“已删除”&#xff0c;而不是真正从物理存储上进行了删除&#xff0c;因而存储空间也没有真正被释放回收。InnoDB的Purge线程会异步地来清理这些没用的索引键和行。但是依然没有把这些释放出来的空间还给操…

ES核心概念(45-48)(56-62)(101-103)

ES集群 ES集群&#xff08;Cluster&#xff09;包含多个节点&#xff08;服务器&#xff09;&#xff0c;整体提供服务 核心概念 索引Index&#xff1a;类似于mysql中的表 映射Mapping:数据的结构信息 文档&#xff1a;相当于表中的一条记录 分片&#xff1a; 将数据分成多片…

4、pod运维replicationCtroller、replicaSet、DeamonSet、Job、Cronjob

1、kubenetes 会自动重新运行失败的pod应用 pod运行失败&#xff0c;会自动重启&#xff0c;但是节点失败&#xff0c;pod会被移除&#xff0c; 除非配置了relicationController来管理资源 2、保持pod的健康存活 配置探针&#xff0c;发送http请求 3、查看前一个pod的运行日…

字节扣子 Bot | Bot 介绍

一、什么是 coze &#xff1f; Coze 是一个由字节跳动开发的一个用于开发新一代 AI Chat Bot 的应用编辑平台。在这个平台上&#xff0c;即使是没有编程基础的小白&#xff0c;也能快速创建各种各样的聊天机器人&#xff0c;并将创建的机器人发布到多个社交平台和通讯软件上。 …

vue3 使用实现签到活动demo静态布局详解

文章目录 1. 实现效果2. 签到设置7天布局2.1 实现代码 3 签到设置15天布局3.1 思路分享 4 完整demo代码5. 总结 1. 实现效果 实现一个签到活动的h5页面布局&#xff0c;需求如下 签到活动天数可配置&#xff0c;可配置7天&#xff0c;15天&#xff0c;30天等默认天数要求展示2行…

利用Python自动化日常任务

在快节奏的现代生活中&#xff0c;时间就是一切。幸运的是&#xff0c;Python提供了一系列强大的库和工具&#xff0c;可以帮助我们自动化那些乏味且重复的任务&#xff0c;从而释放我们的时间&#xff0c;让我们可以专注于更有创造性和有意义的工作。下面&#xff0c;我们将探…

MySQL的初学者教程—Navicat的基本操作方法

MySQL的初学者教程—Navicat的基本操作方法 1、运行Navicat 双击桌面的Navicat 12 for MySQL。 2、新建MySQL连接 点击【测试连接】。 zyyMySQL的连接创建成功&#xff01; 3、新建数据库 4、新建表 点击【保存】 表【usermanage】建好了。 点【usermanage】的鼠标右键&#…

chatgpt-next-web搭建教程,超低成本部署属于自己的ChatGPT

随着AI的应用变广&#xff0c;各类AI程序已逐渐普及&#xff0c;尤其是在一些日常办公、学习等与撰写/翻译文稿密切相关的场景&#xff0c;大家都希望找到一个适合自己的稳定可靠的ChatGPT软件来使用。 ChatGPT-Next-Web就是一个很好的选择。它是一个Github上超人气的免费开源…

06、MongoDB -- MongoDB 基本用法(删除文档、查询文档、查询运算符)

目录 MongoDB 基本用法演示前提&#xff1a;登录单机模式的 mongodb 服务器命令登录【admin】数据库的 mongodb 客户端命令登录【test】数据库的 mongodb 客户端命令 删除文档语法格式两个变体版本&#xff1a;1、remove&#xff1a;根据【name】字段删除一条文档2、deleteOne&…

pyqt程序打包成exe最新版保姆级教程

运行环境&#xff1a;win10、pycharm、pyqt5、pyinstaller 要求&#xff1a;将下面软件转换为可执行的exe文件&#xff0c;发送给别人使用。 操作步骤&#xff1a; 1、main.py为我们要转换的代码文件&#xff0c;icon中放着我们exe程序的图标&#xff08;注意&#xff1a;图标…

Stable Diffusion 模型分享:3D Animation Diffusion(3D动漫)

本文收录于《AI绘画从入门到精通》专栏&#xff0c;专栏总目录&#xff1a;点这里。 文章目录 模型介绍生成案例案例一案例二案例三案例四案例五案例六案例七案例八 下载地址 模型介绍 3D Animation Diffusion 是 Lykon 大神的 3D 动漫模型。 作者述&#xff1a;在迪士尼、皮…

CentOS7配置静态IP地址以及主机名

配置静态IP [rootwyx ~]# vim /etc/sysconfig/network-scripts/ifcfg-ens33TYPE"Ethernet" PROXY_METHOD"none" BROWSER_ONLY"no" BOOTPROTO"static" #将dhcp修改为static DEFROUTE"yes" IPV4_FAILURE_FATAL"no"…

神经网络3-时间卷积神经网络

在深度学习的知识宝库中&#xff0c;卷积神经网络&#xff08;CNN&#xff09;广泛应用于视觉&#xff0c;视频等二维或者多维的图像领域。卷积网络具有深度&#xff0c;可并行等多种优良特性&#xff0c;那么这种技术是否可以应用于解单维度的时间序列问题呢&#xff1f;本文介…

微信小程序中使用特使字体

1、首先下载字体文件 推荐几个常用下载字体的网站 https://font.chinaz.com/zhongwenziti.html https://www.hellofont.cn/ 2、转换字体 使用下面这个网站进行字体转换 https://transfonter.org/ 点击add fonts 按钮进行上传刚刚下载的字体文件选择formats格式&#xff1a;可…

数学建模【灰色关联分析】

一、灰色关联分析简介 一般的抽象系统,如社会系统、经济系统、农业系统、生态系统、教育系统等都包含有许多种因素&#xff0c;多种因素共同作用的结果决定了该系统的发展态势。人们常常希望知道在众多的因素中&#xff0c;哪些是主要因素&#xff0c;哪些是次要因素;哪些因素…