目前公司网站只有一个tomcat和mysql服务器,全部托管在阿里云服务平台,稳定运行三年,基本上没出现大的问题。所以,不要总觉得单点显的很低级,优化做好,一样没问题。还是那句话,适合自己的才是最重要的,我也相信业务驱动技术,不过由于公司的发展比较快,网站的查询效率就显得尤为重要。所以,预计搭建一个小的集群环境来承载大量用户同一时间来访问的问题,以前我这块使用ehcache来当作二级缓存,其实也很好,速度也非常的快,但是毕竟是插件类的,分布式的支持也没有nosql的多,于是考虑一下,还是用redis来做吧。
先准备环境,项目采用spring+jpa+struct2的结构,前端采用nginx来做反向代理(nginx做反向代理,完全能够避免我们的应用程序直接暴露在用户的面前,同时能够做到动静分离,也就是说静态文件类似于图片,完全不用走程序,直接通过I/O留读取,效率可想而知,而且可以写一些shell脚本,阻断恶意用户的访问,使之进入不到程序入口),中间搭建一个redis缓存服务器,下设两个应用服务器tomcat,然后redis缓存服务器在阿里云的价格也不贵,一年一万多块钱足够,完全适合小型企业。思路是就是这样。
准备开发工具,开发环境都在windows下进行,开发eclipse,下载nginx的window版本和reids的window版本,以及reids需要的jar包,本项目没有采用maven,所以jar包都在网上下载的,稍后我把需要的东西附件中上传,省着大家下载了。
第一步,启动nginx,把下载的nginx放在c盘,然后进入命令行,找到对应的盘符,输入start nginx,启动完毕,时间太长,忘记了是否需要配置环境变量了,这个默认端口是80的,不过可以修改,修改文件在conf下的nginx.conf里面。
启动成功的页面效果,然后输入localhost,出现welcome nginx页面,说明启动成功
第二步,修改nginx的配置文件,让它适合集群环境。打开nginx下的nginx.conf文件,修改地方在下图,第一个红线指的地方为两台tomcat服务器的地址,权重一样,第二个红线是监听的nginx的地址,也就是说,我只要访问192.168.1.118,那么nginx就会随机的派发请求到上面的两台服务器里面,修改完毕后进行保存。然后重启,记住,nginx的重启要nginx -s reload一下,否则重启不生效。至此,nigix的环境搭建完毕。
第三步,搭建redis缓存服务器,启动reids服务器,点击如下图
出现如下图
输入1,回车,启动成功,如果发现第一次能启动成功,但是用着用着就报内存不足的话,需要修改redis的配置文件,打开redis.conf文件,找到如下图的地方,按照红色箭头的格式进行添加
至此,reids缓存服务器也开启成功,那么选择一个可视化的工具吧,否则没办法查看redis到底存了什么东西。采用redis-desktop-manager这个工具,挺好用的,打开的效果如下图,非常简单,一看就知道怎么用
第四歩:分布式最先要解决的就是session问题,tomcat本身提供了广播机制来进行session的共享,但是这种方案,我并没有采取,本身就用redis作为缓存,那么就不用tomcat本身的广播机制了,再有,广播机制毕竟占有带宽,而且tomcat服务器一多,出现问题都不好定位。spring已经集成了redis来解决session共享问题。
需要的jar包如下,记住,版本一定要对,否则,启动会报各种找不到或者实例化的错误,jar包如下图添加即可。
commons-pool2-2.4.2
jedis-2.4.1.jar
spring-data-redis-1.4.1.RELEASE
spring-session-1.2.0.RELEASE
jar添加完毕后,需要在web.xml里面和spring的配置文件里面分别添加
记住,要加在所有过滤器的最前面,spring的配置文件里面加上的也要靠前,至于poolConfig可以先不加,后续会介绍。至此,spring +redis整合完毕,两个tomcat一起启动,启动之后,通过redis可视化工具可以发现session已经存入到redis里面,如下图。
第五步:改写Captcha的存储方式,本项目的验证码采用的是jcaptcha-1.6的验证码,这个验证码在单个tomcat里面没问题,因为它的生成规则是这样的,生成的验证码会存在jvm里面,这样,如果登录的时候是tomcat1,那么这个验证码就存在了tomcat1里面,而验证的时候正好是tomcat2,因为tomcat2里面根本没有存验证码,那么就会报验证码输入错误的问题了。为了解决这个问题,我百度了所有的方式,发现都不适合我的项目,于是,只能从源码下手。先说一下本项目生成验证码的方式比较简单,代码如下,就三个方法而已
/**
* 生产校验码
* @throws IOException
*/
protected voidgenernateCaptchaImage() throws IOException {
response.setHeader("Cache-Control","no-store");
response.setHeader("Pragma","no-cache");
response.setDateHeader("Expires",0);
response.setContentType("image/jpeg");
ServletOutputStream out =response.getOutputStream();
try {
String captchaId =request.getSession(true).getId();
BufferedImage challenge =(BufferedImage) CaptchaServiceSingleton.getInstance().getChallengeForID(captchaId,request.getLocale());
ImageIO.write(challenge,"jpg", out);
out.flush();
} catch (CaptchaServiceException e) {
} finally {
out.close();
}
}
和
publicclass CaptchaServiceSingleton {
private static ImageCaptchaService instance = newDefaultManageableImageCaptchaService(
new FastHashMapCaptchaStore(),new RdImageEngine(), 180,
100000 , 75000);
/* private static ImageCaptchaService instance = newDefaultManageableImageCaptchaService(
new FastHashMapCaptchaStore(),new GMailEngine(), 180,
100000 , 75000); */
public static ImageCaptchaService getInstance(){
return instance;
}
}
RdImageEngine类是继承了ListImageCaptchaEngine
验证验证码是否正确的代码
protectedboolean checkValidImg(String valid){
if(isOpenValidCode()){
boolean b=false;
try {
b=CaptchaServiceSingleton.getInstance().validateResponseForID(request.getSession().getId(),valid.toLowerCase());
} catch (CaptchaServiceException e) {
logger.debug(e.getMessage());
b=false;
}
return b;
}else{
return true;
}
}
是不是特别简单,可以仔细看代码发现,生成验证码和校验验证码的代码全部封装好了,无法修改。而网上的解决办法我都试了,不是没看明白就是根本不行,于是打开jcaptcha的源码进行查看,找到如下图的地方
\jcaptcha-src-1.0-RC6\service\src\java\com\octo\captcha\service\image就是这个路径
找到DefaultManageableImageCaptchaService这个文件,至于原因,可以往上看,因为
privatestatic ImageCaptchaService instance = new DefaultManageableImageCaptchaService这个,所以要找到实现类,打开发现里面的代码是这样的
发现这里面还是没有getChallengeForID这个方法,为什么要找这个方法,因为要重写它,改变验证码的存储方式。
根据extends一层一层找,最终会找到原始方法的,这里就不找了,因为不管继承了多少层,DefaultManageableImageCaptchaService这个类里面一定继承了getChallengeForID这个方式,否则CaptchaServiceSingleton.getInstance().getChallengeForID(captchaId,request.getLocale()); 这个就不成立了,于是,重写这个类,让存储验证码的地方放在redis里面而不是jvm里面。重写的类如下
public class DefaultManageableImageCaptchaServiceChild extendsDefaultManageableImageCaptchaService {
/*private RedisCacheUtilredisCacheUtil;
public RedisCacheUtilgetRedisCacheUtil() {
return redisCacheUtil;
}
public voidsetRedisCacheUtil(RedisCacheUtil redisCacheUtil) {
this.redisCacheUtil = redisCacheUtil;
}*/
publicDefaultManageableImageCaptchaServiceChild() {}
publicDefaultManageableImageCaptchaServiceChild(
FastHashMapCaptchaStorefastHashMapCaptchaStore,
RdImageEngine rdImageEngine, int i,int j, int k) {
super(fastHashMapCaptchaStore,rdImageEngine, i,
j, k);
}
public ObjectgetChallengeForID(String ID, Locale locale)
throws CaptchaServiceException {
Captchacaptcha;
Objectchallenge;
captcha =(Captcha) RedisCacheUtil.get("captcha"+ID);
//else get it
// captcha = this.store.getCaptcha(ID);
// captcha = storeRedis.getCaptcha(ID);
if (captcha == null) {
captcha = generateAndStoreCaptcha(locale, ID);
} else {
//if dirty
if (captcha.hasGetChalengeBeenCalled().booleanValue()) {
//get a new one and store it
captcha = generateAndStoreCaptcha(locale,ID);
} else {
//else nothing
}
}
//checkif has capthca
/* if(!this.store.hasCaptcha(ID)) {
//if not generate and store
captcha = generateAndStoreCaptcha(locale, ID);
} else {
//else get it
captcha = this.store.getCaptcha(ID);
if (captcha == null) {
captcha = generateAndStoreCaptcha(locale, ID);
} else {
//if dirty
if (captcha.hasGetChalengeBeenCalled().booleanValue()) {
//get a new one and store it
captcha = generateAndStoreCaptcha(locale,ID);
} else {
//else nothing
}
}
}*/
challenge= getChallengeClone(captcha);
captcha.disposeChallenge();
returnchallenge;
}
public BooleanvalidateResponseForID(String ID, Object response)
throws CaptchaServiceException {
Captcha captcha;
captcha = (Captcha)RedisCacheUtil.get("captcha"+ID);
if (captcha==null) {
throw newCaptchaServiceException("captcha is null ,please check baseAction or DefaultManageable...");
} else {
Boolean valid =captcha.validateResponse(response);
RedisCacheUtil.del("captcha"+ID);
return valid;
}
}
public CaptchagenerateAndStoreCaptcha(Locale locale, String ID) {
Captchacaptcha = engine.getNextCaptcha(locale);
this.store.storeCaptcha(ID, captcha, locale);
RedisCacheUtil.del("captcha"+ID);
RedisCacheUtil.set("captcha"+ID, captcha, 600);
returncaptcha;
}
}
重新完之后再上面new对象的时候要写成子类
ImageCaptchaServiceinstance = new DefaultManageableImageCaptchaServiceChild(
new FastHashMapCaptchaStore(),new RdImageEngine(), 180,
100000 , 75000);
这样的话,生成验证码的地方就会调用子类了,而且会发现,上述地方只要是生成验证码和校验验证码的地方全部改成程redis来实现,至此验证码的存储地点改写完成
第六部 编写redisUtil的工具类,这块也就是在第四歩为什么要写poolConfig的原因了
编写RedisCacheUtil类
publicclass RedisCacheUtil
{
@Autowired
private staticRedisTemplate
public RedisTemplate
return redisTemplate;
}
public voidsetRedisTemplate(RedisTemplate
this.redisTemplate = redisTemplate;
}
/**
* 删除缓存
* @param key 可以传一个值 或多个
*/
@SuppressWarnings("unchecked")
public static void del(String ... key){
if(key!=null&&key.length>0){
if(key.length==1){
redisTemplate.delete(key[0]);
}else{
redisTemplate.delete(CollectionUtils.arrayToList(key));
}
}
}
/**
* 普通缓存获取
* @param key 键
* @return 值
*/
public static Object get(String key){
returnkey==null?null:redisTemplate.opsForValue().get(key);
}
/**
* 普通缓存放入
* @param key 键
* @param value 值
* @return true成功 false失败
*/
public static boolean set(String key,Object value) {
try {
redisTemplate.opsForValue().set(key,value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
}
这个类写完的时候,重启程序发现redisTemplate为null,没有注入进来。找到spring的配置文件,写下
因为要把这个类当作bean来进行管理,spring才会进行注入,而redisTemplate是java提供的操作redis的模板,模板里面写的都是一些配置参数,但是要区分StringRedisTemplate和redisTemplate,这两个是完全不一样的,我这块是这么弄的,如果存字符串对象,采用StringRedisTemplate,存储java对象采用redisTemplate,也就是说,需要把redisTemplate的配置文件全部复制一份,变成StringRedisTemplate。虽然两个模板不通用,但是保证了不侵入式的编码。而且加好备注,我相信没啥问题。然后重启项目,输入验证码发现如下图,验证码的key已经存入进来了,并且验证也通过。
至此,spring+redis并解决验证码存储问题全部完事,在实现的过程中,网上有些资料的确给人很大的帮助,但是要结合项目本身的情况,多看看实现的源码,所有的问题就都不复杂了。把所有的东西都上传了,如果没有分的话也自己自己去网上搜索资源,jar包的版本已经写了,然后redis和nginx以及可视化工具,jcaptcha的源码,需要的东西就这么多。
https://download.csdn.net/download/tianya0138/10303534