redis实现分布式锁实例分析
发表于:2024-12-13 作者:千家信息网编辑
千家信息网最后更新 2024年12月13日,本文小编为大家详细介绍"redis实现分布式锁实例分析",内容详细,步骤清晰,细节处理妥当,希望这篇"redis实现分布式锁实例分析"文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知
千家信息网最后更新 2024年12月13日redis实现分布式锁实例分析
本文小编为大家详细介绍"redis实现分布式锁实例分析",内容详细,步骤清晰,细节处理妥当,希望这篇"redis实现分布式锁实例分析"文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。
1、业务场景引入
模拟一个电商系统,服务器分布式部署,系统中有一个用户下订单的接口,用户下订单之前需要获取分布式锁,然后去检查一下库存,确保库存足够了才会给用户下单,然后释放锁。
由于系统有一定的并发,所以会预先将商品的库存保存在redis中,用户下单的时候会更新redis的库存。
2、基础环境准备
2.1.准备库存数据库
-- ------------------------------ Table structure for t_goods-- ----------------------------DROP TABLE IF EXISTS `t_goods`;CREATE TABLE `t_goods` ( `goods_id` int(11) NOT NULL AUTO_INCREMENT, `goods_name` varchar(255) DEFAULT NULL, `goods_price` decimal(10,2) DEFAULT NULL, `goods_stock` int(11) DEFAULT NULL, `goods_img` varchar(255) DEFAULT NULL, PRIMARY KEY (`goods_id`)) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;-- ------------------------------ Records of t_goods-- ----------------------------INSERT INTO `t_goods` VALUES ('1', 'iphone8', '6999.00', '5000', 'img/iphone.jpg');INSERT INTO `t_goods` VALUES ('2', '小米9', '3000.00', '5000', 'img/rongyao.jpg');INSERT INTO `t_goods` VALUES ('3', '华为p30', '4000.00', '5000', 'img/huawei.jpg');
2.2.创建SpringBoot工程,pom.xml中导入依赖,请注意版本。
4.0.0 com.springlock.task springlock.task 1.0-SNAPSHOT org.springframework.boot spring-boot-starter-parent 2.1.3.RELEASE org.springframework.boot spring-boot-starter-web org.mybatis.spring.boot mybatis-spring-boot-starter 2.0.0 com.alibaba druid-spring-boot-starter 1.1.10 mysql mysql-connector-java 5.1.28 org.springframework.boot spring-boot-starter-test test org.projectlombok lombok org.springframework.boot spring-boot-starter-data-redis redis.clients jedis 2.9.0 src/main/java **/*.xml
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启动类
/** * @author swadian * @date 2022/3/4 * @Version 1.0 * @describetion */@SpringBootApplicationpublic class SpringLockApplicationApp { public static void main(String[] args) { SpringApplication.run(SpringLockApplicationApp.class,args); }}
2.5.添加Redis的配置类
import com.fasterxml.jackson.annotation.JsonAutoDetect;import com.fasterxml.jackson.annotation.PropertyAccessor;import com.fasterxml.jackson.databind.ObjectMapper;import lombok.extern.slf4j.Slf4j;import org.springframework.boot.context.properties.ConfigurationProperties;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;import org.springframework.data.redis.serializer.StringRedisSerializer;import redis.clients.jedis.JedisPoolConfig;@Slf4j@Configurationpublic class RedisConfig { /** * 1.创建JedisPoolConfig对象。在该对象中完成一些链接池配置 * @ConfigurationProperties:会将前缀相同的内容创建一个实体。 */ @Bean @ConfigurationProperties(prefix="spring.redis.jedis.pool") public JedisPoolConfig jedisPoolConfig(){ JedisPoolConfig config = new JedisPoolConfig(); log.info("JedisPool默认参数-最大空闲数:{},最小空闲数:{},最大链接数:{}",config.getMaxIdle(),config.getMinIdle(),config.getMaxTotal()); return config; } /** * 2.创建JedisConnectionFactory:配置redis链接信息 */ @Bean @ConfigurationProperties(prefix="spring.redis") public JedisConnectionFactory jedisConnectionFactory(JedisPoolConfig config){ log.info("redis初始化配置-最大空闲数:{},最小空闲数:{},最大链接数:{}",config.getMaxIdle(),config.getMinIdle(),config.getMaxTotal()); JedisConnectionFactory factory = new JedisConnectionFactory(); //关联链接池的配置对象 factory.setPoolConfig(config); return factory; } /** * 3.创建RedisTemplate:用于执行Redis操作的方法 */ @Bean public RedisTemplateredisTemplate(JedisConnectionFactory factory){ RedisTemplate template = new RedisTemplate<>(); //关联 template.setConnectionFactory(factory); //使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值(默认使用JDK的序列化方式) Jackson2JsonRedisSerializer jacksonSeial = new Jackson2JsonRedisSerializer(Object.class); ObjectMapper om = new ObjectMapper(); //指定要序列化的域,field,get和set,以及修饰符范围,ANY是都有包括private和public om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); //指定序列化输入的类型,类必须是非final修饰的,final修饰的类,比如String,Integer等会跑出异常 om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); jacksonSeial.setObjectMapper(om); //使用StringRedisSerializer来序列化和反序列化redis的key值 template.setKeySerializer(new StringRedisSerializer()); //值采用json序列化 template.setValueSerializer(jacksonSeial); //设置hash key 和value序列化模式 template.setHashKeySerializer(new StringRedisSerializer()); template.setHashValueSerializer(jacksonSeial); return template; }}
2.6.pojo层
import lombok.AllArgsConstructor;import lombok.Data;import lombok.NoArgsConstructor;@Data@NoArgsConstructor@AllArgsConstructorpublic class Goods { private String goods_id; private String goods_name; private Double goods_price; private Long goods_stock; private String goods_img;}
2.7.mapper层
import com.springlock.pojo.Goods;import org.apache.ibatis.annotations.Mapper;import org.apache.ibatis.annotations.Select;import org.apache.ibatis.annotations.Update;import java.util.List;@Mapperpublic interface GoodsMapper { /** * 01-更新商品库存 * @param goods * @return */ @Update("update t_goods set goods_stock=#{goods_stock} where goods_id=#{goods_id}") Integer updateGoodsStock(Goods goods); /** * 02-加载商品信息 * @return */ @Select("select * from t_goods") ListfindGoods(); /** * 03-根据ID查询 * @param goodsId * @return */ @Select("select * from t_goods where goods_id=#{goods_id}") Goods findGoodsById(String goodsId);}
2.8.SpringBoot监听Web启动事件,加载商品数据到Redis中
import com.springlock.mapper.GoodsMapper;import com.springlock.pojo.Goods;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.ApplicationListener;import org.springframework.context.annotation.Configuration;import org.springframework.context.event.ContextRefreshedEvent;import org.springframework.data.redis.core.RedisTemplate;import javax.annotation.Resource;import java.util.List;/** * @author swadian * @date 2022/3/4 * @Version 1.0 * @describetion ApplicationListener监听器,项目启动时出发 */@Slf4j@Configurationpublic class ApplicationStartListener implements ApplicationListener{ @Resource GoodsMapper goodsMapper; @Autowired private RedisTemplate redisTemplate; @Override public void onApplicationEvent(ContextRefreshedEvent event) { System.out.println("Web项目启动,ApplicationListener监听器触发..."); List goodsList = goodsMapper.findGoods(); for (Goods goods : goodsList) { redisTemplate.boundHashOps("goods_info").put(goods.getGoods_id(), goods.getGoods_stock()); log.info("缓存商品详情:{}",goods); } }}
3、Redis实现分布式锁
3.1 分布式锁的实现类
import redis.clients.jedis.Jedis;import redis.clients.jedis.JedisPool;import redis.clients.jedis.JedisPoolConfig;import redis.clients.jedis.Transaction;import redis.clients.jedis.exceptions.JedisException;import java.util.List;import java.util.UUID;public class DistributedLock { //redis连接池 private static JedisPool jedisPool; static { JedisPoolConfig config = new JedisPoolConfig(); // 设置最大连接数 config.setMaxTotal(200); // 设置最大空闲数 config.setMaxIdle(8); // 设置最大等待时间 config.setMaxWaitMillis(1000 * 100); // 在borrow一个jedis实例时,是否需要验证,若为true,则所有jedis实例均是可用的 config.setTestOnBorrow(true); jedisPool = new JedisPool(config, "192.168.3.28", 6379, 3000); } /** * 加锁 * @param lockName 锁的key * @param acquireTimeout 获取锁的超时时间 * @param timeout 锁的超时时间 * @return 锁标识 * Redis Setnx(SET if Not eXists) 命令在指定的 key 不存在时,为 key 设置指定的值。 * 设置成功,返回 1 。 设置失败,返回 0 。 */ public String lockWithTimeout(String lockName, long acquireTimeout, long timeout) { Jedis conn = null; String retIdentifier = null; try { // 获取连接 conn = jedisPool.getResource(); // value值->随机生成一个String String identifier = UUID.randomUUID().toString(); // key值->即锁名 String lockKey = "lock:" + lockName; // 超时时间->上锁后超过此时间则自动释放锁 毫秒转成->秒 int lockExpire = (int) (timeout / 1000); // 获取锁的超时时间->超过这个时间则放弃获取锁 long end = System.currentTimeMillis() + acquireTimeout; while (System.currentTimeMillis() < end) { //在获取锁时间内 if (conn.setnx(lockKey, identifier) == 1) {//设置锁成功 conn.expire(lockKey, lockExpire); // 返回value值,用于释放锁时间确认 retIdentifier = identifier; return retIdentifier; } // ttl以秒为单位返回 key 的剩余过期时间,返回-1代表key没有设置超时时间,为key设置一个超时时间 if (conn.ttl(lockKey) == -1) { conn.expire(lockKey, lockExpire); } try { Thread.sleep(10); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } catch (JedisException e) { e.printStackTrace(); } finally { if (conn != null) { conn.close(); } } return retIdentifier; } /** * 释放锁 * @param lockName 锁的key * @param identifier 释放锁的标识 * @return */ public boolean releaseLock(String lockName, String identifier) { Jedis conn = null; String lockKey = "lock:" + lockName; boolean retFlag = false; try { conn = jedisPool.getResource(); while (true) { // 监视lock,准备开始redis事务 conn.watch(lockKey); // 通过前面返回的value值判断是不是该锁,若是该锁,则删除,释放锁 if (identifier.equals(conn.get(lockKey))) { Transaction transaction = conn.multi();//开启redis事务 transaction.del(lockKey); List
3.2 分布式锁的业务代码
service业务逻辑层
public interface SkillService { Integer seckill(String goodsId,Long goodsStock);}
service业务逻辑层实现层
import com.springlock.lock.DistributedLock;import com.springlock.mapper.GoodsMapper;import com.springlock.pojo.Goods;import com.springlock.service.SkillService;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 javax.annotation.Resource;@Slf4j@Servicepublic class SkillServiceImpl implements SkillService { private final DistributedLock lock = new DistributedLock(); private final static String LOCK_NAME = "goods_stock_resource"; @Resource GoodsMapper goodsMapper; @Autowired private RedisTemplateredisTemplate; @Override public Integer seckill(String goodsId, Long goodsQuantity) { // 加锁,返回锁的value值,供释放锁时候进行判断 String identifier = lock.lockWithTimeout(LOCK_NAME, 5000, 1000); Integer goods_stock = (Integer) redisTemplate.boundHashOps("goods_info").get(goodsId); if (goods_stock > 0 && goods_stock >= goodsQuantity) { //1.查询数据库对象 Goods goods = 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); return 1; } else { log.info("商品Id:{},库存不足!,库存数:{},购买量:{}", goodsId, goods_stock, goodsQuantity); //释放锁 lock.releaseLock(LOCK_NAME, identifier); return -1; } }}
controller层
import com.springlock.service.SkillService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.annotation.Scope;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;@RestController@Scope("prototype") //prototype 多实例,singleton单实例public class SkillController { @Autowired SkillService skillService; @RequestMapping("/skill") public String skill() { Integer count = skillService.seckill("1", 1L); return count > 0 ? "下单成功" + count : "下单失败" + count; }}
4、分布式锁测试
把SpringBoot工程启动两台服务器,端口分别为8888、9999。启动8888端口后,修改配置文件端口为9999,启动另一个应用
然后使用jmeter进行并发测试,开两个线程组,分别代表两台服务器下单,1秒钟起20个线程,循环25次,总共下单1000次。
查看控制台输出:
注意:该锁在并发量太高的情况下,会出现一部分失败率。手动写的程序,因为操作的非原子性,会存在并发问题。该锁的实现只是为了演示原理,并不适用于生产。
jmeter聚合报告
读到这里,这篇"redis实现分布式锁实例分析"文章已经介绍完毕,想要掌握这篇文章的知识点还需要大家自己动手实践使用过才能领会,如果想了解更多相关内容的文章,欢迎关注行业资讯频道。
时间
库存
配置
分布式
序列
商品
实例
最大
数据
空闲
链接
成功
业务
对象
用户
实例分析
分析
事务
信息
内容
数据库的安全要保护哪些东西
数据库安全各自的含义是什么
生产安全数据库录入
数据库的安全性及管理
数据库安全策略包含哪些
海淀数据库安全审计系统
建立农村房屋安全信息数据库
易用的数据库客户端支持安全管理
连接数据库失败ssl安全错误
数据库的锁怎样保障安全
网络安全团会背景
数据库连接不上iphone
两个服务器共享管理口
关于计算机网络技术的大学
正益互联网科技有限公司官网
吉安美萍系统软件开发
检索数据库user表的所有记录
服务器空载
h5网络安全活动
苏州oa软件开发价格
净网2018网络安全无小事
网络安全对国家的威胁和风险
东软香港软件开发
乐至县宽带服务器
美国为什么要网络安全
朝阳区网络技术服务收费
联想e32服务器
天府杯网络安全招聘
数据库服务器
管理系统的数据库需要哪些特点
广东网络技术工程职业学院
服务器镜像怎么选
sql数据库添加数据是乱码
信息化网络安全评估
软件开发大会演讲稿
网络安全法 落地
拖拉拽软件开发怎么提升
2021服务器cpu排行天梯图
网络安全面临的问题和威胁
sql新建数据库找不到sa