Rust中的无锁编程技术(五)

在前面的博客里面,我们通过对“锁”的不断认识,并且对其实现的不断改进已经有一个相对比较好的模型了,即TicketLokc的实现。正如前面提到的,TicketLock就好比是一个叫号模式的锁,每当上一个线程的工作做完了,就轮到持有下一个号码的锁。比起自旋锁,TicketLock让线程之间更加具备公平性,但是我们最后也提到了TicketLock的实现其实也并没有那么的美好,无论是SpinLock还是TicketLock,所有的线程都是在为同一个内存空间打架,而解决这个问题的思路就是我们嫩故不能通过内存来换取系统的等待时间?我们的设计要尽量减少对同一个共享变量访问的线程的数量。

基于隐式链表的模型

我们从TicketLock的模型下手来解决TicketLock所带来的问题。在上一个博客中,我提到了TicketLock的实现就好像是银行里面的叫号模式,当有很多人同时去银行办事的时候,大家都会卡在叫号处,这正好对应了TicketLock里面的self.curr,也就是我们的问题所在—叫号模式所有的负担都集中在了叫号的地方,也正是因为这个,大量的线程在对同一个共享变量进行访问。我们能不能将叫号这个点的负担分散出去呢?

假设有A,B,C,D四个人同时去银行办业务,我们先让A去办业务,并且A将会给B一个闹钟,同理,B拿到闹钟之后,会给C一个闹钟,以此类推。当A办完事情之后,A之前给B的闹钟会响,这时候B就可以去办业务了,以此类推下去。对于每个人来说,他都会从前一个人那里得到一个闹钟,当闹钟没有响的时候,表示的是前面的人还正在办业务,没有轮到自己去办业务,当闹钟响的时候,就表示轮到自己了。

我们仔细来思考一下这个新的模型。A,B,C,D四个人之间其实是一个隐式的链表,对于每个人来说,都在等待前一个人对其的唤醒。相比起之前TocketLock的模型,大家并不需要都卡在一个地方等待分发号码,相反,每个人只需要等待前一个人的唤醒。通过这样的模型,我们就可以将叫号这个点的负担分散出去了。

MORE

Rust 中的无锁编程技术(四)

在之前的博客里面,我们通过最基本的SpinLock的实现对锁有一个比较完整的认识了,但同时我也指出了自旋锁实际上是相当粗糙的,我们只要仔细想想其工作原理就会发现,最大的问题是线程与线程之间毫无公平性可言,导致这个结果的本质原因是对于Atomic类的compare_and_swap对于不同线程之间是完全随机的,也就是这个函数没有办法控制哪个线程将会成功置换共享内存中的值。那么从理论上来讲,如果我们的操作系统没有很好的处理好CPU调度的优先级的问题,有可能有的线程会饿死,而有的线程可能可以一直占据共有资源。那么我们如何解决这个问题呢?

一个更好的模型(TicketLock)

我们先思考一下SpinLock的模型:

  1. 有多个线程在同时试着置换其AtomicBool
  2. OS会在这些线程中随即选一个线程,让其成功置换AtomicBool的值。
  3. 其他线程会一直卡在代码某处,一直自旋。
  4. 当上一个线程结束了对共享资源的使用后,会回到上诉的第二点。
MORE

Rust 中的无锁编程技术(三)

在前面两篇博客中,我们从两方面介绍了“锁”。第一个方面就是我们常说的“锁”其实是一个协议,这个协议从外部给出了锁的行为规范。另一个方面是“锁”和数据的绑定。但是我们一直没有提及究竟如何实现“原始锁”(请参考前文,“原始锁”即是上面提到的协议),这个就是这个博客要解释的。

Atomic 以及 Memory Ordering

在介绍如何实现原始锁前,我们要对最基本的概念有一定的了解。对于原子类,我会做简单的介绍,因为原子类的实现牵扯到 CPU 的架构,牵扯到 ISA,我尽量从一个软件工程师的角度来解释这个基础类。而 Memory Ordering 我将会在后面的博客花大篇幅来介绍。

MORE

Serverless产品的学习与比较

All函数ServerlessZHWebAssembly

Serverless 作为一种新的架构风格,成为了这几年云计算的热门话题。Serverless 的理想是建立真正的云计算,按需付费,用完即走不需要自己去管理任何的基础设施。本文从几个开源 Serverless 平台结构分析入手,带大家了解一下当前现有 Serverless 平台的架构。

一、Apache OpenWhisk

OpenWhisk 是由 IBM 在 2016 年公布并贡献给开源社区,IBM Cloud 本身也提供完全托管的 OpenWhisk FaaS 服务—IBM Cloud Function。从业务逻辑上看,OpenWhisk 为用户提供基于事件驱动的无状态的计算模型,并直接支持多种编程语言(理论上可以将任何语言的 runtime 打包上传,间接调用)。下面我们看下 OpenWhisk 的架构图:

MORE

Rust 中的无锁编程技术(二)

虽然这个系列博客的标题叫 Rust 中的无锁编程技术,但是我们要先了解一个锁的工作原理,才能够很好的理解无锁编程。在上一篇的博客中,我提到了“锁”就是一个协议,协议仅仅只是给出了各种“锁”的行为规范,也就是对于一个“锁”必须有的外在功能是什么,但是我并没有提及“锁”的另一个很重要的方面,就是和数据的绑定

锁与数据

“锁”应该和其所保护的数据紧紧绑定在一起的。但是我们之前对“锁”的定义是一种协议,而在编程语言中,协议是不和数据相关的,所以细心的读者应该能发现在上一篇博客的代码中:

pub trait RawLock: Default + Send + Sync {
    type Token: Clone;

    fn lock(&self) -> Self::Token;

    /// # Safety
    ///
    /// `unlock()` should be called with the token given by the corresponding `lock()`.
    unsafe fn unlock(&self, token: Self::Token);
}

协议的名称实际上是 Rawlock,也可以称之为“原始锁”。确实在上一篇博客中我一直在把 Rawlock 说成为中文里面的“锁”并不能说是一个很准确的翻译,并不是有意误导大家,而是确实一般我们口中常说的“锁”其实就是“原始锁”,是对“锁”的行为的描述,但是只有协议是不够的,正如标题所说,“锁”要紧紧的和其保护的数据绑定在一起:

#[repr(C)]
pub struct Lock<L: RawLock, T> {
    lock: L,
    data: UnsafeCell<T>,
}

unsafe impl<L: RawLock, T: Send> Send for Lock<L, T> {}
unsafe impl<L: RawLock, T: Send> Sync for Lock<L, T> {}

严格意义上,这个才是“锁”的真面目。既有对应的行为规范,也就是要提供tokenlock函数以及unlock函数的实现,也有对数据紧紧的绑定。

值得注意的地方是UnsafeCellUnsafeCell是最基本的 interior mutability 的类型了。RefCellCell都是建立于UnsafeCell的。而UnsafeCell!Sync 并且 Send的,但是“锁”当然是应该可以Sync的,因此我们人为的给Lock加上SyncSend

guard 与数据

在上一篇博客中,我提到了 token 是一个证明,但是和“锁”一样,光有 token 这个证明是毫无意义的,这个 token 也应该紧紧的和数据绑定在一起,从编程语言的语义来说就是:持有这个 token 的线程可以对锁里面的数据进行操作。我们一起看看 guard 是怎么对 token 进行封装的。

pub struct LockGuard<'s, L: RawLock, T> {
    lock: &'s Lock<L, T>,
    token: L::Token,
    _marker: PhantomData<*const ()>, // !Send + !Sync
}

unsafe impl<'s, L: RawLock, T: Send> Send for LockGuard<'s, L, T> {}
unsafe impl<'s, L: RawLock, T: Sync> Sync for LockGuard<'s, L, T> {}

这里非常有意思的是关于 Rust 的 lifetimes 的使用。参照我们上面关于Lock的实现,guard实际上就是对证明以及数据的绑定。而 lifetimes 在这里的语义是:guard的生命周期必须小于或等于lock的生命周期。Rust 通过 lifetimes 非常好的表达出了guardlock的关系。

也就是说,一个线程在持有guard的时候,这个线程可以对其lock里面的数据进行操作。所以我们的“锁”应该暴露出来一个lock函数,允许当前线程去试着获取guard,而在guard被 drop 的同时也意味着当前的共享资源又可以被下一个线程访问了,所以我们需要在drop函数做一个unlock的动作。

PhantomData就不在这里展开说了,这个和 drop check 有关,感兴趣的小伙伴自己去看看。

impl<L: RawLock, T> Lock<L, T> {

...

    pub fn lock(&self) -> LockGuard<L, T> {
        let token = self.lock.lock();
        LockGuard {
            lock: self,
            token,
            _marker: PhantomData,
        }
    }
...

}
impl<'s, L: RawLock, T> Drop for LockGuard<'s, L, T> {
    fn drop(&mut self) {
        unsafe { self.lock.lock.unlock(self.token.clone()) };
    }
}

对于持有guard的线程可以通过derefderef_mut的方式来对数据进行操作:

impl<'s, L: RawLock, T> Deref for LockGuard<'s, L, T> {
    type Target = T;

    fn deref(&self) -> &Self::Target {
        unsafe { &*self.lock.data.get() }
    }
}

impl<'s, L: RawLock, T> DerefMut for LockGuard<'s, L, T> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        unsafe { &mut *self.lock.data.get() }
    }
}

最后缺失的一步

我们现在对“锁”有一个更加全面的了解了。从一开始我们对“锁”的认识只是一个协议,到现在了解到“锁”还需要对其保护的数据有一个绑定关系,我们还了解到所谓的guard的本质是证明以及数据的绑定。我们下一个博客就和大家说说最后缺失的一步,究竟如何实现RawLock协议。

远程团建

我这里说的远程团建,即线上团建。我因为有此经历和需求,也觉得这个话题挺有意思,所以想聊一聊。 我聊的更多是远程团建形式,而且或许更适合小团队。

团建形式

就地取材

远程团队成员工作场景不一、城市各异,我们正好可以借此差异,就地取材搞团建,比如:

花式马拉松

凑巧,我们团队有的是跑步爱好者、有的拥有不同跑步工具,所以,我琢磨着哪天可以来一场花式马拉松。有工具的上工具,比如跑步机,动感单车等;有场地的去场地,什么浪漫的海边、美丽的健康步道都可;都没有的可以去园区里找辆共享单车。然后寻一良辰吉日,各定一个小目标,在约定的时间里开跑,具体细节可以大胆安排,应是不错的。

一起看世界

远程伙伴分布在各自的地盘,总有特色的吃食、有意思的地标、特别或赏心悦目的玩意儿等,独乐乐不如众乐乐,大家可以约着,直播“吃喝玩儿乐”。 具体来说,可以是单一主题的,比如“舔屏广州/重庆/温哥华”、“约会苏州/泉州/橘子洲”,也可以是综合主题的,伙伴们想出镜什么就直播什么,同时可以随意聊聊分享对象的背景、特色内容、出镜原因。

共建项目

黑客松

黑客松即“编程马拉松”,成员们约定一个时段,比如某日的 10:00-16:00,以他们想要的方式完成一个项目。我们建议成员选择自己不擅长的领域,现场构思、边学边写,不必纠结完成度,不必设定条条框框,鼓励天马行空。 非技术团队,可以安排玩别的项目。

做“年夜饭”

远程做“年夜饭”,这里肯定不是说大年三十搞团建,只是大家一起做一顿丰富如年夜饭的大餐。我认为必须设定几个条件,增加难度和未知的刺激。比如:

  • 规定的菜单,菜式各异,总菜品数(含饭和菜)要大于等于人头数,如 10 人参加则总菜品数要大于等于 10;
  • 确定的预算,准备“总菜品数”个大小不一的数字,这些数字等于买菜可支出的预算,不可超出预算,不可挪用自家原有的材料;
  • 规定的时间,视情况安排;
  • 随机的搭配,每人先随机抽取自己的菜品,然后随机抽取一个预算数字。
  • 最后大家各自照单买菜、做饭,到点上菜、吃饭,过程中难免出现抽到不会做的菜,或者 5 块钱做一份水煮肉片等等,想想竟会流口水,当然也可能会惨不忍睹。

轮番当家

团建可以是由特定的组织者或者部门全权负责,也可以是成员轮流牵头。前者是普遍做法,或许,我们偶尔也可以让成员们轮番当家,此时,要求不应太严苛,鼓励大家成功“出圈”。 这个是我实践过的,形式是由团队全员轮流安排一个“我的 45 分钟”,形式不限、内容不限,各自提前 10 天准备各自的主题和内容,不泄题、保持神秘感。 团建结果风格迥异、百花齐放,既能学东西、玩起来、刷新认知,也成功“引诱”成员们主动嚷着以后还要再安排起来。尽管,我被建议下次来一个家乡方言的歌曲,我的天,长沙花鼓戏,期待一下?

关于“形式”

我上述说的远程团建的三种形式,并非各自独立,只是突出各自重点。“就地取材”的乐趣在于发现、启用与分享,“共建项目”在于合作,“轮番当家”在于换位思考与惊喜,三者完全可以搭配组合。 比如组织 “我们的文案我们的体”:

  1. 从各团队分别选一个代表,完成命题文案的某部分,要求文案书写风格与自己团队性质保持一致,比如行政体、财务体、营销体、研发体等等,最后合成整个文案,这属于“共建项目”的形式;
  2. 再加一个任务,比如在分享文案时,分享者应当取一个道具边分享边打节奏,至于什么道具、什么节奏,可随意发挥,这便是“共建项目”与“就地取材”的结合;
  3. 如果组织一次后大家尝到甜头并且有感而发,那就“轮番当家”,安排起来呗。

这些形式不是线上团建特有的,线下当然也可以组织。当然,也还有其他形式,也有很多线下团建游戏也可以直接搬到线上来,更有直接找第三方团建公司来安排,这里不赘述。

回头看,会问自己,怎么看待团建呢?我认为不拔高团建目的,让参与者觉得被尊重、有意思、轻松快乐,这就很重要。 我本文仅仅是抛砖,如果大家也有经验和兴趣,欢迎分享和指教。

Rust中的无锁编程技术

AllRustconcurrentprogramlockZH

Rust 中的无锁编程技术

当多线程同时对共享资源读写的时候,我们需要用锁去保护这个共享资源。这里有几个概念,一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并行多个线程,每条线程并行执行不同的任务[1],共享资源就是“堆(heap)”中的一段数据,那么们平常说的“锁”究竟是什么呢?而我们平常听到的无锁编程究竟是什么呢?在 Rust 开源社区中,crossbeam 这个库究竟给开发者们提供了什么工具,解决了什么问题呢?这些问题我们会慢慢在以后的文章中给大家解答。

我们要解决的核心问题

多线程带来的最大的问题在于其不确定性。在并行并发计算中,我们是没有办法控制 cpu 什么时候执行哪条线程的,我们为了最大限度地享受 cpu 所给我们带来的性能优化,只需要将所有的工作交给 cpu,让 cpu 自己决定什么时候执行什么指令。不仅仅是 cpu,实际上,编译器,操作系统都会有自己的优化策略,都会给我们的程序运行带来不确定性。所以我们在写多线程程序的时候,脑海里面时时刻刻要记住我们最大的三个敌人:

  1. cpu
  2. 编译器
  3. 操作系统

cpu 和编译器都会根据自己的优化策略去更改指令的执行顺序,而操作系统是负责线程调度的,什么时候哪条线程获得了 cpu 的使用权,这是我们无法控制的。

核心问题带来的另一个问题

上面我们只提到了多线程编程指令执行顺序上的不确定性,而忽略了另一个很重要的环节,那就是共享资源的内存管理问题。正是因为线程执行顺序上的不确定性,这使得对于共享内存的管理变得格外复杂。比如在处理无锁数据结构的时候,从一个线程共享的链表里面删除一个节点,我们是否能立马释放其内存?答案是否定的,因为我们不确定是否有别的线程还持有对这块内存的引用。特别是对于 Rust 或者是 C 或者是 C++这样的系统语言,这些语言没有垃圾回收机制,要求编程者对于共享内存进行手动管理,至于如何管理,这也是我们以后会讨论的问题。

锁应该是最简单也是最直接可以解决上述两个痛点的解决方案了。锁给我们对多线程的运行提供了一个非常好的“模型”。这个模型是这样的:

  • 在堆上我们有一个共享数据。
  • 同时有很多线程都在尝试往这块内存写数据,或者是读数据。
  • 锁就像一个门卫,这个门卫可以保证在任意的时间点,最多只有一个线程对这块内存有使用权,这个线程可以往里面写数据或者读数据。
  • 当共享的内存发现自身的引用量达到 0 的时候释放内存。

那么这个神奇的“锁”究竟是什么呢?在 Rust 中又是如何实现的呢?

锁其实是一个协议(protocal),在 Rust 里面我们称之为 trait。我们一起来看看这个 trait:

pub trait RawLock: Default + Send + Sync {
    type Token: Clone;

    fn lock(&self) -> Self::Token;

    /// # Safety
    ///
    /// `unlock()` should be called with the token given by the corresponding `lock()`.
    unsafe fn unlock(&self, token: Self::Token);
}

这里最主要要看到这几个概念Token lock函数以及unlock函数的签名。我们今天主要讲讲这个 token 是什么。

token 我们可以将其理解为一个证明,在编程语言的语义中,这个证明是证明当前持有这个 token 的线程可以对这个共享资源进行任何操作。而其他没有这个证明的线程,只能卡在抢锁的代码处。

在 Rust 的 std 里面,lock函数返回的是一个LockResult<MutexGuard<'_, T>>,暂时不用管LockResultMutextGuard实际上就是对我们 token 的进一步的封装。

那么我们从以上的定义可以得到以下几个信息:

  1. 任何的锁,包括 spinlock,ticketlock,mcslock,等等,都必须实现以上的协议,也就是,必须提供 token,提供 lock 函数的实现,提供 unlock 函数的实现。
  2. 任何的锁,都应该是 Send 以及 Sync 的,也就是说,任何的锁都可以安全的在线程中转移(Send),并且其借用(&)也必须是可以安全的在线程中转移(Sync)。
  3. lock 函数成功返回的话,代表当前线程获得了一个可以访问共享资源的证明。
  4. unlock 函数的签名中,需要提供 token 的 ownership,并且这个函数是 unsafe 的,虽然函数本身并没有需要 deref raw pointer 这样的操作,但语言本身是无法保证调用这个函数的线程传入的 token 是否就是这个线程在之前调用 lock 成功时所返回的 token。所以我们加上 unsafe 来提醒使用者。

我们现在对“锁”有一个初步的认识了,但是我们还没有提及另一个非常重要的概念,就是guard,也就是上面提及的对 token 的进一步的封装,在下一篇文章中,我会再慢慢和大家解释这个“证明”究竟是什么。

[1][wiki: thread](https://zh.wikipedia.org/wiki/%E7%BA%BF%E7%A8%8B)

很多男孩子的童年里都沉迷于一种玩具“红白机”,喊上三五小伙伴,趁着父母不在厮杀一整天。而《坦克大战》就是曾经的经典之一,上手容易,关卡丰富,可以双打,甚至还可以编辑。成为 80,90 集体的回忆杀。儿时的玩伴已成家立业,我们可以通过网络呼唤儿时好友,来上一局联网的坦克大战,重温童年的简单的幸福。

我们以 Unity3D 制作的坦克大战客户端为底子,以游戏房间管理的三大功能为例,演示如何通过云函数搭建坦克大战的后端部分(关于“构建后端应用市场”的想法,可参见我之前的文章:FuncStore: 组合函数,构建后端应用的市场

MORE

本篇文档将分步骤介绍函数市场的使用:

一、下载证书

  • 登录函数市场
  • 右上角点击头像,选择 pems manager
  • 点击 generate 按钮生成证书
  • 点击 download 按钮下载证书

二、放置证书

将证书放至本地的~/.compute 目录下,此为 cli 上传的凭证

三、全局安装 cli

npm install immux-compute-cli -g

四、上传文件

  • 建立如图所示的文件结构
MORE

编程流派演义

All编程语言历史函数RustZH

编程流派演义

有人的地方就有江湖,有思想的地方就有谱系,编程自然也不例外。

所谓编程,就是描述计算过程。计算应该怎样理解,粗略分之,有两大门派:步骤派、函数派。

一、步骤派

步骤派(Procedural),又译过程派,祖师是图灵,基础模型是图灵机。

所谓图灵机,是一台想像的机器,有两个部件:一条纸带、一个读写头(可以想像成磁带机)。纸带分成一个个格子,每个格子里面写着指令或者数据,读写头按照顺序读取一个个指令或数据,一步一步执行,在纸带上读写。

现代电子计算机的核心部件是 CPU,CPU 就是那个读写头。内存就是那个纸带。现在计算无论功能多么神奇,其实都只是从内存里读数据,运算,然后再存回内存而已。

图灵机易用电路实现。现在量产的「计算机」,就只有步骤派的机器了,其他门派思路无论如何不一样,最后想在物理世界实现,还是要用步骤派的功夫。

步骤派之后又分为两支:原教旨步骤派和结构派(Structural)。

原教旨步骤派的代表语言是机器语言和汇编语言(Assembly),现在职业写汇编的人已经很少了。

结构派是步骤派里面的革命党。图灵和冯・诺以曼之贡献,在于说「指令可以当成数据存储」。结构派则认为,「数据」和「指令」就算本质相同,写代码的时候,应该明确区分。指令就是指令,不应该当作数据,随意操作,而是应该用固定的一套结构来组织(故名结构派)。

结构派祖师是 Algol,领军语言是 C,多年屹立不倒。

你如果在 60 年代学习编程,会遇到「结构化编程」(Structural programming)这个概念,这个概念现在已经没什么人用了,原因很简单:结构派大获全胜,今天流行的编程语言,语法上,基本都是结构派的后裔。

二、函数派

话分两头,正当图灵发明图灵机,开宗立派之时,还有一人能与之抗衡,名曰邱奇,也是数学家。

邱奇提出了一套理论,叫做 λ 演算(Lambda calculus),跟图灵机一样,可以描述计算过程。

λ 演算的核心模型不是读写纸带,而是函数(所以叫函数派),具体来说,是「数学函数」,也就是「变量的关系」——比如,1 个梨子 1 元,2 个梨子就是 2 元,这是「梨子数量和总价格的关系」,也是一个函数。几个函数可以组合成新函数,从而表达各种概念和关系。

函数派的旗帜是 Lisp。上一波人工智能热潮中,Lisp 就是主力。函数派甚至有过自己的机器,叫 Lisp Machine,可惜昙花一现。 80 年代末,AI 寒冬,Lisp 失势,函数派也一蹶不振。 AI 近几年再起,主力语言已经变成 Python 和 Java/C++了。

Lisp 后代繁多,目前有两大家:Scheme 家和 Common Lisp 家,Scheme 重纯粹,Common Lisp 重应用。

Lisp 还有一个分家 ML,此家的强项是类型理论,尤其是静态类型加自动类型推导,成员包括 Haskell 和 OCaml。 OCaml 也是奇怪——近二十年通常的趋势是对象派语言向函数派靠拢——OCaml 则是函数派主动混入对象派。 OCaml 在金融界应用广泛。

OCaml 又影响了 Rust。Rust 谱系甚为驳杂,兼有结构派、对象派、函数派。C 有一个小众后裔 Cyclone,针对 C 的内存安全问题做了大量的语法改进。Rust 承袭了 Cyclone 的内存安全(以及类似 C 的语法),又继承了 ML 系的代数类型、模式匹配、类型推导,Haskell 的 Typeclass(Trait),还从 C++借用了大量的概念(引用、智能指针,RAII 等)。

Rust

近些年,函数派再次崛起,函数派的思想倾向,在新语言和架构里已经很常见了:一等函数(无名函数、高阶函数)、自动内存管理(垃圾回收)、鼓励纯函数、避免修改变量,等等。

函数派对「数据」「指令」的关系认知,倒与原教旨步骤派相似:「数据」「指令」一体二相,本应自由转换。把「指令」当成「数据」修改的技术,美名曰「元编程」(metaprogramming)。

函数派本是小众派别,学校不教,人数少,散落各地。 λ 演算名字里「λ」这个字,便是函数派的标志,函数派弟子以此相认。

三、对象派

步骤派和函数派之外,还有一个思潮,传承模糊松散,勉强统称为「对象派」。

对象派的共通思路是,数据和指令还是有关系的,在代码形式上应该体现出来。对象派是对步骤派的反动,希望重新建立数据和指令的联系,以此组织代码。

粗略数来,对象派又分三支:类型宗、消息宗、原型宗。

类型宗,顾名思义,重视「类型」(Class),喜欢研究「这个概念属于什么类型」、「这个类型是否包含那个类型」。类型中人写代码,先要研究问题领域里面的概念,然后结一张类型网,然后一个个节点去实现。

类型宗的祖师是 Simula(Simula 又从 Algol 起步),后代有 C++和 Java 发扬光大,流传甚广。类型宗是对象派的主流,你现在在网上看到的「面向对象编程」教程,基本上都是传类型宗的道。

消息宗,始祖 Smalltalk(Smalltalk 师从 Simula),后代有 Objective-C。消息宗的核心观点是,软件就像生物体,应该是由半独立的「细胞」构成,细胞之间的合作,通过消息传递完成,消息处理方法由收信细胞决定,而非发信细胞直接指导收信细胞。

Smalltalk 创始人 Alan Kay 也是敲定「面向对象编程」(Object-oriented programming,OOP)一词之人,然而据他后来说,名字取坏了,应该叫「Message-oriented programming」的。如果他真的这么取了,「面向对象」这张旗子可能就打不起来了。

原型宗,祖师是 Self(Self 是 Smalltalk 的后裔)。它说,刻画结构,应该先做一个「原型」(Prototype)概念,这个原型应该可以独立使用。其他概念再去延展这个原型,由此刻画概念之间的关系。这一支很小众,但有一个门徒混出来了,那就是 JavaScript。 JavaScript 乘 Web 和 Node.js 的东风,垄断前端,染指后端。有它在,原型派一时之间不会亡了。

「面向对象编程」,80 年代、90 年代炒得火热,尤其是类型双煞 C++和 Java,一时无两。类型宗提出的方案,迎合了当时软件开发职业化、大规模化的需求,宣传类型继承(省钱)、模块封装(部门之间不用撕逼了)、设计模式(限制野鸡程序员破坏力)之类的。近几年大家发现,这派功夫并没有吹得那么好。类型继承其实满足的是「不改旧代码,只加新代码,就能开发新功能」的需求,但是现在敏捷开发、持续集成流行,「不用改旧代码」并不是那么重要。在小而精的创业团队,工程师有全栈开发能力,很多类型宗隔离模块的规范和模式,反而干扰开发节奏。

四、逻辑派和其他小门派

总体而言,步骤派和函数派互为表里,对象派是结构步骤派的支流,此三派为当今显学。三派之外,还有些小门派。

逻辑派

此派与函数派渊源颇深,也是从数学而来。逻辑派讲究描述「逻辑关系」,例如你给出「张三是人」「凡人必死」,它能算出「张三会死」。它还有独门秘技,「反查」:比如你说「张三是人」「李四是神」「人死神不死」,然后可以问电脑「谁会死?」,它能回答「张三」。

其他门派,当然也能做到反查,但是需要专门实现反查规则。逻辑派则可自动由数据推出。

人工智能派

以上门派,都是人来编程。现在有人在研究机器自己来编程自己,比如所谓「神经图灵机」(Neural Turing Machine),给它大量的输入和输出,它会自动尝试写出最优的算法,攀拟输入和输出之内在逻辑。此派现在尚在襁褓,今后成熟,大概会革了程序员自己的命。

编程流派多,派中有派,又不断交杂,挂一漏万。欲知详情,请参:

http://wiki.c2.com/?ThereAreExactlyThreeParadigms http://www.eecs.ucf.edu/~leavens/ComS541Fall97/hw-pages/paradigms/major.html https://en.wikipedia.org/wiki/Programming_paradigm

一网创业初体验

关于创业,我看过一篇文章,说“没病到一定程度不要去创业”,但也听说过“人生不经历一次创业是不完整的”,我倒觉得没这么极端,说说我近九个月的创业感受吧。

1. 自由与压力

我们有极大的“自由”。

不用打卡,远程办公,弹性工作制,不计事假,无限年假,错峰出去浪~ 当然,此处存在争议“你们创业公司,上班是上班,下班还是上班,无限年假和弹性工作都是假象”,再有“你们从稳定性、市场、品牌等来说还要啥没啥,只能整这些噱头呀”,亦或“这里存在用工的隐忧”,也都没错,但不是说创业公司就都想到要这样、会这样。我们目前现状是:有的偶尔工作日在外面浪,有的把自家仓库改造成了二十四小时工作室。普遍是日出勤八小时、双休,出勤时集中专注干活儿,双休时会捣鼓新技能、乐意交流。没人把无限年假当摆设而不好意思用,也没人老惦记无限年假而要出去走走看世界。我们省了考勤管理这档事儿,后续会如何,我们摸索着看看。

“自由”当然不止工作方式与作息,更大的“自由”是价值创造,然而“压力”也因“自由”而来。

对于公司而言,方向或者说战略太重要,而创业公司初期在这点上通常不那么明朗及稳定。我们会定一些远景和原则,做一些近期规划和目标,每个人都被给予极大的空间去驰骋。或在熟悉的专业内搭建、创新,或在未知的领域里摸索、迂回,创造价值是企业的生存之基,但我们需要的是先一起甚至独立去定义价值,再找寻价值和创造价值。这是很大的“自由”,不是容易的事情,不能“自由发挥”,有压力。 幸而,我们常说的却是:这个丢给我吧,这个我可以去学一下,这个我来加上去,我想做下这个,我做了一个这个…… 想起团队一个伙伴说“虽然难,但却很有意思”,迎着困难,我们都愿再多考虑一些、再做好一点,方对得起头儿说的 “我就喜欢这样,彼此不断给出惊喜”。

2. 惊喜的路上

什么是惊喜?

分内事或确定事,高效高质完成,带有点睛之笔,超额交付。 分外事或抽象事,纯属自主自产,设计用心,成果贴合又周到。

怎么给出惊喜?

多尝试,能分解的就分解,抽象事会越来越具体,就少些杵在那里一筹莫展。 公司战略性的事情,多交流、多问、不同阶段拿出来反复再探讨,方向会更清晰,理解也更透彻,慢慢地,工作内外会有意无意建立更多联系,产生灵感。 听不懂技术、搞不清关联,那就投入时间和心力去学习,让自己多了解一些、再接近一些,能够参与了,就更懂得换位,进而才可能建议、组织与优化,再给出惊喜。 显然,这种情况下大脑处在前所未有的活跃状态,被激活的未知技能越来越多,会更忙乱,盲区与困难也越多了。

3. 忙、难与自律

有时候是事情多而杂。

我现在深觉越忙要越自律,越自律时间才越多。简单来说,比如当做到早睡早起、锻炼身体,一天的时间好像变多了。因为早睡和坚持锻炼使人的精神状态更佳,身心问题更少,做事效率自然更高,加上早起,能做的事情更多,时间也就“变多了”,也可以说越自律越自由。 自律,好多都是我们从小听到大的叮嘱。除了上面说的早睡早起、锻炼身体,还有按时吃饭、劳逸结合、多喝水等等。我常常做不到并深受其害,为了脱离苦海且不拖累团队,近期有所改观,也乐见其效。 再忙再乱,若是长期加班赶工几下子,生病缺勤一阵子,时效更差或大致抵消不说,也不是长久之计。

有时候是难搞的坑、难跨的坎儿。

我在面对有些难题时通常会掉入同样的漩涡,症结都是同一个。比如一知半解,比如磨不开脸,比如习惯对自己食言等等,久而久之,顽疾般存在的地方正是自己的瓶颈,也多半与我不够自律有关。 这些困难,绕是绕不过去的,面上绕过去了心里绕不过去。我必须去搞定,给出结果,不然就会失眠。为了不要成为自己最讨厌的人的样子,我选择面对,然后死磕,一些通病竟也迎刃而解。 被投以信任,便报以倾心,是最基本的道理呀!

4. 基本的道理

在一网,常常会问到、听到、聊到一些“基本的道理”。 成员主动额外付出没发钱,请假为何要扣钱; 这个事情从根上就有问题,我们为何要在枝上搞长期工程; 这个事情如果是非我们自己做不可,那么不是你做、就是我做,多说无益; 这个概念是什么意思,它源于何处,如何演进,现状如何,我们为何要用……

创业本就是要开拓蓝海,敲门工具难免要自己造。 创业初期多问几个为什么,多想一些原理性的问题,不是矫情。 我们白手起家、搭架子,好比如在生成基因,要以终为始。

结语

过程的苦乐,自知。 一个巧合的时间点,遇到几个同道人,都想做一些事。 恰好,我就加入创业了。

创业路漫漫,边走边欣赏。 月底有彩蛋,欢迎来围观。 这里是一网: https://www.immux.com/zh/

ImmuxDB 一款能让你追溯历史的数据库

记得在读书的时候,教授和我们说计算机没有什么神奇的地方,计算机领域不外乎就是如何存储数据以及如何处理数据两个问题。其实这两个问题是密不可分的,数据的存储方式是直接影响到数据的处理方式的。好的数据存储模式可以让系统的响应更快、业务逻辑更简单、运行起来更稳定;不好的数据存储模式不利于以后系统的扩展,甚至可能要把之前所有的代码库推翻重写。 那么什么是“好”的数据存储方式呢? 这个问题当然需要看系统的业务需求,但是我们希望能给大家提供另一种角度来审视数据,ImmuxDB就是一款从历史角度来看数据的数据库,其灵感来自于区块链、源码控制、以及前端的React和Redux技术。

数据本该如此

计算机的体系结构是叠床架式的,在最底层是硬件,也就是CPU和硬盘等等,然后是编译器和操作系统,再往上走才是存储引擎等等的软件。其实在不同的层面,都有一些设计是体现了从历史角度来审视软件的想法的。我举几个例子:

  1. 在文件系统,有ZFS,其核心思想是写入时复制事物(Copy-on-write),这样的好处是在写新数据的时候断电或者系统崩溃了,那么原数据会被保留下来,在修复系统的时候不需要再运行fsck来检查和修复文件系统,文件系统不会因为一些外在的原因而处在一个不正常的状态。也是因为这种特性,使得快照功能变得更简单以及自然,在恢复文件系统的时候也更为简单。
  2. 在操作系统层面,有Timeshift这样的软件,在OSX上应该是叫Time Machine。Unix类的操作系统都会将所有一切视为文件,上面提到的这类软件其核心想法就是将某一个时刻该系统的所有的文件状态记录下来,在恢复的时候直接将之前记录下来的文件拷贝到对应的系统路径即可。
  3. 前端有React和Redux,Redux也提供了time traveling这样的功能,单向的数据流使得前端开发更为简便,更容易debug,这也使得React这几年成为非常火热的前端框架。
  4. 再往宏观上看,就是区块链技术,其历史不变性才使得数字货币的出现成为了可能。

ImmuxDB的出现也是受到了上面技术的启发,从历史的角度来审视我们的数据。现在主流的数据库产品并没有从这个角度来看数据,在设计数据库的时候,数据都是允许被覆盖的,其实也就是允许用户覆盖历史数据,历史并没有被保留下来。而ImmuxDB,正如其名字所示,所有的数据是不可变的,写进去的数据就是曾经的历史,如果再要写新的数据,那么新的数据将会是原来数据的一个复制,也正是这样的设计初衷,使得ImmuxDB具有主流数据库所不具备的特性,比如可以回溯到数据库历史上某一个版本,查看某个键所有的历史数据,所有的数据是可以通过历史来解释的。

基于日志文件的设计

从技术角度来说,数据库是一个很模糊的词。同为Database,比如LevelDB和MySQL是完全不同的两个软件,做的事情也有很多不同的地方。在这里我想给出一个比较明确的定义,以便后面的阅读。

数据库和存储引擎是两个不同层面的东西,面向的使用者也不一样。数据库是提供给后端工程师,业务逻辑开发人员等等使用的,数据库提供了丰富的类型,提供了较强的data manipulation language (DML),甚至数据库会集成不同的网络请求的接口,有自己的网络报文格式等等,而对于使用者,具体后面是分布式的存储还是单机版的存储,这并不重要,使用者只需要会对应的DML,便可以开始最基本的使用了。而存储引擎是数据库的核心,存储引擎没有丰富的类型,存储引擎通用的类型只有byte,没有丰富的DML,一个存储引擎最基本的“DML”可能是KV的方式查询的。对于现阶段的ImmuxDB,它包涵了存储引擎的功能,也有一部分数据库的功能,我们支持相对丰富的类型,支持较为简单的Tcp报文协议,支持RESTful API,也有较为简单的DML。

我们主要聊聊存储引擎的设计,存储引擎其实最主要就是处理两方面的问题,第一是内存,第二是硬盘。一个数据的读写,都会在这两个物理硬件上停留,如何协同这两个硬件,使得读写的速度更快,就是一个存储引擎应该要处理的问题。ImmuxDB是一个很有特色的数据库,特色的地方就在于对历史数据的保留,这和日志文件(Log File)的设计是非常吻合的。在设计一款存储引擎之前,作为开发人员,我觉得最重要的是先搞清楚这个引擎要用在哪里,这个引擎应该会有的特性,比如LevelDB以及RocksDB,这两款存储引擎的设计就是为了提高写入的性能而牺牲掉一部分读取的性能,那是因为这两款存储引擎存在的目的就是为了让爬虫尽可能快的写入数据。ImmuxDB的存在就是为了保护好历史数据,读写的性能反而求其次,因此我参考了两款基于日志文件的存储引擎,Sled以及Bitcask. Sled是一款用无锁编程技术写的存储引擎,Bitcask是作为Riak的存储引擎,两者的核心思想类似,内存都是一个Hash Table,而实际的数据是按日志顺序存储在硬盘上的。ImmuxDB的设计灵感也是源自于这两款存储引擎,通过在内存维护Hash Table来对日志文件进行检索,而历史数据都记录日志里面,如果有任何系统崩坏等情况发生,通过日志也可以使得存储引擎恢复到崩坏前的健康状态。

未来的路

现在的ImmuxDB基本的接口已经稳定,在事物(Transaction)上还有更高级别的独立性需要实现,性能上也还有很大的上升空间,比如内存的数据结构可以通过无锁编程实现,对于硬盘的写入,io_uring的日渐成熟似乎非常值得我们去尝试,在硬件层面,19年Intel的persistent memory的出现也是非常有潜力的。我们的软件都是开源的,我本人主要负责ImmuxDB的开发工作,有想法的小伙伴欢迎来提PR,来交流。下面是我们的Github:

https://github.com/immux/immux

FuncStore: 组合函数,构建后端应用的市场

我们在开发后端服务过程中,有很多重复的逻辑与功能。有时候通用逻辑占用的时间和精力,会接近甚至超过业务逻辑。

这些通用的逻辑在很多项目之中都是高度相似的,比如登录注册,实时通信,权限管理等。

如果将这些通用功能透明化,以组合的方式去使用,那么我们开发时只需要聚焦业务逻辑,从而加速我们的迭代。而这就是我们Function Store想要实现的目标。

要实现如此之秀的功能背后要靠什么支撑呢?我们需要了解几个前置的知识。

1. 前置知识

  • 函数即服务(FaaS: Function as a Service)

FaaS是一种事件驱动的由消息触发的服务,FaaS供应商一般会集成各种同步和异步的事件源,通过订阅这些事件源,可以突发或者定期的触发函数运行。

我们知道现在流行的微服务(MicroService)是以专注于单一责任与功能的小型功能块为基础,利用模组化的方式组合出复杂的大型应用程序,而还有一种更加碎片化的软件架构范式,即通过粒度更细的函数去组合应用。所谓的“函数”(Function)提供的是相比微服务更加细小的程序单元。我们可以通过微服务为项目执行CRUD操作所需的代码,而FaaS中的“函数”可以代表项目所要执行的每个操作:创建、读取、更新以及删除。

  • 后端即服务(BaaS: Backend as a Service)

后端即服务,比如对象存储,数据库应用,缓存服务,在使用时不需要关注它的服务器部署在哪里、是什么样子的,而是开通服务就可以使用,后面的运维工作都交给了云,无需感知它最底层的服务器。

  • 无服务器架构 / 轻服务 (ServerLess)

云函数,就是FaaS模式的具体实现。同样,对象存储、数据库应用、缓存服务等,是BaaS模式的具体实现。而他们两个组合而成,即为ServerLess。

Serverless架构最早可追溯的第一个商业化的平台是2006年的Zimki,他们第一个提出”按照实际调用付费”,那时还没有Serverless这个名词。因为硬件与网络条件以及开发理念的限制,这种想法也始终没有出现在主流视野中。随着2011年的Parse,2012年的Firebase| IronWorker,直到2014年AWS Lambda出现定义了FaaS,这种模式才正式闯入大众的视野之中。而后其他大型公共云提供商开始着手建立自己的FaaS平台,随着2016年Google Cloud Function & Microsoft Azure Cloud Functions的大力推广,落地使用的项目越来越多。国内随着阿里云、华为云、腾讯云的推广,也在很多项目中落地,特别是微信小程序、小游戏的兴起,让更多开发者对云函数的开发已经轻车熟路。

2. 我们的特点

好了,前置知识了解完毕,该来正菜了。我们第一版ImmuxCompute就是以上述提到的ServerLess的形式,为用户提供服务。具体来说,我们提供全套的包含云函数执行、数据库存储、运维监控。开发者只需要开通服务,编写JavaScript函数组合形成业务逻辑,上传到平台即可生成后端服务。我们的云平台接口简单,在网页上即可完成代码部署。我们有下面几个特点:

1.支持http、websocket协议,无论是需要REST风格的网络接口还是实时对战、即时通讯我们都可满足。

2.降低启动开发成本,只需编写函数即可完成后端服务。

3.无需要学习新的语法,简洁明了没有心智负担。用函数的组合去实现应用,像书写普通函数一样,用起来更自然。

4.数据是企业最重要的财富,对于数据库而言我们是专业的。我们自研区块链型数据库,自动保存所有数据变动历史,无需备份,无需担心删库或黑客侵扰,而我们的计算平台与数据库是紧密结合的,确保数据安全并可追溯。

5.我们的代码开源,可以私有化部署,可满足您的安全需要。

6.我们提供函数的交易市场,让开发者分享、出租各种重复的业务逻辑与函数,收获您代码的价值。

7.随着越来越多的开发者上传函数,丰富生态,项目可获得更加丰富的功能。

诚然,ServerLess这种形式也不是银弹,对于已有项目或者其他项目,可以部分业务采用我们的方式,无缝集成。

3. 未来的展望

未来我们会支持更多的编程语言运行于计算平台之上,也会与数据库结合的更加紧密,充分发挥我们数据库的特点,让您的数据安全并可追溯。

我们会推出一些可信赖经过验证的官方通用函数模块,覆盖各种类型的Demo以便快速组建应用。

同时也非常欢迎各位开发大佬贡献自己的函数模块,让那些曾经绞尽脑汁写出的精彩函数被更多人使用,同时获取收益,共建良好的生态。

对我们有兴趣的话,产品方面欢迎来我们网站https://immux.cn/zh/ 和博客https://immux.cn/zh/blog/ 。 技术方面欢迎来我们的GitHub看看:https://github.com/immux/immux ,我们的软件是开源的。

一网网络:整合计算架构的理想

软件开发,本不应该如此复杂;系统维护,本不需要如履薄冰。

现在通行的互联网软件开发体系,是数十年来,成百上千个组织与个人共同构建的。比方说云服务器-Linux操作系统-SQL数据库-Java后端-React前端这个常见的技术栈,每一层都由不同的人设计,这些人各有专攻,大多并不相识,时间上也横跨十年二十年三十年。于是,我们得到了一块百衲衣,确实能大致满足我们的需求,但是这个体系里有大量偶然的复杂度,冗余无处不在,结构难以理解。

比如说,现在想做一个稍微使用一点的网络服务,比如说一个能让客户下单购物的网店,就需要各种基础软件的支持:存储数据需要数据库,分配流量流量需要负载平衡器或反向代理,还要另加缓存、搜索引擎、统计引擎,图像、音视频需要静态存储,备份还需要“冷存储”,然后要有系统运维管理。

我们称这种现象为「计算基建的碎片化」。

1. 碎片化的历史

在服务器领域,Linux占主导地位,Linux的设计承袭自Unix,Unix在60年代的就开始了。Windows一开始是MS-DOS的图形界面,MS-DOS又承袭自CP/M,这也是70年代的设计了。

在Windows和Unix设计的时候,电脑还是科研机构里的珍贵器材,这些操作系统早期处理的问题是,如果我只有一台电脑、一个处理器,但是有很多人想用,我要怎么分计算资源?

那时候,电脑内存以KB计算,硬盘、网络都不一定有,并行处理也尚在襁褓。

后来,操作系统的文件系统满足不了我们对数据存储的需求,我们就开发了各种数据库软件。

为了进一步改进数据库,我们开发了内存数据库(比如Redis),以充当其他数据库的缓存。

为了替换系统函数,我们有脚本语言,如JVM,Node.js,Python等等,代我们进行调用系统函数。

要替换操作系统本身,我们有“容器”(如Docker),然后容器还需要专门的“编排”工具(如Kubernetes)。

这些扩展和改进,往往缺乏协调。新增机制叠床架屋,有时候互相倾轧,有时候顾此失彼。

例如,Linux和Windows都有「用户」系统,然后可是数据库(比如MySQL)也有「用户」系统,应用层可能还有「用户」系统。这些用户系统之间,并没有明确的逻辑关系。你只有数据库的「读」权限,是不是代表你写不了东西进数据库?不一定,如果你能拿到数据库文件的写权限,也可以绕过数据库直接操作文件。

又如,在内存里,不同程序之间,通常是不能读写别的程序的内存的,隔离进程权限。但是,在硬盘上,不同程序却可以读别的程序存储的文件,只要是同一个用户创建的。如果一个程序不能读别的程序的内存,为什么可以读别的程序的文件?

许多文件系统提供日志功能,可以记录未提交的更改以在发生系统崩溃时提高数据一致性,但是许多数据库也要建立日志;

编程语言提供多种数据类型,数据库也有数据类型,这些数据类型,有时候对对得上,有时候对不上。比如,JavaScript有个null,SQL也有个NULL,JavaScript的null当成SQL的NULL存取,却会有问题。

2. 我们的理想

我们希望建设一个计算架构体系,吸取现在现有体系设计的经验,合并重复、冲突的机制,构建一个统一、协调、安全、容易理解的系统。

第一步:数据层

我们从不可变的存储引擎开始,其灵感来自于区块链,尤其是比特币。对于文件系统和内存管理,它将仍然依赖于传统的操作系统。

该引擎的工作方式类似于数据库管理系统,可通过HTTP和TCP接口进行访问。

该引擎将具有面向文档的接口,同时,所有数据条目都内置版本回溯能力。

第二步:计算层

然后,我们将虚拟机连接到数据存储引擎,接口对于每种编程语言应该自然的。如果编程语言是面向对象的,访问数据层就应该像访问「对象」 一样;如果编程语言是函数式的,访问数据层就应该像应用函数。

我们嗨需要构建一个适用于高级语言(例如JavaScript,Python和Java)的虚拟机。

然后,我们为这些语言实现翻译器,解析源代码,生成供虚拟机执行的“字节码”。

第三步:操作系统层

然后,我们使我们的系统独立于现有的操作系统体系,可以直接引导启动,从而绕开现有的任务调度、磁盘存取限制,这个系统需要处理磁盘驱动器,内存和驱动器。

第四步:硬件层

由于我们的系统需要的专用功能(不变性),当前的通用计算和存储芯片的支持力度有上限。待成熟时,我们将投资于专用于我们的目的的存储设备和计算芯片,包括物理上不可变的存储器,针对不可变计算、函数式编程做专门优化。

3. 今天与明天

我们在2018年11月成立,2019年获得一轮种子融资。现在,我们在数据层有ImmuxDB,这一版已经经过一年多的开发,从头重写过一次,从依赖RocksDB到自己实现键值引擎。ImmuxDB已经有数个应用案例。

在计算层,我们正在开发第一版的ImmuxCompute,可以运行JavaScript函数。

在ImmuxCompute基础上我们在做一个函数市场,让广大程序员分享或者出租各种重复的业余逻辑与函数。

对我们有兴趣的话,产品方面欢迎来我们网站https://immux.cn/zh/ 和博客https://immux.cn/zh/blog/ 。 技术方面欢迎来我们的GitHub看看:https://github.com/immux/immux ,我们的软件是开源的。

TypeScript实现IoC容器

背景概述

在公司项目的实践中,我们需要实现一个serverless的运行时。既然目的是为了消除server端的代码,那么我们要先研究下,当今主流的server端代码的组织与架构方式。

在Node社区中,我们看到主流的Nest、Midway,它们都是基于控制反转(IoC = Inversion of Control)原则来设计的框架,并且都使用了依赖注入(DI = Dependency Injection)的方式来解决耦合的问题。熟悉Java的同学看到可能会说,这不就是Spring嘛!是的,没错,虽然Java的臃肿饱受诟病,但是无疑众多的最佳实践与模式都诞生于此。

本文讲述如何使用TypeScript的装饰器与注解实现一个IoC容器,以及如何在项目中灵活运用。

IoC定义

我们先看维基百科定义:

控制反转(Inversion of Control,缩写为 IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入(Dependency Injection,简称 DI),还有一种方式叫“依赖查找”(Dependency Lookup)。通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体,将其所依赖的对象的引用传递(注入)给它。 ———— 维基百科

简单来说,IoC 本质上是一种设计思想,可以将对象控制的所有权交给容器。由容器注入依赖到指定对象中。由此实现对象依赖解耦。比如Class A中用到了Class B的对象b,一般情况下,需要在A的代码中显式的new一个B的对象。采用依赖注入技术之后,A的代码只需要定义一个私有的B对象,不需要直接new来获得这个对象,而是通过相关的容器控制程序来将B对象在外部new出来并注入到A类里的引用中。

举个通俗点的🌰:

假设你是一个想开公司的大明星,开公司首先需要一间办公室。那么你不用自己去买,你只需要在你的清单列表上写上办公室这么一项,那么,你经纪人已经派人给你安排好了办公室,这间办公室长什么样?多大?在哪里?是租的?还是买的?你根本不知道,你也不需要知道。 现在你又在清单上写了需要80台办公电脑,你经纪人又给你安排好了80台, 你自己并不需要关心这些电脑是什么配置,买什么样的CPU性价比更高,只要他们是能办公的电脑就行了。那么你的经纪人就是所谓的 IoC 容器,你在编写 Company 这个 class 的时候,你内部用到的 Office、Computers 对象不需要你自己导入和实例化,你只需要在 Company 这个类的 Constructor (构造函数) 中声明你需要的对象,IoC 容器会帮你把所依赖的对象实例注入进去。

JS社区很多框架都有使用IoC,比如Angular.js、Nest.js、Midway.js,下面以NestJS为例,来看下DI是如何应用其中的。

MORE

LevelDB 阅读笔记

LevelDB 的背景

LevelDB 是 Google 出品的一个单机版 Key-Value 存储引擎,LevelDB 的想法是来自于 Google 大名鼎鼎的 Bigtable,因为 Bigtable 是通过 Google 很多内部的代码实现的,这些代码并没有开源,所以两位作者 Jeffrey Dean 和 Sanjay Ghemawat 做了一个更适合开源的版本,也就是我们现在看到的 LevelDB。LevelDB 是一个适用于写多读少的数据库,里面最核心的思想就是 LSM 树(log-structured merge-tree),以下简称 LSM,LSM 更多是一种思想,LevelDB 通过 LSM 的实现方式,减少随机写的次数,提高了写入的效率,并且通过 Compaction 这种内部的数据整合机制,达到了平衡读写速率的效果。在这里可以看到在每个 Value 都不是非常大(100,000 bytes each)的时候,LevelDB 都有不俗的表现。

LevelDB 总体架构

其实数据库无外乎就是要解决两个问题:怎么存储数据怎么读取数据。为了解决上面的两个核心问题,就需要数据库这样一个调度系统,而内存和硬盘就是被调度的对象,解决好了内存和硬盘之间的互动关系,自然也就能理解数据库是怎么读写数据了。我们可以先看一下 LevelDB 的写入概括图:

MORE