关于golang:GO中-gjson-的应用和分享

[TOC] GO中gjson的利用和分享咱们上次分享到应用 GO 爬取动态网页的数据,一起来回顾一下 分享动态网页和动静网页的简要阐明GO 爬取动态网页简略数据GO 爬取网页上的图片并发爬取网页上的资源要是对 GO 爬取静态数据还有点趣味的话,欢送查看文章 分享一波 GO 的爬虫 json 是什么?JSON(JavaScript Object Notation, JS 对象简谱) 是一种轻量级的数据交换格局 它基于 ECMAScript (欧洲计算机协会制订的 JS 标准)的一个子集,采纳齐全独立于编程语言的文本格式来存储和示意数据 json 有如下几个劣势: 层次结构简洁清晰易于浏览和编写易于机器解析和生成可能晋升网络传输效率 简略列一下咱们罕用的数据序列化的形式 jsonxml是可扩大标记语言,是一种简略的数据存储语言protobuf是一种平台无关、语言无关、可扩大且轻便高效的序列化数据结构的协定,能够用于 网络通信 和 数据存储gjson 是什么?是 GO 外面的一个库 它次要是提供了一种十分疾速且简略的形式从json文档中获取相应值 这个 gjson库,实际上是 get + json的缩写,独一无二,同样的也有sjson库,小伙伴们就晓得他代表的含意了吧,是 set + json的意思 gjson 如何应用?对于 gjson如何应用,XDM,我这里把这个库的根本应用,波及到的知识点,以及注意事项,给大家梳理梳理 要是想看看 gjson的源码是如何实现高效疾速的操作json的,感兴趣的敌人,能够在先会援用的根底上再去具体查看一下源码 本文的分享,围绕如下 4 个方面来实操和梳理 gjson 的应用: gjson 的简略应用gjson 的 json 行gjson 的 修饰符 和 自定义修饰符gjson 键门路的匹配规定 gjson 的简略应用咱们简略应用一个gjson ,如下编码波及如下几个点: 设置具体的json 数据校验 json 数据 是否非法一次性获取单个值一次性获取多个值package mainimport ( "log" "github.com/tidwall/gjson")func main() { // 设置参数,打印行数 log.SetFlags(log.Lshortfile | log.LstdFlags) // 设置 json 数据 json := ` { "author": { "name": "xiaomotong", "age": 18, "hobby": "writing" }, "extra": "hello wolrd" "picList":[{"name":"xiaozhu1"},{"name":"xiaozhu2"}] } ` // 校验 json 字符串是否非法 // 如果不非法的话, gjson 不会报错 panic,可能会拿到一个奇怪值 if gjson.Valid(json){ log.Println("json valid ...") }else{ log.Fatal("json invalid ... ") } // 获取 author.name 的 值 aName := gjson.Get(json, "author.name") log.Println("aName :", aName.String()) // 获取 extra 的值 extra := gjson.Get(json, "extra") log.Println("extra:", extra) // 获取 一个不存在的 键 对应的 值 non := gjson.Get(json, "non") log.Println("non:", non) // 一次性 获取json 的多个键 值 res := gjson.GetMany(json, "author.age", "author.hobby","picList") for i, v := range res{ if i == 0{ log.Println(v.Int()) }else if i == 2{ for _,vv := range v.Array(){ log.Println("picList.name :",vv.Get("name")) } }else{ log.Println(v) } }}运行上述代码后,能够看到如下成果: ...

July 28, 2021 · 7 min · jiezi

关于golang:golangslice扩容后的长度是怎么算的

原来的cap计作old_cap, 须要的cap计作need_cap, 最初失去的cap计作new_cap. 只探讨need_cap > old_cap的状况. 如果need_cap > old_cap*2, new_cap = need_cap 如果need_cap < old_cap*2, 且old_cap < 1024, new_cap = old_cap*2 如果need_cap < old_cap*2, 但old_cap > 1024, 则new_cap=old_cap, 并在new_cap<need_cap的条件下, new_cap = new_cap*1.25. 因为need_cap < old_cap*2, 所以最多也就循环4次. 在这种状况下如果new_cap溢出了, new_cap = need_cap. 附上源码 newcap := old.cap doublecap := newcap + newcap if cap > doublecap { newcap = cap } else { if old.cap < 1024 { newcap = doublecap } else { // Check 0 < newcap to detect overflow // and prevent an infinite loop. for 0 < newcap && newcap < cap { newcap += newcap / 4 } // Set newcap to the requested cap when // the newcap calculation overflowed. if newcap <= 0 { newcap = cap } } }举一些例子来阐明 ...

July 28, 2021 · 1 min · jiezi

关于golang:Go语言RESTful-API-服务急速入门

RESTful APIEST即表述性状态传递(英文:Representational State Transfer,简称REST),它是一种针对网络应用的设计和开发方式,能够升高开发的复杂性,进步零碎的可伸缩性。(援用自百度百科)。 HTTP Method早在 HTTP 0.9 版本中,只有一个GET办法,该办法是一个幂等办法,用于获取服务器上的资源;HTTP 1.0 版本中又减少了HEAD和POST办法,其中罕用的是 POST 办法,个别用于给服务端提交一个资源。HTTP1.1 版本的时,又减少了几个办法。总共加起来有9个。 它们的作用:GET 办法可申请一个指定资源的示意模式,应用 GET 的申请应该只被用于获取数据。HEAD 办法用于申请一个与 GET 申请的响应雷同的响应,但没有响应体。POST 办法用于将实体提交到指定的资源,通常导致服务器上的状态变动或副作用。PUT 办法用于申请有效载荷替换指标资源的所有以后示意。DELETE 办法用于删除指定的资源。CONNECT 办法用于建设一个到由指标资源标识的服务器的隧道。OPTIONS 办法用于形容指标资源的通信选项。TRACE 办法用于沿着到指标资源的门路执行一个音讯环回测试。PATCH 办法用于对资源利用局部批改。在 RESTful API 中,应用的次要是以下五种 HTTP 办法: GET,示意读取服务器上的资源;POST,示意在服务器上创立资源;PUT,示意更新或者替换服务器上的资源;DELETE,示意删除服务器上的资源;PATCH,示意更新 / 批改资源的一部分。一个简略的 RESTful APIGolang 提供了内置的 net/http 包,用来解决这些 HTTP 申请,能够比拟不便地开发一个 HTTP 服务。 示例: package mainimport ( "fmt" "net/http")func main() { http.HandleFunc("/users",handleUsers) http.ListenAndServe(":8080", nil)}func handleUsers(w http.ResponseWriter, r *http.Request){ fmt.Fprintln(w,"ID:1,Name:张三") fmt.Fprintln(w,"ID:2,Name:李四") fmt.Fprintln(w,"ID:3,Name:王五")}运行程序后,在浏览器中输出 http://localhost:8080/users, 就能够看到如下内容信息: ID:1,Name:张三ID:2,Name:李四ID:3,Name:王五示例中,不光能够用 GET 能进行拜访,POST、PUT等也能够,接下来对示例进行改良,使它使它合乎 RESTful API 的标准: ...

July 28, 2021 · 2 min · jiezi

关于golang:gocarbon-145-版本发布修复已知-bug-和优化功能

carbon 是一个轻量级、语义化、对开发者敌对的golang工夫解决库,反对链式调用、农历和gorm、xorm等支流orm 如果您感觉不错,请给个star吧 github:github.com/golang-module/carbon gitee:gitee.com/go-package/carbon 更新日志修复IsFebruary()备注信息谬误的bug修复WeekOfMonth()计算错误的bug修复文档中单词拼写错误局部源码中减少英文备注ToRfc1123ZString()办法改名为ToRfc1123zString()

July 28, 2021 · 1 min · jiezi

关于golang:Go-append-扩容机制

在《切片传递的暗藏危机》一文中,小菜刀有简略地提及到切片扩容的问题。在读者探讨群中,有人举了以下例子,想得到一个正当的答复。 package mainfunc main() { s := []int{1,2} s = append(s, 3,4,5) println(cap(s))}// output: 6为什么后果不是5,不是8,而是6呢?因为小菜刀在该文中对于扩容的形容不够精确,让读者产生了纳闷。因而本文想借此机会粗疏剖析一下append函数及其背地的扩容机制。 咱们晓得,append是一种用户在应用时,并不需要引入相干包而可间接调用的函数。它是内置函数,其定义位于源码包 builtin 的builtin.go。 // The append built-in function appends elements to the end of a slice. If// it has sufficient capacity, the destination is resliced to accommodate the// new elements. If it does not, a new underlying array will be allocated.// Append returns the updated slice. It is therefore necessary to store the// result of append, often in the variable holding the slice itself:// slice = append(slice, elem1, elem2)// slice = append(slice, anotherSlice...)// As a special case, it is legal to append a string to a byte slice, like this:// slice = append([]byte("hello "), "world"...)func append(slice []Type, elems ...Type) []Typeappend 会追加一个或多个数据至 slice 中,这些数据会存储至 slice 的底层数组。其中,数组长度是固定的,如果数组的残余空间足以包容追加的数据,则能够失常地将数据存入该数组。一旦追加数据后总长度超过原数组长度,原数组就无奈满足存储追加数据的要求。此时会怎么解决呢? ...

July 27, 2021 · 5 min · jiezi

关于golang:Go语言代码检查和优化

代码标准查看代码标准查看,是依据 Go 语言的标准,对代码进行 动态扫描查看,这种检查和业务没有关系。 比方程序中定义了个常量,从未应用过,尽管代码运行没有什么影响,然而为了节俭内存,咱们能够删除它,这种状况能够通过代码标准查看检测进去。 golangci-lintgolangci-lint 是一个集成工具,它集成了很多动态代码剖析工具(动态代码剖析是不会运行代码的),咱们通过配置这个工具,便可灵便启用须要的代码标准查看。 装置golangci-lint 是 Go 语言编写的,能够从源代码装置它,在终端输出命令: go get github.com/golangci/golangci-lint/cmd/golangci-lint@v1.32.2 此处装置的是 v1.32.2 版本,装置实现后,查看是否装置胜利,输出命令: golangci-lint version//golangci-lint has version v1.32.2装置胜利后,咱们应用它来进行代码查看,比方咱们有如下代码: const name = "微客鸟窝"func main() {}终端输出命令: golangci-lint run test/ 示意检测目录 test 下的代码,运行后果: test\test.go:3:7: `name` is unused (deadcode)const name = "微客鸟窝" ^test\test.go:4:6: `main` is unused (deadcode)func main() { ^能够看到,程序常量未应用的问题被检测进去了,后续咱们就能够对代码进行欠缺。 golangci-lint 配置golangci-lint 的配置能够自定义要启用哪些 linter。golangci-lint 默认启用的 linter 有: deadcode - 死代码查看errcheck - 返回谬误是否应用查看gosimple - 查看代码是否能够简化govet - 代码可疑查看,比方格式化字符串和类型不统一ineffassign - 查看是否有未应用的代码staticcheck - 动态剖析查看structcheck - 查找未应用的构造体字段typecheck - 类型查看unused - 未应用代码查看varcheck - 未应用的全局变量和常量查看更多的 linter 咱们能够在终端输出命令: golangci-lint linters , 来查看。 批改默认启用的 linter ,须要在我的项目根目录下新建一个名字为 .golangci.yml 的文件,这个就是 golangci-lint 的配置文件。在运行标准查看时,golangci-lint 会主动应用它。 不如咱们在团队开发中,须要应用一个固定的 golangci-lint 版本,这样大家就能够基于同样的规范查看代码。须要在配置文件中增加如下代码: ...

July 27, 2021 · 2 min · jiezi

关于golang:golang中的下划线用法整理

1.疏忽返回值这个应该是最简略的用处,比方某个函数返回三个参数,然而咱们只须要其中的两个,另外一个参数能够疏忽,这样的话代码能够这样写:v1, v2, _ := function(...) 2.用在变量(特地是接口断言)例如咱们定义了一个接口(interface): type Foo interface { Say()}而后定义了一个构造体(struct) type Dog struct {}而后咱们心愿在代码中判断Dog这个struct是否实现了Foo这个interface var _ Foo = Dog{}下面用来判断Dog是否实现了Foo, 用作类型断言,如果Dog没有实现Foo,则会报编译谬误。残缺正确demo: package mainimport ( "fmt")type Foo interface { Say()}type Dog struct {}func (r *Dog) Say() { fmt.Println("谈话Say办法", r)}var _ Foo = &Dog{}func main() { var dog_one = Dog{} fmt.Println("dog_one",dog_one) dog_one.Say()}反例Demo: package mainimport ( "fmt")type Foo interface { Say()}type Dog struct {}/*func (r *Dog) Say() { fmt.Println("谈话Say办法", r)}*/var _ Foo = &Dog{}func main() { var dog_one = Dog{} fmt.Println("dog_one",dog_one) //dog_one.Say()}经典包用法举例: ...

July 27, 2021 · 1 min · jiezi

关于golang:基于-Golang-构建高可扩展的云原生-PaaS附-PPT-下载

作者|刘浩杨起源|尔达 Erda 公众号 本文整顿自刘浩杨在 GopherChina 2021 北京站主会场的演讲,微信增加:Erda202106,分割小助手即可获取讲师 PPT。 前言当今时代,数字化转型概念在 ToB 畛域十分炽热,越来越多的企业须要数字化转型,因而越来越多的厂商涌入了 ToB 市场,端点就是其中一员。 端点的外围业务是给企业提供从洽购、交易、履约、仓储到批发全链路的数字化转型解决方案。围绕这个目标,咱们建设了一整套面向云原生的企业数字化软件产品,其中: PaaS 平台 Erda 是技术底座。Trantor 是具备 HPAPaaS 能力的研发底座,运行在 Erda 之上,提供低代码和高效开发的能力。Gaia 是残缺的业务能力平台,笼罩打算、寻源、洽购、营销、服务、销售、交易、履约、结算等企业供应链业务畛域,提供会员营销、全渠道经营、SRM 洽购和在线销售平台等业务产品。 通过这几层能力的组合,使咱们具备了生产可定制 SaaS 化产品的能力,为企业客户提供端到端的解决方案,满足各行业不同客户的各类需要。所以能够看到在这个分层的企业数字化研发体系中,咱们通过 PaaS 平台向下屏蔽简单的基础设施,使整个业务零碎领有架构多云之上的能力;同时,PaaS 平台也向上提供微服务研发和治理的技术中台能力,让下层用户可能更专一于业务零碎本身的构建。 接下来,简略介绍一下咱们在构建 Erda 过程中的思考,文章后半局部将回到技术自身,来分享一下咱们如何应用 Golang 实现云原生的 PaaS 平台。 软件交付面临的问题和挑战端点从 2012 年成立到当初,始终聚焦在企业级软件开发和交付上,咱们也从最开始的软件定制交付,到明天应用规范软件 + 二开机制解决定制需要的形式交付。在这个过程中,咱们遇到的问题能够总结为以下 4 点: 随着交付规模的快速增长,开发和交付团队要如何提效?业务软件系统如何适配简单的客户部署环境?大规模交付的部署过程须要可被标准化。交付上线后须要继续保障业务稳定性。现在,麻利开发是一个绝对遍及的概念,咱们在做软件交付的时候,也须要把开发流程从传统的瀑布式开发转变为麻利开发,比方一个现实的开发方式是:应用我的项目协同工具来治理研发过程的需要、工作、缺点;应用 gitflow 来治理性能需要的开发;应用自动化的流水线实现继续集成;而后基于容器化来部署咱们的软件;部署之后触发自动化测试来验证性能的正确性。同时,像 API 网关、微服务治理、容器服务、流计算等这些在互联网公司风行的技术,也逐步开始受到大型传统企业的青眼,这对咱们的要求也是进一步提高。 为了解决这些问题,咱们就须要一个残缺的 DevOps 平台来撑持整个研发交付的流程,来达到进步研发效力的目标。方才讲了在研发过程中的问题,接下来聊一下在需要开发实现后,去企业提供的环境进行交付的环节。 在咱们做过的客户外面,有自建机房用 zstack 来提供虚拟化,也有洽购阿里云、腾讯云、或者微软云 azure 的 ECS,还有的会提供现成的容器环境但治理接口不同,比方 Rancher 和 OpenShift,这些状况咱们都碰到过。甚至最近的一个我的项目里,客户提供的是 arm 架构的运行环境。 因为这些企业提供的环境千差万别,所以交付就变成了一件很苦楚的事件。 回顾计算机软件的倒退历史,咱们晓得 java 是当初很风行的一个开发语言和平台,java 能风行的很大一个起因是因为它提出了 Java 虚拟机,通过编译 Java 代码到通用的字节码,再由运行时去即时编译字节码到不同平台的汇编形式来实现“一次编写,到处运行”。那么,咱们构建的软件系统是否也能够像 java 一样 “一次打包,到处运行”?说到这里,可能很多人都会想到:基于 Docker 和 Kubernetes 作为软件的运行时也能够让咱们的软件制品屏蔽掉不同的部署环境。然而,治理不同环境下的 Kubernetes 也是一件很简单的事件,咱们须要一个平台来向上承载不同的业务零碎,向下治理资源和利用调度。 ...

July 27, 2021 · 3 min · jiezi

关于golang:T-T-T-傻傻分不清楚

前言作为一个 Go 语言老手,看到所有”诡异“的代码都会感到好奇;比方我最近看到的几个办法;伪代码如下: func FindA() ([]*T,error) {}func FindB() ([]T,error) {}func SaveA(data *[]T) error {}func SaveB(data *[]*T) error {}置信大部分刚入门 Go 的老手看到这样的代码也是一脸懵逼,其中最让人纳闷的就是: []*T*[]T*[]*T这样对切片的申明,先不看前面两种写法;独自看 []*T 还是很好了解的:该切片中寄存的是所有 T 的内存地址,会比寄存 T 自身来说要更省空间,同时 []*T 在办法外部是能够批改 T 的值,而[]T 是批改不了。 func TestSaveSlice(t *testing.T) { a := []T{{Name: "1"}, {Name: "2"}} for _, t2 := range a { fmt.Println(t2) } _ = SaveB(a) for _, t2 := range a { fmt.Println(t2) }}func SaveB(data []T) error { t := data[0] t.Name = "1233" return nil}type T struct { Name string}比方以上例子打印的是 ...

July 27, 2021 · 3 min · jiezi

关于golang:Golang-读写锁设计

在《Go精妙的互斥锁设计》一文中,咱们具体地解说了互斥锁的实现原理。互斥锁为了防止竞争条件,它只容许一个线程进入代码临界区,而因为锁竞争的存在,程序的执行效率会被升高。同时咱们晓得,只有多线程在共享资源中有写操作,才会引发竞态问题,只有资源没有发生变化,多线程读取雷同的资源就是平安的。因而,咱们引申出更细粒度的锁:读写锁。 什么是读写锁读写锁是一种多读单写锁,分读和写两种锁,多个线程能够同时加读锁,然而写锁和写锁、写锁与读锁之间是互斥的。 读写锁对临界区的解决如上图所示。其中,t1时刻,因为线程1已加写锁,线程2被互斥期待写锁的开释;t2时刻,线程2已加读锁,线程3能够对其持续加读锁并进入临界区;t3时刻,线程3加了读锁,线程4被互斥期待读锁的开释。 饥饿问题依据读写锁的性质,读者应该能猜到读写锁实用于读写明显的场景。依据优先级,能够分为读优先锁和写优先锁。读优先锁能容许最大并发,然而写线程可能被饿死;同理,写优先锁是优先服务写线程,这样读线程就可能被饿死。 相对而言,写锁饥饿的问题更为突出。因为读锁是共享的,如果以后临界区曾经加了读锁,后续的线程持续加读锁是没问题的,然而如果始终有读锁的线程加锁,那尝试加写锁的线程则会始终获取不到锁,这样加写锁的线程始终被阻塞,导致了写锁饥饿。 同时,因为多读锁共享,可能会有读者问:为什么不间接去掉读锁,在写操作线程进来时只加写锁就好了呢,这样岂不是很好实现了。情理很简略,如果以后临界区加了写锁,在写锁解开之前又有新的写操作线程进来,等到写锁开释,新的写操作线程又上了写锁。这种状况如果连续不断,那整个程序就只能执行写操作线程,读操作线程就活活被饿死了。 所以,为了防止饥饿问题,通用的做法是实现偏心读写锁,它将申请锁的线程用队列进行排队,保障了先入先出(FIFO)的准则进行加锁,这样就能无效地防止线程饥饿问题。 那Go语言的读写锁,对于饥饿问题,它是如何解决的呢? Go读写锁设计本文代码版本为Go 1.15.2。如下所示,sync.RWMutex构造体蕴含5个字段。 type RWMutex struct { w Mutex writerSem uint32 readerSem uint32 readerCount int32 readerWait int32 }w 互斥锁sync.Mutex,用于互斥写操作。writerSem 写操作goroutine阻塞期待信号量。最初一个阻塞写操作的读操作goroutine开释读锁时,会告诉阻塞的写锁goroutine。readerSem 读操作goroutine阻塞期待信号量。写锁goroutine开释写锁后,会告诉阻塞的读锁goroutine。readerCount 读操作goroutine数量。readerWait 阻塞写操作goroutine的读操作goroutine数量。对于互斥锁的几个字段,当初可能不好了解,先不必焦急,看完咱们对sync.RWMutex对外提供的四个办法接口解析,就天然明确了。 RLock():加读锁RUnlock():解读锁Lock():加写锁Unlock():解写锁上面,咱们来顺次剖析。 加读锁这里,须要阐明一下的是,为了更好了解代码逻辑,本文所有的代码块均去除了竞态检测的逻辑局部,即if race.Enabled {}办法块。 func (rw *RWMutex) RLock() { if atomic.AddInt32(&rw.readerCount, 1) < 0 { runtime_SemacquireMutex(&rw.readerSem, false, 0) }}atomic.AddInt32是一个原子性操作,其底层通过硬件指令LOCK实现封装(详情可见文章《同步原语的基石》)。rw.readerCount代表读操作goroutine数量,如果将其+1,还小于0,则通过用于同步库的sleep原语runtime_SemacquireMutex阻塞期待写锁开释。 简略地说,如果以后有写操作goroutine曾经进来了,则新来的读操作goroutine会被排队阻塞期待。然而,读者必定会感觉判断条件很奇怪,为什么rw.readerCount会是负值?不要急,下文会有答案。 当然,如果此时没有写锁,则仅仅将rw.readerCount数目加1,而后间接退出,代表加读锁胜利。 解读锁func (rw *RWMutex) RUnlock() { if r := atomic.AddInt32(&rw.readerCount, -1); r < 0 { rw.rUnlockSlow(r) }}将读操作goroutine数目-1,如果其数目r大于等于0,则间接退出,代表解读锁胜利。否则,带着以后处于负值的数目r进入以下rUnlockSlow逻辑 func (rw *RWMutex) rUnlockSlow(r int32) { if r+1 == 0 || r+1 == -rwmutexMaxReaders { race.Enable() throw("sync: RUnlock of unlocked RWMutex") } if atomic.AddInt32(&rw.readerWait, -1) == 0 { runtime_Semrelease(&rw.writerSem, false, 1) }}如果r+1==0,则证实在解读锁时,其实并没有读goroutine加读锁;rwmutexMaxReaders = 1 << 30,这代表读写锁所能接管的最大读操作goroutine数量。至于这里为什么r+1 == -rwmutexMaxReaders也代表并没有goroutine加读锁,同样留在下文解答。在没有加读锁的锁上解读锁,会抛出异样并panic。 ...

July 26, 2021 · 1 min · jiezi

关于golang:Go精妙的互斥锁设计

Some people, when confronted with a problem, think, “I know, I’ll use threads,” and then two they hav erpoblesms. 竞争条件多线程程序在多核CPU机器上访问共享资源时,难免会遇到问题。咱们能够来看一个例子 var Cnt intfunc Add(iter int) { for i := 0; i < iter; i++ { Cnt++ }}func main() { wg := &sync.WaitGroup{} for i := 0; i < 2; i++ { wg.Add(1) go func() { Add(100000) wg.Done() }() } wg.Wait() fmt.Println(Cnt)}很显著,程序的预期后果是200000,但理论的输入却是不可确定的,可能为100910、101364或者其余数值,这就是典型的多线程拜访抵触问题。 利用go tool trace剖析工具(须要在代码中退出runtime/trace包获取程序运行信息,此处省略),查看该程序运行期间goroutine的执行状况如上图所示。其中G20和G19就是执行Add()函数的两个goroutine,它们在执行期间并行地拜访了共享变量Cnt。 相似这种状况,即两个或者多个线程读写某些共享数据,而最初的后果取决于程序运行的准确时序,这就是竞争条件(race condition) 临界区与互斥怎么防止竞争条件?实际上凡波及共享内存、共享文件以及共享任何资源的状况都会引发上文例子中相似的谬误,要防止这种谬误,要害是要找出某种路径来阻止多线程同时读写共享的数据。换言之,咱们须要的是互斥(mutual exclusion),即以某种伎俩确保当一个线程在应用一个共享变量或文件时,其余线程不能做同样的操作。 ...

July 26, 2021 · 4 min · jiezi

关于golang:golang-goplugins-http请求错误

1.谬误1go build github.com/lucas-clemente/quic-go/internal/qtls: build constraints exclude all Go files in C:\Users\admin\go\pkg\mod\github.com\lucas-clemente\quic-go@v0.22.0\internal\qtlsFAIL gomicro-demo2 [build failed]解决办法go.mod中的quic-go正文掉// github.com/lucas-clemente/quic-go v0.22.0 // indirect 2.谬误2cannot use client.ContentType("application/json") (value of type client.Option) as client.Option value in argument to phttp.NewClient 解决办法go-micro版本不统一,批改为统一 "github.com/micro/go-micro/v2/client""github.com/micro/go-micro/v2/client/selector""github.com/micro/go-micro/v2/registry" phttp "github.com/micro/go-plugins/client/http/v2" "github.com/micro/go-plugins/registry/consul/v2"

July 26, 2021 · 1 min · jiezi

关于golang:Go-语言的非协作式抢占原理

从 Go 1.14 开始,通过应用信号,Go 语言实现了调度和 GC 过程中的真“抢占“。 抢占流程由抢占的发动方向被抢占线程发送 SIGURG 信号。 当被抢占线程收到信号后,进入 SIGURG 的解决流程,将 asyncPreempt 的调用强制插入到用户以后执行的代码地位。 本节会对该过程进行详尽剖析。 抢占发动的机会抢占会在下列机会产生: STW 期间在 P 上执行 safe point 函数期间sysmon 后盾监控期间gc pacer 调配新的 dedicated worker 期间panic 解体期间 除了栈扫描,所有触发抢占最终都会去执行 preemptone 函数。栈扫描流程比拟非凡: 从这些流程里,咱们挑出三个来一探到底。 STW 抢占 上图是当初 Go 语言的 GC 流程图,在两个 STW 阶段都须要将正在执行的线程上的 running 状态的 goroutine 停下来。 func stopTheWorldWithSema() { ..... preemptall() ..... // 期待残余的 P 被动停下 if wait { for { // wait for 100us, then try to re-preempt in case of any races // 期待 100us,而后从新尝试抢占 if notetsleep(&sched.stopnote, 100*1000) { noteclear(&sched.stopnote) break } preemptall() } }GC 栈扫描goroutine 的栈是 GC 扫描期间的根,所有 markroot 中须要将用户的 goroutine 停下来,次要是 running 状态: ...

July 26, 2021 · 6 min · jiezi

关于golang:Go-每日一库之-goth

简介以后很多网站间接采纳第三方认证登录,例如支付宝/微信/ Github 等。goth封装了接入第三方认证的办法,并且内置实现了很多第三方认证的实现: 图中截取的只是goth反对的一部分,残缺列表可在其GitHub 首页查看。 疾速应用本文代码应用 Go Modules。 创立目录并初始化: $ mkdir goth && cd goth$ go mod init github.com/darjun/go-daily-lib/goth装置goth库: $ go get -u github.com/markbates/goth咱们设计了两个页面,一个登录页面: // login.tpl<a href="/auth/github?provider=github">Login With GitHub</a>点击登录链接会申请/auth/github?provider=github。 一个主界面: // home.tpl<p><a href="/logout/github">logout</a></p><p>Name: {{.Name}} [{{.LastName}}, {{.FirstName}}]</p><p>Email: {{.Email}}</p><p>NickName: {{.NickName}}</p><p>Location: {{.Location}}</p><p>AvatarURL: {{.AvatarURL}} <img src="{{.AvatarURL}}"></p><p>Description: {{.Description}}</p><p>UserID: {{.UserID}}</p><p>AccessToken: {{.AccessToken}}</p><p>ExpiresAt: {{.ExpiresAt}}</p><p>RefreshToken: {{.RefreshToken}}</p>显示用户的根本信息。 同样地,咱们应用html/template规范模板库来加载和治理页面模板: var ( ptTemplate *template.Template)func init() { ptTemplate = template.Must(template.New("").ParseGlob("tpls/*.tpl"))}主页面解决如下: func HomeHandler(w http.ResponseWriter, r *http.Request) { user, err := gothic.CompleteUserAuth(w, r) if err != nil { http.Redirect(w, r, "/login/github", http.StatusTemporaryRedirect) return } ptTemplate.ExecuteTemplate(w, "home.tpl", user)}如果用户登录了,gothic.CompleteUserAuth(w, r)会返回一个非空的User对象,该类型有如下字段: ...

July 26, 2021 · 2 min · jiezi

关于golang:Go语言反射获取结构体字段的过程

原文链接:读者发问:反射是如何获取构造体成员信息的? 前言哈喽,大家好,我是asong,明天这篇文章的目标次要是解答一位读者的疑难,波及知识点是反射和构造体内存布局。咱们先看一下读者的问题: 咱们通过两个问题来解决他的纳闷: 构造体在内存中是如何存储的反射获取构造体成员信息的过程构造体是如何存储的构造体是占用一块间断的内存,一个构造体变量的大小是由构造体中的字段决定的,构造体变量的地址等于构造体第一个字段的首地址。示例: type User struct { Name string Age uint64 Gender bool // true:男 false: 女}func main(){ u := User{ Name: "asong", Age: 18, Gender: false, } fmt.Printf("%p\n",&u) fmt.Printf("%p\n",&u.Name)}// 运行后果0xc00000c0600xc00000c060从运行后果咱们能够验证了构造体变量u的寄存地址就是字段Name的首地址。 构造体的内存布局其实就是调配一段间断的内存,具体是在栈上调配还是堆上调配取决于编译器的逃逸剖析,构造体在内存调配时还要思考到内存对齐。 对齐的作用和起因:CPU拜访内存时,并不是一一字节拜访,而是以字长(word size)单位拜访。比方32位的CPU,字长为4字节,那么CPU拜访内存的单位也是4字节。这样设计能够缩小CPU拜访内存的次数,加大CPU拜访内存的吞吐量。假如咱们须要读取8个字节的数据,一次读取4个字节那么就只需读取2次就能够。内存对齐对实现变量的原子性操作也是有益处的,每次内存拜访都是原子的,如果变量的大小不超过字长,那么内存对齐后,对该变量的拜访就是原子的,这个个性在并发场景下至关重要。C语言的内存对齐规定与Go语言一样,所以C语言的对齐规定对Go同样实用: 对于构造的各个成员,第一个成员位于偏移为0的地位,构造体第一个成员的偏移量(offset)为0,当前每个成员绝对于构造体首地址的 offset 都是该成员大小与无效对齐值中较小那个的整数倍,如有须要编译器会在成员之间加上填充字节。除了构造成员须要对齐,构造自身也须要对齐,构造的长度必须是编译器默认的对齐长度和成员中最长类型中最小的数据大小的倍数对齐。依据这个规定咱们来剖析一下下面示例的构造体User,这里我应用的mac,所以是64位CPU,编译器默认对齐参数是8,String、uint64、bool的对齐值别离是8、8、1,依据第一条规定剖析: 第一个字段类型是string,对齐值是8,大小为16,所以放在内存布局中的第一位。第二个字段类型是uin64,对齐值是8,大小为8,所以他的内存偏移值必须是8的倍数,因为第一个字段Name占有16位,所以间接从16开始不要补位。第三个字段类型是bool,对齐值是1,大小为1,所以他的内存偏移值必须是1的倍数,因为User的前两个字段曾经排到了24位,所以下一个偏移量正好是24。接下来咱们在剖析第二个规定: 依据第一条内存对齐规定剖析后,内存长度曾经为25字节了,咱们开始应用第2条规定进行对齐,默认对齐值是8,字段中最大类型的长度是16,所以能够得出该构造体的对齐值是8,咱们目前的内存长度是25,不是8的倍数,所以须要补全,所以最终的后果是32,补了7位,由编译器进行填充,个别为0值,也称之为空洞。 留神:这里对内存对齐没有说的很细,想要更深理解内存对齐能够看我之前的一篇文章:Go看源码必会常识之unsafe包Go语言反射获取构造体成员信息Go语言提供了一种机制在运行时更新和查看变量的值、调用变量的办法和变量的外在操作,然而在编译时并不知道这些变量的具体类型,这种机制被称为反射。Go语言提供了 reflect 包来拜访程序的反射信息。 咱们能够通过调用reflect.TypeOf()取得反射对象信息,如果他的类型是构造体,接着能够通过反射值对象reflect.Type的NumField和Field办法获取构造体成员的详细信息,先看一个例子: type User struct { Name string Age uint64 Gender bool // true:男 false: 女}func main() { u := User{ Name: "asong", Age: 18, Gender: false, } getType := reflect.TypeOf(u) for i:=0; i < getType.NumField(); i++{ fieldType := getType.Field(i) // 输入成员名 fmt.Printf("name: %v \n", fieldType.Name) }}// 运行后果name: Name name: Age name: Gender 接下来咱们就一起来看一看Go语言是如何通过反射来获取构造体成员信息的。 ...

July 26, 2021 · 2 min · jiezi

关于golang:golang-grpc实现加法计算

1.grpc 近程加法计算proto装置及proto-gen装置省略 2.proto文件编写syntax="proto3"; //指定proto3格局package service; option go_package="./;service"; //go 打包的packageservice Calculator{ rpc Add(Numbers) returns (CalResult){} //定义一个服务 参数为Numbers 后果为CalResult}message Numbers{ //申请参数message,有a和b int64 a = 1; int64 b = 2;}message CalResult{ //响应后果 CalResult int64 message = 1;}生成go格式文件protoc -I ./proto/ ./proto/calculator.proto --go_out=plugins=grpc:service 3.服务端package mainimport ( "context" "fmt" "grpcdemo/service" "net" "google.golang.org/grpc" "google.golang.org/grpc/reflection")type Server struct{} //定义一个构造体实现Add()办法func (s *Server) Add(ctx context.Context, in *service.Numbers) (*service.CalResult, error){ return &service.CalResult{Message: in.A + in.B},nil}func main(){ lis,err := net.Listen("tcp",":8089") if err != nil { fmt.Printf("监听端口失败:%s",err) return } s := grpc.NewServer() service.RegisterCalculatorServer(s,&Server{}) reflection.Register(s) err = s.Serve(lis) if err != nil { fmt.Printf("开启服务失败:%s",err) return }}实现的办法为 ...

July 26, 2021 · 1 min · jiezi

关于golang:Go语言SliceHeaderslice-如何高效处理数据

数组Go 语言中,数组类型包含两局部:数组大小、数组外部元素类型。 a1 := [1]string("微客鸟窝")a2 := [2]string("微客鸟窝")示例中变量 a1 的类型是 [1]string,变量 a2 的类型是 [2]string,因为它们大小不统一,所以不是同一类型。 数组局限性数组被申明之后,它的大小和外部元素的类型就不能再被扭转因为在 Go 语言中,函数之间的参数传递是值传递,数组作为参数的时候,会将其复制一份,如果它十分大,会造成大量的内存节约正是因为数组有这些局限性,Go 又设计了 slice ! slice 切片slice 切片的底层数据是存储在数组中的,能够说是数组的改良版,slice 是对数组的形象和封装,它能够动静的增加元素,容量有余时能够主动扩容。 ## 动静扩容 通过内置的 append 办法,能够对切片追加任意多个元素: func main() { s := []string{"微客鸟窝","无尘"} s = append(s,"wucs") fmt.Println(s) //[微客鸟窝 无尘 wucs]}append 办法追加元素时,如果切片的容量不够,会主动进行扩容: func main() { s := []string{"微客鸟窝","无尘"} fmt.Println("切片长度:",len(s),";切片容量:",cap(s)) s = append(s,"wucs") fmt.Println("切片长度:",len(s),";切片容量:",cap(s)) fmt.Println(s) //}运行后果: 切片长度: 2 ;切片容量: 2切片长度: 3 ;切片容量: 4[微客鸟窝 无尘 wucs]通过运行后果咱们发现,在调用 append 之前,容量是 2,调用之后容量是 4,阐明主动扩容了。 扩容原理是新建一个底层数组,把原来切片内的元素拷贝到新的数组中,而后返回一个指向新数组的切片。 ...

July 26, 2021 · 2 min · jiezi

关于golang:go基础变量

一、变量值容许扭转二、申明与初始化1、先定义,再赋值var name stringname = "调度"fmt.Printf("先定义,再赋值: %s \n",name)2、先定义+赋值var name string = "调度"fmt.Printf("先定义,再赋值: %s \n",name)3、先定义+赋值+主动推导var name = "调度"fmt.Printf("先定义,再赋值: %s \n",name)4、简短申明name := "调度"fmt.Printf("先定义,再赋值: %s \n",name)5、变量替换i,j :=10,20i,j = j,ifmt.Printf("变量替换--- %d,%d \n",i,j)6、申明多变量 var( user string age int address string ) user="1111" age=10 address="的说法是" fmt.Printf("定义多变量--- %s,%d,%s \n",user,age,address)7、_匿名变量不占用命名空间不分配内存可屡次应用 func main(){ sum , _ :=add(10,20) fmt.Printf("匿名变量--- %d",sum)}func add(a int ,b int)(int,error){ if a< 0{ return 0,errors.New("产生谬误") } return a+b,nil}留神变量申明,必须应用:=简短申明不能呈现在 全局变量

July 25, 2021 · 1 min · jiezi

关于golang:源码赏析singleflight设计-防缓存穿透神器

原文链接:赏析Singleflight设计 前言哈喽,大家好,我是asong。明天想与大家分享一下singleflight这个库,singleflight仅仅只有100多行却能够做到避免缓存击穿,有点厉害哦!所以本文咱们就一起来看一看他是怎么设计的~。 留神:本文基于 https://pkg.go.dev/golang.org...进行剖析。 缓存击穿什么是缓存击穿平时在高并发零碎中,会呈现大量的申请同时查问一个key的状况,如果此时这个热key刚好生效了,就会导致大量的申请都打到数据库下面去,这种景象就是缓存击穿。缓存击穿和缓存雪崩有点像,然而又有一点不一样,缓存雪崩是因为大面积的缓存生效,打崩了DB,而缓存击穿则是指一个key十分热点,在不停的扛着高并发,高并发集中对着这一个点进行拜访,如果这个key在生效的霎时,继续的并发到来就会穿破缓存,间接申请到数据库,就像一个完整无缺的桶上凿开了一个洞,造成某一时刻数据库申请量过大,压力剧增!如何解决办法一 咱们简略粗犷点,间接让热点数据永远不过期,定时工作定期去刷新数据就能够了。不过这样设置须要辨别场景,比方某宝首页能够这么做。办法二 为了避免出现缓存击穿的状况,咱们能够在第一个申请去查询数据库的时候对他加一个互斥锁,其余的查问申请都会被阻塞住,直到锁被开释,前面的线程进来发现曾经有缓存了,就间接走缓存,从而爱护数据库。然而也是因为它会阻塞其余的线程,此时零碎吞吐量会降落。须要结合实际的业务去思考是否要这么做。办法三 办法三就是singleflight的设计思路,也会应用互斥锁,然而绝对于办法二的加锁粒度会更细,这里先简略总结一下singleflight的设计原理,前面看源码在具体分析。 singleflightd的设计思路就是将一组雷同的申请合并成一个申请,应用map存储,只会有一个申请达到mysql,应用sync.waitgroup包进行同步,对所有的申请返回雷同的后果。 源码赏析曾经急不可待了,直奔主题吧,上面咱们一起来看看singleflight是怎么设计的。 数据结构singleflight的构造定义如下: type Group struct { mu sync.Mutex // 互斥锁,保障并发平安 m map[string]*call // 存储雷同的申请,key是雷同的申请,value保留调用信息。}Group构造还是比较简单的,只有两个字段,m是一个map,key是雷同申请的标识,value是用来保留调用信息,这个map是懒加载,其实就是在应用时才会初始化;mu是互斥锁,用来保障m的并发平安。m存储调用信息也是独自封装了一个构造: type call struct { wg sync.WaitGroup // 存储返回值,在wg done之前只会写入一次 val interface{} // 存储返回的错误信息 err error // 标识别是否调用了Forgot办法 forgotten bool // 统计雷同申请的次数,在wg done之前写入 dups int // 应用DoChan办法应用,用channel进行告诉 chans []chan<- Result}// Dochan办法时应用type Result struct { Val interface{} // 存储返回值 Err error // 存储返回的错误信息 Shared bool // 标示后果是否是共享后果}Do办法// 入参:key:标识雷同申请,fn:要执行的函数// 返回值:v: 返回后果 err: 执行的函数错误信息 shard: 是否是共享后果func (g *Group) Do(key string, fn func() (interface{}, error)) (v interface{}, err error, shared bool) { // 代码块加锁 g.mu.Lock() // map进行懒加载 if g.m == nil { // map初始化 g.m = make(map[string]*call) } // 判断是否有雷同申请 if c, ok := g.m[key]; ok { // 雷同申请次数+1 c.dups++ // 解锁就好了,只须要期待执行后果了,不会有写入操作了 g.mu.Unlock() // 已有申请在执行,只须要期待就好了 c.wg.Wait() // 辨别panic谬误和runtime谬误 if e, ok := c.err.(*panicError); ok { panic(e) } else if c.err == errGoexit { runtime.Goexit() } return c.val, c.err, true } // 之前没有这个申请,则须要new一个指针类型 c := new(call) // sync.waitgroup的用法,只有一个申请运行,其余申请期待,所以只须要add(1) c.wg.Add(1) // m赋值 g.m[key] = c // 没有写入操作了,解锁即可 g.mu.Unlock() // 惟一的申请该去执行函数了 g.doCall(c, key, fn) return c.val, c.err, c.dups > 0}这里是惟一有疑难的应该是辨别panic和runtime谬误局部吧,这个与上面的docall办法有关联,看完docall你就晓得为什么了。 ...

July 25, 2021 · 3 min · jiezi

关于golang:编程模式之Go语言如何实现装饰器

原文链接:编程模式之Go如何实现装璜器 前言哈喽,大家好,我是asong。明天想与大家聊一聊如何用Go实现装璜器代码。为什么会有这个想法呢?最近因为我的项目须要始终在看python的代码,在这个我的项目中利用了大量的装璜器代码,一个装璜器代码能够在全文共用,缩小了冗余代码。python的语法糖让实现装璜器变得很简略,然而Go语言的糖不多,而且又是强类型的动态无虚拟机的语言,所以,没有方法做到像 Java 和 Python 那样写出优雅的装璜器的代码,但也是能够实现的,明天咱们就看看如何Go语言写出装璜器代码!什么是装璜器介绍装璜器基本概念之前,咱们先举个例子,跟装璜器很贴切: 现在咱们的生存程度进步了,根本人手一台手机,大家也晓得手机屏幕摔到地板上是很容易碎屏的,手机屏幕一坏,又要多花一笔费用进行培修,很是心痛;那么有什么什么方法来防止这个问题呢,在不毁坏手机屏幕构造的状况下,让咱们的手机更耐坏呢?其实咱们只须要花几元钱买一个钢化膜,钢化膜在不扭转原有手机屏幕的构造下,让手机变得更耐摔了。依据下面这个例子,就能够引出本文的外围 -> 装璜器。装璜器实质就是: 函数装璜器用于在源码中“标记”函数,以某种形式加强函数的行为。装璜器是一个弱小的性能,然而若想把握,必须要了解闭包!闭包的概念咱们在上面一大节阐明,咱们先来看一看python是如何应用装璜器的: def metric(fn): @functools.wraps(fn) def timer(*arag, **kw): start = time.time() num = fn(*arag, **kw) end = time.time() times = (end - start) * 1000 print('%s executed in %s ms' % (fn.__name__, times)) return num return timer@metricdef Sum(x, y): time.sleep(0.0012) return x + y;Sum(10, 20)这里要实现性能很简略,metric就是一个装璜器函数,他能够作用于任何函数之上,并打印该函数的执行工夫,有个这个装璜器,咱们想要晓得任何一个函数的执行工夫,就简便很多了。 简略总结一下装璜器应用场景: 插入日志:使面向切面编程变的更简略了。缓存:读写缓存应用装璜器来实现,缩小了冗余代码。事务处理:使代码看起来更简洁了。权限校验:权限校验器是都是一套代码,缩小了冗余代码。装璜器的应用场景还用很多,就不一一列举了,上面咱们就来看看如何应用Go也来实现装璜器代码吧! 闭包装璜器的实现和闭包是分不开的,所以咱们先来学习一下什么是闭包! 咱们通常会把闭包和匿名函数弄混,这是因为:在 函数外部定义函数不常见,直到开始应用匿名函数才会这样做。而且, 只有波及嵌套函数时才有闭包问题。因而,很多人是同时晓得这两个概念的。 其实,闭包指延长了作用域的函数,其中蕴含函数定义体中援用、然而不在定义体中定义的非全局变量。函数是不是匿名的没有关系,要害是 它能拜访定义体之外定义的非全局变量。 光看概念其实挺难了解闭包,咱们通过例子来进行了解。 func makeAverager() func(val float32) float32{ series := make([]float32,0) return func(val float32) float32 { series = append(series, val) total := float32(0) for _,v:=range series{ total +=v } return total/ float32(len(series)) }}func main() { avg := makeAverager() fmt.Println(avg(10)) fmt.Println(avg(30))}这个例子,你猜运行后果是什么?10,30还是10,20? ...

July 25, 2021 · 2 min · jiezi

关于golang:Go-中-channel-range-的深度理解

咱们来看下两端代码:代码段1 func main() { channels := make([]chan int, 10) for i := 0; i < 10; i++ { go func(ch chan int) { time.Sleep(time.Second) ch <- 1 }(channels[i]) } for ch := range channels { fmt.Println("Routine ", ch, " quit!") } fmt.Println("完结")}猜测下这下面会打印什么后果。 代码段2 func main() { ch := make(chan int, 10) for i := 0; i < 10; i++ { go func() { ch <- i }() } for range ch { <-ch } fmt.Println(1111)}猜测下代码2会打印什么后果。 ...

July 25, 2021 · 1 min · jiezi

关于golang:Go语言指针和unsafePointer有什么区别

指针类型转换在 Go 语言中,处于平安思考,是不容许两个指针类型进行转换的,比方 int 不能转为 float64。 func main() { i := 5 ip := &i var fp *float64 = (*float64)(ip)}运行后果: cannot convert ip (type * int) to type * float64发现报错了,并不能进行强制转型。 如果你非要~,那么 Go 提供了 unsafe 包,应用包里的 Pointer 来进行转换! unsafe.Pointerunsafe.Pointer 是一种非凡意义的指针,能够示意任意类型的地址。应用它能够进行两个指针类型的转换。 func main() { i:= 5 ip := &i var fp *float64 = (*float64)(unsafe.Pointer(ip)) *fp = *fp * 6 fmt.Println(i)}示例中咱们通过 unsafe.Pointer 能够在不同指针类型之间做任何转换。咱们看下 unsafe.Pointer 的源码定义: // ArbitraryType is here for the purposes of documentation// only and is not actually part of the unsafe package. // It represents the type of an arbitrary Go expression.type ArbitraryType inttype Pointer *ArbitraryTypeArbitraryType 能够示意任何类型,ArbitraryType 咱们无需关注太多,晓得它能够示意任何类型即可unsafe.Pointer 是 *ArbitraryType,表明 unsafe.Pointer 是任何类型的指针,是一个通用型的指针,能够示意任何内存地址。uintptr 指针类型uintptr 也是一种指针类型,也能够示意任何类型,和 unsafe.Pointer 的区别是,uintptr 能够进行运算。通过 uintptr 能够对指针偏移进行计算,来达到拜访特定的内存,对特定内存进行读写的目标。 示例: ...

July 25, 2021 · 2 min · jiezi

关于golang:gozero源码解析布隆过滤器

本篇文章的前提须要理解布隆过滤器有什么用! 实现原理go-zero中布隆过滤器的实现是借助于redis的setbit和getbit实现的 // New create a Filter, store is the backed redis, key is the key for the bloom filter,// bits is how many bits will be used, maps is how many hashes for each addition.// best practices:// elements - means how many actual elements// when maps = 14, formula: 0.7*(bits/maps), bits = 20*elements, the error rate is 0.000067 < 1e-4// for detailed error rate table, see http://pages.cs.wisc.edu/~cao/papers/summary-cache/node8.htmlfunc New(store *redis.Redis, key string, bits uint) *Filter { return &Filter{ bits: bits, bitSet: newRedisBitSet(store, key, bits), }} 布隆过滤器(redis中)实质是一个二进制数组,数组中每一个元素的值只能够是0或者1,长度的范畴是0~2^32。 对于这个布隆过滤器的形容能够从正文中失去,每一个布隆过滤器的元素会生成14个hash值,并且误报率为0.000067 ...

July 24, 2021 · 1 min · jiezi

关于golang:Go语言运行时反射深度解析

咱们在开发的时候常常会遇到字符串跟构造体之间的转换,比方在调用 API 时,须要将 JSON 字符串转成 struct 构造体。具体如何进行转换,就须要用到反射了。 反射对于反射,之前的文章曾经有所介绍,传送门:《断言、反射的了解与应用!》,此处咱们再深刻解说下反射。 reflect.Value 和 reflect.Type反射有两种类型 reflect.Value 和 reflect.Type ,别离示意变量的值和类型,并且提供了两个函数 reflect.ValueOf 和 reflect.TypeOf 别离获取任意对象的 reflect.Value 和 reflect.Type。 reflect.Valuereflect.Value 能够通过函数 reflect.ValueOf 取得。reflect.Value 被定义为一个 struct 构造体: type Value struct { typ *rtype ptr unsafe.Pointer flag}能够看到,这个构造体中的字段都是公有的,咱们只能应用 reflect.Value 的办法,它的办法有: //针对具体类型的系列办法//以下是用于获取对应的值BoolBytesComplexFloatIntStringUintCanSet //是否能够批改对应的值//以下是用于批改对应的值SetSetBoolSetBytesSetComplexSetFloatSetIntSetStringElem //获取指针指向的值,个别用于批改对应的值//以下Field系列办法用于获取struct类型中的字段FieldFieldByIndexFieldByNameFieldByNameFuncInterface //获取对应的原始类型IsNil //值是否为nilIsZero //值是否是零值Kind //获取对应的类型类别,比方Array、Slice、Map等//获取对应的办法MethodMethodByNameNumField //获取struct类型中字段的数量NumMethod//类型上办法集的数量Type//获取对应的reflect.Type办法分为三类: 获取和批改对应的值struct 类型的字段无关,用于获取对应的字段类型上的办法集无关,用于获取对应的办法任意类型的对象 与 reflect.Value 互转:func main() { i := 5 //int to reflect.Value iv:=reflect.ValueOf(i) //reflect.Value to int i1 := iv.Interface().(int) fmt.Println(i1)}批改对应的值func main() { i := 5 ipv := reflect.ValueOf(&i) ipv.Elem().SetInt(6) fmt.Println(i)}示例中咱们通过反射批改了一个变量reflect.ValueOf 函数返回的是一份值的拷贝,所以咱们要传入变量的指针才能够因为传递的是一个指针,所以须要调用 Elem 办法找到这个指针指向的值,这样能力批改要批改一个变量的值,关键点:传递指针(可寻址),通过 Elem 办法获取指向的值,才能够保障值能够被批改,reflect.Value 为咱们提供了 CanSet 办法判断是否能够批改该变量批改 struct 构造体字段的值func main() { p := person{Name: "微客鸟窝",Age: 18} pv:=reflect.ValueOf(&p) pv.Elem().Field(0).SetString("无尘") fmt.Println(p)}type person struct { Name string Age int}步骤总结: ...

July 24, 2021 · 3 min · jiezi

关于golang:用GO写一个RPC框架-s05-客户端编写

前言后面几章咱们实现了 服务端的编写 当初开始客户端编写 https://github.com/dollarkill...Clienttype Client struct { options *Options}func NewClient(discover discovery.Discovery, options ...Option) *Client { client := &Client{ options: defaultOptions(), } client.options.Discovery = discover for _, fn := range options { fn(client.options) } return client}option type Options struct { Discovery discovery.Discovery // 服务发现插件 loadBalancing load_banlancing.LoadBalancing // 负载平衡插件 serializationType codes.SerializationType // 序列化插件 compressorType codes.CompressorType // 压缩插件 pool int // 连接池大小 cryptology cryptology.Cryptology rsaPublicKey []byte writeTimeout time.Duration readTimeout time.Duration heartBeat time.Duration Trace bool AUTH string // AUTH TOKEN}func defaultOptions() *Options { defaultPoolSize := runtime.NumCPU() * 4 if defaultPoolSize < 20 { defaultPoolSize = 20 } return &Options{ pool: defaultPoolSize, serializationType: codes.MsgPack, compressorType: codes.Snappy, loadBalancing: load_banlancing.NewPolling(), cryptology: cryptology.AES, rsaPublicKey: []byte(`-----BEGIN PUBLIC KEY----------END PUBLIC KEY-----`), writeTimeout: time.Minute, readTimeout: time.Minute * 3, heartBeat: time.Minute, Trace: false, AUTH: "", }}具体每个链接type Connect struct { Client *Client pool *connectPool close chan struct{} serverName string}func (c *Client) NewConnect(serverName string) (conn *Connect, err error) { connect := &Connect{ Client: c, serverName: serverName, close: make(chan struct{}), } connect.pool, err = initPool(connect) return connect, err}初始化连接池func initPool(c *Connect) (*connectPool, error) { cp := &connectPool{ connect: c, pool: make(chan LightClient, c.Client.options.pool), } return cp, cp.initPool()}func (c *connectPool) initPool() error { hosts, err := c.connect.Client.options.Discovery.Discovery(c.connect.serverName) // 调用服务发现 查看 发现具体服务 if err != nil { return err } if len(hosts) == 0 { return errors.New(fmt.Sprintf("%s server 404", c.connect.serverName)) } c.connect.Client.options.loadBalancing.InitBalancing(hosts) // 初始化 负载平衡插件 // 初始化连接池 for i := 0; i < c.connect.Client.options.pool; i++ { client, err := newBaseClient(c.connect.serverName, c.connect.Client.options) // 建设链接 if err != nil { return errors.WithStack(err) } c.pool <- client } return nil}// 连接池中获取一个链接func (c *connectPool) Get(ctx context.Context) (LightClient, error) { select { case <-ctx.Done(): return nil, errors.New("pool get timeout") case r := <-c.pool: return r, nil }}// 放回一个链接func (c *connectPool) Put(client LightClient) { if client.Error() == nil { c.pool <- client return } // 如果 client.Error() 有异样 须要新初始化一个链接 放入连接池 go func() { fmt.Println("The server starts to restore") for { time.Sleep(time.Second) hosts, err := c.connect.Client.options.Discovery.Discovery(c.connect.serverName) if err != nil { log.Println(err) continue } if len(hosts) == 0 { err := errors.New(fmt.Sprintf("%s server 404", c.connect.serverName)) log.Println(err) continue } c.connect.Client.options.loadBalancing.InitBalancing(hosts) baseClient, err := newBaseClient(c.connect.serverName, c.connect.Client.options) if err != nil { log.Println(err) continue } c.pool <- baseClient fmt.Println("Service recovery success") break } }()}Connect 调用具体服务func (c *Connect) Call(ctx *light.Context, serviceMethod string, request interface{}, response interface{}) error { ctxT, _ := context.WithTimeout(context.TODO(), time.Second*6) var err error // 连接池中获取一个链接 client, err := c.pool.Get(ctxT) if err != nil { return errors.WithStack(err) } // 用完 放回链接 defer func() { c.pool.Put(client) }() // 设置token ctx.SetValue("Light_AUTH", c.Client.options.AUTH) // 具体调用 err = client.Call(ctx, serviceMethod, request, response) if err != nil { return errors.WithStack(err) } return nil}调用外围 重点温习 s03 协定设计/** 协定设计 起始符 : 版本号 : crc32校验 : magicNumberSize: serverNameSize : serverMethodSize : metaDataSize : payloadSize: respType : compressorType : serializationType : magicNumber : serverName : serverMethod : metaData : payload 0x05 : 0x01 : 4 : 4 : 4 : 4 : 4 : 4 : 1 : 1 : 1 : xxx : xxx : xxx : xxx : xxx*/留神: 每一个申请都有一个 magicNumber 都有一个申请ID ...

July 23, 2021 · 7 min · jiezi

关于golang:用GO写一个RPC框架-s04-编写服务端核心

前言通过上两篇的学习 咱们曾经理解了 服务端本地服务的注册, 服务端配置,协定 当初咱们开始写服务端的外围逻辑 https://github.com/dollarkill...默认配置咱们先看下默认的配置 func defaultOptions() *Options { return &Options{ Protocol: transport.TCP, // default TCP Uri: "0.0.0.0:8397", UseHttp: false, readTimeout: time.Minute * 3, // 心跳包 默认 3min writeTimeout: time.Second * 30, ctx: context.Background(), // ctx 是管制服务退出的 options: map[string]interface{}{ "TCPKeepAlivePeriod": time.Minute * 3, }, processChanSize: 1000, Trace: false, RSAPublicKey: []byte(`-----BEGIN PUBLIC KEY----------END PUBLIC KEY-----`), RSAPrivateKey: []byte(`-----BEGIN RSA PRIVATE KEY----------END RSA PRIVATE KEY-----`), Discovery: &discovery.SimplePeerToPeer{}, }}run服务注册结束之后 调用Run办法 启动服务 func (s *Server) Run(options ...Option) error { // 初始化 服务端配置 for _, fn := range options { fn(s.options) } var err error // 更具配置传入的protocol 获取到 网络插件 (KCP UDP TCP) 咱们等下细讲 s.options.nl, err = transport.Transport.Gen(s.options.Protocol, s.options.Uri) if err != nil { return err } log.Printf("LightRPC: %s %s \n", s.options.Protocol, s.options.Uri) // 这里是服务注册 咱们这里先跳过 if s.options.Discovery != nil { // 读取服务配置文件 sIdb, err := ioutil.ReadFile("./light.conf") if err != nil { // 如果没有 就生成 分布式ID id, err := utils.DistributedID() if err != nil { return err } sIdb = []byte(id) } // 进行服务注册 sId := string(sIdb) for k := range s.serviceMap { // 进行服务注册 err := s.options.Discovery.Registry(k, s.options.registryAddr, s.options.weights, s.options.Protocol, s.options.MaximumLoad, &sId) if err != nil { return err } log.Printf("Discovery Registry: %s addr: %s SUCCESS", k, s.options.registryAddr) } ioutil.WriteFile("./light.conf", sIdb, 00666) } // 启动服务 return s.run()}func (s *Server) run() error {loop: for { select { case <-s.options.ctx.Done(): // 查看是否须要退出服务 break loop default: accept, err := s.options.nl.Accept() // 获取一个链接 if err != nil { log.Println(err) continue } if s.options.Trace { log.Println("connect: ", accept.RemoteAddr()) } go s.process(accept) // 开一个协程去解决 该 链接 } } return nil}咱们先回顾一下 上章讲的 握手逻辑建设链接 通过非对称加密 传输 aes 密钥给服务端 (携带token)服务端 验证 token 并记录 aes 密钥 前面与客户端交互 都采纳对称加密具体解决 链接 process (重点!!!)func (s *Server) process(conn net.Conn) { defer func() { // 网络不牢靠 if err := recover(); err != nil { utils.PrintStack() log.Println("Recover Err: ", err) } }() // 每进来一个申请这里就ADD s.options.Discovery.Add(1) defer func() { s.options.Discovery.Less(1) // 解决完 申请就退出 // 退出 回收句柄 err := conn.Close() if err != nil { log.Println(err) return } if s.options.Trace { log.Println("close connect: ", conn.RemoteAddr()) } }() // 这里定义一个xChannel 用于拆散 申请和返回 xChannel := utils.NewXChannel(s.options.processChanSize) // 握手 handshake := protocol.Handshake{} err := handshake.Handshake(conn) if err != nil { return } // 非对称加密 解密 AES KEY aesKey, err := cryptology.RsaDecrypt(handshake.Key, s.options.RSAPrivateKey) if err != nil { encodeHandshake := protocol.EncodeHandshake([]byte(""), []byte(""), []byte(err.Error())) conn.Write(encodeHandshake) return } // 检测 AES KEY 是否正确 if len(aesKey) != 32 && len(aesKey) != 16 { encodeHandshake := protocol.EncodeHandshake([]byte(""), []byte(""), []byte("aes key != 32 && key != 16")) conn.Write(encodeHandshake) return } // 解密 TOKEN token, err := cryptology.RsaDecrypt(handshake.Token, s.options.RSAPrivateKey) if err != nil { encodeHandshake := protocol.EncodeHandshake([]byte(""), []byte(""), []byte(err.Error())) conn.Write(encodeHandshake) return } // 对TOKEN进行校验 if s.options.AuthFunc != nil { err := s.options.AuthFunc(light.DefaultCtx(), string(token)) if err != nil { encodeHandshake := protocol.EncodeHandshake([]byte(""), []byte(""), []byte(err.Error())) conn.Write(encodeHandshake) return } } // limit 限流 if s.options.Discovery.Limit() { // 熔断 encodeHandshake := protocol.EncodeHandshake([]byte(""), []byte(""), []byte(pkg.ErrCircuitBreaker.Error())) conn.Write(encodeHandshake) log.Println(s.options.Discovery.Limit()) return } // 如果握手没有问题 则返回握手胜利 encodeHandshake := protocol.EncodeHandshake([]byte(""), []byte(""), []byte("")) _, err = conn.Write(encodeHandshake) if err != nil { return } // send go func() { loop: for { select { // 这就是刚刚的xChannel 对读写进行拆散 case msg, ex := <-xChannel.Ch: if !ex { if s.options.Trace { log.Printf("ip: %s close send server", conn.RemoteAddr()) } break loop } now := time.Now() if s.options.writeTimeout > 0 { conn.SetWriteDeadline(now.Add(s.options.writeTimeout)) } // send message _, err := conn.Write(msg) if err != nil { if s.options.Trace { log.Printf("ip: %s err: %s", conn.RemoteAddr(), err) } break loop } } } }() defer func() { xChannel.Close() }()loop: for { // 具体音讯获取 now := time.Now() if s.options.readTimeout > 0 { conn.SetReadDeadline(now.Add(s.options.readTimeout)) } proto := protocol.NewProtocol() msg, err := proto.IODecode(conn) // 获取一个音讯 if err != nil { if err == io.EOF { if s.options.Trace { log.Printf("ip: %s close", conn.RemoteAddr()) } break loop } // 遇到谬误敞开链接 if s.options.Trace { log.Printf("ip: %s err: %s", conn.RemoteAddr(), err) } break loop } go s.processResponse(xChannel, msg, conn.RemoteAddr().String(), aesKey) }}具体解决 (重点!!!)留神此RPC传输音讯都是编码过的 要进行转码 ...

July 23, 2021 · 7 min · jiezi

关于golang:用GO写一个RPC框架-s03-协议设计

前言该RPC反对 TCP KCP UNIX 所以咱们要对协定进行设计 https://github.com/dollarkill...定义握手协定握手逻辑: 建设链接 通过非对称加密 传输 aes 密钥给服务端 (携带token)服务端 验证 token 并记录 aes 密钥 前面与客户端交互 都采纳对称加密起始符keySizetokenSizeerrorSizekeytokenerr0x09444xxxxxxxxxxxxtype Handshake struct { Key []byte // aes 加密 KEY Token []byte // AUTH 校验应用 Error []byte // 谬误填充}编码func EncodeHandshake(key, token, err []byte) []byte { buf := make([]byte, 13+len(key)+len(token)+len(err)) buf[0] = LightHandshakeSt binary.LittleEndian.PutUint32(buf[1:5], uint32(len(key))) binary.LittleEndian.PutUint32(buf[5:9], uint32(len(token))) binary.LittleEndian.PutUint32(buf[9:13], uint32(len(err))) st := 13 endI := st + len(key) copy(buf[st:endI], key) st = endI endI = st + len(token) copy(buf[st:endI], token) st = endI endI = st + len(err) copy(buf[st:endI], err) return buf}解码func (h *Handshake) Handshake(r io.Reader) error { headerByte := make([]byte, 13) // 读取标记位 _, err := io.ReadFull(r, headerByte[:1]) if err != nil { return err } if headerByte[0] != LightHandshakeSt { return errors.New("NO Light Handshake") } // 读取剩下的 _, err = io.ReadFull(r, headerByte[1:]) if err != nil { return err } // 解析头 keySize := binary.LittleEndian.Uint32(headerByte[1:5]) tokenSize := binary.LittleEndian.Uint32(headerByte[5:9]) errorSize := binary.LittleEndian.Uint32(headerByte[9:13]) bodyData := make([]byte, keySize+tokenSize+errorSize) _, err = io.ReadFull(r, bodyData) if err != nil { return err } var st uint32 = 0 endIdx := keySize key := make([]byte, keySize) copy(key, bodyData[st:endIdx]) h.Key = key st = endIdx endIdx = st + tokenSize token := make([]byte, tokenSize) copy(token, bodyData[st:endIdx]) h.Token = token st = endIdx endIdx = st + errorSize errStr := make([]byte, errorSize) copy(errStr, bodyData[st:endIdx]) h.Error = errStr return nil}定义传输协定/** 协定设计 起始符 : 版本号 : crc32校验 : magicNumberSize: serverNameSize : serverMethodSize : metaDataSize : payloadSize: respType : compressorType : serializationType : magicNumber : serverName : serverMethod : metaData : payload 0x05 : 0x01 : 4 : 4 : 4 : 4 : 4 : 4 : 1 : 1 : 1 : xxx : xxx : xxx : xxx : xxx*/type Message struct { Header *Header MagicNumber string ServiceName string ServiceMethod string MetaData []byte Payload []byte}type Header struct { St byte Version byte Crc32 uint32 MagicNumberSize uint32 ServerNameSize uint32 ServerMethodSize uint32 MetaDataSize uint32 PayloadSize uint32 RespType byte CompressorType byte SerializationType byte}编码// EncodeMessage 根底编码func EncodeMessage(magicStr string, server, method, metaData []byte, respType, compressorType, serializationType byte, payload []byte) (magic string, data []byte, err error) { var magicNumber = []byte(magicStr) if magicStr == "" { magicNumber = []byte(xid.New().String()) } bufSize := HeadSize + len(server) + len(method) + len(metaData) + len(payload) + len(magicNumber) buf := make([]byte, bufSize) buf[0] = LightSt buf[1] = byte(V1) binary.LittleEndian.PutUint32(buf[6:10], uint32(len(magicNumber))) binary.LittleEndian.PutUint32(buf[10:14], uint32(len(server))) binary.LittleEndian.PutUint32(buf[14:18], uint32(len(method))) binary.LittleEndian.PutUint32(buf[18:22], uint32(len(metaData))) binary.LittleEndian.PutUint32(buf[22:26], uint32(len(payload))) buf[26] = respType buf[27] = compressorType buf[28] = serializationType st := HeadSize endI := st + len(magicNumber) copy(buf[st:endI], magicNumber) st = endI endI = st + len(server) copy(buf[st:endI], server) st = endI endI = st + len(method) copy(buf[st:endI], method) st = endI endI = st + len(metaData) copy(buf[st:endI], metaData) st = endI endI = st + len(payload) copy(buf[st:endI], payload) if Crc32 { u := crc32.ChecksumIEEE(buf[6:]) binary.LittleEndian.PutUint32(buf[2:6], u) } return string(magicNumber), buf, nil}解码func (m *Protocol) IODecode(r io.Reader) (*Message, error) { headerByte := make([]byte, HeadSize) // 读取标记位 _, err := io.ReadFull(r, headerByte[:1]) if err != nil { return nil, err } if headerByte[0] != LightSt { log.Println(headerByte) return nil, errors.New("NO Light") } // 读取剩下的 _, err = io.ReadFull(r, headerByte[1:]) if err != nil { return nil, err } // 解析 header header, err := DecodeHeader(headerByte) if err != nil { return nil, err } bodyLen := header.MagicNumberSize + header.ServerNameSize + header.ServerMethodSize + header.MetaDataSize + header.PayloadSize bodyData := make([]byte, bodyLen) _, err = io.ReadFull(r, bodyData) if err != nil { return nil, err } msg, err := DecodeMessageV2(bodyData, header, 0) if err != nil { return nil, err } return msg, nil}func DecodeHeader(data []byte) (*Header, error) { var header Header header.St = data[0] header.Version = data[1] header.Crc32 = binary.LittleEndian.Uint32(data[2:6]) if Crc32 { u := crc32.ChecksumIEEE(data[6:]) if header.Crc32 != u { return nil, errors.New("CRC Calibration") } } header.MagicNumberSize = binary.LittleEndian.Uint32(data[6:10]) header.ServerNameSize = binary.LittleEndian.Uint32(data[10:14]) header.ServerMethodSize = binary.LittleEndian.Uint32(data[14:18]) header.MetaDataSize = binary.LittleEndian.Uint32(data[18:22]) header.PayloadSize = binary.LittleEndian.Uint32(data[22:26]) header.RespType = data[26] header.CompressorType = data[27] header.SerializationType = data[28] return &header, nil}// DecodeMessage 残缺Decodefunc DecodeMessage(data []byte) (*Message, error) { header, err := DecodeHeader(data) if err != nil { return nil, err } return DecodeMessageV2(data, header, HeadSize)}func DecodeMessageV2(data []byte, header *Header, headSize uint32) (*Message, error) { var result Message result.Header = header var st uint32 = headSize endI := st + header.MagicNumberSize les := endI - st magicNumber := make([]byte, les) copy(magicNumber, data[st:endI]) result.MagicNumber = string(magicNumber) st = endI endI = st + header.ServerNameSize les = endI - st serverName := make([]byte, les) copy(serverName, data[st:endI]) result.ServiceName = string(serverName) st = endI endI = st + header.ServerMethodSize les = endI - st serverMethodSize := make([]byte, les) copy(serverMethodSize, data[st:endI]) result.ServiceMethod = string(serverMethodSize) st = endI endI = st + header.MetaDataSize les = endI - st metaDataSize := make([]byte, les) copy(metaDataSize, data[st:endI]) result.MetaData = metaDataSize st = endI endI = st + header.PayloadSize les = endI - st payloadSize := make([]byte, les) copy(payloadSize, data[st:endI]) result.Payload = payloadSize return &result, nil}

July 23, 2021 · 4 min · jiezi

关于golang:用GO写一个RPC框架-s02服务端配置

前言上一篇 咱们讲了服务注册的外部设计, 写了一个服务的管理工具 type Server struct { serviceMap map[string]*service options *Options beforeMiddleware []MiddlewareFunc afterMiddleware []MiddlewareFunc beforeMiddlewarePath map[string][]MiddlewareFunc afterMiddlewarePath map[string][]MiddlewareFunc}这次咱们看看服务的配置如何写 才最优雅 先来看看Options type Options struct { Protocol transport.Protocol UseHttp bool Uri string nl net.Listener ctx context.Context options map[string]interface{} // 零散配置 Trace bool readTimeout time.Duration writeTimeout time.Duration processChanSize int MaximumLoad int64 RSAPublicKey []byte RSAPrivateKey []byte AuthFunc AuthFunc Discovery discovery.Discovery registryAddr string weights float64}这里有十分多 能够填写的中央如果咱们采纳 最原始的形式 这个配置就会十分的简短 不好保护 func NewServer(protocol transport.Protocol, useHttp bool, uri string, nl net.listener ...)咱们当初引入一个新的设计形式 ...

July 23, 2021 · 1 min · jiezi

关于golang:用GO写一个RPC框架-s01服务内部注册实现

Dubbo/Dubbox、Google gRPC、RPCX、Spring Boot/Spring Cloud 如此多的RPC框架 咱们当然要理解下他们的原理呀 咱们以 LightRPC github.com/dollarkillerx/light 为例 写一个属于本人的RPC 最初的成绩是怎么的https://github.com/dollarkill...payload: // 申请体strcut Request { Msg string}// 返回体struct Response { Msg string}server: // 定义根底服务struct HelloWorldServer {}func (h *HelloWorldServer) HelloWorld(ctx *light.Context, request *Request, response *Response) error { response.Msg = fmt.Sprintf("request: %s ",request.Msg) return nil}// 启动服务func main() { ser := server.NewServer() err := ser.RegisterName(&HelloWorldServer{}, "HelloWorldServer") // 注册 服务 if err != nil { log.Fatalln(err) } // 监听服务 if err := ser.Run(server.UseTCP("0.0.0.0:8074")); err != nil { log.Fatalln(err) } }client: ...

July 23, 2021 · 3 min · jiezi

关于golang:Go语言new还是make到底该如何选择

new 函数咱们间接申明一个指针类型的变量 p ,而后对扭转量的值进行批改,为“微客鸟窝”: func main() { var p *string *p = "微客鸟窝" fmt.Println(*p)}程序运行,会报错: panic: runtime error: invalid memory address or nil pointer dereference这是因为指针类型的变量如果没有分配内存,默认值是零值 nil。它没有指向的内存,所以无奈应用。 如果要应用,给它调配一块内存就能够了,能够应用 new函数: func main() { var p *string p = new(string) //new函数进行内存调配 *p = "微客鸟窝" fmt.Println(*p)}下面示例便能够失常运行,内置函数 new 的作用是什么呢?源码剖析: // The new built-in function allocates memory. The first argument is a type,// not a value, and the value returned is a pointer to a newly// allocated zero value of that type.func new(Type) *Type作用: ...

July 23, 2021 · 2 min · jiezi

关于golang:一文详读-Go-By-Example

通道同步func worker(done chan bool) { fmt.Println("working...") time.Sleep(2 * time.Second) fmt.Println("done") done <- true}func main() { done := make(chan bool, 1) go worker(done) <-done}后果: working...done[Finished in 3.1s]通道抉择import ( "fmt" "time")func main() { ch1 := make(chan string, 1) ch2 := make(chan string, 2) go func() { time.Sleep(time.Second * 2) ch1 <- "one" }() go func() { time.Sleep(time.Second) ch2 <- "two" }() select { case msg1 := <-ch1: fmt.Println(msg1) case msg2 := <-ch2: fmt.Println(msg2) }}后果: ...

July 23, 2021 · 8 min · jiezi

关于golang:Go-每日一库之-gorillasecurecookie

简介cookie 是用于在 Web 客户端(个别是浏览器)和服务器之间传输大量数据的一种机制。由服务器生成,发送到客户端保留,客户端后续的每次申请都会将 cookie 带上。cookie 当初曾经被多多少少地滥用了。很多公司应用 cookie 来收集用户信息、投放广告等。 cookie 有两大毛病: 每次申请都须要传输,故不能用来寄存大量数据;安全性较低,通过浏览器工具,很容易看到由网站服务器设置的 cookie。gorilla/securecookie提供了一种平安的 cookie,通过在服务端给 cookie 加密,让其内容不可读,也不可伪造。当然,敏感信息还是强烈建议不要放在 cookie 中。 疾速应用本文代码应用 Go Modules。 创立目录并初始化: $ mkdir gorilla/securecookie && cd gorilla/securecookie$ go mod init github.com/darjun/go-daily-lib/gorilla/securecookie装置gorilla/securecookie库: $ go get github.com/gorilla/securecookiepackage mainimport ( "fmt" "github.com/gorilla/mux" "github.com/gorilla/securecookie" "log" "net/http")type User struct { Name string Age int}var ( hashKey = securecookie.GenerateRandomKey(16) blockKey = securecookie.GenerateRandomKey(16) s = securecookie.New(hashKey, blockKey))func SetCookieHandler(w http.ResponseWriter, r *http.Request) { u := &User { Name: "dj", Age: 18, } if encoded, err := s.Encode("user", u); err == nil { cookie := &http.Cookie{ Name: "user", Value: encoded, Path: "/", Secure: true, HttpOnly: true, } http.SetCookie(w, cookie) } fmt.Fprintln(w, "Hello World")}func ReadCookieHandler(w http.ResponseWriter, r *http.Request) { if cookie, err := r.Cookie("user"); err == nil { u := &User{} if err = s.Decode("user", cookie.Value, u); err == nil { fmt.Fprintf(w, "name:%s age:%d", u.Name, u.Age) } }}func main() { r := mux.NewRouter() r.HandleFunc("/set_cookie", SetCookieHandler) r.HandleFunc("/read_cookie", ReadCookieHandler) http.Handle("/", r) log.Fatal(http.ListenAndServe(":8080", nil))}首先须要创立一个SecureCookie对象: ...

July 23, 2021 · 4 min · jiezi

关于golang:Go-每日一库之-gorillaschema

简介gorilla/schema 是 gorilla 开发工具包中用于解决表单的库。它提供了一个简略的形式,能够很不便地将表单数据转为构造体对象,或者将构造体对象转为表单数据。 疾速应用本文代码应用 Go Modules。 创立目录并初始化: $ mkdir gorilla/schema && cd gorilla/schema$ go mod init github.com/darjun/go-daily-lib/gorilla/schema装置gorilla/schema库: $ go get -u github.com/gorilla/schema咱们还是拿后面登录的例子: func index(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "Hello World")}func login(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, `<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Login</title></head><body><form action="/login" method="post"> <label>Username:</label> <input name="username"><br> <label>Password:</label> <input name="password" type="password"><br> <button type="submit">登录</button></form></body></html>`)}type User struct { Username string `schema:"username"` Password string `schema:"password"`}var ( decoder = schema.NewDecoder())func dologin(w http.ResponseWriter, r *http.Request) { r.ParseForm() u := User{} decoder.Decode(&u, r.PostForm) if u.Username == "dj" && u.Password == "handsome" { http.Redirect(w, r, "/", 301) return } http.Redirect(w, r, "/login", 301)}func main() { r := mux.NewRouter() r.HandleFunc("/", index) r.Handle("/login", handlers.MethodHandler{ "GET": http.HandlerFunc(login), "POST": http.HandlerFunc(dologin), }) log.Fatal(http.ListenAndServe(":8080", r))}首先调用schema.NewDecoder()办法创立一个解码器decoder。在处理器中,先调用r.ParseForm()解析表单数据,而后创立用户对象u,调用decoder.Decode(&u, r.PostForm)通过表单数据填充该对象。 ...

July 23, 2021 · 2 min · jiezi

关于golang:goquery-选择器的使用

Jquery 的选择器堪称之弱小无比,这里简略地总结一下罕用的元素查找办法. $("#myELement") 抉择id值等于myElement的元素,id值不能反复在文档中只能有一个id值是myElement所以失去的是惟一的元素$("div") 抉择所有的div标签元素,返回div元素数组$(".myClass") 抉择应用myClass类的css的所有元素$("*") 抉择文档中的所有的元素,能够使用多种的抉择形式进行联结抉择:例如$("#myELement,div,.myclass") 层叠选择器:$("form input") 抉择所有的form元素中的input元素$("#main > *") 抉择id值为main的所有的子元素$("label + input") 抉择所有的label元素的下一个input元素节点,经测试选择器返回的是label标签前面间接跟一个input标签的所有input标签元素$("#prev ~ div") 同胞选择器,该选择器返回的为id为prev的标签元素的所有的属于同一个父元素的div标签 根本过滤选择器:$("tr:first") 抉择所有tr元素的第一个$("tr:last") 抉择所有tr元素的最初一个$("input:not(:checked) + span")过滤掉:checked的选择器的所有的input元素 $("tr:even") 抉择所有的tr元素的第0,2,4... ...个元素(留神:因为所抉择的多个元素时为数组,所以序号是从0开始) $("tr:odd") 抉择所有的tr元素的第1,3,5... ...个元素$("td:eq(2)") 抉择所有的td元素中序号为2的那个td元素$("td:gt(4)") 抉择td元素中序号大于4的所有td元素$("td:ll(4)") 抉择td元素中序号小于4的所有的td元素$(":header")$("div:animated") 内容过滤选择器:$("div:contains('John')") 抉择所有div中含有John文本的元素$("td:empty") 抉择所有的为空(也不包含文本节点)的td元素的数组$("div:has(p)") 抉择所有含有p标签的div元素$("td:parent") 抉择所有的以td为父节点的元素数组 可视化过滤选择器:$("div:hidden") 抉择所有的被hidden的div元素$("div:visible") 抉择所有的可视化的div元素 属性过滤选择器:$("div[id]") 抉择所有含有id属性的div元素$("input[name='newsletter']") 抉择所有的name属性等于'newsletter'的input元素$("input[name!='newsletter']") 抉择所有的name属性不等于'newsletter'的input元素$("input[name^='news']") 抉择所有的name属性以'news'结尾的input元素$("input[name$='news']") 抉择所有的name属性以'news'结尾的input元素$("input[name*='man']") 抉择所有的name属性蕴含'news'的input元素$("input[id][name$='man']") 能够应用多个属性进行联结抉择,该选择器是失去所有的含有id属性并且那么属性以man结尾的元素 子元素过滤选择器:$("ul li:nth-child(2)"),$("ul li:nth-child(odd)"),$("ul li:nth-child(3n + 1)")$("div span:first-child") 返回所有的div元素的第一个子节点的数组$("div span:last-child") 返回所有的div元素的最初一个节点的数组$("div button:only-child") 返回所有的div中只有惟一一个子节点的所有子节点的数组 表单元素选择器:$(":input") 抉择所有的表单输出元素,包含input, textarea, select 和 button$(":text") 抉择所有的text input元素$(":password") 抉择所有的password input元素$(":radio") 抉择所有的radio input元素$(":checkbox") 抉择所有的checkbox input元素$(":submit") 抉择所有的submit input元素$(":image") 抉择所有的image input元素$(":reset") 抉择所有的reset input元素$(":button") 抉择所有的button input元素$(":file") 抉择所有的file input元素$(":hidden") 抉择所有类型为hidden的input元素或表单的暗藏域 ...

July 22, 2021 · 1 min · jiezi

关于golang:Go语言参数传递中值引用及指针之间的区别

值类型、援用类型1、在Go语言中,值类型和援用类型有以下特点: a、值类型:根本数据类型,int,float,bool,string,以及数组和struct特点:变量间接存储值,内存通常在栈上调配,栈在函数调用完会被开释 b、援用类型:指针,slice,map,chan,interface等都是援用类型特点:变量存储的是一个地址,这个地址存储最终的值。内存通常在堆上调配,通过GC回收。 严格来说,Go 语言没有援用类型。然而咱们能够把 map、chan、函数、接口、slice 切片, 称为援用类型,这样便于了解。指针类型也能够了解为是一种援用类型。下面咱们提到了堆、栈,这里简略介绍下 内存调配中的堆和栈: 栈(操作系统):由操作系统主动调配开释 ,寄存函数的参数值,局部变量的值等。其操作形式相似于数据结构中的栈。堆(操作系统): 个别由程序员调配开释, 若程序员不开释,程序完结时可能由OS回收,调配形式倒是相似于链表。值类型和指针类型参数示例: package mainimport "fmt"func main() { name := "无尘" modify1(name) fmt.Println("name的值为:", name) modify2(&name) fmt.Println("name的值为:", name)}func modify1(name string) { //值类型 name = "wucs"} func modify2(name *string) { //指针类型 *name = "wucs"}//运行后果://name的值为: 无尘//name的值为: wucs援用类型map以map类型为参数示例: package mainimport "fmt"func main() { m:=make(map[string]int) m["无尘"] = 18 fmt.Println("无尘的年龄为",m["无尘"]) modify(m) fmt.Println("无尘的年龄为",m["无尘"])}func modify(p map[string]int) { p["无尘"] =20}//运行后果://无尘的年龄为 18//无尘的年龄为 20咱们看到,函数 modify 的参数类型为 map ,数据依然批改胜利了。其实,在创立 map 的时候,最终调用的是 runtime.makemap 函数,makemap 函数返回的是一个 hmap 类型,也就是说返回的是一个指针,所以咱们创立的 map 其实就是一个 hmap。因为 map 实质上就是个指针,所以通过 map 类型的参数能够批改原始数据。 ...

July 22, 2021 · 1 min · jiezi

关于golang:Go-每日一库之-gorillahandlers

简介上一篇文章中,咱们介绍了 gorilla web 开发工具包中的路由治理库gorilla/mux,在文章最初咱们介绍了如何应用中间件解决通用的逻辑。在日常 Go Web 开发中,开发者遇到了很多雷同的中间件需要,gorilla/handlers(后文简称为handlers)收集了一些比拟罕用的中间件。一起来看看吧~ 对于中间件,后面几篇文章曾经介绍的很多了。这里就不赘述了。handlers库提供的中间件可用于规范库net/http和所有反对http.Handler接口的框架。因为gorilla/mux也反对http.Handler接口,所以也能够与handlers库联合应用。这就是兼容规范的益处。 我的项目初始化&装置本文代码应用 Go Modules。 创立目录并初始化: $ mkdir gorilla/handlers && cd gorilla/handlers$ go mod init github.com/darjun/go-daily-lib/gorilla/handlers装置gorilla/handlers库: $ go get -u github.com/gorilla/handlers上面顺次介绍各个中间件和相应的源码。 日志handlers提供了两个日志中间件: LoggingHandler:以 Apache 的Common Log Format日志格局记录 HTTP 申请日志;CombinedLoggingHandler:以 Apache的Combined Log Format日志格局记录 HTTP 申请日志,Apache 和 Nginx 默认都应用这种日志格局。两种日志格局差异很小,Common Log Format格局如下: %h %l %u %t "%r" %>s %b各个批示符含意如下: %h:客户端的 IP 地址或主机名;%l:RFC 1413定义的客户端标识,由客户端机器上的identd程序生成。如果不存在,则该字段为-;%u:已验证的用户名。如果不存在,该字段为-;%t:工夫,格局为day/month/year:hour:minute:second zone,其中: day: 2位数字;month:月份缩写,3个字母,如Jan;year:4位数字;hour:2位数字;minute:2位数字;second:2位数字;zone:+或-后跟4位数字;例如:21/Jul/2021:06:27:33 +0800%r:蕴含 HTTP 申请行信息,例GET /index.html HTTP/1.1;%>s:服务器发送给客户端的状态码,例如200;%b:响应长度(字节数)。Combined Log Format格局如下: %h %l %u %t "%r" %>s %b "%{Referer}i" "%{User-Agent}i"可见相比Common Log Format只是多了: ...

July 22, 2021 · 5 min · jiezi

关于golang:golang-gin框架实现https访问

起因: 在应用facebook三方登陆时,无效 OAuth 跳转 URI这一项目前看来只反对https拜访,本地测试就要实现httpsmkcert 简介mkcert 是一个应用go语言编写的生成本地自签证书的小工具,具备跨平台,应用简略,反对多域名,主动信赖CA等一系列不便的个性,可供本地开发时疾速创立 https 环境应用。github介绍:https://github.com/FiloSottil... macOSOn macOS, use Homebrew brew install mkcertbrew install nss # if you use Firefox$ mkcert -installCreated a new local CA The local CA is now installed in the system trust store! ⚡️The local CA is now installed in the Firefox trust store (requires browser restart)! $ mkcert example.com "*.example.com" example.test localhost 127.0.0.1 ::1Created a new certificate valid for the following names - "example.com" - "*.example.com" - "example.test" - "localhost" - "127.0.0.1" - "::1"The certificate is at "./example.com+5.pem" and the key at "./example.com+5-key.pem" ✅代码开发:加一个中间件 ...

July 21, 2021 · 1 min · jiezi

关于golang:Go-语言什么情况下应该使用指针

什么是指针咱们都晓得,程序运行时的数据是寄存在内存中的,每一个存储在内存中的数据都有一个编号,这个编号就是内存地址。咱们能够依据这个内存地址来找到内存中存储的数据,而内存地址能够被赋值给一个指针。咱们也能够简略的了解为指针就是内存地址。 指针的申明和定义在Go语言中,获取一个指针,间接应用取地址符&就能够。 示例: func main() { name := "微客鸟窝" nameP := &name //取地址 fmt.Println("name变量值为:", name) fmt.Println("name变量的内存地址为:", nameP)}//运行后果://name变量值为: 微客鸟窝//name变量的内存地址为: 0xc00004e240nameP 指针的类型是 *stringGo语言中,*类型名示意一个对应的指针类型变量内存中数据内存地址name := "微客鸟窝"微客鸟窝0xc00004e240nameP := &name0xc00004e2400xc00004e360从下面表格能够看到: 一般变量 name 的值是微客鸟窝,寄存在内存地址为 0xc00004e240 的内存中指针变量 namep 的值是一般变量的内存地址 0xc00004e240指针变量 nameP 的值寄存在 内存地址为 0xc00004e360 的内存中一般变量存的是数据,指针变量存的是数据的地址var 关键字申明咱们也能够应用 var 关键字申明 var nameP *stringnameP = &namenew 函数申明nameP := new(string)nameP = &name能够传递类型给这个内置的 new 函数,它会返回对应的指针类型。 指针的操作这里强调一下: 指针变量是一个变量,这个变量的值是指针(内存地址)! 指针变量是一个变量,这个变量的值是指针(内存地址)! 指针变量是一个变量,这个变量的值是指针(内存地址)! 获取指针指向的值:只须要在指针变量钱加 * 号即可取得指针变量值所对应的数据: nameV := *namePfmt.Println("nameP指针指向的值为:",nameV) //nameP指针指向的值为: 微客鸟窝批改指针指向的值: *nameP = "公众号:微客鸟窝" //批改指针指向的值fmt.Println("nameP指针指向的值为:",*nameP)fmt.Println("name变量的值为:",name)//运行后果://nameP指针指向的值为: 公众号:微客鸟窝//name变量的值为: 公众号:微客鸟窝咱们发现nameP 指针指向的值被扭转了,变量 name 的值也被扭转了因为变量 name 存储数据的内存就是指针 nameP 指向的内存,这块内存被 nameP 批改后,变量 name 的值也被批改了。通过 var 关键字间接定义的指针变量是不能进行赋值操作的,因为它的值为 nil,也就是还没有指向的内存地址 ...

July 21, 2021 · 1 min · jiezi

关于golang:gozero框架之zrpcRpcServerConf配置源码分析

在go-zero中应用zrpc包来配置rpc服务,zrpc/config.go文件中定义了两个构造体(RpcServerConf和RpcClientConf)。 一、RpcServerConf构造体:type RpcServerConf struct { service.ServiceConf ListenOn string Etcd discov.EtcdConf `json:",optional"` Auth bool `json:",optional"` Redis redis.RedisKeyConf `json:",optional"` StrictControl bool `json:",optional"` // setting 0 means no timeout Timeout int64 `json:",default=2000"` CpuThreshold int64 `json:",default=900,range=[0:1000]"`}service.ServiceConf:调用的是core/serviceConf.go配置文件,文件中定义ServiceConf构造体,其中Prometheus字段被定义在core/prometheus/config.go文件// 服务端的根本配置信息type ServiceConf struct { Name string Log logx.LogConf Mode string `json:",default=pro,options=dev|test|rt|pre|pro"` MetricsUrl string `json:",optional"` Prometheus prometheus.Config `json:",optional"`}// prometheus配置type Config struct { Host string `json:",optional"` Port int `json:",default=9101"` Path string `json:",default=/metrics"`}ListenOn:监听门路Etcd:etcd配置,该项调用的是core/discov/config.go文件中的EtcdConf构造体,构造体有一个Validate()办法,该办法用于判断Hosts和Key是否为空,如果为空返回谬误,反之返回nil。// EtcdConf is the config item with the given key on etcd.type EtcdConf struct { Hosts []string Key string}// Validate validates c.func (c EtcdConf) Validate() error { if len(c.Hosts) == 0 { return errors.New("empty etcd hosts") } else if len(c.Key) == 0 { return errors.New("empty etcd key") } else { return nil }}Auth:bool值Redis:redis配置,调用的是core/stores/redis/conf.go文件中的RedisKeyConf构造体。该文件定义两个构造体:RedisKeyConf和RedisConf,RedisKeyConf内嵌RedisConf。 ...

July 20, 2021 · 2 min · jiezi

关于golang:一文搞懂一致性hash的原理和实现

在 go-zero 的分布式缓存零碎分享里,Kevin 重点讲到过一致性hash的原理和分布式缓存中的实际。本文来具体讲讲一致性hash的原理和在 go-zero 中的实现。 以存储为例,在整个微服务零碎中,咱们的存储不可能说只是一个单节点。 一是为了进步稳固,单节点宕机状况下,整个存储就面临服务不可用;二是数据容错,同样单节点数据物理损毁,而多节点状况下,节点有备份,除非互为备份的节点同时损毁。那么问题来了,多节点状况下,数据应该写入哪个节点呢? hash 所以实质来讲:咱们须要一个能够将输出值“压缩”并转成更小的值,这个值通常情况下是惟一、格局极其紧凑的,比方uint64: 幂等:每次用同一个值去计算 hash 必须保障都能失去同一个值这个就是 hash 算法实现的。 然而采取一般的 hash 算法进行路由,如:key % N 。有一个节点因为异样退出了集群或者是心跳异样,这时再进行 hash route ,会造成大量的数据从新 散发到不同的节点 。节点在承受新的申请时候,须要重新处理获取数据的逻辑:如果是在缓存中,容易引起 缓存雪崩。 此时就须要引入 consistent hash 算法了。 consistent hash咱们来看看 consistent hash 是怎么解决这些问题的: rehash先解决大量 rehash 的问题: 如上图,当退出一个新的节点时,影响的key只有 key31,新退出(剔除)节点后,只会影响该节点左近的数据。其余节点的数据不会收到影响,从而解决了节点变动的问题。 这个正是:枯燥性。这也是 normal hash 算法无奈满足分布式场景的起因。 数据歪斜其实上图能够看出:目前少数的key都集中在 node 1 上。如果当 node 数量比拟少的状况下,能够回引发少数 key 集中在某个 node 上,监控时发现的问题就是:节点之间负载不均。 为了解决这个问题,consistent hash 引入了 virtual node 的概念。 既然是负载不均,咱们就人为地结构一个平衡的场景进去,然而理论 node 只有这么多。所以就应用 virtual node 划分区域,而理论服务的节点仍然是之前的 node。 ...

July 20, 2021 · 1 min · jiezi

关于golang:Go语言你必须掌握的高效并发模式

对于并发操作,后面咱们曾经理解到了 channel 通道、同步原语 sync 包对共享资源加锁、Context 跟踪协程/传参等,这些都是并发编程比拟根底的元素,置信你曾经有了很好的把握。明天咱们介绍下如何应用这些根底元素组成并发模式,更好的编写并发程序。 for select 有限循环模式这个模式比拟常见,之前文章中的示例也应用过,它个别是和 channel 组合实现工作,格局为: for { //for 有限循环,或者应用 for range 循环 select { //通过 channel 管制 case <-done: return default: //执行具体的工作 }}这种是 for + select 多路复用的并发模式,哪个 case 满足条件就执行对应的分支,直到有满足退出的条件,才会退出循环。没有退出条件满足时,则会始终执行 default 分支for range select 无限循环模式for _,s:=range []int{}{ select { case <-done: return case resultCh <- s: }个别把迭代的内容发送到 channel 上done channel 用于退出 for 循环resultCh channel 用来接管循环的值,这些值能够通过 resultCh 传递给其余调用者select timeout 模式如果一个申请须要拜访服务器获取数据,然而可能因为网络问题而迟迟获取不到响应,这时候就须要设置一个超时工夫: package mainimport ( "fmt" "time")func main() { result := make(chan string) timeout := time.After(3 * time.Second) // go func() { //模仿网络拜访 time.Sleep(5 * time.Second) result <- "服务端后果" }() for { select { case v := <-result: fmt.Println(v) case <-timeout: fmt.Println("网络拜访超时了") return default: fmt.Println("期待...") time.Sleep(1 * time.Second) } }}运行后果: ...

July 20, 2021 · 3 min · jiezi

关于golang:Go-每日一库之-gorillamux

简介gorilla/mux是 gorilla Web 开发工具包中的路由治理库。gorilla Web 开发包是 Go 语言中辅助开发 Web 服务器的工具包。它包含 Web 服务器开发的各个方面,有表单数据处理包gorilla/schema,有 websocket 通信包gorilla/websocket,有各种中间件的包gorilla/handlers,有 session 治理包gorilla/sessions,有平安的 cookie 包gorilla/securecookie。本文先介绍gorilla/mux(下文简称mux),后续文章会顺次介绍下面列举的 gorilla 包。 mux有以下劣势: 实现了规范的http.Handler接口,所以能够与net/http规范库联合应用,十分轻量;能够依据申请的主机名、门路、门路前缀、协定、HTTP 首部、查问字符串和 HTTP 办法匹配处理器,还能够自定义匹配逻辑;能够在主机名、门路和申请参数中应用变量,还能够为之指定一个正则表达式;能够传入参数给指定的处理器让其结构出残缺的 URL;反对路由分组,方便管理和保护。疾速应用本文代码应用 Go Modules。 创立目录并初始化: $ mkdir -p gorilla/mux && cd gorilla/mux$ go mod init github.com/darjun/go-daily-lib/gorilla/mux装置gorilla/mux库: $ go get -u github.com/gorilla/gorilla/mux我当初身边有几本 Go 语言的经典著作: 上面咱们编写一个治理图书信息的 Web 服务。图书由 ISBN 惟一标识,ISBN 意为国际标准图书编号(International Standard Book Number)。 首先定义图书的构造: type Book struct { ISBN string `json:"isbn"` Name string `json:"name"` Authors []string `json:"authors"` Press string `json:"press"` PublishedAt string `json:"published_at"`}var ( mapBooks map[string]*Book slcBooks []*Book)定义init()函数,从文件中加载数据: ...

July 20, 2021 · 4 min · jiezi

关于golang:100-Go-mistakes之意外的变量隐藏

本文是对 《100 Go Mistackes:How to Avoid Them》 一书的翻译。因翻译程度无限,不免存在翻译准确性问题,敬请体谅变量的作用域是指它的可见性。换句话说,程序中的名称在哪局部是无效的。在Go中,在块中申明的变量名称能够在外部块中从新申明。这种被称为变量暗藏的准则很容易呈现谬误。 在上面的例子中,咱们将看到一个对于变量暗藏产生的bug。咱们将应用两种不同的形式创立一个HTTP客户端,具体取决于tracing布尔值: var client *http.Client ①if tracing { client, err := createClientWithTracing() ② if err != nil { return err } log.Println(client)}else { client, err := createDefaultClient() ③ if err != nil { return err } log.Println(client)}//use client① 生命一个client变量② 应用带tracing的创立一个HTTP客户端,client变量在该块内被暗藏了③ 创立一个默认的HTTP客户端,client变量在该模块仍然被暗藏掉了。 首先,咱们申明了一个client变量。而后,在两个外部块中,咱们应用 := 操作符,也叫做短变量申明运算符。该操作符应用和开始的时候雷同的名称创立了一个新的client变量;它不会为第①行中的client变量赋值。因而,在该示例中,HTTP客户端将始终是nil值。 留神:该代码之所以能够编译胜利,是因为在logging调用中应用了外部变量client。否则,咱们就会有编译谬误:client declared and not used。咱们如何确保给client赋值了呢?有两种不同的办法。第一种办法是在外部块中应用长期变量,像上面这样: var client *http.Clientif tracing { c, err := createClientWithTracing() ① if err != nil { return err } client = c ②} else { c, err := createDefaultClient() if err != nil { return err } client = c}// Use client① 创立了一个长期变量c② 将长期变量赋给变量client变量c的生命周期只在if/else块中。而后,咱们将这些变量赋值给client。 ...

July 19, 2021 · 1 min · jiezi

关于golang:gocarbon-143-版本发布新增对jsonUnmarshalJSON的支持

carbon 是一个轻量级、语义化、对开发者敌对的Golang工夫解决库,反对链式调用、农历和gorm、xorm等支流orm 如果您感觉不错,请给个star吧 github:github.com/golang-module/carbon gitee:gitee.com/go-package/carbon 更新日志优化IsZero()办法的判断逻辑修复Microsecond()办法获取毫秒数谬误的bug修复SetMicrosecond()办法设置毫秒数谬误的bug修复Lunar().Festival()办法不是任何节气时panic的bug修复Format()办法无奈原样解析的bug修复ParseByFormat()办法无奈原样解析的bug应用github.com/stretchr/testify/assert库代替原生testing库减少单元测试笼罩场景,单元测试覆盖率晋升到96%对立谬误格局,批改局部谬误文案Lunar()办法实现Stringer接口,能够间接作为字符串输入农历年月日,同Lunar().ToString()新增CreateFromTimestamp()办法对工夫戳是0的判断新增Lunar().ToString()办法获取农历年月日,如二零二零年六月十六新增对json.UnmarshalJSON()的反对

July 19, 2021 · 1 min · jiezi

关于golang:Go-函数式编程Higherorder-function

在申请处理过程中,应用程序会承受和解决申请,而后返回响应后果。在该过程中,还存在一些通用的性能,例如:鉴权、监控、链路追踪。泛滥 RPC 框架会提供称之为 Middleware 或者 Interceptor 等概念,以可插拔的形式来反对上述谈到的泛滥性能。以 gRPC 为例,工作原理如图: 其服务端的接口如下所示: func UnaryServerInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error)func StreamServerInterceptor (srv interface{}, stream grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error能够看到,接口明确定义了输出参数,输入后果。如果咱们要本人实现一个组件,须要反对使用者传入特定的配置,有没有什么方法能够做到呢? 答案是必定的。 Higher-order function在理解具体的解决方案之前,须要先理解一个概念叫Higher-order function(高阶函数) 高阶函数是指至多反对以下特定之一的函数: 将一个或多个函数作为参数(即过程参数),返回函数作为其后果第二点,正是须要的个性。以限流的 interceptor 为例,反对传入自定义的限流器。此时就须要定义一个以限流器为参数的高阶函数,而后返回的函数是框架须要的 Interceptor,并在 Interceptor 函数内应用传入的限流器判断是否须要限流。依照接口限流的 Interceptor 具体实现为: type Limiter interface { Limit(key string) bool}func UnaryServerInterceptor(limiter Limiter) grpc.UnaryServerInterceptor { return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { if limiter.Limit(info.FullMethod) { return nil, status.Errorf(codes.ResourceExhausted, "%s is rejected by grpc_ratelimit middleware, please retry later.", info.FullMethod) } return handler(ctx, req) }}...目前传入的参数是固定的,能够这么来实现。更进一步,如果应用比较复杂,除了以后曾经确定的参数,能够预期当前会减少更多的参数。也就要求以后设计的接口须要有很好的扩展性。还有方法么? ...

July 19, 2021 · 1 min · jiezi

关于golang:Go语言并发控制神器之Context

协程如何退出一个协程启动后,个别是代码执行结束,主动退出,然而如果须要提前终止怎么办呢? 一个方法是定义一个全局变量,协程中通过查看这个变量的变动来决定是否退出。这种方法须要加锁来保障并发平安,说到这里,有没有想的什么解决方案? select + channel 来实现: package mainimport ( "fmt" "sync" "time")func main() { var wg sync.WaitGroup stopWk := make(chan bool) wg.Add(1) go func() { defer wg.Done() worker(stopWk) }() time.Sleep(3*time.Second) //工作3秒 stopWk <- true //3秒后收回进行指令 wg.Wait()}func worker(stopWk chan bool){ for { select { case <- stopWk: fmt.Println("上班咯~~~") return default: fmt.Println("认真摸鱼中,请勿打扰...") } time.Sleep(1*time.Second) }}运行后果: 认真摸鱼中,请勿打扰...认真摸鱼中,请勿打扰...认真摸鱼中,请勿打扰...上班咯~~~能够看到,每秒打印一次“认真摸鱼中,请勿打扰...”,3秒后收回进行指令,程序进入 “上班咯~~~”。 Context 初体验下面咱们应用 select+channel 来实现了协程的终止,然而如果咱们想要同时勾销多个协程怎么办呢?如果须要定时勾销又怎么办呢? 此时,Context 就须要退场了,它能够跟踪每个协程,咱们重写下面的示例: package mainimport ( "context" "fmt" "sync" "time")func main() { var wg sync.WaitGroup ctx, stop := context.WithCancel(context.Background()) wg.Add(1) go func() { defer wg.Done() worker(ctx) }() time.Sleep(3*time.Second) //工作3秒 stop() //3秒后收回进行指令 wg.Wait()}func worker(ctx context.Context){ for { select { case <- ctx.Done(): fmt.Println("上班咯~~~") return default: fmt.Println("认真摸鱼中,请勿打扰...") } time.Sleep(1*time.Second) }}运行后果: ...

July 19, 2021 · 2 min · jiezi

关于golang:Go协程简单学习

什么是协程?协程相似于线程,然而比线程更加轻量。一个程序启动会占用一个过程 而一个过程能够领有多个线程 ,一个线程能够领有多个协程。一个过程至多蕴含一个主线程,一个主线程能够有更多的子线程。 线程有两种调度策略,一是:分时调度,二是:抢占式调度。对于操作系统来说 线程是最小的执行单元 过程是最小的资源管理单位 线程个别有五种状态:初始化、可运行、运行中、阻塞 、销毁。协程是用户态执行,不禁操作系统内核治理 是齐全由程序本人调度和管制的 。协程的创立、切换、挂起、销毁全副为内存操作。协程属于线程,协程是在线程外面执行的。协程调度策略:合作式调度。#### go的goroutine go是一个种对并发十分敌对的语言。它提供了两大机制的简略语法:协程(goroutine)和管道(channel)。 goroutine是轻量级的线程 go在语言层面就反对原生协程 go的协程绝对于线程开销更小 大略在2kb 依据程序开销需要增大或者放大 线程必须指定堆栈的大小 大小是固定的 goroutine 是通过 GPM 调度模型实现的。GPM 调度模型 简略应用协程原生反对package mainimport ( "fmt" "time")func main() { fmt.Println("测试") // 这里开始异步go func() { time.Sleep(time.Microsecond*10) fmt.Println("测试3") }() fmt.Println("测试3") //提早主程序退出time.Sleep(time.Microsecond*100)}Go属于多线程版协程,能够利用多核CPU,同一时间能够有多个协程在调度,会存在并发问题。上面这段代码,执行后果如何。按失常应该是打印1-20package mainimport ( "fmt" "time")var count =0func main() { for i:=0;i<=20;i++ { go func() { count ++ fmt.Println(count) }() } time.Sleep(time.Microsecond*100)}$ go run main.go //第一次执行135214181910111213615161748209721$ go run main.go //第二次执行131825781910111213141516174920216每次执行后果是不一样的。在做写入操作的时候 同时多个协程写入 导致数据乌七八糟的打印 从变量中读取变量是惟一平安的并发解决变量的形式。 你能够有想要多少就多少的读取者, 然而写操作必须要得同步。 有太多的办法能够做到这个了,包含应用一些依赖于非凡的 CPU 指令集的真原子操作。然而,罕用的操作还是应用互斥量。 ...

July 19, 2021 · 2 min · jiezi

关于golang:图解协程调度模型GMP模型

当初无论是客户端、服务端或web开发都会波及到多线程的概念。那么大家也晓得,线程是操作系统可能进行运算调度的最小单位,同一个过程中的多个线程都共享这个过程的全副系统资源。 线程三个基本概念内核线程:在内核空间实现的线程,由内核治理用户线程:在用户空间实现的线程,不归内核治理,由用户态实现治理轻量级过程(LWP):在内核中反对用户线程(用户线程与内核线程的中间层,内核线程的高度形象)线程模型 一对一模型(内核级线程模型)由过程创立LWP,因为每个LWP对应一个内核级线程,所以用户也就是在创立一个一个内核线程,由内核治理线程。咱们熟知的大多数语言就是属于一对一模型,比方C#、java、c等(这些语言也有相应的形式实现用户态线程,只是语言自身的线程是一对一模型)一对多模型(用户级模型)用户过程创立多个用户级线程对应在同一个LWP上,所以实质上在内核态只会有一个内核线程运行,那么用户及线程的调度就是在用户态通过用户空间的线程库对线程进行治理。 这类模型长处就是调度在用户态,所以不必频繁地进行内核态与用户态切换,大大晋升线程效率。python语言的协程就是这种模型实现的,然而这种模型并不能实现真正意义上的的并发。 多对多模型(用户级与内核级线程模型)多对多模型也就是在上述两个模型根底上做一些长处的汇合。用户态的多个线程对应多个LWP,但它们之间不是一一对应的,在用户态治理调度每个LWP对应的用户线程绑定关系。 所以多对多模型是能够有更多的用户态线程在绝对于比拟少的内核线程上运行的。这种用户态线程也就是咱们平时说的协程。Go语言的协程就是多对多模型,它由Go语言的runtime在用户态做调度,这也是Go语言高并发的起因。 协程(Goroutine)后面咱们说到,协程就是用户态线程,它的所有调度都在用户态实现,协程这方面Go语言应该是最具代表性的(毕竟以高并发为卖点),以Go语言的协程--Goroutine作为钻研对象。 GM模型在Go语言设计的初期,Go团队应用简略的GM模型实现协程。 G:goroutine,也就是协程,它个别所占的栈内存为2KB,运行过程中如果栈空间不够用则会主动扩容。它能够被了解成一个被打包的代码段。goroutine并不是一个执行单元。 M:machine工作线程,就是真正用来执行代码的线程。由Go的runtime治理好它的生命周期。 在Go语言初期应用一个全局的队列来保留所有的G。当用户新建一个G的时候,runtime就会将打包好的G放到全局队列的队尾,并保护好队尾指针。当M执行完一个G后,就会到全局队列的队头取一个G给M执行,并且保护好全局队列的队头指针。 能够发现这里有个很重大的问题,如果放任随便退出G,也放任任何M随便取G,那么就会呈现并发问题。解决并发问题很简略,就是加锁,Go语言团队也的确是这样做的。那么加了锁之后也就代表所有的存取操作都会波及到这个繁多的全局互斥锁,那整个调度的执行效率大打折扣。除了锁导致性能问题以外,还有相似零碎调用时,工作线程常常被阻塞和勾销阻塞,等等一些问题[2]。所以后续官网团队调整了调度模型,退出了P的概念。 GMP模型新的模型中引入了P的概念,G与M没有什么大的变动。 P:process处理器,代表了M所需的上下文环境,也能够了解为一个M运行所须要的Token,当P有工作时须要创立或者唤醒一个M来执行它队列里的工作。所以P的数量决定了并行执行工作的数量,能够通过runtime.GOMAXPROCS来设定,当初的Go版本中默认为CPU的外围数。 上图依据曹大的GMP模型图自行简化绘制的。 P构造:一个P对应一个M,P构造中带有runnext字段和一个本地队列,runnext字段中存储的就是下一个被M执行的G,每个P带有一个256大小的本地数组队列,这个队列是无锁的,这两个数据结构相似于缓存的概念,能够无效的解决全局队列被频繁拜访的问题,而且runnext和本地队列是PM构造本地的,没有其余的执行单元竞争,所以也不必加锁。 M构造: runtime中有两个非凡的M: ①是主线程,它专门用来解决主线逻辑; ②是一个监控线程sysmon,它不须要P就能够执行,监控线程外面是一个死循环,一直地检测是否有阻塞或者执行工夫过长的G,发现之后将抢占这些G。 一般的M: 一般的M会有很多个,闲暇的M会被放在全局调度器的m idle(m 闲暇)链表中,在须要m的时候会优先从这里取,取不到的话就会创立新的M,M创立后就不会销毁了。每个M构造有三个G,gsignal是M专门解决runtime的信号的G,能够解决一些唤醒机制,G0是在执行runtime调度代码的时候须要切换到的G,还有一个G就是以后M须要执行的用户逻辑的G。 M有几种状态: 自旋中(spinning): 临时还没找到 Goroutine 来执行,然而正在找的一个状态。有这个状态和 nmsping 的计数,可能帮 runtime 判断是不是须要再启动额定的线程来执行 goroutine;执行go代码中: M正在执行go代码, 这时候M会领有一个P;执行原生代码中: M正在执行原生代码或者阻塞的syscall, 这时M并不领有P;休眠中: M发现无待运行的G时会进入休眠,并增加到闲暇M链表中, 这时M并不领有P。调度 整体的调度流程就是一个生产生产过程。用户作为生产者,用户起了一个goroutine后,被打包成一个G,通过runtime的一系列调度后被M生产。 生产端生产过程次要是指将一个打包好的G放入到队列中的过程。如果以后的本地队列满的话,就会将打包的batch放到全局队列中。 生产端生产过程相较于生产过程简单十分多,次要形容P和M怎么被调度,最终由M实现执行的过程。 后面咱们说到,每个M中有一个非凡的G0用来做调度,能够了解成当咱们的M没有执行完一个G后,须要切换到G0的栈空间,开始调用schedule函数找一个G来执行,找到后就切到找到的G的栈空间,并且执行它。 这边留神到,在每次执行的时候,会有一个变量在一直的++,这个schedtick咱们会在每次schedule函数调度G的时候用到,接下来咱们就来看看,每次调用schedule函数是如何找G的。 能够从图中看到,整个寻找可执行G的过程,这个过程中只有找到G,就会完结schedule,返回拿到的G去执行。 在schedule函数中会判断 schedtick%61 == 0,也就是每61次(为什么是61次?我也不晓得,算是个魔法数字,凭作者本人的思考)就去全局队列取一次(确保全局队列中的G会被执行到)。否则咱们就去以后M绑定的P的队列中拿,当然优先执行runnext中的G。当本地队列中找不到可用的G的时候咱们就会进入一个findrunnable的函数专门用来找G。进入findrunnable中首先也是先在以后绑定的P的runnext和本地队列中寻找。从全局队列中获取,获取的数量是 全局队列的长度/gomaxprocs+1 ,然而最大只能拿128个G到本地队列中,并拿出一个执行。从netpoll网络轮询器(监控网络I/O和文件I/O等耗时操作的)中获取阻塞的G继续执行。work stealing,去其余P(随机算法随机选中的)的本地队列中窃取G到本人这边,窃取的数量是被窃取队列的一半。再次尝试去全局队列获取。查看所有闲暇的P队列,如果有可运行的G,就去绑定到那个P并执行。再次尝试去netpoll中获取。 最初,在整个findrunnable中都没有找到能够执行的G,以后的M就会进入休眠,并放到全局M链表中期待唤醒。 本文中,疏忽了GC方面解决以及很多细节解决,真正的调度流程是非常复杂的,Go语言的官网代码是开源的并且有具体的正文,并且在Go版本升级过程中也会对一些逻辑有所调整,请自行留神时效性(本文基于Go1.15) References[1]https://mp.weixin.qq.com/s/Nb... "过程、线程与协程傻傻分不清?一文带你吃透!"[2]https://docs.google.com/docum... "Scalable Go Scheduler Design Doc"

July 18, 2021 · 1 min · jiezi

关于golang:Go语言sync包控制并发详解

除了上一节咱们介绍的 channel 通道,还有 sync.Mutex、sync.WaitGroup 这些原始的同步机制来,更加灵便的实现数据同步和管制并发。 资源竞争所谓资源竞争,就是在程序中,同一块内存同时被多个 goroutine 拜访。对于这个共享的资源(内存)每个 goroutine 都有不同的操作,就有可能造成数据错乱。 示例: package mainimport ( "fmt" "time")var sum = 0func main() { //开启100个协程来让 sum + 1 for i := 1; i <= 100; i++ { go add() } // 睡眠两秒避免程序提前退出 time.Sleep(2 * time.Second) fmt.Println("sum:",sum)}func add(){ sum += 1}//运行后果: sum:98 或 sum:99 或 ...屡次运行下面的程序,发现打印的后果可能存在不同,因为咱们用多个协程来操作 sum,而 sum 不是并发平安的,存在竞争。咱们应用 go build、go run、go test 命令时,增加 -race 标识能够查看代码中是否存在资源竞争。解决这个问题,咱们能够给资源进行加锁,让其在同一时刻只能被一个协程来操作。 sync.Mutex互斥锁,使同一时刻只能有一个协程执行某段程序,其余协程期待该协程执行完再顺次执行。互斥锁只有两个办法 Lock (加锁)和 Unlock(解锁),当一个协程对资源上锁后,只有等该协程解锁,其余协程能力再次上锁。Lock 和 Unlock 是成对呈现,为了避免上锁后遗记开释锁,咱们能够应用 defer 语句来开释锁。示例: ...

July 18, 2021 · 4 min · jiezi

关于golang:Go语言并发Goroutines-和-Channels-的声明与使用

什么是过程、线程过程就是一个应用程序的工作空间,比方你关上的QQ,微信,工作空间蕴含了该程序运行所需的所有资源。而线程是过程中的执行单位,一个过程起码有一个线程。 过程与线程比照过程是零碎资源分配和调度的最小单位线程是程序执行的最小单位一个过程由一个或多个线程组成,线程是过程中代码的不同执行路线过程之间互相独立,过程中的线程共享程序的内存空间及资源线程在工夫效率和空间效率都比过程要高协程协程是一种用户态的轻量级线程,线程是CPU来调度,而协程的调度齐全是由用户来管制的。 协程与线程比照一个线程能够有多个协程线程、过程都是同步机制,而协程是异步协程能够保留上一次调用时的状态,当过程重入时,相当于进入了上一次的调用状态协程是须要线程来承载运行的,所以协程并不能取代线程,线程是被宰割的CPU资源,协程是组织好的代码流程并发、并行并发和并行是绝对于过程或者线程来说的。并发是一个或多个CPU对多个过程/线程之间的多路复用,艰深讲就是CPU轮流执行多个工作,而每个工作都执行一小段,从宏观来看就像在同时执行。并行必须有多个CPU来提供反对,真正意义上的在同一时刻执行多个过程或线程。 Go语言协程Go中没有线程的概念,只有协程(goroutine),协程相比线程更加轻量,上下文切换更快。Goroutine由 Go 本人来调度,咱们只管启用。 goroutine 通过 go 关键字来启动,非常简单,go 关键字前面加一个办法或函数 go function() package mainimport ( "fmt" "time")func main (){ go fmt.Println("微客鸟窝") fmt.Println("我是无尘啊") time.Sleep(time.Second) //期待一秒,使goroutine 执行结束}运行后果: 我是无尘啊微客鸟窝Channelchannel(通道) 是用来解决多个 goroutine 之间通信问题的。 在 Go 语言中,提倡通过通信来共享内存,而不是通过共享内存来通信,其实就是提倡通过 channel 发送接管音讯的形式进行数据传递,而不是通过批改同一个变量。所以在数据流动、传递的场景中要优先应用 channel,它是并发平安的,性能也不错。channel 申明ch := make(chan string) 应用 make 函数chan 是关键字,示意 channel 类型,chan 是一个汇合类型string 示意 channel 里存放数据的类型chan 应用chan 只有发送和接管两种操作: 发送: <-chan //向chan内发送数据接管: chan-> //从chan中获取数据示例: package mainimport ( "fmt")func main() { ch := make(chan string) go func(){ fmt.Println("微客鸟窝") ch <- "执行结束" }() fmt.Println("我是无尘啊") value := <-ch fmt.Println("获取的chan的值为:",value)}运行后果 ...

July 18, 2021 · 2 min · jiezi

关于golang:Yearing-源码编译搭建环境

https://guide.yearning.io/ins...1.装置 npm 编译环境, 下载node js 装置(http://nodejs.cn/download/) 2.npm install 搭建环境 下载这个前端的代码, 通过npm run build 编译实现, 有dist 目录, copy 放到 src/service/ 目录下。git clone https://github.com/cookieY/Ye... 切换到要编译代码的环境如果出错:降级 npm 至 v7.6.2 后, 运行 npm i 装置依赖,报错 ERESOLVE unable to resolve dependency tree解决方案应用 npm i --legacy-peer-deps 切换到 Yearning 目录./Yearning install./Yearning run关上浏览器 http://127.0.0.1:8000 默认账号/明码:admin/Yearning_admin

July 17, 2021 · 1 min · jiezi

关于golang:听说过对-Go-map-做-GC-吗

在 Golang 中的 map 构造,在删除键值对的时候,并不会真正的删除,而是标记。那么随着键值对越来越多,会不会造成大量内存节约? 首先答案是会的,很有可能导致 OOM,而且针对这个还有一个探讨:https://github.com/golang/go/issues/20135。大抵的意思就是在很大的 map 中,delete 操作没有真正开释内存而可能导致内存 OOM。 所以个别的做法:就是 重建map。而 go-zero 中内置了 safemap 的容器组件。safemap 在肯定水平上能够防止这种状况产生。 那首先咱们看看 go 原生提供的 map 是怎么删除的? 原生map删除1 package main23 func main() {4 m := make(map[int]string, 9)5 m[1] = "hello"6 m[2] = "world"7 m[3] = "go"89 v, ok := m[1]10 _, _ = fn(v, ok)1112 delete(m, 1)13 }1415 func fn(v string, ok bool) (string, bool) {16 return v, ok17 }测试代码如上,咱们能够通过 go tool compile -S -N -l testmap.go | grep "CALL" : ...

July 15, 2021 · 2 min · jiezi

关于golang:golang实现计算1到100000范围内的素数

1.计算1到100000范畴内的素数golang实现package concurrentdemoimport ( "fmt" "sync")//1-10000000var start int = 0 //开始值// var end int = 1000000 //完结值var end int = 1000var interval int = 100 //划分goroutine距离var wg sync.WaitGroup func calResult(x int) bool { //素数计算函数,返回boolean类型的值 max := x/2 + 1 result := true for i := 2; i < max; i++ { if x%i == 0 { result = false break } } return result}func startTask(start, end, interval int) { //开始工作 resultChan := make(chan int,10) //素数计算后的后果放到resultChan中 exitChan := make(chan bool,16) //为了晓得各个goroutine执行结束,而后敞开resultChan time := (end - start) / interval //总goroutine fmt.Printf("time:%d \n", time) for i := 1; i <= time; i++ { wg.Add(1) startTemp := (i-1)*interval + start endTemp := i * interval fmt.Printf("startTemp:%d,endTemp:%d \n", startTemp, endTemp) go resolve(startTemp, endTemp, resultChan,exitChan) } wg.Add(1) go func () { for i := 1; i <= time; i++ { //循环取总time次 <- exitChan } wg.Done() close(resultChan) //在所有goroutine将本人的范畴内的素数计算完之后,并发送到resultChan后,敞开resultChan }() for v := range resultChan{ fmt.Printf("result:%d \n",v) } fmt.Println("main finished") wg.Wait()}func resolve(start, end int, resultChan chan<- int,exitChan chan<- bool) { for i := start; i < end; i++ { if calResult(i) { resultChan <- i } } exitChan <- true fmt.Printf("start:%d,end:%d finished \n", start, end) wg.Done()}func ConcurrentDemoT() { startTask(start, end, interval)}2.后果 ...

July 12, 2021 · 1 min · jiezi

关于golang:Golang-一文带你深入context

前言首先解答上一篇文章一文带你疾速入门context中留下的纳闷,为什么要defer cancelFunc()? func main() { parent := context.Background() for i := 0; i < 100; i++ { go doRequest(parent) } time.Sleep(time.Second * 10)}// doRequest 模仿网络申请func doRequest(parent context.Context) { ctx, _ := context.WithTimeout(parent, time.Second*5) time.Sleep(time.Millisecond * 200) go func() { <-ctx.Done() fmt.Println("ctx done!") }()}看下面的代码,在main函数中异步调用doRequest函数,doRequest函数中新建一个5s超时的上下文,doRequest函数的调用时长为200ms <img src="https://images-1255831004.cos.ap-guangzhou.myqcloud.com/halo/2021-07-10-142940.png" alt="image-20210710222940035" style="zoom:50%;" /> 能够看到,doRequest的上下文工夫范畴远大于函数调用破费的工夫,在函数完结后没有被动勾销上下文,这会造成上下文泄露 所以,defer cancelFunc()的目标是防止上下文泄露!! 被动调用cancelFunc是一个好习惯!理解一下Context接口type Context interface { // [1] 返回上下文的截止工夫 Deadline() (deadline time.Time, ok bool) // 返回一个通道,当上下文完结时,会敞开该通道,此时 <-ctx.Done() 完结阻塞 Done() <-chan struct{} // [2] 该办法会在上下文完结时返回一个not nil err,该err用于示意上下文完结的起因 Err() error // 返回与key关联的上下文的value Value(key interface{}) interface{}}[1]处,当上下文没有设置截止工夫时,调用Deadline,返回后果值中,ok = false ...

July 12, 2021 · 3 min · jiezi

关于golang:Golang-一文带你快速入门context

前言Go1.7引入了context包,其中定义了多种上下文,包含可被动勾销的上下文,带截止工夫或超时工夫的上下文,带值流传的上下文 context包的引入意义不凡, 它能够让信息(如:用户信息)在协程之间传递变得更加便捷,也能够把控一组协程的退出机会 假如,以后有一个函数gen,它承受context入参,作用是创立一个无缓冲区的channel,起一个协程每秒往这个通道中塞入一个信号,当context完结时,敞开通道,协程失常退出 func gen(ctx context.Context) chan struct{} { c := make(chan struct{}) go func() { for { select { case <-ctx.Done(): // [1]context完结 fmt.Println("receive context done signal, closing channel now") close(c) // 敞开通道 return // groutine失常退出 default: c <- struct{}{} // [2]应用空构造体节俭内存 } time.Sleep(time.Second) } }() return c}接下来,通过几个简略的例子来体验一下context包提供的各种类型的上下文 例子1:可被动勾销的上下文通过context包WithCancel函数,能够创立一个反对被动勾销的上下文 func UseCancelContext() { ctx, cancelFunc := context.WithCancel(context.Background()) go func() { time.AfterFunc(time.Second*5, func() { // [1] 五秒后执行勾销上下文的操作 cancelFunc() fmt.Println("send context done signal") }) }() for range gen(ctx) { fmt.Println("receive signal from another goroutine") } fmt.Println("main goroutine is finished")}[1]处被动调用cancelFunc,其底层逻辑就是敞开以后上下文及其子上下文中的通道,当上下文中的通道被敞开时,<-ctx.Done()就会有值,gen函数中的select会抉择case <-ctx.Done(),而后执行敞开通道,退出协程 ...

July 12, 2021 · 2 min · jiezi

关于golang:Golang-方法集的那些事

类型的值也能够调用指针接收者的办法! 学习golang中对于办法局部常识的人肯定理解过办法集(Method Set)的概念,也肯定对下面这张图不生疏,办法集定义如下规定: 规定: 类型的值的办法集只蕴含值接收者申明的办法指向 T 类型的指针的办法集既蕴含值接收者申明的办法,也蕴含指针接收者申明的办法理论应用过程中会发现,在T的值可寻址的状况下,类型的值也能够调用指针接收者的办法 package mainimport "fmt"type baseImpl stringconst ( BI = baseImpl("1234"))type base interface { func1 ()}func (b *baseImpl) func1() { fmt.Println("invoke base impl func1")}func main() { bi := baseImpl("1234") bi.func1() // 失常执行,输入 "invoke base impl func1" BI.func1() // 编译器报错,Cannot call a pointer method on 'BI'}上述代码中,定义了一个接口 base 以及其实现类 baseImpl,并且baseImpl 是通过指针接收者的模式来实现接口定义的办法 在理论应用过程中能够看到,定义baseImpl的值bi是能够调用func1办法的,因为bi是能够寻址的 然而,因为常量BI是不可寻址的,所以它遵循规定,无奈调用func1办法 由此可见,对于可寻址的值而言,其办法集的范畴蕴含了接收者类型为值类型和指针类型;对于不可寻址的值而言,其办法集的范畴仅蕴含值类型接收者 什么值是不可寻址的?常见不可寻址的值的类型有: 常量根本类型值运算后果值字符串索引表达式和切片表达式的后果值字典的索引表达式后果值函数、办法,及其调用表达式类型转换断言package mainconst ( I = 1)func main() { i := &I // 常量不可寻址 str := &"1234" // 根本类型,不可寻址 u := &str[0] // 字符串索引表达式,不可寻址 s := &str[1:2] // 字符串切片表达式,不可寻址 i2 := &int64(123) // 类型强转,不可寻址 strArr := []string{""} sa := &strArr[0] // 切片的索引表达式是可寻址的}总的来看,不可变的、长期后果、以及不平安的,都不可寻址 ...

July 11, 2021 · 1 min · jiezi

关于golang:Golang-Functional-Options-来了解一下

在开发或者学习源码过程中,不少人应该见过上面的代码片段 type Option func(opts *Options)// 新建一个server,client,pool等func NewXXX(name string, opts ...Option) {}刚开始从java转go的时候,我看这些代码真的是看得一脸懵逼,看不懂这些option是什么,也不明确为什么须要它们的存在 直到起初,我才晓得,这叫函数式选项(Functional Options) 函数式选项是Golang中实现简洁API的一种形式 在应用NewXXX函数构建struct的时候,struct中的属性并不都是必须的,这些非必须属性,在构建struct的过程中能够通过函数式选项的形式,实现更加简洁的API 假如须要实现一个协程池GPool,其中必备的属性有协程数量size,还有可选项:是否异步async,错误处理errorHandler,最大缓存工作数maxTaskNums,那么struct的设计应该如下 package pool// Option 定义函数式选项type Option func(options *Options)// GPool 协程池type GPool struct { size int64 // 协程数量 options *Options}type ErrorHandler func(err error)// Options 将非必须的选项都放到这里type Options struct { async bool // 是否反对异步提交工作 handler ErrorHandler // 工作执行出错时,回调该函数 maxTasks int64 // 协程池所承受的最大缓存工作数}// NewGPool 新建协程池func NewGPool(size int64, opts ...Option) *GPool { options := loadOpts(opts) return &GPool{ size: size, options: options, }}func loadOpts(opts []Option) *Options { options := &Options{} for _, opt := range opts { opt(options) } return options}func WithAsync(async bool) Option { return func(options *Options) { options.async = async }}func WithErrorHandler(handler ErrorHandler) Option { return func(options *Options) { options.handler = handler }}func WithMaxTasks(maxTasks int64) Option { return func(options *Options) { options.maxTasks = maxTasks }}如果须要创立一个协程池,协程数量为100,只须要这样写 ...

July 11, 2021 · 2 min · jiezi

关于golang:cc1exe-sorry-unimplemented-64bit-mode-not-compiled-in

问题形容:gcc版本谬误导致go get 无奈编译胜利 cc1.exe: sorry, unimplemented: 64-bit mode not compiled in解决办法:下载正确的版本,并设置path环境变量只想下载文件的bin目录(如果应用goland开发须要重启goland负责无奈失常加载环境变量)

July 10, 2021 · 1 min · jiezi

关于golang:Go-语言中的一些不太常见的优化

这次去 Gopher China 和不少老朋友见了个面,还有不少在微信上意识已久,始终没见过面的网友。同时也和各个公司的一线开发们聊了聊,相互交换了彼此应用 Go 时的一些心得和痛点。 综合近期理解的一些相干分享,我把到目前为止见到的,不那么常见的,各方对 Go 的优化和 hack 集中在这篇文章里。因为思考到一些公司状况比拟非凡,所以本文中列出的点就不标记是哪个公司做的了。将来他们感觉时机成熟应该也会本人进去做一些分享。 网络方面以后 Go 的网络形象在效率上有些低,每一个连贯至多要有一个 goroutine 来保护,有些协定实现可能有两个。因而 goroutine 总数 = 连接数 1 or 连接数 2。当连接数超过 10w 时,goroutine 栈自身带来的内存耗费就有几个 GB。 大量的 goroutine 也会给调度和 GC 均带来很大压力。Go 底层的网络库和加密库效率也不是很高,所以在同类利用上和 C++ 等语言有较大性能差距(内存、吞吐量)。 社区有不少应用裸调 Epoll 实现优化的库,就不给他们打广告了。因为用户的 syscall.EpollWait 是运行在一个没有任何优先级的 goroutine 中,当 CPU idle 较低时,零碎整体的提早不可控,比规范库的提早还要高很多。之前集体做过相干的测试,外围数的减少也会使零碎相应的提早大幅回升。 接下来不同的公司的优化思路就走向了两个方向: 批改 runtime,在 runtime 减少用户 Epoll 函数的回调。相似 runtime 本人实现的 netpoll 那样。这种形式会导致 Go 自身难以降级,必须跟着有 hack 的版本走。当遇到 Go 的 bug 时会比拟难堪。把 c 实现的网络库当作根底组件,把 Go 实现的业务逻辑作为业务嫁接在 c 库之上。Go 语言的强项就是在网络编程上,当初却逼得大家为了优化都须要去做对 runtime 的 hack,甚至嫁接其它语言,这不能不说有点悲痛。还是心愿将来官网可能有更好的底层根底工具来反对这种超高连接数的场景。 ...

July 10, 2021 · 1 min · jiezi

关于golang:手把手教你用-reflect-包解析-Go-的结构体-Step-2-结构体成员遍历

上一篇文章咱们学习了如何用 reflect 查看一个参数的类型。这一篇文章,咱们取得了一个构造体类型,那么咱们须要探索构造体外部的构造以及其对应的值。 构造体成员迭代上一篇文章,咱们的 marshal 函数目前是长这个样子: func marshalToValues(in interface{}) (kv url.Values, err error) { v, err := validateMarshalParam(in) if err != nil { return nil, err } // ......}到这里,咱们拿到了一个 struct 的 reflect.Value 变量。接下来,咱们再增加几行代码,变成这个样子: func marshalToValues(in interface{}) (kv url.Values, err error) { v, err := validateMarshalParam(in) if err != nil { return nil, err } t := v.Type() numField := t.NumField() kv = url.Values{} // 迭代每一个字段 for i := 0; i < numField; i++ { fv := v.Field(i) // field value ft := t.Field(i) // field type // ...... } return kv, nil}变量 t 是一个 reflect.Type,示意以后变量的类型,其函数 NumField(),对于 struct 类型变量,则示意该变量下的所有成员字段的数量。 ...

July 10, 2021 · 4 min · jiezi

关于golang:手把手教你用-reflect-包解析-Go-的结构体-Step-1-参数类型检查

引言Go 原生的 encoding/json 的 Unmarshal 和 Marshal 函数的入参为 interface{},并且可能反对任意的 struct 或 map 类型。这种函数模式,具体是如何实现的呢?本文便大略探索一下这种实现模式的根底:reflect 包。 基本概念interface{}初学 Go,很快就会接触到 Go 的一个非凡类型:interface。Interface 的含意是:实现指定 interface 体内定义的函数的所有类型。举个例子,咱们有以下的接口定义: type Dog interface{ Woof()}那么只有是实现了 Woof() 函数(汪汪叫),都能够认为是实现了 Dog 接口的类型。留神,是所有类型,不局限于简单类型或者是根本类型。比如说咱们用 int 从新定义一个类型,也是能够的: type int FakeDogfunc (d FakeDog) Woof() { // do hothing}好,接下来,咱们又会见到一个常见的写法:interface{},interface 单词紧跟着一个未蕴含任何内容的花括号。咱们要晓得,Go 反对匿名类型,因而这仍然是一种接口类型,只是这个接口没有规定任何须要实现的函数。 那么从语义上咱们能够晓得,任意类型都合乎这个接口的定义。反过来说,interface{} 就能够用来示意任意类型。这就是 json marshaling 和 unmarshaling 的入参。 reflectOK,尽管有了 interface{} 用于示意 “任意类型”,然而咱们最终总得解析这个 “任意类型” 参数吧?Go 提供了 reflect 包,用来解析。这就是中文材料中常提的 “反射机制”。反射能够做很多事件,本文中咱们次要波及解析构造体的局部。 以下,咱们设定一个试验 / 利用场景,来一步步介绍 reflect 的用法和注意事项。 试验场景各种支流的序列化 / 反序列化协定如 json、yaml、xml、pb 什么的都有权威和官网的库了;不过在 URL query 场景下,绝对还不特地欠缺。咱们就拿这个场景来玩一下吧 —— URL query 和 struct 互转。 ...

July 10, 2021 · 3 min · jiezi

关于golang:Golang工具集String工具时间工具http工具等

gotoolgotool是一个小而全的Golang工具集,次要是将日常开发中罕用的到办法进行提炼集成,防止反复造轮子,进步工作效率,每一个办法都是作者通过工作教训,和从以往的我的项目中提炼进去的。 2021-7-9更新内容具体应用请看文档增加文件IO操作工具FileUtils增加验证码生成工具CaptchaUtils增加文件目录压缩和解压缩工具ZipUtis字符串数组工具StrArrayUtils如何应用gotool呢?装置go get github.com/druidcaesa/gotool go.mod github.com/druidcaesa/gotool 引入import "github.com/druidcaesa/gotool"StrUtilsgolang一个string常用工具集,根本涵盖了开发中常常用到的工具,目前正在不端的欠缺中 1、gotool.StrUtils.ReplacePlaceholder 占位符替换func TestStringReplacePlaceholder(t *testing.T) {s := "你是我的{},我是你的{}"placeholder, err := gotool.StrUtils.ReplacePlaceholder(s, "惟一", "所有")if err == nil {fmt.Println(placeholder)}}//out== = RUN TestStringReplacePlaceholder你是我的惟一, 我是你的所有--- PASS: TestStringReplacePlaceholder (0.00s)PASS2、gotool.StrUtils.RemoveSuffix 去除文件扩展名获取文件名func TestRemoveSuffix(t *testing.T) {fullFilename := "test.txt"suffix, _ := gotool.StrUtils.RemoveSuffix(fullFilename)fmt.Println(suffix)fullFilename = "/root/home/test.txt"suffix, _ = gotool.StrUtils.RemoveSuffix(fullFilename)fmt.Println(suffix)}//out== = RUN TestRemoveSuffixtesttest--- PASS: TestRemoveSuffix (0.00s)PASS3、gotool.StrUtils.GetSuffix 获取文件扩展名func TestGetSuffix(t *testing.T) {fullFilename := "test.txt"suffix, _ := gotool.StrUtils.GetSuffix(fullFilename)fmt.Println(suffix)fullFilename = "/root/home/test.txt"suffix, _ = gotool.StrUtils.GetSuffix(fullFilename)fmt.Println(suffix)}//out== = RUN TestGetSuffix.txt.txt--- PASS: TestGetSuffix (0.00s)PASS4、gotool.StrUtils.HasEmpty 判断字符串是否未空,我空返回turefunc TestHasStr(t *testing.T) {str := ""empty := gotool.StrUtils.HasEmpty(str)fmt.Println(empty)str = "11111"empty = gotool.StrUtils.HasEmpty(str)fmt.Println(empty)}//out== = RUN TestHasStrtruefalse--- PASS: TestHasStr (0.00s)PASSStrArrayUtils string数组操作工具1、gotool.StrArrayUtils.StringToInt64 字符串数组转int64数组,调用前请确保字符串数组均为数字func TestStringToInt64(t *testing.T) {//字符串数组转int64strings := []string{"1", "23123", "232323"}fmt.Println(reflect.TypeOf(strings[0]))toInt64, err := gotool.StrArrayUtils.StringToInt64(strings)if err != nil {t.Fatal(err)}fmt.Println(reflect.TypeOf(toInt64[0]))}//out== = RUN TestStringToInt64stringint64--- PASS: TestStringToInt64 (0.00s)PASS2、gotool.StrArrayUtils.StringToInt32 字符串数组转int64数组,调用前请确保字符串数组均为数字func TestStringToInt32(t *testing.T) {//字符串数组转int64strings := []string{"1", "23123", "232323"}fmt.Println(reflect.TypeOf(strings[0]))toInt64, err := gotool.StrArrayUtils.StringToInt32(strings)if err != nil {t.Fatal(err)}fmt.Println(reflect.TypeOf(toInt64[0]))}//out== = RUN TestStringToInt32stringint32--- PASS: TestStringToInt32 (0.00s)PASS3、gotool.StrArrayUtils.ArrayDuplication 数组去重func TestArrayDuplication(t *testing.T) {//string数组去重strings := []string{"hello", "word", "gotool", "word"}fmt.Println("去重前----------------->", strings)duplication := gotool.StrArrayUtils.ArrayDuplication(strings)fmt.Println("去重后----------------->", duplication)}//out== = RUN TestArrayDuplication去重前-----------------> [hello word gotool word]去重后-----------------> [hello word gotool]--- PASS: TestArrayDuplication (0.00s)PASSDateUtilgolang一个工夫操作工具集,根本涵盖了开发中常常用到的工具,目前正在不端的欠缺中 ...

July 9, 2021 · 6 min · jiezi

关于golang:golang-实现ssh公钥登录

我有一个需要,须要实现ssh应用公钥免密登录。在网上查了大量的材料,竟然没有找到对于这方面的实现,所以记录一下。网上有应用SSH_AUTH_SOCK环境变量的办法实现的,然而一来这个SSH_AUTH_SOCK环境变量须要借助ssh-agent命令去生成,二来那段代码不明不白的,基本不能运行,所以就间接放弃了。那段代码如下: func SSHClient(hostport string, username string) (*ssh.Client, error) { sock, err := net.Dial("unix", os.Getenv("SSH_AUTH_SOCK")) if err != nil { logrus.Infof("error login,details: %s",err.Error()) return nil,err } agent := agent.NewClient(sock) //这个agent.NewClient是啥?不清不楚的,无奈运行 signers, err := agent.Signers() if err != nil { logrus.Infof("error login,details: %s",err.Error()) return nil,err } auths := []ssh.AuthMethod{ssh.PublicKeys(signers...)} cfg := &ssh.ClientConfig{ User: username, Auth: auths, HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error { return nil }, } cfg.SetDefaults() logrus.Infof("tcp dial to %s",hostport) client, err := ssh.Dial("tcp", hostport, cfg) if err != nil { logrus.Infof("error login,details: %s",err.Error()) return nil,err } return client, nil}找了良久,终于在官网的example_test.go的代码里找到了能够参考的中央。以下是我的实现,亲测可用。欢送大家探讨: ...

July 9, 2021 · 1 min · jiezi

关于golang:golang-http11的请求负载均衡按照http20转发问题

要害参数:TLSNextProto 解决方案:把TLSNextProto设置成空map,这样就不会主动降级,相当于敞开http2.0 client := &http.Client { Transport:&http.Transport{ TLSNextProto: map[string]func(authority string, c *tls.Conn) http.RoundTripper{}, },}起因:来自 https://stackoverflow.com/que... Starting with Go 1.6, the http package has transparent support for the HTTP/2 protocol when using HTTPS. Programs that must disable HTTP/2 can do so by setting Transport.TLSNextProto (for clients) or Server.TLSNextProto (for servers) to a non-nil, empty map. Alternatively, the following GODEBUG environment variables are currently supported:

July 8, 2021 · 1 min · jiezi

关于golang:HTTP的四种请求方法

golang中net/http包提供了http相干操作的封装,其中get办法和post办法进行的进一步的封装,应用起来更加不便,其余的申请形式须要咱们本人调用底层实现 package mainimport ( "fmt" "io/ioutil" "net/http")func get(){ resp, err := http.Get("http://httpbin.org/get") if err != nil { panic(err) } defer func() {_ = resp.Body.Close()}() content, err := ioutil.ReadAll(resp.Body) if err != nil { panic(err) } fmt.Printf("%s", content) //{ // "args": {}, // "headers": { // "Accept-Encoding": "gzip", // "Host": "httpbin.org", // "User-Agent": "Go-http-client/1.1", // "X-Amzn-Trace-Id": "Root=1-60e4663e-4a475772249555e35a89632c" //}, // "origin": "222.211.214.252", // "url": "http://httpbin.org/get" //}}func post(){ resp, err := http.Post("http://httpbin.org/post", "", nil) if err != nil { panic(err) } defer func() {_ = resp.Body.Close()}() content, err := ioutil.ReadAll(resp.Body) if err != nil { panic(err) } fmt.Printf("%s", content) //{ // "args": {}, // "data": "", // "files": {}, // "form": {}, // "headers": { // "Accept-Encoding": "gzip", // "Content-Length": "0", // "Host": "httpbin.org", // "User-Agent": "Go-http-client/1.1", // "X-Amzn-Trace-Id": "Root=1-60e466bc-19f2a05e219847055d72f159" //}, // "json": null, // "origin": "222.211.214.252", // "url": "http://httpbin.org/post" //}}func put(){ request, err := http.NewRequest(http.MethodPut, "http://httpbin.org/put", nil) if err != nil { panic(err) } resp, err := http.DefaultClient.Do(request) if err != nil { panic(err) } defer func() {_ = resp.Body.Close()}() content, err := ioutil.ReadAll(resp.Body) if err != nil { panic(err) } fmt.Printf("%s", content) //{ // "args": {}, // "data": "", // "files": {}, // "form": {}, // "headers": { // "Accept-Encoding": "gzip", // "Content-Length": "0", // "Host": "httpbin.org", // "User-Agent": "Go-http-client/1.1", // "X-Amzn-Trace-Id": "Root=1-60e467e5-4db5430f5a0aa8ea75f5f805" //}, // "json": null, // "origin": "222.211.214.252", // "url": "http://httpbin.org/put" //}}func delete(){ request, err := http.NewRequest(http.MethodDelete, "http://httpbin.org/delete", nil) if err != nil { panic(err) } resp, err := http.DefaultClient.Do(request) if err != nil { panic(err) } defer func() {_ = resp.Body.Close()}() content, err := ioutil.ReadAll(resp.Body) if err != nil { panic(err) } fmt.Printf("%s", content) //{ // "args": {}, // "data": "", // "files": {}, // "form": {}, // "headers": { // "Accept-Encoding": "gzip", // "Host": "httpbin.org", // "User-Agent": "Go-http-client/1.1", // "X-Amzn-Trace-Id": "Root=1-60e4683b-6e4e08c400343a9f29a735fe" //}, // "json": null, // "origin": "222.211.214.252", // "url": "http://httpbin.org/delete" //}}func main(){ get() post() put() delete()}

July 7, 2021 · 2 min · jiezi

关于golang:使用-GOLANG-发送邮件

[TOC] 应用 GOLANG 发送邮件咱们一起来回顾一下上一次咱们说到的GO 的验证码案例 验证码品种梳理验证码库的装置验证码库的源码介绍实操,编码验证码成果展现想看看GO 咋做验证码的,欢送查看文章 GO 的验证码案例 生存和工作中,咱们都离不开邮件的收发,要么在手机上查收和发送,要么在本人的电脑前面进行邮件编辑和解决 可是,咱们会发现,很多时候,某类邮件,咱们每天都必须要在同一个时刻收回去,并且内容也是大同小异的, 而且,有时候因为各种各种各样的起因,不能准时的发送邮件,这个时候,咱们如果能够写一个定制化的发送邮件的程序那能够说还是很香的 那么,咱们还是先来看看一些根本的常识吧 邮件是什么?邮件是指经传递形式解决的文件 邮件进行传递的过程称为邮递,而从事邮递服务的机构或零碎,则称为邮政 邮件有国内邮件和国内邮件两类 那么电子邮件又是个啥?电子邮件是—种用电子伎俩提供信息替换的通信形式是互联网利用最广的服务 电子邮件的劣势是啥?电子邮件依靠于网络的电子邮件系统,有如下劣势: 价格十分低廉不论发送到哪里,都只需累赘网费 传输疾速几秒钟之内能够发送到世界上任何指定的目的地,与世界上任何一个角落的网络用户分割 电子邮件的模式是啥样的?文字图像声音等想一想每次发邮件都须要经验如下的步骤 关上电脑进入浏览器关上电子邮件新建 - 编辑 - 发送大部分内容还是复制粘贴的, 妥妥的一个工具人,好滴,当初就来看看 应用 GOLang 咋发邮件 邮件协定咱们应用编程语言须要恪守编程语言的标准,咱们在浏览器外面浏览网页也是一样,须要遵循各种网络协议 那么,咱们发送邮件的必须也要先晓得有哪些邮件协定能够应用,咱们都来列举一下 SMTPSMTP是 简略邮件传输协定,是一组用于从源地址到目标地址传输邮件的标准,通过它来管制邮件的直达形式 另外 SMTP 协定属于TCP/IP协定簇 POP3邮局协定的第3个版本,是因特网电子邮件的第一个离线协定规范 IMAP是一种优于POP的新协定 和POP一样,IMAP也能下载邮件、从服务器中删除邮件或询问是否有新邮件 IMAP可让用户在服务器上创立并治理邮件文件夹或邮箱、删除邮件、查问某封信的一部分或全部内容 最终实现所有这些工作都不须要把邮件从服务器下载到用户的集体计算机上 OK,让咱们开始进入到编码环节 开始编码发邮件咱们明天就应用 SMTP 协定来发送邮件,有如下几个步骤 在 QQ 邮箱下面,拿到受权码编码,并装置email 邮件库开始发送邮件在 QQ 邮箱下面,拿到受权码进入 QQ邮箱,点击 设置 -> 账户 下滑页面,看到 POP3/IMAP/SMTP/Exchange/CardDAV/CalDAV服务 开启 POP3/SMTP服务开启 IMAP/SMTP服务生成受权码,这个受权码本人找中央保存起来编码,并装置email 邮件库咱们本次写的邮件小案例,用到的GO 的包是 "github.com/jordan-wright/email" , 咱们能够这样来手动装置 ...

July 7, 2021 · 3 min · jiezi

关于golang:关于goproxy

原文:https://segmentfault.com/a/11...

July 7, 2021 · 1 min · jiezi

关于golang:搞一个自娱自乐的博客四-友链

性能形容将首页革新为友链,性能蕴含链接题目和链接 因为后盾管理系统还没选好 临时不做增加性能 由数据库增加数据,页面做展现应用。 后盾代码modelstitle 题目构造体 package modelstype Title struct { Id int `form:"id"` Name string `form:"name"`}link 寄存具体链接 package modelstype Link struct { Id int `form:"id"` Pid int `form:"pid"` Title string `form:"title"` Path string `form:"path"`}repotitle_repo 链接数据库查问所有题目 package repoimport ( "github.com/go-xorm/xorm" "log" "myCommunity/models")type TitleRepo struct { engine *xorm.Engine}func TitleDao(engine *xorm.Engine) *TitleRepo { return &TitleRepo{ engine: engine, }}func (repo TitleRepo) GetAll() []models.Title { var datalist []models.Title err := repo.engine.Where("1=1").Find(&datalist) if err != nil { log.Println(err) return datalist } else { return datalist }}link_repo 链接数据库查问所有链接 ...

July 7, 2021 · 2 min · jiezi

关于golang:go语言之grpc和protobuf

以下是我本地mac装置的 对于grpc和protobuf的介绍以及和restful比照可参考这篇介绍:https://golang2.eddycjy.com/p...编译器protoc装置在 gRPC 开发中,咱们经常须要与 Protobuf 进行打交道,而在编写了.proto 文件后,咱们会须要到一个编译器,那就是 protoc,protoc 是 Protobuf 的编译器,是用 C++ 所编写的,其次要性能是用于编译.proto 文件要切换到root用户才能够,不然后续make会没有权限, sudo -icd /usr/local/libwget https://github.com/protocolbuffers/protobuf/releases/download/v3.17.3/protobuf-all-3.17.3.tar.gz有针对不同的版本,最好抉择all解压tar -xzvf protobuf-all-3.17.3.tar.gzcd protobuf-all-3.17.3设置编译目录./configure --prefix=/usr/local/protobuf装置make && make install也能够多线程装置 -j参数,前面是线程数make -j4 && make installexit退出root用户vim ~/.bash_profile #没有就创立此文件结尾增加export PROTOBUF=/usr/local/protobuf export PATH=$PATH:$PROTOBUF/bin退出保留失效source ~/.bash_profile测试protoc --version 装置插件咱们在上一步装置了 protoc 编译器,然而还是不够的,针对不同的语言,还须要不同的运行时的 protoc 插件,那么对应 Go 语言就是 protoc-gen-go 插件能够参考这个:https://grpc.io/docs/language...$ go install google.golang.org/protobuf/cmd/protoc-gen-go@latest$ go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest也能够装置指定版本不过留神插件和编译器对应版本,有时候会不兼容配置export PATH="$PATH:$(go env GOPATH)/bin"或者export PATH=$PATH:$GOPATH/bin失效source ~/.bash_profile

July 7, 2021 · 1 min · jiezi

关于golang:goLang中range的注意小点

对于range应用时有个点须要留神一下,防止踩坑 平时应用range时都是这样for i, element := range xxxx 有没有思考过element是如何赋值的?每次循环element是否是新生成长期变量? 先看看如下代码的运行后果 idNum := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}waitGrp := sync.WaitGroup{}for index, unit := range idNum { waitGrp.Add(1) fmt.Printf("unit(%d) addr:%p value:%d \n", index, &unit, unit) /* go func(num int) { fmt.Printf("------Num:%d, unit addr:%p \n", num, &num) waitGrp.Done() }(unit) */ go func() { fmt.Printf("******Num:%d, unit addr:%p \n", unit, &unit) waitGrp.Done() }()}waitGrp.Wait()输入后果如下 unit(0) addr:0xc0000ae040 value:0 unit(1) addr:0xc0000ae040 value:1 unit(2) addr:0xc0000ae040 value:2 unit(3) addr:0xc0000ae040 value:3 unit(4) addr:0xc0000ae040 value:4 unit(5) addr:0xc0000ae040 value:5 unit(6) addr:0xc0000ae040 value:6 unit(7) addr:0xc0000ae040 value:7 unit(8) addr:0xc0000ae040 value:8 unit(9) addr:0xc0000ae040 value:9 unit(10) addr:0xc0000ae040 value:10 unit(11) addr:0xc0000ae040 value:11 unit(12) addr:0xc0000ae040 value:12 unit(13) addr:0xc0000ae040 value:13 unit(14) addr:0xc0000ae040 value:14 unit(15) addr:0xc0000ae040 value:15 ******Num:14, unit addr:0xc0000ae040 ******Num:15, unit addr:0xc0000ae040 ******Num:15, unit addr:0xc0000ae040 ******Num:15, unit addr:0xc0000ae040 ******Num:15, unit addr:0xc0000ae040 ******Num:15, unit addr:0xc0000ae040 ******Num:15, unit addr:0xc0000ae040 ******Num:15, unit addr:0xc0000ae040 ******Num:15, unit addr:0xc0000ae040 ******Num:15, unit addr:0xc0000ae040 ******Num:15, unit addr:0xc0000ae040 ******Num:15, unit addr:0xc0000ae040 ******Num:3, unit addr:0xc0000ae040 ******Num:15, unit addr:0xc0000ae040 ******Num:15, unit addr:0xc0000ae040 ******Num:10, unit addr:0xc0000ae040 看第一局部输入 ...

July 7, 2021 · 2 min · jiezi

关于golang:生产环境遇到一个-Go-问题整组人都懵逼了

微信搜寻【脑子进煎鱼了】关注这一只爆肝煎鱼。本文 GitHub github.com/eddycjy/blog 已收录,有我的系列文章、材料和开源 Go 图书。大家好,我是煎鱼。 前段时间正在疯狂写代码的时候,忽然有一个读者给我提了一个问题,让我有了肯定的趣味: 我还是比拟感兴趣的,因为是生产环境、有代码,且整组人都懵逼的问题。 在征求了小伙伴的意见后,明天分享进去,大家也思考一下起因,一起躲避这个 “坑”。 案例一代码示例如下: type MyErr struct { Msg string}func main() { var e error e = GetErr() log.Println(e == nil)}func GetErr() *MyErr { return nil}func (m *MyErr) Error() string { return "脑子进煎鱼了"}请思考一下,这段程序的输入后果是什么? 该程序所调用的 GetErr 办法所返回的是 nil,而内部判断是 e == nil,因而最终的输入后果是 true,对吗? 输入后果如下: 2021/04/04 08:39:04 false答案是:false。 案例二代码示例如下: type Base interface { do()}type App struct {}func main() { var base Base base = GetApp() log.Println(base) log.Println(base == nil)}func GetApp() *App { return nil}func (a *App) do() {}请思考一下,这段程序的输入后果是什么? ...

July 7, 2021 · 1 min · jiezi

关于golang:搞一个自娱自乐的博客三数据库设计

链接mysqlxormgo get -u github.com/xormplus/xormconf在conf目录中减少db.go 申明mysql的链接构造体 package confimport ( "github.com/json-iterator/go" "io/ioutil")type DbConf struct { Driver string `json:"DbName"` Host string `json:"DbHost"` Port string `json:"DbPort"` User string `json:"DbUser"` Pwd string `json:"DbPwd"` DbName string `json:"DbDbName"`}// MasterDbConfig 主库配置var MasterDbConfig = DbConf{ Driver: "mysql", Host: "192.168.1.1", Port: "3306", User: "root", Pwd: "123456", DbName: "go",}datasource在datasource目录中减少dbhelper.go 链接数据库 package datasourceimport ( "fmt" _ "github.com/go-sql-driver/mysql" "github.com/go-xorm/xorm" "log" "myCommunity/conf" "sync")var ( masterEngine *xorm.Engine lock sync.Mutex)func DbHelper() *xorm.Engine { // 判断是否曾经存在 if masterEngine != nil { return masterEngine } lock.Lock() defer lock.Unlock() // 判断是否曾经存在 因为加锁 想要从新判断是否存在 if masterEngine != nil { return masterEngine } c := conf.MasterDbConfig driverSource := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8", c.User, c.Pwd, c.Host, c.Port, c.DbName) log.Println(driverSource) engine, err := xorm.NewEngine(c.Driver, driverSource) if err != nil { log.Fatalf("DbHelper.DbInstanceMaster,", err) return nil } // Debug模式,打印全副的SQL语句,帮忙比照,看ORM与SQL执行的对照关系 engine.ShowSQL(true) masterEngine = engine return engine}测试jdbc批改main.go ...

July 6, 2021 · 2 min · jiezi

关于golang:搞一个自娱自乐的社区二-架构搭建

搭建基础架构目录构造conf 配置文件logs 日志文件controllers 控制器 承受参数 api的入口datasource 数据库配置models 构造体模型repo 数据库的操作route 注册路由service 业务逻辑代码utils 工具类config.json 配置文件的映射main.go 主程序入口热启动改变内容主动重启 速度快 go get -u github.com/kataras/rizlarizla main.go批改main.go日志输入到本地func newLogFile() *os.File { //20060102是语法糖 为yyyyMMdd filename := "logs/" + time.Now().Format("20060102") + ".log" // 关上以以后日期为文件名的文件(不存在则创立文件,存在则追加内容) f, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) if err != nil { panic(err) } return f}main.go中减少 func main() { f := newLogFile() defer f.Close() app := iris.New() // 在控制台输入同时,会被写入日志文件 app.Logger().SetOutput(io.MultiWriter(f, os.Stdout)) // 不在控制台输入 只写入日志文件 //app.Logger().SetOutput(f) // 设置日志级别 app.Logger().SetLevel("debug") // 增加中间件 app.Use(recover.New()) app.Use(logger.New( logger.Config{ // 是否记录状态码,默认false Status: true, // 是否记录近程IP地址,默认false IP: true, // 是否出现HTTP谓词,默认false Method: true, // 是否记录申请门路,默认true Path: true, // 是否开启查问追加,默认false Query: true, })) ...注册template在main.go 的main() 办法中增加如下代码 ...

July 6, 2021 · 2 min · jiezi

关于golang:ingerface类型转换总结

goLang的interface{}功能强大,能够指向各种类型数据,在数据类型转换之间能够有以下几种办法 有如下定义 type BaseService struct { IService serviceName string routineNum int}type MyEnum intconst ( CAT MyEnum = iota DOG)var unknowValue interface{}unknowValue = baseService办法1:应用断言的形式如下的形式originalValue, ok := unknowValue.(OriginalType) val, ok := unknowValue.(BaseService)if ok { fmt.Println(val)}这种形式简略,没什么可说的,间接转换成你想要的类型 办法2:应用反射能够应用反射的 reflect.TypeOf(unknowValue)也能够是用reflect.ValueOf(unknowValue) typeOfUnknown := reflect.TypeOf(unknowValue)fmt.Printf("%v, %v\n", typeOfUnknown.Name(), typeOfUnknown.Kind())unknowValue = "123"typeOfUnknown = reflect.TypeOf(unknowValue)fmt.Printf("%v, %v\n", typeOfUnknown.Name(), typeOfUnknown.Kind())unknowValue = 123typeOfUnknown = reflect.TypeOf(unknowValue)fmt.Printf("%v, %v\n", typeOfUnknown.Name(), typeOfUnknown.Kind())输入后果为 BaseService, structMyEnum, intstring, stringint, intfunc showKind(typeOfUnknown interface{}) { vv := reflect.ValueOf(typeOfUnknown) switch vv.Kind() { case reflect.String: fmt.Println("string") case reflect.Int: fmt.Println("int") fmt.Println(vv.Type().Name()) case reflect.Struct: fmt.Println("Struct") fmt.Println(vv.Type().Name()) }}后果高深莫测对于struct类型,Kind是struct,Name是理论的构造体名对于自定义类型,Kind是理论的类型,Name是自定义的名称对于根底数据类型,Kind就是理论的类型。 ...

July 6, 2021 · 1 min · jiezi

关于golang:Go语言中string和byte的转换原理

前言哈喽,大家好,我是asong。为什么会有明天这篇文章呢?前天在一个群里看到了一份Go语言面试的八股文,其中有一道题就是"字符串转成byte数组,会产生内存拷贝吗?";这道题挺有意思的,实质就是在问你string和[]byte的转换原理,考验你的根本功底。明天咱们就来好好的探讨一下两者之间的转换形式。byte类型咱们看一下官网对byte的定义: // byte is an alias for uint8 and is equivalent to uint8 in all ways. It is// used, by convention, to distinguish byte values from 8-bit unsigned// integer values.type byte = uint8咱们能够看到byte就是uint8的别名,它是用来辨别字节值和8位无符号整数值。 其实能够把byte当作一个ASCII码的一个字符。 示例: var ch byte = 65var ch byte = '\x41'var ch byte = 'A'[]byte类型[]byte就是一个byte类型的切片,切片实质也是一个构造体,定义如下: // src/runtime/slice.gotype slice struct { array unsafe.Pointer len int cap int}这里简略阐明一下这几个字段,array代表底层数组的指针,len代表切片长度,cap代表容量。看一个简略示例: func main() { sl := make([]byte,0,2) sl = append(sl, 'A') sl = append(sl,'B') fmt.Println(sl)}依据这个例子咱们能够画一个图: ...

July 5, 2021 · 3 min · jiezi

关于golang:Go-channel-VS-Java-BlockingQueue

前言最近在实现两个需要,因为两者之间并没有依赖关系,所以想利用队列进行解耦;但在 Go 的规范库中并没有现成可用并且并发平安的数据结构;但 Go 提供了一个更加优雅的解决方案,那就是 channel。 channel 利用Go 与 Java 的一个很大的区别就是并发模型不同,Go 采纳的是 CSP(Communicating sequential processes) 模型;用 Go 官网的说法: Do not communicate by sharing memory; instead, share memory by communicating.翻译过去就是:不必应用共享内存来通信,而是用通信来共享内存。 而这里所提到的通信,在 Go 里就是指代的 channel。 只讲概念并不能疾速的了解与利用,所以接下来会联合几个理论案例更不便了解。 futrue taskGo 官网没有提供相似于 Java 的 FutureTask 反对: public static void main(String[] args) throws InterruptedException, ExecutionException { ExecutorService executorService = Executors.newFixedThreadPool(2); Task task = new Task(); FutureTask<String> futureTask = new FutureTask<>(task); executorService.submit(futureTask); String s = futureTask.get(); System.out.println(s); executorService.shutdown(); }}class Task implements Callable<String> { @Override public String call() throws Exception { // 模仿http System.out.println("http request"); Thread.sleep(1000); return "request success"; }}但咱们能够应用 channel 配合 goroutine 实现相似的性能: ...

July 5, 2021 · 3 min · jiezi

关于golang:译-方法是否应该在-T-或-T-上声明

情谊提醒:此篇文章大概须要浏览 3分钟49秒,不足之处请多指教,感谢您的浏览。 订阅本站 译文原地址:Should methods be declared on T or *T – David 在 Go 中,对于任何的类型 T,都存在一个类型 *T,他是一个表达式的后果,该表达式接管的是类型 T ,例如: type T struct { a int; b bool } var t T // t's type is T var p = &t // p's type is *T这两种类型,T 和 *T 是不同的,但 *T 不能代替 T。 你能够在你领有的任意类型上申明一个办法;也就是说,在您的包中的函数申明的类型。因而,您能够在申明的类型 T 和对应的派生指针类型 *T 上申明办法。另一种说法是,类型上的办法被申明为接收器接收者值的正本,或一个指向其接收者值的指针。所以问题就存在了,到底是哪种模式最合适? 显然,如果你的办法扭转了他的接收者,他应该在 *T 上申明。然而,如果办法不扭转他的接收者,在 T 上申明它是平安的么? 事实证明,这样做的话平安的状况十分无限(简略了解就是不平安的)。例如,家喻户晓,你不应该复制一个 sync.Mutex 的值,因为它突破了互斥量的不变量。因为互斥锁管制对变量(共享资源)的拜访,他们常常被包装在一个构造体中,蕴含他们的管制的值(共享资源): package countertype Val struct { mu sync.Mutex val int}func (v *Val) Get() int { v.mu.Lock() defer v.mu.Unlock() return v.val}func (v *Val) Add(n int) { v.mu.Lock() defer v.mu.Unlock() v.val += n}大部分 Gopher 都晓得,遗记在指针接收器 *Val 上是申明 Get 或 Add 办法是谬误的。然而,任何嵌入 Val 来利用其 0 值的类型,也必须仅在其指针接收器上申明办法,否者可能会无心复制其嵌入类型值的内容: ...

July 4, 2021 · 1 min · jiezi

关于golang:安装Go语言支持及Gogs版本管理工具

装置Go语言反对及Gogs版本管理工具1. GO 语言:1.1 介绍1.1.1 官网介绍:The Go programming language is an open source project to make programmers more productive. Go is expressive, concise, clean, and efficient. Its concurrency mechanisms make it easy to write programs that get the most out of multicore and networked machines, while its novel type system enables flexible and modular program construction. Go compiles quickly to machine code yet has the convenience of garbage collection and the power of run-time reflection. It's a fast, statically typed, compiled language that feels like a dynamically typed, interpreted language. ...

July 3, 2021 · 1 min · jiezi

关于golang:数据结构单向链表

单向链表的一种Go语言实现 package mainimport "fmt"type Node struct { no int name string next *Node}//实现尾部插入一个节点到链表(队列Push的一种实现)func InsertTail(head *Node, newNode *Node){ //先找到链表的最初一个节点,须要放弃头结点不动,这里创立一个两头长期节点 temp := head //遍历链表直到最初一个节点进行插入 for { if temp.next == nil { //这个条件就是链表的最初地位 break } temp = temp.next } //将新的节点接入到链表尾部 temp.next = newNode}//实现有序插入一个节点到链表中func InsertSort(head, newNode *Node){ temp := head //重点:插入时必须要在temp的地位让temp.next.no和newNode.no进行比拟,不然会错过插入机会 for { if temp.next == nil { //阐明曾经在链表的尾部了 //留神:上面两行的程序不能颠倒 newNode.next = temp.next temp.next = newNode break } else if temp.next.no >= newNode.no { newNode.next = temp.next temp.next = newNode break } temp = temp.next }}//实现头部插入节点(队列Push的一种实现)func InsertHead(head, newNode *Node){ newNode.next = head.next head.next = newNode}//实现一个删除链表节点的函数func Delete(head *Node, node *Node) { temp := head for { if temp.next == nil { //阐明曾经在链表的尾部了,没有找到要删除的节点 break } else if temp.next.no == node.no { temp.next = node.next //上面这种办法也能够,不过了解起来有点绕 //temp.next = temp.next.next break } temp = temp.next }}//实现一个头部删除链表节点的函数(队列Pop的一种实现)func DeleteHead(head *Node){ if head.next == nil{ return }else { head.next = head.next.next }}//实现一个尾部删除链表节点的函数(队列Pop的一种实现)func DeleteTail(head *Node){ temp := head for { if temp.next == nil{ //阐明链表为空 return }else if temp.next.next == nil{ temp.next = nil break } temp = temp.next }}//实现显示链表中所有节点信息func List(head *Node){ //前提是不能扭转头结点 temp := head if temp.next == nil { fmt.Println("链表为空") return } for { fmt.Printf("%d %s -->", temp.next.no, temp.next.name) //打印下一个节点的信息 temp = temp.next if temp.next == nil { break } }}func main(){ //定义一个头结点 head := &Node{} //定义一个节点信息 node1 := &Node{ no: 1, name: "Number1", next: nil, } node2 := &Node{ no: 2, name: "Number2", next: nil, } node3 := &Node{ no: 3, name: "Number3", next: nil, } node4 := &Node{ no: 2, name: "Number4", next: nil, } InsertTail(head, node1) InsertTail(head, node2) InsertSort(head, node3) InsertSort(head, node2) InsertSort(head, node1) InsertSort(head, node4) Delete(head, node4) InsertHead(head, node1) InsertHead(head, node2) InsertHead(head, node3) InsertHead(head, node4) DeleteHead(head) DeleteTail(head) List(head)}

July 2, 2021 · 2 min · jiezi

关于golang:Go-每日一库之-colly

简介colly是用 Go 语言编写的功能强大的爬虫框架。它提供简洁的 API,领有强劲的性能,能够主动解决 cookie&session,还有提供灵便的扩大机制。 首先,咱们介绍colly的基本概念。而后通过几个案例来介绍colly的用法和个性:拉取 GitHub Treading,拉取百度小说热榜,下载 Unsplash 网站上的图片。 疾速应用本文代码应用 Go Modules。 创立目录并初始化: $ mkdir colly && cd colly$ go mod init github.com/darjun/go-daily-lib/colly装置colly库: $ go get -u github.com/gocolly/colly/v2应用: package mainimport ( "fmt" "github.com/gocolly/colly/v2")func main() { c := colly.NewCollector( colly.AllowedDomains("www.baidu.com" ), ) c.OnHTML("a[href]", func(e *colly.HTMLElement) { link := e.Attr("href") fmt.Printf("Link found: %q -> %s\n", e.Text, link) c.Visit(e.Request.AbsoluteURL(link)) }) c.OnRequest(func(r *colly.Request) { fmt.Println("Visiting", r.URL.String()) }) c.OnResponse(func(r *colly.Response) { fmt.Printf("Response %s: %d bytes\n", r.Request.URL, len(r.Body)) }) c.OnError(func(r *colly.Response, err error) { fmt.Printf("Error %s: %v\n", r.Request.URL, err) }) c.Visit("http://www.baidu.com/")}colly的应用比较简单: ...

July 2, 2021 · 4 min · jiezi

关于golang:Go-每日一库之-termtables

简介明天学个简略点的,dateparse库时偶然见遇到了这个库。 疾速应用本文代码应用 Go Modules。 创立目录并初始化: $ mkdir termtables && cd termtables$ go mod init github.com/darjun/go-daily-lib/termtables装置termtables库: $ go get -u github.com/scylladb/termtables最原始的termtables库为github.com/apcera/termtables,而后原始仓库曾经被删除了。目前应用的都是其他人 fork 的仓库。 应用: package mainimport ( "fmt" "github.com/scylladb/termtables")func main() { t := termtables.CreateTable() t.AddHeaders("User", "Age") t.AddRow("dj", 18) t.AddRow("darjun", 30) fmt.Println(t.Render())}运行: $ go run main.go+--------+-----+| User | Age |+--------+-----+| dj | 18 || darjun | 30 |+--------+-----+应用很不便,首先调用termtables.CreateTable()创立一个表格对象,调用该对象的AddHeader()办法增加头部信息,而后调用AddRow()逐行增加数据。最初调用Render()返回渲染后的表格字符串。 模式解决一般的文本表格,termtables还反对输入 HTML 和 Markdown 格局的表格。只须要调用表格对象的SetModeHTML()/SetModeMarkdown()办法设置一些模式即可 。 func main() { t := termtables.CreateTable() t.AddHeaders("User", "Age") t.AddRow("dj", 18) t.AddRow("darjun", 30) fmt.Println("HTML:") t.SetModeHTML() fmt.Println(t.Render()) fmt.Println("Markdown:") t.SetModeMarkdown() fmt.Println(t.Render())}运行: ...

July 2, 2021 · 1 min · jiezi

关于golang:Go语言几种字符串的拼接方式比较

背景介绍在咱们理论开发过程中,不可避免的要进行一些字符串的拼接工作。比方将一个数组依照肯定的标点符号拼接成一个句子、将两个字段拼接成一句话等。 而在咱们Go语言中,对于字符串的拼接解决有许多种办法,咱们最常见的可能是间接用“+”加号进行拼接,或者应用join解决切片,再或者应用fmt.Sprintf("")去组装数据。 那么这就有个问题,咱们如何高效的应用字符串的拼接,在线上高并发的场景下,不同的字符串拼接办法对性能的影响又有多大? 上面我将对Go语言中常见的几种字符串的拼接办法进行测试,剖析每个办法的性能如何。 0 筹备工作为了测试各个办法的实际效果,本文将采纳benchmark来测试,这里仅对benchmark做一个简略的介绍,后续将会出一篇文章对benchmark进行具体的介绍。 benchmark是Go自带的测试利器,应用benchmark咱们能够方便快捷的测试一个函数办法在串行和并行环境下的体现,指定一个工夫(默认测试1秒),看被测办法在达到这个工夫下限所能执行的次数和内存分配情况。 benchmark的罕用API有如下几种: // 开始计时b.StartTimer() // 进行计时b.StopTimer() // 重置计时b.ResetTimer()b.Run(name string, f func(b *B))b.RunParallel(body func(*PB))b.ReportAllocs()b.SetParallelism(p int)b.SetBytes(n int64)testing.Benchmark(f func(b *B)) BenchmarkResult本文次要用的是以下三种 b.StartTimer() // 开始计时 b.StopTimer() // 进行计时 b.ResetTimer() // 重置计时 在编写实现测试文件后,执行命令go test -bench=. -benchmem 能够执行测试文件,并显示内存 1 构建测试用例这里我在测试文件里会有一个全局的slice,用来做拼接的原始数据集。 var StrData = []string{"Go语言高效拼接字符串"}而后应用在init函数里进行数据组装,把这个全局的slice变大,同时能够管制较大的slice的拼接和较小的slice拼接有什么区别。 func init() { for i := 0; i < 200; i++ { StrData = append(StrData, "Go语言高效拼接字符串") }}1.1 “+”间接拼接func StringsAdd() string { var s string for _, v := range StrData { s += v } return s}// 测试方法func BenchmarkStringsAdd(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { StringsAdd() } b.StopTimer()}1.2 应用fmt包进行组装func StringsFmt() string { var s string = fmt.Sprint(StrData) return s}// 测试方法func BenchmarkStringsFmt(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { StringsFmt() } b.StopTimer()}1.3 应用strings包的join办法func StringsJoin() string { var s string = strings.Join(StrData, "") return s}// 测试方法func BenchmarkStringsJoin(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { StringsJoin() } b.StopTimer()}1.4 应用bytes.Buffer拼接func StringsBuffer() string { var s bytes.Buffer for _, v := range StrData { s.WriteString(v) } return s.String()}// 测试方法func BenchmarkStringsBuffer(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { StringsBuffer() } b.StopTimer()}1.5 应用strings.Builder拼接func StringsBuilder() string { var b strings.Builder for _, v := range StrData { b.WriteString(v) } return b.String()}// 测试方法func BenchmarkStringsBuilder(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { StringsBuilder() } b.StopTimer()}2 测试后果及剖析2.1 应用benchmark运行测试,查看后果接下来执行:go test -bench=. -benchmem 命令来获取benchmark的测试后果。 ...

July 1, 2021 · 4 min · jiezi

关于golang:golangselect详解

golang select 详解前言select 是golang用来做channel多路复用的一种技术,和switch的语法很像,不过每个case只能够有一个channel,send 操作和 receive 操作都应用 “<-” 操作符,在 send 语句中,channel 和值别离在操作符左右两边,在 receive 语句中,操作符放在 channel 操作数的后面。示例: c0 := make(chan struct{}) c1 := make(chan int, 100) for { select { case <-c0: return case <-c1: return } }select与channel之前channel详解文章中讲到过channel的阻塞写、阻塞读、非阻塞写、非阻塞读,这里不再赘述,须要阐明的是,select不止用来做channel的非阻塞操作,次要是用来作为多路复用操作channel的,机制和linux的select很像不同的写法会触发不同的机制,上面咱们看看示例 // 阻塞读,对应channel的 chanrecv1函数select {case <-c0: return}// 非阻塞读,对应channel的 selectnbrecv 函数select {case <-c0: returndefault: return}// 多路复用select {case <-c0: returncase <-c1: returndefault: return}从下面的代码中能够看出select的三种机制1:只有一个case,并且没有default,相当于 <- c0的写法,阻塞读写数据2:一个case,一个default,就会间接对应channel的非阻塞读写数据3:有多个case,对应了真正的select多路复用机制,case随机执行,源码位于runtime/select.go明天咱们次要来讨论一下第三种机制 数据结构const ( caseNil = iota caseRecv caseSend caseDefault)type scase struct { c *hchan // channel elem unsafe.Pointer // 发送或者承受数据的变量地址 kind uint16 // case类型,<-, >-, default 对应上方常量 //...}因为非 default 的 case 中都与 Channel 的发送和接收数据无关,所以在 scase 构造体中也蕴含一个 c 字段用于存储 case 中应用的 Channel,elem 是用于接管或者发送数据的变量地址、kind 示意以后 case 的品种 ...

July 1, 2021 · 6 min · jiezi

关于golang:关于指针

转载:https://www.zhihu.com/questio...

July 1, 2021 · 1 min · jiezi

关于golang:Mix-VarWatch-Go-监视配置变量数据的变化并执行一些任务

OpenMix 出品:https://openmix.orgMix VarWatch监督配置构造体变量的数据变动并执行一些工作 Monitor the data changes of configuration structure variables and perform some tasks 源码地址Star 一下不迷路,下次用的时候还能找到 https://github.com/mix-php/vegaInstallationgo get github.com/mix-go/varwatchUsage当采纳 spf13/viper jinzhu/configor 这种绑定变量的配置库来动静更新配置信息 任何采纳 &Config 指针绑定数据的配置库都能够var Config struct { Logger struct { Level int `json:"level"` } `json:"logger" varwatch:"logger"` Database struct { User string `json:"user"` Pwd string `json:"pwd"` Db string `json:"db"` MaxOpen int `json:"max_open"` MaxIdle int `json:"max_idle"` } `json:"database" varwatch:"database"`}err := viper.Unmarshal(&Config)以动静批改日志级别举例:当 Config.Logger.Level 发生变化时咱们须要执行一些代码批改日志的级别 首先将 Logger 节点配置 varwatch:"logger" 标签信息而后采纳以下代码执行监听逻辑w := varwatch.NewWatcher(&Config, 10 * time.Second)w.Watch("logger", func() { // 获取变动后的值 lv := Config.Logger.Level // 批改 logrus 的日志级别 logrus.SetLevel(logrus.Level(uint32(lv)))})须要动静批改连接池信息,或者数据库账号密码都能够通过下面的范例实现。 ...

July 1, 2021 · 1 min · jiezi

关于golang:使用nginx作grpc的反向代理踩坑总结

本文记录应用nginx作gRPC的反向代理踩的坑和解决办法。 背景家喻户晓,nginx是一款高性能的web服务器,罕用于负载平衡和反向代理。所谓的反向代理是和正向代理绝对应,正向代理即咱们惯例意义上了解的“代理”:例如失常状况下在国内是无法访问google的,如果咱们须要拜访,就须要通过一层代理去转发。这个正向代理代理的是服务端(也就是google),而反向代理则相同,代理的是客户端(也就是用户),用户的申请达到nginx后,nginx会代理用户的申请向理论的后端服务发动申请,并将后果返回给用户。(图片来自维基百科) 正向代理和反向代理实际上是站在用户的角度来定义的,正向也就是代理用户所要申请的服务,而反向则是代理用户向服务发动申请。两者一个很重要的区别: 正向代理服务方不感知申请方,反向代理申请方不感知服务方。思考一下下面的例子,你通过代理拜访google时,google只能感知到申请来自代理服务器,而无奈间接感知到你(当然通过cookie等伎俩也能够追踪到);而通过nginx反向代理时,你是不感知申请具体被转发到哪个后端服务器上的。 nginx最常被用于反向代理的场景就是咱们所熟知的http协定,通过配置nginx.conf文件能够很简略地定义一个反向代理规定: worker_processes 1;events { worker_connections 1024;}http { include mime.types; default_type application/octet-stream; server { listen 80; server_name localhost; location / { proxy_pass http://domain; } }}nginx从1.13.10当前就反对gRPC协定的反向代理,配置相似: worker_processes 1;events { worker_connections 1024;}http { include mime.types; default_type application/octet-stream; server { listen 81 http2; server_name localhost; location / { grpc_pass http://ip; } }}然而当需要场景更加简单的时候,就发现nginx的gRPC模块实际上有很多坑,实现的能力不如http残缺,当套用http的解决方案时就会呈现问题 场景最开始咱们的场景很简略,通过gRPC协定实现一个简略的C/S架构:但这种单纯的直连有些场景下是不可行的,例如client和server在两个网络环境下,彼此不相连通,那就无奈通过简略的gRPC连贯拜访服务。一种解决办法是通过两头的代理服务器转发,用下面说的nginx反向代理gRPC办法:nginx proxy部署在两个环境都能拜访的集群上,这样就实现了跨网络环境的gRPC拜访。随之而来的问题是如何配置这个路由规定?留神咱们最开始的gRPC的指标节点都是清晰的,也就是server1和server2的ip地址,当两头加了一层nginx proxy后,client发动的gRPC申请的对象都是nginx proxy的ip地址。那client与nginx建设连贯后,nginx如何晓得须要将申请转发给server1还是server2呢?(这里server1和server2不是简略的同一个服务的冗备部署,可能须要依据申请的属性决定由谁响应,例如用户id等,因而不能应用负载平衡随机筛选一个响应申请) 解决办法如果是http协定,那有很多实现办法: 通过门路辨别申请将server的信息增加在path里,例如:/server1/service/method,而后nginx转发申请的时候还原为原始的申请: worker_processes 1;events { worker_connections 1024;}http { include mime.types; default_type application/octet-stream; server { listen 80; server_name localhost; location ~ ^/server1/ { proxy_pass http://domain1/; } location ~ ^/server2/ { proxy_pass http://domain2/; } }}留神http://domain/最初的斜杠,如果没有这个斜杠申请的门路会是/server1/service/method,而服务端只能响应/service/method的申请,这样就会报404的谬误。 ...

June 30, 2021 · 2 min · jiezi

关于golang:Golang底层实现系列channel的底层实现

Golang底层实现系列-channel的底层实现channel底层构造hchan: type hchan struct { qcount uint // buffer中曾经存在的元素个数 dataqsiz uint // buffer的缓存大小(最大元素个数) buf unsafe.Pointer // buffer地址 elemsize uint16 // buffer中每个元素的大小 closed uint32 // channel是否曾经敞开,0未敞开 elemtype *_type // channel中的元素的类型 sendx uint // buffer中已发送的索引地位 recvx uint // buffer中已承受的索引地位 recvq waitq // 期待接管的sudog(sudog为封装了goroutine和数据的构造)队列 sendq waitq // 期待发送的sudogo队列 lock mutex // 一个轻量级锁}向channel中发送简略流程形容: 查看 recvq 是否为空,如果不为空,则从 recvq 头部取一个 goroutine,将数据发送过来,并唤醒对应的 goroutine 即可。如果 recvq 为空,则将数据放入到 buffer 中。如果 buffer 已满,则将要发送的数据和以后 goroutine 打包成 sudog 对象放入到 sendq 中。并将以后 goroutine 置为 waiting 状态。流程图: ...

June 30, 2021 · 1 min · jiezi

关于golang:Go语言中的channel是如何设计的

前言哈喽,大家好,我是asong。终于回归了,停更了两周了,这两周始终在搞留言号的事,通过漫长的期待,终于搞定了。兄弟们,当前就能够在留言区纵情开喷了,只有你敢喷,我就敢精选。(因为产生了账号迁徙,需点击右上角从新增加星标,优质文章第一工夫获取!) 明天给大家带来的是Go语言中的channel。Go语言从入世以来就以高并发著称,得益于其Goroutine的设计,Goroutine也就是一个可执行的轻量级协程,有了Goroutine咱们能够轻松的运行协程,但这并不能满足咱们的需要,咱们往往还心愿多个线程/协程是可能通信的,Go语言为了反对多个Goroutine通信,设计了channel,本文咱们就一起从GO1.15的源码登程,看看channel到底是如何设计的。 好啦,开往幼儿园的列车就要开了,敌人们系好安全带,我要开车啦 什么是channel通过结尾的介绍咱们能够晓得channel是用于goroutine的数据通信,在Go中通过goroutine+channel的形式,能够简略、高效地解决并发问题。咱们先来看一下简略的示例: func GoroutineOne(ch chan <-string) { fmt.Println("GoroutineOne running") ch <- "asong真帅" fmt.Println("GoroutineOne end of the run")}func GoroutineTwo(ch <- chan string) { fmt.Println("GoroutineTwo running") fmt.Printf("女朋友说:%s\n",<-ch) fmt.Println("GoroutineTwo end of the run")}func main() { ch := make(chan string) go GoroutineOne(ch) go GoroutineTwo(ch) time.Sleep(3 * time.Second)}// 运行后果// GoroutineOne running// GoroutineTwo running// 女朋友说:asong真帅// GoroutineTwo end of the run// GoroutineOne end of the run这里咱们运行了两个Goroutine,在GoroutineOne中咱们向channel中写入数据,在GoroutineTwo中咱们监听channel,直到读取到"asong真帅"。咱们能够画一个简略的图来表明一下这个程序: 下面的例子是对无缓冲channel的一个简略利用,其实channel的应用语法还是挺多的,上面且听我缓缓道来,毕竟是从入门到放弃嘛,那就先从入门开始。 入门channelchannel类型channel有三种类型的定义,别离是:chan、chan <-、<- chan,可选的<-代表channel的方向,如果咱们没有指定方向,那么channel就是双向的,既能够接收数据,也能够发送数据。 chan T // 接管和发送类型为T的数据chan<- T // 只能够用来发送 T 类型的数据<- chan T // 只能够用来接管 T 类型的数据创立channel咱们能够应用make初始化channel,能够创立两种两种类型的channel:无缓冲的channel和有缓冲的channel。 ...

June 30, 2021 · 8 min · jiezi

关于golang:上手最容易的go微服务框架

github.com/little-bit-shy/go-xgz(我的项目地址) go-xgz应用最容易的go微服务框架 1分钟即可搭建一个本人的微服务 以后我的项目为Go微服务脚手架 开发环境Centos\Ubuntu 目录阐明go-xgz├─ ......├─# 业务代码├─internal│ ├─# 定时工作注册层│ ├─cli│ ├─# 数据通讯层│ ├─dao│ ├─# 数据交互层│ ├─data│ ├─# 依赖注入层│ ├─di│ ├─# 服务注册层│ ├─server│ ├─# 服务业务层│ ├─service│ └─└─应用阐明通信容器:make bash 拉取镜像建设容器:make build 初始化脚手架:make init 革除依赖:make clean 依赖更新:make mod 依赖查看:make tidy 我的项目打包:make install 编译proto协定(生成服务文档):make proto 编译依赖注入:make write 运行脚手架:make run 运行zipkin:make zipkin 运行jaeger:make jaeger 初始化脚手架开发环境要求:docker wget -qO- https://get.docker.com/ | sh 第一步:git clone -b v1.0.0 --depth=1 http://github.com/little-bit-shy/go-xgz.git 第二步:cd go-xgz && make init 我的项目初始化初始化过程中禁止中断旧我的项目地位为/data旧我的项目包名为github.com/little-bit-shy/go-xgz以后文件夹为data初始化我的项目包名(示例:github.com/little-bit-shy/go-xgz): github.com/little-bit-shy/abc 以后包名非法开始重构我的项目...go: creating new go.mod: module github.com/little-bit-shy/abcgo: finding module for package github.com/olivere/elastic/v7go: finding module for package github.com/go-playground/validator/v10go: finding module for package github.com/shopspring/decimalgo: finding module for package github.com/google/wirego: found github.com/google/wire in github.com/google/wire v0.5.0go: found github.com/olivere/elastic/v7 in github.com/olivere/elastic/v7 v7.0.22go: found github.com/shopspring/decimal in github.com/shopspring/decimal v1.2.0go: found github.com/go-playground/validator/v10 in github.com/go-playground/validator/v10 v10.4.1我的项目初始化实现第三步:make run ...

June 29, 2021 · 1 min · jiezi

关于golang:重学-C-语言

重学 C 语言近几年始终在做 Web 开发,从PHP到Go,从数据库到缓存。随着技术的深刻,越发感到非科班转行写代码的我在了解源码和技术细节的力不从心。万丈高楼平地起,基础知识不牢固很难持续晋升。我打算近期重学 C 语言,夯实基础知识。 步骤我不是为了应酬学校考试,不会花太多工夫在记忆语法上。过一遍语法后开始入手写代码,遇到问题后再去查资料解决。有其余语言语法根底不必在语法上过多纠结,专一于 C 语言和其余语言相比的不同个性上。 视频教程可能有人感觉看视频比看书花工夫更多,而我集体更喜爱看视频教程。一方面是因为看视频能产生具体形象,加深记忆。另一方面每天通勤工夫很长,在地铁上近 50 分钟,这段时间不能节约,我随身携带 Kindle 看电子书,然而嘈杂嘈杂的地铁上无奈集中注意力,尤其看比拟干燥的技术类书籍。当然并不只是看了视频就会了,书籍是后续补充。 B 站各类自学视频资源十分多,能够充分利用起来。 代码实战"光说不念假把式"。编程是一门偏实际的学科,只有在一直实际的过程中能力真正烂熟于心,一直挖坑踩坑填坑中成长。用 C 语言写个简略的 web 服务器,反对HTTP和Tcp协定。后续浏览开源我的项目比方Redis的源代码,加深了解。记录心得体会在实际和学习过程中,以文章的模式继续输入。把C语言的特点和高级语言做比照,也会举荐比价好的教程。重点指标第一阶段:内存分区、函数调用模型数据类型、变量的含意内存四区特点函数调用模型内存操作函数宏定义与预处理第二阶段:指针高级指针根本应用指针的步长的概念指针简介赋值指针以及多级指针做函数参数的个性与应用通过指针操作字符串const 类型修饰符位的逻辑运算符移位运算符数组指针与指针数组多维数组做函数参数构造体嵌套一级指针构造体嵌套二级指针浅拷贝深拷贝第三阶段:函数指针与回调函数封装和设计思维函数指针定义的三种形式回调函数第四阶段:数据结构与链表链表的基本概念单链表的相干操作第五阶段:递归函数递归函数的概念递归的应用第六阶段:数据结构与算法大 O 表示法基本概念线性表顺序存储和链式存储概念线性表的顺序存储(数组)线性表的链式存储(链表)队列的程序与链式存储栈的程序与链式存储二叉树基本操作罕用排序算法第七阶段:接口的封装和设计函数的封装设计解耦合的设计理念模块实现与业务拆散的思维本阶段能够把握的外围能力:把握 C 语言当中的字符串、一堆数组、二维数组的用法;把握一级指针,二级指针,三级指针的高级用法,了解 N 级指针概念,指针数组和数组指针;学会构造体、文件的应用 C 语言接口封装设计可解决的显示问题:能够实现本人开发小程序,例如贪吃蛇一类的用 C 语言写的程序;利用 C 语言接口封装设计的办法,进行企业我的项目开发总结我学 C 语言的目标不是要在工作中用它开发web利用,而是要帮忙我更加深刻理解计算机工作原理。以前的技术老大说过一句话,“C语言开发者看其余语言都是语法糖”。 与君共勉。 微信公众号

June 28, 2021 · 1 min · jiezi

关于golang:golangchannel详解

golang channel 详解前言CSP:不要通过共享内存来通信,而要通过通信来实现内存共享,它是Go 的并发哲学,基于 channel 实现。Channel是Go中的一个外围类型,你能够把它看成一个管道,通过它并发外围单元就能够发送或者接收数据进行通信(communication)。 数据结构runtime/chan.go type hchan struct { qcount uint // 队列中残余元素 dataqsiz uint // 队列长度,eg make(chan int64, 5), dataqsiz为5 buf unsafe.Pointer // 数据存储环形数组 elemsize uint16 // 每个元素的大小 closed uint32 // 是否敞开 0 未敞开 elemtype *_type // 元素类型 sendx uint // 发送者写入地位 recvx uint // 接受者读数据地位 recvq waitq // 接收者队列,保留正在读取channel的goroutian sendq waitq // 发送者队列,保留正在发送channel的goroutian lock mutex // 锁}waitq是双向链表,sudog为goroutian的封装 type waitq struct { first *sudog last *sudog}make(chan int, 6) ...

June 28, 2021 · 8 min · jiezi

关于golang:红黑树未验证

package mainimport "fmt"const ( RED bool = true BLACK bool = false)type RBTree struct { root *Node}func NewRBTree() *RBTree { return &RBTree{}}func (t *RBTree) Search (v int64) *Node { n := t.root for n != nil { if v < n.v { n = n.l } else if v >n.v { n = n.r } else { return n } } return nil}func (t *RBTree) Insert(v int64) { if t.root == nil { t.root = &Node{v:v, c:BLACK} return } n := t.root addNode := &Node{v:v, c:RED} var np *Node for n != nil { if (v == n.v) { return } np = n if v < n.v { n = n.l } else { n = n.r } } addNode.p = np if v < np.v { np.l = addNode } else { np.r = addNode } //验证规定 t.insertFix(addNode)}func (t *RBTree) insertFix(n *Node) { for !isBlack(n.p) { uncle := n.p.getBrother() if !isBlack(uncle) { n.p.c = BLACK uncle.c = BLACK uncle.p.c = RED n = n.p.p t.root.c = BLACK continue } if n.p == n.p.p.l { if n == n.p.l { n.p.p.c = RED n.p.c = BLACK n = n.p.p n.rTurn(t) t.root.c = BLACK continue } n = n.p n.lTurn(t) t.root.c = BLACK continue } if n == n.p.r { n.p.p.c = RED n.p.c = BLACK n = n.p.p n.lTurn(t) t.root.c = BLACK continue } n = n.p n.rTurn(t) t.root.c = BLACK }}func (t *RBTree) Del(v int64) { n := t.Search(v) np := n.p if n == nil { return } var revise string if n.l == nil && n.r == nil { revise = "none" } else if np == nil { revise = "root" } else if n == np.l { revise = "left" } else if n == np.r { revise = "right" } //内含递归 n.del(t) if isBlack(n) { if revise == "root" { t.delFix(t.root) } else if revise == "left" { t.delFix(np.l) } else if revise == "right" { t.delFix(np.r) } }}func (t *RBTree) delFix(n *Node) { var b *Node for n != t.root && isBlack(n) { //删除节点为左节点,存在兄弟节点 if n.p.l == n && n.p.r != nil { b = n.p.r if !isBlack(b) { b.c = BLACK n.p.c = RED n.p.lTurn(t) } else if isBlack(b) && b.l != nil && isBlack(b.l) && b.r != nil && isBlack(b.r){ b.c = RED n = n.p } else if isBlack(b) && b.l != nil && !isBlack(b.l) && b.r != nil && isBlack(b.r) { b.c = RED b.l.c = BLACK b.rTurn(t) } else if isBlack(b) && b.r != nil && !isBlack(b.r) { b.c = RED b.r.c = BLACK b.p.c = BLACK b.p.lTurn(t) n = t.root } } else if n.p.r == n && n.p.l != nil { b = n.p.l if !isBlack(b) { b.c = BLACK n.p.c = RED n.p.rTurn(t) } else if isBlack(b) && b.l != nil && isBlack(b.l) && b.r != nil && isBlack(b.r) { b.c = RED n = n.p } else if isBlack(b) && b.l != nil && isBlack(b.l) && b.r != nil && !isBlack(b.r) { b.c = RED b.r.c = BLACK b.lTurn(t) } else if isBlack(b) && b.l != nil && !isBlack(b.l) { b.c = RED b.l.c = BLACK b.p.c = BLACK b.p.rTurn(t) n = t.root } } else { return } }}func (t *RBTree) min(n *Node) *Node { if n == nil { return nil } for n.l != nil { n = n.l } return n}func (t *RBTree) max(n *Node) *Node { if n == nil { return nil } for n.r != nil { n =n.r } return n}//获取前驱节点func (t *RBTree) getPredecessor(n *Node) *Node { if n == nil { return nil } //小分支里的最大节点 if n.l != nil { return t.max(n.l) } //如果不存在又节点就从父节点中找 for { if n.p == nil { break } if n == n.p.r { return n.p } n = n.p } return nil}//获取继承节点func (t *RBTree) getSuccessor(n *Node) *Node { if n == nil { return nil } //大分支里的最小节点 if n.r != nil { return t.min(n.r) } //如果不存在又节点就从父节点中找 for { if n.p == nil { break } if n == n.p.l { return n.p } n = n.p } return nil}/////////////////////////////////////func isBlack(n *Node) bool { if n == nil { return true } return n.c == BLACK}func setColor(n *Node, color bool) { if n == nil { return } n.c = color}////////////////////////////////////////////////////////////type Node struct { l, r, p *Node v int64 c bool}func (n *Node) lTurn(t *RBTree) { if n == nil || n.r == nil { return } np := n.p nr := n.r //父节点解决 if np != nil { if n == np.l { np.l = nr } else { np.r = nr } } else { t.root = nr } //解决本人关系 n.p = nr n.r = nr.l if n.r != nil { //左孙节点解决 n.r.p = n } //右子节点解决 nr.l = n nr.p = np}func (n *Node) rTurn(t *RBTree) { if n == nil || n.l == nil { return } nl := n.l np := n.p //父节点解决 if np != nil { if n == np.l { np.l = nl } else { np.r = nl } } else { t.root = nl } //解决本人关系 n.p = nl n.l = nl.r if n.l != nil { //右孙节点解决 n.l.p = n } //左子节点解决 nl.r = n nl.p = np }func (n *Node) getBrother() *Node { if n.p == nil { return nil } if n.p.l == n { return n.p.r } if n.p.r == n { return n.p.l } return nil}func (n *Node) del(t *RBTree) { np := n.p if n.l == nil && n.r == nil { //节点为尾部节点(不存在子节点) //根节点、左尾节点、右尾节点 if n == t.root { t.root = nil } else if np.l == n { np.l = nil } else { np.r = nil } } else if n.l != nil && n.r == nil { //存在左子节点 if n == t.root { //根节点 n.l.p = nil t.root = n.l } else { n.l.p = np if np.l == n { np.l = n.l } else { np.r = n.l } } } else if n.l == nil && n.r != nil { //存在右子节点 if n == t.root { n.r.p = nil t.root = n.r } else { n.r.p = np if np.l == n { np.l = n.r } else { np.r = n.r } } } else { //存在两个节点 successor := t.getSuccessor(n) n.v = successor.v n.c = successor.c //递归删除 successor.del(t) }}func main() { btree := NewRBTree() btree.Insert(1) btree.Insert(21) btree.Insert(3) btree.Insert(4) btree.Insert(5) btree.Insert(6) btree.Insert(7) btree.Insert(8) btree.Insert(9) btree.Insert(10) btree.Insert(1) fmt.Println(btree.Search(11)) fmt.Println(btree.Search(10)) fmt.Println(btree.Search(9)) fmt.Println(btree.Search(8)) fmt.Println(btree.Search(7)) fmt.Println(btree.Search(6)) fmt.Println(btree.Search(5)) fmt.Println(btree.Search(4)) fmt.Println(btree.Search(3)) fmt.Println(btree.Search(21)) fmt.Println(btree.Search(1)) fmt.Println(btree.root)}

June 28, 2021 · 6 min · jiezi

关于golang:vs-终端命令不生效

1.vs中golang环境变量不失效新增加的golang GOPATH变量后,vs cmd golang环境变量不失效,在cmd命令行中环境变量是失效的 2.解决办法重启vs即可,使GOPATH环境变量失效

June 28, 2021 · 1 min · jiezi

关于golang:Go-每日一库之-resty

简介resty是 Go 语言的一个 HTTP client 库。resty功能强大,个性丰盛。它反对简直所有的 HTTP 办法(GET/POST/PUT/DELETE/OPTION/HEAD/PATCH等),并提供了简略易用的 API。 疾速应用本文代码应用 Go Modules。 创立目录并初始化: $ mkdir resty && cd resty$ go mod init github.com/darjun/go-daily-lib/resty装置resty库: $ go get -u github.com/go-resty/resty/v2上面咱们来获取百度首页信息: package mainimport ( "fmt" "log" "github.com/go-resty/resty/v2")func main() { client := resty.New() resp, err := client.R().Get("https://baidu.com") if err != nil { log.Fatal(err) } fmt.Println("Response Info:") fmt.Println("Status Code:", resp.StatusCode()) fmt.Println("Status:", resp.Status()) fmt.Println("Proto:", resp.Proto()) fmt.Println("Time:", resp.Time()) fmt.Println("Received At:", resp.ReceivedAt()) fmt.Println("Size:", resp.Size()) fmt.Println("Headers:") for key, value := range resp.Header() { fmt.Println(key, "=", value) } fmt.Println("Cookies:") for i, cookie := range resp.Cookies() { fmt.Printf("cookie%d: name:%s value:%s\n", i, cookie.Name, cookie.Value) }}resty应用比较简单。 ...

June 27, 2021 · 4 min · jiezi

关于golang:使用-Golang-实现-SSH-隧道功能

文章目录:指标包 golang.org/x/cryptogopkg.in/yaml.v2留神 本文讲的是客户端局部文章应用到的软件: Mac 12.0 Beta(macOS Monterey),处理器为:M1Goland 2021.1.3Golang 1.17beta1 指标 通过Go在客户端实现ssh隧道性能并连贯到服务器的mysqlGo程序 Gitee 网址Github 网址在工作目录创立一个go应用程序,并配置SSH的信息....还是看正文吧! 阿巴阿巴阿巴 package mainfunc main() { // 设置SSH配置 config := &ssh.ClientConfig{ // 服务器用户名 User: "", Auth: []ssh.AuthMethod{ // 服务器明码 ssh.Password(""), }, Timeout: 30 * time.Second, HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error { return nil }, } // 设置本地监听器,格局:地址:端口 localListener, err := net.Listen("tcp", "localhost:13306") if err != nil { fmt.Printf("net.Listen failed: %v\n", err) } for { localConn, err := localListener.Accept() if err != nil { fmt.Printf("localListener.Accept failed: %v\n", err) } go forward(localConn, config) }}// 转发func forward(localConn net.Conn, config *ssh.ClientConfig) { // 设置服务器地址,格局:地址:端口 sshClientConn, err := ssh.Dial("tcp", "", config) if err != nil { fmt.Printf("ssh.Dial failed: %s", err) } // 设置近程地址,格局:地址:端口(请在服务器通过 ifconfig 查看地址) sshConn, err := sshClientConn.Dial("tcp", "") // 将localConn.Reader复制到sshConn.Writer go func() { _, err = io.Copy(sshConn, localConn) if err != nil { fmt.Printf("io.Copy failed: %v", err) } }() // 将sshConn.Reader复制到localConn.Writer go func() { _, err = io.Copy(localConn, sshConn) if err != nil { fmt.Printf("io.Copy failed: %v", err) } }()}疾速安顿 ...

June 27, 2021 · 1 min · jiezi

关于golang:GO进阶训练营

download:GO进阶训练营<!-- <!DOCTYPE html><html xmlns="http://www.w3.org/1999/xhtml"><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /><title>JS贪吃蛇</title> <style type="text/css"> #pannel table { border-collapse: collapse; } #pannel table td { border: 1px solid #808080; width: 10px; height: 10px; font-size: 0; line-height: 0; overflow: hidden; } #pannel table .snake { background-color: green; } #pannel table .food { background-color: blue; }</style><script type="text/javascript">var Direction = new function () { this.UP = 38; this.RIGHT = 39; this.DOWN = 40; this.LEFT = 37;};var Common = new function () { this.width = 20; /*程度方向方格数*/ this.height = 20; /*垂直方向方格数*/ this.speed = 250; /*速度 值越小越快*/ this.workThread = null;};var Main = new function () { var control = new Control(); window.onload = function () { control.Init("pannel"); /*开始按钮*/ document.getElementById("btnStart").onclick = function () { control.Start(); this.disabled = true; document.getElementById("selSpeed").disabled = true; document.getElementById("selSize").disabled = true; }; /*调速度按钮*/ document.getElementById("selSpeed").onchange = function () { Common.speed = this.value; } /*调大小按钮*/ document.getElementById("selSize").onchange = function () { Common.width = this.value; Common.height = this.value; control.Init("pannel"); } };};/*控制器*/function Control() { this.snake = new Snake(); this.food = new Food(); /*初始化函数,创建表格*/ this.Init = function (pid) { var html = []; html.push("<table>"); for (var y = 0; y < Common.height; y++) { html.push("<tr>"); for (var x = 0; x < Common.width; x++) { html.push('<td id="box_' + x + "_" + y + '"> </td>'); } html.push("</tr>"); } html.push("</table>"); this.pannel = document.getElementById(pid); this.pannel.innerHTML = html.join(""); }; /*开始游戏 - 监听键盘、创立食物、刷新界面线程*/ this.Start = function () { var me = this; this.MoveSnake = function (ev) { var evt = window.event || ev; me.snake.SetDir(evt.keyCode); }; try { document.attachEvent("onkeydown", this.MoveSnake); } catch (e) { document.addEventListener("keydown", this.MoveSnake, false); } this.food.Create(); Common.workThread = setInterval(function () { me.snake.Eat(me.food); me.snake.Move(); }, Common.speed); };}/*蛇*/function Snake() { this.isDone = false; this.dir = Direction.RIGHT; this.pos = new Array(new Position()); /*挪动 - 擦除尾部,向前挪动,判断游戏完结(咬到本人或者移出边界)*/ this.Move = function () { document.getElementById("box_" + this.pos[0].X + "_" + this.pos[0].Y).className = ""; //所有 向前挪动一步 for (var i = 0; i < this.pos.length - 1; i++) { this.pos[i].X = this.pos[i + 1].X; this.pos[i].Y = this.pos[i + 1].Y; } //从新设置头的地位 var head = this.pos[this.pos.length - 1]; switch (this.dir) { case Direction.UP: head.Y--; break; case Direction.RIGHT: head.X++; break; case Direction.DOWN: head.Y++; break; case Direction.LEFT: head.X--; break; } this.pos[this.pos.length - 1] = head; //遍历画蛇,同时判断游戏完结 for (var i = 0; i < this.pos.length; i++) { var isExits = false; for (var j = i + 1; j < this.pos.length; j++) if (this.pos[j].X == this.pos[i].X && this.pos[j].Y == this.pos[i].Y) { isExits = true; break; } if (isExits) { this.Over(); /*咬本人*/ break; } var obj = document.getElementById("box_" + this.pos[i].X + "_" + this.pos[i].Y); if (obj) obj.className = "snake"; else { this.Over(); /*移出边界*/ break; } } this.isDone = true; }; /*游戏完结*/ this.Over = function () { clearInterval(Common.workThread); alert("游戏完结!"); location.reload(); }/*吃食物*/ this.Eat = function (food) { var head = this.pos[this.pos.length - 1]; var isEat = false; switch (this.dir) { case Direction.UP: if (head.X == food.pos.X && head.Y == food.pos.Y + 1) isEat = true; break; case Direction.RIGHT: if (head.Y == food.pos.Y && head.X == food.pos.X - 1) isEat = true; break; case Direction.DOWN: if (head.X == food.pos.X && head.Y == food.pos.Y - 1) isEat = true; break; case Direction.LEFT: if (head.Y == food.pos.Y && head.X == food.pos.X + 1) isEat = true; break; } if (isEat) { this.pos[this.pos.length] = new Position(food.pos.X, food.pos.Y); food.Create(this.pos); } }; /*管制挪动方向*/ this.SetDir = function (dir) { switch (dir) { case Direction.UP: if (this.isDone && this.dir != Direction.DOWN) { this.dir = dir; this.isDone = false; } break; case Direction.RIGHT: if (this.isDone && this.dir != Direction.LEFT) { this.dir = dir; this.isDone = false; } break; case Direction.DOWN: if (this.isDone && this.dir != Direction.UP) { this.dir = dir; this.isDone = false; } break; case Direction.LEFT: if (this.isDone && this.dir != Direction.RIGHT) { this.dir = dir; this.isDone = false; } break; } };}/*食物*/function Food() { this.pos = new Position(); /*创立食物 - 随机地位创建设*/ this.Create = function (pos) { document.getElementById("box_" + this.pos.X + "_" + this.pos.Y).className = ""; var x = 0, y = 0, isCover = false; /*排除蛇的地位*/ do { x = parseInt(Math.random() * (Common.width - 1)); y = parseInt(Math.random() * (Common.height - 1)); isCover = false; if (pos instanceof Array) { for (var i = 0; i < pos.length; i++) { if (x == pos[i].X && y == pos[i].Y) { isCover = true; break; } } } } while (isCover); this.pos = new Position(x, y); document.getElementById("box_" + x + "_" + y).className = "food"; };}function Position(x, y) { this.X = 0; this.Y = 0; if (arguments.length >= 1) this.X = x; if (arguments.length >= 2) this.Y = y;}</script></head> ...

June 25, 2021 · 4 min · jiezi

关于golang:go-深浅拷贝结构体对象

在 go 中,当咱们以 new 或 & 的形式,创立一个 struct 的对象实例后(指针),如果间接应用赋值运算符,则像其余语言一样,会浅拷贝到一个新对象,二者指向的内存地址是雷同的。go 并没有相似 clone 这种 深拷贝 操作 关键字,那如何简略疾速的 深拷贝 一个 对象 ? package mainimport ( "encoding/json" "fmt")type Student struct { Name string Age uint8}func main() { // 深拷贝 将 stud1 对象 深拷贝出一个 stud2 // stud1 和 stud2 指向了两块内存地址 正本 stud1 := &Student{Name: "sqrtCat", Age: 35} // 为 tmp 调配新的内存地址 tmp := *stud1 // 将 tmp 的内存地址赋给指针变量 stud2 stud2 := &tmp stud2.Name = "bigCat" fmt.Printf("%+v\n%+v\n", stud1, stud2) // 浅拷贝 stud3 := &Student{Name: "sqrtCat", Age: 35} stud4 := stud3 stud4.Name = "bigCat" fmt.Printf("%+v\n%+v\n", stud3, stud4)}还有个指针和变量的小例子能够参阅 ...

June 25, 2021 · 1 min · jiezi

关于golang:Go-每日一库之-dateparse

简介不论什么时候,解决工夫总是让人头疼的一件事件。因为工夫格局太多样化了,再加上时区,夏令时,闰秒这些细枝末节解决起来更是艰难。所以在程序中,波及工夫的解决咱们个别借助于规范库或第三方提供的工夫库。明天要介绍的dateparse专一于一个很小的工夫解决畛域——解析日期工夫格局的字符串。 疾速应用本文代码应用 Go Modules。 创立目录并初始化: $ mkdir dateparse && cd dateparse$ go mod init github.com/darjun/go-daily-lib/dateparse装置dateparse库: $ go get -u github.com/araddon/dateparse应用: package mainimport ( "fmt" "log" "github.com/araddon/dateparse")func main() { t1, err := dateparse.ParseAny("3/1/2014") if err != nil { log.Fatal(err) } fmt.Println(t1.Format("2006-01-02 15:04:05")) t2, err := dateparse.ParseAny("mm/dd/yyyy") if err != nil { log.Fatal(err) } fmt.Println(t2.Format("2006-01-02 15:04:05"))}ParseAny()办法承受一个日期工夫字符串,解析该字符串,返回time.Time类型的值。如果传入的字符串dateparse库无奈辨认,则返回一个谬误。下面程序运行输入: $ go run main.go2014-03-01 00:00:002021/06/24 14:52:39 Could not find format for "mm/dd/yyyy"exit status 1须要留神,当咱们写出"3/1/2014"这个工夫的时候,能够解释为2014年3月1日,也能够解释为2014年1月3日。这就存在二义性,dateparse默认采纳mm/dd/yyyy这种格局,也就是2014年3月1日。咱们也能够应用ParseStrict()函数让这种具备二义性的字符串解析失败: func main() { t, err := dateparse.ParseStrict("3/1/2014") if err != nil { log.Fatal(err) } fmt.Println(t.Format("2006-01-02 15:04:05"))}运行: ...

June 25, 2021 · 3 min · jiezi

关于golang:GoPython双语言混合开发-盯紧技术先机-抓紧高薪机遇

download:Go+Python双语言混合开发 盯紧技术先机 放松高薪时机from django. db. models. signals import pre_save class ArticleCategory (models. Model ): name = models. CharField (max_length = 50 ) parent = models. ForeignKey ( 'self' , null = True , blank = True , related_name = 'children' ) path = models. CharField (max_length = 255 , null = True , blank = True ) def __unicode__ ( self ): if self. id == self. path: return self. name else: return self. node def _node ( self ): indent_num = len ( self. path. split ( ':' ) ) - 1 indent = '....' * indent_num node = u '%s%s' % (indent , self. name ) return node node = property (_node ) class Meta: ordering = [ 'path' ] #设置在model中的用处是,是在所有节点保留时递归的循环上来,更新所有的节点的门路 def save ( self , * args , ** kwargs ): super (ArticleCategory , self ). save (*args , ** kwargs ) if self. parent: self. path = '%s:%s' % ( self. parent. path , self. id ) else: self. path = self. id childrens = self. children. all ( ) if len (childrens ) > 0: for children in childrens: children. path = '%s:%s' % ( self. path , children. id ) children. save ( ) super (ArticleCategory , self ). save (*args , ** kwargs ) 信号触发,更新def inital_articlecategory_path (sender , instance , **kwargs ): ...

June 25, 2021 · 2 min · jiezi

关于golang:cgo的几种使用方式

最简略的CGO程序 //cgo.gopackage mainimport "C"func main(){ println("hello cgo")}上述代码是一个残缺的CGO程序,通过import "C"语句启动了CGO个性,go build命令会在编译和链接阶段启动gcc编译器 源码形式调用C函数 cgoTest.h void SayHello(const char* s);cgoTest.c #include <stdio.h>#include "cgoTest.h"void SayHello(const char* s) { puts(s);}main.go package main/*#include <cgoTest.h> */import "C"func main(){ C.SayHello(C.CString("Hello world\n"))}上述.c文件也能够是.cpp文件,前提是编译时须要g++ cgoTest.cpp #include <iostream>extern "C" { #include "cgo01.h"}void SayHello(const char* s) { std::cout << s;}上述.c和.cpp的不同实现都实现了SayHello函数,阐明解放了函数的实现者,那如果是这种状况,可不可以应用go实现SayHello函数呢? 答案是能够的,这种技术也称为面向C语言接口(.h中的接口申明)的编程技术,该技术不仅仅能够解放函数的实现者,同时也能够简化函数的使用者。 cgoTest.go package mainimport "C"import "fmt"//export SayHellofunc SayHello(s *C.char){ fmt.Print(C.GoString(s)) //留神:这里是C.GoString}留神:上述main.go文件在应用C函数CString后在程序退出前没有开释C.CString创立的字符串会导致内存透露,然而对于这个小程序来说,这样是没有问题的,因为程序推出后操作系统会主动回收程序的所有资源 改良后的main.go代码 package main/*#include <cgoTest.h>#include <stdlib.h> */import "C"import "unsafe"func main(){ cs := C.CString("CPP Hello world\n") C.SayHello(cs) C.free(unsafe.Pointer(cs))}当然也有其余办法能够防止这种麻烦的状况呈现,而且只须要一个go文件就能够实现面向C语言的编程 ...

June 24, 2021 · 1 min · jiezi