使用原因:

  • sql数据库的压力过大(单台mysql最大并发量500)
  • 多台服务器数据不同步
  • 多台服务器之间的互斥锁失效

性能高

nosql(非关系型数据库,类似的还有:mongodb,memcached)基于内存,较不安全,一般两种同时使用
基于Key-Value,内存储存,支持持久化,单线程模型(原子性),read(1w/s)write(8k/s),io多路复用
内部采用自己的事件分离器,非阻塞,吞吐量大
主从,哨兵,集群(横向扩展)

场景

会话缓存,全页缓存,消息队列,排行榜,计数器,发布,订阅(K-用户,V-关注)
有效期数据(优惠卷,秒杀,验证码)

命令

https://blog.csdn.net/zzu_seu/article/details/106323114

String

set key value
setnx key value (如果没有,则创建;有则不发生变化)
get key(只用于String)
getset key value
incr/decr key
incrby/decrby key number
append key number(拼接)

注意事项 string在redis内部存储默认就是一个字符串,当遇到增减类操作incr,decr时会转成数值型进行计算 redis所有的操作都是原子性的,采用单线程处理所有业务,命令是一个一个执行的,因此无需考虑并发带来的数据影响。 按数值进行操作的数据,如果原始数据不能转成数值,或超过了redis数值上线范围,将会报错。9223372036854775807 (java中long型数据最大值,Long.MAX_VALUE)

List

lpush/rpush key value1 value2 ..
lrange key start end(0代表第一个, -1表示链表尾部的元素,-2则表示倒数第二个,依次类推….)
lpushx/rpushx key value(key不存在时不建立)
lpop/rpop key(第一和倒数第一)
rpoplpush list1 list2(宁做龙头,不当凤尾)
llen key(返回元素个数)
lset key index value
lrem key count value(删除count个值为value的元素,如果count大于0,从头向尾遍历并删除count个值为value的 元素,如果count小于0,则从尾向头遍历并删除。如果count等于0,则删除链表中所有等于value的元素。)
linsert key before/after pivot value(在pivot前/后插入)

Set(无重复元素)

sadd key value1 value2
smembers key
scard key
sismember key value(判断是否有)
srem key v1 v2 (删除)
srandmember key (随机抽选一名玩家)
sdiff key1 key2 (来找茬,与顺序有关)
sdiffstore key9 key1 key2 (返回相差的集合key9)
sinter key key1 key2 (交集)
sunion key1 key2 (并集)
sunionstore key8 key1 key2(并集)

有序集合(sorted set)

zadd key n1 v1 n2 v2 n3 v3 (n是数字)
zcard key
zcount key min max (区间)
zincrby key increment member (设置指定成员的分数)
zrange key start end [withscores] (获取集合中脚标为start-end的成员,[withscores]参数表明返回的成员包含其分数。)
zrangebyscore key min max withscores (返回分数在[min,max]的成员并按照分数从低到高排序. withcores:显示的分数; [limit offset count]:offset,表明从脚标为offset的元素开始并返回count个成员。 )
zrank key member:返回成员在集合中的位置。
zrem key member[member…]:移除集合中指定的成员,可以指定多个成员。
zscore key member:返回指定成员的分数。

Hash

hset key field value:为指定的key设定field/value对(键值对)。
hgetall key:获取key中的所有filed-vaule。
hget key field:返回指定的key中的field的值。
hmset key fields:设置key中的多个filed/value。
hmget key fileds:获取key中的多个filed的值。
hexists key field:判断指定的key中的filed是否存在。
hlen key:获取key所包含的field的数量。
hincrby key field increment:设置key中filed的值增加increment,如:age增加20。

BItmap

位图 集合 0或1 存储大量数据
场景:签到,活跃用户,在线用户
setbit key

Geo

HyperLogLog

通用操作

keys [] 模糊查询 *:所有 ?:一个字符
del key1 key2
exists key :判空
rename key newname :重命名
expire key second :设置过期时间
ttl key :查看剩余时光
type key
flushall :删库跑路

redis连接

基于Docker安装Redis
1.创建文件夹,新增配置文件
mkdir -p /usr/local/docker/redis6379
同时上传redis.conf配置文件
核心更改:
a.ip地址 #bind 127.0.0.1
b.设置密码 requiredpass qfjava
2.创建并运行Redis实例

  1. docker run -d --name redis6380 -p 6380:6379 -v /usr/local/docker/redis6379/redis.conf:/etc/redis/redis.conf redis redis-server /etc/redis/redis.conf

客户端自带的连接工具:redis-cli
docker执行以下命令:
1.docker exec -it redis6380 bash
2.redis-cli
3.auth qfjava
如果没有密码,就不用auth

Jedis

修改/usr/local/redis-5.0.4/bin目录下的redis.conf配置文件,然后启动redis服务端,将绑定127.0.0.1注释掉,然后把保护模式关掉.
如果设置有密码 方式一:通过修改 redis.conf 文件,设置Redis的密码校验requirepass 密码;
方式二:在不修改 redis.conf 文件的前提下,在第一次链接Redis时,输入命令:Config set requirepass 密码
后续连接redis客户端的时候,需要先 AUTH 做一下校验
127.0.0.1:6379> auth 密码

创建Maven工程pom.xml:jedis,fastjson,lombok

package com.qf.jedis;

import org.junit.Test;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

public class JedisDemo {

    // 通过java程序访问redis数据库
    // 获得单一的jedis对象操作数据库
    @Test
    public void test1() {

        //获得连接对象
        Jedis jedis = new Jedis("192.168.153.132", 6379);
        //认证密码
        //jedis.auth("root");
        //获得之前redis中存储的数据
        String name = jedis.get("name");
        System.out.println(name);

        //存储数据
        jedis.set("password", "123");
        System.out.println(jedis.get("password"));

        //关闭
        jedis.close();

    }

    //通过jedis的pool获得jedis连接对象
    @Test
    public void test2() {

        // 创建池子的配置对象
        JedisPoolConfig poolConfig = new JedisPoolConfig();

        poolConfig.setMaxIdle(30);// 最大闲置个数
        poolConfig.setMinIdle(10);// 最小闲置个数
        poolConfig.setMaxTotal(50);// 最大连接数

        // 创建一个redis的连接池
        JedisPool pool = new JedisPool(poolConfig, "192.168.153.132", 6379);
        // 从池子中获取redis的连接资源
        Jedis jedisPoolResource= pool.getResource();
        // 创建User类进行存储
        User user = new User(1001, "李四", "123");
        // 将对象转换成json存储
        jedisPoolResource.set("user", JSON.toJSONString(user));
        String db_user = jedisPoolResource.get("user");
        // 返回json数据
        System.out.println(db_user);
        // 返回User类型
        System.out.println(JSON.parseObject(db_user,User.class));
        // 关闭资源
        jedis.close();
        pool.close();
    }
}

Redis事务

不同于MySQL的事务,Redis的事务是一个单独的隔离操作,将几条命令序列化按顺序执行(可以看作单线程)
没有隔离级别的概念(被提交前都不会执行);不保证原子性
常用命令: multi,exec,discard,watch,unwatch

watch在multi前执行,提供乐观锁功能

Redis持久化

RDB

快照的方式写入磁盘 redis.rdb(用于备份,在启动redis的目录下生成)

redis.conf可以看到 save 900 1 save 300 10 save 60 10000

AOF

默认不开启,需手动开启
需修改配置文件redis.conf将appendonly改为yes,之后的任何修改步骤写入appendonly.aof

比较

RDB优点与缺点 优点 大规模数据的恢复,RDB快。 RDB是一个非常紧凑(compact)的文件,保存了时间点的数据集,非常适合用作备份,同时也非常适合用作灾难性恢复,它只有一个文件,内容紧凑,通过备份原文件到本机外的其他主机上,一旦本机发生宕机,就能将备份文件复制到redis安装目录下,通过启用服务就能完成数据的恢复。 缺点 RDB这种持久化方式不太适应对数据完整性要求严格的情况,因为,尽管我们可以用过修改快照实现持久化的频率,但是要持久化的数据是一段时间内的整个数据集的状态,如果在还没有触发快照时,本机就宕机了,那么对数据库所做的写操作就随之而消失了并没有持久化本地dump.rdb文件中。 AOF优点与缺点 优点 AOF有着多种持久化策略: appendfsync always:每次修改后保存 appendfsync everysec: 每秒同步,异步操作 appendfsync no AOF文件是一个只进行追加操作的日志文件,对文件写入不需要进行seek,即使在追加的过程中,写入了不完整的命令(例如:磁盘已满),可以使用redis-check-aof工具可以修复这种问题 Redis可以在AOF文件变得过大时,会自动地在后台对AOF进行重写:重写后的新的AOF文件包含了恢复当前数据集所需的最小命令集合。整个重写操作是绝对安全的,因为Redis在创建AOF文件的过程中,会继续将命令追加到现有的AOF文件中,即使在重写的过程中发生宕机,现有的AOF文件也不会丢失。一旦新AOF文件创建完毕,Redis就会从旧的AOF文件切换到新的AOF文件,并对新的AOF文件进行追加操作。 缺点 对于相同的数据集来说,AOF文件要比RDB文件大。 根据所使用的持久化策略来说,AOF的速度要慢于RDB。一般情况下,每秒同步策略效果较好。不使用同步策略的情况下,AOF与RDB速度一样快。

消息的订阅和发布

subsribe key
publish key value

主从复制

将主节点的数据复制到从节点
作用:数据冗余(热备份:系统运行中备份),故障恢复(服务的冗余),负载均衡(主写从读;适用于写入少,读取多),高可用性(哨兵模式的的基础)
127.0.0.1:6379> info replication
拷贝3个文件
修改这3个端口号,pid名字,log文件名,dump.rdb名;

port 6379 pidfile /var/run/redis_6379.pid logfile “6379.log” dbfilename dump6379.rdb

启动三台服务器图片.png
第四个窗口查看图片.png
配置从机图片.png
查看主机图片.png

由于这里使用的是命令进行配置,所以是暂时的,一般公司配置会在配置文件中进行配置,属于永久性配置,相当于一打开当前服务器,该服务器就是从机,一般主机可以写,从机不能写只能读,主机中的所有信息和数据都会被从机保存!如果使用的是命令行配置的从机,从机一旦断开链接后,就会变回主机了,如果再次变回从机,仍旧可以获取主机中的值. 如果主机断开链接,从机可以使用命令:127.0.0.1:6380>slaveof no one 使自己成为主机

主从复杂原理

Slave启动成功连接到master后会发送一个sync同步命令,Master接到命令后,会启动后台的存盘进程,同时收集所有接收到的用于修改数据集命令,在后台进程执行完毕后,master将传送整个数据文件到salve,并完成一次完整的同步.

全量复制:salve服务在接收到数据库文件数据后,将其存盘并加载到内存中.
增量复制:master继续将新的所有收集到的修改命令依次传递给salve,完成同步.

哨兵模式

1、监控:Sentinel会不断的检查主服务器和从服务器是否正常运行。
2、通知:当被监控的某个redis服务器出现问题,Sentinel通过API脚本向管理员或者其他的应用程序发送通知。
3、自动故障转移:当主节点不能正常工作时,Sentinel会开始一次自动的故障转移操作,它会将与失效主节点是主从关系的其中一个从节点升级为新的主节点,并且将其他的从节点指向新的主节点

配置步骤:

1.创建哨兵配置文件 [root@localhost bin]# vim sentinel.conf sentinel.conf文件内容如下:(格式:sentinel monitor 被监控名称 host port 1) sentinel monitor myredis 127.0.0.1 6379 1 注意:后面的数字1表示主机挂了,slave会以投票的方式选举成为主机. 2.启动哨兵 [root@localhost bin]# ./redis-sentinel sentinel.conf 3.如果Master节点断开了(主机宕机了),过一会,会发送哨兵日志,并自动通过算法在其他两个从机中选择一个成为主机.

优点 1.哨兵集群模式是基于主从模式的,所有主从的优点,哨兵模式同样具有。 2.主从可以切换,故障可以转移,系统可用性更好。 3.哨兵模式是主从模式的升级,系统更健壮,可用性更高。 缺点 1.Redis较难支持在线扩容,在.集群容量达到上限时在线扩容会变得很复杂。 2.实现哨兵模式的配置也不简单,甚至可以说有些繁琐

缓存穿透,击穿,雪崩,倾斜

穿透(查不到)

多用户未查询到内存的数据

1.布隆过滤器:是一种数据结构,对所有可能查询的参数以hash形式存储,在控制层先进行校验,不符合则丢弃,从而避免了对底层存储系统的压力. 2.缓存空对象:当存储层查不到时,即使返回的空对象也将其缓存起来,同时设置一个过期时间,之后再访问这个数据将会从缓存中获取,保护后端数据.

但会有两个问题:

1.如果空值被缓存起来,就意味着需要更多的空间存储更多的键,会有很多空值的键.

2.即使对空值设置了过期时间,还是会存在 缓存层和存储层会有一段时间窗口不一致,这对于需要保持一致性的业务会有影响.

击穿(访问量大,缓存过期)

一个热点key的失效导致大量请求访问数据库

解决方案: 1.设置热点数据永不过期:从缓存层面上来说,不设置过期时间,就不会出现热点key过期后产生的问题.

2.添加互斥锁:使用分布式锁,保证对每个key同时只有一个线程去查询后端服务,其他线程没有获得分布式锁的权限,因此只需要等待即可,这种方式将高并发的压力转移到了分布式锁上,对分布式锁也是一种极大的考验.

更改docker容器端口

雪崩

一个时间段大量缓存失效,压力来到存储层
缓存服务器的节点宕机或断网,造成数据库服务器的压力增大

解决方案:

1.配置Redis的高可用:其实就是搭建集群环境,有更多的备用机.

2.限流降级:在缓存失效后,通过加锁或者队列来控制读服务器以及写缓存的线程数量,比如对某个key只允许一个线程查询数据和写缓存,其他线程等待.

3.数据预热:在项目正式部署之前,把可能用的数据预先访问一边,这样可以把一些数据加载到缓存中,在即将发生大并发访问之前手动触发加载缓存中不同的key,设置不同的过期时间,让缓存失效的时间尽量均衡.

倾斜

指某一台redis服务器压力过大而导致该服务器宕机.

1.扩展主从架构,搭建大量的从节点,缓解Redis的压力。

2.可以在Tomcat中做JVM缓存,在查询Redis之前,先去查询Tomcat中的缓存。

Redis集群的搭建

图片.png

问答

key的生存时间到了,Redis会立即删除吗?

不会立即删除

1.1定期删除:Redis每隔一段时间就去会去查看Redis设置了过期时间的key,会再100ms的间隔中默认查看3个key。

1.2惰性删除:如果当你去查询一个已经过了生存时间的key时,Redis会先查看当前key的生存时间,是否已经到了,直接删除当前key,并且给用户返回一个空值

redis淘汰机制

在Redis内存已经满的时候,添加了一个新的数据,执行淘汰机制。(redis.conf中配置) 2.1 volatile-lru:在内存不足时,Redis会再设置过了生存时间的key中干掉一个最近最少使用的key。 2.2 allkeys-lru:在内存不足时,Redis会再全部的key中干掉一个最近最少使用的key。 2.3 volatile-lfu:在内存不足时,Redis会再设置过了生存时间的key中干掉一个最近最少频次使用的key。 2.4 allkeys-lfu:在内存不足时,Redis会再全部的key中干掉一个最近最少频次使用的key。 2.5 volatile-random:在内存不足时,Redis会再设置过了生存时间的key中随机干掉一个。 2.6 allkeys-random:在内存不足时,Redis会再全部的key中随机干掉一个。 2.7 volatile-ttl:在内存不足时,Redis会再设置过了生存时间的key中干掉一个剩余生存时间最少的key。 2.8 noeviction:(默认)在内存不足时,直接报错。 方案:指定淘汰机制的方式:maxmemory-policy具体策略,设置Redis的最大内存:maxmemory 字节大小

redis哈希槽?

Redis集群没有使用一致性hash,而是引入了哈希槽的概念,Redis集群有16384个哈希槽,每个key通过CRC16校验后对16384取模来决定放置哪个槽,集群的每个节点负责一部分hash槽

redis事务?

事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行,事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。事务是一个原子操作:事务中的命令要么全部被执行,要么全部都不执行。

redis淘汰策略?

  • noeviction:返回错误当内存限制达到并且客户端尝试执行会让更多内存被使用的命令(大部分的写入指令,但DEL和几个例外)
  • allkeys-lru:尝试回收最少使用的键(LRU),使得新添加的数据有空间存放。volatile-lru:尝试回收最少使用的键(LRU),但仅限于在过期集合的键,使得新添加的数据有空间存放。
  • allkeys-random:回收随机的键使得新添加的数据有空间存放。
  • volatile-random:回收随机的键使得新添加的数据有空间存放,但仅限于在过期集合的键。
  • volatile-ttl:回收在过期集合的键,并且优先回收存活时间(TTL)较短的键,使得新添加的数据有空间存放。

    redis为何是原子性,怎样保持原子性?

    对于Redis而言,命令的原子性指的是:一个操作的不可以再分,操作要么执行,要么不执行。Redis的操作之所以是原子性的,是因为Redis是单线程的。Redis本身提供的所有API都是原子操作,Redis中的事务其实是要保证批量操作的原子性。多个命令在并发中也是原子性的吗?不一定,将get和set改成单命令操作,incr。使用Redis的事务,或者使用Redis+Lua==的方式实现

    redis主从复制的过程

    1、从服务发送一个sync同步命令给主服务要求全量同步
    2、主服务接收到从服务的sync同步命令时,会fork一个子进程后台执行bgsave命令(非阻塞)快照保存,生成RDB文件,并将RDB文件发送给从服务
    3、从服务再将接收到的RDB文件载入自己的redis内存
    4、待从服务将RDB载入完成后,主服务再将缓冲区所有写命令发送给从服务
    5、从服务在将主服务所有的写命令载入内存从而实现数据的完整同步
    6、从服务下次在需要同步数据时只需要发送自己的offset位置(相当于mysqlbinlog的位置)即可,只同步新增加的数据,再不需要全量同步

    redis常见的性能问题和解决方案?

  1. Master最好不要做任何持久化工作,如RDB内存快照和AOF日志文件
  2. 如果数据比较重要,某个Slave开启AOF备份数据,策略设置为每秒同步一次
  3. 为了主从复制的速度和连接的稳定性,Master和Slave最好在同一个局域网内
  4. 尽量避免在压力很大的主库上增加从库
  5. 主从复制不要用图状结构,用单向链表结构更为稳定,即:Master<-Slave1<-Slave2<-Slave3…

布隆过滤器

替代品:布谷鸟过滤器 说没有就没有,说有是可能有

本质上是一种数据结构,比较巧妙的概率性数据结构

一个固定的二进制向量图或位图
高效插入读取
不能修改,删除有风险

利用布隆过滤器减少磁盘 IO 或者网络请求,因为一旦一个值必定不存在的话,我们可以避免进行后续昂贵的查询请求。

与Memcache对比

性能 内存大小 操作便利上(只能用于缓存) 可靠性 应用场景
与redis都是内存数据库,
redis底层直接构建了vm系统(一般系统调用系统函数会浪费时间去移动和请求)
Redis:v最大512MB memcache最大1MB

数据类型不丰富
可以缓存图片和视频(序列化)
过期策略在set时设置
没有持久化,数据无法灾难恢复

一级缓存二级缓存

一级缓存作用于sqlsession默认开启,但在spring环境中需要开启事务,开启spring事务后,当对于相同的查询,会从缓存中返回结果而不是查询数据库。
当进行增删改的时候sqlsession会关闭缓存也会清空。因此对于多次请求,肯定不存在一级缓存。

二级缓存作用于namespace和mapper,默认是关闭的,只要相同的查询sql对优先从缓存区域查找,所以多次请求二级缓存无影响。不同的sqlsession两次执行相同的namespace下的sql语句,且向sql中传递的参数也相同,即最终执行相同的sql语句。则第一次执行完毕会将数据库中查询的数据写到缓存。第二次查询会从缓存中获取数据,不在去底层数据库查询,从而提高效率。秒杀不适合二级缓存

Redis插件

可视化插件https://blog.csdn.net/weixin_39524984/article/details/111213210
RedisDesktopManager
ledis
irdis:将k-v转为json https://iredis.io/

Redis序列化

1:JdkSerializationRedisSerializer:
2:GenericJackson2JsonRedisSerializer
3:StringRedisSerializer
4:GenericFastJsonRedisSerializer
https://blog.csdn.net/u013282737/article/details/89682378

Redis二进制安全

终止符不依赖与”0”,读取内容时SDS通过len来限制读取长度
https://segmentfault.com/a/1190000023130486

len:表buf已占用的字节
free:表buf剩余的可用字节
buf是数据空间

SDS-动态字符串

https://blog.csdn.net/qq193423571/article/details/81637075
https://blog.csdn.net/TCJGGSDDU/article/details/81275462?utm_medium=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.vipsorttest&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.vipsorttest
本质上为char
预分配冗余空间的方式来减少内存的频繁分配
SDS表头的len成员就保存着字符串长度,所以获得字符串长度的操作复杂度为O(1)

redis的hash与string区别

https://blog.csdn.net/luwei42768/article/details/54945087
hash适合存储对象,存储空间使用zipmap节省空间