Junedayday Blog

六月天天的个人博客

0%

2022-01

2022-01-17 CNCF-gRPC

今天,我们一起来看CNCF的 Remote Procedure Call - RPC 模块中最具代表性的项目 - gRPC。

gRPC官网的定义很简洁,重点强调了其高性能的特点:

A high performance, open source universal RPC framework.

我们再看一段官方更详细的描述:

gRPC is a modern open source high performance Remote Procedure Call (RPC) framework that can run in any environment. It can efficiently connect services in and across data centers with pluggable support for load balancing, tracing, health checking and authentication. It is also applicable in last mile of distributed computing to connect devices, mobile applications and browsers to backend services.

除了高性能,这里还提到了两个重要特性:

  • 插件化的支持:负载均衡、tracing、健康监测、认证等
  • 最后一公里:将手机app、浏览器与后端服务器的联通

第一点体现出了gRPC的高度可扩展性。这里的插件化不仅体现在上面列举的这些具体功能,甚至像序列化的方案、底层通信的协议,都可以做到快速替换与迭代。

第二点在实际生产上应用的case并不多,仍需要大量的实践与经验沉淀后,才建议大家尝试。目前,绝大多数的gRPC通信都是发生在后端服务之间。

接下来,我们来谈谈官网列举的四个特性:

Simple service definition

gRPC采用Protocol Buffers进行定义,从整体来说阅读体验确实是比较简洁的,但在实际工程中仍存在两个问题:

  1. 迁移成本大:gRPC没有提供从很多历史RPC解决方案中迁移的途径,如Thrift等,完全是另起炉灶,这对有历史包袱的团队来说很难接受;
  2. 配套开发工具缺失:高频使用Protocol Buffers的朋友都有了解,主流IDE对pb文件的错误提示和文件格式化都有缺失,很多问题都无法在coding的过程中实时提示,而是需要运行二进制命令protoc后才能了解。

Start quickly and scale

快速启动 - 强调的是通过Protocol Buffers生成的代码,可以在各语言内用简洁的代码就可运行(这部分与各语言强相关)

快速扩容 - 这个特性需要与Kubernetes的能力结合。

Works across languages and platforms

跨语言与平台的特性,非常依赖背后的生态 - 需要大量的工作去兼容多语言,也要兼顾性能、稳定性等问题。

所以,这不仅仅是技术上的问题,更需要社区、资金等复杂因素的支持。

Bi-directional streaming and integrated auth

  • 双向流式通信 - 这点是gRPC的核心特性,体现出了与其它RPC方案的差异
  • 集成认证功能 - Auth是gRPC插件化生态中的重要一环,这部分其实与服务网格的功能存在一定的重叠

2022-01-18 CNCF-Envoy

作为云原生Service Mesh代表性的产品之一,Envoy占据了大壁江山。

Envoy is an L7 proxy and communication bus designed for large modern service oriented architectures.

  1. 定位:L7 代理和通信总线
  2. 重点服务对象:SOA面向服务架构(也完全适用于微服务架构)

核心理念:

The network should be transparent to applications. When network and application problems do occur it should be easy to determine the source of the problem.

网络应对应用程序透明。当网络和应用程序出现问题时,应该很容易地确定问题根源。

文档资料:

我们依旧挑选三个核心特性展开:

Out of process architecture 进程外架构

进程外架构,其实就是一种微服务理念的体现,它的利与弊也是和微服务强相关的。

优点很直观 - Envoy可以支持多语言,也可以独立于业务快速迭代与发展。

而弊端在官网上没有体现,最关键的一点是:性能损耗。作为一种side-car模式,Envoy是在本机内、进程间进行通信,远远地比进程内调用的压栈、出栈更花时间。

Service discovery and dynamic configuration 服务发现和动态配置

很多运维同学都会拿Envoy去对比Nginx,确实在很多特性上,两者有很大的相似之处。但在这个特性上,Envoy有得天独厚的优势。

以一个常见的动态负载均衡为例,nginx需要结合consul template来实现,还需要对应用程序做一定的侵入。而Envoy可以与Kubernetes完美契合,大幅度减少维护成本。

Best in class observability 最佳的可观察性

Envoy 的最主要目标是使网络透明,而最佳的可观测性往往会从OpenTelemetry推荐的三点入手:Metrics、Logging、Tracing。

Envoy提供了大量的插件,一方面保证了Envoy内部的问题清晰明了,同时也能快速地协助定位到应用侧的问题。

2022-01-19 CNCF-Contour

作为CNCF中的控制平面的主打产品,Contour的知名度远不如Google的Istio。但由于种种原因,Istio并没有被捐献给云原生基金会,于是CNCF就孵化了Contour。

定义

Contour is an open source Kubernetes ingress controller providing the control plane for the Envoy edge and service proxy. Contour supports dynamic configuration updates and multi-team ingress delegation out of the box while maintaining a lightweight profile.

三大特性

  • Envoy Inside
  • Flexible Architecture
  • TLS Certificate Delegation

从定义与三个特性的描述里,我们很难真实地感受到Contour的特点。

今天我将换一个角度:从控制平面与数据平面进行分析。如果我们能清楚地认识到这两者的定义与边界,那么会对云原生的Service Mesh理念,以及Envoy、Contour、Istio等产品有更深的理解。

这篇博客的定义很具有参考意义:

  • 控制平面 - 通过配置和控制消息来组织编排网络的逻辑,并下发给数据平面
  • 数据平面 - 以 Sidecar 的形式与应用部署在一起,承载其流量的发送与接收

控制平面的关键功能就是控制与管理,重点对象是配置信息

我们可以将数据平面看作为基础的Nginx服务,它是和底层的应用强相关的,解析相关的数据包并实现路由、负载均衡等功能。但Nginx自身也需要有一定的配置信息,比如说:

  • 数据平面的服务发现中心往往是因服务而异的;
  • 数据平面的超时参数会频繁调整;
  • 在做蓝绿测试时,需要频繁调整流量;
  • 认证相关的设置往往需要统一更新,如证书;

在我看来,控制平面更像是数据平面的配置中心,解决的是高频变化或应用间差异性很大的配置信息,让数据平面更稳定地运行。

从架构层面来说,如果一层解决不了问题,那就再抽象一层。

从具体的工程实践来说,数据平面主要负责的是底层实现,在保证稳定性的情况下更要兼具性能,所以不宜有太多丰富的功能。这时,引入了控制平面,我们可以将很多的配置信息通过界面UI方式进行管理,下发到各个控制平面,然后控制平面再针对性地进行转发到数据平面,就能大幅度地提高整个Service Mesh方案的可维护性。

2022-01-20 CNCF-Emissary-Ingress

Emissary-Ingress是CNCF中又一个与网络相关的项目。这款软件的前身叫作Ambassador。但Ambassador是一款商业软件,为了避免相关的问题,更名为Emissary-Ingress后被捐献给了CNCF。因为网上资料更多的都是Ambassador,下面统一用Ambassador对这款软件进行描述。

CNCF上的定义为:

open source Kubernetes-native API gateway for microservices built on the Envoy Proxy

我们很容易将这个产品与Istio、Envoy混淆起来,那它们之间的区别是什么呢?这里有篇文章,介绍了具体的实践,但我们直接可以从标题中得到关键信息:

  • Ambassador - Edge proxy
  • Istio - service mesh

而Envoy+Contour对标的是Istio,所以不难看出,云原生推荐将Ambassador作为一种边缘网关,更多地是作为整体流量的出入口。如果说service mesh是一种对网络的精细化管理,那么边缘网关更多地是对整体流量的管理。

但在实际使用中,两者有大量的重叠功能,如路由、认证、限速等,而我也没有在网上找到相关的Best Practice,这部分就很需要大家的经验与摸索了。从我的角度来看,更倾向于大家多使用Envoy的特性,毕竟它的生态最成熟。即便后续有了替代品,对方也往往会提供对应的兼容方案。

这里,我引用了一个链接,讲述了微服务网关(Emissary-Ingress定位)与传统企业级网关的差异。

Primary Purpose 主要目标

  • 传统企业级网关 - Expose, compose, and manage internal business APIs
  • 微服务网关 - Expose and observe internal business services

微服务网关相对而言会更轻量级,而且对外以服务的维度呈现。

Publishing Functionality 发布功能

  • 传统企业级网关 - API management team or service team registers / updates gateway via admin API
  • 微服务网关 - Service team registers / updates gateway via declarative code as part of the deployment process

微服务网关更倡导自运维的方式。

Handling and Debugging Issues 处理和Debug问题

  • 传统企业级网关 - L7 error-handling (e.g. custom error page or payload). Run gateway/API with additional logging. Troubleshoot issue in staging environment

  • 微服务网关 - Configure more detailed monitoring. Enable traffic shadowing and / or canarying

微服务网关更强调的是网络侧的更细致化的管理,突出了灵活性。

2022-01-21 谈谈Context在Go程序外的传递

这两天,有个读者跟我讨论了一个关于链路追踪的问题 - 他发现通过context传递的trace-id丢失了。从根本上来说,这涉及到了Context在Go程序外的传递的理解,细想的话很有意思,所以我今天专门拿出来讲讲。

如果你对Go语言有一定的基础,会很习惯地使用Context作为上下文信息的传递:无论是内部函数的调用,还是RPC的调用,这就会产生一种错觉:context的传递是水到渠成的

这位同学的调用链路是 Go程序 - MQ - Go程序,发现context中的trace-id丢失了。

trace-id用于分布式的链路追踪

分析这个问题不难,有很多种思路,但我比较建议大家从底层实现去理解:

  1. context的本质,是一种key-value的数据结构;
  2. 服务之间的调用,本质上是一种网络上的二进制数据的传递,而context信息肯定是保存在其中的
  3. HTTP是一种二进制数据上的协议表达格式,我们常见的RESTful API 的 json数据是填入到body中,但HTTP协议还有很多地方可以保存数据,尤其是HTTP Header。context信息大概率是被保存在Header里的;

Go语言的Client将context封装到Header里,Server将context从Header中解析出来

默认的官方库是不会写直接写的,需要做一层浅封装:

发送前,从ctx中将对应的k-v填入到header;

收到后,从header中提取k-v,写入到context。

要验证一下上面的猜想很简单,我强烈大家用最底层的方式:Wireshark抓包。事实也如我们的预期,Go语言的ctx传递正是用这种方式实现的。而MQ这种第三方组件,往往不会去解析这种特定的Header,所以会发生丢失。

这时,我们有什么样的解决方案呢?

  1. 制定标准。这也是OpenTracing等规范推崇的,对广大的开发者来说非常友好,尤其是跨语言的情况,但这块非常依赖生态,需要大量的SDK,整体来说推进进度比较难。(这就体现了Service Mesh的价值)
  2. 显示传递。相当于把关键信息填到HTTP的body里,对具体代码的实现来说侵入性很强,但不失为中小型公司快速落地的方案;遇到一些不支持标准的第三方软件,我们也往往只能采用这种方式。

Github: https://github.com/Junedayday/code_reading

Blog: http://junes.tech/

Bilibili: https://space.bilibili.com/293775192

公众号: golangcoding

二维码

2022-01

2022-01-10 Unix设计哲学概览

Unix设计哲学在程序员的圈子里经久不衰,备受追捧。而Go语言背后有很多UnixC语言的影子,三位创始人Rob Pike(罗伯. 派克),Ken Thompson(肯. 汤普森)和Robert Griesemer(罗伯特. 格利茨默)都是这两块领域的泰山北斗。了解Unix的设计哲学,对写出优秀的代码很有帮助。

Doug McIlroy的总结

Write programs that do one thing and do it well. Write programs to work together. Write programs to handle text streams, because that is a universal interface.

做一件事,做好它。让程序能够互相协同工作。应该让程序处理文本数据流,因为这是一个通用的接口。

作为管道机制的发明者,Doug McIlroy对这块的解释重点很具有管道特色。这里,我们聚焦于一个点:做好一件事

将其拆分一下,主要分为两块:

  1. 做好。做一件事,可能只需要10分的能力;而做好,则需要100分的能力(但 做好 这个标准,尽可能地按照自己的认知上限去做,才可能有成长)。
  2. 一件事。怎么定义一件事呢?边界的定义就难倒了很多人。这个非常考验能力,更具体可能需要case by case去看。

Rob Pike的总结

  1. 你永远不会知道你的程序会在什么地方耗费时间。程序的瓶颈常常出现在意想不到的地方,因此在你确信找到瓶颈后再动手优化代码吧。
  2. 测试代码。只有在你详细测试了代码,并且发现一部分代码耗费了绝大部分的运行时间时再对程序作速度优化。
  3. 功能全面的算法(fancy algorithm)在处理小规模问题时效率很低,这是因为算法时间效率中的常量很大,而问题往往规模很小。除非你知道你遇到的常常是复杂的情况,否则就让代码丑陋但是简单而高效吧。(即使问题规模确实很大,也首先尝试第二条规则。)
  4. 功能全面的算法比简单的算法更容易产生bug,更难实现。尽量使用简单的算法和数据结构。
  5. 数据决定一切。如果选择的数据结构能很好的管理数据,算法部分往往不言自明。记住,数据结构,而非算法,才是编程的关键。
  6. 没有第六条规则。

按照wiki上的说法,1、2可以归纳为 过早的优化是一切罪恶的根源,3、4可以理解为 疑惑不定之时最适合穷举

而第五条就非常有意思,也就是 数据结构比算法更重要

这点,和我们在刷算法题时认知相反 - 在做算法题目时,我们往往已经得到了一个具体的数据结构,要做的更多是根据这个数据结构选择合适的算法。当数据结构确定时,可选择的算法就很有限了,这也大大缩小了题解的范围。

在复杂的场景中,我们首先得确定数据结构,这一步尤为复杂:

  1. 复合 - 数据结构往往是复合的、嵌套的,单一的数据结构很少见;
  2. 抓住核心 - 可用数据结构解往往有多种,关键是识别其中决定性的因素;
  3. 不确定性 - 最优解的数据结构,往往会根据时间变化而变化;
  4. 简单性 - 性能最优解并不一定是最终的解,实际工程中多考虑简单性;

小结

关于Unix的设计哲学还有很多优秀的见解,但综合起来,可以归纳为KISS原则,也就是简单性。希望大家能在工程实践中,多多思考怎么保证简单性,对做出优秀的设计会很有启发。

2022-01-11 CNCF-CrossPlane

今天我们一起来看CNCF中的第二个项目 - CrossPlane 。它位于CNCF全景图中Kubernetes旁,受众比较小。

先看一段来自官网 https://crossplane.io/ 的信息:

Crossplane is an open source Kubernetes add-on that enables platform teams to assemble infrastructure from multiple vendors, and expose higher level self-service APIs for application teams to consume, without having to write any code.

我们依旧抽取其中关键的词进行解析:

  • Kubernetes add-on CrossPlane的定位是Kubernetes的插件,并非一个独立的平台
  • assemble infrastructure from multiple vendors 统一封装多个依赖方的基础设施
  • expose higher level self-service APIs 暴露高层接口

然后,官方描述了五个关键性的特征:

  • Provision and manage cloud infrastructure and services using kubectl
  • There is a flavor of infrastructure for everyone on Crossplane
  • Publish simplified infrastructure abstractions for your applications
  • The Universal Cloud API
  • Run Crossplane anywhere

概括起来,可以定义为:使用 kubectl 封装了各类云的API,来统一管理基础设施、服务和应用

我们从具体的实践角度来看,统一封装接口往往只是加分项,而核心在于 支持的云基础设施与服务的范围

官方文档 可以看到,CrossPlane主要支持了亚马逊云、谷歌云、微软云等厂商。可以看到,这款产品主要面向的是国外的公有云厂商。这其实也决定了CrossPlane更多面向的是国外开发者。

学习CrossPlane的更多细节,对我们来说暂时没有更大的意义。我们可以尝试着从其理念中得到以下启发:

  1. 对接各类云厂商是复杂的。尽管CNCF统一了大致上的理念,但细节上的实现各有不同;尤其是结合了账号、权限、资源分配等各类特性后,对接起来的复杂度很高。
  2. 如果一层不能解决,那就加一层。 直接对接各类公有云复杂度很高,也会为Kubernetes这个核心项目带来大量侵入性极强的代码。这时,引入另一个项目作为防腐层,很具有扩展意义。
  3. 一个核心项目不一定需要做成大型平台,更重要的是它的特性符合整个生态的发展理念。

2022-01-12 微服务架构特征

今天的话题将围绕着一篇谈论微服务架构的文章展开。下面给出原文链接,以及一个翻译的版本:

文中给出了以下九个微服务特征:

  1. Componentization via Services 通过服务来实现组件化
  2. Organized around Business Capabilities 围绕业务能力构建
  3. Products not Projects 面向产品,而不是面向项目
  4. Smart endpoints and dumb pipes 终端智能化,通讯轻量化
  5. Decentralized Governance 分散治理
  6. Decentralized Data Management 数据去中心化管理
  7. Infrastructure Automation 基础设施自动化
  8. Design for failure 容错性设计
  9. Evolutionary Design 演进式设计

这些点,每个抛出来都可以写一篇长文。建议有兴趣的各位可以阅读原文,结合自己的实践多多思考。

这里,我选择三个最近感触比较深的点,自己也曾经陷入过的认知误区,在这里和大家聊聊:

围绕业务能力构建

业务能力的概念很抽象,虽然我们会经常提及,但在实践过程中又往往容易忽略。

从系统的角度来看,业务能力往往就是对外呈现的功能,对应到内部的技术模块,往往已经决定了七七八八。如何将这些技术模块做合理的拆分与合并,就是微服务架构需要考量的点。这里我谈谈最近比较有心得的三个考量点:

  • 拆分高频变化与低频变化
  • 分离计算密集型和IO密集型
  • 基础能力尽早引入业界的通用模块

当然,还有更多的内容,需要大家在实践中摸索。

终端智能化,通讯轻量化

这一点在云原生的服务中体现得淋漓尽致:以RPCService Mesh服务发现等技术为代表。

终端体现在Pod这一层,也就是对一个具体运行的App来说,通过Istio、CoreDNS等技术将分布式的服务做到和单体应用一致,然后通过轻量级的通讯方案,如HTTP进行交互。这种方式的优点很明显:

  1. 分布式服务之间的通讯复杂度最高的部分,由专用的、成熟的组件,引入到Pod层面完成;
  2. 开发者的代码实现只需要关心RPC的数据出入,复杂度大幅度降低;

目前云原生的Service Mesh技术还未完全形成行业标准,相信很快随着它的落地,将迎来微服务的又一波热潮。

容错性设计

容错性设计,也就是为错误而设计,这一点很反直觉。

作为一名开发者,我们实现功能的思路往往是按照顺序的逻辑步骤;一个一个步骤的串联,才能保证最后的功能实现。但这个时候,如果要我们去关注各类错误的发生,小到网络波动、程序崩溃,大到机房断电,很容易无所适从。

这里,我谈谈自己的理解:主要从发生的概率与影响的严重程度来思考,不要过度追求细节。这里有一个很重要的权衡点 - **健壮性 **与 简单性 :一般来说,要保证程序足够健壮,会引入各种异常的容错性设计,增加系统的复杂度,但这一点并不是绝对的。

从系统整体功能的维度,虽然看起来增加了复杂度,但通过分层、模块化、服务拆分等方式,分而治之 - 一些简单的模块用简单的规则组合成一个大模块,可维护性远远高于一个复杂的模块。

2022-01-13 CNCF-CoreDNS

CoreDNS是CNCF全景图中 协调与服务发现 模块的核心项目,在Kubernetes1.12版本之后成为了默认的DNS服务。熟悉CoreDNS是掌握Kubernetes必不可少的技能。

照例,我们先一起看下其核心定义,非常简洁明了:

官网 - CoreDNS: DNS and Service Discovery

CNCF - CoreDNS is a DNS server that chains plugins

今天,我们将围绕一个关键词chains plugins - 链式插件 展开,这也是CoreDNS实现的核心特性。

官方对这个特性的定义如下,

CoreDNS chains plugins. Each plugin performs a DNS function, such as Kubernetes service discovery, prometheus metrics, rewriting queries, or just serving from zone files. And many more.

从中不难看出,CoreDNS将各种DNS的功能抽象成一个插件,进行链式调用。

我们用 官方github上的Corefile 来了解这个特性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
example.org:1053 {
file /var/lib/coredns/example.org.signed
transfer {
to * 2001:500:8f::53
}
errors
log
}

. {
any
forward . 8.8.8.8:53
errors
log
}
  1. 当访问example.org:1053 时,依次触发 file、transfer、errors、log四个插件
  2. 访问其余DNS时,依次触发 any、forward、errors、log四个插件

上面的语法与nginx非常类似,而作者实际上是参考Caddy软件进行设计的,有兴趣的可以查阅相关资料,例如这个 博客

链式调用是一种表述形式非常强的语法:它以一个大众容易接受的顺序逻辑,讲述了一个完整的调用过程,将各个细节也描述地很清晰

既然链式调用的描述方式那么棒,为什么目前没有大规模地推广到各类工具上呢?这里,我谈谈个人的三个理解:

  1. 核心模块支持插件化 - 链式调用需要动态加载各类插件,这一点对核心的模块要求很高,很多软件设计之初就决定了很难走插件化的道路;
  2. 无需兼容历史问题 - 很多的工具都存在大量的历史版本,很难做到完全兼容这种表达形式;而CoreDNS在设计之初就强制采用这种规范,没有历史包袱;
  3. 每个插件的输入与输出保持一致 - 作为一种链式调用,为了保证前者输入可以作为后者输出,两者支持的数据格式必须一样(可以参考Unix中的管道)。DNS服务的功能比较简单,可以保证一致;

同样的,Corefile这种声明方式,也或多或少带来了一些问题,例如:

  1. 文件解析的复杂性(这点与链式调用本身无关) - 比如数据类型的问题,很难确定文件中的5是数字还是字符串;
  2. 链式调用很难解决前后有依赖的情况 - 如调用A插件的结果有个特别的输出,用于插件B的输入时,很难解决;
  3. 长链式调用的复杂度 - 如果链式调用过长,一方面带来了异常情况下排查问题的复杂度,另一方面很容易出现性能问题;

CoreDNS的成功,链式调用插件 这个特性只体现了简单性的理念,并不是关键性的原因,而更多地是依赖大量开箱即用的插件

2022-01-14 不可变基础设施

看起来,这功能描述与CICD流程差不多,但使用体验差距很大。在传统的模式下,我们执行的是一个具体的动作,比如扩1个应用、升级2个程序等;而在k8s里,使用者只要声明最终的预期状态,比如5个应用运行v1.0版本的程序,那么整个系统该扩容还是缩容、该升级还是回滚,都由k8s自行根据当前状态进行判断。

ab4ea1c14f669e0e42040689da7aa074c4e479f3

云原生有五大代表性的技术 - 容器、服务网格、微服务、不可变基础设施和声明式 API。相对于其余四种概念,不可变基础设施 - Immutable Infrastructure 更难理解,今天我们来一起看看。

入门可以参考这篇文章 - https://zhuanlan.zhihu.com/p/382203966

网上可搜索到的不可变基础设施定义有很多,这里我选择一个比较有代表性的:

Immutable infrastructure refers to servers (or VMs) that are never modified after deployment.

从开发者的角度来看,不可变基础设施是一个很棒的概念。如果用一个词总结它的优点,那就是 时间与空间上的一致性

如果有一个传统应用希望改造成适配 不可变基础设施 的场景,那么会有哪些常见的改造点呢?

  1. 将传统应用的运行环境打造成一个具体的服务器,例如虚拟机、容器;
  2. 重点分析该应用的输出形式,让其与 服务器无关

第二点有些抽象,这里我举三个具体的例子:

  1. 本地缓存 - 转移到分布式缓存服务;
  2. 本地保存的文件 - 转移到分布式存储服务中;
  3. 本地日志 - 将原来打印到本地文件的日志服务,重定向到标准输出,由日志采集的side-car收集后统一汇总;

但在实际的工程中,追求 不可变基础设施 很难完全落地,我们可以适当地做一些权衡:

  1. 如果日志不允许落盘对部分程序的改造成本很高,那么我们可以用ELK等一套日志收集方案做准实时的同步,保证日志可丢失;
  2. 如果完全依赖分布式缓存对性能压力过大,那么就建立一套分布式缓存与本地缓存的自动同步机制,保证重启后本地缓存丢失,仍可以恢复;

不难看出,只要我们保证应用在基础设施上产生的数据 可在任意时间丢失,就能实现了一定程度上 应用无状态化,也能保证了不可变基础设施的落地。

不可变基础设施是一种理念,具体落地的技术非常依赖容器或虚拟机,以及分布式存储等配套设施。我们没有必要把它作为一种技术标准去强制执行,而应该结合现状,选择性地朝着这个方向不断优化。

Github: https://github.com/Junedayday/code_reading

Blog: http://junes.tech/

Bilibili: https://space.bilibili.com/293775192

公众号: golangcoding

二维码

2022-01

2022-01-04 Go1.18概览

在2021年年底,Go推出了1.18Beta版本。由于正式版本没有完全敲定,普通开发人员没有必要研究到底层实现,但如果能先形成一个全局上的认知,能帮助我们领先一步。

关于1.18的核心改动,是 对泛型(Generics)的支持。Go语言的泛型语法比较简单,如下:

1
2
3
4
5
6
7
8
9
10
type numeric interface {
type int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64
}

func min[T numeric](a, b T) T {
if a < b {
return a
}
return b
}

如果要在实际工程上落地,还有很多考量点,我这边重点提三点:

  1. 主流IDE的支持 - 包括Goland与VSCode,尽量帮助开发者能在编码时发现问题;
  2. 历史库的迁移与兼容 - 实现泛型后,许多主流库会有大量的冗余函数,如何提供迁移方案,值得我们去关注;
  3. 泛型的最佳工程实践 - 作为一种新特性,Go的泛型如何应用在工程中、尤其是复杂工程中,需要一定的实践摸索,并总结规律(可借鉴其余支持泛型的语言);

除了泛型,另外一个比较大的特性就是Fuzzy Testing

这个特性是为单元测试提供更全面的数据输入,这样就能覆盖更多的case,提前发现问题。关键词Fuzzy支持的主要特性是将一个输入参数,从具体的值变成范围,如原先输入a=1,现在支持a输入范围为[-10,10]。在跑单元测试时,大量的Fuzzy肯定会带来一定的性能压力,这时可以引入一定的并发特性。

总体来说,Go1.18对工程侧的影响更多地是提高代码的 简洁性。新特性的学习成本很低,我们不用过于急着引入,可以多花时间学习底层原理。

Go Blog - https://go.dev/blog/go1.18beta1

Medium - https://betterprogramming.pub/golang-1-18-what-you-need-to-know-a5701f7e14ab

2022-01-05 CNCF-概览

CNCF作为云原生的代表性组织,提供了大量开源的软件,以及配套的、开箱即用的解决方案。有很多朋友对CNCF和云原生的认识可能仍停留在新闻报道里。今天,我先带大家在整体上入个门,后续选择具有代表性的软件进行分析。

由于篇幅所限,我的分享只会提重点知识,帮大家建立这部分的知识框架,更详细的内容需要大家自行学习。

CNCF的概览可以参考这个全景图 - https://landscape.cncf.io/,更新迭代非常频繁。其中,最核心的为下面五块:

landscape

  • App Definition and Development 应用定义与开发
    • Database 数据库
    • Streaming & Messaging 流处理和消息通信
    • Application Definition & Image Build 应用定义与镜像构建
    • Continuous Integration & Delivery 持续集成与交付
  • Orchestration & Management 编排和管理
    • Scheduling & Orchestration 调度与编排
    • Coordination & Service Discovery 协调与服务发现
    • Remote Procedure Call 远程过程调用
    • Service Proxy 服务代理
    • API Gateway API网关
    • Service Mesh 服务网格
  • RunTime 运行时
    • Cloud Native Storage 云原生存储
    • Container Runtime 容器运行时
    • Cloud Native Network 云原生网络
  • Provisioning 提供者
    • Automation & Configuration 自动化与配置
    • Container Registry 容器注册
    • Security & Compliance 安全与合规
    • Key Management 密钥管理
  • Observability and Analysis 可观察性和分析
    • Monitoring 监控
    • Logging 日志
    • Tracing 跟踪
    • Chaos Engineering 混沌工程

其余还包括Kubernetes的平台提供商、Serverless、成员、认证的服务提供商等周边内容,并不在我们讨论的范围之内。但从基金会来看,它提供了一整套生态,非常有助于落地。

那么,如何认识这五块呢?其实Landscape提供了很好的图形效果,我们只要记住两点:

  1. 应用定义与开发编排和管理运行时提供者 这四块是自上而下的核心链路,下层为上层提供能力支撑
  2. Observability and Analysis 是核心链路旁边的重要支撑

相信到这里,你对CNCF已经有了初步认识。

2022-01-06 《我做系统架构的一些原则》From 陈皓

今天,给大家推荐一篇来自左耳朵耗子-陈皓的文章

  1. 关注于真正的收益而不是技术本身
  2. 以应用服务和 API 为视角,而不是以资源和技术为视角
  3. 选择最主流和成熟的技术
  4. 完备性会比性能更重要
  5. 制定并遵循服从标准、规范和最佳实践
  6. 重视架构扩展性和可运维性
  7. 对控制逻辑进行全面收口
  8. 不要迁就老旧系统的技术债务
  9. 不要依赖自己的经验,要依赖于数据和学习
  10. 千万要小心 X – Y 问题,要追问原始需求
  11. 激进胜于保守,创新与实用并不冲突

以上11点,理解会因人而异,我重点挑三个争议性比较大的聊聊,其余的内容建议大家阅读原文。

完备性会比性能更重要

借用书中的一句话:使用最科学严谨的技术模型为主,并以不严谨的模型作为补充,也就是先紧后松。

有不少开发者在实际工程中的实践往往相反:为了追求快速落地,会希望毕其功于一役,引入所谓的“一站式解决方案”(如例子中NoSQL),但实践下来引入大量的问题,让后人叫苦不迭。

关于这个问题,我个人有三个思考:

  • 不断提高自己的基础能力。很多架构上的局限性,往往是设计者停留于自己的舒适区,不愿意往前一步。
  • 分清主次、合理分工。 在设计时,我们要分清楚核心功能和非核心功能,懂得取舍,将功能交由合适的模块或软件。
  • 功能的实现不是对应到单模块,而是整个系统的涌现。性能问题的解法不仅仅限于单个软件,而是有一整套生态,可以多去查一些大厂的分享。

不要迁就老旧系统的技术债务

为了缩小讨论范围,我对这里 技术债务 做一个收口:不仅仅是指有弊端的技术问题,更是需要投入时间精力等成本去维护。有技术债务,不代表就一定要去还,而需要一个契机 - 维护的成本 > 修复的收益

举个例子,某个程序写得很烂,性能很差:

  • 前期可以通过扩容快速解决,上线后业务收益很高,那就是 成本大于收益,不需要排到最高优先级去修复;
  • 但随着业务收益稳定下来,增长只有个位数,但发现在机器上投入的成本很高,通过优化预计能缩容50%,可以让总收益提升20%,那这时还技术债务的优先级就很高了。

还技术债,技术能力只是一个基本,以下两点更为重要:

  • 评估成本与收益,尽可能地做到有数据支撑,有助于决策者下决心还债;
  • 用更长远、更广维度地看待技术债务问题,不要拆东墙补西墙。

激进胜于保守,创新与实用并不冲突

这个观点是很aggressive的,遇到这样的观念冲突时,决策者找不到客观标准去评估,就很难有二义性的定义:要么激进,要么保守;要么创新,要么实用。这种情况下,我遇到过的比较好的解法有两种:

  1. 决策者自身技术能力强,能掌控团队技术的大致走向。
  2. 决策者和执行者之间培养出足够的信任,适当评审与把控,放权实践。

第一种情况在实际工作场景中并不多见,尤其当团队规模很大时,就像CTO往往不是公司技术最强的那位。所以,我更倾向于大家多尝试第二种途径。

当然,我也遇到过很多效果不好的解法,比如说:决策者既然不清楚怎么做才好,那就找2个执行者进行battle,一个代表创新方,另一个代表保守方。也许在少数情况下,最后能帮助决策者找到正确的方向;但更多的实际场景中,会产生如下问题:

  • 两个执行者碰撞越来越激烈,但始终谁也说服不了谁
  • 决策者越听越迷茫,不知道该如何抉择

在我看来,这类决策者往往是偏管理,技术上的掌控力不足,导致在决策时没有足够的倾向性;同时,与执行者之间的信任也不足,就希望把决策这件事下移、尝试着走平衡之道。

我个人的想法是:先选择一个能力相对优秀的执行者,认真评估其方案,然后交由对方执行;最后哪怕失败了,也可以通过复盘改进,想想下次如何更好地决策,更好地把控方案;当然,如果你认为纯粹是执行者的问题,那就换个可信赖的人。

2022-01-07 CNCF-Kubernetes

今天,我们一起来看看CNCF中的最核心项目 - KubernetesKubernetes相关内容非常庞大,我们依旧关注聚焦于核心能力。

Kubernetes 位于CNCF核心的 调度与编排 模块,也就是整个解决方案的基石。在CNCF上的介绍为:

Kubernetes is an open-source system for automating deployment, scaling, and management of containerized applications.

对这个定义,我们关注两个点:

  • automating deployment, scaling, and management 自动化的部署、扩缩容和管理,这是k8s核心能力;
  • containerized applications 容器化应用,这是k8s操作的基本对象;

为了更好地介绍Kubernetes,我对官方首页描述中的关键特性再做一些更详细地说明:

Automated rollouts and rollbacks

自动化扩缩容和升级回滚。这个特性是k8s最核心的,也是大规模推广的根本原因。

看起来,这功能描述与CICD流程差不多,但使用体验差距很大。在传统的模式下,我们执行的是一个具体的动作,比如扩1个机器、升级2个程序等;而在k8s里,使用者只要声明最终的预期状态,比如5台机器运行v1.0版本的程序,那么整个系统该扩容还是缩容、该升级还是回滚,都由k8s自行根据当前状态进行判断。

这个,就是云原生的一大特性:声明式API ,而不是传统上的命令式API

声明式API不一定比命令式API好。在应用程序开发时,命令式API更容易理解。

Service discovery and load balancing

服务发现与负载均衡。这个功能很大程度上减少了分布式软件运行模式的复杂性。

服务发现,以前非常依赖zookeeper/etcd等这类注册中心,往往需要侵入到业务代码;而负载均衡,则很依赖nginx这类软件,并在上面做复杂配置。

当然,k8s给出的只是通用解法,对一些具备很强业务属性的服务发现与负载均衡,仍需要程序自行实现。

Storage orchestration

存储编排。存储的编排是k8s重点演进的功能。

k8s抽象了存储概念,从传统的本地存储扩展为分布式云存储,对上层应用屏蔽了存储这块的复杂度。

Designed for extensibility

为扩展性而设计。

扩展性是k8s非常重视的点,无论是开放出容器、网络、存储等接口规范,还是像自定义资源(CRD)等插件的开放,都体现出了一种开放的精神,也是k8s如今能作为云原生标志性的软件的立足之本。

有一个点希望大家认识到:k8s的成功不是简单地因为开放性,更重要的是,它定义的这些开放性的规范与接口,都是Google经过实践总结出来的经验,符合主流厂商的趋势与开发者的需求。

Github: https://github.com/Junedayday/code_reading

Blog: http://junes.tech/

Bilibili: https://space.bilibili.com/293775192

公众号: golangcoding

二维码

go-tip

Ent概览

Simple, yet powerful ORM for modeling and querying data.

Ent作为一款由Facebook开源的库,官方定义为An entity framework for Go。从整个微服务框架来看,它更准确的定位应是 数据模型层的工具库。了解Ent这款企业级工具的大致实现,不仅有助于我们在技术选型时拓宽视野,也能帮助我们能更好地认识数据模型层。

阅读全文 »

go-tip

Wire概览

在讲解Kratos的过程中,我们引入了google推出的wire这个工具。我们先阅读一下官方的定义:

Wire is a code generation tool that automates connecting components using dependency injection.

从关键词入手:

  • code generation 代码生成,一方面说明了有学习成本,需要了解这个工具的原理;另一方面,也说明了它的目标是消除重复性的coding
  • automates connecting components 自动连接组件,明确了wire工具的目标是将多个对象组合起来
  • dependency injection 依赖注入,指明了wire实现自动连接组件的思想。依赖注入是一个很强大的功能,我会在下面结合具体的case聊一聊

我们从具体的case着手,学习wire这个工具。

阅读全文 »

go-tip

Go Micro框架概况

截止到本文发布时,Go-Micro在github上的star数达到了10.8k,也已经累计发布了v1、v2、v3这三个大版本,目前前两个已经停止维护。

本文主要以最新的技术视角去看待这个框架,所以会集中目光在v3版本。本文包含大量个人的主观观点,请大家选择性听取,更欢迎与我讨论。

阅读全文 »

go-tip

Kratos框架概况

截止到本文发布时,Kratos在github上的star数达到了15.9k。其中,在2021年7月,也正式推出了v2这个大版本。

本人并不是Kratos的重度使用者,主要会通过官方介绍对它的特性进行剖析。接下来的内容依旧包含大量主观认知,可能会对官方文档有理解上的偏差,欢迎大家与我讨论。

阅读全文 »

Go-Framework

作为云原生程序监控的标准组件,Prometheus支持了各类Paas、Saas平台,并提供了一整套采集+存储+展示的解决方案。

今天我们专注于自定义服务中的Prometheus的监控,在框架中引入Prometheus相关的组件。关于更细致的使用方式,我会给出相关的链接,有兴趣进一步学习Prometheus的同学可以边参考资料边实践。

阅读全文 »