先从web session的共享说起

 
许多系统需要提供7*24小时服务,这类系统肯定需要考虑灾备问题,单台服务器如果宕机可能无法立马恢复使用,这必定影响到服务。这个问题对于系统规模来说,从小到大可能面临的难度会相差很大。但对于原理来说其实就是需要准备备份系统随时可以替代正在服务的系统,也就是无论何时都有服务器可以提供服务。也就是灾备系统或者负载均衡。
 
提供灾备系统或者负载均衡系统都需要面临一个问题,那就是如何解决共享数据的问题。对于web服务器而言首先要解决的就是web session共享问题,比如A服务器的session如何可以在B服务器上也能一样使用呢?毕竟是物理隔离的两台服务器。
 
这方面的方案主要是两类:cookies和session共享。
 

cookies

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

session池化

还有一种方法就是把session共享出来,所有的服务器都连接到这个共享。这种方案可能是许多系统会使用的方案吧。因为将session池化,对于系统而言就变成透明了。程序员终于开心的将数据写入session咯。这种方案除了http服务器外,许多的tcp服务器也是类似的方案。
 
我们系统因为使用的java开发,使用tomcat时可以将session共享到memcached/redis中。而且这种操作完全不需要改动系统,直接在tomcat中配置即可。所以这种方案天然就支持啦。
 
 

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

原先的数据缓存都是放在jvm里的,所以机器多了每台服务器都要自己去加载缓存,这样一来命中就低。最近打算在系统里引入第三方缓存,当时在memcached和火的要死的redis里选择。现在来看每种内存产品都各有优势,如果硬生生的将现在这些老的缓存直接改成redis的如果以后需要用别的内存数据库又得大改代码。想到这就决定把缓存做一次设计,将现有的jvm缓存保留下来,然后做成策略以扩展新的缓存存储。
 
以前的许多缓存用的HashMap/ConcurrentHashMap,反正是键-对值。如果我们直接使用Map结构来作为缓存接口就可以不改变现有的一些代码,只需要改动缓存类内部的数据结构即可。这样的改动量就比较少。
 
比如原来的一些缓存单元结构:
  1. public class RoleMenuCache implements IClearCache {
  2. private static Map<String, RoleMenu> roleMenuCache = new HashMap<String, RoleMenu>();
  3. ...业务代码省略
  4. }

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

 
 
先来看看类图
 
 

Cachemanager

这个就是缓存的管理类,用于创建、释放缓存对象。这个类是各个所有缓存申请的入口。下面贴出来主要的代码:
  1. public class CacheManager {
  2. private final static Logger logger = LoggerFactory.getLogger(CacheManager.class);
  3. private static Map<String, ICache> caches = new ConcurrentHashMap<>();
  4. private static ICacheStrategy cacheStrategy = new DefaultCacheStategy();
  5. private static String cacheStrategyClass;
  6.  
  7. @SuppressWarnings("unchecked")
  8. public static synchronized <T extends ICache> T getOrCreateCache(String cacheName, Class<?> keyClass, Class<?> valueCalss) {
  9. T cache = (T) caches.get(cacheName);
  10. if (cache != null) {
  11. return cache;
  12. }
  13. cache = (T) cacheStrategy.createCache(cacheName, keyClass, valueCalss);
  14. caches.put(cacheName, cache);
  15. return cache;
  16. }
  17.  
  18. @SuppressWarnings("rawtypes")
  19. public static synchronized void destroyCache(String cacheName) {
  20. ICache cache = caches.remove(cacheName);
  21. if (cache != null) {
  22. cache.clear();
  23. }
  24. }
  25.  
  26. }

ICache<K,V>

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

ICacheStrategy

对于具体的缓存实现就有一套策略,有一个ICacheStrategy接口来规范。这么一来,不管是jvm还是redis都可以自己单独扩展来实现。
  1. public interface ICacheStrategy {
  2. ICache createCache(String name, Class<?> keyClass, Class<?> valueCalss);
  3. void destroyCache(ICache cache);
  4. }
看一下DefaultCache的实现(代码只放了一部分主要的):
  1. public class DefaultCache<K, V> implements ICache<K, V> {
  2. protected Map<K, V> map;
  3. private String name;
  4. private long maxCacheSize;
  5. private long maxLifetime;
  6. private int cacheSize = 0;
  7.  
  8. public DefaultCache(String name, long maxSize, long maxLifetime) {
  9. this.name = name;
  10. this.maxCacheSize = maxSize;
  11. this.maxLifetime = maxLifetime;
  12. map = new ConcurrentHashMap<K, V>(103);
  13. }
  14.  
  15. @Override
  16. public V get(Object key) {
  17. return map.get(key);
  18. }
  19.  
  20. @Override
  21. public V put(K key, V value) {
  22. return map.put(key, value);
  23. }
  24.  
  25. @Override
  26. public V remove(Object key) {
  27. return map.remove(key);
  28. }
  29.  
  30. @Override
  31. public void putAll(Map<? extends K, ? extends V> m) {
  32. map.putAll(m);
  33. }
  34.  
  35. @Override
  36. public void clear() {
  37. if (map != null) {
  38. map.clear();
  39. }
  40. }
  41.  
  42. }

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

例子,我们改造一下:
  1. public class RoleMenuCache implements IClearCache {
  2. private static Map<String, RoleMenu> roleMenuCache;
  3. static {
  4. roleMenuCache = CacheManager.getOrCreateCache("permissionCache", String.class, RoleMenu.class);
  5. }
  6. ...业务代码省略
  7. }

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

 
遇到Redis与泛型的问题
 
在扩展redis缓存策略的时候遇到一个问题,就是使用的jedis时,对于key值都是使用的string类型,这就给我们使用泛型设计留下了难题。当然为了兼容现在的设计,最后用了JSON来解决。
 
但是新的问题来了,对于put时是这样的:
  1. /**
  2. * 根据key设置map的值
  3. */
  4. @Override
  5. public V put(K key, V value) {
  6. jedisTemp.hset(name, JSON.toJSONString(key), JSON.toJSONString(value));
  7. return value;
  8. }

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

alibaba.fastjson提供的接口并不能转回成具体类型的对象,因为get方法的的返回值是V类型,是泛型类型,没法得到class的type。
像这样的代码就不行啦:JSON.parseObject(json, V.class)。最后没办法,我只好把K和V的类型在创建时由调用者传入。看下面的代码里,两个红色的参数,当然这也没问题,毕竟调用者是知道类型的:
  1. CacheManager.getOrCreateCache("permissionCache", String.class, RoleMenu.class);

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

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

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

 
 
总结与反思
      整套的设计受openfire的集群设计影响比较大,我基本是借鉴过来的,目前来看还是挺不错,最近准备尝试Ignite,非常容易就接入了系统。
 
      只是openfire使用的是java实现的方案(Hazelcast/Coherence

),这些都是带Map结构的,并不会有我遇到的Redis的问题。但我觉得这套设计还挺不错,如果把map接口去掉,自己重新定义方法就可以解决这个问题,不使用泛型,当然这样对老代码的改动会比较大。
 
      还有一种情况就是多种缓存产品并存,比如同时使用redis和memcached,现有的设计可能支持不了。但是因为入口限制在了CacheManager,我想加个泛型支持就可以解决。只是这种场景或许并不多见吧。
 
 
 
注:此文章为原创,欢迎转载,请在文章页面明显位置给出此文链接!
若您觉得这篇文章还不错请点击下右下角的推荐,非常感谢!
http://www.cnblogs.com/5207

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

  1. session和cookie区别,多台WEB服务器如何共享session,禁用COOKIE后SESSION是否可用,为什么?

    答:session的运行机制: 用户A访问站点Y,如果站点Y指定了session_start();(以下假设session_start()总是存在)那么会产生一个session_id,这个sessio ...

  2. nginx+iis+redis+Task.MainForm构建分布式架构 之 (redis存储分布式共享的session及共享session运作流程)

    本次要分享的是利用windows+nginx+iis+redis+Task.MainForm组建分布式架构,上一篇分享文章制作是在windows上使用的nginx,一般正式发布的时候是在linux来配 ...

  3. ASP.NET Session的共享

    注: 在ashx文件中使用Session 首先添加引用 using System.Web.SessionState; 实现接口 public class XXXX: IHttpHandler ==&g ...

  4. 通过memcached来实现对tomcat集群中Session的共享策略

    近期在做一套集群的实现,实现的方案是在Linux下完成对Apache + Tomcat 负载均衡的功能. 上述功能已经实现,有需要了解的朋友可以看我另外一篇博文. Linux下Apache与Tomca ...

  5. Session分布式共享 = Session + Redis + Nginx

    一.Session 1.Session 介绍 我相信,搞Web开发的对Session一定再熟悉不过了,所以我就简单的介绍一下. Session:在计算机中,尤其是在网络应用中,称为"会话控制 ...

  6. 详解Session分布式共享(.NET CORE版)

    一.前言&回顾 在上篇文章Session分布式共享 = Session + Redis + Nginx中,好多同学留言问了我好多问题,其中印象深刻的有:nginx挂了怎么办?采用Redis的S ...

  7. spring-session实现分布式集群session的共享

    前言 HttpSession是通过Servlet容器创建和管理的,像Tomcat/Jetty都是保存在内存中的.但是我们把应用搭建成分布式的集群,然后利用LVS或Nginx做负载均衡,那么来自同一用户 ...

  8. Tomcat通过自带的Cluster方式实现Session会话共享环境操作记录

    一般来说,在多个tomcat集群业务中,session会话共享是必须的需求,不然前端nginx转发过来的请求不知道之前请求在哪台tomcat节点上,从而就找不到session以至于最终导致请求失败.要 ...

  9. redis缓存web session

    redis缓存web session 首先说下架构图.使用Redis作为会话服务器,统一管理Session.如图,集群里的WEB服务器共享存放在REDIS里面全部的客户端SESSION. 当然,反向代 ...

随机推荐

  1. 【分享】标准springMVC+mybatis项目maven搭建最精简教程

    文章由来:公司有个实习同学需要做毕业设计,不会搭建环境,我就代劳了,顺便分享给刚入门的小伙伴,我是自学的JAVA,所以我懂的.... (大图直接观看显示很模糊,请在图片上点击右键然后在新窗口打开看) ...

  2. web前端基础知识

    #HTML    什么是HTML,和他ML...    网页可以比作一个装修好了的,可以娶媳妇的房子.    房子分为:毛坯房,精装修    毛坯房的修建: 砖,瓦,水泥,石头,石子....    精 ...

  3. Shell碎碎念

    1. 字符串如何大小写转换 str="This is a Bash Shell script." 1> tr方式 newstr=`tr '[A-Z]' '[a-z]' < ...

  4. C# 工厂模式+虚方法(接口、抽象方法)实现多态

    面向对象语言的三大特征之一就是多态,听起来多态比较抽象,简而言之就是同一行为针对不同对象得到不同的结果,同一对象,在不同的环境下得到不同的状态. 实例说明: 业务需求:实现一个打开文件的控制台程序的d ...

  5. css3更改input单选和多选的样式

    在项目开发中我们经常会遇到需要更改input单选和多选样式的情况,今天就给大家介绍一种简单改变input单选和多选样式的办法. 在这之前先简单介绍一下:before伪类 :before 选择器向选定的 ...

  6. 解决Ubuntu Kylin 1610安装ANSYS17.2的NVIDIA显卡驱动问题

    Ubuntu Kylin 1610在安装完毕后,会自动安装显卡驱动,对于一般的图形图像使用来说自然不会有太大的问题,但是对于ANSYS17.2的一些模块,还是会出现问题.一个比较常见的问题就是Open ...

  7. Xamarin.Android之Fragment Walkthrough

    利用Fragment设计能够兼容不同屏幕的应用 这里我们先围观下最后的成果图,给读者打打气: 普通手机上显示的结果: 在平板上显示的结果: 笔者要郑重声明下,虽然看似是两种不同的显示效果,但是同一个应 ...

  8. NodeJs 开发微信公众号(三)微信事件交互

    微信公众号有个规则,一旦开启了开发者模式,其他的常规功能就都必须通过接口调用完成.比如说自定义菜单功能,必须通过发送post请求的方式生成.本章就通过关注到取消关注的整个过程来谈一谈nodejs是怎么 ...

  9. ABP源码分析二十八:ABP.MemoryDB

    这个模块简单,且无实际作用.一般实际项目中都有用数据库做持久化,用了数据库就无法用这个MemoryDB 模块了.原因在于ABP限制了UnitOfWork的类型只能有一个(前文以作介绍),一般用了数据库 ...

  10. ABP源码分析四十三:ZERO的本地化

    ABP Zero模块扩展了ABP基础框架中的本地化功能,实现了通过数据库管理本地化的功能.其通过数据库保存本地化语言及其资源. ApplicationLanguage:代表本地化语言的实体类.一种语言 ...