Nettyの参数优化简单RPC框架实现

本篇介绍Netty调优,在上篇聊天室的案例中进行改造,手写一个简单的RPC实现。

1、超时时间参数

        CONNECT_TIMEOUT_MILLIS 是Netty的超时时间参数,属于客户端SocketChannel的参数,客户端连接时如果一定时间没有连接上,就会抛出 timeout 异常

        如何在代码中添加参数?在new Bootstrap()时使用

.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 300)

        启动客户端,不启动服务器,发现连接超时

        打上断点(选择多线程模式):

        这一行获取到的值是创建BootStrap时添加CONNECT_TIMEOUT_MILLIS 的值(300)

int connectTimeoutMillis = config().getConnectTimeoutMillis();

         满足条件,进入If块:

         这是一个定时任务,延迟CONNECT_TIMEOUT_MILLIS 的值(300)后触发,执行Runnable中的逻辑,抛出超时异常。

connectTimeoutFuture = eventLoop().schedule(new Runnable() {@Overridepublic void run() {ChannelPromise = AbstractNioChannel.this.connectPromise;ConnectTimeoutException cause = new ConnectTimeoutException("connection timed out: " + remoteAddress);if (connectPromise != null && connectPromise.tryFailure(cause)) {close(voidPromise());}}
}, connectTimeoutMillis, TimeUnit.MILLISECONDS);

        主线程和NIO线程也是通过connectPromise进行异步通信的,两个线程持有的是同一个connectPromise对象。

2、SO_BACKLOG

        SO_BACKLOG是一个与服务器套接字相关的参数,主要用于配置服务器套接字的接受队列大小,属于ServerSocketChannel的参数

什么是套接字?

套接字是计算机网络中的一种通信端点,用于在两个节点之间建立连接并进行数据传输,它包含了IP地址端口号,通过这两个标识符,网络上的设备可以相互定位和通信。

套接字在客户端和服务器的工作顺序:

服务器端:

  1. 创建套接字。
  2. 绑定到指定的IP地址和端口号。
  3. 监听连接请求。
  4. 接受连接,创建一个新的套接字用于与客户端通信。
  5. 读取或写入数据。

客户端

  1. 创建套接字。
  2. 连接到服务器的IP地址和端口号。
  3. 读取或写入数据。

         而SO_BACKLOG参数决定了服务器套接字在操作系统内核中维护的一个挂起连接队列的最大长度。

        当一个服务器应用程序启动并监听某个端口时,它会创建一个服务器套接字,用于等待客户端的连接请求。

        当客户端尝试连接服务器时,连接请求会首先进入服务器端的一个等待队列,称为挂起连接队列。这个队列中的连接请求还没有被服务器应用程序正式接受处理。

        在大多数操作系统中,套接字操作是由操作系统内核负责管理的。内核会为每个监听中的服务器套接字维护一个挂起连接队列。

        如果参数设置的如果队列已满,新的连接请求将被拒绝或被操作系统忽略。

        假设SO_BACKLOG设置为50,这意味着挂起连接队列的最大长度是50。当第51个连接请求到达时,如果前面的请求还没有被处理,新的请求将被拒绝。

3、ulimit -n

ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.childOption(ChannelOption.TCP_NODELAY, true);

       ulimit -n控制了操作系统中一个进程可以打开的最大文件描述符(file descriptor)数量。

什么是文件描述符?

文件描述符是操作系统内核用于管理打开文件的一个抽象概念,包括普通文件、套接字、管道等。每个打开的文件、网络连接都会占用一个文件描述符。

        为什么要设置最大文件描述符

        Netty 是一个高性能的网络框架,设计用于处理大量并发连接。如果文件描述符的限制太低,当连接数超过此限制时,服务器将无法接受新的连接,这将导致连接失败。 

4、TCP_NODELAY

        TCP_NODELAY 是 TCP 协议中的一个选项,用于控制 Nagle 算法的启用或禁用。

        Nagle 算法 在前篇中有所提及,简单的说,当发送方有小数据包要发送时,如果前一个数据包的确认(ACK)尚未收到,Nagle 算法会将这些小数据包暂时存储起来,直到收到前一个数据包的确认或足够多的数据可以组成一个较大的数据包。

        可以通过以下的代码设置是否开启Nagle 算法 ,同样地,这个参数属于ServerSocketChannel

ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.childOption(ChannelOption.TCP_NODELAY, true);

        什么场景下应该禁用Nagle 算法

  • 实时应用:在需要低延迟的实时应用中(例如在线游戏、实时通信应用)。
  • 小数据包频繁发送:如果应用程序频繁发送小数据包,并且对每个数据包的发送延迟敏感。

5、SO_SNDBUF & SO_RCVBUF

        SO_SNDBUF & SO_RCVBUFSO_BACKLOG类似,也是与网络套接字相关的两个重要参数,用于配置发送和接收缓冲区的大小。

        发送缓冲区用于临时存储应用程序要发送到网络的数据。

        接收缓冲区用于临时存储从网络接收到的数据,直到应用程序读取它们。

        缓冲区过大可能增加延迟,因为数据在缓冲区中停留的时间更长;缓冲区过小可能导致频繁的缓冲区溢出和数据包丢失。

        可以通过以下的代码进行设置:

ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.childOption(ChannelOption.SO_SNDBUF, 32 * 1024); // 发送缓冲区大小32KB
bootstrap.childOption(ChannelOption.SO_RCVBUF, 32 * 1024); // 接收缓冲区大小32KB

        如何选择合适的缓冲区大小

  1. 根据网络带宽和延迟:在高带宽和高延迟的网络环境中,需要更大的缓冲区来充分利用带宽。例如,宽带网络和跨国连接可能需要更大的缓冲区。
  2. 根据应用需求:不同的应用有不同的需求。实时应用(如视频流和在线游戏)通常需要较小的缓冲区以减少延迟,而大数据传输(如文件下载和大数据处理)可能需要较大的缓冲区以提高吞吐量。
  3. 测试和调优:最佳的缓冲区大小通常需要通过测试和调优来确定。可以通过逐步调整缓冲区大小并监测网络性能来找到最佳配置。

6、ALLOCATOR

        ALLOCATOR 参数用于配置 ByteBuf 分配器,ByteBuf的相关概念在前篇中也提到过,大致可以分为池化和非池化:

  • PooledByteBufAllocator:池化分配器,重复使用内存以减少分配和释放内存的开销,适用于高并发和性能敏感的应用。
  • UnpooledByteBufAllocator:非池化分配器,每次都进行新的内存分配,适用于内存使用模式不可预测的应用。

        可以通过以下代码进行设置:

ServerBootstrap bootstrap = new ServerBootstrap();// 使用池化分配器
bootstrap.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);// 使用非池化分配器
// bootstrap.option(ChannelOption.ALLOCATOR, UnpooledByteBufAllocator.DEFAULT);

7、RCVBUF_ALLOCATOR

        RCVBUF_ALLOCATOR 是一个用于管理接收缓冲区大小的机制,用于确定和管理网络连接上每次读取操作时分配的字节缓冲区的大小。

        它是一个接口,常用的实现类有:

  • FixedRecvByteBufAllocator:每次读操作分配固定大小的缓冲区。

   

  • DefaultMaxBytesRecvByteBufAllocator:一个可以限制每次读取消息数量的实现。

  •  AdaptiveRecvByteBufAllocator:根据流量动态调整缓冲区大小,这是最常用的实现之一。  

8、RPC简单实现

        接下来会通过一个案例实现简单的RPC框架。

什么是RPC框架?

RPC(Remote Procedure Call,远程过程调用)框架是一种使程序能够通过网络调用远程服务器上的函数或方法的技术。

在表面上这种调用方式对用户是透明的,就像调用本地函数一样简单,但实际上底层会通过网络协议进行通信。

        8.1、1.0版      

        首先需要新增RPC的请求和响应消息:

@Data
public abstract class Message implements Serializable {// 省略旧的代码public static final int RPC_MESSAGE_TYPE_REQUEST = 101;public static final int  RPC_MESSAGE_TYPE_RESPONSE = 102;static {// ...messageClasses.put(RPC_MESSAGE_TYPE_REQUEST, RpcRequestMessage.class);messageClasses.put(RPC_MESSAGE_TYPE_RESPONSE, RpcResponseMessage.class);}}

         然后定义一个RPC请求消息类,在请求消息类中,包括了调用接口及接口中方法的信息:

@Getter
@ToString(callSuper = true)
public class RpcRequestMessage extends Message {/*** 调用的接口全限定名,服务端根据它找到实现*/private String interfaceName;/*** 调用接口中的方法名*/private String methodName;/*** 方法返回类型*/private Class<?> returnType;/*** 方法参数类型数组*/private Class[] parameterTypes;/*** 方法参数值数组*/private Object[] parameterValue;public RpcRequestMessage(int sequenceId, String interfaceName, String methodName, Class<?> returnType, Class[] parameterTypes, Object[] parameterValue) {super.setSequenceId(sequenceId);this.interfaceName = interfaceName;this.methodName = methodName;this.returnType = returnType;this.parameterTypes = parameterTypes;this.parameterValue = parameterValue;}@Overridepublic int getMessageType() {return RPC_MESSAGE_TYPE_REQUEST;}
}

        再定义一个响应消息类,包括正常返回的值以及发生异常时的返回值。

@Data
@ToString(callSuper = true)
public class RpcResponseMessage extends Message {/*** 返回值*/private Object returnValue;/*** 异常值*/private Exception exceptionValue;@Overridepublic int getMessageType() {return RPC_MESSAGE_TYPE_RESPONSE;}
}

        定义一个获取配置文件中接口实现类的工厂类:

public class ServicesFactory {static Properties properties;static Map<Class<?>, Object> map = new ConcurrentHashMap<>();static {try (InputStream in = SerializedConfig.class.getResourceAsStream("/application.properties")) {properties = new Properties();properties.load(in);Set<String> names = properties.stringPropertyNames();for (String name : names) {if (name.endsWith("Service")) {Class<?> interfaceClass = Class.forName(name);Class<?> instanceClass = Class.forName(properties.getProperty(name));map.put(interfaceClass, instanceClass.newInstance());}}} catch (IOException | ClassNotFoundException | InstantiationException | IllegalAccessException e) {throw new ExceptionInInitializerError(e);}}public static <T> T getService(Class<T> interfaceClass) {return (T) map.get(interfaceClass);}
}

        application.properties 

cn.itcast.server.service.HelloService=cn.itcast.server.service.HelloServiceImpl

       HelloService接口及实现类:

public interface HelloService {String sayHello(String name);
}
public class HelloServiceImpl implements HelloService {@Overridepublic String sayHello(String msg) {
//        int i = 1 / 0;return "你好, " + msg;}
}

        准备RPC服务器端和客户端的代码,和聊天室案例类似,但是加上了对应的RPC请求消息和响应消息的处理器:

        服务器端:

/**
*
* RPC服务器端
**/
@Slf4j
public class RpcServer {public static void main(String[] args) {NioEventLoopGroup boss = new NioEventLoopGroup();NioEventLoopGroup worker = new NioEventLoopGroup();LoggingHandler LOGGING_HANDLER = new LoggingHandler(LogLevel.DEBUG);MessageCodecSharable MESSAGE_CODEC = new MessageCodecSharable();RpcRequestMessageHandler RPC_HANDLER = new RpcRequestMessageHandler();try {ServerBootstrap serverBootstrap = new ServerBootstrap();serverBootstrap.channel(NioServerSocketChannel.class);serverBootstrap.group(boss, worker);serverBootstrap.childHandler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel ch) throws Exception {ch.pipeline().addLast(new ProcotolFrameDecoder());ch.pipeline().addLast(LOGGING_HANDLER);ch.pipeline().addLast(MESSAGE_CODEC);ch.pipeline().addLast(RPC_HANDLER);}});Channel channel = serverBootstrap.bind(8080).sync().channel();channel.closeFuture().sync();} catch (InterruptedException e) {log.error("server error", e);} finally {boss.shutdownGracefully();worker.shutdownGracefully();}}
}

        客户端:

/**
*
*RPC 客户端
**/
public class RpcClient {public static void main(String[] args) {NioEventLoopGroup group = new NioEventLoopGroup();LoggingHandler LOGGING_HANDLER = new LoggingHandler(LogLevel.DEBUG);MessageCodecSharable MESSAGE_CODEC = new MessageCodecSharable();// rpc 响应消息处理器,待实现RpcResponseMessageHandler RPC_HANDLER = new RpcResponseMessageHandler();try {Bootstrap bootstrap = new Bootstrap();bootstrap.channel(NioSocketChannel.class);bootstrap.group(group);bootstrap.handler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel ch) throws Exception {ch.pipeline().addLast(new ProcotolFrameDecoder());ch.pipeline().addLast(LOGGING_HANDLER);ch.pipeline().addLast(MESSAGE_CODEC);ch.pipeline().addLast(RPC_HANDLER);}});Channel channel = bootstrap.connect("localhost", 8080).sync().channel();channel.closeFuture().sync();} catch (Exception e) {log.error("client error", e);} finally {group.shutdownGracefully();}}
}

        先编写服务器端的自定义RPC消息处理器RpcRequestMessageHandler

@Slf4j
@ChannelHandler.Sharable
public class RpcRequestMessageHandler extends SimpleChannelInboundHandler<RpcRequestMessage> {@Overrideprotected void channelRead0(ChannelHandlerContext ctx, RpcRequestMessage message) {RpcResponseMessage responseMessage = new RpcResponseMessage();int sequenceId = message.getSequenceId();responseMessage.setSequenceId(sequenceId);try {//获取RPC消息对象中将要调用的接口的实现类 写在配置文件中HelloService service = (HelloService) ServicesFactory.getService(Class.forName(message.getInterfaceName()));//获取实现类中的方法Method method = service.getClass().getMethod(message.getMethodName(), message.getParameterTypes());//通过反射调用方法Object result = method.invoke(service, message.getParameterValue());responseMessage.setReturnValue(result);} catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {e.printStackTrace();responseMessage.setExceptionValue(e);}//触发出站事件ctx.writeAndFlush(responseMessage);}
}

        通过main方法测试一下:

/*** 测试代码* @param args* @throws ClassNotFoundException* @throws NoSuchMethodException* @throws InvocationTargetException* @throws IllegalAccessException*/public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {//封装RPC消息对象RpcRequestMessage message = new RpcRequestMessage(1,"cn.itcast.server.service.HelloService","sayHello",String.class,new Class[]{String.class},new Object[]{"张三"});//获取RPC消息对象中将要调用的接口的实现类HelloService service = (HelloService) ServicesFactory.getService(Class.forName(message.getInterfaceName()));//获取实现类中的方法Method method = service.getClass().getMethod(message.getMethodName(), message.getParameterTypes());//调用方法Object result = method.invoke(service, message.getParameterValue());System.out.println(result);}

        编写客户端的代码以及自定义RPC消息返回处理器RpcResponseMessageHandler 暂时只将接收到的消息返回出去:

@Slf4j
@ChannelHandler.Sharable
public class RpcResponseMessageHandler extends SimpleChannelInboundHandler<RpcResponseMessage> {@Overrideprotected void channelRead0(ChannelHandlerContext ctx, RpcResponseMessage msg) throws Exception {log.debug("{}", msg);}
}

        改造客户端的代码,发送调用方法请求:

   ChannelFuture future = channel.writeAndFlush(new RpcRequestMessage(1,"cn.itcast.server.service.HelloService","sayHello",String.class,new Class[]{String.class},new Object[]{"张三"})).addListener(promise -> {if (!promise.isSuccess()) {Throwable cause = promise.cause();log.error("error", cause);}});

        它的执行顺序是:

        客户端发送消息,触发所有出站处理器:

        然后到服务器:

        在RpcRequestMessageHandler 中无论消息处理是否报错,都会触发出站处理器将返回值传递给客户端:

        最后再回到客户端:

        注意:LOGGING_HANDLER和MESSAGE_CODEC是双向处理,既可以是入站,也可以是出站!


        这样一个简单的RPC通信案例就已经实现了。

        8.2、2.0版

        但是在第一版中,用户在客户端发送调用请求时,需要自己封装RpcRequestMessage 请求对象,参数复杂,换做是我是绝对不愿意这样做的。那么我们对其进行优化。

        改造客户端,首先定义一个成员变量channel:

private static volatile Channel channel = null;

        然后将原有客户端的代码抽取成一个初始化channel的方法:

 private static void initChannel() {NioEventLoopGroup group = new NioEventLoopGroup();LoggingHandler LOGGING_HANDLER = new LoggingHandler(LogLevel.DEBUG);MessageCodecSharable MESSAGE_CODEC = new MessageCodecSharable();RpcResponseMessageHandler RPC_HANDLER = new RpcResponseMessageHandler();Bootstrap bootstrap = new Bootstrap();bootstrap.channel(NioSocketChannel.class);bootstrap.group(group);bootstrap.handler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel ch) throws Exception {ch.pipeline().addLast(new ProcotolFrameDecoder());//双向事件ch.pipeline().addLast(LOGGING_HANDLER);ch.pipeline().addLast(MESSAGE_CODEC);//入站事件ch.pipeline().addLast(RPC_HANDLER);}});try {channel = bootstrap.connect("localhost", 8080).sync().channel();channel.closeFuture().addListener(future -> {group.shutdownGracefully();});} catch (Exception e) {log.error("client error", e);}}

        这个channel只应该存在一个实例,采用双检锁单例的方式获取:

  /*** 初始化单例channel* @return*/private static Channel getChannel(){if (channel != null){return channel;}synchronized (LOCK){if (channel!=null){return channel;}initChannel();return channel;}}

        复习一下,为什么要使用双检锁模式?

        (成员位置的channel可以不用volatile关键字?此时的channel对象不是走构造方法new出来的)

         然后创建一个代理对象代理对象负责将请求参数打包并发送给远程服务器:

 public static <T> T getProxyService(Class<T> serviceClass){ClassLoader classLoader = serviceClass.getClassLoader();Class<?>[] interfaces = new Class[]{serviceClass};Object o = Proxy.newProxyInstance(classLoader, interfaces, (proxy, method, args) -> {//将方法调用转换成消息对象int sequenceId = SequenceIdGenerator.nextId();RpcRequestMessage message = new RpcRequestMessage(sequenceId,serviceClass.getName(),method.getName(),method.getReturnType(),method.getParameterTypes(),args);//发送消息Channel channel = getChannel();channel.writeAndFlush(message);//异步通信获取结果DefaultPromise<Object> objectDefaultPromise = new DefaultPromise<>(channel.eventLoop());//向PROMISE中注册ID和DefaultPromiseRpcResponseMessageHandler.PROMISES.put(sequenceId,objectDefaultPromise);//等待结果objectDefaultPromise.await();if (objectDefaultPromise.isSuccess()){return objectDefaultPromise.getNow();}else {throw new RuntimeException(objectDefaultPromise.cause());}});return (T) o;}

        重点在于向客户端接收服务器响应的RpcResponseMessageHandler  中注册自己的消息ID和promise对象。

//向PROMISE中注册ID和DefaultPromise
RpcResponseMessageHandler.PROMISES.put(sequenceId,objectDefaultPromise);

        这样用户只需要调用代理对象的方法就可以了:

public static void main(String[] args) {HelloService helloService = getProxyService(HelloService.class);System.out.println(helloService.sayHello("张三"));
}

        同时需要修改客户端接受服务器响应的RpcResponseMessageHandler ,去找到对应消息ID的promise对象,并且移除,然后根据服务器返回的结果写入成功或异常情况,这时客户端的

//等待结果
objectDefaultPromise.await();

        获取到了结果,进行最后的处理。

/*** 接受服务器的响应*/
@Slf4j
@ChannelHandler.Sharable
public class RpcResponseMessageHandler extends SimpleChannelInboundHandler<RpcResponseMessage> {/*** k:消息id* v:消息ID对应的promise对象*/public static final ConcurrentHashMap<Integer, Promise<Object>> PROMISES = new ConcurrentHashMap<>();@Overrideprotected void channelRead0(ChannelHandlerContext ctx, RpcResponseMessage msg) throws Exception {int sequenceId = msg.getSequenceId();Promise<Object> promise = PROMISES.remove(sequenceId);Exception exceptionValue = msg.getExceptionValue();Object returnValue = msg.getReturnValue();if (exceptionValue == null) {promise.setSuccess(returnValue);} else {promise.setFailure(exceptionValue);}}
}

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

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

相关文章

Spring Cloud 是什么?(Spring Cloud 组件介绍)

什么是 Spring Cloud&#xff1f; Spring Cloud 是微服务系统架构的一站式解决方案&#xff0c;是各个微服务架构落地技术的集合体&#xff0c;让架构师、 开发者在使用微服务理念构建应用系统的时候&#xff0c; 面对各个环节的问题都可以找到相应的组件来处理&#xff0c;比…

React 19 竞态问题解决

竞态问题/竞态条件 指的是&#xff0c;当我们在交互过程中&#xff0c;由于各种原因导致同一个接口短时间之内连续发送请求&#xff0c;后发送的请求有可能先得到请求结果&#xff0c;从而导致数据渲染出现预期之外的错误。 因为防止重复执行可以有效的解决竞态问题&#xff0…

聊天广场(Vue+WebSocket+SpringBoot)

由于心血来潮想要做个聊天室项目 &#xff0c;但是仔细找了一下相关教程&#xff0c;却发现这么多的WebSocket教程里面&#xff0c;很多都没有介绍详细&#xff0c;代码都有所残缺&#xff0c;所以这次带来一个比较完整得使用WebSocket的项目。 目录 一、效果展示 二、准备工…

html+css+js图片手动轮播

源代码在界面图片后面 轮播演示用的几张图片是Bing上的&#xff0c;直接用的几张图片的URL&#xff0c;谁加载可能需要等一下&#xff0c;现实中替换成自己的图片即可 关注一下点个赞吧&#x1f604; 谢谢大佬 界面图片 源代码 <!DOCTYPE html> <html lang&quo…

安全测试之使用Docker搭建SQL注入安全测试平台sqli-labs

1 搜索镜像 docker search sqli-labs 2 拉取镜像 docker pull acgpiano/sqli-labs 3 创建docker容器 docker run -d --name sqli-labs -p 10012:80 acgpiano/sqli-labs 4 访问测试平台网站 若直接使用虚拟机&#xff0c;则直接通过ip端口号访问若通过配置域名&#xff0…

第十五章 Nest Pipe(内置及自定义)

NestJS的Pipe是一个用于数据转换和验证的特殊装饰器。Pipe可以应用于控制器&#xff08;Controller&#xff09;的处理方法&#xff08;Handler&#xff09;和中间件&#xff08;Middleware&#xff09;&#xff0c;用于处理传入的数据。它可以用来转换和验证数据&#xff0c;确…

【Linux进阶】文件系统5——ext2文件系统(inode)

1.再谈inode (1) 理解inode&#xff0c;要从文件储存说起。 文件储存在硬盘上&#xff0c;硬盘的最小存储单位叫做"扇区"&#xff08;Sector&#xff09;。每个扇区储存512字节&#xff08;相当于0.5KB&#xff09;。操作系统读取硬盘的时候&#xff0c;不会一个个…

记录excel表生成一列按七天一个周期的方法

使用excel生成每七天一个周期的列。如下图所示&#xff1a; 针对第一列的生成办法&#xff0c;使用如下函数&#xff1a; TEXT(DATE(2024,1,1)(ROW()-2)*7,"yyyy/m/d")&" - "&TEXT(DATE(2024,1,1)(ROW()-1)*7-1,"yyyy/m/d") 特此记录。…

charles使用教程

安装与配置 下载链接&#xff1a;https://www.charlesproxy.com/download/ 进行移动端抓包&#xff1a; 电脑端配置&#xff1a; 关闭防火墙 Proxy–>勾选 macOS Proxy Proxy–>Proxy Setting–>填入代理端口8888–>勾选Enable transparent http proxying 安装c…

昇思25天学习打卡营第1天|初识MindSpore

# 打卡 day1 目录 # 打卡 day1 初识MindSpore 昇思 MindSpore 是什么&#xff1f; 昇思 MindSpore 优势|特点 昇思 MindSpore 不足 官方生态学习地址 初识MindSpore 昇思 MindSpore 是什么&#xff1f; 昇思MindSpore 是全场景深度学习架构&#xff0c;为开发者提供了全…

女生学计算机好不好?感觉计算机分有点高……?

众所周知&#xff0c;在国内的高校里&#xff0c;计算机专业的女生是非常少的&#xff0c;很多小班30人左右&#xff0c;但是每个班女生人数只有个位数。这就给很多人一个感觉&#xff0c;是不是女生天生就不适合学这个东西呢&#xff1f;女生是不是也应该放弃呢&#xff1f;当…

常见算法和Lambda

常见算法和Lambda 文章目录 常见算法和Lambda常见算法查找算法基本查找&#xff08;顺序查找&#xff09;二分查找/折半查找插值查找斐波那契查找分块查找扩展的分块查找&#xff08;无规律的数据&#xff09; 常见排序算法冒泡排序选择排序插入排序快速排序递归快速排序 Array…

SpringBoot新手快速入门系列教程二:MySql5.7.44的免安装版本下载和配置,以及简单的Mysql生存指令指南。

我们要如何选择MySql 目前主流的Mysql有5.0、8.0、9.0 主要区别 MySQL 5.0 发布年份&#xff1a;2005年特性&#xff1a; 基础事务支持存储过程、触发器、视图基础存储引擎&#xff08;如MyISAM、InnoDB&#xff09;外键支持基本的全文搜索性能和扩展性&#xff1a; 相对较…

2024年江苏省研究生数学建模竞赛B题火箭烟幕弹运用策略优化论文和代码分析

经过不懈的努力&#xff0c; 2024年江苏省研究生数学建模竞赛B题火箭烟幕弹运用策略优化论文和代码已完成&#xff0c;代码为B题全部问题的代码&#xff0c;论文包括摘要、问题重述、问题分析、模型假设、符号说明、模型的建立和求解&#xff08;问题1模型的建立和求解、问题2模…

[学习笔记]SQL学习笔记(连载中。。。)

学习视频&#xff1a;【数据库】SQL 3小时快速入门 #数据库教程 #SQL教程 #MySQL教程 #database#Python连接数据库 目录 1.SQL的基础知识1.1.表(table)和键(key)1.2.外键、联合主键 2.MySQL安装&#xff08;略&#xff0c;请自行参考视频&#xff09;3.基本的MySQL语法3.1.规…

进程控制-fork函数

一个进程&#xff0c;包括代码、数据和分配给进程的资源。 fork &#xff08;&#xff09;函数通过系统调用创建一个与原来进程几乎完全相同的进程&#xff0c;也就是两个进程可以做完全相同的事&#xff0c;但如果初始参数或者传入的变量不同&#xff0c;两个进程也可以做不同…

DatawhaleAI夏令营2024 Task2

#AI夏令营 #Datawhale #夏令营 赛题解析一、Baseline详解1.1 环境配置1.2 数据处理任务理解2.3 prompt设计2.4 数据抽取 二、完整代码总结 赛题解析 赛事背景 在数字化时代&#xff0c;企业积累了大量对话数据&#xff0c;这些数据不仅是交流记录&#xff0c;还隐藏着宝贵的信…

【鸿蒙学习笔记】@Link装饰器:父子双向同步

官方文档&#xff1a;Link装饰器&#xff1a;父子双向同步 目录标题 [Q&A] Link装饰器作用 [Q&A] Link装饰器特点样例&#xff1a;简单类型样例&#xff1a;数组类型样例&#xff1a;Map类型样例&#xff1a;Set类型样例&#xff1a;联合类型 [Q&A] Link装饰器作用…

信号与系统-实验6-离散时间系统的 Z 域分析

一、实验目的 1、掌握 z 变换及其性质&#xff1b;了解常用序列的 z 变换、逆 z 变换&#xff1b; 2、掌握利用 MATLAB 的符号运算实现 z 变换&#xff1b; 3、掌握利用 MATLAB 绘制离散系统零、极点图的方法&#xff1b; 4、掌握利用 MATLAB 分析离散系统零、极点的方法&a…

MySQL基础篇(二)字符集以及校验规则

在MySQL基础篇&#xff08;一&#xff09;中&#xff0c;我们知道了如何创建数据库&#xff0c;这篇文章带大家了解创建的一些细节。 红色框&#xff1a;可省略&#xff0c;作用如果存在相同的数据库名称&#xff0c;就不会再创建&#xff0c;反之&#xff0c;创建。 蓝色框&…