String 基本用法
#查看所有的key
keys *
#切换数据库
select 3
#清空当前数据库
flushdb
#清空所有数据库
flushall
#检查key是否存在 返回1/0
exists key
#设置key过期时间 单位秒
expire key 10
#查看key过期时间
ttl key
#查看key类型
type key
#追加key字段 如果没找到key 新建key == set操作
append key "123"
# +1操作 例如阅读量等
incr key
# -1操作
decr key
# +10 自定义加多少
incrby key 10
# -10 自定义减多少
decrby key 10
##################################
#截取操作 [0,3]
getrange key 03
#截取全部 == get key
getrange key 0-1
#替换操作 从第二个开始替换字符串
setrange key 2 xx
#set的时候 设置过期时间30s
setex key 30"hello"
#分布式锁 尝使用 如果不存在创建key(1) 如果存在创建失败(0)
setnx key "redis"
##################################
#同时set多个key
mset k1 v1 k2 v2 k3 v3
#同时get多个key
mget k1 k2 k3
# 如果不存在创建 原子性操作(一起成功/一起失败)
msetnx k1 v1 k4 v4
#对象 设置一个user:1 对象 值为json字符来保存一个对象
set user:1 {name:zhangsan,age:3}
#这里的key是一个巧妙的设计 user:{id}:{filed}
mset user:1:name zhangsan user:1:age 3
mget user:1:name user:1:age
####################
#getset 先get在set 如果不存在 返回null 如果存在 获取存在的值 并设置新的值
getset key aaa #输出null
get key #输出aaa
getset key bbb #输出aaa
get key #输出bbb
List
设置规则 可当队列 栈 阻塞队列使用等
所有list命令 “ L ”开头
#LPUSH 将一个值插入到列表头部(左)value可重复
LPUSH list one
LPUSH list two
LPUSH list three
#range操作 0,-1 == get
LRANGE list 0-1#返回 three two one 栈顺序
#RPUSH 将一个值插入到列表尾部(右)
RPUSH list one
#############################
#移除操作
LPOP list #移除第一个 返回被移除的value
RPOP list #移除最后一个 返回被移除的value
#############################
#根据index获取list值
lindex list 1# 返回对应值
#############################
#list长度
Llen list #返回列表长度
############
#移除指定值 移除1个 值为one的
lrem list 1 one
#移除指定值 移除3个 值为one的
lrem list 3 one
############################
#trim修剪 截取list 只保留[1,3]的value
ltrim list 13
############################
# rpoplpush 移除list最后一个元素 添加到新list内
rpoplpush list otherList
#查看原list
range otherList 0-1
#查看新list
lrange otherList 0-1
############################
exists list
#将list 指定下标的值 替换为另外一个值 更新操作
#将第1个 替换为other 如不存在 报错 必须存在才可用!!!
lset list 0"other"
############################
#插入 指定已有字段value前 插入other
linsert list before "已有字段value""other"
#插入 指定已有字段value后 插入other
linsert list after "已有字段value""other"
list实际是一个链表 可以做队列和栈
Set
所有Set命令 “ S ”开头
值不可重复 无序不重复
#添加
sadd myset "hello"
sadd myset "hello1"
sadd myset "hello2"
sadd myset "hello3"
...
#查看
smembers myset
#判断 myset里存在hello? 0/1
sismember myset hello
#set集合中的个数
scard myset
#移除set中的指定元素
srem myset hello
#随机抽选一个元素
srandmember myset
#随机抽选指定个数元素
srandmember myset 2
#移除随机的key
spop myset
#移除指定的key
#移动hello 从myset移动到myset2
smove myset myset2 "hello"
###
#并集 共同关注
#数字集合类
# - 差集 sdiff
sdiff key1 key2
# - 交集 sinter
sinter key1 key2
# - 并集 sunion
sunion key1 key2
使用场景:
微博 A用户所有关注的人放进一个set内 所有粉丝放进一个set内
共同关注 共同好友 二度好友 推荐好友等
Hash
Map集合 key-map! 本质和string没有太大区别 基于K-V
set myhasj field hello
#添加
hset myhash field1 hello
#获取一个字段值
hget myhash field1
#同时添加多个
hmset myhash field1 hello1 field2 world
#同时获取多个值
hmget myhash field1 field2
#获取全部值
hgetall myhash
#删除hash指定的key字段
hdel myhash field1
#####################
#获取长度
hlen myhash
#判断hash中的key存在
hexists myhash field1
#####################
#只获得key
hkeys myhash
#只获得value
hvalue myhash
#####################
# +1
incr myhash field3 1
# -1
decr myhash field3 1
#如果不存在 则添加成功 0/1
hsetnx myhash field4 "aa"
Zset
在set基础上 增加一个值 set k1 v1 zset k1 score1 v1
#添加一个值 one薪资1块钱
zadd myzset 1 one
#添加多个值two薪资2块钱 three薪资3块钱
zadd myzset 2 two 3 three
#获取 range用法 0,-1 等于get
zrange myzset 0-1
#排序获取 -inf +inf 负无穷-正无穷 从小到大
zrangebyscore myzset -inf+inf
#查看范围 从大到小
zrevrange myzset 0-1
#排序获取 -inf +inf 负无穷-正无穷
zrangebyscore myzset -inf+inf withscores #返回带排序字段附带score(薪水)
#排序获取 -inf +inf 负无穷 到 score(薪水)为2 的
zrangebyscore myzset -inf2 withscores
#移除 myzset里值为one的
zrem myzset one
#查看myzset的元素个数
zcard myzset
#获取指定区间的成员数量
zcount myzset 13
Geospatial
#geoadd 添加经纬度 key 值(纬度 经度 城市名)
geoadd china:city 116.40 39.90 beijing
geoadd china:city 121.47 31.23 shanghai
geoadd china:city 106.50 29.53 chongqing
...
#获取指定城市的信息
geopos china:city beijing
#获取多个城市的信息
geopos china:city beijing chongqing
#两人之间的距离 单位km 默认m 可选m km mi(英里) ft(英尺)
GEODIST china:city beijing shanghai km
#半径范围 以给定的经纬度为中心 查询 方圆1000 km的城市
georadius china:city 110301000 km
#widthdist 显示到中心距离的位置
georadius china:city 110301000 km widthdist
#widthdist 显示他人的经纬度
georadius china:city 110301000 km widthcoord
#count n 查询n个 筛选出结果
georadius china:city 110301000 km count 2
################################
# 查询指定位置元素周围的其他元素
georadiusbymember china:city beijing 1000 km
# 了解即可 返回11位的Geohash字符串
geohash china:city beijing chongqing
底层实现原理是zset 可以用zset操作geo
#查看地图中全部元素
zrange china:city 0-1
#移除地图中元素
zrem china:city beijing
zrange china:city 0-1
hyperloglog
基数 不重复的元素
优点 占用的内存是固定的,2^64 不同元素的技术,只需要提供12kb内存 从内存角度 他是首选
网页的uv(一个人访问多次 还是算作一次)
传统方式是用set保存用户id 然后统计set元素个数作为标准判断
这个方式如果保存大量的用户id 比较麻烦!我们的目的是计数 不是保存用户id
0.81%错误率!统计uv任务,可以忽略不计的!
#添加元素
pfadd mykey a b c d e f g h i j
#查看mykey 不重复的个数
pfcount mykey
pfadd mykey2 i j m n p o l
pfcount mykey2
#将两个合并去掉重复 mykey3 = mykey+mykey2
pfmerge mykey3 mykey mykey2
pfcount mykey3
如果允许容错 hyperloglog最优选择
不允许容错 使用set
bitmap
位存储
统计疫情感染人数:0 0 0 0 1 1 0 1 0....
统计用户信息,活跃 不活跃 /登录 不登录 / 打卡 不打卡等 两个状态的 都可以使用bitmaps
bitmaps 位图 ,数据结构!都是操作2进制 只有0 1
365天 = 365bit 1字节 = 8 bit 46字节存一年信息
记录周一到周日打卡
#周一:0 ;周二:1 ;周三:2 ;周四:3...
setbit sign 01
setbit sign 11
setbit sign 20
setbit sign 31
setbit sign 40
setbit sign 51
setbit sign 61
#查看某一天是否打卡
#周四是否打卡
getbit sign 3#返回 0 1
#统计一周的打卡记录
bitcount sign #返回为1 的个数
事务
要么同时成功 要么同时失败 原子性
redis事务本质: 一组命令的集合!一个事务中所有命令都会被序列化,在事务执行过程中,会按照顺序执行!
一次性、顺序性、排他性! 执行一系列的命令
-------队列 setsetset 执行----------
redis事务没有隔离级别的概念
所有的命令在事务中 并没有直接执行! 只有发起执行命令的时候才会执行!exec
redis 单挑命令保证原子性 但是事务不保证原子性!
redis的事务:
开始事务(multi)
命令入队(...)
执行事务(exec)
正常执行事务!
# 开启事务
multi
#命令入队
set k1 v1
set k2 v2
get k2
set k3 v3
# 执行事务
exec
放弃事务
# 开启事务
multi
#命令入队
set k1 v1
set k2 v2
set k4 v4
# 取消事务 事务队列里所有命令都不会执行
discard
编译型异常(代码有问题! 命令有错),事务所有命令都不会执行
# 开启事务
multi
#命令入队
set k1 v1
set k2 v2
getset k4 #报错点
# 执行事务
exec
运行时异常(1/0) ,如果事务队列存在语法型错误,那么执行命令的时候,其他命令可以正常执行 错误命令抛出异常
set k1 "v1"
# 开启事务
multi
#命令入队
incr k1 #字符串不能执行+1操作 异常点
set k2 v2
set k3 v3
# 执行事务
exec
监控! watch
悲观锁
无论做什么都加锁
乐观锁
更新数据的时候去判断一下 在此期间是否有人修改过这个数据,mysql中 用version字段判断
获取version
执行操作
更新的时候比较version
测试watch
正常执行成功!
set money 100
set out 0
# 监视money
watch money
# 事务正常结束 期间没有发生变动 这个时候正常执行成功
multi
decrby money 20
incrby out 20
exec
测试多线程 改值 使用watch 当redis乐观锁
# 监视money
watch
# 事务
multi
decrby money 20
incrby out 20
exec # 执行之前 另外一个线程修改了我们的值 这时候就会导致事务执行失败
# 事务执行失败 放弃监视
unwatch
# 重新监视 获取最新的值
watch money
#
...
Jedis
使用java操作redis
什么是Jedis 是官方推荐java连接开发工具 使用java操作redis中间件
Springboot整合
springboot操作数据:spring -data redis mongodb jpa jdbc
springboot2.0 之后原来的jedis 替换为lettuce
jedis :采用的直连 多线程操作的话 不安全 如果想避免不安全 使用jedis pool连接池 更像BIO模式
lettuce:采用netty 实例可以在多线程中进行共享 安全,减少线程数据,更像NIO模式
// 默认的 redistemplate 没有过多的设置 redis对象需要序列化
// 两个泛型都是object 需要强转
整合测试
spring:
redis:
host:127.0.0.1
port:6379
lettuce:
pool:
max-active: 20
max-wait: -1
#最大阻塞等待时间(负数表示没限制)
max-idle: 5
min-idle: 0
@Autowired
RedisTemplateredisTemplate
// opsForValue() 操作string
// opsForList() 操作list
// opsForSet() opsForGeo() ...
// redisTemplate.opsForValue().方法
// 事务和基本的操作 直接操作
config 配置文件 配自己的redisTemplate
@EnableCaching
@Configuration
publicclassRedisConfigextendsCachingConfigurerSupport {
/**
* 固定模板
* @param redisConnectionFactory
* @return
*/
@Bean
publicRedisTemplate<String, Object>myRedisTemplate(RedisConnectionFactoryredisConnectionFactory) {
RedisTemplate<String,Object>redisTemplate=newRedisTemplate<>();
// 设置链接工厂
redisTemplate.setConnectionFactory(redisConnectionFactory);
// 序列化配置
Jackson2JsonRedisSerializerjackson2JsonRedisSerializer=newJackson2JsonRedisSerializer(Object.class);
ObjectMapperom =newObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
// string 序列化
StringRedisSerializerstringRedisSerializer=newStringRedisSerializer();
redisTemplate.setKeySerializer(stringRedisSerializer);
redisTemplate.setHashKeySerializer(stringRedisSerializer);
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.afterPropertiesSet();
returnredisTemplate;
}
@Bean
publicCacheManagercacheManager(RedisConnectionFactoryfactory) {
RedisSerializer<String>redisSerializer=newStringRedisSerializer();
Jackson2JsonRedisSerializerjackson2JsonRedisSerializer=newJackson2JsonRedisSerializer(Object.class);
//解决查询缓存转换异常的问题
ObjectMapperom=newObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
// 配置序列化(解决乱码的问题),过期时间600秒
RedisCacheConfigurationconfig=RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(600))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
.disableCachingNullValues();
RedisCacheManagercacheManager=RedisCacheManager.builder(factory)
.cacheDefaults(config)
.build();
returncacheManager;
}
}
redis工具类
/**
* Redis工具类,使用之前请确保RedisTemplate成功注入
*
* @author ye17186
* @version 2019/2/22 10:48
*/
publicclassRedisUtils {
privateRedisUtils() {
}
@SuppressWarnings("unchecked")
privatestaticRedisTemplate<String, Object>redisTemplate=SpringUtils
.getBean("redisTemplate", RedisTemplate.class);
/**
* 设置有效时间
*
* @param key Redis键
* @param timeout 超时时间
* @return true=设置成功;false=设置失败
*/
publicstaticbooleanexpire(finalStringkey, finallongtimeout) {
returnexpire(key, timeout, TimeUnit.SECONDS);
}
/**
* 设置有效时间
*
* @param key Redis键
* @param timeout 超时时间
* @param unit 时间单位
* @return true=设置成功;false=设置失败
*/
publicstaticbooleanexpire(finalStringkey, finallongtimeout, finalTimeUnitunit) {
Booleanret=redisTemplate.expire(key, timeout, unit);
returnret!=null&&ret;
}
/**
* 删除单个key
*
* @param key 键
* @return true=删除成功;false=删除失败
*/
publicstaticbooleandel(finalStringkey) {
Booleanret=redisTemplate.delete(key);
returnret!=null&&ret;
}
/**
* 删除多个key
*
* @param keys 键集合
* @return 成功删除的个数
*/
publicstaticlongdel(finalCollection<String>keys) {
Longret=redisTemplate.delete(keys);
returnret==null?0 : ret;
}
/**
* 存入普通对象
*
* @param key Redis键
* @param value 值
*/
publicstaticvoidset(finalStringkey, finalObjectvalue) {
redisTemplate.opsForValue().set(key, value, 1, TimeUnit.MINUTES);
}
// 存储普通对象操作
/**
* 存入普通对象
*
* @param key 键
* @param value 值
* @param timeout 有效期,单位秒
*/
publicstaticvoidset(finalStringkey, finalObjectvalue, finallongtimeout) {
redisTemplate.opsForValue().set(key, value, timeout, TimeUnit.SECONDS);
}
/**
* 获取普通对象
*
* @param key 键
* @return 对象
*/
publicstaticObjectget(finalStringkey) {
returnredisTemplate.opsForValue().get(key);
}
// 存储Hash操作
/**
* 往Hash中存入数据
*
* @param key Redis键
* @param hKey Hash键
* @param value 值
*/
publicstaticvoidhPut(finalStringkey, finalStringhKey, finalObjectvalue) {
redisTemplate.opsForHash().put(key, hKey, value);
}
/**
* 往Hash中存入多个数据
*
* @param key Redis键
* @param values Hash键值对
*/
publicstaticvoidhPutAll(finalStringkey, finalMap<String, Object>values) {
redisTemplate.opsForHash().putAll(key, values);
}
/**
* 获取Hash中的数据
*
* @param key Redis键
* @param hKey Hash键
* @return Hash中的对象
*/
publicstaticObjecthGet(finalStringkey, finalStringhKey) {
returnredisTemplate.opsForHash().get(key, hKey);
}
/**
* 获取多个Hash中的数据
*
* @param key Redis键
* @param hKeys Hash键集合
* @return Hash对象集合
*/
publicstaticList<Object>hMultiGet(finalStringkey, finalCollection<Object>hKeys) {
returnredisTemplate.opsForHash().multiGet(key, hKeys);
}
// 存储Set相关操作
/**
* 往Set中存入数据
*
* @param key Redis键
* @param values 值
* @return 存入的个数
*/
publicstaticlongsSet(finalStringkey, finalObject... values) {
Longcount=redisTemplate.opsForSet().add(key, values);
returncount==null?0 : count;
}
/**
* 删除Set中的数据
*
* @param key Redis键
* @param values 值
* @return 移除的个数
*/
publicstaticlongsDel(finalStringkey, finalObject... values) {
Longcount=redisTemplate.opsForSet().remove(key, values);
returncount==null?0 : count;
}
// 存储List相关操作
/**
* 往List中存入数据
*
* @param key Redis键
* @param value 数据
* @return 存入的个数
*/
publicstaticlonglPush(finalStringkey, finalObjectvalue) {
Longcount=redisTemplate.opsForList().rightPush(key, value);
returncount==null?0 : count;
}
/**
* 往List中存入多个数据
*
* @param key Redis键
* @param values 多个数据
* @return 存入的个数
*/
publicstaticlonglPushAll(finalStringkey, finalCollection<Object>values) {
Longcount=redisTemplate.opsForList().rightPushAll(key, values);
returncount==null?0 : count;
}
/**
* 往List中存入多个数据
*
* @param key Redis键
* @param values 多个数据
* @return 存入的个数
*/
publicstaticlonglPushAll(finalStringkey, finalObject... values) {
Longcount=redisTemplate.opsForList().rightPushAll(key, values);
returncount==null?0 : count;
}
/**
* 从List中获取begin到end之间的元素
*
* @param key Redis键
* @param start 开始位置
* @param end 结束位置(start=0,end=-1表示获取全部元素)
* @return List对象
*/
publicstaticList<Object>lGet(finalStringkey, finalintstart, finalintend) {
returnredisTemplate.opsForList().range(key, start, end);
}
}
redis.conf
config get requirepass
#设置密码
config set requirepass "123456"
append only 模式 aof配置
appendonly no #默认不开启aof模式 使用rdb方式持久化
appendfilename "appendonly.aop"#持久化文件名字
#leverysec always(每次写入都同步,消耗性能) no(不执行sync 系统自己同步)
appendfsync leverysec #每秒执行一次同步 可能会丢失这一秒的数据
rdb
rdb保存的文件是dump.rdb
主从复制中 rdb就是备用的! 从机上面!
#conf文件
#60s内 修改5次key -> 生成快照
save 60 5
# 文件名
dbfilename dump.rdb
#删除rdb文件
rm-rf dump.rdb
触发机制
1 save的规则满足 触发rdb规则
2 执行flushall 也会触发rdb规则
3 退出redis 也会产生rdb文件!
备份就会生成一个dump.rdb
如何恢复rdb文件
只需要将rdb文件放在redis启动目录就可以,redis启动的时候回自动检查dump.rdb 恢复数据
2 查看需要存放的位置
config get dir #返回目录
".../bin" 如果这个目录下存在rdb文件 启动自动恢复
几乎 默认配置就够用了 但是还要做了解
优点:
适合大规模的数据回复
对数据完整性不高
缺点:
需要一定的时间间隔,如果意外宕机 最后一次修改的数据就没了
fork进程的时候 会占用内存空间!!
aof(append only file)
将所有命令记录下来 history,恢复的时候执行一遍
appendonly yes#开启 aof模式
appendfilename "appenfonly.aof"
手动配置aof开启
重启redis就可以生效
如果aof配置文件有错误 这时候redis不能启动 需要修复aof配置文件
redis提供了一个修复工具 redis-check-aof --fix appendonly.aof
如果文件正常 重启直接恢复
重写规则 如果aof大于64m ,太大了 ,fork一个新的进程 来讲我们的文件重写
aof默认无限追加
优缺点
优点:
1 每一次修改都同步 文件的完整更好
2 每秒同步一次 可能回丢失一秒的数据
3 从不同步 效率最高
缺点:
1 相对于数据文件来说 aof远远大于rdb 修复速度比rdb慢
2 aof 运行效率 也要比rdb慢 所以我们redis默认是rdb!
redis发布订阅
redis发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。微信 微博 关注系统
redis 客户端可以订阅任意数量的频道
订阅发布消息图:
发送者
频道
订阅者
展示了频道channel1 以及订阅这个频道的三个客户端client2 client5 client1之间的关系
当有消息通过publish 命令发送给频道 channel1 时 这个消息就会呗发送给订阅他的三个客户端
测试
订阅端
#订阅频道test 实时监听
subscribe test
...
#等待推送
1) "message"#消息
2) "test"#频道名
3) "hello"#内容
发送端
#发布者发送消息 "hello" 到test频道
publish test "hello"
Redis主从复制
主从复制 是将一台redis服务器的数据 复制到其他的redis服务器 前者称为主节点master 后者成为从节点slave 数据复制是单向的 只能由主节点到从节点 ,master以写为主 slave以读为主.
默认情况下 每台reids服务器都是主节点
主从复制的作用主要包括
数据冗余:主从复制实现了数据的热备份 是持久化之外的一种数据荣誉方式
数据故障:当主节点出现问题是 可以由从节点提供服务 实现快速的故障恢复 实际上是一种服务的冗余
负载均衡:在主从复制的基础上 配和读写分离 可以由主节点提供写服务 由从节点提供读服务,分担服务器负载 尤其是在写少读多的场景下 通过多个从节点分担读负载 可以大大提高redis服务器的并发量
高可用(集群)基石:除了上述作用以外 主从复制还是哨兵和集群能够实施的基础 因此说主从复制是redis高可用基础
一般来说 要将redis运用于工程项目中 只使用一台服务器是万万不能的(宕机,一主二从)原因如下
结构上 单个redis服务器会发生单点故障 并且一台服务器需要处理所有的请求负载 压力较大
容量上 单个redis服务器内存容量有限 就算一台redis内存容量为256G 也布恩那个将所有内存用作redis存储内存 一般来说 单台redis最大使用内存不超过20G
电商网站上的商品 一般都是一次上传
主从复制 读写分离! 80%的情况下都是进行读操作! 减缓服务器的压力 架构中经常使用! 一主二从!
只要在公司中 主从复制是必须要使用的 是为在真是的项目中不可能单机使用redis
环境配置
只配置从库 不用配置主库
info replication #查看当前库信息
#返回值
role:master #角色 master
connected_slaves:0 #没有从机
赋值三个conf配置文件 修改对应信息
端口
pid名字
log名字
rdb备份文件名字
修改完毕之后 启动三个redis服务
一主二从
默认情况 每台都是主节点 一般情况 配置从机就可以了
a机器 主机
#连接客户端
redis-cli -p6379
#查看
info replication
b机器 从机
#连接客户端
redis-cli -p6380
# 配置主机
slaveof 127.0.0.1 6379
#查看
info replication
role:slave #当前角色 master
master_host:127.0.0.1 #
master_host:6379 #
c机器 从机
#连接客户端
redis-cli -p6381
# 配置主机
slaveof 127.0.0.1 6379
真实的配置 是配置文件修改 配置文件是暂时的
#============================= replication=================
replicaof <masterip><masterport>
masterauth <master-password>
细节
主机可以写 从机只能读 主机中所有数据都会被从机保存
主机操作
set k1 v1
从机
get k1
set k2 v2 #报错 从机不可写
测试:
主机断开链接 从机依然链接到主机 但是没有写操作 这个时候 主机回来了 从机依旧可以读取 主机写的操作
如果使用命令行配置主从 ·如果重启 变回主机,只要配置为从机 立马就会从主机获取值
复制原理
全量复制:slave 服务在接受到数据库文件数据后 将其存盘并加载到内存中
增量复制:master 继续将新的所有手机的修改命令依次传给slave 完成同步
只要重新链接master 一次性完全同步(全量复制)将自动执行!数据在从机中一定可以看到
手动变成主机
如果主机断开 使用slave no one 手动变成主机
哨兵模式(自动选举)
(自动选取)
概述
主从切换技术的方法是 当主机服务器宕机 需要手动把一台服务器切换为主服务器 这就需要人工干预 费事费力 话会造成一段时间内服务不可用,这不是一种推荐的方式,更多时候 我们有限考虑哨兵模式 redis2.8开始 提供了哨兵架构
监控主机是否故障 如果故障了 根据投票数自动将从库切换为主库
哨兵通过发消息确定是否活着
假设主服务器宕机,哨兵1先检测到这个结果,系统并不会马上进行failover过程,仅仅是哨兵1主观的认为主服务器不可用,这个现象成为主观下线。当后面的哨兵也检测到主服务器不可用,并且数量达到一定值时,那么哨兵之间就会进行一次投票,投票的结果由一个哨兵发起,进行failover[故障转移]操作。切换成功后,就会通过发布订阅模式,让各个哨兵把自己监控的从服务器实现切换主机,这个过程称为客观下线。
测试
我们目前一主二从
1 配置哨兵文件 sentinel.conf
vim sentinel
# sentinel monitor 被检控的名称 host port 主机挂了 slave投票 看让谁接替成为主机 票多者得
sentinel monitor myredis 127.0.0.1 63791
2 启动
redis-sentinel config/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 配置多少个sentinel哨兵统一认为master主节点失联 那么这时客观上认为主节点失联了
# sentinel monitor <master-name> <ip> <redis-port> <quorum>
sentinel monitor mymaster 127.0.0.1 63792
# 当在Redis实例中开启了requirepass foobared 授权密码 这样所有连接Redis实例的客户端都要提供密码
# 设置哨兵sentinel 连接主从的密码 注意必须为主从设置一样的验证密码
# sentinel auth-pass <master-name> <password>
sentinel auth-pass mymaster MySUPER--secret-0123passw0rd
# 指定多少毫秒之后 主节点没有应答哨兵sentinel 此时 哨兵主观上认为主节点下线 默认30秒
# sentinel down-after-milliseconds <master-name> <milliseconds>
sentinel down-after-milliseconds mymaster 30000
# 这个配置项指定了在发生failover主备切换时最多可以有多少个slave同时对新的master进行 同步,这个数字越小,完成failover所需的时间就越长,但是如果这个数字越大,就意味着越 多的slave因为replication而不可用。可以通过将这个值设为 1 来保证每次只有一个slave 处于不能处理命令请求的状态。
# sentinel parallel-syncs <master-name> <numslaves>
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 <master-name> <milliseconds>
sentinel failover-timeout mymaster 180000
# SCRIPTS EXECUTION
#配置当某一事件发生时所需要执行的脚本,可以通过脚本来通知管理员,例如当系统运行不正常时发邮件通知相关人员。
#对于脚本的运行结果有以下规则:
#若脚本执行后返回1,那么该脚本稍后将会被再次执行,重复次数目前默认为10
#若脚本执行后返回2,或者比2更高的一个返回值,脚本将不会重复执行。
#如果脚本在执行过程中由于收到系统中断信号被终止了,则同返回值为1时的行为相同。
#一个脚本的最大执行时间为60s,如果超过这个时间,脚本将会被一个SIGKILL信号终止,之后重新执行。
#通知型脚本:当sentinel有任何警告级别的事件发生时(比如说redis实例的主观失效和客观失效等等),将会去调用这个脚本,这时这个脚本应该通过邮件,SMS等方式去通知系统管理员关于系统不正常运行的信息。调用该脚本时,将传给脚本两个参数,一个是事件的类型,一个是事件的描述。如果sentinel.conf配置文件中配置了这个脚本路径,那么必须保证这个脚本存在于这个路径,并且是可执行的,否则sentinel无法正常启动成功。
#通知脚本
# shell编程
# sentinel notification-script <master-name> <script-path>
sentinel notification-script mymaster /var/redis/notify.sh
# 客户端重新配置主节点参数脚本
# 当一个master由于failover而发生改变时,这个脚本将会被调用,通知相关的客户端关于master地址已经发生改变的信息。
# 以下参数将会在调用脚本时传给脚本:
# <master-name> <role> <state> <from-ip> <from-port> <to-ip> <to-port>
# 目前<state>总是“failover”,
# <role>是“leader”或者“observer”中的一个。
# 参数 from-ip, from-port, to-ip, to-port是用来和旧的master和新的master(即旧的slave)通信的
# 这个脚本应该是通用的,能被多次调用,不是针对性的。
# sentinel client-reconfig-script <master-name> <script-path>
sentinel client-reconfig-script mymaster /var/redis/reconfig.sh # 一般都是由运维来配置!
缓存穿透和雪崩
我是在这位大佬的文章里了解的 https://blog.csdn.net/qq_39781497/article/details/126268506
穿透
bloomFilter
击穿
setnx 分布式锁