在线文档教程
Erlang 20

15.高级代理主题 | 15. Advanced Agent Topics

15 高级代理主题

高级代理主题 ”一章介绍了SNMP开发工具的更高级的代理相关功能。涵盖以下主题:

  • 何时使用子代理

  • Agent语义

  • 子代理和依赖项

  • 分布式表

  • 容错

  • 使用Mnesia表作为SNMP表

  • 审计痕迹记录

  • 偏离标准

15.1何时使用子代理

剖面何时使用子代理描述装载和卸载MIB机制不足的情况。在这种情况下,需要一个子代理。

特殊集事务机制

每个子代理可以实现它自己的机制setgetget-next。例如,如果应用程序要求该get机制是异步的,或者需要N阶段set机制,则应使用专门的子代理程序。

该工具包同时允许不同类型的子代理。因此,不同的MIB可能有不同的setget机制。

进程通信

一个简单的分布式代理可以在没有子代理的情况下进行管理。仪器功能可以使用分布式Erlang与应用程序的其他部分进行通信。但是,如果每个节点产生过多不必要的通信量,则可以在每个节点上使用子代理。子代理处理每个传入的SNMP请求,而不是每个变量.。因此,网络流量被最小化。

如果检测功能与UNIX进程通信,那么使用特殊的子代理可能是个好主意。此子代理将SNMP请求发送到一个数据包中的其他进程,以最大限度地减少上下文切换。例如,如果在UNIX中的C级别上实现了一个完整的MIB,但仍然希望使用Erlang SNMP工具,那么您可能有一个特殊的子代理,它将请求中的变量作为单个操作向下发送C。

MIB频繁装载

加载和卸载MIB是非常便宜的操作。但是,如果应用程序经常这样做,也许每分钟多次执行一次,它应该在子代理程序中一次性加载MIB​​。此子代理仅在另一个代理下注册和取消注册,而不是每次加载MIB​​。这比加载MIB​​便宜。

与其他SNMP代理工具包的交互

如果SNMP代理需要与在另一个包中构造的子代理交互,则应该使用一个特殊的子代理,该子代理通过另一个包指定的协议进行通信。

15.2 Agent语义

代理可以配置为多线程,一次处理一个传入请求,或者启用请求限制(可用于负载控制或限制DoS攻击的影响)。如果它是多线程的,读请求(getget-nextget-bulk)和陷阱在相互平行并处理set请求。但是,所有set请求将被序列化,这意味着如果代理正在等待应用程序完成复杂的写入操作,则它将不会处理任何新的写入请求,直到此操作完成。它同时处理读取请求并发送陷阱。并行处理写入请求的原因是即使在最简单的情况下也需要复杂的锁定机制。即使使用上述方案,用户也必须小心,不要违反set请求是原子的请求。如果这很难做到,请不要使用多线程功能。

请求中的顺序未定义,变量未按定义的顺序处理。不要假设PDU中的第一个变量将在第二个变量之前处理,即使代理按此顺序处理变量。甚至不能假定属于不同子代理的请求有任何顺序。

如果经理试图在相同的PDU中多次设置相同的变量,那么代理可以随意即兴创作。没有定义确定仪器是否会被调用一次或两次。如果仅调用一次,则没有定义确定将提供哪个新值。

当代理收到请求后,它会在发送响应后将请求ID保留一秒。如果代理在这段时间内使用相同的IP地址和UDP端口接收到具有相同请求ID的另一请求,则该请求将被丢弃。该机制与该功能snmpa:current_request_id/0无关。

15.3 子代理和依赖关系

该工具包支持使用不同类型的子代理,但不支持构建子代理。

而且,工具箱不支持子代理之间的依赖关系。根据定义,子代理应该是独立的,因此在它们之间创建依赖关系并不是一个好的设计。

15.4 分布式表格

更复杂的系统中的一种常见情况是表中的数据是分布式的。不同的表格行在不同的地方实施。某些SNMP工具包为表的每个部分分配一个SNMP子代理,并将相应的MIB加载到所有子代理中。主代理负责将分布表作为单个表呈现给经理。所提供的工具包使用了不同的方法。

用于使用此SNMP工具实现分布式表的方法是实现一个表协调器进程,负责协调保存表数据的进程,并将它们称为表持有者。所有餐桌持有人必须以某种方式由协调员了解; 表格数据的结构决定了如何实现这一点。协调员可能要求表格持有者明确注册并指定其信息。在其他情况下,表格持有人可以在编译时确定一次。

当调用分布表的检测函数时,请求应该被转发到表协调器。协调员在表格持有人中找到请求的信息,然后将答案返回给检测功能。SNMP工具包不支持协调表,因为它必须独立于实施。

将表协调器与SNMP工具分离的优点是:

  • 我们不需要为每个桌子持有人的分代理。通常情况下,需要子代理来处理通信,但在分布式Erlang中,我们使用的是普通的消息传递。

  • 很可能,某种类型的表协调器已经存在。此过程应负责表的检测。

  • 用于表示分布式表的方法与应用程序密切相关。使用不同的掩蔽技术只对一小部分问题有效,而在分布式表中注册每一行使其非分布式。

15.5容错

SNMP代理工具包从三个不同的源获得输入:

  • 来自网络的UDP数据包

  • 从用户定义的插装函数返回值。

  • 从MIB返回值。

该代理具有高度的容错能力。如果管理器从代理获得意外响应,则可能某些检测函数返回了一个错误的值。即使检测工具崩溃,代理也不会崩溃。应该注意的是,如果插装函数进入无限循环,代理也将永远被阻塞。主管或应用程序指定如何重新启动代理。

在分布式环境中使用SNMP代理

在分布式环境中使用代理的正常方式是使用位于一个节点的一个主代理以及位于其他节点上的零个或多个子代理。但是,此配置使主代理节点成为单点故障。如果该节点关闭,代理将无法工作。

解决此问题的一个解决方案是使SNMP应用程序成为分布式Erlang应用程序,这意味着可以将代理配置为在多个节点之一上运行。如果运行的节点出现故障,则另一个节点将重新启动代理。这叫做故障转移当节点再次启动时,它可能接管申请。这个问题的解决方案增加了另一个问题。通常,新节点的IP地址比二多个,这可能会导致SNMP管理器与代理之间的通信出现问题。

如果SNMP代理被配置为分布式Erlang应用程序,它将在接管期间尝试加载与旧节点加载的MIB相同的MIB。它使用与旧节点相同的文件名。如果MIB不在不同节点的相同路径上,则必须在接管后显式加载MIB。

15.6 使用Mnesia表作为SNMP表

MnesiaDBMS可用于存储SNMP表的数据。这意味着SNMP表可以实现为Mnesia表,并且可以通过SNMP使Mnesia表可见。这种映射基本上是自动化的。

使用此映射有三个主要原因:

  • 我们获得了Mnesia的所有特性,如容错、持久数据存储、复制等。

  • 大部分工作都是自动化的。这包括get-next处理和RowStatus处理。

  • 该表可以作为普通的Mnesia表使用,在应用程序内部使用Mnesia API,同时通过SNMP可以看到它。

使用此映射时,原始Mnesia表中的插入和删除速度较慢,因子为O(log n)。读访问不受影响。

实施SNMP表作为Mnesia表的一个缺点是内部资源被迫使用MIB中的表定义,这意味着外部数据模型必须在内部使用。其实,这只是部分正确的。Mnesia表可以扩展SNMP表,这意味着Mnesia表可能有内部使用的列,而SNMP不会看到这些列。但是,SNMP的数据模型必须保持。尽管这是不受欢迎的,但在许多情况下,这是一种务实的妥协方式,在这种情况下简单而有效的实现比抽象更可取。

创建Mnesia表

该表必须在管理人员可以使用之前在Mnesia中创建。该表必须声明为类型snmp。这使得该表按照SNMP的字典排序规则进行排序。Mnesia表的名称必须与SNMP表名相同。必须指定相应SNMP表中的INDEX字段的类型。

如果SNMP表具有多个INDEX列,则相应的Mnesia行是一个元组,其中第一个元素是具有INDEX列的元组。 通常,如果SNMP表具有N个INDEX列和C个数据列,则Mnesia表格具有arity(C-N)+1,其中如果N> 1则关键字是元数据N,或者如果N = 1则是单个项。

有关如何将Mnesia表声明为SNMP表的信息,请参阅Mnesia用户指南。

下面的例子说明了我们有一个我们希望作为Mnesia表实现的SNMP表的情况。该表存储有关公司员工的信息。每位员工都使用部门编号和名称进行索引。

empTable OBJECT-TYPE SYNTAX SEQUENCE OF EmpEntry ACCESS not-accessible STATUS mandatory DESCRIPTION "A table with information about employees." ::= { emp 1} empEntry OBJECT-TYPE SYNTAX EmpEntry ACCESS not-accessible STATUS mandatory DESCRIPTION "" INDEX { empDepNo, empName } ::= { empTable 1 } EmpEntry ::= SEQUENCE { empDepNo INTEGER, empName DisplayString, empTelNo DisplayString, empStatus RowStatus }

相应的Mnesia表指定如下:

mnesia:create_table([{name, employees}, {snmp, [{key, {integer, string}}]}, {attributes, [key, telno, row_status]}]).

在Mnesia表中,这两个键列存储为包含两个元素的元组。因此,表的重要性是3。

仪器功能

上一节所示的MIB表可以按以下方式编译:

1> snmpc:compile("EmpMIB", [{db, mnesia}]).

这就是要做的一切!现在管理员可以读取,添加和修改行。另外,您可以使用普通的Mnesia API从程序中访问表格。唯一明确的操作是创建Mnesia表,这是用户必须执行的操作,以创建所需的表模式。

添加自己的操作

在修改表时,通常需要采取一些特定的操作。这是通过一个检测功能来完成的。它在设置表时执行一些特定的代码,并将所有其他请求向下传递给预定义函数。

下面的示例说明了这一想法:

emp_table(set, RowIndex, Cols) -> notify_internal_resources(RowIndex, Cols), snmp_generic:table_func(set, RowIndex, Cols, {empTable, mnesia} emp_table(Op, RowIndex, Cols) -> snmp_generic:table_func(Op, RowIndex, Cols, {empTable, mnesia}).

默认的检测功能在模块snmp_generic中定义。 有关详细信息,请参阅参考手册的SNMP部分,模块snmp_generic。

扩展记忆表

一个表格可能包含内部使用的列,但不应该对管理员可见。这些内部列必须是表中的最后一列。该set操作不适用于这种安排,因为代理不知道哪些列。这种情况通过在set函数中添加内部列的值来处理。

为了说明这一点,假设我们empTable用一个内部列来扩展我们的Mnesia 。我们像以前一样创建它,但是通过添加另一个属性来创建4。

mnesia:create_table([{name, employees}, {snmp, [{key, {integer, string}}]}, {attributes, {key, telno, row_status, internal_col}}]).

最后一列是内部列。在执行set操作,它创建一行,我们必须给内部列赋值。现在,插装功能将如下所示:

-define(createAndGo, 4). -define(createAndWait, 5). emp_table(set, RowIndex, Cols) -> notify_internal_resources(RowIndex, Cols), NewCols = case is_row_created(empTable, Cols) of true -> Cols ++ [{4, "internal"}]; % add internal column false -> Cols % keep original cols end, snmp_generic:table_func(set, RowIndex, NewCols, {empTable, mnesia} emp_table(Op, RowIndex, Cols) -> snmp_generic:table_func(Op, RowIndex, Cols, {empTable, mnesia}). is_row_created(Name, Cols) -> case snmp_generic:get_status_col(Name, Cols) of {ok, ?createAndGo} -> true; {ok, ?createAndWait} -> true; _ -> false end.

如果创建了一行,我们总是将内部列设置为"internal"

15.7偏离标准

在某些方面,代理没有完全实现SNMP。以下是不同之处:

  • 缺省函数和snmp_generic不能像INDEX(SNMPv1 only!)那样处理NetworkAddress类型的对象。 改用IpAddress。

  • 代理不检查为INTEGER对象指定的复杂范围。在这些情况下,它只检查该值是否在指定的最小值和最大值内。例如,如果范围是指定的,1..10 | 12..20代理将让11通过,但不是0或21.仪表功能必须检查复杂的范围本身。

  • 代理将永远不会产生wrongEncoding错误。如果变量绑定是错误编码的,则asn1ParseError计数器将递增。

  • 一个tooBig在SNMPv1的数据包错误总是会使用'NULL'所有变量绑定值。

  • 默认函数和snmp_generic不检查从八进制字符串派生的文本约定中每个八位组的范围。DisplayStringDateAndTime.必须在超载时签入is_set_ok功能。