CAS横向扩展落地方案讲解,以及代码讲解

伴随着系统上线,各种问题接踵而来,首当其冲的就是登陆问题。我先大概介绍下问题情况

经过压测我们服务器可承受的的单台CAS的最大并发数是700用户,但是按照原先设计最起码要求达到1400-2000左右,我们配置了2台CAS服务器,架构上WEB负载采用的是硬件A10进行轮播,1400是可以达到的,但是随着并发的提高经常会出现401的问题。经过漫长的源码分析最后发现了问题,CAS的默认配置中,他无法横向扩展。也就是说,通过CAS来产生登录的凭证只能本机生效,现在有2台CAS服务器,用户登是用的A服务器的产生的凭证,这个时候如果访问某个应用的时候需要验证单点A10轮播到了B服务器,那么B服务器上是没有凭证的,这个时候就会出现凭证不存在的问题(浏览器401)。


问题既然找到了,那么就好办了,最后综合考虑,我们添加了一台redis缓存服务器用来存放凭证,并且对CAS的源代码进行了扩展达到了凭证共享。


代码上的操作如下。

1.修改WEB-INF\spring-configuration\ticketRegistry.xml文件。修改注册方式

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">
    <description>
    	Configuration for the default TicketRegistry which stores the tickets in-memory and cleans them out as specified intervals.
    </description>
       
    <!-- 修改注册方式改为自己扩展原生的CAS注册代码-->
	<bean id="ticketRegistry" class="com.cas.ticket.RedisTicketRegistry" />
	
	<!--Quartz -->
	<!-- TICKET REGISTRY CLEANER -->
	<bean id="ticketRegistryCleaner" class="org.jasig.cas.ticket.registry.support.DefaultTicketRegistryCleaner"
		p:ticketRegistry-ref="ticketRegistry" />
	
	<bean id="jobDetailTicketRegistryCleaner" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean"
		p:targetObject-ref="ticketRegistryCleaner"
		p:targetMethod="clean" />
	
	<bean id="triggerJobDetailTicketRegistryCleaner" class="org.springframework.scheduling.quartz.SimpleTriggerBean"
		p:jobDetail-ref="jobDetailTicketRegistryCleaner"
		p:startDelay="20000"
		p:repeatInterval="5000000" />
</beans>

2.com.cas.ticket.RedisTicketRegistry讲解

package com.cas.ticket;

import org.jasig.cas.ticket.registry.AbstractDistributedTicketRegistry;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Collection;

import org.jasig.cas.ticket.Ticket;
import org.jasig.cas.ticket.TicketGrantingTicket;
import org.jasig.cas.ticket.registry.AbstractDistributedTicketRegistry;

/***
 * 
  * @Title: RedisTicketRegistry 
  * @Description: TODO(CAS分布式注册凭证生成) 
  * @Copyright: Copyright (c) 2015-2020
  * @Company:CCS
  * @author sunwenbo 
  * @date 2018年5月23日 下午11:55:48 
  *
 */
public class RedisTicketRegistry extends AbstractDistributedTicketRegistry {



	/**
	 * ST最大空闲时间
	 */
	private static int st_time;

	/**
	 * TGT最大空闲时间
	 */
	private static int tgt_time;
	
	/***
	 * (非 Javadoc) 
	  * <p>Title: addTicket</p> 
	  * <p>Description:注册凭证 </p> 
	  * @param ticket 
	  * @see org.jasig.cas.ticket.registry.TicketRegistry#addTicket(org.jasig.cas.ticket.Ticket)
	 */
	@Override
	public void addTicket(Ticket ticket) {
		//设置redis失效时间,这个必须设置,不然容易造成缓存节点爆掉
		int seconds = 3600 * 12 * 1000;

		String key = ticket.getId();
		
		if (ticket instanceof TicketGrantingTicket) {
			seconds = tgt_time / 1000;
		} else {
			seconds = st_time / 1000;
		}

		ByteArrayOutputStream bos = new ByteArrayOutputStream();
		ObjectOutputStream oos = null;

		try {

			oos = new ObjectOutputStream(bos);

			oos.writeObject(ticket);

		} catch (Exception e) {
			e.printStackTrace();
			System.out.println("adding ticket to redis error.");

		} finally {
			try {
				if (null != oos)
					oos.close();

			} catch (Exception e) {
				e.printStackTrace();
				System.out
						.println("oos closing error when adding ticket to redis.");

			}
		}
		//设置redis,这个是自己写的工具类,也可以自己写个工具类
		RedisUtils.setObj(key, ticket, seconds);

	}
	
	/***
	 * (非 Javadoc) 
	  * <p>Title: deleteTicket</p> 
	  * <p>Description: 删除凭证</p> 
	  * @param ticketId
	  * @return 
	  * @see org.jasig.cas.ticket.registry.TicketRegistry#deleteTicket(java.lang.String)
	 */
	@Override
	public boolean deleteTicket(String ticketId) {
		if (ticketId == null) {
			return false;
		}

		RedisUtils.del(ticketId);

		return true;
	}

	@Override
	public Ticket getTicket(String ticketId) {
		return getRawTicket(ticketId);
	}
	
	/***
	 * 
	  * @Title: getRawTicket 
	  * @Description: TODO(这个是关键的方法,CAS登录会调用此方法验证) 
	  * @param @param ticketId
	  * @param @return    设定参数 
	  * @return Ticket    返回类型 
	  * @throws
	 */
	private Ticket getRawTicket(final String ticketId) {
		if (null == ticketId) {
			return null;
		}

		if (RedisUtils.getObj(ticketId) == null) {
			return null;
		}
		Ticket ticket = (Ticket) RedisUtils.getObj(ticketId);

		return ticket;
	}

	@Override
	public Collection<Ticket> getTickets() {
		throw new UnsupportedOperationException("GetTickets not supported.");
	}

	protected boolean needsCallback() {
		return false;
	}

	@Override
	protected void updateTicket(Ticket ticket) {
		this.addTicket(ticket);
	}
}

3.redis配置文件放在webapp\WEB-INF\spring-configuration目录下,在WEB.XML中配置下容器启动的时候回自动扫描。

4.RedisUtils工具类

package com.cas.ticket;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;




import redis.clients.jedis.JedisCluster;

public class RedisUtils {

	public JedisCluster jedisCluster;

	private static RedisUtils redisUtils;

	public void setJedisCluster(JedisCluster jedisCluster) {
		this.jedisCluster = jedisCluster;
	}

	public void init() {
		redisUtils = this;
		redisUtils.jedisCluster = this.jedisCluster;
	}

	/**
	 * 
	 * TODO 基本写入缓存方法
	 * 
	 * @Title com.paas.common.jedis.RedisUtils.java
	 * @author <a href="mailto:[email protected]">chengyanhua</a>
	 * @param key
	 * @param value
	 * @param cacheSeconds
	 *            单位:秒(永不超时置为0)
	 */
	public static String set(String key, String value, int cacheSeconds) {
		if (cacheSeconds == 0) {
			return redisUtils.jedisCluster.set(key, value);
		} else {
			return redisUtils.jedisCluster.setex(key, cacheSeconds, value);
		}
	}

	/**
	 * 
	 * TODO 基本的取值方法
	 * 
	 * @Title com.paas.common.jedis.RedisUtils.java
	 * @author <a href="mailto:[email protected]">chengyanhua</a>
	 * @param key
	 * @return
	 */
	public static String get(String key) {
		return redisUtils.jedisCluster.get(key);
	}

	/**
	 * 
	 * TODO redis删除缓存
	 * 
	 * @Title com.paas.common.jedis.RedisUtils.java
	 * @author <a href="mailto:[email protected]">chengyanhua</a>
	 * @param key
	 */
	public static void del(String key) {
		
		redisUtils.jedisCluster.del(key);
		redisUtils.jedisCluster.del(key.getBytes());
	}

	/**
	 * 
	 * TODO 是否存在指定key的缓存
	 * 
	 * @Title com.paas.common.jedis.RedisUtils.java
	 * @author <a href="mailto:[email protected]">chengyanhua</a>
	 * @param key
	 * @return
	 */
	public static Boolean exists(String key) {
		return redisUtils.jedisCluster.exists(key)
				|| redisUtils.jedisCluster.exists(key.getBytes());
	}

	/**
	 * 
	 * TODO 缓存redis存入单个对象
	 * 
	 * @Title com.paas.common.jedis.RedisUtils.java
	 * @author <a href="mailto:[email protected]">chengyanhua</a>
	 * @param key
	 * @param obj
	 * @param cacheSeconds
	 *            单位:秒(永不超时置为0)
	 */
	public static String setObj(String key, Object obj, int cacheSeconds) {

		String s="";
		if (cacheSeconds == 0) {

		
			s=redisUtils.jedisCluster.set(key.getBytes(), serialize(obj));
			
			return s;
		} else {
	
			s=redisUtils.jedisCluster.setex(key.getBytes(), cacheSeconds,
					serialize(obj));
			
			return s;
		}
	}

	/**
	 * 
	 * TODO 缓存redis获取单个对象
	 * 
	 * @Title com.paas.common.jedis.RedisUtils.java
	 * @author <a href="mailto:[email protected]">chengyanhua</a>
	 * @param key
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public static <T> T getObj(String key) {
		return (T) deserialize(redisUtils.jedisCluster.get(key.getBytes()));
	}

	/**
	 * 
	 * TODO 缓存redis存入对象List
	 * 
	 * @Title com.paas.common.jedis.RedisUtils.java
	 * @author <a href="mailto:[email protected]">chengyanhua</a>
	 * @param <T>
	 * @param key
	 * @param obj
	 * @param cacheSeconds
	 *            单位:秒(永不超时置为0)
	 */
	public static <T> void setObjList(String key, List<T> objList,
			int cacheSeconds) {
		redisUtils.jedisCluster.del(key.getBytes());
		for (Object o : objList) {
			redisUtils.jedisCluster.rpush(key.getBytes(), serialize(o));
		}
		if (cacheSeconds != 0) {
			redisUtils.jedisCluster.expire(key.getBytes(), cacheSeconds);
		}
	}

	/**
	 * 
	 * TODO 缓存redis获取多个对象
	 * 
	 * @Title com.paas.common.jedis.RedisUtils.java
	 * @author <a href="mailto:[email protected]">chengyanhua</a>
	 * @param key
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public static <T> List<T> getObjList(String key) {
		List<byte[]> lrange = redisUtils.jedisCluster.lrange(key.getBytes(), 0,
				-1);
		List<T> list = new ArrayList<T>();
		for (byte[] b : lrange) {
			list.add((T) deserialize(b));
		}
		return list;
	}
	/**
	 * 
	 * TODO 批量插入
	 * @Title com.paas.common.jedis.RedisUtils.java
	 * @author <a href="mailto:[email protected]">chengyanhua</a>
	 * @param map(其中每对键值对应一条缓存)
	 */
	public static void batchSetObject(Map<String, Object> map) {
		JedisClusterPipeline jcp = JedisClusterPipeline
				.pipelined(redisUtils.jedisCluster);
		jcp.refreshCluster();
		try {
		for (Entry en : map.entrySet()) {
			jcp.set(en.getKey().toString().getBytes(), serialize(en.getValue()));
		}
		jcp.syncAndReturnAll();
		}finally {
            jcp.close();
        }
	}

	public static void close(Closeable closeable) {
		if (closeable != null) {
			try {
				closeable.close();
			} catch (Exception e) {
			}
		}
	}

	public static byte[] serialize(Object value) {
		if (value == null) {
			throw new NullPointerException("Can't serialize null");
		}
		byte[] result = null;
		ByteArrayOutputStream bos = null;
		ObjectOutputStream os = null;
		try {
			bos = new ByteArrayOutputStream();
			os = new ObjectOutputStream(bos);
			os.writeObject(value);
			os.close();
			bos.close();
			result = bos.toByteArray();
		} catch (IOException e) {
			throw new IllegalArgumentException("Non-serializable object", e);
		} finally {
			close(os);
			close(bos);
		}
		return result;
	}

	public static Object deserialize(byte[] in) {
		Object result = null;
		ByteArrayInputStream bis = null;
		ObjectInputStream is = null;
		try {
			if (in != null) {
				bis = new ByteArrayInputStream(in);
				is = new ObjectInputStream(bis);
				result = is.readObject();
				is.close();
				bis.close();
			}
		} catch (IOException e) {
			throw new IllegalArgumentException(
					"Caught IOException decoding %d bytes of data", e);
		} catch (ClassNotFoundException e) {
			throw new IllegalArgumentException(
					"Caught CNFE decoding %d bytes of data", e);
		} finally {
			close(is);
			close(bis);
		}
		return result;
	}

}

写完后,发布,会发现不仅仅401的问题解决了,同时CAS的横向扩展的问题也解决,并且,使用了缓存后,避免了服务器生重复生成凭证导致的系统性能问题。

猜你喜欢

转载自blog.csdn.net/qq_20657953/article/details/80427950