Redis笔记

前置知识

NoSQL  VS  SQL

Redis特征

redis6.0有多线程,但是仅针对网络请求处理,核心的命令执行还是单线程

运行命令

1. 前台启动 

redis-server

2. 后台运行

// 启动服务
redis-server --service-start 
// 停止服务
redis-server --service-stop

   客户端启动

1.  redis-cli -h 地址 -p 端口号 -a 密码
2.  redis-cli -h 地址 -p 端口号
    AUTH 密码

Redis

数据结构

通用命令

String

key命名

Hash

List

Set

SortedSet

GEO

BitMap

HyperLogLog

⭐五种基本数据类型的使用场景

  • String:

存储常规数据(token)、需要计数、分布式锁(setnx)

  • List:

信息流(最新动态)、消息队列

  • Set:

存放的数据不能重复(点赞收藏)、获取多个数据源交集、并集和差集(共同好友)、随机获取数据源中的元素

  • Hash:

存储对象数据(用户信息)

  • sorted set:

随机获取数据源中的元素根据某个权重进行排序(排行榜)

存储的数据有优先级或者重要程度

Redis的java客户端

Jedis

1. 引入依赖

        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>3.3.0</version>
        </dependency>

2. 创建jedis对象,建立连接

    private Jedis jedis;
    @BeforeEach
    void setUp() {
        //连接jedis
        jedis = new Jedis("192.168.150.101",6379);
        //设置密码
        jedis.auth("123456")
        //设置库,一共有16个,默认为0
        jedis.select(0);
    }

3. 使用Jedis,方法名与Redis命令一样

    //操作String 
    @Test
    public void test() {
        // 插入数据
        String res = jedis.set("name", "zhangsan");
        System.out.println("res = " + res);
​        // 获取数据
        String name = jedis.get("name");
        System.out.println("name = " + name);
    }
    //操作hash
    @Test
    public void test2() {
        jedis.hset("hash","name","zhangsan");
        jedis.hset("hash","age","11");
​
        Map<String, String> map = jedis.hgetAll("hash");
        System.out.println(map);
    }

4. 释放资源

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

因为Jedis本身是线程不安全的,并且频繁的创建和销毁连接会有性能损耗,因此使用Jedis连接池代替Jedis的直连方式 

​public class JedisConnecitonFactiory {
​
    private static final JedisPool jedispool;
    static {
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        //最大连接数
        jedisPoolConfig.setMaxTotal(8);
        //最大空闲连接
        jedisPoolConfig.setMaxIdle(8);
        //最小空闲连接
        jedisPoolConfig.setMinIdle(0);
        //设置最长等待时间  ms
        jedisPoolConfig.setMaxWaitMillis(2000);
        //1000 是等待连接redis的超时时间
        jedispool = new JedisPool(jedisPoolConfig,"192.168.119.120",6379,1000,"123456");
    }
​
    public static Jedis getJedis() {
        return jedispool.getResource();
    }
}

SpringDataRedis

API

操作

        1. 引入依赖

​<!--        redis启动类-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
<!--        redis连接池-->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>

        2. 配置文件

spring:
  redis:
    host: 192.168.150.101
    port: 6379
    password: 123321
    # springboot默认使用lecctuce  如果要使用jedis 需要自己引入jedis的依赖
    lettuce:
      pool:
        # 最大连接
        max-active: 8
        # 最大空闲连接
        max-idle: 8
        # 最小空闲连接
        min-idle: 0
        # 最大等待时长,没有等到会报错
        max-wait: 100ms

        3. 测试 

@SpringBootTest
class SpringbootdataRedisApplicationTests {
    @Autowired
    private RedisTemplate redisTemplate;
    @Test
    void contextLoads() {
        redisTemplate.opsForValue().set("name","大虎");
        Object name = redisTemplate.opsForValue().get("name");
        System.out.println("name = " + name);
    }
}
自定义序列化

        RedisTemplate可以接收任意Object作为值写入Redis,只不过写入前会把Object序列化为字节形式,默认是采用JDK序列化,得到的结果是这样的:

        可读性差且占用内存,因此需要自定义序列化

        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
        </dependency>
// redis配置类
@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory){
        // 1 创建RedisTemplate对象
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        // 2 设置连接工厂
        template.setConnectionFactory(connectionFactory);
        // 3 创建JSON序列化工具
        GenericJackson2JsonRedisSerializer jsonRedisSerializer = 
            							new GenericJackson2JsonRedisSerializer();
        // 4.1 设置Key的序列化  
        //     key和 hashkey 采用String序列化
        template.setKeySerializer(RedisSerializer.string());
        template.setHashKeySerializer(RedisSerializer.string());
        // 4.2 设置Value的序列化  
        //     value 和 hashvalue 采用JSON序列化
        template.setValueSerializer(jsonRedisSerializer);
        template.setHashValueSerializer(jsonRedisSerializer);
        // 5 返回
        return template;
    }
}
StringRedisTemplate
@Test
    void contextLoads2() {
        User user = new User("虎哥",33);
        redisTemplate.opsForValue().set("user:10",user);
        user =(User) redisTemplate.opsForValue().get("user:10");
        System.out.println("user = " + user);
​
    }

        得到结果: 

为了在反序列化时知道对象的类型,JSON序列化器会将类的class类型写入json结果中,存入Redis,会带来额外的内存开销

因此为了节省内存空间,我们并不会使用JSON序列化器来处理value,而是统一使用String序列化器,要求只能存储String类型的key和value。当需要存储Java对象时,手动完成对象的序列化和反序列化

SpringDataRedis提供了RedisTemplate的子类:StringRedisTemplate,它的key和value的序列化方式默认就是String方式

@SpringBootTest
class RedisStringTests {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 存字符串
     */
    @Test
    void testString() {
        // 写入一条String数据
        stringRedisTemplate.opsForValue().set("verify:phone:13600527634", "124143");
        // 获取string数据
        Object name = stringRedisTemplate.opsForValue().get("name");
        System.out.println("name = " + name);
    }

    // springmvc中默认使用的JSON序列化工具
    private static final ObjectMapper mapper = new ObjectMapper();
    /**
     * 存对象
     */
    @Test
    void testSaveUser() throws JsonProcessingException {
        // 创建对象
        User user = new User("虎哥", 21);
        // 手动序列化!!!!!!!!!!
        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() {
        // hash存用的是put 取一个用的是get
        stringRedisTemplate.opsForHash().put("user:400", "name", "虎哥");
        stringRedisTemplate.opsForHash().put("user:400", "age", "21");
        // 一次性把所有的key value都取出来 用entries
        Map<Object, Object> entries = stringRedisTemplate.opsForHash().entries("user:400");
        System.out.println("entries = " + entries);
    }
}

缓存更新策略

  • Cache Aside(旁路缓存)策略;
  • Read/Write Through(读穿 / 写穿)策略;
  • Write Back(写回)策略;

1. Cache Aside(旁路缓存)

应用程序直接与「数据库、缓存」交互,并负责对缓存的维护

2. Read/Write Through(读穿 / 写穿)

应用程序只和缓存交互,不再和数据库交互,而是由缓存和数据库交互,相当于更新数据库的操作由缓存自己代理了。

3. Write Back(写回/异步缓存写入)

在更新数据的时候,只更新缓存,同时将缓存数据设置为脏的(表示被修改过),然后立马返回,并不会更新数据库。对于数据库的更新,会通过批量异步更新的方式进行。

缓存问题

1. 缓存穿透

当有大量这样的请求到来时,数据库的压力骤增

布隆过滤器可以看作是由 位数组哈希函数 组成的一种数据结构

每个元素只占1bit,只能为0或1

会误判,把不是在集合中的元素判断为处在集合中。

2. 缓存雪崩

 3. 缓存击穿

缓存重建:redis未命中,到数据库查询并重新更新到缓存中

  • 针对热点数据提前预热,将其存入缓存中并设置合理的过期时间,比如秒杀场景下的数据在秒杀结束之前不过期。

逻辑过期

不设置 ttl(避免key失效),而是添加一个字段expire(表示过期时间),在逻辑上进行判断

互斥锁:

保证只有一个请求会落到数据库上

后台异步更新缓存:

不给热点数据设置过期时间,由后台异步更新缓存。或者在热点数据准备要过期前,提前通知后台线程更新缓存以及重新设置过期时间;

缓存和数据库数据一致性

1. 先更新数据库再删除缓存

     删除失败导致数据不一致怎么办?
    ① 引入消息队列,重试,将删除缓存的操作加到消息队列中
    ② 更新数据库成功,就会产生一条变更日志,记录在 binlog 里。订阅 MySQL binlog(伪装成数据库的一个从节点),拿到具体要操作的数据,再操作缓存

2. 延迟双删(解决 先删除缓存再更新数据库 数据不一致性)

    删除缓存 -> 更新数据库 -> 睡眠 -> 删除缓存
    睡眠:为了确保请求 A 在睡眠的时候,请求 B 能够在这一段时间完成「从数据库读取数据,再把缺失的缓存写入缓存」的操作,然后请求 A 睡眠完,再删除B写入的缓存
缺点:延时时间难以把控,删除可能失败

3. 先更新数据库再更新缓存:

    怎么解决数据不一致?/ 怎么保证双写一致性:
   ① 更新前加分布式锁;       ② 更新完缓存时,给缓存加上较短的过期时间

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值