Go入门指南-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)

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

白小白的学习笔记

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值