代码之家  ›  专栏  ›  技术社区  ›  loose11

在init或handler函数中读取模板?

  •  2
  • loose11  · 技术社区  · 9 年前

    我正在为一个网站编写一个基本服务器。现在我面临一个(对我来说)困难的绩效问题。在 init() 作用

    // Initialize all pages of website
    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")
    }
    

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

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

    2 回复  |  直到 9 年前
        1
  •  5
  •   Josh Crozier HBP    9 年前

    让我们分析一下性能:

    我们为您的第一个解决方案命名(略有变化,见下文) 和你的第二个解决方案 b .

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

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

    10000 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 模板 一旦 ,
    处决 模板 对于每个请求 .

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

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

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

    对不起,如果我弄错了。

        2
  •  3
  •   Community paulsm4    7 年前

    千万不要在生产环境中读取和解析请求处理程序中的模板文件,这是最糟糕的(您应该总是避免这样做)。在开发过程中,这当然是可以的。

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

    It takes too much time when using "template" package to generate a dynamic web page to client in 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")
    }
    

    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")
    }
    

    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() {
        // 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")
    }