V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
sophos
V2EX  ›  Go 编程语言

利用 nginx fastcgi_cache 及 golang-lru 解决接口瓶颈

  •  
  •   sophos ·
    sysulq · 2016-10-31 16:37:43 +08:00 · 1061 次点击
    这是一个创建于 2732 天前的主题,其中的信息可能已经有所发展或是发生改变。

    为满足特定的业务需求,我们有些接口数据极大,返回给客户端的数据 gzip 后大概是 80KB 左右, 不做 gzip 的话大概有 600KB 的样子。

    实现方案

    1. nginx 做接入层,将请求转发到 golang/php 实现的接口服务
    2. golang/php 接收请求,先查 redis 集群,如果没有数据则查 mysql ,对数据做 json 序列化再存入 redis 集群 如果查到数据则对其做 json 反序列化,然后做相应业务逻辑生成结果并返回给 nginx
    3. nginx 对数据做 gzip 再返回给客户端

    存在问题

    1. redis 集群流量较高
    2. golang/php 服务的 cpu 消耗比较明显,但是内存并没占用多少
    3. json 反序列化效率极差,尤其在大数据量的场景下

    解决方案

    由于 golang 与 php 的实现是分别由两个团队完成的,考虑到实现的可行性,我们先考虑通过 nginx 的 fastcgi_cache 来实现静态接口的缓存。这类接口并不要求实时更新,且大多通过 php 实现,因此完全可以通过 nginx fastcgi_cache 实现。而且此方案还有一些额外的好处,譬如避免请求蜂拥至后端,若后端出错可直接返回老数据等等。

    然而,对于动态接口而言,这个方案完全没有办法满足需求。幸而动态接口基本都是基于 golang 实现的,因此我们只需要引入一种本地内存缓存方案,就可以很好的解决前面提及的三个问题。

    经过一周左右时间的调研,我们考察了 golang-lru 、 go-cache 、 groupcache 、 freecache 及 bigcache 等各种缓存库,出于简单稳定高效的角度,我们最终选用了 golang-lru ,并在此基础上实现了 expire feature ,参考链接:GitHub - hnlq715/golang-lru: Golang LRU cache with expire feature.

    与此同时,我们也参考了 groupcache 的特性,实现了类似逻辑,避免同时涌入过多请求到 redis :

    func (l *lruCache) GetWithLoader(ctx context.Context, key string, load GetterFunc) (interface{}, error) {
    	l.stats.Gets.Add(1)
    	data, ok := l.arc.Get(key)
    	if !ok && load != nil {
    		return l.g.Do(key, func() (interface{}, error) {
    			l.stats.Loads.Add(1)
    			data, err := load()
    			if err != nil {
    				return nil, err
    			}
    			l.Set(ctx, key, data)
    			return data, err
    		})
    	}
    
    	l.stats.Hits.Add(1)
    	return data, nil
    }
    

    通过这个方案,我们实现了以下几个目标:

    1. 极大降低了 redis 集群流量
    2. cpu 资源消耗降低,内存利用率提升
    3. 避免过多 json 反序列化

    从线上监控情况来看,目前的服务无论从响应时间、缓存命中率还是接口可用性来看,都比之前有了大幅提升。

    作者: Sophos
    链接: http://zhuanlan.zhihu.com/p/23326080
    来源:知乎
    著作权归作者所有,转载请联系作者获得授权。
    
    3 条回复    2016-11-01 11:00:45 +08:00
    mooncakejs
        1
    mooncakejs  
       2016-10-31 20:14:28 +08:00 via iPhone
    不怎么更新的我一般扔 cdn
    sophos
        2
    sophos  
    OP
       2016-10-31 21:00:55 +08:00
    @mooncakejs 静态接口的确可以扔 cdn , fastcgi_cache 实际上也就是扮演这个角色,但动态接口还是得靠自己 : )
    denghongcai
        3
    denghongcai  
       2016-11-01 11:00:45 +08:00
    简单点讲:从完全使用外部缓存( Redis )改成了外部缓存和进程内缓存并用
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   5808 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 02:29 · PVG 10:29 · LAX 19:29 · JFK 22:29
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.