- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
文本仅用于澄清声明式配置技术概述, KCL 概念以及核心设计,以及与其他配置语言的对比.
软件不是一成不变的,每天有成千上万的配置更新,并且配置本身也在逐渐演进,对规模化效率有较高的诉求 。
配置更新越来越频繁 :配置提供了一种改变系统功能的低开销方式,不断发展的业务需求、基础设施要求和其他因素意味着系统需要不断变化.
配置规模越来越大 :一份配置往往要分发到不同的云站点、不同的租户、不同的环境等.
配置场景广泛 :应用配置、数据库配置、网络配置、监控配置等.
配置格式繁多 :JSON, YAML, XML, TOML, 各种配置模版如 Java Velocity, Go Template 等.
配置的稳定性至关重要,系统宕机或出错的一个最主要原因是有大量工程师进行频繁的实时配置更新,表 1 示出了几个由于配置导致的系统出错事件.
时间 | 事件 |
---|---|
2021 年 7 月 | 中国 Bilibili 公司由于 SLB Lua 配置计算出错陷入死循环导致网站宕机 |
2021 年 10 月 | 韩国 KT 公司由于路由配置错误导致在全国范围内遭受重大网络中断 |
表 1 配置导致的系统出错事件 。
云原生时代带来了如雨后春笋般的技术发展,出现了大量面向终态的声明式配置实践,如图 1 所示,声明式配置一般可分为如下几种方式。 图 1 声明式配置方式分类 。
结构化的 KV 可以满足最小化数据声明需求,比如数字、字符串、列表和字典等数据类型,并且随着云原生技术快速发展应用,声明式 API 可以满足 X as Data 发展的诉求,并且面向机器可读可写,面向人类可读。其优劣如下
优势 。
语法简单,易于编写和阅读 。
多语言 API 丰富 。
有各种 Path 工具方便数据查询,如 XPath, JsonPath 等 。
痛点 。
冗余信息多:当配置规模较大时,维护和阅读配置很困难,因为重要的配置信息被淹没在了大量不相关的重复细节中 。
功能性不足 。
约束校验能力 。
复杂逻辑编写能力 。
测试、调试能力 。
不易抽象和复用 。
Kustomize 的 Patch 比较定制,基本是通过固定几种 Patch Merge 策略 。
结构化 KV 的代表技术有 。
图 2 Kustomize 典型工作方式 。
模版化 (Templated) 的 KV 赋予静态配置数据动态参数的能力,可以做到一份模版+动态参数输出不同的静态配置数据。其优劣如下
优势 。
简单的配置逻辑,循环支持 。
支持外部动态参数输入模版 。
痛点 。
容易落入所有配置参数都是模版参数的陷阱 。
当配置规模变大时,开发者和工具都难以维护和分析它们 。
模版化代表技术有
图 3 Helm Jekins Package ConfigMap 配置模版 。
Configuration as Code (CaC), 使用代码产生配置,就像工程师们只需要写高级 GPL 代码,而不是手工编写容易出错而且难以理解的服务器二进制代码一样。配置变更同代码变更同样严肃地对待,同样可以执行单元测试、集成测试等。代码模块化和重用是维护配置代码比手动编辑 JSON/YAML 等配置文件更容易的一个关键原因。其优劣如下
优势 。
必要的编程能力(变量定义、逻辑判断、循环、断言等) 。
代码模块化与抽象(支持定义数据模版,并用模版得到新的配置数据) 。
可以抽象配置模版+并使用配置覆盖 。
痛点 。
类型检查不足 。
运行时错误 。
约束能力不足 。
代码化 KV 的代表技术有
类型化的 KV,基于代码化 KV,多了类型检查和约束的能力,其优劣如下
优势 。
配置合并完全幂等,天然防止配置冲突 。
丰富的配置约束语法用于编写约束 。
将类型和值约束编写抽象为同一种形式,编写简单 。
配置顺序无关 。
痛点 。
图合并和幂等合并等概念复杂,用户理解成本较高 。
类型和值混合定义提高抽象程度的同时提升了用户的理解成本,并且所有约束在运行时进行检查,大规模配置代码下有性能瓶颈 。
对于想要配置覆盖、修改的多租户、多环境场景难以实现 。
对于带条件的约束场景,定义和校验混合定义编写用户界面不友好 。
类型化 KV 的代表技术有
模型化的 KV 在代码化和类型化 KV 的基础上以高级语言建模能力为核心描述,期望做到模型的快速编写与分发,其优劣如下
优势 。
引入可分块、可扩展的 KV 配置块编写方式 。
类高级编程语言的编写、测试方式 。
语言内置的强校验、强约束支持 。
面向人类可读可写,面向机器部分可读可写 。
不足 。
扩展新模型及生态构建需要一定的研发成本,或者使用工具对社区中已有的 JsonSchema 和 OpenAPI 模型进行模型转换、迁移和集成.
模型化 KV 的代表技术有
图 4 使用 KCL 编写应用交付配置代码 。
此外,从不同声明式配置方式的使用场景出发 。
不同于社区中的其他同类型领域语言,KCL 是一种面向应用研发人员并采纳了现代语言设计和技术的静态强类型编译语言 。
注意,本文将不会讨论通用语言编写配置的情况,通用语言一般是 Overkill 的,即远远超过了需要解决的问题,通用语言存在各式各样的安全问题,比如能力边界问题 (启动本地线程、访问 IO, 网络,代码死循环等不安全隐患),比如像音乐领域就有专门的音符去表示音乐,方便学习与交流,不是一般文字语言可以表述清楚的.
此外,通用语言因为本身就样式繁多,存在统一维护、管理和自动化的成本,通用语言一般用来编写客户端运行时,是服务端运行时的一个延续,不适合编写与运行时无关的配置,最终被编译为二进制从进程启动,稳定性和扩展性不好控制,而配置语言往往编写的是数据,再搭配以简单的逻辑,描述的是期望的最终结果,然后由编译器或者引擎来消费这个期望结果.
Kusion 配置语言(KCL)是一个开源的基于约束的记录及函数语言。KCL 通过成熟的编程语言技术和实践来改进对大量繁杂配置的编写,致力于构建围绕配置的更好的模块化、扩展性和稳定性,更简单的逻辑编写,以及更快的自动化集成和良好的生态延展性.
KCL 的核心特性是其 建模 和 约束 能力,KCL 核心功能基本围绕 KCL 这个两个核心特性展开,此外 KCL 遵循以用户为中心的配置理念而设计其核心特性,可以从两个方面理解:
不论是以何为中心的配置视图,对于代码而言(包括配置代码)都存在对配置数据约束的需求,比如类型约束、配置字段必选/可选约束、范围约束、不可变性约束等,这也是 KCL 致力于解决的核心问题之一。综上,KCL 是一个开源的基于约束和声明的函数式语言,KCL 主要包含如图 5 所示的核心特性:
图 5 KCL 核心特性 。
图 6 KCL 语言核心设计 。
更多语言设计和能力详见 KCL 文档 ,尽管 KCL 不是通用语言,但它有相应的应用场景,如图 6 所示,研发者可以通过 KCL 编写 配置(config) 、 模型(schema) 、 函数(lambda) 及 规则(rule) ,其中 Config 用于定义数据,Schema 用于对数据的模型定义进行描述,Rule 用于对数据进行校验,并且 Schema 和 Rule 还可以组合使用用于完整描述数据的模型及其约束,此外还可以使用 KCL 中的 lambda 纯函数进行数据代码组织,将常用代码封装起来,在需要使用时可以直接调用.
对于使用场景而言,KCL 可以进行结构化 KV 数据验证、复杂配置模型定义与抽象、强约束校验避免配置错误、分块编写及配置合并能力、自动化集成和工程扩展等能力,下面针对这些功能和使用场景进行阐述.
如图 7 所示,KCL 支持对 JSON/YAML 数据进行格式校验。作为一种配置语言,KCL 在验证方面几乎涵盖了 OpenAPI 校验的所有功能。在 KCL 中可以通过一个结构定义来约束配置数据,同时支持通过 check 块自定义约束规则,在 schema 中书写校验表达式对 schema 定义的属性进行校验和约束。通过 check 表达式可以非常清晰简单地校验输入的 JSON/YAML 是否满足相应的 schema 结构定义与 check 约束.
图 7 KCL 中结构化 KV 校验方式 。
基于此,KCL 提供了相应的 校验工具 直接对 JSON/YAML 数据进行校验。此外,通过 KCL schema 的 check 表达式可以非常清晰简单地校验输入的 JSON 是否满足相应的 schema 结构定义与 check 约束。此外,基于此能力可以构建如图 8 所示的 KV 校验可视化产品.
图 8 基于 KCL 结构化 KV 校验能力构建的可视化产品界面 。
如图 9 所示,借助 KCL 语言丰富的特性及 KCL OpenAPI 等工具,可以将社区中广泛的、设计良好的模型直接集成到 KCL 中(比如 K8s 资源模型 CRD),用户也可以根据自己的业务场景设计、实现自己的 KCL 模型 (库) ,形成一整套领域模型架构交由其他配置终端用户使用.
图 9 KCL 复杂配置建模的一般方式 。
基于此,可以像图 10 示出的那样用一个大的 Konfig 仓库 管理全部的 KCL 配置代码,将业务配置代码 (应用代码)、基础配置代码 (核心模型+底层模型)在一个大库中,方便代码间的版本依赖管理,自动化系统处理也比较简单,定位唯一代码库的目录及文件即可,代码互通,统一管理,便于查找、修改、维护,可以使用统一的 CI/CD 流程进行配置管理(此外,大库模式也是 Google 等头部互联网公司内部实践的模式).
图 10 使用 KCL 的语言能力集成领域模型并抽象用户模型并使用 。
如图 11 所示,在 KCL 中可以通过丰富的强约束校验手段避免配置错误:
图 11 KCL 强约束校验手段 。
App
中定义对 containerPort
、 services
、 volumes
的校验规则,目前校验规则在运行时执行判断,后续 KCL 会尝试通过编译时的静态分析对规则进行判断从而发现问题。 图 12 带规则约束的 KCL 代码校验 。
KCL 提供了配置分块编写及自动合并配置的能力,并且支持幂等合并、补丁合并和唯一配置合并等策略。幂等合并中的多份配置需要满足交换律,并且需要开发人员手动处理基础配置和不同环境配置冲突。 补丁合并作为一个覆盖功能,包括覆盖、删除和添加。唯一的配置要求配置块是全局唯一的并且未修改或以任何形式重新定义。 KCL 通过多种合并策略简化了用户侧的协同开发,减少了配置之间的耦合.
如图 13 所示,对于存在基线配置、多环境和多租户的应用配置场景,有一个基本配置 base.k。 开发和 SRE 分别维护生产和开发环境的配置 base.k 和 prod.k,他们的配置互不影响,由 KCL 编译器合并成一个 prod 环境的等效配置代码.
图 13 多环境场景配置分块编写实例 。
在 KCL 中提供了很多自动化相关的能力,主要包括工具和多语言 API。 通过 package_identifier : key_identifier 的模式支持对任意配置键值的索引,从而完成对任意键值的增删改查。比如图 14 所示修改某个应用配置的镜像内容,可以直接执行如下指令修改镜像,修改前后的 diff 如下图所示.
图 14 使用 KCL CLI/API 自动修改应用配置镜像 。
此外,可以基于 KCL 的自动化能力实现如图 15 所示的一镜交付及自动化运维能力并集成到 CI/CD 当中.
图 15 典型 KCL 自动化集成链路 。
YAML/JSON 配置等适合小规模的配置场景,对于大规模且需要频繁修改的云原生配置场景,比较适合 KCL 比较适合,其中涉及到主要差异是配置数据抽象与展开的差异:
Kustomize 的核心能力是其 Overlay 能力,并 Kustomize 支持文件级的覆盖,但是存在会存在多个覆盖链条的问题,因为找到具体字段值的声明并不能保证这是最终值,因为其他地方出现的另一个具体值可以覆盖它,对于复杂的场景,Kustomize 文件的继承链检索往往不如 KCL 代码继承链检索方便,需要仔细考虑指定的配置文件覆盖顺序。此外,Kustomize 不能解决 YAML 配置编写、配置约束校验和模型抽象与开发等问题,较为适用于简单的配置场景,当配置组件增多时,对于配置的修改仍然会陷入大量重复不相关的配置细节中,并且在 IDE 中不能很好地显示配置之间的依赖和覆盖关系情况,只能通过搜索/替换等批量修改配置.
在 KCL 中,配置合并的操作可以细粒度到代码中每一个配置字段,并且可以灵活的设置合并策略,并不局限于资源整体,并且通过 KCL 的 import 可以静态分析出配置之间的依赖关系.
HCL | KCL | |
---|---|---|
建模能力 | 通过 Terraform Go Provider Schema 定义,在用户界面不直接感知,此外编写复杂的 object 和必选/可选字段定义时用户界面较为繁琐 | 通过 KCL Schema 进行建模,通过语言级别的工程和部分面向对象特性,可以实现较高的模型抽象 |
约束能力 | 通过 Variable 的 condition 字段对动态参数进行约束,Resource 本身的约束需要通过 Go Provider Schema 定义或者结合 Sentinel/Rego 等策略语言完成,语言本身的完整能力不能自闭环,且实现方式不统一 | 以 Schema 为核心,在进行建模的同时定义其约束,在 KCL 内部自闭环并一统一方式实现,支持多种约束函数编写,支持可选/必选字段定义 |
扩展性 | Terraform HCL 通过分文件进行 Override, 模式比较固定,能力受限。 | KCL 可以自定义配置分块编写方式和多种合并策略,可以满足复杂的多租户、多环境配置场景需求 |
语言化编写能力 | 编写复杂的对象定义和必选/可选字段定义时用户界面较为繁琐 | 复杂的结构定义、约束场景编写简单,不借助其他外围 GPL 或工具,语言编写自闭环 |
Terraform HCL Variable 约束校验编写 vs. KCL Schema 声明式约束校验编写 。
variable "subnet_delegations" {
type = list(object({
name = string
service_delegation = object({
name = string
actions = list(string)
})
}))
default = null
validation {
condition = var.subnet_delegations == null ? true : alltrue([for d in var.subnet_delegations : (d != null)])
}
validation {
condition = var.subnet_delegations == null ? true : alltrue([for n in var.subnet_delegations.*.name : (n != null)])
}
validation {
condition = var.subnet_delegations == null ? true : alltrue([for d in var.subnet_delegations.*.service_delegation : (d != null)])
}
validation {
condition = var.subnet_delegations == null ? true : alltrue([for n in var.subnet_delegations.*.service_delegation.name : (n != null)])
}
}
schema SubnetDelegation:
name: str
service_delegation: ServiceDelegation
schema ServiceDelegation:
name: str
actions?: [str] # 使用 ? 标记可选属性
subnet_delegations: [SubnetDelegation] = option("subnet_delegations")
此外,KCL 还可以像高级语言一样写类型,写继承,写内置的约束,这些功能是 HCL 所不具备的 。
Terraform HCL 函数 vs. KCL Lambda 函数编写 。
add_func = lambda x: int, y: int -> int {
x + y
}
two = add_func(1, 1) # 2
HCL 删除 null 值与 KCL 使用 -n 编译参数删除 null 值 。
variable "conf" {
type = object({
description = string
name = string
namespace = string
params = list(object({
default = optional(string)
description = string
name = string
type = string
}))
resources = optional(object({
inputs = optional(list(object({
name = string
type = string
})))
outputs = optional(list(object({
name = string
type = string
})))
}))
results = optional(list(object({
name = string
description = string
})))
steps = list(object({
args = optional(list(string))
command = optional(list(string))
env = optional(list(object({
name = string
value = string
})))
image = string
name = string
resources = optional(object({
limits = optional(object({
cpu = string
memory = string
}))
requests = optional(object({
cpu = string
memory = string
}))
}))
script = optional(string)
workingDir = string
}))
})
}
locals {
conf = merge(
defaults(var.conf, {}),
{ for k, v in var.conf : k => v if v != null },
{ resources = { for k, v in var.conf.resources : k => v if v != null } },
{ steps = [for step in var.conf.steps : merge(
{ resources = {} },
{ for k, v in step : k => v if v != null },
)] },
)
}
schema Param:
default?: str
name: str
schema Resource:
cpu: str
memory: str
schema Step:
args?: [str]
command?: [str]
env?: {str:str}
image: str
name: str
resources?: {"limits" | "requests": Resource}
script?: str
workingDir: str
schema K8sManifest:
name: str
namespace: str
params: [Param]
results?: [str]
steps: [Step]
conf: K8sManifest = option("conf")
综上可以看出,在 KCL 中,通过 Schema 来声明方式定义其类型和约束,可以看出相比于 Terraform HCL, 在实现相同功能的情况下,KCL 的约束可以编写的更加简单 (不需要像 Terraform 那样重复地书写 validation 和 condition 字段),并且额外提供了字段设置为可选的能力 ( ? 运算符,不像 Terraform 配置字段默认可空,KCL Schema 字段默认必选),结构更加分明,并且可以在代码层面直接获得类型检查和约束校验的能力.
CUE | KCL | |
---|---|---|
建模能力 | 通过 Struct 进行建模,无继承等特性,当模型定义之间无冲突时可以实现较高的抽象。由于 CUE 在运行时进行所有的约束检查,在大规模建模场景可能存在性能瓶颈 | 通过 KCL Schema 进行建模,通过语言级别的工程和部分面向对象特性(如单继承),可以实现较高的模型抽象。 KCL 是静态编译型语言,对于大规模建模场景开销较小 |
约束能力 | CUE 将类型和值合并到一个概念中,通过各种语法简化了约束的编写,比如不需要泛型和枚举,求和类型和空值合并都是一回事 | KCL 提供了跟更丰富的 check 声明式约束语法,编写起来更加容易,对于一些配置字段组合约束编写更加简单(能力上比 CUE 多了 if guard 组合约束,all/any/map/filter 等集合约束编写方式,编写更加容易) |
分块编写能力 | 支持语言内部配置合并,CUE 的配置合并是完全幂等的,对于满足复杂的多租户、多环境配置场景的覆盖需求可能无法满足 | KCL 可以自定义配置分块编写方式和多种合并策略,KCL 同时支持幂等和非幂等的合并策略,可以满足复杂的多租户、多环境配置场景需求 |
语言化编写能力 | 对于复杂的循环、条件约束场景编写复杂,对于需要进行配置精确修改的编写场景较为繁琐 | 复杂的结构定义、循环、条件约束场景编写简单 |
CUE 约束校验编写 vs. KCL Schema 声明式约束校验编写及配置分块编写能力 。
CUE (执行命令 cue export base.cue prod.cue ) 。
// base.cue
import "list"
#App: {
domainType: "Standard" | "Customized" | "Global",
containerPort: >=1 & <=65535,
volumes: [...#Volume],
services: [...#Service],
}
#Service: {
clusterIP: string,
type: string,
if type == "ClusterIP" {
clusterIP: "None"
}
}
#Volume: {
container: string | *"*" // The default value of `container` is "*"
mountPath: string,
_check: false & list.Contains(["/", "/boot", "/home", "dev", "/etc", "/root"], mountPath),
}
app: #App & {
domainType: "Standard",
containerPort: 80,
volumes: [
{
mountPath: "/tmp"
}
],
services: [
{
clusterIP: "None",
type: "ClusterIP"
}
]
}
// prod.cue
app: #App & {
containerPort: 8080, // error: app.containerPort: conflicting values 8080 and 80:
}
KCL (执行命令 kcl base.k prod.k ) 。
# base.k
schema App:
domainType: "Standard" | "Customized" | "Global"
containerPort: int
volumes: [Volume]
services: [Service]
check:
1 <= containerPort <= 65535
schema Service:
clusterIP: str
$type: str
check:
clusterIP == "None" if $type == "ClusterIP"
schema Volume:
container: str = "*" # The default value of `container` is "*"
mountPath: str
check:
mountPath not in ["/", "/boot", "/home", "dev", "/etc", "/root"]
app: App {
domainType = "Standard"
containerPort = 80
volumes = [
{
mountPath = "/tmp"
}
]
services = [
{
clusterIP = "None"
$type = "ClusterIP"
}
]
}
# prod.k
app: App {
# 可以使用 = 属性运算符对 base app 的 containerPort 进行修改
containerPort = 8080
# 可以使用 += 属性运算符对 base app 的 volumes 进行添加
# 此处表示在 prod 环境增加一个 volume, 一共两个 volume
volumes += [
{
mountPath = "/tmp2"
}
]
}
此外由于 CUE 的幂等合并特性,在场景上并无法使用类似 kustomize 的 overlay 配置覆盖和 patch 等能力,比如上述的 base.cue 和 prod.cue 一起编译会报错.
在代码规模较大或者计算量较高的场景情况下 KCL 比 CUE/Jsonnet/HCL 等语言性能更好 (CUE 等语言受限于运行时约束检查开销,而 KCL 是一个静态编译型语言) 。
import "list"
temp: {
for i, _ in list.Range(0, 10000, 1) {
"a(i)": list.Max([1, 2])
}
}
a = lambda x: int, y: int -> int {
max([x, y])
}
temp = {"a${i}": a(1, 2) for i in range(10000)}
local a(x, y) = std.max(x, y);
{
temp: {["a%d" % i]: a(1, 2) for i in std.range(0, 10000)},
}
output "r1" {
value = {for s in range(0, 1000) : format("a%d", s) => max(1, 2)}
}
output "r2" {
value = {for s in range(1000, 2000) : format("a%d", s) => max(1, 2)}
}
output "r3" {
value = {for s in range(1000, 2000) : format("a%d", s) => max(1, 2)}
}
output "r4" {
value = {for s in range(2000, 3000) : format("a%d", s) => max(1, 2)}
}
output "r5" {
value = {for s in range(3000, 4000) : format("a%d", s) => max(1, 2)}
}
output "r6" {
value = {for s in range(5000, 6000) : format("a%d", s) => max(1, 2)}
}
output "r7" {
value = {for s in range(6000, 7000) : format("a%d", s) => max(1, 2)}
}
output "r8" {
value = {for s in range(7000, 8000) : format("a%d", s) => max(1, 2)}
}
output "r9" {
value = {for s in range(8000, 9000) : format("a%d", s) => max(1, 2)}
}
output "r10" {
value = {for s in range(9000, 10000) : format("a%d", s) => max(1, 2)}
}
环境 | KCL v0.4.3 运行时间 (包含编译+运行时间) | CUE v0.4.3 运行时间 (包含编译+运行时间) | Jsonnet v0.18.0 运行时间 (包含编译+运行时间) | HCL in Terraform v1.3.0 运行时间 (包含编译+运行时间) |
---|---|---|---|---|
OS: macOS 10.15.7; CPU: Intel(R) Core(TM) i7-8850H CPU @ 2.60GHz; Memory: 32 GB 2400 MHz DDR4; 不开启 NUMA | 440 ms (kclvm_cli run test.k) | 6290 ms (cue export test.cue) | 3340 ms (jsonnet test.jsonnet) | 1774 ms (terraform plan -parallelism=1) |
综上可以看出:CUE 和 KCL 均可以覆盖到绝大多数配置校验场景,并且均支持属性类型定义、配置默认值、约束校验等编写,但是 CUE 对于不同的约束条件场景无统一的写法,且不能很好地透出校验错误,KCL 使用 check 关键字作统一处理,支持用户自定义错误输出.
使用 KCL 和 CUE 编写 Kubernetes 配置 。
package templates
import (
apps "k8s.io/api/apps/v1"
)
deployment: apps.#Deployment
deployment: {
apiVersion: "apps/v1"
kind: "Deployment"
metadata: {
name: "me"
labels: me: "me"
}
}
import kubernetes.api.apps.v1
deployment = v1.Deployment {
metadata.name = "me"
metadata.labels.name = "me"
}
环境 | KCL v0.4.3 运行时间 (包含编译+运行时间) | CUE v0.4.3 运行时间 (包含编译+运行时间) |
---|---|---|
OS: macOS 10.15.7; CPU: Intel(R) Core(TM) i7-8850H CPU @ 2.60GHz; Memory: 32 GB 2400 MHz DDR4; no NUMA | 140 ms (kclvm_cli run test.k) | 350 ms (cue export test.cue) |
文本对声明式配置技术做了整体概述,其中重点阐述了 KCL 概念、核心设计、使用场景以及与其他配置语言的对比,期望帮助大家更好的理解声明式配置技术及 KCL 语言。更多 KCL 的概念、背景、设计与用户案例等相关内容,欢迎访问 KCL 网站 。
最后此篇关于万字长文详解声明式配置发展历程的文章就讲到这里了,如果你想了解更多关于万字长文详解声明式配置发展历程的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我目前正在为 karaf 开发 bundle 并有一些问题... 我写了一个基于 cxf 的 bundle/webservice,我尝试在 karaf 中部署它,但它无法启动那个 bundle,因为它
我正在为我正在制作的快速网站使用高级 wordpress 主题。几乎所有内容都可以在管理面板中设置,这很棒,但标题大小不是其中之一,我现在发现它有点大。我想在管理面板中使用“自定义 css”部分,因为
我们正在考虑为基于 Scala 的 Akka Persistence 应用程序使用序列化方法。我们认为我们的持久化事件很可能会随着时间的推移而“演变”,因此我们希望支持模式演变,并首先考虑 Avro。
我正在开始一个新的 python 项目,主要针对可能在几年内升级到 RHEL6 的 RHEL5 机器。鉴于 python 2.4 是 RHEL5 上的标准,并且系统管理员不会支持他们必须支持的更多内容
我正在为我的 Chrome 扩展程序开发寻找一个简单的 Javascript 框架。我正在使用 jQuery 和 jQuery UI,但随着我的项目的增长,我对代码的定位变得更糟。因此,我需要某种一致
我是一名优秀的程序员,十分优秀!