学习笔记——redis

NoSql数据库简介

引入

解决功能问题:语言

解决扩展问题:框架

解决性能问题:NoSQL,Java线程,Hadoop,Nginx,MQ,ElasticSearch

概述

NoSql(not only sql)“不仅仅是数据库”,泛指非关系数据库。NoSql不依赖业务逻辑方式存储,而以简单的key-value模式存储,扩展性更强。

不遵循sql标准

不支持ACID(事务特性)

远超于sql标准

适用场景

对数据高并发的读写

对海量数据读写

对数据可扩展性

例子

Mecache(不能持久化,类型单一)

redis(数据存在内存中,支持持久化,一般作为缓存数据库辅助持久化数据库)

MongoDB

Redis概述及应用场景

Redis是用C语言开发的一个开源的高性能基于内存运行的键值对NoSQL数据库

特征
(1) 支持数据的持久化,可以将数据保存在磁盘中,重启之后可以再次加载到内存中使用
(2) 支持多种数据类型,除了KV类型的数据,还支持list、set、hash等数据结构
(3) 支持master-slave(主从同步)模式的数据备份

单线程多路IO复用

应用场景

  1. 热点数据加速查询(主要场景),如热点商品、热点信息等访问量较高的数据
  2. 即时信息查询,如公交到站信息、在线人数信息等
  3. 时效性信息控制,如验证码控制、投票控制等
  4. 分布式数据共享,如分布式集群架构中的session分离消息队列

安装

1.阿里云服务器

2.参考

尚硅谷 Redis 6入门到精通 04节 【尚硅谷】Redis 6 入门到精通 超详细 教程_哔哩哔哩_bilibili

(84条消息) 最新版Redis安装配置教程(Windows+Linux)_欢迎来到 Baret~H 的博客-CSDN博客_redis安装配置教程

操作目录

cd /usr/local/bin     redis-cli

不切换默认使用0号数据库

Redis五大数据类型

select 1 切换到数据库1

dbsize 查看当前数据库的数量

flushdb 清空数据库

Redis采用键值对存储数据,key永远是String类型,五大数据类型指的是value部分

key

keys * 查看当前所有key

exists key 判断某个key是否存在

type key 查看key类型

del key 删除指定key数据

ulink key 删除指定key数据(仅将keyspace元数据删除,真正删除在后续的异步中)

expire key 10:为给定的key设置过期时间10s

ttl key:查看还有多少秒过期,-1表示永不过期,-2表示已过期

string

是二进制安全的,可以包含任何数据,比如jpg图片,或者序列化对象.String是Redis最基本的 数据类型,一个String的value最大可支持512M

set mystr “hello world!” //设置字符串类型
get mystr //读取字符串类型

append 给原定的value末尾加value

strlen 获取·value长度

setnx 只有在key不存在时

incr 将key对的value+1,如果为空则新增value为1

decr将key存储的值减1,如果为空则新增value为-1

incrby/decrby <步长> 自定义incr,decr的步长

mset 设置多个key value

get range : name-lucyname get name 0 3 -> lucy

​ 获取value值的索引范围指定的部分内容

setex <过期时间> 设置键值的同时设置过期时间

数据结构:动态字符串,可以修改的字符串。类似java的ArrayList,本身可以修改还可以不断扩容。字符串小于1M,是双倍扩容。超过1M,是多每次扩1M.最大512M.

List

简介:单键多值,redis列表是字符串列表,可以添加到头或者尾。双向链表,对两端的操作性能高,对中间部分性能差。

lpush/rpush … :从右边加/从左边加

loop/roop 从value列表中从左/右 弹出一个值 值光键完

lrange 0 1 列出mylist中从编号0到倒数第一个元素

lrange 0 -1 列出mylist中从编号0到倒数第一个元素

lindex 索引下标获得的元素

llen 获得列表长度

linsertbefore :在的后面插入插入值

lrem :从左边删除n个value

lset :将key左侧索引index的值替换成value

数据结构:quicklist,多个压缩链表构成的双向链表。

set

相对于list多了自动去重的功能,set提供了判断某一元素是否在集合里的功能

sadd…

将一个或多个member加入集合key中,已存在则忽略。

smembers取出集合的所有值

sismember 将判断元素是否在集合中

scard 返回集合元素个数

srem…删除集合元素

sunion 并集

sinter 交集

数据结构

hashtable

hash

键值对集合,类似java中的map<String,Object>

hset 将key中的filed赋值

hget 将key中的fileid的值取出

hmset 批量设置hash的值

hexists key中的fileid是否存在

hkeys 列出集合所有的field

hvals 列出集合的values

hincrby 将field的值加上increment

数据结构

长度较短用ziplist(压缩链表),长度大用哈希表。

Zset

相对set,每个成员都关联了一个评分,这个评分被用来按照最低分到最高分排序。集合成员唯一,但评分可重复。

zadd 将元素和评分加入有序集中

zrange [WITHSCORES] 返回有序集中start到stop的元素

带WITHSCORES可以让分数一起和值返回到结果集

zrangebyscore [WITHSCORES] 返回分数范围的元素,小到大

zrevrangebyscore [WITHSCORES] 返回分数范围的元素,大到小

zincrby 给value加上increment

zrem 删除该集合下,指定值的元素

zcount 统计分段的元素

zrank 返回该值在集合中的排名(名次从0开始)

数据结构

1.hashtable

2.跳跃表,跳跃表的目的在于给元素value排序,根据score的范围获取元素列表。

新数据类型

Bitmaps

HyperLogLog

Geospatial

Jedis的使用

通过java来操作redis

方法

与linux操作命令几乎一样,只是把关键字提出来作为方法名,后续的key/value 按次序放在括号里

如 sadd key6 abc abc deh

 jedis.sadd("key6", "abc", "abc", "deh");

配置文件:

#最大连接数
redis.maxTotal=50
#默认开启的活跃连接数
redis.maxIdel=10
#Linux的ip地址
redis.host=114.55.55.160
#redis的端口号
redis.port=6379

工具类

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

import java.util.ResourceBundle;

public class JedisUtils {

    // 将从配置文件读取的配置信息赋予如下变量
    private static int maxTotal;
    private static int maxIdel;
    private static String host;
    private static int port; // 端口号为int类型

    // Jedis的连接池配置
    private static JedisPoolConfig jedisPoolConfig;

    // Jedis连接池
    private static JedisPool jedisPool;

    static {
        // 读取redis.properties配置文件
        ResourceBundle bundle = ResourceBundle.getBundle("redis");
        maxTotal = Integer.parseInt(bundle.getString("redis.maxTotal"));
        maxIdel = Integer.parseInt(bundle.getString("redis.maxIdel"));
        host = bundle.getString("redis.host");
        port = Integer.parseInt(bundle.getString("redis.port"));

        // Jedis连接池配置
        jedisPoolConfig = new JedisPoolConfig();
        jedisPoolConfig.setMaxTotal(maxTotal);
        jedisPoolConfig.setMaxIdle(maxIdel);
        jedisPool = new JedisPool(jedisPoolConfig, host, port);
    }

    public static Jedis getJedis() {
        return jedisPool.getResource();
    }
}

测试类
    import redis.clients.jedis.Jedis;

public class JedisTest {
    public static void main(String[] args) {
        // 1. 获取Jedis对象
        Jedis jedis = JedisUtils.getJedis();

        // 2. 执行操作,Jedis中操作的方法名与Linux中命令行工具中的指令同名
        jedis.sadd("key6", "abc", "abc", "deh");
        Long key1 = jedis.scard("key6");
        System.out.println("运行结果:" + key1);

        // 3.关闭连接
        jedis.close();
    }
}


配置问题

判断是否能连接到redis

cmd输入: telnet <端口号> ——不要用:

1.更改redis.config的配置

1.bind 127.0.0.1要注释掉

2.protected-mode :yes 改成no

3.配置改完要关闭redis-cli shutdown,再重新运行 redis-server /etc/redis.config

2.安全组是否开启端口

3.防火墙状态,可以给端口放行或者直接关闭

Springboot整合

配置文件

spring.redis.host=114.55.55.160
#????????
spring.redis.port=6379
#redis???????
spring.redis.password=
#redis?????????0?
spring.redis.database=0
#????????????????????
spring.redis.jedis.pool.max-wait=10000
#????????(???????)
spring.redis.jedis.pool.max-active=100
#???????????
spring.redis.jedis.pool.max-idle=20
#???????????
spring.redis.jedis.pool.min-idle=0
#??????
spring.redis.timeout=3000

配置类

package jedis;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
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.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {

        RedisTemplate<String, Object> template = new RedisTemplate<>();
        // 配置连接工厂
        template.setConnectionFactory(factory);

        //序列化和反序列化redis中的值
        Jackson2JsonRedisSerializer jackson = new Jackson2JsonRedisSerializer(Object.class);
        //Java对象转换成JSON结构
        ObjectMapper objectMapper = new ObjectMapper();
        // 指定要序列化的域
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        // 指定序列化输入的类型
        objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson.setObjectMapper(objectMapper);

        // 值采用json序列化
        template.setValueSerializer(jackson);
        //使用StringRedisSerializer来序列化和反序列化redis的key值
        template.setKeySerializer(new StringRedisSerializer());

        // 设置hash key和value序列化模式
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setHashValueSerializer(jackson);
        template.afterPropertiesSet();

        return template;
    }

}

serviceImpl

@Service("redisService")
public class RedisServiceIml implements RedisService {
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;


    public void redisString(String val) {
        redisTemplate.boundValueOps("StringKey").set(val);
        System.out.println("传值 "+val);
        ValueOperations ops = redisTemplate.opsForValue();
        String str3 = (String) ops.get("StringKey");
        System.out.println(str3);

    }
}

通过controller测试

@Controller
@RequestMapping("redis")
public class RedisController {

    @Autowired
    private RedisService redisService;

    @RequestMapping("/redisString")
    public void redisString(@RequestParam("id")String val)
    {
        redisService.redisString(val);
    }



}
RedisTemplate中API使用

可以在该博客查询

(85条消息) RedisTemplate操作Redis,这一篇文章就够了(一)_lydms的博客-CSDN博客_redistemplate

事务和锁机制

简介

Redis的事务是一个先进先出的队列,将一系列将要执行的指令包装成一个整体,当执行事务时,一次性的将命令按照添加顺序执行,中间不会被干扰或中断。

如果不开启事务,两个客户端操作同一个服务器时就会发生错误

基本操作

开启事务:multi

  • 执行此指令后,后续的指令都会被添加到事务队列中等待执行
  • 添加到队列中并不会立即执行,只有执行事务时命令才会被执行

执行事务:exec

  • 执行此指令后,事务队列中的任务会被依次执行
  • multi 成对使用

取消事务:discard

  • 终止当前事务的定义(发生在exec之前,也就是说还没有执行事务时终止事务)

image-20210606195352087

image-20210606195713520

WATCH

当某个事务需要按条件执行时,就要使用这个命令将给定的键设置为受监控的。

这个命令的运行格式如下所示:

WATCH key [key …]
这个命令的返回值是一个简单的字符串,总是OK。

防止事务冲突,乐观锁监听key是否修改,如果已经被别的线程修改,则不能进行事务操作

UNWATCH
清除所有先前为一个事务监控的键。

如果你调用了EXEC或DISCARD命令,那么就不需要手动调用UNWATCH命令。

这个命令的运行格式如下所示:

UNWATCH

悲观锁和乐观锁
悲观锁

总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。Java中synchronizedReentrantLock等独占锁就是悲观锁思想的实现。

乐观锁

总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法实现。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_condition机制,其实都是提供的乐观锁。在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。

乐观锁两种方式
版本号机制

一般是在数据表中加上一个数据版本号version字段,表示数据被修改的次数,当数据被修改时,version值会加一。当线程A要更新数据值时,在读取数据的同时也会读取version值,在提交更新时,若刚才读取到的version值为当前数据库中的version值相等时才更新,否则重试更新操作,直到更新成功。

举一个简单的例子:

假设数据库中帐户信息表中有一个 version 字段,当前值为 1 ;而当前帐户余额字段( balance )为 $100 。当需要对账户信息表进行更新的时候,需要首先读取version字段。

  1. 操作员 A 此时将其读出( version=1 ),并从其帐户余额中扣除 $50( $100-$50 )。
  2. 在操作员 A 操作的过程中,操作员B 也读入此用户信息( version=1 ),并从其帐户余额中扣除 $20 ( $100-$20)。
  3. 操作员 A 完成了修改工作,提交更新之前会先看数据库的版本和自己读取到的版本是否一致,一致的话,就会将数据版本号加( version=2 ),连同帐户扣除后余额( balance=$50),提交至数据库更新,此时由于提交数据版本大于数据库记录当前版本,数据被更新,数据库记录 version 更新为 2 。
  4. 操作员 B 完成了操作,提交更新之前会先看数据库的版本和自己读取到的版本是否一致,但此时比对数据库记录版本时发现,操作员 B
    提交的数据版本号为 2 ,而自己读取到的版本号为1 ,不满足 “ 当前最后更新的version与操作员第一次读取的版本号相等 “
    的乐观锁策略,因此,操作员 B 的提交被驳回。

这样,就避免了操作员 B 用基于 version=1 的旧数据修改的结果覆盖操作员A 的操作结果的可能。

CAS算法

CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。 如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值 。否则,处理器不做任何操作。无论哪种情况,它都会在 CAS 指令之前返回该 位置的值。(在 CAS 的一些特殊情况下将仅返回 CAS 是否成功,而不提取当前 值。)CAS 有效地说明了“我认为位置 V 应该包含值 A;如果包含该值,则将 B 放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可。”

通常将 CAS 用于同步的方式是从地址 V 读取值 A,执行多步计算来获得新 值 B(自旋),然后使用 CAS 将 V 的值从 A 改为 B。(比较和替换是一个原子操作)如果 V 处的值尚未同时更改,则 CAS 操作成功。

Redis事务特性

无ACID

单独的事务隔离:事务中的命令不会被别的命令打断

没有隔离级别概念:队列中的命令没有提交之前都不会实际的被执行,因为事务提交前任何指令都不会被实际执行,也就不存在”事务内的查询要看到事务里的更新,在事务外查询不能看到”这个让人万分头痛的问题 无I

不保证原子性:在事务运行期间,虽然Redis命令可能会执行失败,但是Redis仍然会执行事务中余下的其他命令,而不会执行回滚操作 无A

redis单线程为什么会有并发问题

多个线程的命令redis串行化执行,但每个线程可能有多段请求,这就涉及到并发问题了。多个客户端线程依旧存在线程竞争,有并发问题。

例子:

ab 两个线程

第一次请求库存情况: a先得到剩余库存为1 b再得到剩余库存为1

​ a判断库存>0 b判断库存>0

第二次请求进行购买: a先库存-1 b再库存-1

每个请求都是串行化,但整体存在了并发问题。

所以不需要纠结单线程,在业务处理中正常考虑并发。

并发解决方案

乐观锁CAS。watch监视key,将key作为预估值,每当线程进行修改时,检测key是否变化,变化了key更新为当前的,继续重复步骤。如果没变化,则数据没被其他线程修改,则可以修改了

分布式锁

Redis持久化操作-RDB

简介

Redis 是基于内存的 KV 数据库,使用时所有数据都在内存中,这就是它存取性能高的重要原因之一。但是如我们所知,保存在内存中的数据是有风险的,一旦机器停电或者意外宕机就会彻底丢失。Redis 持久化机制就是为了应对这种情况,其主要手段是将内存中的数据保存到磁盘上,宕机重启后再通过磁盘上的持久化文件将数据恢复。RDB 是 Redis 持久化机制的一种,它会将内存中的所有数据进行快照保存,并且以二进制文件形式存储到硬盘上

原理

Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写进一个临时文件中,等到持久化过程结束了,再用这个临时文件替换上次持久化好的文件。在这个过程中,只有子进程来负责IO操作,主进程仍然处理客户端的请求,这就确保了极高的性能。
在默认情况下, Redis 将数据库快照保存在名字为 dump.rdb 的二进制文件中。通过触发快照的形式,来做到将指定时间间隔内的数据持久化到dump.rdb。例如,可以2分钟内持久化一次,将对数据库的写操作,备份到磁盘上的dump.rdb。如何触发持久化呢?可以通过查看或者设置redis.conf配置文件来指定触发规则。

img

优缺点

优点

1.RDB 文件保存的是 Redis 在某个时间点的数据集,二进制形式使其占用磁盘空间很小,非常适于备份和灾难恢复
2.生成RDB文件的时候,Redis 主进程可以 fork(复制出一个和当前进程一样的进程) 一个子进程来处理所有保存工作,主进程可以继续提供服务,不需要进行任何磁盘IO操作
3.RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快,这是因为 RDB 文件直接加载进内存就可以恢复数据,AOF 文件加载进内存的是一条条 Redis 命令,需要执行后才能恢复数据

缺点

1.RDB 需要保存内存中整个数据集,通常这个操作是很耗时的,因此一般保存 RDB 文件的操作间隔是比较长的。在这种情况下,一旦发生故障停机,此前没有来得及写入磁盘的数据都将丢失。也就是说,RDB 机制的数据可靠性比 AOF 低,可能丢失的数据更多
2.每次保存 RDB 的时候,Redis 可以 fork 出一个子进程来进行实际的持久化工作,但在数据集比较庞大时,fork 操作本身可能就非常耗时,从而导致 fork 完成前主进程无法处理任何请求

fork

复制出一个和当前进程一样的进程,使用同一个物理的内存。linux引入了写时复制技术

RDB触发方式

在这里插入图片描述

save 命令触发

Redis 服务端接收到 save 命令时会在主线程中进行保存 RDB 文件的操作,需注意 Redis 对业务逻辑的处理都在主线程中串行执行,因此如果 RDB 需要保存的数据量大耗时长的话,会影响服务端对其他客户端命令的处理响应,不建议使用

bgsave 命令触发

Redis 服务端接收到 bgsave 命令时会 fork 一个子进程进行后台保存 RDB 文件的操作,这样主线程依然可以处理其他客户端命令,不影响线上使用

定时任务触发

Redis 的server.c#serverCron() 定时任务会检查一定时间内数据变动是否超过配置文件中配置的值,如果超过则会触发 rdb.c#rdbSaveBackground()函数使用子进程后台完成 RDB 文件的保存

主从同步全量复制触发

Redis 6.0 源码阅读笔记(10)-主从复制 Master 节点流程分析 一文中提到过,当从节点连接上主节点的时候,主节点会根据从节点携带的参数判断是否能够进行部分复制,不能进行部分复制则需要后台保存 RDB 进行全量复制。此处保存 RDB 的方式与 bgsave 命令的处理基本一致,都是父进程 fork 子进程,由子进程进行 RDB 数据的保存工作,不过后续对 RDB 数据的处理有两种不同的方式,下文将详细介绍

RDB处理流程
RDB 文件传输

主要介绍主从同步全量复制触发保存 RDB 的流程,事实上其他几种 fork 子进程保存 RDB 的处理也与此类似

在这里插入图片描述

RDB 涉及的技术原理

写时复制 Copy On Write
Redis 主进程可以 fork 一个子进程来执行 RDB 持久化,二者共享同一份内存空间。fork 操作之后主进程可以继续对外提供服务,那么必然存在对内存的写操作,如果共享的内存数据因此发生改变,那子进程保存的 RDB 就不能称为数据快照。因此,这其中必然有一种机制保证了主进程修改的内存数据对子进程不可见,其实这就采用了 Copy On Write 技术
在这里插入图片描述

fork 操作之后,因为快照机制不可以改变原先的物理内存。内核会把父进程中所有的内存页都设为只读权限,然后将子进程的地址空间指向父进程。当其中某个进程写内存时,CPU 检测到内存页是只读的,于是触发页异常中断(page-fault),从而进入内核态。内核会把触发异常的页复制一份分配给写内存的进程,于是这个进程可以在复制的页上进行写操作,而不会更改共享的内存数据

Redis持久化操作-AOF

简介

以日志的形式记录Redis每一个写操作,将Redis执行过的所有写指令记录下来(读操作不记录),只许追加文件不可以改写文件,redis启动之后会读取appendonly.aof文件来实现重新恢复数据,完成恢复数据的工作。默认不开启,需要将redis.conf中的appendonly no改为yes启动Redis。

原理

Redis的AOF是如何做到持久化的呢?从配置文件中,我们可以发现

appendfsync always:每修改同步,每一次发生数据变更都会持久化到磁盘上,性能较差,但数据完整性较好。

appendfsync everysec: 每秒同步,每秒内记录操作,异步操作,如果一秒内宕机,有数据丢失。

appendfsync no:不同步。

优缺点

优点

AOF文件是一个只进行追加操作的日志文件,对文件写入不需要进行seek,即使在追加的过程中,写入了不完整的命令(例如:磁盘已满),可以使用redis-check-aof工具可以修复这种问题

Redis可以在AOF文件变得过大时,会自动地在后台对AOF进行重写:重写后的新的AOF文件包含了恢复当前数据集所需的最小命令集合。整个重写操作是绝对安全的,因为Redis在创建AOF文件的过程中,会继续将命令追加到现有的AOF文件中,即使在重写的过程中发生宕机,现有的AOF文件也不会丢失。一旦新AOF文件创建完毕,Redis就会从旧的AOF文件切换到新的AOF文件,并对新的AOF文件进行追加操作。

AOF文件有序地保存了对数据库执行的所有写入操作。这些写入操作一Redis协议的格式保存,易于对文件进行分析;例如,如果不小心执行了FLUSHALL命令,但只要AOF文件未被重写,通过停止服务器,移除AOF文件末尾的FLUSHALL命令,重启服务器就能达到FLUSHALL执行之前的状态。

缺点

对于相同的数据集来说,AOF文件要比RDB文件大。
根据所使用的持久化策略来说,AOF的速度要慢与RDB。一般情况下,每秒同步策略效果较好。不使用同步策略的情况下,AOF与RDB速度一样快。

RDB和AOF同时存在

重启Redis时,如果dump.rdb与appendfsync.aof同时都存在时,Redis会自动读取appendfsync.aof文件,通过该文件中对数据库的日志操作,来实现数据的恢复。当然如果该文件被破坏,我们可以通过redis-check-aof工具来修复,如redis-check-aof --fix能修复破损的appendfsync.aof文件,当然如果dump.rdb文件有破损,我们也可以用redis-check-rdb工具来修复,如果appendfsync.aof文件破损了,是启动不客户端的,也就是无法完成数据的恢复。

重写

当然如果AOF 文件一直被追加,这就可能导致AOF文件过于庞大。因此,为了避免这种状况,Redis新增了重写机制,当AOF文件的大小超过所指定的阈值时,Redis会自动启用AOF文件的内容压缩,只保留可以恢复数据的最小指令集,可以使用命令bgrewiteaof。

Redis主从复制

主机数据更新后台根据配置和策略,自动同步到备机的master/slaver机制,master(主服务器)以写为主,Slaver(从服务器·)以读为主。

好处

1.读写分离

2.容灾的快速恢复:一台服务器宕机快速切换到另一个服务器

主从复制原理

1.当从服务器连到主服务器后,从服务区向主服务器发送进行数据同步消息。

2.主服务器接到从服务器发送过来同步消息,把主服务器数据进行持久化rdb文件,把rbd文件发送到从服务器,从服务器拿到rbd进行读取。

3.每次主服务器进行写操作主动向从服务器进行数据同步

全量复制:slave接收到数据库数据文件,将其存在内存

增量复制:master将新的修改命令依次传给slave,完成同步

一主二仆

img

一个Master,两个Slave,Slave只能读不能写;当Slave与Master断开后需要重新slave of连接才可建立之前的主从关系;Master挂掉后,Master关系依然存在,Master重启即可恢复。

薪火相传

上一个Slave可以是下一个Slave的Master,Slave同样可以接收其他slaves的连接和同步请求,那么该slave作为了链条中下一个slave的Master,如此可以有效减轻Master的写压力。如果slave中途变更转向,会清除之前的数据,重新建立最新的。

反客为主

当Master挂掉后,Slave可键入命令 slaveof no one使当前redis停止与其他Master redis数据同步,转成Master redis。

哨兵模式

反客为主的自动版,后台监控主服务器是否故障,如果故障了根据投票数自动将从库转为主库。

哨兵模式的主要功能

监控: 哨兵会不断地检查主节点和从节点是否运作正常
自动故障转移: 当主节点不能正常工作时,哨兵会开始自动故障转移操作,它会将失效主节点的其中一个从节点升级为新的主节点,并让其他从节点改为复制新的主节点。
配置提供者: 客户端在初始化时,通过连接哨兵来获得当前Redis服务的主节点地址。
通知: 哨兵可将故障转移的结果发送给客户端。

实例演示

哨兵模式中有两种类型的节点:
哨兵节点: 哨兵系统有一个或多个哨兵节点组成,哨兵节点是特殊的Redis节点,不存储数据。
数据节点: 主节点和从节点都是数据节点。

Redis集群

容量不够redis如何扩容,并发操作redis如何分摊?

可以用代理主机解决,推荐无中心化集群(任何服务器都可以作为集群入口)。

应用问题

缓存雪崩

是指缓存同一时间大面积的失效,所以,后面的请求都会落到数据库上,造成数据库短时间内承受大量请求而崩掉。如在某一个时间段,缓存的key大量集中同时过期了,所有的请求全部冲到持久层数据库上,导致持久层数据库挂掉!
范例:双十一零点抢购,这波商品比较集中的放在缓存,设置了失效时间为1个小时,那么到了零点,这批缓存全部失效了,而大量的请求过来时,全部冲过了缓存,冲到了持久层数据库!

解决方案

缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
一般并发量不是特别多的时候,使用最多的解决方案是加锁排队。
给每一个缓存数据增加相应的缓存标记,记录缓存的是否失效,如果缓存标记失效,则更新数据缓存。

缓存穿透

缓存穿透是指缓存和数据库中都没有的数据,导致所有的请求都落到数据库上,造成数据库短时间内承受大量请求而崩掉。如用户需要查询一个数据,但是redis中没有(比如说mysql中id=-1的数),直接去请求MySQL,当很多用户同时请求并且都么有命中!于是都去请求了持久层的数据库,那么这样会给持久层数据库带来非常大的压力。一般出现这样的情况都不是正常用户,基本上都是恶意用户(普通用户看到的都是数据库存在的)!

解决方案

接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截;
从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击
采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的 bitmap 中,一个一定不存在的数据会被这个 bitmap 拦截掉,从而避免了对底层存储系统的查询压力
附加

对于空间的利用到达了一种极致,那就是Bitmap和布隆过滤器(Bloom Filter)。
Bitmap: 典型的就是哈希表
缺点是,Bitmap对于每个元素只能记录1bit信息,如果还想完成额外的功能,恐怕只能靠牺牲更多的空间、时间来完成了。

布隆过滤器(推荐)

就是引入了k(k>1)k(k>1)个相互独立的哈希函数,保证在给定的空间、误判率下,完成元素判重的过程。
它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。
Bloom-Filter算法的核心思想就是利用多个不同的Hash函数来解决“冲突”。
Hash存在一个冲突(碰撞)的问题,用同一个Hash得到的两个URL的值有可能相同。为了减少冲突,我们可以多引入几个Hash,如果通过其中的一个Hash值我们得出某元素不在集合中,那么该元素肯定不在集合中。只有在所有的Hash函数告诉我们该元素在集合中时,才能确定该元素存在于集合中。这便是Bloom-Filter的基本思想。
Bloom-Filter一般用于在大数据量的集合中判定某元素是否存在。

缓存击穿

缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力。如一个非常热点的key,在不停的扛着大并发,当这个key失效时,一瞬间大量的请求冲到持久层的数据库中,就像在一堵墙上某个点凿开了一个洞!和缓存雪崩不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。

解决方案

设置热点数据永远不过期。
加互斥锁,互斥锁

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值