乐趣区

关于程序员:golang-后端技术开发必备总结

Golang

首先谈谈后端开发,罕用的语言是 Java、Golang、Python。Java 语言类型的程序员是目前市面上最多的,也是很多公司都会抉择的。Java 语言开发的整个后端我的项目有比拟好的我的项目标准,实用于业务逻辑简单的状况。自己从事的是 Golang 语言,Golang 语言实用于开发微服务,特点是开发快、效率低等。大多数的公司(字节主力语言 Go, B 站主力 Go, 腾讯偏差 Go 等等),都开始抉择用 Golang 开发,因为这门语言相比于 Java、Python 最大的一个特点就是节俭内存, 反对高并发,简洁高效,容易上手学习。

Golang 语言的后端框架有很多,像 Gin(举荐应用)、Beego、Iris 等,关系型数据库的操作有 gorm。这些是 Golang 后端开发把握的根底能力。
微服务框架有: go-zero kratos

Golang 语言有一些特有的个性,比方协程 goroutine,它比线程更加轻量级、高效。比方通道 channel,是一种通过共享内存反对协程之间的通信形式。在多个 goroutine 生产 channel 中的数据时,channel 外部反对锁机制,每一条音讯最终只会调配给一个 goroutine 生产。在 Golang 外部,有一整套 goroutine 调度机制 GMP,其中 G 指的是 Goroutine,M 指的是 Machine,P 指的是 Process。GMP 的原理大抵就是通过全局 Cache 和各个线程 Cache 的形式保留须要运行的 Goroutine,通过 Process 的协调,将 Goroutine 调配在无限的 Machine 上运行。

Golang 是反对 GC 的语言,外部应用三色标记垃圾回收算法,原理大略的说就是通过可达性算法,标记出那些被援用的对象,将剩下来没有被标记也就是须要被开释的对象进行内存回收。在之前比拟老的版本,STW 的影响比拟大,所谓的 STW 就是在 GC 的时候,因为多线程拜访内存会呈现不平安的问题,为了保障内存 GC 的准确性,在标记对象的时候,会通过屏障进行程序代码持续运行上来,直到所有对象都被解决过后,再持续运行程序,这个短暂的工夫,就被成为 STW(Stop The World)。因为 STW,会导致在程序无奈提供服务的问题。在 Java 中,也存在这种景象。然而目前随着 Golang 版本的不断更新,GC 算法也在一直优化,STW 的工夫也缓缓越来越短。

大家须要留神的一点,在定义 map 的时候,尽量不要在 value 中寄存指针,因为这样会导致 GC 的工夫过长。

另外一个知识点,就是 Golang 的 map 是一个无序 map,如果须要从 map 中遍历数据,须要用 slice 进行保留,依照肯定的程序进行排序,这样能力保障每次查问进去的数据程序统一。并且 map 是无锁并发不平安的。在应用 map 进行内存缓存的时候,须要思考到多线程拜访缓存带来的平安问题。常见的两种方法,一种是加读写锁 RWLock,另一种是应用 sync.Map。在写多读少的场景,举荐应用 RWLock,因为 sync.Map 外部应用空间换工夫的办法,外部有两个 map,一个反对读操作一个反对写操作,当写操作过于频繁,会导致 map 不断更新,带来的是频繁 GC 操作,会带来比拟大的性能开销。

Golang 外面开进去的 Goroutine 是无状态的,如果须要主函数期待 Goroutine 执行实现或者终止 Goroutine 运行,通常有三种办法。第一种是应用 sync 中的 waiteGroup,蕴含 Add、Done、Wait 办法。能够类比于 Java 中的 CountDownLatch。第二种是应用 Context 包中 Done 办法,将主函数的 context 带入到 Goroutine 中,同时在主函数中应用 select 监听 Goroutine 接管的 context 收回的 Done 信号。第三种是自定义一个 channel,传入 Goroutine 中,主函数期待读取 Goroutine 中执行实现向 channel 发送的终止信息。

Golang 没有继承的概念,只有组合的概念,每一个 struct 的定义,能够当做一个类,struct 与 struct 之间能够组合嵌套。在软件设计准则中,类的组合比类的继承更能达到解耦的成果。Golang 没有显著的接口实现逻辑,当一个 struct 实现了一个 interface 申明的所有办法,这个 struct 就默认实现了这个 interface。在函数调用的入参中,咱们通常在调用方传入具体实现了这个 interface 的 struct,而在函数体的接管参数定义这个 interface 来接管,以此达到被调用函数的复用成果。这也是面向对象个性中多态思维的体现。

在 Golang 中,error 的解决是最蛋疼的。基本上十个函数调用有九个会返回 error,对于每一个 error 都须要进行解决或者向上抛。通常在业务逻辑中,咱们都会自定义 error,申明 error 的类型。在 Golang 官网 errors 包中,error 只是一个 struct,它提供了 New、Wrap、Error 等办法,提供了创立 error、向上抛出 error、输入 error 信息的性能。所以须要留神的是,咱们不能用 string 的等值比拟 error 是否雷同,因为 error 是一个 struct,是一个实例对象,只管两个 error 的值信息一样,然而对象在内存中只是一个寄存地址值,两者并不相同。通常咱们在函数的第一行,应用 defer 的性能,对函数体中所有的 error 进行对立的解决。其中 defer 是提早解决标记,函数会在 return 前拦挡解决 defer 匿名函数内的代码。(能够应用 pkg/errors 包解决)

Golang 的我的项目构造在 github 有一个比拟闻名的 example,大家能够参考或者模拟。大家须要留神的是,当内部我的项目须要调用该项目标代码时,只能调用 internel 包以外的函数或者对象办法。对于 internel 包内的代码,对外部调用我的项目来说,是不可用的。这也是一种代码爱护机制。


MySQL

后端我的项目,离不开的就是数据的增删改查。通常大家接触到最多的就是 MySQL 了, 举荐去看下 MySQL 45 讲

MySQL 罕用的版本有 5.7 和 8.0,通常为了向前兼容,大部分公司应用的 MySQL 版本都是 5.7。在这个版本中,MySQL 默认反对 InnoDB 存储引擎,这个引擎的特点就是反对事务,也就是咱们常说的 ACID。

一般来说,如果须要对多张表进行增、改、删等操作的时候,为了避免多阶段操作的成功失败不统一问题,须要用到事务个性。如果操作不齐全失败,就进行事务回滚,将所有操作都勾销。

事务有四种隔离级别,别离是读未提交、读已提交、可反复读和序列化。MySQL 中 InnoDB 默认反对的事务隔离级别是可反复读。

大家须要留神的是,对于事务的每一种隔离级别,存储引擎外部都会提供对应的锁机制实现。大家在对数据进行操作的平时,须要留神呈现死锁的状况。在数据读取和操作中,反对读写锁,读锁也就是共享锁,多把读锁能够同时领有。写锁也叫排它锁,同一时刻只容许一把写锁对数据进行操作。不同的存储引擎,有不同的锁级别,有表锁、行锁、间隙锁。大家留神在执行 delete 或者 update 操作的时候,最好带上 where 条件,避免全表删除或者更新的状况,或者因为触发表锁导致死锁的状况。

数据的查问通过索引查找的形式和全表扫描的形式效率差距很大,实质的起因是在 InnoDB 引擎外部,会对增加了索引的表字段建设 B + 树以进步查问效率。在查问语句的编写过程中,尽量表明须要查问的字段,这样在查问的字段如果曾经创立了联结索引的状况下 InnoDB 查找不须要进行回表。B+ 树的叶子节点通常存储的是表的主键,通过查问条件在索引 B + 树中查问到对应主键,再到以主键为查问条件建设的 B + 树中查找整行数据的形式咱们称为回表,回表会进行两次 B + 树查问。

联结索引的反对查问形式是最左匹配准则,如果查问语句中的 where 条件没有依照联结索引的最左匹配准则进行查问,InnoDB 将会全表扫描。索引优化应用 Explain 语句。

在表设计上,一张表的字段不应设计过多,个别不超过 20 个字段。每个字段的字段类型应该依照理论状况尽量缩减,比方 uuid 默认是 32 位,那么定义 varchar(32)即可,定义 varchar(255)会造成空间节约。

在分页查问中,limit 反对的 page 和 pageSize 两个字段,当 page 越大,查问的效率越低。因而尽量设计一个主动递增的整型字段,在 page 过大的时候,通过增加过滤主动递增的整型字段的 where 条件进步查问效率。

MySQL 默认是单机存储,对于读多写少的业务场景,能够主从部署,反对读写拆散,加重写服务器的压力。

MySQL 最多只能反对几 k 的并发,对于大量的并发查问数据的场景,倡议在上游增加缓存服务比方 Redis、Memcached 等。

MySQL 在操作数据的时候会提供 binlog 日志,通常会应用 cancal 等组件服务将数据进行导出到音讯队列,进行剖析、特定搜寻、用户举荐等其余场景。如果 MySQL 服务器数据失落,也能够应用 binlog 日志进行数据恢复,然而因为数据操作会在一段时间内存在零碎内存中,定期 flush 到硬盘,所以通过 binlog 日志也不肯定能完全恢复出所有数据。


Redis

当用户量剧增,拜访频繁的时候,在 MySQL 上游增加一个缓存服务,同步一部分热点数据,能够加重数据库的拜访压力。常见的缓存服务有 Redis。

redis 是由 c 语言编写的内存型分布式缓存组件。特点是反对大量读写场景,查问数据高效。

尽管 redis 是分布式缓存,然而为了避免服务宕机,通常会应用长久化机制将数据保留到硬盘中。redis 反对的长久化机制包含 AOF 和 RDB 两种。AOF 通过记录每一次写、改、删操作的日志,在服务宕机后,通过操作日志进行命令从新执行的形式复原数据。RDB 通过记录数据快照的形式,在服务宕机后,通过数据快照复原该时间段以前的所有数据。通常来说,两者都有各自的毛病,AOF 的毛病是数据恢复慢,RDB 的毛病是数据快照是定时执行的,那么在宕机时刻与上一次数据快照记录时刻的两头这一段时间的数据操作,将会失落。所以咱们会两者兼用同步执行。倡议 RDB 的工夫距离不要设置的太短,因为 RDB 快照的时候执行外部的 bgsave 命令会导致 redis 在短暂的工夫内无奈提供服务。

尽管 redis 能无效的加重数据库的拜访压力,然而 redis 也不是银弹。如果数据最终还是以数据库中为准,那么在对数据进行读写操作的时候,须要思考缓存与数据库不统一的问题。

redis 与 mysql 数据一致性的解决方案
读取操作: 如果 redis 某个数据过期了,间接从 Mysql 中查问数据.
写操作: 先更新 Mysql, 而后再更新 Redis 即可;如果更新 redis 失败, 能够思考重试,

对于上述操作, 如果还存在不统一的状况, 思考加一个兜底计划, 监听 mysql binlog 日志, 而后 binlog 日志发送到 kafka 队列中生产解决。

引入 redis,除了数据不统一的问题之外,还有可能呈现缓存雪崩、缓存穿透,缓存击穿的状况。在增加缓存的时候,尽量设置不一样的缓存生效工夫,避免同一时间内大量缓存数据生效,数据拜访 db 造成 db 拜访压力过大的问题; 缓存穿透能够思考布隆过滤器, 缓存击穿思考分布式锁解决。

redis 之所以读取效率快,是因为大量数据存在内存中,如果须要大量的缓存数据存储,单机内存容量无限,redis 须要进行集群部署。redis 的集群部署存储形式是将拆分的一万多个 slot 槽位均匀分布在各个 redis 服务器中,redis 的 key 通过一致性哈希,将数据存储在某个 slot 槽位对应的 redis 服务器中。redis 的扩容和缩容操作会引起比拟大数据迁徙,这个时候尽量对外进行服务,否则可能会导致缓存数据生效的问题。

redis 通过哨兵机制发现服务高低线的问题。通常的部署模式是一主二从三哨兵。

redis 的利用场景有很多,比方利用 zsort 实现排行榜,利用 list 实现轻量级音讯队列,利用 hash set 实现微博点赞等等。

在 redis 存储的时候须要留神,key 值尽量不要应用中文,value 值尽量不要过大。在设计 key 的时候,应该依据业务对立 key 的设计规范。

尽管 redis 有 16 个 db 库,然而只是逻辑隔离,缓存数据都是存储在一个中央,不同的 db 库的读写是竞争关系。


Kafka

接下来谈一谈音讯队列, 所以在这里只谈一谈 Kafka。

音讯队列的利用场景不必多讲了,上下游 解耦、流量削峰、异步解决等等大家依据理论场景去应用就好了。

先说一说音讯队列会遇到的一些常见问题吧。比方音讯失落、音讯反复发送、音讯重试机制、音讯程序性、音讯反复生产等

在 Kafka 中音讯呈现失落的状况极低,因为 Kafka 是保障了至多一次的发送机制。只有是在 HW 以内的 offset,Kafka 默认曾经长久化到了硬盘中,所以在生产 HW 以内的 offset 音讯,不会呈现音讯失落的状况。

Kafka 提供了音讯发送的 ACK 机制,这个 ACK 机制有三个值能够抉择。

当 ACK= 0 的时候,即音讯发送到了 leader 即确认发送胜利,此时并不知道其余 replica 是否曾经将音讯长久化了没有,这种状况下极有可能呈现音讯发送了然而失落的状况。因为如果此时 leader 节点宕机,其余 replica 会竞选 leader,当某一个 replica 竞选了 leader 当前,Kafka 外部引入了 leader epoach 机制进行日志截断,此时如果该 replica 并没有同步到 leader 接管到这一条音讯,那么这条音讯就会失落。

当 ACK= 1 的时候,即音讯发送到了该 partition 下的 ISR 汇合内的所有 replica 内。当 ISR 汇合中有多个 replica 存在,即便此时 leader 所在的节点宕机,也不会存在音讯失落的状况。因为 partition 下的 leader 默认是从 ISR 汇合中产生的,而此时 ISR 汇合内的所有 replica 曾经存储了该条音讯,所以失落的可能性简直为零。

当 ACK=- 1 的时候,即音讯发送到了该 partition 下的所有 replica 内。不论 leader 所在的节点是否宕机,也不论该 ISR 下的 replica 是否只有一个,只有该 parition 下的 replica 超过一个,那么该音讯就不会失落。

在日常状况下,咱们默认 ACK=1,因为 ACK= 0 音讯极有可能失落,ACK=- 1 音讯发送确认工夫太长,发送效率太低。

对于音讯反复发送的问题,我倡议从生产端进行去重解决。因为对于 producer 端,如果呈现了音讯发送然而没有接管到 ACK,但实际上曾经发送胜利却判断音讯发送失败,所以反复发送一次的场景,Kafka 也大刀阔斧。不过能够开启事务机制,确保只发送一次,然而一旦开启事务,Kafka 的发送生产能力将大打折扣,所以不倡议开启事务。

在 Kafka 中,producer 端每发送的一条音讯,都会存在对应 topic 下的 partition 中的某个 offset 上。音讯发送必须指定 topic,能够指定某个 partition,也能够不指定。当 partition 不指定时候,某个 topic 下的音讯会通过负载平衡的形式散布在各个 partition 下。因为只有同一个 parititon 下的音讯是有序的,所以在给有多个 partition 的 topic 发送音讯的时候不指定 partition,就会呈现音讯乱序的状况。

Kafka 的通过 topic 对音讯进行逻辑隔离,通过 topic 下的 partition 对音讯进行物理隔离,在 topic 下划分多个 partition 是为了进步 consumer 端的生产能力。一个 partition 只能被一个 consumer 端生产,然而一个 consumer 端能够生产多个 partition。每个 consumer 端都会被调配到一个 consumer group 中,如果该 consumer group 组中只有一个 consumer 端,那么该 consumer group 订阅的 topic 下的所有 partition 都会被这一个 consumer 端生产。如果 consumer group 组的 consumer 端个数小于等于 topic 下的 partition 数目,那么 consumer group 中的 consumer 端会被平均的调配到肯定的 partition 数,有可能是一个 partition,也有可能是多个 partition。相同,如果 consumer group 组的 consumer 端个数大于 topic 下的 partition 数目,那么 consumer group 中将会有 consumer 端分不到 partition,生产不到数据。

在理论利用场景中,通常在 consumer group 中设置与 partition 数目对等的 consumer 端数。确保每个 consumer 端至多生产一个 partition 下的 offset 音讯。

Kafka 集群的每一个服务称作 broker,多个 broker 中会通过 zookeeper 选举出一个 controller 解决外部申请和内部操作。然而数据真正的读写操作都产生在 partition 上,partition 归属于某个 topic 下,为了避免数据失落,partition 个别会设置多个,每一个称作 replica。每个 partition 都会从多个 replica 中选举出一个 partition leader,负责解决数据的写操作和读操作。其余的 replica 负责于 leader 交互,进行数据的同步。同一个 partition 下的多个 replica 会平均的散布在不同的 broker 中。因而在设计上,咱们能够发现,实际上 Kafka 的音讯解决是负载平衡的,基本上每个 broker 都会参加进来。partition 的 leader 默认是从 ISR 汇合中选举产生的。ISR 全名是 In Sync Replica,意思是曾经于 leader 的音讯保持一致的 Replica。如果在肯定工夫内,或者肯定数目的 offset 内,replica 没有与 leader 的 offset 保持一致,那么就不能存在于 ISR 汇合中,就算之前存在 ISR 汇合中,也会被踢出去。期待一段时间后,音讯及时同步了,才有机会退出到 ISR 汇合中。因而,从 ISR 汇合中选举 leader 在肯定水平上是为了保障在 leader 从新选举的时候音讯也能保障同步统一,不会失落。

因为 Kafka 中引入了 consumer group 机制,所以能很大水平上进步 consumer 端的生产能力。然而也因为 consumer group 的 rebalance 机制,会让 consumer 端的生产产生短暂性的不可用。问题是这样的,因为 consumer group 中存在一个叫 coordinate 的均衡器,负责将 partition 平均的调配到 consumer group 的每个 consumer 端中。如果 consumer group 中 consumer 端有增加或者缩小,那么 partition 就须要重新分配,这个时候,该 consumer group 下的所有 consumer 端都会进行生产,期待 coordinate 给他重新分配新的 partition。consumer 端和 partition 越多,这个等待时间就越长。因而,不倡议 topic 下的 partition 设置的过多,个别在 20 个以内。

退出移动版