2009-02-18 8 views
1

Je travaille sur un service qui s'exécute dans une batterie de serveurs d'applications Java qui doit effectuer des tâches périodiques (par exemple, toutes les 2 minutes). Le service doit s'interfacer avec des entités externes, et il est nécessaire de synchroniser les différentes instances de sorte qu'un seul d'entre elles travaille sur le travail à un moment donné. Étant donné que le service utilise une base de données au cours de ce travail, je pensais à la mise en œuvre de la synchronisation basée sur une simple table de DB:Synchronisation d'instance basée sur une base de données simple

id, owner, stamp 

où id est l'id du verrou, le propriétaire est le propriétaire actuel et le timbre est le temps qu'il a été verrouillé .

Les méthodes seraient:

tryLock(id, maxAge, owner) - to try to lock a record or break an old record 
refresh(id, owner) - to update the stamp to signal we're still around working on the job 
release(id, owner) - to release the lock 

Comment voulez-vous mettre en œuvre cette?

Edit: retiré ma mise en œuvre, je post-it comme une « réponse »

Répondre

1

je suis venu avec la mise en œuvre suivante, mais je ne suis pas sûr si elle gère tous les cas de coin (et je ne suis pas entièrement sûr que j'utilise BeanManagedTransaction correctement). Aussi, si vous pensez que ce problème de synchronisation pourrait être traité d'une manière plus simple, pointez-moi dans la bonne direction.

@Service(objectName=Sync.EjbName) 
@Management(SyncMgt.class) 
@TransactionManagement(value=TransactionManagementType.BEAN) 
public class SyncSvc implements SyncMgt { 

    @PersistenceContext 
    protected EntityManager entityManager_; 
    @Resource 
    protected UserTransaction utx_; 

    @TransactionAttribute(TransactionAttributeType.REQUIRED) 
    private boolean update(SyncRecord sr, String owner) { 
     Date stamp = (owner != null) ? new Date() : null; 
     Query q; 
     if (sr.getOwner() != null) { 
      q = entityManager_.createQuery("UPDATE SyncRecord sr SET sr.owner = :newOwner, sr.stamp = :stamp WHERE sr.id = :id AND sr.owner = :origOwner AND sr.stamp = :origStamp"); 
      q.setParameter("origOwner", sr.getOwner()); 
      q.setParameter("origStamp", sr.getStamp()); // make it fail if someone refreshed in the meantime 
     } 
     else { 
      q = entityManager_.createQuery("UPDATE SyncRecord sr SET sr.owner = :newOwner, sr.stamp = :stamp WHERE sr.id = :id AND sr.owner IS NULL"); 
     } 
     q.setParameter("id", sr.getId()); 
     q.setParameter("newOwner", owner); 
     q.setParameter("stamp", stamp); 
     int res = q.executeUpdate(); 
     if (res != 1) { 
      return false; 
     } 
     return true; 
    } 

    @TransactionAttribute(TransactionAttributeType.REQUIRED) 
    private boolean tryLockImpl(String id, long maxAge, String owner) { 
     SyncRecord sr = entityManager_.find(SyncRecord.class, id); 
     if (sr == null) { 
      // no record yet, create one 
      sr = new SyncRecord(id, owner); 
      sr.touch(); 
      entityManager_.persist(sr); 
      entityManager_.flush(); 
      return true; 
     } 
     // found a SyncRecord, let's see who owns it 
     if (owner.equals(sr.getOwner())) { 
      // log some warning, re-locking old lock, should use refresh instead 
      return update(sr, owner); 
     } 
     if (sr.getOwner() == null) { 
      // sr is not held by anyone, safe to grab it 
      return update(sr, owner); 
     } 
     // someone else holds it, let's check the age 
     if (maxAge >= 0) { 
      long maxAgeStamp = System.currentTimeMillis() - maxAge; 
      if (sr.getStamp().getTime() < maxAgeStamp) { 
       if (update(sr, owner)) { 
        return true; 
       } 
       return false; 
      } 
     } 
     return false; 
    } 

    // Sync impl: 

    /** 
    * Try to lock "id" for "owner" 
    * If the lock is held by someone else, but is older than maxAge, break it 
    */ 
    @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) 
    public boolean tryLock(String id, long maxAge, String owner) { 
     if (id == null) 
      throw new IllegalArgumentException("id is null"); 
     try { 
      utx_.begin(); 
      if (tryLockImpl(id, maxAge, owner)) { 
       utx_.commit(); 
       return true; 
      } 
     } 
     catch (EntityExistsException e) { 
      // failed to lock, someone beat us to it 
     } 
     catch (Throwable e) { 
      // some fishy error, raise alarm, log, etc 
     } 
     try { 
      utx_.rollback(); 
     } 
     catch (Throwable e) { 
      // log the error, not much else we can do at this point 
     } 
     return false; 
    } 

    /** 
    * Refresh lock "id" belonging to "owner" (update its stamp) 
    */ 
    @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) 
    public boolean refresh(String id, String owner) { 
     if (id == null) 
      throw new IllegalArgumentException("id is null"); 
     try { 
      utx_.begin(); 
      SyncRecord sr = entityManager_.find(SyncRecord.class, id); 
      if (sr == null || !owner.equals(sr.getOwner())) { 
       utx_.rollback(); 
       return false; 
      } 
      if (update(sr, owner)) { 
       utx_.commit(); 
       return true; 
      } 
     } 
     catch (Throwable e) { 
      // some fishy error, raise alarm, log, etc 
     } 
     try { 
      utx_.rollback(); 
     } 
     catch (Throwable e) { 
      // log the error, not much else we can do at this point 
     } 
     return false; 
    } 

    /** 
    * release lock "id" held by "owner" 
    */ 
    @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) 
    public void release(String id, String owner) { 
     if (id == null) 
      throw new IllegalArgumentException("id is null"); 
     try { 
      utx_.begin(); 
      SyncRecord sr = entityManager_.find(SyncRecord.class, id); 
      if (sr == null || !owner.equals(sr.getOwner())) { 
       // we don't own it 
       utx_.rollback(); 
       return; 
      } 
      if (update(sr, null)) { 
       utx_.commit(); 
       return; 
      } 
     } 
     catch (Throwable e) { 
      // some fishy error, raise alarm, log, etc 
     } 
     try { 
      utx_.rollback(); 
     } 
     catch (Throwable e) { 
      // log the error, not much else we can do at this point 
     } 
    } 

    // LifeCycle impl: 

    public void start() {} 
    public void stop() {} 

} 
Questions connexes