X

如何通过spring-boot-starter-redis使用aliyun的Redis

我有一个项目,使用的是spring boot构建,整个系统使用spring security构建了一个登录系统,关于spring boot security 如何使用,这里就不介绍了,这里只描述如何解决我遇到的问题。

背景 : spring boot + spring security 构建的登陆系统,只能满足单机的需求,如果程序部署在集群之上,那么就涉及到了session的共享问题,否则你的用户就会显示一会登陆,一会又没登陆(落到了另外一台机器)。核心思想是通过redis来在集群之间共享用户的session,实现这样的方案有两种方法。

 

方法一: 通过修改tomcat的配置来实现。不幸的是spring  boot的程序,tomcat是内置的(你当然也可以用外置的), 所以先排除这种办法。

方法二:   通过spring session来实现。文档
其实spring boot配置也比较简单,按照官方的文档只需要@EnableRedisHttpSession,以及在配置文件中配置你的host和password就可以了,配置如下。
spring.redis.host=127.0.0.1
spring.redis.password=xxxx
spring.redis.port=6379

 

是不是很嗨皮? 本地测试通过了,等你部署到阿里云服务器发现,服务起不来???

刚开始我怀疑是阿里云的redis版本问题,可是他的版本是2.8.14也是2.8版本,理论上是支持的。

问题在于在阿里云提供的redis上无法启动程序,报错如下:

 

2017-08-17 21:58:48 ERROR SpringApplication.handleRunFailure:827 # Application startup failed
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'enableRedisKeyspaceNotificationsInitializer' defined in class path resource [org/springframework/session/data/redis/config/annotation/web/http/RedisHttpSessionConfiguration.class]: Invocation of init method failed; nested exception is org.springframework.data.redis.RedisSystemException: Redis exception; nested exception is com.lambdaworks.redis.RedisCommandExecutionException: ERR CONFIG subcommand must be GET
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1578)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:545)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:482)
        at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306)
        at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
        at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302)
        at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197)
        at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:772)
        at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:839)
        at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:538)
        at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.refresh(EmbeddedWebApplicationContext.java:118)
        at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:766)
        at org.springframework.boot.SpringApplication.createAndRefreshContext(SpringApplication.java:361)
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:307)
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1191)
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1180)
        at com.alibaba.quantum.QuantumPlatform.main(QuantumPlatform.java:22)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at org.springframework.boot.loader.MainMethodRunner.run(MainMethodRunner.java:54)
        at java.lang.Thread.run(Thread.java:748)
Caused by: org.springframework.data.redis.RedisSystemException: Redis exception; nested exception is com.lambdaworks.redis.RedisCommandExecutionException: ERR CONFIG subcommand must be GET
        at org.springframework.data.redis.connection.lettuce.LettuceExceptionConverter.convert(LettuceExceptionConverter.java:70)
        at org.springframework.data.redis.connection.lettuce.LettuceExceptionConverter.convert(LettuceExceptionConverter.java:41)
        at org.springframework.data.redis.PassThroughExceptionTranslationStrategy.translate(PassThroughExceptionTranslationStrategy.java:37)
        at org.springframework.data.redis.FallbackExceptionTranslationStrategy.translate(FallbackExceptionTranslationStrategy.java:37)
        at org.springframework.data.redis.connection.lettuce.LettuceConnection.convertLettuceAccessException(LettuceConnection.java:326)
        at org.springframework.data.redis.connection.lettuce.LettuceConnection.getConfig(LettuceConnection.java:658)
        at org.springframework.session.data.redis.config.ConfigureNotifyKeyspaceEventsAction.getNotifyOptions(ConfigureNotifyKeyspaceEventsAction.java:74)
        at org.springframework.session.data.redis.config.ConfigureNotifyKeyspaceEventsAction.configure(ConfigureNotifyKeyspaceEventsAction.java:55)
        at org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration$EnableRedisKeyspaceNotificationsInitializer.afterPropertiesSet(RedisHttpSessionConfiguration.java:251)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1637)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1574)
        ... 22 common frames omitted
Caused by: com.lambdaworks.redis.RedisCommandExecutionException: ERR CONFIG subcommand must be GET
        at com.lambdaworks.redis.LettuceFutures.await(LettuceFutures.java:104)

然后我觉得是不是连接出现了问题,所以我又修改换了一种方式来连接,使用Lettuce来连接,依然报错,程序里也没有看到是哪里抛出了ERR CONFIG subcommand must be GET。

2017-08-17 21:58:48 ERROR SpringApplication.handleRunFailure:827 # Application startup failed
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'enableRedisKeyspaceNotificationsInitializer' defined in class path resource [org/springframework/session/data/redis/config/annotation/web/http/RedisHttpSessionConfiguration.class]: Invocation of init method failed; nested exception is org.springframework.data.redis.RedisSystemException: Redis exception; nested exception is com.lambdaworks.redis.RedisCommandExecutionException: ERR CONFIG subcommand must be GET
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1578)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:545)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:482)
        at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306)
        at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
        at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302)
        at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197)
        at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:772)
        at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:839)
        at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:538)
        at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.refresh(EmbeddedWebApplicationContext.java:118)
        at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:766)
        at org.springframework.boot.SpringApplication.createAndRefreshContext(SpringApplication.java:361)
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:307)
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1191)
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1180)
        at com.alibaba.quantum.QuantumPlatform.main(QuantumPlatform.java:22)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at org.springframework.boot.loader.MainMethodRunner.run(MainMethodRunner.java:54)
        at java.lang.Thread.run(Thread.java:748)
Caused by: org.springframework.data.redis.RedisSystemException: Redis exception; nested exception is com.lambdaworks.redis.RedisCommandExecutionException: ERR CONFIG subcommand must be GET
        at org.springframework.data.redis.connection.lettuce.LettuceExceptionConverter.convert(LettuceExceptionConverter.java:70)
        at org.springframework.data.redis.connection.lettuce.LettuceExceptionConverter.convert(LettuceExceptionConverter.java:41)
        at org.springframework.data.redis.PassThroughExceptionTranslationStrategy.translate(PassThroughExceptionTranslationStrategy.java:37)
        at org.springframework.data.redis.FallbackExceptionTranslationStrategy.translate(FallbackExceptionTranslationStrategy.java:37)
        at org.springframework.data.redis.connection.lettuce.LettuceConnection.convertLettuceAccessException(LettuceConnection.java:326)
        at org.springframework.data.redis.connection.lettuce.LettuceConnection.getConfig(LettuceConnection.java:658)
        at org.springframework.session.data.redis.config.ConfigureNotifyKeyspaceEventsAction.getNotifyOptions(ConfigureNotifyKeyspaceEventsAction.java:74)
        at org.springframework.session.data.redis.config.ConfigureNotifyKeyspaceEventsAction.configure(ConfigureNotifyKeyspaceEventsAction.java:55)
        at org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration$EnableRedisKeyspaceNotificationsInitializer.afterPropertiesSet(RedisHttpSessionConfiguration.java:251)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1637)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1574)
        ... 22 common frames omitted
Caused by: com.lambdaworks.redis.RedisCommandExecutionException: ERR CONFIG subcommand must be GET
        at com.lambdaworks.redis.LettuceFutures.await(LettuceFutures.java:104)
        at com.lambdaworks.redis.LettuceFutures.awaitOrCancel(LettuceFutures.java:77)
        at com.lambdaworks.redis.FutureSyncInvocationHandler.handleInvocation(FutureSyncInvocationHandler.java:73)
        at com.google.common.reflect.AbstractInvocationHandler.invoke(AbstractInvocationHandler.java:87)
        at com.sun.proxy.$Proxy89.configGet(Unknown Source)
        at org.springframework.data.redis.connection.lettuce.LettuceConnection.getConfig(LettuceConnection.java:656)
        ... 27 common frames omitted

 

是为啥呢? 去看了下源代码发现,

org.springframework.session.data.redis.config.ConfigureNotifyKeyspaceEventsAction 有redis的config set和config get 操作。
于是,通过命令行连上aliyun的redis,果然报错如下:
r-uf6db95eb74a1f64.redis.rds.aliyuncs.com:6379> config set

(error) ERR CONFIG subcommand must be GET

 

再去查看阿里云的官方文档,发现官网说了,不支持config set操作, 看到这里我的眼泪掉下来。好不容易在本地测试通过了,此路居然不通。

 

到这里我就去查找如何避免spring data redis去调用config set 命令呢? 我去查看

org.springframework.data.redis.connection.jedis.JedisConnection 的代码,发现有if提前返回的? 分别是在
pipline和transaction模式下可以提前返回,后来继续查发现这两个其实仅仅是延迟提交,实际上最后还是要提交。
到这里我几乎就要放弃了,开始想转向用jdbc的方式了,不过每次请求都读一遍数据库实在是不是我愿意看到的。
继续看spring -session文档,customcookie也不能干这事。

    public void setConfig(String param, String value) {
        try {
            if (isPipelined()) {
                pipeline(new JedisStatusResult(pipeline.configSet(param, value)));
                return;
            }
            if (isQueueing()) {
                transaction(new JedisStatusResult(transaction.configSet(param, value)));
                return;
            }
            jedis.configSet(param, value);
        } catch (Exception ex) {
            throw convertJedisAccessException(ex);
        }
    }

 

我再看看原始调用的地方是干啥的ConfigureNotifyKeyspaceEventsAction仔细看这个类,发现他是用来测试redis是否支持
notify-keyspace-events这个的,如果不支持就把他开启起来。

Ensures that Redis Keyspace events for Generic commands and Expired events are enabled

 

那么怎么才能不让这个东西执行了,搜索ConfigureNotifyKeyspaceEventsAction还真的找到了线索,感谢这个人提供的参考建议

http://xxgblog.com/2016/09/29/spring-session-redis/

原来大家都遇到了同样的问题,果真有办法关闭,那么怎么关闭呢? 很简单,在你的autoconfig文件里添加这么一个东西,就可以关闭检查。

@Configuration
@EnableRedisHttpSession
public class HttpSessionConfig {

    @Bean
    public static ConfigureRedisAction configureRedisAction() {
        return ConfigureRedisAction.NO_OP;
    }
}

 

 

添加好了之后,去检查,发现应用正常启动,再到redis去看, 登陆成功之后,session数据成功写入redis,大功告成。

 

原来我离正确答案这么近,差点我就放弃了。

 


龙安_任天兵: 不忘初心,方得始终!