一、Redis概述
- Redis是一个开源的key-value存储系统。
- 支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(sorted set --有序集合)和hash(哈希类型)。
- 这些数据类型都支持push/pop、add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。
- 在此基础上,Redis支持各种不同方式的排序。
- 与memcached一样,为了保证效率,数据都是缓存在内存中。
- 区别的是Redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件。
- 并且在此基础上实现了**master-slave(主从)**同步。
Redis是单线程+多路IO复用技术
多路复用 是指使用一个线程来检查多个文件描述符(Socket)的就绪状态,比如调用select和poll函数,传入多个文件描述符,如果有一个文件描述符就绪,则返回,否则阻塞直到超时。得到就绪状态后进行真正的操作可以在同一个线程里执行,也可以启动线程执行(比如使用线程池)
安装过程 及后台运行 本文章不会介绍,可查阅 码云地址,内有详解
二、redis数据类型及常用命令
1、redis key
-
select [index] :切换数据库–默认16个库 值为0-15
-
dbsize :查看当前库的key数量
-
flushdb:清空当前库
-
flushall:清空所有库
-
keys *:查看当前库所有key --匹配:keys *1
-
exists key:判断某个key是否存在 1存在 0 不存在
-
type key 查看你的key是什么类型
-
del key 删除指定的key数据 --仅将keys从keyspace元数据中删除,真正的删除会在后续异步操作。
-
expire key 10 10秒钟:为给定的key设置过期时间
-
ttl key 查看还有多少秒过期,-1表示永不过期,-2表示已过期
2、redis数据类型及该类型常用命令
1、String
String类型是二进制安全的,一个key对应一个value。
意味着Redis的string可以包含任何数据。比如jpg图片或者序列化的对象,value最多可以是512M。
-
set key value [expiration EX seconds|PX milliseconds] [NX|XX] :添加键值对 -可设置过期时间,key存在默认覆盖
*NX:当数据库中key不存在时,可以将key-value添加数据库
*XX:当数据库中key存在时,可以将key-value添加数据库,覆盖之前value
*EX:key的超时秒数
*PX:key的超时毫秒数,与EX互斥
如:set a a ex 1000 设置 key为 a 值为 a 过期时长为1000秒 -
setex key 时间 value :设置键值的同时,设置过期时间,单位秒。
-
get key :获取值
-
getset key value :设置了新值同时获得旧值。
-
append key value 将给定的 追加到原值的末尾
-
incr key:将 key 中储存的数字值增1,只能对数字值操作,如果为空,新增值为1
-
decr key :将 key 中储存的数字值减1,只能对数字值操作,如果为空,新增值为-1
-
mset key1 value1 key2 value2 :同时设置一个或多个 key-value对
-
mget key1 key2 :同时获取一个或多个 value
-
incrby / decrby key 步长 :将 key 中储存的数字值增减。自定义步长。
-
strlen key 获得值的长度
-
getrange key start end :类似substring 对值进行截取
-
setrange key startinx value : 从value的起始位置进行覆盖
注意:
incr 、decr 的操作是原子性的,每次只有一个线程操作,并不会被打断,一个失败 则全部失败。
数据结构
2、list:单键多值
Redis 列表是简单的字符串列表,按照插入顺序排序。它的底层实际是个双向链表,列表元素较少:ziplist,数据量比较多quickList(多个ziplist使用双向指针连接)。,你可以添加一个元素到列表的头部(左边)或者尾部(右边)。
一个key 对应的是一个列表
-
lpush/rpush key value1 value2… 从左边/右边插入一个或多个值。String类型key名相同就会报错 并且 push命令顺序是1 2 3 ,那么list value的顺序就是 3 2 1
-
lpop/rpop key 从左边/右边删除一个值。值在键在,值光键亡。
-
lrange key start stop :按照索引下标获得元素(从左到右)==0 -1表示获取所有
-
lindex key index:按照索引下标获得元素(从左到右)
-
llen key:获得列表长度
-
lrem key n value:从左边删除n个value(从左到右)
-
lset key index value :将列表key下标为index的值替换成value
-
rpoplpush key1 key2 从 key1 列表右边吐出一个值,插到 key2 列表左边。
-
linsert key before/after value newvalue:在value的前面/后面插入 newvalue 插入值
数据结构
3、集合set(无序:不按插入顺序排列)
Redis set对外提供的功能与list类似是一个列表的功能,特殊之处在于set是可以自动排重的,当你需要存储一个列表数据,又不希望出现重复数据时,set是一个很好的选择,并且set提供了判断某个成员是否在一个set集合内的重要接口,这个也是list所不能提供的。
Redis的Set是string类型的无序集合:不按插入顺序。它底层其实是一个value为null的hash表,所以添加,删除,查找的复杂度都是O(1)。
一个算法,随着数据的增加,执行时间的长短,如果是O(1),数据增加,查找数据的时间不变
- sadd key value1 value2 … :将一个或多个 元素加入到集合 key 中,已经存在的元素将被忽略
- smembers key :取出该集合的所有值
- sismember key value:判断集合是否为含有该值,有1,没有0
- scard key:返回该集合的元素个数。
- srem key value1 value2 :删除集合中的某个元素
- spop key :随机从该集合中吐出一个值,会删除。
- sinter key1 key2:返回两个集合的交集元素。
- sunion key1 key2:返回两个集合的并集元素。
- sdiff key1 key2:返回两个集合的差集元素(key1中的,不包含key2中的)
- srandmember key n :随机从该集合中取出n个值。不会从集合中删除 。
数据结构
4、有序(分数)集合:zset
Redis有序集合zset与普通集合set非常相似,是一个没有重复元素的字符串集合。
有序集合zset的每个成员都关联了一个评分(score),这个评分(score)被用来按照从最低分到最高分的方式排序集合中的成员。集合的成员是唯一的,但是评分可以是重复了 。
因为元素是有序的, 所以你也可以很快的根据评分(score)或者次序(position)来获取一个范围的元素。
访问有序集合的中间元素也是非常快的,因此你能够使用有序集合作为一个没有重复成员的智能列表。
-
zadd key score1 value1 score2 value2 …:将一个或多个 元素及其 score 值加入到有序集 key 当中
-
zrange key start stop [WITHSCORES] : 返回有序集 key 中,下标在 star stop 之间的元素0 -1 全部 。带WITHSCORES,可以让分数一起和值返回到结果集。
-
zrangebyscore key minmax [withscores] [limit offset count]
返回有序集 key 中,所有 score 值介于 min 和 max 之间(包括等于 min 或 max )的成员。有序集成员按 score 值递增(从小到大)次序排列。 -
zrevrangebyscore key maxmin [withscores] [limit offset count]
同上,改为从大到小排列。 -
zincrby key increment value > 为元素的score加上增量
-
zrem key value >删除该集合下,指定值的元素
-
zcount key min max >统计该集合,分数区间内的元素个数
-
zrank key value > 返回该值在集合中的排名,从0开始。
数据结构
5、hash存对象
Redis hash 是一个键值对集合。
Redis hash是一个string类型的field和value的映射表,hash特别适合用于存储对象。
类似Java里面的Map<String,Object>
用户ID为查找的key,存储的value是序列化用户对象包含姓名,年龄,生日等信息
key(用户ID) + field(属性标签)
- hset key field value : 给 key 集合中的 field 键赋值 value
- hget key1 field : 从 key1 集合 field 取出 value
- hmset key1 field1 value1 field2 value2 : 批量设置hash的值
- hexists key1 field : 查看哈希表 key 中, field 是否存在。
- hkeys key : 列出该hash集合的所有field
- hvals key : 列出该hash集合的所有value
- hincrby key field increment: 为哈希表 key 中的域 field 的值加上增量 1 -1
- hsetnx key field value:将哈希表 key 中的域 field 的值设置为 value ,当且仅当域 field 不存在 .
数据结构
三、redis配置文件redis.conf
1、网络相关配置
1、bind
默认情况bind=127.0.0.1只能接受本机的访问请求
不写的情况下 且 protected-mode no,无限制接受任何ip地址的访问
生产环境肯定要写你应用服务器的地址;服务器是需要远程访问的,所以需要将其注释掉
如果开启了protected-mode,那么在没有设定bind ip且没有设密码的情况下,Redis只允许接受本机的响应
2、protected-mode
将本机访问保护模式设置no
3、Port
默认 6379
4、tcp-backlog
设置tcp的backlog,backlog其实是一个连接队列,backlog队列总和=未完成三次握手队列 + 已经完成三次握手队列。
在高并发环境下你需要一个高backlog值来避免慢客户端连接问题。
注意Linux内核会将这个值减小到/proc/sys/net/core/somaxconn的值(128),所以需要确认增大/proc/sys/net/core/somaxconn和/proc/sys/net/ipv4/tcp_max_syn_backlog(128)两个值来达到想要的效果
5、timeout
一个空闲的客户端维持多少秒会关闭,0表示关闭该功能。即永不关闭。
6、tcp-keepalive
对访问客户端的一种心跳检测,每个n秒检测一次。
单位为秒,如果设置为0,则不会进行Keepalive检测,建议设置成60
2、通用
4.4.1.daemonize
是否为后台进程,设置为yes
守护进程,后台启动
4.4.2.pidfile
存放pid文件的位置,每个实例会产生一个不同的pid文件
4.4.3.loglevel
指定日志记录级别,Redis总共支持四个级别:debug、verbose、notice、warning,默认为notice
四个级别根据使用阶段来选择,生产环境选择notice 或者warning
4.4.4.logfile
日志文件名称
4.4.5.databases 16
设定库的数量 默认16,默认数据库为0
3、设置密码
4、限制
4.6.1.maxclients
- 设置redis同时可以与多少个客户端进行连接。
- 默认情况下为10000个客户端。
- 如果达到了此限制,redis则会拒绝新的连接请求,并且向这些连接请求方发出“max number of clients reached”以作回应。
2、maxmemory
- 建议必须设置,否则,将内存占满,造成服务器宕机
- 设置redis可以使用的内存量。一旦到达内存使用上限,redis将会试图移除内部数据,移除规则可以通过maxmemory-policy来指定。
- 如果redis无法根据移除规则来移除内存中的数据,或者设置了“不允许移除”,那么redis则会针对那些需要申请内存的指令返回错误信息,比如SET、LPUSH等。
- 但是对于无内存申请的指令,仍然会正常响应,比如GET等。如果你的redis是主redis(说明你的redis有从redis),那么在设置内存使用上限时,需要在系统中留出一些内存空间给同步队列缓存,只有在你设置的是“不移除”的情况下,才不用考虑这个因素
3、maxmemory-policy
- volatile-lru:使用LRU算法移除key,只对设置了过期时间的键;(最近最少使用)
- allkeys-lru:在所有集合key中,使用LRU算法移除key
- volatile-random:在过期集合中移除随机的key,只对设置了过期时间的键
- allkeys-random:在所有集合key中,移除随机的key
- volatile-ttl:移除那些TTL值最小的key,即那些最近要过期的key
- noeviction:不进行移除。针对写操作,只是返回错误信息
4、maxmemory-samples
- 设置样本数量,LRU算法和最小TTL算法都并非是精确的算法,而是估算值,所以你可以设置样本的大小,redis默认会检查这么多个key并选择其中LRU的那个。
- 一般设置3到7的数字,数值越小样本越不准确,但性能消耗越小。
四、redis的发布订阅
-
订阅一个主题
subscribe channel1
-
往主题上发布消息
publish channel1 hello
五、Sringboot 整合redis
1、pom
<!-- redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- spring2.X集成redis所需common-pool2-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.6.0</version>
</dependency>
2、application.properties
#Redis服务器地址
spring.redis.host=192.168.140.136
#Redis服务器连接端口
spring.redis.port=6379
#Redis数据库索引(默认为0)
spring.redis.database= 0
#连接超时时间(毫秒)
spring.redis.timeout=1800000
#连接池最大连接数(使用负值表示没有限制)
spring.redis.lettuce.pool.max-active=20
#最大阻塞等待时间(负数表示没限制)
spring.redis.lettuce.pool.max-wait=-1
#连接池中的最大空闲连接
spring.redis.lettuce.pool.max-idle=5
#连接池中的最小空闲连接
spring.redis.lettuce.pool.min-idle=0
3、配置类
package com.springboot.redis.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.time.Duration;
@EnableCaching
@Configuration
public class RedisConfig extends CachingConfigurerSupport {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
template.setConnectionFactory(factory);
// template.setEnableTransactionSupport(true); //开启事务支持
//key序列化方式
template.setKeySerializer(redisSerializer);
//value序列化
template.setValueSerializer(jackson2JsonRedisSerializer);
//value hashmap序列化
template.setHashValueSerializer(jackson2JsonRedisSerializer);
return template;
}
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
//解决查询缓存转换异常的问题
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
// 配置序列化(解决乱码的问题),过期时间600秒
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(600))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
.disableCachingNullValues();
RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
.cacheDefaults(config)
.build();
return cacheManager;
}
}
测试
@RestController
@RequestMapping("/redisTest")
public class RedisTestController {
@Autowired
private RedisTemplate redisTemplate;
@GetMapping
public String testRedis() {
//设置值到redis
redisTemplate.opsForValue().set("name","lucy");
//从redis获取值
String name = (String)redisTemplate.opsForValue().get("name");
return name;
}
}
六、redis事务
1、设置事务Multi、Exec、discard
Redis事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
Redis事务的主要作用就是串联多个命令防止别的命令插队。
- 从输入Multi命令开始,输入的命令都会依次进入命令队列中,但不会执行,
- 输入Exec后,Redis会将之前的命令队列中的命令依次执行。
- 组队的过程中可以通过discard来放弃组队。
2、事务的错误处理
- 组队中某个命令出现了报告错误,执行时整个的所有队列都会被取消。
- 如果执行阶段某个命令报出了错误,则只有报错的命令不会被执行,而其他的命令都会执行,不会回滚。
3、redis事务锁
悲观锁:每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁
乐观锁:每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用 版本号等机制
10.5.4.WATCH key [key …]
在执行multi之前,先执行watch key1 [key2],可以监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断。
10.5.5.unwatch
取消 WATCH 命令对所有 key 的监视。
如果在执行 WATCH 命令之后,EXEC 命令或DISCARD 命令先被执行了的话,那么就不需要再执行UNWATCH 了。
redsi事务三特性
1、单独的隔离操作
事务中的所有命令,按顺序执行,不会被其他客户端的请求打断
2、队列中的命令没提交之前 不会执行
3、不保证原子性
组队阶段,命令要么全部正确,要么全部失败
执行阶段 ,失败的命令就执行失败,命令成功的执行,不会回滚。
七、redis持久化
Redis 提供了2个不同形式的持久化方式。
- RDB(Redis DataBase)
- AOF(Append Of File)
1、RDB
在指定的时间间隔内将内存中的数据集快照写入磁盘
2、流程
Redis会单独创建(fork)一个子进程来进行持久化,当数据有改动时,子线程会先将数据(写的命令)写入到 一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。
- 整个过程中,主进程是不进行任何IO操作的,这就确保了极高的性能
- 如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效。
- RDB的缺点是最后一次持久化后的数据可能丢失。
3、fock一个子进程
- Fork的作用是复制一个与当前进程所有数据一样的进程,作为执行持久化的子进程。
- 父进程和子进程用一个内存,当进程各段内容发送变化时,子线程才会将父进程的内容复制,并写入临时文件(写时复制技术)
4、实现
在redis.conf中配置文件名称,默认为dump.rdb
1、配置位置
rdb文件的保存路径,也可以修改。默认为Redis启动时命令行所在的目录下
dir “/myredis/”
2、触发RDB
1、配置
2、命令save VS bgsave
- save :save时只管保存,其它不管,会造成全部阻塞。手动保存。不建议。
- bgsave:Redis会在后台异步进行快照操作, 快照同时还可以响应客户端请求。
- 可以通过lastsave 命令获取最后一次成功执行快照的时间
3、flushall命令
执行flushall命令,也会产生dump.rdb文件,但里面是空的,无意义
4、stop-writes-on-bgsave-error
当Redis无法写入磁盘的话,直接关掉Redis的写操作。推荐yes.
5、rdbcompression 压缩文件
对于存储到磁盘中的快照,可以设置是否进行压缩存储。
如果是的话,redis会采用LZF算法进行压缩。
如果你不想消耗CPU来进行压缩的话,可以设置为关闭此功能。推荐yes.
6、rdbchecksum 检查完整性
在存储快照后,还可以让redis使用CRC64算法来进行数据校验,但是这样做会增加大约10%的性能消耗,
如果希望获取到最大的性能提升,可以关闭此功能,推荐yes.
3、rdb的备份
- 先根据 config get dir 找到 rdb文件目录,
- 将所有 *.rdb 文件 copy别的地方
4、rdb的恢复
- 关闭redis
- 把备份文件 copy到配置文件配置的 rdb目录下
- 启动redis 备份数据会加载
5、优劣势
优势:
- 适合大规模的数据恢复
- 对数据完整性和一致性要求不高更适合使用
- 节省磁盘空间
- 恢复速度快
劣势:
- 父线程复制一份数据给子线程,会造成 数据占用双倍的内存空间
- 虽然用了写时拷贝,但数据大时,还是会影响性能,fock子线程耗时
- 如果redis崩了,会丢失最后一次备份后的所有修改,数据丢失风险大
6、停止 rdb
动态停止RDB:redis-cli config set save “”#save后给空值,表示禁用保存策略
2、aof
以日志的形式记录每次的写操作指令,日志文件内容只许追加,不许改写
redis启动后,会将日志文件所有指令执行一次,完成数据的恢复
AOF默认不开启
可以在redis.conf中配置文件名称,默认为 appendonly.aof
AOF文件的保存路径,同RDB的路径一致。
1、aof的持久化流程
- 每次写命令会被追加到 aof缓存区
- 缓存区根据aof持久化策略(always、everysec、no)将操作异步写入到aof文件中
- aof文件大小超过 重写策略时,对aof文件重写,压缩aof文件容量
- redis重启时,执行aof文件内容,恢复数据。
2、持久化策略
- appendfsync always
始终同步,每次Redis的写入都会立刻记入日志;性能较差但数据完整性比较好 - appendfsync everysec
每秒同步,每秒记入日志一次,如果宕机,本秒的数据可能丢失。 - appendfsync no
redis不主动进行同步,把同步时机交给操作系统。
3、aof启动、修复、恢复
正常恢复
- 修改默认的appendonly no,改为yes
- 将有数据的aof文件复制一份保存到对应目录(查看目录:config get dir)
- 恢复:重启redis然后重新加载
异常恢复
- 修改默认的appendonly no,改为yes
- 如遇到AOF文件损坏,通过/usr/local/bin/redis-check-aof–fix appendonly.aof进行恢复
- 备份被写坏的AOF文件
- 恢复:重启redis,然后重新加载
优劣势
优点
- 备份数据 不易丢失,完整性高
- 可读的日志文本,可以恢复误操作的数据
劣势
- 比rdb更占磁盘
- 恢复备份速度慢
3、AOF和RDB同时开启,redis取AOF的数据
4、用哪个
- 如果对数据不敏感,可以选单独用RDB。不建议单独使用aof
- 只做缓存:如果你只希望你的数据在服务器运行的时候存在,可以不使用任何持久化方式.
- 同时开启两种持久化方式,其中aof做数据的恢复,他的完整度更好,rdb用于数据库的备份,以防 aof出现潜在的bug
5、性能建议
- 因为RDB文件只用作后备用途,建议只在Slave上持久化RDB文件,而且只要15分钟备份一次就够了,只保留save 900 1这条规则。
- 如果使用AOF,好处是在最恶劣情况下也只会丢失不超过两秒数据,启动脚本较简单只load自己的AOF文件就可以了。
代价,一是带来了持续的IO,二是AOF rewrite的最后将rewrite过程中产生的新数据写到新文件造成的阻塞几乎是不可避免的。
只要硬盘许可,应该尽量减少AOF rewrite的频率,AOF重写的基础大小默认值64M太小了,可以设到5G以上。
默认超过原大小100%大小时重写可以改到适当的数值。
八、主从复制
主机数据更新后根据配置和策略, 自动同步到备机的master/slaver机制,Master以写为主,Slave以读为主
1、实现主从复制
1、配置文件
1、创建 myredis文件夹
2、拷贝redis.conf文件到 myredis(写绝对路径)
注意redis.conf
- 开启daemonize yes
- Pid文件名字pidfile
- 指定端口port
- Log文件名字
- dump.rdb名字dbfilename
- Appendonly 关掉或者换名字
3、配置一主多从 【下面 三个配置文件】
4、从:配置文件要改的地方
slave-priority 10
设置从机的优先级,值越小,优先级越高,用于选举主机时使用。默认100
- 创建redis6379.conf
include /home/ls/myredis/redis.conf
pidfile /var/run/redis_6379.pid
port 6379
dbfilename dump6379.rdb
- 创建redis6380.conf
include /home/ls/myredis/redis.conf
pidfile /var/run/redis_6380.pid
port 6380
dbfilename dump6380.rdb
slave-priority 1
- 创建redis6381.conf
include /home/ls/myredis/redis.conf
pidfile /var/run/redis_63801.pid
port 6381
dbfilename dump6381.rdb
slave-priority 2
2、启动三个redis服务器,并查看运行状态
- 启动
- info replication 打印主从复制的相关信息
目前三个都是主
3、配从不配主
slaveof :成为某个实例的从服务器
在主机上写,在从机上可以读取数据
在6380和6381上执行: slaveof 127.0.0.1 6379 或者写进配置文件 永久生效
2、一主二仆
-
仆 挂掉,此时 主 set两个数据 仆 重启后 依旧可以获得主set 的数据
-
主 挂掉 仆依旧认主 标识 主 的状态 为 down,主 重启后 依旧是主
3、薪火相传
主 领导 仆A 仆A 领导仆 B。。
仆也可以是其他服务器的主, 这样主可以只把数据写给 仆A,由仆A再往下去写,减轻主的压力。
- 用 slaveof
- 中途变更转向:会清除之前的数据,重新建立拷贝最新的
- 风险是一旦某个slave宕机,后面的slave都没法备份
- 主机挂了,从机还是从机,无法写数据了
4、反客为主
用 slaveof no one 将从机变为主机
当一个master宕机后,后面的slave可以立刻升为master,其后面的slave不用做任何修改。
5、复制原理
- Slave(从)启动成功连接到master(主)后会发送一个sync(数据同步)命令
- Master接到命令启动后台的存盘进程,同时收集所有接收到的用于修改数据集命令, 在后台进程执行完毕之后,master将传送整个数据文件到slave,以完成一次完全同步
- 全量复制:而slave服务在接收到数据库文件数据后,将其存盘并加载到内存中。
- 增量复制:Master继续将新的所有收集到的修改命令依次传给slave,完成同步
九、哨兵模式
反客为主的自动版,能够后台监控主机是否故障,如果故障了根据投票数自动将从库转换为主库
1、实现
启动三台服务器,一主二仆模式 79带着80、81
1、自定义的/myredis目录下新建sentinel.conf文件,名字绝不能错
内容为:sentinel monitor mymaster 127.0.0.1 6379 1
其中mymaster为监控对象起的服务器名称, 1 为至少有多少个哨兵同意迁移的数量
2、启动哨兵
执行redis-sentinel sentinel.conf
3、当主机挂掉,从机选举中产生新的主机
(大概10秒左右可以看到哨兵窗口日志,切换了新的主机)
哪个从机会被选举为主机呢?根据优先级别:slave-priority
原主机重启后会变为从机。
哨兵窗口日志
2、复制延时
由于所有的写操作都是先在Master上操作,然后同步更新到Slave上,所以从Master同步到Slave机器有一定的延迟,当系统很繁忙的时候,延迟问题会更加严重,Slave机器数量的增加也会使这个问题更加严重。
3、主重启后
旧主挂掉,会在从服务器中选举一个新的主服务器,并向旧主的所有从服务器发起认新主的命令,当旧主重新上线后,会让其作为新主的从服务器
十、redis集群(看文档吧)
1、解决的问题
- 容量不够,redis如何进行扩容?
- 并发写操作, redis如何分摊?
2、什么是集群
Redis 集群实现了对Redis的水平扩容,即启动N个redis节点,将整个数据库分布存储在这N个节点中,每个节点存储总数据的1/N。
Redis 集群通过分区(partition)来提供一定程度的可用性(availability): 即使集群中有一部分节点失效或者无法进行通讯, 集群也可以继续处理命令请求。
十一、redis问题解决
1、缓存穿透:
key对应的数据在mysql数据库中和缓存不存在,每次针对这个key去查询,缓存中查不到,就会到 数据库查,大量的请求到mysql数据库中,使数据库崩溃。
解决:
将数据库返回的null数据,存入缓存中,设置过期时间,不超过5分钟
2、缓存击穿:某个key过期,但一直访问
key对应的数据存在,但缓存过期,大量并发请求过来,会到数据库中查询然后再保存到缓存中,大量的请求可能会使数据库mysql崩溃
解决
- (被访问前):在缓存中预先设置热门数据,加大过期时长
- (被访问中):现场监控哪些数据热门,实时调整key的过期时间
- 使用锁:
1、当缓存失效,结果返回为null,设置排它锁,set key_mutex 1 ex 180 nx
2、设置排它锁成功:查询数据库后,将结果加入缓存,并删除排它锁
3、设置排它锁失败:有别的线程在查数据,加入缓存,睡眠一段时间再次查询
3、缓存雪崩:大量key集中过期,造成数据库压力骤增
缓存击穿的 super-plus版
解决:
- 如果key过期,会触发通知另外的线程去更新key的缓存
- 将缓存的失效时间分散开,在原有的失效时间的基础上加个随机数
十二、分布式锁
由于分布式系统多线程、多进程并且分布在不同机器上,这将使原单机部署情况下的并发控制锁策略失效。
为了解决这个问题就需要一种跨JVM的互斥机制来控制共享资源的访问,这就是分布式锁要解决的问题!
- 多个客户端同时获取锁(setnx)
- 获取成功,执行业务逻辑{从db获取数据,放入缓存},执行完成释放锁(del)
- 其他客户端等待重试
代码实现
setIfAbsent:底层就是 setnx key value
setnx lock value 当 lock存在时,不会set成功 就没有获取到锁,当lock不存在时,set成功 获取到了锁,执行业务后,del 删除这个key
@GetMapping("testLock")
public void testLock() {
String uuid = UUID.randomUUID().toString();
// 1、获取锁
//2 定义一个锁:lua 脚本可以使用同一把锁,来实现删除!
String skuId = "25"; // 访问skuId 为25号的商品 100008348542
String locKey = "lock:" + skuId; // 锁住的是每个商品的数据
Boolean lock = redisTemplate.opsForValue().setIfAbsent(locKey, uuid,2, TimeUnit.SECONDS);
// 2、获取锁成功,查询 num 的值
if (lock) {
Object value = redisTemplate.opsForValue().get("num");
// 2.1、判断 num为空 return
if (StringUtils.isEmpty(value)) {
return;
}
// 2.2、有值 转为 int
int num = Integer.parseInt(value.toString());
// 2.3、把redis的num + 1
redisTemplate.opsForValue().set("num", ++num);
// 使用lua脚本释放锁
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
// 使用redis执行lua执行
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
redisScript.setScriptText(script);
// 设置一下返回值类型 为Long
// 因为删除判断的时候,返回的0,给其封装为数据类型。如果不封装那么默认返回String 类型,
// 那么返回字符串与0 会有发生错误。
redisScript.setResultType(Long.class);
// 第一个要是script 脚本 ,第二个需要判断的key,第三个就是key所对应的值。
redisTemplate.execute(redisScript, Arrays.asList(locKey), uuid);
}else {
//3获取锁失败、每隔0.1秒再获取
try {
Thread.sleep(100);
testLock();
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
代码解读:
1、设置过期时间 :防止业务失败,无法释放锁del
2、设置uuid:在执行释放锁之前del ,先判断 value是不是等于uuid,等于就是自己的锁,而不是其他服务器的锁
3、使用lua脚本:del删除操作缺少原子性,使用lua脚本保证原子性
如何解决 任务未执行完 但是锁过期的问题
思想: 定时去查看 锁是否存在,如果存在就给他续期,设置一个新的过期,不存在就停止续期
-
方式1、Redisson 看门狗机制, 只要客户端加锁成功,leaseTime = -1 就会启动一个 Watch Dog,默认加锁30S。
原理
Watch Dog 是一个后台的定时任务线程,会把获取锁的线程 放到一个map里,每隔10秒检查一下,如果客户端还持有锁,就会不断延长key的生存时间 -
方式2 :redis有监听机制,当key即将过期会发送一个事件通知客户端,客户端收到后,对key进行重新设定过期时间
不给锁过期后果
如果系统异常,导致锁一直被这个进程占有,无法释放
给锁设置过长的过期时间
系统异常,导致锁的释放延迟