导读

  大家都听过1万小时定律,可事实真的是这样吗?做了1万小时的CRUD,不还只会CRUD吗,这年头适当的更新自身下技术栈出门和别人聊天吹牛的时候,都没拿不出手,(⊙o⊙)…Redis没入门的童鞋不推荐往下看先去脑补下Redis入门(点我直达),SpringBoot整合Redis的教程(点我直达),这篇不会讲浅的知识点!!!!

面试专题

什么是分布式锁?

  首先,为了确保分布式锁可用,至少要满足以下三个条件

  1. 互斥性。在任意时刻,只有一个客户端能持有锁
  2. 不会发生死锁。即便有一个客户端在持有锁的期间奔溃而没有主动解锁,也能保证后续其他客户端能加锁
  3. 解铃还须系铃人。加锁解锁必须是同一个客户端客户端自己不能把别人加的锁给解了

实现分布式锁方式?

  两种实现,下面都会有讲到

  1. 采用lua脚本操作分布式锁
  2. 采用setnx、setex命令连用的方式实现分布式锁

分布式锁的场景

什么是分布式锁?

  • 分布式锁控制分布式系统不同系统之间共同访问共享资源的一种实现
  • 如果不同的系统或同一个系统的不同主机之间共享了某个资源时,往往通过互斥来防止彼此干扰

为什么要有分布式锁?

  可以保证分布式部署的应用集群中,一个方法在同一操作只能一台机器上的一个线程执行

设计要求

  1. 重入锁(避免死锁)
  2. 获取锁和释放高可用
  3. 获取锁和释放高性能

实现方案

  1. 获取锁,使用setnx():SETNX key val:当且仅当key不存在时,set一个key为val的字符串,返回1
  2. 若key存在,则什么都不做,返回【0】加锁,锁的value值为当前占有锁服务器内网IP编号拼接任务标识
  3. 释放锁候进行判断。并使用expire命令为锁添加一个超时时间,超过该时间则自动释放锁
  4. 返回1成功获取锁还设置一个获取超时时间,若超过这个时间放弃获取锁setex(key,value,expire)过期以秒为单位
  5. 释放锁的时候,判断是不是该(即value为当前服务器内网IP编号拼接任务标识),若是该锁,则执行delete进行锁释放

Redis分布式锁的实现

创建一个SpringBoot工程

网址:https://start.spring.io/

步骤

  1、启动类上加上注解@EnableScheduling

  2、执行方法上加上注解@Scheduled

打包并上传至Linux服务器中启动

  准备3台Linux服务器,并将打好的jar包,上传至3台服务器中,然后启动

nohub之持久化启动方式

nohup java -jar jar名称 &

查看集群里面所有集群是否启动成功

1、先安装lsof:yum install lsof
2、验证:lsof -i:8080

TCP三次握手

查看本机TCP连接状态

 netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'

为什么要三次握手?

  主要是为了防止已失效的连接请求报文段突然又传到了B,因而报文错乱问题,假定A发出的第一个连接请求报文段并没有丢失,而是在某些网络节点长时间滞留,一直延迟到连接释放以后的某个时间才到达B,本来这是一个早已经失效的报文段。但B收到失效的连接请求报文段后,就误认为是A又发出一个新的连接请求,于是就向A发出确认报文段,同意建立连接。

  假定不采用三次握手,那么只要B发出确认新的连接就建立,这样一直等待A发来数据,B的许多资源就这样白白浪费了。

图解

有3次握手了,为啥还有4次挥手?

第一次挥手:主动关闭方发送一个FIN,用来关闭主动方到被动关闭方的数据传送,也就是主动关闭方告诉被动关闭方:我已经不 会再给你发数据了(当然,在fin包之前发送出去的数据,如果没有收到对应的ack确认报文,主动关闭方依然会重发这些数据),但是,此时主动关闭方还可 以接受数据。
第二次挥手:被动关闭方收到FIN包后,发送一个ACK给对方,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号)。
第三次挥手:被动关闭方发送一个FIN,用来关闭被动关闭方到主动关闭方的数据传送,也就是告诉主动关闭方,我的数据也发送完了,不会再给你发数据了。
第四次挥手:主动关闭方收到FIN后,发送一个ACK给被动关闭方,确认序号为收到序号+1,至此,完成四次挥手。

作用

  确保数据能够完整传输

Redis分布式锁实现源码讲解

图文讲解

步骤

  1. 分布式锁满足两个条件,一个是加有效时间,一个是高性能解锁
  2. 采用redis命令setnx (set if not exist)、setex(set expire value)实现
  3. 解锁流程不能遗漏否则导致任务执行一次就永不过期
  4. 加锁代码任务逻辑放到try catch代码块解锁流程finally代码块

项目结构

pom.xml

<?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 https://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.3.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.cyb</groupId>
<artifactId>yb-mobile-redis</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>yb-mobile-redis</name>
<description>Demo project for Spring Boot</description> <properties>
<java.version>1.8</java.version>
</properties> <dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies> <build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build> </project>

application.properties

spring.redis.database=0
spring.redis.host=192.168.199.142
spring.redis.port=6379
spring.redis.password=12345678
server.port=9001

RedisService.java

package com.cyb.ybmobileredis.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.*;
import org.springframework.stereotype.Service; import java.io.Serializable;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit; /**
* @ClassName:RedisService
* @Description:TODO
* @Author:chenyb
* @Date:2020/8/16 5:39 下午
* @Versiion:1.0
*/
@Service
public class RedisService {
@Autowired
private RedisTemplate redisTemplate; private static double size = Math.pow(2, 32); /**
* 写入缓存
*
* @param key
* @param offset 位 8Bit=1Byte
* @return
*/
public boolean setBit(String key, long offset, boolean isShow) {
boolean result = false;
try {
ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
operations.setBit(key, offset, isShow);
result = true;
} catch (Exception e) {
e.printStackTrace();
}
return result;
} /**
* 写入缓存
*
* @param key
* @param offset
* @return
*/
public boolean getBit(String key, long offset) {
boolean result = false;
try {
ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
result = operations.getBit(key, offset);
} catch (Exception e) {
e.printStackTrace();
}
return result;
} /**
* 写入缓存
*
* @param key
* @param value
* @return
*/
public boolean set(final String key, Object value) {
boolean result = false;
try {
ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
operations.set(key, value);
result = true;
} catch (Exception e) {
e.printStackTrace();
}
return result;
} /**
* 写入缓存设置时效时间
*
* @param key
* @param value
* @return
*/
public boolean set(final String key, Object value, Long expireTime) {
boolean result = false;
try {
ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
operations.set(key, value);
redisTemplate.expire(key, expireTime, TimeUnit.SECONDS);
result = true;
} catch (Exception e) {
e.printStackTrace();
}
return result;
} /**
* 批量删除对应的value
*
* @param keys
*/
public void remove(final String... keys) {
for (String key : keys) {
remove(key);
}
} /**
* 删除对应的value
*
* @param key
*/
public void remove(final String key) {
if (exists(key)) {
redisTemplate.delete(key);
}
} /**
* 判断缓存中是否有对应的value
*
* @param key
* @return
*/
public boolean exists(final String key) {
return redisTemplate.hasKey(key);
} /**
* 读取缓存
*
* @param key
* @return
*/
public Object get(final String key) {
Object result = null;
ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
result = operations.get(key);
return result;
} /**
* 哈希 添加
*
* @param key
* @param hashKey
* @param value
*/
public void hmSet(String key, Object hashKey, Object value) {
HashOperations<String, Object, Object> hash = redisTemplate.opsForHash();
hash.put(key, hashKey, value);
} /**
* 哈希获取数据
*
* @param key
* @param hashKey
* @return
*/
public Object hmGet(String key, Object hashKey) {
HashOperations<String, Object, Object> hash = redisTemplate.opsForHash();
return hash.get(key, hashKey);
} /**
* 列表添加
*
* @param k
* @param v
*/
public void lPush(String k, Object v) {
ListOperations<String, Object> list = redisTemplate.opsForList();
list.rightPush(k, v);
} /**
* 列表获取
*
* @param k
* @param l
* @param l1
* @return
*/
public List<Object> lRange(String k, long l, long l1) {
ListOperations<String, Object> list = redisTemplate.opsForList();
return list.range(k, l, l1);
} /**
* 集合添加
*
* @param key
* @param value
*/
public void add(String key, Object value) {
SetOperations<String, Object> set = redisTemplate.opsForSet();
set.add(key, value);
} /**
* 集合获取
*
* @param key
* @return
*/
public Set<Object> setMembers(String key) {
SetOperations<String, Object> set = redisTemplate.opsForSet();
return set.members(key);
} /**
* 有序集合添加
*
* @param key
* @param value
* @param scoure
*/
public void zAdd(String key, Object value, double scoure) {
ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();
zset.add(key, value, scoure);
} /**
* 有序集合获取
*
* @param key
* @param scoure
* @param scoure1
* @return
*/
public Set<Object> rangeByScore(String key, double scoure, double scoure1) {
ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();
redisTemplate.opsForValue();
return zset.rangeByScore(key, scoure, scoure1);
} //第一次加载的时候将数据加载到redis中
public void saveDataToRedis(String name) {
double index = Math.abs(name.hashCode() % size);
long indexLong = new Double(index).longValue();
boolean availableUsers = setBit("availableUsers", indexLong, true);
} //第一次加载的时候将数据加载到redis中
public boolean getDataToRedis(String name) { double index = Math.abs(name.hashCode() % size);
long indexLong = new Double(index).longValue();
return getBit("availableUsers", indexLong);
} /**
* 有序集合获取排名
*
* @param key 集合名称
* @param value 值
*/
public Long zRank(String key, Object value) {
ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();
return zset.rank(key,value);
} /**
* 有序集合获取排名
*
* @param key
*/
public Set<ZSetOperations.TypedTuple<Object>> zRankWithScore(String key, long start,long end) {
ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();
Set<ZSetOperations.TypedTuple<Object>> ret = zset.rangeWithScores(key,start,end);
return ret;
} /**
* 有序集合添加
*
* @param key
* @param value
*/
public Double zSetScore(String key, Object value) {
ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();
return zset.score(key,value);
} /**
* 有序集合添加分数
*
* @param key
* @param value
* @param scoure
*/
public void incrementScore(String key, Object value, double scoure) {
ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();
zset.incrementScore(key, value, scoure);
} /**
* 有序集合获取排名
*
* @param key
*/
public Set<ZSetOperations.TypedTuple<Object>> reverseZRankWithScore(String key, long start,long end) {
ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();
Set<ZSetOperations.TypedTuple<Object>> ret = zset.reverseRangeByScoreWithScores(key,start,end);
return ret;
} /**
* 有序集合获取排名
*
* @param key
*/
public Set<ZSetOperations.TypedTuple<Object>> reverseZRankWithRank(String key, long start, long end) {
ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();
Set<ZSetOperations.TypedTuple<Object>> ret = zset.reverseRangeWithScores(key, start, end);
return ret;
}
}

LockNxExJob.java

package com.cyb.ybmobileredis.schedule;

import com.cyb.ybmobileredis.service.RedisService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service; import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.util.Enumeration; /**
* @ClassName:LockNxExJob
* @Description:分布式获取锁和释放锁
* @Author:chenyb
* @Date:2020/8/16 5:44 下午
* @Versiion:1.0
*/
@Service
public class LockNxExJob {
private static final Logger logger = LoggerFactory.getLogger(LockNxExJob.class);
@Autowired
private RedisService redisService;
@Autowired
private RedisTemplate redisTemplate;
private static String LOCK_PREFIX = "prefix_";
@Scheduled(fixedRate = 8000)
public void lockJob() {
String lock = LOCK_PREFIX + "LockNxExJob";
boolean nxRet=false;
try{
//redistemplate setnx操作
nxRet = redisTemplate.opsForValue().setIfAbsent(lock,getHostIp());
Object lockValue = redisService.get(lock);
System.out.println(lockValue);
//获取锁失败
if(!nxRet){
String value = (String)redisService.get(lock);
//打印当前占用锁的服务器IP
logger.info(System.currentTimeMillis()+" get lock fail,lock belong to:{}",value);
return;
}else{
redisTemplate.opsForValue().set(lock,getHostIp(),3600); //获取锁成功
logger.info(System.currentTimeMillis()+" start lock lockNxExJob success");
Thread.sleep(4000);
}
}catch (Exception e){
logger.error("lock error",e); }finally {
if (nxRet){
System.out.println("释放锁成功");
redisService.remove(lock);
}
}
}
/**
* 获取本机内网IP地址方法
* @return
*/
private static String getHostIp(){
try{
Enumeration<NetworkInterface> allNetInterfaces = NetworkInterface.getNetworkInterfaces();
while (allNetInterfaces.hasMoreElements()){
NetworkInterface netInterface = (NetworkInterface) allNetInterfaces.nextElement();
Enumeration<InetAddress> addresses = netInterface.getInetAddresses();
while (addresses.hasMoreElements()){
InetAddress ip = (InetAddress) addresses.nextElement();
if (ip != null
&& ip instanceof Inet4Address
&& !ip.isLoopbackAddress() //loopback地址即本机地址,IPv4的loopback范围是127.0.0.0 ~ 127.255.255.255
&& ip.getHostAddress().indexOf(":")==-1){
return ip.getHostAddress();
}
}
}
}catch(Exception e){
e.printStackTrace();
}
return null;
}
}

验证

  打个jar运行在Linux上,一个在本地运行,一个获取锁成功一个获取锁失败

Redis分布式锁可能出现的问题

  上面我们已经使用代码实现分布式锁的功能,同一时刻只能一把锁获取成功。从上图可以看出,极端情况下第一个Server获取锁成功后,服务或者Redis宕机了,会导致Redis锁无法释放的问题,其他Server一直获取锁失败

模拟server获取锁宕机

  先把项目跑起来获取锁之后,立马kill -9 进程id杀掉当前进程,然后在运行项目,控制台就会一直提示,获取锁失败了。

解决方案(重点)

  1. 一次性执行一条命令就不会出现该情况发生,采用Lua脚本
  2. Redis从2.6之后,支持setnx、setex连用

lua脚本

  1. 在redource目录下新增一个后缀名为.lua结尾的文件
  2. 编写lua脚本
  3. 传入lua脚本的key和arg
  4. 调用redisTemplate.execute方法执行脚本

编写lua脚本

local lockKey = KEYS[1]
local lockValue = KEYS[2]
-- setnx info
local result_1 = redis.call('SETNX',lockKey,lockValue)
if result_1 == true
then
local result_2 = redis.call('SETEX',lockKey,3600,lockValue)
return result_1
else
return result_1
end

封装调用lua脚本方法

    @Autowired
private RedisTemplate redisTemplate;
private DefaultRedisScript<Boolean> lockScript; /**
* 获取lua结果
*
* @param key 键
* @param value 值
* @return
*/
public Boolean luaExpress(String key, String value) {
lockScript = new DefaultRedisScript<>();
lockScript.setScriptSource(
new ResourceScriptSource(new ClassPathResource("add.lua"))
);
//设置返回值
lockScript.setResultType(Boolean.class);
//封装参数
List<Object> keyList = new ArrayList<>();
keyList.add(key);
keyList.add(value);
Boolean result = (Boolean) redisTemplate.execute(lockScript, keyList);
return result;
}

改造之前的分布式锁方法

package com.cyb.ybmobileredis.schedule;

import com.cyb.ybmobileredis.service.RedisService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.scripting.support.ResourceScriptSource;
import org.springframework.stereotype.Service; import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List; /**
* @ClassName:LockNxExJob
* @Description:分布式获取锁和释放锁
* @Author:chenyb
* @Date:2020/8/16 5:44 下午
* @Versiion:1.0
*/
@Service
public class LockNxExJob {
private static final Logger logger = LoggerFactory.getLogger(LockNxExJob.class);
@Autowired
private RedisService redisService;
@Autowired
private RedisTemplate redisTemplate;
private static String LOCK_PREFIX = "prefix_";
private DefaultRedisScript<Boolean> lockScript;
//一般分布式锁
// @Scheduled(fixedRate = 8000)
// public void lockJob() {
// String lock = LOCK_PREFIX + "LockNxExJob";
// boolean nxRet = false;
// try {
// //redistemplate setnx操作
// nxRet = redisTemplate.opsForValue().setIfAbsent(lock, getHostIp());
// Object lockValue = redisService.get(lock);
// System.out.println(lockValue);
// //获取锁失败
// if (!nxRet) {
// String value = (String) redisService.get(lock);
// //打印当前占用锁的服务器IP
// logger.info(System.currentTimeMillis() + " get lock fail,lock belong to:{}", value);
// return;
// } else {
// redisTemplate.opsForValue().set(lock, getHostIp(), 3600000);
//
// //获取锁成功
// logger.info(System.currentTimeMillis() + " start lock lockNxExJob success");
// Thread.sleep(4000);
// }
// } catch (Exception e) {
// logger.error("lock error", e);
//
// } finally {
// if (nxRet) {
// System.out.println("释放锁成功");
// redisService.remove(lock);
// }
// }
// } /**
* lua脚本方式分布式锁
*/
@Scheduled(fixedRate = 8000)
public void luaLockJob() {
String lock = LOCK_PREFIX + "LockNxExJob";
boolean nxRet = false;
try {
//redistemplate setnx操作
nxRet = luaExpress(lock,getHostIp());
Object lockValue = redisService.get(lock);
System.out.println(lockValue);
//获取锁失败
if (!nxRet) {
String value = (String) redisService.get(lock);
//打印当前占用锁的服务器IP
logger.info(System.currentTimeMillis() + " lua get lock fail,lock belong to:{}", value);
return;
} else {
redisTemplate.opsForValue().set(lock, getHostIp(), 3600000); //获取锁成功
logger.info(System.currentTimeMillis() + " lua start lock lockNxExJob success");
Thread.sleep(4000);
}
} catch (Exception e) {
logger.error("lua lock error", e); } finally {
if (nxRet) {
System.out.println("lua 释放锁成功");
redisService.remove(lock);
}
}
}
/**
* 获取lua结果
*
* @param key 键
* @param value 值
* @return
*/
public Boolean luaExpress(String key, String value) {
lockScript = new DefaultRedisScript<>();
lockScript.setScriptSource(
new ResourceScriptSource(new ClassPathResource("add.lua"))
);
//设置返回值
lockScript.setResultType(Boolean.class);
//封装参数
List<Object> keyList = new ArrayList<>();
keyList.add(key);
keyList.add(value);
Boolean result = (Boolean) redisTemplate.execute(lockScript, keyList);
return result;
} /**
* 获取本机内网IP地址方法
*
* @return
*/
private static String getHostIp() {
try {
Enumeration<NetworkInterface> allNetInterfaces = NetworkInterface.getNetworkInterfaces();
while (allNetInterfaces.hasMoreElements()) {
NetworkInterface netInterface = (NetworkInterface) allNetInterfaces.nextElement();
Enumeration<InetAddress> addresses = netInterface.getInetAddresses();
while (addresses.hasMoreElements()) {
InetAddress ip = (InetAddress) addresses.nextElement();
if (ip != null
&& ip instanceof Inet4Address
&& !ip.isLoopbackAddress() //loopback地址即本机地址,IPv4的loopback范围是127.0.0.0 ~ 127.255.255.255
&& ip.getHostAddress().indexOf(":") == -1) {
return ip.getHostAddress();
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}

验证

补充解决Redis中的key乱码问题

  只需要添加RedisConfig.java配置文件即可

package com.cyb.ybmobileredis.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer; /**
* @ClassName:RedisConfig
* @Description:Redis配置类
* @Author:chenyb
* @Date:2020/8/16 11:48 下午
* @Versiion:1.0
*/
@Configuration //当前类为配置类
public class RedisConfig {
@Bean //redisTemplate注入到Spring容器
public RedisTemplate<String,String> redisTemplate(RedisConnectionFactory factory){
RedisTemplate<String,String> redisTemplate=new RedisTemplate<>();
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
redisTemplate.setConnectionFactory(factory);
//key序列化
redisTemplate.setKeySerializer(redisSerializer);
//value序列化
redisTemplate.setValueSerializer(redisSerializer);
//value hashmap序列化
redisTemplate.setHashKeySerializer(redisSerializer);
//key hashmap序列化
redisTemplate.setHashValueSerializer(redisSerializer);
return redisTemplate;
}
}

RedisConnection实现分布式锁

简介

  RedisConnection实现分布式锁的方式,采用redisTemplate操作redisConnection实现setnx和setex两个命令连用

代码实现

package com.cyb.ybmobileredis.schedule;

import com.cyb.ybmobileredis.service.RedisService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.scripting.support.ResourceScriptSource;
import org.springframework.stereotype.Service; import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List; /**
* @ClassName:LockNxExJob
* @Description:分布式获取锁和释放锁
* @Author:chenyb
* @Date:2020/8/16 5:44 下午
* @Versiion:1.0
*/
@Service
public class LockNxExJob {
private static final Logger logger = LoggerFactory.getLogger(LockNxExJob.class);
@Autowired
private RedisService redisService;
@Autowired
private RedisTemplate redisTemplate;
private static String LOCK_PREFIX = "prefix_";
private DefaultRedisScript<Boolean> lockScript;
//一般分布式锁
// @Scheduled(fixedRate = 8000)
// public void lockJob() {
// String lock = LOCK_PREFIX + "LockNxExJob";
// boolean nxRet = false;
// try {
// //redistemplate setnx操作
// nxRet = redisTemplate.opsForValue().setIfAbsent(lock, getHostIp());
// Object lockValue = redisService.get(lock);
// System.out.println(lockValue);
// //获取锁失败
// if (!nxRet) {
// String value = (String) redisService.get(lock);
// //打印当前占用锁的服务器IP
// logger.info(System.currentTimeMillis() + " get lock fail,lock belong to:{}", value);
// return;
// } else {
// redisTemplate.opsForValue().set(lock, getHostIp(), 3600000);
//
// //获取锁成功
// logger.info(System.currentTimeMillis() + " start lock lockNxExJob success");
// Thread.sleep(4000);
// }
// } catch (Exception e) {
// logger.error("lock error", e);
//
// } finally {
// if (nxRet) {
// System.out.println("释放锁成功");
// redisService.remove(lock);
// }
// }
// } /**
* lua脚本方式分布式锁
*/
// @Scheduled(fixedRate = 8000)
// public void luaLockJob() {
// String lock = LOCK_PREFIX + "LockNxExJob";
// boolean nxRet = false;
// try {
// //redistemplate setnx操作
// //nxRet = luaExpress(lock,getHostIp());
// nxRet = setLock(lock,600);
// Object lockValue = redisService.get(lock);
// System.out.println(lockValue);
// //获取锁失败
// if (!nxRet) {
// String value = (String) redisService.get(lock);
// //打印当前占用锁的服务器IP
// logger.info(System.currentTimeMillis() + " lua get lock fail,lock belong to:{}", value);
// return;
// } else {
// redisTemplate.opsForValue().set(lock, getHostIp(), 3600000);
//
// //获取锁成功
// logger.info(System.currentTimeMillis() + " lua start lock lockNxExJob success");
// Thread.sleep(4000);
// }
// } catch (Exception e) {
// logger.error("lua lock error", e);
//
// } finally {
// if (nxRet) {
// System.out.println("lua 释放锁成功");
// redisService.remove(lock);
// }
// }
// }
/**
* setnx和setex连用分布式锁
*/
@Scheduled(fixedRate = 8000)
public void setLockJob() {
String lock = LOCK_PREFIX + "LockNxExJob";
boolean nxRet = false;
try {
//redistemplate setnx操作
//nxRet = luaExpress(lock,getHostIp());
nxRet = setLock(lock,getHostIp(),3);
Object lockValue = redisService.get(lock);
System.out.println(lockValue);
//获取锁失败
if (!nxRet) {
String value = (String) redisService.get(lock);
//打印当前占用锁的服务器IP
logger.info(System.currentTimeMillis() + " setnx and setex get lock fail,lock belong to:{}", value);
return;
} else {
redisTemplate.opsForValue().set(lock, getHostIp(), 3600000); //获取锁成功
logger.info(System.currentTimeMillis() + " setnx and setex start lock lockNxExJob success");
Thread.sleep(4000);
}
} catch (Exception e) {
logger.error(" setnx and setex lock error", e); } finally {
if (nxRet) {
System.out.println(" setnx and setex 释放锁成功");
redisService.remove(lock);
}
}
} /**
* setnx和setex连用
* @param key 键
* @param value 值
* @param expire 超时时间
* @return
*/
public boolean setLock(String key,String value,long expire){
try{
Boolean result=(boolean)redisTemplate.execute(new RedisCallback<Boolean>() { @Override
public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
return connection.set(key.getBytes(),value.getBytes(),Expiration.seconds(expire),RedisStringCommands.SetOption.ifAbsent());
}
});
return result;
}catch (Exception e){
logger.error("set redis occured an exception",e);
}
return false;
}
/**
* 获取lua结果
*
* @param key 键
* @param value 值
* @return
*/
public Boolean luaExpress(String key, String value) {
lockScript = new DefaultRedisScript<>();
lockScript.setScriptSource(
new ResourceScriptSource(new ClassPathResource("add.lua"))
);
//设置返回值
lockScript.setResultType(Boolean.class);
//封装参数
List<Object> keyList = new ArrayList<>();
keyList.add(key);
keyList.add(value);
Boolean result = (Boolean) redisTemplate.execute(lockScript, keyList);
return result;
} /**
* 获取本机内网IP地址方法
*
* @return
*/
private static String getHostIp() {
try {
Enumeration<NetworkInterface> allNetInterfaces = NetworkInterface.getNetworkInterfaces();
while (allNetInterfaces.hasMoreElements()) {
NetworkInterface netInterface = (NetworkInterface) allNetInterfaces.nextElement();
Enumeration<InetAddress> addresses = netInterface.getInetAddresses();
while (addresses.hasMoreElements()) {
InetAddress ip = (InetAddress) addresses.nextElement();
if (ip != null
&& ip instanceof Inet4Address
&& !ip.isLoopbackAddress() //loopback地址即本机地址,IPv4的loopback范围是127.0.0.0 ~ 127.255.255.255
&& ip.getHostAddress().indexOf(":") == -1) {
return ip.getHostAddress();
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}

测试

分布式锁优化细节(重点)

  上面几个案例,已经实现了分布式锁的功能,但是极端情况下ServerA程序还没执行完ServerB程序执行完把锁释放掉了,就会造成A的锁释放掉了,这不是扯嘛,ServerA还没执行完,锁就被其他人释放了。解决方案:释放的时候,使用lua,通过get方法获取value,判断value是否等于本机ip,是自己的才能释放

package com.cyb.ybmobileredis.schedule;

import com.cyb.ybmobileredis.service.RedisService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.scripting.support.ResourceScriptSource;
import org.springframework.stereotype.Service; import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List; /**
* @ClassName:LockNxExJob
* @Description:分布式获取锁和释放锁
* @Author:chenyb
* @Date:2020/8/16 5:44 下午
* @Versiion:1.0
*/
@Service
public class LockNxExJob {
private static final Logger logger = LoggerFactory.getLogger(LockNxExJob.class);
@Autowired
private RedisService redisService;
@Autowired
private RedisTemplate redisTemplate;
private static String LOCK_PREFIX = "prefix_";
private DefaultRedisScript<Boolean> lockScript;
//一般分布式锁
// @Scheduled(fixedRate = 8000)
// public void lockJob() {
// String lock = LOCK_PREFIX + "LockNxExJob";
// boolean nxRet = false;
// try {
// //redistemplate setnx操作
// nxRet = redisTemplate.opsForValue().setIfAbsent(lock, getHostIp());
// Object lockValue = redisService.get(lock);
// System.out.println(lockValue);
// //获取锁失败
// if (!nxRet) {
// String value = (String) redisService.get(lock);
// //打印当前占用锁的服务器IP
// logger.info(System.currentTimeMillis() + " get lock fail,lock belong to:{}", value);
// return;
// } else {
// redisTemplate.opsForValue().set(lock, getHostIp(), 3600000);
//
// //获取锁成功
// logger.info(System.currentTimeMillis() + " start lock lockNxExJob success");
// Thread.sleep(4000);
// }
// } catch (Exception e) {
// logger.error("lock error", e);
//
// } finally {
// if (nxRet) {
// System.out.println("释放锁成功");
// redisService.remove(lock);
// }
// }
// } /**
* lua脚本方式分布式锁
*/
// @Scheduled(fixedRate = 8000)
// public void luaLockJob() {
// String lock = LOCK_PREFIX + "LockNxExJob";
// boolean nxRet = false;
// try {
// //redistemplate setnx操作
// //nxRet = luaExpress(lock,getHostIp());
// nxRet = setLock(lock,600);
// Object lockValue = redisService.get(lock);
// System.out.println(lockValue);
// //获取锁失败
// if (!nxRet) {
// String value = (String) redisService.get(lock);
// //打印当前占用锁的服务器IP
// logger.info(System.currentTimeMillis() + " lua get lock fail,lock belong to:{}", value);
// return;
// } else {
// redisTemplate.opsForValue().set(lock, getHostIp(), 3600000);
//
// //获取锁成功
// logger.info(System.currentTimeMillis() + " lua start lock lockNxExJob success");
// Thread.sleep(4000);
// }
// } catch (Exception e) {
// logger.error("lua lock error", e);
//
// } finally {
// if (nxRet) {
// System.out.println("lua 释放锁成功");
// redisService.remove(lock);
// }
// }
// } /**
* setnx和setex连用分布式锁
*/
@Scheduled(fixedRate = 8000)
public void setLockJob() {
String lock = LOCK_PREFIX + "LockNxExJob";
boolean nxRet = false;
try {
//redistemplate setnx操作
//nxRet = luaExpress(lock,getHostIp());
System.out.println("hostIp1="+getHostIp());
nxRet = setLock(lock, getHostIp(), 30);
Object lockValue = redisService.get(lock);
System.out.println(lockValue);
//获取锁失败
if (!nxRet) {
String value = (String) redisService.get(lock);
//打印当前占用锁的服务器IP
logger.info(System.currentTimeMillis() + " setnx and setex get lock fail,lock belong to:{}", value);
return;
} else {
//获取锁成功
logger.info(System.currentTimeMillis() + " setnx and setex start lock lockNxExJob success");
Thread.sleep(4000);
}
} catch (Exception e) {
logger.error(" setnx and setex lock error", e); } finally {
if (nxRet) {
System.out.println(" setnx and setex 释放锁成功");
//redisService.remove(lock);
//使用lua脚本释放锁
System.out.println("hostIp2="+getHostIp());
Boolean result = releaseLock(lock, getHostIp());
System.out.println("状态:"+result);
}
}
} /**
* 释放锁操作
*
* @param key 键
* @param value 值
* @return
*/
private boolean releaseLock(String key, String value) {
lockScript = new DefaultRedisScript<Boolean>();
lockScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("unlock.lua")));
lockScript.setResultType(Boolean.class);
//封装参数
List<Object> keyList = new ArrayList<>();
keyList.add(key);
keyList.add(value);
Boolean result = (Boolean) redisTemplate.execute(lockScript, keyList);
return result;
} /**
* setnx和setex连用
*
* @param key 键
* @param value 值
* @param expire 超时时间
* @return
*/
public boolean setLock(String key, String value, long expire) {
try {
Boolean result = (boolean) redisTemplate.execute(new RedisCallback<Boolean>() { @Override
public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
return connection.set(key.getBytes(), value.getBytes(), Expiration.seconds(expire), RedisStringCommands.SetOption.ifAbsent());
}
});
return result;
} catch (Exception e) {
logger.error("set redis occured an exception", e);
}
return false;
} /**
* 获取lua结果
*
* @param key 键
* @param value 值
* @return
*/
public Boolean luaExpress(String key, String value) {
lockScript = new DefaultRedisScript<>();
lockScript.setScriptSource(
new ResourceScriptSource(new ClassPathResource("add.lua"))
);
//设置返回值
lockScript.setResultType(Boolean.class);
//封装参数
List<Object> keyList = new ArrayList<>();
keyList.add(key);
keyList.add(value);
Boolean result = (Boolean) redisTemplate.execute(lockScript, keyList);
return result;
} /**
* 获取本机内网IP地址方法
*
* @return
*/
private static String getHostIp() {
try {
Enumeration<NetworkInterface> allNetInterfaces = NetworkInterface.getNetworkInterfaces();
while (allNetInterfaces.hasMoreElements()) {
NetworkInterface netInterface = (NetworkInterface) allNetInterfaces.nextElement();
Enumeration<InetAddress> addresses = netInterface.getInetAddresses();
while (addresses.hasMoreElements()) {
InetAddress ip = (InetAddress) addresses.nextElement();
if (ip != null
&& ip instanceof Inet4Address
&& !ip.isLoopbackAddress() //loopback地址即本机地址,IPv4的loopback范围是127.0.0.0 ~ 127.255.255.255
&& ip.getHostAddress().indexOf(":") == -1) {
return ip.getHostAddress();
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}

unlock.lua脚本

local lockKey = KEYS[1]
local lockValue = KEYS[2] -- get key
local result_1 = redis.call('get', lockKey)
if result_1 == lockValue
then
local result_2= redis.call('del', lockKey)
return result_2
else
return false
end

演示

  为了演示方便,我把失效时间设置短一点,8秒

尾声

  嫖都嫖完了,难道你忍心不点赞关注嘛,O(∩_∩)O哈哈~~~~~今天,先到这,后续继续写Redis秒杀系统的设计

案例源码下载

链接: https://pan.baidu.com/s/1uVoRQs8K3_zTfHXSTeP0uA  密码: k9sv

Redis高级项目实战,都0202年了,还不会Redis?的更多相关文章

  1. Java高级项目实战03:CRM系统数据库设计

    接上一篇:Java高级项目实战02:客户关系管理系统CRM系统模块分析与介绍 欢迎点击回顾,接下来我们说说 CRM系统数据库设计. 我们根据产品的原型搞以及UI组的设计稿, 接下来就要设计数据库, 一 ...

  2. Java高级项目实战02:客户关系管理系统CRM系统模块分析与介绍

    本文承接上一篇:Java高级项目实战之CRM系统01:CRM系统概念和分类.企业项目开发流程 先来CRM系统结构图: 每个模块作用介绍如下: 1.营销管理 营销机会管理:针对企业中客户的质询需求所建立 ...

  3. Java高级项目实战之CRM系统01:CRM系统概念和分类、企业项目开发流程

    1. CRM系统介绍 CRM系统即客户关系管理系统, 顾名思义就是管理公司与客户之间的关系. 是一种以"客户关系一对一理论"为基础,旨在改善企业与客户之间关系的新型管理机制.客户关 ...

  4. 都0202了,还在问Vegas和Pr哪个好?

    自媒体时代,蕴藏着很多机会.许多平凡的人,通过制作视频,收获了掌声.赢得了粉丝,甚至改变了自己的命运. 图1:B站百大UP主颁奖现场   但这条路真的一路畅通吗?其实不然,他们成功的背后,必定有多方面 ...

  5. Redis 配置文件 redis.conf 项目详解

    Redis.conf 配置文件详解 # [Redis](http://yijiebuyi.com/category/redis.html) 配置文件 # 当配置中需要配置内存大小时,可以使用 1k, ...

  6. 小D课堂-SpringBoot 2.x微信支付在线教育网站项目实战_汇总

    2018年Spring Boot 2.x整合微信支付在线教育网站高级项目实战视频课程 小D课堂-SpringBoot 2.x微信支付在线教育网站项目实战_1-1.SpringBoot整合微信支付开发在 ...

  7. redis在项目中的应用

    redis在项目中的应用  ps:PHP 会自动 关redis连接 不需要手动关 对于临时的数据 可以不经过数据库直接redis上操作<pre>/*消息队列实例 消息队列详细步骤在http ...

  8. 面试还搞不懂redis,快看看这40道面试题(含答案和思维导图)

    Redis 面试题 1.什么是 Redis?. 2.Redis 的数据类型? 3.使用 Redis 有哪些好处? 4.Redis 相比 Memcached 有哪些优势? 5.Memcache 与 Re ...

  9. 【转】面试还搞不懂redis,快看看这40道Redis面试题(含答案和思维导图)

    ———————————————— 版权声明:本文为CSDN博主「程序员追风」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明. 原文链接:https://blog. ...

随机推荐

  1. PHP中interface的用处

    确实,PHP 接口是有它的目的的.   它们是契约,是给其他开发人员的说明手册.然而,还是很难理解接口有什么用.   基础 接口是抽象的类,无法直接实例化,但是可被实现.   这是一个基本的例 int ...

  2. Jquery日历编写小练习

    日历练习 总体效果展示: 代码展示: 源代码部分 <body> <!-- 日历--> <div class="div_sty"> <tab ...

  3. CentOS6.5安装Oracle11g

    安装前必读: 1.      安装Oracle的虚拟机需要固定IP. 2.      注意安装过程中root用户与oracle用户的切换(su root/su oracle) 3.      环境变量 ...

  4. pandas属性和方法

    Series对象的常用属性和方法 loc[ ]和iloc[ ]格式示例表 Pandas提供的数据整理方法 Pandas分组对象的属性和方法 date_range函数的常用freq参数表

  5. pandas第三方库

    # 一维数组与常用操作 import pandas as pd # 设置输出结果列对齐 pd.set_option('display.unicode.ambiguous_as_wide',True) ...

  6. Python File read() 方法

    概述 read() 方法用于从文件读取指定的字节数,如果未给定或为负则读取所有.高佣联盟 www.cgewang.com 语法 read() 方法语法如下: fileObject.read(); 参数 ...

  7. PHP curl_share_setopt函数

    (PHP 5 >= 5.5.0) curl_share_setopt — 设置 cURL 共享句柄的一个选项. 说明 bool curl_share_setopt ( resource $sh ...

  8. PHP date_add() 函数

    ------------恢复内容开始------------ 实例 添加 40 天到 2013 年 3 月 15 日: <?php$date=date_create("2013-03- ...

  9. PHP unset() 函数

    unset() 函数用于销毁给定的变量.高佣联盟 www.cgewang.com PHP 版本要求: PHP 4, PHP 5, PHP 7 语法 void unset ( mixed $var [, ...

  10. luogu P2183 [国家集训队]礼物

    LINK:礼物 n个物品 m个人 每个人要分得wi 个物品 每个物品互异 分给每个人的物品不分顺序 求方案数. \(n,p\leq 1e9 m\leq 5\) 方案数 那显然是 第一个人拿了w1件物品 ...