动态扩缩容/容灾的一点思考

当下,越来越多的业务场景,由于规模的扩大,基本都使用了分布式架构。.
分布式架构的服务模型:基于请求key,通过路由算法(随机、取模、一致性hash)选取服务节点,完成逻辑处理后返回结果。

服务器服务分为有状态服务和无状态服务。
所谓无状态,就是一次请求一次处理结果,不存在中间态,没有缓存数据。
而有状态则是某一次的请求依赖上一次请求处理的中间结果,比如游戏业务中的某次玩家操作必然依赖于登录请求等。

静态扩缩容
不论是否有状态,均可以支持。

动态扩缩容
节点宕机与恢复属于动态扩缩容的范畴,宕机缩容,恢复扩容。

无状态服务,扩缩容|容灾无需额外处理,扩缩容的时候直接增删服务节点,而遇到宕机、网络不通等情况,直接下线问题节点,后续消息基于路由规则,路由到其他节点。
有状态服务,会产生一些问题,比如以下场景
1 动态扩容场景:假如有AB节点,现在增加了C节点,那么B上原本服务的部分key,会路由到C,B上残留的本地缓存失效,成为冗余数据,占用内存。
2 在1的基础上,如果C宕机,B上的脏数据被使用,产生数据不一致问题。
3 网络不通恢复场景:假如有ABC节点,C网络故障,下线,C服务的key路由到B,B加载db到本地缓存,过了一会儿,C网络恢复,上线,C上的本地缓存其实已经失效,成为脏数据。

关键性问题其实就是要有合理的冗余数据/脏数据清理能力。

分布式锁方案
每个节点需要使用数据的时候,先获取锁,如果锁在其他节点,发起抢锁策略,被抢锁的节点清理缓存,释放锁,对于宕机情况,抢锁超时认为抢锁成功。
无锁方案
1 给cache设置一个多久未使用就淘汰的策略。这个时间间隔可以稍微长一些,分段轮询,主动淘汰。主要是为了应对扩容场景(1)下,冗余数据清理。
当扩容之后,原节点的内存占用会慢慢降下来,如果扩容是为了考虑cpu,不考虑内存,该策略可以不使用。
2 给cache设置一个多久未使用就失效的策略。这个时间间隔最好小于节点检测失效时间,惰性淘汰。应对场景(2)下,缓存更新。同时,对于场景3,由于网络不通的这段时间,缓存未使用,在使用之前,会被更新。
这里存在一个性能上的考量,因为这个策略不会考虑缓存是否不一致,无脑失效。
3 version检测,当一个本地cache数据超过节点失效时间未被检测,那么在使用前需要执行检测逻辑。
检测逻辑
1 从db恢复本地cache时,在redis里写入, 每次写入时更新version+1(version也可以用写入时间代替)
2 检测时,校验本地cache数据的version和redis里的version是否一致,不一致即失效
该方案相比锁方案,具备主动清理能力,并且在使用场景,由于无需每次都去校验,因此具备更高的性能。
该方案基于一个假设,一份数据仅会在一个节点被使用,如果有多端使用,需要结合锁方案实施。

部分code

bool cache::check(const char* key, long long value)
{

    redisReply* reply = (redisReply*)redis_conn.ExecutevCmd("eval %s 1 %s %lld", 
    "local ret = false \
    local result \
    result = redis.call('GET', KEYS[1]) \
    if result then \
        if result == ARGV[1] then \
            ret = true \
        end \
    else    \
        ret = true  \
    end \
    return ret", 
        key, value);
    if(reply != nullptr && REDIS_REPLY_INTEGER == reply->type && true == reply->integer)
    {
        return true;
    }
    return false;
}

bool cache::write(const char* key, long long value)
{
    redisReply* reply = (redisReply*)redis_conn.ExecutevCmd("eval %s 1 %s %lld", 
    "local ret = false \
    local result \
    result = redis.call('SET', KEYS[1], ARGV[1]) \
    if result and result.ok then \
        return true \
    end \
    return false",
        key, value);
    if(reply != nullptr && REDIS_REPLY_INTEGER == reply->type && true == reply->integer)
    {
        return true;
    }
    return false;
}
// use 这里的1 2 实际可以使用节点名字或者ipport的hash代替
    printf("write 1\n");
    bool result = cache::instance().write("test_key", 1);
    
    result = cache::instance().check("test_key", 1);
    printf("checked 1 %d\n", result);
    result = cache::instance().check("test_key", 2);
    printf("checked 2 %d\n", result);

    printf("write 2\n");
    result = cache::instance().write("test_key", 2);

    result = cache::instance().check("test_key", 1);
    printf("checked 1 %d\n", result);
    result = cache::instance().check("test_key", 2);
    printf("checked 2 %d\n", result);
运行结果:
write 1
checked 1 1
checked 2 0
write 2
checked 1 0
checked 2 1

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值