4、RocketMQ中Producer的启动(四)

在NameServer与Broker启动之后,我们就可以来创建Producer进行生产消息,客户端常用的生产类是DefaultMQProducer,我们启动消费者其实就是调用该类的start方法。
在这里插入图片描述
初始化逻辑

通过构建一起DefaultMQProducer类来实现初始化,查看源码我们可以得到DefaultMQProducer类的构造方法存在很多类型,但是最终会去执行最后一个构造方法。


public DefaultMQProducer(final String namespace, final String producerGroup, RPCHook rpcHook) {// 命名空间this.namespace = namespace;// 生产者组this.producerGroup = producerGroup;// 创建DefaultMQProducerImpl实例,负责发送消息defaultMQProducerImpl = new DefaultMQProducerImpl(this, rpcHook);
}

该构造函数主要就是用来指定命名空间,生产者组以及用来处理消息的发送的DefaultMQProducerImpl类,该类里面包含消息处理的所有方法。


public DefaultMQProducerImpl(final DefaultMQProducer defaultMQProducer, RPCHook rpcHook) {this.defaultMQProducer = defaultMQProducer;this.rpcHook = rpcHook;// 异步发送消息的线程池队列this.asyncSenderThreadPoolQueue = new LinkedBlockingQueue<Runnable>(50000);// 初始化线程this.defaultAsyncSenderExecutor = new ThreadPoolExecutor(Runtime.getRuntime().availableProcessors(),Runtime.getRuntime().availableProcessors(),1000 * 60,TimeUnit.MILLISECONDS,this.asyncSenderThreadPoolQueue,new ThreadFactory() {private AtomicInteger threadIndex = new AtomicInteger(0);@Overridepublic Thread newThread(Runnable r) {return new Thread(r, "AsyncSenderExecutor_" + this.threadIndex.incrementAndGet());}});
}

主要就是初始化一个异步发送消息的线程池。

启动逻辑

生产者的启动逻辑调用start方法,其实就是去调用DefaultMQProducerImpl的start方法。


public void start(final boolean startFactory) throws MQClientException {// 根据serviceState的状态来判断是否重复启动switch (this.serviceState) {case CREATE_JUST:this.serviceState = ServiceState.START_FAILED;// 检查配置信息this.checkConfig();if (!this.defaultMQProducer.getProducerGroup().equals(MixAll.CLIENT_INNER_PRODUCER_GROUP)) {this.defaultMQProducer.changeInstanceNameToPID();}// 客户端核心管理组件的初始化mQClientFactorythis.mQClientFactory = MQClientManager.getInstance().getOrCreateMQClientInstance(this.defaultMQProducer, rpcHook);// 往核心组件中注册一个Producer,往hashMap插入数据boolean registerOK = mQClientFactory.registerProducer(this.defaultMQProducer.getProducerGroup(), this);// 如果存在就报错if (!registerOK) {this.serviceState = ServiceState.CREATE_JUST;throw new MQClientException("The producer group[" + this.defaultMQProducer.getProducerGroup()+ "] has been created before, specify another name please." + FAQUrl.suggestTodo(FAQUrl.GROUP_NAME_DUPLICATE_URL),null);}// 注册topicPublishInfoTablethis.topicPublishInfoTable.put(this.defaultMQProducer.getCreateTopicKey(), new TopicPublishInfo());if (startFactory) {// 启动客户端通讯实例mQClientFactory.start();}log.info("the producer [{}] start OK. sendMessageWithVIPChannel={}", this.defaultMQProducer.getProducerGroup(),this.defaultMQProducer.isSendMessageWithVIPChannel());this.serviceState = ServiceState.RUNNING;break;case RUNNING:case START_FAILED:case SHUTDOWN_ALREADY:throw new MQClientException("The producer service state not OK, maybe started once, "+ this.serviceState+ FAQUrl.suggestTodo(FAQUrl.CLIENT_SERVICE_NOT_OK),null);default:break;}this.mQClientFactory.sendHeartbeatToAllBrokerWithLock();RequestFutureHolder.getInstance().startScheduledTask(this);
}

首先使用checkConfig去检查配置信息,比如生产者组等是否符合规范,并通过getOrCreateMQClientInstance方法根据clientId获取mQClientFactory的实例。

将生产者注册到MQClientInstance实例的ProducerTable当中,同时注册注册topicPublishInfoTable对象。

启动MQClientInstance实例,调用其start方法用于初始化netty服务,定时任务,拉取消息服务等。

最后主动调用sendHeartbeatToAllBrokerWithLock方法发送心跳信息给所有Broker并且移除超时的request请求,执行异常回调。

MQClientInstance的启动

MQClientInstance类封装了RockerMQ底层通讯处理的API,Producer与Consumer类都会使用到这个类,它是Producer,Consumer与NameServer,Broker打交道的网络通道,这个类可以理解成一个工厂,是对消费者与生产者以及控制台三者的一个合集,内部封装了netty客户端,消息的生成,消费和负载均衡的实现类等。

MQClientInstance类的初始化

public MQClientInstance(ClientConfig clientConfig, int instanceIndex, String clientId, RPCHook rpcHook) {this.clientConfig = clientConfig;this.instanceIndex = instanceIndex;// 创建netty客户端配置类this.nettyClientConfig = new NettyClientConfig();this.nettyClientConfig.setClientCallbackExecutorThreads(clientConfig.getClientCallbackExecutorThreads());this.nettyClientConfig.setUseTLS(clientConfig.isUseTLS());// 客户端请求处理器this.clientRemotingProcessor = new ClientRemotingProcessor(this);// 创建客户端远程通信API实现类的实例this.mQClientAPIImpl = new MQClientAPIImpl(this.nettyClientConfig, this.clientRemotingProcessor, rpcHook, clientConfig);// 更新nameServer的地址if (this.clientConfig.getNamesrvAddr() != null) {this.mQClientAPIImpl.updateNameServerAddressList(this.clientConfig.getNamesrvAddr());log.info("user specified name server address: {}", this.clientConfig.getNamesrvAddr());}// 客户端IDthis.clientId = clientId;// mq的admin控制台操作实现this.mQAdminImpl = new MQAdminImpl(this);// push模式下拉取消息服务this.pullMessageService = new PullMessageService(this);// 负载均衡服务器this.rebalanceService = new RebalanceService(this);this.defaultMQProducer = new DefaultMQProducer(MixAll.CLIENT_INNER_PRODUCER_GROUP);this.defaultMQProducer.resetClientConfig(clientConfig);// 消费者统计管理器this.consumerStatsManager = new ConsumerStatsManager(this.scheduledExecutorService);log.info("Created a new client Instance, InstanceIndex:{}, ClientID:{}, ClientConfig:{}, ClientVersion:{}, SerializerType:{}",this.instanceIndex,this.clientId,this.clientConfig,MQVersion.getVersionDesc(MQVersion.CURRENT_VERSION), RemotingCommand.getSerializeTypeConfigInThisServer());
}

初始化MQClientInstance类的时候会初始化netty客户端以及各种服务实例等。

MQClientInstance类的启动
在调用MQClientInstance的start方法时,会启动很多相关的类比如初始化netty客户端等,接下来我们就一起来分析。

public void start() throws MQClientException {synchronized (this) {switch (this.serviceState) {case CREATE_JUST:// 状态的二次校验this.serviceState = ServiceState.START_FAILED;// If not specified,looking address from name serverif (null == this.clientConfig.getNamesrvAddr()) {// 获取nameServer的地址this.mQClientAPIImpl.fetchNameServerAddr();}// Start request-response channel+// 启动mQClientAPIImpl,建立客户端与服务端的channelthis.mQClientAPIImpl.start();// Start various schedule tasks// 启动定时任务this.startScheduledTask();// Start pull service// 启动拉消息服务// 发送消息this.pullMessageService.start();// Start rebalance service// 负载均衡 rebalanceService为线程类,start方法执行run方法// 负载均衡服务,消费者与生产者建立关系this.rebalanceService.start();// Start push servicethis.defaultMQProducer.getDefaultMQProducerImpl().start(false);log.info("the client factory [{}] start OK", this.clientId);this.serviceState = ServiceState.RUNNING;break;case START_FAILED:throw new MQClientException("The Factory object[" + this.getClientId() + "] has been created before, and failed.", null);default:break;}}
}

首先会去启动一个netty的客户端,在前面分析的NettyRemotingServer类里面有过类似的启动逻辑,这里就不再重复,感兴趣的客户去看一下。

调用this.startScheduledTask()启动很多的定时任务,获取nameServer地址的,更新topic路由的,清除取消Broker以及发送心跳信息到所有Broker,持久化消费者进度,广播消息持久化到本地,集群消息推送到Broker端,尝试调整消费线程池的线程数量(目前还未实现该方法)。

注意:该类为生产者和消费者公用的类,所以作用于每一个Producer与Consumer。


private void startScheduledTask() {if (null == this.clientConfig.getNamesrvAddr()) {// 定时获取nameServer的地址this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {@Overridepublic void run() {try {MQClientInstance.this.mQClientAPIImpl.fetchNameServerAddr();} catch (Exception e) {log.error("ScheduledTask fetchNameServerAddr exception", e);}}}, 1000 * 10, 1000 * 60 * 2, TimeUnit.MILLISECONDS);}// 更新topic路由this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {@Overridepublic void run() {try {MQClientInstance.this.updateTopicRouteInfoFromNameServer();} catch (Exception e) {log.error("ScheduledTask updateTopicRouteInfoFromNameServer exception", e);}}}, 10, this.clientConfig.getPollNameServerInterval(), TimeUnit.MILLISECONDS);// 清理无效的Broker以及发送心跳信息给所有的Brokerthis.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {@Overridepublic void run() {try {MQClientInstance.this.cleanOfflineBroker();MQClientInstance.this.sendHeartbeatToAllBrokerWithLock();} catch (Exception e) {log.error("ScheduledTask sendHeartbeatToAllBroker exception", e);}}}, 1000, this.clientConfig.getHeartbeatBrokerInterval(), TimeUnit.MILLISECONDS);// 持久化消费者偏移量,即消费进度 广播消息持久化到本地,集群消息推送到Broker端this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {@Overridepublic void run() {try {MQClientInstance.this.persistAllConsumerOffset();} catch (Exception e) {log.error("ScheduledTask persistAllConsumerOffset exception", e);}}}, 1000 * 10, this.clientConfig.getPersistConsumerOffsetInterval(), TimeUnit.MILLISECONDS);// 尝试调整push模式的消费线程池的线程数量(没有实现逻辑)this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {@Overridepublic void run() {try {MQClientInstance.this.adjustThreadPool();} catch (Exception e) {log.error("ScheduledTask adjustThreadPool exception", e);}}}, 1, 1, TimeUnit.MINUTES);
}

更新topic路由信息

每隔30s从nameServer内部拉取并更新topic路由消息。

public void updateTopicRouteInfoFromNameServer() {// 使用set集合就是为了有效的去重Set<String> topicList = new HashSet<String>();// 从ConsumerTable中获取{Iterator<Entry<String, MQConsumerInner>> it = this.consumerTable.entrySet().iterator();while (it.hasNext()) {Entry<String, MQConsumerInner> entry = it.next();MQConsumerInner impl = entry.getValue();if (impl != null) {Set<SubscriptionData> subList = impl.subscriptions();if (subList != null) {for (SubscriptionData subData : subList) {topicList.add(subData.getTopic());}}}}}// 从producerTable中获取{Iterator<Entry<String, MQProducerInner>> it = this.producerTable.entrySet().iterator();while (it.hasNext()) {Entry<String, MQProducerInner> entry = it.next();MQProducerInner impl = entry.getValue();if (impl != null) {Set<String> lst = impl.getPublishTopicList();topicList.addAll(lst);}}}// 比较更新本地的路由信息for (String topic : topicList) {/*** 从nameSerer拉取到topic路由信息之后,调用topicRouteDataIsChange方法与本地* 的旧topic路由信息比较看是否更改,比较的数据包括:topic的队列信息queueDatas,* topic的broker信息brokerDatas,顺序topic配置orderTopicConf,* 消费过滤信息filterServerTable。* 当判断需要更新的时候,会更新本地的topic缓存,包括:*      1. 更新brokerName到brokerAddr的地址的映射关系,即brokerAddrTable;*      2. 更新生产者producerTable集合,更新MQProducerInner的topicPublishInfoTable属性;*      3. 更新消费者的consumerTable集合,更新MQConsumerInner的rebalanceImpl.topicSubscribeInfoTable属性;*      4. 更新topicRouteTable集合,更新本地topic路由信息。*/this.updateTopicRouteInfoFromNameServer(topic);}
}

主要逻辑就是从consumerTable以及producerTable中获取配置的所有topic集合,包括consumer订阅的topic集合以及Producer中topicPublishTable集合中的数据。

从nameSerer拉取到topic路由信息之后,调用topicRouteDataIsChange方法与本地的旧topic路由信息比较看是否更改,比较的数据包括:topic的队列信息queueDatas,topic的broker信息brokerDatas,顺序topic配置orderTopicConf,消费过滤信息filterServerTable。

清理无效的Broker

调用cleanOfflineBroker()方法区清除下线的Broker。

private void cleanOfflineBroker() {try {// 加锁if (this.lockNamesrv.tryLock(LOCK_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS))try {ConcurrentHashMap<String, HashMap<Long, String>> updatedTable = new ConcurrentHashMap<String, HashMap<Long, String>>(this.brokerAddrTable.size(), 1);Iterator<Entry<String, HashMap<Long, String>>> itBrokerTable = this.brokerAddrTable.entrySet().iterator();// 遍历brokerAddrTablewhile (itBrokerTable.hasNext()) {Entry<String, HashMap<Long, String>> entry = itBrokerTable.next();String brokerName = entry.getKey();HashMap<Long, String> oneTable = entry.getValue();HashMap<Long, String> cloneAddrTable = new HashMap<Long, String>(oneTable.size(), 1);cloneAddrTable.putAll(oneTable);Iterator<Entry<Long, String>> it = cloneAddrTable.entrySet().iterator();while (it.hasNext()) {Entry<Long, String> ee = it.next();String addr = ee.getValue();// 判断broker地址是否存在于topicRouteTable的任意一个topic的路由信息中,// 如果不存在则直接移除该broker地址if (!this.isBrokerAddrExistInTopicRouteTable(addr)) {it.remove();log.info("the broker addr[{} {}] is offline, remove it", brokerName, addr);}}// 集合为空就移除if (cloneAddrTable.isEmpty()) {itBrokerTable.remove();log.info("the broker[{}] name's host is offline, remove it", brokerName);} else {// 否则更新剩下的updatedTable.put(brokerName, cloneAddrTable);}}// 不为空直接更新if (!updatedTable.isEmpty()) {this.brokerAddrTable.putAll(updatedTable);}} finally {this.lockNamesrv.unlock();}} catch (InterruptedException e) {log.warn("cleanOfflineBroker Exception", e);}
}

该方法间隔30s就会被执行,遍历并更新brokerAddrTable集合,其主要的步骤就是获取每一个人对象地址,首先去本地topicRouteTable是否存在,存在即保留不存在就表名Broker以下线需要被清除,如果brokerAddrTable中的value集合也是空则会直接删除键值对。

发送心跳信息给Broker

该方法每隔30s向所有的Broker发送心跳包的定时任务方法,客户的consumer和producer都是通过该定时任务发送心跳数据包的。在其他地方也会主动调用一次该方法,例如DefaultMQProducerImpl、DefaultMQPushConsumerImpl等类的start方法的结尾都会主动调用一次该方法。

public void sendHeartbeatToAllBrokerWithLock() {// 加锁if (this.lockHeartbeat.tryLock()) {try {// 发送心跳包给所有brokerthis.sendHeartbeatToAllBroker();// 上传过滤类到Broker对应的所有Filtersrv,push模式消费使用this.uploadFilterClassSource();} catch (final Exception e) {log.error("sendHeartbeatToAllBroker exception", e);} finally {this.lockHeartbeat.unlock();}} else {log.warn("lock heartBeat, but failed. [{}]", this.clientId);}
}

首先会通过prepareHeartbeatData方法准备心跳包的数据进行发送,然后会遍历brokerAddrTable的broker地址进行循环发送心跳包。
注意:如果单单启动了生产者,这时候我们要去判断是否为Master节点,由于生产者只能与Master发送消息数据,而当我们启动消费者此时我们就需要向所有的broker发送心跳包。

持久化消费偏移量

@Override
public void persistConsumerOffset() {try {this.makeSureStateOK();Set<MessageQueue> mqs = new HashSet<MessageQueue>();Set<MessageQueue> allocateMq = this.rebalanceImpl.getProcessQueueTable().keySet();mqs.addAll(allocateMq);// 持久化的方法this.offsetStore.persistAll(mqs);} catch (Exception e) {log.error("group: " + this.defaultMQPushConsumer.getConsumerGroup() + " persistConsumerOffset exception", e);}
}

调用persistAllConsumerOffset()进行持久化,而持久化的功能时消费端所有的。通过调用DefaultMQPushConsumer的persistAll()方法,如果你是广播就会执行LocalFileOffsetStore的对应方法,如果你是集群就会执行RemoteBrokerOffsetStore的对应方法。

发送消息的逻辑在Producer的消息发送逻辑里面,下一章分析。

负载均衡也是相对于消费端,消费端在处理消息的时候使用的消费模式,后期去消费端里面详解。

@Override
public void run() {log.info(this.getServiceName() + " service started");while (!this.isStopped()) {this.waitForRunning(waitInterval);// 执行该方法进行负载均衡this.mqClientFactory.doRebalance();}log.info(this.getServiceName() + " service end");
}

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

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

相关文章

15.(vue3.x+vite)组件间通信方式之默认插槽(匿名插槽)

前端技术社区总目录(订阅之前请先查看该博客) 示例效果 默认插槽(匿名插槽) 插槽 slot 通常用于两个父子组件之间,最常见的应用就是我们使用一些 UI 组件库中的弹窗组件时,弹窗组件的内容是可以让我们自定义的,这就是使用了插槽的原理。 (1)slot 是 Vue中的内置标签…

认识 Redis - Redis 的安装与下载

Redis 从今天开始&#xff0c;我们迎接一个全新的专栏 - Redis 专栏。 相信我们在日常的学习中&#xff0c;肯定也是对 Redis 早有耳闻&#xff0c;今天我们还是先来了解一下 Redis 是什么&#xff1f;以及 Redis 应该怎么使用&#xff1f;… 1. Redis 是什么&#xff1f; …

uni-app 微信小程序之swiper轮播图

1. 实现效果 2. 完成代码 <template><view class"components-home"><view style"margin-top:-50rpx;height: 486rpx; position: relative;margin-bottom: 80rpx;"><image srchttps://xxx.com/img/wccQQP.png modewidthFix classpng …

精密基准电路WL431 输出电压可设定 响应速度快 可应用于电脑主板等产品上

WL431为三端可调节精密基准源。通过两个外接电阻&#xff0c;输出电压可在Vref约2.5 V )到36V连续调节。该电路输出阻抗小(0.2Q)。 开启特性好&#xff0c;在许多应用场合&#xff0c;它能较好地替换齐纳极管。 主要特点&#xff1a;● 温度系数 50pmC ● 在…

unity 2d入门飞翔小鸟按钮点击功能且场景切换(二)

1、素材包获取 链接: https://pan.baidu.com/s/1KgCtQ_7wt2mlbGbIaMVvmw 提取码: xxh8 2、将素材全部拉进去 3、创建新的场景 并且将场景添加到build settings里面 4、脚本 using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityE…

Vue阶段笔记(有js包)

目录 1.要先上传Vue的js包&#xff0c;包的路径在这&#xff1a; 2.获取 3.定义Vue接管的区域和他所要实现的内容 #整体代码如下&#xff1a; Vue的指令(被绑定得必须有声明) #v-bind #v-model #v-on #V-ifV-else-ifV-elseV-show #v-show #v-for 1.要先上传Vue的js包&…

WordPiece词表的创建

文章目录 一、简单介绍二、步骤流程2.1 预处理2.2 计数2.3 分割2.4 添加subword 三、代码实现 本篇内容主要介绍如何根据提供的文本内容创建 WordPiece vocabulary&#xff0c;代码来自谷歌&#xff1b; 一、简单介绍 wordpiece的目的是&#xff1a;通过考虑单词内部构造&…

FL Studio中如何录音的技巧,让你的声音更加出众哦!

​ Hey小伙伴们&#xff01;今天我要和大家分享一下在FL Studio中如何录音的技巧&#xff0c;让你的声音更加出众哦&#xff01; 编曲软件FL Studio 即“Fruity Loops Studio ”&#xff0c;也就是众所熟知的水果软件&#xff0c; 全能音乐制作环境或数字音频工作站&#xff0…

nodejs+vue+ElementUi酒店餐饮客房点餐管理系统

系统非功能需求&#xff0c;只能是为了满足客户需求之外的非功能性要求。系统需要具有数据完整性验证的功能&#xff0c;对界面上非法的数据和不完整的数据进行提示&#xff0c;不能直接保存到数据库中&#xff0c;造成不完整性因素。运行软件:vscode 前端nodejsvueElementUi 语…

win11 install oh-my-posh

安装配置 下载 Nerd Fonts 字体 oh-my-posh font installNerd Fonts 网站下载&#xff0c;解压后右击安装 为终端设置 Nerd Fonts 字体 修改 Windows 终端设置&#xff08;默认快捷方式&#xff1a;CTRL SHIFT ,&#xff09;&#xff0c;在settings.json文件defaults属性下添…

【Vue】安装 vue-router 库报错 npm ERR! ERESOLVE unable to resolve dependency tree

问题描述 运行npm install vue-router&#xff0c;安装vue-router库&#xff0c;npm报错。 npm ERR! code ERESOLVE npm ERR! ERESOLVE unable to resolve dependency tree npm ERR! npm ERR! While resolving: my-project0.1.0 npm ERR! Found: vue2.7.15 npm ERR! node_mod…

typescript中的策略模式

typescript中的策略模式 当我们需要以整洁、易于维护和易于调试的方式构建应用程序时&#xff0c;使用设计模式是一种非常好的方式。 在本文中&#xff0c;我们的目标是阐明如何将策略模式无缝地集成到我们的应用程序中。如果我们熟悉依赖性注入&#xff0c;可能会发现策略模…

数字化车间|用可视化技术提升车间工作效率

数字化车间正在成为现代制造业的重要组成部分。随着科技的不断进步&#xff0c;传统的车间生产方式逐渐地被数字化和自动化取代。数字化车间将机器和软件进行整合&#xff0c;实现了生产过程的高效、精确和可追溯。在数字化车间中&#xff0c;机器之间可以进行无缝的通信和协作…

Linux 中用户与权限

1.添加用户 useradd 1&#xff09;创建用户 useradd 用户名 2&#xff09;设置用户密码 passwd 用户名 设置密码是便于连接用户时使用到&#xff0c;如我使用物理机链接该用户 ssh 用户名 ip 用户需要更改密码的话&#xff0c;使用 passwd 指令即可 3)查看用户信息 id 用…

Landsat 5 C02数据集2007-2011年

Landsat 5是美国陆地卫星系列&#xff08;Landsat&#xff09;的第五颗卫星&#xff0c;于1984年3月1日发射&#xff0c;2011年11月停止工作。16天可覆盖全球范围一次。Landsat5_C2_TOA数据集是由Collection2 level1数据通过MTL文件计算得到的TOA反射率产品。数据集的空间分辨率…

STM32开发基础知识之位操作、宏定义、ifdef条件编译、extern变量申明、typedef类型别名、结构体

一、引言 本文将对STM32入门开发的基本C语言基础知识进行回顾和总结&#xff0c;一边学者在开发过程中能较顺利地进行。主要包括位操作、define宏定义、ifdef条件编译、extern变量申明、typedef类型别名、结构体等基本知识。 二、基础C语言开发知识总结 &#xff08;一&…

无频闪护眼灯哪个好?顶级无蓝光频闪护眼台灯推荐

国家卫生健康委员会疾控局宋士勋表示&#xff0c;根据近期发布的2021年监测数据来看&#xff0c;截至2020年&#xff0c;我国儿童青少年总体的近视率是52.7%&#xff0c;从不同年龄段来看&#xff0c;幼儿园6岁孩子的近视率达到14.3%&#xff0c;小学达到35.6%&#xff0c;初中…

『亚马逊云科技产品测评』活动征文|基于亚马逊EC2云服务器配置Nginx静态网页

授权声明&#xff1a;本篇文章授权活动官方亚马逊云科技文章转发、改写权&#xff0c;包括不限于在 Developer Centre, 知乎&#xff0c;自媒体平台&#xff0c;第三方开发者媒体等亚马逊云科技官方渠道 亚马逊EC2云服务器&#xff08;Elastic Compute Cloud&#xff09;是亚马…

【Linux】Linux基础

文章目录 学习目标操作系统不同应用领域的主流操作系统虚拟机 Linux系统的发展史Linux内核版和发行版 Linux系统下的文件和目录结构单用户操作系统vs多用户操作系统Windows和Linux文件系统区别 Linux终端命令格式终端命令格式查阅命令帮助信息 常用命令显示文件和目录切换工作目…

Spatial Data Analysis(三):点模式分析

Spatial Data Analysis&#xff08;三&#xff09;&#xff1a;点模式分析 ---- 1853年伦敦霍乱爆发 在此示例中&#xff0c;我将演示如何使用 John Snow 博士的经典霍乱地图在 Python 中执行 KDE 分析和距离函数。 感谢 Robin Wilson 将所有数据数字化并将其转换为友好的 G…