donglilian0061 2014-06-23 22:34
浏览 27
已采纳

API库中的后台获取

I'm writing an API client (library) that hits a JSON end-point and populates an in-memory cache.

Thus far:

  • I kick off a time.Ticker loop in the library's init() function that hits the API every minute, which refreshes the cache (a struct that embeds the JSON struct and a timestamp).
  • The public facing function calls in the library just fetch from the catch and therefore don't need to worry about rate-limiting on their own part, but can check the timestamp if they want to confirm the freshness of the data

However, starting a time.Ticker in init() does not feel quite right: I haven't seen any other libs do this. I do however want to avoid the package user having to do a ton of work just to get data back from few JSON endpoints.

My public API looks like this:

// Example usage:
// rt := api.NewRT()
// err := rt.GetLatest
// tmpl.ExecuteTemplate(w, "my_page.tmpl", M{"results": rt.Data})

func (rt *RealTime) GetLatest() error {
    rt = realtimeCache.Cached
    if rt == nil {
        return errors.New("No cached response is available.")
    }

    return nil
}

And the internal fetcher is as below:

func fetchLatest() error {
    log.Println("Fetching latest RT results.")
    resp, err := http.Get(realtimeEndpoint)
    if err != nil {
        return err
    }
    defer resp.Body.Close()

    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        return err
    }

    // Lock our cache from writes
    realtimeCache.Lock()
    defer realtimeCache.Unlock()

    var rt *RealTime
    err = json.Unmarshal(body, &rt)
    if err != nil {
        return err
    }

    // Update the cache
    realtimeCache.Cached = rt

    return nil
}

func init() {
    // Populate the cache on start-up
    fetchLatest()
    fetchHistorical()

    // Refresh the cache every minute (default)
    ticker := time.NewTicker(time.Second * interval)
    go func() {
        for _ = range ticker.C {
            fetchLatest()
            fetchHistorical()
        }
    }()
}

There are similar functions for other parts of the API (which I'm modularising, but I've kept it simple to start with), but this is the gist of it.

Is there a better way to have a background worker fetch results that's still user-friendly?

  • 写回答

2条回答 默认 最新

  • donglu8334 2014-06-30 03:56
    关注

    Like Elwinar said, starting the timer in init is a bad idea, however you have a constructor, so any "object construction" should happen in it, here's a short example :

    (check the playground for the full code)

    func NewRT(interval int) (rt *realTime) {
        rt = &realTime{
            tk: time.NewTicker(time.Second * time.Duration(interval)),
        }
        go func() {
            rt.fetch()
            for _ = range rt.tk.C {
                rt.fetch()
            }
        }()
    
        return
    }
    
    func (rt *realTime) fetch() {
        rt.Lock()
        defer rt.Unlock()
        rt.fetchLatest()
        rt.fetchHistory()
    }
    

    ......

    func (rt *realTime) GetLatest() error {
        rt.RLock()
        defer rt.RUnlock()
        if rt.cached == nil || len(rt.cached) == 0 {
            return ErrNoCachedResponse
        }
    
        return nil
    }
    
    func (rt *realTime) Stop() {
        rt.Lock()
        defer rt.Unlock()
        rt.tk.Stop()
    }
    
    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论
查看更多回答(1条)

报告相同问题?

悬赏问题

  • ¥15 一道ban了很多东西的pyjail题
  • ¥15 关于#r语言#的问题:如何将生成的四幅图排在一起,且对变量的赋值进行更改,让组合的图漂亮、美观@(相关搜索:森林图)
  • ¥15 C++识别堆叠物体异常
  • ¥15 微软硬件驱动认证账号申请
  • ¥15 有人知道怎么在R语言里下载Git上的miceco这个包吗
  • ¥15 GPT写作提示指令词
  • ¥20 根据动态演化博弈支付矩阵完成复制动态方程求解和演化相图分析等
  • ¥20 关于DAC输出1.000V对分辨率和精度的要求
  • ¥15 华为超融合部署环境下RedHat虚拟机分区扩容问题
  • ¥15 哪位能做百度地图导航触点播报?