极客时间Go实战训练营全新升级第5期

极客时间Go实战训练营全新升级第5期插图1https://www.sisuoit.com/3244.html

下哉地址:https://www.sisuoit.com/3244.html

golang开发首先说一下后端开发。常用的语言有Java,Golang,Python。Java程序员是目前市面上最多的,很多公司都会选择。Java语言开发的整个后端项目都有很好的项目规范,适合复杂的业务逻辑。本人从事Golang语言,适合开发微服务,特点是开发快,效率高。大多数公司(Go,字节的主要语言,B站

的Go,腾讯的Go等。)都开始选择Golang进行开发,因为与Java和Python相比,这种语言最大的特点就是节省内存,支持高并发,简洁高效,简单易学。Golang语言的后端框架有很多,比如Gin(推荐)、Beego、Iris等。关系数据库的操作包括gorm。以上是Golang后端开发已经掌握的基本能力。微服务框架包括:归零奎托斯Golang语言有一些独特的功能,比如coroutine goroutine,它比threads更轻量级、更高效。例如,通道是通过共享内存支持进程间通信的一种方式。当多个goroutine消费通道中的数据时,通道中支持锁机制,每条消息只会分配给一个go routine消费。在Golang内部,有一套完整的Goroutine调度机制GMP,其中g指goroutine,m指机器,p指流程。GMP的原理大致是通过全局缓存和每个线程的缓存来保存需要运行的go routine,通过进程的协调将go routine分发到有限的机器上运行。

Golang是一种支持GC的语言,内部使用三色标记垃圾收集算法。原理大致是通过可达性算法对那些被引用的对象进行标记,对剩余的需要释放的未标记对象进行回收。在旧版本中,STW影响很大。所谓的STW是指在GC中,因为多线程访问内存会导致不安全的问题。为了保证内存GC的准确性,在标记对象时,它会阻止程序代码继续运行通过一道屏障,然后继续运行程序,直到所有的对象都被处理完。这个短暂的时间被称为STW(停止世界)。因为STW,会导致程序无法提供服务的问题。在Java中,这种现象也存在。但是随着Golang版本的更新,GC算法不断优化,STW时间越来越短。需要注意的是,在定义map时,尽量不要将指针存储在值中,因为这样会导致GC时间过长。另一个知识点是Golang的地图是一个无序的地图。如果需要从地图上遍历数据,需要用slice保存,并按照一定的顺序排序,这样才能保证每次查询的数据都是同一个顺序。而且地图是无锁的,不安全。使用map进行内存缓存时,需要考虑多线程访问缓存带来的安全问题。常见的方法有两种,一种是添加读写锁RWLock,另一种是使用sync。Map在多写少读的场景下建议使用RWLock,因为sync。Map内部用空间换时间的方法,内部有两张地图,一张支持阅读,一张支持写作。写的太频繁,会导致map的不断更新,带来频繁的GC操作,带来比较大的性能开销。Golang的Goroutine是无国籍的。如果需要main函数等待Goroutine结束或终止Goroutine,通常有三种方法。第一种是在sync中使用WaitGroup,它包括Add、Done和wait方法。可以比作Java中的CountDownLatch。第二种方法是使用Context包中的Done方法将主函数的上下文带入Goroutine,同时使用主函数中的select来监听Goroutine接收到的上下文发出的Done信号。第三种方法是定义一个通道,并将其发送到Goroutine中。在Goroutine中执行后,main函数等待读取发送给通道的终止信息。Golang没有继承的概念,只有组合的概念。每个结构的定义可以看作一个类,结构和结构可以组合嵌套。在软件设计原理中,类的组合比类的继承更能达到解耦的效果。Golang没有明显的接口实现逻辑。当结构实现接口声明的所有方法时,默认情况下,该结构实现接口。在函数调用的参数中,我们通常是从调用方传入实现这个接口的struct,在要接收的函数体的接收参数中定义这个接口,从而达到被调用函数的重用效果。这也是多态思想在面向对象特性中的体现。在Golang,错误的处理是最痛苦的。基本上,十个函数调用中有九个会返回一个错误,每个错误都需要处理或抛出。通常,在业务逻辑中,我们会自定义错误并声明错误的类型。在Golang官方的Errors包中,error只是一个struct,提供New、Wrap、error等方法,提供创建错误、抛出错误、输出错误信息等功能。所以需要注意的是,我们不能用string的等价来比较errors是否相同,因为error是struct,是instance对象。虽然两个错误的值信息是一样的,但是对象只是内存中的一个存储地址值,两者并不相同。通常,在函数的第一行,我们使用defer函数来统一处理函数体中的所有错误。Defer是延迟处理标志,函数在返回前截取并处理defer匿名函数中的代码。(可以用pkg/errors包解决)Golang的项目结构在github中有一个众所周知的例子,可以参考或者模仿。需要注意的是,当外部项目需要调用项目的代码时,只能调用internel包之外的函数或对象方法。对于internel包中的代码,它不可用于外部调用项目。这也是一种代码保护机制。关系型数据库后端项目离不开数据的增删查。通常人们接触最多的是MySQL,所以推荐看MySQL 45。MySQL常用的版本有5.7和8.0。通常,为了向前兼容,大多数公司使用MySQL版本。在这个版本中,MySQL默认支持InnoDB存储引擎,这个引擎的特点就是支持事务,也就是我们常说的ACID。一般来说,如果需要对多个表进行添加、修改、删除等操作,为了防止多阶段操作的成败不一致,需要使用事务特性。如果操作完全失败,事务将回滚,所有操作将被取消。事务隔离有四个级别,即未提交读、提交读、可重复读和序列化。默认情况下,MySQL中InnoDB支持的事务隔离级别是可重复的。应该注意,对于每个隔离级别的事务,存储引擎将提供相应的锁定机制。大家操作数据的时候要注意死锁。在数据读取和操作中,支持读写锁。读锁是共享锁,可以同时拥有多个读锁。写锁也叫排他锁,同一时间只允许一个写锁对数据进行操作。不同的存储引擎有不同的锁级别,包括表锁、行锁和间隙锁。请注意,在执行删除或更新操作时,最好采用where条件来防止整个表被删除或更新,或者防止由于接触表锁而导致的死锁。索引搜索和全表扫描的数据查询效率差距很大。本质原因是在InnoDB engine中,会为索引表字段构建一个B+树,以提高查询效率。在编写查询语句的过程中,尽量注明需要查询的字段,这样如果已经为查询的字段创建了联合索引,InnoDB search就不需要返回表了。+B树的叶节点通常存储表的主键。通过查询条件在索引B+树中找到对应的主键,然后在以主键为查询条件建立的B+树中找到整行数据。我们称之为table return,在B+树中会被查询两次。联合索引支持的查询方法是最左匹配原则。如果查询语句中的where条件没有按照union索引的最左匹配原则进行查询,InnoDB会扫描整个表。索引优化使用Explain语句。在表格设计中,一个表格中不能有太多的字段,一般不超过20个字段。每个字段的字段类型要根据实际情况尽量减少。比如uuid默认为32位,那么定义varchar(32)就够了,定义varchar(255)会浪费空间。在分页查询中,limit支持两个字段,page和pageSize。页面越大,查询效率越低。所以,尽量设计一个自动递增的整数字段。当页面过大时,可以通过添加过滤自动递增整数字段的where条件来提高查询效率。默认情况下,MySQL是独立的存储。对于多读少写的业务场景,可以从主部署到,支持读写分离,减轻写服务器压力。MySQL最多只能支持几K并发。对于大量并发查询数据的场景,建议在上游增加Redis、Memcached等缓存服务。MySQL在操作数据时提供binlog日志,通常使用cancal等组件服务将数据导出到消息队列中进行分析、特定搜索、用户推荐等场景。如果MySQL服务器数据丢失,还可以使用binlog日志进行数据恢复。但是,由于数据操作会在系统内存中存储一段时间,并定期刷新到硬盘,所以binlog log并不能完全恢复所有数据。使用心得当用户数量剧增,访问频繁时,在MySQL上游增加一个缓存服务来同步一些热点数据,可以减轻数据库访问的压力。常见的缓存服务是Redis。Redis是用C语言编写的基于内存的分布式缓存组件。其特点是支持大量的读写场景和高效的数据查询。虽然redis是分布式缓存,但为了防止服务宕机,通常采用持久化机制将数据保存到硬盘。redis支持的持久性机制包括AOF和RDB。AOF记录每个写入、更改和删除操作的日志,在服务关闭后,它通过操作日志重新执行命令来恢复数据。RDB记录数据快照,在服务停止后,它通过数据快照恢复该时间段之前的所有数据。总的来说,两者都有各自的缺点。AOF的缺点是数据恢复比较慢,RDB的缺点是定期进行数据快照,所以停机到最后一次数据快照这段时间的数据操作会丢失。因此,我们将同时使用两者。建议RDB间隔不要设置太短,因为在RDB快照期间执行内部bgsave命令会导致redis短时间内无法提供服务。虽然redis可以有效降低数据库访问的压力,但它并不是银弹。如果数据最终是基于数据库的,那么在读写数据时就要考虑缓存和数据库的不一致性。redis与mysql数据一致性的解决方案读:如果redis的一个数据过期了,直接从Mysql查询数据。操作:先更新Mysql,再更新Redis如果更新redis失败,可以考虑再试一次。对于以上操作,如果仍然存在不一致的情况,可以考虑增加一个自底向上的方案来监控mysql binlog日志,然后将binlog日志发送到kafka队列进行消费。redis引入后,除了数据不一致,还可能出现缓存雪崩、缓存穿透、缓存击穿等问题。增加缓存时,尽量设置不同的缓存失效时间,防止大量缓存数据同时失效,数据访问db造成db访问压力过大的问题;缓存穿透可以考虑Bloom filter,缓存击穿可以考虑分布式锁解决方案。redis之所以读取效率快,是因为内存中存在大量的数据。如果需要大量的缓存数据进行存储,那么单机的内存容量是有限的,所以redis需要部署在集群中。redis的集群部署和存储方式是在每个redis服务器上均匀分布10000多个拆分槽,redis的key通过一致hash将数据存储在一个槽对应的redis服务器上。redis的扩展和收缩会引起大量的数据迁移。此时尽量停止对外服务,否则缓存数据可能会失效。Redis通过哨兵机制发现了服务上下波动的问题。通常的部署模式是一主二从三哨。redis的应用场景有很多,比如用zset实现排名,用list实现轻量级消息队列,用hash集实现微博点赞等等。在存储redis时,需要注意的是,key值不能是中文,value值不能太大。在设计密钥时,应根据业务统一密钥的设计规范。虽然redis有16 db库,但它们只是逻辑上隔离的。缓存的数据都存储在一个地方,不同db库的读写是竞争的。卡夫卡接下来说消息队列,这里就只说卡夫卡吧。消息队列的应用场景就不用说了。只是根据实际场景使用上下游解耦,流量削峰,异步处理等等。下面说一些消息队列会遇到的常见问题。如消息丢失、重复发送消息、消息重试机制、消息顺序、重复消耗消息等。在卡夫卡那里,消息的丢失是极低的,因为卡夫卡是一种机制,保证了至少一次传输。只要是HW内的offset,Kafka默认已经持久化到硬盘,所以如果消耗HW内的offset消息,不会有消息丢失。Kafka为消息发送提供了ACK机制。这种ACK机制有三个值可供选择。当ACK=0时,即消息发送到leader时,确认消息发送成功。此时,不知道其他复制品是否保存了该消息。在这种情况下,很有可能消息发送了,但是丢失了。如果此时首节点停止运行,其他副本将为首节点运行。在某个副本为领导者运行之后,Kafka引入了领导者epoach机制来截断日志。此时,如果副本直到领导者收到此消息后才同步,则消息将会丢失。当ACK=1时,消息被发送到该分区下的ISR集中的所有副本。当ISR集合中有多个副本时,即使首领所在的节点出现故障,也不会有消息丢失。由于分区下的leader默认从ISR集合中产生,并且ISR集合中的所有副本都已经存储了该消息,因此丢失的可能性几乎为零。当ACK=-1时,消息被发送到分区下的所有副本。无论领导所在的节点是否宕机,或者这个ISR下是否只有一个副本,只要这个Paris下有多个副本,消息就不会丢失。日常情况下,我们默认ACK=1,因为ACK=0消息很可能会丢失,而ACK=-1消息发送时间太长,发送效率太低。对于消息重复发送的问题,我建议从消费端解决。对于制作者来说,如果发送了消息但是没有收到ACK,但是消息实际发送成功但是判断消息失败,对于重复发送的场景,卡夫卡无能为力。但是,可以打开事务机制,以确保只发送一次。但一旦开启交易,卡夫卡的发送消费能力会大打折扣,不建议开启交易。在Kafka中,生产者发出的每一条消息都会存在于相应主题下的分区中的一个偏移量上。消息发送必须指定主题,有或没有分区。当未指定分区时,主题下的消息将通过负载平衡分布在每个分区下。因为只有在同一个分区下的消息才是有序的,所以如果在向一个有多个分区的主题发送消息时没有指定分区,那么消息就会乱序。卡夫卡逻辑上按主题隔离消息,物理上按主题下的分区隔离消息,并在主题下划分多个分区,目的是为了提高消费者的消费能力。一个分区只能由一个使用者使用,但是一个使用者可以使用多个分区。每个消费者终端将被分配到一个消费者组。如果该消费群中只有一个消费终端,则该消费群订阅的主题下的所有分区都将被该消费终端消费。如果消费者组中的消费者终端数量小于或等于主题下的分区数量,则消费者组中的消费者终端将被平均分配到一定数量的分区中,可以是一个分区,也可以是多个分区。相反,如果消费群组中的消费终端数量大于topic下的分区数量,那么消费群中就会出现无法分区,无法消费数据的消费者。

极客时间Go实战训练营全新升级第5期插图3

极客时间Go实战训练营全新升级第5期插图5

在实际应用场景中,消费者终端的数量通常等于消费者组中的分区数量。确保每个使用者在分区下至少使用一条偏移消息。Kafka集群的每个服务称为broker,zookeeper会在多个broker中选出一个控制器来处理内部请求和外部操作。但是真正的数据读写操作都发生在分区上,属于一个主题。为了防止数据丢失,通常有多个分区,每个分区称为副本。每个分区从多个副本中选择一个分区领导者,负责数据的读写。其他副本负责领导者交互和数据同步。同一分区下的多个副本将平均分布在不同的代理中。所以在设计上可以发现,其实卡夫卡的消息处理是负载均衡的,基本上每个经纪人都会参与。默认情况下,分区的领导者是从ISR集合中选出的。ISR的全称是同步副本,意思是与领导传达的信息一致的副本。如果在一定时间内,或者在一定数量的偏移量内,复制品与领导者的偏移量不一致,那么它就不能存在于ISR集合中。即使之前存在于ISR集合中,也会被踢出去。等待一段时间后,消息在加入ISR集之前会进行时间同步。所以在一定程度上,领袖是从ISR集合中选取的,以保证领袖改选时消息会同步一致,不会丢失。因为卡夫卡引入了消费群体机制,可以大大提高消费者的消费能力。然而,由于消费群体的再平衡机制,消费者的消费将暂时无法获得。问题是这样的,因为在消费群中有一个叫做coordinate的均衡器,负责将分区平均分配到消费群的各个消费端。如果使用者组中的使用者端增加或减少,那么分区需要重新分配。此时,该消费群下的所有消费端都会停止消费,等待坐标给他重新分配一个新的分区。消费者和分区越多,这个等待时间就越长。所以不建议在topic下设置太多分区设置,一般在20以内。

原创文章 极客时间Go实战训练营全新升级第5期,版权所有
如若转载,请注明出处:https://www.itxiaozhan.cn/202211780.html

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注