Should things like an app's mailer system run in a separate channel as shown in this example?

huangapple go评论87阅读模式
英文:

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

问题

  1. 这是正确的方法吗?
  2. 它是否是线程安全的?如果不是,如何使其线程安全?
英文:

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 (
&quot;fmt&quot;
&quot;log&quot;
&quot;net/http&quot;
&quot;os&quot;
)
// 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(&quot;Sending email to:`%s`\nSubject: %s\n%s\n\n&quot;, message.To, message.Subject, message.Body)
}
// Messages returns the channel to which messages can be passed
func (m *Mailer) Messages() chan&lt;- Message {
cm := make(chan Message)
go func() {
msg := &lt;-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&#39;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{&quot;email@example.com&quot;, fmt.Sprintf(&quot;visited `%s`&quot;, r.URL.Path[1:]), &quot;Lorem ipsum&quot;}
mailer.Messages() &lt;- m
fmt.Fprintf(w, &quot;Sent out email with subject line `%s`\n&quot;, m.Subject)
}
func main() {
http.HandleFunc(&quot;/&quot;, Index)
port := os.Getenv(&quot;PORT&quot;)
if port == &quot;&quot; {
port = &quot;8080&quot;
}
if err := http.ListenAndServe(&quot;:&quot;+port, nil); err != nil {
log.Fatal(&quot;ListenAndServe: &quot;, 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

  1. Is this the right approach?
  2. 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(&quot;Sending email to:`%s`\nSubject: %s\n%s\n\n&quot;, message.To, message.Subject, message.Body)
}
}
var mailer *Mailer
func Index(w http.ResponseWriter, r *http.Request) {
m := Message{&quot;email@example.com&quot;, fmt.Sprintf(&quot;visited `%s`&quot;, r.URL.Path[1:]), &quot;Lorem ipsum&quot;}
mailer.Messages &lt;- m
fmt.Fprintf(w, &quot;Sent out email with subject line `%s`\n&quot;, m.Subject)
}
func main() {
mailer = &amp;Mailer{
// buffer up to 100 message to be sent before blocking
Messages: make(chan Message, 100),
}
// start the mailer send loop
go mailer.send()
...

huangapple
  • 本文由 发表于 2015年9月5日 01:11:14
  • 转载请务必保留本文链接:https://go.coder-hub.com/32403317.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定