草庐IT

短视频媒体处理系统应急响应自动化实践

字节跳动技术团队 2023-06-02 原文

动手点关注

干货不迷路

背景

每天在世界各地都有海量用户在短视频 App 上分享充满创意的视频或是生活中的精彩故事。

由于使用者所在的环境不可控(高铁、电梯等弱网环境),若直接播放原始画质的视频,可能导致观看影片的过程中出现卡顿甚至无法播放的情形,导致观影感受不佳。为了让不同网络条件的使用者,都能顺畅地观看这些视频,每一条视频的发布,都需要经过转码的过程,生成不同档位的视频,即使用户在网络不好的环境中,也能提供适合档位的视频,让使用者有顺畅的观影体验。

针对视频转码的场景,目前业界通用的解决方案大多为原始视频上传到物件储存后,通过事件触发媒体处理过程,当中可能涉及使用工作流系统做媒体处理任务的调度或是编排,处理完成的视频存档至物件储存后再透过内容分发网络(Content Delivery Network,CDN)分发给观影者。

图 1:媒体处理系统在 AWS 公有云上的业界通用解决方案

[来源]https://aws.amazon.com/media-services

在业界通用的公有云解决方案中,对开发者而言需要整合公有云上各个子系统,来完成视频转码的生命周期,以及管理虚拟机计算资源,这需要较大的认知成本以及对各种云服务的学习成本,对开发者来说是比较大的负担。

在字节,视频架构团队经过长年的技术积累,在视频这个领域已经形成了一个内部多媒体处理 PaaS 平台。用户通过上传系统上传视频到物件储存中,接着会触发媒体处理平台的任务编排系统,下发转码、生成动图、封面照等任务到具有海量资源的计算资源池,最后透过内容分发网络分发给观影者。多媒体处理 PaaS 平台主要由两大子系统构成,工作流系统与计算平台,并提供多租户的接入方式,用以支撑字节整个生态系的视频处理需求。

计算平台主要提供了一个大型的计算资源池,将各种异构资源(CPU、GPU)封装起来,让团队内媒体处理专业的开发者无需关注计算资源整合相关工作,可以专注于开发具有各种原子能力的无伺服器函数,提供转码、生成封面照、生成动图等丰富功能。工作流系统则提供了任务编排的能力,能够定义视频上传后需要执行各种媒体处理任务的先后顺序,并将任务下发到计算平台利用大型的计算资源池来完成媒体处理的工作。

透过这两大子系统提供的基础能力,可以大大减少开发者的负担以及提高功能迭代的速度。

图 2:视频架构团队媒体处理系统解决方案

技术框架 1.0

这么庞大的一个线上系统,如何保持它的稳定性,并且在线上有任何异常情况时,能够准确、快速地处理问题,减少对用户的影响就显得特别重要。针对部署在世界各地的多媒体处理 PaaS 平台都会定义服务水准指标 (SLI),并以此为基础定义服务水准目标 (SLO),并配置针对 SLO 的适当报警规则。

图 3:应急响应流程

如图 3 所示,当服务发生异常,例如:5分钟内请求正确率低于 99.9% 时,触发报警,并发送 Webhook 消息给团队内研发的应急响应中心平台,平台会将当下的值班人员创立一个告警处理群组,并把后续相关的报警信息都聚合到群组中,随后就由 SRE 开始介入处理。当前流程在创建告警处理群组之后,主要仰赖 SRE 去自主搜集与应急事件相关的异常指标,缺乏自动化工具提前做讯息的汇总,可能导致整体事故处理流程需要花费较多时间先梳理目前异常的指标才能做事故止损操作。

当前的痛点

微服务及依赖数量多

在团队中,服务的开发大部分走的是微服务架构,并且作为一个内部的 PaaS 平台,势必得提供全球跨区域的服务因此在服务本身以及基础设施方面,需要有多区域以及多机房的部署。目前只看单一区域媒体处理任务调度的微服务就有 30 个,此外还需考虑相关的基础设施的监控,如:数据库、缓存、分布式锁以及消息队列等......

图 4:数量庞大的微服务监控仪表板

因此,即使制作了如上图全局视角的监控仪表板,但在应急事件发生的当下就能迅速的定位如此庞大的服务拓扑中的异常点,仍然是一个具有挑战的任务。

不同指标比较基准不同

对于应急事件发生时,通常可以分为以下两种情况:基础设施异常、突发流量。

第一个例子:数据库基础设施。通常在正常运作下的查询延迟都会处于一个固定的水平,例如 10ms 。延迟上升分为:整体数据库延迟上升(可能是当下负载高),部分实例延迟上升(可能是部门网段有抖动)。

图 5:数据库延迟异常指标

第二个例子,突发流量。作为一个内部的 PaaS 平台,势必是提供了多租户的功能以服务字节内诸多团队的需求,且当租户数量达到一个量级,逐个租户去了解他们何时有活动或是有突发流量已经是不太合乎经济效益的事情,以下方的例子为例,可以看到指标呈现以天为周期的规律分布,但可以看到红框处紫色的指标较昨天明显得更多,这称之为昨日同比上升。

图 6:流量指标同比上升

错误排查涉及不同内部系统

第三个例子,涉及依赖系统的错误。以下图为例,红框处的错误量明显比过去半小时要高得多,这称之为环比上升。针对这种情况则需要到内部的 PaaS 平台去查询详细的错误码以及对应的错误日志。

图 7:依赖系统错误指标环比上升

目标

以上三种情况,以现有的监控以及排查手段,在应急事件发生时,整个排查的过程需要比对多个仪表板甚至是不停地在仪表板上切换不同的查询时间段来比较指标的正常性,更甚者需要打开其他内部系统来查找日志,这都大大延长了定位问题以及做应急处理的决策时间。

因此如果能够把上面的排查工作做一定程度的自动化,那就能大大提高 SRE 成员在值班时对照值班手册 SOP(标准作业流程)来排查的速度,并能减少值班的辛苦感受。

量化指标

平均修复时间(Mean time to repair,MTTR)包含了发现故障(Identify)、故障定位止损(Know)以及故障恢复(Fix)的整体时间。导入自动化排查工具的主要目的是减少故障定位止损的时间,目前系统都有设定针对 SLO 目标的告警发生以及恢复时间的数据统计,因此决定以 MTTR 这个指标来作为这次自动化系统导入的成果量化指标。

图 8:平均修复时间在事故时间序中的范围

架构

技术架构 2.0

图 9:改善后的应急响应流程

视频架构稳定性团队研发的应急响应中心平台(Emergency Center)内建了名为 SOP Engine 的集成解决方案,提供了 SDK 让 SRE 的成员能快速开发,如:Metrics 查询、分析、发起 HTTP 请求等通用的无服务器函数,并能够利用 yaml 定义状态机工作流编排,来实现自定义的告警诊断或是应急处理预案的工作流编排使用。

自动化工作流设计

整个自动化告警处理流程可以归纳成如下图的步骤:

  1. 工作流被告警的 Webhook 触发,平台携带告警上下文(时间、区域),以及预先设定在工作流中的 Metrics 查询目标(为服务名称、数据库名称、消息队列 Topic)和异常阈值

  2. 使用 Parallel Task 方式触发子工作流分别做:作业系统(CPU、Memory)、基础设施以及微服务的告警诊断

  3. 每个告警诊断子工作流中,都会经过 Metrics 查询、分析以及结果聚合三个阶段

  4. 最后组装要发送到应急响应群组的卡片并发送

图 10:自动化工作流内部流程

Metrics Query 函数

Metrics 查询函数设计了如下方范例的 API,能对接字节基于 OpenTSDB 搭建的 Metrics 平台,主要提供以下几种功能来大幅提升本函数的重用性。

  • Metrics 查询模板化,针对 indicator、tags、filters 都可以撰写 go template 语法并从 template_values 栏位带入值。

  • 支援一次查询多种时间区段资料,利用 time_ranges 栏位下可定义如:30分钟前、1天、1周前等......不同时间范围,在一次函数呼叫中全部取得。

  • Metrics 下钻功能,在 drill_downs 栏位可以定义针对原有 tags 上再额外追加 tags 来取得如:原本查询整体服务的 CPU 使用率,再额外查询该服务每个主机的 CPU 使用率。

{
    "zone": "xx",
    "indicator": "service.thrift.{{ .service_name }}.call.success.throughput",
    "template_values": {
        "service_name": "my_service_name",
        "to": "redis_cache"
    },
    "aggregator": "avg",
    "tags": {
        "idc": "literal_or(*)",
        "cluster": "literal_or(*)",
        "to": "literal_or({{ .to }})"
    },
    "filters": {
        "cluster": "my_cluster_name"
    },
    "rate_option": {
        "counter": false,
        "diff": false
    },
    "start_at": "now-5m",
    "end_at": "now",
    "time_ranges": {
        "5mago": {
            "start_at": "now-10m",
            "end_at": "now-5m"
        }
    },
    "drill_downs": {
        "instances": {
            "top": 1,
            "top_aggregator": "max",
            "tags": {
                "host": "literal_or(*)"
            }
        }
    }
}

Metrics Analysis 函数

Metrics 分析函数设计了如下图的 API ,让阈值、同环比分析甚至是针对 Metrics 中某一个 Tag 的下钻分析,都能够定制要分析的汇总结果(最大、最小、平均、总和),此外比较运算子跟阈值也能够随意调整,这对于后续要修改阈值或是分析的逻辑都提供了很大的便利性。

{
    "display": { // 必填
        "namePrefix": "今日", // 可选,显示名称前缀,默认:当前
        "name": "延迟", // 必填,分析结果指标显示名称
        "format": "latencyMs" // 可选,分析结果指标显示格式,不填则按原样输出,只显示到小数第二位,格式支援 default, percent, latency, latencyMs
    },
    "summary": "avg", // 必填,对哪一个汇总资料做显示及分析 sum, avg, max, min, count
    "threshold": { // 可选,阈值分析
        "value": 4, // 必填,原始数值阈值
        "operator": "gt" // 必填,比较运算子,支援 gt, gte, lt, lte, eq, ne
    },
    "time_ranges_percentage_difference": { // 可选,分析不同时间偏移资料
        "5mago": { // 键名,可自行指定名称
            "display": { // 必填
                "name": "5分环比" // 必填,分析结果显示名称
            },
            "summary": "avg", // 必填,对哪一个汇总资料做显示及分析 sum, avg, max, min, count
            "precondition": { // 可选,前置条件,原始 metrics 满足条件后才进行变化率分析
                "value": 4, // 必填,阈值
                "operator": "gt" // 必填,比较运算子,支援 gt, gte, lt, lte, eq, ne
            },
            "threshold": { // 可选,变化率阈值
                "value": 0.1, // 必填,阈值
                "operator": "gt" // 必填,比较运算子,支援 gt, gte, lt, lte, eq, ne
            }
        }
    },
    "drill_downs": { // 可选,分析不同下钻资料
        "instances": { // 键名,可自行指定名称
            "display": { // 必填
                "name": "单实例" // 必填,分析结果显示名称
            },
            "summary": "max", // 必填,对哪一个汇总资料做显示及分析 sum, avg, max, min, count
            "threshold": { // 可选,阈值分析
                "value": 10, // 可选,单实例原始数值阈值
                "stdDiff": 1, // 可选,单实例原始数值与其他下钻值平均比较标准差阈值
                "operator": "gt" // 必填,比较运算子,支援 gt, gte, lt, lte, eq, ne
            }
        }
    },
    "filter": true, // 可选,只显示有达到阈值的分析结果
    "metrics": [...] // 略,Metrics 查询函数返回的资料内容
}

JavaScript 执行函数

在 Metrics 聚合以及机器人卡片信息组装的步骤中,不同的 Metrics 的聚合条件以及机器人卡片显示逻辑各不相同,如果分别开发会让整体函数的重用性以及开发效率降低,因此利用了 github.com/rogchap/v8go 这个套件开发了可以对输入 JSON 数据动态执行 JavaScript 的函数来处理这一系列的用途,谁叫 JavaScript 就是处理 JSON 格式数据的最好方式呢,如下,对 JSON 内的 Array 数据都能用原生的 JavaScript 做群组、排序、倒序以及映射的操作,十分便利。

{
    "script": "data.flat().map(x => x * 2).filter(x => x > 5).reverse()",
    "data": [
        [1,2,3,4,5],
        [6,7,8,9]
    ]
}

实际案例:MySQL 延迟诊断

下图是一个实际异常诊断的例子如何用上述三个函数做组合,下图以 MySQL 延迟作为例子,可以看到大部分的 MySQL 延迟正常范围在 1s 以下,其中一台主机的延迟突然上升至 20.6s 这在应急响应中是需要被主动发现出来并且是有可能造成应急事件的异常。

图 11:MySQL 延迟单实例异常

  • 查询延迟

    ,如下方的工作流定义,只需要从 Grafana 仪表板中把用来做图的 Metrics 以及查询条件、时间范围、下钻 tag 依照前面提到的 Metrics 查询函数的 API 定义填入就能做 Metrics 查询。

MetricQuery:
  type: Task
  next: MetricAnalysis
  atomicOperationRef: metric_query
  variables:
    zone: xxx
    indicator: mysql.latency.pct99
    tags:
      idc: literal_or(*)
      db: my_database
    aggregator: avg
    start_at: now-30m
    end_at: now
    time_ranges:
      1d:
        start_at: now-1d30m
        end_at: now-1d
    drill_downs:
      instances:
        top: 30
        top_aggregator: max
        tags:
          host: literal_or(*)
          port: literal_or(*)
  • 分析延迟

    ,下方的工作流定义,会在 Metrics 查询函数执行完成后执行,主要需要提供显示分析结果的文案、Metrics 的单位、以及各项异常分析的阈值。

MetricAnalysis:
  type: Task
  next: GroupResult
  atomicOperationRef: metric_analysis
  variables:
    metrics.@: "@.data" # 从查询延迟函数输出取得查询结果
    filter: true
    display:
      name: 延迟
      format: latencyMs
    summary: avg # 整体延迟平均超过 500ms 视为异常
    threshold:
      value: 500
      operator: gt
    time_ranges_percentage_difference:
      1d:
        display:
          name: 昨日同比
        summary: avg
        precondition: # 整体延迟平均超过 200ms 则分析当下延迟与昨日对比
          value: 200
          operator: gt
        threshold: # 整体延迟平均超过昨日的 50% 视为异常
          value: 0.5
          operator: gt
    drill_downs:
      instances:
        display:
          name: 单实例
        summary: max # 单一 MySQL 实例延迟最大超过 1s 视为异常
        threshold:
          value: 1000
          operator: gt
  • 分组结果

    ,这个工作流步骤相对简单,主要针对 Metrics 分析函数的结果,以特定的 tag 做分组并排序,在这个例子里,我们希望利用 IDC(机房)来做分组的键,因此在以下工作流定义中就把执行上述逻辑的 JavaScript 代码引入即可。

GroupResult:
  type: Task
  end: true
  atomicOperationRef: jsrun
  resultSelector:
    mysqlLatency.@: "@.data" # 从分析延迟函数输出取得查询结果
  variables: 
    data.@: "@"
    script: | # 针对 IDC tag 分组结果
      data = data.map(x => x.data).flat().groupBy(x => x.template_values?.idc)
      
      // 排序资料并转换格式
      for (const key in data) {
          data[key] = data[key].
              sort((a, b) => a.original_value - b.original_value).
              reverse().
              map(x => ({
                  ...x.tags,
                  usage: {
                      current: x.value,
                      "1d": x.time_ranges_percentage_difference ? x.time_ranges_percentage_difference["1d"]?.value : "无数据"
                  },
                  threshold: {
                      current: x.threshold,
                      "1d": x.time_ranges_percentage_difference ? x.time_ranges_percentage_difference["1d"]?.threshold : "无数据突增"
                      "instances": x.drill_downs?.instances
                  }
              }))
      }
      data

最终经过以上三个工作流的执行,可以得到以下资料输出结果,基本上有异常的 Metrics 以及诊断结论都已经结构化的方式做好分组以及过滤,并附有诊断结论,可以作为聊天机器人讯息的输入使用。

{
    "mysqlLatency": {
        "xx": [
            {
                "cluster": "xxxx",
                "idc": "xx",
                "threshold": {
                    "1d": "平均延迟昨日同比大于:50%",
                    "current": "当前平均延迟大于:1s",
                    "instances": [
                        {
                            "name": "mysql.latency.pct99{cluster=xxxx,dc=xx,host=xxx-xxx-xxx-001}",
                            "original_value": 20600.546,
                            "tags": {
                                "cluster": "xxxx",
                                "idc": "xx",
                                "host": "xxx-xxx-xxx-001"
                            },
                            "threshold": "单实例最大延迟大于:1s",
                            "value": "单实例最大延迟:20.6s"
                        }
                    ]
                },
                "usage": {
                    "1d": "平均延迟昨日同比:62%",
                    "current": "当前平均延迟:501.49ms"
                }
            }
        ]
    }
}

而针对应用容器相关的指标诊断,如:CPU、Memory,或是应用本身的 Metrics 指标都是遵照类似的逻辑来编排工作流,只要替换查询的 Metrics 以及诊断的阈值即可。

收益

有了以上的自动化分析工具,在视频架构团队的日常应急响应流程中得到了很大的收益,在一次应急事件中,某一个 IDC 的网路发生故障,如下图:某一个 IP 的错误以及延迟都特别高,在应急响应处理群中自动触发的诊断都能直接把这类异常直接发现出来,就能马上针对异常的实例进行处置操作。

图 12:自动化工具在事故群组中展示异常指标的汇总讯息

本自动化流程完整导入后统计 MTTR 缩短成效如下图,从2022年10月初开始导入到目前2023年1月底,每双周统计一次 MTTR:从初期的 70 分钟,到目前 17 分钟,总体下降约 75.7%。

总结

在面对如此大量的微服务以及种类繁多的基础设施依赖环境下要能在应急事件发生时快速做决策以及执行应急操作,除了要有相对完整的监控之外,并且平时需要收集应急响应处理记录,才能统计出高频率发生的事件并归纳出一个自动化的排查流程来缩短 MTTR。

有关短视频媒体处理系统应急响应自动化实践的更多相关文章

  1. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

    很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

  2. ruby-on-rails - 每次我尝试部署时,我都会得到 - (gcloud.preview.app.deploy) 错误响应 : [4] DEADLINE_EXCEEDED - 2

    我是Google云的新手,我正在尝试对其进行首次部署。我的第一个部署是RubyonRails项目。我基本上是在关注thisguideinthegoogleclouddocumentation.唯一的区别是我使用的是我自己的项目,而不是他们提供的“helloworld”项目。这是我的app.yaml文件runtime:customvm:trueentrypoint:bundleexecrackup-p8080-Eproductionconfig.ruresources:cpu:0.5memory_gb:1.3disk_size_gb:10当我转到我的项目目录并运行gcloudprevie

  3. 叮咚买菜基于 Apache Doris 统一 OLAP 引擎的应用实践 - 2

    导读:随着叮咚买菜业务的发展,不同的业务场景对数据分析提出了不同的需求,他们希望引入一款实时OLAP数据库,构建一个灵活的多维实时查询和分析的平台,统一数据的接入和查询方案,解决各业务线对数据高效实时查询和精细化运营的需求。经过调研选型,最终引入ApacheDoris作为最终的OLAP分析引擎,Doris作为核心的OLAP引擎支持复杂地分析操作、提供多维的数据视图,在叮咚买菜数十个业务场景中广泛应用。作者|叮咚买菜资深数据工程师韩青叮咚买菜创立于2017年5月,是一家专注美好食物的创业公司。叮咚买菜专注吃的事业,为满足更多人“想吃什么”而努力,通过美好食材的供应、美好滋味的开发以及美食品牌的孵

  4. 怎样用一台手机做自媒体? - 2

    其实做自媒体的成本并不高,入门只需要一部手机即可!在手机上找视频素材、使用手机剪辑视频、最后使用手机发布视频作品获得收益!方法并不难,今天这期内容就来给粉丝们分享一种小方法,每天稳定收益100-300,抓紧点赞收藏!1、找素材(1)使用手机拍摄自己喜欢的经典段落,使用程序把文案内容提取出来(2)也可以在豆瓣、知乎、微博等网站中找一些自己需要的文案素材(3)把文案进行润色修改,可以加入一些自己的观点(4)视频素材可以使用软件中自带的素材,也可以在素材网站中下载完整版的素材2、文案配音(1)把复制好的文案直接导入小程序中(2)调整音色、音调后一键合成音频即可(3)可以选择自己朗读配音,需要花一点时

  5. 电脑0x0000001A蓝屏错误怎么U盘重装系统教学 - 2

      电脑0x0000001A蓝屏错误怎么U盘重装系统教学分享。有用户电脑开机之后遇到了系统蓝屏的情况。系统蓝屏问题很多时候都是系统bug,只有通过重装系统来进行解决。那么蓝屏问题如何通过U盘重装新系统来解决呢?来看看以下的详细操作方法教学吧。  准备工作:  1、U盘一个(尽量使用8G以上的U盘)。  2、一台正常联网可使用的电脑。  3、ghost或ISO系统镜像文件(Win10系统下载_Win10专业版_windows10正式版下载-系统之家)。  4、在本页面下载U盘启动盘制作工具:系统之家U盘启动工具。  U盘启动盘制作步骤:  注意:制作期间,U盘会被格式化,因此U盘中的重要文件请注

  6. 【鸿蒙应用开发系列】- 获取系统设备信息以及版本API兼容调用方式 - 2

    在应用开发中,有时候我们需要获取系统的设备信息,用于数据上报和行为分析。那在鸿蒙系统中,我们应该怎么去获取设备的系统信息呢,比如说获取手机的系统版本号、手机的制造商、手机型号等数据。1、获取方式这里分为两种情况,一种是设备信息的获取,一种是系统信息的获取。1.1、获取设备信息获取设备信息,鸿蒙的SDK包为我们提供了DeviceInfo类,通过该类的一些静态方法,可以获取设备信息,DeviceInfo类的包路径为:ohos.system.DeviceInfo.具体的方法如下:ModifierandTypeMethodDescriptionstatic StringgetAbiList​()Obt

  7. kvm虚拟机安装centos7基于ubuntu20.04系统 - 2

    需求:要创建虚拟机,就需要给他提供一个虚拟的磁盘,我们就在/opt目录下创建一个10G大小的raw格式的虚拟磁盘CentOS-7-x86_64.raw命令格式:qemu-imgcreate-f磁盘格式磁盘名称磁盘大小qemu-imgcreate-f磁盘格式-o?1.创建磁盘qemu-imgcreate-fraw/opt/CentOS-7-x86_64.raw10G执行效果#ls/opt/CentOS-7-x86_64.raw2.安装虚拟机使用virt-install命令,基于我们提供的系统镜像和虚拟磁盘来创建一个虚拟机,另外在创建虚拟机之前,提前打开vnc客户端,在创建虚拟机的时候,通过vnc

  8. ruby-on-rails - 在 Ruby on Rails 中发送响应之前如何等待多个异步操作完成? - 2

    在我做的一些网络开发中,我有多个操作开始,比如对外部API的GET请求,我希望它们同时开始,因为一个不依赖另一个的结果。我希望事情能够在后台运行。我找到了concurrent-rubylibrary这似乎运作良好。通过将其混合到您创建的类中,该类的方法具有在后台线程上运行的异步版本。这导致我编写如下代码,其中FirstAsyncWorker和SecondAsyncWorker是我编写的类,我在其中混合了Concurrent::Async模块,并编写了一个名为“work”的方法来发送HTTP请求:defindexop1_result=FirstAsyncWorker.new.async.

  9. ruby-on-rails - Rails 中同一个类的多个关联的最佳实践? - 2

    我认为我的问题最好用一个例子来描述。假设我有一个名为“Thing”的简单模型,它有一些简单数据类型的属性。像...Thing-foo:string-goo:string-bar:int这并不难。数据库表将包含具有这三个属性的三列,我可以使用@thing.foo或@thing.bar之类的东西访问它们。但我要解决的问题是当“foo”或“goo”不再包含在简单数据类型中时会发生什么?假设foo和goo代表相同类型的对象。也就是说,它们都是“Whazit”的实例,只是数据不同。所以现在事情可能看起来像这样......Thing-bar:int但是现在有一个新的模型叫做“Whazit”,看起来

  10. ruby-on-rails - 向 Rails 3 添加 Ruby 扩展方法的最佳实践? - 2

    我有一个要在我的Rails3项目中使用的数组扩展方法。它应该住在哪里?我有一个应用程序/类,我最初把它放在(array_extensions.rb)中,在我的config/application.rb中我加载路径:config.autoload_paths+=%W(#{Rails.root}/应用程序/类)。但是,当我转到railsconsole时,未加载扩展。是否有一个预定义的位置可以放置我的Rails3扩展方法?或者,一种预先定义的方式来添加它们?我知道Rails有自己的数组扩展方法。我应该将我的添加到active_support/core_ext/array/conversion

随机推荐