Dubbo3.x 异步转同步源码

底层netty通信是异步的,那我们平时调用采取的同步是如何将底层的异步转为同步的呢?

dubbo远程rpc协议和网络框架有多种,我们以默认的dubbo协议、网络框架netty作为切入点.

注意点:debug时将过期时间设置长一点:

调用发送消息

1. DubboInvoker

这个类很重要,因为客户端没有具体的实现都是通过代理实现的调用逻辑,而这个类就是最终的工作者,其内部核心方法如下:

    @Overrideprotected Result doInvoke(final Invocation invocation) throws Throwable {// 将Invocation转为RpcInvocation类型RpcInvocation inv = (RpcInvocation) invocation;// 获取方法名final String methodName = RpcUtils.getMethodName(invocation);// 将路径和版本设置为附件inv.setAttachment(PATH_KEY, getUrl().getPath());inv.setAttachment(VERSION_KEY, version);ExchangeClient currentClient;// 获取可用的交换客户端列表List<? extends ExchangeClient> exchangeClients = clientsProvider.getClients();if (exchangeClients.size() == 1) {// 若只有一个客户端则直接使用该客户端currentClient = exchangeClients.get(0);} else {// 若有多个客户端则通过取模操作选择一个客户端currentClient = exchangeClients.get(index.getAndIncrement() % exchangeClients.size());}// 将当前客户端的本地地址设置到RpcContext中RpcContext.getServiceContext().setLocalAddress(currentClient.getLocalAddress());try {// 检查是否是单向调用boolean isOneway = RpcUtils.isOneway(getUrl(), invocation);// 计算超时时间int timeout = RpcUtils.calculateTimeout(getUrl(), invocation, methodName, DEFAULT_TIMEOUT);if (timeout <= 0) {// 若超时时间小于等于0,则返回默认的异步调用结果return AsyncRpcResult.newDefaultAsyncResult(new RpcException(RpcException.TIMEOUT_TERMINATE,"No time left for making the following call: " + invocation.getServiceName() + "."+ RpcUtils.getMethodName(invocation) + ", terminate directly."),invocation);}// 将超时时间设置为附件invocation.setAttachment(TIMEOUT_KEY, String.valueOf(timeout));// 获取数据的大小Integer payload = getUrl().getParameter(PAYLOAD, Integer.class);// 创建Request对象Request request = new Request();if (payload != null) {request.setPayload(payload);}request.setData(inv);request.setVersion(Version.getProtocolVersion());if (isOneway) {// 若为单向调用,则发送请求boolean isSent = getUrl().getMethodParameter(methodName, Constants.SENT_KEY, false);request.setTwoWay(false);currentClient.send(request, isSent);return AsyncRpcResult.newDefaultAsyncResult(invocation);} else {// 若为双向调用request.setTwoWay(true);// 获取回调执行器ExecutorService executor = getCallbackExecutor(getUrl(), inv);CompletableFuture<AppResponse> appResponseFuture =currentClient.request(request, timeout, executor).thenApply(AppResponse.class::cast);// 保存兼容的Futureif (setFutureWhenSync || ((RpcInvocation) invocation).getInvokeMode() != InvokeMode.SYNC) {FutureContext.getContext().setCompatibleFuture(appResponseFuture);}// 返回异步调用结果AsyncRpcResult result = new AsyncRpcResult(appResponseFuture, inv);result.setExecutor(executor);return result;}} catch (TimeoutException e) {// 超时异常处理throw new RpcException(RpcException.TIMEOUT_EXCEPTION,"Invoke remote method timeout. method: " + RpcUtils.getMethodName(invocation) + ", provider: "+ getUrl() + ", cause: " + e.getMessage(),e);} catch (RemotingException e) {// 远程调用异常处理String remoteExpMsg = "Failed to invoke remote method: " + RpcUtils.getMethodName(invocation)+ ", provider: " + getUrl() + ", cause: " + e.getMessage();if (e.getCause() instanceof IOException && e.getCause().getCause() instanceof SerializationException) {throw new RpcException(RpcException.SERIALIZATION_EXCEPTION, remoteExpMsg, e);} else {throw new RpcException(RpcException.NETWORK_EXCEPTION, remoteExpMsg, e);}}}

其中看一下这行代码:

CompletableFuture<AppResponse> appResponseFuture =currentClient.request(request, timeout, executor).thenApply(AppResponse.class::cast);
会调用到org.apache.dubbo.remoting.exchange.support.header.HeaderExchangeClient#request(java.lang.Object, int, java.util.concurrent.ExecutorService)
 @Overridepublic CompletableFuture<Object> request(Object request, int timeout, ExecutorService executor)throws RemotingException {return channel.request(request, timeout, executor);}

然后进入到org.apache.dubbo.remoting.exchange.support.header.HeaderExchangeChannel#request(java.lang.Object, int, java.util.concurrent.ExecutorService)

@Overridepublic CompletableFuture<Object> request(Object request, int timeout, ExecutorService executor)throws RemotingException {if (closed) {throw new RemotingException(this.getLocalAddress(),null,"Failed to send request " + request + ", cause: The channel " + this + " is closed!");}Request req;if (request instanceof Request) {req = (Request) request;} else {// create request.req = new Request();req.setVersion(Version.getProtocolVersion());req.setTwoWay(true);req.setData(request);}DefaultFuture future = DefaultFuture.newFuture(channel, req, timeout, executor);try {channel.send(req);} catch (RemotingException e) {future.cancel();throw e;}return future;}

2. DefaultFuture

  可以看到这里创建了一个DefaultFuture类,而DefaultFuture继承了CompletableFuture<Object>

其中进入到这行中的newFuture方法

DefaultFuture future = DefaultFuture.newFuture(channel, req, timeout, executor);
public static DefaultFuture newFuture(Channel channel, Request request, int timeout, ExecutorService executor) {final DefaultFuture future = new DefaultFuture(channel, request, timeout);future.setExecutor(executor);// timeout checktimeoutCheck(future);return future;}

继续进入new DefaultFuture(channel, request, timeout)

private DefaultFuture(Channel channel, Request request, int timeout) {this.channel = channel;this.request = request;this.id = request.getId();this.timeout = timeout > 0 ? timeout : channel.getUrl().getPositiveParameter(TIMEOUT_KEY, DEFAULT_TIMEOUT);// put into waiting map.FUTURES.put(id, this);CHANNELS.put(id, channel);}

重点:FUTURES.put(id, this); CHANNELS.put(id, channel);

将创建出来的放入map中

private static final Map<Long, DefaultFuture> FUTURES = new ConcurrentHashMap<>()

DefaultFuture类是异步转同步的关键,

当netty监听到有数据返回时,会根据这里的id进行找到对应的DefaultFuture

继续回到org.apache.dubbo.remoting.exchange.support.header.HeaderExchangeChannel#request(java.lang.Object, int, java.util.concurrent.ExecutorService)中的channel.send(req);

它会进入到:

org.apache.dubbo.remoting.transport.AbstractPeer#send

然后继续进入到org.apache.dubbo.remoting.transport.netty4.NettyChannel#send

@Overridepublic void send(Object message, boolean sent) throws RemotingException {// whether the channel is closedsuper.send(message, sent);boolean success = true;int timeout = 0;try {Object outputMessage = message;if (!encodeInIOThread) {ByteBuf buf = channel.alloc().buffer();ChannelBuffer buffer = new NettyBackedChannelBuffer(buf);codec.encode(this, buffer, message);outputMessage = buf;}ChannelFuture future = writeQueue.enqueue(outputMessage).addListener(new ChannelFutureListener() {@Overridepublic void operationComplete(ChannelFuture future) throws Exception {if (!(message instanceof Request)) {return;}ChannelHandler handler = getChannelHandler();if (future.isSuccess()) {handler.sent(NettyChannel.this, message);} else {Throwable t = future.cause();if (t == null) {return;}Response response = buildErrorResponse((Request) message, t);handler.received(NettyChannel.this, response);}}});if (sent) {// wait timeout mstimeout = getUrl().getPositiveParameter(TIMEOUT_KEY, DEFAULT_TIMEOUT);success = future.await(timeout);}Throwable cause = future.cause();if (cause != null) {throw cause;}} catch (Throwable e) {removeChannelIfDisconnected(channel);throw new RemotingException(this,"Failed to send message " + PayloadDropper.getRequestWithoutData(message) + " to "+ getRemoteAddress() + ", cause: " + e.getMessage(),e);}if (!success) {throw new RemotingException(this,"Failed to send message " + PayloadDropper.getRequestWithoutData(message) + " to "+ getRemoteAddress() + "in timeout(" + timeout + "ms) limit");}}

然后看这行:

handler.sent(NettyChannel.this, message);

然后会进入:org.apache.dubbo.remoting.transport.AbstractPeer#sent

然后进入:org.apache.dubbo.remoting.transport.AbstractChannelHandlerDelegate#sent

然后进入:org.apache.dubbo.remoting.exchange.support.header.HeartbeatHandler#sent

org.apache.dubbo.remoting.transport.dispatcher.WrappedChannelHandler#sent

org.apache.dubbo.remoting.transport.AbstractChannelHandlerDelegate#sent

org.apache.dubbo.remoting.exchange.support.header.HeaderExchangeHandler#sent

到这里消息发送出去后,我们还看HeaderExchangeHandler这里的一个重要方法:

@Overridepublic void received(Channel channel, Object message) throws RemotingException {final ExchangeChannel exchangeChannel = HeaderExchangeChannel.getOrAddChannel(channel);if (message instanceof Request) {// handle request.Request request = (Request) message;if (request.isEvent()) {handlerEvent(channel, request);} else {if (request.isTwoWay()) {handleRequest(exchangeChannel, request);} else {handler.received(exchangeChannel, request.getData());}}} else if (message instanceof Response) {handleResponse(channel, (Response) message);} else if (message instanceof String) {if (isClientSide(channel)) {Exception e = new Exception("Dubbo client can not supported string message: " + message+ " in channel: " + channel + ", url: " + channel.getUrl());logger.error(TRANSPORT_UNSUPPORTED_MESSAGE, "", "", e.getMessage(), e);} else {String echo = handler.telnet(channel, (String) message);if (StringUtils.isNotEmpty(echo)) {channel.send(echo);}}} else {handler.received(exchangeChannel, message);}}
看一下这一行:handleResponse(channel, (Response) message);

最终会进入到org.apache.dubbo.remoting.exchange.support.DefaultFuture#received(org.apache.dubbo.remoting.Channel, org.apache.dubbo.remoting.exchange.Response, boolean)

public static void received(Channel channel, Response response, boolean timeout) {try {DefaultFuture future = FUTURES.remove(response.getId());if (future != null) {Timeout t = future.timeoutCheckTask;if (!timeout) {// decrease Timet.cancel();}future.doReceived(response);shutdownExecutorIfNeeded(future);} else {logger.warn(PROTOCOL_TIMEOUT_SERVER,"","","The timeout response finally returned at "+ (new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date()))+ ", response status is " + response.getStatus()+ (channel == null? "": ", channel: " + channel.getLocalAddress() + " -> "+ channel.getRemoteAddress())+ ", please check provider side for detailed result.");}} finally {CHANNELS.remove(response.getId());}}

然后进入到org.apache.dubbo.remoting.exchange.support.DefaultFuture#doReceived

private void doReceived(Response res) {if (res == null) {throw new IllegalStateException("response cannot be null");}if (res.getStatus() == Response.OK) {this.complete(res.getResult());} else if (res.getStatus() == Response.CLIENT_TIMEOUT || res.getStatus() == Response.SERVER_TIMEOUT) {this.completeExceptionally(new TimeoutException(res.getStatus() == Response.SERVER_TIMEOUT, channel, res.getErrorMessage()));} else if (res.getStatus() == Response.SERIALIZATION_ERROR) {this.completeExceptionally(new SerializationException(res.getErrorMessage()));} else {this.completeExceptionally(new RemotingException(channel, res.getErrorMessage()));}}

看一下这行:this.complete(res.getResult());

这就是利用了:

CompletableFuture 类中的 complete() 方法用于手动完成一个异步任务,并设置其结果。通过调用 complete() 方法,可以将一个特定的结果设置到 CompletableFuture 对象中,然后任何等待该异步任务的操作都会得到这个预先设置的结果。

注意:

一旦调用了 complete() 方法,CompletableFuture 对象的状态会立即变为已完成,而且之后任何对该对象的计算都不会再触发异步任务的执行。如果该对象已经处于完成状态,再次调用 complete() 方法不会有任何效果。
如果异步任务已经抛出了异常,调用 complete() 方法将不会有任何效果。此时,可以使用 completeExceptionally(Throwable ex) 方法手动设置异步任务的异常结果。
如果有多个线程同时尝试调用 complete() 方法,只有第一个成功的线程能够设置结果,其他线程的调用将被忽略。

CompletableFuture get()调用会阻塞等待结果,只要执行了complete(T value)就会立即得到结果

那我们接下来会想,那HeaderExchangeHandler中的received是何时被调用的呢?

就是netty监听到有返回值时调用的,会调用到org.apache.dubbo.remoting.transport.netty4.NettyClientHandler#channelRead

然后进入到org.apache.dubbo.remoting.transport.AbstractPeer#received

可以看下图:

最终进入到:org.apache.dubbo.remoting.exchange.support.header.HeaderExchangeHandler#received

然后回到org.apache.dubbo.rpc.protocol.dubbo.DubboInvoker#doInvoke

这个方法是org.apache.dubbo.rpc.protocol.AbstractInvoker#invoke调用的,看一下这个方法:

@Overridepublic Result invoke(Invocation inv) throws RpcException {// if invoker is destroyed due to address refresh from registry, let's allow the current invoke to proceedif (isDestroyed()) {logger.warn(PROTOCOL_FAILED_REQUEST,"","","Invoker for service " + this + " on consumer " + NetUtils.getLocalHost() + " is destroyed, "+ ", dubbo version is " + Version.getVersion()+ ", this invoker should not be used any longer");}RpcInvocation invocation = (RpcInvocation) inv;// prepare rpc invocationprepareInvocation(invocation);// do invoke rpc invocation and return async resultAsyncRpcResult asyncResult = doInvokeAndReturn(invocation);// wait rpc result if syncwaitForResultIfSync(asyncResult, invocation);return asyncResult;}

其中waitForResultIfSync(asyncResult, invocation);就是同步阻塞等待

这个方法中的asyncResult.get(timeout, TimeUnit.MILLISECONDS);会进入到:org.apache.dubbo.rpc.AsyncRpcResult#get(long, java.util.concurrent.TimeUnit)

@Overridepublic Result get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {long deadline = System.nanoTime() + unit.toNanos(timeout);if (executor instanceof ThreadlessExecutor) {ThreadlessExecutor threadlessExecutor = (ThreadlessExecutor) executor;try {while (!responseFuture.isDone() && !threadlessExecutor.isShutdown()) {long restTime = deadline - System.nanoTime();if (restTime > 0) {threadlessExecutor.waitAndDrain(deadline);} else {throw new TimeoutException("Timeout after " + unit.toMillis(timeout) + "ms waiting for result.");}}} finally {threadlessExecutor.shutdown();}}long restTime = deadline - System.nanoTime();if (!responseFuture.isDone() && restTime < 0) {throw new TimeoutException("Timeout after " + unit.toMillis(timeout) + "ms waiting for result.");}return responseFuture.get(restTime, TimeUnit.NANOSECONDS);}

方法的最后一行:responseFuture.get(restTime, TimeUnit.NANOSECONDS)就是调用的java.util.concurrent.CompletableFuture#get(long, java.util.concurrent.TimeUnit)

同步阻塞等待结果

这里注意:org.apache.dubbo.rpc.protocol.AbstractInvoker#invoke返回的是AsyncRpcResult类型的结果,那真正将AsyncRpcResult中的result拿出来的是哪里呢?

是org.apache.dubbo.rpc.proxy.InvocationUtil#invoke这个方法中的

invoker.invoke(rpcInvocation).recreate()

我们进入recreate()看一下:org.apache.dubbo.rpc.AsyncRpcResult#recreate

@Overridepublic Object recreate() throws Throwable {RpcInvocation rpcInvocation = (RpcInvocation) invocation;if (InvokeMode.FUTURE == rpcInvocation.getInvokeMode()) {return RpcContext.getClientAttachment().getFuture();} else if (InvokeMode.ASYNC == rpcInvocation.getInvokeMode()) {return createDefaultValue(invocation).recreate();}return getAppResponse().recreate();}

然后先看org.apache.dubbo.rpc.AsyncRpcResult#getAppResponse方法:

public Result getAppResponse() {try {if (responseFuture.isDone()) {return responseFuture.get();}} catch (Exception e) {// This should not happen in normal request process;logger.error(PROXY_ERROR_ASYNC_RESPONSE,"","","Got exception when trying to fetch the underlying result from AsyncRpcResult.");throw new RpcException(e);}return createDefaultValue(invocation);}
responseFuture.get()会拿到org.apache.dubbo.remoting.exchange.support.DefaultFuture#doReceived方法中complete(res.getResult())的值

即AppResponse类型:

然后回到getAppResponse().recreate();再进入到:org.apache.dubbo.rpc.AppResponse#recreate

@Overridepublic Object recreate() throws Throwable {if (exception != null) {// fix issue#619try {Object stackTrace = exception.getStackTrace();if (stackTrace == null) {exception.setStackTrace(new StackTraceElement[0]);}} catch (Exception e) {// ignore}if (Dubbo2CompactUtils.isEnabled()&& Dubbo2RpcExceptionUtils.isRpcExceptionClassLoaded()&& (exception instanceof RpcException)&& !Dubbo2RpcExceptionUtils.getRpcExceptionClass().isAssignableFrom(exception.getClass())) {RpcException recreated = Dubbo2RpcExceptionUtils.newRpcException(((RpcException) exception).getCode(), exception.getMessage(), exception.getCause());if (recreated != null) {recreated.setStackTrace(exception.getStackTrace());throw recreated;}}throw exception;}return result;}

拿到最终想要得到的result。

最后补充一下其中的监听是否超时任务

3. 定时任务

org.apache.dubbo.common.resource.GlobalResourceInitializer

我们看一下DefaultFuture类:

    private static final GlobalResourceInitializer<Timer> TIME_OUT_TIMER = new GlobalResourceInitializer<>(() -> new HashedWheelTimer(new NamedThreadFactory("dubbo-future-timeout", true), 30, TimeUnit.MILLISECONDS),DefaultFuture::destroy);
看一下 new HashedWheelTimer方法org.apache.dubbo.common.timer.HashedWheelTimer#HashedWheelTimer(java.util.concurrent.ThreadFactory, long, java.util.concurrent.TimeUnit, int, long)
public HashedWheelTimer(ThreadFactory threadFactory,long tickDuration, TimeUnit unit, int ticksPerWheel,long maxPendingTimeouts) {if (threadFactory == null) {throw new NullPointerException("threadFactory");}if (unit == null) {throw new NullPointerException("unit");}if (tickDuration <= 0) {throw new IllegalArgumentException("tickDuration must be greater than 0: " + tickDuration);}if (ticksPerWheel <= 0) {throw new IllegalArgumentException("ticksPerWheel must be greater than 0: " + ticksPerWheel);}// Normalize ticksPerWheel to power of two and initialize the wheel.wheel = createWheel(ticksPerWheel);mask = wheel.length - 1;// Convert tickDuration to nanos.this.tickDuration = unit.toNanos(tickDuration);// Prevent overflow.if (this.tickDuration >= Long.MAX_VALUE / wheel.length) {throw new IllegalArgumentException(String.format("tickDuration: %d (expected: 0 < tickDuration in nanos < %d",tickDuration, Long.MAX_VALUE / wheel.length));}workerThread = threadFactory.newThread(worker);this.maxPendingTimeouts = maxPendingTimeouts;if (INSTANCE_COUNTER.incrementAndGet() > INSTANCE_COUNT_LIMIT &&WARNED_TOO_MANY_INSTANCES.compareAndSet(false, true)) {reportTooManyInstances();}}

其中:workerThread = threadFactory.newThread(worker);

而work为:Worker worker = new Worker();

然后接着看DefaultFuture类中的org.apache.dubbo.remoting.exchange.support.DefaultFuture#timeoutCheck:

    private static void timeoutCheck(DefaultFuture future) {TimeoutCheckTask task = new TimeoutCheckTask(future.getId());future.timeoutCheckTask = TIME_OUT_TIMER.get().newTimeout(task, future.getTimeout(), TimeUnit.MILLISECONDS);}

其中的newTimeout方法:

@Overridepublic Timeout newTimeout(TimerTask task, long delay, TimeUnit unit) {if (task == null) {throw new NullPointerException("task");}if (unit == null) {throw new NullPointerException("unit");}long pendingTimeoutsCount = pendingTimeouts.incrementAndGet();if (maxPendingTimeouts > 0 && pendingTimeoutsCount > maxPendingTimeouts) {pendingTimeouts.decrementAndGet();throw new RejectedExecutionException("Number of pending timeouts ("+ pendingTimeoutsCount + ") is greater than or equal to maximum allowed pending "+ "timeouts (" + maxPendingTimeouts + ")");}start();// Add the timeout to the timeout queue which will be processed on the next tick.// During processing all the queued HashedWheelTimeouts will be added to the correct HashedWheelBucket.long deadline = System.nanoTime() + unit.toNanos(delay) - startTime;// Guard against overflow.if (delay > 0 && deadline < 0) {deadline = Long.MAX_VALUE;}HashedWheelTimeout timeout = new HashedWheelTimeout(this, task, deadline);timeouts.add(timeout);return timeout;}

然后看其中的start()方法

public void start() {switch (WORKER_STATE_UPDATER.get(this)) {case WORKER_STATE_INIT:if (WORKER_STATE_UPDATER.compareAndSet(this, WORKER_STATE_INIT, WORKER_STATE_STARTED)) {workerThread.start();}break;case WORKER_STATE_STARTED:break;case WORKER_STATE_SHUTDOWN:throw new IllegalStateException("cannot be started once stopped");default:throw new Error("Invalid WorkerState");}// Wait until the startTime is initialized by the worker.while (startTime == 0) {try {startTimeInitialized.await();} catch (InterruptedException ignore) {// Ignore - it will be ready very soon.}}}
workerThread.start();

而Worker是一个Runnable,所以会调用到run()

org.apache.dubbo.common.timer.HashedWheelTimer.Worker#run

@Overridepublic void run() {// Initialize the startTime.startTime = System.nanoTime();if (startTime == 0) {// We use 0 as an indicator for the uninitialized value here, so make sure it's not 0 when initialized.startTime = 1;}// Notify the other threads waiting for the initialization at start().startTimeInitialized.countDown();do {final long deadline = waitForNextTick();if (deadline > 0) {int idx = (int) (tick & mask);processCancelledTasks();HashedWheelBucket bucket =wheel[idx];transferTimeoutsToBuckets();bucket.expireTimeouts(deadline);tick++;}} while (WORKER_STATE_UPDATER.get(HashedWheelTimer.this) == WORKER_STATE_STARTED);// Fill the unprocessedTimeouts so we can return them from stop() method.for (HashedWheelBucket bucket : wheel) {bucket.clearTimeouts(unprocessedTimeouts);}for (; ; ) {HashedWheelTimeout timeout = timeouts.poll();if (timeout == null) {break;}if (!timeout.isCancelled()) {unprocessedTimeouts.add(timeout);}}processCancelledTasks();}

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

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

相关文章

使用vue3+ts+vite从零开始搭建bolg(五):layout(持续更新中)

五、layout搭建 5.1静态搭建 在src下创建如图文件夹 这里用logo举例&#xff0c;在scripts里export <script lang"ts">export default {name: Logo,}</script> 然后在layout里引入 //引入左侧菜单顶部用户信息 import Logo from ./logo/index.vue 接…

java版数据结构:堆,大根堆,小根堆

目录 堆的基本概念&#xff1a; 如何将一个二叉树调整成一个大根堆&#xff1a; 转成大根堆的时间复杂度 根堆中的插入&#xff0c;取出数据&#xff1a; 堆的基本概念&#xff1a; 堆是一种特殊的树形数据结构&#xff0c;它满足以下两个性质&#xff1a; 堆是一个完全二叉…

【半夜学习MySQL】表结构的操作(含表的创建、修改、删除操作,及如何查看表结构)

&#x1f3e0;关于专栏&#xff1a;半夜学习MySQL专栏用于记录MySQL数据相关内容。 &#x1f3af;每天努力一点点&#xff0c;技术变化看得见 文章目录 创建表查看表结构修改表删除表 创建表 语法&#xff1a; create table table_name(field1 datatype,field2 datatype,fiel…

JWT令牌技术实现登录校验

一.简单登录功能 在登录界面中&#xff0c;我们可以输入用户的用户名以及密码&#xff0c;然后点击 "登录" 按钮就要请求服务器&#xff0c;服务端判断用户输入的用户名或者密码是否正确。如果正确&#xff0c;则返回成功结果&#xff0c;跳转至系统首页面。 1.功能…

[笔试训练](二十二)064:添加字符065:数组变换066:装箱问题

目录 064:添加字符 065:数组变换 066:装箱问题 064:添加字符 添加字符_牛客笔试题_牛客网 (nowcoder.com) 题目&#xff1a; 题解&#xff1a; 枚举所有A&#xff0c;B字符串可能的对应位置&#xff0c;得出对应位置不同字符数量的最小情况 两字符串的字符数量差n-m&…

springboot月度员工绩效考核管理系统

摘要 本月度员工绩效考核管理系统采用java语言做为代码编写工具&#xff0c;采用mysql数据库进行系统中信息的存储与处理。框架采用springboot。 本系统的功能分为管理员和员工两个角色&#xff0c;管理员的功能有&#xff1a; &#xff08;1&#xff09;个人中心管理功能&am…

“Linux”目录结构and配置网络

了解完命令格式和vi、vim编辑器后&#xff0c;我们来认识一下目录的结构&#xff1a; 一、目录 &#xff08;1&#xff09;目录的特点 windows特点&#xff1a; Windows中有C、D、E盘&#xff0c;每个都是一个根系统 Linux特点&#xff1a; linux中只有一个根&#xff08;单…

冯喜运:5.14黄金大幅度修正?原油价格下跌成拖累?

【黄金消息面分析】&#xff1a;本周重要的美国数据的发布可能会对美元以及黄金产生重大影响。周四将公布更多经济指标&#xff0c;包括新屋开工和许可证、费城联储指数、工业生产数据和每周初请失业金人数。对于黄金而言&#xff0c;人们的注意力集中在经济和劳动力市场疲软对…

DRF 纯净版创建使用

【一】介绍 &#xff08;1&#xff09;使用原因 在Django中&#xff0c;contrib 包包含了许多内置的app和中间件&#xff0c;如auth、sessions、admin等&#xff0c;这些app在创建新的Django项目时默认是包含在内的。然而&#xff0c;在开发RESTful API时&#xff0c;可能不需…

jenkis

文章目录 安装插件配置构建超时自动停止 安装插件 在线安装&#xff1a;安装jenkins后&#xff0c;初次启动的时候安装插件 在线安装&#xff1a;插件管理&#xff0c;可选插件中 离线安装&#xff1a;下载插件(.hpi格式) 使用该方法安装插件每次只能安装一个插件&#xff0c;…

基于RTL8710BN与天猫精灵的WIFI智能家居方案

0 项目简介 目的&#xff1a; 语音控制智能家居产品 基于阿里云的物联网产品 基于WiFi技术的嵌入式产品 主要技术&#xff1a; WiFi技术 常用的物联网协议 网络编程 云平台配置 MCU OPENSDK开发 阿里物联网操作系统 硬件&#xff1a; wifi开发板RTL8710BN 天猫精灵…

大数据可视化实验(五):Tableau数据可视化

目录 一、实验目的... 1 二、实验环境... 1 三、实验内容... 1 1&#xff09;打开数据源... 1 2&#xff09;进入工作簿... 2 3&#xff09;字段设置... 2 4&#xff09;数据筛选... 3 5&#xff09;绘制条形图... 3 四、思考问题... 4 五、总结与心得体会... 4 一、…

5月14(信息差)

&#x1f30d;字节携港大南大升级 LLaVA-NeXT&#xff1a;借 LLaMA-3 和 Qwen-1.5 脱胎换骨&#xff0c;轻松追平 GPT-4V Demo 链接&#xff1a;https://llava-next.lmms-lab.com/ &#x1f384;阿里巴巴开源的15个顶级Java项目 ✨ 欧洲在线订餐服务Takeaway.com&#xff1a…

JavaScript中带日期的操作

当我们把日期转换为Number类型的时候&#xff0c;就会变成时间戳&#xff08;毫秒&#xff09; const future new Date(2037, 10, 19, 15, 23); console.log(Number(future)); // console.log(future); //与上行代码等效● 所以我们就可以利用时间戳去做点东西&#xff0c;例…

GPT-4o:融合文本、音频和图像的全方位人机交互体验

引言&#xff1a; GPT-4o&#xff08;“o”代表“omni”&#xff09;的问世标志着人机交互领域的一次重要突破。它不仅接受文本、音频和图像的任意组合作为输入&#xff0c;还能生成文本、音频和图像输出的任意组合。这一全新的模型不仅在响应速度上达到了惊人的水平&#xff0…

qt cmake加入程序exe图标

可以看到qt自动编译出来的图标是默认的&#xff0c;如下图所示 我想要更改成自定义的图标&#xff0c;比如下方的样子 下边是操作步骤&#xff1a; 图标选择与转化成ico 通过这个网站将正常图片转化成ico&#xff1a;https://www.bitbug.net/创建rc文件 将ico复制到cmakelis…

短视频拍摄+直播间搭建视觉艺术实战课:手把手场景演绎 从0-1短视频-8节课

抖音短视频和直播间你是否遇到这些问题? 短视频是用手机拍还是相机拍?画面怎么拍都没有质感 短视频产量低&#xff0c;拍的素材可用率低 看到别人用手机就能把短视频拍好自己却无从下手 明明已经打了好几盏灯了,但是画面还是比较暗 直播软件参数不会设置&#xff0c;电脑…

纯电动汽车的发展趋势简述

纯电车简介 纯电动汽车是使用电池驱动电动马达而不是传统的内燃机的汽车。它们通常使用电池组储存能量&#xff0c;然后通过电动马达转化为动力来驱动车辆。相比于传统的燃油车&#xff0c;纯电动汽车具有零排放、低噪音、低维护成本等优点&#xff0c;因此在环保和能源效率方…

数据新探:用Python挖掘互联网的隐藏宝藏

Hello&#xff0c;我是你们的阿佑&#xff0c;今天给大家上的菜是——数据存储&#xff01;听起来枯燥无味&#xff1f;错了&#xff01;阿佑将带你重新认识数据存储的艺术。就像为珍贵的艺术品寻找完美的展览馆&#xff0c;为你的数据选择合适的存储方式同样重要&#xff01; …

版本控制:软件开发的基石(一文读懂版本控制)

未经允许&#xff0c;禁止转载&#xff01; 在现代软件开发中&#xff0c;版本控制是不可或缺的工具。它帮助开发者跟踪和管理代码的变化&#xff0c;协作完成项目&#xff0c;并确保代码的完整性和安全性。本文将基于Git官网的视频“什么是版本控制”来深入探讨版本控制的基本…