go微服务系列(三) - 服务调用(http)
1. 关于服务调用
这里的服务调用,我们调用的可以是http api
也可以是gRPC
等。主要意思就是调用我们从consul
获取到的服务的API。
下面的所有示例以RESTful HTTP API
为例
2. 基本方式调用服务
我们在服务发现之后,肯定要调用发现之后的服务,这里的服务可以是http的RESTful API
也可以是RPC
服务等,这里以前面的定义的productService
的RESTful API
作为被调用者
其实就是用获取到的服务地址,调API
下面要演示的是使用标准库net/http
的httpclient
进行的比较原始的请求API的方法
被调用的API
- EndPoint
/v1/list
服务调用的代码
func main() {
// 1.连接到consul
cr := consul.NewRegistry(registry.Addrs("47.100.220.174:8500"))
// 2.根据service name获取对应的微服务列表
services, err := cr.GetService("productService")
if err != nil {
log.Fatal("cannot get service list")
}
// 3.使用random随机获取其中一个实例
next := selector.RoundRobin(services)
svc, err := next()
if err != nil {
log.Fatal("cannot get service")
}
fmt.Println("[测试输出]:", svc.Address)
// 4. 请求获取到的服务的API方法
resp, err := RequestApi(http.MethodGet, svc.Address, "/v1/list", nil)
if err != nil {
log.Fatal("request api failed")
}
fmt.Println("[请求API结果]:", resp)
}
// 简单封装一个请求api的方法
func RequestApi(method string, host string, path string, body io.Reader) (string, error) {
// 1.如果没有http开头就给它加一个
if !strings.HasPrefix(host, "http://") && !strings.HasPrefix(host, "https://") {
host = "http://" + host
}
// 2. 新建一个request
req, _ := http.NewRequest(method, host+path, body)
// 3. 新建httpclient,并且传入request
client := http.DefaultClient
res, err := client.Do(req)
if err != nil {
return "", err
}
defer res.Body.Close()
// 4. 获取请求结果
buff, err := ioutil.ReadAll(res.Body)
if err != nil {
return "", err
}
return string(buff), nil
}
如下可以调用成功:
3. 服务调用正确姿势(初步)
上面我们调用api的方式是没什么问题,但是有缺点就是
- 但是假如有多个微服务,每个微服务都会有很多重复的基础设施,go-micro就把这部分抽取出来,弄了一个plugin
按照官方的说法:
go-plugins使您可以交换基础设施结构,而不必重写所有代码。这样就可以在多个环境中运行相同的软件,而无需进行大量工作
查看go-plugins
的组成部分,client中有http api
3.1 服务端代码
服务端代码跟之前的差不太多
package main
import (
"github.com/gin-gonic/gin"
"github.com/micro/go-micro/registry"
"github.com/micro/go-micro/web"
"github.com/micro/go-plugins/registry/consul"
"gomicro-quickstart/product_service/model"
"net/http"
)
func main() {
// 添加consul地址
cr := consul.NewRegistry(registry.Addrs("127.0.0.1:8500"))
// 使用gin作为路由
router := gin.Default()
v1 := router.Group("v1")
{
v1.POST("list", func(c *gin.Context) {
var req ProdRequest
if err := c.Bind(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"data": "模型绑定失败",
})
c.Abort()
return
}
c.JSON(http.StatusOK, gin.H{
"data": model.NewProductList(req.Size),
})
})
}
server := web.NewService(
web.Name("ProductService"), // 当前微服务服务名
web.Registry(cr), // 注册到consul
web.Address(":8001"), // 端口
web.Metadata(map[string]string{"protocol": "http"}), // 元信息
web.Handler(router)) // 路由
_ = server.Init()
_ = server.Run()
}
type ProdRequest struct {
Size int `json:"size"`
}
下面是返回的model对象代码
package model
import "strconv"
type Product struct {
Id int
Name string
}
func NewProduct(id int, name string) *Product {
return &Product{
Id: id,
Name: name,
}
}
func NewProductList(count int) []*Product {
products := make([]*Product, 0)
for i := 0; i < count; i++ {
products = append(products, NewProduct(i+1, "productName"+strconv.Itoa(i+1)))
}
return products
}
3.2 客户端调用(重要)
这里使用了go-plugins
中client
下的http
的包,优点是
- 可以直接通过服务名来调用服务,省去了
getService
的步骤
package main
import (
"context"
"fmt"
"log"
"github.com/micro/go-micro/client"
"github.com/micro/go-micro/client/selector"
"github.com/micro/go-micro/registry"
"github.com/micro/go-plugins/client/http"
"github.com/micro/go-plugins/registry/consul"
)
func main() {
// 1. 注册consul地址
cr := consul.NewRegistry(registry.Addrs("47.100.220.174:8500"))
// 2. 实例化selector
mySelector := selector.NewSelector(
selector.Registry(cr), // 传入上面的consul
selector.SetStrategy(selector.RoundRobin), // 指定获取实例的算法
)
// 3. 请求服务
resp, err := callByGoPlugin(mySelector)
if err != nil {
log.Fatal("request API failed", err)
}
fmt.Printf("[服务调用结果]:\r\n %v", resp)
}
func callByGoPlugin(s selector.Selector) (map[string]interface{}, error) {
// 1. 调用`go-plugins/client/http`包的函数获取它们提供的httpClient
gopluginClient := http.NewClient(
client.Selector(s), // 传入上面的selector
client.ContentType("application/json"), // 指定contentType
)
// 2. 新建请求对象,传入: (1)服务名 (2)endpoint (3)请求参数
req := gopluginClient.NewRequest("ProductService", "/v1/list", map[string]interface{}{"size": 6})
// 3. 新建响应对象,并call请求,获取响应
var resp map[string]interface{}
err := gopluginClient.Call(context.Background(), req, &resp)
if err != nil {
return nil, err
}
return resp, nil
}
客户端调用结果:
go微服务系列(三) - 服务调用(http)的更多相关文章
- 玩转Windows服务系列——Windows服务小技巧
伴随着研究Windows服务,逐渐掌握了一些小技巧,现在与大家分享一下. 将Windows服务转变为控制台程序 由于默认的Windows服务程序,编译后为Win32的窗口程序.我们在程序启动或运行过程 ...
- 玩转Windows服务系列——Windows服务小技巧
原文:玩转Windows服务系列——Windows服务小技巧 伴随着研究Windows服务,逐渐掌握了一些小技巧,现在与大家分享一下. 将Windows服务转变为控制台程序 由于默认的Windows服 ...
- 玩转Windows服务系列——Windows服务启动超时时间
最近有客户反映,机房出现断电情况,服务器的系统重新启动后,数据库服务自启动失败.第一次遇到这种情况,为了查看是不是断电情况导致数据库文件损坏,从客户的服务器拿到数据库的日志,进行分析. 数据库工作机制 ...
- 玩转华为物联网IoTDA服务系列三-自动售货机销售分析场景示例
场景简介 通过收集自动售货机系统的销售数据,EI数据分析售货销量状况. 该场景主要描述的是设备可以通过MQTT协议与物联网平台进行交互,应用侧可以到物联网平台订阅设备侧变化的通知,用户可以在控制台或通 ...
- go微服务系列(二) - 服务注册/服务发现
目录 1. 服务注册 1.1 代码演示 1.2 在go run的时候传入服务注册的参数 2. 服务发现均衡负载 2.1 均衡负载算法 2.2 服务发现均衡负载的演示 1. 服务注册 1.1 代码演示 ...
- 玩转Windows服务系列——Debug、Release版本的注册和卸载,及其原理
Windows服务Debug版本 注册 Services.exe -regserver 卸载 Services.exe -unregserver Windows服务Release版本 注册 Servi ...
- 玩转Windows服务系列——Debug、Release版本的注册和卸载,及其原理
原文:玩转Windows服务系列——Debug.Release版本的注册和卸载,及其原理 Windows服务Debug版本 注册 Services.exe -regserver 卸载 Services ...
- 玩转Windows服务系列——给Windows服务添加COM接口
当我们运行一个Windows服务的时候,一般情况下,我们会选择以非窗口或者非控制台的方式运行,这样,它就只是一个后台程序,没有界面供我们进行交互. 那么当我们想与Windows服务进行实时交互的时候, ...
- 玩转Windows服务系列——使用Boost.Application快速构建Windows服务
玩转Windows服务系列——创建Windows服务一文中,介绍了如何快速使用VS构建一个Windows服务.Debug.Release版本的注册和卸载,及其原理和服务运行.停止流程浅析分别介绍了Wi ...
随机推荐
- Go的100天之旅-06数组和Slice
目录 数组 Slice 数组 Go的数组和其它语言基本上一样,是长度固定的特定类型元素组成的序列,这基本上是所有语言数组的特性.和其它语言相比差异主要在声明和初始化的写法上,下面是简单声明一个数组: ...
- Git 推送到远程仓库
github:https://github.com/ 国内的:https://gitee.com/ (和Github非常相似的) 一.Http方式进行推送 右击同步,配置远端,将URL替换成远程仓库的 ...
- PHP入门之流程控制
前言 上一篇文章对PHP的一些类型和运算符进行了简单的讲解.PHP入门之类型与运算符 这篇简单讲解一下流程控制.结尾有实例,实例内容是用switch分支和for循环分别做一个计算器和金字塔. 分支控制 ...
- rpm -ivh vsftpd-3.0.2-22.el7.x86_64.rpm出现error: open of vsftpd-3.0.2-22.el7.x86_64.rpm failed: No such file or directory的解决方法
情况一: 出现如图问题, 我当时的问题是通过安装rpmbuild工具软件解决的 以前制作rpm时,没有遇到过这个问题,几经搜索也没有解决.后来发现当前的centos没有安装 rpmbuild 工具软件 ...
- iframe和DataForm
一.iframe使用 iframe在一个页面中,相当于整个window窗口的子窗口,可通过页面的元素结构查看. <div> <p>学习iframe</p> < ...
- HttpServletRequest、HttpServletResponse
doGet()/doPost()方法都有两个参数,一个为代表请求的request,另一个代表响应response. request是获取前台传递的内容,response是反馈给前台数据 HttpSer ...
- 总结HashMap实现原理分析
一.底层数据结构在JDK1.6,JDK1.7中,HashMap采用位桶+链表实现,即使用链表处理冲突,同一hash值的键值对会被放在同一个位桶里,当桶中元素较多时,通过key值查找的效率较低. 而JD ...
- 阿里云ecs轻量级服务器node镜像部署
这个是自带安装pm2,nginx,node,mongodb的环境的,目录在控制台有给出, server端的配置按照开发手册去操作即可. 而静态的页面.需要修改nginx的配置文件,找到nginx的ng ...
- 两数相加(B站看视频总结)
''' 两数相加: 给出两个 非空 的链表用来表示两个非负的整数 各自的位数是按照逆序的方式存储的 每一个节点只能保存 一位数 示例: 输入:(2->4->3) + (5->6-&g ...
- Python os.lseek() 方法
概述 os.lseek() 方法用于设置文件描述符 fd 当前位置为 pos, how 方式修改.高佣联盟 www.cgewang.com 在Unix,Windows中有效. 语法 lseek()方法 ...