MQTT 客户端出现连接订阅等问题时如何排查?

大家好,这是一期社区专题 FAQ。我们整理了近期社区中关注度较高的问题,在这里进行统一汇总解答。

今后本系列内容将不定期推送,敬请关注。

同时,如果大家在使用 EMQX 的过程中遇到问题,欢迎通过以下方式进行解决:

  • 查阅 EMQX 产品文档与博客文章。
  • 如果在现有资料中未能查询到问题的解决办法,可以在问答社区中留言提问,我们会尽快解答您的问题。

Q:向 MQTT Broker 发布多条消息,MQTT Broker 向订阅者转发这些消息的时候能否保证原始顺序?

MQTT Broker 一定会保证来自同一客户端的相同主题的消息按照到达顺序被转发,这与消息的 QoS 等级无关,QoS 等级不会影响转发顺序,不管是消息丢失,还是消息重复,也都不会导致消息失序。

对于不同主题的消息,MQTT Broker 不会提供转发顺序保证,我们可以将他们视为进入了不同的通道,比如主题 A 的消息先于主题 B 的消息到达 MQTT Broker,但最终可能主题 B 的消息会更早下发。

Q:我的客户端无法连接到 EMQX/订阅失败/发布消息但是对端没有收到任何消息,出现这些情况怎么办?

A:其实 EMQX 的 Debug 日志基本已经记录了所有的行为和现象,通过阅读 Debug 日志我们能够知道客户端何时发起了连接,连接时指定了哪些字段,连接是否通过,被拒绝连接的原因是什么等等。但是由于 Debug 日志记录的信息过多,会带来额外的资源消耗,并且不利于我们针对单个客户端或主题进行分析。

所以 EMQX 提供了日志追踪功能,我们可以指定想要追踪的客户端或主题,EMQX 会将所有与该客户端或主题相关的 Debug 日志都输出到指定日志文件中。这样不管是自己分析调试,还是寻求社区帮助,都会方便许多。

Q:为什么会有 Client ID 为 CENSYS 的或者是其他我不认识的客户端?

A:CENSYS 是一款互联网探测扫描工具,它会周期性扫描 IPv4 地址空间,探测 HTTP、SSH、MQTT 等协议的默认端口。所以如果你发现有 Client ID 为 CENSYS 的或者其他未知的客户端接入了你的 MQTT Broker,这意味你目前处于相对较低的安全性保障下。以下措施可以有效帮助你避免这个问题:

  1. 不要使用默认配置,例如 EMQX 用于验证 HTTP API 访问权限的 AppID 与 AppSecret 等
  2. 启用认证,可以是用户名密码认证,也可以是 JWT 认证,避免只需要知道 IP 地址就可以登录的尴尬情况
  3. 启用 TLS 双向认证,只有持有有效证书的客户端才能接入系统
  4. 启用授权,避免非法设备登录后可以获取敏感数据
  5. 配置你的防火墙,尽量关闭一些不需要的端口

Q:EMQX 是一个主题一个消息队列吗?

A:不是。EMQX 中的每个客户端进程都会有一个消息队列,这个消息队列会存储所有因飞行窗口满或连接断开而暂时无法下发给客户端的消息。消息队列有最大长度限制,以避免消息无限制堆积,达到最大长度后,为了使新消息继续入队,EMQX 会陆续丢弃队列中最老的消息。消息队列最大长度由 max_mqueue_len 这个配置项指定。

Q:EMQX 日志中出现 "Parse failed for function_clause" 是什么原因?

A:这个日志表示报文解析失败,可能因为这不是一个 MQTT 报文,我们遇到过很多向 MQTT 端口发送 HTTP 请求的情况,也可能因为报文中包含了非 UTF-8 字符等等。我们可以在这条 "Parse failed..." 日志中检索 Frame data 关键字以查看完整的报文,帮助我们分析解析失败的可能原因。

Q:EMQX 日志中出现 "Context: maximum heap size reached" 是什么原因?

A:出现这个日志通常表示相应的客户端进程已经达到了最大堆栈内存占用限制,之后这个进程就会被 EMQX 强制 Kill。这一机制存在的原因是为了保证 EMQX 的可用性,避免客户端进程的内存占用无限制增长最终导致 EMQX OOM。客户端进程的堆栈占用主要来源于飞行窗口和消息队列中未完成确认或未投递的消息,而这两处消息堆积的主要原因通常是客户端消费能力不足,无法及时处理响应消息。

与此相关的配置项是 force_shutdown_policy,它的配置格式为 <Maximum Message Queue Length>|<Maximum Heap Size>,例如 10000|64MB。其中 <Maximum Heap Size> 就是限制每个客户端进程能够占用的最大堆栈内存。

我们见过一些用户为了不想客户端进程被强制关闭,不去提升客户端的消费能力,而是一味增大 <Maximum Heap Size>,这除了给 EMQX 带来 OOM 风险,也会使得消息的时延增加,往往得不偿失。

版权声明: 本文为 EMQ 原创,转载请注明出处。

原文链接:https://www.emqx.com/zh/blog/mqtt-client-faq?utm_source=cloud.tencent.com&utm_medium=referral

本文转载于网络 如有侵权请联系删除

相关文章

  • 594. 最长和谐子序列

    和谐数组是指一个数组里元素的最大值和最小值之间的差别正好是1。 现在,给你一个整数数组nums,请你在所有可能的子序列中找到最长的和谐子序列的长度。 可以通过删除一些元素或不删除元素、且不改变其余元素的顺序而得到。 示例1: 输入:nums=[1,3,2,2,5,2,3,7] 输出:5 解释:最长的和谐子序列是[3,2,2,2,3] 示例2: 输入:nums=[1,2,3,4] 输出:2 示例3: 输入:nums=[1,1,1,1] 输出:0 classSolution{ publicintfindLHS(int[]nums){ /** 哈希表解法 把所有元素存入 定义最小值 遍历数组,看有灭有当前元素+1在表里面 有的话更新下res */ HashMap<Integer,Integer>map=newHashMap(); //把所有元素,和他出现的次数填入 for(inti:nums){ if(!map.containsKey(i)){ map.put(i,1); } else{ map.put(i,map.get(i)+1); } } //定义最小长度 intres=

  • Nginx结构全解析(42)

    3root和alias的使用nginx指定文件路径有两种方式root和alias,root与alias主要区别在于nginx如何解释location后面的uri,这会使两者分别以不同的方式将请求映射到服务器文件上。3.1最基本的区别alias指定的目录是准确的,给location指定一个目录。root指定目录的上级目录,并且该上级目录要含有locatoin指定名称的同名目录。以root方式设置资源路径:语法:rootpath;配置块:http、server、location、if语法:aliaspath;配置块:locationeglocation/img/{alias/var/www/image/;}#若按照上述配置的话,则访问/img/目录里面的文件时,ningx会自动去/var/www/image/目录找文件location/img/{root/var/www/image;}#若按照这种配置的话,则访问/img/目录下的文件时,nginx会去/var/www/image/img/目录下找文件

  • 40个Java多线程问题总结

    40个问题汇总 1、多线程有什么用?一个可能在很多人看来很扯淡的一个问题:我会用多线程就好了,还管它有什么用?在我看来,这个回答更扯淡。所谓"知其然知其所以然","会用"只是"知其然","为什么用"才是"知其所以然",只有达到"知其然知其所以然"的程度才可以说是把一个知识点运用自如。OK,下面说说我对这个问题的看法:(1)发挥多核CPU的优势随着工业的进步,现在的笔记本、台式机乃至商用的应用服务器至少也都是双核的,4核、8核甚至16核的也都不少见,如果是单线程的程序,那么在双核CPU上就浪费了50%,在4核CPU上就浪费了75%。单核CPU上所谓的"多线程"那是假的多线程,同一时间处理器只会处理一段逻辑,只不过线程之间切换得比较快,看着像多个线程"同时"运行罢了。多核CPU上的多线程才是真正的多线程,它能让你的多段逻辑同时工作,多线程,可以真正发挥出多核CPU的优势来,达到充分利用CPU的目的。(2)防止阻塞从程序运行效率的角度

  • 趣玩爬虫 | 12306火车车次信息爬取分析

    点击上方蓝色字关注[啃饼思录]~12306火车车次信息爬取分析本篇我们要进行的是12306火车车次信息的爬取分析。都说12306是目前反爬措施最强的网站,的确如此。博主于2017年专门研究过如何爬取并进行了购票分析,费了很大功夫终于成功地抢到了票,但是很不幸,没过多久12306就进行了大改版,写过的爬虫代码几乎成为了摆设。这里只是爬取车次信息,并不进行购票操作,后续可能会出专门的教程介绍这一块,本篇文章的重点不在于此。分析与爬取过程车站名称信息爬取12306网址:https://www.12306.cn/index/,由于我们爬取的是车次信息,因此首先需要知道所有的车站名称: 通过观察,很容易发现出发地和到达地都是一个弹窗,由JS控制。我们思考一下,如果想要爬取所有的车站名称,是不是爬取这个JS弹窗内的信息就可以?是的,我们就是这样做的。首先我们需要登录账号,接着进行下面的操作。我们按F12或者直接开启开发者模式,先在出发地随便选一个车站,然后观察network处的变化,我们发现了一个名为station_name_v10026.js的文件,就是这样: 接着我们就点击这个js文件

  • 你的接口,真的能承受高并发吗?

    本文转自公众号【肥朝】 前言本篇主要讲解的是前阵子的一个压测问题.那么就直接开门见山可能有的朋友不并不知道forceTransactionTemplate这个是干嘛的,首先这里先普及一下,在Java中,我们一般开启事务就有三种方式XML中根据service及方法名配置切面,来开启事务(前几年用的频率较高,现在基本很少用)@Transactional注解开启事务(使用频率最高)采用spring的事务模板(截图中的方式,几乎没什么人用)我们先不纠结为什么使用第三种,后面在讲事务传播机制的时候我会专门介绍,我们聚焦一下主题,你现在只要知道,那个是开启事务的意思就行了.我特意用红色和蓝色把日志代码圈起来,意思就是,进入方法的时候打印日志,然后开启事务后,再打印一个日志.一波压测之后,发现接口频繁超时,数据一致压不上去.我们查看日志如下:我们发现.这两个日志输出时间间隔,竟然用了接近5秒!开个事务为何用了5秒?事出反常必有妖!如何切入解决问题线上遇到高并发的问题,由于一般高并发问题重现难度比较大,所以一般肥朝都是采用眼神编译,九浅一深静态看源码的方式来分析.具体可以参考本地可跑,上线就崩?慌了!

  • 日本网络安全部长从来没用过电脑,甚至不知道USB是什么

    我们经常说“专业的人做专业的事”,而日本一个表示没用过电脑,甚至不知道USB是什么的人却已经成为了网络安全部长,并且他将负责2020年东京奥运会网络安全的准备工作。不知道国际奥组会对此作何感想……2018年10月2日,日本安倍晋三任命樱田义孝出任网络安全与奥运会部长。而在11月14日,这位新升职的部长就出了大糗。在问答环节,樱田义孝被问到“你自己使用过电脑吗?”作为现任的网络安全部长,樱田义孝的回答却让人震惊:电脑相关的操作我一般都指示职员来做,所以自己并没有用过电脑。随即现场一片哄笑,作为一个安全部长负责管理国家网络安全的相关工作,甚至还将负责2020年东京奥运会的网络安全准备工作,却是一个从来没有使用过电脑的人在主导。拥有这种顾虑的当然不只是我们,当场提问的议员就提出了质疑:“让一个没有用过电脑的人来制定网络空间安全对策,就我认为是难以置信的”。而樱田义孝则表示,网络安全工作需要的是利用整个国家的力量,而不仅仅是自己的团队。现场貌似多数人对他的回答不太感冒,引起了一阵哄笑。有了这样的开头,似乎现场有更多的人像知道这位部长对计算机的了解究竟是什么地步。在探究日本核电站的安全性问题上,

  • 一文盘点三大顶级Python库(附代码)

    来源:开源最前线本文约1500字,建议阅读5分钟。 本文为你分享最受数据科学青睐的3个顶级的Python库。Python在许多方面有着强大的吸引力-例如效率、代码可读性和速度方面,也正因为如此,对于希望提升应用程序功能的数据科学家和机器学习专家来说,Python通常是首选编程语言。(例如,AndreyBulezyuk使用Python编程语言创建了一个很牛逼的机器学习应用程序。)由于其广泛的用途,Python拥有大量的库,使数据科学家可以更轻松地完成复杂的任务,而无需面对很多编程麻烦。以下是最受数据科学青睐的3个顶级的Python库,如果你正需要,那就试试吧。1.NumPyNumPy(NumericalPython的缩写)是顶级的库之一,它配备了大量有用的资源来帮助数据科学家将Python变成强大的科学分析和建模工具。这个流行的开源库可以在BSD许可下使用。它是在科学计算中执行任务的基础Python库。NumPy是一个更大的基于python的开源工具生态系统SciPy的一部分。这个库为Python提供了大量的数据结构,可以轻松地执行多维数组和矩阵计算。除了用于求解线性代数方程和其他数学计

  • 大数据与深度学习在一起:雅虎开源TensorFlowOnSpark

    量子位李林|编译雅虎宣布开源一个名为TensorFlowOnSpark的项目,支持对ApacheSpark集群进行分布式TensorFlow训练和推断。TensorFlowOnSpark的开源代码已经在GitHub上发布。ApacheSpark是一个用于处理大数据的开源框架,旨在提高并行计算的效率。Netflix就是用它来处理大量的用户数据,以提供个性化推荐。Spark它和机器学习密不可分,目前流行的深度学习算法更是特别依赖于庞大的数据量。雅虎可以说是Spark社区的模范成员,去年,他们开源了CaffeOnSpark,刚刚开源的TensorFlowOnSpark原理几乎和它完全相同,只是换了个更流行的深度学习框架。在开发TensorFlowOnSpark之前,雅虎的工程师们为了吧TensorFlow和Spark结合起来,尝试了SparkNet、TensorFrame等现有的工具,但最终还是决定自己做一个。这个开源项目的介绍中为它列出了如下优点:▪很容易将现有的TensorFlow项目迁移过来,只需要不到10行的代码;▪支持所有TensorFlow功能:同步/异步训练,模型/数据并行,推

  • 腾讯云数据万象触发任务(独立节点)任务与工作流

    功能描述通过独立任务参数触发批量数据处理任务。 APIExplorer提供了在线调用、签名验证、SDK代码生成和快速检索接口等能力。您可查看每次调用的请求内容和返回结果以及自动生成SDK调用示例。 请求请求示例POST/inventorytriggerjobHTTP/1.1 Host:<BucketName-APPID>.ci.<Region>.myqcloud.com Date:<GMTDate> Authorization:<AuthString> Content-Length:<length> Content-Type:application/xml 复制 说明: Authorization:AuthString(详情请参见请求签名文档)。 通过子账号使用时,需要授予相关的权限,详情请参见授权粒度详情文档。 请求头此接口仅使用公共请求头部,详情请参见公共请求头部文档。 请求体该请求操作的实现需要有如下请求体。 <Request> <Name>demo&l

  • 「gym - 102331J」Jiry Matchings

    目录descriptionsolutioncodedetails description 给定一棵带权树,对于\(k\in[1,n-1]\)的每个\(k\),输出匹配数为\(k\)的最大权匹配的权和。 problemlink。 solution 记\(f_{0/1,x,i}\)表示在\(x\)的子树中选\(i\)条匹配,不选中/选中\(x\)的最大和。 不难想到\(f\)是凸的(根据费用流的性质),因此合并两个\(f\)的过程可以做到线性。 如果在链上,可以分治做到\(O(n\logn)\)。 如果在树上,可以进行链分治:将树轻重链剖分,以重链为单位自底向上合并。具体来说你需要做两步: (1)对于每个点,合并它所有轻儿子的答案,可以分治; (2)对于每条重链,套用链的分治方法求出答案。 总复杂度\(O(n\log^2n)\)。 code #include<bits/stdc++.h> usingnamespacestd; typedeflonglongll; typedefvector<ll>vl; constintN=200000; constllINF

  • 159_模型_Power BI 地理分析之形状地图

    159_模型_PowerBI地理分析之形状地图 声明以下地图元素仅供学习交流所用,如需地图公开使用请提前做好报审工作。 一、背景 当企业的体量达到一定体量的时候,保持稳定的增长是非常重要的事情。本案例展示如何用PowerBI的形状地图来寻求业务的增长。 我们先来看结论: 省级市场共计:34个,已开拓:34个;占比:100.0%。 地市级市场共计:370个,已开拓:214个;占比:57.8%。 区县级市场共计:2875个,已开拓:356个;占比:12.4%。 其实从这个三条结论来看,市场层级越下沉,其实我们的空白市场越多;当然要业务能支撑这样的规模才行。 通过三张图,就让各大区领着各自团队回去开会,制定一个空白市场开发的计划及相应的配套预算。 PowerBI公共web效果:https://demo.jiaopengzi.com/pbi/159-full.html 二、模型设计 1、地图数据 要分析从省份>地市>区县三个层级的地理业务数据,首先要准备的是省份、地市、区县三个层级的地理数据。 数据获取可以从阿里云的DataV.GeoAtlas(https:/

  • 通信常识

    扩频方式主要有DSSS,FHSS,THSS等。复用和多址主要有TDMA,FDMA,CDMA,SDMA,OFDM等。调制方式主要有BPSK,QPSK,QAM等。 每个子载波又采用BPSK,QAM,16QAM,64QAM的调制方式,每种调制方式都对应不同的数据速率。  IQ信号深度解析  根号(I²+Q²)是IQ信号的振幅, 功率就是振幅平方?   无线通信自由空间路径损耗   https://www.cnblogs.com/hzl6255/p/9808053.html:ZigBee物理层数据包长范围6~(127+6)bytes。 本博客由博主原创,链接:https://www.cnblogs.com/WindyZ/

  • 【Python】调用WPS V9 API,实现PPT转PDF

    WPS的API,即COM,主要分为V8与V9两个版本,网上容易查到的例子,都是V8的。现在官网上可以下载的,2013抢鲜版,就是V9的API。 Python调用COM需要安装PythonforWindowsExtensions,即pywin32 调用就很简单了,直接代码:   importsys importos importwin32com.client argc=len(sys.argv) print("Numberofarguments:",argc,'arguments.') print('ArgumentList:',str(sys.argv)) ifargc<2: sys.exit(-1) src=sys.argv[1] dst=sys.argv[2] wpp=win32com.client.Dispatch("Kwpp.Application") #o.Visible=False ppt=wpp.Presentations.Open(src) ppt.SaveAs(dst,32) ppt.Close() wpp.Quit() sys.exit

  • Python写xml文件

      在上一篇使用selenium找出外卖点餐次数最多的10个顾客中,抓取信息后只输出了点餐次数TOPN的顾客,其实可以把信息保存到本地,便于做更多的统计。   为了便于后续的读取处理,这里就将信息保存在xml文件中,想到得到的文件如下: 1<?xmlversion="1.0"encoding="utf-8"?> 2<orderlist> 3<order> 4<customer>姓名1</customer> 5<phone>电话1</phone> 6<address>地址1</address> 7<count>点餐次数1</count> 8</order> 9<order> 10<customer>姓名2</customer> 11<phone>电话2</phone> 12<address>地址2</address> 13<count>点餐次数2&l

  • 改变文档结构的方法(5种)

    1.在元素的最后面追加子元素 语法:父元素.appendChild(子元素对象) 案例:   1<!DOCTYPEhtml> 2<html> 3<headlang="en"> 4<metacharset="UTF-8"> 5<title></title> 6 7<style> 8.main{ 9width:600px; 10height:300px; 11margin:0auto; 12display:flex; 13background-color:#5d656b; 14} 15.child{ 16width:200px; 17height:400px; 18text-align:center; 19} 20.c1{ 21background-color:#e8e318; 22} 23.c2{ 24background-color:darkgoldenrod; 25} 26.c3{ 27background-color:chartreuse; 28} 29#d1{ 30width:200p

  • 卸载伽卡他卡学生端后如何打开任务管理器

    很多人卸载了伽卡他卡学生端后发现,任务管理器仍然打不开。这里教大家如何打开。 1.按Win+R组合键(Win就是键盘上那个窗户辣)打开运行命令,当然在开始菜单打开也可以。然后输入regedit打开注册表编辑器 2.沿着这条线依次打开 HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Policies\System直到找到这个界面 3.双击打开 4.修改至下图 5.点击确定,任务管理器就出现了 附:这种方法也可以在启动伽卡他卡学生端后启动任务管理器,学生端启动后会自动把上面那个DWORD的值改成1,手动将其改成0即可。不过任务管理器无法关闭学生端,要想关闭可以用360软件小助手的加速功能,或者断网(虽然断网不能关闭学生端,但至少老师控制不了)。 伽卡他卡教师端安装程序密码4z7n

  • vue前端开发仿钉图系列(4)右侧行政区绘制的开发详解

      行政区绘制是基于高德地图的api,需要在高德提供的代码基础上做好html代码在vue页面上的适配。核心功能就是选择省市区,可以根据需要绘制对应选中的地图图层。整理总结不易,如需全部代码,请联系我15098950589(微信同号)。和以往一样,先发一下效果图。       行政区查询核心代码如下     getData(data,level){ varcitySelect=document.getElementById('city'); vardistrictSelect=document.getElementById('district'); varareaSelect=document.getElementById('street');   varbounds=data.boundaries;   if(bounds){ varpolygon for(vari=0,l=bounds.length;i<l;i++){ polygon=newAMap.Pol

  • k8s 网络策略案例

    k8s-网络策略案例 1.k8s-网络策略案例 案例1:拒绝其他命名空间Pod访问 案例2:同一个命名空间下应用之间限制访问 案例3:只允许指定命名空间中的应用访问 附:准备环境快捷命令 kubectlrunbusybox--image=busybox-ntest--sleep12h kubectlrunweb--image=nginx-ntest kubectlexecbusybox-ntest--ping10.244.169.135 复制 2.案例1:拒绝其他命名空间Pod访问 需求:test命名空间下所有pod可以互相访问,也可以访问其他命名空间Pod,但其他命名空间不能访问test命名空间Pod。 示例: apiVersion:networking.k8s.io/v1 kind:NetworkPolicy metadata: name:deny-all-namespaces namespace:test spec: podSelector:{}#未配置,匹配本命名空间所有pod policyTypes: -Ingress ingress: -fr

  • FFmpeg(二) 解封装相关函数理解

    一、解封装基本流程   ①av_register_All()////初始化解封装,注册解析和封装的格式。   ②avformat_netword_init()//初始化网络,解析rtsp协议   ③avformat_open_init()//打开   ④avformat_find_stream_info()//探测   ⑤av_find_best_stream() //获取音视频的索引   ⑥av_read_Frame()//读一帧数据,音频可能好几帧、视频是I帧   ⑦av_seek_frame()//跳转复制 二、函数介绍   ①av_register_all();    初始化解封装,注册解封装格式     在最开始编译FFmpeg的时候,我们做了一个configure的配置,其中开启或者关闭了很多选项。configure的配置会生成两个文件:config.mk和config.h。       config.mk:就是makefile文件需要包含进去的子模块,会作用在编译阶段,帮助开发者编译出正确的库。       config.h:作用在运行阶段,主要是确定需要注册那些容器

  • Develop with asyncio部分的翻译

    Developwithasyncio 异步程序和普通的连续程序(也就是同步程序)是很不一样的,这里会列出一些常见的陷阱,并介绍如何去避开他们。 Debugmodeofasyncio 我们用asyncio就是为了提高性能,而为了更容易去开发编写异步的代码,我们需要开启debug模式 在应用中开启调试模式: 全局开启异步的调试模式,可以通过设置环境变量PYTHONASYNCIODEBUG=1,或者直接调用AbstractEventLoop.set_debug() 设置asynicologger的日志等级为DEBUG,如在代码开头logging.basicConfig(level=logging.DEBUG) 配置warnings模块去显示ResourceWarning警告,如在命令行中添加-Wdefault这个选项去启动python来显示这些 Cancellation 取消任务(执行)这个操作对于普通的程序来讲并不常见,但是在异步程序中,这不仅是个很普通的事情,而且我们还需要去准备好去处理它们。 可以直接用Future.cancel()这个方法去取消掉Future类和tasks类,而w

  • 性能提升的14条规则(八)

    规则8——使用外部的JavaScript和CSS 内联VS外置 内联示例只有一个HTML文档,其大小为87KB,所有的JavaScript和CSS都包含在HTML文件自身中。外部示例包含一个HTML文档(7KB)、一个样式表(59KB)和三个脚本(1KB、11KB和9KB),总计87KB。尽管所需下载的总数据量是相同的,内联示例还是逼外部示例快30%~50%。这只要是因为外部示例需要承担多个HTTP请求带来的开销。尽管外部示例可以从样式表和脚本的并行下载中获益,但一个HTTP请求与五个HTTP请求之间的差距导致内联示例更快一些。 尽管结果如此,现实中还是使用外部文件会产生较快的页面。这是由于本例中没有涉及的、外部文件所带来的收益——JavaScript和CSS文件有机会被浏览器缓存起来。HTML文档——至少是那些包含动态内容的HTML文档——通常不会被配置为可以进行缓存。当遇到这种情况时(HTML文档没有被缓存),每次请求HTML文档都要下载内联的JavaScript和CSS。另一方面,如果JavaScript和CSS是外部文件,浏览器就能缓存它们,HTML文档的大小减小,而且不会增加

相关推荐

推荐阅读