聊聊从web session的共享到可扩展缓存设计

先从web session的共享说起

 

许多系统需要提供7*24小时服务,这类系统肯定需要考虑灾备问题,单台服务器如果宕机可能无法立马恢复使用,这必定影响到服务。这个问题对于系统规模来说,从小到大可能面临的难度会相差很大。但对于原理来说其实就是需要准备备份系统随时可以替代正在服务的系统,也就是无论何时都有服务器可以提供服务。也就是灾备系统或者负载均衡。

 

提供灾备系统或者负载均衡系统都需要面临一个问题,那就是如何解决共享数据的问题。
对于web服务器而言首先要解决的就是web session共享问题,比如A服务器的session如何可以在B服务器上也能一样使用呢?毕竟是物理隔离的两台服务器。

 

这方面的方案主要是两类:cookies和session共享。

 

cookies

这种方案的思路就是将session的数据写入到cookies里,每次请求的时候就可以带上信息,这样不管是哪台服务器都能得到同样的数据啦。这样不管换多少服务器都好处理。只不过这种方案需要在服务端开发时需要注意session的数据管理,而且需要接管session的生命周期。如果有一些老的系统可能session用的比较多,就不大好使了。而且将一些敏感数据写入session还要考虑安全问题,这对于一些数据敏感的系统也可能是个问题。

 

但如果能控制好session的数据这种方案个人觉得还是挺不错的,毕竟session并不适合存过多的数据。所以在我们的系统中是支持这种方案的,只需要打开开关参数就行。

 

session池化

还有一种方法就是把session共享出来,所有的服务器都连接到这个共享。这种方案可能是许多系统会使用的方案吧。
因为将session池化,对于系统而言就变成透明了。程序员终于开心的将数据写入session咯。
这种方案除了http服务器外,许多的tcp服务器也是类似的方案。

 

我们系统因为使用的java开发,使用tomcat时可以将session共享到memcached/redis中
而且这种操作完全不需要改动系统,直接在tomcat中配置即可。所以这种方案天然就支持啦。

 

 

做一个可扩展的缓存策略设计

原先的数据缓存都是放在jvm里的,所以机器多了每台服务器都要自己去加载缓存,这样一来命中就低。最近打算在系统里引入第三方缓存,当时在memcached和火的要死的redis里选择。现在来看每种内存产品都各有优势,如果硬生生的将现在这些老的缓存直接改成redis的如果以后需要用别的内存数据库又得大改代码。想到这就决定把缓存做一次设计,将现有的jvm缓存保留下来,然后做成策略以扩展新的缓存存储。

 

以前的许多缓存用的HashMap/ConcurrentHashMap,反正是键-对值。如果我们直接使用Map结构来作为缓存接口就可以不改变现有的一些代码,只需要改动缓存类内部的数据结构即可。这样的改动量就比较少。

 

比如原来的一些缓存单元结构:

public class RoleMenuCache implements IClearCache {
    private static Map<String, RoleMenu> roleMenuCache = new HashMap<String, RoleMenu>();
...业务代码省略
}

这里主要是替换这个HashMap所以改动就比较小。

 

 

先来看看类图

 

Cachemanager

这个就是缓存的管理类,用于创建、释放缓存对象。这个类是各个所有缓存申请的入口。下面贴出来主要的代码:

public class CacheManager {
    private final static Logger  logger = LoggerFactory.getLogger(CacheManager.class);
    private static Map<String, ICache> caches = new ConcurrentHashMap<>();
    private static ICacheStrategy cacheStrategy = new DefaultCacheStategy();
    private static String cacheStrategyClass;

    @SuppressWarnings("unchecked")
    public static synchronized <T extends ICache> T  getOrCreateCache(String cacheName, Class<?> keyClass, Class<?> valueCalss) {
       T cache = (T) caches.get(cacheName);
        if (cache != null) {
            return cache;
        }
        cache = (T) cacheStrategy.createCache(cacheName, keyClass, valueCalss);
        caches.put(cacheName, cache);
        return cache;
    }

    @SuppressWarnings("rawtypes")
    public static synchronized void destroyCache(String cacheName) {
        ICache cache = caches.remove(cacheName);
        if (cache != null) {
            cache.clear();
        }
    }

}

 

ICache<K,V>

这个接口是规范缓存类的接口,所有的缓存类都要实现这个接口,而且它是继承java.util.Map接口的,这样就支持了Map派生的类,兼容老程序就好多了。

 

ICacheStrategy

对于具体的缓存实现就有一套策略,有一个ICacheStrategy接口来规范。这么一来,不管是jvm还是redis都可以自己单独扩展来实现。

public interface ICacheStrategy {
   ICache createCache(String name, Class<?> keyClass, Class<?> valueCalss);
   void destroyCache(ICache cache);
}

 

看一下DefaultCache的实现(代码只放了一部分主要的):

public class DefaultCache<K, V> implements ICache<K, V> {
    protected Map<K, V> map;
    private String name;
    private long maxCacheSize;
    private long maxLifetime;
    private int cacheSize = 0;

    public DefaultCache(String name, long maxSize, long maxLifetime) {
        this.name = name;
        this.maxCacheSize = maxSize;
        this.maxLifetime = maxLifetime;
        map = new ConcurrentHashMap<K, V>(103);
    }

    @Override
    public V get(Object key) {
        return map.get(key);
    }

    @Override
    public V put(K key, V value) {
        return map.put(key, value);
    }

    @Override
    public V remove(Object key) {
        return map.remove(key);
    }

    @Override
    public void putAll(Map<? extends K, ? extends V> m) {
        map.putAll(m);
    }

    @Override
    public void clear() {
        if (map != null) {
            map.clear();
        }
    }

}

对于调用方来说其实就很简单,只需要调用CacheManager即可,还是前面举的RoleMenuCache

例子,我们改造一下:

public class RoleMenuCache implements IClearCache {
    private static Map<String, RoleMenu> roleMenuCache;
    static {
        roleMenuCache = CacheManager.getOrCreateCache("permissionCache", String.class, RoleMenu.class);
    }
...业务代码省略
}

对于老代码的改造还是比较小的,而且这样的好处是以后想换成redis的也很简单,对于业务代码就不需要再修改了。

 

遇到Redis与泛型的问题

 

在扩展redis缓存策略的时候遇到一个问题,就是使用的jedis时,对于key值都是使用的string类型,这就给我们使用泛型设计留下了难题。当然为了兼容现在的设计,最后用了JSON来解决。

 

但是新的问题来了,对于put时是这样的:

/**
 * 根据key设置map的值
 */
@Override
public V put(K key, V value) {
    jedisTemp.hset(name, JSON.toJSONString(key), JSON.toJSONString(value));
    return value;
}

这并没啥问题,因为对象转换成json串是正常的。问题是get的时候,我们使用的

alibaba.fastjson提供的接口并不能转回成具体类型的对象,因为get方法的的返回值是V类型,是泛型类型,没法得到class的type。

像这样的代码就不行啦:JSON.parseObject(json, V.class)。最后没办法,我只好把K和V的类型在创建时由调用者传入。看下面的代码里,两个红色的参数,当然这也没问题,毕竟调用者是知道类型的:

CacheManager.getOrCreateCache("permissionCache", String.class, RoleMenu.class);

最终get方法的实现就是这样:

@Override
public V get(Object key) {
    String json = jedisTemp.hget(name, JSON.toJSONString(key));
    return (V) JSON.parseObject(json, valueClass);
}

问题虽然是解决了,只不过总觉得怪怪的。

 

 

总结与反思

整套的设计受openfire的集群设计影响比较大,我基本是借鉴过来的,目前来看还是挺不错,最近准备尝试Ignite,非常容易就接入了系统。

 

只是openfire使用的是java实现的方案(Hazelcast/Coherence

),这些都是带Map结构的,并不会有我遇到的Redis的问题。但我觉得这套设计还挺不错,如果把map接口去掉,自己重新定义方法就可以解决这个问题,不使用泛型,当然这样对老代码的改动会比较大。

 

还有一种情况就是多种缓存产品并存,比如同时使用redis和memcached,现有的设计可能支持不了。但是因为入口限制在了CacheManager,我想加个泛型支持就可以解决。只是这种场景或许并不多见吧。

 

 

 

注:此文章为原创,欢迎转载,请在文章页面明显位置给出此文链接!

若您觉得这篇文章还不错请点击下右下角的推荐,非常感谢!

http://www.cnblogs.com/5207

http://www.cnblogs.com/5207/p/5788439.html

 

时间: 2024-11-01 05:40:37

聊聊从web session的共享到可扩展缓存设计的相关文章

JSP与ASP.Net之间的Session值共享

asp.net|js|session   介绍:ASP.NET中登录后,JSP可以用ASP.Net中的Session的值 这个话题刚开始,宝宝(itbaby.jss.cn)的思路是ASP.NET中序列化Session以二进制数据保存到数据库,然后由JSP读取数据库中的二进制数据反序列化成Session对 象,再强制转化成JAVA的Session对象,在JAVA端转换时,出现了错误,找遍网上的资料也没能解决,故采用一种替换的方式. 替换的方式的思路: 登录的ASPX文件中,在登录成功后将Sessi

tomcat+nginx+memcached+windows session不能共享

问题描述 tomcat+nginx+memcached+windows session不能共享 困扰我两天了. 使用tomcat7+nginx,实现负载均衡,并且测试通过. 但是session不能够共享,项目登录之后,进行其他操作时候提示登录,说明nginx转发请求到其他tomcat时候,丢失了session. 加入memcached,想要实现session共享. 按照网上的说明配置,启动,但是session并不能实现共享. 下载的jar,拷贝到tomcat/lib下面. memcached下载

apache2.2+tomcat负载均衡在SSH2项目中session无法共享!!!!!!

问题描述 apache2.2+tomcat负载均衡在SSH2项目中session无法共享!!!!!! 使用apache2.2和三个tomcat实例在同一台机器配置负载均衡成功,基本软件:apache.2.225Tomcat8.0.20Tomcat-connectors-1.2.40使用如下Jsp页面时显示session要以复制且sessionId相同,但是加载实际SSH2实际项目,则发现session丢失且每次都创建新的session请有类似配置经验或解决方案的同仁不吝赐教!<% HttpSes

java-不同web应用间共享页面

问题描述 不同web应用间共享页面 整个项目是分为两个web项目的一个前端一个后端.后端是独立的其他项目也可以使用所以就独立出了一个web应用但是后端项目中好多插件啊页面啊公用的JS啊.这些东西都可以是公用的封装好的一些东西现在每次起一个新的web项目都要把那一份复制过来.很麻烦.有什么办法可以在其他web项目中去引用那个web应用的资源呢?两个web项目是部署在同一个服务器同一个端口上. 解决方案 js之类的 按地址引用 可以不?类似于这种: 解决方案二: 写控制servlet来控制 解决方案

java-同一域名下 session无法共享的问题?

问题描述 同一域名下 session无法共享的问题? 我 tomcat中域名指向是/owm的路径. 如我访问了http://localhost/owm/index.jsp,则会把cookie记在/owm路径下 如我访问了http://localhost/indexjsp,则会把cookie记在/owm路径下 故在cookie中出现了2个jsessionid,因我的页面有的带上了/owm路径,有的直接是/路径,导致出现了重复要登录的情况. 请问各位高手帮忙看下该如何解决. 具体截图可以在这地址上看

java web session失效

问题描述 java web session失效 用myeclipse编写的Java web项目,request.getSession 设置session成功后,跳到别的页面却取不到session 解决方案 关于Web中Session失效java web工程,过滤器判断session失效java web工程,过滤器判断session失效 解决方案二: session是服务端用来跟踪状态的对象,request请求之后都会生成session而且有一定的失效时间 取不到session应该不太可能,请检查

使用Spring Session实现Spring Boot水平扩展

本文使用Spring Session实现了Spring Boot水平扩展,每个Spring Boot应用与其他水平扩展的Spring Boot一样,都能处理用户请求.如果宕机,Nginx会将请求反向代理到其他运行的Spring Boot应用上,如果系统需要增加吞吐量,只需要再启动更多的Spring Boot应用即可. Spring Boot应用通常会部署在多个Web服务器上同时提供服务,这样做有很多好处: 单个应用宕机不会停止服务,升级应用可以逐个升级而不必停止服务. 提高了应用整体的吞吐量.

基于MVC4+EasyUI的Web开发框架形成之旅--MVC控制器的设计

自从上篇<基于MVC4+EasyUI的Web开发框架形成之旅--总体介绍>总体性的概括,得到很多同行的关注和支持,不过上一篇主要是介绍一个总体的界面效果和思路,本系列的文章将逐步介绍其中的细节,本文主要介绍整个Web开发框架中的MVC控制器的设计.在设计之初,我就希望尽可能的减少代码,提高编程模型的统一性.因此希望能够以基类继承的方式,和我Winform开发框架一样,尽可能通过基类,而不是子类的重复代码来实现各种通用的操作. 1.登录控制的控制器基类设计 我们知道,一般我们创建一个MVC的控制

java web项目用redis怎样实现oracle缓存,如何保持oracle和redis的同步?

问题描述 java web项目用redis怎样实现oracle缓存,如何保持oracle和redis的同步? java web项目用redis实现oracle缓存,实现思路是怎样的,怎样保持oracle和redis的同步? 解决方案 可以使用oracle中的row_scn,,从oracle中读出的row_scn和redis中保存的相比,如果大于redis中的就更新redis,如果oracle数据更新,重新从oracle中读一遍出来. 数据库高可用架构(MySQL.Oracle.MongoDB.R