整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

线性调频扩频技术(CSS)赋能非蜂窝广域网络超连接

hirp,中文译名啁啾(读音:“周纠”),是一种编码脉冲技术。CSS是英文Chirp Spread Spectrum的缩写,中文意为啁啾扩频,又称线性调频扩频,是数字通信中的一种扩频技术。CSS技术能够提升无线通信的性能和距离,实现比FSK(Frequency Shift Keying,频移键控)等调制技术距离更远的无线通信,这非常有助于非蜂窝广域网络规模化的组网应用。本文就从CSS技术、市场、射频收发器等方面做简要阐述。

CSS扩频技术传输性能优异 实现更远距离的无线通信

CSS技术并非是一种新的技术。在自然界里,Chirp脉冲就为海豚和蝙蝠等生物所用。20世纪40年代Hüttmann教授发明了雷达应用专利,后由Sidney Darlington进一步将CSS技术引入雷达系统创造性地开发了脉冲压缩(Chirp)雷达。自1997年以来人们开始研究将CSS技术应用于商业的无线数据传输。后来,IEEE 802.15.4标准将CSS指定为了一种用于低速率无线个人局域网(LR-WPAN)的技术,实现了数据速率可扩展性、远距离、更低功耗和成本,其与差分相移键控调制(DPSK)等技术相结合,可以实现更好的通信性能。CSS技术使用了其全部分配带宽来广播信号,从而使其对信道噪声具有一定的鲁棒性。CSS技术在非常低的功率下也能够抵抗多径衰落,非常适用于要求低功耗和较低数据速率的应用场景。CSS技术的低成本、低功耗、远距离以及数据速率的可扩展性等特性为产品商业化应用提供了现实的可能。

时域线性扩频调频upchirp示例 来源:Wikipedia

从CSS技术应用情况来看,德国Nanotron公司使用CSS技术在2.4GHz频段上实现了570米的距离通信。美国Semtech公司的LoRa产品使用CSS技术在Sub-1GHz频段上实现了几公里,甚至几十公里的距离通信。

CSS技术通信距离远可以在一定范围内实现更大规模的无线连接,大大降低无线接入和组网的成本,组建经济高效的无线广域网络,有助于物联网络规模化部署应用。CSS技术的普及应用将为新兴的非蜂窝广域网络市场的发展注入了新的活力,将会有力地推动行业应用的发展。

非蜂窝广域网络方兴未艾 物联网发展步入规模化应用阶段

低功耗广域网络(Low-Power Wide Area Network, LPWAN)大致可以分为蜂窝和非蜂窝广域网络。蜂窝广域网络是指由运营商建设的基于蜂窝技术的网络,一般是指3GPP主导的物联网标准,代表技术有NB-IoT、LTE-M(eMTC)和EC-GSM-IoT等;非蜂窝广域网络主要是指由企业自主建设使用免许可频段组建的网络,代表技术有SIGFOX、LoRaWAN、ZETA等。也有的提出0G网络,是相对于1G/2G/3G/4G而言,在通信领域一般是指蜂窝移动电话之前的移动电话技术,如无线电话。在物联网领域,0G指的是一个低带宽的无线网络,没有SIM卡、没有流量、低成本接入、远距离通信、传输少量数据的网络,也就是非蜂窝广域网络。非蜂窝广域网络的发展是源于对数据大规模采集和大量设备管理等的需求,并借助互联网技术和平台提升了基于数据的智能化管理水平。物联网市场发展步入规模化应用阶段。目前,非蜂窝广域网络主要应用于市政、园区、水务、消防、物流、家居、电力、社区、工厂、农业、环境等领域。

不同网络技术示意图

实际上,非蜂窝广域网络和蜂窝广域网络相互之间是一种相互依存互为补充的关系。一般地,非蜂窝广域网络都是通过网关(或称为集中器,或称为基站)连接到互联网,而网关连接到互联网的方式一般是有线或蜂窝网络等公网,最终还是要走公网的管道,毕竟有线和蜂窝网络是广泛存在的基础性网络。另一方面,传感器或设备多是基于微控制器(MCU)的,受其资源限制,仅可运行轻量的简单通信协议或定制化通信协议,通过网关转换成互联网协议(IP),网关起到了非蜂窝广域网络和互联网连接器的作用。非蜂窝广域网络更是蜂窝网络的拓展延伸。非蜂窝广域网络不同的无线接入技术可以满足物联网实际部署中各种各样无线连接的应用需求,为传感器网络或设备联网提供了灵活的无线接入方式和便捷的网络部署。

非蜂窝和蜂窝技术也可以相互融合。最近有报道称,在手机上集成了无线通信技术,可以在没有蜂窝网络的情况下,实现两机或多机的无线远距离相互通信,并可以实现自组网、定位等功能,这也为非蜂窝广域网络的应用提供了新的应用场景。

同时,非蜂窝网络也在国家电网方面具有非常强劲的发展势头。据最近流传的国家电网《电力设备无线传感器网络节点组网协议》显示,针对电力设备无线传感器网络的组网和传感器接入应用,在物理层协议规范中有对CSS物理层进行了定义,”CSS物理层:工作在470-510MHz或者2400-2483.5MHz频段,采用线性调频扩频(CSS)调制。线性调频扩频(CSS)调制应符合LoRaWAN™ 1.1 Specification 和IEEE Std 802.15.4TM-2015物理层的规定”。随着泛在电力物联网的建设发展,非蜂窝广域网络在泛在电力物联网中将会有着更为广阔的应用场景。除电力市场之外,其他抄表类市场应用,如:水表、气表、燃气表等,也是非蜂窝广域网络重要的典型应用市场。

另外,在一些重要的应用领域里,考虑到数据和安全等方面的因素,需要非蜂窝网络技术将设备接入到专网上,以保障私域网络的数据隐私和安全性。非蜂窝无线技术以其独特的优势在物联网络应用中发挥着重要的作用。

非蜂窝广域网络可以组建无线传感网络,连接和管理一定范围内大量传感器或设备等,也可以成为一种网络基础设施,由专门公司来提供网络服务,或者说是一种物联网络运营服务。在国外物联网运营模式已开始发展,如Sigfox等。而国内情况还处于探索发展阶段,目前主要还是以提供解决方案为主。

低功耗广域网络市场发展前景看好 非蜂窝广域网络预期规模增速明显

根据IHS Markit预测,2017年全球LPWAN连接数量为8753.7万个,预计到2023年可达171698.4万个,2017-2023年复合增长率(CAGR)为64%。其中,除NB-IoT和LTE-M等蜂窝连接之外,非蜂窝广域网络连接数量2017年为8124.8万个,2023年预计可达84443.6万个,2017-2023年复合增长率(CAGR)为48%。到2023年非蜂窝广域网络连接规模占比约为50%,非蜂窝广域网络市场未来具有很大的发展潜力。

2017-2023年全球LPWAN连接数量 来源:IHS Markit 2019

射频收发器受市场关注 Sub-1GHz频段更受青睐

一个完整的应用非蜂窝技术的应用图包括感知层、网络层和应用层。其中感知层中的射频收发器主要用于传感器和网关之间的信息交互。

非蜂窝技术系统应用框图

射频收发器是非蜂窝技术组网应用的关键器件,随着非蜂窝广域网络的发展,射频收发器产品越来越受到市场关注。从业界目前非蜂窝广域网络技术应用情况来看, 采用的都是国外半导体公司的射频收发器产品,这些厂商有Semtech、ST、Silicon Labs、TI、NXP、ON等,鲜有国内半导体公司的产品。Semtech公司的LoRa产品在中国市场上得到了很多公司的支持,国内少数公司通过IP授权的方式获得了LoRa IP,提供本地化产品,这些厂商有翱捷(ASR)、国民技术、华普等公司。随着射频收发器市场需求的发展,国内的一些芯片设计公司也开始研究和开发射频收发器产品。最近有报道称,国内上海磐启微电子有限公司推出了基于CSS技术的Chirp-IOT芯片PAN3028,融合了多维信号调制技术解决了频率不连续对射频的影响,提高了接收灵敏度,在射频收发器领域实现了新的技术突破。Chirp-IOT产品的国产化也填补了中国非蜂窝广域网络市场的空白。

由于射频收发器在Sub-1GHz频段上具有良好的无线传输特性,传输距离远、障碍物穿透能力强等,非蜂窝广域网络基本都是采用Sub-1GHz射频收发器组建网络。下面是关于Sub-1GHz射频收发器主要的厂商:

Sub-1GHz射频收发器厂商

万物智联市场快速发展需求大 集成电路设计国产化迎新机遇

中国市场规模大,对集成电路的需求也大,而目前还较多地依赖于集成电路的进口。根据海关统计,2018年中国进口集成电路有4170亿块,进口金额达3107亿美元。据国家统计局的统计显示,我国2018年集成电路产量1739.47亿个,国产集成电路产量不足进口量一半。近些年,国家不断加大对集成电路产业的政策扶持力度,出现了一大批新的集成电路设计公司,集成电路技术水平也在逐步提升。加之近两年中美贸易环境的变化,加速了集成电路国产化的速度。在涉及到国家核心重要应用领域,仍然是强调国产自主可控。这是中国集成电路设计公司一个重要的发展契机,也是非蜂窝广域网络行业一个发展机会。随着万物智联市场的快速发展,中国集成电路设计也将会迎来一波新的发展机遇。

根据半导体行业协会的统计,2018年中国集成电路设计产值为2519.3亿人民币,同比增长21.5%,2009到2018年中国集成电路设计产值年复合增长率(CAGR)为28.7%,集成电路设计产业保持了较高的发展速度。

2009-2018年中国集成电路设计产值 来源:半导体行业协会

结语

CSS技术在无线通信方面具有显著的优势,有助于非蜂窝广域网络实现大范围的组网应用。随着物联网市场无线连接需求的不断增长,射频收发器产品越来越受到芯片公司的关注。而国内射频收发器产品厂商少,行业发展还比较薄弱,需要更多的国内射频收发器厂商共同的参与,助力非蜂窝广域网络行业的发展,赋能非蜂窝广域网无线超连接,创新更多的物联网应用。

未来,随着集成电路技术的不断发展,或许会出现更多的新技术、新产品,这也将会大大丰富非蜂窝广域网络生态。“独木不成林”。需要各行各业共同的参与,建立共建共享共荣的良性发展生态。

作簿是 Excel 文档的基础,基于工作簿的操作主要有新建、打开、保存工作簿,以及工作簿的保护与撤销等。一个工作簿对象(Workbook)就是一个 Excel 文件,多个 Workbook 对象组成 Workbooks集合。工作簿对象包括工作表对象 Worksheet、单元格区域对象 Range、图表对象 Chart 等。

[批量新建工作簿]

按照A列内容创建Excel文件

Sub 批量新建工作簿() 
 Dim str1 As String, wbPath As String 
 Dim i As Integer, n As Integer 
 Dim ws1 As Worksheet, wb1 As Workbook 
 Application.DisplayAlerts = False '禁止警告信息
 Set ws1 = ActiveSheet '获取当前工作表的引用
 wbPath = ThisWorkbook.Path & "\例 1" '保存文件的目录
 n = ws1.Range("A1").End(xlDown).Row '总的数据行数
 For i = 2 To n '循环创建工作簿
 Set wb1 = Workbooks.Add '新建一个工作簿
 wb1.SaveAs wbPath & "\" & ws1.Cells(i, 1) & ".xls" '保存工作簿
 wb1.Close '关闭工作簿
 Next 
 Application.DisplayAlerts = True 
End Sub

打开工作簿

保存工作簿

更名保存工作簿

将工作簿保存为 Web 页

Sub 将工作簿保存为 Web 页() 
 ActiveWorkbook.SaveAs Filename:="MyWeb.htm", FileFormat:=xlHtml 
End Sub

打开文本文件

使用 Workbooks 集合对象的 OpenText 方法,可载入一个文本文件,并将其作为包含单个工作表的新工作簿进行分列处理,然后在此工作表中放入经过分列处理的文本文件数据。该方法的语法格式如下:

表达式 .

OpenText(Filename, Origin, StartRow, DataType, TextQualifier, ConsecutiveDelimiter, Tab, Semicolon, Comma, Space, Other, OtherChar, FieldInfo, TextVisualLayout, DecimalSeparator, ThousandsSeparator, TrailingMinusNumbers, Local)

该方法的参数有很多,除了 Filename 为必需的参数之外,其他参数都可省略。各参数的含义如下。

● Filename:指定要打开和分列的文本文件的名称。

● Origin:指定文本文件来源。可为常量 xlMacintosh、xlWindows 或 xlMSDOS。此外,它还可以是一个整数,表示所需代码页的代码页编号。例如,“1256”指定源文本文件的编码是阿拉伯语。如果省略该参数,则此方法将使用“文本导入向导”中“文件原始格式”选项的当前设置。

● StartRow:文本分列处理的起始行号。默认值为 1。

● DataType:指定文件中数据的列格式。可为常量 xlDelimited 或 xlFixedWidth。如果未指定该参数,则 Excel 将尝试在打开文件时确定列格式。

● TextQualifier:指定文本识别符号。

● ConsecutiveDelimiter:如果为 True,则将连续分隔符视为一个分隔符。默认值为False。

● Tab:如果为 True,则将制表符用作分隔符(DataType 必须为 xlDelimited)。默认值为 False。

● Semicolon:如果为 True,则将分号用作分隔符(DataType 必须为 xlDelimited)。默认值为 False。

● Comma:如果为 True,则将逗号用作分隔符(DataType 必须为 xlDelimited)。默认值为 False。

● Space:如果为 True,则将空格用作分隔符(DataType 必须为 xlDelimited)。默认值为 False。

● Other:如果为 True,则将 OtherChar 参数指定的字符用作分隔符(DataType 必须为 xlDelimited)。默认值为 False。

● OtherChar:(如果 Other 为 True,则为必选项)。当 Other 为 True 时,指定分隔符。如果指定了多个字符,则仅使用字符串中的第一个字符而忽略剩余字符。

● FieldInfo:包含单列数据相关分列信息的数组。对该参数的解释取决于 DataType的值。如果此数据由分隔符分隔,则该参数为由两个元素数组组成的数组,其中每个两元素数组指定一个特定列的转换选项。第一个元素为列标(从 1 开始),第二个元素是 XlColumnDataType 的常量之一,用于指定分列方式。

● TextVisualLayout:文本的可视布局。

● DecimalSeparator:识别数字时,Excel 使用的小数分隔符。默认设置为系统设置。

● ThousandsSeparator:识别数字时,Excel 使用的千位分隔符。默认设置为系统设置。

● TrailingMinusNumbers:如果应将结尾为减号字符的数字视为负数处理,则指定为True。如果为 False 或省略该参数,则将结尾为减号字符的数字视为文本处理。

● Local:如果分隔符、数字和数据格式应使用计算机的区域设置,则指定为 True。

Sub 打开文本文件() 
 Workbooks.OpenText Filename:="员工花名册.txt", _ 
 DataType:=xlDelimited, Tab:=True 
End Sub

设置工作簿密码

Sub 设置工作簿密码() 
 Dim pw As String 
 pw = Application.InputBox(prompt:="请输入保护工作簿的密码:", _ 
 Title:="输入密码", Type:=2) 
 ActiveWorkbook.Password = pw 
 ActiveWorkbook.Save 
 ActiveWorkbook.Close 
End Sub

保护工作簿

Sub 保护工作簿() 
 Dim pw As String 
 pw = Application.InputBox(prompt:="请输入保护工作簿的密码:", _ 
 Title:="输入密码", Type:=2) 
 ActiveWorkbook.Protect Password:=pw, Structure:=True, Windows:=True 
End Sub

查看文档属性

判断工作簿是否存在

判断工作簿是否打开

Sub 判断工作簿是否打开() 
 Dim str1 As String 
 str1 = Application.InputBox(prompt:="请输入 Excel 工作簿文件名:", _ 
 Title:="文件名", Type:=2) 
 If str1 = "False" Then Exit Sub 
 If Not WorkbookIsOpen(str1) Then 
 MsgBox "工作簿“" & str1 & "”未打开!" 
 Else 
 MsgBox "工作簿“" & str1 & "”已打开!" 
 End If 
End Sub

以上程序调用了一个自定义函数 WorkbookIsOpen,该函数的 VBA 代码如下:

Private Function WorkbookIsOpen(WorkBookName As String) As Boolean 
 '如果该工作簿已打开,则返回真
 Dim wb As Workbook 
 On Error Resume Next 
 Set wb = Workbooks(WorkBookName) 
 If Err = 0 Then 
 WorkbookIsOpen = True 
 Else 
 WorkbookIsOpen = False 
 End If 
End Function


备份工作簿

以上程序首先获取对当前工作簿的引用,如果当前工作簿是新建工作簿,则弹出“另存为”对话框。接着获取工作簿的全名,使用 InStrRev 函数查找工作簿的全名是否有扩展名,若有扩展名,则截取文件名的前面部分(不含扩展),再将文件名后面加上“.bak”,形成备份文件的名称,最后保存当前工作簿,并使用备份文件名另存文件,得到备份文件。

限制工作簿使用时间

今日代码文件

https://wws.lanzous.com/iZe8rnu5w9e

、版本

  • elasticsearch 8.13

二、背景

最近接到一个需求,将 MySQL 中的数据迁移到 Elasticsearch 中,并且相关的业务接口全部切换为使用 Elasticsearch 实现。

其中有个统计的功能,先根据 group 进行分组,然后对每个组内对象的 type 值进行分组统计。

举个例子:对学生进行统计,相当于先按照班级分组,在统计每个班级里面男女生的人数。

我一想切换到 Elasticsearch 中,相当于嵌套子聚合,一个聚合查询就出来结果,半小时搞定这个接口。

三、问题来了

3.1、初始化数据

创建索引


PUT zuiyu_index
{
  "settings": {
    "number_of_replicas": 1,
    "number_of_shards": 1
  },
  "mappings": {
    "properties": {
      "group": {
        "type": "text",
        "fields": {
          "keyword": {
            "type": "keyword",
            "ignore_above": 256
          }
        }
      },
      "type": {
        "type": "long"
      }
    }
  }
}
插入测试数据
POST _bulk
{ "index" : { "_index" : "zuiyu_index", "_id" : "1" } }
{ "group" : "1","type":1 ,"sort":1}
{ "index" : { "_index" : "zuiyu_index", "_id" : "2" } }
{ "group" : "1","type":2 ,"sort":1}
{ "index" : { "_index" : "zuiyu_index", "_id" : "3" } }
{ "group" : "1","type":3 ,"sort":1}
{ "index" : { "_index" : "zuiyu_index", "_id" : "4" } }
{ "group" : "2","type":1 ,"sort":1}
{ "index" : { "_index" : "zuiyu_index", "_id" : "5" } }
{ "group" : "2","type":1 ,"sort":1}
{ "index" : { "_index" : "zuiyu_index", "_id" : "6" } }
{ "group" : "2","type":1 ,"sort":1}
{ "index" : { "_index" : "zuiyu_index", "_id" : "7" } }
{ "group" : "3","type":2 ,"sort":1}
{ "index" : { "_index" : "zuiyu_index", "_id" : "8" } }
{ "group" : "4","type":1 ,"sort":1}
{ "index" : { "_index" : "zuiyu_index", "_id" : "9" } }
{ "group" : "5","type":1 ,"sort":1}
{ "index" : { "_index" : "zuiyu_index", "_id" : "10" } }
{ "group" : "6","type":1 ,"sort":1}
{ "index" : { "_index" : "zuiyu_index", "_id" : "11" } }
{ "group" : "7","type":1 ,"sort":1}
{ "index" : { "_index" : "zuiyu_index", "_id" : "12" } }
{ "group" : "8","type":1 ,"sort":1}
{ "index" : { "_index" : "zuiyu_index", "_id" : "13" } }
{ "group" : "9","type":1 ,"sort":1}
{ "index" : { "_index" : "zuiyu_index", "_id" : "14" } }
{ "group" : "10","type":1 ,"sort":1}
{ "index" : { "_index" : "zuiyu_index", "_id" : "15" } }
{ "group" : "10","type":2 ,"sort":1}
{ "index" : { "_index" : "zuiyu_index", "_id" : "16" } }
{ "group" : "11","type":1 ,"sort":1}
{ "index" : { "_index" : "zuiyu_index", "_id" : "17" } }
{ "group" : "11","type":3 ,"sort":1}


像这种简单的一条 sql 就出来结果的业务,我一般都是先写个 sql 语句,然后根据 sql 再写代码。所以这里我就先写了个DSL语句。

3.2、聚合查询


GET zuiyu_index/_search
{
  "aggregations": {
    "agg_group": {
      "aggregations": {
        "agg_type": {
          "terms": {
            "field": "type"
          }
        }
      },
      "terms": {
        "field": "group.keyword"
      }
    }
  },
  "query": {
    "bool": {
      "must": [
        {
          "terms": {
            "group.keyword": ["1","2","3","4","5","6","7","8","9","10","11"]
          }
        }
      ]
    }
  },
  "size":0
}


同学们可以看一下上面的语句有问题吗,如果你能发现问题,那么这篇文章也希望你能看下去,也许会有意想不到的收获。

提示一下:就像标题所说,可以关心一下聚合桶的数量。

四、发现问题

上述 DSL 语句执行之后,大眼一看 ,结果 OK,是我想要的,那就按这个逻辑直接写 Java 代码。上述 DSL 语句中聚合操作对应的 Java 代码如下:


Aggregation aggType = Aggregation.of(agg -> agg.terms(t -> t.field("type")));
 Aggregation aggGroup = Aggregation.of(agg -> agg.terms(t -> t.field("group"))
                .aggregations("agg_type", aggType));


做接口数据层的迁移,最简单的就是修改完业务代码之后直接对比返回结果,保持返回结果的一致,这样的修改对于前端来说没有影响。

所以,修改完代码之后直接拿接口的返回值与修改之前的版本进行比对,验证业务逻辑是否一致。直接 F12 控制台,找到该接口的返回值,复制,粘贴到对比工具中,进行对比。

此处使用的对比工具是 Beyond Compare 。

通过对比返回结果发现,聚合桶的数量少了一个

上面的例子中,我们的预期结果是,最外层 group 的分组最少11个,排除 group 不存在的情况。这里 terms 中条件 group 在索引中都已存在。

然后我就赶紧再去执行了一遍上面 DSL 语句,一个一个的验证聚合桶,发现返回结果中竟然没有 group=9 的桶存在。

DSL 返回结果如下


{
  "took": 2,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": {
      "value": 17,
      "relation": "eq"
    },
    "max_score": null,
    "hits": []
  },
  "aggregations": {
    "agg_group": {
      "doc_count_error_upper_bound": 0,
      "sum_other_doc_count": 1,
      "buckets": [
        {
          "key": "1",
          "doc_count": 3,
          "agg_type": {
            "doc_count_error_upper_bound": 0,
            "sum_other_doc_count": 0,
            "buckets": [
              {
                "key": 1,
                "doc_count": 1
              },
              {
                "key": 2,
                "doc_count": 1
              },
              {
                "key": 3,
                "doc_count": 1
              }
            ]
          }
        },
        {
          "key": "2",
          "doc_count": 3,
          "agg_type": {
            "doc_count_error_upper_bound": 0,
            "sum_other_doc_count": 0,
            "buckets": [
              {
                "key": 1,
                "doc_count": 3
              }
            ]
          }
        },
        {
          "key": "10",
          "doc_count": 2,
          "agg_type": {
            "doc_count_error_upper_bound": 0,
            "sum_other_doc_count": 0,
            "buckets": [
              {
                "key": 1,
                "doc_count": 1
              },
              {
                "key": 2,
                "doc_count": 1
              }
            ]
          }
        },
        {
          "key": "11",
          "doc_count": 2,
          "agg_type": {
            "doc_count_error_upper_bound": 0,
            "sum_other_doc_count": 0,
            "buckets": [
              {
                "key": 1,
                "doc_count": 1
              },
              {
                "key": 3,
                "doc_count": 1
              }
            ]
          }
        },
        {
          "key": "3",
          "doc_count": 1,
          "agg_type": {
            "doc_count_error_upper_bound": 0,
            "sum_other_doc_count": 0,
            "buckets": [
              {
                "key": 2,
                "doc_count": 1
              }
            ]
          }
        },
        {
          "key": "4",
          "doc_count": 1,
          "agg_type": {
            "doc_count_error_upper_bound": 0,
            "sum_other_doc_count": 0,
            "buckets": [
              {
                "key": 1,
                "doc_count": 1
              }
            ]
          }
        },
        {
          "key": "5",
          "doc_count": 1,
          "agg_type": {
            "doc_count_error_upper_bound": 0,
            "sum_other_doc_count": 0,
            "buckets": [
              {
                "key": 1,
                "doc_count": 1
              }
            ]
          }
        },
        {
          "key": "6",
          "doc_count": 1,
          "agg_type": {
            "doc_count_error_upper_bound": 0,
            "sum_other_doc_count": 0,
            "buckets": [
              {
                "key": 1,
                "doc_count": 1
              }
            ]
          }
        },
        {
          "key": "7",
          "doc_count": 1,
          "agg_type": {
            "doc_count_error_upper_bound": 0,
            "sum_other_doc_count": 0,
            "buckets": [
              {
                "key": 1,
                "doc_count": 1
              }
            ]
          }
        },
        {
          "key": "8",
          "doc_count": 1,
          "agg_type": {
            "doc_count_error_upper_bound": 0,
            "sum_other_doc_count": 0,
            "buckets": [
              {
                "key": 1,
                "doc_count": 1
              }
            ]
          }
        }
      ]
    }
  }
}


到了这,其实我还没想到是什么原因造成的,然后看了好几遍的聚合语句,都没有发现问题。一度的自我怀疑,聚合不是这样用的吗,嵌套的聚合难道还有花样?

  • 查阅官方文档

官方文档肯定是最权威的,所以去官方文档看看吧,是不是可以给自己点灵感,找到解决方案。

首先去的是如下地址:

https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations.html#run-sub-aggs

官方给的代码示例好简单,确实没毛病,对我没啥启发,告辞转下个网页。

还是这个网页,回到页面顶端,有一个 Bucket 字样的地方,点进去。

https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket.html



在这个页面,发现可以通过 search.max_buckets 设置请求返回聚合桶的总数,然后我脑海中那丢失的记忆回来了。

想起了在 Elasticsearch 聚合时,聚合桶的返回数量是可以指定的,但是怎么指定,参数是什么,我又忘了。

但是大方向肯定是这个了,我就开始找相关的资料,翻阅官网关于聚合的文档,终于在官方文档的嵌套聚合中找到了相关的说明。

https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-composite-aggregation.html#_size

大体意思就是size参数可以控制返回聚合桶的数量,默认 10

但是这里也没有给出示例,我还是不会用啊。

毕竟咱也是有点 ES 基础的,内心其实已经有了想法,大概知道怎么用了,只是还得需要确认下。

最后想起来之前写过关于聚合的文章,抱着试试看的态度,在回顾一下吧。

聚合在Elasticsearch中的使用及示例验证

发文日期,2023年8月2日,真是老了,才半年多的时间都忘了,好了回到主题。

在这篇文档中,发现了 size 参数的使用,在这里终于确认,聚合桶数量需要指定,并且根据自己的查询条件进行设置,或直接设置一个最大值兼容自己所有的聚合请求。

所以修改之后的 DSL 语句如下:


GET zuiyu_index/_search
{
  "aggregations": {
    "agg_group": {
      "aggregations": {
        "agg_type": {
          "terms": {
            "field": "type"
          }
        }
      },
      "terms": {
        "field": "group.keyword",
        "size": 11
      }
    }
  },
  "query": {
    "bool": {
      "must": [
        {
          "terms": {
            "group.keyword": ["1","2","3","4","5","6","7","8","9","10","11"]
          }
        }
      ]
    }
  },
  "size":0
}


相对应的 Java 代码也修改:


Aggregation aggType = Aggregation.of(agg -> agg.terms(t -> t.field("type")));
 Aggregation aggGroup = Aggregation.of(agg -> agg.terms(t -> t.field("group").size(groupList.size())
                .aggregations("agg_type", aggType));


  • groupList.size() 为 terms 查询条件值的数量。


五、search.max_buckets

可以通过 _cluster 的 API 设置此参数。


PUT _cluster/settings
{
  "transient": {
    "search.max_buckets":100
  }
}


此处使用的是 transient ,还可以使用 persistent,他俩的区别就是transient 的配置会在集群重启之后失效,persistent会持久化保存。

其中这个参数在之前的索引分片分配策略一文中讲过了,还没看过的可以跳过去看一下,链接我放下面。

Elasticsearch Index Shard Allocation 索引分片分配策略

六、总结

本文通过 demo 示例,从发现聚合桶数量丢失,到排查产生丢失的问题,最后通过 size 参数解决聚合桶数量丢失问题的过程。

意外收获的是一次请求返回聚合桶数量的总数也是可以通过 search.max_buckets设置的。

日常的积累固然重要,熟悉官方文档中相关 API 的位置也是必不可少的。

工作中你遇到问题是如何排查的呢,会不会查阅官网文档呢?欢迎在评论区沟通交流,大家一起学习进步!

参考链接

https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations.html

https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket.html

https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-composite-aggregation.html

https://mp.weixin.q‍q.com/s/18bz77nwgC_tF8l_jYD3CA

https://mp.weixin.qq.com/s/Rlu-GDNwnAUR3tzJYk‍m5Aw