关于golang:使用RaceDetector为并发代码保驾护航

明天咱们来聊聊 Golang 中剖析并发 Bug 的神器:race detector。咱们会先理解数据竞争,以及原子性等概念,进而通过理论的例子来领会和应用 race detector。文末还会剖析 dave大神 的博客上一个典型的与 data race 无关的案例。愿咱们的代码永不呈现 并发Bug~ 什么是 data race?当多个goroutine并发地拜访同一个变量,并且至多有一个拜访是写时,就会产生data race(数据竞争)由data race引起的Bug特地难以剖析,既因为它出Bug比拟“随机”,也因为并发程序人造的高复杂度。 原子赋值产生data race的重要起因是,程序的很多操作不是原子的。(原子性就是 CPU 一口气干完,不存在一个中间状态)在 Go(甚至是大部分语言)中,一条一般的赋值语句其实并不是一个原子操作。例如,在 32 位机器上写 int64 类型的变量是有中间状态的,它会被拆成两次写操作 MOV—— 写低 32 位和写高 32 位(也就是所谓的撕写),如下图所示: 命令: go tool compile -S xx.go如果一个线程刚写完低 32 位,还没来得及写高 32 位时,另一个线程读取了这个变量,那它失去的就是一个毫无逻辑的两头变量,这很有可能使咱们的程序呈现诡异的 Bug。对于 64 位的机器,一个指针的大小是 8 bytes(字节),一个 8 个字节的赋值是原子的。 race detector在 Golang1.1 版本中,Golang 引入了race detector。它能检测并报告它发现的任何 data race。咱们只须要在执行测试或者是编译的时候加上 -race 的 flag 就能够开启数据竞争的检测 go build -race 对性能有影响,除非生产环境出了很难排查的并发BUG,否则不倡议这么做。go test -race ...

April 24, 2022 · 3 min · jiezi

关于golang:Prometheus-Granfana-监控

Prometheus + Granfana 监控 Go 链路追踪代码

April 24, 2022 · 1 min · jiezi

关于golang:跨MysqlRedisMongo的分布式事务

Mysql、Redis、Mongo都是十分风行的存储,并且各自有本人的劣势。在理论的利用中,经常会同时应用多种存储,也会遇见在多种存储中保证数据一致性的需要,例如保障数据库中的库存和Redis中的库存统一等。 本文基于分布式事务框架https://github.com/dtm-labs/dtm给出了一个跨Mysql、Redis、Mongo多种存储引擎的一个可运行的分布式事务实例,心愿可能帮忙大家解决这方面的问题。 这种灵便的组合多个存储引擎造成一个分布式事务的能力,也是dtm独创做到的,目前未看到其余的分布式事务框架有这样的能力。 问题场景咱们先来看问题场景,假设当初用户加入一次流动,将本人的余额,充值话费,同时流动会赠送商城积分。其中余额存储在Mysql,话费保留在Redis,商城积分保留在Mongo,并且因为流动限时,因而可能呈现加入流动失败的状况,所以须要反对回滚。 对于上述问题场景,能够应用DTM的Saga事务,上面咱们就来具体解说计划。 筹备数据首先是筹备数据,为了不便用户疾速上手相干的例子,咱们曾经把相干的数据筹备好了,地址在en.dtm.pub,外面包含Mysql、Redis、Mongo,具体的连贯用户名明码能够在https://github.com/dtm-labs/d...找到。 如果您想要本人在本地筹备相干的数据环境,能够通过 https://github.com/dtm-labs/dtm/blob/main/helper/compose.store.yml 启动Mysql、Redis、Mongo,而后通过https://github.com/dtm-labs/dtm/tree/main/sqls上面的脚本筹备本例子的数据,其中busi.*为业务数据,barrier.*为DTM应用的辅助表 编写业务代码咱们先看最相熟的Mysql的业务代码 func SagaAdjustBalance(db dtmcli.DB, uid int, amount int) error { _, err := dtmimp.DBExec(db, "update dtm_busi.user_account set balance = balance + ? where user_id = ?", amount, uid) return err}这段代码次要是进行数据库中用户余额的调整 对于Saga事务模式来说,当咱们回滚时,咱们须要反向调整余额,这部分的解决,咱们能够仍旧调用上述的SagaAdjustBalance,只须要传入正数的金额即可。 对于Redis和Mongo,业务代码的解决也是相似的,只须要对相应的余额进行增减即可 如何做幂等对于Saga事务模式来说,当咱们的子事务服务呈现长期故障,呈现故障就会进行重试,这个故障可能呈现在子事务提交前,也可能呈现在子事务提交之后,因而子事务服务就须要做到幂等。 DTM 提供了辅助表和辅助的函数,用于帮忙用户疾速实现幂等。对于Mysql,他会在业务数据库中创立辅助表barrier,当用户开启事务调整余额时,会先在barrier表中写入gid,如果这是一个反复申请,那么写入gid时,会发现反复而失败,此时跳过用户业务上的余额调整,保障幂等。辅助函数的应用代码如下: app.POST(BusiAPI+"/SagaBTransIn", dtmutil.WrapHandler2(func(c *gin.Context) interface{} { return MustBarrierFromGin(c).Call(txGet(), func(tx *sql.Tx) error { return SagaAdjustBalance(tx, TransInUID, reqFrom(c).Amount, reqFrom(c).TransInResult) })}))Mongo解决幂等的原理与Mysql相近,不再赘述 Redis解决幂等的原理与Mysql不同,次要是因为事务的原理不同。Redis的事务次要是通过lua的原子执行来保障的。DTM的辅助函数会通过lua脚本来调整余额,调整余额前,会在redis中查问gid,如果存在,则跳过业务上的余额调整;如果不存在,则执行业务上的余额调整。辅助函数的应用代码如下: app.POST(BusiAPI+"/SagaRedisTransOut", dtmutil.WrapHandler2(func(c *gin.Context) interface{} { return MustBarrierFromGin(c).RedisCheckAdjustAmount(RedisGet(), GetRedisAccountKey(TransOutUID), -reqFrom(c).Amount, 7*86400)}))如何做弥补对于Saga来说,咱们还须要解决弥补操作,但弥补操作并不是简略的反向调整,也有很多坑须要留神,否则很容易弥补出错。一方面,弥补须要思考幂等,因为在弥补过程中,也同样须要思考故障重试的状况,与前一大节中的幂等解决一样。另一方面,弥补还须要思考空弥补,因为正向分支返回失败,这个失败可能是在正向的数据曾经调整实现提交之后的失败,也可能是还没有提交就返回了失败。对于数据已提交的失败,咱们须要执行反向操作,对于数据未提交的失败,咱们须要跳过反向操作,即解决空弥补。 ...

April 24, 2022 · 1 min · jiezi

关于golang:微服务从代码到k8s部署应有尽有系列六订单服务

咱们用一个系列来解说从需要到上线、从代码到k8s部署、从日志到监控等各个方面的微服务残缺实际。 整个我的项目应用了go-zero开发的微服务,根本蕴含了go-zero以及相干go-zero作者开发的一些中间件,所用到的技术栈根本是go-zero项目组的自研组件,根本是go-zero全家桶了。 实战我的项目地址:https://github.com/Mikaelemmm... 1、订单服务业务架构图 2、依赖关系order-api(订单api) order-rpc(订单rpc)payment-rpc(领取rpc)payment-rpc(领取rpc) mqueue-rpc(音讯队列)order-rpc(订单rpc) mqueue-rpc(音讯队列)travel-rpc(民宿rpc)3、订单举例3.1 下订单1、用户在去浏览travel服务中的民宿homestay看中抉择日期之后要下单,调用下单api接口 app/order/cmd/api/desc/order.api // 订单模块v1版本的接口@server( prefix: order/v1 group: homestayOrder)service order { @doc "创立民宿订单" @handler createHomestayOrder post /homestayOrder/createHomestayOrder (CreateHomestayOrderReq) returns (CreateHomestayOrderResp) .....}2、order-api中调用order-rpc 3、rpc中校验条件创立订单之后 ,会调用mqueue-rpc创立一个提早敞开订单的音讯队列 4、mqueue-rpc 提早队列 提早队列是用的asynq,asynq是基于redis的高性能队列,同时反对音讯队列、定时队列、固定周期队列,然而咱们这个我的项目为了演示go-zero官网的音讯队列go-queue(go-queue是基于kafka的),所以音讯队列用的go-queue,提早队列、定时工作用asynq。这里留神,这里只是往提早队列增加提早工作,具体执行不在这里,那咱们去看看20分钟之后具体执行的代码,在app/order/cmd/mq 5、提早20分钟执行的工作队列 在app/order/cmd/mq,这里我要阐明一下,go-zero官网goctl反对生成的服务目前是api、rpc,目前临时还没有反对console、mq等,然而go-zero提供了ServiceGroup,不便咱们治理咱们本人任何的服务,所以在mq中我是用了ServiceGroup治理了服务,这也是官网举荐的应用形式,代码如下: 1)app/order/cmd/mq/order.go 首先咱们看main.go func main() { flag.Parse() var c config.Config conf.MustLoad(*configFile, &c) prometheus.StartAgent(c.Prometheus) serviceGroup := service.NewServiceGroup() defer serviceGroup.Stop() for _, mq := range listen.Mqs(c) { serviceGroup.Add(mq) } serviceGroup.Start()}ServiceGroup 能够增加任何service进去,然而如何成为一个service呢? 那你就要实现两个办法一个Starter、一个Stoper 2)咱们在main中能够看到循环listen.Mqs(c) ,那咱们看看listen.Mqs(c) 都有哪些 ...

April 24, 2022 · 1 min · jiezi

关于golang:gomicro开发RPC服务的方法及其运行原理

go-micro是一个出名的golang微服务框架,最新版本是v4,这篇文章将介绍go-micro v4开发RPC服务的办法及其运作原理。 基本概念go-micro有几个重要的概念,后边开发RPC服务和介绍其运行原理的时候会用到,这里先相熟下: Service:代表一个go-micro应用程序,Service中包含:Server、Client、Broker、Transport、Registry、Config、Store、Cache等程序运行所需的各个模块。Server:代表一个go-micro服务器,次要函数包含:Start、Stop、Handle、Subscribe。默认创立的Server是 rpcServer。Broker:用于解决异步音讯,次要的函数包含:Connect、Publish、Subscribe。默认的Broker是httpBroker。Router:用于音讯解决的路由,外部包含两种路由形式:RPC服务映射serviceMap和音讯订阅器subscribers。Codec:用于音讯的编解码,次要函数包含:Marshal、Unmarshal默认的Codec是json.Marshaler,是基于jsonpb的。RPC服务是依据申请头中的Content-Type主动创立的。Registry:用于服务发现,次要函数包含:Register、Deregister、GetService、ListServices、Watch。默认的Registry是mdns。Selector: 用于从同一个服务的多个实例之中抉择一个,反对缓存,有随机和轮询两种策略。Transport:用于同步通信,次要函数包含:Dial、Listen。它的底层基于Socket的send、recv语义,有多种实现,包含http、grpc、quic等。默认的Transport是httpTransport。开发RPC服务RPC全称是Remote Procedure Call,翻译过去是就是:近程过程调用,中心思想是:像调用本地函数一样调用近程函数。常见的Dubbo、Spring Cloud都能够称为RPC框架,还有最近很风行的gRPC。 应用go-micro创立一个RPC服务很简略,共分三步走: 1、编写proto协定文件这个服务提供的性能很简略,名字为Hello,提供一个办法名字为Say,须要传入一个字符串Name,而后返回一个字符串Message。这个文件我命名为 hello.proto,放到了我的项目中的 proto 文件夹中。 syntax = "proto3";option go_package="/proto";package Business;service Hello { rpc Say (SayRequest) returns (SayResponse);}message SayResponse { string Message = 1;}message SayRequest { string Name = 1;}2、生成go-micro服务端代理须要首先装置protoc和两个代码生成插件。 protoc下载地址:https://github.com/protocolbu...,保留到 GOPATH/bin目录中。同时倡议将 GOPATH/bin 增加到环境变量 PATH 中,不便间接执行相干命令。 两个插件间接通过命令即可装置: go install google.golang.org/protobuf/cmd/protoc-gen-gogo install go-micro.dev/v4/cmd/protoc-gen-micro@v4而后在我的项目的目录下执行命令: protoc --go_out=. --go_opt=paths=source_relative --micro_out=. --micro_opt=paths=source_relative proto/hello.proto而后会在proto文件夹中生成两个文件:hello.pb.go 和 hello.pb.micro.go 。 下个步骤中就要应用它们来创立RPC服务。 3、编写go-micro服务这里先把代码贴出来,而后再做一个简要阐明: package mainimport ( "context" "fmt" "log" "rpchello/proto" "go-micro.dev/v4" "go-micro.dev/v4/server")type Hello struct{}func (s *Hello) Say(ctx context.Context, req *proto.SayRequest, rsp *proto.SayResponse) error { fmt.Println("request:", req.Name) rsp.Message = "Hello " + req.Name return nil}func main() { rpcServer := server.NewServer( server.Name("rpchello.service"), server.Address("0.0.0.0:8001"), ) proto.RegisterHelloHandler(rpcServer, &Hello{}) service := micro.NewService( micro.Server(rpcServer), ) if err := service.Run(); err != nil { log.Fatal(err) }}上边咱们创立了一个 Hello 类型,而后给它绑定了一个名为Say的函数。这个是和proto协定对应的,其实是实现了生成代码 hello.pb.micro.go 中的HelloHandler接口: ...

April 24, 2022 · 1 min · jiezi

关于golang:Mysql事务实现原理以及在Go语言中使用数据库事务

数据库并发拜访问题数据库应用中通常存在多个客户端同时拜访数据库,因而数据库系统要可能解决这种并发拜访的状况。在理论工作中,并发拜访时数据库应用中的常态,然而并发拜访时数据库时,可能呈现以下问题: 脏读:以后事务读到其余事务未提交的数据(脏数据),这种景象是脏读。在这里咱们应用一个简略的订单表阐明什么状况下会呈现脏读,订单表(order)的表构造如下:字段名数据类型形容idbigint(20)自增idnovarchar(64)订单编号statustinyint(4)订单状态:0 待领取 1 已领取 2 实现假如以后事务为A,在事务A中读取订单的状态。并假如事务B与事务A并发执行,在事务B中进行订单状态的批改操作。 工夫点事务A事务BT1开始事务开始事务T2 批改订单状态:0=>1T3查问订单状态(此时是1) T4 回滚操作(此时是0)在这个例子中,事务A读取了未提交的数据,获取了谬误的数据。 不可反复读:在以后事务中先后两次读取同一个数据,两次读取的后果不一样,这种景象称为不可反复读。脏读与不可反复读的区别在于:前者读到的是其余事务未提交的数据,后者读到的是其余事务已提交的数据。同样采纳订单状态的例子来阐明那种状况下会呈现不可反复读。工夫点事务A事务BT1开始事务开始事务T2查问订单状态(此时是0) T2 批改订单状态:0=>1T4 提交事务T5查问订单状态(此时是1) 在一个事务中前后两次读取的后果并不致,导致了不可反复读。 幻读:在以后事务中依照某个条件先后两次查询数据库,两次查问后果的条数不同,这种景象称为幻读。不可反复读与幻读的区别能够艰深的了解为:前者是数据变了,后者是数据的行数变了。 还是采纳订单状态的例子来阐明,不过在这个例子中将事务A与事务B的性能批改一下,在事务A中执行查问全副订单的操作,在并发执行的的事务B中执行增加订单的操作。工夫点事务A事务BT1开始事务开始事务T2查问订单数量(此时订单数量是1) T2 增加一个新的订单(此时订单数量是2)T4 提交事务T5查问订单数量(此时订单数量是2) 在一个事务中前后两次读取的后果的条数不同,导致了幻读。 数据长久化问题:InnoDB以数据页为单位来读写文件,为了晋升性能InnoDB采纳了缓冲池(Buffer Pool)。读数据会首先从缓冲池中读取,如果缓冲池中没有,则首先从磁盘读取数据页并退出缓冲池。之后如果批改了一个数据页,也不会立刻写到磁盘文件,而是把这个数据页增加到缓冲池的flush链表中,而后再某个工夫再将数据保留到磁盘。缓冲池技术尽管会提交数据库的性能,然而会存在失落数据的问题。对于一个曾经提交的事务,如果数据还没有刷新到磁盘时出现异常,则会失落这些数据。可能的异常情况包含: 数据库异样操作系统异样零碎断电事务的定义在数据库中应用事务来解决数据库并发拜访问题以及数据长久化问题。在数据库中一个事务是由一条或多条SQL语句所组成的一个的执行单元,只有当事务中的所有操作都失常执行完了,整个事务才会被提交给数据库。如果有局部SQL语句解决失败,那么事务就回退到最后的状态。数据库事务应该具备以下的四大个性: 原子性事务的原子性事务中的操作必须作为一个整体来执行,一个事务中的操作要么全副胜利提交,要么全副失败回滚,对于一个事务来说不可能只执行其中的局部操作。持久性 事务的持久性是指当事务提交之后,数据库的扭转就应该是永久性的,即事务一旦提交,其所作做的批改会永恒保留到数据库中,此时即便零碎解体批改的数据也不会失落。隔离性隔离性是指,事务外部的操作与其余事务是隔离的,并发执行的各个事务之间不能相互烦扰。一致性一致性是指事务执行完结后,数据库的完整性束缚没有被毁坏,事务执行的前后都是非法的数据状态。一致性是事务谋求的最终目标,原子性、持久性和隔离性,实际上都是为了保障数据库状态的一致性而存在的。Mysql事务隔离级别Mysql隔离级别有以下四种(级别由低到高): read uncommited(未提交读)最低的隔离级别,这种级别下能够脏读、不可反复读、幻读都有可能产生。read commited(已提交读)一个事务的批改在他提交之前的所有批改,对其余事务都是不可见的。其余事务能读到已提交的批改变动。在很多场景下这种逻辑是能够承受的。然而该级别会产生不可重读以及幻读问题。repeatable read(可反复读)在一个事务内的屡次读取的后果是一样的。这种级别下能够防止,脏读,不可反复读等查问问题。serializable(串行化)事务串行化执行,隔离级别最高,这种级别下不会造成数据不统一问题,然而会就义零碎的并发性。在理论利用中,读未提交在并发时会导致很多问题,而性能绝对于其余隔离级别进步却很无限,因而应用较少。可串行化强制事务串行,并发效率很低,只有当对数据一致性要求极高且能够承受没有并发时应用,因而应用也较少。因而在大多数数据库系统中,默认的隔离级别是读已提交(如Oracle)或可反复读。 应用redo日志实现事务持久性MySQL为了进步的性能,对于增、删、改这种操作都是在内存中实现的。数据的增删改查都是先将磁盘中数据页的数据加载到缓冲池中,而后再对缓存页中的数据进行操作。MySQL有专门的后盾线程等其余机制负责将脏数据页刷新到磁盘。因为脏数据页不是实时刷新到磁盘,若产生异样,那些曾经提交然而没有保留的数据将会失落,这样就不能保障事务的持久性了。为了解决这个问题,InnoDB数据库引擎采纳了redo日志的机制。redo日志会把事务在执行过程中对数据库所做的批改都记录下来,在之后零碎解体重启后能够把事务所做的任何批改都复原进去。 为什么不间接将数据页刷新到磁盘,而是采纳redo日志来实现数据的持久性,次要因为: 首先一个缓存页的大小为16K,如果一个缓存页中只批改了大量的数据,也要将一个残缺的缓存页刷入磁盘,这样显然不是很正当。另外的起因是一个事务可能蕴含很多语句,即便是一条语句也可能批改许多页面,这些页面通常都不是一个间断的存储区域。间接保留这些页面须要进行多个磁盘IO,造成对磁盘进行随机拜访,从而升高零碎的性能。应用redo日志可能防止上述问题,redo日志有以下有点: redo日志占用的空间十分小。redo日志是程序写入磁盘的。另外须要阐明的是redo日志也不肯定是立刻刷新的磁盘,MySQL提供了零碎变量:innodb_flush_log_at_trx_commit来管制redo日志的刷盘。该零碎变量有3个选项: 0:提交事务不会立刻刷盘,MySQL应用后盾线程来刷新日志到磁盘。该做法的长处是可能升高磁盘IO次数,进步零碎的性能。毛病是零碎异样时可能会失落数据。1:若提交事务会立刻将日志刷新到磁盘,该选项可能确保事务的持久性。2:若提交事务会立刻将日志刷新到操作系统的缓存,而后依靠于操作系统的刷新机制将数据同步到磁盘中。这种状况下数据库异样时不会失落数据,但若是操作系统异样,也不能保证数据的持久性。除了事务提交时,还有其余刷盘机会: redo log buffer的空间有余时,若日志量达到redo log buffer的总容量的50%时会将这些日志刷新到磁盘。后盾线程每隔一秒将redo log buffer外面的redo log block刷入磁盘。保留数据页时会将相干的redo日志刷新到磁盘。应用undo日志实现事务原子性Mysql数据库undo日志用于实现事务的原子性。事务执行过程中,以下两种状况会造成事务只执行了一部分操作: 事务执行过程中呈现了异样,例如:数据库异样、操作系统异样,服务器断电。事务执行了局部语句后,数据库的客户端输出ROLLBACK语句完结以后事务的执行。上述两种状况中事务只执行了局部逻辑,在事务的执行中可能曾经批改数据库的数据。为了确保事务的原子性,须要将曾经批改的内容复原为事务初始执行的状态,即对事务进行回滚。 Mysql数据库应用undo日志来实现事务回滚。在事务执行过程中,除了记录redo日志,还会记录undo日志。msyql执行事务时,会记录数据库操作(insert、delete、update)的回滚信息,这些回滚信息称为:undo日志。有了undo日志,若事务执行过程中出现异常或者客户端手动回滚,就能够应用undo日志将数据恢复为事务执行前的状态。 undo日志有两个作用,一是用于事务回滚,二是实现MVCC性能。 应用MVCC实现事务的隔离Mysql中有两种机制可用于解决读写并发抵触,一种形式是采纳锁机制,读、写操作都要进行加锁;另外一种形式是读操作应用MVCC,写操作加锁。因为应用MVCC时读不加锁,读写能够并发执行,零碎的性能好,因而Mysql数据库在隔离等级read commited、repeatable read下缺省采纳了MVCC机制来解决读写并发抵触。MVCC(Multi-Version Concurrency Control)被称为多版本并发管制,次要是依赖undo日志以及Read View(一致性视图)来实现事务的并发读写。在事务执行过程中,不同的事务对一个记录进行的批改,都会生成一条undo日志。每条undo日志都有一个roll_pointer属性,能够将这些undo日志都连起来,串成一个链表,这个链表就叫做:版本链。下图是一个版本链的示意图: 在事务中查问该记录时,会产生一个Read view(一致性视图),Read view相当于给以后数据库生成了一个快照,记录并且保护以后沉闷的事务的ID。而后Mysql读取undo日志的版本链,把以后undo日志的事务ID与Read view中的沉闷事务ID进行比拟,来获取最新的已提交的记录数据。比拟的规定如下: 以后事务ID如果小于min_id(最小的沉闷事务ID),则以后事务可见。以后事务ID如果大于max_id(零碎的最大事务ID),则以后事务不可见。以后事务大于min_id小于max_id,再判断是沉闷事务,如果是阐明事务还没提交,以后事务不可见。如果不是沉闷事务,则以后事务可见。若以后事务为不可见,则须要持续遍历undo日志的版本链,并依照上述规定进行比拟。应用锁实现事务的隔离应用MVCC时读不加锁,读写能够并发执行,零碎的性能好,然而MVCC只能用于读写事务的场景中,其余的场景则必须应用锁来解决事务的并发问题。另外有些状况下须要应用锁定读语句来查问记录。 锁的分类1)从操作的粒度可分为表级锁、行级锁 表级锁:每次操作锁住整张表。锁定粒度大,产生锁抵触的概率最高,并发度最低。行级锁:每次操作锁住一行数据。锁定粒度最小,产生锁抵触的概率最低,并发度最高。2)从操作的类型可分为共享锁和排他锁 共享锁:共享锁也称为读锁、S锁。一条数据被加了S锁之后,其余事务也能来读数据,能够共享一把锁。排他锁排他锁也称为写锁,X锁。以后写操作没有实现前,它会阻断其余写锁和读锁。3)意向锁 意向锁为表锁,分为两种类型:动向共享锁(简称为IS)和动向排他锁(简称为IX)。意向锁有应用规定为: 当须要给一行数据加上S锁的时候,MySQL会先给这张表加上IS锁。当须要给一行数据加上X锁的时候,MySQL会先给这张表加上IX锁。InnoDB中的行级锁Record Locks(记录锁)官网的类型名称为:LOCK_REC_NOT_GAP,记录锁又分为S锁和X锁。Gap Locks(间隙锁)官网的类型名称为:LOCK_GAP,间隙锁的提出仅仅是为了避免插入幻影记录。Next-Key Locks(临键锁)官网的类型名称为:LOCK_ORDINARY,临键锁的实质就是一个记录锁和一个间隙锁的合体,它既能爱护该条记录,又能阻止别的事务将新记录插入被爱护记录前边的间隙。Insert Intention Locks(插入意向锁)官网的类型名称为:LOCK_INSERT_INTENTION。一个事务在插入一条记录时须要判断一下插入地位是不是被别的事务加了间隙锁,如果有的话,插入操作须要期待,在期待时事务须要在内存中生成一个锁构造,表明有事务想在某个间隙中插入新记录,然而当初在期待,而这个锁构造就是插入意向锁。SQL语句加锁阐明1) SELECT语句在不同的隔离级别下,一般的SELECT语句有不同的加锁状态: ...

April 24, 2022 · 2 min · jiezi

关于golang:LotusDB-设计与实现1-基本概念

LotusDB 是一个全新的 KV 存储引擎,Github 地址:https://github.com/flower-corp/lotusdb,心愿大家多多反对呀,点个 star 或者参加进来!LotusDB 是一个基于 LSM Tree 进行设计,并联合 B+ 树劣势的单机 KV 存储引擎,读写性能稳固、疾速。 在传统的 LSM Tree 架构中,增删数据均是追加有序写入到 SST 文件中,雷同的 key 对应的数据可能存在多份,须要通过简单的 compaction 策略来进行空间回收,这同时带来了空间放大和写放大问题。 LSM Tree 在磁盘上保护多级 SSTable 文件,在数据读取时,须要逐层扫描文件来查找指定的数据,最坏状况下须要扫描每一层的 SSTable,读性能不稳固。 和 LSM Tree 绝对应的,另一种常见的数据存储模型是 B+ Tree,B+ 树因为有着很好的适配磁盘页的个性,在数据库存储引擎中广泛应用,例如最为人熟知的 Mysql 的 InnoDB 引擎。 B+ Tree 将数据保护在树最底层叶子节点中,读性能比较稳定,然而数据的插入和更新均是随机 IO 进行写入,导致 B+ Tree 的写性能绝对较低。 咱们晓得,LSM 存储模型诞生于 HDD(机械硬盘) 时代,HDD 的随机和程序读写速度差异微小,所以 LSM 的设计最大限度的施展了程序 IO 的劣势,所有的数据先到内存 buffer 里缓存,而后批量有序写入到文件中。然而随着存储硬件的更新迭代,磁盘的随机和程序读写差异变小了,在一些介质中,程序和随机读写甚至没有太大的差异。 LSM Tree 针对程序 IO 的一些设计就会显得过于简单,导致整个零碎难以实现和管制(如果你相熟 rocksdb 的话,就会深有体会)。 自行设计一个零碎的底层存储引擎,比把握一个简单的我的项目要更加容易,呈现了相干的问题也更容易定位和解决,这也是为什么 cockroach 采纳自研的 Pebble 存储引擎代替 rocksdb,而 LotusDB 就是一个这样能够轻易学习和把握的存储引擎,因为它简洁、直观且高效。 ...

April 23, 2022 · 1 min · jiezi

关于golang:Go语言学习笔记读锁重入导致死锁

景象func TestLock(t *testing.T) { l := &lockWrapper{} var wg sync.WaitGroup wg.Add(2) go func(){ s := 0 for i := 0 ; i < 0 ; 1000000; i++{ s += l.Get() } t.Log(s) wg.Done() }() go func() { for i := 0 ; i < 0 ; 1000000; i++{ l.Set(i) } t.Log(s) wg.Done() }() wg.Wait()}type lockWrapper struct { mu sync.RWMutex a int}func (l *lockWrapper) Get() int { l.mu.RLock() defer l.mu.RUnlock() //... l.mu.RLock() defer l.mu.RUnlock() return a}func (l *lockWrapper) Set(s int) { l.mu.Lock() defer l.mu.Unlock() l.a = s}上述代码可能导致死锁 ...

April 22, 2022 · 1 min · jiezi

关于golang:RPCX源码学习client端

rpc相干介绍见上一篇:https://segmentfault.com/a/11...client端源码分析首先创立连接池: // NewXClientPool creates a fixed size XClient pool.func NewXClientPool(count int, servicePath string, failMode FailMode, selectMode SelectMode, discovery ServiceDiscovery, option Option) *XClientPool { pool := &XClientPool{ // 连接池对象 count: uint64(count), xclients: make([]XClient, count), servicePath: servicePath, failMode: failMode, selectMode: selectMode, discovery: discovery, option: option, } for i := 0; i < count; i++ {// 创立client,数量跟count雷同 xclient := NewXClient(servicePath, failMode, selectMode, discovery, option) pool.xclients[i] = xclient } return pool}// XClientPool is a xclient pool with fixed size.// It uses roundrobin algorithm to call its xclients.// All xclients share the same configurations such as ServiceDiscovery and serverMessageChan.type XClientPool struct { count uint64 // 池子中保留的连贯数量 index uint64 // 从池子里获取连贯时,用来定位获取哪个连贯 xclients []XClient // 客户端对象 servicePath string // rpc服务名称 failMode FailMode // rpc调用失败后的解决形式 selectMode SelectMode // 路由策略 discovery ServiceDiscovery // 服务发现 option Option serverMessageChan chan<- *protocol.Message}// NewXClient creates a XClient that supports service discovery and service governance.func NewXClient(servicePath string, failMode FailMode, selectMode SelectMode, discovery ServiceDiscovery, option Option) XClient { client := &xClient{ // 创立client对象 failMode: failMode, selectMode: selectMode, discovery: discovery, servicePath: servicePath, // rpc服务名称,一个裸露进去的服务会提供多个办法 cachedClient: make(map[string]RPCClient), option: option, } // 获取注册的服务配置 pairs := discovery.GetServices() servers := make(map[string]string, len(pairs)) for _, p := range pairs { servers[p.Key] = p.Value } filterByStateAndGroup(client.option.Group, servers) client.servers = servers if selectMode != Closest && selectMode != SelectByUser { // 设置路由形式 client.selector = newSelector(selectMode, servers) } client.Plugins = &pluginContainer{} // 目前固定返回nil ch := client.discovery.WatchService() if ch != nil { client.ch = ch go client.watch(ch) } return client}创立完连接池之后,等到用的时候,间接从池子里拿一个就行: ...

April 22, 2022 · 3 min · jiezi

关于golang:泛型最佳实践Go泛型设计者教你如何用泛型

前言Go泛型的设计者Ian Lance Taylor在官网博客网站上发表了一篇文章when to use generics,具体阐明了在什么场景下应该应用泛型,什么场景下不要应用泛型。这对于咱们写出合乎最佳实际的Go泛型代码十分有指导意义。 自己对原文在翻译的根底上做了一些表述上的优化,不便大家了解。 原文翻译Ian Lance Taylor 2022.04.14 这篇博客汇总了我在2021年Google开源流动日和GopherCon会议上对于泛型的分享。 Go 1.18版本新增了一个重大性能:反对泛型编程。本文不会介绍什么是泛型以及如何应用泛型,而是把重点放在解说Go编程实际中,什么时候应该应用泛型,什么时候不要应用泛型。 须要明确的是,我将会提供一些通用的指引,这并不是硬性规定,大家能够依据本人的判断来决定,然而如果你不确定如何应用泛型,那倡议参考本文介绍的指引。 写代码Go编程有一条通用准则:write Go programs by writing code, not by defining types. 具体到泛型,如果你写代码的时候从定义类型参数束缚(type parameter constraints)开始,那你可能搞错了方向。从编写函数开始,如果写的过程中发现应用类型参数更好,那再应用类型参数。 类型参数何时有用?接下来咱们看看在什么状况下,应用类型参数对咱们写代码更有用。 应用Go内置的容器类型如果函数应用了语言内置的容器类型(包含slice, map和channel)作为函数参数,并且函数代码对容器的解决逻辑并没有预设容器里的元素类型,那应用类型参数(type parameter)可能就会有用。 举个例子,咱们要实现一个函数,该函数的入参是一个map,要返回该map的所有key组成的slice,key的类型能够是map反对的任意key类型。 // MapKeys returns a slice of all the keys in m.// The keys are not returned in any particular order.func MapKeys[Key comparable, Val any](m map[Key]Val) []Key { s := make([]Key, 0, len(m)) for k := range m { s = append(s, k) } return s}这段代码没有对map里key的类型做任何限定,并且没有用map里的value,因而这段代码实用于所有的map类型。这就是应用类型参数的一个很好的示例。 ...

April 22, 2022 · 2 min · jiezi

关于golang:muduo源码分析之muduo简单运用

明天不先实现muduo我的项目,咱们先来看下muduo库的根本应用,只有理解了如何用,能力在写代码的时候晓得本人写的找个函数是干嘛的,实际上是怎么应用的这个函数。首先说简略点,就是定义一个Server,设置两个回调函数 // 回调连贯相干的事件void onConnection(const TcpConnectionPtr &conn);// 回调读写事件void onMessage(const TcpConnectionPtr &conn, Buffer *buffer, Timestamp time);意思就是当有客户连贯或者断开连接的时候,须要Server做什么,当连贯上有读写事件产生时候,须要Server做什么。比方一个EchoServer,当建设连贯的时候,会主动调用onConnection函数,当比方咱们发送一个音讯时候,会主动调用onMessage函数。还有2个重要函数loop,start server.start();loop.loop();这里简答讲下这2个的区别,其实如果和Epoll做比照的话,start就相当于epoll_create,loop就相当于epoll_wait,前面再依据代码具体阐明2个的区别。以上就是根本的muduo应用,下一章就开始具体的muduo代码实现。 本人的网址:www.shicoder.top欢送加群聊天 452380935本文由博客一文多发平台 OpenWrite 公布!

April 22, 2022 · 1 min · jiezi

关于golang:muduo源码分析之Buffer

这一次咱们来剖析下muduo中Buffer的作用,咱们晓得,当咱们客户端向服务器发送数据时候,服务器就会读取咱们发送的数据,而后进行一系列解决,而后再发送到其余中央,在这里咱们设想一下最简略的EchoServer服务器,客户端建设一个连贯,当前服务器和客户端之间的通信都是通过这个connfd发送和承受数据,于是每一个connfd都应该有一个本人buffer,当咱们发送数据太快,服务器发送的太慢,则服务器会将待发送的数据这个buffer中,所以这就是这个类的作用。咱们先看下buffer的构造是什么: 咱们这里次要针对connfd这个对应的channel进行剖析,首先上图是buffer的初始状态,后面8个字节中示意该buffer的大小,初始大小为1024。当客户端发送数据给服务器,同时若服务器承受迟缓,则会向buffer中开始写数据,则writerIndex_会向右挪动,如果此时挪动到如下模式: 则此时缓冲区能够读的数据为writerIndex_ - readerIndex_,能够写的数据为buffer_.size() - writerIndex_。这时候当服务器有多余资源进行读操作,就能够去缓冲区读数据了,如果这时候的状态为如下: 这就是常见的几个状态,上面咱们去看几个重点的函数: // 把onMessage函数上报的buffer内容转为string std::string retrieveAllAsString() { return retrieveAsString(readableBytes()); // 利用可读取数据的长度 } // 可读的数据 就是寄存的是即发送的数据 size_t readableBytes() const { return writerIndex_ - readerIndex_; } std::string retrieveAsString(size_t len) { // 从可读数据开始地位,长度为len的char结构为一个string std::string result(peek(), len); retrieve(len); // 下面一句把缓冲区中可读的数据,曾经读取进去,这里必定要对缓冲区进行复位操作 return result; } // 将缓冲区len的长度进行复位 void retrieve(size_t len) { // 示意还没有读完数据 if (len < readableBytes()) { readerIndex_ += len; // 利用只读取了刻度缓冲区数据的一部分,就是len,还剩下readerIndex_ += len -> writerIndex_ } else // len == readableBytes() { retrieveAll(); } }以上是根本的操作,上面的2个函数很重要,一个是向connfd写数据,一个是读数据,对于一个TcpConnection而言,当有数据来的时候,回去调用handleRead回调函数。咱们晓得muduo设置的每次读取的大小为65536字节,当缓冲区可写的数据大小大于65536,就会间接将读到的数据写入到缓冲区中,但当缓冲区的可写数据大小小于65536的时候,就会将残余数据先写到一个额定的空间 ...

April 22, 2022 · 2 min · jiezi

关于golang:Lightly新一代的-Go-IDE

Go 又称为 Golang,一种编译型且具备垃圾回收性能的编程语言。作为一种较年老的编程语言,谷歌于 2007 年开始设计 Go 编程语言,并于 2009 年正式推出。 因为开发者们对过来繁琐而效率低下的编程语言感到腻烦,Go 便从这样的气氛中诞生,并逐年开始占据编程的重要位置。在 TIOBE 的编程语言排名中,Go 现在已跃升至第 14 位,紧逼原先占支流的编辑器。(抵赖吧,你抉择 Go 是因为它的吉祥物。) 新一代的 Go IDE —— Lightly 在 Lightly 的 Go IDE 中,咱们参考了泛滥程序员的编程习惯和需要,总结了以下特色作为咱们的首发性能: 完全免费。无需再为每个月的 IDE 月费担心,即使是初尝编程的学生党,也能轻松无累赘地应用轻量且功能强大的编辑器(IDE)。语法高亮、智能提醒、主动补全对程序员而言是编程中必不可少的效率利器,语法高亮能让代码中的各项参数变得更清晰,智能提醒和主动补全更是免去了死记硬背、反复打字等麻烦。将来,咱们还将退出自定义模块,让 Lightly 变成你专属的编辑器。网页、客户端双模式编程。Lightly Go IDE 通过云端技术,让用户实现在不同操作系统和浏览器中编写代码并运行 Go 我的项目。Lightly 会将用户的代码实时保留在云端,无需任何操作即可从任意浏览器或客户端上登录账号并持续实现工作。自动检测及配置环境。作为程序员的一分子,Lightly 团队深知切换编辑器及设施时,繁琐的环境配置对许多人而言是一项难题。咱们在这一步骤上开发了自动检测和配置环境性能,缩小环境配置为程序员们带来的困扰,同时也为初尝编程的老手程序员们护航。简洁的操作页面。极简主义始终是编程的大趋势,Lightly 简洁的图形化操作界面能让用户专一于编写代码,进一步增进编程效率。一键分享代码,在线多人合作。无论你是编程区博主或只是想把代码分享给共事,Lightly 的分享性能都能满足你的需要。只须要生成并复制链接,即可无忧分享你的代码。你甚至还能够通过内置的多人合作性能,邀请敌人退出你的我的项目,共同完成编写工作!除了上述性能外,咱们还踊跃参考用户们的反馈与意见,依据需求量安排技术路线,从用户的角度登程尽力满足大家的编辑器的需要,呼声颇高的主题模式切换也在热火朝天地进行中: 如果你对编辑器还有更多的需要与期待,无妨通知咱们的团队,让咱们与你携手打造出更精彩的编程环境。 TeamCode 官网 | www.teamcode.comLightly 官网 | lightly.teamcode.com分割咱们 | lightly@teamcode.com

April 22, 2022 · 1 min · jiezi

关于golang:解决gomicro与其它gRPC框架之间的通信问题

在之前的文章中别离介绍了应用gRPC官网插件和go-micro插件开发gRPC应用程序的形式,都能失常走通。不过当两者混合应用的时候,相互拜访就成了问题。比方应用go-micro插件生成的gRPC客户端拜访基于gRPC官网插件创立的服务端时就会呈现如下谬误: {"id":"go.micro.client","code":501,"status":"Not Implemented"}通过一番摸索,发现是因为go-micro的插件生成代码时抛弃了proto定义中的package,客户端API和服务端API都没有应用这个package,所以它本人也能逻辑自洽,然而和其它框架或者语言的gRPC服务通信时就呈现问题了。 这里以 hello.proto 为例: syntax = "proto3";option go_package="/proto";package Business;service Hello { rpc Say (SayRequest) returns (SayResponse);}message SayResponse { string Message = 1;}message SayRequest { string Name = 1;}对于客户端代理,protoc-gen-go-grpc生成的是: err := c.cc.Invoke(ctx, "/Business.Hello/Say", in, out, opts...)protoc-gen-micro生成的是: req := c.c.NewRequest(c.name, "Hello.Say", in)能够显著看到,go-micro生成的gRPC method中短少package。当然这个method的格调也有些差别,不过这个不是问题,因为go-micro还会它进行一些格式化解决,格式化代码在grpc插件中。 plugins/client/grpc/request.go : func methodToGRPC(service, method string) string { // no method or already grpc method if len(method) == 0 || method[0] == '/' { return method } // assume method is Foo.Bar mParts := strings.Split(method, ".") if len(mParts) != 2 { return method } if len(service) == 0 { return fmt.Sprintf("/%s/%s", mParts[0], mParts[1]) } // return /pkg.Foo/Bar return fmt.Sprintf("/%s.%s/%s", service, mParts[0], mParts[1])}能够看到go-micro间接把服务名称作为了package名称,这两者不能等同,不雷同时就会呈现问题。 ...

April 22, 2022 · 1 min · jiezi

关于golang:Prometheus-的使用

Prometheus是什么?Prometheus是一套开源的监控&报警&工夫序列数据库的组合。 Prometheus数据模型Prometheus 从根本上所有的存储都是按工夫序列去实现的,每条工夫序列是由惟一的 指标名称 和 一组 标签 (key=value)的模式组成。 指标名称通常代表了监控对象的名称,能够简略了解为数据表的表名 标签就是对一条工夫序列不同维度的辨认了,能够简略了解为数据表的字段。 四种指标类型 Counter 计数器一种累加的 metric,典型的利用如:申请的个数,完结的工作数,呈现的谬误数等。随着客户端一直申请,数值越来越大。 Gauge 计量器与Counter不同,Gauge类型的指标侧重于反馈零碎的以后状态。因而这类指标的样本数据可增可减,比方监控cpu使用率,内存占用等提供了增、减相干的办法. Histogram 累积直方图直方图,柱状图。罕用于跟踪事件产生(通常是申请持续时间或响应大小)的规模,例如:申请耗时、响应大小。它特别之处是能够对记录的内容进行分组,提供 count 和 sum 全副值的性能。 Summary跟 histogram 相似,summary 也对观测值(相似申请提早或回复包大小)进行采样。同时它会给出一个总数以及所有观测值的总和,它在一个滑动的工夫窗口上计算可配置的分位数。, 典型的利用如:申请持续时间,响应大小。次要做统计用,设置分位数的值,会实时返回该分位数上的值。 服务端收集监控数据次要有两种形式。1、Prometheus server间接到client客户端拉取2、由客户端将metrics推送至push gateway服务,再由prometheus server到push gateway拉取 具体实例: package mainimport ( "github.com/gin-gonic/gin" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp")//定义指标var ( requestHistogram = prometheus.NewHistogramVec(prometheus.HistogramOpts{ Namespace: "http_server", Subsystem: "", Name: "requests_seconds", Help: "Histogram of response latency (seconds) of http handlers.", ConstLabels: nil, Buckets: []float64{0, 0.01, 0.05, 0.1, 0.2, 0.3, 0.4, 0.5, 1, 2, 5, 10}, }, []string{"userId", "requestType"}))//注册监控指标func init() { prometheus.MustRegister(requestHistogram)}func main() { r := gin.Default() r.GET("/metrics", gin.WrapH(promhttp.Handler())) //收集数据 requestHistogram.With(prometheus.Labels{"userId": "11111", "requestType": "商品详情页"}).Observe(1.0) r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")}间接拜访: 127.0.0.1:8080/metrics 查看prometheus的数据 ...

April 21, 2022 · 1 min · jiezi

关于golang:muduo项目介绍

在上一个集群聊天服务器我的项目中,我应用了muduo作为网络库,而后次要实现了业务逻辑等,所以为了深刻网络库的代码和实现,我跟着一位老师的代码去实现了muduo库的基本原理和作用,当然只是实现了主体的代码,有些细节便没有深究,以下是本人的指标: 从开源C++ muduo网络库,学习优良的代码设计把握基于事件驱动和事件回调的epoll+线程池面向对象编程实现TcpServer、TcpConnection、Poller、Chanel等重要局部重写muduo外围组件,去依赖boost,用C++11重构测试代码是否胜利本人的网址:www.shicoder.top欢送加群聊天 452380935本文由博客一文多发平台 OpenWrite 公布!

April 21, 2022 · 1 min · jiezi

关于golang:环形链表1和2双指针详解

这篇文章次要探讨 leetcode 141leetcode 142 首先,问题141和142都是不保障链表肯定有环路,因而咱们首先须要判断链表是否有环路。其次对于142,还要判断链表环路开始。 双指针判断是否有环路设置一个快指fast针和一个慢指针slow,初始都指向head。前面fast每次挪动两步,slow每次挪动一步。加如链表有环,在某一个工夫,fast肯定会追上slow,和slow重合,且相对不会跳过slow。因为fast的速度为2,slow速度为1,以slow为参考系,因而fast绝对于slow的速度为fast-slow=1。fast绝对slow每次只向前一步,因而不会跳过slow。 因而141的答案能够写成这样,要留神fast后退时,须要别离判断fast.Next,fast.Next.Next是否为空。而后因为fast快于slow,因而fast探路完了不须要再判断slow。 func hasCycle(head *ListNode) bool { if head==nil{ return false } started:=false fast,slow:=head,head for !started || fast!=slow { started=true if fast.Next==nil || fast.Next.Next==nil{ return false }else{ fast=fast.Next.Next } slow=slow.Next } return true}判断环路起始点这里借用官网图,链表头head到入环点间隔为a。b+c为一个残缺的环长,fast将在入环后转了n圈后与slow相撞。 假如fast在环里转了n圈后和slow相撞,fast走过的途程能够示意为f=a+n(b+c)+b。此外,这里先阐明一个事实,slow肯定会在入环后第一圈内和fast相撞,这里咱们先不证实,前面再解释。基于这个事实,咱们能够得出slow走过的途程为s=a+b。此外,咱们晓得fast和slow从一个终点登程,fast速度是slow的两倍,因而,fast和slow相撞时,fast的途程肯定是slow的两倍,即f=2s。 基于上述公司,开始推理。因为我想晓得链表头起始点,咱们要关注a。 2s=f2(a+b)=a+n(b+c)+ba=n(b+c)-ba=(n-1)(b+c)+c因而能够得出,链表头挪动到入环点的间隔a等于在环内转了n-1圈后再走了间隔c。这个可能难以了解,然而咱们晓得走完一次b+c相当于转了一圈,也就是停留在原地,因而咱们能够把(n-1)(b+c)约掉。即变成a=c。或者能够这样想,fast如果只转了一圈后就撞上slow,即n=1,那可得a=c。 好,当初咱们只要求出c就行了。因为相撞点到入环点的间隔等于链表头到入环点的间隔。所以我设置一个指针p,从链表头开始,和slow每次挪动一格,最初肯定会在入环点相撞。 因而142的代码 func detectCycle(head *ListNode) *ListNode { if head==nil{ return nil } started:=false fast,slow:=head,head for !started || fast!=slow { started=true if fast.Next==nil || fast.Next.Next==nil{ return nil }else{ fast=fast.Next.Next } slow=slow.Next } p:=head for p!=slow{ slow=slow.Next p=p.Next } return p}为什么slow肯定会在第一圈内相撞其实这个想法是官解评论区看到的,咱们能够通过转换参考系的方法来思考。已知fast速度为2,slow速度为1,因而fast绝对于slow的速度为fast-slow=1。上文也说过,这也是为什么fast肯定会撞上slow而不会跳过。 ...

April 21, 2022 · 1 min · jiezi

关于golang:redis持久化

本次次要是对redis中驰名的长久化策略进行代码层面形容,次要包含RDB长久化和AOF长久化 <!-- more --> 因为AOF文件的更新频率比RDB高,所以如果服务器开启AOF长久化,redis优先应用AOF文件还原,只有当AOF长久化敞开,才应用RDB文件进行还原 RDB长久化RDB长久化次要有两个命令实现:SAVE和BGSAVE SAVE、BGSAVESAVE会阻塞redis服务器,晓得RDB文件创建结束void saveCommand(redisClient *c) { // BGSAVE 曾经在执行中,不能再执行 SAVE // 否则将产生竞争条件 if (server.rdb_child_pid != -1) { addReplyError(c,"Background save already in progress"); return; } // 执行 if (rdbSave(server.rdb_filename) == REDIS_OK) { addReply(c,shared.ok); } else { addReply(c,shared.err); }}BGSAVE不会阻塞,他会创立一个子过程,由子过程解决RDB文件保留void bgsaveCommand(redisClient *c) { // 不能反复执行 BGSAVE if (server.rdb_child_pid != -1) { addReplyError(c,"Background save already in progress"); // 不能在 BGREWRITEAOF 正在运行时执行 } else if (server.aof_child_pid != -1) { addReplyError(c,"Can't BGSAVE while AOF log rewriting is in progress"); // 执行 BGSAVE } else if (rdbSaveBackground(server.rdb_filename) == REDIS_OK) { addReplyStatus(c,"Background saving started"); } else { addReply(c,shared.err); }}int rdbSaveBackground(char *filename) { pid_t childpid; long long start; // 如果 BGSAVE 曾经在执行,那么出错 if (server.rdb_child_pid != -1) return REDIS_ERR; // 记录 BGSAVE 执行前的数据库被批改次数 server.dirty_before_bgsave = server.dirty; // 最近一次尝试执行 BGSAVE 的工夫 server.lastbgsave_try = time(NULL); // fork() 开始前的工夫,记录 fork() 返回耗时用 start = ustime(); if ((childpid = fork()) == 0) { int retval; /* 子过程 */ // 敞开网络连接 fd closeListeningSockets(0); // 设置过程的题目,不便辨认 redisSetProcTitle("redis-rdb-bgsave"); // 执行保留操作 retval = rdbSave(filename); // 打印 copy-on-write 时应用的内存数 if (retval == REDIS_OK) { size_t private_dirty = zmalloc_get_private_dirty(); if (private_dirty) { redisLog(REDIS_NOTICE, "RDB: %zu MB of memory used by copy-on-write", private_dirty/(1024*1024)); } } // 向父过程发送信号 exitFromChild((retval == REDIS_OK) ? 0 : 1); } else { /* 父过程 */ // 计算 fork() 执行的工夫 server.stat_fork_time = ustime()-start; // 如果 fork() 出错,那么报告谬误 if (childpid == -1) { server.lastbgsave_status = REDIS_ERR; redisLog(REDIS_WARNING,"Can't save in background: fork: %s", strerror(errno)); return REDIS_ERR; } // 打印 BGSAVE 开始的日志 redisLog(REDIS_NOTICE,"Background saving started by pid %d",childpid); // 记录数据库开始 BGSAVE 的工夫 server.rdb_save_time_start = time(NULL); // 记录负责执行 BGSAVE 的子过程 ID server.rdb_child_pid = childpid; // 敞开主动 rehash updateDictResizePolicy(); return REDIS_OK; } return REDIS_OK; /* unreached */}两个命令外部都是执行rdbSave函数 ...

April 21, 2022 · 8 min · jiezi

关于golang:Golang力扣Leetcode-657-机器人能否返回原点模拟

题目:在二维立体上,有一个机器人从原点 (0, 0) 开始。给出它的挪动程序,判断这个机器人在实现挪动后是否 在 (0, 0) 处完结。 挪动程序由字符串 moves 示意。字符 move[i] 示意其第 i 次挪动。机器人的无效动作有 R(右),L(左),U(上)和 D(下)。 如果机器人在实现所有动作后返回原点,则返回 true。否则,返回 false。 留神:机器人“面朝”的方向无关紧要。 “R” 将始终使机器人向右挪动一次,“L” 将始终向左挪动等。此外,假如每次移动机器人的挪动幅度雷同。 链接: 力扣Leetcode - 657. 机器人是否返回原点. 示例1: 输出: moves = "UD"输入: true解释:机器人向上挪动一次,而后向下挪动一次。所有动作都具备雷同的幅度,因而它最终回到它开始的原点。因而,咱们返回 true。示例 2: 输出: moves = "LL"输入: false解释:机器人向左挪动两次。它最终位于原点的左侧,距原点有两次 “挪动” 的间隔。咱们返回 false,因为它在挪动完结时没有返回原点。思路:咱们设机器人以后所在的坐标为 (x,y),起始时 x=0,y=0。接下来咱们遍历指令并更新机器人的坐标: 如果指令是 U,则令 y=y−1如果指令是 D,则令 y=y+1如果指令是 L,则令 x=x−1如果指令是 R,则令 x=x+1最初判断 (x,y) 是否为 (0,0) 即可。 go代码如下: package mainimport "fmt"func judgeCircle(moves string) bool { x, y := 0, 0 for i := 0; i < len(moves); i++ { if moves[i] == 'U' { y-- } else if moves[i] == 'D' { y++ } else if moves[i] == 'L' { x-- } else if moves[i] == 'R' { x++ } } if x == 0 && y == 0 { return true } return false}func main() { fmt.Println(judgeCircle("UD"))}提交截图: ...

April 21, 2022 · 1 min · jiezi

关于golang:基于知名微服务框架gomicro开发gRPC应用程序

go-micro是golang的一个微服务框架。 go-micro各个版本之间的兼容性问题始终被诟病,前几年go-micro更是分化出了两个分支: 一个连续了go-micro,只不过转到了其公司CEO的集体Github仓库中,拜访地址: asim/go-micro: A Go microservices framework (github.com) 一个转向了云原生方向,名字叫Micro,拜访地址: micro/micro: API first cloud platform (github.com) 不过都还是开源的,以后的许可证都是Apache 2.0,不是某些人说的不能商用了,当然无奈保障当前不会改许可证。 回到注释,这篇文章将介绍应用go-micro最新版本v4开发gRPC服务的形式。 1、装置protoc这个工具也称为proto编译器,能够用来生成各种开发语言应用proto协定的代码。 下载地址:https://github.com/protocolbu... 个别下载最新版本就行,留神要合乎本人以后的操作系统。 解压后里边有个 protoc.exe ,拷贝到 GOPATH 的 bin 目录下,我这里就是 C:/Users/PC-001/go/bin ,PC-001 是登录以后操作系统的用户名,须要换成你本人的。GOPATH 能够通过执行 go env 查看到。 个别都是把 GOPATH 的 bin 目录增加到环境变量的 PATH 变量中,如果没有,请自行添加上。 2、装置protoc的go-micro插件须要装置两个插件,它们用来生成 Golang 版本的 proto 协定代码和 go-micro 的 gRPC 代理代码。 执行如下命令,会在 GOPATH 的 bin 目录下生成两个可执行文件:protoc-gen-go.exe 和 protoc-gen-micro.exe。 go install google.golang.org/protobuf/cmd/protoc-gen-gogo install go-micro.dev/v4/cmd/protoc-gen-micro@v43、编写proto文件proto文件是合乎Protocol Buffers语言标准的数据交换协定文件,就像以前WebService定义服务时应用的XML文件。当初个别都是用proto3了,这里创立一个名为 hello.proto 的文件,放到我的项目的proto目录下: ...

April 21, 2022 · 2 min · jiezi

关于golang:面试官for-循环这道题竟然难道了所有人

本篇文章基于 Golang 1.17.2话说胖虎上次没有问到实习生,感觉实习生底子不错,最近闲来无事,决定在考考实习生。 for 循环的奇怪景象胖虎:以下代码输入什么 package mainimport "fmt"func main() { s := []int{0, 1} for num := 0; num < len(s); num++ { s = append(s, s[num]) } fmt.Printf("s的值是:%+v\n", s)}实习生吐口而出: [0 1 0 1]胖虎笑着说:不要焦急答复,思考三秒后在说出你的答案。 实习生: 难道不对? 胖虎看着实习生纳闷的表情,说:咱们来执行下吧 居然是死循环!!!实习生差点喊出来,同时发现胖虎奋斗多年的笔记本,风扇吭吭唧唧不愿意的开始干活了。 胖虎:如果想要 0 1 0 1,应该怎么改呢? 实习生:难道用range?边说边敲下以下代码。 package mainimport "fmt"func main() { s := []int{0, 1} for _, value := range s { s = append(s, value) } fmt.Printf("s的值是:%+v\n", s)}胖虎:那你晓得为什么吗?实习生:范畴循环在迭代过程中,难道是迭代值的拷贝?我再试下吧。 package mainimport "fmt"func main() { s := []int{0, 1} for _, value := range s { value += 10 } fmt.Printf("s的值是:%+v\n", s)}实习生:跟我猜测的一样。 ...

April 20, 2022 · 4 min · jiezi

关于golang:redis数据结构

引言从本次开始,对Redis设计与实现进行浏览及相干读书笔记的记录。Redis版本为3.0 <!-- more --> 数据结构简略动静字符串SDSsds数据结构位于sds.h/sdshdr /* * 保留字符串对象的构造 */struct sdshdr { // buf 中已占用空间的长度 int len; // buf 中残余可用空间的长度 int free; // 数据空间 char buf[];};绝对于C语言的字符串,SDS的长处在于 常数复杂度获取字符串长度杜绝缓冲区溢出缩小批改字符串所带来的内存重新分配(留神,开释空间时候,不会真的开释,而是设置free的值)链表链表的相干代码在adlist.h中 链表节点listNode /* * 双端链表节点 */typedef struct listNode { // 前置节点 struct listNode *prev; // 后置节点 struct listNode *next; // 节点的值 void *value;} listNode;由多个listNode组成的双端链表 链表构造list /* * 双端链表构造 */typedef struct list { // 表头节点 listNode *head; // 表尾节点 listNode *tail; // 节点值复制函数 void *(*dup)(void *ptr); // 节点值开释函数 void (*free)(void *ptr); // 节点值比照函数 int (*match)(void *ptr, void *key); // 链表所蕴含的节点数量 unsigned long len;} list; ...

April 20, 2022 · 4 min · jiezi

关于golang:redis服务器

这一次次要讲下redis中服务器这个构造体相干代码,次要从是代码层面进行解说 <!-- more --> redis服务器redis服务器构造体次要代码在redis.h/redisServer,上面给出该构造体源码,能够看到源码中对该构造体定义很长,这一节咱们一点点剖析,当然有些中央可能我也了解不到位hhh // redis服务器实例struct redisServer { char *configfile; /* 配置文件的绝对路径 */ int hz; /* serverCron() 每秒调用的次数 */ redisDb *db; /* 数据库数组,外面寄存的是该服务器所有的数据库 */ dict *commands; /* 命令表(受到 rename 配置选项的作用) */ dict *orig_commands; /* 命令表(无 rename 配置选项的作用) */ aeEventLoop *el; /* 事件状态 */ unsigned lruclock:REDIS_LRU_BITS; /* 最近一次应用时钟 */ int shutdown_asap; /* 敞开服务器的标识 */ int activerehashing; /* 在执行 serverCron() 时进行渐进式 rehash */ char *requirepass; /* 是否设置了明码 */ char *pidfile; /* PID 文件门路 */ int arch_bits; /* 架构类型32or64 */ int cronloops; /* serverCron() 函数的运行次数计数器 */ char runid[REDIS_RUN_ID_SIZE+1]; /* 本服务器的 RUN ID ID在每秒都会变动 */ int sentinel_mode; /* 服务器是否运行在 SENTINEL 模式 */ int port; /* TCP 监听端口 */ int tcp_backlog; /* TCP连贯中已实现队列(实现三次握手之后)的长度 */ char *bindaddr[REDIS_BINDADDR_MAX]; /* 绑定地址 */ int bindaddr_count; /* bindaddr地址数量 */ char *unixsocket; /* UNIX socket 门路 */ mode_t unixsocketperm; /* UNIX socket permission */ int ipfd[REDIS_BINDADDR_MAX]; /* TCP套接字描述符 */ int ipfd_count; /* ipfd中应用的套接字数量 */ int sofd; /* Unix套接字描述符 */ int cfd[REDIS_BINDADDR_MAX];/* 集群总线监听套接字 */ int cfd_count; /* cfd应用到的套接字数量 */ list *clients; /* 链表,保留了所有客户端状态构造 */ list *clients_to_close; /* 链表,保留了所有待敞开的客户端 */ list *slaves, *monitors; /* 链表,保留了所有从服务器,以及所有监视器 */ redisClient *current_client; /* C服务器的以后客户端,仅用于解体报告 */ int clients_paused; /* 客服端是否被paused */ mstime_t clients_pause_end_time; /* 执行undo clients_paused的工夫 */ char neterr[ANET_ERR_LEN]; /* anet.c网络谬误缓冲区 */ dict *migrate_cached_sockets;/* MIGRATE缓冲套接字 */ int loading; /* 服务器是否正在被载入 */ off_t loading_total_bytes; /* 正在载入的数据的大小 */ off_t loading_loaded_bytes; /* 已载入数据的大小 */ time_t loading_start_time; /* 开始进行载入的工夫 */ off_t loading_process_events_interval_bytes; // 常用命令的快捷连贯 struct redisCommand *delCommand, *multiCommand, *lpushCommand, *lpopCommand, *rpopCommand; time_t stat_starttime; /* 服务器启动工夫 */ long long stat_numcommands; /* 已解决命令的数量 */ long long stat_numconnections; /* 服务器接到的连贯申请数量 */ long long stat_expiredkeys; /* 已过期的键数量 */ long long stat_evictedkeys; /* 因为回收内存而被开释的过期键的数量 */ long long stat_keyspace_hits; /* 胜利查找键的次数 */ long long stat_keyspace_misses; /* 查找键失败的次数 */ size_t stat_peak_memory; /* 已应用内存峰值 */ long long stat_fork_time; /* 最初一次执行 fork() 时耗费的工夫 */ long long stat_rejected_conn; /* 服务器因为客户端数量过多而回绝客户端连贯的次数 */ long long stat_sync_full; /* 执行 full sync 的次数 */ long long stat_sync_partial_ok; /* PSYNC 胜利执行的次数 */ long long stat_sync_partial_err;/* PSYNC 执行失败的次数 */ list *slowlog; /* 保留了所有慢查问日志的链表 */ long long slowlog_entry_id; /* SLOWLOG以后条目ID */ long long slowlog_log_slower_than; /* 服务器配置 slowlog-log-slower-than 选项的值(SLOWLOG工夫限度) */ unsigned long slowlog_max_len; /* 服务器配置 slowlog-max-len 选项的值(SLOWLOG记录的最大项目数) */ size_t resident_set_size; /* serverCron()中rss采样次数. */ long long ops_sec_last_sample_time; /* 最初一次进行抽样的工夫 */ long long ops_sec_last_sample_ops; /* 最初一次抽样时,服务器已执行命令的数量 */ long long ops_sec_samples[REDIS_OPS_SEC_SAMPLES]; /* 抽样后果 */ int ops_sec_idx; /* 数组索引,用于保留抽样后果,并在须要时回绕到 0 */ int verbosity; /* 日志等级 Redis总共反对四个级别:debug、verbose、notice、warning,默认为notice */ int maxidletime; /* 客户端超时最大工夫 */ int tcpkeepalive; /* 是否开启SO_KEEPALIVE选项 */ int active_expire_enabled; /* 测试时候能够禁用 */ size_t client_max_querybuf_len; /* 客户端查问缓冲区长度限度 */ int dbnum; /* 服务器初始化应该创立多少个服务器 config中databases 16能够设定该选项 */ int daemonize; /* 如果作为守护过程运行,则为True */ // 客户端输入缓冲区大小限度 // 数组的元素有 REDIS_CLIENT_LIMIT_NUM_CLASSES 个 // 每个代表一类客户端:一般、从服务器、pubsub,诸如此类 clientBufferLimitsConfig client_obuf_limits[REDIS_CLIENT_LIMIT_NUM_CLASSES]; int aof_state; /* AOF 状态(开启/敞开/可写) */ int aof_fsync; /* 所应用的 fsync 策略(每个写入/每秒/从不) */ char *aof_filename; /* AOF文件名字 */ int aof_no_fsync_on_rewrite; /* 如果重写是在prog中,请不要fsync */ int aof_rewrite_perc; /* Rewrite AOF if % growth is > M and... */ off_t aof_rewrite_base_size; /* 最初一次执行 BGREWRITEAOF 时, AOF 文件的大小 */ off_t aof_current_size; /* AOF 文件的以后字节大小 */ int aof_rewrite_scheduled; /* BGSAVE终止后重写 */ pid_t aof_child_pid; /* 负责进行 AOF 重写的子过程 ID */ list *aof_rewrite_buf_blocks; /* AOF 重写缓存链表,链接着多个缓存块 */ sds aof_buf; /* AOF 缓冲区 */ int aof_fd; /* 以后所选AOF文件的文件描述符 */ int aof_selected_db; /* 以后在AOF中抉择的数据库 */ time_t aof_flush_postponed_start; /*推延AOF flush的UNIX工夫 */ time_t aof_last_fsync; /* 最初始终执行 fsync 的工夫 */ time_t aof_rewrite_time_last; /* 最初一次AOF重写运行所用的工夫 */ time_t aof_rewrite_time_start; /* 以后AOF重写开始工夫 */ int aof_lastbgrewrite_status; /* 最初一次执行 BGREWRITEAOF 的后果REDIS_OK或REDIS_ERR */ unsigned long aof_delayed_fsync; /* 记录 AOF 的 write 操作被推延了多少次 */ int aof_rewrite_incremental_fsync;/* 批示是否须要每写入一定量的数据,就被动执行一次 fsync() */ int aof_last_write_status; /* REDIS_OK or REDIS_ERR */ int aof_last_write_errno; /* 如果aof_last_write_status是ERR,则无效 */ long long dirty; /* 自从上次 SAVE 执行以来,数据库被批改的次数 */ long long dirty_before_bgsave; /* BGSAVE 执行前的数据库被批改次数 */ pid_t rdb_child_pid; /* 负责执行 BGSAVE 的子过程的 ID,没在执行 BGSAVE 时,设为 -1 */ struct saveparam *saveparams; /* 为RDB保留点数组 */ int saveparamslen; /* saveparams长度 */ char *rdb_filename; /* RDB文件的名称 */ int rdb_compression; /* 是否在RDB中应用压缩 */ int rdb_checksum; /* 是否应用RDB校验和 */ time_t lastsave; /* 最初一次实现 SAVE 的工夫 */ time_t lastbgsave_try; /* 最初一次尝试执行 BGSAVE 的工夫 */ time_t rdb_save_time_last; /* 最近一次 BGSAVE 执行消耗的工夫 */ time_t rdb_save_time_start; /* 数据库最近一次开始执行 BGSAVE 的工夫 */ int lastbgsave_status; /* 最初一次执行 SAVE 的状态REDIS_OK or REDIS_ERR */ int stop_writes_on_bgsave_err; /* 如果不能BGSAVE,不容许写入 */ /* Propagation of commands in AOF / replication */ redisOpArray also_propagate; /* Additional command to propagate. */ char *logfile; /* 日志文件的门路 */ int syslog_enabled; /* 是否启用了syslog */ char *syslog_ident; /* 指定syslog的标示符,如果下面的syslog-enabled no,则这个选项有效 */ int syslog_facility; /* 指定syslog facility,必须是USER或者LOCAL0到LOCAL7 */ int slaveseldb; /* Last SELECTed DB in replication output */ long long master_repl_offset; /* 全局复制偏移量(一个累计值) */ int repl_ping_slave_period; /* Master每N秒ping一次slave */ // backlog 自身 char *repl_backlog; /* Replication backlog for partial syncs */ long long repl_backlog_size; /* Backlog循环缓冲区大小 */ long long repl_backlog_histlen; /* backlog 中数据的长度 */ long long repl_backlog_idx; /* backlog 的以后索引 */ long long repl_backlog_off; /* backlog 中能够被还原的第一个字节的偏移量 */ time_t repl_backlog_time_limit; /* backlog 的过期工夫 */ time_t repl_no_slaves_since; /* 间隔上一次有从服务器的工夫 */ int repl_min_slaves_to_write; /* 是否开启最小数量从服务器写入性能 */ int repl_min_slaves_max_lag; /* 定义最小数量从服务器的最大提早值 */ int repl_good_slaves_count; /* 提早良好的从服务器的数量 lag <= max_lag. */ char *masterauth; /* 主服务器的验证明码 */ char *masterhost; /* 主服务器的地址 */ int masterport; /* 主服务器的端口 */ int repl_timeout; /* 主机闲暇N秒后超时 */ redisClient *master; /* 主服务器所对应的客户端 */ redisClient *cached_master; /* 被缓存的主服务器,PSYNC 时应用 */ int repl_syncio_timeout; /* Timeout for synchronous I/O calls */ int repl_state; /* 复制的状态(服务器是从服务器时应用) */ off_t repl_transfer_size; /* 在同步期间从主机读取的RDB的大小 */ off_t repl_transfer_read; /* 在同步期间从主设施读取的RDB字节数 */ // 最近一次执行 fsync 时的偏移量 // 用于 sync_file_range 函数 off_t repl_transfer_last_fsync_off; /* 上次fsync-ed时偏移 */ int repl_transfer_s; /* 主服务器的套接字 */ int repl_transfer_fd; /* 保留 RDB 文件的临时文件的描述符 */ char *repl_transfer_tmpfile; /* 保留 RDB 文件的临时文件名字 */ time_t repl_transfer_lastio; /* 最近一次读入 RDB 内容的工夫 */ int repl_serve_stale_data; /* Serve stale data when link is down? */ int repl_slave_ro; /* 从服务器是否只读 */ time_t repl_down_since; /* 连贯断开的时长 */ int repl_disable_tcp_nodelay; /* 是否要在 SYNC 之后敞开 NODELAY */ int slave_priority; /* 从服务器优先级 */ char repl_master_runid[REDIS_RUN_ID_SIZE+1]; /*本服务器(从服务器)以后主服务器的 RUN ID */ long long repl_master_initial_offset; /* Master PSYNC offset. */ /* ---------上面一些属性有些很难用到,对此我也没认真看 */ /* Replication script cache. */ // 复制脚本缓存 // 字典 dict *repl_scriptcache_dict; /* SHA1 all slaves are aware of. */ // FIFO 队列 list *repl_scriptcache_fifo; /* First in, first out LRU eviction. */ // 缓存的大小 int repl_scriptcache_size; /* Max number of elements. */ /* Synchronous replication. */ list *clients_waiting_acks; /* Clients waiting in WAIT command. */ int get_ack_from_slaves; /* If true we send REPLCONF GETACK. */ int maxclients; /* 最大并发客户端数 */ unsigned long long maxmemory; /* 要应用的最大内存字节数 */ int maxmemory_policy; /* Policy for key eviction */ int maxmemory_samples; /* Pricision of random sampling */ unsigned int bpop_blocked_clients; /* 列表阻止的客户端数量 */ list *unblocked_clients; /* 在下一个循环之前解锁的客户端列表 */ list *ready_keys; /* List of readyList structures for BLPOP & co */ /* Sort parameters - qsort_r() is only available under BSD so we * have to take this state global, in order to pass it to sortCompare() */ int sort_desc; int sort_alpha; int sort_bypattern; int sort_store; /* Zip structure config, see redis.conf for more information */ size_t hash_max_ziplist_entries; size_t hash_max_ziplist_value; size_t list_max_ziplist_entries; size_t list_max_ziplist_value; size_t set_max_intset_entries; size_t zset_max_ziplist_entries; size_t zset_max_ziplist_value; size_t hll_sparse_max_bytes; time_t unixtime; /* Unix time sampled every cron cycle. */ long long mstime; /* Like 'unixtime' but with milliseconds resolution. */ /* Pubsub */ // 字典,键为频道,值为链表 // 链表中保留了所有订阅某个频道的客户端 // 新客户端总是被增加到链表的表尾 dict *pubsub_channels; /* Map channels to list of subscribed clients */ // 这个链表记录了客户端订阅的所有模式的名字 list *pubsub_patterns; /* A list of pubsub_patterns */ int notify_keyspace_events; /* Events to propagate via Pub/Sub. This is an xor of REDIS_NOTIFY... flags. */ /* Cluster */ int cluster_enabled; /* 群集是否已启用 */ mstime_t cluster_node_timeout; /* 集群节点超时工夫. */ char *cluster_configfile; /* 集群主动生成的配置文件名 */ struct clusterState *cluster; /* 集群的状态*/ int cluster_migration_barrier; /* Cluster replicas migration barrier. */ /* Scripting */ // Lua 环境 lua_State *lua; /* The Lua interpreter. We use just one for all clients */ // 复制执行 Lua 脚本中的 Redis 命令的伪客户端 redisClient *lua_client; /* The "fake client" to query Redis from Lua */ // 以后正在执行 EVAL 命令的客户端,如果没有就是 NULL redisClient *lua_caller; /* The client running EVAL right now, or NULL */ // 一个字典,值为 Lua 脚本,键为脚本的 SHA1 校验和 dict *lua_scripts; /* A dictionary of SHA1 -> Lua scripts */ // Lua 脚本的执行时限 mstime_t lua_time_limit; /* Script timeout in milliseconds */ // 脚本开始执行的工夫 mstime_t lua_time_start; /* Start time of script, milliseconds time */ // 脚本是否执行过写命令 int lua_write_dirty; /* True if a write command was called during the execution of the current script. */ // 脚本是否执行过带有随机性质的命令 int lua_random_dirty; /* True if a random command was called during the execution of the current script. */ // 脚本是否超时 int lua_timedout; /* True if we reached the time limit for script execution. */ // 是否要杀死脚本 int lua_kill; /* Kill the script if true. */ /* Assert & bug reporting */ char *assert_failed; char *assert_file; int assert_line; int bug_report_start; /* True if bug report header was already logged. */ int watchdog_period; /* Software watchdog period in ms. 0 = off */};上面重点讲下redis服务器启动的流程,次要包含以下几个步骤,不懂的同学能够看下redis.c/main函数,就能够大抵理解其过程 ...

April 20, 2022 · 20 min · jiezi

关于golang:GlideIM-Golang-实现的高性能的分布式-IM

GlideIM 是一款齐全开源, Golang 实现的高性能分布式 IM 服务, 有残缺的安卓 APP 示例, JAVA SDK, Web 端示例, 继续更新迭代中. GlideIM 反对单实例, 分布式部署. 反对 WebSocket, TCP 两种连贯协定, 内置 JSON, ProtoBuff 两种音讯替换协定, 并反对增加其余协定, 音讯加密等. 还实现了智能心跳保活机制, 死链接检测, 音讯送达机制等性能. 这个我的项目自 2020 年中旬开始, 三端均开发由我一个人单独不间断开发直到现在, 也是我第一个消耗最多精力去开发及学习的我的项目. 因为我工作较多闲暇工夫, 基本上都在开发本我的项目, 边学习边开发, 查阅了大量材料, 通过大量的思考, 指标就是一个高性能分布式 IM. 第一版微服务架构在开发三个月的时候根本曾经实现了, 前面则是做了微服务架构调整, 和进一步优化 IM 服务细节, 截至目前(2022年3月3日), 间隔我预期的第二版, 只剩下一些收尾工作. 本我的项目将继续更新迭代. 服务端源码: GlideIM - GitHubAndroid App下载: Android - GitHubWeb 端: GlideIM一. 性能1.1 用户侧性能无感掉线重连, 音讯同步登录注册及放弃登录一对一聊天, 群聊音讯漫游, 历史记录离线音讯多设施登录, 同设施互挤下线多种类型的音讯 (图片, 语音等, 由客户端定义)音讯撤回联系人治理, 群治理1.2 开发侧性能反对 WebSocket, TCP, 自定义连贯协定反对 JSON 或 Protobuff 或自定义数据交换协定反对分布式部署, 程度扩大心跳保活, 超时断开, 清理死链接音讯缓冲, 异步解决, 弱网优化音讯送达机制, 音讯重发, ACK音讯去重, 程序保障, 读扩散二. 我的项目架构为了进步可用性和整体的稳定性, 单机性能的限度, 必须应用分布式架构, 微服务的模式不便了保护. 本我的项目对 IM 业务局部拆分了六大外围主模块(服务), 每个服务能够程度任意数量扩大, 整个零碎能够具备肯定的伸缩性, 每个模块依据其业务个性划分, 逻辑和接口拆散, 在保障接口简洁性的同时也有足够的扩展性. ...

April 20, 2022 · 7 min · jiezi

关于golang:微服务从代码到k8s部署应有尽有系列五民宿服务

咱们用一个系列来解说从需要到上线、从代码到k8s部署、从日志到监控等各个方面的微服务残缺实际。 整个我的项目应用了go-zero开发的微服务,根本蕴含了go-zero以及相干go-zero作者开发的一些中间件,所用到的技术栈根本是go-zero项目组的自研组件,根本是go-zero全家桶了。 实战我的项目地址:https://github.com/Mikaelemmm... 1、民宿服务业务架构图 2、依赖关系travel-api(民宿api) 依赖 travel-rpc(民宿rpc)、usercenter-rpc(用户核心rpc) usercenter-rpc(用户核心rpc)依赖 identity-rpc(受权核心rpc) travel分为几个业务 homestay :民宿房源// 民宿模块v1版本的接口@server( prefix: travel/v1 group: homestay)service travel { @doc "民宿列表(为你优选)" @handler homestayList post /homestay/homestayList (HomestayListReq) returns (HomestayListResp) @doc "房东所有民宿列表" @handler businessList post /homestay/businessList (BusinessListReq) returns (BusinessListResp) @doc "猜你喜爱民宿列表" @handler guessList post /homestay/guessList (GuessListReq) returns (GuessListResp) @doc "民宿详情" @handler homestayDetail post /homestay/homestayDetail (HomestayDetailReq) returns (HomestayDetailResp)}homestayBusiness : 民宿店家// 店铺模块v1版本的接口@server( prefix: travel/v1 group: homestayBussiness)service travel { @doc "最佳房东" @handler goodBoss post /homestayBussiness/goodBoss (GoodBossReq) returns (GoodBossResp) @doc "店铺列表" @handler homestayBussinessList post /homestayBussiness/homestayBussinessList (HomestayBussinessListReq) returns (HomestayBussinessListResp) @doc "房东信息" @handler homestayBussinessDetail post /homestayBussiness/homestayBussinessDetail (HomestayBussinessDetailReq) returns (HomestayBussinessDetailResp)}homestayComment : 民宿评论// 民宿评论模块v1版本的接口@server( prefix: travel/v1 group: homestayComment)service travel { @doc "民宿评论列表" @handler commentList post /homestayComment/commentList (CommentListReq) returns (CommentListResp)}3、举例:民宿列表(为你优选)1、api服务1、写api接口文件 ...

April 20, 2022 · 2 min · jiezi

关于golang:十分钟学会Golang开发gRPC服务

gRPC是Google发动的一个开源RPC框架,应用HTTP/2传输协定,应用Protocol Buffers编码协定,相比RESTful框架的程序性能进步不少,而且以后风行的编程语言根本都曾经反对。 Golang开发gRPC应用程序的套路也曾经很清晰,这篇文章就来做一个简略的介绍,算是入门。 1、装置protoc这个工具也称为proto编译器,能够用来生成各种开发语言应用proto协定的代码。 下载地址:https://github.com/protocolbu... 个别下载最新版本就行,留神要合乎本人以后的操作系统。 解压后里边有个 protoc.exe ,拷贝到 GOPATH 的 bin 目录下,我这里就是 C:/Users/PC-001/go/bin ,PC-001 是登录以后操作系统的用户名,须要换成你本人的。GOPATH 能够通过执行 go env 查看到。 个别都是把 GOPATH 的 bin 目录增加到环境变量的 PATH 变量中,如果没有,请自行添加上。 2、装置protoc的Golang gRPC插件执行如下命令,会在 GOPATH 的 bin 目录下生成两个可执行文件:protoc-gen-go.exe 和 protoc-gen-go-grpc.exe。这两个插件能够用来生成Golang版本的proto协定代码和gRPC代理代码。 go install google.golang.org/protobuf/cmd/protoc-gen-gogo install google.golang.org/grpc/cmd/protoc-gen-go-grpc3、编写proto文件proto文件是合乎Protocol Buffers语言标准的数据交换协定文件,就像以前WebService定义服务时应用的XML文件。当初个别都是用proto3了,这里创立一个名为 hello.proto 的文件,放到我的项目的proto目录下: syntax = "proto3";option go_package="/proto";package Business;service Hello { rpc Say (SayRequest) returns (SayResponse);}message SayResponse { string Message = 1;}message SayRequest { string Name = 1;}这个协定很简略,有个名字为Hello的服务,提供一个名字为Say的rpc办法,这个办法有输入输出,输出信息中有一个名为Name的参数,输入信息中有一个名为Message的返回值。 ...

April 20, 2022 · 2 min · jiezi

关于golang:Go语言GMP调度模型

明天学习了之前买的课程,明天次要是学习了一下goroutine的调度相干的事件。有了一个全新的意识 GMP调度合成在go中,通过了多代的更新,才应用了当初的GMP调度,这里咱们须要晓得的是GMP到底都代表的什么货色 G首先咱们就来说一下这个G,在go语言中,每一个工作咱们都封装成一个G,能够把G了解成一个工作。 MM就是处理器,也就是是实体的线程,有了实体线程能力进行解决 PP就是这个协程解决的时候须要的资源,能够说P相当于一个token,有了这个token能力进行相干的解决。也就是说有了P, M能力进行解决。 GMP调度对于goroutine的调度,咱们能够想像成一个生产生产的模型。在具体的学习生产生产的时候,咱们首先来认识一下,对于协程调度的几个货色。这是一个实现的外部图片,咱们首先认识一下外面的构造,其中GMP咱们都意识了,当初次要是认识一下其余的货色,特地是三个队列。咱们能够分明的看到,有三个队列:runnext、本地队列、全局队列前面咱们会具体的阐明这个几个队列的具体细节。首先第一个runnext就只能放下一个Goruntine第二个本地队列是用的数组实现的。为什么?次要根据的是局部性原理,最初调用的程序,可能是最进行调用的,所以咱们应用的是数组实现。这里的数组大小是256.第三那个全局的队列,是应用的是链表的数据结构实现的。 生产有了生产生产的模型,当初咱们来看一下生产端是怎么来进行的。在这里,次要须要理解的事件就是runqput这里是怎么进行运行的,也就是goruntine是怎么抉择队列进行存储的。首先创立进去的G,首先会到runnext,而后如果说runnext上有G,咱们就会将老的G进行踢出,而后将老的G放入到本地队列中。如果本地队列放满了,这个时候咱们会将这个G,而后加上本地队列的一半生成一个batch,转换成链表退出到全局队列中。这里咱们大略的解说了一下生产的逻辑,而后咱们上面来看一下生产的逻辑。 生产下面的就是生产端的结构图,其实生产的逻辑,就是在这个四个外面始终进行循环,然而对于不同的队列中咱们还是有不同的调度。在这个循环外面有一个魔法数字,61。上面咱们就进行具体的解说。进入这个循环过后,咱们首先对循环次数进行61取模,当为0的时候,咱们就会取全局队列的第一个G进行执行。当然如果不是,咱们回去runnext中取,如果有就执行,如果没有咱们就会在本地队列中进行查找,而后取出进行执行,当然咱们也会遇见本地队列也没有的状况,这个时候咱们会取全局队列中取,首先拿出一半,而后取一个取执行,其余的放入本地队列。在这里如果都没有的时候,咱们的调度也不会完结,而是取其余的处理器中取取。 总结GMP的调度过程,看代码的确有点打老壳,然而有了这个图后,我感觉更加的好了解。最初感激大家的观看,心愿大家多多反对,给我点点赞谢谢。

April 19, 2022 · 1 min · jiezi

关于golang:Prometheus以及时序数据库的基本概念

Prometheus 是由 SoundCloud 开发的开源监控报警零碎和时序列数据库。从字面上了解,Prometheus 由两个局部组成,一个是监控报警零碎,另一个是自带的时序数据库(TSDB)。 Prometheus 的生态组件Prometheus ServerPrometheus组件中的外围局部,收集和存储工夫序列数据,提供PromQL查询语言的反对。内置的 Express Browser UI,通过这个 UI 能够间接通过 PromQL 实现数据的查问以及可视化。 Exporters将监控数据采集的端点通过HTTP服务的模式裸露给Prometheus Server,Prometheus Server通过拜访该Exporter提供的Endpoint端点,即能够获取到须要采集的监控数据 PushGateway次要是实现接管由 Client push 过去的指标数据,在指定的工夫距离,由主程序来抓取。因为 Prometheus 数据采集基于 Pull 模型进行设计,因而在网络环境的配置上必须要让 Prometheus Server 可能间接与 Exporter 进行通信。当这种网络需要无奈间接满足时,就能够利用 PushGateway 来进行直达。能够通过 PushGateway 将外部网络的监控数据被动 Push 到 Gateway 当中。而 Prometheus Server 则能够采纳同样 Pull 的形式从 PushGateway 中获取到监控数据。 Alertmanager治理告警,次要是负责实现报警性能。在 Prometheus Server 中反对基于 PromQL 创立告警规定,如果满足PromQL定义的规定,则会产生一条告警,而告警的后续解决流程则由 AlertManager 进行治理。在 AlertManager 中咱们能够与邮件,Slack 等等内置的告诉形式进行集成,也能够通过 Webhook 自定义告警解决形式。AlertManager 即 Prometheus 体系中的告警解决核心。 Grafana是一个大型可视化零碎,功能强大,能够创立本人的自定义面板,反对多种数据起源,当然也反对普罗米修斯。 参考:https://blog.51cto.com/u_1208...

April 19, 2022 · 1 min · jiezi

关于golang:Go汇编语法和MatrixOne使用介绍

目录MatrixOne数据库是什么?Go汇编介绍为什么应用Go汇编? 为什么不必CGO?Go汇编语法特点 操作数程序寄存器宽度标识函数调用约定对写Go汇编代码有帮忙的工具 avotext/template在Go汇编代码中应用宏在MatrixOne数据库中的Go语言汇编利用 根本向量运算减速Go语言无奈间接调用的指令编译器无奈达到的非凡优化成果MatrixOne社区MatrixOne数据库是什么?MatrixOne是一个新一代超交融异构数据库,致力于打造繁多架构解决TP、AP、流计算等多种负载的极简大数据引擎。MatrixOne由Go语言所开发,并已于2021年10月开源,目前曾经release到0.3版本。在MatrixOne已公布的性能报告中,与业界当先的OLAP数据库Clickhouse相比也不落下风。作为一款Go语言实现的数据库,能够达到C++实现的数据库一样的性能,其中一个很重要的优化就是利用Go语言自带的汇编能力,来通过调用SIMD指令进行硬件加速。本文就将对Go汇编及在MatrixOne的利用做具体介绍。 Github地址:https://github.com/matrixorig... 有趣味的读者欢送star和fork。 Go汇编介绍Go是一种较新的高级语言,提供诸如协程、疾速编译等激动人心的个性。然而在数据库引擎中,应用纯正的Go语言会无力所未逮的时候。例如,向量化是数据库计算引擎罕用的减速伎俩,而Go语言无奈通过调用SIMD指令来使向量化代码的性能最大化。又例如,在平安相干代码中,Go语言无奈调用CPU提供的密码学相干指令。在C/C++/Rust的世界中,解决这类问题可通过调用CPU架构相干的intrinsics函数。而Go语言提供的解决方案是Go汇编。本文将介绍Go汇编的语法特点,并通过几个具体场景展现其应用办法。 本文假设读者曾经对计算机体系架构和汇编语言有根本的理解,因而罕用的名词(比方“寄存器”)不做解释。如不足相干准备常识,能够寻求网络资源进行学习,例如这里。 如无非凡阐明,本文所指的汇编语言皆针对x86(amd64)架构。对于x86指令集,Intel和AMD官网都提供了残缺的指令集参考文档。想疾速查阅,也能够应用这个列表。Intel的intrinsics文档也能够作为一个参考。 为什么应用Go汇编?维基百科把应用汇编语言的理由概括成3类: 间接操作硬件应用非凡的CPU指令解决性能问题Go程序员应用汇编的理由,也不外乎这3类。如果你面对的问题在这3个类别外面,并且没有现成的库可用,就能够思考应用Go汇编。 为什么不必CGO?微小的函数调用开销内存治理问题突破goroutine语义 若协程里运行CGO函数,会占据独自线程,无奈被Go运行时失常调度。可移植性差 穿插编译须要目标平台的全套工具链。在不同平台部署须要装置更多依赖库。假使在你的场景中以上几点无奈承受,无妨尝试一下Go汇编。 Go汇编语法特点依据Rob Pike的The Design of the Go Assembler,Go应用的汇编语言并不严格与CPU指令一一对应,而是一种被称作Plan 9 assembly的“伪汇编”。 The most important thing to know about Go's assembler is that it is not a direct representation of the underlying machine. Some of the details map precisely to the machine, but some do not. This is because the compiler suite needs no assembler pass in the usual pipeline. Instead, the compiler operates on a kind of semi-abstract instruction set, and instruction selection occurs partly after code generation. The assembler works on the semi-abstract form, so when you see an instruction like MOV what the toolchain actually generates for that operation might not be a move instruction at all, perhaps a clear or load. Or it might correspond exactly to the machine instruction with that name. In general, machine-specific operations tend to appear as themselves, while more general concepts like memory move and subroutine call and return are more abstract. The details vary with architecture, and we apologize for the imprecision; the situation is not well-defined.咱们不必关怀Plan 9 assembly与机器指令的对应关系,只须要理解Plan 9 assembly的语法特点。网络上有一些可取得的文档,如这里和这里。 ...

April 19, 2022 · 4 min · jiezi

关于golang:Golang力扣Leetcode-389-找不同求和

题目:给定两个字符串 s 和 t ,它们只蕴含小写字母。 字符串 t 由字符串 s 随机重排,而后在随机地位增加一个字母。 请找出在 t 中被增加的字母。 链接: 力扣Leetcode - 389. 找不同. 示例1: 输出:s = "abcd", t = "abcde"输入:"e"解释:'e' 是那个被增加的字母。示例 2: 输出:s = "", t = "y"输入:"y"思路:将字符串 s 和字符串 t 中每个字符的 ASCII 码的值求和,失去 sumS 和 sumT 。两者的差值 sumT - sumS 即代表了被增加的字符。 次要Go代码如下: package mainimport "fmt"func findTheDifference(s, t string) byte { sumS, sumT := 0, 0 for _, ch := range s { sumS += int(ch) } for _, ch := range t { sumT += int(ch) } return byte(sumT - sumS)}func main() { fmt.Println(findTheDifference("abcd", "abcde"))}提交截图: ...

April 19, 2022 · 1 min · jiezi

关于golang:微服务从代码到k8s部署应有尽有系列四用户中心

咱们用一个系列来解说从需要到上线、从代码到k8s部署、从日志到监控等各个方面的微服务残缺实际。 整个我的项目应用了go-zero开发的微服务,根本蕴含了go-zero以及相干go-zero作者开发的一些中间件,所用到的技术栈根本是go-zero项目组的自研组件,根本是go-zero全家桶了。 实战我的项目地址:https://github.com/Mikaelemmm... 一、用户核心业务架构图 二、依赖关系usercenter-api(用户核心api) 依赖 identity-rpc(受权认证rpc)、usercenter-rpc(用户核心rpc) usercenter-rpc(用户核心rpc)依赖 identity-rpc(受权核心rpc) 咱们看我的项目usercenter/cmd/api/desc/usercenter.api ,所有的用户api对外的http办法都在这外面 这外面有4个业务注册、登陆、获取用户信息、微信小程序受权 三、注册举例1、注册api服务咱们在写api服务代码的时候是先要在usercenter.api中定义好service中的办法,而后在desc/user中写request、response,这样拆离开的益处是不那么臃肿 a、在usercenter.api中定义注册办法如下 // 用户模块v1版本的接口@server( prefix: usercenter/v1 group: user)service usercenter { @doc "注册" @handler register post /user/register (RegisterReq) returns (RegisterResp) .....}b、在app/usercenter/cmd/api/desc/user/user.api中定义RegisterReq\RegisterResp type ( RegisterReq { Mobile string `json:"mobile"` Password string `json:"password"` } RegisterResp { AccessToken string `json:"accessToken"` AccessExpire int64 `json:"accessExpire"` RefreshAfter int64 `json:"refreshAfter"` })c、goctl生成api代码 1)命令行进入app/usercenter/cmd/api/desc目录下。 2)去我的项目目录下deploy/script/gencode/gen.sh中,复制如下一条命令,在命令行中执行(命令行要切换到app/usercenter/cmd目录) $ goctl api go -api *.api -dir ../ -style=goZerod、关上app/usercenter/cmd/api/internal/logic/user/register.go文件 这里就很容易了,间接调用user的rpc服务即可 这里有个小技巧,很多同学感觉rpc服务返回的字段跟api定义差不多,每次都要手动去复制很麻烦,那么go有没有像java一样的BeanCopyUtils.copy 这种工具呢?答案必定是有的,能够看下面的代码copier.Copy ,这个库是gorm作者的另一款新作,是不是很兴奋。 那咱们持续看看调用后端的rpc是什么样子的。 ...

April 19, 2022 · 2 min · jiezi

关于golang:简单聊聊-GOPATH-与-Go-Modules

话说胖虎最近招了一个实习生,实习生进来也有一段时间了,天天一个人坐在工位,很少跟四周的共事交换,也不晓得有没有遇到什么问题。 胖虎决定主动出击,简略理解下实习生最近的学习状况,也关怀一下工作是否顺利 什么是GOROOT胖虎:来了有几天了,我来验收一下学习的状况吧。先说说什么是 GOROOT 吧 实习生笑道:这个简略啊, 几乎就是送分题啊,学长, GOROOT 是环境变量,它的值是 Golang 安装包门路 胖虎心田os:这又不是面试造火箭,必定不会尴尬你啊。要是面试问这个问题,算我输。 什么是GOPATH胖虎:那 GOPATH 呢? 实习生:GOPATH 是Golang 1.5版本之前一个重要的环境变量配置,是寄存 Golang 我的项目代码的文件门路。 如何查看 GOPATH 门路实习生:在命令控制台输出 go env GOPATH 或者输出: go env | grep GOPATH 进入GOPATH目录,查看该目录下的所有文件。 go├── bin├── pkg└── src ├── github.com ├── golang.org ├── google.golang.org ....能够看到有三个文件夹。 bin 寄存编译生成的二进制文件。比方 执行命令 go get github.com/google/gops,bin目录会生成 gops 的二进制文件。pkg 其中pkg上面以下三个文件夹。 XX_amd64: 其中 XX 是指标操作系统,比方 mac 零碎对应的是darwin_amd64, linux 零碎对应的是 linux_amd64,寄存的是.a结尾的文件。mod: 当开启go Modules 模式下,go get命令缓存下依赖包寄存的地位sumdb: go get命令缓存下载的checksum数据寄存的地位 src 寄存golang我的项目代码的地位 ...

April 18, 2022 · 2 min · jiezi

关于golang:七天实现web框架错误处理

很快到了七天实现web框架的最初一天了,明天的主题是错误处理,在go语言中咱们是没有像其余语言一样的解决机制了。 错误处理在go语言中,咱们应用panic对异样进行抛出,这样会将程序间接中断,然而这样必定是不好的。 咱们首先来看一下,defer的利用,对于defer函数,咱们须要晓得的是,defer就是将操作进行一个提早,而后期待return前进行执行,这里是一个栈的实现形式。所以是先进后出的。 而后咱们还要晓得的是recover的应用,这个肯定要在defer之后进行应用,它的作用是将panic的谬误进行一个压栈的操作,这样程序就不会间接中断,起到了一个try-catch的作用。 ## 具体的实现 package geeimport ( "fmt" "log" "net/http" "runtime" "strings")// print stack trace for debugfunc trace(message string) string { var pcs [32]uintptr n := runtime.Callers(3, pcs[:]) // skip first 3 caller var str strings.Builder str.WriteString(message + "\nTraceback:") for _, pc := range pcs[:n] { fn := runtime.FuncForPC(pc) file, line := fn.FileLine(pc) str.WriteString(fmt.Sprintf("\n\t%s:%d", file, line)) } return str.String()}func Recovery() HandlerFunc { return func(c *Context) { defer func() { if err := recover(); err != nil { message := fmt.Sprintf("%s", err) log.Printf("%s\n\n", trace(message)) c.Fail(http.StatusInternalServerError, "Internal Server Error") } }() c.Next() }}

April 18, 2022 · 1 min · jiezi

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

咱们用一个系列来解说从需要到上线、从代码到k8s部署、从日志到监控等各个方面的微服务残缺实际。 整个我的项目应用了go-zero开发的微服务,根本蕴含了go-zero以及相干go-zero作者开发的一些中间件,所用到的技术栈根本是go-zero项目组的自研组件,根本是go-zero全家桶了。 实战我的项目地址:https://github.com/Mikaelemmm... 1、鉴权服务 1.1 identity-apiidentity次要是用来做鉴权服务的,后面咱们nginx网关的时候有提到。在拜访一个资源的时候,nginx外部会先来identity-api中解析token,identity-api会去申请identity-rpc,所有的验证与颁发token,对立是在identity-rpc中做的 咱们会从header的Authorization中获取token,从x-Original-Uri获取拜访的资源门路 如果以后拜访的路由须要登陆: token解析失败:就会返回给前端http401错误码;token解析胜利:就会将解析进去的userId放入header的x-user中返回给auth模块,auth模块会把header传递给对应拜访的服务(usercenter), 这样咱们在usercenter间接就能够拿到该登陆用户的id了如果以后拜访的路由不须要登陆: 前端header中传递了token 如果token校验失败:返回http401;如果token校验胜利:就会将解析进去的userId放入header的x-user中返回给auth模块,auth模块会把header传递给对应拜访的服务(usercenter), 这样咱们在usercenter间接就能够拿到该登陆用户的id了前端header中没传递token:userid 会传递 0 给后端服务urlNoAuth办法判断以后资源是否在yml中配置能够不登陆 //以后url是否须要受权验证func (l *TokenLogic) urlNoAuth(path string) bool { for _, val := range l.svcCtx.Config.NoAuthUrls { if val == path { return true } } return false}isPass办法就是去identity-rpc校验token,次要也是应用了go-zero的jwt的办法 1.2 identity-rpc当咱们在注册、登陆胜利时候,用户服务会调用identity-rpc生成token,所以咱们对立在identity-rpc中颁发、校验token,这样就不必每个服务都要写个jwt去保护。 当identity-api申请进来时候,identity-api本人能够解析进去userid,然而咱们要测验这个token是否是过期,就要去后端rpc中的redis中去进行二次校验(当然如果你感觉这里多一次申请,你能够把这一步放到api里间接申请redis也能够),通过rpc的validateToken办法校验 message ValidateTokenReq { int64 userId = 1; string token = 2;}message ValidateTokenResp { bool ok = 1;}rpc validateToken(ValidateTokenReq) returns(ValidateTokenResp);校验之前登陆、注册等受权时候颁发进来存在redis的token是否正确、过期。 这样api就能够返回给nginx的auth模块是否失败,如果失败auth会间接返回给前端http code 401(所以你们前端应该是先判断http状态码>=400全副异样,再判断业务错误码) , 如果胜利间接拜访后端服务了拿到数据间接返回给前端展现 ...

April 18, 2022 · 1 min · jiezi

关于golang:一看就会的集成部署太简单了那顺便再撸个存储文件服务吧-二

本篇次要介绍Go开发minio存储文件服务的过程. 篇幅有点长. 要实现的性能, 如下: 鉴权(jwt、casbin) 正文文档(swagger) MinioSDK(minio) 集成部署(jenkins, docker) 代码↓: Github \前端 https://github.com/guangnaoke... Go https://github.com/guangnaoke... Gitee \前端 https://gitee.com/Xiao_Yi_Zho... Go https://gitee.com/Xiao_Yi_Zho... 都是些比较简单的性能. 那么... 开整! 装置GO装置库及插件 go get -u github.com/gin-gonic/gingo get github.com/casbin/casbin/v2go get github.com/golang-jwt/jwtgo get github.com/minio/minio-go/v7go get github.com/swaggo/gin-swaggergo get github.com/swaggo/swaggo get gopkg.in/yaml.v3go get -u gorm.io/gormgo get -u gorm.io/driver/sqlitedocker装置Minio docker run \-d \-p 9000:9000 \-p 9001:9001 \--name minio \--restart=always \-v /www/minio/data:/data \-e "MINIO_ROOT_USER=YOURNAME" \-e "MINIO_ROOT_PASSWORD=YOURPASSWORD" \minio/minio:latest server /data --console-address ":9001"MINIO_ROOT_USER, MINIO_ROOT_PASSWORD 账号密码改成本人的 ...

April 18, 2022 · 13 min · jiezi

关于golang:golang-GRPC连接池的设计与实现

在分布式高并发服务器中,client到server以及server中的多个节点之间的连贯往往应用连接池来治理。简略来说就是将提前创立好的连贯保留在池中,当有申请到来时,间接应用连接池中的连贯对server端拜访,省去了创立连贯和销毁连贯的开销(TCP建设连贯时的三次握手和开释连贯时的四次挥手),从而进步了性能。 GRPC的两个个性:多路复用、超时重连。 多路复用GRPC应用HTTP/2作为应用层的传输协定,HTTP/2会复用底层的TCP连贯。每一次RPC调用会产生一个新的Stream,每个Stream蕴含多个Frame,Frame是HTTP/2外面最小的数据传输单位。同时每个Stream有惟一的ID标识,如果是客户端创立的则ID是奇数,服务端创立的ID则是偶数。如果一条连贯上的ID应用完了,Client会新建一条连贯,Server也会给Client发送一个GOAWAY Frame强制让Client新建一条连贯。一条GRPC连贯容许并发的发送和接管多个Stream,而管制的参数便是MaxConcurrentStreams,Golang的服务端默认是100。 超时重连咱们在通过调用Dial或者DialContext函数创立连贯时,默认只是返回ClientConn构造体指针,同时会启动一个Goroutine异步的去建设连贯。如果想要等连贯建设完再返回,能够指定grpc.WithBlock()传入Options来实现。超时机制很简略,在调用的时候传入一个timeout的context就能够了。重连机制通过启动一个Goroutine异步的去建设连贯实现的,能够防止服务器因为连贯闲暇工夫过长敞开连贯、服务器重启等造成的客户端连贯生效问题。也就是说通过GRPC的重连机制能够完满的解决连接池设计准则中的闲暇连贯的超时与保活问题。 举荐开源我的项目: https://github.com/grpc-ecosy...gRPC-Gateway是protoc的插件.它读取gRPC服务定义并生成反向代理服务器,将 RESTful JSON API转换为gRPC.此服务器是依据你的gRPC定义中的自定义选项生成的.gRPC-Gateway可帮忙你同时提供gRPC和RESTful格调的API.

April 17, 2022 · 1 min · jiezi

关于golang:七天实现web框架模版渲染

当初风行的前后端拆散我的项目,应用更加业余的前端框架来制作页面,应用ajax进行数据交互,这样使得页面更加的业余。然而前后端拆散的我的项目,还是很有毛病的,所以对于模版渲染这个性能还是要有的。 模版渲染的实现网页的三剑客,JavaScript、CSS 和 HTML。要做到服务端渲染,第一步便是要反对 JS、CSS 等动态文件。还记得咱们之前设计动静路由的时候,反对通配符匹配多级子门路。比方路由规定/assets/filepath,能够匹配/assets/结尾的所有的地址。例如/assets/js/geektutu.js,匹配后,参数filepath就赋值为js/geektutu.js。那如果我么将所有的动态文件放在/usr/web目录下,那么filepath的值即是该目录下文件的绝对地址。映射到实在的文件后,将文件返回,动态服务器就实现了。找到文件后,如何返回这一步,net/http库曾经实现了。因而,gee 框架要做的,仅仅是解析申请的地址,映射到服务器上文件的实在地址,交给http.FileServer解决就好了。 // create static handlerfunc (group *RouterGroup) createStaticHandler(relativePath string, fs http.FileSystem) HandlerFunc { absolutePath := path.Join(group.prefix, relativePath) fileServer := http.StripPrefix(absolutePath, http.FileServer(fs)) return func(c *Context) { file := c.Param("filepath") // Check if file exists and/or if we have permission to access it if _, err := fs.Open(file); err != nil { c.Status(http.StatusNotFound) return } fileServer.ServeHTTP(c.Writer, c.Req) }}// serve static filesfunc (group *RouterGroup) Static(relativePath string, root string) { handler := group.createStaticHandler(relativePath, http.Dir(root)) urlPattern := path.Join(relativePath, "/*filepath") // Register GET handlers group.GET(urlPattern, handler)}咱们给RouterGroup增加了2个办法,Static这个办法是裸露给用户的。用户能够将磁盘上的某个文件夹root映射到路由relativePath。例如: ...

April 17, 2022 · 2 min · jiezi

关于golang:七天实现web框架中间件的实现

在web开发中,咱们须要在真正的逻辑解决前,咱们有些操作须要提前进行解决,同时逻辑解决后,咱们须要解决其余的逻辑。 什么是中间件在正式的业务逻辑解决前或之后,咱们须要进行一些解决。这些解决是须要咱们增加中间件来进行增加的。因为在框架中,咱们须要给用户本人增加一些解决形式函数的中央的,这个中央呢咱们不能太过于底层,这样对于用户来说必定是不好的。所以咱们须要一个比拟好的形式进行增加。 实现思路对于这个中间件的增加,逻辑咱们也是很简略的,咱们应用一个切片进行存储函数来实现,首先是须要在路由中进行存储的,而后咱们将这个货色放在上下文中去进行存储。 type RouterGroup struct { prefix string middlewares []HandlerFunc // support middleware parent *RouterGroup // support nesting engine *Engine // all groups share a Engine instance}上下文中的存储 type Context struct { // origin objects Writer http.ResponseWriter Req *http.Request // request info Path string Method string Params map[string]string // response info StatusCode int // middleware handlers []HandlerFunc index int}func (c *Context) Next() { c.index++ s := len(c.handlers) for ; c.index < s; c.index++ { c.handlers[c.index](c) }}为什么在路由中进行存储了咱们还须要在上下文进行存储呢?咱们能够看到,在这里咱们还实现了一个函数,就是next,对于next函数其实就是咱们将中间件分成了两个局部,一个是解决业务代码前解决,和业务代码解决后处理,那么在next前的就是业务逻辑前,而后前面就是业务逻辑后。而后这里咱们做一个演示: ...

April 17, 2022 · 2 min · jiezi

关于golang:七天实现web框架分组路由

在我的项目中咱们很多时候会对一组的路由进行解决,咱们应该如何实现这个性能呢。目前来说,咱们进行的都是单个路由的解决。 分组的实现明天的内容不是很多,咱们对router文件进行改在。在分组的时候,给每个组要用本人的中间件,也就是共有的中间件,所以咱们首先实现一个构造体 type RouterGroup struct { prefix string middlewares []HandlerFunc // support middleware parent *RouterGroup // support nesting engine *Engine // all groups share a Engine instance}而后engine要实现所以的性能,所以咱们要进行一个革新: func New() *Engine { engine := &Engine{router: newRouter()} engine.RouterGroup = &RouterGroup{engine: engine} engine.groups = []*RouterGroup{engine.RouterGroup} return engine}而后上面是咱们是咱们总体的代码: package geeimport ( "log" "net/http")type RouterGroup struct { prefix string middlewares []HandlerFunc // support middleware parent *RouterGroup // support nesting engine *Engine // all groups share a Engine instance}// HandlerFunc defines the request handler used by geetype HandlerFunc func(*Context)// Engine implement the interface of ServeHTTPtype Engine struct { *RouterGroup router *router groups []*RouterGroup // store all groups}// New is the constructor of gee.Enginefunc New() *Engine { engine := &Engine{router: newRouter()} engine.RouterGroup = &RouterGroup{engine: engine} engine.groups = []*RouterGroup{engine.RouterGroup} return engine}// Group is defined to create a new RouterGroup// remember all groups share the same Engine instancefunc (group *RouterGroup) Group(prefix string) *RouterGroup { engine := group.engine newGroup := &RouterGroup{ prefix: group.prefix + prefix, parent: group, engine: engine, } engine.groups = append(engine.groups, newGroup) return newGroup}func (group *RouterGroup) addRoute(method string, comp string, handler HandlerFunc) { pattern := group.prefix + comp log.Printf("Route %4s - %s", method, pattern) group.engine.router.addRoute(method, pattern, handler)}// GET defines the method to add GET requestfunc (group *RouterGroup) GET(pattern string, handler HandlerFunc) { group.addRoute("GET", pattern, handler)}// POST defines the method to add POST requestfunc (group *RouterGroup) POST(pattern string, handler HandlerFunc) { group.addRoute("POST", pattern, handler)}// Run defines the method to start a http serverfunc (engine *Engine) Run(addr string) (err error) { return http.ListenAndServe(addr, engine)}//这里就是最初的处理函数,这里开始建设的上下文。func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) { c := newContext(w, req) engine.router.handle(c)}

April 15, 2022 · 2 min · jiezi

关于golang:Go的高效开发套路

A. 背景以后在公司进行Go服务端研发工作时,发现短少Go开发的最佳实际,而导致以下景象 用Go开发时会比拟迷茫,不知如何下手,怎么发展工作比拟高效。反复造轮子比较严重。我的项目的代码品质参差不齐,导致交付的产品质量参差不齐。产品运行黑盒,可观测性差,能跑就行。代码实现考验研发人员程度,但顶尖的毕竟是多数,往往比拟差,而且顶尖也说不准会犯错。一个人负责整个性能开发,一旦人员到职,代码保护就会难上艰巨。而我在进行产品研发工作时,意识到后续产品的性能会更加简单,不重视这些问题的话,后续恐怕呈现代码更加难以保护、产品性能难以改变、用户需要无奈疾速满足的多米诺效应。为了尽早禁止该景象的产生,摸索一套能够在Go畛域进行最佳实际的范式,从而达到以下成果: 代码易保护,起码有两个人熟知代码实现目标,也容易进行代码迭代更新。代码设计正当,防止过于奇葩、适度以及不全面的实现。产品质量保障,交付产品可观测性强、Bug率低。升高反复造轮子景象,专一于业务逻辑开发,晋升研发效率。故此从研发效力方面钻研解法,摸索从集体开发行为到团队合作上的晋升,最终在研发代码质控以及研发框架两个方面去实际各种办法,最终总结出以后的高效开发的模式,并继续优化积淀中,心愿通过实际验证该模式的成果,推广至所有应用Go进行服务端开发的场景实现,进步整个团队研发效力。 B. 模式简介本模式有三个局部组成,别离为标准化研发标准、契约合作以及对立的开发框架。 标准化研发标准旨在研发人员在进行代码开发时可能从代码编程格调、Git提交标准、Review机制能达成共识契约并严格遵守,为产品的品质负责。契约合作旨在研发人员与内部须要调用其服务的合作伙伴,造成以契约为准,单方工作发展不相互强依赖具体实现的合作模式,升高单方沟通老本。对立的开发框架旨在束缚研发人员应用对立的框架开发,积淀各种场景实现用于优化并演进框架,达到开发时可疾速复用场景、继续迭代优化各场景实现以及为将来更多场景做松软的积攒。C. 模式实际-Go服务端开发以后在理论开发业务中都有相应的实际标准化研发标准代码编程标准在接手一些组件的开发时,看到代码的实现上存在格调迥异,浏览了解上比拟艰难,所以定下束缚格调的编程标准,能够晋升代码的可浏览性,代码的易维护性。在摸索Go代码编程标准中,业界有EffectiveGo和UberGoGuide等比拟出名的编程标准。 EffectiveGo作为Go官网出品的编程标准,简直所有应用Go语言开发的程序员都有看过并从中获益,普适性高。UberGoGuide是业界中应用Go语言开发的佼佼者,其开源了很多高质量的开源软件,比方Zap;它所开源的编程标准也是在EffectiveGo以及其余编程标准作为根底下来扩大。从综合思考上,Uber有中文翻译版上手速度快,且其公司依照标准开源的软件品质实现也侧面证实了该标准的优越性,所以抉择UberGoGuide作为领导编程标准,但并不作为惟一参考规范,也思考能够依据理论运行的最佳实际作为相应变动。 Git提交标准在团队合作开发组件时,查看到Git的提交信息很随便,仅仅简略形容几句,但齐全不晓得理论做了什么。在回溯代码问题时,不晓得这个提交做了什么,对代码保护上造成肯定妨碍。预计参考业界优良对立标准Conventional Commit 1.0进行束缚。 Conventional Commit 1.0,是一种标准提交信息的轻量级规约,它提供了一些易于了解的规定用于领导咱们如何编写commit信息,因而这也很容易被机器进行解读,联合SemVer版本标准,能够很容易通过工具既能自动化治理版本号,也进一步束缚开发人员对提交信息的严谨性,毕竟如果乱打commit信息会间接影响到版本的制订。 因而在实践中上,严格遵守Conventional Commit 1.0来进行提交信息的规定,对于提交信息不明确者也能够回绝其代码提交。 Review机制在原先的团队单干模式中,大部分是各自开发相应的模块并间接提交到Master分支,而后就公布了,这样的做法尽管速度很快但交付品质却很堪忧,毕竟它很考验研发人员的综合程度,但毕竟不是人人都是神,所以它无奈防止到缺点的数量增长,不仅对整体代码品质堪忧,甚至对交付产品的品质也无奈保障,更甚会影响到整个团队的口碑。为了解决这些问题,引入CodeReview机制来管制代码合并到代码交付的过程,从而管控整个代码品质。 CodeView机制参加的人员角色有代码提交者(Commiter)以及代码审核者(Reviewer),代码提交者每次提交代码后,均须要由代码审核者进行代码,审核通过后能力合并代码进主分支,从而达到可公布的可能性。 这样的机制履行下,须要能够做到以下方面成果: 使研发人员在每次提交中,有其余视角去验证代码实现计划、解决思路,保障肯定的客观性。负责Reviewr角色的人员,须要在过程做到辨认bugs、是否有逻辑问题还有是否有笼罩全边缘场景,这样能够保障实现的思路足够全面。其益处列举如下: 常识共享:团队外部在进行Review就能晓得代码的实现、应用的设计等进行共享,有利于团队代码程度的进步。能够更早的发现bug:防止在性能推出后才发现Bug,做到及时止损。确保合规性:程序员的背景不同导致各自的代码格调不同,但通过标准规范束缚我的项目代码的合规性。加强安全性:当有平安技术背景的业余人员加入到review中时,安全性的等级是十分高的。加强团队单干意识:当团队成员一起去解决一个问题,这样有利于晋升他们各自的OwnerShip思维以及团队的归属感。以后也须要做到以下约定: Reviewr和Commiter在一次提交上,严格意义上不能为同一个人。Comment时须要是善意的就事论事,单方均须要以案例和谨严的逻辑斟酌去进行探讨,而且只有Reviewr批准后能力终止Comment。提交代码前须要本身曾经通过编译以及测试。一次提交尽量做到外围代码不超过400行的。一次Review尽量低于1小时。须要知会到Reviewr清晰晓得这次提交的实现目标,最终冀望是什么。如果胆怯提交的代码写的不好,那就将代码实现达到本身认可的水平先。单次提交如果代码量过大或逻辑简单能够设置两个以上的Reviewer进行Review。Reviewer和Commiter存在连带责任,Commiter提交的代码在生产呈现问题并呈现价值损失时,Reviewer也须要承当主要责任。Review机制很容易会被忽视并绕过,但其结果有时候是很惨痛的,所以须要团队人员统一认可并恪守,能力做得好。契约合作PB文件既契约模式在开发人员进行API开发到实现后,往往都须要提供文档给他人去调用,这样的做法有以下毛病: 接口文档交付迟缓,接口需求方须要期待接口文档给出能力进行开发,这样对于需求方来说会更加开发危险会减少。接口更新不及时,当开发人员在更改API时,90%的概率会遗记更新文档,导致第三方在调用时呈现很多情况须要沟通,这样减少单方的沟通老本,升高了单方的开发效率。为了解决以上问题,推广Protobuf文件(以下简称PB文件)既契约的模式。该模式遵循Google API 指南,实现了对应通信协议反对,并且恪守了 gRPC API 应用 HTTP 映射性能进行 JSON/HTTP 的反对。因而能够做到通过定义PB文件即可定义出REST和RPC API,通过相似GoogleAPI的仓库形式进行API Schema的治理。 再者联合丰盛的Protoc插件能够自动化Swagger文档或生成不同语言类型的代码,如下: Go HTTP API:通过protoc-gen-go-http插件进行生成Go版的Http相干代码Go gRPC API:通过protoc-gen-go-grpc插件生成Go版的gPRC相干代码Swagger文档:通过protoc-gen-openapiv2插件生成因而,研发人员通过PB文件即可清晰晓得API的定义,参数、返回内容等bin并能根据PB文件生成相应的代码(Server和Client)就可进行各自的开发,也能够间接生成Swagger文档来查看应用,极大的升高了沟通的老本。 该模式也须要恪守以下规定: 新契约变更须要告诉到对方已运行一段时间的旧契约遇到BreakChange需要,应思考新起契约,不应在原有根底改变。新契约定制时需先通过需求方Review后,再持续开发,防止返工,也保障需要实现准确性。契约尽量以清晰简洁明了的构造体定义为优先,对于非凡需要再应用Map、PBValue等。对立的开发框架对于框架的入门应用有另外一篇文章进行领导开发框架选型在过往的编程教训中,对一个框架的积攒积淀有助于晋升整体的开发效力,毕竟前人栽树后人乘凉,前人曾经摸清楚框架的应用,就有足够的信念领导前人以此进行开发、优化框架代码以及建设起整套的周边生态,能够有余力应酬将来的简单业务场景。 因以后团队人力无限仅4人,即便有心也有力去做到自研框架这种须要大量脑力心力且须要积攒积淀的工作,于是放弃自研框架,抉择借力开源产品。 在从框架选型上,从以后团队需要场景考量,团队须要的是一套能够束缚实现标准、生态欠缺、用户自主性强、后盾性能要求不高以及反对微服务化的框架。 在Go这边选型中预选了GVA、Gin和Kratos作为比对并进行实际,总结GVA、Gin以及Kratos三者比对如下 Gin, 为一个根底轻量级高性能的Web框架,实现场景案例较多,但代码实现无约束标准须要靠开发者本人摸索,普适性较强。GVA,是一个前后端配套的框架,次要解决是疾速开发web利用,但框架代码实现上并没有很好的理论指导以及标准束缚,整体应用后的代码品质较差难保护,应答简单业务需要能力差,最让人诟病的是滥用的公共变量。Kratos,bilibli业务验证出品,配套健全的微服务框架,设计上遵循整洁架构以及DDD思维,实现的模块耦合度低,用户自主性强,应答简单的业务需要能力强,但总体设计理念上偏向于标准规范定制,整体性能方面中上,需本人扩大模块实现更高性能。框架类型协定反对扩展性领导力易保护生态性能框架代码品质GitHubStar数GVAWeb前后端框架HTTP差弱弱不欠缺低低11.8kGin后端HTTP框架HTTP强弱中欠缺高高56.1kKratos微服务框架HTTP/gRPC强强强欠缺中高16.9k最终采纳Kratos作为对立的开发框架,具体决策依据如下 多协定:默认反对HTTP/gRPC,可自主依照标准定制扩大,反对采纳gin来实现transport外围。API契约理念:反对PB文件定义,可生成服务端、客户端代码以及Swagger文档,并提供在线实时文档性能。DDD思维分层开发:领导开发人员进行开发时,能依照畛域驱动的形式进行开发,让性能更加聚焦,实现更加精炼,且耦合度低,比方将ClickHouse替换为SLS时只须要改变基础设施层实现即可。框架结构模块化清晰:简直涵盖微服务框架开发中所波及的模块,比方认证、注册、自监控等,并这些模块均可自定义实现,适配性强。高扩展性的中间件设计:极强扩展性的中间件设计,灵便扩大各种业务所需中间件模块。框架代码品质:总体代码品质下层,模块依赖解耦明显,用户浏览上手容易,自主定制优化可行。性能方面:当初业务临时或未来1~2年均以Web后端利用以及微服务为主,所以性能要求方面不刻薄,且如需高性能时也可自主实现扩大模块实现。开发框架积淀以后组内已在Kratos框架有肯定的积攒,为后续进步后端开发进步很好的根底,具体如下: Layout疾速开发:依附Kratos的Layout能力,定制合乎本身的Layout模板,在后续开发中能够疾速应用,以后Layout实现上已蕴含各层的写法模板、Prom指标集成、Opentelemetry全链路集成、本地缓存实现等。自定义API文档信息的写法领导:已充沛踩坑如何丰盛API文档,对于任何自定义信息需要均能满足。疾速应用Kratos开发的指南:配合Layout,助力疾速上手框架,疾速进行业务开发。通过在线API文档生成前端SDK:通过Swagger-gen疾速生成前端SDK,前端无需手写SDK,可保护强。之后也会积淀更多的场景到框架和文档中,丰盛框架的应用场景,以助力开发提效。实际成果以后将该模式在外部我的项目中实际,有以下成果晋升 Bug状况:在通过次实际后代码级别的bug数量从我的项目开始到当初低于10个,需要级别Bug数量低于15个。前后端合作效率:前后端以后沟通协商根本只在需要协商、协定定义和理论联调阶段进行协商,整体交付性能速度均低于2个星期。个性实现速度:后端服务交付实现个性,均低于5天一周期,我的项目停顿卡点不在后端实现上。团队单干气氛:review机制的退出,让团队成员更加富裕凝聚力以及更违心做常识分享,晋升了团队人员整体能力。交付服务自监控欠缺:落地全链路监控,晋升前后端全链路可观测性,定位问题低于5分钟内。总体的成果是合乎预期设计的,但还有许多优化的空间比方API治理、Error标准、CICD标准化,将持续积淀优化该模式,晋升后续个性以及将来新我的项目的交付速率,晋升开发人员幸福感,升高开发成本。 Referencehttps://github.com/uber-go/gu...https://go.dev/doc/effective_gohttps://www.conventionalcommi...https://semver.org/https://www.bookstack.cn/read...https://google.aip.dev/https://developers.google.com...https://developers.google.com...https://colobu.com/2017/03/16...https://go-kratos.dev/docs/https://about.gitlab.com/topi...https://www.perforce.com/blog...

April 15, 2022 · 1 min · jiezi

关于golang:Go语言轻松进阶

导读本文基于Go源码版本1.16、64位Linux平台、1Page=8KB、本文的内存特指虚拟内存明天咱们开始进入《Go语言轻松进阶》系列第二章「内存与垃圾回收」第二局部「Go语言内存治理」。 对于「内存与垃圾回收」章节,会从如下三大部分开展: 读前常识储备(已完结) 指针的大小内存的线性调配什么是FreeList?虚拟内存TCMalloc内存调配原理Go语言内存治理(以后局部)Go语言垃圾回收原理(未开始)第一局部「读前常识储备」曾经完结,为了更好了解本文大家能够点击历史链接进行查看或温习。 目录对于解说「Go语言内存治理」局部我的思路如下: 介绍整体架构介绍架构设计中一个很有意思的中央通过介绍Go内存治理中的要害构造mspan,带出page、mspan、object、sizeclass、spanclass、heaparena、chunk的概念接着介绍堆内存、栈内存的调配回顾和总结通过这个思路拆解的目录: Go内存治理架构(本篇内容) mcachemcentralmheap为什么线程缓存mcache是被逻辑处理器p持有,而不是零碎线程m?Go内存治理单元mspan page的概念mspan的概念object的概念sizeclass的概念spanclass的概念heaparena的概念chunk的概念Go堆内存的调配 微对象调配小对象调配大对象调配Go栈内存的调配 栈内存调配机会小于32KB的栈调配大于等于32KB的栈调配Go内存治理架构Go的内存对立由内存管理器治理的,Go的内存管理器是基于Google本身开源的TCMalloc内存分配器为理念设计和实现的,对于TCMalloc内存分配器的具体介绍能够查看之前的文章。 先来简略回顾下TCMalloc内存分配器的外围设计。 回顾TCMalloc内存分配器TCMalloc诞生的背景?在多核以及超线程时代的明天,多线程技术曾经被宽泛使用到了各个编程语言中。当应用多线程技术时,因为多线程共享内存,线程申在请内存(虚拟内存)时,因为并行问题会产生竞争不平安。 为了保障分配内存的过程足够平安,所以须要在内存调配的过程中加锁,加锁过程会带来阻塞影响性能。之后就诞生了TCMalloc内存分配器并被开源。 TCMalloc如何解决这个问题?TCMalloc全称Thread Cache Memory alloc线程缓存内存分配器。顾名思义就是给线程增加内存缓存,缩小竞争从而进步性能,当线程内存不足时才会加锁去共享的内存中获取内存。 接着咱们来看看TCMalloc的架构。 TCMalloc的架构?TCMalloc三层逻辑架构 ThreadCache:线程缓存CentralFreeList(CentralCache):地方缓存PageHeap:堆内存TCMalloc架构上不同的层是如何合作的?TCMalloc把申请的内存对象按大小分为了两类: 小对象 <= 256 KB大对象 > 256 KB咱们这里以调配小对象为例,当给小对象分配内存时: 先去线程缓存ThreadCache中调配当线程缓存ThreadCache的内存不足时,从对应SizeClass的地方缓存CentralFreeList获取最初,再从对应SizeClass的PageHeap中调配 Go内存分配器的逻辑架构采纳了和TCMalloc内存分配器一样的三层逻辑架构: mcache:线程缓存mcentral:地方缓存mheap:堆内存<p align="center"> <img src="http://cdn.tigerb.cn/20220405133623.png" style="width:60%"></p> 理论地方缓存central是一个由136个mcentral类型元素的数组形成。 除此之外须要特地留神的中央:mcache被逻辑处理器p持有,而并不是被真正的零碎线程m持有。(这个设计很有意思,后续会有一篇文章来解释这个问题) 咱们更新下架构图如下: 「Go内存分配器」把申请的内存对象按大小分为了三类: 微对象 0 < Micro Object < 16B小对象 16B =< Small Object <= 32KB大对象 32KB < Large Object为了清晰看出这三层的关系,这里以堆上调配小对象为例: 先去线程缓存mcache中分配内存找不到时,再去地方缓存central中分配内存最初间接去堆上mheap调配一块内存<p align="center"> <img src="http://cdn.tigerb.cn/20220405224348.png" style="width:80%"></p> 架构总结通过以上的剖析能够看出Go内存分配器的设计和开源TCMalloc内存分配器的理念、思路基本一致。比照图如下: 最初咱们总结下: Go内存分配器采纳了和TCMalloc一样的三层架构。逻辑上为: mcache:线程缓存mcentral:地方缓存mheap:堆内存线程缓存mcache是被逻辑处理器p持有,而不是零碎线程m查看《Go语言轻松进阶》系列更多内容链接 http://tigerb.cn/go/#/kernal/

April 15, 2022 · 1 min · jiezi

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

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

April 14, 2022 · 3 min · jiezi

关于golang:七天实现web框架动态路由

之前看学长他们用过这个动静路由,然而以前也没有太留神,最近在学习框架的建设。第一次让我感触到了算法在写我的项目时候的利用,之前都感觉算法这个货色,就是刷题用的,在实战的时候很少用。明天学习这一块略微有一点吃力,然而还是看懂了,很nice。<!-- more --> 什么是动静路由呢动静路由在开发的时候,就是说不是像原来写我的项目的时候,这个固定路由绑定固定的函数,而是变动的路由进行固定函数的绑定,这样就能够实现很多的性能。 动静路由的实例:/api/v1/:name 这里咱们要晓得的是,下面这个路由能够匹配很多的路由比方: /api/v1/lzz /api/v1/ceshi ....等 这里第三个分隔开的字段,就是对name的赋值。这就是动静路由 实现动静路由须要筹备什么首先咱们要用到的就是前缀树,这里咱们看一下怎么实现前缀树。咱们还是阐明一下,比如说,在咱们路由中咱们能够通过/来宰割节点,而后前面通过这些节点进行寻找正确的路由。、 下面的这个图就是展现了前缀树。而后上面咱们就来实现一下这个前缀树。 首先定义前缀树的节点: type node struct { pattern string //待匹配的路由 part string //路由中的一部分 children []*node //子节点 isWild bool //这里如果是*或者:结尾的就是true}而后咱们建设一个注册函数,一个查找函数 func (n *node) insert(pattern string, parts []string, height int) { //查看是不是最初一个,是的话就返回 if len(parts) == height { n.pattern = pattern return } //取出门路中的一部分 part := parts[height] //查看当初的树中是不是有这个节点,没有的话就进行创立 child := n.matchChild(part) //上面是创立的流程。也就是创立 if child == nil { //如果是动静的就会设置为true child = &node{part: part, isWild: part[0] == ':' || part[0] == '*'} n.children = append(n.children, child) } child.insert(pattern, parts, height+1)}查找函数: ...

April 14, 2022 · 2 min · jiezi

关于golang:微服务从代码到k8s部署应有尽有系列二网关

咱们用一个系列来解说从需要到上线、从代码到k8s部署、从日志到监控等各个方面的微服务残缺实际。 整个我的项目应用了go-zero开发的微服务,根本蕴含了go-zero以及相干go-zero作者开发的一些中间件,所用到的技术栈根本是go-zero项目组的自研组件,根本是go-zero全家桶了。 实战我的项目地址:https://github.com/Mikaelemmm... 1. go-zero 网关概念go-zero架构往大的说次要由两局部组成,一个是api,一个是rpc。api次要是http对外拜访的,rpc次要就是外部业务交互应用的是protobuf+grpc,当咱们我的项目体量还不大的时候,咱们能够应用api来做一个单体我的项目,等后续量上来之后,能够拆分到rpc做微服务,从单体转向微服务非常容易,很像java的springboot转像springcloud,十分不便。 api被很多同学了解成了网关,实际意义上来说当你的我的项目在应用go-zero做微服务时候,你把api当成网关也没什么大的问题,不过这样做导致的问题就是一个api对应前面多个rpc,api充当了网关,这样如果我在更新后续业务代码时候,更新任何业务都要去改变这个api网关,比方我只是改了一个小小的不起眼的服务,那就要从新构建整个api,这样不太正当,效率极低也很不不便。所以,咱们只是把api当成一个聚合服务,能够拆分成多个api,比方用户服务有用户服务的rpc与api,订单服务,有订单服务的rpc与api,这样当我批改用户服务时候,我只须要更新用户的rpc与api,所有的api只是用来聚合后端rpc的业务。那有的同学就会说,我总不能每个服务解析个域名对应你的api吧,当然不能,这时候api后面就要有一个网关了,这个网关才是真正意义上的网关,比方咱们常说的nginx、kong、apisix,很多微服务都内置了网关,比方springcloud提供了springcloud-gateway , go-zero没有提供,理论也用不着独自去写一个网关,市面上的网关曾经够多了,go-zero官网在晓黑板中用的nginx足够用了,当然你如果更相熟kong、apisix都能够替换,实质上没什么不一样的,只是一个对立流量入口,对立鉴权等。 2. nginx网关【注】:在看这里的时候,倡议先看一下前一节的业务架构图 本我的项目中理论也应用了nginx做为网关,应用nginx的auth_request模块作为对立鉴权,业务外部不做鉴权(设计到资产的最好业务外部做二次鉴权,次要多一层平安),nignx的网关配置在我的项目的data/nginx/conf.d/looklook-gateway.conf server{ listen 8081; access_log /var/log/nginx/looklook.com_access.log; error_log /var/log/nginx//looklook.com_error.log; location /auth { internal; proxy_set_header X-Original-URI $request_uri; proxy_pass_request_body off; proxy_set_header Content-Length ""; proxy_pass http://identity-api:8001/identity/v1/verify/token; } location ~ /usercenter/ { auth_request /auth; auth_request_set $user $upstream_http_x_user; proxy_set_header x-user $user; proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header REMOTE-HOST $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_pass http://usercenter-api:8002; } location ~ /travel/ { auth_request /auth; auth_request_set $user $upstream_http_x_user; proxy_set_header x-user $user; proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header REMOTE-HOST $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_pass http://travel-api:8003; } location ~ /order/ { auth_request /auth; auth_request_set $user $upstream_http_x_user; proxy_set_header x-user $user; proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header REMOTE-HOST $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_pass http://order-api:8004; } location ~ /payment/ { auth_request /auth; auth_request_set $user $upstream_http_x_user; proxy_set_header x-user $user; proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header REMOTE-HOST $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_pass http://payment-api:8005; }}容器外部nginx端口是8081,应用docker裸露进来8888映射端口8081,这样内部通过8888来拜访网关,应用location来匹配每个服务,当然会有人说,每加一个api服务都要来nignx配置太麻烦,你也能够应用confd对立配置,自行百度。 ...

April 14, 2022 · 1 min · jiezi

关于golang:用Go构建你专属的JA3指纹

来自公众号:Gopher指北在这篇文章中将会简略回顾https的握手流程,并基于读者的提问题解释什么是JA3指纹以及如何用Go定制专属的JA3指纹。 本文纲要如下,请各位读者跟着老许的思路逐渐构建本人专属的JA3指纹。 回顾HTTPS握手流程在正式开始理解什么是JA3指纹之前,咱们先回顾一下HTTPS的握手流程,这将有助于对后文的了解。 在码了2000多行代码就是为了讲清楚TLS握手流程这篇文章中次要剖析了HTTPS单向认证和双向认证流程(TLS1.3)。 在单向认证中,客户端不须要证书,只需验证服务端证书非法即可。其握手流程和替换的msg如下。 在双向认证中,服务端和客户端均需验证对方证书的合法性。其握手流程和替换的msg如下。 单向认证和双向认证的比照: 单向认证和双向认证中,总的数据收发仅三次,单次发送的数据中蕴含一个或者多个音讯clientHelloMsg和serverHelloMsg未通过加密,之后发送的音讯均做了加密解决Client和Server会各自计算两次密钥,计算机会别离是读取到对方的HelloMsg和finishedMsg之后双向认证和单向认证相比,服务端多发送了certificateRequestMsgTLS13音讯双向认证和单向认证相比,客户端多发送了certificateMsgTLS13和certificateVerifyMsg两个音讯无论是单向认证还是双向认证,Server对于Client的根本信息理解齐全依赖于Client被动告知Server,而其中比拟要害的信息别离是客户端反对的TLS版本、客户端反对的加密套件(cipherSuites)、客户端反对的签名算法和客户端反对的密钥替换协定以及其对应的公钥。这些信息均在蕴含clientHelloMsg中,而这些信息也是生成JA3指纹的要害信息,并且clientHelloMsg和serverHelloMsg未通过加密。未加密意味着批改难度升高,这也就为咱们定制本人专属的JA3指纹提供了可能。 如果有趣味理解HTTPS握手流程的更多细节,请浏览上面文章: 码了2000多行代码就是为了讲清楚TLS握手流程 码了2000多行代码就是为了讲清楚TLS握手流程(续) 什么是JA3指纹后面说了这么多,那么到底什么是JA3指纹呢。依据Open Sourcing JA3这篇文章,老许简略将其了解为JA3就是一种在线辨认TLS客户端指纹的办法。 该办法用于收集clientHelloMsg数据包中以下字段的十进制字节值:TLS Version、Accepted Ciphers、List of Extensions、Elliptic Curves和Elliptic Curve Formats。而后,它将这些值串联起来,应用“,”来分隔各个字段,同时应用“-”来分隔各个字段中的值。最初,计算这些字符串的md5哈希值,即失去易于应用和共享的长度为32字符的指纹。 为了更近一步形容分明这些数据的起源,老许将John Althouse文章中的抓包图联合Go源码中的clientHelloMsg构造体做了字段一一映射。 仔细的同学可能曾经发现了,依据前文形容JA3指纹总共有5个数据字段,而上图却只映射了4个。那是因为TLS的extension字段比拟多,老许就不一一整顿了。尽管没有一一列举,但老许筹备了一个单元测试,有趣味深入研究的同学能够通过这个单元测试进行调试剖析。 https://github.com/Isites/go-coder/blob/master/http2/tls/handsh/msg_test.goJA3指纹用处依据前文的形容,JA3指纹就是一个md5字符串。请大家回忆一下在平时的开发中md5的用处。 判断内容是否统一作为惟一标识md5尽管不平安,然而JA3抉择md5作为哈希的次要起因是为了更好的向后兼容很显著,JA3指纹也有其相似用处。举个简略的例子,攻击者构建了一个可执行文件,那么该文件的JA3指纹很有可能是惟一的。因而,咱们能通过JA3指纹识别出一些恶意软件。 在本大节的最初,老许给大家举荐一个网站,该网站挂出了很多歹意JA3指纹列表。 https://sslbl.abuse.ch/ja3-fingerprints/构建专属的JA3指纹http1.1的专属指纹前文提到clientHelloMsg和serverHelloMsg未通过加密,这为定制本人专属的JA3指纹提供了可能,而在github下面有一个库(https://github.com/refraction...) 能够在肯定水平上批改clientHelloMsg。上面咱们将通过这个库构建一个本人专属的JA3指纹。 // 要害importimport ( xtls "github.com/refraction-networking/utls" "crypto/tls")// 克隆一个Transporttr := http.DefaultTransport.(*http.Transport).Clone()// 自定义DialTLSContext函数,此函数会用于创立tcp连贯和tls握手tr.DialTLSContext = func(ctx context.Context, network, addr string) (net.Conn, error) { dialer := net.Dialer{} // 创立tcp连贯 con, err := dialer.DialContext(ctx, network, addr) if err != nil { return nil, err } // 依据地址获取host信息 host, _, err := net.SplitHostPort(addr) if err != nil { return nil, err } // 构建tlsconf xtlsConf := &xtls.Config{ ServerName: host, Renegotiation: xtls.RenegotiateNever, } // 构建tls.UConn xtlsConn := xtls.UClient(con, xtlsConf, xtls.HelloCustom) clientHelloSpec := &xtls.ClientHelloSpec{ // hellomsg中的最大最小tls版本 TLSVersMax: tls.VersionTLS12, TLSVersMin: tls.VersionTLS10, // ja3指纹须要的CipherSuites CipherSuites: []uint16{ tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, // tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384, tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, // tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384, tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, }, CompressionMethods: []byte{ 0, }, // ja3指纹须要的Extensions Extensions: []xtls.TLSExtension{ &xtls.RenegotiationInfoExtension{Renegotiation: xtls.RenegotiateOnceAsClient}, &xtls.SNIExtension{ServerName: host}, &xtls.UtlsExtendedMasterSecretExtension{}, &xtls.SignatureAlgorithmsExtension{SupportedSignatureAlgorithms: []xtls.SignatureScheme{ xtls.ECDSAWithP256AndSHA256, xtls.PSSWithSHA256, xtls.PKCS1WithSHA256, xtls.ECDSAWithP384AndSHA384, xtls.ECDSAWithSHA1, xtls.PSSWithSHA384, xtls.PSSWithSHA384, xtls.PKCS1WithSHA384, xtls.PSSWithSHA512, xtls.PKCS1WithSHA512, xtls.PKCS1WithSHA1}}, &xtls.StatusRequestExtension{}, &xtls.NPNExtension{}, &xtls.SCTExtension{}, &xtls.ALPNExtension{AlpnProtocols: []string{"h2", "http/1.1"}}, // ja3指纹须要的Elliptic Curve Formats &xtls.SupportedPointsExtension{SupportedPoints: []byte{1}}, // uncompressed // ja3指纹须要的Elliptic Curves &xtls.SupportedCurvesExtension{ Curves: []xtls.CurveID{ xtls.X25519, xtls.CurveP256, xtls.CurveP384, xtls.CurveP521, }, }, }, } // 定义hellomsg的加密套件等信息 err = xtlsConn.ApplyPreset(clientHelloSpec) if err != nil { return nil, err } // TLS握手 err = xtlsConn.Handshake() if err != nil { return nil, err } fmt.Println("以后申请应用协定:", xtlsConn.HandshakeState.ServerHello.AlpnProtocol) return xtlsConn, err}上述代码总结起来分为三步。 ...

April 13, 2022 · 2 min · jiezi

关于golang:家庭收支记账软件-GO语言实现

一个简略的命令行账本,用于练习 GO 语法应用,话不多说,源码贴上 package mainimport "fmt"// 定义存储生产信息的总汇合var sumMessage []count//定义总金额var sumMoney float32func main() { fmt.Println("=============家庭收支记账软件==============") fmt.Print("1 收支明细\t") fmt.Print("2 注销支出\t") fmt.Print("3 注销收入\t") fmt.Println("4 退出") var choose int //输出选项 fmt.Scanln(&choose) if choose == 1 { detail() } else if choose == 2 { add() } else if choose == 3 { sub() } else if choose == 4 { return }}// 定义每笔账单,存储生产金额和生产信息type count struct { txt string //账单信息 money float32 //账单金额}// 账单详情func detail() { var choose int if len(sumMessage) < 1 { fmt.Println("没有账单信息") } else { fmt.Println("===========生产明细===============") for _, sm := range sumMessage { fmt.Print(sm.txt) fmt.Print("\t\t\t") fmt.Println(sm.money) } fmt.Println("————————————————结算——————————————") if sumMoney < 0 { fmt.Println("负债为:", sumMoney) } else { fmt.Println("余额为:", sumMoney) } } fmt.Println("==================================") fmt.Println("输出 1 进入菜单\t输出 2 退出零碎") fmt.Scanln(&choose) if choose == 1 { main() } else { return }}// 支出func add() { var cadd count fmt.Print("输出支出金额:") fmt.Scanln(&cadd.money) sumMoney += cadd.money //将支出金额加到总金额 fmt.Print("输出支出起源: ") fmt.Scanln(&cadd.txt) sumMessage = append(sumMessage, cadd) main()}// 生产func sub() { var csub count fmt.Print("输出生产金额:") fmt.Scanln(&csub.money) sumMoney -= csub.money // 将生产金额从总金额中减去 fmt.Print("输出生产去向: ") fmt.Scanln(&csub.txt) sumMessage = append(sumMessage, csub) main()}

April 13, 2022 · 1 min · jiezi

关于golang:刷题笔记剑指-Offer-58-I-翻转单词顺序

这题思路很简略,因为这个是简略题。次要就是要宰割每个单词,而后逆序组成新的字符串。有两种方法,一个是背API,利用自带的函数帮忙咱们实现去除首尾空格和按空格宰割。另一个如官网题解所示,本人实现双指针,宰割字符串。 难点为什么我要写这篇题解,因为我面试这题挂了。基于缸中之脑实践和局部性原理,我在接下来几天内,都会频繁地再遇到这题。这题思路上没有任何难点,难点次要在于,如果是用自带函数,在线面试过程中,咱们没方法查看函数定义,甚至没方法应用规范输入(stdout),须要背API。如果是本人实现双指针,须要极度注意边界问题。 背APIGo 中负责去除首尾空格的函数为strings.Trim,函数签名为func Trim(s, cutset string) string,s参数为要解决的字符串,sep为须要前后去除的字符串,返回值是解决好的字符串。在这里,咱们只须要输出s = strings.Trim(s, " ")就能去除首尾的所有空格。 其次,咱们须要将字符串按空格宰割成一个个单词,单词间的可能空格不只一个,比方"abc defg"。这里咱们要留神,相对不能应用strings.Split! Split只能去除单个空格,如果有多个空格,将会宰割成多个长度为0的空字符串。比方下面的例子会宰割成["abc", "", "", "", "", "defg"]。因而咱们须要应用strings.Fields,函数签名如下func Fields(s string) []string。该函数能去除单词之间一个或多个的空格,返回字符串切片。应用下面的例子,会返回["abc","defg"],合乎咱们的要求。 如果有更多个性化的宰割需要,能够应用strings.FieldsFunc,函数签名如下func FieldsFunc(s string, f func(rune) bool) []string,须要你写一个函数f。对于合乎f函数的要求的一个或多个字符,都会被视为分隔符,而被宰割。上面正文的例子写得比较清楚,如果咱们还是要按空格宰割,就写一个匿名函数func(c rune)bool{ return c==' ' },留神该函数的参数必须是rune类型。 最初拼接字符串时,记得单词间要有空格,然而开端不要有空格。 func reverseWords(s string) string { s = strings.Trim(s, " ") words := strings.Fields(s) //words := strings.FieldsFunc(s, func(c rune) bool { // return c==' ' //}) result := "" for i := len(words) - 1; i >= 0; i-- { result += words[i] if i != 0 { result += " " } } return result}手动实现双指针手动实现双指针,须要注意边界问题,须要留神的点我都在正文下方写上了。首先咱们应用一个左右指针l,r。去除首尾空格。留神不要让l大于r,否则会有异样,因而咱们对于l指针的遍历,须要用l<r条件断定。而后的宰割操作。咱们应用两个指针i,j,同时指向单词开端。先让i跳过所有非空格,去到第一个空格,此时s[i+1:j+1]就是一个单词了,因而将此退出到列表中。随后让i继续前进,跳过所有空格,去到下一个单词的开端,同时让j跟上i,也就j=i。这时就能够进入下一个循环了。当去到正序一个单词时,i指针此时应该为-1,在将正序第一个单词退出列表后,外层循环i>=0会断定跳出。 ...

April 13, 2022 · 1 min · jiezi

关于golang:七天实现web框架上下文建立

对Web服务来说,无非是依据申请*http.Request,结构响应http.ResponseWriter 上下文必要性在书写web利用的时候,咱们应用原始的库的时候,其中承受申请信息和返回的音讯体是独自的存在,而后这样就会导致咱们书写很多冗余的代码,而后在返回的时候咱们须要返回json这个时候如果应用原生的库这个时候咱们书写的代码是: obj = map[string]interface{}{ "name": "geektutu", "password": "1234",}w.Header().Set("Content-Type", "application/json")w.WriteHeader(http.StatusOK)encoder := json.NewEncoder(w)if err := encoder.Encode(obj); err != nil { http.Error(w, err.Error(), 500)}而后如果咱们封装了过后咱们书写的代码是: c.JSON(http.StatusOK, gee.H{ "username": c.PostForm("username"), "password": c.PostForm("password"),})这里咱们能够显著的感触到咱们的代码简略了很多,如果没有进行封装,这个时候咱们应用起来是十分好受的。在这里咱们须要晓得的是,其实在web利用中,咱们须要很多的工具,在解决申请的时候,例如,未来解析动静路由/hello/:name,参数:name的值放在哪呢?再比方,框架须要反对中间件,那中间件产生的信息放在哪呢?Context 随着每一个申请的呈现而产生,申请的完结而销毁,和以后申请强相干的信息都应由 Context 承载。 也就是在这次会话的时候context就是一个会话的百宝箱,这个时候咱们能够找到咱们解决这次会话的所有工具。 实现代码type H map[string]interface{}type Context struct { // origin objects Writer http.ResponseWriter Req *http.Request // request info Path string Method string // response info StatusCode int}func newContext(w http.ResponseWriter, req *http.Request) *Context { return &Context{ Writer: w, Req: req, Path: req.URL.Path, Method: req.Method, }}func (c *Context) PostForm(key string) string { return c.Req.FormValue(key)}func (c *Context) Query(key string) string { return c.Req.URL.Query().Get(key)}func (c *Context) Status(code int) { c.StatusCode = code c.Writer.WriteHeader(code)}func (c *Context) SetHeader(key string, value string) { c.Writer.Header().Set(key, value)}func (c *Context) String(code int, format string, values ...interface{}) { c.SetHeader("Content-Type", "text/plain") c.Status(code) c.Writer.Write([]byte(fmt.Sprintf(format, values...)))}func (c *Context) JSON(code int, obj interface{}) { c.SetHeader("Content-Type", "application/json") c.Status(code) encoder := json.NewEncoder(c.Writer) if err := encoder.Encode(obj); err != nil { http.Error(c.Writer, err.Error(), 500) }}func (c *Context) Data(code int, data []byte) { c.Status(code) c.Writer.Write(data)}func (c *Context) HTML(code int, html string) { c.SetHeader("Content-Type", "text/html") c.Status(code) c.Writer.Write([]byte(html))}在这外面咱们将很多的工具进行了封装代码最结尾,给map[string]interface{}起了一个别名gee.H,构建JSON数据时,显得更简洁。Context目前只蕴含了http.ResponseWriter和*http.Request,另外提供了对 Method 和 Path 这两个罕用属性的间接拜访。提供了拜访Query和PostForm参数的办法。提供了疾速结构String/Data/JSON/HTML响应的办法。 ...

April 13, 2022 · 2 min · jiezi

关于golang:协程-以及进程和线程

背景与场景在Golang语言学习中,对协程的了解必不可少。仿佛咱们是通过Golang才晓得了“协程”的概念,不过协程并不只存在于Golang中,是一个由来已久的设计。据Knuth(计算机界最驰名大佬之一)说,1958年Melvin Conway就提出了协程的概念,且曾经被广泛应用于典型的重量级语言C#,Erlang,Golang;以及各种轻量级语言python,lua,JavaScript,ruby;包含函数式语言scala,scheme。相比于2007年才开始设计的Golang,曾经属于爷爷辈技术了。 当初的协程被设计用于实现合作式的多任务,又被形象地称为“用户态线程”(这和它背地的实现密切相关)。常见的利用场景包含实现状态机,实现并发的“演员模型”(通常被用在制作游戏中),实现生成器,实现通信程序过程等。 协程定义回到协程的定义,协程,coroutine,cooperation routine,合作的例程,维基百科的定义为:通过生成灵便的挂起和复原的子例程,以实现合作式多任务(非抢占式多任务)的一类计算机程序组件。 在这个定义中,有必要廓清一下“子例程”,subroutine的概念,同样来自维基百科定义,子例程是计算机编程中用于实现一组特定工作的编程指令。与整个程序相比,它有比拟显著的独立性和内聚性,函数,过程,办法都能够是一类子例程,更相熟的名词是子程序,一种高级语言中的概括性术语。 通过伪代码定义一个简略的协程,如下所示: # producer coroutineloopwhile queue is not full create some new items add the items to queueyield to consumer # consumer coroutineloopwhile queue is not empty remove some items from queue use the itemsyield to producer两个协程,生产者和消费者,生产者产生商品放入队列,而后通过“yield”告诉消费者。消费者从队列中拿出商品并耗费,再“yield”生产者。就这样两组程序互相合作,在失当的机会让出CPU的代码执行权,默契敌对。 那在Go语言中,又是如何利用协程的呢?Go中协程术语为Goroutine,应用关键字“go”来启动。Go by Example的例子如下: package main import ( "fmt" "time") func f(from string) { for i := 0; i < 3; i++ { fmt.Println(from, ":", i) }} func main() { f("direct") go f("goroutine") go func(msg string) { fmt.Println(msg) }("going") time.Sleep(time.Second) fmt.Println("done")}和过程,线程的比照,以及纤程咱们比拟相熟的是过程和线程的概念,协程与之有什么区别呢?首先回顾一下: ...

April 13, 2022 · 1 min · jiezi

关于golang:go入门到跑路

GO入门到跑路

April 13, 2022 · 1 min · jiezi

关于golang:B树原理以及Go语言实现

B+树介绍B+树是B树的一种变种,因而若想理解B+树,首先要理解B树的定义。B树又称多路均衡查找树,B树中所有结点的孩子个数的最大值称为B树的阶,通常用M示意。个别从查找效率思考,通常要求M>=3。一棵M阶B树,有如下个性: 若根节点不是叶子结点,则至多有两棵树。每一个节点最多M棵子树,最多有M-1个关键字。除根节点外,其余的每个分支至多有ceil(M/2)个子树,至多含有ceil(M/2)-1个关键字。每个节点中的关键字都依照大小顺序排列,每个关键字的左子树的所有关键字都小于它,每个关键字的右子树都大于它。所有叶子节点都位于同一层,或者说根节点到每个叶子节点的长度都雷同。下图是一个B树的例子: 为了适应磁盘IO拜访的特点以及适应范畴查问的需要,B+树对B树进行了改良。对于一棵m阶的B+树,有如下个性: 每个节点至少有M个子树。除根结点外,每个结点至多有ceil(M/2)个子树。结点的子树个数于关键字个数相等。所有的叶子结点中蕴含了全副关键字的信息,及指向含这些关键字记录的指针,且叶子结点自身依关键字的大小自小而大程序链接。所有的非终端结点(非叶子结点)能够看成是索引局部,结点中仅含有其子树(根结点)中的最大(或最小)关键字。下图是一个B+树的例子:B+树和B树相比,次要有以下区别: 非叶子节点只存储键值信息,数据记录都寄存在叶子节点中。所有叶子节点之间都有一个链指针。非叶子节点的关键字的个数与其子树的个数雷同,不像B树,子树的个数总比关键字个数多1个。B+树通常用于数据库索引,例如Mysql的InnoDB存储引擎以及MyISAM存储引擎的索引文件中应用的就是B+树。一般来说,数据库的索引都比拟大,不可能全副存储在内存中,因而索引往往以文件的模式存储的磁盘上。零碎从磁盘读取文件到内存时,须要定位到文件所在的地位:文件所在柱面号,磁盘号,扇区号。这个操作时十分耗时的,远高于内存操作。思考到磁盘IO是十分昂扬的操作,操作系统在读取文件时做了一些优化,零碎从磁盘读取文件到内存时是以磁盘块(block)为根本单位的,位于同一个磁盘块中的数据会被一次性读取进去,而不是须要什么取什么。每一次IO读取的数据咱们称之为一页(page)。具体一页有多大数据跟操作系统无关,个别为4k或8k。 因为磁盘IO十分耗时,因而评估一个数据结构作为索引的优劣最重要的指标就是在查找过程中是否可能无效缩小磁盘I/O的操作次数。Mysql抉择应用B+树作为索引文件的数据结构,次要基于B+树的以下特点: B+树的磁盘读写代价更低< B+树的外部结点只有关键字,没有数据,一个结点能够包容更多的关键字。查问时一次性读入内存中的关键字也就越多,相对来说I/O读写次数也就升高了。B+树查问效率更加稳固< B+树外部结点不保留数据,而只是叶子结点中数据的索引。所以任何关键字的查找必须走一条从根结点到叶子结点的路。所有关键字查问的门路长度雷同,导致每一个数据的查问效率相当。B+树便于范畴查问< 所有叶子节点造成有序链表,对于数据库中频繁应用的范畴查问,B+树有着更高的性能。。在InnoDB中,表数据文件自身就是按B+树组织的一个索引构造,它应用数据库主键作为Key,叶节点保留了残缺的数据记录。InnoDB中有页(Page)的概念,页是其磁盘治理的最小单位。InnoDB中默认每个页的大小为16KB,可通过参数innodb_page_size将页的大小设置为4K、8K、16K。InnoDB中,B+Tree的高度个别都在2~4层。因为根节点常驻内存的,因而查找某一键值的行记录时最多只须要1~3次磁盘I/O操作。因为InnoDB的数据文件自身要按主键汇集,所以InnoDB要求表必须有主键(MyISAM能够没有),如果没有显式指定,则MySQL零碎会主动抉择一个能够惟一标识数据记录的列作为主键,如果不存在这种列,则MySQL主动为InnoDB表生成一个隐含字段作为主键,这个字段长度为6个字节,类型为长整形。汇集索引这种实现形式使得按主键的搜寻非常高效,然而辅助索引搜寻须要检索两遍索引:首先检索辅助索引取得主键,而后用主键到主索引中检索取得记录。 B+树中插入数据在B+树中插入数据时,须要留神以下几点: 插入数据时首先定位到数据所在的叶子结点,而后将数据插入到该结点,插入数据后不能毁坏关键字自小而大的排列程序。若插入元素后该节点关键字数目<=阶数M,则间接实现插入操作。若插入的元素为该节点的最大值,则须要批改其父结点中的索引值。若插入元素后该节点关键字数目>阶数M,则须要将该结点决裂为两个结点,关键字的个数别离为:floor((M+1)/2)和ceil((M+1)/2)。若决裂结点后导致父节点的关键字数目>阶数M,则父节点也要进行相应的决裂操作。数据插入阐明: 若被插入关键字所在的结点,其含有关键字数目小于阶数M,则直接插入完结。下图是插入关键字:15后的后果: 若被插入关键字所在的结点,其含有关键字数目等于阶数M,则须要将该结点决裂为两个结点。上图中插入关键字:9后结点的关键字数量为:4,超过了B+树阶数M,因而须要进行结点决裂操作,决裂后的后果为: B+树中删除数据在 B+树中做删除关键字的操作,采取如下的步骤: 当删除某结点中最大或者最小的关键字,就会波及到更改其双亲结点始终到根结点中所有索引值的更改。删除关键字后,如果若以后结点中关键字个数小于>=[M/2],则间接实现删除操作。在删除关键字后,如果导致其结点中关键字个数<[M/2],若其兄弟结点中含有多余的关键字,能够从兄弟结点中借关键字。在删除关键字后,如果导致其结点中关键字个数<[M/2],并且其兄弟结点没有多余的关键字,则须要同其兄弟结点进行合并。结点合并后,须要批改父结点的关键字的个数,若父结点的关键字个数<[M/2],须要按照以上法则进行解决。数据删除阐明: 删除关键字后,如果若以后结点中关键字个数小于>=[M/2],则间接实现删除操作。上图中删除关键字:8,删除后的后果如下:在删除关键字后,如果导致其结点中关键字个数<[M/2],若其兄弟结点中含有多余的关键字,能够从兄弟结点中借关键字。上图中删除关键字:21,因为删除后结点只有一个关键字:25<[M/2],因而须要从兄弟结点中借用一个关键字:17,删除后的后果如下: 在删除关键字后,如果导致其结点中关键字个数<[M/2],并且其兄弟结点没有多余的关键字,则须要同其兄弟结点进行合并。上图中删除关键字:9,因为删除后结点只有一个关键字:11<[M/2],并且兄弟结点也没有多余的关键字,因而须要与兄弟结点进行合并,删除后的后果如下:Go语言代码接下来咱们给出B+树的Go语言的实现,目前代码曾经上传到github中,下载地址 结点的定义首先给出B+树结点的定义,在此叶子结点与索引结点应用了雷同的数据结构: type BPItem struct { Key int64 Val interface{}}type BPNode struct { MaxKey int64 Nodes []*BPNode Items []BPItem Next *BPNode}其中: BPItem用于数据记录。MaxKey:用于存储子树的最大关键字Nodes:结点的子树(叶子结点的Nodes=nil)Items:叶子结点的数据记录(索引结点的Items=nil)Next:叶子结点中指向下一个叶子结点,用于实现叶子结点链表B+树的定义type BPTree struct { mutex sync.RWMutex root *BPNode width int halfw int其中: root:指向B+树的根结点width:用于示意B+树的阶halfw:用于[M/2]=ceil(M/2)B+树的初始化func NewBPTree(width int) *BPTree { if width < 3 { width = 3 } var bt = &BPTree{} bt.root = NewLeafNode(width) bt.width = width bt.halfw = (bt.width + 1) / 2 return bt}//申请width+1是因为插入时可能临时呈现节点key大于申请width的状况,待前期再决裂解决func NewLeafNode(width int) *BPNode { var node = &BPNode{} node.Items = make([]BPItem, width+1) node.Items = node.Items[0:0] return node}B+树的查问func (t *BPTree) Get(key int64) interface{} { t.mutex.Lock() defer t.mutex.Unlock() node := t.root for i := 0; i < len(node.Nodes); i++ { if key <= node.Nodes[i].MaxKey { node = node.Nodes[i] i = 0 } } //没有达到叶子结点 if len(node.Nodes) > 0 { return nil } for i := 0; i < len(node.Items); i++ { if node.Items[i].Key == key { return node.Items[i].Val } } return nil}B+树的插入操作func (t *BPTree) Set(key int64, value interface{}) { t.mutex.Lock() defer t.mutex.Unlock() t.setValue(nil, t.root, key, value)}func (t *BPTree) setValue(parent *BPNode, node *BPNode, key int64, value interface{}) { for i:=0; i < len(node.Nodes); i++ { if key <= node.Nodes[i].MaxKey || i== len(node.Nodes)-1 { t.setValue(node, node.Nodes[i], key, value) break } } //叶子结点,增加数据 if len(node.Nodes) < 1 { node.setValue(key, value) } //结点决裂 node_new := t.splitNode(node) if node_new != nil { //若父结点不存在,则创立一个父节点 if parent == nil { parent = NewIndexNode(t.width) parent.addChild(node) t.root = parent } //增加结点到父亲结点 parent.addChild(node_new) }}代码中应用递归调用实现数据的插入。插入时首先定位到叶子结点,而后调用 BPNode.setValue()来设置关键字: ...

April 13, 2022 · 4 min · jiezi

关于golang:Go源码学习map

1. 前言map是CS中十分根底的数据结构,对于golang map的根本应用,这里不再赘述,能够参考官网文档。golang的map实现是基于hash查找表,并且基于链表来解决hash碰撞问题。 2. 环境信息go版本:go1.15.4 darwin/amd643. go map数据结构剖析map的根底构造体是hmap,该构造体存在文件runtime/map.go中hmap源码: // A header for a Go map.type hmap struct { // Note: the format of the hmap is also encoded in cmd/compile/internal/gc/reflect.go. // Make sure this stays in sync with the compiler's definition. count int // # live cells == size of map. Must be first (used by len() builtin) flags uint8 B uint8 // log_2 of # of buckets (can hold up to loadFactor * 2^B items) noverflow uint16 // approximate number of overflow buckets; see incrnoverflow for details hash0 uint32 // hash seed buckets unsafe.Pointer // array of 2^B Buckets. may be nil if count==0. oldbuckets unsafe.Pointer // previous bucket array of half the size, non-nil only when growing nevacuate uintptr // progress counter for evacuation (buckets less than this have been evacuated) extra *mapextra // optional fields}// mapextra holds fields that are not present on all maps.type mapextra struct { // If both key and elem do not contain pointers and are inline, then we mark bucket // type as containing no pointers. This avoids scanning such maps. // However, bmap.overflow is a pointer. In order to keep overflow buckets // alive, we store pointers to all overflow buckets in hmap.extra.overflow and hmap.extra.oldoverflow. // overflow and oldoverflow are only used if key and elem do not contain pointers. // overflow contains overflow buckets for hmap.buckets. // oldoverflow contains overflow buckets for hmap.oldbuckets. // The indirection allows to store a pointer to the slice in hiter. overflow *[]*bmap oldoverflow *[]*bmap // nextOverflow holds a pointer to a free overflow bucket. nextOverflow *bmap}count:map中kv对的数量;flags:map的一些标记位;B:map中bucket数量为2^B个;意味着此时map数据结构中能够存储loadFactor * 2^B个数据,如果超过,则须要扩容;todonoverflow:map中溢出bucket的近似数量;todohash0:hash函数的种子;buckets:map中bucket的首指针,map中一共有2^B个bucket;如果count==0的状况下,该字段可能是nil;oldbuckets:map中旧bucket的首指针,该字段只有在map扩容的时候,才不等于nil;todonevacuate:map中bucket迁徙数量,至少有此数量的bucket从旧bucket迁徙到新bucket;todoextra:扩大字段;bmap是bucket真正的构造体 ...

April 13, 2022 · 21 min · jiezi

关于golang:七天实现web框架路由映射

最近在学习一个七天系列课程,而后在这里对本人学习到的货色进行一个总结。明天实现的是web框架中的路由映射。 Http-Net包在实现这个路由解析器的时候,咱们首先要明确go本来的http/net包是如何实现路由解析定向的。 其中重要的函数 // HandleFunc registers the handler function for the given pattern// in the DefaultServeMux.// The documentation for ServeMux explains how patterns are matched.func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) { DefaultServeMux.HandleFunc(pattern, handler)}这里的函数参数,第一个是门路的函数,第二个是处理函数handle,这个函数最初调用的其实就是 // The HandlerFunc type is an adapter to allow the use of// ordinary functions as HTTP handlers. If f is a function// with the appropriate signature, HandlerFunc(f) is a// Handler that calls f.type HandlerFunc func(ResponseWriter, *Request)// ServeHTTP calls f(w, r).func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) { f(w, r)}也就是说实现了handler的接口,那么就有了解决申请的能力。那么咱们来看实现handler这个接口的条件是什么。这里浮浅的解释一下这个go接口的实现,在go语言中是反对鸭子类型的,所以咱们只有实现了接口外面的办法,那么就实现了这个接口。handler接口: ...

April 12, 2022 · 2 min · jiezi

关于golang:go语言学习函数方法与接口

概述function,函数function,函数,是一个能够被其余代码或其本身调用的代码片段。当函数被调用时,参数被作为输出传递给函数,并且函数能够返回输入。 函数能够让咱们将一个语句序列打包为一个单元,而后能够从程序中其它中央屡次调用。函数的机制能够让咱们将一个大的工作合成为小的工作,这样的小工作能够让不同程序员在不同工夫、不同中央独立实现。一个函数同时对用户暗藏了其实现细节。因为这些因素,对于任何编程语言来说,函数都是一个至关重要的局部。在不同的编程语言中,函数的类型有不同的定义。例如,在 JavaScript 中,函数也是一个对象。而在golang之中,函数是一个被独自定义的类型。函数被看作第一类值(first-class values):函数像其余值一样,领有类型,能够被赋值给其余变量,传递给函数,从函数返回。 第一类值的个性如下 能够被存入变量或其余构造能够被作为参数传递给其余函数能够被作为函数的返回值能够在执行期发明,而无需齐全在设计期全副写出即便没有被系结至某一名称,也能够存在与之相比拟的是,例如在C语言与C++中的函数不是第一类对象,因为在这些语言中函数不能在执行期发明,而必须在设计时全副写好。 函数能够分为具名函数和匿名函数,具名函数个别对应于包级的函数,是匿名函数的一种特例,当匿名函数援用了内部作用域中的变量时就成了闭包函数,闭包函数是函数式编程语言的外围。 办法,method在Go语言中,构造体就像是类的一种简化模式,那么类的办法在哪里呢?在Go语言中有一个概念,它和办法有着同样的名字,并且大体上意思雷同,go中的办法个别指的是静态方法,就是一个蕴含了接受者(receiver)的函数,receiver能够是内置类型或者构造体类型的一个值或者是一个指针。 接收器类型能够是(简直)任何类型,不仅仅是构造体类型,任何类型都能够有办法,甚至能够是函数类型,能够是 int、bool、string 或数组的别名类型,然而接收器不能是一个接口类型,因为接口是一个形象定义,而办法却是具体实现,如果这样做了就会引发一个编译谬误 从90年代晚期开始,面向对象编程(OOP)就成为了称霸工程界和教育界的编程范式,所以之后简直所有大规模被利用的语言都蕴含了对OOP的反对,go语言也不例外。援用只管没有被公众所承受的明确的OOP的定义,从咱们的了解来讲,一个对象其实也就是一个简略的值或者一个变量,在这个对象中会蕴含一些办法,而一个办法则是一个一个和非凡类型关联的函数。一个面向对象的程序会用办法来表白其属性和对应的操作,这样应用这个对象的用户就不须要间接去操作对象,而是借助办法来做这些事件。办法是绑定到一个具体类型的非凡函数,Go语言中的办法是依靠于类型的,必须在编译时动态绑定。 接口Go 语言提供了另外一种数据类型即接口,它把所有的具备共性的办法定义在一起,任何其余类型只有实现了这些办法就是实现了这个接口。 接口定义了办法的汇合,这些办法依靠于运行时的接口对象,因而接口对应的办法是在运行时动静绑定的。Go语言通过隐式接口机制实现了鸭子面向对象模型。 “当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就能够被称为鸭子。”go 鸭子类型的疑难? - DJun的答复 - 知乎https://www.zhihu.com/questio... go程序的执行程序Go语言程序的初始化和执行总是从main.main函数开始的。然而如果main包导入了其它的包,则会依照程序将它们蕴含进main包里(这里的导入程序依赖具体实现,个别可能是以文件名或包路径名的字符串程序导入)。如果某个包被屡次导入的话,在执行的时候只会导入一次。当一个包被导入时,如果它还导入了其它的包,则先将其它的包蕴含进来,而后创立和初始化这个包的常量和变量,再调用包里的init函数,如果一个包有多个init函数的话,调用程序未定义(实现可能是以文件名的顺序调用),同一个文件内的多个init则是以呈现的程序顺次调用(init不是一般函数,能够定义有多个,所以也不能被其它函数调用)。最初,当main包的所有包级常量、变量被创立和初始化实现,并且init函数被执行后,才会进入main.main函数,程序开始失常执行。下图是Go程序函数启动程序的示意图: 要留神的是,在main.main函数执行之前所有代码都运行在同一个goroutine,也就是程序的主零碎线程中。因而,如果某个init函数外部用go关键字启动了新的goroutine的话,新的goroutine只有在进入main.main函数之后才可能被执行到。 函数申明func name(parameter-list) (result-list) { body}参数在函数体中,函数的形参作为局部变量,被初始化为调用者提供的值。函数的形参和有名返回值作为函数最外层的局部变量,被存储在雷同的词法块中。 实参通过值的形式传递,因而函数的形参是实参的拷贝。对形参进行批改不会影响实参。然而,如果实参包含援用类型,如指针,slice(切片)、map、function、channel等类型,实参可能会因为函数的间接援用被批改。 变长参数和变长参数函数类型一个函数的最初一个参数能够是一个变长参数。一个函数能够最多有一个变长参数。一个变长参数的类型总为一个切片类型。 变长参数在申明的时候必须在它的(切片)类型的元素类型后面前置三个点...,以示这是一个变长参数。 变长函数申明和一般函数申明相似,只不过最初一个参数必须为变长参数。 一个变长参数在函数体内将被视为一个切片。 // Sum返回所有输出实参的和。func Sum(values ...int64) (sum int64) { // values的类型为[]int64。 sum = 0 for _, v := range values { sum += v } return}// Concat是一个低效的字符串拼接函数。func Concat(sep string, tokens ...string) string { // tokens的类型为[]string。 r := "" for i, t := range tokens { if i != 0 { r += sep } r += t } return r}函数返回多个值Go 函数能够返回多个值,例如: ...

April 11, 2022 · 1 min · jiezi

关于golang:gomod依赖问题

问题当用go来应用第三方依赖时的问题:咱们去获取一下mysql的依赖go get github.com/go-sql-driver/mysql报错:依据报错,看起来仿佛应该删除go.mod文件然而当你真的删除后,再去执行go get仍然会报错蒙蔽如我... 解决首先,go.mod文件还是必须的,咱们应用 go mod init来初始化go.mod文件. 要害而后输出go mod tidy命令.这条指令会让go主动的去把go我的项目中须要的第三方以来治理起来,主动引入新增的依赖,主动删除未应用的依赖.接下来,持续应用go get就能够找寻到任何你须要的依赖包了

April 11, 2022 · 1 min · jiezi

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

从本篇文章开始,咱们用一个系列来解说从需要到上线、从代码到k8s部署、从日志到监控等各个方面的微服务残缺实际。 实战我的项目地址:https://github.com/Mikaelemmm... 一、我的项目简介整个我的项目应用了go-zero开发的微服务,根本蕴含了go-zero以及相干go-zero作者开发的一些中间件,所用到的技术栈根本是go-zero项目组的自研组件,根本是go-zero全家桶了。 我的项目目录构造如下: app:所有业务代码蕴含api、rpc以及mq(音讯队列、提早队列、定时工作)common:通用组件 error、middleware、interceptor、tool、ctxdata等data:该我的项目蕴含该目录依赖所有中间件(mysql、es、redis、grafana等)产生的数据,此目录下的所有内容应该在git疏忽文件中,不须要提交。deploy: filebeat: docker部署filebeat配置go-stash:go-stash配置nginx: nginx网关配置prometheus : prometheus配置script: gencode:生成api、rpc,以及创立kafka语句,复制粘贴应用mysql:生成model的sh工具goctl: 该我的项目goctl的template,goctl生成自定义代码模版,tempalte用法可参考go-zero文档,复制到家目录下.goctl即可,该我的项目用到goctl版本是v1.3.0doc : 该我的项目系列文档二、用到技术栈go-zeronginx网关filebeatkafkago-stashelasticsearchkibanaprometheusgrafanajaegergo-queueasynqasynqmondtmdockerdocker-composemysqlredis三、我的项目架构图 四、业务架构图 五、我的项目环境搭建本我的项目采纳air热加载功即时批改代码及时失效,并且不须要每次都要重启,改了代码主动就在容器中从新加载了,本地不须要启动服务,本地装置的sdk就是写代码主动提醒应用的,理论运行是以来容器中cosmtrek/air的golang环境。所以应用goland、vscode都一样 1、clone代码&更新依赖$ git clone git@github.com:Mikaelemmmm/go-zero-looklook.git$ go mod tidy2、启动我的项目所依赖的环境$ docker-compose -f docker-compose-env.yml up -djaeger: http://127.0.0.1:16686/search asynq (延时、定时音讯队列): http://127.0.0.1:8980/ kibana: http://127.0.0.1:5601/ Elastic search: http://127.0.0.1:9200/ Prometheus: http://127.0.0.1:9090/ Grafana: http://127.0.0.1:3001/ Mysql: 自行客户端工具(Navicat、Sequel Pro)查看 host : 127.0.0.1port : 33069username : rootpwd : PXDN93VRKUm8TeE7Redis: 自行工具(redisManager)查看 host : 127.0.0.1port : 63799pwd : G62m50oigInC30sfKafka: 自行客户端工具查看 host : 127.0.0.1port : 90923、拉取我的项目依赖镜像因为本我的项目是用air热加载的,所以是在air+golang镜像中运行,间接docker-compose也能够,然而思考依赖可能会比拟大,会影响启动我的项目,所以最好先把这个镜像拉取下来再去启动我的项目,拉取air+golang我的项目依赖的镜像命令如下 $ docker pull cosmtrek/air:latest4、导入mysql数据创立数据库looklook_order && 导入deploy/sql/looklook_order.sql数据 ...

April 11, 2022 · 2 min · jiezi

关于golang:深度剖析分布式事务之-AT-与-XA-对比

AT 这种事务模式是阿里开源的seata主推的事务模式,本文会详解AT的原理,并将它与XA模式进行比拟 原理AT 从原理下面看,与 XA 的设计有很多相近之处。XA 是数据库层面实现的二阶段提交, AT 则是利用/驱动层实现的二阶段提交。建议您理解了XA相干的常识后,来浏览这篇文章,这样可能更快更好的把握 AT 的原理与设计。 AT的角色和XA一样分为3个,然而起了不一样的名称,大家留神分辨: RM 资源管理器,是业务服务,负责本地数据库的治理,与XA中的RM统一TC 事务协调器,是Seata服务器,负责全局事务的状态治理,负责协调各个事务分支的执行,相当于XA中的TMTM 事务管理器,是业务服务,负责全局事务的发动,相当于XA中的APPAT 的第一阶段为prepare,它在这一阶段会实现以下事件: RM 侧,用户开启本地事务RM 侧,用户每进行一次业务数据批改,假如是一个update语句,那么 AT 会做以下内容: 依据update的条件,查问出批改前的数据,该数据称为BeforeImage执行update语句,依据BeforeImage中的主键,查问出批改后的数据,该数据称为AfterImage将BeforeImage和AfterImage保留到一张undolog表将BeforeImage中的主键以及表名,该数据称为lockKey,记录下来,留待后续应用RM 侧,用户提交本地事务时,AT 会做以下内容: 将2.4中记录的所有的lockKey,注册到 TC(即事务管理器seata)上3.1中的注册解决会查看 TC 中,是否已存在抵触的主键+表名,如果有抵触,那么AT会睡眠期待后重试,没有抵触则保留3.1胜利实现后,提交本地事务如果 AT 的第一阶段所有分支都没有谬误,那么会进行第二阶段的commit,AT 会做以下内容: TC 会将以后这个全局事务所有相干的lockKey删除TC 告诉与以后这个全局事务相干的所有业务服务,告知全局事务已胜利,能够删除undolog中保留的数据RM 收到告诉后,删除undolog中的数据如果 AT 的第一阶段有分支出错,那么会进行第二阶段的rollback,AT 会做以下内容: TC 告诉与以后这个全局事务相干的所有业务服务,告知全局事务失败,执行回滚RM 收到告诉后,对本地数据的批改进行回滚,回滚原理如下: 从undolog中取出批改前后的BeforeImage和AfterImage如果AfterImage与数据库中的以后记录校验统一,那么应用BeforeImage中的数据笼罩以后记录如果AfterImage与数据库中的以后记录不统一,那么这个时候产生了脏回滚,此时须要人工染指解决TC 待全局事务所有的分支,都实现了回滚,TC 将此全局事务所有的lockKey删除问题剖析AT 模式的一个突出问题是rollback中2.3的脏回滚难以避免。以下步骤可能触发该脏回滚: 全局事务g1对数据行A1进行批改 v1 -> v2另一个服务将对数据行A1进行批改 v2 -> v3全局事务g1回滚,发现数据行A1的以后数据为v3,不等于AfterImage中的v2,回滚失败这个脏回滚一旦产生,那么分布式事务框架没有方法保证数据的一致性了,必须要人工染指解决。想要防止脏回滚,须要把所有对这个表的写访问,都加上非凡解决(在Seata的Java客户端中,须要加上GlobalLock注解)。这种束缚对于一个上了肯定规模的简单零碎,是十分难以保障的。 AT vs XA上述脏回滚问题,在 XA 事务中不会呈现,因为 XA 事务是在数据库层面实现的,当另一个服务对为数据行A1进行批改时,会因为行锁被阻塞,与一般事务的体现齐全一样,不会产生问题。 另外 XA 不会产生脏读,而 AT 会产生脏读,思考AT下的如下执行步骤: 全局事务g1对数据行A1进行批改 v1 -> v2另一个服务将读取数据行A1,取得数据 v2全局事务g1回滚,将数据行A1改回 v2 -> v1这外面步骤2读取的数据是v2,是一个两头态数据。在Seata的手册中,尽管也有一些办法可能防止AT模式下,然而波及到注解和sql改写,并不优雅。而在XA模式下,因为还没有进行xa commit,那么步骤2依据MVCC读取到的数据仍然是v1,没有AT模式中的脏读的困扰。 ...

April 11, 2022 · 1 min · jiezi

关于golang:Excelize-发布-260-版本功能强大的-Excel-文档基础库

Excelize 是 Go 语言编写的用于操作 Office Excel 文档根底库,基于 ECMA-376,ISO/IEC 29500 国际标准。能够应用它来读取、写入由 Microsoft Excel™ 2007 及以上版本创立的电子表格文档。反对 XLAM / XLSM / XLSX / XLTM / XLTX 等多种文档格局,高度兼容带有款式、图片(表)、透视表、切片器等简单组件的文档,并提供流式读写 API,用于解决蕴含大规模数据的工作簿。可利用于各类报表平台、云计算、边缘计算等零碎。入选 2020 Gopher China - Go 畛域明星开源我的项目(GSP)、2018 年开源中国码云最有价值开源我的项目 GVP (Gitee Most Valuable Project),目前已成为 Go 语言最受欢迎的 Excel 文档根底库。 开源代码GitHub: github.com/xuri/excelize Gitee: gitee.com/xurime/excelize 中文文档: xuri.me/excelize/zh-hans 2022年4月11日,社区正式公布了 2.6.0 版本,该版本蕴含了多项新增性能、谬误修复和兼容性晋升优化。上面是无关该版本更新内容的摘要,残缺的更改列表可查看 changelog。 此版本中最显著的变动包含: 兼容性提醒重命名导出常量 NameSpaceDublinCoreMetadataIntiative 为 NameSpaceDublinCoreMetadataInitiative 以修复拼写错误重命名导出变量 ErrUnsupportEncryptMechanism 为 ErrUnsupportedEncryptMechanism重命名导出变量 ErrDataValidationFormulaLenth 为 ErrDataValidationFormulaLength重命名导出变量 ErrDefinedNameduplicate 为 ErrDefinedNameDuplicate移除了导出变量 XMLHeaderByte移除了设置数据数据验证列表函数 SetSqrefDropList 的第二个形参 isCurrentSheet 和异样返回值移除了行迭代器中的导出字段 TotalRows新增性能ProtectSheet 新增反对通过指定的算法爱护工作表,反对的算法包含: XOR、MD4、MD5、SHA1、SHA256、SHA384 和 SHA512UnprotectSheet 反对通过指定第二个可选参数在移除工作表爱护时验证明码新增 71 项公式函数: AVERAGEIFS, BETADIST, BETA.DIST, BETAINV, BETA.INV, BINOMDIST, BINOM.DIST, BINOM.DIST.RANGE, BINOM.INV, CHIINV, CHITEST, CHISQ.DIST, CHISQ.DIST.RT, CHISQ.INV, CHISQ.INV.RT, CHISQ.TEST, CONFIDENCE.T, CORREL, COVAR, COVARIANCE.P, CRITBINOM, ERROR.TYPE, EXPON.DIST, EXPONDIST, F.DIST, F.DIST.RT, FDIST, F.INV, F.INV.RT, FINV, FORMULATEXT, F.TEST, FTEST, GAMMA.DIST, GAMMADIST, GAMMA.INV, GAMMAINV, GAMMALN.PRECISE, GAUSS, HOUR, HYPGEOM.DIST, HYPGEOMDIST, INDIRECT, LOGINV, LOGNORM.DIST, LOGNORMDIST, LOGNORM.INV, MODE, MODE.MULT, MODE.SNGL, NEGBINOM.DIST, NEGBINOMDIST, PHI, SECOND, SERIESSUM, SUMIFS, SUMPRODUCT, SUMX2MY2, SUMX2PY2, SUMXMY2, T.DIST, T.DIST.2T, T.DIST.RT, TDIST, TIMEVALUE, T.INV, T.INV.2T, TINV, T.TEST, TTEST, TYPE保留或另存为工作簿时减少对文件扩展名进行查看反对设置工作簿视图模式和显示/暗藏标尺引入依赖库 NFP (number format parser) 以减少对自定义工夫、日期和文本类型数字格局的反对,可对蕴含 19 种语言(南非荷兰语、孟加拉语、汉语、英语、法语、德语、奥地利语、爱尔兰语、意大利语、俄语、西班牙语、泰语、藏语、土耳其语、威尔士语、沃洛夫语、科萨语、彝语和祖鲁语)本地月份名称和 12 小时制格局的数字格局表达式进行解析,相干 issues #660, #764, #1093, #1112 和 #1133新增 API: SetWorkbookPrOptions 和 GetWorkbookPrOptions 反对设置和获取工作簿中的 FilterPrivacy 与 CodeName 属性,以解除局部状况下向工作簿中嵌入 VBA 工程时的限度,相干 issue #1148公式计算引擎反对中断运算符后蕴含无参数公式函数的计算反对以文本模式读取布尔型单元格的值通过 AddChart 函数增加圆环图时,反对指定圆环图内径大小,解决 issue #1172新增导出 4 项错误信息 ErrPasswordLengthInvalid, ErrUnsupportedHashAlgorithm, ErrUnsupportedNumberFormat, ErrWorkbookExt,以便开发者可依据不同的谬误类型进行采取相应解决兼容性晋升晋升与 LibreOffice 电子表格应用程序的兼容性,修复在 LibreOffice 中关上的工作表名蕴含空格时,主动过滤器生效的问题,解决 issue #1122晋升对工作簿中代替内容的反对,保留工作簿、工作表以及 drawingML 中的代替内容晋升与页面设置中打印质量 DPI 设置属性的兼容性问题修复修复另存为工作簿时,页面布局属性失落的问题,解决 issue #1117修复局部状况下,对工作表进行批改后合并单元格区域未更新的问题修复款式解析异样导致的粗体和局部其余字体款式失落问题,解决 issue #1119修复局部状况下将文档保留为 XLAM / XLSM / XLTM / XLTX 格局后文档损坏的问题单元格款式反对继承行/列款式,以修复对工作表进行批改后合并单元格区域单元格款式不正确的问题,解决 issue #1129修复局部状况下获取单元格款式 ID 谬误的问题修复编号为 42 的内建数字格局定义谬误的问题修复局部状况下数字精度解析谬误的问题SetCellDefault 反对设置非数字类型单元格的值,解决 issue #1139修复局部状况下另存为工作簿时,显示或暗藏工作表标签属性失落的问题,解决 issue #1160修复局部状况下嵌套公式计算错误的问题,解决 issue #1164修复局部状况下公式计算结果精度不精确以及在 x86 和 arm64 架构 CPU 下公式计算结果精度不统一的问题修复局部状况下应用迷信记数法示意的数值解析失败的问题修复图表轴最大值最小值为 0 时不起作用的问题性能优化进步应用行迭代器进行流式读取的性能,当读取蕴含大规模数据的电子表格文档时,内存开销相较于上一版本升高最高约 50%,内存垃圾回收次数升高约 80%其余Go Modules 依赖模块更新单元测试与文档更新蕴含简体中文、英语、法语、俄语、日语、韩语、阿拉伯语、德语和西班牙语的多国语言文档网站更新

April 11, 2022 · 2 min · jiezi

关于golang:Go进阶基础特性panic-和-recover

panic 和 recover 也是罕用的关键字,这两个关键字与上一篇提到的 defer 分割很严密。用一句话总结就是:调用 panic 后会立即进行执行以后函数的残余代码,并在以后 Goroutine 中递归执行调用方的 defer;而 recover 能够停止 panic 造成的程序解体,不过它只能在 defer 中发挥作用。 panicpanic 是一个内置函数,承受一个任意类型的参数,参数将在程序解体时打印进去,如果被 recover 复原的话,该参数也是 recover 的返回值。panic 能够由程序员显式触发,运行时遇到意料之外的谬误如内存越界时也会触发。 在上一篇中咱们晓得每个 Goroutine 都保护了一个 _defer 链表(非凋谢编码状况下),执行过程中每遇到一个 defer 关键字都会创立一个 _defer 实例插入链表,函数退出时一次取出这些 _defer 实例并执行。panic 产生时,实际上是触发了函数退出,也即把执行流程转向了 _defer 链表。 panic 的执行过程中有几点须要明确: panic 会递归执行以后 Goroutine 中所有的 defer,解决实现后退出;panic 不会解决其余 Goroutine 中的 defer;panic 容许在 defer 中屡次调用,程序会终止以后 defer 的执行,持续之前的流程。数据结构panic 关键字在 Go 语言中是由数据结构 runtime._panic 示意的。每当咱们调用 panic 都会创立一个如下所示的数据结构: type _panic struct { argp unsafe.Pointer arg interface{} link *_panic recovered bool aborted bool goexit bool}argp 是指向 defer 函数参数的指针;arg 是调用 panic 时传入的参数;link 指向前一个_panic 构造;recovered 示意以后 _panic 是否被 recover 复原;aborted 示意以后的 _panic 是否被终止;goexit 示意以后 _panic 是否是由 runtime.Goexit 产生的。_panic 链表与 _defer 链表一样,都是保留在 Goroutine 的数据结构中: ...

April 10, 2022 · 3 min · jiezi

关于golang:go语言学习匿名函数

什么是匿名函数匿名函数是指不须要定义函数名的一种函数实现形式,由一个不带函数名的函数申明和函数体组成。 匿名函数的定义格局如下: func(参数列表)(返回参数列表){ 函数体}除了没有名字之外,它与一般的函数申明没有什么区别 一些性质匿名函数能够在申明后调用func(data int) { fmt.Println("hello", data)}(100)函数能够作为一种类型被赋值给函数类型的变量// 将匿名函数体保留到f()中f := func(data int) { fmt.Println("hello", data)}// 应用f()调用f(100)领有函数名的函数只能在包级语法块中被申明。 如果将匿名函数赋给一个全局变量,那么这个匿名函数则能够被全局应用。 匿名函数能够看成一个独立的内存空间,即闭包例如 // squares返回一个匿名函数。// 该匿名函数每次被调用时都会返回下一个数的平方。func squares() func() int { var x int return func() int { x++ return x * x }}func main() { f := squares() fmt.Println(f()) // "1" fmt.Println(f()) // "4" fmt.Println(f()) // "9" fmt.Println(f()) // "16"}函数squares返回另一个类型为 func() int 的函数。对squares的一次调用会生成一个局部变量x并返回一个匿名函数。每次调用匿名函数时,该函数都会先使x的值加1,再返回x的平方。第二次调用squares时,会生成第二个x变量,并返回一个新的匿名函数。新匿名函数操作的是第二个x变量。 squares的例子证实,函数值不仅仅是一串代码,还记录了状态。在squares中定义的匿名外部函数能够拜访和更新squares中的局部变量,这意味着匿名函数和squares中,存在变量援用。这就是函数值属于援用类型和函数值不可比拟的起因。Go应用闭包(closures)技术实现函数值,Go程序员也把函数值叫做闭包。 这里引入一下闭包函数的定义: 闭包就是可能读取其余函数外部变量的函数。在实质上,闭包是将函数外部和函数内部连接起来的桥梁。闭包的具体作用能够参考这篇博客https://www.runoob.com/w3cnot... 通过这个例子,咱们看到变量的生命周期不禁它的作用域决定:squares返回后,变量x依然隐式的存在于f中。 匿名函数的用法匿名函数的用处十分宽泛,它自身就是一种值,能够不便地保留在各种容器中实现回调函数和操作封装。 匿名函数用作回调函数什么是回调函数把一段可执行的代码像参数传递那样传给其余代码,而这段代码会在某个时刻被调用执行,这就叫做回调。如果代码立刻被执行就称为同步回调,如果在之后晚点的某个工夫再执行,则称之为异步回调。 为什么要应用回调函数?软件工程设计中有个概念:高内聚,低耦合 耦合:软件结构中各个模块之间互相关联水平的度量 如果应用回调函数能够将内部耦合升高为数据耦合,显著进步了代码的品质。 这样做的益处是:高内聚,低耦合的益处体现在零碎继续倒退的过程中,高内聚,低耦合的零碎具备更好的 重用性 , 维护性 , 扩展性 ,能够更高效的实现零碎的保护开发,继续的反对业务的倒退,而不会成为业务倒退的阻碍。简略来说,当前如果要改代码,能够更释怀的改变,不会影响到太多其余的代码。 ...

April 10, 2022 · 2 min · jiezi

关于golang:gozero源码阅读负载均衡下第六期

一致性哈希一致性哈希次要针对的是缓存服务做负载平衡,以保障缓存节点变更后缓存生效过多,导致缓存穿透,从而把数据库打死。 一致性哈希原理能够参考这篇文章图解一致性哈希算法,细节分析本文不再赘述。 咱们来看看其外围算法 // service node 构造体定义type ServiceNode struct { Ip string Port string Index int}// 返回service node实例func NewServiceNode(ip, port string) *ServiceNode { return &ServiceNode{ Ip: ip, Port: port, }}func (sn *ServiceNode) SetIndex(index int) { sn.Index = index}type UInt32Slice []uint32// Len()func (s UInt32Slice) Len() int { return len(s)}// Less()func (s UInt32Slice) Less(i, j int) bool { return s[i] < s[j]}// Swap()func (s UInt32Slice) Swap(i, j int) { s[i], s[j] = s[j], s[i]}// 虚构节点构造定义type VirtualNode struct { VirtualNodes map[uint32]*ServiceNode NodeKeys UInt32Slice sync.RWMutex}// 实例化虚构节点对象func NewVirtualNode() *VirtualNode { return &VirtualNode{ VirtualNodes: map[uint32]*ServiceNode{}, }}// 增加虚构节点func (v *VirtualNode) AddVirtualNode(serviceNode *ServiceNode, virtualNum uint) { // 并发读写map-加锁 v.Lock() defer v.Unlock() for i := uint(0); i < virtualNum; i++ { hashStr := serviceNode.Ip + ":" + serviceNode.Port + ":" + strconv.Itoa(int(i)) v.VirtualNodes[v.getHashCode(hashStr)] = serviceNode } // 虚构节点hash值排序 v.sortHash()}// 移除虚构节点func (v *VirtualNode) RemoveVirtualNode(serviceNode *ServiceNode, virtualNum uint) { // 并发读写map-加锁 v.Lock() defer v.Unlock() for i := uint(0); i < virtualNum; i++ { hashStr := serviceNode.Ip + ":" + serviceNode.Port + ":" + strconv.Itoa(int(i)) delete(v.VirtualNodes, v.getHashCode(hashStr)) } v.sortHash()}// 获取虚构节点(二分查找)func (v *VirtualNode) GetVirtualNodel(routeKey string) *ServiceNode { // 并发读写map-加读锁,可并发读不可同时写 v.RLock() defer v.RUnlock() index := 0 hashCode := v.getHashCode(routeKey) i := sort.Search(len(v.NodeKeys), func(i int) bool { return v.NodeKeys[i] > hashCode }) // 当i大于下标最大值时,证实没找到, 给到第0个虚构节点, 当i小于node节点数时, index为以后节点 if i < len(v.NodeKeys) { index = i } else { index = 0 } // 返回具体节点 return v.VirtualNodes[v.NodeKeys[index]]}// hash数值排序func (v *VirtualNode) sortHash() { v.NodeKeys = nil for k := range v.VirtualNodes { v.NodeKeys = append(v.NodeKeys, k) } sort.Sort(v.NodeKeys)}// 获取hash code(采纳md5字符串后计算)func (v *VirtualNode) getHashCode(nodeHash string) uint32 { // crc32形式hash code // return crc32.ChecksumIEEE([]byte(nodeHash)) md5 := md5.New() md5.Write([]byte(nodeHash)) md5Str := hex.EncodeToString(md5.Sum(nil)) h := 0 byteHash := []byte(md5Str) for i := 0; i < 32; i++ { h <<= 8 h |= int(byteHash[i]) & 0xFF } return uint32(h)}咱们来写测试代码,测试下 ...

April 10, 2022 · 7 min · jiezi

关于golang:Golang力扣Leetcode-278-第一个错误的版本二分查找

题目:你是产品经理,目前正在率领一个团队开发新的产品。可怜的是,你的产品的最新版本没有通过品质检测。因为每个版本都是基于之前的版本开发的,所以谬误的版本之后的所有版本都是错的。 假如你有 n 个版本 [1, 2, ..., n],你想找出导致之后所有版本出错的第一个谬误的版本。 你能够通过调用 bool isBadVersion(version) 接口来判断版本号 version 是否在单元测试中出错。实现一个函数来查找第一个谬误的版本。你应该尽量减少对调用 API 的次数。 链接: 力扣Leetcode - 278. 第一个谬误的版本. 示例1 : 输出:n = 5, bad = 4输入:4解释:调用 isBadVersion(3) -> false 调用 isBadVersion(5) -> true 调用 isBadVersion(4) -> true所以,4 是第一个谬误的版本。示例2 : 输出:n = 1, bad = 1输入:1标签:二分查找、交互 思路:使用二分查找,先取两头 mid,如为 true,可能刚好在或者曾经超过第一个谬误的版本,取right=mid,再在 left<right 的前提下持续二分查找;如果false,证实第一个谬误的版本在mid之后,取left=mid+1,再在 left<right 的前提下持续二分查找。在查找的时候留神边界。 次要Go代码如下: package mainimport "fmt"func isBadVersion(version int) bool { if version >= 4 { return true } return false}func firstBadVersion(n int) int { left := 0 right := n for left < right { mid := (left + right) / 2 if isBadVersion(mid) == false { left = mid + 1 } else if isBadVersion(mid) == true { right = mid } } return right}func main() { fmt.Println(firstBadVersion(5))}提交截图: ...

April 10, 2022 · 1 min · jiezi

关于golang:Golang力扣Leetcode-69-x-的平方根-二分查找

题目:给你一个非负整数 x ,计算并返回 x 的 算术平方根 。 因为返回类型是整数,后果只保留 整数局部 ,小数局部将被 舍去 。 留神:不容许应用任何内置指数函数和算符,例如 pow(x, 0.5) 或者 x ** 0.5 。 链接: 力扣Leetcode - 69. x 的平方根. 示例 1: 输出:x = 4输入:2示例 2: 输出:x = 8输入:2解释:8 的算术平方根是 2.82842..., 因为返回类型是整数,小数局部将被舍去。标签:数学、二分查找 思路:通过 二分查找法 取得后果,下届为0,上届设定为x,在二分查找的每一步中,都须要比拟两头元素mid的平方与x的大小关系,并通过比拟的后果调整上下界的范畴。 mid*mid < x,left = mid + 1;mid*mid > x,right = mid - 1;mid*mid = x,取得答案。全副Go代码如下: package mainimport ( "fmt")func mySqrt(x int) int { left := 0 right := x res := 0 for left <= right { mid := (left + right) / 2 if mid*mid <= x { left = mid + 1 res = mid } else { right = mid - 1 } } return res}func main() { fmt.Println(mySqrt(4))}提交截图: ...

April 9, 2022 · 1 min · jiezi

关于golang:go错误与异常

在编写程序的时候很容易遇见的一个问题,也是必须要解决的,明天简略的去理解了一下什么是谬误什么是异样,在go中是如何解决的 <!-- more --> 谬误异样在理解怎么做之前,咱们首先应该要明确,什么是谬误,什么是异样 谬误:能够预测异样:不可预测在程序的编码中,咱们写的代码有时候咱们晓得他可能会呈现什么错,那么咱们就叫做谬误,比如说咱们写的这个代码: func pan(i int) { if i == 0 { err := fmt.Sprintf("i 不能为 0 ", time.Now()) panic(err) }}如果咱们用这个判断除数的话,那么能够晓得除数是不能为0的,如果呈现了除数为0那么他必定是谬误的,这种能够预测的,咱们称之为谬误。其余的谬误是咱们没有预测那么那些谬误就是异样。感觉说的有点小绕。其实简略的就是,预测的叫做谬误没有预测的就是异样。 如何进行解决在go中如果咱们执行了panic,咱们会失去上面这种构造。首先咱们来看一下咱们执行的代码: func main() { pan(0) fmt.Println("main平安退出")}func pan(i int) { if i == 0 { err := fmt.Sprintf("i 不能为 0 ", time.Now()) panic(err) }}这里咱们输出了0,那么必定是会执行panic函数的,那么执行了panic函数过后程序就会间接被终止,所以咱们会失去上面这个后果。 /private/var/folders/55/2cf9m54s36q_1yf2j1svkf580000gn/T/GoLand/___go_build_awesomeProject1_2022_4_9_PanicAndRecoverpanic: i 不能为 0 %!(EXTRA time.Time=2022-04-09 11:19:17.141969 +0800 CST m=+0.000072167)goroutine 1 [running]:main.pan(0x0) /Users/lizhongzheng/GolandProjects/awesomeProject1/2022-4-9/PanicAndRecover/main.go:16 +0xd0main.main() /Users/lizhongzheng/GolandProjects/awesomeProject1/2022-4-9/PanicAndRecover/main.go:9 +0x24这里会分明的写到在那里执行执行了panic,而后panic的谬误是什么等相干信息。然而咱们这里就会产生一个疑难,如果咱们的程序正在运行然而不想让这个Panic将咱们的程序间接终止,咱们应该怎么解决。咱们看到这里的后果是间接终止了程序的执行,最初是没有回到主函数执行输入的。对着这种解决,在php和java中应用有try catch这个办法进行解决,然而在go中是没有的,那么应该如果进行相似的解决呢?这里咱们就要应用到一个recover()函数了,这个函数和panic个别都是一起呈现的,这个函数的作用就是解决panic报的谬误,而后收集不让程序间接退出。这里咱们来演示一下有recover这个函数后,程序的执行吧,还是先上咱们执行的源码: func main() { pan(0) fmt.Println("main平安退出")}func pan(i int) { defer func() { if err := recover(); err != nil { fmt.Println("检测到谬误", err) } else { panic("检测失败") } }() if i == 0 { err := fmt.Sprintf("i 不能为 0 ", time.Now()) panic(err) }}这个咱们应用的defer,提早处理函数,而后在这里return前进行一个调用,对于defer函数在当前咱们再进行解说,在这里咱们晓得它是提早解决就能够了。而后上面是咱们的执行后果: ...

April 9, 2022 · 1 min · jiezi

关于golang:Go进阶基础特性defer

defer 是咱们常常会应用的一个关键字,它会在以后函数返回前执行传入的函数,罕用于敞开文件描述符、敞开数据库连贯以及解锁资源。 应用场景开释资源这是 defer 最常见的用法,包含开释互斥锁、敞开文件句柄、敞开网络连接、敞开管道和进行定时器等,如: m.mutex.Lock()defer m.mutex.Unlock()异样解决defer 第二个重要用处就是解决异样,与 recover 搭配一起解决 panic,让程序从异样中复原。例如 gin 框架中 recovery 中间件的源码: return func(c *Context) { defer func() { if err := recover(); err != nil { // Check for a broken connection, as it is not really a // condition that warrants a panic stack trace. var brokenPipe bool if ne, ok := err.(*net.OpError); ok { if se, ok := ne.Err.(*os.SyscallError); ok { if strings.Contains(strings.ToLower(se.Error()), "broken pipe") || strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") { brokenPipe = true } } }// ...批改命名返回值// $GOROOT/src/fmt/scan.gofunc (s *ss) Token(skipSpace bool, f func(rune) bool) (tok []byte, err error) { defer func() { if e := recover(); e != nil { if se, ok := e.(scanError); ok { err = se.err } else { panic(e) } } }()...} 打印调试信息// $GOROOT/src/net/conf.gofunc (c *conf) hostLookupOrder(r *Resolver, hostname string) (ret hostLookupOrder) { if c.dnsDebugLevel > 1 { defer func() { print("go package net: hostLookupOrder(", hostname, ") = ", ret.String(), "\n") }() } ...} 行为规定defer 的语法很简略,不过衍生出的用法很多,有时让人蛊惑,在这里咱们总结一下 defer 的几个根本应用规定。 ...

April 8, 2022 · 3 min · jiezi

关于golang:benchmark-基准测试

转载自:benchmark 基准测试 1 稳固的测试环境当咱们尝试去优化代码的性能时,首先得晓得以后的性能怎么样。Go 语言规范库内置的 testing 测试框架提供了基准测试(benchmark)的能力,能让咱们很容易地对某一段代码进行性能测试。 性能测试受环境的影响很大,为了保障测试的可重复性,在进行性能测试时,尽可能地放弃测试环境的稳固。 机器处于闲置状态,测试时不要执行其余工作,也不要和其他人共享硬件资源。机器是否敞开了节能模式,个别笔记本会默认关上这个模式,测试时敞开。防止应用虚拟机和云主机进行测试,个别状况下,为了尽可能地进步资源的利用率,虚拟机和云主机 CPU 和内存个别会超调配,超分机器的性能体现会十分地不稳固。超调配是针对硬件资源来说的,商业上对应的就是云主机的超卖。虚拟化技术带来的最大间接收益是服务器整合,通过 CPU、内存、存储、网络的超调配(Overcommitment)技术,最大化服务器的使用率。例如,虚拟化的技能之一就是得心应手的操控 CPU,例如一台 32U(物理外围)的服务器可能会创立出 128 个 1U(虚构外围)的虚拟机,当物理服务器资源闲置时,CPU 超调配个别不会对虚拟机上的业务产生显著影响,但如果大部分虚拟机都处于忙碌状态时,那么各个虚拟机为了取得物理服务器的资源就要相互竞争,互相期待。Linux 上专门有一个指标,Steal Time(st),用来掂量被虚拟机监视器(Hypervisor)偷去给其它虚拟机应用的 CPU 工夫所占的比例。2 benchmark 的应用2.1 一个简略的例子Go 语言规范库内置了反对 benchmark 的 testing 库,接下来看一个简略的例子: 应用 go mod init example 初始化一个模块,新增 fib.go 文件,实现函数 fib,用于计算第 N 个菲波那切数。 // fib.gopackage mainfunc fib(n int) int { if n == 0 || n == 1 { return n } return fib(n-2) + fib(n-1)}接下来,咱们在 fib_test.go 中实现一个 benchmark 用例: // fib_test.gopackage mainimport "testing"func BenchmarkFib(b *testing.B) { for n := 0; n < b.N; n++ { fib(30) // run fib(30) b.N times }}benchmark 和一般的单元测试用例一样,都位于 _test.go 文件中。函数名以 Benchmark 结尾,参数是 b *testing.B。和一般的单元测试用例很像,单元测试函数名以 Test 结尾,参数是 t *testing.T。2.2 运行用例go test <module name>/<package name> 用来运行某个 package 内的所有测试用例。 ...

April 8, 2022 · 4 min · jiezi

关于golang:Golang力扣Leetcode-367-有效的完全平方数二分查找

题目:给定一个 正整数 num ,编写一个函数,如果 num 是一个齐全平方数,则返回 true ,否则返回 false 。 进阶:不要 应用任何内置的库函数,如 sqrt 。 链接: 力扣Leetcode - 367. 无效的齐全平方数. 示例 1: 输出:num = 16输入:true示例 2: 输出:num = 14输入:false思路:应用 二分查找 解决问题。因为 num 是正整数,所以若正整数 x 满足 x*x=num,则 x 肯定满足 1≤x≤num。于是咱们能够将 1 和 num 作为二分查找搜寻区间的初始边界。          因为咱们在挪动左侧边界 left 和右侧边界 right 时,新的搜寻区间都不会蕴含被查看的下标 mid,所以搜寻区间的边界始终是咱们没有查看过的。因而,当 left = right 时,咱们仍须要查看 mid=(left+right)/2。 如果 mid*mid=num,则下标 mid 即为要寻找的下标,返回true;如果 mid*mid<num,则 left=mid+1;如果 mid*mid>num,则 right=mid-1。Go代码: package mainimport "fmt"func isPerfectSquare(num int) bool { left := 0 right := num for left <= right { mid := (left + right) / 2 if mid*mid == num { return true } else if mid*mid < num { left = mid + 1 } else { right = mid - 1 } } return false}func main() { fmt.Println(isPerfectSquare(15))}提交截图: ...

April 8, 2022 · 1 min · jiezi

关于golang:带你十天轻松搞定-Go-微服务之大结局分布式事务

序言咱们通过一个系列文章跟大家具体展现一个 go-zero 微服务示例,整个系列分十篇文章,目录构造如下: 环境搭建服务拆分用户服务产品服务订单服务领取服务RPC 服务 Auth 验证服务监控链路追踪分布式事务(本文)冀望通过本系列带你在本机利用 Docker 环境利用 go-zero 疾速开发一个商城零碎,让你疾速上手微服务。 残缺示例代码:https://github.com/nivin-studio/go-zero-mall 首先,咱们来看一下整体的服务拆分图: 10.1 DTM 介绍DTM 是一款 golang 开发的分布式事务管理器,解决了跨数据库、跨服务、跨语言栈更新数据的一致性问题。 绝大多数的订单零碎的事务都会跨服务,因而都有更新数据一致性的需要,都能够通过 DTM 大幅简化架构,造成一个优雅的解决方案。 而且 DTM 曾经深度单干,原生的反对go-zero中的分布式事务,上面就来具体的解说如何用 DTM 来帮忙咱们的订单零碎解决一致性问题 10.2 go-zero 应用 DTM首先咱们回顾下 第五章 订单服务 中 order rpc 服务中 Create 接口解决逻辑。办法里判断了用户和产品的合法性,以及产品库存是否短缺,最初通过 OrderModel 创立了一个新的订单,以及调用 product rpc 服务 Update 的接口更新了产品的库存。 func (l *CreateLogic) Create(in *order.CreateRequest) (*order.CreateResponse, error) { // 查问用户是否存在 _, err := l.svcCtx.UserRpc.UserInfo(l.ctx, &user.UserInfoRequest{ Id: in.Uid, }) if err != nil { return nil, err } // 查问产品是否存在 productRes, err := l.svcCtx.ProductRpc.Detail(l.ctx, &product.DetailRequest{ Id: in.Pid, }) if err != nil { return nil, err } // 判断产品库存是否短缺 if productRes.Stock <= 0 { return nil, status.Error(500, "产品库存有余") } newOrder := model.Order{ Uid: in.Uid, Pid: in.Pid, Amount: in.Amount, Status: 0, } res, err := l.svcCtx.OrderModel.Insert(&newOrder) if err != nil { return nil, status.Error(500, err.Error()) } newOrder.Id, err = res.LastInsertId() if err != nil { return nil, status.Error(500, err.Error()) } _, err = l.svcCtx.ProductRpc.Update(l.ctx, &product.UpdateRequest{ Id: productRes.Id, Name: productRes.Name, Desc: productRes.Desc, Stock: productRes.Stock - 1, Amount: productRes.Amount, Status: productRes.Status, }) if err != nil { return nil, err } return &order.CreateResponse{ Id: newOrder.Id, }, nil}之前咱们说过,这里解决逻辑存在数据一致性问题,有可能订单创立胜利了,然而在更新产品库存的时候可能会产生失败,这时候就会存在订单创立胜利,产品库存没有缩小的状况。 ...

April 8, 2022 · 8 min · jiezi

关于golang:go语言学习数组字符串和切片未完待续

数组Go语言的数组是一种值类型,尽管数组的元素能够被批改,然而数组自身的赋值和函数传参都是以整体复制的形式解决的。 定义形式var a [3]int // 定义长度为3的int型数组, 元素全副为0var b = [...]int{1, 2, 3} // 定义长度为3的int型数组, 元素为 1, 2, 3var c = [...]int{2: 3, 1: 2} // 定义长度为3的int型数组, 元素为 0, 2, 3var d = [...]int{1, 2, 4: 5, 6} // 定义长度为6的int型数组, 元素为 1, 2, 0, 0, 5, 6第一种形式是定义一个数组变量的最根本的形式,数组的长度明确指定,数组中的每个元素都以零值初始化。 第二种形式定义数组,能够在定义的时候程序指定全副元素的初始化值,数组的长度依据初始化元素的数目主动计算。 第三种形式是以索引的形式来初始化数组的元素,因而元素的初始化值呈现程序比拟随便。这种初始化形式和map[int]Type类型的初始化语法相似。数组的长度以呈现的最大的索引为准,没有明确初始化的元素仍然用0值初始化。 第四种形式是混合了第二种和第三种的初始化形式,后面两个元素采纳程序初始化,第三第四个元素零值初始化,第五个元素通过索引初始化,最初一个元素跟在后面的第五个元素之后采纳程序初始化。 咱们还能够定义一个空的数组:var d [0]int // 定义一个长度为0的数组var e = [0]int{} // 定义一个长度为0的数组var f = [...]int{} // 定义一个长度为0的数组长度为0的数组在内存中并不占用空间。空数组尽管很少间接应用,然而能够用于强调某种特有类型的操作时防止调配额定的内存空间,比方用于管道的同步操作: c1 := make(chan [0]int)go func() { fmt.Println("c1") c1 <- [0]int{}}()<-c1在这里,咱们并不关怀管道中传输数据的实在类型,其中管道接管和发送操作只是用于音讯的同步。对于这种场景,咱们用空数组来作为管道类型能够缩小管道元素赋值时的开销。当然个别更偏向于用无类型的匿名构造体代替: ...

April 8, 2022 · 2 min · jiezi

关于golang:Golang力扣Leetcode-35-搜索插入位置二分查找

题目:给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按程序插入的地位。 请必须应用工夫复杂度为 O(log n) 的算法。 链接: 力扣Leetcode - 35. 搜寻插入地位. 示例 1: 输出: nums = [1,3,5,6], target = 5输入: 2示例 2: 输出: nums = [1,3,5,6], target = 2输入: 1示例 3: 输出: nums = [1,3,5,6], target = 7输入: 4思路:题目要求工夫复杂度为 O(log n) 的算法,我首先想到就是二分查找。但这题还多了个额定的条件,即如果不存在数组中的时候须要返回按程序插入的地位。那咱们就得换个思维:思考这个插入的地位 res,它成立的条件为:nums[res−1] < target ≤ nums[res],其中 nums 代表排序数组。因为如果存在这个目标值,咱们返回的索引也是 res,因而咱们能够将两个条件合并得出最初的指标:「在一个有序数组中找第一个大于等于 target 的下标」。而后间接套用二分法即可。 Go代码: package mainimport "fmt"func searchInsert(nums []int, target int) int { n := len(nums) left, right := 0, n-1 res := n for left <= right { mid := (right + left) / 2 if target <= nums[mid] { res = mid right = mid - 1 } else { left = mid + 1 } } return res}func main() { a := []int{1, 3, 5, 6} fmt.Println(searchInsert(a, 2))}提交截图: ...

April 7, 2022 · 1 min · jiezi

关于golang:带你十天轻松搞定-Go-微服务系列九链路追踪

序言咱们通过一个系列文章跟大家具体展现一个 go-zero 微服务示例,整个系列分十篇文章,目录构造如下: 环境搭建服务拆分用户服务产品服务订单服务领取服务RPC 服务 Auth 验证服务监控链路追踪(本文)分布式事务冀望通过本系列带你在本机利用 Docker 环境利用 go-zero 疾速开发一个商城零碎,让你疾速上手微服务。 残缺示例代码:https://github.com/nivin-studio/go-zero-mall 首先,咱们来看一下整体的服务拆分图: 9.1 Jaeger 介绍Jaeger 是 Uber 开发并开源的一款分布式追踪零碎,兼容 OpenTracing API,实用于以下场景: 分布式跟踪信息传递分布式事务监控问题剖析服务依赖性剖析性能优化Jaeger 的全链路追踪性能次要由三个角色实现: client:负责全链路上各个调用点的计时、采样,并将 tracing 数据发往本地 agent。agent:负责收集 client 发来的 tracing 数据,并以 thrift 协定转发给 collector。collector:负责收集所有 agent 上报的 tracing 数据,对立存储。9.2 go-zero 应用 Jaeger 链路追踪go-zero 框架曾经帮咱们实现了链路追踪(详见:go-zero链路追踪),并且集成反对了 Jaeger, Zipkin 这两种链路追踪上报工具,咱们只有简略配置下,就能够可视化的查看到一个申请的残缺的调用链,以及每一个环节的调用状况及性能。 9.2.1 增加 user api 服务 Telemetry 配置$ vim mall/service/user/api/etc/user.yamlName: UserHost: 0.0.0.0Port: 8000...Telemetry: Name: user.api Endpoint: http://jaeger:14268/api/traces Sampler: 1.0 Batcher: jaeger9.2.2 增加 user rpc 服务 Telemetry 配置$ vim mall/service/user/rpc/etc/user.yamlName: user.rpcListenOn: 0.0.0.0:9000...Telemetry: Name: user.rpc Endpoint: http://jaeger:14268/api/traces Sampler: 1.0 Batcher: jaeger9.2.3 增加 product api 服务 Telemetry 配置$ vim mall/service/product/api/etc/product.yamlName: ProductHost: 0.0.0.0Port: 8001...Telemetry: Name: product.api Endpoint: http://jaeger:14268/api/traces Sampler: 1.0 Batcher: jaeger9.2.4 增加 product rpc 服务 Telemetry 配置$ vim mall/service/product/rpc/etc/product.yamlName: product.rpcListenOn: 0.0.0.0:9001...Telemetry: Name: product.rpc Endpoint: http://jaeger:14268/api/traces Sampler: 1.0 Batcher: jaeger9.2.5 增加 order api 服务 Telemetry 配置$ vim mall/service/order/api/etc/order.yamlName: OrderHost: 0.0.0.0Port: 8002...Telemetry: Name: order.api Endpoint: http://jaeger:14268/api/traces Sampler: 1.0 Batcher: jaeger9.2.6 增加 order rpc 服务 Telemetry 配置$ vim mall/service/order/rpc/etc/order.yamlName: order.rpcListenOn: 0.0.0.0:9002...Telemetry: Name: order.rpc Endpoint: http://jaeger:14268/api/traces Sampler: 1.0 Batcher: jaeger9.2.7 增加 pay api 服务 Telemetry 配置$ vim mall/service/pay/api/etc/pay.yamlName: PayHost: 0.0.0.0Port: 8003...Telemetry: Name: pay.api Endpoint: http://jaeger:14268/api/traces Sampler: 1.0 Batcher: jaeger9.2.8 增加 pay rpc 服务 Telemetry 配置$ vim mall/service/pay/rpc/etc/pay.yamlName: pay.rpcListenOn: 0.0.0.0:9003...Telemetry: Name: pay.rpc Endpoint: http://jaeger:14268/api/traces Sampler: 1.0 Batcher: jaeger提醒:配置批改后,须要重启服务才会失效。9.3 应用 Jaeger UI 查看链路拜访 /api/user/userinfo api接口 ...

April 7, 2022 · 2 min · jiezi

关于golang:Golang力扣Leetcode-852-山脉数组的峰顶索引二分查找

题目:合乎下列属性的数组 arr 称为 山脉数组 : arr.length >= 3存在 i(0 < i < arr.length - 1)使得: arr[0] < arr[1] < ... arr[i-1] < arr[i]arr[i] > arr[i+1] > ... > arr[arr.length - 1]给你由整数组成的山脉数组 arr ,返回任何满足 arr[0] < arr[1] < ... arr[i - 1] < arr[i] > arr[i + 1] > ... > arr[arr.length - 1] 的下标 i 。 链接: 力扣Leetcode - 852. 山脉数组的峰顶索引. 示例 1: 输出:arr = [0,1,0]输入:1示例 2: 输出:arr = [0,2,1,0]输入:1示例 3: ...

April 7, 2022 · 1 min · jiezi

关于golang:gozero源码阅读负载均衡上第五期

在浏览 go-zero 源码之前咱们先来看看罕用的负载平衡算法,看看其原理,以及是如何实现,而后咱们在用这些负载平衡算法来和 go-zero 的比照下,看看各自的优缺点是啥。 轮询proxy 服务与 ndoe 服务配置文件 { "proxy": { "url": "127.0.0.1:8080" }, "nodes": [ { "url": "127.0.0.1:8081" }, { "url": "127.0.0.1:8082" }, { "url": "127.0.0.1:8083" } ]}proxy 服务、 ndoe 服务、轮询算法代码 // 配置type Config struct { Proxy Proxy `json:"proxy"` Nodes []*Node `json:"nodes"`}// proxy 服务器配置type Proxy struct { Url string `json:"url"`}// node 服务器配置type Node struct { URL string `json:"url"` IsDead bool useCount int mu sync.RWMutex}var cfg Configfunc init() { // 加载配置文件 data, err := ioutil.ReadFile("./config.json") if err != nil { log.Fatal(err.Error()) } json.Unmarshal(data, &cfg)}// 设置 node 服务器宕机状态func (node *Node) SetDead(b bool) { node.mu.Lock() node.IsDead = b node.mu.Unlock()}// 获取 node 服务器是否宕机func (node *Node) GetIsDead() bool { node.mu.RLock() isAlive := node.IsDead node.mu.RUnlock() return isAlive}var ( mu sync.Mutex idx int = 0)// 轮询算法func rrlbbHandler(w http.ResponseWriter, r *http.Request) { maxLen := len(cfg.Nodes) // Round Robin mu.Lock() currentNode := cfg.Nodes[idx%maxLen] // 循环数组 if currentNode.GetIsDead() { idx++ // 如果 node 宕机,则轮询到下一个 node currentNode = cfg.Nodes[idx%maxLen] } currentNode.useCount++ targetURL, err := url.Parse("http://" + currentNode.URL) log.Println(targetURL.Host) if err != nil { log.Fatal(err.Error()) } idx++ mu.Unlock() reverseProxy := httputil.NewSingleHostReverseProxy(targetURL) reverseProxy.ErrorHandler = func(w http.ResponseWriter, r *http.Request, e error) { log.Printf("%v is dead.", targetURL) currentNode.SetDead(true) rrlbbHandler(w, r) // 节点宕机 递归调用本人 } reverseProxy.ServeHTTP(w, r)}// node是否存活func isAlive(url *url.URL) bool { conn, err := net.DialTimeout("tcp", url.Host, time.Minute*1) if err != nil { log.Printf("Unreachable to %v, error %s:", url.Host, err.Error()) return false } defer conn.Close() return true}// node探活func healthCheck() { t := time.NewTicker(time.Minute * 1) for { select { case <-t.C: for _, node := range cfg.Nodes { pingURL, err := url.Parse(node.URL) if err != nil { log.Fatal(err.Error()) } isAlive := isAlive(pingURL) node.SetDead(!isAlive) msg := "ok" if !isAlive { msg = "dead" } log.Printf("%v checked %s by healthcheck", node.URL, msg) } } }}// 启动 proxy 服务func proxyServerStart() { var err error go healthCheck() s := http.Server{ Addr: cfg.Proxy.Url, Handler: http.HandlerFunc(rrlbbHandler), } if err = s.ListenAndServe(); err != nil { log.Fatal(err.Error()) }}// 启动所有 node 服务func nodeServerStart() { http.HandleFunc("/ping", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("pong")) }) wg := new(sync.WaitGroup) wg.Add(len(cfg.Nodes)) for i, node := range cfg.Nodes { go func() { if i > 0 { // 模仿一个node宕机 log.Fatal(http.ListenAndServe(node.URL, nil)) } wg.Done() }() time.Sleep(time.Millisecond * 100) } wg.Wait()}最外围的算法就是这一段,非常简单,轮询的实质其实是循环数组 ...

April 7, 2022 · 6 min · jiezi

关于golang:Go-118工作区模式workspace-mode

背景Go 1.18除了引入泛型(generics)、含糊测试(Fuzzing)之外,另外一个重大性能是引入了工作区模式(workspace mode)。 工作区模式对本地同时开发多个有依赖的Module的场景十分有用。 举个例子,咱们当初有2个Go module我的项目处于开发阶段,其中一个是example.com/main,另外一个是example.com/util。其中example.com/main这个module须要应用example.com/util这个module里的函数。 咱们来看看Go 1.18版本前后如何解决这种场景。 Go 1.18之前怎么做在Go 1.18之前,对于上述场景有2个解决计划: 计划1:被依赖的模块及时提交代码到代码仓库这个计划很好了解,既然example.com/main这个module依赖了example.com/util这个module,那为了example.com/main能应用到example.com/util的最新代码,须要做2个事件 本地开发过程中,如果example.com/util有批改,都马上提交代码到代码仓库,而后打tag紧接着example.com/main更新依赖的example.com/util的版本号(tag),能够应用go get -u命令。这种计划比拟繁琐,每次example.com/util有批改,都要提交代码,否则example.com/main这个module就无奈应用到最新的example.com/util。 计划2:go.mod里应用replace指令为了解决方案1的痛点,于是有了计划2:在go.mod里应用replace指令。 通过replace指令,咱们能够间接应用example.com/util这个module的本地最新代码,而不必把example.com/util的代码提交到代码仓库。 为了不便大家了解,咱们间接上代码。代码目录构造如下: module||------main| |---main.go| |---go.mod |------util| |---util.go| |---go.modmain目录下的main.go代码如下: //main.gopackage mainimport ( "fmt" "example.com/util")func main() { result := util.Add(1, 2) fmt.Println(result)}main目录下的go.mod内容如下: module example.com/maingo 1.16require example.com/util v1.0.0replace example.com/util => ../utilutil目录下的util.go代码如下: // util.gopackage utilfunc Add(a int, b int) int { return a + b}util目录下的go.mod内容如下: module example.com/utilgo 1.16这里最外围的是example.com/main这个module的go.mod,最初一行应用了replace指令。 module example.com/maingo 1.16require example.com/util v1.0.0replace example.com/util => ../util通过replace指令,应用go命令编译代码的时候,会找到本地的util目录,这样example.com/main就能够应用到本地最新的example.com/util代码。进入main目录,运行代码,后果如下所示: $ cd main$ go run main.go3然而这种计划也有个问题,咱们在提交example.com/main这个module的代码到代码仓库时,须要删除最初的replace指令,否则其余开发者下载后会编译报错,因为他们本地可能没有util目录,或者util目录的门路和你的不一样。 ...

April 6, 2022 · 1 min · jiezi

关于golang:社区文章|MOSN-社区性能分析利器Holmes-原理浅析

文|Junlong Liu Shopee Digital Purchase & Local Services Engineering 本文1743字 浏览 6分钟 贡献者前言我是在开发工作过程中理解到 Holmes 的,为了保障系统稳定性须要一个性能排查工具,因而也须要一个保留现场的性能监控工具。当我在网上查问该方面的开源库时,发现可用的并不多。后续找到 MOSN 社区的 Holmes ,发现这个开源库性能根本齐全、扩展性也高,特地是 GCHeapDump 这个业界当先的性能,对解决内存升高的问题非常有用。 2021 年年末理解到的 Holmes 组件,而后开始理解 Holmes 所在的 MOSN 社区。Holmes 作为性能排查工具,外围性能是及时发现性能指标异样,并对系统进行 Profiling。 因为 Holmes 还处于萌芽期,除了 Readme 之外的文档资料并不多。还有一些 Holmes 过后不反对的性能,比方动静配置调整与上报。Holmes 过后也还没公布第一个版本,然而本人对这方面也有趣味和了解,于是在 GitHub 上提了几个 Issue 探讨,社区回复的速度非常快。后续在社区前辈们的领导下提了 PR,也因而通过 Holmes 的代码设计学习到了很多对于开源组件的设计理念。 因而我决定参加开源社区并奉献代码,以解决理论需要。有了肯定的理解和教训之后,通过和人德前辈探讨,总结这样一篇分享文章。 本文将介绍 Holmes 的应用场景、疾速开始案例、多个监控类型、设计原理、扩大性能与如何借助 Holmes 搭建起一套简略的性能排查零碎,欢送大家留言领导。 Holmes 应用场景对于零碎的性能尖刺问题,咱们通常应用 Go 官网内置的 pprof 包进行剖析,然而难点是对于一闪而过的“尖刺”,开发人员很难及时保留现场:当你收到告警信息,从被窝中爬起来,关上电脑链接 VPN,零碎说不定都曾经重启三四趟了。 MOSN 社区的 Holmes 是一个基于 Golang 实现的轻量级性能监控零碎,当利用的性能指标产生了异样稳定时,Holmes 会在第一工夫保留现场,让你第二天下班能够一边从容地喝着枸杞茶,一边追究问题的根因。 Quick Start应用 Holmes 的形式非常简略,只须要在您的零碎初始化逻辑内增加以下代码: ...

April 6, 2022 · 2 min · jiezi

关于golang:Golang力扣Leetcode-704-二分查找二分查找

题目:给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜寻 nums 中的 target,如果目标值存在返回下标,否则返回 -1。 链接: 力扣Leetcode - 704. 二分查找. 示例 1: 输出: nums = [-1,0,3,5,9,12], target = 9输入: 4解释: 9 呈现在 nums 中并且下标为 4示例 2: 输出: nums = [-1,0,3,5,9,12], target = 2输入: -1解释: 2 不存在 nums 中因而返回 -1思路:定义查找的范畴 [left,right],初始查找范畴是整个数组。每次取查找范畴的中点 mid,比拟 nums[mid] 和 target 的大小,如果相等则 mid 即为要寻找的下标,如果不相等则依据 nums[mid] 和 target 的大小关系将查找范畴放大一半。 如果 nums[i]=target,则下标 i 即为要寻找的下标;如果 nums[i]>target,则 target 只可能在下标 i 的左侧;如果 nums[i]<target,则 target 只可能在下标 i 的右侧。Go代码: ...

April 6, 2022 · 1 min · jiezi

关于golang:带你十天轻松搞定-Go-微服务系列八服务监控

序言咱们通过一个系列文章跟大家具体展现一个 go-zero 微服务示例,整个系列分十篇文章,目录构造如下: 环境搭建服务拆分用户服务产品服务订单服务领取服务RPC 服务 Auth 验证服务监控(本文)链路追踪分布式事务冀望通过本系列带你在本机利用 Docker 环境利用 go-zero 疾速开发一个商城零碎,让你疾速上手微服务。 残缺示例代码:https://github.com/nivin-studio/go-zero-mall 首先,咱们来看一下整体的服务拆分图: 8.1 Prometheus 介绍Prometheus 是一款基于时序数据库的开源监控告警零碎,基本原理是通过 HTTP 协定周期性抓取被监控服务的状态,任意服务只有提供对应的 HTTP 接口就能够接入监控。不须要任何 SDK 或者其余的集成过程,输入被监控服务信息的 HTTP 接口被叫做 exporter 。目前互联网公司罕用的服务大部分都有 exporter 能够间接应用,比方 Varnish、Haproxy、Nginx、MySQL、Linux 零碎信息(包含磁盘、内存、CPU、网络等等)。Promethus 有以下特点: 反对多维数据模型(由度量名和键值对组成的工夫序列数据)反对 PromQL 查询语言,能够实现非常复杂的查问和剖析,对图表展现和告警十分有意义不依赖分布式存储,单点服务器也能够应用反对 HTTP 协定被动拉取形式采集工夫序列数据反对 PushGateway 推送工夫序列数据反对服务发现和动态配置两种形式获取监控指标反对接入 Grafana 8.2 go-zero 应用 Prometheus 监控服务go-zero 框架中集成了基于 Prometheus 的服务指标监控,go-zero 目前在 http 的中间件和 rpc 的拦截器中增加了对申请指标的监控。 次要从 申请耗时 和 申请谬误 两个维度,申请耗时采纳了 Histogram 指标类型定义了多个 Buckets 不便进行分位统计,申请谬误采纳了 Counter 类型,并在 http metric 中增加了 path 标签,rpc metric 中增加了 method 标签以便进行细分监控。 ...

April 6, 2022 · 3 min · jiezi

关于golang:Golang力扣Leetcode-448-找到所有数组中消失的数字哈希

题目:给你一个含 n 个整数的数组 nums ,其中 nums[i] 在区间 [1, n] 内。请你找出所有在 [1, n] 范畴内但没有呈现在 nums 中的数字,并以数组的模式返回后果。 链接: 力扣Leetcode - 448. 找到所有数组中隐没的数字. 示例 1: 输出:nums = [4,3,2,7,8,2,3,1]输入:[5,6]示例 2: 输出:nums = [1,1]输入:[2]思路:应用哈希表,遍历数组把呈现过的数存在哈希表中。再遍历哈希表,把没有存在哈希表中的数存进数组即可。 Go代码: package mainimport "fmt"func findDisappearedNumbers(nums []int) []int { HashMap := map[int]int{} var res []int for _, i := range nums { HashMap[i] = 1 } for j := 1; j <= len(nums); j++ { if HashMap[j] != 1 { res = append(res, j) } } return res}func main() { a := []int{4, 3, 2, 7, 8, 2, 3, 1} fmt.Println(findDisappearedNumbers(a))}提交截图: ...

April 5, 2022 · 1 min · jiezi

关于golang:带你十天轻松搞定-Go-微服务系列七

序言咱们通过一个系列文章跟大家具体展现一个 go-zero 微服务示例,整个系列分十篇文章,目录构造如下: 环境搭建服务拆分用户服务产品服务订单服务领取服务RPC 服务 Auth 验证(本文)服务监控链路追踪分布式事务冀望通过本系列带你在本机利用 Docker 环境利用 go-zero 疾速开发一个商城零碎,让你疾速上手微服务。 残缺示例代码:https://github.com/nivin-studio/go-zero-mall 首先,咱们来看一下整体的服务拆分图: 7 RPC服务 Auth 验证在后面几章咱们曾经别离实现了 user product order pay 的 rpc 服务和 api 服务。在 api 服务中咱们应用 go-zero 框架自带的 jwt 实现鉴权验证。那么接下里咱们就说说 rpc 服务的 auth 验证。 go-zero 框架 rpc 服务的 auth 验证原理是,客户端拜访 rpc 服务须要携带 App 标识以及 Token 值,rpc 服务会从指定的 Redis 服务中验证 App 标识和 Token 值是否正确。所以客户端的 App 标识,Token 值,是须要提前打入 Redis 服务中。 7.1 开启 rpc 服务 auth 验证上面咱们以 user rpc 服务,和 user api 服务为例,来开启并应用 rpc 服务的 auth 验证 ...

April 5, 2022 · 2 min · jiezi

关于golang:Go进阶基础特性错误

错误处理是任何编程语言都绕不开的话题。始终以来,编程语言的错误处理机制有两大流派:基于异样的结构化 try-catch-finally 解决机制和基于值的解决机制。前者的成员包含 C++、Java、Python、PHP 等支流编程语言,后者的代表则是 C 语言。Go 的设计谋求简略,采纳的是后一种解决机制:谬误就是值,而错误处理就是基于值比拟后的决策。 意识 error在 Go 语言中,谬误是值,不过是一个接口值,也即咱们平时罕用的 error: // $GOROOT/src/builtin/builtin.gotype interface error { Error() string} error 接口很简略,只申明了一个 Error() 办法。在规范库中提供了结构谬误值的两种根本办法:errors.New() 和 fmt.Errorf(),在 Go 1.13 版本之前,这两种办法实际上返回的是一个未导出类型 errors.errorString: // $GOROOT/src/errors/errors.gofunc New(text string) error { return &errorString{text}}type errorString struct { s string}func (e *errorString) Error() string { return e.s}// $GOROOT/src/fmt/errors.go// 1.13 版本之前func Errorf(format string, a ...interface{}) error { return errors.New(Sprintf(format, a...))} fmt.Errorf() 实用于须要格式化输入字符串的场景,如果不须要格式化字符串,则倡议应用 errors.New()。因为 fmt.Errof() 在生成格式化字符串时须要遍历所有字符,会有肯定的性能损失。 错误处理根本策略理解了谬误值后,咱们来看一下 Go 语言错误处理的几种习用策略。 ...

April 5, 2022 · 5 min · jiezi

关于golang:Golang力扣Leetcode-258-各位相加

题目:给定一个非负整数 num,重复将各个位上的数字相加,直到后果为一位数。返回这个后果。 链接: 力扣Leetcode - 258. 各位相加. 示例 1: 输出: num = 38输入: 2 解释: 各位相加的过程为:38 --> 3 + 8 --> 1111 --> 1 + 1 --> 2因为 2 是一位数,所以返回 2。示例 2: 输出: num = 0输入: 0思路:求出每一位再相加,如果还是大于一位数,持续循环,每一位再相加,直到和为一位数跳出循环输入。 Go代码: package mainimport "fmt"func addDigits(num int) int { for num >= 10 { sum := 0 for num != 0 { a := num % 10 // 取出个位 sum += a // 每一位加起来 num = (num - a) / 10 } num = sum } return num}func main() { fmt.Println(addDigits(38))}提交截图: ...

April 5, 2022 · 1 min · jiezi

关于golang:Golang力扣Leetcode-374-猜数字大小二分查找

题目:猜数字游戏的规定如下: 每轮游戏,我都会从 1 到 n 随机抉择一个数字。 请你猜选出的是哪个数字。如果你猜错了,我会通知你,你猜想的数字比我选出的数字是大了还是小了。你能够通过调用一个事后定义好的接口 int guess(int num) 来获取猜想后果,返回值一共有 3 种可能的状况(-1,1 或 0): -1:我选出的数字比你猜的数字小 pick < num1:我选出的数字比你猜的数字大 pick > num0:我选出的数字和你猜的数字一样。祝贺!你猜对了!pick == num返回我选出的数字。链接: 力扣Leetcode - 374. 猜数字大小. 示例 1: 输出:n = 10, pick = 6输入:6示例 2: 输出:n = 1, pick = 1输入:1示例 3: 输出:n = 2, pick = 1输入:1示例 4: 输出:n = 2, pick = 2输入:2思路:用二分查找,mid = left + right 如果 guess(mid) = -1,证实咱们猜的数比他想的大,right = mid - 1 放大范畴;如果 guess(mid) = 1,证实咱们猜的数比他想的小,left = mid + 1 放大范畴;如果 guess(mid) = 0,就证实他想的数就是 mid直至找到他要的数 return 进去 ...

April 5, 2022 · 1 min · jiezi

关于golang:Golang力扣Leetcode-414-第三大的数排序

题目:给你一个非空数组,返回此数组中 第三大的数 。如果不存在,则返回数组中最大的数。 链接: 力扣Leetcode - 414. 第三大的数. 示例 1: 输出:[3, 2, 1]输入:1解释:第三大的数是 1 。示例 2: 输出:[1, 2]输入:2解释:第三大的数不存在, 所以返回最大的数 2 。示例 3: 输出:[2, 2, 3, 1]输入:1解释:留神,要求返回第三大的数,是指在所有不同数字中排第三大的数。此例中存在两个值为 2 的数,它们都排第二。在所有不同数字中排第三大的数为 1 。思路: 先用 sort.Ints 排序数组去除数组中反复的元素,利用一个 j 来确定每个不反复元素“应该在的地位”,遍历整个数组,当这个元素与前一个元素相等时,那么 j 不挪动,表明下一个不反复元素应该在这里。以后元素与前一个元素不雷同时,以后元素挪动到j的地位,j++。最初再判断去除反复项的数组长度,进行输入Go代码: package mainimport ( "fmt" "sort")func thirdMax(nums []int) int { // 排序数组 sort.Ints(nums) // 利用一个j来确定每个不反复元素“应该在的地位”, // 遍历整个数组,当这个元素与前一个元素相等时,那么j不挪动,表明下一个不反复元素应该在这里。 // 以后元素与前一个元素不雷同时,以后元素挪动到j的地位,j++。 j := 1 for i := 1; i < len(nums); i++ { if nums[i] != nums[i-1] { nums[j] = nums[i] j++ } } var res = nums[:j] // 判断 n := len(res) if n <= 2 { return res[n-1] } else { return res[n-3] }}func main() { a := []int{2, 2, 3, 1} fmt.Println(thirdMax(a))}提交截图: ...

April 4, 2022 · 1 min · jiezi

关于golang:go制作dockerfile并部署到docker

go 代码文件: package mainimport ( "github.com/gin-gonic/gin" "log" "net/http")func main() { r := gin.Default() r.GET("/", handlerindex) r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")}func handlerindex(ctx *gin.Context) { log.Println("hello world handlerindex") ctx.JSON(http.StatusOK, `handlerindex`)}Dockerfile 文件: #源镜像FROM golang:latest#作者MAINTAINER Razil "test2022@163.com"## 在docker的根目录下创立相应的应用目录RUN mkdir -p /www/webapp## 设置工作目录WORKDIR /www/webapp## 把以后(宿主机上)目录下的文件都复制到docker上刚创立的目录下COPY . /www/webapp#将服务器的go工程代码退出到docker容器中#ADD . $GOPATH/src/github.com/mygohttp#go构建可执行文件RUN go build main.go#裸露端口EXPOSE 8080RUN chmod +x mainENTRYPOINT ["./main"]## 启动docker须要执行的文件#CMD go run main.go#最终运行docker的命令#ENTRYPOINT ["./mygohttp"]1.dockerfile 制作dockerfile 制作的源镜像咱们能够在 hub.docker.com 找到 golang官网提供的源镜像,咱们采纳golang:latest。留神dockerfile文件名称必须是Dockerfile,其文件必须再工程目录下。2.执行dockerfile,并生成docker镜像在当前目录下,执行 docker build -t mygohttp .docker images3.docker运行mygohttpdocker run --name mygohttp -p 8080:8080 -d mygohttp此时便启动了docker容器。咱们能够在 http://127.0.0.1:8080/ 拜访该服务。或者docker run -d --name golang -p 8080:8080 --net mynet --ip 172.172.0.10 -v /Volumes/work/www/Go/webapp/:/www/webapp goweb## 这里创立的docker名字为“golan” 名称应该写在 --name 之后;## 网络的设置,我应用了自定义的docker网络设置,见后面的几个对于docker的内容;## 创立共享目录,吧宿主机的“/Volumes/work/www/Go/webapp/” 文件夹同步到到 docker下的 /www/webapp ,这个目录也就是咱们再创立镜像时候设置的工作目录## 应用咱们刚刚创立的镜像"goweb"## 映射宿主机的 8080端口到docker上的8080端口###go语言工程制作yaml文件,并部署到kubernetes1.1.制作yaml文件首先基于现有的docker镜像,制作出deployment和service。2.部署mygohttp服务kubectl create -f kube-mygohttp.yaml此时咱们能够通过 http://10.7.28.129:30836 拜访。通过 kubectl logs -f 实时查看日志。罕用docker命令: ...

April 3, 2022 · 1 min · jiezi

关于golang:Fuzzing-一文读懂Go-Fuzzing使用和原理

背景Go 1.18除了引入泛型(generics)这个重大设计之外,Go官网团队在Go 1.18工具链里还引入了fuzzing含糊测试。 Go fuzzing的次要开发者是Katie Hockman, Jay Conrod和Roland Shoemaker。 编者注:Katie Hockman已于2022.02.19从Google到职,Jay Conrod也于2021年10月来到Google。 什么是FuzzingFuzzing中文含意是含糊测试,是一种自动化测试技术,能够随机生成测试数据集,而后调用要测试的性能代码来查看性能是否合乎预期。 含糊测试(fuzz test)是对单元测试(unit test)的补充,并不是要代替单元测试。 单元测试是查看指定的输出失去的后果是否和预期的输入后果统一,测试数据集比拟无限。 含糊测试能够生成随机测试数据,找出单元测试笼罩不到的场景,进而发现程序的潜在bug和安全漏洞。 Go Fuzzing怎么应用Fuzzing在Go语言里并不是一个全新的概念,在Go官网团队公布Go Fuzzing之前,GitHub上曾经有了相似的含糊测试工具go-fuzz。 Go官网团队的Fuzzing实现借鉴了go-fuzz的设计思维。 Go 1.18把Fuzzing整合到了go test工具链和testing包里。 示例上面举个例子阐明下Fuzzing如何应用。 对于如下的字符串反转函数Reverse,大家能够思考下这段代码有什么潜在问题? // main.gopackage fuzzfunc Reverse(s string) string { bs := []byte(s) length := len(bs) for i := 0; i < length/2; i++ { bs[i], bs[length-i-1] = bs[length-i-1], bs[i] } return string(bs)}编写Fuzzing含糊测试如果没有发现下面代码的bug,咱们无妨写一个Fuzzing含糊测试函数,来发现下面代码的潜在问题。 Go Fuzzing含糊测试函数的语法如下所示: 含糊测试函数定义在xxx_test.go文件里,这点和Go已有的单元测试(unit test)和性能测试(benchmark test)一样。函数名以Fuzz结尾,参数是* testing.F类型,testing.F类型有2个重要办法Add和Fuzz。Add办法是用于增加种子语料(seed corpus)数据,Fuzzing底层能够依据种子语料数据主动生成随机测试数据。Fuzz办法接管一个函数类型的变量作为参数,该函数类型的第一个参数必须是*testing.T类型,其余的参数类型和Add办法里传入的实参类型保持一致。比方上面的例子里,f.Add(5, "hello")传入的第一个实参是5,第二个实参是hello,对应的是i int和s string。 Go Fuzzing底层会依据Add里指定的种子语料,随机生成测试数据,执行含糊测试。比方上图的例子里,会依据Add里指定的5和hello,随机生产新的测试数据,赋值给i和s,而后一直调用作为f.Fuzz办法的实参,也就是func(t *testing.T, i int, s string){...}这个函数。晓得了上述规定后,咱们来给Reverse函数编写一个如下的含糊测试函数。 ...

April 3, 2022 · 3 min · jiezi

关于golang:Golang力扣Leetcode-205同构字符串哈希

题目:给定两个字符串 s 和 t ,判断它们是否是同构的。 如果 s 中的字符能够按某种映射关系替换失去 t ,那么这两个字符串是同构的。 每个呈现的字符都该当映射到另一个字符,同时不扭转字符的程序。不同字符不能映射到同一个字符上,雷同字符只能映射到同一个字符上,字符能够映射到本人自身。 链接: 力扣Leetcode - 205.同构字符串. 示例 1: 输出:s = "egg", t = "add"输入:true示例 2: 输出:s = "foo", t = "bar"输入:false示例 3: 输出:s = "paper", t = "title"输入:true思路:用两个hash表,最初通过遍历如果 hash1[s[i]] != hash2[t[i]] 输入 false ;其余输入 true 。 Go代码: package mainimport "fmt"func isIsomorphic(s string, t string) bool { if len(s) != len(t) { return false } hash1 := make(map[byte]int) hash2 := make(map[byte]int) for i := 0; i < len(s); i++ { hash1[s[i]] = i hash2[t[i]] = i } for i := 0; i < len(s); i++ { if hash1[s[i]] != hash2[t[i]] { return false } } return true}func main() { s := "egg" t := "add" fmt.Println(isIsomorphic(s, t))}提交截图: ...

April 2, 2022 · 1 min · jiezi

关于golang:带你十天轻松搞定-Go-微服务系列六

序言咱们通过一个系列文章跟大家具体展现一个 go-zero 微服务示例,整个系列分十篇文章,目录构造如下: 环境搭建服务拆分用户服务产品服务订单服务领取服务(本文)RPC 服务 Auth 验证服务监控链路追踪分布式事务冀望通过本系列带你在本机利用 Docker 环境利用 go-zero 疾速开发一个商城零碎,让你疾速上手微服务。 残缺示例代码:https://github.com/nivin-studio/go-zero-mall 首先,咱们来看一下整体的服务拆分图: 6 领取服务(pay)进入服务工作区$ cd mall/service/pay6.1 生成 pay model 模型创立 sql 文件$ vim model/pay.sql编写 sql 文件CREATE TABLE `pay` ( `id` bigint unsigned NOT NULL AUTO_INCREMENT, `uid` bigint unsigned NOT NULL DEFAULT '0' COMMENT '用户ID', `oid` bigint unsigned NOT NULL DEFAULT '0' COMMENT '订单ID', `amount` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '产品金额', `source` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '领取形式', `status` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '领取状态', `create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP, `update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_uid` (`uid`), KEY `idx_oid` (`oid`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;运行模板生成命令$ goctl model mysql ddl -src ./model/pay.sql -dir ./model -c6.2 生成 pay api 服务创立 api 文件$ vim api/pay.api编写 api 文件type ( // 领取创立 CreateRequest { Uid int64 `json:"uid"` Oid int64 `json:"oid"` Amount int64 `json:"amount"` } CreateResponse { Id int64 `json:"id"` } // 领取创立 // 领取详情 DetailRequest { Id int64 `json:"id"` } DetailResponse { Id int64 `json:"id"` Uid int64 `json:"uid"` Oid int64 `json:"oid"` Amount int64 `json:"amount"` Source int64 `json:"source"` Status int64 `json:"status"` } // 领取详情 // 领取回调 CallbackRequest { Id int64 `json:"id"` Uid int64 `json:"uid"` Oid int64 `json:"oid"` Amount int64 `json:"amount"` Source int64 `json:"source"` Status int64 `json:"status"` } CallbackResponse { } // 领取回调)@server( jwt: Auth)service Pay { @handler Create post /api/pay/create(CreateRequest) returns (CreateResponse) @handler Detail post /api/pay/detail(DetailRequest) returns (DetailResponse) @handler Callback post /api/pay/callback(CallbackRequest) returns (CallbackResponse)}运行模板生成命令$ goctl api go -api ./api/pay.api -dir ./api6.3 生成 pay rpc 服务创立 proto 文件$ vim rpc/pay.proto编写 proto 文件syntax = "proto3";package payclient;option go_package = "pay";// 领取创立message CreateRequest { int64 Uid = 1; int64 Oid = 2; int64 Amount = 3;}message CreateResponse { int64 id = 1;}// 领取创立// 领取详情message DetailRequest { int64 id = 1;}message DetailResponse { int64 id = 1; int64 Uid = 2; int64 Oid = 3; int64 Amount = 4; int64 Source = 5; int64 Status = 6;}// 领取详情// 领取详情message CallbackRequest { int64 id = 1; int64 Uid = 2; int64 Oid = 3; int64 Amount = 4; int64 Source = 5; int64 Status = 6;}message CallbackResponse {}// 领取详情service Pay { rpc Create(CreateRequest) returns(CreateResponse); rpc Detail(DetailRequest) returns(DetailResponse); rpc Callback(CallbackRequest) returns(CallbackResponse);}运行模板生成命令$ goctl rpc proto -src ./rpc/pay.proto -dir ./rpc6.4 编写 pay rpc 服务6.4.1 批改配置文件批改 pay.yaml 配置文件$ vim rpc/etc/pay.yaml批改服务监听地址,端口号为0.0.0.0:9003,Etcd 服务配置,Mysql 服务配置,CacheRedis 服务配置Name: pay.rpcListenOn: 0.0.0.0:9003Etcd: Hosts: - etcd:2379 Key: pay.rpcMysql: DataSource: root:123456@tcp(mysql:3306)/mall?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghaiCacheRedis:- Host: redis:6379 Type: node Pass:6.4.2 增加 pay model 依赖增加 Mysql 服务配置,CacheRedis 服务配置的实例化$ vim rpc/internal/config/config.gopackage configimport ( "github.com/tal-tech/go-zero/core/stores/cache" "github.com/tal-tech/go-zero/zrpc")type Config struct { zrpc.RpcServerConf Mysql struct { DataSource string } CacheRedis cache.CacheConf}注册服务上下文 pay model 的依赖$ vim rpc/internal/svc/servicecontext.gopackage svcimport ( "mall/service/pay/model" "mall/service/pay/rpc/internal/config" "github.com/tal-tech/go-zero/core/stores/sqlx")type ServiceContext struct { Config config.Config PayModel model.PayModel}func NewServiceContext(c config.Config) *ServiceContext { conn := sqlx.NewMysql(c.Mysql.DataSource) return &ServiceContext{ Config: c, PayModel: model.NewPayModel(conn, c.CacheRedis), }}6.4.3 增加 user rpc,order rpc 依赖增加 user rpc, order rpc 服务配置$ vim rpc/etc/pay.yamlName: pay.rpcListenOn: 0.0.0.0:9003Etcd: Hosts: - etcd:2379 Key: pay.rpc...UserRpc: Etcd: Hosts: - etcd:2379 Key: user.rpcOrderRpc: Etcd: Hosts: - etcd:2379 Key: order.rpc增加 user rpc, order rpc 服务配置的实例化$ vim rpc/internal/config/config.gopackage configimport ( "github.com/tal-tech/go-zero/core/stores/cache" "github.com/tal-tech/go-zero/zrpc")type Config struct { zrpc.RpcServerConf Mysql struct { DataSource string } CacheRedis cache.CacheConf UserRpc zrpc.RpcClientConf OrderRpc zrpc.RpcClientConf}注册服务上下文 user rpc, order rpc 的依赖$ vim rpc/internal/svc/servicecontext.gopackage svcimport ( "mall/service/order/rpc/orderclient" "mall/service/pay/model" "mall/service/pay/rpc/internal/config" "mall/service/user/rpc/userclient" "github.com/tal-tech/go-zero/core/stores/sqlx" "github.com/tal-tech/go-zero/zrpc")type ServiceContext struct { Config config.Config PayModel model.PayModel UserRpc userclient.User OrderRpc orderclient.Order}func NewServiceContext(c config.Config) *ServiceContext { conn := sqlx.NewMysql(c.Mysql.DataSource) return &ServiceContext{ Config: c, PayModel: model.NewPayModel(conn, c.CacheRedis), UserRpc: userclient.NewUser(zrpc.MustNewClient(c.UserRpc)), OrderRpc: orderclient.NewOrder(zrpc.MustNewClient(c.OrderRpc)), }}6.4.4 增加领取创立逻辑 Create增加依据 oid 查问订单领取记录 PayModel 办法 FindOneByOid$ vim model/paymodel.gopackage model...var ( ... cachePayIdPrefix = "cache:pay:id:" cachePayOidPrefix = "cache:pay:oid:")type ( PayModel interface { Insert(data *Pay) (sql.Result, error) FindOne(id int64) (*Pay, error) FindOneByOid(oid int64) (*Pay, error) Update(data *Pay) error Delete(id int64) error } ...)...func (m *defaultPayModel) FindOneByOid(oid int64) (*Pay, error) { payOidKey := fmt.Sprintf("%s%v", cachePayOidPrefix, oid) var resp Pay err := m.QueryRow(&resp, payOidKey, func(conn sqlx.SqlConn, v interface{}) error { query := fmt.Sprintf("select %s from %s where `oid` = ? limit 1", payRows, m.table) return conn.QueryRow(v, query, oid) }) switch err { case nil: return &resp, nil case sqlc.ErrNotFound: return nil, ErrNotFound default: return nil, err }}......增加领取创立逻辑 ...

April 2, 2022 · 7 min · jiezi

关于golang:gozero源码阅读限流器第四期

go-zero 给咱们提供了两种限流器,而且都是基于 redis 实现的可分布式的 限流器外围文件带正文代码如下,大家能够参阅 计数器限流器 https://github.com/TTSimple/g...令牌桶限流器 https://github.com/TTSimple/g...咱们通过最小化代码来看看限流器的外围思路 繁难计数器算法// 繁难计数器算法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 Test_Counter(t *testing.T) { c := Counter{} c.Set(20, time.Second) reqTime := 2 * time.Second // 总申请工夫 reqNum := 200 // 总申请次数 reqInterval := reqTime / time.Duration(reqNum) // 每次申请距离 var trueCount, falseCount int for i := 0; i < reqNum; i++ { go func() { if c.Allow() { trueCount++ } else { falseCount++ } }() time.Sleep(reqInterval) } fmt.Println("true count: ", trueCount) fmt.Println("false count: ", falseCount)}最终输入 ...

April 1, 2022 · 3 min · jiezi

关于golang:Golang-远程调试工具Delve-安装使用

本地调试是首选,如果能够本地调试,那天然是本地调试最不便。然而本地环境和理论环境很多时候是不一样的,很难在本机搭建出与线上完全相同的环境,比方咱们想调试数据库、调试rpc、调试服务注册发现,这些线上都是现成的,但在咱们本机就很难复制,再比方线上的网络和咱们本机的网络环境是不一样的,这些场景下本地调试均无奈满足咱们的需要,须要把咱们的程序部署在线上环境,能力测试。 之前不晓得近程调试,我都是改好了程序,push到git上,而后到线上的机器pull下来,编译,运行,看日志。。。一方面,每次改完程序想测试一下,很繁琐,另一方面,残缺的开发完一个feature,产生了几十次commit。。。 好在,goland有近程调试性能! 有了近程调试,在goland写完代码,间接同步到远端机器,而后点小虫子按钮调试,而后设断点,就基本上跟本地调试一样不便了。 Golang debug 举荐应用 Delve 工具,我的项目地址:https://github.com/derekparke...1.装置 # git clone https://github.com/derekparker/delve.git# cd delve/cmd/dlv/# go build# go install或者 go get -u github.com/go-delve/delve/cmd/dlv2.调试调试就一句语句: dlv debug main.go # cd helloword/# go mod init# dlv debug main.godlv罕用的命令: The following commands are available: args ------------------------ 打印函数参数. break (alias: b) ------------ 设置断点. breakpoints (alias: bp) ----- 输入流动断点的信息. call ------------------------ 复原过程,注入一个函数调用(还在试验阶段!!) clear ----------------------- 删除断点. clearall -------------------- 删除多个断点. condition (alias: cond) ----- 设置断点条件. config ---------------------- 批改配置参数. continue (alias: c) --------- 运行到断点或程序终止. deferred -------------------- 在提早调用的上下文中执行命令. disassemble (alias: disass) - 反汇编程序. down ------------------------ 将以后帧向下挪动. edit (alias: ed) ------------ 在$DELVE_EDITOR或$EDITOR中关上你所在的地位 exit (alias: quit | q) ------ 退出调试器. frame ----------------------- 设置以后帧,或在不同的帧上执行命令. funcs ----------------------- 打印函数列表. goroutine ------------------- 显示或更改以后goroutine goroutines ------------------ 列举程序goroutines. help (alias: h) ------------- 打印帮忙信息. list (alias: ls | l) -------- 显示源代码. locals ---------------------- 打印局部变量. next (alias: n) ------------- 转到下一个源行. on -------------------------- 在命中断点时执行命令. print (alias: p) ------------ 计算一个表达式. regs ------------------------ 打印CPU寄存器的内容. restart (alias: r) ---------- 重启过程. set ------------------------- 更改变量的值. source ---------------------- 执行蕴含delve命令列表的文件 sources --------------------- 打印源文件列表. stack (alias: bt) ----------- 打印堆栈跟踪信息. step (alias: s) ------------- 单步执行程序. step-instruction (alias: si) 单步执行一条cpu指令. stepout --------------------- 跳出以后函数. thread (alias: tr) ---------- 切换到指定的线程. threads --------------------- 打印每个跟踪线程的信息. trace (alias: t) ------------ 设置跟踪点. types ----------------------- 打印类型列表 up -------------------------- 向上挪动以后帧. vars ------------------------ 打印包变量. whatis ---------------------- 打印表达式的类型.近程调试几个步骤:近程服务器装置 delv近程服务器编译:go build -gcflags "all=-N -l" test1.go ...

April 1, 2022 · 1 min · jiezi

关于golang:golang-protoc环境安装

1.去网站下载 protoc对应的文件https://github.com/protocolbu... 下载解压之后,protoc.exe 放到 go 对应的bin 目录下. 2.从github上下载我的项目到本地,而后编译git clone https://github.com/golang/pro... go build 生成的protoc-gen-go.exe文件也Copy 到go 对应的bin 目录下. 3.如何应用定义 hello.proto 文件: syntax = "proto3"; // 指定proto版本package hello;// protoc -I . --go_out=plugins=grpc:. ./hello.proto//定义包名称option go_package = "hello";// 定义Hello服务service Hello{ // 定义SayHello办法 rpc SayHello(HelloRequest) returns (HelloResponse){}}// HelloRequest 申请构造message HelloRequest{ string name = 1;}// HelloResponse 响应构造message HelloResponse{ string message = 1;}在命令行cd 到该目录,执行命令protoc --go_out=. hello.proto生成了hello.pb.go文件

March 31, 2022 · 1 min · jiezi

关于golang:golang-jsonstringstruct相互转换

package main import ( "encoding/json" "fmt" "os") type ConfigStruct struct { Host string `json:"host"` Port int `json:"port"` AnalyticsFile string `json:"analytics_file"` StaticFileVersion int `json:"static_file_version"` StaticDir string `json:"static_dir"` TemplatesDir string `json:"templates_dir"` SerTcpSocketHost string `json:"serTcpSocketHost"` SerTcpSocketPort int `json:"serTcpSocketPort"` Fruits []string `json:"fruits"`} type Other struct { SerTcpSocketHost string `json:"serTcpSocketHost"` SerTcpSocketPort int `json:"serTcpSocketPort"` Fruits []string `json:"fruits"`} func main() { jsonStr := `{"host": "http://localhost:9090","port": 9090,"analytics_file": "","static_file_version": 1,"static_dir": "E:/Project/goTest/src/","templates_dir": "E:/Project/goTest/src/templates/","serTcpSocketHost": ":12340","serTcpSocketPort": 12340,"fruits": ["apple", "peach"]}` //json str 转map var dat map[string]interface{} if err := json.Unmarshal([]byte(jsonStr), &dat); err == nil { fmt.Println("==============json str 转map=======================") fmt.Println(dat) fmt.Println(dat["host"]) } //json str 转struct var config ConfigStruct if err := json.Unmarshal([]byte(jsonStr), &config); err == nil { fmt.Println("================json str 转struct==") fmt.Println(config) fmt.Println(config.Host) } //json str 转struct(部份字段) var part Other if err := json.Unmarshal([]byte(jsonStr), &part); err == nil { fmt.Println("================json str 转struct==") fmt.Println(part) fmt.Println(part.SerTcpSocketPort) } //struct 到json str if b, err := json.Marshal(config); err == nil { fmt.Println("================struct 到json str==") fmt.Println(string(b)) } //map 到json str fmt.Println("================map 到json str=====================") enc := json.NewEncoder(os.Stdout) enc.Encode(dat) //array 到 json str arr := []string{"hello", "apple", "python", "golang", "base", "peach", "pear"} lang, err := json.Marshal(arr) if err == nil { fmt.Println("================array 到 json str==") fmt.Println(string(lang)) } //json 到 []string var wo []string if err := json.Unmarshal(lang, &wo); err == nil { fmt.Println("================json 到 []string==") fmt.Println(wo) }}转发自@阿修罗王 的答复 ...

March 31, 2022 · 1 min · jiezi

关于golang:IO-密集型服务-耗时优化

背景我的项目背景Feature 服务作为特色服务,产出特色数据供上游业务应用。服务压力:高峰期 API 模块 10wQPS,计算模块 20wQPS。服务本地缓存机制: 计算模块有本地缓存,且命中率较高,最高可达 50% 左右;计算模块本地缓存在每分钟第 0 秒会全副生效,而在此时流量会全副击穿至上游 Codis;Codis 中 Key 名 = 特色名 + 天文格子 Id + 分钟级工夫串; Feature 服务模块图面对问题服务 API 侧存在较重大的 P99 耗时毛刺问题(固定呈现在每分钟第 0-10s),导致上游服务的拜访错误率达到 1‰ 以上,影响到业务指标;指标:解决耗时毛刺问题,将 P99 耗时整体优化至 15ms 以下; API 模块返回上游 P99 耗时图解决方案服务 CPU 优化背景偶尔的一次上线变动中,发现对 Feature 服务来说 CPU 的使用率的高下会较大水平上影响到服务耗时,因而从进步服务 CPU Idle 角度动手,对服务耗时毛刺问题开展优化。 优化通过对 Pprof profile 图的察看发现 JSON 反序列化操作占用了较大比例(50% 以上),因而通过缩小反序列化操作、更换 JSON 序列化库(json-iterator)两种形式进行了优化。 成果收益:CPU idle 晋升 5%,P99 耗时毛刺从 30ms 升高至 20 ms 以下。 ...

March 31, 2022 · 4 min · jiezi

关于golang:Go-118-泛型全面讲解一篇讲清泛型的全部

序2022年3月15日,争议十分大但同时也备受期待的泛型终于随同着Go1.18公布了。 可是因为Go对泛型的反对时间跨度太大,有十分多的以“泛型”为关键字的文章都是在介绍Go1.18之前的旧泛型提案或者设计。而很多设计最终在Go1.18中被废除或产生了更改。并且很多介绍Go1.18泛型的文章(包含官网的)都过于简略,并没对Go的泛型做残缺的介绍,也没让大家意识到这次Go引入泛型给语言减少了多少复杂度(当然也可能单纯是我没搜到更好的文章) 出于这些起因,我决定参考 The Go Programming Language Specification ,写一篇比拟残缺零碎介绍Go1.18 泛型的文章。这篇文章可能是目前介绍Go泛型比拟全面的文章之一了 本文力求能让未接触过泛型编程的人也能较好了解Go的泛型,所以行文可能略显啰嗦。然而请置信我,看完这篇文章你能取得对Go泛型十分全面的理解 1. 所有从函数的形参和实参说起假如咱们有个计算两数之和的函数 func Add(a int, b int) int { return a + b}这个函数很简略,然而它有个问题——无奈计算int类型之外的和。如果咱们想计算浮点或者字符串的和该怎么办?解决办法之一就是像上面这样为不同类型定义不同的函数 func AddFloat32(a float32, b float32) float32 { return a + b}func AddString(a string, b string) string { return a + b}可是除此之外还有没有更好的办法?答案是有的,咱们能够来回顾下函数的 形参(parameter) 和 实参(argument) 这一基本概念: func Add(a int, b int) int { // 变量a,b是函数的形参 "a int, b int" 这一串被称为形参列表 return a + b}Add(100,200) // 调用函数时,传入的100和200是实参咱们晓得,函数的 形参(parameter) 只是相似占位符的货色并没有具体的值,只有咱们调用函数传入实参(argument) 之后才有具体的值。 ...

March 31, 2022 · 13 min · jiezi

关于golang:Golang力扣Leetcode-58-最后一个单词的长度

题目:给你一个字符串 s,由若干单词组成,单词前后用一些空格字符隔开。返回字符串中 最初一个 单词的长度。 单词 是指仅由字母组成、不蕴含任何空格字符的最大子字符串。 链接: 力扣Leetcode - 58. 最初一个单词的长度. 示例 1: 输出:s = "Hello World"输入:5解释:最初一个单词是“World”,长度为5。示例 2: 输出:s = " fly me to the moon "输入:4解释:最初一个单词是“moon”,长度为4。示例 3: 输出:s = "luffy is still joyboy"输入:6解释:最初一个单词是长度为6的“joyboy”。思路:从右往左第一个没有空格的字符开始记录为 i,之后接在 i 的前面,持续向左遍历,遇到第一个空字符为止,i-j 即为最初一位字符长度。 Go代码: package mainimport ( "fmt")func lengthOfLastWord(s string) int { i := len(s) - 1 //从右往左,若始终是空格则始终往左走,到最初一个单词的最初一个字母 for i >= 0 && s[i] == ' ' { i-- } //从i开始,到最初一个单词首字母后面的空格 j := i for j >= 0 && s[j] != ' ' { j-- } //i-j则为最初一个单词的长度 return i - j}func main() { fmt.Println(lengthOfLastWord("Hello World"))}提交截图: ...

March 30, 2022 · 1 min · jiezi

关于golang:gozero源码阅读过载保护第三期

入口源码地址:github.com/zeromicro/go-zero/rest/handler/sheddinghandler.go 在看文章之前能够看看万总的这篇文章《服务自适应降载爱护设计》,文章曾经给咱们介绍很分明了,从根底原理到架构需要再到代码正文,无不细致入微,感激万总。 之前在设计架构的时候对于服务过载爱护只会想到在客户端、网关层来实现,没思考过在服务端也能够达到这种成果,一来波及这种技术的文章较少(可能是我见多识广了),二来服务端不确定的状况比拟多,比方服务器呈现问题,或者其余在同一台服务器运行的软件把服务器间接搞挂,这样在服务端实现过载爱护在某些层面来说鲁棒性可能不太好 ,但在和熔断器联合后,用服务端来实现过载爱护也是荒诞不经的。 咱们来看下过载爱护设计到的几个算法 自旋锁原理问:假如有1个变量lock,2个协程怎么用锁实现lock++,lock的后果最初为2 答: 锁也是1个变量,初值设为0;1个协程将锁原子性的置为1;操作变量lock;操作实现后,将锁原子性的置为0,开释锁。在1个协程获取锁时,另一个协程始终尝试,直到可能获取锁(一直循环),这就是自旋锁。2、自旋锁的毛病 某个协程持有锁工夫长,期待的协程始终在循环期待,耗费CPU资源。 不偏心,有可能存在有的协程等待时间过程,呈现线程饥饿(这里就是协程饥饿) go-zero 自旋锁源码type SpinLock struct { // 锁变量 lock uint32}// Lock locks the SpinLock.func (sl *SpinLock) Lock() { for !sl.TryLock() { // 暂停以后goroutine,让其余goroutine后行运算 runtime.Gosched() }}// TryLock tries to lock the SpinLock.func (sl *SpinLock) TryLock() bool { // 原子替换,0换成1 return atomic.CompareAndSwapUint32(&sl.lock, 0, 1)}// Unlock unlocks the SpinLock.func (sl *SpinLock) Unlock() { // 原子置零 atomic.StoreUint32(&sl.lock, 0)}源码中还应用了 golang 的运行时操作包 runtime runtime.Gosched()暂停以后goroutine,让其余goroutine后行运算 留神:只是暂停,不是挂起。 当工夫片轮转到该协程时,Gosched()前面的操作将主动复原 ...

March 30, 2022 · 2 min · jiezi

关于golang:Golang力扣Leetcode-53最大子数组和动态规划

题目:给你一个整数数组 nums ,请你找出一个具备最大和的间断子数组(子数组起码蕴含一个元素),返回其最大和。 子数组 是数组中的一个间断局部。 链接: 力扣Leetcode - 53.最大子数组和. 示例 1: 输出:nums = [-2,1,-3,4,-1,2,1,-5,4]输入:6解释:间断子数组 [4,-1,2,1] 的和最大,为 6 。示例 2: 输出:nums = [1]输入:1示例 3: 输出:nums = [5,4,-1,7,8]输入:23思路:使用动静布局,遍历数组,以后一个元素大于0,则将其加到以后元素上 Go代码: package mainimport "fmt"func maxSubArray(nums []int) int { max := nums[0] for i := 1; i < len(nums); i++ { if nums[i]+nums[i-1] > nums[i] { nums[i] += nums[i-1] } if nums[i] > max { max = nums[i] } } return max}func main() { a := []int{-2, 1, -3, 4, -1, 2, 1, -5, 4} fmt.Println(maxSubArray(a))}提交截图: ...

March 30, 2022 · 1 min · jiezi

关于golang:zeroexamplebloom

bloom 为咱们展现的是 布隆过滤器

March 30, 2022 · 1 min · jiezi

关于golang:kratos分布式事务实践

背景随着业务的疾速倒退、业务复杂度越来越高,微服务作为最佳解决方案之一,它解耦服务,升高复杂度,减少可维护性的同时,也带来一部分新问题。 当咱们须要跨服务保证数据一致性时,原先的数据库事务力不从心,无奈将跨库、跨服务的多个操作放在一个事务中。这样的利用场景十分多,咱们能够列举出很多: 跨行转账场景,数据不在一个数据库,但须要保障余额扣减和余额减少要么同时胜利,要么同时失败公布文章后,更新文章总数等统计信息。其中公布文章和更新统计信息通常在不同的微服务中微服务化之后的订单零碎出行游览须要在第三方零碎同时定几张票面对这些本地事务无奈解决的场景,咱们须要分布式事务的解决方案,保障跨服务、跨数据库更新数据的一致性。 dtm作为一款十分风行的分布式事务框架,曾经反对了接入多种微服务框架,上面咱们就来着重介绍一下go-kratos如何接入dtm,解决分布式事务的问题 运行一个例子咱们来看一个可运行的例子,而后再看如何本人开发实现一个残缺的分布式事务 上面以etcd作为注册服务中心,能够依照如下步骤运行一个kratos的示例: 配置dtm MicroService: Driver: 'dtm-driver-kratos' # name of the driver to handle register/discover Target: 'discovery://127.0.0.1:2379/dtmservice' # register dtm server to this url EndPoint: 'grpc://localhost:36790'启动etcd # 前提:已装置etcdetcd启动dtm # 请先配置好dtm的数据库go run app/main.go -c conf.yml # conf.yml 为你对应的 dtm 配置文件运行一个kratos的服务 git clone https://github.com/dtm-labs/dtmdriver-clients && cd dtmdriver-clientscd kratos/transmake build && ./bin/trans -conf configs/config.yaml发动一个 kratos 应用 dtm 的事务 # 在 dtmdriver-clients 的目录下cd kratos/app && go run main.go当您在trans的日志中看到 INFO msg=config loaded: config.yaml format: yamlINFO msg=[gRPC] server listening on: [::]:90002022/03/30 09:35:36 transfer out 30 cents from 12022/03/30 09:35:36 transfer in 30 cents to 2那就是事务失常实现了 ...

March 30, 2022 · 1 min · jiezi

关于golang:带你十天轻松搞定-Go-微服务系列五

序言咱们通过一个系列文章跟大家具体展现一个 go-zero 微服务示例,整个系列分十篇文章,目录构造如下: 环境搭建服务拆分用户服务产品服务订单服务(本文)领取服务RPC 服务 Auth 验证服务监控链路追踪分布式事务冀望通过本系列带你在本机利用 Docker 环境利用 go-zero 疾速开发一个商城零碎,让你疾速上手微服务。 残缺示例代码:https://github.com/nivin-studio/go-zero-mall 首先,咱们来看一下整体的服务拆分图: 5 订单服务(order)进入服务工作区$ cd mall/service/order5.1 生成 order model 模型创立 sql 文件$ vim model/order.sql编写 sql 文件CREATE TABLE `order` ( `id` bigint unsigned NOT NULL AUTO_INCREMENT, `uid` bigint unsigned NOT NULL DEFAULT '0' COMMENT '用户ID', `pid` bigint unsigned NOT NULL DEFAULT '0' COMMENT '产品ID', `amount` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '订单金额', `status` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '订单状态', `create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP, `update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_uid` (`uid`), KEY `idx_pid` (`pid`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;运行模板生成命令$ goctl model mysql ddl -src ./model/order.sql -dir ./model -c5.2 生成 order api 服务创立 api 文件$ vim api/order.api编写 api 文件type ( // 订单创立 CreateRequest { Uid int64 `json:"uid"` Pid int64 `json:"pid"` Amount int64 `json:"amount"` Status int64 `json:"status"` } CreateResponse { Id int64 `json:"id"` } // 订单创立 // 订单批改 UpdateRequest { Id int64 `json:"id"` Uid int64 `json:"uid,optional"` Pid int64 `json:"pid,optional"` Amount int64 `json:"amount,optional"` Status int64 `json:"status,optional"` } UpdateResponse { } // 订单批改 // 订单删除 RemoveRequest { Id int64 `json:"id"` } RemoveResponse { } // 订单删除 // 订单详情 DetailRequest { Id int64 `json:"id"` } DetailResponse { Id int64 `json:"id"` Uid int64 `json:"uid"` Pid int64 `json:"pid"` Amount int64 `json:"amount"` Status int64 `json:"status"` } // 订单详情 // 订单列表 ListRequest { Uid int64 `json:"uid"` } ListResponse { Id int64 `json:"id"` Uid int64 `json:"uid"` Pid int64 `json:"pid"` Amount int64 `json:"amount"` Status int64 `json:"status"` } // 订单列表)@server( jwt: Auth)service Order { @handler Create post /api/order/create(CreateRequest) returns (CreateResponse) @handler Update post /api/order/update(UpdateRequest) returns (UpdateResponse) @handler Remove post /api/order/remove(RemoveRequest) returns (RemoveResponse) @handler Detail post /api/order/detail(DetailRequest) returns (DetailResponse) @handler List post /api/order/list(ListRequest) returns (ListResponse)}运行模板生成命令$ goctl api go -api ./api/order.api -dir ./api5.3 生成 order rpc 服务创立 proto 文件$ vim rpc/order.proto编写 proto 文件syntax = "proto3";package orderclient;option go_package = "order";// 订单创立message CreateRequest { int64 Uid = 1; int64 Pid = 2; int64 Amount = 3; int64 Status = 4;}message CreateResponse { int64 id = 1;}// 订单创立// 订单批改message UpdateRequest { int64 id = 1; int64 Uid = 2; int64 Pid = 3; int64 Amount = 4; int64 Status = 5;}message UpdateResponse {}// 订单批改// 订单删除message RemoveRequest { int64 id = 1;}message RemoveResponse {}// 订单删除// 订单详情message DetailRequest { int64 id = 1;}message DetailResponse { int64 id = 1; int64 Uid = 2; int64 Pid = 3; int64 Amount = 4; int64 Status = 5;}// 订单详情// 订单列表message ListRequest { int64 uid = 1;}message ListResponse { repeated DetailResponse data = 1;}// 订单列表// 订单领取message PaidRequest { int64 id = 1;}message PaidResponse {}// 订单领取service Order { rpc Create(CreateRequest) returns(CreateResponse); rpc Update(UpdateRequest) returns(UpdateResponse); rpc Remove(RemoveRequest) returns(RemoveResponse); rpc Detail(DetailRequest) returns(DetailResponse); rpc List(ListRequest) returns(ListResponse); rpc Paid(PaidRequest) returns(PaidResponse);}运行模板生成命令$ goctl rpc proto -src ./rpc/order.proto -dir ./rpc5.4 编写 order rpc 服务5.4.1 批改配置文件批改 order.yaml 配置文件$ vim rpc/etc/order.yaml批改服务监听地址,端口号为0.0.0.0:9002,Etcd 服务配置,Mysql 服务配置,CacheRedis 服务配置Name: order.rpcListenOn: 0.0.0.0:9002Etcd: Hosts: - etcd:2379 Key: order.rpcMysql: DataSource: root:123456@tcp(mysql:3306)/mall?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghaiCacheRedis:- Host: redis:6379 Type: node Pass:5.4.2 增加 order model 依赖增加 Mysql 服务配置,CacheRedis 服务配置的实例化$ vim rpc/internal/config/config.gopackage configimport ( "github.com/tal-tech/go-zero/core/stores/cache" "github.com/tal-tech/go-zero/zrpc")type Config struct { zrpc.RpcServerConf Mysql struct { DataSource string } CacheRedis cache.CacheConf}注册服务上下文 order model 的依赖$ vim rpc/internal/svc/servicecontext.gopackage svcimport ( "mall/service/order/model" "mall/service/order/rpc/internal/config" "github.com/tal-tech/go-zero/core/stores/sqlx")type ServiceContext struct { Config config.Config OrderModel model.OrderModel}func NewServiceContext(c config.Config) *ServiceContext { conn := sqlx.NewMysql(c.Mysql.DataSource) return &ServiceContext{ Config: c, OrderModel: model.NewOrderModel(conn, c.CacheRedis), }}5.4.3 增加 user rpc,product rpc 依赖增加 user rpc, product rpc 服务配置$ vim rpc/etc/order.yamlName: order.rpcListenOn: 0.0.0.0:9002Etcd: Hosts: - etcd:2379 Key: order.rpc ......UserRpc: Etcd: Hosts: - etcd:2379 Key: user.rpcProductRpc: Etcd: Hosts: - etcd:2379 Key: product.rpc增加 user rpc, product rpc 服务配置的实例化$ vim rpc/internal/config/config.gopackage configimport ( "github.com/tal-tech/go-zero/core/stores/cache" "github.com/tal-tech/go-zero/zrpc")type Config struct { zrpc.RpcServerConf Mysql struct { DataSource string } CacheRedis cache.CacheConf UserRpc zrpc.RpcClientConf ProductRpc zrpc.RpcClientConf}注册服务上下文 user rpc, product rpc 的依赖$ vim rpc/internal/svc/servicecontext.gopackage svcimport ( "mall/service/order/model" "mall/service/order/rpc/internal/config" "mall/service/product/rpc/productclient" "mall/service/user/rpc/userclient" "github.com/tal-tech/go-zero/core/stores/sqlx" "github.com/tal-tech/go-zero/zrpc")type ServiceContext struct { Config config.Config OrderModel model.OrderModel UserRpc userclient.User ProductRpc productclient.Product}func NewServiceContext(c config.Config) *ServiceContext { conn := sqlx.NewMysql(c.Mysql.DataSource) return &ServiceContext{ Config: c, OrderModel: model.NewOrderModel(conn, c.CacheRedis), UserRpc: userclient.NewUser(zrpc.MustNewClient(c.UserRpc)), ProductRpc: productclient.NewProduct(zrpc.MustNewClient(c.ProductRpc)), }}5.4.4 增加订单创立逻辑 Create订单创立流程,通过调用 user rpc 服务查问验证用户是否存在,再通过调用 product rpc 服务查问验证产品是否存在,以及判断产品库存是否短缺。验证通过后,创立用户订单,并通过调用 product rpc 服务更新产品库存。 ...

March 30, 2022 · 9 min · jiezi

关于golang:带你十天轻松搞定-Go-微服务系列四

序言咱们通过一个系列文章跟大家具体展现一个 go-zero 微服务示例,整个系列分十篇文章,目录构造如下: 环境搭建服务拆分用户服务产品服务(本文)订单服务领取服务RPC 服务 Auth 验证服务监控链路追踪分布式事务冀望通过本系列带你在本机利用 Docker 环境利用 go-zero 疾速开发一个商城零碎,让你疾速上手微服务。 残缺示例代码:https://github.com/nivin-studio/go-zero-mall 首先,咱们来看一下整体的服务拆分图: 4. 产品服务(product)进入服务工作区$ cd mall/service/product4.1 生成 product model 模型创立 sql 文件$ vim model/product.sql编写 sql 文件CREATE TABLE `product` ( `id` bigint unsigned NOT NULL AUTO_INCREMENT, `name` varchar(255) NOT NULL DEFAULT '' COMMENT '产品名称', `desc` varchar(255) NOT NULL DEFAULT '' COMMENT '产品描述', `stock` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '产品库存', `amount` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '产品金额', `status` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '产品状态', `create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP, `update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;运行模板生成命令$ goctl model mysql ddl -src ./model/product.sql -dir ./model -c4.2 生成 product api 服务创立 api 文件$ vim api/product.api编写 api 文件type ( // 产品创立 CreateRequest { Name string `json:"name"` Desc string `json:"desc"` Stock int64 `json:"stock"` Amount int64 `json:"amount"` Status int64 `json:"status"` } CreateResponse { Id int64 `json:"id"` } // 产品创立 // 产品批改 UpdateRequest { Id int64 `json:"id"` Name string `json:"name,optional"` Desc string `json:"desc,optional"` Stock int64 `json:"stock"` Amount int64 `json:"amount,optional"` Status int64 `json:"status,optional"` } UpdateResponse { } // 产品批改 // 产品删除 RemoveRequest { Id int64 `json:"id"` } RemoveResponse { } // 产品删除 // 产品详情 DetailRequest { Id int64 `json:"id"` } DetailResponse { Id int64 `json:"id"` Name string `json:"name"` Desc string `json:"desc"` Stock int64 `json:"stock"` Amount int64 `json:"amount"` Status int64 `json:"status"` } // 产品详情)@server( jwt: Auth)service Product { @handler Create post /api/product/create(CreateRequest) returns (CreateResponse) @handler Update post /api/product/update(UpdateRequest) returns (UpdateResponse) @handler Remove post /api/product/remove(RemoveRequest) returns (RemoveResponse) @handler Detail post /api/product/detail(DetailRequest) returns (DetailResponse)}运行模板生成命令$ goctl api go -api ./api/product.api -dir ./api4.3 生成 product rpc 服务创立 proto 文件$ vim rpc/product.proto编写 proto 文件syntax = "proto3";package productclient;option go_package = "product";// 产品创立message CreateRequest { string Name = 1; string Desc = 2; int64 Stock = 3; int64 Amount = 4; int64 Status = 5;}message CreateResponse { int64 id = 1;}// 产品创立// 产品批改message UpdateRequest { int64 id = 1; string Name = 2; string Desc = 3; int64 Stock = 4; int64 Amount = 5; int64 Status = 6;}message UpdateResponse {}// 产品批改// 产品删除message RemoveRequest { int64 id = 1;}message RemoveResponse {}// 产品删除// 产品详情message DetailRequest { int64 id = 1;}message DetailResponse { int64 id = 1; string Name = 2; string Desc = 3; int64 Stock = 4; int64 Amount = 5; int64 Status = 6;}// 产品详情service Product { rpc Create(CreateRequest) returns(CreateResponse); rpc Update(UpdateRequest) returns(UpdateResponse); rpc Remove(RemoveRequest) returns(RemoveResponse); rpc Detail(DetailRequest) returns(DetailResponse);}运行模板生成命令$ goctl rpc proto -src ./rpc/product.proto -dir ./rpc4.4 编写 product rpc 服务4.4.1 批改配置文件批改 product.yaml 配置文件$ vim rpc/etc/product.yaml批改服务监听地址,端口号为0.0.0.0:9001,Etcd 服务配置,Mysql 服务配置,CacheRedis 服务配置Name: product.rpcListenOn: 0.0.0.0:9001Etcd: Hosts: - etcd:2379 Key: product.rpcMysql: DataSource: root:123456@tcp(mysql:3306)/mall?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghaiCacheRedis:- Host: redis:6379 Type: node # node能够不写,能够设为cluster # Pass: xxx # 如果有明码4.4.2 增加 product model 依赖增加 Mysql 服务配置,CacheRedis 服务配置的实例化$ vim rpc/internal/config/config.gopackage configimport ( "github.com/tal-tech/go-zero/core/stores/cache" "github.com/tal-tech/go-zero/zrpc")type Config struct { zrpc.RpcServerConf Mysql struct { DataSource string } CacheRedis cache.CacheConf}注册服务上下文 product model 的依赖$ vim rpc/internal/svc/servicecontext.gopackage svcimport ( "mall/service/product/model" "mall/service/product/rpc/internal/config" "github.com/tal-tech/go-zero/core/stores/sqlx")type ServiceContext struct { Config config.Config ProductModel model.ProductModel}func NewServiceContext(c config.Config) *ServiceContext { conn := sqlx.NewMysql(c.Mysql.DataSource) return &ServiceContext{ Config: c, ProductModel: model.NewProductModel(conn, c.CacheRedis), }}4.4.3 增加产品创立逻辑 Create$ vim rpc/internal/logic/createlogic.gopackage logicimport ( "context" "mall/service/product/model" "mall/service/product/rpc/internal/svc" "mall/service/product/rpc/product" "github.com/tal-tech/go-zero/core/logx" "google.golang.org/grpc/status")type CreateLogic struct { ctx context.Context svcCtx *svc.ServiceContext logx.Logger}func NewCreateLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CreateLogic { return &CreateLogic{ ctx: ctx, svcCtx: svcCtx, Logger: logx.WithContext(ctx), }}func (l *CreateLogic) Create(in *product.CreateRequest) (*product.CreateResponse, error) { newProduct := model.Product{ Name: in.Name, Desc: in.Desc, Stock: in.Stock, Amount: in.Amount, Status: in.Status, } res, err := l.svcCtx.ProductModel.Insert(&newProduct) if err != nil { return nil, status.Error(500, err.Error()) } newProduct.Id, err = res.LastInsertId() if err != nil { return nil, status.Error(500, err.Error()) } return &product.CreateResponse{ Id: newProduct.Id, }, nil}4.4.4 增加产品详情逻辑 Detail$ vim rpc/internal/logic/detaillogic.gopackage logicimport ( "context" "mall/service/product/model" "mall/service/product/rpc/internal/svc" "mall/service/product/rpc/product" "github.com/tal-tech/go-zero/core/logx" "google.golang.org/grpc/status")type DetailLogic struct { ctx context.Context svcCtx *svc.ServiceContext logx.Logger}func NewDetailLogic(ctx context.Context, svcCtx *svc.ServiceContext) *DetailLogic { return &DetailLogic{ ctx: ctx, svcCtx: svcCtx, Logger: logx.WithContext(ctx), }}func (l *DetailLogic) Detail(in *product.DetailRequest) (*product.DetailResponse, error) { // 查问产品是否存在 res, err := l.svcCtx.ProductModel.FindOne(in.Id) if err != nil { if err == model.ErrNotFound { return nil, status.Error(100, "产品不存在") } return nil, status.Error(500, err.Error()) } return &product.DetailResponse{ Id: res.Id, Name: res.Name, Desc: res.Desc, Stock: res.Stock, Amount: res.Amount, Status: res.Status, }, nil}4.4.5 增加产品更新逻辑 Update$ vim rpc/internal/logic/updatelogic.gopackage logicimport ( "context" "mall/service/product/model" "mall/service/product/rpc/internal/svc" "mall/service/product/rpc/product" "github.com/tal-tech/go-zero/core/logx" "google.golang.org/grpc/status")type UpdateLogic struct { ctx context.Context svcCtx *svc.ServiceContext logx.Logger}func NewUpdateLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UpdateLogic { return &UpdateLogic{ ctx: ctx, svcCtx: svcCtx, Logger: logx.WithContext(ctx), }}func (l *UpdateLogic) Update(in *product.UpdateRequest) (*product.UpdateResponse, error) { // 查问产品是否存在 res, err := l.svcCtx.ProductModel.FindOne(in.Id) if err != nil { if err == model.ErrNotFound { return nil, status.Error(100, "产品不存在") } return nil, status.Error(500, err.Error()) } if in.Name != "" { res.Name = in.Name } if in.Desc != "" { res.Desc = in.Desc } if in.Stock != 0 { res.Stock = in.Stock } if in.Amount != 0 { res.Amount = in.Amount } if in.Status != 0 { res.Status = in.Status } err = l.svcCtx.ProductModel.Update(res) if err != nil { return nil, status.Error(500, err.Error()) } return &product.UpdateResponse{}, nil}4.4.6 增加产品删除逻辑 Remove$ vim rpc/internal/logic/removelogic.gopackage logicimport ( "context" "mall/service/product/model" "mall/service/product/rpc/internal/svc" "mall/service/product/rpc/product" "github.com/tal-tech/go-zero/core/logx" "google.golang.org/grpc/status")type RemoveLogic struct { ctx context.Context svcCtx *svc.ServiceContext logx.Logger}func NewRemoveLogic(ctx context.Context, svcCtx *svc.ServiceContext) *RemoveLogic { return &RemoveLogic{ ctx: ctx, svcCtx: svcCtx, Logger: logx.WithContext(ctx), }}func (l *RemoveLogic) Remove(in *product.RemoveRequest) (*product.RemoveResponse, error) { // 查问产品是否存在 res, err := l.svcCtx.ProductModel.FindOne(in.Id) if err != nil { if err == model.ErrNotFound { return nil, status.Error(100, "产品不存在") } return nil, status.Error(500, err.Error()) } err = l.svcCtx.ProductModel.Delete(res.Id) if err != nil { return nil, status.Error(500, err.Error()) } return &product.RemoveResponse{}, nil}4.5 编写 product api 服务4.5.1 批改配置文件批改 product.yaml 配置文件$ vim api/etc/product.yaml批改服务地址,端口号为0.0.0.0:8001,Mysql 服务配置,CacheRedis 服务配置,Auth 验证配置Name: ProductHost: 0.0.0.0Port: 8001Mysql: DataSource: root:123456@tcp(mysql:3306)/mall?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghaiCacheRedis:- Host: redis:6379 Type: node # node能够不写,能够设为cluster # Pass: xxx # 如果有明码Auth: AccessSecret: uOvKLmVfztaXGpNYd4Z0I1SiT7MweJhl AccessExpire: 864004.5.2 增加 product rpc 依赖增加 product rpc 服务配置$ vim api/etc/product.yamlName: ProductHost: 0.0.0.0Port: 8001...ProductRpc: Etcd: Hosts: - etcd:2379 Key: product.rpc增加 product rpc 服务配置的实例化$ vim api/internal/config/config.gopackage configimport ( "github.com/tal-tech/go-zero/rest" "github.com/tal-tech/go-zero/zrpc")type Config struct { rest.RestConf Auth struct { AccessSecret string AccessExpire int64 } ProductRpc zrpc.RpcClientConf}注册服务上下文 product rpc 的依赖$ vim api/internal/svc/servicecontext.gopackage svcimport ( "mall/service/product/api/internal/config" "mall/service/product/rpc/productclient" "github.com/tal-tech/go-zero/zrpc")type ServiceContext struct { Config config.Config ProductRpc productclient.Product}func NewServiceContext(c config.Config) *ServiceContext { return &ServiceContext{ Config: c, ProductRpc: productclient.NewProduct(zrpc.MustNewClient(c.ProductRpc)), }}4.5.3 增加产品创立逻辑 Create$ vim api/internal/logic/createlogic.gopackage logicimport ( "context" "mall/service/product/api/internal/svc" "mall/service/product/api/internal/types" "mall/service/product/rpc/product" "github.com/tal-tech/go-zero/core/logx")type CreateLogic struct { logx.Logger ctx context.Context svcCtx *svc.ServiceContext}func NewCreateLogic(ctx context.Context, svcCtx *svc.ServiceContext) CreateLogic { return CreateLogic{ Logger: logx.WithContext(ctx), ctx: ctx, svcCtx: svcCtx, }}func (l *CreateLogic) Create(req types.CreateRequest) (resp *types.CreateResponse, err error) { res, err := l.svcCtx.ProductRpc.Create(l.ctx, &product.CreateRequest{ Name: req.Name, Desc: req.Desc, Stock: req.Stock, Amount: req.Amount, Status: req.Status, }) if err != nil { return nil, err } return &types.CreateResponse{ Id: res.Id, }, nil}4.5.4 增加产品详情逻辑 Detail$ vim api/internal/logic/detaillogic.gopackage logicimport ( "context" "mall/service/product/api/internal/svc" "mall/service/product/api/internal/types" "mall/service/product/rpc/product" "github.com/tal-tech/go-zero/core/logx")type DetailLogic struct { logx.Logger ctx context.Context svcCtx *svc.ServiceContext}func NewDetailLogic(ctx context.Context, svcCtx *svc.ServiceContext) DetailLogic { return DetailLogic{ Logger: logx.WithContext(ctx), ctx: ctx, svcCtx: svcCtx, }}func (l *DetailLogic) Detail(req types.DetailRequest) (resp *types.DetailResponse, err error) { res, err := l.svcCtx.ProductRpc.Detail(l.ctx, &product.DetailRequest{ Id: req.Id, }) if err != nil { return nil, err } return &types.DetailResponse{ Id: res.Id, Name: res.Name, Desc: res.Desc, Stock: res.Stock, Amount: res.Amount, Status: res.Status, }, nil}4.5.5 增加产品更新逻辑 Update$ vim api/internal/logic/updatelogic.gopackage logicimport ( "context" "mall/service/product/api/internal/svc" "mall/service/product/api/internal/types" "mall/service/product/rpc/product" "github.com/tal-tech/go-zero/core/logx")type UpdateLogic struct { logx.Logger ctx context.Context svcCtx *svc.ServiceContext}func NewUpdateLogic(ctx context.Context, svcCtx *svc.ServiceContext) UpdateLogic { return UpdateLogic{ Logger: logx.WithContext(ctx), ctx: ctx, svcCtx: svcCtx, }}func (l *UpdateLogic) Update(req types.UpdateRequest) (resp *types.UpdateResponse, err error) { _, err = l.svcCtx.ProductRpc.Update(l.ctx, &product.UpdateRequest{ Id: req.Id, Name: req.Name, Desc: req.Desc, Stock: req.Stock, Amount: req.Amount, Status: req.Status, }) if err != nil { return nil, err } return &types.UpdateResponse{}, nil}4.5.6 增加产品删除逻辑 Remove$ vim api/internal/logic/removelogic.gopackage logicimport ( "context" "mall/service/product/api/internal/svc" "mall/service/product/api/internal/types" "mall/service/product/rpc/product" "github.com/tal-tech/go-zero/core/logx")type RemoveLogic struct { logx.Logger ctx context.Context svcCtx *svc.ServiceContext}func NewRemoveLogic(ctx context.Context, svcCtx *svc.ServiceContext) RemoveLogic { return RemoveLogic{ Logger: logx.WithContext(ctx), ctx: ctx, svcCtx: svcCtx, }}func (l *RemoveLogic) Remove(req types.RemoveRequest) (resp *types.RemoveResponse, err error) { _, err = l.svcCtx.ProductRpc.Remove(l.ctx, &product.RemoveRequest{ Id: req.Id, }) if err != nil { return nil, err } return &types.RemoveResponse{}, nil}4.6 启动 product rpc 服务提醒:启动服务须要在 golang 容器中启动$ cd mall/service/product/rpc$ go run product.go -f etc/product.yamlStarting rpc server at 127.0.0.1:9001...4.7 启动 product api 服务提醒:启动服务须要在 golang 容器中启动$ cd mall/service/product/api$ go run product.go -f etc/product.yamlStarting server at 0.0.0.0:8001...我的项目地址https://github.com/zeromicro/go-zero ...

March 30, 2022 · 7 min · jiezi