Redis
Nosql概述
什么是NoSQL
NoSQL特点3V+3高 NoSQL的四大分类 Redis入门
概述Windows安装Linux安装测试性能基础知识 五大数据类型
Redis-KeyString(字符串)List(列表)Set(集合)Hash(哈希)Zset(有序集合) 三种特殊数据类型
geospatial 地理位置HyperloglogBitmaps 事务JedisSpringBoot整合Redis.conf详解Redis持久化
RDB持久化AOF (Append only File)扩展 Redis发布订阅Redis主从复制
概念从机配置哨兵模式 Redis缓存击穿和雪崩
缓存穿透缓存击穿(缓存过期瞬间,大量请求)缓存雪崩
参考视频:
【狂神说Java】Redis最小超详细版教程通俗易懂
NoSQL特点NoSQL = Not only SQL (不仅仅是SQL)
关系型数据库:表格、行、列
泛指非关系型数据库,随着web2.0互联网的诞生!传统的关系型数据库很难对付web2.0时代!尤其是超大规模的高并发的社区!暴露出来很多难以克服的问题。NoSQL在当今大数据环境下发展的十分迅速,Redis是发展最快的,而且是我们当下必须掌握的一个技术!
很多数类型用户的个人信息,社交网络,地理位置、这些数据类型的存储不需要一个固定的格式!不需要多月的操作就可以横向扩展的!Map
使用键值对来控制!
解耦!
方便扩展(数据之间没有关系,很好扩展)
大数据量高性能(Redis 一秒 写8万次,读取11万次,NoSQL的缓存记录级,是一种细粒度的缓存,性能比较高)
数据类型是多样型的(不需要事先设计数据库!随取随用,如果是数据量十分大的表,很多人就无法设计了)
传统RDBMS和NoSQL
传统的 RDBMS - 结构化组长 - SQL - 数据和关系都存在单独的表中 row col - 数据操作,数据定义语言 - 严格的一致性 - 基础的事务
NoSQL - 不仅仅是数据 - 没有固定的查询语言 - 键值对存储,列存储,文档存储,图形数据库(社交关系) - 最终一致性 - CAP定理和base (异地多活)--> 初级架构师 - 高性能 高并发 高可拓
大数据时代的3V:主要是描述问题的
- 海量Volume多样Variety实时Velocity
大数据时代的3高:主要是对程序的要求
- 高并发高可用高性能 (保证用户体验和性能)
真正在公司中的实践:NoSQL + RDBMS 一起使用才是最强的
NoSQL的四大分类KV键值对
Redis
文档型数据库(bson‘格式 和json一样)
MongoDB (一般必须要求掌握)
MongoDB 是一个基于分布式文件存储的数据库,C++编写,主要用来处理大量的文档MongoDB是一个介于关系数据库和非关系型数据库中中间的产品!MongoDB是非关系数据库中功能最丰富,最像关系型数据库的!
ConthDB
列存储数据库
Hbase分布式文件系统
图形关系数据库
他不是存图片,存放的是关系,比如:朋友圈社交网络,广告推荐!Neo4j,InfoGrid
Redis入门 概述Redis 是什么?
Redis ( Remote Dictionary Server ) 远程字典服务
是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。
免费和开源!是当下最热门的NoSQL技术之一!也被人们称谓诶结构化数据库。
Redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)复制。
Redis 能干嘛?
- 内存存储、持久化,内存中是断电即失、所以说持久化很重要**(rdb、aof)**效率高,可以用于高速缓存发布订阅系统地图信息分析计时器、计数器(浏览量)
特性
- 多样的数据类型持久化集群事务
学习中需要用到的东西
官网:https://redis.io/
中文官网:http://www.redis.cn/
下载地址:官网下载
注意:Windows在GitHub上下载(停更很久了)
Redis推荐都是在Linux服务器上搭建的
下载安装包:https://github.com/MicrosoftArchive/redis/releases
解压到电脑上的环境目录下即可!Redis非常小,只有5M
开启Redis,双击运行服务(redis-server.exe)即可
端口:6379
使用redis客户端来连接redis
下载安装包 redis.tar.gz
解压Redis安装包
cp redis.tar.gz /opt #复制压缩包到/opt下 tar -zxvf redis.tar.gz #解压
保证c++环境yum install gcc-c++
在解压后的redis目录下执行make,make install
redis默认安装路径 /usr/local/bin
配置文件的目录/opt/redis/redis.conf,使用配置文件启动
redis默认不是后台启动的,需要配置
启动服务 redis-server 配置文件目录/redis.conf
启动客户端 redis-cli -p 6379
如果有密码则在程序中输入 auth “密码”
关闭客户端,在程序中输入 shutdown关闭连接,然后exit退出
redis-benchmark 是一个压力测试工具
官方自带的性能测试工具
简单测试:
# 测试:100个连接 100000次请求 redis-benchmark -h localhost -p 6379 -c 100 -n 100000基础知识
redis默认有16个数据库
默认使用第0个
redis 的命令不区分大小写可以使用 select 数据库号进行切换数据库
127.0.0.1:6379> select 7 OK 127.0.0.1:6379[7]> DBSIZE (integer) 0
查看数据库所有的key keys *
清空当前数据库flushdb
清空全部数据库FLUSHALL
Redis 是单线程的!
Redis是很快的,官方表示,Redis是基于内存操作,CPU并不是Redis性能瓶颈,Redis的瓶颈是根据机器的内存和网络带宽,既然可以使用单线程来实现,就使用单线程。
Redis是C语言写的,官方提供的数据为 100000 + 的QPS,完全不比同样是使用key-value的Memecache差!
Redis为什么单线程还这么快?
- 误区1:高性能的服务器一定是多线程的?误区2:多线程(CPU上下文会切换)一定比单线程效率高!
核心:redis是将所有的数据全部存放在内存中的,所以说使用单线程去操作效率就是最高的,多线程(CPU上下文会切换:耗时的操作),对于内存系统来说,如果没有上下文切换,效率就是最高的。多次读写都是在一个CPU上的,在内存情况下,这个就是最佳的方案。
Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。 它支持多种类型的数据结构,如 字符串(strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 与范围查询, bitmaps, hyperloglogs 和 地理空间(geospatial) 索引半径查询。 Redis 内置了 复制(replication),LUA脚本(Lua scripting), LRU驱动事件(LRU eviction),事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过 Redis哨兵(Sentinel)和自动 分区(Cluster)提供高可用性(high availability)。
Redis-Keyset [key] [value] get [key] rename [key] [newKey] # 更换key名 randomkey # 获取随机的key del [key] exist [key] #判断key是否存在 move [key] [数据库号] #移动某一个键值对到指定库中 expire [key] [过期时间] #设置某个键值对的过期时间 type [key] #查看当前key的类型 ttl [eky] #查看当前key的过期剩余时间 keys * #获取所有key setex [key] [time] #expire [key] [time] setnx [key] [value] #if [key] not exist set [key] [value](分布式锁常用) mset [k1] [v1] [k2] [v2] ... # 同时设置多个值 mget [k1] [k2] ... # 同时获取多个值 msetnx [k1] [v1] [k2] [v2] ... # 同时多次setnx,原子性,要么都成功要么都失败! getset [key] [value] #先执行get [key],最后set [key] [value] dbsize #获取当前数据库key数量 flushdb #清空当前数据库 flushall #清空所有数据库 select [dbnum] #切换到dbnum号数据库String(字符串)
append [key] [追加的字符串] # 追加字符串(拼接),返回字符串长度,如果key不存在则相当于set strlen [key] # 查看字符串长度 incr [key] # 自增1 decr [key] # 自减1 incrby [key] [num] # 自增 num decrby [key] [num] # 自减 num getrange [key] [start] [end] #截取[start,end]字符,若start=0,end=-1则获取整个字符串
String类似的使用场景:value除了是我们的字符串还可以是我们的数字!
计数器统计多单位的数量粉丝数对象缓存存储 List(列表)
在redis里面,我们可以把list当栈、队列、阻塞队列来使用。
LPUSH [list] [v1] [v2] ... #将一个或多个值 按顺序插入列表左部 LRANGE [list] [start] [end] #从左部开始获取[start,end]的值列表 LPOP [list] ([count]) #从左部出栈一个元素,若count存在则出栈count个元素 RPUSH [list] [v1] [v2] ... #将一个或多个值 按顺序插入列表右部 RRANGE [list] [start] [end] #从右部开始获取[start,end]的值列表 RPOP [list] ([count]) #从右部出栈一个元素,若count存在则出栈count个元素 LINDEX [list] [index] #从左部开始 获取 第index个值 LLEN [list] #获取列表的长度 LREM [list] [count] [value] #从左往右,删除count个value LTRIM [list] [start] [end] #保留[start,end]的元素,删除其他元素 LSET [list] [index] [value] #将第index个元素的值换成value(要保证长度大于index) RPOPLPUSH [list1] [list2] #将list1右部元素出栈返回,并将该元素压入list2的左部 ...# 其他组合不再写出 LINSERT [list] BEFORE/AFTER [v1] [v2] #将v2插入到首个值为v1的 左/右 边
小结
实际上是一个链表如果key不存在,创建新的链表如果key存在则扩容如果移除了所有值,空链表,也代表不存在!在两边插入或者改动值,效率最高,中间元素效率相对较低 Set(集合)
set中的值是不能重复的
SADD [set] [v1] [v2] ... #插入一个或多个元素 SMEMBERS [set] #查看set中的所有成员 SISMEMBER [set] [v] #判断v是不是set的成员 SREM [set] [v] #移除一个指定元素 SRANDMEMBER [set] ([count]) #从set中随机抽选一个元素,若count存在则抽取count个元素 SPOP [set] (count) #从set中随机抽选 1/count 个元素,移除并返回 SMOVE [set1] [set2] [v] #将set1中的v移除,然后插入到set2中 SDIFF [set1] [set2] #差集 SINTER [set1] [set2] #交集 SUNIOn [set1] [set2] #并集Hash(哈希)
Map集合,hash-map 存储一个键值对集合
hash:{k1,v1},{k2,v2}
#插入一个或多个键值对
HSET [hash] [k1] [v1] [k2] [v2] ...
HMEST [hash] [k1] [v1] [k2] [v2] ...
HGET [hash] [key] #获取hash中以key为键的值
HMGET [hash] [k1] [k2] ... #获取hash中一个或多个指定键的值
HDEL [hash] [key] #删除hash中以key为键的键值对
HGETALL [hash] #获取hash所有的键和值
#例如:[{1,1},{2,2}] --> 1 1 2 2
HLEN [hash] #获取hash键值对数量
HKEYS [hash] #获取hash所有的键
HVALS [hash] #获取hash所有的值
HINCRBY [hash] [key] [count] #hash中的key键的值自增count
#没有HDECRBY、HINCR、HDECR
HSETNX [hash] [key] [value] #如果hash不存在键key 则可以插入键值对{key,value}
Zset(有序集合)
在set的基础上,增加了一个值,set k1 v1 变成 zset k1 score1 v1 (score1指的是)
ZRANGEBYSCORE [zset] [min] [max] (WITHSCORES) # min 和 max 可以为 +inf 或 -inf # 可以在min左边紧贴(表示不取到min,max右边可以紧贴)表示不取到max # 如果有WITHSCORES则也将每个元素的SCORE获取 # 从 min 到 max 进行排序并返回 ZREVRANGEBYSCORE ...#逆序返回 ZADD [zset] [s1] [v1] ([s2] [v2] ...) #向zset中添加一个或多个排序值为score的val元素 ZREM [zset] [val] #移除值为val的元素 ZRANGE [zset] [start] [end] #获取score在[start,end]区间的元素,并正序返回 ZREVRANGE [zset] [start] [end] #获取score在[start,end]区间的元素,并逆序返回 ZCOUNT [zset] [min] [max] #获取score在[min,max]上的元素个数 #score可以重复三种特殊数据类型 geospatial 地理位置
朋友的定位,附件的人,打车举例计算
Redis的Geo在Redis3.2版本推出!可以推算地理位置的信息,两地之间的距离
GETADD
添加地理位置
# GETADD 添加地理位置 # 规则:两极无法直接添加,我们一般会下载城市数据,直接通过java程序一次性导入 # 有效的经度从-180度到180度。 # 有效的纬度从-85.05112878度到85.05112878度。 GETADD [key][经度][纬度][地名]
GETPOS
获取当前定位:一定是一个坐标值!
# GETPOS 获取指定地理位置的坐标值 GETPOS [key][地名1]([地名2] ...)
GETDIST
获取两地距离
单位:
m 米km 千米mi 英里ft 英尺
GETDIST [key] [地位1] [地位2]
GETRADIUS
获取以 指定 [经纬度|地点] 为中心 指定半径距离 的所有地理位置名称
GETRADIUS [key] [经度] [纬度] [距离] [单位] (WITHDIST | WITHCOORD | COUNT 数字) GETRADIUS [key] [中心地点] [距离] [单位] (WITHDIST | WITHCOORD | COUNT 数字) #如果包含[WITHDIST]将获取对应点到中心的直线距离 #如果包含[WITHCOORD]将获取对应点的经纬度 #如果包含[COUTNT num]将获取最近的num个元素
GEOHASH
返回一个或多个位置元素的经纬度的长度为固定11的字符串
# 将二维的经纬度转换为一维的字符串,如果两个字符串越接近,那么则距离越近! GEOHASH
HyperloglogGEO的底层实现原理其实就是 ZSET!可以直接使用ZSET命令来操作GEO!
什么是基数?
A{1,3,4,7,8,7}
B{1,3,5,7,8}
基数(不重复的元素)= 5 , 可以接受误差!
简介
Redis 2.8.9 版本就更新了Hyperloglog数据结构
Redis Hyperloglog 基数统计的算法
优点:占用的内存是固定的,2^64不同的元素的计数,只需要花费12KB内存!如果从内存角度来比较的话Hyperloglog首选 。
网页的UV(一个人访问一个网站多次,但是还是算作一个人)
传统的方式,set保存用户的id,然后就可以统计set中的元素数量作为标准判断
这个方式如果保存大量的用户id,就会比较麻烦,我们的目的是为了计数,而不是保存用户id
0.81% 的错误率,统计UV任务,可以忽略不计
命令
PFADD [key] [v1] ([v2] ...) #添加元素 PFCOUNT [key] #统计基数数量 PFMERGE [keyResult] [key1] [key2] #合并key1和key2到keyResult中
如果运行容错,那么一定可以使用Hyperloglog
如果不允许容错就使用set或者自己的数据类型即可
Bitmaps位存储
统计用户信息。活跃,不活跃。登录,不登录。打卡,未打卡。
两个状态的,都可以使用Bitmaps
Bitmaps 位图,数据结构。都是操作二进制位来进行记录,就只有0和1两个状态。
命令
SETBIT [map] [index] [status] #将map的index位置设置为status的状态 status只能为0或1 GETBIT [map] [index] #获取map的index位置的状态值 BITCOUNT [map] ([start] [end]) #获取map所有(或指定区间)状态为1的数量事务
Redis事务本质:一组命令的集合。一个事务中的索引命令都会被序列化,在事务执行过程中,会按照顺序执行。
一次性、顺序性、排他性。执行一些命令。
Redis事务没有隔离级别的概念
所有的命令在事务中,并没有直接被执行,只有发起执行命令的时候才会执行
Redis单条命令是保证原子性的,但是事务不保证原子性!
redis的事务:
开启事务(MULTI)命令入队()执行事务(EXEC)放弃事务(DISCARD)
正常执行事务
> MULTI #开启事务 OK > SET k1 v1 QUEUE > SET k2 v2 QUEUE > GET k1 QUEUE > GET k2 QUEUE > EXEC #执行事务
编译型异常(代码有问题,命令有错 事务的所有命令都不会被执行
> MULTI #开启事务 OK > SET k1 v1 QUEUE > SET k2 v2 QUEUE > GETSET k1 #错误命令! (error) ERR wrong number of arguments for 'GETSET' command > GET k2 QUEUE > EXEC #执行事务 (error) EXECABORT Transaction discarded because of previous errors.
运行时异常,如果事务队列中存在非语法性错误,那么执行命令时,其他正常命令仍会执行,错误命令抛出异常
> MULTI #开启事务 OK > SET k1 "v1" QUEUE > SET k2 "v2" QUEUE > INCR k1 #编译无错,但是运行会出错 QUEUE > GET k1 QUEUE > GET k2 QUEUE > EXEC #执行事务 1) OK 2) OK 3) (error) ERR value is not an integer or out of range #仅异常命令报错,其他仍然执行了 4) "v1" 5) "v2"
监控
悲观锁:
很悲观,认为什么时候都会出问题,无论做什么都加锁
乐观锁:
很乐观,任务什么时候都不会出问题,所以不会上锁。更新数据的时候去判断一下,在此期间是否有人修改过这个数据获取version更新的时候比较version
Redis监视测试 WATCH UNWATCH (面试常问) 乐观锁
# 正常执行 ################################################# > SET a 100 OK > SET b 0 OK > WATCH a #监视 a 对象 > MUTI OK > DECRBY a 20 QUEUE > INCRBY b 20 QUEUE > EXEC 1) (integer) 80 2) (integer) 20 # watch在多线程中可以当做redis的乐观锁操作! # 如果执行之前,另一个线程,修改了监视的对象的值,就会导致事务执行失败 返回 (nil) # 如果发现事务执行失败 就需要先解锁 UNWATCHJedis
我们若使用Java来操作Redis
Jedis 是 Redis 官方推荐的 java 连接 开发工具
导入依赖
redis.clients
jedis
4.1.1
com.alibaba
fastjson
1.2.78
编码测试
public static void main(String[] args) {
// 1 new Jedis 对象
final Jedis jedis = new Jedis("127.0.0.1", 6379);
//直接使用Redis的命令
System.out.println(jedis.ping());
}
}
SpringBoot操作数据:spring-data jpa jdbc mongodb redis
SpringData 是和 SpringBoot 齐名的项目
说明:在SpringBoot 2.x 之后,原来的 jedis 被替换为了 lettuce
jedis:采用的直连,多线程操作不安全,如果想要避免不安全,需要使用jedis pool连接池。BIO模式
lettuce:采用netty,实例可以在多个线程中进行共享,不存在线程不安全的情况。可以减少线程数据了。NIO模式
源码分析:
@Bean
@ConditionalOnMissingBean(
name = {"redisTemplate"}
)
//我们可以自定义一个RedisTemplate来替换这个默认的
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public RedisTemplate
在源码中,redis实现类未注入,而lecttue实现类是注入的,所以配置的时候要使用lecttue
整合测试
导入依赖
org.springframework.boot spring-boot-starter-data-redis
配置连接
# 配置 Redis spring.redis.host=127.0.0.1 spring.redis.port=6379
测试
package springdata.redis;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisTemplate;
@SpringBootTest
class SpringDataRedisApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
void contextLoads() {
// redisTemplate
// opsForValue 操作字符串 类似String
// opsForList 操作List 类似List
// opsForSet
// opsForHash
// opsForGeo
// opsForZSet
// opsForHyperLogLog
//除了基本的操作,常用的方法都可以直接通过RedisTemplate操作,比如事务和CRUD
//获取RedisTemplate连接对象
//final RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
redisTemplate.opsForValue().set("name","guokun");
System.out.println(redisTemplate.opsForValue().get("name"));
}
}
序列化
RedisTemplate类中序列化代码
//声明的序列化对象 @Nullable private RedisSerializer keySerializer = null; @Nullable private RedisSerializer valueSerializer = null; @Nullable private RedisSerializer hashKeySerializer = null; @Nullable private RedisSerializer hashValueSerializer = null; private RedisSerializerstringSerializer = RedisSerializer.string(); //默认的序列化方式是JDK序列化,我们可能会使用Json来序列化! if (defaultSerializer == null) { defaultSerializer = new JdkSerializationRedisSerializer( classLoader != null ? classLoader : this.getClass().getClassLoader()); }
所有对象都需要序列化!
在企业中,所有的pojo实体类都会序列化(implement Serializable)
自定义序列化:
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
//为了开发方便 一般直接使用
RedisTemplate template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
//JSON序列化配置
// 使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
// 指定要序列化的域,field,get和set,以及修饰符范围,ANY是都有包括private和public
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
// 指定序列化输入的类型,类必须是非final修饰的,final修饰的类,比如String,Integer等会跑出异常
objectMapper.activateDefaultTyping(LaissezFaireSubTypevalidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
//String的序列化配置
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
//key采用String的序列化方式
template.setKeySerializer(stringRedisSerializer);
//hash的key也采用String的序列化方式
template.setHashKeySerializer(stringRedisSerializer);
//value序列化方式采用jackson
template.setValueSerializer(jackson2JsonRedisSerializer);
//hash的value采用jackson
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
//配置具体的序列化方式
return template;
}
}
RedisUtil
所有的redis操作,对于开发人员来说很简单,更重要的是去理解redis的思想和每一种数据结构的用处和使用场景。
Redis.conf详解单位
# 1k => 1000 bytes # 1kb => 1024 bytes # 1m => 1000000 bytes # 1mb => 1024*1024 bytes # 1g => 1000000000 bytes # 1gb => 1024*1024*1024 bytes
配置文 unit单位 对字母大小写不敏感
包含
# include /path/to/local.conf # include /path/to/other.conf
类似于import,导入其他文件的配置内容
网络
bind 127.0.0.1 # 绑定的IP protected-mode yes # 保护模式 port 6309 # 端口设置
通用GENERAL
# 守护进程模式 默认no 需要手动设置为yes daemonize yes # 如果以后台方式运行,我们就需要指定一个pid文件! pidfile /www/server/redis/redis.pid # 日志 # Specify the server verbosity level. # This can be one of: # 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) loglevel notice logfiole "" # 日志文件位置 databases 16 # 数据库数量,默认16个 always-show-log yes # 是否总是显示LOGO
快照
持久化,在规定的时间内,执行了多少次操作,则会持久化文件 .rdb .aof
redis是内存数据库,如果没有持久化,那么数据断电即失!
# 如果900秒内,如果至少有1个key进行了修改,我们就进行持久化操作 save 900 1 # 如果300秒内,如果至少有10个key进行了修改,我们就进行持久化操作 save 300 10 # 如果60秒内,如果至少有 一万 个key进行了修改,我们就进行持久化操作 save 60 10000 # 持久化如果出错!是否还要继续工作 stop-writes-on-bgsave-error yes # 是否压缩 rdb 文件,需要消耗CPU资源 rdbcompression yes # 保存rdb文件时,是否进行错误的检查校验 rdbchecksum yes # rdb 文件保存的目录 dir ./
REPLICATION 复制
SECURITY 安全
可以在这里设置redis密码,默认是没有密码的。
# 配置文件中 requirepass 密码 # 客户端程序中 config get requirepass # 获取密码 config set requirepass "111" # 设置密码
限制 CLIENTS
maxclients 10000 # 设置能连接上redis的最大连接数 maxmemory# 配置最大的内存容量 maxmemory-policy noevicion # 内存达到上限后的处理策略
maxmemory-policy策略
- volatile-lru:
只对设置了过期时间的key进行LRUallkeys-lru:
删除LRU算法的keyvolatile-random:
随机删除即将过期的keyallkeys-random:
随机删除volatile-ttl:
删除即将过期的keynoeviction:
永不过期,返回错误
APPEND onLY 模式 aof配置
appendonly no # 默认是不开启aof模式的,默认是rdb方式持久化,大部分情况下rdb完全够用 appendfilename "appendonly.aof" # 持久化的文件名字 # appendfsync awalys # 每次修改都会 sync 。消耗性能 appendfsync everysec # 没秒执行一次 sync ,可能会丢失这1秒数据 # appendfsync no # 不执行sync , 由操作系统自己同步数据,速度最快,但不安全Redis持久化
面试和工作,持久化是重点!
Redis是内存数据库,如果不能将内存中的数据库状态保存到磁盘,那么一旦服务器程序退出,服务器中的数据库状态也会消失。所以Redis提供了持久化功能。
RDB持久化什么是RDB?
在主从复制中,RDB就是备用了。在从机上面。
在指定的时间间隔内将内存中的数据集快照写入磁盘,就是就是行话Snapshot快照,它回复时是将快照文件直接读到内存里。
Redis会单独创建(fork)一个紫禁城来进行持久化,会先将数据写入一个临时文件中,待持久化过程结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程是不进行任何IO操作的。这就确保了极高的性能。如果需要进行大规模的数据恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效。RDB的缺点是最后一次持久化后的数据可能丢失。我们默认的就是RDB,一般情况下不需要修改这个配置。
有时候在生产环境中我们需要对其进行备份。
rdb保存的文件是 dump.rdb 都是在我们的配置文件中快照中配置的。
save [time] [num] #当在time时间内操作了key达到num次则会触发保存
触发机制
- svae 的规则满足条件下,会自动触发rdb机制执行flushall命令也会触发rdb机制退出redis,也会产生rdb文件(shutdown)
如何恢复rdb文件
只需要将rdb文件放在我们redis启动目录即可,redis启动的时候会自动检查dump.rdb恢复其中的数据
查看需要存在的位置
> config get dir 1) "dir" 2) "/usr/local/bin" # 如果在这个目录下存在 dump.rdb 文件,启动就会自动恢复其中的数据
优缺点
优点:
- 适合大规模的数据恢复对数据的完整性不高
缺点:
- 需要一定的时间间隔进程操作,如果redis意外宕机了,这个最后一次修改数据就没有了。fork进程的时候,会占用一定的内存空间
将我们的所有命令记录下来,history,恢复的时候就全部重新执行这个文件
是什么
以日志的形式来记录每一个写操作,将Redis执行过的所有指令记录下来(读操作不记录),只允许追加文件但不可以改写文件,redis启动之初会读取该文件重新构建数据,换言之,redis重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作
AOF保存的是 appendonly.aof 文件
append
默认是不开启的,需要手动配置。我们只需要appendonly 改为 yes 即可开启AOF。
重启,redis就可以生效,生成appendonly.aof文件。
如果AOF文件有错误,是无法启动redis的,我们需要修复AOF这个文件。
redis提供了一个工具redis-check-aof : (redis-check-aof --fix appendonly.aof)
重写规则说明
AOF文件会无限追加,因此会越来越大。
auto-aof-rewrite-percentage 100 #重写百分比 auto-aof-rewrite-min-size 64mb
如果AOF文件大于64m,就会fork一个新进程来将文件进行重写。
优缺点
appendonly no # 默认是不开启aof模式的,默认是rdb方式持久化,大部分情况下rdb完全够用 appendfilename "appendonly.aof" # 持久化的文件名字 # appendfsync awalys # 每次修改都会 sync 。消耗性能 appendfsync everysec # 没秒执行一次 sync ,可能会丢失这1秒数据 # appendfsync no # 不执行sync , 由操作系统自己同步数据,速度最快,但不安全 #rewrite 重写
优点:
- 每次修改都同步,文件完整性会更加好每秒同步一次,可能丢失一秒数据从不同步,效率高
缺点:
- 相对于数据文件来说,AOF远远大于RDB,修复速度也比rdb慢AOF运行效率也比RDB慢,所以我们通常使用RDB
- RDB持久化方式能够在指定的时间间隔内对数据进行快照存储AOF持久化方式记录每次对服务器写的操作,当服务器重启的时候会重写执行这些命令来恢复原始的数据,AOF命令以Redis协议追加保存每次写的操作到文件末尾,Redis还能对AOF文件进行后台重写,使得AOF文件的体积不至于过大。只做缓存,如果只希望数据在服务器运行的时候存在,可以不使用任何持久化同时开启两种持久化方式
在这种情况下,当redis重启的时候会优先载入AOF文件来恢复原始的数据,因为通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整RDB的数据虽然没有那么完整,但是RDB更加适合作为备份数据库(AOF不断变化不好备份),快速重启,而且不会有AOF可能潜在的bug,留着预备万一。 性能建议
因为RDB文件只用作后备用途,建议只在Slave上持久化RDB文件,而且只要15分钟备份一次就行,值保留save 900 1这一条规则如果Enable AOF,好处是在最恶劣的情况下也只会丢失不超过两秒数据,启动脚本较为简单只需要加载AOF文件。代价是带了持续的IO,二是AOF rewrite的最后将rewrite过程中产生的新数据写到新文件造成的阻塞几乎不可避免。只要硬盘许可,应该尽量减少AOF rewrite频率,可以设置容量为5G以上,比例也可以改到合适的数值。如果不Enable AOF,仅靠Master-Slave Replication实现高可用性也可,可以节省很多IO,减少rewrite时带来的系统波动。代价是如果Master、Slave同时宕机,会丢失十几分钟的数据,启动脚本也要比较Master、Slave中的RDB文件,载入较新的那个,微博就是采取该架构。
Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。微信、微博、关注系统。
Redis 客户端可以订阅任意数量的频道。
订阅、发布消息图:
第一个:消息发布者,第二个:频道,第三个:消息订阅者
命令
这些命令被广泛用于构建即时通信应用,比如网络聊天室(chatroom)和实时广播、实时提醒等。
- PSUBSCRIBE [模式1] ([模式2] ...)
订阅一个或多个 给定模式 的频道PUBSUB [subcommand] ([argument] ...)
查看订阅与发布系统状态PUBLISH [channel] [message]
将信息发送到指定的频道PUNSUBSCRIBE ([模式] ...)
退订所有给定模式的频道SUBSCRIBE [channel1] ([channel2 ...])
订阅一个或多个频道UNSUBSCRIBE ([channel] ...)
退订给定的频道
A> SUBSCRIBE b # 订阅频道b Reding messages... 1) "subscribe" 2) "b" 3) (integer) 1 1) "message" # 收到消息 2) "b" # 频道 3) "hello" # 消息内容 B> PUBLISH b "hello" # 向b频道发送消息 "hello"
原理
通过SUBSCRIBE订阅某个频道后,redis-server维护一个字典,字典的键就是一个个 频道。二字典的值是一个链表。链表中保存了索引订阅了这个channel的客户端。SUBSCRIBE就是将客户端放入给定channel的订阅链表中。
通过PUBLISH命令向订阅者发送消息,redis-server会使用给定的频道作为键,在维护的字典中找到对应链表,然后遍历链表,将消息发布给所有订阅者。
使用场景:
- 实时消息系统实时聊天(频道当做聊天室)订阅,关注系统
稍微复制的场景我们要使用专门的消息中间件 kafka 之类
Redis主从复制 概念主从复制,是将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(master),后者称为从节点(slave)。==数据的复制是单向的,只能由主节点到从节点。==Master以写为主,Slave以读为主。
默认情况下,每台Redis服务器都是主节点。
且每一个主节点可以有多个从节点,但是一个从节点只能有一个主节点。
主从复制的作用:
- 数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。故障恢复:当主节点出现问题,可以由从节点提供写服务,由从节点提供读服务,分担服务器负载。尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高Redis服务器的并发量。高可用(集群)基石:除了上诉作用外,主从复制还是哨兵和集群能够实施的基础,因此主从复制是Redis高可用的基础
一般来说,要将Redis运用于工程项目中,只使用一台Redis是万万不能的(宕机,一主二从),原因:
- 从结构上:单个Redis服务器会发送单点故障,并且一台服务器需要处理所有的请求负载,压力大。从容量上:单个Redis服务器内存容量有限,单台Redis最大使用内存不应该超过20G。
电商网站上的商品,一般都是上传一次,无数次浏览,专业术语(多读少写)。
主从复制,读写分离。80%的情况下都是在进行读操作。减缓服务器压力。
从机配置只配置从库,不用配置主库。
127.0.0.1:6379> info replication # 查看当前库的信息 # Replication role:master # 角色 master connected_slaves:0 # 从机数量 master_repl_offset:0 repl_backlog_active:0 repl_backlog_size:1048576 repl_backlog_first_byte_offset:0 repl_backlog_histlen:0
指定主机
- 命令配置 SLAVEOF [host] [port] 临时修改配置文件 SLAVEOF [host] [port] 永久
细节
从机可以写,从机不能写只能读。主机中的所有信息和数据,都会被从机保存。
主机断开连接,从机依旧连接到主机。若主机恢复,从机依旧可以直接获取到主机写的信息。
如果是使用命令行,来临时配置的主从,若此时从机断开,就会变回主机,需要重新配置。
只要配置为从机,里面会从主机中获取值。
复制原理
Slave启动成功连接到master后会发送一个sync同步命令
Master接到命令,启动后台的存盘进程,同时收集所有接收到的用于修改数据集的命令,在后台进程执行完毕之后,master将传送整个数据文件到slave,并完成一次完全同步。
全量复制:而slave服务在接收到数据库文件数据后,将其存盘并加载到内存中。
增量复制:Master继续将新的所有收集到的修改命令依次传给slave,完成同步。
但是只要是重新连接maste,一次完全同步(全量复制)将被自动执行。数据一定可以在从机中看到。
变成主机
谋朝篡位
如果主机断开了连接,从机可以使用SLAVEOF NO ONE让自己变成主机
然后其他从节点再连接到这个新主机上。
原主机恢复后需要重新配置,才能恢复原本状态。
哨兵模式(自动选举老大的模式)
概述
主从切换技术的方法是:当主服务器宕机后,需要手动把一台服务器切换为主服务器,这就需要人工干预,费事费力,还会造成一段时间内服务不可用。推荐使用哨兵模式(Redis2.8版本推出sentinel)
能够后台监控主机是否故障,如果故障了根据投票数自动将从库转换为主库。
哨兵模式是一种特殊地 模式,首先Redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程,他会独立运行。其原理是哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例。
配置
配置哨兵配置文件 sentinel.conf
# sentinel monitor 被监控的名称 host port 1 sentinel monitor myredis 127.0.0.1 6379 1
后面的的这个数字1,代表主机挂了,slave投票看让谁接替成为主机(票数最多的)
启动哨兵进程 redis-sentinel 配置目录/sentinel.conf
如果Master节点断开了,这时候会从从机中投票选举新的主机
如果主机此时回来了,只能归于新的主机下,当做从机。
哨兵模式
有点:
- 哨兵集群,基于主从复制模式,所有的主从配置优点都具有主从可以切换,故障可以转移,系统的可用性就会更好哨兵模式就是主从模式的升级,手动到自动,更加健壮
缺点:
- Redis不好在线扩容,集群容量一旦到达上限,在线扩容十分麻烦实现哨兵模式的配置非常麻烦,有很多种选择
哨兵模式的全部配置
# Example sentinel.conf # 哨兵sentinel实例运行的端口 默认26379 port 26379 # 哨兵sentinel的工作目录 dir /tmp # 哨兵sentinel监控的redis主节点的 ip port # master-name 可以自己命名的主节点名字 只能由字母A-z、数字0-9 、这三个字符".-_"组成。 # quorum 当这些quorum个数sentinel哨兵认为master主节点失联 那么这时 客观上认为主节点失联了 # sentinel monitorRedis缓存击穿和雪崩 缓存穿透sentinel monitor mymaster 127.0.0.1 6379 2 # 当在Redis实例中开启了requirepass foobared 授权密码 这样所有连接Redis实例的客户端都要提供密码 # 设置哨兵sentinel 连接主从的密码 注意必须为主从设置一样的验证密码 # sentinel auth-pass sentinel auth-pass mymaster MySUPER--secret-0123passw0rd # 指定多少毫秒之后 主节点没有应答哨兵sentinel 此时 哨兵主观上认为主节点下线 默认30秒 # sentinel down-after-milliseconds sentinel down-after-milliseconds mymaster 30000 # 这个配置项指定了在发生failover主备切换时最多可以有多少个slave同时对新的master进行 同步, 这个数字越小,完成failover所需的时间就越长, 但是如果这个数字越大,就意味着越 多的slave因为replication而不可用。 可以通过将这个值设为 1 来保证每次只有一个slave 处于不能处理命令请求的状态。 # sentinel parallel-syncs sentinel parallel-syncs mymaster 1 # 故障转移的超时时间 failover-timeout 可以用在以下这些方面: #1. 同一个sentinel对同一个master两次failover之间的间隔时间。 #2. 当一个slave从一个错误的master那里同步数据开始计算时间。直到slave被纠正为向正确的master那里同步数据时。 #3.当想要取消一个正在进行的failover所需要的时间。 #4.当进行failover时,配置所有slaves指向新的master所需的最大时间。不过,即使过了这个超时,slaves依然会被正确配置为指向master,但是就不按parallel-syncs所配置的规则来了 # 默认三分钟 # sentinel failover-timeout sentinel failover-timeout mymaster 180000 # scriptS EXECUTION #配置当某一事件发生时所需要执行的脚本,可以通过脚本来通知管理员,例如当系统运行不正常时发邮件通知相关人员。 #对于脚本的运行结果有以下规则: #若脚本执行后返回1,那么该脚本稍后将会被再次执行,重复次数目前默认为10 #若脚本执行后返回2,或者比2更高的一个返回值,脚本将不会重复执行。 #如果脚本在执行过程中由于收到系统中断信号被终止了,则同返回值为1时的行为相同。 #一个脚本的最大执行时间为60s,如果超过这个时间,脚本将会被一个SIGKILL信号终止,之后重新执行。 #通知型脚本:当sentinel有任何警告级别的事件发生时(比如说redis实例的主观失效和客观失效等等),将会去调用这个脚本, 这时这个脚本应该通过邮件,SMS等方式去通知系统管理员关于系统不正常运行的信息。调用该脚本时,将传给脚本两个参数, 一个是事件的类型, 一个是事件的描述。 如果sentinel.conf配置文件中配置了这个脚本路径,那么必须保证这个脚本存在于这个路径,并且是可执行的,否则sentinel无法正常启动成功。 #通知脚本 # sentinel notification-script sentinel notification-script mymaster /var/redis/notify.sh # 客户端重新配置主节点参数脚本 # 当一个master由于failover而发生改变时,这个脚本将会被调用,通知相关的客户端关于master地址已经发生改变的信息。 # 以下参数将会在调用脚本时传给脚本: # # 目前 总是“failover”, # 是“leader”或者“observer”中的一个。 # 参数 from-ip, from-port, to-ip, to-port是用来和旧的master和新的master(即旧的slave)通信的 # 这个脚本应该是通用的,能被多次调用,不是针对性的。 # sentinel client-reconfig-script sentinel client-reconfig-script mymaster /var/redis/reconfig.sh
概念
用户查询一个数据,发现redis中没有(缓存未命中),于是向持久层数据库查询。发现也没有,查询失败。当用户很多时候缓存都没有命中(秒杀场景),会给持久层数据库造成很大的压力。
解决方案
布隆过滤器
布隆过滤器是一种数据结构,对所有可能查询的参数以hash形式存储,在控制层先进行校验,不符合则丢弃,从而避免对底层存储系统的查询压力。
缓存空对象
如果查询的对象 持久层数据库 中没有则在 redis 缓存中创建一个空对象
存在的问题:
- 如果空值能够被缓存,这就意味着缓存需要更多的空间存储更多的键,因为这当中可能会有很多的空值键即使对空值设置了过期时间,还是会存在缓存层和存储层的数据会有一段时间窗口的不一致,这对于需要保持一致性的业务会有影响。
微博服务器宕机
概述
缓存击穿指一个key非常热点,在不停的扛着大并发,大并发集中对着一个点进行访问,当这个key在失效的瞬间,持续的大并发就击破缓存,直接请求数据库,就像一个屏幕上凿开了一个洞。
当某个key在过期的瞬间,有大量的请求并发访问,这类数据一般是热点数据,由于缓存过期,会同时访问数据库来查询最新数据,并且写回缓存,会导致数据库瞬间压力过大。
解决方案
设置热点数据永不过期
从缓存层面来看,没有设置过期时间,所以不会出现热点key过期后产生的问题
加互斥锁
分布式锁:使用分布式锁,保证对于每个key同时只有一个线程去查询后端服务,其他线程没有获得分布式锁的权限,因此只需要等待即可。这种方式将高并发的压力转移到了分布式锁,因此对分布式锁的考验很大。
缓存雪崩概念
缓存雪崩,是指在某一个时间段,缓存集中过期失效。Redis宕机!
产生雪崩的原因之一:周期性大量访问,缓存过期后查询将落在数据库上,产生周期性压力波峰。
其中集中过期,倒不是非常致,比较致命的缓存雪崩是 缓存服务器某个节点宕机或断网。因为自然形成的缓存雪崩,一定是某个时间段集中创建缓存,这个时候,数据库勉强可以顶住压力。无非是产生周期性压力而已。而缓存服务器节点的宕机,对数据库服务器造成的压力是不可预知的,很有可能瞬间把数据库压垮。
解决方案
redis高可用
多设置几台redis缓存服务器,搭建集群。
限流降级
在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。
数据预热
在正式部署之前,把可能的热点数据预先访问一遍。在即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。
双十一,停掉一些服务,为主服务减轻压力。



