单体式和微服务
单体式架构服务
------过往大家熟悉的服务器
特性:
1.复杂性随着开发越来越高,遇到问题解决困难。
2. 技术债务逐渐上升
3. 耦合度高,维护成本大
出现bug,不容易排查
解决旧bug,会出新bug
4. 持续交付时间较长
5. 技术选型成本高,风险大。
6. 扩展性较差
1.垂直扩展:通过增加单个系统程序的负荷来实现扩展。
2.水平扩展:通过增加更多的系统成员来实现扩展。
微服务
· 优点
1.职责单一
2.轻量级通信
3.独立性
4.迭代开发
· 缺点
- 运维成本高
- 分布式复杂度
- 接口成本高
- 重复性劳动
- 业务分离困难
单体式和微服务对比
RPC协议
什么是RPC
Remote Procedure Call Protocol – 远程过程调用协议!
IPC:进程间通信
RPC:远程进程通信 — 应用层协议(http协议同层),底层使用TCP实现。
回顾:
OSI7层模型架构:物理层,数据链路层,网络层,传输层,会话层,表示层,应用层
TCP/IP4层架构:链路层,网络层,传输层,应用层
理解RPC:
像调用本地函数一样,去调用远程函数。
通过rpc协议,传递:函数名,函数参数,达到在本地,调用远端函数,得返回值到本地的目标
为什么微服务使用RPC:
1.每个服务都被封装成进程,彼此“独立”。
2.进程和进程之间,可以使用不同的语言实现。
RPC入门使用
远程 — 网络!!
回顾:Go语言 一般性 网络socket通信
server端:
net.Listen() – listener 创建监听器
listener.Accpet() – conn 启动监听,建立连接
conn.read()
conn.write()
defer conn.Close() / listener.Close()
client端:
net.Dial() – conn
conn.Write()
conn.Read()
defer conn.Close()
RPC使用的步骤
-------服务端
1.注册RPC服务对象。给对象绑定方法(1.定义类,2.绑定类方法)
rpc.RegisterName("服务名",回调对象)
- 创建监听器
listener,err := net.Listen()
- 建立连接
conn,err := listener.Accept()
- 将连接 绑定rpc服务
rpc.ServeConn(conn)
------ 客户端:
1.用rpc连接服务器。
conn,err := rpc.Dial()
2.调用远程函数。
conn.Call("服务名.方法名",传入参数,传出参数)
RPC相关函数
1.注册rpc服务
func (server *Server) RegisterName(name string,rcvr interface{}) error 参1:服务名。字符串类型。 参2:对应rpc对象。该对象绑定方法要满足如下条件: 1.方法必须是导出的----包外可见,首字母大写。 2.方法必须有两个参数,都是导出类型,内建类型。 3.方法的第二个参数必须是“指针” (传出参数) 4.方法只有一个 error接口类型的返回值。 举例: type World stuct{ } func(this *World) HelloWorld(name string,resp *string) error{ } rpc.RegisterName("服务名",new(World))
2.绑定rpc服务
func (server *Server) ServerConn(conn io.ReadWriteCloser) conn:成功建立好连接的 socket -- conn
3.调用远程函数:
func (client *Client) Call(serviceMethod string,args interface{},reply interface{}) error serviceMethod:“服务器.方法名” args:传入参数,方法需要的数据。 reply:传出参数。定义var变量,&变量名 完成传参。
编码实现
server端
package main import ( "net/rpc" "fmt" "net" ) // 定义类对象 type World struct { } // 绑定类方法 func (this *World) HelloWorld (name string,resp *string) error { *resp = name + "你好!" return nil } func main(){ // 1.注册RPC服务,绑定对象方法 err := rpc.RegisterName("hello",new(World)) if err != nil{ fmt.Println("注册 rpc 服务失败!",err) return } // 2.设置监听 listener,err := net.Listen("tcp","127.0.0.1:8802") if err != nil{ fmt.Println("net.Listen err:",err) return } defer listener.Close() fmt.Println("开始监听...") // 3.建立连接 conn,err := listener.Accept() if err != nil{ fmt.Println("Accpet() err:",err) return } defer conn.Close() fmt.Println("连接成功....") // 4. 绑定服务 rpc.ServeConn(conn) }
client端
package main import ( "net/rpc" "fmt" ) func main(){ // 1.用rpc连接服务器 --Dial() conn,err := rpc.Dial("tcp","127.0.0.1:8802") if err != nil { fmt.Println("Dial err:",err) return } defer conn.Close() // 2.调用远程函数 var reply string // 接收返回值 --- 传出参数 err = conn.Call("hello.HelloWorld","李白",&reply) if err != nil { fmt.Println("Call err:",err) return } fmt.Println(reply) }
json版rpc
使用 nc -l 127.0.0.1 8800 充当服务器
2_client.go充当客户端,发起通信。 --乱码
因为,rpc使用了go语言特有的序列化gob,其他编程语言不能解析
使用通用的序列化反序列化。 --json,protobuf
修改客户端
修改客户端,使用jsonrpc:
conn,err := jsonrpc.Dial("tcp","127.0.0.1:8800")
修改服务器端
修改服务器端,使用jsonrpc:
jsonrpc.ServerConn(conn)
如果,绑定方法返回值的error不为空?无论传出参数是否有值,服务端都不会返回数据。
rpc封装
3_design.go
package main import ( "net/rpc/jsonrpc" "net/rpc" ) // 要求,服务端在注册rpc对象时,能让编译期检查出 注册对象是否合法 // 创建接口,在接口中定义方法原型 type MyInterface interface{ HelloWorld(string,*string) error } // 调用该方法,需要给 i 传参,参数应该是实现了 HelloWorld 方法的类对象 func RegisterService(i MyInterface) { rpc.RegisterName("hello",i) } //-------------------客户端调用 // 定义类 type Myclient struct { c *rpc.Client } // 由于使用了c调用Call,因此需要初始化c func InitClient(addr string) Myclient { conn,_ := jsonrpc.Dial("tcp",addr) return Myclient{c:conn} } // 实现函数,原型参照上面的Interface来实现 func (this *Myclient) HelloWorld (a string,b *string) error { // 参数1,参照上面的 Interface,RegisterName 而来,a:传入参数 b:传出参数 return this.c.Call("hello.HelloWorld",a,b) }
1_server.go
package main import ( // "net/rpc" "net/rpc/jsonrpc" "fmt" "net" ) // 定义类对象 type World struct { } // 绑定类方法 func (this *World) HelloWorld (name string,resp *string) error { *resp = name + "你好!" return nil } func main(){ // 1.注册RPC服务,绑定对象方法 RegisterService(new(World)) // err := rpc.RegisterName("hello",new(World)) // if err != nil{ // fmt.Println("注册 rpc 服务失败!",err) // return // } // 2.设置监听 listener,err := net.Listen("tcp","127.0.0.1:8803") // listener,err := InitClient("127.0.0.1:8803") if err != nil{ fmt.Println("net.Listen err:",err) return } defer listener.Close() fmt.Println("开始监听...") // 3.建立连接 conn,err := listener.Accept() if err != nil{ fmt.Println("Accpet() err:",err) return } defer conn.Close() fmt.Println("连接成功....") // 4. 绑定服务 // rpc.ServeConn(conn) jsonrpc.ServeConn(conn) }
2_client.go
package main import ( "net/rpc" "fmt" ) func main01(){ // 1.用rpc连接服务器 --Dial() conn,err := rpc.Dial("tcp","127.0.0.1:8802") if err != nil { fmt.Println("Dial err:",err) return } defer conn.Close() // 2.调用远程函数 var reply string // 接收返回值 --- 传出参数 err = conn.Call("hello.HelloWorld","李白",&reply) if err != nil { fmt.Println("Call err:",err) return } fmt.Println(reply) } // 结合 3_design.go 测试 func main(){ myClient := InitClient("127.0.0.1:8803") var resp string err := myClient.HelloWorld("杜甫",&resp) if err != nil{ fmt.Println("HelloWorld err:",err) return } fmt.Println(resp,err) }
ProtoBuf认识与使用
protobuf简介
Protobuf是Protocol Buffers的简称,它是Google公司开发的一种描述语言,是一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化,或者说序列化。他很适合做数据存储或者RPC数据交换格式。
可以用于通信协议,数据存储等领域的语言无关,平台无关,可扩展的序列化结构数据格式。目前提供了C++,Java,Python三种语言的API。
Protobuf刚开源是的定位类似于xml,json等数据描述语言,通过附带工具生成代码并实现将结构化数据序列化的功能,这里我们更关注的是Protobuf作为接口规范的描述语言,可以作为设计安全的跨语言RPC接口的基础工具。
需要了解两点
- protobuf是类似于json一样的数据描述语言(数据格式)
- protobuf非常适合于RPC数据交换格式
接着我们来看一下protobuf的优势和劣势:
优势:
1.序列化后面积比Json和Xml很小,适合网络传输
2.支持跨平台多语言
3.消息格式升级和兼容性还不错
4.序列化反序列化速度很快,快于json的处理速度
劣势:
- 应用不够广(相比xml和json)
- 二进制格式导致可读性差
- 缺乏自描述
protobuf的安装
1.下载protobuf
方法一:===》git clone https://github.com/protocolbuffers/protobuf.git 方法二:===》 或者将准备好的压缩包进行拖入 解压到 $GOPATH/src/github.com/protocolbuffers/下面 Unizip protobuf.zip
- 安装(Ubuntu)
3.测试protobuf编译工具
protoc -h
如果正常输出 相关指令 没有报任何error,为安装成功
4.安装protobuf的go语言插件
感谢大家观看,我们下次见