Dawn's Blogs

分享技术 记录成长

0%

Redis 单线程模型

Redis 单线程指的是「接收客户端请求->解析请求 ->进行数据读写等操作->发送数据给客户端」这个过程是由一个线程(主线程)来完成的,这也是我们常说 Redis 是单线程的原因。

后台线程

但是,Redis 程序并不是单线程的,Redis 在启动的时候,是会启动后台线程(BIO)的:

  • Redis 在 2.6 版本,会启动 2 个后台线程,分别处理关闭文件AOF 刷盘这两个任务;
  • Redis 在 4.0 版本之后,新增了一个新的后台线程,用来异步释放 Redis 内存,也就是 lazyfree 线程。例如执行 unlink key / flushdb async / flushall async 等命令,会把这些删除操作交给后台线程来执行,好处是不会导致 Redis 主线程卡顿。

因此,当删除一个大 key 的时候,不要使用 del 命令删除,因为 del 是在主线程处理的,这样会导致 Redis 主线程卡顿,因此我们应该使用 unlink 命令来异步删除大key。

阅读全文 »

Buffer Pool 介绍

MySQL 的数据是存储在磁盘中的,但是直接读取磁盘数据性能差。所以为了提升查询性能,加入缓冲池 buffer pool。有了 buffer pool 之后:

  • 读取数据时,如果数据存在于 Buffer Pool 中,客户端就会直接读取 Buffer Pool 中的数据,否则再去磁盘中读取。
  • 修改数据时,首先是修改 Buffer Pool 中数据所在的页,然后将其页设置为脏页,最后由后台线程将脏页写入到磁盘。

buffer pool 大小

在 MySQL 启动时,会申请一段连续的存储空间作为 buffer pool,默认配置下 buffer pool 有 128 MB。

可以修改 innodb_buffer_pool_size 参数来设置 buffer pool 的大小,一般建议设置成**可用物理内存的 60%~80%**。

缓存内容

InnoDB 在存储数据时,会划分若干个,以页作为磁盘和内存交互的基本单位。所以,buffer pool 也是按照页划分的,一个页的默认大小为 16 KB。

在 MySQL 启动的时候,InnoDB 会为 Buffer Pool 申请一片连续的内存空间,然后按照默认的 16KB 的大小划分出一个个的页, Buffer Pool 中的页就叫做缓存页。MySQL 刚启动时,使用的虚拟内存空间很大,但是物理内存空间很小,只有这些虚拟内存被访问后,操作系统才会触发缺页中断,接着将虚拟地址和物理地址建立映射关系。

Buffer pool 除了缓存数据页索引页,还包括了回滚页、插入换成、自适应哈希索引、锁信息等。

img

阅读全文 »

模型部署按照部署设备分类,主要分为两类:

  • 云端/服务器上的部署,将模型作为一个线上服务进行部署。
  • 移动端/设备端上的部署,就是将模型作为 SDK 进行部署。

本文主要探讨模型作为一个线上服务进行部署。

作为线上服务部署

把 AI 模型作为一个线上服务部署,就需要提供外界访问的接口:

  • Restful HTTP 接口,使用 Flask、Django 等构建 HTTP API 服务。
  • RPC 接口,如 GRPC、Thrift 等。

也可以使用深度学习框架 Serving 实现快速部署:Torch Serving、Tensorflow Serving、Triton 等。

服务优化

模型部署涉及到了服务优化,例如内存占用太大怎么办、响应速度太慢怎么办,如何在线模型更新等等问题。

更小的内存占用

优化内存占用,有两个基本思路:

  • 模型的参数都需要保存在内存中?
  • 把模型变小。

分布式 kv 存储

如下图的模型结构,分为两个部分:

  • dense 网络:右侧深层网络,输入用户画像、用户行为序列等高级特征,结构较复杂。
  • sparse 网络:左侧浅层网络,输入用户或物料 id,结构非常简单。

推荐模型中,由于用于、候选物料极多, sparse 网络的参数占模型参数的 99%。但是进行预测时,sparse 网络只有输入的用户和候选内容 ID 对应的节点参与运算,dense 网络大部分节点都参与运算

image-20231030113409693

dense 网络每次计算都用到,且占用空间小,不适合独立存储;sparse 网络每次计算只用到很少一部分参数,适合独立存储。

所以将 dense 网络存储在内存中,sparse 网络以分布式 kv 的方式进行存储

压缩模型

多用于 NLP、CV 等领域,可以使用:

  • 模型量化(把模型从浮点数变为整数 INT8)。
  • 蒸馏(将复杂的网络特征提取出来,迁移到参数量小的网络中)。

压缩模型会使得模型的效果变差,要在模型效果和计算速度上进行 balance。

更快的推理速度

模型加速推理的优化方向:

  • 算法层加速:
    • 训练前:选用轻量级的网络。
    • 训练后加速
  • 框架层加速:tensorRT 等。
  • 硬件层加速:使用 GPU 而不是 CPU。

算法层加速 - 训练后加速

训练后加速方法:

  • 模型量化。
  • 模型蒸馏。
  • 模型剪枝:去掉模型中不重要的参数。

模型剪枝可行性:研究表明,并不是所有的参数都在模型中发挥作用,部分参数作用有限、表达冗余,甚至会降低模型的性能。

image-20231030114328061

  • 模型拆分:模型按照计算逻辑拆分成几部分,推理时只需要计算部分模型,其他部分在合适时机提前计算、存储好

框架层加速

使用 tensorRT 等加速计算框架,可以合并网络结构、提高计算的并发度。

硬件加速

硬件加速就是使用 GPU 来进行推理,而不是使用 CPU。

硬件加速并不是万能药,对于简单模型不适合硬件加速,I/O 耗时大于计算耗时。

更快的模型更新

模型热更新时,有模型文件大加载时间长加载/更新时不可用来预测的问题,所以通过加锁的方式解决模型热更新,会造成服务短暂不可用

double buffer 机制

为了解决加锁时服务不可用的问题,所以可以在内存空间中保存两份模型,一份用于正常运行,另一份用于更新模型,模型更新完成后切换新模型进行预测。

online learning 在线学习

在推荐领域经常用到,这里只是提出来不做详细解释。

image-20231030115700729

可维护性 & 开发效率提升

Serving 框架

成熟的 Serving 框架可以简化开发,提供包括但不限于以下:

  • 对外暴露 HTTP/GRPC 接口。
  • 模型版本管理。
  • 动态 batch。

A/B 实验

部署多个模型服务,用于接收 client 的请求,通过流量控制实现 A/B 实验。

image-20231030120530335

MLOps

MLOps = ML + Dev + Ops,MLOps 的原则:

  • Automation:环节尽可能自动化
  • Continuous:模型的持续集成、持续部署、持续监控、持续训练
  • Versioning:代码、数据、模型的版本管理
  • Experiment Tracking:实验记录
  • Testing:数据、模型、应用的自动化测试
  • Monitoring:输入数据、模型输出的监控、降级
  • Reproducibility:减少随机性,保证可复现

image-20231030120327944

Pilot 的 xDS 服务器功能由 DiscoveryServer 实现,由于 pilot 与 envoy 之间的 Grpc 连接是双向 stream,所以 pilot 可以主动向 envoy 推送更新,或者 envoy 也可以主动发起 xDS 请求。

在 DiscoveryServer 开始时,会启动几个协程,其中最重要的是:

  • handleUpdates:用于从 pushChannel 中取出推送消息,并进行 debounce 后送入 pushQueue
  • sendPushes:用于从 pushQueue 中 取出消息,生成 xDS 消息,并发送到客户端的 pushChannel 中
1
2
3
4
5
6
7
func (s *DiscoveryServer) Start(stopCh <-chan struct{}) {
go s.WorkloadEntryController.Run(stopCh)
go s.handleUpdates(stopCh)
go s.periodicRefreshMetrics(stopCh)
go s.sendPushes(stopCh)
go s.Cache.Run(stopCh)
}

向 Envoy 推送更新

向 Envoy 推送更新的流程如下:

  • Config Controller 和 Service Controller 在配置或者服务发生变化时通过回调方法通知 Discovery Server,Discovery Server 将变化的消息推至 Push Channel 中
  • Discovery Server 通过一个 goroutine 从 Push Channel 中接收变化消息,将一段时间内连续发生的变化消息进行合并(debounce)。如果超过指定时间没有新的变化消息,则将合并后的消息加入到一个队列 Push Queue 中
  • 另一个 goroutine 从 Push Queue 中取出变化消息,生成 XdsEvent,发送到每个客户端连接的 Push Channel 中
  • (每一个 Grpc 客户端连接)在 DiscoveryServer.StreamAggregatedResources 方法中从 Push Channel 中取出 XdsEvent,然后根据上下文生成符合 xDS 接口规范的 DiscoveryResponse,通过 GRPC 推送给 Envoy
阅读全文 »

在 pilot Server 初始化过程中,在初始化 ConfigController 和 ServiceController 之后,利用 initRegistryEventHandlers 方法注册了配置和服务变化事件的处理 handler。这其中头两个重要步骤:

  • 监听到配置/服务的变化(在 Kube 中为 Informer)。
  • 针对这些变化做出响应(注册 Handler)。

监听配置变化

在使用 Kubernetes 作为 ConfigController 时,使用 crdclient.Client 作为 ConfigController 的实现,在 crdclient.Client 中为每一个 istio 定义的 CRD 配置开启了一个 Informer 用于监听 CRD 配置信息的变化

阅读全文 »

在 Istio 架构中,Pilot 属于最核心的组件,Pilot 的一个主要职责就是将服务信息和配置数据转换为 xDS 接口的标准数据结构,通过 Grpc stream 下发到数据平面的 Envoy。Polit 的输入来源主要有两个:

  • 服务数据:来源于各个服务注册表(Service Registry),如 Kubernetes 和 Consul。
  • 配置规则:各种配置规则,包括路由规则和流量管理等规则。

image-20231018112119194

Istio Pilot 代码分为 pilot-agent 和 pilot-discovery,其中 pilot-agent 主要在数据平面负责管理 Envoy 的生命周期,pilot-discovery 是控制平面组件,本节主要分析 pilot-discovery。

阅读全文 »

本节说明 webhook 和 snp 组件,在 dubbo-cp 中:

  • webhook 用于实现动态准入控制器的注入功能。
  • snp(ServiceNameMapping)用于记录接口名 InterfaceName 到应用名 AppName 的映射并更新到 SNP CRD 中,通过 DDS 的通知各个 Dubbo 应用进行流量控制等数据平面的操作。

webhook

webhook 组件结构体定义如下,主要包括以下几个部分:

  • WebhookServer:动态准入控制的 Webhook 服务器。
  • JavaInjector:用于在创建 Pod 时注入信息。
1
2
3
4
5
6
7
8
type WebhookServer struct {
Options *dubbo_cp.Config
WebhookClient webhookclient.Client
CertStorage *cert.CertStorage

WebhookServer *webhook.Webhook
JavaInjector *patch.JavaSdk
}

Webhook 服务器

Webhook 服务器用于监听 Kubernetes 的动态准入控制请求,进而修改创建 Pod 的 Spec 信息实现信息注入。HTTP Path 为 mutating-services,Handler 为 Mutate。

其中 Webhook.Paches 为注入函数,getCertificate 为 dubbo-cp 服务器证书的获取方式,服务器证书通过证书管理模块管理。

1
2
3
4
5
webhookServer.WebhookServer = webhook.NewWebhook(
func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
return rt.CertStorage().GetServerCert(info.ServerName), nil
})
webhookServer.WebhookServer.Init(rt.Config())
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
type (
PodPatch func(*v1.Pod) (*v1.Pod, error)
GetCertificate func(*tls.ClientHelloInfo) (*tls.Certificate, error)
)

type Webhook struct {
Patches []PodPatch
AllowOnErr bool
getCertificate GetCertificate
Server *http.Server
}

func NewWebhook(certificate GetCertificate) *Webhook {
return &Webhook{
getCertificate: certificate,
AllowOnErr: true,
}
}

func (wh *Webhook) NewServer(port int32) *http.Server {
mux := http.NewServeMux()
mux.HandleFunc("/health", wh.ServeHealth)
mux.HandleFunc("/mutating-services", wh.Mutate)
return &http.Server{
Addr: fmt.Sprintf(":%d", port),
Handler: mux,
TLSConfig: &tls.Config{
GetCertificate: wh.getCertificate,
},
}
}

Mutate

Webhook 的 Mutate 方法用于动态准入控制,修改 Pod,注入信息。在 Mutate 执行的过程中,会依次执行 Patches 内所定义的函数,完成信息的注入。

更新 K8s webhook 配置

webhook 在启动时会更新 Kubernetes 动态准入 webhook 配置,更新的是 Kubernetes MutatingWebhookConfiguration 资源,用于指定:

  • 创建 Pod 时进行动态准入配置。
  • 配置在进行动态准入控制 webhook 的地址为 dubbo-cp Kubernetes Service
  • 配置在 webhook 过程中所信任的 webhook 服务器 CA 证书

在后续过程中,dubbo-cp 的证书管理模块用于更新动态准入 webhook 的所使用的证书。

阅读全文 »

控制平面功能

在 dubbo-kubernetes 中,控制平面称为 dubbo-cp,功能包括以下几点:

  • 安全性:包括证书管理和 CA 证书发布。
  • 动态准入控制服务器 webhook:在生成 Dubbo 应用时,用于注入 Pod。
  • DDS:与 Envoy 中的 xDS 一样提供各种资源的服务发现,这里称为 DDS(Dubbo DS),监听的资源包括 AuthenticationPolicy、AuthorizationPolicy、ServiceNameMapping、ConditionRoute、DynamicConfig、TagRoute 这些 CRD。主要的工作有两点:
    • 接受 Dubbo 应用的 observe RPC 请求,以长连接的方式监听资源。
    • 利用 Kubernetes 的 Informer 机制,监听各种资源的变化,并且将资源的变更推送到 Dubbo 应用中。
  • SNP(Service Name Mapping):在 Dubbo 应用启动时,会向控制平面发起 registerServiceAppMapping RPC 请求,注册接口名称与应用名称之间的映射,控制平面将接口名到应用名称的映射记录到 SNP CRD,DDS 监听到 SNP 资源变更后会推送至各个 Dubbo 应用中,Dubbo 应用根据 SNP 进行流量控制。
  • Admin:面向控制面管理人员,提供 HTTP 接口。

在 Dubbo 生态中,dubbo-cp 作为控制平面,而 Dubbo 应用作为数据平面。

阅读全文 »