全文链接如下:
https://aiportal.github.io/data-driven-service
简单说就是: 每个微服务仅可引用另一个微服务释放出来的数据缓存,不允许直接调用另一个微服务。至于数据缓存何时更新,完全取决于维护该部分数据的那个微服务。
优点: 微服务与微服务之间没有直接调用,不会互相影响,即使某一个或某几个微服务当掉了,其他微服务仍可使用旧版的缓存数据继续运行。
缺点: 设计和划分微服务的时候难度较大,必须让各个微服务之间不依赖同步调用,仅依赖异步数据。
补足: 特殊情况必须做同步调用时,可以用消息队列加等待异步操作完成的伪同步来实现同步操作。
1
lhx2008 2020-04-03 10:10:57 +08:00 via Android 1
不知道这个概念是楼主发明的还是。。那写入操作怎么办呢
|
2
hebin 2020-04-03 10:16:12 +08:00
这个优点解决的问题现有的方案应该也能解决。 但是这个缺点就很明显了
|
3
p2pCoder 2020-04-03 10:18:17 +08:00 1
事务性系统,几乎没法用
|
4
EmdeBoas 2020-04-03 10:22:39 +08:00 1
一个服务应该有两层:无状态的应用层和有状态的存储层,前者根本不存在挂,有问题替换就行;后者挂了才是真的挂;所以每个微服务的有状态层需要隔离开来;你这样所有服务都去查缓存,存在的问题很致命:
1. 数据都在同一个有状态的服务上,有状态的服务一旦出现问题全都给挂 2. 数据写入 /读取的一致性问题 3. 一刀切使用旧版数据完全脱离业务,很多业务并不能接受过期的数据 |
5
gemini767 2020-04-03 10:32:17 +08:00 1
|
6
kindjeff 2020-04-03 10:36:51 +08:00 1
槽点有点多吧。上面说的事务性系统没法用之外,即使是逻辑最简单的系统,只要是写多读少,就退化成了没有缓存;如果是读多写少,在调用方那边做缓存不是更简单么,你提到的优点也是一样有。
|
7
fox0001 2020-04-03 10:37:41 +08:00 via Android 1
只读的话,CDN 是不是更简单?
|
8
guolaopi 2020-04-03 10:40:15 +08:00
MVVM ?
整出了 Vue 的感觉 |
10
iisky1121 2020-04-03 10:56:18 +08:00 1
共享缓存,瓶颈不就是在缓存里面了吗?
|
11
xuanbg 2020-04-03 12:11:17 +08:00 1
不客气地说,这个主意真的很糟糕。问题楼上提了一大堆了,我也就不重复了。我来给楼主一个建议,也把我在项目的实践和大家共享一下。
楼主要解决的问题其实是服务不可用时,如何保持数据一致性的问题。 其实这个问题要一分为二地来看,一是必须保证成功的调用,譬如我钱都付了,你不能让我的订单状态还处于待付款状态。二是可以失败的调用,譬如商品中心服务突然调不通了,无法确定商品价格,这个时候就可以返回前端下单失败,让客户等会再来下单。 针对第一种情况,可以用消息队列进行对接。支付中心作为生产者,在客户付款成功后发布一条付款成功的消息到队列,订单服务作为消费者订阅这个队列就行了。如果你担心更新的时候数据库炸了导致更新失败的话,在更新数据发生异常的时候把消息发到延时队列进行重试就好了。 至于第二种情况,那就很简单了,当服务调用失败的时候返回一个错误就行了。一级一级返回回去,用户端总会收到错误消息的。 |
12
q8164305 2020-04-03 12:13:40 +08:00 via Android 1
有 mvvm 那味了
|
13
nicebird 2020-04-03 14:03:05 +08:00 1
适合特定只读场景
|
14
wellsc 2020-04-03 14:06:08 +08:00 1
微服务是 stateless 的吧,你这释放缓存的方案,感觉更像是中间件层面的东西了
|
15
TransAM 2020-04-03 14:11:24 +08:00 via Android 1
我觉得缓存和不缓存对于调用方最好是透明的,被调用方自己处理缓存的事情,调用方拿着 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 1
你就告诉我,缓存用啥吧。然后用你说的方案解决一下交易问题,就支付宝目前的业务模型。中间有任何一次非本机房的网络调用出问题怎么办?
|
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 1
明明本来就非常简单的时期,非得做成那么复杂
还有,你说的这些不就是 mq 或者 rpc 的机制么?建议你了解下 grpc 先,然后再反省反省你的这个思路 用传统的数据库做消息机制,亏你想的出来 |
24
iisky1121 2020-04-03 16:05:18 +08:00 1
这个,我看懂了,其实就是把面向数据库编程变成面向缓存数据编程嘛,难道记忆 key 的名称不是一个门槛吗?最后会发现相似的业务,会出现不同的 key 缓存,而且开发人员还相互不知道,那么你用 MQ 机制来修改缓存的时候,你怎么知道要修改哪一些 key 的缓存值?
|
25
stevenkang 2020-04-03 16:11:33 +08:00 1
数据写入硬盘,让其他微服务读硬盘。
数据写入数据库,让其他微服务读数据库。 明显后者具有优势吧。 |
26
miao1007 2020-04-03 19:29:44 +08:00 via iPhone 1
cps 技术嘛
|
27
fcten 2020-04-03 20:24:48 +08:00 1
有一个微服务提供十亿用户信息的查询服务,如何实现数据缓存?
退一步,假设读的问题解决了,那么写呢?要更新用户信息的时候怎么做? |
28
CoderGeek 2020-04-03 20:33:39 +08:00 1
你是想说弱数据库 弱依赖吧...
|
29
fcten 2020-04-03 20:39:55 +08:00 1
更新一下,看到前面楼主回复说写入操作只在服务内部,只能说楼主根本不了解微服务。
首先,微服务不能承担从接入层网关到数据库整条链路的操作,这种设计无法实现高可用。接入层,业务层,数据层是非常常见的分层模式。 再者,举个例子:在金融系统中,更新余额是一个几乎任何功能都会用到的操作,难道我们把所有这些功能都写到同一个服务中?你能想象支付宝把所有与更新余额相关的功能都写在一个服务里吗? |
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 1
哈哈,有意思的想法;楼主再换个思路想想,读取结果数据总是需要调用 API 的对不对?这个 API 可以是一个读取本地文件 /远程数据库 /远程服务等等;如果是远程服务,那这个 API 就是一个 RPC 调用了,又回到了微服务模式;另外数据总是多变的,而接口可以保持相对稳定
|