1. 为什么要有锁
在说锁之前,需要先说一下进程和线程的资源问题
当我们写完程序后,程序会在一个进程的线程里面工作,这个线程称为主线程
当我们在程序中手动创建一个子线程后,会和主线程“并行”,当我们同时有一个公共资源
比如在主线程里面的全局变量,这个时候主线程和子线程都会操作这个全局变量,问题来了
怎么才能保证每个线程读到的和修改的全局变量是一定的,假设主线程在修改变量期间,子线程已经修改好了,这个时候会不会出现程序错误
这种情况就叫做资源竞争,如果系统特别依赖这个全局变量,这种资源竞争带来的危害是很大的
所以,引入了锁,当一个线程操作的时候,先拿到锁,这个时候其他线程拿不到锁,只有拿到锁的线程才能对资源进行操作
2. 锁的分类
先说一下乐观锁和悲观锁
这不是具体的某一种锁,是一种锁的概念
悲观锁 -> 就是认为每一个操作都是大概率修改(写)操作,悲观认为,不加锁就出事,比如:行锁、表锁、读写锁、互斥锁
乐观锁 -> 就是认为每一个操作都是大大概率读取的操作,乐观认为,不用经常加锁,获取数据的时候不加锁,修改数据的时候,判断是否与原始数据一致来决定是否加锁
一般常用的锁就互斥锁和读写锁
【互斥锁】
简简单单,一个锁一把钥匙,只有一个人能拿到钥匙,其他人要使用这个钥匙,就要等拿到钥匙的人归还钥匙,也就是释放锁
【读写锁】
很好理解,一个门,门上有窗户,一个人拿到钥匙进去了,但是这个时候的钥匙是有类型的,分为读和写,读有很多把,写只有一把
当是读的时候,后边的人如果拿的是读,那么可以一起看;如果拿的是写,那么就要等等了(阻塞)
当是写的时候,后边的人不管拿什么,都需要等等(阻塞)
总结就是:
当前和新锁类型相同,新锁阻塞等待
当前和新锁类型不相同,读锁不阻塞,写锁阻塞
【公平锁/非公平锁】
公平锁:是指多个线程按照申请锁的顺序来获取锁。
非公平锁:是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁(允许“插队”的情况存在)。
【自旋锁】
自旋锁的理念是如果线程现在拿不到锁,并不直接陷入阻塞或者释放 CPU 资源,而是采用循环的方式去尝试获取锁,这个循环过程被形象地比喻为“自旋”,就像是线程在“自我旋转”,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗 CPU
3. 什么是分布式锁
传统项目,基本上都是一个巨石架构,那么在一台物理机或者云服务器上面,一个项目会有一个或多个进程,那么对于本地资源,比如文件,多进程操作这个文件,就需要系统级别的上锁
如果是一个进程里面的共享资源,比如一个全局变量,也就通过代码内的锁进行上锁操作
目前微服务,分布式计算等盛行,所以一个项目可能会在很多云服务器或容器(docker)上,每个进程都是系统级别的隔离,很多时候资源都是在其他机子上,这个时候如果很多进程需要更改资源,就需要涉及到锁
但是,这个锁在每个进程的代码上是无法实现的,需要借助一个第三方的服务,比如redis,ETCD等,提供一个锁的服务,每个进程就可以通过获取这个锁,从而获取对资源的操作权
这个锁,就是分布式锁了
4. 分布式锁实现的逻辑和js代码
我们先来看下我们要实现的逻辑
下面,就简单使用redis实现一个简单的锁,这个锁不严谨,还有许多问题,这里只是作为演示
- locker.js
- client-1.js
- client-2.js
5. golang和nodejs一些实现的redis分布式锁的库
- golang
- nodejs
6. 关于redis用于分布式锁的安全性的解读
有关redis实现分布式锁,有一些安全性的讨论,大家可以看这篇文章