2016-11-01 4 views
2

우리는 현장의 다양한 클라이언트에 연결된 스프링 부트 응용 프로그램을 가지고 있습니다. 이 응용 프로그램에는 클라이언트에서 호출되어 DB 및 실제 스위치를 사용하여 조명을 끄거나 켜는 컨트롤러가 있습니다.스프링 부트 - 컨트롤러에 대한 동시 액세스를 피하는 방법

두 개 이상의 클라이언트가 서버의 API에 액세스 할 때 문제가 발생합니다.이 방법은 상태를 변경하기 위해 표시등이 켜져 있거나 꺼져 있는지 (DB에서) 확인하기 때문에 문제가됩니다. 표시등이 꺼져 있고 동시에 2 개의 클라이언트가 동시에 서비스를 호출하면 첫 번째는 표시등을 켜고 db의 상태를 변경하지만 두 번째는 표시등에도 액세스하지만 DB의 상태는 OFF이지만 첫 번째 클라이언트는 이미 조명을 조정 했으므로 결국 초가 켜지면서 꺼질 것입니다. 아마도 내 설명이 약간 명확하지 않을 수 있습니다. 문제는 다음과 같습니다. 컨트롤러에 한 번 액세스하여 요청할 수 있습니까? 아래의 대답

덕분에, 우리는 스위치를 전환하는 방법에 대한 비관적 잠금을 도입,하지만 우리는

우리는 봄 부트를 사용하여 + 최대 절전 모드 있습니다 ... 우리의 클라이언트에서 200 개 상태가 계속

이제 컨트롤러 비관적 잠금 예외를 갖는다

@Override 
@Transactional(isolation=Isolation.REPEATABLE_READ) 
public void toggleSwitchNew(GpioPinDigitalOutput relePin, Interruttore interruttore, boolean on) { 
    Date date = new Date(); 
    interruttore.setDateTime(new Timestamp(date.getTime())); 
    interruttore.setStato(on); 

    String log = getLogStatus(on) + interruttore.getNomeInterruttore(); 
    logger.debug(log); 
    relePin.high(); 
    try { 
     Thread.sleep(200); 
    } catch (InterruptedException e) { 
     logger.error("Errore sleep ", e); 
    } 
    relePin.low(); 
    updateInterruttore(interruttore); 
    illuminazioneService.createIlluminazione(interruttore, on); 


} 
0 다음

try { 

           String pinName = interruttore.getPinName(); 
           // logger.debug("Sono nel nuovo ciclo di 
           // gestione interruttore"); 
           if (!interruttore.isStato()) { // solo se 
                   // l'interruttore 
                   // è 
                   // spento 

            GpioPinDigitalOutput relePin = interruttore.getGpio() 
              .provisionDigitalOutputPin(RaspiPin.getPinByName(pinName)); 
            interruttoreService.toggleSwitchNew(relePin, interruttore, lit);               // accendo 
            interruttore.getGpio().unprovisionPin(relePin); 
           } 



         } catch (GpioPinExistsException ge) { 
          logger.error("Gpio già esistente"); 
         } catch (PessimisticLockingFailureException pe){ 
          logger.error("Pessimistic Lock conflict", pe); 
          return new ResponseEntity<Sensoristica>(sensoristica, HttpStatus.CONFLICT); 
         } 

toggleSwitchNew

그런 다음 우리는 우리의 고객의 요청 상태 코드를 기록하고 그들은 항상 곁에들이 동시

답변

4

이것은 고전적인 잠금 문제입니다. pessimistic locking을 사용할 수 있습니다. 한 번에 하나의 클라이언트 만 데이터를 조작하도록 허용 (상호 배제)하거나 optimistic locking에 의해 수행 할 수 있습니다. 즉, 여러 동시 클라이언트가 데이터를 조작 할 수 있지만 첫 번째 커미터 만 성공하도록 허용합니다.

사용하는 기술에 따라 여러 가지 방법이 있습니다. 예를 들어 해결할 수있는 또 다른 방법은 오른쪽 database isolation level을 사용하는 것입니다. 귀하의 경우에는 적어도 "반복 읽기"격리 수준이 필요해 보입니다.

반복 읽기를 사용하면 두 개의 동시 트랜잭션이 동일한 레코드를 동시에 읽고 변경하는 경우 어느 하나만 성공할 수 있습니다.

귀하의 경우 봄 거래를 오른쪽 isolation level으로 표시 할 수 있습니다.

@Transacational(isolation=REPEATABLE_READ) 
public void toggleSwitch() { 
    String status = readSwithStatus(); 
    if(status.equals("on") { 
     updateStatus("off"); 
    } else { 
     updateStatus("on"); 
    } 
} 

두 개의 동시 클라이언트가 스위치 상태를 업데이트하려고하면 첫 번째 커밋이 성공하고 두 번째 클라이언트는 항상 실패합니다.동시 실패로 인해 두 번째 클라이언트에게 트랜잭션이 성공하지 못했다는 사실을 알리도록 준비해야합니다. 이 두 번째 트랜잭션은 자동으로 롤백됩니다. 귀하 또는 귀하의 고객이 재 시도할지 여부를 결정할 수 있습니다. 난 당신이 (예를 들어, JPA, 최대 절전 모드, 일반 JDBC)를 사용하는지에 따라 말을이었다로

@Autowire 
LightService lightService; 

@GET 
public ResponseEntity<String> toggleLight(){ 
    try { 
     lightService.toggleSwitch(); 
     //send a 200 OK 
    }catch(OptimisticLockingFailureException e) { 
     //send a Http status 409 Conflict! 
    } 
} 

는하지만, 비관적 또는 낙관적 잠금 전략 중 하나와 함께이 작업을 수행하는 여러 가지 방법이 있습니다.

왜 스레드 동기화가 아닌가?

다른 대답은 지금까지 제안 당신은 당신의 코드를 실행하는 하나의 JVM이있는 경우 작동 할 수 동기화 블록을 사용하여 스레드 수준에서 자바의 상호 배제를 사용하여 비관적 잠금에 대한 있습니다. 이 전략은 코드를 실행하는 둘 이상의 JVM이 있거나 수평 적으로 확장하고 부하 분산 장치 뒤에 JVM 노드를 더 추가하는 경우 비효율적 일 수 있습니다.이 경우 스레드 잠금은 더 이상 문제를 해결하지 못합니다.

하지만 여전히 데이터베이스 수준에서 비관적 잠금을 구현할 수 있습니다. 변경하기 전에 데이터베이스 레코드를 잠그고 데이터베이스 수준에서 상호 제외 영역을 만들면 프로세스가 강제로 비관적 잠금을 구현할 수 있습니다.

그래서 여기서 중요한 것은 잠금 원칙을 이해하고 특정 시나리오 및 기술 스택에 적합한 전략을 찾는 것입니다. 대부분의 경우, 어떤 경우 데이터베이스 레벨에서 잠기는 몇 가지 형태의 잠금이 필요합니다.

+1

덕분에,이 설명은, 내가 비관적 DB 잠금, 당신이 사용하는 데이터베이스의 어떤 종류의 – besmart

+0

에 최선의 전략이라고 분명히 생각했다? 어떻게 동시성을 테스트하고 있습니까? Hibernate로 동시성 시나리오를 테스트하는 것은 커밋시 변경 사항의 동시성이 평가되기 때문에 그렇게 간단하지 않습니다. 따라서 두 개 이상의 트랜잭션을 동시에 시작해야하며, 하나는 커밋하고 다른 트랜잭션은 실패해야합니다. @Transactional 어노테이션을 사용하면 그 단계는 스프링 프록시에서 일어납니다. – besmart

+0

최대 절전 모드 사용하고 그런데이 경우 –

0

를 사용하여 동기화 경우에도 200를 얻을 수 -하지만 사용자는 여전히 즉시 실행할 것을 하나의 명령에 문제가있을 것입니다 후 충분히 빠른 클릭하면 또 다른 후.

동기화는 한 번에

synchronized(this) { ... } 

에 하나의 스레드가 블록을 실행하고 있는지 확인합니다.

지연을 도입하고 명령을 빠르게 연속적으로 거부 할 수도 있습니다.

try { 
    synchronized(this) { 
     String pinName = interruttore.getPinName();      
      if (!interruttore.isStato()) { // switch is off 
      GpioPinDigitalOutput relePin = interruttore.getGpio() 
       .provisionDigitalOutputPin(RaspiPin.getPinByName(pinName)); 
      interruttoreService.toggleSwitchNew(relePin, interruttore, lit); // turn it on 
      interruttore.getGpio().unprovisionPin(relePin); 
     } 
    } 
} catch (GpioPinExistsException ge) { 
    logger.error("Gpio già esistente"); 
} 
0

다른 사람들의 대답은 지나치게 복잡해 보입니다. 간단하게하십시오.

토글 링하는 대신 요청에 새 값이 지정됩니다. 컨트롤러 내부에 synchronized 블록을 넣으십시오. 새 값이 현재 값과 다른 경우에만 동기화 된 블록 내부에서 작업을 수행하십시오.

Object lock = new Object(); 
Boolean currentValue = Boolean.FALSE; 
void ligthsChange(Boolean newValue) { 
    synchronized(lock) { 
    if (!currentValue.equals(newValue)) { 
     doTheSwitch(); 
     currentValue = newValue; 
    } 
    } 
} 
관련 문제