春天已经悄悄到来,南方大城市中很难感受到春的变化。除非出游去远离都市的郊区,本着去看油菜花的目的,上周末去逛了一下深圳边缘的观澜版画村。
勤劳的小蜜蜂🐝
油菜花田
芦苇
这天天气很炎热,人也很多。
春天已经悄悄到来,南方大城市中很难感受到春的变化。除非出游去远离都市的郊区,本着去看油菜花的目的,上周末去逛了一下深圳边缘的观澜版画村。
勤劳的小蜜蜂🐝
油菜花田
芦苇
这天天气很炎热,人也很多。
最近体验了一段时间 M1 芯片的 Macbook Pro,我不得不承认,是我当初低估了它。简单的聊一下使用体验。
首先说一下我的日常使用电脑场景,vscode 窗口若干(3到5个窗口),远程开发。chrome 3到4组,每组20-30个tab,微信,QQ,企业微信(消息量4k+),基于electron的ssh终端,Microsoft TODO,Microsoft Remote Desktop,基于Chrome插件的youtube客户端(偶尔划个水)。
我原来的 Macbook Pro 是 Intel i7 的,内存32G,用起来不卡顿,但是风扇能够听到明显的声音,键盘很热,Touchbar位置的外壳烫手。
我现在体验的 Macbook Pro 14寸是M1处理器(8核,4E4P),16G内存。虽然只有16G内存,使用起来一点也不卡顿,并且键盘几近冰凉,Touchbar位置外壳仅有一点点温度。
可以看到,温度只有43度,风扇根本没转起来,事实上这台电脑从我拿到手到现在一个多星期风扇几乎一直是没转过。当然,还有一个很明显的地方就是,不插电的情况下续航时间明显比我原来的 Macbook Pro 长多了。
对比一下我 Intel 处理器的 Macbook Pro。
注意,这是我使用 Turbo Boost Switcher 强制关闭了 CPU 的 Boost 能力之后的表现,且是开机5分钟后的温度(并没有重度使用),然而多使用一会儿之后,温度很快就到达60度以上了。
总结一下,M1是真香,流畅、凉爽、持久。
去年因为疫情严重,春节临时取消了回老家过年的计划,改为在深圳过年。这一年的春节也是我第一次在深圳见到群众放烟花,估计是这几年群众过得太压抑了,然后公安部门明显也没有严厉的禁放。仓促的开放疫情管控,打得大家措手不及;但是这似乎也阻止不了大家回归正常生活的愿望。
因为在深圳过年,所以春节期间就决定去顺德逛一逛,这是我老婆那个吃货怂恿的。但是可惜,在顺德也没吃的什么特别的东西。而且,发现到处都是人;尤其是逛清晖园的时候,人特别的多,本来园子还挺漂亮的,但是一眼看去,全是人头。很多人觉得自己已经阳过了,就无所谓了;也不知道阳一次能管多久,希望阳了之后产生的免疫能管得久一点;也就是希望第二次阳不要太快到来,毕竟没有人愿意再经历一次这样的苦难。
清晖园 (人太多了,只好让鱼出镜,祝大家年年有鱼)
渔人码头
正月初六深圳采草莓
采摘园路边的小花花
最近想使用rust写个tcp透明代理转发服务,这中间涉及到socket监听以及连接处理逻辑中连接后端服务并处理连接后的相关逻辑。
监听端口
fn main() {
let skt = Socket::new(Domain::IPV4, Type::STREAM, Some(Protocol::TCP)).unwrap();
let address: SocketAddr = "0.0.0.0:3333".parse().unwrap();
skt.set_ip_transparent(true).unwrap();
skt.set_reuse_address(true).unwrap();
skt.set_reuse_port(true).unwrap();
skt.bind(&address.into()).unwrap();
skt.listen(128).unwrap();
let listener: TcpListener = skt.into();
println!("Server listening on port 3333");
for stream in listener.incoming() {
match stream {
Ok(stream) => {
println!("peer addr: {}", stream.peer_addr().unwrap());
thread::spawn(move || {
// connection succeeded
handle_client(stream)
});
}
Err(e) => {
println!("Error: {}", e);
/* connection failed */
}
}
}
// close the socket server
drop(listener);
}
处理客户端连接
fn handle_client(mut stream: TcpStream) {
let x = get_original_destination_addr(&stream).unwrap();
println!("target addr: {:?}", x);
let mut client_stream = get_rs_connect_stream_by_ipport(x.to_string().as_str());
println!("client stream: {:?}", client_stream);
let mut data = [0 as u8; 50]; // using 50 byte buffer
while match stream.read(&mut data) {
Ok(size) => {
if size == 0 {
false
} else {
// echo everything!
println!("len: {:?}", size);
stream.write(&data[0..size]).unwrap();
true
}
}
Err(_) => {
println!(
"An error occurred, terminating connection with {}",
stream.peer_addr().unwrap()
);
stream.shutdown(Shutdown::Both).unwrap();
false
}
} {}
}
可以看到这里每接受一个连接,就会起一个线程,如果在handle_client
里再连接后端的话,就需要针对client_stream
再起一个线程在线程中执行死循环,否则死循环会卡主stream
这个死循环。这样一来,一个链接就得起2个线程了,那么连接一多,线程就更多了,一个进程能创建的线程数是有限的,因为线程对资源占用相对比较大,这样连接数一多,系统资源就不够用,性能就会很差。
对比到golang中的协程,我们能否在rust中使用协程呢?答案是肯定的。下面简单介绍一下rust中如何使用协程。rust中使用“协程”,我们用到tokio
这个包。
对于单个异步,我们可以用async和await就可以了
#[tokio::main]
async fn main() {
let skt = Socket::new(Domain::IPV4, Type::STREAM, Some(Protocol::TCP)).unwrap();
let address: SocketAddr = "0.0.0.0:3333".parse().unwrap();
skt.set_ip_transparent(true).unwrap();
skt.set_reuse_address(true).unwrap();
skt.set_reuse_port(true).unwrap();
skt.bind(&address.into()).unwrap();
skt.listen(128).unwrap();
let listener: TcpListener = skt.into();
println!("Server listening on port 3333");
for stream in listener.incoming() {
match stream {
Ok(stream) => {
println!("peer addr: {}", stream.peer_addr().unwrap());
handle_client(stream).await // 其中 handle_client 改为了 async 异步函数
}
Err(e) => {
println!("Error: {}", e);
/* connection failed */
}
}
}
// close the socket server
drop(listener);
}
其中 handle_client 是 async 异步函数,但是如果我想在handle_client
中再起一个异步函数,直接使用async是不行的。我们可以这样处理,使用tokio::spawn
就可以了,这里tokio::spawn
有点类似golang中的go
关键词,调用tokio::spawn
的地方可以起一个异步函数。
async fn rs_handle(rs_stream: Arc<TcpStream>, stream: Arc<TcpStream>) {
let mut client_data = [0 as u8; 50]; // using 50 byte buffer
while match rs_stream.as_ref().read(&mut client_data) {
Ok(client_size) => {
if client_size == 0 {
false
} else {
println!("len: {:?}", client_size);
stream.as_ref().write(&client_data[0..client_size]).unwrap();
true
}
}
Err(_) => {
println!("client error occurred.");
false
}
} {}
}
async fn handle_client(stream: TcpStream) {
let x = get_original_destination_addr(&stream).unwrap();
println!("target addr: {:?}", x);
let stream = Arc::new(stream);
let rs_stream = Arc::new(get_rs_connect_stream_by_ipport(x.to_string().as_str()));
println!("rs stream: {:?}", rs_stream);
let rsh_rs_stream = rs_stream.clone();
let rsh_stream = stream.clone();
tokio::spawn(rs_handle(rsh_rs_stream, rsh_stream)); // 这里相当于起一个线程
println!("rs handle setted.");
let mut data = [0 as u8; 50]; // using 50 byte buffer
while match stream.as_ref().read(&mut data) {
Ok(size) => {
if size == 0 {
false
} else {
rs_stream.as_ref().write(&data[0..size]).unwrap();
true
}
}
Err(_) => {
println!(
"An error occurred, terminating connection with {}",
stream.peer_addr().unwrap()
);
stream.shutdown(Shutdown::Both).unwrap();
false
}
} {}
}
如上代码,首先需要定义一个异步函数async fn rs_handle(...)
然后在调用的地方使用tokio::spawn(rs_handle(...))
来调用。就可以实现同等于golang中go
关键词的效果,起一个协程。
快递不派件,丢件。找快递公司,快递公司不理会怎么办?相信很多人都遇到过这种情况。简单讲一下我遇到的情况,我有一个快递14号显示派送中,然后就显示疫情原因派送失败,然后我就想既然是疫情导致的,就等等吧,这一等就等到了月底,都两周多了。于是我打电话给快递总公司,找人工服务,对面说他们会联系一下快递网点公司,然后就没消息了,于是等了一个星期我再打电话给快递总公司,他给我发了一个快递网点公司的电话,让我联系快递网点。我联系了快递网点,对面又说他问一下派件业务员,然后就又是没消息了,于是又等了一个星期。
我终于不愿意再等了,没有哪个网点的疫情防控防控半个月的吧。接下来我这样操作
首先,到国家邮政业申诉服务平台(https://sswz.spb.gov.cn/index.html)注册账号并登录。
然后,点我要申诉,填写申诉相关信息。申诉单创建了,如下图
当天我给快递总公司去电三次,没能接通,给网点去电两次,没能接通。直到我 12:56 在邮政投诉平台提单投诉,然后,13:00 立马就收到了快递公司的短信,说会有专人跟进处理,14:38 立马快递总公司来电联系我,不出2小时就立马响应我的申诉单了。当天快递公司和网点先后电话联系我5次,最终告诉我快递没下落了,等他查一下监控,然后又说加我微信,如果找不到的话,就给我理赔,加了微信最后告诉我快递找不到了,因为有业务员离职了交接没搞清楚导致的,然后给我按原价赔偿了120块钱。
这快递公司是真的狗,我先后好声好气的联系他们,都如同石沉大海,不给我反馈。快递状态上拿疫情糊弄我。如果不是我了解到邮政投诉,估计这单快递就黄了,损失就是我的了。特此记录一下,有遇到相同情况的人,可以试一下邮政投诉。注意这里的邮政是政府机构,邮件政务管理部门的简称,并不是邮政快递。邮政是一个政务部门,在我国早期叫做邮电,也分政务部门和业务部门,政务部门负责管理,业务部门负责信件和电报业务,后来随着国家发展,邮电被拆分为邮政和电信,电信又发转为三大运营商,所以现在邮政快递是业务部门,邮政是政务部门负责管理包括邮政快递在内的所有快递公司,工信部是三大运营商的政务部门负责管理三大运营商。去政务部门“邮政”去投诉快递公司,一投一个准。快递公司绝对不敢怠慢。
家里闲置一段时间了的x79洋垃圾被我安装pve当做服务器了,但是它上面插了一张 nvidia GTX1060 6G 显卡,一直没用上,最近看到云游戏架构介绍之后,了解到显卡也可以虚拟化,决定自己动手将这张显卡用起来,在pve上虚拟化,这样一来可以在linux系统使用同时也可以在windows系统使用,让显卡发挥它的作用,以免浪费。
环境介绍:
一开始我参考这篇文章,首先在宿主机上安装必须软件以及显卡驱动。注意其中显卡驱动和mdevctl
是核心服务软件。
apt update && apt install dkms git build-essential pve-kernel-5.15 pve-headers-5.15 dkms cargo jq uuid-runtime -y
wget -P /opt/ http://ftp.br.debian.org/debian/pool/main/m/mdevctl/mdevctl_0.81-1_all.deb
dpkg -i /opt/mdevctl_0.81-1_all.deb
然后,配置内核
echo vfio >> /etc/modules
echo vfio_iommu_type1 >> /etc/modules
echo vfio_pci >> /etc/modules
echo vfio_virqfd >> /etc/modules
echo "blacklist nouveau" >> /etc/modprobe.d/blacklist.conf
echo "options kvm ignore_msrs=1" > /etc/modprobe.d/kvm.conf
# 更新 initramfs
update-initramfs -k all -u
配置引导
#编辑grub,请不要盲目改。根据自己的环境,选择设置
nano /etc/default/grub
#在里面找到:
GRUB_CMDLINE_LINUX_DEFAULT="quiet"
#然后修改为:
GRUB_CMDLINE_LINUX_DEFAULT="quiet intel_iommu=on"
#如果是amd cpu请改为:
GRUB_CMDLINE_LINUX_DEFAULT="quiet amd_iommu=on"
#更新引导
update-grub
重启宿主机一次
检查iommu
是否开启成功
出现有如下iommu group说明成功
root@pve3:~# dmesg |grep iommu
[ 0.000000] Command line: BOOT_IMAGE=/boot/vmlinuz-5.11.22-7-pve root=/dev/mapper/pve-root ro quiet iommu=pt intel_iommu=on
[ 0.075784] Kernel command line: BOOT_IMAGE=/boot/vmlinuz-5.11.22-7-pve root=/dev/mapper/pve-root ro quiet iommu=pt intel_iommu=on
[ 0.352588] iommu: Default domain type: Passthrough (set via kernel command line)
[ 1.373583] pci 0000:00:00.0: Adding to iommu group 0
[ 1.373592] pci 0000:00:02.0: Adding to iommu group 1
[ 1.373605] pci 0000:00:14.0: Adding to iommu group 2
[ 1.373613] pci 0000:00:17.0: Adding to iommu group 3
[ 1.373623] pci 0000:00:1c.0: Adding to iommu group 4
[ 1.373637] pci 0000:00:1d.0: Adding to iommu group 5
[ 1.373647] pci 0000:00:1d.2: Adding to iommu group 6
[ 1.373656] pci 0000:00:1d.3: Adding to iommu group 7
[ 1.373675] pci 0000:00:1f.0: Adding to iommu group 8
[ 1.373683] pci 0000:00:1f.2: Adding to iommu group 8
[ 1.373691] pci 0000:00:1f.3: Adding to iommu group 8
[ 1.373699] pci 0000:00:1f.4: Adding to iommu group 8
[ 1.373707] pci 0000:00:1f.6: Adding to iommu group 9
[ 1.373717] pci 0000:01:00.0: Adding to iommu group 10
[ 1.373726] pci 0000:03:00.0: Adding to iommu group 11
[ 1.373735] pci 0000:05:00.0: Adding to iommu group 12
[ 1.656483] intel_iommu=on
注意,上面日志中必须出现iommu group
相关内容,否则是iommu
开启失败,具体原因很可能是机器的bios中没有开启或者没有完全开启VT-d
,请将VT-d
及相关bios选项设置为enabled
状态,重启机器后再执行上述dmesg
命令查看日志是否正常。
安装驱动
# 将驱动下载至/opt目录
wget https://foxi.buduanwang.vip/pan/foxi/Virtualization/vGPU/NVIDIA-Linux-x86_64-460.73.01-grid-vgpu-kvm-v5-5.15.run -P /opt
# 给驱动添加可执行权限
chmod +x /opt/NVIDIA-Linux-x86_64-460.73.01-grid-vgpu-kvm-v5-5.15.run
# 安装
sh -c /opt/NVIDIA-Linux-x86_64-460.73.01-grid-vgpu-kvm-v5-5.15.run
安装过程可以参考上文中所指出的引用文章
配置 vgpu_unlock
vgpu_unlock 是为消费级显卡虚拟化功能解锁,nvidia消费级显卡默认是不能开启vgpu虚拟化的,想要专业的支持虚拟化的显卡,需要购买 nvidia Tesla 等型号的显卡。我们这里是 GTX1060 所以需要使用 vgpu_unlock 进行 vpgu 解锁。
# 下载vgpu_unlock-rs版本
cd /opt/ && git clone https://github.com/mbilker/vgpu_unlock-rs.git
# 编译
cd /opt/vgpu_unlock-rs && git checkout v2.0.1 && cargo build --release
# 安装 vgpu_unlock
cp /opt/vgpu_unlock-rs/target/release/libvgpu_unlock_rs.so /lib/nvidia/libvgpu_unlock_rs.so
重启宿主机一次
验证显卡驱动及 vgpu_unlock 是否成功。重启之后,使用nvidia-smi 确认是否如下,显示GPU信息。
➜ ~ nvidia-smi
Tue Nov 29 09:31:35 2022
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 460.73.01 Driver Version: 460.73.01 CUDA Version: 11.2 |
|-------------------------------+----------------------+----------------------+
| GPU Name Persistence-M| Bus-Id Disp.A | Volatile Uncorr. ECC |
| Fan Temp Perf Pwr:Usage/Cap| Memory-Usage | GPU-Util Compute M. |
| | | MIG M. |
|===============================+======================+======================|
| 0 GeForce GTX 106... On | 00000000:03:00.0 Off | N/A |
| 10% 53C P8 9W / 120W | 4084MiB / 6143MiB | 0% Default |
| | | N/A |
+-------------------------------+----------------------+----------------------+
+-----------------------------------------------------------------------------+
| Processes: |
| GPU GI CI PID Type Process name GPU Memory |
| ID ID Usage |
|=============================================================================|
| No running processes found |
+-----------------------------------------------------------------------------+
使用mdevctl types 验证是否出现mdev设备
➜ ~ mdevctl types
0000:03:00.0
nvidia-156
Available instances: 0
Device API: vfio-pci
Name: GRID P40-2B
Description: num_heads=4, frl_config=45, framebuffer=2048M, max_resolution=5120x2880, max_instance=12
nvidia-215
Available instances: 0
Device API: vfio-pci
Name: GRID P40-2B4
Description: num_heads=4, frl_config=45, framebuffer=2048M, max_resolution=5120x2880, max_instance=12
nvidia-241
Available instances: 0
Device API: vfio-pci
Name: GRID P40-1B4
Description: num_heads=4, frl_config=45, framebuffer=1024M, max_resolution=5120x2880, max_instance=24
nvidia-283
Available instances: 0
Device API: vfio-pci
Name: GRID P40-4C
Description: num_heads=1, frl_config=60, framebuffer=4096M, max_resolution=4096x2160, max_instance=6
nvidia-284
Available instances: 0
Device API: vfio-pci
Name: GRID P40-6C
Description: num_heads=1, frl_config=60, framebuffer=6144M, max_resolution=4096x2160, max_instance=4
nvidia-285
Available instances: 0
Device API: vfio-pci
Name: GRID P40-8C
Description: num_heads=1, frl_config=60, framebuffer=8192M, max_resolution=4096x2160, max_instance=3
nvidia-286
Available instances: 0
Device API: vfio-pci
Name: GRID P40-12C
Description: num_heads=1, frl_config=60, framebuffer=12288M, max_resolution=4096x2160, max_instance=2
nvidia-287
Available instances: 0
Device API: vfio-pci
Name: GRID P40-24C
Description: num_heads=1, frl_config=60, framebuffer=24576M, max_resolution=4096x2160, max_instance=1
nvidia-46
Available instances: 0
Device API: vfio-pci
Name: GRID P40-1Q
Description: num_heads=4, frl_config=60, framebuffer=1024M, max_resolution=5120x2880, max_instance=24
nvidia-47
Available instances: 0
Device API: vfio-pci
Name: GRID P40-2Q
Description: num_heads=4, frl_config=60, framebuffer=2048M, max_resolution=7680x4320, max_instance=12
nvidia-48
Available instances: 0
Device API: vfio-pci
Name: GRID P40-3Q
Description: num_heads=4, frl_config=60, framebuffer=3072M, max_resolution=7680x4320, max_instance=8
nvidia-49
Available instances: 0
Device API: vfio-pci
Name: GRID P40-4Q
Description: num_heads=4, frl_config=60, framebuffer=4096M, max_resolution=7680x4320, max_instance=6
nvidia-50
Available instances: 0
Device API: vfio-pci
Name: GRID P40-6Q
Description: num_heads=4, frl_config=60, framebuffer=6144M, max_resolution=7680x4320, max_instance=4
nvidia-51
Available instances: 0
Device API: vfio-pci
Name: GRID P40-8Q
Description: num_heads=4, frl_config=60, framebuffer=8192M, max_resolution=7680x4320, max_instance=3
nvidia-52
Available instances: 0
Device API: vfio-pci
Name: GRID P40-12Q
Description: num_heads=4, frl_config=60, framebuffer=12288M, max_resolution=7680x4320, max_instance=2
nvidia-53
Available instances: 0
Device API: vfio-pci
Name: GRID P40-24Q
Description: num_heads=4, frl_config=60, framebuffer=24576M, max_resolution=7680x4320, max_instance=1
nvidia-54
Available instances: 0
Device API: vfio-pci
Name: GRID P40-1A
Description: num_heads=1, frl_config=60, framebuffer=1024M, max_resolution=1280x1024, max_instance=24
nvidia-55
Available instances: 10
Device API: vfio-pci
Name: GRID P40-2A
Description: num_heads=1, frl_config=60, framebuffer=2048M, max_resolution=1280x1024, max_instance=12
nvidia-56
Available instances: 0
Device API: vfio-pci
Name: GRID P40-3A
Description: num_heads=1, frl_config=60, framebuffer=3072M, max_resolution=1280x1024, max_instance=8
nvidia-57
Available instances: 0
Device API: vfio-pci
Name: GRID P40-4A
Description: num_heads=1, frl_config=60, framebuffer=4096M, max_resolution=1280x1024, max_instance=6
nvidia-58
Available instances: 0
Device API: vfio-pci
Name: GRID P40-6A
Description: num_heads=1, frl_config=60, framebuffer=6144M, max_resolution=1280x1024, max_instance=4
nvidia-59
Available instances: 0
Device API: vfio-pci
Name: GRID P40-8A
Description: num_heads=1, frl_config=60, framebuffer=8192M, max_resolution=1280x1024, max_instance=3
nvidia-60
Available instances: 0
Device API: vfio-pci
Name: GRID P40-12A
Description: num_heads=1, frl_config=60, framebuffer=12288M, max_resolution=1280x1024, max_instance=2
nvidia-61
Available instances: 0
Device API: vfio-pci
Name: GRID P40-24A
Description: num_heads=1, frl_config=60, framebuffer=24576M, max_resolution=1280x1024, max_instance=1
nvidia-62
Available instances: 0
Device API: vfio-pci
Name: GRID P40-1B
Description: num_heads=4, frl_config=45, framebuffer=1024M, max_resolution=5120x2880, max_instance=24
如果上面两个验证不正常,请检查 nvidia-vgpud
服务和 nvidia-vgpu-mgr
服务的日志输出,命令如下
journalctl -u nvidia-vgpud
journalctl -u nvidia-vgpu-mgr
请根据日志错误自行谷歌,然后尝试修复。
配置 vgpu 参数,即前面安装的 vgpu_unlock-rs 的配置文件,路径 /etc/vgpu_unlock/profile_override.toml
。内容如下
➜ ~ cat /etc/vgpu_unlock/profile_override.toml
[profile.nvidia-55]
num_displays = 1
display_width = 1920
display_height = 1080
max_pixels = 2073600
cuda_enabled = 1
frl_enabled = 0
注意:framebuffer
,pci_id
,pci_device_id
这三个选项不要配置,因为这三个参数的值你拿不准,会导致后续创建虚拟机虚拟显卡后,虚拟机无法启动,常见报错如下
注意:我这里选择 nvidia-55
这个虚拟显卡,它显存是2G,我6G显卡可以虚拟出3个这个型号的显卡,你也可以选择其他显卡,具体显卡对应参数,参见上面mdevctl types
的输出内容。
Input/output error Verify all devices in group 29 are bound to vfio-<bus> or pci-stub and not already in use
具体配置可以参考 vgpu_unlock-rs 项目主页。
接下来就可以在pve-web上创建虚拟显卡了,首先设备选择 GTX 1060
然后 MDev
选择前面 vgpu_unlock/profile_override.toml
里配置的 nvidia-55
显卡创建完毕,虚拟机开机,windows系统中设备管理器可以看到未知的显示设备,安装驱动,从这里下载,选择grid_win10
原生驱动即可,也可以去NVIDIA官网下载grid驱动,但是据说虚拟机里的驱动版本不能比宿主机驱动版本高,具体我没验证。安装完毕如下
接下来我们在我们的 ubuntu-20.04
虚拟机上也添加一个显卡,添加步骤相同,看结果
root@ubuntu-gpu:~# lspci
00:00.0 Host bridge: Intel Corporation 440FX - 82441FX PMC [Natoma] (rev 02)
00:01.0 ISA bridge: Intel Corporation 82371SB PIIX3 ISA [Natoma/Triton II]
00:01.1 IDE interface: Intel Corporation 82371SB PIIX3 IDE [Natoma/Triton II]
00:01.2 USB controller: Intel Corporation 82371SB PIIX3 USB [Natoma/Triton II] (rev 01)
00:01.3 Bridge: Intel Corporation 82371AB/EB/MB PIIX4 ACPI (rev 03)
00:02.0 VGA compatible controller: Device 1234:1111 (rev 02)
00:03.0 Unclassified device [00ff]: Red Hat, Inc. Virtio memory balloon
00:05.0 PCI bridge: Red Hat, Inc. QEMU PCI-PCI bridge
00:10.0 VGA compatible controller: NVIDIA Corporation GP102GL [Tesla P40] (rev a1)
00:12.0 Ethernet controller: Red Hat, Inc. Virtio network device
00:1e.0 PCI bridge: Red Hat, Inc. QEMU PCI-PCI bridge
00:1f.0 PCI bridge: Red Hat, Inc. QEMU PCI-PCI bridge
01:01.0 SCSI storage controller: Red Hat, Inc. Virtio SCSI
可以看到我们的NVIDIA显卡,表示虚拟显卡添加正常,驱动可以自己去官网下载安装Grid驱动。
可惜的是,显卡虚拟化成功了,但是需要买 nvidia 的 License 才能在虚拟机中用。老黄的刀法,不得不服啊……😅
在golang中经常会踩一个坑,那就是slice append,golang的动态数组也称为slice,使用append可以对动态数组进行添加元素,但是slice空间不够之后golang会自动重新分配内存空间,每次重新分配的内存空间是原空间的2倍,而且有个更坑的是,golang中slice每次重新分配内存都是重新分配一片 2N 大小的内存,然后把原来的数据拷贝过去,这样一来,性能损耗更大了。
那么rust中的数组是怎么处理动态增长的呢,我们来一段代码测试一下。
fn main() {
let mut vec = Vec::with_capacity(100);
// The vector contains no items, even though it has capacity for more
println!("vec.len: {:?}", vec.len());
println!("vec.cap: {:?}", vec.capacity());
// These are all done without reallocating...
for i in 0..100 {
vec.push(i);
}
println!("vec.len: {:?}", vec.len());
println!("vec.cap: {:?}", vec.capacity());
// ...but this may make the vector reallocate
vec.push(101);
println!("vec.len: {:?}", vec.len());
println!("vec.cap: {:?}", vec.capacity());
}
输出如下
vec.len: 0
vec.cap: 100
vec.len: 100
vec.cap: 100
vec.len: 101
vec.cap: 200
可以看到,rust中,当vector数组存储满了之后,再往里面添加元素,vector就会重新分配内存,新分配的内存也是原来空间的2倍,但是,他是在原来的内存上扩充的,而不是像golang一样重新分配一片2N的内存空间替换旧的内存。性能损耗上,相比golang少了copy数据和释放旧空间。所以在高性能场景下,这里依然不建议使用vector的自动增长特性,自动增长的内存分配会消耗存储空间,而且 2N 的增长步长会很容易导致内存泄漏,你如果依赖这个自动增长特性,你将会发现你使用的内存可能会发生 2^n 指数级增长。这绝对会在大部分边界条件下导致你的程序迅速的发生OOM。
远离动态数组,远离bug。
最近发现我香港服务器上放置的几个 web 站点经常会偶尔出现无法访问的情况,这个香港服务器上放置的是 trojan 加 nginx,流量从trojan进入,部分转发出去,另外部分是web站点的流量,转发到nginx,从而实现流量代理和web访问。
这个香港节点出现web访问异常,之前也遇到过几次,都是重启nginx就正常了。这次决定仔细看下是什么情况,登陆节点,首先查看trojan日志,发现在正常转发,再看下nginx的日志,如下
2022/11/14 01:27:11 [error] 22#22: *473474 upstream timed out (110: Connection timed out) while connecting to upstream, client: 61.177.173.46, server: 0.0.0.0:22, upstream: "36.36.106.166:23000", bytes from/to client:0/0, bytes from/to upstream:0/0
2022/11/14 01:27:27 [error] 22#22: *473486 upstream timed out (110: Connection timed out) while connecting to upstream, client: 203.205.141.115, server: 0.0.0.0:22, upstream: "36.36.106.166:23000", bytes from/to client:0/0, bytes from/to upstream:0/0
2022/11/14 01:28:36 [error] 22#22: *473490 upstream timed out (110: Connection timed out) while connecting to upstream, client: 203.205.141.115, server: 0.0.0.0:22, upstream: "36.36.106.166:23000", bytes from/to client:0/0, bytes from/to upstream:0/0
2022/11/14 01:29:19 [error] 22#22: *473492 upstream timed out (110: Connection timed out) while connecting to upstream, client: 61.177.173.52, server: 0.0.0.0:22, upstream: "36.36.106.166:23000", bytes from/to client:0/0, bytes from/to upstream:0/0
发现nginx日志显示有连接超时,于是我决定判断一下是否真的连接不上,telnet
➜ trojan git:(master) telnet 36.36.106.166 23000
Trying 36.36.106.166...
^C
果然连接不上,我web站点配置如下
server {
listen 10110 ssl http2;
server_name xxx.duguying.net;
root /usr/share/nginx/html;
index index.php index.html;
ssl_certificate /data/certs/_.duguying.net.crt;
ssl_certificate_key /data/certs/_.duguying.net.key;
ssl_stapling on;
ssl_stapling_verify on;
add_header Strict-Transport-Security "max-age=31536000";
location / {
include git.deny;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header Host $http_host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_pass http://jxx.duguying.net:****;
proxy_buffering off;
proxy_buffer_size 128k;
proxy_buffers 100 128k;
}
}
web站点的流量经香港节点转发到 jxx.duguying.net
,dig一下
➜ ~ dig jxx.duguying.net
; <<>> DiG 9.11.5-P4-5.1+deb10u7-Debian <<>> jxx.duguying.net
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 25787
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
;; QUESTION SECTION:
;jxx.duguying.net. IN A
;; ANSWER SECTION:
jxx.duguying.net. 60 IN A 222.248.21.219
;; Query time: 22 msec
;; SERVER: 192.168.1.1#53(192.168.1.1)
;; WHEN: 一 11月 14 11:17:24 CST 2022
;; MSG SIZE rcvd: 51
发现nginx转发的节点居然不是目前upstream域名解析的节点,这说明我upstream域名dns更新了,但是nginx上upstream域名解析没更新。网上查询之后才知道,域名作为upstream,它的解析节点并不会时事更新。解决方案
方案一:每次dns有变化,重启Nginx (最开始出现故障重启nginx恢复就是这种解决方案)
方案二:使用Nginx Resolver
方案三:使用 Nginx-upstream-dynamic-server (nginx模块)
方案四:使用 ngx_upstream_jdomain (nginx模块)
这里介绍一下方案二,添加resolver相关配置,只需要将nginx配置改为如下
server {
listen 10110 ssl http2;
server_name xxx.duguying.net;
resolver 127.0.0.1 valid=60s; // 这里设置dns服务器
resolver_timeout 3s; // 这里设置dns解析超时时间
root /usr/share/nginx/html;
index index.php index.html;
ssl_certificate /data/certs/_.duguying.net.crt;
ssl_certificate_key /data/certs/_.duguying.net.key;
ssl_stapling on;
ssl_stapling_verify on;
add_header Strict-Transport-Security "max-age=31536000";
location / {
include git.deny;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header Host $http_host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_pass http://jxx.duguying.net:****;
proxy_buffering off;
proxy_buffer_size 128k;
proxy_buffers 100 128k;
}
}
rust中,Option表示一个可能不存在值的复合类型。其定义如下
pub enum Option<T> {
None,
Some(T),
}
可以看到,它里面的值要么就是类型T的值,要么就是None(也表示不存在值)。通常,我们在获取到可能为空的值时,Option类型很有用,它要求你必须去处理可能为None的情况。它有方法 is_none, is_some 等,可以判定是否为空。但是如果你使用这两个方法来解包 Option,免不了if else判断,代码会比较难看。比如
let x: Option<u32> = Some(2);
let mut value: u32 = 0;
if x.is_none() {
value = default;
} else {
value = x.unwrap(); // unwrap 方法可以解一切包,但是遇到 None 会 panic
}
可以看到上面的代码用了2条语句,首先是初始化value值,然后判定是否为None,根据不同的情况,对value重新赋值,明显复杂,我解个Option还得分两步,而且申明的value还必需是mut类型。那么,对于“我需要解包Option,如果Option为None则给默认值”这个需求,有更优雅的写法吗?有,如下
let x: Option<u32> = Some(2);
// 方法一:利用 match,和语句块
let value = match x {
Some(val) => val,
None => default,
};
// 方法二:利用 if-let,和语句块
let value = {
if let Some(val) = x {
*val
} else {
default
}
};
上述两种方法都是借助语句块一步到位的解包Option,并且没有调用任何方法。
最近手机电池不耐用了,而且经常发生剩余电量20%就自动关机,充电到80%就充不进的情况。于是决定换一下电池。
关于给iPhone换电池我之前也干过,之前给iPhone SE换电池,按照网上的教程拉电池胶,结果一再拉断,最后只能掰电池,撬电池,弄下来之后电池已经不成样子了。后面给macbook也换过电池,macbook电池是因为使用时间太久导致电池鼓包,电池损耗到太高提醒电池维修,拆卸过程中遇到的同样是电池胶的问题,不得不暴力撕下电池。后来听说电子洗涤剂很容易除电池胶,正好之前玩flash芯片买了一瓶电子洗涤剂,试了一下,真香。电子洗涤剂从缝里一喷,原本难以撕开的电池,自动的轻松分离开了。
备注:这个手机是二换电池了,5月份买了块品胜电池,用了5个月就不行了,看样子品胜电池次品率还挺高。这次换个德赛电池试一下。
对于换电池的小伙伴,这里强烈推荐电子洗涤剂来除胶,根本用不着像教学视频里一点点的卷胶,洗涤剂会让你轻而易举的就能拆卸下电池。还有一点需要注意,据说电子洗涤剂有毒,请妥善保存。