跳转到正文

后端与配置

应用通常注入默认字符串模板读写数据,在需要隔离编码协议时通过 StoreClient 创建额外模板。后端由依赖和 own.store.backend 共同决定,不会因为类路径中出现 Redisson 就自动改用 Redis。

本地后端

使用快速开始中的 springboot-component-store-starter-local。数据保存在当前客户端的内存中,适合单实例临时数据、开发演示和不需要跨实例协调的场景。

每个命名空间模板为 Value、Map、Set、List、Atomic 分别创建有容量限制的数据区。maximum-size 是每个数据区的键数上限,不是整个应用的总键数,也不是字节数。Caffeine 可淘汰已有键;即使未设置 TTL,数据仍可能丢失。

单个 Map 的字段数、Set 的成员数或 List 的元素数超过 maximum-collection-size 时抛出 StoreLimitException。不同命名空间会增加独立的数据区,客户端还会缓存模板,因此应使用有限、稳定的命名空间,把用户 ID 等高基数字段放在逻辑键中。

Redis 后端

沿用快速开始中的版本管理,将运行时依赖替换为:

xml
<dependency>
    <groupId>com.own.component</groupId>
    <artifactId>springboot-component-store-starter-redis</artifactId>
</dependency>

该 Starter 同时引入 Store Redisson 实现和 redisson-spring-boot-starter。连接已有 Redis 的最小配置如下:

yaml
own:
  store:
    backend: redis
    namespace: example-service
spring:
  data:
    redis:
      host: 127.0.0.1
      port: 6379
      database: 0

按部署环境补充连接认证、TLS、哨兵或集群配置。需要 Redisson 原生配置时使用 spring.redis.redisson.filespring.redis.redisson.config,不属于 own.store 属性。已有自定义 RedissonClient Bean 时可以复用;Store 不修改该客户端的全局 Codec。

选择 redis 后必须存在可唯一解析的 RedissonClient,缺少时启动失败,不会回退本地;多个客户端需通过 @Primary 明确选择。同样,只引入 Redis Starter 而不设置 backend: redis,也不能获得默认本地实现。

多实例共享数据、锁或限流时,要使用相同 Redis 目标、数据库和命名空间,并约定一致的逻辑键与编码协议。修改 namespace 会切换数据空间,不会迁移旧数据。

配置参考

下表属性均以 own.store. 为前缀:

属性默认值约束与作用
backendlocal可选 localredis;选择实现,不代替引入对应依赖
namespacedefault非空,只允许英文字母、数字、点、下划线和连字符
scan-max-results1000正整数,限制 keys 的物化结果;不限制 scanKeys 全量迭代
enable-data-cachetrue控制自动创建的 DataCacheUtil 是否读写缓存;关闭仍保留 Bean
local.maximum-size10000正 long,每个本地数据区的最大键数
local.maximum-collection-size10000正整数,单个本地 Map、Set、List 的容量上限
local.lock-stripes1024正整数,本地锁分片数;键碰撞时不同逻辑键也会串行
local.maximum-rate-limiters10000正 long,本地限流器缓存最大数量,另有一小时空闲淘汰

本地限额不约束 Redis 容量;缓存开关也不会关闭 StoreTemplate 的直接读写、锁或限流。namespace 和扫描上限在公共装配时校验,本地选项在创建本地实现时校验,非法值会导致相应 Bean 创建失败。

命名空间与编码协议

StoreClient.template(namespace, codec) 返回模板。同一客户端中,同一命名空间首次绑定后,使用相同 Codec ID 会返回已有模板;使用不同 ID 抛出 StoreException。这项检查只在客户端实例内生效,不会自动校验其他应用进程。

默认模板使用 StoreCodecs.string(),即 UTF-8 字符串,协议 ID 为 string:utf-8:v1。以下配置创建另一个字符串模板,不覆盖默认模板:

java
package example.store;

import com.own.component.store.api.StoreClient;
import com.own.component.store.api.StoreTemplate;
import com.own.component.store.api.common.StoreCodecs;
import com.own.component.store.api.common.StoreNamespace;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration(proxyBeanMethods = false)
public class StoreTemplateConfiguration {

    @Bean("lookupStoreTemplate")
    StoreTemplate<String> lookupStoreTemplate(StoreClient client) {
        return client.template(
                StoreNamespace.of("lookup-v1"), StoreCodecs.string());
    }
}

使用时以 @Qualifier("lookupStoreTemplate") 注入。Redis 默认锁和限流管理器仍使用自动配置的命名空间;新增模板不会自动生成配套管理器。需要隔离的 Redis 锁或限流器时应手动装配对应实例。

自定义对象编码实现 StoreCodec<T>id()encode(T)decode(byte[])。组件只内置字符串 Codec;普通对象查询可先使用JSON 缓存工具。自定义协议需满足:

  • ID 稳定且非空,包含类型与协议版本;不兼容变更使用新命名空间,不能仅替换实现却沿用旧 ID。
  • 编码返回非 null 字节数组,解码应能还原相同协议的数据;在实现中处理序列化错误并保留异常原因。
  • 相同值产生稳定字节。条件删除、Set 去重及集合的按值查找/删除依赖编码字节,不只是 Java 对象的 equals
  • 不修改或泄露可变字节数组;StoreCodecs.copy 可用于防御性复制。

Redis 中 Store 管理的键使用 o:v2:<namespace>:<type>:<logical-key>,类型包含 valuemapsetlistatomiclockrate。调用 API 时只传逻辑键;同名逻辑键在不同类型中独立存在。组件不提供旧前缀数据的自动读取或迁移。

自动配置与覆盖

Bean创建前提与覆盖方式
StoreClient有选定后端的实现类;按类型缺失时创建
StoreNamespace按类型缺失时从配置创建
storeTemplate存在 StoreClient 且没有同名 Bean;默认是 StoreTemplate<String>
StoreLockManagerStoreRateLimiterManager选定后端可用且对应类型缺失
DataCacheUtil有锁管理器和名为 storeTemplate 的模板,且没有该类型 Bean
StoreLockReleaseStrategy有 Spring 事务相关类且未自定义时提供同步事务感知策略,否则内置管理器回退立即释放
StoreLockAspect有 AOP、SpEL 相关类和锁管理器,且没有该类型 Bean

Starter 通过自动配置模块传递引入 AspectJ Starter;Spring 事务依赖是可选的。组件不创建数据库事务管理器,也不会仅因调用锁 API 而启动事务。

自定义同名 storeTemplate 时,仍需满足依赖它的组件所要求的字符串类型。只替换 StoreClient 不会一并替换锁和限流实现,应保持这几项的共享范围一致。自定义 DataCacheUtil 时,缓存开关由创建它的代码决定。

非 Spring 环境与后端扩展

引入 springboot-component-store-caffeine 后,可以直接装配本地对象:

java
import com.own.component.store.api.cache.DataCacheUtil;
import com.own.component.store.api.common.StoreCodecs;
import com.own.component.store.api.common.StoreNamespace;
import com.own.component.store.caffeine.LocalStoreClient;
import com.own.component.store.caffeine.LocalStoreLockManager;
import com.own.component.store.caffeine.LocalStoreOptions;
import com.own.component.store.caffeine.LocalStoreRateLimiterManager;

// 放入初始化方法,应用内复用这些实例。
var options = LocalStoreOptions.defaults();
var client = new LocalStoreClient(options, 1000);
var store = client.template(StoreNamespace.of("standalone"), StoreCodecs.string());
var locks = new LocalStoreLockManager(options.lockStripes());
var limiters = new LocalStoreRateLimiterManager(options.maximumRateLimiters());
var cache = new DataCacheUtil(store, locks);

Redis 对应构造方法为 new RedissonStoreClient(redissonClient, scanMaxResults)new RedissonStoreLockManager(redissonClient, namespace)new RedissonStoreRateLimiterManager(redissonClient, namespace),类型位于 com.own.component.store.redisson。调用方负责创建和关闭 RedissonClient。手动创建的锁管理器默认立即释放;如需其他释放时机,通过构造参数提供 StoreLockReleaseStrategy

实现新后端时,提供 StoreClientStoreTemplate 及各数据接口,另行装配匹配的锁和限流管理器。可继承 AbstractStoreClient 复用模板与 Codec ID 注册。尤其要实现原子的 ValueStore.compareAndDelete:缓存损坏恢复依赖它;接口默认抛出不支持异常,不会降级成先读后删。AtomicStore.compareAndSet 和键扫描也有需要覆盖的默认不支持方法,不能只实现基本读写便视为兼容所有上层能力。

示例涉及的源码类型

LocalStoreClientLocalStoreLockManagerLocalStoreOptionsLocalStoreRateLimiterManager