我写了一个 Redis Java SDK 客户端,为了简化 springboot 项目配置, 自己实现了一个 springboot starter 。
Github 地址 :https://github.com/makemyownlife/platform-redis
1 SDK 设计思路SDK 的设计理念核心两点:
提供精简的 API 供开发者使用,方便与用户接入;屏蔽三方依赖,用户只和对外 API 层交互 。Redis SDK 如上图,分为三层:
最上层为 API ,用户与该层打交道 ,为了方便用户方便接入,设计了springboot starter模块。中间为核心功能层,提供了两类功能:Redis 操作命令(比如 String 相关命令)和增强功能(比如分布式锁)。最底层是三方依赖,我们有两种选择:自己实现与 Redis 服务端的交互,工作量非常大,另一种是选择选择成熟的 Redis Java 客户端框架 ,再此基础上进行封装,并补充其他的实现。笔者选择 Redisson 框架,因为该框架实现了非常高级的功能,比如分布式锁,延迟队列等。2 操作命令封装封装操作命令时基本原则是:尽量不要提供危险的接口供开封者使用,尽量减少开封者的使用心智。
比如,KEYS 命令用于查找符合指定模式的所有键。它是一个用于调试目的的命令,但在生产环境中不推荐频繁使用,因为在大型数据库中执行 KEYS 命令可能会导致性能问题。考虑到假如提供给开发者使用会引发,我们并不会封装 KEYS 命令给开发者使用。
项目分为四个模块:
客户端模块:核心模块,封装了 Redis 操作命令(比如 String 相关命令)和增强功能(比如分布式锁)。springboot starter : 开发者只需要在 pom.xml 引入 starter 的依赖定义,在配置文件中编写约定的配置。ID 生成器: 通过 Redis 改造雪花算法生成唯一编号。使用示例:使用简单的 springboot starter 的例子。本节我们重点讲解客户端模块的实现。
//1. 定义配置 SingleConfig config = new SingleConfig();config.setAddress("redis://127.0.0.1:6379");//2. 定义Redis操作对象RedisOperation redisOperation = new RedisOperation(config);// 3.使用String命令的 setEx 方法 StringCommand stringCommand = redisOperation.getStringCommand();stringCommand.setEx("hello", "mylife", 109);// 4.使用Hash命令的hset方法 HashCommand hashCommand = this.redisOperation.getHashCommand();hashCommand.hset("myhash", "time", "mybatis");首先我们定义一个配置类,例子中是单机配置类 SingleConfig ,还有集群配置、主从配置。
然后初始化操作对象 RedisOperation , 参数是配置对象。最后获取内置的 String 命令 、Hash 命令对象,调用该对象的操作方法。
RedisOperation 对象内置了 RedissonClient 对象 ,该对象对用户是不可视的。
public RedisOperation(SingleConfig SingleServerConfig) { Config config = ConfigBuilder.buildBySingleServerConfig(SingleServerConfig); //默认string编解码 config.setCodec(new StringCodec()); this.redissonClient = Redisson.create(config);}三种根据配置类初始化的构造函数,每个构造函数内会创建初始化 RedissonClient 对象。
然后根据 RedissonClient 对象创建不同的操作命令:
public StringCommand getStringCommand() { StringCommand StringCommand = new StringCommandImpl(this.redissonClient); return StringCommand;}public StringCommand getStringCommand(RedisCodec RedisCodec) { StringCommand StringCommand = new StringCommandImpl(this.redissonClient , RedisCodec); return StringCommand;}public AtomicCommand getAtomicCommand() { AtomicCommand AtomicCommand = new AtomicCommandImpl(this.redissonClient); return AtomicCommand;}public ListCommand getListCommand() { ListCommand ListCommand = new ListCommandImpl(this.redissonClient); return ListCommand;}// 省略其他的命令代码最后,我们看看字符串操作命令 StringComand 如何实现。
1、核心接口
public interface StringCommand extends KeyCommand { String get(String key); void set(String key, String value); void setEx(String key, String value, int second); boolean setNx(String key, String value); boolean setNx(String key, String value, int aliveSecond); Map<String, Object> mget(String... keys);}2、接口实现类
public StringCommandImpl extends KeyCommandImpl implements StringCommand { public StringCommandImpl(RedissonClient redissonClient) { super(redissonClient); } public StringCommandImpl(RedissonClient redissonClient, RedisCodec redisCodec) { super(redissonClient, redisCodec); } public String get(final String key) { return invokeCommand(new InvokeCommand<String>(RedisCommandType.GET) { @Override public String exe(RedissonClient redissonClient) { RBucket<String> result = getRedissonClient().getBucket(key); if (result == null || !result.isExists()) { return null; } return result.get(); } }); } public void set(final String key, final String value) { invokeCommand(new InvokeCommand<String>(RedisCommandType.SET) { @Override public String exe(RedissonClient redissonClient) { RBucket<String> result = getRedissonClient().getBucket(key); result.set(value); return null; } }); } public void setEx(final String key, final String value, final int second) { invokeCommand(new InvokeCommand<String>(RedisCommandType.SET_EX) { @Override public String exe(RedissonClient redissonClient) { RBucket<String> result = getRedissonClient().getBucket(key); result.set(value, second, TimeUnit.SECONDS); return null; } }); } public boolean setNx(final String key, final String value) { return invokeCommand(new InvokeCommand<Boolean>(RedisCommandType.SET_NX) { @Override public Boolean exe(RedissonClient redissonClient) { RBucket<String> result = getRedissonClient().getBucket(key); return result.trySet(value); } }); } public boolean setNx(final String key, final String value, final int aliveSecond) { return invokeCommand(new InvokeCommand<Boolean>(RedisCommandType.SET_NX) { @Override public Boolean exe(RedissonClient redissonClient) { RBucket<String> result = getRedissonClient().getBucket(key); return result.trySet(value, aliveSecond, TimeUnit.SECONDS); } }); } public Map<String, Object> mget(final String... keys) { return invokeCommand(new InvokeCommand<Map<String, Object>>(RedisCommandType.MGET) { @Override public Map<String, Object> exe(RedissonClient redissonClient) { return getRedissonClient().getBuckets().get(keys); } }); } }实现类里面方法实现都比较简单,都是使用 RedissonClient 的 API 方法 ,我们做一层简单的包装。
在包装类内部,我们除了实现基本的 API 调用之外,也可以做访问统计等额外功能。
3 实现 springboot starter3.1 启动器我们都知道,Spring Boot 基于“约定大于配置”(Convention over configuration)这一理念来快速地开发、测试、运行和部署 Spring 应用,并能通过简单地与各种启动器(如 spring-boot-web-starter)结合,让应用直接以命令行的方式运行,不需再部署到独立容器中。
Spring Boot starter 构造的启动器使用起来非常方便,开发者只需要在 pom.xml 引入 starter 的依赖定义,在配置文件中编写约定的配置即可。
很多开源组件都会为 Spring 的用户提供一个 spring-boot-starter 封装给开发者,让开发者非常方便集成和使用。
spring-boot-starter 实现流程如下:
01、定创建starter项目,定义 Spring 自身的依赖包和 Bean 的依赖包 ;
02、定义spring.factories 文件
在 resources 包下创建 META-INF 目录后,新建 spring.factories 文件,并在文件中定义自动加载类,文件内容格式:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\xx.xx.xx.xx.xx.MyConfigspring boot 会根据文件中配置的自动化配置类来自动初始化相关的 Bean、Component 或 Service。
03、配置自动配置类
编写自动配置类,这些类将在Spring应用程序中自动配置starter。自动配置类应该有一个@Configuration注解,并且应该包含可以覆盖的默认值,以允许用户自定义配置。在自动配置类中,可以使用@ConditionalOnClass、@ConditionalOnMissingBean等条件注解,以便只有在需要的情况下才会配置 starter。
3.2 实现方式首先在 resources 包下创建 META-INF 目录后,新建 spring.factories 文件,并在文件中定义自动加载类,文件内容格式:
# Auto Configureorg.springframework.boot.autoconfigure.EnableAutoConfiguration=\com.courage.platform.redis.client.springboot.starter.configuration.RedisClientAutoConfiguration然后定义自动配置类 RedisClientAutoConfiguration。
@Configurationpublic RedisClientAutoConfiguration { @Configuration @ConditionalOnMissingBean(Config.class) @ConditionalOnProperty(name = "spring.redis.type", havingValue = "single") static StaticBuildSingleServer { @Bean(value = "platformSingleServerConfig") @ConfigurationProperties(prefix = "spring.redis.single") public SingleConfig getSingleConfig() { SingleConfig config = new SingleConfig(); return config; } @Bean public Config singleServerConfig(SingleConfig singleConfig) { return ConfigBuilder.buildBySingleServerConfig(singleConfig); } @Bean(destroyMethod = "shutdown") public RedisOperation redisOperation(Config config) { RedisOperation redisOperation = new RedisOperation(config); return redisOperation; } } @Configuration @ConditionalOnMissingBean(Config.class) @ConditionalOnProperty(name = "spring.redis.type", havingValue = "sentinel") static StaticBuildSentinelServer { @Bean(value = "platformSentinelServerConfig") @ConfigurationProperties(prefix = "spring.redis.sentinel") public SentinelConfig getPlatformSentinelServersConfig() { SentinelConfig sentinelConfig = new SentinelConfig(); return sentinelConfig; } @Bean public Config sentinelServerConfig(SentinelConfig sentinelConfig) { return ConfigBuilder.buildBySentinelServerConfig(sentinelConfig); } @Bean(destroyMethod = "shutdown") public RedisOperation redisOperation(Config config) { RedisOperation redisOperation = new RedisOperation(config); return redisOperation; } } @Bean("stringCommand") @ConditionalOnBean(RedisOperation.class) public StringCommand createStringCommand(RedisOperation redisOperation) { return redisOperation.getStringCommand(); } @Bean @ConditionalOnBean(RedisOperation.class) public ZSetCommand createZSetCommand(RedisOperation redisOperation) { return redisOperation.getZSetCommand(); } @Bean @ConditionalOnBean(RedisOperation.class) public SetCommand createPlatformSetCommand(RedisOperation redisOperation) { return redisOperation.getSetCommand(); } @Bean @ConditionalOnBean(RedisOperation.class) public AtomicCommand createPlatformAtomicCommand(RedisOperation redisOperation) { return redisOperation.getAtomicCommand(); } @Bean @ConditionalOnBean(RedisOperation.class) public HashCommand createHashCommand(RedisOperation redisOperation) { return redisOperation.getHashCommand(); } @Bean @ConditionalOnBean(RedisOperation.class) public ScriptCommand createPlatformScriptCommand(RedisOperation redisOperation) { return redisOperation.getScriptCommand(); } @Bean @Primary @ConditionalOnBean(RedisOperation.class) public IdGenerator createIdGenerator(RedisOperation redisOperation) { return new IdGenerator(redisOperation); } }该配置类首先会根据配置类创建 RedisOperation 对象 ,然后获取命令对象(比如 StringCommand、HashCommand)注入到 Spring 容器里。
3.3 如何使用1、pom文件添加依赖
<dependency> <groupId>com.courage</groupId> <artifactId>platform-redis-client-springboot-starter</artifactId> <version>1.0.0-SNAPSHOT</version></dependency>2、yaml缓存配置
spring: redis: type: single single: address: 127.0.0.1:63793、使用缓存
@Autowiredprivate RedisOperation redisOperation;@RequestMapping(value = "/hello", method = RequestMethod.GET)@ResponseBodypublic String hellodanbiao() { String mylife = redisOperation.getStringCommand().get("hello"); System.out.println(mylife); redisOperation.getStringCommand().set("hello", "zhang勇"); return "hello-service";}参考资料:
Redis 集成简介:https://juejin.cn/post/70762445