繁体   English   中英

防止不同服务器多次调用操作

[英]Preventing an action from being called multiple times by different servers

我有一个小表(<100行),其中包含一个有序的项目列表。 我想随机置换这些物品。 我想要这样做的方法是选择5个最近最少使用的项目并随机选择其中一个。 但是,我只想偶尔做一次。

我正在考虑使用存储过程执行此操作,然后查询简单地变为类似SELECT TOP 1 * FROM myTable ORDER BY LastUsedDate DESC

不幸的是,这个解决方案并不好。 如果每个排列之间的时间(每次运行存储过程)都是可变的,则每X分钟运行一次的SQL-Server作业将不起作用。 如果我让我的服务器执行排列,多个服务器可能最终会进行排列。

这是我想在服务器上做的逻辑:

  1. 从DB获取最近使用的项目
  2. 如果是时候进行排列,请尝试锁定表格
  3. 如果它已被锁定,则表示另一台服务器已在执行排列(转到1)
  4. 执行排列
  5. 返回最近使用的项目。

但是,我可以想象锁定表并不是一个很好的解决方案。 所以我在寻找建议:)。

我在使用Hibernate的服务器上使用Java。

谢谢!

更新:

我最终尝试使用hibernate锁定行,而不是使用存储过程(更容易调试,更容易推送)。 但是,我不认为hibernate正确锁定了necassary行。 这是我的代码:

Session s = sessionFactory.openSession();
Transaction tx = null;
try {
    tx = s.beginTransaction();

    //Check whether the most recent tournament is expired or not. If it's not, abort (another server already updated it)
    TournamentTemplateRecord lastActiveTournament = (TournamentTemplateRecord) s.createCriteria(TournamentTemplateRecord.class)
    .addOrder(Order.desc("lastUse"))
    .setMaxResults(1)
    .uniqueResult();

    long startTime = lastActiveTournament.getLastUse().getTime();
    long tournamentDurationMillis =  lastActiveTournament.getDurationInSec() * 1000;
    if ((startTime + tournamentDurationMillis) < System.currentTimeMillis()){
        //Tournament is still active and valid. Abort.
        System.out.println("Tournament is still active");
        tx.rollback();
        return;
    }

    // Fetch the 5 least recently used tournaments
    List<TournamentTemplateRecord> leastRecentlyUsedTournaments = s.createCriteria(TournamentTemplateRecord.class)
    .addOrder(Order.asc("lastUse"))
    .setMaxResults(5)
    .setLockMode(LockMode.PESSIMISTIC_WRITE)
    .setTimeout(0) //If rows are locked, another server is probably already doing this.
    .list();

    Random rand = new Random();

    // Pick one at random
    TournamentTemplateRecord randomTournament = leastRecentlyUsedTournaments.get(rand.nextInt(leastRecentlyUsedTournaments.size()));

    randomTournament.setLastUse(new Date());

    s.update(randomTournament);

    tx.commit();
} catch (Exception e) {
    if(tx != null) {
        tx.rollback();
    }
} finally {
    s.close();
}

但是Hibernate没有生成SELECT ... FOR UPDATE NOWAIT 有任何想法吗?

这是生成的HQL:

Hibernate: 
    WITH query AS (select
        ROW_NUMBER() OVER (
    order by
        this_.lastuse desc) as __hibernate_row_nr__,
        this_.combattemplateid as id89_0_,
        this_1_.combattypeid as combatty2_89_0_,
        this_1_.combattargetid as combatta3_89_0_,
        this_1_.resourcenameid as resource4_89_0_,
        this_1_.resourcedescriptionid as resource5_89_0_,
        this_1_.rewardloottemplateid as rewardlo6_89_0_,
        this_1_.combatcontainertypeid as combatco7_89_0_,
        this_.requirementtemplateid as requirem2_90_0_,
        this_.assetid as assetid90_0_,
        this_.durationinsec as duration4_90_0_,
        this_.lastuse as lastuse90_0_ 
    from
        tournament_tournamenttemplate this_ 
    inner join
        readyforcombat_combattemplate this_1_ 
            on this_.combattemplateid=this_1_.id ) SELECT
            * 
    FROM
        query 
    WHERE
        __hibernate_row_nr__ BETWEEN ? AND ?
Hibernate: 
    WITH query AS (select
        ROW_NUMBER() OVER (
    order by
        this_.lastuse asc) as __hibernate_row_nr__,
        this_.combattemplateid as id89_0_,
        this_1_.combattypeid as combatty2_89_0_,
        this_1_.combattargetid as combatta3_89_0_,
        this_1_.resourcenameid as resource4_89_0_,
        this_1_.resourcedescriptionid as resource5_89_0_,
        this_1_.rewardloottemplateid as rewardlo6_89_0_,
        this_1_.combatcontainertypeid as combatco7_89_0_,
        this_.requirementtemplateid as requirem2_90_0_,
        this_.assetid as assetid90_0_,
        this_.durationinsec as duration4_90_0_,
        this_.lastuse as lastuse90_0_ 
    from
        tournament_tournamenttemplate this_ 
    inner join
        readyforcombat_combattemplate this_1_ with (updlock, rowlock) 
            on this_.combattemplateid=this_1_.id ) SELECT
            * 
    FROM
        query 
    WHERE
        __hibernate_row_nr__ BETWEEN ? AND ?
Hibernate: 
    update
        Tournament_TournamentTemplate 
    set
        RequirementTemplateId=?,
        AssetId=?,
        DurationInSec=?,
        LastUse=? 
    where
        combatTemplateId=?

锁定表将正常工作,如果每个操作需要通过这些步骤将没有成本(因为你需要锁定一些东西)。

真的,你需要重新审视你的要求和架构,但这有一些东西的气味会很快变得非常混乱。 只需负责随机化,在服务器上运行它,在需要时随机化。 保持简单。

这是一个有趣的难题。 由于我不知道如何确定一次 ,它是存储过程的参数。 但是,决策逻辑应该在数据库中实现。 这比尝试跨多个服务器协调决策要简单得多。 如果你可以更具体,我可以改进我的答案。

这个想法是将五个最近最少使用的行存储在一个带有计算序数的表变量中。 将@pick计算为0到4之间的随机数,并更新myTable中选择了计算序数的行。

CREATE PROCEDURE [dbo].[GetPermutedMostRecentlyUsed]
AS
BEGIN

    DECLARE @isTimeToPermute BIT
    SELECT TOP 1 @isTimeToPermute=CASE WHEN Expiry<GETDATE() THEN 1 ELSE 0 END FROM MyTable ORDER BY LastUsedDate DESC

    IF @isTimeToPermute = 1
    BEGIN
        BEGIN TRAN
            SELECT @isTimeToPermute=CASE WHEN Expiry<GETDATE() THEN 1 ELSE 0 END FROM MyTable WITH (TABLOCKX) ORDER BY LastUsedDate DESC
            IF @isTimeToPermute = 1
            BEGIN
                DECLARE @P TABLE
                (
                    ID INT PRIMARY KEY NOT NULL,
                    Ordinal INT NOT NULL
                )

                INSERT @P (ID, Ordinal)
                SELECT TOP 5 ID, ROW_NUMBER() OVER(ORDER BY LastUsedDate ASC) - 1 AS Ordinal
                FROM MyTable WITH (TABLOCKX)

                DECLARE @Pick INT; SET @Pick = FLOOR(RAND() * 5)

                UPDATE MyTable SET LastUsedDate=GETDATE(), Expiry=DATEADD(SECOND, 300, GETDATE())
                FROM MyTable AS T
                INNER JOIN @P AS P ON P.ID=T.ID AND P.Ordinal=@Pick
            END
        COMMIT TRAN
    END

    SELECT TOP 1 * FROM MyTable ORDER BY LastUsedDate DESC

END
GO

只要不加任何参数调用它。

EXEC GetPermutedMostRecentlyUsed

您必须调整列名等,以匹配您的表架构。

编辑在内部计算@isTimeToPermute并执行表锁定。 只有在需要置换记录时才会进行表锁定。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM