概述
本文属于学习Redis过程中整理的笔记,学习视频为B站狂神Redis视频,可能有部分笔记叙述有误的地方,欢迎指正。
狂神说Java-Redis学习
基本知识
-
基本知识:
- Redis有16个数据库,默认使用第一个数据库,可以使用select语句切换数据库,各数据库之间独立;
- 使用 " keys * " 查看当前数据库下的所有key;
- 使用 " flushdb " 清空当前数据库的键值对," flushall " 清除全部数据库中的内容;
- Redis是单线程的;
- Redis是基于内存操作的,内存的读写速度非常快(纯内存); 数据存在内存中,数据结构用HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1);
- 使用单线程可以省去很多上下文切换的时间;
- redis使用IO多路复用技术,可以处理并发的连接;
- 为什么设计成单线程的?——因为Redis是基于内存操作的,CPU并不是Redis性能的瓶颈,Redis瓶颈是网络带宽和机器内存的大小,而使用单线程可以比使用多线程省去很多麻烦,因此使用单线程。
-
Redis-Key的基本命令:
keys * //显示所有的key值 exists key //查看某个key是否存在 move key index //移除数据库index中的某个键key所代表的键值对 expire key long //设置key所代表的键值对在long秒后过期 ttl key //查看key所代表的键值对的存活时间 type key //查看当前key的数据类型
-
String字符串:
append key value //为目标key所携带的原始值添加后缀value,如果该key不存在则新建 strlen key //获取目标key对应的value的长度 incr key //使目标key对应的value值(如果是数字的话)自增 incrby key length //使目标key对应的value值(如果是数字的话)自增length大小 decr key //使目标key对应的value值(如果是数字的话)自减 decrby key length //使目标key对应的value值(如果是数字的话)自减length大小 getrange key start end //获得目标key对应的字符串的子串(范围由start到end,前闭后闭) //如果end值取-1代表取到字符串尾部 setrange key offset value //从目标key对应的字符串的offset位置开始,将其后等长内容替换为value setex key seconds value //为已存在的key-value对设置新值和过期时间 //如果不存在则新建并设置过期时间 setnx key value //如果目标key不存在则新建,如果存在则保持原貌,有返回值 //不存在新建返回1,已存在则返回0 mset k1 v1 k2 v2 k3 v3 //批量设置键值对 mget k1 k2 k3 //批量获取键值对 msetnx k1 v1 k2 v2 //批量设置键值对(在都是not exist的情况下,随便一个exist都导致操作失误,原子操作) set user:1 {name:Lin,age:22} //将用户id为1的用户信息以json的格式存到redis中 //这样子在java中只需要实现对信息实现序列化即可保存,取出时使用反序列化即可 mset user:1:name Lin user:1:age 22 //将每个字段单独存放,虽然麻烦了一点,但是提高了复用性 //上面的方法需要使用个别字段时很麻烦 getset key value //先拿到目标key的value再设置新值
-
List列表:所有的List命令都是用l开头的,底层结构是链表
lpush listname value //将value添加到目标列表listname中,插入到头部 lrange listname start end //查看目标列表中的内容 rpush listname value //将value添加到目标列表listname中,添加到尾部 lpop listname //移除列表的头部第一个元素 rpop listname //移除列表的尾部第一个元素 lindex listname //通过下标获取列表中的值 llen listname //获得list的长度 lrem listname count element //移除list中的指定element,count用于指定移除多少个 //因为list中允许存放重复元素,从头开始移除直到满足count ltrim listname start end //截取list,使得只剩下start到end的元素,左闭右闭 rpoplpush source dest //将source的队尾元素取出后添加到dest队头 lset listname index element //将list中的index位置元素更新,前提是index存在 linsert listname before|after element element //往list中的第一个element前面或者后面插入一个element
-
Set集合:
sadd setname member [member …… ] //往集合中添加元素,插入到队头 smembers setname //查看集合中的元素 sismember setname member //查看集合中是否存在某个member,返回1存在,否则不存在 scard setname //获取set集合中的元素个数 srem setname member //移除集合中的member srandmember setname [count] //随机抽取set中的元素并返回,count指定返回几个,默认是1 spop setname [count] //顺序取出队尾元素,先进先出,count指定取出个数,默认是1 smove source dest member //将源目标集合中的一个member添加到dest集合中 sdiff setname1 [setname ……] //做差集处理 sinter setname1 [setname ……] //做交集处理 sunion setname1 [setname ……] //做并集处理
-
Hash哈希:
hset hashname field value //给目标哈希表中添加KV键值对 hget hashname field //从目标哈希表中取出field对应的值 hmset hsahname field vlaue [field value] //同时设置多个值 hmget hashname field field //同时取出多个值 hgetall hashname //获取目标哈希表中的所有key-value hdel hashname field //删除哈希表中的指定KV键值对 hlen hashname //查看目标哈希表中有多少键值对 hexists hashname field //判断目标哈希表中是否存在某个field,返回1存在,否则不存在 hkeys hashname //获取哈希表中的所有field hvals hashname //获取哈希表中的所有value hincrby hashname field increment //给哈希表中目标字段的value自增increment hdecrby hashname field decrement //给哈希表中目标字段的value自减decrement hsetnx hashname field value //给哈希表中添加不存在的KV键值对,如果存在则不添加
应用:可以用来存储变更数据或者说对象,比如user的详细信息;
-
ZSet有序集合:
zadd zsetname score element //添加权值为score的element到有序集合zset中,score越小优先级越高 zrange zsetname start end //显示zset的内容元素 zrevrange zsetname start end //逆序显示zset的内容元素,start到end zrangeByScore zsetname min max //输出值在min-max之间的已排序set集合 zrem zsetname element //移除有序集合中的元素 zcard zsetname //查看有序集合中的元素个数 zcount zsetname min max //查看min-max的区间有多少个元素
应用:存储班级成绩,工资表,实现排行榜。
-
geospatial地理位置:只有六个命令
geoadd key longitude latitude member [longtitude latitude member ……] geoadd china.city xxx xxx beijing xxx xxx xxx shanghai //有点像集合,用来存放一大堆的城市的经纬度信息 //有效经度-180~+180,有效纬度-85~+85 geopos key member [member] //从集合中取出相关元素的地理位置信息 geodist key member1 member2 [m|km|ft|mi] //返回两地之间的距离,后面选择参数为距离单位,米,千米,英里,英尺,默认为米 georadius key longitude latitude redius m|km|ft|mi //以给定的经纬度为原点,根据redius半径进行查找集合里的其他元素 georadiusbymember key member radius m|km|ft|mi //找出位于指定元素周围的其他元素 geohash key member [member ……] //返回一个或几个位置的hash表示
GEO底层的数据结构是ZSet,排序的依据就是元素的hash表示
-
Hyperloglog基数统计:基数是指不重复的元素,基数统计是指统计不重复元素的个数,可用于记录网站的用户访问数,传统的做法是使用Set数据结构,但是这样会为了计数增加数据保存;使用Hyperloglog的话,内存占用的情况很小;
pfadd pfname element [element ……] //往一个pfname中添加元素element pfcount pfname //给pfname做基数统计 pfmerge destkey sourcekey [sourcekey ……] //将sourcekey合并到destkey
-
Bitmaps位图:所有操作都是操作二进制位进行记录,只有0和1两个状态;
setbit bitname offset value //给bitname位图的offset位置设置值value getbit bitname offset //查看bitname位图的offset位置的值 bitcount bitname [start end] //计算从start到end的1的个数
事务
-
Redis的单条命令是保持原子性的,但是Redis事务不保持原子性;
-
Redis事务的本质:
(1)一组命令的集合,一个事务中的所有命令都会被序列化,在事务执行过程中,会按照顺序执行;
(2)Redis事务没有隔离级别的概念,因为序列化执行命令不需要;
(3)Redis的事务:
multi //开启事务 set name xxx get name set field xxx get field …… //命令入队 exec //执行事务 discard //放弃事务,事务内的所有命令不会执行
-
事务的错误处理:(导致没有原子性的原因)
- 编译型异常,即命令编译失败,所有命令都不会被执行;
- 运行时异常,即逻辑错误,比如Set中查找不存在的index的元素,对字符自增等,可以类比java中的1/0,这种情况下只有问题命令执行异常抛出错误,其他命令正常执行,因此不保证原子性;
-
Redis乐观锁:watch
watch key //监控某个key,主要是在事务中起到作用
watch命令会对key进行一次快照,编写事务的过程中,如果对watch的key进行更新,会导致事务无法执行(事务中的其他命令也不执行,即使命令在出错命令前面)。redis中通过watch实现乐观锁,底层机制不是比较版本号,而是比较版本内容,即存在ABA问题;
因为乐观锁被触发导致事务失败怎么办?——解锁后加锁最新的数据重新执行事务即可;
可以做秒杀!
Jedis——Java连接开发工具
-
导入Jedis依赖——从maven上面找
-
Java中提供一个Jedis类用来表示主机与Redis之间的连接,使用方法如下:
Jedis jedis = new Jedis("127.0.0.1",6379); //与127.0.0.1的6379端口建立连接 //如果是远程连接,需要对方开放6379端口 jedis.xxx(); //Redis的指令全部成为了jedis中的方法,指令参数为方法参数,直接调用即可 //执行事务的时候,由于事务不保证原子性,因此可以参考以下的代码结构 try{ jedis.multi(); jedis.xxx(); jedis.xxx(); jedis.exec(); }catch(Exception e){ jedis.discard(); }finally{ jedis.close(); }
-
SpringBoot整合Redis:
-
引入依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!-- 使用spring-data对redis进行处理 -->
-
SpringBoot2.0开始,支持jedis和lettuce两种客户端,默认使用lettuce客户端;jedis配置在SpringBoot2中已失效;
jedis——底层采用直接连接,多线程操作下是不安全的,如果想要避免需要使用连接池,有点像BIO
lettuce——底层采用netty实现,只有一个实例,可以在多线程中共享,不存在线程不安全的情况,更像NIO
-
SpringBoot2中提供了两个工具类,RedisTemplate<Object, Object>和StringRedisTemplate用于实现Java对Redis的操作,这里用的注入注解是ConditionOnMissingBean,即我们可以通过自定义的方式实现覆盖。
@Bean @ConditionalOnMissingBean( name = {"redisTemplate"} ) public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException { //这里没有做序列化操作,实际上存储到Redis中会出现一些问题 RedisTemplate<Object, Object> template = new RedisTemplate(); template.setConnectionFactory(redisConnectionFactory); return template; } @Bean @ConditionalOnMissingBean public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException { StringRedisTemplate template = new StringRedisTemplate(); template.setConnectionFactory(redisConnectionFactory); return template; }
-
使用:
@Autowired private RedisTemplate redisTemplate; @Test public void test(){ redisTemplate.opsForValue(); //对应String redisTemplate.opsForList(); //对应List redisTemplate.opsForSet(); //对应Set redisTemplate.opsForHash(); //对应Hash redisTemplate.opsForZSet(); //对应ZSet redisTemplate.opsForGeo(); //对应GEO redisTemplate.opsForHyperLogLog(); //对应基数统计 //获取连接对象 RedisConnection connection = redisTemplate.getConnectionFactory().getConnection(); }
-
存在的问题:在Java中查看操作是没有问题的,但是在Redis查看存放情况会发现没有实现序列化带来的问题:
-
-
自定义RedisTemplate:
-
自定义的pojo类最好实现序列化(实现序列化接口即可),因为在RedisTemplate中,如果直接存放对象的话,会报错,而如果存放的是实现了序列化的对象,则可以正常运行;
-
默认实现序列化方式是JDK序列化,也可以通过自定义实现JSON序列化或其他序列化;
-
代码:
@Configuration public class RedisConfig { @Bean public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory factory){ RedisTemplate<String,Object> redisTemplate = new RedisTemplate<>(); redisTemplate.setConnectionFactory(factory); //自定义Jackson序列化配置 Jackson2JsonRedisSerializer jsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); ObjectMapper objectMapper = new ObjectMapper(); objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL); jsonRedisSerializer.setObjectMapper(objectMapper); //key使用String的序列化方式 StringRedisSerializer stringRedisSerializer = new StringRedisSerializer(); redisTemplate.setKeySerializer(stringRedisSerializer); //hash的key也是用String的序列化方式 redisTemplate.setHashKeySerializer(stringRedisSerializer); //value的key使用jackson的序列化方式 redisTemplate.setValueSerializer(jsonRedisSerializer); //hash的value也是用jackson的序列化方式 redisTemplate.setHashValueSerializer(jsonRedisSerializer); redisTemplate.afterPropertiesSet(); return redisTemplate; } }
-
Redis高级
-
Redis.conf详解:
-
INCLUDES:引入配置文件
-
NETWORK:
bind 127.0.0.1 //指定redis只接收来自该IP地址的请求,不设置将处理所有请求 protected-mode yes //是否开启保护模式,默认开启,开启后拒绝外部访问 port 6379 //redis监听的端口号
-
GENERAL:
daemonize yes //是否设置为守护/后台进程 pdifile /var/run/redis_6379.pid //如果以后台的方式进行,我们就需要指定一个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 logfile "" //日志的文件位置名 database 16 //数据库的数量,默认是16个数据库 always-show-logo yes //是否总是显示logo
-
SNAPSHOTTING:快照,与持久化相关
# RDB核心规则配置,save <指定时间间隔> <执行指定次数更新操作>,满足条件就将内存中的数据同步到磁盘中, # 官方配置默认是 900秒内有一个更改,300秒内有10个更改,60秒内有10000个更改, # 则将内存中的数据快照写入磁盘 # save "" save 900 1 save 300 10 save 60 10000 stop-writes-ob-bgsave-error yes //当RDB持久化出现错误后是否还要继续工作 rdbcompression yes //是否压缩rdb文件,需要消耗一定的cpu资源 rdbchecksum yes //保存rdb文件的时候,是否要做差错校验,会有一定的性能损耗 dbfilename dump.rdb //指定本地数据库文件名 dir ./ //rdb文件存储目录
-
REPLICATION:有关主从复制;
-
SECURITY:可以在这里设置redis的密码,默认没有密码,也可以通过命令行设置
requirepass 自定义的密码 //设置密码
127.0.0.1:6379> config get requirepass 1) "requirepass" 2) "" 127.0.0.1:6379> config set requirepass 123456 OK 127.0.0.1:6379> auth 123456 OK 127.0.0.1:6379> ping PONG
-
CLIENTS:一些限制
maxclients 10000 //允许连接的最大客户端数量 maxmemory <bytes> //redis配置最大的内存容量 # maxmemory-policy noeviction //内存容量溢出后的处理策略 # volatile-lru:利用LRU算法移除设置过过期时间的key。 # volatile-random:随机移除设置过过期时间的key。 # volatile-ttl:移除即将过期的key,根据最近过期时间来删除(辅以TTL) # allkeys-lru:利用LRU算法移除任何key。 # allkeys-random:随机移除任何key。 # noeviction:不移除任何key,只是返回一个写错误。 # 上面的这些驱逐策略,如果redis没有合适的key驱逐,对于写命令,还是会返回错误
-
APPEND ONLY MODE:AOF持久化配置
appendonly no //默认不开启AOF模式,RDB足够使用 appendfilename "appendonly.aof" //持久化的文件名字 # appendfsync always //每次修改都会sync,消耗性能 appendfsync everysec //每秒执行一次sync,可能会丢失这1s的数据 # appendfsync no //不执行sync,操作系统自己同步是,速度最快
-
-
持久化:
- 为什么需要持久化——因为Redis是内存数据库,如果不做持久化会断电即失;
- Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程是不进行任何I/O操作的,这就确保了极高的性能。如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加得高效。RDB得缺点是最后一次持久化后的数据可能丢失。RDB的保存文件默认是dump.rdb;
- AOF——Append Only File,以日志的形式来记录每个写操作,将Redis执行过的所有指令记录下来(写操作不记录),只许追加文件不可改写文件,Redis启动之初会读取该文件重新构建数据,即Redis重启会根据日志文件的内容将写指令从前到后执行一次完成数据库恢复工作;AOF保存的是appendonly.aof文件;
-
RDB持久化:
- save的规则满足的情况下会自动触发持久化;
- 执行flushall命令也会触发持久化;
- 退出redis也会产生rdb文件。
怎么通过rdb文件恢复数据库?
- 从rdb存储目录(conf文件中可以设置)中检索目标文件(dump.rdb,conf文件中可以设置),如果检索成功自动恢复即可;
优点:
- 适合大规模的数据恢复;
- 对数据的完整性要求不高可以考虑RDB持久化。
缺点:
- 需要一定的时间间隔进行进程操作,如果Redis意外宕机了,那么最后一次修改的数据就丢失了;
- fork进程的时候,会占用一定的内存空间。
-
AOF持久化:
-
默认是每一秒保存一次写命令,AOF文件的写入(即AOF持久化过程)也是由子进程来完成的;
-
如果AOF文件被损坏了,那样会导致Redis无法正常启动,可以使用以下工具恢复AOF文件:
redis-check-aof --fix appendonly.aof
-
如果AOF文件过大(默认设置是超过64m),Redis就会再开一个进程,主要负责对原AOF文件进行重写;
-
优点:
- 可以设置为每一次修改都同步,虽然性能会下降但是文件的完整性会更加好;
缺点:
- AOF文件大小比RDB答,修复速度也比RDB慢;
- AOF运行效率也比RDB慢,因此Redis默认配置是选择RDB;
-
-
关于持久化:
- 如果只是使用Redis作为缓存,只是希望数据在服务器运行期间存在,那么也可以不使用持久化;
- 同时开启两种持久化方式的情况下:
- Redis重启优先考虑AOF文件来恢复至原始的数据,因为通常AOF保存的数据集要比RDB更加完整;
- RDB的完整性不如AOF,重启也是优先使用AOF,可不可以只开启AOF持久化?Redis作者建议不要,因为RDB更适合用于备份数据库(AOF在不断变化不好做备份),快速重启,而且不会有AOF可能潜在的BUG,留着作为一个容错;
- 性能建议:
- 如果RDB文件只用作后备用途,建议只在从机(主从复制)上持久化RDB文件,而且只要15分钟备份一次就够了,即只保留 " save 900 1" 这条规则;
- 如果开启AOF,在最恶劣的情况下也只丢失两秒的数据,而代价一个是带来了持续的I/O(不停地追加文件),一个是重写的过程中将新数据写到新文件会带来阻塞。因此,只要硬许可,应该尽量减少重写的频率,AOF重写默认阈值是64M,可以设置为5G以上;
- 如果不开启AOF,仅仅依靠主从复制实现高可用性也可以,最大的好处是省掉一大笔I/O损耗,也减少了重写时带来的系统波动。代价是如果主机从机同时宕机那么会丢失十几分钟的数据,主从复制在启动时会比较两个RDB文件中较新的那个,微博就是这样的架构。
-
Redis订阅发布:
publish channel message //推送信息给channel上的所有人 subscribe channel //订阅某个channel unsubscribe channel //退订某个channel
原理:
Redis是使用C实现的,通过SUBSCRIBE命令订阅某频道后,redis-server里维护了一个字典,字典的键就是一个个的Channel,而字典的值则是一个链表,链表中保存了所有订阅了这个Channel的客户端,SUBSCRIBE的关键,就是将客户端添加到给定Channel的订阅链表中。
通过PUBLISH命令向订阅者发送信息,redis-server会使用给定的Channel作为键,在它所维护的Channel字典中查找记录了订阅这个频道的所有客户端的链表,遍历发送信息给所有客户端。
-
Redis主从复制:
- 首先,Redis服务的开启是通过读取配置文件开启的,因此如果想要搭建集群的话(考虑到使用哨兵机制最少都要1主2从),需要进行多个配置文件的设置,同时更改配置文件中相关的参数:端口号,pid名字,log日志文件名,rdb备份文件名
- 通过命令 " info replication " 可以查看当前机器在主从复制的环境中是主机还是从机的状态,通过命令 " slaveof host port " 可以临时指定成为哪台主机的从机;
- 临时性配置在重启服务后即失效,如果想要永久配置主机从机绑定可以在配置文件中的Replication模块进行设置;
- 只要是从机,默认就只能读不能写,而主机既可以写也可以读;
- 每次服务重启(配置文件已配置主从绑定)或者每次指令设置主从绑定关系时,主机都会向从机发起以此全量复制(即一次完整的复制),之后每次有新的写操作时主机都会对从机发起增量复制,只复制更新的部分;
- 作用:
- 数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式;
- 故障恢复:当主节点出现问题时,可以由从结点提供服务,实现快速的故障恢复;(在哨兵机制出来之前该方式需要进行手动配置)
- 负载均衡:在主从复制的基础上配合读写分离,可以由主节点负责写,从节点负责读,尤其是在写少读多的情况下,通过多个从节点分担读负载,可大大提高并发量;
- 高可用:主从复制还是哨兵和集群能够实现的基础;
-
哨兵机制:
-
哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个独立的进程,其原理是哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例;这里的哨兵有两个作用:
- 一个是通过发送命令,让Redis服务器返回监控机器的运行状态;
- 一个是当哨兵监测到master宕机,会自动将slave切换成master,然后通过发布订阅模式通知其他的从服务器,修改配置文件让它们切换主机;
-
如果只有一个哨兵的话,可能会出现哨兵进程死亡的情况,因此可以引入哨兵集群,同时让不同的哨兵互相监督,在这种情况下,如果主服务器宕机,哨兵1先检测到这个结果,并不会马上进行故障恢复,而是判断当前处于哨兵1认为的主观下线状态,当其他哨兵也监测到主机不可用且达到一定数量时,那么哨兵之间就会进行一次投票,决定在slave中间推出一名新的master,切换成功后再通过订阅发布模式告知其他从服务器切换主机,这个过程称为客观下线。
-
配置哨兵模式,需要编写哨兵模式相关的配置文件,并使用工具redis-sentinel将配置文件作为参数启动进程;
-
哨兵模式相关配置:
# 哨兵 sentine1 监控的redis主节点的 ip port # master-name ,可以自己命名的主节点名字 只能由字母A-Z、数字0-9、这三个字符 " . - _ "组成。 # quorum配置多少个sentine1哨兵统一认为master主节点失联那么这时客观上认为主节点失联了 # sentine1 monitor <master-name> <ip> <redis-port> <quorum> sentinel monitor mymaster 127.0.0.1 6379 2 # 当在Redis实例中开启了requirepass foobared 授权密码这样所有连接redis实例的客户端都要提供密码 # 设置哨兵sentinel连接主从的密码注意必须为主从设置一样的验证密码 # sentine1 auth-pass <master-name> <password> sentine1 auth-pass mymaster MySUPER--secret-0123passwOrd # 指定多少毫秒之后主节点没有应答哨兵 sentine1 此时哨兵主观上认为主节点下线,默认30秒 # sentinel down-after-mi 11i seconds <master-name> <mi 11iseconds> sentine1 down-after-mi 11iseconds mymaster 30000 # 这个配置项指定了在发生failover主备切换时最多可以有多少个slave同时对新的master进行同步,这个数字越小 # 完成fai lover所需的时间就越长,但是如果这个数字越大,就意味着越多的slave因为replication而 不可用。 # 可以通过将这个值设为1来保证每次只有一个slave处于不能处理命令请求的状态。 # sentine1 paralle1-syncs <master-name> <numslaves> sentine1 paralle1-syncs mymaster 1 #故障转移的超时时间failover-timeout 可以用在以下这些方面: #1.同一个sentine1对同一 个master两次fai lover之间的间隔时间。 #2.当一个slave从一 个错误的master那里同步数据开始计算时间。直到s1ave被纠正为向正确的master那里同步数据时。 #3.当想要取消一个正在进行的failover所需要的时间。 #4.当进行failover时,配置所有s1aves指向新的master所需的最大时间。不过,即使过了这个超时,slaves #依然会被正确配置为指向master,但是就不按parallel-syncs所配置的规则来了 #默认三分钟 # sentine1 failover-timeout <master-name> <milliseconds> sentine1 fai lover-ti meout mymaster 180000 # SCRIPTS EXECUTION #配置当某一事件发生时所需要执行的脚本,可以通过脚本来通知管理员,例如当系统运行不正常时发邮件通知相关人员。 #对于脚本的运行结果有以下规则: #若脚本执行后返回1,那么该脚本稍后将会被再次执行,重复次数目前默认为10 #若脚本执行后返回2,或者比2更高的一个返回值,脚本将不会重复执行。 #如果脚本在执行过程中由于收到系统中断信号被终止了,则同返回值为1时的行为相同。 #一个脚本的最大执行时间为60s,如果超过这个时间,脚本将会被-一个SIGKILL信号终止,之后重新执行。 #通知型脚本:当sentine1有任何警告级别的事件发生时(比如说redis实例的主观失效和客观失效等等),将会去调用这个脚本 #这时这个脚本应该通过邮件,SMS等 方式去通知系统管理员关于系统不正常运行的信息。调用该脚本时,将传给脚本两个参数 #一个是事件的类型,一个是事件的描述。如果sentine1.conf配置文件中配置了这个脚本路径 #那么必须保证这个脚本存在于这个路径,并且是可执行的,否则sentine1无法正常启动成功。 #通知脚本 # she11编程 # sentine1 notification-script <master-name> <script-path> sentine1 notificati on-script mymaster /var/redis/notify. sh #客户端重新配置主节点参数脚本 #当一个master由于failover而发生改变时,这个脚本将会被调用,通知相关的客户端关于master地址已经发生改变的信息。 #以下参数将会在调用脚本时传给脚本: # <master-name> <role> <state> <from-ip> <from-port> <to-ip> <to-port> #目前<state>总是“failover", # <role>是“Teader"或者"observer"中的-一个。 #参数from-ip, from-port, to-ip,to-port是用来和旧的master和新的master(即旧的s lave)通信的 #这个脚本应该是通用的,能被多次调用,不是针对性的。 # sentine1 client-reconfig-script <master-name> <script-path> sentine1 client-reconfig-script mymaster /var/redis/reconfig.sh #一般都是由运维来配置!
-
-
缓存穿透,击穿和雪崩:
-
用户想要查询一个数据,发现Redis内存数据库没有,即缓存没有命中,而持久层数据库也没有,于是本次查询失败。当用户很多的时候,缓存都没有命中,于是都去请求数据库,这会给持久层数据库造成很大的压力,这种现象就称为缓存穿透。
解决方案:在缓存查询前设置一个过滤器,不处理不存在于持久层数据库的查询(设置布隆过滤器),或者对于缓存命中失败的情况,我们将其键存放到缓存数据库中,值设置为空,这样也可以解决问题,但是会增加缓存数据库的存储压力。
-
缓存击穿,是指一个热点key,在短时间内进行了大量查询,而在这个key缓存失效的一瞬间,高并发的查询瞬间击破缓存,直接查询数据库,同时数据库还需要负责回写缓存,瞬间导致数据库压力过大,与缓存穿透的区别是,缓存击穿时命中了的,而缓存穿透不命中;可以设置热点数据不过期,但会浪费内存空间,或者添加互斥锁(分布式锁),保证对于同一个key只有一个线程去查询后端数据,将高并发的压力转移给分布式锁(这里的加锁是加在缓存与数据库之间的);
-
缓存雪崩,指在某一个时间段内,大量缓存集中过期失效,导致在高并发的情况下数据库查询请求到达持久层,瞬间导致持久层数据库压力过大,可能会宕机。
解决方案:
- 多建立几个redis缓存,避免了redis宕机导致的缓存雪崩;
- 限流降级:指在缓存失效后,通过加锁或者队列的方式来控制读数据库写缓存的线程数量,减小压力(与前面分布式锁的思路差不多);
- 数据预热:先访问一遍热点数据,设置不同的过期时间,让缓存失效的时间点尽量平均;