你所不知道的golang高级用法

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

谈必谈及epollgolang一直因为它的协程功能给程度员更轻松的界入到操作系统层面,从而更人性化的提高程序性能而被津津乐道。很多文章都在谈及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元《中国各阶层男性脱单上娶指南》;