Node.js 当需要比较更新的时间戳时,如何解决Firebase实时数据库服务器端时间戳的波动性?

Node.js 当需要比较更新的时间戳时,如何解决Firebase实时数据库服务器端时间戳的波动性?,node.js,firebase,firebase-realtime-database,Node.js,Firebase,Firebase Realtime Database,在阅读之后,我的印象是,一旦对象访问数据库,时间戳占位符将计算一次并保持不变,但我的情况并非如此: // Example on Node: > const db = f.FIREBASE_APP.database(); > const timestamp = f.FIREBASE_APP.database.ServerValue.TIMESTAMP; > const ref = db.ref('/test'); > ref.on( ... 'child_added

在阅读之后,我的印象是,一旦对象访问数据库,时间戳占位符将计算一次并保持不变,但我的情况并非如此:

// Example on Node:

> const db = f.FIREBASE_APP.database();
> const timestamp = f.FIREBASE_APP.database.ServerValue.TIMESTAMP;

> const ref = db.ref('/test'); 

> ref.on(
... 'child_added',
... function(snapshot) {
..... console.log(`Timestamp from listener: ${snapshot.val().timestamp}`);
..... }
... )

> var child_key = "";

> ref.push({timestamp: timestamp}).then(
... function(thenable_ref) {
..... child_key = thenable_ref.key;
..... }
... );
Timestamp from listener: 1534373384299

> ref.child(child_key).once('value').then(
... function(snapshot) {
..... console.log(`Timestamp after querying: ${snapshot.val().timestamp}`);
..... }
... );
> Timestamp after querying: 1534373384381

> 1534373384299 < 1534373384381
true
底线是,如果需要依赖不可变的服务器端时间戳,请记住这一点,或者解决它。

当您执行ref.push{timestamp:timestamp}时,Firebase客户端会立即对客户端上的时间戳进行估计,并在本地为此触发事件。然后它将命令发送到服务器

Firebase客户端从服务器接收到响应后,将检查实际时间戳是否与其估计值不同。如果确实不同,客户将触发对账事件

通过在设置值之前附加值侦听器,您可以最容易地看到这一点。您将看到它与服务器的初始估计值和最终值一起启动

另见:


警告:在浪费了一天之后,最终的解决方案是根本不使用Firebase服务器时间戳,如果您必须在类似于下面的用例中比较它们的话。当事件足够快时,第二次“值”更新可能根本不会触发

Frank在回答中描述的双重更新条件的一个解决方案是,获取最终的服务器时间戳值为1,以嵌入一个on“event”。。。“添加的子项”中的侦听器。。。和2删除“事件”上的。。。在特定用例允许的情况下,侦听器将立即启动

> const db = f.FIREBASE_APP.database();
> const ref = db.ref('/test');
> const timestamp = f.FIREBASE_APP.database.ServerValue.TIMESTAMP;

> ref.on(
    'child_added',
    function(child_snapshot) {
      console.log(`Timestamp in 'child_added':    ${child_snapshot.val().timestamp}`);
      ref.child(child_snapshot.key).on(
        'value',
        function(child_value_snapshot) {

          // Do a timestamp comparison here and remove `on('value',...)`
          // listener here, but keep in mind: 
          // + it will fire TWICE when new child is added
          // + but only ONCE for previously added children!

          console.log(`Timestamp in embedded 'event': ${child_value_snapshot.val().timestamp}`);
          }
        )
      }
    )

// One child was already in the bank, when above code was invoked:
Timestamp in 'child_added':    1534530688785
Timestamp in embedded 'event': 1534530688785

// Adding a new event:
> ref.push({timestamp: timestamp});null;

Timestamp in 'child_added':    1534530867511
Timestamp in embedded 'event': 1534530867511
Timestamp in embedded 'event': 1534530867606
在我的CQRS/ES案例中,事件被写入/event_存储路径,“添加的子事件”侦听器在新事件出现时更新累积状态,其中每个事件都有一个ServerValue.TIMESTAMP。侦听器比较新事件和状态的时间戳,以确定是否应应用新事件或是否已应用新事件。这在服务器重新启动以构建内存中的内部状态时最为重要,但这里有一个关于如何处理单/双点火的简短概述:

event_store.on(
    'child_added',
    function(event_snapshot) {

        const event_ref = event_store.child(event_id)

        event_ref.on(
            'value',
            function(event_value_snapshot){

                const event_timestamp = event_value_snapshot.val().timestamp;

                if ( event_timestamp <= state_timestamp ) {

                    // === 1 =======
                    event_ref.off();
                    // =============

                } else {

                    var next_state =  {};

                    if ( event_id === state.latest_event_id ) { 
                        next_state["timestamp"] = event_timestamp;

                        Object.assign(state, next_state);
                        db.ref("/state").child(stream_id).update(state);

                        // === 2 =======
                        event_ref.off();
                        // =============

                    } else {

                        next_state =  event_handler(event_snapshot, state);

                        next_state["latest_event_id"] = event_id;
                        Object.assign(state, next_state);
                    }
                }
            }
        );
    }
);
当服务器重新启动时,在'child_added'上。。。遍历/event_存储中已存在的所有事件,并附加“value”,。。。动态地在所有子项上执行,并将事件的时间戳与当前状态的时间戳进行比较

如果事件的时间早于当前状态event_timestamp 否则,该事件较新,这意味着它尚未应用于当前状态和ServerValue.TIMESTAMP,也尚未计算,导致回调触发两次。为了处理双重更新,此块将实际子项的键(即事件id)保存到最新事件id的状态,并将其与传入事件的键(即事件id)进行比较:


这不是你在这里展示作品的方式。数据在写入后不会在数据库中自动更改,直到再次写入。如果它真的像这样工作,它将完全打破人们对听众工作方式的期望。@DougStevenson我正要测试Frank的解释,但他所说的是有道理的,因为在随后的查询中,值确实是不同的,即在听众上,以及之后。我正试图尽可能习惯地使用Firebase的功能,但这个问题确实让我感到困惑,我需要解决它。我只花了大约10分钟,没有多少人会用这种方式使用DB,但这仍然是一个令人不快的惊喜。如果我做错了什么,请告诉我。啊,是的,弗兰克是对的。首先得到一个估计值,然后是实际值。在那之后就不应该改变了。你是说这是故意的,什么也做不了?在这种情况下不应该记录这一点吗?可能还有其他情况下,比较服务器端时间戳是否相等很重要,但这会破坏所有这些情况。实现方式是否相同?
event_store.on(
    'child_added',
    function(event_snapshot) {

        const event_ref = event_store.child(event_id)

        event_ref.on(
            'value',
            function(event_value_snapshot){

                const event_timestamp = event_value_snapshot.val().timestamp;

                if ( event_timestamp <= state_timestamp ) {

                    // === 1 =======
                    event_ref.off();
                    // =============

                } else {

                    var next_state =  {};

                    if ( event_id === state.latest_event_id ) { 
                        next_state["timestamp"] = event_timestamp;

                        Object.assign(state, next_state);
                        db.ref("/state").child(stream_id).update(state);

                        // === 2 =======
                        event_ref.off();
                        // =============

                    } else {

                        next_state =  event_handler(event_snapshot, state);

                        next_state["latest_event_id"] = event_id;
                        Object.assign(state, next_state);
                    }
                }
            }
        );
    }
);