实现分布式锁的解决方案 1

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: 实现分布式锁的解决方案

1. 分布式锁

1.1 什么是分布式锁

在我们进行单机应用开发涉及并发同步的时候,我们往往采用synchronized或者ReentrantLock的方式来解决多线程间的代码同步问题。但是当我们的应用是在分布式集群工作的情况下,那么就需要一种更加高级的锁机制,来处理种跨机器的进程之间的数据同步问题,这就是分布式锁。


分布式锁,是控制分布式系统之间同步访问共享资源的一种方式。在分布式系统中,常常需要协调他们的动作。如果不同的系统或是同一个系统的不同主机之间共享了一个或一组资源,那么访问这些资源的时候,往往需要互斥来防止彼此干扰来保证一致性,在这种情况下,便需要使用到分布式锁。


分布式锁可以理解为:控制分布式系统有序的去对共享资源进行操作,通过互斥来保证数据的一致性。


可能有同学会问,使用我们以前学习的Java中的锁机制,例如synchronized、ReentrantLock不就能解决问题了吗?为什么还要使用分布式锁?


对于简单的单体项目,即运行时程序在同一个Java虚拟机中,此时使用上面的Java的锁机制确实可以解决多线程并发问题。例如下面程序代码:

public class LockTest implements Runnable {
    public synchronized void get() {
        System.out.println("1 线程 -->" + Thread.currentThread().getName());
        System.out.println("2 线程 -->" + Thread.currentThread().getName());
        System.out.println("3 线程 -->" + Thread.currentThread().getName());
    }
    public void run() {
        get();
    }
    public static void main(String[] args) {
        LockTest test = new LockTest();
        for (int i = 0; i < 10; i++) {
            new Thread(test, "线程-" + i).start();
        }
    }
}

运行结果如下:


1 线程 -->线程-0 2 线程 -->线程-0 3 线程 -->线程-0 1 线程 -->线程-2 2 线程 -->线程-2 3 线程 -->线程-2 1 线程 -->线程-1 2 线程 -->线程-1 3 线程 -->线程-1 1 线程 -->线程-3 2 线程 -->线程-3 3 线程 -->线程-3 1 线程 -->线程-4 2 线程 -->线程-4 3 线程 -->线程-4但是在分布式环境中,程序是集群方式部署,如下图:

上面的集群部署方式依然会产生线程并发问题,因为synchronized、ReentrantLock只是jvm级别的加锁,没有办法控制其他jvm。也就是上面两个tomcat实例还是可以出现并发执行的情况。要解决分布式环境下的并发问题,则必须使用分布式锁。


分布式锁的实现方式有多种,例如:数据库实现方式、ZooKeeper实现方式、Redis实现方式等。


1.2 为什么要使用分布式锁

为了能够说明分布式锁的重要性,下面通过一个电商项目中减库存的案例来演示如果没有使用分布式锁会出现什么问题。代码如下:


第一步:导入坐标

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.5.RELEASE</version>
        <relativePath/>
    </parent>
    <groupId>com.itheima</groupId>
    <artifactId>lock-test</artifactId>
    <version>1.0-SNAPSHOT</version>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--集成redis-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
            <version>1.4.1.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.3</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
        </dependency>
    </dependencies>
</project>

第二步:配置application.yml文件

server:
  port: 8080
spring:
  redis:
    host: 68.79.63.42
    port: 26379
    password: itheima123

第三步:编写Controller

package com.itheima.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class StockController {
    @Autowired
    private StringRedisTemplate redisTemplate;
    @GetMapping("/stock")
    public String stock(){
        int stock = Integer.parseInt(redisTemplate.opsForValue().get("stock"));
        if(stock > 0){
            stock --;
            redisTemplate.opsForValue().set("stock",stock+"");
            System.out.println("库存扣减成功,剩余库存:" + stock);
        }else {
            System.out.println("库存不足!!!");
        }
        return "OK";
    }
}

第四步:编写启动类

@SpringBootApplication
public class MyApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class,args);
    }
}

第五步:设置redis

测试方式:使用jmeter进行压力测试,如下:

注:Apache JMeter是Apache组织开发的基于Java的压力测试工具。用于对软件做压力测试,它最初被设计用于Web应用测试,但后来扩展到其他测试领域。

查看控制台输出,发现已经出现了线程并发问题,如下:

由于当前程序是部署在一个Tomcat中的,即程序运行在一个jvm中,此时可以对减库存的代码进行同步处理,如下:

再次进行测试(注意恢复redis中的数据),此时已经没有线程并发问题,控制台输出如下:

这说明如果程序运行在一个jvm中,使用synchronized即可解决线程并发问题。

下面将程序进行集群部署(如下图所示),并通过Nginx进行负载,再进行测试。

操作过程:

第一步:配置Nginx

    upstream upstream_name{
        server 127.0.0.1:8001;
        server 127.0.0.1:8002;
    }
    server {
        listen       8080;
        server_name  localhost;
        location / {
            proxy_pass http://upstream_name;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }
    }

第二步:修改application.yml中端口号改为8001和8002并分别启动程序

第三步:使用jemter再次测试,可以看到又出现了并发问题

1.3 分布式锁应具有的特性

  • 在分布式系统环境下,一个方法在同一时间只能被一个机器的一个线程执行
  • 高可用的获取锁与释放锁
  • 高性能的获取锁与释放锁
  • 具备可重入特性
  • 具备锁失效机制,防止死锁
  • 具备非阻塞锁特性,即没有获取到锁将直接返回获取锁失败
相关实践学习
基于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
目录
相关文章
|
4月前
|
存储 SQL 微服务
常用的分布式事务解决方案(三)
常用的分布式事务解决方案(三)
|
4月前
|
关系型数据库 MySQL
常见分布式事务的解决方案(一)
常见分布式事务的解决方案(一)
|
2月前
|
缓存 NoSQL PHP
Redis作为PHP缓存解决方案的优势、实现方式及注意事项。Redis凭借其高性能、丰富的数据结构、数据持久化和分布式支持等特点,在提升应用响应速度和处理能力方面表现突出
本文深入探讨了Redis作为PHP缓存解决方案的优势、实现方式及注意事项。Redis凭借其高性能、丰富的数据结构、数据持久化和分布式支持等特点,在提升应用响应速度和处理能力方面表现突出。文章还介绍了Redis在页面缓存、数据缓存和会话缓存等应用场景中的使用,并强调了缓存数据一致性、过期时间设置、容量控制和安全问题的重要性。
46 5
|
4月前
|
消息中间件 中间件 关系型数据库
常用的分布式事务解决方案(四)
常用的分布式事务解决方案(四)
|
4月前
常用的分布式事务解决方案(二)
常用的分布式事务解决方案(二)
|
5月前
|
存储 NoSQL Java
一天五道Java面试题----第十一天(分布式架构下,Session共享有什么方案--------->分布式事务解决方案)
这篇文章是关于Java面试中的分布式架构问题的笔记,包括分布式架构下的Session共享方案、RPC和RMI的理解、分布式ID生成方案、分布式锁解决方案以及分布式事务解决方案。
一天五道Java面试题----第十一天(分布式架构下,Session共享有什么方案--------->分布式事务解决方案)
|
5月前
|
机器学习/深度学习 分布式计算 Cloud Native
云原生架构下的高性能计算解决方案:利用分布式计算资源加速机器学习训练
【8月更文第19天】随着大数据和人工智能技术的发展,机器学习模型的训练数据量和复杂度都在迅速增长。传统的单机训练方式已经无法满足日益增长的计算需求。云原生架构为高性能计算提供了新的可能性,通过利用分布式计算资源,可以在短时间内完成大规模数据集的训练任务。本文将探讨如何在云原生环境下搭建高性能计算平台,并展示如何使用 PyTorch 和 TensorFlow 这样的流行框架进行分布式训练。
149 2
|
5月前
|
存储 监控 数据可视化
性能监控之JMeter分布式压测轻量日志解决方案
【8月更文挑战第11天】性能监控之JMeter分布式压测轻量日志解决方案
112 0
性能监控之JMeter分布式压测轻量日志解决方案
|
6月前
|
存储 NoSQL 算法
实现分布式锁的Java解决方案
实现分布式锁的Java解决方案
|
6月前
|
关系型数据库 分布式数据库 数据库
PolarDB,阿里云的开源分布式数据库,与微服务相结合,提供灵活扩展和高效管理解决方案。
【7月更文挑战第3天】PolarDB,阿里云的开源分布式数据库,与微服务相结合,提供灵活扩展和高效管理解决方案。通过数据分片和水平扩展支持微服务弹性,保证高可用性,且兼容MySQL协议,简化集成。示例展示了如何使用Spring Boot配置PolarDB,实现服务动态扩展。PolarDB缓解了微服务数据库挑战,加速了开发部署,为云原生应用奠定基础。
342 3