把所有的东西放在一起
现在是时候把所有的点连接起来,建立最终的工具了。最重要的组件已经在前面的文章中涵盖了,但还缺少一些东西:最初的HTTP请求,意在用于下载gzip文件。
让我们先解决这个问题,然后我们就可以把所有的东西放在一起。
最低要求
所有与本帖相关的代码都在Github上,请随意浏览以了解更多细节,以下是运行本例的最低要求。
- Go 1.14
- PostgreSQL 12.3:理论上任何最近的版本都可以,README.md包括了用Docker运行的具体说明。
下载文件
为了通过HTTP下载文件,我们必须使用标准库,特别是net/http.Client 和compress/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 。
下一步是什么?
这是这个系列的最后一篇文章,但我不认为这是结束,我将在未来跟进并改进现有的代码。我将回答(至少)以下两个问题。
- 如何提供处理状态?还剩多少事件?有多少事件被处理?
- 在失败的情况下如何恢复事件的处理?
我将改进这个实现,等待它。