首页   注册   登录
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
bfbd
V2EX  ›  程序员

探讨一下微服务,考虑用数据缓存代替服务调用。

  •  
  •   bfbd · 65 天前 · 2200 次点击
    这是一个创建于 65 天前的主题,其中的信息可能已经有所发展或是发生改变。

    全文链接如下:

    https://aiportal.github.io/data-driven-service

    简单说就是: 每个微服务仅可引用另一个微服务释放出来的数据缓存,不允许直接调用另一个微服务。至于数据缓存何时更新,完全取决于维护该部分数据的那个微服务。

    优点: 微服务与微服务之间没有直接调用,不会互相影响,即使某一个或某几个微服务当掉了,其他微服务仍可使用旧版的缓存数据继续运行。

    缺点: 设计和划分微服务的时候难度较大,必须让各个微服务之间不依赖同步调用,仅依赖异步数据。

    补足: 特殊情况必须做同步调用时,可以用消息队列加等待异步操作完成的伪同步来实现同步操作。

    38 条回复    2020-04-10 10:07:29 +08:00
    lhx2008
        1
    lhx2008   65 天前 via Android   ❤️ 1
    不知道这个概念是楼主发明的还是。。那写入操作怎么办呢
    hebin
        2
    hebin   65 天前
    这个优点解决的问题现有的方案应该也能解决。 但是这个缺点就很明显了
    p2pCoder
        3
    p2pCoder   65 天前   ❤️ 1
    事务性系统,几乎没法用
    EmdeBoas
        4
    EmdeBoas   65 天前   ❤️ 1
    一个服务应该有两层:无状态的应用层和有状态的存储层,前者根本不存在挂,有问题替换就行;后者挂了才是真的挂;所以每个微服务的有状态层需要隔离开来;你这样所有服务都去查缓存,存在的问题很致命:
    1. 数据都在同一个有状态的服务上,有状态的服务一旦出现问题全都给挂
    2. 数据写入 /读取的一致性问题
    3. 一刀切使用旧版数据完全脱离业务,很多业务并不能接受过期的数据
    gemini767
        5
    gemini767   65 天前   ❤️ 1
    @hebin +1

    缺点不仅仅是异步这么简单,强一致性如何做?高频低效和低频高效的相同数据源,如何划分,多副本存储?感觉为了解决一个问题引入更多问题
    kindjeff
        6
    kindjeff   65 天前   ❤️ 1
    槽点有点多吧。上面说的事务性系统没法用之外,即使是逻辑最简单的系统,只要是写多读少,就退化成了没有缓存;如果是读多写少,在调用方那边做缓存不是更简单么,你提到的优点也是一样有。
    fox0001
        7
    fox0001   65 天前 via Android   ❤️ 1
    只读的话,CDN 是不是更简单?
    guolaopi
        8
    guolaopi   65 天前
    MVVM ?
    整出了 Vue 的感觉
    guolaopi
        9
    guolaopi   65 天前
    @guolaopi #8
    另:Vue 的双向绑定会引发许多玄学,对比来说的话这种思路也会引发一些玄学
    iisky1121
        10
    iisky1121   65 天前   ❤️ 1
    共享缓存,瓶颈不就是在缓存里面了吗?
    xuanbg
        11
    xuanbg   64 天前   ❤️ 1
    不客气地说,这个主意真的很糟糕。问题楼上提了一大堆了,我也就不重复了。我来给楼主一个建议,也把我在项目的实践和大家共享一下。

    楼主要解决的问题其实是服务不可用时,如何保持数据一致性的问题。

    其实这个问题要一分为二地来看,一是必须保证成功的调用,譬如我钱都付了,你不能让我的订单状态还处于待付款状态。二是可以失败的调用,譬如商品中心服务突然调不通了,无法确定商品价格,这个时候就可以返回前端下单失败,让客户等会再来下单。

    针对第一种情况,可以用消息队列进行对接。支付中心作为生产者,在客户付款成功后发布一条付款成功的消息到队列,订单服务作为消费者订阅这个队列就行了。如果你担心更新的时候数据库炸了导致更新失败的话,在更新数据发生异常的时候把消息发到延时队列进行重试就好了。

    至于第二种情况,那就很简单了,当服务调用失败的时候返回一个错误就行了。一级一级返回回去,用户端总会收到错误消息的。
    q8164305
        12
    q8164305   64 天前 via Android   ❤️ 1
    有 mvvm 那味了
    nicebird
        13
    nicebird   64 天前   ❤️ 1
    适合特定只读场景
    wellsc
        14
    wellsc   64 天前   ❤️ 1
    微服务是 stateless 的吧,你这释放缓存的方案,感觉更像是中间件层面的东西了
    TransAM
        15
    TransAM   64 天前 via Android   ❤️ 1
    我觉得缓存和不缓存对于调用方最好是透明的,被调用方自己处理缓存的事情,调用方拿着 uri 访问就行。
    bfbd
        16
    bfbd   64 天前
    1 、写入操作的问题。
    大批量的写入操作就应该封装在本服务内部,而不应该由其他服务承担。如果有,也只能是分层关系,而不应是并列关系。
    2 、事务的实现。
    可以参考这里:
    ```
    PS: 如果需要实时响应,可以在缓存信息表中增加一个 sync 列,sync 为 true 的 URL 缓存会在资源更新操作 (INSERT, UPDATE, DELETE) 返回前完成部署。
    ```
    [https://aiportal.github.io/etag-cache-service]( https://aiportal.github.io/etag-cache-service)

    假设 B 服务的某个资源更新操作需要调用到 A 服务的资源更新操作,当 A 服务的资源更新操作成功返回时,B 服务所需的缓存数据已经部署完毕。同样的,B 服务的更新操作成功返回前,该事务应该提供的缓存数据,也已经部署完毕。
    bfbd
        17
    bfbd   64 天前
    @lhx2008

    @p2pCoder

    关于写入和事务,参见楼上。
    kkeiko
        18
    kkeiko   64 天前   ❤️ 1
    你就告诉我,缓存用啥吧。然后用你说的方案解决一下交易问题,就支付宝目前的业务模型。中间有任何一次非本机房的网络调用出问题怎么办?
    bfbd
        19
    bfbd   64 天前
    @EmdeBoas

    1. 数据只是由一个有状态的服务 (Distribute Service) 负责更新,但数据本身是磁盘上的文件,所以服务挂了没关系,硬盘没挂就可以。
    2. 数据的写入,数据的完整性,都由拥有该数据的服务负责,其他服务只能读取。外部服务只能通过消息队列影响本服务的数据。
    3. 是的,例如修改密码的操作,用户信息和登录验证层可以是两个服务,总不能让用户无限期等待新密码缓存部署。所以这一块设计了 sync 机制,在密码修改操作返回之前,验证操作所需的缓存就已经部署好了。
    这部分内容参见:ETag 缓存服务设计。
    bfbd
        20
    bfbd   64 天前
    @fox0001

    的确有考虑 CDN,这样就可以超出 docker volume 的系统局限,实现广域网分布式了。
    bfbd
        21
    bfbd   64 天前
    @xuanbg

    是的,消息队列和异步操作可以解决微服务间紧密耦合的问题。

    的确是想解决服务间耦合依赖的问题,同时也想降低系统向微服务迁移的难度。

    用数据层缓存的方式,可以让程序员不关心微服务调用,不处理微服务依赖,仅使用原始的开发模式,开发 [数据库 -> Restful -> 前端] 即可。这样开发人员的技术门槛就降低了,培训成本也降低了。框架的部分交给框架工程师去处理,普通程序员 CRUD 就可以了。
    bfbd
        22
    bfbd   64 天前
    @kkeiko

    微服务与微服务之间没有网络调用。

    Distribute Service 把数据写入到目标磁盘上就不管了,微服务自己读数据,自己用。如果需要跨机房,那就是 Distribute Service 需要跨机房把数据远程写进去,写不进去就先用旧数据将就,直到故障排除。
    xsen
        23
    xsen   64 天前   ❤️ 1
    明明本来就非常简单的时期,非得做成那么复杂
    还有,你说的这些不就是 mq 或者 rpc 的机制么?建议你了解下 grpc 先,然后再反省反省你的这个思路

    用传统的数据库做消息机制,亏你想的出来
    iisky1121
        24
    iisky1121   64 天前   ❤️ 1
    这个,我看懂了,其实就是把面向数据库编程变成面向缓存数据编程嘛,难道记忆 key 的名称不是一个门槛吗?最后会发现相似的业务,会出现不同的 key 缓存,而且开发人员还相互不知道,那么你用 MQ 机制来修改缓存的时候,你怎么知道要修改哪一些 key 的缓存值?
    stevenkang
        25
    stevenkang   64 天前   ❤️ 1
    数据写入硬盘,让其他微服务读硬盘。

    数据写入数据库,让其他微服务读数据库。

    明显后者具有优势吧。
    miao1007
        26
    miao1007   64 天前 via iPhone   ❤️ 1
    cps 技术嘛
    fcten
        27
    fcten   64 天前   ❤️ 1
    有一个微服务提供十亿用户信息的查询服务,如何实现数据缓存?
    退一步,假设读的问题解决了,那么写呢?要更新用户信息的时候怎么做?
    CoderGeek
        28
    CoderGeek   64 天前   ❤️ 1
    你是想说弱数据库 弱依赖吧...
    fcten
        29
    fcten   64 天前   ❤️ 1
    更新一下,看到前面楼主回复说写入操作只在服务内部,只能说楼主根本不了解微服务。
    首先,微服务不能承担从接入层网关到数据库整条链路的操作,这种设计无法实现高可用。接入层,业务层,数据层是非常常见的分层模式。
    再者,举个例子:在金融系统中,更新余额是一个几乎任何功能都会用到的操作,难道我们把所有这些功能都写到同一个服务中?你能想象支付宝把所有与更新余额相关的功能都写在一个服务里吗?
    bfbd
        30
    bfbd   61 天前
    @xsen

    grpc 也是 RPC 的一种吧。就像楼上有朋友讲的,如果被调用的服务挂了,就返回错误。没错,这样是对的。但有些时候,在数据一致性要求没那么高的情况下,其实可以不返回错误,用旧版的数据继续服务的。grpc 好像实现不了这个目的。

    至于说何时不要求那么高的数据一致性,这就看如何进行领域的划分了,只能具体问题具体分析。
    bfbd
        31
    bfbd   61 天前
    @iisky1121

    在 Generic Rest API 一文里,查询数据的 url 就可以作为 key,当然 key 太长了可以 hash 一下,ETag Cache Service 里就是这么用的。
    bfbd
        32
    bfbd   61 天前
    @stevenkang

    直接写入数据库的方案的确考虑过,但有两点顾虑:一是数据的频繁更新是否会给数据库造成负担,影响数据库响应其他请求的效率,二是数据库里新旧版本数据的替换速度如何?磁盘文件可以先写个新的,然后交换下文件名,再把旧的删掉。这样数据文件仅改名期间不可用,是瞬时的。

    当然,写磁盘文件也有缺点,数据库自带的缓存优化就失效了,查询速度可能受影响。这一点要想改进,可能就得自己打造定制版的 file_fdw 插件了。
    bfbd
        33
    bfbd   61 天前
    @miao1007

    CPS 不了解,有空学习下,谢谢。
    bfbd
        34
    bfbd   61 天前
    @CoderGeek

    差不多吧,主要目的是隔离。
    或者也可以看成是多个子系统,而不是多个微服务,后面可以在子系统内部再进行分层的微服务划分。
    bfbd
        35
    bfbd   61 天前
    @fcten

    海量数据的缓存问题在 [Series data cache] 这篇里有提(好像贴不了网址)。
    核心思想就是用某个键值做分块,然后分块缓存,这个参考的 Redis 分布式策略。

    所有的缓存都是面向结果的,换句话说,缓存的就是查询结果。
    如果查询结果的组合有一千种,就要生成一千份缓存。理论上就是这样的。
    但实际上可以二八法则,80% 的常用查询结果能命中缓存就可以了,剩下的 20% 慢点也没关系,可以待预算充足时再加以补足。

    更新的时候就是更新了哪个块,就触发哪个块的缓存替换过程。
    举个例子:十亿用户分成一千块,查询条件一百种。改了两个用户,触发两个块的缓存更新,就要修改两百个缓存文件。就是这样。
    bfbd
        36
    bfbd   61 天前
    @fcten

    是的,的确不能把一整套逻辑都写到一个服务里。
    不过微服务里也提到说微服务的划分是可以嵌套的,如果按嵌套的方式去设计呢?

    比如:先按领域划分大块,就把你说的更新余额相关的,要求实时响应和数据一致的部分,都划分到一个领域,然后再在这个领域里,划分出多个层级的微服务。
    至于其他的领域,应该与这个更新余额相关的领域没有强依赖,没有强相关,可以容忍一定程度的异步延迟。
    fishioon
        37
    fishioon   61 天前   ❤️ 1
    哈哈,有意思的想法;楼主再换个思路想想,读取结果数据总是需要调用 API 的对不对?这个 API 可以是一个读取本地文件 /远程数据库 /远程服务等等;如果是远程服务,那这个 API 就是一个 RPC 调用了,又回到了微服务模式;另外数据总是多变的,而接口可以保持相对稳定
    bfbd
        38
    bfbd   58 天前
    @fishioon

    读取结果数据是 Distribute Service 集中调用 API,成功失败无所谓。
    如果成功了,更新缓存数据供其他微服务使用。
    如果失败了,记录失败信息,供系统管理员参考,有点儿类似于微服务的健康监控。

    所以,也可以看作是在微服务与微服务之间的直接调用路径中加了个间接层,这个间接层顺便还实现了微服务的可用性监控。
    关于   ·   FAQ   ·   API   ·   我们的愿景   ·   广告投放   ·   感谢   ·   实用小工具   ·   1849 人在线   最高记录 5168   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 27ms · UTC 03:27 · PVG 11:27 · LAX 20:27 · JFK 23:27
    ♥ Do have faith in what you're doing.