代码之家  ›  专栏  ›  技术社区  ›  toraritte

当需要比较更新的时间戳时,如何绕过Firebase Realtime数据库的服务器端时间戳波动性?

  •  0
  • toraritte  · 技术社区  · 8 年前

    在阅读了 docs on ServerValue.TIMESTAMP

    // 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
    

    从中查询时,时间戳不同 on

    这是不是像这样的设计,我只是错过了一些文件的一部分?如果是这样的话,什么时候 ServerValue.TIMESTAMP 稳定?

    我正在实时数据库上构建一个CQRS/ES库,只是想避免 expected_version


    更新

    /* `db`, `ref` and `timestamp` are defined above,
       and the test path ("/test") has been deleted
       from DB beforehand to avoid noise.
    */
    
    > ref.on(   
    ... 'child_added', 
    ... function(snapshot) {    
    ..... console.log(`Timestamp from listener: ${snapshot.val().timestamp}`);     
    ..... }
    ... )
    
    > ref.on(   
    ... 'value', 
    ... function(snapshot) {
    ..... console.log(snapshot.val());
    ..... }
    ... )
    
    > ref.push({timestamp: timestamp}); null;
    
    Timestamp from listener: 1534434409034
    { '-LK2Pjd8FS_L8hKqIpiE': { timestamp: 1534434409034 } }
    { '-LK2Pjd8FS_L8hKqIpiE': { timestamp: 1534434409114 } }
    

    如果需要依赖不可变的服务器端时间戳,请记住这一点,或者解决它 .

    2 回复  |  直到 8 年前
        1
  •  1
  •   Frank van Puffelen    8 年前

    当你执行 ref.push({timestamp: timestamp}) 火力基地 客户 立即估计客户机上的时间戳,并在本地为此事件触发事件。然后它将命令发送到服务器。

    一旦Firebase客户端接收到来自服务器的响应,它就会检查实际时间戳是否与其估计值不同。如果确实不同,客户机将触发和解事件。

    value 在设置值之前侦听器。您将看到它与服务器的初始估计值和最终值一起启动。

        2
  •  0
  •   toraritte    8 年前

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


    对于Frank在回答中描述的双重更新条件,一个解决方案是获取最终的服务器时间戳值(1)以嵌入 on('event', ...) 内部侦听器 on('child_added', ...) on('事件',…) 在特定用例允许的情况下立即侦听。

    > 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\u store”路径,并且 'child_added' ServerValue.TIMESTAMP . 侦听器将新事件与状态的时间戳进行比较,看是应用新事件还是已经应用了新事件(这在重新启动服务器以构建内存中的内部状态时非常重要)。 Link to the full implementation ,但这里有一个关于如何处理单/双触发的简短概述:

    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);
                        }
                    }
                }
            );
        }
    );
    

    重新启动服务器时, on('添加了子项',…) 浏览“/event\u store”中已存在的所有事件 on('value',...)

    1. 如果事件的时间早于当前状态的时间( event_timestamp < state_timestamp true ),唯一的操作是分离“value”侦听器。此回调将在 占位符已在过去解析过一次。

    2. 否则,事件较新,这意味着它尚未应用于当前状态,并且 ServerValue.TIMESTAMP 也没有被评估,导致回调触发两次。为了处理双重更新,此块保存实际子项的密钥(即。, event_id 在这里)到国家 latest_event_id )并将其与传入事件的键(即。, 事件id

    enter image description here