文章目录
Redis
历史演进
- 90年代单机版,servlet+jsp+mysql,存储在一台服务器上。
- 后来随着访问量上升,出现性能问题,增加数据库垂直拆分(读写分离),在三个服务器上安装三个mysql,其中写操作只往其中专门写的服务器上进行,然后将写完的数据同步到其他读服务器上。
- 后来发现大部分都是读操作,因此增加缓存,将一个已经读取过的数据加入缓存中,下次直接从缓存中取,这样就减轻了数据库的压力
- mysql单表超过300w必须使用索引
- 读写混合
- 阿里巴巴架构演进
nosql数据模型
nosql四大分类
- KV键值对:
- 新浪:redis
- 美团:redis+tair
- 阿里、百度:redis+memecache
- 文档型数据库(bson格式和json一样)
- MongoDB(重要)
- MongoDB是一个基于分布式文件存储的数据库,c++编写,主要用来处理大量文档的存储
- MongoDB是一个介于关系型数据库和非关系型数据库中间产品,是非关系型数据库中功能最丰富的,最像关系型数据库的
- ConthDB
- MongoDB(重要)
- 列存储数据库
- HBase
- 分布式文件系统
- 图关系数据库(存储数据关系,不是保存图片)
- Neo4j、infoGrid
redis属于键值对nosql数据库,与文档相关的MongoDB,大数据领域的HBase,了解图数据库Neo4j。
CAP
BASE
Redis入门
Redis安装
下载
访问官网,点击首页的download it即可下载,Windows要去github上下载,但是已经停更很久了,redis官网不建议使用Windows系统,存在兼容问题。
Windows
-
redis GitHub Windows下载安装包
点击官网这里,在介绍中有说不支持Windows,但是Windows官方在支持redis,可以点击这个Windows支持的版本去GitHub上下载安装包。在GitHub上点击Releases,选择对应版本下载即可。 -
解压安装包,解压后如下图,可以发现这个包大小只有5M

-
启动redis服务,双击
redis-server.exe,如下图

-
使用redis客户端连接服务,双击redis-cli.exe,发现黑窗口127.0.0.1:6379,表示已经连接,输入命令ping,响应pang表示连接成功。
Linux
-
下载安装包,访问官网,点击download it即可。中文网和英文网的版本有点区别
-
使用xftp或其他软件将安装包上传至服务器
-
移动压缩包到opt目录下
-
使用命令
tar -zxvf redis-5.0.8.tar.gz解压,解压后如图,这里需要注意的是这个redis.conf这个文件和sentinel.conf,这两个一个是redis-server的配置文件,另一个是哨兵模式的配置文件
-
安装gcc环境,执行命令
yum install gcc-c++ -
使用命令
gcc -v查看是否安装成功 -
make命令加载依赖库,等待一段时间,大概几分钟。完成之后再执行make命令,就会发现很快。完成之后就会发现redis已经安装成功,再执行make install(可以不执行,只是为了确认),就会发现都已经安装成功了。 -
redis的默认安装路径
/usr/local/bin,安装完成后在该目录下可以查看,文件与Windows是一样的,如图,下图中的redisredis-sentinel就是哨兵,哨兵是一个单独的进程。
-
将解压后的文件内redis.conf文件拷贝到当前目录下
cp /opt/redis-5.0.8/redis.conf /usr/local/bin,这样就即保留了原始的配置文件在解压目录中,又可以更改当前目录下的配置文件自定义配置启动redis。 -
redis默认不是后台启动的,因此需要修改配置
-
修改配置文件,vim redis.conf,这里需要修改的是
usr/local/bin下的配置文件,- 修改配置文件中 daemonize 为yes,意思是改为后台1启动
-
启动redis服务
redis-server redis.conf表示使用该配置文件启动redis服务 -
使用客户端连接redis服务
redis-cli -p 6379回车后发现连接成功。如图
-
查看redis进程是否开启
ps -ef|grep redis
-
关闭redis服务,shutdown命令表示停止服务,exit表示退出。
- shutdown
- exit
性能测试工具
redis-benchmark 使用命令与redis-server类似,使用redis-benchmark -h 127.0.0.1 -p 6379 ....

测试结果如下:

基础知识补充
- redis默认有16个数据库,配置文件redis.conf 中database项=16
- select 数据库索引 :切换数据库
- dbsize:查看数据库存储数
- 使用redis-cli 连接其他redis服务
.\redis-cli.exe -h 192.168.0.113 -p 6379win10默认不用当前目录命令,因此前面添加.\。 - flushdb:清空当前库
- flushall:清空所有库
- redis 为什么快?
redis是单线程的,官方表示,redis是基于内存操作,CPU不是性能瓶颈,机器的内存和网络带宽才是性能瓶颈,既然可以使用单线程来实现,就用了。为什么单线程还这么快?单线程并且基于内存,单线程没有多线程的切换开销,因此最快。并且redis内部采用多路复用技术实现, 减少IO的重复操作。
五大基本数据类型
其他命令行命令
exists key判断某个key是否存在expire key设置key过期时间,如果设置10秒,则expire key 10ttl key查看key还剩多久过期,如果已经过期,则返回-2move key移除keytype key判断当前key是什么类型,string还是list等等
小提示:使用redis-cli工具连接服务时,输入的命令会有提示
string
append key value在字符串后面追加value,返回数字,如果key不存在,就相当于set keystrlen key返回key的值的字符串长度incr key给这个key的值+1。如果这个key不存在,则相当于set key 1.如果这个key的值不是一个数字,则返回 ERR value is not an integer or out of range。value不是一个数字或超出范围,范围是多大我也不知道,应该是integer的取值范围2147483648。decr key给这个key减一incrby key 10给这个key设置每次增加10,设置步长,指定增量decrby key 5给这个key设置每次自减5.getrange key 0 3获取key对应字符串的部分,相当于java的substring,如果是getranger key 0 -1则表示获取key的所有=GET KEY,而不是截取的部分setrange key 2 xx将字符串中下标为2的值替换为xx,例如set key1 abcdesetrange key1 2 xxget key1返回abxxcd。相当于java的replacesetex key second value表示设置后多少秒过期,seconds表示过期时间。ex就是expiresetnx key value表示如果这个key不存在才设置,如果存在则不设置。nx就是 not existmset k1 v1 k2 v2 k3 v3 ...表示同时设置多个键值对mget k1 k2 k3同时获取多个msetnx k1 v1 k2 v2 k3 v3 k4 v4..如果不存在则设置,如果key有一个存在的,则所有的都设置不成功。这是一个原子性的操作,redis的事务是不保证原子性操作的,但是msetnx是保证原子性操作的。举例:
当我们要在redis中保存一个用户的信息的时候,一般情况下是将user对象转为json串保存到redis中
mset user:1 {name:zhangsan,age:10...},表示设置id为1的用户信息是一个json,同时也可以mset user:1:name zhangsan user:1:age 10 ...这相当于将这个user的每个属性都分开设置到redis中了。getset key value先get 再set,当key不存在时,返回null 并设置这个key和value,当key存在时,返回key的值并用value覆盖之前的value
string类型的使用场景,value除了可以是字符串还可以是数字,因此可以用到以下场景:
- 计数器:比如浏览量、粉丝数等等,当该对象被浏览了之后,可以使用incr article🆔title;或者粉丝数 incr user🆔fans 或decr
List
在redis中可以将List作为一个栈、队列、阻塞队列来实现。
所有的命令都是用L开头的
Lpush list one 1 2 3给list左边添加一个或多个元素Rpush list value v1 v2 v3...给list右边添加一个或多个元素Lpop list从列表的左边弹出一个元素Rpop list从列表的右边弹出一个元素,弹出后元素内容-1Lindex list 0获取list中下标为0的元素Llen list获取list长度Lrem list count value表示移除list中count个value元素。Ltrim list start stop表示根据下标截取队列rpoplpush list1 list2将list1中最后一个元素移除,并将其放入list2中lset将列表中指定下标的值替换为另一个值,更新操作,如果列表不存在或下标不存在会报错Linsert list befor/after value newvalue往list中的value的before或after插入一个newvalue处插入一个值。
Lpush Rpop 左边进右边出,这是一个队列;Lpush Lpop 左边进左边出,这是一个栈
Set
set不能重复
sadd set v1 v2 v3....往set集合中添加一个或多个元素,重复元素只能添加进一个smembers set获取所有元素sismember set value判断value是否存在scard set获取set中元素的个数srem set value将value移除set集合srandmember set count随机返回count个数的元素,count默认1spop set count从set中随机移除一个或count个元素smove set newset member将set中的指定元素移动到newset中
应用场景:
微博、b站 共同关注(并集)
sdiff set1 set2差集sinter set1 set2交集 共同关注就可以这么实现sunion set1 set2并集
Hash(map)
hset hash key value key1 value1 ...与string类似,命令以H开头,只不过值是k-vhget hash key1 key2...hmset hash key value key1 value1 ...多个操作hmget hash key1 key2...hgetall hash获取所有的键值对
hdel hash key删除指定的key的字段,对应的value也就没有了hlen hash获取这个hash中有多少个键值对hexists hsah key判断这个key是否存在hkeys hash获取所有keyhvals hash获取所有valuehincrby hash key 1自增hdecrby hash key 1自减hsetnx hash key如果不存在可以设置,如果存在,则不能设置
保存变更的数据,可以将一个用户变形为hashset hash user:id:name zhangsan,因此hash更适合存储对象,而string比较适合存储字符串
Zset(有序不重复集合)
zadd key 序号 valuezrange zset 0 -1- ``
- ``
- ``
- ``
三种特殊数据类型
geospatial地理位置
朋友的定位,附近的 人,打车距离,比如两地之间的距离等等
共六个命令
-
geoadd key 维度 经度 名称添加地理位置。南北极无法添加,一般会下载城市地理数据,使用java程序一次性导入,例子GEOADD Sicily 13.361389 38.115556 "Palermo" 15.087269 37.502669 "Catania"。经度和维度的范围超出会报错 -
geopos获取指定的城市的经度和维度,获得的
-
geodist
m 表示单位为米。
km 表示单位为千米。
mi 表示单位为英里。
ft 表示单位为英尺。
如果用户没有显式地指定单位参数, 那么 GEODIST 默认使用米作为单位。
-
georadius如图,显示110 30 这个经纬度周围500km的城市

附近的人的原理就是这个,当我们将所有用户的位置信息加入redis后,某个用户查看周围范围(可指定)多少人(可指定) -
georadiusbymember找出位于指定范围内的元素,中心点是由给定的位置元素决定 -
geohash了解一下 返回一个或多个位置元素的 Geohash 表示
原理
geo的底层实现是基于zset,因此我们可以使用zset的命令来操作geo的元素。如下图,这个china:city内的数据都是使用geo命令添加的,
hyperloglog
什么是基数?
A{1,2,3,6,7,8,9} B{a,3,5,4,6,1}.基数就是不重复的元素,因此这两个集合的基数就是9
简介:
Redis2.5.9版本更新了Hyperloglog数据结构
应用场景:统计网站访问量,同一个人多次访问也只计做一次
传统的做法:使用set保存用户id,计算set的size,这个方法的缺点就是耗费内存,并且我们的主要目的是计算访问量,而不是保存id
使用Hyperloglog:内存占用小,2^64的数据仅占用12KB;错误率0.81%很低,基本上可以忽略不计
命令如下图:

bitmap
使用场景:
统计用户信息,活跃、不活跃,登录、未登录,打卡,两种状态的都可以使用BitMap
简介
Bitmap 位图,使用二进制来进行记录,只有0和1两个状态的都可以这么做
具体我也不知道咋操作,参考官网文档
redis事务操作
ACID:
- A(atomicity)原子性
- C (consistency)一致性
- I (isolation)隔离性
- D (durability)持久性
Redis的事物中不保证原子性,但是Redis的单挑命令是保证原子性的
Redis事物的本质:
Redis会将一组命令保存在一个队列中,然后顺序执行,在事物中命令都会被序列化,并且是一次性全部执行完毕,不允许被打断。
redis的事物:
- 开启事物:multi
- 命令入队:
- 执行事物:execu

例如上图:multi命令开启事物,然后将一堆set、get命令加入队列QUEUED,最后执行命令exec,才将所有命令运行完
- 放弃事物:discard 
异常:
-
编译时异常:命令错误,在程序启动时就会报错,所有命令都不执行

-
运行时异常:抛出异常,其他命令执行

由此可以看出,redis中事物是不保证原子性的,但是单条命令是保证原子性的。在这个例子中按照原子性的解释是所有命令都不能被执行,要成功全部成功,要失败全部失败。
redis乐观锁:
锁:
-
悲观锁:认为任何时候都有可能出问题,因此在任何时候都要加锁
-
乐观锁:认为 一般不会出问题,所以不上锁。只有在特定的时候才上
-
使用watch实现乐观锁
举例:如下图,当我们有一个存款为100的时候,开启监视watch命令,然后开启事物,执行消费二十,然后给支出项增加二十,或者余额减去二十,然后提交事物,执行命令,这个时候是正常执行的没有问题。

当出现如下情况的时候,首先监视money,这个时候它的值是100,然后开启事物,消费10块钱,支出项增加10,然后在exec命令执行之前,另一个线程中修改了money的值,这个时候exec执行的时候发现money的值已经改变了,就会返回一个null,表示事物执行失败

那么如何解决这个问题呢?首先使用UNwatch命令解锁,将刚才出异常的锁释放掉,然后重新给 money加锁,重新开启事物,加入命令到队列,再exec,这个时候获取到的就是最新的money。
jedis
什么是jedis?
jedis是Redis官方推荐的java连接开发工具,使用java操作redis中间件,在springboot中使用redisTemplate操作,但是还是要对jedis十分了解才行。
jedis 操作redis的方法就是上面写的命令。例如:
jedis事务
public void testTx() throws JSONException {
JSONObject jsonObject = new JSONObject();
jsonObject.put("name","张三");
jsonObject.put("age","16");
//创建redis连接对象
Jedis jedis = new Jedis("127.0.0.1",6379);
//开启事物
Transaction multi = jedis.multi();
//给user添加乐观锁
jedis.watch("user");
try {
multi.set("testkey","testValue");
multi.set("user",jsonObject.toString());
//如果在事物执行的时候出现异常
//执行事物
multi.exec();
}catch (Exception e){
multi.discard();//出现异常放弃事物
}finally{
jedis.close();
}
Springboot集成Redis操作
在springboot2.x之后,原来使用的jedis被替换为lettuce,为什么替换呢?
redis:采用直连的方式,多线程操作不安全,如果想要避免不安全,就要使用jedis pool连接池,类似BIO模型 ,是阻塞的。
lettuce:采用netty,实例可以在多个线程中共享,不存在线程不安全情况,更像NIO模型
springboot整合redis
- 导入依赖
在maven仓库或者spring官网都可以找到整个redis的依赖,加入启动器spring-boot-starter-data-redis,这个启动器底层是使用的spring-data-redis来连接redis的,springData也是spring的一个大型项目,包括jpa、jdbc、MongoDB、reds都是使用springdata进行连接。 - 配置连接
我们知道在springboot中有自动配置类和对应的properties类,这两个类帮助springBoot快速构建组件,这里我们说一下springBoot整合Redis的源码分析
public class RedisAutoConfiguration {
public RedisAutoConfiguration() {
}
@Bean
@ConditionalOnMissingBean(
name = {"redisTemplate"}
)//当我们的项目中没有redisTemplate的时候,才启用这个bean,也就是说我们可以重写一个redisTemplate类来覆盖当前这个类。
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
//并且,这里的泛型是<Object, Object>,而我们期望的泛型是<String, Object>;
//还有就是我们在保存对象的时候,这些对象都是要序列化的,而这个默认redisTemplate并没有做过多的配置,因此大部分情况下我们都需要重写一个redisTemplate
RedisTemplate<Object, Object> template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean//由于开发中操作的string类型比较多,因此单独提出了一个stringRedisTemplate
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
//再来看一下redis的配置类
public class RedisProperties {
private int database = 0;//默认使用0数据库,我们可以使用命令select 下标的方式来切换使用的数据库
private String url;//连接redis服务的url
private String host = "localhost";//主机默认localhost
private String password;
private int port = 6379;
private boolean ssl;//是否开启了ssl(Secure Sockets Layer 安全套接字协议),如果开启,则设置为true
private Duration timeout;//连接超时时间
//下面这些事关于redis集群的相关配置
private RedisProperties.Sentinel sentinel;
private RedisProperties.Cluster cluster;
//在上面提到的,springboot2.x之后将jedispool替换为lettcupool,因此springboot默认使用的就是Lettuce,如果要使用redispool,则需要单独配置和加入依赖。
private final RedisProperties.Jedis jedis = new RedisProperties.Jedis();
private final RedisProperties.Lettuce lettuce = new RedisProperties.Lettuce();
- springboot中RedisTemplate操作redis
public void testRedis(){
//springboot 中opsForxxx即表示操作某个类型,如下:
redisTemplate.opsForValue();//操作string类型
redisTemplate.opsForList();//操作List类型
redisTemplate.opsForHash();//操作map类型
redisTemplate.opsForSet();//操作set类型
...
//还有一些常用的操作,可以直接用redisTemplate进行
redisTemplate.multi(); //开启事务
redisTemplate.exec();//执行事务
redisTemplate.watch(...);//开启乐观锁
redisTemplate.discard();//关闭事务
}
- 自定义RedisTemplate
在项目开发中我们一般不使用默认的RedisTemplate,而是要自己实现,修改序列化机制等等,默认使用JdkSerializationRedisSerializer,因此对象都需要实现serializerable接口,否则 JdkSerializationRedisSerializer 是无法序列化对象的,会报错。下图是redis中包含的其他序列化器

自己实现一个RedisTemplate,如下代码,是一个通用的,这样一来在自动注入的时候,就会存在两个重名的RedisTemplate,因此在自动注入的时候可以使用@Qualifier注解指定全类名
@Configuration
public class MyRedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
RedisTemplate<String, Object> template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
//使用json的方式序列化所有对象
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>();
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
//创建string的序列化器
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
//设置所有的key都使用string类型的序列化器
template.setKeySerializer(stringRedisSerializer);
//设置所有的hash类型的key也是用string类型的序列化器
template.setHashKeySerializer(stringRedisSerializer);
//设置hash类型的value使用json序列化器
template.setHashValueSerializer(jackson2JsonRedisSerializer);
//设置所有的value都使用json序列化器
template.setValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}
redis配置详解
redis的配置文件redis.conf,这个文件中使用################### 模块名############## 这种方式将各个配置项分割开来,具体如下:
-
单位配置:不知道这玩意是干啥的,一般不动。配置大小写不敏感
-
INCLUDES,包含,意思就是可以把其他配置文件包含进来组成一个配置文件,这在你有标准配置模板但是每个redis服务器又需要个性设置的时候很有用。并且include 配置最好放在配置文件的最后一行?
# include .\path\to\local.conf# include c:\path\to\other.conf
-
NETWORK 网络配置
- bind 127.0.0.1 绑定的ip
- protected-mode yes 是否保护模式
- port 6379 端口设置
- timeout 0
-
GENERAL 通用
- loglevel notice 日志级别
- debug (a lot of information, useful for development/testing) 开发或测试时用
- verbose (many rarely useful info, but not a mess like the debug level) 一些常用的信息
- notice (moderately verbose, what you want in production probably) 生产模式使用
- warning (only very important / critical messages are logged) 只打印一些重要的 信息
- daemonize no 是否设置为守护进程,Windows中不支持该配置。默认yes
- supervised no
- pidfile /var/run/redis.pid 如果以守护进程的方式运行,就需要指定这个pid文件,不知道有什么用
- logfile “” 日志文件保存路径
- databases 16 默认一共16个数据库
- always-show-logo yes 启动时是否显示logo
- loglevel notice 日志级别
-
SNAPSHOTTING 快照,快照分rdb和aof
- save “” 持久化操作的策略
- save 900 1 如果900秒内有一次key进行了更新,则持久化一次
- save 300 10 如果300秒内有10 key进行了更新,则持久化一次
- save 60 10000 如果60秒 内有至少10000个key进行了更新,则持久化一次。一般情况我们不会使用这几种方式,一般都是
- stop-writes-on-bgsave-error yes 如果持久化过程出现错误,是否还要继续
- rdbcompression yes 是否压缩rdb文件,需要消耗一些CPU资源
- rdbchecksum yes 保存rdb文件的时候,进行错误的检查校验
- dir ./ rdb文件保存的目录,默认当前目录下
- dbfilename dump.rdb rdb文件的名字,默认dump
- save “” 持久化操作的策略
-
REPLICATION 主从复制
-
SECURITY 安全
- requirepass foobared 设置密码,也可以使用如下命令
vm_redis:0>config get requirepass //获取密码 1) "requirepass" 2) "" vm_redis:0>config set requirepass 123456 //设置密码 "OK" vm_redis:0>config get requirepass 1) "requirepass" 2) "123456" vm_redis:0>ping "PONG" vm_redis:0>auth 123456 //使用密码登录 "OK" -
CLIENTS 客户端配置
-
maxclients 10000 最大允许一万个客户端连接
-
maxmemory 配置redis的最大内存容量
-
maxmemory-policy noeviction 内存达到上限后的回收策略
- noeviction: 默认的策略,即当内存使用达到阈值的时候,所有引起申请内存的命令都会报错;
- allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰 。
适用场景: 如果我们的应用对缓存的访问都是相对热点数据,就可以选择这个策略; - allkeys-random:随机移除某个key。
适合的场景:如果我们的应用对于缓存key的访问概率相等,则可以使用这个策略。
从已经设置了过期时间的key中去选择
- volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰。
- volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰。
- volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰;适合场景:这种策略使我们可以向Redis提示哪些key更适合被淘汰,可以自己控制 。
-
-
APPEND ONLY 模式 aof配置(了解,大部分情况下rdb已经足够使用,aof是对rdb的一个补充,并且性能不高,因此使用少)
- appendonly no 默认不开启aof模式,如果我们要启用aof,一般情况下只需要将该项配置改为yes即可
- appendfilename “appendonly.aof” aof持久化文件的名字
- appendfsync
- appendfsync always 每次修改都会同步,消耗性能
- appendfsync everysec 每秒执行一次同步sync,可能会丢失这一秒的数据
- appendfsync no 不执行同步,速度快
- no-appendfsync-on-rewrite no 保持默认即可
- xxx-rewrite-xxx 重写配置(了解,一般不做修改)
- 例如64M,就是说aof会一直将写操作命令追加进aof文件中,如果文件大小超过了这个64m,就会生成一个新的 文件。参考
redis持久化
RDB
redis 是内存数据库,断电及失,因此需要持久化,默认使用RDB,一般情况下我们无需修改RDB配置,即可使用。
Redis会单独创建一个fork子进程来进行持久化,子进程中循环所有的数据,将数据写入到二进制文件中,会先将数据 写入到一个临时文件中,待持久化过程都结束了,在用这个临时文件替换上次持久化好了的文件。整个过程中,主进程是不进行任何IO操作的,确保极高的性能,如果需要进行大规模数据的回复,且对数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加高效。RDB的去电就是最后一次持久化后的数据可能丢失。
RDB保存的文件就是dump.rdb
当我们修改了配置文件后,直接使用save命令就可以保存修改
关于快照的配置都在SNAPSHOTTING 项中
- 触发机制
- 在配置文件中的save,例如60秒内修改了5个key就会触发RDB,生成一个dump.rdb文件
- 执行flushall 命令,也会触发我们的RDB规则
- 退出redis,输入命令shutdown,就会关闭redis服务,这个时候也会禅城RDB文件。
- 如何恢复RDB文件
- 只需要将rdb文件放在我们redis启动目录就可以,redis启动的时候会自动检查demp.rdb恢复其中的数据
- 查看rdb文件保存的位置,输入命令
config get dir如果在该目录下存在,就会自动恢复其中的数据
RDB的优点:
- 适合大规模的数据恢复
- 对数据的完整性不高
RDB的缺点: - 需要一定的时间间隔进行操作,如果redis意外宕机了,最后一次修改的数据就没有了
- fork进程的时候回占用一定的内存空间
AOF(append only file)
什么事aof?
aof是将我们所有的命令都记录下来,然后在恢复的时候再重新执行一次,并且只记录写操作的命令,读命令不会记录。只追加文件。因此aof的速度会慢一些,尤其是大数据的情况下,恢复数据也需要很久
aof保存的文件是 appendonly.aof
AOF (配置文件中 APPEND ONLY Model 项)配置:
- appendonly no 默认不开启,一般如果要启用,只需要将该项改为yes即可,其他配置默认。
- 文件内容例如,这里就保存了所有的写操作命令

- 如果AOF文件出错了呢?
例如我们手动修改了appendonly.aof文件,里面加入一些乱码,这样一来redis就会无法启动 。这个时候redis提供了一个持久化文件修复工具,如下图,一个是redis-chech-rdb,另一个是redis-chech-aof,因此如果出现这种情况,我们就可以使用这两个工具修复文件,如下下图


修复完成后即可重新启动redis
AOF优点:
- 每次修改都同步,文件的完成性会更好
- 每秒同步一次,可能会丢失最后一秒数据
- 从不同步效率最高
AOF缺点: - 相对于rdb,aof远大于rdb,修复速度也比rdb慢
- aof运行效率也比rdb慢,因此默认rdb
扩展
- RDB持久化方式能够在指定的时间间隔内对你的数据进行快照存储

redis实现订阅发布
与消息中间件MQ中发布订阅一样,是一种小心通信模式,发送者pub发送消息到channel,订阅者sub从channel获取消息,redis实现的发布订阅相比MQ要简单一些,主要是几个命令。主要的应用场景例如微博微信的关注等等。redis客户端可以订阅任意数量的频道(channel)
命令
- PUBLISH channel message 将信息发送到指定的频道(发送者)
- PSUBSCRIBE pattern [pattern …] 订阅一个或多个给定模式的频道
- PUNSUBSCRIBE [pattern [pattern …]] 退订所有给定模式的频道
- SUBSCRIBE channel [channel …] 订阅给定的一个或多个频道的信息
- UNSUBSCRIBE [channel [channel …]] 退订频道
例子:
#订阅者,订阅了channel01 这个频道,然后监听该频道,当有发送者发送消息的时候,这个监听的控制台就会打印出来。
192.168.0.113:6379> SUBSCRIBE channel01 #订阅者订阅 channel01频道
Reading messages... (press Ctrl-C to quit) #开启监听,等待读取信息,也就是说频道是订阅者建立的,因为我们可以参考下面例子2中的命令,发现当我们往一个不存在的频道channelxx中发送消息的时候,是不成功的,返回integer为0
1) "subscribe"
2) "channel01"
3) (integer) 1 # 表示订阅成功,处于监听状态
1) "message"
2) "channel01"
3) "one message" #第一次收到消息,发送者就是下面这个发布者,当发送者发送完成后,这里显示频道和message
1) "message"
2) "channel01"
3) "tow message hello world"
#发布者往channel01频道发送消息
192.168.0.113:6379> PUBLISH channel01 "one message"
(integer) 1
例子2
#发布者往一个不存在的频道发送信息
192.168.0.113:6379> PUBLISH channelxx "xxxxx message"
(integer) 0
java
JedisPushSub类
Jedis中的JedisPubSub类是Jedis的一个抽象类,此类定义了public/subscribe的回调方法,通过继承JedisPubSub类重写回调方法。实现java中Redis的发布订阅。当Redis发生发布或者订阅的相关事件时会调用这些回调方法,只在回调方法中实现自己的业务逻辑。
onMessage():发布者发布消息时,会执行订阅者的回调方法onMessage()接收发布的消息,在此方法实现消息接收后进行自定义的业务逻辑处理。
//订阅者
public class RedisSubscriber extends JedisPubSub {
@Override
public void onMessage(String channel, String message) {
SimpleDateFormat df = new SimpleDateFormat("yyyy MM dd HH:mm:ss");
System.out.println("订阅者:订阅频道["+channel+"],收到消息["+message+"],时间:"+df.format(new Date()));
}
public static void main(String[] args) {
System.out.println("启动订阅者");
//创建Jedis
Jedis jedis = new Jedis("192.168.40.133", 6379);
//创建订阅者
RedisSubscriber redisSubscriber = new RedisSubscriber();
//订阅频道
jedis.subscribe(redisSubscriber,"badaodechengxvyuan");
}
}
//发送者,发送者可以使用redis-cli客户端使用命令行的方式发送,也可以新建一个项目,如下,在main方法中发送
public class Publisher {
public static void main(String[] args) {
System.out.println("开始发布......");
//创建Jedis
Jedis jedis = new Jedis("192.168.40.133", 6379);
//发布消息
jedis.publish("badaodechengxvyuan", "紧急通知:...");
System.out.println("消息发送完毕......");
}
}
原理
redis是通过publish、subscribe、和psubscribe等命令实现发布和订阅功能
通过subscribe命令订阅某频道后,redis-server里维护了一个 字典,字典的键就是一个个的channel(通道、频道),而字典的值则是一个链表,链表中保存了所有订阅这个channel的客户端。subscribe命令的关键,就是讲客户端添加到给定channel的订阅链表中。
通过publish命令想订阅者发送消息,redis-server会使用给定的频道作为键,在他所维护的channel字典中查找记录了订阅这个频道的所有客户端的链表,便利这个链表,将消息发布给所有订阅者。
pub/sub在字面上理解就是发布(publish)与订阅(subscribe),在redis中,你可以设定对某个key值进行消息发布及消息订阅,当一个key值上进行了消息发布后,所有订阅它的客户端都会受到相应的消息,这一功能最明显的用法就是用作实时消息系统,例如群聊等
redis主从复制
什么是redis主从复制
就是将一台redis服务器的数据,复制到其他的redis服务器,前者称为主节点(master/leader,主节点不用配置,redis默认单机就是主节点),后者称为从节点(slave/follower);数据的复制是单向的,只能由主节点到从节点。master以写为主,slave以读为主。主从复制,读写分离。
redis集群搭建
首先是集群环境搭建,一般最低一主二从,因为redis的哨兵模式有个从机的分配策略,如果一主一从就没发分配,下面我们模拟一个一主二从的环境。
步骤:
- 首先使用命令info replication查看主机参数
192.168.0.113:6379> info replication
# Replication
role:master # 角色主机
connected_slaves:0 # 连接的从机 0个
master_replid:ac2ce59a7afcd52e3860e7a1083e304a0214ee29
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
- 复制多个redis.conf文件并重命名,例如分别叫redis6379.conf、redis6380.conf、redis6381.conf,分别修改三个配置文件中的端口(port)、持久化文件名(dbfilename dump6379.rdb)、日志文件的名称(logfile 6379.log)、pidfile(pidfile /var/run/redis_6379.pid)
- 使用命令启动服务
redis-server redis6379.conf分别启动三个不同端口的服务
参考redis三种启动方式 - 启动完成后使用命令
ps -ef|grep redis查看启动的redis服务,出现下图表示三个redis服务器启动成功
说明:我们这里使用的是单机集群,多机集群怎么配不知道,猜测应该是在从机的配置文件中配置指向主机的地址
这个时候我们还没有配置主从复制,因此上面三个服务都是主节点,默认情况下,每台redis服务都是主节点
redis主从复制
主从配置
一般情况下只需要配置从机,不需要配置主机。我们只需要在从机命令行窗口输入命令slaveof 127.0.0.1 6379即可,使用命令配置的方式是暂时的,如果重启就失效,推荐使用配置文件的方式,如下:
使用配置文件的方式
在redis.conf文件中有个叫replication的模块,该模块就是主从复制的配置
slaveof <masterip> <masterport>和命令行一样,配置该项,将主机的地址和端口配置即可
masterauth <master-password>如果主机有密码,则在这里配置密码
slave-serve-stale-data yes该项表示与主机断开连接后的处理措施,yes表示断开后依然启动服务,只不过数据有可能是旧数据,如果主机从来没有同步过来数据,则该从机有可能是空的。如果是no,则返回与‘主机同步中’的错误信息
配置完成后输入命令info replication查看如下图


可以看出这里的信息role已经发生了变更,并且主机查看显示从机也不为0.至此主从复制配置完成
补充:
- 主从配置的默认原则
- 主写,从读,当强行往从机写会报错。
- 当主挂了以后,从机会等待,不会翻身做主人,会等主机恢复,这个时候因为没有主机也就没有写操作,所有的从机数据都不会更新,因此会存在读到旧数据的可能。这里可以参考
slave-serve-stale-data yes的配置策略。(启用哨兵模式后就不会这样) - 当从机挂了,恢复连接后,不会去主动找主机。相当是断开了连接。这是因为没有在配置文件中将该redis设置为从机,就是slaveof无效,因此这个时候它就变成了主机,不会去连其他主机。但是只要重新配置为从机,马上就会拥有全部主机的信息,这是因为什么呢?因为当slave启动成功连接到master后会发送一个sync同步命令,master接到命令后,启动后台的存盘进程,同事手机所有接收到的用于修改数据集命令,在后台进程执行完毕后,master将传送整个数据文件到slaver,并完成一次完全同步,这就是全量复制。之后master又有更新,会同步给其他从机,这就是增量复制。
- 全量复制:slave服务在接收到数据库文件数据后,将其存盘并加载到内存中
- 增量复制:master继续将新的所有收集到的修改命令依次传给slave,完成同步
只要重新连接了主机,就一定会有一次全量复制
- 主从配置模型2
上面我们配置的模型如下
还有另一种主从配置
对应到slaveof中就是6379为主机,6380Slaveof6379,6381Slaveof6380,这种配置中这个6380即是主机又是从机,那么如果这个时候6379宕机了6380会怎么样,使用命令info replication查看后发现,这个6380依然是从机。因此上面两种配置在我们工作的时候多不会用。
手动配置主机
在没有哨兵模式之前,我们主机宕机之后,需要手动指定主机,使用命令Slaveof no one,表示没有任何一个从机,我就是主机。然后在给其他从机重新Slaveof 到新的主机。如果这个时候原来的主机又上线了,那么他依然是个主机。
主从复制的主要作用
- 数据冗余
- 故障恢复
- 负载均衡
- 高可用(集群)
springboot连接redis集群
Redis集群的Spring Boot连接方式非常简单,只需要在application.properties配置文件中把之前单机配置的ip和端口号注释掉,换成集群的nodes节点即可,用逗号分隔:
#spring.redis.host=192.168.253.129
#spring.redis.port=6379
spring.redis.cluster.nodes=192.168.253.129:7001, 192.168.253.129:7002, 192.168.253.129:7003, 192.168.253.129:7004, 192.168.253.129:7005, 192.168.253.129:7006
redis哨兵模式(重点)
这个是重点
什么是哨兵模式?
哨兵模式就是自动选取主机的方法。在上面的案例中我们使用一主二从的方式配置了redis集群,可以发现如果当主机宕机,就会存在一些问题,例如无法写数据等等,哨兵模式就是自动切换主机。
哨兵默认端口26379
配置
与redis类似,在解压文件中有个与redis.conf 同级目录的文件sentinel.conf,这个文件就是redis哨兵模式的配置文件。与redis-server同级的redis-sentinel 就是哨兵模式的服务进程。哨兵模式是一个单独的进程,用来监视redis-server。
- 配置哨兵配置文件
sentinel monitor myredis 127.0.0.1 6379 1表示sentinel monitor 被监控的名称(自己随意) 主机 端口;后面这个1代表主机挂了,哨兵会投票选取一个从机作为主机(投票算法) 这个是最基础最核心的配置- 启动哨兵时要指定配置文件,因此我们需要先将配置文件放在一个固定的地方,例如与redis-conf文件放在一起,然后打开修改其参数sentinel-monitor。也可以直接新建一个配置文件然后重命名为sentinel-conf,在配置文件中加入上面的命令即可。
- 启动哨兵
redis-sentinel sentinel.conf使用配置文件的方式启动


可以看出这里已经监视了6379并且有两个从机
哨兵的两个作用
- 通过发送命令,让redis服务器返回监控其运行状态,包括主服务器和从服务器。
哨兵是一个单独的进程,会通过网络发送命令,等待redis服务器响应,判断监控的对象是否正常。如果不正常或发生故障,则哨兵会根据投票数自动将从库转换为主库

- 当哨兵检测到master炖鸡,会自动将slave切换成master,然后通过发布订阅模式,通知其他的从服务器,修改配置文件让其切换主机。
然而一个哨兵进程对redis服务器进行监控,可能会出现问题,为此,我们可以使用多个哨兵进行监控,各个哨兵之间还会进行监控,这样就形成了多哨兵模式
哨兵模式也可以集群,如果我们配置单个哨兵来监视所有主从集群,一旦哨兵所在的主机宕机,那么哨兵也就不存在了,因此也可以配置哨兵集群,哨兵之间不仅监视redis,还要各自监视。
假设主库宕机,哨兵1先检测到这个结果,系统并不会马上进行切换,仅仅是哨兵1认为主服务器不可用,这个现象称为主观下线。当后面的哨兵也检测到主服务器不可用,并且数量达到一定值的时候,那么哨兵之间就会进行一次投票,投票的结果由一个哨兵发起,进行failover(故障转移)操作。切换成功后就会通过发布订阅模式,让各个哨兵吧自己监视的从服务器切换主机,这个过程称为客观下线。
哨兵集群的配置与redis的集群配置类似,在配置文件中分别指定哨兵的端口,然后以配置文件的方式启动。
哨兵模式全部配置



缓存穿透、雪崩、及解决方案
穿透
- 什么是缓存穿透?
例如用户想要 查询一个数据,发现redis内存中没有,也就是没有缓存命中,于是向持久层数据库查询,发现也没有,于是本次查询失败。当用户很多的时候,缓存中都没有,于是都去请求持久层数据库,这会给持久层数据库造成很大压力,这就是缓存穿透。 - 解决方案
- 布隆过滤器:布隆过滤器是一种数据结构,对所有可能查询的参数 一hash的形式存储。在控制层先进行校验,不符合的则丢弃,从而避免了对底层存储系统的查询压力。
如果想判断一个元素是不是在一个集合里,一般想到的是将所有元素保存起来,然后通过比较确定。链表,树等等数据结构都是这种思路. 但是随着集合中元素的增加,我们需要的存储空间越来越大,检索速度也越来越慢。不过世界上还有一种叫作散列表(又叫哈希表,Hash table)的数据结构。它可以通过一个Hash函数将一个元素映射成一个位阵列(Bit Array)中的一个点。这样一来,我们只要看看这个点是不是 1 就知道可以集合中有没有它了。这就是布隆过滤器的基本思想。 - 缓存空对象 :
- 如果空值能够被缓存 起来,就一位置缓存需要更多的空间存储更多的键,因此这其中可能会有很多的空值的键,浪费存储空间。
- 即使对空值设置了过期时间,还是会存在缓存层和存储层的数据不一致,这对数据一致性高的业务会有影响,
- 布隆过滤器:布隆过滤器是一种数据结构,对所有可能查询的参数 一hash的形式存储。在控制层先进行校验,不符合的则丢弃,从而避免了对底层存储系统的查询压力。
缓存击穿
- 什么是缓存击穿?
缓存击穿的意思就是,例如当微博热搜,这个时候这个点的访问量巨大,然后在某个时刻该热点的缓存过期或者其他 情况导致缓存中暂时没有该热点,这个时候依然大量并发就会全部集中到数据库去查询最新数据并回写缓存,很有可能在这一瞬间就导致数据库崩溃。这就是缓存击穿。 - 解决方案
- 设置热点数据永不过期
从缓存层面来看,没有设置过期时间,所以不会出现热点key过期后产生的问题。但是有可能会逐渐的让redis缓存增加,内存占用庞大。 - 加互斥锁
分布式锁:使用分布式锁,保证每个key同事只有一个县城去查询后端服务,其他县城没有获得分布式锁的权限,因此只需要等待即可。这种方式将高并发的压力转移到了分布式锁,因此对分布式锁的考验很大。
- 设置热点数据永不过期
雪崩
缓存雪崩是指在某个一时间段,缓存集中过期失效
例如快要双十一0的抢购高峰的时候,这个时候商品时间比较几种的放入了缓存中,假设缓存一小时,那么到了1点的时候,这批商品的缓存信息失效,对这批商品的查询都会落入到数据库中,对数据库而言,就会产生周期性的波峰压力。有可能会引起数据库挂掉的情况。
**笔记来源 **
bilibili狂神说-redis学习视频


1570

被折叠的 条评论
为什么被折叠?



