英文:
Should things like an app's mailer system run in a separate channel as shown in this example?
问题
想象一个具有相当多不同路由的网络服务。其中一些路由会触发向用户发送事务性电子邮件。每次请求想要发送邮件时,初始化一个mailer实例(例如使用github.com/aws/aws-sdk-go/service/sns
)似乎很奇怪。
相反,我认为应该有一个mailer实例,并且所有操作都在一个单独的通道上进行,请求将消息发布到该通道。
示例
我创建了一个简单的示例来说明这个问题。全局的Mailer
实例在配置一次后,Index
处理程序请求一个通道并传递一个Message
。
package main
import (
"fmt"
"log"
"net/http"
"os"
)
// Message是用于传递通道的自定义类型
type Message struct {
To string
Subject string
Body string
}
// Mailer负责发送电子邮件
type Mailer struct{}
// send发送电子邮件
func (m *Mailer) send(message Message) {
fmt.Printf("发送邮件给:`%s`\n主题: %s\n%s\n\n", message.To, message.Subject, message.Body)
}
// Messages返回可以传递消息的通道
func (m *Mailer) Messages() chan<- Message {
cm := make(chan Message)
go func() {
msg := <-cm
m.send(msg)
close(cm)
}()
return cm
}
// mailer是此示例中的全局变量,可能是某种应用程序上下文的一部分,可以从任何处理程序访问。
//
// 请注意,mailer不是处理程序范围的。
var mailer = Mailer{} // 这是否是线程安全的?
// Index处理程序
func Index(w http.ResponseWriter, r *http.Request) {
m := Message{"email@example.com", fmt.Sprintf("访问了 `%s`", r.URL.Path[1:]), "Lorem ipsum"}
mailer.Messages() <- m
fmt.Fprintf(w, "发送了主题为 `%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
将呈现...
发送了主题为
访问了
hello-world`` 的电子邮件
... 并记录
发送邮件给
email@example.com
:
访问了hello-world
Lorem ipsum
问题
- 这是正确的方法吗?
- 它是否是线程安全的?如果不是,如何使其线程安全?
英文:
Imagine a web service with a fair amount of different routes. Some of them trigger transactional emails being sent to the user. It seems weird to initialise a mailer instance, for instance something using github.com/aws/aws-sdk-go/service/sns
every time a request wants to send something.
Instead I'd assume there's one mailer instance and everything happens on a separate channel to which a message
gets posted to.
Example
I created a simple example illustrating the problem. A global Mailer
instance gets configured once, the Index
handler asks for a channel and passes a 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)
}
}
Output
Visting http://localhost:8080/hello-world
will render…
> Sent out email with subject line `visited `hello-world``
… and log
> sending email to `email@example.com`:
> visited `hello-world`
> Lorem ipsum
Questions
- Is this the right approach?
- Is it thread-safe – if not how to get it thread-safe?
答案1
得分: 1
你在这个例子中并没有做任何实际操作,只是通过通道传递消息,但是通过通道传递消息始终是安全的——通道是该语言中的基本并发原语之一。根据send
实际执行的操作,你可能会遇到竞态条件的可能性。另一种处理方式是让send
从一个单独的通道接收消息。
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()
...
英文:
You're not really doing anything in this example, but passing message over channels is always safe -- channels are one of the basic concurrency primitives in the language. You are leaving yourself open to the possibility of race conditions, depending on what send
actually ends up doing. Another way to handle this is to have send
receive from a single 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()
...
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论