Gemini永久会员 Redis Lua 脚本 Java 完整 Demo
·
Redis Lua 脚本 Java 完整 Demo
- 项目依赖配置
Maven pom.xml
<dependencies>
<!-- Redis客户端 -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>4.3.1</version>
</dependency>
<!-- 连接池 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.11.1</version>
</dependency>
<!-- 可选:JSON处理 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.14.2</version>
</dependency>
<!-- 可选:Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.28</version>
<scope>provided</scope>
</dependency>
</dependencies>
Gradle build.gradle
dependencies {
implementation 'redis.clients:jedis:4.3.1'
implementation 'org.apache.commons:commons-pool2:2.11.1'
implementation 'com.fasterxml.jackson.core:jackson-databind:2.14.2'
compileOnly 'org.projectlombok:lombok:1.18.28'
annotationProcessor 'org.projectlombok:lombok:1.18.28'
}
- Redis 配置类
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import java.time.Duration;
public class RedisConfig {
private static JedisPool jedisPool;
static {
initPool();
}
private static void initPool() {
JedisPoolConfig poolConfig = new JedisPoolConfig();
// 连接池配置
poolConfig.setMaxTotal(20); // 最大连接数
poolConfig.setMaxIdle(10); // 最大空闲连接
poolConfig.setMinIdle(5); // 最小空闲连接
poolConfig.setMaxWait(Duration.ofMillis(3000)); // 最大等待时间
poolConfig.setTestOnBorrow(true); // 借出时测试连接
poolConfig.setTestOnReturn(true); // 归还时测试连接
poolConfig.setTestWhileIdle(true); // 空闲时测试连接
// 创建连接池
jedisPool = new JedisPool(poolConfig, "localhost", 6379);
// 可选的:添加关闭钩子
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
if (jedisPool != null && !jedisPool.isClosed()) {
jedisPool.close();
}
}));
}
public static Jedis getJedis() {
return jedisPool.getResource();
}
public static void returnJedis(Jedis jedis) {
if (jedis != null) {
jedis.close();
}
}
public static JedisPool getJedisPool() {
return jedisPool;
}
}
- Lua 脚本管理类
import redis.clients.jedis.Jedis;
import redis.clients.jedis.exceptions.JedisException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
/**
* Lua脚本管理器
*/
public class LuaScriptManager {
// 存储脚本的SHA值
private static final Map<String, String> scriptCache = new HashMap<>();
/**
* 加载并缓存Lua脚本
*/
public static String loadScript(Jedis jedis, String scriptName, String scriptContent) {
try {
String sha = jedis.scriptLoad(scriptContent);
scriptCache.put(scriptName, sha);
System.out.println("脚本加载成功: " + scriptName + ", SHA: " + sha);
return sha;
} catch (JedisException e) {
System.err.println("加载脚本失败: " + scriptName);
e.printStackTrace();
return null;
}
}
/**
* 执行Lua脚本(自动处理脚本不存在的情况)
*/
public static Object executeScript(Jedis jedis, String scriptName,
String scriptContent,
int keyCount, String... params) {
try {
String sha = scriptCache.get(scriptName);
if (sha == null) {
// 如果SHA不存在,先加载脚本
sha = loadScript(jedis, scriptName, scriptContent);
if (sha == null) {
throw new RuntimeException("脚本加载失败: " + scriptName);
}
}
try {
// 使用EVALSHA执行
return jedis.evalsha(sha, keyCount, params);
} catch (JedisException e) {
if (e.getMessage().contains("NOSCRIPT")) {
// 如果脚本不存在,重新加载并执行
System.out.println("脚本不存在,重新加载: " + scriptName);
sha = loadScript(jedis, scriptName, scriptContent);
return jedis.evalsha(sha, keyCount, params);
}
throw e;
}
} catch (JedisException e) {
System.err.println("执行脚本失败: " + scriptName);
e.printStackTrace();
return null;
}
}
/**
* 清除脚本缓存
*/
public static void clearScriptCache() {
scriptCache.clear();
}
/**
* 获取缓存的脚本SHA
*/
public static String getScriptSha(String scriptName) {
return scriptCache.get(scriptName);
}
}
- Lua 脚本常量定义
/**
* Lua脚本常量
*/
public class LuaScripts {
// 1. 原子递增并设置过期时间
public static final String INCR_WITH_EXPIRE =
"local current = redis.call('GET', KEYS[1])\n" +
"if current == false then\n" +
" current = tonumber(ARGV[1])\n" +
" redis.call('SET', KEYS[1], current)\n" +
"else\n" +
" current = tonumber(current) + 1\n" +
" redis.call('SET', KEYS[1], current)\n" +
"end\n" +
"\n" +
"if tonumber(ARGV[2]) > 0 then\n" +
" redis.call('EXPIRE', KEYS[1], ARGV[2])\n" +
"end\n" +
"\n" +
"return current";
// 2. 分布式锁 - 获取锁
public static final String DISTRIBUTED_LOCK_ACQUIRE =
"local result = redis.call('SET', KEYS[1], ARGV[1], 'NX', 'PX', ARGV[2])\n" +
"if result then\n" +
" return 1\n" +
"else\n" +
" return 0\n" +
"end";
// 3. 分布式锁 - 释放锁
public static final String DISTRIBUTED_LOCK_RELEASE =
"if redis.call('GET', KEYS[1]) == ARGV[1] then\n" +
" return redis.call('DEL', KEYS[1])\n" +
"else\n" +
" return 0\n" +
"end";
// 4. 滑动窗口限流
public static final String SLIDING_WINDOW_RATE_LIMIT =
"local key = KEYS[1]\n" +
"local window = tonumber(ARGV[1]) * 1000 -- 转换为毫秒\n" +
"local limit = tonumber(ARGV[2])\n" +
"\n" +
"local currentTime = redis.call('TIME')\n" +
"local now = tonumber(currentTime[1]) * 1000 + math.floor(tonumber(currentTime[2]) / 1000)\n" +
"\n" +
"local clearBefore = now - window\n" +
"redis.call('ZREMRANGEBYSCORE', key, 0, clearBefore)\n" +
"\n" +
"local requestCount = redis.call('ZCARD', key)\n" +
"\n" +
"if requestCount < limit then\n" +
" redis.call('ZADD', key, now, now)\n" +
" redis.call('EXPIRE', key, math.floor(window / 1000))\n" +
" return 1 -- 允许通过\n" +
"else\n" +
" return 0 -- 限流\n" +
"end";
// 5. 购物车操作
public static final String SHOPPING_CART_OPERATION =
"local cartKey = KEYS[1]\n" +
"local operation = ARGV[1]\n" +
"local itemId = ARGV[2]\n" +
"local quantity = tonumber(ARGV[3])\n" +
"\n" +
"if operation == 'add' then\n" +
" local current = redis.call('HGET', cartKey, itemId)\n" +
" if current then\n" +
" quantity = quantity + tonumber(current)\n" +
" end\n" +
" redis.call('HSET', cartKey, itemId, quantity)\n" +
" \n" +
"elseif operation == 'remove' then\n" +
" redis.call('HDEL', cartKey, itemId)\n" +
" \n" +
"elseif operation == 'update' then\n" +
" if quantity <= 0 then\n" +
" redis.call('HDEL', cartKey, itemId)\n" +
" else\n" +
" redis.call('HSET', cartKey, itemId, quantity)\n" +
" end\n" +
" \n" +
"elseif operation == 'clear' then\n" +
" redis.call('DEL', cartKey)\n" +
"end\n" +
"\n" +
"-- 返回购物车总商品数\n" +
"local items = redis.call('HGETALL', cartKey)\n" +
"local totalItems = 0\n" +
"for i = 2, #items, 2 do\n" +
" totalItems = totalItems + tonumber(items[i])\n" +
"end\n" +
"\n" +
"return totalItems";
// 6. CAS操作(Compare and Set)
public static final String COMPARE_AND_SET =
"local oldValue = redis.call('GET', KEYS[1])\n" +
"if oldValue == ARGV[1] then\n" +
" redis.call('SET', KEYS[1], ARGV[2])\n" +
" return 1\n" +
"else\n" +
" return 0\n" +
"end";
// 7. 批量设置键值对
public static final String BATCH_SET =
"local results = {}\n" +
"for i = 1, #KEYS do\n" +
" redis.call('SET', KEYS[i], ARGV[i])\n" +
" redis.call('EXPIRE', KEYS[i], ARGV[#ARGV])\n" +
" table.insert(results, 1)\n" +
"end\n" +
"return results";
}
- 业务服务类
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Response;
import redis.clients.jedis.Transaction;
import redis.clients.jedis.params.SetParams;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
/**
* Redis Lua脚本业务服务类
*/
public class RedisLuaService {
private static final String LOCK_PREFIX = "lock:";
private static final String RATE_LIMIT_PREFIX = "rate_limit:";
private static final String CART_PREFIX = "cart:";
/**
* 1. 原子递增并设置过期时间
*/
public Long incrementWithExpire(String key, long initialValue, long expireSeconds) {
try (Jedis jedis = RedisConfig.getJedis()) {
Object result = LuaScriptManager.executeScript(
jedis,
"INCR_WITH_EXPIRE",
LuaScripts.INCR_WITH_EXPIRE,
1, // key个数
key,
String.valueOf(initialValue),
String.valueOf(expireSeconds)
);
return result != null ? Long.parseLong(result.toString()) : null;
}
}
/**
* 2. 获取分布式锁
*/
public boolean acquireDistributedLock(String lockKey, long expireMillis) {
try (Jedis jedis = RedisConfig.getJedis()) {
String lockValue = UUID.randomUUID().toString();
Object result = LuaScriptManager.executeScript(
jedis,
"DISTRIBUTED_LOCK_ACQUIRE",
LuaScripts.DISTRIBUTED_LOCK_ACQUIRE,
1,
LOCK_PREFIX + lockKey,
lockValue,
String.valueOf(expireMillis)
);
if (result != null && Long.parseLong(result.toString()) == 1) {
// 成功获取锁,保存锁的值用于后续释放
ThreadLocalLockHolder.setLockValue(lockValue);
return true;
}
return false;
}
}
/**
* 2.1 释放分布式锁
*/
public boolean releaseDistributedLock(String lockKey) {
try (Jedis jedis = RedisConfig.getJedis()) {
String lockValue = ThreadLocalLockHolder.getLockValue();
if (lockValue == null) {
return false;
}
Object result = LuaScriptManager.executeScript(
jedis,
"DISTRIBUTED_LOCK_RELEASE",
LuaScripts.DISTRIBUTED_LOCK_RELEASE,
1,
LOCK_PREFIX + lockKey,
lockValue
);
if (result != null && Long.parseLong(result.toString()) == 1) {
ThreadLocalLockHolder.clear();
return true;
}
return false;
}
}
/**
* 3. 滑动窗口限流
*/
public boolean slidingWindowRateLimit(String resource, String key,
int windowSeconds, int maxRequests) {
try (Jedis jedis = RedisConfig.getJedis()) {
String rateLimitKey = RATE_LIMIT_PREFIX + resource + ":" + key;
Object result = LuaScriptManager.executeScript(
jedis,
"SLIDING_WINDOW_RATE_LIMIT",
LuaScripts.SLIDING_WINDOW_RATE_LIMIT,
1,
rateLimitKey,
String.valueOf(windowSeconds),
String.valueOf(maxRequests)
);
return result != null && Long.parseLong(result.toString()) == 1;
}
}
/**
* 4. 购物车操作
*/
public Long shoppingCartOperation(String userId, String operation,
String itemId, Integer quantity) {
try (Jedis jedis = RedisConfig.getJedis()) {
String cartKey = CART_PREFIX + userId;
Object result = LuaScriptManager.executeScript(
jedis,
"SHOPPING_CART_OPERATION",
LuaScripts.SHOPPING_CART_OPERATION,
1,
cartKey,
operation,
itemId,
String.valueOf(quantity)
);
return result != null ? Long.parseLong(result.toString()) : null;
}
}
/**
* 5. CAS操作
*/
public boolean compareAndSet(String key, String expectedValue, String newValue) {
try (Jedis jedis = RedisConfig.getJedis()) {
Object result = LuaScriptManager.executeScript(
jedis,
"COMPARE_AND_SET",
LuaScripts.COMPARE_AND_SET,
1,
key,
expectedValue,
newValue
);
return result != null && Long.parseLong(result.toString()) == 1;
}
}
/**
* 6. 批量设置键值对
*/
public List<Long> batchSetWithExpire(List<String> keys, List<String> values, long expireSeconds) {
try (Jedis jedis = RedisConfig.getJedis()) {
// 准备参数
String[] params = new String[keys.size() + 1];
for (int i = 0; i < values.size(); i++) {
params[i] = values.get(i);
}
params[params.length - 1] = String.valueOf(expireSeconds);
Object result = LuaScriptManager.executeScript(
jedis,
"BATCH_SET",
LuaScripts.BATCH_SET,
keys.size(),
keys.toArray(new String[0]),
params
);
if (result instanceof List) {
@SuppressWarnings("unchecked")
List<Long> list = (List<Long>) result;
return list;
}
return Collections.emptyList();
}
}
/**
* 7. 使用管道和Lua脚本组合
*/
public List<Object> pipelineWithLua() {
try (Jedis jedis = RedisConfig.getJedis()) {
// 开启管道
jedis.pipelined();
// 执行多个操作
Transaction transaction = jedis.multi();
// 操作1: 设置键值
transaction.set("key1", "value1");
// 操作2: 执行Lua脚本
String luaScript = "return redis.call('GET', KEYS[1])";
transaction.eval(luaScript, 1, "key1");
// 操作3: 增加计数器
transaction.incr("counter");
// 执行事务
List<Object> results = transaction.exec();
return results;
}
}
/**
* 锁值持有器(ThreadLocal)
*/
private static class ThreadLocalLockHolder {
private static final ThreadLocal<String> lockValueHolder = new ThreadLocal<>();
public static void setLockValue(String value) {
lockValueHolder.set(value);
}
public static String getLockValue() {
return lockValueHolder.get();
}
public static void clear() {
lockValueHolder.remove();
}
}
}
- 注解式分布式锁
import java.lang.annotation.*;
import java.util.concurrent.TimeUnit;
/**
* 分布式锁注解
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RedisLock {
/**
* 锁的key
*/
String key();
/**
* 锁的前缀
*/
String prefix() default "lock:";
/**
* 等待时间(默认不等待)
*/
long waitTime() default 0;
/**
* 等待时间单位
*/
TimeUnit waitTimeUnit() default TimeUnit.MILLISECONDS;
/**
* 锁的过期时间
*/
long expireTime() default 30000;
/**
* 过期时间单位
*/
TimeUnit expireTimeUnit() default TimeUnit.MILLISECONDS;
/**
* 获取锁失败时的错误信息
*/
String errorMessage() default "获取分布式锁失败";
}
/**
* 分布式锁切面
*/
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.UUID;
@Aspect
@Component
public class RedisLockAspect {
private final RedisLuaService redisLuaService;
public RedisLockAspect(RedisLuaService redisLuaService) {
this.redisLuaService = redisLuaService;
}
@Around("@annotation(redisLock)")
public Object around(ProceedingJoinPoint joinPoint, RedisLock redisLock) throws Throwable {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
// 生成锁的key
String lockKey = redisLock.prefix() + redisLock.key();
// 尝试获取锁
boolean acquired = false;
long waitTime = redisLock.waitTimeUnit().toMillis(redisLock.waitTime());
long startTime = System.currentTimeMillis();
while (System.currentTimeMillis() - startTime < waitTime) {
if (redisLuaService.acquireDistributedLock(
lockKey,
redisLock.expireTimeUnit().toMillis(redisLock.expireTime())
)) {
acquired = true;
break;
}
// 等待一段时间再重试
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("获取锁时被中断", e);
}
}
if (!acquired && waitTime > 0) {
// 如果是等待获取锁,超时后抛出异常
throw new RuntimeException(redisLock.errorMessage());
} else if (!acquired) {
// 如果不等待,直接返回null或抛出异常
return null;
}
try {
// 执行业务逻辑
return joinPoint.proceed();
} finally {
// 释放锁
redisLuaService.releaseDistributedLock(lockKey);
}
}
}
- 配置类(Spring Boot)
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.util.List;
@Configuration
public class RedisConfig {
/**
* 配置RedisTemplate
*/
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
// 使用Jackson序列化
GenericJackson2JsonRedisSerializer jacksonSerializer =
new GenericJackson2JsonRedisSerializer();
// Key使用String序列化
template.setKeySerializer(new StringRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
// Value使用JSON序列化
template.setValueSerializer(jacksonSerializer);
template.setHashValueSerializer(jacksonSerializer);
template.afterPropertiesSet();
return template;
}
/**
* 注册Lua脚本Bean
*/
@Bean
public RedisScript<Long> incrWithExpireScript() {
DefaultRedisScript<Long> script = new DefaultRedisScript<>();
script.setScriptText(LuaScripts.INCR_WITH_EXPIRE);
script.setResultType(Long.class);
return script;
}
@Bean
public RedisScript<Long> distributedLockAcquireScript() {
DefaultRedisScript<Long> script = new DefaultRedisScript<>();
script.setScriptText(LuaScripts.DISTRIBUTED_LOCK_ACQUIRE);
script.setResultType(Long.class);
return script;
}
@Bean
public RedisScript<Long> rateLimitScript() {
DefaultRedisScript<Long> script = new DefaultRedisScript<>();
script.setScriptText(LuaScripts.SLIDING_WINDOW_RATE_LIMIT);
script.setResultType(Long.class);
return script;
}
}
- Spring Boot 服务类
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Service;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
@Service
public class RedisLuaSpringService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private RedisScript<Long> incrWithExpireScript;
@Autowired
private RedisScript<Long> distributedLockAcquireScript;
@Autowired
private RedisScript<Long> rateLimitScript;
/**
* 使用Spring RedisTemplate执行Lua脚本
*/
public Long incrementWithExpireSpring(String key, long initialValue, long expireSeconds) {
return redisTemplate.execute(
incrWithExpireScript,
Arrays.asList(key),
String.valueOf(initialValue),
String.valueOf(expireSeconds)
);
}
/**
* Spring分布式锁
*/
public boolean tryLock(String lockKey, long expireTime, TimeUnit timeUnit) {
String lockValue = UUID.randomUUID().toString();
Boolean success = redisTemplate.opsForValue().setIfAbsent(
"lock:" + lockKey,
lockValue,
expireTime,
timeUnit
);
return Boolean.TRUE.equals(success);
}
/**
* 释放锁(使用Lua脚本确保原子性)
*/
public boolean releaseLock(String lockKey, String lockValue) {
String script =
"if redis.call('get', KEYS[1]) == ARGV[1] then\n" +
" return redis.call('del', KEYS[1])\n" +
"else\n" +
" return 0\n" +
"end";
Long result = redisTemplate.execute(
new DefaultRedisScript<>(script, Long.class),
Arrays.asList("lock:" + lockKey),
lockValue
);
return result != null && result == 1;
}
/**
* 滑动窗口限流(Spring版本)
*/
public boolean isAllowed(String key, int windowInSec, int maxRequests) {
Long result = redisTemplate.execute(
rateLimitScript,
Arrays.asList("rate_limit:" + key),
String.valueOf(windowInSec),
String.valueOf(maxRequests)
);
return result != null && result == 1;
}
}
- 测试类
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import javax.annotation.Resource;
import java.util.Arrays;
import java.util.List;
@SpringBootTest
public class RedisLuaDemoTest {
@Resource
private RedisLuaService redisLuaService;
@Resource
private RedisLuaSpringService redisLuaSpringService;
@Test
public void testIncrementWithExpire() {
System.out.println("=== 测试原子递增 ===");
Long result = redisLuaService.incrementWithExpire("test:counter", 0, 60);
System.out.println("递增结果: " + result);
}
@Test
public void testDistributedLock() {
System.out.println("=== 测试分布式锁 ===");
String lockKey = "order:123";
// 获取锁
boolean acquired = redisLuaService.acquireDistributedLock(lockKey, 30000);
System.out.println("获取锁结果: " + acquired);
if (acquired) {
try {
System.out.println("执行业务逻辑...");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 释放锁
boolean released = redisLuaService.releaseDistributedLock(lockKey);
System.out.println("释放锁结果: " + released);
}
}
}
@Test
public void testRateLimit() {
System.out.println("=== 测试限流器 ===");
String resource = "login";
String userId = "user123";
for (int i = 1; i <= 15; i++) {
boolean allowed = redisLuaService.slidingWindowRateLimit(
resource, userId, 60, 10
);
System.out.println("请求" + i + ": " + (allowed ? "允许" : "限流"));
if (!allowed) {
System.out.println("触发限流,等待重试...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
@Test
public void testShoppingCart() {
System.out.println("=== 测试购物车 ===");
String userId = "user123";
// 添加商品
Long total1 = redisLuaService.shoppingCartOperation(userId, "add", "item1", 2);
System.out.println("添加商品后总数: " + total1);
// 再次添加同一商品
Long total2 = redisLuaService.shoppingCartOperation(userId, "add", "item1", 3);
System.out.println("再次添加后总数: " + total2);
// 添加另一商品
Long total3 = redisLuaService.shoppingCartOperation(userId, "add", "item2", 1);
System.out.println("添加另一商品后总数: " + total3);
// 更新商品数量
Long total4 = redisLuaService.shoppingCartOperation(userId, "update", "item1", 1);
System.out.println("更新商品后总数: " + total4);
// 移除商品
Long total5 = redisLuaService.shoppingCartOperation(userId, "remove", "item2", 0);
System.out.println("移除商品后总数: " + total5);
}
@Test
public void testBatchSet() {
System.out.println("=== 测试批量设置 ===");
List<String> keys = Arrays.asList("batch:key1", "batch:key2", "batch:key3");
List<String> values = Arrays.asList("value1", "value2", "value3");
List<Long> results = redisLuaService.batchSetWithExpire(keys, values, 300);
System.out.println("批量设置结果: " + results);
}
@Test
public void testSpringVersion() {
System.out.println("=== 测试Spring版本 ===");
// 测试Spring版本的递增
Long result = redisLuaSpringService.incrementWithExpireSpring("spring:counter", 0, 60);
System.out.println("Spring递增结果: " + result);
// 测试Spring版本的限流
boolean allowed = redisLuaSpringService.isAllowed("api:test", 60, 5);
System.out.println("Spring限流结果: " + allowed);
}
}
/**
* 使用注解的分布式锁示例
*/
@Service
class OrderService {
@RedisLock(key = "'order:' + #orderId",
waitTime = 5000,
expireTime = 30000,
errorMessage = "订单处理中,请稍后重试")
public void processOrder(String orderId) {
// 业务逻辑
System.out.println("处理订单: " + orderId);
try {
Thread.sleep(2000); // 模拟耗时操作
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
@RedisLock(key = "'payment:' + #paymentId")
public void processPayment(String paymentId) {
// 支付处理逻辑
System.out.println("处理支付: " + paymentId);
}
}
- 性能监控工具类
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import java.util.HashMap;
import java.util.Map;
/**
* Redis性能监控工具
*/
public class RedisMonitor {
/**
* 监控Lua脚本执行性能
*/
public static class LuaScriptMonitor {
private final Map<String, ScriptStats> statsMap = new HashMap<>();
public void recordExecution(String scriptName, long executionTime, boolean success) {
ScriptStats stats = statsMap.computeIfAbsent(scriptName, k -> new ScriptStats());
stats.totalExecutions++;
stats.totalExecutionTime += executionTime;
if (success) {
stats.successCount++;
} else {
stats.failureCount++;
}
if (executionTime > stats.maxExecutionTime) {
stats.maxExecutionTime = executionTime;
}
if (stats.minExecutionTime == 0 || executionTime < stats.minExecutionTime) {
stats.minExecutionTime = executionTime;
}
}
public ScriptStats getStats(String scriptName) {
return statsMap.get(scriptName);
}
public void printStats() {
System.out.println("=== Lua脚本执行统计 ===");
statsMap.forEach((name, stats) -> {
System.out.printf("脚本: %s%n", name);
System.out.printf(" 总执行次数: %d%n", stats.totalExecutions);
System.out.printf(" 成功次数: %d%n", stats.successCount);
System.out.printf(" 失败次数: %d%n", stats.failureCount);
System.out.printf(" 平均执行时间: %.2fms%n",
stats.getAverageExecutionTime());
System.out.printf(" 最长执行时间: %dms%n", stats.maxExecutionTime);
System.out.printf(" 最短执行时间: %dms%n", stats.minExecutionTime);
System.out.println();
});
}
static class ScriptStats {
long totalExecutions;
long successCount;
long failureCount;
long totalExecutionTime; // 毫秒
long maxExecutionTime;
long minExecutionTime;
double getAverageExecutionTime() {
return totalExecutions > 0 ?
(double) totalExecutionTime / totalExecutions : 0;
}
}
}
/**
* 检查Redis服务器信息
*/
public static void checkRedisInfo() {
try (Jedis jedis = RedisConfig.getJedis()) {
String info = jedis.info();
System.out.println("=== Redis服务器信息 ===");
// 解析重要信息
String[] lines = info.split("\r?\n");
for (String line : lines) {
if (line.startsWith("redis_version") ||
line.startsWith("used_memory_human") ||
line.startsWith("connected_clients") ||
line.startsWith("instantaneous_ops_per_sec") ||
line.startsWith("total_commands_processed")) {
System.out.println(line);
}
}
}
}
}
- 最佳实践总结
11.1 使用建议
public class RedisLuaBestPractices {
/**
* 最佳实践示例
*/
public static class BestPractices {
// 1. 总是使用参数化查询,避免在脚本中拼接字符串
public void goodPractice1(Jedis jedis) {
// 好:使用参数传递
String script = "return redis.call('GET', KEYS[1])";
jedis.eval(script, 1, "key1");
// 不好:在脚本中拼接
// String badScript = "return redis.call('GET', 'key1')";
}
// 2. 限制脚本执行时间
public void goodPractice2() {
// Redis配置文件中设置:
// lua-time-limit 5000 # 脚本最大执行5秒
// 或者在代码中监控执行时间
long startTime = System.currentTimeMillis();
// 执行脚本...
long endTime = System.currentTimeMillis();
if (endTime - startTime > 5000) {
System.err.println("脚本执行时间过长!");
}
}
// 3. 使用SCRIPT LOAD和EVALSHA
public void goodPractice3(Jedis jedis, String script) {
// 首次加载脚本
String sha = jedis.scriptLoad(script);
// 后续使用SHA执行
try {
jedis.evalsha(sha, 1, "key1");
} catch (Exception e) {
// 如果脚本不存在,重新加载
if (e.getMessage().contains("NOSCRIPT")) {
sha = jedis.scriptLoad(script);
jedis.evalsha(sha, 1, "key1");
}
}
}
// 4. 避免在循环中执行Redis命令
public void goodPractice4(Jedis jedis) {
// 好:使用一个脚本完成所有操作
String script =
"for i = 1, 100 do\n" +
" redis.call('SET', 'key' .. i, 'value' .. i)\n" +
"end\n" +
"return 'done'";
// 不好:在Java循环中多次调用Redis
// for (int i = 1; i <= 100; i++) {
// jedis.set("key" + i, "value" + i); // 网络开销大
// }
}
// 5. 错误处理
public void goodPractice5(Jedis jedis) {
try {
Object result = jedis.eval("return redis.call('GET', KEYS[1])", 1, "key1");
// 处理结果
} catch (Exception e) {
// 记录日志
System.err.println("执行脚本失败: " + e.getMessage());
// 根据错误类型进行不同处理
if (e.getMessage().contains("BUSY")) {
// 脚本正在执行,重试或降级
} else if (e.getMessage().contains("NOSCRIPT")) {
// 脚本不存在,重新加载
} else {
// 其他错误
throw e;
}
}
}
}
}
- 完整示例:秒杀系统
import redis.clients.jedis.Jedis;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 秒杀系统示例
*/
public class SeckillDemo {
private static final String SECKILL_SCRIPT =
"local productKey = KEYS[1]\n" +
"local stockKey = KEYS[2]\n" +
"local orderKey = KEYS[3]\n" +
"local userId = ARGV[1]\n" +
"\n" +
"-- 检查库存\n" +
"local stock = tonumber(redis.call('GET', stockKey))\n" +
"if not stock or stock <= 0 then\n" +
" return 0 -- 库存不足\n" +
"end\n" +
"\n" +
"-- 检查是否已购买\n" +
"local hasBought = redis.call('SISMEMBER', orderKey, userId)\n" +
"if hasBought == 1 then\n" +
" return -1 -- 重复购买\n" +
"end\n" +
"\n" +
"-- 扣减库存\n" +
"stock = stock - 1\n" +
"redis.call('SET', stockKey, stock)\n" +
"\n" +
"-- 记录购买订单\n" +
"redis.call('SADD', orderKey, userId)\n" +
"\n" +
"-- 设置订单过期时间\n" +
"redis.call('EXPIRE', orderKey, 3600)\n" +
"\n" +
"return 1 -- 购买成功";
/**
* 初始化秒杀商品
*/
public static void initSeckillProduct(String productId, int stock) {
try (Jedis jedis = RedisConfig.getJedis()) {
jedis.set("seckill:stock:" + productId, String.valueOf(stock));
}
}
/**
* 执行秒杀
*/
public static int seckill(String productId, String userId) {
try (Jedis jedis = RedisConfig.getJedis()) {
String productKey = "seckill:product:" + productId;
String stockKey = "seckill:stock:" + productId;
String orderKey = "seckill:orders:" + productId;
Object result = jedis.eval(
SECKILL_SCRIPT,
3,
productKey,
stockKey,
orderKey,
userId
);
return result != null ? ((Long) result).intValue() : -2;
}
}
/**
* 并发测试秒杀
*/
public static void concurrentTest() throws InterruptedException {
String productId = "p001";
initSeckillProduct(productId, 100); // 100个库存
int threadCount = 200;
CountDownLatch latch = new CountDownLatch(threadCount);
ExecutorService executor = Executors.newFixedThreadPool(50);
System.out.println("开始并发秒杀测试...");
System.out.println("商品: " + productId + ", 库存: 100, 并发用户: " + threadCount);
for (int i = 0; i < threadCount; i++) {
String userId = "user" + i;
executor.execute(() -> {
try {
int result = seckill(productId, userId);
switch (result) {
case 1:
System.out.println(userId + ": 秒杀成功");
break;
case 0:
System.out.println(userId + ": 库存不足");
break;
case -1:
System.out.println(userId + ": 重复购买");
break;
default:
System.out.println(userId + ": 系统错误");
}
} finally {
latch.countDown();
}
});
}
latch.await();
executor.shutdown();
// 查看最终库存
try (Jedis jedis = RedisConfig.getJedis()) {
String stock = jedis.get("seckill:stock:" + productId);
System.out.println("\n秒杀结束,剩余库存: " + stock);
}
}
public static void main(String[] args) throws InterruptedException {
concurrentTest();
}
}
这个完整的 Java Demo 包含了:
- 基础配置:连接池配置、工具类
- Lua脚本管理:脚本加载、缓存、执行
- 常用场景实现:分布式锁、限流、购物车等
- Spring集成:Spring Boot配置和使用
- 测试示例:完整的测试用例
- 性能监控:监控脚本执行性能
- 最佳实践:使用建议和注意事项
- 完整案例:秒杀系统实现
你可以根据实际需求调整和扩展这些代码。
更多推荐

所有评论(0)