Redis笔记

77 阅读9分钟

简介

redis是一种非关系型数据库,非结构化

在linux安装redis

安装Redis依赖

yum install -y gcc tcl

上传安装包并解压

上传安装包:直接将压缩文件拖到linux对应的文件夹中

tar -zxvf redis-7.0.11.tar.gz

启动

默认启动

redis-server

指定配置文件启动

#启动
redis-server redis.conf
#结束  2852为进程id
kill -9 2852

开机启动

vi /etc/systemd/system/redis.service
[Unit]
Description=redis-server
After=network.target

[Service]
Type=forking
ExecStart=/usr/local/bin/redis-server /usr/local/src/redis-7.0.11/redis.conf
PrivateTmp=true

[Install]
WantedBy=multi-user.target
systemctl daemon-reload
systemctl enable redis
# 启动
systemctl start redis
# 停止
systemctl stop redis
# 重启
systemctl restart redis
# 查看状态
systemctl status redis
# 查看redis进程命令
ps -ef | grep redis

Redis客户端

Redis命令行客户端

redis-cli [options] [commonds]

其中常见的options有:

  • -h 127.0.0.1:指定要连接的redis节点的IP地址,默认是127.0.0.1
  • -p 6379:指定要连接的redis节点的端口,默认是6379
  • -a 123321:指定redis的访问密码

其中commonds就是Redis的操作命令,例如:

  • ping:与redis服务端做心跳测试,服务端正常会返回pong

不指定cmmond时会进入redis-cli的交互控制台

图形化桌面客户端

RedisInsight下载地址:redis.com/thank-you/r…

下载Redis Desktop Manager

连接虚拟机redis

连接之前一定要把虚拟机的防火墙打开,把6379端口打开!!!!!!

systemctl start firewalld.service
firewall-cmd --add-port=6379/tcp --permanent
firewall-cmd --reload

https://cdn.nlark.com/yuque/0/2023/png/28605255/1682745906562-cfb45361-c212-41ab-9d14-6a40d64f6b33.png#averageHue=%23f5f5f4&clientId=uc9714033-11e5-4&from=ui&id=uf7af13a4&originHeight=717&originWidth=593&originalType=binary&ratio=1.25&rotation=0&showTitle=false&size=54373&status=done&style=none&taskId=u3404ae07-cc01-4915-873b-9e8d770e9cf&title=

Redis数据结构

Redis通用命令

通过help[command]可以查看一个命令的具体用法

  • KEYS:查看符合模板的所有key
keys *
  • EXISTS:查看key是否存在
EXISTS name
  • EXPIRE:给一个key设置有效期,有效期到期该key自动删除
# 给key值为name设置20秒的有效期
EXPIRE name 20
  • TTL:查看一个key的剩余有效期
    • 当返回值为-1时,则永久有效
    • 当返回值为-2时,则已经过期
TTL name

Redis的key格式

String类型

字符串类型 常见命令:

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

Hash类型

也叫散列,其中value是一个无序字典,类似于java中的HashMap结构 常见命令:

  • 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不存在,否则不执行

List类型

类似于java中的LinkedList,可看作双向链表 特征与LinkedList相似:

  • 有序
  • 元素可重复
  • 插入和删除快
  • 查询速度慢

常见命令:

  • LPUSH key element...:向列表左侧插入一个或多个元素
  • LPOP key:移除并返回列表左侧的第一个元素,没有则返回nil
  • RPUSH key element...:向列表右侧插入一个或多个元素
  • RPOP key:移除并返回列表右侧第一个元素
  • LRANGE key star end:返回一段角标范围内的所有元素
  • BLPOP和BRPOP:与LPOP和RPOP类似,只不过在没有元素时等待指定时间,而不是直接返回nil

Set类型

类似于java中的HashSet 特征:

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

常见命令:

  • SADD key menber...:向set中添加一个或多个元素
  • SREM key menber...:移除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的并集

SortedSet类型

是一个可排序的set集合(常用于实现排行榜功能),与java中的TreeSet类似,但底层数据结构却差别很大。SortedSet中的每一个元素都带有一个score属性,可以基于score属性对元素排序,底层实现是一个跳表(SkipList)加hash表 特征:

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

常用命令:

  • ZADD key score menber:添加一个或多个元素到sorted set,如果已经存在则更新其score值
  • ZREM key menber:删除sorter 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:求差集、交集、并集

Redis的Java客户端

Gitee地址

Jedis测试的gitee地址:gitee.com/LJJspider/r… SpringDataRedis测试的gitee地址:gitee.com/LJJspider/s…

Jedis引入和连接

  1. 引入依赖
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>3.7.0</version>
</dependency>
  1. 建立连接
public class JedisTest {
    
    private Jedis jedis;

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

Jedis连接池

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

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 jedisPoolConfig = new JedisPoolConfig();
        //最大连接数
        jedisPoolConfig.setMaxTotal(8);
        //最大空闲连接
        jedisPoolConfig.setMaxIdle(8);
        //最小空闲连接
        jedisPoolConfig.setMinIdle(0);
        //设置最长等待时间  ms
        jedisPoolConfig.setMaxWaitMillis(200);
        jedisPool = new JedisPool(jedisPoolConfig,"127.0.0.1",6379,1000);
    }

    //获取Jedis对象
    public static Jedis getJedis(){
        return jedisPool.getResource();
    }

}

SpringDataRedis

  1. 引入依赖
<!-- redis -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- common-pool -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>
  1. 配置文件
spring:
  redis:
    host: 127.0.0.1
    port: 6379
    password: 123321
    lettuce:
      pool:
        max-active: 8
        max-idle: 8
        min-idle: 0
        max-wait: 100
  1. 注入RedisTemplate
@Autowired
private RedisTemplate redisTemplate;
  1. 编写测试
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;

@SpringBootTest
class SpringDataRedisTestApplicationTests {

    @Autowired
    private RedisTemplate redisTemplate;

    @Test
    void testString() {
        //写入一条String数据
        redisTemplate.opsForValue().set("name","虎哥");
        Object name = redisTemplate.opsForValue().get("name");
        System.out.println(name);
    }
}

RedisTemplate两种序列化方案

  1. 自定义RedisTemplate序列化,修改RedisTemplate的序列化器为GenericJackson2JsonRedisSerializer
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.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;

@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory connectionFactory){
        //创建RedisTemplate对象
        RedisTemplate<String,Object> template = new RedisTemplate<>();
        //设置连接工厂
        template.setConnectionFactory(connectionFactory);
        //创建JSON序列化工具
        GenericJackson2JsonRedisSerializer jsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
        //设置key的序列化
        template.setKeySerializer(RedisSerializer.string());
        template.setHashKeySerializer(RedisSerializer.string());
        //设置value的序列化
        template.setValueSerializer(jsonRedisSerializer);
        template.setHashValueSerializer(jsonRedisSerializer);
        //返回
        return template;
    }

}
  1. 使用StringRedisTemplate,手动序列化

手动序列化.png

@Autowired
private StringRedisTemplate stringRedisTemplate;

@Autowired
private static final ObjectMapper mapper = new ObjectMapper();

@Test
void testSaveUser() throws JsonProcessingException {
    //创建一个对象
    User user = new User("胡歌",21);
    //手动序列化
    String json = mapper.writeValueAsString(user);
    //写入一条String数据
    stringRedisTemplate.opsForValue().set("user:200",json);
    //获取数据
    String jsonUser = stringRedisTemplate.opsForValue().get("user:200");
    //手动反序列化
    User user1 = mapper.readValue(jsonUser,User.class);
    System.out.println(user1);
}

缓存

数据交换的缓存区(Cache),是存储数据的临时地方,一般读写性能较高 缓存作用:

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

缓存成本:

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

缓存更新

1.png

  • 低一致性需求:使用Redis自带的内存淘汰机制
  • 高一致性需求:主动更新,并以超时剔除作为兜底方案
    • 读操作
      • 缓存命中直接返回
      • 缓存未命中则查询数据库,并写入缓存,设定超时时间
    • 写操作
      • 先写数据库,然后再删除缓存
      • 要确保数据库与缓存操作的原子性

缓存雪崩

缓存雪崩是指同一时间内大量的缓存key同时失效或者Redis服务宕机,导致大量请求到达数据库,带来巨大压力 解决方案:

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

缓存击穿

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

  • 互斥锁

5.png

  • 逻辑过期

6.png

缓存穿透

缓存穿透是指客户端请求的数据在缓存中和数据库中都不存在,这样缓存永远不会生效,这些请求都会打到数据库 常见的解决方案:

  • 缓存空对象
    • 优点:实现简单,维护方便
    • 缺点:可能造成短期的不一致

1.png

  • 布隆过滤
    • 优点:内存占用较少,没有多余key
    • 缺点:实现复杂,存在误判可能

2.png

案例

短信登录

手机验证码登录.png

商户查询缓存

存在缓存穿透问题

商品缓存.png

解决缓存穿透问题
  • 缓存空对象

3.png

解决缓存击穿问题
  • 互斥锁解决缓存击穿

1.png

  • 逻辑过期解决缓存击穿

1.png

优惠券秒杀

1.png

使用乐观锁对秒杀并发做处理
乐观锁和悲观锁
  • 悲观锁:认为线程安全问题一定会发生,因此在操作数据之前先获取锁,确保线程串行执行

例如:Synchronized、Lock都是属于悲观锁

  • 乐观锁:认为线程安全问题不一定发生,因此不加锁,只是在更新数据时去判断有没有其他线程对数据做修改
    • 如果没有修改则认为是安全的,自己才更新数据
    • 如果已经被其他线程修改,说明发生了安全问题,此时可以重试或异常