使用Quarkus与Infinispan缓存构建弹性购物车服务:逐步指南
什么是Infinispan
Infinispan是一个开源内存数据库,可存储几乎任何类型的数据,从纯文本到结构化对象。通过Infinispan强大的全文和向量搜索能力,以闪电般的速度检索数据。通过将数据分布在弹性可扩展的Infinispan集群中,保证可靠性和完整性,这些集群提供高可用性、容错能力以及在多个地理站点之间复制的能力。可以使用原生高性能协议或选择的Redis或Memcached客户端连接到Infinispan。
Infinispan是设置分布式缓存的绝佳工具,使用Quarkus可以增强这种体验。本文是使用Quarkus和Infinispan开发购物车服务的全面演练。如果您是希望快速创建跨集群和站点的Kubernetes友好多核缓存系统的开发人员,请继续阅读。
购物车服务
购物车是一个简单的服务,此时创建一个购物车并保存0个或多个产品。
1
2
3
4
5
6
7
8
9
|
{
"cartItemTotal":0.0,
"cartItemPromoSavings":0.0,
"shippingTotal":0.0,
"shippingPromoSavings":0.0,
"cartTotal":0.0,
"cartId":"1fa842e1-077a-4c09-85c5-a12288c2be7e",
"cartItemList":[]
}
|
像这样的简单GET调用:http://localhost:8084/api/cart/v2/1fa842e1-077a-4c09-85c5-a12288c2be7e
(其中插入CartID)将创建并初始化一个新购物车。
coolstore-fe使用uuidv4为每个浏览器实例生成uuid。每次创建新的coolstore-fe实例时,它将生成一个uuid并调用cart-service以初始化购物车(0个项目)。
类图
购物车服务中的类架构和关系如下:
CartServiceImpl管理购物车项目、促销和运费计算之间的交互。
在单站点上部署
以下说明假设已预安装infinispan operator。如果只想在一个实例上使用购物车,要在没有备份的nyc站点上使用缓存,请在命名空间中创建以下CR:
1
2
3
4
5
6
7
8
9
|
apiVersion: infinispan.org/v1
kind: Infinispan
metadata:
name: nyc
namespace: nyc
spec:
replicas: 2
expose:
type: Route
|
在DG/Infinispan服务器中创建缓存,按Next并加载以下缓存配置:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
{
"distributed-cache": {
"mode": "SYNC",
"owners": 2,
"statistics": true,
"encoding": {
"media-type": "application/x-protostream"
},
"locking": {
"isolation": "REPEATABLE_READ"
}
}
}
|
application.properties定义以下属性以连接到DG/Infinispan集群:
1
2
3
4
5
6
|
%prod.quarkus.infinispan-client.hosts=nyc.nyc.svc.cluster.local:11222
%prod.quarkus.infinispan-client.use-auth=true
%prod.quarkus.infinispan-client.username=developer
%prod.quarkus.infinispan-client.password=XXXXXX
%prod.quarkus.infinispan-client.trust-store=/var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt
%prod.quarkus.infinispan-client.trust-store-type=pem
|
确保在cart-service中配置了正确的主机和密码。本节还描述了如何获取这些详细信息。
设置完成后,现在可以通过发出以下命令将cart-service实例部署到OpenShift:
1
|
mvn clean compile package -Dquarkus.kubernetes.deploy=true
|
部署后,通过curl调用cart-service:
1
2
|
curl -X GET http://CART-SERIVCE-ROUTE/api/cart/v2/1fa842e1-077a-4c09-85c5-a12288c2be77
{"cartItemTotal":0.0,"cartItemPromoSavings":0.0,"shippingTotal":0.0,"shippingPromoSavings":0.0,"cartTotal":0.0,"cartId":"1fa842e1-077a-4c09-85c5-a12288c2be77","cartItemList":[]}
|
Infinispan跨站点集群部署
Infinispan中的跨站点集群是一项功能,可在多个地理分布的集群或数据中心之间实现数据复制和同步。跨站点集群的主要目标是确保在单个集群或数据中心发生故障或灾难时的数据可用性和容错能力。通过跨多个站点复制数据,Infinispan提供冗余,即使一个或多个站点不可用,也能实现数据的高可用性。
以下是Infinispan中跨站点集群工作方式的高级概述:
- 数据中心和集群:Infinispan允许设置多个数据中心,每个数据中心由一个或多个集群组成。集群代表一组协同工作以存储和处理数据的节点(服务器)。
- 站点和备份站点:在跨站点集群中,每个数据中心通常与一个站点关联,每个站点内可以有一个或多个集群。此外,其中一个站点被指定为备份站点。
- 数据复制:Infinispan在同一站点内的集群之间以及不同站点的集群之间复制数据。这种复制通常是异步的,意味着在一个站点进行的更新最终会传播到其他站点。
- 复制模式:Infinispan支持跨站点集群的不同复制模式,包括同步和异步模式。在同步模式下,更新在确认操作成功之前立即复制到其他站点。在异步模式下,更新在后台发送到其他站点,无需等待确认。
- 一致性模型:Infinispan还提供各种一致性模型来控制跨站点的数据一致性级别。例如,强一致性确保所有站点以相同顺序看到更新,而最终一致性允许一定程度的不一致性以提高性能。
- 故障检测和恢复:Infinispan监控节点、集群和站点的运行状况和可用性。如果站点或集群变得不可用,Infinispan自动将读写操作重定向到备份站点或其他可用集群,以确保数据访问和持续操作。
通过利用Infinispan中的跨站点集群,可以实现改进的数据可用性、灾难恢复和容错能力。它允许将数据分布在多个位置,确保即使一个站点或集群宕机,数据仍可从其他站点访问并保持一致。
本节是在跨站点集群中部署购物车的指南。
NYC LON
部署CRs
一个数据中心中的Infinispan Operator可以发现另一个数据中心中另一个Infinispan Operator管理的Infinispan集群。这种发现允许Infinispan自动形成跨站点视图并创建全局集群。
以下插图提供了一个示例,其中Infinispan Operator管理纽约市(NYC)数据中心的Infinispan集群。在伦敦(LON)的另一个数据中心,另一个Infinispan Operator也管理一个Infinispan集群。
Infinispan Operator使用Kubernetes API在NYC和LON的OpenShift Container Platform集群之间建立安全连接。然后Infinispan Operator创建跨站点复制服务,以便Infinispan集群可以跨位置备份数据。
每个Infinispan集群有一个站点主节点,协调所有备份请求。Infinispan Operator识别站点主节点,以便所有通过跨站点复制服务的流量都流向站点主节点。
假设Infinispan或Infinispan operator已安装在集群或两个命名空间中。运行以下CRs:
命名空间:nyc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
apiVersion: infinispan.org/v1
kind: Infinispan
metadata:
name: nyc
spec:
replicas: 2
expose:
type: Route
logging:
categories:
org.jgroups.protocols.TCP: error
org.jgroups.protocols.relay.RELAY2: error
service:
container:
storage: 1Gi
sites:
local:
expose:
type: ClusterIP
name: NYC
locations:
- clusterName: lon
name: LON
namespace: lon
secretName: lon-token
url: 'infinispan+xsite://lon-site.user1-cache2.svc:7900'
type: DataGrid
|
命名空间:lon
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
apiVersion: infinispan.org/v1
kind: Infinispan
metadata:
name: lon
spec:
replicas: 2
expose:
type: Route
logging:
categories:
org.jgroups.protocols.TCP: error
org.jgroups.protocols.relay.RELAY2: error
service:
container:
storage: 1Gi
sites:
local:
expose:
type: ClusterIP
name: LON
locations:
- clusterName: nyc
name: NYC
namespace: nyc
secretName: lon-token
url: 'infinispan+xsite://nyc-site.nyc.svc:7900'
type: DataGrid
|
要创建跨越NYC和LON的缓存,通过登录DG/Infinispan控制台在NYC集群中创建以下缓存。
有关已部署集群的详细信息,请按Next并加载以下缓存配置:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
{
"carts": {
"replicated-cache": {
"mode": "SYNC",
"remote-timeout": "17500",
"statistics": true,
"backups": {
"LON": {
"backup": {
"strategy": "ASYNC",
"take-offline": {
"min-wait": "120000"
}
}
}
},
"encoding": {
"media-type": "application/x-protostream"
},
"locking": {
"concurrency-level": "1000",
"acquire-timeout": "15000",
"striping": false
},
"state-transfer": {
"timeout": "60000"
}
}
}
}
|
以下内容也以类似方式在LON DG/Infinispan控制台中创建:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
{
"carts": {
"distributed-cache": {
"mode": "SYNC",
"backups": {
"NYC": {
"backup": {
"strategy": "SYNC",
"take-offline": {
"min-wait": "120000"
}
}
}
},
"encoding": {
"media-type": "application/x-protostream"
}
}
}
}
|
在LON备份站点中创建新模式。使用以下配置。
Proto模式:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
// File name: CartContextInitializer.proto
// Generated from : org.coolstore.cart.cache.CartContextInitializer
syntax = "proto2";
package coolstore;
message Product {
optional string itemId = 1;
optional string name = 2;
optional string desc = 3;
optional double price = 4 [default = 0.0];
}
message Cart {
optional string cartId = 1;
repeated CartItem cartItemList = 2;
optional double cartItemTotal = 3 [default = 0.0];
optional double shippingTotal = 4 [default = 0.0];
optional double cartTotal = 5 [default = 0.0];
optional double cartItemPromoSavings = 6 [default = 0.0];
optional double shippingPromoSavings = 7 [default = 0.0];
}
message CartItem {
optional Product product = 1;
optional double price = 2 [default = 0.0];
optional int32 quantity = 3 [default = 0];
optional double promoSavings = 4 [default = 0.0];
}
message Promotion {
optional string itemId = 1;
optional double percentOff = 2 [default = 0.0];
}
|
完成以上所有操作后,现在通过发出以下命令将cart-service实例部署到OpenShift:
1
|
mvn clean compile package -Dquarkus.kubernetes.deploy=true
|
部署后,通过curl调用cart-service:
1
2
|
curl -X GET http://CART-SERIVCE-ROUTE/api/cart/v2/1fa842e1-077a-4c09-85c5-a12288c2be77
{"cartItemTotal":0.0,"cartItemPromoSavings":0.0,"shippingTotal":0.0,"shippingPromoSavings":0.0,"cartTotal":0.0,"cartId":"1fa842e1-077a-4c09-85c5-a12288c2be77","cartItemList":[]}
|
此时,写入NYC缓存的所有内容也将直接复制到LON。
微服务的分布式特性在与外部系统进行可靠通信方面提出了挑战,更加注重应用程序的弹性。为了促进开发更具弹性的应用程序,Quarkus提供了Smallrye Fault Tolerance,它实现了MicroProfile Fault Tolerance规范。
在我们的主站点不响应的情况下,可以使用此扩展回退到备份站点。
在CartServiceImpl中:
- 注入两个客户端site-nyc、site-lon
- 添加一个maybefail方法,随机(boolean)通过随机抛出RuntimeException模拟故障
- 需要添加fallbackCache(String cartId)以在服务回退时调用代码
- getShoppingCart(String cartId)也需要更改以添加注解来回退并调用我们的随机maybefail方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
|
// primary
@Inject
@InfinispanClientName("site-nyc")
@Remote(CART_CACHE)
RemoteCache<String, Cart> carts;
@Inject
@InfinispanClientName("site-lon")
@Remote(CART_CACHE)
RemoteCache<String, Cart> cartsBkp;
private void maybeFail(String failureLogMessage) {
if (new Random().nextBoolean()) {
log.error(failureLogMessage);
throw new RuntimeException("Resource failure.");
}
}
public Cart fallbackCache(String cartId) {
log.info("Falling back to the backup cache...");
// safe bet, return something that everybody likes
if (!cartsBkp.containsKey(cartId)) {
Cart cart = new Cart(cartId);
cartsBkp.put(cartId, cart);
return cart;
}
Cart cart = cartsBkp.get(cartId);
priceShoppingCart(cart);
cartsBkp.put(cartId, cart);
return cart;
}
@Override
@Fallback(fallbackMethod = "fallbackCache")
public Cart getShoppingCart(String cartId) {
maybeFail("trying to failover.....");
if (!carts.containsKey(cartId)) {
Cart cart = new Cart(cartId);
carts.put(cartId, cart);
return cart;
}
Cart cart = carts.get(cartId);
priceShoppingCart(cart);
carts.put(cartId, cart);
return cart;
}
|
完美,现在服务已准备好在NYC和LON之间回退。它还需要部署。
application.properties定义以下属性以连接到DG/Infinispan集群:
1
2
3
4
5
6
7
8
9
10
11
|
%prod.quarkus.infinispan-client.site-nyc.hosts=nyc.nyc.svc.cluster.local:11222
%prod.quarkus.infinispan-client.site-nyc.username=developer
%prod.quarkus.infinispan-client.site-nyc.password=XXXXXXXXXX
%prod.quarkus.infinispan-client.site-nyc.trust-store=/var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt
%prod.quarkus.infinispan-client.site-nyc.trust-store-type=pem
%prod.quarkus.infinispan-client.site-lon.hosts=lon.lon.svc.cluster.local:11222
%prod.quarkus.infinispan-client.site-lon.username=developer
%prod.quarkus.infinispan-client.site-lon.password=XXXXXXXXXX
%prod.quarkus.infinispan-client.site-lon.trust-store=/var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt
%prod.quarkus.infinispan-client.site-lon.trust-store-type=pem
|
完成以上所有操作后,现在通过发出以下命令将cart-service实例部署到OpenShift:
1
|
mvn clean compile package -Dquarkus.kubernetes.deploy=true
|
部署后,通过curl调用cart-service:
1
|
curl -X GET http://CART-SERIVCE-ROUTE/api/cart/v2/1fa842e1-077a-4c09-85c5-a12288
|