Agentes Distribuidos usando DSS/VPL

En este post, exploraremos algunas ideas para implementar agentes distribuidos, aprovechando las capacidades que nos brindan Decentrilized Software Services (DSS) y el Visual Programming Language (VPL), includos en el Microsoft Robotics Developer Studio (estoy trabajando con la versión CTP 2.0, con VS 2008). Pueden bajarse el código desde mi Skydrive:

AjDssAgents-0.1.zip

En un anterior post:

Web Crawler example using DSS (Decentralized Software Services)
Ejemplo de Web Crawler usando DSS (Decentralized Software Services)

escribí algunos componentes DSS orquestados desde VPL, para implementar un web crawler. En ese ejemplo, hay un Dispatcher, un Resolver, un Downloader, y un Harvester. Pueden leer ahí el detalle de sus funciones.

Pero supongamos que ahora tenemos varias máquinas para poder ejecutar el proceso de web crawling. Queremos instalar y ejecutar varios Downloaders y Harvesters, en una grilla de máquinas, usando load balancing automático. El problema con la orquestación desde VPL es que no soporta conceptos como load balancing, por lo menos no directamente. Entonces, escribí este ejemplo donde los componentes se comunican entre sí, como agentes, usando mensajes especiales.

Un agente, en este ejemplo, es un DSS service component, capaz de recibir y procesar mensajes que les envían los otros agentes. Puede enviar mensajes a otros componentes. En vez de indicar a cuál agente va dirigido un mensaje, se especifica el tipo lógico de agente al que va destinado, por ejemplo “WebCrawler/Harvester”.

Otro componente especializado, el AgentHost, se encargar de recibir esos mensajes a enviar, y los destina a un agente local o remoto, que corresponda al tipo lógico especificado.

La solución

La solución .NET tiene tres proyectos:

AjDssAgents contiene el contrato genérico de un agente, y sus tipos, y la implementación concreta del AgentHost.

DecrementAgent and WebCrawler son simples agentes a usar en el ejemplo. El web crawler implementado es similar al de mi anterior post, mencionado arriba.

El mensaje

Los agentes intercambian mensajes, objetos del tipo AgentMessage:


[DataContract]
public class AgentMessage { [DataMember] public string From { get; set; } [DataMember] public string To { get; set; } [DataMember] public string Action { get; set; } [DataMember] public object Payload { get; set; } }

El campo From indica el origen del mensaje (no estoy usando ese campo todavía) El campo To es la dirección física (dirección DSSP) del agente destino, o su tipo lógico. En el ejemplo de este post, solamente estoy usando los tipos lógicos. ¿Por qué usar un tipo lógico? Así, si un mensaje tiene como To el valor “WebCrawler/Dispatcher”, será enviado a un agente que corresponda a ese tipo lógico.

¿Cómo un agente conoce cuáles otros agentes están siendo ejecutados, y cuáles son sus tipos lógicos? Pues bien, no lo sabe. El componente que mantiene esa información es el AgenHost local, único en cada DssHost activo. Cada agente envía sus mensajes salientes a su AgentHost local, y éste los reenvía a los agentes locales o remotos apropiados.

Los agentes

Cada agente es un DSS service component, con una dirección asignada cuando es creado. Durante el comienzo de su ejecucuón, el agente envía a su AgentHost local un mensaje DSS, indicando su dirección y su tipo lógico (p.ej. WebCrawler/Dispatcher). Esta es la forma por la que el AgentHost conoce los agentes que estan ejecutándose localmente, en su DssHost. Veamos el código de inicio de un agente Dispatcher del ejemplo WebCrawler:

 

protected override void Start() { base.Start(); // Add service specific initialization here. _state.AgentType = "WebCrawler/Dispatcher"; host.NewNode newNode = new host.NewNode(new host.AgentInfo() { Address = this.ServiceInfo.Service, AgentType = _state.AgentType }); _hostPort.Post(newNode); }

El tipo del agente es mantenido en su estado.

Este es un código típico, de un agente, en este caso un Dispatcher, mostrando el tratamiento de un mensaje entrante y la producción de mensajes salientes:

 

[ServiceHandler(ServiceHandlerBehavior.Concurrent)]
public IEnumerator<ITask> PostMessageHandler(generic.PostMessage postMessage) { if (postMessage.Body.Action.Equals("Dispatch")) Dispatch(postMessage.Body); else if (postMessage.Body.Action.Equals("Resolve")) Resolve(postMessage.Body); postMessage.ResponsePort.Post(DefaultSubmitResponseType.Instance); yield break; } private void Dispatch(AgentMessage msg) { LogInfo("Entering Dispatcher with Action: " + msg.Action); LogInfo("URL: " + msg.Payload); DownloadTarget target = new DownloadTarget(); target.Uri = (string) msg.Payload; target.Depth = 1; AgentMessage postmsg = new AgentMessage() { Action = "Resolve", To = _state.AgentType, Payload = target }; host.PostMessage post = new host.PostMessage(postmsg); _hostPort.Post(post); } private void Resolve(AgentMessage msg) { LogInfo("Entering Dispatcher with Action: " + msg.Action); DownloadTarget downloadtarget = (DownloadTarget)msg.Payload; LogInfo("URL: " + downloadtarget.Uri + ", Depth: " + downloadtarget.Depth); DownloadTarget target = ProcessUrl(downloadtarget); if (target != null) { AgentMessage agentmsg = new AgentMessage() { To = "WebCrawler/Downloader", Action="Download", Payload = downloadtarget }; host.PostMessage postmsg = new host.PostMessage(agentmsg); _hostPort.Post(postmsg); } }

El AgentHost

Hay uno y sólo uno por DssHost en ejecución. El AgentHost recibe la información de los nuevos agentes que se crean (su dirección y tipo lógico), y mantiene esa información en su propio estado.

Recibe mensajes de sus agentes locales, y los reenvía a otros agentes locales o a un AgentHost remoto. En este último caso, serializa el contenido del mensaje en un string, usando serialización XML (no podemos enviar un objeto genérico, debido a limitaciones en el Proxy que usa DSS). Esta es la estructura de un mensaje remoto:

 

[DataContract]
public class RemoteAgentMessage { [DataMember] public string From { get; set; } [DataMember] public string To { get; set; } [DataMember] public string Action { get; set; } [DataMember] public string PayloadTypeName { get; set; } [DataMember] public string Payload { get; set; } }


Notemos que el mensaje remote tiene un string Payload, que es la serialización XML del contenido original, y también tiene su tipo calificado, así el host destino podrá deserializarlo a su tipo correspondiente al objeto original.


Un AgentHost soporta subscripciones. Otros AgentHosts pueden subscribirse a recibir información de sus nuevos agentes. En general, si tenemos tres máquinas, debemos susbcribir a los tres AgentHosts entre ellos, así tendrán toda la información de los agentes que se encuentre corriendo, en las distintas máquinas.


Un ejemplo de Web Crawler con VPL


El ejemplo WebCrawlerVpl2 en VPL contiene dos diagramas, el primero:



Hay un Dispatcher, dos Downloaders y dos agentes Harvesters. El Dispatcher lanza la URL inicial a procesar, y mantiene una lista de URLs ya procesadas. El Downloader obtiene el contenido de cada página en proceso. El Harvester examina el contenido y obtiene los nuevos links a procesar.


Notemos que hay dos AgentHosts, y ellos se relacionan por notificaciones para informar sus nuevos agentes al otro.


Todos estos agentes y componentes se distribuyen en dos nodos:



El nodo Windows ejecutará en localhost:50000/50001, y el nodo Windows0 usar localhost:50002/50003 como dirección. Podemos modificar estos parámetros, agregar más agentes y nodos, sin cambiar el código de la aplicación.


Para ejecutar la distribución en forma distribuida, debemos compilarlar usando Build -> Compile as a Service en el menú de VPL. Deberá cambiar las propiedades de VPL, ahora en el ejemplo están apuntando a directorios locales en mi máquina de desarrollo:



Al compilar, VPL mostrará su avance:



Luego de la compilación, ir a la consola de DOS de MRDS, cambiar al directorio bin y lanzar el comando rundeployer.cmd:



Yo ejecuto el deployer en mi máquina local. Uds. pueden ejecutar el ejemplo en otras máquinas remotas, iniciando el deployer en cada una de ellas.


Ahora, estamos listos para ejecutar el web crawler. Seleccionamos la opción Run -> Run on distributed nodes , y la aplicación comenzará a ejecutar. Un ventana de diálogo nos pedirár la URL inicial. La ingresamos, y el proceso comienza a recuperar las páginas de ese sitio. Pueden ver el estado del primer AgentHost (ejemplo) en:


http://localhost:50000/agenthost



Hay tres agentes locales y dos agentes remotos.


En el otro DssHost, hay otro AgentHost:


http://localhost:50002/agenthost0



Vemos la diferencia: aquí hay dos nodos locales y tres remotos.


Para ver el avance del proceso, pedir en el navegador


http://localhost:50000/console/output



Conclusiones


Con estas ideas, podemos implementar aplicaciones tipo grilla, ejecutándose en varios nodos físicos. Perdemos la orquestación de VPL, no podemos dibujar el camino de los mensajes. Pero ganamos en balanceo de carga y deploying dinámico. Con algo de esfuerzo adicional, podemos escribir un servicio que inicia e instale el sistema en una nueva máquina remota, en el medio de un proceso en ejecución. La serialización de objetos arbitrarios es posible, ahora está usando serialización XML, podemos cambiarla por serialización “custom” o binaria.


Podría agregar subscripción a mensajes, en una futura versión. Un agente podría recibir mensajes que no estaban destinados a él, especificando algún criterio de subscripción. Esos criterios se mantendrían en los AgentHosts. Cuando un AgentHost rutea un mensaje saliente, podría reenviar una copia del mismo a cualquier agente interesando, ya sea local o remoto.


Tienen la versión original de este post en “Anglish”:


http://ajlopez.wordpress.com/2008/06/15/distributed-agents-using-dssvpl


Nos leemos!


Angel “Java” Lopez
http://www.ajlopez.com/en

This entry was posted in 1389, 6149, 7337, 7747. Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>