英文:
How to store or cache values to be served in http requests in Golang?
问题
我正在编写一些 Go 的 Web 服务(同时使用 Go 实现 Web 服务器,使用 http.ListenAndServe)。我有一个结构体的映射,我希望将其保存在内存中(大约占用 100KB 的数据大小),以供不同的 HTTP 请求使用。
在 Go 中如何实现这一点?我考虑使用全局包变量或缓存系统(如 memcache/groupcache)。
英文:
I am writing some Go web services (also implementing the webserver in Go with http.ListenAndServe).
I have a map of structs which I would like to keep in memory (with an approximate data size of 100Kb) to be used by different HTTP requests.
How can this be achieved in Go? I am thinking to use global package variables or caching systems (like memcache/groupcache).
答案1
得分: 13
除了你已经收到的答案之外,考虑使用接收器柯里化的方法值和http.HandlerFunc。
如果你的数据是在进程启动之前加载的数据,你可以使用以下代码:
type Common struct {
Data map[string]*Data
}
func NewCommon() (*Common, error) {
// 加载数据
return c, err
}
func (c *Common) Root(w http.ResponseWriter, r *http.Request) {
// 处理函数
}
func (c *Common) Page(w http.ResponseWriter, r *http.Request) {
// 处理函数
}
func main() {
common, err := NewCommon()
if err != nil { ... }
http.HandleFunc("/", common.Root)
http.HandleFunc("/page", common.Page)
http.ListenAndServe(...)
}
如果所有的Common
数据都是只读的,这种方法非常适用。如果Common
数据是可读写的,那么你可能需要使用以下代码:
type Common struct {
lock sync.RWMutex
data map[string]Data // Data 应该不包含任何引用字段
}
func (c *Common) Get(key string) (*Data, bool) {
c.lock.RLock()
defer c.lock.RUnlock()
d, ok := c.data[key]
return &d, ok
}
func (c *Common) Set(key string, d *Data) {
c.lock.Lock()
defer c.lock.Unlock()
c.data[key] = *d
}
其余部分基本相同,只是不再直接通过接收器的字段访问数据,而是通过获取器和设置器来访问数据。在大多数数据被读取的 Web 服务器中,你可能需要使用 RWMutex,以便可以并发执行读取操作。第二种方法的另一个优点是你封装了数据,因此如果你的应用程序在将来需要透明地进行写入或读取操作,可以轻松地添加到 memcache 或 groupcache 等缓存中。
我非常喜欢将处理函数定义为对象的方法,因为这样可以更容易地对它们进行单元测试:你可以轻松地定义一个包含所需值和期望输出的表驱动测试,而无需处理全局变量的麻烦。
英文:
In addition to the answers you've already received, consider making use of receiver-curried method values and http.HandlerFunc.
If your data is data that is loaded before the process starts, you could go with something like this:
type Common struct {
Data map[string]*Data
}
func NewCommon() (*Common, error) {
// load data
return c, err
}
func (c *Common) Root(w http.ResponseWriter, r *http.Request) {
// handler
}
func (c *Common) Page(w http.ResponseWriter, r *http.Request) {
// handler
}
func main() {
common, err := NewCommon()
if err != nil { ... }
http.HandleFunc("/", common.Root)
http.HandleFunc("/page", common.Page)
http.ListenAndServe(...)
}
This works nicely if all of the Common
data is read-only. If the Common
data is read/write, then you'll want to have something more like:
type Common struct {
lock sync.RWMutex
data map[string]Data // Data should probably not have any reference fields
}
func (c *Common) Get(key string) (*Data, bool) {
c.lock.RLock()
defer c.lock.RUnlock()
d, ok := c.data[key]
return &d, ok
}
func (c *Common) Set(key string, d *Data) {
c.lock.Lock()
defer c.lock.Unlock()
c.data[key] = *d
}
The rest is basically the same, except instead of accessing the data through the receiver's fields directly, you'd access them through the getters and setters. In a webserver where most of the data is being read, you will probably want an RWMutex, so that reads can be executed concurrently with one another. Another advantage of the second approach is that you've encapsulated the data, so you can add in transparent writes to and/or reads from a memcache or a groupcache or something of that nature in the future if your application grows such a need.
One thing that I really like about defining my handlers as methods on an object is that it makes it much easier to unit test them: you can easily define a table driven test that includes the values you want and the output you expect without having to muck around with global variables.
答案2
得分: 2
不要沉迷于过早优化。定义一个Go包API来封装数据,这样你可以随时更改实现方式。例如,只需简单地编写以下代码:
package data
type Key struct {
// . . .
}
type Data struct {
// . . .
}
var dataMap map[Key]Data
func init() {
dataMap = make(map[Key]Data)
}
func GetData(key Key) (*Data, error) {
data := dataMap[key]
return &data, nil
}
英文:
Don't indulge in premature optimization. Define a Go package API to encapsulate the data and then you can change the implementation at any time. For example, just scribbling,
package data
type Key struct {
// . . .
}
type Data struct {
// . . .
}
var dataMap map[Key]Data
func init() {
dataMap = make(map[Key]Data)
}
func GetData(key Key) (*Data, error) {
data := dataMap[key]
return &data, nil
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论