2024年10月

服务器开启 FTP

  • 操作系统:Windows
  • 服务器镜像:Windows Server 2022 数据中心版(简体中文)64 位

一、新建用户以及文件夹

步骤 1:新建用户

  1. 打开计算机管理

  2. 打开
    ”系统工具“
    中的
    ”本地用户和组“
    ,右键选择
    ”新建组“

  3. 右键选择
    ”新用户“
    ,设置用户名和密码,并选择
    ”密码永不过期“

  4. 双击新建的用户,在
    ”隶属于“
    一栏添加到刚才新建的用户组
    ”FTP Users“



步骤 2:新建文件夹

  1. 新建一个文件夹作为 FTP 根目录的文件夹。
  2. 右击新建的
    ”FTPShare“
    文件夹,选择属性,打开安全一栏,点击编辑,再点击添加。

  3. 输入之前新建的用户组
    ”FTP Users“
    ,点确定后再点击应用。
  4. 新增成功会多出一项用户组。

二、配置 FTP 站点

步骤 1:安装 FTP 服务器角色

  1. 打开服务器管理器
    :点击任务栏上的“服务器管理器”图标。
  2. 点击右上角的
    “管理”
    ,选择
    “添加角色和功能”

  3. 在“添加角色和功能向导”中,点击
    “下一步”
    ,直到到达“角色”页面。
  4. 勾选
    “Web 服务器(IIS)”
    以及
    FTP 服务器
    相关功能,确保安装以下两项:
    • FTP 服务
    • FTP 扩展


  5. 继续点击“下一步”,然后点击“安装”按钮,等待安装完成。

步骤 2:配置 FTP 站点

  1. 打开
    IIS 管理器
    (可以在开始菜单中搜索 "IIS Manager")。
  2. 在左侧的“连接”窗格中,展开服务器名称,右键点击
    “站点”
    ,选择
    “添加 FTP 站点”

  3. 在“添加 FTP 站点”向导中:
    • 站点名称
      :输入一个描述性的名称,例如“FTP站点”。
    • 物理路径
      :选择作为 FTP 根目录的文件夹。


4. 点击“下一步”。

步骤 3:绑定和 SSL 设置

  1. 在“绑定和 SSL 设置”页面:
    • IP 地址
      :选择"全部未分配"。
    • 端口
      :默认为 21,可以保持不变。
    • SSL 设置
      :选择“不需要 SSL”(在公网访问时建议配置 SSL,初次配置时可以选择不需要 SSL,后期可以配置)。


2. 点击“下一步”。

在配置 FTP 站点时的绑定和 SSL 设置中,应该根据服务器的实际网络环境来选择绑定的 IP 地址。以下是三种选择的建议:

  1. 全部未分配(All Unassigned)

    • 如果您的服务器只有一个公网 IP 地址,并且没有其他需要特殊配置的情况,选择 "全部未分配" 是一个常见的选择。
    • 这种配置将允许服务器监听所有可用的 IP 地址(包括私有和公网 IP),从而适应更多的访问场景。
  2. 服务器的私有 IP 地址

    • 如果您的服务器位于内网,并且通过 NAT 转发端口访问公网(例如通过路由器或防火墙设备),可以选择私有 IP 地址。
    • 在这种情况下,需要在路由器或防火墙上设置端口转发规则,将公网 IP 的 21 端口请求转发到服务器的私有 IP 上。
  3. 服务器的公网 IP 地址

    • 如果服务器直接暴露在互联网上,并且具有固定的公网 IP 地址,建议在绑定时选择服务器的公网 IP。
    • 这样配置后,客户端直接访问公网 IP 地址时会更加准确,并且避免了其他不必要的网络流量干扰。

步骤 4:身份验证和授权

  1. 在“身份验证和授权”页面:
    • 身份验证
      :启用“基本”身份验证。
    • 授权
      :选择”指定角色或用户组“,填入之前新建的用户组”FTP Users“,授予其“读取”或“读取/写入”权限。
  2. 点击“完成”以完成 FTP 站点的设置。

步骤 5:配置 Windows 防火墙

  1. 打开
    Windows Defender 防火墙
    ,点击
    “高级设置”
  2. 在左侧菜单中,点击
    “入站规则”
    ,然后点击右侧的
    “新建规则”
  3. 在规则向导中选择
    “端口”
    ,然后点击“下一步”。
  4. 选择
    “TCP”
    并在“特定本地端口”中输入
    21
    ,然后点击“下一步”。
  5. 允许连接,然后点击“下一步”。
  6. 选择适用的配置文件(域、专用、公用),然后点击“下一步”。
  7. 输入一个描述性名称,例如“FTP 端口 21 规则”,然后点击“完成”。
  8. 配置完结果如下。

步骤 6:被动模式端口范围配置

  1. 打开
    IIS 管理器
  2. 在左侧窗格中,点击服务器名称,选择
    “FTP 防火墙支持”

  3. 在右侧编辑窗口中,配置
    数据通道端口范围
    (例如:1024-65535)。
  4. 设置
    ”防火墙的外部 IP 地 址“
    ,也就是服务器的公网 IP。
  5. 点击
    “应用”
    保存设置。
  6. 然后,在防火墙中打开这些端口的范围(如上面步骤 5 中的操作),以允许这些端口用于被动 FTP 连接。设置完结果如下。

步骤 7:服务器的安全组新增规则

  1. 打开弹性云主机的安全组,点击添加规则,添加最下面两个规则。

其他注意事项

  • 确保服务器已正确配置公网 IP 地址,并且可以通过外部网络访问。
  • 如果服务器和客户端之间有路由器或防火墙设备,请确保相应的端口转发(
    端口 21 和被动端口范围
    )已正确配置。
  • 确保服务器的安全组新增了
    端口 21 和被动端口范围
    的规则。

三、测试

通过公网 IP 测试 FTP 连接

  1. 在本地计算机上,打开 FTP 客户端(例如:FileZilla 或 Xftp 或 Windows 资源管理器)。
  2. 连接到 FTP 服务器,输入以下信息:
    • 服务器地址
      :服务器的公网 IP 地址。
    • 用户名
      :您在 FTP 站点中授权的用户名。
    • 密码
      :用户的密码。
  3. 连接后,应该能够看到服务器上的 FTP 文件夹内容。

在一些小的设备上跑Ubuntu系统,需要快速的开发和调整项目的时候,往往使用SSH进行远程的开发测试,这样可以避免传统的打包更新处理,能够快速的在实际环境上测试具体的内容。另外由于系统设备往往需要重启后能够保留应用的工作,因此也需要在Ubuntu系统设置自动启动的服务处理。本篇随笔介绍在Ubuntu小设备上使用VSCode+SSH开发部署nicegui的Web应用,并设置系统开机自动启动应用。

1、使用VSCode+SSH开发部署nicegui的Web应用

VSCode的功能非常强大,使用扩展来实现更丰富的功能是它的重要特色之一,要使用远程开发,我们需要安装Remote-SSH扩展来实现直接打开服务器端的文件进行开发、编译等。

首先我们在扩展页面中找到微软的 Remote-SSH 扩展插件,给VSCode安装上。

如果你需要使用FTP的一些处理,也可以安装一个SFTP来配合VSCode实现文件的上传下载,不过即使不安装SFTP也没关系。

在VSCode命令面板中打开配置

在配置参数界面中指定Remote-SSH的配置文件,如下所示。

由于我是在Windows上开发,因此配置如下所示。如果是MacOS或其他系统,有所不同。

安装完毕的Remote-SSH,会在左侧的导航中有一个SSH的项目,我们添加自己的服务器的IP和用户名信息,用来连接服务器。

一般格式如下所示,替换为自己的IP和登录系统用户:

Host 192.168.1.103HostName192.168.1.103User root

发起连接到服务器的操作后,会提示输入用户密码:

成功后我们打开服务器的代码目录,如下所示。

然后开发就和本地文件一样了,我们在上面编写代码、编译运行都是基于服务器的环境的,如果缺少模块引用,记得使用pip install进行安装依赖即可,安装也是给服务器环境进行的安装。

对服务器的SSH控制命令处理,我们可以通过VScode中的bash面板打开。

创建命令窗口后如下所示。

我们就可以基于这个进行相关的命令操作了,就和本地命令处理类似了。

VScode上对服务器文件的操作很方便,可以直接拖动本地文件到服务器目录上,也可以从服务器目录上下载代码文件到本地。

2、设置Ubuntu系统开机自动启动应用

有时候,我们在实际环境上部署项目后,都是希望每次重启,相关的应用能自动启动,避免每次手工干预的繁琐。

1) 编写启动脚本

首先我们在我们的项目代码上,创建一个脚本,用于启动服务引用的,如创建文件:start_my_backend.sh

然后填写内容,这个和我们实际运行python项目的命令差不多一样。

#!/bin/bash
# 启动Python后端程序
/usr/bin/python /root/test/src/nicegui/modularization/main.py

确保路径是正确的,并且指向你的 Python 解释器和 Python 脚本文件。

注意文件开头 #!/bin/bash 是标识为Bash命令的特征,如下文件在目录中所示。

给予脚本执行权限

chmod +x /root/test/src/start_my_backend.sh

2)创建 systemd 服务文件

然后再 /etc/systemd/system/ 目录上创建一个服务启动的处理,命名为:my_backend.service

添加下面的内容:

[Unit]
Description
=My Python Backend Service
After
=network.target

[Service]
ExecStart
=/root/test/src/start_my_backend.shRestart=always
User
=root
WorkingDirectory
=/root/test/src
Environment
="PATH=/usr/bin"[Install]
WantedBy
=multi-user.target

解释:

  • ExecStart
    指定了要执行的脚本路径。
  • User
    指定哪个用户运行该服务。
  • Restart=always
    表示当服务崩溃时,会自动重启。
  • WorkingDirectory
    是脚本的工作目录。

保存并退出编辑器(按
Ctrl+O
然后
Ctrl+X
)。

3)启用并启动服务

  1. 重新加载
    systemd
    配置:
sudo systemctl daemon-reload
  1. 启用服务,使其开机启动:
sudo systemctl enable my_backend.service
  1. 立即启动服务:
sudo systemctl start my_backend.service
  1. 检查服务状态,确认它是否正常运行:
sudo systemctl status my_backend.service
  1. 日志查看

如果你想查看服务的运行日志,可以使用以下命令:

journalctl -u my_backend.service

这样,每次系统启动时,
my_backend
服务就会自动启动并运行你的 Python 后端程序了。

最后查看服务器端的启动项目端口是否正常。

这样,从服务器上直接编写代码开发或者调试,并直接自动启动服务器,就实现了我们常规的开发处理了。

1.
背景


qiang~
关注到
OpenAI
两周前发布的轻量级多代理框架
Swarm
,因此想要深入了解了一下,运行了官方提供的例子,整理并总结一些心得体会
~

源码非常简单,各位看官们可以小读一下,本文采用
gpt-4o-mini
进行验证,如果想免费使用
gpt-4o-mini
,可私信沟通。

Ps:
发布之后,便在
X
引起了
Swarm
涉嫌抄袭,但吃瓜的我还是关注技术内部本身。

2.
简介

Swarm
项目是一个轻量级
multi-agent

编排框架,当前主要是为了实验及教学,而非用于生产

Swarm
专注于使
agent
协调和运行变得轻量级、高度可控且易于测试。

有两个主要抽象模块:
Agent

handoffs(
切换
)

Agent
封装了
instructions

tools
,且可以随时选择将对话移交给另一个代理。

3.
适用场景

适用于大量的独立功能和指令难以整合到一个
prompt
的场景,而是将独立功能和指令通过
agent, functions, tools
等方式进行链接。

4.
Swarm
核心模块

4.1 Swarm.run()

类似于
openai

chat completions api
中的
create()
函数,接收并返回
messages
,并在调用之间不保存任何状态。注意,该方法也处理
Agent
函数执行、切换、上下文变量引用,且可以在返回最终输出前进行多轮对话。

其核心实现了如下循环:

(1)
从当前
Agent
调用模型返回结果

(2) 执行工具调用并且保存结果

(3) 必要时切换代理

(4) 必要时更新上下文变量

(5) 如果没有新的函数调用,则返回

该方法执行完毕后,会返回一个
Response
,包含所有相关的更新状态,包括新的
messages,
最后被调用的
agent
以及最新的上下文变量
context_variables

4.2 Agents

Agent
简单封装了一组
instructions

functions
,且具有将执行移交给另一个
agent
的能力。这些
instructions

functions
可用于表示非常具体的工作流程或步骤。

Instructions
将被直接转化为一个对话的
system
提示,任何时候,只会存在活跃
agent

instructions

instructions
可以是普通的字符串,也可以是返回字符串的函数,该函数可以选择一个
context_variables
参数,进而传入到
swarm.run()
中。

Swarm

Agent
可以直接调用
python
函数,通常
function
应返回一个
str
,但也可以是一个
Agent
,如果返回
Agent
的话,则将执行转移到该
Agent
。如果
Agent
的函数调用出现错误,错误响应会添加到聊天中,以便
Agent
可以正常恢复。如果
Agent
调用多个函数,他们将按顺序进行。

4.3
切换
Agent
及更新上下文变量

一个
agent
可以通过在
function
中返回它来将其移交给另一个代理。还可以通过返回更完整的
Result
对象来更新
context_variables

5.
实战

本次实战主要运行项目自带的例子
airline
。这个例子能够很好地说明
Swarm
的运行机理。

5.1
Agent
之间的整体交互流程

交互步骤如下:

  1. 首先经过意图识别
    Agent
    ,判断是处理航班变更还是行李丢失,并分别通过
    transfer_to_flight_modification, transfer_to_lost_baggage
    切换到对应的
    Agent
  2. 如果是航班变更,则进一步判断是航班取消,还是航班更换,如果是航班取消,通过
    transfer_to_flight_cancel
    切换到

    航班取消
    Agent

    进行处理,该
    Agent
    支持退款、兑换飞行积分、向上级反馈、问题已解决、切换到意图识别
    Agent

    5
    个方法;如果是航班更换,通过
    transfer_to_flight_change
    切换到

    航班更换
    Agent

    ,该
    Agent
    支持更换有效性验证、更换成功、向上级反馈、问题已解决、切换到意图识别
    Agent

    5
    个方法。
  3. 如果是行李的相关问题,则切换至”
    行李丢失
    Agent

    ,该
    Agent
    包含行李搜寻、向上级反馈、问题已解决、切换到意图识别
    Agent

    4
    个方法

5.2
实战效果

其中
User
是我的输出

[2024...]
是打印的日志,
Triage Agent

Flight Modification Agent

Flight cancel traversal
是对应的
Agent
或方法

调用大模型后返回的结果。

Starting Swarm CLI
��

User:
我想要退票

[2024-10-22 09:38:14] Ending turn.

Triage Agent:
请问您能提供一下您的航班号以及您希望退票的原因吗?

User:
航班号是
1919
,我的时间有些冲突了

[2024-10-22 09:38:48] Processing tool call: transfer_to_flight_modification with arguments {}

[2024-10-22 09:38:49] Processing tool call: transfer_to_flight_cancel with arguments {}

[2024-10-22 09:38:50] Ending turn.

Triage Agent: transfer_to_flight_modification()

Flight Modification Agent: transfer_to_flight_cancel()

Flight cancel traversal:
您提到的航班号是
1919
。请确认您是否确实是要取消这个航班。如果是的话,我将继续进行处理。

User:
是的

[2024-10-22 09:38:57] Ending turn.

Flight cancel traversal:
请问您是希望申请退款还是希望获得航班积分?

User:
退款吧

[2024-10-22 09:39:05] Processing tool call: initiate_refund with arguments {}

[2024-10-22 09:39:06] Ending turn.

Flight cancel traversal: initiate_refund()

Flight cancel traversal:
您的退款请求已被处理,退款将在
3-5
个工作日内完成。请您耐心等待。请问您还有其他问题吗?

User:
没有了,谢谢

[2024-10-22 09:39:16] Processing tool call: case_resolved with arguments {}

[2024-10-22 09:39:20] Ending turn.

Flight cancel traversal: case_resolved()

Flight cancel traversal:
感谢您的联系!如果将来有任何问题,请随时与我们联系。祝您有美好的一天!

User:

6.
总结

一句话足矣
~

本文主要是
OpenAI
开源
multi-agent
框架
swarm
进行介绍,包括其核心组件
Agent, instructions,functions
,多个
Agent
之间如何进行切换,以及针对其中的
examples
进行验证,框架及源码非常简单,看官们可以亲自尝试一下。

Multi-agent
的适用场景非常丰富,比如客服、工作流处理等

如果想免费获取使用
openai
的看官,可私信沟通。

7.
参考

(1)
Swarm
仓库
: https://github.com/openai/swarm

0 导引

最近有几个网友希望我续写《iceoryx源码阅读》的剩余部分,所以还是继续写,供大家参考。

本文主要介绍正常的消息接收流程,从流程本身而言,这部分代码逻辑比较简单。为了内容充实,我们对代码做了更深入地解读,结合对前面几篇文章的内容,从代码角度进行分析。

1 正常的消息接收流程

正常的消息接收流程是指接收端调用接收函数,从共享消息队列中获取消息所在位置的描述信息,然后根据这一描述信息,从共享内存读取数据的过程。下面,我们iceoryx向应用层提供的接收消息的函数开始介绍,逐步深入。

1.1 SubscriberImpl::take

职责:
应用层通过SubscriberImpl::take函数接收数据。从逻辑上来说,这个函数通过更底层的方法获取到
ChunkHeader
指针(我们在iceoryx源码阅读(二)中已经介绍这一数据结构),进而获取到用户负载数据,据此得到用户描述消息所用的结构体。

模板参数:

  • T:
    消息体类型。
  • H:
    消息头类型。
  • BaseSubscriberType:
    基类类型,不明白为什么iceoryx常常把基类也定义为范型,范型用的太范了。

入参:

返回值:

返回值的类型为
cxx::expected<Sample<const T, const H>, ChunkReceiveResult>
,包含两部分:

  • 正常情况的返回值类型,即:
    Sample<const T, const H>
    ,这个类型是对用户负载数据的封装类,以便于更方便地读取其中的数据。

  • 错误情况的返回值类型,即:ChunkReceiveResult,其类型定义如下:

enum class ChunkReceiveResult
{
    TOO_MANY_CHUNKS_HELD_IN_PARALLEL,
    NO_CHUNK_AVAILABLE
};

说明了错误原因。

以下是这个函数的代码清单:

template <typename T, typename H, typename BaseSubscriberType>
inline cxx::expected<Sample<const T, const H>, ChunkReceiveResult>
SubscriberImpl<T, H, BaseSubscriberType>::take() noexcept
{
    auto result = BaseSubscriberType::takeChunk();
    if (result.has_error())
    {
        return cxx::error<ChunkReceiveResult>(result.get_error());
    }
    auto userPayloadPtr = static_cast<const T*>(result.value()->userPayload());
    auto samplePtr = iox::unique_ptr<const T>(userPayloadPtr, [this](const T* userPayload) {
        auto* chunkHeader = iox::mepoo::ChunkHeader::fromUserPayload(userPayload);
        this->port().releaseChunk(chunkHeader);
    });
    return cxx::success<Sample<const T, const H>>(std::move(samplePtr));
}

逐段代码分析:

  • LINE 05 ~ LINE 09:
    调用基类BaseSubscriberType的takeChunk,即:BaseSubscriber的takeChunk方法,获取指向ChunkHeader的指针,关于ChunkHeader,请参考
    iceoryx源码阅读(二)第4节

  • LINE 10 ~ LINE 15:
    根据指向ChunkHeader的指针,获取指向用户数据的指针,并据此构造便于应用层使用的
    Sample<const T, const H>
    实例并返回。

1.2 BaseSubscriber<port_t>::takeChunk

职责:

模板类
BaseSubscriber<port_t>
中,包含了类型为
port_t
的成员
m_port
,这就是端口数据结构,iceoryx的端口中封装了队列数据结构,具体见
iceoryx源码阅读(四)第1节
。对于订阅者类,
port_t
实际上就是
SubscriberPortUser

入参:

返回值:
cxx::expected<const mepoo::ChunkHeader*, ChunkReceiveResult>

  • 正常情况下返回值类型为
    const mepoo::ChunkHeader*
    ,即指向
    ChunkHeader
    的指针。

  • 错误情况下返回值类型为
    ChunkReceiveResult
    ,用以说明获取失败的原因。

整体代码分析:

template <typename port_t>
inline cxx::expected<const mepoo::ChunkHeader*, ChunkReceiveResult> BaseSubscriber<port_t>::takeChunk() noexcept
{
    return m_port.tryGetChunk();
}

这个函数只是调用
m_port
(这里是枚举类型
port_t
,特化类型为
SubscriberPortUser
)的
SubscriberPortUser::tryGetChunk
方法。

1.3 SubscriberPortUser::tryGetChunk

这个函数的实现代码如下:

cxx::expected<const mepoo::ChunkHeader*, ChunkReceiveResult> SubscriberPortUser::tryGetChunk() noexcept
{
    return m_chunkReceiver.tryGet();
}

同样是直接调用了
m_chunkReceiver
,其类型为
ChunkReceiver<SubscriberPortData::ChunkReceiverData_t>

tryGet
方法,这里的范型参数
SubscriberPortData::ChunkReceiverData_t
通过追溯至
SubscriberPortData
,位于iceoryx_posh/include/iceoryx_posh/internal/popo/ports/subscriber_port_data.hpp:64,发现其为
iox::popo::SubscriberChunkReceiverData_t
类型:

using ChunkReceiverData_t = iox::popo::SubscriberChunkReceiverData_t;


SubscriberChunkReceiverData_t
又是一个类型别名,定义于iceoryx_posh/include/iceoryx_posh/internal/popo/ports/pub_sub_port_types.hpp::

using SubscriberChunkReceiverData_t =
    ChunkReceiverData<MAX_CHUNKS_HELD_PER_SUBSCRIBER_SIMULTANEOUSLY, SubscriberChunkQueueData_t>;

ChunkReceiverData
类也是一个范型类,它的基类是第二个范型参数,在上述代码中就是
SubscriberChunkQueueData_t
,为类型别名,实际类型如下:

using SubscriberChunkQueueData_t = ChunkQueueData<DefaultChunkQueueConfig, ThreadSafePolicy>;

这说明
ChunkReceiverData
范型类继承自
ChunkQueueData
范型类,这个类是通信队列数据机构,我们在
《iceoryx源码阅读(四)——共享内存通信(二)》
介绍过。

ChunkReceiverData
类中(不包括基类)定义了唯一一个成员
m_chunksInUse
,具体如下:

static constexpr uint32_t MAX_CHUNKS_IN_USE = MaxChunksHeldSimultaneously + 1U;
UsedChunkList<MAX_CHUNKS_IN_USE> m_chunksInUse;

这一成员用于缓存接收到的
SharedChunk
,原因下一小节将会说明。需要指出的是,
UsedChunkList
虽然类型名为
List
,但其本质上是一个数组,具体可以参考iceoryx_posh/include/iceoryx_posh/internal/popo/used_chunk_list.hpp:79-82中的数据成员:

uint32_t m_usedListHead{INVALID_INDEX};
uint32_t m_freeListHead{0u};
uint32_t m_listIndices[Capacity];
DataElement_t m_listData[Capacity];

下面来简单分析一下
ChunkReceiver
的继承结构,
ChunkReceiver<SubscriberPortData::ChunkReceiverData_t>
继承自
ChunkQueuePopper<typename ChunkReceiverDataType::ChunkQueueData_t>
,顾名思义,这个类是从通信队列获取元素的,是对
ChunkQueueData
的封装。原因如下:这里范型类型
ChunkReceiverData_t
,即:
ChunkReceiverData
中定义了一个类型别名:

using ChunkQueueData_t = ChunkQueueDataType;

这里的
ChunkQueueDataType
其实就是上文中的
ChunkQueueData
,通过它可以访问消息队列
m_queue
。下面,我们介绍
ChunkReceiver<ChunkReceiverDataType>::tryGet
的具体实现,进一步加深对上述数据结构的理解。

1.4 ChunkReceiver
::tryGet

ChunkReceiver<ChunkReceiverDataType>
为范型类,范型参数

职责:

调用基类的成员方法,取出接收队列中的一个元素,函数返回类型为
SharedChunk
,我们在
《iceoryx源码阅读(二)——共享内存管理》
中已经介绍过了(现在看来介绍得有点粗糙 :-)。

入参:

返回值:
cxx::expected<const mepoo::ChunkHeader*, ChunkReceiveResult>

  • 正常情况下返回值类型为
    const mepoo::ChunkHeader*
    ,即指向
    ChunkHeader
    的指针。

  • 错误情况下返回值类型为
    ChunkReceiveResult
    ,用以说明获取失败的原因。

逐段代码分析:

template <typename ChunkReceiverDataType>
inline cxx::expected<const mepoo::ChunkHeader*, ChunkReceiveResult>
ChunkReceiver<ChunkReceiverDataType>::tryGet() noexcept
{
    auto popRet = this->tryPop();

    if (popRet.has_value())
    {
        auto sharedChunk = *popRet;

        // if the application holds too many chunks, don't provide more
        if (getMembers()->m_chunksInUse.insert(sharedChunk))
        {
            return cxx::success<const mepoo::ChunkHeader*>(
                const_cast<const mepoo::ChunkHeader*>(sharedChunk.getChunkHeader()));
        }
        else
        {
            // release the chunk
            sharedChunk = nullptr;
            return cxx::error<ChunkReceiveResult>(ChunkReceiveResult::TOO_MANY_CHUNKS_HELD_IN_PARALLEL);
        }
    }
    return cxx::error<ChunkReceiveResult>(ChunkReceiveResult::NO_CHUNK_AVAILABLE);
}

LINE 05 ~ LINE 05:
调用基类(即:上一小节介绍的
ChunkQueuePopper<typename ChunkReceiverDataType::ChunkQueueData_t>
)成员函数
tryPop

ChunkQueueData
维护的消息队列中弹出一个消息元素,返回值为
SharedChunk
,下一小节详细介绍这个函数的具体实现。

LINE 07 ~ LINE 23:
从消息队列中成功获取消息,将其存入缓存数组中,目的是:(1)后续根据
ChunkHeader *
指针获取对应的
SharedChunk
;(2)若不存起来,离开这个函数,
SharedChunk
实例被析沟,意味着共享内存被释放。有人会问为什么不直接返回
SharedChunk
呢?其实也是可以的,但还是需要有个地方缓存
SharedChunk
实例的,直到这块共享内存不再被需要。首次获得就缓存起来是一种更好的方式。

LINE 24 ~ LINE 24:
若缓存数组已满,则释放
SharedChunk
,即:释放了共享内存,并返回错误
ChunkReceiveResult::TOO_MANY_CHUNKS_HELD_IN_PARALLEL

1.5 ChunkQueuePopper::tryPop

职责:
从消息队列
m_queue
(存放于共享内存中,见:
iceoryx源码阅读(四)——共享内存通信(二)
)获取消息描述数据,并将其转化为
SharedChunk
实例返回。

入参:

返回值:
cxx::optional<mepoo::SharedChunk>

这里不需要说明错误原因等额外信息,所以不像前一个函数那样,使用expect作为返回类型。

  • 正常情况下返回值类型为
    mepoo::SharedChunk

  • 错误情况下返回特殊值
    nullopt_t

逐段代码分析:

template <typename ChunkQueueDataType>
inline cxx::optional<mepoo::SharedChunk> ChunkQueuePopper<ChunkQueueDataType>::tryPop() noexcept
{
    auto retVal = getMembers()->m_queue.pop();

    // check if queue had an element that was poped and return if so
    if (retVal.has_value())
    {
        auto chunk = retVal.value().releaseToSharedChunk();

        auto receivedChunkHeaderVersion = chunk.getChunkHeader()->chunkHeaderVersion();
        if (receivedChunkHeaderVersion != mepoo::ChunkHeader::CHUNK_HEADER_VERSION)
        {
            LogError() << "Received chunk with CHUNK_HEADER_VERSION '" << receivedChunkHeaderVersion
                       << "' but expected '" << mepoo::ChunkHeader::CHUNK_HEADER_VERSION << "'! Dropping chunk!";
            errorHandler(PoshError::POPO__CHUNK_QUEUE_POPPER_CHUNK_WITH_INCOMPATIBLE_CHUNK_HEADER_VERSION,
                         ErrorLevel::SEVERE);
            return cxx::nullopt_t();
        }
        return cxx::make_optional<mepoo::SharedChunk>(chunk);
    }
    else
    {
        return cxx::nullopt_t();
    }
}

LINE 04 ~ LINE 04:
从共享内存的消息队列
m_queue
中获取
ShmSafeUnmanagedChunk
元素(请参考
《iceoryx源码阅读(三)——共享内存通信(一)》
)。

LINE 07 ~ LINE 21:
获取成功

通过
ShmSafeUnmanagedChunk
的成员方法
releaseToSharedChunk
找到对应的共享内存区域,据此创建
SharedChunk
实例,并返回。注意,LINE 11 ~ LINE 19做了版本检查,如果和接收端版本不一致,同样认为获取失败。版本号是一个整数,用于标识某种不兼容的修改,iceory有如下解释:

/// @brief From the 1.0 release onward, this must be incremented for each incompatible change, e.g.
///            - data width of members changes
///            - members are rearranged
///            - semantic meaning of a member changes
static constexpr uint8_t CHUNK_HEADER_VERSION{1U};

LINE 22 ~ LINE 25:
获取失败

直接返回特殊值
cxx::nullopt_t()
,这里会隐式调用
optional
的如下构造函数:

optional(const nullopt_t) noexcept;

2 回调函数接收机制

如果接收端先启动,此时共享内存队列中为空,此时会返回错误
ChunkReceiveResult::NO_CHUNK_AVAILABLE
,如果接收端一直轮询,则会浪费性能。是否可以在发送端通知的机制实现消息的异步监听和处理?答案是肯定的,我们将在《iceoryx源码阅读(九)——等待与通知机制》对消息发送端的通知逻辑和接收端的等待逻辑进行深入的介绍。

3 小结

本文主要介绍了正常的消息接收流程,对于异步回调的消息接收流程将在后续文章中进行介绍。

国产数据库的崛起与挑战:谁将在激烈市场洗牌中脱颖而出

数据

先看一下当前企业数据

截至最新的公开数据,中国的企业数量总量非常庞大。

截至2022年底,民营企业已超过
4500万家
,在各类企业中占比极大。

截至2022年底,国有企业约有
15万家
,其中中央企业有97家。

目前国情

在数据库领域,国家的政策推动与市场需求紧密结合。

  • 一方面,政府和大型国企对数据库有着广泛的采购需求,国产数据库在这些领域的应用获得了显著推进。当然这里面也包含了上面的压力,各个地方的国资委的硬推要求,使政府和国企成为XC的主力军。
  • 另一方面,随着民营企业的崛起,它们对降本增效、灵活部署的需求愈发强烈,特别是在选择数据库系统上,越来越倾向于高性价比甚至开源的方案。

现状分析

目前国产数据库需要维持市场份额,需要做到以下几点

  • 民营企业大量采用

目前国产数据库的主要用户群体仍然是政府部门和大型国有企业,但要真正实现国产数据库的成功,关键在于能否大规模进入民营企业市场。民营企业通常更关注成本、灵活性以及技术支持的可靠性,因此它们更倾向于选择开源数据库系统,如
MySQL

PostgreSQL
等,这为国产数据库带来了巨大的挑战。

另外,国产数据库厂商也在积极推广具有竞争力的解决方案,致力于帮助民营企业降本增效。比如,国产数据库企业某Base、某SQL 等,正在通过推出灵活的社区版、开源版、个人开发版等版本,吸引中小企业用户,尤其是那些对数据处理性能要求较高、预算有限的企业。通过开源生态策略反哺和围攻政企市场。

  • 国际化拓展

国产数据库要走出国门并在国际市场立足,是目前国产数据库厂商面临的一大挑战。虽然国产数据库在技术上有了长足进步,但在全球范围内,仍面临着与老牌国际巨头
(如Oracle、SQL Server、Amazon Aurora等)
的激烈竞争。
要成功“出海”,国产数据库需要在性能、可靠性、生态兼容性上做到世界一流水平。

  • 大企业的背书

国产数据库的成功在很大程度上离不开大企业的支持。
比如,腾讯的TDSQL、阿里的PolarDB、华为的GaussDB,这些产品背后都有大型企业的技术、资金和生态支持。这样的支持不仅仅帮助数据库厂商在技术上快速迭代,还在市场拓展上起到了重要的推动作用。

  • 资金实力雄厚

雄厚的资雄厚的资金实力是国产数据库企业成功的另一关键因素。没有充足的资金支持,国产数据库企业难以在竞争激烈的市场中生存和发展。
即便是上市公司北京优炫软件,也因经营压力和战略调整,前段时间出售其全资子公司优炫软件(拳头产品:优炫数据库,英文名称:UXDB)100%的股权,以化解风险。
实际上,国产数据库厂商提桶跑路的新闻也是屡见不鲜,前有贵州易鲸捷,后有北京优炫,特别是小型数据库厂商更加容易暴雷。
这反映出就连资金相对充裕的上市公司,资金压力仍然是不可忽视的问题。

来源出处:https://www.modb.pro/db/1844565979135836160
来源出处:https://baijiahao.baidu.com/s?id=1812307184969436235&wfr=spider&for=pc

谁能胜出?

结合当前的中国国情和市场趋势,国产数据库未来的竞争格局将逐步清晰。

总体来看,能够胜出的企业需要具备以下几个关键特征:

  • 技术实力强劲:必须具备足够的技术积累,能够在高并发、分布式、云原生等前沿领域具备领先优势。

  • 适应民营企业需求:提供更灵活、开源、低成本的解决方案,吸引中小型民营企业成为客户,拓展更广泛的用户基础。

  • 国际化能力:不仅仅依赖国内市场,还要具备国际化的能力,能够在海外市场竞争并获得收入。

  • 生态建设:背后有大企业的资源支持,能够快速构建完善的技术生态和商业生态,帮助数据库产品在各个行业内拓展。

总结

国产数据库的未来在于是否能够真正被市场广泛接受,尤其是民营企业的采用情况,以及在国际市场的竞争力。结合中国的国情和当前的市场环境,能够适应民营企业需求并具备国际化拓展能力的数据库厂商,将有望在激烈的竞争中脱颖而出。

未来3-5年,国内的数据库厂商肯定会经历一次大规模的市场洗牌, 无论是在oltp、olap、htap领域,预计最终能存活下来的企业数量可能不到10家。大家觉得谁有希望笑到最后? 附上墨天轮中国数据库排行榜9月份数据作为参考,只列举前20名

参考文章

https://www.51cto.com/article/721008.html
https://pigsty.io/zh/blog/db/db-china/
https://www.modb.pro/db/24797?yll
https://asktug.com/t/topic/1021354
https://cloud.tencent.com/developer/article/2146305
http://www.ce.cn/xwzx/gnsz/gdxw/202311/14/t20231114_38791512.shtml
https://www.acfic.org.cn/rdst/202311/t20231115_197642.html

本文版权归作者所有,未经作者同意不得转载。