The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
sophos

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

  •  
  •   sophos · Oct 31, 2016 · 1652 views
    This topic created in 3575 days ago, the information mentioned may be changed or developed.

    为满足特定的业务需求,我们有些接口数据极大,返回给客户端的数据 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 replies    2016-11-01 11:00:45 +08:00
    mooncakejs
        1
    mooncakejs  
       Oct 31, 2016 via iPhone
    不怎么更新的我一般扔 cdn
    sophos
        2
    sophos  
    OP
       Oct 31, 2016
    @mooncakejs 静态接口的确可以扔 cdn , fastcgi_cache 实际上也就是扮演这个角色,但动态接口还是得靠自己 : )
    denghongcai
        3
    denghongcai  
       Nov 1, 2016
    简单点讲:从完全使用外部缓存( Redis )改成了外部缓存和进程内缓存并用
    About   ·   Help   ·   Advertise   ·   Blog   ·   API   ·   FAQ   ·   Solana   ·   2737 Online   Highest 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 12:25 · PVG 20:25 · LAX 05:25 · JFK 08:25
    ♥ Do have faith in what you're doing.