Netty高性能数据结构
文章目录
- Netty高性能数据结构
- FastThreadLocal
- HashedWheelTimer时间轮
- Mpsc无锁队列
Netty高性能数据结构
Netty
用高性能数据结构的主要目的是为了提高网络通信的效率和系统的整体性能。
所谓的高性能数据结构是指,那些在特定场景下优化了性能和效率的数据结构,通常能够提供更快的操作速度、更低的内存消耗或更高的并发处理能力。
数据结构 | 原因 | 好处 |
---|---|---|
FastThreadLocal | ThreadLocal 在高并发场景中性能较低且容易造成内存泄漏。 | 更快的访问速度,优化线程本地存储;避免内存泄漏,提供更安全的内存管理。 |
HashedWheelTimer | 传统定时任务调度效率低,在高负载环境中成为性能瓶颈。 | 高效调度,时间轮算法优化任务组织;减少资源消耗,通过批量处理任务。 |
MpscLinkedQueue | 传统队列实现性能瓶颈,需要无锁队列支持多个生产者。 | 提高并发性,支持多个线程同时添加任务;高效任务处理,减少锁竞争。 |
FastThreadLocal
FastThreadLocal
是Netty
针对高并发场景优化的线程本地存储解决方案,通过减少内存占用、锁竞争和性能开销,提供了比ThreadLocal
更高效的线程本地存储能力。
在Netty
中,FastThreadLocal
被广泛使用于处理网络事件和任务调度。主要包括:
EventLoop
:Netty
的EventLoop
组件使用FastThreadLocal
来存储和管理线程本地的数据,如Channel
处理上下文、任务调度信息等。- 线程池管理:
FastThreadLocal
用于优化线程池中的线程本地存储,来提高任务处理的效率。
FastThreadLocal
对比ThreadLocal
优势主要在快速的定位数据和更高的安全性。FastThreadLocal
的设计是为了在高性能需求场景下提供优化的性能表现。
快速定位数据具体体现在,当调用ThreadLocal.set()
添加Entry
对象时,ThreadLocal
是使用线性探测法解决Hash
冲突的。线性探测法是一种用于解决哈希表中冲突的开放地址法。它的基本思路是在哈希表中发生冲突时,线性地探测哈希表中的下一个位置来寻找空闲的存储位置。当两个或更多的元素被哈希到相同的槽位时,会发生冲突。当发生冲突时,探测下一个位置。如果下一个位置也被占用,则继续探测下一个位置,直到找到一个空闲的位置。
为了便于理解,我们采用一组简单的数据模拟ThreadLocal.set()
的过程是如何解决Hash
冲突的。
threadLocalHashCode = 4
,threadLocalHashCode & 15 = 4
;此时数据应该放在数组下标为4的位置。下标4的位置正好没有数据,可以存放。threadLocalHashCode = 19
,threadLocalHashCode & 15 = 4
;但是下标4的位置已经有数据了,如果当前需要添加的Entry
与下标4位置已存在的Entry
两者的key
相同,那么该位置Entry
的value
将被覆盖为新的值。我们假设key
都是不相同的,所以此时需要向后移动一位,下标5的位置没有冲突,可以存放。threadLocalHashCode = 33
,threadLocalHashCode & 15 = 3
;下标3的位置已经有数据,向后移一位,下标4位置还是有数据,继续向后查找,发现下标6没有数据,可以存放。
ThreadLocal.get()
的过程也是类似的,也是根据threadLocalHashCode
的值定位到数组下标,然后判断当前位置Entry
对象与待查询Entry
对象的key
是否相同,如果不同,继续向下查找。由此可见,ThreadLocal.set()/get()
方法在数据密集时很容易出现Hash
冲突,需要O(n)
时间复杂度解决冲突问题,效率较低。
FastThreadLocal
在定位数据的时候可以直接根据数组下标index
获取,时间复杂度O(1)
。此外,FastThreadLocal
相比ThreadLocal
数据扩容更加简单高效,FastThreadLocal
以index
为基准向上取整到2的次幂作为扩容后容量,然后把原数据拷贝到新数组。而ThreadLocal
由于采用的哈希表,所以在扩容后需要再做一轮rehash
。
安全性体现在,ThreadLocal
使用不当可能造成内存泄漏,只能等待线程销毁。在使用线程池的场景下,ThreadLocal
只能通过主动检测的方式防止内存泄漏,从而造成了一定的开销。然而FastThreadLocal
不仅提供了remove()
主动清除对象的方法,而且在线程池场景中Netty
还封装了FastThreadLocalRunnable
,FastThreadLocalRunnable
最后会执行FastThreadLocal
.removeAll()
将Set
集合中所有FastThreadLocal
对象都清理掉。
FastThreadLocal
使用示例:
public class FastThreadLocalExample {private static final FastThreadLocal<String> threadLocal = new FastThreadLocal<String>() {@Overrideprotected String initialValue() {return "Initial Value";}};public static void main(String[] args) {// 创建并启动两个线程FastThreadLocalThread thread1 = new FastThreadLocalThread(() -> {String value = threadLocal.get();System.out.println("Thread1 initial value: " + value);threadLocal.set("Thread1 Value");System.out.println("Thread1 new value: " + threadLocal.get());});FastThreadLocalThread thread2 = new FastThreadLocalThread(() -> {String value = threadLocal.get();System.out.println("Thread2 initial value: " + value);threadLocal.set("Thread2 Value");System.out.println("Thread2 new value: " + threadLocal.get());});thread1.start();thread2.start();try {thread1.join();thread2.join();} catch (InterruptedException e) {e.printStackTrace();}}
}
HashedWheelTimer时间轮
为了实现高性能的定时任务调度,Netty
引入了时间轮算法驱动定时任务的执行。它通过将定时任务分配到轮盘上的不同槽位来避免高频率的任务调度开销,这种设计主要用于处理大量的定时任务。
在Netty
中,HashedWheelTimer
主要用于处理任务的延迟执行,例如定时任务、超时处理等。HashedWheelTimer
是一种基于时间轮算法的定时任务调度器,主要用于高效地管理和执行大量定时任务。时间轮算法将时间分割成一个个固定大小的时间片,并将任务安排到相应的时间片上,从而优化了定时任务的管理。
时间轮可以理解为一种环形结构,像钟表一样被分为多个slot
槽位。每个slot
代表一个时间段,每个slot
中可以存放多个任务,使用的是链表结构保存该时间段到期的所有任务。时间轮通过一个时针随着时间一个个slot
转动,并执行slot
中的所有到期任务。
时间轮有点类似HashMap
,如果多个任务如果对应同一个slot
,处理冲突的方法采用的是拉链法。在任务数量比较多的场景下,适当增加时间轮的slot
数量,可以减少时针转动时遍历的任务个数。时间轮定时器最大的优势就是,任务的新增和取消都是O(1)
时间复杂度,而且只需要一个线程就可以驱动时间轮进行工作。
时间轮是以时间作为刻度组成的一个环形队列,所以叫做时间轮。这个环形队列采用数组来实现HashedWheelBucket[]
,数组的每个元素称为槽,每个槽可以存放一个定时任务列表,叫HashedWheelBucket
,它是一个双向链表,链表的每个节点表示一个定时任务项(HashedWheelTimeout
),其中封装了真正的定时任务TimerTask
。
public HashedWheelTimer(ThreadFactory threadFactory,long tickDuration, TimeUnit unit, int ticksPerWheel, boolean leakDetection,long maxPendingTimeouts) {// 省略其他代码wheel = createWheel(ticksPerWheel); // 创建时间轮的环形数组结构mask = wheel.length - 1; // 用于快速取模的掩码long duration = unit.toNanos(tickDuration); // 转换成纳秒处理// 省略其他代码workerThread = threadFactory.newThread(worker); // 创建工作线程leak = leakDetection || !workerThread.isDaemon() ? leakDetector.track(this) : null; // 是否开启内存泄漏检测this.maxPendingTimeouts = maxPendingTimeouts; // 最大允许等待任务数,HashedWheelTimer 中任务超出该阈值时会抛出异常// 如果 HashedWheelTimer 的实例数超过 64,会打印错误日志if (INSTANCE_COUNTER.incrementAndGet() > INSTANCE_COUNT_LIMIT &&WARNED_TOO_MANY_INSTANCES.compareAndSet(false, true)) {reportTooManyInstances();}
}
HashedWheelTimer
的核心组件:
HashedWheelTimeout
,任务的封装类,包含任务的到期时间、需要经历的圈数remainingRounds
等属性。HashedWheelBucket
,相当于时间轮的每个slot
,内部采用双向链表保存了当前需要执行的HashedWheelTimeout
列表。Worker
,HashedWheelTimer
的核心工作引擎,负责处理定时任务。
HashedWheelTimer
的工作流程:
- 新增任务:通过当前时间和任务的延迟时间计算出任务的到期时间。根据任务的到期时间,计算出任务应放置在时间轮的哪个槽位。这个计算基于当前时间与到期时间的差值,经过一定的模运算。将任务封装成
HashedWheelTimeout
对象,并插入到对应的HashedWheelBucket
中。public Timeout newTimeout(TimerTask task, long delay, TimeUnit unit) {// 计算任务的到期时间long deadline = System.nanoTime() + unit.toNanos(delay);// 计算任务应插入的槽位int tick = (int) ((deadline / tickDurationNanos) & mask);// 创建 HashedWheelTimeout 对象HashedWheelTimeout timeout = new HashedWheelTimeout(this, task, deadline, tick);// 将任务添加到相应的 HashedWheelBucket 中wheel[tick].add(timeout);return timeout; }
- 执行任务:
HashedWheelTimer
使用Worker
线程来周期性地推进时间轮。Worker
线程每隔一个刻度时间推进时间轮一个槽位。每次推进时,它检查当前槽位中的所有任务。对于到期的任务,Worker
线程将其从槽位中移除并执行。任务执行后,HashedWheelTimeout
对象会被标记为已完成,执行相关的回调方法。private final class Worker implements Runnable {@Overridepublic void run() {while (workerState == WORKER_STATE_STARTED) {try {Thread.sleep(tickDuration);advanceClock(); // 推进时间轮processTimeouts(); // 执行到期任务} catch (InterruptedException e) {// 处理异常}}}private void advanceClock() {// 更新当前时间轮的槽位}private void processTimeouts() {// 遍历当前槽位,执行到期任务} }
- 取消任务:如果需要取消一个任务,可以调用
Timeout
对象的cancel
方法。取消任务会从槽位中移除相应的HashedWheelTimeout
对象,确保它不会被执行。public boolean cancel() {boolean removed = bucket.remove(this); // 从槽位中移除if (removed) {// 执行取消回调等操作}return removed; }
- 停止定时器:当需要停止
HashedWheelTimer
时,stop
方法会被调用。使用CAS
操作将Worker
线程的状态更新为SHUTDOWN
,确保不会再有新的任务被添加。中断Worker
线程并等待其停止。处理线程中断异常,确保线程被完全停止。关闭内存泄漏检测器并减少实例计数,返回所有未处理的任务。
public Set<Timeout> stop() {// 如果当前线程是 workerThread,则抛出异常if (Thread.currentThread() == workerThread) {throw new IllegalStateException(HashedWheelTimer.class.getSimpleName() +".stop() cannot be called from " +TimerTask.class.getSimpleName());}// 尝试通过 CAS 操作将工作线程的状态更新为 SHUTDOWNif (!WORKER_STATE_UPDATER.compareAndSet(this, WORKER_STATE_STARTED, WORKER_STATE_SHUTDOWN)) {if (WORKER_STATE_UPDATER.getAndSet(this, WORKER_STATE_SHUTDOWN) != WORKER_STATE_SHUTDOWN) {INSTANCE_COUNTER.decrementAndGet();if (leak != null) {boolean closed = leak.close(this);assert closed;}return Collections.emptySet();}}try {boolean interrupted = false;// 中断 workerThread 并等待其停止while (workerThread.isAlive()) {workerThread.interrupt(); // 中断工作线程try {workerThread.join(100); // 等待工作线程停止} catch (InterruptedException ignored) {interrupted = true;}}if (interrupted) {Thread.currentThread().interrupt();}} finally {INSTANCE_COUNTER.decrementAndGet(); // 减少实例计数if (leak != null) {boolean closed = leak.close(this);assert closed;}}return worker.unprocessedTimeouts(); // 返回未处理的任务
}
以下是HashedWheelTimer
使用示例,展示了如何创建定时器、添加定时任务、执行任务以及停止定时器:
public class HashedWheelTimerExample {public static void main(String[] args) {// 创建 HashedWheelTimer 实例,刻度为1秒,时间轮槽的数量为512HashedWheelTimer timer = new HashedWheelTimer(1, TimeUnit.SECONDS, 512);// 定义一个定时任务TimerTask task = new TimerTask() {@Overridepublic void run(Timeout timeout) {System.out.println("定时任务执行时间: " + System.currentTimeMillis());}};// 添加定时任务到时间轮,设置任务延迟5秒执行timer.newTimeout(task, 5, TimeUnit.SECONDS);// 等待10秒,让定时任务有机会执行try {Thread.sleep(10000);} catch (InterruptedException e) {e.printStackTrace();}// 停止时间轮定时器,并返回未处理的定时任务数量Set<Timeout> unprocessedTimeouts = timer.stop();System.out.println("未处理的定时任务数量: " + unprocessedTimeouts.size());}
}
Mpsc无锁队列
Mpsc
的全称是Multi Producer Single Consumer
,多生产者单消费者。Mpsc Queue
可以保证多个生产者同时访问队列是线程安全的,而且同一时刻只允许一个消费者从队列中读取数据。Netty Reactor
线程中任务队列必须满足多个生产者可以同时提交任务,所以Mpsc Queue
非常适合Netty Reactor
线程模型。
在Netty
中,EventLoopGroup
负责管理和调度处理网络事件的线程。EventLoopGroup
的实现使用了无锁队列来维护待处理的任务和事件。具体来说是SingleThreadEventLoop
和NioEventLoop
这两个类使用无锁队列来存储任务和事件。这些队列是Mpsc
类型的无锁队列,允许多个线程同时提交任务,而由单个事件循环线程处理这些任务。无锁队列的使用有效地提高了并发性能,减少了锁竞争的开销,使得事件处理更加高效。
在传统的锁机制下,当多个线程尝试同时访问共享数据结构时,会产生竞争,从而导致性能下降。使用无锁队列可以避免使用锁,减少线程间的竞争和上下文切换,提高并发性能。它的核心在于利用原子操作来保证线程安全。原子操作是指在执行过程中不会被其他线程打断的操作,保证数据的一致性。通常实现是使用链表结构,其中每个节点包含数据和指向下一个节点的引用。队列的头节点和尾节点通过原子操作来维护,插入和删除操作通过调整节点的链接来完成。
public class MpscQueue<E> {private final Node<E> head;private final AtomicReference<Node<E>> tail;public MpscQueue() {head = new Node<>(null);tail = new AtomicReference<>(head);}public void offer(E item) {Node<E> newTail = new Node<>(item);Node<E> oldTail = tail.getAndSet(newTail);oldTail.next = newTail;}public E poll() {Node<E> headNode = head.next;if (headNode == null) {return null; // 队列为空}head.next = headNode.next;return headNode.item;}private static class Node<E> {private final E item;private Node<E> next;Node(E item) {this.item = item;}}
}
相关文章:
Netty高性能数据结构
文章目录 Netty高性能数据结构FastThreadLocalHashedWheelTimer时间轮Mpsc无锁队列 Netty高性能数据结构 Netty 用高性能数据结构的主要目的是为了提高网络通信的效率和系统的整体性能。 所谓的高性能数据结构是指,那些在特定场景下优化了性能和效率的数据结构&am…...

关于百度、微软语音合成的实现案例
关键词 自助机产品、排队呼叫功能、网络喇叭、百度语音合成SDK、微软TTS 阅读建议 对自助机产品功能扩展感兴趣的读者、需要实现远程语音呼叫功能的开发者、想要了解网络喇叭选型及其使用的技术人员、对百度语音合成SDK和微软TTS感兴趣的开发者 阅读时长 预计阅读时长…...

二叉树:镜像树,子结构,二叉树转链表,二叉树的倒数K个数,对称,Z型打印
1.把一棵二叉树转换为它的镜像树。 void mirror_tree(TreeNode *root) {if(rootNULL) return ;TreeNode *temproot->right;root->rightroot->left;root->lefttemp;mirror_tree(root->right);mirror_tree(root->left);}2、输入两棵二叉树A,B&…...

瑞秋,詹妮弗·安妮斯顿多年来与本·阿弗莱克保持着“调情”友谊 又一个詹妮弗
尽管所有迹象都表明本阿弗莱克和詹妮弗洛佩兹的婚姻即将走向离婚,但他尚未公开评论此事。不过,好莱坞圈内人士已经纷纷将他与另一位名人联系起来。事实上,是另一位詹妮弗。 一位消息人士向媒体透露,詹妮弗安妮斯顿和阿弗莱克一直都很有默契——无论是在银幕上还是在银幕外…...

指纹失效,忘记iPhone屏幕解锁密码怎么应对?
为保证手机的安全及隐私,我们会给手机设置屏幕锁屏密码,通过输入设置密码来解锁手机屏幕锁,但为了给大家提供快速便捷的解锁方式,苹果公司提供了指纹解锁,不仅解锁更便捷了还极大地增强了设备的安全性。但有时我们手指…...

09.XSS跨站脚本攻击(超详细!!!)
1、什么是XSS XSS(跨站脚本攻击):攻击者利用这个漏洞将恶意脚本注入到网页中,当其它用户浏览这些页面时,恶意脚本会在用户的浏览器中执行。XSS攻击允许攻击者在用户的浏览器上执行脚本,从而可能获取用户的…...
讲解人工智能在现代科技中的应用和未来发展趋势-水文
人工智能(Artificial Intelligence,简称AI)是一种模拟人类智能的科技领域,它通过计算机模拟人类的思维、学习、推理和决策能力,以便解决复杂的问题。近年来,人工智能技术的发展取得了惊人的进展,…...

2.2 QT 环境配置
2.2 QT环境配置 QT是一个1991年由QT Company开发的跨平台C图形用户界面应用程序开发框架。它既可以开发GUI程序,也可以用于开发非GUI程序,比如控制台工具和服务器。Qt是面向对象的框架,使用特殊的代码生成扩展(称为元对象编译器&…...

2.类和对象(上)
1. 类的定义 1.1 类定义格式 • class为定义类的关键字,Stack为类的名字,{ }中为类的主体,注意类定义结束时后面分号不能省略。类体中内容称为类的成员:类中的变量称为类的属性或成员变量; (类和结构体非常像&#…...

【实际案例】服务器宕机情况分析及处理建议
了解银河麒麟操作系统更多全新产品,请点击访问麒麟软件产品专区:https://product.kylinos.cn 服务器环境以及配置 物理机/虚拟机/云/容器 物理机 外网/私有网络/无网络 私有网络 处理器: Kunpeng 920 内存: 4 TiB BIOS版…...

Linux系统之ncdu命令的基本使用
Linux系统之ncdu命令的基本使用 一、ncdu命令命令介绍1.1 ncdu简介1.2 ncdu特点 二、本地环境介绍2.1 本地环境规划2.2 本次实践介绍 三、检查本地环境3.1 检查本地操作系统版本3.2 检查系统内核版本3.3 检查系统镜像源3.4 更新软件列表 四、安装ncdu工具4.1 安装ncdu软件4.2 n…...

STM32L051K8U6-HAL-LED闪烁设计
HAL三步法: 1、配置下载线 2、配置晶振 3、配置时钟 注意:中断优先级(这里防止HAL_Delay卡死,详细请看 http://t.csdnimg.cn/NQhQV) 4、 配置灯引脚属性为输出模式。并设置标签为LED 生成代码:编写while里…...
记一次远程API调用失败
记一次远程API调用失败 最近开发忙,项目紧,系统出现一些忽隐忽现的问题,本地也不能复现,当时也无法理解,就先搁置了,现在回想起来,这里还是明智的。 这个bug很神奇 今天,原本好好的…...
【力扣】746.使用最小花费爬楼梯
题目描述 给你一个整数数组 cost ,其中 cost[i] 是从楼梯第 i 个台阶向上爬需要支付的费用。一旦你支付此费用,即可选择向上爬一个或者两个台阶。 你可以选择从下标为 0 或下标为 1 的台阶开始爬楼梯。 请你计算并返回达到楼梯顶部的最低花费。 示例…...

06:【stm32】中断一:NVIC的配置
中断 1、中断的简介1.1、什么是中断1.2、为什么需要中断 2、中断的优先级2.1、中断优先级的表示方法 3、NVIC3.1、什么的NVIC3.2、NVIC的内部结构3.3、中断向量表3.4、程序实现①开启中断源②配置NVIC③中断响应函数 1、中断的简介 1.1、什么是中断 正在进行的事务被突发事件打…...
Flutter简介
Flutter是一个由Google开发的开源移动UI框架,它允许开发者使用Dart语言来构建高性能、高保真的iOS和Android应用。Flutter的设计理念是"编写一次,到处运行"(write once, run everywhere),这意味着开发者可以…...

WT2605C蓝牙语音芯片赋能对讲机新体验:无屏操控、音频解码与蓝牙音箱三合一
一、产品概况 对讲机市场是一个技术成熟且具有广泛应用前景的市场。对讲机作为无线通信设备的一种,在许多不同的领域和业务中发挥着重要作用。从技术发展角度来看,对讲机经历了从模拟到数字的转型,以及从简单通信工具向多功能设备的演进。当…...

ctfshow-web入门-sql注入(web191-web195)
目录 1、web191 2、web192 3、web193 4、web194 5、web195 1、web191 过滤了 ascii 使用 ord 代替: import requests import string url "http://a585c278-320a-40e7-841f-109b1e394caa.challenge.ctf.show/api/index.php" out for j in range(1…...

【ARM】v8架构programmer guide(3)_ARMv8的寄存器
目录 4.ARMv8 registers 4.1 AArch64 特殊寄存器 4.1.1 Zero register 4.1.2 Stack pointer (SP) 4.1.3 Program Counter (PC) 4.1.4 Exception Link Register(ELR) 4.1.5 Saved Process Status Register (SPSR) 4.2 Proc…...

SpringIOC整合dbUtil做的增删改查以及转账业务的实现
目录 一、xml方式实现 1.介绍lombok插件 2.功能 3.步骤 3.1 idea安装插件(只做一次) 3.2 添加坐标 3.3 编写注解 4.核心类 4.1 QueryRunner 4.2 query() 查询 4.3 update() 增删改 5.配置文件applicationContext.xml 6.junit测试 6.1使用步骤 6.1.1 坐标 6.1.2…...
ssc377d修改flash分区大小
1、flash的分区默认分配16M、 / # df -h Filesystem Size Used Available Use% Mounted on /dev/root 1.9M 1.9M 0 100% / /dev/mtdblock4 3.0M...
Frozen-Flask :将 Flask 应用“冻结”为静态文件
Frozen-Flask 是一个用于将 Flask 应用“冻结”为静态文件的 Python 扩展。它的核心用途是:将一个 Flask Web 应用生成成纯静态 HTML 文件,从而可以部署到静态网站托管服务上,如 GitHub Pages、Netlify 或任何支持静态文件的网站服务器。 &am…...
DeepSeek 技术赋能无人农场协同作业:用 AI 重构农田管理 “神经网”
目录 一、引言二、DeepSeek 技术大揭秘2.1 核心架构解析2.2 关键技术剖析 三、智能农业无人农场协同作业现状3.1 发展现状概述3.2 协同作业模式介绍 四、DeepSeek 的 “农场奇妙游”4.1 数据处理与分析4.2 作物生长监测与预测4.3 病虫害防治4.4 农机协同作业调度 五、实际案例大…...

Unsafe Fileupload篇补充-木马的详细教程与木马分享(中国蚁剑方式)
在之前的皮卡丘靶场第九期Unsafe Fileupload篇中我们学习了木马的原理并且学了一个简单的木马文件 本期内容是为了更好的为大家解释木马(服务器方面的)的原理,连接,以及各种木马及连接工具的分享 文件木马:https://w…...

macOS 终端智能代理检测
🧠 终端智能代理检测:自动判断是否需要设置代理访问 GitHub 在开发中,使用 GitHub 是非常常见的需求。但有时候我们会发现某些命令失败、插件无法更新,例如: fatal: unable to access https://github.com/ohmyzsh/oh…...
深入理解 React 样式方案
React 的样式方案较多,在应用开发初期,开发者需要根据项目业务具体情况选择对应样式方案。React 样式方案主要有: 1. 内联样式 2. module css 3. css in js 4. tailwind css 这些方案中,均有各自的优势和缺点。 1. 方案优劣势 1. 内联样式: 简单直观,适合动态样式和…...

Python爬虫(52)Scrapy-Redis分布式爬虫架构实战:IP代理池深度集成与跨地域数据采集
目录 一、引言:当爬虫遭遇"地域封锁"二、背景解析:分布式爬虫的两大技术挑战1. 传统Scrapy架构的局限性2. 地域限制的三种典型表现 三、架构设计:Scrapy-Redis 代理池的协同机制1. 分布式架构拓扑图2. 核心组件协同流程 四、技术实…...

【HTML】HTML 与 CSS 基础教程
作为 Java 工程师,掌握 HTML 和 CSS 也是需要的,它能让你高效与前端团队协作、调试页面元素,甚至独立完成简单页面开发。本文将用最简洁的方式带你掌握核心概念。 一、HTML,网页骨架搭建 核心概念:HTML通过标签定义内…...
Asp.net Core 通过依赖注入的方式获取用户
思路:Web项目中,需要根据当前登陆的用户,查询当前用户所属的数据、添加并标识对象等。根据请求头Authorization 中token,获取Redis中存储的用户对象。 本做法需要完成 基于StackExchange.Redis 配置,参考:…...

ADB识别手机系统弹授权框-如何处理多重弹框叠加和重叠问题
ADB识别手机系统弹授权框-如何处理多重弹框叠加和重叠问题 --蓝牙电话SDK自动部署 上一篇:手机App-插入USB时自动授权点击确定按钮-使系统弹出框自动消失 下一篇:编写中。 一、前言 我们在上一篇《手机App-插入USB时自动授权点击确定按钮-使系统弹出框…...