一、Redis set命令
set key value [ex seconds] [px milliseconds] [nx|xx]
ex: seconds:为键设置秒级过期时间。
px: milliseconds:为键设置毫秒级过期时间。
nx:键必须不存在,才可以设置成功,用于添加。
xx:与nx相反,键必须存在,才可以设置成功,用于更新。
二、Redis事务相关的命令
主要有multi,exec,watch,discard
将一组需要一起执行的命令放到multi和exec两个命令之间。multi命令代表事务开始,exec命令代表事务结束,它们之间的命令是原子顺序执行的,可以使用discard命令替换exec表示放弃事务。
在multi和exec之间的命令其实是被加入一个队列中的,直到执行exec命令才一次执行,所有我们可以看到multi和exec直接的名返回的是QUEUED。
WATCH命令可以监控一个或多个键,一旦其中有一个键被修改(或删除),之后的事务就不会执行。监控一直持续到EXEC命令
注意:Redis是不支持事务回滚的,也就是说如果multi和exec命令之间的命令只执行了一些,另一些因为错误终止,也不会回退到最初的状态。
三、Redis 与 Luau相关
使用eval执行脚本:
eval 脚本内容 key个数 key列表 参数列表
eval 'return "hello " .. KEYS[1] .. ARGV[1]' 1 redis world
注意Lua脚本的下标是从1开始的。
Lua使用redis.call函数实现对Redis的操作:
redis.call("set", "hello", "world")
redis.call("get", "hello")
eval 'return redis.call("get", KEYS[1])' 1 hello "world"
Lua除了使用redis.call函数,还可以使用redis.pcall函数实现对Redis的调用,redis.call和redis.pcall的不同在于,如果redis.call执行失败,那么脚本执行结束会直接返回错误,而redis.pcall会忽略错误继续执行脚本
使用evalsha执行脚本:
evalsha 脚本SHA1值 key个数 key列表 参数列表
evalsha 7413dc2440db1fea7c0a0bde841fa68eefaf149c 1 redis world
evalsha只是用脚本文件的sha1的值替换了eval的脚本内容而已,本质和eval是一样的。
四、通过set来实现获取锁
先看一下代码:
/**
* 尝试获取分布式锁
* @param lockKey 锁名称
* @param requestId 获取锁客户端标识 只有获取客户端能够释放锁
* @param expireTime 超时时间,避免死锁
* @return
*/
public static boolean getLock(String lockKey, String requestId, long expireTime) {
Jedis jedis = null;
try {
String result;
synchronized (setLock) {//避免多个线程同时执行,全部获取失败
jedis = pool.getResource();
if (jedis == null)
return false;
result = jedis.set(lockKey, requestId, NOT_EXIST, P_EXPIRE_TIME, expireTime);
}
if (SUCCESS.equals(result)) {
return true;
}
}catch (Exception e){
e.printStackTrace();
}finally {
jedis.close();
}
return false;
}
有4个要注意的地方:
- 使用set key value [ex | px] nx这个命令保证原子性和超时时间,避免死锁
- 使用requestId来标志加锁的客户端,保证释放的时候是同一个客户端
- 把获取jedis连接的代码放在同步块中,避免多个线程阻塞连接耗尽的问题
- 注意jedis不是线程安全的
五、使用事务相关命令来释放锁
public static void releaseLock(String lockKey, String requestId) {
Jedis jedis = null;
try {
jedis = pool.getResource();
if(jedis == null)
return;
jedis.watch(lockKey);//避免因为锁超时,其他客户端重新获取到锁情况的误释放锁
final String rid = jedis.get(lockKey);
final Transaction transaction = jedis.multi();
if (requestId.equals(rid)) {
transaction.del(lockKey);
}
transaction.exec();//不要放在if里面避免共享连接时候watch没有释放
}catch (Exception e){
e.printStackTrace();
}finally {
jedis.close();
}
}
- 使用watch命令,避免get命令之后lockKey超时其他客户端设置了新值情况的误释放锁
- get不要放在multi中,因为multi是队列返回的是queue,exec才执行
- 虽然只有一个del命令还是使用multi,exec吧,watch命令必须exec来释放
六、使用Lua脚本来释放锁
public static void releaseLockByLua(String lockKey, String requestId) {
Jedis jedis = null;
try {
jedis = pool.getResource();
if (jedis == null)
return;
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
}finally {
jedis.close();
}
}
好像使用Lua脚本比较简洁,没有什么可以说的。
七、使用Redisson锁
public static void redissonCountPlusPlus(){
RLock lock = redisson.getLock(COUNT_LOCK);
boolean isLock = false;
do {
try {
isLock = lock.tryLock(2,10,TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
}while (!isLock);
for(int i=0;i<100;i++)
count++;
lock.unlock();
}
使用Redison封装的锁来实现感觉比较简单,和JDK只带的锁风格一致。想上面的:
lock.tryLock(2,10,TimeUnit.SECONDS);
就是尝试获取锁,最多等2秒,10秒之后自动释放锁。
还可以直接使用
lock.lock()
这个会阻塞,这个有可能永远挂起的风险
后面附录完整的代码,有一个简单的本地测试,不是分布式的,可以参考一下。
八、附录
链接
完整代码
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import redis.clients.jedis.Transaction;
import java.util.Collections;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public final class RedisDislock {
private static final String SUCCESS = "OK";
private static final String NOT_EXIST = "NX";
private static final String P_EXPIRE_TIME = "PX";
private static JedisPool pool = new JedisPool();
private static RedissonClient redisson;
private static final Object setLock = new Object();
private static int count = 0;
public static final String COUNT_LOCK = "COUNT_LOCK";
static {
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(200);
config.setMaxIdle(100);
config.setMaxWaitMillis(20000);
config.setTestOnBorrow(false);
config.setTestOnReturn(false);
pool = new JedisPool(config,"localhost");
Config rconfig = new Config();
rconfig.useSingleServer().setAddress("127.0.0.1:6379");
redisson = Redisson.create(rconfig);
}
/**
* 尝试获取分布式锁
* @param lockKey 锁名称
* @param requestId 获取锁客户端标识 只有获取客户端能够释放锁
* @param expireTime 超时时间,避免死锁
* @return
*/
public static boolean getLock(String lockKey, String requestId, long expireTime) {
Jedis jedis = null;
try {
String result;
synchronized (setLock) {//避免多个线程同时执行,全部获取失败
jedis = pool.getResource();
if (jedis == null)
return false;
result = jedis.set(lockKey, requestId, NOT_EXIST, P_EXPIRE_TIME, expireTime);
}
if (SUCCESS.equals(result)) {
return true;
}
}catch (Exception e){
e.printStackTrace();
}finally {
jedis.close();
}
return false;
}
public static void releaseLock(String lockKey, String requestId) {
Jedis jedis = null;
try {
jedis = pool.getResource();
if(jedis == null)
return;
jedis.watch(lockKey);//避免因为锁超时,其他客户端重新获取到锁情况的误释放锁
final String rid = jedis.get(lockKey);
final Transaction transaction = jedis.multi();
if (requestId.equals(rid)) {
transaction.del(lockKey);
}
transaction.exec();//不要放在if里面避免共享连接时候watch没有释放
}catch (Exception e){
e.printStackTrace();
}finally {
jedis.close();
}
}
public static void releaseLockByLua(String lockKey, String requestId) {
Jedis jedis = null;
try {
jedis = pool.getResource();
if (jedis == null)
return;
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
}finally {
jedis.close();
}
}
public static void main(String[] args) {
final ExecutorService es = Executors.newCachedThreadPool();
for(int i=0;i<100;i++){
es.submit(new Runnable() {
@Override
public void run() {
countPlusPlus();
// redissonCountPlusPlus();
}
});
}
es.shutdown();
while (true){
if(es.isTerminated()){
System.out.println(count);
break;
}
}
System.out.println("done");
}
public static void countPlusPlus() {
String uuid = UUID.randomUUID().toString().replaceAll("-", "");
boolean lock;
do{
lock = getLock(COUNT_LOCK, uuid, 1000);
}while (!lock);
for(int i=0;i<100;i++)
count++;
// releaseLock(COUNT_LOCK, uuid);
releaseLockByLua(COUNT_LOCK, uuid);
}
public static void redissonCountPlusPlus(){
RLock lock = redisson.getLock(COUNT_LOCK);
boolean isLock = false;
do {
try {
isLock = lock.tryLock(2,10, TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
}while (!isLock);
for(int i=0;i<100;i++)
count++;
lock.unlock();
}
}
部分pom
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring.version>4.3.6.RELEASE</spring.version>
</properties>
<dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.11</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.1.2</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.1.2</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>3.9</version>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.3.2</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>21.0</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml-schemas</artifactId>
<version>3.9</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>3.9</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.6.1</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-mapper-asl</artifactId>
<version>1.9.13</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.40</version>
</dependency>
<dependency>
<groupId>jstl</groupId>
<artifactId>jstl</artifactId>
<version>1.1.2</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.7.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.7.0</version>
</dependency>
<dependency>
<groupId>taglibs</groupId>
<artifactId>standard</artifactId>
<version>1.1.2</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.2.8</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.2.2</version>
</dependency>
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>3.7.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.35</version>
<type>jar</type>
<scope>compile</scope>
</dependency>
<!-- COMMONS BEGIN -->
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>commons-pool</groupId>
<artifactId>commons-pool</artifactId>
<version>1.6</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.1</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
<!--test-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>3.2.13.RELEASE</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
</dependencies>