2009-11-22 31 views
5

Tenemos tres servicios web (/a, /b, /c), donde cada mapas de servicio a un método (go()) en una clase Java separado (ClassA, ClassB, ClassC).Cómo evitar la concurrencia en la API del servicio web?

Solo se debe ejecutar un servicio al mismo tiempo (es decir: /b no se puede ejecutar mientras se está ejecutando /a). Sin embargo, como se trata de una API REST, no hay nada que impida que los clientes soliciten que los servicios se ejecuten simultáneamente.

¿Cuál es la mejor y más sencillo método en el servidor para hacer cumplir que los servicios No funcionan concurrentemente?


actualización: Se trata de una aplicación interna, no vamos a tener una carga grande y sólo tendremos un único servidor de aplicaciones.

Actualización: Esta es una pregunta subjetiva ya que puede hacer diferentes argumentos sobre el diseño general de la aplicación que afecta a la respuesta final. Acepté la respuesta de overthink porque la encontré más interesante y útil.

+2

@Marcus: bien puede ser una aplicación interna, pero el diseño de algo con limitaciones incorporadas en esta etapa es una mala idea. Puede * pensar * que nunca necesitará escalar, pero ¿puede estar seguro? Ahórrese el dolor de cabeza más adelante y siga las mejores prácticas. – jkp

+0

@jkp, punto tomado. –

Respuesta

6

Suponiendo que no está bien forzar al servidor web a tener solo un hilo de escucha sirviendo solicitudes ... Supongo que usaría un candado estático (ReentrantLock probablemente, para mayor claridad, aunque podría sincronizar en cualquier objeto compartido , realmente):

public class Global { 
    public static final Lock webLock = new ReentrantLock(); 
} 

public class ClassA { 
    public void go() { 
     Global.webLock.lock() 
     try { 
      // do A stuff 
     } finally { 
      Global.webLock.unlock() 
     } 
    } 
} 

public class ClassB { 
    public void go() { 
     Global.webLock.lock() 
     try { 
      // do B stuff 
     } finally { 
      Global.webLock.unlock() 
     } 
    } 
} 

public class ClassC { 
    public void go() { 
     Global.webLock.lock() 
     try { 
      // do C stuff 
     } finally { 
      Global.webLock.unlock() 
     } 
    } 
} 
+0

@overthink: ¿Qué sucede cuando quiere más niveles de servicio? Esta solución no se escalará si los bloqueos se implementan en esos niveles. – jkp

+1

@overthink: ¿Entonces está diciendo que también podría usar bloques 'synchronized (sharedObject) {...}' en lugar de las sentencias 'Global.webLock.lock()/unlock()'? –

+1

@Marcus: Sí, podría usar sincronizado en lugar de bloquear/desbloquear. p.ej. en mi ejemplo anterior, podrías hacer que 'webLock' sea un simple' Object' y usar 'synchronized (webLock) {...}' en su lugar. – overthink

3

En primer lugar, sin conocer su arquitectura, probablemente se encontrará con problemas si tiene que imponer restricciones de concurrencia en los niveles de WebService. Si bien puede usar bloqueos tradicionales, etc. para serializar las solicitudes en ambos servicios, ¿qué sucede cuando agrega un segundo nivel web para escalar su solución? Si los bloqueos son locales a la capa web, serán inútiles.

Supongo que probablemente exista una capa de algún tipo debajo de los servicios web y es aquí donde debe aplicar estas restricciones. Si el cliente B entra después de que el cliente A ha hecho una solicitud conflictiva, el backend debería rechazar la solicitud cuando descubra que el estado ha cambiado y luego debe devolver un 409 al segundo cliente. Al final, las condiciones de carrera siguen siendo posibles, pero debe tener su capa común más baja para protegerlo de las solicitudes contradictorias.

0

Puede usar un semáforo de algún tipo para mantener el acceso a todos los servicios en serie.

6

Su diseño es defectuoso. Los servicios deben ser idempotentes. Si las clases que tienes no son compatibles, rediseñalas hasta que lo hagan. Parece que cada uno de los tres métodos debería ser la base de los servicios, no las clases.

+0

¿Puedes elaborar? –

+0

La única razón que puedo ver para evitar que el método go() en la clase A, B y C se ejecute al mismo tiempo es que están compartiendo algo. Si se trata de datos de clase o datos de la base de datos, debe intentar rediseñar para que no compartan nada. Eso les permitiría correr al mismo tiempo y no preocuparse por interferir. Si eso no es posible, entonces debe tener un servicio que los llame en la secuencia correcta para asegurarse de que no se puedan llamar fuera de servicio. En cualquier caso, creo que su diseño es defectuoso y la cura será peor que la enfermedad. – duffymo

+0

+100 si pudiera. Hay un gran problema en algún lugar del diseño y semáforos, bloqueos, etc. no son la forma de resolver esto. –

0

¿Por qué no utilizar hipermedia para restringir el acceso?

usar algo como,

POST /A 

a initate el primer proceso.El cuando se ha completado los resultados deben proporcionar un enlace a seguir para iniciar el segundo proceso,

<ResultsOfProcessA> 
    <Status>Complete</Status> 
    <ProcessB href="/B"/> 
</ResultsOfProcessA> 

siga el enlace a initate el segundo proceso,

POST /B 

y de repetición para la parte C.

Podría decirse que un cliente con mal comportamiento podría almacenar en caché el enlace al paso B e intentar reutilizarlo en alguna solicitud futura para eludir la secuencia. Sin embargo, no sería demasiado difícil asignar algún tipo de token al hacer el paso A y requerir que el token se pase al paso B y C para evitar que el cliente construya el URL manualmente.

Al leer más sus comentarios, parece que tiene una situación en la que A podría ejecutarse antes o después de B. En este caso, sugeriría crear un recurso D que represente el estado de todo el conjunto de procesos (A, B y C). Cuando un cliente recupera D, se le presentan los URI que puede seguir. Una vez que un cliente ha iniciado el proceso A, entonces el recurso D debe eliminar el enlace B durante el procesamiento. Lo contrario debería ocurrir cuando B se inicia antes de A.

La otra ventaja de esta técnica es que es obvio si A o B se han ejecutado durante el día ya que el estado se puede mostrar en D. Una vez que A y B tienen ejecutado, entonces D puede contener un enlace para C.

El hipermedio no es una solución 100% infalible porque podría tener dos clientes con la misma copia de D y ambos podrían pensar que el proceso A no se ha ejecutado y ambos podrían intento de ejecutarlo simultáneamente. Esto podría abordarse teniendo algún tipo de marca de tiempo "Última modificación" en D y podría actualizar esa marca de tiempo siempre que cambie el estado de D. Esto podría permitir que la solicitud posterior sea denegada. Según la descripción de su escenario, parecería que se trata de un caso marginal y los hipermedios detectarían la mayoría de los intentos de ejecutar procesos en paralelo.

Cuestiones relacionadas