高并发原子性处理

高并发原子性处理

场景:redis校验是否存在->存表

相同的请求1000次/s

    //判断是否请求过
    if(redis.get(key,hashKey)!=null){
        //重复请求,抛出异常
        throw new RunTimeException("重复请求");
    }
    //没有请求过,存redis
    redis.put(key,hashKey,value);
    //存表
    saveDB();

发现表中存在多条数据

解决方式一:synchronized

    synchronized(this){
        //判断是否请求过
        if(redis.get(key,hashKey)!=null){
            //重复请求,抛出异常
            throw new RunTimeException("重复请求");
        }
        //没有请求过,存redis
        redis.put(key,hashKey,value);
        redis.expire(key,timeout);
        //存表
        saveDB();
    }

问题:

这种方式并不是原子性的,还是可能会存在问题,比如put和expire其中一条成功,一条失败。

分布式环境下,也会有问题,可能会每台机都存一条

redis

解决方式二:setnx

    if(redisTemplate.opsForValue().setIfAbsent(key,value,Duration.ofMillis(timeout))){
        //存表
        saveDB();
    }
    else{
        //重复请求,抛出异常
        throw new RunTimeException("重复请求");
    }

多台redis服务器可能会出现问题,(setnx(key,value,1000)的时候,A服务器存入缓存,还没同步到本地或者没来得及同步到slave就挂了,下一次(setnx(key,value,1000))的时候,在集群中是没有的。这时候就多条的情况。

解决方式三:RedLock

  1. 获取当前时间戳
  2. client尝试按照顺序使用相同的key,value获取所有redis服务的锁,在获取锁的过程中的获取时间比锁过期时间短很多,这是为了不要过长时间等待已经关闭的redis服务。并且试着获取下一个redis实例。
    • 比如:TTL为5s,设置获取锁最多用1s,所以如果一秒内无法获取锁,就放弃获取这个锁,从而尝试获取下个锁
  3. client通过获取所有能获取的锁后的时间减去第一步的时间,这个时间差要小于TTL时间并且至少有3个redis实例成功获取锁,才算真正的获取锁成功
  4. 如果成功获取锁,则锁的真正有效时间是 TTL减去第三步的时间差 的时间;比如:TTL 是5s,获取所有锁用了2s,则真正锁有效时间为3s(其实应该再减去时钟漂移);
  5. 如果客户端由于某些原因获取锁失败,便会开始解锁所有redis实例;因为可能已经获取了小于3个锁,必须释放,否则影响其他client获取锁

注意的点:

  1. 先假设client获取所有实例,所有实例包含相同的key和过期时间(TTL) ,但每个实例set命令时间不同导致不能同时过期,第一个set命令之前是T1,最后一个set命令后为T2,则此client有效获取锁的最小时间为TTL-(T2-T1)-时钟漂移;
  2. 对于以N/2+ 1(也就是一半以 上)的方式判断获取锁成功,是因为如果小于一半判断为成功的话,有可能出现多个client都成功获取锁的情况, 从而使锁失效
  3. 一个client锁定大多数事例耗费的时间大于或接近锁的过期时间,就认为锁无效,并且解锁这个redis实例(不执行业务) ;只要在TTL时间内成功获取一半以上的锁便是有效锁;否则无效

mysql

解决方式四:Mysql insert

表:

create table database_lock(
  id int not null auto_increment primary key,
  resource int not null comment '资源',
  description varchar(1024) not null default "" comment '描述',
  version int,
  updated_time datetime,
  unique key `uiq_idx_resource` (`resource`)
)engine=InnoDB default charset=utf8;

根据resource是unique key唯一约束原则实现的。

try{
    insertIntoMysqlLock(user.getId(),"mall.user.id",1);
    saveDB();
}
catch (Exception e){
    throw new MallDefaultException("重复下单");
}
insert into database_lock value(1,'mall.user.id',1,sysdate);

解决方式五:Mysql update

insert into database_lock value(1,'unlock',1,sysdate);
if (update(user.getId(),1)) {
    saveDB();
}
else {
    throw new MallDefaultException("重复下单");
}

```sql
update database_lock set version = version+1 ,updated_time = sysdate where resource = 1 and version = 1 and description = 'unlock';

zookeeper

分布式锁三要素

1.互斥;任何时刻只能有一个client获取锁
2.释放死锁;即使锁定资源的服务崩溃或者分区,仍然能释放锁
3.容错性;只要多数redis节点(一半以上)在使用,client就可以获取和释放锁

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容