记录一个redis并发问题

1,105 阅读2分钟

博客索引

有一个用redis去重的情景。当时业务代码如下:


public void handle(String key){

// 判断集合中是否存在key元素

if(redis.sismember("myset",key)){

return;

}else{

// 如果不存在则添加key元素到myset集合中

redis.sadd("myset",key);

}

// 业务代码

伪代码的含义就是:如果集合中已经存在了元素key,那么直接返回;若不存在,则向集合myset中添加元素key,执行业务代码。

咋一看好像没啥问题,但是由于存在并发的情况,会导致了业务数代码重复执行。

这里假设有两个线程同时调用handle方法,入参key都为007。执行顺序如下:

线程一线程二
redis.sismember("myset",key)返回false
切换redis.sismember("myset",key)返回false
执行redis.sadd("myset",007);
切换
执行redis.sadd("myset",007);
执行业务代码
执行业务代码

线程一调用的redis.sismember("myset",key)的时候,显然集合中没有007,返回false,跳转到else。这个时候时候线程一的时间片用完了,切换到线程二。

线程二也开始调用redis.sismember("myset",key),由于线程一还没有执行添加方法,所有这里集合中还是不存在007这个元素,还是返回false,然后线程二执行sadd("myset",key)方法添加007元素,然后线程二再次切换到线程一,线程一继续向下执行sadd("myset",key),添加007元素。

这里就会出现多次执行业务代码,导致数据出现问题。

虽然redis的单个命令都能保证原子性,但是上面伪代码的“如果不存在就添加(put-if-absent)”的操作存在竞态条件。虽然可以通过加锁来保证该方法的原子性,

但是由于这里使用的是redis,而redis正好提供了一个原子命令setnx key value,如果不存在key,则设置,如果存在,则什么也不做;所以这里只需要修改伪代码。


// 修改后的伪代码

public void handle(String key){

if(!redis.setnx(key,0)){

return;

}

// 业务代码

留下一个问题,如果这里的集合换成是线程安全的vector,那么该如何修改呢?