在现代的分布式系统中,订单系统是一个常见的业务场景。为了确保订单处理的高效性和一致性,我们可以使用Redis作为缓存和数据存储,并结合Lua脚本来实现原子操作。本文将介绍如何使用Spring Boot、Redis和Lua脚本实现一个简单的订单系统。
1. 项目概述
我们将实现一个简单的订单系统,主要功能包括:
- 创建订单
- 查询订单
- 取消订单
为了确保订单操作的原子性,我们将使用Redis的Lua脚本来处理订单的创建和取消操作。
2. 环境准备
在开始之前,确保你已经安装了以下环境:
- JDK 1.8+
- Maven 3.x
- Redis 5.x+
3. 创建Spring Boot项目
首先,使用Spring Initializr创建一个新的Spring Boot项目,添加以下依赖:
- Spring Web
- Spring Data Redis
生成的pom.xml
文件应该包含以下依赖:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
4. 配置Redis
在application.properties
文件中配置Redis连接信息:
spring.redis.host=localhost
spring.redis.port=6379
5. 创建订单实体类
创建一个简单的订单实体类Order
:
public class Order {
private String id;
private String userId;
private String productId;
private int quantity;
private double price;
private String status;
// Getters and Setters
}
6. 实现订单服务
接下来,我们实现订单服务OrderService
,使用Redis和Lua脚本来处理订单的创建和取消操作。
6.1 创建订单
我们使用Lua脚本来确保创建订单的原子性。Lua脚本如下:
local orderId = KEYS[1]
local userId = ARGV[1]
local productId = ARGV[2]
local quantity = tonumber(ARGV[3])
local price = tonumber(ARGV[4])
local orderKey = "order:" .. orderId
local userOrdersKey = "user:orders:" .. userId
-- 检查订单是否已存在
if redis.call("EXISTS", orderKey) == 1 then
return 0
end
-- 创建订单
redis.call("HSET", orderKey, "userId", userId, "productId", productId, "quantity", quantity, "price", price, "status", "CREATED")
redis.call("SADD", userOrdersKey, orderId)
return 1
在OrderService
中,我们使用RedisTemplate
来执行Lua脚本:
@Service
public class OrderService {
@Autowired
private StringRedisTemplate redisTemplate;
private static final String CREATE_ORDER_SCRIPT = "local orderId = KEYS[1]\n" +
"local userId = ARGV[1]\n" +
"local productId = ARGV[2]\n" +
"local quantity = tonumber(ARGV[3])\n" +
"local price = tonumber(ARGV[4])\n" +
"local orderKey = \"order:\" .. orderId\n" +
"local userOrdersKey = \"user:orders:\" .. userId\n" +
"if redis.call(\"EXISTS\", orderKey) == 1 then\n" +
" return 0\n" +
"end\n" +
"redis.call(\"HSET\", orderKey, \"userId\", userId, \"productId\", productId, \"quantity\", quantity, \"price\", price, \"status\", \"CREATED\")\n" +
"redis.call(\"SADD\", userOrdersKey, orderId)\n" +
"return 1";
public boolean createOrder(Order order) {
DefaultRedisScript<Long> script = new DefaultRedisScript<>(CREATE_ORDER_SCRIPT, Long.class);
List<String> keys = Collections.singletonList(order.getId());
String[] args = {order.getUserId(), order.getProductId(), String.valueOf(order.getQuantity()), String.valueOf(order.getPrice())};
Long result = redisTemplate.execute(script, keys, args);
return result != null && result == 1;
}
}
6.2 取消订单
同样地,我们使用Lua脚本来确保取消订单的原子性。Lua脚本如下:
local orderId = KEYS[1]
local userId = ARGV[1]
local orderKey = "order:" .. orderId
local userOrdersKey = "user:orders:" .. userId
-- 检查订单是否存在
if redis.call("EXISTS", orderKey) == 0 then
return 0
end
-- 取消订单
redis.call("HSET", orderKey, "status", "CANCELLED")
redis.call("SREM", userOrdersKey, orderId)
return 1
在OrderService
中,我们使用RedisTemplate
来执行Lua脚本:
@Service
public class OrderService {
@Autowired
private StringRedisTemplate redisTemplate;
private static final String CANCEL_ORDER_SCRIPT = "local orderId = KEYS[1]\n" +
"local userId = ARGV[1]\n" +
"local orderKey = \"order:\" .. orderId\n" +
"local userOrdersKey = \"user:orders:\" .. userId\n" +
"if redis.call(\"EXISTS\", orderKey) == 0 then\n" +
" return 0\n" +
"end\n" +
"redis.call(\"HSET\", orderKey, \"status\", \"CANCELLED\")\n" +
"redis.call(\"SREM\", userOrdersKey, orderId)\n" +
"return 1";
public boolean cancelOrder(String orderId, String userId) {
DefaultRedisScript<Long> script = new DefaultRedisScript<>(CANCEL_ORDER_SCRIPT, Long.class);
List<String> keys = Collections.singletonList(orderId);
String[] args = {userId};
Long result = redisTemplate.execute(script, keys, args);
return result != null && result == 1;
}
}
6.3 查询订单
查询订单的操作相对简单,直接从Redis中获取订单信息即可:
@Service
public class OrderService {
@Autowired
private StringRedisTemplate redisTemplate;
public Order getOrder(String orderId) {
String orderKey = "order:" + orderId;
Map<Object, Object> orderMap = redisTemplate.opsForHash().entries(orderKey);
if (orderMap.isEmpty()) {
return null;
}
Order order = new Order();
order.setId(orderId);
order.setUserId((String) orderMap.get("userId"));
order.setProductId((String) orderMap.get("productId"));
order.setQuantity(Integer.parseInt((String) orderMap.get("quantity")));
order.setPrice(Double.parseDouble((String) orderMap.get("price")));
order.setStatus((String) orderMap.get("status"));
return order;
}
}
7. 创建控制器
最后,我们创建一个简单的控制器OrderController
来处理HTTP请求:
@RestController
@RequestMapping("/orders")
public class OrderController {
@Autowired
private OrderService orderService;
@PostMapping
public ResponseEntity<String> createOrder(@RequestBody Order order) {
boolean result = orderService.createOrder(order);
if (result) {
return ResponseEntity.ok("Order created successfully");
} else {
return ResponseEntity.status(HttpStatus.CONFLICT).body("Order already exists");
}
}
@GetMapping("/{orderId}")
public ResponseEntity<Order> getOrder(@PathVariable String orderId) {
Order order = orderService.getOrder(orderId);
if (order != null) {
return ResponseEntity.ok(order);
} else {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(null);
}
}
@PostMapping("/{orderId}/cancel")
public ResponseEntity<String> cancelOrder(@PathVariable String orderId, @RequestParam String userId) {
boolean result = orderService.cancelOrder(orderId, userId);
if (result) {
return ResponseEntity.ok("Order cancelled successfully");
} else {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body("Order not found");
}
}
}
8. 测试
启动Spring Boot应用程序后,可以使用Postman或curl等工具测试API:
- 创建订单:
POST /orders
- 查询订单:
GET /orders/{orderId}
- 取消订单:
POST /orders/{orderId}/cancel?userId={userId}
9. 总结
通过本文,我们实现了一个简单的订单系统,使用Redis作为数据存储,并结合Lua脚本来确保订单操作的原子性。Spring Boot提供了与Redis的便捷集成,使得开发过程更加高效。希望本文对你理解如何在Spring Boot项目中使用Redis和Lua脚本有所帮助。