Soul Orbit

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

0%

也许是从C++转过来的习惯,学习一门语言,我总是比较喜欢先弄明白它的对象模型,所以自己就做了一些简单的实验,看看Go的对象模型~


1. 前置准备

为了查看对象的内存布局,我们这里先写一个非常简单的小函数,利用反射递归地遍历对象的所有成员,然后输出其内存位置,从而了解其在内存中的真实状态。

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
func PrintObjectDumpTableHeader() {
fmt.Printf("%-12s%-15s%-20s%-10s %-11s %-4s\n", "Var", "Type", "Address", "RootOffset", "LocalOffset", "Size")
}

func DumpObject(name string, p reflect.Value) {
v := p.Elem()
dumpObject(name, v, v.UnsafeAddr(), v.UnsafeAddr())
}

func dumpObject(path string, v reflect.Value, rootBaseAddr uintptr, localBaseAddr uintptr) {
dumpObjectDetail(path, v, rootBaseAddr, localBaseAddr)

switch v.Kind() {
case reflect.Struct:
childLocalBaseAddr := v.UnsafeAddr()

for i := 0; i < v.NumField(); i++ {
fieldPath := fmt.Sprintf("%s.%s", path, v.Type().Field(i).Name)
dumpObject(fieldPath, v.Field(i), rootBaseAddr, childLocalBaseAddr)
}
}
}

func dumpObjectDetail(path string, v reflect.Value, rootBaseAddr uintptr, localBaseAddr uintptr) {
fmt.Printf("%-12s%-15s0x%016x %10v %11v %4v\n", path, v.Type().String(), v.UnsafeAddr(),
v.UnsafeAddr() - rootBaseAddr, v.UnsafeAddr() - localBaseAddr, v.Type().Size())
}

这样,我们就可以非常简单的调用这个函数来查看任何对象的内存布局了,如下:

1
2
3
4
PrintObjectDumpTableHeader()

var b bool
DumpObject("b", reflect.ValueOf(&b))
Read more »

最近难得放假,想好好学习一下Go,毕竟C++用的我欲仙欲死,而平时比较常用的.NetCore也不是编译语言(Compiled Language),经常遇到一些神奇的限制……

学一门新语言说容易也容易,毕竟语法也就那么一些,而且Go非常精简,只有仅仅25个保留字,但是说难也难,要真正理解一门语言真的需要花费不少的心思,特别我属于那种不了解到一定程度连用都不敢用的人(在工作中经常因为需要使用一些库,就把这些库的实现通读一遍……),于是查找了不少的学习资料。我觉得这些资料对于新手上路真的非常有用,所以把他们都记录在这,希望也会对其他对Go感兴趣的朋友有所帮助。

那就让我们开始吧!
Gopher

1. 基础入门

首先,我们先从最基础的开始:

  • 弄明白怎么使用是第一步,官方的示例讲解必不可少:A Tour of Go
  • 接下来,可以系统的看看Go语言的方方面面,Go语言圣经gopl当仁不让:gopl.io,而且还有中文版
  • 在了解了Go语言本身之后,我们还可以看看语言的一些最佳实践,官方的Effective Go绝对值得一读:https://golang.org/doc/effective_go.html

至此,基础入门肯定是没有问题了,但是如果你是从C/C++这种比较传统的编译语言转过来的,估计到这里会一头雾水 —— 语法我都懂了,但是为什么呢?比如,为什么Go的struct成员变量和函数不提供const修饰?为什么没有信号量?使用嵌入代替继承看着很好,但是多态用起来经常却经常踩坑?这些问题我们就需要更多的资料来帮忙了。

Read more »

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

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


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 »