Redis基础

Redis基础

参考:
1.https://www.jianshu.com/p/56999f2b8e3b
2.https://www.runoob.com/redis/redis-lists.html
3.http://how2j.cn/k/redis/redis-download-install/1367.html
4.https://www.cnblogs.com/EasonJim/p/7803067.html
5.https://blog.csdn.net/f641385712/article/details/84679456
6.https://blog.csdn.net/wwrzyy/article/details/85089463

源代码地址

概念

Redis的引入

在我们日常的Java Web开发中,无不都是使用数据库来进行数据的存储,由于一般的系统任务中通常不会存在高并发的情况,所以这样看起来并没有什么问题,可是一旦涉及大数据量的需求,比如一些商品抢购的情景,或者是主页访问量瞬间较大的时候,单一使用数据库来保存数据的系统会因为面向磁盘,磁盘读/写速度比较慢的问题而存在严重的性能弊端,一瞬间成千上万的请求到来,需要系统在极短的时间内完成成千上万次的读/写操作,这个时候往往不是数据库能够承受的,极其容易造成数据库系统瘫痪,最终导致服务宕机的严重生产问题

为了克服上述的问题,Java Web项目通常会引入NoSQL技术,这是一种基于内存的数据库,并且提供一定的持久化功能。
RedisMongoDB是当前使用最广泛的NoSQL,而就Redis技术而言,它的性能十分优越,可以支持每秒十几万此的读/写操作,其性能远超数据库,并且还支持集群、分布式、主从同步等配置,原则上可以无限扩展,让更多的数据存储在内存中,更让人欣慰的是它还支持一定的事务能力,这保证了高并发的场景下数据的安全和一致性

什么是Redis

Redis是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。
换句话说,Redis就像是一个HashMap,不过不是在JVM中运行,而是以一个独立进程的形式运行。
一般说来,会被当作缓存使用。 因为它比数据库(mysql)快,所以常用的数据,可以考虑放在这里,这样就提高了性能。

Redis在Java Web的应用

  • 存储缓存用的数据
  • 需要高速读/写的场合使用它快速读/写

缓存

如果日常数据库访问中,读操作比写操作要大得多,使用数据库时需要去磁盘把对应数据索引取出来,这是一个相对较慢的过程

如果我们把数据放在Redis中,也就是直接放在内存中,让服务端直接去读取内存中的数据,这样速度明显会快上不少,并且会极大减小数据库的压力,但是使用内存进行数据存储开销也是比较大的,限于成本的原因,一般只是使用Redis存储一些常用和主要的数据,比如用户登录的信息等

一般使用Redis进行存储时,我们需要从以下几个方面来考虑:

  • 业务数据常用吗?命中率如何?如果命中率低,就没有必要写入缓存
  • 该业务数据是读操作多,还是写操作多?如果写操作多,频繁需要写入数据库,也没有必要使用缓存
  • 业务数据大小如何?如果要存储几百兆字节的文件,会给缓存带来很大压力,这样也没有必要

对于写操作,要分别写入数据库和Redis,所以对写次数远大于读次数的情况,就没有必要使用Redis

快速读/写

在如今的互联网中,越来越多的存在高并发的情况,比如天猫双11、抢红包、抢演唱会门票等,这些场合都是在某一个瞬间或者是某一个短暂的时刻有成千上万的请求到达服务器,如果单纯的使用数据库来进行处理,就算不崩,也会很慢的,轻则造成用户体验极差用户量流失,重则数据库瘫痪,服务宕机,而这样的场合都是不允许的!

  • 当一个请求到达服务器,只是把业务数据在Redis上进行读写,而没有对数据库进行任何操作,这样就能大大提高读写的速度,从而满足高速响应的需求
  • 在一个请求操作完Redis的读/写后,会去判断该高速读/写业务是否结束,如果结束,就触发事件将Redis的缓存的数据以批量的形式一次性写入数据库,从而完成持久化的工作

运行Redis

Windows的Redis安装

在命令行参数运行redis-server.exe和redis-cli.exe即可,Linux也类似,略

配置Redis

Redis 的配置文件位于 Redis 安装目录下,文件名为 redis.conf(Windows 名为 redis.windows.conf)
可以通过 CONFIG 命令查看或设置配置项
CONFIG GET * 获取所有配置项
CONFIG SET CONFIG_SETTING_NAME NEW_CONFIG_VALUE 命令可以修改配置

配置文件参数如下:

配置项 说明
daemonize no Redis 默认不是以守护进程的方式运行,可以通过该配置项修改,使用 yes 启用守护进程(Windows 不支持守护线程的配置为 no )
pidfile /var/run/redis.pid 当 Redis 以守护进程方式运行时,Redis 默认会把 pid 写入 /var/run/redis.pid 文件,可以通过 pidfile 指定
port 6379 指定 Redis 监听端口,默认端口为 6379
bind 127.0.0.1 绑定的主机地址
timeout 300 当客户端闲置多长时间后关闭连接,如果指定为 0,表示关闭该功能
loglevel notice 指定日志记录级别,Redis 总共支持四个级别:debug、verbose、notice、warning,默认为 notice
logfile stdout 日志记录方式,默认为标准输出,如果配置 Redis 为守护进程方式运行,而这里又配置为日志记录方式为标准输出,则日志将会发送给 /dev/null
databases 16 设置数据库的数量,默认数据库为0,可以使用SELECT 命令在连接上指定数据库id
save <seconds> <changes> Redis 默认配置文件中提供了三个条件: save 900 1 save 300 10 save 60 10000 分别表示 900 秒(15 分钟)内有 1 个更改,300 秒(5 分钟)内有 10 个更改以及 60 秒内有 10000 个更改。 指定在多长时间内,有多少次更新操作,就将数据同步到数据文件,可以多个条件配合
rdbcompression yes 指定存储至本地数据库时是否压缩数据,默认为 yes,Redis 采用 LZF 压缩,如果为了节省 CPU 时间,可以关闭该选项,但会导致数据库文件变的巨大
dbfilename dump.rdb 指定本地数据库文件名,默认值为 dump.rdb
dir ./ 指定本地数据库存放目录
slaveof <masterip> <masterport> 设置当本机为 slav 服务时,设置 master 服务的 IP 地址及端口,在 Redis 启动时,它会自动从 master 进行数据同步
masterauth <master-password> 当 master 服务设置了密码保护时,slav 服务连接 master 的密码
requirepass foobared 设置 Redis 连接密码,如果配置了连接密码,客户端在连接 Redis 时需要通过 AUTH <password> 命令提供密码,默认关闭
maxclients 128 设置同一时间最大客户端连接数,默认无限制,Redis 可以同时打开的客户端连接数为 Redis 进程可以打开的最大文件描述符数,如果设置 maxclients 0,表示不作限制。当客户端连接数到达限制时,Redis 会关闭新的连接并向客户端返回 max number of clients reached 错误信息
maxmemory <bytes> 指定 Redis 最大内存限制,Redis 在启动时会把数据加载到内存中,达到最大内存后,Redis 会先尝试清除已到期或即将到期的 Key,当此方法处理 后,仍然到达最大内存设置,将无法再进行写入操作,但仍然可以进行读取操作。Redis 新的 vm 机制,会把 Key 存放内存,Value 会存放在 swap 区
appendonly no 指定是否在每次更新操作后进行日志记录,Redis 在默认情况下是异步的把数据写入磁盘,如果不开启,可能会在断电时导致一段时间内的数据丢失。因为 redis 本身同步数据文件是按上面 save 条件来同步的,所以有的数据会在一段时间内只存在于内存中。默认为 no
appendfilename appendonly.aof 指定更新日志文件名,默认为 appendonly.aof
appendfsync everysec 指定更新日志条件,共有 3 个可选值:no:表示等操作系统进行数据缓存同步到磁盘(快),always:表示每次更新操作后手动调用 fsync() 将数据写到磁盘(慢,安全),everysec:表示每秒同步一次(折中,默认值)
vm-enabled no 指定是否启用虚拟内存机制,默认值为 no,简单的介绍一下,VM 机制将数据分页存放,由 Redis 将访问量较少的页即冷数据 swap 到磁盘上,访问多的页面由磁盘自动换出到内存中
vm-swap-file /tmp/redis.swap 虚拟内存文件路径,默认值为 /tmp/redis.swap,不可多个 Redis 实例共享
vm-max-memory 0 将所有大于 vm-max-memory 的数据存入虚拟内存,无论 vm-max-memory 设置多小,所有索引数据都是内存存储的(Redis 的索引数据 就是 keys),也就是说,当 vm-max-memory 设置为 0 的时候,其实是所有 value 都存在于磁盘。默认值为 0
vm-page-size 32 Redis swap 文件分成了很多的 page,一个对象可以保存在多个 page 上面,但一个 page 上不能被多个对象共享,vm-page-size 是要根据存储的 数据大小来设定的,作者建议如果存储很多小对象,page 大小最好设置为 32 或者 64bytes;如果存储很大大对象,则可以使用更大的 page,如果不确定,就使用默认值
vm-pages 134217728 设置 swap 文件中的 page 数量,由于页表(一种表示页面空闲或使用的 bitmap)是在放在内存中的,在磁盘上每 8 个 pages 将消耗 1byte 的内存
vm-max-threads 4 设置访问swap文件的线程数,最好不要超过机器的核数,如果设置为0,那么所有对swap文件的操作都是串行的,可能会造成比较长时间的延迟。默认值为4
glueoutputbuf yes 设置在向客户端应答时,是否把较小的包合并为一个包发送,默认为开启
hash-max-zipmap-entries 64;hash-max-zipmap-value 512 指定在超过一定的数量或者最大的元素超过某一临界值时,采用一种特殊的哈希算法
activerehashing yes 指定是否激活重置哈希,默认为开启
include /path/to/local.conf 指定包含其它的配置文件,可以在同一主机上多个Redis实例之间使用同一份配置文件,而同时各个实例又拥有自己的特定配置文件

数据类型

Redis支持5种数据类型,分别为:String,List,Hash,Set,Sorted Set

类型 简介 特性 场景
String 二进制安全 可以包含任何数据,比如jpg图片或者序列化的对象,一个键最大能存储512M
Hash 键值对集合,即编程语言中的Map类型 适合存储对象,并且可以像数据库中update一个属性一样只修改某一项属性值 存储、读取、修改用户属性
List 链表(双向链表) 增删快,提供了操作某一段元素的API 1,最新消息排行等功能(比如朋友圈的时间线) 2,消息队列
Set 哈希表实现,元素不重复 1、添加、删除,查找的复杂度都是O(1) 2、为集合提供了求交集、并集、差集等操作 1、共同好友 2、利用唯一性,统计访问网站的所有独立ip 3、好友推荐时,根据tag求交集,大于某个阈值就可以推荐
Sorted Set 将Set中的元素增加一个权重参数score,元素按score有序排列 数据插入集合时,已经进行天然排序 1、排行榜 2、带权重的消息队列

Sorted Set的用法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
redis 127.0.0.1:6379> ZADD runoobkey 1 redis
(integer) 1
redis 127.0.0.1:6379> ZADD runoobkey 2 mongodb
(integer) 1
redis 127.0.0.1:6379> ZADD runoobkey 3 mysql
(integer) 1
redis 127.0.0.1:6379> ZADD runoobkey 3 mysql
(integer) 0
redis 127.0.0.1:6379> ZADD runoobkey 4 mysql
(integer) 0
redis 127.0.0.1:6379> ZRANGE runoobkey 0 10 WITHSCORES

1) "redis"
2) "1"
3) "mongodb"
4) "2"
5) "mysql"
6) "4"

Redis HyperLogLog

Redis 在 2.8.9 版本添加了 HyperLogLog 结构。

Redis HyperLogLog 是用来做基数统计的算法,HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定 的、并且是很小的。

在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基 数。这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比。

但是,因为 HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身,所以 HyperLogLog 不能像集合那样,返回输入的各个元素。

1
2
3
4
5
6
7
8
redis 127.0.0.1:6379> PFADD runoobkey "redis"
1) (integer) 1
redis 127.0.0.1:6379> PFADD runoobkey "mongodb"
1) (integer) 1
redis 127.0.0.1:6379> PFADD runoobkey "mysql"
1) (integer) 1
redis 127.0.0.1:6379> PFCOUNT runoobkey
(integer) 3

Redis命令

redis-cli -h host -p port -a password 连接远程服务

中文乱码的话 redis-cli.exe --raw启动

官方命令手册
redis命令参考
菜鸟教程的redis命令

发布订阅

Redis支持发布订阅,发送者发送消息,订阅者接收消息,使用PUBLISH可以指定频道发布消息,SUBSCRIBE可以订阅频道,详情见手册

事务

Redis事务可以一次执行多个命令, 并且带有以下三个重要的保证:

  • 批量操作在发送 EXEC 命令前被放入队列缓存
  • 收到 EXEC 命令后进入事务执行,事务中任意命令执行失败,其余的命令依然被执行
  • 在事务执行过程,其他客户端提交的命令请求不会插入到事务执行命令序列中

一个事务从开始到执行会经历以下三个阶段:

  • 开始事务
  • 命令入队
  • 执行事务
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
redis 127.0.0.1:6379> MULTI
OK
redis 127.0.0.1:6379> SET book-name "Mastering C++ in 21 days"
QUEUED
redis 127.0.0.1:6379> GET book-name
QUEUED
redis 127.0.0.1:6379> SADD tag "C++" "Programming" "Mastering Series"
QUEUED
redis 127.0.0.1:6379> SMEMBERS tag
QUEUED
redis 127.0.0.1:6379> EXEC
1) OK
2) "Mastering C++ in 21 days"
3) (integer) 3
4) 1) "Mastering Series"
2) "C++"
3) "Programming"

单个 Redis 命令的执行是原子性的,但 Redis 没有在事务上增加任何维持原子性的机制,所以 Redis 事务的执行并不是原子性的

事务可以理解为一个打包的批量执行脚本,但批量指令并非原子化的操作,中间某条指令的失败不会导致前面已做指令的回滚,也不会造成后续的指令不做

It’s important to note that even when a command fails, all the other commands in the queue are processed – Redis will not stop the processing of commands.

Redis 数据备份与恢复

SAVA命令会在redis安装目录创建dump.rdb文件
把备份文件移动到redis安装目录并启动服务即可恢复数据,CONFIG GET dir获取redis目录
BGSAVE也是创建Redis备份文件(异步)

Redis安全

可以通过Redis的配置文件设置密码参数,这样客户端连接Redis就要密码验证

CONFIG get requirepass查看是否设置密码验证
CONFIG set requirepass "youpass"设置密码
AUTH password使用password连接Redis

其他命令

Redis还有LUA脚本命令,连接命令,服务器命令,测试性能命令等,包括执行LUA脚本,后台异步保存当前数据库的数据到磁盘,同步保存数据到硬盘,关闭客户端连接等,详情见文档

Jedis

POM添加依赖

1
2
3
4
5
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.1.0</version>
</dependency>

创建Jedis对象

1
Jedis jedis = new Jedis("localhost");

然后就可以用Java方法来操纵Redis了

使用Redis连接池

跟数据库连接池相同,Java Redis也提供了类redis.clients.jedis.JedisPool来管理我们的Redis连接池对象,并且我们可以使用redis.clients.jedis.JedisPoolConfig来对连接池进行配置,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
JedisPool pool;
Jedis jedis;
JedisPoolConfig poolConfig = new JedisPoolConfig();
// 最大空闲数
poolConfig.setMaxIdle(50);
// 最大连接数
poolConfig.setMaxTotal(100);
// 最大等待毫秒数
poolConfig.setMaxWaitMillis(20000);
// 使用配置创建连接池
pool = new JedisPool(poolConfig, "localhost");
// 从连接池中获取单个连接
jedis = pool.getResource();
// 如果需要密码
//jedis.auth("password");

Redis Spring集成

Redis 只能支持六种数据类型(string/hash/list/set/zset/hyperloglog)的操作,但在 Java 中我们却通常以类对象为主,所以在需要 Redis 存储的五中数据类型与 Java 对象之间进行转换,如果自己编写一些工具类,比如一个角色对象的转换,还是比较容易的,但是涉及到许多对象的时候,这其中无论工作量还是工作难度都是很大的,所以总体来说,就操作对象而言,使用 Redis 还是挺难的,好在 Spring 对这些进行了封装和支持。

上面所说的Redis无法操作对象的问题,无法在基础类型和Java对象之间方便的转换,但在Spring中,这些问题都可以通过使用RedisTemplate得到解决

  1. 先引入jar包

Spring Data Redis 2.x binaries require JDK level 8.0 and above and Spring Framework 5.0.7.RELEASE and above.

spring-data-redis 2.0以上需要jdk8和spring5以上,因为我的Spring版本是4.x,所以我使用的spring-data-redis版本如下,同时为了兼容,我把jedis也降级为2.x

(2.x的spring-data-redis与spring 4不兼容,3.x的jedis与1.x的spring-data-redis不兼容,我人晕了…)
最终可以兼容的spring-data-redis与jedis版本如下:

1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.1</version>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>1.8.22.RELEASE</version>
</dependency>
  1. 使用Spring配置JedisPoolConfig对象
1
2
3
4
5
6
7
8
<bean class="redis.clients.jedis.JedisPoolConfig" id="jedisPoolConfig" >
<!--最大空闲数-->
<property name="maxIdle" value="50" />
<!--最大连接数-->
<property name="maxTotal" value="100" />
<!--最大等待时间-->
<property name="maxWaitMillis" value="20000" />
</bean>
  1. 为连接池配置工厂模型
1
2
3
4
5
<bean id="jedisConnFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<property name="hostName" value="localhost" />
<property name="port" value="6379" />
<property name="poolConfig" ref="jedisPoolConfig" />
</bean>
  1. 配置RedisTemplate

普通的连接根本没有办法直接将对象直接存入 Redis 内存中,我们需要替代的方案:将对象序列化(可以简单的理解为继承Serializable接口)。我们可以把对象序列化之后存入Redis缓存中,然后在取出的时候又通过转换器,将序列化之后的对象反序列化回对象,这样就完成了我们的要求

RedisTemplate可以帮助我们完成这份工作,它会找到对应的序列化器去转换Redis的键值

1
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate" p:connection-factory-ref="jedisConnFactory" />
  1. 编写测试的POJO类
1
2
3
4
5
6
7
public class Student implements Serializable {

private String name;
private int age;

// getter setter toString()
}
  1. 编写测试类,获取redisTemplate对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@RunWith(BlockJUnit4ClassRunner.class)
public class TestSpringJedis extends UnitTestBase {
// UnitTestBase可以见我Spring相关的博客

public TestSpringJedis() {
super("classpath:spring-data-redis.xml");
}

@Test
public void test() {
RedisTemplate redisTemplate = super.getBean("redisTemplate");
Student student = new Student();
student.setName("aha");
student.setAge(22);
redisTemplate.opsForValue().set("student", student);
Student student1 = (Student) redisTemplate.opsForValue().get("student");
System.out.println(student1);
}
}

RedisTemplate

Spring封装了RedisTemplate对象来进行对Redis的各种操作,它支持所有的Redis原生的api。RedisTemplate位于spring-data-redis包下

1
2
3
4
5
redisTemplate.opsForValue();//操作字符串
redisTemplate.opsForHash();//操作hash
redisTemplate.opsForList();//操作list
redisTemplate.opsForSet();//操作set
redisTemplate.opsForZSet();//操作有序set

StringRedisTemplate与RedisTemplate的区别:

  • 两者的关系是StringRedisTemplate继承RedisTemplate。
  • 两者的数据是不共通的;也就是说StringRedisTemplate只能管理StringRedisTemplate里面的数据,RedisTemplate只能管理RedisTemplate中的数据。
  • SDR默认采用的序列化策略有两种,一种是String的序列化策略,一种是JDK的序列化策略。
    • StringRedisTemplate默认采用的是String的序列化策略,保存的key和value都是采用此策略序列化保存的。
    • RedisTemplate默认采用的是JDK的序列化策略,保存的key和value都是采用此策略序列化保存的。

RedisTemplate的序列化问题

RedisTemplate可以帮助我们序列化对象,但使用默认的配置,会发现序列化对象在redis-cli中查找不到,如:

1
2
3
4
5
6
@Test
public void test() {
RedisTemplate<String, String> redisTemplate = super.getBean("redisTemplate");
redisTemplate.opsForValue().set("myspringredis", "Hello World From Spring");
System.out.println(redisTemplate.opsForValue().get("myspringredis"));
}

会发现可以正常输出,但只要在redis-cli上GET myspringredis,发现输出的是null,使用keys *查看,发现有一个类似的\xac\xed\x00\x05t\x00\rmyspringredis
网上查阅资料发现,原来是序列化的问题,RedisTemplate会默认使用JdkSerializationRedisSerializer,这个序列化器会导致上述问题的出现,它的优点和缺点如下:

  • 它要求存储的对象都必须实现java.io.Serializable接口,他存储二进制数据,对开发者不友好
  • 因为他存储的为二进制。但是有时候,我们的Redis会在一个项目的多个project中共用,这样如果同一个可以缓存的对象在不同的project中要使用两个不同的key来分别缓存,既麻烦,又浪费
  • 序列化后的结果非常庞大,是JSON格式的5倍左右,这样就会消耗redis服务器的大量内存
  • 优点是反序列化时不需要提供(传入)类型信息(class)

要解决上述问题,可以使用StringRedisSerializer,它是StringRedisTemplate默认的序列化方法,如下所示配置RedisTemplate

1
2
3
4
5
6
7
8
<bean id="stringRedisSerializer" class="org.springframework.data.redis.serializer.StringRedisSerializer" />

<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate" p:connection-factory-ref="jedisConnFactory" >
<property name="valueSerializer" ref="stringRedisSerializer" />
<property name="keySerializer" ref="stringRedisSerializer" />
<property name="hashValueSerializer" ref="stringRedisSerializer" />
<property name="hashKeySerializer" ref="stringRedisSerializer" />
</bean>

这样,序列化对象在redis-cli就可以成功找到了