uni-app 项目的视图层和逻辑层是分离开的,虽然我们在开发项目过程中,将 html、js 代码都写在同一个文件中,但是实际运行的时候是分离开的。视图层负责进行页面渲染,也就是用户能看到的页面,用来展示数据的,包括页面结构代码 <template> 部分、页面样式代码 <style> 部分。逻辑层负责执行后端的业务逻辑,用户看不到这部分的逻辑,用来处理数据的,包括页面逻辑代码 <script> 部分,以及其他 .js 文件。视图层和逻辑层分离,减少了项目的耦合程度,并且逻辑层的运算不会影响到视图层的渲染,窗体动画会比较稳。而两层分离也有一个缺点,就是这两层互相通信会有损耗。我们下面进行的性能优化,都是基于uni-app 运行原理来操作的,下面来具体看一下。
在介绍 Watch 的原理之前,我们先熟悉一个概念:Zookeeper 客户端对 Znode 的写操作,也就是新增节点、更新节点、删除节点这些操作,默认会开启监听;Zookeeper 客户端对 Znode 的读操作,也就是查询节点数据、查询节点是否存在、查询子节点等操作,需要手动设置开启监听。这也是为什么在 GetDataRequest 请求体中会有 watch 这个属性的原因。Watch 的运行过程分为 4 部分,分别是:客户端注册 Watch 、服务端注册 Watch、服务端触发 Watch、客户端处理回调。客户端注册 Watch当我们使用 Zookeeper 客户端向 Zookeeper 服务端发送带有事件监听的请求时,Zookeeper 客户端会把该请求标记成带有 Watch 的请求,然后把 Watch 监听器注册到 ListenerManager 中。服务端注册 WatchZookeeper 服务端接收到 Zookeeper 客户端发送过来的请求,解析请求体,判断该请求是否带有 Watch 事件,如果有 Watch 事件,就会把 Watch 事件注册到 WatchManager 中。服务端触发 WatchZookeeper 服务端注册完 Watch 事件后,会调用 WatchManager 的 triggerWatch 方法来触发 Watch 事件,Watch 事件完成后,向客户端发送响应。客户端处理回调Zookeeper 客户端接收到 Zookeeper 服务端的响应后,解析响应体,根据响应体的类型去 ListenerManager 中查找相对应的 Watch 监听器,然后触发监听器的回调函数。
原理介绍:Java 语言提供了一种弱同步机制,即 volatile 变量,用来确保将变量的更新操作通知到其他线程。当把变量声明为 volatile 类型后,编译器与运行时都会注意到这个变量是共享的,volatile 变量不会被缓存在寄存器或者对其他处理器不可见的地方,因此在读取 volatile 类型的变量时总会返回最新写入的值。Tips:在访问 volatile 变量时不会执行加锁操作,因此也就不会使执行线程阻塞,因此 volatile 变量是一种比 sychronized 关键字更轻量级的同步机制。我们来通过下图对非 volatile 关键字修饰的普通变量的读取方式进行理解,从而更加细致的了解 volatile 关键字修饰的变量。当对非 volatile 变量进行读写的时候,每个线程先从内存拷贝变量到 CPU 缓存中。如果计算机有多个 CPU,每个线程可能在不同的 CPU 上被处理,这意味着每个线程可以拷贝到不同的 CPU cache 中。而声明变量是 volatile 的,JVM 保证了每次读变量都从内存中读,跳过 CPU cache。
前面的小节我们介绍了代码编辑和资源管理方面的知识,从本小结开始我们学习编译运行相关的知识,首先我们学习下编译运行前的配置。
看到上面性能的对比,相信此刻的你迫不及待想要知道序列 (Sequences) 内部性能优化的原理吧,那么我们一起来看下序列内部的原理。来个例子fun main(args: Array<String>){ (0..10) .asSequence() .map { it + 1 } .filter { it % 2 == 0 } .count { it < 6 } .run { println("by using sequence result is $this") }}1、基本原理描述序列操作 :基本原理是惰性求值,也就是说在进行中间操作的时候,是不会产生中间数据结果的,只有等到进行末端操作的时候才会进行求值。也就是上述例子中 0~10 中的每个数据元素都是先执行 map 操作,接着马上执行 filter 操作。然后下一个元素也是先执行 map 操作,接着马上执行 filter 操作。然而普通集合是所有元素都完执行 map 后的数据存起来,然后从存储数据集中又所有的元素执行 filter 操作存起来的原理。集合普通操作 :针对每一次操作都会产生新的中间结果,也就是上述例子中的 map 操作完后会把原始数据集循环遍历一次得到最新的数据集存放在新的集合中,然后进行 filter 操作,遍历上一次 map 新集合中数据元素,最后得到最新的数据集又存在一个新的集合中。2、原理图解//使用序列fun main(args: Array<String>){ (0..100) .asSequence() .map { it + 1 } .filter { it % 2 == 0 } .find { it > 3 }}//使用普通集合fun main(args: Array<String>){ (0..100) .map { it + 1 } .filter { it % 2 == 0 } .find { it > 3 }}通过以上的原理转化图,会发现使用序列会逐个元素进行操作,在进行末端操作 find 获得结果之前提早去除一些不必要的操作,以及 find 找到一个符合条件元素后,后续众多元素操作都可以省去,从而达到优化的目的。而集合普通操作,无论是哪个元素都得默认经过所有的操作。其实有些操作在获得结果之前是没有必要执行的以及可以在获得结果之前,就能感知该操作是否符合条件,如果不符合条件提前摒弃,避免不必要操作带来性能的损失。
异常是程序运行过程中不可避免的问题。异常出现的原因很多,但不管怎样,都需要提前预知或者当异常发生后采取相应的处理措施。异常的处理原则是:能预知的尽可能在逻辑层面提前制止。如用户注册时,要求登录名是唯一的,可先检查数据库是否存在同名用户名后,再进行添加操作;以一种友好的方式告知使用者出错的原因;采用多层体系结构的项目中,建议异常由下逐层向上抛出,一直到达应用层面;使用日志记录功能把异常信息记录在日志文件中,便于开发者分析。如下面的控制器方法:@Controllerpublic class ExceptionAction {@RequestMapping("/exception01")public String exception01(@RequestParam("userName") String userName) { return "exception";}}在浏览器中输入:http://localhost:8888/sm-demo/exception01 ,页面中会出现错误提示。这个原因是 @RequestParam(“userName”) 注解在默认情况下,要求请求包中一定要有 userName 这个参数。显然,页面中显示出来的错误信息是不友好的。所谓的异常处理,并不能完全阻止异常的发生。而是把异常信息对外、对内做一个封装,换一个浅白的、直接的、非专业的方式告诉使用者。对于前面的异常解决方案,可以在 @RequestParam(value = “userName”,required = false) 中添加一个 required = false 的设置。这是一种最理想的异常解决方案。
使用浏览器运行原生 ES6 模块的源码在 ES6-wiki 的 mjs 文件中,浏览器是不能直接运行 ES6 模块化的,需要做一些准备工作。首先,在引入 js 文件时需要定义 script 的类型:type="module" 。另外,js 文件的后缀不能使用 .js 了,需要使用 .mjs 。这样还是不能在浏览器中运行,还需要最后一步。模块化会涉及到文件系统,而本地打开的 html 文件是没有服务的,所以我们要使用 node 服务的方式打开 html 文件,这里我们使用 node 的包 http-server 可以在相应的文件目录中启动 node 服务。安装如下:npm install --global http-server安装完启动服务的工具还是会有问题,依然打不开,这是需要在浏览器中打开一些配置:浏览器地址栏输入:chrome://flags/ 然后搜索 JavaScript 把 Experimental JavaScript 项选择 Enabled 启用状态。如下图。到这里我们就把前期的工作做完了,如何打开 html 文件呢?在控制台中进入对应的目录中执行:http-server 命令。本节的目录在 ES6-wiki/packages/module/mjs 下。在浏览器打开控制台返回的地址即可,本实例的地址是:http://127.0.0.1:8080/index.html
在了解上面这些基本术语后,我们介绍下当在浏览器中敲下 www.baidu.com 这个 URL,到百度返回给我们搜索首页,这个过程中究竟发生了哪些事情?解析输入 URL 中包含的信息,比如 HTTP 协议和域名 (baidu.com);客户端先检查本地是否有对应的 IP 地址,若找到则返回响应的 IP 地址。若没找到则请求在 ISP 的 DNS 服务器上。如果还没找到,则请求会被发向根域名服务器,直到找到对应的 IP 地址;浏览器在 DNS 服务器中找到对应域名的 IP 地址,然后结合 URL 中的端口(没有指明端口则使用默认端口,HTTP 协议的默认端口是 80,HTTPS 的默认端口是 443) 组成新的请求 URL,并与百度的 Web 服务建立 TCP 连接;浏览器根据用户操作向百度的 Web 服务器发送 HTTP 请求;Web 服务器接收到该请求后会根据请求的路径查找对应的 Web 资源并返回;最后客户端浏览器将这些返回的 Web 信息 (包括图片、HTML 静态页面,JS 等)组织成用户可以查看的网页形式,最后就得到了我们熟悉的那个 百度一下,你就知道 的搜索主页了。
从运行结果,我们可以看出,虽然我们使用了二值化进行了灰度处理,但是程序仍然不能 100% 的识别验证码,所以,后面我们删除了识别错误产生的小数点,才是最后的结果。
1. 路由设置: 定义视图函数相关的URL(网址) ,即规定访问什么网址对应什么内容。打开 urls.py ,按下面内容修改文件:2. Django 是自带后台管理的,如果需要,需要初始化数据库。在Tools -> Run manage.py task窗口执行下面的命令makemigrationsmigratecreatesuperuser相当于在 Terminal 窗口执行:python manage.py makemigrationspython manage.py migratepython manage.py createsuperuser然后点击运行按钮,可以访问home 页,也可以访问后台管理系统。也可运行命令 python manage.py runserver 8000
看到上面 IOS HelloWorld 项目运行起来,大家有没有思考一个问题,Kotlin 的代码的代码是怎么在 IOS 设备上跑起来呢?实际上,在这背后使用了一些脚本和工具在默默支撑着整个项目的运行,如前所述,Kotlin / Native 平台有自己的编译器,但每次想要构建项目时手动运行它明显不是高效的。 所以 Kotlin 团队了选择 Gradle。Kotlin / Native 使用 Gradle 构建工具在 Xcode 中自动完成 Kotlin / Native 的整个构建过程。在这里使用 Gradle 意味着开发人员可以利用其内部增量构建架构,只需构建和下载所需内容,从而节省开发人员的宝贵时间。如果,你还对上述有点疑问不妨一起来研究下 Kotlin/Native 项目中的构建参数脚本:打开构建脚本是需要在 Xcode 中打开的,具体可以参考如下图:通过以上项目可以分析到在 Xcode 中编译一个 Kotlin/Native 项目,实际上在执行一段 shell 脚本,并在 shell 脚本执行中 gradlew 命令来对 Kotlin/Native 编译,该脚本调用 gradlew 工具,该工具是 Gradle Build System 的一部分,并传递构建环境和调试选项。然后调用一个 konan gradle 插件实现项目编译并输出 xxx.kexe 文件,最后并把它复制到 iOS 项目构建目录 ("$TARGET_BUILD_DIR/$EXECUTABLE_PATH")。最后来看下 Supporting Files 中的 build.gradle 构建文件,里面就引入了 konan 插件 (Kotlin/Native 编译插件), 有空的话建议可以深入研究下 konan 插件,这里其实也是比较浅显分析了下整个编译过程,如果深入研究 konan 插件源码的话,更能透过现象看到 Kotlin/Native 本质,这点才是最重要的。buildscript { ext.kotlin_version = '1.2.0' repositories { mavenCentral() maven { url "https://dl.bintray.com/jetbrains/kotlin-native-dependencies" } } dependencies { classpath "org.jetbrains.kotlin:kotlin-native-gradle-plugin:0.7" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" }}apply plugin: 'kotlin'repositories { mavenCentral()}dependencies { compile "org.jetbrains.kotlin:kotlin-stdlib"}apply plugin: 'konan'konan.targets = [ 'ios_arm64', 'ios_x64'] konanArtifacts { program('KotlinNativeOC')}
由于 Zookeeper 是 C/S 架构,所以 Zookeeper ACL 的实现原理也分为两部分,Zookeeper 客户端和 Zookeeper 服务端。我们首先从 Zookeeper 客户端开始介绍。
要进行专业的原理解释需要很多的数学推算,这里以平常最常见的二维卷积对图片进行处理为例进行说明。说到卷积层,就不得不谈起卷积层的最重要的一个概念:卷积核。卷积核可以看作是一个算子,它会对图片数据的每个像素进行一次遍历,并且得到一个新的数据。比如说,我们目前有一个图片数据,它的具体的数据为如下所示,其中每个数据表示一个像素点的数据。1 2 1 2 00 2 3 4 54 2 0 1 31 1 2 3 45 0 0 1 2假如我们的卷积核为:1 0 10 1 01 0 1那么卷积核会从左上角开始扫描,按照从左到右,从上到下的顺序进行扫描。对于每一次扫描,得到的结果为:结果 = 求和(卷积核每个位置的系数 * 扫描区域之中对应位置的数据)比如对于第一次扫描,扫描区域为:1 2 1 0 2 3 4 2 0那么我们得到结果为1*1 + 2*0 + 1*1 +0*0 + 2*1 + 3*0 +4*1 + 2*0 + 0*1 = 8用动画形式展示来说就是 (图片来源于链接):
我们都知道内联函数的原理,编译器把实现内联函数的字节码动态插入到每次的调用点。那么实化的原理正是基于这个机制,每次调用带实化类型参数的函数时,编译器都知道此次调用中作为泛型类型实参的具体类型。所以编译器只要在每次调用时生成对应不同类型实参调用的字节码插入到调用点即可。总之一句话很简单,就是带实化参数的函数每次调用都生成不同类型实参的字节码,动态插入到调用点。由于生成的字节码的类型实参引用了具体的类型,而不是类型参数所以不会存在擦除问题。
运行这个 scrapy 爬虫的命令如下:(scrapy-test) [root@server scrapy-test]# scrapy crawl China-Pub-Crawler# 开始源源不断的爬取数据# ...下面来看我们的视频演示效果:79通过今天的一个简单小项目,大家对 Scrapy 框架是否有了初步的印象?后面我们会仔细剖析 Scrapy 框架的各个模块以及实现原理,让大家真正理解和掌握 Scrapy 框架。
运行脚本可以归纳为三种方式,注意一般在运行脚本的时候为脚本添加 x 可执行权限
动态代理与静态代理不同点在于,它可以动态生成任意个代理对象,无需要开发者手动编写代理类代码。动态代理机制在运行时动态生成代理类字节码 byte 数组,然后通过 jvm 内部将字节码 byte 数组反序列化对应代理的 Class 对象,然后再通过反射机制创建代理类的实例。
在 C 语言中,相同的数字可以用不同的数制来表示。也就是十进制的数字可以等价的表示为二进制或者十六进制。那么对于二进制来说,可以进行逐个数字之间,也就是每一个数字位的运算。这种运算也广泛的存在我们日程使用的数字电路中。其实计算机的运算原理最底层就是位运算,也就是 0 和 1 的运算。
池化层的原理相对而言比较简单,它和卷积层一样,都包括一个算子,只不过该算子在扫描的过程之中不会经过之前扫描的部分,也就是说每个数据只会被扫描一遍。用动画形式展示来说就是 (图片来源于链接):不同的池化方法对应着不同的池化层,我们最常使用的是最大池化与平均池化:最大池化:取扫描区域的最大值;平均池化:取扫描区域的平均值。
通过以上例子思考一下顶层函数在 JVM 中是怎么运行的?仅仅是在Kotlin中使用这些顶层函数,那么可以不用细究。但是 Java 和 Kotlin 混合开发模式,那么就有必要深入内部原理。都知道 Kotlin 和 Java 互操作性是很强的,所以就衍生出了一个问题:在 Kotlin 中定义的顶层函数,在Java可以调用吗?答案肯定是可以的。要想知道内部调用原理很简单,我们只需要把上面例子代码反编译成 Java 代码就一目了然了:package com.imooc.kotlin.top;import java.math.BigDecimal;import kotlin.Metadata;import kotlin.jvm.JvmName;import kotlin.jvm.internal.Intrinsics;import org.jetbrains.annotations.NotNull;@Metadata( mv = {1, 1, 9}, bv = {1, 0, 2}, k = 2, d1 = {"\u0000\u001c\n\u0000\n\u0002\u0010\u000e\n\u0000\n\u0002\u0010\u0006\n\u0000\n\u0002\u0010\u0002\n\u0000\n\u0002\u0010\u0011\n\u0002\b\u0002\u001a\u000e\u0010\u0000\u001a\u00020\u00012\u0006\u0010\u0002\u001a\u00020\u0003\u001a\u0019\u0010\u0004\u001a\u00020\u00052\f\u0010\u0006\u001a\b\u0012\u0004\u0012\u00020\u00010\u0007¢\u0006\u0002\u0010\b¨\u0006\t"}, d2 = {"formateFileSize", "", "size", "", "main", "", "args", "", "([Ljava/lang/String;)V", "production sources for module Function"})@JvmName(//注意这里多了注解 name = "FileFormatUtil")public final class FileFormatUtil {//这里生成的类名就是注解中自定义生成的类名了 @NotNull public static final String formateFileSize(double size) { if(size < (double)0) { return "0 KB"; } else { double kBSize = size / (double)1024; if(kBSize < (double)1) { return "" + size + " B"; } else { double mBSize = kBSize / (double)1024; if(mBSize < (double)1) { return "" + (new BigDecimal(String.valueOf(kBSize))).setScale(1, 4).toPlainString() + " KB"; } else { double mGSize = mBSize / (double)1024; if(mGSize < (double)1) { return "" + (new BigDecimal(String.valueOf(mBSize))).setScale(1, 4).toPlainString() + " MB"; } else { double mTSize = mGSize / (double)1024; return mTSize < (double)1?"" + (new BigDecimal(String.valueOf(mGSize))).setScale(1, 4).toPlainString() + " GB":"" + (new BigDecimal(String.valueOf(mTSize))).setScale(1, 4).toPlainString() + " TB"; } } } } } public static final void main(@NotNull String[] args) { Intrinsics.checkParameterIsNotNull(args, "args"); String var1 = "文件大小: " + formateFileSize(15582.0D); System.out.println(var1); }}通过以上的代码可以总结出两点内容:1、顶层文件会反编译成一个容器类。(类名一般默认就是顶层文件名+"Kt"后缀,注意容器类名可以自定义)2、顶层函数会反编译成一个 static 静态函数,如代码中的 formateFileSize 和 main 函数。
VS Code 是微软推出的一款很强大的编辑器,它提供了丰富的插件系统,通过使用这些插件,我们就可以很轻松地运行我们的 ES6 代码。要在 VS Code 中运行 ES6 代码,需要添加以下几内容:安装 NodeJS;安装 VS Code 编辑器;安装 Code Runner 插件。首先我们可以在 官网 下载最新的 Node 安装包,安装 NodeJS 会附带 npm 包管理器。Mac 用户也可以使用 Homebrew 这个工具进行安装,执行命令:brew install node安装完 NodeJS 后需要下载 VS Code 编辑器,可以在 VS Code 官网 下载,下载完直接安装。安装完成后我们需要在 VS Code 的插件市场中搜索 Code Runner,这是一个可以运行选中代码的 VS Code 插件神器,更加方便地查看 ES6 代码运行出来的结果,这个插件可以让我们更加细致地关注代码片段的运行情况。具体操作步骤我们可以看如下视频的演示: 70
在对 RabbitMQ 的整体架构有一个宏观了解之后,我们还需要对 RabbitMQ 的消息发送原理也有所了解,知道消息在 RabbitMQ Server 是怎样流转的。同样地,RabbitMQ 消息的发送原理也是基于 AMQP 协议中消息的发送原理,结合 AMQP 消息的发送原理(同学们不需要知道),我们可以得出 RabbitMQ 消息的发送原理。我们先来看一下,结合 RabbitMQ 整体架构而得出的 RabbitMQ 消息发送原理是怎样的,如下图所示:由此图,我们可以得出 RabbitMQ 消息发送的步骤:第一步,生产者将消息生产出来,并将消息发送到 RabbitMQ Server 上,即我们发到 RabbitMQ 中的消息,会首先置于 RabbitMQ Server 中;第二步,RabbitMQ Server 根据客户端所发来的连接请求,判断将消息传递到哪个 Virtual Host 中,如果我们在连接 RabbitMQ Server 时,没有设置要连接的 Virtual Host 地址,则 RabbitMQ Server 会将我们的消息传递到地址为 “/” 的 Virtual Host 中去;第三步,在将消息传递到对应的 Virtual Host 中后,Virtual Host 会继续解析我们的连接请求,并在这一步解析出我们需要的 Exchange 的类型,以及 Channel 的名称,Queue 的名称,以及消息和 Exchange 之间是否有 routing_key ,Channel 和 Queue 之间是否有 bidding_key 这些信息;第四步,Virtual Host 会根据解析出来的这些信息,将消息和 Exchange 进行匹配,相应的,Exchange 也会和对应的 Channel 进行匹配,并最终将 Queue 和 Channel 进行绑定,使消息进入到对应的消息队列中去;第五步,待消息进入到对应的消息队列中之后,RabbitMQ Server 会返回给我们一个确认应答(确认应答后续会进行介绍),来通知我们,消息已经成功被 RabbitMQ Server 所发送,于是,消费者变回根据一定的策略来从消息队列中获取消费,并最终将该消息消费掉,消息消费之后,也会给我们返回一个确认应答(确认应答后续会进行介绍),告诉我们消息已经成功消费掉了。以上就是 RabbitMQ 进行消息发送的先后步骤,为了更直观地为各位同学呈现 RabbitMQ 的消息发送原理,我做了一个流程图给大家,如下图所示:同学们可以根据上述步骤,结合流程图进行学习和验证。
如果有些同学并没有能够理解的话,我们可以给背景加上一个过渡动画,方便大家理解究竟是如何切换图标的:891运行结果: 可以看到其实就是这么个原理,两张形状一样但颜色不一样的图形放在一张雪碧图中,然后再控制位置即可。
点击 Postman 左上角的 “Runner”,也可以打开 Collections 然后在里面点击运行;打开 Collections 之后,会看到里面有一些最近运行过的集合;一般来说,请求是按照在 Collections 里的顺序来运行的,当然你可以在右侧窗口中调整请求的顺序或者反勾选请求。可以看到在左侧,还可以为集合运行器的一些配置:运行环境 – Envrionment;集合运行的迭代次数,可以针对不同的数据集多次运行集合 – Iterations;请求之间的延迟时间(毫秒计算)-- Delay;集合运行的数据文件 – Data;将响应保存到日志 – Save response;启用此选项将在运行结束时将变量的值写入会话中的当前值 --Keep variable values;如果请求使用了 cookies, 可以选择禁用来运行集合。-- Run collection without using stored cookies;更新此会话中存储的 cookies 并将其保存到 cookies 管理器中。 – Save cookies after collection run。
点击工具栏上的箭头按键,编译并运行 Hello World 应用;应用在模拟器上的运行效果。
密封类原理其实挺简单,实际上就是在一个私有的抽象类内部再声明多个 Java 中嵌套类也就是 static class . 可以把上述代码反编译成 Java 代码验证下:public abstract class Expression { private Expression() {//密封类构造器私有化,防止密封类被外部实例化 } // $FF: synthetic method public Expression(DefaultConstructorMarker $constructor_marker) { this(); } public static final class Num extends Expression {//声明成static class静态类,也就是Expression的嵌套类 private final int value; public final int getValue() { return this.value; } public Num(int value) { super((DefaultConstructorMarker)null); this.value = value; } } public static final class Sum extends Expression {//声明成static class静态类,也就是Expression的嵌套类 @NotNull private final Expression left; @NotNull private final Expression right; @NotNull public final Expression getLeft() { return this.left; } @NotNull public final Expression getRight() { return this.right; } public Sum(@NotNull Expression left, @NotNull Expression right) { Intrinsics.checkParameterIsNotNull(left, "left"); Intrinsics.checkParameterIsNotNull(right, "right"); super((DefaultConstructorMarker)null); this.left = left; this.right = right; } }}
下面,我们将通过源码的方式介绍 EventLoop 在 Netty 当中是如何运行的。首先,我们需要了解 EventLoop 三个核心步骤,如下图所示:
Prometheus 专注于现在正在发生的事情,而不是追踪数周或数月前的数据。Prometheus 通常不用于长期数据保留,默认保存 15 天的时间序列数据。它有这样一个前提,即大多数监控查询和警报都是从最近的数据中生成的。Prometheus的基本原理是通过HTTP协议周期性抓取被监控组件的状态,任意组件只要提供对应的HTTP接口就可以接入监控。不需要任何SDK或者其他的集成过程。输出被监控组件信息的HTTP接口被叫做exporter 。常用的组件大部分都有exporter可以直接使用,比如Nginx、MySQL、Linux系统信息等等。大致工作流程如下:Prometheus定时去目标上抓取指标监控数据,抓取目标需要暴露一个http服务的接口给它定时抓取。对于不能直接抓取的目标,Prometheus支持这些应用服务主动推送监控指标到PushGateway,而后Prometheus定时去这些网关上抓取数据。Prometheus在本地存储保存抓取的数据,并按规则进行过滤和整理数据。Prometheus支持很多方式的图表可视化,例如Grafana、自带的Promdash以及自身提供的模版引擎等等。Prometheus还提供HTTP API的查询方式,自定义所需要的输出。
我们来将上面添加的 HelloWorld 运行起来吧。1. 运行到浏览器点击工具栏中的运行->运行到浏览器->选择相应的浏览器运行:选择浏览器之后,HBuilderX 开发者工具会出现正在编译的提示,第一次运行会慢一点,编译成功后,自动打开浏览器并显示项目的页面。可以看到 HelloWorld 打印出来了。2. 运行到内置浏览器点击工具栏中的运行->运行到内置浏览器,会出现一个 Web 浏览器的弹出框,第一次打开会比较慢,需要耐心等一会才会出现项目的页面。3. 运行到手机或模拟器使用这个功能需要先用数据线连接手机,否则会提示“未检测到手机或模拟器,请稍后再试”。数据线连接手机后,我们再点击工具栏中的运行->运行到手机或模拟器。系统会自动在我们手机上面安装 HBuilderX 手机版。在手机上面打开 HBuilderX 手机版,就可以看到 HelloWorld 页面。Tips:如果打开手机版 HBuilderX 没有看到正确的页面,可以关掉手机应用进程,重新打开看一下。如果编译出错,点击查看工具栏中的运行->运行到手机或模拟器->真机运行常见故障排除指南,排除错误。4. 运行到小程序模拟器第一次使用小程序模拟器,需要先安装小程序开发者工具,并在工具栏->运行->运行到小程序模拟器->运行设置中设置小程序开发者工具安装的路径。在微信开发者工具里运行点击工具栏中的运行->运行到小程序模拟器->微信开发者工具,即可在微信开发者工具里面体验 uni-app。在 HBuilderX 里面开发,微信开发者工具里面就可以看到实时效果。uni-app 会将项目编译到根目录的 unpackage/dist 目录下面。Tips:如果没有成功运行可以做下面的操作。如果微信开发者工具已经打开,关闭微信开发者工具,重试一下;如果还是不行的话,建议将微信开发者工具升级到最新版本;最后如果自动启动微信开发工具失败,可以手动在开发者工具中打开HBuilderX控制台中提示的项目路径。在百度开发者工具里运行点击工具栏的运行->运行到小程序模拟器->百度开发者工具,在百度开发者工具中打开 HBuilderX 控制台中提示的项目路径,就可以在百度开发者工具中体验 uni-app。在支付宝小程序开发者工具里运行点击工具栏的运行->运行到小程序模拟器->支付宝小程序开发者工具,在支付宝小程序开发者工具中打开 HBuilderX 控制台中提示的项目路径,就可以在支付宝小程序开发者工具中体验 uni-app。在字节跳动开发者工具里运行点击工具栏的运行->运行到小程序模拟器->字节跳动开发者工具,在字节跳动开发者工具中打开 HBuilderX 控制台中提示的项目路径,就可以在字节跳动开发者工具中体验 uni-app。
CAS 主要包含三个操作数,内存位置 V,进行比较的原值 A,和新值 B。当位置 V 的值与 A 相等时,CAS 才会通过原子方式用新值 B 来更新 V,否则不会进行任何操作。无论位置 V 的值是否等于 A,都将返回 V 原有的值。上面说到了同步锁是一种悲观策略,CAS 是一种乐观策略,每次都开放自己,不用担心其他线程会修改变量等数据,如果其他线程修改了数据,那么 CAS 会检测到并利用算法重新计算。CAS 也是同时允许一个线程修改变量,其他的线程试图修改都将失败,但是相比于同步锁,CAS 对于失败的线程不会将他们挂起,他们下次仍可以参加竞争,这也就是非阻塞机制的特点。