Hutool Sftp 负载均衡踩坑记

踩坑背景

最近有个项目使用到了Sftp,部署的时候发现了一个奇怪的现象,当sftp通过ip直接连接的时候,程序是一切正常的,在使用运维给的LB地址时,老是会出现下面这个报错

cn.hutool.extra.ssh.JschRuntimeException: JschException: Session.connect: java.net.SocketException: Connection reset
    at cn.hutool.extra.ssh.JschUtil.openSession(JschUtil.java:116)
    at cn.hutool.extra.ssh.JschUtil.openSession(JschUtil.java:97)
    at cn.hutool.extra.ssh.JschSessionPool.lambda$getSession$64b21fc$1(JschSessionPool.java:45)
    at cn.hutool.core.lang.SimpleCache.get(SimpleCache.java:112)
    at cn.hutool.extra.ssh.JschUtil.getSession(JschUtil.java:55)
    at cn.hutool.extra.ssh.Sftp.init(Sftp.java:166)
    at cn.hutool.extra.ssh.Sftp.init(Sftp.java:93)
    at cn.hutool.extra.ssh.Sftp.init(Sftp.java:80)
    at cn.hutool.extra.ssh.Sftp.init(Sftp.java:70)
    at cn.hutool.extra.ssh.Sftp.init(Sftp.java:56)
    at 

报错的同时,在服务器上尝试使用命令行LB地址连接Sftp却是一切正常的!!!同时经过排查发现,当程序刚启动尝试第一次连接的时候并没有出现这个奇怪的问题,正当我看这个奇怪的堆栈百思不得其解的时候,突然注意到了一个奇怪的打印

at cn.hutool.core.lang.SimpleCache.get(SimpleCache.java:112)

经过排查发现,hutool的Sftp工具类是默认开启了一个JschSessionPool的连接池,默认的直接使用 new Sftp(…) 或者 JschUtil.getSession(…) 都会把新建立的连接放到 JschSessionPool 这个连接池中,而恰恰就是这个连接池导致的问题!!!

修改方案

如果你也是使用的hutool Sftp工具类且涉及一些比较复杂的网络场景,建议跳过Hutool的Sftp自带的连接池逻辑,改成直接使用 JschUtil.createSession(…) 手动建立连接并且手动释放,这种场景反而更好做一些细粒度的控制

教训总结

在使用一个工具类前,必须要大致了解其底层实现逻辑以及相关issue,避免老是踩前辈们踩过的坑!!!!!!!!!!

SQL-CTE递归查询子ID

场景1: 查询某个节点的所有子孙id

idparent_idname
1NULLRoot
21Child_A
32Child_B
父子id结构
WITH RECURSIVE descendants AS (
    -- 初始条件
    SELECT id, parent_id, name
    FROM my_table
    WHERE id = 1
    UNION ALL
    -- 递归条件
    SELECT t.id, t.parent_id, t.name
    FROM my_table t
    JOIN descendants d ON t.parent_id = d.id
)
SELECT id, name FROM descendants WHERE id <> 1;

场景2: 查询某个节点的所有子孙id,构建children数组结构JSON

用sql来说似乎没法实现

Postgres 分布式拓展 citus 初探

citus是什么

citus是postgres的分布式拓展,它能够以非常简单的配置实现分布式的postgres数据库并实扩容,还能够将SQL进行并行化处理,充分利用多台机器的资源,使用于大量数据的实时查询场景。

citus的基本概念

节点:Coordinator Node(协调节点)、Worker Node(工作节点)

表:Distributed Table(分布式表)、 Reference Table(引用表) 、Local Tables (本地表)

citus的节点分为工作节点和协调节点。

协调节点是我们连接使用的主节点,它的主要职责是连接所有的工作节点分发和协调数据,不会保存分布式表的数据。

工作节点是实际保存数据和执行SQL的主体,协调节点会将SQL处理后分发到对应的节点执行任务。

citus的表有三种类型:分布式表、引用表、本地表。

分布式表,顾名思义就是实现分布式存储的表,它的数据是根据实现创建分布式表时指定的字段作为key进行哈希,水平分布在所有的数据节点中,是citus最常用的分布式表。

引用表也是分布式表的一种,它的数据完整会被保存所有的工作节点中,所有工作节点都拥有该表的全部数据。

本地表不是分布式表,它是存储在协调节点上面的表,和普通postgres数据库的表一样。

实战体验

准备一台主节点、若干工作节点

我这边正好有四台Hosthatch虚拟机,都重装成debian11用来搭建实战。

协调节点 192.168.10.202

工作节点1 192.168.10.101

工作节点2 192.168.10.102

工作节点3 192.168.10.103

先安装postgres和citus拓展(所有节点都要执行下面的步骤)

apt update &amp;&amp; apt install -y sudo gnupg2

#添加citus源
curl https://install.citusdata.com/community/deb.sh | sudo bash
#添加postgresql源,我们要安装最新的postgresql-15
sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list'
wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add -

#更新源
sudo apt-get update

#安装postgresql-15、以及对应版本的citus拓展
sudo apt-get -y install postgresql-15 postgresql-15-citus-11.1

修改postgresql监听IP、添加四台节点互相信任(所有节点都要执行)

# 预加载citus扩展
sudo pg_conftool 15 main set shared_preload_libraries citus
# 修改监听IP,这里我直接改了 *,如果是暴露在公网的建议只监听内网ip
sudo pg_conftool 15 main set listen_addresses '*'
# 修改添加四台节点互信
sudo vim /etc/postgresql/15/main/pg_hba.conf
#在最底下追加下面内容
host    all             all              192.168.10.202/24                trust
host    all             all              192.168.10.101/24                trust
host    all             all              192.168.10.102/24                trust
host    all             all              192.168.10.103/24                trust

#重启 
systemctl restart postgresql

完成了上面的步骤后,我们开始选择一个数据库添加citus拓展(所有节点都要执行)

# 我这边直接选择的postgres,如果要使用不同的库,所有节点都需要有该库存在
sudo -i -u postgres psql -c "CREATE EXTENSION citus;"

接下来在协调节点中执行相关函数,设置协调节点和添加工作节点(只在协调节点运行)

#设置协调节点 IP端口根据实际情况来
sudo -i -u postgres psql -c "SELECT citus_set_coordinator_host('192.168.10.202', 5432);"
#添加工作节点
sudo -i -u postgres psql -c "SELECT * from citus_add_node('192.168.10.101', 5432);"
sudo -i -u postgres psql -c "SELECT * from citus_add_node('192.168.10.102', 5432);"
sudo -i -u postgres psql -c "SELECT * from citus_add_node('192.168.10.103', 5432);"

#查看活跃的工作节点
sudo -i -u postgres psql -c "SELECT * FROM citus_get_active_worker_nodes();"

到此为止,我们前置配置相关的工作已经完成了,接下来就是创建分布式表并感受分布式的快乐了,比如下面的流程

#后续都是在协调节点执行
#创建表结构
CREATE TABLE IF NOT EXISTS key (
                                   id uuid,
                                   word varchar NULL,
                                   url varchar NULL,
                                   description varchar null,
                                   PRIMARY KEY (id)
);
#调用citus提供的函数创建分布式表 第一个参数是表名 第二个参数是依据该字段进行分片,将数据分配到所有的工作节点
SELECT create_distributed_table('key', 'id');

#这样就建立好了一个分布式表,使用它也是让程序直接连接协调节点,就像平常连接一个数据库那样直接使用即可

笔记-字节顺序标记

字节顺序标记(Byte Order Mark,简称 BOM)是一个用于指示字节序(Byte Order)的特殊字符,具体是 Unicode 字符 U+FEFF。字节序问题主要存在于那些使用多于一个字节表示每个字符的编码方案中,比如 UTF-16 和 UTF-32。字节序是指在这些字节中哪一个字节先存储,这在不同架构的系统中可能不同,通常分为大端序(Big-Endian)和小端序(Little-Endian):

  • 大端序(Big-Endian):高位字节(more significant byte)在前,低位字节(less significant byte)在后。例如,Unicode 字符 U+1234 在大端序中存储为 12 34
  • 小端序(Little-Endian):低位字节在前,高位字节在后。同样的 U+1234 字符在小端序中存储为 34 12

由于大端序和小端序的存在,使用如 UTF-16 和 UTF-32 等编码的文本文件可能需要一个 BOM 来明确字节序。例如,如果一个文本文件以 FE FF 开头,则表明该文件采用大端序的 UTF-16 编码;如果以 FF FE 开头,则表明采用小端序。

然而,UTF-8 不受字节序问题的影响,因为它使用单个字节序列来表示每个字符,不涉及字节间的顺序问题。UTF-8 编码中的每个字节是明确的,不会根据存储顺序而改变含义。因此,UTF-8 编码不需要 BOM 来指示字节序。

在实践中,虽然 UTF-8 本身不需要 BOM,但有时会在 UTF-8 编码的文件开始处看到一个 BOM(即 EF BB BF)。这是为了与那些需要 BOM 的编码方式兼容,或者用来明确指明文件是以 UTF-8 编码的。但在很多情况下,UTF-8 BOM 是不必要的,甚至可能导致问题,比如在不期望 BOM 的上下文中解析文本时。因此,是否使用 UTF-8 BOM 往往取决于具体的应用需求和上下文。


UTF-8(8-bit Unicode Transformation Format)是一种针对Unicode字符集的可变长度字符编码系统。它可以用来表示Unicode标准中的任何字符,并且其设计旨在兼容传统的ASCII编码。

网络中会存在大端系统和小端系统?

大端(Big-Endian)系统和小端(Little-Endian)系统的存在主要是由于不同的硬件架构和设计哲学所导致的。当计算机科学还处于起步阶段时,不同的系统设计者对于如何在内存中存储数据有不同的看法。这些看法转化成了两种主要的数据表示方法,即大端序和小端序,它们定义了多字节数据在内存中字节的顺序。

大端序(Big-Endian)

在大端序中,最高有效字节(MSB)存储在最低的内存地址上,这也意味着数字的第一个字节是最重要的字节。如果我们读取一个地址,我们首先得到的是数字的最高有效位。这种方式直观地反映了我们通常书写数字的方式,例如数值0x12345678在内存中的存储顺序将是 12 34 56 78

大端序的优点:

  • 读取数据的高位非常直接,特别是对于网络协议而言,因为协议字段经常是从高位开始定义的。
  • 更符合人类阅读数字的习惯。

小端序(Little-Endian)

而在小端序中,最低有效字节(LSB)存储在最低的内存地址上。数值0x12345678在小端序的系统中将被存储为 78 56 34 12。这种方式在内存地址增长的方向上按照数字的权重递增。

小端序的优点:

  • 在进行数值计算时,低位在低地址,有时可以简化数值增长和扩展的处理。
  • 对于可以变长的数据表示,添加新的字节不会改变现有字节的地址。

为什么同时存在

不同的处理器架构在设计时会选择适合自己的一种模式。例如,Intel的x86架构就是小端序,而许多网络协议和早期的RISC架构如SPARC则是大端序。这些选择往往是基于硬件设计时的考虑和历史遗留问题。

随着时间的推移,两种系统都已经被广泛应用,而且各有其优势和特定的使用场景。在多数现代架构中,如x86-64和许多ARM架构,支持在两种模式之间切换,给操作系统和应用程序提供了更大的灵活性。然而,由于这两种不同的存储方式在互操作性上可能导致问题,因此在网络协议设计中通常会选择使用网络字节顺序(大端序),以确保不同系统之间数据传输的一致性。

最低有效字节是什么意思?

最低有效字节(Least Significant Byte,简称 LSB)指的是在多字节数字中数值最小的那个字节。在数字表示中,最低有效字节代表着数值中的最低位(通常是最右边的数字在十进制表示中,或是最低的二进制位)。换句话说,对于数字的修改,最低有效字节中的任何改变将对整个数值产生最小的影响。

例如,对于一个32位的整数0x12345678(在十六进制表示中),其字节从最低位到最高位依次为:

  • 0x78(十六进制的120,二进制的01111000)是最低有效字节。
  • 0x56(十六进制的86)是第二字节。
  • 0x34(十六进制的52)是第三字节。
  • 0x12(十六进制的18)是最高有效字节(Most Significant Byte,简称 MSB)。

在小端序存储中,0x78作为最低有效字节会被放置在最低的地址。这意味着如果我们在小端序系统中查看地址从低到高的数据,我们会首先看到78

在计算机内存和数据处理中,了解哪个字节是最低有效字节非常重要,特别是在执行二进制算术运算时。对于小端序系统,最低有效字节的处理通常是首先进行的,尤其在涉及数值增长或者运算进位时。

实习(摸鱼)十天随记

不知不觉已经来10天了,感觉做开发工作是真的苦逼。每天从早上到下午就是对着个电脑,还特么每周要加班!!!!!不知道是不是上了贼船了。

我们这些实习生本身就是地方研发中心的,这次是来总部实习。不知道是不是因为以后也不会待在总部,所以总部研发部的人也不怎么管我们,就让我们随便弄弄学点东西。

这几天跟着写了一下简单的接口,感觉自己的水平确实是欠佳,虽然说是真的学过也接触过很多东西,但是大多都是浅尝辄止,没有深入的理解底层的一些东西和优化,甚至连用也用的不精,很多新工具都不了解,只会玩各种教程学习视频那些老一套。

所以,我决定每过几天就把学到的或者之前花很多时间搜索才得到结果的东西都写进博客,顺便水一水文章数量,之前本来想着自己用react写个简单的博客,不知道以后会不会进展,做这一行还是得多学学,学的多工资才多。

学习!!!!!!!!!!!!!!!!!!

树莓派4B 安装Emby/Jellyfin服务端 开启硬件解码

第一步.安装Emby/Jellyfin

直接安装

安装Emby

https://emby.media/linux-server.html选择你树莓派对应的发行版,直接复制命令安装

安装好后,命令行输入下面命令

systemctl start emby-server.service //启动embyserver
systemctl enbale emby-server.service //设置开机启动
systemctl status emby-server.service //查看embyserver服务状态

启动成功后即可在 http://ServerIP:8096 访问,ServerIP是树莓派的IP地址。

安装Jellyfin

https://jellyfin.org/docs/general/administration/installing.html中根据你的发行版按照文档安装

systemctl start jellyfin.service //启动jellyfin
systemctl enbale jellyfin.service //设置开机启动
systemctl status jellyfin.service //查看jellyfin服务状态

Docker安装 除了镜像名字不同其他的都一样

Docker安装Emby

先安装Docker,然后去Dockerhub找到emby-server镜像 linuxserver/emby

#拉取docker镜像 会自动按照操作系统拉取相应的镜像 无论系统是32位还是64位
docker pull   linuxserver/emby 

#运行镜像  不要直接复制粘贴 先看下面的传入参数解释 按需修改 
#传参说明 例如 -v /a:/b 左边的/a是你系统中的实际路径,右边的/b是映射到容器中的路径
,docker run -d \
  --name=emby \
  -e PUID=1000 \
  -e PGID=1000 \
  -e TZ=Asia/Shanghai \
  -p 8096:8096 \
  -p 8920:8920 `#optional` \
  -v /path/to/config:/config \
  -v /path/to/tvshows:/data/tvshows \
  -v /path/to/movies:/data/movies \
  -v /opt/vc/lib:/opt/vc/lib `#optional` \
  --device /dev/dri:/dev/dri `#optional` \
  --device /dev/vchiq:/dev/vchiq `#optional` \
  --device /dev/video10:/dev/video10 `#optional` \
  --device /dev/video11:/dev/video11 `#optional` \
  --device /dev/video12:/dev/video12 `#optional` \
  --restart unless-stopped \
  linuxserver/emby

Docker安装Jellyfin 除了镜像名字不同其他的都一样

#拉取docker镜像 会自动按照操作系统拉取相应的镜像 无论系统是32位还是64位
docker pull   linuxserver/jellyfin 

#运行镜像  不要直接复制粘贴 先看下面的传入参数解释 按需修改 
#传参说明 例如 -v /a:/b 左边的/a是你系统中的实际路径,右边的/b是映射到容器中的路径
,docker run -d \
  --name=emby \
  -e PUID=1000 \
  -e PGID=1000 \
  -e TZ=Asia/Shanghai \
  -p 8096:8096 \
  -p 8920:8920 `#optional` \
  -v /path/to/config:/config \
  -v /path/to/tvshows:/data/tvshows \
  -v /path/to/movies:/data/movies \
  -v /opt/vc/lib:/opt/vc/lib `#optional` \
  --device /dev/dri:/dev/dri `#optional` \
  --device /dev/vchiq:/dev/vchiq `#optional` \
  --device /dev/video10:/dev/video10 `#optional` \
  --device /dev/video11:/dev/video11 `#optional` \
  --device /dev/video12:/dev/video12 `#optional` \
  --restart unless-stopped \
  linuxserver/jellyfin

运行成功后即可在 http://ServerIP:8096 访问,ServerIP是树莓派的IP地址。

docker传入参数详解

参数功能
-p 8096Http WebUI。
-p 8920Https webUI(您需要设置自己的证书)。
-e PUID=1000用于UserID-指定用户ID 用于解决权限问题
-e PGID=1000用于GroupID-指定组ID 用于解决权限问题
-e TZ=Asia/Shanghai指定时区 默认是上海
-v /config配置文件位置。这可能会变得非常大,对于一个大型集合,可能会达到50gb +。
-v /data/tvshows媒体在这里。添加尽可能多的需要例如/data/movies/data/tv等等。
-v /data/movies媒体在这里。添加尽可能多的需要例如/data/movies/data/tv等等。
-v /opt/vc/libRaspberry Pi OpenMAX的路径libs可选
--device /dev/dri仅当您要使用Intel或AMD GPU进行硬件加速视频编码(vaapi)时才需要。
--device /dev/vchiq仅当您要使用Raspberry Pi OpenMax视频编码(Bellagio)时才需要。
--device /dev/video10仅在要使用Raspberry Pi V4L2视频编码时才需要。
--device /dev/video11仅在要使用Raspberry Pi V4L2视频编码时才需要。
--device /dev/video12仅在要使用Raspberry Pi V4L2视频编码时才需要。
参数作用

PUID和PGID可以用来指定用户ID和组ID,可以解决-v传入目录时权限不足的问题,将其传入为目录拥有者ID即可 查询可以使用下面的命令 比如我的目录拥有者ID是dragonfsky 当然也可以都设置成0 即拥有root权限 但这可能会导致安全问题.

[dragonfsky@dragonfsky-mi ~]$ id dragonfsky
用户id=1000(dragonfsky) 组id=1001(dragonfsky) 组=1001(dragonfsky),998(wheel),991(lp),3(sys),90(network),98(power),1000(autologin)

树莓派硬件解码方式有两种

OpenMAX(Raspberry Pi)

Raspberry Pi OpenMAX的硬件加速用户需要通过在运行或创建容器时传递以下选项,将/ dev / vchiq视频设备安装在容器中以及系统OpenMax库中:

--device=/dev/vchiq:/dev/vchiq
-v /opt/vc/lib:/opt/vc/lib

V4L2(Raspberry Pi)

Raspberry Pi V4L2的硬件加速用户将需要在运行或创建容器时通过传递以下选项来将其/ dev / video1X设备安装在容器内:

--device=/dev/video10:/dev/video10
--device=/dev/video11:/dev/video11
--device=/dev/video12:/dev/video12

第二步.增加GPU内存

要使用硬件加速,我们需要给树莓派GPU分配更多的内存。修改/boot/config.txt中的gpu_mem字段,如果没有的话就增加。我使用的发行版是Manjaro arm 64位,默认的设置gpu_mem=96,我将其改成gpu_mem=320。当然如果你内存够多的话可以考虑加多一点。

#这是我修改后的显示
[root@dragonfsky ~]# cat /boot/config.txt
# See /boot/overlays/README for all available options

gpu_mem=320
initramfs initramfs-linux.img followkernel
kernel=kernel8.img
arm_64bit=1
enable_gic=1
disable_overscan=1

#enable sound
dtparam=audio=on
hdmi_drive=2

#enable vc4
dtoverlay=vc4-fkms-v3d
max_framebuffers=2

改完以后重启即可生效。接下来就可以直接在emby/jellyfin中开启硬件转码加速

emby-点击设置-服务器-转码
emby-开启硬件加速
jellyfin-设置-控制台-播放

选择开启硬件加速即可,我这里把临时转码改到了USB硬盘上,默认路径是在config文件夹下,建议尽量把这个保存路径放在外置硬盘上。如果是SD卡装系统的话也尽量把SWAP关掉,开启了反而会让系统变卡并且消耗SD卡寿命。

总结

其实树莓派这性能就算开启了硬件加速也很难流畅的转码,经常CPU就跑满了 O(∩_∩)O哈哈哈~。如果不需要硬件转码可以直接在 用户 设置中,把允许转码取消。

emby-用户
jellyfin-用户

随记

今天花了好久慢慢看完墨茶的所有动态以及聊天记录截图,越看越破防。一个身负重病仍然坚强乐观的少年,一位马克思主义同志,一个虽然穷苦潦倒被病痛折磨但仍然努力挣扎的少年,一个每日靠泡面过活连1块8的止痛片都舍不得买却舍得用仅有的钱救助流浪猫的少年,在春节的前一个月独自一人在破旧的出租屋离开了。除了他在网上结识的朋友,没有人在乎他,没有人关心他,更没有人帮助他,甚至他的死亡也是房东在多天后才发觉的。

“四川在线”对他的家人进行了采访,他的家庭并没有他描述的那样贫穷,不如说正相反,他的家庭在大凉山这个贫困地区甚至算的上富有。不知为何,在这篇采访中似乎特别地描述了他家”并不贫穷” : “75岁的老人每月有政府定时发放的优抚金和农村养老保险金等固定收入。单家独户的小院,一栋两层楼房,有一个客厅五间卧室,院内东西厢房有猪圈、鸡棚、厨房、厕所、太阳能热水器洗澡间等,屋内有电视机、冰箱、冰柜、洗衣机、电饭煲等电器。猪圈内有一头已经怀上猪崽的母猪,鸡棚内喂有七八只鸡,后院菜地种植的青菜、白菜等蔬菜长势良好。” ““墨茶official”的母亲告诉四川在线记者:其家境还算不错,自己有一辆2014年购买的东风悦达起亚牌轿车,又在西昌市区与人合伙开了一家足浴店,其于2016年1月和2019年5月分别在西昌市购买了18.53平方的商业用房和43.11平方的办公用房” ,甚至能精确到小数点后两位,又极力将他渲染为一个”网瘾少年” 其母亲陈某某介绍,“墨茶official”因家庭离异的原因,性格较为孤僻,不爱说话,最大喜好就是网络,职高肄业后,长期辗转于西昌、会理、成都等地,要求其母给予生活费,其母因要求其自食其力导致母子关系较紧张。 而且这篇文章与墨茶动态和聊天透露的情况有着极大的出入,2020年,“墨茶official”在西昌检查出鼻肿瘤,后由其父带至攀枝花医院手术,并支付了手术费,期间发现其患糖尿病、高血压等病症。手术后,“墨茶official”因与父亲不和,独自外出,不接父母电话,直至被发现已在会理县迎宾大道出租屋内去世。根据自助墨茶群友晒出来的账单,确实是他们一起资助了他的看病钱,在出院后仍然欠着医院2000多元连吃一块八的止疼片都会肉痛,在患病后他也是一直打工一边直播攒钱,天天吃的也只有泡面,直到他因为糖尿病的并发症离开人世。

我多希望他如那些杂种所说的,只是一个孤僻的”网瘾少年”,只是一个该死的骗子啊,那样我们就不会这么心痛和愤怒了。