Redis监听 集群阻塞锁

2 篇文章 0 订阅
1 篇文章 0 订阅

前言

之前项目需要对数据库数据下的记录进行更新的时候设计多个进程中多线程操作,为保证数据的准确需要进行加锁操作;一开始通过setnx那种形式,在没有获取到锁的时候,线程不能等待,除非无限循环,但比较耗费行能,后来就想着能否像synchronized那样自己等待,由此出发。

项目结构

在这里插入图片描述

redis配置

redis的配置类,根据自己看着配,没啥说的

@Configuration
public class RedisConfiguration {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(factory);
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        redisTemplate.setKeySerializer(stringRedisSerializer);
        redisTemplate.setHashKeySerializer(stringRedisSerializer);
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }
}

redis监听配置

一、redis监听容器

@Configuration
public class RedisLockMessageListenerContainer {
    @Bean
    public MessageListenerAdapter messageListenerAdapter() {
        return new MessageListenerAdapter(new RedisLockListener());
    }

    @Bean
    public RedisMessageListenerContainer redisContainer(RedisConnectionFactory factory) {
        final RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(factory);
        container.addMessageListener(messageListenerAdapter(), new ChannelTopic("redis-topic"));
        return container;
    }
}

二、实际监听类

@Slf4j
@Component
public class RedisLockListener implements MessageListener {

    @Override
    public void onMessage(Message message, byte[] bytes) {
        byte[] channel = message.getChannel();
        byte[] body = message.getBody();
        Jackson2JsonRedisSerializer<String> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<String>(String.class);
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        String msg = jackson2JsonRedisSerializer.deserialize(body);
        String topic = stringRedisSerializer.deserialize(channel);
        if (topic.equals("redis-topic")) {
            RedisUtil redisUtil = SpringUtil.getBean(RedisUtil.class);
            redisUtil.notifyLock(msg);
        }
    }
}

redis操作工具

@Slf4j
@Component
public class RedisUtil {
    private static Map<String, String> synchronizedMap = new HashMap<>();

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    public void publish(String topic, String msg) {
        redisTemplate.convertAndSend(topic, msg);
    }

    public Boolean getLock(String key, String value, Long timeOut) {
        boolean flag = redisTemplate.opsForValue().setIfAbsent(key, value, timeOut, TimeUnit.SECONDS);
        log.info("redis获取锁:{}-{}-{}", key, value, flag);
        return flag;
    }

    @SneakyThrows
    public void getLock(String lock) {
        while(!getLock(lock,lock,100l)) {
            String lockKey = getValue(lock);
            synchronized (lockKey) {
                lockKey.wait();
            }
        }
    }

    private String getValue(String lock) {
        synchronizedMap.putIfAbsent(lock, lock);
        return synchronizedMap.get(lock);
    }

    public void notifyLock(String key) {
        String lock = synchronizedMap.get(key);
        if (null != lock) {
            synchronized (lock) {
                synchronizedMap.remove(lock);
                lock.notifyAll();
            }
        }
    }

    public void delete(String lock) {
        redisTemplate.delete(lock);
    }
}

这里主要想法就是redis在获取不到锁的时候通过synchronized和wait方法阻塞线程,等待监听到后唤醒线程;getValue这个方法的作用是保证用到同一个锁的时候,用同一个对象去锁线程(此处一直认为这个方法应该加上synchronized,但是后期测试的时候发现不加也行,想不明白,这里我去了),例如:A、B、C三个线程都是操作同一条数据的时候,A进程先执行,B、C需要加锁,若用不同的对象锁的话,那么监听到的时候就需要不同的对象去唤醒,所以这里对同一条数据的多个线程操作采用同个锁;而在不同进程间通过redis本身就可以控制了。

测试

随便写了简单的测试

@Slf4j
@RestController
public class TestController {

    @Autowired
    TestService testService;

    @GetMapping("/test")
    public void test() {
        ThreadPoolExecutor executor = new ThreadPoolExecutor(10, 10, 0L, TimeUnit.SECONDS, new ArrayBlockingQueue<>(10));
        for (int i = 0; i < 10; i++) {
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    testService.add(Thread.currentThread().getName());
                }
            });
        }
    }

    @GetMapping("/getnum")
    public void getNum() {
        log.info("**************{}", testService.getNum());
    }
}

@Slf4j
@Service
public class TestService {

    @Autowired
    RedisUtil redisUtil;

    int num = 1;

    public void add(String name) {
        log.info("step into thread-{}........", name);
        redisUtil.getLock("lock");
        log.info("{} get lock true===========", name);
        num = num + 1;
        log.info("{} execute end -----------", name);
        redisUtil.delete("lock");
        redisUtil.publish("redis-topic", "lock");
    }

    public int getNum() {
        return num;
    }
}

启动项目,可以改变端口号启动多次,模拟多个进程,这边只启动一个进程不多做测试了,因为在实际运用中已测过多个进程
在这里插入图片描述
先通过 http://localhost:9999/getnum看下num值
在这里插入图片描述
再调用 http://localhost:9999/test,可以观察下日志
在这里插入图片描述
再调用下getnum查看num值
在这里插入图片描述

配置文件

server.port=9999
spring.redis.database=0
spring.redis.host=127.0.0.1
spring.redis.port=6379
#spring.redis.password=aaa
spring.redis.jedis.pool.max-active=20
spring.redis.jedis.pool.max-idle=10
spring.redis.jedis.pool.max-wait=-1
spring.redis.jedis.pool.min-idle=0
spring.redis.timeout=1000
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值