5台redis实现红锁(完整demo)
示例环境:
Spring Boot
JDK1.8.0_131
apache-maven-3.5.4
nginx-1.14.2
redis-3.2.1
1.Controller :
package com..redlock; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest; @RestController @RequestMapping("/lock") public class RedLockController { @Autowired // 红锁 @Qualifier("RedLockLockService") private RedLockService grabService; @RequestMapping("/do") public String grabMysql(){ ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = servletRequestAttributes.getRequest(); int port = request.getLocalPort(); long i = Thread.currentThread().getId(); String type = grabService.grabOrder(port+"-"+i); System.out.println(port+"-"+i+"返回页面:"+type); return type; } }
2. Service :
package com..redlock; public interface RedLockService { public String grabOrder(String orderId); }
3. ServiceImpl:
package com..redlock; import com..dao.SaveInfoMapper; import com..entity.SaveInfo; import org.redisson.RedissonRedLock; import org.redisson.api.RLock; import org.redisson.api.RedissonClient; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; import java.sql.Timestamp; import java.util.Date; @Service("RedLockLockService") public class RedLockLockServiceImpl implements RedLockService { // 红锁 @Autowired @Qualifier("redissonRed1") private RedissonClient redissonRed1; @Autowired @Qualifier("redissonRed2") private RedissonClient redissonRed2; @Autowired @Qualifier("redissonRed3") private RedissonClient redissonRed3; @Autowired @Qualifier("redissonRed4") private RedissonClient redissonRed4; @Autowired @Qualifier("redissonRed5") private RedissonClient redissonRed5; @Autowired SaveInfoMapper saveInfoMapper; @Override public String grabOrder(String orderId ){ // System.out.println(orderId+" : 来加锁"); //生成key String lockKey = ("lock_red").intern(); //红锁 redis son RLock rLock1 = redissonRed1.getLock(lockKey); RLock rLock2 = redissonRed2.getLock(lockKey); RLock rLock3 = redissonRed3.getLock(lockKey); RLock rLock4 = redissonRed4.getLock(lockKey); RLock rLock5 = redissonRed5.getLock(lockKey); RedissonRedLock rLock = new RedissonRedLock(rLock1,rLock2,rLock3,rLock4,rLock5); String count =""; boolean b1= true; boolean b2= true; try { //包含锁续命 默认设置key 超时时间30秒,过10秒,再延时 b1 = rLock.tryLock(); if (b1==true){ System.out.println(orderId+" : 加锁成功"); count = updataData(); System.out.println(orderId+" 数据库返回结果 :"+count); if(count.equals("0")){ return "数据库总数已为0"; } }else { // System.out.println(orderId+" : 加锁失败#####"); b2 = false; try { //未拿到锁,暂停一下再去加锁 Thread.sleep(100); grabOrder(orderId); } catch (InterruptedException e) { e.printStackTrace(); } } }catch (Exception e){ System.out.println("加锁过程异常 :"+e); }finally { if(b2==true){ System.out.println(orderId+" : 去解锁~~~~~"); rLock.unlock(); } } return count; } //数据库写入操作 public Boolean sendData(String orderId){ Timestamp saveTime=new Timestamp(new Date().getTime()); SaveInfo info = new SaveInfo(); info.setId(orderId); info.setApplyno("004098020000002"); info.setUserName("user"); info.setUserNumber("002"); info.setAddress("上海"); info.setSaveTime(saveTime); info.setType(orderId); info.setCount("count"); int mu = saveInfoMapper.insert(info); if(mu!=1){ return false; } return true; } //数据库减一操作 public String updataData() { //查出id为1112的数据,判断需要减1的字段当前值 SaveInfo save = saveInfoMapper.selectInfoByid("1112"); String type = save.getType(); if(type.equals("0")){ return "0"; } int count = Integer.parseInt(type)-1; System.out.println("数据库剩余个数 :"+count); SaveInfo info = new SaveInfo(); info.setId("1112"); info.setType(count+""); int mu = saveInfoMapper.updateByPrimaryid(info); System.out.println("mu : "+mu); if(mu!=1){ return "数据库扣除报错"; } System.out.println("mu2 : "+mu); return "数据库总数减一"; } }
4.RedLockConfig:
package com..redlock; import org.redisson.Redisson; import org.redisson.api.RedissonClient; import org.redisson.config.Config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Primary; import org.springframework.stereotype.Component; @Component public class RedLockConfig { //红锁: @Bean(name = "redissonRed1") @Primary public RedissonClient redissonRed1(){ Config config = new Config(); config.useSingleServer().setAddress("127.0.0.1:6379").setDatabase(0); return Redisson.create(config); } @Bean(name = "redissonRed2") public RedissonClient redissonRed2(){ Config config = new Config(); config.useSingleServer().setAddress("127.0.0.1:6380").setDatabase(0); return Redisson.create(config); } @Bean(name = "redissonRed3") public RedissonClient redissonRed3(){ Config config = new Config(); config.useSingleServer().setAddress("127.0.0.1:6381").setDatabase(0); return Redisson.create(config); } @Bean(name = "redissonRed4") public RedissonClient redissonRed4(){ Config config = new Config(); config.useSingleServer().setAddress("127.0.0.1:6382").setDatabase(0); return Redisson.create(config); } @Bean(name = "redissonRed5") public RedissonClient redissonRed5(){ Config config = new Config(); config.useSingleServer().setAddress("127.0.0.1:6383").setDatabase(0); return Redisson.create(config); } }
5.dao :
package com..dao; import com..entity.SaveInfo; import org.apache.ibatis.annotations.Mapper; import org.springframework.stereotype.Component; import java.util.List; @Mapper @Component(value = "SaveInfoMapper") public interface SaveInfoMapper { int insert(SaveInfo record); List<SaveInfo> selectAll(); SaveInfo selectInfoByid(String id); int updateByPrimaryid(SaveInfo record); }
6.entity :
package com..entity; import java.io.Serializable; import java.util.Date; /** * * This class was generated by MyBatis Generator. * This class corresponds to the database table save */ public class SaveInfo{ private String id; private String applyno; private String userName; private String userNumber; private String address; private Date saveTime; private String type; private String count; private byte[] saveBolb; private String saveClob; public String getId() { return id; } public void setId(String id) { this.id = id == null ? null : id.trim(); } public String getApplyno() { return applyno; } public void setApplyno(String applyno) { this.applyno = applyno == null ? null : applyno.trim(); } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName == null ? null : userName.trim(); } public String getUserNumber() { return userNumber; } public void setUserNumber(String userNumber) { this.userNumber = userNumber == null ? null : userNumber.trim(); } public String getAddress() { return address; } public void setAddress(String address) { this.address = address == null ? null : address.trim(); } public Date getSaveTime() { return saveTime; } public void setSaveTime(Date saveTime) { this.saveTime = saveTime; } public String getType() { return type; } public void setType(String type) { this.type = type == null ? null : type.trim(); } public String getCount() { return count; } public void setCount(String count) { this.count = count == null ? null : count.trim(); } public byte[] getSaveBolb() { return saveBolb; } public void setSaveBolb(byte[] saveBolb) { this.saveBolb = saveBolb; } public String getSaveClob() { return saveClob; } public void setSaveClob(String saveClob) { this.saveClob = saveClob == null ? null : saveClob.trim(); } }
7.Mapper.xml:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com..dao.SaveInfoMapper"> <resultMap id="BaseResultMap" type="com..entity.SaveInfo"> <result column="ID" jdbcType="VARCHAR" property="id" /> <result column="APPLYNO" jdbcType="VARCHAR" property="applyno" /> <result column="USER_NAME" jdbcType="VARCHAR" property="userName" /> <result column="USER_NUMBER" jdbcType="VARCHAR" property="userNumber" /> <result column="ADDRESS" jdbcType="VARCHAR" property="address" /> <result column="SAVE_TIME" jdbcType="TIMESTAMP" property="saveTime" /> <result column="TYPE" jdbcType="VARCHAR" property="type" /> <result column="COUNT" jdbcType="VARCHAR" property="count" /> <result column="SAVE_BOLB" jdbcType="LONGVARBINARY" property="saveBolb" /> <result column="SAVE_CLOB" jdbcType="LONGVARCHAR" property="saveClob" /> </resultMap> <insert id="insert" parameterType="com..entity.SaveInfo"> insert into save (ID, APPLYNO, USER_NAME, USER_NUMBER, ADDRESS, SAVE_TIME, TYPE, COUNT, SAVE_BOLB, SAVE_CLOB) values (#{id,jdbcType=VARCHAR}, #{applyno,jdbcType=VARCHAR}, #{userName,jdbcType=VARCHAR}, #{userNumber,jdbcType=VARCHAR}, #{address,jdbcType=VARCHAR}, #{saveTime,jdbcType=TIMESTAMP}, #{type,jdbcType=VARCHAR}, #{count,jdbcType=VARCHAR}, #{saveBolb,jdbcType=LONGVARBINARY}, #{saveClob,jdbcType=LONGVARCHAR}) </insert> <select id="selectAll" resultMap="BaseResultMap"> select ID, APPLYNO, USER_NAME, USER_NUMBER, ADDRESS, SAVE_TIME, TYPE, COUNT, SAVE_BOLB, SAVE_CLOB from save </select> <select id="selectInfoByid" parameterType="java.lang.String" resultMap="BaseResultMap"> select ID, APPLYNO, USER_NAME, USER_NUMBER, ADDRESS, SAVE_TIME, TYPE, COUNT, SAVE_BOLB, SAVE_CLOB from save where id = #{id,jdbcType=VARCHAR} </select> <update id="updateByPrimaryid" parameterType="com..entity.SaveInfo"> update save set type = #{type,jdbcType=VARCHAR} where id = #{id,jdbcType=INTEGER} </update> </mapper>
application.properties 配置:
#服务端口--模仿集群 (6011,7011,8011) server.port: 6011 #redis配置 spring.redis.database=0 spring.redis.host=127.0.0.1 spring.redis.port=6379 spring.redis.timeout=2000 spring.redis.password= #数据库配置 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.type=com.alibaba.druid.pool.DruidDataSource spring.datasource.url=jdbc:mysql://localhost:3306/ac-new?characterEncoding=UTF-8&serverTimezone=Asia/Shanghai spring.datasource.username=root spring.datasource.password=root spring.druid.datasource.max-idle=10 spring.druid.datasource.max-wait=10000 spring.druid.datasource.minIdle=5 spring.druid.datasource.initial-size=5 #mybatis配置 mybatis.mapper-locations:classpath:mapper/*.xml
pom 配置:
<?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.4.1</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.wond</groupId> <artifactId>redlock</artifactId> <version>0.0.1-SNAPSHOT</version> <name>redlock</name> <description>redLock</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>3.3.2</version> </dependency> <!-- mysql:MyBatis相关依赖 --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.0.0</version> </dependency> <!-- 整合MyBatis java类依赖 --> <dependency> <groupId>org.mybatis.generator</groupId> <artifactId>mybatis-generator-maven-plugin</artifactId> <version>1.4.0</version> <type>maven-plugin</type> </dependency> <!-- mysql:mysql驱动 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <!-- mysql:阿里巴巴数据库连接池 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.12</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
Jmeter 测试:
后台打印信息:
最后,两个坑:
1.如果第一个业务线程过来加锁,加了3台之后redis突然挂掉一台,怎么解决?
方案:延时启动挂掉的redis.
2.一个业务线程加锁成功,突然出现 Stop-The-World(GC导致),锁超时之后系统(集群)其他服务的业务线程来 加锁成功,第一个线程又恢复了 怎么办?
方案:(目前还没好的解决方案) 建议换zookeeper.