性能优化的方法
一、缓存
二、并行化处理
- redis 多线程版本, 把读取socket的操作放到一个线程中, 读取数据后, 通过队列传递给其他线程处理,实际执行操作还是单线程
- mysql 主从同步,按表维度分发到同一个线程进行数据同步,再到按行维度分发到同一个线程
三、批量化处理
kafka发消息,将发送到同一个topic同一个分区的消息通过main函数的partitioner组件发送到同一个队列中由sender线程不断拉取队列中消息批量发送到broker中。利用批量发送消息处理,节省大量的网络开销,提高发送效率。
redis持久化 其中AOF在执行命令写入内存后,会写入到AOF缓冲区,可以选择合适的时机将AOF缓冲区中的数据写入到磁盘中。有三个值always、everysec、no
四、数据压缩合并
- 因为AOF是追加写日志,对于同一个key可能存在多条修改修改命令,导致AOF文件过大,redis重启后加载AOF文件会变得缓慢,导致启动时间过长。可以利用重写命令将对于同一个key的修改只保存一条记录,减小AOF文件体积。
- 大数据领域的Hbase、cassandra等nosql数据库写入性能都很高,它们的底层存储数据结构就是LSM树(log structured merge tree) ,这种数据结构的核心思想是追加写,积攒一定的数据后合并成更大的segement,对于数据的删除也只是增加一条删除记录。同样对一个key的修改记录也有多条。这种存储结构的优点是写入性能高,但是缺点也比较明显,数据存在冗余和文件体积大。主要通过线程进行段合并将多个小文件合并成更大的文件来减少存储文件体积,提升查询效率。
- kafka 生产端压缩,消费时解压缩,可以以降低成本、提高传输效率
- 对存入redis的数据进行压缩,采用snappy压缩,压缩后只是原来数据的40%-50%
- 将服务之间的调用从http的json改为trpc的pb协议,因为pb协议编码后的数据更小,提升传输效率
- 存储在es中的数据也可以手动调用api进行段合并
五、无锁化
- redis单线程
- go 调度模型时GM模型 优化为 GPM 模型
- mysql 的 mvcc , 通过数据版本减少锁的竞争
- atomic.value 通过CAS操作减少锁的竞争
- 针对秒杀模型
- 利用消息队列,对于秒杀系统的秒杀操作进行异步处理,将秒杀操作发布一个消息到消息队列中,这样所有用户的秒杀行为就形成了一个先进先出的队列,只有前面先添加到消息队列中的用户才能抢购商品成功。从队列中消费消息进行库存变更的线程是个单线程,因此对于db的操作不会存在冲突,不需要加锁操作。
- 将库存分成多份,分别加载到服务server的本地,这样多机之间在对库存变更的时候就避免了锁的竞争
顺序写
- WAL日志,将随机写转变为顺序写
- mysql的InnoDB存储引擎在创建主键时通常会建议使用自增主键,而不是使用uuid,最主要的原因是InnoDB底层采用B+树用来存储数据,每个叶子结点是一个数据页,存储多条数据记录,页面内的数据通过链表有序存储,数据页间通过双向链表存储。由于uuid是无序的,有可能会插入到已经空间不足的数据页中间,导致数据页分裂成两个新的数据页以便插入新数据,影响整体写入性能。
- kakfa中的每个分区是一个有序不可变的消息队列,新的消息会不断的添加的partition的尾部,每个partition由多个segment组成,一个segment对应一个物理日志文件,kafka对segment日志文件的写入也是顺序写。顺序写入的好处是避免了磁盘的不断寻道和旋转次数,极大的提高了写入性能。
分片化
- redis 数据分片
- 集群模式,hash 分片
- 代理模式,proxy分片,代表是 codis
- kafka中每个topic也支持多个partition,partition分布到多个broker上.通过增加partition数量可以增加消费者并行消费消息,提高kafka的水平扩展能力和吞吐量。
- 分库分表,apache sharding sphere
避免请求
为提升写入性能,mysql在写入数据的时候,对于在bufferpool中的数据页,直接修改bufferpool的数据页并写redolog;对于不在内存中的数据页并不会立刻将磁盘中的数据页加载到bufferpool中,而是仅仅将变更记录在缓冲区,等后续读取磁盘上的数据页到bufferpool中时会进行数据合并,需要注意的是对于非唯一索引才会采用这种方式,对于唯一索引写入的时候需要每次都将磁盘上的数据读取到bufferpool才能判断该数据是否已存在,对于已存在的数据会返回插入失败。
db 查询通过索引覆盖,避免回表操作
- 懒加载思想,减少请求策略
池化
- 对于goroutine的销毁就不是用完直接销毁,而是放到P的本地空闲队列中,当下次需要创建G的时候会从空闲队列中直接取一个G复用即可
- 数据库连接池
- 线程池
异步
- redis 的 bgSave、bgrewriteof
redis删除key的方式有del跟unlink两种,对于del命令是同步删除,直接释放内存,当遇到大key时,删除操作会让redis出现卡顿的问题,而unlink是异步删除的方式,执行后对于key只做不可达的标识,对于内存的回收由异步线程回收,不阻塞主线程。
mysql的主从同步支持异步复制、同步复制跟半同步复制
- 消息队列发消息 通过异步+回调函数的方式。当发送失败后通过回调函数进行感知,后续进行消息补偿。
- 在做服务性能优化中,发现之前的一些监控上报,曝光上报等操作都在主流程中,可以将这部分功能做异步处理,降低接口的时延