全文链接如下:
https://aiportal.github.io/data-driven-service
简单说就是: 每个微服务仅可引用另一个微服务释放出来的数据缓存,不允许直接调用另一个微服务。至于数据缓存何时更新,完全取决于维护该部分数据的那个微服务。
优点: 微服务与微服务之间没有直接调用,不会互相影响,即使某一个或某几个微服务当掉了,其他微服务仍可使用旧版的缓存数据继续运行。
缺点: 设计和划分微服务的时候难度较大,必须让各个微服务之间不依赖同步调用,仅依赖异步数据。
补足: 特殊情况必须做同步调用时,可以用消息队列加等待异步操作完成的伪同步来实现同步操作。
     1 
                    
                    lhx2008      2020-04-03 10:10:57 +08:00 via Android    不知道这个概念是楼主发明的还是。。那写入操作怎么办呢 
                 | 
            
     2 
                    
                    hebin      2020-04-03 10:16:12 +08:00 
                    
                    这个优点解决的问题现有的方案应该也能解决。 但是这个缺点就很明显了 
                 | 
            
     3 
                    
                    p2pCoder      2020-04-03 10:18:17 +08:00    事务性系统,几乎没法用 
                 | 
            
     4 
                    
                    EmdeBoas      2020-04-03 10:22:39 +08:00    一个服务应该有两层:无状态的应用层和有状态的存储层,前者根本不存在挂,有问题替换就行;后者挂了才是真的挂;所以每个微服务的有状态层需要隔离开来;你这样所有服务都去查缓存,存在的问题很致命: 
                1. 数据都在同一个有状态的服务上,有状态的服务一旦出现问题全都给挂 2. 数据写入 /读取的一致性问题 3. 一刀切使用旧版数据完全脱离业务,很多业务并不能接受过期的数据  | 
            
     5 
                    
                    gemini767      2020-04-03 10:32:17 +08:00     | 
            
     6 
                    
                    kindjeff      2020-04-03 10:36:51 +08:00    槽点有点多吧。上面说的事务性系统没法用之外,即使是逻辑最简单的系统,只要是写多读少,就退化成了没有缓存;如果是读多写少,在调用方那边做缓存不是更简单么,你提到的优点也是一样有。 
                 | 
            
     7 
                    
                    fox0001      2020-04-03 10:37:41 +08:00 via Android    只读的话,CDN 是不是更简单? 
                 | 
            
     8 
                    
                    guolaopi      2020-04-03 10:40:15 +08:00 
                    
                    MVVM ? 
                整出了 Vue 的感觉  | 
            
     10 
                    
                    iisky1121      2020-04-03 10:56:18 +08:00    共享缓存,瓶颈不就是在缓存里面了吗? 
                 | 
            
     11 
                    
                    xuanbg      2020-04-03 12:11:17 +08:00    不客气地说,这个主意真的很糟糕。问题楼上提了一大堆了,我也就不重复了。我来给楼主一个建议,也把我在项目的实践和大家共享一下。 
                楼主要解决的问题其实是服务不可用时,如何保持数据一致性的问题。 其实这个问题要一分为二地来看,一是必须保证成功的调用,譬如我钱都付了,你不能让我的订单状态还处于待付款状态。二是可以失败的调用,譬如商品中心服务突然调不通了,无法确定商品价格,这个时候就可以返回前端下单失败,让客户等会再来下单。 针对第一种情况,可以用消息队列进行对接。支付中心作为生产者,在客户付款成功后发布一条付款成功的消息到队列,订单服务作为消费者订阅这个队列就行了。如果你担心更新的时候数据库炸了导致更新失败的话,在更新数据发生异常的时候把消息发到延时队列进行重试就好了。 至于第二种情况,那就很简单了,当服务调用失败的时候返回一个错误就行了。一级一级返回回去,用户端总会收到错误消息的。  | 
            
     12 
                    
                    q8164305      2020-04-03 12:13:40 +08:00 via Android    有 mvvm 那味了 
                 | 
            
     13 
                    
                    nicebird      2020-04-03 14:03:05 +08:00    适合特定只读场景 
                 | 
            
     14 
                    
                    wellsc      2020-04-03 14:06:08 +08:00    微服务是 stateless 的吧,你这释放缓存的方案,感觉更像是中间件层面的东西了 
                 | 
            
     15 
                    
                    TransAM      2020-04-03 14:11:24 +08:00 via Android    我觉得缓存和不缓存对于调用方最好是透明的,被调用方自己处理缓存的事情,调用方拿着 uri 访问就行。 
                 | 
            
     16 
                    
                    bfbd   OP 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 服务的更新操作成功返回前,该事务应该提供的缓存数据,也已经部署完毕。  | 
            
     18 
                    
                    kkeiko      2020-04-03 15:30:57 +08:00    你就告诉我,缓存用啥吧。然后用你说的方案解决一下交易问题,就支付宝目前的业务模型。中间有任何一次非本机房的网络调用出问题怎么办? 
                 | 
            
     19 
                    
                    bfbd   OP @EmdeBoas  
                1. 数据只是由一个有状态的服务 (Distribute Service) 负责更新,但数据本身是磁盘上的文件,所以服务挂了没关系,硬盘没挂就可以。 2. 数据的写入,数据的完整性,都由拥有该数据的服务负责,其他服务只能读取。外部服务只能通过消息队列影响本服务的数据。 3. 是的,例如修改密码的操作,用户信息和登录验证层可以是两个服务,总不能让用户无限期等待新密码缓存部署。所以这一块设计了 sync 机制,在密码修改操作返回之前,验证操作所需的缓存就已经部署好了。 这部分内容参见:ETag 缓存服务设计。  | 
            
     21 
                    
                    bfbd   OP @xuanbg  
                是的,消息队列和异步操作可以解决微服务间紧密耦合的问题。 的确是想解决服务间耦合依赖的问题,同时也想降低系统向微服务迁移的难度。 用数据层缓存的方式,可以让程序员不关心微服务调用,不处理微服务依赖,仅使用原始的开发模式,开发 [数据库 -> Restful -> 前端] 即可。这样开发人员的技术门槛就降低了,培训成本也降低了。框架的部分交给框架工程师去处理,普通程序员 CRUD 就可以了。  | 
            
     22 
                    
                    bfbd   OP @kkeiko  
                微服务与微服务之间没有网络调用。 Distribute Service 把数据写入到目标磁盘上就不管了,微服务自己读数据,自己用。如果需要跨机房,那就是 Distribute Service 需要跨机房把数据远程写进去,写不进去就先用旧数据将就,直到故障排除。  | 
            
     23 
                    
                    xsen      2020-04-03 15:57:09 +08:00    明明本来就非常简单的时期,非得做成那么复杂 
                还有,你说的这些不就是 mq 或者 rpc 的机制么?建议你了解下 grpc 先,然后再反省反省你的这个思路 用传统的数据库做消息机制,亏你想的出来  | 
            
     24 
                    
                    iisky1121      2020-04-03 16:05:18 +08:00    这个,我看懂了,其实就是把面向数据库编程变成面向缓存数据编程嘛,难道记忆 key 的名称不是一个门槛吗?最后会发现相似的业务,会出现不同的 key 缓存,而且开发人员还相互不知道,那么你用 MQ 机制来修改缓存的时候,你怎么知道要修改哪一些 key 的缓存值? 
                 | 
            
     25 
                    
                    stevenkang      2020-04-03 16:11:33 +08:00    数据写入硬盘,让其他微服务读硬盘。 
                数据写入数据库,让其他微服务读数据库。 明显后者具有优势吧。  | 
            
     26 
                    
                    miao1007      2020-04-03 19:29:44 +08:00 via iPhone    cps 技术嘛 
                 | 
            
     27 
                    
                    fcten      2020-04-03 20:24:48 +08:00    有一个微服务提供十亿用户信息的查询服务,如何实现数据缓存? 
                退一步,假设读的问题解决了,那么写呢?要更新用户信息的时候怎么做?  | 
            
     28 
                    
                    CoderGeek      2020-04-03 20:33:39 +08:00    你是想说弱数据库 弱依赖吧... 
                 | 
            
     29 
                    
                    fcten      2020-04-03 20:39:55 +08:00    更新一下,看到前面楼主回复说写入操作只在服务内部,只能说楼主根本不了解微服务。 
                首先,微服务不能承担从接入层网关到数据库整条链路的操作,这种设计无法实现高可用。接入层,业务层,数据层是非常常见的分层模式。 再者,举个例子:在金融系统中,更新余额是一个几乎任何功能都会用到的操作,难道我们把所有这些功能都写到同一个服务中?你能想象支付宝把所有与更新余额相关的功能都写在一个服务里吗?  | 
            
     30 
                    
                    bfbd   OP @xsen  
                grpc 也是 RPC 的一种吧。就像楼上有朋友讲的,如果被调用的服务挂了,就返回错误。没错,这样是对的。但有些时候,在数据一致性要求没那么高的情况下,其实可以不返回错误,用旧版的数据继续服务的。grpc 好像实现不了这个目的。 至于说何时不要求那么高的数据一致性,这就看如何进行领域的划分了,只能具体问题具体分析。  | 
            
     31 
                    
                    bfbd   OP @iisky1121  
                在 Generic Rest API 一文里,查询数据的 url 就可以作为 key,当然 key 太长了可以 hash 一下,ETag Cache Service 里就是这么用的。  | 
            
     32 
                    
                    bfbd   OP @stevenkang  
                直接写入数据库的方案的确考虑过,但有两点顾虑:一是数据的频繁更新是否会给数据库造成负担,影响数据库响应其他请求的效率,二是数据库里新旧版本数据的替换速度如何?磁盘文件可以先写个新的,然后交换下文件名,再把旧的删掉。这样数据文件仅改名期间不可用,是瞬时的。 当然,写磁盘文件也有缺点,数据库自带的缓存优化就失效了,查询速度可能受影响。这一点要想改进,可能就得自己打造定制版的 file_fdw 插件了。  | 
            
     34 
                    
                    bfbd   OP  | 
            
     35 
                    
                    bfbd   OP @fcten  
                海量数据的缓存问题在 [Series data cache] 这篇里有提(好像贴不了网址)。 核心思想就是用某个键值做分块,然后分块缓存,这个参考的 Redis 分布式策略。 所有的缓存都是面向结果的,换句话说,缓存的就是查询结果。 如果查询结果的组合有一千种,就要生成一千份缓存。理论上就是这样的。 但实际上可以二八法则,80% 的常用查询结果能命中缓存就可以了,剩下的 20% 慢点也没关系,可以待预算充足时再加以补足。 更新的时候就是更新了哪个块,就触发哪个块的缓存替换过程。 举个例子:十亿用户分成一千块,查询条件一百种。改了两个用户,触发两个块的缓存更新,就要修改两百个缓存文件。就是这样。  | 
            
     36 
                    
                    bfbd   OP @fcten  
                是的,的确不能把一整套逻辑都写到一个服务里。 不过微服务里也提到说微服务的划分是可以嵌套的,如果按嵌套的方式去设计呢? 比如:先按领域划分大块,就把你说的更新余额相关的,要求实时响应和数据一致的部分,都划分到一个领域,然后再在这个领域里,划分出多个层级的微服务。 至于其他的领域,应该与这个更新余额相关的领域没有强依赖,没有强相关,可以容忍一定程度的异步延迟。  | 
            
     37 
                    
                    fishioon      2020-04-06 21:18:35 +08:00    哈哈,有意思的想法;楼主再换个思路想想,读取结果数据总是需要调用 API 的对不对?这个 API 可以是一个读取本地文件 /远程数据库 /远程服务等等;如果是远程服务,那这个 API 就是一个 RPC 调用了,又回到了微服务模式;另外数据总是多变的,而接口可以保持相对稳定 
                 |