博客: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 收到音讯并进行解码,失去最终后果