前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >使用 Go 优化我们的接口

使用 Go 优化我们的接口

作者头像
haifeiWu
发布2020-02-10 17:59:00
7280
发布2020-02-10 17:59:00
举报
文章被收录于专栏:haifeiWu与他朋友们的专栏

标题起的是有点大,不过还好本片文章主要也是使用 Go 来优化 HTTP 服务的,也算打个擦边球吧~

背景

特征数据暴增,导致获取一个城市下所有的特征的接口延时高,下面是监控上看到的接口响应耗时,最慢的时候接口响应时间能达到 5s 多。

缓存优化方案

代码优化思路:

1,使用缓存

1.1为什么使用内存,而不是 Redis?

分析业务需求,当前需要存储起来的数据是ObjectId,ObjectId 是一个长度为14左右的字符串,我们假设平均下来ObjectId是长度为16的字符串,这样算下来就是每个ObjectId占用的内存大小是2个字节,当前业务需要存储的ObjectId大概是30万条,这样算下来当前业务需要存储的ObjectId要占用的内存在0.5M完全可以在内存中进行操作。相比于使用 Redis 来说没有网络开销,效率更高。

1.2,缓存初始化:当服务启动时,本地缓存初始化为空。

1.3 关于缓存版本的概念

缓存版本是离线特征生产任务更新后将数据版本更新到 fusion 中。

下面三种方案都是基于内存存储 ObjectId 数据,在内存更新的时候策略有所不同。

方案一

2.1 缓存更新

使用主动更新缓存的方式,创建定时任务,每间隔1分钟查一次fusion的数据版本,若更新则更新缓存中的数据。

2.2 缺点

单独启动一个缓存更新线程,代码不好维护,也会有定时任务线程挂掉的情况,不易发现。还有就是需要提前把相关参数配置到代码中或者引入配置中心,维护成本较高。

方案二

3.1 缓存更新

采用被动触发的缓存更新策略,由接口调用触发。请求进来后检测当前缓存中的数据的版本与fusion中的数据版本是否一致,若版本更新,则重新读取当前请求对应城市的所有feature数据到缓存中,并将更新后的数据返回给调用方。

3.2 缺点

由于是被动触发的是同步更新缓存的,容易造成接口调用时如果正好遇上版本更新,需要更新数据到内存中,会出现偶现的毛刺。

3.3 业务执行时序图

方案三(最终采用的方案)

4.1,缓存更新

采用被动更新缓存的策略,由接口调用方触发。若当前缓存中有数据则直接返回缓存中的数据,然后检测当前缓存中的数据的版本与fusion中的数据版本是否一致,若版本更新,则重新读取当前请求对应城市的所有feature数据到缓存中,反之结束缓存更新逻辑。

4.2 业务执行时序图

并发优化方案

使用 Goroutine 来优化我们的串行逻辑

Go语言最大的特色就是从语言层面支持并发(Goroutine),Goroutine是Go中最基本的执行单元。事实上每一个Go程序至少有一个Goroutine:主Goroutine。当程序启动时,它会自动创建。

为了更好理解Goroutine,现讲一下线程和协程的概念

线程(Thread):有时被称为轻量级进程(Lightweight Process,LWP),是程序执行流的最小单元。一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成。另外,线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。

线程拥有自己独立的栈和共享的堆,共享堆,不共享栈,线程的切换一般也由操作系统调度。

协程(coroutine):又称微线程与子例程(或者称为函数)一样,协程(coroutine)也是一种程序组件。相对子例程而言,协程更为一般和灵活,但在实践中使用没有子例程那样广泛。

和线程类似,共享堆,不共享栈,协程的切换一般由程序员在代码中显式控制。它避免了上下文切换的额外耗费,兼顾了多线程的优点,简化了高并发程序的复杂。

golang 中的 map 是线程不安全的

很显然,我们可以用锁机制解决 Map 的并发读写问题。我们将map结构改成如下:

代码语言:javascript
复制
// M
type M struct {
    Map    map[string]string
    lock sync.RWMutex // 加锁
}

// Set ...
func (m *M) Set(key, value string) {
    m.lock.Lock()
    defer m.lock.Unlock()
    m.Map[key] = value
}

// Get ...
func (m *M) Get(key string) string {
    m.lock.RLock()
    defer m.lock.RUnlock()
    return m.Map[key]
}

在上面的代码中,我们引入了锁机制操作,从而保证了map在多个goroutine中的安全。

使用策略模式优化我们的逻辑

这块主要是因为代码中存在太多的 if/else ,故采用策略模式来优化我们的代码结构。这里先放上一篇网上找到的文章,之后有时间再单独出一篇相关文章吧。优化后的代码相较于之前代码量少了 50% ,更加清晰与便于维护。下面是优化的代码上线后的效果,请求耗时都在100ms以下:

小结

上面整体介绍了下当我们的接口耗时较长的时候的一般处理方案,当然具体问题还得具体分析,所以当出现接口反应慢的情况的时候,我们应该具体分析接口反应慢的具体原因,方可对症下药!

作 者:haifeiWu 原文链接:https://www.hchstudio.cn/article/2019/41d1/ 版权声明:非特殊声明均为本站原创作品,转载时请注明作者和原文链接。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 背景
  • 缓存优化方案
    • 方案一
      • 方案二
        • 方案三(最终采用的方案)
        • 并发优化方案
          • 使用 Goroutine 来优化我们的串行逻辑
            • golang 中的 map 是线程不安全的
            • 使用策略模式优化我们的逻辑
            • 小结
            相关产品与服务
            云数据库 Redis®
            腾讯云数据库 Redis®(TencentDB for Redis®)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档