【Elasticsearch】学好Elasticsearch系列-聚合查询

news2024/11/26 14:50:57

本文已收录至Github,推荐阅读 👉 Java随想录

先看后赞,养成习惯。
点赞收藏,人生辉煌。

在这里插入图片描述

文章目录

    • 概念
    • doc values 和 fielddata
    • multi-fields(多字段)类型
    • 聚合分类
      • 分桶聚合
        • Histogram 聚合
      • 指标聚合
        • Percentiles 聚合
        • cardinality聚合
      • 管道聚合
      • 嵌套聚合
    • 基于查询结果和聚合 & 基于聚合结果的查询
    • 聚合排序
      • _count
      • _term

Elasticsearch中的聚合是一种以结构化的方式提取和展示数据的机制。可以把它视为SQL中的GROUP BY语句,但是它更加强大和灵活。

Elasticsearch支持很多类型的聚合,包括:

  1. Metrics Aggregations:这类聚合基于文档字段的数值进行计算并返回一个单一的数值结果。例如最大值(max)、最小值(min)、平均值(average)、总和(sum)、统计信息(stats,包含了上述几种操作),以及其他复杂的聚合如百分数(percentiles)、基数(cardinality)等。
  2. Bucket Aggregations:这类聚合会创建一组buckets,每个bucket对应一个特定的条件或范围,然后文档会根据这些条件或范围被分类到相应的bucket中。常见的包括区间(range)、日期区间(date range)、直方图(histogram)、日期直方图(date histogram)、地理哈希网格(geohash grid)等。
  3. Pipeline Aggregations:这类聚合可以基于其他聚合的结果进行二次计算。比如计算差异、比例、移动平均等。

Elasticsearch的聚合操作支持嵌套,即一个聚合内部可以包含别的子聚合,从而实现非常复杂的数据挖掘和统计需求。

概念

聚合(aggs)不同于普通查询,是目前学到的第二种大的查询分类,第一种即“query”,因此在代码中的第一层嵌

套由“query”变为了“aggs”。用于进行聚合的字段必须是exact value,分词字段不可进行聚合,对于text字段如

果需要使用聚合,需要开启fielddata,但是通常不建议,因为fielddata是将聚合使用的数据结构由磁盘

(doc_values)变为了堆内存(field_data),大数据的聚合操作很容易导致OOM。

doc values 和 fielddata

在 Elasticsearch 中,聚合操作主要依赖于 doc values 或 fielddata 来进行。

  1. Doc values:对于大多数字段类型,Elasticsearch 使用 doc values 进行排序和聚合。doc values 是一种在磁盘上的、列式存储的数据结构,适用于稀疏字段,也就是字段中有很多不同的值。它们默认开启,并且不能被禁用。
  2. Fielddata:对于TEXT字段,doc values 默认是关闭的,因为文本字段通常包含很多不同的值,使用 doc values 会消耗大量内存。这时候,如果需要对文本字段进行聚合或排序,Elasticsearch 使用 fielddata。fielddata 是一个将所有文档的字段值加载到内存的数据结构,使用它可以使得聚合、排序和脚本运行更快,但代价是消耗更多的内存。

当执行聚合操作时,Elasticsearch 需要访问所有匹配文档的字段值。对于非文本字段,默认情况下Elasticsearch 使用 doc values 来实现。对于文本字段,必须首先启用 fielddata。然而,由于 fielddata 占用大量内存,Elasticsearch 默认禁用了它。

对于文本字段,fielddata 默认是禁用的。如果你确实需要对一个文本字段启用 fielddata(虽然大多数场景下不推荐这么做,因为可能导致内存消耗过大),你可以通过更新映射(mapping)来实现。以下是如何在 my_field 字段上启用 fielddata 的示例:

PUT my-index/_mapping

{
  "properties": {
    "my_field": { 
      "type":     "text",
      "fielddata": true
    }
  }
}

注意,更改 fielddata 设置只会影响新的数据,已经索引的数据不会受到更改。如果你想让更改生效,需要重新索引(reindex)你的数据

另外,一般情况下,建议你使用 mapping 中的 keyword 类型来进行聚合、排序或脚本,而不是启用 text 类型的 fielddata。这是因为 keyword 类型字段默认开启了 doc values,比在 text 上启用 fielddata 更加高效且节省内存。

multi-fields(多字段)类型

在 Elasticsearch 中,一个字段有可能是 multi-fields(多字段)类型,这意味着同一份数据可以被索引为不同类型的字段。常见的情况就是,一个字段既被索引为 text 类型用于全文搜索,又被索引为 keyword 类型用于精确值搜索、排序和聚合。

当你在一个字段名后面加上 .keyword(例如 field.keyword),这说明你是在引用这个字段的 keyword 子字段。这个 keyword 子字段在索引时并不会被分词器拆分成单独的词条,而是作为一个完整的字符串被存储。这样,你就可以对这个字段进行精确值匹配、排序或者聚合操作。

举例来说,如果你有一个 message 字段并且想要对其进行聚合,你应该使用 message.keyword 而非 message。因为如果你直接对 message 进行聚合,Elasticsearch 就会尝试对每一个独立的词条进行聚合,而不是对整个字段值进行聚合。

如果你的字段没有 .keyword 子字段,那可能是在定义 mapping 时没有包含这一部分,或者这个字段的类型本身就是 keyword

聚合分类

  • 分桶聚合(Bucket agregations):类比SQL中的group by的作用,主要用于统计不同类型数据的数量。
  • 指标聚合(Metrics agregations):主要用于最大值、最小值、平均值、字段之和等指标的统计。
  • 管道聚合(Pipeline agregations):用于对聚合的结果进行二次聚合,如要统计绑定数量最多的标签bucket,就是要先按照标签进行分桶,再在分桶的结果上计算最大值。

分桶聚合

分桶(Bucket)聚合是一种特殊类型的聚合,它将输入文档集合中的文档分配到一个或多个桶中,每个桶都对应于一个键(key)。

下面是一些常用的分桶聚合类型:

  • terms:基于文档中某个字段的值,将文档分组到各个桶中。
  • date_histogram:基于日期字段,将文档按照指定的时间间隔分组到各个桶中。
  • histogram:基于数值字段,将文档按照指定的数值范围分组到各个桶中。
  • range:根据设置的范围,将数据分为不同的桶。

以下是一个使用 terms 分桶聚合的例子:

假设你有一个包含博客文章的 blog 索引,你想知道每个作者写了多少篇文章,可以使用以下查询:

GET /blog/_search
{
  "size": 0,
  "aggs": {
    "authors": {
      "terms": { "field": "author.keyword" }
    }
  }
}

在这个查询中:

  • size: 0 表示我们只对聚合结果感兴趣,不需要返回任何具体的搜索结果。
  • "aggs" (或者 "aggregations") 块定义了我们的聚合。
  • "authors" 是我们自己为这个聚合命名的标签,你可以用任何你喜欢的标签名。
  • "terms": { "field": "author.keyword" } 定义了我们要进行聚合的方式和字段。这里,我们告诉 Elasticsearch 使用 terms 聚合,并且使用 author.keyword 字段的值作为分桶的依据。

Elasticsearch 将返回一个包含每个作者以及他们所写的文章数量的列表。注意,由于 Elasticsearch 默认只返回前十个桶,如果你的数据中有更多的作者,可能需要设置 size 参数来获取更多的结果。

Histogram 聚合

histogram 是一个类型的桶聚合,它可以按照指定的间隔将数字字段的值划分为一系列桶。每个桶代表了这个区间内的所有文档。

以下是一个例子,我们根据价格字段创建一个间隔为 50 的直方图:

GET /products/_search
{
  "size": 0,
  "aggs" : {
    "prices" : {
      "histogram" : {
        "field" : "price",
        "interval" : 50
      }
    }
  }
}

在这个例子中,“prices” 是一个 histogram 聚合,它以 50 为间隔将产品的价格划分为一系列的桶。

指标聚合

在 Elasticsearch 中,指标聚合是对数据进行统计计算的一种方式,例如求和、平均值、最小值、最大值等。以下是一些常用的指标聚合类型:

  • avg:计算字段的平均值。
  • sum:计算字段的总和。
  • min:查找字段的最小值。
  • max:查找字段的最大值。
  • count:计算匹配文档的数量。
  • stats:提供了 count、sum、min、max 和 avg 的基本统计。

下面是一个示例,假设我们有一个包含售卖商品的 “sales” 索引,我们想要知道所有销售记录中的平均价格,可以使用 avg 聚合如下操作:

GET /sales/_search
{
  "size": 0,
  "aggs": {
    "average_price": {
      "avg": { "field": "price" }
    }
  }
}

在这个查询中:

  • "size": 0 表示我们只对聚合结果感兴趣,不需要返回任何具体的搜索结果。
  • "aggs" (或者 "aggregations") 块定义了我们的聚合。
  • "average_price" 是我们自己为这个聚合命名的标签,可以用任何你喜欢的标签名。
  • "avg": { "field": "price" } 定义了我们执行的聚合类型以及对哪个字段进行聚合。在这里,我们告诉 Elasticsearch 使用 avg 聚合,并且对 price 字段的值进行计算。Elasticsearch 将返回一个包含所有销售记录平均价格的结果。

Percentiles 聚合

percentiles 是指标聚合的一种,它用于计算数值字段的百分位数。给定一个列表百分比,Elasticsearch 可以计算每个百分比下的数值。

以下是一个例子,我们计算价格字段的 1st, 5th, 25th, 50th, 75th, 95th, and 99th 百分位数:

GET /products/_search
{
  "size": 0,
  "aggs" : {
    "price_percentiles" : {
      "percentiles" : {
        "field" : "price",
        "percents" : [1, 5, 25, 50, 75, 95, 99]
      }
    }
  }
}

在这个例子中,“price_percentiles” 是一个 percentiles 聚合,它计算了价格在各个百分位点的数值。

注意,对于大数据集,计算精确的百分位数可能需要消耗大量资源。因此,Elasticsearch 默认使用一个名为 TDigest 的算法来提供近似的计算结果,同时还能保持内存使用的可控性。

cardinality聚合

如果你想在 Elasticsearch 中进行去重操作,可以使用 terms 聚合加上 cardinality 聚合。这是一个示例,假设我们有一个包含user_id的 “users” 索引,并且我们想要知道有多少唯一的 user_id:

GET /users/_search
{
  "size": 0,
  "aggs": {
    "distinct_user_ids": {
      "cardinality": {
        "field": "user_id.keyword"
      }
    }
  }
}

在这个查询中:

  • "distinct_user_ids" 是我们自己为这个聚合命名的标签。
  • "cardinality": { "field": "user_id.keyword" } 使用了 cardinality 聚合,该聚合会返回指定字段(在这里是 user_id.keyword)的不同值的数量。

Elasticsearch 将返回一个结果,告诉我们有多少个不同的 user_id。请注意,cardinality 聚合可能并不总是完全精确,特别是对于大型数据集,因为它在内部使用了一种叫做 HyperLogLog 的算法来近似计算基数,这种算法会在保持内存消耗相对较小的情况下提供接近准确的结果。如果你需要完全精确的结果,可能需要考虑其他方法,例如使用脚本或者将数据导出到外部系统进行处理。

管道聚合

在 Elasticsearch 中,管道聚合(pipeline aggregations)是指这样一种聚合:它以其他聚合的结果作为输入,并进行进一步处理。常见的管道聚合包括:

  • avg_bucket
  • sum_bucket
  • min_bucket
  • max_bucket
  • stats_bucket
  • extended_stats_bucket
  • percentiles_bucket

这些都是 bucket 级别的管道聚合,它们会在一组数据桶上操作。

下面给出一个示例,假设我们有一个销售记录索引 “sales”,每个销售记录都有售价 “price” 和销售日期 “date” 字段。如果我们想要计算每月平均销售价格,并找出所有月份中平均价格最高的月份,可以使用 date_histogram 聚合加上 avg 以及 max_bucket 聚合来实现:

GET /sales/_search
{
  "size": 0,
  "aggs": {
    "sales_per_month": {
      "date_histogram": {
        "field": "date",
        "calendar_interval": "month"
      },
      "aggs": {
        "avg_price": {
          "avg": { "field": "price" }
        }
      }
    },
    "max_avg_price": {
      "max_bucket": {
        "buckets_path": "sales_per_month>avg_price"
      }
    }
  }
}

在这个查询中:

  • "sales_per_month" 是一个按月聚合销售记录的 date_histogram 聚合。
  • "avg_price" 是一个嵌套在 "sales_per_month" 下的 avg 聚合,用于计算每月的平均销售价格。
  • "max_avg_price" 是一个 max_bucket 聚合,它会找出 "sales_per_month" 中所有子桶的 "avg_price" 最大值。

注意到 "max_avg_price" 中的 "buckets_path": "sales_per_month>avg_price"buckets_path 参数指定了此管道聚合的输入来源,> 符号表示路径层次,即先取 "sales_per_month" 聚合的结果,再取其中的 "avg_price" 聚合的结果作为输入。

返回的结果中会包含每个月的平均销售价格,以及所有月份中平均销售价格的最大值。

嵌套聚合

嵌套聚合就是在聚合内使用聚合,在 Elasticsearch 中,嵌套聚合通常用于处理 nested 类型的字段。nested 类型允许你将一个文档中的一组对象作为独立的文档进行索引和查询,这对于拥有复杂数据结构(例如数组或列表中的对象)的场景非常有用。

假设我们有一个 users 索引,每个 user 文档都有一个 purchases 字段,该字段是一个列出用户所有购买记录的数组,每个购买记录包含 product_idprice。如果我们想要找出价格超过 100 的所有产品的 ID,可以使用 nested 聚合:

GET /users/_search
{
  "size": 0,
  "aggs": {
    "all_purchases": {
      "nested": {
        "path": "purchases"
      },
      "aggs": {
        "expensive_purchases": {
          "filter": { "range": { "purchases.price": { "gt": 100 } } },
          "aggs": {
            "product_ids": { "terms": { "field": "purchases.product_id" } }
          }
        }
      }
    }
  }
}

在这个查询中:

  • "all_purchases" 是一个 nested 聚合,指定了 nested 对象的路径 purchases
  • "expensive_purchases" 是一个嵌套在 "all_purchases" 下的 filter 聚合,它会过滤出 price 大于 100 的购买记录。
  • "product_ids" 是一个嵌套在 "expensive_purchases" 下的 terms 聚合,它会提取出所有满足条件的 product_id

返回的结果将包含所有 price 大于 100 的产品的 ID 列表。

请注意,在处理 nested 数据时,你需要确保 mapping 中相应的字段已经被设置为 nested 类型,否则该查询可能无法按预期工作。

基于查询结果和聚合 & 基于聚合结果的查询

基于查询结果的聚合:在这种情况下,我们首先执行一个查询,然后对查询结果进行聚合。例如,如果我们要查询所有包含某关键字的文档,并计算它们的平均价格,可以这样做:

GET /products/_search
{
  "query": {
    "match": {
      "description": "laptop"
    }
  },
  "aggs": {
    "average_price": {
      "avg": {
        "field": "price"
      }
    }
  }
}

在上述例子中,我们首先通过 match 查询找到描述中包含 “laptop” 的所有产品,然后对这些产品的价格进行平均值聚合。

基于聚合结果的查询(Post-Filter):这种情况下,我们先执行聚合,然后基于聚合的结果执行过滤操作。这通常用于在聚合结果中应用一些额外的过滤条件。例如,如果我们想对所有产品进行销售数量聚合,然后从结果中过滤出销售数量大于10的产品,可以这样做:

GET /sales/_search
{
  "size": 0,
  "aggs": {
    "sales_per_product": {
      "terms": {
        "field": "product_id"
      }
    }
  },
  "post_filter": {
    "bucket_selector": {
      "buckets_path": {
        "salesCount": "sales_per_product._count"
      },
      "script": {
        "source": "params.salesCount > 10"
      }
    }
  }
}

在上述例子中,我们首先执行了一个 terms 聚合,按产品ID汇总销售记录。然后我们使用 bucket_selector post-filter 进一步筛选出销售数量大于10的桶(每个桶对应一个产品)。

聚合排序

_count

在 Elasticsearch 中,聚合排序允许你基于某一聚合的结果来对桶进行排序。例如,你可能希望查看销售量最高的10个产品,可以使用 terms 聚合以及其 sizeorder 参数来实现:

GET /sales/_search

{
  "size": 0,
  "aggs": {
    "top_products": {
      "terms": {
        "field": "product_id",
        "size": 10,
        "order": { "_count": "desc" }
      }
    }
  }
}

在这个例子中,top_products 是一个 terms 聚合,用于按 product_id 对销售记录进行分组。

  • "size": 10 的意思是只返回销售量最高的前10个产品(即只返回前10个桶)。
  • "order": { "_count": "desc" } 表示按桶中文档的数量(也就是销售量)降序排序。_count 是一个内置的排序键,代表桶中文档的数量。

返回的结果将包含销售量最高的前10个产品的 ID 列表。

需要注意的是,由于 Elasticsearch 默认会对桶进行优化,所以在使用 size 参数时可能无法得到完全准确的结果。如果需要更精确的结果,可以在请求中设置 "size": 0 ,然后使用 composite 聚合来分页获取所有结果。

_term

_term 在 Elasticsearch 的聚合排序中用来指定按照词条(即桶的键)来排序。

GET /sales/_search

{
  "size": 0,
  "aggs": {
    "products": {
      "terms": {
        "field": "product_id",
        "order": { "_term": "asc" }
      }
    }
  }
}

在这个例子中,通过 "order": { "_term": "asc" } 指定了按照 product_id 的值升序排序这些桶。

返回的结果将包含按照 product_id 升序排列的产品 ID 列表,每个产品 ID 对应一个桶,并且每个桶内包含对应产品的销售记录。

需要注意的是,在新版本的 Elasticsearch 中(7.0 以后),_term 已经被 key 替代用于排序。

GET /sales/_search

{
  "size": 0,
  "aggs" : {
    "products" : {
      "terms" : {
        "field" : "product_id",
        "order" : { "_key" : "asc" }
      }
    }
  }
}

本篇文章就到这里,感谢阅读,如果本篇博客有任何错误和建议,欢迎给我留言指正。

有收获?希望老铁来个三连,给更多的同学看到这篇文章,顺便激励下我,嘻嘻。

老铁们,关注我的微信公众号「Java随想录」,专注分享技术,文章持续更新,可以关注公众号第一时间阅读。
一个人走的很快,一群人走的更远。关注我,我们一起学习成长!

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

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

相关文章

用chatGPT从左右眼图片生成点云数据

左右眼图片 需求 需要将左右眼图像利用视差生成三维点云数据 先问问chatGPT相关知识 进一步问有没有现成的软件 chatGPT提到了OpenCV,我们让chatGPT用OpenCV写一个程序来做这个事情 当然,代码里面会有一些错误,chatGPT写的代码并不会做模…

笔记本WIFI连接无网络【实测有效,不用重启电脑】

笔记本Wifi连接无网络实测有效解决方案 问题描述: 笔记本买来一段时间后,WIFI网络连接开机一段时间还正常连接,但是过一段时间显示网络连接不上,重启电脑太麻烦,选择编写重启网络脚本解决。三步解决问题。 解决方案&a…

蓝牙技术在工业物联网 (IIoT)中的应用_串口透传蓝牙模块

物联网 (IoT) 正在通过托管和可扩展的数字解决方案帮助全球各行各业提高效率。 更具体地说,工业物联网 (IIoT) 侧重于连接石油和天然气、水电以及制造业等关键行业的机器和设备。 在工厂中,连接传感器在机器上的应用被用来收集有价值的数据,用…

标题:使用 Python 的 wxPython 模块生成 PPTX 文档

导语:本文介绍了如何使用 wxPython 模块和 python-pptx 模块来编写一个程序,用于生成包含首页、内容页和感谢页的 PPTX 文档。 介绍 PPTX 文档是一种常用的演示文稿格式,用于展示和分享信息。在本文中,我们将使用 Python 的 wxP…

前后端分离式项目架构流程复盘之宿舍管理系统

文章目录 🐒个人主页🏅JavaEE系列专栏📖前言:【🎇前端】先创建Vue-cli项目(版本2.6.10,仅包含babel),请选择此项目并创建 【整理简化项目模板】【🎀创建路由】…

kubesphere 部署 ingress 并使用 80 端口

文章目录 创建集群网关创建应用路由访问域名使用 80 端口 创建集群网关 官方文档:集群网关 点击左上角的平台管理并选择集群管理 点击导航面板中集群设置下的网关设置,选择集群网关选项卡,并点击启用网关 选择 NodePort 模式,配…

如何在风控引擎中快速增加策略

风控策略是由规则和模型组成的、用以实现风险控制目标的集合。模型与规则相辅相成,相互补充,在不同的业务阶段彼此的侧重点不同。新业务上线期,数据量不足,策略以规则为主;当业务运行一段时候后,数据有了一…

【逗老师的PMP学习笔记】7、项目的成本管理

目录 一、规划成本管理二、估算成本1、【关键工具】数据分析-考虑质量成本 三、制定预算1、【关键输出】成本基准2、【关键输出】资金需求 四、控制成本1、【定义】成本类型2、【关键工具】挣值分析 一、规划成本管理 成本管理计划是项目管理计划的组成部分,描述将如…

springboot+jpa+mysql电子数码商城含后台管理源码

#开发技术 #前端 bootstarp框架 html页面 #后端技术 SpringBoot SpringMvc jpa #开发工具 eclipse或者idea jdk1.8 mysql5点几版本 maven环境 maven3.5 前端地址::http://localhost:8080/mall 后台地址&#x…

知识图谱推荐系统研究综述

基于协同过滤的推荐是当前应用最为广泛的推荐方法,但也存在着新用户或新项目的冷启动以及数据稀疏等问题。针对上述两种方法出现的问题,研究者进一步提出了混合推荐系统。混合推荐系统结合上述两种方法的优点,可以有效缓解其中的不足,增加推荐的准确性。但是,混合推荐系统…

原型模式(C++)

定义 使用原型实例指定创建对象的种类,然后通过拷贝这些原型来创建新的对象。 应用场景 在软件系统中,经常面临着“某些结构复杂的对象”的创建工作;由于需求的变化,这些对象经常面临着剧烈的变化,但是它们却拥有比较稳定一致的…

并发编程2:如何进行对象共享?

目录 1、对象的可见性:Volatile 变量 2、发布和逸出 3、线程封闭:ThreadLocal 4、对象的不变性 5、安全发布 5.1 - 安全发布常用的模式 5.2 - 可变对象 5.3 - 安全地共享对象 同步在用于实现原子性或者确定 “临界区(Critical Section)” 的同时…

大公司搞网络部署,关键在哪步?

下午好,我是老杨。 很久没有聊网络部署,今天就说这事儿。 要进行网络部署,涉及的技术点/知识有哪些? 最基本的,你需要掌握vlan划分、路由选择、出口nat处理;你可能还需要懂得链路聚合、mstp、vrrp、ospf…

恒运资本:沪指震荡微涨,医药、酿酒板块反弹,传媒板块活跃

8日早盘,沪指早盘弱势震动下探,临近午盘翻红;深成指、创业板指均止跌回升;两市半日成交超5000亿元,北向资金净卖出超40亿元。 截至午间收盘,沪指微涨0.01%报3269.29点,深成指跌0.06%&#xff0c…

案例分享:NetApp SSD 硬盘重启后全部故障

近日连续处理了几个NetApp FAS存储系统SSD磁盘重启后,全部故障的案例。这里是case的总结和分享,以后有遇到的可以参考处理。 案例1:客户一套FAS8020,带一个DS2246盘柜,内置24个800G X447A的磁盘,机房掉电后…

2023网络安全常用工具汇总(附学习资料+工具安装包)

几十年来,攻击方、白帽和安全从业者的工具不断演进,成为网络安全长河中最具技术特色的灯塔,并在一定程度上左右着网络安全产业发展和演进的方向,成为不可或缺的关键要素之一。 话不多说,网络安全10款常用工具如下 1、…

SQL力扣练习(十)

目录 1.体育馆的人流量(501) 示例 1 解法一(row_number()) 解法二(自定义变量) 解法三 2.好友申请(602) 示例 解法一(union all) 解法二 3.销售员&…

hive修改表或者删除表时卡死问题的解决(2023-08-08)

背景:前阶段在做hive表的改表名时,总是超时,表是内部表,数据量特别大,无论你是修改表名还是删除表都是卡死的状态,怎么破? 终于:尝试出来一个新的方法 将内部表转化成外部表&#…

面试常问:tcp的三次握手和四次挥手你了解吗?

三次握手和四次挥手是各个公司常见的考点,一个简单的问题,却能看出面试者对网络协议的掌握程度,对问题分析与解决能力,以及数据流管理理解和异常情况应对能力。所以回答好一个tcp的三次握手和四次挥手的问题对于我们的面试成功与否…

【雕爷学编程】Arduino动手做(193)---移远 BC20 NB+GNSS模块13

37款传感器与模块的提法,在网络上广泛流传,其实Arduino能够兼容的传感器模块肯定是不止37种的。鉴于本人手头积累了一些传感器和执行器模块,依照实践出真知(一定要动手做)的理念,以学习和交流为目的&#x…