1.认识微服务架构
微服务诞生背景
- 互联网行业的快速发展,需求变化快,用户数量变化快
- 敏捷开发深入人心,用最小的代价,做最快的迭代,频繁修改,测试,上线
- 容器技术的成熟,是微服务的技术基础
微服务架构的特点
- 将系统服务层完全独立出来,并将服务层抽取为一个一个的微服务
- 采用一些轻量协议进行传输
- 服务拆分粒度更细,有利于资源重复利用,提高开发效率
- 可以更加精准的制定每个服务的优化方案,提高系统可维护性
- 相比ESB更轻量
什么是微服务
- 使用一套小服务来开发单个应用的方式,每个服务运行在独立的进程里,一般采用轻量级的通讯机制互联,并且它们可以通过自动化的方式部署
微服务的特点 - 单一职责:独立的业务单独放在一个项目里,比如订单服务作为一个项目
- 轻量级通信:http,rpc,非轻量级(例如Java的RMI)
- 隔离性:每个服务相互隔离,不干扰
- 有自己的数据
- 技术多样性
架构
- 单体架构
- 所有功能集成在一个项目中
- 项目整个打包,可以部署到服务器运行
- 应用与数据库可以分开部署,提高性能
- 优点:
· 小项目的首选,开发成本低,架构简单 - 缺点:
· 项目复杂之后,很难有扩展和维护
· 扩展成本高,有瓶颈
· 技术栈受限
- 垂直架构
- 对于单体架构的拆分,大项目拆成单个的项目的架构
- 存在数据冗余
- 项目之间要处理数据同步,通过数据库同步
- 优点:
· 小项目的首选,架构简单
· 避免了单体架构的无限扩大
· 技术不在受限 - 缺点:
· 很多功能放在一个工程中,有一定瓶颈
· 系统性能扩展要通过集群节点扩展,成本较高
- SOA架构(面向服务的架构)
- 将重复性的功能进行抽取,抽取成对应的服务
- 通过 ESB 服务总线去访问
- 优点:
· 提高系统的可重用性
· ESB 减少系统接口耦合问题 - 缺点:
· 系统与服务界限模糊,不利于开发
· ESB 服务接口协议不固定,不利于系统维护
· 抽取粒度较大,仍存有一些耦合性
- 微服务架构
- 将服务层一个一个抽取为微服务
- 遵循单一原则
- 微服务之间采用一些轻量协议传输数据
- 优点:
· 服务拆分粒度非常细,利于开发
· 提高系统可维护性
· 比 ESB 更轻量
· 适用于互联网更新换代快的情况 - 缺点:
· 服务过多,服务治理成本高
· 开发技术要求更高
传统访问方式
微服务架构访问方式
服务发现
传统服务发现方式
一般是IP:端口号访问
)
微服务发现有两中,客户端发现和服务端发现
客户端发现
微服务启动后,将自己IP和端口进行注册,客户端查询注册,得到提供服务的IP和端口,通过负载均衡,访问微服务
**服务端发现:**客户端访问时,不去注册中心了,通过服务发现代理去直接访问
2.RPC远程调用
RPC(Remote Procedure Call),即远程过程调用。它允许像调用本地服务一样调用远程服务。
RPC是一种服务器-客户端(Client/Server)模式,经典实现是一个通过发送请求-接受回应进行信息交互的系统。
首先与RPC(远程过程调用)相对应的是本地调用。
基于tcp协议的RPC
server 端
package main
import (
"fmt"
"net"
"net/rpc"
)
type Args struct {
X, Y int
}
type ServiceA struct {
}
func (s *ServiceA) Add(args *Args, reply *int) error {
*reply = args.X + args.Y
return nil
}
func main() {
service := new(ServiceA)
// 注册服务
rpc.Register(service)
fmt.Println("server register success...")
listener, err := net.Listen("tcp", ":9091")
if err != nil {
log.Fatal("listen error:", e)
}
for {
coon, _ := listener.Accept()
rpc.ServeConn(coon)
}
}
client 端
package main
import (
"fmt"
"log"
"net/rpc"
)
type Args struct {
X, Y int
}
func main() {
// 建立连接
client, err := rpc.Dial("tcp", "127.0.0.1:9091")
if err != nil {
log.Fatal("dialing:", err)
}
fmt.Println("client connect success...")
// 同步调用
args := &Args{10, 20}
var reply 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)
// 异步调用
var reply2 int
divCall := client.Go("ServiceA.Add", args, &reply2, nil)
replyCall := <-divCall.Done // 接收调用结果
fmt.Println(replyCall.Error)
fmt.Println(reply2)
}
这个RPC调用过程可以简化如下图所示
rpc原理
RPC 让远程调用就像本地调用一样,其调用过程可拆解为以下步骤。
)
① 服务调用方(client)以本地调用方式调用服务;
② client stub接收到调用后负责将方法、参数等组装成能够进行网络传输的消息体;
③ client stub找到服务地址,并将消息发送到服务端;
④ server 端接收到消息;
⑤ server stub收到消息后进行解码;
⑥ server stub根据解码结果调用本地的服务;
⑦ 本地服务执行并将结果返回给server stub;
⑧ server stub将返回结果打包成能够进行网络传输的消息体;
⑨ 按地址将消息发送至调用方;
⑩ client 端接收到消息;
⑪ client stub收到消息并进行解码;
⑫ 调用方得到最终结果。