分布式锁-redis实现(1)

本文涉及的产品
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
简介: 一、前言      前段时间出去耍,没得时间写博客,最近没有什么系统性的文章,写一些实际工作中遇到的问题。

一、前言

      前段时间出去耍,没得时间写博客,最近没有什么系统性的文章,写一些实际工作中遇到的问题。我个人项目在公司服务器是部署到两台机子上,但是在消费资源的时候,出现数据同步问题。我们原来用的是class的实例做同步出来,这个实例对于单台机子拥有,对两台机子就失去作用了。这个时候就需要分布式锁实现同步处理。

二、redis 实现分布式锁

      首先我想分享一点,当然在网上看的redis 的源码,自己没有仔细分析,redis 的同步是支持多进程多线程的,最后都是用文件来做同步,这样就摆脱单线程问题。既然它本身解决了分布式锁的问题,我们就可以用它来解决我们事物同步问题。对于redis不了解的同学不重要,毕竟只是一个数据库,你不是dba一样用mysql是不是??

2.1 编写redis 锁

 对于 redis锁的主要步骤如下,思路首先要清晰

  •       首先初始化redis获取redis客户端jedis(或者SharedJedis)
  •       制定锁定时间参数,锁等待与锁超时
  •       编写锁方法
  •       关闭锁
注:再次我们就要贴出代码了,  代理里面前面的都是构造方法,最后两个方法才是关键(申明:代码来自网络)
package com.github.jedis.lock;

import redis.clients.jedis.Jedis;

/**
 * Redis distributed lock implementation.
 */
public class JedisLock {

    Jedis jedis;

    /**
     * Lock key path.
     */
    String lockKey;

    /**
     * Lock expiration in miliseconds.
     * 锁超时,防止线程在入锁以后,无限的执行等待
     */
    int expireMsecs = 60 * 1000;

    /**
     * Acquire timeout in miliseconds.
     * 锁等待,防止线程饥饿
     */
    int timeoutMsecs = 10 * 1000;

    boolean locked = false;

    /**
     * Detailed constructor with default acquire timeout 10000 msecs and lock expiration of 60000 msecs.
     *
     * @param jedis
     * @param lockKey lock key (ex. account:1, ...)
     */
    public JedisLock(Jedis jedis, String lockKey) {
        this.jedis = jedis;
        this.lockKey = lockKey;
    }

    /**
     * Detailed constructor with default lock expiration of 60000 msecs.
     *
     * @param jedis
     * @param lockKey      lock key (ex. account:1, ...)
     * @param timeoutMsecs acquire timeout in miliseconds (default: 10000 msecs)
     */
    public JedisLock(Jedis jedis, String lockKey, int timeoutMsecs) {
        this(jedis, lockKey);
        this.timeoutMsecs = timeoutMsecs;
    }

    /**
     * Detailed constructor.
     *
     * @param jedis
     * @param lockKey      lock key (ex. account:1, ...)
     * @param timeoutMsecs acquire timeout in miliseconds (default: 10000 msecs)
     * @param expireMsecs  lock expiration in miliseconds (default: 60000 msecs)
     */
    public JedisLock(Jedis jedis, String lockKey, int timeoutMsecs, int expireMsecs) {
        this(jedis, lockKey, timeoutMsecs);
        this.expireMsecs = expireMsecs;
    }

    /**
     * Detailed constructor with default acquire timeout 10000 msecs and lock expiration of 60000 msecs.
     *
     * @param lockKey lock key (ex. account:1, ...)
     */
    public JedisLock(String lockKey) {
        this(null, lockKey);
    }

    /**
     * Detailed constructor with default lock expiration of 60000 msecs.
     *
     * @param lockKey      lock key (ex. account:1, ...)
     * @param timeoutMsecs acquire timeout in miliseconds (default: 10000 msecs)
     */
    public JedisLock(String lockKey, int timeoutMsecs) {
        this(null, lockKey, timeoutMsecs);
    }

    /**
     * Detailed constructor.
     *
     * @param lockKey      lock key (ex. account:1, ...)
     * @param timeoutMsecs acquire timeout in miliseconds (default: 10000 msecs)
     * @param expireMsecs  lock expiration in miliseconds (default: 60000 msecs)
     */
    public JedisLock(String lockKey, int timeoutMsecs, int expireMsecs) {
        this(null, lockKey, timeoutMsecs, expireMsecs);
    }

    /**
     * @return lock key
     */
    public String getLockKey() {
        return lockKey;
    }

    /**
     * Acquire lock.
     *
     * @return true if lock is acquired, false acquire timeouted
     * @throws InterruptedException in case of thread interruption
     */
    public synchronized boolean acquire() throws InterruptedException {
        return acquire(jedis);
    }

    /**
     * Acquire lock.
     *
     * @param jedis
     * @return true if lock is acquired, false acquire timeouted
     * @throws InterruptedException in case of thread interruption
     */
    public synchronized boolean acquire(Jedis jedis) throws InterruptedException {
        //锁等待,防止线程饥饿
        int timeout = timeoutMsecs;
        while (timeout >= 0) {
            //锁超时,防止线程在入锁以后,无限的执行等待(为什么加1:因为可能有相同时间的操作,这样做不完美,但是实用)
            long expires = System.currentTimeMillis() + expireMsecs + 1;
            String expiresStr = String.valueOf(expires); //锁到期时间
            if (jedis.setnx(lockKey, expiresStr) == 1) {
                // lock acquired
                locked = true;
                return true;
            }

            String currentValueStr = jedis.get(lockKey); //redis里的时间
            if (currentValueStr != null && Long.parseLong(currentValueStr) < System.currentTimeMillis()) {
                //判断是否为空,不为空的情况下,如果被其他线程设置了值,则第二个条件判断是过不去的
                // lock is expired

                String oldValueStr = jedis.getSet(lockKey, expiresStr);
                //获取上一个锁到期时间,并设置现在的锁到期时间,
                //只有一个线程才能获取上一个线上的设置时间,因为jedis.getSet是同步的
                if (oldValueStr != null && oldValueStr.equals(currentValueStr)) {
                    //如过这个时候,多个线程恰好都到了这里,但是只有一个线程的设置值和当前值相同,他才有权利获取锁
                    // lock acquired
                    locked = true;
                    return true;
                }
            }
            timeout -= 100;
            Thread.sleep(100);
        }
        return false;
    }

    /**
     * Acqurired lock release.
     */
    public synchronized void release() {
        release(jedis);
    }

    /**
     * Acqurired lock release.
     */
    public synchronized void release(Jedis jedis) {
        if (locked) {
            jedis.del(lockKey);
            locked = false;
        }
    }
}

以上锁定原理:就是锁定一个键,时间为自己定义,为了防止其他操作修改值 特意判断如下
Long.parseLong(currentValueStr) < System.currentTimeMillis()
保证了每次都是原来的值。

2.2 测试锁

package com.chinaso.phl.concurrent;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import org.apache.commons.pool2.impl.GenericObjectPoolConfig;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

import com.github.jedis.lock.JedisLock;

/**
 * @author come from network
kk @date 2014-3-1
 */
public class JedisLockTest {
    private static ExecutorService executor = Executors.newFixedThreadPool(100);

    /**
     * @param args
     * @author piaohailin
     * @date 2014-3-1
     */
    public static void main(String[] args) {
//        final JedisPool pool = new JedisPool("192.168.56.2", 6379);
        GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
        final JedisPool pool = new JedisPool(poolConfig, "192.168.142.237", 6379, 3000); //最后一个参数为密码
        final String key = "h";
        for (int i = 0; i < 5; i++) {
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        Jedis jedis = pool.getResource();
                        JedisLock lock = new JedisLock(jedis, key, 3000, 10000);
                        if (lock.acquire()) {
                            //如果锁上了
                            try {
                                Thread.sleep(1000);
                                System.out.println(System.currentTimeMillis());
                            } catch (Exception e) {
                                e.printStackTrace();
                            } finally {
                                lock.release();//则解锁
                            }
                        }
                    } catch (Throwable t) {
                        t.printStackTrace();
                    }
                }
            });
        }
    }

}

测试结果:
1459417921818
1459417922871
1459417923922

2.2.1 观看不能锁的情况

package com.chinaso.phl.concurrent;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import org.apache.commons.pool2.impl.GenericObjectPoolConfig;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

import com.github.jedis.lock.JedisLock;

/**
 * @author come from network
kk @date 2014-3-1
 */
public class JedisLockTest {
    private static ExecutorService executor = Executors.newFixedThreadPool(100);

    /**
     * @param args
     * @author piaohailin
     * @date 2014-3-1
     */
    public static void main(String[] args) {
//        final JedisPool pool = new JedisPool("192.168.56.2", 6379);
        GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
        final JedisPool pool = new JedisPool(poolConfig, "192.168.142.237", 6379, 3000); //最后一个参数为密码
        final String key = "h";
        for (int i = 0; i < 3; i++) {
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        Jedis jedis = pool.getResource();
                        JedisLock lock = new JedisLock(jedis, key, 3000, 10000);
                        if (true) {
                            //如果锁上了
                            try {
                                Thread.sleep(1000);
                                System.out.println(System.currentTimeMillis());
                            } catch (Exception e) {
                                e.printStackTrace();
                            } finally {
                                lock.release();//则解锁
                            }
                        }
                    } catch (Throwable t) {
                        t.printStackTrace();
                    }
                }
            });
        }
    }

}

测试结果
1459418004056
1459418004056
1459418004056


    
      
    
相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
11天前
|
监控 NoSQL 算法
探秘Redis分布式锁:实战与注意事项
本文介绍了Redis分区容错中的分布式锁概念,包括利用Watch实现乐观锁和使用setnx防止库存超卖。乐观锁通过Watch命令监控键值变化,在事务中执行修改,若键值被改变则事务失败。Java代码示例展示了具体实现。setnx命令用于库存操作,确保无超卖,通过设置锁并检查库存来更新。文章还讨论了分布式锁存在的问题,如客户端阻塞、时钟漂移和单点故障,并提出了RedLock算法来提高可靠性。Redisson作为生产环境的分布式锁实现,提供了可重入锁、读写锁等高级功能。最后,文章对比了Redis、Zookeeper和etcd的分布式锁特性。
118 16
探秘Redis分布式锁:实战与注意事项
|
12天前
|
NoSQL Java 大数据
介绍redis分布式锁
分布式锁是解决多进程在分布式环境中争夺资源的问题,与本地锁相似但适用于不同进程。以Redis为例,通过`setIfAbsent`实现占锁,加锁同时设置过期时间避免死锁。然而,获取锁与设置过期时间非原子性可能导致并发问题,解决方案是使用`setIfAbsent`的超时参数。此外,释放锁前需验证归属,防止误删他人锁,可借助Lua脚本确保原子性。实际应用中还有锁续期、重试机制等复杂问题,现成解决方案如RedisLockRegistry和Redisson。
|
13天前
|
缓存 NoSQL Java
【亮剑】分布式锁是保证多服务实例同步的关键机制,常用于互斥访问共享资源、控制访问顺序和系统保护,如何使用注解来实现 Redis 分布式锁的功能?
【4月更文挑战第30天】分布式锁是保证多服务实例同步的关键机制,常用于互斥访问共享资源、控制访问顺序和系统保护。基于 Redis 的分布式锁利用 SETNX 或 SET 命令实现,并考虑自动过期、可重入及原子性以确保可靠性。在 Java Spring Boot 中,可通过 `@EnableCaching`、`@Cacheable` 和 `@CacheEvict` 注解轻松实现 Redis 分布式锁功能。
|
14天前
|
NoSQL Redis 微服务
分布式锁_redis实现
分布式锁_redis实现
|
1月前
|
存储 NoSQL 算法
09- Redis分片集群中数据是怎么存储和读取的 ?
Redis分片集群使用哈希槽分区算法,包含16384个槽(0-16383)。数据存储时,通过CRC16算法对key计算并模16383,确定槽位,进而分配至对应节点。读取时,根据槽位找到相应节点直接操作。
68 12
|
1月前
|
NoSQL Linux Redis
06- 你们使用Redis是单点还是集群 ? 哪种集群 ?
**Redis配置:** 使用哨兵集群,结构为1主2从,加上3个哨兵节点,总计分布在3台Linux服务器上,提供高可用性。
369 0
|
3天前
|
存储 监控 NoSQL
Redis哨兵&分片集群
Redis哨兵&分片集群
7 0
|
4天前
|
NoSQL 算法 Java
深入浅出Redis(八):Redis的集群模式
深入浅出Redis(八):Redis的集群模式
|
9天前
|
NoSQL Redis
透视Redis集群:心跳检测如何维护高可用性
Redis心跳检测保障集群可靠性,通过PING命令检测主从连接状态,预防数据丢失。当连接异常时,自动触发主从切换。此外,心跳检测辅助实现`min-slaves-to-write`和`min-slaves-max-lag`策略,避免不安全写操作。还有重传机制,确保命令无丢失,维持数据一致性。合理配置心跳检测,能有效防止数据问题,提升Redis集群的高可用性。关注“软件求生”获取更多Redis知识!
115 10
透视Redis集群:心跳检测如何维护高可用性