UP | HOME

8 Fault-tolerance basics 容错基础

Table of Contents

容错的目的是承认失败的存在,最小化它们的影响,并最终在没有人为干预的情况下恢复

在一个足够复杂的系统中,许多事情可能会出错。偶尔会发生错误,您依赖的组件可能会失败,并且您可能会遇到硬件故障。

最后,如果系统是分布式的,则可能会遇到其他问题,例如远程计算机可能因崩溃或网络链接断开而不可用。

1 Runtime errors 运行时错误

一些运行时错误, 如 模式匹配错误, 无效的算术运算(例如除零),调用一个不存在的函数

处理运行时错误 try-catch

1.1 Error types 错误类型

BEAM区分三种类型的运行时错误: errors exits throws 以下是一些典型的错误示例:

iex(1)> 1/0
 ** (ArithmeticError) bad argument in arithmetic expression

iex(1)> Module.nonexistent_function()
 ** (UndefinedFunctionError) function Module.nonexistent_function/0 is
   undefined or private

iex(1)> List.first({1,2,3})
 ** (FunctionClauseError) no function clause matching in List.first/1
  • 不可用的算术表达式
  • 调用不存在的函数
  • 模式匹配错误

直接使用 raise/1 宏 抛出错误

iex(1)> raise("Something went wrong")
 ** (RuntimeError) Something went wrong

tips:

! 结尾的函数, 比哪 File.open! 表示出错会 raise error, 这是Elixir的惯例用法

iex(1)> File.open!("nonexistent_file")
 ** (File.Error) could not open non_existing_file: no such file or directory

File.open/1 有错误会返回错误信息, 不是抛出error

iex(1)> File.open("nonexistent_file")
{:error, :enoent}

在这段代码中没有运行时错误, File.open返回一个结果, 调用者可以以某种方式处理该结果

exit/1

要退出当前进程,可以调用exit/1,提供退出原因

iex(2)> spawn(fn ->
          exit("I'm done")
          IO.puts("This doesn't happen")
        end)

最后是 throw/1

Elixir程序有许多嵌套函数调用, 特别是, 实现循环的递归, 结果是没有诸如break, continue和return之类的结构 在Elixir中,你可以抛出(throw)一个值稍后处理, throw 有点让人想起goto,你应该尽可能避免这种技术。

iex(3)> throw(:thrown_value)
 ** (throw) :thrown_value

另一个例子

https://elixir-lang.org/getting-started/try-catch-and-rescue.html#throws

iex> try do
...>   Enum.each -50..50, fn(x) ->
...>     if rem(x, 13) == 0, do: throw(x)
...>   end
...>   "Got nothing"
...> catch
...>   x -> "Got #{x}"
...> end
"Got -39"

1.2 Handling errors

使用 try 进行错误处理

try do
  ...
catch error_type, error_value ->
  ...
end

执行 do block 有错误时执行 catch

注意catch中指定了两件事情 error_type error_value

error_type:error, :exit, or :throw

实验示例

iex(1)> try_helper = fn fun ->
          try do
            fun.()
            IO.puts("No error.")
          catch type, value ->
            IO.puts("Error\n  #{inspect(type)}\n  #{inspect(value)}")
          end
        end

接受函数参数 在 try 中调用函数

iex(2)> try_helper.(fn -> raise("Something went wrong") end)
Error
  :error
  %RuntimeError{message: "Something went wrong"}

返回类型是 :error 错误是 RuntimeError 其中 message 是返回的错误信息

返回原始的错误信息使用 :erlang.error/1

试下 throw

iex(3)> try_helper.(fn -> throw("Thrown value") end)
Error
  :throw
  "Thrown value"

exit/1

iex(4)> try_helper.(fn -> exit("I'm done") end)
Error
  :exit
  "I'm done"

在elixir中 表达式都有返回值, try 返回值是最后执行的语句

没有错误返回 do block 执行结果, 否则返回 catch 执行结果

iex(5)> result =
          try do
            throw("Thrown value")
          catch type, value -> {type, value}
          end

iex(6)> result
{:throw, "Thrown value"}

catch 是一个模式匹配, 可以指定多个子句

try do
  ...
catch
  type_pattern_1, error_value_1 ->
    ...

  type_pattern_2, error_value_2 ->
    ...

  ...
end

使用 after

iex(7)> try do
          raise("Something went wrong")
        catch
          _,_ -> IO.puts("Error caught"); 2
        after
          IO.puts("Cleanup code"); 1
        end

Error caught
Cleanup code

after block 总会被执行, 需要注意的是, after 并不会影响 try 表达式的返回结果

2 Errors in concurrent systems

Author: lidashuang

Created: 2018-05-18 Fri 23:52

Emacs 25.3.3 (Org mode 8.2.10)