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

我正在为一个网站编写一个基本的服务器。现在我面临一个(对我来说)困难的性能问题。读取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")

}

我认为在第一个示例中,服务器启动后您无需访问磁盘并提高请求的性能。

但是在开发过程中我想刷新浏览器并查看新内容。这可以通过第二个例子来完成。


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


忽然笑
浏览 159回答 2
2回答

当年话下

我们来分析一下性能:我们将您的第一个解决方案(略有变化,见下文)命名为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。如果在执行模板或写入其输出时发生错误,则执行将停止,但部分结果可能已写入输出写入器。模板可以安全地并行执行。所以,发生的事情是这样的:您从磁盘读取模板您将文件解析为模板您选择的数据填补空白与您Execute使用具有该数据的模板,结果将写入io.Writer您选择的数据是最新的。这与从磁盘重新读取模板,甚至重新解析它无关。这是模板背后的整个想法:一次磁盘访问,一次解析,多个动态最终结果。上面引用的文档告诉我们另一件事:模板可以安全地并行执行。这非常有用,因为您的http.HandlerFuncs 是并行运行的,如果您有多个并行请求。那么,现在该怎么办?Read模板文件一次,Parse模板一次,每个请求Execute的模板。我不确定您是否应该在init()函数中读取和解析,因为至少Must可以恐慌(并且不要在那里使用一些相对的硬编码路径!) - 我会尝试在更受控的环境中这样做,例如,提供一个函数(如New())来创建服务器的新实例并在其中执行这些操作。编辑:我重新阅读了你的问题,我可能误解了你:如果模板本身仍在开发中,那么是的,您必须在每个请求中阅读它才能获得最新的结果。这比每次更改模板时都重新启动服务器更方便。对于生产,模板应该是固定的,只有数据应该改变。对不起,如果我在那里弄错了。

蛊毒传说

永远不要在生产中的请求处理程序中读取和解析模板文件,这是最糟糕的(你应该总是避免这种情况)。在开发过程中当然是可以的。阅读此问题了解更多详情:golang中使用“模板”包生成动态网页给客户端的时间太长了你可以通过多种方式来解决这个问题。在这里,我列出了 4 个示例实现。1.带有“开发模式”设置如果您在开发模式下运行,您可以有一个常量或变量告诉您,这意味着模板不会被缓存。这是一个例子:const dev = truevar indexTmpl *template.Templatefunc 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.Templatefunc 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.Templatefunc 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 mytemplfunc 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")}
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Go