Keycloak:专用 Infinispan 集群的远程存储配置

Keycloak: remote-store configuration for dedicated Infinispan cluster

最近,我强化了我的 Keycloak 部署,使用专用的 Infinispan 集群作为 remote-store 为 Keycloak 的各种缓存提供额外的持久层。更改本身相当顺利,尽管在进行此更改后,由于 expired_code 错误消息,我们开始看到很多登录错误:

WARN [org.keycloak.events] (default task-2007) type=LOGIN_ERROR, realmId=my-realm, clientId=null, userId=null, ipAddress=192.168.50.38, error=expired_code, restart_after_timeout=true

此错误消息通常会在短时间内从同一个 IP 地址重复数十次。这似乎是最终用户的浏览器在登录时无限重定向,直到浏览器本身停止循环。

我已经看到各种 GitHub 问题 (https://github.com/helm/charts/issues/8355) 也记录了这种行为,并且一致认为这是由于 Keycloak 集群无法正确发现其成员造成的JGroups.

当您考虑到某些 Keycloak 缓存分布在 standalone-ha.xml 内默认配置中的 Keycloak 节点时,这种解释是有道理的。但是,我已将这些缓存修改为本地缓存,其中 remote-store 指向我的新 Infinispan 集群,我相信我对它的工作原理做出了一些错误的假设,导致此错误开始发生。

这是我的 Keycloak 缓存的配置方式:

<subsystem xmlns="urn:jboss:domain:infinispan:7.0">
    <cache-container name="keycloak" module="org.keycloak.keycloak-model-infinispan">
        <transport lock-timeout="60000"/>
        <local-cache name="realms">
            <object-memory size="10000"/>
        </local-cache>
        <local-cache name="users">
            <object-memory size="10000"/>
        </local-cache>
        <local-cache name="authorization">
            <object-memory size="10000"/>
        </local-cache>
        <local-cache name="keys">
            <object-memory size="1000"/>
            <expiration max-idle="3600000"/>
        </local-cache>
        <local-cache name="sessions">
            <remote-store cache="sessions" remote-servers="remote-cache" fetch-state="false" passivation="false" preload="false" purge="false" shared="true">
                <property name="rawValues">
                    true
                </property>
                <property name="marshaller">
                    org.keycloak.cluster.infinispan.KeycloakHotRodMarshallerFactory
                </property>
            </remote-store>
        </local-cache>
        <local-cache name="authenticationSessions">
            <remote-store cache="authenticationSessions" remote-servers="remote-cache" fetch-state="false" passivation="false" preload="false" purge="false" shared="true">
                <property name="rawValues">
                    true
                </property>
                <property name="marshaller">
                    org.keycloak.cluster.infinispan.KeycloakHotRodMarshallerFactory
                </property>
            </remote-store>
        </local-cache>
        <local-cache name="offlineSessions">
            <remote-store cache="offlineSessions" remote-servers="remote-cache" fetch-state="false" passivation="false" preload="false" purge="false" shared="true">
                <property name="rawValues">
                    true
                </property>
                <property name="marshaller">
                    org.keycloak.cluster.infinispan.KeycloakHotRodMarshallerFactory
                </property>
            </remote-store>
        </local-cache>
        <local-cache name="clientSessions">
            <remote-store cache="clientSessions" remote-servers="remote-cache" fetch-state="false" passivation="false" preload="false" purge="false" shared="true">
                <property name="rawValues">
                    true
                </property>
                <property name="marshaller">
                    org.keycloak.cluster.infinispan.KeycloakHotRodMarshallerFactory
                </property>
            </remote-store>
        </local-cache>
        <local-cache name="offlineClientSessions">
            <remote-store cache="offlineClientSessions" remote-servers="remote-cache" fetch-state="false" passivation="false" preload="false" purge="false" shared="true">
                <property name="rawValues">
                    true
                </property>
                <property name="marshaller">
                    org.keycloak.cluster.infinispan.KeycloakHotRodMarshallerFactory
                </property>
            </remote-store>
        </local-cache>
        <local-cache name="loginFailures">
            <remote-store cache="loginFailures" remote-servers="remote-cache" fetch-state="false" passivation="false" preload="false" purge="false" shared="true">
                <property name="rawValues">
                    true
                </property>
                <property name="marshaller">
                    org.keycloak.cluster.infinispan.KeycloakHotRodMarshallerFactory
                </property>
            </remote-store>
        </local-cache>
        <local-cache name="actionTokens">
            <remote-store cache="actionTokens" remote-servers="remote-cache" fetch-state="false" passivation="false" preload="false" purge="false" shared="true">
                <property name="rawValues">
                    true
                </property>
                <property name="marshaller">
                    org.keycloak.cluster.infinispan.KeycloakHotRodMarshallerFactory
                </property>
            </remote-store>
        </local-cache>
        <replicated-cache name="work">
            <remote-store cache="work" remote-servers="remote-cache" fetch-state="false" passivation="false" preload="false" purge="false" shared="true">
                <property name="rawValues">
                    true
                </property>
                <property name="marshaller">
                    org.keycloak.cluster.infinispan.KeycloakHotRodMarshallerFactory
                </property>
            </remote-store>
        </replicated-cache>
    </cache-container>
    <cache-container name="server" aliases="singleton cluster" default-cache="default" module="org.wildfly.clustering.server">
        <transport lock-timeout="60000"/>
        <replicated-cache name="default">
            <transaction mode="BATCH"/>
        </replicated-cache>
    </cache-container>
    <cache-container name="web" default-cache="dist" module="org.wildfly.clustering.web.infinispan">
        <transport lock-timeout="60000"/>
        <distributed-cache name="dist">
            <locking isolation="REPEATABLE_READ"/>
            <transaction mode="BATCH"/>
            <file-store/>
        </distributed-cache>
    </cache-container>
    <cache-container name="ejb" aliases="sfsb" default-cache="dist" module="org.wildfly.clustering.ejb.infinispan">
        <transport lock-timeout="60000"/>
        <distributed-cache name="dist">
            <locking isolation="REPEATABLE_READ"/>
            <transaction mode="BATCH"/>
            <file-store/>
        </distributed-cache>
    </cache-container>
    <cache-container name="hibernate" module="org.infinispan.hibernate-cache">
        <transport lock-timeout="60000"/>
        <local-cache name="local-query">
            <object-memory size="10000"/>
            <expiration max-idle="100000"/>
        </local-cache>
        <invalidation-cache name="entity">
            <transaction mode="NON_XA"/>
            <object-memory size="10000"/>
            <expiration max-idle="100000"/>
        </invalidation-cache>
        <replicated-cache name="timestamps"/>
    </cache-container>
</subsystem>

请注意,与默认 standalone-ha.xml 配置文件相比,此缓存配置的大部分内容没有变化。我在这里所做的更改是将以下缓存更改为 local 并将它们指向我的远程 Infinispan 集群:

这是我的 remote-cache 服务器的配置:

<socket-binding-group name="standard-sockets" default-interface="public" port-offset="${jboss.socket.binding.port-offset:0}">
    <!-- Default socket bindings from standalone-ha.xml are not listed here for brevity -->
    <outbound-socket-binding name="remote-cache">
        <remote-destination host="${env.INFINISPAN_HOST}" port="${remote.cache.port:11222}"/>
    </outbound-socket-binding>
</socket-binding-group>

以下是我的缓存在 Infinispan 端的配置方式:

<subsystem xmlns="urn:infinispan:server:core:9.4" default-cache-container="clustered">
    <cache-container name="clustered" default-cache="default">
        <transport lock-timeout="60000"/>
        <global-state/>
        <replicated-cache-configuration name="replicated-keycloak" mode="SYNC">
            <locking acquire-timeout="3000" />
        </replicated-cache-configuration>
        <replicated-cache name="work" configuration="replicated-keycloak"/>
        <replicated-cache name="sessions" configuration="replicated-keycloak"/>
        <replicated-cache name="authenticationSessions" configuration="replicated-keycloak"/>
        <replicated-cache name="clientSessions" configuration="replicated-keycloak"/>
        <replicated-cache name="offlineSessions" configuration="replicated-keycloak"/>
        <replicated-cache name="offlineClientSessions" configuration="replicated-keycloak"/>
        <replicated-cache name="actionTokens" configuration="replicated-keycloak"/>
        <replicated-cache name="loginFailures" configuration="replicated-keycloak"/>
    </cache-container>
</subsystem>

我相信我对本地缓存与远程存储的工作方式做出了一些错误的假设,我希望有人能够为我解决这个问题。我的意图是让 Infinispan 集群成为所有 Keycloak 缓存的真实来源。通过将每个缓存设为本地,我假设数据将通过 Infinispan 集群复制到每个 Keycloak 节点,这样对 keycloak-0 上的本地 authenticationSessions 缓存的写入将同步持久化到 keycloak-1 通过 Infinispan 集群。

我认为正在发生的事情是,写入 Keycloak 上的本地缓存与将该值持久化到远程 Infinispan 集群并不同步。换句话说,当对 authenticationSessions 缓存执行写入时,它不会在等待将此值写入 Infinispan 集群时阻塞,因此在另一个 Keycloak 节点上立即读取此数据会导致缓存miss,在本地和 Infinispan 集群中。

我正在寻求一些帮助来确定我当前配置导致此问题的原因,以及对 remote-store 行为的一些澄清 - 是否有办法将缓存写入支持的本地缓存by a remote-store 是同步的?如果没有,是否有更好的方法来完成我在这里想要完成的事情?

其他一些可能相关的详细信息:

提前致谢!

我将尝试阐明在集群中配置 Keycloak 时要记住的一些要点。

说到"infinite redirects"的主题,我几年前在开发环境中遇到过类似的问题。虽然 keycloak 团队已经纠正了几个与无限循环相关的错误(例如 KEYCLOAK-5856, KEYCLOAK-5022, KEYCLOAK-4717, KEYCLOAK-4552, KEYCLOAK-3878),但有时它是由于配置问题而发生的。

检查站点是否为 HTTPS 的一件事是也通过 HTTPS 访问 Keycloak 服务器。

我记得当 Keycloak 被放置在 HTTPS 反向代理之后并且所需的 headers 没有传播到 Keycloak 时遇到了与无限循环类似的问题(headers X-FOWARDED ...)。设置好环境就解决了。当集群中的节点发现无法正常工作时(JGroups),可能会发生类似的问题。

关于错误信息"expired_code",我会验证每个节点的时钟是否同步,因为它会导致这种过期令牌/代码错误。

现在更好地了解您的配置,将 "local-cache" 模式与 remote-store 一起使用似乎并不合适,指向 infinispan 集群。

尽管通常情况下,共享存储(例如 remote-cache)通常与 invalidation-cache 一起使用,避免集群复制完整数据(请参阅可以此处应用 https://developer.jboss.org/message/986847#986847),与分布式或失效缓存可能没有太大区别。

我相信 distributed-cache 和 remote-store 会更好(或者 invalidation-cache 以避免将大量数据复制给所有者)但是我无法确定 "local-cache" 使用远程存储(共享),因为我从未尝试过这种配置。 我会首先选择测试一个 distributed-cache 或一个 invalidation-cache ,由它如何处理被驱逐/无效的数据给出。通常,本地缓存不会与集群中的其他远程节点同步。如果这种实现方式在内存中保留一个本地映射,很可能即使remote-storage中的数据被修改,这些修改在某些情况下也可能不会反映出来。 我可以给你一个 Jmeter 测试文件,你可以使用它来尝试用这两种配置执行你自己的测试。

回到你的配置主题,除了复制缓存有一定的局限性之外,你还必须考虑到它通常比只将数据复制到定义的所有者的分布式缓存慢一点(复制的写入所有节点)。还有一个名为 scattered-cache 的变体性能更好,但例如缺少事务支持(您可以在此处查看比较图表 https://infinispan.org/docs/stable/user_guide/user_guide.html#which_cache_mode_should_i_use)。 由于需要发送的复制消息数量众多,复制通常只在小型集群(8 或 10 个服务器以下)中表现良好。分布式缓存允许 Infinispan 通过按条目定义多个副本来线性扩展。

配置您尝试执行的类型而不是类似于 Keycloak (standalone-ha.xml) 提出的配置的主要原因是当您有需求时独立扩展应用程序的 infinispan 集群或使用 infinispan 作为持久存储。

我会解释Keycloak是如何管理它的缓存的,以及它是如何将它基本上分成两组或三组的,这样你就可以更好地理解你需要的配置。

通常,要在集群中配置 Keycloak,只需在 HA 模式下启动和配置 Keycloak,就像使用传统的 Wildfly 实例一样。如果观察到 keycloak 安装中 standalone.xml 和 standalone-ha.xml 之间的差异,就会注意到基本上支持添加到 "Jgroups"、"modcluster",并且缓存在 Wildfly / Keycloak (HA) 的节点之间分布(以前是本地的)。

详细:

  • 添加了jgroups子系统,它将负责连接集群节点并在集群中进行消息/通信。 JGroups 提供网络通信功能、可靠通信和其他功能,如节点发现、point-to-point 通信、多播通信、故障检测和集群节点之间的数据传输。
  • EJB3 缓存从 SIMPLE 缓存(在没有事务处理的本地内存中)变为 DISTRIBUTED。但是,根据我扩展该项目的经验,我会确保 Keycloak 项目不需要使用 EJB3。
  • 缓存:"realms"、"users"、"authorization" 和 "keys" 保留在本地,因为它们仅用于减少数据库的负载。
  • cache: "work" 变为 REPLICATED 因为它是 Keycloak 用来通知集群节点缓存条目必须 evicted/invalidated 因为它的状态已被修改。
  • 缓存 "sessions"、"authenticationSessions"、"offlineSessions"、"clientSessions"、"offlineSessions"、"loginFailures" 和 "actionTokens" 变为 DISTRIBUTED因为性能优于 replicated-cache(参见 https://infinispan.org/docs/stable/user_guide/user_guide.html#which_cache_mode_should_i_use),因为您只需将数据复制给所有者。
  • keycloak 对其默认 HA 配置提出的其他更改是分发 "web" 和 "ejb"(及以上)缓存容器,并将 "hibernate" 缓存更改为 "invalidation-cache"(类似于本地缓存,但具有失效同步)。

我认为对于 "sessions"、"authenticationSessions"、"offlineSessions"、"clientSessions"、[=133 等缓存,您的缓存配置应定义为 "distributed-cache" =]、"loginFailures" 和 "actionTokens"(而不是 "local")。但是,因为您使用的是远程共享存储,所以您应该像我之前所说的那样对其进行测试,看看它是如何工作的。

此外,名为 "work" 的缓存应该是 "replicated-cache",其他缓存("keys"、"authorization"、"realms" 和 "users")应该定义为 "local-cache".

在您的 infinispan 集群中,您可以将其定义为 "distributed-cache"(或 "replicated-cache")。

记住:

In a replicated cache all nodes in a cluster hold all keys i.e. if a key exists on one node, it will also exist on all other nodes. In a distributed cache, a number of copies are maintained to provide redundancy and fault tolerance, however this is typically far fewer than the number of nodes in the cluster. A distributed cache provides a far greater degree of scalability than a replicated cache. A distributed cache is also able to transparently locate keys across a cluster, and provides an L1 cache for fast local read access of state that is stored remotely. You can read more in the relevant User Guide chapter.

Infinispan 文档。参考:cache mode

正如 Keycloak (6.0) 文档所说:

Keycloak has two types of caches. One type of cache sits in front of the database to decrease load on the DB and to decrease overall response times by keeping data in memory. Realm, client, role, and user metadata is kept in this type of cache. This cache is a local cache. Local caches do not use replication even if you are in the cluster with more Keycloak servers. Instead, they only keep copies locally and if the entry is updated an invalidation message is sent to the rest of the cluster and the entry is evicted. There is separate replicated cache work, which task is to send the invalidation messages to the whole cluster about what entries should be evicted from local caches. This greatly reduces network traffic, makes things efficient, and avoids transmitting sensitive metadata over the wire.

The second type of cache handles managing user sessions, offline tokens, and keeping track of login failures so that the server can detect password phishing and other attacks. The data held in these caches is temporary, in memory only, but is possibly replicated across the cluster.

文档。参考:cache configuration

如果你想阅读另一篇好文档,你可以看看"cross-dc"部分(cross-dc mode) especially section "3.4.6 Infinispan cache" (infinispan cache)

我尝试使用 Keycloak 6.0.1 和 Infinispan 9.4。11.Final,这是我的测试配置(基于 standalone-ha.xml 文件)。

Keycloak infinispan 子系统:

    <subsystem xmlns="urn:jboss:domain:infinispan:8.0">
        <cache-container name="keycloak" module="org.keycloak.keycloak-model-infinispan">
            <transport lock-timeout="60000"/>
            <local-cache name="realms">
                <object-memory size="10000"/>
            </local-cache>
            <local-cache name="users">
                <object-memory size="10000"/>
            </local-cache>
            <distributed-cache name="sessions" owners="1" remote-timeout="30000">
                <remote-store cache="sessions" remote-servers="remote-cache" socket-timeout="60000" fetch-state="false" passivation="false" preload="false" purge="false" shared="true">
                <property name="rawValues">
                true
                </property>
                <property name="marshaller">
org.keycloak.cluster.infinispan.KeycloakHotRodMarshallerFactory
                </property>
                </remote-store>
                </distributed-cache>
            <distributed-cache name="authenticationSessions" owners="1" remote-timeout="30000">
                <remote-store cache="authenticationSessions" remote-servers="remote-cache" socket-timeout="60000" fetch-state="false" passivation="false" preload="false" purge="false" shared="true">
                <property name="rawValues">
                true
                </property>
                <property name="marshaller">
org.keycloak.cluster.infinispan.KeycloakHotRodMarshallerFactory
                </property>
                </remote-store>
                </distributed-cache>
            <distributed-cache name="offlineSessions" owners="1" remote-timeout="30000">
                <remote-store cache="offlineSessions" remote-servers="remote-cache" socket-timeout="60000" fetch-state="false" passivation="false" preload="false" purge="false" shared="true">
                <property name="rawValues">
true
                </property>
                <property name="marshaller">
org.keycloak.cluster.infinispan.KeycloakHotRodMarshallerFactory
                </property>
                </remote-store>
                </distributed-cache>
            <distributed-cache name="clientSessions" owners="1" remote-timeout="30000">
                <remote-store cache="clientSessions" remote-servers="remote-cache" socket-timeout="60000" fetch-state="false" passivation="false" preload="false" purge="false" shared="true">
                <property name="rawValues">
true
                </property>
                <property name="marshaller">
org.keycloak.cluster.infinispan.KeycloakHotRodMarshallerFactory
                </property>
                </remote-store>
                </distributed-cache>
            <distributed-cache name="offlineClientSessions" owners="1" remote-timeout="30000">
                <remote-store cache="offlineClientSessions" remote-servers="remote-cache" socket-timeout="60000" fetch-state="false" passivation="false" preload="false" purge="false" shared="true">
                <property name="rawValues">
                true
                </property>
                <property name="marshaller">
org.keycloak.cluster.infinispan.KeycloakHotRodMarshallerFactory
                </property>
                </remote-store>
                </distributed-cache>
            <distributed-cache name="loginFailures" owners="1" remote-timeout="30000">
                <remote-store cache="loginFailures" remote-servers="remote-cache" socket-timeout="60000" fetch-state="false" passivation="false" preload="false" purge="false" shared="true">
                <property name="rawValues">
                true
                </property>
                <property name="marshaller">
org.keycloak.cluster.infinispan.KeycloakHotRodMarshallerFactory
                </property>
                </remote-store>
                </distributed-cache>
            <replicated-cache name="work"/>
            <local-cache name="authorization">
                <object-memory size="10000"/>
            </local-cache>
            <local-cache name="keys">
                <object-memory size="1000"/>
                <expiration max-idle="3600000"/>
            </local-cache>
            <distributed-cache name="actionTokens" owners="1" remote-timeout="30000">
                <remote-store cache="actionTokens" remote-servers="remote-cache" socket-timeout="60000" fetch-state="false" passivation="false" preload="false" purge="false" shared="true">
                <property name="rawValues">
                true
                </property>
                <property name="marshaller">
org.keycloak.cluster.infinispan.KeycloakHotRodMarshallerFactory
                </property>
                </remote-store>
                <object-memory size="-1"/>
                <expiration max-idle="-1" interval="300000"/>
            </distributed-cache>
         </cache-container>

Keycloak 套接字绑定:

<socket-binding-group name="standard-sockets" default-interface="public" port-offset="${jboss.socket.binding.port-offset:0}">
    <socket-binding name="management-http" interface="management" port="${jboss.management.http.port:9990}"/>
    <socket-binding name="management-https" interface="management" port="${jboss.management.https.port:9993}"/>
    <socket-binding name="ajp" port="${jboss.ajp.port:8009}"/>
    <socket-binding name="http" port="${jboss.http.port:8080}"/>
    <socket-binding name="https" port="${jboss.https.port:8443}"/>
    <socket-binding name="jgroups-mping" interface="private" multicast-address="${jboss.default.multicast.address:230.0.0.4}" multicast-port="45700"/>
    <socket-binding name="jgroups-tcp" interface="private" port="7600"/>
    <socket-binding name="jgroups-udp" interface="private" port="55200" multicast-address="${jboss.default.multicast.address:230.0.0.4}" multicast-port="45688"/>
    <socket-binding name="modcluster" multicast-address="${jboss.modcluster.multicast.address:224.0.1.105}" multicast-port="23364"/>
    <socket-binding name="txn-recovery-environment" port="4712"/>
    <socket-binding name="txn-status-manager" port="4713"/>
<outbound-socket-binding name="remote-cache">
<remote-destination host="my-server-domain.com" port="11222"/>  
</outbound-socket-binding>
    <outbound-socket-binding name="mail-smtp">
        <remote-destination host="localhost" port="25"/>
    </outbound-socket-binding>
</socket-binding-group>

Infinispan集群配置:

<subsystem xmlns="urn:infinispan:server:core:9.4" default-cache-container="clustered">
    <cache-container name="clustered" default-cache="default" statistics="true">
        <transport lock-timeout="60000"/>
        <global-state/>
        <distributed-cache-configuration name="transactional">
            <transaction mode="NON_XA" locking="PESSIMISTIC"/>
        </distributed-cache-configuration>
        <distributed-cache-configuration name="async" mode="ASYNC"/>
        <replicated-cache-configuration name="replicated"/>
        <distributed-cache-configuration name="persistent-file-store">
            <persistence>
                <file-store shared="false" fetch-state="true"/>
            </persistence>
        </distributed-cache-configuration>
        <distributed-cache-configuration name="indexed">
            <indexing index="LOCAL" auto-config="true"/>
        </distributed-cache-configuration>
        <distributed-cache-configuration name="memory-bounded">
            <memory>
                <binary size="10000000" eviction="MEMORY"/>
            </memory>
        </distributed-cache-configuration>
        <distributed-cache-configuration name="persistent-file-store-passivation">
            <memory>
                <object size="10000"/>
            </memory>
            <persistence passivation="true">
                <file-store shared="false" fetch-state="true">
                    <write-behind modification-queue-size="1024" thread-pool-size="1"/>
                </file-store>
            </persistence>
        </distributed-cache-configuration>
        <distributed-cache-configuration name="persistent-file-store-write-behind">
            <persistence>
                <file-store shared="false" fetch-state="true">
                    <write-behind modification-queue-size="1024" thread-pool-size="1"/>
                </file-store>
            </persistence>
        </distributed-cache-configuration>
        <distributed-cache-configuration name="persistent-rocksdb-store">
            <persistence>
                <rocksdb-store shared="false" fetch-state="true"/>
            </persistence>
        </distributed-cache-configuration>
        <distributed-cache-configuration name="persistent-jdbc-string-keyed">
            <persistence>
                <string-keyed-jdbc-store datasource="java:jboss/datasources/ExampleDS" fetch-state="true" preload="false" purge="false" shared="false">
                    <string-keyed-table prefix="ISPN">
                        <id-column name="id" type="VARCHAR"/>
                        <data-column name="datum" type="BINARY"/>
                        <timestamp-column name="version" type="BIGINT"/>
                    </string-keyed-table>
                    <write-behind modification-queue-size="1024" thread-pool-size="1"/>
                </string-keyed-jdbc-store>
            </persistence>
        </distributed-cache-configuration>
        <distributed-cache name="default"/>
        <replicated-cache name="repl" configuration="replicated"/>
        <replicated-cache name="work" configuration="replicated"/>
        <replicated-cache name="sessions" configuration="replicated"/>
        <replicated-cache name="authenticationSessions" configuration="replicated"/>
        <replicated-cache name="clientSessions" configuration="replicated"/>
        <replicated-cache name="offlineSessions" configuration="replicated"/>
        <replicated-cache name="offlineClientSessions" configuration="replicated"/>
        <replicated-cache name="actionTokens" configuration="replicated"/>
        <replicated-cache name="loginFailures" configuration="replicated"/>
    </cache-container>
</subsystem>

P.S。将属性 "owners" 从 1 更改为您喜欢的值。

希望对您有所帮助。

你们之间的交流很棒,令人难以置信的是,我的假设与迈克尔完全相同,我将我的本地缓存配置为使用远程存储,并期望密钥 read/write 始终来自远程存储,但显然不是这样。

遗憾的是,从这里完成的所有交流中,我找不到为什么会这样,为什么我们不能将本地 infinispan 配置为仅充当远程 infinispan 的代理,从而使该实例保持无状态并拥有一个更容易重新部署的过程。