Spring boot 论坛项目实战_04

Redis, 一站式高兴能存储方案

1. Redis 入门

  • Redis 是一款 基于键值对 的NoSQL 数据库, 它的值支持多种数据结构:

    • 字符串(Strings)、哈希(hashes)、列表(lists)、集合(sets)、有序集合(sorted sets)等

  • Redis 将所有的数据都存放在内存中,所以它的读写性能十分惊人。同时,Redis还可以将内存中的数据以快照或日志的形式保存到硬盘上,以保证数据的安全性

  • Redis 典型的应用场景包括:缓存、排行榜、计数器、社交网络、消息队列等。

  • 参考网站:

  • Redis 常用命令:

    • 清空当前数据:flushdb

    • 字符串(Strings):

      • 存储: set key【字符拼接用“ : ”】 value

      • 获取: get key【字符拼接用“ : ”】

      • 数字加:incr key,返回 integer

      • 数字减:decr key,返回 integer

        • 加减运算只能是 整数类型 integer

    • 哈希(hashes):

      • 存储:hset key【字符拼接用“ : ”】 field【hash 的 key】 value

      • 获取:hget key【字符拼接用“ : ”】 field

    • 列表(lists):【可以理解为一个横向的数组】

      • 根据其存取 可左可右,可分别实现 队列 和 栈 的特性

      • 进:可左可右

      • 左进

        • 左存多个数值: lpush key value [value ...]

        • 查看当前列表长度: llen key

        • 查看指定 key 的 list 里面第 index 个值:lindex key index

        • 查看指定范围的值:lrange key start stop

      • 出:可左可右

      • 指定右出: rpop key

    • 集合(sets):

      • 存入集合元素:sadd key member [member ...]

      • 统计集合元素:scard key

      • 随机弹出元素:spop key 【可用于抽奖】

      • 查看集合所剩元素:smembers key

    • 有序集合(sorted sets):

      • 添加元素:zadd key [NX|XX] [CH] [INCR] score member [score member ...] 【分数 元素名】

      • 查询某一个值的分数:zscore key member

      • 返回某个值的排名:zrank key member 【默认由小到大,从 0 开始】

      • 取某个范围的数据:zrange key start stop [WITHSCORES]

    • 全局:keys pattern

      • 查看当前所有的 key :keys *

      • 查看所有 test 开头的 key:keys test*

      • 查看某个 key 的类型:type key

      • 查看某个 key 是否存在:EXISTS key [key ...]

      • 删除某 key:del key [key ...]

      • 设置 某 key 的过期时间【到期自动删除】:expire key seconds

 

2. Spring 整合 Redis

 

  • 引入依赖

    • spring-boot-starter-data-redis

  • 配置Redis

  • Spring 默认配置的 k-v 中 k:Object

    实际开放常用的 k: String 类型

    所以这里需要自己再手动配置

    • 配置数据库参数

    • 编写配置类,构造RedisTemplate

      • // 基于 Spring 框架的
        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;
        ​
        @Configuration
        public class RedisConfig {
        ​
            // 接入连接工厂, 才能创建对象
            @Bean
            public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory factory){
                // 方法的实例化
                RedisTemplate<String,Object> template = new RedisTemplate<String,Object>();
                // 给对象设置连接工厂
                template.setConnectionFactory(factory);
        ​
                // 设置 key 的序列化方式, 参数是 spring 框架的 redis 下的
                // 返回一个能够序列化字符串的序列化器
                template.setKeySerializer(RedisSerializer.string());
        ​
                // 设置 value 的序列化方式
                template.setValueSerializer(RedisSerializer.json());
        ​
                // 设置 hash 的 key 的序列化方式
                template.setHashKeySerializer(RedisSerializer.string());
        ​
                // 设置 hash 的 value 的序列化方式
                template.setHashValueSerializer(RedisSerializer.json());
        ​
                // 触发设置结束后 生效
                template.afterPropertiesSet();
                return template;
            }
        ​
        }
  • 访问Redis

    • redisTemplate.opsForValue()

      • @Test
        public void testStrings(){
            String redisKey = "test:count";
        ​
            // String 类型的值
            redisTemplate.opsForValue().set(redisKey,1);
        ​
            System.out.println(redisTemplate.opsForValue().get(redisKey));
            System.out.println(redisTemplate.opsForValue().increment(redisKey));
            System.out.println(redisTemplate.opsForValue().decrement(redisKey));
        }

         

    • redisTemplate.opsForHash()

      • // 访问 hash
            @Test
            public void  testHashes(){
                String redisKey = "test:user";
                redisTemplate.opsForHash().put(redisKey,"id",1);
                redisTemplate.opsForHash().put(redisKey,"username","张三");
        ​
                System.out.println(redisTemplate.opsForHash().get(redisKey, "id"));
                System.out.println(redisTemplate.opsForHash().get(redisKey, "username"));
        ​
            }

         

    • redisTemplate.opsForList()

      • @Test
            public void testLists(){
                // 左进列表
                String redisKey = "test:ids";
        ​
                // 放入数据
                redisTemplate.opsForList().leftPush(redisKey,101);
                redisTemplate.opsForList().leftPush(redisKey,102);
                redisTemplate.opsForList().leftPush(redisKey,103);
        ​
                // 取出数据
                // 统计当前元素个数
                System.out.println(redisTemplate.opsForList().size(redisKey));
                // 获取指定位置的元素, 获取某个索引所对应的元素
                System.out.println(redisTemplate.opsForList().index(redisKey,0));
                // 按照 索引范围获取元素
                System.out.println(redisTemplate.opsForList().range(redisKey, 0, 2));
                // 弹出元素, 左出
                System.out.println(redisTemplate.opsForList().leftPop(redisKey));
                System.out.println(redisTemplate.opsForList().leftPop(redisKey));
                System.out.println(redisTemplate.opsForList().leftPop(redisKey));
            }

         

    • redisTemplate.opsForSet()

      • @Test
            public void  testSets(){
                String redisKey = "test:teachers";
        ​
                redisTemplate.opsForSet().add(redisKey,"刘备","关羽","张飞","赵云","孔明");
        ​
                // 统计元素个数
                System.out.println(redisTemplate.opsForSet().size(redisKey));
                // 弹出一个数据, 随机弹出
                System.out.println(redisTemplate.opsForSet().pop(redisKey));
                // 统计现在集合中的数据都是什么, 展示现在集合中所有元素
                System.out.println(redisTemplate.opsForSet().members(redisKey));
                
            }

         

    • redisTemplate.opsForZSet()

      • @Test
            public void testSortedSets(){
                String redisKey = "test:students";
        ​
                // 添加 元素, 及其对应的分数
                redisTemplate.opsForZSet().add(redisKey,"唐僧",80);
                redisTemplate.opsForZSet().add(redisKey,"悟空",90);
                redisTemplate.opsForZSet().add(redisKey,"八戒",50);
                redisTemplate.opsForZSet().add(redisKey,"沙僧",70);
                redisTemplate.opsForZSet().add(redisKey,"龙马",60);
        ​
                // 统计元素总数
                System.out.println(redisTemplate.opsForZSet().zCard(redisKey));
                // 查询某个元素的分数
                System.out.println(redisTemplate.opsForZSet().score(redisKey,"八戒"));
                // 查询某个元素的排名, 默认由小到大
                System.out.println(redisTemplate.opsForZSet().rank(redisKey,"八戒"));
                // 倒叙排名, 按分数由大到小
                System.out.println(redisTemplate.opsForZSet().reverseRank(redisKey,"八戒"));
                // 从小到大取前三
                System.out.println(redisTemplate.opsForZSet().range(redisKey,0,2));
                // 从大到小取前三
                System.out.println(redisTemplate.opsForZSet().reverseRange(redisKey,0,2));
                
            }
    • 对 key 的访问

      • // 访问公共类型 key
            @Test
            public void testKeys(){
        ​
                // 程序中一般不会用 keys * 这个命令, 这个命令一般是直接查询
        ​
                redisTemplate.delete("test:user");
        ​
                // 判断 key 是否存在
                System.out.println(redisTemplate.hasKey("test:user"));
        ​
                // 设置 key 的过期时间
                // TimeUnit. 设置时间 单位: 日, 时, 分, 秒, 毫秒...
                redisTemplate.expire("test:students",10, TimeUnit.SECONDS);
            }

         

    • 多次访问同一个key , 可采用绑定 key 的方式,来避免复写 redisKey:

      • // 多次访问同一个 key
            @Test
            public void testBoundOperations(){
                String redisKey = "test:count";
        ​
                // Bound XXX Operations: XXX : 你具体要访问的数据类型, 这里是String
                // redisTemplate.bound XXX Ops : 绑定具体的数据类型, 这里是String
                BoundValueOperations operations = redisTemplate.boundValueOps(redisKey);
                // 这样就是绑定了 key 不用每次都写了.
                operations.increment();
                operations.increment();
                operations.increment();
                operations.increment();
                operations.increment();
                System.out.println(operations.get());
        ​
            }

 

Redis 的事务

Redis 是 非关系型数据库, 所以不用严格遵守 ACID

在实际生产环境中, 建议用编程式事务实现, 声明式只能针对方法整体不能 对单独代码行控制

// 编程式事务
 
@Test
    public void testTransactional() {
        Object obj = redisTemplate.execute(new SessionCallback() {
            @Override
            public Object execute(RedisOperations operations) throws DataAccessException {
                String redisKey = "test:tx";
                // 启用事务
                operations.multi();
​
                operations.opsForSet().add(redisKey,"张三");
                operations.opsForSet().add(redisKey,"李四");
                operations.opsForSet().add(redisKey,"王五");
​
                // 事务过程是不能允许读的
                System.out.println(operations.opsForSet().members(redisKey));
                //operations.exec() : 提交事务
                return operations.exec();
            }
        });
​
        System.out.println(obj);
    }

 

  • 打印结果可以看出:

    • 第一次 查询结构为空, 保证了事务的原子性

    • 第二次打印结果: 前面三个 “1”: 每次操作影响的元素个数; 后面就是这个集合内的具体元素

 

3. 点赞

将点赞数据存入 Redis 提升性能, 优于存在内存中

异步请求

  • 点赞

    • 支持对帖子、评论点赞

    • 第1次点赞,第二次取消点赞【双击取消操作】

  • 首页点赞数量

    • 统计帖子的点赞数量

  • 详情页点赞数量

    • 统计点赞数量

    • 显示点赞状态

 

4. 我收到的赞

  • 重构点赞功能

    • 以用户为key, 记录点赞数量

    • increment(key), decrement(key)

  • 开发个人主页

    • 以用户为key, 查询点赞数量

 

5. 关注、取消关注

  • 需求

    • 开发关注、取消关注功能

    • 统计用户的关注数、粉丝数

  • 关键

    • 若 A 关注了 B, 则 A 是 B 的 Follower(粉丝) , B 是 A 的 Followee(目标)。

    • 关注的目标可以是用户、帖子、题目等,在实现时将这些目标抽象为实体。

 

6. 关注列表、粉丝列表

  • 业务层

    • 查询某个用户关注的人,支持分页。

    • 查询某个用户的粉丝,支持分页。

  • 表现层

    • 处理 “查询关注的人”、“查询粉丝” 请求。

    • 编写 “查询关注的人”、“查询粉丝” 模板。

 

7. 优化登录模块

  • 使用Redis 存储验证码

    • 验证码需要频繁的访问与刷新,对性能要求较高

    • 验证码不需永久保存,通常在很短的时间后就会失效

      • 设置过期时间

    • 分布式部署,存在Session 共享的问题

  • 使用Redis 存储登录凭证【不删,保留用户记录】

    • 处理每次请求时,都要查询用户的登录凭证,访问的效率非常高

  • 使用Redis 缓存用户信息【暂存,要删+】

    • 处理每次请求时,都要根据凭证查询用户信息,访问的频率非常高

posted @ 2020-09-16 16:41  云川望雨  阅读(220)  评论(0编辑  收藏  举报