面向K8s设计误区

简介: K8s 取其精华去其糟粕,是我们程序员应该做的事情。

K8s设计模式

 

Kubernetes是一个具有普遍意义的容器编排工具,它提供了一套基于容器构建分布式系统的基础依赖,其意义等同于Linux在操作系统中的地位,可以认为是分布式的操作系统。

 

自定义资源

 

K8s提供了Pod、Service、Volume等一系列基础资源定义,为了更好提供扩展性,CRD 功能是在1.7 版本被引入。

 

用户可以根据自己的需求添加自定义的 Kubernetes 对象资源(CRD)。值得注意的是,这里用户自己添加的 Kubernetes 对象资源都是 native 的都是一等公民,和 Kubernetes 中自带的、原生的那些 Pod、Deployment 是同样的对象资源。在 Kubernetes 的 API Server 看来,它们都是存在于 etcd 中的一等资源。同时,自定义资源和原生内置的资源一样,都可以用 kubectl  来去创建、查看,也享有 RBAC、安全功能。用户可以开发自定义控制器来感知或者操作自定义资源的变化。

 

Operator

 

在自定义资源基础上,如何实现自定义资源创建或更新时的逻辑行为,K8s Operator提供了相应的开发框架。Operator通过扩展Kubernetes定义Custom Controller,list/watch 对应的自定义资源,在对应资源发生变化时,触发自定义的逻辑。

 

Operator 开发者可以像使用原生 API 进行应用管理一样,通过声明式的方式定义一组业务应用的期望终态,并且根据业务应用的自身特点进行相应控制器逻辑编写,以此完成对应用运行时刻生命周期的管理并持续维护与期望终态的一致性。

 

通俗的理解

 

CRD是K8s标准化的资源扩展能力,以java为例,int、long、Map、Object是java内置的类,用户可以自定义Class实现类的扩展,CRD就是K8s中的自定义类,CR就是对应类的一个instance。

 

Operator模式 = 自定义类 + 观察者模式,Operator模式让大家编写K8s的扩展变得非常简单快捷,逐渐成为面向K8s设计的标准。

 

Operator提供了标准化的设计流程:

 

  1. 使用 SDK 创建一个新的 Operator 项目
  2. 通过添加自定义资源(CRD)定义新的资源 API
  3. 指定使用 SDK API 来 watch 的资源
  4. 自定义Controller实现K8s协调(reconcile)逻辑

有了锤子,看到的只有钉子

 

我们团队(KubeOne团队)一直在致力于解决复杂中间件应用如何部署到K8s,自然也是Operator模式的践行者。经历了近2年的开发,初步解决了中间件在各个环境K8s的部署,当前中间也走了很多弯路,踩了很多坑。

 

KubeOne内核也经历3个大版本的迭代,前2次开发过程基本都是follow Operator标准开发流程进行开发设计。遵循一个标准的、典型的Operator的设计过程,看上去一切都是这么的完美,但是每次设计都非常痛苦,践行Operator模式之后,最值得反思和借鉴的就是”有了锤子,看到的只有钉子“,简单总结一下就是4个一切:

 

  1. 一切设计皆yaml
  2. 一切皆合一
  3. 一切皆终态
  4. 一切交互皆cr

 

误区1 一切设计皆yaml

 

K8s的API是yaml格式,Operator设计流程也是让大家首先定义crd,所以团队开始设计时直接采用了yaml格式。

 

案例

 

根据标准化流程,团队面向yaml设计流程大体如下:

 

  1. 先根据已知的数据初步整理一个大而全的yaml,做一下初步的分类,例如应用大概包含基础信息,依赖服务,运维逻辑,监控采集等,每个分类做一个子部分
  2. 开会讨论具体的内容是否能满足要求,结果每次开会都难以形成共识
    • 因为总是有新的需求满足不了,在讨论A时,就有人提到B、C、D,不断有新的需求
    • 每个部分的属性非常难统一,因为不同的实现属性差异较大
    • 理解不一致,相同名字但使用时每个人的理解也不同
  1. 由于工期很紧,只能临时妥协,做一个中间态,后面再进一步优化
  2. 后续优化升级,相同的流程再来一遍,还是很难形成共识

 

这是第2个版本的设计:

apiVersion: apps.mwops.alibaba-inc.com/v1alpha1
kind: AppDefinition
metadata:labels:app: "A"name: A-1.0 //chart-name+chart-versionnamespace: kubeone
spec:appName: A  //chart-nameversion: 1.0 //chart-versiontype: apps.mwops.alibaba-inc.com/v1alpha1.argo-helmworkloadSettings:   //注 workloadSettings 标识type应该使用的属性- name: "deployToK8SName"value: ""- name: "deployToNamespace"value: ${resources:namespace-resource.name}parameterValues:   //注 parameterValues标识业务属性- name: "enableTenant"value: "1"- name: "CPU"value: "1"- name: "MEM"value: "2Gi"- name: "jvm"value: "flag;gc"- name: vip.fileserver-edas.ipvalue: ${resources:fileserver_edas.ip}- name: DB_NAMEvalueFromConfigMap:name: ${resources:rds-resource.cm-name}expr: ${database}- name: DB_PASSWORDvalueFromSecret:name: ${instancename}-rds-secretexpr: ${password}- name: object-storage-endpointvalue: ${resources:object-storage.endpoint}- name: object-storage-usernamevalueFromSecret:name: ${resources:object-storage.secret-name}expr: ${username}- name: object-storage-passwordvalueFromSecret:name: ${resources:object-storage.secret-name}expr: ${password}- name: redis-endpointvalue: ${resources:redis.endpoint}- name: redis-passwordvalue: ${resources:redis.password}resources:- name: tolerationstype: apps.mwops.alibaba-inc.com/tolerationsparameterValues:- name: keyvalue: "sigma.ali/is-ecs"- name: keyvalue: "sigma.ali/resource-pool"- name: namespace-resourcetype: apps.mwops.alibaba-inc.com/v1alpha1.namespaceparameterValues:- name: namevalue: edas- name: fileserver-edastype: apps.mwops.alibaba-inc.com/v1alpha1.database.vipparameterValues:- name: portvalue: 21,80,8080,5000- name: src_portvalue: 21,80,8080,5000- name: typevalue: ClusterIP- name: check_typevalue: ""- name: urivalue: ""- name: ipvalue: ""- name: test-dbtype: apps.mwops.alibaba-inc.com/v1alpha1.database.mysqlhaparameterValues:- name: namevalue: test-db- name: uservalue: test-user- name: passwordvalue: test-passwd- name: secretvalue: test-db-mysqlha-secret- name: service-slbtype: apps.mwops.alibaba-inc.com/v1alpha1.slbmode: post-createparameterValues:- name: servicevalue: "serviceA"- name: annotationsvalue: "app:a,version:1.0"- name: external-ipvalue: - name: service-resource2type: apps.mwops.alibaba-inc.com/v1alpha1.serviceparameterValues: - name: second-domainvalue: edas.console- name: portsvalue: "80:80"- name: selectorsvalue: "app:a,version:1.0"- name: typevalue: "loadbalance"- name: service-dnstype: apps.mwops.alibaba-inc.com/v1alpha1.dnsparameterValues:- name: domainvalue: edas.server.${global:domain}- name: vipvalue: ${resources:service-resource2.EXTERNAL-IP}- name: dns-resourcetype: apps.mwops.alibaba-inc.com/v1alpha1.dnsparameterValues:- name: domainvalue: edas.console.${global:domain}- name: vipvalue: “127.0.0.1”- name: cni-resourcetype: apps.mwops.alibaba-inc.com/v1alpha1.cniparameterValues:- name: countvalue: 4- name: ip_listvalue: - name: object-storagetype: apps.mwops.alibaba-inc.com/v1alpha1.objectStorage.minioparameterValues:- name: namespacevalue: test-ns- name: usernamevalue: test-user- name: passwordvalue: test-password- name: storage-capacityvalue: 20Gi- name: secret-namevalue: minio-my-store-access-keys- name: endpointvalue: minio-instance-external-service- name: redistype: apps.mwops.alibaba-inc.com/v1alpha1.database.redisparameterValues:- name: cpuvalue: 500m- name: memoryvalue: 128Mi- name: passwordvalue: i_am_a_password- name: storage-capacityvalue: 20Gi- name: endpointvalue: redis-redis-cluster - name: accesskeytype: apps.mwops.alibaba-inc.com/v1alpha1.accesskeyparameterValues:- name: namevalue: default- name: userNamevalue: ecs_test@aliyun.comexposes:- name: dnsvalue: ${resources:dns-resource.domain}- name: db-endpointvalueFromConfigmap:name: ${resources:rds-resource.cm-name}expr: ${endpoint}:3306/${database}- name: ip_listvalue: ${resources:cni-resource.ip_list}- name: object-storage-endpointvalue: ${resources:object-storage.endpoint}.${resource:namespace-resource.name}- name: object-storage-usernamevalueFromSecret:name: ${resources:object-storage.secret-name}expr: ${username}- name: object-storage-passwordvalueFromSecret:name: ${resources:object-storage.secret-name}expr: ${password}- name: redis-endpointvalue: ${resources:redis.endpoint}.${resource:namespace-resource.name}- name: redis-passwordvalue: ${resources:redis.password}

 

反思

 

这样的痛苦难以用语言表达,感觉一切都脱离了掌控,没有统一的判断标准,设计标准,公说公有理婆说婆有理,内容一直加,字段一直改。事不过三,第三次设计时,我们集体讨论反思为什么这么难形成共识?为什么每个人理解不同?为什么总是在改?

 

结论很一致,没有面向yaml的设计,只有面向对象的设计,设计语言也只有UML,只有这些历经考验、成熟的设计方法论,才是最简单也是最高效的。

 

从上面那个一个巨大无比的yaml大家可以体会我们设计的复杂,但是这还是不是最痛苦的。最痛苦的是大家抛弃了原有的设计流程及设计语言,试图使用一个开放的Map来描述一切。当设计没有对象,也没有关系,只剩下Map里一个个属性,也就无所谓对错,也无所谓优劣。最后争来争去,最后不过是再加一个字段,争了一个寂寞。

 

适用范围

 

那Operator先设计CRD,再开发controller的方式不正确吗?

答案:部分正确

 

适用场景

与Java Class相同,简单对象不需要经过复杂的设计流程,直接设计yaml简单高效。

不适用场景

在设计一个复杂的体系时,例如:应用管理,包含多个对象且对象之间有复杂的关系,有复杂的用户故事,UML和面向对象的设计就显得非常重要。

 

设计时只考虑UML和领域语言,设计完成后,CRD可以认为是java的Class,或者是数据库的表结构,只是最终要实现时的一种选择。而且有很多对象不需要持久化,也不需要通过Operator机制触发对应的逻辑,就不需要设计CRD,而是直接实现一个controller即可。

 

yaml是接口或Class声明的一种格式化表达,常规yaml要尽可能小,尽可能职责单一,尽可能抽象。复杂的yaml是对简单CRD资源的一种编排结果,提供类似一站式资源配套方案。

在第3个版本及PaaS-Core设计时,我们就采取了如下的流程:

 

  1. UML 用例图
  2. 梳理用户故事
  3. 基于用户故事对齐Domain Object,确定关键的业务对象以及对象间关系
  4. 需要Operator化的对象,每个对象描述为一个CRD,当然CRD缺乏接口、继承等面向对象的能力,可以通过其他方式曲线表达
  5. 不需要Operator化的对象,直接编写Controller

 

误区2 一切皆合一

 

为了保证一个应用的终态,或者为了使用gitops管理一个应用,是否应该把应用相关的内容都放入一个CRD或一个IAC文件?根据gitops设计,每次变更时需要下发整个文件?

 

案例

 

案例1: 应用WordPress,需要依赖一个MySQL,终态如何定义?

apiVersion: apps.mwops.alibaba-inc.com/v1alpha1
kind: AppDefinition
metadata:labels:app: "WordPress"name: WordPress-1.0 //chart-name+chart-versionnamespace: kubeone
spec:appName: WordPress  //chart-nameversion: 1.0 //chart-versiontype: apps.mwops.alibaba-inc.com/v1alpha1.argo-helmparameterValues:   //注 parameterValues标识业务属性- name: "enableTenant"value: "1"- name: "CPU"value: "1"- name: "MEM"value: "2Gi"- name: "jvm"value: "flag;gc"- name: replicasvalue: 3- name: connectstringvalueFromConfigMap:name: ${resources:test-db.exposes.connectstring}expr: ${connectstring}- name: db_user_namevalueFromSecret:....resources:- name: test-db //创建一个新的DBtype: apps.mwops.alibaba-inc.com/v1alpha1.database.mysqlhaparameterValues:- name: cpuvalue: 2- name: memoryvalue: 4G- name: storagevalue: 20Gi - name: usernamevalue: myusername- name: passwordvalue: i_am_a_password- name: dbnamevalue: wordPressexposes:- name: connectstring- name: username- name: passwordexposes:- name: dnsvalue: ...

 

上方的代码是wordPress应用的终态吗?这个文件包含了应用所需要的DB的定义和应用的定义,只要一次下发就可以先创建对应的数据库,再把应用拉起。

 

案例2:每次变更时,直接修改整个yaml的部分内容,修改后直接下发到K8s,引起不必要的变更。例如:要从3个节点扩容到5个节点,修改上面yaml文件的replicas之后,需要下发整个yaml。整个下发的yaml经过二次解析成底层的StatefulSet或Deployment,解析逻辑升级后,可能会产生不符合预期的变化,导致所有pod重建。

 

反思

 

先回答第一个问题,上方yaml文件不是应用的终态,而是一个编排,此编排包含了DB的定义和应用的定义。应用的终态只应该包含自己必须的依赖引用,而不包含依赖是如何创建的。因为这个依赖引用可以是新创建的,也可以是一个已有的,也可以是手工填写的,依赖如何创建与应用终态无关。

apiVersion: apps.mwops.alibaba-inc.com/v1alpha1
kind: AppDefinition
metadata:labels:app: "WordPress"name: WordPress-1.0 //chart-name+chart-versionnamespace: kubeone
spec:appName: WordPress  //chart-nameversion: 1.0 //chart-versionname: WordPress-testtype: apps.mwops.alibaba-inc.com/v1alpha1.argo-helmparameterValues:   //注 parameterValues标识业务属性- ....resources:- name: test-db-secretvalue: "wordPress1Secret" //引用已有的secret  exposes:- name: dnsvalue: ...

创建一个应用,就不能先创建db,再创建应用吗?

 

可以的,多个对象之间依赖是通过编排实现的。编排有单个应用创建的编排,也有一个复杂站点创建的编排。以Argo为例。

apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:generateName: wordPress-
spec:templates:- name: wordPresssteps:# 创建db- - name: wordpress-dbtemplate: wordpress-dbarguments:parameters: [{name: wordpress-db1}]# 创建应用- - name: template: wordpressarguments:parameters: [{db-sercet: wordpress-db1}]

 

针对第2个案例,是否每次交互都需要下发全部完整的yaml?

 

答案:

 

  1. 编排是一次性的配置,,编排文件下发一次之后,后续操作都是操作单个对象,例如:变更时,只会单独变更wordPress,或单独变更wordPressDB,而不会一次性同时变更2个对象。
  2. 单独变更应用时,是否需要下发整个终态yaml,这个要根据实际情况进行设计,值得大家思考。后面会提出针对整个应用生命周期状态机的设计,里面有详细的解释。

 

适用范围

适用场景

CRD或Iac定义时,单个对象的终态只应该包含自身及对依赖的引用。与面向对象的设计相同,我们不应该把所有类的定义都放到一个Class里面。

不适用场景

多个对象要一次性创建,并且需要按照顺序创建,存在依赖关系,需要通过编排层实现。

 

误区3 一切皆终态

 

体验了K8s的终态化之后,大家在设计时言必称终态,仿佛不能用上终态设计,不下发一个yaml声明对象的终态就是落伍,就是上一代的设计。

 

案例

 

案例1:应用编排 还是以WordPress为例,将WordPressDB和WordPress放在一起进行部署,先部署DB,再创建应用。示例yaml同上。

 

案例2:应用发布 应用第一次部署及后续的升级直接下发一个完整的应用yaml,系统会自动帮你到达终态。但为了能够细粒度控制发布的流程,努力在Deployment或StatefulSet上下功夫,进行partition的控制,试图在终态里增加一点点的交互性。

 

反思

 

说到终态,必然要提到命令式、声明式编程,终态其实就是声明式最终的执行结果。我们先回顾一下命令式、终态式编程。

 

命令式编程

 

命令式编程的主要思想是关注计算机执行的步骤,即一步一步告诉计算机先做什么再做什么。

 

比如:如果你想在一个数字集合 collection(变量名) 中筛选大于 5 的数字,你需要这样告诉计算机:

 

1. 第一步,创建一个存储结果的集合变量 results;

2. 第二步,遍历这个数字集合 collection;

3. 第三步:一个一个地判断每个数字是不是大于 5,如果是就将这个数字添加到结果集合变量 results 中。

 

代码实现如下:

 

List results = new List();

foreach(var num in collection)

{

if (num > 5)

results.Add(num);

}

 

很明显,这个样子的代码是很常见的一种,不管你用的是 C, C++ 还是 C#, Java, Javascript, BASIC, Python, Ruby 等等,你都可以以这个方式写。

 

声明式编程

 

声明式编程是以数据结构的形式来表达程序执行的逻辑。它的主要思想是告诉计算机应该做什么,但不指定具体要怎么做。

 

SQL 语句就是最明显的一种声明式编程的例子,例如:

 

SELECT * FROM collection WHERE num > 5

 

除了 SQL,网页编程中用到的 HTML 和 CSS 也都属于声明式编程。

 

通过观察声明式编程的代码我们可以发现它有一个特点是它不需要创建变量用来存储数据。

另一个特点是它不包含循环控制的代码如 for, while。

 

换言之

 

• 命令式编程:命令“机器”如何去做事情(how),这样不管你想要的是什么(what),它都会按照你的命令实现。

• 声明式编程:告诉“机器”你想要的是什么(what),让机器想出如何去做(how)。

 

当接口越是在表达“要什么”,就是越声明式;越是在表达“要怎样”,就是越命令式。SQL就是在表达要什么(数据),而不是表达怎么弄出我要的数据,所以它就很“声明式”。

 

简单的说,接口的表述方式越接近人类语言——词汇的串行连接(一个词汇实际上是一个概念)——就越“声明式”;越接近计算机语言——“顺序+分支+循环”的操作流程——就越“命令式”。

 

越是声明式,意味着下层要做更多的东西,或者说能力越强,也意味着效率的损失。越是命令式,意味着上层对下层有更多的操作空间,可以按照自己特定的需求要求下层按照某种方式来处理。

 

简单的讲,Imperative Programming Language (命令式语言)一般都有control flow, 并且具有可以和其他设备进行交互的能力。而Declarative Programming language (声明式语言) 一般做不到这些。

 

基于以上的分析,编排或工作流本质是一个流程性控制的过程,一般是一次性的过程,无需强行终态化,而且建站编排执行结束后,不能保持终态,因为后续会根据单个应用进行发布和升级。案例1是一个典型的编排,只是一次性的创建了2个对象DB和应用的终态。

 

应用发布其实是通过一个发布单或工作流,控制2个不同版本的应用节点和流量的终态化的过程,不应该是应用终态的一部分,而是一个独立的控制流程。

 

适用范围

 

声明式或终态设计

 

适用场景

无过多交互,无需关注底层实现的场景,即把声明提供给系统后,系统会自动化达到声明所要求的状态,而不需要人为干预。

不适用场景

一次性的流程编排,有频繁交互的控制流程

命令式和声明式本就是2种互补的编程模式,就像有了面向对象之后,有人就鄙视面向过程的编程,现在有了声明式,就开始鄙视命令式编程,那一屋!

 

误区4 一切交互皆cr

 

因为K8s的API交互只能通过yaml,导致大家的设计都以cr为中心,所有的交互都设计为下发一个cr,通过watch cr触发对应的逻辑。

 

案例

 

  1. 调用一个http接口或function,需要下发一个cr
  2. 应用crud都下发完整cr

 

反思

 

案例1

 

是否所有的逻辑都需要下发一个cr?

 

下发cr其实做了比较多的事情,流程很长,效率并不高,流程如下:

 

  1. 通过API传入cr,cr 保存到etcd
  2. 触发informer
  3. controller接收到对应的事件,触发逻辑
  4. 更新cr状态
  5. 清理cr,否则会占用etcd存储

 

如果需要频繁的调用对应的接口,尽量通过sdk直接调用。

 

案例2

 

K8s 对yaml操作命令有 create、apply、patch、delete、get等,但一个应用的生命周期状态机不只是这几个命令可以涵盖,我们比较一下应用状态机(上)和yaml状态机(下):

 

不同的有状态应用,在收到不同的指令,需要触发不同的逻辑,例如:MQ在收到stop指令时,需要先停写,检查数据是否消费完成。如果只是通过yaml状态机是无法涵盖应用状态机相关的event,所以我们必须打破下发cr的模式。对于应用来说,理想的交互方式是通过event driven 应用状态机的变化,状态发生变换时触发对应的逻辑。

 

适用范围

 

适用场景

需要持久化,保持终态的数据

 

不适用场景

高频的服务调用,无需持久化的数据

复杂状态机的驱动

 

总结

 

K8s给我们打开了一扇门,带给了我们很多优秀的设计,优秀的理念,但是这些设计和理念也是有自己的适用的场景,并不是放之四海而皆准。我们不应该盲从,试图一切都要follow k8s的设计和规则,而抛弃之前的优秀设计理念。

 

软件设计经历了10多年的发展,形成了一套行之有效的设计方法论,k8s也是在这些设计方法论的支持下设计出来的。取其精华去其糟粕,是我们程序员应该做的事情。

 

参考文章:

  1. 揭秘Kubernetes Operator http://www.dockone.io/article/8769
  2. 声明式编程和命令式编程有什么区别 https://www.zhihu.com/question/22285830
  3. 如何在Kubernetes中编写自定义控制器 https://www.sohu.com/a/363619791_198222

原文链接

本文为阿里云原创内容,未经允许不得转载。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/513436.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

电脑word在哪_word是什么?小学生:单词,大学生:论文排版工具

word是什么,对于不同人会有不同的理解,它可能只是一个单词,它也可能是一个排版工具。今天就以我自己的经历给大家讲述一下,人生的不同阶段,word分别是什么。一、小学阶段,好像是一个单词我们那个时候的小学…

Kubernetes 稳定性保障手册:洞察+预案

简介: 稳定性保障是个复杂的话题,需要有效、可迭代、可持续保障集群的稳定性,系统性的方法或许可以解决该问题。 作者 | 悟鹏 来源 | 阿里巴巴云原生公众号 《Kubernetes 稳定性保障手册》系列文章: ​ Kubernetes 稳定性保障手…

为啥学java要看那么多东西_编程语言那么多,为啥学Java的人那么多?

Java一直都是稳居排行榜第一的语言,在未来10年Java都会是最热门的语言之一,因为Java技术具有卓越的通用性、高效性、安全性和平台移植性,它可以跨平台的应用到不同的领域,工作需求足够大。为什么选择学习Java编程语言?…

墨奇科技:生物识别进入可信发展驱动的新阶段

编辑 | 宋慧 供稿 | 墨奇科技 头图 | 付费下载于视觉中国 在好莱坞电影大片中,经常有“换脸”或者指纹开锁的情节,戏中角色通过“戴上”足以以假乱真的“面皮”,或者按下“盗取”的指纹,顷刻之间就改变了身份,从而影…

安卓游戏开发用什么引擎_游戏开发学习第一天————用什么软件

今天开始了虚幻学习的第一天!好的,那我们就开始学习做游戏吧!始学习做游戏吧学习做游戏吧习做游戏吧做游戏吧游戏吧戏吧吧等等,要学做游戏啊。。。。。。第一步要做什么,我还完全不知道啊喂!于是&#xff0…

360浏览器收藏夹_换了一台电脑,浏览器收藏的网站不见了,咋办?

导语:大家知道,用浏览器上网时,重要的网站要及时收藏,方便下次打开。但如果换电脑以后,收藏的网站就不见了。下面以360浏览器为例来说明一下工具:网络收藏夹、360浏览器说明:360的收藏夹就是网络…

Flink 和 Pulsar 的批流融合

简介:StreamNative 联合创始人翟佳在本次演讲中介绍了下一代云原生消息流平台 Apache Pulsar,并讲解如何通过 Apache Pulsar 原生的存储计算分离的架构提供批流融合的基础,以及 Apache Pulsar 如何与 Flink 结合,实现批流一体的计…

Flink 在有赞的实践和应用

简介: 本文介绍了Flink 在有赞的实践和应用,内容包括:Flink 的容器化改造和实践、Flink SQL 的实践和应用、未来规划。 作者:沈磊 一、Flink 的容器化改造和实践 1. 有赞的集群演进历史 2014 年 7 月,第一个 Storm…

用imspost制作catia后处理_这些有趣又精致的模型,都是用3D打印机打印出来的

3D打印已经出现在了生活的方方面面,有相当多别具一格又十分有趣的模型会让你惊艳。今天,小编搜集了一些有趣的3D打印模型,让我们一饱眼福。海边棕榈树来源:Curufin via Cults这个凉爽的棕榈树模型是由几个3D打印模型组合起来实现的…

cdr 表格自动填充文字_做平面广告设计,AI和CDR如何选择?

关于CDR和AI两个软件,很多人都不知道它们有什么区别。今天我就给大家好好整理了一下。不知道两者区别,不知道该选用哪个软件的都来看看下面的AI和CAD教程吧。首先CorelDRAW是一款由世界顶尖软件公司之一的加拿大的Corel公司开发的图形图像软件。其非凡的…

麒麟信安:根植于openEuler,走操作系统自主创新之路

随着信息安全问题日益突出,行业内外也越来越深刻地认识到,核心技术受制于人是我们最大的隐患。而2020年12月8日,CentOS社区宣布CentOS8将于2021年底停止维护,CentOS7将于2024年6月30日停止维护,这一消息的发布&#xf…

Hologres如何支持超高基数UV计算(基于roaringbitmap实现)

简介: 本文将会介绍Hologres基于roaringbitmap实现超高基数的UV计算 RoaringBitmap是一种压缩位图索引,RoaringBitmap自身的数据压缩和去重特性十分适合对于大数据下uv计算。其主要原理如下: 对于32bit数, RoaringBitmap会构造2^16个桶&…

阿里云贾扬清:大数据+AI工程化,让数据从「成本」变为「资产」

简介: 近年来,数字经济发展迅速,企业转型背后频频涌现「数字力量」的身影。云计算、大数据、人工智能的快速融合形成了数字经济的新基建,也为数字经济发展带来了新的机遇。 5 月 20 日,阿里巴巴副总裁、阿里云计算平台…

easyexcel 日期类型 convert_数据库的几种日期时间类型,你真的会用吗?

日期和时间是每个系统,每个数据库设计必不可少的部分。也是容易被大家忽视的部分。很多开发者可能根本不了解以不同类型存储日期和时间意味着什么。有朋友可能会说,数据库定义一个datetime或timestamp类型的字段,然后在Java代码中获取当前时间…

从重复到重用

简介: 开发技术的发展,从第一次提出“函数/子程序”,实现代码级重用;到面向对象的“类”,重用数据结构与算法;再到“动态链接库”、“控件”等重用模块;到如今流行的云计算、微服务可重用整个系…

JAVA中randomfile_java中的RandomAccessFile的用法

Java的RandomAccessFile提供对文件的读写功能,与普通的输入输出流不一样的是RamdomAccessFile可以任意的访问文件的任何地方。这就是“Random”的意义所在。RandomAccessFile的对象包含一个记录指针,用于标识当前流的读写位置,这个位置可以向…

官宣|Apache Flink 1.13.0 正式发布,流处理应用更加简单高效!

简介: Flink 1.13.0 版本让流处理应用的使用像普通应用一样简单和自然,并且让用户可以更好地理解流作业的性能。 ​翻译 | 高赟 Review | 朱翥、马国维 Flink 1.13 发布了!Flink 1.13 包括了超过 200 名贡献者所提交的 1000 多项修复和优化…

lightning接口_Lightning太赚钱?iPhone永远不会用Type-C

欧盟日前通过了法案,要求在欧洲销售的各手机制造商要统一充电接口,全部采用USB Type-C接口,但这可能让一直使用Lightning(闪电)充电接口的Apple受到很大影响。虽说目前苹果的Mac电脑、iPad平板电脑以及智能音箱等部分产品都开始使用USB Type-…

这个冬天,头秃了,口袋也空了......

秋天里的第一杯奶茶刚喝完,冬天里的第一条秋裤就要安排上!不让加班的程序员,从此的生活便不只有脑袋秃秃,还有口袋空空!从外包出来,没想到新的面试让我手忙脚乱外包虐我千百遍,只能待她如初恋&a…

云数据仓库的未来趋势:计算存储分离

简介: 随着云时代的到来,数据库也开始拥抱云数据库时代,各类数据库系统在各内外云平台百花齐放,有开源的MySQL、PostgreSQL、MongoDB,传统数据库厂商的SQLServer、Oracle,云厂商自研的Aurora、Redshift、Po…