Openstack平台列表缓存概要设计

问题描述

当OpenStack集群部署规模大,资源数量多时(虚机资源,镜像资源,云硬盘资源,网络,子网,Port资源等)越来越多时, 浏览器前端列表显示随着资源增大会越来越慢。目前前端列表都是在前端JS分页,列表的API都是返回所有数据。

方案提议

  • 方案思路

    • 使用API分页
      优点 可彻底解决列表慢的问题。
      缺点 API分页无法实现列表搜索功能。
    • 使用缓存方案,后续通过缓存分页
      优点 速度快,侵入性小。
      缺点 需要保证缓存的实时和一直性, 不同的缓存方案实现的复杂度也不同。
    • 优化各个组件性能
      缺点 需要优化整个链路上的各个环节的性能,工作量不可估计。
  • 缓存位置分析

    • Horizon rest API结果缓存: 缓存API请求结果,API太多,且查询条件多,难以管理。
    • OpenStack API结果缓存: 缓存OpenStack API时的缓存结果,并保证缓存按时更新。
    • 数据库查询结果缓存: 缓存sql查询结果,sql查询组合多,缓存难以管理。

    综上所述选择OpenStack API结果缓存。

  • 方案设计
    • 项目架构


      cache.png

      (图片未更新)其中缓存介质为redis,并且后期加了cache-api组件,通过rpc调用给cache下发task(task msg格式类似jsonrpc)。cache api组件同时也给horizon提供rest api接口。

    • 缓存媒介: memcache(后改成了redis,原因参见此前文章:由用户session过期引发的memcached内存分配的思考 - 简书 (jianshu.com)
    • memcache python client: pylibmc(c语言实现,查询性能高)
    • 缓存数据结构设计: 以云主机数据为例

缓存数据结构

key value 说明
instance_uuids [uuid1, uuid2…] value为所有云主机uuid列表
instance_tenant_uuid1 [uuid1, uuid2…] key的末尾的uuid为租户uuid, value为对应租户下所有云主机 uuid列表
uuid1 {...} value为uuid1对应云主机的数据
  • 当有云主机数据发生变化时,只需要更新对于云主机uuid对应的缓存数据和instance_uuids以及 instance_tenant_uuid列表即可。
  • 缓存数据一致性: 每个种类的缓存均对应一种plugin来维护缓存数据的一致性(nova,neutron,cinder,glance)。 其中每种plugin中主要分为三个部分(periodic_sync, consume_queue, quick_periodic_sync, others)
    • periodic_sync: 每隔5分钟对插件通过调用组件client获取负责资源的所有数据,并将缓存数据进行一次更新。
    • consume_queue: 通过消费OpenStack组件的notification msg,并更加notification的event type和资源uuid来热更新缓存数据。
    • others: 参见FAQ[1]
  • 实现:
    • 每种缓存插件都有两个个queue,一个用于缓冲msg,一个用于接受cache api组件的rpc调用的msg。
    • 当插件运行时会启动多个个单独的线程。其中一个用于消费OpenStack组件的notification,当消费者收到消息后会将notification的msg解码后放入缓冲queue。
    • 另外还有一个线程用于处理queue中的msg,每当从queue中获取到msg时,先根据msg中的event_type类型,将msg传入对应 类型的handle_msg方法中进行处理。不直接从MQ的queue中消费消息
    • 使用自己的queue做缓冲的原因
      • 不会因接收到消息后处理慢就导致msg堆积。
      • 对于 有的OpenStack组件有的操作没法notification的情况,我们可以直接构造一个fake_msg后发送到对应组件的queue中,用于 更新组件对应资源的缓存数据。不通过MQ的生产者发送原因,由于平台其他功能也有使用OpenStack的notification (例如计费等)所以为了不影响原有功能,中间加一层queue做缓冲,即防止msg堆积,也能解决组件某些操作无notification 导致的缓存数据不同步的问题。

项目剖析

  • 代码结构

    escache
      ├── cache.py         # 函数入口
      ├── cmd
      │   ├── cache.py     # entrypoint script
      │   └── __init__.py
      ├── common           # 存放公共函数
      │   ├── __init__.py
      │   ├── memcache.py  # 用于get/set/del缓存数据
      │   ├── rabbit.py    # 用于生成MQ消费者
      │   └── utils.py     # 公共函数(由于是并发可能导致list中重复append数据,用于解决此类问题)
      ├── config.py        # 配置
      ├── __init__.py
      ├── plugins          # 组件plugin,每种组件的插件维护一类资源,例如nova负责云主机缓存数据的一致性维护
      │   ├── base.py      # plugin基类,组建根据自身需要重写单独的方法
      │   ├── cinder.py
      │   ├── glance.py
      │   ├── __init__.py
      │   ├── neutron.py
      │   └── nova.py
      └── tests            # UT测试脚本目录
          └── __init__.py
    
  • 错误处理
    所有的exception均在common/base中处理,plugin内部不做try catch处理, 需记录日志。 memcache操作error时,需要重连memcache,原因参见common/memcache部分。
  • common/memcache
    潜在问题 平台中memcached是多副本形态,当主 memcache和”备”memcache pod之间网络不通时,”备”memcache会修改memcached svc的 endpoints,将memcached的域名解析到自己的pod ip。而cache组件和memecache建立的是一个长连接,horizon中CacheManager每次查询都会建立一个新连接,并在操作结束后关闭连接,会导致数据不同步。
    为了解决上述问题,每当memcache操作error后都重新建立一个新的memcache连接。
  • common/rabbit
    连接RabbitMQ并创建一个消费者,工作模式[Topic](RabbitMQ tutorial - Topics — RabbitMQ
    )。
  • plugins/base
    负责启动3个线程(consume_queue, periodic_sync, quick_periodic_sync)。
    • periodic_sync(nova为例)
      periodic_sync会启动每个plugin的sync_cache。sync_cache中首先会定义两个变量 (sync_uuids, sync_tenant_uuids)分别用于保存OpenStack client查询出来的 数据的数据的uuid列表和对应租户下的数据的uuid列表。
      由于OpenStack client list获取资源数据耗时较长,所以在此期间用户可能对资源进行了 增删改,如果直接用这时候list出来的数据并直接更新缓存会导致缓存中数据不是最新的。 因此plugin中会定义两个deque(recent_deleted_uuids, recent_updated_uuids) 分别用于保存OpenStack client list期间内被删除的资源列表和更新的资源列表。每次 sync_cache中执行OpenStack client list前会清空这两个deque。如果listu出的数据的 uuid在recent_deleted_uuids中则说明在list期间这个资源已经被删除,直接跳过。如果 uuid在recent_updated_uuids中,但是没在list到的数据中,说明该uuid对应的资源是在 list期间创建的,则需要将这个uuid添加到sync_uuids和sync_tenant_uuids中。
      更新list到中的数据时,需要判断list到的数据的updated字段如果晚于缓存中该数据的updated 字段才需要更新缓存中的数据。
    • consume_queue
      consume_queue会启动每个pulgin的handle_msg。其中根据msg的event_type来将msg丢 给对应的msg处理函数处理,其中如果删除资源,会将uuid记录到recent_deleted_uuids中, 并且从recent_updated_uuids中移除,如果存在recent_updated_uuids中的话。当更新 操作时当数据在缓存中更新完后会将uuid记录至recent_updated_uuids中。(fake_msg也 由此方法i处理,不同的fake_msg需要根据需要单独处理)
    • quick_periodic_sync
      quick_periodic_sync会触发plugin的quick_sync_cache方法,用于间隔查询某些资源 数据并更新缓存数据,其间隔时间较短,用于一些特殊的资源(例如error的volume),该类 资源的操作无任何notification,所以只能通过OpenStack的client list去获取该资源 的数据,并更新缓存数据。
  • plugin(neutron为例)
    plugin用于维护某一类特定资源的缓存数据,例如neutron用于维护port和floatingip的缓存数 据。但是用户进行网络资源的操作时会引起port的增删,或者floatingip的绑定与解绑操作也会引 起云主机的某些字段更新等,并且由于这些操作虽然引发的数据更新,但是对应的nova或者neutron 却并未发送notification所以会导致缓存数据的不同步,所以plugin中除了维护常规的资源uuid 列表数据之外还需要单独维护例如port_device_data, port_fip_data, fip_device_data, fip_router_data, router_port_data, fip_port_data, lb_port_data等字典,用于 查询资源与资源之间uuid的对应关系(例如port_device_data中以port uuid为key,云主机的 instance_uuid为value,以此当云主机断开网络时,port删除时可以根据port的uuid获取对应 云主机的instance_uuid,并以此构造fake_msg通知nova的缓存plugin更新对应uuid的云主机 缓存数据。该部分数据不仅在sync_cache中需要统一维护(当缓存启动前就存在部分数据有关联关 系,所以需要维护),并且在处理msg时也需要维护其对应关系。
    其中每种资源的msg都分为两大类,删除和更新,其中更新中也包含了新建。处理消息时除了正常的 删除,更新数据外,同样需要根据event_type来判断该资源的更新删除会不会引发关联的资源发生 更新,如果会则需要单独处理。并且有的操作引发的关联资源的更新需要等待资源更新生效,所以如 果存在这种情况时,我们需要在send fake msg之前sleep一下或者在关联资源检查状态时加上状 态标志并一直通过OpenStack client获取该资源直至状态符合或者超时,来等待资源更新生效。 (例如云主机绑定公网ip时检查port状态是否为active)
  • 已知OpenStack组件缺失notification情况汇总及处理
    其中处理大多数均通过构造fake_msg通知对应组建更新数据
    • 云主机绑定或解绑公网ip时需通知nova更新云主机数据
    • 申请公网ip时需通知neutron更新port数据
    • 新建或删除loadbalancer时需通知neutron更新port数据
    • 删除或者断开网络时port时通知nova, neutron更新云主机和floatingip数
    • 连接网络时通知nova更新云主机状态
    • 创建删除路由时通知neutron更新port
    • 路由器设置网关时通知neutron更新floatingip
    • 删除错误的云硬盘时无notification,每隔2秒获取一下状态为error的云硬盘数据,并更新缓存数据
  • Horizon hooks
    horizon部分在api中有一个cache.py里面定义了一个CacheManager的类,nova,glance,cinder, neutron api当调用OpenStack client时获取数据时,先会通过CacheManager获取对应的list数据, 如果未获取到数据,再通过OpenStack client去获取数据。其中CacheManager获取数据支持search_opts 并且只有cloud_admin可以获取所有的缓存数据,否则只能获取对应租户的所有缓存数据。

FAQ
[1] OpenStack中某些操作缺失notification(eg:nova删除错误虚机时不会发送port.delete.end的notification,会导致neutron中port数据没删除)
解决方案:

  1. 删除错误虚机时,cache的nova plugin收到instance.delete.end消息时判断虚机状态,如果是error望neutron plugin的queue中放入一个port.delete.end msg(使用该方案)
  2. sqlalchemy中添加trigger,这样可以不通过notification来更新缓存中数据
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,029评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,238评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,576评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,214评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,324评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,392评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,416评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,196评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,631评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,919评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,090评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,767评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,410评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,090评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,328评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,952评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,979评论 2 351

推荐阅读更多精彩内容