对分布式锁的认知和实现

为了防止并发编程,或多或少会接触到这个概念。Java的锁有 synchronized 和 Lock 两种。

最大的区别在于:synchronized是个关键字,而Lock是个接口类。但是Java的锁,只能保证在同一个JVM中执行的时候起作用,那么分布式项目呢?

 

一、什么是锁

    锁的执行:

       · 加锁(锁持有)

             去KTV开了个包间,把门一关,这个时候你就给这个包间加了个锁,明确说明这个包间是本大爷的了。其他人想用这个包间,就只能等你用完了。这个时候你持有了锁。

       · 解锁(锁释放)

             唱着唱着你不想唱了,把包间一退,这个时候别人就能使用这个包间了,你就把这个锁主动给释放掉了。

       · 锁超时:

             唱嗨了怎么办?忘了时间忘了我。拿着锁不释放。这个时候包间会强制终止包间里的播放器,告诉你超时了,该走了。这个时候也会释放掉你的锁,但是这种释放是被动的。

 

二、分布式锁

          思想:分布式锁是一种思想,照顾要

          分类:分布式锁的实现有很多,比如基于数据库、memcached、Redis、系统文件、zookeeper等。

 

三、为什么要用分布式锁

     为了解决在不同JVM中,线程并发执行的问题。

     如:我有一个定时任务,在机器A、B、C上都有,如果我不加分布式锁,那么它会执行3次。如果这个定时任务是发微信模板推送的,那么用户会接收到3次推送,就很烦,可能接到大量投诉,饭碗除脱。

 

四、各种分布式锁实现介绍

4.1 数据库

  也不推荐用,不如单线程的redis来得痛快。

  1.建表

DROP TABLE IF EXISTS `method_lock`;
CREATE TABLE `method_lock`  (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `method_name` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '方法名',
  `expiration_time` datetime(0) NOT NULL COMMENT '过期时间',
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE INDEX `uidx_method_name`(`method_name`) USING BTREE COMMENT '方法名唯一'
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '方法锁表' ROW_FORMAT = Dynamic;

  2.想要执行某个方法,就使用这个方法名向表中插入数据;

  3.因为对方法名建了唯一索引,所以只能插入一条。插入成功的算锁定成功。插入失败的,未获取到锁。

  4.方法执行结束,执行删除该方法名的sql

  5.锁失效机制,定时任务检测数据库给过期的数据执行删除操作。(定时任务可能也会出现临界值的相关情况)

  6.因为是基于数据库的操作,数据库的可用性和性能将直接影响分布式锁的可用性及性能,所以,数据库需要双机部署、数据同步、主备切换。

 

4.2 memcached

  有点LOW

 

4.3 Redis

 4.3.1 不可重入锁

  1.引包

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>3.1.0</version>
</dependency>

  2.注入类

    public static JedisCluster jedisCluster = SpringUtils.getBean(JedisCluster.class);

  3.加锁解锁方法

package com.nanci.utils;

import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.params.SetParams;

import java.util.Collections;
 
/**
 *
 * redis实现分布式锁,并释放锁
 */
public class RedisUtils {
 
    private static final String LOCK_SUCCESS = "OK";
    private static final String SET_IF_NOT_EXIST = "NX";
    private static final String SET_WITH_EXPIRE_TIME = "PX";
    private static final Long RELEASE_SUCCESS = 1L;
 
    /**
     * 尝试获取分布式锁
     * @param jedis Redis客户端
     * @param lockKey 锁
     * @param requestId 请求标识
     * @param expireTime 超期时间,毫秒
     * @return 是否获取成功
     */
    public static boolean tryGetDistributedLock(JedisCluster jedis, String lockKey, String requestId, Long expireTime) {
        /*
        *设置锁并设置超时时间,lockKey表示Redis key,requestId表示Redis value,SET_IF_NOT_EXIST表示有值不进行设置(NX),
        * SET_WITH_EXPIRE_TIME表示是否设置超时时间(PX)设置,expireTime表示设置超时的毫秒值
        * */
        // jedis---3.1.0的版本
        String result = jedis.set(lockKey, requestId, new SetParams().px(expireTime));
        // jedis---2.9.0的版本
        //String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
        if (LOCK_SUCCESS.equals(result)) {
            return true;
        }
        return false;
    }
 
    /**
     * 释放分布式锁
     * @param jedis Redis客户端
     * @param lockKey 锁
     * @param requestId 请求标识
     * @return 是否释放成功
     */
    public static boolean releaseDistributedLock(JedisCluster jedis, String lockKey, String requestId) {
        /*
        * 利用Lua脚本代码,首先获取锁对应的value值,检查是否与requestId相等,如果相等则删除锁(解锁)
        * eval命令执行Lua代码的时候,Lua代码将被当成一个命令去执行,并且直到eval命令执行完成,Redis才会执行其他命令,这样就不会出现上一个代码执行完挂了后边的出现问题,还是一致性的解决
        * */
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
 
        if (RELEASE_SUCCESS.equals(result)) {
            return true;
        }
        return false;
 
    }
 
}

 4.3.2 可重入锁

    redisson

 4.4 zookeeper

     一般用不上

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值