Files
the-way-to-go_ZH_CN/eBook/19.8.md
2019-08-05 23:31:59 -07:00

132 lines
4.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 版本 5 - 分布式程序
第 5 个版本的代码 *goto_v5*19.8 节和 19.9 节讨论)见 [goto_v5](examples/chapter_19/goto_v5)。该版本仍然基于 `gob` 存储,但很容易调整为使用 json正如版本 4 演示的那样。
# 19.8 多服务器处理架构
目前为止 goto 以单线程运行,但即使用协程,在一台机器上运行的单一进程,也只能为一定数量的并发请求提供服务。一个缩短网址服务,相对于 `Add`(用 `Put()` 写入),通常 `Redirect` 服务(用 `Get()` 读取要多得多。因此我们应该可以创建任意数量的只读的从slave服务器提供服务并缓存 `Get` 方法调用的结果,将 `Put` 请求转发给主master服务器类似如下架构
![图 19.5 跨越主从计算机的分布式负载](images/19.8_fig19.5.jpg?raw=true)
对于 slave 进程,要在网络上运行 goto 应用的一个 master 节点实例它们必须能相互通信。Go 的 `rpc` 包为跨越网络发起函数调用提供了便捷的途径。这里将把 `URLStore` 变为 RPC 服务([15.9 节](15.9.md) 详细讨论了 rpc 包。slave 进程将应对 `Get` 请求以交付长 URL。当一个长 URL 要被转换为缩短版本(使用 `Put` 方法)时,它们通过 rpc 连接把任务委托给 master 进程,因此只有 master 节点会写入数据文件。
截至目前 `URLStore` 上基本的 `Get()``Put()` 方法具有如下签名:
```go
func (s *URLStore) Get(key string) string
func (s *URLStore) Put(url string) string
```
而 RPC 调用仅能使用如下形式的方法t 是 T 类型的值):
```go
func (t T) Name(args *ArgType, reply *ReplyType) error
```
要使 `URLStore` 成为 RPC 服务,需要修改 `Put``Get` 方法使它们符合上述函数签名。以下是修改后的签名:
```go
func (s *URLStore) Get(key, url *string) error
func (s *URLStore) Put(url, key *string) error
```
`Get()` 代码变更为:
```go
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")
}
```
现在,键和长 URL 都变成了指针,必须加上前缀 `*` 来取得它们的值,例如 `*key` 这种形式。`u` 是一个值,可以用 `*url = u` 来将其赋值给指针。
接着对 `Put()` 代码做同样的改动:
```go
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` 参数现在是指针类型,还必须返回 `error` 取代 `boolean`
```go
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()` 时,也必须做调整:
```go
s.Set(&r.Key, &r.URL)
```
还必须修改 HTTP 处理函数以适应 `URLStore` 上的更改。`Redirect` 处理函数现在返回 `URLStore` 给出错误的字符串形式:
```go
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` 处理函数也以基本相同的方式修改:
```go
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 服务器:
```go
var rpcEnabled = flag.Bool("rpc", false, "enable RPC server")
```
要使 RPC 工作,还要用 `rpc` 包来注册 `URLStore`,并用 `HandleHTTP` 创建基于 HTTP 的 RPC 处理器:
```go
func main() {
flag.Parse()
store = NewURLStore(*dataFile)
if *rpcEnabled { // flag has been set
rpc.RegisterName("Store", store)
rpc.HandleHTTP()
}
... (set up http like before)
}
```
## 链接
- [目录](directory.md)
- 上一节:[以 json 格式存储](19.7.md)
- 下一节:[使用代理缓存](19.9.md)