redis简单入门

redis入门

    • 安装
      • 性能测试(redis-benchmark)
      • 基础
    • 数据类型
      • String
      • List
      • Set
      • Hash
      • Zset(有序集合)
      • geospatial(地理空间)
      • Hyperloglog
      • Bitmap(位存储)
    • 事务
      • 锁(监控)
    • jeids
    • SpringBoot整合redis
      • 使用
      • 部分源码
      • 编写自己的 redisTemplate
    • Redis持久化
      • RDB(Redis DateBase)
      • AOF(Append Only File)
    • Redis发布订阅
    • Resis主从复制
      • 模拟主从复制
      • 哨兵模式
    • 缓存穿透
    • 缓存击穿
    • 缓存雪崩

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)。

安装

官网下载:https://redis.io/download/#redis

redis-7.0.0.tar.gz

传输到Linux上(xftp),解压

tar -zxf redis-7.0.0.tar.gz

切换目录,安装

mv /home/pi/Download/redis-7.0.0 /usr/local/redis
cd /usr/local/redis/
sudo make    // 编译
sudo make install   // 安装

redis的配置文件(解压之后的文件)
请添加图片描述

树莓派apt-get安装(默认安装路经:usr/bin)

sudo apt-get install redis-server    // 安装
sudo vim /etc/redis/redis.conf     // 更改配置文件信息

// 找到 bind 127.0.0.1 ::1     将其注掉(让除了本机外的其他机器访问)
// requirepass 密码     在配置文件中添加这一行

sudo /etc/init.d/redis-server restart     // 重启
    
/etc/init.d/redis-server stop
/etc/init.d/redis-server start
/etc/init.d/redis-server restart

redis简单入门_第1张图片

测试使用

redis-server     // 启动服务
sudo redis-server ./redis.conf    // 指定配置文件启动
ps -aux | grep redis      // 查看进程

redis-cli     // 连接redis
auth 密码      // 输入密码(若没有设置密码,可以省略)
set name feng     // 存入键值对
get name      // 获取键对应的值
keys *      // 查询所有key
shutdown     // 关闭服务
    
config set requirepass        // 可以这样更改配置文件(设置密码)
config get requirepass        // 可以这样更改配置文件(查看密码)
info replication      // 查看当前库的信息

性能测试(redis-benchmark)

redis简单入门_第2张图片

测试

redis-benchmark -c 200 -n 100000 -d 4

redis简单入门_第3张图片

基础

中文官网命令查询:http://redis.cn/commands.html

dbsize     // 查看当前数据库的大小
select 3      // 切换第四个数据库 (databases 16)默认16个数据库
flushdb    // 清空当前数据库
flushall      // 清空所有数据库

set name feng     // 存入键值对,存在则覆盖
get name      // 获取键对应的值
keys *      // 查询所有key

exists name    // 判断是否key存在,(1:存在,0:不存在)
move name 4     // 将当前数据库下的name键值对移动到4号数据库(1:成功,0:失败)

expire name 10     // 将指定的键值对设置为10秒过期
ttl name       // 查看对应键值对的过期时间(正数:时间,-1:永久,-2:过期)
type name     // 查看当前key的类型

数据类型

String

运用场景:

  • 计数器
  • 统计数量(粉丝数,浏览量等等)
  • 对象的缓存存储
append name "bb"    // 追加(如果当前key不存在,相当于set name)
strlen name         // 判断字符串的长度
incr views        // 自增一
decr views       // 减一
incrby views 10      // 增加10
decrby views 10      // 减少10

getrange name 2 5     // 切分(左闭右闭[])(0 -1  取全部)
setrange name 1 ff    // 替换指定位置开始的字符串
setex key 30 "feng"            // (set with expire)    设置过期时间
setnx key "feng"            // (set if not exist)    不存在则创建,存在则不变
    
mset k1 v1 k2 v2 k3 v3    // 设置多个键值对
mget k1 k2 k3
msetnx k1 v1 k2 v2 k3 v3    // 原子性操作(一起成功,一起失败)
    
// 存对象
set user:1 {name:feng,age:18}      // 使用json保存对象,
get user:1
mset user:2:name feng user:2:age 18        // 存对象
mget user:2:name user:2:age

getset name han        // 如果不存在,返回nil(null),存在,返回之前的值并设置新的值

List

本质:链表,可以用于消息队列,栈

lpush list1 v1     // 往list1列表中从左边添加v1
rpush list1 v2      // 从右边添加v2
lrange list1 0 -1       // 查看list内容(遍历)
lpop list1        // 移除,返回移除的值(左右都可以)

lindex list1 0    // 索引取值
llen list1      // 长度
lrem list1 0 feng     // 移除全部feng
lrem list1 2 feng     // 移除两个feng
ltrim list 1 3        // 截取([])

rpoplpush list list1    // 移除一个元素,移动到新的列表中
lset list 1 feng      // 指定下标的值替换成feng(不存在list,则报错)
linsert list before hello2 hello3     // 将hello3插入到hello2的前面(after:后面)

Set

无序,不可重复,可用于记录用户关注之间的信息等

sadd set1 hello1      // 往集合set1中添加hello1元素
smembers set1       // 查看集合set1中的元素
sismember set1 hello1      // 查看hello1是否存在set1集合中
scard set1        // 元素个数
srem set1 hello1      // 移除set1集合中的hello1元素

srandmember set1      // 随机获取set1集合中的一个数据
srandmember set1 2      // 随机获取set1集合中的两个数据
spop set1        // 随机删除一个元素
smove set1 set2 hello1     // 将set1中的hello1移动到set2集合中

sadd set1 a b c d
sadd set2 b c d e
sdiff set1 set2      // 差集
sinter set1 set2      // 交集
sunion set1 set2      // 并集

Hash

Map集合:key-map(map:key-value)

更适合对象的存储(用户信息,经常变动的数据)

hset myhash1 hash1 value1         // 设置一个具体的key-value
hget myhash1 hash1           // 获取
hmset myhash1 hash2 value2 hash3 value3      // 设置多个 
hmget myhash1 hash1 hash2 hash3       // 获取多个
hgetall myhash1           // 获取全部
hdel myhash1 hash1       // 删除其中的一个key-value

hlen myhash1      // 获取长度
hexists myhash1 hash2     // 判断myhash1 中是否存在key hash2
hkeys myhash1       // 获取所有的key
hvals myhash1       // 获取所有的value
incr    decr     hsetnx

Zset(有序集合)

本质:set(在set的基础上,增加了排序功能)

运用:排序(成绩表,工资表,权重,排行等)

zadd zset1 1 a 2 b 3 c       // 添加
zrange zset1 0 -1         // 查看
zrevrange zset1 0 -1      // 反向查看

zadd salary 2500 feng 5000 han 500 hang
zrangebyscore salary -inf +inf withscores       // 排序(从小到大)有withscores: 同时显示salary数据
zrevrangebyscore salary +inf -inf     // 从大到小

zrem salary feng         // 移除salary中的feng
zcard salary        // 获取个数
zcount salary 600 3000    // 查询指定区间的个数

geospatial(地理空间)

地理空间:可以用于附近的人,找朋友,计算距离

geo的本质:底层使用的是Zset,可以用Zset的指令来操作geo

查询地理位置经纬度:http://www.jsons.cn/lngcode/

相关命令

  • GEOADD 添加坐标与城市名称
  • GEODIST 获取坐标
  • GEOHASH 将坐标值转为hash值
  • GEOPOS 获取距离
  • GEORADIUS 获取方圆多少距离中存在的城市
  • GEORADIUSBYMEMBER
127.0.0.1:6379> geoadd chian:city 116.40 39.90 beijing 121.47 31.23 shanghai    # 添加
(integer) 2
127.0.0.1:6379> geoadd chian:city 113.28 23.13 guangdong 114.08 22.55 shenzhen
(integer) 2
127.0.0.1:6379> geodist chian:city beijing shanghai     # 计算距离
"1067378.7564"
127.0.0.1:6379> geodist chian:city beijing shanghai km    # 加上单位
"1067.3788"
127.0.0.1:6379> geopos chian:city beijing    # 获取坐标
1) 1) "116.39999896287918091"
   2) "39.90000009167092543"
127.0.0.1:6379> georadius chian:city 116 39 3000 km    # 获取指定坐标,半径,存在其中的城市
1) "shenzhen"
2) "guangdong"
3) "shanghai"
4) "beijing"
127.0.0.1:6379> georadius chian:city 116 39 3000 km withcoord withdist      # 可以同时查看对方的坐标和距离
1) 1) "shenzhen"
   2) "1838.7189"
   3) 1) "114.08000081777572632"
      2) "22.5500010475923105"
2) 1) "guangdong"
   2) "1783.8295"
   3) 1) "113.27999979257583618"
      2) "23.13000101271457254"
3) 1) "shanghai"
   2) "996.7785"
   3) 1) "121.47000163793563843"
      2) "31.22999903975783553"
4) 1) "beijing"
   2) "105.8343"
   3) 1) "116.39999896287918091"
      2) "39.90000009167092543"
127.0.0.1:6379> georadiusbymember chian:city shanghai 2000 km    # 与georadius类似,指定城市与城市
1) "shenzhen"
2) "guangdong"
3) "shanghai"
4) "beijing"
127.0.0.1:6379> zrange chian:city 0 -1     # 可以通过怕zset的操作来操作geo
1) "shenzhen"
2) "guangdong"
3) "shanghai"
4) "beijing"

单位

  • m 表示单位为米。
  • km 表示单位为千米。
  • mi 表示单位为英里。
  • ft 表示单位为英尺。

Hyperloglog

基数统计的算法

基数:不重复的元素

运用:统计重复元素不计数的问题(访问量,点击量,播放量等)

127.0.0.1:6379> pfadd key1 a b c d e      # 添加
(integer) 1
127.0.0.1:6379> pfcount key1      # 计数
(integer) 5
127.0.0.1:6379> pfadd key2 d e f g   
(integer) 1
127.0.0.1:6379> pfmerge key3 key1 key2     # 并集
OK
127.0.0.1:6379> pfcount key3
(integer) 7

Bitmap(位存储)

位图,一种数据结构,操作二进制位来进行记录,只有0, 1两个状态

运用:统计只有两个状态的数据信息(打卡,登录,活跃等)

127.0.0.1:6379> setbit feng 0 0     # 设置打卡记录,(第一天没有打卡)
(integer) 0
127.0.0.1:6379> setbit feng 1 1
(integer) 0
127.0.0.1:6379> setbit feng 2 1
(integer) 0
127.0.0.1:6379> setbit feng 3 0
(integer) 0
127.0.0.1:6379> getbit feng 2       # 获取指定那天的打卡记录
(integer) 1
127.0.0.1:6379> getbit feng 0
(integer) 0
127.0.0.1:6379> bitcount feng        # 统计为一的个数(总的打卡数)
(integer) 2

事务

本质:一组命令的集合,一个事务中,所有的事务都会被序列化,执行的过程中,安照顺序执行

一次性,顺序性,排他性(执行一系列命令)

redis的单条命令保证原子性,事务不保证原子性

所有的命令在事务中,并没有直接执行,而是先将命令放在一个队列中,当发起执行命令时,才会执行

  • 开启事务(multi)
  • 加入命令(get set …)
  • 执行事务(exec)
(integer) 2
127.0.0.1:6379> multi    # 开启事务
OK
127.0.0.1:6379> set k1 v1    # 一些指令
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> get k2
QUEUED
127.0.0.1:6379> exec     # 执行事务
1) OK
2) OK
3) "v2"

discard    # 取消事务,之前添加到事务中的命令都不会执行

编译异常(指令有误):事务中的指令都不会执行

127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> gets k1    # 错误指令
(error) ERR unknown command `gets`, with args beginning with: `k1`, 
127.0.0.1:6379> get k1
QUEUED
127.0.0.1:6379> exec
(error) EXECABORT Transaction discarded because of previous errors.

运行时异常:事务中正常的指令正常执行,错误的指令抛出异常

127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 feng
QUEUED
127.0.0.1:6379> incrby k1 10
QUEUED
127.0.0.1:6379> get k1
QUEUED
127.0.0.1:6379> exec
1) OK             # 其他正常执行
2) (error) ERR value is not an integer or out of range       # 运行时出错的指令抛出异常
3) "feng"

锁(监控)

redis的监控:watch (乐观锁)

  • 悲观锁:认为什么时候都会出问题,无论什么时候都加上锁
  • 乐观锁:认为什么时候都不会出问题,不加锁,但操作数据时,判断一下该数据有没有被改动过来判断是否执行操作
127.0.0.1:6379> set money 100       # 模拟存钱取钱
OK
127.0.0.1:6379> set out 0
OK 
127.0.0.1:6379> watch money       # 给money一个监控(上锁)
OK
127.0.0.1:6379> multi      # 通常一起使用,开启事务
OK
127.0.0.1:6379> decrby money 10       # 存取钱操作
QUEUED
127.0.0.1:6379> incrby out 10
QUEUED
127.0.0.1:6379> exec       # 执行事务,会直动解除watch监控的数据(自动解锁)
1) (integer) 90
2) (integer) 10

使用多个客户端模拟多个线程请求

127.0.0.1:6379> watch money       # 开启监控
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> decrby money 20
QUEUED
127.0.0.1:6379> incrby out 20
QUEUED
127.0.0.1:6379> exec      # 另一客服端更改了数据,当前事务中的指令都不会执行
(nil)
127.0.0.1:6379> watch money     # 重新监控,再次操作,就可以成功了
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> decr money
QUEUED
127.0.0.1:6379> incr out
QUEUED
127.0.0.1:6379> exec
1) (integer) 189
2) (integer) 11

jeids

与jdbc类似,用于java连接redis

导包


<dependency>
    <groupId>redis.clientsgroupId>
    <artifactId>jedisartifactId>
    <version>4.2.2version>
dependency>

使用

public class TestJedis {
    public static void main(String[] args) {
        // 创建jedis对象
        Jedis jedis = new Jedis("192.168.43.88", 6379);
        jedis.auth("1507298022");
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("hello", "world");
        jsonObject.put("name", "feng");
        String result = jsonObject.toJSONString();

        // 开启事务
        Transaction multi = jedis.multi();
        try {
            multi.set("user1", result);
            multi.set("user2", result);
            multi.exec();      // 提交事务
        } catch (Exception e) {
            multi.discard();       // 出错之后,放弃事务
            e.printStackTrace();
        } finally {
            System.out.println(jedis.get("user1"));
            System.out.println(jedis.get("user2"));
            jedis.close();      // 关闭连接
        }
    }
}

SpringBoot整合redis

springboot2.x之前,底层用的jedis,之后用的lettuce

jeids:采用直连,多个线程操作的情况下是不安全的,可以使用jedis pool连接池避免

lettuce:采用netty,实例可以在多个线程中共享,不存在线程不安全

使用

spring boot 整合的依赖包(启动器)

<dependency>
   <groupId>org.springframework.bootgroupId>
   <artifactId>spring-boot-starter-data-redisartifactId>
dependency>

application.properties 中的配置

spring.redis.host=192.168.43.88
spring.redis.port=6379
spring.redis.password=1507298022

测试

@Autowired
private RedisTemplate<Object, Object> redisTemplate;

@Test
void contextLoads() {
    // opsForValue():操作String类型的
    // opsForList(): List
    redisTemplate.opsForValue().set("name", "丰");
    // 常用的方法可以直接操作
    DataType name = redisTemplate.type("name");
    System.out.println(name);
    System.out.println(redisTemplate.opsForValue().get("name"));

    // 通过连接对象来操作
    //    RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
    //    connection.flushAll();
}

部分源码

// spring boot 所有的配置类都有一个自动配置类: RedisAutoConfiguration
// 自动配置类都会绑定一个properties 配置文件类:   RedisProperties

// RedisAutoConfiguration
@Configuration(proxyBeanMethods = false)      // 自动配置类的标记
@ConditionalOnClass(RedisOperations.class)   // 引用了RedisOperations中的包
@EnableConfigurationProperties(RedisProperties.class)      // 绑定配置文件类:RedisProperties
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {

   @Bean
    // 如果存在自己定义的 redisTemplate 则使用自己定义的 redisTemplate,没有就默认
   @ConditionalOnMissingBean(name = "redisTemplate")
   @ConditionalOnSingleCandidate(RedisConnectionFactory.class)
    // RedisConnectionFactory:接口 
    // 实现类:LettuceConnectionFactory(默认使用)
    // 实现类:JedisConnectionFactory(其中有许多类不存在,所以默认不生效)
   public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
       // 默认的RedisTemplate 没有做过的的配置,reids对象都需要序列化
       // 两个泛型都是Object,之后使用需要转换
      RedisTemplate<Object, Object> template = new RedisTemplate<>();
      template.setConnectionFactory(redisConnectionFactory);
      return template;
   }

   @Bean
   @ConditionalOnMissingBean
   @ConditionalOnSingleCandidate(RedisConnectionFactory.class)
    // StringRedisTemplate: String类型比较常用,单独提了一个出来 
   public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
      return new StringRedisTemplate(redisConnectionFactory);
   }
}


// 配置文件类
@ConfigurationProperties(prefix = "spring.redis")   // 可以用它配置以下属性
public class RedisProperties {
	private int database = 0;    // 使用哪个(几号)数据库
	private String url;    // redis://user:[email protected]:6379
	private String host = "localhost";     // ip
	private String username;    // redis 服务器的登录用户名。
	private String password;    // 密码
	private int port = 6379;   //端口号
	private boolean ssl;     // 是否启用 SSL 支持。
	private Duration timeout;
	private Duration connectTimeout;
	private String clientName;
	private ClientType clientType;
	private Sentinel sentinel;
	private Cluster cluster;
	private final Jedis jedis = new Jedis();      
	private final Lettuce lettuce = new Lettuce();
}

RedisTemplate.class 类中的部分代码

// 部分属性
@Nullable
private RedisSerializer<?> defaultSerializer;
@Nullable
private RedisSerializer keySerializer = null;      // 有序列化
@Nullable
private RedisSerializer valueSerializer = null;
@Nullable
private RedisSerializer hashKeySerializer = null;
@Nullable
private RedisSerializer hashValueSerializer = null;

// 给属性序列化的方法
public void afterPropertiesSet() {
    super.afterPropertiesSet();
    boolean defaultUsed = false;
    if (this.defaultSerializer == null) {
        // JdkSerializationRedisSerializer: 默认使用的是jdk序列化
        this.defaultSerializer = new JdkSerializationRedisSerializer(this.classLoader != null ? this.classLoader : this.getClass().getClassLoader());
    }

    if (this.enableDefaultSerializer) {
        if (this.keySerializer == null) {
            this.keySerializer = this.defaultSerializer;
            defaultUsed = true;
        }

        if (this.valueSerializer == null) {
            this.valueSerializer = this.defaultSerializer;
            defaultUsed = true;
        }

        if (this.hashKeySerializer == null) {
            this.hashKeySerializer = this.defaultSerializer;
            defaultUsed = true;
        }

        if (this.hashValueSerializer == null) {
            this.hashValueSerializer = this.defaultSerializer;
            defaultUsed = true;
        }
    }
}

编写自己的 redisTemplate

@Configuration
public class RedisConfig {
    // 自定义一个RedisTemplate
    @Bean
    @SuppressWarnings("all")
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        // 自定义泛型
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        // 连接工厂
        template.setConnectionFactory(redisConnectionFactory);
        // json序列化配置
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);

        // string的序列化
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        // key采用string
        template.setKeySerializer(stringRedisSerializer);
        template.setHashKeySerializer(stringRedisSerializer);
        // value采用json
        template.setValueSerializer(jackson2JsonRedisSerializer);
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        template.afterPropertiesSet();
        return template;
    }
}

实体类

@Component
@AllArgsConstructor
@NoArgsConstructor
@Data
// 在开发中,pojo对象大多需要序列化
public class User implements Serializable {
    private String name;
    private Integer age;
}

测试

@Autowired   // 需要绑定自己的RedisTemplate
@Qualifier("redisTemplate")
private RedisTemplate<String, Object> redisTemplate;
@Test
void test1() throws JsonProcessingException {
    User user = new User("feng", 18);
    // 开发中通常用json
    // ObjectMapper:jackson中的方法
    //    String jsonUser = new ObjectMapper().writeValueAsString(user);
    redisTemplate.opsForValue().set("user", user);
    System.out.println(redisTemplate.opsForValue().get("user"));
}

Redis持久化

redis是内存数据库,不将数据持久化到硬盘中,数据会断电即失去

RDB(Redis DateBase)

在指定的间隔内将内存中的数据集快照写入磁盘,恢复时,将快照文件读取到内存中。

  • Redis会单独创建一个子进程(fork)来进行数据的持久化
  • 先将数据写入到一个临时文件中
  • 待持久化过程都结束了,在用这个临时文件替换之前的快照文件
  • 子进程(fork)退出
  • 整个过程中,主进程不进行I/O操作
# 部分配置文件内容
save 900 1       # 900秒内修改了一次就持久化
save 300 10
save 60 10000

dbfilename dump.rdb    # 持久化的数据保存在dump.rdb中

dir /var/lib/redis    # dump.rdb 文件存放在这个目录下,启动redis时,会自动检测恢复dump.rdb中的数据

dump.rdb文件

  • save 满足要求时,自动创建
  • 执行flushall 时,自动创建
  • 退出redis时,自动创建

AOF(Append Only File)

以日志的形式来记录每一个写的操作,还是一个新的线程来操作的

本质:将执行reids的指令记下来,依次追加到文件中,每次启动redis,会读取文件重构数据

# 部分配置文件内容
appendonly on      # 开启需要哦改为yes
appendfilename "appendonly.aof"       # 文件名称
appendfsync always        # 每次修改都会执行sync
appendfsync everysec        # 每秒执行一次sync
appendfsync no       # 不执行sync

Redis发布订阅

一种消息通知模式:发送者(pub)发送信息,接收者(sub)接收信息

运用:通信应用,网络聊天室,实时广播,实时提醒(软件,公众号)等

订阅:subscribe [订阅对象(可以有多个)]

redis简单入门_第4张图片

发送:publish 谁发送 发送的信息

请添加图片描述

原理

底层是c语言,通过subscribe命令订阅频道后,redis-server中维护了一个字典,字典的键就是一个个channel ,字典的值是一个链表,链表中存储了所有订阅这个channel的用户,subscribe命令的关键就是将用户添加到指定的channel的订阅链表中。

通过publish指令向订阅者发送信息, redis-server使用给定的channel作为键,在channel字典中查找订阅了这个频道的用户的链表,遍历这个链表,把消息发送给订阅者。

Resis主从复制

将一台redis服务器的数据复制到另一台redis服务器中,前者为“主(master)”,后者为“从(slave)”,数据的复制是单向的,master主要写,slave只读

模拟主从复制

cd /etc/redis       # 到redis.conf存在的文件目录
cp redis.conf redis79.conf      # 复制三个配置文件(79, 80, 81)
sudo vim redis79.conf      # 更改配置文件  修改端口,pid名称,log名, dump.rdb名
sudo redis-server ./redis79.conf     # 指定配置文件分别启动三个服务
ps -ef|grep redis       # 查看进程,我们的三个进程已经开启

redis简单入门_第5张图片

info replication     # 查看三台服务器均为主机
slaveof 127.0.0.1 6379       # 将80和81改为79的从机

# 也可以通过配置文件改主从关系(replication)

redis简单入门_第6张图片

从机只能读取

redis简单入门_第7张图片

主机断开连接,从机不会有影响

复制原理

slave启动成功连接到master后,会马上发送一个sync(同步)命令,master接收到命令,启动后台的存盘进程,同时收集到所有用于修改数据集的命令,在后台进程执行完成之后,master将整个数据传输到slave,完成一次同步。

复制分为两个方式

  • 全量复制:slave连接master时,接收数据,存盘的过程
  • 增量复制:后续master有写或修改操作时,同步给slave
slaveof no one     # 取消自己属于从机的关系, 谋权篡位

哨兵模式

能够后台监控主机是否故障,来根据投票数从slave中选择一个作为master

哨兵是一个独立的进程,开启之后,它将独立运行

原理:哨兵通过发送命令,等待Redis的响应,从而监控多个redis

redis简单入门_第8张图片

执行过程:当master当机,哨兵1先检测到结果,不会马上进行failover(故障转移),此时认为是主观下线,当其他哨兵也检测到结果了,就认为是客观下线那么就会在哨兵之间进行一次投票(决定谁来当master),进行操作failover操作,切换成功后,通过发布订阅模式,让各个哨兵把监控对象切换到新的master

# 配置哨兵,编写配置文件 sentinel.conf
sentinel monitor myredis 127.0.0.1 6379 1     # 最基本的配置
# 两种启动方式用哪种都可以
sudo redis-server ./sentinel.conf --sentinel
redis-sentinel ./sentinel.conf

redis简单入门_第9张图片

master挂了

redis简单入门_第10张图片

master从6379转移到了6380

如果master转移之后,之前的master回来之后,会自动成为新的masterslave

# Example sentinel.conf

# *** IMPORTANT ***
# 绑定IP地址
# bind 127.0.0.1 192.168.1.1
# 保护模式(是否禁止外部链接,除绑定的ip地址外)
# protected-mode no

# port 
# 此Sentinel实例运行的端口
port 26379

# 默认情况下,Redis Sentinel不作为守护程序运行。 如果需要,可以设置为 yes。
daemonize no

# 启用守护进程运行后,Redis将在/var/run/redis-sentinel.pid中写入一个pid文件
pidfile /var/run/redis-sentinel.pid

# 指定日志文件名。 如果值为空,将强制Sentinel日志标准输出。守护进程下,如果使用标准输出进行日志记录,则日志将发送到/dev/null
logfile ""

# sentinel announce-ip 
# sentinel announce-port 
#
# 上述两个配置指令在环境中非常有用,因为NAT可以通过非本地地址从外部访问Sentinel。
#
# 当提供announce-ip时,Sentinel将在通信中声明指定的IP地址,而不是像通常那样自动检测本地地址。
#
# 类似地,当提供announce-port 有效且非零时,Sentinel将宣布指定的TCP端口。
#
# 这两个选项不需要一起使用,如果只提供announce-ip,Sentinel将宣告指定的IP和“port”选项指定的服务器端口。
# 如果仅提供announce-port,Sentinel将通告自动检测到的本地IP和指定端口。
#
# Example:
#
# sentinel announce-ip 1.2.3.4

# dir 
# 每个长时间运行的进程都应该有一个明确定义的工作目录。对于Redis Sentinel来说,/tmp就是自己的工作目录。
dir /tmp

# sentinel monitor    
#
# 告诉Sentinel监听指定主节点,并且只有在至少哨兵达成一致的情况下才会判断它 O_DOWN 状态。
#
#
# 副本是自动发现的,因此您无需指定副本。
# Sentinel本身将重写此配置文件,使用其他配置选项添加副本。另请注意,当副本升级为主副本时,将重写配置文件。
#
# 注意:主节点(master)名称不能包含特殊字符或空格。
# 有效字符可以是 A-z 0-9 和这三个字符 ".-_".
sentinel monitor mymaster 127.0.0.1 6379 2

# 如果redis配置了密码,那这里必须配置认证,否则不能自动切换
# Example:
#
# sentinel auth-pass mymaster MySUPER--secret-0123passw0rd

# sentinel down-after-milliseconds  
#
# 主节点或副本在指定时间内没有回复PING,便认为该节点为主观下线 S_DOWN 状态。
#
# 默认是30秒
sentinel down-after-milliseconds mymaster 30000

# sentinel parallel-syncs  
#
# 在故障转移期间,多少个副本节点进行数据同步
sentinel parallel-syncs mymaster 1

# sentinel failover-timeout  
#
# 指定故障转移超时(以毫秒为单位)。 它以多种方式使用:
#
# - 在先前的故障转移之后重新启动故障转移所需的时间已由给定的Sentinel针对同一主服务器尝试,是故障转移超时的两倍。
#
# - 当一个slave从一个错误的master那里同步数据开始计算时间。直到slave被纠正为向正确的master那里同步数据时。
#
# - 取消已在进行但未生成任何配置更改的故障转移所需的时间
#
# - 当进行failover时,配置所有slaves指向新的master所需的最大时间。
#   即使过了这个超时,slaves依然会被正确配置为指向master。
#
# 默认3分钟
sentinel failover-timeout mymaster 180000

# 脚本执行
#
# sentinel notification-script和sentinel reconfig-script用于配置调用的脚本,以通知系统管理员或在故障转移后重新配置客户端。
# 脚本使用以下规则执行以进行错误处理:
#
# 如果脚本以“1”退出,则稍后重试执行(最多重试次数为当前设置的10次)。
#
# 如果脚本以“2”(或更高的值)退出,则不会重试执行。
#
# 如果脚本因为收到信号而终止,则行为与退出代码1相同。
#
# 脚本的最长运行时间为60秒。 达到此限制后,脚本将以SIGKILL终止,并重试执行。

# 通知脚本
#
# sentinel notification-script  
#
# 为警告级别生成的任何Sentinel事件调用指定的通知脚本(例如-sdown,-odown等)。
# 此脚本应通过电子邮件,SMS或任何其他消息传递系统通知系统管理员 监控的Redis系统出了问题。
#
# 使用两个参数调用脚本:第一个是事件类型,第二个是事件描述。
#
# 该脚本必须存在且可执行,以便在提供此选项时启动sentinel。
#
# 举例:
#
# sentinel notification-script mymaster /var/redis/notify.sh

# 客户重新配置脚本
#
# sentinel client-reconfig-script  
#
# 当主服务器因故障转移而变更时,可以调用脚本执行特定于应用程序的任务,以通知客户端,配置已更改且主服务器地址已经变更。
#
# 以下参数将传递给脚本:
#
#       
#
#  目前始终是故障转移 "failover"
#  是 "leader" 或 "observer"
#
# 参数 from-ip, from-port, to-ip, to-port 用于传递主服务器的旧地址和所选副本的新地址。
#
# 举例:
#
# sentinel client-reconfig-script mymaster /var/redis/reconfig.sh

# 安全
# 避免脚本重置,默认值yes
# 默认情况下,SENTINEL SET将无法在运行时更改notification-script和client-reconfig-script。
# 这避免了一个简单的安全问题,客户端可以将脚本设置为任何内容并触发故障转移以便执行程序。
sentinel deny-scripts-reconfig yes

# REDIS命令重命名
#
#
# 在这种情况下,可以告诉Sentinel使用不同的命令名称而不是正常的命令名称。
# 例如,如果主“mymaster”和相关副本的“CONFIG”全部重命名为“GUESSME”,我可以使用:
#
# SENTINEL rename-command mymaster CONFIG GUESSME
#
# 设置此类配置后,每次Sentinel使用CONFIG时,它将使用GUESSME。 请注意,实际上不需要尊重命令案例,因此在上面的示例中写“config guessme”是相同的。
#
# SENTINEL SET也可用于在运行时执行此配置。
#
# 为了将命令设置回其原始名称(撤消重命名),可以将命令重命名为它自身:
#
# SENTINEL rename-command mymaster CONFIG CONFIG

缓存穿透

用户查询数据时,redis缓存中不存在,到持久层数据库(mysql)中去查,mysql中也没有数据,当很多用户查询的时候,都去请求了mysql,给mysql服务造成了很大压力。

解决:

  • 布隆过滤器:一种数据结构,把可能查询的参数用hash形式存储,在控制层先较验,不符合要求的丢弃。
  • 缓存空的对象:将查询都没有的数据时,将改键的值设置为空值,之后查询都会通过缓存返回空值。

缓存击穿

当一个key非常热点,在不停的扛着大量并发,大并发的集中访问,当这个key在失效的瞬间,大量并发就击穿了缓存,直接访问持久层数据库。

解决:

  • 将热点数据设置为永不过期。
  • 加互斥锁:使用分布式锁,保证对于每一个key同时只有一个线程去访问持久数据库。

缓存雪崩

某个时间段,缓存集中失效,如双十一时的超大量访问。断电也是缓存雪崩。

解决:

  • 搭建集群:多设置几台redis,一台挂掉之后,另外的还可以工作。
  • 限流降级:当缓存失效后,通过加锁或队列来限制用户访问缓存的线程数量。
  • 数据预热:在正式部署之前,预先访问持久层数据库,把数据加载到缓存中,并设置不同的过期时间,让缓存失效的时间均匀。

源自视频:https://www.bilibili.com/video/BV1S54y1R7SB?p=36&spm_id_from=333.880.my_history.page.click

你可能感兴趣的:(redis,数据库,java,spring,boot)