Redis5:讲义位置
什么是NOSQL?
为什么用NOSQL?
NOSQL的劣势?
Redis是一个key-value结构的数据库,可以把redis看做是一个大的Map
Redis的作用:
Cache(缓存):存在内存中的数据
使用Cache
user发起请求----控制器----Service----Redis
user发起请求----控制器----Service—Redis(没有数据)
Dao—数据库
Redi客户端:
Redis客户端的功能:
发送redis命令,编写redis命令
显示服务端的处理结果,显示数据
用户开发人员—在客户端中编写redis-----客户端把命令发送给服务端
从服务端把处理结果发给—客户端—把数据呈现给用户
有三个文件
使用redis
启动redis的服务端
启动redis客户端,双击执行 redis-cli.exe
第一个命令 ,创建字符类型的数据
set key value : 等同于 insert into
get key : 获取key的值, 等同于select
获取redis的安装 redis-4.0.13.tar.gz(redis.io)
上传安装文件到linux
解压缩文件 tar -zxvf redis-4.0.13.tar.gz -C /usr/local
开始安装gcc , yum -y install gcc
使用gcc 编译redis的源文件, 使用make
在redis的安装根目录下,src的上级目录中执行 make
启动redis
/usr/local/redis-4.0.13/src/redis-server
/usr/local/redis-4.0.13/redis.conf
启动客户端
绝对路径 /usr/local/redis-4.0.13/src/redis-cli
关闭redis服务端:
sudo apt install redis-server
设置密码
sudo vim /etc/redis/redis.config
# 找到 # requirepass foobared这行,将 # 去掉。设置密码
requirepass 123
开启远程访问
#1. 关闭防火墙(上网查)
#2.
sudo vim /etc/redis/redis.config
#2.1 使用注释符号 # 注释掉bind 127.0.0.1这行
#2.2 将 protected-mode yes 改为 no
protected-mode no
redis服务控制命令
/etc/init.d/redis-server start # 启动服务端。(后台启动:通过 ps -ef | gref redis命令可以查看)
# 在 /bin目录下执行 ./redis-cli 即可启动客户端
/etc/init.d/redis-server stop # 关闭服务端
/etc/init.d/redis-server restart #重启
Ubuntu上的 Redis的安装路径在 /bin目录下
redis.config文件目录在:/etc/redis
在window通过远程访问的方式利用图形用户界面 访问虚拟机上的 redis
用xshell(redis的服务端已开,客户端需手动打开,在 /bin/redis-cli)
讲义2.5节
输入 ping,redis 给我们返回 PONG,表示 redis 服务运行正常
Redis 默认使用 16 个库,从 0 到 15。 对数据库个数的修改,在 redis.conf 文件中
databases 16

index 取值 [0, 15]
使用 db(index) 数据库
讲义2.6节
keys pattern
通配符:
语法:exists key [key…]
作用:判断 key 是否存在
返回值:整数
使用单个key,存在 key 返回 1,其他返回 0。
使用多个 key,返回这些key中存在的 key 的数量
语法:expire key seconds
作用:设置 key 的生存时间,超过时间,key 自动删除(前提是key必须存在)。单位是秒。
返回值:设置成功返回数字 1,其他情况是 0 。
set redlight 张三
expire redlight 5 # 5s后 key redlight自动删除
ttl redlight # 查看key的剩余生存时间
用于实现验证码的倒计时操作
set code1234 1234 # 验证码为1234
expire code1234 60 # 设置验证码的存活时间
ttl code1234 # 查询验证码的剩余存活时间:>=0 key没有过期, 验证码是有效;<0 key过期,验证码失效的
语法:ttl key
作用:以秒为单位,返回 key 的剩余生存时间(ttl: time to live)
返回值:
-1 :没有设置 key 的生存时间, key 永不过期。
-2:key 不存在
数字:key 的剩余时间,秒为单位
语法:type key
作用:查看 key 所存储值value的数据类型
返回值:字符串表示的数据类型
语法:del key [key…]
作用:删除存在的 key,不存在的 key 忽略。
返回值:数字,删除的 key 的数量。
讲义第3节
以下内容,我使用了linux上的redis(本机在Ubuntu),因为linux上的redis服务已经在后台启动了;然后使用RDM远程访问Ubuntu上的redis
字符串类型是 Redis 中最基本的数据类型,它能存储任何形式的字符串,包括二进制数据,序列化后的数据,JSON 格式数据。
一个String类型最多能够存储512M数据
redis区分大小写。key大小写不同是俩个不同的key
将字符串值 value 设置到 key 中
语法:set key value
向已经存在的 key 设置新的 value,会覆盖原来的值
查看已经插入的 key:keys *
set k2 "linux" # k2 = "linux"
set k2 "hello world" # k2 = "hello world"
#存放json字符串(将java对象转为json字符串存到redis)
set k3 "{'name':'lisi', 'age':'20'}"
将 key 中储存的数字值加 1,并返回 value 加1后的值
如果 key 不存在,则会创建key,并且 key 的值先被初始化为 0 再执行incr 操作(只能对数字类型的数据操作)
语法:incr key
set index 1
incr index
get index
可作为计算器使用
将 key 中储存的数字值减1,
如果 key 不存在,则么创建key,并且 key 的值先被初始化为 0。再执行 decr 操作(只能对数字类型的数据操作)
语法:decr key
语法:append key value
说明:如果 key 存在,则将 value 追加到 key 原来旧值的末尾
如果 key 不存在,则将 key 设置值为 value
返回值:追加字符串之后的总长度
语法:getrange key start end
作用:获取 key 中字符串值从下标 start 开始到 end 结束的子字符串,包括 start 和 end, 负数表示从字符串的末尾开始,-1 表示最后一个字符(很像python)。【截取字串 [start, end],无论正负】
返回值:截取的子字符串。
set school bjpowernode
getrange school 7 10
getrange school -4 -1 # 这里不能写成 -1 -4
getrange school 0 -1 # 相当于 get school
hash类型类似于:key field value[field value…]。类似于k v(v =
[ …]。即 Map > hash类型一般用于存储对象
// 对象封装的类
class Student {
private String name;
private String age;
private String sex;
}
// data代表一个对象,封装了一个对象的属性
Map<String,String> data = new HashMap();
data.put("name","zs");
data.put("age","20);
data.put("sex","男");
// map存储这类对象的集合
Map<String,Map<String,String> map = new ..
map.put("stu",data);
map:就是redis中的hash类型
user:叫做hash类型的key
name,age,sex:叫做field
语法:hset hash表的key field value
作用:将哈希表 key 中的域 field 的值设为 value,
如果 key 不存在,则新建 hash 表,执行赋值。如果有 field,则覆盖值。
返回值:
hash表中一个key可以包含多个 field value(键值对),相当于Map集合中V的类型还是一个Map,即V还是一个键值对
语法:hmset key field value [field value…]
说明:同时将多个 field-value (域-值)设置到哈希表 key 中,此命令会覆盖已经存在的 field,
hash 表 key 不存在,创建空的 hash 表,执行 hmset.
返回值:设置成功返回 ok,如果失败返回一个错误
rpop:从list右边弹出数据显示(会删除)
lrange key 0 -1 获取list中所有元素
唯一 无序
zset 的每个元素都会关联一个分数(分数可以重复),redis 通过分数来为集合中的成员进行从小到大的排序。如果分数相同,按集合中的成员的字典顺序排序
语法:zrangebyscore key min max [WITHSCORES ] [LIMIT offset count]
作用:获取有序集 key 中,所有 score 值介于 min 和 max 之间(包括 min 和 max)的成员,有序成员是按递增(从小到大)排序。min ,max 是包括在内,使用符号"( "表示不包括。
min, max 可以使用 -inf ,+inf 表示最小和最大。limit 用来限制返回结果的数量和区间(分页)。withscores 显示 score 和 value
返回值:指定区间的集合数据
zrangebyscore sal 2000 (4000 withscores # [2000, 4000)
语法:zrevrangebyscore key max min [WITHSCORES ] [LIMIT offset count]
作用:返回有序集 key 中, score 值介于 max 和 min 之间(默认包括等于 max 或 min )的所有
的成员。有序集成员按 score 值递减(从大到小)的次序排列。其他同 zrangebyscore
讲义第4节
事务是指一系列操作步骤,这一系列的操作步骤,要么完全地执行,要么完全地不执行。类似于OS中的原子操作
redis是支持事务的,但redis的事务是不支持自动回滚的。
multi # 开启事务
sadd name zhangsan
sadd name lisi # 命令入队
exec # 提交事务
事务执行exec之前,入队命令错误(一般是语法错误),会放弃事务。(事务块中所有指令都不执行)
multi # 开启事务
set name zhangsan
get name
incr k1 k2 # 入对命令语法错误
exec # 提交事务。但事务块中所有命令都不执行
事务执行exec之后,命令错误(不是语法错误,是逻辑错误),事务会提交
multi # 开启事务
set name zhangsan
# 该命令语法并没有错误,但在这个脚本块中,name的类型是string,lpop指令是操作list类型的数据的。执行的时候出错
lpop name # 执行失败
exec # set name zhangsan 正常执行
get name
set age 10
multi # 开启事务
set age 20
discard # 放弃事务
get age # 还是10,事务没有执行
与mysql的锁机制不同。为了提高程序的性能,并在一定程度上解决 多事务执行时数据不安全问题,redis采用watch机制
WATCH 机制原理:
WATCH 机制:使用 WATCH 监视一个或多个 key , 跟踪 key 的 value 修改情况,如果有key 的 value 值在事务 EXEC 执行之前被修改了,整个事务被取消(让别人先做,类似于多线程中的主动退让)。EXEC 返回提示信息,表示事务已经失败。
WATCH 机制使得事务 EXEC 变的有条件,事务只有在被 WATCH 的 key 没有修改的前提下才能执行。不满足条件,事务被取消。使用 WATCH 监视了一个带过期时间的键,那么即使这个键过期了,事务仍然可以正常执行
大多数情况下,不同的客户端会访问不同的键,相互同时竞争同一 key 的情况一般都很少,watch 能很好解决数据冲突的问题。
何时取消key的监视
①WATCH 命令可以被调用多次。对键的监视从 WATCH 执行之后开始生效,直到调用 EXEC 为止。不管事务是否成功执行,对所有键的监视都会被取消。
②当客户端断开连接时,该客户端对键的监视也会被取消。
③UNWATCH 命令可以手动取消对所有键的监视
准备工作:使用xshell开启俩个连接(连接虚拟机)。并且俩个连接都打开redis客户端(模拟俩个请求)
A 客户端(红色):WATCH 某个 key,同时执行事务;B 客户端(黄色):对 A 客户端 WATCH 的 key 修改其 value 值。
1) 在 A 客户端:set age 10
2) 在 A 客户端监视age: watch age
3) 在 A 客户端开启事务: multi
4) 在 A 客户端修改:set age 20
5) 在 B 客户端修改:set age 100
6) 在 A 客户端提交事务:exec
7) 在 A 客户端查看 age 值:get age
结论:
A 客户端执行的事务没有提交,因为 WATCH 的 age 的值已经被修改了,所以A客户端放弃事务。(乐观锁)
持久化可以理解为存储,就是将数据存储到一个不会丢失的地方,如果把数据放在内存中,电脑关闭或重启数据就会丢失,所以放在内存中的数据不是持久化的,而放在磁盘就算是一种持久化
redis 的数据存储在内存中,内存是瞬时的,如果 linux 宕机或重启,又或者 Redis 崩溃或重启,所有的内存数据都会丢失,为解决这个问题,Redis 提供两种机制对数据进行持久化存储,便于发生故障后能迅速恢复数据。
如何实现?
RDB 方式的数据持久化,仅需在 redis.conf 文件中配置即可,默认配置是启用的。
在配置文件 redis.conf 中搜索 SNAPSHOTTING,查找在注释开始和结束之间的关于 RDB的配置说明。配 SNAPSHOTTING 置地方有三处。
配置步骤:
优劣
优点:由于存储的是数据快照文件,恢复数据很方便,也比较快
缺点:
如何实现
AOF 方式的数据持久化,仅需在 redis.conf 文件中配置即可
配置项:
配置步骤
总结
append-only 文件是另一个可以提供完全数据保障的方案;
AOF 文件会在操作过程中变得越来越大。比如,如果你做一百次加法计算,最后你只会在数据库里面得到最终的数值,但是在你的 AOF 里面会存在 100 次记录,其中 99 条记录对最终的结果是无用的;但 Redis 支持在不影响服务的前提下在后台重构 AOF 文件,让文件得以整理变小
可以同时使用这两种方式,redis 默认优先加载 aof 文件(aof 数据最完整);
通过持久化功能,Redis 保证了即使在服务器重启的情况下也不会丢失(或少量丢失)数据,但是由于数据是存储在一台服务器上的,如果这台服务器出现故障,比如硬盘坏了,也会导致数据丢失。
为了避免单点故障,我们需要将数据复制多份部署在多台不同的服务器上,即使有一台服务器出现故障其他服务器依然可以继续提供服务。这就要求当**一台服务器上的数据更新后,自动将更新的数据同步到其他服务器上,**那该怎么实现呢?Redis 的主从复制。
Redis 提供了复制(replication)功能来自动实现多台 redis 服务器的数据同步(每天 19点新闻联播,基本从 cctv1-8,各大卫视都会播放)
我们可以通过部署多台 redis,并在配置文件中指定这几台 redis 之间的主从关系,主负责写入数据,同时把写入的数据实时同步到从机器,这种模式叫做主从复制,即master/slave,并且 redis 默认 master 用于写,slave 用于读,向 slave 写数据会导致错误
见讲义
1、一个 master 可以有多个 slave
2、slave 下线,读请求的处理性能下降
3、master 下线,写请求无法执行
4、当 master 发生故障,需手动将其中一台 slave 使用 slaveof no one 命令提升为 master,其它 slave 执行 slaveof 命令指向这个新的 master,从新的 master 处同步数据
5、主从复制模式的故障转移需要手动操作,要实现自动化处理,这就需要 Sentinel 哨兵,实现故障自动转移
上面一节中,如果要进行容灾处理,必须 运维人员手动处理,更改redis的配置文件。例如一个主redis故障,需要手动将一个从redis 设置为主redis,并修改其他的从redis的配置。而哨兵可以自动进行上述操作
Sentinel 哨兵是 redis 官方提供的高可用方案,可以用它来监控多个 Redis 服务实例的运行情况。Redis Sentinel 是一个运行在特殊模式下的 Redis 服务器。Redis Sentinel 是在多个Sentinel 进程环境下互相协作工作的。
Sentinel哨兵在 redis安装目录下(与redis-service处于同一位置),名为 redis-sentinel的程序
哨兵程序的配置文件在 redis.conf的同级目录下,默认名为 sentinel.conf
Sentinel 系统有三个主要任务:
多个哨兵监控着主redis,当多数redis(少数服从多数)任务主redis已经无法工作时,就会进行故障迁移操作(哨兵有多个,奇数个。默认端口 26379)
监控
1)Sentinel 会不断检查 Master 和 Slave 是否正常
2)如果 Sentinel 挂了,就无法监控,所以需要多个哨兵,组成 Sentinel 网络,一个健康的Sentinel 至少有 3 个 Sentinel 应用。彼此在独立的物理机器或虚拟机。
3)监控同一个 Master 的 Sentinel 会自动连接,组成一个分布式的 Sentinel 网络,互相通信并交换彼此关于被监 控服务器的信息
4)当一个 Sentinel 认为被监控的服务器已经下线时,它会向网络中的其它 Sentinel 进行确认,判断该服务器是 否真的已经下线
5)如果下线的服务器为主服务器,那么 Sentinel 网络将对下线主服务器进行自动故障转移,通过将下线主服务器的某个从服务器提升为新的主服务器,并让其从服务器转移到新的主服务器下,以此来让系统重新回到正常状态
6)下线的旧主服务器重新上线,Sentinel 会让它成为从,挂到新的主服务器下
总结
主从复制,解决了读请求的分担,从节点下线,会使得读请求能力有所下降,Master 下线,写请求无法执行
Sentinel 会在 Master 下线后自动执行故障转移操作,提升一台 Slave 为 Master,并让其它Slave 成为新 Master 的 Slave
Redis 默认是没有密码的,
配置文件中设置密码:
访问有密码的redis:
redis.conf 中修改 port 6379,将其修改为自己指定的端口(可随意),端口 1024 是保留给操作系统使用的。用户可以使用的范围是 1024-65535
更多的内容讲义第五章
通过maven创建quickstart
添加依赖
<dependency>
<groupId>redis.clientsgroupId>
<artifactId>jedisartifactId>
<version>2.9.3version>
dependency>
<dependency>
<groupId>org.apache.commonsgroupId>
<artifactId>commons-pool2artifactId>
<version>2.6.0version>
dependency>
编写代码
springboot为我们提供了两个模板类操作Redis数据库,此处不在讲
使用 Jedis操作redis。
Jedis 几乎涵盖了 Redis 的所有命令。操作 Redis 的命令在 Jedis 中以方法的形式出现。jedis 完全兼容 redis 2.8.x and 3.x.x
public class StringRedisPrimary {
public static void main(String[] args) {
// 创建redis对象,通过Jedis的方法,操作 Redis数据
String host = "192.168.230.138"; // redis所在的虚拟机的ip地址
int port = 6379; // redis运行的端口
Jedis jedis = new Jedis(host, port);
// 如果redis设置了密码,要执行下述语句
// jedis.auth("123");
// 通过jedis的方法操作Redis
jedis.set("break","banana");
// 获取数据
String value = jedis.get("break");
System.out.println(value);
jedis.mset("lunch", "红烧肉", "dinner", "面条");
List<String> values = jedis.mget("break", "lunch", "dinner");
values.forEach(v -> System.out.println(v));
// redis当缓存:查询id=1的student,key=student:1
// 先查redis,查不到就去查数据库
if (jedis.exists("student")) {
String student = jedis.get("student");
} else {
// 访问数据库,得到student对象
// 把student对象转换成json字符串,并将数据保存到redis中
jedis.set("student:1", "{student}");
}
}
}
使用commons-pool2连接池操作redis
Jedis 对象并不是线程安全的,在多线程下使用同一个 Jedis 对象会出现并发问题。为了避免每次使用 Jedis 对象时都需要重新构建,Jedis 提供了 JedisPool。JedisPool 是基于Commons Pool 2 实现的一个线程安全的连接池
创建线程池对象的工具类
public class RedisUtil {
// 线程池对象
private static JedisPool pool;
// 创建线程池
public static JedisPool open(String host, int port) {
if (pool == null) {
// 在配置器中设置线程池的参数
JedisPoolConfig config = new JedisPoolConfig();
// 最大线程数量
config.setMaxTotal(100);
// 设置空闲数
config.setMaxIdle(2);
// 设置检查项为true,避免null的情况(确保拿到的线程对象一定可用)
config.setTestOnBorrow(true);
// 创建 JedisPool
pool = new JedisPool(config, host, port, 6000); // 超时时间,密码
}
return pool;
}
// 关闭线程池
public static void close() {
if (pool!=null) {
pool.close();
}
}
}
使用
package org.example;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import java.util.List;
public class StringRedisPrimary {
public static void main(String[] args) {
// 创建redis对象,通过Jedis的方法,操作 Redis数据
String host = "192.168.230.138"; // redis所在的虚拟机的ip地址
int port = 6379; // redis运行的端口
Jedis jedis = null;
JedisPool pool = null;
try {
// 通过线程池对象获取线程对象
pool = RedisUtil.open(host, port);
jedis = pool.getResource();
// 这里写操作redis数据库的语句
}finally {
// 把使用完毕的jedis放回到线程池中,让其他的客户端使用
if (jedis != null) {
jedis.close();
}
}
}
// 最大线程数量
config.setMaxTotal(100);
// 设置空闲数
config.setMaxIdle(2);
// 设置检查项为true,避免null的情况(确保拿到的线程对象一定可用)
config.setTestOnBorrow(true);
// 创建 JedisPool
pool = new JedisPool(config, host, port, 6000); // 超时时间,密码
}
return pool;
}
// 关闭线程池
public static void close() {
if (pool!=null) {
pool.close();
}
}
}
使用
```java
package org.example;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import java.util.List;
public class StringRedisPrimary {
public static void main(String[] args) {
// 创建redis对象,通过Jedis的方法,操作 Redis数据
String host = "192.168.230.138"; // redis所在的虚拟机的ip地址
int port = 6379; // redis运行的端口
Jedis jedis = null;
JedisPool pool = null;
try {
// 通过线程池对象获取线程对象
pool = RedisUtil.open(host, port);
jedis = pool.getResource();
// 这里写操作redis数据库的语句
}finally {
// 把使用完毕的jedis放回到线程池中,让其他的客户端使用
if (jedis != null) {
jedis.close();
}
}
}