使用Redis设置分布式锁
目录结构
首先引入依赖
<?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>
<groupId>com.zhou</groupId>
<artifactId>springboot-redis</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springboot-redis</name>
<description>Demo project for Spring Boot</description>
<parent>
<artifactId>springboot-vue</artifactId>
<groupId>com.zhou</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.2.0</version>
</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>
</project>
配置redis和mysql
# 应用名称
spring.application.name=springboot-redis
# 应用服务 WEB 访问端口
server.port=8080
# Redis地址
spring.redis.host=127.0.0.1
# Redis端口
spring.redis.port=6379
# Redis服务器连接密码(默认为空)
# spring.redis.password=123456
# Redis数据库索引(默认为0)
spring.redis.database=0
# 连接池最大连接数(使用负值表示没有限制)
spring.redis.jedis.pool.max-active=10
# 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.jedis.pool.max-wait=-1ms
# 连接池中的最大空闲连接
spring.redis.jedis.pool.max-idle=8
# 连接池中的最小空闲连接
spring.redis.jedis.pool.min-idle=0
# 连接超时时间(毫秒)
spring.redis.timeout=10000ms
spring.datasource.url= jdbc:mysql://192.168.1.4:3306/springboot_redis?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
logging.level.com.zhou=debug
设置单机版的锁
首先创建一个实体类,这个实体类用来返回数据库数据
package com.zhou.springbootredis.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
@Data
@TableName("user")
@NoArgsConstructor
@AllArgsConstructor
public class UserEntity {
@TableId(type = IdType.ID_WORKER)
private Long id;
private String name;
private String passward;
}
创建一个mapper
package com.zhou.springbootredis.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.zhou.springbootredis.entity.UserEntity;
public interface UserMapper extends BaseMapper<UserEntity> {
}
创建一个接口
package com.zhou.springbootredis.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.zhou.springbootredis.entity.UserEntity;
public interface UserService extends IService<UserEntity> {
Object getUserById(Integer id);
void parallel(Integer id);
}
实现接口,getUserById方法实现的单机版锁,parallel直接模拟并发
package com.zhou.springbootredis.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.zhou.springbootredis.entity.UserEntity;
import com.zhou.springbootredis.mapper.UserMapper;
import com.zhou.springbootredis.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import javax.annotation.Resource;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, UserEntity> implements UserService {
private final String REDISLOCK="REDISLOCK";
@Autowired
public RedisTemplate redisTemplate;
@Resource
private UserMapper userMapper;
@Override
public Object getUserById(Integer id) {
String key = "user:" + id;
Object userObj = redisTemplate.opsForValue().get(key);
if(userObj == null){
synchronized (this.getClass()){
userObj = redisTemplate.opsForValue().get(key);
if(userObj == null ){
log.debug("查询数据库");
UserEntity userEntity = userMapper.selectById(id);
redisTemplate.opsForValue().set(key,userEntity);
return userEntity;
}else{
log.debug("查询缓存,进入锁");
return userObj;
}
}
}else{
log.debug("查询缓存");
}
return userObj;
}
@Override
public void parallel(Integer id){
ExecutorService es = Executors.newFixedThreadPool(10);
for(int i=0 ;i<100;i++){
es.submit(new Runnable() {
@Override
public void run() {
getUserById(id);
}
});
}
try {
es.shutdown();
if(!es.awaitTermination(1,TimeUnit.MINUTES)){
es.shutdownNow();
}
log.debug("所有任务完成");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
创建controller
package com.zhou.springbootredis.controller;
import com.zhou.springbootredis.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@RestController
@RequestMapping("redis")
public class Redis2Controller {
@Autowired
UserService userService;
@GetMapping("getUserById/{id}")
public Object getUserById(@PathVariable("id") Integer id){
return userService.getUserById(id);
}
@GetMapping("parallel/{id}")
public Object parallel(@PathVariable("id") Integer id){
userService.parallel(id);
return "成功";
}
}
结果只查了一次数据库
分布式锁
在UserService 接口文件中加如下两个接口
Long redisLock(String userId,String selectKey);
void parallelRedisLock(String selectKey);
在UserServiceImpl 文件中实现上面两个接口,redisLock是分布式锁的实现,parallelRedisLock直接模拟并发
@Override
public void parallelRedisLock(String selectKey){
ExecutorService es = Executors.newFixedThreadPool(10);
for(int i=0 ;i<100;i++){
int finalI = i;
es.submit(new Runnable() {
@Override
public void run() {
redisLock("userId:"+ finalI,selectKey);
}
});
}
try {
es.shutdown();
if(!es.awaitTermination(1,TimeUnit.MINUTES)){
es.shutdownNow();
}
log.debug("所有任务完成");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public Long redisLock(String userId,String selectKey){
Long value;
while (true) {
Boolean lock = redisTemplate.opsForValue().setIfAbsent(REDISLOCK, userId, 10, TimeUnit.SECONDS);
if (lock) {
log.debug(userId+"拿到锁");
value=redisTemplate.boundValueOps(selectKey).increment(1);
Object lockUserId = redisTemplate.opsForValue().get(REDISLOCK);
if (userId.equals(lockUserId)) {
redisTemplate.delete(REDISLOCK);
}
break;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return value;
}
首先在redis中加入一个键值对,后面直接操作这个键值对
在controller中加入两个方法
@GetMapping("redisLock")
public Object redisLock(@RequestParam String userId,@RequestParam String key){
return userService.redisLock(userId,key);
}
@GetMapping("parallelRedisLock")
public Object parallelRedisLock(@RequestParam String key){
userService.parallelRedisLock(key);
return "成功";
}
访问
结果
gitee地址
|