• 关于 单元数据怎么安装 的搜索结果

回答

Layout Go工程项目的整体组织 首先我们看一下整个 Go 工程是怎么组织起来的。 很多同事都在用 GitLab 的,GitLab 的一个 group 里面可以创建很多 project。如果我们进行微服务化改造,以前很多巨石架构的应用可能就拆成了很多个独立的小应用。那么这么多小应用,你是要建 N 个 project 去维护,还是说按照部门或者组来组织这些项目呢?在 B 站的话,我们之前因为是 Monorepo,现在是按照部门去组织管理代码,就是说在单个 GitLab 的 project 里面是有多个 app 的,每一个 app 就表示一个独立的微服务,它可以独立去交付部署。所以说我们看到下面这张图里面,app 的目录里面是有好多个子目录的,比方说我们的评论服务,会员服务。跟 app 同级的目录有一个叫 pkg,可以存放业务有关的公共库。这是我们的一个组织方式。当然,还有一种方式,你可以按照 GitLab 的 project 去组织,但我觉得这样的话可能相对要创建的 project 会非常多。 如果你按部门组织的话,部门里面有很多 app,app 目录怎么去组织?我们实际上会给每一个 app 取一个全局唯一名称,可以理解为有点像 DNS 那个名称。我们对业务的命名也是一样的,我们基本上是三段式的命名,比如账号业务,它是一个账号业务、服务、子服务的三段命名。三段命名以后,在这个 app 目录里面,你也可以按照这三层来组织。比如我们刚刚说的账号目录,我可能就是 account 目录,然后 VIP,在 VIP 目录下可能会放各种各样的不同角色的微服务,比方说可能有一些是做 job,做定时任务或者流式处理的一些任务,有可能是做对外暴露的 API 的一些服务,这个就是我们关于整个大的 app 的组织的一种形式。 微服务中的 app 服务分类 微服务中单个 app 的服务里又分为几类不同的角色。我们基本上会把 app 分为 interface(BFF)、service、job(补充:还有一个 task,偏向定时执行,job 偏向流式) 和 admin。 Interface 是对外的业务网关服务,因为我们最终是面向终端用户的 API,面向 app,面向 PC 场景的,我们把这个叫成业务网关。因为我们不是统一的网关,我们可能是按照大的业务线去独立分拆的一些子网关,这个的话可以作为一个对外暴露的 HTTP 接口的一个目录去组织它的代码,当然也可能是 gRPC 的(参考 B 站对外的 gRPC Moss 分享)。 Service 这个角色主要是面向对内通信的微服务,它不直接对外。也就是说,业务网关的请求会转发或者是会 call 我们的内部的 service,它们之间的通讯可能是使用自己的 RPC,在 b 站我们主要是使用 gRPC。使用 gRPC 通讯以后,service 它因为不直接对外,service 之间可能也可以相互去 call。 Admin 区别于 service,很多应用除了有面向用户的一些接口,实际上还有面向企业内部的一些运营侧的需求,通常数据权限更高,从安全设计角度需要代码物理层面隔离,避免意外。 第四个是 ecode。我们当时也在内部争论了很久,我们的错误码定义到底是放在哪里?我们目前的做法是,一个应用里面,假设你有多种角色,它们可能会复用一些错误码。所以说我们会把我们的 ecode 给单独抽出来,在这一个应用里面是可以复用的。注意,它只在这一个应用里面复用,它不会去跨服跨目录应用,它是针对业务场景的一个业务错误码的组织。 App 目录组织 我们除了一个应用里面多种角色的这种情况,现在展开讲一下具体到一个 service 里面,它到底是怎么组织的。我们的 app 目录下大概会有 api、cmd、configs、 internal 目录,目录里一般还会放置 README、CHANGELOG、OWNERS。 API 是放置 api 定义以及对应的生成的 client 代码,包含基于 pb 定义(我们使用 PB 作为 DSL 描述 API) 生成的 swagger.json。 而 cmd,就是放 main 函数的。Configs 目录主要是放一些服务所需的配置文件,比方说说我们可能会使用 TOML 或者是使用 YAML 文件。 Internal 的话,它里面有四个子目录,分别是 model、dao、service 和 server。Model 的定位职责就是对我们底层存储的持久化层或者存储层的数据的映射,它是具体的 Go 的一个 struct。我们再看 dao,你实际就是要操作 MySQL 或者 Redis,最终返回的就是这些 model(存储映射)。Service 组织起来比较简单,就是我们通过 dao 里面的各个方法来完成一个完整的业务逻辑。我们还看到有个 server,因为我一个微服务有可能企业内部不一定所有 RPC 都统一,那我们处于过渡阶段,所以 server 里面会有两个小目录,一个是 HTTP 目录,暴露的是 HTTP 接口,还有一个是 gRPC 目录,我们会暴露 gRPC 的协议。所以在 server 里面,两个不同的启动的 server,就是说一个服务和启动两个端口,然后去暴露不同的协议,HTTP 接 RPC,它实际上会先 call 到 service,service 再 call 到 dao,dao 实际上会使用 model 的一些数据定义 struct。但这里面有一个非常重要的就是,因为这个结构体不能够直接返回给我们的 api 做外对外暴露来使用,为什么?因为可能从数据库里面取的敏感字段,当我们实际要返回到 api 的时候,可能要隐藏掉一些字段,在 Java 里面,会抽象的一个叫 DTO 的对象,它只是用来传输用的,同理,在我们 Go 里面,实际也会把这些 model 的一些结构体映射成 api 里面的结构体(基于 PB Message 生成代码后的 struct)。 Rob Pike 当时说过的一句话,a little copying is better than a little dependency,我们就遵循了这个理念。在我们这个目录结构里面,有 internal 目录,我们知道 Go 的目录只允许这个目录里面的人去 import 到它,跨目录的人实际是不能直接引用到它的。所以说,我们看到 service 有一个 model,那我的 job 代码,我做一些定时任务的代码或者是我的网关代码有可能会映射同一个 model,那是不是要把这个 model 放到上一级目录让大家共享?对于这个问题,其实我们当时内部也争论过很久。我们认为,每一个微服务应该只对自己的 model 负责,所以我们宁愿去做一小部分的代码 copy,也不会去为了几个服务之间要共享这一点点代码,去把这个 model 提到和 app 目录级别去共用,因为你一改全错,当然了,你如果是拷贝的话,就是每个地方都要去改,那我们觉得,依赖的问题可能会比拷贝代码相对来说还是要更复杂的。 这个是一个标准的 PB 文件,就是我们内部的一个 demo 的 service。最上面的 package 是 PB 的包名,demo.service.v1,这个包使用的是三段式命名,全局唯一的名称。那这个名称为什么不是用 ID?我见过有些公司对内部做的 CMDB 或者做服务树去管理企业内部微服务的时候,是用了一些名称加上 ID 来搞定唯一性,但是我们知道后面那一串 ID 数字是不容易被传播或者是不容易被记住的,这也是 DNS 出来的一个意义,所以我们用绝对唯一的一个名称来表示这个包的名字,在后面带上这一个 PB 文件的版本号 V1。 我们看第二段定义,它有个 Service Demo 代码,其实就表示了我们这个服务要启动的服务的一个名称,我们看到这个服务名称里面有很多个 RPC 的方法,表示最终这一个应用或者这个 service 要对外暴露这几个 RPC 的方法。这里面有个小细节,我们看一下 SayHello 这个方法,实际它有 option 的一个选项。通过这一个 PB 文件,你既可以描述出你要暴露的是 gRPC 协议,又暴露出 HTTP 的一个接口,这个好处是你只需要一个 PB 文件描述你暴露的所有 api。我们回想一下,我们刚刚目录里面有个 api 目录,实际这里面就是放这一个 PB 文件,描述这一个工程到底返回的接口是什么。不管是 gRPC 还是 HTTP 都是这一个文件。还有一个好处是什么?实际上我们可以在 PB 文件里面加上很多的注释。用 PB 文件的好处是你不需要额外地再去写文档,因为写文档和写服务的定义,它本质上是两个步骤,特别容易不一致,接口改了,文档不同步。我们如果基于这一个 PB 文件,它生成的 service 代码或者调用代码或者是文档都是唯一的。 依赖顺序与 api 维护 就像我刚刚讲到的,model 是一个存储层的结构体的一一映射,dao 处理一些数据读写包,比方说数据库缓存,server 的话就是启动了一些 gRPC 或者 HTTP Server,所以它整个依赖顺序如下:main 函数启动 server,server 会依赖 api 定义好的 PB 文件,定义好这些方法或者是服务名之后,实际上生成代码的时候,比方说 protocbuf 生成代码的时候,它会把抽象 interface 生成好。然后我们看一下 service,它实际上是弱依赖的 api,就是说我的 server 启动以后,要注册一个具体的业务代码的逻辑,映射方法,映射名字,实际上是弱依赖的 api 生成的 interface 的代码,你就可以很方便地启动你的 server,把你具体的 service 的业务逻辑给注入到这个 server,和方法进行一一绑定。最后,dao 和 service 实际上都会依赖这个 model。 因为我们在 PB 里面定义了一些 message,这些 message 生成的 Go 的 struct 和刚刚 model 的 struct 是两个不同的对象,所以说你要去手动 copy 它,把它最终返回。但是为了快捷,你不可能每次手动去写这些代码,因为它要做 mapping,所以我们又把 K8s 里类似 DeepCopy 的两个结构体相互拷贝的工具给抠出来了,方便我们内部 model 和 api 的 message 两个代码相互拷贝的时候,可以少写一些代码,减少一些工作量。 上面讲的就是我们关于工程的一些 layout 实践。简单回溯一下,大概分为几块,第一就是 app 是怎么组织的,app 里面有多种角色的服务是怎么组织的,第三就是一个 app 里面的目录是怎么组织的,最后我重点讲了一下 api 是怎么维护的。 Unittest 测试方法论 现在回顾一下单元测试。我们先看这张图,这张图是我从《Google 软件测试之道》这本书里面抠出来的,它想表达的意思就是最小型的测试不能给我们的最终项目的质量带来最大的信心,它比较容易带来一些优秀的代码质量,良好的异常处理等等。但是对于一个面向用户场景的服务,你只有做大型测试,比方做接口测试,在 App 上验收功能的这种测试,你应用交付的信心可能会更足。这个其实要表达的就是一个“721 原则”。我们就是 70% 写小型测试,可以理解为单元测试,因为它相对来说好写,针对方法级别。20% 是做一些中型测试,可能你要连调几个项目去完成你的 api。剩下 10% 是大型测试,因为它是最终面向用户场景的,你要去使用我们的 App,或者用一些测试 App 去测试它。这个就是测试的一些简单的方法论。 单元测试原则 我们怎么去对待 Go 里面的单元测试?在《Google 软件测试之道》这本书里面,它强调的是对于一个小型测试,一个单元测试,它要有几个特质。它不能依赖外部的一些环境,比如我们公司有测试环境,有持续集成环境,有功能测试环境,你不能依赖这些环境构建自己的单元测试,因为测试环境容易被破坏,它容易有数据的变更,数据容易不一致,你之前构建的案例重跑的话可能就会失败。 我觉得单元测试主要有四点要求。第一,快速,你不能说你跑个单元测试要几分钟。第二,要环境一致,也就是说你跑测试前和跑测试后,它的环境是一致的。第三,你写的所有单元测试的方法可以以任意顺序执行,不应该有先后的依赖,如果有依赖,也是在你测试的这个方法里面,自己去 setup 和 teardown,不应该有 Test Stub 函数存在顺序依赖。第四,基于第三点,你可以做并行的单元测试,假设我写了一百个单元测试,一个个跑肯定特别慢。 doker-compose 最近一段时间,我们演进到基于 docker-compose 实现跨平台跨语言环境的容器依赖管理方案,以解决运行 unittest 场景下的容器依赖问题。 首先,你要跑单元测试,你不应该用 VPN 连到公司的环境,好比我在星巴克点杯咖啡也可以写单元测试,也可以跑成功。基于这一点,Docker 实际上是非常好的解决方式。我们也有同学说,其他语言有一些 in-process 的 mock,是不是可以启动 MySQL 的 mock ,然后在 in-process 上跑?可以,但是有一个问题,你每一个语言都要写一个这样的 mock ,而且要写非常多种,因为我们中间件越来越多,MySQL,HBase,Kafka,什么都有,你很难覆盖所有的组件 Mock。这种 mock 或者 in-process 的实现不能完整地代表线上的情况,比方说,你可能 mock 了一个 MySQL,检测到 query 或者 insert ,没问题,但是你实际要跑一个 transaction,要验证一些功能就未必能做得非常完善了。所以基于这个原因,我们当时选择了 docker-compose,可以很好地解决这个问题。 我们对开发人员的要求就是,你本地需要装 Docker,我们开发人员大部分都是用 Mac,相对来说也比较简单,Windows 也能搞定,如果是 Linux 的话就更简单了。本地安装 Docker,本质上的理解就是无侵入式的环境初始化,因为你在容器里面,你拉起一个 MySQL,你自己来初始化数据。在这个容器被销毁以后,它的环境实际上就满足了我们刚刚提的环境一致的问题,因为它相当于被重置了,也可以很方便地快速重置环境,也可以随时随地运行,你不需要依赖任何外部服务,这个外部服务指的是像 MySQL 这种外部服务。当然,如果你的单元测试依赖另外一个 RPC 的 service 的话,PB 的定义会生成一个 interface,你可以把那个 interface 代码给 mock 掉,所以这个也是能做掉的。对于小型测试来说,你不依赖任何外部环境,你也能够快速完成。 另外,docker-compose 是声明式的 API,你可以声明你要用 MySQL,Redis,这个其实就是一个配置文件,非常简单。这个就是我们在单元测试上的一些实践。 我们现在看一下,service 目录里面多了一个 test 目录,我们会在这个里面放 docker-compose 的 YAML 文件来表示这次单元化测试需要初始化哪些资源,你要构建自己的一些测试的数据集。因为是这样的,你是写 dao 层的单元测试的话,可能就需要 database.sql 做一些数据的初始化,如果你是做 service 的单元测试的话,实际你可以把整个 dao 给 mock 掉,我觉得反而还相对简单,所以我们主要针对场景就是在 dao 里面偏持久层的,利用 docker-compose 来解决。 容器的拉起,容器的销毁,这些工作到底谁来做?是开发同学自己去拉起和销毁,还是说你能够把它做成一个 Library,让我们的同学写单元测试的时候比较方便?我倾向的是后者。所以在我们最终写单元测试的时候,你可以很方便地 setup 一个依赖文件,去 setup 你的容器的一些信息,或者把它销毁掉。所以说,你把环境准备好以后,最终可以跑测试代码也非常方便。当然我们也提供了一些命令函,就是 binary 的一些工具,它可以针对各个语言方便地拉起容器和销毁容器,然后再去执行代码,所以我们也提供了一些快捷的方式。 刚刚我也提到了,就是我们对于 service 也好,API 也好,因为依赖下层的 dao 或者依赖下层的 service,你都很方便 mock 掉,这个写单元测试相对简单,这个我不展开讲,你可以使用 GoMock 或者 GoMonkey 实现这个功能。 Toolchain 我们利用多个 docker-compose 来解决 dao 层的单元测试,那对于我刚刚提到的项目的一些规范,单元测试的一些模板,甚至是我写了一些 dao 的一些占位符,或者写了一些 service 代码的一些占位符,你有没有考虑过这种约束有没有人会去遵循?所以我这里要强调一点,工具一定要大于约束和文档,你写了约束,写了文档,那么你最终要通过工具把它落实。所以在我们内部会有一个类似 go tool 的脚手架,叫 Kratos Tool,把我们刚刚说的约定规范都通过这个工具一键初始化。 对于我们内部的工具集,我们大概会分为几块。第一块就是 API 的,就是你写一个 PB 文件,你可以基于这个 PB 文件生成 gRPC,HTTP 的框架代码,你也可以基于这个 PB 文件生成 swagger 的一些 JSON 文件或者是 Markdown 文件。当然了,我们还会生成一些 API,用于 debug 的 client 方便去调试,因为我们知道,gRPC 调试起来相对麻烦一些,你要去写代码。 还有一些工具是针对 project 的,一键生成整个应用的 layout,非常方便。我们还提了 model,就是方便 model 和 DTO,DTO 就是 API 里面定义的 message 的 struct 做 DeepCopy,这个也是一个工具。 对于 cache 的话,我们操作 memcache,操作 Redis 经常会要做什么逻辑?假如我们有一个 cache aside 场景,你读了一个 cache,cache miss 要回原 DB,你要把这个缓存回塞回去,甚至你可能这个回塞缓存想异步化,甚至是你要去读这个 DB 的时候要做归并回源(singleflight),我们把这些东西做成一些工具,让它整个回源到 DB 的逻辑更加简单,就是把这些场景描述出来,然后你通过工具可以一键生成这些代码,所以也是会比较方便。 我们再看最后一个,就是 test 的一些工具。我们会基于项目里面,比方说 dao 或者是 service 定义的 interface 去帮你写好 mock 的代码,我直接在里面填,只要填代码逻辑就行了,所以也会加速我们的生产。 上图是 Kratos 的一个 demo,基本就是支持了一些 command。这里就是一个 kratos new kratos-demo 的一个工程,-d YourPath 把它导到某一个路径去,--proto 顺便把 API 里面的 proto 代码也生成了,所以非常简单,一行就可以很快速启动一个 HTTP 或者 gRPC 服务。 我们知道,一个微服务的框架实际非常重,有很多初始化的方式等等,非常麻烦。所以说,你通过脚手架的方式就会非常方便,工具大于约定和文档这个这个理念就是这么来的。 Configuration 讲完工具以后,最后讲一下配置文件。我为什么单独提一下配置文件?实际它也是工程化的一部分。我们一个线上的业务服务包含三大块,第一,应用程序,第二,配置文件,第三,数据集。配置文件最容易导致线上出 bug,因为你改一行配置,整个行为可能跟 App 想要的行为完全不一样。而且我们的代码的开发交付需要经过哪些流程?需要 commit 代码,需要 review,需要单元测试,需要 CD,需要交付到线上,需要灰度,它的整个流程是非常长的。在一步步的环境里面,你的 bug 需要前置解决,越前置解决,成本越低。因为你的代码的开发流程是这么一个 pipeline,所以 bug 最终流到线上的概率很低,但是配置文件没有经过这么复杂的流程,可能大家发现线上有个问题,决定要改个线上配置,就去配置中心或者配置文件改,然后 push 上线,接着就问题了,这个其实很常见。 从 SRE 的角度来说,导致线上故障的主因就是来自配置变更,所以 SRE 很大的工作是控制变更管理,如果能把变更管理做好,实际上很多问题都不会出现。配置既然在整个应用里面这么重要,那在我们整个框架或者在 Go 的工程化实践里面,我们应该对配置文件做一些什么事情? 我觉得是几个。第一,我们的目标是什么?配置文件不应该太复杂,我见过很多框架,或者是业务的一些框架,它实际功能非常强大,但是它的配置文件超级多。我就发现有个习惯,只要有一个同事写错了这个配置,当我新起一个项目的时候,一定会有人把这个错误的配置拷贝到另外一个系统里面去。然后当发现这个应用出问题的时候,我们一般都会内部说一下,你看看其他同事有没有也配错的,实际这个配错概率非常高。因为你的配置选项越多,复杂性越高,它越容易出错。所以第一个要素就是说,尽量避免复杂的配置文件。配得越多,越容易出错。 第二,实际我们的配置方式也非常多,有些用 JSON,有些用 YAML,有些用 Properties,有些用 INI。那能不能收敛成通用的一种方式呢?无论它是用 Python 的脚本也好,或者是用 JSON 也好,你只要有一种唯一的约定,不需要太多样的配置方式,对我们的运维,对我们的 SRE 同时来说,他跨项目的变更成本会变低。 第三,一定要往简单化去努力。这句话其实包含了几个方面的含义。首先,我们很多配置它到底是必须的还是可选的,如果是可选,配置文件是不是就可以把它踢掉,甚至不要出现?我曾经有一次看到我们 Java 同事的配置 retry 有一个重试默认是零,内部重试是 80 次,直接把 Redis cluster 打故障了,为什么?其实这种事故很低级,所以简单化努力的另外一层含义是指,我们在框架层面,尤其是提供 SDK 或者是提供 framework 的这些同事尽量要做一些防御编程,让这种错配漏配也处于一个可控的范围,比方重试 80 次,你觉得哪个 SDK 会这么做?所以这个是我们要考虑的。但是还有一点要强调的是,我们对于业务开发的同事,我们的配置应该足够的简单,这个简单还包含,如果你的日志基本上都是写在这个目录,你就不要提供这个配置给他,反而不容易出错。但是对于我们内部的一些 infrastructure,它可能需要非常复杂的配置来优化,根据我的场景去做优化,所以它是两种场景,一种是业务场景,足够简单,一种是我要针对我的通用的 infrastructure 去做场景的优化,需要很复杂的配置,所以它是两种场景,所以我们要想清楚你的业务到底是哪一种形态。 还有一个问题就是我们配置文件一定要做好权限的变更和跟踪,因为我们知道上线出问题的时候,我们的第一想法不是查 bug,是先止损,止损先找最近有没有变更。如果发现有变更,一般是先回滚,回滚的时候,我们通常只回滚了应用程序,而忘记回滚了配置。每个公司可能内部的配置中心,或者是配置场景,或者跟我们的二进制的交付上线都不一样,那么这里的理念就是你的应用程序和配置文件一定是同一个版本,或者是某种意义上让他们产生一个版本的映射,比方说你的应用程序 1.0,你的配置文件 2.0,它们之间存在一个强绑定关系,我们在回滚的时候应该是一起回滚的。我们曾经也因为类似的一些不兼容的配置的变更,二进制程序上线,但配置文件忘记回滚,出现过事故,所以这个是要强调的。 另外,配置的变更也要经过 review,如果没问题,应该也是按照 App 发布一样,先灰度,再放量,再全量等等类似的一种方式去推,演进式的这种发布,我们也叫滚动发布,我觉得配置文件也是一样的思路。 加入阿里云钉钉群享福利:每周技术直播,定期群内有奖活动、大咖问答 原文链接

有只黑白猫 2020-01-09 17:29:54 0 浏览量 回答数 0

问题

Nginx性能为什么如此吊

小柒2012 2019-12-01 21:20:47 15038 浏览量 回答数 3

回答

Go 的优势在于能够将简单的和经过验证的想法结合起来,同时避免了其他语言中出现的许多问题。本文概述了 Go 背后的一些设计原则和工程智慧,作者认为,Go 语言具备的所有这些优点,将共同推动其成为接替 Java 并主导下一代大型软件开发平台的最有力的编程语言候选。很多优秀的编程语言只是在个别领域比较强大,如果将所有因素都纳入考虑,没有其他语言能够像 Go 语言一样“全面开花”,在大型软件工程方面,尤为如此。 基于现实经验 Go 是由经验丰富的软件行业老手一手创建的,长期以来,他们对现有语言的各种缺点有过切身体会的痛苦经历。几十年前,Rob Pike 和 Ken Thompson 在 Unix、C 和 Unicode 的发明中起到了重要作用。Robert Griensemer 在为 JavaScript 和 Java 开发 V8 和 HotSpot 虚拟机之后,在编译器和垃圾收集方面拥有数十年的经验。有太多次,他们不得不等待 Google 规模的 C++/Java 代码库进行编译。于是,他们开始着手创建新的编程语言,将他们半个世纪以来的编写代码所学到的一切经验包含进去。 专注于大型工程 小型工程项目几乎可以用任何编程语言来成功构建。当成千上万的开发人员在数十年的持续时间压力下,在包含数千万行代码的大型代码库上进行协作时,就会发生真正令人痛苦的问题。这样会导致一些问题,如下: 较长的编译时间导致中断开发。代码库由几个人 / 团队 / 部门 / 公司所拥有,混合了不同的编程风格。公司雇佣了数千名工程师、架构师、测试人员、运营专家、审计员、实习生等,他们需要了解代码库,但也具备广泛的编码经验。依赖于许多外部库或运行时,其中一些不再以原始形式存在。在代码库的生命周期中,每行代码平均被重写 10 次,被弄得千疮百痍,而且还会发生技术偏差。文档不完整。 Go 注重减轻这些大型工程的难题,有时会以使小型工程变得更麻烦为代价,例如,代码中到处都需要几行额外的代码行。 注重可维护性 Go 强调尽可能多地将工作转给自动化的代码维护工具中。Go 工具链提供了最常用的功能,如格式化代码和导入、查找符号的定义和用法、简单的重构以及代码异味的识别。由于标准化的代码格式和单一的惯用方式,机器生成的代码更改看起来非常接近 Go 中人为生成的更改并使用类似的模式,从而允许人机之间更加无缝地协作。 保持简单明了 初级程序员为简单的问题创建简单的解决方案。高级程序员为复杂的问题创建复杂的解决方案。伟大的程序员找到复杂问题的简单解决方案。 ——Charles Connell 让很多人惊讶的一点是,Go 居然不包含他们喜欢的其他语言的概念。Go 确实是一种非常小巧而简单的语言,只包含正交和经过验证的概念的最小选择。这鼓励开发人员用最少的认知开销来编写尽可能简单的代码,以便许多其他人可以理解并使用它。 使事情清晰明了 良好的代码总是显而易见的,避免了那些小聪明、难以理解的语言特性、诡异的控制流和兜圈子。 许多语言都致力提高编写代码的效率。然而,在其生命周期中,人们阅读代码的时间却远远超过最初编写代码所需的时间(100 倍)。例如,审查、理解、调试、更改、重构或重用代码。在查看代码时,往往只能看到并理解其中的一小部分,通常不会有完整的代码库概述。为了解释这一点,Go 将所有内容都明确出来。 错误处理就是一个例子。让异常在各个点中断代码并在调用链上冒泡会更容易。Go 需要手动处理和返回每个错误。这使得它可以准确地显示代码可以被中断的位置以及如何处理或包装错误。总的来说,这使得错误处理编写起来更加繁琐,但是也更容易理解。 简单易学 Go 是如此的小巧而简单,以至于人们可以在短短几天内就能研究通整个语言及其基本概念。根据我们的经验,培训用不了一个星期(相比于掌握其他语言需要几个月),初学者就能够理解 Go 专家编写的代码,并为之做出贡献。为了方便吸引更多的用户,Go 网站提供了所有必要的教程和深入研究的文章。这些教程在浏览器中运行,允许人们在将 Go 安装到本地计算机上之前就能够学习和使用 Go。 解决之道 Go 强调的是团队之间的合作,而不是个人的自我表达。 在 Go(和 Python)中,所有的语言特性都是相互正交和互补的,通常有一种方法可以做一些事情。如果你想让 10 个 Python 或 Go 程序员来解决同一个问题,你将会得到 10 个相对类似的解决方案。不同的程序员在彼此的代码库中感觉更自在。在查看其他人的代码时,国骂会更少,而且人们的工作可以更好地融合在一起,从而形成了一致的整体,人人都为之感到自豪,并乐于工作。这还避免了大型工程的问题,如: 开发人员认为良好的工作代码很“混乱”,并要求在开始工作之前进行重写,因为他们的思维方式与原作者不同。 不同的团队成员使用不同的语言子集来编写相同代码库的部分内容。 ![image.png](https://ucc.alicdn.com/pic/developer-ecology/e64418f1455d46aaacfdd03fa949f16d.png) 简单、内置的并发性 Go 专为现代多核硬件设计。 目前使用的大多数编程语言(Java、JavaScript、Python、Ruby、C、C++)都是 20 世纪 80 年代到 21 世纪初设计的,当时大多数 CPU 只有一个计算内核。这就是为什么它们本质上是单线程的,并将并行化视为边缘情况的马后炮。通过现成和同步点之类的附加组件来实现,而这些附加组件既麻烦又难以正确使用。第三方库虽然提供了更简单的并发形式,如 Actor 模型,但是总有多个可用选项,结果导致了语言生态系统的碎片化。今天的硬件拥有越来越多的计算内核,软件必须并行化才能高效运行。Go 是在多核处理器时代编写的,并且在语言中内置了简单、高级的 CSP 风格并发性。 面向计算的语言原语 就深层而言,计算机系统接收数据,对其进行处理(通常要经过几个步骤),然后输出结果数据。例如,Web 服务器从客户端接收 HTTP 请求,并将其转换为一系列数据库或后端调用。一旦这些调用返回,它就将接收到的数据转换成 HTML 或 JSON 并将其输出给调用者。Go 的内置语言原语直接支持这种范例: 结构表示数据 读和写代表流式 IO 函数过程数据 goroutines 提供(几乎无限的)并发性 在并行处理步骤之间传输管道数据 因为所有的计算原语都是由语言以直接形式提供的,因此 Go 源代码更直接地表达了服务器执行的操作。 OO — 好的部分 更改基类中的某些内容的副作用 面向对象非常有用。过去几十年来,面向对象的使用富有成效,并让我们了解了它的哪些部分比其他部分能够更好地扩展。Go 在面向对象方面采用了一种全新的方法,并记住了这些知识。它保留了好的部分,如封装、消息传递等。Go 还避免了继承,因为它现在被认为是有害的,并为组合提供了一流的支持。 现代标准库 目前使用的许多编程语言(Java、JavaScript、Python、Ruby)都是在互联网成为当今无处不在的计算平台之前设计的。因此,这些语言的标准库只提供了相对通用的网络支持,而这些网络并没有针对现代互联网进行优化。Go 是十年前创建的,当时互联网已全面发展。Go 的标准库允许在没有第三方库的情况下创建更复杂的网络服务。这就避免了第三方库的常见问题: 碎片化:总是有多个选项实现相同的功能。 膨胀:库常常实现的不仅仅是它们的用途。 依赖地狱:库通常依赖于特定版本的其他库。 未知质量:第三方代码的质量和安全性可能存在问题。 未知支持:第三方库的开发可能随时停止支持。 意外更改:第三方库通常不像标准库那样严格地进行版本控制。 关于这方面更多的信息请参考 Russ Cox 提供的资料 标准化格式 Gofmt 的风格没有人会去喜欢,但人人都会喜欢 gofmt。 ——Rob Pike Gofmt 是一种以标准化方式来格式化 Go 代码的程序。它不是最漂亮的格式化方式,但却是最简单、最不令人生厌的格式化方式。标准化的源代码格式具有惊人的积极影响: 集中讨论重要主题: 它消除了围绕制表符和空格、缩进深度、行长、空行、花括号的位置等一系列争论。 开发人员在彼此的代码库中感觉很自在, 因为其他代码看起来很像他们编写的代码。每个人都喜欢自由地按照自己喜欢的方式进行格式化代码,但如果其他人按照自己喜欢的方式格式化了代码,这么做很招人烦。 自动代码更改并不会打乱手写代码的格式,例如引入了意外的空白更改。 许多其他语言社区现在正在开发类似 gofmt 的东西。当作为第三方解决方案构建时,通常会有几个相互竞争的格式标准。例如,JavaScript 提供了 Prettier 和 StandardJS。这两者都可以用,也可以只使用其中的一个。但许多 JS 项目并没有采用它们,因为这是一个额外的决策。Go 的格式化程序内置于该语言的标准工具链中,因此只有一个标准,每个人都在使用它。 快速编译 ![image.png](https://ucc.alicdn.com/pic/developer-ecology/8a76f3f07f484266af42781d9e7b8692.png) 对于大型代码库来说,它们长时间的编译是促使 Go 诞生的原因。Google 主要使用的是 C++ 和 Java,与 Haskell、Scala 或 Rust 等更复杂的语言相比,它们的编译速度相对较快。尽管如此,当编译大型代码库时,即使是少量的缓慢也会加剧编译的延迟,从而激怒开发人员,并干扰流程。Go 的设计初衷是为了提高编译效率,因此它的编译器速度非常快,几乎没有编译延迟的现象。这给 Go 开发人员提供了与脚本类语言类似的即时反馈,还有静态类型检查的额外好处。 交叉编译 由于语言运行时非常简单,因此它被移植到许多平台,如 macOS、Linux、Windows、BSD、ARM 等。Go 可以开箱即用地为所有这些平台编译二进制文件。这使得从一台机器进行部署变得很容易。 快速执行 Go 的运行速度接近于 C。与 JITed 语言(Java、JavaScript、Python 等)不同,Go 二进制文件不需要启动或预热的时间,因为它们是作为编译和完全优化的本地代码的形式发布的。Go 的垃圾收集器仅引入微秒量级的可忽略的停顿。除了快速的单核性能外,Go 还可以轻松利用所有的 CPU 内核。 内存占用小 像 JVM、Python 或 Node 这样的运行时不仅仅在运行时加载程序代码,每次运行程序时,它们还会加载大型且高度复杂的基础架构,以进行编译和优化程序。如此一来,它们的启动时间就变慢了,并且还占用了大量内存(数百兆字节)。而 Go 进程的开销更小,因为它们已经完全编译和优化,只需运行即可。Go 还以非常节省内存的方式来存储数据。在内存有限且昂贵的云环境中,以及在开发过程中,这一点非常重要。我们希望在一台机器上能够快速启动整个堆栈,同时将内存留给其他软件。 部署规模小 Go 的二进制文件大小非常简洁。Go 应用程序的 Docker 镜像通常比用 Java 或 Node 编写的等效镜像要小 10 倍,这是因为它无需包含编译器、JIT,以及更少的运行时基础架构的原因。这些特点,在部署大型应用程序时很重要。想象一下,如果要将一个简单的应用程序部署到 100 个生产服务器上会怎么样?如果使用 Node/JVM 时,我们的 Docker 注册表就必须提供 100 个 docker 镜像,每个镜像 200MB,那么一共就需要 20GB。要完成这些部署就需要一些时间。想象一下,如果我们想每天部署 100 次的话,如果使用 Go 服务,那么 Docker 注册表只需提供 10 个 docker 镜像,每个镜像只有 20MB,共只需 2GB 即可。大型 Go 应用程序可以更快、更频繁地部署,从而使得重要更新能够更快地部署到生产环境中。 独立部署 Go 应用程序部署为一个包含所有依赖项的单个可执行文件,并无需安装特定版本的 JVM、Node 或 Python 运行时;也不必将库下载到生产服务器上,更无须对运行 Go 二进制文件的机器进行任何更改。甚至也不需要讲 Go 二进制文件包装到 Docker 来共享他们。你需要做的是,只是将 Go 二进制文件放到服务器上,它就会在那里运行,而不用关心服务器运行的是什么。前面所提到的那些,唯一的例外是使用net和os/user包时针对对glibc的动态链接。 供应依赖关系 Go 有意识避免使用第三方库的中央存储库。Go 应用程序直接链接到相应的 Git 存储库,并将所有相关代码下载(供应)到自己的代码库中。这样做有很多好处: 在使用第三方代码之前,我们可以对其进行审查、分析和测试。该代码就和我们自己的代码一样,是我们应用程序的一部分,应该遵循相同的质量、安全性和可靠性标准。 无需永久访问存储依赖项的各个位置。从任何地方(包括私有 Git repos)获取第三方库,你就能永久拥有它们。 经过验收后,编译代码库无需进一步下载依赖项。 若互联网某处的代码存储库突然提供不同的代码,这也并不足为奇。 即使软件包存储库速度变慢,或托管包不复存在,部署也不会因此中断。 兼容性保证 Go 团队承诺现有的程序将会继续适用于新一代语言。这使得将大型项目升级到最新版本的编译器会非常容易,并且可从它们带来的许多性能和安全性改进中获益。同时,由于 Go 二进制文件包含了它们需要的所有依赖项,因此可以在同一服务器上并行运行使用不同版本的 Go 编译器编译的二进制文件,而无需进行复杂的多个版本的运行时设置或虚拟化。 文档 在大型工程中,文档对于使软件可访问性和可维护性非常重要。与其他特性类似,Go 中的文档简单实用: 由于它是嵌入到源代码中的,因此两者可以同时维护。 它不需要特殊的语法,文档只是普通的源代码注释。 可运行单元测试通常是最好的文档形式。因此 Go 要求将它们嵌入到文档中。 所有的文档实用程序都内置在工具链中,因此每个人都使用它们。 Go linter 需要导出元素的文档,以防止“文档债务”的积累。 商业支持的开源 当商业实体在开放式环境下开发时,那么一些最流行的、经过彻底设计的软件就会出现。这种设置结合了商业软件开发的优势——一致性和精细化,使系统更为健壮、可靠、高效,并具有开放式开发的优势,如来自许多行业的广泛支持,多个大型实体和许多用户的支持,以及即使商业支持停止的长期支持。Go 就是这样发展起来的。 缺点 当然,Go 也并非完美无缺,每种技术选择都是有利有弊。在决定选择 Go 之前,有几个方面需要进行考虑考虑。 未成熟 虽然 Go 的标准库在支持许多新概念(如 HTTP 2 Server push 等)方面处于行业领先地位,但与 JVM 生态系统中的第三方库相比,用于外部 API 的第三方 Go 库可能不那么成熟。 即将到来的改进 由于清楚几乎不可能改变现有的语言元素,Go 团队非常谨慎,只在新特性完全开发出来后才添加新特性。在经历了 10 年的有意稳定阶段之后,Go 团队正在谋划对语言进行一系列更大的改进,作为 Go 2.0 之旅的一部分。 无硬实时 虽然 Go 的垃圾收集器只引入了非常短暂的停顿,但支持硬实时需要没有垃圾收集的技术,例如 Rust。 结语 本文详细介绍了 Go 语言的一些优秀的设计准则,虽然有的准则的好处平常看起来没有那么明显。但当代码库和团队规模增长几个数量级时,这些准则可能会使大型工程项目免于许多痛苦。总的来说,正是这些设计准则让 Go 语言成为了除 Java 之外的编程语言里,用于大型软件开发项目的绝佳选择。

有只黑白猫 2020-01-07 14:11:38 0 浏览量 回答数 0

拒绝“不安全”网站标签

精选SSL证书,2年仅需567元

回答

这个问题困扰了公司里面很多比较资深的人,尝试过很多方案,都是失败告终, 希望有类是经验的朋友们献策。 如果问题描述的不是很清晰的话,可以通过我的QQ【1103205613】进行探讨。 期待您的分享。。。。。###### 将windows下的字体复制一份到linux下面就可以了  ######补充两问题, 1.在fontsConfig.xml中期中的alias 与mapping ,encoding,composite它们之间是如何区别的, 2. 在*.rpt文件中定义的font-famliy的属性名称是如何和系统的字体进行匹配的?在配置文件中是如何体现的, 求解。。。。。。 期待中。。。。######谢谢你的回复,这种方案,尝试过,但是没有效果,######你把那几个签名的xml中的类删掉。不过可能会引起无法访问本地文件。或者你重新签名一下。######谢谢,我尝试过将META-INF中的文件跟清除掉,但是我有个问题,对于jar进行签名的话,是否需要遵循jar签名的机制,还是单独的重新进行签名就可? 同时还有个问题,为什么在weblogic中进行发布的时候,就不会出现这种问题?###### 引用来自“保罗的寓言”的答案 这个问题困扰了公司里面很多比较资深的人,尝试过很多方案,都是失败告终, 希望有类是经验的朋友们献策。 如果问题描述的不是很清晰的话,可以通过我的QQ【1103205613】进行探讨。 期待您的分享。。。。。 保罗,你好! 不晓得你这个问题最后怎么解决的呢? 我也遇到你同样的问题。导出PDF后,字体样式改变,一些没有相应字体的用户电脑打开该PDF文件时,提示需要下载字体包。 开发环境:Birt3.7.0,Windows。 在Eclipse Birt环境中,通过修改plugins\org.eclipse.birt.report.engine.fonts_3.7.0.v20110602\fontsConfig_pdf.xml <Code> <font> <font-aliases> <mapping name="serif" font-family="SimSun" /> <mapping name="sans-serif" font-family="SimSun" /> <mapping name="monospace" font-family="SimSun" /> </font-aliases> <font-encodings> <encoding font-family="SimSun" encoding="utf-8" /> <encoding font-family="Times-Roman" encoding="Cp1252" /> <encoding font-family="Helvetica" encoding="Cp1252" /> <encoding font-family="Courier" encoding="Cp1252" /> <encoding font-family="Zapfdingbats" encoding="Cp1252" /> <encoding font-family="Symbol" encoding="Cp1252" /> <encoding font-family="STSong-Light" encoding="UniGB-UCS2-H" /> <encoding font-family="STSongStd-Light" encoding="UniGB-UCS2-H" /> <encoding font-family="MHei-Medium" encoding="UniCNS-UCS2-H" /> <encoding font-family="MSung-Light" encoding="UniCNS-UCS2-H" /> <encoding font-family="MSungStd-Light" encoding="UniCNS-UCS2-H" /> <encoding font-family="HeiseiMin-W3" encoding="UniJIS-UCS2-H" /> <encoding font-family="HeiseiKakuGo-W5" encoding="UniJIS-UCS2-H" /> <encoding font-family="KozMinPro-Regular" encoding="UniJIS-UCS2-H" /> <encoding font-family="HYGoThic-Medium" encoding="UniKS-UCS2-H" /> <encoding font-family="HYSMyeongJo-Medium" encoding="UniKS-UCS2-H" /> <encoding font-family="HYSMyeongJoStd" encoding="UniKS-UCS2-H" /> </font-encodings> <composite-font name="all-fonts"> <font font-family="Times-Roman" catalog="Western" /> <font font-family="SimSun" catalog="Chinese" /> <font font-family="HeiseiKakuGo-W5" catalog="Japanese" /> <font font-family="HYGoThic-Medium" catalog="Korean" /> </composite-font> </font> </Code> 在Birt中导出PDF,其字体都转化为宋体。其他用户也可以正常访问。但是无法部署到birt3.7.0环境中。因为只有在org.eclipse.birt.runtime_3.7.0.v20110615-1818.jar修改。把fontsConfig_pdf.xml添加进去后,导出PDF后打开不了。 我的问题是:怎么去修改org.eclipse.birt.runtime_3.7.0.v20110615-1818.jar这个文件才有效呢? ###### 引用来自“小黑哥L”的答案 引用来自“保罗的寓言”的答案 这个问题困扰了公司里面很多比较资深的人,尝试过很多方案,都是失败告终, 希望有类是经验的朋友们献策。 如果问题描述的不是很清晰的话,可以通过我的QQ【1103205613】进行探讨。 期待您的分享。。。。。 保罗,你好! 不晓得你这个问题最后怎么解决的呢? 我也遇到你同样的问题。导出PDF后,字体样式改变,一些没有相应字体的用户电脑打开该PDF文件时,提示需要下载字体包。 开发环境:Birt3.7.0,Windows。 在Eclipse Birt环境中,通过修改plugins\org.eclipse.birt.report.engine.fonts_3.7.0.v20110602\fontsConfig_pdf.xml <Code> <font> <font-aliases> <mapping name="serif" font-family="SimSun" /> <mapping name="sans-serif" font-family="SimSun" /> <mapping name="monospace" font-family="SimSun" /> </font-aliases> <font-encodings> <encoding font-family="SimSun" encoding="utf-8" /> <encoding font-family="Times-Roman" encoding="Cp1252" /> <encoding font-family="Helvetica" encoding="Cp1252" /> <encoding font-family="Courier" encoding="Cp1252" /> <encoding font-family="Zapfdingbats" encoding="Cp1252" /> <encoding font-family="Symbol" encoding="Cp1252" /> <encoding font-family="STSong-Light" encoding="UniGB-UCS2-H" /> <encoding font-family="STSongStd-Light" encoding="UniGB-UCS2-H" /> <encoding font-family="MHei-Medium" encoding="UniCNS-UCS2-H" /> <encoding font-family="MSung-Light" encoding="UniCNS-UCS2-H" /> <encoding font-family="MSungStd-Light" encoding="UniCNS-UCS2-H" /> <encoding font-family="HeiseiMin-W3" encoding="UniJIS-UCS2-H" /> <encoding font-family="HeiseiKakuGo-W5" encoding="UniJIS-UCS2-H" /> <encoding font-family="KozMinPro-Regular" encoding="UniJIS-UCS2-H" /> <encoding font-family="HYGoThic-Medium" encoding="UniKS-UCS2-H" /> <encoding font-family="HYSMyeongJo-Medium" encoding="UniKS-UCS2-H" /> <encoding font-family="HYSMyeongJoStd" encoding="UniKS-UCS2-H" /> </font-encodings> <composite-font name="all-fonts"> <font font-family="Times-Roman" catalog="Western" /> <font font-family="SimSun" catalog="Chinese" /> <font font-family="HeiseiKakuGo-W5" catalog="Japanese" /> <font font-family="HYGoThic-Medium" catalog="Korean" /> </composite-font> </font> </Code> 在Birt中导出PDF,其字体都转化为宋体。其他用户也可以正常访问。但是无法部署到birt3.7.0环境中。因为只有在org.eclipse.birt.runtime_3.7.0.v20110615-1818.jar修改。把fontsConfig_pdf.xml添加进去后,导出PDF后打开不了。 我的问题是:怎么去修改org.eclipse.birt.runtime_3.7.0.v20110615-1818.jar这个文件才有效呢? 我的邮箱是:alexxr.liu@sodexo.com 保罗兄如果能给点建议,将不胜感激!谢谢了先! ###### 1. 由于birt中的org.eclipse.birt.runtime_3.7.0.v20110615-1818.jar 是进过数字指纹的,一般修改了fontsConfig_pdf.xml都会有问题的,可以在外部引入的方式进行指定birt的报表的字体设置,如果没有很特别的中文的话,是不需要的处理字体的,因为birt pdf中使用的字体中基本上包含sumlight 字体的。即使没有的话,也会自动提示下载字体的。 2. 针对我题的问题的背景是,birt报表项目进行过运行环境的迁移,具体是有tomcat环境迁移到weblogic下面,造成导出pdf空白的主要原因 a. 字体的缺失, b. birt 中依赖的EMCASCript 相关的包和weblogic中的依赖包中存在版本差异,造成weblogic在启动的时候加载自身的包之后,不在加载项目中的有冲突的包,       解决: 将birt中的包,加入到启动启动脚本中, ###### 引用来自“保罗的寓言”的答案 1. 由于birt中的org.eclipse.birt.runtime_3.7.0.v20110615-1818.jar 是进过数字指纹的,一般修改了fontsConfig_pdf.xml都会有问题的,可以在外部引入的方式进行指定birt的报表的字体设置,如果没有很特别的中文的话,是不需要的处理字体的,因为birt pdf中使用的字体中基本上包含sumlight 字体的。即使没有的话,也会自动提示下载字体的。 2. 针对我题的问题的背景是,birt报表项目进行过运行环境的迁移,具体是有tomcat环境迁移到weblogic下面,造成导出pdf空白的主要原因 a. 字体的缺失, b. birt 中依赖的EMCASCript 相关的包和weblogic中的依赖包中存在版本差异,造成weblogic在启动的时候加载自身的包之后,不在加载项目中的有冲突的包,       解决: 将birt中的包,加入到启动启动脚本中, 非常感谢保罗兄的详细答复! 由于导出来的报表是需要发给许多不同的客户查看,有些客户没有安装相应的字体会提示下载AdobePDF的字体包,这给用户带来很大的不便。 可行的方法是在设计报表的时候,指定所有中文为宋体。但目前有上百个已存在的报表,修改测试均非常耗时,且以后开发也得时刻小心同样的问题。所以想找个一劳永逸的法子。 你提示的“在外部引入的方式进行指定birt的报表的字体设置”,这个具体些是怎样的办法呢? ######给相应的label、text等设置为宋体试试######换成FineReport应该很好解决###### 用FineReport来导出相对没有那么多问题: 1. 导出格式 PDF:导出PDF格式文件; Excel:导出Excel格式文件,又分为分页导出、原样导出、分页分Sheet导出,详细请查看 Excel多种导出方式文档; Word:导出Word格式文件,不支持导出悬浮元素,详细请查看 Word导出 Image:导出为图片,又分为JPG、PNG、GIF和BMP四种格式。 注:将报表输出为Word不支持导出悬浮元素和多Sheet下的不同页面设置;excel不支持Html中部分标签,单元格背景只支持纯色,另外在导出为图片时,不建议使用GIF格式,可以采用PNG替代,原因是导出GIF需要的内存比较大。 另:聚合报表和表单不支持导出。 2. 步骤 预览模板,点击工具栏中的 输出,即可以对模板进行多种格式的导出,如下图所示: 注:若模板有参数,需要查询出数据后才能够点击输出进行导出。 3. 示例 下面我们以GettingStarted.cpt导出成PDF样式为例说明导出方法。 3.1 打开模板 打开模板: %FR_HOME%\WebReport\WEB-INF\reportlets\GettingStarted.cpt。 3.2 分页预览 点击分页预览,选择完参数后,点击查询。 3.3 导出 点击工具栏上的 输出按钮,然后选择PDF格式,会弹出如下图所示的对话框: 点击保存,选择保存路径即可将数据导出成PDF格式。

kun坤 2020-06-06 00:28:11 0 浏览量 回答数 0
阿里云大学 云服务器ECS com域名 网站域名whois查询 开发者平台 小程序定制 小程序开发 国内短信套餐包 开发者技术与产品 云数据库 图像识别 开发者问答 阿里云建站 阿里云备案 云市场 万网 阿里云帮助文档 免费套餐 开发者工具 云栖号物联网 小程序开发制作 视频内容分析 企业网站制作 视频集锦 代理记账服务 2020阿里巴巴研发效能峰会 企业建站模板 云效成长地图 高端建站 云栖号弹性计算 阿里云云栖号 云栖号案例 云栖号直播