一次掌握Redis

介绍

Redis是一个C语言编写的开源的高性能key-value数据库

特点:

  • 数据之间没有必然的关系
  • 内部采用单线程机制进行工作,代表所有的操作都是原子性的
  • 数据存在内存中,支持数据持久化,可以进行数据灾难恢复
  • 支持多种数据类型,比如String、Hash、List、Set、SortedSet

可以做什么

  • 为热点数据加速查询,做缓存
  • 任务队列
  • 即时信息的查询
  • 时效性信息控制,比如验证码
  • 分布式数据共享,比如分布式集群架构中的Session共享

Key的命名规则

在说怎么存储之前,先来了解一下key的命名规则

  • 以业务名(或数据库名)为前缀(防止key冲突),用冒号分隔,比如业务名:表名:id
  • 保证语义的前提下,控制key的长度,当key较多时,内存占用也不容忽视
  • 【强制】:不要包含特殊字符,比如空格,换行,引号

数据类型

redis自身是一个Map,存储的格式为key:value,数据类型指的是value的数据类型,key的数据类型只能是字符串

String类型

存储的数据为单个数据,是redis最简单最常用的数据类型;一个存储空间存储一个数组

  • 数据最大存储量:512MB

  • 数值最大范围:2的64次幂,也就是java中long的最大值

添加/修改数据

set key value

获取数据

get key

删除数据

del key

添加/修改 多个数据 m — multiple

mset key1 value2  key1  value2 ..

获取多个数据

mget key1 key2 ..

获取数据字符个数(字符长度)

strlen key 

追加信息到原始信息后面(如果有就追加,没有就新建),返回追加后信息的长度个数

append key value

业务场景:大型企业级应用中,分表操作是基本操作,使用多张表存储同类型数据,但是对应的主键 id 必须保证统一性 ,不能重复,这个时候就可以使用redis的incr命令来实现自增与返回

给数值数据增加值

#加1
incr key 
#增加指定值
incrby key increment
#增加小数值
#incybyfloat key increment

给数值数据减少值

#减1
decr key
#减少指定值
decy key increment

业务场景:需要对用户的访问次数做限制,比如1分钟内只能请求50次,防止用户恶意刷接口,那么可以写 一个过滤器,将用户的id作为key存在redis中,value为请求的次数,有效时间设置为1分钟,当用户每请求一次,就对value进行incr加1,如果1分钟内超过了50,就提示访问次数过多

设置数据的有效时间

# 秒
setex key seconds value
# 毫秒
psetex key milliseconds value

查看数据的有效时间,返回-1代表永久,返回-2代表不存在

ttl key

Hash类型

对一系列的数据进行编组,方便管理;一个存储空间存储多个键值对数据;底层使用Hash表存储

Hash存储结构优化:

  • 如果field数量少,存储结构优化为数组
  • 如果field数量多,存储结构优化为HashMap结构

添加/修改数据:

hset key field vallue

获取数据

hget key field

删除数据

hdel key field1 field2..

添加/修改多个数据:

hmset key field1 value1 field2 value2 ..

获取多个数据

hmget key field1 field2 ..

获取hash表键值对的数量

hlen key 

获取hash表是否存在指定的键值对,返回0或1

hexists key field 

获取hash表所有的字段或字段值

hkeys key
hvals key
hgetall key

给hash表中指定的字段的数值增加指定的值

hincrby key field increment
hincrbyfloat key field increment

hash数据类型操作注意事项:

  • hash类型的value只能存储字符串,不允许存储其他类型

业务场景,使用redis实现购物车数据存储

1.创建一个hash表为:cart:userid:100

2.有两个field,一个用于保存商品购买的数量;一个用于保存购物车的信息

3.将购物车的信息抽取出来为一个单独的hash,field中只保存信息的编号,可以提高效率

List类型

存储多个数据,并且对数据进入存储空间的顺序进行区分;底层使用双向链表结构

添加/修改数据

#从左进入
lpush key value1 value2 ..
#从右进入
rpush key value1 value2 ..

获取数据,获取全部数据结束索引为-1

#查询区间数据 stop等于-1表示到最后一个
lrange key start stop
#根据索引查
lindex key indexs

获取list元素个数

llen key

获取并移除数据

lpop key
rpop key

规定时间内获取并移除数据,比如blpop key 10,如果10秒内获取数据就会放回,否则返回nil

blpop key timeout
brpop key timeout

移除指定数据,lrem list 1 djn,移除list中一个djn数据

 lrem key count value

Set类型

存储大量的数据,并且在查询方面提供更高的效率;底层也是hash结构,仅存储键,value为nul,代表它是无序且不可重复s

添加数据

sadd key member1 member2

获取全部数据

smembers key

删除数据

srem key member1 member2

获取集合数据个数

scard key

判断集合是否包含指定数据

sismember key member

业务场景:随机推荐某类数据,从指定的集合随机取出一部分

随机获取集合中指定数量的数据,默认是获取一个

sramdmember. key count

随机获取集合中的某个数据并移除

spop key

业务场景:求出两个用户的共同喜好,也就是交集

求两个集合的交集、并集、差集

# 也就是求出key1和key2重复的
sinter key1 key2
# 也就是求出key+key2并且去重后的
sunion key1 key2
# 也就是求出key1-key2,把相同的减掉
sdiff key1 key2

求出两个集合的交集、并集、差集并存储到指定集合中

sinterstore newkey key1 key2
sunionstore newkey key1 key2
sdiffstore newkey key1 key2

将制定数据从原始集合移动到目标集合

# source:原集合 target:目标集合 member:数据
smove source target member

Sortedset类型

在Set的数据结构上添加可排序的字段,这个字段不用来存储数据,只负责排序,也就是根据自身的特征进行排序

添加数据

zadd key score1 member1 score2 member2 ..

获取全部数据

#正序 withscores 获取数据时带上他的score值
zrange key start stop [withscores]

#倒序
zrevrange key start stop

删除数据

zrem key member

按照条件获取数据

zrangebyscore key min max
zrevrangebyscore key min max

按照条件删除数据

# 根据索引删除
zremrangebyrank key start stop
# 根据score删除
zremrangebyscore key min max

获取集合数据个数

zcard sorts
# 查询score区间之间的个数 
zcard sorts min max

通用命令

key

s查询key

删除指定key

del key

查看key是否存在

exists key

获取key的类型

type  key

扩展操作

为指定key设置有效期

expire key second
# 毫秒
pexpire key milliseconds

获取key的有效期

# 秒
ttl key
# 毫秒
pttl key

切换key从时效性转换为永久性

persist key

为key改名

rename key newkey
renamenx key newkey

对list、set、sortedset进行排序,只是排序,不会改变原数据

sort

db

随着数据越来越多,可能会导致key命名重复或者冲突,因此,Redis为每个服务提供16个数据库,从0-15,每个数据库之间的数据独立

切换数据库

select index

其他操作

#退出
quit
#测试是否链接成功,返回pong
ping
#查看有多少key
dbsize

数据移动,从当前数据库移动到其他数据库

move key db

数据清除

#清除当前数据库
flushdb
#清除所有数据库
flushalll

Jedis

Jedis是一个操作Redis的Java客户端,其他的还有Spring Data Redis等

具体的操作和Redis命令都是一样的,所以掌握了Redis命令也就会使用了

首先导入pom.xml

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

主要分为三部:创建连接 、操作数据、关闭连接

public class JedisTest {

    private Jedis jedis;

    @Before
    public void before() {
        //创建连接
        jedis = new Jedis("127.0.0.1", 6379);
    }

    //操作string
    @Test
    public void testString() {
        jedis.set("test", "testupdate");
        String test = jedis.get("test");
        System.out.println(test);
    }

  	//操作list
    @Test
    public void testList() {
        jedis.lpush("list1", "a", "b", "c");
        jedis.rpush("list1", "x");
        List<String> list1 = jedis.lrange("list1", 0, -1);
        list1.stream().forEach(System.out::println);
        System.out.println(jedis.llen("list1"));
    }

  	//操作hash
    @Test
    public void testHash() {
        Map<String, String> map = new HashMap<>();
        map.put("key1", "value1");
        map.put("key2", "value2");
        map.put("key3", "value3");
      	//批量添加使用hmset
        jedis.hmset("hash1", map);
        Map<String, String> map1 = jedis.hgetAll("hash1");
        Set<String> keySet = map1.keySet();
        keySet.stream()
                .forEach(c->{
                    System.out.println(c+"--"+map1.get(c));
                });
    }

    @After
    public void after(){
      	//关闭连接
        jedis.close();
    }
}


基于连接池获取连接,接下来写一个Jedis工具类

将配置定义在配置文件中

redis.host=127.0.0.1
redis.port=6379
redis.maxTotal=30
redis.maxIdle=10

public class JedisUtils {
    //Jedis连接池配置
    private static JedisPoolConfig config = null;
    private static String host;
    private static Integer port;
  	//最大连接数
    private static Integer maxTotal;
  	//最大活动数
    private static Integer maxIdle;
    private static JedisPool jedisPool;

  	//使用静态代码块保证只初始化一次
    static {
        Properties properties = new Properties();
        try {
         properties.load(JedisUtils.class.getClassLoader().getResourceAsStream("redis.properties"));
            host = properties.getProperty("redis.host","127.0.0.1");
            port = Integer.parseInt(properties.getProperty("redis.port","6379"));
            maxTotal = Integer.parseInt(properties.getProperty("redis.maxTotal","30"));
            maxIdle = Integer.parseInt(properties.getProperty("redis.maxIdle","10"));
            config = new JedisPoolConfig();
            config.setMaxTotal(maxTotal);
            config.setMaxIdle(maxIdle);
            //创建Jediss连池
            jedisPool = new JedisPool(config, host, port);
        } catch (IOException e) {
            System.out.println("加载文件失败");
        }
    }
		
  	//获取Jedis连接
    public static Jedis getJedis() {
        return jedisPool.getResource();
    }
}

Redis Desktop Manager

RDM是一个开源的Redis可视化客户端,项目地址

Linux安装Redis

实际工作中,我们Redis服务还是运行在Linux服务器的,所有学习Linux安装是很有必要的

下载Redis,这里是Redis下载地址

wget http://download.redis.io/releases/redis-4.0.0.tar.gz

解压

tar -zxvf redis-4.0.0.tar.gz

编译

cd redis-4.0.0
make

安装

make test

如果在make install提示你"You need tcl 8.5 or newer in order to run the Redis test",需要先安装tcl,然后再安装

wget http://downloads.sourceforge.net/tcl/tcl8.6.1-src.tar.gz  
sudo tar xzvf tcl8.6.1-src.tar.gz  -C /usr/local/  
cd  /usr/local/tcl8.6.1/unix/  
sudo ./configure  
sudo make  
sudo make install   


启动Redis

默认配置启动:

  • redis-server
  • redis-server --port 6379
  • redis-server --port 6379 -h 127.0.0.1

通过配置文件启动:

  • redis-server redis.conf

redis.conf:

#以进程方式启动,redis将以后台服务的形式存在,日志打印在指定文件中,不再打印在窗口
daemonize yes
#设置当前redis服务端口号
port 3306
#当前服务文件保存位置
dir ./data
#日志文件名
logfile 6379.log

可以创建一个 conf 目录,管理多个conf配置文件

Redis持久化

持久化:利用永久性存储介质(比如硬盘)将数据进行保存,在特定的时间将保存的数据进行恢复就成为持久化

为什么要进行持久化呢?防止数据的意外丢失,比如服务器重启导致数据丢失,持久化可以确保数据的安全

Redis有两种持久化方式:一种是持久化数据,叫做RDB,一种是持久化过程,叫做AOF

RDB

将数据进行持久化,可以手动或者自动保存数据到RDB二进制文件中,当服务器重启时恢复数据

启动方式一:save命令,手动执行保存操作,立即执行,会在data目录下生成一个dump.rdb文件

save

当服务重新启动的时候,会将文件中的数据进行恢复

save命令因为是立即执行,所以会阻塞当前的Redis服务器,直到当前RDB过程完成为止,有可能会造成长时间的阻塞,线上环境不推荐使用!!!

启动方式二:bgsave命令,手动启动保存操作,在后台执行

bgsave

bgsave原理:后台fork函数创建的子进程来完成创建rdb文件的操作

bgsave是对save造成阻塞的问题做了优化,将生成rdb文件的操作放在后台执行,Redis内部涉及rdb的操作都用bgsave完成,save命令可以放弃使用了

启动方式三:如果我们忘记了进行手动保存怎么办呢,Redis默认有save配置,当满足了条件时会自动执行,在conf文件中进行配置

#以下默认配置,可以根据业务需要进行配置
#距上次创建RDB文件已经过去900秒,且数据库总共发生1次以上的修改,执行bgsave命令
save 900 1
#距上次创建RDB文件已经过去300秒,且数据库总共发生10次以上的修改,执行bgsave命令
save 300 10
#距上次创建RDB文件已经过去60秒,且数据库总共发生1000次以上的修改,执行bgsave命令
save 60 10000

以上自动执行的操作都是通过bgsave完成的

自动执行的原理:当满足条件后会向Redis发送指令,Redis返回结果

RDB的三种启动方式说完了,接下来说一下它的优点和缺点

RDB的优点 :

  • RDB是一个压缩的二进制文件,存储效率高
  • RDB内部存储的是redis在某个时间点的数据快照,非常适用于数据备份
  • RDB恢复数据的速度要比AOF快很多
  • 应用场景:每X个小时需要对数据进行一个备份

RDB的缺点:

  • RDB无法做到实时持久化,可能会造成数据的丢失
  • bgsave命令每次运行需要执行fork函数创建子进程,会降低性能
  • Redis众多版本没有对RDB文件统一,可能会造成不兼容问题
  • 大数据量下的IO性能较低,因为基于快照思想,每次读写都是大量的数据,当数据量大,效率会非常低

AOF

将记录数据产生的过程进行持久化,重启时再执行AOF文件中的命令达到恢复数据的目的

AOF主要是为了解决数据持久化的实时性,目前是Redis持久化的主流方式

AOF写数据三种策略:

  • always(每次):每次写入操作都同步到AOF文件中,数据完全不会丢失,性能较低
  • everysec(每秒):每秒将将缓冲区的指令同步到AOF文件中,数据准确性较高,性能较高,在系统突然宕机的情况下会丢失1秒的数据
  • no(系统控制):由系统控制同步到AOF文件的周期,整体过程不可控

在conf文件开启AOF配置:

#开启AOF持久化功能
appendonly yes|no
#AOF写数据策略
appendfsync always|everysec|no

只会记录改变数据的命令,不会记录get命令

以下是aof文件存储的内容,存储的是记录数据的过程

$3
set
$4
haha
$4
haha

AOF重写

随着命令不断的写入文件,文件会越来越大,因此Redis引入了AOF重写机制压缩文件体积,AOF重写机制就是对同一个数据的若干个条命令执行结果转化成最终结果数据对应的指令进行记录。

AOF重写的规则:

  • 进程内已经超时的数据命令不再写入文件
  • 忽略无效的指令,比如set num 1 set num2 set num 3,只会写入set num 3指令
  • 对同一个数据的多条命令合并为一条命令,比如lpush list 1 , lpush list 2,合并为lpush list 1 2

作用:

  • 降低磁盘占用量
  • 提高持久化效率
  • 提高数据恢复效率

手动执行重写:

bgrewriteaof

接下来测试一下:

执行以下命令

set num 1
set num 2
set num 3

然后aof文件记录的内容

num
$1
1
*3
$3
set
$3
num
$1
2
*3
$3
set
$3
num
$1
3

然后再执行bgrewriteaof重写,返回消息Background append only file rewriting started

这个时候再看下aof文件,可以看到文件的内容少了很多,文件的大小也就小了很多

SET
$3
num
$1
3

自动执行重写,在conf文件进行配置:

自动重写触发条件设置

#当aof_current_size大于该值就重写
auto-aof-rewrite-min-size size
#当aof文件-aof_base_size/aof_base_size的百分比大于该值重写
auto-aof-rewrite-percentage percentage

自动重写触发比较参数(运行指令info Persistence获取具体信息)

#aof当前文件大小
aof_current_size
aof_base_size

自动重写触发条件

aof_current_size>=auto-aof-rewrite-min-size
aof_current_size-aof_base_size/aof_base_size >= auto-aof-rewrite-percentage percentage

AOF重写原理

如何选择

事务

Redis事务就是将一组指令放入一个队列,当执行时,按照添加顺序依次执行,不被其他线程的指令干扰,可能会有人说了,Redis不是单线程的吗?为什么会存在并发问题呢?Reids是单线程的没错,但是会有多个客户端连接Redis,每个客户端会有一个线程,会形成竞争

开启事务:此命令执行后,后续所有的指令都加入到事务中

multi

执行事务:设置事务结束的位置,同时执行事务,与multi成对使用

exec

注意:加入事务的命令没有立即执行,只有执行exec命令才会统一执行并返回 结果

取消事务:终止当前事务的定义,发生在multi后,exec之前,如果发现命令写错了可以执行该命令

discard

事务的执行流程:

事务注意事项:

  • 如果命令中有语法错误,那么所有的指令将都不执行

  • 如果命令正确,但是语法错误,那么只会运行正确的命令,错误的命令不会执行

    multi
    set name djn
    get name 
    lpush name 1 2 3
    
    
  • 已经执行完毕的命令对应的数据不会回滚,需要自己在代码中回滚

锁:

多个客户端操作同一个数据 ,但是只希望修改一次,可以在操作之前锁定要操作的数据,一旦发生变化,终止当前操作

对 key 添加监视锁,在执行exec前如果key发生了变化,终止事务执行

watch key1 [key2……]

取消对所有 key 的监视

unwatch

分布式锁:

超卖问题

watch已经无法解决该问题,因为watch是监控有没有发生改变,而这里是要监控一个值变不变,而不是其他人能不能改这个值

使用setnx设置一个公共锁

setnx lock-key value

利用setnx命令的返回值特征,有值则返回设置失败,无值则设置成功

操作完毕通过del lock-key 释放锁

这种方案是一种设计概念,依赖规范性,需要保证锁是同一个

当某个用户获取到了分布式锁但是这个时候对应的客户端宕机了,怎么解决

  • 由于锁操作是由用户控制加锁解锁,那必然会存在加锁后未解锁的风险

  • 需要解锁操作不仅由用户控制,系统应该给出一个保底的方案

解决方案:

使用expire给锁一个时间,如果指定的时间用户没有释放锁,系统就帮它释放

expire local-key seconds
pexpire local-key seconds

删除策略

这里的删除策略针对的是有时效性能的数据,我们知道Reids中 -2 代表 已经过期的数据 或 被删除的数据 或 未定义的数据,那么过期的数据真的删除了吗

1.定时删除

给key设置一个过期时间,时间到达时,由定时任务立即执行对key的删除操作

优点:节约内存,到时就删除,快速释放掉不必要的内存占用

缺点:CPU压力大,无论CPU此时负载多高,均占用CPU,会影响redis服务的响应时间

总结:用时间换空间

2.惰性删除

数据到达过期时间,不做处理。等下次访问该数据时,如果未过期,返回数据,如果已过期,就删除

优点:节约CPU性能,发现必须删除的时候才删除

缺点:内存压力很大,出现长期占用内存的数据

总结:用空间换时间

3.定期删除

中折方案;周期性轮询redis库中的时效性数据,采用随机抽取的策略,利用过期数据占比的方式控制删除频度

  • 内存压力不是很大,长期占用内存的冷数据会被持续清理

总结:随机抽查进行删除

4.逐出策略

这里的逐出策略指的是所有数据,当Redis内存满了,而此时还有新数据添加,怎么办?

Redis使用内存存储数据,在执行每一个命令前,会调用freeMemoryIfNeeded()检测内存是否充足。如

果内存不满足新加入数据的最低存储要求,redis要临时删除一些数据为当前指令清理存储空间。清理数据

的策略称为逐出算法。

有8种策略:

在conf配置文件中配置

maxmemory-policy vloatile-lru

redis.conf配置

设置服务器以守护进程方式执行,日志不打印在窗口

daemonize yes|no

主从复制

单机redis的风险与问题

问题1.机器故障

  • 现象:硬盘故障、系统崩溃

  • 本质:数据丢失,很可能对业务造成灾难性打击

  • 结论:基本上会放弃使用redis.

问题2.容量瓶颈

  • 现象:内存不足,从16G升级到64G,从64G升级到128G,无限升级内存

  • 本质:穷,硬件条件跟不上

  • 结论:放弃使用redis

  • 结论:

为了避免单点Redis服务器故障,准备多台服务器,互相连通。将数据复制多个副本保存在不同的服

务器上,连接在一起,并保证数据是同步的。即使有其中一台服务器宕机,其他服务器依然可以继续

提供服务,实现Redis的高可用,同时实现数据冗余备份。

主从连接方式:

方式一:客户端发送命令

slaveof masterip masterport

方式二:从库启动服务器参数

redis-server --slaveof masterip masterport

方式三:在conf文件中 配置

slaveof masterip masterport

数据同步阶段工作流程

1.slave向master发送PSYNC2指令

2.master执行bgsave命令生成RDB快照文件,同时还会客户端接收到的所有命令写入缓存区,通过socket发送给slave

3.slave接收 RDB文件恢复数据

4.slave发送命令告知 masterRDB恢复完成

5.master发送缓冲区信息

6.slave接收并恢复

数据同步阶段包含全量复制和部分复制:

  • 全量复制:从发送指令开始以前的数据
  • 部分复制:进行RDB过程中的数据

如何保证redis中存放的都是热点数据

限定 Redis 的内存,当内存满了就会执行数据淘汰策略,所以,计算一下50W数据大约占的内存,然后配置,并将淘汰策略allkeys-lru或者volatile-lru

缓存预热

posted @ 2020-04-27 17:09  范特西-  阅读(134)  评论(0)    收藏  举报