go 语言实现轻量级队列事件

最近再开展整合系统的工作,将原有系统中的共用逻辑抽离出来形成一个中台系统,根据业务开展形式的不同,划分出各个子系统。 在开展的过程中,遇到一种情况,在一些场景下子系统需要根据中台系统的一些操作去初始化自身的业务数据。 我们准备用事件( Event ) 的形式来解决这个问题,子系统监听中台系统发布的一些事件,当中台系统进行相关操作时,触发事件通知监听中的子系统。 我们的中台系统 ( main system ) 是用 go 语言开发的,事件的触发是通过队列的形式发送给 event hadle 然后由它去发送事件通知到各个子系统。 在一些其他语言中,队列的消费一般都是启动一个进程来监听处理。而 go 有协程,可以用更轻量的协程来处理队列消费,这样以来有一个直接的好处就是服务启动了,队列的消费者就跟着启动了,不需要单独维护一个进程的启停。 当然要实现上述能力,有几个问题需要解决: 协程消费者如果崩溃了,不能影响主进程的运行 协程异常要能重新拉起一个新的消费者,保证协程消费者能一直运行。 崩溃隔离 消费者有自己的逻辑,在逻辑处理中有可能会出现 panic,我们需要消费者的 panic 限制在协程里,以避免协程崩溃影响主进程。 Go 有 recover 机制,可以让你捕获 panic 并且限制 panic 不在向上蔓延。代码如下: go (func() { defer func() { if err := recover(); err != nil { fmt.Println("捕获到 panic:", err) return } }() err := EventHandle() if err != nil { Logger.Error("event 处理监听失败", err) } })() 消费者协程保活 消费者业务逻辑在协程里,可能在一些场景(panic 或其他不可知情况)下意外退出,我们需要保证能够重新拉起消费者协程,从而保证整个事件逻辑的闭环。...

January 31, 2024 · 1 min · 云溪

golang 简洁架构介绍

在 go 开发中,首先遇到的一个问题,应该怎么设计开发架构。一个好的架构可以在后续的项目开发中能够更好的使用项目的推进,同时能够让团队的配合更加的顺利。 一个简洁的架构应该具有如下几个特点: 不依赖框架:好的架构应当是独立的,不依赖框架可扩展的 不依赖 UI:UI 应该很容易修改,而不需要改业务部分代码 独立于数据库:不绑定数据库,可以在不改动业务代码的情况下,更换其他数据库 可测试的: 能够比较方便测试用例的编写 下面目录结构引自 《go 整洁模板》 ├─cmd 应用入口 │ └─app ├─config ├─docs // 存放文档 ├─internal │ ├─app │ ├─controller // 控制器 │ │ ├─amqp_rpc │ │ └─http │ │ └─v1 │ ├─entity // 实体层 │ ├─middleware // 中间件 │ └─usecase │ ├─repo // 数据库操作 │ └─webapi // RESTful API ├─migrations ├─pkg //以被外部程序安全导入的包 │ ├─crypto │ ├─httpresponse │ ├─httpserver │ ├─logger │ ├─mysql │ ├─postgres │ ├─rabbitmq │ └─redis 分层介绍 层级调用是通过外层->内层,内层是不知道外层的存在的。在内层代码中,不应该引用外层声明的:变量、类、方法,结构体。...

June 29, 2022 · 1 min · 云溪

golang 接口(interface)最佳实践

接口定义 应该定义在接口所使用的包中,而非接口实现的包中。 以io包中的io.Reader/io.Wirter为例,接口定义在io包中,而他的实现bytes.Buffer、os.File等都是在不同包中。 接口实现应该返回具体类型 应该返回对应的结构体或指针,这样实现类如果扩展新的方法就可以直接添加,无需改动接口定义部分,更加灵活。 //为了方便演示,本实例代码未遵循「接口需要在使用包中声明」规范 package demo import "fmt" type Demo interface { show() error } type MyDemo struct { } //这里的返回值应该是 `MyDemo` 或 `*MyDemo` 而非 `Demo` func NewMyDemo() *MyDemo { return &MyDemo{} } func (md *MyDemo) show() error { fmt.Println("show") return nil } 接口应该什么时候定义 在当前模块有依赖外部服务的时候,这时候就会定义一个接口,来对外部的依赖资源进行抽象解耦,屏蔽接口的实现。相反,依赖的提供方在不知道使用方需求的时候,定义接口也就没什么意义。

June 11, 2022 · 1 min · 云溪

golang设计模式之单例模式

日常开发中经常会遇到单例模式的使用场景,单例模式可以保证我们初始化出来的结构体只有一个,在一些请求上下文,mysql 连接池..场景经常有着不可估量的作用。 在 golang 开发中,我们应当如何去设计单例模式呢? 常见的错误书写方式 相信如果你对 PHP 写的比较多的情况下经常会写出下面代码一样的单例模式 package main type Singleton struct { } var ins *Singleton func GetIns() *Singleton { if ins == nil { ins = &Singleton{} } return ins } 那上述代码,在正常开发存在什么样的问题呢?上述代码如果用于 PHP 项目是没有任何问题的,因为 PHP 本事是请求隔离的,因而也不会存在并发的问题,但是如果放在常驻内存类型的语言中就会出现并发性不安全问题。 接下来考虑一个场景,在高并发场景下,同时又两个业务都执行到 ins == nil 而此时的 ins 确实没有实例化,那程序将会如何执行呢?答案显而易见,两个业务都会实例化出自己的 ins 结构体,这显然不符合我们的程序预期。 如何解决 golang 标准库sync中找到了Once类型。它能保证某个操作仅且只执行一次。有了他我们就可以很方便的解决并发的问题了接下看看代码示例: package main import "sync" type Singleton struct { } var ins *Singleton var once sync.Once func GetIns() *Singleton { once....

June 19, 2021 · 1 min · 云溪

golang base64 编码与 PHP 输出不一致

最近开发中,将一个 php 算法,移植到 golang 中,发现 base64 算法生成的字符串不一致,经过排查发现是由于 ASCII 控制字符导致的原因,加下来看代码 <?php $asciiArr =[10, 187, 217, 12, 207, 183, 184, 231, 184, 149, 118, 151]; $str = ''; foreach ($asciiArr as $ascii) { $str .= chr($ascii); } echo base64_encode($str); 上述代码输出: CrvZDM+3uOe4lXaX package main import ( "encoding/base64" "fmt" ) func main() { res := []int{10, 187, 217, 12, 207, 183, 184, 231, 184, 149, 118, 151} var str string var b []byte for _, v := range res { str += string(v) b = append(b, byte(v)) } byteCode := base64....

June 16, 2021 · 1 min · 云溪