19.9. 使用 ProxyStore

未匹配的标注

现在,我们已经有了可以用于 RPC 服务的 URLStore , 我们可以构建另一种类型来代表 RPC 客户端,并将发送请求到 RPC 服务器端;我们叫它 ProxyStore :


type ProxyStore struct {

    client *rpc.Client

}

一个 RPC 客户端必须使用 DialHTTP() 方法去连接一个 RPC 服务器端,所以我们将它合并到我们的 ProxyStore 对象的 NewProxyStore 函数中:


func NewProxyStore(addr string) *ProxyStore {

    client, err := rpc.DialHTTP("tcp", addr)

    if err != nil {

        log.Println("Error constructing ProxyStore:", err)

    }

    return &ProxyStore{client: client}

}

这个 ProxyStore 有 Get 和 Put 方法,可以在 RPC 客户端调用这些方法将请求直接传递给 RPC 服务器端:


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

    return s.client.Call("Store.Get", key, url)

}

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

    return s.client.Call("Store.Put", url, key)

}

缓存 ProxyStore :

但是,如果从服务器只是简单的将工作委托给主服务器,这样做没有任何意义! 我们希望从服务要去处理 Get 请求。为了做到这一点,从服务器必须有一个带有 map 的 URLStore 的副本(一个缓存)。所以我们扩展下 ProxyStore ,在它里面定义一个 URLStore :


type ProxyStore struct {

    urls *URLStore

    client *rpc.Client

}

并且 NewProxyStore 也必须被更改:


func NewProxyStore(addr string) *ProxyStore {

    client, err := rpc.DialHTTP("tcp", addr)

    if err != nil {

        log.Println("ProxyStore:", err)

    }

    return &ProxyStore{urls: NewURLStore(""), client: client}

}

我们必须修改 URLStore ,以便在给他一个空的 filename 时不去尝试写入或读取磁盘:


func NewURLStore(filename string) *URLStore {

    s := &URLStore{urls: make(map[string]string)}

    if filename != "" {

        s.save = make(chan record, saveQueueLength)

        if err := s.load(filename); err != nil {

            log.Println("Error loading URLStore: ", err)

        }

        go s.saveLoop(filename)

    }

    return s

}

我们的 Get 方法需要去扩展: 它应该首先检查缓存中是否有 key 。如果有, Get 返回缓存中的结果。如果没有,它应该进行 RPC 调用,并将结果更新到它的本地缓存:


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

    if err := s.urls.Get(key, url); err == nil { // 在本地 map 中找到 url

        return nil

    }

    // 本地 map 中没有找到 url ,运行 RPC 调用:

    if err := s.client.Call("Store.Get", key, url); err != nil {

        return err

    }

    s.urls.Set(key, url)

    return nil

}

同样的, Put 方法在成功执行 RPC 调用 Put 之后,只需要更新本地缓存:


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

    if err := s.client.Call("Store.Put", url, key); err != nil {

        return err

    }

    s.urls.Set(key, url)

    return nil

}

总结一下: 所有的从服务器都使用 ProxyStore , 只有主服务器使用 URLStore 。但是我们创建它们的方式看起来非常相似: 它们都实现了使用相同签名的 Get 和 Put 方法,所以我们能定义一个接口 Store 来归纳它们的行为:


type Store interface {

    Put(url, key *string) error

    Get(key, url *string) error

}

现在我们的全局变量 store 的类型可以是 Store 类型: var store Store

最终我们调整我们的 main() 函数,以便启动一个从服务器或者一个主服务器(并且我们只能这样,因为 store 现在是一个 Store 类型的接口!)。

为此,我们添加一个新的命令行标志 masterAddr ,它没有默认值(也就是空字符串)。


var masterAddr = flag.String("master", "", "RPC master address")

如果给出一个主服务器地址,我们启动一个从服务器进程,并且创建一个新的 ProxyStore;否则,我们启动一个主服务器进程并且创建一个新的 URLStore :


func main() {

    flag.Parse()

    if *masterAddr != "" { // 如果主服务器地址不为空,我们是一个从服务器

        store = NewProxyStore(*masterAddr)

    } else {

    // 我们是主服务器

        store = NewURLStore(*dataFile)

    }

    ...

}

通过这种方式,我们启动了 ProxyStore 来代替 URLStore 去使用 web 前端。

其余的前端代码会像以前一样执行,它不需要去了解 Store 接口。将只有主服务器可以向数据文件写入数据。

现在,我们可以启动一个主服务器和多个从服务器,并且对这些从服务器进行压力测试。

编译这个第 5 版的代码或者使用现有的可执行文件(译者注:示例代码中没有,自己编译执行)。

要测试它,首先要在命令行下启动主服务器:


./goto -http=:8081 -rpc=true

(or goto replacing ./goto on Windows)

指定了两个参数: 在端口 8081 上的主服务器的监听地址、启用 RPC 。

启动一个从服务器: ./goto -master=127.0.0.1:8081

它收到了主服务器的地址,并将在 8080 端口上接收客户端请求。

在示例代码中包含了下面这个 shell 脚本: demo.sh ,它可以像 Unix 系统一样自动启动。


#!/bin/sh

gomake

./goto -http=:8081 -rpc=true &

master_pid=$!

sleep 1

./goto -master=127.0.0.1:8081 &

slave_pid=$!

echo "Running master on :8081, slave on :8080."

echo "Visit: http://localhost:8080/add"

echo "Press enter to shut down"

read

kill $master_pid

kill $slave_pid

要在 Windows 下测试, 启动一个 MINGW shell 并启动主服务器,然后每一个从服务器启动一个新的 MINGW shell 并启动从服务器的进程。

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

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

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

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

上一篇 下一篇
讨论数量: 0
发起讨论 查看所有版本


暂无话题~