Redis是什么:
Redis是现在最受欢迎的NoSQL数据库之一,Redis是一个使用ANSI C编写的开源、包含多种数据结构、支持网络、基于内存、可选持久性的键值对存储数据库,其具备如下特性:
基于内存运行,性能高效
支持分布式,理论上可以无限扩展
key-value存储系统
开源的使用ANSI C语言编写、遵守BSD协议、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API
相比于其他数据库类型,Redis具备的特点是:
C/S通讯模型
单进程单线程模型
丰富的数据类型
操作具有原子性
持久化
高并发读写
支持lua脚本
分布式锁常见的场景:
互联网秒杀(淘宝双11、京东的618等)
抢优惠券
1. 互斥性 (即任意时刻,只有一个客户端持有锁) 2. 不发生死锁(即使一个客户端在持有锁期间崩溃而没有释放锁的情况下,也能保证后续其他客户端加锁) 3. 具有容错性(只有大部分redis节点正常运行,客户端就可以加锁和解锁) 4. 解铃还需系铃人(也就是加锁和解锁必须是同一个客户端,客户端不能把别人的锁给解了) 5. 锁不能自己失效(锁正常执行程序时,锁不能因为某些原因失效)
文档地址:
http://redisdoc.com/
setnx (SET if Not eXists)
语法:
SETNX key value
将 key 的值设为 value ,当且仅当 key 不存在。
若给定的 key 已经存在,则 SETNX 不做任何动作。
返回值:
设置成功,返回 1 设置失败,返回 0
get
语法:
GET key
返回值:
当 key 不存在时,返回 null ,否则,返回 key 的值。
如果 key 不是字符串类型,那么返回一个错误
相关依赖:
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.16.RELEASE</version> <relativePath/> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> <fastjson.version>1.2.29</fastjson.version> <druid.version>1.0.26</druid.version> <mysql.version>5.1.46</mysql.version> <swagger.version>2.7.0</swagger.version> <lombok.version>1.18.2</lombok.version> <maven.build.timestamp.format>yyyy-MM-dd</maven.build.timestamp.format> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!----> <dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>3.13.4</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!--lombok--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.2</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
server: port: 8060 spring: redis: database: 0 host: 127.0.0.1 port: 6379 password: timeout: 20000 jedis: pool: max-active: 50 //是最大激活连接数,这里取值为20,表示同时最多有50个reids连接 max-wait: 60000。//最大等待毫秒数, 单位为 ms, 超过时间会出错误信息 max-idle: 5。 //最大等待连接中的数量,设 0 为没有限制 min-idle: 0 //最小等待连接中的数量,设 0 为没有限制
/** * stringRedisTemplate redis模版 */ @Autowired private RedisTemplate redisTemplate; /** * 库存扣减 * @return */ @RequestMapping("/operation_stock") public String operStock() { String stock = (String) redisTemplate.opsForValue().get("stock");// jedis.get("stock") if (StringUtil.isNotBlank(stock) && Integer.parseInt(stock) > 0) { int realStock = Integer.parseInt(stock) - 1; redisTemplate.opsForValue().set("stock", realStock + ""); // jedis.set(key, val) log.info(String.format("扣减成功:[%s]", realStock)); } else { log.info("扣减失败,库存不足"); } return "success"; }
/** * 操作库存 * @return 返回值 */ @RequestMapping("/operation/stock") public String operationStock() { synchronized (this) { String stock = (String) redisTemplate.opsForValue().get("stock");// jedis.get("stock") if (StringUtil.isNotBlank(stock) && Integer.parseInt(stock) > 0) { int realStock = Integer.parseInt(stock) - 1; redisTemplate.opsForValue().set("stock", realStock + ""); // jedis.set(key, val) log.info(String.format("扣减成功:[%s]", realStock)); } else { log.info("扣减失败,库存不足"); } return "success"; } }
使用Jemeter压测接口:
启动一个8060的应用:
启动一个8070的应用:
@RequestMapping("/operation_stock") public String operStock() { String lockKey = "product_001"; String clentId = UUID.randomUUID().toString(); try { // Boolean result = redisTemplate.opsForValue().setIfAbsent(lockKey, "testProduct"); // redisTemplate.expire(lockKey, 10, TimeUnit.SECONDS); redisTemplate.opsForValue().set(lockKey, clentId, 10, TimeUnit.SECONDS); String stock = (String) redisTemplate.opsForValue().get("stock");// jedis.get("stock") if (StringUtil.isNotBlank(stock) && Integer.parseInt(stock) > 0) { int realStock = Integer.parseInt(stock) - 1; redisTemplate.opsForValue().set("stock", realStock + ""); // jedis.set(key, val) log.info(String.format("扣减成功:[%s]", realStock)); } else { log.info("扣减失败,库存不足"); } } finally { // 防止锁永久失效的问题 if (clentId.equals(redisTemplate.opsForValue().get(lockKey))) { redisTemplate.delete(lockKey); } } return "success"; }
采用Redisson实现:
@Bean public Redisson redisson () { Config config = new Config(); config.useSingleServer().setAddress("redis://127.0.0.1:6379").setDatabase(0); return (Redisson) Redisson.create(config); }
/** * rediss分布式锁机制 * @return */ @RequestMapping("/operation/redission/stock") public String operationRedissionStock() { String lockKey = "lockKey"; RLock redissonLock = redisson.getLock(lockKey); try { redissonLock.lock(30, TimeUnit.SECONDS); String stock = (String) redisTemplate.opsForValue().get("stock");// jedis.get("stock") if (StringUtil.isNotBlank(stock) && Integer.parseInt(stock) > 0) { int realStock = Integer.parseInt(stock) - 1; redisTemplate.opsForValue().set("stock", realStock + ""); log.info(String.format("扣减成功:[%s]", realStock)); } else { log.info("扣减失败,库存不足"); } } finally { redissonLock.unlock(); } return "success"; }
Redission实现原理:
redission官网地址:
https://github.com/redisson/redisson/wiki/%E7%9B%AE%E5%BD%95