当前位置: 首页 > news >正文

手写RPC框架--7.封装响应

RPC框架-Gitee代码(麻烦点个Starred, 支持一下吧)
RPC框架-GitHub代码(麻烦点个Starred, 支持一下吧)

封装响应

  • 封装响应
    • a.封装响应
    • b.请求id生成器(雪花算法)
    • c.抽象序列化
    • d.建立序列化工厂
    • e.hessian的序列化方式(拓展)

封装响应

a.封装响应

在core模块下的com.dcyrpcenumeration

在包内创建ResponseCode 枚举:定义响应码枚举

/*** 响应码枚举*/
public enum ResponseCode {SUCCESS((byte) 1, "成功"), FAIL((byte) 2, "失败");private byte code;private String desc;ResponseCode(byte code, String desc) {this.code = code;this.desc = desc;}
}

在core模块下com.dcyrpctransprt.message包下

创建DcyRpcResponse类:服务提供方回复的响应

/*** 服务提供方回复的响应*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class DcyRpcResponse {// 请求的idprivate long requestId;// 压缩的类型private byte compressType;// 序列化的方式private byte serializeType;// 响应码类型:1 成功,2 异常private byte code;// 具体的消息体private Object body;
}

在core模块channelhandler.handler包下创建DcyRpcResponseEncoder类:对调用结果进行编码

/*** 编码器** 4B magic(魔数值) -- Drpc.getBytes()* 1B version(版本) -- 1* 2B header length(首部的长度)* 4B full length(报文的总长度)* 1B serialize (序列化类型的长度)* 1B compress(压缩类型的长度)* 1B code(响应码)* 8B requestId** Object body*/
@Slf4j
public class DcyRpcResponseEncoder extends MessageToByteEncoder<DcyRpcResponse> {@Overrideprotected void encode(ChannelHandlerContext channelHandlerContext, DcyRpcResponse dcyRpcResponse, ByteBuf byteBuf) throws Exception {// 4个字节魔数值byteBuf.writeBytes(MessageFormatConstant.MAGIC);// 1个字节版本号byteBuf.writeByte(MessageFormatConstant.VERSION);// 2个字节的头部的长度byteBuf.writeShort(MessageFormatConstant.HEADER_LENGTH);// 总长度未知,不知道body的长度byteBuf.writerIndex(byteBuf.writerIndex() + MessageFormatConstant.FULL_FIELD_LENGTH);// 响应码byteBuf.writeByte(dcyRpcResponse.getCode());// 序列化类型byteBuf.writeByte(dcyRpcResponse.getSerializeType());// 压缩类型byteBuf.writeByte(dcyRpcResponse.getCompressType());// 8个字节的请求idbyteBuf.writeLong(dcyRpcResponse.getRequestId());// 写入请求体body(requestPayload)byte[] body = getBodyBytes(dcyRpcResponse.getBody());if (body != null) {byteBuf.writeBytes(body);byteBuf.writeInt(MessageFormatConstant.HEADER_LENGTH + body.length);}int bodyLength = body ==null ? 0 : body.length;// 重新处理报文的总长度// 先获取当前的写指针的位置int writerIndex = byteBuf.writerIndex();// 将写指针的位置移动到总长度的位置上byteBuf.writerIndex(MessageFormatConstant.MAGIC.length + MessageFormatConstant.VERSION_LENGTH + MessageFormatConstant.HEADER_FIELD_LENGTH);byteBuf.writeInt(MessageFormatConstant.HEADER_LENGTH + bodyLength);// 将写指针归位byteBuf.writerIndex(writerIndex);}private byte[] getBodyBytes(Object body) {// 心跳请求没有payloadif (body == null) {return null;}// 对象序列化成字节数组try {ByteArrayOutputStream baos = new ByteArrayOutputStream();ObjectOutputStream outputStream = new ObjectOutputStream(baos);outputStream.writeObject(body);return baos.toByteArray();} catch (IOException e) {log.error("序列化时出现异常");throw new RuntimeException(e);}}
}

修改DcyRpcBootstrapstart()方法

  • 添加 DcyRpcResponseEncoder 响应编码器
// 略....
// 3.配置服务器
serverBootstrap = serverBootstrap.group(boss, worker).channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel socketChannel) throws Exception {// TODO 核心内容,需要添加很多入栈和出栈的handlersocketChannel.pipeline().addLast(new LoggingHandler(LogLevel.INFO))// 对报文进行解码.addLast(new DcyRpcRequestDecoder())// 根据请求进行方法调用.addLast(new MethodCallHandler())// 对响应结果进行编码.addLast(new DcyRpcResponseEncoder());}});
// 略....

在core模块channelhandler.handler包下创建DcyRpcResponseEncoder类:服务请求方对响应结果进行解码

/*** 解码器*/
@Slf4j
public class DcyRpcResponseDecoder extends LengthFieldBasedFrameDecoder {public DcyRpcResponseDecoder() {// 找到当前报文的总长度,截取报文,截取出来的报文可以进行解析super(// 最大帧的长度,超过这个maxFrameLength值,会直接丢弃MessageFormatConstant.MAX_FRAME_LENGTH,// 长度字段偏移量MessageFormatConstant.MAGIC.length + MessageFormatConstant.VERSION_LENGTH + MessageFormatConstant.HEADER_FIELD_LENGTH,// 长度的字段的长度MessageFormatConstant.FULL_FIELD_LENGTH,// todo 负载的适配长度-(MessageFormatConstant.MAGIC.length + MessageFormatConstant.VERSION_LENGTH + MessageFormatConstant.HEADER_FIELD_LENGTH + MessageFormatConstant.FULL_FIELD_LENGTH),// 跳过的字段0);}@Overrideprotected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {Object decode = super.decode(ctx, in);if (decode instanceof ByteBuf) {ByteBuf byteBuf = (ByteBuf) decode;return decodeFrame(byteBuf);}return null;}private Object decodeFrame(ByteBuf byteBuf) {// 1.解析魔数byte[] magic = new byte[MessageFormatConstant.MAGIC.length];byteBuf.readBytes(magic);// 检测魔数值是否匹配for (int i = 0; i < magic.length; i++) {if (magic[i] != MessageFormatConstant.MAGIC[i]) {throw new RuntimeException("获得的请求类型不匹配");}}// 2.解析版本号byte version = byteBuf.readByte();if (version > MessageFormatConstant.VERSION) {throw new RuntimeException("获得的请求版本不被支持");}// 3.解析头部的长度short headLength = byteBuf.readShort();// 4.解析总长度int fullLength = byteBuf.readInt();// 5.解析响应码byte responseCode = byteBuf.readByte();// 6.解析序列化类型byte serializeType = byteBuf.readByte();// 7.解析压缩型byte compressType = byteBuf.readByte();// 8.解析请求Idlong requestId = byteBuf.readLong();DcyRpcResponse dcyRpcResponse = new DcyRpcResponse();dcyRpcResponse.setCode(responseCode);dcyRpcResponse.setCompressType(compressType);dcyRpcResponse.setSerializeType(serializeType);dcyRpcResponse.setRequestId(requestId);// 9.解析消息体payloadint bodyLength = fullLength - headLength;byte[] body = new byte[bodyLength];byteBuf.readBytes(body);// 解压缩和反序列化// todo 解压缩// 反序列化try (ByteArrayInputStream bis = new ByteArrayInputStream(body);ObjectInputStream ois = new ObjectInputStream(bis)) {Object object = ois.readObject();dcyRpcResponse.setBody(object);} catch (IOException | ClassNotFoundException e) {log.error("请求【{}】反序列化时出现异常", requestId, e);throw new RuntimeException(e);}return dcyRpcResponse;}
}

修改channelHandler包下的ConsumerChannelInitializer类:添加入站的解码器 DcyRpcResponseDecoder()

public class ConsumerChannelInitializer extends ChannelInitializer<SocketChannel> {@Overrideprotected void initChannel(SocketChannel socketChannel) throws Exception {socketChannel.pipeline()// netty自带的日志处理器.addLast(new LoggingHandler(LogLevel.INFO))// 消息编码器.addLast(new DcyRpcRequestEncoder())// 入站的解码器.addLast(new DcyRpcResponseDecoder())// 处理结果.addLast(new MySimpleChannelInboundHandler());}
}

修改channelhandler.handler包下的MySimpleChannelInboundHandler类:将响应结果的ByteBuf改成DcyRpcResponse

/*** 处理响应结果*/
public class MySimpleChannelInboundHandler extends SimpleChannelInboundHandler<DcyRpcResponse> {@Overrideprotected void channelRead0(ChannelHandlerContext ctx, DcyRpcResponse dcyRpcResponse) throws Exception {// 异步// 服务提供方,给予的结果Object returnValue = dcyRpcResponse.getBody();// 从全局的挂起的请求中,寻找与之匹配的待处理 completeFutureCompletableFuture<Object> completableFuture = DcyRpcBootstrap.PENDING_REQUEST.get(1L);completableFuture.complete(returnValue);}
}

在core模块channelhandler.handler包的MethodCallHandler类:封装响应结果

// 略...
// 3.封装响应
DcyRpcResponse dcyRpcResponse = DcyRpcResponse.builder().code(ResponseCode.SUCCESS.getCode()).requestId(dcyRpcRequest.getRequestId()).compressType(dcyRpcRequest.getCompressType()).serializeType(dcyRpcRequest.getSerializeType()).body(result).build();// 4.写出响应
channelHandlerContext.channel().writeAndFlush(dcyRpcResponse);// 略...

b.请求id生成器(雪花算法)

在当前项目中,我们需要给请求一个唯一标识,用来标识一个请求和响应的关联关系,我们要求请求的id必须唯一,且不能占用过大的空间,可用的方案如下:

  • 1.自增id,单机的自增id不能解决不重复的问题,微服务情况下我们需要一个稳定的发号服务才能保证,但是这样做性能偏低。

  • 2.uuid,将uuid作为唯一标识占用空间太大

  • 3.雪花算法,最优解。

雪花算法(snowflake)最早是twitter内部使用分布式环境下的唯一ID生成算法,他使用64位long类型的数据存储

id,具体如下:

0 - 0000000000 0000000000 0000000000 0000000000 0 - 0000000000 - 000000000000
符号位 时间戳 机器码 序列号
最高位表示符号位,其中0代表整数,1代表负数,而id一般都是正数,所以最高位为0。

通过雪花算法实现 – (世界上没有一片雪花是一样的) 5+5+42+12=64位

  • 机房号(数据中心)5bit
  • 机器号 5bit
  • 时间戳(long)原本64位表示的时间,必须减少到42位,可以自由选择一个时间。如: 公司的成立日期
  • 序列化 12bit:同一个机房的同一个机器号的同一个时间可能因并发量需要很多个Id
时间戳 (42) 机房号 (5) 机器号 (5) 序列号 (12)
101010101010101010101010101010101010101011 10101 10101 101011010101

在common块下的com.dcy包下创建IdGenerator

/*** 请求id的生成器:雪花算法*/
public class IdGenerator {// 起始时间戳private static final long START_STAMP = DateUtil.get("2022-1-1").getTime();// 机房号public static final long DATA_CENTER_BIT = 5L;// 机器号public static final long MACHINE_BIT = 5L;// 序列化号public static final long SEQUENCE_BIT = 5L;// 机房号的最大值public static final long DATA_CENTER_MAX = ~(-1L << DATA_CENTER_BIT);// 机器号的最大值public static final long MACHINE_MAX = ~(-1L << MACHINE_BIT);// 序列号的最大值public static final long SEQUENCE_MAX = ~(-1L << SEQUENCE_BIT);// 时间戳需要左移的位数public static final long TIMESTAMP_LEFT = DATA_CENTER_BIT + MACHINE_BIT + SEQUENCE_BIT;// 机房号需要左移的位数public static final long DATA_CENTER_LEFT = MACHINE_BIT + SEQUENCE_BIT;// 机器号需要左移的位数public static final long MACHINE_LEFT = SEQUENCE_BIT;private long dataCenterId;private long machineId;private LongAdder sequenceId = new LongAdder();private long lastTimeStamp = -1;public IdGenerator(long dataCenterId, long machineId) {//参数是否合法if (dataCenterId > DATA_CENTER_MAX || machineId > MACHINE_MAX) {throw new IllegalArgumentException("传入的数据中心编号和机器编号不合法");}this.dataCenterId = dataCenterId;this.machineId = machineId;}public long getId() {// 1.处理时间戳的问题long currentTime = System.currentTimeMillis();long timeStamp = currentTime - START_STAMP;// 2.判断时钟回拨if (timeStamp < lastTimeStamp) {throw new RuntimeException("服务器进行了时钟回调");}// 3.对sequenceId做一些处理:如果是同一个时间节点,必须自增if (timeStamp == lastTimeStamp) {sequenceId.increment();if (sequenceId.sum() >= SEQUENCE_MAX) {timeStamp = getNextTimeStamp();sequenceId.reset();}} else {sequenceId.reset();}// 执行结束将时间戳赋值给lastTimeStamplastTimeStamp = timeStamp;long sequence = sequenceId.sum();return timeStamp << TIMESTAMP_LEFT | dataCenterId << DATA_CENTER_LEFT | machineId << MACHINE_LEFT | sequence;}private long getNextTimeStamp() {// 获取当前的时间戳long current = System.currentTimeMillis() - START_STAMP;// 如果一样就一直循环,直到下一个时间戳while (current == lastTimeStamp) {current = System.currentTimeMillis() - START_STAMP;}return current;}
}

在common块下的com.dcy包下创建DateUtil类:用于时间日期相关的工具类

/*** 时间日期相关的工具类*/
public class DateUtil {public static Date get(String pattern) {SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");try {return sdf.parse(pattern);} catch (ParseException e) {throw new RuntimeException(e);}}
}

DcyRpcBootstrap类中添加IdGenerator

// 略...
private int port = 8088;public static final IdGenerator ID_GENERATOR = new IdGenerator(1, 2);
// 略...

RpcConsumerInvocationHandler类的封装报文位置,将requestId的值设置为通过id生成器获取

// 略...
DcyRpcRequest dcyRpcRequest = DcyRpcRequest.builder().requestId(DcyRpcBootstrap.ID_GENERATOR.getId()).compressType((byte) 1).serializeType((byte) 1).requestType(RequestType.REQUEST.getId()).requestPayload(requestPayload).build();

c.抽象序列化

在core模块下创建serialize

在该包下创建Serializer接口:序列化器

public interface Serializer {/*** 序列化* @param object 待序列化的对象实例* @return 字节数组*/byte[] serializer(Object object);/*** 反序列化* @param bytes 待反序列化的字节数组* @param clazz 目标类的class对象* @return 目标实例* @param <T> 目标类泛型*/<T> T deserialize(byte[] bytes, Class<T> clazz);
}

serialize包下创建impl包,创建JdkSerializer类,实现Serializer接口:jdk序列化器

@Slf4j
public class JdkSerializer implements Serializer{@Overridepublic byte[] serializer(Object object) {// 心跳请求没有payloadif (object == null) {return null;}// 对象序列化成字节数组try (ByteArrayOutputStream baos = new ByteArrayOutputStream();ObjectOutputStream outputStream = new ObjectOutputStream(baos)) {outputStream.writeObject(object);return baos.toByteArray();} catch (IOException e) {log.error("序列化对象【{}】时出现异常", object);throw new SerializeException(e);}}@Overridepublic <T> T deserialize(byte[] bytes, Class<T> clazz) {if (bytes == null || clazz == null) {return null;}// 字节数组转成对象序列化try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes);ObjectInputStream objectInputStream = new ObjectInputStream(bais)) {return (T) objectInputStream.readObject();} catch (IOException | ClassNotFoundException e) {log.error("反序列化对象【{}】时出现异常", clazz);throw new SerializeException(e);}}
}

在common的exceptions中创建SerializeException类:序列化异常处理

public class SerializeException extends RuntimeException{public SerializeException() {super();}public SerializeException(String message) {super(message);}public SerializeException(Throwable cause) {super(cause);}
}

d.建立序列化工厂

consumer包下的Application启动类中添加序列化方法

// 略...
DcyRpcBootstrap.getInstance().application("first-dcyrpc-consumer").registry(new RegistryConfig("zookeeper://127.0.0.1:2181")).serialize("jdk").reference(reference);
// 略...

DcyRpcBootstrap类中,添加序列化配置项和方法

// 略...
public static String SERIALIZE_TYPE = "jdk";
// 略...
/*** 配置序列化的方式* @param serializeType* @return*/
public DcyRpcBootstrap serialize(String serializeType) {SERIALIZE_TYPE = serializeType;return this;
}

在core模块下的serialize包下创建SerializerWrapper类:序列化包装类

@NoArgsConstructor
@AllArgsConstructor
@Data
public class SerializerWrapper {private byte code;private String type;private Serializer serializer;
}

在core模块下的serialize包下创建SerializerFactory类:序列化工厂类

/*** 序列化工厂类*/
public class SerializerFactory {private final static Map<String, SerializerWrapper> SERIALIZER_CACHE = new ConcurrentHashMap<>(8);private final static Map<Byte, SerializerWrapper> SERIALIZER_CACHE_CODE = new ConcurrentHashMap<>(8);static {SerializerWrapper jdk = new SerializerWrapper((byte) 1, "jdk", new JdkSerializer());SerializerWrapper json = new SerializerWrapper((byte) 2, "json", new JsonSerializer());SERIALIZER_CACHE.put("jdk", jdk);SERIALIZER_CACHE.put("json", json);SERIALIZER_CACHE_CODE.put((byte) 1, jdk);SERIALIZER_CACHE_CODE.put((byte) 2, json);}/*** 使用工厂方法获取一个SerializerWrapper* @param serializeType 序列化的类型* @return*/public static SerializerWrapper getSerializer(String serializeType) {return SERIALIZER_CACHE.get(serializeType);}public static SerializerWrapper getSerializer(byte serializeCode) {return SERIALIZER_CACHE_CODE.get(serializeCode);}
}

修改DcyRpcRequestEncoder类,在请求类,对请求添加有关序列化的代码

//略...
// 8个字节的请求id
byteBuf.writeLong(dcyRpcRequest.getRequestId());// 写入请求体body(requestPayload)
// 1.根据配置的序列化方式进行序列化
Serializer serializer = SerializerFactory.getSerializer(dcyRpcRequest.getSerializeType()).getSerializer();
byte[] body = serializer.serializer(dcyRpcRequest.getRequestPayload());// 2.根据配置的压缩方式进行压缩if (body != null) {byteBuf.writeBytes(body);byteBuf.writeInt(MessageFormatConstant.HEADER_LENGTH + body.length);
}
//略...

修改RpcConsumerInvocationHandler类:修改填写序列化器的代码

// 略...
DcyRpcRequest dcyRpcRequest = DcyRpcRequest.builder().requestId(DcyRpcBootstrap.ID_GENERATOR.getId()).compressType((byte) 1).serializeType(SerializerFactory.getSerializer(DcyRpcBootstrap.SERIALIZE_TYPE).getCode()).requestType(RequestType.REQUEST.getId()).requestPayload(requestPayload).build();
// 略...

修改DcyRpcRequestDecoder类,在响应类,对请求添加反序列化的代码

// 略...
// 9.解析消息体payload
int payloadLength = fullLength - headLength;
byte[] payload = new byte[payloadLength];
byteBuf.readBytes(payload);// 解压缩和反序列化
// todo 解压缩// 反序列化
Serializer serializer = SerializerFactory.getSerializer(serializeType).getSerializer();
RequestPayload requestPayload = serializer.deserialize(payload, RequestPayload.class);dcyRpcRequest.setRequestPayload(requestPayload);return dcyRpcRequest;

修改DcyRpcResponseEncoder类:在响应类,对响应添加序列化的代码

// 略...
// 8个字节的请求id
byteBuf.writeLong(dcyRpcResponse.getRequestId());// 写入请求体body(requestPayload)
// 对响应做序列化器
Serializer serializer = SerializerFactory.getSerializer(dcyRpcResponse.getSerializeType()).getSerializer();
byte[] body = serializer.serializer(dcyRpcResponse.getBody());if (body != null) {byteBuf.writeBytes(body);byteBuf.writeInt(MessageFormatConstant.HEADER_LENGTH + body.length);
}
// 略...

修改DcyRpcResponseDecoder类,在请求类,对响应添加反序列化的代码

// 略...
// 9.解析消息体payload
int payloadLength = fullLength - headLength;
byte[] payload = new byte[payloadLength];
byteBuf.readBytes(payload);// 解压缩和反序列化
// todo 解压缩Serializer serializer = SerializerFactory.getSerializer(dcyRpcResponse.getSerializeType()).getSerializer();
Object body = serializer.deserialize(payload, Object.class);dcyRpcResponse.setBody(body);return dcyRpcResponse;
// 略...

e.hessian的序列化方式(拓展)

Hessian序列化是一种支持动态类型、跨语言、基于对象传输的网络协议,Java对象序列化的二进制流可以被其他语言(如,c++,python)。特性如下:

  • 1.自描述序列化类型。不依赖外部描述文件或者接口定义,用一个字节表示常用的基础类型,极大缩短二进制流。
  • 2.语言无关,支持脚本语言
  • 3.协议简单,比Java原生序列化高效
  • 4.相比hessian1,hessian2中增加了压缩编码,其序列化二进制流大小是Java序列化的50%,序列化耗时是Java序列化的30%,反序列化耗时是Java序列化的20%

序列化操作:

  • 1.new一个Hessian2Output,传入一个OutputStream
  • 2.writeObject(),传入具体要序列化的对象
  • 3.flush()

反序列化操作:

  • 1.new一个Hessian2Output,传入一个InputStream
  • 2.readObject()
/*** hessian序列化器*/
@Slf4j
public class HessianSerializer implements Serializer {@Overridepublic byte[] serializer(Object object) {// 心跳请求没有payloadif (object == null) {return null;}// 对象序列化成字节数组try (ByteArrayOutputStream baos = new ByteArrayOutputStream();) {Hessian2Output hessian2Output = new Hessian2Output(baos);hessian2Output.writeObject(object);hessian2Output.flush();log.info("对象使用hessian【{}】完成了序列化", object);return baos.toByteArray();} catch (IOException e) {log.error("使用hessian序列化对象【{}】时出现异常", object);throw new SerializeException(e);}}@Overridepublic <T> T deserialize(byte[] bytes, Class<T> clazz) {if (bytes == null || clazz == null) {return null;}// 字节数组转成对象序列化try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes);) {HessianInput hessianInput = new HessianInput(bais);T t = (T) hessianInput.readObject();log.info("对象使用hessian【{}】完成了反序列化", clazz);return t;} catch (IOException e) {log.error("使用hessian反序列化对象【{}】时出现异常", clazz);throw new SerializeException(e);}}
}

相关文章:

手写RPC框架--7.封装响应

RPC框架-Gitee代码(麻烦点个Starred, 支持一下吧) RPC框架-GitHub代码(麻烦点个Starred, 支持一下吧) 封装响应 封装响应a.封装响应b.请求id生成器(雪花算法)c.抽象序列化d.建立序列化工厂e.hessian的序列化方式&#xff08;拓展&#xff09; 封装响应 a.封装响应 在core模块…...

Linux入门教程||Linux系统目录结构

登录系统后&#xff0c;在当前命令窗口下输入命令&#xff1a; ls / 你会看到如下图所示: 树状目录结构&#xff1a; 以下是对这些目录的解释&#xff1a; /bin&#xff1a; bin是Binary的缩写, 这个目录存放着最经常使用的命令。 /boot&#xff1a; 这里存放的是启动Linux时…...

LeetCode 88. 合并两个有序数组

文章目录 一、题目二、C# 题解 一、题目 给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2&#xff0c;另有两个整数 m 和 n &#xff0c;分别表示 nums1 和 nums2 中的元素数目。 请你 合并 nums2 到 nums1 中&#xff0c;使合并后的数组同样按 非递减顺序 排列。 注意&a…...

C语言实现扫雷小游戏

1.首先扫雷游戏要存储布置好的雷信息&#xff0c;需要一个二维数组 不是雷放* 雷&#xff1a;# 不是雷&#xff1a;0 雷&#xff1a;1 2. 给2个二维数组 9*9 一个存放雷的信息&#xff0c;一个存放布置好雷的信息 3.为了防止在统计坐标周围的…...

【linux基础(五)】Linux中的开发工具(上)---yum和vim

&#x1f493;博主CSDN主页:杭电码农-NEO&#x1f493;   ⏩专栏分类:Linux从入门到开通⏪   &#x1f69a;代码仓库:NEO的学习日记&#x1f69a;   &#x1f339;关注我&#x1faf5;带你学更多操作系统知识   &#x1f51d;&#x1f51d; Linux中的开发工具 1. 前言2.…...

C++学习之list的实现

在了解学习list实现之前我们首先了解一下关于迭代器的分类&#xff1a; 按功能分类&#xff1a; 正向迭代器 反向迭代器 const正向迭代器 const反向迭代器 按性质分类&#xff1a; 单向迭代器 只能 例如单链表 双向迭代器 可&#xff0c;也可-- 例如双…...

一种高效且节约内存的聚合数据结构的实现

一种高效且节约内存的聚合数据结构的实现 在特定的场景中&#xff0c;特殊定制数据结构能够得到更加好的性能且更节约内存。 聚合函数GroupArray的问题 GroupArray聚合函数是将分组内容组成一个个数组&#xff0c;例如下面的例子&#xff1a; SELECT groupArray(concat(ABC…...

机器学习(10)---特征选择

文章目录 一、概述二、Filter过滤法2.1 过滤法说明2.2 方差过滤2.3 方差过滤对模型影响 三、相关性过滤3.1 卡方过滤3.2 F检验3.3 互信息法3.4 过滤法总结 四、Embedded嵌入法4.1 嵌入法说明4.2 以随机森林为例的嵌入法 五、Wrapper包装法5.1 包装法说明5.2 以随机森林为例的包…...

Python之数据库(MYSQL)连接

一&#xff09;数据库SQL语言基础 MySQL是一个关系型数据库管理系统&#xff0c;由瑞典MySQL AB 公司开发&#xff0c;目前属于 Oracle 旗下产品。MySQL 是最流行的关系型数据库管理系统之一&#xff0c;在 WEB 应用方面&#xff0c;MySQL是最好的 RDBMS (Relational Database…...

【建站教程】使用阿里云服务器怎么搭建网站?

使用阿里云服务器快速搭建网站教程&#xff0c;先为云服务器安装宝塔面板&#xff0c;然后在宝塔面板上新建站点&#xff0c;阿里云服务器网以搭建WordPress网站博客为例&#xff0c;阿小云来详细说下从阿里云服务器CPU内存配置选择、Web环境、域名解析到网站上线全流程&#x…...

【自然语言处理】关系抽取 —— MPDD 讲解

MPDD 论文信息 标题:MPDD: A Multi-Party Dialogue Dataset for Analysis of Emotions and Interpersonal Relationships 作者:Yi-Ting Chen, Hen-Hsen Huang, Hsin-Hsi Chen 期刊:LREC 2020 发布时间与更新时间:2020 主题:自然语言处理、关系抽取、对话场景、情感预测 数…...

深入理解JVM虚拟机第三篇:JVM的指令集架构模型和JVM的生命周期

文章目录 一:JVM的指令集架构模型 1:基于栈式架构的特点...

[小尾巴 UI 组件库] 组件库配置与使用

文章归档于&#xff1a;https://www.yuque.com/u27599042/row3c6 组件库地址 npm&#xff1a;https://www.npmjs.com/package/xwb-ui?activeTabreadme小尾巴 UI 组件库源码 gitee&#xff1a;https://gitee.com/tongchaowei/xwb-ui小尾巴 UI 组件库测试代码 gitee&#xff1a…...

Linux系统中fork()函数的理解

fork() 函数是一个在Unix和类Unix操作系统中常见的系统调用&#xff0c;用于创建一个新的进程&#xff0c;该进程是调用进程&#xff08;父进程&#xff09;的副本。fork() 函数的工作原理如下&#xff1a; 1. 当父进程调用 fork() 时&#xff0c;操作系统会创建一个新的进程&a…...

Linux网络编程:网络协议及网络传输的基本流程

目录 一. 计算机网络的发展 二. 网络协议的认识 2.1 对于协议分层的理解 2.2 TCP/IP五层协议模型 2.3 OSI七层模型 三. 网络传输的流程 3.1 同一网段中计算机通信的流程 3.2 不同网段中计算机设备的通信 3.3 对于IP地址和MAC地址的理解 3.4 数据的封装和解包 四. 总结…...

【大数据之Kafka】十、Kafka消费者工作流程

1 Kafka消费方式 &#xff08;1&#xff09;pull&#xff08;拉&#xff09;模式&#xff1a;消费者从broker中主动拉取数据。&#xff08;Kafka中使用&#xff09; 不足&#xff1a;如果Kafka中没有数据&#xff0c;消费者可能会陷入循环&#xff0c;一直返回空数据。 &#…...

如何确保ChatGPT的文本生成对特定行业术语的正确使用?

确保ChatGPT在特定行业术语的正确使用是一个重要而复杂的任务。这涉及到许多方面&#xff0c;包括数据预处理、模型训练、微调、评估和监控。下面我将详细介绍如何确保ChatGPT的文本生成对特定行业术语的正确使用&#xff0c;并探讨这一过程中的关键考虑因素。 ### 1. 数据预处…...

行业追踪,2023-09-11

自动复盘 2023-09-11 凡所有相&#xff0c;皆是虚妄。若见诸相非相&#xff0c;即见如来。 k 线图是最好的老师&#xff0c;每天持续发布板块的rps排名&#xff0c;追踪板块&#xff0c;板块来开仓&#xff0c;板块去清仓&#xff0c;丢弃自以为是的想法&#xff0c;板块去留让…...

LVS + Keepalived群集

文章目录 1. Keepalived工具概述1.1 什么是Keepalived1.2 工作原理1.3 Keepailved实现原理1.4 Keepalived体系主要模块及其作用1.5 keepalived的抢占与非抢占模式 2. 脑裂现象 &#xff08;拓展&#xff09;2.1 什么是脑裂2.2 脑裂的产生原因2.3 如何解决脑裂2.4 如何预防脑裂 …...

springboot将jar改成war

一、maven项目 1、修改pom文件 <packaging>war</packaging>2、添加Servlet API依赖&#xff0c;Spring Boot的Starter依赖通常会包含这个依赖&#xff0c;所以你可能已经有了&#xff0c;没有就需要添加 <dependency><groupId>javax.servlet</gr…...

2025 后端自学UNIAPP【项目实战:旅游项目】6、我的收藏页面

代码框架视图 1、先添加一个获取收藏景点的列表请求 【在文件my_api.js文件中添加】 // 引入公共的请求封装 import http from ./my_http.js// 登录接口&#xff08;适配服务端返回 Token&#xff09; export const login async (code, avatar) > {const res await http…...

Java面试专项一-准备篇

一、企业简历筛选规则 一般企业的简历筛选流程&#xff1a;首先由HR先筛选一部分简历后&#xff0c;在将简历给到对应的项目负责人后再进行下一步的操作。 HR如何筛选简历 例如&#xff1a;Boss直聘&#xff08;招聘方平台&#xff09; 直接按照条件进行筛选 例如&#xff1a…...

JAVA后端开发——多租户

数据隔离是多租户系统中的核心概念&#xff0c;确保一个租户&#xff08;在这个系统中可能是一个公司或一个独立的客户&#xff09;的数据对其他租户是不可见的。在 RuoYi 框架&#xff08;您当前项目所使用的基础框架&#xff09;中&#xff0c;这通常是通过在数据表中增加一个…...

#Uniapp篇:chrome调试unapp适配

chrome调试设备----使用Android模拟机开发调试移动端页面 Chrome://inspect/#devices MuMu模拟器Edge浏览器&#xff1a;Android原生APP嵌入的H5页面元素定位 chrome://inspect/#devices uniapp单位适配 根路径下 postcss.config.js 需要装这些插件 “postcss”: “^8.5.…...

IP如何挑?2025年海外专线IP如何购买?

你花了时间和预算买了IP&#xff0c;结果IP质量不佳&#xff0c;项目效率低下不说&#xff0c;还可能带来莫名的网络问题&#xff0c;是不是太闹心了&#xff1f;尤其是在面对海外专线IP时&#xff0c;到底怎么才能买到适合自己的呢&#xff1f;所以&#xff0c;挑IP绝对是个技…...

【WebSocket】SpringBoot项目中使用WebSocket

1. 导入坐标 如果springboot父工程没有加入websocket的起步依赖&#xff0c;添加它的坐标的时候需要带上版本号。 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId> </dep…...

解析两阶段提交与三阶段提交的核心差异及MySQL实现方案

引言 在分布式系统的事务处理中&#xff0c;如何保障跨节点数据操作的一致性始终是核心挑战。经典的两阶段提交协议&#xff08;2PC&#xff09;通过准备阶段与提交阶段的协调机制&#xff0c;以同步决策模式确保事务原子性。其改进版本三阶段提交协议&#xff08;3PC&#xf…...

React核心概念:State是什么?如何用useState管理组件自己的数据?

系列回顾&#xff1a; 在上一篇《React入门第一步》中&#xff0c;我们已经成功创建并运行了第一个React项目。我们学会了用Vite初始化项目&#xff0c;并修改了App.jsx组件&#xff0c;让页面显示出我们想要的文字。但是&#xff0c;那个页面是“死”的&#xff0c;它只是静态…...

理想汽车5月交付40856辆,同比增长16.7%

6月1日&#xff0c;理想汽车官方宣布&#xff0c;5月交付新车40856辆&#xff0c;同比增长16.7%。截至2025年5月31日&#xff0c;理想汽车历史累计交付量为1301531辆。 官方表示&#xff0c;理想L系列智能焕新版在5月正式发布&#xff0c;全系产品力有显著的提升&#xff0c;每…...

基于django+vue的健身房管理系统-vue

开发语言&#xff1a;Python框架&#xff1a;djangoPython版本&#xff1a;python3.8数据库&#xff1a;mysql 5.7数据库工具&#xff1a;Navicat12开发软件&#xff1a;PyCharm 系统展示 会员信息管理 员工信息管理 会员卡类型管理 健身项目管理 会员卡管理 摘要 健身房管理…...