golang中编写测试用例

<!– wp:paragraph –> 作为专业的Go语言软件开发人员,你了解到写单元测试的重要性。在使用Go语言开发软件的过程中,你需要确保你的代码是可靠的,并且可以在各种情况下正常运行。为了实现这一目标,你需要写出可靠的单元测试用例,可以帮助你检测和修复 bugs。 <!– /wp:paragraph –><!– wp:paragraph –> 在Go语言中,为了简化单元测试的过程,你可以使用gotests工具。gotests是一种可用的工具,可以自动化单元测试的过程,并且可以生成单元测试用例。在本文中,我将介绍如何使用gotests生成单元测试用例,并且如何使用gotests工具对照前文中的方法zipDirectory进行单元测试。 <!– /wp:paragraph –><!– wp:paragraph –> ===如何使用gotests生成单元测试用例? <!– /wp:paragraph –><!– wp:paragraph –> 当使用gotests工具生成单元测试用例时,你需要在代码中加入gotests的配置。gotests的配置文件名为_test.go,在每个目录下都需要存在一个_test.go文件。这个文件用来定义测试用例和测试器。 <!– /wp:paragraph –><!– wp:paragraph –> 在代码中,你需要定义测试用例和测试器的函数。测试用例函数的名字必须以Test前缀,示例如TestZipDirectory。测试器函数是一个函数,用来执行测试用例。测试器函数的名字必须以Test前缀,示例如TestZipDirectory。 <!– /wp:paragraph –><!– wp:paragraph –> 如果你没有定义测试用例或测试器函数,gotests会自动生成测试用例和测试器函数。当gotests生成测试用例时,它会自动生成测试用例名,可以选择自定义测试用例名。当gotests生成测试器函数时,它会根据测试用例名生成测试器函数名。 <!– /wp:paragraph –><!– wp:paragraph –> ===使用gotests生成 zipDirectory 方法的单元测试用例 <!– /wp:paragraph –><!– wp:paragraph –> 在使用gotests生成 zipDirectory 方法的单元测试用例时,你需要在代码中定义测试用例和测试器函数。 <!– /wp:paragraph –><!– wp:list –> <!– wp:list-item –>* 首先,你需要在代码中定义测试用例函数。测试用例函数的名字必须以Test前缀,示例如TestZipDirectory。 <!– /wp:list-item –> <!– /wp:list –><!– wp:code –> <pre class=“wp-block-code”><code>func TestZipDirectory(t *testing.T) {</code></pre> ...

April 14, 2024 · 1 min · jiezi

关于golang:Go-反射机制详解

反射的实质就是在程序运行的时候,获取对象的类型信息和内存构造。 应用反射的三个步骤: 先有一个接口类型的变量把它转成reflect对象 个别就是type 或者 value类型而后依据不同的状况调用相应的函数为了阐明其用法,先举个最简略的例子: package mainimport ( "fmt" "reflect")func main() { var x float64 = 3.4 fmt.Println("type : ", reflect.TypeOf(x))}运行后果是:type : float64未完待续。。。。。

May 27, 2022 · 1 min · jiezi

关于golang:Go-CSP并发模型

    Communication Sequential Process(简称CSP),通信顺序进程,这个思维最早是1977年Tony Ho are提出的,CSP的局部引入这被认为是Go语言在并发编程上胜利的关键因素。 大多数语言的并发编程是基于线程和内存同步的访问控制。Go语言的并发编程是基于goroutine【CSP思维中的process】和chanel【CSP思维中的chanel】来代替。哲学:尽量多应用chanel,goroutine来执行并发工作,chanel用来海鲜goroutine之间的同步通信, 能够把chanel了解为在不同goroutine之间的桥梁,从而实现不同goroutine 之间的通信。并且chanel是线程平安的,用起来也非常平安不便。 参考:bilibili

May 27, 2022 · 1 min · jiezi

关于golang:Go-select语句详解

    select是Go提供的一个抉择语句,通过select能够监听chanel上的数据流动。    select语句的应用办法和switch语句类似,由select开始一个新的抉择块,每一个抉择块,每一个抉择条件由case语句来实现。    和switch语句不同的中央在于,select的case条件都是chanel的通信操作, select语句依据不同的case有可能被阻塞,也可能被执行。 举个例子: package mainimport ( "fmt" "time")func main() { ch1 := make(chan int) ch2 := make(chan int) go func() { time.Sleep(3 * time.Second) ch1 <- 100 }() go func() { time.Sleep(3 * time.Second) ch2 <- 100 }() select { case num1 := <-ch1: fmt.Println("ch1中获取的数据: ", num1) case num2, ok := <-ch2: if ok { fmt.Println("ch2中读取的数据: ", num2) } else { fmt.Println("ch2 已敞开") } //default: // fmt.Println("default语句可选 可有可无") } fmt.Println("main goroutine has been completed")}这里因为ch1和ch2都写入了数据,select会随机抉择一个case执行,有default语句就执行default语句,都没有的话就阻塞直到有满足条件的case呈现。 ...

May 27, 2022 · 1 min · jiezi

关于golang:Go-Time包中和chanel相关的函数

    规范库的Timer容许用户自定义超时逻辑(实用于单个chanel读写超时、select解决多个chanel超时等状况)。 留神:Timer是一次性触发,和肯定工夫距离触发的Ticker不同(相似JavaScript里的settimeout和setinterval的区别)。Timer常见的创立形式如下: t := time.NewTimer(d)t := time.AfterFunc(d, f)c := time.After(d)(注:d代表定时工夫;f代表触发的动作;c就是chanel)未完待续...

May 27, 2022 · 1 min · jiezi

关于golang:go语言syncatomic源码阅读

一、atomic包与原子操作atomic包是Go语言提供的原子操作(atomic operation)原语的相干接口。原子操作是绝对于一般指令操作而言的。以一个整型变量自增的语句为例: var i inti++i++这行语句须要以下3条一般机器指令来实现变量i的自增。 LOAD:将变量从内存加载到CPU寄存器。ADD:执行加法指令。STORE:将后果存储回原内存地址。这3条一般指令在执行过程中是可中断的。而原子操作的指令是不可中断的,它就好比一个事务,要么不执行,一旦执行就一次性全副执行结束,不可分割。正因如此,原子操作可用于共享数据的并发同步。 二、i++ 100次为什么失去了谬误的值

May 27, 2022 · 1 min · jiezi

关于golang:通过-SingleFlight-模式学习-Go-并发编程

最近接触到微服务框架go-zero,翻看了整个框架代码,发现构造清晰、代码简洁,所以决定浏览源码学习下,本次浏览的源码位于core/syncx/singleflight.go。 在go-zero中SingleFlight的作用是:将并发申请合并成一个申请,以缩小对上层服务的压力。 利用场景查问缓存时,合并申请,晋升服务性能。假如有一个 IP 查问的服务,每次用户申请先在缓存中查问一个 IP 的归属地,如果缓存中有后果则间接返回,不存在则进行 IP 解析操作。 如上图所示,n 个用户申请查问同一个 IP(8.8.8.8)就会对应 n 个 Redis 的查问,在高并发场景下,如果能将 n 个 Redis 查问合并成一个 Redis 查问,那么性能必定会晋升很多,而 SingleFlight就是用来实现申请合并的,成果如下: 避免缓存击穿。缓存击穿问题是指:在高并发的场景中,大量的申请同时查问一个 key ,如果这个 key 正好过期生效了,就会导致大量的申请都打到数据库,导致数据库的连贯增多,负载回升。 通过SingleFlight能够将对同一个Key的并发申请进行合并,只让其中一个申请到数据库进行查问,其余申请共享同一个后果,能够很大水平晋升并发能力。 利用形式间接上代码: func main() { round := 10 var wg sync.WaitGroup barrier := syncx.NewSingleFlight() wg.Add(round) for i := 0; i < round; i++ { go func() { defer wg.Done() // 启用10个协程模仿获取缓存操作 val, err := barrier.Do("get_rand_int", func() (interface{}, error) { time.Sleep(time.Second) return rand.Int(), nil }) if err != nil { fmt.Println(err) } else { fmt.Println(val) } }() } wg.Wait()}以上代码,模仿 10 个协程申请 Redis 获取一个 key 的内容,代码很简略,就是执行Do()办法。其中,接管两个参数,第一个参数是获取资源的标识,能够是 redis 中缓存的 key,第二个参数就是一个匿名函数,封装好要做的业务逻辑。最终取得的后果如下: ...

May 27, 2022 · 2 min · jiezi

关于golang:Go实现安全双检锁的方法和应用场景

不平安的双检锁从其余语言转入Go语言的同学常常会陷入一个思考:如何创立一个单例? 有些同学可能会把其它语言中的双检锁模式移植过去,双检锁模式也称为懒汉模式,首次用到的时候才创立实例。大部分人首次用Golang写进去的实例大略是这样的: type Conn struct { Addr string State int}var c *Connvar mu sync.Mutexfunc GetInstance() *Conn { if c == nil { mu.Lock() defer mu.Unlock() if c == nil { c = &Conn{"127.0.0.1:8080", 1} } } return c}这里先解释下这段代码的执行逻辑(曾经分明的同学能够间接跳过): GetInstance用于获取构造体Conn的一个实例,其中:先判断c是否为空,如果为空则加锁,加锁之后再判断一次c是否为空,如果还为空,则创立Conn的一个实例,并赋值给c。这里有两次判空,所以称为双检,须要第二次判空的起因是:加锁之前可能有多个线程/协程都判断为空,这些线程/协程都会在这里等着加锁,它们最终也都会执行加锁操作,不过加锁之后的代码在多个线程/协程之间是串行执行的,一个线程/协程判空之后创立了实例,其它线程/协程在判断c是否为空时必然得出false的后果,这样就能保障c仅创立一次。而且后续调用GetInstance时都会仅执行第一次判空,得出false的后果,而后间接返回c。这样每个线程/协程最多只执行一次加锁操作,后续都只是简略的判断下就能返回后果,其性能必然不错。 理解Java的同学可能晓得Java中的双检锁是非线程平安的,这是因为赋值操作中的两个步骤可能会呈现乱序执行问题。这两个步骤是:对象内存空间的初始化和将内存地址设置给变量。因为编译器或者CPU优化,它们的执行程序可能不确定,先执行第2步的话,锁外边的线程很有可能拜访到没有初始化结束的变量,从而引发某些异样。针对这个问题,Java以及其它一些语言中能够应用volatile来润饰变量,理论执行时会通过插入内存栅栏阻止指令重排,强制依照编码的指令程序执行。 那么Go语言中的双检锁是平安的吗? 答案是也不平安。 先来看看指令重排问题: 在Go语言标准中,赋值操作分为两个阶段:第一阶段对赋值操作左右两侧的表达式进行求值,第二阶段赋值依照从左至右的程序执行。(参考:https://golang.google.cn/ref/...) 说的有点形象,但没有提到赋值存在指令重排的问题,隐约感觉不会有这个问题。为了验证,让咱们看一下上边那段代码中赋值操作的伪汇编代码: 红框圈进去的局部对应的代码是: c = &Conn{"127.0.0.1:8080", 1} 其中有一行:CMPL $0x0, runtime.writeBarrier(SB) ,这个指令就是插入一个内存栅栏。前边是要赋值数据的初始化,后边是赋值操作。如此看,赋值操作不存在指令重排的问题。 既然赋值操作没有指令重排的问题,那这个双检锁怎么还是不平安的呢? 在Golang中,对于大于单个机器字的值,读写它的时候是以一种不确定的程序屡次执行单机器字的操作来实现的。机器字大小就是咱们通常说的32位、64位,即CPU实现一次无定点整数运算能够解决的二进制位数,也能够认为是CPU数据通道的大小。比方在32位的机器上读写一个int64类型的值就须要两次操作。(参考:https://golang.google.cn/ref/...) 因为Golang中对变量的读和写都没有原子性的保障,所以很可能呈现这种状况:锁里边变量赋值只解决了一半,锁外边的另一个goroutine就读到了未齐全赋值的变量。所以这个双检锁的实现是不平安的。 Golang中将这种问题称为data race,说的是对某个数据产生了并发读写,读到的数据不可预测,可能产生问题,甚至导致程序解体。能够在构建或者运行时查看是否会产生这种状况: $ go test -race mypkg // to test the package$ go run -race mysrc.go // to run the source file$ go build -race mycmd // to build the command$ go install -race mypkg // to install the package另外上边说单条赋值操作没有重排序的问题,然而重排序问题在Golang中还是存在的,稍不留神就可能写出BUG来。比方下边这段代码: ...

May 27, 2022 · 3 min · jiezi

关于golang:又一-Golang-Proto-Toml-SQL-转换神器

背景在用 Golang 语言做“多”服务开发过程中,做了很多 CURD 业务, 静下来一推敲发现: 出需要依照需要建表通过表定义 grpc 字段,也就是定义 proto 。proto 生成 pb.go 文件。端口层出接口定义, json 格局。就是把mysql字段提取进去,而后生成go构造体,proto message 而已,于是就在想是否做个工具来解决这个问题。 笔者菜鸡,也就用 golang 搞搞 curd 啦~所以,我就想有一款工具可能定义好 create table sql,就能主动创立出 proto 文件,json 构造体。 鸽说干就干,不过磕磕绊绊,捣鼓了一年多,鸽了又鸽,终于面世了。 看着去年的提交,做这么个简略的货色也要那么久啊~ 我的项目地址拜访 http://tools.itjsz.com 应用SQL通过SQL 生成 Go struct, Proto这里咱们传入 Wordpress 库的 wp_user 表。 暂不反对通过其余类型数据来生成 create table SQLYaml通过 yaml 生成其余格局数据这里拿 k8s 创立 deploy 的yaml举例 Toml通过 Toml 转换成其余格局数据我的项目中经常用 Toml 作为配置文件。这里咱们传入 Toml 文本 其余类型其余还有 json,xml,proto 格局,就不再一一演示了,应用办法大同小异。 最初产品已上线,欢送大家体验,应用中遇到啥问题,或者有什么倡议,通过上面工总号通知我。

May 26, 2022 · 1 min · jiezi

关于golang:go语言syncmap源码阅读

一、sync.map介绍Go语言在提供CSP并发模型原语的同时,还通过规范库的sync包提供了针对传统基于共享内存并发模型的根本同步原语,包含互斥锁(sync.Mutex)、读写锁(sync.RWMutex)、条件变量(sync.Cond)哈希表sync.map等。 sync.map是基于传统基于共享内存并发模型的根本同步原语。

May 26, 2022 · 1 min · jiezi

关于golang:golang分布式集群实现与TCPIP精讲

https://edu.51cto.com/course/...

May 26, 2022 · 1 min · jiezi

关于golang:M1-芯片-Mac-上更好的-Golang-使用方案

本篇文章,将分享如何在苹果 M1 Mac 设施上,来进行高效、牢靠的 Golang 开发环境的装置和治理。 写在后面如果你是一个 Golang 的用户,那么你大概率会遇到治理和保护 Golang 版本的诉求,如果你恰好同时须要开发调试两个不同版本的我的项目,在不思考强制跳版本的状况下,你或者就须要应用“Golang 版本管理工具”来帮忙你减轻负担了。 去年年末,我曾在一篇文章中分享过三种对于《Golang 多版本治理》的计划。 相比拟官网计划 golang/dl 或者社区计划 voidint/g 这两个更适宜装置或治理繁多版本的计划之外,我更举荐应用的计划是 gvm 。 它除了可能实现 golang 开发环境的疾速装置之外,还可能保障多个版本的 golang 共存,不同版本的软件依赖包都放弃失常工作。并且,它的实现和社区赫赫有名的 nvm-sh/nvm 、shyiko/jabba 是统一的,都是由 BASH 编写,和所须要治理的 Runtime 语言无关,可能更稳固的实现“管理工作”。 但惋惜的是,它和 M1 设施存在兼容性问题,它并不可能很好的运行,甚至能够说,齐全无奈运行。 暂且不说我是如何解决问题的,让咱们先来看看,怎么可能在数秒、几分钟内实现 Golang 开发环境的装置和切换吧。 装置和应用 Golang 版本管理工具:gvm想要应用 gvm,须要先实现一次“绿色装置”(下载)。 如何装置 gvmgvm 的装置非常简单,只须要“一句话”: curl -sSL https://github.com/soulteary/gvm/raw/master/binscripts/gvm-installer | bash当命令执行结束之后,咱们能够看到相似上面的日志输入: Cloning from https://github.com/soulteary/gvm.git to /Users/soulteary/.gvmNo existing Go versions detectedInstalled GVM v1.0.24Please restart your terminal session or to get started right away run `source /Users/soulteary/.gvm/scripts/gvm`接下来,咱们抉择从新关上命令行终端,或者是抉择执行上一步提醒的 source /Users/soulteary/.gvm/scripts/gvm 命令都能够让装置的 gvm 命令失效。 ...

May 26, 2022 · 3 min · jiezi

关于golang:进程内优雅管理多个服务

前言在 go-zero 社区里,常常会有同学问,把 API gateway 和 RPC service 放在同一个过程内可不可以?怎么弄?有时也会有同学把对外服务和生产队列放在一个过程内。咱们权且不说此种用法正当与否,因为各个公司的业务场景和开发模式的差别,咱们就只来看看此类问题怎么解比拟优雅。 问题举例咱们用两个 HTTP 服务来举例,咱们有这样两个服务,须要启动在一个过程内的两个不同端口。代码如下: package mainimport ( "fmt" "net/http")func morning(w http.ResponseWriter, req *http.Request) { fmt.Fprintln(w, "morning!")}func evening(w http.ResponseWriter, req *http.Request) { fmt.Fprintln(w, "evening!")}type Morning struct{}func (m Morning) Start() { http.HandleFunc("/morning", morning) http.ListenAndServe("localhost:8080", nil)}func (m Morning) Stop() { fmt.Println("Stop morning service...")}type Evening struct{}func (e Evening) Start() { http.HandleFunc("/evening", evening) http.ListenAndServe("localhost:8081", nil)}func (e Evening) Stop() { fmt.Println("Stop evening service...")}func main() { // todo: start both services here}代码是足够简略的,就是有申请 morning 接口,服务返回 morning!,申请 evening 接口,服务返回 evening 。让咱们来尝试实现一下~ ...

May 26, 2022 · 2 min · jiezi

关于golang:unsafe-非类型安全类型

unsafe库彷徨在“类型平安”边缘,因为它们绕过了 Golang 的内存平安准则,个别被认为应用该库是不平安的。然而,在许多状况下,unsafe库的作用又是不可代替的,灵便地应用它们能够实现对内存的间接读写操作。在reflect库、syscall库以及其余许多须要操作内存的开源我的项目中都有对它的援用。 unsafe库源码极少,只有两个类型的定义和三个办法的申明。 Pointer 类型 这个类型比拟重要,它是实现定位欲读写的内存的根底。官网文档对该类型有四个重要形容:(1)任何类型的指针都能够被转化为 Pointer(2)Pointer 能够被转化为任何类型的指针(3)uintptr 能够被转化为 Pointer(4)Pointer 能够被转化为 uintptr举例来说,该类型能够这样应用: func main() { i := 100 fmt.Println(i) // 100 p := (*int)unsafe.Pointer(&i) fmt.Println(*p) // 100 *p = 0 fmt.Println(i) // 0 fmt.Println(*p) // 0}Sizeof 函数该函数的定义如下: func Offsetof(v ArbitraryType) uintptr该函数返回由 v 所批示的某构造体中的字段在该构造体中的地位偏移字节数,留神,v 的表达方式必须是“ struct.filed ”模式。举例说明,在 64 为零碎中运行以下代码: type Datas struct{ c0 byte c1 int c2 string c3 int}func main(){ var d Datas fmt.Println(unsafe.Offset(d.c0)) // 0 fmt.Println(unsafe.Offset(d.c1)) // 8 fmt.Println(unsafe.Offset(d.c2)) // 16 fmt.Println(unsafe.Offset(d.c3)) // 32}如果晓得的构造体的起始地址和字段的偏移值,就能够间接读写内存: ...

May 25, 2022 · 1 min · jiezi

关于golang:Go学习笔记Gin常用功能

路由gin 框架中采纳的路由库是基于httprouter做的Restful格调的APIRepresentational State Transfer 体现层状态转化,是一种互联网应用程序的API设计理念: URL定位资源,用HTTP形容操作增 POST / 删 DELETE / 改 PUT / 查 GET参数API参数:Param办法 r.GET("/:name/*action", func(c *gin.Context) { name := c.Param("name") action := c.Param("action") // action = /yyy action = strings.Trim(action, "/") //截取 c.String(http.StatusOK, name+" - "+action) })URL参数: DefaultQuery():参数不存在,返回默认值 c.DefaultQuery("name", "枯藤")Query():参数不存在,返回空表单参数:PostForm办法 types := c.DefaultPostForm("type", "post")username := c.PostForm("username")上传文件multipart/form-data格局用于文件上传gin文件上传与原生的net/http办法相似,不同在于gin把原生的request封装到c.Request中上传单个文件<form action="http://localhost:8080/upload" method="post" enctype="multipart/form-data"> 上传文件:<input type="file" name="file" > <input type="submit" value="提交"></form>//限度上传最大尺寸r.MaxMultipartMemory = 8 << 20r.POST("/upload", func(c *gin.Context) { file, err := c.FormFile("file") if err != nil { c.String(500, "上传图片出错") return } // c.JSON(200, gin.H{"message": file.Header.Context}) c.SaveUploadedFile(file, file.Filename) c.String(http.StatusOK, file.Filename)})上传限度限度文件类型:headers.Header.Get("Content-Type")限度文件大小:headers.Sizer.POST("/upload-limit", func(c *gin.Context) { _, headers, err := c.Request.FormFile("file") if err != nil { c.String(500, "上传图片出错") return } if headers.Size > 1024*1024*2 { c.String(500, "图片太大了") return } t := headers.Header.Get("Content-Type") if t != "image/jpeg" { c.String(500, "图片格式谬误:"+t) return } // c.JSON(200, gin.H{"message": file.Header.Context}) c.SaveUploadedFile(headers, "./upload/"+headers.Filename) c.String(http.StatusOK, headers.Filename)})上传多个文件<form action="http://localhost:8000/upload" method="post" enctype="multipart/form-data"> 上传文件:<input type="file" name="files" multiple> <input type="submit" value="提交"></form>r.POST("/upload", func(c *gin.Context) { form, err := c.MultipartForm() if err != nil { c.String(http.StatusBadRequest, fmt.Sprintf("get err %s", err.Error())) } // 获取所有图片 files := form.File["files"] // 遍历所有图片 for _, file := range files { // 一一存 if err := c.SaveUploadedFile(file, file.Filename); err != nil { c.String(http.StatusBadRequest, fmt.Sprintf("upload err %s", err.Error())) return } } c.String(200, fmt.Sprintf("upload ok %d files", len(files)))})分组 Groupv1 := r.Group("/v1"){ v1.GET("/login", login) v1.GET("submit", submit)}数据解析和绑定type Login struct { // binding:"required"润饰的字段,若接管为空值,则报错,是必须字段 User string `form:"username" json:"user" uri:"user" xml:"user" binding:"required"` Password string `form:"password" json:"password" uri:"password" xml:"password" binding:"required"`}Jsonvar json Login// 将request的body中的数据,主动依照json格局解析到构造体c.ShouldBindJSON(&json); //json.User json.Password表单<form action="http://localhost:8000/loginForm" method="post" enctype="application/x-www-form-urlencoded"> 用户名<input type="text" name="username"><br> 明码<input type="password" name="password"> <input type="submit" value="提交"></form>var form Loginc.Bind(&form); // form.User form.PasswordURIr.GET("/:user/:password", func(c *gin.Context) { var login Login c.ShouldBindUri(&login);// login.User login.Password} 渲染数据渲染JSON ...

May 25, 2022 · 8 min · jiezi

关于golang:Go-定向通道单向通道

定向通道是只能发送or只能接收数据的通道。未完待续。。。。。

May 25, 2022 · 1 min · jiezi

关于golang:Go-缓冲通道bufchan用法

非缓冲通道: make(chan T)一次发送 一次接管 都是阻塞的缓冲通道: make(chan T, capacity)发送:缓冲区数据满了才阻塞接管:缓冲区数据空了才接管 举个例子: package mainimport ( "fmt" "strconv")func main() { //非缓冲通道 ch1 := make(chan int) fmt.Println(len(ch1), cap(ch1)) //0 0 //缓存通道 ch2 := make(chan int, 5) ch2 <- 100 ch2 <- 200 ch2 <- 300 ch2 <- 400 ch2 <- 500 fmt.Println(len(ch2), cap(ch2)) fmt.Println("=======================") ch3 := make(chan string, 4) // 启动一个子协程 放进去ch3 go sendData(ch3) for { v, ok := <-ch3 if !ok { fmt.Println("Reading completed ") break } fmt.Println("\tthe data read is : ", v) } fmt.Println("main-goroutine is finished")}func sendData(ch chan string) { for i := 0; i < 10; i++ { ch <- "Data " + strconv.Itoa(i) fmt.Println("子协程写入第 %d 个数据", i) } close(ch)}运行后果是: ...

May 25, 2022 · 2 min · jiezi

关于golang:Go-布隆过滤器

A go bloom filter , base on different implement like redis ... what?上一篇在提到缓存击穿的时候, 有一种解决办法就是布隆过滤器布隆过滤器(英語:Bloom Filter)是1970年由布隆提出的。 它实际上是一个很长的二进制向量和一系列随机映射函数。 布隆过滤器能够用于检索一个元素是否在一个汇合中。 它的长处是空间效率和查问工夫都远远超过个别的算法,毛病是有肯定的误识别率和删除艰难why?布隆过滤器: 能够判断某元素在不在汇合外面, 因为存在肯定的误判和删除简单问题;个别的应用场景是: 避免缓存击穿(避免歹意攻打)垃圾邮箱过滤cache digests (缓存索引)模型检测器判断是否存在某行数据,用以缩小对磁盘拜访,进步服务的拜访性能how?根本思维通过多个hash办法, 进行屡次 hash 操作, 使其值位于bit不同位上, 检测该bit上的数据是否为1, 从而判断是否存在 源码剖析interface: bloom.go // 过滤器的外围实现, 通过interface的形式, 能够反对多种实现// 目前实现了基于redis bit数据类型的过滤器Provider interface { Add(data []byte) error Exists(data []byte) (bool, error)}// Filter is a bloom filterFilter struct { // todo counter total int64 hit int64 miss int64 provider Provider}redis实现: internal/redis/redis_bit.go // 实现Provider接口的两个办法// Add implement Provider interfacefunc (r *Provider) Add(data []byte) error { location := r.getBitLocation(data) return r.set(location)}// Exists implement Provider interfacefunc (r *Provider) Exists(data []byte) (bool, error) { location := r.getBitLocation(data) return r.check(location)}// 外围办法// 通过14次hash, 每次hash都在数据最初追加一个byte(index), 最初进行取模, 散布在map外面的每个区间// 查看是否存在时, 对每个bit位进行判断, 如果有一个等于0, 则数据不存在// getBitLocation return data hash to bit locationfunc (r *Provider) getBitLocation(data []byte) []uint { l := make([]uint, maps) for i := 0; i < maps; i++ { hashV := r.hash(append(data, byte(i))) l[i] = uint(hashV % uint64(maps)) } return l}todo ...

May 25, 2022 · 1 min · jiezi

关于golang:架构整洁之道读书笔记-Clean-Aechitecture

 最近感觉网络上贩卖的焦虑切实是太多了, 其实以前也多多少少有这种感觉, 最近尤为重大, 可能也和开年后工作强度变大无关吧, 搞的身心比拟疲乏. 一些专业性比拟强的书也看不进去了, 拿起来之前没有看完的 Bob 大叔整洁系列的书缓缓看完了, 不得不说, 看书的确是一种比拟好的降压形式, 本篇文章次要记录一些书中的内容. 1) 介绍 这本书为 Bob 大叔(Robert C. Martin)的驰名作品, 同系列的还有《代码整洁之道》, 都是经典作品, 值得浏览. 该书零碎的分析了架构设计的缘起, 外延以及利用场景. 第 1 局部形容架构设计的重点与模式第 2~4 局部次要为编程范式第 5 局部为架构设计中的组件边界、设计模式等第 6 局部为实现细节附录局部为作者的一些亲身经历等 这个系列其实还有一本作品 - 《代码整洁之道 - 程序员的职业素养》(The Clean Coder - A code of Conduct For Professional Programmers), 次要讲述作者的一些经验, 如何走上编程路线等, 同样举荐浏览. 读完不禁感叹, 大神的成长之路也不是一帆风顺的, 其中教训尽管不能说肯定实用于当初, 然而齐全值得咱们学习和借鉴的. <!--truncate--> 2) 设计与架构到底是什么1) 指标软件架构的终极目标 用最小的人力老本满足构建和保护该零碎的需要;2) 两个价值维度能够从两个价值维度来形容 行为价值是最直观的价值维度, 程序员的工作就是让机器依照某种指定形式运行, 给零碎的使用者发明或者进步利润. 大部分程序员认为这就是他们的全副工作. 他们的工作是且仅是: 依照需要文档编写代码, 并且修复任何Bug. ...

May 25, 2022 · 2 min · jiezi

关于golang:gozero效率工具一览图

高清无码图昨晚发了朋友圈,后果泛滥好友通知我,看不清,看不清。。。 所以,今日特奉上高清无码大图,一字不差,供大家缓缓消化。 额定福利除了奉上高清无码原图外,还轻轻通知你一个无敌技巧,输出 goctl 之后,按 Tab 键有可用命令的残缺提醒,如图: 再比方: 是不是不看图也能应用了呢? 快来通知咱们,你还想 goctl 加上啥你冀望的性能,助你进一步晋升开发效率。 go-zero 的口号(slogan)是:缩短从需要到上线的间隔 1.18 公布啦大家最最关注的个性应该就是泛型了,然而怎么用呢? https://github.com/kevwan/mapreduce 看这个就晓得了。然而 Go 1.18 仍然不反对 template method,具体是啥?看上面这个就晓得了 https://github.com/kevwan/stream 目前没有方法搞定这个的实现,预知其祥,点击查看。 我的项目地址https://github.com/zeromicro/go-zero 欢送应用 go-zero 并 star 反对咱们! 微信交换群关注『微服务实际』公众号并点击 交换群 获取社区群二维码。

May 18, 2022 · 1 min · jiezi

关于golang:你会犯这些-Go-编码错误吗二

大家好,我是煎鱼。 前一次给大家分享了《你会犯这些 Go 编码谬误吗(一)?》,不晓得大家排汇的怎么样,还有再踩到相似的坑吗? 明天持续来第二弹,跟煎鱼上车。 Go 常见谬误6. 同名变量的作用域问题咱们在编写程序时,因为各种长期变量,会罕用变量名 n、i、err 等。有时候会遇到一些问题,如下代码: func main() { n := 0 if true { n := 1 n++ } fmt.Println(n)}程序的输入后果是什么。n 是 1,还是 2? 输入后果: 0解决办法上述代码的 n := 1 又从新申明了一个新变量,他是同名变量,同时很要害的,他蕴含同名局部变量。 咱们的 n++ 影响的是 if 区块里的变量 n,而不是内部的同名变量 n。如果要正确影响,该当批改为: func main() { n := 0 if true { n = 1 n++ } fmt.Println(n)}输入后果: 2这一个案例尽管繁多提出来看并不简单,但在许多 Go 初学者刚入门时常常会在应用程序中遇到相似的问题,而后问为什么不行... 据鱼的 7s 记忆,在全局 DB 句柄中等场景中遇到这个问题,来问煎鱼的应该有 10 次以上,是一个比拟高频的 ”坑“ 了。 7. 循环中的长期变量问题置信不少同学在业务代码中做过相似的事件,那就是:边循环解决业务数据,边变更值内容。 ...

May 18, 2022 · 2 min · jiezi

关于golang:SegmentFault-思否技术周刊-Go-语言通关攻略

Go(又称 Golang )是Google开发的一种动态强类型、编译型、并发型,并具备垃圾回收性能的编程语言。 常识进阶《Go 1.18 泛型全面解说:一篇讲清泛型的全副》 2022年3月15日,争议十分大但同时也备受期待的泛型终于随同着Go1.18公布了。可是因为Go对泛型的反对时间跨度太大,有十分多的以“泛型”为关键字的文章都是在介绍Go1.18之前的旧泛型提案或者设计,而很多设计最终在Go1.18中被废除或产生了更改。并且很多介绍Go1.18泛型的文章(包含官网的)都过于简略,并没对Go的泛型做残缺的介绍,也没让大家意识到这次Go引入泛型给语言减少了多少复杂度(当然也可能单纯是我没搜到更好的文章)出于这些起因,我决定参考 The Go Programming Language Specification ,写一篇比拟残缺零碎介绍Go1.18 泛型的文章,这可能是目前介绍Go泛型比拟全面的文章之一了。本文力求能让未接触过泛型编程的人也能较好了解Go的泛型,所以行文可能略显啰嗦,置信看完这篇文章你能取得对Go泛型十分全面的理解。《集体教训分享如何浏览Go语言源码》 Go源码包含哪些、查看规范库源代码、查看Go语言底层实现《Go 读者发问:Go 函数返回值命名有存在的意义吗?》 实际上带命名的返回参数,比拟带有 Go 的格调,就是显式命名了返回。但也会带来可能存在的函数内返回的省略,以至于很多人新入门的敌人看不懂。又或是像是文章内所介绍的,带命名的返回参数写着写着变成递归函数,一手抖也是会呈现的。《Go 设计哲学:少即是多,哪里来的?》 之前在 Go 社区分享常识和教训时,常常会听见诸如:less is more、少即是多,大道至简、小道不停地至简等黑话。甚至探讨 Go issues 和提案时,都会有人用 “less is more” 来反驳或做为论点撑持,十分有意思。大家都会很好奇,出处是哪里,是什么意思?《为什么 Go 用起来会好受?这 6 个细节你晓得吗?》 在做新的利用选型时,咱们会进行利用编程语言的抉择,这时会纠结 Java、PHP、Go...各种,会思考有没有致命的问题,不能用?能够明确的是,Go 没有十分致命的问题,否则你我他都不会在这里相遇,也不会大火。好受的点倒是有不少,明天就和大家一起来看看。上手实操《golang 开发框架文档集》 Istio、Go-kit、Go-kratos、Go-micro、Go-zero、Goa、gizmo、Dubbo-go、Jupiter、Tars-go《Go的高效开发套路》 以后在公司进行Go服务端研发工作时,发现短少Go开发的最佳实际,而导致以下景象:1.用Go开发时会比拟迷茫,不知如何下手,怎么发展工作比拟高效。2.反复造轮子比较严重。3.我的项目的代码品质参差不齐,导致交付的产品质量参差不齐。4.产品运行黑盒,可观测性差,能跑就行。5.代码实现考验研发人员程度,但顶尖的毕竟是多数,往往比拟差,而且顶尖也说不准会犯错。6.一个人负责整个性能开发,一旦人员到职,代码保护就会难上艰巨。......《用Go构建你专属的JA3指纹》 本文将简略回顾https的握手流程,并解释什么是JA3指纹以及如何用Go定制专属的JA3指纹,各位读者能够跟着作者的思路逐渐构建本人专属的JA3指纹。《带你十天轻松搞定 Go 微服务之大结局(分布式事务)》 咱们通过一个系列文章跟大家具体展现一个 go-zero 微服务示例,整个系列分十篇文章,目录构造如下:1.环境搭建2.服务拆分3.用户服务4.产品服务5.订单服务6.领取服务7.RPC 服务 Auth 验证8.服务监控9.链路追踪10.分布式事务(本文)冀望通过本系列带你在本机利用 Docker 环境利用 go-zero 疾速开发一个商城零碎,让你疾速上手微服务。面试真题《面试官:简略聊聊 Go 逃逸剖析?》 面试官:“写过C/C++的同学都晓得,调用驰名的malloc和new函数能够在堆上调配一块内存,这块内存的应用和销毁的责任都在程序员。一不小心,就会产生内存泄露。那你说下Golang 是怎么解决这个问题的”胖虎:“Golang 通过逃逸剖析,对内存治理进行的优化和简化,它能够决定一个变量是调配到堆还栈上。”《面试官:来说一说Go语言的函数调用常规》 本文与大家聊一聊Go语言的函数调用常规,调用常规是调用方和被调用方对于参数和返回值传递的约定,Go语言的调用常规在1.17版本进行了优化,上面就让咱们看一下两个版本的调用常规是什么样的吧~《Go语言切片面试真题7连问》 1.数组和切片有什么区别?2.拷贝大切片肯定比拷贝小切片代价大吗?3.切片的深浅拷贝4.零切片、空切片、nil切片是什么5.切片的扩容策略6.参数传递切片和切片指针有什么区别?7.range遍历切片有什么要留神的?热门问答Golang通道不了解之处Golang 参数为接口类型怎么读取属性这段go代码是什么意思?Golang的map不能批改元素?Golang打印一个构造体后果是一个整型?Golang的interface为什么不能接管int?golang 的第三方包管理机制和 Python 有什么不同?从 Golang 的 context 中读写数据为什么是并发平安的?课程举荐《7 天玩转 GO 语言》 本门课程为图文课程 ...

May 18, 2022 · 1 min · jiezi

关于golang:windows安装golang

golang下载链接:https://go.dev/dl/配置环境变量 在零碎环境变量外面加上下载的go目录eg:C:\Program Files\Go\bin$ go version 查看装置的版本配置go环境 举荐用PowerShell关上cmd$go env 查看go的环境变量$go env -w GO111MODULE=on 设置go module 治理我的项目$go env -w GOPROXY=https://goproxy.cn,direct 设置代理goroot为go装置的门路gopath为go我的项目所在的门路(高版本不在依赖gopath来治理包)装置git不便在github上下载包https://git-scm.com/downloads$git version查看装置的版本将git目录的bin文件增加到零碎环境变量Idea举荐 vscode 须要code runner插件和go插件goland 须要插件generate struct tags for golang

May 18, 2022 · 1 min · jiezi

关于golang:使用Air热加载Go程序windows下的配置

Windows 下装置Air go env -w GOPROXY=https://goproxy.cn,directgo env -w GO111MODULE=ongo install github.com/cosmtrek/air@latest装置当前,门路退出环境变量配置. 2.我的项目配置在我的项目的根目录下创立文件 .air.conf root = "."tmp_dir = "tmp"[build]# 只须要写你平时编译应用的shell命令。你也能够应用 `make`cmd = "go build -o main.exe ."# 由`cmd`命令失去的二进制文件名bin = "main.exe"# 自定义执行程序的命令,能够增加额定的编译标识例如增加 GIN_MODE=releasefull_bin = "main.exe"# 监听以下文件扩展名的文件.include_ext = ["go", "tpl", "tmpl", "html"]# 疏忽这些文件扩展名或目录exclude_dir = ["assets", "tmp", "vendor", "frontend/node_modules"]# 监听以下指定目录的文件include_dir = []# 排除以下文件exclude_file = []# 如果文件更改过于频繁,则没有必要在每次更改时都触发构建。能够设置触发构建的延迟时间delay = 1000 # ms# 产生构建谬误时,进行运行旧的二进制文件。stop_on_error = true# air的日志文件名,该日志文件搁置在你的`tmp_dir`中log = "air_errors.log"[log]# 显示日志工夫time = true[color]# 自定义每个局部显示的色彩。如果找不到色彩,应用原始的应用程序日志。main = "magenta"watcher = "cyan"build = "yellow"runner = "green"[misc]# 退出时删除tmp目录clean_on_exit = true3.应用命令air启动我的项目 air.exe -c .air.conf ...

May 17, 2022 · 1 min · jiezi

关于golang:常见排序算法的golang-实现

五种根底排序算法比照 1:冒泡排序算法形容比拟相邻的元素。如果第一个比第二个大,就替换它们两个;对每一对相邻元素作同样的工作,从开始第一对到结尾的最初一对,这样在最初的元素应该会是最大的数;针对所有的元素反复以上的步骤,除了最初一个;反复步骤1~3,直到排序实现。 动图演示 代码演示func bubbleSort(arr []int) []int { if len(arr) <= 1 { return arr } for e := len(arr) - 1; e > 0; e-- { for i := 0; i < e; i++ { if arr[i] > arr[i+1] { Swap(arr, i, i+1) //替换元素 } } } return arr}func Swap(arr []int, i, j int) []int { temp := arr[j] arr[j] = arr[i] arr[i] = temp return arr}2:抉择排序算法形容n个记录的间接抉择排序可通过n-1趟间接抉择排序失去有序后果。具体算法形容如下: 将假想墙搁置在数字列表最左侧,墙的左侧为已排序子列表,右侧为未排序子列表;找出(抉择)未排序子列表中的最小(或最大)元素;把抉择的元素与未排序列表中第一个元素进行替换;将假想墙向右挪动一个地位;重复执行 2 至 4 步操作,直至整个数字列表排序实现(须要 n - 1 轮)。动图演示 ...

May 17, 2022 · 2 min · jiezi

关于golang:Radix压缩字典树的原理以及Go语言实现代码

Radix树Radix树,即基数树,也称压缩字典树,是一种提供key-value存储查找的数据结构。radix tree罕用于疾速查找的场景中,例如:redis中存储slot对应的key信息、内核中应用radix tree治理数据结构、大多数http的router通过radix治理路由。Radix树在Trie Tree(字典树)的原理上优化过去的。因而在介绍Radix树的特点之首先简略介绍一下Trie Tree。 Trie树Trie Tree,即字典树。Trie Tree的原理是将每个key拆分成一个个字节,而后对应到每个分支上,分支所在的节点对应为从根节点到以后节点的拼接出的key的值。下图由四个字符串:haha、hehe、god、goto,造成的一个Trie树的示例:利用上图中结构的Trie Tree,咱们能够很不便的检索一个单词是否呈现在原来的字符串汇合中。例如,咱们检索单词:hehe,那么咱们从根节点开始,而后一一单词进行比照,路径结点:h-e-h-e,就定位到要检索的字符串了。从中能够看出,Trie Tree查找效率是十分高的,达到了O(K),其中K是字符串的长度。 Radix树特点尽管Trie Tree具备比拟高的查问效率,然而从上图能够看到,有许多结点只有一个子结点。这种状况是不必要的,岂但影响了查问效率(减少了树的高度),次要是节约了存储空间。齐全能够将这些结点合并为一个结点,这就是Radix树的由来。Radix树将只有一个子节点的两头节点将被压缩,使之具备更加正当的内存应用和查问的效率。下图是采纳Radix树后的示例: 与hash表的比照查问效率Radix树的插入、查问、删除操作的工夫复杂度都为O(k)。hash表的安查问效率:hash函数计算效率+O(1),当然与若hash函数若选取不合理,则会造成抵触,会造成hash表的查问效率升高。 总总体上来说两者查问性能根本相等(hash函数的效率基本上等于O(k))。空间效率从空间应用来说,hash占优,因为Radix树的每个节点的节点负载较高,Radix树的结点须要有指向子结点的指针。其余方面hash表的毛病有:数据量增长的状况下可能须要rehash。Radix树不会存在rehash的状况,然而若数据数据密集,则会造成Radix树进化为Trie树,会升高空间应用效率以及查问效率。取舍倡议如果数据稠密,Radix树能够更好的节俭空间,这种状况下应用Radix树会更佳。如果数据量小但数据密集,则倡议抉择hash表。Radix树的Go语言版本本文应用Go语言来实现Radix树,目前代码曾经上传到github中,下载地址 在该代码中应用二叉树来实现Radix树,绝对与四叉树的版本,本版本在存储空间的使用率方面会更好一点,但会就义肯定的查问效率。若谋求查问效率,请应用多叉树来实现。 应用二叉树来解决字符串时,将字符串看作bit数组,而后一一bit来比照两这个bit数组,将bit数组后面雷同的局部保留到父结点,将两个bit数组的前面的局部别离保留到left子结点以及right子结点中。例如: god: 0110-0111 0110-1111 0110-0100goto: 0110-0111 0110-1111 0111-0100 0110-1111从上例能够看到,二者在地20位处不相等,因而: 0110-0111 0110-1111 011:被划分到父节点。0-0100:被划分到left子结点。1-0100 0110-1111:被划分到right子结点。定义首先给出Radix树的定义: type RaxNode struct { bit_len int bit_val []byte left *RaxNode right *RaxNode val interface{}}type Radix struct { root [256]*RaxNode}func NewRaxNode() *RaxNode { nd := &RaxNode{} nd.bit_len = 0 nd.bit_val = nil nd.left = nil nd.right = nil nd.val = nil return nd}将数据增加到Radix树//增加元素func (this *Radix)Set(key string, val interface{}) { data := []byte(key) root := this.root[data[0]] if root == nil { //没有根节点,则创立一个根节点 root = NewRaxNode() root.val = val root.bit_val = make([]byte, len(data)) copy(root.bit_val, data) root.bit_len = len(data) * 8 this.root[data[0]] = root return } else if root.val == nil && root.left == nil && root.right == nil { //只有一个根节点,并且是一个空的根节点,则间接赋值 root.val = val root.bit_val = make([]byte, len(data)) copy(root.bit_val, data) root.bit_len = len(data) * 8 this.root[data[0]] = root return } cur := root blen := len(data) * 8 for bpos := 0; bpos < blen && cur != nil; { bpos, cur = cur.pathSplit(data, bpos, val) }}func (this *RaxNode)pathSplit(key []byte, key_pos int, val interface{}) (int, *RaxNode) { //与path对应的key数据(去掉曾经解决的公共字节) data := key[key_pos/8:] //key以bit为单位长度(蕴含开始字节的字节内bit位的偏移量) bit_end_key := len(data) * 8 //path以bit为单位长度(蕴含开始字节的字节内bit位的偏移量) bit_end_path := key_pos % 8 + int(this.bit_len) //以后的bit偏移量,须要越过开始字节的字节内bit位的偏移量 bpos := key_pos % 8 for ; bpos < bit_end_key && bpos < bit_end_path; { ci := bpos / 8 byte_path := this.bit_val[ci] byte_data := data[ci] //起始字节的外部偏移量 beg := 0 if ci == 0 { beg = key_pos % 8 } //终止字节的外部偏移量 end := 8 if ci == bit_end_path / 8 { end = bit_end_path % 8 if end == 0 { end = 8 } } if beg != 0 || end != 8 { //不残缺字节的比拟,若不等则跳出循环 //若相等,则增长bpos,并持续比拟下一个字节 num := GetPrefixBitLength2(byte_data, byte_path, beg, end) bpos += num if num < end - beg { break } } else if byte_data != byte_path { //残缺字节比拟,num为雷同的bit的个数,bpos减少num后跳出循环 num := GetPrefixBitLength2(byte_data, byte_path, 0, 8) bpos += num break } else { //残缺字节比拟,相等,则持续比拟下一个字节 bpos += 8 } } //以后字节的地位 char_index := bpos / 8 //以后字节的bit偏移量 bit_offset := bpos % 8 //残余的path长度 bit_last_path := bit_end_path - bpos //残余的key长度 bit_last_data := bit_end_key - bpos //key的数据有残余 //若path有子结点,则持续解决子结点 //若path没有子结点,则创立一个key子结点 var nd_data *RaxNode = nil var bval_data byte if bit_last_data > 0 { //若path有子结点,则退出本函数,并在子结点中进行解决 byte_data := data[char_index] bval_data = GET_BIT(byte_data, bit_offset) if bit_last_path == 0 { if bval_data == 0 && this.left != nil { return key_pos + int(this.bit_len), this.left } else if bval_data == 1 && this.right != nil { return key_pos + int(this.bit_len), this.right } } //为残余的key创立子结点 nd_data = NewRaxNode() nd_data.left = nil nd_data.right = nil nd_data.val = val nd_data.bit_len = bit_last_data nd_data.bit_val = make([]byte, len(data[char_index:])) copy(nd_data.bit_val, data[char_index:]) //若bit_offset!=0,阐明不是残缺字节, //将字节决裂,并将字节中的非公共局部分离出来,保留到子结点中 if bit_offset != 0 { byte_tmp := CLEAR_BITS_LOW(byte_data, bit_offset) nd_data.bit_val[0] = byte_tmp } } //path的数据有残余 //创立子节点:nd_path结点 //并将数据离开,公共局部保留this结点,其余保留到nd_path结点 var nd_path *RaxNode = nil var bval_path byte if bit_last_path > 0 { byte_path := this.bit_val[char_index] bval_path = GET_BIT(byte_path, bit_offset) //为残余的path创立子结点 nd_path = NewRaxNode() nd_path.left = this.left nd_path.right = this.right nd_path.val = this.val nd_path.bit_len = bit_last_path nd_path.bit_val = make([]byte, len(this.bit_val[char_index:])) copy(nd_path.bit_val, this.bit_val[char_index:]) //将byte_path字节中的非公共局部分离出来,保留到子结点中 if bit_offset != 0 { byte_tmp := CLEAR_BITS_LOW(byte_path, bit_offset) nd_path.bit_val[0] = byte_tmp } //批改以后结点,作为nd_path结点、nd_data结点的父结点 //多申请一个子节,用于存储可能呈现的不残缺字节 bit_val_old := this.bit_val this.left = nil this.right = nil this.val = nil this.bit_len = this.bit_len - bit_last_path //=bpos - (key_pos % 8) this.bit_val = make([]byte, len(bit_val_old[0:char_index])+1) copy(this.bit_val, bit_val_old[0:char_index]) this.bit_val = this.bit_val[0:len(this.bit_val)-1] //将byte_path字节中的公共局部分离出来,保留到父结点 if bit_offset != 0 { byte_tmp := CLEAR_BITS_HIGH(byte_path, 8-bit_offset) this.bit_val = append(this.bit_val, byte_tmp) } } //若path蕴含key,则将val赋值给this结点 if bit_last_data == 0 { this.val = val } if nd_data != nil { if bval_data == 0 { this.left = nd_data } else { this.right = nd_data } } if nd_path != nil { if bval_path == 0 { this.left = nd_path } else { this.right = nd_path } } return len(key) * 8, nil}Radix树的查问func (this *Radix)Get(key string) interface{}{ data := []byte(key) blen := len(data) * 8 cur := this.root[data[0]] var iseq bool for bpos := 0; bpos < blen && cur != nil; { iseq, bpos = cur.pathCompare(data, bpos) if iseq == false { return nil } if bpos >= blen { return cur.val } byte_data := data[bpos/8] bit_pos := GET_BIT(byte_data, bpos%8) if bit_pos == 0 { cur = cur.left } else { cur = cur.right } } return nil}func (this *RaxNode)pathCompare(data []byte, bbeg int) (bool, int) { bend := bbeg + int(this.bit_len) if bend > len(data) * 8 { return false, len(data) * 8 } //起始和终止字节的地位 cbeg := bbeg / 8; cend := bend / 8 //起始和终止字节的偏移量 obeg := bbeg % 8; oend := bend % 8 for bb := bbeg; bb < bend; { //获取两个数组的以后字节地位 dci := bb / 8 nci := dci - cbeg //获取数据的以后字节以及循环步长 step := 8 byte_data := data[dci] if dci == cbeg && obeg > 0 { //清零不残缺字节的低位 byte_data = CLEAR_BITS_LOW(byte_data, obeg) step -= obeg } if dci == cend && oend > 0 { //清零不残缺字节的高位 byte_data = CLEAR_BITS_HIGH(byte_data, 8-oend) step -= 8-oend } //获取结点的以后字节,并与数据的以后字节比拟 byte_node := this.bit_val[nci] if byte_data != byte_node { return false, len(data)*8 } bb += step } return true, bend}Radix树的删除func (this *Radix)Delete(key string) { data := []byte(key) blen := len(data) * 8 cur := this.root[data[0]] var iseq bool var parent *RaxNode = nil for bpos := 0; bpos < blen && cur != nil; { iseq, bpos = cur.pathCompare(data, bpos) if iseq == false { return } if bpos >= blen { //将以后结点批改为空结点 //若parent是根节点,不能删除 cur.val = nil if parent == nil { return } //以后结点是叶子结点,先将以后结点删除,并将以后结点指向父结点 if cur.left == nil && cur.right == nil { if parent.left == cur { parent.left = nil } else if parent.right == cur { parent.right = nil } bpos -= int(cur.bit_len) cur = parent } //尝试将以后结点与以后结点的子节点进行合并 cur.pathMerge(bpos) return } byte_data := data[bpos/8] bit_pos := GET_BIT(byte_data, bpos%8) if bit_pos == 0 { parent = cur cur = cur.left } else { parent = cur cur = cur.right } }}func (this *RaxNode)pathMerge(bpos int) bool { //若以后结点存在值,则不能合并 if this.val != nil { return false } //若以后结点有2个子结点,则不能合并 if this.left != nil && this.right != nil { return false } //若以后结点没有子结点,则不能合并 if this.left != nil && this.right != nil { return false } //获取以后结点的子结点 child := this.left if this.right != nil { child = this.right } //判断以后结点最初一个字节是否是残缺的字节 //若不是残缺字节,须要与子结点的第一个字节进行合并 if bpos % 8 != 0 { char_len := len(this.bit_val) char_last := this.bit_val[char_len-1] char_0000 := child.bit_val[0] child.bit_val = child.bit_val[1:] this.bit_val[char_len-1] = char_last | char_0000 } //合并以后结点以及子结点 this.val = child.val this.bit_val = append(this.bit_val, child.bit_val...) this.bit_len += child.bit_len this.left = child.left this.right = child.right return true}

May 17, 2022 · 5 min · jiezi

关于golang:微服务从代码到k8s部署应有尽有大结局k8s部署

咱们用一个系列来解说从需要到上线、从代码到k8s部署、从日志到监控等各个方面的微服务残缺实际。 整个我的项目应用了go-zero开发的微服务,根本蕴含了go-zero以及相干go-zero作者开发的一些中间件,所用到的技术栈根本是go-zero项目组的自研组件,根本是go-zero全家桶了。 实战我的项目地址:https://github.com/Mikaelemmm... 1、概述上一节,咱们曾经把gitlab、jenkins、harbor、k8s都曾经搭建好了,这一节咱们来编写jenkins的pipline将咱们的服务通过jenkins残缺的公布到k8s中。 2、部署中间件将mysql、redis、es等部署到k8s之外 , 模仿用作线上独立环境(至于线上你想把某些中间件部署到k8s外部这个自行处理,本次重点是如何将go-zero开发的微服务部署到k8s集群外部),这里我就间接应用我的项目下的docker-compose-env.yaml了,把所有依赖的第三方中间件环境间接装置在srv-data.com(192.168.1.181)这台服务器,前提是这台服务器曾经装置好docker、docker-compose。 登陆到 192.168.1.181 $ mkdir data && cd data && vim docker-compose.yml$ docker-compose up -d$ docker-compose ps #查看确认3、独立配置将每个服务的配置都独立进去,对立放在一个git仓库,这样只给一个人线上仓库的权限,如果线上配置有变间接批改这个仓库的文件,在jenkins做cd的时候,会先拉取代码再拉取对应服务的配置主动构建,具体能够看前面的pipline。 【问】为什么不必配置核心? 1)批改db、redis等须要重启服务,然而有一些配置又不须要重启服务,运维又要去记,记混了比拟容易造成线上事变 2)不便回滚。咱们发新版本到线上,并且又改了新版本配置。这时候线上用户反馈有问题,线上须要疾速回滚的话,如果咱们应用将文件构建到镜像中,间接应用k8s一行命令就能够将上一个版本代码加配置间接回滚回来。如果应用了配置核心,回滚了代码,还要将上个版本的配置去配置核心改回来,很麻烦。 独立线上仓库目录构造如下(这个构造是跟pipline中写法相干的) 仓库地址 : https://github.com/Mikaelemmm... , 间接下载就好 1、批改配置中的中间件,数据库、redis等都要改成192.168.1.181这台机器,咱们把这台机器当成线上环境的中间件。 2、另外一个就是咱们的服务发现,线上咱们部署在k8s中,go-zero间接反对k8s服务发现,所以不须要etcd等,咱们在配置zrpc client的时候,要改成target,k8s的配置形式。 4、编写 jenkins 的 pipline4.1 配置参数拜访 http://192.168.1.180:8989/ 关上jenkins,进入jenkins首页,点击左侧菜单新建Item 咱们先创立 identity 受权服务的流水线 而后点击“General” , 抉择“This project is parameterized” , "增加参数",“Choice Parameter”,如下图 而后编写内容如下 间接保留。 4.2 编写pipline向下滑动找到Pipeline script,填写脚本内容 pipeline { agent any parameters { gitParameter name: 'branch', type: 'PT_BRANCH', branchFilter: 'origin/(.*)', defaultValue: 'master', selectedValue: 'DEFAULT', sortMode: 'ASCENDING_SMART', description: '抉择须要构建的分支' } stages { stage('服务信息') { steps { sh 'echo 分支:$branch' sh 'echo 构建服务类型:${JOB_NAME}-$type' } } stage('拉取代码') { steps { checkout([$class: 'GitSCM', branches: [[name: '$branch']], doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [], userRemoteConfigs: [[credentialsId: 'gitlab-cert', url: 'ssh://git@192.168.1.180:2222/root/go-zero-looklook.git']]]) } } stage('获取commit_id') { steps { echo '获取commit_id' git credentialsId: 'gitlab-cert', url: 'ssh://git@192.168.1.180:2222/root/go-zero-looklook.git' script { env.commit_id = sh(returnStdout: true, script: 'git rev-parse --short HEAD').trim() } } } stage('拉取配置文件') { steps { checkout([$class: 'GitSCM', branches: [[name: '$branch']], doGenerateSubmoduleConfigurations: false, extensions: [[$class: 'RelativeTargetDirectory', relativeTargetDir: 'conf']], submoduleCfg: [], userRemoteConfigs: [[credentialsId: 'gitlab-cert', url: 'ssh://git@192.168.1.180:2222/root/go-zero-looklook-pro-conf.git']]]) } } stage('goctl版本检测') { steps{ sh '/usr/local/bin/goctl -v' } } stage('Dockerfile Build') { steps{ sh 'yes | cp -rf conf/${JOB_NAME}/${type}/${JOB_NAME}.yaml app/${JOB_NAME}/cmd/${type}/etc' //线上配置文件 sh 'cd app/${JOB_NAME}/cmd/${type} && /usr/local/bin/goctl docker -go ${JOB_NAME}.go && ls -l' script{ env.image = sh(returnStdout: true, script: 'echo ${JOB_NAME}-${type}:${commit_id}').trim() } sh 'echo 镜像名称:${image} && cp app/${JOB_NAME}/cmd/${type}/Dockerfile ./ && ls -l && docker build -t ${image} .' } } stage('上传到镜像仓库') { steps{ //docker login 这里要留神,会把账号密码输入到jenkins页面,能够通过port.sh相似形式解决,官网文档有这里我就不具体写了 sh 'docker login --username=${docker_username} --password=${docker_pwd} http://${docker_repo}' sh 'docker tag ${image} ${docker_repo}/go-zero-looklook/${image}' sh 'docker push ${docker_repo}/go-zero-looklook/${image}' } } stage('部署到k8s') { steps{ script{ env.deployYaml = sh(returnStdout: true, script: 'echo ${JOB_NAME}-${type}-deploy.yaml').trim() env.port=sh(returnStdout: true, script: '/root/port.sh ${JOB_NAME}-${type}').trim() } sh 'echo ${port}' sh 'rm -f ${deployYaml}' sh '/usr/local/bin/goctl kube deploy -secret docker-login -replicas 2 -nodePort 3${port} -requestCpu 200 -requestMem 50 -limitCpu 300 -limitMem 100 -name ${JOB_NAME}-${type} -namespace go-zero-looklook -image ${docker_repo}/${image} -o ${deployYaml} -port ${port} --home /root/template' sh '/usr/local/bin/kubectl apply -f ${deployYaml}' } } stage('Clean') { steps{ sh 'docker rmi -f ${image}' sh 'docker rmi -f ${docker_repo}/${image}' cleanWs notFailBuild: true } } }}十分重要!!!构建优化:pipline中生成dockerfile的时候,咱们是应用k8s形式部署不须要etcd,然而这种形式部署须要指定账号(有去k8s的endpoints中get的权限,应用默认default就好了,每次创立一个新的命名空间k8s会主动帮咱们创立好一个default),然而应用goctl 生成的 k8s yml没有增加指定账号选项,这个曾经反馈了,可能后续版本会加上,这里咱们也用模版做了,同样模版是在我的项目目录下https://github.com/Mikaelemmm...,pipline中构建指定这个模版即可${credentialsId}要替换为你的具体凭据值,即【增加凭据】模块中的一串字符串,咱们之前配置的是gitlab-cert所以这里就填写gitlab-cert,如果你不是这个本人要更换,${gitUrl}须要替换为你代码的git仓库地址,其余的${xxx}模式的变量无需批改,放弃原样即可。这里跟官网文档有一点点不一样,因为我我的项目文件夹目录不同,goctl生成的dockerfile文件我手动做了点调整,在一个我不是在构建时候生成的dockerfile,是在创立我的项目时候就把dockerfile一起放在目录下,这样构建镜像时候不须要goctl了5、配置k8s拉取公有仓库镜像k8s在默认状况下,只能拉取harbor镜像仓库的私有镜像,如果拉取公有仓库镜像,则是会报 ErrImagePull 和 ImagePullBackOff 的谬误 ...

May 17, 2022 · 3 min · jiezi

关于golang:Golang将日志以Json格式输出到Kafka

在上一篇文章中我实现了一个反对Debug、Info、Error等多个级别的日志库,并将日志写到了磁盘文件中,代码比较简单,适宜练手。有趣味的能够通过这个链接返回:https://github.com/bosima/ylo... 工程实际中,咱们往往还须要对日志进行采集,将日志归集到一起,而后用于各种解决剖析,比方生产环境上的谬误剖析、异样告警等等。在日志音讯零碎畛域,Kafka久负盛名,这篇文章就以将日志发送到Kafka来实现日志的采集;同时思考到日志剖析时对结构化数据的需要,这篇文章还会提供一种输入Json格局日志的办法。 这个升级版的日志库还要放弃向前兼容,即还可能应用一般文本格式,以及写日志到磁盘文件,这两个个性和要新增的两个性能别离属于同类解决,因而我这里对它们进行形象,造成两个接口:格式化接口、写日志接口。 格式化接口所谓格式化,就是日志的格局解决。这个日志库目前要反对两种格局:一般文本和Json。 为了在不同格局之上提供一个对立的形象,ylog中定义 logEntry 来代表一条日志: type logEntry struct { Ts time.Time `json:"ts"` File string `json:"file"` Line int `json:"line"` Level LogLevel `json:"level"` Msg string `json:"msg"`}格式化接口的能力就是将日志从logEntry格局转化为其它某种数据格式。ylog中对它的定义是: type LoggerFormatter interface { Format(*logEntry, *[]byte) error}第1个参数是一个logEntry实例,也就是要被格式化的日志,第2个参数是日志格式化之后要写入的容器。 一般文本格式化器其实现是这样的: type textFormatter struct {}func NewTextFormatter() *textFormatter { return &textFormatter{}}func (f *textFormatter) Format(entry *logEntry, buf *[]byte) error { formatTime(buf, entry.Ts) *buf = append(*buf, ' ') file := toShort(entry.File) *buf = append(*buf, file...) *buf = append(*buf, ':') itoa(buf, entry.Line, -1) *buf = append(*buf, ' ') *buf = append(*buf, levelNames[entry.Level]...) *buf = append(*buf, ' ') *buf = append(*buf, entry.Msg...) return nil}能够看到它的次要性能就是将logEntry中的各个字段依照某种程序平铺开来,两头用空格分隔。 ...

May 17, 2022 · 3 min · jiezi

关于golang:Go语言将引入新型排序算法pdqsort

原文链接:Go语言将引入新型排序算法:pdqsort 哈喽,大家好,我是asong。最近在逛Go仓库时看到了一个commit是对于排序算法的,即pdqsort排序算法,Go打算将在一个版本中反对该排序算法,上面咱们就具体来看一看这个事件; commit地址:https://github.com/golang/go/... <img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/42c63502e8f44a87a904ad340e934d63~tplv-k3u1fbpfcp-zoom-1.image" alt="截屏2022-05-06 下午1.37.10" style="zoom:67%;" /> 该commit中介绍了pqdsort的测试后果: 在所有基准测试中,pdqsort未体现出比以前的其它算法慢常见模式中pdqsort通常更快(即在排序切片中快10倍)pdqsort本质为一种混合排序算法,在不同状况下切换到不同的排序机制,该实现灵感来自C++和RUST的实现,是对C++规范库算法introsort的一种改良,其现实状况下的工夫复杂度为 O(n),最坏状况下的工夫复杂度为 O(n* logn),不须要额定的空间。 pdqsort算法的改良在于对常见的状况做了非凡优化,其次要的思维是一直断定目前的序列状况,而后应用不同的形式和门路达到最优解;如果大家想看一下该算法的具体实现,能够查看https://github.com/zhangyunhao116/pdqsort中的实际,其实现就是对上面三种状况的一直循环: 短序列状况:对于长度在 [0, MAX_INSERTION] 的输出,应用 insertion sort (插入排序)来进行排序后间接返回,这里的 MAX_INSERTION 咱们在 Go 语言下的性能测试,选定为 24。最坏状况,如果发现改良的 quicksort 成果不佳(limit == 0),则后续排序都应用 heap sort 来保障最坏状况工夫复杂度为 O(n*logn)。失常状况,对于其余输出,应用改良的 quicksort 来排序具体的源代码实现能够自行查看,本文就不过多剖析了,上面咱们来看一下pdqsort的demo: import ( "fmt" "github.com/zhangyunhao116/pdqsort")func main() { x := []int{3, 1, 2, 4, 5, 9, 8, 7} pdqsort.Slice(x) fmt.Printf("sort_result = %v\n", x) search_result := pdqsort.Search(x, 4) fmt.Printf("search_result = %v\n", search_result) is_sort := pdqsort.SliceIsSorted(x) fmt.Printf("is_sort = %v\n", is_sort)}运行后果: ...

May 16, 2022 · 1 min · jiezi

关于golang:Go语言如何在测试发现goroutine泄漏

原文链接:Go语言如何在测试发现goroutine透露 前言哈喽,大家好,我是asong; 家喻户晓,gorourtine的设计是Go语言并发实现的外围组成部分,易上手,然而也会遭逢各种疑难杂症,其中goroutine透露就是重症之一,其呈现往往须要排查很久,有人说能够应用pprof来排查,尽管其能够达到目标,然而这些性能剖析工具往往是在呈现问题后借助其辅助排查应用的,有没有一款能够防患于未然的工具吗?当然有,goleak他来了,其由 Uber 团队开源,能够用来检测goroutine透露,并且能够联合单元测试,能够达到防备于未然的目标,本文咱们就一起来看一看goleak。 goroutine透露不晓得你们在日常开发中是否有遇到过goroutine透露,goroutine透露其实就是goroutine阻塞,这些阻塞的goroutine会始终存活直到过程终结,他们占用的栈内存始终无奈开释,从而导致系统的可用内存会越来越少,直至解体!简略总结了几种常见的透露起因: Goroutine内的逻辑进入死循坏,始终占用资源Goroutine配合channel/mutex应用时,因为使用不当导致始终被阻塞Goroutine内的逻辑长时间期待,导致Goroutine数量暴增接下来咱们应用Goroutine+channel的经典组合来展现goroutine透露; func GetData() { var ch chan struct{} go func() { <- ch }()}func main() { defer func() { fmt.Println("goroutines: ", runtime.NumGoroutine()) }() GetData() time.Sleep(2 * time.Second)}这个例子是channel遗记初始化,无论是读写操作都会造成阻塞,这个办法如果是写单测也是查看不进去问题的: func TestGetData(t *testing.T) { GetData()}运行后果: === RUN TestGetData--- PASS: TestGetData (0.00s)PASS内置测试无奈满足,接下来咱们引入goleak来测试一下。 goleakgithub地址:https://github.com/uber-go/go... 应用goleak次要关注两个办法即可:VerifyNone、VerifyTestMain,VerifyNone用于繁多测试用例中测试,VerifyTestMain能够在TestMain中增加,能够缩小对测试代码的入侵,举例如下: 应用VerifyNone: func TestGetDataWithGoleak(t *testing.T) { defer goleak.VerifyNone(t) GetData()}运行后果: === RUN TestGetDataWithGoleak leaks.go:78: found unexpected goroutines: [Goroutine 35 in state chan receive (nil chan), with asong.cloud/Golang_Dream/code_demo/goroutine_oos_detector.GetData.func1 on top of the stack: goroutine 35 [chan receive (nil chan)]: asong.cloud/Golang_Dream/code_demo/goroutine_oos_detector.GetData.func1() /Users/go/src/asong.cloud/Golang_Dream/code_demo/goroutine_oos_detector/main.go:12 +0x1f created by asong.cloud/Golang_Dream/code_demo/goroutine_oos_detector.GetData /Users/go/src/asong.cloud/Golang_Dream/code_demo/goroutine_oos_detector/main.go:11 +0x3c ]--- FAIL: TestGetDataWithGoleak (0.45s)FAILProcess finished with the exit code 1通过运行后果看到具体产生goroutine透露的具体代码段;应用VerifyNone会对咱们的测试代码有入侵,能够采纳VerifyTestMain办法能够更快的集成到测试中: ...

May 16, 2022 · 2 min · jiezi

关于golang:个人经验分享如何阅读Go语言源码

原文链接:分享如何浏览Go语言源码 前言哈喽,大家好,我是asong;最近在看Go语言调度器相干的源码,发现看源码真是个技术活,所以本文就简略总结一下该如何查看Go源码,心愿对你们有帮忙。Go源码包含哪些?以我集体了解,Go源码次要分为两局部,一部分是官网提供的规范库,一部分是Go语言的底层实现,Go语言的所有源码/规范库/编译器都在src目录下:https://github.com/golang/go/...,想看什么库的源码任君抉择; 观看Go规范库 and Go底层实现的源代码难易度也是不一样的,咱们个别也能够先从规范库动手,筛选你感兴趣的模块,把它吃透,有了这个根底后,咱们在看Go语言底层实现的源代码会略微轻松一些;上面就针对我集体的一点学习心得分享一下如何查看Go源码; 查看规范库源代码规范库的源代码看起来稍容易些,因为规范库也属于下层利用,咱们能够借助IDE的帮忙,其在IDE上就能够跳转到源代码包,咱们只须要一直来回跳转查看各个函数实现做好笔记即可,因为一些源代码设计的比较复杂,大家在看时最好通过画图辅助一下,集体感觉画UML是最有助于了解的,能更清晰的理清各个实体的关系; 有些时候只看代码是很难了解的,这时咱们应用在线调试辅助咱们了解,应用IDE提供的调试器或者GDB都能够达到目标,写一个简略的demo,断点一打,单步调试走起来,比方你要查看fmt.Println的源代码,开局一个小红点,而后就是点点点; <img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/020ee5d660264362b42dab2a142bc369~tplv-k3u1fbpfcp-zoom-1.image" /> 查看Go语言底层实现人都是会对未知领域充斥好奇,当应用一段时间Go语言后,就想更深刻的搞明确一些事件,例如:Go程序的启动过程是怎么的,goroutine是怎么调度的,map是怎么实现的等等一些Go底层的实现,这种间接依附IDE跳转追溯代码是办不到的,这些都属于Go语言的外部实现,大都在src目录下的runtime包内实现,其实现了垃圾回收,并发管制, 栈治理以及其余一些 Go 语言的要害个性,在编译Go代码为机器代码时也会将其也编译进来,runtime就是Go程序执行时候应用的库,所以一些Go底层原理都在这个包内,咱们须要借助一些形式能力查看到Go程序执行时的代码,这里分享两种形式:剖析汇编代码、dlv调试; <img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/43155623d60042d98539d4885bb73020~tplv-k3u1fbpfcp-zoom-1.image" alt="" /> 剖析汇编代码后面咱们曾经介绍了Go语言实现了runtime库,咱们想看到一些Go语言关键字个性对应runtime里的那个函数,能够查看汇编代码,Go语言的汇编应用的plan9,与x86汇编差异还是很大,很多敌人都不相熟plan9的汇编,然而要想看懂Go源码还是要对plan9汇编有一个根本的理解的,这里举荐曹大的文章:plan9 assembly 齐全解析,会一点汇编咱们就可以看源代码了,比方想在咱们想看make是怎么初始化slice的,这时咱们能够先写一个简略的demo: // main.goimport "fmt"func main() { s := make([]int, 10, 20) fmt.Println(s)}有两种形式能够查看汇编代码: 1. go tool compile -S -N -l main.go2. go build main.go && go tool objdump ./main形式一是将源代码编译成.o文件,并输入汇编代码,形式二是反汇编,这里举荐应用形式一,执行形式一命令后,咱们能够看到对应的汇编代码如下: <img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/6071b1f1459144f5b891c3c4d60559df~tplv-k3u1fbpfcp-zoom-1.image" /> s := make([]int, 10, 20)对应的源代码就是 runtime.makeslice(SB),这时候咱们就去runtime包下找makeslice函数,一直追踪上来就可查看源码实现了,可在runtime/slice.go中找到: <img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d9905faa0ef2479b997279691ddba98f~tplv-k3u1fbpfcp-zoom-1.image" /> 在线调试尽管下面的办法能够帮忙咱们定位到源代码,然而后续的操作全靠review还是难于了解的,如果能在线调试跟踪代码能够更好助于咱们了解,目前Go语言反对GDB、LLDB、Delve调试器,但只有Delve是专门为Go语言设计开发的调试工具,所以应用Delve能够轻松调试Go汇编程序,Delve的入门文章有很多,这篇就不在介绍Delve的具体应用办法,入门大家能够看曹大的文章:https://chai2010.cn/advanced-...,本文就应用一个小例子带大家来看一看dlv如何调试Go源码,大家都晓得向一个nil的切片追加元素,不会有任何问题,在源码中是怎么实现的呢?接下老咱们应用dlv调试跟踪一下,先写一个小demo: import "fmt"func main() { var s []int s = append(s, 1) fmt.Println(s)}进入命令行包目录,而后输出dlv debug进入调试 ...

May 16, 2022 · 4 min · jiezi

关于golang:Go-语言接口几个例子

Go 语言接口Go 语言提供了另外一种数据类型即接口,它把所有的具备共性的办法定义在一起,任何其余类型只有实现了这些办法就是实现了这个接口。 实例/* 定义接口 */type interface_name interface { method_name1 [return_type] method_name2 [return_type] method_name3 [return_type] ... method_namen [return_type]}/* 定义构造体 */type struct_name struct { /* variables */}/* 实现接口办法 */func (struct_name_variable struct_name) method_name1() [return_type] { /* 办法实现 */}...func (struct_name_variable struct_name) method_namen() [return_type] { /* 办法实现*/}实例package mainimport ( "fmt")type Phone interface { call()}type NokiaPhone struct {}func (nokiaPhone NokiaPhone) call() { fmt.Println("I am Nokia, I can call you!")}type IPhone struct {}func (iPhone IPhone) call() { fmt.Println("I am iPhone, I can call you!")}func main() { var phone Phone phone = new(NokiaPhone) phone.call() phone = new(IPhone) phone.call()}在下面的例子中,咱们定义了一个接口Phone,接口外面有一个办法call()。而后咱们在main函数外面定义了一个Phone类型变量,并别离为之赋值为NokiaPhone和IPhone。而后调用call()办法,输入后果如下: ...

May 16, 2022 · 1 min · jiezi

关于golang:Golang手撸一个支持六种级别的日志库

Golang规范日志库提供的日志输入办法有Print、Fatal、Panic等,没有常见的Debug、Info、Error等日志级别,用起来不太棘手。这篇文章就来手撸一个本人的日志库,能够记录不同级别的日志。 其实对于谋求简略来说,Golang规范日志库的三个输入办法也够用了,了解起来也很容易: Print用于记录一个一般的程序日志,开发者想记点什么都能够。Fatal用于记录一个导致程序解体的日志,并会退出程序。Panic用于记录一个异样日志,并触发panic。不过对于用惯了Debug、Info、Error的人来说,还是有点不习惯;对于想更粗疏的辨别日志级别的需要,规范日志库还提供了一个通用的Output办法,开发者在要输入的字符串中退出级别也是能够的,但总是有点顺当,不够间接。 目前市面上也曾经有很多优良的三方日志库,比方uber开源的zap,常见的还有zerolog、logrus等。不过我这里还是想本人手撸一个,因为大多数开源产品都不会齐全贴合本人的需要,有很多本人用不上的性能,这会减少零碎的复杂性,有没有暗藏的坑也很难说,当然本人入坑的可能性也很大;再者看了官网日志库的实现之后,感觉能够简略封装下即可实现本人想要的性能,可能hold住。 初始需要我这里的初始需要是: 将日志写入磁盘文件,每个月一个文件夹,每个小时一个文件。反对常见日志级别:Trace、Debug、Info、Warn、Error、Fatal,并且程序可能设置日志级别。我给这个日志库取名为ylog,预期的应用办法如下: ylog.SetLevel(LevelInfo)ylog.Debug("I am a debug log.")ylog.Info("I am a Info log.")技术实现类型定义须要定义一个构造体,保留日志级别、要写入的文件等信息。 type FileLogger struct { lastHour int64 file *os.File Level LogLevel mu sync.Mutex iLogger *log.Logger Path string}来看一下这几个参数: lastHour 用来记录创立日志文件时的小时数,如果小时变了,就要创立新的日志文件。 file 以后应用的日志文件。 Level 以后应用的日志级别。 mu 因为可能在不同的go routine中写日志,须要一个互斥体保障日志文件不会反复创立。 iLogger 规范日志库实例,因为这里是封装了规范日志库。 Path 日志输入的最上层目录,比方程序根目录下的logs目录,这里就保留一个字符串:logs。 日志级别先把日志级别定义进去,这里日志级别其实是int类型,从0到5,级别一直升高。 如果设置为ToInfo,则Info级别及比Info级别高的日志都能输入。 type LogLevel intconst ( LevelTrace LogLevel = iota LevelDebug LevelInfo LevelWarn LevelError LevelFatal)上文提到能够在Output办法的参数中退出日志级别,这里就通过封装Output办法来实现不同级别的日志记录办法。这里贴出其中一个办法,封装的形式都一样,就不全都贴出来了: func (l *FileLogger) CanInfo() bool { return l.Level <= LevelInfo }func (l *FileLogger) Info(v ...any) { if l.CanInfo() { l.ensureFile() v = append([]any{"Info "}, v...) l.iLogger.Output(2, fmt.Sprintln(v...)) }}输入日志前做了三件事: ...

May 16, 2022 · 2 min · jiezi

关于golang:聊聊golang的Pseudoversions

序本文次要钻研一下golang的Pseudo-versions Pseudo-versions定义Pseudo-versions,中文大略是伪版本的意思,就是没有打语义版本tag(semantic version tags)的会应用伪版本 格局相似v0.0.0-yyyymmddhhmmss-abcdefabcdef,两头的工夫为UTC工夫(东八区为utc+8),最初的12位为git commit的hash的前12位 formsvX.0.0-yyyymmddhhmmss-abcdefabcdef 如果之前都没有major的语义版本tag则其Pseudo version第一部分为vX.0.0vX.Y.Z-pre.0.yyyymmddhhmmss-abcdefabcdef 在vX.Y.Z-pre(v3.9.0-pre)版本之后提交的commit,其Pseudo version第一部分为vX.Y.Z-pre.0(v3.9.0-pre.0)vX.Y.(Z+1)-0.yyyymmddhhmmss-abcdefabcdef 在vX.Y.Z(v3.9.0)版本之后提交的commit,其Pseudo version第一部分为vX.Y.(Z+1)-0(v3.9.1-0)+incompatible对于有些依赖没有go.mod的,go.sum会呈现+incompatible,比方 github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=问题基于分支commit的版本在改commit被删除之后会导致go mod invalid version 比方从个性分支合并到骨干的时候采纳git merge --squash且同时删除个性分支的形式会造成依赖之前依赖个性分支的commit失落,最初导致依赖这个commit的工程无奈build基于tag的版本在tag被删除的时候,也会呈现go mod invalid version 其余语言诸如java的maven,由仓库治理,除非非凡状况,个别不会去仓库删除版本,个别不会有误操作。go的这点也要特地留神,在删除tag的时候要小心。小结go的Pseudo-versions有点相似maven的snapshot的概念,都是基于工夫戳的形式,不过go的仓库是基于git仓库的,所以带上了commit的hash信息。然而要特地留神go mod invalid version的问题。 docPseudo-versionsWhere pseudo version with non-existent tagWhy go module pseudo version have a specific version?Go Big With Pseudo-Versions and GoCenter

May 16, 2022 · 1 min · jiezi

关于golang:Go-语言第一课基础篇2

构造体咱们编写程序的目标就是与真实世界交互,解决真实世界的问题,帮忙真实世界进步运行效率与改善运行品质。所以咱们就须要对真实世界事物体的重要属性进行提炼,并映射到程序世界中,这就是所谓的对真实世界的形象。 实质上雷同的两个类型,它们的变量能够通过显式转型进行互相赋值,相同,如果实质上是不同的两个类型,它们的变量间连显式转型都不可能,更不要说互相赋值了. 咱们还能够用空标识符“_”作为构造体类型定义中的字段名称。这样以空标识符为名称的字段,不能被内部包援用,甚至无奈被构造体所在的包应用。 第一种:定义一个空构造体。咱们能够定义一个空构造体,也就是没有蕴含任何字段的构造体类型. type Empty struct{} // Empty是一个不蕴含任何字段的空构造体类型空构造体类型变量的内存占用为 0。基于空构造体类型内存零开销这样的个性,咱们在日常 Go 开发中会常常应用空构造体类型元素,作为一种“事件”信息进行 Goroutine 之间的通信。 var c = make(chan Empty) // 申明一个元素类型为Empty的channelc<-Empty{} // 向channel写入一个“事件”第二种状况:应用其余构造体作为自定义构造体中字段的类型这种形式定义的构造体字段,咱们叫做嵌入字段(Embedded Field)。咱们也能够将这种字段称为匿名字段,或者把类型名看作是这个字段的名字. 构造体变量的申明与初始化:1.零值初始化说的是应用构造体的零值作为它的初始值.“零值”这个术语重复呈现过屡次,它指的是一个类型的默认值。对于 Go 原生类型来说,这个默认值也称为零值。Go 构造体类型由若干个字段组成,当这个构造体类型变量的各个字段的值都是零值时,咱们就说这个构造体类型变量处于零值状态。 在 Go 语言规范库和运行时的代码中,有很多践行“零值可用”理念的好例子,最典型的莫过于 sync 包的 Mutex 类型了。 2.对构造体变量进行显式初始化的形式,就是按程序顺次给每个构造体字段进行赋值 type Book struct {Title string // 书名Pages int // 书的页数Indexes map[string]int // 书的索引} vt := T{}像这类通过专用构造函数进行构造体类型变量创立、初始化的例子还有很多,咱们能够总结一下,它们的专用构造函数大多都合乎这种模式。 func NewT(field1, field2, ...) *T {... ...}NewT 是构造体类型 T 的专用构造函数,它的参数列表中的参数通常与 T 定义中的导出字段绝对应,返回值则是一个 T 指针类型的变量。 T 的非导出字段在 NewT 外部进行初始化,一些须要简单初始化逻辑的字段也会在 NewT 外部实现初始化。 这样,咱们只有调用 NewT 函数就能够失去一个可用的 T 指针类型变量了。 ...

May 15, 2022 · 3 min · jiezi

关于golang:联想一员工侵占公司工时费近千万Go-可以浏览器上运行啦Fedora-36-发布-思否周刊

40s 新闻速递苹果打算往年推出 Apple Pay「先买后付」服务华为追踪器曝光,命名为 HUAWEI TagWhatsApp 前高管:当年把公司卖给 Facebook,当初就是非常悔恨北京衰弱宝遭网络攻击细节披露:典型 DDoS 攻打,发动团伙曝光Go 能够浏览器上运行啦苹果发表停产 iPod touch,售完即止联想一员工内外勾结,强占公司工时费近 1000 万Twitter 市值跌破马斯克收购价:投资者担心交易难实现Fedora 36 公布PHP 8.1.6 公布Win10 版本 20H2 进行反对IntelliJ IDEA 2022.1.1 公布.NET 7 Preview 4 公布行业资讯苹果打算往年推出 Apple Pay「先买后付」服务据彭博社记者 Mark Gurman 报道,苹果正在开发两项与金融相干的服务,别离为硬件订阅打算和 Apple Pay 交易的「先买后付」服务。 华为追踪器曝光,命名为 HUAWEI Tag长期以来,电子市场内的蓝牙追踪器层出不穷,例如 Tile 的 Tile Pro 和 Musegear Finder 2、三星 Galaxy SmartTag 和 Galaxy SmartTag+ 、苹果 AirTag 等等。 当初有网友发现了华为也将推出相似产品,目前 “HUAWEI Tag” 曾经呈现在了云空间 App 相干权限设置界面的介绍中。据悉,HUAWEI Tag”除了惯例的蓝牙外仿佛还反对超声短距准确查找,目前尚不分明华为将在何时推出这一产品。 WhatsApp 前高管:当年把公司卖给 Facebook,当初就是非常悔恨据报道,WhatsApp 前首席财务官示意,对于当年将公司发售给 Facebook 这件事感到十分悔恨。 ...

May 15, 2022 · 2 min · jiezi

关于golang:10-条-Go-官方谚语你知道几条

大家好,我是煎鱼。 作为一个 Go 语言开发工程师,总是在入门、深刻、撕提案时可能听到各种带有 Go 特色的话语,例如:少即是多(less is more),十分乏味。 明天带来了 Go 语言之父 Rob Pike 在 2015 年分享的主题《Go Proverbs》,他在该主题中讲了的 10+ 条 Go 谚语,心愿大家能够用上。 核心理念是:简略、诗意、简洁(Simple, Poetic, Pithy)。 谚语不要通过共享内存来通信,通过通信来共享内存(Don't communicate by sharing memory, share memory by communicating)。并发不是并行(Concurrency is not parallelism)。通道是协调的,互斥是串行的(Channels orchestrate; mutexes serialize)。接口越大,抽象性越弱(The bigger the interface, the weaker the abstraction)。让零值变得有用(Make the zero value useful)。interface{} 什么也没说(interface{} says nothing)。Gofmt 的格调没有人喜爱,但 Gofmt 却是大家的最爱(Gofmt's style is no one's favorite, yet gofmt is everyone's favorite)。复制一点总比依赖一点好(A little copying is better than a little dependency)。Syscall 必须始终用 build 标签来爱护(Syscall must always be guarded with build tags)。Cgo 必须始终用构建标签来爱护(Cgo must always be guarded with build tags)。Cgo 不是 Go(Cgo is not Go)。应用 unsafe 包没有任何保障(With the unsafe package there are no guarantees)。清晰的比聪慧的好(Clear is better than clever)。反射从来不是清晰的(Reflection is never clear)。谬误就是价值(Errors are values)。不要只是查看谬误,要优雅地解决它们(Don't just check errors, handle them gracefully)。设计架构,命名组件,记录细节(Design the architecture, name the components, document the details)。文档是为用户筹备的(Documentation is for users)。不要应用恐慌(Don't panic)。总结Rob Pike 在演讲中所波及到的这 18 条谚语,是他长年在计算机软件畛域开发和设计 Go 时所积攒下来的教训。 ...

May 15, 2022 · 1 min · jiezi

关于golang:这些常见的-Go-编码错误你犯过吗一

大家好,我是煎鱼。 在用 Go 编程时,总会遇到各种奇奇怪怪的谬误,国内外曾经有许多小伙伴总结过(参考链接见参考),感觉都能凑一桌了。 之前始终想写,想着五一假期肝一肝。明天给大家分享 Go 里常见的编码谬误(一),心愿对大家有所帮忙。 Go 常见谬误1. nil Map问题在程序中申明(定义)了一个 map,而后间接写入数据。如下代码: func main() { var m map[string]string m["煎鱼"] = "进脑子了"}输入后果: panic: assignment to entry in nil map会间接抛出一个 panic。 解决办法解决办法其实就是要申明并初始化,Go 里规范写法是调用 make 函数就能够了。如下代码: func main() { m := make(map[string]string) m["煎鱼"] = "上班了"}这个问题在初学 Go 时是最容易踩到的谬误。 2. 空指针的援用问题咱们在 Go 常常会利用构造体去申明一系列的办法,他看起来向面向对象中的 ”类“,在业务代码中十分常见。 如下代码: type Point struct { X, Y float64}func (p *Point) Abs() float64 { return math.Sqrt(p.X*p.X + p.Y*p.Y)}func main() { var p *Point fmt.Println(p.Abs())}这段程序可能失常运行吗?失常计算和输入? ...

May 15, 2022 · 2 min · jiezi

关于golang:Go-语言第一课基础篇

---Tony Bai · Go语言第一课 在编程语言中,为了不便操作内存特定地位的数据,咱们用一个特定的名字与位于特定位置的内存块绑定在一起,这个名字被称为变量。变量所绑定的内存区域是要有一个明确的边界的。 Go 是动态语言,所有变量在应用前必须先进行申明。申明的意义在于通知编译器该变量能够操作的内存的边界信息,而这种边界通常又是由变量的类型信息提供的。 变量分类:Go 语言的变量能够分为两类: 一类称为包级变量 (package varible),也就是在包级别可见的变量。如果是导出变量(大写字母结尾),那么这个包级变量也能够被视为全局变量;(包级变量只能应用带有 var 关键字的变量申明模式,不能应用短变量申明模式.)另一类则是局部变量 (local varible),也就是 Go 函数或办法体内申明的变量,仅在函数或办法体内可见。代码块与作用域Go 语言中的代码块是包裹在一对大括号外部的申明和语句序列,如果一对大括号外部没有任何申明或其余语句,咱们就把它叫做空代码块。 一个标识符的作用域就是指这个标识符在被申明后能够被无效应用的源码区域. 依照 Go 语言定义,一个标识符要成为导出标识符需同时具备两个条件:一是这个标识符申明在包代码块中,或者它是一个字段名或办法名;二是它名字第一个字符是一个大写的Unicode 字符。 变量遮蔽变量遮蔽问题的根本原因,就是内层代码块中申明了一个与外层代码块同名且同类型的变量,这样,内层代码块中的同名变量就会代替那个外层变量,参加此层代码块内的相干计算,咱们也就说内层变量遮蔽了外层同名变量。 根本数据类型Go 语言的类型大体可分为根本数据类型、复合数据类型和接口类型这三种. 平台相干整型,它们的长度会依据运行平台的扭转而扭转。Go语言原生提供了三个平台相干整型,它们是 int、uint 与 uintptr 因为这三个类型的长度是平台相干的,所以咱们在编写有移植性要求的代码时,千万不要强依赖这些类型的长度. 整型的溢出问题如果某个整型因为参加某个运算,导致后果超出了这个整型的值边界,咱们就说产生了整型溢出的问题。学习整型时你要特地留神,每个整型都有本人的取值范畴和示意边界,一旦超出边界,便会呈现溢出问题。 一个浮点数被分为符号位、阶码与尾数三个局部。浮点数在内存中的二进制示意分三个局部:符号位、阶码(即通过换算的指数),以及尾数。这样示意的一个浮点数,它的值等于:咱们首先来看单精度(float32)与双精度(float64)浮点数在阶码和尾数上的不同 咱们来看看如何将一个十进制模式的浮点值 139.8125,转换为 IEEE 754 规定中的那种单精度二进制示意。步骤一:咱们要把这个浮点数值的整数局部和小数 局部,别离转换为二进制模式(后缀 d示意十进制数,后缀 b 示意二进制数):这样,原浮点值 139.8125d 进行二进制转换后,就变成 10001011.1101b步骤二:挪动小数点,直到整数局部仅有一个 1,也就是 10001011.1101b =>1.00010111101b。咱们看到,为了整数局部仅保留一个 1,小数点向左移了 7 位,这样指数就为 7,尾数为 00010111101b步骤三:计算阶码IEEE754 规定不能将小数点挪动而失去的指数,始终填到阶码局部,指数到阶码还须要一个转换过程。对于 float32 的单精度浮点数而言,阶码 = 指数 + 偏移值。偏移值的计算公式为 2^(e-1)-1,其中 e 为阶码局部的 bit 位数,这里为 8,于是单精度浮点数的阶码偏移值就为 2^(8-1)-1 = 127。这样在这个例子中,阶码 = 7 + 127 = 134d = 10000110bfloat64 的双精度浮点数的阶码计算也是这样的。步骤四:将符号位、阶码和尾数填到各自地位,失去最终浮点数的二进制示意。尾数位数有余 23 位,可在前面补 0这样,最终浮点数 139.8125d 的二进制示意就为0b_0_10000110_00010111101_000000000000但日常应用中咱们尽量应用 float64,这样不容易呈现浮点溢出的问题。math.MaxFloat32示意float32能示意的最大数值,大概是 3.4e38;对应的math.MaxFloat64常量大概是1.8e308 ...

May 15, 2022 · 2 min · jiezi

关于golang:微服务从代码到k8s部署应有尽有系列十四部署环境搭建

1、概述我的项目开发好后,咱们须要部署,咱们接下来就基于gitlab + jenkins + harbor + k8s 搭建部署环境 gitlab: 放代码,能够做cijenkins: 做cd公布我的项目harbor: 镜像仓库k8s: 运行服务咱们只在k8s外部运行服务,至于中间件(mysql、redis、es等)就会部署在k8s之外,如果你是线上应用云服务能够间接应用云服务,如果自建也最好运行在k8s之外。因为我是在本地演示,这里的中间件我就应用之前开发环境的中间件了,不要纠结这个,次要演示如何部署go-zero服务到k8s中 k8s部署这里就不介绍了,如果没有k8s环境本人用rancher或者kubeadm等搭建即可,再不行就去买个按时付费的云服务k8s 所以咱们须要配置如下: 服务器名称作用Ipdeploy-server.com部署gitlab、jenkins、harbor(事后装好docker、docker-compose)192.168.1.180srv-data.com部署mysql、redis、es等等,模仿独立环境,k8s外部连贯到此服务器192.168.1.181nginx-gateway.com网关,独立于k8s集群内部192.168.1.182k8s集群K8s 集群192.168.1.1832、gitlab2.1 部署gitlab创立文件夹 $ mkdir gitlab && cd gitlab$ vim docker-compose.ymldocker-compose.yml version: '3'services: gitlab: image: 'twang2218/gitlab-ce-zh' container_name: 'gitlab' restart: always hostname: '192.168.1.180' #部署机器的ip,非容器ip(因为是本地不是线上所以用ip,线上的话能够用域名) environment: TZ: 'Asia/Shanghai' GITLAB_OMNIBUS_CONFIG: | external_url 'http://192.168.1.180' #应用这个地址拜访gitlab web ui(因为是本地不是线上所以用ip,线上的话能够用域名) gitlab_rails['gitlab_shell_ssh_port'] = 2222 #ssh clone代码地址 unicorn['port'] = 8888 #gitlab一个外部端口 ports: - '80:80' #web 80 端口 #- '443:443' #web 443 端口,本次未应用就不凋谢了 - '2222:22' #ssh 检出代码 端口 volumes: - ./etc:/etc/gitlab #Gitlab配置文件目录 - ./data:/var/opt/gitlab #Gitlab数据目录 - ./logs:/var/log/gitlab #Gitlab日志目录执行 ...

May 14, 2022 · 2 min · jiezi

关于golang:Go-113版本引入的bug你遇到过这个坑么

Bug大家看看上面这段程序,思考下输入后果应该是什么? func main() { n := 0 f := func() func(int, int) { n = 1 return func(int, int) {} } g := func() (int, int) { println(n) return 0, 0 } f()(g())}先思考几秒钟。。。 在Go 1.13版本之前,上述程序打印的后果是 1在Go 1.13版本开始,上述程序打印的后果是 0从习惯认知来看,编译器应该依照如下程序执行 step 1: evaluate f(),此时变量n的值为1step 2: evaluate g(),此时打印n时,因为step 1曾经把n批改为1了,所以应该打印1step3: 依据step 1和step 2的后果,执行最终的函数调用Go编译器其实也是遵循这个设计的,官网文档的形容如下: At package level, initialization dependencies determine the evaluation order of individual initialization expressions in variable declarations. Otherwise, when evaluating the operands of an expression, assignment, or return statement, all function calls, method calls, and communication operations are evaluated in lexical left-to-right order. ...

May 14, 2022 · 2 min · jiezi

关于golang:ElasticSearch-查询使用

1. 基本概念1.1 index(索引)在Elasticsearch中索引(index)相似mysql的表,代表文档数据的汇合,文档指的是ES中存储的一条数据。 1.2 Document(文档)Elasticsearch是面向文档的数据库,文档是最根本的存储单元,文档相似mysql表中的一行数据。简略的说在ES中,文档指的就是一条JSON数据。 1.3 Field(文档字段)文档由多个json字段(Field)组成, 这里的字段相似mysql中表的字段。 1.4 mapping (映射)Elasticsearch的mapping (映射)相似mysql中的表构造定义,每个索引都有一个映射规定,咱们能够通过定义索引的映射规定,提前定义好文档的json构造和字段类型,如果没有定义索引的映射规定,Elasticsearch会在写入数据的时候,依据咱们写入的数据字段揣测出对应的字段类型,相当于主动定义索引的映射规定。 类比MYSQL存储构造: 元数据的字段名都是以下划线结尾的常见的元数据如下: _index - 代表以后JSON文档所属的文档名字_type - 代表以后JSON文档所属的类型,尽管新版ES废除了type的用法,然而元数据还是能够看到_id - 文档惟一Id, 如果咱们没有为文档指定id,零碎会主动生成_source - 代表咱们插入进去的JSON数据_version - 文档的版本号,每批改一次文档数据,字段就会加1,这个字段新版的ES曾经不应用了_seq_no - 文档的版本号, 代替老的_version字段_primary_term - 文档所在主分区,这个能够跟_seq_no字段搭配实现乐观锁开发实战:第三方开发者保护的库 olivere/elastic官网的Golang客户端 go-elasticsearch olivere/elastic 应用 package mainimport ( "context" "fmt" "github.com/olivere/elastic/v7" "log" "os" "reflect" "time")// 查问后果定义type QueryInfo struct { Domain string `json:"domain"` // 查问域名 ClientIP string `json:"client_ip"` // 起源IP Scheme string `json:"scheme"` // 协定类型 Referer string `json:"referer"` // 申请起源 Url string `json:"url"` // 申请url HttpCode string `json:"http_code"` // 返回状态码 Method string `json:"method"` // 申请办法}// 初始化变量var ( queryInfo QueryInfo indexName = "chegva_ngx_proxy_log" // es集群地址列表:https://sgp.api.es.che/product/es/cluster-list apiAddresses = []string{"http://sgp.api.es.che"} // elasticsearch 服务地址,多个服务地址应用逗号分隔 username, password = "anzhihe", "anzhihe" client *elastic.Client res *elastic.SearchResult err error ctx context.Context)func init() { // 连贯es集群 client, err = elastic.NewClient( elastic.SetURL(apiAddresses...), elastic.SetBasicAuth(username, password), // 容许您指定弹性是否应该定期检查集群(默认为真) elastic.SetSniff(false), // 设置监控查看工夫距离 elastic.SetHealthcheckInterval(10*time.Second), // 设置谬误日志输入 elastic.SetErrorLog(log.New(os.Stderr, "ELASTIC ", log.LstdFlags)), // 设置info日志输入 elastic.SetInfoLog(log.New(os.Stdout, "", log.LstdFlags))) if err != nil { fmt.Println("连贯失败:%v\n", err) panic(err) } fmt.Println("连贯胜利", client)}// 查问日志func Search() { // 执行ES申请须要提供一个上下文对象 ctx = context.Background() // 创立bool组合查问;must条件相似SQL的and;must_not与must作用相同;should相似SQL中的or,只有匹配区中一个就行 //boolQuery := elastic.NewBoolQuery().Must() // 创立查问 // term准确查问;terms多值查问相似SQL的in查问 domainQuery := elastic.NewTermQuery("domain", "api.pay.chegva.com") // Match查问,匹配单个字段 urlQuery := elastic.NewMatchQuery("url", "/pay/") // Ranges范畴查问 timeQuery := elastic.NewRangeQuery("timestamp"). Gte("2022-02-22"). Lte("now"). Format("yyyy-MM-dd") // Filter 多重条件筛选 boolSearch := elastic.NewBoolQuery(). Filter(domainQuery). Filter(urlQuery). Filter(timeQuery) // 设置bool查问的must条件, 组合了两个子查问 //boolQuery.Must(domainQuery, timeQuery) res, err = client.Search(). Index(indexName). Query(boolSearch). From(0).Size(10). // 拿前10个后果,默认不能大于10000 Pretty(true). Do(ctx) // 执行 if err != nil { panic(err) } total := res.TotalHits() fmt.Printf("Found %d results\n", total) if total > 0 { printQueryInfo(res, err) } else { fmt.Println("Not found!") }}// 打印查问到的输入func printQueryInfo(res *elastic.SearchResult, err error) { if err != nil { print(err.Error()) return } // 通过Each办法,将es后果的json构造转换成struct对象 for _, item := range res.Each(reflect.TypeOf(queryInfo)) { //从搜寻后果中取数据的办法 if q, ok := item.(QueryInfo); ok { fmt.Printf("%#v\n", q) } }}func main() { Search()}官网库go-elasticsearch应用: ...

May 13, 2022 · 2 min · jiezi

关于golang:Go学习笔记GC回收机制

V1.3前-标记革除法mark and sweepGoV1.3之前的标记革除: mark and sweep 暂停程序业务逻辑STW(stop the world), 分类出可达和不可达的对象,而后做上标记。程序找出它所有可达的对象,并做上标记。革除未标记的对象。(对象5,6不可达,被GC革除)进行暂停,让程序持续跑。而后循环反复这个过程,直到process程序生命周期完结。 留神:mark and sweep算法在执行的时候,须要程序暂停!即 STW(stop the world),STW的过程中,CPU不执行用户代码,全副用于垃圾回收,这个过程的影响很大,所以STW也是一些回收机制最大的难题和心愿优化的点。所以在执行第三步的这段时间,程序会暂定进行任何工作,卡在那期待回收执行结束。 mark and sweep的毛病: STW,stop the world;让程序暂停,程序呈现卡顿 (重要);标记须要扫描整个heap(堆);革除数据会产生heap碎片。V1.3 STW步骤提前 STW的步骤提前了一步,因为在Sweep革除的时候,能够不须要STW进行,因为这些对象曾经是不可达对象了,不会呈现回收写抵触等问题。 然而无论怎么优化,Go V1.3都面临这个一个重要问题,就是mark-and-sweep 算法会暂停整个程序 。 V1.5 三色标记法每次新创建的对象,默认的色彩都是标记为“红色”每次GC回收开始, 会从根节点开始遍历所有对象,把遍历到的对象从红色汇合放入“灰色”汇合遍历灰色汇合,将灰色对象援用的对象从红色汇合放入灰色汇合,之后将此灰色对象放入彩色汇合反复第三步, 直到灰色中无任何对象回收所有的红色标记表的对象. 也就是回收垃圾 没有STW的三色标记法在还没有扫描到对象2的时候,如果对象4指向对象3,同时对象2指针p移除,会导致对象3被误杀。 对象失落条件1: 一个红色对象被彩色对象援用(红色被挂在彩色下)条件2: 灰色对象与它之间的可达关系的红色对象受到毁坏(灰色同时丢了该红色)当以上两个条件同时满足时,就会呈现对象失落景象! 强弱三色不变式GC回收器满足上面两种状况之一时,即可保对象不失落。 这两种形式就是“强三色不变式”和“ 弱三色不变式”。 强三色不变式:不存在彩色对象援用到红色对象的指针。弱三色不变式:所有被彩色对象援用的红色对象都处于灰色爱护状态。 插入屏障具体操作:在A对象援用B对象的时候,B对象被标记为灰色。(将B挂在A上游,B必须被标记为灰色) 满足: 强三色不变式. (不存在彩色对象援用红色对象的状况了, 因为红色会强制变成灰色) 插入屏障机制,在栈空间的对象操作中不应用, 仅仅应用在堆空间对象的操作中。 解决流程: 程序创立,对象全副标记为红色,放入红色汇合中遍历Root Set(只遍历一次),失去灰色节点遍历灰色标记表,将可达对象标记为灰色,遍历后的灰色对象标记为彩色此时对象4增加对象8(堆区:触发插入屏障),对象1增加对象9(不触发)因为插入写屏障,对象8变为灰色,对象9仍为红色 持续循环上述流程进行三色标记,直到没有灰色节点在筹备回收红色节点前,从新遍历扫描一次栈空间,此时加STW暂停爱护栈,避免外界烦扰(有新的红色被彩色增加)在STW中,将占中的对象进行三色标记,直到没有灰色节点进行STW革除红色 有余:完结时须要STW来从新扫描,大概须要10~100ms 删除屏障具体操作: 被删除的对象,如果本身为灰色或者红色,那么被标记为灰色。 满足: 弱三色不变式. (爱护灰色对象到红色对象的门路不会断) 解决流程: 程序创立,对象全副标记为红色,放入红色汇合中遍历Root Set(只遍历一次),失去灰色节点对象1删除对象5,触发删除写屏障,对象5标记为灰色循环进行三色标记,直到没有灰色节点革除红色 毛病:回收精度低,一个对象即便被删除了最初一个指向它的指针也仍旧能够活过这一轮,在下一轮GC中被清理掉。 V1.8 三色标记法+混合写屏障混合写屏障具体操作: ...

May 13, 2022 · 1 min · jiezi

关于golang:Go开源视频下载工具

lux(like youtube-dl) Fast and simple video download library and CLI tool written in Go githubhttps://github.com/iawia002/lux usagelux -d "https://www.bilibili.com/video/BV1Fi4y1S754" install luxgo install github.com/iawia002/lux@latestinstall ffmpeguse choco https://community.chocolatey....

May 12, 2022 · 1 min · jiezi

关于golang:深入Go底层原理重写Redis中间件实战一起分享

download:https://www.97yrbl.com/t-1413... JSPJSP在理论开发中,次要是作为MVC模型中的V(View)层呈现的。当然,View层的渲染技术除了JSP,还有FreeMaker、Velocity等。JSP作为页面模板,在后端通过MVC框架渲染成HMTL,而后再发送到客户端(例如浏览器)来出现。这也就是咱们常说的“前后端不拆散”,“混合式”开发。而以后,包含很多的公司,以及大部分互联网公司。要么曾经摈弃这种模式,要么正在摈弃的路上,而转向彻底的“前后端拆散”。在“前后端拆散”模式下,后端只负责提供服务接口(例如REST),而前端(例如HTML5)通过接口发送/获取,出现数据(例如JSON格局)。这样,在后端,原来的MVC框架,某种意义上曾经演变为MC框架。因而,与V(View)相干的所有模板技术都失去了学习的必要,其中当然也包含JSP。**所以,起初的Java学习者,我的倡议是:“齐全能够放弃对JSP的学习。”Struts在Java后端开发中,MVC模型还是支流。而Struts作为一个MVC框架,单从技术上来说,还是很优良的。然而,当初Spring切实是太强势了,越来越成为Java开发中的“一站式”工具包,其中的一个利器就是Spring MVC。望名知意,Spring MVC也是一个MVC框架。而且因为它是Spring的亲儿子,天然和Spring符合的十分完满。同时,在设计之初,Spring MVC就参照了其余MVC框架的优缺点(包含Struts),所以用起来十分爽。因而,在MVC框架畛域,Spring MVC大有一统天下的趋势。因而当初,很多公司,老的Struts我的项目还在保护。但新的我的项目开发,更多转向了Spring MVC。因而,如果你是Java老手,正在学习中,我的倡议是:“不要再学习Struts了,从Spring MVC开始吧!”

May 12, 2022 · 1 min · jiezi

关于golang:深入Go底层原理重写Redis中间件实战吾爱fen享

download:https://www.sisuoit.com/2926....需要对于须要前端实现无痛刷新Token,无非就两种: 申请前判断Token是否过期,过期则刷新申请后依据返回状态判断是否过期,过期则刷新 解决逻辑实现起来也没多大差异,只是判断的地位不一样,外围原理都一样: 判断Token是否过期没过期则失常解决过期则发动刷新Token的申请拿到新的Token保留从新发送Token过期这段时间内发动的申请重点: 放弃Token过期这段时间发动申请状态(不能进入失败回调)把刷新Token后从新发送申请的响应数据返回到对应的调用者 实现 创立一个flag isRefreshing 来判断是否刷新中创立一个数组队列retryRequests来保留须要从新发动的申请判断到Token过期 isRefreshing = false 的状况下 发动刷新Token的申请刷新Token后遍历执行队列retryRequestsisRefreshing = true 示意正在刷新Token,返回一个Pending状态的Promise,并把申请信息保留到队列retryRequests中import axios from "axios";import Store from "@/store";import Router from "@/router";import { Message } from "element-ui";import UserUtil from "@/utils/user";// 创立实例const Instance = axios.create();Instance.defaults.baseURL = "/api";Instance.defaults.headers.post["Content-Type"] = "application/json";Instance.defaults.headers.post["Accept"] = "application/json";// 定义一个flag 判断是否刷新Token中let isRefreshing = false;// 保留须要从新发动申请的队列let retryRequests = [];// 申请拦挡Instance.interceptors.request.use(async function(config) { Store.commit("startLoading"); const userInfo = UserUtil.getLocalInfo(); if (userInfo) { //业务须要把Token信息放在 params 外面,一般来说都是放在 headers外面 config.params = Object.assign(config.params ? config.params : {}, { appkey: userInfo.AppKey, token: userInfo.Token }); } return config;});// 响应拦挡Instance.interceptors.response.use( async function(response) { Store.commit("finishLoading"); const res = response.data; if (res.errcode == 0) { return Promise.resolve(res); } else if ( res.errcode == 30001 || res.errcode == 40001 || res.errcode == 42001 || res.errcode == 40014 ) { // 须要刷新Token 的状态 30001 40001 42001 40014 // 拿到本次申请的配置 let config = response.config; // 进入登录页面的不做刷新Token 解决 if (Router.currentRoute.path !== "/login") { if (!isRefreshing) { // 扭转flag状态,示意正在刷新Token中 isRefreshing = true; // 刷新Token return Store.dispatch("user/refreshToken") .then(res => { // 设置刷新后的Token config.params.token = res.Token; config.params.appkey = res.AppKey; // 遍历执行须要从新发动申请的队列 retryRequests.forEach(cb => cb(res)); // 清空队列 retryRequests = []; return Instance.request(config); }) .catch(() => { retryRequests = []; Message.error("主动登录失败,请从新登录"); const code = Store.state.user.info.CustomerCode || ""; // 刷新Token 失败 清空缓存的用户信息 并调整到登录页面 Store.dispatch("user/logout"); Router.replace({ path: "/login", query: { redirect: Router.currentRoute.fullPath, code: code } }); }) .finally(() => { // 申请实现后重置flag isRefreshing = false; }); } else { // 正在刷新token,返回一个未执行resolve的promise // 把promise 的resolve 保留到队列的回调外面,期待刷新Token后调用 // 原调用者会处于期待状态直到 队列从新发动申请,再把响应返回,以达到用户无感知的目标(无痛刷新) return new Promise(resolve => { // 将resolve放进队列,用一个函数模式来保留,等token刷新后间接执行 retryRequests.push(info => { // 将新的Token从新赋值 config.params.token = info.Token; config.params.appkey = info.AppKey; resolve(Instance.request(config)); }); }); } } return new Promise(() => {}); } else { return Promise.reject(res); } }, function(error) { let err = {}; if (error.response) { err.errcode = error.response.status; err.errmsg = error.response.statusText; } else { err.errcode = -1; err.errmsg = error.message; } Store.commit("finishLoading"); return Promise.reject(err); });export default Instance;

May 12, 2022 · 2 min · jiezi

关于golang:深入Go底层原理重写Redis中间件实战同步追更

复制URL下哉ZY:https://www.666xit.com/3506/download:深刻Go底层原理,重写Redis中间件实战第一次Composition API在vue3.2中,正式反对了script setup的写法,这样能够大大简化组件的代码量,缩小一些反复操作,我认为当你写vue3时,应该把这当作默认写法。 在vue3.2之前,个别会这样写。 <script> export default { setup(props,ctx){ const a = ref(0) //必须return能力在template中应用,这里就存在一个反复操作的问题,每次都得cv,万一遗记就得查看 return { a } } }</script>那么当初,咱们能够这样写,比照一下,缩小了多少行代码呢 <script setup> const a = ref(0)</script>PS:之后的代码我会省略script setup,默认都在script setup标签下。兴许你会感觉这样就更简略了,其实恰恰相反,CompositionAPI其实要求你对逻辑解决有更清晰的意识,对于封装有更高的要求,否则,你一样会写成比以前更丑的代码。例如: const a = ref(0) const b = ref('') const c = ref(true) const d = reactive({}) const actionA = ()=>{a.value++} const actionC = ()=>{c.value=!c.value} const actionB = ()=>{b.value += 'test' } const actiond = async ( )=> { const res = await ajax(`url`) d.a = res.a d.b = res.b d.c = res.c } const resetD = ()=>{ Object.keys(d).forEach(key=>delete d[key]) }这一堆代码其实就是当你没有思考逻辑,没有想过封装的时候,像流水账一样间接写进去的代码,这些代码真的比optionsApi更好浏览吗,当然不。这里更加凌乱,你很难一眼看出某个函数用的是哪个变量,程序凌乱,这时候须要封装,须要组合,这也是CompositionAPI的含意之一。 ...

May 12, 2022 · 1 min · jiezi

关于golang:slog-Go-实现的一个易于使用的易扩展可配置的日志库

slog - Go 实现的一个易于应用的,易扩大、可配置的日志库 控制台日志成果: 性能特色简略,无需配置,开箱即用反对罕用的日志级别解决 如: trace debug info notice warn error fatal panic能够任意扩大本人须要的 Handler Formatter反对同时增加多个 Handler 日志解决,输入日志到不同的中央反对自定义构建 Handler 处理器 内置的 handler.Config handler.Builder,能够方便快捷的构建想要的日志处理器反对自定义 Formatter 格式化解决 内置了 json text 两个日志记录格式化 Formatter曾经内置了罕用的日志处理器 console 输入日志到控制台,反对色调输入writer 输入日志到指定的 io.Writerfile 输入日志到指定文件,可选启用 buffer 缓冲写入simple 输入日志到指定文件,无缓冲间接写入文件rotate_file 输入日志到指定文件,并且同时反对按工夫、按大小宰割文件,默认启用 buffer 缓冲写入更多内置实现请查看 ./handler 文件夹代码仓库GithubGitee装置go get github.com/gookit/slog疾速开始slog 应用非常简单,无需任何配置即可应用。 package mainimport ( "github.com/gookit/slog")func main() { slog.Info("info log message") slog.Warn("warning log message") slog.Infof("info log %s", "message") slog.Debugf("debug %s", "message")}输入预览: [2020/07/16 12:19:33] [application] [INFO] [main.go:7] info log message [2020/07/16 12:19:33] [application] [WARNING] [main.go:8] warning log message [2020/07/16 12:19:33] [application] [INFO] [main.go:9] info log message [2020/07/16 12:19:33] [application] [DEBUG] [main.go:10] debug message 启用控制台色彩您能够在输入控制台日志时启用色彩输入,将会依据不同级别打印不同色调。 ...

May 11, 2022 · 5 min · jiezi

关于golang:Go-读写锁-详解

    后面讲到,在资源竞争的时候能够应用互斥锁,保障了资源拜访的唯一性,但也升高了性能,仔细分析一下场景,如果只是读取数据,无论多少个goroutine都是不会存在逻辑上的互斥操作的。这里读写锁 RWMutex就应运而生了,RWMutex能够别离针对读操作和写操作进行上锁和解锁。    RWMutex同一时刻容许多个读操作进行,但只容许一个写操作进行,同时,在某一个写操作进行的时候,读操作不可进行。 未完待续。。。 参考资料:bilibili

May 10, 2022 · 1 min · jiezi

关于golang:Go-语言基础入门篇

---Tony Bai · Go语言第一课 在 Go 语言中,只有首字母为大写的标识符才是导出的(Exported),能力对包外的代码可见;如果首字母是小写的,那么就阐明这个标识符仅限于在申明它的包内可见。 Go 包是 Go 语言的根本组成单元。一个 Go 程序就是一组包的汇合,所有 Go 代码都位于包中;Go 源码能够导入其余 Go 包,并应用其中的导出语法元素,包含类型、变量、函数、方法等,而且,main 函数是整个 Go 利用的入口函数;Go 源码须要先编译,再散发和运行。如果是单 Go 源文件的状况,咱们能够间接应用go build 命令 +Go 源文件名的形式编译。不过,对于简单的 Go 我的项目,咱们须要在Go Module 的帮忙下实现我的项目的构建。咱们应用 tree 命令来查看一下 Go 语言我的项目本身的最后源码构造布局,以 Go 1.3 版本为例,后果是这样的: 你会看到 src 下的二级目录 pkg 上面寄存着运行时实现、规范库包实现,这些包既能够被下面 cmd 下各程序所导入,也能够被 Go 语言我的项目之外的 Go 程序依赖并导入。 Go 1.4 版本删除 pkg 这一中间层目录并引入 internal 目录。 依据 internal 机制的定义,一个 Go 我的项目里的 internal 目录下的 Go 包,只能够被本我的项目外部的包导入。我的项目内部是无奈导入这个 internal 目录上面的包的。能够说,internal 目录的引入,让一个 Go 我的项目中 Go 包的分类与用处变得更加清晰。 ...

May 9, 2022 · 2 min · jiezi

关于golang:Go语言的设计哲学是怎么一回事

---Tony Bai · Go语言第一课 设计哲学之于编程语言,就好比一个人的价值观之于这个人的行为。 Go 语言的设计哲学总结为五点:简略、显式、组合、并发和面向工程。 简略Go 语法层面: 仅有 25 个关键字,支流编程语言起码内置垃圾收集,升高开发人员内存治理的心智累赘;首字母大小写决定可见性,无需通过额定关键字润饰;变量初始为类型零值,防止以随机值作为初值的问题;内置数组边界查看,极大缩小越界拜访带来的安全隐患;内置并发反对,简化并发程序设计;内置接口类型,为组合的设计哲学奠定根底;原生提供欠缺的工具链,开箱即用;简略的设计哲学是 Go 生产力的源泉. 显式package mainimport "fmt"func main() {var a int16 = 5var b int = 8var c int64c = a + bfmt.Printf("%d\n", c)}如果咱们编译这段程序,将失去相似这样的编译器谬误:“invalid operation: a + b(mismatched types int16 and int)”。咱们能看到 Go 与 C 语言的隐式主动类型转换不同,Go 不容许不同类型的整型变量进行混合计算,它同样也不会对其进行隐式的主动转换。 在 Go 语言中,不同类型变量是不能在一起进行混合计算的,这是因为 Go 心愿开发人员明确晓得本人在做什么. 组合Go 推崇的是组合的设计哲学. Go 语言无类型档次体系,各类型之间是互相独立的,没有子类型的概念;每个类型都能够有本人的办法汇合,类型定义与办法实现是正交独立的;实现某个接口时,无需像 Java 那样采纳特定关键字润饰;包之间是绝对独立的,没有子包的概念。Go 语言其实是为咱们出现了这样的一幅图景:一座座没有关联的“孤岛”,但每个岛内又都很精彩。那么当初摆在面前的工作,就是在这些孤岛之间以最适当的形式建设关联,并造成一个整体。而 Go 抉择采纳的组合形式,也是最次要的形式. Go 语言为撑持组合的设计提供了类型嵌入(Type Embedding)。通过类型嵌入,咱们可以将曾经实现的性能嵌入到新类型中,以疾速满足新类型的性能需要. 被嵌入的类型和新类型两者之间没有任何关系,甚至互相齐全不晓得对方的存在,更没有经典面向对象语言中的那种父类、子类的关系,以及向上、向下转型(Type Casting)。通过新类型实例调用办法时,办法的匹配次要取决于办法名字,而不是类型。这种组合方式,我称之为垂直组合,即通过类型嵌入,疾速让一个新类型“复用”其余类型曾经实现的能力,实现性能的垂直扩大。 你能够看看上面这个 Go 规范库中的一段应用类型嵌入的组合形式的代码段。 ...

May 9, 2022 · 1 min · jiezi

关于golang:为什么-Go-用起来会难受这-6-个细节你知道吗

大家好,我是煎鱼。 在做新的利用选型时,咱们会进行利用编程语言的抉择,这时会纠结 Java、PHP、Go...各种,会思考有没有致命的问题,不能用? 能够明确的是,Go 没有十分致命的问题,否则你我他都不会在这里相遇,也不会大火。 好受的点,倒是有不少,明天就由煎鱼和大家一起来看看。 好受的点泛型在 Go1.18 以前,在所有社交媒体和调查报告上来看。Go 最好受的莫过于没有泛型, 写一个通用的办法,要不得把入参申明为 interface,要不得写 N 个不同类型的一样代码的函数,代码反复率高。 如下图: 这是 Go1.18 以前最好受的点,当初新版本尽管有了泛型,但现阶段配套规范和开源库还没齐全到位,影响还是会持续存在。 浅拷贝和泄露在写 Go 程序时,咱们常常要用到 slice、map 等根底类型。但有一个比拟麻烦的点,就是会波及到浅拷贝。 一个不留神就会引起 BUG,如下代码: type T struct { A string B []string}func main() { x := T{"煎鱼", []string{"下班"}} y := x y.A = "咸鱼" y.B[0] = "上班" fmt.Println(x) fmt.Println(y)}输入后果是什么? 煎鱼到底是下班了,还是上班了? 后果如下: {煎鱼 [上班]}{咸鱼 [上班]}实际上在 y := x 时,他拷贝的是指向对象的指针,这个时候 x 和 y 的底层数据其实是一家子,天然一变动 y,x 的煎鱼也就上班了。 同类型的 slice 也有 append 的泄露,以及 len、cap 的不精确问题,是比拟折腾人的。 ...

May 9, 2022 · 2 min · jiezi

关于golang:布隆过滤器的原理以及Go语言实现

布隆过滤器介绍判断目标值是否在一个汇合中是比拟常见的业务场景。在Go语言中通常应用map来实现给性能。然而当汇合比拟大时,应用map会耗费大量的内存。 这种状况下可应用BitMap来代替map。BitMap尽管可能在肯定状况下缩小的内存的耗费,然而BitMap也存在以下局限性: 当样本分布极度不平均的时候,BitMap会造成很大空间上的节约。若数据的类型Int64,并且数据分布的跨度比拟大,则也无奈满足对内存的要求。当元素不是整型的时候,BitMap就不实用了。BitMap只能保留整形数据,对于字符串类型的数据则不适合应用。BitMap只能解决整形数据,对于字符串则不能说实用。若可能把字符串映射为整形,就能够应用BitMap来存储字符串的状态了。 hash函数能够将字符串映射为整形数据,然而hash函数映射为整形是存在hash抵触。为了缩小hash抵触,能够应用多个hash函数来将一个字符串映射为多个整数,并将映射后的整数存在BitMap中。在查问字符串时,应用同样的hash函数来计算hash值,并应用同样的hash值来查问BitMap,若其中有一个hash值没有命中,则该url不存在。 上述思路就是布隆过滤器的外围思路。布隆过滤器是由布隆(Burton Howard Bloom)在1970年提出的,它实际上是由一个很长的二进制向量和一系列随机映射函数组成,布隆过滤器能够用于检索一个元素是否在一个汇合中。它的长处是空间效率和查问工夫都远远超过个别的算法,毛病是有肯定的误识别率:即布隆过滤器报告某个值存在于BitMap中中,然而实际上该值可能并不在汇合中。然而布隆过滤器若报告某个值不在BitMap中,则该值必定不在汇合中。 缩小布隆过滤器误识别率的办法布隆过滤器误辨认的起因在于hash抵触,因而缩小hash抵触能够升高布隆过滤器误识别率。hash抵触和BitMap数组的大小以及hash函数的个数以及每个hash函数自身的好坏无关。能够采纳以下办法升高hash抵触的概率: 多个hash,增大随机性,缩小hash碰撞的概率。扩充数组范畴,使hash值均匀分布,进一步缩小hash碰撞的概率。布隆过滤器的Go语言实现接下来咱们给出布隆过滤器的Go语言实现,目前代码曾经上传到github中,下载地址 定义首先给出布隆过滤器构造的定义: type BloomFilter struct { bset *BitMap size uint}其中: BitMap的实现在bitmap.go文件中size是BitMap二进制位的数量创立BloomFilter构造func NewBloomFilter(size_val ...uint) *BloomFilter { var size uint = 1024*1024 if len(size_val) > 0 && size_val[0] > 0 { size = size_val[0] } bf := &BloomFilter{} bf.bset = NewBitMap(size) bf.size = size return bf}BloomFilter应用的hash函数var seeds = []uint{3011, 3017, 3031}func (bf *BloomFilter)hashFun(seed uint, value string) uint64 { hash := uint64(seed) for i := 0; i < len(value); i++ { hash = hash*33 + uint64(value[i]) } return hash}将数据增加到BloomFilterfunc (bf *BloomFilter)Set(value string) { for _, seed := range seeds { hash := bf.hashFun(seed, value) hash = hash % uint64(bf.size) bf.bset.Set(uint(hash)) }}判断元素是否存在func (bf *BloomFilter)Check(value string) bool { for _, seed := range seeds { hash := bf.hashFun(seed, value) hash = hash % uint64(bf.size) ret := bf.bset.Check(uint(hash)) if !ret { return false } } return true}

May 9, 2022 · 1 min · jiezi

关于golang:Go-互斥锁-详解

go语言中有两种常见的锁机制,互斥锁,读写锁。互斥锁比拟暴力,未完待续。。。 参考资料:bilibili

May 8, 2022 · 1 min · jiezi

关于golang:Go-sync包的WaitGroup同步等待组详解

    sync全称是synchronization,意思是同步。WaitGroup同步期待组是有一组goroutine要期待执行,而以后的goroutine须要期待这一组goroutine都执行完能力执行。    上面是WaitGroup的定义: type WaitGroup struct { //}    每个WaitGroup中都有一个计数器counter记录要期待执行gorouting的数量,能够通过Add这个办法来设置同步期待组中期待goroutine的个数。每个goroutine都会执行,执行完之后都会调用done办法示意把计数器的值-1,与此同时,应用wait办法阻塞,直到所goroutine都执行结束。func (wg *WaitGroup) Add(delta int) 如果计数器的值为0,那么期待过程中被阻塞的goroutine都会被开释如果计数器得悉为负,那么就会引发panic那么具体什么状况下用,怎么应用呢?上面举个例子: package mainimport "fmt"func main() { //waitGroup go fun1() go fun2()}func fun1() { for i := 1; i < 10; i++ { fmt.Println("print A in fun1", i) }}func fun2() { for i := 0; i < 10; i++ { fmt.Println("\t fun2 print: ", i) }}这样最初什么都没有打印,因为主goroutine在fun1 fun2这两个goroutine执行之前完结了。这时候就能够用WaitGroup了。 package mainimport ( "fmt" "sync")//创立同步期待组var wg sync.WaitGroupfunc main() { //waitGroup wg.Add(2) //设置计数器为2(goroutine的个数) go fun1() go fun2() fmt.Println("main 进入阻塞状态, 须要期待wg的goroutine完结") wg.Wait() //示意main goroutine进入阻塞 fmt.Println("计数器归零 进行阻塞")}func fun1() { defer wg.Done() //计数器-1 for i := 1; i < 10; i++ { fmt.Println("print A in fun1", i) }}func fun2() { defer wg.Done() //计数器-1 for i := 0; i < 10; i++ { fmt.Println("\t fun2 print: ", i) }}后果如下: ...

May 8, 2022 · 1 min · jiezi

关于golang:Go-临界资源的安全问题引入同步异步处理

临界资源定义并发环境中多个过程、线程、协程共享的资源 临界资源的特点可能会因为并发操作导致数据呈现不一致性,举个栗子,上面代码中的a及时临界资源 package mainimport ( "fmt" "time")func main() { //临界资源 a := 1 go func() { a = 2 fmt.Println("in this goroutine: a is : ", a) }() //在主goroutine中 a = 3 time.Sleep(1) fmt.Println("in the main goroutine: a is : ", a)}    经典的售票问题,好多个窗口同时售票的门票的数量就是一个典型的临界资源平安问题,上面用4个协程模仿一下售票过程: package mainimport ( "fmt" "math/rand" "time")var ticket = 10 //the amount of the total ticket is 100func main() { //这里启动4个goroutine模仿4个售票口 同时售票 go saleTickets("ticket window1") go saleTickets("ticket window2") go saleTickets("ticket window3") go saleTickets("ticket window4") //这里为了保障 主协程 最初执行完 先用sleep (当然,也能够用同步期待组、chanel实现) time.Sleep(10 * time.Second)}func saleTickets(name string) { rand.Seed(time.Now().UnixNano()) for { if ticket > 0 { time.Sleep(time.Duration(rand.Intn(1000)) * time.Millisecond) fmt.Println(name, "saled: ", ticket) ticket-- } else { fmt.Println(name, "sorry tickets are sold out") break } }}运行后果如下: ...

May 7, 2022 · 1 min · jiezi

关于golang:go-int64传递到前端导致溢出问题排查

简介 开周会的时候一位共事分享了一个踩坑教训,说在go外面还好好的int64类型,到前端就变得奇奇怪怪了,和原来不一样了。正好我对前端javascript有一点点理解,而后连夜写了点代码摸索了一下这个问题。这个问题的实质是javascript number类型能示意的数据范畴不能残缺包含go中int64的范畴导致的。上面看笔者娓娓道来。 踩坑剖析 话不多说,咱们应用以下代码构建一下go http后端试验场景。上面代码提供了go原生的http api和http框架gin两种形式启动http服务。读者们能够运行这两种形式之中的任意一种去将服务跑起来。 type Data struct { Id int64 `json:"id"`}// 构建试验数据。var int64Data = []Data{ { Id: 1, }, { Id: 2, }, { Id: 3, }, { Id: 1 << 53, }, { Id: 1<<53 + 1, }, { Id: 1<<53 + 2, }, { Id: 1<<53 + 3, },}// go 原生http服务func TestHttpJson(t *testing.T) { var httpJsonTestHandler = func(w http.ResponseWriter, r *http.Request) { log.Println("com here") r.Header.Set("Access-Control-Allow-Origin", "*") w.Header().Set("content-type", "text/json") if resByte, jsonErr := json.Marshal(int64Data); jsonErr != nil { w.Write([]byte(jsonErr.Error())) log.Fatal(jsonErr) } else { w.Write(resByte) } return } http.HandleFunc("/json_test", httpJsonTestHandler) err := http.ListenAndServe(":8888", nil) if err != nil { log.Fatal("ListenAndServe: ", err) } select {}}// gin http服务func TestJsonNumberWithGin(t *testing.T) { r := gin.Default() r.GET("json_test", func(c *gin.Context) { c.Request.Header.Set("Access-Control-Allow-Origin", "*") c.JSON(http.StatusOK, int64Data) }) r.Run(":8888")}运行起来服务之后,咱们应用postman拜访一下这个接口。这看起来没有什么问题啊,一切正常的样子。然而这只是表象。因为咱们返回的是字符数组,postman读的也是字符数组,所以相当于postman拿到字符数组之后拼接成字符串展现给咱们,这样子的话无论你后端的http接口返回的是什么,postman都能给你做一摸一样的展现。同理,在浏览器上拜访这个接口意识这样的,数据看不出什么同样。 ...

May 7, 2022 · 2 min · jiezi

关于golang:Redis-如何批量设置过期时间PIPLINE的使用

正当的应用缓存策略对开发同学来讲,就如同孙悟空习得自在极意功个别~抛出问题Redis如何批量设置过期工夫呢?不要说在foreach中通过set()函数批量设置过期工夫 给出计划咱们引入redis的PIPLINE,来解决批量设置过期工夫的问题。 PIPLINE的原理是什么?未应用pipline执行N条命令 应用pipline执行N条命令 通过图例能够很显著的看进去PIPLINE的原理: 客户端通过PIPLINE拼接子命令,只须要发送一次申请,在redis收到PIPLINE命令后,解决PIPLINE组成的命令块,缩小了网络申请响应次数。 网络提早越大PIPLINE的劣势越能体现进去 拼接的子命令条数越多应用PIPLINE的劣势越能体现进去 留神:并不是拼接的子命令越多越好,N值也有是下限的,当拼接命令过长时会导致客户端期待很长时间,造成网络梗塞;咱们能够依据理论状况,把大批量命令拆分成几个PIPLINE执行。 代码封装//批量设置过期工夫public static function myPut(array $data, $ttl = 0){ if (empty($data)) { return false; } $pipeline = Redis::connection('cache') ->multi(\Redis::PIPELINE); foreach ($data as $key => $value) { if (empty($value)) { continue; } if ($ttl == 0) { $pipeline->set(trim($key), $value); } else { $pipeline->set(trim($key), $value, $ttl); } } $pipeline->exec();}我的项目实战需要形容关上APP,给喜爱我的人发送我的上线告诉(为了防止打搅,8小时内反复登录不触发告诉)每个人每半小时只会收到一次这类上线告诉(即半小时内就算我喜爱的1万人都上线了,我也只收到一次喜爱的人上线告诉)要点剖析正当应用缓存,缩小DB读写次数不仅要缩小DB读写次数,也要缩小Redis的读写次数,应用PIPLINE代码实现解析canRecall() 写的比拟优雅,先判断是否已发送的标记,再判断HouseOpen::getCurrentOpen(),因为HouseOpen::getCurrentOpen()是要查问DB计算的,这种代码要尽可能少的被执行到,缩小DB查问。array_diff() 取差集的思路,取得须要推送的人封装工具类<?phpnamespace App\Model\House;...class HouseLikeRecallUser{ protected $_userid = ''; protected $_availableUser = []; protected $_recallFlagKey = ''; const TYPE_TTL_HOUSE_LIKE_RECALL = 60 * 30; //半小时后能够再次接管到喜爱的xxx进入告诉 const TYPE_TTL_HOUSE_LIKE_RECALL_FLAG = 60 * 60 * 8; //8小时反复登录不触发 //初始化 传入setRecalled 的过期工夫 public function __construct($userid) { $this->_userid = $userid; //登录后给喜爱我的人推送校验:同一场次反复登录不反复发送 $this->_recallFlagKey = CacheKey::getCacheKey(CacheKey::TYPE_HOUSE_LIKE_RECALL_FLAG, $this->_userid); } //设置以后用户推送标示 public function setRecalled() { Cache::put($this->_recallFlagKey, 1, self::TYPE_TTL_HOUSE_LIKE_RECALL_FLAG); } //获取以后用户是否触发推送 public function canRecall() { $res = false; if (empty(Cache::get($this->_recallFlagKey))) { $houseOpen = HouseOpen::getCurrentOpen(); if ($houseOpen['status'] == HouseOpen::HOUSE_STATUS_OPEN) { $res = true; } } return $res; } //获取须要推送用户 public function getAvailableUser() { //取得最近喜爱我的用户 $recentLikeMeUser = UserRelationSingle::getLikeMeUserIds($this->_userid, 100, Utility::getBeforeNDayTimestamp(7)); //取得最近喜爱我的用户的 RECALL缓存标记 foreach ($recentLikeMeUser as $userid) { $batchKey[] = CacheKey::getCacheKey(CacheKey::TYPE_HOUSE_LIKE_RECALL, $userid); } //取得最近喜爱我的且曾经推送过的用户 $cacheData = []; if (!empty($batchKey)) { $cacheData = Redis::connection('cache')->mget($batchKey); } //计算最近喜爱我的用户 和 曾经推送过的用户 的差集:就是须要推送的用户 $this->_availableUser = array_diff($recentLikeMeUser, $cacheData); return $this->_availableUser; } //更新曾经推送的用户 public function updateRecalledUser() { //批量更新差集用户 $recalledUser = []; foreach ($this->_availableUser as $userid) { $cacheKey = CacheKey::getCacheKey(CacheKey::TYPE_HOUSE_LIKE_RECALL, $userid); $recalledUser[$cacheKey] = $userid; } //批量更新 设置过期工夫 self::myPut($recalledUser, self::TYPE_TTL_HOUSE_LIKE_RECALL); } //批量设置过期工夫 public static function myPut(array $data, $ttl = 0) { if (empty($data)) { return false; } $pipeline = Redis::connection('cache') ->multi(\Redis::PIPELINE); foreach ($data as $key => $value) { if (empty($value)) { continue; } if ($ttl == 0) { $pipeline->set(trim($key), $value); } else { $pipeline->set(trim($key), $value, $ttl); } } $pipeline->exec(); }}调用工具类public function handle(){ $userid = $this->_userid; $houseLikeRecallUser = new HouseLikeRecallUser($userid); if ($houseLikeRecallUser->canRecall()) { $recallUserIds = $houseLikeRecallUser->getAvailableUser(); $houseLikeRecallUser->setRecalled(); $houseLikeRecallUser->updateRecalledUser(); //群发推送音讯 . . . }}总结不同量级的数据须要不同的解决方法,缩小网络申请次数,正当应用缓存,是性能优化的必经之路。 ...

May 7, 2022 · 2 min · jiezi

关于golang:微服务从代码到k8s部署应有尽有系列十三服务监控

咱们用一个系列来解说从需要到上线、从代码到k8s部署、从日志到监控等各个方面的微服务残缺实际。 整个我的项目应用了go-zero开发的微服务,根本蕴含了go-zero以及相干go-zero作者开发的一些中间件,所用到的技术栈根本是go-zero项目组的自研组件,根本是go-zero全家桶了。 实战我的项目地址:https://github.com/Mikaelemmm... 1、概述好的服务肯定是能够被及时监控的,在go-zero-looklook中咱们应用目前比拟风行的prometheus来作为监控工具,而后应用grafana来显示 go-zero曾经在代码中给咱们集成好了prometheus // StartAgent starts a prometheus agent.func StartAgent(c Config) { if len(c.Host) == 0 { return } once.Do(func() { enabled.Set(true) threading.GoSafe(func() { http.Handle(c.Path, promhttp.Handler()) addr := fmt.Sprintf("%s:%d", c.Host, c.Port) logx.Infof("Starting prometheus agent at %s", addr) if err := http.ListenAndServe(addr, nil); err != nil { logx.Error(err) } }) })}无论当咱们启动api、rpc都会额定启动一个goroutine 提供prometheus的服务 【注】如果像咱们之前order-mq这种应用serviceGroup治理的服务,在启动文件main中要显式调用一下才能够,api、rpc不须要,配置都一样 package main.....func main() { .... // log、prometheus、trace、metricsUrl. if err := c.SetUp(); err != nil { panic(err) } ......}2、实现2.1 配置prometheus与grafana在我的项目下的docker-compose-env.yml文件中 ...

May 7, 2022 · 2 min · jiezi

关于golang:gomicro集成RabbitMQ实战和原理

在go-micro中异步音讯的收发是通过Broker这个组件来实现的,底层实现有RabbitMQ、Kafka、Redis等等很多种形式,这篇文章次要介绍go-micro应用RabbitMQ收发数据的办法和原理。 Broker的外围性能Broker的外围性能是Publish和Subscribe,也就是公布和订阅。它们的定义是: Publish(topic string, m *Message, opts ...PublishOption) errorSubscribe(topic string, h Handler, opts ...SubscribeOption) (Subscriber, error)公布公布第一个参数是topic(主题),用于标识某类音讯。 公布的数据是通过Message承载的,其包含音讯头和音讯体,定义如下: type Message struct { Header map[string]string Body []byte}音讯头是map,也就是一组KV(键值对)。 音讯体是字节数组,在发送和接管时须要开发者进行编码和解码的解决。 订阅订阅的第一个参数也是topic(主题),用于过滤出要接管的音讯。 订阅的数据是通过Handler解决的,Handler是一个函数,其定义如下: type Handler func(Event) error其中的参数Event是一个接口,须要具体的Broker来实现,其定义如下: type Event interface { Topic() string Message() *Message Ack() error Error() error}Topic() 用于获取以后音讯的topic,也是发布者发送时的topic。Message() 用于获取音讯体,也是发布者发送时的Message,其中包含Header和Body。Ack() 用于告诉Broker音讯曾经收到了,Broker能够删除音讯了,可用来保障音讯至多被生产一次。Error() 用于获取Broker解决音讯过胜利的谬误。开发者订阅数据时,须要实现Handler这个函数,接管Event的实例,提取数据进行解决,依据不同的Broker,可能还须要调用Ack(),解决呈现谬误时,返回error。 go-micro集成RabbitMQ实战大略理解了Broker的定义之后,再来看下如何应用go-micro收发RabbitMQ音讯。 启动一个RabbitMQ如果你曾经有一个RabbitMQ服务器,请跳过这个步骤。 这里介绍一个应用docker疾速启动RabbitMQ的办法,当然前提是你得装置了docker。 执行如下命令启动一个rabbitmq的docker容器: docker run --name rabbitmq1 -p 5672:5672 -p 15672:15672 -d rabbitmq而后进入容器进行一些设置: docker exec -it rabbitmq1 /bin/bash启动管理工具、禁用指标采集(会导致某些API500谬误): rabbitmq-plugins enable rabbitmq_management cd /etc/rabbitmq/conf.d/echo management_agent.disable_metrics_collector = false > management_agent.disable_metrics_collector.conf最初重启容器: ...

May 7, 2022 · 2 min · jiezi

关于golang:Go-Quiz-从Go面试题看变量的零值和初始化赋值的注意事项

背景Google工程师Valentin Deleplace出了1道对于变量初始化的题目,原本认为很简略,没想到答复正确率不到30%,拿进去和大家分享下。 题目// quiz.gopackage mainimport "fmt"func main() { var a *int *a = 5.0 fmt.Println(*a)}A: 5B: 5.0 C: panicD: 编译谬误解析这道题次要考查2个知识点: 变量零值。题目中a是一个指针类型的变量,var a *int这行代码没有对变量a初始化赋值,所以变量a的值是零值,指针的零值是nil,所以a的值是nil。变量初始化赋值。如果对int类型的变量赋值为浮点数5.0是非法的,因为5.0是untyped float constant,是能够在不损失精度的状况下转换为5的。如果是赋值为5.1那就非法了,因为要损失小数点前面的精度,编译报错如下: ./quiz1.go:8:7: cannot use 5.1 (untyped float constant) as int value in assignment (truncated)所以本题答案是C,编译的时候不会报错,然而运行的时候因为a 的值是nil,对nil做*操作就会引发panic,具体panic内容为:panic: runtime error: invalid memory address or nil pointer dereference。 思考题题目1: // quiz1.gopackage mainimport "fmt"func main() { var a *int = new(int) *a = 5.0 fmt.Println(*a)}题目2: // quiz2.gopackage mainimport "fmt"func main() { var a *int = new(int) var b float32 = 5.0 *a = b fmt.Println(*a)}题目3: ...

May 6, 2022 · 1 min · jiezi

关于golang:微服务从代码到k8s部署应有尽有系列十二链路追踪

咱们用一个系列来解说从需要到上线、从代码到k8s部署、从日志到监控等各个方面的微服务残缺实际。 整个我的项目应用了go-zero开发的微服务,根本蕴含了go-zero以及相干go-zero作者开发的一些中间件,所用到的技术栈根本是go-zero项目组的自研组件,根本是go-zero全家桶了。 实战我的项目地址:https://github.com/Mikaelemmm... 1、概述如果依照我前两节错误处理、日志收集配置的话,咱们通过日志中的traceId也能够残缺看到报错时候的整体链路日志,然而不报错的时候或者想不便的查看单个业务整个链路调用的执行工夫是不太不便查看的,所以最好还是加上链路追踪。 go-zero底层曾经帮咱们把代码跟链路追踪对接的代码曾经写好了 func startAgent(c Config) error { opts := []sdktrace.TracerProviderOption{ // Set the sampling rate based on the parent span to 100% sdktrace.WithSampler(sdktrace.ParentBased(sdktrace.TraceIDRatioBased(c.Sampler))), // Record information about this application in an Resource. sdktrace.WithResource(resource.NewSchemaless(semconv.ServiceNameKey.String(c.Name))), } if len(c.Endpoint) > 0 { exp, err := createExporter(c) if err != nil { logx.Error(err) return err } // Always be sure to batch in production. opts = append(opts, sdktrace.WithBatcher(exp)) } tp := sdktrace.NewTracerProvider(opts...) otel.SetTracerProvider(tp) otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator( propagation.TraceContext{}, propagation.Baggage{})) otel.SetErrorHandler(otel.ErrorHandlerFunc(func(err error) { logx.Errorf("[otel] error: %v", err) })) return nil}默认反对jaeger、zinpink ...

May 6, 2022 · 1 min · jiezi

关于golang:sqlx操作MySQL实战及其原理

sqlx是Golang中的一个出名三方库,其为Go规范库database/sql提供了一组扩大反对。应用它能够不便的在数据行与Golang的构造体、映射和切片之间进行转换,从这个角度能够说它是一个ORM框架;它还封装了一系列地罕用SQL操作方法,让咱们用起来更爽。 sqlx实战这里以操作MySQL的增删改查为例。 筹备工作先要筹备一个MySQL,这里通过docker疾速启动一个MySQL 5.7。 docker run -d --name mysql1 -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 mysql:5.7在MySQL中创立一个名为test的数据库: CREATE DATABASE `test` /*!40100 DEFAULT CHARACTER SET utf8mb4 */;数据库中创立一个名为Person的数据库表: CREATE TABLE test.Person ( Id integer auto_increment NOT NULL, Name VARCHAR(30) NULL, City VARCHAR(50) NULL, AddTime DATETIME NOT NULL, UpdateTime DATETIME NOT NULL, CONSTRAINT Person_PK PRIMARY KEY (Id))ENGINE=InnoDBDEFAULT CHARSET=utf8mb4COLLATE=utf8mb4_general_ci;而后创立一个Go我的项目,装置sqlx: go get github.com/jmoiron/sqlx因为操作的是MySQL,还须要装置MySQL的驱动: go get github.com/go-sql-driver/mysql编写代码增加援用增加sqlx和mysql驱动的援用: import ( "log" _ "github.com/go-sql-driver/mysql" "github.com/jmoiron/sqlx")MySQL的驱动是隐式注册的,并不会在接下来的程序中间接调用,所以这里加了下划线。 创立连贯操作数据库前须要先创立一个连贯: db, err := sqlx.Connect("mysql", "root:123456@tcp(127.0.0.1:3306)/test?charset=utf8mb4&parseTime=true&loc=Local") if err != nil { log.Println("数据库连贯失败") }这个连贯中指定了程序要用MySQL驱动,以及MySQL的连贯地址、用户名和明码、数据库名称、字符编码方式;这里还有两个参数parseTime和loc,parseTime的作用是让MySQL中工夫类型的值能够映射到Golang中的time.Time类型,loc的作用是设置time.Time的值的时区为以后零碎时区,不应用这个参数的话保留到的数据库的就是UTC工夫,会和北京时间差8个小时。 ...

May 6, 2022 · 4 min · jiezi

关于golang:Go-GPM的理解-与-runtime包

了解GPMGo语言的scheduler的实现原理Go语言中撑持整个scheduler实现的次要有四个构造,别离是G、P、M、Sched,前三个定义在runrtime.h,Sched定义在proc.c中。 Sched构造就是调度器,保护了存储M和G的队列,以及调度器的所有状态信息G是goroutine实现的外围构造,是对一个要并发执行的工作的封装,能够称作为用户态的线程,属于用户级资源,对操作系统通明,轻量级,能够大量创立,上下文的切换成本低。P是Processor,逻辑处理器,次要作用是治理G对象,并为G在M上的运行提供本地化资源,他保护了一个goroutine队列runqueue,也能够了解为goroutine的上下文环境。M是Machine,是利用零碎调用创立进去的操作系统线程实体,作用是执行G中的并发工作,代表了一个内核线程,由操作系统治理,goroutine就是运行在M上的,M中保护了小对象内存mcache,以后执行的goroutine,随机数发生器等信息。runtime包runtime相似于Java或者.Net中的虚拟机,负责内存调配,垃圾回收,goroutine,chanel,切片,map,反射等等。 未完待续。。。。 参考资料:bilibili

May 5, 2022 · 1 min · jiezi

关于golang:带你了解Go项目标准目录布局

很多的时候,咱们开发一个简略的Go我的项目的时候并不需要纠结于我的项目的的目录布局,因为咱们会将所有go源码文件扔在我的项目的根目录中,就像上面这样: demo├── main.go├── model.go└── service.go但当咱们的我的项目变得复杂的时候,咱们就须要好好思考怎么组织咱们的我的项目了,这时候你可能会想,用官网的Go我的项目目录布局就好了,然而Go的官网并没有给出一个规范的Go我的项目规范目录布局。 目前,Go社区开发者比拟举荐的是project-layout我的项目中给出的目录布局,如: demo├── api├── assets├── build├── cmd├── configs├── deployments├── docs├── examples├── githooks├── init├── internal├── pkg├── scripts├── test├── third_party├── tools├── vendor├── web└── website然而官网并不举荐这种我的项目布局形式,上面是Go团队开发leader Russ Cox对project-layout目录的意见: 大体上,咱们能够将Go的我的项目分为两类,利用我的项目与库我的项目。 利用我的项目:即蕴含可执行文件的我的项目,我的项目开发后,须要编译成可执行文件并上线运行。库我的项目:库文件个别用于裸露Api,被其余我的项目通过import关键字引入。利用我的项目与库我的项目的目录布局形式稍有差别。 利用我的项目对于利用我的项目,其目录布局形式咱们能够参考Go我的项目本身,以及一些应用Go语言开发的优良开源我的项目(如Docker,Kubernetes,etcd)等,通过钻研这些我的项目的目录布局,咱们能够形象出以下规范目录: demo├── cmd│ ├── app1│ │ └── main.go│ └── app2│ └── main.go├── go.mod├── go.sum├── internal│ ├── pkga│ │ └── pkg_a.go│ └── pkgb│ └── pkg_b.go├── pkg1│ └── pkg1.go├── pkg2│ └── pkg2.go└── vendorcmd可执行文件目录,如果一个我的项目有多个可执行文件,能够放在不同的子目录中,如下面例子中的app1和app2目录。 internal我的项目外部公有代码,其余我的项目引入时会报错。 pkgN:留神,这里并不是说肯定要pkg为前缀来命名,你能够对取任意合乎包命名标准的名称,比方service,model等即下面示例中的pkg1与pkg2,pkgN包与internal包一样寄存我的项目的依赖代码,但存储在这里的代码,能够被其我的项目引入。 vendor这个目录用于存储我的项目的依赖包,但因为现今Go我的项目都是应用go module进行依赖治理,因而这个目录是可省略的。 如果咱们的我的项目只有一个可执行文件,能够将main.go文件间接写在根目录,而vendor能够省略,因而,我的项目目录能够进一步精简为: demo├── go.mod├── go.sum├── internal│ ├── pkga│ │ └── pkg_a.go│ └── pkgb│ └── pkg_b.go├── main.go├── pkg1│ └── pkg1.go└── pkg2 └── pkg2.go库我的项目库我的项目不须要可执行文件,相比于利用我的项目,省略了cmd目录,其规范目录下: ...

May 5, 2022 · 1 min · jiezi

关于golang:Go-并发模型详解

go语言的并发模型     无论语言层面的哪种并发模型,到了操作系统层面肯定是以线程的状态存在的,操作系统依据资源拜访权限的不同,体系架构能够分为用户空间(不能够间接调用系统资源,必须通过零碎调用,函数库,shell脚本等调用内核空间的资源)和内核空间(次要拜访CPU资源,内存资源,io等硬件资源,为上一层的应用程序提供根底资源)。    咱们当初计算机语言比方Java,Go外面的线程指的都是用户空间里的线程,和操作系统内核空间的线程是不同的,Go语言的并发模型底层是由操作系统提供的线程库来撑持的,所以上面就先从线程模型说起。 线程模型    线程能够看做过程中的控制流,一个过程至多蕴含一个线程,因为在过程执行的时候,至多会有一个控制流继续执行。    所以一个过程的第一个线程会随着一个过程的启动而创立,这个线程常常被叫做主线程。一个过程中能够蕴含多个线程,这些线程是被曾经存在的线程创立进去的。另一方面,线程不能够独立于过程存在,线程的生命周期不可逾越过程的生命周期。    领有更多个线程的过程就能够并发的执行多个工作,并且就算某一个工作被阻塞,其余工作也能够失常运行,这样就能够大大改善程序的响应工夫和吞吐量。    线程的实现模型次要有3个,未完待续。。。 参考资源:bilibili

May 5, 2022 · 1 min · jiezi

关于golang:微服务从代码到k8s部署应有尽有系列十一日志收集

咱们用一个系列来解说从需要到上线、从代码到k8s部署、从日志到监控等各个方面的微服务残缺实际。 整个我的项目应用了go-zero开发的微服务,根本蕴含了go-zero以及相干go-zero作者开发的一些中间件,所用到的技术栈根本是go-zero项目组的自研组件,根本是go-zero全家桶了。 实战我的项目地址:https://github.com/Mikaelemmm... 序言在介绍之前,我先说一下整体思路,如果你的业务日志量不是特地大恰好你又应用的是云服务,那你间接应用云服务日志就能够了,比方阿里云的SLS,根本就是点点鼠标配置几步就能够将你的日志收集到阿里云的SLS外面了,间接就能够在阿里云中查看收集上来的日志了,感觉也没必要折腾。 如果你的日志量比拟大,那就能够上日志零碎了。 1、日志零碎咱们将业务日志打印到console、file之后,市面上比拟罕用的形式是elk、efk等基本思路一样,咱们拿常说的elk来举例,基本思路就是logstash收集过滤到elasticsearch中,而后kibana出现 然而logstash自身是应用java开发的,占用资源是真滴高,咱们用go做业务,自身除了快就是占用资源少构建块,当初在搞个logstash浪费资源,那咱们应用go-stash代替logstash,go-stash是go-zero官网本人开发的并且在线上通过长期大量实际的,然而它不负责收集日志,只负责过滤收集上来信息。 go-stash: https://github.com/kevwan/go-... 2、架构计划 filebeat收集咱们的业务日志,而后将日志输入到kafka中作为缓冲,go-stash获取kafka中日志依据配置过滤字段,而后将过滤后的字段输入到elasticsearch中,最初由kibana负责出现日志 3、实现计划在上一节错误处理中,咱们能够看到曾经将咱们想要的谬误日志打印到了console控制台中了,当初咱们只须要做后续收集即可 3.1 kafka#音讯队列kafka: image: wurstmeister/kafka container_name: kafka ports: - 9092:9092 environment: KAFKA_ADVERTISED_HOST_NAME: kafka KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181 TZ: Asia/Shanghai restart: always volumes: - /var/run/docker.sock:/var/run/docker.sock networks: - looklook_net depends_on: - zookeeper先配置好kafka、zookeeper 而后咱们进入kafka中先创立好filebeat收集日志到kafka的topic 进入kafka容器 $ docker exec -it kafka /bin/sh批改kafka监听配置(或者你把配置文件挂载到物理机在批改也能够) $ vi /opt/kafka/config/server.propertieslisteners=PLAINTEXT://kafka:9092 # 原文件中,要加kafka listeners=PLAINTEXT://:9092advertised.listeners=PLAINTEXT://kafka:9092 #源文件中,要加kafka advertised.listeners=PLAINTEXT://:9092创立topic $ cd /opt/kafka/bin$ ./kafka-topics.sh --create --zookeeper zookeeper:2181 --replication-factor 1 -partitions 1 --topic looklook-log3.2 filebeat在我的项目根目录下 docker-compose-env.yml文件中能够看到咱们配置了filebeat ...

May 5, 2022 · 2 min · jiezi

关于golang:gomicro集成链路跟踪的方法和中间件原理

前几天有个同学想理解下如何在go-micro中做链路跟踪,这几天正好看到wrapper这块,wrapper这个货色在某些框架中也称为中间件,里边有个opentracing的插件,正好用来做链路追踪。opentracing是个标准,还须要搭配一个具体的实现,比方zipkin、jeager等,这里抉择zipkin。 链路跟踪实战装置zipkin通过docker疾速启动一个zipkin服务端: docker run -d -p 9411:9411 openzipkin/zipkin程序结构为了不便演示,这里把客户端和服务端放到了一个我的项目中,程序的目录构造是这样的: main.go 服务端程序。client/main.go 客户端程序。config/config.go 程序用到的一些配置,比方服务的名称和监听端口、zipkin的拜访地址等。zipkin/ot-zipkin.go opentracing和zipkin相干的函数。装置依赖包须要装置go-micro、opentracing、zipkin相干的包: go get go-micro.dev/v4@latestgo get github.com/go-micro/plugins/v4/wrapper/trace/opentracinggo get -u github.com/openzipkin-contrib/zipkin-go-opentracing编写服务端首先定义一个服务端业务处理程序: type Hello struct {}func (h *Hello) Say(ctx context.Context, name *string, resp *string) error { *resp = "Hello " + *name return nil}这个程序只有一个办法Say,输出name,返回 "Hello " + name。 而后应用go-micro编写服务端框架程序: func main() { tracer := zipkin.GetTracer(config.SERVICE_NAME, config.SERVICE_HOST) defer zipkin.Close() tracerHandler := opentracing.NewHandlerWrapper(tracer) service := micro.NewService( micro.Name(config.SERVICE_NAME), micro.Address(config.SERVICE_HOST), micro.WrapHandler(tracerHandler), ) service.Init() micro.RegisterHandler(service.Server(), &Hello{}) if err := service.Run(); err != nil { log.Println(err) }}这里NewService的时候除了指定服务的名称和拜访地址,还通过micro.WrapHandler设置了一个用于链路跟踪的HandlerWrapper。 ...

May 5, 2022 · 4 min · jiezi

关于golang:什么是-Golang-中的反射通俗易懂

对于 GO 反射很多人对这块十分含糊,而官网的介绍又太业余,用简略的话形容一下对于反射(reflect )在 Golang 中文规范库中是这样介绍的 reflect包实现了运行时反射,容许程序操作任意类型的对象。典型用法是用动态类型interface{}保留一个值,通过调用TypeOf获取其动静类型信息,该函数返回一个Type类型值。调用ValueOf函数返回一个Value类型值,该值代表运行时的数据。Zero承受一个Type类型参数并返回一个代表该类型零值的Value类型值。抽取进去咱们看到以下几点: 1. 运行时,容许程序操作任意类型的对象 失常状况下,咱们操作一个构造体或者对象里的字段或办法时,咱们都须要率先晓得咱们要操作的构造体和对象是什么类型,只有晓得是什么类型后能力调用。尽管咱们能够对拿到的数据进行类型转换,然而在类型转换的过程中,不仍然还是指定了要转成的类型吗,因而反射就是在程序运行的过程中,能够对一个未知类型的数据进行操作的过程。一句话来说就是啥也不晓得,但就是要用 2. interface{}保留一个值 既然要用,那么如何应用呢?解释外面说的很分明,通过 interface{} 空接口存储要解决未知类型的数据,那为什么要用 interface{} 来接管呢,答案是因为 Golang 中的空接口没有定义任何办法,任何类型变量都实现空接口,因而用空接口能够示意任意数据类型 3. ValueOf,TypeOf函数 通过调用下面俩个函数,就能够取得 Value 类型值和 Type 类型值,Value 代表这个未知类型外面的数据,能够通过函数对数据进行操作,Type 代表这个未知类型代表着数据类型,比方 int、string、指针、构造体 Student 等。对于反射的具体落实,次要用于框架的开发,而个别开发中绝对较少,毕竟在框架中对于操作的数据很多状况下都是未知的。 以上就是集体对反射的了解,能力无限,如有谬误请留言 若你了解了文章且有帮忙,无妨点个赞,是对作者的认可,谢谢

May 4, 2022 · 1 min · jiezi

关于golang:Go语言实现手机与电脑windows无线文件快速互传方案跨平台无需安装客户端

【百灵快传】是一款应用局域网进行文件互传的软件,软件是开源的,互传文件是不会走服务器的,齐全通过局域网进行传输。 【百灵快传】 无需装置,只有一个exe可执行文件,实际上这个可执行文件也只是一个一键启动服务器的快捷启动器,无需装置手机客户端,因为是web页面进行传输的,只须要手机和电脑连贯同一个wifi即可,如果电脑是网线连贯的,那么手机连贯的wifi必须是与电脑网线同一个路由器的。 应用办法1、下载b0pass_win32.exe2、双击b0pass_win32.exe3、b0pass_win32.exe会主动帮你开启本地http服务器4、手机和电脑连贯同一个wifi5、手机扫码即可拜访网页,而后就能够进行互传文件。 留神扫码拜访须要扫描局域网的ip地址那个二维码,而不是127.0.0.1的二维码。软件下载地址:https://likeyun.lanzout.com/i...软件开源地址:https://github.com/bitepeng/b...

May 4, 2022 · 1 min · jiezi

关于golang:注意Go-118版本iota的bug

Iotaiota是Go语言的预申明标识符,用于常量的申明。 iota的值是const语句块里的行索引,值从0开始,每次递减少1。通过上面的代码示例咱们先回顾下iota的个性。 const ( c0 = iota // c0 == 0 c1 = iota // c1 == 1 c2 = iota // c2 == 2)const ( a = 1 << iota // a == 1 (iota == 0) b = 1 << iota // b == 2 (iota == 1) c = 3 // c == 3 (iota == 2, unused) d = 1 << iota // d == 8 (iota == 3))const ( u = iota * 42 // u == 0 (untyped integer constant) v float64 = iota * 42 // v == 42.0 (float64 constant) w = iota * 42 // w == 84 (untyped integer constant))const x = iota // x == 0const y = iota // y == 0const ( class1 = 0 class2 // class2 = 0 class3 = iota //iota is 2, so class3 = 2 class4 // class4 = 3 class5 = "abc" class6 // class6 = "abc" class7 = iota // class7 is 6)Bug2022年3月15日,Go官网团队正式公布了Go 1.18版本。Go 1.18是Go语言诞生以来变动最大的版本,引入了泛型、Fuzzing、工作区模式等泛滥新性能和性能优化。 ...

May 3, 2022 · 2 min · jiezi

关于golang:gozero源码阅读服务部署

服务部署生产环境搭建docker & k8s搭建具体请看我的下一篇文章 git公有仓库 & 容器公有仓库 & CI、DI具体请看我的下一篇文章 配置文件编写 & 生成dockerfile咱们先用网关局部代码来演示 执行代码./cmd.sh gen dockerfile gateway生成文件 code/service/gateway/api/Dockerfile### 加载根底镜像FROM golang:alpine AS builderLABEL stage=gobuilderENV CGO_ENABLED 0ENV GOOS linux### 设置 go module 代理ENV GOPROXY https://goproxy.cn,directWORKDIR /build/zero### 下载依赖文件ADD go.mod .ADD go.sum .RUN go mod downloadCOPY . .COPY service/gateway/api/etc /app/etc### 编译源代码RUN go build -ldflags="-s -w" -o /app/gateway service/gateway/api/gateway.go### 生成docker镜像FROM alpineRUN apk update --no-cache && apk add --no-cache ca-certificates tzdataENV TZ Asia/ShanghaiWORKDIR /appCOPY --from=builder /app/gateway /app/gatewayCOPY --from=builder /app/etc /app/etcCMD ["./gateway", "-f", "etc/gateway.prod.yaml"]从生成的Dockerfile能够看到次要有两个局部 ...

May 3, 2022 · 3 min · jiezi

关于golang:Golang内存管理详解

根底存储金字塔CPU寄存器CPU Cache:三级Cache别离是L1、L2、L3,L1最快,L3最慢内存硬盘等辅助存储设备鼠标等外接设备从上至下的访问速度越来越慢,拜访工夫越来越长。 虚拟内存拜访内存,理论拜访的是虚拟内存,虚拟内存通过页表查看,以后要拜访的虚拟内存地址,是否曾经加载到了物理内存。如果曾经在物理内存,则取物理内存数据,如果没有对应的物理内存,则从磁盘加载数据到物理内存,并把物理内存地址和虚拟内存地址更新到页表。 物理内存就是磁盘存储缓存层,在没有虚拟内存的时代,物理内存对所有过程是共享的,多过程同时拜访同一个物理内存会存在并发问题。而引入虚拟内存后,每个过程都有各自的虚拟内存,内存的并发拜访问题的粒度从多过程级别,能够升高到多线程级别。 栈和堆代码中应用的内存地址都是虚拟内存地址,而不是理论的物理内存地址。栈和堆只是虚拟内存上2块不同性能的内存区域: 栈在高地址,从高地址向低地址增长堆在低地址,从低地址向高地址增长栈和堆相比有这么几个益处: 栈的内存治理简略,调配比堆上快。栈的内存不须要回收,而堆须要进行回收,无论是被动free,还是被动的垃圾回收,这都须要破费额定的CPU。栈上的内存有更好的局部性,堆上内存拜访就不那么敌对了,CPU拜访的2块数据可能在不同的页上,CPU拜访数据的工夫可能就下来了。内存分区Golang内存分区:代码区、数据区、堆区、栈区// 低地址 ——----------------------------------------------------------------》 高地址// 代码区 | 数据区(初始化数据区,未初始化数据区,常量区) | 堆区 | 栈区(函数信息,外部变量)// 函数地址(0x7c7620):代码区。是一个低地址地位,计算机指令// 全局变量(0xd03250) :初始化数据区,如果初始化了:初始化数据;未初始化:未初始化数据// 局部变量(0xc0000120b0):栈区,高地址// 堆区:一个很大的空间,在应用时,开拓内存空间,完结时,开释内存空间。// 栈区:用来存储程序执行过程中函数外部定义的信息和局部变量值。最内层函数后进先出,最内层函数先执行后,开释内存,向下层传递后果。 函数return返回值将函数执行的后果保留下来,返回给调用者。 变量局部变量 在C语言中写在{}中或者函数中或者函数的形参, 就是局部变量Go语言中的局部变量和C语言一样全局变量 在C语言中写在函数里面的就是全局变量Go语言中的全局变量和C语言一样局部变量和全局变量的作用域 在C语言中局部变量的作用域是从定义的那一行开始, 直到遇到 } 完结或者遇到return为止Go语言中局部变量的作用域和C语言一样在C语言中全局变量的作用域是从定义的那一行开始, 直到文件开端为止Go语言中的全局变量, 只有定义了, 在定义之前和定义之后都能够应用局部变量和全局变量的生命周期 在C语言中局部变量, 只有执行了才会调配存储空间, 只有来到作用域就会主动开释, C语言的局部变量存储在栈区Go语言局部变量的生命周期和C语言一样在C语言中全局变量, 只有程序一启动就会调配存储空间, 只有程序敞开才会开释存储空间, C语言的全局变量存储在动态区(数据区)Go语言全局变量的生命周期和C语言一样局部变量和全局变量的留神点 在C语言中雷同的作用域内, 不能呈现同名的局部变量Go语言和C语言一样, 雷同干的作用域内, 不能呈现同名的局部变量package mainimport "fmt"func main() { var num int; // 局部变量 //var num int; // 报错,不能呈现同名局部变量}在C语言中雷同的作用域内, 能够呈现同名的全局变量在Go语言中雷同的作用域内, 不能呈现同名的全局变量例:package mainimport "fmt"var value int // 全局变量//var value int // 报错,不能呈现同名全局变量func main() {}非凡点 ...

May 2, 2022 · 2 min · jiezi

关于golang:一文读懂原子操作内存屏障锁偏向锁轻量级锁重量级锁自旋锁

[]()背景 []()在做了9年前端之后,自我感在此畛域曾经没有晋升空间,同时市场行情绝对较差,不如趁着这个工夫补充下后端系列技术,被裁之后也好接个私活不至于饿死。学两周Go,如盲人摸象般不知重点。那么重点谁晓得呢?必定是应用Go的后端工程师,那便利用业余时间找了几个老哥对练一下。其中一位问道在利用多个goroutine发送申请拿到后果之后如果进行销毁。 []()有句话叫做老成持重天下无敌,再练三年举步维艰。本着不服输精力回来钻研了一下这个问题,很简略须要应用Go提供的Context,api应用起来也很简略,然而我一贯喜爱刨根问底,于是乎钻研Context源码发现互斥锁(Mutex)、原子操作(atomic),钻研atomic发现CAS,钻研CAS发现了java的自旋锁、偏差锁、轻量级锁、重量级锁,钻研锁发现Disruptor,钻研Disruptor发现CPU伪共享、MESI协定、内存屏障。 []()所以这篇文章会自下向上的解说,先讲CPU硬件设计带来的劣势以及带来的问题(多核并发抵触、伪共享),从底层上了解并发问题存在的根因,而后解说原子操作与CAS,之后解说操作系统为解决并发问题的锁机制、信号量,而后介绍下高并发框架Disruptor利用这些机制的高性能实现,最初回到Go中的atomic.Value以及建设在aotmic.Value和Mutex之上的Context,最初的最初答复下那位老哥问题,怎么应用Context来做goroutine的调度。 []()并发与并行 []()并行是并发的一个子集;并发(ConcurrentMode)强调的是从任务调度角度来看,同时安顿多个是工作;工作能够穿插着执行,并不一定是同一时刻在同时进行;并行(Parallel)是从理论执行角度来看真的有多个工作在同一时刻同时执行。 []()古代CPU多半是多核设计,所以我了解会存在以多核并行的形式进行高并发运行。当然单核单线程仍然存在并行,它下面也存在真正的“并行”。只不过,这个并行并不是CPU外部的、线程之间的并行;而是CPU执行程序的同时,DMA控制器也在执行着网络报文收发、磁盘读写、音视频播放/录制等等工作。 []()不像js这种单线程异步的语言,向java、Go语法上看起来都是以多线程同步阻塞的模式运行,个别多个申请来时,都是开启一个线程池利用多线程的形式进行解决。得益于CPU多核的劣势,能够疾速并行运行申请工作,然而同样因为这个起因会人造的引起多线程拜访数据抵触。 []()  []()硬件底层起因 []()并发抵触 []()并发资源抵触的底层起因与CPU设计无关,先看CPU的缓存构造 []()能够看到i7-8700k是6核,右下角L1、L2都6x多少kb,能够看到L1、L2缓存是各个外围独有的,L3是共用的,读取数据时会先从内存中把数据和指令读到本人的L1缓存中,这就能够晓得当两个线程拜访同一个数据时,CPU角度他们其实在各自外围中都是独有的。 []()同样在写的时候,CPU为了节俭跟内存通信带来的性能开销,也并不一定是程序更新后立刻放到内存中。而是有两种策略:写中转和写回。写中转比较简单,都是每次写入到内存中,性能开销大。而写回是当L1数据缓存中的变量A被更改后不立刻写回内存,只是做一个脏标记,这样屡次写同一个变量A能够始终在L1中解决,直到这个缓存地位的A不在解决了,须要A腾出中央给另一个变量B时,才把A的数据写到内存中,这样晋升缓存命中率,缩小与内存的通信晋升性能。 []()能够设想的是,这种高性能在多核并发运行时,两个外围都读到了变量A值为0,外围1上运行的线程T1把A改成了10,但没有写到内存中,也没有告诉外围2,它的L1缓存中A还是0,这时候外围2的线程T2把A改成了20;当他们都往内存写的时候,必然呈现抵触。这就是在单机上多线程并发引起的资源抵触的底层起因,也是后续各种原子操作、锁、信号量等机制要解决的问题。推广到宏观业务场景是一样的,两个服务为了性能优化,先把数,0读到本人的内存中,一个改成了1,一个改成了2,而数据库看到的还是0,这时候两个服务都往数据库写,就会产生抵触。 []()  []()原子操作 []()为了解决这个问题,晚期科学家走了很多弯路,花了好多年才找到解决软件和硬件的解决方案。首先在CPU硬件层面,有两个伎俩:写流传和MESI协定。 []()写流传的计划就是当某个外围更新了Cache数据后,要把事件播送告诉到其余外围,然而并不能保障时序问题。比方外围1更改了A为100,外围2更改了A为200,这两个是在同一时间产生的,更改之后他们别离告诉对方,那么在外围1看来A是先变成了100而后又被改成了200,外围2看来A是先被改成了200,而后有收到告诉要改成100;当他们往内存写的时候还是存在抵触。 []()解决这个问题就是要在CPU硬件上做到事务的串行化,MESI就是解决这个问题。 []()MESI 协定其实是 4 个状态单词的结尾字母缩写,别离是: []()Modified,已批改[]()Exclusive,独占[]()Shared,共享[]()Invalidated,已生效[]()这四个状态来标记 Cache Line 四个不同的状态。 []()「已批改」状态就是咱们后面提到的脏标记,代表该 Cache Block 上的数据曾经被更新过,然而还没有写到内存里。而「已生效」状态,示意的是这个 Cache Block 里的数据曾经生效了,不能够读取该状态的数据。 []()「独占」和「共享」状态都代表 Cache Block 里的数据是洁净的,也就是说,这个时候 Cache Block 里的数据和内存外面的数据是一致性的。 []()「独占」和「共享」的差异在于,独占状态的时候,数据只存储在一个 CPU 外围的 Cache 里,而其余 CPU 外围的 Cache 没有该数据。这个时候,如果要向独占的 Cache 写数据,就能够间接自在地写入,而不须要告诉其余 CPU 外围,因为只有你这有这个数据,就不存在缓存一致性的问题了,于是就能够轻易操作该数据。 []()另外,在「独占」状态下的数据,如果有其余外围从内存读取了雷同的数据到各自的 Cache ,那么这个时候,独占状态下的数据就会变成共享状态。 []()那么,「共享」状态代表着雷同的数据在多个 CPU 外围的 Cache 里都有,所以当咱们要更新 Cache 外面的数据的时候,不能间接批改,而是要先向所有的其余 CPU 外围播送一个申请,要求先把其余外围的 Cache 中对应的 Cache Line 标记为「有效」状态,而后再更新以后 Cache 外面的数据。 ...

May 2, 2022 · 3 min · jiezi

关于golang:go-链路追踪之-jaeger

docker下进装置jaeger docker run -d -p 9411:9411 openzipkin/zipkindocker run -d -p 5775:5775/udp -p 16686:16686 -p 14250:14250 -p 14268:14268 jaegertracing/all-in-one:latest同一个过程中进行追踪 package mainimport ( "context" "fmt" "github.com/opentracing/opentracing-go" "github.com/uber/jaeger-client-go" "github.com/uber/jaeger-client-go/config" "io" "time")func main() { tracer, closer := initJaeger("jaeger-demo") //初始化 defer closer.Close() opentracing.SetGlobalTracer(tracer) //StartspanFromContext创立新span时会用到 span := tracer.StartSpan("span_root") //开始监控 //ContextWithSpan来创立一个新的ctx,将span的信息与context关联,传到foo3中时,须要创立一个子span,父span是ctx中的span。 //用StartSpanFromContext来模仿从context中启动一个子span,然而StartSpanFromContext或者SpanFromContext只能在同一个服务内应用, //grpc中client的context和server的context并不是同一个context,无奈应用这两个函数。 ctx := opentracing.ContextWithSpan(context.Background(), span) r1 := foo3("Hello foo3", ctx) r2 := foo4("Hello foo4", ctx) fmt.Println(r1, r2) span.Finish() //完结监控}func foo3(req string, ctx context.Context) (reply string) { //1.创立子span span, _ := opentracing.StartSpanFromContext(ctx, "span_foo3") defer func() { //4.接口调用完,在tag中设置request和reply span.SetTag("request", req) span.SetTag("reply", reply) span.Finish() }() println(req) //2.模仿解决耗时 time.Sleep(time.Second / 2) //3.返回reply reply = "foo3Reply" return}//跟foo3一样逻辑func foo4(req string, ctx context.Context) (reply string) { span, _ := opentracing.StartSpanFromContext(ctx, "span_foo4") defer func() { span.SetTag("request", req) span.SetTag("reply", reply) span.Finish() }() println(req) time.Sleep(time.Second / 2) reply = "foo4Reply" return}//初始化jaeger tracer的initJaeger办法func initJaeger(serviceName string) (opentracing.Tracer, io.Closer) { cfg := &config.Configuration{ Sampler: &config.SamplerConfig{ Type: "const", Param: 1, //设置采样率 1 }, // reporter中配置jaeger Agent的ip与端口,以便将tracer的信息公布到agent中。 // LocalAgentHostPort参数为127.0.0.1:6381,6381接口是承受压缩格局的thrift协定数据。 Reporter: &config.ReporterConfig{ LogSpans: true, LocalAgentHostPort: "127.0.0.1:6831", }, ServiceName: serviceName, } tracer, closer, err := cfg.NewTracer(config.Logger(jaeger.StdLogger)) if err != nil { panic(fmt.Sprintf("ERROR: cannot init Jaeger: %v\n", err)) } return tracer, closer}跨过程追踪:Client: ...

May 2, 2022 · 4 min · jiezi

关于golang:Go微服务入门到容器化实践落地可观测的微服务电商项目资料齐全

Go微服务入门到容器化实际,落地可观测的微服务电商我的项目超清原画 残缺无密 包含所有视频课件以及源码 MP4格局 获取材料:网盘链接实现一个 Code Pen:(一)我的项目初始化前言前段时间掘金上线了一个新功能 Code pen,可能在线写代码,阅读器就可能运行预览,在文章中就可能插入代码片段,对 web 开发者大有裨益,非常便利读者对文章的理解,笔者对这种在线实时编辑的功能充满了好奇,所以打算开发一个繁难的 Code pen。 技术栈Next.jsTailwindcssUniapp 云数据库初始化我的项目使用以下命令初始化一个 next 我的项目 npx create-next-app next-code-pencd next-code-pen复制代码安装 tailwindcss 相干包,初始化 tailwind.config.js npm install -D tailwindcss postcss autoprefixernpx tailwindcss init -p复制代码修改 tailwind.config.js 配置,将代码移动到src目录下,这个是我的集体偏好 module.exports = { content: [ "./src/**/*.{js,ts,jsx,tsx}"], theme: { extend: {},}, plugins: [],}复制代码页面结构用 Tailwind 来实现一个页面 SVG 实现 LOGO有个好的 logo 才能够开始一个好的我的项目 <div className="flex justify-center items-center h-16 w-28"> <svgclassName="w-10 h-10"viewBox="0 0 1024 1024"version="1.1"xmlns="http://www.w3.org/2000/svg"><path d="M512 341.33333336c0-94.4 76.8-171.2 171.2-171.2 94.4 0 171.2 76.8 171.2 171.2s-76.8 171.2-171.2 171.2C588.8 512.53333336 512 435.73333336 512 341.33333336z" fill="#FF3C41"></path><path d="M171.2 682.13333336c0-94.4 76.8-171.2 171.2-171.2H512v171.2C512 776.53333336 435.2 853.33333336 340.8 853.33333336s-169.6-76.8-169.6-171.2z" fill="#0EBEFF"></path><path d="M171.2 341.33333336c0 94.4 76.8 171.2 171.2 171.2H512V170.13333336H340.8c-94.4 0-169.6 76.8-169.6 171.2z" fill="#FCD000"></path><text fill="#fff" x="520" y="860" fontFamily="Verdana" fontSize="460"> +</text></svg><span className="ml2 text-gray-50">CODE</span></div>复制代码这个 logo 部分起源figma,前面再加一个+,意味着后咱们可能从它开始一些五彩斑斓的我的项目。 ...

May 1, 2022 · 1 min · jiezi

关于golang:Go高级工程师实战营资料齐全

Go高级工程师实战营资料齐全超清原画 残缺无密 包含所有视频课件以及源码 MP4格局 获取材料:网盘链接实现一个 Code Pen:(一)我的项目初始化前言前段时间掘金上线了一个新功能 Code pen,可能在线写代码,阅读器就可能运行预览,在文章中就可能插入代码片段,对 web 开发者大有裨益,非常便利读者对文章的理解,笔者对这种在线实时编辑的功能充满了好奇,所以打算开发一个繁难的 Code pen。 技术栈Next.jsTailwindcssUniapp 云数据库初始化我的项目使用以下命令初始化一个 next 我的项目 npx create-next-app next-code-pencd next-code-pen复制代码安装 tailwindcss 相干包,初始化 tailwind.config.js npm install -D tailwindcss postcss autoprefixernpx tailwindcss init -p复制代码修改 tailwind.config.js 配置,将代码移动到src目录下,这个是我的集体偏好 module.exports = { content: [ "./src/**/*.{js,ts,jsx,tsx}"], theme: { extend: {},}, plugins: [],}复制代码页面结构用 Tailwind 来实现一个页面 SVG 实现 LOGO有个好的 logo 才能够开始一个好的我的项目 <div className="flex justify-center items-center h-16 w-28"> <svgclassName="w-10 h-10"viewBox="0 0 1024 1024"version="1.1"xmlns="http://www.w3.org/2000/svg"><path d="M512 341.33333336c0-94.4 76.8-171.2 171.2-171.2 94.4 0 171.2 76.8 171.2 171.2s-76.8 171.2-171.2 171.2C588.8 512.53333336 512 435.73333336 512 341.33333336z" fill="#FF3C41"></path><path d="M171.2 682.13333336c0-94.4 76.8-171.2 171.2-171.2H512v171.2C512 776.53333336 435.2 853.33333336 340.8 853.33333336s-169.6-76.8-169.6-171.2z" fill="#0EBEFF"></path><path d="M171.2 341.33333336c0 94.4 76.8 171.2 171.2 171.2H512V170.13333336H340.8c-94.4 0-169.6 76.8-169.6 171.2z" fill="#FCD000"></path><text fill="#fff" x="520" y="860" fontFamily="Verdana" fontSize="460"> +</text></svg><span className="ml2 text-gray-50">CODE</span></div>复制代码这个 logo 部分起源figma,前面再加一个+,意味着后咱们可能从它开始一些五彩斑斓的我的项目。 ...

May 1, 2022 · 1 min · jiezi

关于golang:微服务监控

微服务的可观测性大抵分为三大类:Metrics,Tracing 和 Logging。 Metrics 监控侧重于技术指标的收集与观测,如服务调用 QPS、响应工夫、错误率和资源使用率 Logging 侧重于运行日志的采集、存储与检索,通常应用ELK Tracing 则偏差于调用链的串联、追踪与 APM 剖析,服务与服务之间的调用耗时等等,链路追踪。 一、对grpc服务进行监控服务端 const ( serviceName = "simple_zipkin_server" zipkinAddr = "http://127.0.0.1:9411/api/v1/spans" zipkinRecorderAddr = "127.0.0.1:9000")/* 获取zipkin的tracer对象,并把tracer封装成ServerInterceptor注入到grpc.ServerOption中*/func NewZipkinTracer(opts []grpc.ServerOption) (opentracing.Tracer, []grpc.ServerOption, error) { collector, err := zipkin.NewHTTPCollector(zipkinAddr) if err != nil { log.Fatal(err.Error()) return nil, opts, err } recorder := zipkin.NewRecorder(collector, true, zipkinRecorderAddr, serviceName) tracer, err := zipkin.NewTracer(recorder, zipkin.ClientServerSameSpan(true), zipkin.TraceID128Bit(true)) if err != nil { log.Fatal(err.Error()) return nil, opts, err } opts = append(opts, grpc_middleware.WithUnaryServerChain( otgrpc.OpenTracingServerInterceptor(tracer, otgrpc.LogPayloads()), )) return tracer, opts, nil}func main() { //... go func() { //... var opts = []grpc.ServerOption{grpc.Creds(cred)} _, opts, err = NewZipkinTracer(opts) if err != nil { log.Fatal(err.Error()) return } rpcserver := grpc.NewServer(opts...) //... } //...}客户端 ...

May 1, 2022 · 2 min · jiezi

关于golang:Go-并发编程

总述Go依附独特的轻量级的协程 轻松创立上百万个工作不会导致系统的资源衰竭 (相比起来过程和线程最多也不会超过1w个) 接下来次要从这几个方面开展: 什么是并发编程Go如何实现并发并发的原理协程之间通信依附的chanel同步异步数据安全并发看起来同时 并行真正同时 留神: 并行肯定是多核的并行看上去很牛逼 然而因为其工作之间的通信老本很高 导致其成果不肯定好过程(process)每一个独立进行的程序就是过程,是程序中的一次动静执行过程,可与了解为正在执行的程序,是系统资源调度和调配的根本单位,领有独立的内存单元,须要经验从代码加载,执行,到最初执行结束。多过程的零碎能够同时运行多个程序,因为CPU有本人的分时机制,所以每个程序都能够获取到本人的工夫片,然而过程之间的信息交互比拟麻烦,另外过程的创立,撤销,切换的开销大。 线程(thread)线程是一个根本的CPU单元,是CPU调度和调配的最小单位。同一个过程下的线程能够共享系统资源,线程之间的切换代价小于过程之间的切换 协程(goroutine)是一种用户态的轻量级线程,协程的调度齐全通过用户来管制,协程和子函数很相似,一个入口 一次返回 一旦退出 就是实现。 Goroutine未完待续

May 1, 2022 · 1 min · jiezi

关于golang:从零开始搭建GoLang语言开发环境

更多干货文章,更多最新文章,欢送到作者主博客 菜鸟厚非一、装置 GoLang1.1 下载首先拜访 https://go.dev/dl/ 下载 GoLang,下载实现后双击装置即可。如下: 1.2 装置GoLang 装置的目录肯定要记得,这目录前面配置 GOROOT环境变量要用。如下: 期待装置,呈现如下界面,即是装置实现。如下: 1.3 检测检测 GoLang 是否装置实现,在装置目录下的 bin 文件夹内,执行 go version。如下: 二、配置环境变量装置实现后,须要配置环境变量,便于咱们开发。须要配置的环境变量有 GOROOT、Path、GOPATH 环境变量阐明GOROOT指定 SDK 装置的门路,也就是 GoLang 装置门路,本文是 C:\Program Files\GoPath增加 SDK 的 Bin 目录,这样那里都能够执行 go 命令,本文是 C:\Program Files\Go\BinGOPATH工作区间,即当前开发我的项目的目录,本文是 D:\GoLang 其中蕴含 src bin pkg 子目录, pkg 存储了 go get xxx 的 package2.1 GOROOT 2.2 Path 2.3 GOPATH

April 30, 2022 · 1 min · jiezi

关于golang:Go-seek方法

seek办法seek办法的目标是为了给下一个光标的读数据或者写数据设置偏移量的,他有2个参数,第一个是偏移多少个字节,第二个参数是从哪里开始偏移。 定义type Seeker interface { Seek(offset int64, whence int) (int64, error)}具体怎么应用呢?举个例子:如果我有一个文件test.txt,外面的内容是hey it is liber,我想要从第五个字符i开始读取外面的数据,就能够这样写: package mainimport ( "fmt" "io" "log" "os")func main() { fileName := "/Users/liberhome/GolandProjects/awesomeProject/I-package/April28_Go_io/test.txt" //外面的内容是hey it is liber file, err := os.OpenFile(fileName, os.O_RDWR, os.ModePerm) //抉择OOpenFile, 模式抉择读写 if err != nil { log.Fatal(err) } defer file.Close() //读写 bs := []byte{0} //首先创立一个切片, 不必搞太大 一个字节就足够了 file.Read(bs) //这个时候read,会默认读取文件第一个字节h fmt.Println(string(bs)) //对了 插播一条Goland-mac的快捷键:shift+control(记住不是command)+R = 运行 //接下来 演示一下seek的用法 file.Seek(4, io.SeekStart) //从头开始偏移4个字符 file.Read(bs) //这个时候read,会默认读取文件第5个字节i fmt.Println(string(bs))}参数设置file.Seek(4, io.SeekStart) //这里的第二个参数 能够有以下选项 ...

April 30, 2022 · 1 min · jiezi

关于golang:Go-语言入门很简单nethttp-包

引言之前的文章学过把模板和视图拆散,建设一个 Web 服务器来展示 HTML 模板。咱们将学习如何应用 Go 的模板包创立动静 HTML 和文本文件。 建设 Web 服务器到目前为止,咱们始终在向终端输入模板,然而当咱们开始深入研究更多 HTML 时,这开始变得不那么有意义了。相同,咱们心愿可视化在 Web 浏览器中生成的 HTML。为此,咱们首先须要设置一个 Web 服务器来出现咱们的 HTML 模板。 package mainimport ( "html/template" "net/http")var testTemplate *template.Templatetype ViewData struct { Name string}func main() { var err error testTemplate, err = template.ParseFiles("hello.gohtml") if err != nil { panic(err) } http.HandleFunc("/", handler) http.ListenAndServe(":8000", nil)}func handler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/html") vd := ViewData{"Kyrie Jobs"} err := testTemplate.Execute(w, vd) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) }}创立一个名为 hello.gohtml 的文件并将以下内容增加到其中: ...

April 29, 2022 · 2 min · jiezi

关于golang:Golang-文件的复制-手动实现-与-库函数实现

手动实现思路就是:先创立一个切片 而后通过Read办法操作源文件把数据读到切片中,而后再通过Write办法把切片外面的数据写到指标文件外面。 具体怎么应用呢?举个例子:如果我有一个文件test.txt,而后我想要把这个文件复制到destination.txt,就能够这样写: package mainimport ( "fmt" "io" "os")func main() { // 拷贝文件 srcFile := "/Users/liberhome/GolandProjects/awesomeProject/I-package/April28_Go_io/test.txt" destFile := "destination.txt" total, err := copyFile1(srcFile, destFile) //函数的具体实现在上面 fmt.Println(total, err)}func copyFile1(srcFile, destFile string) (int, error) { //返回值是实现拷贝数据的字节数&err file1, err := os.Open(srcFile) //这里读 所以用open就足够了 if err != nil { return 0, err } file2, err := os.OpenFile(destFile, os.O_WRONLY|os.O_CREATE, os.ModePerm) //这里要写文件 所以须要用到OpenFile if err != nil { return 0, err } // 敞开文件 defer file1.Close() defer file2.Close() //读写文件 bs := make([]byte, 1024, 1024)//用make发明切片 n := -1 //每次读取的字节数 total := 0 //读取的总数量 for { n, err = file1.Read(bs) if err == io.EOF || n == 0 { fmt.Println("copy is complete") break } else if err != nil { fmt.Println("error happen") return total, err } total += n file2.Write(bs[:n]) } return total, nil}当然了,这段代码不仅能够复制txt文件,其余类型,比方png什么的都不在话下,毕竟是通过字节传递的。 ...

April 29, 2022 · 2 min · jiezi

关于golang:微服务从代码到k8s部署应有尽有系列十错误处理

咱们用一个系列来解说从需要到上线、从代码到k8s部署、从日志到监控等各个方面的微服务残缺实际。 整个我的项目应用了go-zero开发的微服务,根本蕴含了go-zero以及相干go-zero作者开发的一些中间件,所用到的技术栈根本是go-zero项目组的自研组件,根本是go-zero全家桶了。 实战我的项目地址:https://github.com/Mikaelemmm... 1、概述咱们在平时开发时候,程序在出错时,心愿能够通过谬误日志能疾速定位问题(那么传递进来的参数、包含堆栈信息必定就要都要打印到日志),但同时又想返回给前端用户比拟友善、能看得懂的谬误提醒,那这两点如果只通过一个fmt.Error、errors.new等返回一个错误信息必定是无奈做到的,除非在返回前端谬误提醒的中央同时在记录log,这样的话日志满天飞,代码难看不说,日志到时候也会很难看。 那么咱们想一下,如果有一个对立的中央记录日志,同时在业务代码中只须要一个return err 就能将返回给前端的谬误提示信息、日志记录置信信息离开提醒跟记录,如果依照这个思路实现,那几乎不要太爽,是的 go-zero-looklook就是这么解决的,接下来咱们看下。 2、rpc错误处理依照失常状况下,go-zero的rpc服务是基于grpc的,默认返回的谬误是grpc的status.Error 没法给咱们自定义的谬误合并,并且也不适宜咱们自定义的谬误,它的错误码、谬误类型都是定义死在grpc包中的,ok ,如果咱们在rpc中能用自定义谬误返回,而后在拦截器对立返回时候转成grpc的status.Error , 那么咱们rpc的err跟api的err是不是能够对立治理咱们本人的谬误了呢? 咱们看一下grpc的status.Error的code外面是什么 package codes // import "google.golang.org/grpc/codes"import ( "fmt" "strconv")// A Code is an unsigned 32-bit error code as defined in the gRPC spec.type Code uint32.......grpc的err对应的错误码其实就是一个uint32 , 咱们本人定义谬误用uint32而后在rpc的全局拦截器返回时候转成grpc的err,就能够了 所以咱们本人定义全局错误码在app/common/xerr errCode.go package xerr// 胜利返回const OK uint32 = 200// 前3位代表业务,后三位代表具体性能// 全局错误码const SERVER_COMMON_ERROR uint32 = 100001const REUQES_PARAM_ERROR uint32 = 100002const TOKEN_EXPIRE_ERROR uint32 = 100003const TOKEN_GENERATE_ERROR uint32 = 100004const DB_ERROR uint32 = 100005// 用户模块errMsg.go ...

April 29, 2022 · 4 min · jiezi

关于golang:MOSN-10-发布开启新架构演进

文|朱德江(花名:人德) 蚂蚁团体技术专家,负责 MOSN 外围开发,关注云原生流量网关的演进。 以下内容整顿自 SOFAStack 周围年的分享 MOSN 1.0 公布MOSN 是一款次要应用 Go 语言开发的云原生网络代理平台,由蚂蚁团体开源,通过双 11 大促几十万容器的生产级验证。 通过 4 年的蓬勃发展,在 11 位 commiter,100 多个 contributor 和整个社区的共同努力下,经验 27 个小版本的迭代,MOSN 1.0 版本正式公布了。 一个足够成熟稳固,有开源用户共建、有商业化落地、有社区,拥抱云原生生态的 MOSN 1.0 来了。 除了在蚂蚁团体的全面落地,MOSN 在业界也有较宽泛的利用,比方有工商银行的商业化落地,还有阿里云、去哪儿网、时速云等企业的生产实践。 同时,随着 1.0 的公布,进入少年期的 MOSN 也将开启新一代 MOE 架构演进,奔向星辰大海。 倒退历史MOSN 起源于 Service Mesh,本来在微服务之间的调用是通过比拟重的 SDK 来实现的,当 SDK 降级的时候,须要利用配合一起降级,有比拟强的打搅。 MOSN 为了解决这一痛点,向着把 SDK 剥离进去的方向演进。在利用所在的 Pod 内,独自有一个运行 MOSN 的 sidecar,那么利用自身只须要跟 MOSN 去通信,就能够实现整个的服务调用的流程。把 SDK 剥离进去,相当于 MOSN 作为一个独立的组件去演进,其演进过程对利用自身没有打搅。这在蚂蚁外部的收益其实是非常明显的。 在整个演进的过程中,有两个比拟深的领会:一个比拟显著的是,有一个独立的 sidecar,能够去跟业务逻辑做解耦;另外一个标准化,在云原生的时代里,管制面和数据面被拆分为两个独立的组件,MOSN 作为数据面的组件,演进过程中要跟很多管制面的服务对接,这期间标准化是一个很重要的。在整个标准化的过程中,它并不像业务解耦那么直观,然而用的工夫越长,对其越深有体会。 当初 MOSN 曾经在蚂蚁外部全面的铺开,部署有几十万 Pod,峰值 QPS 千万级。 ...

April 28, 2022 · 3 min · jiezi

关于golang:Golang-IO操作详解

Go语言的IO操作能够应用其io包里的API实现。 Reader接口type Reader interface { Read(p []byte) (n int, err, error) //参数是切片p;返回值n是理论读取到的字节数量,所以n最小是0最多不超过p的长度。如果读完了就会返回EOF}具体怎么应用呢?举个例子:如果我有一个文件test.txt,外面的内容是abcdefghijklmnopqrstuvwxyz,而后我想要用Reader接口读取外面的数据,就能够这样写: package mainimport ( "fmt" "os")func main() { // 读取本地test.txt文件可分为3个步骤 // 1.关上文件 fileName := "/Users/liberhome/GolandProjects/awesomeProject/test/test.txt" file, err := os.Open(fileName) if err != nil { fmt.Println(err) return } // 3.敞开文件 defer file.Close() // 2.读取数据 bs := make([]byte, 15, 15)// 先创立一个切片,长度和容量都为4(这能够依据你的需要扭转,罕用1024的倍数) n, err := file.Read(bs)// 第一次从读取数据 存到筹备好的切片外面 fmt.Println(err)//nil fmt.Println(n)//15 fmt.Println(string(bs))//abcdefghijklmno n, err = file.Read(bs)// 第2次读取数据 fmt.Println(err)//nil fmt.Println(n)//11 fmt.Println(string(bs))//pqrstuvwxyzlmno n, err = file.Read(bs)// 第3次读取数据 fmt.Println(err)//EOF fmt.Println(n)//0 fmt.Println(string(bs))//pqrstuvwxyzlmno}上述代码只是为了阐明每一一次读取的工作原理,平时应用的的时候个别会用循环来写。如下: ...

April 28, 2022 · 2 min · jiezi

关于golang:微服务从代码到k8s部署应有尽有系列九事务精讲

咱们用一个系列来解说从需要到上线、从代码到k8s部署、从日志到监控等各个方面的微服务残缺实际。 整个我的项目应用了go-zero开发的微服务,根本蕴含了go-zero以及相干go-zero作者开发的一些中间件,所用到的技术栈根本是go-zero项目组的自研组件,根本是go-zero全家桶了。 实战我的项目地址:https://github.com/Mikaelemmm... 对于分布式事务因为本我的项目服务划分绝对独立一些,所以目前没有应用到分布式事务,不过go-zero联合dtm应用分布式事务的最佳实际,我有整顿demo,这里就介绍一下go-zero联合dtm的应用,我的项目地址go-zero联合dtm最佳实际仓库地址 : https://github.com/Mikaelemmm... 【注】上面说的不是go-zero-looklook我的项目,是这个我的项目 https://github.com/Mikaelemmm... 一、注意事项go-zero 1.2.4版本以上,这个肯定要留神dtm 你用最新的就行了二、clone dtmgit clone https://github.com/yedf/dtm.git三、配置文件1、找到我的项目跟文件夹下的conf.sample.yml 2、cp conf.sample.yml conf.yml 3、应用etcd , 把配置中上面这段正文关上 (如果没用etcd就更简略了 ,这个都省了,间接链接到dtm server地址就能够了) MicroService: Driver: 'dtm-driver-gozero' # name of the driver to handle register/discover Target: 'etcd://localhost:2379/dtmservice' # register dtm server to this url EndPoint: 'localhost:36790'解释一下: MicroService 这个不要动,这个代表要对把dtm注册到那个微服务服务集群外面去,使微服务集群外部服务能够通过grpc间接跟dtm交互 Driver :'dtm-driver-gozero' , 应用go-zero的注册服务发现驱动,反对go-zero Target: 'etcd://localhost:2379/dtmservice' 将以后dtm的server间接注册到微服务所在的etcd集群中,如果go-zero作为微服务应用的话,就能够间接通过etcd拿到dtm的server grpc链接,间接就能够跟dtm server交互了 EndPoint: 'localhost:36790' , 代表的是dtm的server的连贯地址+端口 , 集群中的微服务能够间接通过etcd取得此地址跟dtm交互了, 如果你本人去改了dtm源码grpc端口,记得这里要改下端口 四、启动dtm server在dtm我的项目根目录下 go run app/main.go dev五、应用go-zero的grpc对接dtm这是一个疾速下单扣商品库存的例子 ...

April 28, 2022 · 7 min · jiezi

关于golang:Golang-file操作详解

文件相干信息能够用fileInfo这个API: package mainimport ( "fmt" "os")func main() { fileInfo, err := os.Stat("/Users/liberhome/GolandProjects/awesomeProject/test/test.txt") if err != nil { fmt.Println("err: ", err) return } // 文件类型 fmt.Printf("%T\n", fileInfo) // 文件名 fmt.Println(fileInfo.Name()) // size fmt.Println(fileInfo.Size()) // isdir fmt.Println(fileInfo.IsDir()) // Mode fmt.Println(fileInfo.Mode())}文件权限权限的示意能够用符号 or 8进制示意 符号示意第一个是类型,如果是文件 用-示意 目录用d示意前面三个别离代表以后利用具备的权限、以后利用所在的组、其他人的权限 权限个别分为3种:r (可读) w(可写) x(可执行) 没有哪一个权限就用-代替示意-rwxr-xr-x 首先第一个-表明了这是一个文件前面的rwx表明了这个文件领有可读可写可执行的3个权限前面的r-x表明了他所在的组的权限是可读可执行不可写前面的r-x表明了其他人的权限是可读可执行不可写 其实更加罕用的是用 数字示意r( 可读) 用004示意 w(可写) 用002示意 x(可执行)用001示意 - 用000示意 比方0777 中的7就是4+2+1就是可读可写可执行 文件门路相对路径与绝对路径的API:filepath filepath.IsAbs() : 判断是否是绝对路径filepath.Abs(xxx): 失去文件xxx的绝对路径 os.ModePerm是一个常数 代表0777 也就是可读可写可执行 os.Mkdir能够创立最初一级文件夹os.MkdirAll能够把到最初一级没有呈现过的文件夹全副创立进去 ...

April 27, 2022 · 1 min · jiezi

关于golang:微服务从代码到k8s部署应有尽有系列八各种队列

咱们用一个系列来解说从需要到上线、从代码到k8s部署、从日志到监控等各个方面的微服务残缺实际。 整个我的项目应用了go-zero开发的微服务,根本蕴含了go-zero以及相干go-zero作者开发的一些中间件,所用到的技术栈根本是go-zero项目组的自研组件,根本是go-zero全家桶了。 实战我的项目地址:https://github.com/Mikaelemmm... 1、概述音讯队列有很多种,有rabbitmq、rocketmq、kafka等罕用的,其中go-queue(https://github.com/zeromicro/...)是go-zero官网开发的音讯队列组件,其中分为2类,一种是kq、一种是dq,kq是基于kafka的音讯队列,dq是基于beanstalkd的提早队列,然而go-queue不反对定时工作。具体想更多理解go-queue的我之前也写过一篇教程能够去看一下这里不细说了。 本我的项目采纳的是go-queue做音讯队列,asynq做提早队列、定时队列 为什么应用asynq的几个起因 间接基于redis,个别我的项目都有redis,而asynq自身就是基于redis所以能够少保护一个中间件反对音讯队列、提早队列、定时任务调度 , 因为心愿我的项目反对定时工作而asynq间接就反对有webui界面,每个工作都能够暂停、归档、通过ui界面查看成功失败、监控为什么asynq反对音讯队列还在应用go-queue? kafka的吞吐是业绩闻名的,如果后期量不大能够间接用asynq没啥目标,就是想给你们演示一下go-queue在咱们应用go-zero的时候,goctl给咱们带了很大的便当,然而目前go-zero只有生成api、rpc,很多同学在群里问定时工作、提早队列、音讯队列如何生成,目录构造该怎么做,其实go-zero是为咱们设计好了的,就是serviceGroup,应用serviceGroup治理你的服务。 2、如何应用在后面订单、音讯等场景咱们其实曾经演示过了,这里再额定独自补充一次 咱们还是拿order-mq来举例子,显然应用goctl生成api、rpc不是咱们想要的,那咱们就本人应用serviceGroup革新,目录构造还是连续api的根本差不多,只是将handler改成了listen , 将logic换成了mqs。 2.1 在main中代码如下var configFile = flag.String("f", "etc/order.yaml", "Specify the config file")func main() { flag.Parse() var c config.Config conf.MustLoad(*configFile, &c) // log, prometheus, trace, metricsUrl if err := c.SetUp(); err != nil { panic(err) } serviceGroup := service.NewServiceGroup() defer serviceGroup.Stop() for _, mq := range listen.Mqs(c) { serviceGroup.Add(mq) } serviceGroup.Start()}首先咱们要定义配置以及解析配置。其次为什么咱们要在这里加SetUp而api、rpc不须要呢?因为api、rpc都是在MustNewServer中曾经框架写的,然而咱们用serviceGroup治理没有,能够手动点进去SetUp看看,这个办法中蕴含了log、prometheus、trace、metricsUrl的定义,一个办法能够省很多事件,这样咱们间接批改配置文件就能够实现日志、监控、链路追踪了。接下来就是go-zero的serivceGroup治理服务了,serviceGroup是用来治理一组service的,那service其实就是一个接口,代码如下 Service (代码在go-zero/core/service/servicegroup.go) // Service is the interface that groups Start and Stop methods.Service interface { Starter // Start Stopper // Stop}所以,只有你的服务实现了这两个接口,就能够退出到serviceGroup对立治理 ...

April 27, 2022 · 4 min · jiezi

关于golang:Go-开发者调查报告出炉APIRPC-服务成主要用途92-的受访者表示满意

近日,GO 开发者核心官网公布了“2021 Go 开发者调查报告”。 据悉,该报告在 2021 年 10 月 26 日至 11 月 16 日期间,共收集到 11840 个 responses 回复,成为考察发动 6 年以来反应最大的一次。 其中,报告后果里一些要害的点如下: 92% 的受访者对 Go 的满意度依然很高,75% 的受访者在工作中应用 Go;开发者应用 Go 最常见的问题包含“短少要害库、语言个性和基础设施”(Go 1.18 已反对泛型);应用模块时最大的挑战波及版本控制、应用公有回购和多模块工作流(Go 1.18 已解决);受访者还心愿优先改善“调试和依赖关系治理”;81% 的受访者对 Go 我的项目的长期方向充满信心。76% 受访者用 Go 编程:API/RPC 服务最多该报告数据显示,2019 、2020、2021 年以来,Go 都次要用于科技行业,金融服务行业次之。其中 70% 的受访者是软件开发人员,少数人在 IT 或 DevOps 工作。 另外,76% 的受访者示意在工作时应用 Go 编程,其中用作 API 网关和 RPC 微服务设计工作的最多,其次为用作可运行交互式程序。 新用户“画像”:次要为大/中小企业,仅在工作时用 Go此次调查报告中,大多数受访者将其组织形容为企业或中小型企业,约 25% 的受访者则将其组织形容为初创企业,绝大多数受访者的团队成员少于10人。企业类型中,征询公司和公共机构则不常见, 另外,考察中超过一半(55%)的受访者每天都在下班,同时受访者在工作之外应用 Go 的频率较低。 ...

April 26, 2022 · 1 min · jiezi

关于golang:微服务从代码到k8s部署应有尽有系列七支付服务

咱们用一个系列来解说从需要到上线、从代码到k8s部署、从日志到监控等各个方面的微服务残缺实际。 整个我的项目应用了go-zero开发的微服务,根本蕴含了go-zero以及相干go-zero作者开发的一些中间件,所用到的技术栈根本是go-zero项目组的自研组件,根本是go-zero全家桶了。 实战我的项目地址:https://github.com/Mikaelemmm... 1、领取服务业务架构图 2、依赖关系payment-api(领取api) order-rpc(订单rpc)payment-rpc(领取rpc)usercenter(用户rpc)payment-rpc(领取rpc) mqueue-rpc(音讯队列)order-rpc(订单rpc) mqueue-rpc(音讯队列)travel-rpcusercenter(用户rpc) identity-rpc(受权认证rpc)3、微信领取举例3.1 创立领取预处理订单1、用户在咱们这边创立完订单之后,要去微信那边创立预领取订单 app/payment/cmd/api/desc/payment.api // 领取服务v1版本的接口@server( prefix: payment/v1 group: thirdPayment)service payment { @doc "第三方领取:微信领取" @handler thirdPaymentwxPay post /thirdPayment/thirdPaymentWxPay (ThirdPaymentWxPayReq) returns (ThirdPaymentWxPayResp) ...}app/payment/cmd/api/internal/logic/thirdPayment/thirdPaymentwxPayLogic.go ThirdPaymentwxPay见下图,咱们创立微信预领取订单时候做了一次封装,因为咱们平台后续领取业务必定不止民宿领取订单,必定还会有其余的,比方咱们后续能够推出商城,推出课程等,所以在这里应用switch做了个业务分类,目前咱们只有民宿订单,然而除了查问业务不一样,其余都一样,咱们把一样的逻辑封装起来,所以咱们持续看封装后的办法 createWxPrePayOrder app/payment/cmd/api/internal/logic/thirdPayment/thirdPaymentwxPayLogic.go createWxPrePayOrder这里就是拿到用户的登陆userId去换openid(这块咱们之前注册登陆那里有小程序注册登陆,那时候就获取了openid),而后调用paymentRpc中的CreatePayment创立咱们本地的领取流水单号,再通过调用微信sdk-> svc.NewWxPayClientV3(这里是我基于go-zero封装了一次,没啥难度都能看懂) , 而后在微信端创立了一个关联咱们本地流水单号的预领取订单,返回给前端,前端通过js发动申请即可 3.2 微信领取回调以后端拿着咱们给的微信预处理订单发动领取,用户输出明码领取胜利后,微信服务器会回调咱们服务器,回调地址在咱们配置中填写的 这个回调地址,肯定要填写咱们领取api服务中的回调解决办法,也就是如下图的接口,这样咱们能力接管到微信回调进来,咱们才能够做后续解决。 微信回调回来之后,咱们要解决回调逻辑,咱们要调用verifyAndUpdateState 将咱们流水单号改为已领取 咱们来看看verifyAndUpdateState办法,咱们要查问单号是否存在,比对回调回来的金额与创立时候金额是否统一更新流水单号即可。这里不必在校验签名了,前一步的sdk曾经做了解决了 这里还要给前端写一个轮训接口,前端用户领取胜利后前端不能以前端的微信返回后果为准,要通过后端提供的接口轮训,判断这个流水单是否真的是后端返回领取胜利状态,如果这个接口返回胜利才算胜利,微信前端返回的不能作为根据,因为微信前端返回的不平安,个别开发都明确不晓得的本人百度。 3.3 领取胜利发送小程序模版音讯咱们领取回调胜利之后,会给用户发送一个入驻码,去了商家那里要展现这个码,商家通过后盾核查码,其实就是美团的样子,咱们去美团下单,美团会给你个码,用户拿着这个码去入住或者生产等。 ok,回调胜利,咱们会调用pyamentRpc去批改以后流水单状态胜利 咱们来看看paymentRpc中做了什么, 后面是校验,外围做了两件事件,第一是更新状态,第二向音讯队列发送了一条音讯,咱们看看音讯队列中对应的代码 能够看到咱们应用了go-queue发送了一条kq音讯到kafka,而不是asynq提早音讯,因为咱们想让所有订阅了该领取状态的业务都能收到此音讯后做相应的解决,尽管目前咱们只有一个中央监听做解决(发送小程序模版音讯告诉用户领取胜利),所以这里就是发了一条该领取流水相干信息到kafka中,这里跟之前订单那里是一样的只是增加音讯到队列,没有解决,那咱们看看order-mq中怎么解决的。 后面order一节曾经介绍了整个order-mq的运作机制,这里不再多说了,咱们只说kq这里 当order-mq启动后,go-queue会监听kafka中的音讯 咱们再来看下具体实现 , 当后面领取回调胜利增加到kafka中时候,order-mq中kafka会承受音讯,也就是PaymentUpdateStatusMq.Consume会接管到kafka的音讯,而后反序列化数据,传递给execService 执行具体业务,那execService中执行了什么呢? 能够看到下方红框内,一个是批改订单状态(非领取状态,订单也有本人状态),一个是发消息(短信、微信小程序模版音讯)给用户 app/order/cmd/mq/internal/mqs/kq/paymentUpdateStatus.go 批改订单状态的咱们就不看了,咱们能够来看看发送小程序模版音讯,下方LiveStateDate\LiveEndDate之前调试写死的,这个间接改成办法传递过去的工夫就好了,转换一下 ...

April 26, 2022 · 1 min · jiezi

关于golang:操作系统实现开发环境配置

这一次咱们开始本人实现一个简略的操作系统,当然本人也是在看他人的视频进行学习,心愿本人能从这个试验中学习到操作系统相干的常识 <!--more--> 环境配置环境配置VMwareArchLinuxVSCodenasmbochsqemugdbVSCode近程连贯这个步骤就大家自行百度下把 boot.asm[org 0x7c00]; 设置屏幕模式为文本模式,革除屏幕mov ax, 3int 0x10; 初始化段寄存器mov ax, 0mov ds, axmov es, axmov ss, axmov sp, 0x7c00; 0xb8000 文本显示器的内存区域mov ax, 0xb800mov ds, axmov byte [0], 'H'; 阻塞jmp $; 填充 0times 510 - ($ - $$) db 0; 主疏导扇区的最初两个字节必须是 0x55 0xaa; dw 0xaa55db 0x55, 0xaa编译nasm -f bin boot.asm boot.bin创立硬盘镜像bximage -q -hd=16 -func=create -sectsize=512 -imgmode=flat master.img配置bochsrc# configuration file generated by Bochsplugin_ctrl: unmapped=true, biosdev=true, speaker=true, extfpuirq=true, parallel=true, serial=true, iodebug=true, pcidev=false, usb_uhci=falseconfig_interface: textconfigdisplay_library: x,options="gui_debug"memory: host=32, guest=32romimage: file="/usr/share/bochs/BIOS-bochs-latest", address=0x00000000, options=nonevgaromimage: file="/usr/share/bochs/VGABIOS-lgpl-latest"boot: diskfloppy_bootsig_check: disabled=0floppya: type=1_44# no floppybata0: enabled=true, ioaddr1=0x1f0, ioaddr2=0x3f0, irq=14ata0-master: type=disk, path="../build/master.img", mode=flatata0-slave: type=noneata1: enabled=true, ioaddr1=0x170, ioaddr2=0x370, irq=15ata1-master: type=noneata1-slave: type=noneata2: enabled=falseata3: enabled=falseoptromimage1: file=noneoptromimage2: file=noneoptromimage3: file=noneoptromimage4: file=noneoptramimage1: file=noneoptramimage2: file=noneoptramimage3: file=noneoptramimage4: file=nonepci: enabled=1, chipset=i440fx, slot1=none, slot2=none, slot3=none, slot4=none, slot5=nonevga: extension=vbe, update_freq=5, realtime=1, ddc=builtincpu: count=1:1:1, ips=4000000, quantum=16, model=bx_generic, reset_on_triple_fault=1, cpuid_limit_winnt=0, ignore_bad_msrs=1, mwait_is_nop=0cpuid: level=6, stepping=3, model=3, family=6, vendor_string="AuthenticAMD", brand_string="AMD Athlon(tm) processor"cpuid: mmx=true, apic=xapic, simd=sse2, sse4a=false, misaligned_sse=false, sep=truecpuid: movbe=false, adx=false, aes=false, sha=false, xsave=false, xsaveopt=false, avx_f16c=falsecpuid: avx_fma=false, bmi=0, xop=false, fma4=false, tbm=false, x86_64=true, 1g_pages=falsecpuid: pcid=false, fsgsbase=false, smep=false, smap=false, mwait=trueprint_timestamps: enabled=0debugger_log: -magic_break: enabled=1port_e9_hack: enabled=0private_colormap: enabled=0clock: sync=none, time0=local, rtc_sync=0# no cmosimagelog: -logprefix: %t%e%ddebug: action=ignoreinfo: action=reporterror: action=reportpanic: action=askkeyboard: type=mf, serial_delay=250, paste_delay=100000, user_shortcut=nonemouse: type=ps2, enabled=false, toggle=ctrl+mbuttonspeaker: enabled=true, mode=systemparport1: enabled=true, file=noneparport2: enabled=falsecom1: enabled=true, mode=nullcom2: enabled=falsecom3: enabled=falsecom4: enabled=false将boot.bin写入到主疏导扇区dd if=boot.bin of=master.img bs=512 count=1 conv=notruncbochs中运行bochs -q -f bochsrc若bochs中显示出一个H,则表明配置环境胜利,下一节,咱们将对刚刚那个boot.asm进行代码解说 ...

April 26, 2022 · 1 min · jiezi

关于golang:redis数据结构附录

引言本次对上一次的数据结构常识进行补充,次要有redis数据结构的相干利用场景和内存相干常识 <!-- more--> 援用计数-内存redis中的对象回收机制是采纳援用计数的形式,首先咱们能够通过redis对象构造体代码可知 /* * Redis 对象 */typedef struct redisObject { ... // 援用计数 int refcount; ...} robj; 下面表格是和援用计数字段相干的api 援用计数-共享咱们晓得援用计数能够实现对象内存什么时候销毁,那么援用计数也能够用来对象共享 比方此时有一个对象A,值为10,新建了一个对象B,值和类型都与A一样,则能够让他们指向同一个地址,而后援用计数+1 留神:redis初始化时候,会主动创立10000个字符串对象,包含0-9999这10000个整数,当服务器要用到这些数字的字符串对象时候,redis就不须要从新创立。(能够批改redis.h/REDIS_SHARED_INTEGERS进行批改) redis> set A 100OK redis> OBJECT REFCOUNT A(integer) 2 利用场景redis因为有多种数据结构,每种数据结构都有着不同的api和对应的工夫复杂度,因而在理论工作或者我的项目开发中,应该面对不同场景抉择不同的redis数据结构,因为本人在字节工作,会经常应用redis,上面依据本人平时的一些总结进行论述,这个局部会继续更新 本人的网址:www.shicoder.top欢送加群聊天 452380935本文由博客一文多发平台 OpenWrite 公布!

April 26, 2022 · 1 min · jiezi

关于golang:gomicro使用Consul做服务发现的方法和原理

go-micro v4默认应用mdns做服务发现。不过也反对采纳其它的服务发现中间件,因为多年来始终应用Consul做服务发现,为了不便和其它服务集成,所以还是抉择了Consul。这篇文章将介绍go-micro应用Consul做服务发现的办法。对于Consul的应用形式请参考我的另一篇文章:应用Consul做服务发现的若干姿态 。 装置Consul如果你曾经装置Consul,或者对Consul很相熟了,依照本人的形式解决Consul就行了。 这里提供一个通过docker疾速装置Consul的形式,当然前提是你得装置了docker。 执行命令: docker run --name consul1 -p 8500:8500 -p 8300:8300 -p 8301:8301 -p 8302:8302 -d consul:latest这会在docker容器中启动一个最新版本的Consul服务,并将相干端口凋谢给主机。 装置Consul插件应用Consul作为服务注册和服务发现,须要先装置go-micro的consul插件: go get github.com/go-micro/plugins/v4/registry/consul服务端应用Consul服务注册为了应用Consul做服务注册,须要为go-micro server显式的指定Consul Registry。间接看代码吧: func main() { registry := consul.NewRegistry() rpcServer := server.NewServer( server.Name("registry-consul.service"), server.Address("0.0.0.0:8001"), server.Registry(registry), ) proto.RegisterHelloHandler(rpcServer, &Hello{}) service := micro.NewService( micro.Server(rpcServer), ) service.Init() // Run server if err := service.Run(); err != nil { log.Fatal(err) }}通过 consul.NewRegistry() 创立一个Consul 注册核心,而后应用 server.NewServer 创立Server的时候把它设置进去;同时咱们须要指定服务的名称,这里设置的是 registry-consul.service;另外这里不应用随机端口,指定了一个服务的监听地址。这样根本就OK了。 这里并没有指定Consul的连贯地址,因为依照举荐的Consul部署形式,服务所在机器或者容器中应该部署一个Consul的客户端,程序能够间接通过 127.0.0.1:8500 拜访到它。如果要显示指定,能够在NewRegistry时设置: ...

April 25, 2022 · 1 min · jiezi

关于golang:muduo源码分析之回调模块

这次咱们次要来说说muduo库中大量应用的回调机制。muduo次要应用的是利用Callback的形式来实现回调,首先咱们在本人的EchoServer构造函数中有这样几行代码 EchoServer(EventLoop *loop, const InetAddress &addr, const std::string &name) : server_(loop, addr, name) , loop_(loop) { // 注册回调函数 server_.setConnectionCallback( std::bind(&EchoServer::onConnection, this, std::placeholders::_1) ); server_.setMessageCallback( std::bind(&EchoServer::onMessage, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3) ); // 设置适合的loop线程数量 loopthread server_.setThreadNum(3); }应用了setConnectionCallback和setMessageCallback,咱们去看下TcpServer对setConnectionCallback怎么实现的 void setConnectionCallback(const ConnectionCallback &cb) { connectionCallback_ = cb; }这样当有新的连贯建设时候,就会执行咱们设置的EchoServer::onConnection,这样咱们就给TcpServer设置了一个回调函数,同时当TcpServer中的Acceptor承受到一个新的连贯,TcpServer就会去用这个connfd和对应的peerAddr建设一个新的TcpConnection,同时TcpServer会给这个TcpConnection设置一个回调,而这个回调就是咱们给TcpServer设置的回调 // 依据连贯胜利的sockfd,创立TcpConnection TcpConnectionPtr conn(new TcpConnection( ioLoop, connName, sockfd, // Socket Channel localAddr, peerAddr)); connections_[connName] = conn; // 上面的回调时用户设置给TcpServer,TcpServer又设置给TcpConnection,TcpConnetion又设置给Channel,Channel又设置给Poller,Poller告诉channel调用这个回调 conn->setConnectionCallback(connectionCallback_); conn->setMessageCallback(messageCallback_); conn->setWriteCompleteCallback(writeCompleteCallback_);当曾经建设的连贯有新音讯来的时候,conn->setMessageCallback(messageCallback_);这一行代码示意咱们给这个conn设置了一个有音讯来的时候回调,咱们去看下TcpConnection中对setMessageCallback是怎么解决的 void setConnectionCallback(const ConnectionCallback& cb) { connectionCallback_ = cb; }所以有音讯来的时候,就会执行咱们所设置的回调函数onMessage。到这里我么就根本晓得新连贯的建设和旧连贯的音讯到来应该做什么,下一章咱们说一下音讯之间发送的Buffer类。 ...

April 24, 2022 · 1 min · jiezi

关于golang:muduo源码分析之TcpServer模块

这次咱们开始muduo源代码的理论编写,首先咱们晓得muduo是LT模式,Reactor模式,下图为Reactor模式的流程图[起源1] 而后咱们来看下muduo的整体架构[起源1] 首先muduo有一个主反应堆mainReactor以及几个子反应堆subReactor,其中子反应堆的个数由用户应用setThreadNum函数设置,mainReactor中次要有一个Acceptor,当用户建设新的连贯的时候,Acceptor会将connfd和对应的事件打包为一个channel而后采纳轮询的算法,指定将该channel给所抉择的subReactor,当前该subReactor就负责该channel的所有工作。 TcpServer类咱们依照从上到下的思路进行解说,以下内容咱们依照一个简略的EchoServer的实现思路来解说,咱们晓得当咱们本人实现一个Server的时候,会在构造函数中实例化一个TcpServer EchoServer(EventLoop *loop, const InetAddress &addr, const std::string &name) : server_(loop, addr, name) , loop_(loop) { // 注册回调函数 server_.setConnectionCallback( std::bind(&EchoServer::onConnection, this, std::placeholders::_1) ); server_.setMessageCallback( std::bind(&EchoServer::onMessage, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3) ); // 设置适合的loop线程数量 loopthread 不包含baseloop server_.setThreadNum(3); }于是咱们去看下TcpServer的构造函数是在干什么 TcpServer::TcpServer(EventLoop *loop, const InetAddress &listenAddr, const std::string &nameArg, Option option) : loop_(CheckLoopNotNull(loop)) , ipPort_(listenAddr.toIpPort()) , name_(nameArg) , acceptor_(new Acceptor(loop, listenAddr, option == kReusePort)) , threadPool_(new EventLoopThreadPool(loop, name_)) , connectionCallback_() , messageCallback_() , nextConnId_(1) , started_(0){ // 当有新用户连贯时候,会执行该回调函数 acceptor_->setNewConnectionCallback(std::bind(&TcpServer::newConnection, this, std::placeholders::_1, std::placeholders::_2));}咱们只须要关注acceptor_(new Acceptor(loop, listenAddr, option == kReusePort))和threadPool_(new EventLoopThreadPool(loop, name_))首先很明确的一点,结构了一个Acceptor,咱们首先要晓得Acceptor次要就是连贯新用户并打包为一个Channel,所以咱们就应该晓得Acceptor按情理应该实现socket,bind,listen,accept这四个函数。 ...

April 24, 2022 · 2 min · jiezi

关于golang:多协程文件读写并排序尚硅谷Go语言

老师的题目上需要感觉很不明确,本人觉的这道练习题对于管道只有在最初判断所有协程是否都执行结束时做一下判断用,尽管办法外面用到了但感觉有些多余了,小伙伴们有问题能够问我package mainimport ( "bufio" "fmt" "io" "math/rand" "os" "sort" "strconv" "strings" "time")var writeFile string = "write"var sortFile string = "sort"var methodNum int = 2//用于统计协程是否执行实现var endchan chan bool = make(chan bool, methodNum*2)func main() { // 先写 go writeText("write.txt") // 期待写入实现 for { if len(endchan) == 1 { break } } //再排序 go readAndsort("write.txt", "sort.txt") // 期待排序实现 for { if len(endchan) == 2 { break } }}// 写入文件func writeText(fileName string) { var numchan chan string = make(chan string, 4) //关上文件 file, _ := os.OpenFile(fileName, os.O_CREATE, 0666) defer file.Close() // 以纳秒工夫戳设置随机数种子,这样多个协程执行时不会呈现随机数一样的状况了 rand.Seed(time.Now().UnixNano()) // 将随机数放入管道 for { numchan <- (strconv.Itoa(rand.Intn(10))) //设置随机数范畴为 2000 if len(numchan) == 4 { break } } //写入数据到文件中 for { io.WriteString(file, <-numchan+"\n") if len(numchan) == 0 { break } } close(numchan) //程序完结时做标记 endchan <- true}//读取文件func readAndsort(fileName string, sortFile string) { //定义切片用于排序应用 var numlist []int //关上文件 file, _ := os.OpenFile(fileName, os.O_CREATE, 0666) defer file.Close() //读取数据 inputReader := bufio.NewReader(file) for { //读到换行符时截取 readString, readerError := inputReader.ReadString('\n') //去除读取的数据中带有的换行符,不便存储排序应用 readString = strings.Replace(readString, "\n", "", -1) fmt.Printf(" str", readString) fmt.Println() //最初会读到开端的空串所以须要筛选 if readString != "" { //string 转 int tempint, _ := strconv.Atoi(readString) numlist = append(numlist, tempint) } //读到文件开端跳出循环 if readerError == io.EOF { break } } //调用办法排序 sort.Ints(numlist) //关上文件 newfile, _ := os.OpenFile(sortFile, os.O_CREATE, 0666) defer newfile.Close() //将排序后的切片遍历放入文件 for i := 0; i < len(numlist); i++ { // int 转 string str := strconv.Itoa(numlist[i]) io.WriteString(newfile, str+"\n") } //程序完结时做标记 endchan <- true}

April 24, 2022 · 2 min · jiezi

关于golang:多协程累加读写尚硅谷GO语言

注解啥的都写外面了,大家有不懂的能够问我 package mainimport ( "fmt")//定义计算的数值大小var numMax int = 20000// 定义开启 read 协程的数量var readMax int = 8// 存储定义的数值1,2,3,4,5........20000var numchan chan int = make(chan int, numMax)// 存储计算结果var reschan chan int = make(chan int, numMax)// 存储协程执行结束后返回的个数var endchan chan bool = make(chan bool, readMax)func main() { go writeNum() go readNum() go readNum() go readNum() go readNum() go readNum() go readNum() go readNum() go readNum() for { // 继续循环,判断完结信息是否都被增加 if len(endchan) == readMax { close(reschan) break } } // 循环遍历 index := 1 for x := range reschan { fmt.Printf("res[%d] = %d", index, x) fmt.Println() index++ }}// 写入数据func writeNum() { for i := 1; i <= numMax; i++ { numchan <- i } close(numchan)}// 读取数据func readNum() { // 继续循环计算 for { if len(numchan) > 0 { number := <-numchan sum := 0 for i := 1; i <= number; i++ { sum += i } reschan <- sum continue } //如果 reschan 的数据都被增加结束 if len(reschan) == numMax { // 增加完结信息 endchan <- true //完结 break } }}

April 24, 2022 · 1 min · jiezi

关于golang:浅谈MatrixOne如何用Go语言设计与实现高性能哈希表

目录[MatrixOne数据库是什么?][哈希表数据结构根底][哈希表根本设计与对性能的影响] [碰撞解决] [链地址法][凋谢寻址法][Max load factor][Growth factor][闲暇桶探测办法][一些常见的哈希表实现] [C++ std::unordered_map/boost::unordered_map][go map][swisstable][ClickHouse的哈希表实现][高效哈希表的设计与实现] [根本设计与参数抉择][哈希函数][非凡优化][具体实现代码][性能测试] [测试环境][测试内容][整数key后果][字符串key后果][总结]MatrixOne数据库是什么?MatrixOne是一个新一代超交融异构数据库,致力于打造繁多架构解决TP、AP、流计算等多种负载的极简大数据引擎。MatrixOne由Go语言所开发,并已于2021年10月开源,目前曾经release到0.3版本。在MatrixOne已公布的性能报告中,与业界当先的OLAP数据库Clickhouse相比也不落下风。作为一款Go语言实现的数据库,竟然能够与C++ 实现的顶级OLAP数据库性能媲美,这其中就波及到了很多方面的优化,包含高性能哈希表的实现,本文就将具体阐明MatrixOne是如何用Go实现高性能哈希表的。 Github地址:https://github.com/matrixorig... 哈希表数据结构根底哈希表(Hashtable)是一种十分根底的数据结构,对于数据库的分组聚合和Join查问的性能至关重要。以如下的分组聚合为例(备注,图引自参考文献[1]): SELECT col, count(*) FROM table GROUP BY col 它蕴含两个解决阶段:第1阶段是应用数据源的数据建设一个哈希表。哈希表中的每条记录都与一个计数器无关。如果记录是新插入的,相干的计数器被设置为1;否则,计数器被递增。第二阶段是将哈希表中的记录集合成一种可用于进一步查询处理的格局。 对于Join查问而言,以如下SQL为例: SELECT A.left_col, B.right_col FROM A JOIN B USING (key_col) 它同样也有两个阶段:第一阶段是应用Join语句右侧表的数据建设一个哈希表,第二阶段是从左侧表中读取数据,并疾速探测刚刚建设的哈希表。构建阶段与下面的分组实现相似,但每个哈希表的槽位都存储了对左边列的援用。 由此可见,哈希表对于数据库的SQL根底能力起着很要害的作用 ,本文探讨哈希表的根本设计与对性能的影响,比拟各种常见哈希表实现,而后介绍咱们为MatrixOne实现的哈希表的设计抉择与工程优化,最初是性能测试后果。 咱们预设读者曾经对文中提到哈希表相干的概念有所理解,次要探讨其对性能的影响,不做具体科普。如果对基本概念并不理解,请从其余起源获取相干常识,例如维基百科。 哈希表根本设计与对性能的影响碰撞解决不同的key经哈希函数映射到同一个桶,称作哈希碰撞。各种实现中最常见的碰撞解决机制是链地址法(chaining)和凋谢寻址法(open-addressing)。 链地址法在哈希表中,每个桶存储一个链表,把雷同哈希值的不同元素放在链表中。这是C++ 规范容器通常采纳的形式。 长处: 实现最简略直观空间节约较少凋谢寻址法若插入时产生碰撞,从碰撞产生的那个哈希桶开始,依照肯定的秩序,找出一个闲暇的桶。 长处: 每次插入或查找操作只有一次指针跳转,对CPU缓存更敌对所有数据寄存在一块间断内存中,内存碎片更少当max load factor较大时,性能不如链地址法。然而当咱们被动就义内存,抉择较小的max load factor时(例如0.5),局势就产生逆转,凋谢寻址法反而性能更好。因为这时哈希碰撞的概率大大减小,缓存敌对的劣势得以凸显。 值得注意的是,C++ 规范容器实现不采纳凋谢寻址法是由C++ 规范决定,而非依据性能考量(具体可见这个boost文档)。 Max load factor对链地址法哈希表,指均匀每个桶所含元素个数下限。 对凋谢寻址法哈希表,指已填充的桶个数占总的桶个数的最大比值。 max load factor越小,哈希碰撞的概率越小,同时节约的空间也越多。 Growth factor指当已填充的桶达到max load factor限定的下限,哈希表须要rehash时,内存扩张的倍数。growth factor越大,哈希表rehash的次数越少,然而内存节约越多。 闲暇桶探测办法在凋谢寻址法中,若哈希函数返回的桶曾经被其余key占据,须要通过预设规定去邻近的桶中寻找闲暇桶。最常见有以下办法(假如一共|T|个桶,哈希函数为H(k)): 线性探测(linear probing):对i = 0, 1, 2...,顺次探测第H(k, i) = H(k) + ci mod |T|个桶。平方探测(quadratic probing):对i = 0, 1, 2...,顺次探测H(k, i) = H(k) + c1i + c2i2 mod |T|。其中c2不能为0,否则进化成线性探测。双重哈希(double hashing):应用两个不同哈希函数,顺次探测H(k, i) = (H1(k) + i * H2(k)) mod |T|。线性探测法比照其余办法,均匀须要探测的桶数量最多。然而线性探测法拜访内存总是程序间断拜访,最为缓存敌对。因而,在抵触概率不大的时候(max load factor较小),线性探测法是最快的形式。 ...

April 24, 2022 · 3 min · jiezi

关于golang:客户管理系统尚硅谷GO语言

感觉老师的有点乱,所以和老师的不太一样,用的形式比较简单,有问题可留言 package mainimport "fmt"func main() { for { choose := -1 fmt.Println("——————客户信息管理系统——————") fmt.Println("1 增加客户 2 批改客户") fmt.Println("3 删除客户 4 客户列表") fmt.Println("5 退出") fmt.Println("请抉择 (1~5)") fmt.Scanln(&choose) if choose != 5 { if choose == 1 { addCus() } else if choose == 2 { updateCus() } else if choose == 3 { delCus() } else if choose == 4 { listCus() } else { return } } else { return } }}//定义客户构造体type customer struct { id string name string sex string age int phone string email string}//存储客户信息var cusList []customer//增加func addCus() { var addCus customer fmt.Println("————增加客户————") fmt.Print("编号: ") fmt.Scanln(&addCus.id) fmt.Print("姓名: ") fmt.Scanln(&addCus.name) fmt.Print("性别: ") fmt.Scanln(&addCus.sex) fmt.Print("年龄: ") fmt.Scanln(&addCus.age) fmt.Print("电话: ") fmt.Scanln(&addCus.phone) fmt.Print("邮箱: ") fmt.Scanln(&addCus.email) fmt.Println("————增加实现————") cusList = append(cusList, addCus)}//批改func updateCus() { var updateCus customer var upID string fmt.Println("————批改客户————") fmt.Print("请输出待批改的客户编号(-1退出到菜单): ") fmt.Scanln(&upID) if upID != "-1" { for index, value := range cusList { if value.id == upID { fmt.Println("————————批改客户信息(间接回车不批改)———————") fmt.Println("编号:", value.id) fmt.Printf("姓名(%s):", value.name) fmt.Scanln(&updateCus.name) if updateCus.name != "" { cusList[index].name = updateCus.name } fmt.Printf("性别(%s):", value.sex) fmt.Scanln(&updateCus.sex) if updateCus.sex != "" { cusList[index].sex = updateCus.sex } fmt.Printf("年龄(%d):", value.age) fmt.Scanln(&updateCus.age) if updateCus.age != 0 { cusList[index].age = updateCus.age } fmt.Printf("电话(%s):", value.phone) fmt.Scanln(&updateCus.phone) if updateCus.phone != "" { cusList[index].phone = updateCus.phone } fmt.Printf("邮箱(%s):", value.email) fmt.Scanln(&updateCus.email) if updateCus.email != "" { cusList[index].email = updateCus.email } fmt.Println("—————————批改实现————————————") break } } fmt.Println("输出编号不存在") } else { return }}//删除func delCus() { var delID string var choose string fmt.Println("————删除客户————") fmt.Print("请输出待删除的客户编号(-1退出): ") fmt.Scanln(&delID) if delID != "-1" { for index, value := range cusList { //存在输出的id if value.id == delID { fmt.Println("编号:", value.id) fmt.Println("姓名:", value.name) fmt.Println("性别:", value.sex) fmt.Println("年龄:", value.age) fmt.Println("电话:", value.phone) fmt.Println("邮箱:", value.email) fmt.Println("确认删除:(是)Y (否)N") fmt.Scanln(&choose) if choose == "Y" || choose == "y" { //删除的元素在结尾 if index == len(cusList)-1 { cusList = cusList[:index] return //删除的为首元素 } else if index == 0 { cusList = cusList[1:] return } else { //删除的元素在两头:将元素的后面和前面从新拼接,不包含要删除的元素自身 cusList = append(cusList[:index], cusList[index+1:]...) //须要保留开端三个点 return } } else { return } } } // 输出的id没有 fmt.Println("编号输出有误!") return } else { return }}//查问func listCus() { var findID string fmt.Println("输出 id 查找,回车显示全副") fmt.Scanln(&findID) if len(cusList) == 0 { fmt.Println("暂无客户信息,返回菜单") return } else { //全副查问 if findID == "" { fmt.Println("————————客户列表——————————") fmt.Println("编号", "\t姓名", "\t性别", "\t年龄", "\t电话", "\t邮箱") for _, value := range cusList { fmt.Println(value.id, "\t", value.name, "\t", value.sex, "\t", value.age, "\t", value.phone, "\t", value.phone) } fmt.Println("——————————————————————") //依据ID查问 } else { for _, value := range cusList { if value.id == findID { fmt.Println("————————客户信息———————") fmt.Println("编号: ", value.id) fmt.Println("姓名: ", value.name) fmt.Println("性别: ", value.sex) fmt.Println("年龄: ", value.age) fmt.Println("电话: ", value.phone) fmt.Println("邮箱: ", value.email) fmt.Println("——————————————————————") return } } fmt.Println("输出ID不存在,返回菜单") return } }}

April 24, 2022 · 2 min · jiezi