关于Redis

关于Redis

Redis是一款使用K-V结构的、基于内存实现数据存取的NoSQL非关系型数据库

使用Redis的主要目的是“缓存数据”,以提高查询数据的效率,对数据库也有一定的保护作用。

需要注意:缓存的数据可能存在“不一致”的问题,因为,如果修改了数据库(例如MySQL)中的数据,而缓存(例如Redis)中的数据没有一并更新,则缓存中的数据是不准确的!但是,并不是所有的场景都要求数据非常准确!

所以,使用Redis的前提条件:

  • 对数据的准确性要求不高
    • 例如:新浪微博的热搜排名、某个热门视频的播放量
  • 数据的修改频率不高
    • 例如:电商平台中的类别、电商平台中的品牌

反之,某些情况下是不应该使用Redis的,例如:在秒杀商品时,使用Redis记录一些频繁修改的数据!

在操作系统的终端下,通过redis-cli命令即可登录Redis控制台:

redis-cli

在Redis控制台中,使用setget命令就可以存取基本类型的数据:

set name liucangsong
get name

在Redis中,有5种典型数据类型:字符串、Hash、Set、zSet、List,在结合编程时,通常,只需要使用“字符串”即可,在程序中,非字符串类型的数据(例如对象、集合等)都会通过工具转换成JSON格式的字符串再存入到Redis中,后续,从Redis中取出的也是字符串,再通过工具转换成原本的类型(例如对象、集合等)即可。

1.1. Redis的简单操作

当已经安装Redis,并确保环境变量可用后,可以在命令提示符窗口(CMD)或终端(IDEA的Terminal,或MacOS/Linux的命令窗口)中执行相关命令。

在终端下,可以通过redis-cli登录Redis客户端:

redis-cli

在Redis客户端中,可以通过ping检测Redis是否正常工作,将得到PONG的反馈:

ping

在Redis客户端中,可以通过set命令向Redis中存入修改简单类型的数据:

set name jack

在Redis客户端中,可以通过get命令从Redis中取出简单类型的数据:

get name

如果使用的Key并不存在,使用get命令时,得到的结果将是(nil),等效于Java中的null

在Redis客户端中,可以通过keys命令检索Key:

keys *
keys a*

注意:默认情况下,Redis是单线程的,keys命令会执行整个Redis的检索,所以,执行时间可能较长,可能导致阻塞!

1.2. 在Spring Boot项目中读写Redis

1.2.1. 添加依赖

需要添加spring-boot-starter-data-redis依赖项:

<!-- Spring Data Redis:读写Redis -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

以上依赖项默认会连接localhost:6379,并且无用户名、无密码,所以,当你的Redis符合此配置,则不需要在application.properties 中添加任何配置就可以直接编程。

如果需要显式的配置,各配置项的属性名分别为:

  • spring.redis.host
  • spring.redis.port
  • spring.redis.username
  • spring.redis.password

1.2.2. 配置RedisTemplate

在使用以上依赖项实现Redis编程时,需要使用到的工具类型为RedisTemplate,调用此类的对象的方法,即可实现读写Redis中的数据。

在使用之前,应该先在配置类中使用@Bean方法创建RedisTemplate,并实现对RedisTemplate的基础配置,则在项目的根包下创建config.RedisConfiguration类:

package cn.tedu.csmall.product.config;

import lombok.extern.slf4j.Slf4j;
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.serializer.RedisSerializer;

import java.io.Serializable;

/**
 * Redis的配置类
 *
 * @author java@tedu.cn
 * @version 0.0.1
 */
@Slf4j
@Configuration
public class RedisConfiguration {
    
    public RedisConfiguration() {
        log.info("加载配置类:RedisConfiguration");
    }

    @Bean
    public RedisTemplate<String, Serializable> redisTemplate(
            RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Serializable> redisTemplate 
                = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        redisTemplate.setKeySerializer(RedisSerializer.string());
        redisTemplate.setValueSerializer(RedisSerializer.json());
        return redisTemplate;
    }

}

1.2.3. 使用ValueOperations读写一般值数据

使用RedisTemplate访问一般值(字符串、数值等)数据时,需要先获取ValueOperations对象,再调用此对象的API进行数据操作。

例如:测试向Redis中写入一个字符串:

@Autowired
RedisTemplate<String, Serializable> redisTemplate;

@Test
void testValueOpsSet() {
    ValueOperations<String, Serializable> ops = redisTemplate.opsForValue();
    String key = "username";
    String value = "admin";
    ops.set(key, value);
    log.debug("已经向Redis中写入Key={}且Value={}的数据!", key, value);
}

由于声明的RedisTemplate的值的泛型是Serializable,所以,从Redis中读取到的值的类型会是Serializable接口类型。

例如:测试从Redis中读取此前写入的字符串:

@Test
void testValueOpsGet() {
    ValueOperations<String, Serializable> ops = redisTemplate.opsForValue();
    // 从Redis中读取数据
    String key = "username";
    Serializable value = ops.get(key);
    log.debug("已经从Redis中读取Key={}的数据,Value={}", key, value);
}

由于配置RedisTemplate时,使用的值序列化器是JSON(redisTemplate.setValueSerializer(RedisSerializer.json());),所以,可以直接写入对象,会被自动处理为JSON格式的字符串。

另外,由于声明的RedisTemplate的值的泛型是Serializable,所以,写入的值的类型必须实现了Serializable接口。

例如:测试向Redis中写入一个对象:

@Test
void testValueOpsSetObject() {
    ValueOperations<String, Serializable> ops = redisTemplate.opsForValue();

    String key = "brand1";
    Brand brand = new Brand();
    brand.setId(1L);
    brand.setName("大白象");
    brand.setEnable(1);
    
    ops.set(key, brand);
    log.debug("已经向Redis中写入Key={}且Value={}的数据!", key, brand);
}

例如:测试从Redis中读取此前写入的对象:

@Test
void testValueOpsGetObject() {
    ValueOperations<String, Serializable> ops = redisTemplate.opsForValue();

    String key = "brand1";
    Serializable value = ops.get(key);
    log.debug("已经从Redis中读取Key={}的数据,Value={}", key, value);
    log.debug("读取到的值类型是:{}", value.getClass().getName());
    
    if (value instanceof Brand) {
        Brand brand = (Brand) value;
    	log.debug("将读取到的值类型转换为Brand类型,成功:{}", brand);
    } else {
        log.debug("读取到的值类型不是Brand类型,无法实现类型转换!");
    } 
}

1.2.4. 查询Redis中已有的Key

直接调用RedisTemplatekeys()方法,即可查询当前Redis中有哪些Key。

例如:测试查询Redis中所有的Key:

@Test
void testKeys() {
    Set<String> keys = redisTemplate.keys("*");
    for (String key : keys) {
        log.debug("{}", key);
    }
}

1.2.5. 删除Redis中的数据

删除数据时,不关心值的类型,只需要知道Key即可,所以,删除数据时直接调用RedisTemplatedelete()方法即可。

例如:测试删除Redis中的某个数据:

@Test
void testDelete() {
    String key = "name";
    Boolean result = redisTemplate.delete(key);
    log.debug("尝试删除Redis中Key={}的数据,操作结果为:{}", key, result);
}

提示:RedisTemplate的API中,还有批量删除的操作,例如(以下是RedisTemplate的部分源代码):

public Long delete(Collection<K> keys) {
    if (CollectionUtils.isEmpty(keys)) {
        return 0L;
    } else {
        byte[][] rawKeys = this.rawKeys(keys);
        return (Long)this.execute((connection) -> {
            return connection.del(rawKeys);
        }, true);
    }
}

1.2.6. 读写List列表数据

在操作List列表数据之前,需要先调用RedisTemplate对象的opsForList()方法,得到ListOperations对象,再进行列表数据的操作。

在存入列表数据时,ListOperations支持从左侧压栈来存入数据,或从右侧压栈来存入数据,这2者的区别如下图所示:

通常,从右侧压栈存入数据比较符合大多情况下的需求。

例如:测试写入列表数据:

@Test
void testRightPushList() {
    // push:压栈(存入数据)
    // pop:弹栈(拿走数据)
    // 使用RedisTemplate向Redis存入List数据:
    // 1. 需要调用 opsForList() 得到 ListOperations 对象
    // 2. ListOperations每次只能存入1个列表项数据
    List<Brand> brands = new ArrayList<>();
    for (int i = 1; i <= 8; i++) {
        Brand brand = new Brand();
        brand.setId(i + 0L);
        brand.setName("测试品牌" + i);
        brands.add(brand);
    }

    ListOperations<String, Serializable> opsForList = redisTemplate.opsForList();
    String key = "brandList";
    for (Brand brand : brands) {
        opsForList.rightPush(key, brand);
    }
}

调用ListOperations对象的size()方法即可获取列表的长度。

例如:获取列表的长度:

@Test
void testListSize() {
    ListOperations<String, Serializable> opsForList = redisTemplate.opsForList();
    String key = "brandList";
    Long size = opsForList.size(key);
    log.debug("列表 key={} 的长度(元素数量)为:{}", key, size);
}

如果需要读取数据,首先必须了解,Redis中的列表数据项即有正数的索引(下标),也有负数的索引(下标),正数的是从左侧第1位使用0开始向右顺序编号,而负数的是从右侧第1位使用-1并向左侧递减的编号,如下图所示:

使用ListOperationsrange()方法可以获取列表的区间段子列表。

例如:测试获取列表数据:

@Test
void testListRange() {
    ListOperations<String, Serializable> opsForList = redisTemplate.opsForList();
    String key = "brandList";
    long start = 0L;
    long end = -1L;
    List<Serializable> list = opsForList.range(key, start, end);
    for (Serializable serializable : list) {
        log.debug("列表项:{}", serializable);
    }
}

1.3. 封装Redis的读写

在当前项目中,品牌、类别这些数据应该是适合使用Redis的!

以缓存品牌数据为例,可以先在根包下创建repository.IBrandCacheRepository接口:

package cn.tedu.csmall.product.repository;

import cn.tedu.csmall.product.pojo.vo.BrandListItemVO;
import cn.tedu.csmall.product.pojo.vo.BrandStandardVO;

import java.util.List;
import java.util.Set;

/**
 * 处理品牌缓存数据的存储接口
 *
 * @author java@tedu.cn
 * @version 0.0.1
 */
public interface IBrandCacheRepository {

    /**
     * 将某个品牌数据保存到Redis中
     *
     * @param brandStandardVO 品牌数据
     */
    void save(BrandStandardVO brandStandardVO);

    /**
     * 从Redis中取出品牌数据
     *
     * @param id 品牌id
     * @return 匹配的品牌数据,如果Redis中没有匹配的数据,将返回null
     */
    BrandStandardVO get(Long id);

    /**
     * 将品牌列表数据保存到Redis中
     *
     * @param brandList 品牌列表数据
     */
    void saveList(List<BrandListItemVO> brandList);

    /**
     * 从Redis中取出品牌列表数据
     *
     * @return 品牌列表数据
     */
    List<BrandListItemVO> getList();

    /**
     * 获取所有品牌缓存数据的Key
     *
     * @return 所有品牌缓存数据的Key
     */
    Set<String> getAllKeys();

    /**
     * 删除所有缓存的品牌数据
     *
     * @param keys 所有缓存的品牌数据的Key的集合
     * @return 删除的数据的数量
     */
    Long deleteAll(Set<String> keys);

}

然后,在根包下创建repository.impl.BrandCacheRepositoryImpl类,实现以上接口:

package cn.tedu.csmall.product.repository.impl;

import cn.tedu.csmall.product.pojo.entity.Brand;
import cn.tedu.csmall.product.pojo.vo.BrandListItemVO;
import cn.tedu.csmall.product.pojo.vo.BrandStandardVO;
import cn.tedu.csmall.product.repository.IBrandCacheRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.ListOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Repository;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

/**
 * 处理品牌缓存数据的存储实现类
 *
 * @author java@tedu.cn
 * @version 0.0.1
 */
@Slf4j
@Repository
public class BrandCacheRepositoryImpl implements IBrandCacheRepository {

    @Autowired
    private RedisTemplate<String, Serializable> redisTemplate;

    public static final String KEY_ITEM_PREFIX = "brand:item:";
    public static final String KEY_LIST = "brand:list";

    public BrandCacheRepositoryImpl() {
        log.debug("创建处理缓存的对象:BrandCacheRepositoryImpl");
    }

    @Override
    public void save(BrandStandardVO brandStandardVO) {
        ValueOperations<String, Serializable> opsForValue = redisTemplate.opsForValue();
        String key = getItemKey(brandStandardVO.getId());
        opsForValue.set(key, brandStandardVO);
    }

    @Override
    public BrandStandardVO get(Long id) {
        ValueOperations<String, Serializable> opsForValue = redisTemplate.opsForValue();
        String key = getItemKey(id);
        Serializable value = opsForValue.get(key);
        if (value != null) {
            if (value instanceof BrandStandardVO) {
                return (BrandStandardVO) value;
            }
        }
        return null;
    }

    @Override
    public void saveList(List<BrandListItemVO> brandList) {
        ListOperations<String, Serializable> opsForList = redisTemplate.opsForList();
        for (BrandListItemVO brand : brandList) {
            opsForList.rightPush(KEY_LIST, brand);
        }
    }

    @Override
    public List<BrandListItemVO> getList() {
        ListOperations<String, Serializable> opsForList = redisTemplate.opsForList();
        long start = 0L;
        long end = -1L;
        List<Serializable> list = opsForList.range(KEY_LIST, start, end);

        List<BrandListItemVO> brands = new ArrayList<>();
        for (Serializable serializable : list) {
           brands.add((BrandListItemVO) serializable);
        }
        return brands;
    }

    @Override
    public Set<String> getAllKeys() {
        String allKeysPattern = "brand:*";
        return redisTemplate.keys(allKeysPattern);
    }

    @Override
    public Long deleteAll(Set<String> keys) {
        return redisTemplate.delete(keys);
    }

    private String getItemKey(Long id) {
        return KEY_ITEM_PREFIX + id;
    }

}

src/test/java的根包下创建repository.BrandCacheRepositoryTests测试类,测试以上方法:

package cn.tedu.csmall.product.repository;

import cn.tedu.csmall.product.pojo.vo.BrandStandardVO;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@Slf4j
@SpringBootTest
public class BrandCacheRepositoryTests {

    @Autowired
    IBrandCacheRepository repository;

    @Test
    void testSave() {
        BrandStandardVO brandStandardVO = new BrandStandardVO();
        brandStandardVO.setId(1L);
        brandStandardVO.setName("华为");

        repository.save(brandStandardVO);
        log.debug("存入数据完成!");
    }

    @Test
    void testGet() {
        Long id = 1L;
        BrandStandardVO brandStandardVO = repository.get(id);
        log.debug("获取数据完成:{}", brandStandardVO);
    }

}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值