RPC 源码解析~Apache Dubbo
解析 RPC(远程过程调用)的源码可以帮助你深入理解其工作原理和实现细节。为了更好地进行源码解析,我们选择一个流行的 RPC 框架——Apache Dubbo 作为示例。Dubbo 是一个高性能、轻量级的开源 Java RPC 框架,广泛应用于企业级应用中。
Dubbo的优劣势
优势
-
高性能:
- Dubbo 使用 Netty 作为底层通信框架,支持高并发场景下的高效通信。
- 支持多种序列化方式(如 Hessian2, Kryo, FST 等),可以根据需要选择最合适的序列化方案以提升性能。
-
丰富的功能:
- 提供多种负载均衡策略(如 RandomLoadBalance, RoundRobinLoadBalance 等)。
- 内置了服务降级、熔断和限流机制,增强了系统的健壮性。
- 支持多协议(如 Dubbo 协议, HTTP 协议等),灵活适应不同的应用场景。
-
良好的生态系统:
- 拥有成熟的社区支持和丰富的文档资源。
- 可与 Spring Boot 和 Spring Cloud 等主流框架无缝集成,简化开发流程。
-
细粒度控制:
- 提供详细的配置选项,允许开发者对每个服务进行精细化管理。
- 支持服务分组、版本控制等功能,便于维护和升级。
-
强大的监控能力:
- 内置了监控中心,可以实时查看服务的调用情况和性能指标。
- 支持与其他监控系统(如 Prometheus, Grafana)集成,实现全面的监控解决方案。
劣势
-
依赖复杂:
- Dubbo 依赖较多,引入时可能需要额外配置多个组件(如注册中心、序列化库等)。
- 学习曲线相对陡峭,初学者需要一定时间掌握其复杂的配置和使用方法。
-
扩展性有限:
- 尽管 Dubbo 提供了插件机制,但相对于一些现代微服务框架(如 Spring Cloud),其扩展性和灵活性稍显不足。
- 对于某些特定需求,可能需要自行开发插件或中间件来满足。
-
生态整合难度:
- 虽然可以与 Spring Boot 和 Spring Cloud 集成,但在某些高级特性上可能存在兼容性问题。
- 相比 Spring Cloud 生态更为丰富和成熟,Dubbo 的第三方组件和支持程度略逊一筹。
-
社区活跃度:
- 虽然 Dubbo 社区活跃,但由于近年来 Spring Cloud 的崛起,部分开发者更倾向于使用后者。
- 新的功能更新速度相对较慢,不如 Spring Cloud 快速迭代。
解析目标
我们将逐步解析 Dubbo 的核心组件和流程,包括但不限于以下部分:
- 服务注册与发现:如何将服务注册到注册中心,并从注册中心发现可用的服务。
- 代理机制:客户端和服务端如何通过动态代理来透明地进行远程调用。
- 序列化:数据在网络传输过程中如何被序列化和反序列化。
- 负载均衡:客户端如何根据不同的策略选择合适的服务提供者。
- 通信协议:底层网络通信是如何实现的,包括 Netty 等网络框架的应用。
准备工作
在开始解析之前,请确保你已经具备以下条件:
- Java 开发环境:安装 JDK 8 或更高版本。
- Git 工具:用于克隆 Dubbo 源码仓库。
- IDE:推荐使用 IntelliJ IDEA 或 Eclipse。
- Maven:用于构建和管理依赖。
克隆 Dubbo 源码
首先,克隆 Dubbo 的 GitHub 仓库到本地:
bash
git clone https://github.com/apache/dubbo.git
cd dubbo
构建 Dubbo 源码
使用 Maven 构建 Dubbo 源码:
bash
mvn clean install -DskipTests=true
核心组件解析
1. 服务注册与发现
Dubbo 使用 ZooKeeper 作为默认的注册中心。以下是服务注册和发现的核心流程。
服务注册
当服务提供者启动时,会将其元数据注册到注册中心。主要涉及 RegistryProtocol
和 ZookeeperRegistry
类。
- RegistryProtocol: 负责处理服务注册和订阅逻辑。
- ZookeeperRegistry: 实现了具体的注册中心操作,如连接 ZooKeeper 并执行注册。
关键代码位置:
dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/protocol/RegistryProtocol.java
dubbo-registry/dubbo-registry-zookeeper/src/main/java/org/apache/dubbo/registry/zookeeper/ZookeeperRegistry.java
核心方法:
export(Invoker<T> invoker)
: 导出服务并注册到注册中心。doRegister(URL url)
: 执行实际的注册操作。
示例代码:
java
@Override
public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {URL registryUrl = getRegistryUrl(originInvoker);// 创建注册器实例Registry registry = getRegistry(registryUrl);final URL providerUrl = getProviderUrl(originInvoker, registryUrl);// 向注册中心注册服务final URL registeredProviderUrl = getRegisteredProviderUrl(providerUrl, registryUrl);ProviderModel providerModel = new ProviderModel(providerUrl.getServiceKey(), originInvoker, providerUrl);ApplicationModel.registerProvider(providerModel);registry.register(registeredProviderUrl);// 订阅 override 数据registry.subscribe(getSubscribedOverrideUrl(providerUrl), event -> {if (logger.isDebugEnabled()) {logger.debug("Notify urls for subscribe url " + event.getUrl() + ", urls: " + event.getUrls());}Map<String, String> notifiedUrls = new HashMap<>();if (CollectionUtils.isNotEmpty(event.getUrls())) {event.getUrls().forEach(url -> notifiedUrls.put(url.getServiceKey(), url.toFullString()));}refreshOverrideAndInvoker(providerUrl, notifiedUrls);});exporterMapLock.lock();try {ExporterChangeableWrapper<T> exporter = new ExporterChangeableWrapper<>(originInvoker, null);exporters.put(originInvoker, exporter);return exporter;} finally {exporterMapLock.unlock();}
}
服务发现
当服务消费者启动时,会从注册中心订阅服务提供者的地址列表。主要涉及 RegistryProtocol
和 ZookeeperRegistry
类。
- RegistryProtocol: 处理订阅逻辑,并生成服务代理。
- ZookeeperRegistry: 监听注册中心的变化,并通知消费者。
关键代码位置:
dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/protocol/RegistryProtocol.java
dubbo-registry/dubbo-registry-zookeeper/src/main/java/org/apache/dubbo/registry/zookeeper/ZookeeperRegistry.java
核心方法:
refer(Class<T> type, URL url)
: 引用远程服务并创建代理对象。doSubscribe(URL url, NotifyListener listener)
: 执行实际的订阅操作。
示例代码:
java
@Override
@SuppressWarnings({"unchecked", "rawtypes"})
public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {URL subscribeUrl = new URL(Constants.CONSUMER_PROTOCOL, mapLocalHost(url.getParameter(Constants.REGISTER_IP_KEY, NetUtils.getLocalHost())), 0,type.getName(), url.getParameters());// 设置检查是否延迟暴露if (!Constants.YES_VALUE.equals(url.getParameter(Constants.LAZY_CONNECT_KEY))) {checkWhetherMetadataCenterExist(subscribeUrl);List<URL> urls = registryCache.get(subscribeUrl);if (urls != null && !urls.isEmpty()) {// 如果存在直接返回StaticDirectory<T> directory = new StaticDirectory<>(subscribeUrl, toInvokers(urls));doRefer(subscribeUrl, directory);return cluster.join(directory);}}// 创建动态目录DynamicDirectory<T> directory = new DynamicDirectory<>(subscribeUrl, registry, directoryFactory, false);directory.buildRouterChain(subscribeUrl);directory.setConsumerUrl(subscribeUrl);directoryList.add(directory);registry.subscribe(subscribeUrl, new CacheListener(directory, url));// 创建集群Invoker<T> clusterInvoker = cluster.join(directory);providersModel.setClusterInvoker(clusterInvoker);return clusterInvoker;
}
2. 代理机制
Dubbo 使用动态代理来简化远程调用的过程。主要涉及 ProxyFactory
和 JavassistProxyFactory
类。
- ProxyFactory: 定义了代理工厂接口。
- JavassistProxyFactory: 实现了具体的代理创建逻辑。
关键代码位置:
dubbo-common/src/main/java/org/apache/dubbo/common/proxy/ProxyFactory.java
dubbo-proxy/dubbo-proxy-javassist/src/main/java/org/apache/dubbo/common/proxy/javassist/JavassistProxyFactory.java
核心方法:
getProxy(Invoker<?> invoker)
: 获取服务代理对象。getInvoker(T proxy, Class<T> type, URL url)
: 将代理对象转换为 Invoker。
示例代码:
java
@Override
public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) {return (T) Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker));
}@Override
public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {// TODO Wrapper cannot handle this scenario at this moment.throw new UnsupportedOperationException("Not supported.");
}
3. 序列化
Dubbo 支持多种序列化方式,如 Hessian2, Kryo, FST 等。主要涉及 Serialization
和具体序列化类。
- Serialization: 定义了序列化接口。
- Hessian2Serialization: 实现了 Hessian2 序列化的具体逻辑。
关键代码位置:
dubbo-common/src/main/java/org/apache/dubbo/common/serialize/Serialization.java
dubbo-serialization/dubbo-serialization-hessian2/src/main/java/org/apache/dubbo/common/serialize/hessian2/Hessian2Serialization.java
核心方法:
serialize(URL url, OutputStream output)
: 返回序列化对象。deserialize(URL url, InputStream input)
: 返回反序列化对象。
示例代码:
java
@Override
public ObjectOutput serialize(URL url, OutputStream out) throws IOException {Hessian2ObjectOutputStream hessian2os = new Hessian2ObjectOutputStream(out);com.caucho.hessian.io.SerializerFactory serializerFactory = getSerializerFactory(url);if (serializerFactory != null) {hessian2os.setSerializerFactory(serializerFactory);}return new CompatibleObjectOutput(hessian2os);
}@Override
public ObjectInput deserialize(URL url, InputStream is) throws IOException {Hessian2ObjectInputStream hessian2is = new Hessian2ObjectInputStream(is);com.caucho.hessian.io.DeserializerFactory deserializerFactory = getDeserializerFactory(url);if (deserializerFactory != null) {hessian2is.setDeserializerFactory(deserializerFactory);}return new CompatibleObjectInput(hessian2is);
}
4. 负载均衡
Dubbo 提供多种负载均衡策略,如 RandomLoadBalance, RoundRobinLoadBalance 等。主要涉及 LoadBalance
和具体策略类。
- LoadBalance: 定义了负载均衡接口。
- RandomLoadBalance: 实现了随机负载均衡的具体逻辑。
关键代码位置:
dubbo-cluster/src/main/java/org/apache/dubbo/rpc/loadbalance/LoadBalance.java
dubbo-cluster/src/main/java/org/apache/dubbo/rpc/loadbalance/RandomLoadBalance.java
核心方法:
select(List<Invoker<T>> invokers, URL url, Invocation invocation)
: 选择合适的 Invoker。
示例代码:
java
@Override
protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {int length = invokers.size(); // Number of providersint totalWeight = 0; // The sum of weightsboolean sameWeight = true; // Every provider has the same weight?for (int i = 0; i < length; i++) {int weight = getWeight(invokers.get(i), invocation);totalWeight += weight; // Sumif (sameWeight && i > 0 && weight != getWeight(invokers.get(i - 1), invocation)) {sameWeight = false;}}if (totalWeight > 0 && !sameWeight) {// If (not every provider has the same weight & at least one provider's weight>0), select randomly based on totalWeight.int offset = ThreadLocalRandom.current().nextInt(totalWeight);// Return a invoker based on the random value.for (int i = 0; i < length; i++) {Invoker<T> invoker = invokers.get(i);offset -= getWeight(invoker, invocation);if (offset < 0) {return invoker;}}}// If all providers have the same weight value or totalWeight=0, return evenly.return invokers.get(ThreadLocalRandom.current().nextInt(length));
}
5. 通信协议
Dubbo 支持多种通信协议,如 Dubbo 协议, HTTP 协议等。主要涉及 Transporter
和具体协议类。
- Transporter: 定义了通信接口。
- NettyTransporter: 实现了基于 Netty 的通信逻辑。
关键代码位置:
dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/Transporter.java
dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyTransporter.java
核心方法:
bind(URL url, ChannelHandler handler)
: 绑定服务器端口。connect(URL url, ChannelHandler handler)
: 连接到服务器。
示例代码:
@Override
public Server bind(URL url, ChannelHandler handler) throws RemotingException {return new NettyServer(url, handler);
}@Override
public Client connect(URL url, ChannelHandler handler) throws RemotingException {return new NettyClient(url, wrapChannelHandler(url, handler));
}
示例项目
为了更好地理解上述解析内容,我们可以创建一个简单的 Dubbo 示例项目,包含服务提供者和消费者。
1. 创建服务接口
定义一个简单的服务接口:
UserApi.java:
java
package com.example.dubbo.service;public interface UserApi {String sayHello(String name);
}
2. 实现服务提供者
实现服务接口并启动服务提供者:
UserServiceImpl.java:
java
package com.example.dubbo.provider;import com.example.dubbo.service.UserApi;
import org.apache.dubbo.config.annotation.DubboService;@DubboService(version = "1.0.0")
public class UserServiceImpl implements UserApi {@Overridepublic String sayHello(String name) {return "Hello, " + name;}
}
ProviderApplication.java:
java
package com.example.dubbo.provider;import org.apache.dubbo.config.spring.context.annotation.EnableDubbo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
@EnableDubbo(scanBasePackages = "com.example.dubbo.provider")
public class ProviderApplication {public static void main(String[] args) {SpringApplication.run(ProviderApplication.class, args);}
}
application.properties:
# Dubbo Application Info
dubbo.application.name=user-provider
dubbo.registry.address=zookeeper://localhost:2181
dubbo.protocol.name=dubbo
dubbo.protocol.port=20880
3. 实现服务消费者
创建服务消费者并调用远程服务:
ConsumerApplication.java:
java
package com.example.dubbo.consumer;import com.example.dubbo.service.UserApi;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
public class ConsumerApplication implements CommandLineRunner {@DubboReference(version = "1.0.0")private UserApi userApi;public static void main(String[] args) {SpringApplication.run(ConsumerApplication.class, args);}@Overridepublic void run(String... args) throws Exception {String result = userApi.sayHello("World");System.out.println(result); // Output: Hello, World}
}
application.properties:
# Dubbo Application Info
dubbo.application.name=user-consumer
dubbo.registry.address=zookeeper://localhost:2181
运行示例
-
启动 ZooKeeper: 确保 ZooKeeper 服务正在运行。如果没有安装,可以从 ZooKeeper 官网 下载并按照官方文档进行安装和启动。
-
启动服务提供者
bash
cd provider
mvn spring-boot:run
3.启动服务消费者
bash
cd consumer
mvn spring-boot:run
总结
通过对 Dubbo 源码的解析,我们深入了解了 RPC 框架的关键组成部分和工作原理,包括服务注册与发现、代理机制、序列化、负载均衡和通信协议。此外,我们还创建了一个简单的示例项目,展示了如何使用 Dubbo 进行服务开发和部署。
相关文章:
RPC 源码解析~Apache Dubbo
解析 RPC(远程过程调用)的源码可以帮助你深入理解其工作原理和实现细节。为了更好地进行源码解析,我们选择一个流行的 RPC 框架——Apache Dubbo 作为示例。Dubbo 是一个高性能、轻量级的开源 Java RPC 框架,广泛应用于企业级应用…...
VS Code--常用的插件
原文网址:VS Code--常用的插件_IT利刃出鞘的博客-CSDN博客 简介 本文介绍VS Code(Visual Studio Code)常用的插件。 插件的配置 默认情况下,插件会放到这里:C:\Users\xxx\.vscode\extensions 修改插件位置的方法 …...
深度学习 Pytorch 张量的索引、分片、合并以及维度调整
张量作为有序的序列,也是具备数值索引的功能,并且基本索引方法和python原生的列表、numpy中的数组基本一致。 不同的是,pytorch中还定义了一种采用函数来进行索引的方式。 作为pytorch中的基本数据类型,张量既具备了列表、数组的基…...

神州数码--制作wifi
防火墙: #ip vrouter trust-vr#router ospf 1#router-id 8.8.8.8#network 10.0.0.0/30 area 0.0.0.0#network 10.0.0.4/30 area 0.0.0.0#network 10.0.0.8/30 area 0.0.0.0 交换机: #vlan 10;50#ip add 192.168.10.1 255.255.255.0#int vlan 50#ip add 192.168.50.…...

Web前端开发技术之HTMLCSS知识点总结
学习路线 一、新闻网界面1. 代码示例2. 效果展示3. 知识点总结3.1 HTML标签和字符实体3.2 超链接、颜色描述与标题元素3.3 关于图片和视频标签:3.4 CSS引入方式3.5 CSS选择器优先级 二、flex布局1. 代码示例2. 效果展示3. 知识点总结3.1 span标签和flex容器的区别3.…...

客户案例:致远OA与携程商旅集成方案
一、前言 本项目原型客户公司创建于1992年,主要生产并销售包括糖果系列、巧克力系列、烘焙系列、卤制品系列4大类,200多款产品。公司具有行业领先的生产能力,拥有各类生产线100条,年产能超过10万吨。同时,经过30年的发展,公司积累了完善的销售网络,核心经销商已经超过1200个,超…...

【常见BUG】Spring Boot 和 Springfox(Swagger)版本兼容问题
???欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学习,不断总结,共同进步,活到老学到老…...

【Python】FastAPI入门
文章目录 第一节:FastAPI入门一、FastAPI框架介绍什么是ASGI服务(WSGI)1、补充Web开发1)Web前端开发2)Web后端开发 二、FastAPI安装1、安装Python虚拟环境2、安装FastAPI 三、第一个FastAPI案例1、访问接口和文档2、接…...
JavaScript系列(32)-- WebAssembly集成详解
JavaScript WebAssembly集成详解 🚀 今天,让我们深入了解JavaScript与WebAssembly的集成,这是一项能够显著提升Web应用性能的关键技术。 WebAssembly基础概念 🌟 💡 小知识:WebAssembly(简称W…...

wps数据分析000002
目录 一、快速定位技巧 二、快速选中技巧 全选 选中部分区域 选中部分区域(升级版) 三、快速移动技巧 四、快速录入技巧 五、总结 一、快速定位技巧 ctrl→(上下左右)快速定位光标对准单元格的上下部分双击名称单元格中…...

无降智o1 pro——一次特别的ChatGPT专业模式探索
这段时间和朋友们交流 ChatGPT 的使用心得,大家都提到一个很“神秘”的服务:它基于 O1 Pro 模型,能够在对话里一直保持相对高水平的理解和回复,不会突然变得“降智”。同时,整体使用还做了免折腾的网络设置——简单一点…...
前端:前端开发任务分解 / 开发清单
一、背景 前端开发过程中,好多任务同时开发,或者一个大的任务分解为若干个子任务进行开发,分解出去的很多内容容易记不清楚 / 不易过程管理,所以记录表格如下,方便开发同事,也辅助掌握整体开发情况。 二、…...

【Django自学】Django入门:如何使用django开发一个web项目(非常详细)
测试机器:windows11 x64 python版本:3.11 一、安装Django 安装步骤非常简单,使用pip安装就行 pip install django安装完成之后,python的 Scripts 文件夹下,会多一个 django-admin.exe (管理创建django项目的工具)。…...
面试经验分享-回忆版某小公司
说说你项目中数据仓库是怎么分层的,为什么要分层? 首先是ODS层,连接数据源和数据仓库,数据会进行简单的ETL操作,数据来源通常是业务数据库,用户日志文件或者来自消息队列的数据等 中间是核心的数据仓库层&a…...
WebSocket——推送方案选型
一、前言:为何需要服务端主动推送? 在现代应用中,很多功能都依赖于“消息推送”。比如: 小红点提醒:我们经常在手机应用里看到的一个小红点提示,表示有新的消息或任务需要我们关注。新消息提醒࿱…...
山石防火墙命令行配置示例
现网1台山石SG6000防火墙,配置都可以通过GUI实现。 但有一些配置在命令行下配置效率更高,比如在1个已有策略中添加1个host或端口。 下面的双引号可以不加 1 创建服务 1.1 单个端口 service "tcp-901"tcp dst-port 901 1.2 端口范围 servi…...

LLM - 大模型 ScallingLaws 的 C=6ND 公式推导 教程(1)
欢迎关注我的CSDN:https://spike.blog.csdn.net/ 本文地址:https://spike.blog.csdn.net/article/details/145185794 Scaling Laws (缩放法则) 是大模型领域中,用于描述 模型性能(Loss) 与 模型规模N、数据量D、计算资源C 之间关系的经验规律…...

Leetcode 983. 最低票价 动态规划
原题链接:Leetcode 983. 最低票价 class Solution { public:int mincostTickets(vector<int>& days, vector<int>& costs) {int n days.size();int last days[n - 1];int dp[last 1];map<int, int> mp;for (auto x : days)mp[x] 1;dp…...
Kafka——两种集群搭建详解 k8s
1、简介 Kafka是一个能够支持高并发以及流式消息处理的消息中间件,并且Kafka天生就是支持集群的,今天就主要来介绍一下如何搭建Kafka集群。 Kafka目前支持使用Zookeeper模式搭建集群以及KRaft模式(即无Zookeeper)模式这两种模式搭…...
springboot使用websocket
文章目录 一、概述1、简介 二、 使用1、引包2、配置处理器3、前端测试 一、概述 1、简介 简介略,附上官方文档,spring5和spring6的官方文档内容大致是一样的: https://docs.spring.io/spring-framework/docs/5.2.25.RELEASE/spring-framewo…...
浅谈 React Hooks
React Hooks 是 React 16.8 引入的一组 API,用于在函数组件中使用 state 和其他 React 特性(例如生命周期方法、context 等)。Hooks 通过简洁的函数接口,解决了状态与 UI 的高度解耦,通过函数式编程范式实现更灵活 Rea…...

微信小程序之bind和catch
这两个呢,都是绑定事件用的,具体使用有些小区别。 官方文档: 事件冒泡处理不同 bind:绑定的事件会向上冒泡,即触发当前组件的事件后,还会继续触发父组件的相同事件。例如,有一个子视图绑定了b…...

51c自动驾驶~合集58
我自己的原文哦~ https://blog.51cto.com/whaosoft/13967107 #CCA-Attention 全局池化局部保留,CCA-Attention为LLM长文本建模带来突破性进展 琶洲实验室、华南理工大学联合推出关键上下文感知注意力机制(CCA-Attention),…...
Java 语言特性(面试系列1)
一、面向对象编程 1. 封装(Encapsulation) 定义:将数据(属性)和操作数据的方法绑定在一起,通过访问控制符(private、protected、public)隐藏内部实现细节。示例: public …...
AtCoder 第409场初级竞赛 A~E题解
A Conflict 【题目链接】 原题链接:A - Conflict 【考点】 枚举 【题目大意】 找到是否有两人都想要的物品。 【解析】 遍历两端字符串,只有在同时为 o 时输出 Yes 并结束程序,否则输出 No。 【难度】 GESP三级 【代码参考】 #i…...

CocosCreator 之 JavaScript/TypeScript和Java的相互交互
引擎版本: 3.8.1 语言: JavaScript/TypeScript、C、Java 环境:Window 参考:Java原生反射机制 您好,我是鹤九日! 回顾 在上篇文章中:CocosCreator Android项目接入UnityAds 广告SDK。 我们简单讲…...
Robots.txt 文件
什么是robots.txt? robots.txt 是一个位于网站根目录下的文本文件(如:https://example.com/robots.txt),它用于指导网络爬虫(如搜索引擎的蜘蛛程序)如何抓取该网站的内容。这个文件遵循 Robots…...
leetcodeSQL解题:3564. 季节性销售分析
leetcodeSQL解题:3564. 季节性销售分析 题目: 表:sales ---------------------- | Column Name | Type | ---------------------- | sale_id | int | | product_id | int | | sale_date | date | | quantity | int | | price | decimal | -…...
OpenLayers 分屏对比(地图联动)
注:当前使用的是 ol 5.3.0 版本,天地图使用的key请到天地图官网申请,并替换为自己的key 地图分屏对比在WebGIS开发中是很常见的功能,和卷帘图层不一样的是,分屏对比是在各个地图中添加相同或者不同的图层进行对比查看。…...
Device Mapper 机制
Device Mapper 机制详解 Device Mapper(简称 DM)是 Linux 内核中的一套通用块设备映射框架,为 LVM、加密磁盘、RAID 等提供底层支持。本文将详细介绍 Device Mapper 的原理、实现、内核配置、常用工具、操作测试流程,并配以详细的…...