Nginx+Tomcat负载平衡,Redis管理session存储

阅读更多

本文的环境:

              1.CentOs6.4 minimal

              2.jdk1.7.0_71

              3.Tomcat7.0.65

              4.Nginx1.8

              5.Redis3.0.5

              6.vm11

环境配置不多说了,假设环境都已经装好。我虚拟机的ip地址是192.168.233.129 后面就不再解释了,直接用。

 

修改nginx配置信息    cd /usr/local/nginx/conf

vim nginx.conf

配置负载均衡 ,增加下面的配置 

upstream localhost {

         server      localhost:8080;

         server      localhost:8081;          

 }

 

server的location 中增加

proxy_pass   http://localhost;

 

ok 重启下nginx  cd /usr/local/nginx/sbin

./nginx -s reload

接下来访问http://192.168.233.129

ok 能够正常访问,显示了tomcat默认页,表示配置成功。

nginx 负载均衡默认是用轮询的方式,为了能够看到每次请求都被转发到了不同的tomcat上的效果,我们分别在webapps下ROOT下增加test.jsp  简单的一句话来表示这是tomcat1还是tomcat2。

 

ok 增加好之后再访问http://192.168.233.129/test.jsp 就显示tomcat1或者tomcat2,刷新页面,就会交替显示tomcat1  tomcat2.

 


Nginx+Tomcat负载平衡,Redis管理session存储_第1张图片
Nginx+Tomcat负载平衡,Redis管理session存储_第2张图片
 
 ok 如果能够交替显示表示负载均衡配置成功了。

 

ok 接下来就要解决session的问题了,打开F12看cookie,发现每次刷新都是新的sessionid,这样肯定是不行的,解决集群环境下的session共享有很多实现的方式,可以通过在upstream下配置 ip_hash;来根据ip指定到固定一台tomcat服务器来解决session的问题。配置完后之后再重启nginx    再刷新页面发现sessionid不变了,无论怎么刷新sessionid都不会变了,而且页面只显示tomcat1,说明现在是固定的访问tomcat1服务器了,而不是轮询的方式。

 

ok 到这里为止,我都非常的顺利,几乎没什么问题,但在配置redis来管理session的时候,问题就来了,而且还是相当的蛋疼,花了一天的时间终于解决了,也是我写这篇的主要原因。

 

ok 接下来我们要实现的是用轮询的方式实现session共享,本文用的方式是用redis来集中管理session,当一个请求过来,tomcat是去redis里找这个sessionid是否存在。

 

首先先把tomcat的依赖包分别拷到tomcat/lib目录下,我一开始用的包是

1.tomcat-redis-session-manager-1.2-tomcat-7.jar
2.jedis-2.1.0.jar
3.commons-pool-1.6.jar
 

 

然后分别修改tomcat下的context.xml配置文件,增加如下配置

 

 



    ok,配置好了,但是在启动tomcat的时候报错了,错误信息如下:
caused by: org.apache.catalina.LifecycleException: Unable to attach to session handling valve; sessions cannot be saved after the request without the valve starting properly.
 借助有道词典看的懂错误的大概意思,但是不知道怎么解决,不用怕,把错误信息复制一下到网上查一下,肯定能解决,因为毕竟我又不是第一个人配置这玩意,别人肯定也会碰到这问题。一点都不用慌,嗯,抱着这样的心态上网查了半天,结果居然没有一个人碰到这问题,我简直日了狗了。
于是看看别人的博客看别人是怎么做的, 几乎所有的博客都说要去 tomcat-redis-session-manager库的主页
https://github.com/jcoleman/tomcat-redis-session-manager
下载源码,修改build.gradle的依赖包版本,改成你自己的版本,然后重新构建项目,用构建生成的jar包。
不就是要编译打包嘛,为什么要弄的这么麻烦,而且也没用过gradle来构建项目,所以我是直接把源码拷到eclipse,注意这个项目有很多分支和tags,有些比较早的版本是依赖common-pool的jar包,我是下的master的分支,所以加的是common-pool2的jar包,要不然会报找不到类的错误,把依赖包加进来,通过export jar文件,来生成jar包。到这里,我的jar包变成了下面的样子
1.common-pool2-2.4.2.jar
2.jedis-2.5.1.jar
3.tomcat-redis-session-manager-1.2-mine.jar(自己通过源码编译生成的)
 
ok,把这3个jar包去替换原来的3个jar,然后还要修改一下context.xml配置文件,否则会提示找不到类,因为master分支的包名变了,修改后的context.xml文件如下
  
 
 
重启tomcat,嗯,感觉这次肯定没问题了。
结果,还是报同样的错,我简直日了狗了,难道真的要用gradle来构建项目才行?这不科学啊,不就是编译几个java类打成jar包吗,改不改build.gradle依赖包版本应该没有半毛钱关系,又不会影响源代码。但是呢,实在没有办法了,还是决定试一下吧。我想gradle应该类似maven应该很简单吧,于是下了个gradle-2.8。通过修改build.gradle的依赖版本,通过 gradle bulid的命令来构建项目,这里也有一些坑,只改依赖版本,根本构建不成功,不过构建过程碰到的问题,网上都找到了
一个是需要把maven私服给注释了,因为没帐号密码,直接让它去maven中央仓库找,或者你可以配开源中国的,速度快点,不过也就才几个包,无所谓拉,懒的搞,另外一个是需要把 signing那一段注释掉。修改后build.gradle文件如下
apply plugin: 'java'
apply plugin: 'maven'
apply plugin: 'signing'

group = 'com.orangefunction'
version = '2.0.0'

repositories {
  mavenCentral()
}

compileJava {
  sourceCompatibility = 1.7
  targetCompatibility = 1.7
}

dependencies {
  compile group: 'org.apache.tomcat', name: 'tomcat-catalina', version: '7.0.65'
  compile group: 'redis.clients', name: 'jedis', version: '2.5.1'
  compile group: 'org.apache.commons', name: 'commons-pool2', version: '2.4.2'
  //compile group: 'commons-codec', name: 'commons-codec', version: '1.9'

  testCompile group: 'junit', name: 'junit', version: '4.+'
  testCompile 'org.hamcrest:hamcrest-core:1.3'
  testCompile 'org.hamcrest:hamcrest-library:1.3'
  testCompile 'org.mockito:mockito-all:1.9.5'
  testCompile group: 'org.apache.tomcat', name: 'tomcat-coyote', version: '7.0.65'
}

task javadocJar(type: Jar, dependsOn: javadoc) {
  classifier = 'javadoc'
  from 'build/docs/javadoc'
}

task sourcesJar(type: Jar) {
  from sourceSets.main.allSource
  classifier = 'sources'
}

artifacts {
  archives jar

  archives javadocJar
  archives sourcesJar
}

//signing {
//  sign configurations.archives
//}

task copyJars(type: Copy) {
  from configurations.runtime
  into 'dist'  
}

uploadArchives {
  repositories {
    mavenDeployer {
      beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) }

      //repository(url: "https://oss.sonatype.org/service/local/staging/deploy/maven2/") {
      //  authentication(userName: sonatypeUsername, password: sonatypePassword)
      //}
      //repository(url: "https://oss.sonatype.org/content/repositories/snapshots") {
      //  authentication(userName: sonatypeUsername, password: sonatypePassword)
      //}

      pom.project {
        name 'tomcat-redis-session-manager'
        packaging 'jar'
        description 'Tomcat Redis Session Manager is a Tomcat extension to store sessions in Redis'
        url 'https://github.com/jcoleman/tomcat-redis-session-manager'

        issueManagement {
          url 'https://github.com:jcoleman/tomcat-redis-session-manager/issues'
          system 'GitHub Issues'
        }

        scm {
          url 'https://github.com:jcoleman/tomcat-redis-session-manager'
          connection 'scm:git:git://github.com/jcoleman/tomcat-redis-session-manager.git'
          developerConnection 'scm:git:[email protected]:jcoleman/tomcat-redis-session-manager.git'
        }

        licenses {
          license {
            name 'MIT'
            url 'http://opensource.org/licenses/MIT'
            distribution 'repo'
          }
        }

        developers {
          developer {
            id 'jcoleman'
            name 'James Coleman'
            email '[email protected]'
            url 'https://github.com/jcoleman'
          }
        }
      }
    }
  }
}

 

 

 其实根本不需要改依赖版本,直接用默认的就可以了,原因我上面说过了。

ok,我们再用gradle build 来构建项目,这次终于成功了,看构建日志会发现,他会去下载依赖包,所以如果用gradle来构建的话,就不需要再另外去下载jar包了,,gradle已经帮你下载了,跟maven类似,也有个本地仓库,在用户目录下\.gradle\caches\modules-2\files-2.1,只要把这jar包拷过去就可以了。

 

ok 这下总可以了吧,把构建生成的jar包再替换tomcat中的。然后再重启

 

结果你猜怎么着,没错,还是报同样的错误,我简直又日了狗了,简直奔溃,因为网上根本搜不到这个问题的解决方案

Stack Overflow上也去查了。

 

试了各种的方法,还去专门深入了解了一下tomcat的原理,都没有什么卵用,好吧,只能根据错误提示,去源码里查原因了,找到出错的那一行,

 

/**
   * Start this component and implement the requirements
   * of {@link org.apache.catalina.util.LifecycleBase#startInternal()}.
   *
   * @exception LifecycleException if this component detects a fatal error
   *  that prevents this component from being used
   */
  @Override
  protected synchronized void startInternal() throws LifecycleException {
    super.startInternal();

    setState(LifecycleState.STARTING);

    Boolean attachedToValve = false;
    for (Valve valve : getContainer().getPipeline().getValves()) {
      if (valve instanceof RedisSessionHandlerValve) {
        this.handlerValve = (RedisSessionHandlerValve) valve;
        this.handlerValve.setRedisSessionManager(this);
        log.info("Attached to RedisSessionHandlerValve");
        attachedToValve = true;
        break;
      }
    }

    if (!attachedToValve) {
      String error = "Unable to attach to session handling valve; sessions cannot be saved after the request without the valve starting properly.";
      log.fatal(error);
      throw new LifecycleException(error);
    }

    try {
      initializeSerializer();
    } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
      log.fatal("Unable to load serializer", e);
      throw new LifecycleException(e);
    }

    log.info("Will expire sessions after " + getMaxInactiveInterval() + " seconds");

    initializeDatabaseConnection();

    setDistributable(true);
  }
 

 

 看抛出的异常说明attachedToValve是false,说明if (valve instanceof RedisSessionHandlerValve) 这句是false。

ok,为了能够看清这个valve到底是什么东西,于是在这句之前加个日志打印一下看是什么,这里写日志的时候一开始把valve写成value了,我靠,我一直以为是value,长的太他妈像了。

 

for (Valve valve : getContainer().getPipeline().getValves()) {
    	log.info("valve======================>" + valve.getClass().getName());
      if (valve instanceof RedisSessionHandlerValve) {
        this.handlerValve = (RedisSessionHandlerValve) valve;
        this.handlerValve.setRedisSessionManager(this);
        log.info("Attached to RedisSessionHandlerValve");
        attachedToValve = true;
        break;
      }
    }
 

 

 ok,改完之后,直接用eclipse的export 重新打成jar包来替换tomcat中的,这时候可以看到刚刚添加的日志

 

INFO: valve======================>org.apache.catalina.core.StandardContextValve
 

 

 ok,valve 果然不是RedisSessionHandlerValve,看来valve没取到值,是不是在context.xml中配置有问题?

于是又去检查了一遍,发现RedisSessionHandlerValve类名没有写错,于是又去看了一下打印的日志。

这一次看,有惊天大突破,平时waring的日志啊什么的肯定都是不看的,但是这次发现这个waring有点特别

 

WARNING:   No rules found matching 'Context/Value'.
 

 

总感觉报错跟这个有关,警告说没有匹配的规则,那应该还是配置文件的问题了,到这里的时候突然想到写日志的时候把valve写成value了,于是去官网确认了一下,到底是value还是valve。


Nginx+Tomcat负载平衡,Redis管理session存储_第3张图片
 

 好吧,果然是写错了,简直崩溃,看来下次配置文件应该用复制的,而不是自己手打。把value改成valve之后,再重启tomcat,ok,终于不报错了。把2台tomcat跑起来,测试下redis管理session的效果。

        可以看到刷新页面,tomcat1和tomcat2交替显示,并且sessionid不变。

        然后去redis 用命令看一下,keys * ,发现存了一个session,ok,到此Nginx负载均衡+Tomcat集群+Redis管理session终于配置成功。

          既然是配置文件写错了,那我用之前的3个jar可以吗,于是又把最开始的3个jar包来替换tomcat中的,记得还要改下context.xml配置,因为包名改了,之前说过的。测试了之后发现一点问题也没有,完全可以用最开始的那3个jar包。

          最后来个非常非常重要的总结,你前面可以都不看,但是总结你应该看看,因为很重要,所以字体要大点。

 

 

总结:用Tomcat通过redis来管理session所需要用到的依赖包tomcat-redis-session-manager.jar,完全没有必要自己去下载源码,然后修改build.gradle的依赖包的版本,再重新构建。真的,我不知道为什么博客几乎所有有关redis管理session的文章都说要自己重新构建,真的没必要。还有的人说如果用jedis-2.1.jar则要用commons-pool jar包,如果要jedis-2.5以上则要用commons-pool2的jar包,真的完全错误。我不知道他们自己有没有测试过。这里说下什么时候用commons-pool,什么时候用commons-pool2,因为commos-pool2的包名发生了变化,所以版本导不对会报找不到类异常。

 

你可以直接去github里下载已经编译好的jar包,比如tomcat-redis-session-manager-1.2-tomcat-7.jar,他里面依赖的是commons-pool版本的jar包,如果你想用commons-pool2,你可以把master分支源码拉下来自己打jar包,master里依赖的commons-pool2,看RedisSessionManager这个类也知道,

import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.apache.commons.pool2.impl.BaseObjectPoolConfig;
 

 

他导的是commons-pool2。

 

所以,最终你可以用我一开始用的3个jar包,也可以用我后来的3个jar包,我都测试过,都没问题。

 

ok,现在我把这两组jar包都上传,你们可以直接下载我的。

 

commons-pool2-2.4.2版本之所以比上面那个大那么多是因为,tomcat-redis-session-manager-1.2-mine.jar这个包把依赖包也打进来了,所以,应该只要拷这个到tomcat下就可以了,我没试过,有兴趣的可以试一下。

 

 

  • Nginx+Tomcat负载平衡,Redis管理session存储_第4张图片
  • 大小: 18.7 KB
  • Nginx+Tomcat负载平衡,Redis管理session存储_第5张图片
  • 大小: 19.1 KB
  • Nginx+Tomcat负载平衡,Redis管理session存储_第6张图片
  • 大小: 31.5 KB
  • commons-pool-1.6版本.zip (240.1 KB)
  • 下载次数: 6
  • commons-pool2-2.4.2版本.zip (2.5 MB)
  • 下载次数: 11
  • 查看图片附件

你可能感兴趣的:(nginx,redis,tomcat集群,负载均衡,session共享)