你所不知道的golang高级用法

2023-03-17
14分钟阅读时长

谈必谈及epoll,golang一直因为它的协程功能给程度员更轻松的界入到操作系统层面,从而更人性化的提高程序性能而被津津乐道。很多文章都在谈及GMP这种组织方式对于golang性能释放的重大意义。但是很多人可能仔细读过文章也意识到了,golang中Goroutine确实是可以大量的生成全局协程或者是线程内的局部协程,但是真正的执行还是要靠P(调度器)把任务分配到Goroutine上,在Machine上执行,才算是任务的执行完毕。所以,我们不得不看看golang在系统调度层面上到底做了哪些优化?这些优化真的比java中的netty或者是epoll的IO多路复用,效率更高吗?

在讲这个概念之前,我有必要对于操作系统的调度流程做一个澄清,大家都知道,软件是塔载在硬件系统上运行的,所以软件和硬件的优先级你真的清楚吗?

可能有人会说,没有软件硬件跟石头又有什么区别呢?也有人会说,如果没有硬件,你软件都没有部署环境,就更别说执行了,仿佛这就进入了一个是先有鸡还是先有蛋的问题。我举一个简单例子来帮大家做一下理解,假如你现在电脑处于开机的状态,这个时候,你突然想关机,但是还有几个软件处于运行的状态,那么,当你按下关机按钮以后,计算机接下来会做哪些事?

首先关机是一个中断信号,而且是全局性的,不针对于进程上运行的某个应用,不针对于进程上运行的某一个线程、不针对于线程上运行的某一个协程,你关闭电脑这一动作一旦触发,计算机就要把所有这些无论进程、线程、协程通通都关闭。

有个例子举的特别恰当,就是把epoll方式和select方式做了对比,针对于epoll方式就仿佛你去女生宿舍找你女朋友,你直接告诉宿管大妈你女朋友叫什么在哪号房间,大妈直接上楼敲门就通知到位,而select模式就类似于你告诉了宿管大妈你要找谁,她就开始带你从101开始按屋找你这个女朋友,效率的高下立判。

select方式执行1000次的操作,如果总数据量是3000,那么,它要把数据从内核态搬到用户态,再扫描用户态里匹配的,还需要把没用到的数据再复制回内核态吗?而epoll直接就cr_create之类的,直接就是内核态了,直接从队列中取出,放入执行队列,总之,没有内核态和用用户态之间的彼此复制,这就白白浪费了性能。而且

java执行要有虚拟机,golang是不需要有虚拟机的,直接跨平台。

字节针对于epoll封装了netpoll在自己公司内部使用,然后现在开源了?

io多路复用,为啥系统阻塞了,却对于cpu的调用都一点没有影响,作者讲epoll到底是对于io密集型还是cpu密集型的工作,哪种更能提高其效率呢?

rbtree,红黑树和双向链表。 原来的实现方式是btree加单向链表吗?

单调栈是一种利于解决什么类型的数据结构? 好像那种小内存对于大数据量进行排序的问题,就是利用了单调栈,还有往里面放,定义栈是从上到下,从小到大来排序,如果进来一个数据,比现在栈顶的元素大,这个时候就要结算,谁让它出栈就放到右边,它原来挨着谁就放到左边,一次结算结束。好像那种最大轮廓值,能不能往外扩,就应用了这种思路。

io多路复用,执行完了要销毁,但是创建出许多个进程还是线程,等待系统的调度,而不需要把执行的线程都销毁掉,再重新创建去完成任务。GIL就是python中被吐槽最多的点,原因是没想到计算机硬件的计算能力提供的这么快,当时引入这个只想通过协调锁的方式,能让进程内的线程最大程度的被利用,所以就会出现锁等待,争抢锁的情况,为了保证线程的安全性,于是就引入了GIL的机制。java中有拓展能够完全的实现真正的并发,但是python官方支持的库,以及拓展还在沿用GIL,就导致了系统的性能始终提升不起来。

线程是执行单位,而线程池是对线程生命周期,调度的一个管理。golang中如果能不用协程池还是不要用协程池,但是可以限制协程创建的数量。

bytes2string,string2bytes它们之间的区别是啥呢?

make只是声明,而new是开辟内存空间的。slice要指定长度和cap,即容量。而array数组是不需要指定的,可以声明成带缓存的和不带缓存的channel,不带缓存的channel必须要接收方,这样就能形成阻塞队列,而带缓存的channel就不会形成阻塞的队列了。

ctl_create:避免了用户态和内核态之间数据的复制,提高了系统的运行性能,将线程放到红黑树和链表中,标记成准备完毕的状态,当创建的时候,直接从红黑树中取出来执行,是这个意思吗? ctl_wait ctl_close

public class ListNode(<K extends Comparable<K,V»){ public int key; public int value; public ArrayList nextNode; public class(Key k,Value v){ this.key = k; this.value = value; this.nextNode = 0 } }

socket epoll_create,epoll_wait,tl_

红黑树id对象和list列表,如果红黑树id已经存在就直接返回,然后把处于可读或可写,或者可读可写状态的socket放在预处理列表中。socket就是套接字,有两种衔接方式,如果缓冲区比较小的话,一次没处理完,那么它就会通知你,下次你从socket处理完的位置继续让你去处理,另外一种方式是只通知你一次,不会有下次通知,直到下个socket套接字连接建立的时候才会给你另外一次通知,前面的如果会一直给你发通知,假如你不回应的话。

sync.Pool(),线程池协程池用是有好处的,能控制协程的数量,不让它无限的增长,平时使用协程就可以,只有在一定情况下才使用sync.Pool,httplib其实也用sync.Pool进行了包装,但是却不能在不同的goroutine里传输,如果你需要能在goroutine间传输的话,那么,你要把方法copy处理,自己再包装一下。

log-要有相应的行号。

对于硬件的理解要深刻,有南桥芯片,也有北桥芯片,思考的出发点就是网卡接入网络,接收到的数据,如何能有效的传输,如果采用自旋的方式,监控缓存区的数据是否发生变化,就把数据从内核态复制到用户态,这种性能损耗就特别明显,而且自旋空转也造成了资源的浪费。而epoll模式的优点就在于epoll_await()等待着list集合中已经整备好,可以做处理状态的进程。

所有的连接都是tcp中http状态,会有建立连接之后的请求于响应,就是发送ack,然后针对于收到的请求,再响应回ack。kafka是一个消息队列,也是一个stream buffer流数据的传输系统。可能出现瓶颈的地方,高效就源于它是可以分布式部署的,要留出20%~30%的内存空间,所以按一小时算,如果要处理IT的数据,那么,每秒要处理322xM的数据,而每秒是最多能处理700M的数据,而且要有性能预留,所以需要30台服务器。部署的方式可以采用master true,data false,总体原则就是在调度节点上不存储数据,或者master节点,即不logstack服务,也不放服务,只做一个负载均衡的master服务器。高度依赖于zookeeper,clientbeat的效果好,但是部署比较复杂,zookeeper部署简单效果也不错。

做成集群的话,如果是master-slave的模式,就涉及到从备节点间数据的复制,而mysql中数据的同步都是生成了bin-log,然后fork出一个进程,这个进程把数据向其它节点同步。ReteenLock,CucurrentMap,线程间是非线程安全的,ArrayMap也是非线程安全的,所以要加锁,加什么锁呢?加锁的方式无非就两种一种是乐观锁,一种是悲观锁,前者就是利用数据库中字段来标记事务的时间与当前时间的比较,来确定是否需要对数据进行更新,也就是所谓的mvvc,要保证幂等性,另外一种就是防御型编程,悲观锁,对于读和写的控制方式是分开的,有些锁加了,那么读也受限,只有拿到了锁才能继续操作,有些是对于写进行加锁的,只有拿到了锁,才能进行自己的写操作。kafka相比于rabbitmq,activeMq,rocketMQ,它跟rocketMq是最类似的,而rabbitmq是属于官方维护的,其它都是ms或者ms以内的级别,只有rabbitmq是微秒的级别,activeMq会出现一定几率的丢数据,而rocketmq,kafka,rabbitmq经过合理的配置几乎不会出现丢数据的情况。配置的个数也有讲究,rabbitmq的topic可以有几百上千个,但是kafka的话稳定的级别就是几十上百个,超过了就会有问题,后来看文章说最好kafka中就是一个topic对应一个group。kafka及rabbitmq可以使用offset达到重复消费的目的,但是让系统的复杂性变高,redis是不允许重复消费的,这对于很多消息队列场景,成为redis无法替代rabbitm或者rocketmq或者kafka的原因。

kafka的使用通常前面要有clientbeat来收集日志,然后通过zookeeper把数据往kafka中写,看那个elasticSearch好像也是前面有收集数据的部分,再把数据往elasticSearch中写,调优的方式是合理利用map,不要把超过50个以上字段都在语句中做查询,也不要把条数写一个很大的值,elasticSearch就要按随便写的数字先开辟出一段内存空间出来,大大降低了效率,map中把要查询的多个字段的数据先做好映射,然后查这个映射之后的字段的值可以加快响应的效率。

elasticSearch是用spark语言写的,lucence是一个数据引擎,它太重了,于是elasticSearch封装出来,提供一些接口,你只要熟悉了这些api,就可以使用这个高效的数据引擎,而不用关注底层lucence引擎相关的东西,大大提高了使用的便捷性。

sum,aggregation,求和,复合表达式。field_nomatch,查询条件的非condition,可以把它和condition条件一起在查询中使用。

kafka的报错,组织的形式,因为它是日志引擎,所以它通常也是与kibana,promethus,一起来配置,让功能支持可视化,同时又支持更好的日志监控的功能,所以,哪个东西是monitor,promethus是就是minotor是吧?那么kafka是个日志流管理的工具,它前端通过clientbeat来来收集日志,它是把消息同步给什么系统的呢?还是它本身就是对外提供服务的。

我现在的理解是,如果想配置prometheus的话,需要一是启动prometheus服务,另外就是让它可以和springboot项目结合起来,结合的意思就是prometheus是个无情的读取spring相关的日志之类的机器,但是你得让spring项目能识别它,所以要做的事情就是在management中声明prometheus的服务,让springboot系统运行的时候,可以源源的使prometheus读到它的日志。这步配好了,只是第一步,同时对于prometheus启动可以采用几种不同的方式,可以k8s配置来启动,可以直接源码编译来启动,也可以用docker方式来部署启动。分别说下每种方式配置的特点:

针对于docker启动: 参考于常规的docker run命令,指定别名,指定使用的snapshot,也就是相应的版本,指定端口这里要注意了,一个是prometheus服务的运行端口,通常指定成8080,另外一个就是与springboot项目交互的端口,也就是通过哪个端口来读取spring的日志,通常就是8090。而我在配置zookeeper的时候,使用的是2181端口,然后kafka,启动的时候,指定./kafka-bootstrap-start.sh ../config/kafka.properties中监听的就是zookeeper的2181端口,然后kafka服务运行的端口实际是9092端口。但是运行prometheus服务压根是不需要kafka掺合进来的,如果从这个方向来思考的话,那么,就只考虑prometheus和grafana引入到springboot项目中,其中前者就是springboot运行日志的读取,后者是给这些日志的展示提供图表展示的,所以说后两者之间没有很强的依赖性。但是如果grafana没有prometheus提供的日志的话,它就没有可展示的日志了。通过这一痛分析,针对于prometheus的web服务的端口,以及与springboot项目交互的端口要定义成不同的端口号,不然会造成数据混淆?

另外nacos是啥东西,看日志相关的追踪,记得当时都是配置了nacos服务?本地测试通过改服务的名称,可以把服务连到本地电脑上,那个时候就要在dubbo服务管理的地方,把应用名称改成你本地定义的项目名称,这样它就能连到你本地的电脑上,这是因为你本地电脑定义的服务名称实际在内网里,并且是一个启用的状态,所以你改成这个名称,它就能连到你本地电脑上?

sj4log,是不是日志的链路服务都是用这个日志服务进行的封装?

那些配置kafka_host的成对标签的东西,作用是啥呢?

要让prometheus和spring项目的入口是一致的吗?这肯定是不可能的,但是就算我在pom.xml文件中声明了这些依赖包,并成功以依赖dependency的形式加载到maven扩展中,但是怎么让springboot项目知道它,并且对它提供支持呢?

好像很多东西的配置都是以spring开头的,所以我依然是可以用串接.号的方式把配置项一个个声明出来,不然如果声明到外部文件中,如果没有声明使用的地方,系统也加载不到?

为什么要使用kafka?为什么kafka的性能如此之高?

带宽可能是kafka之类的上限,也就是说如果你优化了对于带宽的合理使用,将更高效的传输数据,本身有哪些算法可以对数据进行压缩呢?Gzip压缩算法,snappy压缩算法

针对于kubernetes进行部署,最关键的几点就是Deployment和Service,其中Deployment是基于镜像启动服务,并且把应用对外开放的端口用nodePort,port等关键字进行指定。之后Service针对于启动的服务,暴露服务,可以让外部可以访问,在k8s的原宇宙内,也是可以通过nodePort端口直接暴露服务的,但是这种方式服务的性能会比较低,所以一般不建议用这种方式。dns如果在容器的内部进行了绑定,即ip地址,在容易内可能就是一个根据docker或者k8s服务生成规则生成的host地址,可能包括cluster的名称,应用的名称-localhost组成的一个类似于ip地址的东西,外部通过ip地址或者域名请求以后,就会打到这台服务器。k8s的主要配置参数有meta,用matchLabels来方便匹配业务的标签,这标签包括了容易的标签,即pod的标签,也包括也部署的标签Deployment标签,也包括ReplicateSet的标签,副本集合的标签。构建的参数中要用app:xx的方式来指定应用的名称。spec中指定具体的配置,同时可以对于启的容易分配的cpu的数量,分配的内存的数值进行指定。昨天那个prometheus的配置中,在management中指定了metrics,就是一种数据结构的存在形式,entrypoint中是否启用prometheus,用prometheus:true进行开启,然后web:include:"*“的方式,用它是来指定路径匹配还是啥?还可以指定path:/admin,然后指定用户名密码,这样是不是这个路径的访问就要输入用户名和密码才可以访问到了。metrics/prometheus就可以访问到路径了,但是问题是,我这样配置了,好像并不有起到作用,我在application.yml文件中做了这个配置,然后在根目录的pom.xml文件中导了acutator的包和microservice-io-prometheus的包。这两步操作就是让这个架包能够工作吧。还是说我一定要外部起一个prometheus的服务,才能操作这个步骤,很显然应该不是这样的。怎么能让springboot项目识别到这套写日志的东西,并把日志通过prometheus抓到它的数据存储的地方是关键吧。如果只是导了架包,没配置数据库啥的,你怎么知道数据查询走哪里呢?还有指定server:port 80和8081,应该是springboot运行的端口吧,而不是prometheus运行的端口吧,然后指定了prometheus的运行端口8090,但是服务没有运行起来,不知道这是个啥原因。

aspeak 3.2.0,这个版本是用命令行可以运行的,但是升级到4.0之后,它就改用了rust编写了,并且如果你不注册microsoft asure的话,会不稳定,免费的是50万的免费额度,额度之外就可能要花钱了。我是用pip安装的。

本主题指南

技术实践与开发文档
Avatar

Aisen

Be water,my friend.
扫码关注公众号,可领取以下赠品:
《夯实基础的go语言体系建设》645页涵盖golang各大厂全部面试题,针对云原生领域更是面面俱到;
扫码加微信,可领取以下赠品:
【完整版】本人所著,原价1299元的《爱情困惑者必学的七堂课》;
100个搞定正妹完整聊天记录列表详情点这里
【完整版】时长7小时,原价699元《中国各阶层男性脱单上娶指南》;
  • 为了给wordpress的ripro主题资源站填充数据,我用golang开发了并发版爬虫和分布式爬虫两个版本,已经顺利跑通!

    为了给wordpress的ripro主题资源站填充数据,我用golang开发了并发版爬虫和分布式爬虫两个版本,已经顺利跑通!代码总量还是比较多了,具体课程中再详细讲解,这篇文章主要分享一下自己的技术选型思路,以及我觉得做这个项目过程中自己的一些感悟!包括对于系统架构的理解以及对于golang语言本身的理解。

  • 在aws上以lambda方式部署golang应用 -(上)用.zip压缩文件部署Go Lambda functions

    在使用 AWS Lambda 运行 Go 时,可以将代码打包成 .zip 文件并上传到 Lambda 上。这样做的好处是可以将所有的依赖项打包在一个文件中,而不需要在 Lambda 中安装它们。同时,也可以避免在多个文件之间进行部署和管理的复杂性。 要将 Go Lambda 函数打包为 .zip 文件,您需要将函数代码和依赖关系放置在一个文件夹中,并将其压缩为 .zip 格式。如果您的 Go 程序包含外部依赖项,则需要在函数代码中包括它们,或者将它们一起打包到 .zip 文件中。 一旦您已经打包了 Lambda 函数,就可以通过 AWS 控制台或 AWS CLI 将其上传到 Lambda。上传完成后,您可以测试该函数并将其发布到您的应用程序中。通过使用 .zip 文件打包 Lambda 函数,可以有效地简化部署和管理过程,使您的开发工作更加高效。

  • 使用golang部署运行tls的https服务时,不用停机,高效证书下放,如何实现?

    在网络通信中,HTTP和HTTPS是常用的应用层协议,而四层服务传输协议和七层服务传输协议则是不同的协议模型。当使用 golang 语言部署和运行带有 TLS 的 HTTPS 服务时,需要采取一些技巧来实现高效证书下放,而不会影响线上服务的正常运行。 HTTP和HTTPS是两种不同的应用层协议,HTTP协议是未加密的、明文传输的,而HTTPS是基于TLS/SSL协议的安全版本,通过加密与认证保护数据传输的安全性。在实际应用中,为了保护用户信息的安全,通常使用HTTPS协议进行网络通信。 四层服务传输协议和七层服务传输协议是不同的协议模型,分别定义了网络通信的逻辑结构和规则。四层服务传输协议(如TCP和UDP)负责在不同主机之间建立端到端的通信连接,并提供可靠的数据传输、流量控制等功能;而七层服务传输协议(如HTTP、SMTP、FTP等)定义了应用程序之间的通信规则、格式和内容,提供特定的服务功能。 Golang是一门开源的编程语言,具有高效性能和良好的并发处理能力。在使用Golang部署和运行TLS的HTTPS服务时,可以采用一些技巧来实现高效证书下放,而不会影响线上服务的正常运行。比如通过使用ACME协议自动化管理证书、使用GRPC框架进行交互式的证书下载等方式,可以有效解决证书下放的问题,并提高系统性能和可靠性。 综上所述,HTTP、HTTPS、四层服务传输协议、七层服务传输协议、Golang、Go语言学习以及Go语言技巧都是网络通信和编程中非常重要的概念和关键词。在进行相关学习和实践时,需要注重理解和掌握这些内容,才能更好地应对实际需求和挑战。

  • 夯实基础的go语言简明学习手册