背景

Serverless 架构的出现让开发者不用过多地考虑传统的服务器采购、硬件运维、网络拓扑、资源扩容等问题,可以将更多的精力放在业务的拓展和创新上。

随着 serverless 概念的深入人心,各大云计算厂商纷纷推出了各自的 serverless 产品,其中比较有代表性的有 AWS lambda、Azure Function、Google Cloud Functions、阿里云函数计算等。

另外,CNCF 也于 2016 年创立了 Serverless Working Group,它致力于 cloud native 和 serverless 技术的结合。下图是 CNCF serverless 全景图,它将这些产品分成了工具型、安全型、框架型和平台型等类别。

同时,容器以及容器编排工具的出现,大大降低了 serverless 产品的开发成本,促进了一大批优秀开源 serverless 产品的诞生,它们大多构建于 kubernetes 之上,如下图所示。

Kubeless 简介

本文将要介绍的 kubeless 便是这些开源 serverless 产品的典型代表。根据官方的定义,kubeless 是 kubernetes native 的无服务计算框架,它可以让用户在 kubernetes 之上使用 FaaS 构建高级应用程序。从 CNCF 视角,kubeless 属于平台型产品。

Kubless 有三个核心概念:

  1. Functions - 代表需要被执行的用户代码,同时包含运行时依赖、构建指令等信息;
  2. Triggers - 代表和函数关联的事件源。如果把事件源比作生产者,函数比作执行者,那么触发器就是联系两者的桥梁;
  3. Runtime - 代表函数运行时所依赖的环境。

原理剖析

本章节将以 kubeless 为例介绍 serverless 产品需要具备的基本能力,以及 kubeless 是如何利用 K8s 现有功能来实现它们的。这些基本能力包括:

  1. 敏捷构建 - 能够基于用户提交的源码迅速构建可执行的函数,简化部署流程;
  2. 灵活触发 - 能够方便地基于各类事件触发函数的执行,并能方便快捷地集成新的事件源;
  3. 自动伸缩 - 能够根据业务需求,自动完成扩容缩容,无须人工干预。

本文所做的调研基于kubeless v1.0.0k8s 1.13

敏捷构建

CNCF 对函数生命周期的定义如下图所示。用户只需提供源码和函数说明,构建部署等工作通常由 serverless 平台完成。 因此,基于用户提交的源码迅速构建可执行函数是 serverless 产品必须具备的基础能力。

在 kubeless 里,创建函数非常简单:

kubeless function deploy hello --runtime python2.7 \
--from-file test.py \
--handler test.hello

该命令各参数含义如下:

  1. hello:将要部署的函数名称;
  2. --runtime python2.7: 指定使用 python 2.7 作为运行环境。Kubeless 可供选择的运行环境请参考链接 runtimes。
  3. --from-file test.py:指定函数源码文件(支持 zip 格式)。
  4. --handler test.hello:指定使用 test.py 中的 hello 方法处理请求。

函数资源与 K8s Operator

Kubeless 函数是一个自定义 K8s 对象,本质上是 k8s operator。k8s operator 原理如下图所示:

下面以 kubeless 函数为例,描述 K8s operator 的一般工作流程:

  1. 使用 k8s 的 CustomResourceDefinition(CRD) 定义资源,这里创建了一个名为functions.kubeless.io的 CRD 来代表 kubeless 函数;
  2. 创建一个 controller 监听自定义资源的 ADD、UPDATE、DELETE 事件并绑定 hander。这里创建了一个名为function-controller的 CRD controller,该 controller 会监听针对 function 的 ADD、UPDATE、DELETE 事件,并绑定 handler(参阅 AddEventHandler);
  3. 用户执行创建、更新、删除自定义资源的命令;
  4. Controller 根据监听到的事件调用相应的 handler。

除了函数外,下文将要介绍的 trigger 也是一个 k8s operator。

函数构成

Kubeless 的 function-controller监听到针对 function 的 ADD 事件后,会触发相应 handler 创建函数。一个函数由若干 K8s 对象组成,包括 ConfigMap、Service、Deployment、Pod 等,其结构如下图所示:

ConfigMap

函数中的 ConfigMap 用于描述函数源码和依赖。

apiVersion: v1
data:
handler: test.hello
# 函数依赖的第三方 python 库
requirements.txt: |
kubernetes==2.0.0
# 函数源码
test.py: |
def hello(event, context):
print event
return event['data']
kind: ConfigMap
metadata:
labels:
created-by: kubeless
function: hello
# 该 ConfigMap 名称
name: hello
namespace: default
...

Service

函数中的 Service 用于描述该函数的访问方式。该 Service 会与执行 function 逻辑的 Pods 相关联,类型是 ClusterIP。

apiVersion: v1
kind: Service
metadata:
labels:
created-by: kubeless
function: hello
# 该 Service 名称
name: hello
namespace: default
...
spec:
clusterIP: 10.109.2.217
ports:
- name: http-function-port
port: 8080
protocol: TCP
targetPort: 8080
selector:
created-by: kubeless
function: hello
# Service 类型
type: ClusterIP
...

Deployment

函数中的 Deployment 用于编排执行函数逻辑的 Pods,通过它可以描述函数期望的个数。

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
labels:
created-by: kubeless
function: hello
name: hello
namespace: default
...
spec:
# 指定函数期望的个数
replicas: 1
...

Pod

函数中的 Pod 包含真正执行函数逻辑的容器。

Volumes

Pod 中的 volumes 段指定了该函数的 ConfigMap。这会将 ConfigMap 中的源码和依赖添加到 volumeMounts.mountPath 指定的目录里面。从容器视角来看,文件路径为/src/test.py和 /src/requirements

...
volumeMounts:
- mountPath: /kubeless
name: hello
- mountPath: /src
name: hello-deps
volumes:
- emptyDir: {}
name: hello
- configMap:
defaultMode: 420
name: hello
...
Init Container

Pod 中的 Init Container 主要作用如下:

  1. 将源码和依赖文件拷贝到指定目录;
  2. 安装第三方依赖。
Func Container

Pod 中的 Func Container 会加载 Init Container 准备好的源码和依赖并执行函数。不同 runtime 加载代码的方式大同小异,可参考 kubeless.py,Handler.java。

小结

  1. Kubeless 通过综合运用 K8s 中的多种组件以及利用各语言的动态加载能力实现了从用户源码到可执行的函数的构建逻辑;
  2. 考虑了函数运行的安全性,通过 Security Context 机制限制容器中的进程以非 root 身份运行。

灵活触发

一款成熟的 serverless 产品需要具备灵活触发能力,以满足事件源的多样性需求,同时需要能够方便快捷地接入新事件源。CNCF 将函数的触发方式分成了如下图所示的几种类别,关于它们的详细介绍可参考链接 Function Invocation Types。

对于 kubeless 的函数,最简单的触发方式是使用 kubeless CLI,另外还支持通过各种触发器。下表展示了 kubeless 函数目前支持的触发方式以及它们所属的类别。

触发方式 类别
kubeless CLI Synchronous Req/Rep
Http Trigger Synchronous Req/Rep
Cronjob Trigger Job (Master/Worker)
Kafka Trigger Async Message Queue
Nats Trigger Async Message Queue
Kinesis Trigger Message Stream

下图展示了 kubeless 函数部分触发方式的原理:

HTTP trigger

如果希望通过发送 HTTP 请求触发函数执行,需要为函数创建 HTTP 触发器。 Kubeless 利用 K8s ingress 机制实现了 http trigger。Kubeless 创建了一个名为httptriggers.kubeless.io的 CRD 来代表 http trigger 对象。同时,kubeless 包含一个名为http-trigger-controller的 CRD controller,它会持续监听针对 http trigger 和 function 的 ADD、UPDATE、DELETE 事件,并执行对应的操作。

以下命令将为函数 hello 创建一个名为http-hello的 http trigger,并指定选用 nginx 作为 gateway。

kubeless trigger http create http-hello --function-name hello --gateway nginx --path echo --hostname example.com

该命令会创建如下 ingress 对象,可以参考 CreateIngress 深入了解 ingress 的创建逻辑。

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
# 该 Ingress 的名字,即创建 http trigger 时指定的 name
name: http-hello
...
spec:
rules:
- host: example.com
http:
paths:
- backend:
# 指向 kubeless 为函数 hello 创建的 ClusterIP 类型的 Service
serviceName: hello
servicePort: 8080
path: /echo

Ingress 只是用于描述路由规则,要让规则生效、实现请求转发,集群中需要有一个正在运行的 ingress controller。可供选择的 ingress controller 有 Contour、F5 BIG-IP Controller for Kubernetes、Kong Ingress Controllerfor Kubernetes、NGINX Ingress Controller for Kubernetes、Traefik 等。这种路由规则描述和路由功能实现相分离的思想很好地提现了 K8s 始终坚持的需求和供给分离的设计理念。

上文中的命令在创建 trigger 时指定了 nginx 作为 gateway,因此需要部署一个 nginx-ingress-controller。该 controller 的基本工作原理如下:

  1. 以 pod 的形式运行在独立的命名空间中;
  2. 以 hostPort 的形式暴露出来供外界访问;
  3. 内部运行着一个 nginx 实例;
  4. 监听和 ingress、service 等资源相关的事件。如果发现这些事件最终会影响到路由规则,ingress controller 会采用向 Lua hander 发送新的 endpoints 列表或者直接修改 nginx.conf 并 reload nginx 等手段达到更新路由规则的目的。

想要更深入地了解 nginx-ingress-controller 的工作原理可参考文章 how-it-works。

完成上述工作后,我们便可以通过发送 HTTP 请求触发函数 hello 的执行:

  1. HTTP 请求首先会由 nginx-ingress-controller 中的 nginx 处理;
  2. Nginx 根据 nginx.conf 中的路由规则将请求转发给函数对应的 service;
  3. 最后,请求会转发至挂载在 service 后的某个函数进行处理。

样例如下:

curl --data '{"Another": "Echo"}' \
--header "Host: example.com" \
--header "Content-Type:application/json" \
example.com/echo # 函数返回
{"Another": "Echo"}

Cronjob trigger

如果希望定期触发函数执行,需要为函数创建 cronjob 触发器。K8s 支持通过 CronJob 定期运行任务,kubeless 利用这个特性实现了 cronjob trigger。Kubeless 创建了一个名为cronjobtriggers.kubeless.io的 CRD 来代表 cronjob trigger 对象。同时,kubeless 包含一个名为cronjob-trigger-controller的 CRD controller,它会持续监听针对 cronjob trigger 和 function 的 ADD、UPDATE、DELETE 事件,并执行对应的操作。

以下命令将为函数 hello 创建一个名为scheduled-invoke-hello的 cronjob trigger,该触发器每分钟会触发函数 hello 执行一次。

kubeless trigger cronjob create scheduled-invoke-hello --function=hello --schedule="*/1 * * * *"

该命令会创建如下 CronJob 对象,可以参考 EnsureCronJob 深入了解 CronJob 的创建逻辑。

apiVersion: batch/v1beta1
kind: CronJob
metadata:
# 该 CronJob 的名字,即创建 cronjob trigger 时指定的 name
name: scheduled-invoke-hello
...
spec:
# 该 CronJob 的执行计划,即创建 cronjob trigger 时指定的 schedule
schedule: */1 * * * *
...
jobTemplate:
spec:
activeDeadlineSeconds: 180
template:
spec:
containers:
- args:
- curl
- -Lv
# HTTP headers,包含 event-id、event-time、event-type、event-namespace 等信息
- ' -H "event-id: xxx" -H "event-time: yyy" -H "event-type: application/json" -H "event-namespace: cronjobtrigger.kubeless.io"'
# kubeless 会为 function 创建一个 ClusterIP 类型的 Service
# 可以根据 service 的 name、namespace 拼出 endpoint
- http://hello.default.svc.cluster.local:8080
image: kubeless/unzip
name: trigger
restartPolicy: Never
...

自定义 trigger

如果发现 kubeless 默认提供的触发器无法满足业务需求,可以自定义新的触发器。新触发器的构建流程如下:

  1. 为新的事件源创建一个 CRD 来描述事件源触发器;
  2. 在自定义资源对象的 spec 里描述该事件源的属性,例如 KafkaTriggerSpec、HTTPTriggerSpec;
  3. 为该 CRD 创建一个 CRD controller。

    1. 该 controller 需要持续监听针对事件源触发器和 function 的 CRUD 操作并作出正确的处理。例如,controller 监听到 function 的删除事件,需要把和该 function 关联的触发器一并删掉;
    2. 当事件发生时,触发关联函数的执行。

我们可以看到,自定义 trigger 的流程遵循了 K8s Operator 设计模式。

小结

  1. Kubeless 提供了一些基本常用的触发器,如果有其他事件源也可以通过自定义触发器接入;
  2. 不同事件源的接入方式不同,但最终都是通过访问函数 ClusterIP 类型的 service 触发函数执行。

自动伸缩

K8s 通过 Horizontal Pod Autoscaler 实现 pod 的自动水平伸缩。Kubeless 的 function 通过 K8s deployment 部署运行,因此天然可以利用 HPA 实现自动伸缩。

度量数据获取

自动伸缩的第一步是要让 HPA 能够获取度量数据。目前,kubeless 中的函数支持基于 cpu 和 qps 这两种指标进行自动伸缩。下图展示了 HPA 获取这两种度量数据的途径。

内置度量指标 cpu

CPU 使用率属于内置度量指标,对于这类指标 HPA 可以通过 metrics API 从 Metrics Server 中获取数据。Metrics Server 是 Heapster 的继承者,它可以通过kubernetes.summary_api从 Kubelet、cAdvisor 中获取度量数据。

自定义度量指标 qps

QPS 属于自定义度量指标,想要获取这类指标的度量数据需要完成下列步骤。

  1. 部署用于存储度量数据的系统,这里选择已经被纳入 CNCF 的 Prometheus。Prometheus 是一套开源监控&告警&时序数据解决方案,并且被 DigitalOcean、Red Hat、SUSE 和 Weaveworks 这些 cloud native 领导者广泛使用;
  2. 采集度量数据,并写入部署好的 Prometheus 中。Kubeless 提供的函数框架会在函数每次被调用时,将下列度量数据 function_duration_seconds、function_calls_total、function_failures_total 写入 Prometheus(可参考 python 样例)。
  3. 部署实现了 custom metrics API 的 custom API server。这里,因为度量数据被存入了 Prometheus,因此选择部署 k8s-prometheus-adapter,它可以从 Prometheus 中获取度量数据。

完成上述步骤后,HPA 就可以通过 custom metrics API 从 Prometheus Adapter 中获取 qps 度量数据。详细配置步骤可参考文章 kubeless-autoscaling。

K8s 度量指标简介

有时基于 cpu 和 qps 这两种度量指标对函数进行自动伸缩还远远不够。如果希望基于其它度量指标,需要了解 K8s 定义的度量指标类型及其获取方式。

目前,K8s 1.13 版本支持的度量指标类型如下:

类型 简介 获取方式
Object 代表 k8s 对象的度量指标,例如上文提到的 Service 对象的 function_calls 指标。 custom.metrics.k8s.io
度量数据采集后,需要通过已有适配器或自己实现适配器获取数据。目前已有的适配器包括 k8s-prometheus-adapter、azure-k8s-metrics-adapter、k8s-stackdriver等。
Pod 代表伸缩目标中每个 pod 的自定义度量指标,例如 pod 每秒处理的事务数。在与目标值比较前需要除以 pod 个数。 同上
Resource 代表伸缩目标中每个 pod 的 K8s 内置资源指标(如 CPU、Memory)。在与目标值比较前需要除以 pod 个数。 metrics.k8s.io
从 Metrics Server 或 Heapster 中获取度量数据。
External External 是一个全局度量指标,它与任何 K8s 对象无关。它允许伸缩目标基于来自 cluster 外部的信息进行伸缩(如外部负载均衡器的 QPS,云消息服务中的队列长度)。 external.metrics.k8s.io
度量数据采集后,需要云平台厂商提供适配器或自己实现。目前已有的适配器包括 azure-k8s-metrics-adapter、k8s-stackdriver等。

准备好相应的度量数据和获取数据的组件,HPA 就能基于它们对函数进行自动伸缩。更多关于 K8s 度量指标的介绍可参考文章 hpa-external-metrics。

度量数据使用

知道了 HPA 获取度量数据的途径后,下面描述 HPA 如何基于这些数据对函数进行自动伸缩。

基于 cpu 使用率

假设已经存在一个名为 hello 的函数,以下命令将为该函数创建一个基于 cpu 使用率的 HPA,它将运行该函数的 pod 数量控制在 1 到 3 之间,并通过增加或减少 pod 个数使得所有 pod 的平均 cpu 使用率维持在 70%。

kubeless autoscale create hello --metric=cpu --min=1 --max=3 --value=70

Kubeless 使用的是 autoscaling/v2alpha1 版本的 HPA API,该命令将要创建的 HPA 如下:

kind: HorizontalPodAutoscaler
apiVersion: autoscaling/v2alpha1
metadata:
name: hello
namespace: default
labels:
created-by: kubeless
function: hello
spec:
scaleTargetRef:
kind: Deployment
name: hello
minReplicas: 1
maxReplicas: 3
metrics:
- type: Resource
resource:
name: cpu
targetAverageUtilization: 70

该 HPA 计算目标 pod 数量的公式如下:

TargetNumOfPods = ceil(sum(CurrentPodsCPUUtilization) / Target)

基于 qps

以下命令将为函数 hello 创建一个基于 qps 的 HPA,它将运行该函数的 pod 数量控制在 1 到 5 之间,并通过增加或减少 pod 个数确保所有挂在服务 hello 后的 pod 每秒能处理的请求次数之和达到 2000。

kubeless autoscale create hello --metric=qps --min=1 --max=5 --value=2k

该命令将要创建的 HPA 如下:

kind: HorizontalPodAutoscaler
apiVersion: autoscaling/v2alpha1
metadata:
name: hello
namespace: default
labels:
created-by: kubeless
function: hello
spec:
scaleTargetRef:
kind: Deployment
name: hello
minReplicas: 1
maxReplicas: 5
metrics:
- type: Object
object:
metricName: function_calls
target:
apiVersion: autoscaling/v2beta1
kind: Service
name: hello
targetValue: 2k

基于多项指标

如果计划基于多项度量指标对函数进行自动伸缩,需要直接为运行 function 的 deployment 创建 HPA。

使用如下 yaml 文件可以为函数 hello 创建一个名为hello-cpu-and-memory的 HPA,它将运行该函数的 pod 数量控制在 1 到 10 之间,并尝试让所有 pod 的平均 cpu 使用率维持在 50%,平均 memory 使用量维持在 200MB。对于多项度量指标,K8s 会计算出每项指标需要的 pod 数量,取其中的最大值作为最终的目标 pod 数量。

kind: HorizontalPodAutoscaler
apiVersion: autoscaling/v2alpha1
metadata:
name: hello-cpu-and-memory
namespace: default
labels:
created-by: kubeless
function: hello
spec:
scaleTargetRef:
kind: Deployment
name: hello
minReplicas: 1
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
targetAverageUtilization: 50
- type: Resource
resource:
name: memory
targetAverageValue: 200Mi

自动伸缩策略

一个理想的自动伸缩策略应当处理好下列场景:

  1. 当负载激增时,函数能迅速扩展以应对突发流量;
  2. 当负载下降时,函数能立即收缩以节省资源消耗;
  3. 具备抗噪声干扰能力,能够精确计算出目标容量;
  4. 能够避免自动伸缩过于频繁造成系统抖动。

Kubeless 依赖的 HPA 充分考虑了上述情形,不断改进和完善其使用的自动伸缩策略。下面以 K8s 1.13 版本为例描述该策略。如果想要更加深入地了解策略原理请参考链接 horizontal。

HPA 每隔一段时间会根据获取的度量数据同步一次和该 HPA 关联的 RC / Deployment 中的 pod 个数,时间间隔通过 kube-controller-manager 的参数--horizontal-pod-autoscaler-sync-period指定,默认为 15s。在每一次同步过程中,HPA 需要经历如下图所示的计算流程。

计算目标副本数

分别计算 HPA 列表中每项指标需要的 pod 数量,记为 replicaCountProposal。选择其中的最大值作为 metricDesiredReplicas。在计算每项指标的 replicaCountProposal 过程中会考虑下列因素:

  1. 允许目标度量值和实际度量值存在一定程度的误差,如果在误差范围内直接使用 currentReplicas 作为 replicaCountProposal。这样做是为了在可接受范围内避免伸缩过于频繁造成系统抖动,该误差值可以通过 kube-controller-manager 的参数--horizontal-pod-autoscaler-tolerance指定,默认值是 0.1。
  2. 当一个 pod 刚刚启动时,该 pod 反映的度量值往往不是很准确,HPA 会将这种 pod 视为 unready。在计算度量值时,HPA 会跳过处于 unready 状态的 pod。这样做是为了消除噪声干扰,可以通过 kube-controller-manager 的参数--horizontal-pod-autoscaler-cpu-initialization-period(默认为 5 分钟)和--horizontal-pod-autoscaler-initial-readiness-delay(默认为 30 秒)调整 pod 被认为处于 unready 状态的时间。

平滑目标副本数

将最近一段时间计算出的 metricDesiredReplicas 记录下来,取其中的最大值作为 stabilizedRecommendation。这样做是为了让缩容过程变得平滑,消除度量数据异常波动造成的影响。该时间段可以通过参数--horizontal-pod-autoscaler-downscale-stabilization-window指定,默认为 5 分钟。

规范目标副本数

  1. 限制 desiredReplicas 最大为 currentReplicas * scaleUpLimitFactor,这样做是为了防止因 采集到了“虚假的”度量数据造成扩容过快。目前 scaleUpLimitFactor 无法通过参数设定,其值固定为 2。
  2. 限制 desiredReplicas 大于等于 hpaMinReplicas,小于等于 hpaMaxReplicas。

执行扩容缩容操作

如果通过上述步骤计算出的 desiredReplicas 不等于 currentReplicas,则“执行”扩容缩容操作。这里所说的执行只是将 desiredReplicas 赋值给 RC / Deployment 中的 replicas,pod 的创建销毁会由 kube-scheduler 和 worker node 上的 kubelet 异步完成的。

小结

  1. Kubeless 提供的自动伸缩功能是对 K8s HPA 的简单封装,避免了将创建 HPA 的复杂细节直接暴露给用户。
  2. Kubeless 目前提供的度量指标过少,功能过于简单。如果用户希望基于新的度量指标、综合多项度量指标或者调整自动伸缩的效果,需要深入了解 HPA 的细节。
  3. 目前 HPA 的扩容缩容策略是基于既成事实被动地调整目标副本数,还无法根据历史规律预测性地进行扩容缩容。

总结

Kubeless 基于 K8s 提供了较为完整的 serverless 解决方案,但和一些商业 serverless 产品还存在一定差距:

  1. Kubeless 并未在镜像拉取、代码下载、容器启动等方面做过多优化,导致函数冷启动时间过长;
  2. Kubeless 并未过多考虑多租户的问题,如果希望多个用户的函数运行在同一个集群里,还需要进行二次开发。

原文链接
本文为云栖社区原创内容,未经允许不得转载。

开源 serverless 产品原理剖析 - Kubeless的更多相关文章

  1. 【Xamarin 跨平台机制原理剖析】

    原文:[Xamarin 跨平台机制原理剖析] [看了请推荐,推荐满100后,将发补丁地址] Xamarin项目从喊口号到现在,好几个年头了,在内地没有火起来,原因无非有三,1.授权费贵 2.贵 3.原 ...

  2. 【Xamain 跨平台机制原理剖析】

    原文:[Xamain 跨平台机制原理剖析] [看了请推荐,推荐满100后,将发补丁地址] Xamarin项目从喊口号到现在,好几个年头了,在内地没有火起来,原因无非有三,1.授权费贵 2.贵 3.原生 ...

  3. MapReduce/Hbase进阶提升(原理剖析、实战演练)

    什么是MapReduce? MapReduce是一种编程模型,用于大规模数据集(大于1TB)的并行运算.概念"Map(映射)"和"Reduce(归约)",和他们 ...

  4. ASP.NET Core 运行原理剖析1:初始化WebApp模版并运行

    ASP.NET Core 运行原理剖析1:初始化WebApp模版并运行 核心框架 ASP.NET Core APP 创建与运行 总结 之前两篇文章简析.NET Core 以及与 .NET Framew ...

  5. 写给 Android 应用工程师的 Binder 原理剖析

    写给 Android 应用工程师的 Binder 原理剖析 一. 前言 这篇文章我酝酿了很久,参考了很多资料,读了很多源码,却依旧不敢下笔.生怕自己理解上还有偏差,对大家造成误解,贻笑大方.又怕自己理 ...

  6. 基本功 | Litho的使用及原理剖析

    1. 什么是Litho? Litho是Facebook推出的一套高效构建Android UI的声明式框架,主要目的是提升RecyclerView复杂列表的滑动性能和降低内存占用.下面是Litho官网的 ...

  7. ARouter原理剖析及手动实现

    ARouter原理剖析及手动实现 前言 路由跳转在项目中用了一段时间了,最近对Android中的ARouter路由原理也是研究了一番,于是就给大家分享一下自己的心得体会,并教大家如何实现一款简易的路由 ...

  8. Visual Leak Detector原理剖析

    认识VLD VLD(Visual Leak Detector)是一款用于Visual C++的开源内存泄漏检测工具,我们只需要在被检测内存泄漏的工程代码里#include “vld.h”就可以开启内存 ...

  9. Spring 核心技术与产品理念剖析(上)

    IT 技术发展太快了,就像浪潮一样一波接着一波,朝你迎面扑来,稍不留神就会被巨浪卷至海底而不得翻身.我们必须要学会抓住那些不变的本质或规律,只有这样才能屹立潮头而不倒,乘风破浪,做这个巨变时代的弄潮儿 ...

随机推荐

  1. android 第三次作业

    android studio音乐播放器 一.实现功能: 1.读取本地SD中的所有音频文件 2.歌单列表展示,并显示音频具体信息 3.进度条显示当前播放进度,可滑动加速 4.点击歌单进行播放 5.实现暂 ...

  2. Windows环境下,本地Oracle创建dblink连接远程mysql

    前言 我的情况是,本地安装了oracle(安装完成后带有SQL Developer,不需要再安装instantclient),创建dblink去连接远程的mysql.有些朋友可能是 本地使用PL\SQ ...

  3. Luogu 3758 [TJOI2017]可乐(有向图邻接矩阵幂的意义 矩阵快速幂)

    题目描述 加里敦星球的人们特别喜欢喝可乐.因而,他们的敌对星球研发出了一个可乐机器人,并且放在了加里敦星球的1号城市上.这个可乐机器人有三种行为: 停在原地,去下一个相邻的城市,自爆.它每一秒都会随机 ...

  4. 安装selenium

    步骤: 1.下载setuptools2.点击安装setuptools.exe3.安装成功后,python的安装目录下会有Scripts的文件夹4.配置环境变量:python安装目录\Scripts5. ...

  5. 微信JS SDK接入的几点注意事项

    微信JS SDK接入,主要可以先参考官网说明文档,总结起来有几个步骤: 1.绑定域名:先登录微信公众平台进入“公众号设置”的“功能设置”里填写“JS接口安全域名”.备注:登录后可在“开发者中心”查看对 ...

  6. Helm 入门指南

    Helm 为Kubernetes的软件包管理工具,Helm有两部分组成:Helm客户端.Tiller服务端,Helm三个主要部件:Chart.仓库.Release: Chart:为Kubernetes ...

  7. SpringBoot报错:Invalid bound statement (not found)

    错误原因: 没有发现Mybatis配置文件的路径 解决方法: 检查Mapper包名与xml文件标签的namespace数据名称是否相同 <mapper namespace="com.t ...

  8. Qt5和VS2017建立开发环境,安装后新建项目找不到Qt选项!!!

    最近开发win驱动和Qt5测试程序,需要建立Qt5和VS2017开发环境---对于Qt5和VS2017安装这里不做多余叙述. 参考资源很多,讲解也不错!! 这里切入正题:在VS2017中安转Qt vs ...

  9. wordpress安装插件和主题

    一.建立ftp服务器: 安装:sudo apt-get install vsftpd 配置:sudo nano /etc/vsftpd.conf 本地写入的注释去掉,可以写入的注释去掉 重启服务: s ...

  10. c++实验二

    1.函数重载编程练习编写重载函数add(),实现对int型,double型,Complex型数据的加法 #include<iostream> using namespace std; st ...