Redis使用

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服务器都是主节点

主从复制的作用主要包括

  1. 数据冗余:主从复制实现了数据的热备份 是持久化之外的一种数据荣誉方式

  1. 数据故障:当主节点出现问题是 可以由从节点提供服务 实现快速的故障恢复 实际上是一种服务的冗余

  1. 负载均衡:在主从复制的基础上 配和读写分离 可以由主节点提供写服务 由从节点提供读服务,分担服务器负载 尤其是在写少读多的场景下 通过多个从节点分担读负载 可以大大提高redis服务器的并发量

  1. 高可用(集群)基石:除了上述作用以外 主从复制还是哨兵和集群能够实施的基础 因此说主从复制是redis高可用基础

一般来说 要将redis运用于工程项目中 只使用一台服务器是万万不能的(宕机,一主二从)原因如下

  1. 结构上 单个redis服务器会发生单点故障 并且一台服务器需要处理所有的请求负载 压力较大

  1. 容量上 单个redis服务器内存容量有限 就算一台redis内存容量为256G 也布恩那个将所有内存用作redis存储内存 一般来说 单台redis最大使用内存不超过20G

电商网站上的商品 一般都是一次上传

主从复制 读写分离! 80%的情况下都是进行读操作! 减缓服务器的压力 架构中经常使用! 一主二从!

只要在公司中 主从复制是必须要使用的 是为在真是的项目中不可能单机使用redis

环境配置

只配置从库 不用配置主库

info replication #查看当前库信息
#返回值
role:master #角色 master
connected_slaves:0 #没有从机

赋值三个conf配置文件 修改对应信息

  1. 端口

  1. pid名字

  1. log名字

  1. 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 分布式锁

课程来源 狂神说https://space.bilibili.com/95256449

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值