go - 应用程序的邮件系统之类的东西是否应该像本例所示那样在单独的 channel 中运行?

标签 go architecture channel

想象一下具有大量不同路由的 Web 服务。其中一些会触发发送给用户的交易电子邮件。初始化一个 mailer 实例似乎很奇怪,例如每次请求想要发送一些东西时使用 github.com/aws/aws-sdk-go/service/sns .

相反,我假设有一个 mailer 实例,并且所有事情都发生在一个单独的 channel 上,消息 被发布到该 channel 。

例子

我创建了一个简单示例来说明问题。全局 Mailer 实例配置一次,Index 处理程序请求一个 channel 并传递一个 Message

package main

import (
    "fmt"
    "log"
    "net/http"
    "os"
)

// Message is the custom type used to pass the channel
type Message struct {
    To      string
    Subject string
    Body    string
}

// Mailer is responsible to send out emails
type Mailer struct{}

// send sends out the email
func (m *Mailer) send(message Message) {
    fmt.Printf("Sending email to:`%s`\nSubject: %s\n%s\n\n", message.To, message.Subject, message.Body)
}

// Messages returns the channel to which messages can be passed
func (m *Mailer) Messages() chan<- Message {
    cm := make(chan Message)

    go func() {
        msg := <-cm
        m.send(msg)

        close(cm)
    }()

    return cm
}

// mailer is a global var in this example, would probably be part of some
// sort of app context that's accessible from any handler.
//
// Note the mailer is NOT handler-scoped.
var mailer = Mailer{} // would this be thread-safe?

// Index handler
func Index(w http.ResponseWriter, r *http.Request) {
    m := Message{"email@example.com", fmt.Sprintf("visited `%s`", r.URL.Path[1:]), "Lorem ipsum"}
    mailer.Messages() <- m

    fmt.Fprintf(w, "Sent out email with subject line `%s`\n", m.Subject)
}

func main() {
    http.HandleFunc("/", Index)

    port := os.Getenv("PORT")
    if port == "" {
        port = "8080"
    }

    if err := http.ListenAndServe(":"+port, nil); err != nil {
        log.Fatal("ListenAndServe: ", err)
    }
}

输出

访问 http://localhost:8080/hello-world 将呈现...

Sent out email with subject line `visited `hello-world``

…并记录

sending email to `email@example.com`:
visited `hello-world`
Lorem ipsum

问题

  1. 这是正确的方法吗?
  2. 它是线程安全的吗?如果不是,如何让它成为线程安全的?

最佳答案

在这个例子中你并没有真正做任何事情,但是通过 channel 传递消息总是安全的—— channel 是语言中的基本并发原语之一。您让自己面临竞争条件的可能性,这取决于 send 实际上最终做了什么。另一种处理方法是让 send 从单个 channel 接收。

type Mailer struct{
    Messages chan Message
}

func (m *Mailer) send() {
    for message := range m.Messages {
        fmt.Printf("Sending email to:`%s`\nSubject: %s\n%s\n\n", message.To, message.Subject, message.Body)
    }
}

var mailer *Mailer

func Index(w http.ResponseWriter, r *http.Request) {
    m := Message{"email@example.com", fmt.Sprintf("visited `%s`", r.URL.Path[1:]), "Lorem ipsum"}
    mailer.Messages <- m

    fmt.Fprintf(w, "Sent out email with subject line `%s`\n", m.Subject)
}


func main() {
    mailer = &Mailer{
        // buffer up to 100 message to be sent before blocking
        Messages: make(chan Message, 100),
    }
    // start the mailer send loop
    go mailer.send()

    ...

关于go - 应用程序的邮件系统之类的东西是否应该像本例所示那样在单独的 channel 中运行?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32403317/

相关文章:

go - 等待多个协程的结果

architecture - Java EE 类加载标准

arrays - unsafe.Pointer to []byte in Go

go - 为什么我不能在 golang 中将 main 添加到我的库中?

go - byte slice 自行更改

Java实用程序类的麻烦

php - mysql表中保存图片源的方法

go - golang channel 收不到

concurrency - 数据未通过 channel 发送

go - 如何处理 stderr?