Redis学习

Redis基础篇

入门

初始Redis

认识NoSql
  • 非关系型数据库

Sql和NoSql对比
SQLNoSql
数据结构机构化非结构化
数据关联关联的无关联的
查询方式SQL查询非SQL查询
事务特性ACIDBASE
储存方式磁盘内存
扩展性垂直水平
使用场景1.数据结构固定 2.相关业务对数据安全性、一致性要求较高1.数据结构不固定 2.对一致性、安全性要求不高 3.对性能要求高

安装

  • Redis是基于Linux系统设计的,所以只能安装到Linux系统上。企业开发也是基于Linux的。

1.1安装Redis依赖
 yum install -y gcc tcl
1.2上传安装包
  • 将安装包上传到虚拟机目录并解压

解压:

 tar -xzf 安装包

进入目录:

 cd 包名

运行预编译命令:

 make && make install

没有意外应该就安装成功了

1.3启动

redis的启动有多种方式:

  • 默认启动

  • 指定配置启动

  • 开机启动

默认启动

在任意命令行输入命令即可启动

 redis-server

这种启动属于前台启动,他启动后的会话窗口不能关闭,否则redis则会关闭。不推荐使用

指定配置启动
  • 如果想让redis后台启动则需要修改redis的配置文件。在安装包的目录下有一个redis.conf的文件

我们先将文件备份一份

 cp redis.conf redis.conf.bck

然后修改一些配置

 # 允许访问的地址,默认是127.0.0.1,会导致只能在本地访问。修改为0.0.0.0则可以任意ip访问,生产环境下不要设置为0.0.0.0
 bind 0.0.0.0
 # 守护进程,修改为yes后即可进入后台运行
 daemonize yes
 # 密码,设置后访问redi必须要密码
 requirepass 123456

启动redis:

 # 进入redis的安装目录
 cd /url/local/src/redis-6.2.6
 # 启动
 redis-server redis.conf
开机自启

首先,我们需要新建一个服务文件:

 vi /etc/systemd/system/redis.service

文件内容如下:

 [Unit]
 Description=redis-server
 After=network.target
 ​
 ​
 [service]
 Type=forking
 ExecStart=/ulr/local/bin/redis-server /usr/local/src/redis-6.2.6/redis.conf
 PrivateTmp=true
 ​
 ​
 [Instal]
 WantedBy-multi-user.target

然后重载系统服务:

 systemctl daemon-reload

通过命令

 systemctl enable redis

就可以让redis开机自启了

Redis客户端

这里操作redis需要用到redis客户端,包括;

  • 命令行客户端

  • 图形化桌面客户端

  • 编程客户端

命令行客户端

Redis安装好了之后就自带命令行客户端:

 redis-cli [options] [commonds]

其中常见的options有:

  • -h 127.0.0.1:指定要链接的redis节点的ip地址,默认是127.0.0.1

  • -p 6379:指定要链接的端口,默认是6379

  • -a 123321:指定访问密码

其中的commonds就是redis的操作命令:

  • ping:与redis做服务端心跳测试,服务端正常返回pong

不指定commonds时,会进入redis-cli的交互控制台

图形化客户端
  • 进入到下面这个仓库可以下载相应的安装包,

 https://github.com/lework/RedisDesktopManager-Windows/releases

安装完成之后,进入到安装目录里,找到rdm.exe文件,双击即可运行

常用数据结构

介绍

  • Redis是一个key-value的数据库,key一般是string类型的,value的类型却有很多种:

stringhello world
Hash{name: "Jack", age: 21}
List[A->B->C->C]
set{A,B,C}
SortedSet{A: 1, B: 2,C: 3}
GEO{A: (120.3, 30.5)}
BitMap01110100101
HyperLog01101010101

其中前五种我们称为redis的基本数据类型,后三种为特殊类型

操作命令
  • Redis官网对操作命令进行了详细的分组和示例,我们查看查看官方文档即可查询到对应的操作命令

通用命令
  • 部分数据类型都可以使用的命令,常见的有:

KEYS:查看符合模板的所有key(类似于模糊查询,查询效率不高。redis是单线程的,当数据量达到千万时,redis的查询效率会很慢,就相当于阻塞了,所以不建议使用)KEYS pattern
DEL:删除键,可以同时删除多个DEL key [key ...]
EXISTS:判断一个key是否存在EXISTS key
EXPIPE:给一个key设置有效期,有效期到期时该key会被自动删除EXPIPE key seconds
TTL:查询一个key的剩余有效期TTL key
String类型
  • 是Redis当中最简单的存储类型。其中value为字符串,不过根据字符串的格式不同,又可以分为3类:

  • string:普通字符串

  • int:整数类型,可以做自增、自减操作

  • float:浮点类型,可以做自增自减操作

常见命令
  • SET:添加或者修改已经存在的一个String类型的键值对

  • GET:根据key获取String类型的value

  • MSET:批量添加多个String类型的键值对

  • MGET:根据多个key获取多个String类型的value

  • INCR:让一个整型的key自增

  • INCRBY:让一个整型的key自增并指定步长,例如:incrby num 2 让num值自增2

  • INCRBYFLOAT:让一个浮点类型的数字自增并指定步长

  • SETNX:添加一个String类型的键值对,前提是这个key不存在,否则不执行

  • SETEX:添加一个String类型的键值对,并且指定有效期

key的结构
  • Redis的key允许多个单词形成层级结构,多个单词之间用‘:’隔开,格式如下:

 项目名:业务名:类型:id

这个格式并非固定,可以根据自己的需求来添加,例如:

heima:user:1

heima:product:1

Hash类型
  • Hash类型,也叫散列,其value是一个无序字典,类似于java中的HashMap结构

  • String结构是将对象转化为string类型的字符串,当需要修改其中一个字段是无法进行的。而Hash结构是将value中的字符串在进行一次key-value类型的分别

常用命令
  • HSETkeyfield value:添加或者修改hash类型key的field的值

  • HGET key field:获取一个hash类型key的field的值

  • HMsET:批量添加多个hash类型key的field的值

  • HMGET:批量获取多个hash类型key的field的值

  • HGETALL:获取一个hash类型的key中的所有的field和value

  • HKEYs:获取一个hash类型的key中的所有的field

  • HvALS:获取一个hash类型的key中的所有的value

  • HINCRBY:让一个hash类型key的字段值自增并指定步长

  • HsETrx:添加一个hash类型的key的field值,前提是这个field不存在,否则不执行

List类型
  • Redis中的List类型与java中的LinkedList类似,可以看作是一个双向链表结构。既可以支持正向检索也可以支持反向检索

常用命令
  • LPUsHkeyelement...:向列表左侧插入一个或多个元素

  • LPOPkey:移除并返回列表左侧的第一个元素,没有则返回nit

  • RPUSHkeyelement...:向列表右侧插入一个或多个元素

  • RPOPkey:移除并返回列表右侧的第一个元素

  • LRANGE key star end:返回一段角标范围内的所有元素

  • BLPOP和BRPOP:与LPOP和RPOP类似,只不过在没有元素时等待指定时间,而不是直接返回nil

Set类型
  • 和java的HashSet类似,可以看作是一个value为null的HashMap。因为是一个表,因此也具备和HashSet类似的特征

  • 无需,元素不重复,查找快,支持交集、并集、差集等功能

常用命令
  • SADD key member .:向set中添加一个或多个元素

  • SREM key member...:移除set中的指定元素

  • SCARD key:返回set中元素的个数

  • SISMEMBER key member:判断一个元素是否存在于set中

  • SMEMBERS:获取set中的所有元素

  • SINTER key1 key2...:求key1与key2的交集

  • SDIFF key1 key2...:求key1与key2的差集

  • SuNION key1 key2..:求key1和key2的并集

SortedSet类型
  • Redis的Sorted是一个可排序的Set集合,与java中的treeSet类似,但底层的数据结构差别却很大。

  • SortedSet中的每一个元素都带有一个score属性,可以基于score属性对元素进行排序。

  • 可排序、元素不重复、查询速度快

  • 因为可排序性,经常被用来实现排行榜这样的功能

常用命令
  • ZADD key score member:添加一个或多个元素到sorted set,如果已经存在则更新其score值

  • ZREM key member:翻除sorted set中的一个指定元素

  • ZSCORE key member:获取sorted set中的指定元素的score值

  • ZRANKkey member:获取sorted set中的指定元素的排名

  • ZCARD key:获取sorted set中的元素个数

  • ZCOUNNT key min max:统计score值在给定范围内的所有元素的个数

  • ZINCRBY keyincrement member:让sorted set中的指定元素自增,步长为指定的increment值

  • ZRANGE key min max:按照score排序后,获取指定排名范围内的元素

  • ZRANGEBYSCORE key min max:按照score排序后,获取指定score范围内的元素

  • ZDIEF、ZINTER、ZUNION:求差集,交集并集

Redsi的java客户端

  • Redis官网提供了很多的客户端

Jedis

  • 它提供了与Redis命令一一对应的方法,并支持使用管道和事务来提高和保证数据的一致性

实现步骤
1.引入依赖
 <dependency>
 <groupId>redis.clients</groupId>
 <artifactId>jedis</artifactId>
 <version>3.7.0</version>
 </dependency>
2.建立连接
 private Jedis jedis;
 @BeforeEach
 void setUp() {
 /建立连接
 jedis=new Jedis("192.168.150.101",6379);
 //设置密码
 jedis.auth("123321");
 //选择库
 jedis.select(@);

3.测试

 @Test
 void teststring() {
 //插入数据,方法名称就是redis命令名称,非常简单
 String result = jedis.set("name", "张三");
 System.out.println("result = " + result);
 //获取数据
 String name = jedis.get("name");
 System.out.println("name = " + name);
 }

4.释放资源

 @AfterEach
 void tearDown() {
 //释放资源
 if (jedis != null) {
 jedis.close();
 }
Jedis连接池
  • Jedis本身是线程不安全的,并且频繁的和销毁连接会有性能损耗,因此我们建议大家使用jedis连接池代替jedis直连方式

 public class JedisConnectionFactory {
 private static final jedisPool jedisPool;
 static {
 JedisPaalConfig jedisPoolconfig =now JedisPaoiconfig();
 //最大连接
 jedisPclconfig.setMaxTotol(8);
 //最大空闲连接
 jedisPoolconfig.setMaxIdle(8);
 //最小空闲连接
 jedisPoalConfig.setMinIdle(0);
 //设置最长等待时间,ms
 jedisPoolconfig.setMaxWaitHillis(200);
 jedisPoot = new JedisPool(jedisPoolConfig. "192.168.150.101", 6379,1000,"123321");
 /获族Dedis对象
 pubiic static Jedis getJedis(){
     return jedispool.getResource();
 }

SpringDataRedis

  • SpringData是Spring中数据操作的模块,包含对各种数据库的集成,其中对redis的集成叫做SpringDataRedis

作用
  • 提供了对不同Redis客户端的整合(Lettuce和jedis)

  • 提供了RedisTemplate统一API来操作Redis

  • 支持Redis的发布订阅模型

  • 支持Redis哨兵和Redis集群

  • 支持基于Lettuce的响应式编程

  • 支持基于JDK、JSON、字符串、Spring对象的数据序列化及反序列化

  • 支持基于Redis的JDKCollection实现

操作命令
  • SpringDataRedis中提供了RedisTemplate工具类,其中封装了各种对Redis的操作。

API返回值说明
redisTemplate.opsForValue()ValueOperations操作String类型的数据
redisTemplate.opsForHash()HashOperations操作Hash类型数据
redisTemplate.opsForList()ListOperations操作List类型数据
redisTemplate.opsForSet()SetOperations操作Set类型数据
redisTemplate.opsForZSet()ZSetOperations操作SortedSet类型数据
redisTemplate通用命令
使用步骤
1.引入依赖
 <!--Redis依赖-->
 <dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-data-redis</artifactId>
 </dependency>
 <!一连接池依赖->
 <dependency>
 <groupId>org.apache.commons</groupId>
 <artifactId>commons-pool2</artifactId>
 </dependency>
2.配置文件
 spring:
 redis:
 host:192.168.150.101
 port:6379
 password: 123321
 lettuce:
  pool:
 max-active:8 # 最大连接
 max-idle:8#最大空闲连接
 min-idle:0 # 最小空闲连接
 max-wait:100
 #连接等待时间
3.注入RedisTemplate
 @Autowired
 private RedisTemplate redisTemplate;
4.编写测试
 @SpringBootTest
 pubtic class RedisTest {
         @Autawired
         private RedisTemplate redisTemplate;
 ​
         @Test
         void testString() {
             //插入一条string类型数据
             redisTemplate.opsForvalue().set("name", "李四");
             //读取一条string类型政据
             Object name = redisTemplate.opsForValue().get("name");
             System.out.println("name = " + name);
         }
     }
序列化问题
  • RedisTemplate可以接收任意的object作为值写入redis,只不过写入前会把object序列化为字节形式,默认是采用jdk序列化,而我们看到的就是序列化后的字节

  • 这样就导致可读性差,内存占用较大

解决方法
  • 改变RedisTemplate中的序列化方式

示例:

 public RedisTemplate<string,object> redisTemplate(RedisConnectionFactory redisConoectionFoctory)
 throws UnknownHostException {
 //创建Template
 RedisTemplate<string, Object> redlsTemplate = new RedisTemplate<>();
 //设置连接工厂
 redisTemplate.setConnectionFactory(redisConnectionFactory);
 //设置序列化工具
 GenericJackson2JsonRedisSerialTizer jsonRedisSeriaizer = new GenericJackson2JsonRedisSerializer();
 //key和 hashKey采用 string序列化
 redisTemplate.setkeySeriatizer(Redisserializer.string());
 redisTemplate.setHashKeySerializer(RedisSerializer.string());
 //value和 hashValue采用JsoN序列化
 redisTemplate.setValueSerializer(jsonRedisSerializer);
 redisTemplate.setHashValueSerializer(jsonRedisSerializer);
 return redlsTemplate;
 }
存入对象数据存在的问题
  • 当存入一个对象数据时,redis中会额外带有对应的对象包的信息,会带来额外的开销

解决方法
  • Spring提供了一个StringRedlsTemplate类,它的key和value的序列化方式默认是string方式。省去了我们自定义redlsTemplate的过程

 @Autowired
 private StringRedisTemplate stringRedisTemplate;
 //JSON工具
 private static final ObjectMapper mapper = new ObjectMapper();
 @Test
 void testStringTemplate() throws JsonProcessingException {
     //准务对象
 User user =new User("虎哥",18);
 //手动序列化
 String json = mapper.writevalueAsString(user);
 //写入一条数据到redis
 stringRedisTemplate.opsForValue().set("user:200", json);
 //读取数据
 String val = stringRedisTemplate.opsForValue().get("user:200");
 //反序列化
 User user1 = mapper.readvatue(vat, User.class);
 System.out.println("user1 = " + user1);
 }

Redis缓存

概念
  • 缓存就是数据交换的缓冲区(cache),是存储数据的临时地方,一般读写性能较高。

优缺点

优点
  • 降低了后端负载

  • 提高了读写效率,降低响应时间

缺点
  • 数据一致性问题。将数据缓存到redis后,如果数据库的数据更新,那么到时候用户得到的数据就是就的数据,会造成一些错误

  • 为了解决数据一致性的问题,这就要求我们的代码更加复杂,导致代码的维护成本增加

缓存更新策略

内存淘汰超时剔除主动更新
说明不用自己维护,利用redis的内存淘汰机制,当内存不足时自动淘汰部分数据。下次查询时更新给缓存添加ttl时间,到期后自动删除缓存。下次查询时更新编写业务逻辑,在修改数据库的同时,更新缓存
一致性一般
维护成本

主动更新策略

Cache Aside Pattern
  • 由缓存的调用者,在更新数据库的同时更新缓存(使用最多)

存在的问题
缓存更新问题

当我们更新缓存时,我们需要考虑一个问题,是更新缓存还是删除缓存

  • 更新缓存:每次更新数据库都更新缓存,无效写操作过多。具体一点就是我现在有一个程序,数据库的数据有多次更新,但是在这期间没有请求来访问这些数据,但是redis一直在进行更新操作,这就造成了大量的无效操作

  • 删除缓存:更新数据库时,让缓存失效,用户查询时再更新缓存。所以我们推荐先删除缓存

操作同时成功问题
  • 如何保证缓存和数据库同时成功或失败

  • 单体系统:将缓存和数据库操作放在同一个事务当中。

  • 分布式系统:利用TCC等分布式事务方案

先后问题

先更新数据库还是先更新缓存?

  • 两者即可,看具体情况。两者都可能发生问题

Read/Write Through Patten
  • 缓存与数据库整合为一个服务,又服务来维护一致性。调用者调用该服务,无需关心缓存一致性的问题

Write Behind Caching Patten
  • 调用者只操作缓存,由其他线程异步的将缓存数据持久化到数据库,保证一致性

缓存穿透

概念
  • 缓存穿透是指客户端请求的数据在缓存中和数据库都不存在,但是用户还在不断的发起请求,这些请求都会打到数据库,导致数据库直接被压垮

解决方案
缓存空对象
  • 缓存一个null

优点
  • 实现简单,维护方便

缺点
  • 造成额外的消耗(如果每一个不存在的请求都要缓存一个,那么就造成了很多浪费)

  • 可能造成短期的不一致

布隆过滤
  • 在客户端和缓存之间增加一个过滤器,每一个请求先被过滤器拦截,然后根据条件选择性的放行

优点
  • 内存占用少,没有多余的key

缺点
  • 实现复炸

  • 存在误判可能

缓存雪崩

概念
  • 缓存雪崩是指在同一时间段大量的缓存key同时失效或者redis服务宕机,导致大量的请求到达了数据库,带来了巨大的压力

解决方案
  • 给不同的key的TTL添加一个随机值

  • 利用redis集群提高服务的可用性

  • 给缓存业务添加降级限流策略

  • 给业务添加多级缓存

缓存击穿

概念
  • 缓存击穿问题也叫热点Key问题,就是一个被高并发访间并且缓存重建业务较复杂的key突然失效了,无数的请求访何会在瞬间给数据库带来巨大的冲击

解决方案
互斥锁

简单来说就是在Redis中根据key获得的value值为空时,先锁上,然后从数据库加载,加载完毕,释放锁。若其他线程也在请求该key时,发现获取锁失败,则睡眠一段时间(比如100ms)后重试

优点
  • 没有额外的消耗

  • 保证一致性

  • 实现简单

缺点
  • 线程需要等待,性能可能受影响

  • 可能有锁死风险

逻辑过期
优点
  • 线程无需等待,性能较好

缺点
  • 不保证一致性

  • 有额外的消耗

  • 实现复炸

Redis实战篇

优惠券秒杀

存在问题

每个店铺都可以发布优惠券,当用户抢购时就会生成订单并保存到订单表中,订单表中有一个字段表示的是使用了那些优惠券。而订单表如果使用数据库自增长ID就会存在一些问题

  • id的规律性太明显(用户可能根据id推测出一天的实际订单数)

  • 受单表数据量的限制

解决方法
全局ID生成器

全局ID生成器,是一种在分布式系统下用来生成全局唯一ID的工具,一般要满足一下特性:

  • 唯一性

  • 高可用

  • 高性能(生成的速度快)

  • 递增性

  • 安全性

为了增加ID的安全性,我们可以不直接使用redis的自增数值,而是拼接一些信息:

ID的组成:

符号位:1bit,永远为0

时间戳:31bit,以秒为单位,可以使用69年

序列号:32bit,以秒为计数器,支持每秒生产2的32次方个不同的ID

分布式锁

概念
  • 满足分布式系统或集群模式下多进程可见且互斥的锁

实现
实现方式
MySQLRedisZookeeper
互斥利用mysql本身的互斥锁机制利用setnx这样的互斥命令利用节点的唯一性和有序性
高可用
高性能一般一般
安全性断开连接,自动释放锁利用锁超时时间,到期自动释放临时节点,断开连接自动释放
基于Redis实现

实现分布式锁需要实现两个基本方法:

  • 获取锁:

    • 而获取锁其中最重要的一个就是互斥:确保只有一个线程获取锁,比如:

       SETNX lock thread1 NX EX 10# 利用setnx的互斥特性,NX互斥,EX存活时间
  • 释放锁:

    • 手动释放:

    • 超时释放:手动添加一个存活时间

     DEL key # 释放锁,删除即可

在释放锁时,为了防止误删,我们可以在获取锁时添加一个线程的标识,释放时进行比较,一致在删除,以防止误删的情况.

但仍旧存在一些问题,当判断结束后要进行释放操作时线程发生了堵塞,也会出现误删问题。原因是因为判断和释放不是同一个操作,中间有间隔。

而解决方法就是让两个操作具有原子性

如何让radis命令具有原子性
  • Redis提供了Lua脚本功能,在一个脚本中编写多条redis命令,确保多条命令的原子性。

Redis调用函数
 # 执行redis命令
 redis.call('命令名称','key','其他参数'...)

例如我们要执行set name jack命令,则脚本为:

 # 执行set name jack
 redis.call('set','name','jack')

如果我们要执行set name Rose,在执行get name,则脚本如下:

 # 先执行set name jack
 redis.call('set','name','jack')
 # 再执行get name
 local name = redis.call('get','name')
 # 返回
 return name

写好脚本后,需要redis命令来执行脚本,调用脚本命令如下:

EVAL script numkeys key

例如,我们要执行redis.call('set','name','jack')这个脚本,语法如下:

 # 调用脚本
 EVAL "return redis.call('set','name','jack')" 0

然后进行改进:基于Lua改进分布式锁

 private static final DefaultRedisScript<Long> UNLOCK_SCRIPT;
 static {
 UNLOCK_SCRIPT = new DefauttRedisscript<>();
 UNLOCK_SCRIPT.setLocation(new classPathResource("unlock.lua"));
 UNLOCK_SCRIPT.setResultType(Long.class);//设置返回值
 }
 ​
 @Override
 pubtic void unlock() 
 //调用Lua脚本
 stringRedisTemplate.execute(){
     UNLOCK_SCRIPT,
     collections.singletonList(KEY_PREFIX + name),
     ID_PREFIX + Thread.currentThread().getId());
 }
分布式锁优化
Redisson
  • Redsson是一个在redis基础上实现的java驻内存数据网格。它不仅提供了一系列的分布式java对象,还提供了许多分布式服务,其中就包含了分布式锁

Redisson入门

1.引入依赖

 <dependency>
 <groupId>org.redisson</groupId>
 <artifactId>redisson</artifactId>
 <version>3.13.6</version>
 </dependency>

2.配置客户端

 @configuration
 pabtic class RedisConfig (
     @Bean
 pubtic RedissonClient redissonClient() {
     //配置类
     Config config = new confng();
     //添加redis地址,这里添加了单点的地址,也可以使用config.useClusterServers()添加集群地址
     config.useSingieServer().setAddress("redis//192.168.150.101:6379").setPassword("123321");
     //创建客户端
     return Redisson.crecte(config);
 }

3.使用锁

 @Resource
 private RedissonClient redissonClient;
 @Test
 void testRedisson() throws InterruptedException {
     //获取锁(可重入),指定锁的名称
     RLock lock = redissonClient.getLock("anyLock");
     //尝试获取锁,参数分放是:获取锁的最大等待时间(期间会重试),锁自动释放时间,时网单位
     boolean isLock = lock.tryLock(1, 10, TimeUnit.SECONDS);
     //判断释放获取成功
     if(isLock)(
         try {
             System.out.printin("执行业务“);
              }finally {
                  //释放锁
                  lock.unlock();
              }
          } 
     }     

Redis高级篇

单点Redis存在的问题

数据丢失问题

  • Redis是内存存储,服务重启后可能会丢失数据

解决方法
  • redis的数据持久化,将数据写入磁盘

并发能力问题

  • 单节点redis并发能力虽然不错,但也无法满足618这样的高并发场景

解决方法
  • redis主从集群,实现读写分离

故障恢复问题

  • 如果redis宕机,则服务不可用,需要一种自动的故障恢复手段

解决方法
  • redis哨兵机制,实现健康检测和自动恢复

存储问题

  • redis的数据是存储在内存的,单节存储的数据难以满足海量的数据需求

解决方法
  • 搭建分片集群,利用插槽机制实现动态扩容

Redis持久化

RDB持久化

概念
  • RDB全redis database backup file(数据备份文件),也被叫做redis数据快照。简单来说就是将内存中的数据记录到磁盘中,当redis出现故障重启后,从磁盘读取快照文件,恢复数据

实现
自动实现
  • redis默认在关闭时就会自动的将数据备份到当前目录中。但这是关闭后才备份的

主动实现
  • redis内部由RDB触发机制,可以在redis.conf文件中找到,格式如下:

 # 900秒内,如果至少有1个key被修改。则执行bgsave,如果是save "" 则表示禁用RDB
 save 900 1
 save 30B 10
 save 60 10000

还有其他设置

 # 是否压缩 ,建议不开启,压缩也会消耗cpu,磁盘的话不值钱
 rdbcompression yes
 # RDB文件名称
 dbfilename dump.rdb
 # 文件保存的路径目录,保存在当前路径
 dir ./
bgsave
  • bgsave开始时会fork主进程得到子进程,子进程共享主进程的内存数据。完成fork后读取内存数据并写入RDB文件

fork采用的是copy-on-write技术:

  • 当主进程执行读操作时,访问共享内存

  • 当主进程执行写操作时,则会拷贝一份数据,执行写操作。

AOF持久化

概念

  • AOF称为追加文件。redis处理的每一个命令都会记录在AOF文件中,可以看作是命令日志文件

AOF默认是关闭的,需要修改配置文件来开启AOF:

 # 是否开启AOF功能,默认是no
 appendonly yes
 # AOF文件的名称
 appendfilename "appendonly.aof"

AOF的命令记录频率也可以通过配置文件来配:

 # 表示每执行一次写命令,立即记录到A0F文件
 appendfsync always
 # 写命令执行完先放入AOF级冲区,然后表示每隔1秒将缓冲区数据写到ADF文件,是默认方案
 appendfsync everysec
 # 写命令执行完先放入A0F缓冲区,由操作系统决定何时将缓冲区内容写回磁盘
 appendfsync no

因为是记录命令。AOF文件会比RDB文件大的多。而且AOF会记录对同一个key的多次写操作。但只有最后一次写操作才有意义,通过执行bgrewriteaof命令,可以让AOF文件执行重写功能,用最少的命令达到相同的效果

Redis也会在出发阈值时,自动重写AOF文件。也是在配置文件中配置的:

 # A0F文件比上次文件 增长超过多少百分比则触发重写
 auto-aof-rewrite-percentage 100
 # AOF文件体积最小多大以上才触发重写
 auto-aof-rewrite-min-size 64mb

主从架构

  • 单节点的redis的并发能力是有限的,要进一步提高redis的并发能力,就需要搭建主从集群,实现读写分离

  • 比如实际操作中读操作更多,那么我们可以将读操作分给主节点。写操作就可以分给从节点

如何搭建

  • 开启3台redis,准备3个不同的配置文件,然后运行。最后进行连接。

数据同步原理

  • 主从第一次同步是全量同步

  • 当主节点的数据更新后,执行bgsave生成RDB文件,然后将RDB文件发送给从节点,从节点扫描文件然后更新数据,这样就能基本保证数据的一致性

哨兵机制

  • slave节点宕机后恢复后可以找master节点同步数据,那master节点宕机后怎么办呢?

作用

监控
  • Sentionel会不断检查你的master和slave是否按预期工作

自动故障恢复
  • 如果master发生了故障宕机,sentinel会将一个slave提升为master。当故障恢复后也以新的master为主

通知
  • Sentinel充当redis客户端的服务发现来源,当集群发生故障转移时,会将最新信息推送给redis客户端

服务状态监控

如何实现监控
  • Sentinel基于心跳机制监测服务状态,每个1秒向集群的每个实例发送ping命令

    • 主观下线:如果Sentinel发现某个实例没有按照规定时间响应,则认为该实例主观下线

    • 客观下线:若超过指定数量的哨兵都认为该实例下线,则该实例客观下线。这个值最好是一超过哨兵数量的一半

如何实现故障转移

当一个slave转化为新的master后,故障转移步骤如下:

  • sentinel给备选的slave1节点发送slaveof no one命令,让该节点成为master

  • sentinel给所有其它slave发送slaveof192.168.150.1017002命令,让这些slave成为新master的从节点,开始从新的master上同步数据。

  • 最后,sentinel将故障节点标记为slave,当故障节点恢复后会自动成为新的master的slave节点

搭建步骤

Java客户端操作哨兵

1.在pom文件中引入redis的starter依赖:

 <dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-data-redis</artifactId>
 </dependency>

2.在配置文件中指定sentienl的信息

 spring:
 redis:
 sentinel:
 master:mymaster # 指定master名称
 nodes:# 指定redis-sentinel集群信息
 - 192.168.150.101:27001
 - 192.168.150.101:27002
 - 192.168.150.101:27003

3.配置主从读写分离

 @Bean
 pubtic LettuceClientConfigurationBuilderCustomizer contigurationBuilderCustomizer(){
     return configBuilder -> config8uilder.reodFrom(ReadFrom.REPLICA_PREFERRED);
 }

Redis分片集群

作用

主从和哨兵可以解决高可用、高并发读的问题。但是依然有两个问题没有解决:

  • 海量数据存储问题

  • 高并发写的问题

而使用分片集群可以解决上述问题,分片集群特征:

  • 集群中有多个master,每个master保存不同的数据

  • 每个master都可以有多个slave节点

  • master之间通过ping检测彼此健康状况

  • 客户端请求可以访问集群任意节点,最总都会被转发到正确节点

搭建步骤

准备配置和实例

  • 创建新的节点目录

     mkdir 7001 7002 7003 8001 8002 8003
  • 准备新的配置文件

 port 6379
 # 开启集群功能
 cluster-enabled yes
 # 集群的配置文件名称,不需要我们创建,由redis自己维护
 cluster-config-file /tmp/6379/nodes.conf
 # 节点心跳失败的超时时间
 cluster-node-timeout 5000
 # 持久化文件存放目录
 dir /tmp/6379
 # 绑定地址
 bind 0.0.0.0
 # 让redis后台运行
 daemonize yes
 # 注册的实例p
 replica-announce-ip 192.168.150.101
 # 保护模式
 protected-mode no
 # 数据库数量
 databases 1
 # 日志
 logfile /tmp/6379/run.log
  • 将文件拷贝到各个目录中

 echo 7001 7002 7003 8001 8002 8003 | xargs -t-n 1 cp redis.conf
  • 修改文件信息,将端口号修改为所在目录一致:

 printf '%s\n' 7001 7002 7003 8001 8002 8003 | xargs -I{} -t sed -i 's/6379/{}/g' {}/redis.conf 
  • 启动

 printf '%s\n' 7001 7002 7003 8001 8002 8003 | xargs -I{} -t redis-server {}/redis.conf 
  • 设定关系

 redis-cli --cluster create --ctuster-replicas 1 192.168.150.101:7001
 192.168.150.101:7002 192.168.150.101:7003 192.168.150.101:8001 192.168.150.101:8002 192.168.150.101:8003

查询集群状态

 redis-cli -p 7001 cluster nodes

散列插槽

  • redis会把每一个master节点映射到0-16383共16384个插槽上(hash slot)上,查看集群信息时就可以看到每一个master节点有一个slots信息

  • 数据根据哈希函数被映射到不同的插槽中存储。每个Redis节点负责管理一部分插槽,这样就实现了数据的分布式存储和负载均衡。

  • 当对一个散列键执行操作时,Redis会通过哈希函数计算出该键应该属于哪个插槽,并将其路由到负责该插槽的节点上

集群伸缩

  • 集群最重要的一点就是能够动态的添加节点和删除节点

添加节点
  • add-node命令

故障转移

自动转移

当一个集群中有一个master宕机以后会发生什么呢?

  • 1.首先是该实例与其他实例失去连接

  • 2.然后疑是宕机

  • 3.最后确定已经下线,自动提升一个新的slave为master

这是自动的转移

手动转移
  • 例如设备的升级

数据迁移
  • 利用cluster failover命令可以手动让集群中的某个master宕机,切换到执行cluster failover命令的这个slave节点,实现无感知的数据迁移

Java操作

  • redistemplate底层通过基于lettuce实现了分片集群的支持,而使用步骤与哨兵模式基本一致

  • 引入redis的starter依赖,配置分片集群地址,配置读写分离模式

只不过配置当时略有差异:

 spring:
 redis:
 cLuster:
 nodes: #指定分片集群的每一个节点信息
 - 192.168.150.101:7001
 - 192.168.150.101:7002
 - 192.168.150.101:7003
 - 192.168.150.101:8001
 - 192.168.150.101:8002
 - 192.168.150.101:8003
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值