19.8. 多台机器上的多线程

未匹配的标注

目前为止, goto 作为单个进程运行,即使使用协程,在一台机器上运行的单个进程也只能提供这么多并发请求。一个 URL 缩短服务通常更多的是重定向(使用 Get() 读取),而不是添加(使用 Put 写入)。因此,我们可以创建任意数量的只读从服务器用于缓存 Get 请求,并将 Puts 传递给主服务器,就像下面这个示例图:

file

多个从服务器进程要运行一个网络中另一台计算上的 goto 应用的主实例,它们必须能够互相通信。Go 的 rpc 包提供了一个通过网络连接进行函数调用的便利的方法,使 URLStore 成为一个 RPC 服务(我们已经在 章节 15.9 中详细的讨论过),这些从服务器进程将处理 Get 请求去提供长 urls 。当一个新的长 url 需要转换成一个短 url (使用 Put() 方法)的时候,它们通过 rpc 连接将任务委托给主服务器进程;所以必须只有主服务器可以写入数据。

到目前为止, URLStore 的 Get()Put() 方法都有签名:


func (s *URLStore) Get(key string) string

func (s *URLStore) Put(url string) string

RPC 只能通过这种形式(t是T类型的值)的方法工作:


func (t T) Name(args *ArgType, reply *ReplyType) error

为了使 URLStore 成为一个 RPC 服务,我们需要去修改 Put 与 Get 方法,以便它们匹配这个函数的签名。这是结果:


func (s *URLStore) Get(key, url *string) error

func (s *URLStore) Put(url, key *string) error

Get() 代码变成:


func (s *URLStore) Get(key, url *string) error {

    s.mu.RLock()

    defer s.mu.RUnlock()

    if u, ok := s.urls[*key]; ok {

        *url = u

        return nil

    }

    return errors.New("key not found")

}

现在,因为 key 和 url 是指针,我们必须在它们前面添加一个 * 来获取它们的值,就像 *keyu 是一个值,我们可以将它分配给指针,这样: *url = u

Put() 的代码也是一样:


func (s *URLStore) Put(url, key *string) error {

    for {

        *key = genKey(s.Count())

        if err := s.Set(key, url); err == nil {

            break

        }

    }

    if s.save != nil {

        s.save <- record{*key, *url}

    }

    return nil

}

因为 Put() 调用 Set() ,后者也必须去适配 key 和 url 现在是指针的情况,并且它必须返回一个错误而不是布尔值:


func (s *URLStore) Set(key, url *string) error {

    s.mu.Lock()

    defer s.mu.Unlock()

    if _, present := s.urls[*key]; present {

        return errors.New("key already exists")

    }

    s.urls[*key] = *url

    return nil

}

因为同样的原因,当我们从 load() 调用 Set() 的时候,这个调用也必须被适配:


s.Set(&r.Key, &r.URL)

我们还必须得修改 HTTP 处理程序,用来适配 URLStore 的修改。Redirect 处理器现在返回的是由 URLStore 提供的错误字符串:


func Redirect(w http.ResponseWriter, r *http.Request) {

    key := r.URL.Path[1:]

    var url string

    if err := store.Get(&key, &url); err != nil {

        http.Error(w, err.Error(), http.StatusInternalServerError)

        return

    }

    http.Redirect(w, r, url, http.StatusFound)

}

Add 处理器的变化大致相同:


func Add(w http.ResponseWriter, r *http.Request) {

    url := r.FormValue("url")

    if url == "" {

        fmt.Fprint(w, AddForm)

        return

    }

    var key string

    if err := store.Put(&url, &key); err != nil {

        http.Error(w, err.Error(), http.StatusInternalServerError)

        return

    }

    fmt.Fprintf(w, "http://%s/%s", *hostname, key)

}

为了使我们的程序更灵活,就我们在上一章中所做的,我们可以添加一个命令行参数 flag ,用来在 main() 中启动 RPC 服务。


var rpcEnabled = flag.Bool("rpc", false, "enable RPC server")

为了使 rpc 工作,我们必须通过 rpc 包去注册 URLStore ,并通过 HandleHTTP 去设置 RPC-over-HTTP 处理器,就像这样:


func main() {

    flag.Parse()

    store = NewURLStore(*dataFile)

    if *rpcEnabled { // flag has been set

        rpc.RegisterName("Store", store)

        rpc.HandleHTTP()

    }

    ... (像以前一样设置 http)

}

本文章首发在 LearnKu.com 网站上。

本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

原文地址:https://learnku.com/docs/the-way-to-go/1...

译文地址:https://learnku.com/docs/the-way-to-go/1...

上一篇 下一篇
贡献者:1
讨论数量: 0
发起讨论 只看当前版本


暂无话题~