项目介绍
SMSS是一个由我个人发起的开源项目,目的是建立一套轻量化,高可用,高安全和方便扩展的业务支撑框架。SMSS面向TCP/IP层开发,适合扩展上层业务接口。数据结构传输序列化通过Protobuf实现。传输过程中的数据经过OpenSSL加密再由接收端进行解密,文件传输也需要由发送方的秘钥首先做签名再由接收方验签。核心功能完全不需要DB支撑,降低学习、开发和部署的难度。客户端使用Electron,也足够简单和保持跨平台特性。另一个方面,我相信对于每一个曾经心怀梦想进入IT行业的人来说,能够创建属于自己的开源项目都是一种对“个人价值”的体现。我也相信这样的梦想从未消失,只是各种“福报”淹没了。所以,三月前我决定与其等待他人给我“福报”不如自己动手来实现属于自己的“福报”。
目前项目已经发布在gitee上(源码地址),技术验证和原型开发已经完成。client目录下是客户端相关源码,开发语言为JavaScript。server目录下是服务端相关源码,开发语言为C++。扩展功能可能会通过Java来支撑。doc目录下主要是一些设计文档和Protobuf结构文档。
开发过程中无论是技术要点还是架构方面都有很多收获,本文主要就这些方面的感悟进行分享。更多实现细节我会在今后分专题介绍。
经验分享
核心框架是越丰富还好还是越简单越好
一个框架无论大小最终都需要服务于具体业务,或提供支撑或提供具体实现。可是我们又不能仅仅通过具体业务来设计框架,那样话就失去了通用性。一段没有通用性的代码是绝不能称之为框架的。因此,为了设计一套优秀的系统,设计人员应该具备至少两个方面的能力——高度抽象的能力和剥离依赖的能力。首先,设计师需要先将业务需求抽象,抽象出那些适合开闭原则的接口。例如,模块A和模块B需要通信,我们可以分别在两个模块中实现一对Socket。可是如果还有模块C需要加入该怎么做呢?作为一个更加合理的方案是设计一个模块X,所有需要通信的模块都需要先向模块X注册,并且只需要向模块X发送消息。转发和消息缓存全部由X内部完成。说到转发,X如何保证消息能够正确的从A发送到B呢。或许你会想到将每一个模块的通信端用一个map保存起来以方便点对点发送。点对点的发送是一个好方法,可是如果业务需求是A要同时向BCD发送消息呢?为了解决这个问题,我参考IP包TTL的概念,每一个需要发送的消息都添加一个计数器。无论该消息是广播还是点对点发送,都会通知每一节点来读取,每读取一次计数器减一。当然只有正确的节点才会将消息发送出去,对应的模块才会接收到。当计数器为0的时候表示该消息已经可以释放。模块X可以从缓存中取出下一条消息继续广播。没错,这就是微服务的本质,而模块X还有一个更通俗的名字——网络总线(netbus)。
我们何时应该考虑将代码封装
这个问题也是我在开发工作中被问及最多的。这是一个简单的问题,每一个开发人员都会面临类似的难题。有时候我们发现,很多的代码堆在一起既无法阅读也无法维护。可是有时候我们也发现,过渡封装让简单的逻辑难以理解。那么到底怎么做才是合理的呢?这里我提供封装的三个层次。在这三个层次上你都可以对代码进行封装,除此之外先堆在一起也不失为一个策略。
- 分解代码的规模:一段代码太长了,仅仅是不好阅读。那么你可以将他们用几个方法先做简单的分类。方法名要通俗易懂,让别人一看就知道大概分几步,需要修改也有的放矢。这种封装也是对应复杂业务最常见的方法,毕竟在面对绝大多数的开发任务中,我们不会有足够的时间深入对业务分解和抽象。
- 降低调用难度:针对一些复杂的逻辑,可能需要调用者传递多个参数。这时你可以考虑利用设计模式(建造者模式、代理模式等)将调用过程分解,必要的时候甚至可以通过返回值告诉调用者内部发生了什么。从而降低调用者出错的几率。
- 隐藏差异:设计接口和面向接口编程的目的是可以隐藏内部实现。优点不言而喻。这个方面C++没有像Java那样提供纯粹的接口,但是设计思想却是共同的。
注释越多越好吗
要解释这个问题,我们应该明确几个概念。你的代码给谁看?注释需要解释什么?注释如何被利用?
- 代码给谁看?答:如果你的代码是让新手能够尽快上手并且在不需要看源码的层次上就立刻着手开发。那么注释应该越详细越好,甚至一行代码三行注释都不为过。
- 注释需要解释什么?答:主要是两个方面。首先是对这段代码的作用加以解释,让别人能够对业务首先有一个了解。其次是对算法做出说明。
- 注释如何被利用?答:现在有越来越多的工具可以根据你的注释生成文档。因此,你应该学习这些工具的使用方法并充分利用它们为你的代码加分。你所需要做的仅仅是按照规定的格式来写注释而已。
- 注释越多越好吗?答:为什么很多开发人员不愿意写注释。不仅仅是因为浪费时间,更多的原因其实是写注释会打断开发的思路。很多时候注释都是在代码开发完成后才补充上去的,这就成了一项额外的工作。大部分时候,企业也很少做代码评审,写的注释也没有人关注。并且我个人其实也很不喜欢看到注释很多的代码。光看注释看不懂,看代码又收到影响。
因此,我对注释的观点是:按照文档生成工具的规定格式写注释即可。提交源码的同时生成一份document,既可以证明你的工作量又满足了大多数情况下的要求。至于方法内部的注释,关键地方说明一下即可。
我应该掌握更加底层的原理还是应该学习更加新颖的技术
如果只能用一句话来回答,就是都要学。
所有的发明本质上都是发现。新技术的产生并不是一个孤立的事件,它往往是为了解决一些以前不被重视的难题。Java中有IO和NIO的概念,相信很多人都了解。工作中我发现,很多人其实对NIO的理解不够深刻,仅仅是知道它是一种non block IO。可是为什么它是非阻塞的,非阻塞就一定比阻塞好吗?我通过学习libevent,理解了在操作系统层面本身就对IO提供了select、poll和epoll几种驱动模型,目的是IO的多路复用。可是系统总线只有一条,复用的到底是什么。复用的其实是程序(CPU运算)。相比IO总线来说,CPU的运算效率高得多。传统的阻塞IO当程序需要从文件或网络上读取/发送数据的时候就需等待在那里直到操作完成,而非阻塞IO模型使得程序可以在这个过程中先向下运行,直到数据准备好以后再回来处理。
那么非阻塞IO一定比传统IO好吗?有过JavaScript开发经验的人一定会对各种回调不陌生,甚至“深恶痛绝”。由于nio模型不会等待数据读取,通常情况下我们需要通过注册回调函数来完成任务。如果有大量的IO需要处理,回调函数就会层层递进。这样就增加了开发和维护的难度。
你瞧,学习底层原理和理解新技术本身并不矛盾。理解一些底层原理也会更容易掌握新技术。除此以外,设计模式和算法也是我们平时需要积累的地方。SMSS中利用红黑树完成用户ID和用户名的查找,在用户登录和用户离线的时候会触发红黑树的旋转。最后我想说,底层原理和新技术本身不矛盾,你都应该学习。更加多元的技术能让你走的更远,更加扎实的理论功底能让你走的更稳。
面对一项复杂的任务,我应该注意些什么
和所有人一样,这也是我在开发SMSS中经常思考的问题。目前,每次增加一个新的功能我都会首先考虑,这个功能应该如何测试,尽量做到每完成一块都可以通过测试观察是否达到了预期。传统的瀑布式开发显然已经不适用了,由于所有的设计和开发工作都由我个人在业余时间完成,时间并不容易自由控制。因此如果一个小的功能点在写完后无法被验证,几天后我就极容易忘记当初的思路。完成一个较大的模块往往需要几周时间,如果等到那时再测试就很难发现问题了。如果你也能遵循这套标准来安排自己的工作和学习,那么恭喜你,你已经掌握了敏捷开发的关键要点。
很多时候,有关设计方面的应用就是在这样的一点一滴中得到实践。
技术介绍
接下来简单介绍一下我在SMSS中运用到的技术。
- 利用libevent开发的网络复用模型,并且在它的基础上实现了一套线程池。使得多条连接可以复用在一个线程中运行,从而最大限度的利用系统性能。
- 数据通信采用protobuf作为序列化方案。这要比json或xml更好的利用网络,减小信道占用。并且,由于protobuf支持多语言的特性,后期扩展也很容易。不过,在使用protobuf后我也发现在js端的支持还有待改进。
- 目前服务端的日志系统使用了log4cplus。作为一个Java开发人员,我对log4j非常熟悉,这也是我选择它的原因。
- 客户端采用Electron,毕竟开发便捷和提供的跨平台特性是目前开发桌面系统的不二选择——相对而言Qt就显得略笨重。不过Electron毕竟是使用JavaScript作为开发语言,开发方式不如传统语言来的顺手(也可能是受个人能力限制)。
- 用户登录验证借鉴了git的方案,依赖linux系统来完成这些工作。
- 信息加密由用户获取服务端通过OpenSSL生成的秘钥来完成,nodejs也支持对应的加解密算法。
最后,希望大家能继续关注我后面的技术分享,也欢迎加群来与我交流。
相关文章:
《开源项目SMSS开发指南(二)——基于libevent的线程池》
《开源项目SMSS开源项目(三)——protobuf协议设计》