ChromeOS Flex, 让我的macbook pro 2013重获新生

我人生第一台MBP在2015年买的——macbook pro 2013… 这电脑很多年都没怎么用了。特别是这几年开始远程办公,主要都是用家里的Desktop。半个月前公司组织outing,我还是把它带上了。很尴尬,期间出了bug,而且还需要我来修。。。 用这个2013款的mac实在太憋屈了,build c/rust太难为她了。本来是想着挂在咸鱼回回血,等着入国产arm/riscv笔记本。没想到,生活处处有惊喜!听了我司前CTO的podcast: #95 - 用 Chromebook 做开发是什么样的体验? ,我的mbp 2013又活了。 本来他们是讨论的chromebook,以及瘦客户端。chromebook就是一个输入输出的设备,计算都在远端完成。可以是ssh连家里的server,也可以是连aws开的机器。开发环境需要能快速构建。所以chromebook就是一个很良好的选择。 当天就就下了个chromeOS flex镜像,直接boot!体验就是,安装很快,UI体验也很好,没有原生app,只有浏览器。还是有点点卡的,不过问题不大。 好巧不巧,当天晚上又失眠了,躺到凌晨4点怎么都睡不着,索性起来了。通宵身体还是不舒服的,也不想坐在电脑前。就开始把玩chromeOS,躺在沙发上,ssh连上desktop,开始干活! 那天晚上也就写了个测试,但是整体上把平常需要用到的地方都用到了,也算把这套流程打通了。 有两个地方体验很差: chromeOS无法安装nerd font,导致ssh看到的字符不是等宽的,所以光标移动会有一个很大的误差,很影响开发体验。 不能从host直接复制到ssh server的剪切板。这是一个老问题了,不好解决,也影响不大。 nerd font有两个解决办法 可以在本地hack,chromeOS的terminal也是html写的,可以直接改css。这个是最快的,也实验过。 再就是整理一个精简的vimrc,去掉所有fancy的功能,当ssh上去使用精简vimrc,平时使用完全版。

November 4, 2022 · 1 min · magicalne

Hey Vim! I'm back to you, again!

过去一段时间主力开发语言是rust和c,当然可能会写一些python/js。日常工作使用vs code作为开发工具,还是比较省心的。 但是最近发现vs code越来越卡了。由于工作需要,经常需要开4、5个项目,来回切着看代码、调试。CPU直接起飞了! 这不得不让我重新想想,vs code是不是真的适合我了。vs code最大的优点是省心,不需要折腾。我也确实很久很久没折腾工具了0.0 所以上周折腾了一下,还是很开心的! 我开始重新使用vim,其实不是vim,是neovim。一个提供异步支持的neovim,倒逼vim进步的项目。当然vim 8也支持异步了,所以我理解这两者差距不大。 值得一提的是,nvim支持使用lua写插件,好多lua写的插件都跑的好快。 最令我开心的是一整套的工具连,用起来十分得心应手,在teminal里完成所有的编辑工作,根本没有碰鼠标的必要。 alacritty - Alacritty - A fast, cross-platform, OpenGL terminal emulator zsh tmux nvim 我把相关配置放到里github. alacritty只做一件事——terminal。没有tab、window等等其他概念。所有配置的修改都是通过yml文件。一个很大的亮点是支持vi-mode。 所以用户需要使用类似tmux的软件来管理window。Oh tmux. So great! zsh的插件我没有用很多,主要使用了zsh-autosuggestion。这个插件师从fish,我用了好久的。 没想到啊,隔了好几年,还是回到了原点…

July 12, 2022 · 1 min · magicalne

使用rust写一个基于QUIC的proxy[1]

magicalane最新版本v0.1.0已经支持socksv5,实测下来效果还不错呢。 当第一次开始测试时,发现不能打满带宽,这让我对quic产生了怀疑。腾讯云lighthouse带宽是30Mbps,但是magicalane单链接只能到大约8kb/s。 首先quic这个协议本身的性能确实不如tcp,所以没能打满带宽也算是意料之中。但是差的有点大阿。 首先怀疑的是quinn-quic实现问题,看过一陀一陀的代码也没有发现问题。回头又一想,这个8kb怎么这么眼熟呢。然后看到自己代码里把proxy的buffer size设定为8kb了。 原来瓶颈是我的hardcode0.0 我能接触到的服务器中,能给出的最大带宽也就30Mpbs。所以我就依照这个30Mpbs,然后改为30 * 128 * 1024 = 3932160,然后就起飞了。 单链接看youtube 8K视频没问题,就是自己的浏览器有点卡,网络是不卡的。带宽基本打满了,cpu和内存使用率也很低。 联想到v2ray,是不是也有buffer size的参数可以配置?一顿搜索之后,果不其然,配置里也有一个bufferSize。所以继续测试了相同buffer size的v2ray的效果。结果是v2ray效果更好。 当然最后这点带宽不是很重要了,使用quic最重要的原因是想解决head of line blocking。接下来会新增一个tcp+tls的proxy实现,这样才能更好的对比性能。 同时再考虑如何综合quic和tcp的优势。比如当网络不稳定,出现了~10%丢包,这时候切换成quic;当网络稳定了,再切回tcp。 手写的未来 手写Future真的很难。proxy的实现直接port了linkerd2-proxy。 手写Future所带来最大的好处就是充分使用范型,可以写抽象的代码。虽然有async-trait这种crate,bin用用可以的,但是lib还是乖乖手写吧。因为lib在设计时候可以在最后阶段交付一个非常generic的Future,还是非常人体工学的。 手写Future的时候,需要反复利用Context wake task的机制。因为在Future实现中,通常会多次调用poll_like方法,而poll_like方法需要传入cx。这时候,如果有多个poll_like方法返回pending,当某一个方法能够产生progress时,只有最近调用的Future会被调用。 也就是说,向poll_like方法传入cx,只是订阅了一个topic,当能够产生progress时,这个topic不会直接触发回调机制。Future只能通过循环状态机的方式工作。 所以呢,当某个poll_like方法返回pending,我们能够明确知道,当Future产生新的progress,我们会继续回到这块代码,因为我们之前已经订阅了这个topic。这么说还是挺绕的。简单的状态机很好写,一个enum搞定。复杂的就难了。。。 pin 再一个难点就是pin。回想过去学习计算机编成相关的概念,貌似没有哪个概念再比pin复杂了吧?当初第一次接触pin,没有什么特殊感觉,以为自己理解了。不就是把一个struct pin到stack/memory么,这样Future调用的时候,即使被其他的cpu core调用,物理内存地址也不会发生修改,这样就能保证正确性。 后来又看了看Unpin,!Unpin,Pin<Box>,再加上deref、structural,蒙了。。。 Pin相关的文档不知道读了多少遍了。对我造成最大困扰的地方就是无法直接查看async fn生成的代码,看编译后的汇编没啥效果。 最后通过实践,有两个特性尤为重要: 只有当struct所有成员都是Unpin,那这个struct才是Unpin,这是编译器默认的。否则只能通过实现Unpin trait来确保自己是Unpin,幸运的是,实现Unpin是安全的; 如果一个T是Unpin,那么Pin<&mut T>与&mut T等价,可以随意deref。 之所以上述两个特性很重要,是因为Pin的设计之初是为了async生成代码时,会生成自引用代码,所以自引用的成员需要声明为Pin。但是平常手写Future,基本不涉及自引用变量,所以这时候的问题就转变为如何去除Pin。依照上述两个特性,大部分的struct都可以是Unpin的。 再接下来就是structual project。我用下来,最需要Pin的时候,是当我需要poll一个async fn。也就是类似: struct MyFut<O> { inner: Pin<Box<dyn Future<Output = Result<O> + Send + 'static>>> } 其他时候可以用pin-project,写起来非常人体工学。当然也可以不用。不用pin-project的好处,就是可以通过deref调用&mut self声明的poll_like方法。

June 9, 2021 · 1 min · magicalne

初探io_uring

linux以性能著称,但是在一些小于1%的场景下,linux在io的表现上并不能让人满意。 主要原因在于上下文切换。 无论是网络请求还是本地磁盘io、read/write、pread/pwrite、epoll、aio都绕不开频繁的上下文切换。这是linux的by design。 io执行一次read操作,一定需要从user space转换到kernel space。为了给网络io增大吞吐量,通常会使用增加线程数量来处理大规模的并发请求。 但是一些要求低延时的场景,过去linux提供的这些io操作不能满足需求。业界解决方案就是DPDK。 网络操作因为linux的设计导致不能满足需求,那就绕过去。自己实现tcp、udp协议栈,使用专业网卡,甚至fpga直接烧。 对于这个问题,linux也给出了自己的解决方案:io_uring io_uring从用户角度看,设计是异常简单明了的。io_uring将一次io操作进行拆分,拆分成提交请求和接受回复。 二者均使用了ring buffer的数据结构,以及生产者-消费者模式。 注意这里有两个ring buffer,提交和完成两个阶段使用不同的ring buffer。 提交请求就是,生产者(客户)向ring buffer发送一个消息,消费者(kernel)从ring buffer读取一个消息。 当kernel处理完之后,生产者(kernel)向完成队列提交一个消息,消费者(客户)从ring buffer读取一个消息。 这里性能提升的来源在于,由于ring buffer是一个共享的数据结构,user space和kernel space不需要不断的切换。 io_uring还提供了其他高级功能,比如异步通知。 因为linux的读写操作都是阻塞的。之前写了一个container,里面起了一个linux shell,我希望在host上执行container linux的shell操作。 我希望知道,ptmx是否有新内容可以读,以及是否我想提交命令到container linux。 这种功能epoll可以很好的完成,但是不高级,是时候祭出io_uring了。 code

February 8, 2021 · 1 min · magicalne

tty、pty、ptmx、pts与container

当我在实现linux container的时候,遇到的最大的问题(至今为止),就是如何让host与container通过命令行通信。所以深入学习了linux的tty。 所谓tty就是最早的tele-type输入设备,后来也叫pty。 现在的问题来了。我在linux上创建了一个container,这个container有自己的namespace,且会通过fork创建另外一个进程。这里把host进程称为pm,container进程称为ps。那么pm和ps之间如何在terminal中通信呢?pm是我们在terminal里启动的进程,可以通过stdio看到pm现在发生着什么。问题是ps是另外一个进程阿,ps是死是活,我怎么通过当前的terminal感知到呢? 所以需要重定向stdio,把ps的stderr、stdout都发送到pm的stdin,并把pm的stdio发送到ps的stdin。 以host的视角看container的目录结构: # ls my_container/rootfs bin dev etc home notify proc root sys tmp usr var 但是以container的视角看自己当然是这样的: # ls / bin dev etc home notify proc root sys tmp usr var devpts devpts历史众多,这里记录了我关注的部分。 在比较new的早期,devpts在linux下是一个单例,如果需要创建一个新的terminal,可以通过创建一个pts来实现,而devpts这个设备仍然是独一份。但是时间来到2008年左右,由于linux加入了container的概念,事情就很不一样了。如果devpts还是单例,那么会有安全问题:container1创建了一个pts1,container2可以通过devpts看到pts1。为了解决这个问题,devpts增加了newinstance这个参数,至此,container中的devpts才是单例。为了可以向后兼容(这个兼容我没看出有啥用),还需要: mount -o bind /dev/pts/ptmx /dev/ptmx patch详见这里。 重点在这里: If multi-instance mode mount is needed for containers, but the system startup scripts have not yet been updated, container-startup scripts should bind mount /dev/ptmx to /dev/pts/ptmx to avoid breaking single- instance mounts....

December 27, 2020 · 2 min · magicalne

升级到fedora 33的问题列表

升级到fedora 33,没有被btrfs摧残,yet。 第一个问题是拉代码发现“no permission”。搜到了redhat bugzilla The server probably does not support SHA2 signatures. You’ll have to switch to LEGACY policy. update-crypto-policies --set LEGACY 无需重启,问题解决。

December 8, 2020 · 1 min · magicalne

如何在user namespace中mount proc

最近在用rust实现一个oci runtime。开始就遇到了很多问题,从这里开始理解到底什么是system programming。 那么什么是system programming? 参考Kamal Marhubi的答案: Systems programming is programming where you spend more time reading man pages than reading the internet. 最近真是读了好多man。。。因为自己想要的东西是google搜不出来的。 我一开始的想法很简单,创建一个user namespace,设置相应的flags,mount devices和fs,然后把pid放到cgroup v2下面,打完收功。没想到第一个步就走不通。 这里参考的user namespace的example。然后开始用rust改这里的逻辑。 创建一个新的user namespace流程: unshare + all flags update uid_map|gid_map mount proc mount / MS_PRIVATE mount rootfs mkdir rootfs/oldrootfs pivot_root chdir / umount2 oldrootfs rmdir oldrootfs 遇到的第一个问题是不能修改gid_map 这个问题很简单: Linux 3.19 made a change in the handling of setgroups(2) and the ‘gid_map’ file to address a security issue....

November 22, 2020 · 2 min · magicalne

初探golang

go作为一门现代语言,没有历史包袱,语法简单。就是由于起初设计语言的时候,把go设计的太简单,导致很多决策都是后面版本迭代出来的。go特别像是简易c语言+runtime。 说go更加现代,首先在变量声明就能提现。到底是强类型好?还是弱类型好?我自己写的代码,变量起个名字就好,为什么还要声明类型?当下的论调就是,自己开发的方法里面的变量声明,可以省略类型声明,如果要暴露给别人调用,应该为入参和返回值声明类型。go的变量声明可以是编译器通过value推演的,天然可以省略类型声明。但是返回和入参是必须声明类型的。 没有exception,error只不过是一个value而已。没有采纳try-catch,而是简单的使用多返回值的形式,强制调用者对error进行处理。这里rust跟go很像,也是没有exception。但是rust吸收了更多函数式编成语言特性,使用Result对返回值进行包装,而不是作为多返回值的形式。 go有指针的概念,而且真的是指针,但是没有c各种神奇的功能。因为go设计为pass by value,每次向一个方法里传递变量都是一份copy。那怎么在这个方法里修改这个参数变量的内容呢?答案就是传一个指针进去。尽管这个指针也是一份copy,但是指向的内容确是真实存在的。这里就很不java了,java通过封装可见性(private/protected/public/final)来防止/允许用户修改某个object。 没有class?用method!这里rust也跟go很像。除了普通的func,还可以为struct实现自己的func,也就是method。加上指针的存在,就可以区分“只读”method和“读写”method。 比较简陋的interface?Go Proverbs这个视频很有意思,其中聊到了java中的抽象。演讲者说到,interface应该声明尽量少的方法,否则,方法越多,抽象越少!不能同意更多。java里有太多为了抽象而抽象的地方,不仅要背诵API还要背interface。说到这里,特别想统计一下,java8前后的sdk,包括spring发布的版本中,interface里的方法数量。在引入lambda之后,interface里的方法数量肯定是越来越少的,关键是那些非lambda的interface,java自己是否有在反思。rust trait和go interface很像,但是go缺少了范型,还是差点了。 go的入门生态不错,内容都集中在一起了,入门和深入的材料离的也很近。不像java,像老古董似的都藏起来的… A tour of Go faq Effective go Go memory model和Java memory model光从篇幅上就能看出,go更简单,事实也确实如此。毕竟go没那么多关键词。也就没有所谓的java volatile带来的可见行,构造方法里声明private final初始化防止reorder等等。 语言审美上说,go有很多“聪明”的设计,这反倒是我不喜欢的。。。 我还是更喜欢rust的,更美!但是对比这两个年轻语言,真的是有大量的相似之处阿。不知道以后会不会出现go+rust的搭配,就像python+c++。两个年轻 好了,学了1天,会了~~开写

November 5, 2020 · 1 min · magicalne

cgroup V2

虽然docker一直有用,但是cgroup、namespace只停留在字面上见过的级别。最近上手rust,想用rust写一个oci的实现,发现rust下有一些cgroup的crate。进而发现cgroup v2已经release有一段时间了,crun、runc等等都已经支持上了。但是rust中没有cgroup v2对应的实现。所以,写一个? 看了下文档,v1和v2的差距还是很大的。网上介绍版本差异的文章很多了,intel的这个pdf介绍的足够了。 我的Fedora32已经默认支持上了cgroup v2,所以我按照文档说明用rust写了写测试代码。从最简单的创建一个cgroup开始。我是按照文档中操作fs的方式实现的: sudo mkdir /sys/fs/cgroup/mycgv2 这里,我陷入了深深的沉思… 为什么要sudo? cgroup是可以mount的类型之一,创建一个新的cgroup必须要用mount,而mount是需要root权限的。而且/sys/fs/cgroup就是在root下的,这里的root是逃不掉的。 那就用已有的cgroup呗。我发现cgroup有一个user目录:/sys/fs/cgroup/user.slice/user-1000.slice 1000是linux为初始化创建用户的id,也就是“我”的id,这个目录下的user和group都是“我”,也就是说我可以直接操作这个目录下的cgroup!那就可以不需要root了! 那么问题来了… 这个user.slice/下面的目录是谁创建的? 这个问题不难搜,目录下的文件名结尾都是.scope/.slice/.service。一看就是systemd创建的。我在另一个目录下用mount重新创建了一个cgroup挂载,发现这个user.slice/目录依然是自动创建好了的。从这里可以看出,cgroup是被systemd管理的。虽然文档并没有说明,但是后来我在社区的邮件也找到了证据。而且不应该在根目录下创建cgroup,因为这里是systemd管理的。 那是不是说,与cgroup正确的交互姿势是使用systemd?而不是fs? 其他cgroup v2实现 这里参考了runc/crun等等项目的实现,基本把cgroup v2的实现都看过一遍了。发现其实fs和systemd的实现是都用的,但是runc在开启rootless模式下建议使用systemd,但不强制。 这里耗费了好多时间,cgroup的文档读了好多好多遍,就想从字里行间理解systemd存在的意义。因为我知道一开始systemd虽然也支持v1,但是kernel还有libcgroup的实现。而到现在,libcgroup应该没有v2对应的release,官方代言就是systemd。在做了几天功课之后,我认为需要systemd接管cgroup的原因还是在于rootless。 cgroup.subtree_controller和cgroup.controllers cgroup.subtree_control是可读写的,用来限制子目录下的cgroup能用什么controller,就是v2的权限控制。 cgroup.controllers是只读的,用来展示当前能用的controller都有什么。 默认在跟目录下,cgroup.controllers包含全集: $ cat cgroup.controllers cpuset cpu io memory hugetlb pids 但是默认cgroup.subtree_control只包含pids和memory。所以使用rootless,默认的所有子cgroup只能操作pids和memory。想要操作更多controllers,只能修改根目录下的cgroup.subtree_control。有两种方式: root用户下,echo “+cpu +memory -io” > cgroup.subtree_control 通过systemd,当然也需要root,修改配置文件并reload: $ mkdir -p /etc/systemd/system/user@.service.d $ cat > /etc/systemd/system/user@.service.d/delegate.conf << EOF [Service] Delegate=cpu cpuset io memory pids EOF $ systemctl daemon-reload 两种方式都需要root权限,这可不妙阿。假设系统初始化与cgroup的管理分割开来,只需要在最开始使用root,开启所有controller权限,之后不需要root。那么问题就是安全性如何保证?虽然这里是rootless了,但是权限都拿到了,啥都可以做了。 nsdelegate cgroup v2有一个mount option: nsdelegate。参见文档。...

September 8, 2020 · 4 min · magicalne

基于QUIC的透明代理客户端优化

在基本功能完善之后,发现代理速度如龟。这跟理论不贴合阿,先从client入手。 第一个问题非常明显,client的udp连接没有做到复用。QUIC是支持单udp socket上建立多connection。而我在实现的时候是把每个tcp连接代理出去的时候就建立一个udp连接。这里改成向future传入&Endpoint就好了。 但是改完之后还是好慢,没有任何改善。 我开始打开trace log,观察twitter首页的加载。一段在http1.1的传过来js,只有~140kb,居然要用10多秒。而log显示每次recv都只有30-40字节,都不是kb…本地代理出去的时间很快,不到1ms,关键就是waiting(TTFB)的时间有点久了,下载速度是肉眼可见的慢,但是还是在下载的,没有断掉。 quinn文档有说到,遇到延迟特别大的时候可以考虑调整SO_SNDBUF and SO_RCVBUF,我一直以为这两个参数值的是我的应用层的buffer size。后来才想到这个可能是操作系统层面的参数。 在我的fedora运行: $ sysctl net.core.rmem_max net.core.rmem_max = 212992 $ sysctl net.core.rmem_default net.core.rmem_default = 212992 也就是说最大不过208kb?改成25M试试: $ sudo sysctl -w net.core.rmem_max=26214400 net.core.rmem_max = 26214400 $ sudo sysctl -w net.core.rmem_default=26214400 net.core.rmem_default = 26214400 速度飞起,心疼流量… 目测+心算,http1.1下应该是**ray的10倍吧。youtube视频4k随便拖拽~~ 之所以想自己写代理的原因,就是在看youtube的时候突然卡掉了,telnet却是通的。等youtube缓冲半天,进度条还是纹丝不动,看看telnet还是通的!

August 19, 2020 · 1 min · magicalne

在rust中处理证书时遇到的问题

基于QUIC的透明代理走到上线环节了。在测试过程中遇到了关于TLS证书的问题,这里记录一下。 背景 在google cloud上有一台instance,因为已经安装了v2ray+caddy,所有tls已经有了。首先要找到server上证书的地址。 一顿find之后,发现了两个文件server.crt和server.key。我就想当然的把这两个文件当作cert和key传给server端了。 结果就报错了。 BadDER 这个错太简写了。。。我发现是key出的问题。我对证书的各个版本和格式不是很了解,这里面有点复杂,我只是知道个大概。这篇文章解释的很清楚。 我打开.key文件开到头尾是这样的: -----BEGIN EC PRIVATE KEY----- ----END EC PRIVATE KEY----- ec private key是个什么鬼? 解释在这里。 github上找到了相关讨论,源码这里不支持ec key,所以需要把ec转为.pem文件。 openssl pkcs8 -topk8 -nocrypt -in server.key -out server.key.pem 重试一下,报了另一个错… UnknownIssuer 通过对比client和server的log,确定这个错是从client的webkpi报出来的。确定是在client接受到server发回来的tls public key之后报的错。这个阶段发生在quic 1-rtt阶段。 所以server这里是没问题了。 再看这个错,字面意思就是Unknown Issuer… 什么是Issuer呢? 这要说说TLS/SSL的工作原理。简单说,server有一套cert和key,用来向client保证:“咱俩的连接是安全的”。那凭什么有了cert和key,连接就安全了呢?那是因为cert和key是由一个有公信力的组织发给server的。比如,这里的let’s encrypt就是一个有公信力的组织,internet上都信任这个组织。全世界的公共发证机关就那么些,chrome、mozilla、操作系统会把各个安全的机关记录下来。这个叫ca root store。 这些发证机关叫Authority。可以在chrome打开chrome://settings/certificates,就能看到了。 Issuer就是发证机关。 Issuer去哪了呢? 首先,caddy是用的Let’s encrypt。难道这个背书在我的fedora32上没有嘛?我第一次搜还真没有。 sudo dnf install ca-certificates awk -v cmd='openssl x509 -noout -subject' ' /BEGIN/{close(cmd)};{print | cmd}' < /etc/ssl/certs/ca-bundle.crt | grep "lets" 这时候搜,确实能搜到let’s encrypt。但是还是不行。...

August 14, 2020 · 1 min · magicalne

使用rust写一个基于QUIC的proxy[0]

简介 QUIC From wikipedia: QUIC (pronounced “quick”) is a general-purpose[1] transport layer[2] network protocol initially designed by Jim Roskind at Google,[3] implemented, and deployed in 2012,[4] announced publicly in 2013 as experimentation broadened,[5][6][7] and described to the IETF.[8] While still an Internet Draft, QUIC is used by more than half of all connections from the Chrome web browser to Google’s servers.[9] Microsoft Edge[10] and Firefox[11] support it, even if not enabled by default, as does Safari Technology Preview....

August 6, 2020 · 2 min · magicalne

rust反序列化提升性能

一个多月前入了rust的坑。当时想学学rust就是想尽可能的提升性能,但是又“不敢”写生产环境级别的C++。退而求其次,学学rust吧。 最近遇到了一个之前写java就遇到过的问题——deserialize慢。 client收到了server发送的response,把json反序列化。这是一个非常常见的场景,使用serde_json的derive可以简单干净的完成功能。 但是serde_json会使用vector,而vector是在heap上,allocation就是性能的第一块瓶颈。加上在业务逻辑处理的地方需要频繁的删除vector中的元素,dealloc也很慢。 咋办呢? 1. impl DeserializeSeed 第一个想法是向deserializer传递一个struct,遍历一遍input即可完成对struct的修改。对于数组字段,我可以声明一个统计意义不可越界的array,这样所有allocation都发生在stack上。DeserializeSeed可以接受参数传递,我可以实现这个trait。 但现实是,这个并不好实现。 pub trait DeserializeSeed<'de>: Sized { /// The type produced by using this seed. type Value; /// Equivalent to the more common `Deserialize::deserialize` method, except /// with some initial piece of data (the seed) passed in. fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error> where D: Deserializer<'de>; } 原因之一在于,deserialize这个method的self参数不是mutable。当然用wrapper可以解决。 第二个原因是不好解决nested object。想了两天没想明白,就放弃了。 2. Nom 这是第二次手写parser了,上次写java用的parboiled。这次的Nom给我打开了新世界。我一开始一直局限在自己的思想之下,一直想着怎么向parser传递变量。这是与Nom的设计相违背的。 Nom parser的返回签名很简单: IResult<Input, Output, Error> parser执行成功,会返回<剩余input, 生成的输出>;如果失败,则会返回<生于input,Error>。典型的rust返回范式。而parser的输入永远只有一个,默认的实现支持&[u8]和&str。文档说也可以支持自定义的input。如果编写的parse方法需要传入其他参数,可以通过call!这个macro,实现原理就是currying,最多支持6层。 对于parser过程中需要识别的部分,则需要通过组合的方式把子parser合并起来。这也就是combinator的思想。各个combinator会byte by byte的消化input。...

April 27, 2020 · 3 min · magicalne

如何为Fedora删除kernel

Linux各个发行版的升级一直以来都是一件头疼的事情。当年第一次用ubuntu的时候,一周重装了3次…真的是遇到问题只会重装。 不过linux的增量更新并且结合grub,在一定程度上降低了升级系统的风险。 昨天我将我的Fedora 30进行了升级,kernel从5.3.16-200.fc30.x86_64升级到了5.4.6。然后就不能开机了。。。讲道理,用了这么多年的linux,以前这种事还是经常发生的,但是近几年真的没啥印象。用户体验的质量还是有把控的。 网上搜了一下,发现这个版本出问题的人还不少呢。好在grub上可以选历史kernel,我就切回了5.3.16。然后我就在5.3.16-200.fc30.x86_64这个image上把之前安装的最新的kernel删掉了。 步骤很简单,首先确认当前的kernel版本: uname -r 5.3.16-200.fc30.x86_64 然后看看最近安装的kernel: dnf repoquery --installonly --latest-limit=1 -q kernel-0:5.3.16-200.fc30.x86_64 kernel-core-0:5.3.16-200.fc30.x86_64 kernel-devel-0:5.3.16-200.fc30.x86_64 kernel-modules-0:5.3.16-200.fc30.x86_64 kernel-modules-extra-0:5.3.16-200.fc30.x86_64 然后删除上述版本的kernel: sudo dnf remove $(dnf repoquery --installonly --latest-limit=1 -q) reboot之后,老版本的kernel就不复存在了。 真香!

January 15, 2020 · 1 min · magicalne

乞丐版微服务之——AWS放大化

首先说几个问题。 microservice是一种架构模式; 功能极致完善的软件系统,内部实现一定复杂且维护代价高;内部实现简单明了且维护成本低,那功能也会相对单一。这是工程师时刻需要trade-off的点; 越来越多的公司上云了,aws、gcloud、azure、aliyun等等。每个云提供商面向的企业也不尽相同,往大了说有netflex,小了说有个人开发者。介于这里介绍乞丐版,那我们定义的目标客户就是小公司吧; 大多数小公司的技术很渣,不足以看懂aws docs的level的渣; 小公司开发必备基础设施 既然都说是小公司了,如果不是纯技术创业,一般来说脱离不了互联网。而且不会有老板愿意掏钱让下面的人闲的没事了,开发个rpc框架,或是xx存储。 假如说要开发一个APP,那需要一个后端提供API访问。后台API除了需要做好load balance,还需要至少一个存储。这里少不了后台JOB,或者其他类似的生产者-消费者模式的服务。用户在APP内的行为数据也是要上报的,crash报告也是要上报的。 同步调用 AWS对于API后台提供了API gateway,后面可连接lambda,是一个较为完整的轻量级网关,不能做聚合,前置了cloudfront。优点是简单易用,最大的缺点我认为是贵。另外一种方案是自己做load balance,后台连多个ec2。麻烦了点,这个是最便宜的。 当初我开始接触AWS的时候,有一个问题百思不得其解,为什么AWS不提供rpc服务?AWS几乎所有服务都是基于HTTP+JSON,比如SQS、dynamodb等等。AWS提供通信的服务有API gateway、SQS、SNS。如果AWS要提供例如dubbo的RPC服务,那一定要有服务发现和服务注册。现在的EKS其实是有基于k8s+istio的配套服务的,但是以前是真没有阿。如今,我认为之所以AWS不提供RPC服务,主要原因就是因为AWS要给小公司服务,小公司用HTTP+JSON够了。再让小公司额外搞服务注册发现熔断。。。而且,lambda其实提供了同步调用的自动服务发现和服务注册。 首先,我们知道在开发过程中应该尽可能缩短调用链,以此来保证高可用性。也就是说要减少同步调用。这里的同步调用也没有必要使用API gateway,尽管支持private模式。这里完全可以用lambda替代。lambda很便宜,如果做一些没有什么上下文的事情,200行代码以内搞定所有事情,lambda是不二之选。我们可以在自己的服务中通过aws client调用lambda,这里注册发现就省下来了。那lambda出现异常要丢数据呢?熔断呢?这里也是可以做的,不过确实需要增加额外的开发量。可以为lambda中的error处理逻辑配置dead-letter-queue。这个dead-letter-queue意义就在于保证不丢数据。如果现有逻辑处理不了,报错了,扔到dead-letter-queue。后面再接cloudwatch alarm报警邮件,调好代码后重新消费dead-letter-queue中的消息。 dead-letter-queue不仅仅可以用于lambda,任何消费服务都需要配置。 服务间的同步调用,可以用lambda来实现,CRUD没问题,CQRS也可以。 异步调用 我们开发过程中经常会遇到这样一个问题,如果一个字段从状态A,变更为状态B,需要通知XXX。这种操作很常见,本身就是一个状态机。AWS提供了一个我认为很鸡肋的服务——step function。这个服务最好的一点是可以结合lambda。这样lambda不仅仅适合开发简单逻辑,也能适应复杂逻辑。但是单元测试是我非常非常注重的流程,而lambda肯定会降低UT覆盖率的。 那么我们还是把状态机放在自己的服务里吧。回到之前的问题,如何通知对方状态发生变化了呢?这种基于DB的服务,脱尾是不二之选。mysql读binlog,postgresql读wal,dynamodb可以stream。我们可以把最新的修改发到queue,让关心的服务去订阅,或者主动通知(lambda调用)。 核心技术就是SNS、SQS和kinesis。我们大部分服务使用SNS和SQS就够了,可以FIFO,也可以吞吐量优先,都是HTTP+JSON的接口。如果需要高级的stream处理,那就上kinesis。 存储 有了API gateway、lambda、sns、sqs,这个架构的形状就出来了。 说说存储。AWS提供了关系型RDS,主推二次开发的Aurora,可兼容mysql和pg,还有低版本serverless DB。serverless DB可以使用生产的snapshot快速拉起一个测试环境,跑完测试再杀掉,听上去还是很美好的。没有常驻实例也便宜不少,生产环境没听说有谁用。 Dynamodb,更像是cassandra的NOSQL,但是不是大宽表,最大支持400KB。适合存放用户信息数据,业务切入点与mongodb类似。读多写少,很便宜。 对于技术选型,Aurora和DDB都不需要考虑分库分表,如果事务第一,选Aurora,其他情况DDB真香。 NoSQL最大问题在于表设计,难度绝对大于关系型DB。如果业务场景多变,关系型数据库更像是以不变应万变的方案。而NOSQL需要想好方方面面的CRUD场景,罗列所有API,根据这些场景设计表结构。microservice架构的好,NOSQL才能香的起来。 我着实认为,大多数情况下,我们都不那么需要关系型DB,毕竟我们要开发微服务,NoSQL更友好。还有一个关键点,技术实例足够强,用啥都是用,技术都是工具。技术渣,再加上SQL,那就是面向SQL编程。一托SHI。 发布 + 监控 codepipeline可以从codecommit触发build和deploy。docker是必须的,逃不掉的。 k8s+istio不适合小公司,istio不适合大公司。。。 生产服务核心的问题是:滚动部署和auto scaling。ECS足够了,开几个ec2组成cluster,把自己的服务放上去。如果有必要,可以组一个spot cluster,便宜到炸。 cloudwatch新建metric,检索log中的关键字pattern,比如“Error”,“Exception”。然后设置Alarm并触发报警邮件。 如果想再完善,可以在特定的branch部署完后跑集成测试。 ETL 不管是binlog、wal还是ddb stream,所有数据都可以通过kinesis firehose实时同步到S3,增量的全量数据。可以用spark还原原始表。

December 3, 2019 · 1 min · magicalne

如何解决Fedora启动慢的问题

家里的台式机是Fedora30/Win10 dual OS,Win10启动是秒开,毕竟有SSD。但是Fedora30开机慢的要死。起码需要1分钟了。 现在开始解决。 systemd-analyze blame 首先查看启动慢的原因,我的机器有两项超过30秒 systemd-udev-settle dnf-makecache.service systemd-udev-settle是跟驱动有关的。我是N卡,Fedora30自带的驱动是不兼容的,我是后面进入terminal另装的驱动。不知道跟这个是不是有关系,估计是个bug。看网上说这个是可以通过mask跳过的。 所以: systemctl mask systemd-udev-settle dnf-makecache.service这个东西想不明白为什么要放在启动之前。看网上说确实是可以放在启动之后的,so: sudo systemctl disable dnf-makecache.service sudo systemctl disable dnf-makecache.timer 来测试一下效果吧。 reboot . . . systemd-analyze blame Startup finished in 9.589s (firmware) + 1.413s (loader) + 1.353s (kernel) + 1.609s (initrd) + 8.030s (userspace) = 21.995s graphical.target reached after 3.070s in userspace 虽然有21秒,但是纯启动时间是能到秒的,满意。

November 27, 2019 · 1 min · magicalne

在netty中同时使用http proxy和websocket带来的问题

最近在用netty开发自己的交易系统。http那块没啥问题,很流畅。在开发基于websocket的market data部分出现了问题。表现为可以与server建立http连接,在upgrade的时候也成功,而且也接收到了server发送过来的数据,但是不能向server发送数据。msg已经被encode成WebSocketFrame了,但是没有发送到server。 因为远端server被墙了,所以只能加代理。init的时候在最前面加上HttpProxyHandler。handler pipeline看起来像这样: http proxy handler -> ssl handler -> http client codec -> http agg -> my handler 问题就出现在netty官方提供的WebSocketClientHandshaker。具体issue 在websocket upgrade的时候(也就是websocket handshake),需要加入websocket encoder和decoder,然后删除http client codec。netty在这里没有处理存在http proxy的情况,默认删除第一个http codec。而且插入websocket encoder和decoder的位置也错了。 这就导致我之前遇到的问题,在消息encode好的情况下,无法通过http proxy handler发送出去。 我的解决办法是继承WebSocketClientHandshaker13,自己实现了handshake0和finishHandshake0,并在初始化pipelie的时候使用别名。

April 16, 2019 · 1 min · magicalne

Hello, Bitcoin!

本着后知后觉的行事准则,18年1月的时候开始关注比特币。算是决定学习一点金融知识,但是迟迟没有动手。当到了年中的时候,比特币价格大约在6600USD上下。那时觉得可以入一波,就花了3万块在huobi上买了点。3个星期后卖掉了,算是挣到的第一桶金,大约1200人民币…后来同样的伎俩又挣了几百块吧。 我意识到这样搞是不行的,没有优势可言,完全依照人类的直觉判断,不够理性。然后开始在huobi上写写策略,尝试用代码挣钱。前前后后好几个月,从huobi转战到binance,without exception,I failed…什么三角套利,网格交易,这些简单的伎俩在手续费面前都是扯淡。再加上交易所糟糕的基础设施,想挣钱不要太难。 那时候深知一个道理,早早放弃相当于前功尽弃,停留在表面是学不到东西的,一定要深入底层。通过这段时间的学习,我对金融有了更深的理解。交易真的很有趣,我不再关注k线,均线什么的指标了。我开始研究market microstructure,limit order book,order flow等等。我认定高频交易是最符合我性格/技术的交易系统。 首先我回顾了自己开发的策略,都是传统策略,也就是网上能找到的东西,确实没有难度。另外,就是手续费。高昂的手续费在没有优势的高频策略里简直就是杀手。后来就换到了bitmex上,因为bitmex的limit order有rebate,我后面的策略就是去赚rebate。 来到了bitmex,开始学习期货知识,研究bitmex上的future contract和swap contract。把bitmex ceo放在youtube的视频都看了一遍,其中包括market making和arbitrage。这些视频对建立基本的金融常识起着至关重要的作用。 我开始在google上搜索market making的相关信息,后来看了很多论文,一开始非常迷茫,这些论文最水的是直接套机器学习模型,抽一些timeseries features、order imbalance features。这种论文指导性一般,这些特征自己也能想出来,实际测试下来也确实一般。模型输出的信号不能直接作为交易信号。因为模型胜率一般,并且会导致一直无法成交。但是理论上应该可以作为辅助来提升策略的优势,这是接下来要优化的点。另外很多论文都是跟随机过程相关的,通篇的公式,上来就是XX分布。好在,without exception,我没有放弃… 这些论文我统一理解为金融数学建模,无非是针对limit order book和order flow进行建模,模型开始都是基于布朗运动,到最后求一个近似最优解。以此作为输出信号,根据当前的position,在两边挂单。在一定条件下取消订单,或重新挂单。总之,目的无非是在挂单时结合当前的position算一个最优的spread,这个spread希望可以让order快速成交/平仓,也就是创造优势。 这里面最难解决的问题就是spread。spread太小的话,很快就被市场runover。如果太大,会迟迟不能成交,最后导致整个策略不收敛,没法快速平仓,裸露的头寸成为最大的风险点。 在刚开始实现自己的bitmex做市策略时,我直接上机器学习,没有效果可言…然后尝试去掉模型,直接跟市场拼网速,结果也不行…原因是bitmex的websocket推送延时太高。尽管我知道bitmex使用的aws,我也在相同region开的ec2,但是我不知道这个是bitmex有意为之,还是本身就没有良好的基础设施建设。总之,推送的order、order book、order flow延迟都很高。跟高频交易的要求差距太大太大了。 这时候是有点灰心的,有点想放弃。后来有一天,看到有人用很挫的策略在botvs上直播,居然还天天挣钱,也是醉了,还收智商税…自己很不服气,接着干。我回顾了之前犯过的错,我发现归根到底有两个问题我没有解决: spread多少合适,如何根据仓位计算skew; 止损。 结合之前总结的教训,我需要从宏观上解决这两个问题。首先考虑的就是我能不能抗住bitcoin一天10%的波动。4、5个点的波动对于币市来说太司空见惯了,没有波动就没有市场。我的策略一定要能抗住10%的波动,也就是说我希望两次10%波动之间,我是能赚钱的!这里没有考虑对冲,纯粹在策略上实现。我不需要追求稳定的收益,比如每天1%收益,这个很没必要。 另一方面,高频率的市价止损太可怕了,本身做市策略就是高胜率、低盈亏比的,加入大量止损,实在没意义了。我决定从两方面止损,一是快速挂单求平仓,二是取消底部买单和顶部的卖单,让策略尽快的反向交易,自行止损。 感谢bitmex良心赌场,开放了所有数据,我可以通过历史的quote data进行回测。我选用了过去一年的数据,而不是两年或者更多,不算杠杆,年化是有100%的。实测下来从元旦到今天18号,10X,也有32个点的收益了。但是,这并不意味整个策略足够安全。期间bitcoin从3800直冲4000+,毫无理由,我的策略确实亏了点收益,但是我作为人类都能认定,价格一定会跌下来。过了3天不到,价格就跌下来了,把之前亏的赚了个底朝天,接近10个点的收益。然后到3500+,都扛下来了,收益也很稳定,就等待下一次10个点的下跌了。 想到的问题还是挺多的,能优化的点也很多。线上用0.1个btc在跑,本身这个策略的资金利用率就不高,而且我还用了10X杠杆,头寸还是比较小的。假使我用1个btc,仍然是10X杠杆的话。假如遭遇+10%的波动,可能会在某个位置爆仓。确实,10X杠杆太作了。另外,整个策略没有信息优势,不会迎合市场进行抬价降价,这个是接下来重点优化的点。最近也在恶补随机过程,希望能从数学模型上再增加点优势。最后就是币价风险,这里后面可以实现动态对冲。首先,在收益趋于稳定时,手动加入对冲。毕竟整个策略是用来赚币的,但是行情看来,币价3000不保啊。 交易很有趣,作为一个软件工程师,真希望能哪一天辞掉工作,安心在家全职交易起来。另外心态、性格都很重要。关于交易的策略,西蒙斯的话还是很受用的:多想想别人没想过的东西,尝试稀有的思路,剩下的交给运气。我自己本职工作还是一名软件工程师,最擅长的还是发现问题,解决问题。本身考虑交易的出发点就在于自己穷,想解决这个困然我终身的大问题…对于今后几十年的财富管理也算有点眉目了吧。 以上。再见2018👋!

January 18, 2019 · 1 min · magicalne

如何在kubernetes上构建grpc服务以及负载均衡

最近一直在搞kubernetes上的服务化。首先切到kubernetes上的项目是一个没有状态的data worker。一切如丝般润滑,简单到爆。但是为了部署我的grpc service时,引入了istio,一切变得艰难了好多。 首先,如果不考虑istio,单看kubernetes的话,这里需要定义一个Deployment,方便进行rolling update。然后,grpc service是一个service,这里还需要定义一个Service。此时,kubernetes的工作就结束了。到这里为止,k8s cluster上有一个grpc service instance了。我们理应可以通过ip访问这个服务。但是如果我们希望有多个instance,如何做load balance? 这里就要引入istio了。istio管理了kubernetes上所有的网络通信。所有跟网络相关的配置,都需要走istio。 如何构建全局的load balancer 我希望可以通过一个域名就能访问到k8s集群上所有的服务。使用kops在aws上创建的集群已经有一个aws classic load balancer,这里就可以直接使用了。这个load balancer不做任何事,只是把请求转发给istio的ingress gateway。注意,这里的loadbalancer是用来访问需要暴露到outside的服务。 Gateway & Virtual Service Istio使用ingress gateway来管理所有流入的流量。我们需要创建不同功能的gateway用来从ingress gateway中分流到自己所需的流量。但是gateway不能单独使用,这里还要配合virtual service。virtual service & gateway共同定义了流量特征。 比如,对应http服务,我希望获取uri开头为/hello的请求,并将这些流量转给端口号为8080的hello-world service。这里就需要定义virtual service和gateway来做这件事。 在这里,我看文档的时候有点懵。因为我发现virtual service和gateway有一些相同的配置,我不明白这两个为什么一定要拆开定义,这里的设计没太看懂。 另外,istio是以sidecar的形式,在初始化服务的时候,将转发的配置信息注入进来的。所以,你会看到一个服务会有两个pod:服务本身和sidecar。 最后,kubernetes和istio对gRPC都十分友好。gRPC使用的是HTTP2,这里在定义virtual service的时候也可以通过uri prefix的方式分割不同的服务流量。 配置好的yaml看起来就像下面这样: apiVersion: networking.istio.io/v1alpha3 kind: Gateway metadata: name: main-server-gateway spec: selector: istio: ingressgateway # use Istio default gateway implementation servers: - port: number: 8888 name: grpc protocol: GRPC #or GRPC, which gives the same result hosts: - "*" --- apiVersion: networking....

October 9, 2018 · 1 min · magicalne

如何让kubernetes中的服务访问外部hbase

Istio通过修改iptables,以此来管理网络访问可行性。也就是说,你在部署你的服务之前,必须先定义好你需要访问的一切。是一切哦。 Service Entry 这就是通过定义service entry来实现的。如果是访问内部服务,貌似只需要指定要访问的service name作为host。访问外部服务就有点麻烦了。 HTTPS 我的服务中需要访问aws codecommit,这就有点烦了,不过官方文档还是能找到例子的。对于HTTPS的external service,不仅仅需要定义service entry,还要定义vritual service。 apiVersion: networking.istio.io/v1alpha3 kind: ServiceEntry metadata: name: external-svc-codecommit spec: hosts: - git-codecommit.ap-southeast-1.amazonaws.com ports: - number: 443 name: https protocol: HTTPS resolution: DNS location: MESH_EXTERNAL --- apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: external-svc-codecommit spec: hosts: - git-codecommit.ap-southeast-1.amazonaws.com tls: - match: - port: 443 sni_hosts: - git-codecommit.ap-southeast-1.amazonaws.com route: - destination: host: git-codecommit.ap-southeast-1.amazonaws.com port: number: 443 weight: 100 从上面可以看出,对于https的请求,需要先访问到virtual service中,然后再转到外部的https请求。这个实现也是挺曲线救国的。 访问HBase集群 访问HBase有一点tricky,因为HBase是富客户端模式,client需要访问master,zookeeper和region servers。所以对应的,要找到它们在集群的端口号。...

October 9, 2018 · 1 min · magicalne