Redis數(shù)據(jù)結構存儲系統(tǒng):第二章:如何使用

Redis與SpringBoot整合:
在這里插入圖片描述
























第一步:在項目中引入

redis.clients jedis

第二步:將連接池和配置類創(chuàng)建好
在這里插入圖片描述








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進行業(yè)務開發(fā)

開始開發(fā)先說明redis key的命名規(guī)范,由于Redis不像數(shù)據(jù)庫表那樣有結構,其所有的數(shù)據(jù)全靠key進行索引,所以redis數(shù)據(jù)的可讀性,全依靠key。

企業(yè)中最常用的方式就是: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()+": 查詢數(shù)據(jù)##################### ##" );
                SkuInfo skuInfoDB = getSkuInfoDB(skuId);
                String skuInfoJsonStr = JSON.toJSONString(skuInfoDB);
                jedis.setex(skuKey,RedisConst.skuinfo_exp_sec,skuInfoJsonStr);
                System.err.println( Thread.currentThread().getName()+":數(shù)據(jù)庫更新完畢############### #####" );
                jedis.close();
                return skuInfoDB;
        }
}

以上基本實現(xiàn)使用緩存的方案。
高并發(fā)時可能會出現(xiàn)的問題:

但在高并發(fā)環(huán)境下還有如下三個問題。

如果redis宕機了,或者鏈接不上,怎么辦?
如果redis緩存在高峰期到期失效,在這個時刻請求會向雪崩一樣,直接訪問數(shù)據(jù)庫如何處理?
 如果用戶不停地查詢一條不存在的數(shù)據(jù),緩存沒有,數(shù)據(jù)庫也沒有,那么會出現(xiàn)什么情況,如何處理?

 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的數(shù)據(jù)
        String skuInfoStr = jedis.get("sku:"+skuId+":info");
        //Json格式轉成實體類類型
        skuInfo = JSON.parseObject(skuInfoStr, SkuInfo.class);
        //從db中取出sku的數(shù)據(jù)
        //緩存中沒有
        if(skuInfo == null){
            System.out.println(ip+"發(fā)現(xiàn)緩存中沒有"+skuId+"商品數(shù)據(jù),申請分布式鎖");
            // 拿到分布式鎖
            String OK = jedis.set("sku:" + skuId + ":lock", "1", "nx", "px", 10000);
            if(StringUtils.isBlank(OK)){
                System.out.println(ip+"分布式鎖申請失敗,三秒后開始自旋");
                // 緩存鎖被占用,等一會兒繼續(xù)申請
                try {
                    Thread.sleep(3000);//讓它等3秒
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                return item(skuId,ip);//自旋,這里沒有啟動新線程,item(skuId,ip);才會啟動新線程
            }else{
                System.out.println(ip+"分布式鎖申請成功,訪問數(shù)據(jù)庫");
                // 拿到緩存鎖,可以訪問數(shù)據(jù)庫
                skuInfo = getSkuInfo(skuId);
            }
            System.out.println(ip+"成功訪問數(shù)據(jù)庫后,歸還鎖,將"+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");

不太理解,大家可以看看前一章節(jié)的博客,或者去官網(wǎng)查看
在這里插入圖片描述

我這里截下來一部分
在這里插入圖片描述

在這里插入圖片描述
在這里插入圖片描述