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个以内。