GitLab 工具
我们开发了一些工具来帮助简化开发工作:
MergeHash
参考 merge_hash.rb:
-
深度合并一个包含哈希、数组或其他对象的元素数组:
Gitlab::Utils::MergeHash.merge( [{ hello: ["world"] }, { hello: "Everyone" }, { hello: { greetings: ['Bonjour', 'Hello', 'Hallo', 'Dzien dobry'] } }, "Goodbye", "Hallo"] )结果为:
[ { hello: [ "world", "Everyone", { greetings: ['Bonjour', 'Hello', 'Hallo', 'Dzien dobry'] } ] }, "Goodbye" ] -
将哈希中的所有键和值提取到数组中:
Gitlab::Utils::MergeHash.crush( { hello: "world", this: { crushes: ["an entire", "hash"] } } )结果为:
[:hello, "world", :this, :crushes, "an entire", "hash"]
Override
参考 override.rb:
-
这个工具可以帮助你检查一个方法是否会覆盖另一个方法。这与 Java 的
@Override注解或 Scala 的override关键字概念相同。但是,我们只在ENV['STATIC_VERIFICATION']设置时运行此检查,以避免生产环境的运行时开销。这对于检查以下情况很有用:-
如果你在覆盖方法中有拼写错误。
-
如果你重命名了被覆盖的方法,这会使原始的覆盖方法变得无关紧要。
这是一个简单的示例:
class Base def execute end end class Derived < Base extend ::Gitlab::Utils::Override override :execute # 在这里进行覆盖检查 def execute end end这也适用于模块:
module Extension extend ::Gitlab::Utils::Override override :execute # 模块不会立即检查这个 def execute end end class Derived < Base prepend Extension # 在这里进行覆盖检查,而不是在模块中 end请注意,只有当以下情况之一发生时才会进行检查:
- 覆盖方法在类中定义,或:
- 覆盖方法在模块中定义,并且它被前置(prepend)到类或模块中。
因为只有类或前置的模块才能真正覆盖方法。包含或扩展模块到另一个模块中无法覆盖任何内容。
-
与 ActiveSupport::Concern、prepend 和 class_methods 的交互
当你使用包含类方法的 ActiveSupport::Concern 时,你不会得到预期的结果,因为 ActiveSupport::Concern 不像普通的 Ruby 模块那样工作。
由于我们已经有了 Prependable 作为 ActiveSupport::Concern 的补丁来启用 prepend,这会影响它与 override 和 class_methods 的交互方式。作为一种变通方法,将 ClassMethods 扩展到定义的 Prependable 模块中。
这允许我们在上述上下文中使用 override 来验证 class_methods。这个变通方法只在我们运行验证时适用,而不是在运行应用程序本身时适用。
以下是展示此变通方法效果的示例代码块:
module Base
extend ActiveSupport::Concern
class_methods do
def f
end
end
end
module Derived
include Base
end
# 没有变通方法
Base.f # => NoMethodError
Derived.f # => nil
# 使用变通方法
Base.f # => nil
Derived.f # => nilStrongMemoize
-
即使值为
nil或false也进行缓存。我们经常使用
@value ||= compute。但是,如果compute最终可能返回nil并且你不想再次计算,这种方法效果不佳。你可以使用defined?来检查值是否已设置。编写这样的模式很繁琐,而StrongMemoize会帮助你使用这种模式。与其编写这样的模式:
class Find def result return @result if defined?(@result) @result = search end end你可以这样写:
class Find include Gitlab::Utils::StrongMemoize def result search end strong_memoize_attr :result def enabled? Feature.enabled?(:some_feature) end strong_memoize_attr :enabled? end在带参数的方法上使用
strong_memoize_attr不受支持。当与override结合使用时可能无法正常工作,并且可能会缓存错误的结果。请改用
strong_memoize_with。# 不好的做法 def expensive_method(arg) # ... end strong_memoize_attr :expensive_method # 好的做法 def expensive_method(arg) strong_memoize_with(:expensive_method, arg) do # ... end end还有
strong_memoize_with来帮助缓存带参数的方法。这应该用于参数可能值较少或在循环中参数重复出现的方法。class Find include Gitlab::Utils::StrongMemoize def result(basic: true) strong_memoize_with(:result, basic) do search(basic) end end end -
清除缓存
class Find include Gitlab::Utils::StrongMemoize end Find.new.clear_memoization(:result)
RequestCache
参考 request_cache.rb。
此模块提供了一种在 RequestStore 中缓存值的简单方法,缓存键将基于类名、方法名、可选的自定义实例级值、可选的自定义方法级值和可选的方法参数。
一个仅使用实例级自定义值的简单示例:
class UserAccess
extend Gitlab::Cache::RequestCache
request_cache_key do
[user&.id, project&.id]
end
request_cache def can_push_to_branch?(ref)
# ...
end
end这样,can_push_to_branch? 的结果将基于缓存键缓存在 RequestStore.store 中。如果 RequestStore 当前未激活,则它将存储在哈希中,并保存在实例变量中,因此缓存逻辑是相同的。
我们还可以为不同的方法设置不同的策略:
class Commit
extend Gitlab::Cache::RequestCache
def author
User.find_by_any_email(author_email)
end
request_cache(:author) { author_email }
endReactiveCaching
阅读 ReactiveCaching 的文档。
TokenAuthenticatable
阅读 TokenAuthenticatable 的文档。
CircuitBreaker
Gitlab::CircuitBreaker 可以包装在任何需要使用断路器保护运行代码的类中。它提供了一个 run_with_circuit 方法,该方法用断路器功能包装代码块,这有助于防止级联故障并提高系统弹性。有关断路器模式的更多信息,请参阅:
使用 CircuitBreaker
要使用 CircuitBreaker 包装器:
class MyService
def call_external_service
Gitlab::CircuitBreaker.run_with_circuit('ServiceName') do
# 与外部服务交互的代码放在这里
raise Gitlab::CircuitBreaker::InternalServerError # 如果有问题
end
end
endcall_external_service 方法是一个与外部服务交互的示例方法。
通过用 run_with_circuit 包装与外部服务交互的代码,该方法在断路器内执行。
该方法应该抛出 InternalServerError 错误,如果在代码块执行期间抛出,该错误将被计入错误阈值。
断路器跟踪错误数量和请求速率,
并在达到配置的错误阈值或容量阈值时打开断路器。
如果断路器打开,后续请求将快速失败而不执行代码块,断路器会定期允许少量请求通过,以测试服务的可用性,然后再关闭断路器。
配置
你需要为每个用作缓存键的唯一断路器指定一个服务名称。这应该是一个标识断路器的 CamelCase 字符串。
断路器有默认值,可以按断路器覆盖,例如:
Gitlab::CircuitBreaker.run_with_circuit('ServiceName', options = { volume_threshold: 5 }) do
...
end默认值为:
exceptions:[Gitlab::CircuitBreaker::InternalServerError]error_threshold:50volume_threshold:10sleep_window:90time_window:60