关于golang:千万并发连接下如何保障网络性能

过来几十年互联网呈爆发式的增长,内容的丰盛以及层出不穷的DDoS攻打等,对网络性能提出了极大的挑战,也同样促成了网络基础设施的疾速倒退。运营商的带宽越来越大,CPU/网卡等硬件的性能也会越来越强。但在很长时间内,软件的性能晋升落后于硬件的性能晋升,并重大限度了应用程序的性能,大部分工夫不得不依附堆机器来应答,造成了大量的资源节约和老本进步。 随着软件的一直倒退,在新世纪的第一个10年时,通过多线程和事件驱动(kqueue/epoll等)解决了C10K的问题。然而在第二个10年却不堪重负,亟需新的解决方案来应答网络流量的增长。 比方腾讯云对外提供的HttpDNS服务每隔几个月申请量都会翻倍,对高性能的网络解决和用户态协定栈都有强烈的需要。HttpDNS晚期应用的内核协定栈只能做到单机不到10万QPS的TCP 短连贯服务。随着技术的提高和倒退,如REUSEPORT等,后续内核协定栈也能够做到几十万QPS了,但仍然存在十分大的横向扩大瓶颈。基于这样的瓶颈下,腾讯云迫切需要一个高性能的网络服务框架,所以抉择了通过DPDK+用户态协定栈来进行内核旁路来晋升网络性能。 Robert David Graham 在2013年针对C10M的演讲中,对于如何达到千万并发连贯,最次要的观点就是内核才是妨碍性能晋升的问题,咱们应该绕过内核(kernel by pass,内核旁路)以及大量其它的技术优化,如轮询、零拷贝、Hugepage等。 Linux内核后续引入的eBPF和XDP同样可能大幅晋升网络性能,然而其晋升性能的实质仍然是绕过内核,目前还未能对Intel DPDK生态造成本质的冲击,尤其是对高内核版本和网卡驱动的依赖,重大限度了在企业的应用推广。 在此次演讲之前,相干的技术曾经失去了肯定的利用,如演讲中提到的PF_RING,Netmap,IntelDPDK等数据驱动,腾讯云DNSPod在2012年就曾经实现了相干软硬件的调研选型工作,并最终抉择DPDK(此时尚未开源)实现了新一代的权威DNS服务器达到了单10GE 1100万 QPS的性能,大幅晋升了DNS的惯例解析和抗攻击能力。然而的确直到该演讲呈现后,相干技术才在业界失去了大规模的开发利用,尤其是从中怀才不遇的DPDK,简直成了高性能网络程序的标配。而咱们也是在16年的时候将权威DNS中应用DPDK的网络模块独自抽出来作为一个独立的通用的网络框架,能够复用到多个业务上晋升网络性能,也就是当初的F-Stack。 F-Stack介绍及技术特点F-Stack是一个全用户态的高性能的网络接入开发包,基于DPDK、FreeBSD协定栈、微线程接口等,用户只须要关注业务逻辑,简略的接入F-Stack即可实现高性能的网络服务器。将网络包进行内核旁路到应用层进行解决尽管大幅晋升了网络性能,然而也无奈再应用内核的网络协议栈了,这对4层以下以及简略的UDP 7层利用影响不大,然而对其余的7层利用来说,一个成熟的用户态协定栈是必须的,所以F-Stack就是腾讯云DNSPod给出的计划。 F-Stack是根本残缺的网络编程框架,相当于用胶水粘合了了DPDK网络I/O模块、FreeBSD用户态协定栈、POSIX Like API,异步编程接口、局部下层利用等,供用户接入应用。应用纯C开发(局部第三方组件应用了C++,F-Stack进行了封装),容易上手,但也要求用户有肯定的DPDK应用根底。应用BSD 2-Clause开源协定,对商业应用十分敌对。那对于F-Stack都有哪些技术特点呢?接下来将持续介绍。 多过程架构,轮询模式 这里是F-Stack的一个根本架构,采纳多过程模型,全用户态,每个过程与一个CPU外围、网卡收发队列进行绑定,领有更好的内存局部性,防止缓存生效,且过程外部应用轮询模式,无锁、无调度、无上下文切换。 F-Stack 目前采纳多过程架构,各过程领有本人过程独立的协定栈,利用接口和应用层业务逻辑,躲避了内核的多种性能瓶颈,各个过程间无数据共享,有十分好的横向扩大能力。 DPDK 开发套件DPDK是宽泛应用的数据立体开发套件,此处不再对其自身进行过多介绍。 F-Stack对DPDK版本的选用上除了初始开源版本应用了16.07版本之外,很快降级并始终放弃应用DPDK的LTS版本(xx.11)版本,但个别会在最新的LTS版本公布之后数个月在dev分支进行降级反对,并在更晚之后的工夫(个别1年左右)正式公布,如目前F-Stack主力稳固的1.20和1.21版本别离应用了DPDK 18.11.x和19.11.x版本,在开发分支中则反对了20.11.x版本。 FreeBSD 协定栈F-Stack对于选用FreeBSD协定栈进行用户态移植,背地其实是有过很多的思考和尝试的,此处仅列觉几个FreeBSD协定栈的长处,更多信息能够通过前面的 F-Stack 背景故事进行理解。 协定栈功能完善,且有大量工具能够对网络进行调试剖析,如sysctl、ifconfig、netstat、netgraph、ipfw、ndp等。能够跟进社区的改良,无需本人开发保护,有原始用户态移植可供参考,大幅缩小工作量,见libplebnet和libuinet。相比Linux的协定栈实现简单,FreeBSD的代码更清晰易懂;Linux遵循GPL协定开源,可能会限度局部用户的应用。F-Stack 目前公布版本均基于 FreeBSD releng 11.0 版本,并移植了局部后续版本的patch,功能完善但也冗余(去除了局部模块未编译进F-Stack,如SCTP,IPSEC等),调试剖析工具欠缺,运行稳固。后续则会降级到 FreeBSD releng 13.0 版本,并将继续跟进社区的重大改良。 POSIX 兼容接口F-Stack提供了POSIX like接口,前缀为“ff_”,如“ff_socket”“ff_bind”等,并提供了“ff_kqueue”事件驱动接口并同时基于kqueue封装了“ff_epoll”接口,除“ff_epoll”接口的应用上与linux零碎接口略有区别外,其余接口用法齐全兼容,现有程序能够做到简略改变即可接入。 须要留神的是,尽管接口用法齐全兼容,然而因为很多标记位在Linux和FreeBSD零碎的定义并不相同,F-Stack接口外部会进行定义的转换,然而并不能保障100%反对,尤其是后续新增的标记定义,也须要继续进行更新保护。 POSIX like接口对原有利用的移植是敌对的,并且应用上也比拟平安,然而因为波及到内存拷贝,所以性能上并不能达到最优,后续 F-Stack 也会提供一套独立的零拷贝 API 供有须要的用户选用。 微线程框架F-Stack 应用程序必须应用异步模式接口进行编程,但也同时提供了微线程(协程)框架,能够供用户进行同步编程,异步执行。 微线程框架应用了同为腾讯开源的 MSEC 中的一部分 micro_thread,须要特地留神的是微线程模块的开源协定是GPL-2.0,并不是F-Stack 次要的必须外围模块,对 F-Stack 主体开源协定并无影响,然而如果用户以 micro_thread 模块进行利用开发,则需关注开源协定可能造成的影响。 利用移植F-Stack目前是提供 lib 库接入的形式,须要与业务应用程序一起编译打包,并间接提供了曾经移植好的 Nginx 和 Redis 利用供用户间接应用。 ...

November 9, 2021 · 2 min · jiezi

关于golang:Go语言重新开始Go-Modules-的前世今生与基本使用

2020 年腾讯外部的一份开发者报告显示,Go 语言曾经成为腾讯外部第二大后端开发语言,在腾讯每天有大量的 Go 开发者在做业务和平台开发,大量的团队和我的项目应用也暴露出一些问题,随着 Go Modules 的呈现,相似于外部自签发证书、平安审计等这些问题也逐步失去解决。 笔者在腾讯以后负责腾讯云在 Go 编程语言应用上的一些问题, 2021年初开始负责外部 goproxy 服务并推广Go Modules应用,这些技术撑持了腾讯云、微信、腾讯视频、腾讯游戏、腾讯音乐、腾讯会议等明星产品,并与公司外部软件源团队、工蜂团队、TRPC 团队以及各个 CI 团队达成亲密的单干。在本系列文章中,笔者就将由浅入深帮忙大家开始学习和理解 Go Modules。 Golang 开发的模式演进从 Go 出世开始,用户就始终在应用 GOPATH 这个环境变量,随着 Go 语言的疾速倒退和一直壮大,由 GOPATH 引起的编译依赖问题也开始逐步呈现。终于在2019 年, Golang 迎来 10 周年之际,Google Go 团队终于开始把眼光投向了这一随同了 Golang 十年的环境变量。 GOPATH的应用目前在 Go 中存在两种开发模式,GOPATH mode 和 Go modules mode。在 Go modules 之前,Go 开发中的依赖治理应用 GOPATH 开发模式。在 GOPATH 开发模式中,Go 命令应用 GOPATH 环境变量来实现以下几个性能: go install 命令装置二进制库到 $GOBIN, 其默认门路为 $GOPATH/bin。go install 命令装置编译好的包到 $GOPATH/pkg/ 中,例如将 example.com/y/z 装置到 $GOPATH/pkg/example.com/y/z.a。go get 命令下载源码包到 $GOPATH/src/ 中,例如将 example.com/y/z 下载到 $GOPATH/src/example。Go modules的倒退历程GOPATH mode 开发模式是终将要被淘汰的,Go 官网在整个 Go 开发生态系统中增加 package version 这一概念,进而引入 Go modules 开发模式。从 GOPATH mode 开发模式变换到 Go modules 是一个漫长的过程,它曾经经验了数个 Go 的发行版本: ...

November 9, 2021 · 3 min · jiezi

关于golang:Golang-NSQ-消息队列使用实战

网上看了好多,都是抄个官网 README,很多重要的货色不说分明。只好本人钻研了一下。 自己博客,关键词 Less-Bug.com ,欢送关注。 NSQ 的全家桶介绍nsqd:守护过程,客户端通信。默认端口 4150(TCP) 4151(HTTP)nsqlookupd:相当于一个路由器。客户端能够经由它发现生产者、nsqd 播送的话题。一个 nsqlookupd 可能治理一群 nsqd。默认端口::4160(TCP),:4161(HTTP)nsqadmin:在线面板,可能通过浏览器间接拜访。默认端口 :4171从命令行启动能够间接下载二进制文件。开三个终端,别离执行: nsqlookupdnsqd --lookupd-tcp-address=127.0.0.1:4160 --broadcast-address=127.0.0.1nsqadmin --lookupd-http-address=127.0.0.1:4161go-nsq 的应用我封装了一个包: package mqimport ( "encoding/json" "fmt" "time" "github.com/nsqio/go-nsq" "go.uber.org/zap")type MessageQueueConfig struct { NsqAddr string NsqLookupdAddr string SupportedTopics []string}type MessageQueue struct { config MessageQueueConfig producer *nsq.Producer consumers map[string]*nsq.Consumer}func NewMessageQueue(config MessageQueueConfig) (mq *MessageQueue, err error) { zap.L().Debug("New message queue") producer, err := initProducer(config.NsqAddr) if err != nil { return nil, err } consumers := make(map[string]*nsq.Consumer) for _, topic := range config.SupportedTopics { nsq.Register(topic,"default") consumers[topic], err = initConsumer(topic, "default", config.NsqAddr) if err != nil { return } } return &MessageQueue{ config: config, producer: producer, consumers: consumers, }, nil}func (mq *MessageQueue) Run() { for name, c := range mq.consumers { zap.L().Info("Run consumer for " + name) // c.ConnectToNSQLookupd(mq.config.NsqLookupdAddr) c.ConnectToNSQD(mq.config.NsqAddr) }}func initProducer(addr string) (producer *nsq.Producer, err error) { zap.L().Debug("initProducer to " + addr) config := nsq.NewConfig() producer, err = nsq.NewProducer(addr, config) return}func initConsumer(topic string, channel string, address string) (c *nsq.Consumer, err error) { zap.L().Debug("initConsumer to " + topic + "/" + channel) config := nsq.NewConfig() config.LookupdPollInterval = 15 * time.Second c, err = nsq.NewConsumer(topic, channel, config) return}func (mq *MessageQueue) Pub(name string, data interface{}) (err error) { body, err := json.Marshal(data) if err != nil { return } zap.L().Info("Pub " + name + " to mq. data = " + string(body)) return mq.producer.Publish(name, body)}type Messagehandler func(v []byte)func (mq *MessageQueue) Sub(name string, handler Messagehandler) (err error) { zap.L().Info("Subscribe " + name) v, ok := mq.consumers[name] if !ok { err = fmt.Errorf("No such topic: " + name) return } v.AddHandler(nsq.HandlerFunc(func(message *nsq.Message) error { handler(message.Body) return nil })) return}应用示例: ...

November 9, 2021 · 6 min · jiezi

关于golang:golang-nethttp的优雅关机

什么是优雅关机http-server运行过程中,若过程被敞开,那么正在解决的申请可能只被解决了一半就进行了,可能会产生数据的不统一。优雅关机是指: 首先,进行接管新申请;而后,期待队列中的申请被处理完毕;最初,应用程序退出;net/http如何实现优雅关机net/http原生反对优雅关机。 首先,在goroutine中启动http-server: srv := &http.Server{ Addr: ":8090", Handler: r,}go func() { if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { log.Fatal("listen: ", err) } else { log.Println("ListenAndServe break") }}()而后,在main中监听关机信号: SIGTERM: kill的默认信号;SIGINT:kill -2,个别是Ctrl+C的退出;SIGKILL:kill -9,捕捉不到;quit := make(chan os.Signal, 1)signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)<- quit最初,监听到关机信号后,执行优雅关机: 调用srv.Shutdown()执行优雅关机;默认期待所有队列中的申请处理完毕后,才返回;传入timeoutContext,减少超时工夫,超时工夫到后返回;ctx, cancel := context.WithTimeout(context.TODO(), 20*time.Second)defer cancel()if err := srv.Shutdown(ctx); err != nil { log.Fatal("Server shutdown: ", err)}net/http优雅关机的实现原理优雅关机是调用net/http的srv.Shutdown(ctx)实现的,该办法会: 先回绝前面的connection申请;而后再缓缓的解决未处理完毕的申请;当未解决的申请一旦被处理完毕,其connection变成Idle,而后被Close()掉;func (srv *Server) Shutdown(ctx context.Context) error { ... for _, f := range srv.onShutdown { go f() } ... ticker := time.NewTicker(shutdownPollInterval) defer ticker.Stop() for { if srv.closeIdleConns() { //敞开idle连贯 return lnerr } select { case <-ctx.Done(): //ctx到期,如cancel()被调用 return ctx.Err() case <-ticker.C: } }}另外,当调用Shutdown()后,srv.ListenAndServer办法将退出,并返回ErrServerClose谬误。 ...

November 8, 2021 · 1 min · jiezi

关于golang:Golang源码分析Golang如何实现自举-程序入口点五

  依据上一章的内容得悉,其实不同零碎的可执行文件都有本人的格局。只有生成对应的格局后,并且有执行权限就能够执行。   那么问题来了,所说的程序入口点到底是什么?可编译性语言,不同的语言的入口点不一样,大多数的都叫main。那么不能叫其余的吗?main真的是入口点吗?如同有很多问题须要摸索,须要去开掘。   既然这么多问题,就带着问题来看看go1.3的入口点是什么? 1.程序入口点  说到程序入口点,这个其实很容易了解,就是程序启动的开始地址。那么接着之前的文章中生成的demo程序,来看看程序入口点。 1.1 查看程序入口  其实想要查看程序入口点,有很多工具比如说objdump、readelf、gdb都能够查看,然而为了解析程序入口点,还是抉择应用objdump。命令如下: #objdump -f demo<center>图1-1 查看程序信息 </center> demo: file format elf64-x86-64architecture: i386:x86-64, flags 0x00000112:EXEC_P, HAS_SYMS, D_PAGEDstart address 0x0000000000421790  通过命令查看后,能够看到与文件的格局类型是elf64-x86-64、并且程序的入口地址是 0x0000000000421790, 也就是“start address 0x0000000000421790”这段形容,如图1-1所示。 1.2 追踪程序入口  通过1.1大节中得悉程序入口地址为 0x0000000000421790,那么能够持续应用objdump命令持续追踪下程序入口。命令如下: #objdump --disassemble demo |grep 421790 -C 10<center>图1-2 追踪程序入口 </center>  通过命令objdump查看入口点汇编代码,过滤掉查看前后10行。如图1-2所示,程序入口是_rt0_amd64_linux,并不是main。 1.3 大节  从追踪内容来看,其实程序入口并不是想的那样肯定是main,也能够变更为其余函数。   其实有聪慧的人就会想,那么批改入口点到本人的内存地址做肯定的操作在跳转到入口点做到神不知鬼不觉。其实你说到对,这个就是所谓对hook api技术,也就是函数劫持。   不过利用级编程劫持地址并不是想的那样,如果你要从A程序去劫持到B程序是做不到的,因为应用层的程序间都是虚拟地址变更是做不到的。然而也不是齐全做不到,A程序能够往B程序注入动态链接库,通过动态链接库则能够操作内存。内核级劫持则没有那么简单,不过操作不当容易蓝屏。 2.解析程序入口源码  曾经晓得入口是_rt0_amd64_linux,那么来看看到底_rt0_amd64_linux是怎么来的。 2.1 追踪_rt0_amd64_linux  在晓得入口是_rt0_amd64_linux之后,其实能够思考入口必定是编译阶段编译进去的,然而通过上一张得悉6l进行链接的时候对应的把执行构造链接起来,那么执行构造中其实就蕴含入口点。   所以当要晓得入口点来源于时,能够去看链接过程。<center>图2-1 追踪_rt0_amd64_linux </center>  通过图2-1能够得悉,在6l编译时初始化了入口点,并且生成程序,不同的零碎入口点是不一样。go对应的main函数,其实是main.main。main.main之前的其实都是一些runtime的初始化操作,比如说栈大小、内存清理、g0初始化等等。  留神的是runtime.main其实也是一个协程,也就是说main.main也是在协程中运行的。 2.2 源码解析  依据图2-1来解析一下对应go1.3链接过程与运行过程中的源代码。 2.2.1 libinit函数  libinit函数在src/cmd/ld/lib.c中,libinit函数其实是链接程序时,写入程序入口点。代码如下: voidlibinit(void){ char *suffix, *suffixsep; funcalign = FuncAlign; fmtinstall('i', iconv); fmtinstall('Y', Yconv); fmtinstall('Z', Zconv); mywhatsys(); // 取得 goroot, goarch, goos。别离代表go的root目录、go的运行零碎环境、go的运行零碎 // add goroot to the end of the libdir list. suffix = ""; suffixsep = ""; if(flag_installsuffix != nil) { suffixsep = "_"; suffix = flag_installsuffix; } else if(flag_race) { suffixsep = "_"; suffix = "race"; } Lflag(smprint("%s/pkg/%s_%s%s%s", goroot, goos, goarch, suffixsep, suffix)); mayberemoveoutfile(); //如果输入文件存储则删除 cout = create(outfile, 1, 0775); //创立输入文件 if(cout < 0) { diag("cannot create %s: %r", outfile); errorexit(); } if(INITENTRY == nil) { INITENTRY = mal(strlen(goarch)+strlen(goos)+20); if(!flag_shared) { sprint(INITENTRY, "_rt0_%s_%s", goarch, goos); //如果不是动态链接库、则设置入口点 } else { sprint(INITENTRY, "_rt0_%s_%s_lib", goarch, goos); //如果是动态链接库,则设置动态链接库入口点 } } linklookup(ctxt, INITENTRY, 0)->type = SXREF;}2.2.2 main与_rt0_amd64_linux  依据链接办法libinit得悉、goarch参数等于_rt0_amd64_linux、goos参数等于linux,因为应用的是动态编译,则得出INITENTRY等于_rt0_amd64_linux,也就是对应的程序入口点。 ...

November 8, 2021 · 2 min · jiezi

关于golang:golangwasm-环境搭建

golang 装置通过 官网地址 下载。MacOS 也可通过 brew 疾速装置: $ brew install golang$ go versiongo version go1.17.2 darwin/arm64golang 环境测试新建文件 main.go,写入: package mainimport "fmt"func main() { fmt.Println("Hello World!")}执行 go run main.go ,将输入: $ go run main.goHello World!如果启用了 GO MODULES ,则须要应用 go mode init 初始化模块,或设置 GO111MODULE=auto。将 golang 打包为 WASM通常有两种打包形式,一种是 golang 自带的,另外是应用 tinygo。举荐应用 tinygo,因为编译出的 wasm 模块更小。 应用 golang 原生编译 在编译 wasm 模块前,须要设置 golang 穿插编译参数,指标零碎 GOOS=js 和指标架构 GOARCH=wasm,编译 wasm 模块: // macos$ GOOS=js GOARCH=wasm go build -o main.wasm// windows 长期设置 golang 环境参数(仅作用于以后CMD)$ set GOOS=js $ set GOARCH=wasm$ go build -o main.wasm应用 tinygo 编译 ...

November 8, 2021 · 2 min · jiezi

关于golang:Docker-系列docker-学习-三

【Docker 系列】docker 学习 三应用 Dcoker 部署 nginx搜寻 nginx 镜像应用 docker search nginx# docker search nginxNAME DESCRIPTION STARS OFFICIAL AUTOMATEDnginx Official build of Nginx. 15246 [OK]jwilder/nginx-proxy Automated Nginx reverse proxy for docker con… 2053 [OK]richarvey/nginx-php-fpm Container running Nginx + PHP-FPM capable of… 815 [OK]...或者在 dockerhub 上搜寻 nginx,具体的版本和详细信息会更加全面,个别应用官网的 拉取 nginx 镜像拉取 nginx 镜像,咱们这里就拉取最新版本的 nginx # docker pull nginxUsing default tag: latest # 最新版本latest: Pulling from library/nginx # nginx 库33847f680f63: Pull complete #分层下载,后续会具体学习分层的原理dbb907d5159d: Pull complete8a268f30c42a: Pull completeb10cf527a02d: Pull completec90b090c213b: Pull complete1f41b2f2bf94: Pull completeDigest: sha256:8f335768880da6baf72b70c701002b45f4932acae8d574dedfddaf967fc3ac90 # 签名Status: Downloaded newer image for nginx:latestdocker.io/library/nginx:latest # nginx实在下载门路创立并运行容器新建一个容器命名为 nginx1nginx 默认端口是 80,将 docker 容器中的 80 端口映射程 主机中的 8888 端口设置后盾运行 nginx 容器# docker run -d --name nginx1 -p 8888:80 nginx2772a40501571630fb6fc2305f41f7a409299c4d15595ba3dd654d73f2a5e7b6# docker psCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES2772a4050157 nginx "/docker-entrypoint.…" 2 seconds ago Up 2 seconds 0.0.0.0:8888->80/tcp nginx1验证应用 curl 命令,拜访一下 主机的 8888 端口,查看是否拜访 OK ...

November 6, 2021 · 3 min · jiezi

关于golang:URL中的空格加号究竟应该使用何种方式编码

来自公众号:Gopher指北 URL中不能显示地蕴含空格这曾经是一个共识,而空格以何种模式存在,在不同的规范中又不完全一致,以致于不同的语言也有了不同的实现。 rfc2396中明确示意空格应该被编码为%20。 而W3C的规范中却又说空格能够被替换为+或者%20。 老许当场懵逼,空格被替换为+,那+自身只能被编码。既然如此,为什么不间接对空格进行编码呢。当然这只是老许心中的纳闷,以前的背景咱们曾经无奈追溯,已成的事实咱们也无奈扭转。但,空格到底是被替换为+还是20%,+是否须要被编码都是当初的咱们须要直面的问题。 Go罕用的三种URL编码方式作为Gopher最先关注的天然是Go语言自身的实现,因而咱们首先理解一下Go中罕用的三种URL编码方式的异同。 url.QueryEscapefmt.Println(url.QueryEscape(" +Gopher指北"))// 输入:+%2BGopher%E6%8C%87%E5%8C%97应用url.QueryEscape编码时,空格被编码为+,而+自身被编码为%2B。 url.PathEscapefmt.Println(url.PathEscape(" +Gopher指北"))// 输入:%20+Gopher%E6%8C%87%E5%8C%97应用url.PathEscape编码时,空格被编码为20%, 而+则未被编码。 url.Valuesvar query = url.Values{}query.Set("hygz", " +Gopher指北")fmt.Println(query.Encode())// 输入:hygz=+%2BGopher%E6%8C%87%E5%8C%97应用(Values).Encode办法编码时,空格被编码为+,而+自身被编码为%2B,进一步查看(Values).Encode办法的源码知其外部仍旧调用url.QueryEscape函数。而(Values).Encode办法和url.QueryEscape的区别在于前者仅编码query中的key和value,后者会对=、&均进行编码。 对咱们开发者而言,这三种编码方式到底应该应用哪一种,请持续浏览后文置信你能够在前面的文章中找到答案。 不同语言中的实现既然空格和+在Go中的URL编码方式有不同的实现,那在其余语言中是否也存在这样的状况呢,上面以PHP和JS为例。 PHP中的URL编码urlencode echo urlencode(' +Gopher指北');// 输入:+%2BGopher%E6%8C%87%E5%8C%97rawurlencode echo rawurlencode(" +Gopher指北");// 输入:%20%2BGopher%E6%8C%87%E5%8C%97PHP的urlencode和Go的url.QueryEscape函数成果统一,而rawurlencode则将空格和+均进行编码。 JS中的URL编码encodeURI encodeURI(' +Gopher指北')// 输入:%20+Gopher%E6%8C%87%E5%8C%97encodeURIComponent encodeURIComponent(' +Gopher指北')// 输入:%20%2BGopher%E6%8C%87%E5%8C%97JS的encodeURI和Go的url.PathEscape函数成果统一,而encodeURIComponent则将空格和+均进行编码。 咱们应该怎么做更举荐应用url.PathEscape函数编码在前文中曾经总结了Go、PHP和JS对 +Gopher指北的编码操作,上面总结一下其对应的解码操作是否可行的二维表。 编码/解码url.QueryUnescapeurl.PathUnescapeurldecoderawurldecodedecodeURIdecodeURIComponenturl.QueryEscapeYNYNNNurl.PathEscapeNYNYYYYYurlencodeYNYNNNrawurlencodeYYYYYNYencodeURINYNYYYencodeURIComponentYYYYYNY上表中的YY和Y同含意,老许仅以YY示意在Go中举荐应用url.PathEscape进行编码,同时在PHP和JS中别离举荐应用rawurldecode和decodeURIComponent进行解码。 在理论的开发过程中,Gopher肯定会存在须要解码的场景,此时就须要和URL编码方进行沟通以失去适合的形式解码。 对值进行编码那有没有通用的不须要URL编解码的形式呢?毫无疑问是有的!以base32编码为例,其编码字符集为A-Z和数字2-7,此时对值进行base32编码后就无需url编码了。 最初,衷心希望本文可能对各位读者有肯定的帮忙。 本文应用环境别离为PHP 7.3.29、go 1.16.6和js Chrome94.0.4606.71的Console参考 https://www.rfc-editor.org/rf...https://www.w3schools.com/tag...

November 6, 2021 · 1 min · jiezi

关于golang:极客大学云原生训练营JIKE

download:极客大学-云原生训练营說到自學Python,大家通常處於如下幾種狀態:▶▶非計算機小白對靠近前沿技術的第一次覺悟 臨近大四畢業,择業毫無头绪,一個人獨處的時分以至有些惶恐不安,怀疑自我成了很多應屆生的常態。放棄大學四年的學問,接觸一個全新的範畴或是技術,這種拿將來做赌注的人生博弈講真谁都會瑟瑟發抖(富二代可绕行)。各種新興技術的崛起我們既是收益者,又是見證者,面對互聯網時期的機遇,很難做到不爲之所動。 ▶▶我有腦上進,爲何不能自學? 很多人選择自學Python更多的原因是想小額投入,這樣即便學無所獲至多也不會血本無歸。而且自學確實非常合適聰明且高度自律的學生,用加倍的付出和致力升高學習的费用本錢,這種勝利無可厚非。每個人都有屬於自己的時區,其實培训學習也是如此,有些人自學也能學得不錯,有些人有集體的學習氣氛才有更強的求知欲,我們無法消除這種時差,能做的就是倾盡所能讓大家找到合適自己的學習節拍)。 ▶▶聽說會Python的程序員更值錢 爲什麼自學Python?我們隨機看幾個後台知乎用戶的心聲: 關於IT在職人員,業餘時間自學Python的很多從事運維工作。不懂開發的運維路线會越走越窄。特別是要學會Python開發,Python能滿足絕大部分自動化運維的需要,又能做後端C/S架構,又能用web框架疾速開發出高大上的web界面,當妳能獨立做出一套運維自動化係統的時分,妳的價值將失去表現。優秀的規範歷來都沒有極限,特別是身處程序員的行列,不進則退,想要讓自己越老越值錢,不時接受新技術的洗禮才是關键。 言歸正傳,如何自學Python? 學習編程不只是學習语法,需要學習算法(計算思维、處理問題的辦法、編程思緒)。計算思维是運用計算機科學的基础概念去求解問題、設計係統和理解人類的行爲;編程思緒,其實就是計算思维的詳細表現,用语法來表達處理問題的辦法、算法。python有多種編程範式,面向過程,面向對象,函數式編程等。函數笼統、需要控製大的問題化解爲小的問題,每一個小的問題用函數來處理,集成起來大的問題就處理了。面向對象的類笼統,類就是由屬性加辦法構成的對象的蓝圖,會用面向對象的思维建模。自學Python,多看多敲多實戰才是王道,碰到問題,想办法處理,才幹進步。在此,小編特別爲大家引薦3套Python視頻教程,針對基础、進階、高級不同需要的學習者,心愿能對大家有所協助: Python基础全套視頻教程一、Python语言基础:第一個Python程序與數據储存Print&input與變量和運算符字符串與循環中的while佈爾&list與條件循環语句與trutle元組&字符串&字典函數基础裝饰器&偏函數與作用域與異常處置與文件讀寫Os與窗口控製與内存修改與语言 遞歸與時間相關模塊模塊的運用與面向對象思维简介面向對象思维的編程面向對象晋升與收發郵件 Tkinter與銀行係統實戰Tkinter自動化辦公與鼠標键盤模拟Py2與py3的區別和測試正則表達式爬蟲简介與json網络編程進程、線程線程、協程 Mysql Mongodb與redis二、前端基础Html&css基础Html&css晋升 Javascript基础Javascript晋升Javascript進階與轮播和飛機大戰坦克Jquery基础Jquery晋升H5c3基础H5c3晋升Bootstrap與photoshop简單運用三、DjangoDjango基本流程走通Django中的模型Django中的視圖Django中的模板Django的高級運用Django愛鮮蜂項目Git的運用四、Tornado走通tornado基础流程懇求與響應模板和數據庫以及接口的調用次序應用安全同步與異步+運用websocket实现在線聊天Python進階教程:Python開發環境經典教程一、Windowsoffice裝置WindowsPythonMachinelearning漢化pycharmVScodeJpythonPython拓展PythonCGI環境二、LinuxVmware12centOSKalilinuxOraclelinux三、WebWindowsLinux四、機器學習深度學習五、樹莓派設備 Python高級視頻教程:Ipython與NumpyDataframe與SeriesPandas數據處置Pandas數據處置與案例分析Pandas绘圖函數與scipyMatplotlibMatplotlib與城市氣候案例分析

November 5, 2021 · 1 min · jiezi

关于golang:Go业务开发中常用的几个开源库

原文链接:Go业务开发中罕用的几个开源库 前言哈喽,大家好,我是asong。拖更了良久,这周开始更新。 最近总有一些初学Go语言的小伙伴问我在业务开发中个别都应用什么web框架、开源中间件;所以我总结了我在日常开发中应用到的库,这些库不肯定是特地完满的,然而根本能够解决日常工作需要,接下来咱们就来看一下。 GinGin是一个用Go编写的Web框架,它是一个相似于martini但领有更好性能的API框架。根本当初每个Go初学者学习的第一个web框架都是Gin。在网上看到一个对于对各个Go-web框架受欢迎的比照: 咱们能够看到Gin在社区受欢迎排第一,Gin 框架往往是进行 Web 利用开发的首选框架,许多公司都会抉择采纳Gin框架进行二次开发,退出日志,服务发现等性能,像Bilibili 开源的一套 Go 微服务框架 Kratos 就采纳 Gin 框架进行了二次开发。 学习Gin通过他的官网文档就能够很快动手,不过文档是英文的,这个不必放心,我曾翻译了一份中文版,能够到我的公众号后盾获取,回复【gin】即可获取。 github地址:https://github.com/gin-gonic/gin zapzap是uber开源的日志库,抉择zap他有两个劣势: 它十分的快它同时提供了结构化日志记录和printf格调的日志记录大多数日志库根本都是基于反射的序列化和字符串格式化的,这样会导致在日志上占用大量CPU资源,不适用于业务开发场景,业务对性能敏感还是挺高的。zap采纳了不同的办法,它设计了一个无反射、零调配的 JSON 编码器,并且根底 Logger 力求尽可能防止序列化开销和调配。 通过在此基础上构建高级 SugaredLogger,zap 容许用户抉择何时须要计算每次调配以及何时更喜爱更相熟的涣散类型的 API。 zap的基准测试如下: 能够看出zap的效率齐全高于其余日志库,选谁不必我明说了吧!!! github地址:https://github.com/uber-go/zap jsoniter做业务开发离不开json的序列化与反序列化,规范库尽管提供了encoding/json,然而它次要是通过反射来实现的,所以性能耗费比拟大。jsoniter能够解决这个痛点,其是一款快且灵便的 JSON 解析器,具备良好的性能并能100%兼容规范库,咱们能够应用jsoniter代替encoding/json,官网文档称能够比规范库快6倍多,起初Go官网在go1.12版本对 json.Unmarshal 函数应用 sync.Pool 缓存了 decoder,性能较之前的版本有所晋升,所以当初达不到快6倍多。 github地址:https://github.com/json-itera... 对于jsoniter优化原理感兴趣的能够移步这里:http://jsoniter.com/benchmark... gormgorm是一个应用Go语言编写的ORM框架,文档齐全,对开发者敌对,并且反对支流的数据库:MySQL, PostgreSQL, SQlite, SQL Server。 集体感觉应用gorm最大的益处在于它是由国人开发,中文文档齐全,上手很快,目前大多数企业也都在应用gorm。咱们来一下gorm的个性: 全功能 ORM关联 (Has One,Has Many,Belongs To,Many To Many,多态,单表继承)Create,Save,Update,Delete,Find 中钩子办法反对 Preload、Joins 的预加载事务,嵌套事务,Save Point,Rollback To Saved PointContext、预编译模式、DryRun 模式批量插入,FindInBatches,Find/Create with Map,应用 SQL 表达式、Context Valuer 进行 CRUDSQL 构建器,Upsert,数据库锁,Optimizer/Index/Comment Hint,命名参数,子查问复合主键,索引,束缚Auto Migration自定义 Logger灵便的可扩大插件 API:Database Resolver(多数据库,读写拆散)、Prometheus…每个个性都通过了测试的重重考验开发者敌对github地址:https://github.com/go-gorm/gorm ...

November 5, 2021 · 1 min · jiezi

关于golang:Go语言小白也能看懂的context包详解从入门到精通

原文链接:小白也能看懂的context包详解:从入门到精通 前言哈喽,大家好,我是asong。明天想与大家分享context包,通过一年的积淀,从新登程,基于Go1.17.1从源码角度再次剖析,不过这次不同的是,我打算先从入门开始,因为大多数初学的读者都想先晓得怎么用,而后才会关怀源码是如何实现的。 置信大家在日常工作开发中肯定会看到这样的代码: func a1(ctx context ...){ b1(ctx)}func b1(ctx context ...){ c1(ctx)}func c1(ctx context ...)context被当作第一个参数(官网倡议),并且一直透传下去,根本一个我的项目代码中到处都是context,然而你们真的晓得它有何作用吗以及它是如何起作用的吗?我记得我第一次接触context时,共事都说这个用来做并发管制的,能够设置超时工夫,超时就会勾销往下执行,疾速返回,我就单纯的认为只有函数中带着context参数往下传递就能够做到超时勾销,疾速返回。置信大多数初学者也都是和我一个想法,其实这是一个谬误的思维,其勾销机制采纳的也是告诉机制,单纯的透传并不会起作用,比方你这样写代码: func main() { ctx,cancel := context.WithTimeout(context.Background(),10 * time.Second) defer cancel() go Monitor(ctx) time.Sleep(20 * time.Second)}func Monitor(ctx context.Context) { for { fmt.Print("monitor") }}即便context透传下去了,不应用也是不起任何作用的。所以理解context的应用还是很有必要的,本文就先从应用开始,逐渐解析Go语言的context包,上面咱们就开始喽!!! context包的起源与作用看官网博客咱们能够晓得context包是在go1.7版本中引入到规范库中的: <img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c0d332914b0c44ae8706589eaef6ebaa~tplv-k3u1fbpfcp-zoom-1.image" style="zoom:50%;" /> context能够用来在goroutine之间传递上下文信息,雷同的context能够传递给运行在不同goroutine中的函数,上下文对于多个goroutine同时应用是平安的,context包定义了上下文类型,能够应用background、TODO创立一个上下文,在函数调用链之间流传context,也能够应用WithDeadline、WithTimeout、WithCancel 或 WithValue 创立的批改正本替换它,听起来有点绕,其实总结起就是一句话:context的作用就是在不同的goroutine之间同步申请特定的数据、勾销信号以及解决申请的截止日期。 目前咱们罕用的一些库都是反对context的,例如gin、database/sql等库都是反对context的,这样更不便咱们做并发管制了,只有在服务器入口创立一个context上下文,一直透传下去即可。 context的应用创立contextcontext包次要提供了两种形式创立context: context.Backgroud()context.TODO()这两个函数其实只是互为别名,没有差异,官网给的定义是: context.Background 是上下文的默认值,所有其余的上下文都应该从它衍生(Derived)进去。context.TODO 应该只在不确定应该应用哪种上下文时应用;所以在大多数状况下,咱们都应用context.Background作为起始的上下文向下传递。 下面的两种形式是创立根context,不具备任何性能,具体实际还是要依附context包提供的With系列函数来进行派生: func WithCancel(parent Context) (ctx Context, cancel CancelFunc)func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc)func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)func WithValue(parent Context, key, val interface{}) Context这四个函数都要基于父Context衍生,通过这些函数,就创立了一颗Context树,树的每个节点都能够有任意多个子节点,节点层级能够有任意多个,画个图示意一下: ...

November 5, 2021 · 5 min · jiezi

关于golang:最佳实践之Golang错误处理

1、原生错误处理Go 语言通过内置的谬误接口提供了非常简单的错误处理机制。error类型是一个接口类型,这是它的定义:type error interface {    Error() string} 咱们能够在编码中通过实现 error 接口类型来生成错误信息。函数通常在最初的返回值中返回错误信息。应用errors.New 可返回一个错误信息: func Sqrt(f float64) (float64, error) { if f < 0 { return 0, errors.New("math: square root of negative number") } // 实现}在上面的例子中,咱们在调用Sqrt的时候传递的一个正数,而后就失去了non-nil的error对象,将此对象与nil比拟,后果为true,所以fmt.Println(fmt包在解决error时会调用Error办法)被调用,以输入谬误,请看上面调用的示例代码: result, err:= Sqrt(-1)if err != nil { fmt.Println(err)}2、开源error包github.com/pkg/errors包在原生error包根底上减少了以下罕用的性能: 能够打印error的堆栈信息:打印谬误须要%+v能力具体输入应用Wrap或Wrapf,初始化一个error应用errors.WithMessage能够在原来的error根底上再包装一层,蕴含原有error信息errors.Is,用于判断error类型,可依据error类型不同做不同解决errors.As,用于解析error具体应用案例见全局错误处理一节。 3、工程中错误处理3.1 需要整顿自定义error信息,并进行编码整顿 controller层能够判断自定义error类型,最终判断是按info解决,还是按error解决能够打印error初始产生的地位(获取error的调用栈)确认以后零碎定位: 用户,获取TagMessage上游服务,须要错误码映射日志监控、监控TagMessage上面在一个工程化的我的项目中利用github.com/pkg/errors包,残缺实现一套的错误处理机制 3.2 形式一:Map保留错误码与Message的映射3.2.1 定义错误信息新建error_handler.go package error_handleimport ( "github.com/pkg/errors")// 1、自定义error构造体,并重写Error()办法// 谬误时返回自定义构造type CustomError struct { Code int `json:"code"` // 业务码 TagMessage string `json:"message"` // 形容信息}func (e *CustomError) Error() string { return e.TagMessage}// 2、定义errorCodeconst ( // 服务级错误码 ServerError = 10101 TooManyRequests = 10102 ParamBindError = 10103 AuthorizationError = 10104 CallHTTPError = 10105 ResubmitError = 10106 ResubmitMsg = 10107 HashIdsDecodeError = 10108 SignatureError = 10109 // 业务模块级错误码 // 用户模块 IllegalUserName = 20101 UserCreateError = 20102 UserUpdateError = 20103 UserSearchError = 20104 // 受权调用方 AuthorizedCreateError = 20201 AuthorizedListError = 20202 AuthorizedDeleteError = 20203 AuthorizedUpdateError = 20204 AuthorizedDetailError = 20205 AuthorizedCreateAPIError = 20206 AuthorizedListAPIError = 20207 AuthorizedDeleteAPIError = 20208 // 管理员 AdminCreateError = 20301 AdminListError = 20302 AdminDeleteError = 20303 AdminUpdateError = 20304 AdminResetPasswordError = 20305 AdminLoginError = 20306 AdminLogOutError = 20307 AdminModifyPasswordError = 20308 AdminModifyPersonalInfoError = 20309 // 配置 ConfigEmailError = 20401 ConfigSaveError = 20402 ConfigRedisConnectError = 20403 ConfigMySQLConnectError = 20404 ConfigMySQLInstallError = 20405 ConfigGoVersionError = 20406 // 实用工具箱 SearchRedisError = 20501 ClearRedisError = 20502 SearchRedisEmpty = 20503 SearchMySQLError = 20504 // 菜单栏 MenuCreateError = 20601 MenuUpdateError = 20602 MenuListError = 20603 MenuDeleteError = 20604 MenuDetailError = 20605 // 借书 BookNotFoundError = 20701 BookHasBeenBorrowedError = 20702)// 3、定义errorCode对应的文本信息var codeTag = map[int]string{ ServerError: "Internal Server Error", TooManyRequests: "Too Many Requests", ParamBindError: "参数信息有误", AuthorizationError: "签名信息有误", CallHTTPError: "调用第三方 HTTP 接口失败", ResubmitError: "Resubmit Error", ResubmitMsg: "请勿反复提交", HashIdsDecodeError: "ID参数有误", SignatureError: "SignatureError", IllegalUserName: "非法用户名", UserCreateError: "创立用户失败", UserUpdateError: "更新用户失败", UserSearchError: "查问用户失败", AuthorizedCreateError: "创立调用方失败", AuthorizedListError: "获取调用方列表页失败", AuthorizedDeleteError: "删除调用方失败", AuthorizedUpdateError: "更新调用方失败", AuthorizedDetailError: "获取调用方详情失败", AuthorizedCreateAPIError: "创立调用方API地址失败", AuthorizedListAPIError: "获取调用方API地址列表失败", AuthorizedDeleteAPIError: "删除调用方API地址失败", AdminCreateError: "创立管理员失败", AdminListError: "获取管理员列表页失败", AdminDeleteError: "删除管理员失败", AdminUpdateError: "更新管理员失败", AdminResetPasswordError: "重置明码失败", AdminLoginError: "登录失败", AdminLogOutError: "退出失败", AdminModifyPasswordError: "批改明码失败", AdminModifyPersonalInfoError: "批改个人信息失败", ConfigEmailError: "批改邮箱配置失败", ConfigSaveError: "写入配置文件失败", ConfigRedisConnectError: "Redis连贯失败", ConfigMySQLConnectError: "MySQL连贯失败", ConfigMySQLInstallError: "MySQL初始化数据失败", ConfigGoVersionError: "GoVersion不满足要求", SearchRedisError: "查问RedisKey失败", ClearRedisError: "清空RedisKey失败", SearchRedisEmpty: "查问的RedisKey不存在", SearchMySQLError: "查问mysql失败", MenuCreateError: "创立菜单失败", MenuUpdateError: "更新菜单失败", MenuDeleteError: "删除菜单失败", MenuListError: "获取菜单列表页失败", MenuDetailError: "获取菜单详情失败", BookNotFoundError: "书未找到", BookHasBeenBorrowedError: "书曾经被借走了",}func Text(code int) string { return codeTag[code]}// 4、新建自定义error实例化func NewCustomError(code int) error { // 首次调用得用Wrap办法,进行实例化 return errors.Wrap(&CustomError{ Code: code, TagMessage: codeTag[code], }, "")}3.3 自定义Error应用新建测试文件:error_handler_test.go ...

November 5, 2021 · 5 min · jiezi

关于golang:Golang-结构体指针接口实现笔记

对函数来说,参数为 struct、*struct首先定义struct cat如下type Cat struct { name string}并申明如下两个函数func changeName1(c Cat, name string) { c.name = name}func changeName2(c *Cat, name string) { c.name = name}func main() { c := Cat{"aaa"} changeName1(c, "bbb") fmt.Println(c) changeName2(&c, "ccc") fmt.Println(c)}// 输入后果// {aaa}// {ccc}论断: 对于函数而言,参数中 构造体和构造体指针 是齐全两个类型,会产生重载。构造体参数会齐全复制一份,包含构造体外部的字段。构造体指针参数会将指针齐全复制一份,构造体内的字段还是会专用。对办法来说,接收者为struct、 *structfunc (c Cat) sayHello() { fmt.Printf("hello everyone, i am %s", c.name)}func (c Cat) changeName(name string) { fmt.Printf("i am %s, change my name to %s\n", c.name, name) c.name = name fmt.Printf("cat指针:%p, cat.name指针:%p\n", &c, &(c.name))}func (c *Cat) changeName2(name string) { fmt.Printf("i am %s, change my name to %s\n", c.name, name) c.name = name fmt.Printf("cat指针:%p, cat.name指针:%p\n", &c, &(c.name))}func main() { c := Cat{"aaa"} fmt.Printf("cat指针:%p, cat.name指针:%p\n", &c, &(c.name)) (&c).changeName2("bbb") fmt.Println(c)}// 代码输入// cat指针:0xc0000461f0, cat.name指针:0xc0000461f0// i am aaa, change my name to bbb// cat指针:0xc000006030, cat.name指针:0xc0000461f0// {bbb}论断: ...

November 5, 2021 · 1 min · jiezi

关于golang:Golang源码分析Golang如何实现自举-6l的链接过程四

  接到催更的信息,其实还是很快乐。原本忙了很长一段时间为生存奔走,想给本人一个假期。起初脑子里响起了莎士比亚的一句话“如果一年到头如假日,岂不像连日工作那样困乏?”,而后还是决定放下劳碌的生存,持续去摸索。   通过最近一段时间的繁忙,对生存也多了一些从新的意识。人活着不要自觉,不要止步不前,不要高估本人的实力,也不要低估本人的能力,多一些自我扫视,重复review或者在生活上、工作上走的会更远~   一顿感概之后还是要回归主题,上一篇文章说到了go1.3编译应用了6g进行编译,编译成对应.a链接库。那么这一章接着来讲6l,那么持续来讲一下6l的实现过程与原理。 1.可执行文件介绍  在链接的过程中就是为了生成可执行文件,那么可执行文件必定须要固定的格局。在计算机科学中,不同零碎的二进制文件,可执行文件,指标代码、共享库等格局是不同的。  那么简略来介绍集中可执行文件的格局。 1.1 PE文件格式  PE文件格式次要利用与Windows系列零碎,可执行文件索引。说到pe格局有一个比拟出名的黑客社区(看雪),就叫pediy。   PE(Portable Executable)格局,是微软Win32环境可移植可执行文件(如exe、dll、vxd、sys和vdm等)的规范文件格式。PE格局衍生于晚期建设在VAX(R)VMS(R)上的COFF(Common Object File Format)文件格式。PE构造如图1-1所示。 <center>图1-1 PE格局 </center> 1.2 ELF文件格式  ELF文件格式是linux系列零碎,可执行文件索引;ELF格局如图1-2所示。   ELF是UNIX零碎实验室(USL)作为应用程序二进制接口(Application Binary Interface,ABI)而开发和公布的,也是Linux的次要可执行文件格局。1999年,被86open我的项目选为x86架构上的类Unix操作系统的二进制文件规范格局,用来取代COFF。因其可扩展性与灵活性,也可利用在其它处理器、计算机系统架构的操作系统上。<center>图1-2 ELF格局 </center> 1.3 mach-o文件格式  mach-o是mac系列零碎的可执行文件格局,苹果零碎是基于FreeBSD的,属于unix-like操作系统;   Mach-O已经为大部分基于Mach外围的操作系统所应用。NeXTSTEP,Darwin和Mac OS X等零碎应用这种格局作为其原生可执行文件,库和指标代码的格局。而同样应用GNU Mach作为其微内核的GNU Hurd零碎则应用ELF而非Mach-O作为其规范的二进制文件格式。 1.4 大节揭示   其实文件格式在很多场景都会利用到,比如说常常听到的蠕虫病毒,还有外挂编程,还有软件查杀等等都会利用到。简略说,就拿PE构造来说,比方通过PE晓得程序入口点,其实入口点个别都是代码段。那么在入口点动静注入代码,这样注入的代码段能做到复制,这就是一个蠕虫病毒的原理。    当然除了这些以外什么对程序加壳,加密,加花都会波及到文件格式。    这边有一篇介绍文件构造比拟好的文章: https://blog.csdn.net/abc_123... 2.Inferno介绍  6l除了应用plan9之外还应用了Inferno零碎库。   Inferno 是一个分布式操作系统,最后由贝尔实验室开发,但当初由 Vita Nuova 作为自由软件开发和保护。应用 Inferno 的并发编程语言 Limbo 编写的应用程序被编译为其可移植虚拟机代码 (Dis),以便在 Inferno 提供的可移植环境中的网络上的任何地位运行。不同寻常的是,该环境的外观和行为就像一个残缺的操作系统。   Inferno 以相似文件的名称层次结构示意服务和资源。程序仅应用文件操作关上、读/写和敞开来拜访它们。“文件”不仅仅是存储的数据,还代表设施、网络和协定接口、动静数据源和服务。该办法对立并为所有系统资源提供根本的命名、构造和访问控制机制。繁多的文件服务协定(与 Plan 9 的 9P 雷同)使所有这些资源都能够通过网络以对立的形式导入或导出,与地位无关。应用程序只是将它须要的资源附加到它本人的每个过程名称层次结构(“命名空间”)。   Inferno 能够在各种 ARM、PowerPC、SPARC 和 x86 平台上“本机”运行,也能够在现有操作系统(包含 AIX、FreeBSD、IRIX、Linux、MacOS X、Plan 9 和 Solaris)下“托管”,再次在各种平台上运行处理器类型。   这个 Bitbucket 我的项目包含根本应用程序的源代码、Inferno 自身(托管和本机)、所有支持软件,包含本机编译器套件、根本可执行文件和反对文件。 ...

November 4, 2021 · 1 min · jiezi

关于golang:JuiceFS-CSI-Driver-的最佳实践

文章依据 Juicedata 工程师朱唯唯,在云原生 Meetup 杭州站所作主题演讲《JuiceFS CSI Driver 的最佳实际》整顿而成。 大家好,我是来自 Juicedata 的朱唯唯,当初次要负责 JuiceFS CSI Driver 方面的开发,很快乐明天有这个机会跟大家做一个分享和交换,我明天分享的题目是 “JuiceFS CSI Driver 的最佳实际”。次要会从以下几个方面给大家做一个分享: Kubernetes 存储计划如何在 Kubernetes 中应用 JuiceFSJuiceFS CSI Driver 架构设计实际Kubernetes 存储计划在 Kubernetes 外面对存储有三个概念,第一个是 PV,也就是长久卷,代表的是集群中的一份存储,能够定义存储的类型、大小等,比方指定它是哪一种类型, NFS 或 GlusterFS ,也能够指定它是 CSI 的。第二个概念是 PVC,长久卷申明,代表的是 Pod 应用存储的一份申请,Pod 不间接应用 PV 而是通过 PVC 来应用 PV。另一个是 StorageClass,长久卷类型,代表的是集群中的存储类型,它不定义存储的大小,只定义类型,在应用的时候 Kubernetes 会依据 StorageClass 主动创立 PV。以上就是 Kubernetes 对 Pod 应用存储所定义的三种资源。 apiVersion: v1 aversion: v1 apiVersion: v1kind: PersistentVolume kind: PersistentVolumeClaim kind: Podmetadata: metadata: metadata: name: pv0001 name: myclaim name: pv-recycler labels: spec: spec: pv: pv0001 accessModes containers:spec: - ReadWriteMany - name: pv-recycler capacity: volumeMode: Filesystem image: "nginx" storage: 5Gi resources volumeMounts: volumeMode: Filesystem requests: - name: my-volume accessModes: storage: 5Gi mountPath: /root/data - ReadWriteMany selector: volumes: hostPath: matchLabels: - name: my-volume path: /root/data/test pv: pv0001 persistentVolumeClaim: claimName: myclaim咱们再来看一下在 Kubernetes 中 Pod 怎么应用存储,次要有两种形式,第一种是动态存储,就是 PV 加 PVC 的形式,能够看上图的几个 yaml 文件,第一个就是 PV 的 yaml 文件。个别由系统管理员先在集群中创立一份 PV,而后在应用的时候创立一个 PVC ,指定应用哪个 PV,然而一个 PV 只能被一个 Pod 应用,每当有新的 Pod 须要应用存储时,系统管理员也要创立相应的 PV,并在 PV 外面是指定它有多大的存储量,用什么样的拜访形式以及它是什么类型的,文中给进去的例子就是 hostPath,当然也能够在这里指定它为 Kubernetes 内置的一些存储类型,比方 NFS 、CSI,如果是 CSI 的话,那就须要咱们第三方去实现对于 CSI 的一个插件。 ...

November 4, 2021 · 4 min · jiezi

关于golang:go内存队列的实现-list-VS-slice

golang中没有队列这种数据结构,通常须要本人实现,常见的能够通过list或slice实现。 go队列的实现形式list是"container/list"中的数据结构,用双向链表实现,能够用来做队列: //入队func (l *List) PushBack(v interface{}) *Element//出队:先Front()获得头,而后Remove()删除func (l *List) Front() *Elementfunc (l *List) Remove(e *Element) interface{}slice实现队列的形式: var s []objs = append(s, obj) //入队s = s[1:] //出队benchmark测试比拟benchmark测试代码: 队列中存入object对象 type EventMsg struct { Id string Msg string}func BenchmarkQueue_ListObject(b *testing.B) { var l = list.New() for i := 0; i < b.N; i++ { l.PushBack(EventMsg{ Id: strconv.Itoa(i), Msg: "1:abcdefghijklmnopqrstuvwxyz-abcdefghijklmnopqrstuvwxyz-abcdefghijklmnopqrstuvwxyz-abcdefghijklmnopqrstuvwxyz", }) l.PushBack(EventMsg{ Id: strconv.Itoa(i), Msg: "1:opqrstuvwxyzabcdefghijklmn-opqrstuvwxyzabcdefghijklmn-opqrstuvwxyzabcdefghijklmn-opqrstuvwxyzabcdefghijklmn", }) l.Remove(l.Front()) }}func BenchmarkQueue_SliceObject(b *testing.B) { var q []EventMsg for i := 0; i < b.N; i++ { q = append(q, EventMsg{ Id: strconv.Itoa(i), Msg: "1:abcdefghijklmnopqrstuvwxyz-abcdefghijklmnopqrstuvwxyz-abcdefghijklmnopqrstuvwxyz-abcdefghijklmnopqrstuvwxyz", }) q = append(q, EventMsg{ Id: strconv.Itoa(i), Msg: "1:opqrstuvwxyzabcdefghijklmn-opqrstuvwxyzabcdefghijklmn-opqrstuvwxyzabcdefghijklmn-opqrstuvwxyzabcdefghijklmn", }) q = q[1:] }}benchmark测试代码:队列中存入Object指针对象 ...

November 3, 2021 · 2 min · jiezi

关于golang:Golang源码分析Golang如何实现自举-g6的编译过程三

  很长的一段时间没有写文章了,因为忙着出版书籍,以及双减的影响有些懈怠。Golang自举这个系列还是接着之前的《【Golang源码剖析】Golang如何实现自举 - dist介绍》一文中持续写,因为linux下go1.3编译*.c文件会调用gcc,编译go文件会调用“/mnt/pkg/tool/linux_amd64/6g”,所以本篇文章次要钻研6g,以及go1.3源码编译过程。 1.g6简略回顾  在go1.3中其实一部分go的源代码还处于c源码实现,其中一部分是go实现,在这种状况下须要采纳混合编译模式,通过dist进行编译。<center>图1-1 dist运行过程 </center>  如图1-1所示,在运行dist编译时进行会有两个分支,一个是编译c文件会应用gcc进行编译,另外编译go会应用lib9库进行编译,也就是应用Plan 9,精确的是plan9port包。 2.Plan 9 与 Plan9port 由来  Plan 9 与 Plan9port 由来参考: http://t.zoukankan.com/ghj197... 2.1 Plan 9  Plan9其实是一个操作系统,由贝尔实验室开发,其次要的负责人是Rob Pike。   Plan 9不是一个很出名的作品,然而它的前身Unix是世人皆知的。而Plan 9是Unix的几位作者在AT&T职业生涯的一件巅峰之作,是被设计来超过Unix的。   实际上,Plan 9在1992年第一次公布时,就同时实现了Google Docs、Dropbox、Github、Remote Desktop等目前很火爆的互联网产品的性能。   Plan 9能做到这些,是因为它把所有内容都注册到一个称为9P的文件系统里。 2.2 Plan9port  Plan9port的作者是Russ Cox, 也是Go的设计者之一Russ Cox是Go和Google Code Search的作者,并且是Rob Pike的师傅。他多年保持用一台老旧的Mac mini搞开发,并且甚为骄傲。   Russ来到AT&T退出Google之后忍不住思念Plan 9,所以把Plan 9上的用户程序——包含Acme——移植到其余操作系统上,称为Plan 9 from User Space。 git 上这个源码在:https://github.com/9fans/plan... Plan 9 from User Space这个名字很有意思——Plan 9这个名字其实来自一部1959年美国科幻电影《Plan 9 from Outer Space》。 Plan 9 from User Space反对以下操作系统: LinuxMac OS XFreeBSD, NetBSD, OpenBSDSunOS参考地址: https://9fans.github.io/plan9... ...

November 3, 2021 · 1 min · jiezi

关于golang:goroutine并发控制

通信共享内存func Test() { ordersInfoApp := make([]orderInfoApp, 0, totalCount) var mux sync.Mutex wg := sync.WaitGroup{} for i := 0; i <= 10; i++ { wg.Add(1) go func(pageIndex int) { // do somethine var ordersInfo orderInfoApp mux.Lock() ordersInfoApp = append(ordersInfoApp, ordersInfo) mux.Unlock() wg.Done() }(i) } wg.Wait()}个别在简略的数据传递下应用 channelfunc Test() { ordersInfoApp := make([]orderInfoApp, 0, totalCount) choi := make(chan orderInfoApp, 10) wg := sync.WaitGroup{} for i := 0; i <= 10; i++ { wg.Add(1) go func(pageIndex int) { // do somethine var ordersInfo orderInfoApp choi <- ordersInfo wg.Done() }(i) } go func() { wg.Wait() close(choi) }() for v := range choi { ordersInfoApp = append(ordersInfoApp, v) }}绝对简单的数据流动状况 ...

November 3, 2021 · 4 min · jiezi

关于golang:Go-日常开发常备第三方库和工具

人不知;鬼不觉写 Go 曾经快一年了,上线了大大小小好几个我的项目;心态也经验了几轮变动。 因为我集体大略前五年工夫写的是 Java ,中途写过一年多的 Python,所以刚接触到 Go 时的感觉如下图: 既没有 Java 的生态,也没有 Python 这么多语法糖。 写到当初的感觉就是: 这里就不探讨这几门语言谁强谁弱了;重点和大家分享下咱们日常开发中所应用到的一些第三方库与工具。 这里我次要将这些库分为两类: 业务开发根底工具开发业务开发首先是业务开发,次要蕴含了 web、数据库、Redis 等。 Gin ⭐️⭐️⭐️⭐️⭐️首先是 Gin,一款 HTTP 框架,应用简略、性能优良、材料泛滥;你还在犹豫抉择哪款框架时,那就抉择它吧,根本没错。 当然和它配套的 github.com/swaggo/gin-swagger swagger 工具也是刚需;利用它能够生成 swagger 文档。 GORM ⭐️⭐️⭐️⭐️⭐️GORM 也没啥好说的,如果你喜爱 orm 的形式操作数据库,那就选它吧;同样的也是应用简略、材料较多。 如果有读写拆散需要,也能够应用 GORM 官网提供的插件 https://github.com/go-gorm/dbresolver ,配合 GORM 应用也是非常简单。 errors ⭐️⭐️⭐️⭐️⭐️Go 语言本身提供的错误处理比较简单,https://github.com/pkg/errors 提供了更弱小的性能,比方: 包装异样包装堆栈等。罕用的有以下 API: // WithMessagef annotates err with the format specifier.func WithMessagef(err error, format string, args ...interface{}) error// WithStack annotates err with a stack trace at the point WithStack was called.func WithStack(err error) errorzorolog ⭐️⭐️⭐️⭐️⭐️Go 里的日志打印库十分多,日志在日常开发中最好就是存在感低;也就是说性能强(不能影响到业务代码)、应用 API 简略。 ...

November 2, 2021 · 3 min · jiezi

关于golang:分布式事务框架dtm141发布支持高级SAGA

更新日志反对并发SAGA反对SAGA超时回滚反对自定义重试距离新性能示例csaga := dtmcli.NewSaga(DtmServer, dtmcli.MustGenGid(DtmServer)). Add(Busi+"/TransOut", Busi+"/TransOutRevert", req). Add(Busi+"/TransIn", Busi+"/TransInRevert", req). EnableConcurrent() // 开启并发 // .AddBranchOrder(1, []int{0}) // 指定分支依赖关系csaga.RetryInterval = 60 // 指定事务分支重试的距离为60scsaga.TimeoutToFail = 1800 // 指定saga事务超过1800s未实现,则进行回滚err := csaga.Submit()上述代码中EnableConcurrent开启SAGA各分支的并发执行,缩短整个SAGA事务的总耗时 上述代码中的正文:AddBranchOrder(1, []int{0}),则能够增加事务分支间的依赖关系。例如这行正文的依赖关系为下标为1的分支,依赖下标为0的分支,只有等0实现后,1能力执行。 跨语言分布式事务管理器DTM是一款golang开发的分布式事务管理器,解决了跨数据库、跨服务、跨语言栈更新数据的一致性问题。 他优雅的解决了幂等、空弥补、悬挂等分布式事务难题,提供了简略易用、高性能、易程度扩大的解决方案。 作者受邀加入中国数据库大会分享多语言环境下分布式事务实际 谁在应用dtmIvydad 常青藤爸爸 Eglass 视咖镜小二 极欧科技 [金数智联]() 亮点极易接入 反对HTTP,提供非常简单的接口,极大升高上手分布式事务的难度,老手也能疾速接入应用简略 开发者不再放心悬挂、空弥补、幂等各类问题,框架层代为解决跨语言 可适宜多语言栈的公司应用。不便go、python、php、nodejs、ruby、c# 各类语言应用。易部署、易扩大 仅依赖mysql,部署简略,易集群化,易程度扩大多种分布式事务协定反对 TCC、SAGA、XA、事务音讯与其余框架比照目前开源的分布式事务框架,暂未看到非Java语言有成熟的框架。而Java语言的较多,有阿里的SEATA、华为的ServiceComb-Pack,京东的shardingsphere,以及himly,tcc-transaction,ByteTCC等等,其中以seata利用最为宽泛。 上面是dtm和seata的次要个性比照: 个性DTMSEATA备注反对语言Go、Java、python、php、c#...Javadtm可轻松接入一门新语言异样解决子事务屏障主动解决手动解决dtm解决了幂等、悬挂、空弥补TCC事务✓✓ XA事务✓✓ AT事务倡议应用XA✓AT与XA相似,性能更好,但有脏回滚SAGA事务并发模式状态机简单模式 事务音讯✓✗dtm提供相似rocketmq的事务音讯单服务多数据源✓✗ 通信协议HTTP、gRPCdubbo等协定,无HTTPdtm对云原生更加敌对从下面比照的个性来看,如果您的语言栈蕴含了Java之外的语言,那么dtm是您的首选。如果您的语言栈是Java,您也能够抉择接入dtm,应用子事务屏障技术,简化您的业务编写。 性能测试报告教程与文档如果您感觉yedf/dtm不错,或者对您有帮忙,请赏颗星吧!

November 1, 2021 · 1 min · jiezi

关于golang:alertmanager-源码分析一-告警的处理

上篇说到告警曾经写入到内存构造中 AlertsProvider 中,并且 Dispatcher 通过订阅 AlertsProvider 获取一个 chan,可能实时读到新写入的 alert。 Dispatcher 的作用就是把 alert 分派给正确的 route 来解决告警,route 是一个树状构造,每个节点有 Matchers 用于判断和以后正在解决的 alert 是否匹配,匹配就对这个 alert 利用这个 route 上的规定,先看看 route 构造: type Route struct { parent *Route Routes []*Route RouteOpts RouteOpts // alert 就是由 alert.LabelSet 和 Route.Matchers 两个是否匹配来决定是否应用这个 Route 来解决 Matchers labels.Matchers // 以后层呈现一个 route 匹配 alert 后,是否在同级上持续应用这个 alert 尝试匹配其它 route Continue bool}// RouteOpts 保留了跟以后 Route 绑定的一些规定,alert 匹配后就会应用这些规定解决type RouteOpts struct { Receiver string GroupBy map[model.LabelName]struct{} GroupByAll bool GroupWait time.Duration GroupInterval time.Duration RepeatInterval time.Duration MuteTimeIntervals []string}对树状构造数据,源码中应用了递归来遍历节点 ...

October 31, 2021 · 4 min · jiezi

关于golang:go-pprof调测工具的使用

pprof是golang提供的排查go利用程序运行状况的调试工具,能够查看运行的goroutine、以后内存对象及其占用状况等。当发现过程内存占用过高、CPU使用率过高时,通过pprof能够帮忙排查应用程序的问题。 pprof仅记录heap内存,未记录stack内存;pprof是基于采样的,默认每1000次内存调配,执行1次ppof。 pprof能够帮忙发现内存问题,但不肯定发现内存泄露问题,还须要联合业务代码和监控指标综合剖析。 pprof配置应用import ( "github.com/DeanThompson/ginpprof" "github.com/gin-gonic/gin" "net/http")func StartHttp() { r := gin.Default() ginpprof.Wrap(r) r.GET("/index", func(c *gin.Context) { c.JSON(http.StatusOK, "index") }) r.Run(":9999")}通过监听的9999拜访web页面: goroutine: goroutine数量及栈信息;heap: 堆中的内存对象信息;threadcreate: 创立的线程信息; 通过go tool pprof可剖析其内存占用统计: # go tool pprof http://192.168.1.1:9999/debug/pprof/heap(pprof) topShowing nodes accounting for 1541.95kB, 100% of 1541.95kB totalShowing top 10 nodes out of 12 flat flat% sum% cum cum% 516.01kB 33.46% 33.46% 1541.95kB 100% github.com/go-playground/validator/v10.init 513.31kB 33.29% 66.75% 513.31kB 33.29% regexp.onePassCopy 512.62kB 33.25% 100% 512.62kB 33.25% regexp/syntax.(*compiler).inst (inline) 0 0% 100% 1025.94kB 66.54% regexp.Compile (inline)能够通过top查看占用最高的内存对象,通过list查看相应的代码: ...

October 30, 2021 · 1 min · jiezi

关于golang:go-interface赋值为对象or对象指针

interface的变量赋值时,须要查看右侧对象是否实现interface的办法集,若实现,则赋值Ok,否则不能被赋值。 常见问题type tester interface { test() string() string}type data struct {}func (d *data) test() {}func (d data) string() string { return ""}赋值: func main() { var d data //谬误 //data实现的办法:string() var t tester = d //正确 //*data实现的办法:string()和test() t = &d //ok t.test()}典型利用在类型的办法定义时,个别场景下都会定义为指针办法: func (d *data) test() {}所以在对interface变量赋值时,个别应用指针赋值: var d datavar t tester = &d

October 30, 2021 · 1 min · jiezi

关于golang:Docker-系列docker-学习-二Docker-的常用命令

【Docker 系列】docker 学习 二,Docker 的常用命令根本帮忙命令# 查看 docker 的根本版本信息docker version# 查看 docker 的零碎信息,如镜像和容器数量docker info# 查看某个命令的帮忙docker xx命令 --help咱们能够看官网的帮忙文档:https://docs.docker.com/refer... 镜像命令docker images 查看镜像Usage: docker images [OPTIONS] [REPOSITORY[:TAG]] 查看本机上的镜像 # docker imagesREPOSITORY TAG IMAGE ID CREATED SIZEubuntu latest 1318b700e415 5 days ago 72.8MBhello-world latest d1165f221234 4 months ago 13.3kB关键字解释REPOSITORY仓库源TAG镜像标签IMAGE ID镜像 IDCREATED创立工夫SIZE镜像大小可选参数: Options: -a, --all 显示所有的镜像 -q, --quiet 只显示镜像IDdocker search 搜寻镜像搜寻 redis 为例 # docker search redisNAME DESCRIPTION STARS OFFICIAL AUTOMATEDredis Redis is an open source key-value store that… 9734 [OK]sameersbn/redis 83 [OK]grokzen/redis-cluster Redis cluster 3.0, 3.2, 4.0, 5.0, 6.0, 6.2 78rediscommander/redis-commander Alpine image for redis-commander - Redis man… 63 [OK]加上参数 ...

October 30, 2021 · 4 min · jiezi

关于golang:一文教会你如何进行Golang服务优化

1、概述嗨喽,大家好呀!我是简凡,一位游走于各互联网大厂间的新时代农民工。对于C端在线业务,服务的稳定性和吞吐量经常是评估一个零碎的重要指标,所以本文将从以下4点进行开展,逐渐解说golang中如何进行性能优化。 为什么要做性能优化性能优化根底优化思路常见的优化场景2、性能优化的目标(Why?)咱们经常在以下时候思考到性能优化: 日常优化零碎: 接口相应工夫优化,以满足对上游的SLACPU优化,保障在线业务cpu idl处于一个较高水平,升高业务量突增对系统稳定性带来的冲击内存优化,缩小内存占用,开释多余的服务器资源解决线上业务问题: 接口相应超时CPU利用率飙升3、性能优化根底(What?)3.1 性能优化指标在Golang服务中,咱们经常从以下4点触发去做服务的优化: CPU profile:报告程序的 CPU 应用状况,依照肯定频率去采集应用程序在 CPU 和寄存器下面的数据Memory Profile(Heap Profile):报告程序的内存应用状况Block Profiling:报告 goroutines 不在运行状态的状况,能够用来剖析和查找死锁等性能瓶颈Goroutine Profiling:报告 goroutines 的应用状况,有哪些 goroutine,它们的调用关系是怎么的4. 性能剖析过程(How?)4.1 如何获取性能快照golang中有两种类型的利用,工具性利用和服务型利用,工具性型利用的main函数仅一段时间,咱们本地跑单元测试的性能测试其实原理就是利用的这种。服务型利用为长期存活的后端利用,例如RPC服务,HTTP服务,咱们后端系统通常都是服务型利用。 4.1.1 工具型利用获取CPU快照测试Demo如下,这里用了一个快排的例子,利用执行完结后,就会生成一个文件,保留了咱们的 CPU profiling 数据。失去采样数据之后,应用go tool pprof工具进行 CPU 性能剖析。 package main import ( "math/rand" "os" "runtime/pprof" "time") func generate(n int) []int { rand.Seed(time.Now().UnixNano()) nums := make([]int, 0) for i := 0; i < n; i++ { nums = append(nums, rand.Int()) } return nums}func bubbleSort(nums []int) { for i := 0; i < len(nums); i++ { for j := 1; j < len(nums)-i; j++ { if nums[j] < nums[j-1] { nums[j], nums[j-1] = nums[j-1], nums[j] } } }} func main() { pprof.StartCPUProfile(os.Stdout) defer pprof.StopCPUProfile() n := 10 for i := 0; i < 5; i++ { nums := generate(n) bubbleSort(nums) n *= 10 }}这里应用的runtime/pprof这个剖析工具,须要指定快照打印的地位,这里打印到规范输入了。能够会与程序中的打印抵触。咱们能够本人实现写到文件中,这里能够用另一个开源工具代替github.com/pkg/profile,它会生成一个日志快照文件到长期目录。 ...

October 29, 2021 · 6 min · jiezi

关于golang:Gin-框架源码学习二-服务启动

本篇次要介绍gin服务启动过程的源码 Run() 启动入口咱们的程序都是通过调用Run函数来启动gin的实例,上面来看一下Run的源码: func (engine *Engine) Run(addr ...string) (err error) { defer func() { debugPrintError(err) }() // 解析服务地址 address := resolveAddress(addr) debugPrint("Listening and serving HTTP on %s\n", address) // 此处会block err = http.ListenAndServe(address, engine) return}该办法其实是对http.ListenAndServe的简略封装,以下逻辑就进入net/http包中了,持续往下看: func ListenAndServe(addr string, handler Handler) error { // 配置tcp的监听地址和监听到来后的处理函数 server := &Server{Addr: addr, Handler: handler} // 持续调用。。。 return server.ListenAndServe()}// Handler定义type Handler interface { // 申请到来后的解决逻辑,gin框架会本人实现 ServeHTTP(ResponseWriter, *Request)}func (srv *Server) ListenAndServe() error { // 异样解决 if srv.shuttingDown() { return ErrServerClosed } addr := srv.Addr if addr == "" { addr = ":http" } // 监听配置 ln, err := net.Listen("tcp", addr) if err != nil { return err } return srv.Serve(ln)}func (srv *Server) Serve(l net.Listener) error { if fn := testHookServerServe; fn != nil { fn(srv, l) // call hook with unwrapped listener } origListener := l l = &onceCloseListener{Listener: l} defer l.Close() if err := srv.setupHTTP2_Serve(); err != nil { return err } if !srv.trackListener(&l, true) { return ErrServerClosed } defer srv.trackListener(&l, false) baseCtx := context.Background() if srv.BaseContext != nil { baseCtx = srv.BaseContext(origListener) if baseCtx == nil { panic("BaseContext returned a nil context") } } var tempDelay time.Duration // how long to sleep on accept failure // 带值的上下文 ctx := context.WithValue(baseCtx, ServerContextKey, srv) // 死循环,监听&解决申请 for { // 申请过去了 rw, err := l.Accept() if err != nil { select { case <-srv.getDoneChan(): return ErrServerClosed default: } if ne, ok := err.(net.Error); ok && ne.Temporary() { if tempDelay == 0 { tempDelay = 5 * time.Millisecond } else { tempDelay *= 2 } if max := 1 * time.Second; tempDelay > max { tempDelay = max } srv.logf("http: Accept error: %v; retrying in %v", err, tempDelay) time.Sleep(tempDelay) continue } return err } connCtx := ctx if cc := srv.ConnContext; cc != nil { connCtx = cc(connCtx, rw) if connCtx == nil { panic("ConnContext returned nil") } } tempDelay = 0 // 创立一个新连贯,一个conn对象,代表服务端的http连贯,里边蕴含该次申请的数据 c := srv.newConn(rw) c.setState(c.rwc, StateNew) // before Serve can return // 开启一个goroutine解决申请,将上下文传递进去,高并发的保障 go c.serve(connCtx) }}// serve函数只摘取最重要的一行func (c *conn) serve(ctx context.Context) { ... // 这里就会调用gin框架实现的ServeHTTP逻辑 serverHandler{c.server}.ServeHTTP(w, w.req) ...}ServeHTTP 逻辑func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) { // 从对象池子里获取一个Context对象,池子里有能够间接拿来用,没有则创立一个新的对象返回 c := engine.pool.Get().(*Context) // 把申请的相干数据都写入Context c.writermem.reset(w) c.Request = req c.reset() // 申请解决逻辑 engine.handleHTTPRequest(c) // 应用结束,放回池子,复用 engine.pool.Put(c)}func (engine *Engine) handleHTTPRequest(c *Context) { httpMethod := c.Request.Method rPath := c.Request.URL.Path unescape := false if engine.UseRawPath && len(c.Request.URL.RawPath) > 0 { rPath = c.Request.URL.RawPath unescape = engine.UnescapePathValues } rPath = cleanPath(rPath) // Find root of the tree for the given HTTP method t := engine.trees // 遍历路由数组,依据申请办法找出对应的那棵树 for i, tl := 0, len(t); i < tl; i++ { if t[i].method != httpMethod { continue } root := t[i].root // Find route in tree // 从树中找到路由对应的函数解决链和参数 handlers, params, tsr := root.getValue(rPath, c.Params, unescape) if handlers != nil { c.handlers = handlers c.Params = params // 按程序执行handler办法,能够配合在中间件中应用 c.Next() // handlers中的所有函数都处理完毕后,该返回了 c.writermem.WriteHeaderNow() return } if httpMethod != "CONNECT" && rPath != "/" { if tsr && engine.RedirectTrailingSlash { redirectTrailingSlash(c) return } if engine.RedirectFixedPath && redirectFixedPath(c, root, engine.RedirectFixedPath) { return } } break } if engine.HandleMethodNotAllowed { for _, tree := range engine.trees { if tree.method == httpMethod { continue } if handlers, _, _ := tree.root.getValue(rPath, nil, unescape); handlers != nil { c.handlers = engine.allNoMethod serveError(c, http.StatusMethodNotAllowed, default405Body) return } } } c.handlers = engine.allNoRoute serveError(c, http.StatusNotFound, default404Body)}// 看一下Next办法,很简略func (c *Context) Next() { c.index++ for c.index < int8(len(c.handlers)) { c.handlers[c.index](c) c.index++ }}总结启动流程很清晰,通过net/http包监听申请,外围逻辑就是一个死循环,有限期待,当有申请达到指定端口时,启动一个goroutine异步解决该申请。申请的解决逻辑应用的是gin框架本人实现的ServeHTTP函数,gin对Conext的封装和复用,也是一大亮点。context的Next办法在中间件中的应用,能够实现后置中间件。

October 29, 2021 · 3 min · jiezi

关于golang:终于Go-118-将支持泛型来听听Go-核心技术团队-Russ-Cox怎么说

近日,Go 语言外围开发团队技术主管 Russ Cox 在 golang-dev group 发了公开邮件,发表称“如果没有意外状况,Go 1.18 将会反对泛型。”据悉,Go 1.18 版行将于2022年初公布。 还记得10月初,本站刚刚报道了“Go语言之父”Rob Pike在github上对于“不倡议在Go 1.18的规范库中应用泛型”issue的音讯。过后,Rob Pike的放心是“Go 1.18版本承载了太多的change,容易出错”,所以倡议先期待察看,稳步向前。 而到了10月28日的昨天,Russ Cox又发文针对 Rob Pike 的 issue,介绍了Go 1.18 版本与泛型目前的停顿和后续的反对策略,这也终于确定了Go外围团队下阶段的方向——也就是说,如果不出意外的话,Go 1.18版本中将反对泛型。 退出泛型对于Go团队的意义 作为Go公布以来最重要的变动,Russ Cox在这封公开邮件中简略解释了泛型的退出对Go团队和用户的意义。 Russ Cox示意,任何Go的新性能个性,无论是语言还是库,都带有不确定性,包含不确定如何应用、如何不应用它们,以及有哪些渺小的bug曾经通过了现有的测试集。泛型也不能防止这种不确定性,特地是因为泛型是个大型的新性能,所以它的不确定性也会更大。 同时,在该团队最后公布的泛型代码–特地是通过提案程序的maps和slices包–将首先放在golang.org/x/exp中,但不能保障向后兼容。Russ Cox称,将来一旦有了更多的教训,会心愿将其中一些包推广到规范库中(constraints包例外,它作为编写某些泛型代码的根底,将被增加到Go 1.18规范库中。) Russ Cox 强调,Go 1.18与其余Go 1.x版本一样具备向后兼容的承诺,“不会毁坏用Go 1.18构建的代码,包含应用泛型的代码。在最坏的状况下,如果咱们发现Go 1.18的语义有一些致命的问题,并须要扭转它们(例如在Go 1.19中),咱们将应用go.mod文件的go版本批示符来确定该module中的源文件是应用Go 1.18还是Go 1.19+的语义。(咱们预计不须要这样做!)” 对于不少急于采纳泛型的软件包作者,Russ Cox倡议称“如果您正在更新您的软件包以应用泛型,请思考将新的泛型API隔离到本人的文件中,并为其应用Go 1.18的构建标签(//go:build go1.18),以便Go 1.17用户能够持续构建和应用非泛型局部。” 值得注意的是,第三方工具可能不会在Go 1.18公布时齐全反对泛型。目前,Go外围团队正在与不少第三方工具的作者沟通,试图确保他们失去适当的更新,但他们都有本人的工夫安排表。 对于“为什么不把泛型变成可选项退出Go 1.18?”的疑难,Russ Cox解释称,缩小不确定性的惟一办法是让其默认可用。 “当咱们在Go 1.5版本中让vendor机制作为可选项退出时,发现简直没有人真正应用它,直到Go 1.6版本默认开启它。所以Go 1.5版本没有缩小咱们对Go开发者应用vendor状况的不确定性。另一方面,Go 1.5版本无疑将生态系统分为’在规范Go下运行的代码‘和 ’在启用vendoring后运行的代码‘两局部。咱们心愿在这里尽可能地防止这种后果。” Go语言为什么须要泛型? 始终以来,业界对于Go语言泛型的话题探讨都十分强烈,而Go团队也始终对否退出泛型而当机立断,因为他们心愿找到一种好的解决方案。 咱们晓得,函数式编程是一种十分风行的编程范式,在很多汇编语言类型里都有构建或反对。而对于Go语言来说,只管并非是一种函数式语言,但它的确提供了一组容许函数式编程的个性(有相当数量的开源Go库提供性能个性集)。 函数式编程的语言反对范畴从只反对函数式范式(如Haskell)到多范式+一流反对(如Scala、Elixir)再到多范式+局部反对(如Javascript、Go)。在后一类语言中,函数式编程个别通过应用社区创立的库来反对,这些库复制前两种语言的规范库中的局部或全副性能。 Go语言则属于最初一类,它能够实现下图中的性能编程: 在Go语言生态系统中,曾经存在许多功能性编程库,它们在风行水平、性能和工效方面各不相同。 ...

October 29, 2021 · 1 min · jiezi

关于golang:go-databasesql-连接的申请与释放超过最大连接数

go自带的database/sql领有连接池的能力,能够通过SetMaxOpenConns配置最大的连接数,当超过最大连贯时,database/sql是如何解决的呢? 答案是将申请放入期待队列并阻塞,当有连贯开释时被唤醒,将开释的连贯给它。 申请连贯申请连贯的流程: 首先查看连接池是否有闲暇的连贯,若有,则返回该连贯;查看是否超过最大连接数: 若超过最大连贯,则将该申请放入期待队列;否则,新建一个连贯对象返回;// conn returns a newly-opened or cached *driverConn.func (db *DB) conn(ctx context.Context, strategy connReuseStrategy) (*driverConn, error) { ...... //先查看连接池是否有闲暇的连贯,若有,则返回该conn ...... //如果超过最大连接数,将该连贯申请放入期待队列中并阻塞 if db.maxOpen > 0 && db.numOpen >= db.maxOpen { req := make(chan connRequest, 1) reqKey := db.nextRequestKeyLocked() db.connRequests[reqKey] = req db.waitCount++ select { case <-ctx.Done(): ...... case ret, ok := <-req: ...... return ret.conn, ret.err } } //新建一个连贯对象,返回 ......}重点关注超过最大连接数的行为,将连贯申请放入期待队列connRequests:map[unit64]chan connRequest类型,key是unit64,value是chan connRequest,申请放入map的逻辑: req := make(chan connRequest, 1)reqKey := db.nextRequestKeyLocked()db.connRequests[reqKey] = reqdb.waitCount++//reqKey是递增的整数值func (db *DB) nextRequestKeyLocked() uint64 { next := db.nextRequest db.nextRequest++ return next}除了将request退出map,还用select将申请阻塞: ...

October 28, 2021 · 2 min · jiezi

关于golang:go-databasesql-连接的申请与释放

database/sql是go自带的操作sql的库,它保护了sql的连接池,包含连贯的申请和开释。 连接池datebase/sql保护了连接池,其配置: db.SetMaxIdleConns(10) //设置闲暇连接池中的最大idle连接数db.SetMaxOpenConns(100) //设置数据库连贯最大关上数db.SetConnMaxLifetime(time.Hour) //设置可重用连贯的最长工夫操作MySQL的示例程序: import ( "database/sql" _ "github.com/go-sql-driver/mysql")func main() { db, _ := db.Open("mysql", "root:rootroot@/dqm?charset=utf8&parseTime=True&loc=Local") defer db.Close() db.SetMaxOpenConns(10) if err := db.Ping(); err != nil { fmt.Println("connect to MySQL failed, err:", err) return } rows, err := db.Query("select * from test where name = 'jackie' limit 10") if err != nil { fmt.Println("query error") } defer rows.Close() for rows.Next() { fmt.Println("close") } row, _ := db.Query("select * from test") fmt.Println(row, rows)}db.Open(“mysql”, dsn)并不会真正连贯MySQL,也不会校验数据库用户名/明码,仅校验了dsn格局。 ...

October 28, 2021 · 3 min · jiezi

关于golang:Netpoll导读

Netpoll 是由 字节跳动 开发的高性能 NIO(Non-blocking I/O) 网络库,专一于 RPC 场景。详情:https://github.com/cloudwego/... 要理解一个像Netty这样我的项目的源码是一件十分艰难的事件。我的项目自身源码量大,波及的底层常识多,加上利用了各种设计模式,都会对源码浏览减少难度。对于这样的工程最好是依据作者提供的导图先理解个大略,而后充沛浏览文档,头脑中构建一个思维框架。一开始就冲进去直怼源码是一件十分怯懦的事件。这样的我的项目往往是一个大工程,汇聚了很多卓越开源作者的心血,想一口气读完,而且丝毫不差的了解外面的设计思路是不事实的,浏览源码往往咱们只须要浏览它一个次要流程的过程是怎么产生构建的,而不须要字字斟酌的去读,这样容易陷在码海里。通过第一遍理清流程之后,能够第二遍零碎的浏览,而后再去反思作者这样设计的益处。源码目录netpoll源码文件全在同一个目录下,高深莫测,有多少文件。次要架构万事开头难,从服务启动开始func main() { //创立 Listener listener, err := netpoll.CreateListener(network, address) if err != nil { panic("create netpoll listener failed") } //创立 EventLoop eventLoop, _ := netpoll.NewEventLoop( handler, // netpoll.WithOnPrepare(prepare), netpoll.WithReadTimeout(time.Second), ) //运行 Server eventLoop.Serve(listener)}//扩大net.Listenertype Listener interface { net.Listener Fd() (fd int)}这个步骤是开启MianReactor 作用在于监听端口,获取操作符地址 func CreateListener(network, addr string) (l Listener, err error) { if network == "udp" { // TODO: udp listener. return udpListener(network, addr) } // tcp, tcp4, tcp6, unix //调用go原生 ln, err := net.Listen(network, addr) if err != nil { return nil, err } return ConvertListener(ln)}//转换成定义的Listener ,向零碎设置Nonblock ...

October 28, 2021 · 4 min · jiezi

关于golang:go操作elasticsearch示例

这里我应用elasticsearch官网给的go语言包(go-elasticsearch)go-elasticsearch向前兼容,这意味着客户端反对与更大或等同主要版本的 Elasticsearch 通信。Elasticsearch 语言客户端仅向后兼容默认发行版,不提供任何保障。 包:https://github.com/elastic/go...Elasticsearch 权威指南:https://www.elastic.co/guide/...环境介绍:版本Elasticsearch:v7.15装置go.mod 文件中增加 require github.com/elastic/go-elasticsearch/v8 main或者 git clone --branch main https://github.com/elastic/go-elasticsearch.git $GOPATH/src/github.com/elastic/go-elasticsearch示例:新建 es.go 存入 es目录 package esimport ( "bytes" "context" "encoding/json" "fmt" "github.com/elastic/go-elasticsearch/v8" "github.com/elastic/go-elasticsearch/v8/esapi" "log" "net/http")var EsClient *elasticsearch.Clientfunc init() { cfg := elasticsearch.Config{ Addresses: []string{ "http://localhost:9200", }, } var err error EsClient, err = elasticsearch.NewClient(cfg) if err != nil { log.Fatalln("Failed to connect to es") }}func failOnError(err error, msg string) { if err != nil { log.Fatalf("%s: %s", msg, err) }}// idx 为空,默认随机惟一字符串func Index(index, idx string, doc map[string]interface{}) { //index:="my_index_name_v1" res, err := EsClient.Info() fmt.Println(res, err) if err != nil { log.Fatalf("Error getting response: %s", err) } var buf bytes.Buffer //doc := map[string]interface{}{ // "title": "中国", // "content": "中国早日对立台湾", // "time": time.Now().Unix(), // "date": time.Now(), //} if err = json.NewEncoder(&buf).Encode(doc); err != nil { fmt.Println(err, "Error encoding doc") return } res, err = EsClient.Index( index, // Index name &buf, // Document body EsClient.Index.WithDocumentID(idx), // Document ID // Document ID EsClient.Index.WithRefresh("true"), // Refresh ) //res, err = EsClient.Create(index, idx, &buf) if err != nil { fmt.Println(err, "Error create response") } defer res.Body.Close() fmt.Println(res.String()) log.Println(res)}//struct 类型容许应用更理论的办法,您能够在其中创立一个新构造,将申请配置作为字段,并应用上下文和客户端作为参数调用 Do() 办法:func IndexEspi(index, idx string, doc map[string]interface{}) { //index:="my_index_name_v1" res, err := EsClient.Info() fmt.Println(res, err) if err != nil { log.Fatalf("Error getting response: %s", err) } var buf bytes.Buffer //doc := map[string]interface{}{ // "title": "中国", // "content": "中国早日对立台湾", // "time": time.Now().Unix(), // "date": time.Now(), //} if err = json.NewEncoder(&buf).Encode(doc); err != nil { fmt.Println(err, "Error encoding doc") return } req := esapi.IndexRequest{ Index: index, // Index name Body: &buf, // Document body DocumentID: idx, // Document ID Refresh: "true", // Refresh } res, err = req.Do(context.Background(), EsClient) if err != nil { log.Fatalf("Error getting response: %s", err) } defer res.Body.Close() fmt.Println(res.String()) log.Println(res)}func Search(index string, query map[string]interface{}) { res, err := EsClient.Info() if err != nil { fmt.Println(err, "Error getting response") } //fmt.Println(res.String()) // search - highlight var buf bytes.Buffer //query := map[string]interface{}{ // "query": map[string]interface{}{ // "match": map[string]interface{}{ // "title": title, // }, // }, // "highlight": map[string]interface{}{ // "pre_tags": []string{"<font color='red'>"}, // "post_tags": []string{"</font>"}, // "fields": map[string]interface{}{ // "title": map[string]interface{}{}, // }, // }, //} if err := json.NewEncoder(&buf).Encode(query); err != nil { fmt.Println(err, "Error encoding query") } // Perform the search request. res, err = EsClient.Search( EsClient.Search.WithContext(context.Background()), EsClient.Search.WithIndex(index), EsClient.Search.WithBody(&buf), EsClient.Search.WithTrackTotalHits(true), EsClient.Search.WithFrom(0), EsClient.Search.WithSize(10), EsClient.Search.WithSort("time:desc"), EsClient.Search.WithPretty(), ) if err != nil { fmt.Println(err, "Error getting response") } defer res.Body.Close() fmt.Println(res.String())}//删除 index 依据 索引名 idfunc Delete(index, idx string) { //index:="my_index_name_v1" res, err := EsClient.Info() fmt.Println(res, err) if err != nil { log.Fatalf("Error getting response: %s", err) } res, err = EsClient.Delete( index, // Index name idx, // Document ID EsClient.Delete.WithRefresh("true"), ) if err != nil { fmt.Println(err, "Error create response") } defer res.Body.Close() fmt.Println(res.String()) log.Println(res)}func DeleteByQuery(index []string, query map[string]interface{}) { res, err := EsClient.Info() if err != nil { fmt.Println(err, "Error getting response") } //fmt.Println(res.String()) // search - highlight var buf bytes.Buffer //query := map[string]interface{}{ // "query": map[string]interface{}{ // "match": map[string]interface{}{ // "title": title, // }, // }, // }, //} if err := json.NewEncoder(&buf).Encode(query); err != nil { fmt.Println(err, "Error encoding query") } // Perform the search request. res, err = EsClient.DeleteByQuery( index, &buf, ) if err != nil { fmt.Println(err, "Error getting response") } defer res.Body.Close() fmt.Println(res.String())}func SearchEsapiSql(query map[string]interface{}) { jsonBody, _ := json.Marshal(query) req := esapi.SQLQueryRequest{Body: bytes.NewReader(jsonBody)} res, _ := req.Do(context.Background(), EsClient) defer res.Body.Close() fmt.Println(res.String())}func SearchHttp(method, url string, query map[string]interface{}) { jsonBody, _ := json.Marshal(query) req, _ := http.NewRequest(method, url, bytes.NewReader(jsonBody)) req.Header.Add("Content-type", "application/json") res, err := EsClient.Perform(req) if err != nil { return } defer res.Body.Close() buf := new(bytes.Buffer) buf.ReadFrom(res.Body) fmt.Println(buf.String())}新建 main.go ...

October 28, 2021 · 6 min · jiezi

关于golang:记一次-Golang-数据库查询组件的优化

欢送到我的博客中查看 线上有一块业务,须要做大量的数据库查问以及编码落盘的工作。数据库查问20分钟左右,大概有2kw条sql被执行。如果能够优化数据库查问的办法,能够节俭一笔很大的开销。 因为代码比拟长远,未能考据过后的数据查问选型为什么不实用orm,而是应用原生的形式本人构建。上面是外围的数据查问代码: func QueryHelperOne(db *sql.DB, result interface{}, query string, args ...interface{}) (err error) { // 数据库查问 var rows *sql.Rows log.Debug(query, args) rows, err = db.Query(query, args...) if err != nil { return err } defer rows.Close() // 获取列名称,并转换首字母大写,用于和struct Field 匹配 var columns []string columns, err = rows.Columns() if err != nil { return err } fields := make([]string, len(columns)) for i, columnName := range columns { fields[i] = server.firstCharToUpper(columnName) } // 传参必须是数组 slice 指针 rv := reflect.ValueOf(result) if rv.Kind() == reflect.Ptr { rv = rv.Elem() } else { return errors.New("Parameter result must be a slice pointer") } if rv.Kind() == reflect.Slice { elemType := rv.Type().Elem() if elemType.Kind() == reflect.Struct { ev := reflect.New(elemType) // 申请slice 数据,之后赋值给result nv := reflect.MakeSlice(rv.Type(), 0, 0) ignoreData := make([][]byte, len(columns)) for rows.Next() { // for each rows // scanArgs 是扫描每行数据的参数 // scanArgs 中存储的是 struct 中field 的指针 scanArgs := make([]interface{}, len(fields)) for i, fieldName := range fields { fv := ev.Elem().FieldByName(fieldName) if fv.Kind() != reflect.Invalid { scanArgs[i] = fv.Addr().Interface() } else { ignoreData[i] = []byte{} scanArgs[i] = &ignoreData[i] } } err = rows.Scan(scanArgs...) if err != nil { return err } nv = reflect.Append(nv, ev.Elem()) } rv.Set(nv) } } else { return errors.New("Parameter result must be a slice pointer") } return}办法通过如下形式调用: ...

October 28, 2021 · 4 min · jiezi

关于golang:面试篇Go语言常见踩坑一

引言本系列会列举一些在Go面试中常见的问题。 切片循环问题For循环在咱们日常编码中可能用的很多。在很多业务场景中咱们都须要用for循环解决。但golang中的for循环在应用上须要留神一些问题,大家可否遇到。先看下边这一段代码: func testSlice() { a := []int64{1,2,3} for _, v := range a { go func() { fmt.Println(v) }() } time.Sleep(time.Second)}output: 3 3 3那么为什么会输入的是这个后果呢? 在golang的for循环中,循环外部创立的函数变量都是共享同一块内存地址,for循环总是应用同一块内存去接管循环中的的value变量的值。不论循环多少次,value的内存地址都是雷同的。咱们能够测试一下: func testSliceWithAddress() { a := []int64{1,2,3} for _, v := range a { go func() { fmt.Println(&v) }() } time.Sleep(time.Second)}output: 0xc0000b2008 0xc0000b2008 0xc0000b2008合乎预期。如果大家比拟感兴趣的话能够去将这段代码的汇编打印进去,就能够发现循环的v始终在操作同一块内存。 同样的,在slice循环这块咱们还会遇见另一个乏味的中央,大家能够看看下边这段代码输入什么? func testRange3() { a := []int64{1,2,3} for _, v := range a { a = append(a, v) } fmt.Println(a)}这段代码的输入后果是:[1 2 3 1 2 3],为什么呢?因为golang在循环前会先拷贝一个a,而后对新拷贝的a进行操作,所以循环的次数不会随着append而增多。 ...

October 28, 2021 · 1 min · jiezi

关于golang:Yaegi让你用标准-Go-语法开发可热插拔的脚本和插件

导语Go 作为一种编译型语言,常常用于实现后盾服务的开发。因为 Go 初始的开发大佬都是 C 的老牌使用者,因而 Go 中保留了不少 C 的编程习惯和思维,这对 C/C++ 和 PHP 开发者来说十分有吸引力。作为编译型语言的个性,也让 Go 在多协程环境下的性能有不俗的体现。 但脚本语言则简直都是解释型语言,那么 Go 怎么就和脚本扯上关系了?请读者带着这个疑难,“听” 本文给你娓娓道来~~ 本文章采纳 常识共享署名-非商业性应用-雷同形式共享 4.0 国内许可协定 进行许可。 什么样的语言能够作为脚本语言?程序员们都晓得,高级程序语言从运行原理的角度来说能够分成两种:编译型语言、解释型语言。Go 就是一个典型的编译型语言。 编译型语言就是须要应用编译器,在程序运行之前将代码编译成操作系统可能间接辨认的机器码文件。运行时,操作系统间接拉起该文件,在 CPU 中间接运行解释型语言则是在代码运行之前,须要先拉起一个解释程序,应用这个程序在运行时就能够依据代码的逻辑执行编译型语言的典型例子就是 汇编语言、C、C++、Objective-C、Go、Rust 等等。 解释型语言的典型例子就是 JavaScript、PHP、Shell、Python、Lua 等等。 至于 Java,从 JVM 的角度,它是一个编译型语言,因为编译进去的二进制码能够间接在 JVM 上执行。但从 CPU 的角度,它仍然是一个解释型语言,因为 CPU 并不间接运行代码,而是间接地通过 JVM 解释 Java 二进制码从而实现逻辑运行。 所谓的 “脚本语言” 则是另外的一个概念,这个别指的是设计初衷就是用来开发一段小程序或者是小逻辑,而后应用预设的解释器解释这段代码并执行的程序语言。这是一个程序语言性能上的定义,实践上所有解释型语言都能够很不便的作为脚本语言,然而实际上咱们并不会这么做,比如说 PHP 和 JS 就很少作为脚本语言应用。 能够看到,解释型语言天生适宜作为脚本语言,因为它们本来就须要应用运行时来解释和运行代码。将运行时稍作革新或封装,就能够实现一个动静拉起脚本的性能。 然而,程序员们并不信邪,ta们素来就没有放弃把编译型语言变成脚本语言的致力。 为什么须要用 Go 写脚本?首先答复一个问题:为什么咱们须要嵌入脚本语言?答案很简略,编译好的程序逻辑曾经固定下来了,这个时候,咱们须要增加一个能力,可能在运行时调整某些局部的性能逻辑,实现这些性能的灵便配置。 在这方面,其实项目组别离针对 Go 和 Lua 都有了比拟成熟的利用,应用的别离是 yaegi 和 gopher。对于后者的文章曾经很多,本文便不再赘述。这里咱们先简略列一下应用 yaegi 的劣势: ...

October 28, 2021 · 3 min · jiezi

关于golang:Go-里的超时控制

前言日常开发中咱们大概率会遇到超时管制的场景,比方一个批量耗时工作、网络申请等;一个良好的超时管制能够无效的防止一些问题(比方 goroutine 泄露、资源不开释等)。 Timer在 go 中实现超时管制的办法非常简单,首先第一种计划是 Time.After(d Duration): func main() { fmt.Println(time.Now()) x := <-time.After(3 * time.Second) fmt.Println(x)}output: 2021-10-27 23:06:04.304596 +0800 CST m=+0.0000856532021-10-27 23:06:07.306311 +0800 CST m=+3.001711390 time.After() 会返回一个 Channel,该 Channel 会在延时 d 段时间后写入数据。 有了这个个性就能够实现一些异步控制超时的场景: func main() { ch := make(chan struct{}, 1) go func() { fmt.Println("do something...") time.Sleep(4*time.Second) ch<- struct{}{} }() select { case <-ch: fmt.Println("done") case <-time.After(3*time.Second): fmt.Println("timeout") }}这里假如有一个 goroutine 在跑一个耗时工作,利用 select 有一个 channel 获取到数据便退出的个性,当 goroutine 没有在无限工夫内实现工作时,主 goroutine 便会退出,也就达到了超时的目标。 ...

October 28, 2021 · 2 min · jiezi

关于golang:一文读懂-Go-syncCond-设计

Go 语言通过 go 关键字开启 goroutine 让开发者能够轻松地实现并发编程,而并发程序的无效运行,往往离不开 sync 包的保驾护航。目前,sync 包的赋能列表包含: sync.atomic 下的原子操作、sync.Map 并发平安 map、sync.Mutex 与 sync.RWMutex 提供的互斥锁与读写锁、sync.Pool 复用对象池、sync.Once 单例模式、 sync.Waitgroup 的多任务合作模式、sync.Cond 的监视器模式。当然,除了 sync 包,还有封装层面更高的 channel 与 context。 要想写出合格的 Go 程序,以上的这些并发原语是必须要把握的。对于大多数 Gopher 而言,sync.Cond 应该是最为生疏,本文将一探到底。 初识 sync.Condsync.Cond 字面意义就是同步条件变量,它实现的是一种监视器(Monitor)模式。 In concurrent programming(also known as parallel programming), a monitor is a synchronization construct that allows threads to have both mutual exclusion and the ability to wait (block) for a certain condition to become false. ...

October 27, 2021 · 4 min · jiezi

关于golang:Docker-学习-一Docker-是什么

Docker 学习 一Docker 是什么网址:https://hub.docker.com/ docker对过程进行封装隔离,属于 操作系统层面的虚拟化技术 因为隔离的过程独立于宿主和其它的隔离的过程,因而也称其为容器 docker 利用场景 自动化测试和继续集成、公布Web 利用的自动化打包和公布后盾利用易部署docker 的劣势 疾速, 统一的交付应用程序可移植,可扩大笨重,疾速,经济,高效,压迫linux本身资源Docker 能做什么?先来说说 Docker 和虚拟机有啥不一样的 以前的虚拟机这样的,零碎占用资源大,很多步骤是冗余的,并且启动还很慢,不能忍 当初的 Docker 是这个样子的, 容器之间相互隔离,互补烦扰,一起运行在同一个操作系统上,最大化应用操作系统资源 Docker 技术和虚拟机技术的不同? 每个容器间都是互相隔离的,他们有属于本人的文件系统,互相不会有影响容器没有本人的内核,没有本人的硬件,容器内的利用是间接运行在宿主机的内核中传统的虚拟机是虚构出一个硬件,运行实现的操作系统,在其下面运行利用那么 Docker 具体能做什么? 做 DevOps做 DevOps 有如下几个晋升点: 利用能够更快捷的部署和交付以前麻烦的装置步骤一去不复返,应用 Docker 容器化后,打包镜像公布测试,一键部署及运行 能够更不便的降级和扩容应用 Docker,将我的项目打包成镜像,降级不便,扩容不便 开发,运维,测试都会更简略再也不必放心开发环境,测试环境,运维环境不统一的状况了 更高效的利用资源Dcoker 是运行在宿主机的内核中,能够在这台物理主机上部署多个 Docker 实例 Docker 的组成Docker 应用客户端-服务器 (C/S) 架构模式,应用近程API来治理和创立 Docker 容器 Docker 的三个基本概念: 图片来源于网络 镜像相当于是一个 root 文件系统,相似于一个模板,这是动态的 容器相当于从模板拉进去的一个实例,容器通过镜像来创立,咱们能够对他做创立,启动,进行,暂停,删除等操作 仓库用来保留镜像的,能够看做是一个代码控制中心 Docker 的装置和应用装置网络上装置 Docker 的形式大抵有如下几种: 官网脚本主动装置应用 Docker 仓库装置应用 ==shell== 脚本装置咱们以 ubuntu 的零碎为例子,应用 Docker 仓库的形式进行装置,我的ubuntu 零碎版本如下: ...

October 27, 2021 · 3 min · jiezi

关于golang:ApacheCN-Golang-译文集-20211025-更新

Go 云原生编程 零、前言一、古代微服务架构二、应用 RESTAPI 构建微服务三、爱护微服务四、应用音讯队列的异步微服务架构五、应用 React 构建前端六、在容器中部署利用七、AWS I——基础知识、AWS Go SDK 和 EC2八、AWS II——S3、SQS、API 网关和 DynamoDB九、继续交付十、监督利用十一、迁徙十二、从这里到哪里去?Go 分布式计算 零、前言一、Go 开发环境二、了解 Goroutines三、通道和信息四、RESTful Web五、Goophr 简介六、Goophr 礼宾部七、Goophr 图书馆员八、部署 Goophr九、Web 级架构的根底Go 编程秘籍 零、前言一、I/O 和文件系统二、命令行工具三、数据转换与组合四、Go 中的错误处理五、网络编程六、所有对于数据库和存储的信息七、Web 客户端和 API八、Go 中的微服务利用九、测试 Go 代码十、并行与并发十一、分布式系统十二、反应式编程和数据流十三、无服务器编程十四、性能改良、提醒和技巧Go 规范库秘籍 零、前言一、与环境互动二、字符串和事物三、解决数字四、很久以前五、进进出出六、摸索文件系统七、连贯网络八、应用数据库九、来到服务器端十、并发性带来的乐趣十一、提醒和技巧Go Web 开发秘籍 零、前言一、创立 Go 中的第一个服务器二、应用模板、动态文件和 HTML 表单三、在 Go 中应用会话、错误处理和缓存四、在 Go 中编写和应用 RESTful Web 服务五、应用 SQL 和 NoSQL 数据库六、应用 Micro 在 Go 中编写微服务——一个微服务工具包七、在 Go 中应用 WebSocket八、应用 Go Web 利用框架——Beego九、与 Go 和 Docker 合作十、爱护 Go Web 利用的平安十一、将 Go Web 利用和 Docker 容器部署到 AWSGo Web 爬虫疾速启动指南 ...

October 26, 2021 · 2 min · jiezi

关于golang:Go语言使用benchmark进行性能测试

在日常开发中,基准测试是必不可少的,基准测试次要是通过测试CPU和内存的效率问题,来评估被测试代码的性能,进而找到更好的解决方案。 而Go语言中自带的benchmark则是一件十分神奇的测试利器。有了它,开发者能够方便快捷地在测试一个函数办法在串行或并行环境下的基准体现。指定一个工夫(默认是1秒),看测试对象在达到或超过工夫下限时,最多能被执行多少次和在此期间测试对象内存分配情况。 1 benchmark的常见用法1.1 如何写一个benchmark的基准测试import ( "fmt" "testing")func BenchmarkSprint(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { fmt.Sprint(i) }}对以上代码做如下阐明: 基准测试代码文件必须是_test.go结尾,和单元测试一样;基准测试的函数以Benchmark结尾;参数须为 *testing.B;基准测试函数不能有返回值;b.ResetTimer是重置计时器,这样能够防止for循环之前的初始化代码的烦扰;b.N是基准测试框架提供的,Go会依据零碎状况生成,不必用户设定,示意循环的次数,因为须要重复调用测试的代码,才能够评估性能;运行:go test -bench=. -run=none 命令失去以下后果 运行benchmark基准测试也要用到 go test 命令,不过咱们前面须要加上-bench=参数,承受一个表达式作为参数,匹配基准测试的函数,"."一个点示意运行所有的基准测试。 因为默认状况下 go test 会运行单元测试,为了避免单元测试的输入影响咱们查看基准测试的后果,能够应用-run=匹配一个素来没有的单元测试办法,过滤掉单元测试的输入,咱们这里应用none,因为咱们基本上不会创立这个名字的单元测试办法。 接下来再解释下输入的后果: 函数名前面的-8,示意运行时对应的 GOMAXPROCS 的值;接着的 1230048 示意运行 for 循环的次数,也就是调用被测试代码的次数,也就是在b.N的范畴内执行的次数;最初的 112.9 ns/op示意每次须要破费 112.9 纳秒;以上是测试工夫默认是1秒,也就是1秒的工夫,调用 1230048 次,每次调用破费 112.9 纳秒。如果想让测试运行的工夫更长,能够通过 -benchtime= 指定,比方-benchtime=3s,示意执行3秒。 然而咱们通过测试发现,测试1s和3s如同没啥显著区别,实际上最终性能并没有多大变动。一般来说不须要太长,罕用1s、3s、5s即可,也可忙依据业务场景来判断。 1.2 并行用法func BenchmarkSprints(b *testing.B) { b.RunParallel(func(pb *testing.PB) { for pb.Next() { // do something fmt.Sprint("代码轶事") } })}RunParallel并发的执行benchmark。RunParallel创立p个goroutine而后把b.N个迭代测试散布到这些goroutine上。goroutine的数目默认是GOMAXPROCS。如果要减少non-CPU-bound的benchmark的并个数,在执行RunParallel之前那就应用b.SetParallelism(p int)来设置,最终goroutine个数就等于p * runtime.GOMAXPROCS(0),。numProcs := b.parallelism * runtime.GOMAXPROCS(0)所以并行的用法比拟适宜IO密集型的测试对象。1.3 性能比照下面是简略写的几个示例,上面应用我后面的文章Go语言几种字符串的拼接形式比拟外面对于字符串拼接的例子进行示例: ...

October 26, 2021 · 4 min · jiezi

关于golang:测试小姐姐问我-gRPC-怎么用我直接把这篇文章甩给了她

原文链接: 测试小姐姐问我 gRPC 怎么用,我间接把这篇文章甩给了她 上篇文章 gRPC,爆赞 间接爆了,内容次要包含:简略的 gRPC 服务,流解决模式,验证器,Token 认证和证书认证。 在多个平台的浏览量都创了新高,在 oschina 更是取得了首页举荐,浏览量到了 1w+,这曾经是我单篇浏览的顶峰了。 看来只有用心写还是有播种的。 这篇咱们还是从实战登程,次要介绍 gRPC 的公布订阅模式,REST 接口和超时管制。 相干代码我会都上传到 GitHub,感兴趣的小伙伴能够去查看或下载。 公布和订阅模式公布订阅是一个常见的设计模式,开源社区中曾经存在很多该模式的实现。其中 docker 我的项目中提供了一个 pubsub 的极简实现,上面是基于 pubsub 包实现的本地公布订阅代码: package mainimport ( "fmt" "strings" "time" "github.com/moby/moby/pkg/pubsub")func main() { p := pubsub.NewPublisher(100*time.Millisecond, 10) golang := p.SubscribeTopic(func(v interface{}) bool { if key, ok := v.(string); ok { if strings.HasPrefix(key, "golang:") { return true } } return false }) docker := p.SubscribeTopic(func(v interface{}) bool { if key, ok := v.(string); ok { if strings.HasPrefix(key, "docker:") { return true } } return false }) go p.Publish("hi") go p.Publish("golang: https://golang.org") go p.Publish("docker: https://www.docker.com/") time.Sleep(1) go func() { fmt.Println("golang topic:", <-golang) }() go func() { fmt.Println("docker topic:", <-docker) }() <-make(chan bool)}这段代码首先通过 pubsub.NewPublisher 创立了一个对象,而后通过 p.SubscribeTopic 实现订阅,p.Publish 来公布音讯。 ...

October 25, 2021 · 6 min · jiezi

关于golang:gozero-实战之-blog-系统

go-zero 实战我的项目:blog本文以 blog 的网站后盾为例,着重介绍一下如何应用 go-zero 开发 blog 的用户模块。 本文波及的所有材料都已上传 github 仓库 kougazhang/go-zero-demo,感兴趣的同学能够自行下载。 用户模块是后盾管理系统常见的模块,它的性能大家也十分相熟。治理用户波及到前端操作,用户信息长久化又离不开数据库。所以用户模块堪称是 "麻雀虽小五脏俱全"。本文将具体介绍一下如何应用 go-zero 实现用户模块性能,如:用户登录、增加用户、删除用户、批改用户、查问用户 等(残缺的 api 文件请参考仓库代码)。 blog 整体架构 最下面是 api 网关层。go-zero 须要 api 网关层来代理申请,把 request 通过 gRPC 转发给对应的 rpc 服务去解决。这块把具体申请转发到对应的 rpc 服务的业务逻辑,须要手写。 接下来是 rpc 服务层。上图 rpc 服务中的 user 就是接下来向大家演示的模块。每个 rpc 服务能够独自部署。服务启动后会把相干信息注册到 ETCD,这样 api 网关层就能够通过 ECTD 发现具体服务的地址。rpc 服务解决具体申请的业务逻辑,须要手写。 最初是 model 层。model 层封装的是数据库操作的相干逻辑。如果是查问类的相干操作,会先查问 redis 中是否有对应的缓存。非查问类操作,则会间接操作 MySQL。goctl 能通过 sql 文件生成一般的 CRDU 代码。目前 goctl 这部分性能反对 MySQL、PostgreSQL、MongoDB。 上面演示如何应用 go-zero 开发一个 blog 零碎的用户模块。 ...

October 25, 2021 · 5 min · jiezi

关于golang:golang微服务之注册与发现

在微服务中,服务注册与发现是必不可少的一环,其中etcd,zookeeper,consul在golang中较为罕用。 程序对于这种中间件的依赖,都倡议加一层接口,基于接口去实现 上面会分层三大板块去阐明 接口定义因为是下层调用是基于接口调用的,所以须要将discover和register接口化。 在discover发现endpoint有变动时,须要调用callback去更新本地的缓存,所以也须要endpoint的接口。如下 const ( EtcdBackend = "etcd" ZookeeperBackend = "zookeeper" ConsulBackend = "consul")// 服务发现接口 type Discover interface { // Start watch with block, 须要一个callback去更新本地endpoint Start(callback EndpointCacher) Stop()}// 服务注册接口 type Register interface { Start() error Stop() error}// endpoint接口type EndpointCacher interface { AddOrUpdate(endpoint string, attribute []byte) Delete(endpoint string) AddError(err error) Error(err error)}接口定义完了,须要有连贯注册核心的信息配置。// 服务发现端配置type DiscoverConfig struct { BackendType string // one of etcd|consul|zookeeperBackendEndPoints []string // register backend endpointDiscoverPrefix stringServiceName stringHostName string} ...

October 22, 2021 · 9 min · jiezi

关于golang:golang构建PC客户端的一些实践

前言我的技术栈次要是vue3、go、java等。 前端对界面的表达力是最强的,所以暂不思考原生相干的ui技术,如fyne、QT bind。 而单纯的基于web的跨端计划,尽管能提供一些根本的零碎级操作,但对于自定义的底层需要还是须要go来解决。 我选的技术计划就落在了vue+go上,从原理上分为: 独立UI+websocket+底层Go:electron, tauriGo管制的UI+websocket+底层Go:lorca, webviewGo残缺打包的UI+jsBridge:wails计划比拟electron算是web跨端中最具生态和成熟的计划,几年内也陆陆续续用过几次。 但最终也是因为它打包太大、依赖装置常常失败、打包上也遇到不少坑,太过折腾就放弃了。 taurihttps://tauri.studio/en/ 底层由rust实现,UI渲染依赖于webview2或webkit,提供js api拜访零碎性能、以及和rust底层交互。 以前端开发为主视角,tauri的相干库搁置于前端工程中的src-tauri文件,对前端我的项目的侵入性较小。 能打包成一个独立的程序,在OSX下测试的成果还不错。 然而在windows下搭建环境时会遇到依赖装置谬误,打包工具是基于node的,过于折腾就先不去解决了(穿插编译的反对也在打算内)。 目前tauri公布了beta版本,go binding的反对还在打算内,所以如果我要以go为底层的话,只能采纳websocket或http的通信形式。 tauri还打算公布android和ios版本,能够期待下。 lorcahttps://github.com/zserge/lorca 原理上是从go调用了chrome,而后启动了定制化的chrome界面,所以须要依赖chrome。 这是我用的比拟多的一个,因为间接能够从go来管制启动敞开,加载页面等等。 毛病也很显著,开启利用自身是chrome的一个窗口,所以不能做过多的定制化。和go之间的通信,须要自行用websocket或http。 其兄弟我的项目 github.com/zserge/webview,提供了较多的定制性能,将webview内置。然而在编译打包时还是有一些坑,以及有些性能没找到用法(可能得用c++?),比方全屏。 wailshttps://wails.io/ 和tauri相似,然而底层为go,而且是以go我的项目的主视角来打包,也是基于webview2或webkit。 目前推出v2版本,临时只反对了windows的打包(在windows中须要webview2环境反对)。 v2的总体应用体验很不错,劣势如下: 能够做到前后端拆散开发,打包时只须要在go中引入前端打包好的dist资源即可。提供了js和go之间的过程内通信。打包工具是基于go的,根本能防止基于node打包时依赖装置的“劣根性”。目前在windows中打包运行测试很顺畅。我认为在go+web这个方向,wails v2 应该是目前最好用并且符合要求的一款。 小劣势就是还在beta中,osx和linux的行将推出。有些浏览器的深度定制没有凋谢进去,比方webview2对不平安资源的拜访权限问题,须要底层反对后开发配置进去,不过好在社区响应及时。 总结我会继续关注wails和tauri的停顿。 我的项目利用中,将从lorca逐渐迁徙到wails的形式中。

October 21, 2021 · 1 min · jiezi

关于golang:不得不知的-Go-SliceHeader-和-StringHeader你知道吗

大家好,我是煎鱼。 在 Go 语言中总是有一些看上去奇奇怪怪的货色,咋一眼一看感觉很相熟,但又不了解其在 Go 代码中的实际意义,面试官却爱问... 明天要给大家介绍的是 SliceHeader 和 StringHeader 构造体,理解分明他到底是什么,又有什么用,并且会在最初给大家介绍 0 拷贝转换的内容。 一起欢快地开始吸鱼之路。 SliceHeaderSliceHeader 如其名,Slice + Header,看上去很直观,实际上是 Go Slice(切片)的运行时体现。 SliceHeader 的定义如下: type SliceHeader struct { Data uintptr Len  int Cap  int}Data:指向具体的底层数组。Len:代表切片的长度。Cap:代表切片的容量。既然晓得了切片的运行时体现,那是不是就意味着咱们能够本人造一个? 在日常程序中,能够利用规范库 reflect 提供的 SliceHeader 构造体造一个: func main() {  // 初始化底层数组 s := [4]string{"脑子", "进", "煎鱼", "了"} s1 := s[0:1] s2 := s[:]  // 结构 SliceHeader sh1 := (*reflect.SliceHeader)(unsafe.Pointer(&s1)) sh2 := (*reflect.SliceHeader)(unsafe.Pointer(&s2)) fmt.Println(sh1.Len, sh1.Cap, sh1.Data) fmt.Println(sh2.Len, sh2.Cap, sh2.Data)}你认为输入后果是什么,这两个新切片会指向同一个底层数组的内存地址吗? 输入后果: 1 4 8246343309364 4 824634330936两个切片的 Data 属性所指向的底层数组是统一的,Len 属性的值不一样,sh1 和 sh2 别离是两个切片。 疑难为什么两个新切片所指向的 Data 是同一个地址的呢? 这其实是 Go 语言自身为了缩小内存占用,进步整体的性能才这么设计的。 将切片复制到任意函数的时候,对底层数组大小都不会影响。复制时只会复制切片自身(值传递),不会波及底层数组。 也就是在函数间传递切片,其只拷贝 24 个字节(指针字段 8 个字节,长度和容量别离须要 8 个字节),效率很高。 坑这种设计也引出了新的问题,在平时通过 s[i:j] 所生成的新切片,两个切片底层指向的是同一个底层数组。 假如在没有超过容量(cap)的状况下,对第二个切片操作会影响第一个切片。 这是很多 Go 开发常会碰到的一个大 “坑”,不分明的排查了很久的都不得而终。 StringHeader除了 SliceHeader 外,Go 语言中还有一个典型代表,那就是字符串(string)的运行时体现。 StringHeader 的定义如下: type StringHeader struct {   Data uintptr   Len  int}Data:寄存指针,其指向具体的存储数据的内存区域。Len:字符串的长度。可得悉 “Hello” 字符串的底层数据如下: var data = [...]byte{    'h', 'e', 'l', 'l', 'o',}底层的存储示意图如下: ...

October 20, 2021 · 1 min · jiezi

关于golang:极客大学云原生训练营

download:极客大学-云原生训练营coding=utf8class UrlManager(object): def __init__(self): self.new_urls = set() self.old_urls = set()#增加新的urldef _add_new_url(self, url): if url is None: return if url not in self.new_urls and url not in self.old_urls: self.new_urls.add(url)#批量增加urldef add_new_urls(self,urls): if urls is None or len(urls) == 0: return for url in urls: self._add_new_url(url)#是否有新的urldef has_new_url(self): return len(self.new_urls) != 0#获取新的urldef get_new_url(self): new_url = self.new_urls.pop() self.old_urls.add(new_url) return new_urlURL下载模块,负责把网页的内容下载下来,使用urllib2库进行下载。 coding utf8import urllib2class HtmlDownloader(object): def download(self, url): if url is None: return None response = urllib2.urlopen(url); if response.getcode == 200: return None return response.read()URL解析模块,使用Beautifulsoup把以后网页的其余链接和网页简介解析进去。 ...

October 20, 2021 · 1 min · jiezi

关于golang:Go经典算法之-冒泡排序-选择排序-快速排序-归并排序

以下是几种经典的排序算法 //抉择排序 O(n^2)func selectSort(nums []int) []int { for i := 0; i < len(nums)-1; i++ { for j := i + 1; j < len(nums); j++ { if nums[i] > nums[j] { nums[i], nums[j] = nums[j], nums[i] } } } return nums}//冒泡排序 O(n^2)func buSort(nums []int) []int { for i := 0; i < len(nums)-1; i++ { for j := 0; j < len(nums)-1-i; j++ { if nums[j] > nums[j+1] { nums[j], nums[j+1] = nums[j+1], nums[j] } } } return nums}//疾速排序 nlog2nfunc quickSort(nums []int, begin int, end int) { if begin >= end { return } pivot := partition(nums, begin, end) quickSort(nums, begin, pivot-1) quickSort(nums, pivot+1, end)}func partition(nums []int, begin int, end int) int { var pivot = end var current = begin for i := begin; i < end; i++ { if nums[i] < nums[pivot] { nums[i], nums[current] = nums[current], nums[i] current++ } } nums[current], nums[pivot] = nums[pivot], nums[current] return current}//归并排序 nlog2nfunc mergeSort(nums []int, begin int, end int) { if begin >= end { return } mid := (begin + end)>>1 mergeSort(nums, begin, mid) mergeSort(nums, mid+1, end) merge(nums, begin, mid, end)}func merge(nums []int, begin int, mid int, end int) { var temp []int var i, j = begin, mid + 1 for i <= mid && j <= end { if nums[i] >= nums[j] { temp = append(temp, nums[j]) j++ } else { temp = append(temp, nums[i]) i++ } } if i <= mid { temp = append(temp, nums[i:mid+1]...) } if j <= end { temp = append(temp, nums[j:end+1]...) } for i := 0; i < len(temp); i++ { nums[begin+i] = temp[i] }}

October 19, 2021 · 2 min · jiezi

关于golang:听说99-的-Go-程序员都被-defer-坑过

原文链接: 据说,99% 的 Go 程序员都被 defer 坑过 先申明:我被坑过。 之前写 Go 专栏时,写过一篇文章:Go 专栏|错误处理:defer,panic 和 recover。有小伙伴留言说:情理都懂,但还是不晓得怎么用,而且还总呈现莫名微妙的问题。 出问题就对了,这个小东西坏的很,一不留神就出错。 所以,面对这种状况,咱们明天就不讲道理了。间接把我收藏多年的代码一把梭,凭借多年踩坑经验和写 BUG 教训,我要站着把这个坑迈过去。 <p style="text-align:center;color:#1e819e;font-size:1.2em;font-weight: bold;">一、</p> 先来一个简略的例子热热身: package mainimport ( "fmt")func main() { defer func() { fmt.Println("first") }() defer func() { fmt.Println("second") }() fmt.Println("done")}输入: donesecondfirst这个比较简单,defer 语句的执行程序是按调用 defer 语句的倒序执行。 <p style="text-align:center;color:#1e819e;font-size:1.2em;font-weight: bold;">二、</p> 看看这段代码有什么问题? for _, filename := range filenames { f, err := os.Open(filename) if err != nil { return err } defer f.Close()}这段代码其实很危险,很可能会用尽所有文件描述符。因为 defer 语句不到函数的最初一刻是不会执行的,也就是说文件始终得不到敞开。所以切记,肯定不要在 for 循环中应用 defer 语句。 ...

October 18, 2021 · 4 min · jiezi

关于golang:GO进阶训练营完结wumi

download:GO进阶训练营【完结】自定义属性以任何形式增加到组件实例,可能通过this拜访这是向每个组件实例增加属性$router和$axios的示例: import { createApp } from "vue";import { Router, createRouter } from "vue-router";import axios from "axios";declare module "@vue/runtime-core" { interface ComponentCustomProperties { $router: Router}}// 无效地将路由器增加到每个组件实例const app = createApp({});const router = createRouter();app.config.globalProperties.$router = router;pp.config.globalProperties.$http = axios;const vm = app.mount("#app");// 咱们可能从实例拜访路由器vm.$router.push("/");

October 18, 2021 · 1 min · jiezi

关于golang:你了解微服务的超时传递吗

为什么须要超时管制?很多连锁故障的场景下的一个常见问题是服务器正在耗费大量资源解决那些早曾经超过客户端截止工夫的申请,这样的后果是,服务器耗费大量资源没有做任何有价值的工作,回复曾经超时的申请是没有任何意义的。 超时管制能够说是保障服务稳定性的一道重要的防线,它的实质是疾速失败(fail fast),良好的超时控制策略能够尽快清空高提早的申请,尽快开释资源防止申请的沉积。 服务间超时传递如果一个申请有多个阶段,比方由一系列 RPC 调用组成,那么咱们的服务应该在每个阶段开始前查看截止工夫以防止做无用功,也就是要查看是否还有足够的剩余时间解决申请。 一个常见的谬误实现形式是在每个 RPC 服务设置一个固定的超时工夫,咱们应该在每个服务间传递超时工夫,超时工夫能够在服务调用的最上层设置,由初始申请触发的整个 RPC 树会设置同样的相对截止工夫。例如,在服务申请的最上层设置超时工夫为3s,服务A申请服务B,服务B执行耗时为1s,服务B再申请服务C这时超时工夫残余2s,服务C执行耗时为1s,这时服务C再申请服务D,服务D执行耗时为500ms,以此类推,现实状况下在整个调用链里都采纳雷同的超时传递机制。 如果不采纳超时传递机制,那么就会呈现如下状况: 服务A给服务B发送一个申请,设置的超时工夫为3s服务B解决申请耗时为2s,并且持续申请服务C如果应用了超时传递那么服务C的超时工夫应该为1s,但这里没有采纳超时传递所以超时工夫为在配置中写死的3s服务C继续执行耗时为2s,其实这时候最上层设置的超时工夫已截止,如下的申请无意义持续申请服务D如果服务B采纳了超时传递机制,那么在服务C就应该立即放弃该申请,因为曾经到了截止工夫,客户端可能曾经报错。咱们在设置超时传递的时候个别会将传递进来的截止工夫缩小一点,比方100毫秒,以便将网络传输工夫和客户端收到回复之后的解决工夫思考在内。 过程内超时传递不光服务间须要超时传递过程内同样须要进行超时传递,比方在一个过程内串行的调用了Mysql、Redis和服务B,设置总的申请工夫为3s,申请Mysql耗时1s后再次申请Redis这时的超时工夫为2s,Redis执行耗时500ms再申请服务B这时候超时工夫为1.5s,因为咱们的每个中间件或者服务都会在配置文件中设置一个固定的超时工夫,咱们须要取剩余时间和设置工夫中的最小值。 context实现超时传递context原理非常简单,但性能却十分弱小,go的规范库也都已实现了对context的反对,各种开源的框架也实现了对context的反对,context未然成为了规范,超时传递也依赖context来实现。 咱们个别在服务的最上层通过设置初始context进行超时管制传递,比方设置超时工夫为3s ctx, cancel := context.WithTimeout(context.Background(), time.Second*3)defer cancel()当进行context传递的时候,比方上图中申请Redis,那么通过如下形式获取剩余时间,而后比照Redis设置的超时工夫取较小的工夫 dl, ok := ctx.Deadline()timeout := time.Now().Add(time.Second * 3)if ok := dl.Before(timeout); ok { timeout = dl}服务间超时传递次要是指 RPC 调用时候的超时传递,对于 gRPC 来说并不需要要咱们做额定的解决,gRPC 自身就反对超时传递,原理和下面差不多,是通过 metadata 进行传递,最终会被转化为 grpc-timeout 的值,如下代码所示 grpc-go/internal/transport/handler_server.go:79 if v := r.Header.Get("grpc-timeout"); v != "" { to, err := decodeTimeout(v) if err != nil { return nil, status.Errorf(codes.Internal, "malformed time-out: %v", err) } st.timeoutSet = true st.timeout = to}超时传递是保障服务稳定性的一道重要防线,原理和实现都非常简单,你们的框架中实现了超时传递了吗?如果没有的话就连忙动起手来吧。 ...

October 18, 2021 · 1 min · jiezi

关于golang:分布式事务框架dtm131发布添加postgres支持

更新日志反对Postgres增加dtmgrpc独立包以及例子重构默认docker-compose配置,更加疾速的启动GO分布式事务管理服务DTM是首款golang开发的跨语言分布式事务管理器,优雅的解决了幂等、空弥补、悬挂等分布式事务难题。提供了简略易用、高性能、易程度扩大的分布式事务解决方案。 受邀加入中国数据库大会分享多语言环境下分布式事务实际 谁在应用dtmIvydad 常青藤爸爸 Eglass 视咖镜小二 极欧科技 [金数智联]() 亮点极易接入 反对HTTP,提供非常简单的接口,极大升高上手分布式事务的难度,老手也能疾速接入应用简略 开发者不再放心悬挂、空弥补、幂等各类问题,框架层代为解决跨语言 可适宜多语言栈的公司应用。不便go、python、php、nodejs、ruby、c# 各类语言应用。易部署、易扩大 仅依赖mysql,部署简略,易集群化,易程度扩大多种分布式事务协定反对 TCC、SAGA、XA、事务音讯与其余框架比照目前开源的分布式事务框架,暂未看到非Java语言有成熟的框架。而Java语言的较多,有阿里的SEATA、华为的ServiceComb-Pack,京东的shardingsphere,以及himly,tcc-transaction,ByteTCC等等,其中以seata利用最为宽泛。 上面是dtm和seata的次要个性比照: 个性DTMSEATA备注反对语言Go、Java、python、php、c#...Javadtm可轻松接入一门新语言异样解决子事务屏障主动解决手动解决dtm解决了幂等、悬挂、空弥补TCC事务✓✓ XA事务✓✓ AT事务✗✓AT与XA相似,性能更好,但有脏回滚SAGA事务简略模式状态机简单模式dtm的状态机模式在布局中事务音讯✓✗dtm提供相似rocketmq的事务音讯单服务多数据源✓✗ 通信协议HTTP、gRPCdubbo等协定,无HTTPdtm对云原生更加敌对从下面比照的个性来看,如果您的语言栈蕴含了Java之外的语言,那么dtm是您的首选。如果您的语言栈是Java,您也能够抉择接入dtm,应用子事务屏障技术,简化您的业务编写。 教程与文档如果您感觉yedf/dtm不错,或者对您有帮忙,请赏颗星吧!

October 18, 2021 · 1 min · jiezi

关于golang:我们一起来学RabbitMQ-五RabbitMQ-应知应会的面试题

MQ 是什么?MQ(Message Queue)音讯队列 用队列机制来实现软件之间的通信,消费者能够到指定队列拉取音讯,或者订阅相应的队列,由MQ服务端给其推送音讯 什么是队列?是一种数据结构,遵循 FIFO (先进先出)准则 凭啥要应用 MQ , MQ 有啥劣势?异步通信将以前也不中不必要的同步操作,优化成异步操作,进步性能 业务解耦将原有A模块间接调用B模块的接口,优化成,A模块的申请给到MQ,A模块的事件就做完了 MQ会被动推给B模块,或者B模块本人来拉 流量削峰当某一时间大量的流量打到服务器上,服务器一时间无奈接受,会宕机 这个时候,若申请都是从音讯队列外面进去,则可能保障这种大流量的状况下,服务器依然可能有序的稳固的解决申请 MQ 有啥劣势呢?零碎可用性升高,对外部有依赖了须要思考 MQ 音讯失落,反复生产的问题须要破费精力保障音讯的程序性,一致性罕用 MQ 性能比照 ActiveMQRabbitMQRocketMQKafka开发语言javaerlangjavascala单机吞吐量万级万级十万级十万级时效性ms级us级ms级ms级以内可用性高主从架构高主从架构十分高分布式架构十分高分布式架构音讯可靠性较低概率失落音讯根本不丢能够做到根本不丢能够做到根本不丢性能反对反对性能全性能好延时低并发能力强MQ 性能较欠缺反对分布式,扩展性好次要用于大数据和日志采集MQ 如何防止音讯沉积进步生产速率(集群的形式)消费者批量获取音讯进行生产MQ 如何防止消费者反复生产(幂等性问题)全局ID(减少标记位) + 保障业务一致性MQ 如何保障音讯不失落音讯确认机制长久化音讯 ACKMQ 如何保障音讯程序一致性绑定同一个消费者和队列MQ 推与拉取架构模型是怎么样的?MQ 服务器与消费者建设长连贯后,MQ 服务器会被动推数据给到消费者当消费者第一次启动的时候,会去找MQ 服务器拉数据mq有哪些生产模式推模式注册一个消费者后,RabbitMQ会在音讯可用时,主动将音讯进行推送给消费者。这种形式效率最高最及时。拉模式属于一种轮询模型,发送一次get申请,取得一个音讯。如果此时RabbitMQ中没有音讯,会取得一个示意空的回复。主动确认消费者生产音讯的时候,将主动向RabbitMQ进行确认。 手动确认消费者生产音讯的时候,手动调用相应函数进行ack 应答 qos预取模式在确认音讯被接管之前,消费者能够事后要求接管肯定数量的音讯,在解决完肯定数量的音讯后,批量进行确认 当然,如果消费者应用程序在确认音讯之前解体,则所有未确认的音讯将被从新发送给其余消费者 RabbitMQ 中既然有了connections 为什么还要有 channel?connection 是什么 connection 是 生产者或消费者与 RabbitMQ Broker 建设的连贯,是一个TCP连贯 一旦 TCP 连贯建设起来,客户端紧接着能够创立一个 AMQP 信道(Channel),每个信道都会被指派一个惟一的 ID 信道是建设在 Connection 之上的虚构连贯,多个信道复用一个TCP连贯,能够缩小性能开销,同时也便于管理 因为一个利用须要向RabbitMQ 中生成或者生产音讯的话,都要建一个TCP连贯,TCP连贯开销十分大,如果遇到应用顶峰,性能瓶颈也随之浮现 信道复用连贯劣势: 复用TCP连贯,缩小性能开销,便于管理RabbitMQ 保障每一个信道的私密性 当每个信道的流量不是很大时,复用繁多的 Connection 能够在产生性能瓶颈的状况下无效地节俭 TCP 连贯资源 ...

October 15, 2021 · 2 min · jiezi

关于golang:用Kratos写项目吧

A. Kratos疾速入门官网文档中写的比拟好,倡议大略浏览一遍,点击此处查看文档,重点“疾速开始.目录构造”、“疾速开始.创立我的项目”、“框架组件”里的内容 1. 装置命令: go get -u github.com/go-kratos/kratos/cmd/kratos/v2@latest 2. 目录构造与DDD的对应关系这个十分重要,能够从DDD层面下来了解框架的设计和整顿咱们写代码的思路B. 开始写我的项目吧!1. 初始化我的项目创立我的项目命令: kratos new 你的我的项目 我的项目编译和运行命令行运行形式cd 你的我的项目make allkratos runGolang运行形式(举荐,反对断点调试)PB文件有更新时需在编译前运行命令如下命令: make all GoBuild配置RunKind: PackagePackagePath:你的我的项目/cmd/你的我的项目 WorkingDirectory: “你的我的项目”的绝对路径Go Tool arguments: -o “你的我的项目”的绝对路径/bin/server -ldflags "-X main.Version=程序版本-X main.Name=服务名" main.Version和main.Name 在Kratos程序外部中会应用Program arguments: --conf 你的配置文件绝对路径 2. 定义API Protocolbuffers新建PB文件cd 你的我的项目kratos proto add api/问题域/版本/畛域对象.proto名称解析问题域:指的是你要解决的这个服务下某一个子问题形象的名称(倡议子问题不要太细) 版本:API的版本,通常为v1、v2、v3畛域对象:就是你在这个子问题下给出的解法的形象,个别能够是性能名或者子问题域的外围对象名,依据理论状况而定举个例子:问题域为“元数据处理”,形象名为 metadata版本定为v1解决子问题为“提供环境数据管理”,形象名为 environment最终成果为 api/metadata/v1/environment.proto 定义PB文件初始化PB文件示例生成的PB文件会有根本的curd的,示例如下syntax = "proto3"; package api.metadata.v1; option go_package = "test/api/metadata/v1;v1";option java_multiple_files = true;option java_package = "api.metadata.v1"; service Environment { rpc CreateEnvironment (CreateEnvironmentRequest) returns (CreateEnvironmentReply); rpc UpdateEnvironment (UpdateEnvironmentRequest) returns (UpdateEnvironmentReply); rpc DeleteEnvironment (DeleteEnvironmentRequest) returns (DeleteEnvironmentReply); rpc GetEnvironment (GetEnvironmentRequest) returns (GetEnvironmentReply); rpc ListEnvironment (ListEnvironmentRequest) returns (ListEnvironmentReply);} ...

October 15, 2021 · 3 min · jiezi

关于golang:Go进阶数据结构map

哈希表是除了数组之外,最常见的数据结构。Go 语言中的 map 底层就是哈希表,能够很不便地提供键值对的映射。 个性未初始化的 map 的值为 nil,向值为 nil 的 map 增加元素会触发 panic,这是老手容易犯的谬误之一。 map 操作不是原子的,多个协程同时操作 map 时有可能产生读写抵触,此时会触发 panic 导致程序退出。如果须要并发读写,能够应用锁来爱护 map,也能够应用规范库 sync 包中的 sync.Map。 实现原理数据结构map 的底层数据结构由 runtime/map.go/hmap 定义: type hmap struct { count int // 元素个数,调用 len(map) 时,间接返回此值 flags uint8 B uint8 // buckets 数组长度的对数 noverflow uint16 // overflow 的 bucket 近似数 hash0 uint32 // 哈希种子,为哈希函数的后果引入随机性,在调用哈希函数时作为参数传入 buckets unsafe.Pointer // 指向 buckets 数组,大小为 2^B,元素个数为0时为 nil oldbuckets unsafe.Pointer // 在扩容时用于保留旧 buckets 数组,大小为 buckets 的一半 nevacuate uintptr // 批示扩容进度,小于此地址的 buckets 都已迁徙实现 extra *mapextra // 附加项}buckets 数组中保留了 bucket 的指针,bucket 很多时候被翻译为桶,它是 map 键值对的真正载体。它的数据结构定义如下: ...

October 12, 2021 · 3 min · jiezi

关于golang:gRPC爆赞

原文链接: gRPC,爆赞 gRPC 这项技术真是太棒了,接口束缚严格,性能还高,在 k8s 和很多微服务框架中都有利用。 作为一名程序员,学就对了。 之前用 Python 写过一些 gRPC 服务,当初筹备用 Go 来感受一下原汁原味的 gRPC 程序开发。 本文的特点是间接用代码谈话,通过开箱即用的残缺代码,来介绍 gRPC 的各种应用办法。 代码曾经上传到 GitHub,上面正式开始。 介绍gRPC 是 Google 公司基于 Protobuf 开发的跨语言的开源 RPC 框架。gRPC 基于 HTTP/2 协定设计,能够基于一个 HTTP/2 链接提供多个服务,对于挪动设施更加敌对。 入门首先来看一个最简略的 gRPC 服务,第一步是定义 proto 文件,因为 gRPC 也是 C/S 架构,这一步相当于明确接口标准。 proto syntax = "proto3";package proto;// The greeting service definition.service Greeter { // Sends a greeting rpc SayHello (HelloRequest) returns (HelloReply) {}}// The request message containing the user's name.message HelloRequest { string name = 1;}// The response message containing the greetingsmessage HelloReply { string message = 1;}应用 protoc-gen-go 内置的 gRPC 插件生成 gRPC 代码: ...

October 12, 2021 · 7 min · jiezi

关于golang:Go进阶数据结构string

个性从规范库文件 src/builtin/builtin.go 中能够看到内置类型 string 的定义和形容: // string is the set of all strings of 8-bit bytes, conventionally but not// necessarily representing UTF-8-encoded text. A string may be empty, but// not nil. Values of string type are immutable.type string string从中咱们能够看出 string 是 8 比特字节的汇合,通常但并不一定是 UTF-8 编码的文本。另外,string 能够为空(长度为0),但不会是 nil,并且 string 对象不可批改。 字符串能够应用双引号赋值,也能够应用反单引号赋值。应用双引号申明的字符串和其余语言中的字符串没有太多的区别,它只能用于单行字符串的初始化,如果字符串外部呈现换行符或双引号等特殊符号,须要应用 \ 符号本义;而反引号申明的字符串能够解脱单行的限度,并且能够在字符串外部间接应用特殊符号,在遇到须要手写 JSON 或者其余简单数据格式的场景下十分不便。 实现原理数据结构源码包 src/runtime/string.go:stringStruct 定义了 string 的数据结构: type stringStruct struct { str unsafe.Pointer len int}构造很简略,两个字段别离示意字符串的首地址和长度。 生成字符串时,会先构建 stringStruct 对象,再转换成 string,代码如下: ...

October 11, 2021 · 2 min · jiezi

关于golang:Go学习笔记Go编译器简介

1.编译器 1.1 三阶段编译器编译器前端: 次要用于了解源代码、扫描解析源代码并进行语义表白IR: Intermediate Representation,可能有多个,编译器会应用多个 IR 阶段、多种数据结构示意程序,并在两头阶段对代码进行屡次优化优化器: 次要目标是升高程序资源的耗费,但有实践曾经表明某些优化存在着NP难题,所以编译器无奈进行最佳优化,通常罕用折中计划编译后端: 次要用于生成特定指标机器上的程序,可能是可执行文件,也可能是须要进一步解决的obj、汇编语言等1.2 Go语言编译器go compiler: Go语言编译器的次要源代码位于 src/cmd/compile/internal目录下:Tips: 留神:大写的GC示意垃圾回收2.词法解析Go编译器会扫描源代码,并且将其符号(token)化,例如 “+”、”-“操作符会被转化为_IncOp,赋值符号 ":=" 会被转化为 _Define。token 是用iota申明的整数,定义在 go/src/cmd/compile/internal/syntax/tokens.go 文件中。Go语言规范库 go/src/go/scanner、go/src/go/token 中提供了许多接口用于扫描源代码。Go 编译器把文件内容词法扫后,将每个标识符与运算符都被特定的 token 代替。2.1 scanner.go 代码简介scanner.go 文件位于 go/src/go/scanner 目录下,实现了Go源代码的扫描,采纳一个 []byte 作为源,而后能够通过反复调用 Scan 办法对其进行标记。 type ErrorHandler func(pos token.Position, msg string): 能够向Scanner.Init 提供 ErrorHandler,如果遇到语法错误并装置了处理程序,则调用处理程序并提供地位和谬误音讯,该地位指向谬误标记的开始。type Scanner struct: 扫描器在解决给定文本时放弃扫描器的外部状态。const bom = 0xFEFF: 字节程序标记,只容许作为第一个字符。next()函数: 将下一个Unicode字符读入 s.ch,S.ch < 0 示意文件完结。peek()函数: Peek返回追随最近读取字符的字节,而不推动扫描仪,如果扫描器在 EOF, peek 返回 0。type Mode uint: 模式值是一组标记(或 0),它们管制扫描行为。 const (ScanComments Mode = 1 << iota // 返回正文作为正文标记dontInsertSemis // 不要主动插入分号-仅用于测试)Init()函数: Init 通过在 src 的结尾设置扫描器来让扫描器 s 标记文本src,扫描器应用文件集文件来获取地位信息,并为每一行增加行信息。当从新扫描雷同的文件时,能够重复使用雷同的文件,因为曾经存在的行信息被忽略了。如果文件大小与 src 大小不匹配,Init 会导致 panic。如果遇到语法错误且 err 不是nil,则调用 Scan 将调用谬误处理程序 err。此外,对于遇到的每个谬误, Scanner 字段 ErrorCount 减少 1,mode 参数决定如何解决正文。Tips: 留神,如果文件的第一个字符有谬误,Init 可能会调用 err。updateLineInfo()函数: updateLineInfo 将输出的正文文本在 offset off 处解析为一个行指令。如果胜利,它将依据 line 指令更新下一个地位的行信息表。isLetter()函数: 判断 rune 字符是否为 a-z、A-Z、_ 或合乎 utf8.RuneSelf 定义的字符。 ...

October 11, 2021 · 5 min · jiezi

关于golang:多图详解万星-Restful-框架原理与实现

rest框架概览咱们先通过 go-zero 自带的命令行工具 goctl 来生成一个 api service,其 main 函数如下: func main() { flag.Parse() var c config.Config conf.MustLoad(*configFile, &c) ctx := svc.NewServiceContext(c) server := rest.MustNewServer(c.RestConf) defer server.Stop() handler.RegisterHandlers(server, ctx) fmt.Printf("Starting server at %s:%d...\n", c.Host, c.Port) server.Start()}解析配置文件将配置文件传入,初始化 serviceContext初始化 rest server将 context 注入 server 中: 注册路由将 context 中的启动的 endpoint 同时注入到 router 当中启动 server接下来咱们来一步步解说其设计原理!Let's Go! web框架从日常开发教训来说,一个好的 web 框架大抵须要满足以下个性: 路由匹配/多路由反对反对自定义中间件框架和业务开发齐全解耦,不便开发者疾速开发参数校验/匹配监控/日志/指标等服务自查性能服务自爱护(熔断/限流)go-zero rest设计https://github.com/zeromicro/go-zero/tree/master/rest概览借助 context (不同于 gin 的 context),将资源初始化好 → 保留在 serviveCtx 中,在 handler 中共享(至于资源池化,交给资源本人解决,serviveCtx 只是入口和共享点)独立 router 申明文件,同时退出 router group 的概念,不便开发者整顿代码构造内置若干中间件:监控/熔断/鉴权等利用 goctl codegen + option 设计模式,不便开发者本人管制局部中间件的接入 ...

October 11, 2021 · 2 min · jiezi

关于golang:深度剖析分布式事务性能

随着微服务的大规模利用,跨微服务的分布式事务也越来越多,那么分布式事务的性能到底怎么样?性能会降落多少?是否满足业务需要?这些指标关系到分布式事务是否顺利的引入到生产利用,是大家十分关怀的问题。 本文尝试深入分析分布式事务带来的额定开销,利用中的哪些因素会影响最终的性能,瓶颈点在哪里,如何晋升性能。本文以反对多语言的分布式事务管理器https://github.com/yedf/dtm的saga事务作为性能测试的样本,对性能测试的后果,进行深度分析。 测试环境机型CPU/内存存储零碎Mysql阿里云ecs.c7.xlarge4核8G500G ESSD IOPS 26800Ubuntu 20.04Docker mysql:5.7测试过程# 在dtm目录下docker-compose -f helper/compose.mysql.yml up -d # 启动Mysql# 运行sysbench对mysql进行测试sysbench oltp_write_only.lua --time=60 --mysql-host=127.0.0.1 --mysql-port=3306 --mysql-user=root --mysql-password= --mysql-db=sbtest --table-size=1000000 --tables=10 --threads=10 --events=999999999 --report-interval=10 preparesysbench oltp_write_only.lua --time=60 --mysql-host=127.0.0.1 --mysql-port=3306 --mysql-user=root --mysql-password= --mysql-db=sbtest --table-size=1000000 --tables=10 --threads=10 --events=999999999 --report-interval=10 rungo run app/main.go bench > /dev/nul # 启动dtm的bench服务,日志较多,重定向到nul设施bench/run-dtm.sh # 新启动命令行,运行dtm相干的各项测试PS:如果您须要入手进行测试,建议您购买香港或国外的主机,这样相干的github、docker拜访会快很多,可能疾速搭建好环境。我在国内购买的主机,拜访github和docker,十分慢,有时连贯不上,无奈顺畅进行测试。 测试指标咱们会对以下几个指标进行比照: Global-TPS:用户视角下,实现了多少个全局事务。DB-TPS:各项测试中,在DB层面实现的事务数量OPS:各项测试中,实现了多少个SQL语句后果比照 Mysql无DTM-2SQLDTM-2SQLDTM-2SQL-Barrier无DTM-10SQLDTM-10SQLDTM-10SQL-BarrierGlobal-TPS-1232575531551357341DB-TPS2006246423002124110214281364OPS120394928575063721062092829548Mysql性能咱们首先用测试了Mysql本身的性能。在DTM的这次性能测试中,写操作较多,因而咱们这次次要对Mysql的写进行了性能测试。 咱们采纳了sysbench中的oltp_write_only基准,在这个基准中,每个事务蕴含6个写SQL(有insert/update/delete)。 在这个基准下,每秒实现的事务数量大概为2006,实现SQL数量大概为为12039。这两项后果,会在后续的DTM相干测试中援用。 DTM测试分布式事务中波及的事务模式有多种,咱们选取一个有代表性的简略Saga模式作为代表,剖析分布式事务DTM的性能。 咱们选取的Saga事务,蕴含两个子事务,一个是TransOut转出余额,一个是TransIn转入余额。转入转出各蕴含两个Sql,别离是更新余额和记录流水。 无DTM-2SQL咱们首先测试不采纳DTM的状况,也就是间接调用TransOut和TransIn,测试后果是每秒实现了1232个全局事务。每个全局事务蕴含转出和转入两个子事务,因而DB-TPS为2464,而后每个子事务又蕴含两个SQL,因而总的SQL操作为4928。 这个后果比照MYSQL,DB-TPS更高,而DB-SQL只有一半,次要起因为每个事务都须要将数据同步到磁盘,须要额定耗费性能,此时瓶颈次要在零碎数据库的事务能力 DTM-2SQL咱们接着测试采纳DTM的状况,采纳了DTM之后,一个SAGA事务的时序图如下: 全局事务会包含4个事务:TransIn、TransOut、保留全局事务+事务分支、批改全局事务为已实现。将每个子事务分支批改为已实现也各须要一个事务,但DTM采纳异步写进行了合并,缩小了事务。 每个全局事务包含的SQL数量为:1个保留全局事务、1个保留分支、1个读所有分支、2个批改分支为实现、1个批改全局事务为实现,一共6个额定的SQL,加上本来子事务的4个SQL是10个。 测试后果中,每秒实现全局事务数为575,那么DB-TPS为2300,OPS为5750,比照后面不采纳DTM的计划,DB-TPS略有降落,OPS有肯定的回升,瓶颈还是在零碎数据库 DTM-2SQL-Barrier退出了子事务屏障后,每个子事务分支会多一个insert语句,每个全局事务对应的SQL数量为12. 测试后果中,每秒实现全局事务数为531,那么DB-TPS为2124,OPS为6372,比照后面DTM的计划,DB-TPS略有降落,OPS略有回升,合乎预期 无DTM-10SQL咱们对压测的数据做调整,将每个子事务里的SQL数量,从2调整为10,将子事务中的SQL循环执行5次。 无DTM的压测后果中,每秒实现的全局事务数为551,DB-TPS为1102,OPS为10620。这个后果中,OPS与MYSQL的靠近,瓶颈次要在数据库的OPS。 ...

October 11, 2021 · 1 min · jiezi

关于golang:Goroutine的创建

原文 传送门Go代码中,利用关键字go启动协程。编译器发现go func(...),将调用newproc package maingo func(){...}/*能够应用go tool compile -S ./main.go失去汇编代码CALL runtime.newproc(SB)*/func newproc(fn *funcval)创立一个g来运行fn将g放入g期待队列中,期待被调度编译器会把go语句转化为调用newprocfunc newproc(fn *funcval) { // 【获取以后调用方正在运行的G】 gp := getg() // 【获取以后调用方 PC/IP 寄存器值】 pc := getcallerpc() // 【用 g0 栈创立 G 对象】 systemstack(func () { newg := newproc1(fn, gp, pc) _p_ := getg().m.p.ptr() // newg 放入待运行队列 runqput(_p_, newg, true) if mainStarted { // wackp 核心思想就是 寻找资源 执行newg wakep() } })}fn *funcval其中 newproc 函数有1个参数fn 是一个可变参数类型 type funcval struct { fn uintptr // variable-size, fn-specific data here}如果咱们有go程序 ...

October 9, 2021 · 5 min · jiezi

关于golang:golang-中Sizeof函数-与内存对齐查看的方式

sizeofSizeof是golang中获取指定类型占用字节的函数。比方slice构造占用的字节数为24(8+8+8)(以下均为64位cpu及64位操作系统下) /** type SliceHeader struct{ Data uintptr //8 Len int //8 Cap int //8 }string为8+8 type StringHeader struct{ Data uintptr //8 Len int //8 }array为size(Type)*len,简略总结如下 。 type alignment guarantee------ ------bool, uint8, int8 1uint16, int16 2uint32, int32 4float32, complex64 4arrays depend on element typesstructs depend on field typesother types size of a native word内存对齐及可视化工具//main.gopackage maintype user struct { name string age int gender int isBuy bool hobbies []string}type sliceCopy struct { sInt []int sString []int}func main() {}顺次执行 ...

October 9, 2021 · 2 min · jiezi

关于golang:Golang-环境搭建

注:本文为Linux下搭建Golang装置GoGo的装置相比其余语言环境非常简单,间接下载安装包,解压装置即可。 1、下载安装包(官网地址:https://golang.org/dl/ 国内已被墙了),间接去对应的Go语言中文网下载即可,稍等一下下就下载好了。 wget https://studygolang.com/dl/golang/go1.17.1.linux-amd64.tar.gz没装置wget? [装置wget:yum install wget] OR [浏览器下载下来,用ftp上传上去]2、解压到应用程序目录 $ tar -zxvf go1.17.1.linux-amd64.tar.gz -C /usr/local/-C 是指定到目标目录, 个别状况咱们用户装置的程序都指定到/usr/local/下3、祝贺你装置完了,不信看看版本 $ /usr/local/go/bin/go versiongo version go1.17.1 linux/amd64 环境变量配置都装置完了,还须要配置环境变量?须要,须要,须要咱们先来看下装置完Go的以后环境变量是啥 ** 查看命令 **$ /usr/local/go/bin/go envGO111MODULE=""GOARCH="amd64"GOBIN=""GOCACHE="/root/.cache/go-build"GOENV="/root/.config/go/env"GOEXE=""GOEXPERIMENT=""GOFLAGS=""GOHOSTARCH="amd64"GOHOSTOS="linux"GOINSECURE=""GOMODCACHE="/root/go/pkg/mod"GONOPROXY=""GONOSUMDB=""GOOS="linux"GOPATH="/root/go" ** go我的项目开发源码所在目录 **GOPRIVATE=""GOPROXY="https://proxy.golang.org,direct"GOROOT="/usr/local/go" ** go源程序的装置目录 **GOSUMDB="sum.golang.org"GOTMPDIR=""GOTOOLDIR="/usr/local/go/pkg/tool/linux_amd64"GOVCS=""GOVERSION="go1.17.1"GCCGO="gccgo"AR="ar"CC="gcc"CXX="g++"CGO_ENABLED="1"GOMOD="/dev/null"CGO_CFLAGS="-g -O2"CGO_CPPFLAGS=""CGO_CXXFLAGS="-g -O2"CGO_FFLAGS="-g -O2"CGO_LDFLAGS="-g -O2"PKG_CONFIG="pkg-config"GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build2477519910=/tmp/go-build -gno-record-gcc-switches"要搞清楚每个字段什么意思,能够参考官网解释。倡议在理论我的项目用到或遇到问题时再查看对应字段相熟深究其中含意。以后只须要明确GOROOT,GOPATH即可。 ** 查看官网字段解释 **$ /usr/local/go/bin/go help environment 到此咱们说一说,为啥须要配置环境变量 下来咱们先设置几个Linux的环境变量,这里留神和Go的env没啥关系,次要起因是我不想每次都应用 [$ /usr/local/go/bin/go version] 这么长的命令了,我就想间接 [go verison]就完事了 ** 写入零碎加载配置 **$ echo 'export GOROOT=/usr/local/go' >> /etc/profile$ echo 'export GOPATH=$HOME/go' >> /etc/profile$ echo 'export PATH=$PATH:$GOROOT/bin:$GOPATH/bin' >> /etc/profile** 加载刚写入的配置 **$ source /etc/profile好了,这下爽了,能够间接只用go命令了,咱们持续。。。 ...

October 8, 2021 · 1 min · jiezi

关于golang:Go-面试官问我如何实现面向对象

大家好,我是煎鱼。 在大家初识 Go 语言时,总会拿其余语言的根本个性来类比 Go 语言,说白了就是老常识和新常识产生关联,实现更高的学习效率。 最常见的类比,就是 “Go 语言如何实现面向对象?”,进一步开展就是 Go 语言如何实现面向对象个性中的继承。 这不仅在学习中才用到类比,在业内的 Go 面试中也有十分多的面试官喜爱问: 来自读者微信群 在明天这篇文章中,煎鱼带大家具体开展理解这块的常识。一起欢快地开始吸鱼之路。 什么是面向对象在理解 Go 语言是不是面向对象(简称:OOP) 之前,咱们必须先晓得 OOP 是啥,得先给他 “下定义”。 依据 Wikipedia 的定义,咱们梳理出 OOP 的几个根本认知: 面向对象编程(OOP)是一种基于 "对象" 概念的编程范式,它能够蕴含数据和代码:数据以字段的模式存在(通常称为属性或属性),代码以程序的模式存在(通常称为办法)。对象本人的程序能够拜访并常常批改本人的数据字段。对象常常被定义为类的一个实例。对象利用属性和办法的公有/受爱护/公共可见性,对象的外部状态受到爱护,不受外界影响(被封装)。基于这几个根本认知进行一步延长出,面向对象的三大根本个性: 封装。继承。多态。至此对面向对象的基本概念解说完结,想更进一步理解的可自行网上冲浪。 Go 是面向对象的语言吗“Go 语言是否一门面向对象的语言?”,这是一个日经话题。官网 FAQ 给出的回答是: 是的,也不是。起因是: Go 有类型和办法,并且容许面向对象的编程格调,但没有类型档次。Go 中的 "接口 "概念提供了一种不同的办法,咱们认为这种办法易于应用,而且在某些方面更加通用。还有一些办法能够将类型嵌入到其余类型中,以提供相似的货色,但不等同于子类。Go 中的办法比 C++ 或 Java 中的办法更通用:它们能够为任何类型的数据定义,甚至是内置类型,如一般的、"未装箱的 "整数。它们并不局限于构造(类)。Go 因为不足类型档次,Go 中的 "对象 "比 C++ 或 Java 等语言更笨重。Go 实现面向对象编程封装面向对象中的 “封装” 指的是能够暗藏对象的外部属性和实现细节,仅对外提供公开接口调用,这样子用户就不须要关注你外部是怎么实现的。 在 Go 语言中的属性拜访权限,通过首字母大小写来管制: 首字母大写,代表是公共的、可被内部拜访的。首字母小写,代表是公有的,不能够被内部拜访。Go 语言的例子如下: type Animal struct { name string}func NewAnimal() *Animal { return &Animal{}}func (p *Animal) SetName(name string) { p.name = name}func (p *Animal) GetName() string { return p.name}在上述例子中,咱们申明了一个构造体 Animal,其属性 name 为小写。没法通过内部办法,在配套上存在 Setter 和 Getter 的办法,用于对立的拜访和设置管制。 ...

October 8, 2021 · 1 min · jiezi

关于golang:定制个机器人帮你和Ta聊天

主动聊天示例聊天1: user: 在吗?bot: 在user: 在干嘛呢?bot: 看电视user: 看啥电视呀bot: 活色生香user: 很难看吗?bot: 特搞笑user: 你在哪里呀?bot: 家里user: 家里就你一个人嘛?bot: 我喜爱一个人玩user: 那我过去找你?bot: 不能够,乖乖下班去聊天2: Q: 在吗?A: 在Q: 干嘛呢?A: 没事Q: 陪我去逛街嘛?A: 嗯Q: 你在打游戏?A: 没有Q: 那去不去?A: 去这是基于200万聊天记录训练进去的,你能够用本人和女朋友的记录训练了试试成果 :P 至于微信机器人怎么用,你能够 GitHub 搜搜看哈 我的项目阐明chatbot 是一个通过已知对话数据集疾速生成答复的 Go 问答引擎。 为啥会有 chatbot 我的项目呢?好多年前,当咱们须要一个聊天机器人的时候,我是先用了 ChatterBot,然而应用下来,咱们的1.2亿对话语料训练后的模型答复一个问题须要21秒左右,切实没法承受。认真看了 ChatterBot 源码之后,我用 Go 从新实现了一个,并用 go-zero 的 MapReduce 框架做了并行优化,后果咱们一个答复均匀耗时大略18毫秒。 国庆假期,我有点闲暇工夫,所以就把这个我的项目整顿了开源进去,一是给大家一个理论的 go-zero 的 MapReduce 示例;二是也提供大家一个闲聊机器人的我的项目玩玩。 BTW:后续我可能会开源智能客服机器人的我的项目,能够关注我的github: https://github.com/kevwan 代码目录和命令行应用阐明bot问答引擎,能够自定义本人的匹配算法 clitrain 训练给定的问答数据并生成 .gob 文件 -d 读取指定目录下所有 json 和 yaml 语料文件-i 读取指定的 json 或 yaml 语料文件,多个文件用逗号宰割-o 指定输入的 .gob 文件-m 定时打印内存应用状况ask ...

October 8, 2021 · 1 min · jiezi

关于golang:组队刷LeetCode-两数之和

摘要: 最近在开始用Go语言刷leetcode题目, 感兴趣的敌人能够一起组队刷哈。本文首发于公众号: GoGuider题目形容 - 两数之和给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target  的那 两个 整数,并返回它们的数组下标。 你能够假如每种输出只会对应一个答案。然而,数组中同一个元素在答案里不能反复呈现。 你能够按任意程序返回答案。 示例 1: 输出:nums = [2,7,11,15], target = 9输入:[0,1]解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。示例 2: 输出:nums = [3,2,4], target = 6输入:[1,2]示例 3: 输出:nums = [3,3], target = 6输入:[0,1]  提醒: 2 <= nums.length <= 104-109 <= nums[i] <= 109-109 <= target <= 109只会存在一个无效答案进阶:你能够想出一个工夫复杂度小于 O(n2) 的算法吗?答案解析1、暴力解法 func twoSum(nums []int, target int) []int { numsLen := len(nums) for i, num := range nums { for j := i + 1; j < numsLen; j++ { if target == num + nums[j] { return []int{i, j} } } } return []int{}}工夫复杂度: O(nlogn) 空间复杂度: O(1) ...

October 7, 2021 · 1 min · jiezi

关于golang:GoRedis实现简单的消息队列

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

October 4, 2021 · 2 min · jiezi

关于golang:Golang中interface的简单分析

原文地址 版本: GO1.17 接口Go语言中的接口,是一组办法的签名,它是Go语言的重要组成部分。应用接口能让咱们写出易于测试的代码。然而很多工程师对Go接口的理解都十分无限,也不分明其底层原理的实现。这成为了开发高性能服务的妨碍。 本文会介绍应用接口时遇到的一些常见问题,以及它的设计与实现,包含接口的类型转换、类型断言以及动静派发机制,帮忙读者更好地了解接口类型。 概述在计算机科学中,接口是多个组件共享的边界,不同的组件能在边界上替换信息。如下图所示,接口的实质是引入一个新的中间层。调用方能够通过接口与具体的实现拆散。接触上下游的耦合,下层的模块不在须要依赖上层的具体模块。只须要依赖一个约定好的的接口。 GOLANG INTERFACE ┌────────┐ ┌───────────────►│ module │ │ └────────┘ │┌────────┐ ┌─────────┴─┐ ┌────────┐│ module ├───────►│ interface ├─────────────►│ module │└────────┘ └─────────┬─┘ └────────┘ │ │ ┌────────┐ └───────────────►│ module │ └────────┘图1 上下游通过接口解耦 这种面向接口的编程形式有着弱小的生命力,无论是在框架中,还是在操作系统中,都能看到接口的身影。可移植操作系统接口(Portable Operating System Interface, POSIX)就是一个典型的例子,它定义了利用程序接口和命令行等规范,为计算机软件带来了可移植性,只有操作系统实现了POSIX,计算机软件就能够在不同操作系统上运行。 除理解耦有依赖关系的上下游,接口还能帮忙咱们暗藏底层实现,缩小关注点。人可能同时解决的信息十分无限,定义良好的接口可能隔离底层实现,让咱们将重点放在以后的代码片段中。SQL就是接口的一个例子。当咱们应用SQL查问数据时,其实不须要关怀底层数据库的具体实现,咱们只在乎SQL返回的后果是否合乎预期。 SQL AND DATABASE ┌────────┐ ┌───────────────►│ MYSQL │ │ └────────┘ │ ┌─────────┴─┐ ┌────────┐ │ SQL ├─────────────►│ SQLITE │ └─────────┬─┘ └────────┘ │ │ ┌────────────┐ └───────────────►│ POSTGRESQL │ └────────────┘图2 SQL和不同数据库 类型接口也是GO语言中的一种类型,它可能呈现在变量的定义,函数的入参和放回值上。GO语言中有两种稍微不同的接口,一种是带一组办法的接口,另一种是不带任何办法的接口。 ...

October 3, 2021 · 3 min · jiezi

关于golang:聊一聊Go语言中的零值它有什么用

背景哈喽,大家好,我是asong。明天与大家聊一聊Go语言中的零值。大学期间我是一名C语言爱好者,工作了当前感觉Go语言和C语言很像,所以抉择了Go语言的工作,时不时就会把这两种语言的一些个性做个比拟,明天要比拟的就是零值个性。相熟C语言的敌人晓得在C语言中默认状况下不初始化局部变量。 未初始化的变量能够蕴含任何值,其应用会导致未定义的行为;如果咱们未初始局部变量,在编译时就会报正告 C4700,这个正告批示一个Bug,这个Bug可能导致程序中呈现不可预测的后果或故障。而在Go语言就不会有这样的问题,Go语言的设计者汲取了在设计C语言时的一些教训,所以Go语言的零值标准如下: 以下内容来自官网blog:https://golang.org/ref/spec#T... 当通过申明或 new 调用为变量调配存储空间时,或通过复合文字或 make 调用创立新值时,且未提供显式初始化,则给出变量或值一个默认值。此类变量或值的每个元素都为其类型设置为零值:布尔型为 false,数字类型为 0,字符串为 “",指针、函数、接口、切片、通道和映射为 nil。此初始化是递归实现的,例如,如果未指定任何值,则构造体数组的每个元素的字段都将其清零。 例如这两个简略的申明是等价的: var i int var i int = 0在或者这个构造体的申明: type T struct { i int; f float64; next *T }t := new(T)这个构造体t中成员字段零值如下: t.i == 0t.f == 0.0t.next == nilGo语言中这种始终将值设置为已知默认值的个性对于程序的安全性和正确性起到了很重要的作用,这样也使整个Go程序更简略、更紧凑。 零值有什么用通过零值来提供默认值咱们在看一些Go语言库的时候,都会看到在初始化对象时采纳"动静初始化"的模式,其实就是在创建对象时判断如果是零值就应用默认值,比方咱们在剖析hystrix-go这个库时,在配置Command时就是应用的这种形式: func ConfigureCommand(name string, config CommandConfig) { settingsMutex.Lock() defer settingsMutex.Unlock() timeout := DefaultTimeout if config.Timeout != 0 { timeout = config.Timeout } max := DefaultMaxConcurrent if config.MaxConcurrentRequests != 0 { max = config.MaxConcurrentRequests } volume := DefaultVolumeThreshold if config.RequestVolumeThreshold != 0 { volume = config.RequestVolumeThreshold } sleep := DefaultSleepWindow if config.SleepWindow != 0 { sleep = config.SleepWindow } errorPercent := DefaultErrorPercentThreshold if config.ErrorPercentThreshold != 0 { errorPercent = config.ErrorPercentThreshold } circuitSettings[name] = &Settings{ Timeout: time.Duration(timeout) * time.Millisecond, MaxConcurrentRequests: max, RequestVolumeThreshold: uint64(volume), SleepWindow: time.Duration(sleep) * time.Millisecond, ErrorPercentThreshold: errorPercent, }}通过零值判断进行默认值赋值,加强了Go程序的健壮性。 ...

October 3, 2021 · 2 min · jiezi

关于golang:CPU内存屏障与缓存一致性

在应用层,对于锁的应用大家应该都很相熟了,作用就是为了爱护共享变量不被同时操作而导致无奈预测的状况。然而深刻到具体实现,锁仅仅只是锁定临界区吗? 锁的实现其实还必须实现一个语义,也就是内存屏障。内存屏障次要用于避免指令重排而导致的无奈预测的状况。代码通过编译器生成的指令并不一定都是按着咱们原先的想法来生成的,可能通过优化等状况进行了指令的重排,然而这些重排在执行后的后果该当是统一的。其实及时编译器不重排指令,在古代的cpu中,也经常会将指令乱序执行,所以内存屏障能够保障屏障指令前后的指令程序。 内存屏障也分为读屏障(rmb)与写屏障(wmb)。这些读写屏障次要用于在多核cpu的情景下能够强制同步cpu中缓存不统一的状况。 这些又牵扯到了多cpu中缓存一致性的问题。假如只有一个cpu,那么cpu只会从本人的缓存中读数据,如果产生了缓存miss,则会从主存中读取数据到缓存中,所以cpu无论在何时看到的最终内存数据都是统一的。 然而在多核状况下,就不是这么简略的了。每个cpu都有本人的缓存,每个cpu最终看到的数据,就是不在缓存中的主存+已在缓存中的数据。所以假如多cpu的状况下,某个cpu更新了某个cache line中的值又没有回写到内存中,那么其它cpu中的数据其实曾经是旧的已作废的数据,这是不可承受的。 为了解决这种状况,引入了缓存一致性协定,其中用的比拟多的称为MESI,别离是cache line可能存在的四种状态: Modified。数据已读入cache line,并且曾经被批改过了。该cpu领有最新的数据,能够间接批改数据。当其它外围须要读取相应数据的时候,此数据必须刷入主存。Exclusive。数据已读入cache line,并且只有该cpu领有它。该cpu能够间接批改数据,然而该数据与主存中数据是统一的。Shared。多个cpu共享某内存的数据,可能由Exclusive状态扭转而来,当某个cpu须要批改数据的时候,必须提交RFO申请来获取数据的独占权,而后能力进行批改。Invalid。有效的cache line,和没有载入一样。当某个cpu的cache line处于Shared状态,别的cpu申请写的时候,接管了RFO申请后会变为此种状态。这四种状态能够一直的扭转,有了这套协定,不同的cpu之间的缓存就能够保证数据的一致性了。然而依赖这套协定,会大大的升高性能,比方一个外围上某个Shared的cache line打算写,则必须先RFO来获取独占权,当其它外围确认了之后能力转为Exclusive状态来进行批改,假如其余的外围正在解决别的事件而导致一段时间后才回应,则会当申请RFO的外围处于无事可做的状态,这是不可承受的。 于是在每个cpu中,又退出了两个相似于缓存的货色,别离称为Store buffer与Invalidate queue。 Store buffer用于缓存写指令,当cpu须要写cache line的时候,并不会执行上述的流程,而是将写指令丢入Store buffer,当收到其它外围的RFO回应后,该指令才会真正执行。 Invalidate queue用于缓存Shared->Invalid状态的指令,当cpu收到其它外围的RFO指令后,会将本身对应的cache line有效化,然而当外围比较忙的时候,无奈立即解决,所以引入Invalidate queue,当收到RFO指令后,立即回应,将有效化的指令投入Invalidate queue。 这套机制大大晋升了性能,然而很多操作其实也就异步化了,某个cpu写入了货色,则该写入可能只对以后CPU可见(读缓存机制会先读Store buffer,再读缓存),而其余的cpu可能无奈感知到内存产生了扭转,即便Invalidate queue中已有该有效化指令。 为了解决这个问题,引入了读写屏障。写屏障次要保障在写屏障之前的在Store buffer中的指令都真正的写入了缓存,读屏障次要保障了在读屏障之前所有Invalidate queue中所有的有效化指令都执行。有了读写屏障的配合,那么在不同的外围上,缓存能够失去强同步。 所以在锁的实现上,个别lock都会退出读屏障,保障后续代码能够读到别的cpu外围上的未回写的缓存数据,而unlock都会退出写屏障,将所有的未回写的缓存进行回写。 参考: https://wudaijun.com/2019/04/...

October 2, 2021 · 1 min · jiezi

关于golang:一步一步带你学golang之切面

以下所有内容均能够在b站观看,如果你爱看视频的同学能够到b站观看本章内容,或一系列的视频教程。 https://www.bilibili.com/vide... Go 语言切片(Slice)Go 数组的长度不可扭转,在特定场景中这样的汇合就不太实用,Go 中提供了一种灵便,性能强悍的内置类型切片("动静数组"),与数组相比切片的长度是不固定的,能够追加元素,在追加时可能使切片的容量增大。 切面是建设在数组的根底上的,要有切片得先有数组。它的内部结构蕴含了 地址 、长度、和容量。切片个别用于疾速地操作一块数据汇合。切片属于援用类型。 值类型和援用类型 咱们在创立一个数组的时候。会在内容空间生成一个内存地址。当咱们要一个数组赋值给另一个数组的时候其实是在内存空间里开拓了一块新的内存空间。而咱们的切片却同一块数组的空间内存数据汇合块。 定义var 变量名 []类型留神和 var 变量名 [...]类型的辨别,加了...定义的是咱们的数组。 切片的长度和容量什么事长度,其实和数组相似,也就是领有多少个元素,就是咱们的长度。那么容量是咱们的这个切片的最大接受的长度。 打个比方,比方咱们的杯子。能装多少水是它的容量。装了多少水是它的长度。 比方下面的这图,如果咱们有一个数组{0,1,2,3,4,5,6,7},咱们从0开始去5个元素。那么这个切片就有{0,1,2,3,4}这5个元素,也就是咱们的长度。因我么是从0开启获取那么这个切片的容量就是我么的数组长度剪去咱们的0,也就是8 - 0。 咱们在开发过程要应用切片必须建设在数组之上,那有没有不必数组的形式来创立咱们的切片呢?答案是有的就是咱们的或应用 make()  函数来创立切片: var slice1 []type = make([]type, len)也能够简写为slice1 := make([]type, len)make([]T, length, capacity)append() 和 copy() 函数这两个函数是go给我操作切片的一些函数。 视频外面有演示,这里就不多讲了 一个是用来给append进行追加元素的。一个是来copy新的切片,内存地址的指向也会产生扭转。

October 1, 2021 · 1 min · jiezi

关于golang:一步一步带你学习-golang-基础知识之数组

一下内容为 B站 所有内容,如果你不喜爱看文章,更喜爱的看视频的话,能够去点击一下链接进入b站进行观看。 https://www.bilibili.com/vide... 为了节俭您的时候,没一个视频都管制在8分钟内。 数组什么是咱们的数组呢?例如有 5,8,,9,79,76 这些元素,如果咱们在没有学习数组这个概念的时候,咱们会如何用go来表白? x:= 5y:=8z:=9q:=79t:=76这个时候咱们就会想有没有一个变量能够一次表白这堆元素呢?于是就有了数组这个概念。 数组就是把雷同数据类型元素的汇合,数组中的元素不能混合应用不同的元素(比方整数和字符串类型) 申明var 变量名 [长度]数据类型var a [3]intvar a [3]int申明了一个长度为3的整数数组。这就是咱们的定义一个数组的一种形式。 数组的一些概念 数组的初始化咱们买新手机,到手开机的时候,也会有一堆默认设置。同样数组也有默认的值,如果咱们不设置的话,默认的int,float类型的默认值为0,string类型为空字符串,bool类型为false var a = [10]int{1,2,3,4,5,6,7,8,10}b:=[10]int{1,2,3,4,5,6,7,8,9}//留神的是这里如果不填写 ... 失去的是go的切片。如果填写了是go的数组...会依据前面的元素生成对于的长度数组a:=[...]{1,2,3,4,5,6,7,8,9} 这就是咱们所说的三种定义数组的形式。 遍历数组办法一for I:=0;i<len(a);i++{     fmt.println(a[I])}办法二for index, value:= range a{     fmt.println(index, value)}如果只是便当,不对数组外面的元素做解决,例如LeeCode的数组外面做最多的双指针问题,平时开发应用办法二即可。可读性更高。 办法一中的i是咱们的下标。 办法二中的index和value别离对应数组外面的下标和元素。 多维数组多维数组能够了解成二维,和三维的概念,通常咱们用的比拟多的是二维数组。 咱们也能够了解二维数组是一个表格。具备一行一列的模式 如这样的一个表格咱们能够用 var a [2][4]int //2行系列来示意那咱们的二维数组要如何拜访呢? 咱们浏览都是又右边往右读,同理咱们先读行,再读列即可。 for rows:=0;rows<2;rows++ { for cloums:=0;cloums<4;cloums++{ fmt.Println(arr[rows][cloums]) //即可 }}arr[0][0]示意咱们的第一行第一列以上为本节所有内容,感觉大家的观看。如果想看跟多内容能够到集体的b站主页进行查阅。关注1w粉女装宠粉...

October 1, 2021 · 1 min · jiezi

关于golang:我们一起来学RabbitMQ-二RabbiMQ-的-6-种模式的基本应用

咱们一起来学RabbitMQ 二:RabbiMQ 的 6 种模式的根本利用嗨,大家好,我是小魔童哪吒,咱们从明天开始进入开源组件的学习,一边学习一边总结一边分享 文章提纲如下: RabbitMQ 成员组成RabbitMQ 的六种工作模式编码RabbitMQ 成员组成生产者 producer消费者 consumer交换机 exchange用于承受、调配音讯 音讯 message队列 queue用于存储生产者的音讯 信道 channel AMQP音讯推送应用的通道 连贯 connections生成者或者消费者与Rabbit 建设的TCP 连贯 路由键 routingKey用于把生成者的数据调配到交换器上 绑定键 BindingKey用于把交换器的音讯绑定到队列上 连贯管理器 ConnectionFactory应用程序与 Rabbit 之间建设连贯的管理器,程序代码中应用 RabbitMQ 的六种工作模式编码single 模式音讯产生者将音讯放入队列音讯的消费者监听音讯队列,如果队列中有音讯就生产掉目录如下: .├── consumer.go├── go.mod├── go.sum├── main.go└── xmtmq └── xmtmq.go理论编码如下: 每种模式的编码思路如下: 生产者 / 消费者 连贯 RabbitMQ 的 server初始化连贯 connection初始化通道 channel初始化交换机 exchange初始化队列 queue应用路由key,绑定队列 bind , key生产音讯 / 生产音讯 produce , consume音讯xmtmq.go package xmtmqimport ( "github.com/streadway/amqp" "log")// single 模式// 定义 RabbitMQ 的数据结构// go get github.com/streadway/amqptype RabbitMQ struct { conn *amqp.Connection // 连贯 channel *amqp.Channel // 通道 QueueName string // 队列名 Exchange string // 交换机 Key string // 路由键 MQUrl string // MQ的虚拟机地址}// New 一个 RabbitMQfunc NewRabbitMQ(rbt *RabbitMQ) { if rbt == nil || rbt.QueueName == "" || rbt.MQUrl == "" { log.Panic("please check QueueName,Exchange,MQUrl ...") } conn, err := amqp.Dial(rbt.MQUrl) if err != nil { log.Panicf("amqp.Dial error : %v", err) } rbt.conn = conn channel, err := rbt.conn.Channel() if err != nil { log.Panicf("rbt.conn.Channel error : %v", err) } rbt.channel = channel}func RabbitMQFree(rbt *RabbitMQ){ if rbt == nil{ log.Printf("rbt is nil,free failed") return } rbt.channel.Close() rbt.conn.Close()}func (rbt *RabbitMQ) Init() { // 申请队列 _, err := rbt.channel.QueueDeclare( rbt.QueueName, // 队列名 true, // 是否长久化 false, // 是否主动删除 false, // 是否排他 false, // 是否阻塞 nil, // 其余参数 ) if err != nil { log.Printf("rbt.channel.QueueDeclare error : %v", err) return }}// 生产音讯func (rbt *RabbitMQ) Produce(data []byte) { // 向队列中退出数据 err := rbt.channel.Publish( rbt.Exchange, // 交换机 rbt.QueueName, // 队列名 false, // 若为true,依据本身exchange类型和routekey规定无奈找到符合条件的队列会把音讯返还给发送者 false, // 若为true,当exchange发送音讯到队列后发现队列上没有消费者,则会把音讯返还给发送者 amqp.Publishing{ ContentType: "text/plain", Body: data, }, ) if err != nil { log.Printf("rbt.channel.Publish error : %v", err) return } return}// 生产音讯func (rbt *RabbitMQ) Consume() { // 生产数据 msg, err := rbt.channel.Consume( rbt.QueueName, // 队列名 "xmt", // 消费者的名字 true, // 是否自动应答 false, // 是否排他 false, // 若为true,示意 不能将同一个Conenction中生产者发送的消息传递给这个Connection中 的消费者 false, // 是否阻塞 nil, // 其余属性 ) if err != nil { log.Printf("rbt.channel.Consume error : %v", err) return } for data := range msg { log.Printf("received data is %v", string(data.Body)) }}main.go ...

September 30, 2021 · 9 min · jiezi

关于golang:如何在-Go-中优雅的处理和返回错误1函数内部的错误处理

在应用 Go 开发的后盾服务中,对于错误处理,始终以来都有多种不同的计划,本文探讨并提出一种从服务内到服务外的谬误传递、返回和回溯的残缺计划,还请读者们一起探讨。 问题提出在后盾开发中,针对错误处理,有三个维度的问题须要解决: 函数外部的错误处理: 这指的是一个函数在执行过程中遇到各种谬误时的错误处理。这是一个语言级的问题函数/模块的错误信息返回: 一个函数在操作谬误之后,要怎么将这个错误信息优雅地返回,不便调用方(也要优雅地)解决。这也是一个语言级的问题服务/零碎的错误信息返回: 微服务/零碎在解决失败时,如何返回一个敌对的错误信息,仍然是须要让调用方优雅地了解和解决。这是一个服务级的问题,实用于任何语言针对这三个维度的问题,笔者筹备写三篇文章一一阐明。首先本文就是第一篇:函数外部的错误处理 高级语言的错误处理机制一个面向过程的函数,在不同的处理过程中须要 handle 不同的错误信息;一个面向对象的函数,针对一个操作所返回的不同类型的谬误,有可能须要进行不同的解决。此外,在遇到谬误时,也能够应用断言的形式,疾速停止函数流程,大大提高代码的可读性。 在许多高级语言中都提供了 try ... catch 的语法,函数外部能够通过这种计划,实现一个对立的错误处理逻辑。而即使是 C 这种 “中级语言”,尽管没有 try catch,然而程序员也能够应用宏定义配合 goto LABEL 的形式,来实现某种程度上的谬误断言和解决。 Go 的谬误断言在 Go 的状况就比拟难堪了。咱们先来看断言,咱们的目标是,仅应用一行代码就可能查看谬误并终止以后函数。因为没有 throw、没有宏,如果要实现一行断言,有两种办法。 办法一:单行 if + return第一种是把 if 的错误判断写在一行内,比方: if err != nil { return err }这种办法有值得商讨的点: 尽管合乎 Go 的代码标准,然而在实操中,if 语句中的花括号不换行这一点还是十分有争议的,并且笔者在理论代码中也很少见到过代码不够直观,大抵浏览代码的时候,断言代码不显眼,而且在花括号中除了 return 之外也没法别的了,起因是 Go 的标准中强烈不倡议应用 ; 来分隔多条语句(if 条件判断除外)因而,笔者强烈不倡议这么做。 办法二:panic + recover第二种办法是借用 panic 函数,联合 recover 来实现,如以下代码所示: func SomeProcess() (err error) defer func() { if e := recover(); e != nil { err = e.(error) } }() assert := func(cond bool, e error) { if !cond { panic(e) } } // ... err = DoSomething() assert(err == nil, fmt.Errorf("DoSomething() error: %w", err)) // ...}这种办法好不好呢?咱们要分状况看 ...

September 30, 2021 · 2 min · jiezi

关于golang:使用-grpcurl-通过命令行访问-gRPC-服务

原文链接: 应用 grpcurl 通过命令行拜访 gRPC 服务 个别状况下测试 gRPC 服务,都是通过客户端来间接申请服务端。如果客户端还没筹备好的话,也能够应用 BloomRPC 这样的 GUI 客户端。 如果环境不反对装置这种 GUI 客户端的话,那么有没有一种工具,相似于 curl 这样的,间接通过终端,在命令行发动申请呢? 答案必定是有的,就是本文要介绍的 grpcurl。 gRPC Server首先来写一个简略的 gRPC Server: helloworld.proto: syntax = "proto3";package proto;// The greeting service definition.service Greeter { // Sends a greeting rpc SayHello (HelloRequest) returns (HelloReply) {}}// The request message containing the user's name.message HelloRequest { string name = 1;}// The response message containing the greetingsmessage HelloReply { string message = 1;}main.go ...

September 30, 2021 · 2 min · jiezi

关于golang:GO语言代理用法

GO语言代理用法 package mainimport ( "fmt" "io/ioutil" "net/http" "net/url")//sock5代理func socksproxy() { urli := url.URL{} urlproxy, _ := urli.Parse("http://测试ip:端口") client := &http.Client{ Transport: &http.Transport{ Proxy: http.ProxyURL(urlproxy), }, } rqt, err := http.NewRequest("GET", "http://myip.top", nil) if err != nil { println("接口获取IP失败!") return } rqt.Header.Add("User-Agent", "Lingjiang") //解决返回后果 response, _ := client.Do(rqt) defer response.Body.Close() body, err := ioutil.ReadAll(response.Body) if err != nil { return } fmt.Println("socks5:", string(body)) return}//http代理func httpproxy() { urli := url.URL{} urlproxy, _ := urli.Parse("http://测试ip:端口") client := &http.Client{ Transport: &http.Transport{ Proxy: http.ProxyURL(urlproxy), }, } rqt, err := http.NewRequest("GET", "http://myip.top", nil) if err != nil { println("接口获取IP失败!") return } rqt.Header.Add("User-Agent", "Lingjiang") //解决返回后果 response, _ := client.Do(rqt) defer response.Body.Close() body, err := ioutil.ReadAll(response.Body) if err != nil { return } fmt.Println("http:", string(body)) return}//本机IPfunc httplocal() { client := &http.Client{} rqt, err := http.NewRequest("GET", "http://myip.top", nil) if err != nil { println("接口获取IP失败!") return } rqt.Header.Add("User-Agent", "Lingjiang") //解决返回后果 response, _ := client.Do(rqt) defer response.Body.Close() body, err := ioutil.ReadAll(response.Body) if err != nil { return } fmt.Println("本机:", string(body)) return}func main() { httplocal() httpproxy() socksproxy()}

September 30, 2021 · 1 min · jiezi

关于golang:译更多关于延迟函数

调用带有返回后果的内置函数不能被提早调用在Go中,自定义函数的返回值能够被抛弃。然而,对于有返回值的内置函数,返回后果是不能被抛弃的(起码对于1.17版本的Go编译器是这样的),除了内置Copy和Recover函数是例外。另一方面,咱们晓得,提早函数的返回值必须抛弃,所以很多内置函数不能当作提早函数应用。 侥幸的是,内置函数(带有返回值)在实践中很少应用。据我所知,只有Append函数有时候在提早中调用。这种状况,咱们能够将append包装到匿名函数中调用。 package mainimport "fmt"func main() { s := []string{"a", "b", "c", "d"} defer fmt.Println(s) // [a x y d] // defer append(s[:1], "x", "y") // error defer func() { _ = append(s[:1], "x", "y") }()}提早函数求值将提早函数(值)被推入以后协程提早调用栈时值就被评估。例如上面打印false: package mainimport "fmt"func main() { var f = func () { fmt.Println(false) } defer f() f = func () { fmt.Println(true) }}提早调用函数的值可能是nil,在这种情景下,在被推入到协程提早调用栈中时,nil函数被调用便会异样,例如: package mainimport "fmt"func main() { defer fmt.Println("reachable 1") var f func() // f is nil by default defer f() // panic here // The following lines are also reachable. fmt.Println("reachable 2") f = func() {} // useless to avoid panicking}提早函数接管参数求值就像下面例子,带参数的提早函数求值也是在推入到以后协程提早函数栈之前。 ...

September 29, 2021 · 2 min · jiezi

关于golang:Golang一键安装入门教程

Golang是Google开发的一种凋谢源代码编程语言,语法简略易懂,罕用于后端Web开发。 简略易学好上手,开发及编译效率高速,还能够跨平台穿插编译。 学习Go语言的开发者越来越多,那如何简略疾速的装置部署好Golang呢? 1.Golang服务找到Golang的装置服务可点击试用。 2.装置部署增加节点-抉择版本-填写参数-部署胜利 装置部署过程简略又疾速,具体的装置教程如下: https://www.bilibili.com/vide... https://www.bilibili.com/vide...

September 27, 2021 · 1 min · jiezi

关于golang:译如何优雅的关闭channel

几天前,我写了一篇解释go通道准则。那篇文章在reddit 和 HN上失去了很多反对。然而go通道设计细节失去了许多批评。 我总结批评内容如下: 不批改channel状态的状况下,没有对立和简略的形式去判断channel是否敞开。敞开曾经敞开的channel会panic,所以敞开channel是十分危险的如果不晓得channel是否敞开。向敞开的channel发送值会panic,所以发送值到channel是十分危险的如果不晓得channel是否敞开。这些评论看起来是正当的(实际上不然),是的,并没有内置的函数去判断channel是否敞开。 如果你能确定不再(未来)往channel发送值,的确有简略的办法去判断channel是否敞开,为了不便了解,请看上面的列子: package mainimport "fmt"type T intfunc IsClosed(ch <-chan T) bool { select { case <-ch: return true default: } return false}func main() { c := make(chan T) fmt.Println(IsClosed(c)) // false close(c) fmt.Println(IsClosed(c)) // true}就像下面提到的,没有对立的形式去查看channel是否敞开。 实际上,即便有简略的内置办法closed 去判断channel是否敞开,就像内置函数len 判断channel 元素个数,价值无限。起因是被查看的channel可能会在函数调用并返回后状态曾经扭转,所以返回的值并不能反映最新的channel状态。不过如果调用closed(ch)返回true能够进行向channel发送值,然而如果调用closed(ch),返回false,则敞开通道或持续向通道发送值是不平安的。 channel的敞开准则最根本的准则是不要要接受者端敞开channel,也不要在有多个并发发送者的状况下敞开channel。换句话说,咱们只能在发送端敞开channel,并且是惟一的发送者。 (上面咱们将称下面的准则为敞开准则) 当然,并没有对立的准则去敞开channel。对立的准则便是不要敞开(或者发送值)曾经敞开的channel。如果咱们能保障没有协程发送或者敞开一个没有敞开且不为nil的channel,那咱们能够平安敞开这个channel。然而,对于接受者或或者多个发送者中作出这样的保障往往须要很多致力,而且常常使代码更为简单。相同,听从channel敞开准则绝对比较简单。 粗犷的敞开channel的解决方案如果你无论如也想从接收端或者多个发送者中一个敞开channel,你能够应用谬误复原机制阻止恐慌的可能来防止程序宕机。上面是一个示例(假如channel 的类型是T): func SafeClose(ch chan T) (justClosed bool) { defer func() { if recover() != nil { // The return result can be altered // in a defer function call. justClosed = false } }() // assume ch != nil here. close(ch) // panic if ch is closed return true // <=> justClosed = true; return}这种解决方案,显然违反了敞开准则。 ...

September 27, 2021 · 7 min · jiezi

关于golang:Go进阶数据结构Slice

slice 又称动静数组,依靠数组实现,但比数组更灵便,在 Go 语言中个别都会应用 slice 而非 array。 个性申明和初始化 slice 的形式除了从数组中间接切取外,次要还有以下几种:变量申明、字面量申明、应用内置函数 new() 和 make()。应用变量申明和 new() 函数后的 slice 的值为 nil,对其应用 append() 追加元素时会导致异样,个别举荐应用 make() 函数来初始化。 实现原理数据结构源码 src/runtime/slice.go:slice 定义了 slice 的数据结构: type slice struct { array unsafe.Pointer // 底层数组指针 len int // 长度 cap int // 容量}相干操作创立当咱们应用 make() 创立 slice 时,能够同时指定长度和容量,底层会调配一个数组,数组的长度即容量。 例如,slice := make([]int, 5, 10)所创立的 slice,构造如下所示: 扩容向 slice 追加元素时,如果 slice 的容量不足以包容追加的元素时,将会触发扩容。扩容实际上是重新分配一块更大的内存,将原 slice 拷贝进新 slice,再将数据追加进去。这也是为什么咱们常常能见到相似于 s := append(s, 1) 这种写法的起因。 扩容时容量的变动遵循以下根本规定: 原 slice 容量小于 1024,则新 slice 的容量扩充为原来的 2 倍;原 slice 容量大于等于 1024,则 新 slice 的容量扩充为原来的 1.25 倍。不过,理论过程中还会综合思考元素的类型和内存调配策略,在该规定的根底上做一些微调。 ...

September 26, 2021 · 1 min · jiezi

关于golang:centos-安装golang

github拜访增强,你懂的 将最新的dns GitHub520 增加到/etc/hosts 装置golang手动下载安装包也能够,版本会更新yum install golang 配置环境变量 # !!!!GOROOT要与装置目录统一# 否则如果装置过两个不同目录的go,在go get的时候会遇到谬误# compile: version "go1.15.6" does not match go tool version "go1.15.14"export GOROOT=/usr/lib/golang# 装置的各种第三方源会放到这里export GOPATH=$HOME/gocodeexport PATH=$PATH:$GOROOT/bin:$GOPATH/bin下载第三方模块 go get github.com/jackc/pgconn# 会主动下载到$GOPATH/src/github.com/jackc/pgconn/上面# 并且会主动下载对应的依赖模块或者手动下载 # 如上述github.com/jackc/pgconn,下载到$GOPATH/src/github.com/jackc/pgconn/有些依赖包来自于golang.org,因为一些你懂的起因,不能下载,例如 package golang.org/x/crypto/pbkdf2: unrecognized import path "golang.org/x/crypto/pbkdf2": https fetch: Get "https://golang.org/x/crypto/pbkdf2?go-get=1": dial tcp 142.251.43.17:443: i/o timeout解决办法,下载golang.org在github上的同步镜像 mkdir -p $GOPATH/src/golang.org/xcd $GOPATH/src/golang.org/xgit clone https://github.com/golang/crypto.git

September 26, 2021 · 1 min · jiezi

关于golang:深入理解Go从0到1实现一个filtermiddleware

filter(也称middleware)是咱们平时业务中用的十分宽泛的框架组件,很多web框架、微服务框架都有集成。通常在一些申请的前后,咱们会把比拟通用的逻辑都会放到filter组件来实现。如打申请日志、耗时、权限、接口限流等通用逻辑。那么接下来我会和你一起实现一个filter组件,同时让你理解到,它是如何从0到1搭建起来的,具体在演进过程中遇到了哪些问题,是如何解决的。 从一个简略的server说起咱们看这样一段代码。首先咱们在服务端开启了一个http server,配置了/这个路由,hello函数解决这个路由的申请,并往body中写入hello字符串响应给客户端。咱们通过拜访127.0.0.1:8080就能够看到响应后果。具体的实现如下: // 模仿业务代码func hello(wr http.ResponseWriter, r *http.Request) { wr.Write([]byte("hello"))}func main() { http.HandleFunc("/", hello) if err := http.ListenAndServe(":8080", nil); err != nil { panic(err) }}打印申请耗时v1.0接下来有一个需要,须要打印这个申请执行的工夫,这个也是咱们业务中比拟常见的场景。咱们可能会这样实现,在hello这个handler办法中退出工夫计算逻辑,主函数不变: // 模仿业务代码func hello(wr http.ResponseWriter, r *http.Request) { // 减少计算执行工夫逻辑 start := time.Now() wr.Write([]byte("hello")) timeElapsed := time.Since(start) // 打印申请耗时 fmt.Println(timeElapsed)}func main() { http.HandleFunc("/", hello) if err := http.ListenAndServe(":8080", nil); err != nil { panic(err) }}然而这样实现依然有肯定问题。假如咱们有一万个申请门路定义、所以有一万个handler和它对应,咱们在这一万个handler中,如果都要加上申请执行工夫的计算,那必然代价是相当大的。 为了晋升代码复用率,咱们应用filter组件来解决此类问题。大多数web框架或微服务框架都提供了这个组件,在有些框架中也叫做middleware。 filter退场filter的基本思路,是把功能性(业务代码)与非功能性(非业务代码)拆散,保障对业务代码无侵入,同时进步代码复用性。在解说2.0的需要实现之前,咱们先回顾一下1.0中比拟重要的函数调用http.HandleFunc("/", hello) 这个函数会接管一个路由规定pattern,以及这个路由对应的处理函数handler。咱们个别的业务逻辑都会写在handler外面,在这里就是hello函数。咱们接下来看一下http.HandleFunc()函数的具体定义: func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) 这里要留神一下,规范库中又把func(ResponseWriter, *Request)这个func从新定义成一个类型别名HandlerFunc: ...

September 26, 2021 · 2 min · jiezi

关于golang:Go进阶数据结构Channel

channel 是 Golang 提供的 goroutine 间的通信形式,能够让一个 goroutine 发送特定值到另一个 goroutine。 个性通道没有缓冲区,或者有缓冲区但缓冲区没有数据时,从通道读取数据会阻塞,直到有协程向通道中写入数据。相似地,通道没有缓冲区,或者缓冲区已满时,向通道写入数据也会阻塞,直到有协程从通道读取数据。对于值为 nil 的通道,无论读写都会阻塞,而且是永恒阻塞。 应用内置函数 close 能够敞开通道,尝试向已敞开的通道发送数据会触发 panic,但此时依然可读。通道读取的表达式最多有两个返回值: x, ok := <-ch 第一个变量示意读出的数据,第二个变量示意是否胜利读取了数据,它的值只跟通道缓冲区中是否有数据无关,与通道的敞开状态无关。 实现原理数据结构源码 src/runtime/chan.go:hchan 定义了 channel 的数据结构: type hchan struct { qcount uint // 以后队列中残余元素个数 dataqsiz uint // 环形队列长度,即能够寄存的元素个数 buf unsafe.Pointer // 环形队列指针 elemsize uint16 // 每个元素的大小 closed uint32 // 标识敞开状态 elemtype *_type // 元素类型 sendx uint // 队列下标,批示元素写入时寄存到队列中的地位 recvx uint // 队列下标,批示元素从队列的该地位读出 recvq waitq // 期待读音讯的 goroutine 队列 sendq waitq // 期待写音讯的 goroutine 队列 lock mutex // 互斥锁,chan 不容许并发读写}能够看出 channel 由队列、类型信息、goroutine 期待队列组成。 ...

September 25, 2021 · 5 min · jiezi

关于golang:我们一起来学-RabbitMQ-一-RabbitMQ-的基本介绍

【咱们一起来学 RabbitMQ 一 】RabbitMQ 的根本介绍嗨,大家好,我是小魔童哪吒,咱们从明天开始进入开源组件的学习,一边学习一边总结一边分享 文章提纲如下: RabbitMQ 简略介绍及应用RabbitMQ 的六种工作模式本次文章不波及编码,编码咱们对立放在下一次 RabbitMQ 简略介绍RabbitMQ 是一套开源(MPL)的音讯队列服务软件,是由 LShift 提供的一个 Advanced Message Queuing Protocol (AMQP) 的开源实现,由以高性能、强壮以及可伸缩性闻名的 Erlang 写成。AMQP 是什么?高级音讯队列协定,它使得听从该标准的客户端利用和消息中间件服务器的全功能互操作成为可能 客户端利用能够通过这个协定与音讯代理和它实现的 AMQP 模型进行交互通信 MQ 是什么?全称 Message Queue , 即音讯总线 是一种跨过程、异步的通信机制 用于上下游传递音讯,由音讯零碎来确保音讯的牢靠传递。 RabbitMQ 个别应用在什么场景?错峰流控保证数据的最终一致性上下游逻辑解耦具体的应用场景与上面说到的 RabbitMQ 的六种工作模式非亲非故RabbitMQ 组件中都蕴含了啥? 消费者能够订阅某个队列 生产者创立音讯,而后公布到队列中(queue),最终将音讯发送到监听的消费者 Broker:一个实体,用于标识音讯队列服务器 Virtual Host虚拟主机 标识一批交换机、音讯队列和相干对象 虚拟主机是共享雷同的身份认证和加密环境的独立服务器域 每个vhost实质上就是一个mini版的RabbitMQ服务器,领有本人的队列、交换器、绑定和权限机制 Exchange交换器,用来接管生产者发送的音讯并将这些音讯路由给服务器中的队列 Queue音讯队列,用来保留音讯直到发送给消费者 它是音讯的容器,也是音讯的起点 一个音讯可投入一个或多个队列 音讯始终在队列外面,期待消费者连贯到这个队列并将它取走 Banding绑定,用于音讯队列和交换机之间的关联 一个绑定就是基于路由键将交换机和音讯队列连接起来的路由规定,所以能够将交换器了解成一个由绑定形成的路由表 Channel信道,多路复用连贯中的一条独立的双向数据流通道 信道是建设在实在的TCP连贯边疆虚构链接,AMQP命令都是通过新到收回去的,不论是公布音讯、订阅队列还是接管音讯,这些动作都是通过信道实现的,为了复用一条TCP连贯 Connection网络连接,例如一个TCP连贯,能够有多个 Publisher音讯的生产者 也是一个向交换器公布音讯的客户端应用程序 Consumer音讯的消费者 示意从一个音讯队列中获得音讯的客户端应用程序 Message音讯,它是由音讯头和音讯体组成 音讯体是不通明的,而音讯头则是由一系列的可选属性组成 这些属性包含如下参数 routing-key (路由键)priority (优先级)delivery-mode (音讯可能须要持久性存储[音讯的路由模式])市面上都有哪些 MQ ?RabbitMQKafkaActiveMQZeroMQRocketMQ那么咱们为啥抉择 RabbitMQ ?咱们来看看 RabbitMQ 的特点 ...

September 25, 2021 · 1 min · jiezi

关于golang:Golang-知识点总结

微服务框架常见的APIGetWay 框架: kong zuul envory nginx 服务发现,服务注册组件: eureka etcd zookeeper consul 基于客户端发现模式(服务发现负载都在客户端)基于服务器服务发现模式(搭建一个服务发现注册的中继服务)Service Mesh 的发现模式(服务发现,注册---与客户端的在一个过程中) go web框架: gin iris beego error与异样的应用: pkg/erros 包 go 的并发模型:go 的内存模型 ---- happens before chan syncMutex (线程阻塞 唤醒) 模式: handoff spinRWMutexContextwaitGroupchanatomic 把握 errGroup 并发解决包 微服务的可用性:隔离 ---> 隔离,以避免相互影响的状况超时管制 ----> 基于 Context 超时过载 ---> RateLimiter 依据CPU 负载状况限流 ----> redis QPS 形式降级 ----> 提供有损的服务重试 ---> 申请失败进行重试机制负载平衡 ---> 基于主机CPU内存状况进行负载 分布式事物的解决: 本地事务+音讯队列 go的底层原理:goroutine 的调度模型 GMP 调度算法内存调配 ---基于groutine模式 --->mcache mcentral mheap, 内存的调配构造 spans bitmap arena, 内存治理的根本单元: mspan内存回收 ---标记革除法三色标记法: 插入屏障 删除屏障 混合屏障模式 ...

September 24, 2021 · 1 min · jiezi

关于golang:GO进阶训练营完结

1 预处理问题1:什么是预编译?何时需要预编译? 答: 预编译又称预处理,是整个编译过程最先做的工作,即程序执行前的一些预处理工作。次要处理#结尾的指令。如拷贝#include蕴含的文件代码、替换#define定义的宏、条件编译#if等。. 何时需要预编译: 1、总是使用不常常改变的大型代码体。 2、程序由多个模块组成,所有模块都使用一组标准的蕴含文件和雷同的编译选项。在这种情况下,可能将所有蕴含文件预编译为一个预编译头。 问题2:写一个“标准”宏,这个宏输出两个参数并返回较小的一个 答:#define MIN(x, y) ((x)<(y)?(x):(y)) //结尾没有; 问题3:#与##的作用? 答:#是把宏参数转化为字符串的运算符,##是把两个宏参数连接的运算符。 例如: define STR(arg) #arg 则宏STR(hello)开展时为”hello”define NAME(y) name_y 则宏NAME(1)开展时仍为name_ydefine NAME(y) name_##y 则宏NAME(1)开展为name_1define DECLARE(name, type) typename##_##type##_type,则宏DECLARE(val, int)开展为int val_int_type 问题4:如何避免头文件被重复蕴含? 答: 例如,为避免头文件my_head.h被重复蕴含,可在其中使用条件编译: ifndef _MY_HEAD_Hdefine _MY_HEAD_H /空宏//其余语句/ endif2 关键字问题1:static关键字的作用? 答: Static的用途次要有两个,一是用于润饰存储类型使之成为动态存储类型,二是用于润饰链接属性使之成为外部链接属性。 1动态存储类型: 在函数内定义的动态局部变量,该变量存在内存的动态区,所以即使该函数运行结束,动态变量的值不会被销毁,函数下次运行时能仍用到这个值。 在函数外定义的动态变量——动态全局变量,该变量的作用域只能在定义该变量的文件中,不能被其余文件通过extern引用。 2 外部链接属性 动态函数只能在申明它的源文件中使用。问题2:const关键字的作用? 答: 1申明常变量,使得指定的变量不能被修改。 const int a = 5;/a的值一直为5,不能被改变/ const int b; b = 10;/b的值被赋值为10后,不能被改变/ const int ptr; /ptr为指向整型常量的指针,ptr的值可能修改,但不能修改其所指向的值*/ int const ptr;/ptr为指向整型的常量指针,ptr的值不能修改,但可能修改其所指向的值*/ const int const ptr;/ptr为指向整型常量的常量指针,ptr及其指向的值都不能修改*/ ...

September 24, 2021 · 4 min · jiezi

关于golang:Go-常见错误集锦之函数式选项模式

本节将通过一个常见的用例来展现如何使API不便且敌对地承受选项配置。咱们将深入研究不同的选项,以达到最初展现一个在Go中风行的解决方案:函数式选项模式。 首先,从概念上看下什么是函数式选项模式。 这个概念由两局部组成:函数式和选项。 所谓函数式,是从函数式编程中借鉴过去的概念,即函数和其余根底类型一样,能够将函数作为参数、返回值以及赋值给其余变量。 选项就是配置中的参数字段。所以,函数式选项就是通过一系列的具备雷同签名的函数(或匿名函数或带某个函数字段的构造体)来对选项中的字段执行相干的逻辑操作。 上面咱们通过一个例子来看看函数式选项模式的演化过程。 假如咱们要设计一个库,并裸露一个函数接口来创立一个HTTP服务器。该函数将承受不同的输出:一个地址和一个端口。该函数的签名如下: func NewServer(addr string, port int) (*http.Server, error) { // ...}调用者开始应用这个函数,并且所有人都很开心。然而,在某个工夫点,调用者开始埋怨该函数有一些限度并短少一些其余参数(例如,超时工夫,连贯上下文)。然而,这时咱们开始留神到如果咱们减少一个新的参数,它将会毁坏兼容性,会强制使用者批改他们曾经调用过的NewServer函数。 同时,咱们也心愿扩大与端口治理相干的逻辑,像下图展现的这样: 如果端口号没有设置,则应用默认值如果端口号是正数,则返回谬误如果端口号是0,则应用随机端口否则,应用用户提供的端口号咱们该如何以敌对的API的形式实现这个函数呢?让咱们来看看所有的不同实现。 实现一:传一个配置构造体的实现(Config struct)第一种办法是应用一个构造体(config struct)来解决不同的配置选项。咱们能够把参数分成两类:根底配置和可选配置。根底配置参数能够作为函数参数,可选参数在config构造体中解决: type Config struct { Port int}func NewServer(addr string, cfg Config) {}这种解决方案修复了兼容性的问题。如果咱们减少了新的配置选项,它也不会中断调用者的调用。然而,这种办法没有解决端口治理相干的需要。事实上,咱们应该晓得如果构造体的字段没有提供,那默认将会被初始化成零值: int类型的零值是0浮点类型的零值是0.0字符串的零值是“”slice、map、channels、指针、接口和函数的零值是nil因而,在上面的例子中两个构造体是相等的: c1 := httplib.Config{ Port:0, ①}c2 := httplib.Config{ ②}① Port被初始化成0② Port字段缺失,所以初始值也是0 在咱们的例子中,咱们须要找到一种办法来正确区分端口号是被设置成了0还是没有提供port字段。一种可能的办法是将构造体的字段都定义成指针类型: type Config struct { Port *int}这种形式也会工作,但有两个毛病。 首先,调用者提供整型指针并不不便。调用者必须要创立一个变量并且要以指针的模式传递:port := 0config := httplib.Config{ Port: &port, ①}① 提供一个整型指针 传递指针的话,整体 API 变得不那么方便使用。 第二个毛病是应用咱们库的调用者,如果是带默认配置的话,调用者必须要传递一个空构造体:httplib.NewServer("localhost", httplib.Config{})这段代码的可读性也不是很好。阅读者不得不思考这个struct构造体到底是什么意思。 实现二: 结构器模式构建器模式为各种对象创立问题提供了灵便的解决方案。咱们看看这种模式是如何帮忙咱们设计一个敌对的API,以满足咱们所有的需要,包含端口号的治理。 type Config struct { ① Port int}type ConfigBuilder struct { ② Port *int}func (b *ConfigBuilder) Port(port int) { ③ b.Port = &port}func (b *ConfigBuilder) Build() (Config, error) { ④ cfg := Config{} if b.Port == nil { ⑤ cfg.Port = defaultHTTPPort } else { if *b.Port == 0 { cfg.Port = randomPort() } else if *b.Port < 0 { return Config{}, errors.New("port should be positive") } else { cfg.Port = *b.Port } } return cfg, nil}func NewServer(addr string, config Config) (*http.Server, error) { // ...}① 定义Config构造体② 定义ConfigBuilder构造体,蕴含一个可选的port字段③ 设置port的public办法④ Build办法创立一个config构造体⑤ 治理Port的次要逻辑 ...

September 23, 2021 · 2 min · jiezi

关于golang:Go之time包用法

Go之time包用法time包提供了工夫的显示和测量用的函数。日历的计算采纳的是公历。time 类型 type Time struct {// wall and ext encode the wall time seconds, wall time nanoseconds,// and optional monotonic clock reading in nanoseconds.//// From high to low bit position, wall encodes a 1-bit flag (hasMonotonic),// a 33-bit seconds field, and a 30-bit wall time nanoseconds field.// The nanoseconds field is in the range [0, 999999999].// If the hasMonotonic bit is 0, then the 33-bit field must be zero// and the full signed 64-bit wall seconds since Jan 1 year 1 is stored in ext.// If the hasMonotonic bit is 1, then the 33-bit field holds a 33-bit// unsigned wall seconds since Jan 1 year 1885, and ext holds a// signed 64-bit monotonic clock reading, nanoseconds since process start.wall uint64ext int64 // loc specifies the Location that should be used to // determine the minute, hour, month, day, and year // that correspond to this Time. // The nil location means UTC. // All UTC times are represented with loc==nil, never loc==&utcLoc. loc *Location}time能够准确到纳秒示例 ...

September 23, 2021 · 3 min · jiezi

关于golang:Go-API-开发基础

最近总是容易想不起来如何从零开搞一个根底的 go-gin api 开发基架,明天来记录以下。 1. 如何开始? 随着 Go 的版本更新引入了新的模块机制,这让一段时间没有应用 Go 的我有点小懵,跟着官网的 Guide 大略也明确了点货色,记录以下。 首先,创立一个空文件夹,作为我的项目的名称命名,而后 cd 到该文件夹外部,应用命令: go mod init xxx.com这句命令的意思是初始化本人我的项目的模块并为其起一个名字,随后本人的我的项目外部模块的援用就能够用这个包的名称作为前缀(有一点 namespace 的意思)。 2. 引入心愿应用的包 例如 go 的 gin 包和 fresh 包 go get -u github.com/gin-gonic/gingo get -u github.com/pilu/freshgin 包是一个不错的 api 开发包,能够用作为 api 我的项目的根底。 fresh 包是一个热加载的包,他能够让你像开发前端一样无需手动重启即可更新批改过的性能。 fresh 在 ubuntu 须要将 gopath/bin 目录增加到环境变量,否则会提醒找不到该命令。3. 入口文件 在 go.mod 同级别创立一个名为 main.go 的文件,贴入如下内容: package mainimport "fmt"func main() { fmt.Print("hello world!")}4. 运行 fresh测试热重载放弃 fresh 的运行状态,间接批改 hello world! 为 你好世界! 查看成果。 ...

September 23, 2021 · 1 min · jiezi

关于golang:Golang正确使用kafka的姿势细节决定成败

本文转自 跟我学IM后盾开发作者 杰克.许 经OpenIM技术人员整顿订正后公布。写在后面Open-IM是由前微信技术专家打造的开源的即时通讯组件。Open-IM包含IM服务端和客户端SDK,实现了高性能、轻量级、易扩大等重要个性。开发者通过集成Open-IM组件,并私有化部署服务端,能够将即时通讯、实时网络能力疾速集成到本身利用中,并确保业务数据的安全性和私密性。 Kafka在OpenIM我的项目中承当重要的角色,感激作者在应用OpenIM中发现的bug(应用Kafka不当的bug) 理解更多原创文章:【OpenIM原创】开源OpenIM:轻量、高效、实时、牢靠、低成本的音讯模型 【OpenIM原创】C/C++调用golang函数,golang回调C/C++函数 【OpenIM原创】简略轻松入门 一文解说WebRTC实现1对1音视频通信原理 【OpenIM扩大】OpenIM服务发现和负载平衡golang插件:gRPC接入etcdv3 【开源OpenIM】高性能、可伸缩、易扩大的即时通讯架构 如果您有趣味能够在文章结尾理解到更多对于咱们的信息,期待着与您的交换单干。01 背景在一些业务零碎中,模块之间通过引入Kafka解耦,拿IM举例(图起源): 用户A给B发送音讯,msg_gateway收到音讯后,投递音讯到Kafka后就给A返回发送胜利。这个时候,其实还没有长久化到mysql中,尽管最终会放弃一致性。所以,试想如果Kafka丢音讯了,是不是就出大问题了?A认为给B发送音讯胜利了,然而在服务器内部消息失落了B并没有收到。 所以,在应用Kafka的时候,有一些业务对音讯失落问题十分的关注。 同样,常见的问题还有: 反复生产的问题。乱序的问题。上面咱们来一起看一下如何应用sarama包来解决这些问题。 02 Kafka音讯失落问题形容以下内容起源: kafka什么时候会丢音讯:https://blog.csdn.net/qrne06/... 下面咱们放心的点须要进一步明确一下丢音讯的定义:kafka集群中的局部或全副broker挂了,导致consumer没有及时收到音讯,这不属于丢音讯。broker挂了,只有音讯全副长久化到了硬盘上,重启broker集群之后,使消费者持续拉取音讯,音讯就没有失落,依然全量生产了。所以我的了解,所谓丢音讯,意味着:开发人员未感知到哪些音讯没有被生产。 作者把音讯的失落演绎了以下几种状况: 1) producer把音讯发送给broker,因为网络抖动,音讯没有达到broker,且开发人员无感知。 解决方案:producer设置acks参数,音讯同步到master之后返回ack信号,否则抛异样使应用程序感知到并在业务中进行重试发送。这种形式肯定水平保障了音讯的可靠性,producer期待broker确认信号的时延也不高。 2)producer把音讯发送给broker-master,master接管到音讯,在未将音讯同步给follower之前,挂掉了,且开发人员无感知。 解决方案:producer设置acks参数,音讯同步到master且同步到所有follower之后返回ack信号,否则抛异样使应用程序感知到并在业务中进行重试发送。这样设置,在更大程度上保障了音讯的可靠性,毛病是producer期待broker确认信号的时延比拟高。 3)producer把音讯发送给broker-master,master接管到音讯,master未胜利将音讯同步给每个follower,有音讯失落危险。 解决方案:同上。 4)某个broker音讯尚未从内存缓冲区长久化到磁盘,就挂掉了,这种状况无奈通过ack机制感知。 解决方案:设置参数,放慢音讯长久化的频率,能在肯定水平上缩小这种状况产生的概率。但进步频率天然也会影响性能。 5)consumer胜利拉取到了音讯,consumer挂了。 解决方案:设置手动sync,生产胜利才提交。 综上所述,集群/我的项目运行失常的状况下,kafka不会丢音讯。一旦集群呈现问题,音讯的可靠性无奈齐全保障。要想尽可能保障音讯牢靠,根本只能在发现音讯有可能没有被生产时,重发消息来解决。所以在业务逻辑中,要思考音讯的反复生产问题,对于关键环节,要有幂等机制。 作者的几条倡议: 1)如果一个业务很要害,应用kafka的时候要思考丢音讯的老本和解决方案。 2)producer端确认音讯是否达到集群,若有异样,进行重发。 3)consumer端保障生产幂等性。 4)运维保障集群运行失常且高可用,保障网络状况良好。 03 生产端丢音讯问题解决下面说了,只须要把producer设置acks参数,期待Kafka所有follower都胜利后再返回。咱们只须要进行如下设置: \1. config := sarama.NewConfig() 2. config.Producer.RequiredAcks = sarama.WaitForAll // -1ack参数有如下取值: 1. const (2. // NoResponse doesn't send any response, the TCP ACK is all you get. 3. NoResponse RequiredAcks = 04. // WaitForLocal waits for only the local commit to succeed before responding. 5. WaitForLocal RequiredAcks = 1 6. // WaitForAll waits for all in-sync replicas to commit before responding. 7. // The minimum number of in-sync replicas is configured on the broker via 8. // the `min.insync.replicas` configuration key. 9. WaitForAll RequiredAcks = -110. )04 生产端丢音讯问题通常生产端丢音讯都是因为Offset主动提交了,然而数据并没有插入到mysql(比方呈现BUG或者过程Crash),导致下一次消费者重启后,音讯漏掉了,天然数据库中也查不到。这个时候,咱们能够通过手动提交解决,甚至在一些简单场景下,还要应用二阶段提交。 ...

September 22, 2021 · 4 min · jiezi

关于golang:手把手教你Golang的协程池设计

前言当初很多公司都在陆续的搭建golang的语言栈,大家有没有想过为什么会呈现这种状况?一是因为go比拟适宜做中间件,还有一个起因就是go的并发反对比拟好,也就是咱们平时所谓的高并发,并发反对离不开协程,当然协程也不是乱用的,须要治理起来,治理协程的形式就是协程池,所以协程池也并没有那么神秘,明天咱们就来一步一步的揭开协程池的面纱,如果你没有接触过go的协程这块的话也没有关系,我会尽量写的具体。 # goroutine(协程) 先来看一个简略的例子 func go_worker(name string) { for i := 0; i < 5; i++ { fmt.Println("我的名字是", name) time.Sleep(1 * time.Second) } fmt.Println(name, "执行结束")}func main() { go_worker("123") go_worker("456") for i := 0; i < 5; i++ { fmt.Println("我是main") time.Sleep(1 * time.Second) }}咱们在执行这段代码的时候,当然是依照程序执行 go_worker("123")->go_worker("456")->我是main执行 输入后果如下 我的名字是 123我的名字是 123我的名字是 123我的名字是 123我的名字是 123123 执行结束我的名字是 456我的名字是 456我的名字是 456我的名字是 456我的名字是 456456 执行结束我是main我是main我是main我是main我是main这样的执行是并行的,也就是说必须得等一个工作执行完结,下一个工作才会开始,如果某个工作比较慢的话,整个程序的效率是可想而知的,然而在go语言中,反对协程,所以咱们能够把下面的代码革新一下 func go_worker(name string) { for i := 0; i < 5; i++ { fmt.Println("我的名字是", name) time.Sleep(1 * time.Second) } fmt.Println(name, "执行结束")}func main() { go go_worker("123") //协程 go go_worker("456") //协程 for i := 0; i < 5; i++ { fmt.Println("我是main") time.Sleep(1 * time.Second) }}咱们在不同的go_worker后面加上了一个go,这样所有工作就异步的串行了起来,输入后果如下 ...

September 22, 2021 · 2 min · jiezi

关于golang:开发方向

Queue:Kafka 音讯队列Cache:Redis/Memcached CacheProxy KV: RoCksDB、 LevelDB、Raft SQL: MySQL DBProxy (Vitess)Contin配置核心:Discovery:( Eureka、 Nacos、etcd)服务治理(可用性、元数据)调度:Yarn、K8s、 Mesos大数据:存储(HDFS),计算(Hve, Spark、 Presto)OLAP : ClickHouse、Kylin、Doris 数仓HQL、数仓建模

September 19, 2021 · 1 min · jiezi

关于golang:深入理解Go协程设计与调度原理上

协程是更轻量的用户态线程,是Go语言的外围。那么如何去调度这些协程何时去执行、如何更正当的调配操作系统资源,须要一个设计良好的调度器来反对。什么才是一个好的调度器?能在适当的机会将适合的协程调配到适合的地位,保障偏心和效率。从go func说起func main() { for i := 0; i < 10; i++ { go func() { fmt.Println(i) }() } time.Sleep(1 * time.Second)}这段代码中,咱们开启了10个协程,每个协程打印去打印i这个变量。因为这10个协程的调度机会并不固定,所以等到协程被调度执行的时候才会去取循环中变量i的值。 咱们写的这段代码,每个咱们开启的协程都是一个计算工作,这些工作会被提交给go的runtime。如果计算工作十分多,有成千上万个,那么这些工作是不可能同时被立即执行的,所以这个计算工作肯定会被先暂存起来,个别的做法是放到内存的队列中期待被执行。 而生产端则是一个go runtime保护的一个调度循环。调度循环简略来说,就是一直从队列中生产计算工作并执行。这里实质上就是一个生产者-消费者模型,实现了用户工作与调度器的解耦。这里图中的G就代表咱们的一个goroutine计算工作,M就代表操作系统线程 调度策略接下来咱们具体解说一下调度策略。 生产端生产端1.0接下面的例子,咱们生产了10个计算工作,咱们肯定是要在内存中先把它存起来期待调度器去生产的。那么很显然,最合适的数据结构就是队列,先来先服务。然而这样做是有问题的。当初咱们都是多核多线程模型,消费者必定不止有一个,所以如果多个消费者去生产同一个队列,会呈现线程平安的问题,必须加锁。所有计算工作G都必须在M上来执行。 生产端2.0在Go中,为了解决加锁的问题,将全局队列拆成了多个本地队列,而这个本地队列由一个叫做P的构造来治理。这样一来,每个M只须要去先找到一个P构造,和P构造绑定,而后执行P本地队列里的G即可,完满的解决了加锁的问题。 然而每个P的本地队列长度不可能有限长(目前为256),设想一下有成千上万个go routine的场景,这样很可能导致本地队列无奈包容这么多的Goroutine,所以Go保留了全局队列,用以解决上述情况。 那么为什么本地队列是数组,而全局队列是链表呢?因为全局队列是本地队列的兜底策略,所以全局队列大小必须是有限的,所以必须是一个链表。 全局队列被调配在全局的调度器构造上,只有一份: type schedt struct { ... // Global runnable queue. runq gQueue // 全局队列 runqsize int32 // 全局队列大小 ...}那么本地队列为什么做成数组而不是链表呢?因为操作系统内存治理会将间断的存储空间提前读入缓存(局部性原理),所以数组往往会被都读入到缓存中,对缓存敌对,效率较高;而链表因为在内存中散布是扩散的,往往不会都读入到缓存中,效率较低。所以本地队列综合思考性能与扩展性,还是抉择了数组作为最终实现。 而Go又为了实现局部性原理,在P中又加了一个runnext的构造,这个构造大小为1,在runnext中的G永远会被最先调度执行。接下来会讲为什么须要这个runnext构造。残缺的生产端数据结构如下: P构造的定义: type p struct { ... // Queue of runnable goroutines. Accessed without lock. runqhead uint32 // 本地队列队头 runqtail uint32 // 本地队列队尾 runq [256]guintptr // 本地队列,大小256 runnext guintptr // runnext,大小为1 ...}残缺的生产流程每个协程G创立进去之后,主线程m0会调用newproc(),这里会先选定以后m0上的P每个协程G都会被尝试先放到P中的runnext,若runnext为空,生产完结若runnext满了,则将原来runnext中的G踢到本地队列中。生产完结若本地队列也满了,则将本地队列中的G拿出一半,加上以后协程G,这个拼成的构造在源码中叫batch,会将batch一起放到全局队列中,生产完结。这样一来本地队列的空间就不会满了,接下来的生产流程不会被本地队列满而阻塞所以咱们看到,最终runnext中的G肯定是最初生产进去的G,也会被优先被调度去执行。这里是思考到局部性原理,最近创立进去的协程肯定会被最先执行,优先级是最高的。 ...

September 19, 2021 · 2 min · jiezi

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

上一篇文章咱们实现了对构造体中根本数据类型的解析。这一篇文章,则是真正令人头疼的、在前两篇文章未解决的几个主题了: 匿名成员构造体中嵌套构造体Go 切片Go 数组Go map构造体中的匿名成员咱们回来看一下上一篇文章中的 marshalToValues 函数,其中有一行 “ft.Anonymous”: func marshalToValues(in interface{}) (kv url.Values, err error) { // ...... // 迭代每一个字段 for i := 0; i < numField; i++ { fv := v.Field(i) // field value ft := t.Field(i) // field type if ft.Anonymous { // TODO: 后文再解决 continue } // ...... } return kv, nil}前文提过,这示意以后的字段是一个匿名字段。在 Go 中,匿名成员常常用于实现靠近于继承的性能,比方: type Dog struct{ Name string}func (d *Dog) Woof() { // ......}type Husky struct{ Dog}这样一来,类型 Husky 就 “继承” 了 Dog 类型的 Name 字段,以及 Woof() 函数。 ...

September 18, 2021 · 4 min · jiezi

关于golang:业务学习分库分表回顾

背景前段时间因为业务须要,须要对外围库分库分表,迁徙了大略有40亿数据,在此记录以便之后再来看看这种计划的优劣。 写在后面为什么要拆库拆表?随着公司业务疾速倒退,数据库中的数据量猛增,拜访性能也变慢了,优化火烧眉毛。剖析一下问题呈现在哪儿呢? 关系型数据库自身比拟容易成为零碎瓶颈,单机存储容量、连接数、解决能力都无限。当单表的数据量达到1000W或100G当前,因为查问维度较多,即便增加从库、优化索引,做很多操作时性能仍降落重大。针对生产环境中呈现的这种状况,咱们通常有软硬两种形式去解决,“硬”指的是在硬件方面上进行进步,即咱们通常挂在嘴边的加存储、加CPU等,这种计划的老本很高(ps:有钱人疏忽),并且如果瓶颈不在硬件就很好受了。”软“指的是咱们在设计层去做宰割,行将打表打散,将压力大的库拆分(星星之火也能够燎原的)。 常见的几种拆表形式分库分表包含分库和分表两个局部,在生产中通常包含:垂直分库、程度分库、垂直分表、程度分表四种形式。咱们先来理解下垂直和程度的概念: “垂直”通常指的是将一个表依照字段分成多表,每个表存储其中一部分字段。”程度“ 通常指的是不会扭转表构造,将数据依照肯定的规定划分到多处。咱们晓得这个概念之后再来解释常见的四种分库分表形式: 垂直分表:将一个宽表的字段按拜访频次、是否是大字段的准则或者其它特定的规定拆分为多个表,这样既能使业务清晰,还能晋升局部性能。拆分后,尽量从业务角度防止联查,否则性能方面将得失相当。垂直分库:将多个表按业务耦合松紧归类,别离寄存在不同的库,这些库能够散布在不同服务器,从而使拜访压力被多服务器负载,大大晋升性能,同时能进步整体架构的业务清晰度,不同的业务库可依据本身状况定制优化计划。然而它须要解决跨库带来的所有简单问题。程度分库:将一个表的数据(按数据行)分到多个不同的库,每个库只有这个表的局部数据,这些库能够散布在不同服务器,从而使拜访压力被多服务器负载,大大晋升性能。它不仅须要解决跨库带来的所有简单问题,还要解决数据路由的问题(数据路由问题后边介绍)。程度分表:将一个表的数据(按数据行)分到多个同一个数据库的多张表中,每个表只有这个表的局部数据,这样做能小幅晋升性能,它仅仅作为程度分库的一个补充优化。一般来说,在零碎设计阶段就应该依据业务耦合松紧来确定垂直分库,垂直分表计划,在数据量及拜访压力不是特地大的状况,首先思考缓存、读写拆散、索引技术等计划。若数据量极大,且持续增长,再思考程度分库程度分表计划。这里咱们还要思考一个问题,对于已有的业务咱们如何从原来的单库单表平滑无损的去迁徙到新的分片库分片表呢?(读者能够思考下这个问题,这也是本篇的重点)。 分库分表后带来的问题主键 id 唯一性。分布式事务问题:在执行分库分表之后,因为数据存储到了不同的库上,数据库事务管理呈现了艰难。跨库跨表的 join 问题:在执行了分库分表之后,难以避免会将本来逻辑关联性很强的数据划分到不同的表、不同的库上,这时,表的关联操作将受到限制,咱们无奈join位于不同分库的表。后期筹备收敛所有直连DB的状况为什么会有这一项呢?因为在咱们的业务代码中,很难防止的因为种种的问题导致有些业务场景中是直连的DB的,如果是本人团队的还好,如果不是就会造成不可预知的结果。这部分工作如果是工程绝对标准且DB监控做的比拟好的状况下还比拟好排查,否则将很难去梳理全面,所以监控和标准不是没有用的,如果你说没有,拆库拆表试试吧。收敛除了保护比拟好保护之外,业务方对于本人的数据掌控度也比拟大,所有的数据写入与读取都有明确的记录(设想一下本人保护的数据不晓得被谁偷偷改了的懊恼)。 分布式ID生成器咱们采纳分库分表,最佳的实现形式是在不同的分片表中应用全局惟一id。到这有的同学会问了,“为什么呢?我即便分库分表后,每条数据拆分到每个表中,因为MySQL数据库主键自增的缘故,它们的ID在各个表是独立的,查问的时候 select * from 表名,也可能查问进去对应的信息,欸,这也不须要唯一性ID啊“。但咱们换个角度思考,如:电商场景订单量微小,订单数据存入数据库,必定须要对数据库进行了分库分表,欸,你有没有发现每个人的订单号必定都是不同的,这就体现了全局唯一性ID,当然同学又会说,我再开一个字段去独自存储这个订单id不行么?这就是要刚我啊,少侠手下留情。详见:ID生成器详解 梳理ID类型变更对依赖方的影响既然咱们采纳了全局惟一id,咱们就不得不思考对依赖方的影响,大略有以下几点: 上游依赖有没有对库表的id进行强制转换类型,例如强制转化为int32。前端是否有间接读取整形的ID,因为Javascript的数字存储应用了IEEE 754中规定的双精度浮点数数据类型,而这一数据类型可能平安存储 -(2^53-1) 到 2^53-1 之间的数值(蕴含边界值)。JSON 是 Javascript 的一个子集,所以它也恪守这个规定。而int64 类型的数值范畴是 -(2^63-1) 到 2^63-1。应用int64 类型json 对于超出范围的数字,会呈现解析谬误的状况。梳理对于binlog的依赖因为咱们须要进行分库分表操作,所以对于原有的依赖老库binlog的中央也要进行相应革新。 梳理分片键是否都能够获取到这个能够依据本人的业务看是否须要解决 SOP肯定要制订具体的SOP且要严格执行 方案设计(单库单表到分片库表的切换)计划一阶段一:创立新库并同步老库数据到新库依据binlog同步数据阶段二:校验数据一致性校验binlog同步数据的一致性阶段三:业务切流量业务代码关上开关,切换为读写新库并放量计划二阶段一:创立新库并同步数据到新库阶段二:停服&&校验数据一致性阶段三:业务切流量计划三阶段一: 双写阶段双写分为几种场景,insert&&update&&delete状况一:失常状况(没有失败,更新的记录存在新老库之中) Insert 业务方双写新库老库: 新库老库失常插入数据,因为是分库分表,所以采纳全局惟一id来代替原来的自增id,历史数据保留原有信息Update 对于新老库进行更新(只针对新库有记录)Delete 现有业务暂无硬删除,疏忽状况二: 异常情况(老库失败,新库失败) 老库失败(老库Insert失败,Update失败) 因为双写是串行的,所以即便失败了也不须要思考新库失败(新库Insert库失败,新库Update失败) 新库Insert失败,记录写库信息,发送失败弥补音讯,进行数据修复新库Update失败,记录写库信息,发送失败弥补音讯,进行数据修复状况三:失败音讯重试 对于新库写库失败的数据,进行重试,比照新老库中的数据,雷同则跳过,不雷同用老库数据笼罩更新新库数据(加锁),如果此时依然失败,从新推送到音讯队列对于新库还未存在的数据进行更新时,依据更新信息从老库读取数据,而后插入到新库,此过程对新老库记录加锁保证数据的一致性阶段二:迁徙数据 (脚本)同步老库数据到新库,采纳insert ignore避免新老数据抵触 阶段三:数据一致性保障分批次比照新老库数据(脚本) 雷同, 跳过不同,老库笼罩新库(仅老库加锁即可)阶段四: 切读灰度放量新库读全量切读阶段五: 迁徙上游依赖次要是binlog依赖 阶段六: 停写老库阶段七: 回收资源&&清理开关计划四阶段一:创立新库并同步数据到新库(开启老库到新库同步数据)阶段二:校验数据一致性阶段三:停服Rename 毫秒级别阶段四:业务切流量至新库 先切读 再切写阶段五:开启新库到老库增量数据同步,保障新老数据增量是统一的计划优缺点简略比拟 计划一计划二计划三计划四长处操作绝对简略操作绝对简略1. 整个过程不停服平滑迁徙且无数据损失 2. 任何阶段零危险回滚。3. 上游有富余的工夫做迁徙。4. 不便读新库灰度。1. 操作绝对简略2. 上游有富余的工夫做迁徙。3. 业务侵入较少。毛病1. 切流量至新库之后若不合乎预期,期间产生的数据均为问题数据且问题数据无奈疾速复原。2. 依赖上游迁徙进度。3. 切换过程中写入失败的数据会失落。4. 上线后验证工夫短。5. 回滚后再次上线代价较大,以上几个问题有反复呈现的危险。同11. 业务侵入较大。2. 双写影响接口性能1. 在切换过程中服务不可用2. 验证工夫短3. 老库到新库写切换不洁净会导致数据时序问题(老库写和新库写操作同一条数据时)4. 回滚后再次上线后,以上几个问题有反复呈现的危险。总结在适合的业务场景采纳不同的计划才是最好的。 ...

September 16, 2021 · 1 min · jiezi

关于golang:Go-专栏|并发编程goroutinechannel-和-sync

原文链接: Go 专栏|并发编程:goroutine,channel 和 sync 优雅的并发编程范式,欠缺的并发反对,杰出的并发性能是 Go 语言区别于其余语言的一大特色。 在当今这个多核时代,并发编程的意义显而易见。应用 Go 开发并发程序,操作起来非常简单,语言级别提供关键字 go 用于启动协程,并且在同一台机器上能够启动成千上万个协程。 上面就来具体介绍。 goroutineGo 语言的并发执行体称为 goroutine,应用关键词 go 来启动一个 goroutine。 go 关键词前面必须跟一个函数,能够是有名函数,也能够是无名函数,函数的返回值会被疏忽。 go 的执行是非阻塞的。 先来看一个例子: package mainimport ( "fmt" "time")func main() { go spinner(100 * time.Millisecond) const n = 45 fibN := fib(n) fmt.Printf("\rFibonacci(%d) = %d\n", n, fibN) // Fibonacci(45) = 1134903170}func spinner(delay time.Duration) { for { for _, r := range `-\|/` { fmt.Printf("\r%c", r) time.Sleep(delay) } }}func fib(x int) int { if x < 2 { return x } return fib(x-1) + fib(x-2)}从执行后果来看,胜利计算出了斐波那契数列的值,阐明程序在 spinner 处并没有阻塞,而且 spinner 函数还始终在屏幕上打印提醒字符,阐明程序正在执行。 ...

September 16, 2021 · 3 min · jiezi

关于golang:GO语言入门-三数据类型

当初须要理解一下GO语言的数据类型。布尔型布尔型的值只能够是常量 true 或者 false。一个简略的例子: package mainimport "fmt"func main() { // 申明一个布尔型的变量i var i bool = true fmt.Println("打印一个布尔型的变量:i=", i)}运行后果为: 数字类型整型 int 和浮点型 float32、float64Go 语言反对整型和浮点型数字,并且反对复数,其中位的运算采纳补码。 package mainimport "fmt"func main() { // 申明一个数字类型的变量i var i int = 100 fmt.Println("打印一个数字类型的变量:i=", i)}运行后果为: 1. 整数型序号类型形容1uint8无符号 8 位整型 (0 到 255)2uint16无符号 16 位整型 (0 到 65535)3uint32无符号 32 位整型 (0 到 4294967295)4uint64无符号 64 位整型 (0 到 18446744073709551615)5int8有符号 8 位整型 (-128 到 127)6int16有符号 16 位整型 (-32768 到 32767)7int32有符号 32 位整型 (-2147483648 到 2147483647)8int64有符号 64 位整型 (-9223372036854775808 到 9223372036854775807)2. 浮点型序号类型形容1float32IEEE-754 32位浮点型数2float64IEEE-754 64位浮点型数3complex6432 位实数和虚数4complex12864 位实数和虚数3. 其余数字类型序号类型形容1byte相似 uint82rune相似 int323uint32 或 64 位4int与 uint 一样大小5uintptr无符号整型,用于寄存一个指针字符串类型字符串就是一串固定长度的字符连接起来的字符序列。Go 的字符串是由单个字节连接起来的。Go 语言的字符串的字节应用 UTF-8 编码标识 Unicode 文本。 ...

September 16, 2021 · 2 min · jiezi

关于golang:Golang中-channel-以及-groutine-理解

1、Curious ChannelsA closed channel never blocksA nil channel always blocks A send to a nil channel blocks foreverA receive from a nil channel blocks foreverA send to a closed channel panicsA receive from a closed channel returns the zero value immediately 2.Never start a goroutine without knowing how it will stopEvery time you write the statement go in a program, you should consider the question of how, and under what conditions, the goroutine you are about to start, will end. ...

September 14, 2021 · 1 min · jiezi

关于golang:Go-开发环境通过-iTerm-Oh-My-Zsh-打造终端

1.介绍iTerm2 是一个收费开源的终端,功能齐全的终端仿真程序;指标是在为用户提供OS X下最佳的命令行体验。 Oh My Zsh 是一个用于治理 Zsh 配置的开源框架,有着十分多的插件反对,比方代码高亮、代码提醒、各种语言反对等,还有很多主题可抉择,让终端界面更加好看。Zsh 是一款用于交互式应用的shell,也能够作为脚本解释器来应用。其蕴含了 bash,ksh,tcsh 等其余shell中许多优良性能,也领有诸多本身特色。 2.装置Homebrew关上自带的 Terminal(终端),安装包管理工具,不便进行装置或者更新应用软件: /bin/bash -c "$(curl -fsSL https://raw.github.com/Homebr...)" 3.下载iTerm2通过官网下载进行装置,可拜访 https://www.iterm2.com/downlo... 进行下载最新版本;而后进行解压,间接把 iTerm 利用拖入到 Application 目录中即可装置实现。或者,通过 Homebrew 进行装置: brew cask install iterm2 4.装置 Oh My Zsh装置 iTerm2 实现后,可间接进行关上,并且输出装置命令: sh -c "$(curl -fsSL https://raw.github.com/ohmyzs...)" 5.终端配置 装置好后能够看到多出了以下几个文件和目录,因为 MacOS 默认进行暗藏了,能够通过 ls -a 显示所有文件: .zshrc # 默认资源配置.zshenv # 环境变量配置.zsh_history # 历史命令 .oh-my-zsh│├───plugins # 默认插件目录├───themes # 默认主题目录└───custom # 用户自定义目录 ├───plugins # 第三方插件目录 └───themes # 第三方主题目录 ...

September 13, 2021 · 1 min · jiezi

关于golang:Go语言如何操纵Kafka保证无消息丢失

原文链接:Go语言如何操纵Kafka保障无音讯失落 背景目前一些互联网公司会应用音讯队列来做外围业务,因为是外围业务,所以对数据的最初一致性比拟敏感,如果两头呈现数据失落,就会引来用户的投诉,年底绩效就变成325了。之前和几个敌人聊天,他们的公司都在用kafka来做音讯队列,应用kafka到底会不会丢音讯呢?如果丢音讯了该怎么做好弥补措施呢? 本文咱们就一起来剖析一下,并介绍如何应用Go操作Kafka能够不失落数据。 本文操作kafka基于:https://github.com/Shopify/sa... 初识kafka架构维基百科对kafka的介绍: Kafka是由Apache软件基金会开发的一个开源流解决平台,由Scala和Java编写。该项目标指标是为解决实时数据提供一个对立、高吞吐、低提早的平台。其长久化层实质上是一个“依照分布式事务日志架构的大规模公布/订阅音讯队列”,这使它作为企业级基础设施来解决流式数据十分有价值。此外,Kafka能够通过Kafka Connect连贯到内部零碎(用于数据输出/输入),并提供了Kafka Streams——一个Java]流式解决库。该设计受事务日志的影响较大。kafka的整体架构比较简单,次要由producer、broker、consumer组成: 针对架构图咱们解释一个各个模块: Producer:数据的生产者,能够将数据公布到所抉择的topic中。Consumer:数据的消费者,应用Consumer Group进行标识,在topic中的每条记录都会被调配给订阅生产组中的一个消费者实例,消费者实例能够散布在多个过程中或者多个机器上。Broker:消息中间件解决节点(服务器),一个节点就是一个broker,一个Kafka集群由一个或多个broker组成。还有些概念咱们也介绍一下: topic:能够了解为一个音讯的汇合,topic存储在broker中,一个topic能够有多个partition分区,一个topic能够有多个Producer来push音讯,一个topic能够有多个消费者向其pull音讯,一个topic能够存在一个或多个broker中。partition:其是topic的子集,不同分区调配在不同的broker上进行程度扩大从而减少kafka并行处理能力,同topic下的不同分区信息是不同的,同一分区信息是有序的;每一个分区都有一个或者多个正本,其中会选举一个leader,fowller从leader拉取数据更新本人的log(每个分区逻辑上对应一个log文件夹),消费者向leader中pull信息。kafka丢音讯的三个节点生产者push音讯节点先看一下producer的大略写入流程: producer先从kafka集群找到该partition的leaderproducer将音讯发送给leader,leader将该音讯写入本地follwers从leader pull音讯,写入本地log后leader发送ackleader 收到所有 ISR 中的 replica 的 ACK 后,减少high watermark,并向 producer 发送 ack 通过这个流程咱们能够看到kafka最终会返回一个ack来确认推送音讯后果,这里kafka提供了三种模式: NoResponse RequiredAcks = 0WaitForLocal RequiredAcks = 1WaitForAll RequiredAcks = -1NoResponse RequiredAcks = 0:这个代表的就是数据推出的胜利与否都与我无关了WaitForLocal RequiredAcks = 1:当local(leader)确认接管胜利后,就能够返回了WaitForAll RequiredAcks = -1:当所有的leader和follower都接管胜利时,才会返回所以依据这三种模式咱们就能推断出生产者在push音讯时有肯定几率失落的,剖析如下: 如果咱们抉择了模式1,这种模式失落数据的几率很大,无奈重试如果咱们抉择了模式2,这种模式下只有leader不挂,就能够保证数据不失落,然而如果leader挂了,follower还没有同步数据,那么就会有肯定几率造成数据失落如果抉择了模式3,这种状况不会造成数据失落,然而有可能会造成数据反复,如果leader与follower同步数据是网络呈现问题,就有可能造成数据反复的问题。所以在生产环境中咱们能够抉择模式2或者模式3来保障音讯的可靠性,具体须要依据业务场景来进行抉择,在乎吞吐量就抉择模式2,不在乎吞吐量,就抉择模式3,要想齐全保证数据不失落就抉择模式3是最牢靠的。 kafka集群本身故障造成kafka集群接管到数据后会将数据进行长久化存储,最终数据会被写入到磁盘中,在写入磁盘这一步也是有可能会造成数据损失的,因为写入磁盘的时候操作系统会先将数据写入缓存,操作系统将缓存中数据写入磁盘的工夫是不确定的,所以在这种状况下,如果kafka机器忽然宕机了,也会造成数据损失,不过这种概率产生很小,个别公司外部kafka机器都会做备份,这种状况很极其,能够忽略不计。 消费者pull音讯节点push音讯时会把数据追加到Partition并且调配一个偏移量,这个偏移量代表以后消费者生产到的地位,通过这个Partition也能够保障音讯的程序性,消费者在pull到某个音讯后,能够设置主动提交或者手动提交commit,提交commit胜利,offset就会产生偏移: 所以主动提交会带来数据失落的问题,手动提交会带来数据反复的问题,剖析如下: 在设置主动提交的时候,当咱们拉取到一个音讯后,此时offset曾经提交了,然而咱们在解决生产逻辑的时候失败了,这就会导致数据失落了在设置手动提交时,如果咱们是在解决完音讯后提交commit,那么在commit这一步产生了失败,就会导致反复生产的问题。比起数据失落,反复生产是合乎业务预期的,咱们能够通过一些幂等性设计来躲避这个问题。 实战残缺代码曾经上传github:https://github.com/asong2020/... 解决push音讯失落问题次要是通过两点来解决: 通过设置RequiredAcks模式来解决,选用WaitForAll能够保证数据推送胜利,不过会影响时延时引入重试机制,设置重试次数和重试距离因而咱们写出如下代码(摘出创立client局部): func NewAsyncProducer() sarama.AsyncProducer { cfg := sarama.NewConfig() version, err := sarama.ParseKafkaVersion(VERSION) if err != nil{ log.Fatal("NewAsyncProducer Parse kafka version failed", err.Error()) return nil } cfg.Version = version cfg.Producer.RequiredAcks = sarama.WaitForAll // 三种模式任君抉择 cfg.Producer.Partitioner = sarama.NewHashPartitioner cfg.Producer.Return.Successes = true cfg.Producer.Return.Errors = true cfg.Producer.Retry.Max = 3 // 设置重试3次 cfg.Producer.Retry.Backoff = 100 * time.Millisecond cli, err := sarama.NewAsyncProducer([]string{ADDR}, cfg) if err != nil{ log.Fatal("NewAsyncProducer failed", err.Error()) return nil } return cli}解决pull音讯失落问题这个解决办法就比拟粗犷了,间接应用主动提交的模式,在每次真正生产完之后在本人手动提交offset,然而会产生反复生产的问题,不过很好解决,应用幂等性操作即可解决。 ...

September 13, 2021 · 2 min · jiezi

关于golang:服务注册与发现的原理和实现

什么是服务注册发现?对于搞微服务的同学来说,服务注册、服务发现的概念应该不会太生疏。 简略来说,当服务A须要依赖服务B时,咱们就须要通知服务A,哪里能够调用到服务B,这就是服务注册发现要解决的问题。 Service B 把本人注册到 Service Registry 叫做 服务注册Service A 从 Service Registry 发现 Service B 的节点信息叫做 服务发现服务注册服务注册是针对服务端的,服务启动后须要注册,分为几个局部: 启动注册定时续期退出撤销启动注册当一个服务节点起来之后,须要把本人注册到 Service Registry 上,便于其它节点来发现自己。注册须要在服务启动实现并能够承受申请时才会去注册本人,并且会设置有效期,避免过程异样退出后仍然被拜访。 定时续期定时续期相当于 keep alive,定期通知 Service Registry 本人还在,可能持续服务。 退出撤销当过程退出时,咱们应该被动去撤销注册信息,便于调用方及时将申请散发到别的节点。同时,go-zero 通过自适应的负载平衡来保障即便节点退出没有被动登记,也能及时摘除该节点。 服务发现服务发现是针对调用端的,个别分为两类问题: 存量获取增量侦听还有一个常见的工程问题是 应答服务发现故障当服务发现服务(比方 etcd, consul, nacos等)呈现问题的时候,咱们不要去批改曾经获取到的 endpoints 列表,从而能够更好的确保 etcd 等宕机后所依赖的服务仍然能够失常交互。 存量获取 当 Service A 启动时,须要从 Service Registry 获取 Service B 的已有节点列表:Service B1, Service B2, Service B3,而后依据本人的负载平衡算法来抉择适合的节点发送申请。 增量侦听上图曾经有了 Service B1, Service B2, Service B3,如果此时又启动了 Service B4,那么咱们就须要告诉 Service A 有个新增的节点。如图: ...

September 12, 2021 · 2 min · jiezi

关于golang:用-Go-map-要注意这个细节避免依赖他

大家好,我是煎鱼。 最近又有同学问我这个日经话题,想转他文章时,后果发现我的公众号居然没有发过,因而明天我再唠叨两句,好让大家避开这个 “坑”。 有的小伙伴没注意过 Go map 输入、遍历程序,认为它是稳固的有序的,会在业务程序中间接依赖这个后果集程序,后果栽了个大跟头,吃了线上 BUG。 有的小伙伴晓得是无序的,但却不晓得为什么,有的却了解谬误? 明天通过本文,咱们将揭开 for range map 输入的 “神秘” 面纱,看看它外部实现到底是怎么样的,程序到底是怎么样? 开始吸鱼之路。 前言例子如下: func main() { m := make(map[int32]string) m[0] = "EDDYCJY1" m[1] = "EDDYCJY2" m[2] = "EDDYCJY3" m[3] = "EDDYCJY4" m[4] = "EDDYCJY5" for k, v := range m { log.Printf("k: %v, v: %v", k, v) }}假如运行这段代码,输入的后果是怎么样?是有序,还是无序输入呢? k: 3, v: EDDYCJY4k: 4, v: EDDYCJY5k: 0, v: EDDYCJY1k: 1, v: EDDYCJY2k: 2, v: EDDYCJY3从输入后果上来讲,是非固定程序输入的,也就是每次都不一样。但这是为什么呢? ...

September 12, 2021 · 3 min · jiezi