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

简介: 先从web session的共享说起   许多系统需要提供7*24小时服务,这类系统肯定需要考虑灾备问题,单台服务器如果宕机可能无法立马恢复使用,这必定影响到服务。这个问题对于系统规模来说,从小到大可能面临的难度会相差很大。

先从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

 

相关文章
|
缓存 应用服务中间件 nginx
Web服务器的缓存机制与内容分发网络(CDN)
【8月更文第28天】随着互联网应用的发展,用户对网站响应速度的要求越来越高。为了提升用户体验,Web服务器通常会采用多种技术手段来优化页面加载速度,其中最重要的两种技术就是缓存机制和内容分发网络(CDN)。本文将深入探讨这两种技术的工作原理及其实现方法,并通过具体的代码示例加以说明。
1088 1
|
监控 前端开发 JavaScript
使用 MERN 堆栈构建可扩展 Web 应用程序的最佳实践
使用 MERN 堆栈构建可扩展 Web 应用程序的最佳实践
259 6
|
开发框架 JavaScript 前端开发
TypeScript 是一种静态类型的编程语言,它扩展了 JavaScript,为 Web 开发带来了强大的类型系统、组件化开发支持、与主流框架的无缝集成、大型项目管理能力和提升开发体验等多方面优势
TypeScript 是一种静态类型的编程语言,它扩展了 JavaScript,为 Web 开发带来了强大的类型系统、组件化开发支持、与主流框架的无缝集成、大型项目管理能力和提升开发体验等多方面优势。通过明确的类型定义,TypeScript 能够在编码阶段发现潜在错误,提高代码质量;支持组件的清晰定义与复用,增强代码的可维护性;与 React、Vue 等框架结合,提供更佳的开发体验;适用于大型项目,优化代码结构和性能。随着 Web 技术的发展,TypeScript 的应用前景广阔,将继续引领 Web 开发的新趋势。
416 2
|
监控 前端开发 JavaScript
探索微前端架构:构建可扩展的现代Web应用
【10月更文挑战第29天】本文探讨了微前端架构的核心概念、优势及实施策略,通过将大型前端应用拆分为多个独立的微应用,提高开发效率、增强可维护性,并支持灵活的技术选型。实际案例包括Spotify和Zalando的成功应用。
|
存储 安全 搜索推荐
理解Session和Cookie:Java Web开发中的用户状态管理
理解Session和Cookie:Java Web开发中的用户状态管理
288 4
|
安全 前端开发 PHP
PHP与现代Web开发:构建高效和可扩展的应用程序
【8月更文挑战第29天】在这篇文章中,我们将深入探讨PHP如何适应现代Web开发的需求。我们将通过实际案例分析,揭示PHP的核心优势,并展示如何利用这些优势来构建高性能、可扩展的Web应用。文章不仅提供理论知识,还包括具体的代码示例,旨在帮助开发者更好地理解和运用PHP解决实际问题。
|
存储 安全 搜索推荐
【JavaWeb 秘籍】Cookie vs Session:揭秘 Web 会话管理的奥秘与实战指南!
【8月更文挑战第24天】本文以问答形式深入探讨了Web开发中关键的会话管理技术——Cookie与Session。首先解释了两者的基本概念及工作原理,随后对比分析了它们在存储位置、安全性及容量上的差异。接着,通过示例代码详细介绍了如何在JavaWeb环境中实现Cookie与Session的操作,包括创建与读取过程。最后,针对不同应用场景提供了选择使用Cookie或Session的指导建议,并提出了保障二者安全性的措施。阅读本文可帮助开发者更好地理解并应用这两种技术。
338 1
|
存储 安全 搜索推荐
深入探讨Session和Cookie的概念、用途以及如何在Java Web开发中有效地使用它们进行用户状态管理。
在Java Web开发中,Session和Cookie是管理用户状态的核心技术。Session存储于服务器端,通过唯一的Session ID识别用户,确保数据安全与隐私;Cookie则存储于客户端,用于记录用户偏好等信息。两者各有优势:Session适合存储敏感数据,但需合理管理避免资源浪费;Cookie便于持久化存储,但在安全性上需谨慎设置。开发者可通过Servlet API轻松操作二者,实现个性化用户体验与应用性能优化。
244 2
|
存储 JSON JavaScript
震撼!Cookie、Session、Token、JWT 终极对决:揭开 Web 认证的神秘面纱!
【8月更文挑战第13天】Web 开发中,Cookie、Session、Token 和 JWT 常混淆。Cookie 是服务器给客户端的小信息片,如登录状态,每次请求都会返回。Session 则是服务器存储的用户数据,通过 Session ID 追踪。Token 类似通行证,证明客户端身份且可加密。JWT 是结构化的 Token,含头部、载荷及签名,确保数据完整性和安全性。
256 4
|
存储 缓存 NoSQL
构建高性能Web应用:缓存的重要性及其实现
构建高性能Web应用:缓存的重要性及其实现