关于golang:GO使用sql-server

参考资料[1] Go语言中查问SqlServer数据库[2] gorm连贯到数据库 1. gorm的形式package daoimport ( "fmt" "gorm.io/driver/sqlserver" "gorm.io/gorm" "testing")type User struct { Userid int64 `gorm:"column:userid"` Name string `gorm:"column:name"` Age int64 `gorm:"column:age"`}func TestGorm(t *testing.T) { dsn := "sqlserver://sa:123456@DESKTOP-HMTA87I:1433?database=wzz" db, err := gorm.Open(sqlserver.Open(dsn), &gorm.Config{}) if err != nil { panic(err) } var users []User db.Table("dbo.users").Limit(10).Order("userid asc").Find(&users) for _, u := range users { fmt.Println(u) }}2.sql形式文件dao/connect_test.go内容: package daoimport ( "database/sql" "fmt" _ "github.com/denisenkom/go-mssqldb" "testing" "time")func TestConnect(t *testing.T) { var isdebug = true var server = "DESKTOP-HMTA87I" var port = 1433 var user = "sa" var password = "123456" var database = "wzz" //连贯字符串 connString := fmt.Sprintf("server=%s;port%d;database=%s;user id=%s;password=%s", server, port, database, user, password) if isdebug { fmt.Println(connString) } //建设连贯 conn, err := sql.Open("mssql", connString) if err != nil { t.Fatal("Open Connection failed:", err.Error()) } defer conn.Close() t.Log("连贯胜利!") //产生查问语句的Statement stmt, err := conn.Prepare(`select * from dbo.users`) if err != nil { t.Fatal("Prepare failed:", err.Error()) } rows, err := stmt.Query() if err != nil { t.Fatal("Query failed:", err.Error()) } defer stmt.Close() //建设一个列数组 cols, err := rows.Columns() var colsdata = make([]interface{}, len(cols)) for i := 0; i < len(cols); i++ { colsdata[i] = new(interface{}) fmt.Print(cols[i]) fmt.Print("\t") } fmt.Println() //遍历每一行 for rows.Next() { rows.Scan(colsdata...) //将查到的数据写入到这行中 PrintRow(colsdata) //打印此行 } defer rows.Close()}//打印一行记录,传入一个行的所有列信息func PrintRow(colsdata []interface{}) { for _, val := range colsdata { switch v := (*(val.(*interface{}))).(type) { case nil: fmt.Print("NULL") case bool: if v { fmt.Print("True") } else { fmt.Print("False") } case []byte: fmt.Print(string(v)) case time.Time: fmt.Print(v.Format("2016-01-02 15:05:05.999")) default: fmt.Print(v) } fmt.Print("\t") } fmt.Println()}3.应用实体实现的办法type AccessRegion struct { userid int64 name string age int64}func TestAccess(t *testing.T) { var server = "DESKTOP-HMTA87I" var port = 1433 var user = "sa" var password = "123456" var database = "wzz" //连贯字符串 connString := fmt.Sprintf("server=%s;port%d;database=%s;user id=%s;password=%s", server, port, database, user, password) //建设连贯 db, err := sql.Open("mssql", connString) if err != nil { t.Fatal("Open Connection failed:", err.Error()) } defer db.Close() //通过连贯对象执行查问 rows, err := db.Query(`select * from dbo.users`) if err != nil { t.Fatal("Query failed:", err.Error()) } defer rows.Close() var rowsData []*AccessRegion //遍历每一行 for rows.Next() { var row = new(AccessRegion) rows.Scan(&row.userid, &row.name, &row.age) rowsData = append(rowsData, row) } //打印数组 for _, ar := range rowsData { fmt.Print(ar.userid, "\t", ar.name, "\t", ar.age) fmt.Println() }}

May 26, 2021 · 2 min · jiezi

关于golang:通用连接池帮你解决资源管理难题

前言群里老有同学问,go-zero 的 数据库 和 redis 库是否有连接池反对。先说论断:有的,能够放心大胆用! 从框架设计来说,对于数据库连贯这种资源当然是尽可能减少频繁操作: 为业务减负晋升框架本身的性能池化技术是一个通用化技术,自身就应该作为一个通用库撑持框架的下层业务所以不论是 sqlx,redis,以及 mongo,等当前可能要反对的数据源类型,底层的池化解决都是通用的;所以当开发者须要一个池化解决组件时,go-zero 也是提供的。 池化技术支持的库就位于:core/resourcemanager.go。上面来看看这个库的应用~~ 应用应用的话,咱们间接来看 sqlx ,它是怎么用的: // 1. 初始化var connManager = syncx.NewResourceManager()func getCachedSqlConn(driverName, server string) (*db, error) { val, err := connManager.GetResource(server, func() (io.Closer, error) { // 2. 此处才是真正创立连贯的中央 conn, err := newDBConnection(driverName, server) ... // 3. 将连贯返回给连接池【外部也必定是存起来】 return &db{ DB: conn, }, nil }) ... return val.(*db), nil}说说其中的要点: NewResourceManager:创立一个池子GetResource(key, createFunc):key是用来避免并发获取资源时反复申请,createFunc 才是正在用来创立资源的函数【此函数须要有开发者本人编写合乎业务需要资源】总结一下资源池的模型: // 1. newvar manager = NewResourceManager()// 2. 业务资源获取函数func getResource(key string) (*resource, error) { return manager.GetResource(key, createFunc);}// 3.业务资源创立函数【由开发者本人编写,此处只是一个样例】func createFunc() (io.Closer, error) { // 关上一个资源 conn, err := openResource(); // 设置一下资源配置 conn.setConfig() return conn, err;}整体剖析 ...

May 26, 2021 · 1 min · jiezi

关于golang:gowebsocket-分布式IM

基于golang实现的分布式聊天零碎,反对一对一聊天,聊天室等性能。为了测试不便发送音讯数据暂未存入数据库,前期会退出数据库,也可自行退出数据库,不便永恒存储聊天内容,以及反对音讯必达等性能。 依赖包github.com/go-redis/redisgithub.com/gin-gonic/gingithub.com/gorilla/websocketgithub.com/smallnest/rpcx包阐明: redis :用于缓存ws服务器信息,用心跳模式保护ws服务器信息。 gin:实现web服务 websocket: 实现websocket协定 rpcx:服务器建rpc通信 架构图 一对一发消息客户端 发送建设长连贯申请,通过nginx负载平衡调配给其中一台ws服务器(这里假如调配的是A服务器)解决。A服务器响应长连贯申请,并缓存客户端地址和用户连贯,用户id等信息。客户端收到服务端响应建设websocet连贯。客户端发送信息,nginx负载平衡调配给其中一台ws服务器(这里假如是B服务器)。B服务器从发送的信息中解析接管用户(假如为a)信息,先验证a用户是否和B服务器建设websocet连贯,若建设则间接发送音讯给a用户。否则通过redis缓存中获取ws服务器信息列表,通过rpc形式发送音讯到ws服务器列表中除B服务器之外的每台ws服务器,这些接管到发送信息的ws服务器,先验证和a用户是否建设连贯,建设则发送信息给a用户,否则抛弃。群发音讯客户端 发送建设长连贯申请,通过nginx负载平衡调配给其中一台ws服务器(这里假如调配的是A服务器)解决。A服务器响应长连贯申请,并缓存客户端地址和用户连贯,用户id等信息。客户端收到服务端响应建设websocet连贯。客户端发送信息,nginx负载平衡调配给其中一台ws服务器(这里假如是B服务器)。B服务器从发送的信息中解析出群信息,依据群信息获取用户列表,遍历用户发送信息(发送形式跟一对一相似)。先验证用户是否和B服务器建设websocet连贯,若建设则间接发送音讯给用户。否则通过redis缓存中获取ws服务器信息列表,通过rpc形式发送音讯到ws服务器列表中除B服务器之外的每台ws服务器,这些接管到发送信息的ws服务器,先验证和用户是否建设连贯,建设则发送信息给用户,否则抛弃。疾速搭建1、拉取代码git clone https://github.com/guyan0319/go-websocket.git注:这里代码版本控制应用go modules 2、运行零碎go run main.go3、配置nginxupstream go-http{ server 127.0.0.1:8282 weight=1 max_fails=2 fail_timeout=10s; keepalive 16;}upstream go-ws{ server 127.0.0.1:8089 weight=1 max_fails=2 fail_timeout=10s; keepalive 16;}server { listen 80; server_name ws.test; root "D:/phpstudy/WWW/"; location /ws { proxy_set_header Host $host; proxy_pass http://go-ws; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; proxy_set_header Connection ""; proxy_redirect off; proxy_intercept_errors on; client_max_body_size 10m; } location / { proxy_set_header X-Forwarded-For $remote_addr; proxy_set_header Host $host; proxy_pass http://go-http; }}4、测试一对一聊天浏览器关上两个窗口拜访 ...

May 25, 2021 · 1 min · jiezi

关于golang:Golang-入门常见初级坑备忘录

置信这些问题,大家在学习之时必定是晓得的,只不过在编程时写着写着不知觉地就给写歪了。 要害是什么呢?要害是有些坑还不容易发现,导致排查一个非常简单和高级的问题都有可能破费一天、半天的大量工夫。这就十分让人恶心的了。 特地地,对于初学者来说,平时大家多留神留神还是能够防止踩坑的。 一、简短变量申明与初始化 :=1、只能用在函数内2、必须至多要申明一个新的变量 // 谬误示例一num := 1num := 2 // 编译谬误:no new variables// 谬误示例二f, err := os.Open(infile)f, err := os.Create(outfile) // 编译谬误:no new variables// 正确示例f, err := os.Open(infile)f2, err := os.Create(outfile)二、常量1、常量的数据类型只能是布尔型、数字型(整数型、浮点型、复数)、字符串2、常量表达式的值在编译期计算,而不是在运行期。所以常量的值必须是在编译时就可能确定的,自定义函数均属于未知,但内置函数是能够应用的。

May 24, 2021 · 1 min · jiezi

关于golang:GO遇到的坑

1.不能传0值 type reqPostProcess struct { ProjectID int64 `json:"project_id" binding:"required,gte=1" example:"10"` JobID int64 `json:"job_id" binding:"required,gte=1" example:"10"` ShowType int `json:"show_type" binding:"required" example:"3"` CurScalarIndex int `json:"cur_scalarIndex" binding:"required" example:"0"` ContourEnable bool `json:"contour_enable" binding:"required" example:"false"`}报错: [Key: 'reqPostProcess.CurScalarIndex' Error:Field validation for 'CurScalarIndex' failed on the 'required' tag]正确的写法 type reqPostProcess struct { ProjectID int64 `json:"project_id" binding:"required,gte=1" example:"10"` JobID int64 `json:"job_id" binding:"required,gte=1" example:"10"` ShowType *int `json:"show_type" binding:"required" example:"3"` CurScalarIndex *int `json:"cur_scalarIndex" binding:"required" example:"0"` ContourEnable bool `json:"contour_enable" binding:"required" example:"false"`}

May 24, 2021 · 1 min · jiezi

关于golang:GOprometheus

参考资料[1] prometheus包的应用[2] Prometheus监控装置及应用[3] Prometheus Client教程[4] 应用 Prometheus 对 Go 应用程序进行监测[5] 带你读《Prometheus监控实战》[6] Prometheushttps://eddycjy.com/posts/pro...https://yunlzheng.gitbook.io/... 1. 简介prometheus包提供了用于实现监控代码的metric原型和用于注册metric的registry。子包(promhttp)容许通过HTTP来裸露注册的metric或将注册的metric推送到Pushgateway。 Metricsprometheus一共有5种metric类型,前四种为:Counter,Gauge,Summary 和Histogram,每种类型都有对应的vector版本:GaugeVec, CounterVec, SummaryVec, HistogramVec,vector版本细化了prometheus数据模型,减少了label维度。第5种metric为Untyped,它的运作形式相似Gauge,区别在于它只向prometheus服务器发送类型信号。 只有根底metric类型实现了Metric接口,metric和它们的vector版本都实现了collector接口。collector负责一系列metrics的采集,然而为了不便,metric也能够“收集本人”。留神:Gauge, Counter, Summary, Histogram, 和Untyped本身就是接口,而GaugeVec, CounterVec, SummaryVec, HistogramVec, 和UntypedVec则不是接口。 为了创立metric和它们的vector版本,须要抉择适合的opts构造体,如GaugeOpts, CounterOpts, SummaryOpts, HistogramOpts, 或UntypedOpts.Custom Collectors and constant Metrics 实现本人的metric,个别只须要实现本人的collector即可。如果曾经有了现成的metric(prometheus上下文之外创立的),则无需应用Metric类型接口,只须要在采集期间将现有的metric映射到prometheus metric即可,此时能够应用 NewConstMetric, NewConstHistogram, and NewConstSummary (以及对应的Must… 版本)来创立metric实例,以上操作在collect办法中实现。describe办法用于返回独立的Desc实例,NewDesc用于创立这些metric实例。(NewDesc用于创立prometheus辨认的metric) 如果只须要调用一个函数来收集一个float值作为metric,那么举荐应用GaugeFunc, CounterFunc, 或UntypedFunc。Advanced Uses of the RegistryMustRegister 是注册collector最通用的形式。如果须要捕捉注册时产生的谬误,能够应用Register 函数,该函数会返回谬误。如果注册的collector与曾经注册的metric不兼容或不统一时就会返回谬误。registry用于使收集的metric与prometheus数据模型保持一致。不统一的谬误会在注册时而非采集时检测到。前者会在零碎的启动时检测到,而后者只会在采集时产生(可能不会在首次采集时产生),这也是为什么collector和metric必须向Registry describe它们的起因。以上提到的registry都被称为默认registry,能够在全局变量DefaultRegisterer中找到。应用NewRegistry能够创立custom registry,或者能够本人实现Registerer 或Gatherer接口。custom registry的Register和Unregister运作形式相似,默认registry则应用全局函数Register和Unregister。custom registry的应用形式还有很多:能够应用NewPedanticRegistry来注册非凡的属性;能够防止由DefaultRegisterer限度的全局状态属性;也能够同时应用多个registry来裸露不同的metrics。DefaultRegisterer注册了Go runtime metrics (通过NewGoCollector)和用于process metrics 的collector(通过NewProcessCollector)。通过custom registry能够本人决定注册的collector。HTTP ExpositionRegistry实现了Gather接口。调用Gather接口能够通过某种形式裸露采集的metric。通常metric endpoint应用http来裸露metric。通过http裸露metric的工具为promhttp子包。 函数和类型阐明: func Register(c Collector) error:应用DefaultRegisterer来注册传入的Collectorfunc Unregister(c Collector) bool:应用DefaultRegisterer来移除传入的Collector的注册信息type AlreadyRegisteredError:该类型实现了error接口,由Register返回,用于判断用于注册的collector是否曾经被注册过type Collector:用于采集prometheus metric,如果运行多个雷同的实例,则须要应用ConstLabels来注册这些实例。实现collector接口须要实现Describe和Collect办法,并注册collector。type Registerer:负责collector的注册和去注册,实现custom registrer时应该实现该接口// MustRegister implements Registerer.func (r *Registry) MustRegister(cs ...Collector) { for _, c := range cs { if err := r.Register(c); err != nil { panic(err) } }}2. 示例https://blog.csdn.net/runner6...https://www.cnblogs.com/FG123... ...

May 24, 2021 · 2 min · jiezi

关于golang:GOcasbin

参考资料[1] 例子[2] model语法[3] API 1. 简介权限实际上就是管制谁能对什么资源进行什么操作。casbin将访问控制模型形象到一个基于 PERM(Policy,Effect,Request,Matchers) 元模型的配置文件(模型文件)中。因而切换或更新受权机制只须要简略地批改配置文件。 policy是策略或者说是规定的定义。它定义了具体的规定。request是对拜访申请的形象,它与e.Enforce()函数的参数是一一对应的matcher匹配器会将申请与定义的每个policy一一匹配,生成多个匹配后果。effect依据对申请使用匹配器得出的所有后果进行汇总,来决定该申请是容许还是回绝。 Request定义[request_definition]局部用于request的定义,它明确了e.Enforce(...)函数中参数的含意。 [request_definition]r = sub, obj, actsub, obj, act 示意经典三元组: 拜访实体 (Subject),拜访资源 (Object) 和拜访办法 (Action)。 然而, 你能够自定义你本人的申请表单, 如果不须要指定特定资源,则能够这样定义 sub、act ,或者如果有两个拜访实体, 则为 sub、sub2、obj、act。 Policy定义[policy_definition] 局部是对vpolicy`的定义,以下文的 model 配置为例: [policy_definition]p = sub, obj, actp2 = sub, act这些是咱们对policy规定的具体形容 p, alice, data1, readp2, bob, write-all-objectspolicy局部的每一行称之为一个策略规定, 每条策略规定通常以形如p, p2的policy type结尾。 如果存在多个policy定义,那么咱们会依据前文提到的policy type与具体的某条定义匹配。 下面的policy的绑定关系将会在matcher中应用, 列举如下: (alice, data1, read) -> (p.sub, p.obj, p.act)(bob, write-all-objects) -> (p2.sub, p2.act) func TestCasbin(t *testing.T) { m := model.NewModel() m.AddDef("r", "r", "sub, obj, act") m.AddDef("p", "p", "sub, obj, act") m.AddDef("g", "g", "_, _") m.AddDef("e", "e", "some(where (p.eft == allow))") m.AddDef("m", "m", "r.sub == g.sub && r.obj == p.obj && r.act == p.act") a := fileadapter.NewAdapter("./policy.csv") e, err := casbin.NewEnforcer(m, a) if err != nil { t.Logf("NewEnforecer failed:%v\n", err) } check(e, "dajun", "data1", "read") check(e, "lizi", "data2", "write") check(e, "dajun", "data1", "write") check(e, "dajun", "data2", "read") users := e.GetAllSubjects() //获取所有用户/获取以后策略中显示的主题列表 t.Log("users=", users) allNamedSubjects := e.GetAllNamedSubjects("p") // 获取以后命名策略中显示的主题列表 t.Log("allNamedSubjects=", allNamedSubjects) allObjects := e.GetAllObjects() //获取以后命名策略中显示的对象列表。 t.Log("allObjects=", allObjects) allNamedObjects := e.GetAllNamedObjects("p") //获取以后命名策略中显示的对象列表 t.Log("allNamedObjects=", allNamedObjects) allActions := e.GetAllActions() t.Log("allActions=", allActions) allNamedActions := e.GetAllNamedActions("p") t.Log("allNamedActions=", allNamedActions) roles := e.GetAllRoles() //获取所有角色 t.Log("roles=", roles) allNamedRoles := e.GetAllNamedRoles("g") t.Log("allNamedRoles=", allNamedRoles) policy := e.GetPolicy() // 获取策略中的所有受权规定。 t.Log("policy =", policy) filteredPolicy := e.GetFilteredPolicy(0, "dajun") //获取策略中的所有受权规定,能够指定字段筛选器 t.Log("filteredPolicy =", filteredPolicy) namedPolicy := e.GetNamedPolicy("p") //获取命名策略中的所有受权规定 t.Log("namedPolicy =", namedPolicy) filteredNamedPolicy := e.GetFilteredNamedPolicy("p", 0, "bob") //获取命名策略中的所有受权规定,能够指定字段过滤器 t.Log("filteredNamedPolicy =", filteredNamedPolicy) hasPolicy := e.HasPolicy("data2_admin", "data2", "read") //确定是否存在受权规定 t.Log("hasPolicy =", hasPolicy) added, _ := e.AddPolicy("eve", "data3", "read") //向以后策略增加受权规定。 如果规定曾经存在,函数返回false,并且不会增加规定。 否则,函数通过增加新规定并返回true t.Log("added =", added) rules := [][]string{ []string{"jack", "data4", "read"}, []string{"katy", "data4", "write"}, []string{"leyo", "data4", "read"}, []string{"ham", "data4", "write"}, } areRulesAdded, _ := e.AddPolicies(rules) t.Log("areRulesAdded =", areRulesAdded) added, _ = e.AddNamedPolicy("p", "eve", "data3", "read") t.Log("added =", added) getRolesForUser, _ := e.GetRolesForUser("dajun") // 获取用户具备的角色。 t.Log("getRolesForUser =", getRolesForUser) getUsersForRole, _ := e.GetUsersForRole("data1") // 获取具备角色的用户 t.Log("getUsersForRole =", getUsersForRole) hasRoleForUser, _ := e.HasRoleForUser("alice", "data1_admin") t.Log("hasRoleForUser =", hasRoleFor![image.png](/img/bVcSebF)

May 24, 2021 · 2 min · jiezi

关于golang:GO常用快捷键

参考资料Goland/CLion罕用快捷键 罕用快捷键:CTRL + SHIFT + F,进行全局查找CTRL + ALT + L,格式化代码CTRL + ALT + H, 查看回调函数 我的项目相干的快捷键CTRL + E,关上最近浏览过的文件。CTRL + SHIFT + E,关上最近更改的文件。CTRL + N,能够疾速关上struct构造体。CTRL + SHIFT + N,能够疾速关上文件。 代码格式化:CTRL + ALT + T,能够把代码包在一个块内,例如if{…}else{…}。CTRL + ALT + L,格式化代码。CTRL + 空格,代码提醒。 CTRL + /,单行正文。CTRL + SHIFT + /,进行多行正文。CTRL + B,疾速关上光标处的构造体或办法(跳转到定义处)。CTRL + “+/-”,能够将以后办法进行开展或折叠。 查找和定位CTRL + R,替换文本。CTRL + F,查找文本。CTRL + SHIFT + F,进行全局查找。CTRL + G,疾速定位到某行。 代码编辑ALT + Q,能够看到以后办法的申明。CTRL + Backspace,按单词进行删除。SHIFT + ENTER,能够向下插入新行,即便光标在以后行的两头。CTRL + X,删除以后光标所在行。CTRL + D,复制以后光标所在行。ALT + SHIFT + UP/DOWN,能够将光标所在行的代码高低挪动。CTRL + SHIFT + U,能够将选中内容进行大小写转化。 ...

May 24, 2021 · 1 min · jiezi

关于golang:GOgomail

参考资料[1] 应用gomail发送邮件[2] Golang 应用gomail包发送邮件 在理论我的项目中,可能会遇到发送邮件的需要,所以就试着实现了一下。可能目前也还没有理论遇到此需要,不过也能够先入手实现一下,兴许此需要正在路上。 1、装置 gomail 包 go get -v gopkg.in/gomail.v22、残缺代码 package mainimport ( "fmt" "gopkg.in/gomail.v2" "mime")/*go邮件发送*/func SendMail(mailTo []string, subject string, body string) error { // 设置邮箱主体 mailConn := map[string]string{ "user": "xxx@qq.com", //发送人邮箱(邮箱以本人的为准) "pass": "xxx", //发送人邮箱的明码,当初可能会须要邮箱 开启受权明码后在pass填写受权码 "host": "smtp.qq.com", //邮箱服务器(此时用的是qq邮箱) } m := gomail.NewMessage( //发送文本时设置编码,避免乱码。 如果txt文本设置了之后还是乱码,那能够将原txt文本在保留时 //就抉择utf-8格局保留 gomail.SetEncoding(gomail.Base64), ) m.SetHeader("From", m.FormatAddress(mailConn["user"], "LLL")) // 增加别名 m.SetHeader("To", mailTo...) // 发送给用户(能够多个) m.SetHeader("Subject", subject) // 设置邮件主题 m.SetBody("text/html", body) // 设置邮件注释 //一个文件(退出发送一个 txt 文件):/tmp/foo.txt,我须要将这个文件以邮件附件的形式进行发送,同时指定附件名为:附件.txt //同时解决了文件名乱码问题 name := "附件.txt" m.Attach("E:/GoCode/src/goMail/gomail.txt", gomail.Rename(name), //重命名 gomail.SetHeader(map[string][]string{ "Content-Disposition": []string{ fmt.Sprintf(`attachment; filename="%s"`, mime.QEncoding.Encode("UTF-8", name)), }, }), ) /* 创立SMTP客户端,连贯到近程的邮件服务器,须要指定服务器地址、端口号、用户名、明码,如果端口号为465的话, 主动开启SSL,这个时候须要指定TLSConfig */ d := gomail.NewDialer(mailConn["host"], 465, mailConn["user"], mailConn["pass"]) // 设置邮件注释 //d.TLSConfig = &tls.Config{InsecureSkipVerify: true} err := d.DialAndSend(m) return err}func main() { // 邮件接管方 mailTo := []string{ //能够是多个接管人 "xxx@163.com", "xxx@qq.com", } subject := "Hello World!" // 邮件主题 body := "测试发送邮件" // 邮件注释 err := SendMail(mailTo, subject, body) if err != nil { fmt.Println("Send fail! - ", err) return } fmt.Println("Send successfully!")}

May 24, 2021 · 1 min · jiezi

关于golang:GOGin

参考资料[1] gin根本应用 [2] gin牛逼的context[3] Gin框架介绍及应用[4] 学习笔记 1. 简介Gin 是 Go语言写的一个 web 框架,它具备运行速度快,分组的路由器,良好的解体捕捉和错误处理,十分好的反对中间件和 json。总之在 Go语言开发畛域是一款值得好好钻研的 Web 框架,开源网址:https://github.com/gin-gonic/gin 在 Go语言开发的 Web 框架中,有两款驰名 Web 框架别离是 Martini 和 Gin,两款 Web 框架相比拟的话,Gin 本人说它比 Martini 要强很多。 gin.Default() 创立路由gin.DisableConsoleColor()禁用控制台色彩gin.SetMode()设置gin模式。参数能够传递:gin.DebugMode、gin.ReleaseMode、gin.TestMode。路由的办法为:假设咱们先创立一个路由 router := gin.Default()获取的形式能够为: router.GET("/someGet", getting)router.POST("/somePost", posting)router.PUT("/somePut", putting)router.DELETE("/someDelete", deleting)router.PATCH("/somePatch", patching)router.HEAD("/someHead", head)router.OPTIONS("/someOptions", options)router.Group(s string)分组,s为组名一、第一个gin程序官网给的一个示例程序 package mainimport "github.com/gin-gonic/gin"func main() { r := gin.Default() r.GET("/ping", func(c *gin.Context) { c.JSON(200, gin.H{ "message": "pong", }) }) r.Run(":8080") // listen and serve on 0.0.0.0:8080}运行程序,在浏览器上拜访:http://127.0.0.1:8080/pingc.String用来返回字符串。c.JSON用来返回Json。 ...

May 24, 2021 · 4 min · jiezi

关于golang:GOweb框架

参考资料[1] Golang(五)最佳Web框架比照 Golang是一门快速增长的语言,专为构建简略、疾速且牢靠的软件而设计。它提供的net/http库对于HTTP协定实现十分好,基于此再结构框架会更容易,因而生态中呈现了很多框架。本文将从风行度、社区反对及内建性能等角度对六款出名Go语言Web框架做比照 六款Web框架[1] Beego 面向Go编程语言的开源高性能web框架https://github.com/astaxie/beegohttps://beego.mebeego是一个疾速开发Go利用的http框架,go 语言方面技术大牛。beego能够用来疾速开发API、Web、后端服务等各种利用,是一个RESTFul的框架,次要设计灵感来源于tornado、sinatra、flask这三个框架,然而联合了Go自身的一些个性(interface、struct继承等)而设计的一个框架。 [2] Buffalo应用Go语言疾速构建Web利用https://github.com/gobuffalo/...https://gobuffalo.ioBuffalo能帮忙你生成一个web我的项目,曾经从前端(JavaScript, SCSS等)后端(数据库、路由等)曾经连贯并筹备运行。 从那里它提供了简略的api来疾速构建web应用程序中去。水牛不只是一个框架; 这是一个全面的web开发环境和我的项目构造让开发人员理解业务,建设他们的业务。 [3] Echo 高性能、极简Go语言Web框架https://github.com/labstack/echohttps://echo.labstack.comEcho是为用Java开发Web应用程序提供的一个面向对象,事件驱动的框架。应用Echo框架来编程相似于应用Swing APIv来开发应用程序或vapplets。 [4] Gin Go语言编写的HTTP Web框架它以更好的性能实现了相似Martini的API,性能更好https://github.com/gin-gonic/ginhttps://gin-gonic.github.io/ginGin是一个vgolang的微框架,封装比拟优雅,API`敌对,源码正文比拟明确,曾经公布了1.0版本。具备疾速灵便,容错不便等特点。 [5] Iris 全宇宙最快的Go语言Web框架,齐备MVC反对,拥抱将来https://github.com/kataras/irishttps://iris-go.comIris是一个疾速,简略但功能齐全的和十分无效的web框架。提供了一个柔美的表现力和容易应用你的下一个网站或API的根底。 [6] Revel Go语言的高效、全栈Web框架https://github.com/revel/revelhttps://revel.github.ioRevel:Go 语言的高效、全栈 Web 框架。高生产率,残缺的web框架去语言。 几个出名的Go语言Web框架(Echo、Gin和Buffalo)因为没有齐备反对所有性能,并不能算是真正意义上的Web框架,但大部分go社区认为它们是的,因而,有必要将这几个框架也列在表格中能够和Iris、Beego、Revel做比拟。以上这些框架,除了Beego和Revel之外,都能够适配任意net/http中间件,其中一部分框架能够轻松地做适配,另外一些可能就须要额定的致力。以上所有这些框架,除了Beego和Revel之外,都能够适配任意 net/http中间件。其中一部分框架能够轻松地做适配,另外一些可能就须要额定的致力 [即便这里的苦楚不是肯定的]。

May 24, 2021 · 1 min · jiezi

关于golang:GOxorm

参考资料http://books.studygolang.com/...https://www.cnblogs.com/MyUni...https://www.cnblogs.com/qfDav...https://www.kancloud.cn/kancl... 1. 简介xorm是一个简略而弱小的Go语言ORM库. 通过它能够使数据库操作十分简便。xorm的指标并不是让你齐全不去学习SQL,咱们认为SQL并不会为ORM所代替,然而ORM将能够解决绝大部分的简略SQL需要。xorm反对两种格调的混用。 个性 反对 Struct 和数据库表之间的灵便映射,并反对主动同步事务反对同时反对原始SQL语句和ORM操作的混合执行应用连写来简化调用反对应用ID, In, Where, Limit, join, Having, Table, SQL, Cols等函数和构造体等形式作为条件反对级联加载 Struct- Schema反对(仅Postgres)反对缓存通过 xorm.io/reverse 反对依据数据库主动生成 xorm 构造体反对记录版本(即乐观锁)通过 xorm.io/builder 内置 SQL Builder 反对上下文缓存反对反对日志上下文驱动反对xorm 以后反对的驱动和数据库如下: Mysql5 / Mysql8.* / Mariadb / Tidbgithub.com/go-sql-driver/mysqlgithub.com/ziutek/mymysql/godrvPostgres / Cockroachgithub.com/lib/pqSQLitegithub.com/mattn/go-sqlite3MsSqlgithub.com/denisenkom/go-mssqldbOraclegithub.com/mattn/go-oci8 (试验性反对)装置 go get xorm.io/xorm2.查问2.1 Cols特定字段查问 Cols办法能够承受一个或者多个特点的表字段名称,用来示意限定于操作特定的表字段。仍然通过案例来阐明: engine.Cols("user_name","status").Find(&admins) //select user_name, status from admin上述Cols操作示意的sql语句就是正文所对应的sql语句,示意从admin表中,查问特定的user_name, status两个字段,并将查问后的汇合进行返回。 engine.Cols("user_name","status").Update(&admin) //update admin set user_name = admin.User_name and status = admin.Status咱们能够看到,除了Find办法外,还能够调用Update办法,这里即示意更新表构造中的某条数据,且仅仅对该条数据的user_name和status两个字段进行更新,这正是由Cols办法的参数限定的。AllCols操作所有字段 除了上述的Cols指定一个或者多个字段以外,还能够通过AllCols办法来操作表所有字段,用法与Cols应用办法统一,咱们不再赘述。MustCols操作限定字段MustCols意为操作必须对某些字段起作用,该办法的应用和Update办法相结合的状况较多。 2.2 Get办法查问单条数据应用Get办法,在调用Get办法时须要传入一个对应构造体的指针,同时构造体中的非空field主动成为查问的条件和后面的办法条件组合在一起查问。 2.3 Find办法查问多条数据应用Find办法,Find办法的第一个参数为slice的指针或Map指针,即为查问后返回的后果,第二个参数可选,为查问的条件struct的指针。 2.4 Count办法统计数据应用Count办法,Count办法的参数为struct的指针并且成为查问条件。 ...

May 24, 2021 · 1 min · jiezi

关于golang:GOgorm

参考资料[1] 连贯到数据库[2] 官网文档 1. 简介gorm是应用的orm映射,所以须要定义要操作的表的model,在go中须要定义一个struct, struct的名字就是对应数据库中的表名,留神gorm查找struct名对应数据库中的表名的时候会默认把你的struct中的大写字母转换为小写并加上“s”,所以能够加上 db.SingularTable(true) 让gorm本义struct名字的时候不必加上“s”。 golang中,首字母大小写来示意public或者private,因而构造体中字段首字母必须大写。 定义model,即struct时,咱们能够只定义咱们须要从数据库中取回的特定字段:gorm在本义表名的时候会把struct的大写字母(首字母除外) 替换成“_”,所以上面的”GoSystemInfo”会本义成数据库中对应的“go_system_info”的表名, 对应的字段名的查找会先依照tag外面的名称去外面查找,如果没有定义标签则依照struct定义的字段查找,查找的时候struct字段中的大写会被本义成“_”,如:“SystemId”会去查找表中的system_id字段。 1. join查问https://blog.csdn.net/f95_slj... type JobCKDao struct{} func (dao *JobCKDao) MonitorQuery(ctx context.Context, gSession *gorm.DB) []*JobData { var db *gorm.DB var jobDatas []*JobData db = gSession.Table("job").Order("job.ysid DESC") //.Where("job.state in (?)", []int{0, 1, 3}) // 只查问状态为0, 1, 3的job db.Select("job.ysid as job_id,job.project_id as project_id,user.name as user_name,job.name as job_name,job.state as status,project.maxproc as maxproc,job.created_at as create_time,job.end_time as end_time"). Joins("join user on user.ysid = job.user_id").Joins("join project on job.project_id = project.ysid").Limit(5).Scan(&jobDatas) return jobDatas}2. 关联查问https://www.jianshu.com/p/b2d... ...

May 24, 2021 · 2 min · jiezi

关于golang:GOinit函数

参考资料五分钟了解golang的init函数 1. golang程序初始化golang程序初始化先于main函数执行,由runtime进行初始化,初始化程序如下: 初始化导入的包(包的初始化程序并不是按导入程序(“从上到下”)执行的,runtime须要解析包依赖关系,没有依赖的包最先初始化,与变量初始化依赖关系相似,参见golang变量的初始化);初始化包作用域的变量(该作用域的变量的初始化也并非依照“从上到下、从左到右”的程序,runtime解析变量依赖关系,没有依赖的变量最先初始化,参见golang变量的初始化);执行包的init函数;2. 特点go语言中init函数用于包(package)的初始化,该函数是go语言的一个重要个性。 init函数是用于程序执行前做包的初始化的函数;每个包能够领有多个init函数;包的每个源文件也能够领有多个init函数;同一个包中多个init函数的执行程序go语言没有明确的定义;不同包的init函数依照包导入的依赖关系决定该初始化函数的执行程序;init函数不能被其余函数调用,而是在main函数执行之前,主动被调用。main包中,能够有init函数程序编译时,先执行导入包的init函数,再执行本包内的init函数3. 示例 main.gopackage main import ( "fmt" )var T int64 = a()func init() { fmt.Println("init in main.go ")}func a() int64 { fmt.Println("calling a()") return 2}func main() { fmt.Println("calling main") }输入: calling a()init in main.gocalling main初始化程序:变量初始化->init()->main()

May 24, 2021 · 1 min · jiezi

关于golang:GOgrpc

参考资料https://www.jianshu.com/p/9ea...https://www.cnblogs.com/baosh...http://doc.oschina.net/grpc?t...https://pkg.go.dev/google.gol...https://www.jianshu.com/p/b72...https://grpc.io/docs/language...https://www.cnblogs.com/ExMan...https://blog.csdn.net/u011518...https://grpc.io/docs/language...https://www.cnblogs.com/aweso...https://blog.csdn.net/zhangmi...https://blog.csdn.net/xp17817... 1.概念RPC(remote procedure call近程过程调用),实际上是提供了一套框架机制,使得位于网络中的不同机器上的应用程序之间能够进行通信互相调用,而且也听从server/client模型。应用的时候客户端调用server端提供的接口就像是调用本地的函数一样。通常RPC都是通过反射机制来实现的,本文不做深入分析,待后续文章再深入分析RPC的实现原理。 与其余的RPC框架相似,gRPC在服务端提供一个gRPC Server,客户端的库是gRPC Stub。典型的场景是客户端发送申请,调用服务端的接口,客户端和服务端之间的通信协议是基于HTTP2的,反对双工的流式保序音讯,性能比拟好,同时也很轻量级。 2.长处既然是vserver/client模型,那么咱们间接用restful api不是也能够的吗,为什么还须要RPC(或者gRPC)呢?上面咱们就来看看gRPC绝对于Restful API到底有哪些劣势?gRPC和restful API都提供了一套通信机制,用于server/client模型通信,而且它们都应用http作为底层的传输协定。不过gRPC还是有些特有的劣势的,如下: gRPC能够通过protobuf来定义接口,从而能够有更加严格的接口约束条件;另外,通过protobuf能够将数据序列化为二进制编码,这会缩小须要传输的数据量,从而进步性能;gRPC能够不便地反对流式通信(实践上通过http2.0就能够应用streaming模式);简略易学,疾速开始,可能反对多种语言和平台,双向流式通信、集成认证模块。3.应用场景须要对接口进行严格束缚的状况,咱们不心愿客户端给咱们传递任意的数据,尤其是思考到安全性的因素,咱们通常须要对接口进行更加严格的束缚。这时gRPC就能够通过protobuf来提供严格的接口束缚; 对于性能有更高的要求时。有时咱们的服务须要传递大量的数据,而又心愿不影响咱们的性能,这个时候也能够思考gRPC服务,因为通过protobuf咱们能够将数据压缩编码转化为二进制格局,通常传递的数据量要小得多,而且通过http2咱们能够实现异步的申请,从而大大提高了通信效率; 然而,通常咱们不会去独自应用gRPC,而是将gRPC作为一个部件进行应用,这是因为在生产环境,咱们面对大并发的状况下,须要应用分布式系统来去解决,而`gRPCv并没有提供分布式系统相干的一些必要组件。而且,真正的线上服务还须要提供包含负载平衡,限流熔断,监控报警,服务注册和发现等必要的组件; 接下来还得简略介绍一下Protobuf,因为gRPC应用vprotobuf来定义接口。Protobuf是什么?Protobuf理论是一套相似于Json或者XML的数据传输格局和标准,用于不同利用或过程之间进行通信时应用。通信时所传递的信息是通过Protobuf定义的message数据结构进行打包,而后编译成二进制的码流再进行传输或者存储。 4.Protobuf概念Protobuf理论是一套相似于Json或者XML的数据传输格局和标准,用于不同利用或过程之间进行通信时应用。通信时所传递的信息是通过Protobuf定义的message数据结构进行打包,而后编译成二进制的码流再进行传输或者存储。 5.Protobuf长处足够简略;序列化后体积很小,音讯大小只须要XML的1/10 ~ 1/3;解析速度快,解析速度比XML快20 ~ 100倍;多语言反对;更好的兼容性,Protobuf设计的一个准则就是要可能很好的反对向下或向上兼容;6.应用Protobuf步骤定义音讯;初始化音讯以及存储传输音讯;读取音讯并解析;Protobuf的音讯构造是通过一种叫做Protocol Buffer Language的语言进行定义和形容的,实际上Protocol Buffer Language分为两个版本,版本2和版本3,默认不申明的状况下应用的是版本2,目前举荐应用的是版本3。 采纳ProtoBuf作为IDL(Interface Definition Language接口定义语言),须要定义service和message,生成客户端和服务端代码。用户本人实现服务端代码中的调用接口,并且利用客户端代码来发动申请到服务端。service代表RPC接口,message代表数据结构(外面能够包含不同类型的成员变量,包含字符串、数字、数组、字典等)。message中成员变量前面的数字代表进行二进制编码时候的提示信息,1~15示意热变量,会用较少的字节来编码。默认所有变量都是可选的(optional),repeated则示意数组。service rpc接口只能承受单个message 参数,返回单个message。 7.golang装置gRpcgit clone https://github.com/grpc/grpc-go.git $GOPATH/src/google.golang.org/grpcgit clone https://github.com/golang/net.git $GOPATH/src/golang.org/x/netgit clone https://github.com/golang/text.git $GOPATH/src/golang.org/x/textgo get -u github.com/golang/protobuf/{proto,protoc-gen-go}git clone https://github.com/google/go-genproto.git $GOPATH/src/google.golang.org/genproto cd $GOPATH/src/go install google.golang.org/grpcwindows装置: $ export GO111MODULE=on # Enable module mode$ go get google.golang.org/protobuf/cmd/protoc-gen-go google.golang.org/grpc/cmd/protoc-gen-go-grpc$ export PATH="$PATH:$(go env GOPATH)/bin"问题:【1】 go get github.com/golang/protobuf/protoc-gen-go的问题解决链接:https://blog.csdn.net/wwqcher... ...

May 24, 2021 · 2 min · jiezi

关于golang:GO环境变量

参考资料Go语言GOPATH详解(Go语言工作目录) 1.GOPATH$ go envGOARCH="amd64" # GOARCH 示意指标处理器架构GOBIN="" # GOBIN 示意编译器和链接器的装置地位GOEXE=""GOHOSTARCH="amd64"GOHOSTOS="linux"GOOS="linux" # GOOS 示意指标操作系统GOPATH="/home/davy/go" # GOPATH 示意当前工作目录GORACE=""GOROOT="/usr/local/go" # 示意 Go 开发包的装置目录GOTOOLDIR="/usr/local/go/pkg/tool/linux_amd64"GCCGO="gccgo"CC="gcc"GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0"CXX="g++"CGO_ENABLED="1"CGO_CFLAGS="-g -O2"CGO_CPPFLAGS=""CGO_CXXFLAGS="-g -O2"CGO_FFLAGS="-g -O2"CGO_LDFLAGS="-g -O2"PKG_CONFIG="pkg-config"2.GOROOTGOROOT就是go的装置门路,在~/.bash_profile中增加上面语句: GOROOT=/usr/local/goexport GOROOT当然, 要执行go命令和go工具, 就要配置go的可执行文件的门路,操作如下:在~/.bash_profile中配置如下: export $PATH:$GOROOT/bin如果是windows须要应用;符号宰割两个门路, mac和类unix都是用:符号宰割 GOPATH:go install/go get和 go的工具等会用到GOPATH环境变量. GOPATH是作为编译后二进制的寄存目的地和import包时的搜寻门路 (其实也是你的工作目录, 你能够在src下创立你本人的go源文件, 而后开始工作)。GOPATH之下次要蕴含三个目录: bin、pkg、srcbin目录次要寄存可执行文件; pkg目录寄存编译好的库文件, 次要是*.a文件; src目录下次要寄存go的源文件不要把GOPATH设置成go的装置门路,能够本人在用户目录上面创立一个目录, 如gopath操作如下:cd ~mkdir gopath在~/.bash_profile中增加如下语句: GOPATH=/Users/username/gopathGOPATH能够是一个目录列表, go get下载的第三方库, 个别都会下载到列表的第一个目录外面须要把GOPATH中的可执行目录也配置到环境变量中, 否则你自行下载的第三方go工具就无奈应用了, 操作如下:在~/bash_profile中配置, export $PATH:$GOPATH/bin创立一个go我的项目, 并且编译运行: $ mkdir goproject$ cd goproject$ touch hello.go在hello.go中输出: package mainimport "fmt"func main() { fmt.Println("Hello, GO !")}在我的项目根目录下执行go build命令来构建你的我的项目, 构建后会生成hello文件运行生成的文件./hello, terminal中输入: Hello, GO !当然你也能够间接运行命令go run hello.go来执行程序. ...

May 24, 2021 · 1 min · jiezi

关于golang:GOgo-bindata

go-bindata是目前程序pugo在用的嵌入动态资源的工具。它能够把动态文件嵌入到一个go文件中,并提供一些操作方法。 go-bindata -o=app/asset/asset.go -pkg=asset source/... theme/... doc/source/... doc/theme/... -o 输入文件到 app/asset/asset.go,包名 -pkg=asset,而后是须要打包的目录,三个点包含所有子目录。这样就能够把所有相干文件打包到asset.go 且结尾是package asset放弃和目录统一。 示例step1: 装置 go-bindata D:\study\myGin> go get -u github.com/jteeuwen/go-bindata/...step2: 写go文件 package mainimport ( "myGin/handler")//go:generate go-bindata -o=assets/tpl_gen.go -pkg=assets tpl/...//-o 输入文件到assets/tpl_gen.go,包名 -pkg=assets,而后是须要打包的目录,三个点包含所有子目录。这样就能够把所有相干文件打包到 asset.go 且结尾是 package asset 放弃和目录统一。func main() { handler.InitHandlers()}step3: 产生文件 D:\study\myGin> go generatestep4: 应用形式 func GetAllStaticFileNames() { names := assets.AssetNames() for _, name := range names { fmt.Println("name=", name) rBytes, _ := assets.Asset(name) fmt.Println("rBytes=", rBytes) }}参考资料Go内嵌动态资源go-bindata的装置及应用

May 24, 2021 · 1 min · jiezi

关于golang:GOgoswagger

1.装置装置 2.简略应用main.go package mainimport ( "github.com/gin-gonic/gin" "swagger_study/handler")func main(){ var server *gin.Engine server =gin.Default() handler.InitHandlers(server) server.Run()}handler/api.go package handlerimport ( "github.com/gin-gonic/gin" ginSwagger "github.com/swaggo/gin-swagger" "github.com/swaggo/gin-swagger/swaggerFiles" "swagger_study/handler/test" _ "swagger_study/docs")// @title SwaggerTest Server// @version 0.1// @description SwaggerTest Server// @in header// @license.name Apache 2.0// @host 127.0.0.1:8080// @BasePath /apifunc InitHandlers(server *gin.Engine) { server.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) //Test API appApi := server.Group("/api/test") { appApi.GET("/hi",test.HandleHi) }}handler/test/test.go // @title SwaggerTest Server// @version 0.1// @description SwaggerTest Server// @in header// @license.name Apache 2.0// @host 127.0.0.1:8080// @BasePath /apifunc InitHandlers(server *gin.Engine) { server.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) //Test API appApi := server.Group("/api/test") { appApi.GET("/hi",test.HandleHi) }}Makefile ...

May 24, 2021 · 2 min · jiezi

关于golang:GOgomod

go module 应用前置条件: GO111MODULE 设置为 on 在以后文件夹下初始化一个新的 module,创立 go.mod 文件; go mod init name拉取短少的模块,移除不必的模块 : go mod tidy将依赖复制到 vendor 下 : go mod vendor下载依赖 : go mod download测验依赖: go mod verify显示模块依赖图: go mod graph解释为什么须要依赖: go mod why编辑 go.mod 文件: go eidt查看命令列表: go mod查看命令帮忙文档: go help mod <command>GOPROXY https://goproxy.cnhttps://goproxy.io//go 1.13 及以上go env -w GOPROXY=https://goproxy.cn,direct参考资料go mod 应用go mod tidygo mod命令

May 24, 2021 · 1 min · jiezi

关于golang:Go程序性能分析工具和方法

作者:宁亮 一、罕用剖析命令和工具pprofgo tool [xxx]go testdelvego racegdb二、程序编译时的参数传递1、gcflags//可应用go tool compile --help查看可用参数及含意go build -gcflags="-m"   比方 -N 禁用编译优化,-l 禁止内联,-m 打印编译优化策略(包含逃逸状况和函数是否内联,以及变量调配在堆或栈),-S 是打印汇编。 如果只在编译特定包时须要传递参数,格局应恪守“包名=参数列表”,如 go build -gcflags='log=-N -l' main.go 2、ldflagsgo build 用 -ldflags 给 go 链接器传入参数,理论是给 go tool link 的参数,能够用 go tool link --help 查看可用的参数。 罕用 -X 来指定版本号等编译时才决定的参数值。例如代码中定义var buildVer string,而后在编译时用go build -ldflags "-X main.buildVer=1.0" 来赋值。留神 -X 只能给string类型变量赋值。 三、go build -x能够列出 go build 触发的所有命令,比方工具链、跨平台编译、传入内部编译器的 flags、链接器等,可应用 -x 来查看所有的触发。 四、竞争检测应用 go run -race main.go 或 go build -race main.go 来进行竞争检测。 ...

May 24, 2021 · 3 min · jiezi

关于golang:GOMap

参考资料Go 语言Map(汇合)

May 24, 2021 · 1 min · jiezi

关于golang:golang-使用-gRPC

RPCRPC(Remote Procedure Call: 近程过程调用)是一个计算机通信协议,该协定容许运行于一台计算机的程序调用另一个地址空间(通常为一个凋谢网络的一台计算机)的子程序,而程序员就像调用本地程序一样,无需额定地为这个交互作用编程(无需关注细节)。 gRPC(https://grpc.io/about)gRPC是一个古代的开源高性能近程过程调用(RPC)框架,能够在任何环境中运行。它能够高效地连贯数据中心中和跨数据中心的服务,并反对可插入的负载平衡、跟踪、健康检查和身份验证。它还实用于分布式计算,以连贯设施、挪动应用程序和浏览器到后端服务。 编写.proto文件,创立user.proto文件syntax = "proto3";option go_package = "./pb_protobuf/white_board;white_board";import "google/api/annotations.proto";package white_board;message ImCreateBoardRequest { int64 id = 1; int32 page_count = 2; string room_no = 3; string doc_id = 4; string url_background = 5; string page_pos = 6; string file_url = 7; string uuid = 8; string nickname = 9;}message ImBoardInfoResponse{ int64 id = 1; int32 page_count = 2; string doc_id = 3; string create_time = 4; string update_time = 5;}message ImGetBoardListByRoomNoResponse{ repeated ImBoardInfoResponse BoardList = 1;}message ImGetBoardByIdRequest{ int64 id = 1;}message ImGetBoardByRoomNoRequest{ string room_no = 1;}message ImBoardResponse{}service Board{ rpc CreateBoard(ImCreateBoardRequest) returns (ImBoardInfoResponse){} rpc DeleteBoardById(ImGetBoardByIdRequest) returns (ImBoardResponse){} rpc DeleteBoardByRoomNo(ImGetBoardByRoomNoRequest) returns (ImBoardResponse){} rpc GetBoardListByRoomNo(ImGetBoardByRoomNoRequest) returns(ImGetBoardListByRoomNoResponse){}}syntax = "proto3";其中第一行指定了咱们应用 protocol buffers的版本,这里应用proto3. ...

May 24, 2021 · 2 min · jiezi

关于golang:GOGMP模型

在Go中,线程是运行goroutine的实体,调度器的性能是把可运行的goroutine调配到工作线程上。 全局队列(Global Queue):寄存期待运行的G。P的本地队列:同全局队列相似,寄存的也是期待运行的G,存的数量无限,不超过256个。新建G'时,G'优先退出到P的本地队列,如果队列满了,则会把本地队列中一半的G挪动到全局队列。P列表:所有的P都在程序启动时创立,并保留在数组中,最多有GOMAXPROCS(可配置)个。M:线程想运行工作就得获取P,从P的本地队列获取G,P队列为空时,M也会尝试从全局队列拿一批G放到P的本地队列,或从其余P的本地队列偷一半放到本人P的本地队列。M运行G,G执行之后,M会从P获取下一个G,一直反复上来。Goroutine调度器和OS调度器是通过M联合起来的,每个M都代表了1个内核线程,OS调度器负责把内核线程调配到CPU的核上执行。无关P和M的个数问题1、P的数量由启动时环境变量$GOMAXPROCS或者是由runtime的办法GOMAXPROCS()决定。这意味着在程序执行的任意时刻都只有$GOMAXPROCS个goroutine在同时运行。 2、M的数量go语言自身的限度:go程序启动时,会设置M的最大数量,默认10000.然而内核很难反对这么多的线程数,所以这个限度能够疏忽。runtime/debug中的SetMaxThreads函数,设置M的最大数量一个M阻塞了,会创立新的M。M与P的数量没有相对关系,一个M阻塞,P就会去创立或者切换另一个M,所以,即便P的默认数量是1,也有可能会创立很多个M进去。 3、P和M何时会被创立 P何时创立:在确定了P的最大数量n后,运行时零碎会依据这个数量创立n个P。M何时创立:没有足够的M来关联P并运行其中的可运行的G。比方所有的M此时都阻塞住了,而P中还有很多就绪工作,就会去寻找闲暇的M,而没有闲暇的,就会去创立新的M。参考资料Golang的协程调度器原理及GMP设计思维

May 24, 2021 · 1 min · jiezi

关于golang:懂得取舍才是缓存设计的真谛

Previously前两篇文章(缓存稳定性 和 缓存正确性)跟大家探讨了缓存的『稳定性』和『正确性』,缓存常见问题还剩下『可观测性』和『标准落地&工具建设』 稳定性正确性可观测性标准落地和工具建设上周文章发完之后,很多同学对我留的问题进行了深刻的探讨,我置信通过深度的思考,会让你对缓存一致性的了解更加粗浅! 首先,各个 Go 群和 go-zero 群里有很多的探讨,然而大家也都没有找到十分称心的答案。 让咱们来一起剖析一下这个问题的几种可能解法: 利用分布式锁让每次的更新变成一个原子操作。这种办法最不可取,就相当于自废文治,放弃了高并发能力,去谋求强一致性,别忘了我之前文章强调过『这个系列文章只针对非谋求强一致性要求的高并发场景,金融领取等同学自行判断』,所以这种解法咱们首先放弃。把 A删除缓存 加上提早,比方过1秒再执行此操作。这样的害处是为了解决这种概率极低的状况,而让所有的更新在1秒内都只能获取旧数据。这种办法也不是很现实,咱们也不心愿应用。把 A删除缓存 这里改成设置一个非凡占位符,并让 B设置缓存 用 redis 的 setnx 指令,而后后续申请遇到这个非凡占位符时从新申请缓存。这个办法相当于在删除缓存时加了一种新的状态,咱们来看下图的状况 是不是又绕回来了,因为A申请在遇到占位符时必须强行设置缓存或者判断是不是内容为占位符。所以这也解决不了问题。 那咱们看看 go-zero 是怎么应答这种状况的,咱们抉择对这种状况不做解决,是不是很吃惊?那么咱们回到原点来剖析这种状况是怎么产生的: 对读申请的数据没有缓存(压根没加载到缓存或者缓存已生效),触发了DB读取此时来了一个对该数据的更新操作须要满足这样的程序:B申请读DB -> A申请写DB -> A申请删除缓存 -> B申请设置缓存咱们都晓得DB的写操作须要锁行记录,是个慢操作,而读操作不须要,所以此类情况绝对产生的概率比拟低。而且咱们有设置过期工夫,事实场景遇到此类情况概率极低,要真正解决这类问题,咱们就须要通过 2PC 或是 Paxos 协定保障一致性,我想这都不是大家想用的办法,太简单了! 做架构最难的我认为是懂得取舍(trade-off),寻找最佳收益的平衡点是十分考验综合能力的。当然,如果大家有什么好的想法,能够通过群或者公众号分割我,感激! 本文作为系列文章第三篇,次要跟大家探讨『缓存监控和代码自动化』 缓存可观测性后面两篇文章咱们解决了缓存的稳定性和数据一致性问题,此时咱们的零碎曾经充沛享受到了缓存带来的价值,解决了从零到一的问题,那么咱们接下来要思考的是如何进一步升高应用老本,判断哪些缓存带来了理论的业务价值,哪些能够去掉,从而升高服务器老本,哪些缓存我须要减少服务器资源,各个缓存的 qps 是多少,命中率多少,有没有须要进一步调优等。 上图是一个服务的缓存监控日志,能够看出这个缓存服务的每分钟有5057个申请,其中99.7%的申请都命中了缓存,只有13个落到DB了,DB都胜利返回了。从这个监控能够看到这个缓存服务把DB压力升高了三个数量级(90%命中是一个数量级,99%命中是两个数量级,99.7%差不多三个数量级了),能够看出这个缓存的收益是相当能够的。 但如果反过来,缓存命中率只有0.3%的话就没什么收益了,那么咱们就应该把这个缓存去掉,一是能够升高零碎复杂度(如非必要,勿增实体嘛),二是能够升高服务器老本。 如果这个服务的 qps 特地高(足以对DB造成较大压力),那么如果缓存命中率只有50%,就是说咱们升高了一半的压力,咱们应该依据业务状况思考减少过期工夫来减少缓存命中率。 如果这个服务的 qps 特地高(足以对缓存造成较大压力),缓存命中率也很高,那么咱们能够思考减少缓存可能承载的 qps 或者加上过程内缓存来升高缓存的压力。 所有这些都是基于缓存监控的,只有可观测了,咱们能力做进一步有针对性的调优和简化,我也始终强调『没有度量,就没有优化』。 如何让缓存被标准应用?理解 go-zero 设计思路或者看过我的分享视频的同学可能对我常常讲的『工具大于约定和文档』有印象。 对于缓存来说,知识点是十分繁多的,每个人写出的缓存代码肯定会格调迥异,而且所有知识点都写对是十分难的,就像我这种写了那么多年程序的老鸟来说,一次让我把所有知识点都写对,仍然是十分艰难的。那么 go-zero 是怎么解决这个问题的呢? 尽可能把形象进去的通用解决办法封装到框架里。这样整个缓存的管制流程就不须要大家来操心了,只有你调用正确的办法,就没有出错的可能性。把从建表 sql 到 CRUD + Cache 的代码都通过工具一键生成。防止了大家去依据表构造写一堆构造和管制逻辑。 ...

May 24, 2021 · 1 min · jiezi

关于golang:Golang-如何优雅地实现并发编排任务

文章继续更新,微信搜一搜「 吴亲强的深夜食堂 」业务场景在做工作开发的时候,你们肯定会碰到以下场景: 场景1:调用第三方接口的时候, 一个需要你须要调用不同的接口,做数据组装。场景2:一个利用首页可能依靠于很多服务。那就波及到在加载页面时须要同时申请多个服务的接口。这一步往往是由后端对立调用组装数据再返回给前端,也就是所谓的 BFF(Backend For Frontend) 层。 针对以上两种场景,假如在没有强依赖关系下,抉择串行调用,那么总耗时即: time=s1+s2+....sn依照当代秒入百万的有为青年,这么长时间早就把你祖宗十八代问候了一遍。 为了平凡的KPI,咱们往往会抉择并发地调用这些依赖接口。那么总耗时就是: time=max(s1,s2,s3.....,sn)当然开始堆业务的时候能够先串行化,等到下面的人焦急的时候,亮出绝招。 这样,年底 PPT 就能够加上浓厚的一笔流水账:为业务某个接口进步百分之XXX性能,间接产生XXX价值。 当然这所有的前提是,做老板不懂技术,做技术”懂”你。 言归正传,如果批改成并发调用,你可能会这么写, package mainimport ( "fmt" "sync" "time")func main() { var wg sync.WaitGroup wg.Add(2) var userInfo *User var productList []Product go func() { defer wg.Done() userInfo, _ = getUser() }() go func() { defer wg.Done() productList, _ = getProductList() }() wg.Wait() fmt.Printf("用户信息:%+v\n", userInfo) fmt.Printf("商品信息:%+v\n", productList)}/********用户服务**********/type User struct { Name string Age uint8}func getUser() (*User, error) { time.Sleep(500 * time.Millisecond) var u User u.Name = "wuqinqiang" u.Age = 18 return &u, nil}/********商品服务**********/type Product struct { Title string Price uint32}func getProductList() ([]Product, error) { time.Sleep(400 * time.Millisecond) var list []Product list = append(list, Product{ Title: "SHib", Price: 10, }) return list, nil}先不论其余问题。从实现上来说,须要多少服务,你会开多少个 G,利用 sync.WaitGroup 的个性,实现并发编排工作的成果。 ...

May 23, 2021 · 4 min · jiezi

关于golang:基于Gin-Web-框架封住自己的业务框架

基于Gin Web 框架封住本人的业务框架在应用Gin开发过程中遇到的问题繁琐的申请绑定、参数查看、异样解决、Error日志等,太多反复低效的代码,吞没了真正的业务逻辑基于Gin Web 框架封装须要反对的中央准则:尽量放弃和Gin 的API 统一 聚焦痛点问题主动申请绑定:框架主动绑定申请到具体构造,开发同学能够间接解决业务参数校验:抽离校验过程,框架主动调用,开发同学只须要定义具体校验内容错误处理计划:设计清晰的错误处理流程更多的中间件反对提供的其余性能优雅重启: 监听信号量func (h *hook) WithSignals(signals ...syscall.Signal) Hook { for _, s := range signals { signal.Notify(h.ctx, s) } return h}func (h *hook) Close(funcs ...func()) { select { case <-h.ctx: } signal.Stop(h.ctx) for _, f := range funcs { f() }}func (d *DefaultServerPlugin) AfterStop(ctx context.Context) error { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() err := d.engine.server.Shutdown(ctx) if err != nil { debugPrint("server shutdown error: %v", err.Error()) } fmt.Print("DefaultServerPlugin AfterStop\n") return nil}对立的错误处理,定义接口type Decode interface { DecodeErr(err error) (int, string)}func (d *DefaultResponses) Response(ctx *gin.Context, result interface{}, err error, DecodeImp Decode){ // 能够在这里实现谬误err解决 code, message := DecodeImp.DecodeErr(err) if out, ok := result.(Output);ok{ out.Output() // 在输入的时候本人做转换 } ctx.JSON(http.StatusOK, response{ Code: uint64(code), Message: message, Data: result, })}申请和响应##### 申请:提供 接口做参数校验和格局转化 ...

May 23, 2021 · 3 min · jiezi

关于golang:syncPool原理解析

介绍领有垃圾回收个性的语言里,gc产生时都会带来性能损耗,为了缩小gc影响,通常的做法是缩小小块对象内存频繁申请,让每次产生垃圾回收时scan和clean沉闷对象尽可能的少。sync.Pool能够帮忙在程序构建了对象池,提供对象可复用能力,自身是可伸缩且并发平安的。 次要构造体Pool对外导出两个办法: Get 和 Put,Get是用来从Pool中获取可用对象,如果可用对象为空,则会通过New预约义的func创立新对象。Put是将对象放入Pool中,提供下次获取。 Getfunc (p *Pool) Get() interface{} { if race.Enabled { race.Disable() } l, pid := p.pin() x := l.private l.private = nil if x == nil { // Try to pop the head of the local shard. We prefer // the head over the tail for temporal locality of // reuse. x, _ = l.shared.popHead() if x == nil { x = p.getSlow(pid) } } runtime_procUnpin() if race.Enabled { race.Enable() if x != nil { race.Acquire(poolRaceAddr(x)) } } if x == nil && p.New != nil { x = p.New() } return x}首先看下GET办法的逻辑(在看前须要对gmp调度模型有大抵理解) ...

May 23, 2021 · 5 min · jiezi

关于golang:GOFunctional-Options

在咱们编程中,咱们会经常性的须要对一个对象(或是业务实体)进行相干的配置。比方上面这个业务实体(留神,这仅只是一个示例): type Server struct { Addr string Port int Protocol string Timeout time.Duration MaxConns int TLS *tls.Config}在这个 Server 对象中,咱们能够看到:要有侦听的IP地址 Addr 和端口号 Port ,这两个配置选项是必填的(当然,IP地址和端口号都能够有默认值,当这里咱们用于举例认为是没有默认值,而且不能为空,须要必填的)。而后,还有协定 Protocol 、 Timeout 和MaxConns 字段,这几个字段是不能为空的,然而有默认值的,比方:协定是tcp, 超时30秒 和 最大链接数1024个。还有一个 TLS 这个是平安链接,须要配置相干的证书和私钥。这个是能够为空的。 要解决这个问题,最常见的形式是应用一个配置对象,如下所示: type Config struct { Protocol string Timeout time.Duration Maxconns int TLS *tls.Config}咱们把那些非必输的选项都移到一个构造体里,于是 Server 对象变成了: type Server struct { Addr string Port int Conf *Config}Functional Options首先,咱们先定义一个函数类型: type Option func(*Server)而后,咱们能够应用函数式的形式定义一组如下的函数: func Protocol(p string) Option { return func(s *Server) { s.Protocol = p }}func Timeout(timeout time.Duration) Option { return func(s *Server) { s.Timeout = timeout }}func MaxConns(maxconns int) Option { return func(s *Server) { s.MaxConns = maxconns }}func TLS(tls *tls.Config) Option { return func(s *Server) { s.TLS = tls }}下面这组代码传入一个参数,而后返回一个函数,返回的这个函数会设置本人的 Server 参数。例如: ...

May 23, 2021 · 2 min · jiezi

关于golang:GOchannel

1.channel的作用Channel 是 Go 语言中一个十分重要的类型,是 Go 里的第一对象。通过 channel,Go 实现了通过通信来实现内存共享, 实际上:(数据拷贝了一份,并通过 channel 传递,实质就是个队列)。Channel 是在多个goroutine 之间传递数据和同步的重要伎俩。应用原子函数、读写锁能够保障资源的共享拜访平安,但应用channel 更优雅。 channel 字面意义是 “通道”,相似于 Linux 中的管道。申明 channel 的语法如下: chan ch // 申明一个双向通道chan<- ch // 申明一个只能用于发送的通道<-chan ch // 申明一个只能用于接管的通道COPYch <- v // 发送值v到Channel ch中v := <-ch // 从Channel ch中接收数据,并将数据赋值给v// 就像 map 和 slice 数据类型一样, channel必须先创立再应用:ch := make(chan int)单向通道的申明,用 <- 来示意,它指明通道的方向。你只有明确,代码的书写程序是从左到右就马上能把握通道的方向是怎么的。 因为 channel 是一个援用类型,所以在它被初始化之前,它的值是 nil,channel 应用 make函数进行初始化。能够向它传递一个 int 值,代表 channel 缓冲区的大小(容量),结构进去的是一个缓冲型的 channel;不传或传 0 的,结构的就是一个非缓冲型的 channel。 两者有一些差异:非缓冲型 channel 无奈缓冲元素,对它的操作肯定程序是 “发送 -> 接管 -> 发送 -> 接管 -> ……”,如果想间断向一个非缓冲 chan 发送 2 个元素,并且没有接管的话,第一次肯定会被阻塞;对于缓冲型 channel 的操作,则要 “宽松” 一些,毕竟是带了 “缓冲” 光环。对 chan 的发送和接管操作都会在编译期间转换成为底层的发送接管函数。 ...

May 23, 2021 · 3 min · jiezi

关于golang:一个基于-wkhtmltox-实现的开箱即用的-http-服务帮助服务端快速生成-pdfimage

前情提要工作中有个我的项目须要将 html 转成 pdf,那时候用的是 dompdf/dompdf 。前面又来了个需要,须要将 html 转成 image。起初去找了下发现 wkhtmltopdf 既能够生成 pdf 又能够生成 html。立马叫苦不迭地折腾起来。 不可否认 wkhtmltopdf 和 wkhtmltoimage 可能不是一个最好的抉择,毕竟谷歌的 Puppeteer 都比它厉害得多。但它作为一个 cli 软件,能够疾速带咱们实现咱们想要的。 另外你可能会问为什么不必第三方包呢。因为工作中的我的项目部署在 docker 上,根底镜像用的是 alpine,这可能会给咱们后续操作带来不可预知的问题。再加上后续可能会有多个我的项目一起应用这项服务,所以目前是用 go 将其包装成一个 HTTP 服务,供所有我的项目拜访调用。 我的项目地址:OverNaive/Html2X,以下其实是 README 文档。 简短介绍Html2X 是一个基于 wkhtmltox 实现的开箱即用的 http 服务,帮忙服务端疾速生成 pdf/image。 我的项目目标1. 以 http 服务代替第三方包,与业务零碎解耦,可独立更新;2. 将 wkhtmltox 的装置封装于 Docker 内,可疾速更新版本;3. 间接拉取镜像即可疾速实现部署,真正的开箱即用。 如何应用请先自行装置好 Docker 1. 获取镜像本地构建镜像:docker build -t overnaive/html2x近程拉取镜像:docker pull overnaive/html2x2. 运行镜像应用命令:docker run --name html2x -p 8080:8888 -it -d overnaive/html2x,即可运行一个容器。 ...

May 23, 2021 · 1 min · jiezi

关于golang:GOreflect深度比较

反射是指在程序运行期对程序自身进行拜访和批改的能力。程序在编译时,变量被转换为内存地址,变量名不会被编译器写入到可执行局部。在运行程序时,程序无奈获取本身的信息。 反对反射的语言能够在程序编译期将变量的反射信息,如字段名称、类型信息、构造体信息等整合到可执行文件中,并给程序提供接口拜访反射信息,这样就能够在程序运行期获取类型的反射信息,并且有能力批改它们。 Go程序在运行期应用reflect包拜访程序的反射信息。 深度比拟:reflect.DeepEqual()当咱们简单一个对象时,这个对象能够是内建数据类型,数组,构造体,map……咱们在复制构造体的时候,当咱们须要比拟两个构造体中的数据是否雷同时,咱们须要应用深度比拟,而不是只是简略地做浅度比拟。这里须要应用到反射 reflect.DeepEqual() ,上面是几个示例 import ( "fmt" "reflect")func main() { v1 := data{} v2 := data{} fmt.Println("v1 == v2:",reflect.DeepEqual(v1,v2)) //prints: v1 == v2: true m1 := map[string]string{"one": "a","two": "b"} m2 := map[string]string{"two": "b", "one": "a"} fmt.Println("m1 == m2:",reflect.DeepEqual(m1, m2)) //prints: m1 == m2: true s1 := []int{1, 2, 3} s2 := []int{1, 2, 3} fmt.Println("s1 == s2:",reflect.DeepEqual(s1, s2)) //prints: s1 == s2: true}参考资料:Go语言反射

May 23, 2021 · 1 min · jiezi

关于golang:手摸手Go-单例模式与syncOnce

I leave uncultivated today, was precisely yesterday perishes tomorrow which person of the body implored。 单例模式作为一个较为常见的设计模式,他的定义也很简略,将类的实例化限度为一个单个实例。在Java的世界里,你可能须要从懒汉模式、双重查看锁模式、饿汉模式、动态外部类、枚举等形式中抉择一种手动撸一遍代码,然而他们操作起来很容易一不小心就会呈现bug。而在Go里,内建提供了保障操作只会被执行一次的sync.Once,操作起来及其简略。 根本应用在开发过程中须要单例模式的场景比拟常见,比方web开发过程中,不可避免的须要跟DB打交道,而DB管理器初始化通常须要保障有且仅产生一次。那么应用sync.Once实现起来就比较简单了。 `var manager *DBManager``var once sync.Once``func GetDBManager()*DBManager{` `once.DO(func(){` `manager = &DBManager{}` `manager.initDB(config)` `})` `return manager``}`能够看到仅仅须要once.DO(func(){...})即可, 开发者只须要关注本人的初始化程序即可,单例由sync.Once来保障,极大升高了开发者的心智累赘。 sync.Once源码剖析数据结构sync.Once构造也比较简单,只有一个uint32字段和一个互斥锁Mutex。 `// 一旦应用不容许被拷贝``type Once struct {` `// done示意以后的操作是否曾经被执行 0示意还没有 1示意曾经执行` `// done属性放在构造体的第一位,是因为它在hot path中应用` `// hot path在每个调用点会被内联。` `// 将done放在构造体首位,像amd64/386等架构上能够容许更多的压缩指令` `// 并且在其余架构上更少的指令去计算偏移量` `done uint32` `m    Mutex``}`sync.Once的外围原理,是利用sync.Mutex和atomic包的原子操作来实现。done示意是否胜利实现一次执行。存在两个状态: 0 示意以后sync.Once 的第一次DO操作尚未胜利1 示意以后sync.Once 的第一次DO操作曾经实现每次DO办法调用都会去查看done的值,如果为1则啥也不做;如果为0则进入doSlow流程,doSlow很奇妙的先应用sync.Mutex。这样如果并发场景,只有一个goroutine会抢到锁执行上来,其余goroutine则阻塞在锁上,这样的益处是如果拿到锁的那个goroutine失败,其余阻塞在锁上的goroutine就是预备队替补下来。确保sync.Once有且仅胜利执行一次的语义。 once flow graph 好了,接下来看源码 操作方法DoDo执行函数f当且仅当对应sync.Once实例第一次调用Do。换句话说,给定var once Once,如果once.Do(f)被调用了屡次,,只管f在每次调用的值均不同,但只有第一次调用会执行f。如果须要每个函数都执行,则须要新的sync.Once实例。 `// Do的作用次要是针对初始化且有且只能执行一次的场景。因为Do直到f返回才返回,``// 所以如果f内调用Do则会导致死锁``// 如果f执行过程中panic了 那么Do工作f曾经执行结束 将来再次调用不会再执行f``func (o *Once) Do(f func()) {` `if atomic.LoadUint32(&o.done) == 0 {//判断f是否被执行` `// 可能会存在并发 进入slow-path` `o.doSlow(f)` `}``}`正文里提到了一种不正确的Do的实现 `if atomic.CompareAndSwapUint32(&o.done, 0, 1) {` `f()``}`这种实现不正确的起因在于,无奈保障f()有且仅执行一次的语义。因为应用间接CAS来解决问题,如果同时有多个goroutine竞争执行Do那么是能保障有且仅有一个goroutine会失去执行机会,其余goroutine只能默默来到。 然而如果取得执行机会的goroutine执行失败了,那么当前f()就在也没有执行机会了。 那么咱们来看看官网的实现形式 `func (o *Once) doSlow(f func()) {` `o.m.Lock()` `defer o.m.Unlock()` `if o.done == 0 {//二次判断f是否曾经被执行` `defer atomic.StoreUint32(&o.done, 1)` `f()` `}``}`官网的做法就是如果多个goroutine都来竞争Do,那么先让一个goroutine拿到sync.Mutex的锁,其余的goroutine先不焦急让他们间接返回,而是都先阻塞在sync.Mutex上。如果那个拿到锁的goroutine很可怜执行f()失败了,那么defer o.m.Unlock()操作会立即唤醒阻塞的goroutine接着尝试执行直到胜利为止。执行胜利后通过defer atomic.StoreUint32(&o.done, 1)来将执行f()的大门给敞开上。 总结有了sync.Once,相比Java或者Python实现单例更加简略,不必殚精竭虑胆怯手抖写出引发线程平安问题的代码了。 ...

May 22, 2021 · 1 min · jiezi

关于golang:Go并发编程八-深入理解-syncOnce

在上一篇文章《Week03: Go 并发编程(七) 深刻了解 errgroup》当中看 errgourp  源码的时候咱们发现最初返回 err  是通过 once 来只保障返回一个非 nil 的值的,本文就来看一下 Once 的应用与实现 案例once 的应用很简略 `func main() {` `var (` `o  sync.Once` `wg sync.WaitGroup` `)` `for i := 0; i < 10; i++ {` `wg.Add(1)` `go func(i int) {` `defer wg.Done()` `o.Do(func() {` `fmt.Println("once", i)` `})` `}(i)` `}` `wg.Wait()``}`输入 `❯ go run ./main.go``once 9`源码剖析`type Once struct {` `done uint32` `m    Mutex``}`done 用于断定函数是否执行,如果不为 0 会间接返回 `func (o *Once) Do(f func()) {` `// Note: Here is an incorrect implementation of Do:` `//` `// if atomic.CompareAndSwapUint32(&o.done, 0, 1) {` `//  f()` `// }` `//` `// Do guarantees that when it returns, f has finished.` `// This implementation would not implement that guarantee:` `// given two simultaneous calls, the winner of the cas would` `// call f, and the second would return immediately, without` `// waiting for the first's call to f to complete.` `// This is why the slow path falls back to a mutex, and why` `// the atomic.StoreUint32 must be delayed until after f returns.` `if atomic.LoadUint32(&o.done) == 0 {` `// Outlined slow-path to allow inlining of the fast-path.` `o.doSlow(f)` `}``}`看 go 的源码真的能够学到很多货色,在这里还给出了很容易犯错的一种实现 `if atomic.CompareAndSwapUint32(&o.done, 0, 1) {` `f()``}`如果这么实现最大的问题是,如果并发调用,一个 goroutine 执行,另外一个不会等正在执行的这个胜利之后返回,而是间接就返回了,这就不能保障传入的办法肯定会先执行一次了 所以回头看官网的实现 `if atomic.LoadUint32(&o.done) == 0 {` `// Outlined slow-path to allow inlining of the fast-path.` `o.doSlow(f)``}`会先判断 done 是否为 0,如果不为 0 阐明还没执行过,就进入 doSlow ...

May 22, 2021 · 1 min · jiezi

关于golang:gRPC介绍

[TOC] gRPC gRPC介绍gRPC是什么? RPC和RESTful的区别是什么?RPC的音讯传输能够是TCP,能够是UDP,也能够是HTTP,当RPC音讯传输是HTTP时,它的构造与RESTful的架构相似 RPC和RESTful有什么不同呢: 操作的对象不一样的,RESTful会更加灵便RPC操作的是办法对象, RESTful操作的是资源 RPC的客户端和服务器端是紧耦合的,客户端须要晓得服务端的函数名字,参数类型、程序等,能力近程过程调用。 RESTful基于 http的语义操作资源,参数的程序个别没有关系 RCP更适宜定制化 RESTful执行的是对资源的操作,次要都是CURD(增删改查)的操作,若须要实现一个特定的性能,如计算一个班级的平均分,这个时候应用RPC定义服务器的办法(如:Stu.CalAvg)供客户端调用则显得更有意义 gRPC的个性是什么?gRPC是能够跨语言开发的在gRPC客户端能够间接调用不同服务器上的近程程序,应用姿态看起来就像调用本地过程调用一样,很容易去构建分布式应用和服务。客户端和服务端能够别离应用gRPC反对的不同语言实现。 基于HTTP2规范设计,比其余框架更优的中央有 反对长连贯,双向流、头部压缩、多复用申请等节俭带宽、升高TCP链接次数、节俭CPU应用和缩短电池寿命进步了云端服务和Web利用的性能客户端和服务端交互通明gRPC默认应用protobuf来对数据序列化gRPC的数据交互模式是怎么样的?申请应答式 客户端收回一次申请,能够从服务端读取一系列的音讯 客户端写一系列音讯给到服务端,期待服务端应答 客户端和服务端都能够通过读写数据流来发送一系列音讯 数据的序列化形式 - protobufprotobuf 是一个对数据序列化的形式,相似的有JSON,XML等 简略介绍protobuf的构造定义蕴含的3个关键字以.proto做为后缀,除构造定义外的语句以分号结尾构造定义能够蕴含:message、service、enum,三个关键字rpc办法定义结尾的分号可有可无Message命名采纳驼峰命名形式,字段是小写加下划线 message ServerRequest { required string my_name = 1; }Enums类型名采纳驼峰命名形式,字段命名采纳大写字母加下划线 enum MyNum { VALUE1 = 1; VALUE2 = 2; }Service与rpc办法名对立采纳驼峰式命名 service Love { // 定义Confession办法 rpc MyConfession(Request) returns (Response) {}}对于prtobuf的装置能够看看之前写的一个装置步骤《5个步骤搞定PROTOBUF的装置》 在proto文件中应用package关键字申明包名,默认转换成go中的包名与此统一,能够自定义包名,批改go_package即可: test.proto syntax = "proto3"; // proto版本package pb; // 指定包名,默认go中包名也是这个// 定义Love服务service Love { // 定义Confession办法 rpc Confession(Request) returns (Response) {}}// 申请message Request { string name = 1;}// 响应message Response { string result = 1;}装置好protoc环境之后,进入到proto文件的目录下,(例如关上window git)执行如下命令,将proto文件编译成pb.go文件 ...

May 21, 2021 · 2 min · jiezi

关于golang:Golang-package-sync-剖析一-syncOnce

前言Go语言在设计上对同步(Synchronization,数据同步和线程同步)提供大量的反对,比方 goroutine和channel同步原语,库层面有 sync:提供根本的同步原语(比方Mutex、RWMutex、Locker)和 工具类(Once、WaitGroup、Cond、Pool、Map)sync/atomic:提供原子操作(基于硬件指令compare-and-swap)留神:__当我说“类”时,是指 Go 里的 struct(__独身狗要有面向“对象”编程的觉醒__)。 Go语言里对同步的反对次要有五类利用场景: 资源独占:当多个线程依赖同一份资源(比方数据),须要同时读/写同一个内存地址时,runtime须要保障只有一个批改这份数据,并且保障该批改对其余线程可见。锁和变量的原子操作为此而设计;生产者-消费者:在生产者-消费者模型中,消费者依赖生产者产出数据。channel(管道) 为此而设计;懒加载:一个资源,当且仅当第一次执行一个操作,该操作执行过程中其余的同类操作都会被阻塞,直到该操作实现。sync.Once为此而设计;fork-join:一个工作首先创立出N个子工作,N个子工作全副执行实现当前,主工作收集后果,执行后续操作。sync.WaitGroup 为此而设计;条件变量:条件变量是一个同步原语,能够同时阻塞多个线程,直到另一个线程 1) 批改了条件; 2)告诉一个(或所有)期待的线程。sync.Cond 为此而设计;留神:__这里当我说”线程”时,理解Go的同学能够主动映射到 “goroutine”(协程)。 对于 1和2,通过官网文档理解其用法和实现。本系列的配角是 sync 下的工工具类,从 sync.Once 开始。内容分两局部:sync.Once 用法和sync.Once 实现。 sync.Once 用法在少数状况下,sync.Once 被用于控制变量的初始化,这个变量的读写通常遵循单例模式,满足这三个条件: 当且仅当第一次读某个变量时,进行初始化(写操作)变量被初始化过程中,所有读都被阻塞(读操作;当变量初始化实现后,读操作持续进行变量仅初始化一次,初始化实现后驻留在内存里在 net 库里,零碎的网络配置就是寄存在一个变量里,代码如下: `package net``var (` `// guards init of confVal via initConfVal` `confOnce sync.Once` `confVal = &conf{goos: runtime.GOOS}``)``// systemConf returns the machine's network configuration.``func systemConf() *conf {` `confOnce.Do(initConfVal)` `return confVal``}``func initConfVal() {` `dnsMode, debugLevel := goDebugNetDNS()` `confVal.dnsDebugLevel = debugLevel` `// 省略局部代码...``}`下面这段代码里,confVal 存放数据, confOnce 管制读写,两个都是 package-level 单例变量。因为 Go 里变量被初始化为默认值,confOnce 能够被立刻应用,咱们重点关注confOnce.Do。首先看成员函数 Do 的定义: ...

May 21, 2021 · 1 min · jiezi

关于golang:go-viper包的使用

viperviper 是一个残缺的Go应用程序配置解决方案。它被设计为在应用程序中工作,能够解决所有类型的配置需要和格局。它反对:反对JSON, TOML, YAML, HCL, envfile,以及java等丰盛的配置文件实时查看和从新读取配置文件(可选)从近程配置零碎(etcd或Consul)读取数据,并察看更改从命令行标记读取实现目录如下.├── config│ ├── app.go //│ └── config.go //配置文件├── go.mod├── go.sum├── main.go└── pkg └── config └── config.go 应用在你的我的项目目录下执行go mod init go get github.com/spf13/viper新建pkg/config 并创立config.go文件/** @author:panliang @data:2021/5/13 @note**/package configimport ( "github.com/spf13/cast" "github.com/spf13/viper" "log")var Viper *viper.Vipertype StrMap map[string]interface{}func init() { Viper = viper.New() // 1. 初始化 Viper 库 Viper = viper.New() // 2. 设置文件名称 Viper.SetConfigName(".env") // 3. 配置类型,反对 "json", "toml", "yaml", "yml", "properties", // "props", "prop", "env", "dotenv" Viper.SetConfigType("env") // 4. 环境变量配置文件查找的门路,绝对于 main.go Viper.AddConfigPath(".") // 5. 开始读根目录下的 .env 文件,读不到会报错 err := Viper.ReadInConfig() log.Println(err) // 6. 设置环境变量前缀,用以辨别 Go 的零碎环境变量 Viper.SetEnvPrefix("appenv") // 7. Viper.Get() 时,优先读取环境变量 Viper.AutomaticEnv()}// Env 读取环境变量,反对默认值func Env(envName string, defaultValue ...interface{}) interface{} { if len(defaultValue) > 0 { return Get(envName, defaultValue[0]) } return Get(envName)}// Add 新增配置项func Add(name string, configuration map[string]interface{}) { Viper.Set(name, configuration)}// Get 获取配置项,容许应用点式获取,如:app.namefunc Get(path string, defaultValue ...interface{}) interface{} { // 不存在的状况 if !Viper.IsSet(path) { if len(defaultValue) > 0 { return defaultValue[0] } return nil } return Viper.Get(path)}// GetString 获取 String 类型的配置信息func GetString(path string, defaultValue ...interface{}) string { return cast.ToString(Get(path, defaultValue...))}// GetInt 获取 Int 类型的配置信息func GetInt(path string, defaultValue ...interface{}) int { return cast.ToInt(Get(path, defaultValue...))}// GetInt64 获取 Int64 类型的配置信息func GetInt64(path string, defaultValue ...interface{}) int64 { return cast.ToInt64(Get(path, defaultValue...))}// GetUint 获取 Uint 类型的配置信息func GetUint(path string, defaultValue ...interface{}) uint { return cast.ToUint(Get(path, defaultValue...))}// GetBool 获取 Bool 类型的配置信息func GetBool(path string, defaultValue ...interface{}) bool { return cast.ToBool(Get(path, defaultValue...))}创立config目录以及app.go/** @author:panliang @data:2021/5/13 @note**/package configimport "gotime/pkg/config"func init() { config.Add("app", config.StrMap{ // 利用名称,临时没有应用到 "name": config.Env("APP_NAME", "test"), // 以后环境,用以辨别多环境 "env": config.Env("APP_ENV", "production"), })}主动读取config目录下配置创立config.go/** @author:panliang @data:2021/5/13 @note**/package configfunc Initialize() { // 触发加载本目录下其余文件中的 init 办法}新建main.go/** @author:panliang @data:2021/5/13 @note**/package mainimport ( "fmt" "gotime/config" config2 "gotime/pkg/config")func init() { //初始化加载 config.Initialize()}func main() { //打印 fmt.Println(config2.Get("app.name"))}新建配置.envAPP_NAME=testsAPP_ENV=local执行go run main.go ...

May 21, 2021 · 2 min · jiezi

关于golang:Go程序性能分析工具和方法

作者:宁亮 一、罕用剖析命令和工具pprofgo tool [xxx]go testdelvego racegdb二、程序编译时的参数传递1、gcflags//可应用go tool compile --help查看可用参数及含意go build -gcflags="-m"   比方 -N 禁用编译优化,-l 禁止内联,-m 打印编译优化策略(包含逃逸状况和函数是否内联,以及变量调配在堆或栈),-S 是打印汇编。 如果只在编译特定包时须要传递参数,格局应恪守“包名=参数列表”,如 go build -gcflags='log=-N -l' main.go 2、ldflagsgo build 用 -ldflags 给 go 链接器传入参数,理论是给 go tool link 的参数,能够用 go tool link --help 查看可用的参数。 罕用 -X 来指定版本号等编译时才决定的参数值。例如代码中定义var buildVer string,而后在编译时用go build -ldflags "-X main.buildVer=1.0" 来赋值。留神 -X 只能给string类型变量赋值。 三、go build -x能够列出 go build 触发的所有命令,比方工具链、跨平台编译、传入内部编译器的 flags、链接器等,可应用 -x 来查看所有的触发。 四、竞争检测应用 go run -race main.go 或 go build -race main.go 来进行竞争检测。 ...

May 21, 2021 · 3 min · jiezi

关于golang:GO切片

Go 语言切片是对数组的形象。 Go 数组的长度不可扭转,在特定场景中这样的汇合就不太实用,Go 中提供了一种灵便,性能强悍的内置类型切片("动静数组"),与数组相比切片的长度是不固定的,能够追加元素,在追加时可能使切片的容量增大。 空(nil)切片一个切片在未初始化之前默认为 nil,长度为 0,实例如下: package mainimport "fmt"func main() { var numbers []int printSlice(numbers) if(numbers == nil){ fmt.Printf("切片是空的") }}func printSlice(x []int){ fmt.Printf("len=%d cap=%d slice=%v\n",len(x),cap(x),x)}切片截取case1:foo和bar的内存是共享的,所以,foo和bar的对数组内容的批改都会影响到对方。 import ( "bytes" "testing")func TestSlice1(t *testing.T) { foo := make([]int, 5) // 首先先创立一个foo的slice,其中的长度和容量都是5 foo[3] = 42 // 而后开始对foo所指向的数组中的索引为3和4的元素进行赋值 foo[4] = 100 t.Log("foo=", foo) // foo= [0 0 0 42 100] bar := foo[1:4] bar[1] = 99 // foo和bar的内存是共享的,所以,foo和bar的对数组内容的批改都会影响到对方。 t.Log("foo=", foo) // foo= [0 0 99 42 100] t.Log("bar=", bar) // bar= [0 99 42]}case2: 数据操作 append() 的示例append()这个函数在 cap不够用的时候就会从新分配内存以扩充容量,而如果够用的时候不不会从新分享内存! ...

May 21, 2021 · 3 min · jiezi

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

简介gotalk专一于过程间的通信,致力于简化通信协议和流程。同时它: 提供简洁、清晰的 API;反对 TCP,WebSocket 等协定;采纳非常简单而又高效的传输协定格局,便于抓包调试;内置了 JavaScript 文件gotalk.js,不便开发基于 Web 网页的客户端程序;内含丰盛的示例可供学习参考。那么,让咱们来玩一下吧~ 疾速应用本文代码应用 Go Modules。 创立目录并初始化: $ mkdir gotalk && cd gotalk$ go mod init github.com/darjun/go-daily-lib/gotalk装置gotalk库: $ go get -u github.com/rsms/gotalk接下来让咱们来编写一个简略的 echo 程序,服务端间接返回收到的客户端信息,不做任何解决。首先是服务端: // get-started/server/server.gopackage mainimport ( "log" "github.com/rsms/gotalk")func main() { gotalk.Handle("echo", func(in string) (string, error) { return in, nil }) if err := gotalk.Serve("tcp", ":8080", nil); err != nil { log.Fatal(err) }}通过gotalk.Handle()注册音讯解决,它承受两个参数。第一个参数为音讯名,字符串类型,保障惟一且可辨识即可。第二个参数为处理函数,收到对应名称的音讯,调用该函数解决。处理函数承受一个参数,返回两个值。失常解决实现通过第一个返回值传递处理结果,出错时通过第二个返回值示意谬误类型。 这里的处理器函数比较简单,承受一个字符串参数,间接原样返回。 而后,调用gotalk.Serve()启动服务器,监听端口。它承受 3 个参数,协定类型、监听地址、处理器对象。此处咱们应用 TCP 协定,监听本地8080端口,应用默认处理器对象,传入nil即可。 服务器外部始终循环解决申请。 而后是客户端: func main() { s, err := gotalk.Connect("tcp", ":8080") if err != nil { log.Fatal(err) } for i := 0; i < 5; i++ { var echo string if err := s.Request("echo", "hello", &echo); err != nil { log.Fatal(err) } fmt.Println(echo) } s.Close()}客户端首先调用gotalk.Connect()连贯服务器,它承受两个参数:协定和地址(IP + 端口)。咱们应用与服务器统一的协定和地址即可。连贯胜利会返回一个连贯对象。调用连贯对象的Request()办法,即可向服务器发送音讯。Request()办法承受 3 个参数。第一个参数为音讯名,这对应于服务器注册的音讯名,申请一个不存在的音讯名会返回谬误。第二个参数是传给服务器的参数,有且只能有一个参数,对应处理器函数的入参。第三个参数为返回值的指针,用于承受服务器返回的后果。 ...

May 21, 2021 · 3 min · jiezi

关于golang:GolangsyncOnce用法以及源码讲解

前言在咱们开发过程中常常会应用到单例模式这一经典的设计模式,单例模式能够帮忙开发者针对某个(些)变量或者对象或者函数(办法)进行在程序运行期间只有一次的初始化或者函数调用操作,比方在开发我的项目中针对某一类连接池的初始化(如数据库连接池等)。针对这种状况,咱们就须要应用单例模式进行操作。单例模式本人搞得单例模式要实现一个单例模式,咱们会很快就想到了在一个构造体中搁置一个flag字段用于标记以后的函数是否被执行过,举个: `type SingletonPattern struct {` `done bool``}``func (receiver *SingletonPattern) Do(f func()) {` `if !receiver.done {` `f()` `receiver.done=true` `}``}`看似很美妙,然而此时,如果传入的须要调用的函数f()会执行很长时间,比方数据库查问或者做一些连贯什么的,当别的goroutine运行到此处的时候因为还没有执行完f(),就会发现done标记依然是false,那么依然会调用一次f(),此时就违反了单例模式的初衷。 那么如何解决下面的并发的问题呢。此时就能够应用go规范库中所提供的并发原语---sync.Once 规范库真香系列之sync.Once话不多说先上sync.Once 构造体的源代码: `type Once struct {` `// 标记符号,用于标记是否执行过` `done uint32` `// 互斥锁,用于爱护并发调用以及避免copy` `m Mutex``}`构造体就这么简略,字段done用于标记是否执行过函数,至于为什么应用uint32类型,作者的了解是为了之后应用atomic操作做的斗争,m字段值用于爱护并发状况下的情景,并且因为继承了Locker接口能够通过vet校验到其是否被复制 接下来看一下用于执行函数调用的Do()函数的实现: `func (o *Once) Do(f func()) {` `// 原子获取以后 done 字段是否等于0` `// 如果以后字段等于1` `// 则代表曾经 执行过` `// 这是第一层校验` `if atomic.LoadUint32(&o.done) == 0 {` `// 如果为0则代表没被调用过则调用` `// 此处写成一个函数的起因是为了` `// 进行函数内联晋升性能` `o.doSlow(f)` `}``}``func (o *Once) doSlow(f func()) {` `// 此处加锁用于避免其余goroutine同时拜访调用` `o.m.Lock()` `defer o.m.Unlock()` `// 二次校验` `// 为的是避免多个goroutine进入此函数的时候,可能产生的反复执行 f()` `if o.done == 0 {` `// 函数执行完结设置done 字段为 1代表曾经执行结束` `defer atomic.StoreUint32(&o.done, 1)` `// 执行` `f()` `}``}`此时,sync.Once 的所有源代码曾经解析结束了(惊不惊喜,意不意外),其实sync.Once 的过程很简略,就是依据标记进行双重判断确定函数是否执行过,没执行就执行,执行了就跳过。 ...

May 20, 2021 · 2 min · jiezi

关于golang:手摸手Go-深入理解syncCond

Today that you are wasting is the unattainable tomorrow to someone who expired yesterday. This very moment that you detest is the unreturnable experience to your future self.sync.Cond实现了一个条件变量,用于期待一个或一组goroutines满足条件后唤醒的场景。每个Cond关联一个Locker通常是一个*Mutex或RWMutex\`依据需要初始化不同的锁。 根本用法老规矩正式分析源码前,先来看看sync.Cond如何应用。比方咱们实现一个FIFO的队列 `package main``import (` `"fmt"` `"math/rand"` `"os"` `"os/signal"` `"sync"` `"time"``)``type FIFO struct {` `lock  sync.Mutex` `cond  *sync.Cond` `queue []int``}``type Queue interface {` `Pop() int` `Offer(num int) error``}``func (f *FIFO) Offer(num int) error {` `f.lock.Lock()` `defer f.lock.Unlock()` `f.queue = append(f.queue, num)` `f.cond.Broadcast()` `return nil``}``func (f *FIFO) Pop() int {` `f.lock.Lock()` `defer f.lock.Unlock()` `for {` `for len(f.queue) == 0 {` `f.cond.Wait()` `}` `item := f.queue[0]` `f.queue = f.queue[1:]` `return item` `}``}``func main() {` `l := sync.Mutex{}` `fifo := &FIFO{` `lock:  l,` `cond:  sync.NewCond(&l),` `queue: []int{},` `}` `go func() {` `for {` `fifo.Offer(rand.Int())` `}` `}()` `time.Sleep(time.Second)` `go func() {` `for {` `fmt.Println(fmt.Sprintf("goroutine1 pop-->%d", fifo.Pop()))` `}` `}()` `go func() {` `for {` `fmt.Println(fmt.Sprintf("goroutine2 pop-->%d", fifo.Pop()))` `}` `}()` `ch := make(chan os.Signal, 1)` `signal.Notify(ch, os.Interrupt)` `<-ch``}`咱们定一个FIFO 队列有Offer和Pop两个操作,咱们起一个gorountine一直向队列投放数据,另外两个gorountine一直取拿数据。 ...

May 20, 2021 · 2 min · jiezi

关于golang:图文讲解Go-中的循环是如何转为汇编的

点击上方“Go编程时光”,抉择“加为星标” 第一工夫关注Go技术干货! 本文基于 Go 1.13 版本 循环在编程中是一个重要的概念,且易于上手。然而,循环必须被翻译成计算机能了解的底层指令。它的编译形式也会在肯定水平上影响到规范库中的其余组件。让咱们开始剖析循环吧。 循环的汇编代码应用循坏迭代 array,slice,channel,以下是一个应用循环对 slice 计算总和的例子。 `func main() {` `l := []int{9, 45, 23, 67, 78}` `t := 0` `for _, v := range l {` `t += v` `}` `println(t)``}`应用 go tool compile -S main.go 生成的汇编代码,以下为相干输入: `0x0041 00065 (main.go:4)   XORL   AX, AX``0x0043 00067 (main.go:4)   XORL   CX, CX``0x0045 00069 (main.go:7)   JMP    82``0x0047 00071 (main.go:7)   MOVQ   ""..autotmp_5+16(SP)(AX*8), DX``0x004c 00076 (main.go:7)   INCQ   AX``0x004f 00079 (main.go:8)   ADDQ   DX, CX``0x0052 00082 (main.go:7)   CMPQ   AX, $5``0x0056 00086 (main.go:7)   JLT    71``0x0058 00088 (main.go:11)  MOVQ   CX, "".t+8(SP)`我把这些指令分为了两个局部,初始化局部和循环主体。前两条指令,将两个寄存器初始化为零值。 `0x0041 00065 (main.go:4)   XORL   AX, AX``0x0043 00067 (main.go:4)   XORL   CX, CX`寄存器 AX 蕴含着以后循环所处地位,而 CX 蕴含着变量 t 的值,上面为带有指令和通用寄存器的直观示意: 循环从示意「跳转到指令 82 」的 JMP 82 开始,这条指令的作用能够通过第二行来判断: 接下来的指令 CMPQ AX,$5 示意「比拟寄存器 AX 和 5」,事实上,这个操作是把 AX 中的值减去 5 ,而后贮存在另一个寄存器中,这个值能够被用在下一条指令 JLT 71 中,它的含意是 「如果值小于 0 则跳转到指令 71 」,以下是更新后的直观示意: 如果不满足条件,则程序将会跳转到循环体之后的下一条指令执行。 所以,咱们当初有了对循环的根本框架,以下是转换后的 Go 循环: `goto end``start:` `?``end:` `if i < 5 {` `goto start` `}``println(t)`咱们短少了循环的主体,接下来,咱们看看这部分的指令: ...

May 20, 2021 · 1 min · jiezi

关于golang:这一次彻底搞懂-Go-Cond

本篇文章会从源码角度去深刻分析下 sync.Cond。Go 日常开发中 sync.Cond 可能是咱们用的较少的管制并发的伎俩,因为大部分场景下都被 Channel 代替了。还有就是 sync.Cond 应用的确也蛮简单的。 比方上面这段代码: `package main``import (` `"fmt"` `"time"``)``func main() {` `done := make(chan int, 1)` `go func() {` `time.Sleep(5 * time.Second)` `done <- 1` `}()` `fmt.Println("waiting")` `<-done` `fmt.Println("done")``}`同样能够应用 sync.Cond 来实现 `package main``import (` `"fmt"` `"sync"` `"time"``)``func main() {` `cond := sync.NewCond(&sync.Mutex{})` `var flag bool` `go func() {` `time.Sleep(time.Second * 5)` `cond.L.Lock()` `flag = true` `cond.Signal()` `cond.L.Unlock()` `}()` `fmt.Println("waiting")` `cond.L.Lock()` `for !flag {` `cond.Wait()` `}` `cond.L.Unlock()` `fmt.Println("done")``}`大部分场景下应用 channel 是比 sync.Cond不便的。不过咱们要留神到,sync.Cond 提供了 Broadcast 办法,能够告诉所有的期待者。想利用 channel 实现这个办法还是不容易的。我想这应该是 sync.Cond 惟一有用武之地的中央。 先列进去一些问题吧,能够带着这些问题来浏览本文: cond.Wait自身就是阻塞状态,为什么 cond.Wait 须要在循环内 ?sync.Cond 如何触发不能复制的 panic ?为什么 sync.Cond 不能被复制 ?cond.Signal 是如何告诉一个期待的 goroutine ?cond.Broadcast 是如何告诉期待的 goroutine 的?源码分析 ...

May 20, 2021 · 2 min · jiezi

关于golang:Golang-语言临时对象池-syncPool

大家好,我是 frank。 欢送大家点击上方蓝色文字「Golang 语言开发栈」关注公众号。01 介绍 sync.Pool 是 sync 包提供的一个数据类型,也称为长期对象池,它的值是用来存储一组能够独立拜访的长期对象,它通过池化缩小申请新对象,晋升程序的性能。sync.Pool 类型是 struct 类型,它的值在被首次应用之后,就不能够再被复制了。因为 sync.Pool 中存储的所有对象都能够随时主动删除,所以应用 sync.Pool 类型的值必须满足两个条件,一是该值存在与否,都不会影响程序的性能,二是该值之间能够相互代替。sync.Pool 是 goroutine 并发平安的,能够平安地同时被多个 goroutine 应用;sync.Pool 的目标是缓存已调配但未应用的对象以供当前重用,从而加重了垃圾收集器的性能影响,因为 Go 的主动垃圾回收机制,会有一个 STW 的工夫耗费,并且大量在堆上创建对象,也会减少垃圾回收标记的工夫。 sync.Pool 的适当用法是治理一组长期对象,这些长期对象在程序包的并发独立客户端之间静默共享并有可能被重用。sync.Pool 提供了一种摊派许多客户端上的调配开销的办法。 然而,作为短期(short-lived)对象的一部分保护的闲暇列表不适用于 sync.Pool,因为在这种状况下,开销无奈很好地摊派。 Golang 语言中的规范库 fmt 包应用了 sync.Pool,它会应用一个动静大小的 buffer 池做输入缓存,当大量的 goroutine 并发输入的时候,就会创立比拟多的 buffer,并且在不须要的时候回收掉。 02 应用形式 sync.Poll 类型蕴含两个办法: func (p *Pool) Put(x interface{})func (p *Pool) Get() interface{}Put() 用于向长期对象池中寄存对象,它接管一个 interface{} 空接口类型的参数;Get()用于从长期对象池中获取对象,它返回一个 interface{} 空接口类型的返回值。 Get() 从长期对象池中抉择一个任意对象,将其从长期对象池中删除,而后将其返回给调用方。 Get() 能够抉择疏忽长期对象池并将其视为空。调用者不应假设传递给 Put() 的值和 Get() 返回的值之间有任何关系。 如果 Get() 返回 nil,而 p.New 不为 nil,则 Get() 返回调用 p.New 的后果。 sync.Pool 类型的 New 字段,字段类型是函数类型 func() interface{},代表创立长期对象的函数,该函数的后果值并不会存入到长期对象池中,而是间接返回给 Get() 办法的调用方。 须要留神的是,sync.Pool 类型的 New 字段的值也须要咱们初始化对象时给定,否则,在调用 Get() 办法时,有可能会失去 nil。 咱们曾经介绍了长期对象什么时候会被创立,当初咱们介绍长期对象什么时候会被销毁。咱们曾经晓得 sync.Pool 应用之前须要先初始化,其实在初始化时,还会向 Golang 运行时中注册一个清理函数,用于清理长期对象池中的所有已创立的值,golang 运行时每次在执行垃圾回收之前,先执行该清理函数。 示例代码: `func main () {` `pool := &sync.Pool{` `New: func() interface{} {` `fmt.Println("New 一个新对象")` `return 0` `},` `}` `// 取,长期对象池中没有数据,会调用 New,New 创立一个新对象间接返回,不会存储在长期对象池中` `val := pool.Get().(int)` `fmt.Println(val)` `// 存` `pool.Put(10)` `// 手动调用 GC(),用于验证 GC 之后,长期对象池中的对象会被清空。` `runtime.GC()` `// 取` `val2 := pool.Get().(int)` `fmt.Println(val2)``}`03 ...

May 19, 2021 · 2 min · jiezi

关于golang:Go-SyncPool-背后的想法

点上方蓝色“云原生畛域”关注我,设个星标,不会让你悲观概述 我最近在我的一个我的项目中遇到了垃圾回收问题。大量对象被反复调配,并导致 GC 的微小工作量。应用 sync.Pool,我可能缩小调配和 GC 工作负载。 什么是 sync.Pool?Go 1.3 版本的亮点之一是同步池。它是 sync 包下的一个组件,用于创立自我管理的长期检索对象池。 为什么要应用 sync.Pool?咱们心愿尽可能减少 GC 开销。频繁的内存调配和回收会给 GC 带来惨重的累赘。sync.Poll 能够缓存临时不应用的对象,并在下次须要时间接应用它们(无需重新分配)。这可能会缩小 GC 工作负载并进步性能。 怎么应用 sync.Pool?首先,您须要设置新函数。当池中没有缓存对象时将应用此函数。之后,您只须要应用 Get 和 Put 办法来检索和返回对象。另外,池在第一次应用后相对不能复制。 因为 New 函数类型是 func() interface{},Get 办法返回一个 interface{}。为了失去具体对象,你须要做一个类型断言。 `// A dummy struct``type Person struct {` `Name string``}``// Initializing pool``var personPool = sync.Pool{` `// New optionally specifies a function to generate` `// a value when Get would otherwise return nil.` `New: func() interface{} { return new(Person) },``}``// Main function``func main() {` `// Get hold of an instance` `newPerson := personPool.Get().(*Person)` `// Defer release function` `// After that the same instance is` `// reusable by another routine` `defer personPool.Put(newPerson)` `// Using the instance` `newPerson.Name = "Jack"``}`基准测试`type Person struct {` `Age int``}``var personPool = sync.Pool{` `New: func() interface{} { return new(Person) },``}``func BenchmarkWithoutPool(b *testing.B) {` `var p *Person` `b.ReportAllocs()` `b.ResetTimer()` `for i := 0; i < b.N; i++ {` `for j := 0; j < 10000; j++ {` `p = new(Person)` `p.Age = 23` `}` `}``}``func BenchmarkWithPool(b *testing.B) {` `var p *Person` `b.ReportAllocs()` `b.ResetTimer()` `for i := 0; i < b.N; i++ {` `for j := 0; j < 10000; j++ {` `p = personPool.Get().(*Person)` `p.Age = 23` `personPool.Put(p)` `}` `}``}`测试后果: ...

May 19, 2021 · 1 min · jiezi

关于golang:深度解密Go语言之syncPool

https://juejin.cn/post/691837... 最近在工作中碰到了 GC 的问题:我的项目中大量反复地创立许多对象,造成 GC 的工作量微小,CPU 频繁掉底。筹备应用 sync.Pool 来缓存对象,加重 GC 的耗费。为了用起来更顺畅,我顺便钻研了一番,造成此文。本文从应用到源码解析,循序渐进,一一道来。 本文基于 Go 1.14是什么有什么用怎么用简略的例子fmt 包如何用pool\_test其余源码剖析Pool 构造体GetPutpack/unpackGC总结参考资料是什么sync.Pool 是 sync 包下的一个组件,能够作为保留长期取还对象的一个“池子”。集体感觉它的名字有肯定的误导性,因为 Pool 里装的对象能够被无告诉地被回收,可能 sync.Cache 是一个更适合的名字。 有什么用对于很多须要反复调配、回收内存的中央,sync.Pool 是一个很好的抉择。频繁地调配、回收内存会给 GC 带来肯定的累赘,重大的时候会引起 CPU 的毛刺,而 sync.Pool 能够将临时不必的对象缓存起来,待下次须要的时候间接应用,不必再次通过内存调配,复用对象的内存,加重 GC 的压力,晋升零碎的性能。 怎么用首先,sync.Pool 是协程平安的,这对于使用者来说是极其不便的。应用前,设置好对象的 New 函数,用于在 Pool 里没有缓存的对象时,创立一个。之后,在程序的任何中央、任何时候仅通过 Get()、Put() 办法就能够取、还对象了。 上面是 2018 年的时候,《Go 夜读》上对于 sync.Pool 的分享,对于实用场景: 当多个 goroutine 都须要创立同⼀个对象的时候,如果 goroutine 数过多,导致对象的创立数⽬剧增,进⽽导致 GC 压⼒增大。造成 “并发⼤-占⽤内存⼤-GC 迟缓-解决并发能⼒升高-并发更⼤”这样的恶性循环。 在这个时候,须要有⼀个对象池,每个 goroutine 不再⾃⼰独自创建对象,⽽是从对象池中获取出⼀个对象(如果池中曾经有的话)。 因而要害思维就是对象的复用,防止反复创立、销毁,上面咱们来看看如何应用。 简略的例子首先来看一个简略的例子: package mainimport ( "fmt" "sync")var pool \*sync.Pooltype Person struct { Name string}func initPool() { pool = &sync.Pool { New: func()interface{} { fmt.Println("Creating a new Person") return new(Person) }, }}func main() { initPool() p := pool.Get().(\*Person) fmt.Println("首次从 pool 里获取:", p) p.Name = "first" fmt.Printf("设置 p.Name = %s\\n", p.Name) pool.Put(p) fmt.Println("Pool 里已有一个对象:&{first},调用 Get: ", pool.Get().(\*Person)) fmt.Println("Pool 没有对象了,调用 Get: ", pool.Get().(\*Person))}运行后果: ...

May 19, 2021 · 15 min · jiezi

关于golang:Go-syncPool-浅析

https://mp.weixin.qq.com/s/MT... hi, 大家好,我是 haohongfan。 sync.Pool 应该是 Go 外面明星级别的数据结构,有很多优良的文章都在介绍这个构造,本篇文章简略分析下 sync.Pool。不过说实话 sync.Pool 并不是咱们日常开发中应用频率很高的的并发原语。 只管用的频率很低,然而不可否认的是 sync.Pool 的确是 Go 的杀手锏,正当应用 sync.Pool 会让咱们的程序性能飙升。本篇文章会从应用形式,源码分析,使用场景等方面,让你对 sync.Pool 有一个清晰的认知。 应用形式sync.Pool 应用很简略,然而想用对却很麻烦,因为你有可能看到网上一堆谬误的示例,各位同学在搜寻 sync.Pool 的应用例子时,要特地留神。 sync.Pool 是一个内存池。通常内存池是用来避免内存泄露的(例如C/C++)。sync.Pool 这个内存池却不是干这个的,带 GC 性能的语言都存在垃圾回收 STW 问题,须要回收的内存块越多,STW 持续时间就越长。如果能让 new 进去的变量,始终不被回收,失去反复利用,是不是就加重了 GC 的压力。 正确的应用示例(上面的demo选自gin) `func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {` `c := engine.pool.Get().(*Context)` `c.writermem.reset(w)` `c.Request = req` `c.reset()` `engine.handleHTTPRequest(c)` `engine.pool.Put(c)``}`肯定要留神的是:是先 Get 获取内存空间,基于这个内存做相干的解决,而后再将这个内存还回(Put)到 sync.Pool。 Pool 构造 sync.Pool 全景图 源码图解 Pool.Get Pool.Put 简略点能够总结成上面的流程: Pool.Get 流程 Pool.Put流程 Pool GC 流程 Sync.Pool 梳理Pool 的内容会清理?清理会造成数据失落吗?Go 会在每个 GC 周期内定期清理 sync.Pool 内的数据。 ...

May 19, 2021 · 2 min · jiezi

关于golang:Go语言中new和make你使用哪个来分配内存

原文链接:Go语言中new和make你应用哪个来分配内存? 前言哈喽,大家好,我是拖更良久的鸽子asong。因为5.1去找女朋友,所以始终没有工夫写文章啦,想着回来就放松学习,无奈,仍然沉迷在5.1的苦涩生存中,一拖再拖,就到当初啦。果然女人影响了我拔刀的速度,然而我很喜爱,略略略。 好啦,不撒狗粮了,开始进入正题,明天咱们就来探讨一下Go语言中的make和new到底怎么应用?它们又有什么不同? 分配内存之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翻译进去就是:new是一个分配内存的内置函数,第一个参数是类型,而不是值,返回的值是指向该类型新调配的零值的指针。咱们平时在应用指针的时候是须要分配内存空间的,未分配内存空间的指针间接应用会使程序解体,比方这样: var a *int64*a = 10咱们申明了一个指针变量,间接就去应用它,就会应用程序触发panic,因为当初这个指针变量a在内存中没有块地址属于它,就无奈间接应用该指针变量,所以new函数的作用就呈现了,通过new来调配一下内存,就没有问题了: var a *int64 = new(int64) *a = 10下面的例子,咱们是针对一般类型int64进行new解决的,如果是复合类型,应用new会是什么样呢?来看一个示例: func main(){ // 数组 array := new([5]int64) fmt.Printf("array: %p %#v \n", &array, array)// array: 0xc0000ae018 &[5]int64{0, 0, 0, 0, 0} (*array)[0] = 1 fmt.Printf("array: %p %#v \n", &array, array)// array: 0xc0000ae018 &[5]int64{1, 0, 0, 0, 0} // 切片 slice := new([]int64) fmt.Printf("slice: %p %#v \n", &slice, slice) // slice: 0xc0000ae028 &[]int64(nil) (*slice)[0] = 1 fmt.Printf("slice: %p %#v \n", &slice, slice) // panic: runtime error: index out of range [0] with length 0 // map map1 := new(map[string]string) fmt.Printf("map1: %p %#v \n", &map1, map1) // map1: 0xc00000e038 &map[string]string(nil) (*map1)["key"] = "value" fmt.Printf("map1: %p %#v \n", &map1, map1) // panic: assignment to entry in nil map // channel channel := new(chan string) fmt.Printf("channel: %p %#v \n", &channel, channel) // channel: 0xc0000ae028 (*chan string)(0xc0000ae030) channel <- "123" // Invalid operation: channel <- "123" (send to non-chan type *chan string) }从运行后果能够看出,咱们应用new函数分配内存后,只有数组在初始化后能够间接应用,slice、map、chan初始化后还是不能应用,会触发panic,这是因为slice、map、chan根本数据结构是一个struct,也就是说他外面的成员变量仍未进行初始化,所以他们初始化要应用make来进行,make会初始化他们的内部结构,咱们上面一节细说。还是回到struct初始化的问题上,先看一个例子: ...

May 19, 2021 · 3 min · jiezi

关于golang:进程内缓存助你提高并发能力

前言缓存,设计的初衷是为了缩小沉重的IO操作,减少零碎并发能力。不论是 CPU多级缓存,page cache,还是咱们业务中相熟的 redis 缓存,实质都是将无限的热点数据存储在一个存取更快的存储介质中。 计算机本身的缓存设计就是 CPU 采取多级缓存。那对咱们服务来说,咱们是不是也能够采纳这种多级缓存的形式来组织咱们的缓存数据。同时 redis 的存取都会通过网络IO,那咱们能不能把热点数据间接存在本过程内,由过程本人缓存一份最近最热的这批数据呢? 这就引出了咱们明天探讨的:local cache,本地缓存,也叫过程缓存。 本文带你一起探讨下 go-zero 中过程缓存的设计。Let’s go! 疾速入门作为一个过程存储设计,当然是 crud 都有的: 咱们先初始化 local cache// 先初始化 local cachecache, err = collection.NewCache(time.Minute, collection.WithLimit(10))if err != nil { log.Fatal(err)}其中参数的含意: expire:key对立的过期工夫CacheOption:cache设置。比方key的下限设置等根底操作缓存// 1. add/update 减少/批改都是该APIcache.Set("first", "first element")// 2. get 获取key下的valuevalue, ok := cache.Get("first")// 3. del 删除一个keycache.Del("first")Set(key, value) 设置缓存value, ok := Get(key) 读取缓存Del(key) 删除缓存高级操作cache.Take("first", func() (interface{}, error) { // 模仿逻辑写入local cache time.Sleep(time.Millisecond * 100) return "first element", nil})后面的 Set(key, value) 是单纯将 <key, value> 退出缓存;Take(key, setFunc) 则是在 key 对于的 value 不存在时,执行传入的 fetch 办法,将具体读取逻辑交给开发者实现,并主动将后果放到缓存里。 ...

May 19, 2021 · 2 min · jiezi

关于golang:goyacc学习1-Lex

一、参考golang学习系列目录——更新ing Lex: A Lexical Analyzer Generator 元字符:如何奇妙记忆正则表达式的根本元件? 二、根本过程2.1 名词列表名称阐明source用户输出的字符串input用户输出的字符串转为字节流automaton 自动机%%界定符,示意开始2.2 解析过程 2.3 语法规定 2.4 词法规定示例

May 18, 2021 · 1 min · jiezi

关于golang:Go并发编程的数据竞争问题

当两个或更多的操作必须以正确的程序执行时,就会呈现竞争状态,但如果程序没有写入,无奈使操作程序失去放弃。 大多数时候,这呈现在所谓的数据竞争中,其中一个并发操作尝试在某些未确定的工夫读取变量,而另一个并发操作尝试写入同一个变量。 数据竞争的产生条件是最隐秘的并发谬误类型之一,因为它们可能在代码投入生产后才会展示进去。 它们通常是由代码执行环境发生变化或前所未有的突发事件引起的。 在这些状况下,代码看起来行为正确,但实际上,这些操作按程序执行的呈现不确定性的几率十分高。 参考资料:并发编程的数据竞争问题以及解决之道Go并发的数据竞争

May 18, 2021 · 1 min · jiezi

关于golang:Go-syncPool-浅析

hi, 大家好,我是 haohongfan。 sync.Pool 应该是 Go 外面明星级别的数据结构,有很多优良的文章都在介绍这个构造,本篇文章简略分析下 sync.Pool。不过说实话 sync.Pool 并不是咱们日常开发中应用频率很高的的并发原语。 只管用的频率很低,然而不可否认的是 sync.Pool 的确是 Go 的杀手锏,正当应用 sync.Pool 会让咱们的程序性能飙升。本篇文章会从应用形式,源码分析,使用场景等方面,让你对 sync.Pool 有一个清晰的认知。 应用形式sync.Pool 应用很简略,然而想用对却很麻烦,因为你有可能看到网上一堆谬误的示例,各位同学在搜寻 sync.Pool 的应用例子时,要特地留神。 sync.Pool 是一个内存池。通常内存池是用来避免内存泄露的(例如C/C++)。sync.Pool 这个内存池却不是干这个的,带 GC 性能的语言都存在垃圾回收 STW 问题,须要回收的内存块越多,STW 持续时间就越长。如果能让 new 进去的变量,始终不被回收,失去反复利用,是不是就加重了 GC 的压力。 正确的应用示例(上面的demo选自gin) func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) { c := engine.pool.Get().(*Context) c.writermem.reset(w) c.Request = req c.reset() engine.handleHTTPRequest(c) engine.pool.Put(c)}肯定要留神的是:是先 Get 获取内存空间,基于这个内存做相干的解决,而后再将这个内存还回(Put)到 sync.Pool。 Pool 构造 源码图解 简略点能够总结成上面的流程: Sync.Pool 梳理Pool 的内容会清理?清理会造成数据失落吗?sync.Pool 会在每个 GC 周期内定期清理 sync.Pool 内的数据。定时清理数据并不会造成数据失落。 ...

May 18, 2021 · 2 min · jiezi

关于golang:缓存数据一致性-架构师峰会演讲实录

Previously缓存零碎波及的问题和知识点是比拟多的,我次要分为以下几个方面来跟大家探讨: 稳定性正确性可观测性标准落地和工具建设上篇 咱们剖析了缓存零碎的稳定性,介绍了 go-zero 是怎么解决缓存穿透、缓存击穿、缓存雪崩问题的。比拟浅显易懂,且具备比拟强的实战意义,举荐一读。 本文作为系列文章第二篇,次要跟大家探讨『缓存数据一致性』 缓存正确性 上篇文章提到,咱们引入缓存的初衷是为了减小DB压力,减少零碎稳定性,所以咱们一开始关注的是缓存零碎的稳定性。当稳定性解决之后,个别咱们就会面临数据正确性问题,可能会常常遇到『明明数据更新了,为啥还是显示老的呢?』这类问题。这就是咱们常说的『缓存数据一致性』问题了,接下来咱们认真下剖析其产生的起因及应答办法。 数据更新常见做法首先,咱们讲数据一致性的前提是咱们DB的更新和缓存的删除不会当成一个原子操作来对待,因为在高并发的场景下,咱们不可能引入一个分布式锁来把这两者绑定为一个原子操作,如果绑定的话就会很大水平上影响并发性能,而且减少零碎复杂度,所以咱们只会谋求数据的最终一致性,且本文只针对非谋求强一致性要求的高并发场景,金融领取等同学自行判断。 常见数据更新形式有两大类,其余根本都是这两类的变种: 先删缓存,再更新数据库 这种做法是遇到数据更新,咱们先去删除缓存,而后再去更新DB,如左图。让咱们来看一下整个操作的流程: A申请须要更新数据,先删除对应的缓存,还未更新DBB申请来读取数据B申请看到缓存里没有,就去读取DB并将旧数据写入缓存(脏数据)A申请更新DB能够看到B申请将脏数据写入了缓存,如果这是一个读多写少的数据,可能脏数据会存在比拟长的工夫(要么有后续更新,要么期待缓存过期),这是业务上不能承受的。 先更新数据库,再删除缓存 上图的右侧局部能够看到在A更新DB和删除缓存之间B申请会读取到老数据,因为此时A操作还没有实现,并且这种读到老数据的工夫是十分短的,能够满足数据最终一致性要求。 上图能够看到咱们用的是删除缓存,而不是更新缓存,起因如下图: 上图我用操作代替了删除或更新,当咱们做删除操作时,A先删还是B先删没有关系,因为后续读取申请都会从DB加载出最新数据;然而当咱们对缓存做的是更新操作时,就会对A先更新缓存还是B先更新缓存敏感了,如果A后更新,那么缓存里就又存在脏数据了,所以 go-zero 只应用删除缓存的形式。 咱们来一起看看残缺的申请解决流程: 留神:不同色彩代表不同申请。 申请1更新DB申请2查问同一个数据,返回了老的数据,这个短时间内返回旧数据是能够承受的,满足最终一致性申请1删除缓存申请3再来申请时缓存里没有,就会查询数据库,并回写缓存再返回后果后续的申请就会间接读取缓存了另外留一个问题大家能够思考下,对于下图的场景,咱们该怎么应答? 如果你有好的解决办法或者想晓得怎么解决,欢送 go-zero 社区微信群内交换,授人以鱼不如授人以渔,求解的过程必将让你播种更多~~ 未完待续本文跟大家一起探讨了缓存数据一致性问题,下一篇我来跟大家一起探讨缓存零碎的监控以及如何让缓存代码更标准、更少bug。 所有这些问题的解决办法都已蕴含在 go-zero 微服务框架里,如果你想要更好的理解 go-zero 我的项目,欢送返回官方网站上学习具体的示例。 视频回放地址ArchSummit架构师峰会-海量并发下的缓存架构设计 我的项目地址https://github.com/tal-tech/go-zero 欢送应用 go-zero 并 star 反对咱们! 微信交换群关注『微服务实际』公众号并点击 进群 获取社区群二维码。 go-zero 系列文章见『微服务实际』公众号

May 17, 2021 · 1 min · jiezi

关于golang:Golang-channel-的本质-Channels-orchestrate-mutexes-serialize

channel 是 Go 语言独有的一个个性,相比 goroutine 更加形象,也更加难以了解。毕竟后者能够类比线程、过程。《Go channels are bad and you should feel bad》 提及在应用 channel 和 mutex 时的困惑。其中提到过一个简略的程序,能够保留一场游戏的各个选手中的最高分。作者别离应用 channel 和 mutex 来实现该性能。 channel 版首先定义 Game 构造体: type Game struct { bestScore int scores chan int}bestScore 不会应用 mutex 爱护,而是应用一个独立的 goroutine 从 channel 接收数据,而后更新其状态。 func (g *Game) run() { for score := range g.scores { if g.bestScore < score { g.bestScore = score } }}而后定义构造函数来开始一场游戏 func NewGame() (g *Game) { g = &Game{ bestScore: 0, scores: make(chan int), } go g.run() return g}紧接着,定义 Player 接口返回该选手的分数,同时返回 error 用以示意 选手放弃较量等异常情况。 ...

May 17, 2021 · 2 min · jiezi

关于golang:CentOS-8-手动安装-Go-116-版本

Go 的装置次要是下载解压后设置门路。 其余都比较简单没有什么太大的问题。 Go 的下载地址为:Downloads - The Go Programming Language (golang.org) 须要到下面的地址中抉择本人 CPU 对应的版本。 运行命令进行下载 wget https://golang.org/dl/go1.16.... 而后运行命令将下载的文件解压到文件夹 /usr/local/go 中。 rm -rf /usr/local/go && tar -C /usr/local -xzf go1.16.4.linux-amd64.tar.gz 下一步就是设置门路了。 编辑文件:/etc/profile 在这个文件的最初增加一段话: export PATH=$PATH:/usr/local/go/bin 而后运行命令 source /etc/profile 使配置失效。 校验装置 运行命令行工具 source /etc/profile 来查看以后零碎装置的版本。 如果你能看到零碎的输入的话,阐明你的装置曾经实现了。 go 的装置还是比较简单的,当然你也能够应用 yum 来装置。 次要起因是版本太低,咱们的程序须要高点的版本,所以只能手动装置了。 https://www.ossez.com/t/cento...

May 16, 2021 · 1 min · jiezi

关于golang:Golang-通用后台权限管理系统-GoFunnyCMS

Golang 通用后盾权限管理系统 (Go-Funny-CMS ) 线上地址演示https://admin-go.surest.cn账号: surest明码: 123456预览 我的项目地址前端我的项目: https://github.com/Lets-Go-to...后端我的项目: https://github.com/Lets-Go-to... 我的项目简介是一个简略版本应用 Casbin + Golang 开发的通用后盾权限管理系统 我的项目构造参考了Laravel初始化目录构造,更加便于 phper 进行开发和学习 目前采纳的技术栈如下 golanggingorm(等)vue + design-vuecasbin采纳前后端拆散的开发方式 疾速装置# 后端我的项目> https://github.com/Lets-Go-together/go-funny-cms.git> cd go-funny-cms> 导入sql: backups/funy_cms_20210514_153117.sql.gz> cp .env .env.example> go run main.go # 或者> air# 前端我的项目> https://github.com/Lets-Go-together/go-funny-cms-front.git> cd go-funny-cms-front> yarn install> npm run dev配置邮件发送# 后盾运行> go run main.go express-run额定命令参考 pkg/command/command.go目前反对性能后盾账号治理用户权限管制主动权限路由生成RABC + ABC权限管制自定义管制菜单栏邮件发送与解决目录构造目前此零碎未集成什么性能,十分便于二次开发进行,目录构造清晰 - app :利用模块 (在次同级别目录,你能够同样创立app2目录) - http :api 接口操作相干 - admin : 依据利用内模块辨别 - controler : 控制器层 - validate : 对于reuqest 和 验证器都走这里 - index : 例如客户端api 模块 - 同上... - middleware : 用于中间件治理(可参考api 中间件的应用) - models : 模型 - service: 字如其名 (service层) - validates: 验证器的二次封装- ... 两头的没什么好介绍的- pkg : 自定义创立的一些包,便于二次开发和提取我的将来因为工夫的关系或者我集体的关系,须要去做一些更重要更值得做的事件,所以就草草的收尾了这个我的项目,欢送提出乏味的想法和见解,咱们一起来个思维碰撞,我也在致力于做一些本人的产品。 ...

May 15, 2021 · 1 min · jiezi

关于golang:如何成为一个更好的开发者

文章继续更新,微信搜一搜「 吴亲强的深夜食堂 」之前看过一篇文章工程师应该如何学习,预计很多人都看过了。最近刚好在思考一些事件,我也来写写。 真正的成功者是在练习中缓缓积攒起来的。 你在看大佬文章的时候,兴许曾经意识到了这一点。看到他们在做的一些事件,你就会想: 我不晓得这个人为了实现目标付出了多少致力。 编码也不例外。如果你想要成为一个顶尖的工程师,你必须时刻地练习你的技能。 我次要会这么做。 指标这是集体的信念,它兴许能疏导着我的毕生。无论是从集体的角度还是从职业生涯,咱们总是须要每个阶段为本人设置一个的指标,而后去干掉它。 比方,你能够: 创立一个你始终想要创立的应用程序。认真看完一本你想看的书并进行独立的输入和思考。学习一门本人感兴趣的语言,通过它实现一个性能也好,还是把之前老我的项目重写。列举公司我的项目或者本人我的项目中的缺点、痛点,通过思考、查阅材料、设计方案、落地欠缺零碎。(别和我吹牛你的零碎没问题)参加开源...... 定指标的时候,不要太过于形象,最好是一个可视化的指标。 另外设置指标的难度应该是超过以后能力范畴,然而这个幅度不能过大,否则会脱离实际意识,咱们就会称之为:劝退师。 如果指标很宏大,那么就须要进行拆解,就和咱们零碎模块一样,能够进行划分。指标也一样,能够进行拆分。设置一个总周期,那么相应的,阶段工作也就进去了。 更重要的是这个过程,过程才是指标的意义所在。后果只是意料之中的附属品。你永远也不晓得这个过程能给你带来什么?打通任督二脉也不是不可能。 有些话我还是挺喜爱的。种一棵树最好的工夫是十年前,其次是当初。 对于编程来说,空想是最没用的,肯定得通过一直的练习。另外,提早满足感是一件很重要的事件,当你感觉本人越来越菜的时候,可能是你越来越强的时候。 为什么是可能呢? 因为,兴许你是真的菜。 我记得初入职场的时候,遗记实现了一个什么功能性的货色,那时候感觉本人好牛逼,当初我只想说:粗率了。 输入在实现目标的同时,肯定会遇上本人不会的,从不会到会到熟练掌握存在一个过程,它也是一个个阶段。每一个阶段你都须要去总结剖析你所学到的技能,如何晓得本人的把握水平?那肯定是输入。输入的形式很多,比方: 单纯写 blog 给本人看,然而只存在本人的认知。社区写文章分享。帮忙他人的同时,可能有人能指出你的问题,或者提供了不一样的角度。通过把握的知识点写程序或者利用到理论我的项目中。组内分享。开源社区线下分享。(预计曾经是大佬级别了)多看书多浏览开源代码目前公众号内卷比较严重,加上你看他人的文章都是他人学习、排汇、总结的最终产物。对于某个不理解的知识点,还是须要先看一手的材料。在这个根底上,回头看作者的文章,兴许会有不一样的领会。 另外一篇文章因为篇幅限度或者侧重点的抉择,往往会比拟全面。这时候更应该看书了。你能吸动 <<TCP/IP详解>>的书,你看不懂他人写的阉割版 TCP/IP 文章? 至于源码,我感觉得从理论登程,比方我的项目中应用到的优良库或者最近工作波及到的技能想看他人的实现。整体架构?底层原理?设计细节?为什么要这样设计?只有理解底层原理,用起来能力更加舒心。从应用到原理,说不定前面有对应的需要,还会进行扩大,提交 PR,一举成为 Contributor。 总结编码不是彩票,并不能一夜暴富。它是一个迟缓的过程,如果你能每天保持向着指标学习,兴许短时间你看不到任何可视化的成果。然而一年,两年.....,当你回头想想一年前的本人是不是个傻逼,你就看到成长了,话糙理不糙。

May 14, 2021 · 1 min · jiezi

关于golang:iota-在-Go-中的使用

文章继续更新,微信搜一搜「 吴亲强的深夜食堂 」介绍Go 语言实际上没有间接反对枚举的关键字。个别咱们都是通过 const + iota 实现枚举的能力。 有人要问了,为什么肯定要应用枚举呢?stackoverflow 上有一个高赞的答复,如下: You should always use enums when a variable (especially a method parameter) can only take one out of a small set of possible values. Examples would be things like type constants (contract status: "permanent", "temp", "apprentice"), or flags ("execute now", "defer execution"). If you use enums instead of integers (or String codes), you increase compile-time checking and avoid errors from passing in invalid constants, and you document which values are legal to use.BTW, overuse of enums might mean that your methods do too much (it's often better to have several separate methods, rather than one method that takes several flags which modify what it does), but if you have to use flags or type codes, enums are the way to go.简略翻译一下, 两点很重要。 ...

May 14, 2021 · 3 min · jiezi

关于golang:Go语言Web编程怎么快速学习Go语言有哪些优势Go-Web编程有哪些优秀书籍

1.Go语言之前编程的痛点 (1)为什么会设计Go语言?         咱们先来理解一下Go的作者和次要外围开发者们:Robert Griesemer, Rob Pike 和 Ken Thompson。设计Go语言是为了解决过后Google开发遇到的以下这些问题: 大量的C++代码,同时又引入了Java和Python; 成千上万的工程师; 数以万计行的代码; 分布式的编译系统; 数百万的服务器;         其次要有以下几个方面的痛点: 编译慢; 失控的依赖; 每个工程师只是用了一个语言外面的一部分; 程序难以保护(可读性差、文档不清晰等); 更新的破费越来越长; 穿插编译艰难;         所以,他们过后设计Go的指标是为了打消各种迟缓和轻便、改良各种低效和扩展性。Go是由那些开发大型零碎的人设计的,同时也是为了这些人服务的;它是为了解决工程上的问题,不是为了钻研语言设计;它还是为了让咱们的编程变得更舒服和不便。         然而联合Google过后外部的一些现实情况,如很多工程师都是C系的,所以新设计的语言肯定要易学习,最好是C-like的语言;因为有太多的分布式系统、太多的开发者,所以新的语言肯定要能够Scale,这个包含开发、工程师、代码、部署和依赖;20年没有出新的语言了,所以新设计的语言必须是现代化的(例如内置GC)等状况,他们感觉要实现这个指标就须要Go成为一个大家都认可的语言。         最初依据实战经验,他们向着指标设计了Go这个语言,其次要的特色有: 没有继承的OO; 强统一类型; Interface然而不须要显示申明(Duck Type); Function 和Method; 没有异样解决(Error is value); 基于首字母的可拜访个性; 不必的Import或者变量引起编译谬误; 残缺而卓越的规范库包;         Go公布之后,很多公司特地是云计算公司开始用Go重构他们的基础架构,很多都是间接采纳Go进行了开发。这几年火到爆的Docker、Kubernetes就是采纳Go开发的。咱们来看看目前为止采纳Go的一些国内外公司,国外的如Google、Docker、Apple、Cloud Foundry、CloudFlare、Couchbase、CoreOS、Dropbox、MongoDB、AWS等公司,国内的如阿里云CDN、百度、小米、七牛云、PingCAP、华为、金山软件、猎豹挪动、饿了么等公司。 (2)Go次要利用的零碎。         下面那些基本上就是Go的历史背景和设计初衷,那么目前Go次要利用于哪些零碎呢?         目前Go次要利用在上面这些零碎: ...

May 13, 2021 · 3 min · jiezi

关于golang:如何让消息队列达到最大吞吐量

你在应用音讯队列的时候关注过吞吐量吗? 思考过吞吐量的影响因素吗? 思考过怎么进步吗? 总结过最佳实际吗? 本文带你一起探讨下音讯队列生产端高吞吐的 Go 框架实现。Let’s go! 对于吞吐量的一些思考写入音讯队列吞吐量取决于以下两个方面 网络带宽音讯队列(比方Kafka)写入速度最佳吞吐量是让其中之一打满,而个别状况下内网带宽都会十分高,不太可能被打满,所以天然就是讲音讯队列的写入速度打满,这就就有两个点须要均衡 批量写入的音讯量大小或者字节数多少提早多久写入go-zero 的 PeriodicalExecutor 和 ChunkExecutor 就是为了这种状况设计的 从音讯队列里生产音讯的吞吐量取决于以下两个方面 音讯队列的读取速度,个别状况下音讯队列自身的读取速度相比于解决音讯的速度都是足够快的处理速度,这个依赖于业务这里有个外围问题是不能不思考业务处理速度,而读取过多的音讯到内存里,否则可能会引起两个问题: 内存占用过高,甚至呈现OOM,pod 也是有 memory limit 的进行 pod 时沉积的音讯来不及解决而导致音讯失落解决方案和实现 借用一下 Rob Pike 的一张图,这个跟队列生产殊途同归。右边4个 gopher 从队列里取,左边4个 gopher 接过去解决。比拟现实的后果是右边和左边速率基本一致,没有谁节约,没有谁期待,两头替换处也没有沉积。 咱们来看看 go-zero 是怎么实现的: Producer 端 for { select { case <-q.quit: logx.Info("Quitting producer") return default: if v, ok := q.produceOne(producer); ok { q.channel <- v } } }没有退出事件就会通过 produceOne 去读取一个音讯,胜利后写入 channel。利用 chan 就能够很好的解决读取和生产的连接问题。 Consumer 端 for { select { case message, ok := <-q.channel: if ok { q.consumeOne(consumer, message) } else { logx.Info("Task channel was closed, quitting consumer...") return } case event := <-eventChan: consumer.OnEvent(event) } }这里如果拿到音讯就去解决,当 ok 为 false 的时候示意 channel 已被敞开,能够退出整个解决循环了。同时咱们还在 redis queue 上反对了 pause/resume,咱们原来在社交场景里大量应用这样的队列,能够告诉 consumer 暂停和持续。 ...

May 13, 2021 · 2 min · jiezi

关于golang:Go-语言-标签-的使用

goto 不记录之前的状态,每次进入循环从新开始计算 LABLE1:for i := 0; i < 5; i++ { for i := 0; i < 5; i++ { if j == 3 { goto LABLE1 } fmt.Println("i:", i, ", j:", j) } fmt.Println("over!")}continue 会记录之前的状态 LABLE1:for i := 0; i < 5; i++ { for i := 0; i < 5; i++ { if j == 3 { continue LABLE1 } fmt.Println("i:", i, ", j:", j) } fmt.Println("over!")}break 间接跳出指定地位的循环 ...

May 12, 2021 · 1 min · jiezi

关于golang:Go语言实现文件的断点续传

本文次要简略实现一个发送文件的端点续传性能,次要解决在传输过程中客户端断开后在从新获得连贯后可在之前的传输根底上进行持续传输,直到文件传输结束。 客户端package mainimport ( "fmt" "io" "net" "os" "strconv" "time")func clientRead(conn net.Conn) int{ buf := make([]byte, 10) n, err := conn.Read(buf) if err != nil { fmt.Println("conn.Read err:", err) } off, err := strconv.Atoi(string(buf[:n])) if err != nil { fmt.Println("strconv.Atoi err:", err) } return off}//发送数据func clientWrite(conn net.Conn, data []byte){ _, err := conn.Write(data) if err != nil { fmt.Println("conn.Write err:", err) } fmt.Println("写入数据:", string(data))}func clientConn(conn net.Conn) { defer conn.Close() clientWrite(conn, []byte("start-->")) off := clientRead(conn) fp, err := os.OpenFile("file.txt", os.O_RDONLY, 0777) if err != nil { fmt.Println("os.OpenFile err:", err) } defer fp.Close() _, err = fp.Seek(int64(off), 0) if err != nil { fmt.Println("Seek err:", err) } for{ data := make([]byte, 10) n, err := fp.Read(data) if err != nil { if err == io.EOF{ time.Sleep(time.Second) //clientWrite(conn, []byte("<--end")) fmt.Println("文件发送完结!") break } } //time.Sleep(time.Second) clientWrite(conn, data[:n]) }}func main(){ conn, err := net.DialTimeout("tcp", "127.0.0.1:8848", time.Second*10) //conn, err := net.Dial("tcp", "127.0.0.1:8848") if err != nil { fmt.Println("Dial err:", err) } clientConn(conn)}服务端package mainimport ( "fmt" "io" "net" "os" "strconv")//追加func WriteFile(content []byte){ if len(content) > 0{ fp, err := os.OpenFile("file_out.txt", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0777) if err != nil { fmt.Println("OpenFile err:", err) } defer fp.Close() _, err = fp.Write(content) if err != nil { fmt.Println("fp.Write err:", err) } fmt.Println("fp.Write ok") }}//判断文件是否存在func getFileState() int64{ stat, err := os.Stat("D:\\GoObject\\Gocode\\端点续传\\clinet\\file_ob.txt") if err != nil { if os.IsNotExist(err){ fmt.Println("文件不存在") return 0 } } return stat.Size()}func serverConn(conn net.Conn){ defer conn.Close() for true { var buf = make([]byte, 10) n, err := conn.Read(buf) if err != nil { if err == io.EOF{ fmt.Println("server is EOF") return } fmt.Println("conn.read err:", err) return } fmt.Println("收到数据:", string(buf[:n])) switch string(buf[:n]) { case "start-->": off := getFileState() stroff := strconv.FormatInt(off, 10) _, err := conn.Write([]byte(stroff)) if err != nil { fmt.Println("conn.Write err:", err) } continue //case "<--end": // fmt.Println("文件写入结束!") // return } WriteFile(buf[:n]) }}func main(){ listen, err := net.Listen("tcp", "127.0.0.1:8848") if err != nil { fmt.Println("net.Listen err:", err) } fmt.Println("正在监听...") defer listen.Close() conn, err := listen.Accept() if err != nil { fmt.Println("Accept err:", err) } serverConn(conn)}

May 12, 2021 · 2 min · jiezi

关于golang:golang如何使用原生RPC及微服务简述

[TOC] golang如何应用原生RPC及微服务简述微服务1. 微服务是什么应用一套小服务来开发单个利用的形式,每个服务运行在独立的过程里,个别采纳轻量级的通信机制互联,并且它们能够通过自动化的形式部署微服务是设计思维,不是量的体现 专一的性能代码量并不少架构变简单2. 特点是啥专一的职责,例如专一于权限治理轻量级的通信,通信与平台和语言无关,例如http是轻量的隔离性,数据隔离有本人的数据技术多样 3. 微服务架构的劣势独立性使用者容易了解技术栈灵便高效团队4. 微服务架构的有余额定的工作,服务的拆分保证数据一致性减少了沟通老本微服务生态1. 硬件层用docker+k8s去解决2. 通信层网络传输,用RPC(近程过程调用) HTTP传输,GET POST PUT DELETE基于TCP,更靠底层,RPC基于TCP,Dubbo(18年底改成反对各种语言),Grpc,Thrift须要晓得调用谁,用服务注册和发现 须要分布式数据同步:etcd,consul,zk数据传递这外面可能是各种语言,各种技术,各种传递数据传输协定选型倡议1、对于公司间的零碎调用,如果性能要求在100ms以上的服务,基于XML的SOAP协定是一个值得思考的计划。 2、对于调试环境比拟顽劣的场景,采纳JSON或XML可能极大的进步调试效率,升高零碎开发成本。 3、当对性能和简洁性有极高要求的场景,Protobuf,Thrift,Avro之间具备肯定的竞争关系。 4、对于T级别的数据的长久化利用场景,Protobuf和Avro是首要抉择。如果长久化后的数据存储在Hadoop子项目里,Avro会是更好的抉择。 5、如果须要提供一个残缺的RPC解决方案,Thrift是一个好的抉择。 6、如果序列化之后须要反对不同的传输层协定,或者须要跨防火墙拜访的高性能场景,Protobuf能够优先思考。 RPC 机制和实现过程1. RPC机制服务间通过轻量级的近程过程调用,个别应用HTTP,RPC HTTP调用应用层协定,构造绝对固定RPC的网络协议就绝对灵便,并且能够定制RPC近程过程调用,个别采纳C/S 模式,客户端服务器模式,客户端过程,调用服务端过程的程序,服务端过程执行后果返回给客户端,客户端从阻塞状态被唤醒,接收数据,提取数据。 上述过程中,客户端调用服务器的函数,来执行工作,它不晓得操作是在本地操作系统进行,还是通过近程过程调用进行的,全程无感。 RPC的根本通信如下: RPC近程过程调用,须要思考的问题有如下四点: 参数传递通信协议机制出错解决超时解决2. 参数传递值传递个别默认是值传递,只须要将参数中的值复制到网络音讯中的数据中即可 援用传递比拟艰难,单纯传递参数的援用是齐全没有用意义的,因为援用的地址给到远端的服务器,服务器上的该内存地址齐全不是客户端想要的数据,若非要这样解决,客户端还必须把数据的正本传递给到远端服务器,并将它们放到远端服务器内存中,服务器复制援用的地址后,即可进行数据的读取。 可是上述做法很麻烦,且很容易出错,个别RPC不反对间接传递援用 数据格式对立问题须要有一个规范来对所有数据类型进行编解码 ,数据格式能够有隐式类型和显式类型 隐式类型只传递值,不传递变量的名称或 类型 显式类型传递字段的类型和值 常见的传输数据格式有: ISO规范的ASN.1JSONPROTOBUFXML3. 通信协议机制狭义上的协定栈分为共有协定和公有协定 共有协定例如HTTP,SMPP,WEBSERVICE都是共有协定,领有通用型上,公网传输的能力上 有劣势 公有协定外部约定而成的协定,弊病多,然而能够高度的定制化,晋升性能,降低成本,进步灵活性和效率。企业外部往往采纳公有协定开发 对于协定的制订须要思考如下5个方面: 协定设计须要思考哪些问题 公有协定的编解码须要有业务针对性的编解码形式办法,如下有案例 命令的定义和命令处理器的抉择协定的过程个别会有2种 负载命令传输业务具体的数据,如申请参数,响应后果的命令 管制命令个别为性能治理命令,如心跳命令等 命令的协定个别是应用序列化协定,不同的协定在编码效率和传输效率上都不雷同,如 通信模式oneway -- 不关怀响应,申请线程不会被阻塞sync -- 调用会被阻塞,晓得返回后果为止future -- 调用时不会阻塞县线程,获取后果的时候会阻塞线程callback -- 异步调用,不会阻塞线程出错解决和超时解决 近程过程调用绝对本地过程调用出错的概率更大,因而须要思考到调用失败的各种场景: 服务端出错,须要如何解决客户端申请服务时候呈现谬误或者超时,须要设置适合的重试机制 4. 繁难GO语言原生RPC大略分为如下4个步骤: 设计数据结构和办法实现办法注册服务客户端连贯服务端,调用服务端的办法 往下看有golang如何应用原生rpc的案例 rpc调用和服务监控RPC相干内容 ...

May 12, 2021 · 6 min · jiezi

关于golang:Go-语言中的一等公民看似普通的函数凭什么

若有任何问题或倡议,欢送及时交换和碰撞。我的公众号是 【脑子进煎鱼了】,GitHub 地址:https://github.com/eddycjy 。大家好,我是煎鱼。 在 Go 语言中,一提函数,大家提的最多的就是 “Go 语言的函数是一等公民”。这个定义来的十分忽然,咱们先理解一下什么是一等公民。 依据维基百科的一等公民(First-class citizen)的定义: In programming language design, a first-class citizen (also type, object, entity, or value) in a given programming language is an entity which supports all the operations generally available to other entities. These operations typically include being passed as an argument, returned from a function, modified, and assigned to a variable.在编程语言设计中,给定编程语言中的一等公民(也就是类型,对象,实体或值)能够把函数赋值给变量,也能够把函数作为其它函数的参数或者返回值来间接应用。 Go 语言的函数也满足这个定义,因而常被称为 “一等公民”。理解分明背景后,接下来进一步开展。 一般函数在 Go 语言中一般函数的定义格局为 func [函数名](入参)(出参),如下: ...

May 12, 2021 · 2 min · jiezi

关于golang:有趣的面试题Go语言字符串的字节长度和字符个数

背景哈喽,大家好,我是asong。明天咱们一起来看看Go语言中的rune数据类型,首先从一道面试题动手,你能很快说出上面这道题的答案吗?func main() { str := "Golang梦工厂" fmt.Println(len(str)) fmt.Println(len([]rune(str)))}运行后果是15和15还是15和9呢?先思考一下,一会揭晓答案。 其实这并不是一道面试题,是我在日常开发中遇到的一个问题,过后场景是这样的:后端要对前端传来的字符串做字符校验,产品的需要是限度为200字符,而后我在后端做校验时间接应用len(str) > 200来做判断,后果呈现了bug,前端字符校验没有超过200字符,调用后端接口确始终是参数谬误,改成应用len([]rune(str)) > 200胜利解决了这个问题。具体起因咱们在文中揭晓。 Unicode和字符编码在介绍rune类型之前,咱们还是要从一些基础知识开始。 ------ Unicode和字符编码。 什么是Unicode?咱们都晓得计算机只能解决数字,如果想要解决文本须要转换为数字能力解决,早些时候,计算机在设计上采纳8bit作为一个byte,一个byte示意的最大整数就是255,想示意更大的整数,就须要更多的byte。显然,一个字节示意中文,是不够的,至多须要两个字节,而且还不能和ASCII编码抵触,所以,我国制订了GB2312编码,用来把中文编进去。然而世界上有很多语言,不同语言制订一个编码,就会不可避免地呈现抵触,所以unicode字符就是来解决这个痛点的。Unicode把所有语言都对立到一套编码里。总结来说:"unicode其实就是对字符的一种编码方式,能够了解为一个字符---数字的映射机制,利用一个数字即可示意一个字符。" 什么是字符编码?尽管unicode把所有语言对立到一套编码里了,然而他却没有规定字符对应的二进制码是如何存储。以汉字“汉”为例,它的 Unicode 码点是 0x6c49,对应的二进制数是 110110001001001,二进制数有 15 位,这也就阐明了它至多须要 2 个字节来示意。能够设想,在 Unicode 字典中往后的字符可能就须要 3 个字节或者 4 个字节,甚至更多字节来示意了。 这就导致了一些问题,计算机怎么晓得你这个 2 个字节示意的是一个字符,而不是别离示意两个字符呢?这里咱们可能会想到,那就取个最大的,如果 Unicode 中最大的字符用 4 字节就能够示意了,那么咱们就将所有的字符都用 4 个字节来示意,不够的就往前面补 0。这样的确能够解决编码问题,然而却造成了空间的极大节约,如果是一个英文文档,那文件大小就大出了 3 倍,这显然是无奈承受的。 于是,为了较好的解决 Unicode 的编码问题, UTF-8 和 UTF-16 两种以后比拟风行的编码方式诞生了。UTF-8 是目前互联网上应用最宽泛的一种 Unicode 编码方式,它的最大特点就是可变长。它能够应用 1 - 4 个字节示意一个字符,依据字符的不同变换长度。在UTF-8编码中,一个英文为一个字节,一个中文为三个字节。 Go语言中的字符串基本概念先来看一下官网对string的定义: // string is the set of all strings of 8-bit bytes, conventionally but not// necessarily representing UTF-8-encoded text. A string may be empty, but// not nil. Values of string type are immutable.type string string人工翻译: ...

May 11, 2021 · 2 min · jiezi

关于golang:Go-面试题Go-interface-的一个-坑-及原理分析

若有任何问题或倡议,欢送及时交换和碰撞。我的公众号是 【脑子进煎鱼了】,GitHub 地址:https://github.com/eddycjy。大家好,我是煎鱼。 前几天在读者交换群里看到一位小伙伴,针对 interface 的应用有了比拟大的纳闷。 独一无二,我也在网上看到有小伙伴在 Go 面试的时候被问到了: 明天特意分享进去让大家避开这个坑。 例子一第一个例子,如下代码: func main() { var v interface{} v = (*int)(nil) fmt.Println(v == nil)}你感觉输入后果是什么呢? 答案是: false为什么不是 true。明明都曾经强行置为 nil 了。是不是 Go 编译器有问题? 例子二第二个例子,如下代码: func main() { var data *byte var in interface{} fmt.Println(data, data == nil) fmt.Println(in, in == nil) in = data fmt.Println(in, in == nil)}你感觉输入后果是什么呢? 答案是: <nil> true<nil> true<nil> false这可就更奇怪了,为什么刚刚申明进去的 data 和 in 变量,的确是输入后果是 nil,判断后果也是 true。 怎么把变量 data 一赋予给变量 in,世界就变了?输入后果仍然是 nil,但断定却变成了 false。 ...

May 11, 2021 · 1 min · jiezi

关于golang:golang-反射原理

本文章次要解说一下reflect包中TypeOf和ValueOf两个函数的工作原理。TypeOf在 Go语言中通过调用 reflect.TypeOf 函数,咱们能够从一个任何非接口类型的值创立一个 reflect.Type 值。reflect.Type 值示意着此非接口值的类型。通过此值,咱们能够失去很多此非接口类型的信息。当然,咱们也能够将一个接口值传递给一个 reflect.TypeOf 函数调用,然而此调用将返回一个示意着此接口值的动静类型的 reflect.Type 值。 实际上,reflect.TypeOf 函数的惟一参数的类型为 interface{},reflect.TypeOf 函数将总是返回一个示意着此惟一接口参数值的动静类型的 reflect.Type 值。TypeOf 的源码如下 type eface struct{ _type *_type data unsafe.Pointer}type emptyInterface struct{ typ *rtype word unsafe.Pointer}func TypeOf (i interface()) Type{ eface := *(* emptyInterface)(unsafe.Pointer(&i)) //&i 是eface 类型 return toType(eface.typ)}这里是反射的一些简略利用 package mainimport "fmt"import "reflect"type Eggo struct{ name string}func (e Eggo) A(){ fmt.Println(e.name)}func main() { a := Eggo{name:"eggo"} t:=reflect.TypeOf(a) fmt.Println(t.NumMethod(),t.Kind(),t)}输入如下:1 struct main.Eggo。t.Kind()会返回golang原始类型,原始类型string,struct等等 package mainimport "fmt"import "reflect"import "encoding/json"type Stu struct { Name string `json:"name"` Age int HIgh bool sex string Class *Class `json:"class"`}type Class struct { Name string Grade int}type Eggo struct{ Name string}func (e Eggo) A(){ fmt.Println(e.Name)}func main() { /* stu := Stu{ Name: "张三", Age: 18, HIgh: true, sex: "男", } //指针变量 cla := new(Class) cla.Name = "1班" cla.Grade = 3 stu.Class=cla //Marshal失败时err!=nil jsonStu, err := json.Marshal(stu) if err != nil { fmt.Println("生成json字符串谬误") } //jsonStu是[]byte类型,转化成string类型便于查看 fmt.Println(string(jsonStu))*/ a := Eggo{Name:"eggo"} b := &Eggo{} bdata, err:= json.Marshal(a) if err != nil { fmt.Println("生成json字符串谬误") } t:=reflect.TypeOf(a) fmt.Println(string(bdata)) aa := reflect.New(t).Interface() fmt.Println("...........",t,aa,b) json.Unmarshal(bdata, aa) fmt.Println("reflect-get-name",(aa).(*Eggo).Name)}输入如下:{"Name":"eggo"}........... main.Eggo &{} &{}reflect-get-name eggo。依据反射解析json。cellnet应用的就是这种形式编码和解码 ...

May 11, 2021 · 1 min · jiezi

关于golang:logrus-源码解析

logrus 源码剖析logrus 个性齐全兼容Go规范库日志模块。logrus领有六种日志级别:debug、info、warn、error、fatal和panic,这是Go规范库日志模块的API的超集。如果你的我的项目应用规范库日志模块,齐全能够用最低的代价迁徙到logrus上。可扩大的Hook机制。容许使用者通过hook形式,将日志散发到任意中央,如本地文件系统、规范输入等。可选的日志输入格局。logrus内置了两种日志格局,JSONFormatter和TextFormatter。如果这两个格局不满足需要,能够本人入手实现接口Formatter,来定义本人的日志格局。Field机制。logrus激励通过Field机制进行精细化、结构化的日志记录,而不是通过简短的音讯来记录日志。logrus是一个可插拔的、结构化的日志框架。logrus 源码构造logrus 对外提供实现的接口(exported.go), 提供的接口默认应用的stderr输入,TextFormater格局。 var ( // std is the name of the standard logger in stdlib `log` std = New())// SetOutput sets the standard logger output.func SetOutput(out io.Writer) { std.SetOutput(out)}// SetFormatter sets the standard logger formatter.func SetFormatter(formatter Formatter) { std.SetFormatter(formatter)}// SetReportCaller sets whether the standard logger will include the calling// method as a field.func SetReportCaller(include bool) { std.SetReportCaller(include)}// SetLevel sets the standard logger level.func SetLevel(level Level) { std.SetLevel(level)}// GetLevel returns the standard logger level.func GetLevel() Level { return std.GetLevel()}func IsLevelEnabled(level Level) bool { return std.IsLevelEnabled(level)}// AddHook adds a hook to the standard logger hooks.func AddHook(hook Hook) { std.AddHook(hook)}// WithError creates an entry from the standard logger and adds an error to it, using the value defined in ErrorKey as key.func WithError(err error) *Entry { return std.WithField(ErrorKey, err)}// WithContext creates an entry from the standard logger and adds a context to it.func WithContext(ctx context.Context) *Entry { return std.WithContext(ctx)}// WithField creates an entry from the standard logger and adds a field to// it. If you want multiple fields, use `WithFields`.//// Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal// or Panic on the Entry it returns.func WithField(key string, value interface{}) *Entry { return std.WithField(key, value)}// WithFields creates an entry from the standard logger and adds multiple// fields to it. This is simply a helper for `WithField`, invoking it// once for each field.//// Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal// or Panic on the Entry it returns.func WithFields(fields Fields) *Entry { return std.WithFields(fields)}// WithTime creates an entry from the standard logger and overrides the time of// logs generated with it.//// Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal// or Panic on the Entry it returns.func WithTime(t time.Time) *Entry { return std.WithTime(t)}// Trace logs a message at level Trace on the standard logger.func Trace(args ...interface{}) { std.Trace(args...)}// Debug logs a message at level Debug on the standard logger.func Debug(args ...interface{}) { std.Debug(args...)}// Print logs a message at level Info on the standard logger.func Print(args ...interface{}) { std.Print(args...)}// Info logs a message at level Info on the standard logger.func Info(args ...interface{}) { std.Info(args...)}// Warn logs a message at level Warn on the standard logger.func Warn(args ...interface{}) { std.Warn(args...)}// Warning logs a message at level Warn on the standard logger.func Warning(args ...interface{}) { std.Warning(args...)}// Error logs a message at level Error on the standard logger.func Error(args ...interface{}) { std.Error(args...)}// Panic logs a message at level Panic on the standard logger.func Panic(args ...interface{}) { std.Panic(args...)}// Fatal logs a message at level Fatal on the standard logger then the process will exit with status set to 1.func Fatal(args ...interface{}) { std.Fatal(args...)}// TraceFn logs a message from a func at level Trace on the standard logger.func TraceFn(fn LogFunction) { std.TraceFn(fn)}// DebugFn logs a message from a func at level Debug on the standard logger.func DebugFn(fn LogFunction) { std.DebugFn(fn)}// PrintFn logs a message from a func at level Info on the standard logger.func PrintFn(fn LogFunction) { std.PrintFn(fn)}// InfoFn logs a message from a func at level Info on the standard logger.func InfoFn(fn LogFunction) { std.InfoFn(fn)}// WarnFn logs a message from a func at level Warn on the standard logger.func WarnFn(fn LogFunction) { std.WarnFn(fn)}// WarningFn logs a message from a func at level Warn on the standard logger.func WarningFn(fn LogFunction) { std.WarningFn(fn)}// ErrorFn logs a message from a func at level Error on the standard logger.func ErrorFn(fn LogFunction) { std.ErrorFn(fn)}// PanicFn logs a message from a func at level Panic on the standard logger.func PanicFn(fn LogFunction) { std.PanicFn(fn)}// FatalFn logs a message from a func at level Fatal on the standard logger then the process will exit with status set to 1.func FatalFn(fn LogFunction) { std.FatalFn(fn)}// Tracef logs a message at level Trace on the standard logger.func Tracef(format string, args ...interface{}) { std.Tracef(format, args...)}// Debugf logs a message at level Debug on the standard logger.func Debugf(format string, args ...interface{}) { std.Debugf(format, args...)}// Printf logs a message at level Info on the standard logger.func Printf(format string, args ...interface{}) { std.Printf(format, args...)}// Infof logs a message at level Info on the standard logger.func Infof(format string, args ...interface{}) { std.Infof(format, args...)}// Warnf logs a message at level Warn on the standard logger.func Warnf(format string, args ...interface{}) { std.Warnf(format, args...)}// Warningf logs a message at level Warn on the standard logger.func Warningf(format string, args ...interface{}) { std.Warningf(format, args...)}// Errorf logs a message at level Error on the standard logger.func Errorf(format string, args ...interface{}) { std.Errorf(format, args...)}// Panicf logs a message at level Panic on the standard logger.func Panicf(format string, args ...interface{}) { std.Panicf(format, args...)}// Fatalf logs a message at level Fatal on the standard logger then the process will exit with status set to 1.func Fatalf(format string, args ...interface{}) { std.Fatalf(format, args...)}// Traceln logs a message at level Trace on the standard logger.func Traceln(args ...interface{}) { std.Traceln(args...)}// Debugln logs a message at level Debug on the standard logger.func Debugln(args ...interface{}) { std.Debugln(args...)}// Println logs a message at level Info on the standard logger.func Println(args ...interface{}) { std.Println(args...)}// Infoln logs a message at level Info on the standard logger.func Infoln(args ...interface{}) { std.Infoln(args...)}// Warnln logs a message at level Warn on the standard logger.func Warnln(args ...interface{}) { std.Warnln(args...)}// Warningln logs a message at level Warn on the standard logger.func Warningln(args ...interface{}) { std.Warningln(args...)}// Errorln logs a message at level Error on the standard logger.func Errorln(args ...interface{}) { std.Errorln(args...)}func Panicln(args ...interface{}) { std.Panicln(args...)}func Fatalln(args ...interface{}) { std.Fatalln(args...)}如果想要输入日志到文件须要SetOutput 设置文件输入file。接下来次要解析三个接口: ...

May 11, 2021 · 9 min · jiezi

关于golang:Google-怎么解决长尾延迟问题

The Tail at Scale,是 Google 2013 年发表的一篇论文,大规模在线服务的长尾提早问题。 要晓得怎么解决长尾问题,先要了解长尾提早是个什么问题,在开发在线服务的时候,咱们都晓得要关注服务的 p99/p999 提早,要让大部分用户都可能在预期的工夫范畴内取得响应。 上面是一个不同响应工夫的申请数分布图: 大部分零碎也都遵循这种散布法则,当初互联网的零碎规模比拟大,一个服务依赖几十上百个服务的状况都是有可能的。繁多模块的长尾提早会在有大量依赖的状况下,在服务粒度被放大,《The Tail at Scale》论文里给出了这样的例子。 思考一个零碎,大部分服务调用在 10ms 内响应,但 99 分位数的提早为 1 秒。如果一个用户申请只在一个这样的服务上解决,那么 100 个用户申请中只有一个会很慢(一秒钟)。这里的图表概述了在这种假如的状况下,服务级别的提早是如何被十分小概率的大提早值影响的。 如果一个用户申请必须从100个这样的服务并行收集响应,那么 63% 的用户申请将须要超过一秒钟(图中标记为 "x")。即便对于只有万分之一概率在单台服务器上遇到超过一秒的响应提早的服务,如果服务的规模达到 2000 实例的话,也会察看到简直五分之一的用户申请须要超过一秒(图中标记为 "o")。Xargin 注: 因为申请是并行的,所以只受最慢的那个影响,100 个申请都落在 99 分位内提早才会小于 1s,所以提早小于 1s 的概率是 pow(0.99, 100) = 0.3660323412732292,也就是说肯定有 63% 的概率会超过 1s。第二个,咱们落在 99 分位的概率是 pow(0.9999, 2000) = 0.8187225652655495,也就是将近 20% 的用户响应会超过 1s。 下面的表格列出了一个实在的谷歌服务的测量后果,该服务在逻辑上与前文简化过的场景类似;根服务通过两头服务将一个申请散发到十分多的叶子服务。表展现了大量扇出(fan-out)调用时,对提早散布的影响。 在根服务器上测量的单个随机申请实现的 99 分位提早是 10ms。然而,所有申请实现的 99 分位数提早是 140ms,95% 的申请 99 分位数提早是 70ms,这意味着期待最慢的 5% 的慢申请要对总的 99 百分位数提早的一半负责。对这些慢申请场景进行优化,会对服务的整体提早产生微小影响。 ...

May 11, 2021 · 1 min · jiezi

关于golang:plan9-assembly-完全解析

这篇文章国内钻研 Go 底层的人应该都看过,筹备去学习 runtime 的你也应该读一读。 家喻户晓,Go 应用了 Unix 老古董(误 们创造的 plan9 汇编。就算你对 x86 汇编有所理解,在 plan9 里还是有些许区别。说不定你在看代码的时候,偶尔发现代码里的 SP 看起来是 SP,但它实际上不是 SP 的时候就抓狂了哈哈哈。 本文将对 plan9 汇编进行全面的介绍,同时解答你在接触 plan9 汇编时可能遇到的大部分问题。 本文所应用的平台是 linux amd64,因为不同的平台指令集和寄存器都不一样,所以没有方法独特探讨。这也是由汇编自身的性质决定的。 根本指令栈调整intel 或 AT&T 汇编提供了 push 和 pop 指令族,plan9 中没有 push 和 pop,plan9 中尽管有 push 和 pop 指令,但个别生成的代码中是没有的,咱们看到的栈的调整大多是通过对硬件 SP 寄存器进行运算来实现的,例如: SUBQ $0x18, SP // 对 SP 做减法,为函数调配函数栈帧... // 省略无用代码ADDQ $0x18, SP // 对 SP 做加法,革除函数栈帧通用的指令和 X64 平台差不多,上面分节详述。 数据搬运常数在 plan9 汇编用 $num 示意,能够为正数,默认状况下为十进制。能够用 $0x123 的模式来示意十六进制数。 ...

May 11, 2021 · 12 min · jiezi

关于golang:Go-语言-switch-的使用

switch 接管命令行参数 package mainimport ( "fmt" "os")func main() { // os.Args[0] 为程序带门路的名称 // os.Args[1] 为第一个参数 args := os.Args if len(args) < 2 { fmt.Println("param error.") } switch args[1] { case "hello": fmt.Println("hello") case "world": fmt.Println("world") default: fmt.Println("default") }}测试 go build -o test.exe switch.go./test.exe hello world wu test

May 10, 2021 · 1 min · jiezi

关于golang:golang中syncWaitGroup的使用

WaitGroup是罕用的同步阻塞期待对象。WaitGroup能够调度goroutinue。首先调用同步期待组的Add(num)其中num为正整数,num会增加到WaitGroup中的counter数据里, 而后再协程外部调用Done() ,Done() 办法调用的是Add(-1),counter值会缩小,所有执行wg.Wait()的goroutinue在counter不等于0时都会被梗塞。等于0时那些goroutinue不再阻塞代码如下。上面的代码中有两个执行wg.Wait()的goroutinue。 func main() { var wg = sync.WaitGroup{} wg.Add(1) go func() { wg.Done() }() go func() { wg.Wait() fmt.Println("Hi1") }() wg.Wait() fmt.Println("Hi2")}然而因为goroutinue调度起因下面的代码的输入不惟一程序和数量都可能不一样。具体起因能够查看golang中GMP的调度。

May 10, 2021 · 1 min · jiezi

关于golang:缓存系统稳定性-架构师峰会演讲实录

前言大家好!我是万俊峰,go-zero 作者。感激 ArchSummit 提供这么好的机会来跟大家分享一下go-zero的缓存最佳实际。 首先,大家能够想一想:咱们在流量激增的状况下,服务端哪个局部最有可能会是第一个瓶颈?我置信大部分人遇到的都会是数据库首先扛不住,量一起来,数据库慢查问,甚至卡死。此时,下层服务有怎么强的治理能力都是杯水车薪的。 所以咱们常说看一个零碎架构设计的好不好,很多时候看看缓存设计的如何就晓得了。咱们已经遇到过这样的问题,在我退出之前,咱们的服务是没有缓存的,尽管过后流量还不算高,然而每天到流量顶峰时间段,大家就会特地缓和,一周宕机好几回,数据库间接被打死,而后啥也干不了,只能重启;我过后还是参谋,看了看零碎设计,只能救急,就让大家先加上了缓存,然而因为大家对缓存的认知不够以及老零碎的凌乱,每个业务开发人员都会依照本人的形式来手撕缓存。这样导致的问题就是缓存用了,然而数据七零八落,压根没有方法保证数据的一致性。这的确是一个比拟苦楚的经验,应该能引起大家的共鸣和回顾。 而后我把整个零碎推倒从新设计了,其中缓存局部的架构设计在其中作用非常明显,于是有了明天的分享。 我次要分为以下几个局部跟大家探讨: 缓存零碎常见问题单行查问的缓存与主动治理多行查问缓存机制分布式缓存零碎设计缓存代码自动化实际缓存零碎波及的问题和知识点是比拟多的,我分为以下几个方面来探讨: 稳定性正确性可观测性标准落地和工具建设因为篇幅太长,本文作为系列文章第一篇,次要跟大家探讨『缓存零碎稳定性』 缓存零碎稳定性 缓存稳定性方面,网上根本所有的缓存相干文章和分享都会讲到三个重点: 缓存穿透缓存击穿缓存雪崩为什么首先讲缓存稳定性呢?大家能够回顾一下,咱们何时会引入缓存?个别都是当DB有压力,甚至常常被打挂的状况下才会引入缓存,所以咱们首先就是为了解决稳定性的问题而引入缓存零碎的。 缓存穿透 缓存穿透存在的起因是申请不存在的数据,从图中咱们能够看到对同一个数据的申请1会先去拜访缓存,然而因为数据不存在,所以缓存里必定没有,那么就落到DB去了,对同一个数据的申请2、申请3也同样会透过缓存落到DB去,这样当大量申请不存在的数据时DB压力就会特地大,尤其是可能会歹意申请打垮(不怀好意的人发现一个数据不存在,而后就大量发动对这个不存在数据的申请)。 go-zero 的解决办法是:对于不存在的数据的申请咱们也会在缓存里短暂(比方一分钟)寄存一个占位符,这样对同一个不存在数据的DB申请数就会跟理论申请数解耦了,当然在业务侧也能够在新增数据时删除该占位符以确保新增数据能够立即查问到。 缓存击穿缓存击穿的起因是热点数据的过期,因为是热点数据,所以一旦过期可能就会有大量对该热点数据的申请同时过去,这时如果所有申请在缓存里都找不到数据,如果同时落到DB去的话,那么DB就会霎时接受微小的压力,甚至间接卡死。 go-zero 的解决办法是:对于雷同的数据咱们能够借助于 core/syncx/SharedCalls 来确保同一时间只有一个申请落到DB,对同一个数据的其它申请期待第一个申请返回并共享后果或谬误,依据不同的并发场景,咱们能够抉择应用过程内的锁(并发量不是十分高),或者分布式锁(并发量很高)。如果不是特地须要,咱们个别举荐过程内的锁即可,毕竟引入分布式锁会减少复杂度和老本,借鉴奥卡姆剃刀实践:如非必要,勿增实体。 咱们来一起看一下上图缓存击穿防护流程,咱们用不同色彩示意不同申请: 绿色申请首先达到,发现缓存里没有数据,就去DB查问粉色申请达到,申请雷同数据,发现已有申请在解决中,期待绿色申请返回,singleflight模式绿色申请返回,粉色申请用绿色申请共享的后果返回后续申请,比方蓝色申请就能够间接从缓存里获取数据了缓存雪崩缓存雪崩的起因是大量同时加载的缓存有雷同的过期工夫,在过期工夫达到的时候呈现短时间内大量缓存过期,这样就会让很多申请同时落到DB去,从而使DB压力激增,甚至卡死。 比方疫情下在线教学场景,高中、初中、小学是分几个时间段同时开课的,那么这时就会有大量数据同时加载,并且设置了雷同的过期工夫,在过期工夫达到的时候就会对等呈现一个一个的DB申请波峰,这样的压力波峰会传递到下一个周期,甚至呈现叠加。 go-zero 的解决办法是: 应用分布式缓存,避免单点故障导致的缓存雪崩在过期工夫上加上5%的标准偏差,5%是假设检验里P值的经验值(有趣味的读者能够自行查阅) 咱们做个试验,如果用1万个数据,过期工夫设为1小时,标准偏差设为5%,那么过期工夫会比拟平均的散布在3400~3800秒之间。如果咱们的默认过期工夫是7天,那么就会均匀分布在以7天为中心点的16小时内。这样就能够很好的避免了缓存的雪崩问题。 未完待续本文跟大家一起探讨了缓存零碎的常见稳定性问题,下一篇我来跟大家一起剖析缓存的数据一致性问题。 所有这些问题的解决办法都已蕴含在 go-zero 微服务框架里,如果你想要更好的理解 go-zero 我的项目,欢送返回官方网站上学习具体的示例。 视频回放地址ArchSummit架构师峰会-海量并发下的缓存架构设计 我的项目地址https://github.com/tal-tech/go-zero 欢送应用 go-zero 并 star 反对咱们! 微信交换群关注『微服务实际』公众号并点击 进群 获取社区群二维码。 go-zero 系列文章见『微服务实际』公众号

May 10, 2021 · 1 min · jiezi

关于golang:Go-语言使用-import-导入包

定义包# add/add.gopackage add// 小写字母结尾为爱护函数,不能被内部拜访,只能同包名拜访func add(a, b int) int { return a + b}# sub/sub.gopackage sub// 大写字母结尾为公共函数,能够被内部拜访func Sub(a, b int) int { // 同包名能够拜访爱护函数 test() return a - b}# sub/utils.gopackage subimport "fmt"func test() { fmt.Println("this is test() in sub/utils!")}拜访包# main.gopackage mainimport ( "sub" "fmt")func main() { res := sub.Sub(20, 10)}# main.gopackage mainimport ( // 起别名 s "sub" "fmt")func main() { res := s.Sub(20, 10)}

May 9, 2021 · 1 min · jiezi

关于golang:Go-语言使用函数

定义函数func test(a int, b int, c string) (int, string, bool) { return a + b, c, true}func main() { v1, s1, _ := test(10, 20, "hello") fmt.Println("v1:", v1, ", s1:", s1)}或者 func test(a, b int, c string) (res int, str string, bl bool) { res = a + b str = c bl = true return}func main() { v1, s1, _ := test(10, 20, "hello") fmt.Println("v1:", v1, ", s1:", s1)}单个返回值能够不应用括号 ...

May 9, 2021 · 1 min · jiezi

关于golang:你不知的-Go-之-slice

简介切片(slice)是 Go 语言提供的一种数据结构,应用非常简单、便捷。然而因为实现层面的起因,切片也常常会产生让人纳闷的后果。把握切片的底层构造和原理,能够防止很多常见的应用误区。 底层构造切片构造定义在源码runtime包下的 slice.go 文件中: // src/runtime/slice.gotype slice struct { array unsafe.Pointer len int cap int}array:一个指针,指向底层存储数据的数组len:切片的长度,在代码中咱们能够应用len()函数获取这个值cap:切片的容量,即在不扩容的状况下,最多能包容多少元素。在代码中咱们能够应用cap()函数获取这个值 咱们能够通过上面的代码输入切片的底层构造: type slice struct { array unsafe.Pointer len int cap int}func printSlice() { s := make([]uint32, 1, 10) fmt.Printf("%#v\n", *(*slice)(unsafe.Pointer(&s)))}func main() { printSlice()}运行输入: main.slice{array:(unsafe.Pointer)(0xc0000d6030), len:1, cap:10}这里留神一个细节,因为runtime.slice构造是非导出的,咱们不能间接应用。所以我在代码中手动定义了一个slice构造体,字段与runtime.slice构造雷同。 咱们联合切片的底层构造,先回顾一下切片的基础知识,而后再逐个看看切片的常见问题。 基础知识创立切片创立切片有 4 种形式: varvar申明切片类型的变量,这时切片值为nil。 var s []uint32这种形式创立的切片,array字段为空指针,len和cap字段都等于 0。 切片字面量应用切片字面量将所有元素都列举进去,这时切片长度和容量都等于指定元素的个数。 s := []uint32{1, 2, 3}创立之后s的底层构造如下: len和cap字段都等于 3。 make应用make创立,能够指定长度和容量。格局为make([]type, len[, cap]),能够只指定长度,也能够长度容量同时指定: s1 := make([]uint32)s2 := make([]uint32, 1)s3 := make([]uint32, 1, 10)切片操作符应用切片操作符能够从现有的切片或数组中切取一部分,创立一个新的切片。切片操作符格局为[low:high],例如: ...

May 9, 2021 · 2 min · jiezi

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

简介在编程开发中,咱们常常会须要频繁创立和销毁同类对象的情景。这样的操作很可能会对性能造成影响。这时,罕用的优化伎俩就是应用对象池(object pool)。须要创建对象时,咱们先从对象池中查找。如果有闲暇对象,则从池中移除这个对象并将其返回给调用者应用。只有在池中无闲暇对象时,才会真正创立一个新对象。另一方面,对象应用完之后,咱们并不进行销毁。而是将它放回到对象池以供后续应用。应用对象池在频繁创立和销毁对象的情景下,能大幅度晋升性能。同时,为了防止对象池中的对象占用过多的内存。对象池个别还配有特定的清理策略。Go 规范库sync.Pool就是这样一个例子。sync.Pool中的对象会被垃圾回收清理掉。 在这类对象中,比拟非凡的一类是字节缓冲(底层个别是字节切片)。在做字符串拼接时,为了拼接的高效,咱们通常将两头后果寄存在一个字节缓冲。在拼接实现之后,再从字节缓冲中生成后果字符串。在收发网络包时,也须要将不残缺的包临时寄存在字节缓冲中。 Go 规范库中的类型bytes.Buffer封装字节切片,提供一些应用接口。咱们晓得切片的容量是无限的,容量有余时须要进行扩容。而频繁的扩容容易造成性能抖动。bytebufferpool实现了本人的Buffer类型,并应用一个简略的算法升高扩容带来的性能损失。bytebufferpool曾经在赫赫有名的 Web 框架fasthttp和灵便的 Go 模块库quicktemplate失去了利用。实际上,这 3 个库是同一个作者:valyala。 疾速应用本文代码应用 Go Modules。 创立目录并初始化: $ mkdir bytebufferpool && cd bytebufferpool$ go mod init github.com/darjun/go-daily-lib/bytebufferpool装置bytebufferpool库: $ go get -u github.com/PuerkitoBio/bytebufferpool典型的应用形式先通过bytebufferpool提供的Get()办法获取一个bytebufferpool.Buffer对象,而后调用这个对象的办法写入数据,应用实现之后再调用bytebufferpool.Put()将对象放回对象池中。例: package mainimport ( "fmt" "github.com/valyala/bytebufferpool")func main() { b := bytebufferpool.Get() b.WriteString("hello") b.WriteByte(',') b.WriteString(" world!") fmt.Println(b.String()) bytebufferpool.Put(b)}间接调用bytebufferpool包的Get()和Put()办法,底层操作的是包中默认的对象池: // bytebufferpool/pool.govar defaultPool Poolfunc Get() *ByteBuffer { return defaultPool.Get() }func Put(b *ByteBuffer) { defaultPool.Put(b) }咱们当然能够依据理论须要创立新的对象池,将雷同用途的对象放在一起(比方咱们能够创立一个对象池用于辅助接管网络包,一个用于辅助拼接字符串): func main() { joinPool := new(bytebufferpool.Pool) b := joinPool.Get() b.WriteString("hello") b.WriteByte(',') b.WriteString(" world!") fmt.Println(b.String()) joinPool.Put(b)}bytebufferpool没有提供具体的创立函数,不过能够应用new创立。 ...

May 9, 2021 · 2 min · jiezi

关于golang:Redis实现简单的消息队列Go语言版

前言假如咱们有这样一个场景,零碎会在某个特定的状况下给用户推送一条音讯,可能是短信、邮件或站内信,因为该场景音讯可能会在某一个时刻同时推送大量的音讯,且主过程不心愿会阻塞。该场景对实时性要求不高,容许音讯被延时送达。在零碎的构建初期,应用业余的音讯队列中间件Rabbitmq和Kafka来实现音讯的异步推送就显得不是很不便,此时咱们能够思考应用Redis来实现简略的音讯队列。当然,如果咱们对音讯的实时性以及可靠性要求十分高,可能就须要应用MQ或kafka来实现了。 音讯队列实现的原理是生产者将数据push到Redis的list里,消费者轮训去pop数据,如果能取到数据,则间接生产数据,如果等不到则持续循环pop数据。以上示意图中的红色箭头就是模仿的一个生产者从左部增加数据,消费者在右侧获取数据的场景。反之一样。然而这是就有个问题,退出队列空了怎么办?当列表为空时,消费者就会一直的轮训来获取数据,然而每次都获取不到数据,就会陷入一个取不到数据的死循环里,这不仅拉高了客户端的CPU,还拉高了Redis的QPS,并且这些拜访都是有效的。 这时咱们能够应用sleep(1)的形式去延时1秒,也能够应用Redis提供的阻塞式拜访,BRPP和BLPOP命令,消费者能够在获取不到数据的时候指定一个如果数据不存在的阻塞的超时工夫,如果在这个工夫内能取到数据,则会立刻返回,否则会返回null,当这个超时工夫设置为0的时候,示意会始终阻塞,但咱们通常并不倡议如此。如果都有多个客户端同时在阻塞期待音讯,则会依照先后顺序排序。 Redis client首先持续先看一下redis client。 import ( "log" "github.com/go-redis/redis")// 队列的keyvar queueKey = "queue:message"var rdb *redis.Clientfunc NewRedis() { rdb = redis.NewClient(&amp;redis.Options{ Addr: "127.0.0.1:6379", Password: "", }) pong, err := rdb.Ping().Result() if err != nil { log.Fatalln(err) } log.Println(pong,"redis success.")}生产者Producer生产者的具体实现代码,模仿一个随机生成音讯的生产者,应用lpush将数据增加到list里。 // 应用list生产音讯func ProducerMessageList(){ rand.Seed(time.Now().UnixNano()) log.Println("开启生产者。。。。") for i := 0;i &lt; 10;i++ { score := time.Now().Unix() log.Println("正在生产一条音讯...", score, i) _,err := rdb.LPush(queueListKey,i).Result() if err != nil { log.Println(err) } time.Sleep(time.Duration(rand.Intn(3)) * time.Second) }}消费者Consumer消费者则应用rpop来从队列里pop出一条音讯进行生产,但咱们后面讲过,如果队列空了,则会一直的轮训pop音讯,会造成大量的资源的节约,因而咱们此处应用brpop命令来实现阻塞的读,阻塞读在队列没有数据时会立刻进入休眠状态,一旦有数据了,则会立刻被唤醒并弹出音讯,提早能够忽略不计。 ...

May 8, 2021 · 2 min · jiezi

关于golang:goLang实现不同类型的切片间互转

golang的切片(slice)简略来说就是动态化的数组,切片的结构化定义如下 type SliceHeader struct { Data uintptr Len int Cap int }C语言中能够应用不同类型的指针指向不同类型的构造来进行拜访。例如能够应用指向char形的指针拜访整形的数组。对于goLang来说也能够实现不同类型切片间的互转,原理也是对SliceHeader构造体的指针Data进行赋值,同时对Len和Cap设置正确的值。 晓得原理接下来说一些相干的反射机制 反射简略来说就是获得对象的类型(Type),类别(Kind),值(Value),对元素(Element)的字段(Field)进行遍历和操作(读写)。 对于类型(Type)和类别(Kind)须要留神一下。Type能够认为是Kind的子集对于根本类型来说Type和Kind是统一的。例如int的Type和Kind一样都是int对于Struct来说,Type是你定义的构造体, Kind为Struct 上面间接上代码,也比拟好了解 func SliceConvert(origSlice interface{}, newSliceType reflect.Type) interface{} { sv := reflect.ValueOf(origSlice) if sv.Kind() != reflect.Slice { panic(fmt.Sprintf("Invalid origSlice(Non-slice value of type %T)", origSlice)) } if newSliceType.Kind() != reflect.Slice { panic(fmt.Sprintf("Invalid newSliceType(non-slice type of type %T)", newSliceType)) } //生成新类型的切片 newSlice := reflect.New(newSliceType) //hdr指向到新生成切片的SliceHeader hdr := (*reflect.SliceHeader)(unsafe.Pointer(newSlice.Pointer())) var newElemSize = int(sv.Type().Elem().Size()) / int(newSliceType.Elem().Size()) //设置SliceHeader的Cap,Len,以及数组的ptr hdr.Cap = sv.Cap() * newElemSize hdr.Len = sv.Len() * newElemSize hdr.Data = uintptr(sv.Pointer()) return newSlice.Elem().Interface()}调用 ...

May 8, 2021 · 1 min · jiezi

关于golang:Go-群友提问学习-defer-时很懵逼这道不会做

若有任何问题或倡议,欢送及时交换和碰撞。我的公众号是 【脑子进煎鱼了】,GitHub 地址:https://github.com/eddycjy。大家好,我是煎鱼。 前几天在读者交换群里看到一位小伙伴,在向大家征询 Go 相干的技术问题。疑难是:“各位大佬,我在学习 defer 遇到闭包的时候很懵逼,谁比拟明确,能指导?” 疑难他的疑难是上面这道 Go 语言的 defer 题目,大家一起看看: func main() { var whatever [6]struct{} for i := range whatever { defer func() { fmt.Println(i) }() }}请本人先想一下输入的后果答案是什么。 这位小伙伴按本人的了解后,认为该当输入 xx。但最终的输入后果,可能与其思考的有所偏差,一时想不通。 解惑这段程序的输入后果是: 555555为什么全是 5,为什么不是 0, 1, 2, 3, 4, 5 这样的输入后果呢? 其根本原因是闭包所导致的,有两点起因: 在 for 循环完结后,局部变量 i 的值曾经是 5 了,并且 defer 的闭包是间接援用变量的 i。联合defer 关键字的个性,可得悉会在 main 办法主体完结后再执行。联合上述,最终输入的后果是曾经自增结束的 5。 进一步思考既然理解了为什么,咱们再变形一下。再看看另外一种状况,代码如下: func main() { var whatever [6]struct{} for i := range whatever { defer func(i int) { fmt.Println(i) }(i) }}与第一个案例不同,咱们这回把变量 i 传了进去。那么他的输入后果是什么呢? ...

May 8, 2021 · 1 min · jiezi

关于golang:Go-语言的-Map-使用

定义 Map// 定义并调配空间idNames := make(map[int]string, 10)idNames[0] = "zhang"idNames[1] = "li"for key, value := range idNames { fmt.Println("key:", key, ", value:", value)}判断key是否在 Map 中// map 中不存在拜访越界问题,所有的key都是无效的,拜访不存在的key会返回零值name := idNames[100]// value 为对应的值,ok 如果key存在返回true,否则返回falsevalue, ok := idNames[100]if ok { fmt.Println("key 100 存在")}删除keydelete(idNames, 1)// 能够删除不存在的keydelete(idNames, 99)fmt.Println(idNames)

May 7, 2021 · 1 min · jiezi

关于golang:Go实现各类限流

前 言在开发高并发零碎时,咱们可能会遇到接口拜访频次过高,为了保证系统的高可用和稳定性,这时候就须要做流量限度,你可能是用的 Nginx 这种 Web Server 来管制申请,也可能是用了一些风行的类库实现。限流是高并发零碎的一大杀器,在设计限流算法之前咱们先来理解一下它们是什么。限 流限流的目标是通过对并发拜访申请进行限速,或者对一个工夫窗口内的申请进行限速来爱护零碎,一旦达到限度速率则能够拒绝服务、排队或期待、降级等解决。通过对并发(或者肯定工夫窗口内)申请进行限速来爱护零碎,一旦达到限度速率则拒绝服务(定向到谬误页或告知资源没有了)、排队期待(比方秒杀、评论、下单)、降级(返回兜底数据或默认数据)。 如 图: 如图上的漫画,在某个时间段流量上来了,服务的接口拜访频率可能会十分快,如果咱们没有对接口拜访频次做限度可能会导致服务器无奈接受过高的压力挂掉,这时候也可能会产生数据失落,所以就要对其进行限流解决。 限流算法就能够帮忙咱们去管制每个接口或程序的函数被调用频率,它有点儿像保险丝,避免零碎因为超过拜访频率或并发量而引起瘫痪。咱们可能在调用某些第三方的接口的时候会看到相似这样的响应头: X-RateLimit-Limit: 60 //每秒60次申请X-RateLimit-Remaining: 22 //以后还剩下多少次X-RateLimit-Reset: 1612184024 //限度重置工夫下面的 HTTP Response 是通过响应头通知调用方服务端的限流频次是怎么的,保障后端的接口拜访下限。为了解决限流问题呈现了很多的算法,它们都有不同的用处,通常的策略就是回绝超出的申请,或者让超出的申请排队期待。 一般来说,限流的罕用解决伎俩有: 计数器滑动窗口漏桶令牌桶计数器计数器是一种最简略限流算法,其原理就是:在一段时间距离内,对申请进行计数,与阀值进行比拟判断是否须要限流,一旦到了工夫临界点,将计数器清零。这个就像你去坐车一样,车厢规定了多少个地位,满了就不让上车了,不然就是超载了,被交警叔叔抓到了就要罚款的,如果咱们的零碎那就不是罚款的事件了,可能间接崩掉了。能够在程序中设置一个变量 count,当过去一个申请我就将这个数 +1,同时记录申请工夫。当下一个申请来的时候判断 count 的计数值是否超过设定的频次,以及以后申请的工夫和第一次申请工夫是否在 1 分钟内。如果在 1 分钟内并且超过设定的频次则证实申请过多,前面的申请就回绝掉。如果该申请与第一个申请的间隔时间大于计数周期,且 count 值还在限流范畴内,就重置 count。代码实现: package mainimport ( "log" "sync" "time")type Counter struct { rate int //计数周期内最多容许的申请数 begin time.Time //计数开始工夫 cycle time.Duration //计数周期 count int //计数周期内累计收到的申请数 lock sync.Mutex}func (l *Counter) Allow() bool { l.lock.Lock() defer l.lock.Unlock() if l.count == l.rate-1 { now := time.Now() if now.Sub(l.begin) >= l.cycle { //速度容许范畴内, 重置计数器 l.Reset(now) return true } else { return false } } else { //没有达到速率限度,计数加1 l.count++ return true }}func (l *Counter) Set(r int, cycle time.Duration) { l.rate = r l.begin = time.Now() l.cycle = cycle l.count = 0}func (l *Counter) Reset(t time.Time) { l.begin = t l.count = 0}func main() { var wg sync.WaitGroup var lr Counter lr.Set(3, time.Second) // 1s内最多申请3次 for i := 0; i < 10; i++ { wg.Add(1) log.Println("创立申请:", i) go func(i int) { if lr.Allow() { log.Println("响应申请:", i) } wg.Done() }(i) time.Sleep(200 * time.Millisecond) } wg.Wait()}OutPut: ...

May 7, 2021 · 3 min · jiezi

关于golang:Go-语言原生的-json-包有什么问题如何更好地处理-JSON-数据

Go 的 “玩家” 们看到这个题目可能会很纳闷——对于 JSON 而言,Go 原生库 encoding/json 曾经是提供了足够舒服的 JSON 解决工具,广受 Go 开发者的好评。它还能有什么问题?然而,实际上在业务开发过程中,咱们遇到了不少原生 json 做不好甚至是做不到的问题,还真是不能齐全满足咱们的要求。 那么,它有什么问题吗?什么状况下应用第三方库?如何选型?性能如何? 不过呢,在抛出具体问题之前,咱们先来尽可能简略地理解一下 Go 目前在解决 JSON 中罕用的一些库,以及对这些库的测试数据分析。如果读者感觉上面的文字太长了,也能够间接跳到论断局部。 局部罕用的 Go JSON 解析库Go 原生 encoding/json这应该是宽广 Go 程序员最相熟的库了,应用 json.Unmarshal 和 json.Marshal 函数,能够轻松将 JSON 格局的二进制数据反序列化到指定的 Go 构造体中,以及将 Go 构造体序列化为二进制流。而对于未知构造或不确定构造的数据,则反对将二进制反序列化到 map[string]interface{} 类型中,应用 KV 的模式进行数据的存取。 这里我提两个大家可能不会留意到的额定个性: json 包解析的是一个 JSON 数据,而 JSON 数据既能够是对象(object),也能够是数组(array),同时也能够是字符串(string)、数值(number)、布尔值(boolean)以及空值(null)。而上述的两个函数,其实也是反对对这些类型值的解析的。比方上面段代码也是能用的var s stringerr := json.Unmarshal([]byte(`"Hello, world!"`), &s)// 留神字符串中的双引号不能缺,如果仅仅是 `Hello, world`,则这不是一个非法的 JSON 序列,会返回谬误。json 在解析时,如果遇到大小写问题,会尽可能地进行大小写转换。即使是一个 key 与构造体中的定义不同,但如果疏忽大小写后是雷同的,那么仍然可能为字段赋值。比方上面的例子能够阐明:cert := struct { Username string `json:"username"` Password string `json:"password"`}{} err := json.Unmarshal([]byte(`{"UserName":"root","passWord":"123456"}`), &cert)if err != nil { fmt.Println("err =", err)} else { fmt.Println("username =", cert.Username) fmt.Println("password =", cert.Password)}// 理论输入: // username = root// password = 123456jsoniter关上 jsoniter 的 GitHub 主页,它一上来就鼓吹两个关键词:high-performance 以及 compatible。这也是这个包最大的两个卖点。 ...

May 7, 2021 · 5 min · jiezi

关于golang:一文带你理解最简消息队列实现

最近在看公司的 redis queue 时,发现底层应用的是 go-zero 的 queue 。本篇文章来看看 queue 的设计,也心愿能够从外面理解到 mq 的最小型设计实际。 应用联合其余 mq 的应用经验,根本的应用流程: 创立 producer 或 consumer启动 mq生产音讯/生产音讯对应到 queue 中,大抵也是这个: 创立 queue// 生产者创立工厂producer := newMockedProducer()// 消费者创立工厂consumer := newMockedConsumer()// 将生产者以及消费者的创立工厂函数传递给 NewQueue()q := queue.NewQueue(func() (Producer, error) { return producer, nil}, func() (Consumer, error) { return consumer, nil})咱们看看 NewQueue 须要什么构建条件: producer constructorconsumer constructor将单方的工厂函数传递给 queue ,由它去执行以及重试。 这两个须要的目标是将生产者/消费者的构建和音讯生产/生产都封装在 mq 中,而且将生产者/消费者的整套逻辑交给开发者解决: type ( // 开发者须要实现此接口 Producer interface { AddListener(listener ProduceListener) Produce() (string, bool) } ... // ProducerFactory定义了生成Producer的办法 ProducerFactory func() (Producer, error))其实也就是将生产者的逻辑交个开发者本人实现,mq 只负责生产者/消费者的消息传递和之间的调度。工厂办法的设计,是将生产者自身和生产音讯,这两个工作都交给 queue 本人来做调度或者重试。生产msg生产音讯当然要回到生产者自身: ...

May 7, 2021 · 2 min · jiezi

关于golang:ginsession使用以及源码分析

gin-session应用以及源码剖析概述个别PC 端网站开发都谈判到Session,服务端开启Session机制,客户端在第一次拜访服务端时,服务端会生成sessionId通过cookie 机制回传到客户端保留,之后每次客户端拜访服务端时都会通过cookie机制带sessionId到服务端,服务端通过解析SessionID找到申请的Session会话信息; Session信息都是保留在服务器中的,相似:SessionID =》 session信息;至于session信息具体内容是什么,这个要依据具体的业务逻辑来确定,然而广泛是用户信息; 服务端保留Session的形式很多:文件,缓存,数据库等,所以衍生进去的session的载体也有很多:redis,文件,mysql,memcached 等等;其中每一种载体都有着本人的优劣,依据不同的业务场景能够选取适合的载体; 上面咱们次要介绍 redis 作为载体: Gin中的 Sessiongin中Session的实现次要依赖于Gin-session中间件实现 (https://github.com/gin-contri... 通过注入不同的 store 从而实现不同的载体保留Session信息 : 次要包含: cookie-basedRedismemcachedMongoDBmemstore简略调用: 创立一个新的store并将中间件注入到gin的路由器中。须要应用的时候在HandlerFunc外部用 sessions.Default(c)即可获取到session// 创立载体形式对象(cookie-based)store := cookie.NewStore([]byte("secret"))r.Use(sessions.Sessions("sessionId", store))r.GET("/hello", func(c *gin.Context) { // session两头应用 session := sessions.Default(c) if session.Get("hello") != "world" { session.Set("hello", "world") session.Save() } ....})Gin-session 源码流程上面咱们以应用频率较高的 redis 作为 store 来看看 Gin-session 次要工作流程简略调用router := gin.Default()// @Todo 创立store对象store, err := sessions.NewRedisStore(10, "tcp", "localhost:6379", "", []byte("secret")) if err != nil {log.Fatal(" sessions.NewRedisStore err is :%v", err)}router.GET("/admin", func(c *gin.Context) { session := sessions.Default(c) var count int v := session.Get("count") if v == nil { count = 0 } else { count = v.(int) count++ } session.Set("count", count) session.Save() c.JSON(200, gin.H{"count": count}) })创立 store 对象底层的 store 创立func NewRediStore(size int, network, address, password string, keyPairs ...[]byte) (*RediStore, error) { return NewRediStoreWithPool(&redis.Pool{ MaxIdle: size, IdleTimeout: 240 * time.Second, TestOnBorrow: func(c redis.Conn, t time.Time) error { _, err := c.Do("PING") return err }, Dial: func() (redis.Conn, error) { return dial(network, address, password) }, }, keyPairs...)}// NewRediStoreWithPool instantiates a RediStore with a *redis.Pool passed in.func NewRediStoreWithPool(pool *redis.Pool, keyPairs ...[]byte) (*RediStore, error) { rs := &RediStore{ // http://godoc.org/github.com/gomodule/redigo/redis#Pool Pool: pool, Codecs: securecookie.CodecsFromPairs(keyPairs...), Options: &sessions.Options{ Path: "/", // 客户端的 Path MaxAge: sessionExpire, // 客户端的Expires/MaxAge }, DefaultMaxAge: 60 * 20, // 过期工夫是20分钟 maxLength: 4096, // 最大长度 keyPrefix: "session_", // key 前缀 serializer: GobSerializer{}, // 外部序列化采纳了Gob库 } // @Todo 尝试创立连贯 _, err := rs.ping() return rs, err}依据函数能够看到,依据传入参数(size:连接数, network:连贯协定,address:服务地址,password:明码)初始化一个 redis.Pool 对象,通过传入 redis.Pool 对象 和 一些默认的参数初始化 RediStore 对象 ...

May 6, 2021 · 5 min · jiezi

关于golang:Go-语言的切片使用

切片(slice)是建设在数组之上的更不便,更灵便,更弱小的数据结构。切片并不存储任何元素而只是对现有数组的援用。 切片的三要素指向数组中的开始地位切片的长度,通过内置函数len取得切片的最大容量,通过内置函数cap取得能够认为切片在外部示意为如下的构造体: type slice struct { // 长度 Length int // 容量 Capacity int // 指向首元素的指针 ZerothElement *byte}切片申明// 基于数组创立一个从 a[start] 到 a[end -1] 的切片a := [5]int{76, 77, 78, 79, 80}var b []int = a[1:4]// 创立了一个长度为4的string数组,并返回一个切片给namesnames := []string{"beijing", "shanghai", "guangzhou", "shenzhen"}// 创立长度为10的字符串切片str := make([]string, 10)// 创立长度为10,容量为20的字符串切片str := make([]string, 10, 20)arr[start:end] 的应用 // 基于数组产生新的切片,a[start:end]为左闭右开name1 := names[0:3]name2 := names[:3]name3 := names[2:]name4 := names[:]援用传递切片自身不蕴含任何数据。它仅仅是底层数组的一个下层示意。对切片进行的任何批改都将反映在底层数组中。 names := []string{"beijing", "shanghai", "guangzhou", "shenzhen"}name1 := names[0:3]// 援用传递,会同时扭转原数组name1[2] = "luoyang"fmt.Println(names)fmt.Println(name1)获取切片长度与容量str := "helloworld"[5:7]fmt.Println("len:", len(str), ", cap:", cap(str))切片追加元素// append会判断切片是否有残余空间,如果没有残余空间,则会主动裁减两倍空间names = append(names, "chongqing")如果切片是建设在数组之上的,而数组自身不能扭转长度,那么切片是如何动静扭转长度的呢?理论产生的状况是,当新元素通过调用 append 函数追加到切片开端时,如果超出了容量,append 外部会创立一个新的数组。并将原有数组的元素被拷贝给这个新的数组,最初返回建设在这个新数组上的切片。这个新切片的容量是旧切片的二倍。切片追加切片能够应用 ... 操作符将一个切片追加到另一个切片开端 ...

May 5, 2021 · 2 min · jiezi

关于golang:Go-语言的数组使用

数组简介固定长度,不能动静扭转元素类型雷同内存间断调配申明数组// 申明了一个长度为10的整型数组,所有元素都被主动赋值为0var nums [10]int// 或申明同时赋值nums := [10]int{1, 2, 3, 4}// 或自动识别数组长度nums := [...]int{1,2,3,4,5,6,7,8,9,10}// 或申明同时通过索引赋值nums := [10]int{1:10, 3:10}拜访数组元素//获取索引为2的值nums[2]//批改索引为2的值nums[2] = 10数组赋值a := [3]int{5, 78, 8}var b [5]int//not possible since [3]int and [5]int are distinct typesb = aa := [3]int{5, 78, 8}var b [3]int// 胜利b = a值传递a := [...]string{"USA", "China", "India", "Germany", "France"}b := ab[0] = "Singapore"// a is [USA China India Germany France] fmt.Println("a is ", a)// b is [Singapore China India Germany France] fmt.Println("b is ", b) 数组长度a := [...]float64{67.7, 89.8, 21, 78}fmt.Println("length of a is:", len(a))遍历数组for i := 0; i < len(nums); i++ { fmt.Println("i:", i, ", j:", nums[i])}// value 是值传递,非援用传递for key, value := range nums { fmt.Println("key:", key, ", value:", value)}// 应用下划线 _ 疏忽keyfor _, value := range nums { fmt.Println("value:", value)}// 应用下划线 _ 和等号 = 疏忽 key, valuefor _, _ = range nums {}

May 5, 2021 · 1 min · jiezi

关于golang:Go-语言的字符串使用

定义字符串# 一般字符串name := "lilei"# 多行字符串usage := `hello,my name is lileii'm a boy.`fmt.Println("name:", name)fmt.Println("usage:", usage)字符串长度及遍历字符串name := "lilei"nameLen := len(name)fmt.Println("name length:" nameLen)for i := 0; i < len(name); i++ { fmt.Printf("i: %d, v: %c\n", i, name[i])}拼接字符串i, j := "hello", "world"fmt.Println("i+j=", i+j)定义常量const ADDRESS := "beijing"fmt.Println("address:", ADDRESS)

May 4, 2021 · 1 min · jiezi

关于golang:go-实现排序算法

go 十个数组排序办法package sufaimport ( "fmt" "math" "math/rand" "strconv" "time")func Main2() { var li []int var r int var start, end int64 //li = []int{90, 23, 433, 343, 53, -1, 56, 7, 7, 8, 6, 45, 4, 5, 66, 7} li = []int{} for i := 0; i < 10; i++ { r = rand.Intn(100) li = append(li, r) } //for i := len(li)-1; i >= 0; i-- { //// fmt.Println(i, li[i]) // var t = li[0] // for j := 1; j < len(li); j++ { // if li[j] < li[j-1] { // t = li[j-1] // li[j-1] = li[j] // li[j] = t // } // } //} fmt.Println(li) start = time.Now().UnixNano() //BubbleSort(li) //SelectSort(li) //InsertSort(li) //ShellSort(li) //MergeSort(li) //QuickSort(li) //CountSort(li) BucketSort(li) //Reverse(li) //ExChange(li, 0,1) end = time.Now().UnixNano() fmt.Println(li) fmt.Println(end - start)}//冒泡排序func BubbleSort(li []int) { for i := len(li) - 1; i >= 0; i-- { // fmt.Println(i, li[i]) var t = li[0] for j := 1; j < len(li); j++ { if li[j] < li[j-1] { t = li[j-1] li[j-1] = li[j] li[j] = t } } fmt.Print(i, "\r") }}//抉择排序func SelectSort(li []int) { for i := 0; i < len(li); i++ { var minIndex int = i for j := i; j < len(li); j++ { if li[j] < li[minIndex] { minIndex = j } } ExChange(li, i, minIndex) }}插入排序func InsertSort(li []int) { for i := 1; i < len(li); i++ { var this int = li[i] for j := i - 1; j >= 0; j-- { if li[j+1] < li[j] { ExChange(li, j, j+1) continue } if li[j+1] >= li[j] { li[j+1] = this break } } }}//希尔排序func ShellSort(li []int) { var l = len(li) var gap = l / 2 if l <= 1 { return } for true { if gap == 1 { InsertSort(li) break } for i := 0; i < gap; i++ { var index int var group []int = make([]int, 0) var groupIdList []int = make([]int, 0) //group = append(group, li[i]) //groupIdList = append(groupIdList, i) for j := 0; j < l; j++ { index = i + j*gap if index >= l { break } group = append(group, li[index]) groupIdList = append(groupIdList, index) } InsertSort(group) for j := 0; j < len(groupIdList) && j < len(group); j++ { var ind int if len(groupIdList) <= 1 || len(group) <= 1 { break } ind = groupIdList[j] li[ind] = group[j] } } gap = gap / 2 }}func MergeSort(li []int) { //merge(li, 0, len(li)) type funcType func(li []int, left, right int) var f funcType f = func(li []int, left, right int) { var middle = (left + right) / 2 if right-left <= 1 { InsertSort(li[left:right]) return } f(li, left, middle) f(li, middle, right) SelectSort(li[left:middle]) SelectSort(li[middle:right]) } f(li, 0, len(li))}//疾速排序func QuickSort(li []int) { //qu(li, 0, len(li)) if len(li) <= 1 { return } type funcType func(li []int, left, right int) var f funcType f = func(li []int, left, right int) { var pivot int = (right + left) / 2 if right-left <= 1 { InsertSort(li[left:right]) return } f(li, left, pivot) f(li, pivot, right) //fmt.Println(li[left:right], li[pivot]) for i := pivot - 1; i >= left; i-- { if li[i] >= li[pivot] { for j := i; j < pivot; j++ { ExChange(li, j, j+1) //if j+1 == pivot { // pivot -= 1 //} } pivot -= 1 } } for i := right - 1; i > pivot; i-- { if li[i] <= li[pivot] { for j := i; j > pivot; j-- { ExChange(li, j, j-1) } pivot += 1 } } SelectSort(li[left:pivot]) SelectSort(li[pivot:right]) //fmt.Println(li[left:right], li[pivot]) } f(li, 0, len(li))}//计数排序func CountSort(li []int) { var ( min = li[0] max = li[0] ma []int res []int ) for i := 0; i < len(li); i++ { if li[i] <= min { min = li[i] } if li[i] >= max { max = li[i] } } ma = make([]int, max+1) for i := 0; i < len(li); i++ { ma[li[i]] += 1 } res = make([]int, 0) for i := 0; i < len(ma); i++ { if ma[i] != 0 { for j := 0; j < ma[i]; j++ { res = append(res, i) } } } for i := 0; i < len(li); i++ { li[i] = res[i] } //fmt.Println(ma, min, max, res)}//桶排序func BucketSort(li []int) { var max = li[0] var maxNumb = 0 for i := 0; i < len(li); i++ { if li[i] >= max { max = li[i] } } var data = strconv.Itoa(max) maxNumb = len(data) for i := 0; i < maxNumb; i++ { var l = make([][]int, len(li)*2) var n = 0 for j := 0; j < len(li); j++ { n = getNumb(li[j], i) //fmt.Println(n, li[j], i) l[n] = append(l[n], li[j]) } var index = 0 for j := 0; j < 10; j++ { if len(l[j]) > 0 { for k := 0; k < len(l[j]); k++ { li[index] = l[j][k] index++ } } } }}//失去数字的第几位func getNumb(num, index int) int { return (num / int(math.Pow(10, float64(index)))) % 10}//替换数组中的两个数func ExChange(li []int, x, y int) { var t int t = li[x] li[x] = li[y] li[y] = t}//逆序func Reverse(li []int) { for i := 0; i < len(li)/2; i++ { ExChange(li, i, len(li)-i-1) }}

May 3, 2021 · 5 min · jiezi

关于golang:Go-向函数传递切片该使用值还是指针

参考:Go 切片:用法和实质 在Go中,切片的实质是一个构造体,蕴含一个指向底层数组的指针(prt),长度(len),容量(cap)。所以,切片自身蕴含一个指针,将切片按值传递给函数,在函数内对其批改,影响将会传递到函数外。因为底层的数组被批改了。但当对切片进行append()操作,若是元素数量超过原有切片的容量,将会使得切片容量增大,这就是问题所在。扩容后的切片,实质上是产生一个新的底层数组。如果在函数内对切片增加元素导致扩容,会导致元素内的切片指向一个新的数组,然而函数外的切片依然指向原来旧的数组,则将会导致影响无奈传递到函数外。如果心愿函数内对切片扩容作用于函数外,就须要以指针模式传递切片。 上面的代码展现了,在change2()外部对切片扩容,导致其指向新的数组,(能够看到切片首个元素地址变了),然而无奈影响内部的切片。 package mainimport "fmt"func change1(result []int){ fmt.Printf("外部1 %p%v len:%d cap:%d\n",result,result,len(result),cap(result)) result[0]=9}func change2(result []int){ result=append(result,92) fmt.Printf("外部2 %p%v len:%d cap:%d\n",result,result,len(result),cap(result))}func change3(result *[]int){ *result=append(*result,92) fmt.Printf("外部3 %p%v len:%d cap:%d\n",*result,*result,len(*result),cap(*result))}func main(){ result:=make([]int,1) fmt.Printf("内部 %p%v len:%d cap:%d\n",result,result,len(result),cap(result)) change1(result) fmt.Printf("内部 %p%v len:%d cap:%d\n",result,result,len(result),cap(result)) change2(result) fmt.Printf("内部 %p%v len:%d cap:%d\n",result,result,len(result),cap(result)) change3(&result) fmt.Printf("内部 %p%v len:%d cap:%d\n",result,result,len(result),cap(result))}输入 内部 0xc000018100[0] len:1 cap:1外部1 0xc000018100[0] len:1 cap:1内部 0xc000018100[9] len:1 cap:1外部2 0xc000018130[9 92] len:2 cap:2内部 0xc000018100[9] len:1 cap:1外部3 0xc000018150[9 92] len:2 cap:2内部 0xc000018150[9 92] len:2 cap:2

May 3, 2021 · 1 min · jiezi

关于golang:Go-语言不支持的语法

不反对 ++i 和 --ii := 1++i不反对地址加减a := 1ptr = &aptr++ptr--不反对三目运算符a, b := 1, 2x := a > b ? 0 : -1逻辑虚实只能应用 true 和false ,不能应用 0 和 nilif 0 { // ...}if nil { // ...}

May 1, 2021 · 1 min · jiezi

关于golang:Golang-Map中value是不可寻址可使用指针类型代替

Golang Map元素取址: package mainimport "fmt"type UserInfo struct { Uid string `json:"uid"` UserName string `json:"user_name"` Sex int `json:"sex"`}func main() { var user = make(map[string]UserInfo) uid := "0001" user[uid] = UserInfo{ Uid: uid, UserName: "jack", Sex: 1, } user[uid].UserName="polly" fmt.Println(user[uid])}以上代码报错:./map.go:19:20: cannot assign to struct field user[uid].UserName in map起因是 map 元素是无奈取址的,也就说能够失去 user[uid].UserName, 然而无奈对其进行批改。解决办法:应用指针的mapgolang外面的map,当通过key获取到value时,这个value是不可寻址的,因为map 会进行动静扩容,当进行扩大后,map的value就会进行内存迁徙,其地址发生变化,所以无奈对这个value进行寻址。也就是造成上述问题的起因所在。map的扩容与slice不同,那么map自身是援用类型,作为形参或返回参数的时候,传递的是值的拷贝,而值是地址,扩容时也不会扭转这个地址。而slice的扩容,会导致地址的变动。 package mainimport "fmt"type UserInfo struct { Uid string `json:"uid"` UserName string `json:"user_name"` Sex int `json:"sex"`}func main() { var user = make(map[string]*UserInfo) uid := "0001" user[uid] = &UserInfo{ Uid: uid, UserName: "jack", Sex: 1, } user[uid].UserName="polly" fmt.Println(user[uid])}

May 1, 2021 · 1 min · jiezi

关于golang:GO-语言之旅-练习等价二叉查找树

题目地址 https://tour.go-zh.org/concur...前提:对两棵二叉树进行中序遍历,若是失去的数值序列雷同,则将其视为等价二叉树。要求:应用goroutine协程 思维:其实和不应用协程的思路差不多,别离中序遍历两棵树,失去数值序列,比拟两个序列是否相等。若是应用协程,则能够很不便地并发遍历两棵树,再利用信道传输每个节点的数值,边遍历边比拟。因为Go协程可利用多CPU外围,若是电脑有两个以上的CPU外围,则理论的运行工夫会大大放慢。相似的,在其余编程语言里,能够应用多线程,而后通过队列传送信息给主线程。边遍历边比拟。 树的数据结构。文档和源代码地址:https://pkg.go.dev/golang.org... // A Tree is a binary tree with integer values.type Tree struct { Left *Tree Value int Right *Tree}题目的模板揭示咱们,通过Same函数检测两棵树是否雷同,通过Walk函数遍历树。思维就是,在Same函数中别离创立两个信道ch1,ch2,启动两个协程运行Walk函数,两个Walk函数别离遍历两棵树,别离通过两个信道将遍历的数值传给Same函数,Same会同时比对两个Walk传输过去的数值,若是直到两个Walk遍历完树(也就是两个Walk都敞开了信道),Same的数值比对都没有出错,则可视为两棵树等价。 package mainimport "golang.org/x/tour/tree"// Walk 步进 tree t 将所有的值从 tree 发送到 channel ch。func Walk(t *tree.Tree, ch chan int)// Same 检测树 t1 和 t2 是否含有雷同的值。func Same(t1, t2 *tree.Tree) boolfunc main() {}理论代码 package mainimport "fmt"import "tree"// Walk 步进 tree t 将所有的值从 tree 发送到 channel ch。func Walk(t *tree.Tree, ch chan int) { if t == nil { return } stack := make([]*tree.Tree, 0) for len(stack) > 0 || t != nil { for t != nil { stack = append(stack, t) t = t.Left } node := stack[len(stack)-1] stack = stack[:len(stack)-1] ch <- node.Value t = node.Right } close(ch)}// Same 检测树 t1 和 t2 是否含有雷同的值。func Same(t1, t2 *tree.Tree) bool { ch1 := make(chan int) ch2 := make(chan int) go Walk(t1, ch1) go Walk(t2, ch2) for { v1, ok1 := <-ch1 v2, ok2 := <-ch2 if ok1 && ok2 { //两个信道都在开启状态 if v1 != v2 { return false } } else if ok1 || ok2 { //一个信道已敞开,阐明两棵树节点数不同 return false } else { //两个信道同时敞开,阐明两棵树节点数雷同 break } } return true}func main() { fmt.Println(Same(tree.New(1), tree.New(1))) fmt.Println(Same(tree.New(2), tree.New(1)))}在Walk中,能够创立一个切片"stack"来模仿栈实现中序遍历。压入栈是stack = append(stack, t),弹出栈是node := stack[len(stack)-1],stack = stack[:len(stack)-1]。每遍历一个节点,就将数值传入信道ch。遍历实现,敞开信道close(ch)。在Same中,创立两个协程同时遍历两棵树,两个信道别离接管两个Walk传来的数值。两棵树等价的必要条件就是,树的节点数雷同,若是有一棵树提前遍实现,则代表这棵树节点数少一些,这两棵树必然不等价。只有两棵树都同时遍历实现,且每次比对数值不出错,才可视为两棵树等价。 ...

May 1, 2021 · 1 min · jiezi

关于golang:动图图解GMP模型里为什么要有P背后的原因让人暖心

文章继续更新,能够微信搜一搜「golang小白成长记」第一工夫浏览,回复【教程】获golang收费视频教程。本文曾经收录在GitHub https://github.com/xiaobaiTec... , 有大厂面试残缺考点和成长路线,欢送Star。GM模型是什么 在 Go 1.1版本之前,其实用的就是GM模型。 G,协程。通常在代码里用 go 关键字执行一个办法,那么就等于起了一个G。M,内核线程,操作系统内核其实看不见G和P,只晓得本人在执行一个线程。G和P都是在用户层上的实现。除了G和M以外,还有一个全局协程队列,这个全局队列里放的是多个处于可运行状态的G。M如果想要获取G,就须要拜访一个全局队列。同时,内核线程M是能够同时存在多个的,因而拜访时还须要思考并发平安问题。因而这个全局队列有一把全局的大锁,每次拜访都须要去获取这把大锁。 并发量小的时候还好,当并发量大了,这把大锁,就成为了性能瓶颈。 GMP模型是什么 基于没有什么是加一个中间层不能解决的思路,golang在原有的GM模型的根底上退出了一个调度器P,能够简略了解为是在G和M两头加了个中间层。 于是就有了当初的GMP模型里。 P 的退出,还带来了一个本地协程队列,跟后面提到的全局队列相似,也是用于寄存G,想要获取期待运行的G,会优先从本地队列里拿,拜访本地队列无需加锁。而全局协程队列仍然是存在的,然而性能被弱化,不到万不得已是不会去全局队列里拿G的。GM模型里M想要运行G,间接去全局队列里拿就行了;GMP模型里,M想要运行G,就得先获取P,而后从 P 的本地队列获取 G。 新建 G 时,新G会优先退出到 P 的本地队列;如果本地队列满了,则会把本地队列中一半的 G 挪动到全局队列。P 的本地队列为空时,就从全局队列里去取。 如果全局队列为空时,M 会从其余 P 的本地队列偷(stealing)一半G放到本人 P 的本地队列。 M 运行 G,G 执行之后,M 会从 P 获取下一个 G,一直反复上来。 为什么P的逻辑不间接加在M上次要还是因为M其实是内核线程,内核只晓得本人在跑线程,而golang的运行时(包含调度,垃圾回收等)其实都是用户空间里的逻辑。操作系统内核哪里还晓得,也不须要晓得用户空间的golang利用原来还有那么多花花肠子。这所有逻辑交给应用层本人去做就好,毕竟改内核线程的逻辑也不适合啊。 如果文章对你有帮忙,看下文章底部右下角,做点正能量的事件(点两下)反对一下。(疯狂暗示,托付托付,这对我真的很重要!)我是小白,咱们下期见。 参考资料[1]《Golang 调度器 GMP 原理与调度全剖析》 ——Aceld :https://learnku.com/articles/... [2]《GMP模型为什么要有P》 ——煎鱼 :https://mp.weixin.qq.com/s/an... [3]《深度解密Go语言之Scheduler》 ——qcrao :https://qcrao.com/2019/09/02/... 文章举荐:i/o timeout,心愿你不要踩到这个net/http包的坑妙啊! 程序猿的第一本互联网黑话指南程序员防猝死指南我感觉,我可能要拿图灵奖了。。。给大家争脸了,用了三年golang,我还是没答对这道内存透露题硬核!漫画图解HTTP知识点+面试题TCP粘包 数据包:我只是犯了每个数据包都会犯的错 |硬核图解硬核图解!30张图带你搞懂!路由器,集线器,交换机,网桥,光猫有啥区别?别说了,关注公众号:【golang小白成长记】,一起在常识的陆地里呛水吧关注公众号:【golang小白成长记】

April 29, 2021 · 1 min · jiezi

关于golang:聊聊tunny的workerWrapper

序本文次要钻研一下tunny的workerWrapper workerWrappertype workerWrapper struct { worker Worker interruptChan chan struct{} // reqChan is NOT owned by this type, it is used to send requests for work. reqChan chan<- workRequest // closeChan can be closed in order to cleanly shutdown this worker. closeChan chan struct{} // closedChan is closed by the run() goroutine when it exits. closedChan chan struct{}}func newWorkerWrapper( reqChan chan<- workRequest, worker Worker,) *workerWrapper { w := workerWrapper{ worker: worker, interruptChan: make(chan struct{}), reqChan: reqChan, closeChan: make(chan struct{}), closedChan: make(chan struct{}), } go w.run() return &w}workerWrapper包装了worker,定义了interruptChan、reqChan、closeChan、closedChan属性interruptfunc (w *workerWrapper) interrupt() { close(w.interruptChan) w.worker.Interrupt()}interrupt办法敞开w.interruptChan,执行w.worker.Interrupt()runfunc (w *workerWrapper) run() { jobChan, retChan := make(chan interface{}), make(chan interface{}) defer func() { w.worker.Terminate() close(retChan) close(w.closedChan) }() for { // NOTE: Blocking here will prevent the worker from closing down. w.worker.BlockUntilReady() select { case w.reqChan <- workRequest{ jobChan: jobChan, retChan: retChan, interruptFunc: w.interrupt, }: select { case payload := <-jobChan: result := w.worker.Process(payload) select { case retChan <- result: case <-w.interruptChan: w.interruptChan = make(chan struct{}) } case _, _ = <-w.interruptChan: w.interruptChan = make(chan struct{}) } case <-w.closeChan: return } }}run首先创立jobChan、retChan,而后for循环执行select读取reqChan,之后读取jobChan的payload,进行解决,而后写入到retChanstopfunc (w *workerWrapper) stop() { close(w.closeChan)}stop办法敞开w.closeChanjoinfunc (w *workerWrapper) join() { <-w.closedChan}join办法则期待w.closedChan小结tunny的workerWrapper包装了worker,定义了interruptChan、reqChan、closeChan、closedChan属性,它提供了interrupt、run、stop、join办法。 ...

April 28, 2021 · 1 min · jiezi

关于golang:golang基本类型

1.简略demoa := 20b := 20fmt.Printf("a:%d",a)fmt.Println()fmt.Printf("b:%d",b)fmt.Println()changeA(&a) //指针拷贝changeB(b) //值拷贝fmt.Printf("after changeA a:%d",a)fmt.Println()fmt.Printf("after changeB b:%d",b)func changeA(a *int){ *a = 100}func changeB(b int){ b = 100}--后果a:20b:20after changeA a:100after changeB b:20指针拷贝会批改传递过去的值,值拷贝仅仅批改拷贝后的值当须要批改传入的参数原来的值或者传入的对象比拟大的时候,能够应用指针传递golang中援用类型的申明默认是没有初始化的,定义一个指针后,该指针是nil能够通过new进行初始化。2.make与new性能雷同的还有make,make只对slice、map和chan进行初始化,slice、map和chan必须通过make初始化后,才能够操作func make(t Type, size ...IntegerType) Typemake返回的是指针类型new对指针进行初始化,返回指针类型对应的默认值,并能够对指针变量进行赋值3.map 找子字符串个数str := "a b c ab c a do do i i test am you"mapC := make(map[string]int)strArr := strings.Split(str, " ")fmt.Println(strArr)for k := range strArr { mapC[strArr[k]] = mapC[strArr[k]] + 1}fmt.Println(mapC)--[a b c ab c a do do i i test am you]map[a:2 ab:1 am:1 b:1 c:2 do:2 i:2 test:1 you:1]

April 28, 2021 · 1 min · jiezi

关于golang:golang-网络连接库-cellnet2

本章将介绍tcp库的peer端,本节次要讲述connector端创立和收发信息 connector type tcpConnector struct { peer.SessionManager peer.CorePeerProperty peer.CoreContextSet peer.CoreRunningTag peer.CoreProcBundle peer.CoreTCPSocketOption peer.CoreCaptureIOPanic defaultSes *tcpSession tryConnTimes int // 尝试连贯次数 sesEndSignal sync.WaitGroup reconDur time.Duration}tcpSession是负责解决音讯接管。tcpSession的构造定义 type tcpSession struct { peer.CoreContextSet peer.CoreSessionIdentify *peer.CoreProcBundle pInterface cellnet.Peer // Socket原始连贯 conn net.Conn connGuard sync.RWMutex // 退出同步器 exitSync sync.WaitGroup // 发送队列 sendQueue *cellnet.Pipe cleanupGuard sync.Mutex endNotify func() closing int64}tcpConnector 初始化连贯。conn实例保留在tcpSession // 连接器,传入连贯地址和发送封包次数func (self *tcpConnector) connect(address string) { self.SetRunning(true) for { self.tryConnTimes++ // 尝试用Socket连贯地址 conn, err := net.Dial("tcp", address) self.defaultSes.setConn(conn) // 产生谬误时退出 if err != nil { if self.tryConnTimes <= reportConnectFailedLimitTimes { log.Errorf("#tcp.connect failed(%s) %v", self.Name(), err.Error()) if self.tryConnTimes == reportConnectFailedLimitTimes { log.Errorf("(%s) continue reconnecting, but mute log", self.Name()) } } // 没重连就退出 if self.ReconnectDuration() == 0 || self.IsStopping() { self.ProcEvent(&cellnet.RecvMsgEvent{ Ses: self.defaultSes, Msg: &cellnet.SessionConnectError{}, }) break } // 有重连就期待 time.Sleep(self.ReconnectDuration()) // 持续连贯 continue } self.sesEndSignal.Add(1) self.ApplySocketOption(conn) self.defaultSes.Start() self.tryConnTimes = 0 self.ProcEvent(&cellnet.RecvMsgEvent{Ses: self.defaultSes, Msg: &cellnet.SessionConnected{}}) self.sesEndSignal.Wait() self.defaultSes.setConn(nil) // 没重连就退出/被动退出 if self.IsStopping() || self.ReconnectDuration() == 0 { break } // 有重连就期待 time.Sleep(self.ReconnectDuration()) // 持续连贯 continue } self.SetRunning(false) self.EndStopping()} self.defaultSes.Start()会开启两个goroutine,一个是发送循环,一个是接管循环// 发送循环func (self *tcpSession) sendLoop() { var writeList []interface{} var capturePanic bool if i, ok := self.Peer().(cellnet.PeerCaptureIOPanic); ok { capturePanic = i.CaptureIOPanic() } for { writeList = writeList[0:0] exit := self.sendQueue.Pick(&writeList) // 遍历要发送的数据 for _, msg := range writeList { if capturePanic { self.protectedSendMessage(&cellnet.SendMsgEvent{Ses: self, Msg: msg}) } else { self.SendMessage(&cellnet.SendMsgEvent{Ses: self, Msg: msg}) } } if exit { break } } // 残缺敞开 conn := self.Conn() if conn != nil { conn.Close() } // 告诉实现 self.exitSync.Done()}// 接管循环func (self *tcpSession) recvLoop() { var capturePanic bool if i, ok := self.Peer().(cellnet.PeerCaptureIOPanic); ok { capturePanic = i.CaptureIOPanic() } for self.Conn() != nil { var msg interface{} var err error if capturePanic { msg, err = self.protectedReadMessage() } else { msg, err = self.ReadMessage(self) } if err != nil { if !util.IsEOFOrNetReadError(err) { var ip string if self.conn != nil { addr := self.conn.RemoteAddr() if addr != nil { ip = addr.String() } } log.Errorf("session closed, sesid: %d, err: %s ip: %s", self.ID(), err, ip) } self.sendQueue.Add(nil) // 标记为手动敞开起因 closedMsg := &cellnet.SessionClosed{} if self.IsManualClosed() { closedMsg.Reason = cellnet.CloseReason_Manual } self.ProcEvent(&cellnet.RecvMsgEvent{Ses: self, Msg: closedMsg}) break } self.ProcEvent(&cellnet.RecvMsgEvent{Ses: self, Msg: msg}) } // 告诉实现 self.exitSync.Done()}self.ProcEvent函数会调用proc.BindProcessorHandler的第三个参数,也就是用户自定义的数据处理函数,上面是一个例子。用户能够依据本人的须要自定义数据处理函数 ...

April 28, 2021 · 2 min · jiezi

关于golang:10行C代码实现高性能HTTP服务

前言是不是感觉C++写个服务太累,但又沉迷于C++的真香性能而无法自拔?作为一个老牌C++程序员(能够看我 github 上十几年前的C++我的项目:https://github.com/kevwan ),这几天听一个好友跟我聊起他写的C++框架,说极简代码即可实现各种C++服务的开发,不禁让我心生好奇!于是我去钻研了一下,发现的确有点意思! 实战(干货)话不多说,咱们来一起看看,10行C++代码怎么实现一个高性能的Http服务,轻松QPS几十万。Linus说:talk is cheap,show me the code ↓ int main() { WFHttpServer server([](WFHttpTask *task) { task-&gt;get_resp()-&gt;append_output_body("Hello World!"); }); if (server.start(8888) == 0) { getchar(); // press "Enter" to end. server.stop(); } return 0;}这个 server 应用了 workflow,装置编译都非常简单,以 Linux 为例,把代码拉下来后,一行命令即搞定编译: ➜ git clone https://github.com/sogou/workflow➜ cd workflow➜ make➜ cd tutorial➜ make➜ ./helloworld代码在 tutorial 目录,编译后的 helloworld 能够间接运行,侦听在 8888 端口,curl 即可拜访: ➜ curl -i http://localhost:8888HTTP/1.1 200 OKContent-Length: 25Connection: Keep-AliveHello World!随同着以上这10行代码,咱们具体地解读: 咱们选用 Http 协定,因而结构了一个WFHttpServer;一次网络交互就是一次工作,因为是 Http 协定,因而咱们是WFHttpTask;对server来说,我的交互工作就是收到申请之后,填好回复,这些通过:task-&gt;get_req()和task-&gt;get_resp()能够取得;逻辑在一个函数中(即下面的 lambda),示意收到音讯之后要做的事件,这里填了一句 “Hello World!”;Server启动和退出应用start()和stop()两个简略的api,而两头要用getchar();卡住,是因为 workflow 是个纯异步的框架。纯异步就是这个 Http 服务器的高性能所在: ...

April 28, 2021 · 1 min · jiezi

关于golang:狗朗NiDongDe

恩.....还想没想好这里放什么..... for range 每次迭代会创立一个数据正本进去, 如果数据量大,并且这个正本的构造比较复杂是不是很影响性能尼 ?鹤岗-阿皮: @广州-xxx 会影响性能,大数据集我个别用[...]person指针数组这样的数据结构去保护,for range是一方面,指不定哪天还要查找,能够再加个map[int]person这样的数据结构去保护。浦东~辉: @鹤岗-阿皮 个别状况下 复制比指针高效 gc因素不可疏忽菜姬: 我刷题的时候遍历slice和map都是用 for k:=range items { //items[k] 代替复制 v}以期达到pk运行工夫和运行内存的良好体现哎呀~ ♥(◡) 不晓得为什么打golang 就成"狗朗" 捏....

April 28, 2021 · 1 min · jiezi

关于golang:Go-语言指针和变量自增自减

指针name := "lily"ptr := &namefmt.Println("name:", *ptr)ptr := new(string)*ptr = "lilei"fmt.Println("name:", *ptr)func main() { res := testPtr() fmt.Println("city:", *res)}// 返回一个string类型的指针func testPtr() *string { city := "广州" ptr := &city return ptr}自增自减i++i--只有后置的自增自减,没有前置的自增自减,并且自增自减必须独自一行。

April 27, 2021 · 1 min · jiezi