目前为止, goto 作为单个进程运行,即使使用协程,在一台机器上运行的单个进程也只能提供这么多并发请求。一个 URL 缩短服务通常更多的是重定向(使用 Get()
读取),而不是添加(使用 Put
写入)。因此,我们可以创建任意数量的只读从服务器用于缓存 Get 请求,并将 Puts 传递给主服务器,就像下面这个示例图:
多个从服务器进程要运行一个网络中另一台计算上的 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 是指针,我们必须在它们前面添加一个 *
来获取它们的值,就像 *key
;u
是一个值,我们可以将它分配给指针,这样: *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)
}