Netty源码NioEventLoop解析

带着问题源码

  • Netty 的 NioEventLoop 是如何实现的?它为什么能够保证 Channel 的操作是线程安全的?
  • Netty 如何解决 JDK epoll 空轮询 Bug?
  • NioEventLoop 是如何实现无锁化的?

一、作用与设计原理

        Netty的NioEventLoop并不是一个存粹的I/O线程,除了负责I/O的读写外(用于处理 Channel 生命周期内的所有I/O事件,如accept、connect、read、write等I/O事件),还负责处理系统任务和延迟任务(定时任务);

主要就是做3个事:轮询 I/O 事件,处理 I/O 事件,处理异步任务队列

1.1 系统任务&延迟任务

NioEventLoop 内部有两个非常重要的异步任务队列,分别为普通任务队列和定时任务队列。

// 普通任务队列
private final Queue<Runnable> taskQueue;默认使用的是 Mpsc Queue(多生产者单消费者队列)
static <T> Queue<T> newMpscQueue() {return USE_MPSC_CHUNKED_ARRAY_QUEUE ? new MpscUnboundedArrayQueue<T>(MPSC_CHUNK_SIZE): new MpscUnboundedAtomicArrayQueue<T>(MPSC_CHUNK_SIZE);}多个外部线程可能会并发操作同一个Channel,用来保证线程的安全性// 统计任务等收尾动作
private final Queue<Runnable> tailTasks// 延迟队列
PriorityQueue<ScheduledFutureTask<?>> scheduledTaskQueue;scheduledTaskQueue = new DefaultPriorityQueue<ScheduledFutureTask<?>>(SCHEDULED_FUTURE_TASK_COMPARATOR,11);

1.1.1 系统任务

       通过execute(Runnable task)方法实现,目的:当I/O线程和用户线程同时操作网络资源时,为了防止并发操作导致的锁竞争,将用户线程封装成task放入到普通队列中,由I/O线程负责执行

public void execute(Runnable task) {if (task == null) {throw new NullPointerException("task");}// 判断thread == this.thread;是否为当前EventExecutor内部线程,可能为其他线程调用该方法boolean inEventLoop = inEventLoop();// 加入到普通队列addTask(task);if (!inEventLoop) {// #AbstractChannel#write 可以写入非内部线程的任务startThread();if (isShutdown()) {boolean reject = false;try {if (removeTask(task)) {reject = true;}} catch (UnsupportedOperationException e) {}if (reject) {reject();}}}if (!addTaskWakesUp && wakesUpForTask(task)) {wakeup(inEventLoop);}
}protected void addTask(Runnable task) {if (task == null) {throw new NullPointerException("task");}if (!offerTask(task)) {reject(task);}
}final boolean offerTask(Runnable task) {if (isShutdown()) {reject();}return taskQueue.offer(task);
}

 1.1.2延迟任务

       ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) 对应的方法实现,具体实现后续专讲

二 、线程执行源码( 4.1.42.Final源码)

protected void run() {for (;;) {try {try {// 如果存在就绪I/O事件那么会返回对应就绪Channel的数量>=0进入default条件switch (selectStrategy.calculateStrategy(selectNowSupplier, hasTasks())) {case SelectStrategy.CONTINUE:continue;case SelectStrategy.BUSY_WAIT:case SelectStrategy.SELECT:// 无任务,则进行轮询I/O事件select(wakenUp.getAndSet(false)); // 轮询 I/O 事件if (wakenUp.get()) {selector.wakeup();}default:}} catch (IOException e) {// 重新构建SelectorrebuildSelector0();handleLoopException(e);continue;}cancelledKeys = 0;needsToSelectAgain = false;final int ioRatio = this.ioRatio;if (ioRatio == 100) {try {processSelectedKeys(); // 处理 I/O 事件} finally {runAllTasks(); // 处理异步任务队列}} else {final long ioStartTime = System.nanoTime();try {processSelectedKeys(); // 处理 I/O 事件} finally {final long ioTime = System.nanoTime() - ioStartTime;runAllTasks(ioTime * (100 - ioRatio) / ioRatio); // 处理完 I/O 事件,再处理异步任务队列}}} catch (Throwable t) {handleLoopException(t);}try {if (isShuttingDown()) {closeAll();if (confirmShutdown()) {return;}}} catch (Throwable t) {handleLoopException(t);}}
}

2.1 轮询 I/O 事件

selectStrategy.calculateStrategy(selectNowSupplier, hasTasks())//实际调用
public int calculateStrategy(IntSupplier selectSupplier, boolean hasTasks) throws Exception {return hasTasks ? selectSupplier.get() : SelectStrategy.SELECT;
}// NioEventLoop#selectNowSupplier
private final IntSupplier selectNowSupplier = new IntSupplier() {@Overridepublic int get() throws Exception {// 若存在任务,则调用Selector选择器中的提供的非阻塞方法,// 执行后会立刻返回如果当前已经有就绪的Channel,会返回对应就绪Channel的数量否则返回0.return selectNow();}
}
// NioEventLoop#selectNow
int selectNow() throws IOException {try {return selector.selectNow();} finally {if (wakenUp.get()) {selector.wakeup();}}
}// 异步任务队列taskQueue和用于统计信息任务用的尾部队列tailTask是否有异步任务
protected boolean hasTasks() {return super.hasTasks() || !tailTasks.isEmpty();
}

      可以得出在 I/O 事件循环的过程中 Netty 选择使用策略的具体判断步骤:

1、如果存在系统任务,则会执行selector.selectNow();并走到 default 分支后直接跳出,然后执行 I/O 事件处理 processSelectedKeys 和任务队列处理 runAllTasks 的逻辑。优先保证 CPU 能够及时处理系统任务。

2、如果不存在系统任务即任务队列为空,返回的是 SELECT 策略, 就会调用 select(boolean oldWakenUp) 方法

2.2 select(boolean oldWakenUp)

 private void select(boolean oldWakenUp) throws IOException {Selector selector = this.selector;try {//计数器置0int selectCnt = 0;long currentTimeNanos = System.nanoTime();//根据注册的定时任务,获取本次select的阻塞时间long selectDeadLineNanos = currentTimeNanos + this.delayNanos(currentTimeNanos);while(true) {//每次循环迭代都重新计算一次select的可阻塞时间long timeoutMillis = (selectDeadLineNanos - currentTimeNanos + 500000L) / 1000000L;//如果可阻塞时间为0,表示已经有定时任务快要超时//此时如果是第一次循环(selectCnt=0),则调用一次selector.selectNow,然后退出循环返回//selectorNow方法的调用主要是为了尽可能检测出准备好的网络事件进行处理if (timeoutMillis <= 0L) {if (selectCnt == 0) {                     selector.selectNow();selectCnt = 1;}break;}//如果没有定时任务超时,但是有以前注册的任务(这里不限定是定时任务),//且成功设置wakenUp为true,则调用selectNow并返回if (this.hasTasks() && this.wakenUp.compareAndSet(false, true)) {selector.selectNow();selectCnt = 1;break;}//调用select方法,阻塞时间为上面算出的最近一个将要超时的定时任务时间int selectedKeys = selector.select(timeoutMillis);//计数器加1++selectCnt;if (selectedKeys != 0 || oldWakenUp || this.wakenUp.get() || this.hasTasks() || this.hasScheduledTasks()) {//进入这个分支,表示正常场景//selectedKeys != 0: selectedKeys个数不为0, 有io事件发生//oldWakenUp:表示进来时,已经有其他地方对selector进行了唤醒操作//wakenUp.get():也表示selector被唤醒//hasTasks() || hasScheduledTasks():表示有任务或定时任务要执行//发生以上几种情况任一种则直接返回break;}//如果线程被中断,计数器置零,直接返回if (Thread.interrupted()) {if (logger.isDebugEnabled()) {logger.debug("Selector.select() returned prematurely because Thread.currentThread().interrupt() was called. Use NioEventLoop.shutdownGracefully() to shutdown the NioEventLoop.");}selectCnt = 1;break;}//这里判断select返回是否是因为计算的超时时间已过,//这种情况下也属于正常返回,计数器置1,进入下次循环long time = System.nanoTime();if (time - TimeUnit.MILLISECONDS.toNanos(timeoutMillis) >= currentTimeNanos) {//进入这个分支,表示超时,属于正常的场景//说明发生过一次阻塞式轮询, 并且超时selectCnt = 1;} else if (SELECTOR_AUTO_REBUILD_THRESHOLD > 0 && selectCnt >= SELECTOR_AUTO_REBUILD_THRESHOLD) {//进入这个分支,表示没有超时,同时 selectedKeys==0//属于异常场景//表示启用了select bug修复机制,//即配置的io.netty.selectorAutoRebuildThreshold//参数大于3,且上面select方法提前返回次数已经大于//配置的阈值,则会触发selector重建//进行selector重建//重建完之后,尝试调用非阻塞版本select一次,并直接返回selector = this.selectRebuildSelector(selectCnt);selectCnt = 1;break;}currentTimeNanos = time;}//这种是对于关闭select bug修复机制的程序的处理,//简单记录日志,便于排查问题if (selectCnt > 3 && logger.isDebugEnabled()) {logger.debug("Selector.select() returned prematurely {} times in a row for Selector {}.", selectCnt - 1, selector);}} catch (CancelledKeyException var13) {if (logger.isDebugEnabled()) {logger.debug(CancelledKeyException.class.getSimpleName() + " raised by a Selector {} - JDK bug?", selector, var13);}}}

第一步:检测若有定时任务要超时,则优先处理异步任务

//该任务指定的延迟时间差值时间-(当前时间-delayedTask创建的时间) protected long delayNanos(long currentTimeNanos) {//当前纳秒减去启动纳秒,相当于一个自增的时间差值currentTimeNanos -= initialNanoTime();ScheduledFutureTask<?> scheduledTask = peekScheduledTask();if (scheduledTask == null) {return SCHEDULE_PURGE_INTERVAL;}// 得出最终触发还有多久return scheduledTask.delayNanos(currentTimeNanos);}// 根据当前参数传入的时间返回参数时间距离deadline触发还有多久public long delayNanos(long currentTimeNanos) {return deadlineToDelayNanos(currentTimeNanos, deadlineNanos);}static long deadlineToDelayNanos(long currentTimeNanos, long deadlineNanos) {return deadlineNanos == 0L ? 0L : Math.max(0L, deadlineNanos - currentTimeNanos);}

首先deadlineNanos为差值时间具体值如下:

private long deadlineNanos;deadlineNanos(getCurrentTimeNanos(), unit.toNanos(initialDelay))deadlineNanos为延迟任务创建时间-系统创建时间+delaystatic long deadlineNanos(long nanoTime, long delay) {long deadlineNanos = nanoTime + delay;// Guard against overflowreturn deadlineNanos < 0 ? Long.MAX_VALUE : deadlineNanos;}

delayNanos计算结果为差值时间,还有多久延迟任务就要触发,如1s后;

selectDeadLineNanos 为当前时间+差值时间

timeoutMillis 为(任务多久后触发+0.5ms)/1000000L, 如果截止时间小于0.5ms,则timeoutMillis 为0,直接调非阻塞的selectNow()方法;若大于则进行阻塞,旧版本以毫秒为判断点。

有定时任务情况,select会在定时任务到期时返回。(存在定时任务到期很久的情况,这里补充知识:其他线程如果因为调用了selector.select()或者selector.select(long)这两个方法而阻塞,调用了selector.wakeup()之后,就会立即返回结果,并且返回的值!=0,所以在异步任务新增的时候,都会进行wakeup())

如果没有定时任务,delayNanos(currentTimeNanos)返回的值TimeUnit.SECONDS.toNanos(1),即1秒; select会在检查到任何NIO事件或executor任务时返回

第二步:若有异步任务,则优先执行任务

this.hasTasks() && this.wakenUp.compareAndSet(false, true)

第三步:执行selcor(long)

解决Epollbug:

1、周期统计:事件轮询时间小于超时时间,并且在该时间周期内连续发生超过 SELECTOR_AUTO_REBUILD_THRESHOLD(默认512)次空轮询,说明可能触发了epoll空轮询 Bug

2、重建Selector

    private Selector selectRebuildSelector(int selectCnt) throws IOException {logger.warn("Selector.select() returned prematurely {} times in a row; rebuilding Selector {}.", selectCnt, this.selector);//进行selector重建this.rebuildSelector();Selector selector = this.selector;//重建完之后,尝试调用非阻塞版本select一次,并直接返回selector.selectNow();return selector;}

2.3 处理 I/O 事件 

调用 processSelectedKeys() 方法处理 I/O 事件,Netty 通过 ioRatio参数控制I/O事件处理和任务处理的时间比例,默认为 ioRatio = 50。如果 ioRatio = 100表示每次都处理完 I/O 事件后,会执行所有的 task。如果 ioRatio < 100,也会优先处理完 I/O 事件,再处理异步任务队列。所以不论如何 processSelectedKeys() 都是先执行的,接下来跟进下 processSelectedKeys() 的源码:

private void processSelectedKeys() {if (selectedKeys != null) {processSelectedKeysOptimized();} else {processSelectedKeysPlain(selector.selectedKeys());}}

处理 I/O 事件时有两种选择,一种是处理 Netty 优化过的 selectedKeys,另外一种是正常的处理逻辑。根据是否设置了 selectedKeys 来判断使用哪种策略,这两种策略使用的 selectedKeys 集

合是不一样的。Netty 优化过的 selectedKeys 是 SelectedSelectionKeySet 类型,而正常逻辑使用的是 JDK HashSet 类型。

2.3.1 processSelectedKeysPlain

    private void processSelectedKeysPlain(Set<SelectionKey> selectedKeys) {// check if the set is empty and if so just return to not create garbage by// creating a new Iterator every time even if there is nothing to process.// See https://github.com/netty/netty/issues/597if (selectedKeys.isEmpty()) {return;}// 遍历所有就绪的SelectionKeyIterator<SelectionKey> i = selectedKeys.iterator();for (;;) {final SelectionKey k = i.next();//  如selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);进行挂载在attachment上final Object a = k.attachment();// 需要自行删除i.remove();// i/o事件if (a instanceof AbstractNioChannel) {processSelectedKey(k, (AbstractNioChannel) a);} else {// 异步任务时@SuppressWarnings("unchecked")NioTask<SelectableChannel> task = (NioTask<SelectableChannel>) a;processSelectedKey(k, task);}if (!i.hasNext()) {break;}if (needsToSelectAgain) {selectAgain();selectedKeys = selector.selectedKeys();// Create the iterator again to avoid ConcurrentModificationExceptionif (selectedKeys.isEmpty()) {break;} else {i = selectedKeys.iterator();}}}}

2.3.1.1 processSelectedKey 

private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {final AbstractNioChannel.NioUnsafe unsafe = ch.unsafe();if (!k.isValid()) { // 检查 Key 是否合法final EventLoop eventLoop;try {eventLoop = ch.eventLoop();} catch (Throwable ignored) {return;}if (eventLoop != this || eventLoop == null) {return;}unsafe.close(unsafe.voidPromise()); // Key 不合法,直接关闭连接return;}try {int readyOps = k.readyOps();// 处理连接事件NioSocketChannelif ((readyOps & SelectionKey.OP_CONNECT) != 0) {int ops = k.interestOps();// 移除对connect事件的监听,否则Selector会一直通知ops &= ~SelectionKey.OP_CONNECT;k.interestOps(ops);// 触发channelActive处理connect事件unsafe.finishConnect();}// 处理可写事件if ((readyOps & SelectionKey.OP_WRITE) != 0) {ch.unsafe().forceFlush();}// 处理可读事件或accept事件。服务端NioServerSocketChannel中的Read方法处理的是Accept事件,NioSocketChannel中的Read方法处理的是Read事件。if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {unsafe.read();}} catch (CancelledKeyException ignored) {unsafe.close(unsafe.voidPromise());}}

2.3.1.2 needsToSelectAgain 

protected void doDeregister() throws Exception {eventLoop().cancel(selectionKey());
}/**
* 将socketChannel从selector中移除 取消监听IO事件
* */
void cancel(SelectionKey key) {key.cancel();cancelledKeys ++;// 当取消的 Key 超过默认阈值 256,needsToSelectAgain 设置为 true,为了清除无效SelectionKeyif (cancelledKeys >= CLEANUP_INTERVAL) {cancelledKeys = 0;needsToSelectAgain = true;}}

2.3.2 processSelectedKeysOptimized

private void processSelectedKeysOptimized() {for (int i = 0; i < selectedKeys.size; ++i) {final SelectionKey k = selectedKeys.keys[i];selectedKeys.keys[i] = null;final Object a = k.attachment();if (a instanceof AbstractNioChannel) {processSelectedKey(k, (AbstractNioChannel) a);} else {@SuppressWarnings("unchecked")NioTask<SelectableChannel> task = (NioTask<SelectableChannel>) a;processSelectedKey(k, task);}if (needsToSelectAgain) {selectedKeys.reset(i + 1);selectAgain();i = -1;}}}
final class SelectedSelectionKeySet extends AbstractSet<SelectionKey> {SelectionKey[] keys;int size;SelectedSelectionKeySet() {keys = new SelectionKey[1024];}@Overridepublic boolean add(SelectionKey o) {if (o == null) {return false;}keys[size++] = o;if (size == keys.length) {increaseCapacity();}return true;}..........}

因为SelectedSelectionKeySet 内部使用的是 SelectionKey 数组,所以 processSelectedKeysOptimized 可以直接通过遍历数组取出 I/O 事件,相比 JDK HashSet 的遍历效率更高。相比于 HashSet,SelectionKey[] 不需要考虑哈希冲突的问题,所以可以实现 O(1) 时间复杂度的 add 操作。

那么 SelectedSelectionKeySet生成摘录核心源码片段如下:

private SelectorTuple openSelector() {final SelectedSelectionKeySet selectedKeySet = new SelectedSelectionKeySet();Object maybeException = AccessController.doPrivileged(new PrivilegedAction<Object>() {@Overridepublic Object run() {try {Field selectedKeysField = selectorImplClass.getDeclaredField("selectedKeys");Field publicSelectedKeysField = selectorImplClass.getDeclaredField("publicSelectedKeys");if (PlatformDependent.javaVersion() >= 9 && PlatformDependent.hasUnsafe()) {long selectedKeysFieldOffset = PlatformDependent.objectFieldOffset(selectedKeysField);long publicSelectedKeysFieldOffset =PlatformDependent.objectFieldOffset(publicSelectedKeysField);if (selectedKeysFieldOffset != -1 && publicSelectedKeysFieldOffset != -1) {                    // 存储为数组PlatformDependent.putObject(unwrappedSelector, selectedKeysFieldOffset, selectedKeySet);// 存储为数组PlatformDependent.putObject(unwrappedSelector, publicSelectedKeysFieldOffset, selectedKeySet);return null;}}。。。。。} catch (NoSuchFieldException e) {return e;} catch (IllegalAccessException e) {return e;}}});    // 省略其他代码
}

 2.4 异步任务

处理异步任务队列 runAllTasks

2.4.1 runAllTasks

    protected boolean runAllTasks() {assert inEventLoop();boolean fetchedAll;boolean ranAtLeastOne = false;do {fetchedAll = fetchFromScheduledTaskQueue();if (runAllTasksFrom(taskQueue)) {ranAtLeastOne = true;}} while (!fetchedAll); // keep on processing until we fetched all scheduled tasks.if (ranAtLeastOne) {lastExecutionTime = getCurrentTimeNanos();}afterRunningAllTasks();return ranAtLeastOne;}

2.4.1 runAllTasks(long timeoutNanos)

protected boolean runAllTasks(long timeoutNanos) {// 合并定时任务到普通任务队列fetchFromScheduledTaskQueue(); // 从普通任务队列中取出任务并处理Runnable task = pollTask();if (task == null) {afterRunningAllTasks();return false;}// 计算任务处理的超时时间final long deadline = ScheduledFutureTask.nanoTime() + timeoutNanos;long runTasks = 0;long lastExecutionTime;for (;;) {safeExecute(task); // 执行任务runTasks ++;// 每执行 64 个任务检查一下是否超时if ((runTasks & 0x3F) == 0) {lastExecutionTime = ScheduledFutureTask.nanoTime();if (lastExecutionTime >= deadline) {break;}}task = pollTask(); // 继续取出下一个任务if (task == null) {lastExecutionTime = ScheduledFutureTask.nanoTime();break;}}// 收尾工作afterRunningAllTasks();this.lastExecutionTime = lastExecutionTime;return true;}

真正处理任务的 safeExecute() 就是直接调用的 Runnable 的 run() 方法。因为异步任务处理是有超时时间的,所以 Netty 采取了定时检测的策略,每执行 64 个任务的时候就会检查一下是否超时,对性能的折中考虑,如果异步队列中有大量的短时间任务,每一次执行完都检测一次超时性能会有所降低。

尾部队列作用:例如任务循环的耗时、占用物理内存的大小等等,都可以向尾部队列添加一个收尾任务完成统计数据的实时更新

三、最新版4.1.96源码

    protected void run() {int selectCnt = 0;for (;;) {try {int strategy;try {// 如果存在就绪I/O事件那么会返回对应就绪Channel的数量>=0进入default条件strategy = selectStrategy.calculateStrategy(selectNowSupplier, hasTasks());switch (strategy) {case SelectStrategy.CONTINUE:continue;case SelectStrategy.BUSY_WAIT:// fall-through to SELECT since the busy-wait is not supported with NIOcase SelectStrategy.SELECT:// 无任务,则进行轮询I/O事件long curDeadlineNanos = nextScheduledTaskDeadlineNanos();if (curDeadlineNanos == -1L) {// -1代表当前定时任务队列中没有定时任务curDeadlineNanos = NONE; // nothing on the calendar}nextWakeupNanos.set(curDeadlineNanos);try {if (!hasTasks()) {strategy = select(curDeadlineNanos);}} finally {// This update is just to help block unnecessary selector wakeups// so use of lazySet is ok (no race condition)nextWakeupNanos.lazySet(AWAKE);}// fall throughdefault:}} catch (IOException e) {// If we receive an IOException here its because the Selector is messed up. Let's rebuild// the selector and retry. https://github.com/netty/netty/issues/8566rebuildSelector0();selectCnt = 0;handleLoopException(e);continue;}selectCnt++;cancelledKeys = 0;needsToSelectAgain = false;final int ioRatio = this.ioRatio;boolean ranTasks;// IO占比为100,则进行IO事件if (ioRatio == 100) {try {//  异步任务执行,优先保证 CPU 能够及时处理异步任务if (strategy > 0) {processSelectedKeys();// 处理 I/O 事件}} finally {// Ensure we always run tasks.ranTasks = runAllTasks();}} else if (strategy > 0) {// 存在异步任务final long ioStartTime = System.nanoTime();try {processSelectedKeys();} finally {// Ensure we always run tasks.final long ioTime = System.nanoTime() - ioStartTime;//  异步任务执行,优先保证 CPU 能够及时处理异步任务ranTasks = runAllTasks(ioTime * (100 - ioRatio) / ioRatio);}} else {ranTasks = runAllTasks(0); // This will run the minimum number of tasks}if (ranTasks || strategy > 0) {if (selectCnt > MIN_PREMATURE_SELECTOR_RETURNS && logger.isDebugEnabled()) {logger.debug("Selector.select() returned prematurely {} times in a row for Selector {}.",selectCnt - 1, selector);}selectCnt = 0;} else if (unexpectedSelectorWakeup(selectCnt)) { // Unexpected wakeup (unusual case)selectCnt = 0;}} catch (CancelledKeyException e) {// Harmless exception - log anywayif (logger.isDebugEnabled()) {logger.debug(CancelledKeyException.class.getSimpleName() + " raised by a Selector {} - JDK bug?",selector, e);}} catch (Error e) {throw e;} catch (Throwable t) {handleLoopException(t);} finally {// Always handle shutdown even if the loop processing threw an exception.try {if (isShuttingDown()) {closeAll();if (confirmShutdown()) {return;}}} catch (Error e) {throw e;} catch (Throwable t) {handleLoopException(t);}}}}private int select(long deadlineNanos) throws IOException {if (deadlineNanos == NONE) {return selector.select();}// Timeout will only be 0 if deadline is within 5 microsecslong timeoutMillis = deadlineToDelayNanos(deadlineNanos + 995000L) / 1000000L;return timeoutMillis <= 0 ? selector.selectNow() : selector.select(timeoutMillis);}


 

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

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

相关文章

pg_database中的datlastsysoid

一&#xff0c;关于 pg_database 在 PostgreSQL 中&#xff0c;对于在数据库集群内创建的每个数据库,其关键信息都会被保存到 pg_database 系统表中。 PostgreSQL 确保通过 pg_database 系统表持久化存储每个数据库的属性信息&#xff0c;以方便后续管理和使用。这也让 pg_da…

Windows安装配置Tomcat服务器教程 - 外网远程访问

文章目录 前言1.本地Tomcat网页搭建1.1 Tomcat安装1.2 配置环境变量1.3 环境配置1.4 Tomcat运行测试1.5 Cpolar安装和注册 2.本地网页发布2.1.Cpolar云端设置2.2 Cpolar本地设置 3.公网访问测试4.结语 前言 Tomcat作为一个轻量级的服务器&#xff0c;不仅名字很有趣&#xff0…

振动智能监测与设备可靠性:无线技术的契机

在现代工业领域&#xff0c;设备的可靠性和稳定运行对于生产效率和安全性至关重要。然而&#xff0c;由于设备的频繁使用和各种环境影响&#xff0c;设备故障和突发停机仍然是不可避免的挑战。为了有效地应对这些挑战&#xff0c;振动智能监测技术结合无线传感器的应用正在成为…

W5500-EVB-PICO主动PING主机IP检测连通性(十)

前言 上一章我们用W5500_EVB_PICO 开发板做UDP组播数据回环测试&#xff0c;那么本章我们进行W5500_EVB_PICO Ping的测试。 什么是PING&#xff1f; Ping &#xff08;Packet Internet Groper&#xff09;是一种因特网包探索器&#xff0c;用于测试网络连接量的程序 。Ping是…

“北科Java面试宝典(211最详细讲解)“

Version : V1.0 北科Java面试宝典一、Java基础面试题【24道】二、JVM虚拟机面试题【14道】三、集合相关面试题【17道】四、多线程 【25道】五、IO【5道】六、网络编程 【9道】七、MySQL以及SQL面试题【20道】八、常用框架【19道】九、中间件和分布式 【54道】十、设计模式面试 …

Plex私人影音云盘搭建教程:本地电脑使用内网穿透实现远程访问

文章目录 1.前言2. Plex网站搭建2.1 Plex下载和安装2.2 Plex网页测试2.3 cpolar的安装和注册 3. 本地网页发布3.1 Cpolar云端设置3.2 Cpolar本地设置 4. 公网访问测试5. 结语 1.前言 用手机或者平板电脑看视频&#xff0c;已经算是生活中稀松平常的场景了&#xff0c;特别是各…

数据库和缓存如何保证一致性?

&#x1f4e2;&#x1f4e2;&#x1f4e2;&#x1f4e3;&#x1f4e3;&#x1f4e3; 哈喽&#xff01;大家好&#xff0c;我是【一心同学】&#xff0c;一位上进心十足的【Java领域博主】&#xff01;&#x1f61c;&#x1f61c;&#x1f61c; ✨【一心同学】的写作风格&#x…

匿名函数( lambda 表达式)

在 C 中&#xff0c;匿名函数也被称为 lambda 表达式。C11 引入了 lambda 表达式&#xff0c;使得在需要函数对象&#xff08;函数符&#xff09;的地方可以使用匿名函数来代替。 lambda 表达式的基本语法如下&#xff1a; [capture list] (parameter list) -> return typ…

JVM-CMS

when 堆大小要求为4-8G 原理 初始标记&#xff1a;执行CMS线程->STW&#xff0c;标记GC Root直接关联的对象->低延迟 并发标记&#xff1a;执行CMS线程和业务线程&#xff0c;从GC Root直接关联的对象开始遍历整个对象图 重新标记&#xff1a;执行CMS线程->STW&a…

Mybatis-Plus快速入门

目录 一、基础工程 1、创建一个数据库&#xff1a;mp 2、添加数据 3、创建初始工程 4、添加依赖 二、Mybatis Mybatis-Plus 1、创建子工程&#xff1a;mybatis-plus-simple 2、在子工程下添加配置 2.1Mybatis实现查询User 2.1.1、编写User实体对象 2.1.2、编写UserMa…

【业务功能篇93】微服务-springcloud-多线程-异步处理-异步编排-CompletableFutrue-实战运用

异步处理编排 我们可以在商品详细信息查询的位置实现CompletableFuture的异步编排处理。 根据业务分析&#xff1a;3.4.5数据接口的入参信息需要来源于1数据接口的返回信息&#xff0c;也就是skuid 所以可以设计 1 3 4 5 串行线程 &#xff0c;而 3 4 5依赖1 &#xff0c;需要等…

Redis项目实战——优惠券秒杀

目录 Redis自增功能解决全局唯一IDRedis实现优惠券秒杀的主要思路实现过程中出现的问题及解决方法超卖问题方案1 悲观锁方案2 乐观锁 一人一单问题分布式锁如何用Redis实现分布式锁&#xff1f; Redis优化秒杀消息队列实现异步秒杀List发布订阅模式Stream Redis自增功能解决全局…

Windows环境下的Tomcat服务器安装和配置教程,包括外网远程访问的设置方法

文章目录 前言1.本地Tomcat网页搭建1.1 Tomcat安装1.2 配置环境变量1.3 环境配置1.4 Tomcat运行测试1.5 Cpolar安装和注册 2.本地网页发布2.1.Cpolar云端设置2.2 Cpolar本地设置 3.公网访问测试4.结语 前言 Tomcat作为一个轻量级的服务器&#xff0c;不仅名字很有趣&#xff0…

Docker consul 容器服务自动发现和更新

目录 一、什么是服务注册与发现 二、Docker-consul集群 1.Docker-consul consul提供的一些关键特性 2.registrator 3.Consul-template 三、Docker-consul实现过程 以配置nginx负载均衡为例 先配置consul-agent &#xff0c;有两种模式server和client 四、Docker-cons…

K8s 持久化存储有几种方式?一文了解本地盘/CSI 外接存储/K8s 原生存储的优缺点

当今云原生环境中&#xff0c;Kubernetes&#xff08;K8s&#xff09;已成为既定的容器编排工具。随着 K8s 的普及&#xff0c;存储也成为 K8s 用户关注的一个重要问题&#xff1a;为了满足不同的场景需求&#xff0c;K8s 可以支持基于不同架构的多种存储方案。这些方案间有什么…

0基础学习VR全景平台篇 第95篇:VR实景智慧导航操作手册

一、实景导航前期准备工作及点位采集 &#xff08;一&#xff09;实景导航前期准备工作 &#xff08;1&#xff09;拍摄设备 1.推荐相机&#xff1a;全画幅的佳能 Canon EOS​ 5D Mark IV 2.搭配镜头&#xff1a;原厂的佳能 Canon EF卡口 8-15mm 全画幅鱼眼镜头 3.三角架 …

stencilJs学习之构建 Drawer 组件

前言 在之前的学习中&#xff0c;我们已经掌握了 stencilJs 中的一些核心概念和基础知识&#xff0c;如装饰器 Prop、State、Event、Listen、Method、Component 以及生命周期方法。这些知识是构建复杂组件和应用的基础&#xff0c;而抽屉组件是一个很好的示例&#xff0c;能够…

Chrome小恐龙快跑小游戏——Python实现

目录 视频演示 代码实现 视频演示 Chrome小恐龙快跑小游戏——Python实现 代码实现 import pygame import os import random pygame.init()# Global Constants SCREEN_HEIGHT 600 SCREEN_WIDTH 1100 game_over False SCREEN pygame.display.set_mode((SCREEN_WIDTH, SCR…

mysql通过.frm和.ibd 文件恢复数据库

问题背景&#xff1a;由于强制在服务关闭mysql导致部分数据表以及数据丢失 如下图只有.frm .ibd的文件为我的问题文件 查找不到表结构和表数据目录D:XXXX\mysql-5.7.24-winx64\data\mydata 从frm文件中恢复表结构 先把原来的数据备份一次 避免过程中出错 先备份之前数据的.fr…

【高危】Apache Airflow Spark Provider 反序列化漏洞 (CVE-2023-40195)

zhi.oscs1024.com​​​​​ 漏洞类型反序列化发现时间2023-08-29漏洞等级高危MPS编号MPS-qkdx-17bcCVE编号CVE-2023-40195漏洞影响广度广 漏洞危害 OSCS 描述Apache Airflow Spark Provider是Apache Airflow项目的一个插件&#xff0c;用于在Airflow中管理和调度Apache Spar…