2023年4月

nack源码浅析


Video Nack

  • 机制概述


    • nack的机制非常简洁,收到非连续的packet seq 会将丢包的seq插入自身nack_list缓存, 之后立即发送一次那组丢包的seq重传请求, 之后如果超时仍然没有收到重传回来的seq, 就通过定时任务继续发送.
  • nack 三个缓存list


    • nack_list_ : 用于记录已丢包的信息,seq 即为list key
    • keyframe_list_ : 记录关键帧序列号,可用于后面清理比关键帧老的过旧nack,
    • recovered_list_ : 用于记录从RTX或FEC恢复过来的包,
  • nack 两种发送方式:


    • 1.kSeqNumOnly : 开启nack模块后,nack会检查接收到packet的序列号,如果序列号连续性中断即认为是丢包了,如下例子,上一次最新收到的包序列号为38,当前新收到的序列号为41,那么[39,40]就判定为是丢掉了,会立刻发送这组[39,40]的nack重传请求

      newest_seq_num_:36 seq_num:37 is_keyframe:0 is_recovered: 0 
      newest_seq_num_:37 seq_num:38 is_keyframe:0 is_recovered: 0 
      newest_seq_num_:38 seq_num:41 is_keyframe:0 is_recovered: 0 
      newest_seq_num_:41 seq_num:42 is_keyframe:0 is_recovered: 0 
      newest_seq_num_:42 seq_num:43 is_keyframe:0 is_recovered: 0
      
    • 2.kTimeOnly : nack 模块创建后会启动一个定时任务,默认周期kUpdateInterval(20ms), 这个周期任务会调用GetNackBatch(kTimeOnly)从nack_list里面获取满足发送条件的seq,批量发送nack重传请求.

      repeating_task_ = RepeatingTaskHandle::DelayedStart(
          TaskQueueBase::Current(), kUpdateInterval,
          [this]() {
            std::vector<uint16_t> nack_batch = GetNackBatch(kTimeOnly);
            if (!nack_batch.empty()) {
              nack_sender_->SendNack(nack_batch, false);
            }
          });
      
    • 3.kSeqNumOnly模式是在接收packet的时候触发一次,并且只发送一次,即第一次,之后如果仍然没有收到重传回来的包就通过kTimeOnly定时任务方式继续请求重传.

nack模块源码简析

nack模块

  • nack模块位于在RTX和FEC 和 jitter buffer之间,经过Call模块将RTP包分发到RtpVideoStreamReceiver模块,当模块RtpVideoStreamReceiver每次对rtp包进行处理的时候都会调用NackModule::OnReceivedPacket()主动驱动NackModule模块.

nack list

  • insert :
    • insert : AddPacketsToNack()会判断包的连续性,相应的丢包序列如果不在recover list里面就会插入
  • erase :
    • 1.序列号距离当前收到的序列号过旧的包kMaxPacketAge(10000)
    • 2.nack_list 大小 + 即将插入的nack 序列数量如果超过kMaxNackPackets(1000) 就会清理掉关键帧之前的nack,循环直至size 小于1000 或者 已经到了最新关键帧
    • 3.如果经步骤2 nack_list大小仍然超过了nackkMaxNackPackets(1000) 会全部清理掉,并重新请求关键帧
    • 4.收到乱序的包, 可能是抖动过来的 或者 后面恢复过来的包.
    • 5.发送超过10次仍然没有收到重传回来的包.

keyFrame list & recovered list

  • insert :
    • 只需要判断是否是关键帧或恢复过来的包即可插入
  • erase:
    • 三个缓存都相同的删除点,清理序列号距离当前收到的序列号过旧的包kMaxPacketAge(10000) 例如[6,7,...,100007],6即被清理.

nack 发送的策略

  • 前面提的两种发送处理在NackModule2::GetNackBatch()里面,一处在创建模块的时候就会启动的定时任务里定时调用,一处在NackModule2:: OnReceivedPacket()检查完包连续性后就会立即调用.

  • NackModule2::GetNackBatch(kSeqNumOnly) : kSeqNumOnly 根据序列号判断是否发送nack
    仅在第一次发送的时会用到序列号方式,延迟发送时间为kDefaultSendNackDelayMs(0ms), 所以基本是入nack_lisk后就立即发送了(可以作为优化点之一后面提), 发送后会更新nack_list[seq].send_at_time = now(), 供后续定时任务判断是否超时

  • NackModule2::GetNackBatch(kTimeOnly) :kTimeOnly 根据时间判断是否发送nack,在没有打开补偿配置的情况下间隔为一个rtt时间,rtt会动态更新(默认频率1000ms), 初始值为kDefaultRttMs(100ms), 再次发送的时间 resend_delay 默认为一个rtt 时间,即一个rtt时间后没有收到重传回来的nack,就继续发送, 实验阶段增加了补偿配置,可以动态延长resend_delay 延迟, 可以作为改进方案之一, 后面有提.

nack 模块的几个重要常量

  • nack 模块的几个重要常量
    const int kMaxPacketAge = 10000; //三个缓存list包序列的生存长度
    const int kMaxNackPackets = 1000; // nack_list 存储 packets 的最大大小
    const int kDefaultRttMs = 100; // 默认rtt 时间
    const int kMaxNackRetries = 10; // 最大重试次数
    const int kDefaultSendNackDelayMs = 0; // 延迟发送nack时间
    static constexpr TimeDelta kUpdateInterval = TimeDelta::Millis(20); // 定时发送nack任务的周期
    

改进参考

经过调研和一小部分数据测试,发现nack有几个可能的优化点

配置一个合适的发送延迟

收到正常顺序外的包,原生机制默认是直接就返送nack的, 当前版本支持了NACK延时发送机制,通过控制NACK延时发送的时间间隔,避免固定延时网络下无必要的重传请求。比如,如果kDefaultSendNackDelayMs=20ms,如果因为网络的固有延时,造成某些数据包迟到了10ms,而此时没有NACK延时发送机制的话,这些包都会被认为丢了,从而对这些包请求重传。但是如果有20ms的NACK延时发送,这些包就不会被计算为丢失,从而避免了没有必要的重传请求,避免了资源浪费

[023:217](nack_module2.cc:182): OnReceivedPacket:seq_num|newest_seq_num_|is_keyframe|is_recovered|loss_ratio|recover_ratio: 25402|25399|0|0|36.21%|0.00%|0|0|903
[023:218](nack_module2.cc:182): OnReceivedPacket:seq_num|newest_seq_num_|is_keyframe|is_recovered|loss_ratio|recover_ratio: 25401|25402|0|0|36.24%|0.00%|0|0|905
[023:219](rtp_video_stream_receiver2.cc:745): RtpVideoStreamReceiver2::RequestPacketRetransmit:SendNack:sn:size():2:|25400|25401|

重发补偿

改进2和上面情况其实类似,

如下日志, 序列33156 刚发送完第二次nack请求(69:466ms), 仅30ms之差收到了重传包(069:496ms),之后又收到了一次重传包(69:843ms),浪费网络资源.

可以配置重传补偿,每次重传时间增加%25,原生机制自带,需要打开配置。


[069:326](video_receive_stream2.cc:581):VideoReceiveStream2:OnRttUpdate|avg_rtt_ms|max_rtt_ms: 377ms|407ms

[069:466](rtp_video_stream_receiver2.cc:742):RtpVideoStreamReceiver2::RequestPacketRetransmit:SendNack:sn:size():1:|33156|

[069:496](rtx_receive_stream.cc:70):RtxReceiveStream::OnRtpPacket:recovered:sq:33156

[069:497](nack_module2.cc:181):OnReceivedPacket:seq_num|newest_seq_num_|is_keyframe|is_recovered|loss_ratio|recover_ratio|dup_recover_ratio:33156|33167|0|1|29.90%|98.80%|30.37%

----

[069:843](rtx_receive_stream.cc:70):RtxReceiveStream::OnRtpPacket:recovered:sq:33156

----

//测试发现在 200ms delay %30丢包下 这类重复重传包的情况也有高达%30左右比率
//这个case尝试增加这个改进后,重传比率降低到了 %21左右

----

[327:764](nack_module2.cc:182):OnReceivedPacket:seq_num|newest_seq_num_|is_keyframe|is_recovered|loss_ratio|recover_ratio|dup_recover_ratio: 23891|23894|0|1|30.93%|97.13%|%21.88

Audio Nack

Audio Nack 默认不打开,可通过配置feedback参数打开

        codec.AddFeedbackParam(
            FeedbackParam(kRtcpFbParamNack, kParamValueEmpty));

具体实现在NackTracker中, 机制大同小异

可改进点
:
固定丢包场景,高丢包等场景,可重置RTT校验策略,增强nack重传效果,配合控制neteq buffer 低水位高度,实验测试可以做到80-90%抗接收丢包.

SRS Nack

机制与webrtc 大同小异, 调用栈如下:

默认nack_list 大小 audio 66 video 666 定时默认20ms

if (is_audio) {
    rtp_queue_ = new SrsRtpRingBuffer(100);
    nack_receiver_ = new SrsRtpNackForReceiver(rtp_queue_, 100 * 2 / 3);
} else {
    rtp_queue_ = new SrsRtpRingBuffer(1000);
    nack_receiver_ = new SrsRtpNackForReceiver(rtp_queue_, 1000 * 2 / 3);
}

----

SrsRtcConnectionNackTimer::SrsRtcConnectionNackTimer(SrsRtcConnection* p) : p_(p)
{
    _srs_hybrid->timer20ms()->subscribe(this);
}

默认初始常量

SrsNackOption::SrsNackOption()
{
    max_count = 15;
    max_alive_time = 1000 * SRS_UTIME_MILLISECONDS;
    first_nack_interval = 10 * SRS_UTIME_MILLISECONDS;
    nack_interval = 50 * SRS_UTIME_MILLISECONDS;
    max_nack_interval = 500 * SRS_UTIME_MILLISECONDS;
    min_nack_interval = 20 * SRS_UTIME_MILLISECONDS;
    nack_check_interval = 20 * SRS_UTIME_MILLISECONDS;
}

论文信息

论文标题:Domain Confused Contrastive Learning for Unsupervised Domain Adaptation
论文作者:Quanyu Long, Tianze Luo, Wenya Wang and Sinno Jialin Pan

论文来源:
NAACL 2023
论文地址:
download
论文代码:download
视屏讲解:click

1 介绍

引入:

    • 监督对比学习不适用于 NLP 无监督域适应,因为存在语法差异、语义偏移;

Note:
域差异较大:对比于图像之间的域差异,文本之间的域差异相对较大;

    • 从领域适应的角度来看,构建跨域正样本和对齐域不可知对在相关文献中得到的重视较少;
      • 跨域正样本对齐:对比学习中的正采样减少了域差异;
      • 对齐域不可知:即下文提到的将源域、目标域和领域谜题 对齐;

提出:

    • 领域谜题(domain puzzle):丢弃与领域相关的信息来混淆模型,因此很难区分这些谜题属于哪个领域;

    • Fig 1:领域谜题可被认为是中间域,目的是使源样本和目标样本更接近,并通过学习域不变表示来连接两个域;

建议:

    • 不建议在源域和目标域之间直接寻找匹配的句子,而是利用源(目标)数据及其相应的领域谜题来减少域的差异,如 Fig1 所示。

    • 制作领域谜题的一个简单想法是屏蔽特定于领域的 Token,然而,Token 级的操作过于离散和不灵活,无法反映自然语言中的复杂语义变化。因此,本文的目标是寻找更好的领域谜题,在每个训练实例的表示空间中保持高置信度的预测和任务辨别能力;

2 相关工作

2.1 对抗训练

域对抗训练已经被证明可以提高很多自然语言模型的性能,这类算法通常考虑对单词嵌入的扰动,并减少输入样本周围的对抗损失,对抗训练的目标是:

$\underset{\theta_{f}, \theta_{y}}{\text{min}}\sum\limits _{(x, y) \sim \mathcal{D}}\left[\max _{\delta} \mathcal{L}\left(f\left(x+\delta ; \theta_{f}, \theta_{y}\right), y\right)\right]$

标准的对抗训练可用通过使用虚拟对抗训练进行正则化:

$\begin{array}{r}\underset{\theta_{f}, \theta_{y}}{\text{min}} \sum_{(x, y) \sim \mathcal{D}}[\mathcal{L}(f(x ; \theta_{f}, \theta_{y}), y)+\alpha_{a d v}\underset{\delta}{\text{max}} \mathcal{L}(f(x+\delta ; \theta_{f}, \theta_{y}), f(x ; \theta_{f}, \theta_{y}))]\end{array}$

内部最大化可以通过投影梯度下降(PGD)来求解,对抗性扰动 $\delta$ 的近似:

$\delta_{t+1}=\Pi_{\|\delta\|_{F} \leq \epsilon}\left(\delta_{t}+\eta \frac{g_{y}^{a d v}\left(\delta_{t}\right)}{\left\|g_{y}^{a d v}\left(\delta_{t}\right)\right\|_{F}}\right)$

$g_{y}^{a d v}\left(\delta_{t}\right)=\nabla_{\delta} \mathcal{L}\left(f\left(x+\delta_{t} ; \theta_{f}, \theta_{y}\right), y\right)$

其中,$\Pi_{\|\delta\|_{F} \leq \epsilon}$ 在 $\epsilon$ 球上执行投影。PGD 的优点在于它只依赖于模型本身来产生不同的对抗性样本,使模型能够更好地推广到不可见的数据。

3 方法

整体框架:

该模型将以源标记和未标记的目标句子作为输入。然后,它将通过制造对抗性扰动来增加输入数据。下一步用一个编码器生成一个隐藏表示,该编码器将进一步用于产生三个损失来训练整个模型,即情绪分类损失、对比损失和一致性损失。

3.1 制作领域谜题

对于 UDA,Saito等人[2017] 提到,简单地匹配分布并不能确保没有目标标签的目标域的高精度。此外,它还可能导致负转移,恶化知识从源域向目标域的转移。即使匹配的句子具有相同的标签,由于巨大的句法和语义转移,基于实例的匹配策略对齐来自不同域的例子,也会为预先训练的语言模型引入噪声,例如在 Fig3 中对齐源域和目标域句子。

或者,我们也可以定位和屏蔽与句子主题和类型相关的领域特定的标记。由于 Fig3 中绿色框中的句子成为领域不可知的,我们将那些领域混淆的句子(无法判断这些句子属于哪个领域)称为领域谜题。将源域与域难题以及目标域和域难题之间的匹配分布,也将使语言模型产生域不变表示。

然而,特定领域的标记并不总是明显的,由于自然语言的离散性,在不影响语义的情况下决定正确的标记是一个挑战,特别是当句子是复杂的。因此,我们在表示空间中寻找领域谜题,并引入对抗性扰动,因为我们可以依赖模型本身来产生不同但有针对性的领域谜题。请注意,这里的对抗性攻击的目的不是为了增强鲁棒性,而是为了构造精细产生的扰动,以便在表示空间中获得更好的域不变性。

为了生成域混淆的增强,我们采用带有扰动的对抗性攻击来进行域分类。使用对抗性攻击学习域分类器的损失可以指定如下:

$\begin{array}{l}\mathcal{L}_{\text {domain }}=\mathcal{L}\left(f\left(x ; \theta_{f}, \theta_{d}\right), d\right)+\alpha_{a d v} \mathcal{L}\left(f\left(x+\delta ; \theta_{f}, \theta_{d}\right), f\left(x ; \theta_{f}, \theta_{d}\right)\right)\end{array}$

$\delta=\Pi_{\|\delta\|_{F} \leq \epsilon}\left(\delta_{0}+\eta \frac{g_{d}^{a d v}\left(\delta_{0}\right)}{\left\|g_{d}^{a d v}\left(\delta_{0}\right)\right\|_{F}}\right)$

3.2 学习域不变特征

在获得域难题后,简单地应用分布匹配将会牺牲从源域学习到的判别知识,而基于实例的匹配也会忽略全局域内信息。为了在没有目标标签的情况下学习情感方面的辨别性表征,我们建议通过对比学习来学习领域不变特征。

此外,对比损失鼓励正对彼此接近,而负对相距很远。具体来说,最大化正对之间的相似性学习基于实例的不变表示,最小化负对之间的相似性从全局视角学习均匀分布的表示,使聚集在任务决策边界附近的实例彼此远离。这将有助于增强学习模型的任务辨别能力。

对于正采样,希望模型能够将原始句子和大多数具有领域挑战性的示例编码为在表示空间中更接近,随着训练的进行逐渐将示例拉到域决策边界。 对于负采样,它扩大了情感决策边界,并为两个领域提升了更好的情感判别特征。 然而,对于跨域负采样,对比损失可能会将目标(源)域中的负样本推离源(目标)域中的 anchor(见F ig4(b)左)。 这与试图拉近不同领域的领域谜题的目标相矛盾。

为了避免跨域排斥的损害,从负集中排除具有不同域的样本非常重要。修改后的 InfoNCE 损失:

$\mathcal{L}_{\text {contrast }}=-\frac{1}{N} \sum\limits _{i}^{N} \log \frac{\exp \left(s\left(z_{i}, z_{i}^{\prime}\right) / \tau\right)}{\sum_{k}^{N} \mathbb{1}_{k \neq i} \exp \left(s\left(z_{i}, z_{k}\right) / \tau\right)}$

其中 $N$ 是具有来自同一域的样本的小批量大小,$z_{i}=g\left(f\left(x_{i} ; \theta_{f}\right)\right)$,$g(\cdot)$ 是一个隐藏层投影头。 我们将 $x^{\prime}=x+\delta$ 表示为域拼图增强,$s(\cdot)$ 计算余弦相似度,$\mathbb{I}_{k \neq i}$ 是指示函数,$\tau$ 是温度超参数。

3.3 一致性正则化

给定基于域分类的扰动嵌入 $x+\delta$,我们还鼓励模型产生与原始实例 $f\left(x ; \theta_{f}, \theta_{y}\right)$ 一致的情感预测。

为此,我们最小化对称 $KL$ 散度,公式为:

$\mathcal{L}_{\text {consist }}=\mathcal{L}\left(f\left(x ; \theta_{f}, \theta_{y}\right), f\left(x+\delta ; \theta_{f}, \theta_{y}\right)\right)$

3.4 整体训练目标

对于整体训练目标,我们以端到端的方式训练神经网络,损失加权和如下。

$\begin{array}{l}\min _{\theta_{f}, \theta_{y}, \theta_{d}} \sum_{(x, y) \sim \mathcal{D} S} \mathcal{L}\left(f\left(x ; \theta_{f}, \theta_{y}\right), y\right)+\sum_{(x, y) \sim \mathcal{D}^{S}, \mathcal{D}^{T}}\left[\alpha \mathcal{L}_{\text {domain }}+\lambda \mathcal{L}_{\text {contrast }}+\beta \mathcal{L}_{\text {consist }}\right] \end{array}$

4 实验

参数敏感实验

可视化分析

5 总结

一.前言

众所周知 OpenAI ChatGPT 是不对中国开放的,包括香港。就最近一个月的情况来看,陆续有 API 调用被限制、大规模账号封禁、关闭注册、无法直接使用银联支付(国内信用卡)等等,使用是越来越艰难。Azure OpenAI 不妨是一个新的选择。

二.介绍

Azure OpenAI 和 OpenAI 公司所提供的 API 服务是一致的(微软是 OpenAI 的大股东),我们是使用的
chat.openai.com
背后也是由 Azure 来进行支持的,包括训练和推理等等。Github Copilot 等 MS 提供的 OpenAI 的服务也是基于 Azure OpenAI 的。

Azure OpenAI:
官网

好处

目前 Azure OpenAI 是没有限制国内使用的,而且可以方便的绑定银联的 Visa/Master 等信用卡直接支付,新注册账户有 200 刀的免费额度。

对于企业私有化部署来说,Azure OpenAI 更为适合,因为可以避免企业数据泄露的风险,服务端数据可以自主控制删除。

定价

对比了服务定价和 OpenAI 是
一致
的。

image-20230404112533495

定价详细文档:
点我

三.申请

需要使用国际版 Azure 而不是由世纪互联运营的中国版 Azure。

GPT-3.5 & DALLE 2 等模型的申请可以通过这个表单:
https://aka.ms/oaiapply。

注意:

表单里面的公司信息如实填写就好了,Azure 订阅 Id 填写你自己的账户订阅 Id

GPT-4.0 需要通过这个表单的申请:
https://aka.ms/oai/get-gpt4

注意:

申请 GPT-4 需要等第一个表单申请通过才行

四.使用

1.Azure OpenAI Studio

在创建好 Azure OpenAI 服务后,提供了一个 Studio 可以在线测试。

image-20230330104238578

2.Azure SDK

目前 Azure 官方的 SDK 已经支持了 Azure OpenAI,你可以直接使用 SDK 接入。

SDK:
点我

API 文档:
点我

如何你是第一次进行 OpenAI 开发,推荐这个视频:
https://www.zhihu.com/zvideo/1618254976240517120

3.OpenAI SDK

部分 OpenAI SDK 直接支持 Azure OpenAI,比如 OpenAI 官方的 Python SDK。因为 Azure OpenAI API 和 OpenAI 还是有一些差异的(响应内容基本无差异),所以无法直接使用一些基于 OpenAI 标准的 SDK 或者开源项目。我写了一个项目可以转换这种差异,可以直接使用。

Azure OpenAI Proxy:
https://github.com/stulzq/azure-openai-proxy

如果你觉得项目不错希望能给 1 个 Star⭐️。

在我们日常项目开发中,操作INI/CFG配置文件,往往会通过调用WinAPI来实现,WinAPI接口参数只支持字符串,而我们项目中,往往数据类型是多种多样的,在保存和获取配置值,我们就要进行类型的转换。

今天给大家推荐一个操作库,这个库就可以解决我们的问题。

项目简介

这是一个基于.Net开发的简单、易用的CFG/INI配置操作库,可以用文本或二进制格式读取、修改和保存配置文件和流,该库与.NET、.NET Core和Mono Framework完全兼容。

技术架构

跨平台:这是基于.Netstandard2.0开发的系统,可以部署在Docker,Windows,Linux,Mac。

项目结构

图片

SharpConfig:配置库操作项目,Example:使用示例。

使用方法

文件加载

Configuration.LoadFromFile("myConfig.cfg");        // 文件
Configuration.LoadFromStream(myStream);            // 文本流
Configuration.LoadFromString(myString);            // 文本
Configuration.LoadFromBinaryFile("myConfig.cfg");  // 二进制
Configuration.LoadFromBinaryStream(myStream);      // 二进制流

文件保存

myConfig.SaveToFile("myConfig.cfg");        // 文件
myConfig.SaveToStream(myStream);            // 文件流
myConfig.SaveToBinaryFile("myConfig.cfg");  // 二进制文件
myConfig.SaveToBinaryStream(myStream);      // 二进制流

使用方法

var cfg = new Configuration();

cfg["SomeStructure"]["SomeString"].StringValue = "foobar";
cfg["SomeStructure"]["SomeInt"].IntValue = 2000;
cfg["SomeStructure"]["SomeInts"].IntValueArray = new[] { 1, 2, 3 };
cfg["SomeStructure"]["SomeDate"].DateTimeValue = DateTime.Now;

cfg.SaveToFile(filename);

对象操作

var cfg = new Configuration();
//对象.
var p = new SomeClass    
{      
    SomeString = "foobar",      
    SomeInt = 2000,      
    SomeInts = new[] { 1, 2, 3 },      
    SomeDate = DateTime.Now    
};
//设置
cfg.Add(Section.FromObject("SomeStructure", p));

数组操作

var cfg = new Configuration();cfg["GeneralSection"]["SomeInts"].IntValueArray = new[] { 1, 2, 3 };
// 获取数组类型值
int[] someIntValuesBack = cfg["GeneralSection"]["SomeInts"].GetValueArray<int>();
float[] sameValuesButFloats = cfg["GeneralSection"]["SomeInts"].GetValueArray<float>();
string[] sameValuesButStrings = cfg["GeneralSection"]["SomeInts"].GetValueArray<string>();
// 获取数组对象
object[] sameValuesButObjects = cfg["GeneralSection"]["SomeInts"].GetValueArray(typeof(int));

配置文件注释

//获取包含所有有效注释分隔字符的数组。当前值为{“#”,“;”}。
Configuration.ValidCommentChars{get;}
//获取或设置保存配置时的首选注释字符。默认值为“#”。
Configuration.PreferredCommentChar{get;set;}
//获取或设置设置的数组元素分隔符。默认值为“,”。
Configuration.ArrayElementSeparator{get;set;}
//获取或设置一个值,该值指示在分析配置时是否应忽略内联注释。
bool Configuration.IgnoreInlineComments{get;set;}
//获取或设置一个值,该值指示在分析配置时是否应忽略前置注释。
bool Configuration.IgnorePreComments{get;set;}
//获取或设置一个值,该值指示在创建配置时是否应添加等号之间的空格。
bool Configuration.SpaceBetweenEquals{get;set;}
//获取或设置一个值,该值指示字符串值是否不带引号,但包括其间的所有内容
bool Configuration.OutputRawStringValues{get;set;}

项目地址

https://github.com/cemdervis/sharpconfig

更多开源项目请查看

一个专注推荐优秀.Net开源项目的榜单

- End -

文章首发于公众号【编程乐趣】,欢迎大家关注。
图片

1、背景

当我们的
hadoop
集群运行了一段时间之后,原有的数据节点的容量已经不能满足我们的存储了,这个时候就需要往集群中增加新的数据节点。此时我们就需要动态的对hdfs集群进行扩容操作(
节点服役
)。

2、集群黑白名单


hdfs
集群中是存在
黑名单

白名单
的。

黑名单:
该文件包含
不允许
连接到
namenode
的主机列表。
必须指定文件的完整路径名
。如果该值
为空
,则表示不排除任何主机。
白名单:
该文件包含
允许
连接到
namenode
的主机列表。
必须指定文件的完整路径名
。如果该值
为空
,则允许所有主机

黑白名单配置

vim hdfs-site.xml

<!-- 配置黑名单,使用黑名单可以下线集群  -->
<property>
    <name>dfs.hosts.exclude</name>
    <value>/opt/bigdata/hadoop-3.3.4/etc/hadoop/blacklist.hosts</value>
</property>
<!-- 配置白名单,只有白名单中的节点才可以访问namenode  -->
<property>
    <name>dfs.hosts</name>
    <value>/opt/bigdata/hadoop-3.3.4/etc/hadoop/whitelist.hosts</value>
</property>

注意:
第一次配置黑白名单时,需要重启集群才可以生效,之后修改了黑白名单文件,只需要执行
hdfs dfsadmin -refreshNodes
命令即可。

3、准备一台新的机器并配置好hadoop环境

3.1 我们现有的集群规划

我们现有的集群规划
参考此文章的集群搭建

3.2 准备一台新的机器

  • ip地址: 192.168.121.143
  • 主机名: hadoop04

3.2.1 查看新机器的ip

查看新机器的ip

3.2.2 修改主机名和host映射

[root@appbasic ~]# vim /etc/hostname
[root@appbasic ~]# cat /etc/hostname
hadoop04
[root@appbasic ~]# vim /etc/hosts
[root@appbasic ~]# cat /etc/hosts
192.168.121.140 hadoop01
192.168.121.141 hadoop02
192.168.121.142 hadoop03
192.168.121.143 hadoop04
[root@appbasic ~]#

3.2.3 配置时间同步

hadoop
集群中的各个机器之间的时间最好都保持一致

[root@hadoop04 ~]# ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
[root@hadoop04 ~]# yum install ntp
已加载插件:fastestmirror
Loading mirror speeds from cached hostfile
base                                                                                                                                                                                           | 3.6 kB  00:00:00
extras                                                                                                                                                                                         | 2.9 kB  00:00:00
updates                                                                                                                                                                                        | 2.9 kB  00:00:00
(1/2): extras/7/aarch64/primary_db                                                                                                                                                             | 252 kB  00:00:00
(2/2): updates/7/aarch64/primary_db                                                                                                                                                            | 3.5 MB  00:00:03
软件包 ntp-4.2.6p5-29.el7.centos.2.aarch64 已安装并且是最新版本
无须任何处理
[root@hadoop04 ~]# systemctl enable ntpd
[root@hadoop04 ~]# service ntpd restart
Redirecting to /bin/systemctl restart ntpd.service
[root@hadoop04 ~]# ntpdate asia.pool.ntp.org
29 Mar 21:42:52 ntpdate[1697]: the NTP socket is in use, exiting
[root@hadoop04 ~]# /sbin/hwclock --systohc
[root@hadoop04 ~]# timedatectl
      Local time: 三 2023-03-29 21:43:03 CST
  Universal time: 三 2023-03-29 13:43:03 UTC
        RTC time: 三 2023-03-29 13:43:03
       Time zone: Asia/Shanghai (CST, +0800)
     NTP enabled: yes
NTP synchronized: no
 RTC in local TZ: no
      DST active: n/a
[root@hadoop04 ~]# timedatectl set-ntp true
[root@hadoop04 ~]#

3.2.4 关闭防火墙

[root@hadoop04 ~]# systemctl stop firewalld
systemctl stop firewalld
[root@hadoop04 ~]# systemctl disable firewalld.service
Removed symlink /etc/systemd/system/multi-user.target.wants/firewalld.service.
Removed symlink /etc/systemd/system/dbus-org.fedoraproject.FirewallD1.service.
[root@hadoop04 ~]#

3.2.5 新建hadoop部署用户

[root@hadoop04 ~]# useradd hadoopdeploy
[root@hadoop04 ~]# passwd hadoopdeploy
更改用户 hadoopdeploy 的密码 。
新的 密码:
无效的密码: 密码包含用户名在某些地方
重新输入新的 密码:
passwd:所有的身份验证令牌已经成功更新。
[root@hadoop04 ~]# vim /etc/sudoers
[root@hadoop04 ~]# cat /etc/sudoers | grep hadoopdeploy -C 3

## Same thing without a password
# %wheel	ALL=(ALL)	NOPASSWD: ALL
hadoopdeploy    ALL=(ALL)       NOPASSWD: ALL

## Allows members of the users group to mount and unmount the
## cdrom as root
[root@hadoop04 ~]#

3.2.6 复制hadoop04机器上的/etc/hosts文件到集群的另外3台机器上

[root@hadoop04 ~]# scp /etc/hosts root@hadoop01:/etc/hosts
[root@hadoop04 ~]# scp /etc/hosts root@hadoop02:/etc/hosts
[root@hadoop04 ~]# scp /etc/hosts root@hadoop03:/etc/hosts

3.2.7 配置集群间的免密登录

此处配置 namenode(hadoop01)

hadoop04
之间的免密登录。

[hadoopdeploy@hadoop01 ~]$ ssh-copy-id hadoop04
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/home/hadoopdeploy/.ssh/id_rsa.pub"
The authenticity of host 'hadoop04 (192.168.121.143)' can't be established.
ECDSA key fingerprint is SHA256:4GL0zHVCdSl3czA0wqcuLT60lUljyEq3DqwPFxNwYsE.
ECDSA key fingerprint is MD5:3e:42:a6:50:0d:fb:f0:41:a8:0d:fb:cc:fd:20:2c:c8.
Are you sure you want to continue connecting (yes/no)? yes
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys
hadoopdeploy@hadoop04's password:

Number of key(s) added: 1

Now try logging into the machine, with:   "ssh 'hadoop04'"
and check to make sure that only the key(s) you wanted were added.

[hadoopdeploy@hadoop01 ~]$

3.2.8将 namenode上的hadoop复制到hadoop04上

[root@hadoop04 ~]# sudo mkdir /opt/bigdata
mkdir: cannot create directory ‘/opt/bigdata’: No such file or directory
[root@hadoop04 ~]# sudo mkdir -p /opt/bigdata
[root@hadoop04 ~]# sudo chown -R hadoopdeploy:hadoopdeploy /opt/bigdata/
[root@hadoop04 ~]# su - hadoopdeploy
Last login: Wed Mar 29 22:19:54 CST 2023 on pts/0
[hadoopdeploy@hadoop04 ~]$ scp -r  hadoopdeploy@hadoop01:/opt/bigdata/hadoop-3.3.4/ /opt/bigdata/
[hadoopdeploy@hadoop04 hadoop]$ rm -rvf /opt/bigdata/hadoop-3.3.4/data/* rm -rvf /opt/bigdata/hadoop-3.3.4/logs/*

注意
目录的创建用户、执行scp命令的用户

注意:
如果
hadoop-3.3.4
目录下存在我们之前配置的数据目录,则需要删除,否则启动这个节点的时候会有问题。日志目录也相应的删除

3.2.9 配置 workers文件

配置这个文件是为了方便集群的一键启动。

[hadoopdeploy@hadoop04 hadoop]$ vim /opt/bigdata/hadoop-3.3.4/etc/hadoop/workers
[hadoopdeploy@hadoop04 hadoop]$ cat /opt/bigdata/hadoop-3.3.4/etc/hadoop/workers
hadoop01
hadoop02
hadoop03
hadoop04
[hadoopdeploy@hadoop04 hadoop]$

注意:
将这个
workers
文件分发到集群的各个机器上。

[hadoopdeploy@hadoop04 hadoop]$ scp /opt/bigdata/hadoop-3.3.4/etc/hadoop/workers hadoopdeploy@hadoop01:/opt/bigdata/hadoop-3.3.4/etc/hadoop/workers
[hadoopdeploy@hadoop04 hadoop]$ scp /opt/bigdata/hadoop-3.3.4/etc/hadoop/workers hadoopdeploy@hadoop02:/opt/bigdata/hadoop-3.3.4/etc/hadoop/workers
[hadoopdeploy@hadoop04 hadoop]$ scp /opt/bigdata/hadoop-3.3.4/etc/hadoop/workers hadoopdeploy@hadoop03:/opt/bigdata/hadoop-3.3.4/etc/hadoop/workers

3.2.10 配置环境变量

配置环境变量
[hadoopdeploy@hadoop04 hadoop]$ source /etc/profile

3.2.11 查看之前集群的节点

查看之前集群的节点

3.3 启动新加入的namenode

[hadoopdeploy@hadoop04 logs]$ hdfs --daemon start datanode
[hadoopdeploy@hadoop04 logs]$ jps
2278 DataNode
2349 Jps
[hadoopdeploy@hadoop04 logs]$

启动新加入的namenode
注意:
如果我们不想让任意一台机器随便就加入到了我们的集群中,那么我们通过
白名单
来控制。

3.3.1 节点之间的数据平衡

新加入的节点磁盘空间比较大,这个时候我们就可以将别的节点的数据均衡到这个节点中来。

# 设置数据传输带宽
[hadoopdeploy@hadoop04 logs]$ hdfs dfsadmin  -setBalancerBandwidth 10485760
Balancer bandwidth is set to 10485760
# 执行banalce
[hadoopdeploy@hadoop04 logs]$ hdfs balancer -policy datanode -threshold 5

3.4 集群节点下线

3.4.1 编辑dfs.hosts.exclude配置指定的文件

注意:
只需要在
NameNode
或者
ResourceManager
上执行即可。

vim hdfs-site.xml

<!-- 配置黑名单,使用黑名单可以下线集群  -->
<property>
    <name>dfs.hosts.exclude</name>
    <value>/opt/bigdata/hadoop-3.3.4/etc/hadoop/blacklist.hosts</value>
</property>

注意:
这个配置文件如果之间没有配置过,则需要重启集群才生效,如果之前配置过,则在
NameNode

ResourceManager
节点上执行
hdfs dfsadmin -refreshNodes
命令即可。

3.4.2 下线节点

编辑blacklist.hosts
文件,加入需要下线的节点。(
NameNode或ResourceManager上操作

[hadoopdeploy@hadoop01 ~]$ cd /opt/bigdata/hadoop-3.3.4/etc/hadoop/
[hadoopdeploy@hadoop01 hadoop]$ vim blacklist.hosts
[hadoopdeploy@hadoop01 hadoop]$ cat blacklist.hosts
hadoop04
[hadoopdeploy@hadoop01 hadoop]$ hdfs dfsadmin -refreshNodes
Refresh nodes successful
[hadoopdeploy@hadoop01 hadoop]$

下线节点
在hdfs集群上可以看到
hadoop04已经下线了
.

节点移除后,可以考虑再次均衡集群中的数据。

注意:
此时可以看到我们的集群中有4台机器,假设我们集群的副本设置为4,那么此时是不可下线节点的,需要修改集群的副本<4。

3.4.3 关闭下线的datanode节点

[hadoopdeploy@hadoop04 logs]$ hdfs --daemon stop datanode
[hadoopdeploy@hadoop04 logs]$

3.4.4 清空黑名单里的内容