Redis
本文最后更新于317 天前,其中的信息可能已经过时,如有错误请发送邮件到big_fw@foxmail.com

什么是Redis?

Redis (Remote Dictionary Server) 是一个开源的内存数据结构存储系统,可以用作数据库、缓存和消息中间件。它支持多种类型的数据结构,如字符串(strings)、哈希(hashes)、列表(lists)、集合(sets)及有序集合(sorted sets)等。

安装Redis

在Linux上安装

  1. 更新包管理器索引:sudo apt-get update
  2. 安装Redis服务器:sudo apt-get install redis-server
  3. 启动Redis服务:sudo service redis start

在Windows上安装

  1. 下载适用于Windows的Redis版本。
  2. 解压缩文件到指定目录。
  3. 运行redis-server.exe来启动Redis服务器。

基本命令

  • SET key value: 设置指定key的值。
  • GET key: 获取指定key的值。
  • DEL key: 删除指定的key。
  • EXISTS key: 检查给定key是否存在。
  • KEYS pattern: 查找所有符合给定模式的keys。
  • FLUSHALL: 清空所有数据库中的所有keys。
  • FLUSHDB: 清空当前数据库中的所有keys。
  • INFO [section]: 获取服务器信息和统计数据。
  • PING: 测试与服务器的连接。

数据持久化

Redis提供两种持久化方式:

  • RDB(Redis Database Backup):在指定的时间间隔内进行快照保存数据。
  • AOF(Append Only File):记录服务器接收到的每一个写、删除操作,在Redis重启时会重新执行这些命令以恢复原始数据。

高级特性

  • 发布/订阅:允许客户端订阅一个或多个频道,并监听频道上的消息。
  • 事务:通过MULTI, EXEC, WATCH命令实现简单的乐观锁机制。
  • Lua脚本:可以在服务器端执行Lua脚本,以原子性的方式对数据进行复杂操作。
  • 集群:Redis Cluster提供了自动分片功能,能够在多个Redis节点之间分配数据。
  • 哨兵系统:Redis Sentinel用于监控和故障转移,保证高可用性。

性能优化

  • 使用合适的过期策略减少内存占用。
  • 调整maxmemory设置控制最大内存使用量。
  • 使用慢查询日志找出性能瓶颈。
  • 优化网络配置以提高传输效率。

监控

  • 使用INFO命令获取服务器状态信息。
  • 利用外部工具如Redis Desktop Manager、Prometheus + Grafana等来进行可视化监控

在SpringBoot使用redis的基本语法

在Spring Boot中使用Redis的基本语法涉及几个关键步骤,包括添加依赖、配置连接信息、创建RedisTemplate Bean以及编写服务层代码来执行Redis操作。以下是详细的说明和示例代码,帮助你在Spring Boot项目中快速集成并使用Redis。

1. 添加依赖

首先,在你的pom.xml文件中添加spring-boot-starter-data-redis依赖。这个starter包含了所有必要的库来与Redis交互:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

如果你更倾向于使用Jedis作为客户端,你可以显式地添加Jedis依赖。不过,默认情况下,Spring Boot使用的是Lettuce

2. 配置Redis连接

接下来,在application.propertiesapplication.yml文件中配置Redis的连接信息。例如,在application.properties中可以这样配置:

# Redis服务器地址
spring.redis.host=localhost
# Redis服务器端口
spring.redis.port=6379
# Redis服务器密码(如果设置了)
spring.redis.password=yourpassword
# Redis数据库索引(默认为0)
spring.redis.database=0
# 连接池配置
spring.redis.jedis.pool.max-active=8
spring.redis.jedis.pool.max-wait=-1
spring.redis.jedis.pool.max-idle=8
spring.redis.jedis.pool.min-idle=0
# 连接超时时间(毫秒)
spring.redis.timeout=5000

对于YAML格式,配置如下:

spring:
  redis:
    host: localhost
    port: 6379
    password: yourpassword
    database: 0
    jedis:
      pool:
        max-active: 8
        max-wait: -1
        max-idle: 8
        min-idle: 0
    timeout: 5000ms

3. 创建RedisTemplate Bean

为了更好地控制序列化方式和其他配置选项,通常会在配置类中定义一个自定义的RedisTemplate Bean。下面是一个简单的例子:

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.StringRedisSerializer;

@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);
        // 设置键的序列化器
        template.setKeySerializer(new StringRedisSerializer());
        // 设置值的序列化器
        template.setValueSerializer(new StringRedisSerializer());
        // 如果你需要操作哈希类型的数据,还需要设置hashKeySerializer和hashValueSerializer
        return template;
    }
}

这段代码设置了字符串类型的键和值的序列化器,确保了数据能够被正确地序列化和反序列化.

4. 编写服务层代码

一旦有了RedisTemplate,就可以开始编写业务逻辑了。这里展示了一个简单的RedisService接口及其实现:

package com.example.vo;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;

import java.util.Map;
import java.util.concurrent.TimeUnit;

    @Service
    public class RedisServiceImpl implements IRedisService {

        /**
         * RedisTemplate实例,用于执行Redis操作.<p>
         *
         * RedisTemplate是Spring Data Redis提供的一个类,它简化了与Redis数据库的交互,
         * 提供了一系列方法来操作Redis中的数据。通过这个模板,可以执行如设置键值对、获取值、删除键等操作。
         * 本例中,它被配置为处理String类型的键和Object类型的值,这意味着它可以灵活地处理多种数据类型,
         * 但需要在使用时注意类型转换的问题。
         */
        private final RedisTemplate<String, Object> redisTemplate;


        /**
         * 构造函数注入RedisTemplate
         * 通过@Autowired注解实现依赖注入,简化了对象的创建和管理
         *
         * @param redisTemplate RedisTemplate对象,用于执行Redis操作
         */
        @Autowired
        public RedisServiceImpl(RedisTemplate<String, Object> redisTemplate) {
            this.redisTemplate = redisTemplate;
        }

        /**
         * 将Map中的数据设置到Redis中
         * 本方法会将每个Map条目作为一个独立的键值对存储,键由传入的key和Map条目的键组成
         * 此外,会为每个存储的键值对设置过期时间
         *
         * @param key Redis中的主键,用于定位存储的Map
         * @param value 包含要存储键值对的Map
         */
        @Override
        public void setMapValue(String key, Map<String, String> value) {
            // 获取Redis的Value操作对象
            ValueOperations<String, Object> vo = redisTemplate.opsForValue();

            // 遍历Map,将每个条目作为一个键值对存储到Redis中
            for (Map.Entry<String, String> entry : value.entrySet()) {
                // 构造新的键名,由传入的key和Map条目的键组成
                // 将新的键名和值存储到Redis中
                vo.set(key + ":" + entry.getKey(), entry.getValue());
            }

            // 为整个Map设置过期时间,这里设置为1小时
            // 这有助于防止数据永久占用空间,确保数据有时效性
            redisTemplate.expire(key, 1, TimeUnit.HOURS);
        }

        /**
         * 重写getValue方法,用于从Redis中获取指定键的值
         *
         * @param key Redis中的键
         * @return 与键关联的值,如果键不存在则返回null
         */
        @Override
        public Object getValue(String key) {
            // 获取Redis模板的Value操作实例
            ValueOperations<String, Object> vo = redisTemplate.opsForValue();
            // 使用Key获取对应的值并返回
            return vo.get(key);
        }

        /**
         * 设置指定key的值,并设置过期时间
         * <p>
         * 此方法用于在Redis中存储键值对,并确保该键值对在一段时间后自动过期
         * 它使用RedisTemplate的opsForValue方法获取ValueOperations对象来进行值的设置
         * 并使用expire方法设置键的过期时间
         *
         * @param key   要设置值的键
         * @param value 键对应的值
         */
        @Override
        public void setValue(String key, String value) {
            // 获取用于操作String类型值的ValueOperations对象
            ValueOperations<String, Object> vo = redisTemplate.opsForValue();
            // 设置键值对
            vo.set(key, value);
            // 设置键的过期时间为1小时
            redisTemplate.expire(key, 1, TimeUnit.HOURS);
        }


        /**
         * 设置缓存中的键值对,并指定过期时间
         *
         * @param key   缓存的键
         * @param value 缓存的值
         */
        @Override
        public void setValue(String key, Object value) {
            // 获取Redis的Value操作实例
            ValueOperations<String, Object> vo = redisTemplate.opsForValue();

            // 设置缓存的键值对
            vo.set(key, value);

            // 设置缓存的过期时间,这里设置为1小时后过期
            redisTemplate.expire(key, 1, TimeUnit.HOURS);
        }

        /**
         * 从Redis中获取存储的Map对象
         *
         * @param key Redis中存储Map对象的键
         * @return 返回从Redis中获取的Map对象,其中键和值都是字符串类型
         */
        @Override
        public Map<String, String> getMap(String key) {
            // 获取Redis中的Value操作对象,用于操作字符串类型的值
            ValueOperations<String, Object> vo = redisTemplate.opsForValue();
            // 使用Value操作对象从Redis中根据键获取值,并强制转换为Map<String, String>类型
            return (Map<String, String>) vo.get(key);
        }
    }

}

在这个实现中,我们不仅实现了基本的增删改查功能,还为每个新插入的数据设定了一个小时的有效期。这有助于防止缓存中的数据过期后仍然占用空间。

5. 使用注解简化缓存操作

除了直接使用RedisTemplate进行操作外,Spring还提供了基于注解的缓存抽象,可以通过简单的注解来管理缓存的行为。例如,可以使用@Cacheable@CachePut@CacheEvict等注解来简化缓存的操作:

import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

@Service
public class CacheService {

    @Cacheable(value = "items", key = "#id")
    public Item getItemById(Long id) {
        // 模拟从数据库获取数据的过程
        return itemRepository.findById(id).orElse(null);
    }
}

在这个例子中,getItemById方法的结果会被自动缓存到名为“items”的缓存区域,并且以传入的id作为缓存键。当再次调用此方法并且传入相同的id时,结果将直接从缓存中返回,而不会再次查询数据库。

通过以上步骤,你可以在Spring Boot应用程序中轻松地集成和使用Redis。根据实际需求的不同,你还可以进一步优化配置,比如调整连接池参数、选择不同的序列化策略或者实现分布式锁等功能。

在springboot中使用redis完成前台热点数据分页与模糊查询案例

@Service
public class QuesService {

    /** 
     * 定义热点数据的数量限制,即最多缓存多少条最热门的问题。
     */
    private static final int HOT_DATA_COUNT = 20;
    
    /** 
     * 分页查询时每页显示的问题数量。
     */
    private static final int PAGE_SIZE = 5;

    @Autowired
    private QuestionMapper questionMapper;

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * 缓存从数据库中获取的最热问题到Redis中。
     * 使用有序集合(Sorted Set)存储问题,分数由当前时间减去问题更新时间决定,
     * 这样可以确保越新的问题拥有更高的排序权重。
     */
    public void cacheHotQuestions() {
        // 从数据库中获取最热的问题列表
        List<Question> questions = questionMapper.getQuestions(0, HOT_DATA_COUNT);
        
        // 获取操作Redis有序集合的对象
        ZSetOperations<String, Object> zSetOps = redisTemplate.opsForZSet();
        
        // 获取当前时间戳
        long currentTime = System.currentTimeMillis();
        
        // 将问题添加到Redis有序集合中,分数为当前时间减去问题更新时间
        for (Question question : questions) {
            zSetOps.add("hot_questions", question, currentTime - question.getUpdateTime().getTime());
        }
        
        // 设置缓存的有效期(可选),以避免数据长期占用内存资源
        // redisTemplate.expire("hot_questions", Duration.ofHours(1));
    }

    /**
     * 根据给定的页码返回分页后的热点问题。
     * 
     * @param pageNo 请求的页面编号,第一页为1。
     * @return 包含分页信息及结果集的PageData对象。
     */
    public PageData<Question> getHotQuestions(int pageNo) {
        // 获取操作Redis有序集合的对象
        ZSetOperations<String, Object> zSetOps = redisTemplate.opsForZSet();
        
        // 计算分页起始位置与结束位置
        long start = (pageNo - 1) * PAGE_SIZE;
        long end = start + PAGE_SIZE - 1;
        
        // 从Redis中按照分数降序获取指定范围内的元素
        Set<Object> range = zSetOps.reverseRange("hot_questions", start, end);
        
        // 将Set转换为List以便后续处理
        List<Question> result = new ArrayList<>(range);
        
        // 创建并填充PageData对象,用于封装分页信息和查询结果
        PageData<Question> pageData = new PageData<>();
        pageData.setResult(result);
        pageData.setTotalCount(zSetOps.zCard("hot_questions").intValue());
        pageData.setPageSize(PAGE_SIZE);
        pageData.setPageNo(pageNo);

        return pageData;
    }

    /**
     * 实现基于关键词的模糊搜索功能。
     * 使用Redis SCAN命令遍历所有可能匹配的键名,并从中提取实际值。
     * 注意SCAN是非阻塞的迭代器,但可能会有重复项,因此我们使用LinkedHashSet去除重复。
     *
     * @param keyword 用户输入的搜索关键字。
     * @return 符合条件的结果列表。
     */
    public List<String> fuzzySearch(String keyword) {
        // 创建一个LinkedHashSet用于存储唯一键名,同时保持插入顺序
        Set<String> keys = new LinkedHashSet<>();
        
        // 执行Redis命令,通过SCAN查找符合模式的所有键
        redisTemplate.execute((RedisCallback<Void>) connection -> {
            try (Cursor<byte[]> cursor = connection.scan(ScanOptions.scanOptions().match("*" + keyword + "*").build())) {
                while (cursor.hasNext()) {
                    String key = new String(cursor.next(), StandardCharsets.UTF_8);
                    keys.add(key);
                }
            }
            return null;
        });

        // 准备返回的结果列表
        List<String> results = new ArrayList<>();
        
        // 获取操作Redis字符串类型数据的对象
        ValueOperations<String, Object> valueOps = redisTemplate.opsForValue();
        
        // 遍历找到的所有键,尝试读取其对应的值
        for (String key : keys) {
            Object value = valueOps.get(key);
            if (value != null) {
                results.add(value.toString());
            }
        }

        return results;
    }
}

总结

  • redis 本质上就是个远程字典服务,所有读写命令等核心逻辑,都在一个线程上完成。什么并发问题和线程切换开销,完全不存在!
  • redis 支持多种数据类型、内存过期策略和多种缓存失效策略,通过 TCP 对外提供了一套非常简单的传输协议。
  • redis 加入了最大程度的持久化保证。将数据持久化为 rdb 和 AOF,确保服务重启后不至于什么数据都没有。
  • redis 支持多种扩展,玩法非常多,比如 RediSearch 和 RedisJSON。

最后遗留一个问题,redis 到目前为止就是个单机服务,高性能是有了,但高可用可扩展性是一点没看到

文末附加内容
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇