Arquitectura de Servidores
•
•
•
•
Servidores Concurrentes
Servidores Iterativos
Servidores con Estado
Servidores sin Estado
Qué pasa cuando varios clientes tratan
de conectarse al mismo tiempo a un
servidor
• Una forma es ir atendiéndolos de a uno en un ciclo: como en el
programa que atiende pedidos de archivos
– Se acepta una conexión
– Se lee la petición
– Se lee desde el archivo y se escribe en el socket hasta encontrar
una marca de fin de archivo
• A este tipo de servidores se les llama servidores iterativos
• El problema es que todo cliente tiene que esperar su turno para
ser atendido
• Si uno de ellos pide un archivo muy grande los demás tienen
que esperar
• La mayor parte de la espera es debido a operaciones de IO, hay
capacidad de CPU ociosa !
Un servidor secuencial (iterativo)
atendiendo a más de un cliente
A CLIENT
A SERVER
A CLIENT
4444
A CLIENT
Durante la conversación no puede
oír por el puerto 4444
A CLIENT
A SERVER
A CLIENT
4444
A CLIENT
Sólo después de efectuar la
transmisión se pone a escuchar de
nuevo por el 4444
A CLIENT
A SERVER
A CLIENT
4444
A CLIENT
Si el servicio consiste en transferir
un archivo, el cliente debe digitar
el nombre
A CLIENT
A SERVER
A CLIENT
4444
A CLIENT
¿Qué sucede si el servidor tiene que
esperar mucho para que un cliente
escriba el nombre de un archivo?
A CLIENT
A SERVER
Timeout
4444
A CLIENT
A CLIENT
ArchServidor2
Un Servidor Concurrente
• Un servidor concurrente atiende a varios clientes al
mismo tiempo.
• Más aún, mientras está atendiendo sigue escuchando
• El problema es que todo cliente tiene que esperar su
turno para ser atendido.
• Si uno de ellos pide un archivo muy grande los
demás tienen que esperar
• La mayor parte de la espera es debido a operaciones
de IO, hay capacidad de CPU ociosa!
• Se trata de crear un nuevo proceso o línea de
ejecución cada vez que un cliente “llega” a pedir un
servicio.
Servidores Comcurrentes: hay procesos
separados para atender el puerto y para
transferir el archivo
A CLIENT
A SERVER
4444
A CLIENT
A CLIENT
Después que el cliente contacta al servidor,
éste crea otro proceso para para atender al
cliente y se queda escuchando el puerto 4444
por otro
A CLIENT
A SERVER
4444
A CLIENT
A CLIENT
Mientras el nuevo proceso está atendiendo
al primer cliente, el segundo cliente puede
contactar al servidor en el puerto 4444
A CLIENT
A SERVER
4444
A CLIENT
A CLIENT
Y el servidor crea otro proceso
A CLIENT
A SERVER
4444
A CLIENT
A CLIENT
Ahora un tercer cliente contacta al
servidor
A CLIENT
A SERVER
4444
A CLIENT
A CLIENT
Y un tercer proceso esclavo o
thread es creado
A CLIENT
A SERVER
4444
A CLIENT
A CLIENT
Algoritmo de Servidor Concurrente
Programa principal o “master” del servidor
1. Crear un Socket de servidor
En un ciclo infinito:
2. Aceptar requerimientos de clientes
3. Cuando llega una petición de un cliente crear un nuevo
proceso “esclavo” que atienda paralelamente la petición
(esto no debe bloquear la ejecución del programa master del
servidor)
4. Volver a 2.
Proceso esclavo:
1. Recibir los parámetros de la comunicación (socket o flujos
de entrada y/o salida)
2. Atender al cliente (ej: leer el nombre del archivo, transmitir
el archivo)
3. Retornar (desaparecer !)
Cómo (y por qué) crear procesos
paralelos
• Si existe sólo una CPU, ¿Por qué crear procesos paralelos?
– Porque algunos programas se escriben más fácilmente así. De hecho, la
programación de un servidor es a veces más fácil si se hace de esta
manera.
– Porque sí hay más de un procesador !!!!! (¿dónde?)
• El concepto de procesos paralelos implentados a nivel de S.O.
aparecen con UNIX y C.
• La forma de crearlos es ejecutando una función llamada fork()
• int i = fork() provoca que se cree un proceso exactamente igual al
que se está ejecutando.
• La única diferencia es que en el proceso hijo (el nuevo creado) la
variable i vale cero. Esto se usa para saber quién soy yo.
• En programación de servidores concurrentes, si soy el hijo ejecuto la
parte que corresponde al proceso esclavo.
• Si soy el padre (i tiene un valor distinto de cero y es el id del
proceso hijo creado) sigo recibiendo peticiones
Ejemplo de procesos paralelos en C
(muy simplificado)
main() {
int pid, msock, ssock;
sock = passivesock(port, “tcp”, qlen);
/* ver capítulo 10.4 del libro Internetworking with tcp/ip de Douglas
Commer para ver cómo se implementa */
while(1) {
ssock = accept(msock, &fsin, &alen);
pid = fork();
if (pid == 0) {
atender al cliente;
retornar;
}
}
Problemas con el fork() en UNIX
• La creación del proceso paralelo es costosa en tiempo.
– En algunos textos se sugiere que se podrían crear los procesos
paralelos al levantar el servidor. Cuando llegue un cliente
simplemente se le pasan los datos por medio de un pipe que se
crea entre el proceso padre y el proceso hijo
• El proceso paralelo duplica exactamente todo el ambiente en el
cual estaba corriendo el proceso original, incluso aquellas
variables que no necesita !!!
• No es fácil manejar procesos paralelos, ya que si no se
terminan en forma “normal” pueden quedar consumiendo
recursos indefinidamente.
• La única información que tiene el padre para controlarlos es su
identificación al crearlos.
• Muchas veces se prefiere usar el método select, que lo que
hace es preguntar de una serie de puntos de lectura de datos (en
este caso sockets) cuál está listo para ser leído: este puede ser
uno de los sockets de comunicación con cliente (en un arreglo)
o el socket por donde se escuchan las peticiones (recordar que
el IO es lo más lento en todo esto)
En JAVA se prefiere usar Threads
• Un thread es una secuencia o flujo de de instrucciones que
se ejecutan dentro de un programa. Tiene un comienzo y un
fin. Entonces qué diferencia tiene con un proceso?
• El thread sólo puede ser creado dentro de un proceso. Y un
proceso (programa) puede crear varios threads dentro de él
que se ejecutan en paralelo.
• Entonces, qué diferencia tiene con el fork(). El programa
principal está “conciente” de los threads que existen, hay
variables que los identifican. Pueden ser creados,
inicializados, sustendidos, reactivados o parados por el el
programa que los creó.
• El programa principal puede darles parámetros distintos a
cada thread. Los thread se pueden programar con la
canatidad de variables necesarias para su ejecución (no lo
heredan TODO).
Servidores Stateless vs. Stateful: el
problema de lectura de un archivo remoto.
requerimiento abrir archivo XYZ
A CLIENT
A SERVER
?
Respuesta archivo XYZ existe y está listo
Open file XYZ
read first 50 bytes
while (not end of file XYZ)
read next 50 bytes
close file
Un servidor stateless (sin estado)
implica que no se acuerda de las
peticiones anteriores
requerimiento leer bytes 0 a 49 de XYZ
A CLIENT
A SERVER
?
Respuesta: el contenido en bytes
Open file XYZ
read first 50 bytes
while (not end of file XYZ)
read next 50 bytes
close file
El cliente debe proporcionar
toda la información de nuevo !
reuquerimiento leer bytes 50 a 99 de XYZ
A CLIENT
A SERVER
?
Respuesta: el contenido en bytes
Open file XYZ
read first 50 bytes
while (not end of file XYZ)
read next 50 bytes
close file
This may cause a lot of network
traffic, especially if there are many
clients
requerimiento leer bytes X a X+50 de XYZ
A CLIENT
A SERVER
?
Respuesta: el contenido en bytes
Open file XYZ
read first 50 bytes
while (not end of file XYZ)
read next 50 bytes
close file
Stateful Server: mantiene alguna
información de lo que ha pasado
Pointer archi
Posición
0
XYZ
0
1
ZXY
50
Open file XYZ
read first 50 bytes
while (not end of file XYZ)
read next 50 bytes
close file
Req. abrir XYZ
A CLIENT
A SERVER
?
respuesta: file pointer a XYZ
La información que tiene que
transmitir el cliente es mucho menos
Pointer
Archivo
Posición
0
XYZ
50
1
ZXY
50
Open file XYZ
read first 50 bytes
while (not end of file XYZ)
read next 50 bytes
close file
Req. 0, leer 50
A CLIENT
A SERVER
?
respuesta: el contenido
La información en la tabla debe ser
actualizada
Pointer Archivo
Posición
0
XYZ
100
1
ZXY
50
Open file XYZ
read first 50 bytes
while (not end of file XYZ)
read next 50 bytes
close file
Req. 0, leer 50
A CLIENT
A SERVER
?
respuesta: el contenido
Es importante cerrar el archivo
Pointer Archivo
Posición
0
XYZ
100
1
ZXY
50
Open file XYZ
read first 50 bytes
while (not end of file XYZ)
read next 50 bytes
close file
Req. 0, leer 50
A CLIENT
A SERVER
?
respuesta: el contenido
Posibilidad de Errores
• La red manda dos veces el datagrama con requerimiento de
lectura
• Si el computador del cleinte se cae y rebootea el programa.
• Si el computador se cae antes de poder “des-registrarse”
• Si otro cliente se conecta en el mismo port que el que se cayó sin
avisar
En una internet real, donde las máquinas pueden caerse y
rebootear y los mensajespueden perderse, llegar atrasados,
duplicados o en orden incorrecto un servidor con manteción de
estado puede resultar difícil de programar para hacerlo
tolerante a los errores.
Arquitectura de un Servidor de
Archivos
Aplicación
Módulo
Cliente
Servicio de Directorio
Servicio plano de
archivo (flat)
Componentes
• Flat File Service: Implementa las operaciones
directamente sobre los archivos. Trabaja con Unique File
Identifieres (UFID). Se genera uno nuevo por cada archivo
• Directory Services: Cliente de el FFS, provee un
mapeo entre los UFID y los nombre textuales de los
archivos y las funciones necesarias para administrar
directorios y obtener las UFID. Los directorios se guardan
como archivos planos.
• Módulo Cliente: Corre en cada computador, integra y
extiende las operaciones del FFS y
Componentes
• Flat File Service: Implementa las operaciones
directamente sobre los archivos. Trabaja con Unique File
Identifieres (UFID). Se genera uno nuevo por cada archivo
• Directory Services: Cliente de el FFS, provee un
mapeo entre los UFID y los nombre textuales de los
archivos y las funciones necesarias para administrar
directorios y obtener las UFID. Los directorios se guardan
como archivos planos.
• Módulo Cliente: Corre en cada computador, integra y
extiende las operaciones del FFS y DS en una aplicación
interfaz usada por los programadores. Posee información
acerca de la localización de archivos en la red. Provee
eficiencia a través de un cache
Modelo de Interfaz para FFS
• read(FileId, i, n) :le hasta n bytes de un archivo a partir de la
posición i los que retorna en un arreglo
• write(FileId, i, Datos): escribe la secuencia de datos en el
archivo a partir de la posición i extendiéndolo en caso
• create() : crea un archivo nuevo de largo 0 y devuelve el UFID
para él
• delete(FileId) : borra el archivo
• getAttributes(FileId) : retorna una estructura con los atributos
• setAttributes(FileId, attr) : pone los atributos según la
estructura
Controles de acceso
• En un sistema local el chequeo se hace sólo al abrir el
archivo y los derechos se conservan
• en un sistema distribuido los chequeos se deben hacer a
nivel de servidor. Para que el servidor siga siendo stateless
se pueden usar 2 estrategias:
– El chequeo se hace cuando el nombre es convertido en UFID y el
resultado es codificado en forma de capacidad que se retorna al
cliente, el cual lo usa para futuros accesos
– La identificación del usuario se hace cada vez que se manda un
request y el chequeo se hace para cada operación
• El segundo es más usado (en NFS y AFS) por su
simplicidad, pero ninguno garantiza seguridad en el caso de
identidad suplantada
Modelo de interfaz para
Directory Service
• Lookup(Dir, File) localiza el nombre de texto en el
directorio y retorna el UFID
• AddName(Dir, Name, File) Si Name no estaba en el
directorio dado añade el par (Name,File) en el directorio
modificando el archivo pertinente
• UnName(Dir, Name) el par (Name, file) correspondiente es
borrado del directorio
• getNames(Dir) retirna la lista de nombres que contiene un
directorio
Ejemplo 1 el NFS
Aplicación
Sistema Virtual
Sistema Virtual
Sist
Local
Client
NFS
Server
NFS
Sist
Local
Características de NFS
• La comunicación es por medio de RPC y es abierta en el
servidor, que reside en el kernel
• La identificación de archivos es por medio de los llamados
file handters consistentes en:
• Filesystem identifier
• i-node number or file
• i-node gerneration number
• El “estado” se guarda en el cliente en un v-node
• Autentificación del cliente en cada llamada mandando user ID y group
ID
• Los servicios de flat file y directory están integrados
• El servicio de mount provee un link a un sistema remoto
Cache en NFS
• Cache en Unix: buffer cache, read ahead, delayed write
• Cache en Server: datos de escritura se guardan en memoria cache y
son escritas antes del reply al cleinte. En la versión 3 se guarda todo en
cache hasta que se recibe la operación commit para ese archivo (buffer
lleno o close)
• Cache en el servidor: resultados de read, write, getattr, lookup y
readdir se almacenan localmente, lo cual puede introducir
inconsistencias en versiones en los distintos clientes ya que escrituras
de un cliente no se actualizan en seguida en los otros. Los clientes son
entonces responsables de mantener actualizados sus caches por medio
de timestamps: Tc= tiempo de la última actualización, Tm= tiempo de
modificación. A un tiempo T el cache será válido si (T - Tc < t) o
(Tmcliente = Tmserver). Normalmente 3-30 seg para archivos y 30-60
para directorios
Interfaz de NFS simplificada
• read(FileId, i, n) :le hasta n bytes de un archivo a partir de la
posición i los que retorna en un arreglo
• write(FileId, i, Datos): escribe la secuencia de datos en el
archivo a partir de la posición i extendiéndolo en caso
• create() : crea un archivo nuevo de largo 0 y devuelve el UFID
para él
• delete(FileId) : borra el archivo
• getAttributes(FileId) : retorna una estructura con los atributos
• setAttributes(FileId, attr) : pone los atributos según la
estructura
Ejemplo 2: El AFS
• Apunta a lograr mejor performance en situaciones de
escalabilidad
– Whole-file serving: El contenido de todo los directorios archivos son
traspasados al cleinte
– Whole-file caching: Los archivos transmitidos son guardados en cache
local. Normalmente varios cientos de archivos ! El cache es casi
permanente.
– Cuando se hace un open del archivo se transmite el archivo entero si no
estaba
– las operaciones de escritura/lectura se hacen localmente
– Con el close, se transmite una copia modificada al servidor
– Debe
Esquema del AFS
Aplicación
Unix Kernel
Vice
Sist
Local
Venus
Unix Kernel
Consistencia del Cache
• Cada vez que se traspasa un archivo del servidor a un cliente se provee
de una promesa de callback, que garantiza que cuando otro cliente
modifique el archivo este será notificado
• El callback puede estar en dos estados: valido o cancelado
• Cuando el archivo es traspasado al cliente el callback se pone en
válido. Cuando se recibe un callback del servidor (porque el archivo
fue modificado) se pone en cancelado
• Cada vez que el cliente abre un archivo busca si está en su cache. Si
está se revisa el callback. Si está cancelado se trae una copia nueva del
archivo, si está válido, se usa el archivo del cache
• Si la estación de trabajo se reinicia (por que se apagó o se cayó) pide
para cada archivo de su cache el timestamp de la última modificación
• si la última modificación es consistente con la copia se pone el
callback en válido, si no en cancelado
Descargar

Arquitectura de Servidores