Redis学习笔记

Redis常见命令

Redis启动

redis-cli -h 192.168.105.130 -p 6379

输入密码镜像验证

AUTH 123321

检验是否连接成功

ping  //返回PONG

Redis数据结构

Redis是一个key-value的数据库

基本类型:

  1. String
  2. Hash
  3. List
  4. Set
  5. SortedSet

特殊类型

  1. GEO
  2. BitMap
  3. Hyperlog

Redis通用命令

  • KEYS:查看符合模板的所有key 不建议在生产环境设备上使用 KEYS *
  • DEL:删除一个指定的key del name
  • EXISTS:判断key是否存在 EXISTS age
  • EXPIRE:给一个key设置有效期,有效期到期时该key会被自动删除 EXPIRE age 20
  • TTL:查看一个KEY的剩余有效期 TTL age

String类型

String类型可以根据字符串的格式不同分为三类:

  1. string:普通字符串
  2. int:整型,可以自增、自减操作
  3. float:浮点型,可以自增自减操作

String的常见命令有:

  • SET:添加或者修改已经存在的一个String类型的键值对
  • GET:根据key获取String类型的value
  • MSET:批量添加多个String类型的键值对
  • MGET:根据多个key获取多个String类型的value
  • INCR:让一个整型的key自增1 INCR age
  • INCRBY:让一个整型的key自增并指定步长,例如:incrby num 2 让num值自增2 INCRBY age 2
  • INCRBYFLOAT:让一个浮点类型的数字自增并指定步长
  • SETNX:添加一个String类型的键值对,前提是这个key不存在,否则不执行
  • SETEX:添加一个String类型的键值对,并且指定有效期

key结构

Redis的key允许有多个单词形成层级结构,多个单词之间用’:'隔开

set xyz:user:1 '{"id":1, "name":"Jack", "age": 21}'
set xyz:user:2 '{"id":2, "name":"Rose", "age": 18}'
set xyz:product:1 '{"id":1, "name":"小米11", "price": 4999}'
set xyz:product:2 '{"id":2, "name":"荣耀6", "price": 2999}'

Hash类型

Hash的常见命令有:

  • HSET key field value:添加或者修改hash类型key的field的值
  • HGET key field:获取一个hash类型key的field的值
  • HMSET:批量添加多个hash类型key的field的值
  • HMGET:批量获取多个hash类型key的field的值
  • HGETALL:获取一个hash类型的key中的所有的field和value
  • HKEYS:获取一个hash类型的key中的所有的field
  • HVALS:获取一个hash类型的key中的所有的value
  • HINCRBY:让一个hash类型key的字段值自增并指定步长
  • HSETNX:添加一个hash类型的key的field值,前提是这个field不存在,否则不执行
HSET xyz:user:3 name Lucy
(integer) 1
192.168.105.130:6379> HSET xyz:user:3 age 21
(integer) 1
192.168.105.130:6379> HSET xyz:user:3 age 19
(integer) 0
192.168.105.130:6379> HGET xyz:user:3 name
"Lucy"
192.168.105.130:6379> HGET xyz:user:3 age
"19"
192.168.105.130:6379> HMSET xyz:user:4 name LiLei age 20 sex male
OK
192.168.105.130:6379> HMGET xyz:user:4 name age sex
1) "LiLei"
2) "20"
3) "male"
192.168.105.130:6379> HGETALL xyz:user:4
1) "name"
2) "LiLei"
3) "age"
4) "20"
5) "sex"
6) "male"
192.168.105.130:6379> HKEYS xyz:user:4
1) "name"
2) "age"
3) "sex"
192.168.105.130:6379> HVALS xyz:user:4
1) "LiLei"
2) "20"
3) "male"
192.168.105.130:6379> HINCRBY xyz:user:4 age 2
(integer) 22
192.168.105.130:6379> HINCRBY xyz:user:4 age -2
(integer) 20
192.168.105.130:6379> HSETNX xyz:user:4 sex female
(integer) 0
192.168.105.130:6379> HSETNX xyz:user:3 sex female
(integer) 1
192.168.105.130:6379> HGETALL xyz:user:3
1) "name"
2) "Lucy"
3) "age"
4) "19"
5) "sex"
6) "female"

List类型

192.168.105.130:6379> LPUSH users 1 2 3
(integer) 3
192.168.105.130:6379> RPUSH 4 5 6
(integer) 2
192.168.105.130:6379> RPUSH users 4 5 6
(integer) 6
192.168.105.130:6379> RPOP users 1
1) "6"
192.168.105.130:6379> LRANGE users 1 2
1) "2"
2) "1"
192.168.105.130:6379> RPOP users 1
1) "5"
192.168.105.130:6379> BLPOP user2 100
1) "user2"
2) "jack"

Set类型

特征:

  • 无序
  • 元素不可重复
  • 查找快
  • 支持交集、并集、差集等功能

Set的常见命令:

  • SADD key member … : 向set中添加一个或多个元素
  • SREM key member … : 移除set中的指定元素
  • SCARD key: 返回set中元素的个数
  • SISMEMBER key member:判断一个元素是否存在于set中
  • SMEMBERS:获取set中的所有元素
  • SINTER key1 key2 … :求key1与key2的交集
  • SDIFF key1 key2 … :求key1与key2的差集
  • SUNION key1 key2 …:求key1和key2的并集

案例:

  • 张三的好友有:李四、王五、赵六
  • 李四的好友有:王五、麻子、二狗
  • 计算张三的好友有几人
  • 计算张三和李四有哪些共同好友
  • 查询哪些人是张三的好友却不是李四的好友
  • 查询张三和李四的好友总共有哪些人
  • 判断李四是否是张三的好友
  • 判断张三是否是李四的好友
  • 将李四从张三的好友列表中移除
192.168.105.130:6379> SADD zs lisi wangwu zhaoliu
(integer) 3
192.168.105.130:6379> SADD ls wangwu mazi ergou
(integer) 3
192.168.105.130:6379> SCARD zs
(integer) 3
192.168.105.130:6379> SINTER zs ls
1) "wangwu"
192.168.105.130:6379> SDIFF zs ls
1) "zhaoliu"
2) "lisi"
192.168.105.130:6379> SUNION zs ls
1) "wangwu"
2) "lisi"
3) "zhaoliu"
4) "mazi"
5) "ergou"
192.168.105.130:6379> SISMEMBER zs lisi
(integer) 1
192.168.105.130:6379> SISMEMBER ls zhangsan
(integer) 0
192.168.105.130:6379> SREM zs lisi
(integer) 1

SortedSet类型

SortedSet是一个可排序的set集合,SortedSet中的每一个元素都带有一个score属性,可以基于score属性对元素排序,底层的实现是一个跳表(SkipList)加 hash表。

特性:

  • 可排序
  • 元素不重复
  • 查询速度快

常见命令:

  • ZADD key score member:添加一个或多个元素到sorted set ,如果已经存在则更新其score值
  • ZREM key member:删除sorted set中的一个指定元素
  • ZSCORE key member : 获取sorted set中的指定元素的score值
  • ZRANK key member:获取sorted set 中的指定元素的排名
  • ZCARD key:获取sorted set中的元素个数
  • ZCOUNT key min max:统计score值在给定范围内的所有元素的个数
  • ZINCRBY key increment member:让sorted set中的指定元素自增,步长为指定的increment值
  • ZRANGE key min max:按照score排序后,获取指定排名范围内的元素
  • ZRANGEBYSCORE key min max:按照score排序后,获取指定score范围内的元素
  • ZDIFF、ZINTER、ZUNION:求差集、交集、并集

所有的排名默认都是升序,如果要降序则在命令的Z后面添加REV即可

案例

将班级的下列学生得分存入Redis的SortedSet中:

Jack 85, Lucy 89, Rose 82, Tom 95, Jerry 78, Amy 92, Miles 76

并实现下列功能:

  • 删除Tom同学
  • 获取Amy同学的分数
  • 获取Rose同学的排名
  • 查询80分以下有几个学生
  • 给Amy同学加2分
  • 查出成绩前3名的同学
  • 查出成绩80分以下的所有同学
192.168.105.130:6379> ZADD stus 85 Jack 89 Lucy 82 Rose 95 Tom 78 Jerry 92 Amy 76 Miles
(integer) 7
192.168.105.130:6379> ZREM stus Tom
(integer) 1
192.168.105.130:6379> ZSCORE stus Amy
"92"
192.168.105.130:6379> ZRANK stus Rose
(integer) 2
192.168.105.130:6379> ZREVRANK stus Rose
(integer) 3
192.168.105.130:6379> ZCOUNT stus 0 80
(integer) 2
192.168.105.130:6379> ZINCRBY stus 2 Amy
"94"
192.168.105.130:6379> ZRANGE stus 0 2
1) "Miles"
2) "Jerry"
3) "Rose"
192.168.105.130:6379> ZREVRANGE stus 0 2
1) "Amy"
2) "Lucy"
3) "Jack"
192.168.105.130:6379> ZRANGEBYSCORE stus 0 80
1) "Miles"
2) "Jerry"

Redis客户端

Jedis

引入依赖

        <!--jedis-->
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>3.7.0</version>
        </dependency>
        <!--单元测试-->
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>5.7.2</version>
            <scope>test</scope>
        </dependency>

JedisTest.java

package com.xyz;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import redis.clients.jedis.Jedis;

import java.util.Map;

public class JedisTest {
    private Jedis jedis;

    @BeforeEach
    void setUp() {
        //1.建立连接
        jedis = new Jedis("192.168.105.130", 6379);
        //2.设置密码
        jedis.auth("123321");
        //3.选择库
        jedis.select(0);
    }

    @Test
    void testString() {
        //存入数据
        String result = jedis.set("name", "虎哥");
        System.out.println("result = "+result);
        //获取数据
        String name = jedis.get("name");
        System.out.println("name = "+name);

    }

    @Test
    void testHash() {
        //插入hash数据
        jedis.hset("user:1","name","Jack");
        jedis.hset("user:1","age","21");

        //获取
        Map<String, String> map = jedis.hgetAll("user:1");
        System.out.println(map);

    }

    @AfterEach
    void tearDown() {
        if (jedis!=null){
            jedis.close();
        }
    }
}

线程池

com.xyz.jedis.utils.JedisConnectionFactory

package com.xyz.jedis.utils;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

public class JedisConnectionFactory {
    private static final JedisPool jedisPool;

    static {
        JedisPoolConfig poolConfig = new JedisPoolConfig();
        poolConfig.setMaxTotal(8);
        poolConfig.setMaxIdle(8);
        poolConfig.setMaxWaitMillis(1000);
        //创建连接池对象
        jedisPool=new JedisPool(poolConfig,"192.168.105.130",6379,1000,"123321");
    }

    public static Jedis getJedis(){
        return jedisPool.getResource();
    }
}

SpringDataRedis

SpringDataRedis的使用步骤:

  1. 引入spring-boot-starter-data-redis依赖

            <!--Redis依赖-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-data-redis</artifactId>
                <version>2.7.6</version>
            </dependency>
            <!--连接池依赖-->
            <dependency>
                <groupId>org.apache.commons</groupId>
                <artifactId>commons-pool2</artifactId>
            </dependency>
            <!--Jackson依赖-->
            <dependency>
                <groupId>com.fasterxml.jackson.core</groupId>
                <artifactId>jackson-databind</artifactId>
                <version>2.12.3</version>
            </dependency>
    
  2. 在application.yml配置Redis信息

    spring:
      redis:
        host: 192.168.105.130
        port: 6379
        password: 123321
        lettuce:
          pool:
            max-active: 8 #最大连接
            max-idle: 8 #最大空闲连接
            min-idle: 0 #最小空闲连接
            max-wait: 100 #连接等待时间(ms)
    
  3. 注入RedisTemplate

RedisTemplate的两种序列化实践方案:

方案一:

  1. 自定义RedisTemplate
  2. 修改RedisTemplate的序列化器为GenericJackson2JsonRedisSerializer

方案二:

  1. 使用StringRedisTemplate
  2. 写入Redis时,手动把对象序列化为JSON
  3. 读取Redis时,手动把读取到的JSON反序列化为对象

RedisConfig.java

@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory redisConnectionFactory){
        //创建Template
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        //设置连接工厂
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        //设置反序列化工具
        GenericJackson2JsonRedisSerializer jsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
        //key和 hashKey采用String序列化
        redisTemplate.setKeySerializer(RedisSerializer.string());
        redisTemplate.setHashKeySerializer(RedisSerializer.string());
        //value和hashValue采用JSON序列化
        redisTemplate.setValueSerializer(jsonRedisSerializer);
        redisTemplate.setHashValueSerializer(jsonRedisSerializer);
        return  redisTemplate;
    }
}

RedisDemoApplicationTests.java

@SpringBootTest
class RedisDemoApplicationTests {

    @Autowired
    private RedisTemplate<String,Object> redisTemplate;

    @Test
    void testString() {
        //写入一条String数据源
        redisTemplate.opsForValue().set("name","笨蛋");
        //获取String数据
        Object name = redisTemplate.opsForValue().get("name");
        System.out.println("name = "+name);
    }

    @Test
    void testSaveUser() {
        //写入数据
        redisTemplate.opsForValue().set("user:100",new User("胖虎",21));
        //获取数据
        User user = (User) redisTemplate.opsForValue().get("user:100");
        System.out.println("user = " + user);
    }
}

RedisStringTests.java

@SpringBootTest
class RedisStringTests {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Test
    void testString() {
        //写入一条String数据源
        stringRedisTemplate.opsForValue().set("name","张三");
        //获取String数据
        Object name = stringRedisTemplate.opsForValue().get("name");
        System.out.println("name = "+name);
    }

    private static final ObjectMapper mapper=new ObjectMapper();
    @Test
    void testSaveUser() throws JsonProcessingException {
        // 创建对象
        User user = new User("李四", 23);
        // 手动序列化
        String json = mapper.writeValueAsString(user);
        //写入数据
        stringRedisTemplate.opsForValue().set("user:200",json);
        // 获取数据
        String jsonUser = stringRedisTemplate.opsForValue().get("user:200");
        //手动反序列化
        User user1 = mapper.readValue(jsonUser, User.class);
        System.out.println("user1 = " + user1);
    }

    @Test
    void testHash() {
        stringRedisTemplate.opsForHash().put("user:400","name","王五");
        stringRedisTemplate.opsForHash().put("user:400","age","20");

        Map<Object, Object> entries = stringRedisTemplate.opsForHash().entries("user:400");
        System.out.println("entries = " + entries);
    }
}

黑马点评项目

短信登录

发送验证码

UserService.sendCode()

校验手机号 -> RegexUtils.isPhoneInvalid()

生成验证码 -> RandomUtil.randomNumbers()

保存Session

发送验证码 -> 使用 log.debug() 模拟发送验证码

log 的 debug()、 error()、 info()方法的区别

  1. Debug

这个级别的信息,可以随意的使用,任何觉得有利于在调试时更详细的了解系统运行状态,比如变量的值等等,都输出来看看也无妨。

当然,在每一个 Debug 调用之前,一定要加上 If 判断。

  1. Info

这个应该用来反馈系统的当前状态给最终用户的,所以,在这里输出的信息,应该对最终用户具有实际意义,也就是最终用户要能够看得明白是什么意思才行。

  1. Error

所谓错误,就是说可以进行一些修复性的工作,但无法确定系统会正常的工作下去,系统在以后的某个阶段,很可能会因为当前的这个问题,导致一个无法修复的错误(例如宕机),但也可能一直工作到停止也不出现严重问题。

实现验证码登录

UserService.login()

  1. 校验手机号

  2. 校验验证码(验证码放常量池 -> utils.SystemConstants)

    1. 一致
    2. 不一致
  3. 根据手机号查询用户

    1. 不存在,则创建用户
    2. 存在,则保存用户信息到Session

持久层接口 | MyBatis-Plus (baomidou.com)

登录验证功能

  1. 编写登录拦截器 LoginInterceptor
    1. 获取Session
    2. 获取Session中的用户
    3. 判断用户是否存在,不存在则拦截,返回401状态码
    4. 将用户信息保存在ThreadLocal
    5. 放行
  2. 配置添加拦截器
  3. 完成 /user/me 接口

用户信息脱敏

import cn.hutool.core.bean.BeanUtil;
session.setAttribute("user",BeanUtil.copyProperties(user, UserDTO.class));

基于Redis的短信登录

验证码相关

将原先保存在session中的验证码,改为存入redis中,设置过期时间2min

定义Redis相关常量RedisConstants

登录相关
  1. 从redis中获取验证码,并校验

  2. 随机生成token作为登录令牌(UUID)

  3. 将User对象转为Hash存储 BeanUtil.beanToMap()

  4. 将用户信息存储至redis,设置有效期30min

拦截器相关
  1. 获取请求头中的token
  2. 基于token获取redis中的用户
  3. 刷新token有效期

登录拦截器的优化

新增token刷新拦截器

商户查询缓存

缓存

缓存就是数据交换的缓冲区(称作Cache),是存贮数据的临时地方,一般读写性能较高。
在这里插入图片描述

缓存的作用

  • 降低后端负载
  • 提高读写效率,降低响应时间

缓存的成本

  • 数据一致性成本
  • 代码维护成本
  • 运维成本

添加商户缓存

shopService.queryShopById()

  1. 从redis查询商户缓存
  2. 判断是否存在
  3. 存在则直接返回 JSONUtil.toBean()
  4. 不存在则根据id查询数据库 getById()
  5. 数据库不存在,返回错误信息
  6. 存在则写入redis

缓存更新策略

缓存更新策略的最佳实践方案
  1. 低一致性需求:使用Redis自带的内存淘汰机制
  2. 高一致性需求:主动更新,并以超时剔除作为兜底方案
    • 读操作:
      • 缓存命中则直接返回
      • 缓存未命中则查询数据库,并写入缓存,设定超时时间
    • 写操作:
      • 先写数据库,然后再删除缓存
      • 要确保数据库与缓存操作的原子性
实现商铺缓存和数据库双写一致问题
  1. 设置商铺缓存时间 30min
  2. 修改更新商铺方法的业务逻辑
    1. 判断id是否为空
    2. 更新数据库
    3. 删除缓存

使用postman测试 注意事项

在Header中需要添加authorization,否则会被拦截器拦截,无法进行测试

在Boody中选择 raw 和 JSON ,并将更新数据填入
在这里插入图片描述

缓存穿透

缓存穿透是指客户端请求的数据在缓存中和数据库中都不存在,这样缓存永远不会生效,这些请求都会打到数据库。

常见的解决方案:

  1. 缓存空对象
  • 优点:实现简单,维护方便
  • 缺点:
    • 额外的内存消耗
    • 可能造成短期的不一致
      在这里插入图片描述
  1. 布隆过滤
  • 优点:内存占用较少,没有多余的key
  • 缺点
    • 实现复杂
    • 存在误判可能
      在这里插入图片描述

拓展:

谈谈布隆过滤器和布谷鸟过滤器的相同点和不同点

  1. 修改代码,查询数据库中商铺信息不存在时,在redis中存入2min的空值数据
  2. 增加代码,判断redis命中是否为空值
缓存雪崩

缓存雪崩是指在同一时段大量的缓存key同时失效或者Redis服务宕机,导致大量请求到达数据库,带来巨大压力。

解决办法:

  • 给不同的Key的TTL添加随机值
  • 利用Redis集群提高服务的可用性
  • 给缓存业务添加降级限流策略
  • 给业务添加多级缓存
缓存击穿

缓存击穿问题也叫热点Key问题,就是一个被高并发访问并且缓存重建业务较复杂的key突然失效了,无数的请求访问会在瞬间给数据库带来巨大的冲击。

常见的解决方案:

  • 互斥锁
  • 逻辑过期
解决方案优点缺点
互斥锁没有额外的内存消耗
保证一致性
实现简单
线程需要等待,性能受影响
可能有死锁的风险
逻辑过期线程无需等待,性能较好不保证一致性
有额外的内存消耗
实现复杂

实现缓存重建

互斥锁

  1. 定义 tryLock() 获取锁方法,使用 setIfAbsent() ,最后使用BooleanUtil.isTrue()拆箱
  2. 获取互斥锁
  3. 判断是否获取成功
    1. 失败,则休眠后重试
    2. 成功则根据id查询数据库
      1. 不存在,返回错误
      2. 存在,写入数据
  4. 释放锁

逻辑过期

  1. 新建类 RedisData 设置逻辑过期时间
  2. 编写方法 saveShop2Redis
    1. 查询店铺数据
    2. 封装逻辑过期时间
    3. 写入Redis
  3. 编写单元测试 testSaveShop()
  4. 编写方法 queryWithLogicalExpire()
    1. redis查询商铺信息
    2. 未命中则返回null
    3. 命中则需要把json反序列化
    4. 判断信息是否过期
      1. 未过期则直接返回
      2. 过期则需要缓存重建
    5. 获取互斥锁
    6. 判断是否获取锁成功
      1. 成功,则开启独立线程,实现缓存重建
    7. 返回过期的商铺信息

优惠券秒杀

全局唯一ID

特性:

  • 唯一性
  • 高可用
  • 高性能
  • 递增性
  • 安全性
    在这里插入图片描述

ID的组成部分:符号位:1bit,永远为0

时间戳:31bit,以秒为单位,可以使用69年

序列号:32bit,秒内的计数器,支持每秒产生2^32个不同ID

编写 RedisIdWorkernextId()方法

  1. 生成时间戳
  2. 生成序列号
    1. 获取当前日期,精确到天
    2. 自增长
  3. 拼接并返回

全局唯一ID生成策略:

  • UUID
  • Redis自增
  • snowflake算法
  • 数据库自增

Redis自增ID策略:

  • 每天一个key,方便统计订单量
  • ID构造是 时间戳 + 计数器

实现秒杀下单

在这里插入图片描述

  1. 查询优惠券
  2. 判断秒杀是否开始
  3. 判断秒杀是否结束
  4. 判断库存是否充足
  5. 扣除库存
  6. 创建订单
    1. 订单id
    2. 用户id
    3. 代金券id
  7. 涉及两张表,添加事务

超卖问题

超卖问题是典型的多线程安全问题,针对这一问题的常见解决方案就是加锁:

  • 悲观锁
  • 乐观锁
    • 版本号法
    • CAS法
  1. 悲观锁:添加同步锁,让线程串行执行
    • 优点:简单粗暴
    • 缺点:性能一般
  2. 乐观锁:不加锁,在更新时判断是否有其它线程在修改
    • 优点:性能好
    • 缺点:存在成功率低的问题

一人一单

需求:修改秒杀业务,要求同一个优惠券,一个用户只能下一单

  1. 查询订单
  2. 判断是否存在

TODO spring事务失效问题

分布式锁

分布式锁:满足分布式系统或集群模式下多进程可见并且互斥的锁。

特点:

  • 多进程可见
  • 互斥
  • 高可用
  • 高性能
  • 安全性
  1. 实现分布式锁
  2. 改进Redis分布式锁误删问题

总结

  1. 不可重入Redis分布式锁:
  • 原理:利用setnx的互斥性;利用ex(expire)避免死锁;释放锁时判断线程标识
  • 缺陷:不可重入、无法重试、锁超时失效
  1. 可重入的Redis分布式锁:
  • 原理:利用hash接口,记录线程标识和重入次数;利用watchDog延续锁时间;利用信号量控制锁重试等待
  • 缺陷:redis宕机硬气锁失效问题
  1. Redisson的multiLock:
  • 原理:多个独立的Redis节点,必须在所有节点都获取重入锁,才算获得锁成功
  • 缺陷:运维成本高、实现复杂

消息队列

认识消息队列

消息队列由三个角色组成:

  • 生产者:发送信息到消息队列
  • 消息队列:存储和管理消息,也被称为消息代理
  • 消费者:从消息队列中获取并处理消息

使用队列的好处在于解耦

基于List实现消息队列

优点:

  • 利用Redis存储,不受限于JVM内存上限
  • 基于Redis的持久化机制,保障了数据的安全
  • 可以满足消息的有序性

缺点:

  • 无法避免数据丢失
  • 只支持单消费者

基于PubSub的消息队列

优点:

  • 采用发布订阅模型,支持多生产、多消费

缺点:

  • 不支持数据持久化
  • 无法避免数据丢失
  • 消息堆积有上限,超出时数据丢失

基于Stream的消息队列

优点:

  • 消息可回溯
  • 一个消息可以被多个消费者读取
  • 可以阻塞读取

缺点:

  • 有消息漏读风险

解决重复读取报错问题

XGROUP CREATE stream.order g1 0 MKSTREAM

达人探店

查看探店笔记

实现查看发布探店笔记的接口

点赞功能

编写 likeBlog() 方法

  1. 获取登录用户
  2. 判断当前用户是否已经点赞
    • 未点赞,则可以点赞
      1. 数据库点赞数+1
      2. 保存用户到Redis的set集合中
    • 如果已点赞,则取消点赞
      1. 数据库点赞数-1
      2. 把用户从Redis的set中移除

点赞排行榜

查询点赞用户列表

  1. 查询top5的点赞用户 zrange key 0 4
  2. 解析出其中的用户id
  3. 根据用户id查询用户
  4. 返回

好友关注

关注和取关

@PutMapping("/{id}/{isFollow}")
public Result follow()
    
@GetMapping("/{id}/{isFollow}")
public Result follow()

follow()

  1. 获取登录用户
  2. 判断是关注还是取关状态
    • 关注,则新增数据
    • 取关,则删除表中数据

isFollow()

  1. 获取登录用户
  2. 查询是否关注
  3. 判断并返回结果

共同关注

将关注对象在放入数据库的同时,放入redis。使用数据结构 set 中的 SINTER 获取交集,即共同关注

@PathVariable@RequestParam都是Spring MVC中用于将请求中的参数映射到控制器方法参数的注解,但它们有以下主要区别:

  1. 参数来源

    • @PathVariable:用于从URL路径中提取参数。它通常用于RESTful API设计中,可以将URL的一部分作为参数传递给控制器方法。
    • @RequestParam:用于从请求的查询字符串(即URL后面的?后面的部分)中提取参数。
  2. 使用场景

    • @PathVariable:当你的URL设计需要包含动态部分时使用,例如:/users/{id},这里的{id}就是一个路径变量。
    • @RequestParam:当你需要从查询字符串中获取参数时使用,例如:/users?name=John,这里的name就是一个请求参数。
  3. 默认值

    • @PathVariable:没有默认值的概念,因为它们是从URL路径中提取的,必须在请求时提供。
    • @RequestParam:可以设置默认值,如果请求中没有提供该参数,则使用默认值。
  4. 类型

    • @PathVariable:可以是任何类型,Spring会尝试将路径参数转换为方法参数的类型。
    • @RequestParam:同样可以是任何类型,但需要确保请求中的参数可以转换为该类型。
  5. 方法签名

    • @PathVariable:通常与value属性一起使用,指定路径变量的名称,例如:@PathVariable("id") Long id
    • @RequestParam:可以与valuenamerequireddefaultValue等属性一起使用,例如:@RequestParam(value = "current", defaultValue = "1") Integer current
  6. 数据绑定

    • @PathVariable:Spring MVC会尝试将路径参数绑定到方法参数上。
    • @RequestParam:Spring MVC会将请求参数绑定到方法参数上,如果参数不存在,且设置了默认值,则使用默认值。

示例

  • 使用@PathVariable

    @GetMapping("/users/{id}")
    public User getUserById(@PathVariable("id") Long id) {
        // ...
    }
    
  • 使用@RequestParam

    @GetMapping("/users")
    public List<User> getUsersByPage(@RequestParam(value = "page", defaultValue = "1") Integer page) {
        // ...
    }
    

在实际开发中,根据API的设计和需求选择合适的注解来处理请求参数。

关注推送

将作者的笔记推送到各个关注者的redis邮箱中,同时实现Feed的滚动分页

Feed流产品有两种常见模式:
Timeline:不做内容筛选,简单的按照内容发布时间排序,常用于好友或关注。例如朋友圈

  • 优点:信息全面,不会有缺失。并且实现也相对简单
  • 缺点:信息噪音较多,用户不一定感兴趣,内容获取效率低

智能排序:利用智能算法屏蔽掉违规的、用户不感兴趣的内容。推送用户感兴趣信息来吸引用户

  • 优点:投喂用户感兴趣信息,用户粘度很高,容易沉迷
  • 缺点:如果算法不精准,可能起到反作用

Timeline模式的实现方案有三种:

  • 拉模式
  • 推模式
  • 推拉结合

用户签到

BitMap的操作命令有:

  • SETBIT:向指定位置(offset)存入一个0或1
  • GETBIT :获取指定位置(offset)的bit值
  • BITCOUNT :统计BitMap中值为1的bit位的数量
  • BITFIELD :操作(查询、修改、自增)BitMap中bit数组中的指定位置(offset)的值
  • BITFIELD_RO :获取BitMap中bit数组,并以十进制形式返回
  • BITOP :将多个BitMap的结果做位运算(与 、或、异或)
  • BITPOS :查找bit数组中指定范围内第一个0或1出现的位置

UV统计

@Test
void testHyperLog(){
    String[] values = new String[1000];
    int j=0;
    for (int i = 0; i < 1000000; i++) {
        j=i%1000;
        values[j]="user_"+i;
        if (j==999){
            stringRedisTemplate.opsForHyperLogLog().add("hl2",values);
        }
    }
    Long count = stringRedisTemplate.opsForHyperLogLog().size("hl2");
    System.out.println("count = " + count);
}
  • 10
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
尚硅谷是一个教育机构,他们提供了一份关于Redis学习笔记。根据提供的引用内容,我们可以了解到他们提到了一些关于Redis配置和使用的内容。 首先,在引用中提到了通过执行命令"vi /redis-6.2.6/redis.conf"来编辑Redis配置文件。这个命令可以让你进入只读模式来查询"daemonize"配置项的位置。 在引用中提到了Redis会根据键值计算出应该送往的插槽,并且如果不是该客户端对应服务器的插槽,Redis会报错并告知应该前往的Redis实例的地址和端口。 在引用中提到了通过修改Redis的配置文件来指定Redis的日志文件位置。可以使用命令"sudo vim /etc/redis.conf"来编辑Redis的配置文件,并且在文件中指定日志文件的位置。 通过这些引用内容,我们可以得出结论,尚硅谷的Redis学习笔记涵盖了关于Redis的配置和使用的内容,并提供了一些相关的命令和操作示例。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [Redis学习笔记--尚硅谷](https://blog.csdn.net/HHCS231/article/details/123637379)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [Redis学习笔记——尚硅谷](https://blog.csdn.net/qq_48092631/article/details/129662119)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值