博客:cbb777.fun
全平台账号: 安妮的心动录
github: https://github.com/anneheartrecord
下文中我说的可能对,也可能不对,鉴于笔者程度无限,请君自辨。有问题欢送大家找我探讨
基本概念
架构演变
用架构历史
1. 单体架构 堆机子 高耦合 一改变就须要重新部署 而且编译工夫很长,不容易拓展,不反对多语言技术栈
2. 分层架构 典型的有 MVC 和 MSC 架构 当访问量逐步增大,单体架构扛不住了,把单体我的项目进行垂直划分,耦合还是很大,我的项目之间的接口多为数据同步,比方不同我的项目之间的数据库同步。
架构简略,成本低开发周期短,通过垂直拆分之后原来的单体我的项目不至于太大,每一层能够用不同的技术,但还是不易拓展和保护
3.SOA 面向服务架构:当垂直架构的利用越来越多,就会呈现多个利用都依赖的业务组件,比方数据库,而且各个利用交互越来越频繁,此时就须要把局部通用的组件拆分独立解决,于是 SOA 面向服务架构诞生了,它带来了模块化开发、分布式拓展部署和服务接口定义等概念
实时 SOA 须要建设企业服务总线,内部利用通过总线调用服务,有以下特色:可从企业内部拜访、随时可用、标准化的服务接口等
长处:
- 曾经具备微服务的影子了,将反复的性能抽离进去,进步开发效率
- 缩小接口耦合
SOA 架构实用于大型软件服务企业对外提供服务的场景,并不适宜个别的业务场景,其服务的定义、注册和调用都须要繁琐的配置,业务总线的吞吐量决定了整个零碎的下限,因为整个零碎都是通过总线进行任务分配的。并且业务总线也容易导致系统崩掉、影响性能。
4. 微服务架构:
特点
1. 服务层齐全独立进去 并将服务层抽取为一个一个的微服务
2. 微服务遵循繁多准则
3. 微服务之间采纳 RESTful 等轻量协定通信
4. 微服务个别用容器技术部署 运行在本人的独立过程中
微服务架构下服务的拆分粒度更细,有利于资源重复利用,进步开发效率,采纳去中心化思维,更轻量级
毛病:如果服务实例过多,治理老本就会很大,不利于保护;服务之间相互依赖,可能造成简单的依赖链条,往往单个服务异样,其余服务也会受到影响,呈现服务雪崩效应。
微服务与 SOA 的区别:
微服务继承了 SOA 的泛滥长处和理念
SOA 更适宜与许多其余应用程序继承的大型简单企业应用程序环境,小型的利用并不适宜 SOA,微服务则更适宜于较小和良好的宰割式 web 业务零碎
微服务不再强调 SOA 架构中比拟重要的 ESB 企业服务总线,而是通过轻量级通信机制互相沟通
SOA 重视的是零碎继承,而微服务关注的则是齐全拆散,SOA 尝试采纳中心化治理来确保各个利用可能协同运作,微服务则尝试部署新性能,疾速无效地拓展开发团队,它着重于扩散治理、代码再利用和自动化执行。
微服务的劣势和劣势
微服务的劣势
1. 快:更重视 CI/CD 麻利开发、继续交付
2. 准:服务粒度小、服务质量精准可控
3. 狠:实用于互联网时代、产品迭代周期更短
微服务的劣势
1. 零碎的复杂性
2. 服务依赖治理
3. 数据的一致性保障
4. 测试更加艰巨
5. 对于 DevOps 等基础设施的高要求
如何划分微服务界线
如何进行服务划分?
1. 依照业务职能进行划分
由公司外部不同部门提供的只能。例如客户服务部门提供客户服
务的职能,财务部门提供财务相干的职能
2. 依照 DDD 的限界上下文划分
限界上下文是 DDD 中用来划分不同业务边界的元素
这里业务边界的含意是“解决不同业务问题”的问题域和对应的解决方案域
为了解决某种类型的业务问题,贴近畛域,也就是业务
CQRS 将零碎中的操作划分为两类,即【命令】Command 和【查问】Query
命令则是对会引起数据发生变化操作的总称,即咱们常说的新增、更新、删除的这些操作,都是命令。
而查问则和字面意思一样,即不会对数据产生变动的操作,只是依照某些条件查问数据。
CQRS 的核心思想是将两类不同的操作进行拆散,而后在两个独立的【服务】中实现。这里的服务个别指的是两个独立部署的利用,在某些非凡状况下,也能够部署在同一个利用内的不同接口上。
微服务的迭代
1. 第一代
2. 第二代
把那些服务监控、服务治理作为根底服务提供给咱们的业务
架构分层
外围组件
- API 网关
- 服务注册核心
- 配置核心
- 服务通信
- 服务治理
- 服务监控
net/rpc
RPC 呈现的起因
RPC 须要解决三个问题
1. 如何要确定要执行的函数?
在本地调用中,函数主体通过函数指针函数指定,而后调用 add 函数,编译器通过函数指针函数确定 add 函数在内存中的地位。
然而在 RPC 中,调用不能通过函数指针实现,因为他们的内存地址可能齐全不同。
因而,调用方和被调用方都须要保护一个 {fuction<->ID} 映射表,以确保调用正确的函数
2. 如何表白参数?
本地过程调用中传递的参数是通过堆栈构造实现的,然而 RPC 不能间接应用内存传递参数,因而参数或返回值须要在传输期间转换成字节流,反之亦然
3. 如何通过网络传输?
函数的调用方和被调用方通常是通过网络连接的,也就是说 function ID 和序列化字节流须要通过网络传输,因而,只有可能实现传输,调用方和被调用方就不受某个网络协议的限度。例如,一些 RPC 框架应用 TCP 协定,一些应用 HTTP。
也就是说,RPC 是一种软性的规定,而不是硬性的协定,只有能解决这三个问题的近程调用,咱们都称之为 ”RPC”
以往实现跨服务调用的时候,咱们会采纳 restful api 的形式,被调用方会对外提供一个 HTTP 接口,调用方按要求发动 HTTP 申请并接管 API 接口返回的响应数据。
上面是通过 HTTP API 实现本地调用的一个栗子
本地调用,通过 HTTP 的 API 的形式
server.go
// 定义参数和响应
type addParam struct {
X int `json:"x"`
Y int `json:"y"`
}
type addResult struct {
Code int `json:"code"`
Data int `json:"data"`
}
func add(x, y int) int {return x + y}
// addHandler 解析参数 + 调用 add+ 响应写回
func addHandler(w http.ResponseWriter, r *http.Request) {
// parse parameters
b, _ := ioutil.ReadAll(r.Body)
var param addParam
json.Unmarshal(b, ¶m)
// use the add func
ret := add(param.X, param.Y)
// return the response
respBytes, _ := json.Marshal(addResult{Code: 0, Data: ret})
w.Write(respBytes)
}
func main() {http.HandleFunc("/add", addHandler)
log.Fatal(http.ListenAndServe(":9090", nil))
}
client.go
type addParam struct {
X int `json:"x"`
Y int `json:"y"`
}
type addResult struct {
Code int `json:"code"`
Data int `json:"data"`
}
func main() {
url := "http://127.0.0.1:9090/add"
param := addParam{
X: 10,
Y: 20,
}
// marshal to json
paramBytes, _ := json.Marshal(param)
// call
resp, _ := http.Post(url, "application/json", bytes.NewReader(paramBytes))
defer resp.Body.Close()
respBytes, _ := ioutil.ReadAll(resp.Body)
var respData addResult
json.Unmarshal(respBytes, &respData)
fmt.Println(respData.Data)
}
而 RPC 调用则不须要如此,上面是一个应用 go 原生 net/rpc 库的栗子
service.go
type Args struct {X, Y int}
type ServiceA struct{}
// Add is an out method
// has two args and a return
// two params must be out
// and the return value must be error type
func (s *ServiceA) Add(args *Args, reply *int) error {
*reply = args.X + args.Y
return nil
}
server.go
func main() {
//new service instance
service := new(yunyuansheng.ServiceA)
//register rpc service
rpc.Register(service)
//botton on http
//rpc.HandleHTTP()
//botton on tcp
l, e := net.Listen("tcp", ":9091")
if e != nil {log.Fatal("listen error:", e)
}
//http.Serve(l, nil)
for {
// accpet the request and serve
conn, _ := l.Accept()
rpc.ServeConn(conn)
}
}
client.go
func main() {
// 因为服务端是 HTTP 申请 所以要建设 HTTP 连贯
client, err := rpc.Dial("tcp", "127.0.0.1:9091")
if err != nil {fmt.Println(err)
}
// 同步调用 Call
args := &yunyuansheng.Args{10, 20}
reply := new(int)
err = client.Call("ServiceA.Add", args, reply)
if err != nil {log.Fatal("ServiceA.Add error:", err)
}
fmt.Printf("ServiceA.Add %d+%d=%d\n", args.X, args.Y, *reply)
// 异步调用 Go
var reply2 int
divCall := client.Go("ServiceA.Add", args, &reply2, nil)
replyCall := <-divCall.Done //Done 是一个调用后果的告诉 有值了就阐明调用实现了
fmt.Println(replyCall.Error)
fmt.Println(reply2)
}
RPC 的最终目标:让调用近程办法更加简略,并且速度更快
Go 原生 net/rpc 库须要留神的几点
1. 能够反对很多种协定,包含但不限于 HTTP 和 TCP,如果应用 HTTP 的话,那么客户端就应用 DialHTTP,服务端通过 HandleHTTP 进行 HTTP 连贯的解决,应用 TCP 的话,客户端应用 Dial,服务端就应该 for 循环监听连贯,一旦有就解决连贯
2. 客户端反对同步调用和异步调用两种形式,对应的别离是 Call 和 Go
3. 暴露出的服务必须满足两个条件,两个参数,一个返回值,返回值必须要是 error 类型,第二个参数必须是指针
RPC 原理
- client 以本地调用形式调用服务
- client stub 接管到调用后负责将办法、参数等组装成可能进行网络传输的音讯体
- client stub 找到服务地址,并将服务发送到服务端
- server 接管到音讯之后,通过 server stub 对音讯进行解码
- server stub 依据解码的后果调用本地服务
- 本地服务执行并把音讯返回给 server stub
- server stub 将后果打包成可能进行网络传输的构造体,发送到音讯方
- client 收到音讯并进行解码,失去最终后果