精华内容
下载资源
问答
  • Redis是一种高性能的内存数据库;而MySQL是基于磁盘文件的关系型数据库,相比于Redis来说,读取速度会慢一些,但是功能强大,可以用于存储持久化的数据。在实际工作中,我们常常将Redis作为缓存与 ...

    Redis是一种高性能的内存数据库;而MySQL是基于磁盘文件的关系型数据库,相比于Redis来说,读取速度会慢一些,但是功能强大,可以用于存储持久化的数据。在实际工作中,我们常常将Redis作为缓存与MySQL配合来使用,当有数据访问请求的时候,首先会从缓存中进行查找,如果存在就直接取出,如果不存在再访问数据库,这样就提升了读取的效率,也减少了堆后端数据库的访问压力。可以说使用Redis这种缓存架构是高并发架构中非常重要的一环。

    在这里插入图片描述

    当然我们也可以对MySQL做主从架构并且进行读写分离,让主服务器(Master)处理写请求,从服务器(Slave)处理读请求,这样同样可以提升数据库的并发处理能力。不过主从架构的作用不止如此,我们今天就从下面几个方面了解一下它:

    1、为什么需要主从同步,设置主从同步有什么样的作用?
    2、主从同步的原理是怎样的?在进行主从同步的同时会引入哪些问题?
    3、为了保证主从同步的数据一致性,都有哪些方案?

    为什么需要主从同步

    首先不是所有的应用都需要对数据库进行主从架构的设置,毕竟设置架构本身是有成本的,如果我们的目的在于提升数据库高并发访问的效率,那么首先需要考虑的应该是如何优化你的SQL和索引,这种方式简单有效,其次才是采用缓存的策略,比如使用Redis,通过Redis高性能的优势把热点数据保存在内存数据库中,提升读取的效率,最后才是对数据库采用主从架构,进行读写分离。

    按照上面的方式进行优化,使用和维护的成本是由小到大的。

    主从同步设计不仅可以提升数据库的吞吐量,还有以下三个方面的作用。

    首先是可以读写分离,我们可以通过主从复制的方式来同步数据,然后通过读写分离提升数据库的并发处理能力。

    简单来说就是同一份数据被放在了多个数据库中,其中一个数据库是Master主库,其余的多个数据库是Slave从库。当主库进行更新的时候,会自动将数据复制到从库中,而我们在客户端读取数据的时候,会从从库进行读取,也就是采用读写分离的方式。互联网的应用往往是“读多写少”的需求,采用读写分离的方式,可以实现更高的并发访问。原本所有的读写压力都由一台服务器承担,现在有多个“兄弟”帮忙处理读请求,这样就减少了对后端大哥(Maste)的压力。同时,我们还能对从服务器进行负载均衡,让不同的读请求按照策略均匀的分配到不同的从服务器中,让读取更加顺畅。读取顺畅的另一个原因,就是减少了锁表的影响,比如我们让主库负责写,当主库出现写锁的时候,不会影响到从库进行SELECT操作。

    第二个作用就是数据备份。我们通过主从复制将主库上的数据复制到了从库上,相当于是一种热备份机制,也就是在主库正常运行下进行备份,不会影响到服务。

    第三个作用是具有高可用性。我刚才讲的数据备份实际上是一种冗余的机制,通过这种冗余的方式可以换取数据库的高可用性,也就是当服务器出现故障或者宕机的情况下,可以切换到从服务器上,让从服务器充当主服务器,保证服务的正常运行。

    关于高可用性的程度,我们可以用一个指标衡量,既正常可用时间 / 全年时间。比如要达到全年99.999%的时间都可用,就意味着系统在一年中的不可用时间不得超过5.256分钟,其他时间都需要保持可用的状态。需要注意的是,这5.256分钟包括了系统崩溃的时间,也包括了日常维护操作导致的停机时间。

    事实上,更高的高可用性,意味着需要付出更高的成本代价,在现实中我们需要结合业务需求和成本来进行选择。

    主从同步的原理是怎样的

    提到主从同步的原理,我们就需要了解在数据库中的一个重要日志文件,那就是Binlog二进制文件,它记录了对数据库进行更新的事件,事实上主从同步的原理就是基于Binlog进行数据同步的。在主从复制过程中,会基于三个线程来操作,一个主库线程,两个从库线程。

    二进制日志转储线程是一个主库线程。当从库线程连接的时候,主库可以将二进制日志发送到从库,当主库读取事件的时候,会在Binglog上加锁,读取完成之后,再将锁释放掉。

    从库IO线程会连接到主库,向主库发送请求更新Binlog,这时从库的IO线程就可以读取到主库的二进制日志转储线程发送的Binlog更新部分,并且拷贝到本地形成中继日志(Relay log)。

    从库SQL线程会读取从库中的中继日志,并且执行日志中的事件,从而将从库中的数据与主库保持同步。

    在这里插入图片描述

    你能看到主从同步的内容就是二进制日志(Binlog),它虽然叫二进制日志,实际上存储的是一个又一个的事件(Event),这些事件分别对应着数据库的更新操作,比如INSERT、UPDATE、DELETE等。另外我们还需要注意的是,不是所有版本的MySQL都默认开启了服务器的二进制日志,在进行主从同步的时候,我们需要先检查服务器是否已经开启了二进制日志。

    进行主从同步的内容是二进制日志,它是一个文件,在进行网络传输的过程中就一定会存在延迟,比如500ms,这样就可能造成用户在从库上读取的数据不是最新的数据,也就是主从同步中的数据不一致问题。比如我们对一条记录进行更新,这个操作是在主库上完成的,而在很短的时间内,比如100ms,又对同一个记录进行读取,这时候从库还没有完成数据的读取,那么,我们通过从库读取到的数据就是一条旧的数据。

    这种情况下该怎么办呢?

    如何解决主从同步的数据一致性问题

    可以想象下,如果我们想要操作的数据都存储在同一个数据库中,那么对数据进行更新的时候,可以对记录进行加写锁,这样在读取的时候就不会发生数据不一致的情况了。但这时从库的作用就是备份数据,没有做到读写分离,分担主库的压力。

    在这里插入图片描述

    因此我们还需要想办法,在进行读写分离的时候,解决主从同步中数据不一致的问题,也就是解决主从之间数据复制方式的问题,如果按照数据一致性从弱到强来进行划分,有以下三种复制方式。

    方式1:异步复制

    异步模式就是客户端提交COMMIT之后不需要等从库返回任何结果,而是直接将结果返回给客户端,这样做的好处就是不会影响主库的执行效率,但是可能会存在主库宕机,而Binlog还没有同步到从库的情况,也就是主库和从库的数据不一致问题,这时候从从库中选一个作为新的主库,那么,新的主库则可能会缺少原来主服务器中已经提交的事务。所以,这种复制模式下的数据一致性是最弱的。

    在这里插入图片描述

    方式2:半同步复制

    MySQL5.5版本之后开始支持半同步复制的方式。原理是在客户端提交COMMIT之后不直接将结果返回给客户端,而是等待至少有一个从库收到了Binlog,并且写入到中继日志中,再返回给客户端。这样做的好处就是提高了数据的一致性,当然相比于异步复制来说,至少多增加了一个网络连接的延迟,降低了主库写的效率。

    在MySQL5.7版本中还增加了一个rpl_semi_sync_master_wait_for_slave_count参数,我们可以对应搭的从库数量进行设置,默认为1,也就是说只要有一个从库进行了响应,就可以返回给客户端。如果将这个参数调大,可以提升数据一致性的强度,但也会增加主库等待从库响应的时间。

    在这里插入图片描述

    方式3:组复制

    组复制技术,简称MGR。是MySQL在5.7.17版本中推出的一种新的数据复制技术,这种复制技术是基于paxos协议的状态机复制。

    我刚才介绍的异步复制和半同步复制都无法最终保证数据的一致性问题,半同步复制是通过判断从库响应的个数来决定是否返回给客户端,虽然数据一致性相比于异步复制有提升,但仍然无法满足对数据一致性要求高的场景,比如金融领域。MGR 很好地弥补了这两种复制模式的不足。

    下面我们来看下 MGR 是如何工作的(如下图所示)。

    首先我们将多个节点共同组成一个复制组,在执行读写(RW)事务的时候,需要通过一致性协议层(Consensus)的同意,也就是读写事务想要进行提交,必须要经过组里“大多数人”(对应Node节点)的同意,大多数指的是同意的节点数量要大于N/2+1,这样才可以进行提交,而不是一方说了算。而针对只读(RO)事务则不需要经过组内同意,直接COMMIT即可。

    在一个复制组内有多个节点组成,它们各自维护了自己的数据副本,并且在一致性协议层实现了源自消息和全局有序消息,从而保证组内数据的一致性。(具体原理点击这里可以参考)。

    在这里插入图片描述

    MGR将MySQL带入了数据强一致性的时代,是一个划时代的创新,其中一个重要原因是MGR是基于paxos协议的。PAxos算法是由2013 年的图灵奖获得者 Leslie Lamport 于 1990 年提出的,有关这个算法的决策机制你可以去网上搜下。

    事实上,Paxos算法提出来之后就作为分布式一致性算法被广泛使用,比如Apache的Zookeeper也是基于paxos算法实现的。

    总结

    我今天讲解了数据库的主从同步,如果你的目标仅仅是数据库的高并发,那么可以先从 SQL 优化,索引以及 Redis 缓存数据库这些方面来考虑优化,然后再考虑是否采用主从架构的方式。

    在主从架构的配置中,如果想要采取读写分离的策略,我们可以自己编写程序,也可以通过第三方的中间件来实现。

    自己编写程序的好处就在于比较自主,我们可以自己判断哪些查询在从库上来执行,针对实时性要求高的需求,我们还可以考虑哪些查询可以在主库上执行。同时,程序直接连接数据库,减少了中间件层,相当于减少了性能损耗。

    采用中间件的方法有很明显的优势,功能强大,使用简单。但因为在客户端和数据库之间增加了中间件层会有一些性能损耗,同时商业中间件也是有使用成本的。我们也可以考虑采取一些优秀的开源工具,比如 MaxScale。它是 MariaDB 开发的 MySySQL 数据中间件。比如在下图中,使用 MaxScale作为数据库的代理,通过路由转发完成了读写分离。同时我们也可以使用 MHA 工具作为强一致的主从切换工具,从而完成 MySQL的高可用架构。

    在这里插入图片描述

    在这里插入图片描述

    展开全文
  • redis和mysql数据不一致问题如何解决

    微信搜索《Java鱼仔》,每天一个知识点不错过

    (一)每天一个知识点

    redis和mysql数据不一致问题如何解决?

    (二)解决思路

    要解决缓存数据不一致的问题,首先要理解为什么缓存和数据库会存在不一致的情况。

    (2.1)什么情况下缓存和数据库会不一致

    在高并发的情况下,如果所有的数据都从数据库中去读取,那再强大的数据库系统都承受不了这个压力,因此我们会将部分数据放入缓存中,比如放入redis中。这是典型的用空间换时间的方式。

    但是这个redis相当于是真实数据的一个副本,这就意味着如果数据库中数据发生变化的时候,就会导致缓存数据不一致的问题。

    归根结底,只要有两份数据存在,数据一致性问题就是不可避免的。

    (2.2)解决方法一:数据实时更新

    当更新数据库的时候,同步更新缓存。

    优点:数据一致性强,不会出现缓存雪崩的问题。

    缺点:代码耦合度高,影响正常业务,增加一次网络开销。

    适用环境:适用于数据一致性要求高的场景,比如银行业务,证券交易等

    (2.3)解决方法二:数据准实时更新

    当更新数据库的同时,异步去更新缓存,比如更新数据库后把一条消息发送到mq中去实现。

    优点:与业务解耦,不影响正常业务,不会出现缓存雪崩。

    缺点:有较短的延迟,并且无法保证最终的一致性,需要补偿机制。

    适用环境:写操作不频繁并且实时性要求不严格的场景。

    (2.4)解决方法三:缓存失效机制

    基于缓存本身的失效机制,具体实现方式为设置缓存失效时间,如果有缓存就从缓存中取数据,如果没缓存就从数据库中取数据,并且重新设置缓存。

    优点:实现方式简单,与业务完美解耦,不影响正常业务。

    缺点:有一定延迟,并且存在缓存雪崩的情况。

    适用环境:适合读多写少的互联网环境,能接受一定的数据延时。

    (2.5)解决方法四:定时任务更新

    通过定时任务,按照一定时间间隔更新缓存。

    优点:不影响正常业务,在特殊场景应用广泛。

    缺点:不保证实时一致性,且需要为每个任务写一个调度代码。

    适用环境:适用于需要复杂数据统计的缓存更新,比如展示高速车流量,五分钟一次的统计不会影响业务使用。

    (三)总结

    关于缓存一致性问题,需要具体场景具体分析,没有任何一种方案可以应用于所有场景,上述四种方式也并非全部实现方式。

    展开全文
  • 如何解决Redis 主从数据不一致问题

    千次阅读 2018-02-05 17:17:09
    线上问题 近期我们在对Redis做大规模迁移升级的时候,采用模拟复制协议的方式进行数据传输同步。 在此期间,我们遇到如下两...针对第一个问题,Redis 过期时间不一致问题,通过测试并且查阅Redis源码中得出如下结论...
        

    线上问题

    近期我们在对Redis做大规模迁移升级的时候,采用模拟复制协议的方式进行数据传输同步。

    在此期间,我们遇到如下两个问题:

    1. 迁移前后Redis过期时间不一致。
    2. 迁移前后Redis key 数量不一致。

    迁移前后Redis过期时间不一致

    针对第一个问题,Redis 过期时间不一致问题,通过测试并且查阅Redis源码中得出如下结论:
    Redis社区版本在正常的主从复制也会出现过期时间不一致问题,主要是由于在主从进行全同步期间,如果主库此时有expire 命令,那么到从库中,该命令将会被延迟执行。因为全同步需要耗费时间,数据量越大,那么过期时间差距就越大。
    Redis expire 命令主要实现如下:

    expireGenericCommand(c,mstime(),UNIT_SECONDS);
    
    void expireGenericCommand(redisClient *c, long long basetime, int unit) {
        robj *key = c->argv[1], *param = c->argv[2];
        long long when; /* unix time in milliseconds when the key will expire. */
        if (getLongLongFromObjectOrReply(c, param, &when, NULL) != REDIS_OK)
            return;
        if (unit == UNIT_SECONDS) when *= 1000;
        when += basetime;

    expire 600 到redis中过期时间其实是(当前timestamp+600)*1000,最终Redis会存储计算后这个值在Redis中。所以上面提到的情况,等到命令到从库的时候,当前的timestamp跟之前的timestamp不一样了,特别是发生在全同步后的expire命令,延迟时间基本上等于全同步的数据,最终造成过期时间不一致。

    这个问题其实已经是官方的已知问题,解决方案有两个:

    1. 业务采用expireat timestamp 方式,这样命令传送到从库就没有影响。
    2. 在Redis代码中将expire命令转换为expireat命令。
    

    官方没有做第二个选择,反而是提供expireat命令来给用户选择。其实从另外一个角度来看,从库的过期时间大于主库的过期时间,其实影响不大。因为主库会主动触发过期删除,如果该key删除之后,主库也会向从库发送删除的命令。但是如果主库的key已经到了过期时间,redis没有及时进行淘汰,这个时候访问从库该key,那么这个key是不会被触发淘汰的,这样如果对于过期时间要求非常苛刻的业务还是会有影响的。
    而且目前针对于我们大规模迁移的时间,在进行过期时间校验的时候,发现大量key的过期时间都不一致,这样也不利于我们进行校验。

    所以针对第一个问题,我们将expire/pexpire/setex/psetex 命令在复制到从库的时候转换成时间戳的方式,比如expire 转成expireat命令,setex转换成set和expireat命令,具体实现如下:

    void propagate(struct redisCommand *cmd, int dbid, robj **argv, int argc,
                   int flags)
    {
        if (server.aof_state != REDIS_AOF_OFF && flags & REDIS_PROPAGATE_AOF)
            feedAppendOnlyFile(cmd,dbid,argv,argc);
        if (flags & REDIS_PROPAGATE_REPL) {
            if (!strcasecmp(argv[0]->ptr,"expire") ||
                !strcasecmp(argv[0]->ptr,"setex") ||
                !strcasecmp(argv[0]->ptr,"pexpire") ||
                !strcasecmp(argv[0]->ptr,"psetex") ) {
                long long when;
                robj *tmpargv[3];
                robj *tmpexpire[3];
                argv[2] = getDecodedObject(argv[2]);
                when = strtoll(argv[2]->ptr,NULL,10);
                if (!strcasecmp(argv[0]->ptr,"expire") ||
                    !strcasecmp(argv[0]->ptr,"setex")) {
                        when *= 1000;
                }    
                when += mstime();
                /* Translate EXPIRE/PEXPIRE/EXPIREAT into PEXPIREAT */
                if (!strcasecmp(argv[0]->ptr,"expire") ||
                    !strcasecmp(argv[0]->ptr,"pexpire")) {
                    tmpargv[0] = createStringObject("PEXPIREAT",9);
                    tmpargv[1] = getDecodedObject(argv[1]);
                    tmpargv[2] = createStringObjectFromLongLong(when);
                    replicationFeedSlaves(server.slaves,dbid,tmpargv,argc);
                    decrRefCount(tmpargv[0]);
                    decrRefCount(tmpargv[1]);
                    decrRefCount(tmpargv[2]);
                }    
                /* Translate SETEX/PSETEX to SET and PEXPIREAT */
                if (!strcasecmp(argv[0]->ptr,"setex") ||
                    !strcasecmp(argv[0]->ptr,"psetex")) {
                    argc = 3;
                    tmpargv[0] = createStringObject("SET",3);
                    tmpargv[1] = getDecodedObject(argv[1]);
                    tmpargv[2] = getDecodedObject(argv[3]);
                    replicationFeedSlaves(server.slaves,dbid,tmpargv,argc);
                    tmpexpire[0] = createStringObject("PEXPIREAT",9);
                    tmpexpire[1] = getDecodedObject(argv[1]);
                    tmpexpire[2] = createStringObjectFromLongLong(when);
                    replicationFeedSlaves(server.slaves,dbid,tmpexpire,argc);
                    decrRefCount(tmpargv[0]);
                    decrRefCount(tmpargv[1]);
                    decrRefCount(tmpargv[2]);
                    decrRefCount(tmpexpire[0]);
                    decrRefCount(tmpexpire[1]);
                    decrRefCount(tmpexpire[2]);
                }
            } else {
                    replicationFeedSlaves(server.slaves,dbid,argv,argc);
            }
    }
    }

    目前上述修改已经应用到线上迁移环境中,上线以后Redis过期时间不一致问题解决,目前迁移前后的过期时间是严格保持一致的。

    迁移前后Redis key 数量不一致

    针对于第二个问题,Redis key 迁移前后数量不一致问题,其实在Redis社区版本的主从复制中,也会经常出现key数量不一致。其中一个非常关键的问题是,redis在做主从复制的时候,会对当前的存量数据做一个RDB快照(bgsave命令),然后将RDB快照传给从库,从库会解析RDB文件并且load到内存中。然儿在上述的两个步骤中Redis会忽略过期的key:

    1. 主库在做RDB快照文件的时候,发现key已经过期了,则此时不会将过期的key写到RDB文件中。
    2. 从库在load RDB文件到内存中的时候,发现key已经过期了,则此时不会将过期的key load进去。
    

    所以针对上述两个问题会造成Redis主从key不一致问题,这个对于我们做数据校验的时候会有些影响,因始终觉得key不一致,但是不影响业务逻辑。
    针对上述问题,目前我们将以上两个步骤都改为不忽略过期key,过期key的删除统一由主库触发删除,然后将删除命令传送到从库中。这样key的数量就完全一致了。
    最终在打上以上两个patch之后,再进行迁移测试的时候,验证key过期时间以及数量都是完全一致的。
    最后贴上以上修改的代码(针对于社区版本Redis 3.0.7):

    以下代码修改均在我标记注释的下面

    1、做bgsave的时候不忽略过期的key

    rdb.c

    /* Save a key-value pair, with expire time, type, key, value.
     * On error -1 is returned.
     * On success if the key was actually saved 1 is returned, otherwise 0
     * is returned (the key was already expired). */
    int rdbSaveKeyValuePair(rio *rdb, robj *key, robj *val,
                            long long expiretime, long long now)
    {
        /* Save the expire time */
        if (expiretime != -1) {
            /* If this key is already expired skip it */
            /* 注释下面这一行 */
            /* if (expiretime < now) return 0; */                                                                                                                                                
            if (rdbSaveType(rdb,REDIS_RDB_OPCODE_EXPIRETIME_MS) == -1) return -1;
            if (rdbSaveMillisecondTime(rdb,expiretime) == -1) return -1;
        }   
    
        /* Save type, key, value */
        if (rdbSaveObjectType(rdb,val) == -1) return -1;
        if (rdbSaveStringObject(rdb,key) == -1) return -1;
        if (rdbSaveObject(rdb,val) == -1) return -1;
        return 1;
    }

    2、做bgrewirteaof 的时候不忽略过期的key

    aof.c

    int rewriteAppendOnlyFile(char *filename) {
        dictIterator *di = NULL;
        dictEntry *de;
        rio aof;
        FILE *fp;
        char tmpfile[256];
        int j;
        long long now = mstime();
        char byte;
        size_t processed = 0;
    
        /* Note that we have to use a different temp name here compared to the
         * one used by rewriteAppendOnlyFileBackground() function. */
        snprintf(tmpfile,256,"temp-rewriteaof-%d.aof", (int) getpid());
        fp = fopen(tmpfile,"w");
        if (!fp) {
            redisLog(REDIS_WARNING, "Opening the temp file for AOF rewrite in rewriteAppendOnlyFile(): %s", strerror(errno));
            return REDIS_ERR;
        }   
    
        server.aof_child_diff = sdsempty();
        rioInitWithFile(&aof,fp);
        if (server.aof_rewrite_incremental_fsync)
            rioSetAutoSync(&aof,REDIS_AOF_AUTOSYNC_BYTES);
        for (j = 0; j < server.dbnum; j++) {
            char selectcmd[] = "*2\r\n$6\r\nSELECT\r\n";
            redisDb *db = server.db+j;
            dict *d = db->dict;
            if (dictSize(d) == 0) continue;
            di = dictGetSafeIterator(d);
            if (!di) {
                fclose(fp);
                return REDIS_ERR;
            }   
    
            /* SELECT the new DB */
            if (rioWrite(&aof,selectcmd,sizeof(selectcmd)-1) == 0) goto werr;
            if (rioWriteBulkLongLong(&aof,j) == 0) goto werr;
    
            /* Iterate this DB writing every entry */  
            while((de = dictNext(di)) != NULL) {
                sds keystr;
                robj key, *o;
                long long expiretime;
     
                keystr = dictGetKey(de);
                o = dictGetVal(de);
                initStaticStringObject(key,keystr);
     
                expiretime = getExpire(db,&key);
     
                /* If this key is already expired skip it */
                /* 注释下面这一行 */
                /* if (expiretime != -1 && expiretime < now) continue; */

    3、在load rdb 的时候不忽略过期key

    rdb.c

    int rdbLoad(char *filename) {
        uint32_t dbid;
        int type, rdbver;
        redisDb *db = server.db+0;
        char buf[1024];
        long long expiretime, now = mstime();
        FILE *fp;
        rio rdb;
    
        if ((fp = fopen(filename,"r")) == NULL) return REDIS_ERR;
    
        rioInitWithFile(&rdb,fp);
        rdb.update_cksum = rdbLoadProgressCallback;
        rdb.max_processing_chunk = server.loading_process_events_interval_bytes;
        if (rioRead(&rdb,buf,9) == 0) goto eoferr;
        buf[9] = '\0';
        if (memcmp(buf,"REDIS",5) != 0) {
            fclose(fp);
            redisLog(REDIS_WARNING,"Wrong signature trying to load DB from file");
            errno = EINVAL;
            return REDIS_ERR;
        }
        rdbver = atoi(buf+5);
        if (rdbver < 1 || rdbver > REDIS_RDB_VERSION) {
            fclose(fp);
            redisLog(REDIS_WARNING,"Can't handle RDB format version %d",rdbver);
            errno = EINVAL;
            return REDIS_ERR;
        }
    
        startLoading(fp);
        while(1) {
            robj *key, *val;
            expiretime = -1;
    
            /* Read type. */
            if ((type = rdbLoadType(&rdb)) == -1) goto eoferr;
            if (type == REDIS_RDB_OPCODE_EXPIRETIME) {
                if ((expiretime = rdbLoadTime(&rdb)) == -1) goto eoferr;
                /* We read the time so we need to read the object type again. */
                if ((type = rdbLoadType(&rdb)) == -1) goto eoferr;
                /* the EXPIRETIME opcode specifies time in seconds, so convert
                 * into milliseconds. */
                expiretime *= 1000;
            } else if (type == REDIS_RDB_OPCODE_EXPIRETIME_MS) {
                /* Milliseconds precision expire times introduced with RDB
                 * version 3. */
                if ((expiretime = rdbLoadMillisecondTime(&rdb)) == -1) goto eoferr;
                /* We read the time so we need to read the object type again. */
                if ((type = rdbLoadType(&rdb)) == -1) goto eoferr;
            }
     
            if (type == REDIS_RDB_OPCODE_EOF)
                break;
     
            /* Handle SELECT DB opcode as a special case */
            if (type == REDIS_RDB_OPCODE_SELECTDB) {
                if ((dbid = rdbLoadLen(&rdb,NULL)) == REDIS_RDB_LENERR)
                    goto eoferr;
                if (dbid >= (unsigned)server.dbnum) {
                    redisLog(REDIS_WARNING,"FATAL: Data file was created with a Redis server configured to handle more than %d databases. Exiting\n", server.dbnum);
                    exit(1);
                }
                db = server.db+dbid;
                continue;
            }
            /* Read key */
            if ((key = rdbLoadStringObject(&rdb)) == NULL) goto eoferr;
            /* Read value */
            if ((val = rdbLoadObject(type,&rdb)) == NULL) goto eoferr;
            /* Check if the key already expired. This function is used when loading
             * an RDB file from disk, either at startup, or when an RDB was
             * received from the master. In the latter case, the master is
             * responsible for key expiry. If we would expire keys here, the
             * snapshot taken by the master may not be reflected on the slave. */
             /* 注释下面5行 */
            /* if (server.masterhost == NULL && expiretime != -1 && expiretime < now) {
                decrRefCount(key);
                decrRefCount(val);
                 continue;
            } */
            /* Add the new object in the hash table */
            dbAdd(db,key,val);
     
            /* Set the expire time if needed */
            if (expiretime != -1) setExpire(db,key,expiretime);
     
            decrRefCount(key);
        }

    总结

    注意上述修改在内存策略为noeviction一直有效,但是其他内存策略只能在Redis 使用内存小于最大内存的时候才会有效,因为从库在使用内存超过最大内存的时候也会触发淘汰,这个时候也没法完全保证数据一致性了。

    展开全文
  • 更新数据时:先删除缓存,再更新...缓存设置过期时间(从一定程度上解决数据不一致的情况,并不能完全解决) 使用队列,保证对相同id操作在同一队列中,一个操作执行完成再执行下一个操作 通过binlog来更新缓存 ...
    1. 更新数据时:先删除缓存,再更新数据库(并发量大的时候仍会造成数据不一致)
    2. 缓存设置过期时间(从一定程度上解决数据不一致的情况,并不能完全解决)
    3. 使用队列,保证对相同id操作在同一队列中,一个操作执行完成再执行下一个操作
    4. 通过binlog来更新缓存
    展开全文
  • 同步延迟可能会导致主从数据不一致。例如:主库正在进行写操作,从库同时也正在读操作,但此时从库还未同步到主库的最新数据,导致从库读到脏数据。 如何解决 忽略:在业务不保证数据强一致性的情况下,可以选择忽略...
  • 你只要用缓存,就可能会涉及到缓存与数据库双存储双写,你只要是双写,就一定会有数据一致性的问题,那么你如何解决一致性问题? 面试题剖析 一般来说,如果允许缓存可以稍微的跟数据库偶尔有不一致的情况,也就是...
  • 在实际应用 Redis 缓存时,我们经常会遇到一些异常问题,概括来说有 4 ...最重要的是,如果数据不一致,那么业务应用从缓存中读取的数据就不是最新数据,这会导致严重的错误。比如说,我们把电商商品的库存信息缓存...
  • 问题解决问题就是怎么对比不一致,然后在不影响业务的情况下,修复数据不一致问题,把从库缺少的数据补上下面是能想到和找到的几个方案1 从新从0开始同步,虽然对主库的使用没有影响,但是那么大的数据量,对...
  • 针对实时Web应用(如:实时通信、股票基金应用、体育实况更新、多玩家游戏等场景),传统Web中为了实时获取Server端的数据,通常是Client端定期发送HTTP请求,...为解决上述问题,越来越多企业在思考如何解决长...
  • 不一致产生的原因 我们在使用redis过程中,通常会这样做:先读取缓存,如果缓存不存在,则读取数据库。伪代码如下:Object stuObj = new Object();public Stu getStuFromCache(String key){ Stu stu = (Stu) redis....
  • 关注爱因诗贤每天进步一点点导读只要在业务中使用缓存,就必然会面对缓存和数据库之间的一致性保证问题了,这也是Redis缓存应用中的必答题,如果某些业务场景数据不一致,就会导致严重的错误,比如某个商品库存信息...
  • 生产环境中经常使用到Oracle的IMP导入和EXP导出来功能来达到数据迁移的目的,通常在源数据库和目标数据库中查询字符集是否致, 测试环境中导入IMP导入报错信息如下: 导入命令如下: [oracle@localhost.localdomain:/...
  • 问题描述 在做某个POC项目的测试时候,先pyspark2界面count表A一共有3条,在另一个界面用beeline往这种表插入一条数据,然后继续在beeline中count,此时显示总数为4,插入正常。但是回到pyspark2的界面执行sql语句...
  • image这个业务场景,主要是解决数据从Redis缓存,一般都是按照下图的流程来进行业务操作。image读取缓存步骤一般没有什么问题,但是一旦涉及到数据更新:数据库和缓存更新,就容易出现缓存(Redis)和数据库(MyS...
  • 本帖最后由 hbm1985 于 2012-5-10 07:39 编辑本人在搭建oracle goldengate 双向同步时,遇到一个问题:当两边同时更新同一条记录时,会出现两边数据不一致问题。A库最终拿到的是B库数据更新的数据。B库最终拿到的...
  • 在高并发的业务场景下,...数据为什么会不一致 这样的问题主要是在并发读写访问的时候,缓存和数据相互交叉执行。 一、单库情况下 同一时刻发生了并发读写请求,例如为A(写) B (读)2个请求 A请求发送一个写...
  • 数学上验证完备性可能有点复杂,...除了单纯转换字段结构之外,还可以加一层校验方便在运行时判断是哪方的数据结构有问题然后报错。首先需要定义字段结构模板描述,比如 API response 的:{status: string,data: {a...
  • 前言通过对数据的垂直拆分或水平拆分后,我们解决了数据库容量、性能等问题,但是将会面临数据迁移和数据一致性的问题。在数据迁移方面,需要考虑如何快速迁移、平滑迁移、停机的迁移等。待数据迁移完毕后,还需要...
  • 需求起因 在高并发的业务场景下,数据库...这个业务场景,主要是解决数据从Redis缓存,一般都是按照下图的流程来进行业务操作。 image 读取缓存步骤一般没有什么问题,但是一旦涉及到数据更新:...
  • 目录 前言 为什么会导致不一致 方案一:后台缓存标记法 方案二:延迟消息 方案三:更新用户再次发起读请求 前言 在你知道如何更新缓存吗?...这篇文章,如何解决DB数据库的数据不一致问题。 在缓存和数...
  •   对于热点数据(经常被查询,但经常被修改的数据),我们可以将其放入redis缓存中,以增加查询效率,但需要保证从redis中读取的数据与数据库中存储的数据最终是一致的。本文基于“孤独烟”与“58沈剑”两位的...
  • 数据为什么会不一致这样的问题主要是在并发读写访问的时候,缓存和数据相互交叉执行。一、单库情况下同一时刻发生了并发读写请求,例如为A(写) B (读)2个请求A请求发送一个写操作到服务端,第一步会淘汰cac...
  • 数据为什么会不一致这类问题主要在于并发读写访问,缓存和数据相互交叉执行。一、单库情况下同一时间发生了并发读写请求,比如A(写) ,B (读),2个请求A请求发送一个写的操作到服务端,第一步就...
  • kaggle :房屋价格预测问题~ #!user/bin/env python # -*- coding:utf-8 -*- import numpy as np import pandas as pd from scipy.stats import mode from sklearn import linear_model from sklearn.cross_...
  • 不一致产生的原因 我们在使用redis过程中,通常会这样做:先读取缓存,如果缓存不存在,则读取数据库。伪代码如下:Object stuObj = new Object(); public Stu getStuFromCache(String key){ Stu stu = (Stu) ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,937
精华内容 774
关键字:

如何解决数据不一致问题