Redis数据结构存储系统:第二章:如何使用
Redis与SpringBoot整合:
第一步:在项目中引入
第二步:将连接池和配置类创建好
RedisUtil:
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
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;
}
}
RedisConfig:
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration//Spring容器中的注解
public class RedisConfig {
//读取配置文件中的redis的ip地址,@Value注入赋值
@Value("${spring.redis.host:disabled}")
private String host;
@Value("${spring.redis.port:0}")
private int port;
@Value("${spring.redis.database:0}")
private int database;
@Bean//将返回值给Spring,Spring容器中就有了RedisUtil(连接池)
public RedisUtil getRedisUtil(){
if(host.equals("disabled")){
return null;
}
RedisUtil redisUtil=new RedisUtil();
redisUtil.initPool(host,port,database);
return redisUtil;
}
}
在哪个项目中使用Redis就在application.properties中配置以下:
客户端登录:
cd /usr/local/redis/bin
./redis-cli -h 192.168.0.100 -p 6379
192.168.0.100:6379> ping
PONG
测试一下:
@RunWith(SpringRunner.class)
@SpringBootTest
public class GmallManageServiceApplicationTests {
@Autowired
RedisUtil redisUtil;
@Test
public void contextLoads() {
Jedis jedis = redisUtil.getJedis();
String ping = jedis.ping();
System.out.println(ping);
}
}
使用redis进行业务开发
开始开发先说明redis key的命名规范,由于Redis不像数据库表那样有结构,其所有的数据全靠key进行索引,所以redis数据的可读性,全依靠key。
企业中最常用的方式就是:object🆔field
比如:sku:1314:info
user:1092:password
拿一个之前的例子:
public SkuInfo getSkuInfo(String skuId){
Jedis jedis = redisUtil.getJedis();
String skuKey= RedisConst.sku_prefix+skuId+RedisConst.skuInfo_suffix;
String skuInfoJson = jedis.get(skuKey);
if(skuInfoJson!=null ){
System.err.println( Thread.currentThread().getName()+":命中缓存" );
SkuInfo skuInfo = JSON.parseObject(skuInfoJson, SkuInfo.class);
jedis.close();
return skuInfo;
}else{
System.err.println( Thread.currentThread().getName()+":未命中缓存" );
System.err.println( Thread.currentThread().getName()+": 查询数据##################### ##" );
SkuInfo skuInfoDB = getSkuInfoDB(skuId);
String skuInfoJsonStr = JSON.toJSONString(skuInfoDB);
jedis.setex(skuKey,RedisConst.skuinfo_exp_sec,skuInfoJsonStr);
System.err.println( Thread.currentThread().getName()+":数据库更新完毕############### #####" );
jedis.close();
return skuInfoDB;
}
}
以上基本实现使用缓存的方案。
高并发时可能会出现的问题:
但在高并发环境下还有如下三个问题。
如果redis宕机了,或者链接不上,怎么办?
如果redis缓存在高峰期到期失效,在这个时刻请求会向雪崩一样,直接访问数据库如何处理?
如果用户不停地查询一条不存在的数据,缓存没有,数据库也没有,那么会出现什么情况,如何处理?
public SkuInfo getSkuInfo(String skuId){
SkuInfo skuInfo = null;
try {
Jedis jedis = redisUtil.getJedis();
String skuInfoKey = ManageConst.SKUKEY_PREFIX + skuId + ManageConst.SKUKEY_SUFFIX;
String skuInfoJson = jedis.get(skuInfoKey);
if (skuInfoJson == null || skuInfoJson.length() == 0) {
System.err.println(Thread.currentThread().getName()+"缓存未命中!");
String skuLockKey = ManageConst.SKUKEY_PREFIX + skuId + ManageConst.SKULOCK_SUFFIX;
String lock = jedis.set(skuLockKey, "OK", "NX", "PX", ManageConst.SKULOCK_EXPIRE_PX);
if ("OK".equals(lock) ){
System.err.println(Thread.currentThread().getName()+"获得分布式锁!");
skuInfo = getSkuInfoFromDB(skuId);
if(skuInfo==null){
jedis.setex(skuInfoKey, ManageConst.SKUKEY_TIMEOUT, "empty");
return null;
}
String skuInfoJsonNew = JSON.toJSONString(skuInfo);
jedis.setex(skuInfoKey, ManageConst.SKUKEY_TIMEOUT, skuInfoJsonNew);
jedis.close();
return skuInfo;
}else{
System.err.println(Thread.currentThread().getName()+"未获得分布式锁,开始自旋!");
Thread.sleep(1000);
jedis.close();
return getSkuInfo( skuId);
}
} else if(skuInfoJson.equals("empty")){
return null;
} else {
System.err.println(Thread.currentThread().getName()+"缓存已命中!!!!!!!!!!!!!!!!!!!");
skuInfo = JSON.parseObject(skuInfoJson, SkuInfo.class);
jedis.close();
return skuInfo;
}
}catch (JedisConnectionException e){
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
return getSkuInfoFromDB(skuId);
}
最近写的也一样:
controller:
@RequestMapping("{skuId}.html")
public String item(@PathVariable("skuId") String skuId, ModelMap map, HttpServletRequest request){
SkuInfo skuInfo = skuService.item(skuId,request.getRemoteAddr());
}
service接口我就不写了
Serviceimpl:
@Override
public SkuInfo item(String skuId,String ip) {
System.out.println(ip+"访问"+skuId+"商品");
SkuInfo skuInfo = null;
//从redis获取redis的客户端jedis
Jedis jedis = redisUtil.getJedis();
// 从缓存中取出skuId的数据
String skuInfoStr = jedis.get("sku:"+skuId+":info");
//Json格式转成实体类类型
skuInfo = JSON.parseObject(skuInfoStr, SkuInfo.class);
//从db中取出sku的数据
//缓存中没有
if(skuInfo == null){
System.out.println(ip+"发现缓存中没有"+skuId+"商品数据,申请分布式锁");
// 拿到分布式锁
String OK = jedis.set("sku:" + skuId + ":lock", "1", "nx", "px", 10000);
if(StringUtils.isBlank(OK)){
System.out.println(ip+"分布式锁申请失败,三秒后开始自旋");
// 缓存锁被占用,等一会儿继续申请
try {
Thread.sleep(3000);//让它等3秒
} catch (InterruptedException e) {
e.printStackTrace();
}
return item(skuId,ip);//自旋,这里没有启动新线程,item(skuId,ip);才会启动新线程
}else{
System.out.println(ip+"分布式锁申请成功,访问数据库");
// 拿到缓存锁,可以访问数据库
skuInfo = getSkuInfo(skuId);
}
System.out.println(ip+"成功访问数据库后,归还锁,将"+skuId+"商品放入缓存");
jedis.del("sku:"+skuId+":lock");
}
//关闭redis客户端
jedis.close();
return skuInfo;
}
getSkuInfo方法:
public SkuInfo getSkuInfo(String skuId) {
SkuInfo skuInfo = new SkuInfo();
skuInfo.setId(skuId);
SkuInfo skuInfos = skuInfoMapper.selectOne(skuInfo);
SkuImage skuImage = new SkuImage();
skuImage.setSkuId(skuId);
List<SkuImage> skuImages = skuImageMapper.select(skuImage);
skuInfos.setSkuImageList(skuImages);
return skuInfos;
}
如果对于
String skuInfoStr = jedis.get(“sku:”+skuId+":info");
String OK = jedis.set(“sku:” + skuId + “:lock”, “1”, “nx”, “px”, 10000);
jedis.del(“sku:”+skuId+":lock");
不太理解,大家可以看看前一章节的博客,或者去官网查看
我这里截下来一部分