Go服务开发高手课(极客讲堂)
一、工具使用
1. pprof 工具。当 CPU 或者内存占用率过高时,使用 pprof 工具能够精准定位到消耗资源的热点代码。
2. benchmark 工具。要对不同方案进行性能对比时,使用 benchmark 工具,可以获取不同方案在耗时 和 内存消耗 方面的对比情况。
二、单机吞吐优化
1. 当用 map 构造集合时,可以将 value 类型设置为空结构体类型(struct{}),不占用内存空间,降低内存消耗。
2. 当创建 map 和切片对象时,如果可以提前确定容器容量,那就把容量传入 make 函数中,从而避免往集合中添加数据时触发扩容迁移,降低内存和 CPU 资源消耗。
3. 有大量字符串拼接操作时,可以使用 strings.Builder 类型,并利用它的内存预分配功能做字符串拼接。
4. 有大量整型转字符串操作时,可以用 strconv 库做转换,避免使用 fmt.Sprint 函数的反射和格式化。
5. 有大量字符串转字节切片操作时,可以用 unsafe 包,通过字符串和字节切片底层数组空间共用,实现高性能转换
6. 需要频繁创建相同类型的临时对象时,可以使用 sync.Pool 对象池,实现临时对象复用,从而减少 Go 中的内存分配和 GC 开销。
7. 需要频繁地创建协程,可以使用协程池。可以降低协程创建的开销。同时,协程池能限制同时运行的协程的最大数目,从而避免同时有太多协程,导致频繁进行协程调度。
三、并发等待:如何降低实时系统的响应延时?
1. WaitGroup 类型。一个规模较大的任务,为了提高执行效率,可以将其拆分成多个子任务,然后让这些子任务并发运行。而此时,如果还需要等待所有子任务都顺利执行完毕,那么 WaitGroup 类型能够精准地满足需求。
2. errgroup 包。errgroup 包可以看作是对 WaitGroup 类型的升级与封装。在实际开发中,需要周全地对可能出现的错误进行处理、灵活地取消任务以及精准地控制最大协程数等需求, errgroup 包就是最佳选择。
四、并发安全:如何为不同并发场景选择合适的锁?
1. 对数据的写操作较多或者读操作不频繁,可以使用互斥锁Mutex。
2. 读操作远多于写操作,可以使用读写锁,允许多个协程同时进行读操作,提高并发读取的性能。
3. 当大量数据存储在 map 中,并且协程对 map 的访问相对均匀地分布在不同的键上时,可以考虑使用分段锁。具体是将 map 分成多段,每段有自己的锁,降低锁粒度,从而提升并发性能。
4. 需要对共享对象进行原子操作,可以利用 atomic 包无锁编程,避免加锁操作,从而提升性能。
五、并发map:百万数据本地缓存,如何降延时减毛刺?
1. sync.Map 借助两个 map 来达成读写分离的设计,提升读取操作的性能。
2. 然而,这种设计虽然效率高,但存在着不容忽视的问题:
(1) 需要两个map,内存占用相对较高。
(2) 数据修改频繁时,读表命中率低。不命中的时候,既要读一次读表,又要读一次写表,降低性能。
(3) 读命中率低时,还会产生两个 map 之间的数据拷贝开销。数据量大时,会导致较大的性能开销。
3. 当 map 中缓存的数据比较多时,为了避免 GC 开销,可以将 map 中的 key-value 类型设计成非指针类型且大小不超过 128 字节,从而避免 GC 扫描。