服务互动
服务可以支持许多简单的交互。 哪种模式最适合您的应用程序,取决于现有应用程序的期望以及您对延迟的要求。 从广义上讲,这些交互属于客户端-服务器和点对点消息传递。
对于点对点消息传递,采用的一种方法是Lambda Architecture,但是从支持GUI的角度来看,客户端-服务器模型可以更轻松地使用。
我认为,设计可整体测试和调试的组件非常重要,就像在一个整体中一样,但是可以作为多个正确大小的服务部署到不同的线程,JVM或机器上。
客户端-服务器消息传递
客户端-服务器消息传递通常以同步方式使用。 异步消息传递可以支持多个并发请求,从而减少了网络延迟的成本并提高了吞吐量。 异步消息传递在延迟方面表现最好,而不是限制您的网络带宽。
使用人类可读的协议
能够以可读文本查看底层消息是很有用的。 我建议使用文本或二进制格式,该格式可以自动转换为文本,以便于检查发送的数据。 我建议使用YAML作为人类可读格式;
- 它被设计为人类可读的,而不是另一种语言/格式的子集。
- 它支持消息,类型和注释。
- 它可以以二进制形式发送以提高性能,并根据需要转换为文本
人类可读的格式使您可以快速确定行为不正确的是发送方还是接收方。 它可以让您查看并阻止测试中未出现的不良行为。 例如,心跳过多。
请求/响应
客户端将消息发送到服务器。 该消息包含消息类型以选择要在服务器上执行的操作,并且通常包含数据的有效负载。 响应通常只是数据。
对于客户端发送给服务器的每条消息,它都会用一条消息进行响应。 客户端通常会等待响应,但是客户端可以异步处理响应以提高吞吐量。
通过异步响应处理,客户端可以在同一通道上发送多个请求,而不必等待每个请求完成。
带有同步客户端的同步服务器
interface OneRequestResponse {
Response requestType(RequestData data);
}
带有异步客户端的同步服务器
interface OneRequestResponse2 {
void requestType(RequestData data, Consumer<Response> responseConsumer);
}
使用此API可以转换为YAML,例如
具有同步或异步客户端的同步服务器
# client sends to server
---
requestType: {
data: 1,
text: my text
}
# server sends to client
---
reply: !MyResponse {
moreData: 128,
message: Success
}
...
请求/代理
这类似于请求/响应,只是返回的对象是进一步事件的代理。 当返回的值表示一个复杂或非常大的对象时,这很有用。
用于Map返回键集或值的映射,该键集或值是基础映射的代理。
java.util.Map上的方法
public interface Map<K, V> {
Set<K> keySet();
Set<Map.Entry<K, V>> entrySet();
Collection<V> values();
}
使用代理可以访问数据,而不必将所有数据从服务器传递到客户端。
java.util.Map上的方法
# client sends to server
--- !!meta-data # binary
csp: /map/my-map?view=map
---
keySet: []
---
entrySet: []
---
values: []
# server sends to client
---
reply: !set-proxy {
csp: /map/my-map?view=keySet
}
---
reply: !set-proxy {
csp: /map/my-map?view=entrySet
}
---
reply: !set-proxy {
csp: /map/my-map?view=values
}
# client sends to server
--- !!meta-data # binary
csp: /map/my-map?view=keySet
--- !data # binary
size: []
---
# server sends to client
---
reply: 128000 (1)
# client sends to server
--- !!meta-data # binary
csp: /map/my-map?view=keySet
--- !data # binary
remove: "key-111"
---
# server sends to client
---
reply: true (2)
...
1个 | 无需发送128,000个密钥即可确定其中有多少个。 |
2 | 密钥已在服务器上删除,而不是发送给客户端的副本。 |
请求/回叫
客户端将消息发送到服务器。 该消息包含用于在服务器上调用操作的信息,并且通常包含数据的有效负载。 回调也是一条包含动作和数据的消息。
这类似于订阅,只是预期将返回一个事件。
回调的使用在调用方和被调用方之间提供了更丰富的交互。
带有回调的同步服务器
interface OneCallback {
void resultOne(ResultOne result);
void resultTwo(List<ResultOne> results);
void errorResult(String message);
}
interface OneRequestCallback {
void requestType(RequestData data, OneCallback callback);
}
可以将客户端配置为等待服务器调用回调,或者异步处理回调。 在回调中执行该方法的线程将在客户端。
带有回调的同步服务器
# client sends to server
---
requestType: {
data: 1,
text: my text
}
# server sends to client
---
resultTwo: [
{
moreData: 128,
message: Success
},
{
moreData: 1111,
message: Failure
}
}
...
请求/访问者
客户端将一到两个访问者发送到服务器以应用于本地对象或参与者。 该访问者可以是原子地应用于演员的更新,和/或访问者可以用于检索特定信息。
传递给给定密钥的功能以在服务器上应用
interface KeyedResources<V> {
void asyncUpdate(String key, Visitor<V> vistor);
<R> R syncUpdate(String key, Visitor<V> updater, Function<V, R> returnFunction);
}
这种方法允许调用者将操作应用于演员,而无需知道该演员在哪里。
传递给给定密钥的功能以在服务器上应用
# client sends to server
---
asyncUpdate: [
"key-5",
!MyVisitor { add: 10 }
]
# no return value
--- # subtract 3 and return x * x
syncUpdate: [
"key-6",
!MyVisiitor { add: -3 },
!Square { }
];
# server sends to client
---
reply: 1024
...
请求/订阅
通过请求订阅,客户端可以接收多个异步事件。 这可以从现有信息的引导开始,然后是实时更新。
订阅完成后,应更改或取消订阅
传递给给定密钥的功能以在服务器上应用
interface Queryable<E> {
<R> Subscription<E, R> subscribe(Filter<E> filter, Function<E, R> returnMapping, Subscriber<R> subscriber);
}
interface Subscription<R> {
// change the current filter.
void setFilter(Filter<E> newFilter);
void cancel();
}
到目前为止,所有消息都是带有单个响应的操作。 在Chronicle-Engine中,我们为每个csp
关联一个csp
或Chronicle服务路径,以及每个操作的tid
或Transaction ID。 这允许对不同参与者进行多个并发操作。 此路由信息以元数据的形式传递,其后针对该目标的操作
传递给给定密钥的功能以在服务器上应用
# client sends server
--- !!meta-data # binary
csp: /maps/my-map
tid: 12345
--- !!data # binary
subscribe: [
!MyFilter { field: age, op: gt, value: 18 },
!Getter { field: name }
]
request: 2 # only send me two events for now.
# server sends client
--- !!meta-data # binary
tid: 12345
--- !data-not-complete # binary
reply: Steve Jobs
--- !data-not-complete # binary
reply: Alan Turing
# client sends server
--- !!meta-data # binary
tid: 12345
--- !data # binary
cancel: []
# server sends client
--- !!meta-data # binary
tid: 12345
--- !data # binary
cancelled: "By request"
...
客户端注入处理程序
此方法允许客户端代表客户端在服务器上使用版本和配置哪些处理程序。 特别是在同时支持多个版本的客户端时,这很有用。
客户端传递一个处理程序以与服务器进行交互并代表该服务器
interface AcceptsHandler {
/**
* The accept method takes a handler to pass to the server.
* and it returns a proxy it can call to invoke that hdnler on the server.
*/
<H extends ContextAcceptor> H accept(H handler);
}
我们使用的处理程序的一个简单示例是心跳
# client sends server
--- !!meta-data # binary
csp: /
cid: 1
handler: !HeartbeatHandler {
heartbeatTimeoutMs: 10000
heartbeatIntervalMs: 2000
}
...
这允许不同的客户端同时使用不同版本的心跳处理程序,并通过单个服务器支持新旧客户端。 |
结论
除了用于后端,点对点服务的Lambda体系结构模型外,我们还可以支持客户端和服务器之间的丰富交互。
这些交互可以在没有传输的情况下执行,即一个组件直接调用另一个组件可以使测试和调试更加容易。
翻译自: https://www.javacodegeeks.com/2016/05/modelling-microservice-patterns-code.html