redis高级

1.redis持久化机制

redis提供了两种持久化策略

1.1RDB

RDB的持久化策略: 按照规则定时把内存的数据同步到磁盘 redis/bin/dump.rdb
snapshot
redis在指定的情况下会触发快照
1)自己配置的快照规则

save <seconds> <changes>
save 900 1  当在900秒内被更改的key的数量大于1的时候,就执行快照
save 300 10
save 60 10000

2)save或者bgsave

  • save: 执行内存的数据同步到磁盘的操作,这个操作会阻塞客户端的请求
  • bgsave: 在后台异步执行快照操作,这个操作不会阻塞客户端的请求
    3)执行flushall的时候
    清除内存的所有数据,只要快照的规则不为空,也就是第一个规则存在。那么redis会执行快照

4)执行复制的时候

1.1.1 快照的实现原理

1:redis使用fork函数复制一份当前进程的副本(子进程)
2:父进程继续接收并处理客户端发来的命令,而子进程开始将内存中的数据写入硬盘中的临时文件
3:当子进程写入完所有数据后会用该临时文件替换旧的RDB文件,至此,一次快照操作完成。  
 注意:redis在进行快照的过程中不会修改RDB文件,只有快照结束后才会将旧的文件替换成新的,也就是说任何时候RDB文件都是完整的。 这就使得我们可以通过定时备份RDB文件来实现redis数据库的备份, RDB文件是经过压缩的二进制文件,占用的空间会小于内存中的数据,更加利于传输。

1.1.2 RDB的优缺点

  1. 使用RDB方式实现持久化,一旦Redis异常退出,就会丢失最后一次快照以后更改的所有数据。这个时候我们就需要根据具体的应用场景,通过组合设置自动快照条件的方式来将可能发生的数据损失控制在能够接受范围。如果数据相对来说比较重要,希望将损失降到最小,则可以使用AOF方式进行持久化
  2. RDB可以最大化Redis的性能:父进程在保存RDB文件时唯一要做的就是fork出一个子进程,然后这个子进程就会处理接下来的所有保存工作,父进程无需执行任何磁盘I/O操作。同时这个也是一个缺点,如果数据集比较大的时候,fork可能比较耗时,造成服务器在一段时间内停止处理客户端的请求;

1.2 AOF

AOF可以将Redis执行的每一条写命令追加到硬盘文件中,这一过程显然会降低Redis的性能,但大部分情况下这个影响是能够接受的,另外使用较快的硬盘可以提高AOF的性能

1.2.1 实践

默认情况下Redis没有开启AOF(append only file)方式的持久化,可以通过修改redis.conf中的appendonly yes ,开启AOF持久化后每执行一条会更改Redis中的数据的命令后,Redis就会将该命令写入硬盘中的AOF文件。AOF文件的保存位置和RDB文件的位置相同,都是通过dir参数设置的,默认的文件名是apendonly.aof. 可以在redis.conf中的属性 appendfilename appendonlyh.aof修改

如下两个参数可以去对aof文件做优化(压缩)
auto-aof-rewrite-percentage 100 表示当前aof文件大小超过上一次aof文件大小的百分之多少的时候会进行重写。如果之前没有重写过,以启动时aof文件大小为准
auto-aof-rewrite-min-size 64mb 限制允许重写最小aof文件大小,也就是文件大小小于64mb的时候,不需要进行优化

1.2.2 aof重写的原理

Redis 可以在 AOF 文件体积变得过大时,自动地在后台对 AOF 进行重写: 重写后的新 AOF 文件包含了恢复当前数据集所需的最小命令集合。 整个重写操作是绝对安全的,因为 Redis 在创建新 AOF 文件的过程中,会继续将命令追加到现有的 AOF 文件里面,即使重写过程中发生停机,现有的 AOF 文件也不会丢失。 而一旦新 AOF 文件创建完毕,Redis 就会从旧 AOF 文件切换到新 AOF 文件,并开始对新 AOF 文件进行追加操作。AOF 文件有序地保存了对数据库执行的所有写入操作, 这些写入操作以 Redis 协议的格式保存, 因此 AOF 文件的内容非常容易被人读懂, 对文件进行分析(parse)也很轻松

1.2.3 同步磁盘数据

redis每次更改数据的时候, aof机制都会将命令记录到aof文件,但是实际上由于操作系统的缓存机制,数据并没有实时写入到硬盘,而是进入硬盘缓存。再通过硬盘缓存机制去刷新到保存到文件

appendfsync always 每次执行写入都会进行同步 , 这个是最安全但是是效率比较低的方式

appendfsync everysec 每一秒执行

appendfsync no 不主动进行同步操作,由操作系统去执行,这个是最快但是最不安全的方式

1.2.4 aof文件损坏以后如何修复

服务器可能在程序正在对 AOF 文件进行写入时停机, 如果停机造成了 AOF 文件出错(corrupt), 那么 Redis 在重启时会拒绝载入这个 AOF 文件, 从而确保数据的一致性不会被破坏。
当发生这种情况时, 可以用以下方法来修复出错的 AOF 文件:

  1. 为现有的 AOF 文件创建一个备份。
  2. 使用 Redis 附带的 redis-check-aof 程序,对原来的 AOF 文件进行修复。
    redis-check-aof --fix
    重启 Redis 服务器,等待服务器载入修复后的 AOF 文件,并进行数据恢复。

1.3 RDB 和 AOF ,如何选择

一般来说,如果对数据的安全性要求非常高的话,应该同时使用两种持久化功能。如果可以承受数分钟以内的数据丢失,那么可以只使用 RDB 持久化。有很多用户都只使用 AOF 持久化, 但并不推荐这种方式: 因为定时生成 RDB 快照(snapshot)非常便于进行数据库备份, 并且 RDB 恢复数据集的速度也要比 AOF 恢复的速度要快 。
两种持久化策略可以同时使用,也可以使用其中一种。如果同时使用的话, 那么Redis重启时,会优先使用AOF文件来还原数据

2.集群

2.1 复制(master、slave)

image1.png | center | 671x411

2.1.1 配置过程

1)修改11.140和11.141的redis.conf文件,增加slaveof masterip masterport
slaveof 192.168.11.138 6379

2)启动后查看当前节点信息 info replication

3)slave监控同步信息

#监听
replconf listening-port 6379
#同步
sync

4)其他配置

slave-serve-stale-data yes #避免读取脏数据

2.1.2 实现原理

  1. slave第一次或者重连到master上以后,会向master发送一个SYNC的命令
  2. master收到SYNC的时候,会做两件事
    1. 执行bgsave(rdb的快照文件)
    2. master会把新收到的修改命令存入到缓冲区
      缺点:没有办法对master进行动态选举

2.1.3 复制的方式

  1. 基于rdb文件的复制(第一次连接或者重连的时候)
  2. 无硬盘复制(需要配置repl-diskless-sync)
  3. 增量复制 PSYNC master run id. offset

2.2 哨兵机制

2.2.1 简介

sentinel

  1. 监控master和salve是否正常运行
  2. 如果master出现故障,那么会把其中一台salve数据升级为master

image.png | left | 589x404

2.2.2 配置

#复制配置文件,在redis目录
cp ./sentinel.conf ../redis/sentinel.conf
#配置修改 ip,端口,投票数 三个哨兵,至少要1个哨兵投票
sentinel monitor mymaster 192.168.98.165 6379 1
#启动
./redis-sentinel ../sentinel.conf

2.3 集群(redis3.0以后的功能)

image.png | left | 737x420

2.3.1 集群的原理

Redis Cluster中,Sharding采用slot(槽)的概念,一共分成16384个槽,这有点儿类似前面讲的pre sharding思路。对于每个进入Redis的键值对,根据key进行散列,分配到这16384个slot中的某一个中。使用的hash算法也比较简单,就是CRC16后16384取模。Redis集群中的每个node(节点)负责分摊这16384个slot中的一部分,也就是说,每个slot都对应一个node负责处理。当动态添加或减少node节点时,需要将16384个槽做个再分配,槽中的键值也要迁移。当然,这一过程,在目前实现中,还处于半自动状态,需要人工介入。Redis集群,要保证16384个槽对应的node都正常工作,如果某个node发生故障,那它负责的slots也就失效,整个集群将不能工作。为了增加集群的可访问性,官方推荐的方案是将node配置成主从结构,即一个master主节点,挂n个slave从节点。这时,如果主节点失效,Redis Cluster会根据选举算法从slave节点中选择一个上升为主节点,整个集群继续对外提供服务。这非常类似服务器节点通过Sentinel监控架构成主从结构,只是Redis Cluster本身提供了故障转移容错的能力。

slot(槽)的概念,在redis集群中一共会有16384个槽,
根据key 的CRC16算法,得到的结果再对16384进行取模。 假如有3个节点
node1 0 5460
node2 5461 10922
node3 10923 16383
节点新增
node4 0-1364,5461-6826,10923-12287
删除节点
先将节点的数据移动到其他节点上,然后才能执行删除

2.3.2 市面上提供了集群方案

  1. redis shardding 而且jedis客户端就支持shardding操作 SharddingJedis ; 增加和减少节点的问题; pre shardding
    • 3台虚拟机 redis 。但是我部署了9个节点 。每一台部署3个redis增加cpu的利用率
    • 9台虚拟机单独拆分到9台服务器
  2. codis基于redis2.8.13分支开发了一个codis-server
  3. twemproxy twitter提供的开源解决方案

3.解决方案

3.1 redis缓存的更新

1)先删除缓存,再更新数据(问题:在删除缓存到更新数据库的时间内,有请求读取进来,这时候会有脏数据)
2)先更新数据库,更新成功后,让缓存失效(解决了上面的问题)
3)更新数据的时候,只更新缓存,不更新数据库,然后异步调度去批量更新数据库

3.2 缓存击穿

3.2.1 缓存穿透

缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中时需要从数据库查询,查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到数据库去查询,造成缓存穿透。

解决方案:布隆过滤

3.2.2 缓存雪崩

缓存雪崩是指在我们设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到DB,DB瞬时压力过重雪崩。

解决方案:可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。

3.2.3 缓存失效

对于一些设置了过期时间的key,如果这些key可能会在某些时间点被超高并发地访问,是一种非常“热点”的数据。这个时候,需要考虑一个问题:缓存被“击穿”的问题,这个和缓存雪崩的区别在于这里针对某一key缓存,前者则是很多key。

缓存在某个时间点过期的时候,恰好在这个时间点对这个Key有大量的并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。

解决方案:
1.使用互斥锁(mutex key)
业界比较常用的做法,是使用mutex。简单地来说,就是在缓存失效的时候(判断拿出来的值为空),不是立即去load db,而是先使用缓存工具的某些带成功操作返回值的操作(比如Redis的SETNX或者Memcache的ADD)去set一个mutex key,当操作返回成功时,再进行load db的操作并回设缓存;否则,就重试整个get缓存的方法。
SETNX,是「SET if Not eXists」的缩写,也就是只有不存在的时候才设置,可以利用它来实现锁的效果

  1. "提前"使用互斥锁(mutex key):
    在value内部设置1个超时值(timeout1), timeout1比实际的memcache timeout(timeout2)小。当从cache读取到timeout1发现它已经过期时候,马上延长timeout1并重新设置到cache。然后再从数据库加载数据并设置到cache中
  2. “永远不过期”:  
    这里的“永远不过期”包含两层意思:

(1) 从redis上看,确实没有设置过期时间,这就保证了,不会出现热点key过期问题,也就是“物理”不过期。
(2) 从功能上看,如果不过期,那不就成静态的了吗?所以我们把过期时间存在key对应的value里,如果发现要过期了,通过一个后台的异步线程进行缓存的构建,也就是“逻辑”过期

3.3 session共享

3.3.1 cookies机制

用户状态存储在客户端:状态存储在客户端不安全

解决方案:cookies加密

3.3.2 session机制

用户状态存储在服务端,tomcat自带的session会话机制

集群部署方案:
session复制共享
session统一存储

3.3.3 token机制(JTW)

服务端生成一个token,写入浏览器的cookies里面;浏览器与服务端的交互通过cookies里面的token进行交互

1)token+redis:客户端保存token,token无用户信息,根据token去redis读取用户信息

2)token+jwt:客户端保存token,token存储了加密后的用户信息
json token web:客户端与服务端安全认证的一个解决方案
jwt由三部分组成(头+肚子+尾巴)
Header: 标题包含了令牌的元数据,并且在最小包含签名和/或加密算法的类型
PayLoad:载荷,包含基础信息与自定义Claims信息,Claims包含您想要签署的任何信息
Signature: 在header中指定的使用该算法的数字签名和声明

java中使用jwt

<dependency>
  <groupId>io.jsonwebtoken</groupId>
  <artifactId>jjwt</artifactId>
  <version>0.9.0</version>
</dependency>
 private static Key getKeyInstanse(){
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
        byte[] secretBytes = DatatypeConverter.parseBase64Binary("JWT-TOKEN");
        Key signingKey = new SecretKeySpec(secretBytes, signatureAlgorithm.getJcaName());
        return signingKey;
    }

    public static String createToken() {

        Map<String, Object> claims = new HashMap<String, Object>();
        claims.put("username", "token");
        claims.put("role", "admin");
        JwtBuilder builder = Jwts.builder().setClaims(claims)
                .setId("tokenid")
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis()+10*60*1000))
                .signWith(SignatureAlgorithm.HS256, getKeyInstanse());

        return builder.compact();
    }

    public static Claims parseToken(String token) {
        return Jwts.parser().setSigningKey(getKeyInstanse())
                .parseClaimsJws(token).getBody();
    }

    public static void validateToken(String token) {
        try{
            Claims claims = parseToken(token);
            String username = claims.get("username").toString();
            String role = claims.get("role").toString();
            String tokenid = claims.getId();
            System.out.println("[username]:"+username);
            System.out.println("[role]:"+role);
            System.out.println("[tokenid]:"+tokenid);
            System.out.println("[claims]:"+claims);
        } catch(ExpiredJwtException e) {
            System.out.println("token expired");
        } catch (InvalidClaimException e) {
            System.out.println("token invalid");
        } catch (Exception e) {
            System.out.println("token error");
        }
    }

    public static void main(String[] args) {
        validateToken(createToken());
    }

4.参考

缓存穿透,缓存击穿,缓存雪崩解决方案分析
JWT全面解读、使用步骤

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值