您的位置:首页 > 其它

增量更新模型的讨论

2015-09-16 11:50 155 查看
客户端和服务端的数据同步过程中,客户端有缓存,不需要每次都是全量刷新,所以可以采用增量的方式更新。

每次在客户端进行刷新的时候,服务端会将最新的增删改操作推送到客户端,客户端对其缓存进行操作,以保持数据的同步。

最原始的方法 - Full Transfer



[图片来自参考文献 1]

我们要实现的是数据的同步,那么我们只要每次在本地列表需要更新时,将所有后端数据库的信息拉取到本地,进行对比,如果有新增就新增,有修改就修改,如果新来的版本里面某一项不见了,就直接在本地缓存中删除该项即可。

优点:

写接口时很清爽,它是最简单的实现

缺点:

服务端发送多余的数据

只能用于小数据量,在数据量比较大的时候,单次刷新的操作就耗费很多网路流量。

应用场景:IMAP 协议,新闻类 APP 中的每日头条,如 Yahoo News Digest

在 CalD*** 日历协议中,每个日历事件都拥有一个 ETag 来标记它的版本信息,客户端对比服务器发来的 ETag 来决定要不要更新某项日历事件,此处的 ETag 和 HTTP headers 中 Last-Modified 和 ETag 其实是一样的东西,用于标记版本信息。这种方式即为接下来要提到的 Timestamp Transfer。

根据修改时间来拉取增删改信息 - Timestamp Transfer



[图片来自参考文献 1]

客户端存储上次拉取的数据的 Timestamp,在请求更新数据时,携带该 Timestamp 作为本地数据版本信息。数据库内每行数据设置一个 LAST_UPDATE_TIME 字段,服务器将比该时间更新的数据返回给客户端。

优点:

相对于 Full Transfer 来说减少了冗余数据的传输

缺点:

传输时 Timestamp 作为版本信息需要精确控制,请求错误的版本号可能带来本地数据的不准确

已经删除的数据其实已经不存在了,取不到 LAST_UPDATE_TIME

第二个缺点可以通过设置 IS_DELETE 字段来避免,每次删除数据时,仅仅更新 LAST_UPDATE_TIME 和设置 IS_DELETE 为 True 来标记已删除。此处带来的缺点是,被删除的数据继续占用空间,不过当只有一个客户端时,可以在客户端确认删除缓存中相应数据后删除数据库中 IS_DELETE 为 True 的数据,这个方法被成为 Soft Delete.

结合算法和修改时间来拉取增删改信息 - Mathematical Transfer



[图片来自参考文献 1]

服务器接收到客户端发来的更新请求时,将客户端根据 Reconciliation 算法生成的值来确定要返回给客户端的增删改信息。此处说的 Reconciliation 算法的作用与 Checksum 校验和类似,用于校验数据是否已经修改。

优点:

避免了查询数据库时对 LAST_UPDATE_TIME 的条件过滤

缺点:

Reconciliation 算法普适性低

Reconciliation 算法开发周期长

增删改日志 - SYNC

服务端记录数据的每次操作都记录进一个增量数据库,数据库内记录了每条操作的对象 ID 和操作的内容。此处思想类似于 Patch 补丁操作,客户端发送一个 Timestamp 信息,服务器将这个时间以后的所有增删改操作返回给客户端,客户端再进行打补丁操作,使得最终结果与服务端同步。

优点:

保持了所有数据的精确可同步

缺点:

客户端很久不更新以后单次的更新补丁很大

如果数据改动很多,那记录操作的表将会变得很大

场景

我们现在的需求是,有一个订单模块,某一用户在 APP 中可以点击刷新订单列表,将服务器上其所有订单显示出来。

# 订单更新操作
def order_update(request):
    # 对其中一个订单做更新操作
    order = Order.objects.get(id=1)
    order.update_time = time.time()
    order.save()
    # 设置缓存内的版本信息
    cache_time = time.time()
    cache.set('order_list_' + str(request.user.id), cache_time, 3600 * 24 * 7)
    return JsonResponse({})

# 返回订单列表
def order_list(request):
    # 保存客户端的版本信息 since,即 timestamp
    since = request.GET.get('since') or 0
    since = float(str(since))
    # 取出缓存中最新版本信息 since_cache
    since_cache = cache.get('order_list_'+ str(request.user.id))
    # 如果客户端携带版本信息为 0,说明客户端请求全量更新
    # 或者当缓存中版本新于客户端版本信息,则返回 update_time 新于客户端版本的所有条目
    if (since == 0) or since_cache and (since_cache > since):
        orders = Order.objects.filter(user=request.user, update_time__gt=since)
        data = [order.id for order in orders]
        return JsonResponse({
            'status': 1001,
            'since': since_cache,
            'data': data
        })
    # 没有更新,直接返回最新的缓存时间
    else:
        return JsonResponse({
            'status': 1001,
            'since': since_cache,
            'data': []
        })

筛选的情况

有时候我们只需要更新满足筛选条件的条目,客户端利用 Timestamp Transfer 来进行拉取所有增删改信息,但是只针对满足筛选条件的那些项目进行更新操作,其余的直接丢弃。

参考文献

DATA SYNCHRONIZATION PATTERNS IN MOBILE
APPLICATION DESIGN

两种增量更新方案

RFC 4791 - CalD***

AppSync.org

Evernote Synchronization via EDAM

Object Synchronizer
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: