My blogger

My way, my world

0%

IT从业者或多或少都对数码产品感兴趣,作为其中一员,我就在这里水一水下我的开发环境,同时谈谈我对数码产品的一些感想。

设备

我开发用的设备很简单:MBA+一台MiniPC(远程开发机器)。

笔记本

目前在服役的是m1版本 16+256版本的,性能上没什么感觉(没遇到过瓶颈其实已经足够了),续航是真的顶,加上无风扇,air中的air,作为Mac入门机型也不贵,基本符合我对笔记本的期待,期待苹果后续的air产品线。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
└> neofetch
'c. *@*-MacBook-Air.local
,xNMM. -------------------------------
.OMMMMo OS: macOS 12.3 21E230 arm64
OMMM0, Host: MacBookAir10,1
.;loddo:' loolloddol;. Kernel: 21.4.0
cKMMMMMMMMMMNWMMMMMMMMMM0: Uptime: 9 days, 4 hours, 19 mins
.KMMMMMMMMMMMMMMMMMMMMMMMWd. Packages: 30 (brew)
XMMMMMMMMMMMMMMMMMMMMMMMX. Shell: zsh 5.8
;MMMMMMMMMMMMMMMMMMMMMMMM: Resolution: 1440x900
:MMMMMMMMMMMMMMMMMMMMMMMM: DE: Aqua
.MMMMMMMMMMMMMMMMMMMMMMMMX. WM: Quartz Compositor
kMMMMMMMMMMMMMMMMMMMMMMMMWd. WM Theme: Blue (Light)
.XMMMMMMMMMMMMMMMMMMMMMMMMMMk Terminal: iTerm2
.XMMMMMMMMMMMMMMMMMMMMMMMMK. Terminal Font: Monaco 12
kMMMMMMMMMMMMMMMMMMMMMMd CPU: Apple M1
;KMMMMMMMWXXWMMMMMMMk. GPU: Apple M1
.cooc,. .,coo:. Memory: 2698MiB / 16384MiB

毕业后一直用的都是苹果家的笔记本,除了被工业设计征服外,MacOS也深得我心,但有一个问题就是太贵。一个是体现在笔记本天然更新换代周期太短了,我之前用的是16款mbp,当时是计划用5年,但用了3年多就已经能感觉到卡了,而且现在的手上的mba在很多方面甚至能吊打19年的16寸mbp,要知道mba发售价格可以比其少了一半多,满足需求和体验情况下,与其买顶配多用几年其实不如买便宜的入门款(甚至是二手)提前更新换代。贵的另外一个体现在内存硬盘升级价格上,当然苹果家的内存和硬盘用料都很足,但也远远不如自己组PC来的划算。再加上软件开发环境也远没Linux发行版用着舒适,所以我又购置了一台MiniPC来作为主要开发环境。

MiniPC

这台MiniPC规格上,5900hx + 32 + 1024,总的价格大概很便宜5000左右吧,而且硬盘和内存还可以继续扩展,其实就是笔记本的板U规格,正好功耗不高可以24小时开机来作为远程开发机。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
root@pve:~# neofetch
.://:` `://:. root@pve
`hMMMMMMd/ /dMMMMMMh` --------
`sMMMMMMMd: :mMMMMMMMs` OS: Proxmox VE 7.1-10 x86_64
`-/+oo+/:`.yMMMMMMMh- -hMMMMMMMy.`:/+oo+/-` Kernel: 5.15.19-2-pve
`:oooooooo/`-hMMMMMMMyyMMMMMMMh-`/oooooooo:` Uptime: 11 days, 20 hours, 10 mins
`/oooooooo:`:mMMMMMMMMMMMMm:`:oooooooo/` Packages: 784 (dpkg)
./ooooooo+- +NMMMMMMMMN+ -+ooooooo/. Shell: bash 5.1.4
.+ooooooo+-`oNMMMMNo`-+ooooooo+. Terminal: /dev/pts/0
-+ooooooo/.`sMMs`./ooooooo+- CPU: AMD Ryzen 9 5900HX with Radeon Graphics (16) @ 3.300GHz
:oooooooo/`..`/oooooooo: GPU: AMD ATI 05:00.0 Cezanne
:oooooooo/`..`/oooooooo: Memory: 26043MiB / 31504MiB
-+ooooooo/.`sMMs`./ooooooo+-
.+ooooooo+-`oNMMMMNo`-+ooooooo+.
./ooooooo+- +NMMMMMMMMN+ -+ooooooo/.
`/oooooooo:`:mMMMMMMMMMMMMm:`:oooooooo/`
`:oooooooo/`-hMMMMMMMyyMMMMMMMh-`/oooooooo:`
`-/+oo+/:`.yMMMMMMMh- -hMMMMMMMy.`:/+oo+/-`
`sMMMMMMMm: :dMMMMMMMs`
`hMMMMMMd/ /dMMMMMMh`
`://:` `://:`

我装了PVE作为宿主机,分了一个Ubuntu虚拟机来跑docker服务,一个Arch容器来作为开发环境。虽然是远程开发机,但感谢VS-CODE remote,让开发CLI程序体验跟本地开发高度一致,当然GUI程序开发比如App开发等还不行。开发环境用Arch主要是因为Arch的滚动升级的特性可以保持相关依赖的软件包都是最新,用最新软件包除了心理上满足外也能第一时间了解到相关软件的更新迭代信息。

整体体验

MBA + MiniPC这套环境对我来说很舒服了,相比之前的一台MBP,极大提升了性能可扩展性,而且花费还少。软件上MBA只需配好ssh和VPN,装一个VS-CODE就够了,不管在哪带着MBA只要有网就行,另外开发环境自由度也很高,有了大内存大硬盘想装什么虚拟机都可以。

数码产品税

数码产品算得上保值率最低的商品了,想想5年前的手机有多少人用,10年前的呢,不管当时买的多贵,到现在基本就是电子废品了。硬件商一直推出性能更强悍的芯片,但软件商也一直在蚕食硬件带来的提升,甚至不再提供老设备的软件更新,导致大家隔几年就想换个手机、电脑。当然厂商通过创新、改善使用体验带来的提升也很大。所以怎么说呢,人就是矛盾的,消费主义陷阱大概就是这样吧。其实还是希望看到体验到更多的新产品,同时也提醒自己要找到需求和消费的平衡点,少交点数码产品税。

目前python asyncio的生态已经可以支撑大部分业务开发了,但异步任务这块,celery一是还没支持asyncio(记得5.0版本是计划支持的)二是还没看到好用的代替品,所以依赖异步任务的场景还是需要集成celery。

event loop

在考虑celery前,asyncio是自带异步任务的,一些小的异步任务可以通过ensure_feature丢到event loop里,比如starlette就支持在response上挂backgroud task

celery

因为celery不支持asyncio,所以worker在调用task需要使用loop.run_until_complete来运行asyncio代码,这样实际上worker在跟broker交互时是syncio,实际执行任务时是asyncio,性能上不会比同步代码差,而且执行任务过程可以不用再拆分大task到小task来实现并发(直接利用loop运行异步的小task),一段简单的包装代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
from celery import shared_task


def task(
*task_args, **task_kwargs
) -> typing.Callable[[typing.Callable], typing.Callable]:
def decorator(func: typing.Callable):
@wraps(func)
def wrapper(*args, **kwargs):
if asyncio.iscoroutinefunction(func):
return asyncio.get_event_loop().run_until_complete(
func(*args, **kwargs)
)
else:
return func(*args, **kwargs)

# setattr(wrapper, "origion_func", func)
return shared_task(*task_args, **task_kwargs)(wrapper)

return decorator


@task()
async def hello():
await asyncio.sleep(1)

hello.delay()

坑一

由于celery worker是fork出来的子进程,然而event_loop是不支持fork的,如果在全局代码里生成了event_loop则子进程无法使用fork的event_loop,这时可以去掉全局代码的event_loop,也可以通过celery loader的hook重新生成event_loop:

1
2
3
4
5
6
7
class CeleryLoader(AppLoader):
def on_worker_process_init(self):
# uvloop.install()
asyncio.set_event_loop(asyncio.new_event_loop())
async_run(init_db())

celery = Celery("celery", loader=CeleryLoader)

坑二

如果不通过delay而是直接调用task函数的话(比如单元测试),由于是无法在一个running状态的loop里再调用run_until_complete的,最直接的解决方案是拿到注册的task的原始函数:

1
2
3
4
5
6
async def run_task(t, *args, **kwargs):
f = t._get_current_object().run.origion_func # 在task装饰器里注入origin_func变量,见上文
if asyncio.iscoroutinefunction(f):
return await f(*args, **kwargs)
else:
return f(*args, **kwargs)

总结

通过一点点的妥协,我们是可以在asyncio的生态享受到celery的便捷的,包括celery-beater等等都没问题。希望celery早日正式加入asyncio生态。

自从装好了Emby后, 除了刮骚好电影信息弄了电视墙后硬是没用它播过几次,因为实在是太卡了,甚至树莓派宕机了好几次,不过最终还是通过解决了。

服务端转码

不用多说,树莓派性能实在不够,必须关了,只使用串流播放

nginx tunnig

前面虽然关了转码,但实际情况没有变化,通过监控看到CPU使用率也很低 但SD卡IO时间超长 猜测是有临时文件频繁写到了SD卡上。
通过iotop查到是nginx写了一大坨临时文件,那就好办了,很容易就查到proxy_max_temp_file_size配置搞得鬼,设置为0后完美解决。

如果无法访问互联网

数据本地化

软件程序的基本作用就是数据的处理和展示,如果切断数据传输渠道,把数据存在自己硬盘上是最简单的应对方法。

存储的数据最好有以下特点:

  • 方便归纳检索
  • 信息密度含量高,一般文字大于音频大于视频,但图片和视频也不是文字能描述出来的

本地化哪些内容?

  • 维基百科
  • 专业类WIKI

参考:

P2P网络

基于蓝牙和热点的网络

线下“网络”

双方买卖/交换U盘

任何一门语言都有自己独特的“黑魔法”,或是语言特性,或是让人开心的语法糖,其中python世界有些简单实用的魔法,能让苦短人生再短一点,在这分享记录下

defaultdict

这个相当常见,带默认值的字典,做统计计数时相当好用,其实更方便的是利用匿名函数处理多层数据,比如按城市统计人名次数

1
2
3
4
from collections import defaultdict
counts = defaultdict(lambda: defaultdict(int))
for city, name in peples:
counts[city][name] += 1

contextlib

我们知道with关键字是python与众不同的语法糖之一,恰当的使用能让代码简洁明了,其中官方库contextlib包下有很多with控制块的实用工具,用过就回不去了,举几个一克斯然跑:

  • suppress

这是官方的解释:Context manager to suppress specified exceptions,使用场景很常见,比如说我们需要处理一堆商品,但是可能会有些未知异常,可能跑到一半挂了,除了跑一遍修一遍,我们可以用以下代码:

1
2
3
for product in products:
with contextlib.suppress(Exception):
handle(product)
  • contextmanager

contextmanager提供了一个简单的方式来创建上下文管理器,拿上面的例子来说,直接使用官方的suppress能让我们遍历完所有商品,但是这种做法会隐藏所有异常,很可能90%的商品处理过程都出了异常,但是我们可能不知情,这时候可以自定义我们的suppress来进行异常捕获输出:

1
2
3
4
5
6
7
8
9
10
11
from contextlib import contextmanager

@contextmanager
def mysuppress():
try:
print("context start")
yield
except Exception as e:
loger.exception(e)
finally:
print("context end")

除了for循环代码,也可以使用在业务流程里,比如将某个不太重要但容易出错步骤包在我们的suppress里,使我们的代码更有鲁棒性