乐趣区

关于golang:微服务架构的演进和go的初步实践

零、背景

近一段时间在学习和实际用 go 来实现微服务架构的开发,本文来记录下什么状况下要应用微服务架构,剖析下利弊。并且用 grpc 初步实现微服务的模型。

一、服务端架构的演进

1、单体架构

在 Web 应用程序倒退的晚期,大部分工程是将所有的服务端功能模块打包成单个巨石型利用,最终会造成如下图所示的架构。

长处:

  • 开发简略
  • 技术繁多
  • 部署不便

毛病:

  • 随着业务的倒退,利用会越来越宏大
  • 技术栈单一,不易扩大
  • 牵一发而动全身

2、垂直分层架构

随着单体利用越来越宏大,单体架构中不同业务模块的差别就会浮现,将大利用拆分成一个个单体构造的利用。垂直分层是一个典型的对简单零碎进行结构化思考和形象聚合的通用性办法。

长处:

  • 分担局部流量
  • 服务间互相独立,能够针对单个服务模块进行优化
  • 易于程度扩大

毛病:

  • 集群搭建变得复杂
  • 可能存在大量耦合代码,调用关系盘根错节
  • 难以保护

3、微服务架构

微服务是一种小型的 SOA 架构(面向服务的架构),其理念是将业务零碎彻底地组件化和服务化,造成多个能够独立开发、部署和保护的服务或者利用的汇合,以应答更快的需要变更和更短的开发迭代周期。

长处:

  • 服务模块解耦
  • 团队分工更容易,更明确
  • 独立部署,可针对独立模块进行公布
  • 扩大能力强

毛病:

  • 服务划分规范多样
  • 减少零碎复杂度
  • 部署更简单
  • 对整个团队的要求更高

微服务特点:

  • 在分布式环境中,将单体利用拆分为一系列服务,独特组成整个零碎。
  • 每个服务都是轻量级,独自部署。
  • 每个微服务重视本人的外围能力的开发,微服务组件之间采纳 RPC 轻量级通信形式进行通信。
  • 依照业务边界进行划分。
  • 微服务是一种编程架构思维,有不同的语言实现。

二、微服务实际

1、语言选择

近几年,随着微服务架构的炽热,也诞生了很多微服务框架,如 Java 语言的 Spring Cloud、Go 语言的 Go Kit 和 Go Micro 以及 Node.js 的 Seneca。充分说明了微服务架构的炽热态势。尽管微服务架构的实际落地独立于编程语言,然而 Go 语言在微服务架构的落地中仍有其独特的劣势。因而,Go 语言的微服务框架也相继涌现,各方面都较为优良的有 Go Kit 和 Go Micro 等。

Go 语言非常轻量,运行效率极高,原生反对并发编程,相较其余语言次要有以下劣势:

  • 语法简略,上手快。Go 提供的语法非常简洁,关键字只有 25 个,简直反对大多数古代编程语言常见的个性。
  • 原生反对并发,协程模型是十分优良的服务端模型。
  • 丰盛的规范库。Go 目前内置了大量的库,开箱即用,十分不便。
  • 部署不便,编译包小,可间接编译成机器码,不依赖其余库。
  • 简言之,用 Go 语言实现微服务,在性能、易用性和生态等方面都领有劣势。

2、服务间的通信

1)RPC

近程过程调用(Remote Procedure Call)是一个计算机通信协议。该协定容许运行于一台计算机的程序调用另一台计算机的子程序。简略地说就是能使利用像调用本地办法一样的调用近程的过程或服务。

2)Protobuf

Protobuf 是由 Google 开源的音讯传输协定,用于将结构化的数据序列化、反序列化通过网络进行传输。Protobuf 首先解决的是如何在不同语言之间传递数据的问题,目前反对的编程语言有 C ++、Java、Python、Objective-C、C#、JavaScript、Ruby、Go、PHP 等。

比照 XML、JSON

长处:XML、JSON 也能够用来存储此类结构化数据,然而应用 ProtoBuf 示意的数据能更加高效,并且将数据压缩得更小。(比 XML 小 3 -10 倍,快 20-100 倍)

毛病:因为是二进制,无奈间接浏览

3)grpc

gRPC 是由 Google 公司开源的一款高性能的近程过程调用 (RPC) 框架。

grpc 联合 protobuf 实现近程服务调用的应用步骤展现

01、定义.proto 文件
syntax = "proto3";  
package services;  
​  
// 订单申请参数  
message OrderRequest {  
 string orderId = 1;  
 int64 timeStamp = 2;  
}  
​  
// 订单信息  
message OrderInfo {  
 string OrderId = 1;  
 string OrderName = 2;  
 string OrderStatus = 3;  
}  
​  
// 订单服务 service 定义  
service OrderService{rpc GetOrderInfo(OrderRequest) returns (OrderInfo);  
}
02、工具生成 go 代码

protoc --go_out=plugins=grpc:. *.proto

03、编写办法体的业务实现
func (os \*OrderServiceImpl) GetOrderInfo(ctx context.Context, request \*OrderRequest) (\*OrderInfo, error) {fmt.Println("收到申请订单号:", request.OrderId)  
​  
 return &OrderInfo{OrderId: request.OrderId, OrderName: "订单名称", OrderStatus: "曾经领取"}, nil  
}
04、service 端启动服务
addr :\= "127.0.0.1:8972"  
 rpcServer :\= grpc.NewServer()  
 services.RegisterOrderServiceServer(rpcServer, new(services.OrderServiceImpl))  
 listen, err :\= net.Listen("tcp", addr)  
 if err != nil {log.Fatalf("启动网络监听失败 %v\\n", err)  
 }  
​  
 // 启动服务  
 if err :\= rpcServer.Serve(listen); err != nil {fmt.Println("启动谬误", err)  
 } else {fmt.Println("服务开启")  
 }
05、client 调用
addr :\= "127.0.0.1:8972"  
 conn, err :\= grpc.Dial(addr, grpc.WithInsecure())  
 if err != nil {log.Fatalf("连贯 GRPC 服务端失败 %v\\n", err)  
 }  
 defer conn.Close()  
​  
 // 实例客户端  
 orderServiceClient :\= sOrder.NewOrderServiceClient(conn)  
 // 模仿订单号  
 orderId :\= "order\_" + strconv.Itoa(time.Now().Second())  
 // 组织申请体  
 orderRequest :\= &sOrder.OrderRequest{OrderId: orderId, TimeStamp: time.Now().Unix()}  
 //rpc 调用 GetOrderInfo  
 orderInfo, err :\= orderServiceClient.GetOrderInfo(context.Background(), orderRequest)  
 if orderInfo != nil {fmt.Println(orderInfo.GetOrderId(), orderInfo.GetOrderName(), orderInfo.GetOrderStatus())  
 return orderInfo.GetOrderStatus()} else {return "订单服务读取失败"}

3、服务注册核心

在微服务架构中,个别每一个服务都是有多个拷贝,来保障高可用。一个服务随时可能下线,也可能应答长期拜访压力减少新的服务节点。这就呈现了新的问题:

  • 服务之间如何互相感知?例如有新的服务实例上线,已上线的实例如何晓得并与之通信。
  • 服务如何治理?服务实例数量多了,也面临着如何治理的问题。

服务注册

service 启动时向注册核心注册

健康检查

注册核心和 service 之间会放弃心跳查看,来保护注册表里的 service 是否存活

服务发现

client 向注册核心获取可用的 service

  • register 在每个服务启动时会向 服务注册核心 上报本人的网络地位。这样,在服务发现核心外部会造成一个 服务注册表 服务注册表 是服务发现的外围局部,是蕴含所有服务实例的网络地址的数据库。
  • healthy check 注册核心与各微服务节点间放弃心跳检测,来保障服务注册表的服务都是可用状态。
  • discover 当须要对某服务进行申请时,服务实例通过该注册表,定位指标服务网络地址。若指标服务存在多个网络地址,则应用负载平衡算法从多个服务实例中抉择出一个,而后发出请求。

注册核心中间件的比拟

Consul

Consul 是 HashiCorp 公司推出的开源工具,用于实现分布式系统的服务发现与配置。Consul 应用 Go 语言编写,因而具备人造可移植性(反对 Linux、windows 和 Mac OS X)。Consul 内置了服务注册与发现框架、散布一致性协定实现、健康检查、Key/Value 存储、多数据中心计划,不再须要依赖其余工具,应用起来也较为简单。

Etcd

etcd 是用 go 开发的,呈现的工夫并不长,不像 zookeeper 那么悠久和有名,然而前景十分好。etcd 是因为 kubernetes 而被人熟知的,kubernetes 的 kube master 应用 etcd 作为分布式存储获取分布式锁,这为 etcd 的弱小做了背书。etcd 应用 RAFT 算法实现的一致性,比 zookeeper 的 ZAB 算法更简略

Zookeeper

zookeeper 起源于 Hadoop,起初进化为 Apache 的顶级我的项目。当初曾经被宽泛应用在 Apache 的我的项目中,例如 Hadoop,kafka,solr 等等。是用 java 开发的,部署的时候要装 JAVA 环境。历史悠久,功能丰富,所以比拟重,易用性不如以上 2 个。

代码展现

我临时选用了 etcd 来做注册核心,以下展现退出注册核心后的 grpc 调用代码。

service 端
package main

import (
    "fmt"
    "google.golang.org/grpc"
    "goshare/etcd"
    "grpc-service/services"
    "log"
    "net"
)

func main() {
    addr := "127.0.0.1:8972"
    rpcServer := grpc.NewServer()
    services.RegisterOrderServiceServer(rpcServer, new(services.OrderServiceImpl))
    listen, err := net.Listen("tcp", addr)
    if err != nil {log.Fatalf("启动网络监听失败 %v\n", err)
    }

    //etcd 服务注册
    reg, err := etcd.NewService(etcd.ServiceInfo{
        Name: "mirco.service.order",
        IP:   addr, //grpc 服务节点 ip
    }, []string{"172.24.132.232:2379"}) // etcd 的节点 ip
    if err != nil {log.Fatal(err)
    }
    go reg.Start()

    // 启动服务
    if err := rpcServer.Serve(listen); err != nil {fmt.Println("启动谬误", err)
    } else {fmt.Println("服务开启")
    }
}
client 端
r := etcd.NewResolver([]string{"172.24.132.232:2379"}, "mirco.service.order")
    resolver.Register(r)

    addr := fmt.Sprintf("%s:///%s", r.Scheme(), "")
    //addr := "127.0.0.1:8972"
    fmt.Println("addr", addr)

    conn, err := grpc.Dial(addr, grpc.WithInsecure())
    if err != nil {log.Fatalf("连贯 GRPC 服务端失败 %v\n", err)
    }
    defer conn.Close()

    orderServiceClient := sOrder.NewOrderServiceClient(conn)

    orderId := "order_" + strconv.Itoa(time.Now().Second())
    orderRequest := &sOrder.OrderRequest{OrderId: orderId, TimeStamp: time.Now().Unix()}
    orderInfo, err := orderServiceClient.GetOrderInfo(context.Background(), orderRequest)
    if orderInfo != nil {fmt.Println(orderInfo.GetOrderId(), orderInfo.GetOrderName(), orderInfo.GetOrderStatus())
        return orderInfo.GetOrderStatus()} else {return "订单服务读取失败"}

总结

以上就实现了注册核心来做服务注册和发现,咱们测试成果的时候能够启用 2 个 service,一个监听 8972,一个监听 8973。client 通过注册核心(172.24.132.232:2379)来做服务发现,当 2 个 service 其中任何一个敞开,咱们的服务仍然能失常提供服务,实现了高可用。

tips:另外举荐大家自己参加的并且感觉不错的学习渠道
一个是拉钩教育上的《Go 微服务实战 38 讲》98 元。

另一个是 58 沈剑的《架构师训练营》399 元。微服务对架构常识体系由肯定要求,这个课程是沈老师 10 多年的架构教训成体系的整顿,能够帮忙咱们从简到深的了解架构层面的常识。另外如果不花钱报课,每周根本都有收费的互联网架构直播课,个别 1 个多小时,都是干货常识。

退出移动版