一个缓存容灾写的例子

背景

有时我们可以使用缓存进行容灾的处理。场景如下:我们当前有一个专门提供各种数据的应用DataCore,该应用开放多个RFC方法供其他应用使用。

     我们平时在读写数据时,会在Cache备份一份(为平时DataCore提高响应速度、降低DB、CPU压力所用),当DB挂掉的时候,Cache还可以用来容灾。使用缓存容灾的好处是:性能足够好,坏处是缓存可比数据库成本高多了。

让我们想象得更猛烈些,当DataCore整个挂掉的时候,A、B、C、D方怎么才能安然的运行下去?

我们可以在A、B、C、D端上提供DataCore的缓存容灾服务,这样,即使在DataCore整个挂掉的情况下,其他应用也不会受影响。

要考虑的几个问题

  1. 容灾读的部分不必说,对象原本在存入缓存时就根据类型分了区域,读的时候直接在相应的区域取出即可。容灾写的话,针对同一类型对象的写操作,如何将这些对象集合在一块,待DataCore恢复后,再将容灾写过的对象覆盖到DB。
  2. 缓存的写操作必须是线程安全的

详细设计

缓存容灾写的一种可能策略是:针对每种类型的对象在缓存中开辟一大段储存空间(数组方式或者数组链接结合方式),然后把每个容灾写的对象塞进这段空间内,在要覆写回DB时,直接从头到尾在缓存里把对象取出来即可。示意图如下:

上面这种设计缺点是需要一大片的连续的储存空间,对于缓存来说,这是要命的。缓存的底层储存机制就是基于分散的hash。

上面设计的一种改进方案是,我们只在连续空间中储存UserDO的唯一标示符,比如id或者key什么的。这样我们的就不需要那么大的连续空间了。示意图如下:

更进一步,我们可以把UserDO的id也分散储存。可以利用一个DisasterIndexDO储存每一个类型的容灾写的信息,利用beginIndex以及currentIndex字段为所有容灾写对象打上一个序号,在缓存中储存该序号与对象id的对应关系,然后我们就可以通过序号检索出id,再通过id检索出对象。示意图如下:

在多个调用方在对某一类型的对象进行容灾写操作时,只需要对DisasterIndexDO进行安全的并发访问即可,抢占currentIndex,然后再进行缓存的写操作。这样,我们的容灾写就实现了。

范例实现

//index对象
public class DisasterIndexDO implements Serializable {

private static final long serialVersionUID = -8688243351154917184L;
     public int namespace;
     public int beginIndex;
     public int currentIndex;
     public long expireLockTime;
     public static final long DEFAULT_EXPIRE_TIME = 50; // 当序列被锁时间超时,防止死锁

     public DisasterIndexDO(int namespace, int bIndex, int cIndex, long expireLockTime) {
        this.namespace = namespace;
        this.beginIndex = bIndex;
        this.currentIndex = cIndex;
        this.expireLockTime = expireLockTime;
     }
}
//容灾实现类
public class DisasterCacheHandler {
     RemoteCache remoteCache;

     private  final int DS_CACHE_NAMESPACE = 67;
     private  final int DS_WRITE_REPETECOUNT = 3;
     private  final int DISASTER_INDEX = 250;
     private  final String DISASTER_KEYS = "disaster_keys";
//容灾读
    public Object dsGetRemoteData(int namespace, String key){
         return remoteCache.get(DS_CACHE_NAMESPACE, namespace+key);
    }

    //本地同步的namespace
     private Map<Integer, Object> synNamespace = new
           HashMap<Integer, Object>();
     //容灾写
     protected boolean dsWriteRemoteData(int namespace,
          String key, Serializable value) {

          //先把数据写入缓存
          remoteCache.put(this.DS_CACHE_NAMESPACE, namespace+key, value);
          //本地同步的NameSpace
          if (!this.synNamespace.containsKey(Integer.valueOf(namespace))) {
                this.synNamespace.put(Integer.valueOf(namespace), namespace);
          }
          // update the Namespace Index and namespace disaster key queue
          synchronized (this.synNamespace.get(namespace)){
               int count = this.DS_WRITE_REPETECOUNT;
               DisasterIndexDO index = null;
               do {
                    count--;
                     // try to lock Namespace Index
                     int rc = remoteCache.lock(this.DISASTER_INDEX,
                         String.valueOf(namespace));
                    // Namespace Index not exist
                    if ( rc == 2 ) {
                         // Initialize Namespace Index
                         index = new DisasterIndexDO(namespace, 1, 1,
                              System.currentTimeMillis());
                         remoteCache.put(this.DISASTER_INDEX,
                              String.valueOf(namespace), index);
                 // for each namespace should handle disaster, keep the only index object
                         remoteCache.put(namespace,
                              this.DISASTER_KEYS + index.currentIndex, key);
                         return true;
                    } else if (rc == 0) { // lock failure
                         index = (DisasterIndexDO)remoteCache.
                                   get(this.DISASTER_INDEX, String.valueOf(namespace));
                    // 如果鎖已經超時,則解开,避免在访问缓存时死住的情况
                         if (System.currentTimeMillis() - index.expireLockTime >
                                   DisasterIndexDO.DEFAULT_EXPIRE_TIME) {
                                        remoteCache.unLock(this.DISASTER_INDEX,
                                             String.valueOf(namespace));
                                   continue;
                          }
                         continue;
                   } else if (rc == 1) { // lock success
                         try {
                              index = (DisasterIndexDO)remoteCache.get(this.DISASTER_INDEX,
                                        String.valueOf(namespace));
                              // update locked Namespace Index
                              int curIdx = index.currentIndex + 1;
                              remoteCache.delete(this.DISASTER_INDEX,
                                        String.valueOf(namespace));
                              remoteCache.put(this.DISASTER_INDEX, String.valueOf(namespace),
                                   new DisasterIndexDO(namespace, index.beginIndex, curIdx,
                                        System.currentTimeMillis()));
                              // keep key of this Namespace with current index
                              remoteCache.put(namespace, this.DISASTER_KEYS + curIdx, key);
                              return true;
                         }  catch (Throwable e ) {
                         } finally {
                              // unlock and handle unlock failure
                              remoteCache.unLock(this.DISASTER_INDEX,
                                   String.valueOf(namespace));
                         }
                   }
             } while (count >= 0);
            if (count <= 0) {
                 return false;
            }
        }
    }
}
时间: 2024-10-13 09:56:51

一个缓存容灾写的例子的相关文章

一个缓存容灾写的样例

背景 有时我们能够使用缓存进行容灾的处理.场景例如以下:我们当前有一个专门提供各种数据的应用DataCore,该应用开放多个RFC方法供其它应用使用.      我们平时在读写数据时,会在Cache备份一份(为平时DataCore提高响应速度.减少DB.CPU压力所用),当DB挂掉的时候.Cache还能够用来容灾.使用缓存容灾的优点是:性能足够好,坏处是缓存可比数据库成本高多了. 让我们想象得更猛烈些,当DataCore整个挂掉的时候,A.B.C.D方怎么才干安然的执行下去? 我们能够在A.B.

写出一个缓存系统的伪代码001

/** * 写出一个缓存系统的伪代码 * @author ysloong * */ public class CacheDemo { private Map<String, Object> map = new HashMap<String, Object>(); public static void main(String[] args) { // TODO Auto-generated method stub } public synchronized Object getDat

容灾与集群(1)

容灾与集群(1) 在上一篇:微软分布式云计算框架Orleans(1):Hello World,我们大概了解了Orleans如何运用,当然上一篇的例子可以说是简单且无效的,因为用了Orleans不可能只写一个Hello World吧,Orleans是为分布式和云计算而生的框架,那么今天我们就简单说一说容灾.集群.容灾与集群在Orleans中的运用. 集群是什么? 下面摘抄自百度百科: 集群(cluster)技术是一种较新的技术,通过集群技术,可以在付出较低成本的情况下获得在性能.可靠性.灵活性方面

备份容灾

备份容灾 在分布式存储系统中,系统可用性是最重要的指标之一,需要保证在机器发生故障时,系统可用性不受影响,为了做到这点,数据就需要保存多个副本,并且多个副本要分布在不同的机器上,只要多个副本的数据是一致的,在机器故障引起某些副本失效时,其它副本仍然能提供服务.本文主要介绍数据备份的方式,以及如何保证多个数据副本的一致性,在系统出现机器或网络故障时,如何保持系统的高可用性. 数据备份 数据备份是指存储数据的多个副本,备份方式可以分为热备和冷备,热备是指直接提供服务的备副本,或者在主副本失效时能立即

分布式存储系统设计(4)—— 备份容灾

在分布式存储系统中,系统可用性是最重要的指标之一,需要保证在机器发生故障时,系统可用性不受影响,为了做到这点,数据就需要保存多个副本,并且多个副本要分布在不同的机器上,只要多个副本的数据是一致的,在机器故障引起某些副本失效时,其它副本仍然能提供服务.本文主要介绍数据备份的方式,以及如何保证多个数据副本的一致性,在系统出现机器或网络故障时,如何保持系统的高可用性. 数据备份 数据备份是指存储数据的多个副本,备份方式可以分为热备和冷备,热备是指直接提供服务的备副本,或者在主副本失效时能立即提供服务的

Redis存储及容灾策略

Redis利用内存发挥的高性能读写在很多场景下大有所为,但是Redis本身毕竟还是一个单机数据库,如果系统对其属于强依赖,那么还是必须做好必要的容灾,针对这个问题,有以下几种策略: 一.M/S切换 由于Redis是单机数据库,所以针对MySQL的一些容灾方案也能顺利适用,例如当Redis意外宕机,可以将请求马上切到备库,同时快速恢复数据. 二.AOF Redis有两种持久化的方式,分别是SnapShotting和Append-Only File,其原理和特性可以参考<对redis数据持久化的一些

【大话存储】学习笔记(17章),数据容灾

数据容灾 数据备份系统只能保证实际上被安全复制了一份,如果生产系统故障,必须将备份数据尽快的恢复到生产系统中继续生产,就叫容灾. 容灾可以分为四个级别: 数据级容灾:只是将生产站点的数据同步到远端. 与应用结合的数据级容灾:保证对应应用数据一致性. 应用级容灾:需要保证灾难发生以后,需要保证原生成系统中的应用系统在灾备站点可用. 业务级容灾:除了保证数据.应用系统在灾备站点可用,还要保证整个企业的业务系统仍对外可用,是最终层次的容灾. 概述 如果要充分保证数据的安全,只是在本地做备份是不够的,所

Redis全方位详解--磁盘持久化和容灾备份

序言 在上一篇博客中,博客介绍了redis的数据类型使用场景和redis分布式锁的正确姿势.我们知道一旦Redis重启,存在redis里面的数据就会全部丢失.所以这篇博客中向大家介绍Redis的磁盘持久化. REDIS持久化 以每隔一段时间对redis进行快照的方式实现持久化 RDB持久化 优点:1.对redis性能影响小. 2.数据集比较大的时候,恢复速度比AOF快.   3.RDB是一个非常紧凑的单一文件,很方便传到第三方数据中心(亚马逊S3),以便日后的灾难恢复. 缺点:1.因为RDB的快

从冷备到多活,阿里毕玄谈数据中心的异地容灾

大数据时代,数据中心的异地容灾变得非常重要.在去年双十一之前,阿里巴巴上线了数据中心异地双活项目.InfoQ就该项目采访了阿里巴巴的林昊(花名毕玄). 毕玄是阿里巴巴技术保障部的研究员,负责性能容量架构.数据中心异地多活项目就是他主导的.多活:从同城到异地 InfoQ:首先请介绍一下数据中心异地多活这个项目. 毕玄:这个项目在我们内部的另外一个名字叫做单元化,双活是它的第二个阶段,多活是第三个阶段.所以我们把这个项目分成三年来实现.所谓异地多活,故名思义,就是在不同地点的数据中心起多个我们的交易