总体介绍:

  多idc缓存方案的invalid方案(如下图),是通过两个操作保证多个idc之间的缓存的高可用性和最终一致性的。
  1. 更新数据库后,发送invalid消息;invalid消息广播到其他idc后,立即删除所在idc缓存中的对应key;单凭这个操作,在使用一个数据库的场景,已经能保证缓存一致性了;在使用主、备数据库的场景,如果主备库的同步非常快,也能保证很大概率的缓存一致性;
  2. invalid消息会在每个idc的缓存中设置一个mark,用来标志这个key已经被其他人更新了,并且设置一个TTL指示在某个时间内它可能是旧值。这个key的所有更新操作(比如set)使用的ttl都会被marker指定的TTL覆盖;这就保证了在TTL之后,改key值会被更新一次。如果TTL设置合理(总是大于数据库之间的同步延时),就能达到最终一致性。

为什么用twemproxy做代理?

  1. 屏蔽其他idc内部缓存的细节。ycache-client只要往本idc的twemproxy发送invalid消息,而无需关注其他idc内有多少缓存,不用关注他们的部署结构、不需要关心他们是不是变化了;
  2. 减少idc之间的tcp连接,从而降低延时,避免网络故障时的重传风暴。由于所有跨idc连接都是由twemproxy连接(通过tcp常链接)的,发送时无需再进行tcp链接建立;而且,网络故障时,也仅需要重建twemproxy之间的连接,不需要idc之间数量巨大的ycache-client之间重传。

client端:


原理:app在更新数据库后(或者其他需要更新缓存的操作),调用IDCMemcacheProxyFactory工厂类获取一个实例,通过这个实例发送一个invalid消息。 这个invalid消息会在内部处理中通过twemproxy广播给所有的idc。

app需要初始化的,与以前的相比,使用IDCMemcacheProxyFactory代替以前的

YmemcacheProxyFactory;使用IDCProxy发送invalid消息,而其他的消息还是通过CacheProxy接口;


内部实现:
除了根据app指定的配置文件路径配置它的cache-pool外,还额外做如下的初始化(把本所有远端idc的2个twemproxy端口当作一个cache-pool始化(从zk上读取twemproxy的ip和端口):

    public class IDCMemcacheProxyFactory extends YmemcacheProxyFactory {
    /**

     * 初始化 IDC cache 和 twemproxy 的连接
     * @throws MemcacheInitException
     */
    public void reInitIDCYCache() throws MemcacheInitException {
        //从ycc 获取各个IDC的对应到本idc的twemproxy pool名称,本地广播消息
        String properties = YccGlobalPropertyConfigurer.loadConfigString(IDCConstants.IDCPoolName, IDCConstants.idc_common);
        Hashtable<String, String> pro = YccGlobalPropertyConfigurer.loadProperties(properties);
        String idc_ycache_poolName_list = pro.get(IDCConstants.IDCCacheIdList);
        if (idc_ycache_poolName_list == null || idc_ycache_poolName_list.trim().length() == 0) {
            logger.error(IDCConstants.idc_common + " 文件缺少值" + IDCConstants.IDCCacheIdList);

}  

        String[] poolNames =  IDCCommandUtil.getOtherIDCPoolNames();
        if(poolNames  == null ){
            poolNames = idc_ycache_poolName_list.split(",");
            IDCCommandUtil.setOtherIDCPoolNames(poolNames);
        }else{
            for (String poolName : poolNames) {
                //先关闭原来的连接
                SockIOPool pool = SockIOPool.getInstance(poolName);
                if (pool != null && pool.isInitialized()) {
                    pool.shutDown();
                }
            }
        }
        //init idc ycache ,全局只需要增加一次
        File loadConfigFile = YccGlobalPropertyConfigurer.loadConfigFile(IDCConstants.IDCPoolName, IDCConstants.idc_ycache_memcache);
        super.add("file:" + loadConfigFile.getAbsolutePath());
    }

public class IDCConstants {
    public static final String IDCPoolName = "yihaodian/common";//poolid
    public static final String idc_ycache_redis = "idc_ycache_redis.xml";// dataid
    public static final String idc_ycache_memcache = "idc_ycache_memcache.xml";
    public static final String idc_common = "idc_common.properties";
    public static final String IDCCacheIdList = "idc_ycache_poolName_list";//idc_ycache_redis.xml  idc_ycache_memcache.xml 中配置的 id
。。。
}

 所以,在调用invalid的时候,实际上是遍历这些代表各个idc的cache-pool发送一个invalid消息;这些cache-pool的配置信息是在IDCPoolName = "yihaodian/common"、idc_ycache_memcache = "idc_ycache_memcache.xml"这连个变量指定的配置中心的配置。(多个idc公用这个配置文件,通过pool id属性扩展为多个)。
注意,由于所有poolname的ycache-client实例都会连接到twenproxy,数量非常大。所以这个配置文件的mincon必须很小,否则会导致twemproxy的连接数很高。因为invalid消息不多,建议1个就够了  

支持的配置项:
    //############# For IDC proxy begin ################
    private boolean noreply = false;//是否使能memcache noreply功能; 多idc的poo(链接twemproxy)必须设置为true;
    private boolean invalidAuto = false;//多IDC下是否自动发送失效消息
    private int invalidTTLMillisecond = 2000;//多IDC下是失效时间,默认2秒
    private int invalidQueueTimeOut = 10;//写invalid队列的超时时间。-1为不等待;0为一直等待;等待意味着阻塞任务
    private int invalidQueueSize = 10000; // invalid mesage's queue size
    private int invalidBatchSendTimeOut = 100; //max waiting time for batch send
    private int invaldiBatchSize = 10;  // max invalid message number wait for batch send
    //############# For IDC proxy end ################

而要发送给几个idc,是在 IDCPoolName = "yihaodian/common"、idc_common = "idc_common.properties"这里指定的:




ycache-server端:

原理:server端事实上是起一个消息分发器的作用。一个idc内有很多的ycache-server,他们都配置在twemproxy的一个服务组内。twemproxy将一个invalid消息随机(算法可配置)分发给一个ycache-server;ycache-server会根据消息内部的poolname,发送一个delete key和add ttl-key的命令到这个poolname的memcached。 所以ycache-server实际上支持所有poolname的cache-client实例。
  
内部实现: 配置文件的读取是通过spring框架的配置文件初始化的。所有的poolname使用同一个"memcacheConfigureTemplatePath"=yihaodian/common : yihaodian_ycache-server/idc_ycache_memcache_server_template.xml指定的配置文件模板,每次初始化一个poolname的clien实例时,更改一下配置文件的pool id属性即可;而poolname的memcached的ip和端口号从zk读取。

ycache server支持简单的invalid消息调度,避免某个pool消耗过多的cpu资源,饿死其他的pool。它的调度原理如下:
/**
 * 优化思路:1. 避免过多的task参与调度; 2. 让更多的工作在一个task内完成; 3. 避免new过多的task; 4. 避免一个pool使用过多的cpu而饿死其他pool。<br/>
 * 异步执行任务,简单的task调度:<br/>
 * 保持线程和各个pool的task之间处于一个平衡状态,可以避免一个pool占用过多的cpu。 <br/>
 * 允许task中断然后再提交执行; 当前执行的task数量总是小于thread数量的两倍。<br/>
 * 
 * 1)以poolname的形式管理task。同一个poolname对应的task做的事情应该是相同的;所以submit的task如果过多,我们可以安全的丢弃一些task。<br/>
 * 2) task 在执行的过程中,可以将当前task重新submit,然后退出task run处理;这个task会被加入队列的尾部重新等待执行;这样是为了避免一个task占用了太多的cpu时间;<br/>
 * 3)不应该提交大量的task,而应该利用已经提交的task继续调度(性能优化);过多的task可能导致调度的不均匀;task的数量其实相当于调度的权重;使用isAcceptTask可以判断是否应该提交新task。<br/>
 * <p/>
 * Created by wanganqing on 2014/8/29.<br/>
 * Redesigned by chenzhiwei on 2014/11/13
 */

public class IDCExecutorService {


    // keep the task number and thread number in a AS good AS POSSIBLE status .
    // the task number of the pool should not greater than the 2 times of the average; at least 1 task.
    private void  scheduler(String poolName){
        while (true) {
            int currTaskNum = MapCurrTaskNum.get(poolName).get();
            if (currTaskNum > 0 // at least one task should be run
                    && (currTaskNum >= MapTaskQueue.get(poolName).size()  //the running task number should not greater than the queued task
                        || currTaskNum > (getRuningTaskNumber()*getWeight(poolName)/getTotalActiveWeight())  //the running task number should not greater than the weight portion.
                        || currTaskNum > THREADS+1  // the running task should not great than the thread-number
                       )
                    ){
                break;
            }
            // run a task, it will directly call run of task in 'queue'.
            MapCurrTaskNum.get(poolName).addAndGet(1);
            if (logger.isInfoEnabled())logger.info(poolName+" add task. curr task "+MapCurrTaskNum.get(poolName).get());
            executor.execute(new RunTask(poolName));
        }
        /* touch the idle pool to be runing. 
         * it will call the scheduler when the first task finished, so we just touch the pool into running status is enough.
        */
        for (String pn: MapTaskQueue.keySet()){
            if (MapCurrTaskNum.get(pn).get() <1 && MapTaskQueue.get(pn).size()>0){
                 MapCurrTaskNum.get(pn).addAndGet(1);
                   executor.execute(new RunTask(pn));
            }
        }
    } 
注意,ycache-server要连接所有poolname的memcache实例,数量非常大。所以这个配置文件的mincon必须很小。因为invalid消息不多,建议1个就够了。

   

public class CacheServiceImpl implements CacheService {  

    public void init() throws Exception {
        logger.info("start init all cache");
        CacheAdmin.getInstance().reInitAll(zkServers, this.memcacheConfigureTemplatePath, this.redisConfigureTemplatePath, initAllCache);
        logger.info("finish init all cache");
    }

/ycache-server/src/main/resources/applicationContext.xml
    <bean id="yccPropertyConfigurer_common"
        class="com.yihaodian.configcentre.client.utils.YccGlobalPropertyConfigurer">
        <property name="locations">
            <list>
                <value>zookeeper-ycache.properties</value>
                <value>idc_common.properties</value>
                <value>yihaodian_ycache-server/idc_ycache_memcache_server_template.xml</value>
                <value>yihaodian_ycache-server/idc_ycache_redis_server_template.xml</value>
            </list>
        </property>
        <property name="poolId">
            <value>yihaodian/common</value>
        </property>
        <property name="ignoreUnresolvablePlaceholders" value="true" />
    </bean>
    <bean id="nettyService" class="com.yhd.ycache.service.NettyServiceImpl" init-method="init"  destroy-method="destroy">
        <property name="port" value="${ycache-server-port}"/>
    </bean>
    <bean id="cacheService" class="com.yhd.ycache.service.CacheServiceImpl" init-method="init"  destroy-method="destroy">
        <!-- 值来自zookeeper-ycache.properties-->
        <property name="memcacheConfigureTemplatePath" value="file:${global.config.path}/ycc/snapshot/yihaodian_ycache-server/idc_ycache_memcache_server_template.xml"/>
        <property name="redisConfigureTemplatePath" value="file:${global.config.path}/ycc/snapshot/yihaodian_ycache-server/idc_ycache_redis_server_template.xml"/>
        <property name="zkServers" value="${zk-servers}"/>
        <property name="initAllCache" value="false"/>
    </bean>
    <bean id="exampleService" class="com.yhd.ycache.service.ExampleServiceImpl"></bean>

twemproxy:

twemproxy在整个组网中起桥接各个idc的作用。每个twemproxy都连接其他idc的twemproxy,任何ycache-client需要跨idc的通信都是通过twemproxy完成。也就是任何ycache-client都不应该看到非本idc的cache的存在。每个idc的twemproxy为本idc的ycache-client开启代表其他idc的端口(每个端口代表一个idc),所以ycache-client需要给其他idc的cache发送消息时,就往本idc的twemproxy的这些端口通信。
  同时,从ycache-client的视角,twemproxy其实相当于cache实例。每个idc有两个twemproxy(相互备份和负载分担),它们像cache一样被分配在一个pool-id内,写到zookeeper的'/ycache/pools/'路径下。所以本idc的ycache以为自己连接的是cache。












跨IDC ycache原理和配置说明的更多相关文章

  1. js中几种实用的跨域方法原理详解(转)

    今天研究js跨域问题的时候发现一篇好博,非常详细地讲解了js几种跨域方法的原理,特分享一下. 原博地址:http://www.cnblogs.com/2050/p/3191744.html 下面正文开 ...

  2. Ajax操作如何实现跨域请求 (JSONP和CORS实现Ajax跨域的原理)

    由于浏览器存在同源策略机制,同源策略阻止ajax (XMLHttpRequest) 从一个源加载的文档或脚本获取或设置另一个源加载的文档的属性. 特别的:由于同源策略是浏览器的限制,所以请求的发送和响 ...

  3. js中几种实用的跨域方法原理详解

    这里说的js跨域是指通过js在不同的域之间进行数据传输或通信,比如用ajax向一个不同的域请求数据,或者通过js获取页面中不同域的框架中(iframe)的数据.只要协议.域名.端口有任何一个不同,都被 ...

  4. JSONP跨域的原理解析( 一种脚本注入行为)

    JavaScript是一种在Web开发中经常使用的前端动态脚本技术.在JavaScript中,有一个很重要的安全性限制, 被称为“some-Origin Policy”(同源策略).这一策略对于Jav ...

  5. JSONP跨域的原理解析

    JavaScript是一种在Web开发中经常使用的前端动态脚本技术.在JavaScript中,有一个很重要的安全性限制,被称为“Same- Origin Policy”(同源策略).这一策略对于Jav ...

  6. Jsonp跨域访问原理和实例

    >>什么是跨域 出于安全方面的考虑,页面中的JavaScript无法访问其他服务器上的数据,当前域名的js只能读取同域下的窗口属性,即同源策略.而跨域就是通过某些手段来绕过同源策略限制,实 ...

  7. .net学习之母版页执行顺序、jsonp跨域请求原理、IsPostBack原理、服务器端控件按钮Button点击时的过程、缓存、IHttpModule 过滤器

    1.WebForm使用母版页后执行的顺序是先执行子页面中的Page_Load,再执行母版页中的Page_Load,请求是先生成母版页的控件树,然后将子页面生成的控件树填充到母版页中,最后输出 2.We ...

  8. keepalived工作原理和配置说明 腾讯云VPC内通过keepalived搭建高可用主备集群

    keepalived工作原理和配置说明 腾讯云VPC内通过keepalived搭建高可用主备集群 内网路由都用mac地址 一个mac地址绑定多个ip一个网卡只能一个mac地址,而且mac地址无法改,但 ...

  9. atitit. hb 原生sql跨数据库解决原理 获得hb 数据库类型运行期获得Dialect

    atitit. hb 原生sql跨数据库解决原理 获得hb 数据库类型运行期获得Dialect   #-----原理 Hibernate 运行期获得Dialect   2010-07-28 12:59 ...

随机推荐

  1. java中的异常和处理详细理解

    异常是程序中的一些错误,但并不是所有的错误都是异常,并且错误有时候是可以避免的. 比如说,你的代码少了一个分号,那么运行出来结果是提示是错误 java.lang.Error:如果你用System.ou ...

  2. 转载:ubuntu 下添加简单的开机自启动脚本

    转自:https://www.cnblogs.com/downey-blog/p/10473939.html linux下添加简单的开机自启动脚本 在linux的使用过程中,我们经常会碰到需要将某个自 ...

  3. 9、linux权限-ACL权限

    来自为知笔记(Wiz)

  4. 8.支撑向量机SVM

    1.什么是SVM 下面我们就来介绍一些SVM(Support Vector Machine),首先什么是SVM,它是做什么的?SVM,中文名是支撑向量机,既可以解决分类问题,也可以解决回归问题,我们来 ...

  5. C语言字符串操作小结

    1)字符串操作strcpy(p, p1) 复制字符串strncpy(p, p1, n) 复制指定长度字符串strcat(p, p1) 附加字符串strncat(p, p1, n) 附加指定长度字符串s ...

  6. LeetCode OJ -- 无重复字符的最长子串

    给定一个字符串,找出不含有重复字符的 最长子串 的长度. 示例: 给定 "abcabcbb" ,没有重复字符的最长子串是 "abc" ,那么长度就是3. 给定  ...

  7. 【bzoj 4059】Non-boring sequences

    这题的重点不在于代码,而在于复杂度分析…… 首先我们肯定会写 $n^2$ 暴力,就是每次暴力扫 $[l,r]$ 区间,找到任意一个在此区间中只出现过一次的数.设其下标为 $mid$,显然在这个区间中任 ...

  8. 创建CUDA项目

    输出选择X64 .cu文件属性: 常规-项类型:CUDA C/C++ 项目属性: 平台:活动(x64) CUDA C/C++ - Common-Target Machine Platform: 64- ...

  9. IDEA查看JDK源代码

    之前已经讲解过如何使用Eclipse查看源代码,IDEA作为一个集成开发环境越来越流行,今天学习以下如何使用Eclipse查看JDK的代码. File->Project Structure,选择 ...

  10. mongodb .net 版本

    1.现下载驱动  再 引用dll 2.https://www.cnblogs.com/zxtceq/p/7692200.html   mongodb  .net 版本 https://www.cnbl ...