在init或处理函数中读取模板?

loo*_*e11 2 performance template-engine http go

我正在为网站编写基本服务器.现在,我面临一个(对我而言)难以解决的表现问题.在init()函数中读取模板文件是否更好?

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

还是在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")
}
Run Code Online (Sandbox Code Playgroud)

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

有人拥有最先进的解决方案吗?或者从性能的角度来看是什么?

mrd*_*l4r 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如果并行有多个请求,则s并行运行.

那么,现在该怎么办?
Read模板文件一次,
Parse模板一次,每个请求
Execute的模板.

我不确定你是否应该阅读和解析init()函数,因为至少Must可以恐慌(并且不要在那里使用一些相对的硬编码路径!) - 我会尝试在更受控制的环境中这样做,例如,提供一个函数(如New())来创建服务器的新实例并在那里执行这些操作.

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

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

对不起,如果我在那里弄错了.

  • @ loose11你可以使用配置的BRA https://github.com/Unknwon/bra这是一个很好的工具,可以检测源代码的变化并自动重启服务器,我不会在你的处理程序中引入代码更改,只是为了介绍环境处理. (2认同)

icz*_*cza 5

永远不要在生产中的请求处理程序中读取和解析模板文件,这是最糟糕的(您应该始终避免这种情况)。开发过程中当然没问题。

阅读此问题了解更多详细信息:

在golang中使用“模板”包生成动态网页给客户端需要花费太多时间

您可以通过多种方式解决这个问题。这里我列出了 4 个示例实现。

1.具有“开发模式”设置

您可以有一个常量或变量来告诉您是否在开发模式下运行,这意味着模板不会被缓存。

这是一个例子:

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")
}
Run Code Online (Sandbox Code Playgroud)

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") != nil {
        t = getIndexTmpl()
    }
    t.Execute(w, "test")
}
Run Code Online (Sandbox Code Playgroud)

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")
}
Run Code Online (Sandbox Code Playgroud)

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

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

实施示例:

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")
}
Run Code Online (Sandbox Code Playgroud)