Spiga

Go学习笔记(九):实现Micro-Kernel(微内核模式)

2020-05-01 20:44:02

摘要:什么是微内核架构 微内核架构(Microkernel Architecture),也被称为插件化架构(Plugin-in Architecture),是一种面向功能进行拆分的可扩展架构。例如 VS Code、Eclipse 这一类 IDE 软件、UNIX 操作系统等等,都是参照微内核架构设计实现的。 微内核架构的两个核心组件 微内核架构包含两类核心的组件:核心系统(Core System)和插件模块(Plug-in modules)。核心系统负责与具体功能无关的通用功能,例如应用生命周期的管理、插件模块的管理(包括:插件模块的注册、载入、卸载等等);插件模块负责实现具体的功能,例如一个 Web 框架基本上会按照功能模块拆分成如下的插件模块:路由模块、安全模块、HTTP 编解码模块等等,每个模块都通过插件实现,每一个插件都只做一件事情。 微内核基本架构示意图如下所示: 核心系统功能尽量保持稳定,不要因为插件模块的扩展而不断修改,插件模块可以根据功能需求进行不断扩展。 特点与要点 特点 易于扩展 错误隔离 保持架构⼀致性 要点 内核包含公共流程或通⽤逻辑 将可变或可扩展部分规划为扩展点 抽象扩展点⾏为,定义接⼝ 利⽤插件进⾏扩展 实例 如下图,我们希望实现Agent在系统主机上,这个Agent可以手机文件信息、进程信息、应用信息,以及提供一个扩展点,可以扩展未来其他要收集的信息,因此我们需要提供一个Extension Point。 1.接口Collector定义 type Collector interface { Init(evtReceiver EventReceiver) error Start(agtCtx context.Context) error Stop() error Destory() error } type Event struct { Source string Content string } type EventReceiver interface { OnEvent(evt Event) } 2.定义Agent结构体 type Agent struct { collectors map[string]Collector evtBuf chan Event cancel c…… 阅读全文

Go学习笔记(八):实现Pipe-Filter(管道过滤器)

2020-04-27 22:50:03

摘要:Pipe-Filter 模式,即管道过滤器模式,这是一种非常经典的架构模式,这种模式与工业制造生产流水线非常类似,就像薯片的生产过程,从土豆的清洗、去皮、切片、烘干、油炸,到最后打包完成,整个生产过程被拆分成了多个环节,每个环节处理完成之后,通过传送带传送到下一个环节的机器。整个生产过程每个环节都是独立的,但又环环相扣,只要有一个环节出问题了,生产出来的薯片就会有质量问题。 适用的场景 ⾮常适合与数据处理及数据分析系统 Filter封装数据处理的功能 Pipe⽤于连接Filter传递数据或者在异步处理过程中缓冲数据流 进程内同步调⽤时,pipe演变为数据在⽅法调⽤间传递 松耦合:Filter只跟数据(格式)耦合 Filter 和组合模式 23 个经典设计模式里面有一个设计模式叫组合模式,当 Pipe-Filter 遇上组合模式时,多个 Filter 又可以再组合成一个新的 Filter,如下图所示,组合出来的 Filter 接收的数据与第一个 Filter 保持一致,返回的数据与最后一个 Filter 保持一致。通过组合,就可以将多个简单的 Filter 可以组合成一个更复杂的 Filter。应用这一套理论去实践,我们会发现,Filter 既可以做的很轻便,也可以做得很强大。 实例 接下来我们用Go语言实现上图实例: 1.定义filter接口 // Package pipefilter is to define the interfaces and the structures for pipe-filter style implementation package pipefilter // Request is the input of the filter type Request interface{} // Response is the output of the filter type Response interface{} // Filter interface is the definition of the data processing components // Pipe-Filter structure type Filter interface { Process(data Request) (Respons…… 阅读全文

Go学习笔记(七):“不安全”编程

2020-04-23 12:31:09

摘要:“不完全”行为的危险性,go语言中是不支持类型转换的,但我们使用“不安全“编程可以将类型的指针转换成任意其他类型,如下: func TestUnsafe(t *testing.T) { i := 10 f := *(*float64)(unsafe.Pointer(i)) t.Log(unsafe.Pointer(i)) t.Log(f) //5e-323, 并不能得到理想的结果 } 也有能转换成功的例子,比如我们对类型起的别名: type MyInt int //合理的类型转换 func TestConvert(t *testing.T) { a := []int{1, 2, 3, 4} b := *(*[]MyInt)(unsafe.Pointer(a)) t.Log(b) //[1 2 3 4] } 原子类型操作 func TestAtomic(t *testing.T) { var shareBufPtr unsafe.Pointer writeDataFn := func() { data := []int{} for i := 0; i 100; i++ { data = append(data, i) } //写完后再通过原子操作,将指针重新指向 atomic.StorePointer(shareBufPtr, unsafe.Pointer(data)) } readDataFn := func() { data := atomic.LoadPointer(shareBufPtr) fmt.Println(data, *(*[]int)(data)) } var wg sync.WaitGroup writeDataFn() for i := 0; i 10; i++ { wg.Add(1) go func() { for i := 0; i 10; i++ { writeDataFn() time.Sleep(time.Microsecond * 100) } wg.Done() }() wg.Add(1) go func() { for i := 0; i 10; i++ { readDataFn(…… 阅读全文

Go学习笔记(六):反射

2020-04-21 21:39:42

摘要:reflect.TypeOf vs. reflect.ValueOf reflect.TypeOf 返回类型 (reflect.Type) reflect.ValueOf 返回值 (reflect.Value) 可以从 reflect.Value 获得类型 通过 kind 的来判断类型 func CheckType(v interface{}) { t := reflect.TypeOf(v) switch t.Kind() { case reflect.Float32, reflect.Float64: fmt.Println(Float) case reflect.Int, reflect.Int32, reflect.Int64: fmt.Println(Integer) default: fmt.Println(Unknown, t) } } func TestBasicType(t *testing.T) { var f float64 = 12 CheckType(f) //Float CheckType(f) //Unknown *float64 } func TestTypeAndValue(t *testing.T) { var f int64 = 10 t.Log(reflect.TypeOf(f), reflect.ValueOf(f)) //int64 10 t.Log(reflect.ValueOf(f).Type()) //int64 } 利用反射编写灵活的代码 按名字访问结构的成员 reflect.ValueOf(*e).FieldByName(Name) 按名字访问结构的方法 reflect.ValueOf(e).MethodByName(UpdateAge).Call([]reflect.Value{reflect.ValueOf(1)}) 实例 type Employee struct { EmployeeID string Name string `format:normal` Age int } …… 阅读全文

Go学习笔记(五):常见并发任务

2020-04-18 23:11:40

摘要:仅执行一次 C#中的单例模式(懒汉式,线程安全) public class Singleton { private static volatile Singleton instance; private static readonly object syncRoot = new object(); private Singleton() { } public static Singleton GetInstance() { if (instance == null) { lock (syncRoot) { if (instance == null) { instance = new Singleton(); } } } return instance; } } Go的实现 type Singleton struct { data string } var singleInstance *Singleton var once sync.Once func GetSingletonObj() *Singleton { once.Do(func() { fmt.Println(Create Obj) singleInstance = new(Singleton) }) return singleInstance } 测试 func TestGetSingletonObj(t *testing.T) { var wg sync.WaitGroup for i := 0; i 5; i++ { wg.Add(1) go func() { obj := GetSingletonObj() fmt.Printf(%X\n, unsafe.Pointer(obj)) …… 阅读全文

Go学习笔记(四):并发

2020-04-15 18:42:50

摘要:协程机制(Groutine) Thead VS Groutine 创建时默认的 stack 的⼤⼩ JDK5 以后 Java Thread stack 默认为1M Groutine 的 Stack 初始化⼤⼩为2K 和 KSE (Kernel Space Entity) 的对应关系 Java Thread 是 1:1 Groutine 是 M:N MPG模型 M 代表着一个内核线程,也可以称为一个工作线程。goroutine就是跑在M之上的 P代表着(Processor)处理器 它的主要用途就是用来执行goroutine的,一个P代表执行一个Go代码片段的基础(可以理解为上下文环境),所以它也维护了一个可运行的goroutine队列,和自由的goroutine队列,里面存储了所有需要它来执行的goroutine G 代表着goroutine 实际的数据结构(就是你封装的那个方法),并维护者goroutine 需要的栈、程序计数器以及它所在的M等信息 我们在看上面这个图,图中P正在执行的Goroutine为蓝色的,处于待执行状态的Goroutine为灰色的,灰色的Goroutine形成了一个队列runqueues 。 我们再看一下三者的宏观图: 由上图可以看出Groutine与KSE是M:N的多对多关系。在这里,当一个P关联多个G时,就会处理G的执行顺序,就是并发,当一个P在执行一个协程工作时,其他的会在等待,当正在执行的协程遇到阻塞情况,例如IO操作等,go的处理器就会去执行其他的协程,因为对于类似IO的操作,处理器不知道你需要多久才能执行结束,所以他不回去等你执行完。 正是因为是非抢占式的,所以才轻松的构造上万的协程,如果是抢占式,那么就会在切换任务时,保存当前的上下文环境,因为当前线程如果正在做一件事,做到一半,我们就强制停止,这时我们就必须多保存很多信息,避免再次切换回来时任务出错。 线程是操作系统层面的多任务,而go的协程属于编译器层面的多任务,go有自己的调度器来调度。一个协程在哪个线程上是不确定的,这个是由调度器来决定的,多个协程可能在一个或多个线程上运行。 编写一个Groutine func TestGroutine(t *testing.T) { for i := 0; i 10; i++ { g…… 阅读全文

Go学习笔记(三):接口

2020-04-12 14:38:51

摘要:封装数据与行为 结构体定义 type Employee struct { Id string Name string Age int } 实例创建及初始化 e := Employee{0, Bob, 20} e1 := Employee{Name: Mike, Age: 30} e2 := new(Employee) //注意这⾥返回的引⽤/指针,相当于 e := Employee{} e2.Id = “2 //与其他主要编程语⾔的差异:通过实例的指针访问成员不需要使⽤- e2.Age = 22 e2.Name = “Rose 行为(方法)定义 //第⼀种定义⽅式在实例对应⽅法被调⽤时,实例的成员会进⾏值复制 func (e Employee) String() string { return fmt.Sprintf(ID:%s-Name:%s-Age:%d, e.Id, e.Name, e.Age) } //通常情况下为了避免内存拷⻉我们使⽤第⼆种定义⽅式 func (e *Employee) String() string { return fmt.Sprintf(ID:%s/Name:%s/Age:%d, e.Id, e.Name, e.Age) } type Employee struct { Id string Name string Age int } func (e Employee) String() string { //这里传递的是类型 fmt.Printf(Address is %x, unsafe.Pointer(e.Name)) } func TestStructOperations(t *testing.T) { e := Employee{0, Bob, 20} fmt.Printf(Address is %x, unsafe.Pointer(e.Name)) t.Log(e.String()) //Address is c000068520 Address is c000068550 } 可以看到上面测试程序调用String方法时传递的是类型,log得到的是2不同的地址,如果改成传递地址呢? func (e Employee) String() …… 阅读全文

Go学习笔记(二):语法基础2

2020-04-09 13:17:12

摘要:数组 数组的声明 var a [3]int //声明并初始化为默认零值 a[0] = 1 b := [3]int{1,2,3} c := [...]int{1,2,3,4,5} //不指定元素个数 d := [2][2]int{{1,2},{3,4}} //多维数组初始化 数组元素遍历 func TestTravelArray(t testing.T) { a := [...]{1,2,3,4,5} for idx/*索引*/,elem/*元素*/ := range a { fmt.PrintIn(idx, elem) } } 数组截取 a[开始索引(包含),结束索引(不报销)] a := [...]int{1,2,3,4,5} a[1:2] //2 a[1:3] //2,3 a[1:len(a)] //2,3,4,5 a[1:] //2,3,4,5 a[:3] //1,2,3 切片 切片的声明 var s0 []int s0 = append(s0, 1) s := []int{} s1 := []int{1, 2, 3} s2 := make([]int, 2, 4) /* []type, len, cap 其中len个元素会被初始化为默认零值,未初始化元素不可以访问 */ 切片共享存储结构 func TestSliceShareMemory(t *testing.T) { year := []string{Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec} Q2 := year[3:6] t.Log(Q2, len(Q2), cap(Q2)) //3,9 summer := year[5:8] t.Log(summer, len(summer), cap(summer)) //3,7 summer[0] = Unknow t.Log(Q2) //因为是共享存储,上一句修改summer[0]会影响Q2的值,结果 [Apr,May,Unknow] t.Log(year) //同理year也会受到summer的赋值影响 }…… 阅读全文

Go学习笔记(一):语法基础1

2020-04-08 11:50:28

摘要:1.编写第一个Go程序 首先我们编写一个Hello World程序 package main //包,表明代码所在的模块(包) import fmt //引⼊代码依赖 //功能实现 func main() { fmt.Println(Hello World!) } 应用程序入口 1.必须是 main 包:package main 2.必须是 main ⽅法:func main() 3.⽂件名不⼀定是main.go main函数的返回值和参数 与其他主要编程语⾔的差异 Go 中main函数不⽀持任何返回值 需要通过os.Exit来返回状态 同样main 函数不⽀持传⼊参数 func main(arg []string) 在程序中直接通过os.Args获取命令⾏参数 package main import ( fmt os ) func main() { if len(os.Args) 1 { fmt.Println(Hello World, os.Args[1]) } os.Exit(-1) } 2. 变量与常量 在开始前先介绍一下go语言如何编写一个测试代码,方便编写测试程序。go语言只要满足下面2点规则就能运行测试 源码⽂件以_test结尾:xxx_test.go 测试⽅法名以Test开头:func TestXXX(t *testing.T) {…} 接下来我们编写一个Fibonacci 数列来介绍变量的定义 数列:1, 1, 2, 3, 5, 8, 13, …. import ( testing ) func TestFibList(t *testing.T) {    // 1. 变量声明一般方法 // var a int = 1 // var b int = 1        // 2. 统一一起声明,注意变量b 去掉了类型定义,系统可以根据赋值自动判断 // var ( // a int = 1 // b     = 1 // )        // 3. 方法3,快捷声明与赋值(推荐) a := 1 b := 1 t.Log(a) for i := 0; i 5…… 阅读全文

数据库调优

2019-11-16 15:35:24

摘要:影响性能因素 数据库结构设计 T-SQL语句 数据量大 事务和隔离级别 硬件资源 IO阻塞 批量删除表数据:大量删除时会记录到日志中,也会造成IO阻塞 优化和注意事项 了解业务 优先考虑第三范式设计,参考设计范式 表关联尽可能少 坚持最小原则 在适当的地方使用约束 用户数据和日志文件隔离存放 T-SQL语句优化 使用字段名,尽量不适用* 条件 从左边开始,先写最小条件锁定最少数据 索引 尽量使用索引字段 索引字段放到左边 不能计算也不要使用函数,否则索引失效 以小表关联大表 SQL语句尽量简单 执行计划 sql官方执行计划文档 执行计划图标和运算符 MSSQLSERVER执行计划详解 - 张龙豪 - 博客园 SQL Server执行计划的理解 - 馨馨妙 - 博客园 点击开启【包括实际的执行计划】 执行计划关键字和图标理解 表扫描: Parameter Table Scan 运算符扫描在当前查询中用作参数的表。 该运算符一般用于存储过程内的 INSERT 查询。 Parameter Table Scan 既是一个逻辑运算符,也是一个物理运算符。 就是扫描查询列整个表全部数据,最耗时性能最低的。 嵌套循环: Nested Loops 运算符执行内部联接、左外部联接、左半部联接和左反半部联接逻辑运算。 嵌套循环联接通常使用索引,针对外部表的每一行在内部表中执行搜索。 查询处理器根据预计的开销来决定是否对外部输入进行排序,以改进内部输入索引上的搜索定位。 将基于所执行的逻辑操作返回所有满足 Argument 列中的(可选)谓词的行。 如果 OPTIMIZED 特性设置为“True”,则表示使用了优化的嵌套循环(或批处理排序) 。 Nested Loops 是一个物理运算符。 有关详细信息,请参阅了解嵌套循环联接。 RID查询 : RID Lookup 是使用提供的行标识符 (RID) 在堆上进行的书签查找。 Argument 列包含用于查找表中的行的书签标签和从中查找行的表的名称。 RID Lookup 通常带有 NESTED LOOP JOIN。 RID Lookup 是一个物理运算符。 有关书签查找的详细信息,请参阅 MSDN SQL Server 博客中的Bookmark Lookup(书签查找)。 哈希匹配: Hash Ma…… 阅读全文