在初始化(init)函数中还是在处理程序(handler function)中读取模板?

huangapple go评论159阅读模式
英文:

Read template in init or in handler function?

问题

我正在为一个网站编写一个基本的服务器。现在我面临一个(对我来说)比较困难的性能问题。在init()函数中读取模板文件是否更好?

// 初始化网站的所有页面
func init() {
    indexPageData, err := ioutil.ReadFile("./tpl/index.tpl")
    check(err)
}

还是在http.HandlerFunc中读取?

func index(w http.ResponseWriter, req *http.Request) {
    indexPageData, err := ioutil.ReadFile("./tpl/index.tpl")
    check(err)
    indexPageTpl := template.Must(template.New("index").Parse(string(indexPageData)))
    indexPageTpl.Execute(w, "test")
}

我认为在第一个示例中,在服务器启动后,您无需访问磁盘并提高请求的性能。但在开发过程中,我想刷新浏览器并查看新内容。这可以通过第二个示例来实现。

有人有最先进的解决方案吗?或者从性能角度来看,哪个是正确的选择?

英文:

I'm writing a basic server for a website. Now I face a (for me) difficult performance question. Is it better to read the template file in the init() function?

// Initialize all pages of website
func init(){
 indexPageData, err := ioutil.ReadFile("./tpl/index.tpl")
 check(err)
}

Or in the http.HandlerFunc?

func index(w http.ResponseWriter, req *http.Request){
  indexPageData, err := ioutil.ReadFile("./tpl/index.tpl")
  check(err)
  indexPageTpl := template.Must(template.New("index").Parse(string(indexPageData)))
  indexPageTpl.Execute(w, "test")
}

I think in the first example, after the server is started you have no need to access the disk and increase the performance of the request.
But during development I want to refresh the browser and see the new content. That can be done with the second example.

Does someone have a state-of-the-art solution? Or what is the right from the performance point of view?

答案1

得分: 5

让我们分析一下性能:

我们将您的第一个解决方案(稍作修改,见下文)称为a,将您的第二个解决方案称为b

一个请求:
a:一次磁盘访问
b:一次磁盘访问

十个请求:
a:一次磁盘访问
b:十次磁盘访问

10,000,000个请求:
a:一次磁盘访问
b:10,000,000次磁盘访问(这很慢)

所以,您的第一个解决方案的性能更好。但是,关于您对数据实时性的担忧呢?从func (t *Template) Execute(wr io.Writer, data interface{}) error的文档中可以得知:

> Execute将解析后的模板应用于指定的数据对象,并将输出写入wr。如果在执行模板或写入其输出时发生错误,则执行停止,但部分结果可能已经写入输出写入器。模板可以安全地并行执行。

所以,具体的执行过程如下:

  1. 从磁盘读取模板
  2. 将文件解析为模板
  3. 选择要用于填充空白的数据
  4. 使用该数据Execute模板,结果写入io.Writer

您的数据与您选择的一样实时。这与重新从磁盘读取模板或重新解析模板无关。这就是模板的整个理念:一次磁盘访问,一次解析,多个动态的最终结果。

上述引用的文档还告诉我们另一件事:

> 模板可以安全地并行执行。

这非常有用,因为如果您有多个并行请求,您的http.HandlerFunc将并行运行。

那么现在该怎么办呢?
只需一次Read模板文件,
只需一次Parse模板,
每个请求都要Execute模板。

我不确定是否应该在init()函数中读取和解析,因为至少Must可能会引发恐慌(而且不要在其中使用一些相对的、硬编码的路径!)- 我建议在一个更受控制的环境中进行,例如提供一个函数(如New())来创建服务器的新实例,并在其中执行这些操作。

编辑:我重新阅读了您的问题,可能误解了您的意思:

如果模板本身仍在开发中,那么是的,您需要在每个请求中读取它以获得最新的结果。这比每次更改模板时重新启动服务器更方便。对于生产环境,模板应该是固定的,只有数据应该发生变化。

如果我理解错了,对不起。

英文:

Let's analyze the performance:

We name your first solution (with slight changes, see below) a and your second solution b.

One request:
a: One disk access
b: One disk access

Ten requests:
a: One disk access
b: Ten disk accesses

10 000 000 requests:
a: One disk access
b: 10 000 000 disk accesses (this is slow)

So, performance is better with your first solution. But what about your concern regarding up-to-date data? From the documentation of func (t *Template) Execute(wr io.Writer, data interface{}) error:

> Execute applies a parsed template to the specified data object, writing the output to wr. If an error occurs executing the template or writing its output, execution stops, but partial results may already have been written to the output writer. A template may be executed safely in parallel.

So, what happens is this:

  1. You read a template from disk
  2. You parse the file into a template
  3. You choose the data to fill in the blanks with
  4. You Execute the template with that data, the result is written out into an io.Writer

Your data is as up-to-date as you choose it. This has nothing to do with re-reading the template from disk, or even re-parsing it. This is the whole idea behind templates: One disk access, one parse, multiple dynamic end results.

The documentation quoted above tells us another thing:

> A template may be executed safely in parallel.

This is very useful, because your http.HandlerFuncs are ran in parallel, if you have multiple requests in parallel.

So, what to do now?
Read the template file once,
Parse the template once,
Execute the template for every request.

I'm not sure if you should read and parse in the init() function, because at least the Must can panic (and don't use some relative, hard coded path in there!) - I would try to do that in a more controlled environment, e.g. provide a function (like New()) to create a new instance of your server and do that stuff in there.

EDIT: I re-read your question and I might have misunderstood you:

If the template itself is still in development then yes, you would have to read it on every request to have an up-to-date result. This is more convenient than to restart the server every time you change the template. For production, the template should be fixed and only the data should change.

Sorry if I got you wrong there.

答案2

得分: 4

在生产环境中,永远不要在请求处理程序中读取和解析模板文件,这是非常糟糕的做法(你应该始终避免这样做)。在开发过程中,这是可以接受的。

阅读以下问题以获取更多详细信息:

https://stackoverflow.com/questions/28451675/it-takes-too-much-time-when-using-template-package-to-generate-a-dynamic-web-p

你可以用多种方式来解决这个问题。这里列出了4种示例实现方法。

1. 使用“开发模式”设置

你可以设置一个常量或变量来表示是否在开发模式下运行,这意味着模板不会被缓存。

以下是一个示例:

const dev = true

var indexTmpl *template.Template

func init() {
	if !dev { // 生产模式,读取并缓存模板
		indexTmpl = template.Must(template.New("index").ParseFiles(".tpl/index.tpl"))
	}
}

func getIndexTmpl() *template.Template {
	if dev { // 开发模式,始终读取最新的模板
		return template.Must(template.New("index").ParseFiles(".tpl/index.tpl"))
	} else { // 生产模式,返回缓存的模板
		return indexTmpl
	}
}

func indexHandler(w http.ResponseWriter, r *http.Request) {
	getIndexTmpl().Execute(w, "test")
}

2. 在请求中指定是否要使用最新的模板(作为参数)

在开发过程中,你可以指定一个额外的URL参数,表示要读取最新的模板而不使用缓存的模板,例如 http://localhost:8080/index?dev=true

以下是示例实现:

var indexTmpl *template.Template

func init() {
	indexTmpl = getIndexTmpl()
}

func getIndexTmpl() *template.Template {
	return template.Must(template.New("index").ParseFiles(".tpl/index.tpl"))
}

func indexHandler(w http.ResponseWriter, r *http.Request) {
	t := indexTmpl
	if r.FormValue("dev") != "" {
		t = getIndexTmpl()
	}
	t.Execute(w, "test")
}

3. 基于主机名进行决策

你还可以检查请求URL的主机名,如果是 "localhost",则可以省略缓存并使用最新的模板。这只需要很少的额外代码和工作量。注意,你可能还想接受其他主机,例如 "127.0.0.1"(根据你的需求决定)。

以下是示例实现:

var indexTmpl *template.Template

func init() {
	indexTmpl = getIndexTmpl()
}

func getIndexTmpl() *template.Template {
	return template.Must(template.New("index").ParseFiles(".tpl/index.tpl"))
}

func indexHandler(w http.ResponseWriter, r *http.Request) {
	t := indexTmpl
	if r.URL.Host == "localhost" || strings.HasPrefix(r.URL.Host, "localhost:") {
		t = getIndexTmpl()
	}
	t.Execute(w, "test")
}

4. 检查模板文件的最后修改时间

你还可以在加载模板文件时存储模板文件的最后修改时间。每当请求模板时,你可以检查源模板文件的最后修改时间。如果发生了变化,可以在执行之前重新加载模板。

以下是示例实现:

type mytempl struct {
	t       *template.Template
	lastmod time.Time
	mutex   sync.Mutex
}

var indexTmpl mytempl

func init() {
	// 你可能希望在init中调用此函数,以避免第一个请求变慢
	checkIndexTempl()
}

func checkIndexTempl() {
	nm := ".tpl/index.tpl"
	fi, err := os.Stat(nm)
	if err != nil {
		panic(err)
	}
	if indexTmpl.lastmod != fi.ModTime() {
		// 发生了变化,重新加载。不要忘记加锁!
		indexTmpl.mutex.Lock()
		defer indexTmpl.mutex.Unlock()
		indexTmpl.t = template.Must(template.New("index").ParseFiles(nm))
		indexTmpl.lastmod = fi.ModTime()
	}
}

func indexHandler(w http.ResponseWriter, r *http.Request) {
	checkIndexTempl()
	indexTmpl.t.Execute(w, "test")
}
英文:

Never read and parse template files in the request handler in production, that is as bad as it can get (you should like always avoid this). During development it is ok of course.

Read this question for more details:

https://stackoverflow.com/questions/28451675/it-takes-too-much-time-when-using-template-package-to-generate-a-dynamic-web-p

You could approach this in multiple ways. Here I list 4 with example implementation.

1. With a "dev mode" setting

You could have a constant or variable telling if you're running in development mode which means templates are not to be cached.

Here's an example to that:

const dev = true

var indexTmpl *template.Template

func init() {
	if !dev { // Prod mode, read and cache template
		indexTmpl = template.Must(template.New("index").ParseFiles(".tpl/index.tpl"))
	}
}

func getIndexTmpl() *template.Template {
	if dev { // Dev mode, always read fresh template
		return template.Must(template.New("index").ParseFiles(".tpl/index.tpl"))
	} else { // Prod mode, return cached template
		return indexTmpl
	}
}

func indexHandler(w http.ResponseWriter, r *http.Request) {
	getIndexTmpl().Execute(w, "test")
}

2. Specify in the request (as a param) if you want a fresh template

When you develop, you may specify an extra URL parameter indicating to read a fresh template and not use the cached one, e.g. http://localhost:8080/index?dev=true

Example implementation:

var indexTmpl *template.Template

func init() {
	indexTmpl = getIndexTmpl()
}

func getIndexTmpl() *template.Template {
	return template.Must(template.New("index").ParseFiles(".tpl/index.tpl"))
}

func indexHandler(w http.ResponseWriter, r *http.Request) {
	t := indexTmpl
	if r.FormValue("dev") != nil {
		t = getIndexTmpl()
	}
	t.Execute(w, "test")
}

3. Decide based on host

You can also check the host name of the request URL, and if it is "localhost", you can omit the cache and use a fresh template. This requires the smallest extra code and effort. Note that you may want to accept other hosts as well e.g. "127.0.0.1" (up to you what you want to include).

Example implementation:

var indexTmpl *template.Template

func init() {
	indexTmpl = getIndexTmpl()
}

func getIndexTmpl() *template.Template {
	return template.Must(template.New("index").ParseFiles(".tpl/index.tpl"))
}

func indexHandler(w http.ResponseWriter, r *http.Request) {
	t := indexTmpl
	if r.URL.Host == "localhost" || strings.HasPrefix(r.URL.Host, "localhost:") {
		t = getIndexTmpl()
	}
	t.Execute(w, "test")
}

4. Check template file last modified

You could also store the last modified time of the template file when it is loaded. Whenever the template is requested, you can check the last modified time of the source template file. If it has changed, you can reload it before executing it.

Example implementation:

type mytempl struct {
	t       *template.Template
	lastmod time.Time
	mutex   sync.Mutex
}

var indexTmpl mytempl

func init() {
	// You may want to call this in init so first request won't be slow
	checkIndexTempl()
}

func checkIndexTempl() {
	nm := ".tpl/index.tpl"
	fi, err := os.Stat(nm)
	if err != nil {
		panic(err)
	}
	if indexTmpl.lastmod != fi.ModTime() {
		// Changed, reload. Don't forget the locking!
		indexTmpl.mutex.Lock()
		defer indexTmpl.mutex.Unlock()
		indexTmpl.t = template.Must(template.New("index").ParseFiles(nm))
		indexTmpl.lastmod = fi.ModTime()
	}
}

func indexHandler(w http.ResponseWriter, r *http.Request) {
	checkIndexTempl()
	indexTmpl.t.Execute(w, "test")
}

huangapple
  • 本文由 发表于 2015年9月15日 13:58:59
  • 转载请务必保留本文链接:https://go.coder-hub.com/32578718.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定