redis实现分布式锁实例分析(redis,开发技术)

时间:2024-04-27 19:50:01 作者 : 石家庄SEO 分类 : 开发技术
  • TAG :

    1、业务场景引入

    模拟一个电商系统,服务器分布式部署,系统中有一个用户下订单的接口,用户下订单之前需要获取分布式锁,然后去检查一下库存,确保库存足够了才会给用户下单,然后释放锁。

    由于系统有一定的并发,所以会预先将商品的库存保存在redis中,用户下单的时候会更新redis的库存。

    2、基础环境准备

    2.1.准备库存数据库

    --------------------------------Tablestructurefort_goods------------------------------DROPTABLEIFEXISTS`t_goods`;CREATETABLE`t_goods`(`goods_id`int(11)NOTNULLAUTO_INCREMENT,`goods_name`varchar(255)DEFAULTNULL,`goods_price`decimal(10,2)DEFAULTNULL,`goods_stock`int(11)DEFAULTNULL,`goods_img`varchar(255)DEFAULTNULL,PRIMARYKEY(`goods_id`))ENGINE=InnoDBAUTO_INCREMENT=4DEFAULTCHARSET=utf8;--------------------------------Recordsoft_goods------------------------------INSERTINTO`t_goods`VALUES('1','iphone8','6999.00','5000','img/iphone.jpg');INSERTINTO`t_goods`VALUES('2','小米9','3000.00','5000','img/rongyao.jpg');INSERTINTO`t_goods`VALUES('3','华为p30','4000.00','5000','img/huawei.jpg');

    2.2.创建SpringBoot工程,pom.xml中导入依赖,请注意版本。

    <?xmlversion="1.0"encoding="UTF-8"?><projectxmlns="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.0http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.springlock.task</groupId><artifactId>springlock.task</artifactId><version>1.0-SNAPSHOT</version><!--导入SpringBoot的父工程把系统中的版本号做了一些定义!--><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.1.3.RELEASE</version></parent><dependencies><!--导入SpringBoot的Web场景启动器Web相关的包导入!--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--导入MyBatis的场景启动器--><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.0.0</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.1.10</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.28</version></dependency><!--SpringBoot单元测试--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><!--导入Lombok依赖--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><!--SpringDataRedis的启动器--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId><version>2.9.0</version></dependency></dependencies><build><!--编译的时候同时也把包下面的xml同时编译进去--><resources><resource><directory>src/main/java</directory><includes><include>**/*.xml</include></includes></resource></resources></build></project>

    2.3.application.properties配置文件

    #SpringBoot有默认的配置,我们可以覆盖默认的配置server.port=8888#配置数据的连接信息spring.datasource.url=jdbc:mysql://127.0.0.1:3306/redislock?useUnicode=true&characterEncoding=utf-8spring.datasource.username=rootspring.datasource.password=rootspring.datasource.type=com.alibaba.druid.pool.DruidDataSource#reids配置spring.redis.jedis.pool.max-idle=10spring.redis.jedis.pool.min-idle=5spring.redis.jedis.pool.maxTotal=15spring.redis.hostName=192.168.3.28spring.redis.port=6379

    2.4.SpringBoot启动类

    /***@authorswadian*@date2022/3/4*@Version1.0*@describetion*/@SpringBootApplicationpublicclassSpringLockApplicationApp{publicstaticvoidmain(String[]args){SpringApplication.run(SpringLockApplicationApp.class,args);}}

    2.5.添加Redis的配置类

    importcom.fasterxml.jackson.annotation.JsonAutoDetect;importcom.fasterxml.jackson.annotation.PropertyAccessor;importcom.fasterxml.jackson.databind.ObjectMapper;importlombok.extern.slf4j.Slf4j;importorg.springframework.boot.context.properties.ConfigurationProperties;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;importorg.springframework.data.redis.connection.jedis.JedisConnectionFactory;importorg.springframework.data.redis.core.RedisTemplate;importorg.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;importorg.springframework.data.redis.serializer.StringRedisSerializer;importredis.clients.jedis.JedisPoolConfig;@Slf4j@ConfigurationpublicclassRedisConfig{/***1.创建JedisPoolConfig对象。在该对象中完成一些链接池配置*@ConfigurationProperties:会将前缀相同的内容创建一个实体。*/@Bean@ConfigurationProperties(prefix="spring.redis.jedis.pool")publicJedisPoolConfigjedisPoolConfig(){JedisPoolConfigconfig=newJedisPoolConfig();log.info("JedisPool默认参数-最大空闲数:{},最小空闲数:{},最大链接数:{}",config.getMaxIdle(),config.getMinIdle(),config.getMaxTotal());returnconfig;}/***2.创建JedisConnectionFactory:配置redis链接信息*/@Bean@ConfigurationProperties(prefix="spring.redis")publicJedisConnectionFactoryjedisConnectionFactory(JedisPoolConfigconfig){log.info("redis初始化配置-最大空闲数:{},最小空闲数:{},最大链接数:{}",config.getMaxIdle(),config.getMinIdle(),config.getMaxTotal());JedisConnectionFactoryfactory=newJedisConnectionFactory();//关联链接池的配置对象factory.setPoolConfig(config);returnfactory;}/***3.创建RedisTemplate:用于执行Redis操作的方法*/@BeanpublicRedisTemplate<String,Object>redisTemplate(JedisConnectionFactoryfactory){RedisTemplate<String,Object>template=newRedisTemplate<>();//关联template.setConnectionFactory(factory);//使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值(默认使用JDK的序列化方式)Jackson2JsonRedisSerializerjacksonSeial=newJackson2JsonRedisSerializer(Object.class);ObjectMapperom=newObjectMapper();//指定要序列化的域,field,get和set,以及修饰符范围,ANY是都有包括private和publicom.setVisibility(PropertyAccessor.ALL,JsonAutoDetect.Visibility.ANY);//指定序列化输入的类型,类必须是非final修饰的,final修饰的类,比如String,Integer等会跑出异常om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);jacksonSeial.setObjectMapper(om);//使用StringRedisSerializer来序列化和反序列化redis的key值template.setKeySerializer(newStringRedisSerializer());//值采用json序列化template.setValueSerializer(jacksonSeial);//设置hashkey和value序列化模式template.setHashKeySerializer(newStringRedisSerializer());template.setHashValueSerializer(jacksonSeial);returntemplate;}}

    2.6.pojo层

    importlombok.AllArgsConstructor;importlombok.Data;importlombok.NoArgsConstructor;@Data@NoArgsConstructor@AllArgsConstructorpublicclassGoods{privateStringgoods_id;privateStringgoods_name;privateDoublegoods_price;privateLonggoods_stock;privateStringgoods_img;}

    2.7.mapper层

    importcom.springlock.pojo.Goods;importorg.apache.ibatis.annotations.Mapper;importorg.apache.ibatis.annotations.Select;importorg.apache.ibatis.annotations.Update;importjava.util.List;@MapperpublicinterfaceGoodsMapper{/***01-更新商品库存*@paramgoods*@return*/@Update("updatet_goodssetgoods_stock=#{goods_stock}wheregoods_id=#{goods_id}")IntegerupdateGoodsStock(Goodsgoods);/***02-加载商品信息*@return*/@Select("select*fromt_goods")List<Goods>findGoods();/***03-根据ID查询*@paramgoodsId*@return*/@Select("select*fromt_goodswheregoods_id=#{goods_id}")GoodsfindGoodsById(StringgoodsId);}

    2.8.SpringBoot监听Web启动事件,加载商品数据到Redis中

    importcom.springlock.mapper.GoodsMapper;importcom.springlock.pojo.Goods;importlombok.extern.slf4j.Slf4j;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.context.ApplicationListener;importorg.springframework.context.annotation.Configuration;importorg.springframework.context.event.ContextRefreshedEvent;importorg.springframework.data.redis.core.RedisTemplate;importjavax.annotation.Resource;importjava.util.List;/***@authorswadian*@date2022/3/4*@Version1.0*@describetionApplicationListener监听器,项目启动时出发*/@Slf4j@ConfigurationpublicclassApplicationStartListenerimplementsApplicationListener<ContextRefreshedEvent>{@ResourceGoodsMappergoodsMapper;@AutowiredprivateRedisTemplate<String,Object>redisTemplate;@OverridepublicvoidonApplicationEvent(ContextRefreshedEventevent){System.out.println("Web项目启动,ApplicationListener监听器触发...");List<Goods>goodsList=goodsMapper.findGoods();for(Goodsgoods:goodsList){redisTemplate.boundHashOps("goods_info").put(goods.getGoods_id(),goods.getGoods_stock());log.info("缓存商品详情:{}",goods);}}}

    3、Redis实现分布式锁

    3.1 分布式锁的实现类

    importredis.clients.jedis.Jedis;importredis.clients.jedis.JedisPool;importredis.clients.jedis.JedisPoolConfig;importredis.clients.jedis.Transaction;importredis.clients.jedis.exceptions.JedisException;importjava.util.List;importjava.util.UUID;publicclassDistributedLock{//redis连接池privatestaticJedisPooljedisPool;static{JedisPoolConfigconfig=newJedisPoolConfig();//设置最大连接数config.setMaxTotal(200);//设置最大空闲数config.setMaxIdle(8);//设置最大等待时间config.setMaxWaitMillis(1000*100);//在borrow一个jedis实例时,是否需要验证,若为true,则所有jedis实例均是可用的config.setTestOnBorrow(true);jedisPool=newJedisPool(config,"192.168.3.28",6379,3000);}/***加锁*@paramlockName锁的key*@paramacquireTimeout获取锁的超时时间*@paramtimeout锁的超时时间*@return锁标识*RedisSetnx(SETifNoteXists)命令在指定的key不存在时,为key设置指定的值。*设置成功,返回1。设置失败,返回0。*/publicStringlockWithTimeout(StringlockName,longacquireTimeout,longtimeout){Jedisconn=null;StringretIdentifier=null;try{//获取连接conn=jedisPool.getResource();//value值->随机生成一个StringStringidentifier=UUID.randomUUID().toString();//key值->即锁名StringlockKey="lock:"+lockName;//超时时间->上锁后超过此时间则自动释放锁毫秒转成->秒intlockExpire=(int)(timeout/1000);//获取锁的超时时间->超过这个时间则放弃获取锁longend=System.currentTimeMillis()+acquireTimeout;while(System.currentTimeMillis()<end){//在获取锁时间内if(conn.setnx(lockKey,identifier)==1){//设置锁成功conn.expire(lockKey,lockExpire);//返回value值,用于释放锁时间确认retIdentifier=identifier;returnretIdentifier;}//ttl以秒为单位返回key的剩余过期时间,返回-1代表key没有设置超时时间,为key设置一个超时时间if(conn.ttl(lockKey)==-1){conn.expire(lockKey,lockExpire);}try{Thread.sleep(10);}catch(InterruptedExceptione){Thread.currentThread().interrupt();}}}catch(JedisExceptione){e.printStackTrace();}finally{if(conn!=null){conn.close();}}returnretIdentifier;}/***释放锁*@paramlockName锁的key*@paramidentifier释放锁的标识*@return*/publicbooleanreleaseLock(StringlockName,Stringidentifier){Jedisconn=null;StringlockKey="lock:"+lockName;booleanretFlag=false;try{conn=jedisPool.getResource();while(true){//监视lock,准备开始redis事务conn.watch(lockKey);//通过前面返回的value值判断是不是该锁,若是该锁,则删除,释放锁if(identifier.equals(conn.get(lockKey))){Transactiontransaction=conn.multi();//开启redis事务transaction.del(lockKey);List<Object>results=transaction.exec();//提交redis事务if(results==null){//提交失败continue;//继续循环}retFlag=true;//提交成功}conn.unwatch();//解除监控break;}}catch(JedisExceptione){e.printStackTrace();}finally{if(conn!=null){conn.close();}}returnretFlag;}}

    3.2 分布式锁的业务代码

    service业务逻辑层

    publicinterfaceSkillService{Integerseckill(StringgoodsId,LonggoodsStock);}

    service业务逻辑层实现层

    importcom.springlock.lock.DistributedLock;importcom.springlock.mapper.GoodsMapper;importcom.springlock.pojo.Goods;importcom.springlock.service.SkillService;importlombok.extern.slf4j.Slf4j;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.data.redis.core.RedisTemplate;importorg.springframework.stereotype.Service;importjavax.annotation.Resource;@Slf4j@ServicepublicclassSkillServiceImplimplementsSkillService{privatefinalDistributedLocklock=newDistributedLock();privatefinalstaticStringLOCK_NAME="goods_stock_resource";@ResourceGoodsMappergoodsMapper;@AutowiredprivateRedisTemplate<String,Object>redisTemplate;@OverridepublicIntegerseckill(StringgoodsId,LonggoodsQuantity){//加锁,返回锁的value值,供释放锁时候进行判断Stringidentifier=lock.lockWithTimeout(LOCK_NAME,5000,1000);Integergoods_stock=(Integer)redisTemplate.boundHashOps("goods_info").get(goodsId);if(goods_stock>0&&goods_stock>=goodsQuantity){//1.查询数据库对象Goodsgoods=goodsMapper.findGoodsById(goodsId);//2.更新数据库中库存数量goods.setGoods_stock(goods.getGoods_stock()-goodsQuantity);goodsMapper.updateGoodsStock(goods);//3.同步Redis中商品库存redisTemplate.boundHashOps("goods_info").put(goods.getGoods_id(),goods.getGoods_stock());log.info("商品Id:{},在Redis中剩余库存数量:{}",goodsId,goods.getGoods_stock());//释放锁lock.releaseLock(LOCK_NAME,identifier);return1;}else{log.info("商品Id:{},库存不足!,库存数:{},购买量:{}",goodsId,goods_stock,goodsQuantity);//释放锁lock.releaseLock(LOCK_NAME,identifier);return-1;}}}

    controller层

    importcom.springlock.service.SkillService;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.context.annotation.Scope;importorg.springframework.web.bind.annotation.RequestMapping;importorg.springframework.web.bind.annotation.RestController;@RestController@Scope("prototype")//prototype多实例,singleton单实例publicclassSkillController{@AutowiredSkillServiceskillService;@RequestMapping("/skill")publicStringskill(){Integercount=skillService.seckill("1",1L);returncount>0?"下单成功"+count:"下单失败"+count;}}

    4、分布式锁测试

    把SpringBoot工程启动两台服务器,端口分别为8888、9999。启动8888端口后,修改配置文件端口为9999,启动另一个应用

    redis实现分布式锁实例分析

    然后使用jmeter进行并发测试,开两个线程组,分别代表两台服务器下单,1秒钟起20个线程,循环25次,总共下单1000次。

    redis实现分布式锁实例分析

    查看控制台输出:

    redis实现分布式锁实例分析

    注意:该锁在并发量太高的情况下,会出现一部分失败率。手动写的程序,因为操作的非原子性,会存在并发问题。该锁的实现只是为了演示原理,并不适用于生产。

    redis实现分布式锁实例分析

    jmeter聚合报告

    redis实现分布式锁实例分析

     </div> <div class="zixun-tj-product adv-bottom"></div> </div> </div> <div class="prve-next-news">
    本文:redis实现分布式锁实例分析的详细内容,希望对您有所帮助,信息来源于网络。
    上一篇:基于resty security的Api权限控制与事务支持的方法下一篇:

    6 人围观 / 0 条评论 ↓快速评论↓

    (必须)

    (必须,保密)

    阿狸1 阿狸2 阿狸3 阿狸4 阿狸5 阿狸6 阿狸7 阿狸8 阿狸9 阿狸10 阿狸11 阿狸12 阿狸13 阿狸14 阿狸15 阿狸16 阿狸17 阿狸18