当前位置: 首页 > news >正文

一种高效且节约内存的聚合数据结构的实现

一种高效且节约内存的聚合数据结构的实现

在特定的场景中,特殊定制数据结构能够得到更加好的性能且更节约内存。

聚合函数GroupArray的问题

GroupArray聚合函数是将分组内容组成一个个数组,例如下面的例子:

SELECT groupArray(concat('ABC-', toString(number))) from numbers(20) group by number % 5;
------------------------------------------------------
Output:
------------------------------------------------------
['ABC-0','ABC-5','ABC-10','ABC-15']
['ABC-1','ABC-6','ABC-11','ABC-16']
['ABC-2','ABC-7','ABC-12','ABC-17']
['ABC-3','ABC-8','ABC-13','ABC-18']
['ABC-4','ABC-9','ABC-14','ABC-19']

原始的实现中使用顺序表存放每个字符串,每个字符串由size和data两部分组成,如以下简化代码所示:


// 内存排布:
// [8 字节]size, [不定字节数]data...
// 例如字符串"ABC"的表示如下:
// 0x0000000000000003, 'A', 'B', 'C'
struct StringNode
{size_t size;// 实例尾部存放实际数据。
};
using AggregateData = PODArray<StringNode*>

在聚合过程中,不断创建StringNode实例并将其指针放入表示聚合数据的AggregateData实例中。这个实现方法会存在几个问题:

  1. 在聚合过程中,由AggregateData表示的聚合数据不断增长,会引发多次因PODArray的内存空间不够而引发的重新申请内存的动作,还会附加复制数据到新内存空间操作的消耗。
  2. PODArray的初始大小也很难确定:初始大小如果过大,浪费空间;初始大小如果过小,重新申请新的内存空间后,原先的初始的内存就浪费了,且新的内存空间可能又过大了。举个例子,如果AggregateData初始大小是32字节,实际聚合数据有48字节,需要重新分配内存空间,重新分配的内存是64字节,这样浪费的内存是:原来的32字节+新空间装完48字节的数据之后空余的16字节,总共浪费了48个字节。
  3. StringNode的size字段并不需要8个字节,因为我们知道在我们的实际使用场景中,字符串最大不会超过65535,用2个字节表示长度就够了,浪费了6个字节。

用链表替代顺序表的解决方案

用链表替代顺序表,似乎第一感觉是不可行的,因为两个根深蒂固(可能来自于教科书)的观点:

  1. 链表的内存分散,局部性不好;
  2. 链表每新增一个节点都需要分配内存,内存分配动作过多影响效率;
  3. 链表访问较慢,需要通过next指针找下一个。

在使用Arena的内存分配器和当前GroupArray的聚合数据结构的前提条件,以上两个观点都不是绝对正确的。

先说第一个问题,在一般内存分配器中,可能每次分配的内存都是不连续的,这是链表的内存分散的原因。但是Arena内存分配器在不切换内存块的前提下分配的内存是连续的。每个聚合线程有独立的Arena内存分配器。因此这种情况下链表的内存是基本连续的,保证了相对的局部性。

第二个问题也是跟Arena内存分配器相关。Arena内存分配器是一次调用系统接口(jemalloc或者mmap或者其他)分配一大块内存,然后每次调用Arena内存分配器分配内存时只要返回当前指向内存块中空余内存的指针的值,然后更新这个指针的值使其指向剩下的空余内存的开头即可。这是一个非常轻量的函数调用而且极有可能被内联了。

第三个问题,聚合数据是StringNode的指针的数组,虽然PODArray是顺序表但是每次访问也是要通过指针才能访问到StringNode实例的,这样跟链表中通过next指针访问的代价是一样的。但我们此时能够直接访问真实数据了,不需要再次通过链表节点的指针去访问真实数据,这样指针取值操作的次数是不变的。

综上所述,链表替代顺序表在这个特定场景下是更好的方案。

节约6个字节

原先的size_t size需要8个字节,但实际两个字节就够了。虽然只是6个字节,但是在数据的行数很大的情况下总共节约下来的内存是很客观的。例如对于十亿条数据,就会节约8GiB左右(8 x 10亿)的内存。

实现方式如下:

struct StringNode
{UInt16 size;char data[6];
}// string_size是字符串的实际大小。sizeof(StringNode::data)等于6。
arena.alignedAlloc(std::max(sizeof(Node), sizeof(Node) - sizeof(StringNode::data) + string_size), alignof(Node));

通过把size改成UInt16并加入一个6个字节的data字段,省出6个字节出来。

根据需要定义count字段

教科书上的链表不需要count字段,因为可以通过遍历next指针获得。但是实际上我们可能需要快速获取一个链表的节点个数,但也有可能不需要。

通过模板参数,来控制是否需要count字段。在不需要获取节点数量(例如本例中的GroupArray聚合函数)的场景下,就可以省下count字段的8个字节。积少成多。

struct EmptyState{};template <bool is_empty, typename T>
using TypeOrEmpty = std::conditional_t<is_empty, EmptyState, T>;template <...,bool require_count,...>
struct ...
{... ...[[no_unique_address]] TypeOrEmpty<!require_count, UInt64> node_count {};... ...
};

当模板参数require_count等于false的时候, node_count不占空间。

具体实现

代码结构

实现由以下几个模板类组成:

  1. struct ArenaLinkedNodeList
    表示整一个链表数据结构。
  2. concept UnfixedSized
    判定数据类型是否为变长的,规定凡是有char * data和size_t size的就是变长数据类型。
  3. struct ArenaMemoryNodeBase
    表示链表中的一个节点的数据结构基类。派生类使用CRTP模式继承此基类。
  4. struct ArenaMemoryUnfixedSizedNode
    表示变长数据的链表节点,通常是字符串、数组。
  5. struct ArenaMemoryFixedSizedNode
    表示固定长度数据的链表节点,通常是数值、日期。

具体代码

以下是具体代码。

struct EmptyState { };  // 表示空类型。template <bool use_type, typename T>
using MaybeEmptyState = std::conditional_t<use_type, T, EmptyState>; // 可能是空类型,也可能是T,由use_type控制。template <typename T>
concept UnfixedSized = requires(T v)
{{v.size} -> std::convertible_to<size_t>;{v.data} -> std::convertible_to<const char *>;
};template <typename T>
concept FixedSized = !UnfixedSized<T>;template <typename Derived, typename ValueType>
struct ArenaMemoryNodeBase
{Derived * next;Derived * asDerived(){return static_cast<Derived*>(this);}const Derived * asDerived() const{return static_cast<const Derived*>(this);}template <bool is_plain_column>ALWAYS_INLINE static Derived * allocate(const IColumn & column, size_t row_num, Arena & arena){return Derived::allocate(Derived::template getValueFromColumn<is_plain_column>(column, row_num, arena), arena);}ALWAYS_INLINE Derived * clone(Arena & arena) const{size_t copy_size = Derived::realMemorySizeOfNode(static_cast<const Derived &>(*this));Derived * new_node = reinterpret_cast<Derived*>(arena.alignedAlloc(copy_size, alignof(Derived)));memcpy(new_node, this, copy_size);new_node->next = nullptr;return new_node;}ALWAYS_INLINE ValueType value() const{return Derived::getValueFromNode(static_cast<const Derived&>(*this));}ALWAYS_INLINE void serialize(WriteBuffer & buf) const{writeBinary(value(), buf);}
};template <UnfixedSized ValueType>
struct ArenaMemoryUnfixedSizedNode : ArenaMemoryNodeBase<ArenaMemoryUnfixedSizedNode<ValueType>, ValueType>
{using Derived = ArenaMemoryUnfixedSizedNode<ValueType>;static constexpr UInt16 unfixed_sized_len_limit = -1;UInt16 size;char data[6];template <bool is_plain_column>ALWAYS_INLINE static ValueType getValueFromColumn(const IColumn & column, size_t row_num, Arena & arena){if (is_plain_column){const char * begin{};auto serialized = column.serializeValueIntoArena(row_num, arena, begin);return allocate(arena, {serialized.data, serialized.size});}else{return allocate(arena, column.getDataAt(row_num));}}ALWAYS_INLINE static ValueType getValueFromNode(const Derived & node){return {node.data, node.size};}ALWAYS_INLINE static Derived * allocate(ValueType value, Arena & arena){if (value.size > unfixed_sized_len_limit){throw Exception();}Derived * node = reinterpret_cast<Derived*>(arena.alignedAlloc(Derived::realUnfixedSizedDataMemorySizeForPayload(value.size), alignof(Derived)));node->next = nullptr;node->size = value.size;memcpy(node->data, value.data, value.size);return node;}ALWAYS_INLINE static size_t realMemorySizeOfNode(const Derived & node){return realUnfixedSizedDataMemorySizeForPayload(node.size);}ALWAYS_INLINE static size_t realUnfixedSizedDataMemorySizeForPayload(size_t payload_size){return std::max(sizeof(Derived), sizeof(Derived) + payload_size - sizeof(Derived::data));}ALWAYS_INLINE static Derived * deserialize(ReadBuffer & buf, Arena & arena){// Treat all unfixed sized data as String and StringRef.String data;readBinary(data, buf);return allocate(arena, StringRef(data));}};template <FixedSized ValueType>
struct ArenaMemoryFixedSizedNode : ArenaMemoryNodeBase<ArenaMemoryFixedSizedNode<ValueType>, ValueType>
{using Derived = ArenaMemoryFixedSizedNode<ValueType>;ValueType data;template <bool is_plain_column>ALWAYS_INLINE static ValueType getValueFromColumn(const IColumn & column, size_t row_num, Arena & arena){static_assert(!is_plain_column);return allocate(arena, assert_cast<const ColumnVector<ValueType>&>(column).getData()[row_num]);}ALWAYS_INLINE static ValueType getValueFromNode(const Derived & node){return node.data;}ALWAYS_INLINE static Derived * allocate(ValueType value, Arena & arena){Derived * node = reinterpret_cast<Derived*>(arena.alignedAlloc(sizeof(Derived), alignof(Derived)));node->next = nullptr;node->data = value;return node;}ALWAYS_INLINE size_t realMemorySizeOfNode() const{return sizeof(Derived);}ALWAYS_INLINE static Derived * deserialize(ReadBuffer & buf, Arena & arena){Derived * new_node = reinterpret_cast<Derived*>(arena.alignedAlloc(sizeof(Derived), alignof(Derived)));new_node->next = nullptr;readBinary(new_node->data, buf);return new_node;}
};template <typename ValueType, bool has_count_field = false>
struct ArenaLinkedList
{template <typename T>struct NodeTraits{using NodeType = ArenaMemoryFixedSizedNode<T>;};template <UnfixedSized T>struct NodeTraits<T>{using NodeType = ArenaMemoryUnfixedSizedNode<T>;};using Node = typename NodeTraits<ValueType>::NodeType;Node * head{};Node * tail{};[[no_unique_address]] std::conditional_t<has_count_field, size_t, EmptyState> count{};struct Iterator{using iterator_category = std::forward_iterator_tag;using difference_type = std::ptrdiff_t;using value_type = ValueType;using pointer = value_type *;using reference = value_type &;value_type operator * (){return p->value();}Iterator & operator ++ (){p = p->next;return *this;}Iterator operator ++ (int){auto retvalue = *this;++(*this);return retvalue;}friend bool operator == (const Iterator & left, const Iterator & right) = default;friend bool operator != (const Iterator & left, const Iterator & right) = default;Node * p{};};Iterator begin() const { return {head}; }Iterator end() const { return {nullptr}; }template <bool is_plain_column>ALWAYS_INLINE void add(const IColumn & column, size_t row_num, Arena & arena){Node * new_node = Node::template allocate<is_plain_column>(arena, column, row_num);add(new_node);}ALWAYS_INLINE void add(ValueType value, Arena & arena){Node * new_node = Node::allocate(value, arena);add(new_node);}ALWAYS_INLINE void add(Node * new_node){new_node->next = nullptr;if (head == nullptr) [[unlikely]]{head = new_node;}if (tail != nullptr) [[likely]]{tail->next = new_node;}tail = new_node;if constexpr (has_count_field){++ count;}}ALWAYS_INLINE size_t size() const{if constexpr (has_count_field){return count;}else{return std::distance(begin(), end());}}ALWAYS_INLINE bool empty() const{return begin() == end();}void merge(const ArenaLinkedList & rhs, Arena & arena){auto rhs_iter = rhs.head;while (rhs_iter){auto new_node = rhs_iter->clone(arena);add(new_node);rhs_iter = rhs_iter->next;}}void mergeLink(const ArenaLinkedList & rhs, Arena &){if (!head) [[unlikely]]{head = rhs.head;}if (tail) [[likely]]{tail->next = rhs.head;}if (rhs.tail) [[likely]]{tail = rhs.tail;}if constexpr (has_count_field){count += rhs.count;}}
};

测试

测试代码是基于gtest写的。

#include <ArenaLinkedNodeList.h>#include <gtest/gtest.h>TEST(ArenaLinkedList, StringList)
{Arena arena;ArenaLinkedList<StringRef> list;String s1{"Activity1"}, s2{"Activity2"}, s3{"ActivityActivity3"};list.add(StringRef(s1), arena);list.add(StringRef(s2), arena);list.add(StringRef(s3), arena);ASSERT_EQ(list.size(), 3);auto iter = list.begin();ASSERT_EQ(*iter, s1);++iter;ASSERT_EQ(*iter, s2);++iter;ASSERT_EQ(*iter, s3);
}TEST(ArenaLinkedList, NumberList)
{Arena arena;ArenaLinkedList<Int64, true> list;std::array<Int64, 10> expected {1, 4, 2, 1024, 10231024, 102310241025, 888, 99999999, -102310241025, -99999999};for (auto x : expected)list.add(x, arena);ASSERT_EQ(list.size(), 10);auto iter = list.begin();for (size_t i = 0; i < expected.size(); ++i, ++iter)ASSERT_EQ(*iter, expected[i]);
}TEST(ArenaLinkedList, MergeList)
{Arena arena;ArenaLinkedList<StringRef> list1, list2;String s1{"Activity1"}, s2{"Activity2"}, s3{"ActivityActivity3"}, s4{"ABCDEFGHIJKLMN"};Strings expected{s1, s2, s3, s4};list1.add(StringRef(s1), arena);list1.add(StringRef(s2), arena);list1.add(StringRef(s3), arena);list2.add(StringRef(s4), arena);list1.merge(list2, arena);auto iter = list1.begin();ASSERT_EQ(list1.size(), expected.size());for (auto x : expected){ASSERT_EQ(*iter, x);++iter;}
}TEST(ArenaLinkedList, MergeEmptyList)
{Arena arena;ArenaLinkedList<StringRef> list1, list2;String s1{"Activity1"}, s2{"Activity2"}, s3{"ActivityActivity3"};Strings expected{s1, s2, s3};list1.add(StringRef(s1), arena);list1.add(StringRef(s2), arena);list1.add(StringRef(s3), arena);// list2 is an empty list.list1.merge(list2, arena);auto iter = list1.begin();ASSERT_EQ(list1.size(), expected.size());for (auto x : expected){ASSERT_EQ(*iter, x);++iter;}
}TEST(ArenaLinkedList, MergeLinkEmptyList)
{Arena arena;ArenaLinkedList<StringRef> list1, list2;String s1{"Activity1"}, s2{"Activity2"}, s3{"ActivityActivity3"};Strings expected{s1, s2, s3};list1.add(StringRef(s1), arena);list1.add(StringRef(s2), arena);list1.add(StringRef(s3), arena);// list2 is an empty list.list1.mergeLink(list2, arena);auto iter = list1.begin();ASSERT_EQ(list1.size(), expected.size());for (auto x : expected){ASSERT_EQ(*iter, x);++iter;}
}TEST(ArenaLinkedList, MergeLinkEmptyListAndAddNew)
{Arena arena;ArenaLinkedList<StringRef> list1, list2;String s1{"Activity1"}, s2{"Activity2"}, s3{"ActivityActivity3"}, s4{"ABCDEFGHIJKLMN"}, s5{"abcdefg"};Strings expected{s1, s2, s3, s4, s5};list1.add(StringRef(s1), arena);list1.add(StringRef(s2), arena);list1.add(StringRef(s3), arena);// list2 is an empty list.list1.mergeLink(list2, arena);list1.add(StringRef(s4), arena);list1.add(s5, arena);auto iter = list1.begin();ASSERT_EQ(list1.size(), expected.size());for (auto x : expected){ASSERT_EQ(*iter, x);++iter;}
}

测试全部通过。

相关文章:

一种高效且节约内存的聚合数据结构的实现

一种高效且节约内存的聚合数据结构的实现 在特定的场景中&#xff0c;特殊定制数据结构能够得到更加好的性能且更节约内存。 聚合函数GroupArray的问题 GroupArray聚合函数是将分组内容组成一个个数组&#xff0c;例如下面的例子&#xff1a; SELECT groupArray(concat(ABC…...

机器学习(10)---特征选择

文章目录 一、概述二、Filter过滤法2.1 过滤法说明2.2 方差过滤2.3 方差过滤对模型影响 三、相关性过滤3.1 卡方过滤3.2 F检验3.3 互信息法3.4 过滤法总结 四、Embedded嵌入法4.1 嵌入法说明4.2 以随机森林为例的嵌入法 五、Wrapper包装法5.1 包装法说明5.2 以随机森林为例的包…...

Python之数据库(MYSQL)连接

一&#xff09;数据库SQL语言基础 MySQL是一个关系型数据库管理系统&#xff0c;由瑞典MySQL AB 公司开发&#xff0c;目前属于 Oracle 旗下产品。MySQL 是最流行的关系型数据库管理系统之一&#xff0c;在 WEB 应用方面&#xff0c;MySQL是最好的 RDBMS (Relational Database…...

【建站教程】使用阿里云服务器怎么搭建网站?

使用阿里云服务器快速搭建网站教程&#xff0c;先为云服务器安装宝塔面板&#xff0c;然后在宝塔面板上新建站点&#xff0c;阿里云服务器网以搭建WordPress网站博客为例&#xff0c;阿小云来详细说下从阿里云服务器CPU内存配置选择、Web环境、域名解析到网站上线全流程&#x…...

【自然语言处理】关系抽取 —— MPDD 讲解

MPDD 论文信息 标题:MPDD: A Multi-Party Dialogue Dataset for Analysis of Emotions and Interpersonal Relationships 作者:Yi-Ting Chen, Hen-Hsen Huang, Hsin-Hsi Chen 期刊:LREC 2020 发布时间与更新时间:2020 主题:自然语言处理、关系抽取、对话场景、情感预测 数…...

深入理解JVM虚拟机第三篇:JVM的指令集架构模型和JVM的生命周期

文章目录 一:JVM的指令集架构模型 1:基于栈式架构的特点...

[小尾巴 UI 组件库] 组件库配置与使用

文章归档于&#xff1a;https://www.yuque.com/u27599042/row3c6 组件库地址 npm&#xff1a;https://www.npmjs.com/package/xwb-ui?activeTabreadme小尾巴 UI 组件库源码 gitee&#xff1a;https://gitee.com/tongchaowei/xwb-ui小尾巴 UI 组件库测试代码 gitee&#xff1a…...

Linux系统中fork()函数的理解

fork() 函数是一个在Unix和类Unix操作系统中常见的系统调用&#xff0c;用于创建一个新的进程&#xff0c;该进程是调用进程&#xff08;父进程&#xff09;的副本。fork() 函数的工作原理如下&#xff1a; 1. 当父进程调用 fork() 时&#xff0c;操作系统会创建一个新的进程&a…...

Linux网络编程:网络协议及网络传输的基本流程

目录 一. 计算机网络的发展 二. 网络协议的认识 2.1 对于协议分层的理解 2.2 TCP/IP五层协议模型 2.3 OSI七层模型 三. 网络传输的流程 3.1 同一网段中计算机通信的流程 3.2 不同网段中计算机设备的通信 3.3 对于IP地址和MAC地址的理解 3.4 数据的封装和解包 四. 总结…...

【大数据之Kafka】十、Kafka消费者工作流程

1 Kafka消费方式 &#xff08;1&#xff09;pull&#xff08;拉&#xff09;模式&#xff1a;消费者从broker中主动拉取数据。&#xff08;Kafka中使用&#xff09; 不足&#xff1a;如果Kafka中没有数据&#xff0c;消费者可能会陷入循环&#xff0c;一直返回空数据。 &#…...

如何确保ChatGPT的文本生成对特定行业术语的正确使用?

确保ChatGPT在特定行业术语的正确使用是一个重要而复杂的任务。这涉及到许多方面&#xff0c;包括数据预处理、模型训练、微调、评估和监控。下面我将详细介绍如何确保ChatGPT的文本生成对特定行业术语的正确使用&#xff0c;并探讨这一过程中的关键考虑因素。 ### 1. 数据预处…...

行业追踪,2023-09-11

自动复盘 2023-09-11 凡所有相&#xff0c;皆是虚妄。若见诸相非相&#xff0c;即见如来。 k 线图是最好的老师&#xff0c;每天持续发布板块的rps排名&#xff0c;追踪板块&#xff0c;板块来开仓&#xff0c;板块去清仓&#xff0c;丢弃自以为是的想法&#xff0c;板块去留让…...

LVS + Keepalived群集

文章目录 1. Keepalived工具概述1.1 什么是Keepalived1.2 工作原理1.3 Keepailved实现原理1.4 Keepalived体系主要模块及其作用1.5 keepalived的抢占与非抢占模式 2. 脑裂现象 &#xff08;拓展&#xff09;2.1 什么是脑裂2.2 脑裂的产生原因2.3 如何解决脑裂2.4 如何预防脑裂 …...

springboot将jar改成war

一、maven项目 1、修改pom文件 <packaging>war</packaging>2、添加Servlet API依赖&#xff0c;Spring Boot的Starter依赖通常会包含这个依赖&#xff0c;所以你可能已经有了&#xff0c;没有就需要添加 <dependency><groupId>javax.servlet</gr…...

从9.10拼多多笔试第四题产生的01背包感悟

文章目录 题面基本的01背包问题本题变式 本文参考&#xff1a; 9.10拼多多笔试ak_牛客网 (nowcoder.com) 拼多多 秋招 2023.09.10 编程题目与题解 (xiaohongshu.com) 题面 拼多多9.10笔试的最后一题&#xff0c;是一道比较好的01背包变式问题&#xff0c;可以学习其解法加深对…...

搭建自己的OCR服务,第一步:选择合适的开源OCR项目

一、OCR是什么&#xff1f; 光学字符识别&#xff08;Optical Character Recognition, OCR&#xff09;是指对文本资料的图像文件进行分析识别处理&#xff0c;获取文字及版面信息的过程。 亦即将图像中的文字进行识别&#xff0c;并以文本的形式返回。 二、OCR的基本流程 1…...

【C++】VScode配置C/C++语言环境(简洁易懂版)

目录 一、下载VScode&#xff08;装好直接跳第五步&#xff09;二、安装VScode三、VScode设置语言为中文四、VScode切换主题&#xff08;个人爱好&#xff09;五、下载C语言编译器&#xff08;MinGW-W64 GCC&#xff09;六、配置编译器环境变量七、配置VScode八、使用单独窗口…...

【hive】—原有分区表新增加列(alter table xxx add columns (xxx string) cascade;)

项目场景&#xff1a; 需求&#xff1a;需要在之前上线的分区报表中新增加一列。 实现方案&#xff1a; 1、创建分区测试表并插入测试数据 drop table test_1; create table test_1 (id string, score int, name string ) partitioned by (class string) row format delimit…...

verilog学习笔记7——PMOS和NMOS、TTL电路和CMOS电路

文章目录 前言一、PMOS和NMOS1、NMOS2、PMOS3、增强型和耗尽型4、两者面积大小 二、CMOS门电路1、非门2、与非门3、或非门4、线与逻辑5、CMOS传输门6、三态门 三、TTL电路四、TTL电路 VS CMOS电路五、数字电平六、使用CMOS电路实现逻辑函数1、上拉网络 PUN2、下拉网络 PDN3、实…...

Java知识点二

Java知识点二 1、Comparable内部比较器&#xff0c;Comparator外部比较器2、源码结构的区别:1&#xff09;Comparable接口&#xff1a;2&#xff09;Comparator接口&#xff1a; 2、Java反射 1、Comparable内部比较器&#xff0c;Comparator外部比较器 我们一般把Comparable叫…...

【网络】每天掌握一个Linux命令 - iftop

在Linux系统中&#xff0c;iftop是网络管理的得力助手&#xff0c;能实时监控网络流量、连接情况等&#xff0c;帮助排查网络异常。接下来从多方面详细介绍它。 目录 【网络】每天掌握一个Linux命令 - iftop工具概述安装方式核心功能基础用法进阶操作实战案例面试题场景生产场景…...

【kafka】Golang实现分布式Masscan任务调度系统

要求&#xff1a; 输出两个程序&#xff0c;一个命令行程序&#xff08;命令行参数用flag&#xff09;和一个服务端程序。 命令行程序支持通过命令行参数配置下发IP或IP段、端口、扫描带宽&#xff0c;然后将消息推送到kafka里面。 服务端程序&#xff1a; 从kafka消费者接收…...

Xshell远程连接Kali(默认 | 私钥)Note版

前言:xshell远程连接&#xff0c;私钥连接和常规默认连接 任务一 开启ssh服务 service ssh status //查看ssh服务状态 service ssh start //开启ssh服务 update-rc.d ssh enable //开启自启动ssh服务 任务二 修改配置文件 vi /etc/ssh/ssh_config //第一…...

Day131 | 灵神 | 回溯算法 | 子集型 子集

Day131 | 灵神 | 回溯算法 | 子集型 子集 78.子集 78. 子集 - 力扣&#xff08;LeetCode&#xff09; 思路&#xff1a; 笔者写过很多次这道题了&#xff0c;不想写题解了&#xff0c;大家看灵神讲解吧 回溯算法套路①子集型回溯【基础算法精讲 14】_哔哩哔哩_bilibili 完…...

连锁超市冷库节能解决方案:如何实现超市降本增效

在连锁超市冷库运营中&#xff0c;高能耗、设备损耗快、人工管理低效等问题长期困扰企业。御控冷库节能解决方案通过智能控制化霜、按需化霜、实时监控、故障诊断、自动预警、远程控制开关六大核心技术&#xff0c;实现年省电费15%-60%&#xff0c;且不改动原有装备、安装快捷、…...

在 Nginx Stream 层“改写”MQTT ngx_stream_mqtt_filter_module

1、为什么要修改 CONNECT 报文&#xff1f; 多租户隔离&#xff1a;自动为接入设备追加租户前缀&#xff0c;后端按 ClientID 拆分队列。零代码鉴权&#xff1a;将入站用户名替换为 OAuth Access-Token&#xff0c;后端 Broker 统一校验。灰度发布&#xff1a;根据 IP/地理位写…...

Frozen-Flask :将 Flask 应用“冻结”为静态文件

Frozen-Flask 是一个用于将 Flask 应用“冻结”为静态文件的 Python 扩展。它的核心用途是&#xff1a;将一个 Flask Web 应用生成成纯静态 HTML 文件&#xff0c;从而可以部署到静态网站托管服务上&#xff0c;如 GitHub Pages、Netlify 或任何支持静态文件的网站服务器。 &am…...

2021-03-15 iview一些问题

1.iview 在使用tree组件时&#xff0c;发现没有set类的方法&#xff0c;只有get&#xff0c;那么要改变tree值&#xff0c;只能遍历treeData&#xff0c;递归修改treeData的checked&#xff0c;发现无法更改&#xff0c;原因在于check模式下&#xff0c;子元素的勾选状态跟父节…...

【Go】3、Go语言进阶与依赖管理

前言 本系列文章参考自稀土掘金上的 【字节内部课】公开课&#xff0c;做自我学习总结整理。 Go语言并发编程 Go语言原生支持并发编程&#xff0c;它的核心机制是 Goroutine 协程、Channel 通道&#xff0c;并基于CSP&#xff08;Communicating Sequential Processes&#xff0…...

sipsak:SIP瑞士军刀!全参数详细教程!Kali Linux教程!

简介 sipsak 是一个面向会话初始协议 (SIP) 应用程序开发人员和管理员的小型命令行工具。它可以用于对 SIP 应用程序和设备进行一些简单的测试。 sipsak 是一款 SIP 压力和诊断实用程序。它通过 sip-uri 向服务器发送 SIP 请求&#xff0c;并检查收到的响应。它以以下模式之一…...