title: Redission
date: 2021-05-04 23:18:15
tags: Redis
Redis 第二篇 Redission
上篇在写客户端的时候提到了我用的比较多的Redission,这节就顺着整理一下。
spring-boot-data-redis 默认使用 Lettuce 客户端操作数据。但Reddissin 很强大,它提供的功能远远超出了一个 Redis 客户端的范畴,使用它来替换默认的 Lettuce。在可以使用基本 Redis 功能的同时,也能使用它提供的一些高级服务:
- 远程调用
- 分布式锁
- 分布式对象、容器
简单讲一下分布式:分布式结构就是将一个完整的系统,按照业务功能,拆分成一个个独立的子系统,在分布式结构中,每个子系统就被称为“服务”。(演变过程:单机结构 -> 集群结构 -> 分布式结构)
引入依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.13.0</version>
</dependency>
</dependencies>
application.properties配置
//redis启动主机ip
spring.redis.host=
//redis端口号
spring.redis.port=
//redis登录密码
spring.redis.password=
两个常用应用
一.分布式ID
ID 是数据的唯一标识,传统的做法是利用 UUID 和数据库的自增 ID。
但由于 UUID 是无序的,不能附带一些其他信息。比如需要生成员工号,这个一般是用数据库递增, UUID 是无法完成这个需求的。
再来说自增 ID,随着业务的发展,数据量会越来越大,需要对数据进行分表,甚至分库。分表后每个表的数据会按自己的节奏来自增,这样会造成 ID 冲突,这时就需要一个单独的机制来负责生成唯一 ID。
举例:淘宝的订单号
代码实现
import org.redisson.api.RAtomicLong;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.TimeUnit;
@Controller
public class AutoIdController {
@Autowired
RedissonClient redissonClient;
@GetMapping("/getautoid")
@ResponseBody
public String getAutoId() {
//格式化格式为年月日
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyyMMdd");
//获取当前时间
String now = LocalDate.now().format(dateTimeFormatter);
//通过redis的自增获取序号
RAtomicLong atomicLong = redissonClient.getAtomicLong(now);
atomicLong.expire(1, TimeUnit.DAYS);
//拼装订单号
return now + "" + atomicLong.incrementAndGet();
}
}
运行结果
202105041
嫌不够长,还可以这样String.format("%08d", xxx);%04d 表示输出时指定格式为使用 0 在左侧补齐至 8 位。
return now + "" + String.format("%08d",atomicLong.incrementAndGet());
运行结果
202105040000002
二.分布式锁
锁,在 Java 中 synchronized 关键字很常见,这些都是本地锁,只能解决一台服务器并发问题。
但是随着业务量不断增大,单机结构不满足那么大的访问量,需要变成集群或者分布式结构,因此无法保证某个数据的改变是同一台服务器操作的。我们需要的是一个能锁所有服务器的锁,这时就需要分布式锁。
实现Redis分布 三步
取得锁
//CUSTOM_NAME 自定义锁名称字符串,一般是跟业务相关的名称
//Rlock 继承于 java.util.concurrent.locks.Lock;(又是Lock类!!)
RLock rLock = redissonClient.getLock("CUSTOM_NAME")
上锁
/**
* 上锁过程
* 两种常用上锁方式tryLock()或者lock()
*/
rLock.tryLock();
解锁(有上锁就必须解锁,否则会导致死锁,系统也就卡死了。)
//解锁
rLock.unlock();
代码实操,模拟商品购买
`package com.example.demo;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;
@Controller
public class ProductController {
@Autowired
RedissonClient redissonClient;
@Autowired
StringRedisTemplate stringRedisTemplate;
private static List<Product> products = new ArrayList<>();
private static Logger logger = LoggerFactory.getLogger(ProductController.class);
/**
* 初始化数据
*/
@PostConstruct
private void init() {
products.add(new Product("1"));
products.add(new Product("2"));
}
@PostMapping("/purchase")
@ResponseBody
public String purchase() {
//获取分布式锁
RLock transferLock = redissonClient.getLock("PURCHASE");
transferLock.lock();
//业务逻辑卸载try...catch中 ,finally最后一定要释放锁
try {
//尝试获取锁
Product product = findById("1"); //为了方便测试,直接写死,实际商品代码应有用户post
if (product.getStock() < 1) {
return "商品已经卖完啦!!!";
}
product.setStock(product.getStock() - 1);
updateProduct(product);
return "商品购买成功!!!";
} catch (Exception e) {
logger.error("",e);
} finally {
// 无论是否出现异常,一定解锁
transferLock.unlock();
}
return "商品购买失败";
}
/**
* 根据id查询商品
*
* @param id 唯一id
* @return Product
*/
private Product findById(String id) {
for (Product product : products) {
if (product.getId().equals(id)) {
return product;
}
}
return null;
}
private Product updateProduct(Product product) {
for (int i = 0; i < products.size(); i++) {
if (products.get(i).getId().equals(product.getId())) {
products.set(i, product);
return product;
}
}
return null;
}
}
Product类
package com.youkeda.app.model;
/**
* TODO
*
* @author zr
* @date 2020/6/2, 周二
*/
public class Product {
/**
* 商品唯一Id
*/
private String id;
/**
* 库存
*/
private Long stock = 2L; //方便测试,请求到第三次时应该为购买失败
public Product(final String id) {
this.id = id;
}
public String getId() {
return id;
}
public void setId(final String id) {
this.id = id;
}
public Long getStock() {
return stock;
}
public void setStock(final Long stock) {
this.stock = stock;
}
}
请求三次时