宜家毕利书柜DIY安装经验和教训

商品:

  1. BILLY 毕利 / OXBERG 奥克伯,书柜,带玻璃门 白色/玻璃 40x30x237 厘米。货号:595.283.49。
  2. FIXA 费克沙,螺丝和螺塞12件套 银色/灰色。货号:403.075.12。

工具:

  1. T型棘轮螺丝刀。
  2. 梯子。
  3. 电锤,带6mm和8mm的钻头。

选购:

  • 毕利书柜零件含上墙固定件,不含上墙螺丝。需要自己根据墙壁类型选购固定螺丝。混凝土墙需要膨胀螺丝。我选择了工作人员介绍的费克沙螺丝。
  • 根据工作人员介绍,TRIXIG 缇克西电动螺丝刀/电钻,无论3.6V,还是12V,都不适合在混凝土墙上打孔。我选择了借用邻居的电锤。
  • 四组595.283.49,市内运费229元,安装费用300多(含上门费一百多)。我选择了DIY安装。房子6楼电梯房,师傅到约定时间送货进门。

安装:

  1. 按照说明书,把书柜(含加高件)组装起来。我把门、隔板的安装,放在上墙固定之后。
  2. 把书柜立起来。书柜会自靠在墙上。
  3. 上墙。这步最难,因为说明书上没介绍具体细节,网上也没经验分享。
    费克沙膨胀螺丝的膨胀管直径8mm、长度39mm,螺丝直径4.5mm、长度55mm。用8mm的钻头,在墙上打出深39mm的孔,再用6mm的钻头把孔加深到55mm。打孔时几个注意事项:a.注意钻头的旋转方向,错误的旋转方向会导致事倍功半,打出的孔也会过大。b.孔一定要有55mm深,即膨胀管和螺丝的最大长度。c.避开墙内的管线。
Posted in Uncategorized | Leave a comment

PaddleNLP安装

PaddleNLP可能是安装体验最差的ML程序了。安装和安装过程中,你可能会遭遇一系列错误。下面挨个介绍。

说明,我的操作系统是Fedora 38和Debian 12。当前PaddleNLP最新版本为:v2.6.1,Paddle最新版本为v2.5.0。下面只包含我当前遇到过的问题。

一、libssl.so.1.1: cannot open shared object file: No such file or directory。

系统没有OpenSSL 1.1。Fedora系统,直接安装openssl1.1即可。Debian系统,需要下载OpenSSL 1.1源码,编译安装。

二、MemoryError: (ResourceExhausted) Fail to alloc memory of XXX size。

不是电脑内存不足。切换到PaddleNLP 2.4.0和Paddle 2.4.2即可。

三、实例代码执行结果与预期不符。比如对“2月8日上午北京冬奥会自由式滑雪女子大跳台决赛中中国选手谷爱凌以188.25分获得金牌!”执行information_extraction信息抽取,结果为空。

切换到PaddleNLP 2.4.0和Paddle 2.4.2即可。

四、PaddleNLP和Paddle所依赖的protobuf版本冲突。

PaddleNLP 2.4.0和Paddle 2.4.2依赖的protobuf版本不冲突。或者可以查下PaddleNLP和Paddle各个tag下requirements.txt要求的protobuf版本。

五、pip提示:ERROR: Could not find a version that satisfies the requirement paddlepaddle==x.y.z。

换个Python版本。 我是换到Python 3.10。原因还未知。如果是Fedora系统,直接安装python3.10即可。如果是debian系统,需要下载Python 3.10源码,编译安装。

六、编译Python3.10时,make test报错:_ctypes.c:107:10: fatal error: ffi.h: No such file or directory。

安装libffi-dev后,重新编译。

七、Python3.10报错:ModuleNotFoundError: No module named ‘_bz2’。

安装libbz2-dev后,重新编译安装OpenSSL3.10。

八、Python3.10报错:ModuleNotFoundError: No module named ‘_lzma’。

安装liblzma-dev后,重新编译安装OpenSSL3.10。

综上所述,建议直接按下面的办法安装:

直接在Python3.10上安装PaddleNLP 2.4.0和Paddle 2.4.2。如果需要编译安装Python3.10,先安装依赖。依赖清单的完整版本:build-essential zlib1g-dev libncurses5-dev libgdbm-dev libnss3-dev libssl-dev libsqlite3-dev libreadline-dev libffi-dev curl libbz2-dev liblzma-dev。

Posted in Uncategorized | Tagged , | Leave a comment

一种从客观数据到代码实现的编程思想

这套编程思想,从我日常工作中总结得来,用于解决一些复杂业务问题。内容偏抽象,表达能力有限,不喜请绕道。

一、调研客观数据,放弃主观思考。

以往解决问题,往往基于认知和思考。认知是有限的,主观思考易产生偏见。

解决问题,就从问题本身出发,尽可能搜集问题的表现数据,找出数据特征和规律。最后,程序要的做,就是把数据的特征和规律描述出来。

二、程序是数据的处理过程。

所有程序,最顶层的抽象,都是数据的“输入 – 处理 – 输出”。如下图所示:

实现中,中间的“处理”过程,是程序的主要复杂点。“计算机科学领域的任何问题都可以通过增加一个间接的中间层来解决”。对于复杂的处理,一个中间层不够,那就多个,于是可以得到:

在上面的模型中,每层都封装了数据。一层的数据,转换为下一层数据的过程,就是逻辑。

下面是一个虚构的商品请求程序模型:

这个商品请求程序模型,存在五层数据。第一层的数据是“商品请求数据”,通过逻辑“查询商品”,转换为第二层的数据“商品基础数据”……

程序也可以描述为:输入数据,经过多层转换处理,最终得到输出数据的过程。

三、数据模型和关联。

数据(模型/结构)由属性/字段组成,不带操作/逻辑(逻辑被视为数据的转换过程)。每层有一到多个/块数据。单层内的数据之前可能存在关联,这种关联在代码上表示为数据间的组合。

下面是一组虚拟的用户和钱包数据模型:

上面的数据模型,如果用代码来实现,可能是下面这样的:

type Wallet struct{
    Currency string
    Balance  string
}

type User struct {
    NickName string
    Wallet   *Wallet
    Credit   int
}

四、完整的可实现的程序模型。

一个程序模型示例(省略了数据模型细节):

在上面的示例模型中:存在多道程序逻辑(数据转换逻辑),模块间的边界清晰,部分数据和逻辑被复用,部分数据被复制而不是转换到下一层(实际代码中可能什么也没干)。

下面是一个虚拟的下单请求程序示例(只有一道程序逻辑):

最后要做的,就是用代码,把这个程序模型描述出来。

下面是一个真实案例的模型(省略了大量细节):

上面真实案例模型,是一个邮件发送程序,包含一个邮件发送的通用框架,这个框架支持不同业务(图中的1、2、3)的邮件。

Posted in Uncategorized | Tagged | Leave a comment

新的Go语言缓存中间件:restcache

我开发的第一套缓存中间件是cachex。cachex支持自定义存储后端,实现了哨兵机制,自带lru存储,支持系统故障时使用过期缓存数据。

cachex的主要问题是不支持批量处理。这也导致在上家公司,cachex未能上阵。我在上家三年时间一直不能开发出支持批量处理的缓存中间件,我也写了三年业务耦合的缓存处理代码。

这次的restcachefastrest组件库的一个组件。在写restcache之前,我创建了gox项目,实现了优化版的lru,和一套完整的支持批量处理的哨兵机制,然后再实现restcache就简单多了。解决一个复杂问题的一个有效方法,就是模块拆分。

一套完整的缓存,应该包括三个模块:存储、查询、中间件。三个名字是我取的,不准确。

  • 存储是存储缓存数据的部分,也可以称为后端,一般是redis客户端,或者进程LRU存储。存储逻辑都是透明处理业务数据,也就是存储逻辑是通用的,不用对每个业务逻辑写一套存储代码。
  • 查询是存储查不到目标数据时,提供新缓存数据的部分,一般是从MySQL等持久化数据库或者服务接口查询。很遗憾,查询逻辑是耦合业务数据的,需要对每个缓存业务写一套定制的查询代码。
  • 中间件是响应用户查询,调用存储和查询的胶水逻辑,也就是实现了典型的缓存流程。这个也是通用的。

cachex和restcache实现的就是缓存的中间件,并定义了存储和查询的接口。restcache的用户需要自己实现存储和查询。restcache已经附带了一套lrucache存储,可以直接用。但因为用户的redis库版本会不同,restcache没有实现redis存储。

典型的缓存流程如下:

  • 先查存储。如果找到,返回结果,结束;如果没找到,可能是因为没存或者已经过期,继续下一步。
  • 调用查询,查到新缓存数据,保存到存储,返回结果,结束;如果没查到,返回notfound,结束。
  • 下次查存储,查到上一步保存的新缓存数据。糟糕的情况是notfound,会重复第一、二步。

应该绝大多数缓存都是这个流程。这个流程的主要麻烦有两个:

  • 缓存失效风暴,即大面积缓存失效。缓存本意是避免执行大量重复的低效的查询,但大面积缓存实效会降低缓存的意义,严重的会导致后面的服务失去服务能力。
  • 批量查询。实际场景中,很多列表的查询。批量处理,会让流程中的每个环节变复杂。

解决方案为:

  • 对于缓存实效风暴,cachex和restcache都用上了哨兵机制。我实现的叫SentinelGroup,Go团队实现了一套singleflight。SentinelGroup不见得写得比singleflight好,但强在支持批量处理。两者核心逻辑都是一致,多个过程需要同时取得相同的结果,只放行一个去做生产者,其它作为消费者等待生产者分享结果。
  • 对于批量查询,这是cachex无法实现,但restcache实现了的特性。重复下:解决一个复杂问题的一个有效方法,就是模块拆分。一个复杂模块,由几个小模块简单地构成,每个小模块都封装了一部分复杂逻辑。然后小模块又可以继续拆分为几个更小的模块。cachex的失败,在于没划分清楚缓存和哨兵机制的边界,没能定义出清晰简单的批量存储接口,导致单个模块过于复杂。解决了实现的复杂性,简单来说,缓存的批量处理,就是要从不同的来源查询数据,再把不同来源的数据组装起来。第一步需要从存储和查询中拿到数据,第二步需要从哨兵机制的生产者和消费者拿到数据。因为这两步的边界足够清楚,也就降低了整体的复杂度。

restcache刚刚开发出来,虽然我写了很多单元测试,但经过项目的考验才能成为一个成熟稳定的组件。

Posted in Uncategorized | Tagged | Leave a comment

reloader:Go服务热升级支持

以前我在公司写过一个简单的Go热升级支持程序。但是工作时间写的,领导不准开源。后我想另写一个开源版本,但一直没想到解决多端口的问题。后来就慢慢忘却了。

直到最近一次偶然的机会,这个程序被人重新提起。又遇上待业期,正需要写点东西打发时间。突然灵感来了,想到了多端口的解决方案。

于是有了reloader

思路基本是之前版本的扩展版。一个master进程,多个worker进程,一个worker进程处理一个侦听器。这样可以实现多端口的动态侦听和关闭。我又做了些进程管理的优化,推送到github。

无法否认,我的解决方案存在很多问题,因为偷偷起了多进程。比如程序在所有侦听器和连接都关闭后需要上层逻辑结束,这对于单进程无碍,对于reloader却是可能多了N个垃圾worker进程。这些问题,得等待找到更好的解决方案。

Posted in Uncategorized | Tagged | Leave a comment

重读《黔之驴》

今天计划做点无聊的事。突然想起柳宗元的《黔之驴》。原文如下:

黔無驢,有好事者,船載以入;至則無可用,放之山下。虎見之,龐然大物也,以為神。蔽林間窺之,稍出近之,憖憖然莫相知。他日,驢一鳴,虎大駭遠遁,以為且噬已也,甚恐!然往來視之,覺無異能者,益習其聲,又近出前后,終不敢搏。稍近益狎,蕩倚衝冒。驢不勝怒,蹄之。虎因喜曰:「技止此耳!」因跳踉大闞,斷其喉,盡其肉,乃去。

噫!形之龐也,類有德;聲之宏也,類有能。向不出其技,虎雖猛,疑畏卒不敢取,今若是焉,悲夫!

大意是说:黔地没有驴,一个无聊人运了头驴过去。黔地老虎没见过驴,开始很害怕,后发现驴不过“技止此耳”,跳上去,吃了驴。

故事创作于作者永贞革新失败之后。后人一致认为,作者是在讽刺朝中外强中干的权贵。

以我今天的眼光来看,故事却是这样的:一个很有想法的人,给一个没有驴子的地方运了头驴。驴新来,遭到当地传统势力的敌视,最终不敌,被吃了。这个很有想法的人,就是指作者自己,驴指永贞革新,老虎指当时朝中掌控实权的权贵和地方的藩镇。

我们今天谋求创新,就要做很多看似无聊的尝试,敢于承担失败的风险。想法的落地,则需要短时间适应现实情况,否则很快夭折。即使最终项目失败了,也未必意味着想法是错误的。现在黔地已经没有老虎,留下的是驴。

Posted in Uncategorised | Leave a comment

Python实现数据类的方法集合

当前Python最新稳定版为3.6。主流Linux发行版官方仓库提供Python版本的有2.7和3.5,默认依然是坚强的2.7。

1、dataclass装饰器。正规军,需要Python 3.7+,还在路上。dataclass装饰器会给类添加__init__、__str__、__repr__等方法。

@dataclass
class InventoryItem:
    '''Class for keeping track of an item in inventory.'''
    name: str
    unit_price: float
    quantity_on_hand: int = 0

    def total_cost(self) -> float:
        return self.unit_price * self.quantity_on_hand

2、collections.namedtupletyping.NamedTuple。要求不多的用这两个最合适了。Python 3.6+的typing.NamedTuple才支持默认参数。

class Employee(NamedTuple):
    name: str
    id: int = 3

employee = Employee('Guido')
assert employee.id == 3
Employee = namedtuple('Employee', 'name, age, title, department, paygrade')

3、autoassign装饰器,和auto_assign装饰器。算是奇技淫巧,前者支持Python2,后者支持Python 3.5+。

4、collections.namedtuple + functools.partial。通过partial实现默认参数,也是我当前采用的方案

fields = ("name", "age", "title")
defaults = OrderedDict({"title": None})
Employee = partial(namedtuple("Employee", fields), **defaults)
employee = Employee("Guido", "18")

网上还有一些相对麻烦点的方案,比如重写namedtuple的__new__方法,就不一一列举了

Posted in Uncategorised | Tagged | Leave a comment

在Linux Minecraft中输入中文——进阶:在告示牌输入中文

两年前的一篇文章,我讲到如何在Linux Minecraft中输入中文。脚本内容是转的别人的。该方法只能用于聊天,不能用于编辑告示牌。

其实只要把脚本修改下,就可以通用。只会复制粘贴,不会派生定制,做程序员的我真的不好意思。

原脚本内容为:

#!/bin/bash -e

chars=$(zenity --title 中文输入 --text 中文输入 --width 500 --entry 2>/dev/null)
sleep 0.1
xdotool key --delay 150 Escape t
sleep 0.2
xdotool type --delay 150 "$chars"
xdotool key Return

其中正文第一行为弹出对话框,接收用户输入。第三行输出t键。第五行输出对话框接收的输入。第六行输出回车。很明显,第一行和第五行才是重点。其它几行不过辅助性的,帮用户偷懒。

现在只要改下:

#!/bin/bash -e
 
chars=$(zenity --title 中文输入 --text 中文输入 --width 500 --entry 2>/dev/null)
#sleep 0.1
#xdotool key --delay 150 Escape t
sleep 0.2
xdotool type --delay 150 "$chars"
#xdotool key Return

只要注释掉辅助性的几行。

编辑告示牌时,通过快捷键执行该脚本,即可在告示牌上输入中文。如果是聊天,得先输入t打开聊天栏,再通过快捷键执行该脚本,聊天编辑栏得到中文后,敲回车——或者新脚本和旧脚本共存,绑定到不同的快捷键。

以前我在游戏中结识位朋友。他发现另一个在告示牌输入中文的办法。可惜我还没学会,就与他失去了联系。

Posted in Uncategorised | Tagged | Leave a comment

一次网件路由器OpenWrt系统TFTP刷机抢救记录

路由器网件WNDR4300,原固件为OpenWrt15.05,计划升级到OpenWrt15.05.1。下载了wndr4300-squashfs-sysupgrade.tar,通过LuCI升级。确认升级前,检查升级文件摘要匹配。

路由器重启后,不能获得IP地址,能PING通网关。不能访问luCI,SSH连接网关密码始终无效。路由器在开机后,指示灯只亮电源指示灯和有连接的LAN指示灯。

开始TFTP升级。先下载固件镜像wndr4300-ubi-factory.img。将电脑通过网线连接到路由器LAN接口。电脑网络地址改为手动设置,IP地址为192.168.1.2-254,网关192.168.1.1,子网掩码255.255.255。ping 192.168.1.1,确保可以连接到路由器。

路由器关机,按下RESET键,再开机。先指示灯全亮。等待电源指示灯由黄灯常亮变为黄灯闪烁,再变为绿灯常亮,最后变为绿灯闪烁。路由器进入TFTP刷机模式。松开RESET键。利用TFTP工具将固件镜像上传到路由器。如果是使用Linux的tftp软件上传,需要设置模式为binary。

等待LuCI可以访问,设置ROOT密码,恢复备份存档——前提是你升级前备份了存档!
如果tftp救不了你的机器,准备TTL刷机吧。

Posted in Uncategorised | Tagged | Leave a comment

长沙寻访古旧书店(一)

首先说明,本次寻访日期为2018年1月9日。本次寻访大区域为芙蓉区,寻访依据以网友提供的线索为主。

第一块区域为湘雅附二医院一带。该区域也靠近湖南图书馆。人民路北侧,湘雅附二医院以东,有三家旧书店。三家书店都是小门面,提供二手旧书。我淘到一本人民出版社的《国际共产主义运动史》,1978年版。向老板询价,老板示意书背面。翻过来看,价格在背面用铅笔标注了,13元。

结账时,又向老板打听附近可有其它旧书店,老板告知复兴街还有家。复兴街其实只能算巷子。入口位于附二医院西边。人民路附二医院西边的口子进去不远,可以看到一家“复兴参观”,进入就是。复兴街北接白沙路。穿过林立的小餐馆,进入相对安静的居民区,就可看到一家孤立的旧书店。该店比之前三家店面稍大,卖的也都是旧书。

既然来到白沙路附近,就免不得去天心阁一带。简牍博物馆下面古玩商店不少,然而没有旧书店。询问了老板,才知道要等到周末才有旧书摊。只好作罢。

下一站八一路。从简牍博物馆到八一路,刚好路过定王台,免不得要过去寻访一番。这次没找到定王台书市对面的述古人文书店。发现了一间民生书局。民生书局卖的是出版社库存书。库存书又免不了堆积大量湖南本地出版社的库存书。淘到一本湖南教育出版社的《青藏高原科考访谈录》,2010年版。店家要价25。这次价格没有用铅笔在书背标注。见书品相较好,价格实惠,就立马掏了钱。

IMG_20180109_134850

出了民生书局,再去搜寻述古人文书店,才发现述古已经关了门,招牌上贴上了招租的广告。

最后一站是八一路的星星书店。星星书店位置是袁家岭地铁站北面。大致占据了两个半的小门面。书店以出版社库存书为主。同前面的民生书店,星星书店也不少湖南本地出版社的书,库存书又以湖南科学技术出版社的书为主。淘到《爱因斯坦传》、《西方科学起源》、《居里一家》,三本都是湖南科学技术出版社的书,而且都是全新的,前两本还有原书塑封。店里上豆瓣搜了下,前两本评论都蛮高,后一本评价人数太少,但看介绍也不错,想必不会浪费票子。又搜到一本港版袁伟时的《文化:中国与世界》,木刻文化2010年版,扉页还有作者的签名“丁总赐正 袁伟时 2011.4.27”。丁总显然工作太忙,无暇“赐正”。书一页没翻,就流落到旧书市场。

IMG_20180109_161608

结账时算收银员价格怎么算。收银员告知三本湖南科学技术出版社的书算四折,一本中华书局的《老子注译及评介》算六折,港版的《文化:中国与世界》打了个电话请示后表示30元。果断放弃了《老子》。买单时掏出钱包所有零钱,刚好还差三毛,问能否优惠三毛。收银美女很直接地答应了。付了钱出门时,收银美女还很客气地道了声慢走。(可惜了,我习惯了没礼貌,不知道怎么回)

IMG_20180109_143946

于是满载而归,背着沉重的背包去袁家岭搭地铁回家。

Posted in Uncategorised | Leave a comment