Fileutils工具类需要引入的依赖
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.3</version>
</dependency>
这里使用的是jedis,先贴下配置文件
@Configuration
public class RedisConfig {
//读取配置文件中的redis的ip地址
@Value("${spring.redis.host:0}")
private String host;
@Value("${spring.redis.port:0}")
private int port;
@Value("${spring.redis.database:0}")
private int database;
@Bean
public RedisUtil getRedisUtil() {
if (host.equals("disabled")) {
return null;
}
RedisUtil redisUtil = new RedisUtil();
redisUtil.initPool(host, port, database);
return redisUtil;
}
}
/*
RedisConfig负责在spring容器启动时自动注入,而RedisUtil就是被注入的工具类以供其他模块调用
*/
public class RedisUtil {
private JedisPool jedisPool;
public void initPool(String host, int port, int database) {
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxTotal(200);
poolConfig.setMaxIdle(30);
poolConfig.setBlockWhenExhausted(true);
poolConfig.setMaxWaitMillis(10 * 1000);
poolConfig.setTestOnBorrow(true);
jedisPool = new JedisPool(poolConfig, host, port, 20 * 1000);
}
public Jedis getJedis() {
Jedis jedis = jedisPool.getResource();
return jedis;
}
}
这里是properties配置文件
# redis链接地址
spring.redis.host=192.169.61.131
# redis端口号
spring.redis.port=6379
# redis数据库
spring.redis.database=0
lua脚本
if redis.call("get",KEYS[1]) == ARGV[1]
then
return redis.call("del",KEYS[1])
else
return 0
end
业务代码
//链接缓存
Jedis jedis = redisUtil.getJedis();
//查询缓存
String skuKey = "sku:" + skuId + ":info";
String skuJson = jedis.get(skuKey);
//如果缓存中有 if(skuJson!=null&&!skuJson.equals(""))
if (StringUtils.isNotBlank(skuJson)) {
pmsSkuInfo = JSON.parseObject(skuJson, PmsSkuInfo.class);
} else {
//如果缓存中没有
//设置分布式锁
String token = UUID.randomUUID().toString();
//锁有10秒的过期时间
String OK = jedis.set("sku:" + skuId + ":lock", token, "nx", "px", 10 * 1000);
if (StringUtils.isNotBlank(OK) && "OK".equals(OK)) {
//设置成功,有权在10秒的过期时间内访问数据库
//如果缓存中没有,查询mysql
pmsSkuInfo = getSkuByIdFromDb(skuId);
if (pmsSkuInfo != null) {
//mysql查询结果存入redis
jedis.set("sku:" + skuId + ":info", JSON.toJSONString(pmsSkuInfo));
} else {
//数据库中不存在该sku
//为了防止缓存穿透,null或空字符串值设置给redis
jedis.setex("sku:" + skuId + ":info", 60 * 3, JSON.toJSONString(""));
}
//在访问mysql后,将分布锁释放
String lockToken = jedis.get("sku:" + skuId + ":lock");
if (StringUtils.isNotBlank(lockToken) && lockToken.equals(token)) {
//可用lua脚本,在查询到key的同时删除该key,防止高并发下的意外发生
String script = Fileutils.readFileByLines("把lua脚本写在一个txt文件里,然后这里写lua脚本的路径");
//eval() 函数可计算某个字符串,并执行其中的的 JavaScript 代码。
jedis.eval(script, Arrays.asList(lockToken), Arrays.asList(token));
} else {
//设置失败,自旋(该线程在睡眠几秒后,重新尝试访问)
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return getSkuById(skuId, ip);
}
}
//关闭Jedis连接
jedis.close();
小知识:
为什么lua脚本就是原子性?
因为redis是单进程单线程模型,执行lua脚本的时候,不会有其它线程来插手