Soul Orbit

I'll take a quiet life. A handshake of carbon monoxide.

服务扩展是几乎每个做后台服务的开发都遇到过的问题,当业务大到一定的水平,当前的服务快要承受不住业务压力的时候,我们就要进行扩展了。一聊起服务扩展,很多人的第一想法都是增加运行实例,但是服务扩展有很多种方法,增加实例只是里面最简单的一种,而有时增加实例并不能改善问题,反而会让情况变得更糟。最近我们项目也在做类似服务扩展的事情,所以想把我之前学的东西大概总结一下,希望也能对项目有所帮助。内容比较粗浅,希望大家不要介意~

在对服务扩展时,我发现有些原则如果扩展过程中可以遵守,对整个过程会很有帮助,所以在讨论具体的方法论之前,我们先来了解一下它们。


1. 业务驱动

首先,我们必须得了解一个事实——业务才是解决用户问题的核心,后台服务的目的是为业务提供支撑。因为如此,一个后台的架构好不好并不是看它看起来有多么的华丽,有没有用到最前沿的技术,有什么技术情节和情怀,而是看它是不是能真正好的服务于产品,服务于业务,所以无论是后台服务的设计还是扩展,都必须由业务驱动,而所有的架构和设计都必须是为了解决实际的问题。有技术追求是好事,但是千万不要为了技术而技术,为了架构而架构。

1.1. 奥卡姆剃刀和康威定律

在执行业务驱动的时候,有两个理论特别好用:奥卡姆剃刀和康威定律。

很多时候我们在设计服务的时候都会纠结,一些地方要不要设计的更灵活一些,但是如果真的那么设计了,实现起来可能会复杂很多,这里我们就可以运用奥卡姆剃刀了。奥卡姆剃刀告诉我们:“如无必要,勿增实体”。要是一个服务可有可无,那么我们就把他砍掉,千万不要为了架构而架构,并不是越大型的架构就越好,后面我们说折中的时候会更详细的讨论这一点。不过,也请不要忘记奥卡姆提出奥卡姆剃刀时的后半句话,过分精简也是不好的。所以请了解自己的业务,做出最适合自己业务的决定。

另外在设计服务的时候,还要注意康威定律的使用。康威定律告诉我们:软件架构是组织架构的一种反映。所以在做任何服务设计和扩展时,我们都应该把组织架构考虑进去,这样我们才能设计出真正“高内聚,低耦合”的服务,让组织之间的沟通成本降到最低。

Read more »

两年半没有更新过博客了,趁着最近难得的假期赶紧除除草,结果发现………………之前给hexo写的包有各种神奇的bug,于是更新博客变成了修bug…………然后对于我这种超级懒人,烦人的事情就来了————每次在本地执行npm的命令发布安装包真的好烦人,要是可以自动化就好了,于是就抱着试一试的心情看了一下Azure Pipeline,结果工作的相当好,而且还在一定额度之内免费(对我来说真的够用了)!所以在这里记录一下,也算是个安利了~

这里就拿我的hexo-asset-path举例子吧(https://github.com/r12f/hexo-asset-path)。

1. 在Azure DevOps上创建项目(Azure DevOps用户请跳过)

因为hexo-asset-path的代码在github上,所以在使用Azure Pipeline之前需要先在Azure DevOps上创建一个项目。没有账号的童鞋可以和我一样直接用GitHub的账号注册。

在登录之后的主界面上点击右上角的“New Project”按钮就可以创建新项目了。
01-new-project-button-on-azure-devops

Read more »

Azure Pipeline是一个非常强大而且部分免费的编译和发布工具,我们可以用它来连接GitHub或者Azure DevOps上的项目进行CI和CD的实践。我们经常需要的一个功能就是在CI Build中自动升级版本号,而在Azure Pipeline中,这个功能的实现却不是那么的直接,在尝试了多种方法之后,我终于找到了一种简单而且有效的方法,于是记录一下。

为了解决这个问题,我们需要用到Azure Pipeline的变量(Variable)和计数器(Counter)。

Read more »

以前从未真正接触过技术团队的管理,所以趁着年末假期,先选了一本比较简单的书入个门。这本书写的比较浅显,但是非常适合第一次接触技术管理的人来读,能帮助新人迅速建立起一些概念,避免很多雷区,虽然有很多不足的地方,不过都还算可以接受,毕竟这种书主要就是看个思想,细节并不是那么的重要,比如结尾的例子结束的过于唐突,看完的时候还以为是我看的版本不全;比如很多技术介绍的部分过于泛泛,比如研发管理体系,另外有些技术也有点过于落后,作为2018年出版的书,源码管理还在讨论vss和cvs,这些难以让读者对其产生同感和共鸣;此外有些观念我也不是特别的认同,比如技术和架构的进化,我觉得理解成演化会更加合适,也能帮助理解技术并没有有高下之分,而主要在于使用的场景。不过总之,对于刚接触基础管理的人来说,这本书还是值得一读的。

和以前一样,读完之后也还是在这里稍稍把这本书的知识点总结一下,方便以后复习:

1. 技术管理工作

1.1. 技术管理

  • 需求方太多
    • 刚通过校招进入团队的小组成员
    • 团队内工作3~4年的小组成员
    • 团队内工作5~6年的小组长
    • 产品团队管理者
    • 直属领导
    • 部门最高领导
    • 兄弟部门领导
    • 文档评委
    • HR接口人
  • 所需技能
    • 深入理解一门或多门编程语言
    • 深入理解多种流行的框架
    • 系统架构能力强,拥有复杂系统的设计经验
    • 积极跟随开源社区
    • 沟通能力强、情商高
    • 有产品意识,不是技术迷
    • 会带人,服从领导,责任心强
    • 会写专利
Read more »

虽然之前做过后台开发,很喜欢微服务的概念,也用了部分微服务的思想,但是回头再看之前很多事情的做法,觉得路子还是相当野的,特别是在微软呆了几年之后,对于Engineering System的理解加深了不少。最近看到了这本书,感觉算是对于这种后台设计思路有了一个非常好的总结,所以写一个小博客把所有的知识点总结一下。

1. 什么是微服务

  • **定义:**微服务是一些协同工作的小而自治的服务。通过将单一职责原则应用在独立的服务上,从而避免由于代码库过大而衍生出的各种问题。
  • **服务多小合适:**一个微服务应该可以在两周内被完全重写。但是服务越小,微服务架构的优点和缺点也就越明显。使用的服务越小,独立性带来的好处就越多,但是管理也会越复杂。
  • **微服务的自治性:**一个微服务是一个独立的实体;服务之间通过网络调用进行通信,避免紧耦合;服务可以彼此间独立进行修改和部署,而不影响其他服务。
  • 微服务的好处:
    • **技术异构性:**微服务帮助你轻松地采用不同的技术。
    • **弹性:**如果系统中一个组件不可用,不会导致级联故障,而影响其他组件。
    • **扩展:**可以只对需要扩展的服务进行扩展,把那些不需要扩展的服务运行在更小的、性能稍差的硬件上。
    • **简化部署:**可以更快地对特定部分的代码进行部署。出了问题,也只会影响一个服务,并且容易快速回滚。
    • **与组织结构相匹配:**自治性,简化管理,提高团队工作热情。
    • **可组合性:**在微服务架构中,系统会开放很多接缝供外部使用。人们可以通过不同的方式使用同一个接缝,从而当情况发生改变时,可以使用不同的方式构建应用。
    • **对可替代性的优化:**可以在需要时轻易地重写服务,或者删除不再使用的服务。
  • **微服务不是银弹:**了解你的系统,找到最适合自己业务的方式。
Read more »

在了解了如何注入API之后,我们来看一下一些比较复杂的JSRT API的用法吧。

在这一篇里,我们会来尝试做如下几件事情:

  1. 支持Promise,并用它来创建异步API
  2. 创建多个执行上下文,并创建一个API用以获取其他执行上下文里面的JS对象

1. 异步调用

Javascript现在在后台都如此被广泛的应用,其最大的好处就在于方便实现异步调用,随着promise和async function的加入,现在在JS中实现异步也越来越方便,代码逻辑也越来越清晰,那么现在我们也来跟上时代,把我们的storage.get API改造成promise吧。

1.1. 修改storage.get的polyfill script

首先,我们把我们注入的API修改成promise的形式,内部实现我们依然可以使用回调函数,这样我们C++的实现部分就不需要做任何的修改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class APIStorageGet : public API
{
public:
// ...
const wchar_t * GetPolyFillScript() const override
{
return
L"(() => {"
L" var executeAPI = apiUtils.executeAPI;"
L" try { storage = storage } catch(err) { storage = {}; };"
L" storage.get = function() { "
L" return new Promise(function(resolve) {"
L" var apiArguments = [ arguments[0], function (data) { resolve(data); } ];"
L" executeAPI('storage.get', apiArguments);"
L" });"
L" };"
L"})()";
}
// ...
};
Read more »

大概了解了ChakraCore的顶层结构和其对应的JSRT API之后,让我们再来看看其他的JSRT API和如何使用它们吧。

为了演示JSRT API,在这一篇里,我们会来尝试如下几件事情:

  1. 注入一个简单的JS API
  2. 简单了解ChakraCore的异常处理
  3. 实现一个简单的API Framework

选择这几个目标的原因是因为——毕竟不管在源码层面使用什么脚本引擎,大家一开始最关注的还是怎么注入自己的API来完成自己想要做的事情呀。

1. Hello World

首先让我们建立一个最简单的程序作为开始吧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
##include "stdafx.h"

##include "ChakraCore.h"


int main()
{
JsRuntimeHandle runtime;
JsContextRef context;
JsValueRef result;

JsCreateRuntime(JsRuntimeAttributeNone, nullptr, &runtime);
JsCreateContext(runtime, &context);
JsSetCurrentContext(context);

JsSetCurrentContext(JS_INVALID_REFERENCE);
JsDisposeRuntime(runtime);

return 0;
}
Read more »

上一篇里我们已经大概了解了如何编译和使用ChakraCore,现在我们来仔细看一看ChakraCore的Hello World,以此出发来了解一下ChakraCore的主体结构大概长的什么样子吧。

1. 代码结构

在看代码之前,先让我们来大概看一眼ChakraCore的代码结构:

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
28
29
30
31
32
33
34
35
.
├─bin :: 生成可执行文件的工程,比如ChakraCore.dll
├─Build :: 各个平台下的makefile或项目工程
├─jenkins :: 给jenkins使用的检查和编译的脚本
├─lib :: ChakraCore主要实现
│ ├─Backend :: JIT的实现
│ ├─Common :: ChakraCore的基础库
│ │ ├─Codex :: 编码转换,比如UTF-8
│ │ ├─Common :: 基础类,比较杂,比如一些和整型相关的数学计算,时间处理之类的
│ │ ├─Core :: 也比较杂,和Common的区别在于这里面一般和ChakraCore的功能相关,比如配置文件等等
│ │ ├─DataStructures :: 基础数据结构,比如数组,链表,HashMap等等
│ │ ├─Exceptions :: 常用异常
│ │ ├─Memory :: 内存管理,GC
│ │ ├─PlaceHolder :: 啊……EXO ME?
│ │ ├─PlatformAgnostic :: 和平台相关的信息的封装,但是这里面只有定义,实现不在这
│ │ └─Util :: 啊……EXO ME?
│ ├─JITClient :: 进程外JIT的客户端
│ ├─JITIDL :: 进程外JIT的协议,IDL定义
│ ├─JITServer :: 进程外JIT的服务端
│ ├─Jsrt :: Jsrt API,其本身都比较简单,主要是用来将ChakraCore内部结构合理的暴露出来
│ ├─Parser :: JavaScript Parser
│ ├─Runtime :: Runtime,ChakraCore最关键的部分之一
│ │ ├─Base :: Runtime中的顶层数据结构,比如我们马上会提到的ThreadContext和ScriptContext
│ │ ├─ByteCode :: ByteCode相关的实现
│ │ ├─Debug :: 用于支持调试的类
│ │ ├─Language :: 比较杂,和JavaScript语言相关但又不太好归类的部分
│ │ ├─Library :: JavaScript对象模型的实现,从基本的bool,int,map到高级的regex和promise
│ │ ├─Math :: 计算相关,加减乘除等等
│ │ ├─PlatformAgnostic :: 平台相关的实现
│ │ └─Types :: 用来实现对象模型的基础类型,比如抽象的Type和RecylableObject
│ └─WasmReader :: WebAssembly加载器
├─manifests :: 用于定义ETW Events的manifest
├─pal :: 一个C标准库的实现,可以使用USING_PAL_STDLIB宏来启用
├─test :: 测试
└─tools :: 一些小工具

2. Runtime和执行上下文(Execution context)

从Hello World中我们可以看到,如果我们需要ChakraCore来运行一段JS脚本,我们至少需要创建两样东西:JsRuntimeHandle(Runtime)和JsContextRef(执行上下文,execution context),这也是ChakraCore一开始最重要的两个概念。

2.1. Runtime:JsrtRuntime

Runtime这个词一听就知道非常重要,在这里也一样,它表示一个完整的用来支持JavaScript运行的环境,它具有自己独立的堆,编译器,JIT的线程和垃圾回收的线程。

在ChakraCore里,Runtime和线程是紧密相关的,但是他们并不是一对一的,一个线程上可以存在多个Runtime,但在任意时刻都只允许存在一个活动的Runtime,而一个Runtime在任意时刻也只能运行在一个线程上,不过如果一个Runtime没有被任何地方使用,比如正在执行一段JS,那么你可以安全地在另一个没有被占用的线程上调用它。

在JSRT API里,JsRuntimeHandle用于表示一个Runtime,如果我们进入JsCreateRuntime这个API,我们就会发现它其实就是其真正Runtime的实现——JsrtRuntime的内存地址。

Read more »

Chakra是Edge浏览器中的JavaScript引擎,在2016年6月份,我看到Edge的博客说,Chakra的benchmark已经超越了Chrome v8,而且看数据还不是一点半点,这件事情让我又惊讶又激动,因为这确实不容易啊,所以当时就非常好奇Chakra是怎么实现的,可是一直都抽不出时间来好好读读代码,直到现在。正好博客也已经搬迁完毕了,所以在一边阅读的过程中,正好一边写一点东西,做个笔记吧。

1. Chakra vs ChakraCore

16年年初,Chakra的源代码被开源放在了github上,项目名叫ChakraCore:https://github.com/Microsoft/ChakraCore

虽然ChakraCore具有几乎所有Chakra的功能,但是它并不是Chakra,他们之间的差别主要有两个:

  1. Chakra中有一套绑定接口用于支持浏览器和UAP,这套接口并没有暴露在ChakraCore中,但是ChakraCore提供了另外一套公开的接口JSRT用来完成同样的功能。
  2. Chakra中有一套用于支持调试的基于COM的接口,但是除了Windows以外的平台并没有COM,所以Chakra正在开发一套新的基于Json接口来支持调试

下面这张来自ChakraCore Github的图很好的说明了ChakraCore和Chakra的区别:
chakracore_componentization

随着时间的发展,现在ChakraCore已经可以在Windows,Linux和Mac上编译和运行。这个我觉得是很有意义的,因为这使得Chakra不是仅仅为了Edge而存在,而是能更好的和整个社区一起发展,比如,现在ChakraCore已经能够支持nodejs,当然我们也可以用它来做别的事情,比如做游戏的脚本引擎等等。

Read more »

在Hexo更新到Hexo 3之后,它提供了一个函数给皮肤用来生成文章目录,不过虽然这个函数提供了一个参数可以在文章目录中输出标题序号,但是文章内部的标题却没有添加,所以阅读文章本身并不是那么直观。为了解决这个问题,我写了一个插件来解决这个问题:hexo-heading-index。

1. 安装

和其他插件一样,hexo-heading-index的安装非常直接:

1
npm install hexo-heading-index --save

2. 配置

hexo-heading-index的配置都放在了_config.yml中,但是可能没有那么的直观,所以我们先来看一个例子:

1
2
3
4
5
6
heading_index:
enable: true
index_styles: "{1} {1} {1} {1} {1} {1}"
connector: "."
global_prefix: ""
global_suffix: ". "

这个配置会让我们生成如"1.2.3.4. "这样的序号。为什么呢?

Read more »
0%