Go中的复杂管道(第五部分)- 把所有东西整合在一起

85 阅读2分钟

把所有的东西放在一起

现在是时候把所有的点连接起来,建立最终的工具了。最重要的组件已经在前面的文章中涵盖了,但还缺少一些东西:最初的HTTP请求,意在用于下载gzip文件。

让我们先解决这个问题,然后我们就可以把所有的东西放在一起。

最低要求

所有与本帖相关的代码都在Github上,请随意浏览以了解更多细节,以下是运行本例的最低要求。

  • Go 1.14
  • PostgreSQL 12.3:理论上任何最近的版本都可以,README.md包括了用Docker运行的具体说明。

下载文件

为了通过HTTP下载文件,我们必须使用标准库,特别是net/http.Clientcompress/gzip.Reader ,这是因为我们期望下载的文件是一个gzipped文件。

对于这两个要求,下面的简短片段应该涵盖了。

 // XXX omiting error handling to keep code short
req, _ := http.NewRequest(http.MethodGet, "https://datasets.imdbws.com/name.basics.tsv.gz", nil)

client := &http.Client{
	Timeout: 10 * time.Minute, // XXX: use something reasonable
}

resp, _ := client.Do(req)
 defer resp.Body.Close()

gr, _ := gzip.NewReader(resp.Body)
defer gr.Close()

for {
	line, err := cr.ReadString('\n')
	if err == io.EOF {
		return
	}

   // XXX: do something with the read value!
}

连接所有的点

在这个整合过程中,最大最重要的事情是我们应该如何处理来自PostgreSQL的下游错误,特别是batcher ,它最终整合了在第二部分(分批存储值)中引入的一个叫做copyFromSourceMediator 的类型,这是与处理错误的想法更紧密。

这个变化的原因是实际的pgx 调用(也就是PostgreSQL)和我们的调用之间的延迟,实际上这意味着我们需要sync.Mutex 来同步处理向PostgreSQL发送消息和从上游接收消息的两个goroutine。

请看。

func (b *batcher) Copy(ctx context.Context, namesC <-chan name) <-chan error {
	outErrC := make(chan error)
	var mutex sync.Mutex
	var copyFromErr error

	copyFrom := func(batchNamesC <-chan name, batchErrC <-chan error) <-chan error {
		cpOutErrorC := make(chan error)

		go func() {
			defer close(cpOutErrorC)

			copier := newCopyFromSource(batchNamesC, batchErrC)

			_, err := b.conn.CopyFrom(ctx,
				pgx.Identifier{"names"},
				[]string{
					"nconst",
					"primary_name",
					"birth_year",
					"death_year",
					"primary_professions",
					"known_for_titles",
				},
				copier)

			if err != nil {
				mutex.Lock()
				copyFromErr = err
				mutex.Unlock()
			}
		}()

		return cpOutErrorC
	}

	go func() {
		batchErrC := make(chan error)
		batchNameC := make(chan name)

		cpOutErrorC := copyFrom(batchNameC, batchErrC)

		defer func() {
			close(batchErrC)
			close(batchNameC)
			close(outErrC)
		}()

		var index int64

		for {
			select {
			case n, open := <-namesC:
				if !open {
					return
				}

				mutex.Lock()
				if copyFromErr != nil {
					namesC = nil
					mutex.Unlock()
					outErrC <- copyFromErr
					return
				}
				mutex.Unlock()

				batchNameC <- n

				index++

				if index == b.size {
					close(batchErrC)
					close(batchNameC)

					if err := <-cpOutErrorC; err != nil {
						outErrC <- err
						return
					}

					batchErrC = make(chan error)
					batchNameC = make(chan name)

					cpOutErrorC = copyFrom(batchNameC, batchErrC)
					index = 0
				}
			case <-ctx.Done():
				if err := ctx.Err(); err != nil {
					batchErrC <- err
					outErrC <- err
					return
				}
			}
		}
	}()

	return outErrC
}

这段代码看起来很多,但真正重要的部分是在变量/函数copyFrom ,它仍然使用copyFromSource 来处理来自上游的事件,也使用一个突变来设置来自pgx的错误CopyFrom

下一步是什么?

这是这个系列的最后一篇文章,但我不认为这是结束,我将在未来跟进并改进现有的代码。我将回答(至少)以下两个问题。

  1. 如何提供处理状态?还剩多少事件?有多少事件被处理?
  2. 在失败的情况下如何恢复事件的处理?

我将改进这个实现,等待它。