Harmony Next 使用 L2TP IPSec PSK VPN
之前在我的 用 Docker 快速部署 PPTP VPN 和 L2TP + IPSEC VPN 一文中,我介绍了如何使用 Docker k快速署 L2TP IPSec VPN,但是在纯血鸿蒙下一直无法成功。
发现是因为无法匹配通讯加密方法。
进入容器,如下修改一下 /etc/ipsec.conf 文件的通讯加密方法,
1 | conn l2tp-psk |
之后重启服务即可,service ipsec restart。
之前在我的 用 Docker 快速部署 PPTP VPN 和 L2TP + IPSEC VPN 一文中,我介绍了如何使用 Docker k快速署 L2TP IPSec VPN,但是在纯血鸿蒙下一直无法成功。
发现是因为无法匹配通讯加密方法。
进入容器,如下修改一下 /etc/ipsec.conf 文件的通讯加密方法,
1 | conn l2tp-psk |
之后重启服务即可,service ipsec restart。
手里有一台 dzire 的廉价独服,配置如下:
| 项目 | 参数 |
|---|---|
| CPU | Intel Xeon E31270(8 核) |
| 内存 | 16GB DDR3 |
| 磁盘 | 机械硬盘(HDD) |
| 用途 | Hivemind 同步 Steem 区块数据 |
Hivemind 是 Steem 链的社交层索引器,通过 Docker Compose 部署,其中 PostgreSQL 数据库承载了大量的写入和查询操作。同步一段时间后,数据库体积达到了 431GB,其中最大的几张表:
| 表名 | 大小 |
|---|---|
| hive_posts_cache | 216GB |
| hive_trxid_block_num | 110GB |
| 其他表 | ~105GB |
问题来了:同步进程始终落后链头几百个区块,追不上。
top 和 htop 里最显眼的指标是 **IO Wait 持续 25%~35%**,CPU 大量时间在等磁盘。
用 iostat 和 vmstat 进一步确认:
1 | # iostat -x 1 观察 |
关键信号:
结论:磁盘 IO 是瓶颈,毫无疑问。
在 SSD 上这些问题可能根本不会出现,但在 HDD 上,PostgreSQL 默认配置的检查点频率、WAL 刷写策略会导致大量小随机写,对机械硬盘来说是灾难性的。
核心策略:减少磁盘写频率,增大批量写入,降低 fsync 开销。
具体来说:
shared_buffers**:让更多热数据留在内存,减少磁盘读取max_wal_size + **延长 checkpoint_timeout**:减少检查点频率,让每次检查点写出更多数据(顺序写更友好)wal_writer_delay、wal_writer_flush_after 调大,减少 fsync 次数full_page_writes:HDD 上这个特性会导致每次检查点后首次修改页写出完整页,极大放大写入量。注意:这依赖文件系统支持原子写(如 ZFS),否则断电有数据损坏风险。 本场景是同步数据,可以接受这个风险。⚠️ 警告:第二轮激进配置的前提是——这台机器只做同步,不对外提供服务。如果你在生产环境中使用,请谨慎评估每一个参数。
初步调整,先把 PostgreSQL 的默认参数改到合理范围:
| 参数 | 默认值 | 调整后 |
|---|---|---|
| shared_buffers | 128MB(或 4GB) | 6GB |
| max_wal_size | 1GB(或 2GB) | 4GB |
| checkpoint_timeout | 5min(或 15min) | 30min |
这一轮调完后,IO Wait 有所下降但仍然不够,同步仍然落后。
因为这台机器的唯一任务就是追赶同步,不需要担心崩溃恢复后的数据一致性(大不了重新同步),于是上了激进方案:
| 参数 | 第一轮值 | 激进值 | 说明 |
|---|---|---|---|
| shared_buffers | 6GB | 8GB | 占总内存 50% |
| max_wal_size | 4GB | 8GB | 允许 WAL 累积更多再检查点 |
| checkpoint_timeout | 30min | 1h | 每小时才做一次检查点 |
| wal_writer_delay | 200ms | 2s | WAL writer 每 2 秒才醒来一次 |
| wal_writer_flush_after | 1MB | 16MB | 累积 16MB 才 fsync |
| bgwriter_delay | 200ms | 5s | 后台写出进程降低唤醒频率 |
| bgwriter_lru_maxpages | 100 | 5000 | 每次醒来多写出一些脏页 |
| bgwriter_lru_multiplier | 2.0 | 10.0 | 更激进地预写出 |
| full_page_writes | on | off | 关闭全页写,大幅减少 IO |
1 | # ============================================================= |
几个值得解释的点:
random_page_cost = 4.0**:HDD 的随机访问代价远高于 SSD(SSD 通常设 1.1),告诉规划器尽量走顺序扫描或索引扫描。effective_io_concurrency = 2**:HDD 的并发 IO 能力有限,设高了没意义。autovacuum_naptime = 30s**:Hivemind 同步产生大量 UPDATE/INSERT,频繁 vacuum 有助于避免表膨胀。full_page_writes = off**:这个最激进。正常情况下 PostgreSQL 在检查点后首次修改一页时会写出完整页(防止部分写撕裂)。关闭后每次只写差异,大幅减少写入量。如果你的文件系统是 ZFS 或使用了带电池保护的 RAID 控制器,风险可控。PostgreSQL 配置文件放在 docker-compose.yml 同目录下,通过 volume 挂载:
1 | services: |
注意 shm_size: '2gb' 不能省——PostgreSQL 的 shared_buffers 依赖共享内存,默认的 64MB 远远不够。
| 指标 | 默认配置 | 第一轮(保守) | 第二轮(激进) |
|---|---|---|---|
| IO Wait | 25~35% | ~22% | ~15% |
| 同步速度 | 落后链头数百块 | 略有改善 | 稳定追赶,差距持续缩小 |
| 检查点写入突发 | 频繁且剧烈 | 减少但仍有突发 | 平滑,几乎无感 |
IO Wait 从 25~35% 降到 15%,看似降幅不大,但对于 HDD 来说这已经是巨大的改善。关键改善在于写入模式——从大量小随机写变成了少量大批量顺序写,这正是机械硬盘最擅长的工作模式。
iostat、vmstat、pg_stat_bgwriter 定位瓶颈,不要盲目改参数full_page_writes = off 在断电时有数据损坏风险,生产环境慎用shared_buffers 给到总内存的 50%,shm_size 别忘了调大如果你的独服也是 HDD,且在跑重 IO 的数据库同步任务,希望这篇能帮到你。
日常开发中,本地工作机的磁盘空间和 CPU 资源往往比较紧张,尤其是在编译大型 Docker 镜像时,构建缓存和中间层会占用大量磁盘空间。与此同时,家里可能有一台配置更强的服务器,磁盘充裕但平时处于闲置状态。
有没有一种优雅的方式,能让本地开发机把 Docker 构建任务分发到远程服务器上执行,同时又不影响本地开发体验?
答案是 Docker Context + Buildx Remote Builder。
Docker CLI 原生支持通过 SSH 连接远程的 Docker daemon。配合 buildx,可以创建一个远程 builder,所有 docker buildx build 命令会自动通过 SSH 把构建任务发送到远程机器上执行。
1 | 本地工作机 远程服务器 |
整个过程中,源码通过 SSH 上下文传递给远程 BuildKit,构建在远程服务器上执行,构建缓存也累积在远程机器上。本地工作机只负责发送构建指令和接收构建日志。
1 | docker context create remote --docker "host=ssh://user@remote-server" |
这样 docker -c remote ps 就等价于 ssh user@remote-server docker ps,Docker CLI 自动走 SSH 隧道。
如果已经在
~/.ssh/config中配置了主机别名(比如Host remote),直接写ssh://remote即可。
1 | docker buildx create --name remote-builder remote --use |
这会在远程服务器上启动一个 BuildKit 容器,所有 docker buildx build 都会走这个 builder。
首次创建时需要拉取 moby/buildkit 镜像,取决于网络情况可能需要一些时间。
在项目目录下执行:
1 | # 构建镜像(镜像存在远程服务器上) |
1 | docker buildx use default |
默认情况下,docker buildx build 构建出来的镜像存在于远程服务器上,本地工作机的 docker images 是看不到的。
如果需要在本地运行该镜像,需要加 --load 参数,但这会把镜像通过 SSH 拉回本地,占用本地磁盘空间。更推荐的做法是加 --push 直接推送到 registry。
首次创建 builder 时,远程服务器需要拉取 moby/buildkit 镜像。如果通过跳板机连接,网络延迟较高,拉取可能需要几分钟。耐心等一下就好。
远程 builder 有自己的 BuildKit 缓存,同一个项目第二次构建时会命中缓存,速度明显更快。不同项目的缓存互相隔离,不会互相干扰。
这个方案不需要修改 Dockerfile,不需要改 CI/CD 配置。只需要在构建时指定用哪个 builder,其他一切照旧。
通过 Docker Context + Buildx,可以零成本地把重型构建任务卸载到闲置的远程服务器上,释放本地工作机的磁盘和 CPU 资源。对于本地磁盘紧张但又需要频繁构建大型镜像的场景,这是一个非常优雅的解决方案。
hclient-cli 是官方推出的一个面向无图形界面的机器的接入方案。
有这个工具的情况下,我们可以让我们远端的服务器接入到懒猫的网络中,访问懒猫上的数据资源。
官方库文档有接口的使用说明,这里就不再阐述,我提交了一个 PR,对官方的接口简单的用 bash 脚本封装了一下,减少使用过程中的输入量。同时还包含了一个构建 Docker 镜像的方案,其实就是把 cli 程序加入到镜像中。
目前我的远端服务是 Docker 容器形式运行的,因此这里分享一下 Docker 使用样例。
这里的远端应用以我的远端服务器上的青龙面板为例。
首先给出一个 Docker 容器启动命令示例:
1 | docker run -itd \ |
- 请注意不要暴露你的 7777 管理端口和 61090 代理端口给全局网络
- 容器启动后,所有 lnmp 网络中的容器可以通过
172.20.0.66:61090代理访问懒猫资源。- 宿主机可以通过
127.0.0.1:7777管理,通过127.0.0.1:61090代理访问。
在宿主机中,调用 cmd.sh (在我的那个 PR 中)完成懒猫网络的登陆。
1 | ./cmd.sh add_box |
登陆成功后,打开远端的青龙面板,安装依赖 axios 和 https-proxy-agent。之后增加环境变量配置如下:
1 | LAZYCAT_PROXY=http://172.20.0.66:61090 |
创建一个新的测试 js 脚本如下:
1 | const axios = require('axios'); |
调试运行,成功获取到懒猫微服上的 Influxdb 中的数据。

今天发现了一个有意思的工具 —— 青龙面板。
平时经常会有一些零零碎碎的脚本需要运行,放在宿主机运行,就要安装 nodejs 的环境,如果放在 docker 里运行,就要写 Dockerfile ,太繁琐。
使用这个青龙面板,相当于直接拥有一个容器空间,自带 python, nodejs 环境。可以理解为是云服务商的那种 server less 服务。

这样我们可以把零碎的脚本放在一个独立的代码库里面,进行版本管理。
当然,如果你想要立即执行拉取任务,也提供了单独的运行按钮可以立即执行。

独立的依赖管理面板,可以只需要安装一次依赖,就可以所有脚本都使用。

这是拉取下来的代码,可以在线编辑。

这是调试界面,可以实时调试。

编辑好的脚本,可以在这里设置计划任务,让脚本按计划运行。
在 React 中,useEffect 的依赖项决定了其什么时候执行。
useEffect 接受两个参数:
1 | useEffect(() => { |
1.无依赖项数组(每次渲染都会执行): 如果不传入依赖项数组,useEffect 中的副作用函数将在每次组件渲染后都执行。
1 | useEffect(() => { |
场景:这种用法不常见,通常用于希望在每次渲染时执行某些逻辑,但要注意性能开销。
2.空依赖项数组(只在组件挂载和卸载时执行): 如果传入一个空数组 [],则 useEffect 只会在组件挂载时运行一次,并在组件卸载时运行清理函数(如果提供了)。
1 | useEffect(() => { |
场景:通常用于初始化数据(例如组件挂载时的 API 请求),或在组件卸载时执行清理操作(例如清理定时器、取消订阅等)。
3.具有依赖项的数组(依赖项变化时执行): 当传入特定的依赖项时,useEffect 只有在这些依赖项的值发生变化时才会重新执行。
1 | useEffect(() => { |
场景:用于根据某个特定状态或 prop 变化来执行副作用逻辑。例如,当 count 变化时,可能需要重新获取某些数据或触发其他副作用。
4.多个依赖项: 可以在依赖项数组中传入多个依赖项,useEffect 将会在其中任何一个依赖发生变化时重新执行。
1 | useEffect(() => { |
场景:用于当多个状态或 prop 变化时,重新执行某个逻辑。例如,可能需要在 count 或 user 变化时重新发起某个 API 请求。
1.依赖项的选择要遵循以下原则:
2.常见的误区与优化:
ET碎碎念,每周更新,欢迎订阅,点赞,转发!
Steemit 的几个前端项目(condenser, wallet, faucet)都使用了 redux 和 redux-saga。
这次升级 faucet 所有依赖库,发现新版本 redux 推荐使用官方的 @reduxjs/toolkit 工具集来实现 redux 的功能。
既然 redux 的官方推荐使用工具集,那么这次升级我们也要相对应的把原有的 redux 相关代码做改动。
其中,为了应对复杂的异步请求而引入的 redux-saga,我不是很确定是否与工具集兼容,因此投入了时间来研究了一下。
redux-saga 与 @reduxjs/toolkit 一起使用创建 Redux Store: @reduxjs/toolkit 提供了 configureStore 来简化 store 的创建过程。你可以将 redux-saga 中间件添加到 store 中。
创建并运行 Saga 中间件: 使用 redux-saga 的 createSagaMiddleware 来创建 saga 中间件,然后将其添加到 configureStore 中。
运行你的 Saga: 在 configureStore 创建 store 后,使用 sagaMiddleware.run 来启动你的 saga。
下面是简单的例子。
store.js 文件
1 | // store.js |
saga.js 文件
1 | // sagas.js |
ET碎碎念,每周更新,欢迎订阅,点赞,转发!
由于 faucet 项目全部是手动搭建的环境,所以在把 react15 升级到 react18 后,
1 | import { createRoot } from 'react-dom/client'; |
使用上面的代码测试环境是否搭建成功的时候,报 React is not defined 错误。
原因是:在 React 18 中,虽然可以使用 createRoot 来渲染组件,
但仍然需要显式地导入 React 以支持 JSX 语法。
在 JSX 中,<h1>Hello, world</h1> 会被编译成 React.createElement('h1', null, 'Hello, world')。
因此,即使你没有直接使用 React,它仍然需要被导入。
由于 babel 我也升级到最新了,在 7.9 版本后,可以使用 @babel/preset-react 来自动引入 JSX 转换。
而不用去显式的导入 React 了。
具体方法就是在 babel.config.js 中对 @babel/preset-react 增加配置如下:
1 | { |
如此设置后,再次编译执行,报错就没有了。
ET碎碎念,每周更新,欢迎订阅,点赞,转发!
在 React 18 中,createStore 是 Redux 提供的一个函数,用于创建 Redux store,但从 Redux Toolkit v5 开始,createStore 已被标记为弃用,并建议使用 configureStore 作为替代。
configureStore 是 Redux Toolkit 中提供的一个函数,它简化了 Redux 的配置过程,内置了 Redux DevTools、默认的中间件配置等。
下面是如何使用 configureStore 替代 createStore 的一个简单示例:
1 | import { configureStore } from '@reduxjs/toolkit'; |
这次重构 faucet 将会替换掉 createStore 方法。
Redux、React-Redux 和 Redux-Saga 是前端开发中常用的状态管理和异步数据处理工具。它们各自有不同的功能和用途。
Redux 是一个用于 JavaScript 应用的状态管理库。它提供了一种可预测的方式来管理应用的全局状态。
React-Redux 是官方提供的 Redux 和 React 的绑定库。它允许 React 组件与 Redux store 进行连接,使得组件能够访问 Redux 的状态并分发 actions。
<Provider> 组件: 这个组件将 Redux store 提供给应用内所有的组件。1 | import { Provider } from 'react-redux'; |
1 | import React from 'react'; |
Redux-Saga 是一个用于处理 Redux 应用中的异步操作的中间件。它基于 ES6 的 Generator 函数,使得处理复杂的异步逻辑(如异步 API 请求、并发请求、失败重试等)变得更直观和可管理。
1 | import { call, put, takeEvery } from 'redux-saga/effects'; |
这三个工具通常配合使用,以实现复杂的状态管理和异步数据处理。
ET碎碎念,每周更新,欢迎订阅,点赞,转发!