简单的基于数据库的实例同步 [英] Simple database-based instance synchronization

查看:210
本文介绍了简单的基于数据库的实例同步的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在开发一个在Java应用服务器场中运行的服务,该服务器场需要执行一些定期作业(比如每2分钟一次)。该服务必须与外部实体接口,并且必须同步不同的实例,以便在给定时间只有一个实例在作业上工作。由于该服务在此作业期间使用数据库,因此我考虑基于简单的数据库表实现同步:

I'm working on a service that runs in a java app server farm that needs to do some periodic jobs (say, once every 2 minutes). The service must interface to external entities, and it is necessary to synchronize the different instances so that only one of them works on the job at a given time. Since the service uses a DB during this job, I thought of implementing the synchronization based on a simple DB table:

id, owner, stamp

其中id是锁的id,所有者是当前所有者,邮票是时间锁定。

where id is the lock's id, owner is the current owner and stamp is the time it was locked.

方法如下:

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

你会如何实现这个?

编辑:删除我的实现,我会将其发布为回答

removed my implementation, I'll post it as an "answer"

推荐答案

我来了以下实现,但我不确定它是否处理所有极端情况(我不完全确定我正确使用BeanManagedTransaction)。此外,如果您认为可以更简单的方式处理此同步问题,请指出正确的方向。

I came up with the following implementation, but I'm not sure if it handles all corner cases (and I'm not entirely sure I'm using the BeanManagedTransaction correctly). Also, if you think this syncronization problem could be handled in a simpler way, point me to the right 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() {}

}

这篇关于简单的基于数据库的实例同步的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆