redis学习笔记


redis

NoSql数据库

1、技术分类

  • 解决功能性的问题:java,jsp,RDBMS,tomcat,html,linux,JDBC,SVN
  • 解决扩展性的问题:Struts,Spring,SpringMVC,Hibernate,Mybatis
  • 解决性能的问题:NoSQL,java线程,hadoop,Nginx,MQ,ElasticSearch

nosql能够减少cpu和io的压力,通过内存进行读取

nosql作为缓存使用减少io的读操作

2、概述

NoSqL(No Only SQL)泛指非关系数据库,不依赖业务逻辑方式存储,而是以简单的key-value模式存储,因此大大的增加了数据库的扩展能力

特点:

  • 不遵循SQL的标准

  • 不支持ACID(原子性、一致性、隔离性、持久性)

  • 远超SQL的性能

适用场景

  • 对数据高并发额读写
  • 海量数据的读写
  • 对数据高可扩展性的

不适用场景

  • 需要事务支持
  • 基于sql的结构化查询,处理复杂的关系,需要即席查询
  • 用不着sql和用了sql也不行的情况,适用NoSQL

安装redis

1、将redis-6.2.6.tar.gz文件远程传输到/opt目录下

2、安装C语言的编译环境

可以使用下面的命令安装C语言的编译环境

yum install centos-release-scl scl-utils-build
yum install -y devtoolset-8-toolchain
scl enable dectoolset-8 bash

也可以安装gcc版本

yum install gcc

原本虚拟机安装会带有gcc,可以使用命令查看是否安装

gcc --version

image-20211224170703444

3、解压redis压缩包

tar -zxvf redis-6.2.6.tar.gz

4、解压完成后进入目录

cd redis-6.2.6/

make命令编译

make

安装

make install

image-20211224171600578

查看安装目录:/usr/loacl/bin,安装成功

cd /usr/loacl/bin

image-20211224171814845

redis-benchmark:性能测试工具,可以在自己本子进行,看看自己机子的性能如何

redis-check-aof:修复有问题的AOF文件

redis-check-dump:修复有问题的dump.rdb文件

redis-sentinel:redis集群使用

redis-server:redis服务器启动命令

redis-cli:客户端,操作入口

启动redis服务

前台启动

redis-server

前台启动,命令行窗口不能关闭,否则服务器停止

后台启动

打开redis-6.2.6/目录,将redis.conf复制到/etc目录下

cp redis.conf /etc/redis.conf

设置 daemonize no 改成yes

vi redis.conf

找到 daemonize no换成yes

启动redis

cd /usr/local/bin
redis-server /etc/redis.conf

查看进程是否启动

ps -ef | grep redis

image-20211224174423468

用客户端访问:

redis-cli

测试访问:

ping

image-20211224174732170

连接客户端

开启服务后,查看redis进行是否开启

ps -ef | grep redis

之后还是无法连接需要关闭防火墙

systemctl stop firewalld

关闭redis

shutdown

或者用kill命令直接杀死进程

kill -9 5780

redis相关介绍

1、默认使用16个数据库,类似数组下标从0开始,初始默认使用0号库

2、使用命令select <dbid> 来切换数据库,如select 9

3、统一密码管理,所有库同样密码,命令config get requirepass查看初始密码,

修改密码config set requirepass 123456

然后密码验证auth 123456,

查询修改后的密码config get requirepass

4、dbsize查看当前数据库的key数量

5、flushdb清空当前库

6、flushall通杀全部库

redis是单线程+多路IO复用技术

多路复用是指使用一个线程来检查多个文件描述符(Socket)的就绪状态,比如吊桶select和poll函数,转入多个文件描述符,如果有一个文件描述符就绪,则返回,否则堵塞知道超时。得到就绪状态后进行真正的操作可以在同一个线程里执行,也可以启动线程执行

redis键(key)

查看当前库所有key

keys *

判断某个可以是否存在

exists key

查看你的key是什么类型

type key

删除指定key数据

del key

根据value选择非堵塞删除

unlink key

仅将keys从keyspace元数据中删除,真正的删除会在后续的异步删除中

给定的key设置过期时间

expire key 10

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

ttl key

常用数据类型

redis字符串(String)

基本介绍

1、String是Redis最基本的数据类型,可以理解成Mencached一模一样的类型,一个key对应一个value

2、String类型是二进制安全的,意味着redis的String可以包含任何的数据,比如jpg图片

3、一个redis中字符串value最多是512M

基本命令

get < key >查询对应键值

append < key >< value >给定的< value >追加到原值的末尾

strlen < key >获得值的长度

setnx < key >< value >只有在key不存在时,设置key的值

incr < key >< value >将key中存储的值加一

decr < key >< value >将key中存储的值减一

incrby/decrby < key ><步长>将key中存储的数字值增减,自定义长度

mset < key1 > < value1 > < key2 > < value2 >….中同时设置一个或多 个key-value对。

mget< key1 > < key2 > < key3 > ….同时获取一个或多个value。

mseitnx < key1 > < value1 > < key2 > < value2 >…同时设置一个或多个 key-value对,当且仅当所有给定key都不存在。原子性,有一个失败都失效

getrange < key > <起始位置> <结束位置>
获得值的范围,类似java中的substring ,前包,后包
setrange < key > <起始位置> < value >
用< value >覆写< key >所储存的字符串值,从<起始位置:开始(索引从0开始)

setex < key> <过期时间>< value >
设置键值的同时,设置过期时间,单位秒。。
getset < key > < value >.
以新换旧,设置了新值同时获得旧值。。

redis列表(List)

基本介绍

1、单键多值

2、redis列表是简单的字符串列表,按照插入排序,可以添加一个元素到列表的头部或者尾部

3、他的底层实际是双向链表。对两端的操作性能很高,通过索引下标的操作中间节点性能会较差

常用命令

ipush/rpush < key >< value1 >< value2 >….从左边/右边插入一个值

lpop/rpop < key > 从左边/右边吐出一个值

rpoplpush < key1 >< key2 >从< key1 >列表右边吐出一个值,插到< key2 >列表左边

irange < key >< start >< stop >按照索引下标获得元素(从左往右)

lrange mylist 0-1 0左边第一 个, -1右边第一个,( 0-1表示获取所有)

lindex < key > < index >按照索引下标获得元素(从左到右)

llen < key >获得列表长度。

linsert < key > before < value > < newvalue > 在< value >的后面插入<newvalue插入值

Irem < key > < n > < value >从左边删除n个value(从左到右)

lset< key > < index > < value >将列表key下标为index的值替换成value .

redis集合(set)

基本介绍

redis set对外提供的功能与list类似是一个列表的功能,特殊之处在于set是可以自动排重的,当需要存储一个列表数据,又不希望出现重复数据时,set是一个很好的选择,并且set还提供了判断某个成员是否在一个set集合内的重要接口,这个接口也是list不能提供的

redis的set是string类型的无序集合,底层是一个value为null的hash表,所以增加、删除、查找的复杂度都是O(1)

常用命令

sadd < key > < value1 > < value2 > … 将一个或多个member元素加入到集合key中,已经存在的member元素将被忽略

smembers < key >取出该集合的所有值

sismember < key > < value >判断集合< key >是否为含有该< value >值,有1 ,没有0。

scard< key >返回该集合的元素个数

srem < key > < value1 > < value2 > …. 删除集合中的某个元素

spop < key >随机从该集合中吐出一个值。

srandmember < key > < n > 随机从该集合中取出n个值,不会从集合中删除

smovle < source > < destination >value把集合中一个值从一个集合移动到另一个集合

sinter < key1 > < key2 >返回两个集合的交集元素中

sunion < key1 > < key2 >返回两个集合的并集元素

sdiff < key1 > < key2 >返回两个集合的差集元素(key1中的,不包含key2中的)

redis哈希(hash)

基本介绍

redis hash 是一个键值对集合

redis hash是一个string类型的field和value的映射表,hash适合存储对象

常用命令

hset < key >< field >< value >给< key >集合中的 < field >键赋值 < value >

hget < key1 >< field >从< key1 >集合 < field >取出value 中

hmset < key1 >< field1 >< value1 >< field2 >< value2 >… 批量设 置hash的值

hexists< key1>< field >查看哈希表key 中,给定域field 是否存在。

hkeys < key >列出该 hash集合的所有fieldv

hvals < key > 列出该hash集合的所有valuer

hincrby < key >< field >< increment >为哈希表 key 中的域field 的值加上增量1 -14

hsetnx < key >< field >< value >将哈希表 key 中的域field 的值设置为value, 当且仅当域
field不存在

hash 类型对应的数据类型是两种:ziplist(压缩列表), hashtable(哈希表),当field-value长度较短并且个数较少时,使用ziplist,否则使用hashtable

redis有序集合Zset

基本介绍

redis有序集合zset和普通集合set非常相似,是一个没有重复元素的字符串集合

不同之处就是有序集合的每一个成员都关联了一个评分(score),这个评分被用来按照最低分到最高分的方式排序集合中的成员。集合的成员是唯一的,但是评分可以是重复的

因为元素是有序的,所以可以根据评分(score)或者次序(position)来获取一个范围的元素

常用命令

zadd < key >< value1 >< value1 >< value2 >< value3 >……将一个或多个member元素及其score值加入到有序集合中key中

arange < key >< start >< stop > withscores返回有序集合key中下标为< start >< stop >之间 的元素,并且score和值一起返回到结果集

zrangebyscore key minmax [ withscores ] [ limit offset count]返回有序集合key中,所有score值介于min 和max之间的成员(包括min或者max)的成员,按照score从小到大次序排列

zrevrangebyscore key maxmin [ withscores ] [ limit offset count]同上,改为从大到小排列

zincrby < key >< increment >< value >为元素的score加上增量

zrem < key >< value >删除该集合下,指定值的元素

zcount < key >< min >< max >统计该集合,分数区间内的元素个数

zrank < key >< value >返回该值在集合中的排名,从0开始

zset底层结构

SortedSet (zset)是Redis提供的一个非常特别的数据结构, 一方面它等价于Java的数据结构Map<String, Double> ,可以给每一个元素value赋予一个权重score ,另一面又类似于TreeSet内部的元素会按照权重score进行排序,可以得到每个元素的名次,还可以通过score的范围来获取元素的列表
zset底层使用了两个数据结构。
( 1 ) hash , hash的作用就是关联元素value和权重score ,保障元素value的唯
一性,可以通过元素value找到相应的score值。。
(2 )跳跃表,跳跃表的目的在于给元素value排序,根据score的范围获取元素
列表。

image-20211227090803517

先从第二层开始找,1、21、null(21<51)没有找到,找下一层1、21、41、61(41<51<61)直接找下一层41往后查找51

配置文件详解

默认情况下bind=127.0.0.1只接受本机的访问请求,将其注释,无限制的接受任何ip地址访问

一般情况下远程连接时都是要注释掉的

image-20211227092416978

protected-mode yes 保护模式 改成no支持远程访问

tcp-backlog 511 设置tcp的backlog , backlog其实是一个连接队列 , backlog 队列总和=未完成三次握手队列+已经完成三次握手队列

timeout 0 永不超时

tcp-keepalive 300 连接redis后,检测是否操作的周期

daemonize yes 后台启动

pidfile /var/run/redis_6379.pid在里面保存记录号

loglevel notice redis中日志的级别

logfile “” 设置日志输出的路径,默认为空

databases 16 redis默认是16个库

requirepass redis密码

maxclients 1000 设置redis同时可以与多少个客户端进行连接

maxmemory 设置redis可以使用的内存量,达到内存使用上限,redis会试图移除内部数据,移除规则可以通过maxmemory-policy来指定

maxmemory-policy

  • volatile-lru :使用LRU算法移除key ,只对设置了过期时间的键(最近最少使用)
  • allkeys-lru :在所有集合key中,使用LRU算法移除key
  • volatile-random :在过期集合中移除随机的key ,只对设置了过期时间的键
  • alkeys-random :在所有集合key中,移除随机的key
  • volatile-tl :移除那些TL值最小的key ,即那些最近要过期的key
  • noeviction :不进行移除。针对写操作,只是返回错误信息v

maxmemory-samples 设置样本数量

发布和订阅

redis发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接受消息

1、客户端可以订阅频道

image-20211227100754977

2、当给这个频道发布消息后,消息就会发送给订阅的频道

image-20211227100807316

发布订阅命令行实现

1、打开一个客户端订阅channel1

SUBSCRIBE channel1

image-20211227101646957

2、打开另一个客户端,给channel1发布消息hello

publish channel1 hello

image-20211227101701438

3、打开第一个客户端可以看到发送的消息

image-20211227101715336

新数据类型

Bitmaps

redis提供了bitmaps这个数据类型,可以实现对位的操作

1、Bitmaps本身不是一种数据类型,实际上就是字符串(key-value)但是它可以对字符串的位进行操作

2、Bitmaps单独提供了一套命令,所以在redis中使用bitmaps和使用字符串的方法不太相同,可以把Bitmaps想象成一个以位为单位的数组,数组的每一个单元只能存储0和1,数组的下标在Bitmaps中叫偏移量

命令

setbit < key >< offset >< value >设置Bitmaps中某个偏移量的值

image-20211227103149795

getbit< key >< offset >获取Bitmaps中某个偏移量的值

bitcount< key >[start end] 统计字符串从start字节到end字节比特值为1的数量

bitop and(or/not/xor) < deskkey >[key…] bitop是一个复合操作,可以做多个Bitmaps的and(交集)、or(并集)、not(非)、xor(异或)操作并将结果保存在deskkey中

HyperLogLog

简介

在工作当中,我们经常会遇到与统计相关的功能需求,比如统计网站PV( PageView页面访问量) ,可以使用Redis的incr. incrby 轻松实现,但像UV ( UniqueVisitor ,独立访客)、独立IP数、搜索记录数等需要去重复计数的问题如何解决?这种求集合中不重复元素个数的问题称为基数问题

能否能够降低-定的精度来平衡存储空间? Redis推出了HyperLogLog.
Redis HyperLogLog 用来做基数统计的算法, HyperLogLog的优点是,在输
入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定的、并且是很小

在Redis,每个HyperLogLog键只需要花费12 KB内存,就可以计算接
近2^64个不同元素的基数。这和计算基数时,元素越多耗费内存就越多的集合形成
鲜明对比。。
但是,因为HyperLogLog只会根据输入元素来计算基数,而不会储存输入元素
本身,所以HyperLogLog不能像集合那样,返回输入的各个元素

命令

pfadd < key >< element >[element…]添加指定元素到HyperLogLog中

pfcount < key > [key…] 计算HLL的近似基数,可以计算多个HLL

pfmerge< destkey >< sourcekey >[sourcekey..] 将一个或多个HLL合并后的结果存储在另一个HLL中

Geospatial

基本介绍

​ Redis 3.2 中增加了对GEO类型的支持。GEO , Geographic ,地理信息的缩写。该类型,就是元素的2维坐标,在地图上就是经纬度。redis 基于该类型, 提供了经纬度设置,查询,范围查询,距离查询,经纬度Hash等常见操作

命令

geoadd< key >< longitude >< latitude >< member >[longitude latitude member] 添加地理位置(经度、维度、名称)

geopos < key >< member >[member…] 获取指定地区的坐标值

geodist < key >< member1 >< member2> [m|km|ft|mi]获取两个位置之间的直线距离

georadius < key >< longitude >< latitude >radius m|km|ft|mi 以给定的经纬度为中心,找出某一半径内的元素

jedis

jedis是redis在java版本的客户端实现

导入依赖

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>3.2.0</version>
</dependency>

连接redis注意事项

在redis.conf注释掉bind 127.0.0.1,然后protected-mode no

连接超时:禁用linux的防火墙systemctl stop/disable firewall.service

测试

public class JedisDemo {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("192.168.194.132",6379);
        String value = jedis.ping();
        System.out.println(value);
    }

    @Test
    public void key(){
        Jedis jedis = new Jedis("192.168.194.132",6379);
        jedis.set("k1","v1");
        jedis.set("k2","v2");
        jedis.set("k3","v3");
        Set<String> keys = jedis.keys("*");
        System.out.println(keys.size());
        for (String key : keys) {
            System.out.println(key);
        }

        System.out.println(jedis.exists("k1"));
        System.out.println(jedis.ttl("k2"));//返回剩余时间(-1表示永不过期)
        System.out.println(jedis.get("k3"));
    }
}

事务操作

事务定义

redis事务是一个单独的隔离操作:事务中所有命令都会序列化、按顺序执行。事务在执行的过程中,不会被其他客户端发送的命令请求所打断

redis事务的主要作用就是串联多个命令防止别的命令插队

Muli、Exec、discard

从输入Muli命令开始,输入的命令都会依次进入命令队列,但是不会执行,直到输入Exec命令后,才会将之间的命令队列中的命令依次执行。组队过程中可以通过discard来放弃组队

image-20211228152554117

image-20211228160447218

image-20211228160605340

事务错误处理

  • 组队中的某个命令出现了报告错误,执行时整个队列的命令都会被取消

  • 如果执行阶段某个命令爆出了错误,则只有报错的命令不会被执行,而其他的命令都会执行,不会回滚

事务冲突的问题

多个人登上同一个账户,账户上有1000,

一个请求想给金额减8000,

一个请求想给金额减5000,

一个请求想给金额减1000,如果正常处理完每个请求,账户就 剩下-4000,显然是不符合常理的

悲观锁

image-20211228171622677

悲观锁(Pessimistic Lock),按意思就是很悲观,每次去拿数据时都认为别人会修改,所以每次拿数据时都会上锁,这样别人想拿这个数据就会block直到他拿到锁。传统的关系型数据库里用到了很多这样的锁机制,比如行锁、表锁等读锁,写锁等,都是在做操作之前先上锁

乐观锁

image-20211228172619409

常见的场景:秒杀

watch key [key……]

在执行multi之前,先执行watch key1 [key2],可以监视一个或者多个key,如果在事务执行之前key被其他命令锁改动,那么事务将会被打断

在第一个客户端

image-20211228185238219

在第二个客户端

image-20211228185339552

redis事务特性

1、单独的隔离操作

事务中的所有命令都会序列化、按要求的执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断

2、没有隔离级别的概念

队列中的命令没有提交之前都不会被实际执行,因为事务提交前任何指令都不会被实际执行

3、不保证原子性

事务中如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚

ab工具模拟并发

安装

yum install httpd-tools

测试

通过浏览器测试

ab -n 1000 -c http://192.168.194.132/secKill

vim postfile 模拟表单提交参数,以&符号结尾;存放当前目录。

内容:prodid=0101&

ab -n(请求) 2000 -c(并发) 200 -k -p ~/postfile -T application/x-www-form-urlencoded http://192.168.2.115:8081/Seckill/doseckill

超卖问题

image-20211228215353640

已经秒杀光了,还出现秒杀成功,就是超卖问题

使用乐观锁淘汰用户

//增加乐观锁
jedis.watch(qtkey);

//判断库存
String qtkeystr = jedis.get(qtkey);
if(qtkeystr==null||"".equals(qtkeystr.trim())){
    System.out.println("未初始化问题");
    jedis.close();
    return false;
}

int qt = Integer.parseInt(qtkeystr);
if(qt<=0){
    System.out.println("已经秒光");
    jedis.close();
    return false;
}

//增加事务
Transaction multi = jedis.multi();
 
//4.减少库存
//jedis.decr(qtkey);
multi.decr(qtkey);
 
//5.加人
//jedis.sadd(usrkey, uid);
multi.sadd(usrkey, uid);
 
//执行事务
List<Object> list = multi.exec();
 
//判断事务提交是否失败
if(list==null || list.size()==0) {
    System.out.println("秒杀失败");
    jedis.close();
    return false;
}
System.err.println("秒杀成功");
jedis.close();

连接池

节省每次连接redis服务带来的消耗,把连接好的实例反复利用。

通过参数管理连接的行为

代码见项目中

  • 链接池参数
    • MaxTotal:控制一个pool可分配多少个jedis实例,通过pool.getResource()来获取;如果赋值为-1,则表示不限制;如果pool已经分配了MaxTotal个jedis实例,则此时pool的状态为exhausted。
    • maxIdle:控制一个pool最多有多少个状态为idle(空闲)的jedis实例;
    • MaxWaitMillis:表示当borrow一个jedis实例时,最大的等待毫秒数,如果超过等待时间,则直接抛JedisConnectionException;

库存遗留问题

一个用户购买后修改版本号,导致其他用户的版本号与其不一样,导致其他用户不能往下做其他操作。导致商品还存在,但是因为版本号不一样,其他用户不能购买

LUA脚本

image-20211228221832571

Lua 是一个小巧的脚本语言,Lua脚本可以很容易的被C/C++ 代码调用,也可以反过来调用C/C++的函数,Lua并没有提供强大的库,一个完整的Lua解释器不过200k,所以Lua不适合作为开发独立应用程序的语言,而是作为嵌入式脚本语言。

很多应用程序、游戏使用LUA作为自己的嵌入式脚本语言,以此来实现可配置性、可扩展性。

这其中包括魔兽争霸地图、魔兽世界、博德之门、愤怒的小鸟等众多游戏插件或外挂

优势

将复杂或者多步的redis操作,写为一个脚本,一次提交给redis执行,减少反复连接redis的次数,提升性能

LUA脚本是类似redis事务,有一定的原子性,不会被其他命令插队,可以完成一些redis事务性的操作。

但是注意redis的lua脚本功能,只有在Redis 2.6以上的版本才可以使用。

利用lua脚本淘汰用户,解决超卖问题。

redis 2.6版本以后,通过lua脚本解决争抢问题,实际上是redis 利用其单线程的特性,用任务队列的方式解决多任务并发问题。

案列

local userid=KEYS[1]; 
local prodid=KEYS[2];
local qtkey="sk:"..prodid..":qt";
local usersKey="sk:"..prodid.":usr'; 
local userExists=redis.call("sismember",usersKey,userid);
if tonumber(userExists)==1 then 
  return 2;
end
local num= redis.call("get" ,qtkey);
if tonumber(num)<=0 then 
  return 0; 
else 
  redis.call("decr",qtkey);
  redis.call("sadd",usersKey,userid);
end
return 1;

持久化操作

redis是基于内存的数据库,他的数据存储到内存中,也可以存到redis中,这一过程就叫持久化

RDB(Redis DataBase)

基本介绍

指定的时间间隔内将内存中的数据集快照写入磁盘,也就是Snapshot快照,它恢复时时将快照文件直接读到内存里

如何工作

redis会单独创建(fork)一个子进程来进行持久化,会先将数据写到一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次替换的文件,整个过程中,主进程是不会进行任何IO操作,这就确保了极高的性能。如果需要进行大规模数据的恢复,且对数据恢复完整性不是非常敏感,那RDB方式要比AOF方式更加高效。RDB的缺点是最后一次持久化存储的数据可能丢失

image-20220115211159676

dump.rdb文件

在redis.conf中配置文件名称,默认为dump.rdb

  • dbfilename dump.rdb

    image-20220115211513852

    默认名称为dump.rdb

  • stop-writes-on-bgsave-error

    image-20220115211916739

    当redis无法写入磁盘中,直接关掉redis的写操作

  • rdbchecksum 检查完整性

    image-20220115212141042

    在存储快照后,还可以让CRC64算法来进行数据检验,但是这样做会增加大约10%的性能功耗,如果希望获取到最大的性能提升,可以关闭此功能

  • save

    写操作次数

    默认是1分钟内改了1万次,或者5分钟内改了10次,或者15分钟内改了1次

    **不设置save指令,或者给save传入空字符串 **

优势

  • 适合大规模的数据恢复
  • 对数据完成性和一致性要求不高,更适合使用
  • 节省磁盘空间
  • 恢复速度快

劣势

  • fork进程时,内存中的数据被克隆了一份,大致2倍的膨胀性需要考虑
  • 虽然redis在fork时使用了写时拷贝技术,但是数据庞大时还是比较消耗性能
  • 在备份周期在一定间隔时间做一次备份,所以如果redis意外down掉的话,就会丢失最后一次快照后的所有修改

rdb的备份

  • 复制当前dump.rdb文件

    cp dump.rdb d.rdb\
    ll//查询
    
  • redis服务停掉(模拟redis服务down掉)

    ps -ef | grep redis
    kill -9 3524
    
  • 将原本的dump.rdb文件删除(模拟数据丢失)

    rm -f dump.rdb
    ll
    
  • 将复制文件名字改成dump.rdb,并重新启动redis服务

    mv d.rdb dump.rdb
    redis-server /etc/redis.conf
    

    如果有些数据没有恢复,因为数据在20秒内添加的,那些数据在20外添加,没有进行持久化操作

AOF(Append Only File)

基本简介

以日志的形式来记录每个写操作(增量保存),将redis执行过的所有写指令记录下来(读写不记录),只许追加文件但不可以改写文件吗,redis启动之初会读取该文件重新构造数据,换言之,redis重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作

持久化流程

AOF默认不开启

可以在redis.conf中配置文件名称,默认为appendonly.aof

保存路径与RDB相同

AOF和RDB同时开启,系统默认读取AOF的数据

启动、修复、恢复

  • AOF的备份机制和性能和RDB不同,但是备份和恢复的操作通RDB一样,都是拷贝备份文件,需要恢复时将其拷贝到redis的工作目录下,启动系统即加载

  • 正常恢复

    • 修改默认的appendonly no 为yes
    • 将有数据的aof文件复制一份保存到对应目录
    • 恢复:重启redis重新加载
  • 异常恢复

    • 修改默认的appendonly no 为yes

    • 遇到AOF文件损坏,通过/usr/local/bin/redis-check-aof-fix

      appendonly.aof进行恢复

    • 备份被写坏的AOF文件

    • 恢复:重启redis,然后重新加载

同步频率设置

appendfsync always始终同步,每次redis的写入都会立刻计入日志;性能较差但是数据完整性比较好

appendfsync everysec每秒同步,每秒计入日志一次,如果宕机,本秒的数据可能丢失

appendfsync noredis不主动进行同步,把同步时机交给操作系统

Rewrite压缩

基本介绍

AOF采用文件追加方式,文件会越来越大为避免出现此种情况,新增了重写机制, 当AOF文件的大小超过所设定的阈值时,Redis就会启动AOF文件的内容压缩, 只保留可以恢复数据的最小指令集.可以使用命令bgrewriteaof

重写原理

AOF文件持续增长而过大时,会fork出一条新进程来将文件重写(也是先写临时文件最后再rename),redis4.0版本后的重写,是指上就是把rdb 的快照,以二级制的形式附在新的aof头部,作为已有的历史数据,替换掉原来的流水账操作。

no-appendfsync-on-rewrite:

如果 no-appendfsync-on-rewrite=yes ,不写入aof文件只写入缓存,用户请求不会阻塞,但是在这段时间如果宕机会丢失这段时间的缓存数据。(降低数据安全性,提高性能)

如果 no-appendfsync-on-rewrite=no, 还是会把数据往磁盘里刷,但是遇到重写操作,可能会发生阻塞。(数据安全,但是性能降低)

触发机制,何时重写

Redis会记录上次重写时的AOF大小,默认配置是当AOF文件大小是上次rewrite后大小的一倍且文件大于64M时触发

重写虽然可以节约大量磁盘空间,减少恢复时间。但是每次重写还是有一定的负担的,因此设定Redis要满足一定条件才会进行重写。

auto-aof-rewrite-percentage:设置重写的基准值,文件达到100%时开始重写(文件是原来重写后文件的2倍时触发)

auto-aof-rewrite-min-size:设置重写的基准值,最小文件64MB。达到这个值开始重写。

例如:文件达到70MB开始重写,降到50MB,下次什么时候开始重写?100MB

系统载入时或者上次重写完毕时,Redis会记录此时AOF大小,设为base_size,

如果Redis的AOF当前大小>= base_size +base_size*100% (默认)且当前大小>=64mb(默认)的情况下,Redis会对AOF进行重写。

重写流程

(1)bgrewriteaof触发重写,判断是否当前有bgsave或bgrewriteaof在运行,如果有,则等待该命令结束后再继续执行。

(2)主进程fork出子进程执行重写操作,保证主进程不会阻塞。

(3)子进程遍历redis内存中数据到临时文件,客户端的写请求同时写入aof_buf缓冲区和aof_rewrite_buf重写缓冲区保证原AOF文件完整以及新AOF文件生成期间的新的数据修改动作不会丢失。

(4)1).子进程写完新的AOF文件后,向主进程发信号,父进程更新统计信息。2).主进程把aof_rewrite_buf中的数据写入到新的AOF文件。

(5)使用新的AOF文件覆盖旧的AOF文件,完成AOF重写。

优势

  • 备份机制更稳健,丢失数据概率更低
  • 可读的日志文件,通过操作AOF稳健,可以处理误操作

劣势

  • 比起RDB占用更多的磁盘空间
  • 恢复备份速度要慢
  • 每次读写都同步的话,有一定的性能压力
  • 存在个别bug,造成不能恢复

主从复制

主机数据更新后根据配置和策略,自动同步到备机的master/slaver机制,master以写为主,slave以读为主

作用

  • 读写分离,性能扩展

  • 容灾快速恢复

    当一台从服务器挂掉了,再读的话,根据策略,切换另一台从服务器中

image-20220121171306954

流程

1、创建/myredis文件夹

2、复制redis.conf配置文件到文件夹

3、配置一主两从,创建三个配置文件

4、在三个配置文件写入内容

修改redis.conf的appendonly

appendonly no
  • redis6379.conf

    include /myredis/redis.conf

    pidfile / var/run/redis_6379.pid

    port 6349

    dbfilename dump6349.rdb

  • redis6380.conf

    include /myredis/redis.conf

    pidfile / var/run/redis_6380.pid

    port 6380

    dbfilename dump6380.rdb

  • redis6381.conf

    include /myredis/redis.conf

    pidfile / var/run/redis_6381.pid

    port 6381

    dbfilename dump6381.rdb

5、启动三台服务器

redis-server redis6349.conf
redis-server redis6380.conf
redis-server redis6381.conf

# 查看服务开启
ps -ef | grep redis

# 连接三台服务器
redis-cli -p 6379
redis-cli -p 6380
redis-cli -p 6381

开启6379的服务,首先将原本的redis服务kill,再开启刚才创建文件redis6379.conf

6、查看三台主机运行情况

# 打印主从复制的相关信息
info replication

7、配从不配主

slaveof < ip > < port > 成为某个实例的从服务器

  • 在6380和6381上执行: slaveof 127.0.0.1 6379

当从服务器shutdown后,主服务器状态信息也改变了,但是从服务器重新开启,并且设置主服务器,主服务或者连接从服务器存储的key可以在新开的从服务器中查出

当主服务shutdown后,两个从服务器还是从服务器的状态

原理

1、当从服务器连接上主服务器之后,从服务器向主服务器发送进行数据同步消息

2、主服务器接收到从服务器发送过来同步消息,把主服务器数据进行持久化,rdb文件,把rdb文件发送到从服务器,从服务器拿到rdb进行读取、主服务器进行写操作后,会和从服务器进行数据同步

薪火相传

主服务器连接一台从服务器,而这台从服务器是另一台从服务器的主服务器

在6381上执行:slaveof 127..0.0.1 6380

  • 中途变更转向会清除之前的数据,重新建立拷贝新的
  • 当某个从服务器挂掉,后面的从服务器都无法备份
  • 主机挂掉了,从服务器还是从服务器,无法写入数据

反客为主

当一个master宕机后,后面的slave可以立刻升为master,后面的slave不同做任何的修改

在6380上执行:slaveof no one将其变成主机

哨兵模式

反客为主自动版,能够后台监控主机是否故障,如果故障了根据投票数自动将从库变成主库

1、主服务器连接两个从服务器(一主两从)

2、自定义的/myredis目录下新建sentinel.conf文件

3、配置哨兵,填写内容

sentinel monitor mymaster 127.0.0.1 6349 1

哨兵监控,mymaster是监控对象起的服务器名称,1为至少一个哨兵同意迁移的数量

4、启动哨兵(前台启动)

redis-sentinel /myredis/sentinel.conf

5、当主机挂掉,从机选举中产生新的主机

根据优先级:slave-priority

主服务器重新连接,主服务器状态变成从服务器

复制延时

所有的写操作都是先在master上操作,然后同步到slave上,所以从master同步到slave机器上有一定的延迟,当系统繁忙时,延迟会更加严重,slave机器数量的增加也会是这个问题更加严重

故障修复

1、从下线的主服务的所有从服务器里面挑选一个从服务,将其变成主服务

选择条件依次为:

  • 选择优先级靠前的
  • 选择偏移量最大的
  • 选择runid最小的从服务

2、挑选出新的主服务之后sentinel向原主服务发送slaveof新的主服务的命令,复制新的master

3、当下线的服务重新上线时,sentinel向其发送slaveof命令。变成新主服务的从服务

优先级在redis.conf中默认:slave-priority(或者replica-priority) 100,值越小优先级越高

偏移量是指获得原主服务数据最全的

每个redis实例启动后都会随机生成一个40位的runid

集群

容量不够,redis‘如何扩容

并发写操作,redis如何分摊

同时主从模式,薪火相传模式,主机宕机,导致主机IP地址发生变化,应用程序中配置需要修改对应的主机、端口等信息

之前通过代理主机来解决,现在redis3.0中提出的无中心化集群配置

介绍

redis集群实现了对redis的水平扩容,即启动n个redis节点,将整个数据库分布存储在这n个节点中,每个节点存储总数据的1/n

redis集群通过分区(partition)来提供一定程度的可用性(availability):解释集群中有一部分节点失效或者无法通讯,集群也可以继续处理命令请求

搭建集群

1、删除持久化数据

将rdb,aof文件都删除掉

2、制作六个实例

3、配置基本信息

开启daemonize yes

Pid文件名字

指定端口

Log文件名字

Dump.rdb名字

Appendonly关掉或者换名字

4、redis cluster配置修改

cluster-enabled yes 打开集群模式

cluster-config-file nodes-6379.conf 设定节点配置文件名

cluster-node-timeout 15000 设定节点失联时间,超过该时间(毫秒),集群自动进行主从切换。

include /home/bigdata/redis.conf
port 6379
pidfile "/var/run/redis_6379.pid"
dbfilename "dump6379.rdb"
dir "/home/bigdata/redis_cluster"
logfile "/home/bigdata/redis_cluster/redis_err_6379.log"
cluster-enabled yes
cluster-config-file nodes-6379.conf
cluster-node-timeout 15000

5、修改redis6349.conf文件,拷贝多个

6、使用查找替换修改另外5个文件

:%s/6349/6380
:%s/6349/6381
:%s/6349/6382
等等

7、启动六个redis服务

8、将六个节点合成一个集群

组合之前,请确保所有redis实例启动后,nodes-xxxx.conf文件都生成正常

合体

cd /opt/redis-6.2.1/src

#replicas 1 采用最简单的方式配置集群,一台主机,一台从机,正好三组
redis-cli --cluster create --cluster-replicas 1 192.168.11.101:6379 192.168.11.101:6380 192.168.11.101:6381 192.168.11.101:6389 192.168.11.101:6390 192.168.11.101:6391

All 16384 slots covered 合体成功

普通方式登录

可能直接进入读主机,存储数据时,会出现MOVED重定向操作,要一步集群方式登录

image-20220204185327075

采用集群策略连接,设置数据会自动切换到相应的写主机

image-20220204185500990

通过cluster nodes命令查看集群信息

image-20220204185612265

redis cluster如何分配六个节点

一个集群至少有三个主节点

分配原则尽量保证每个主数据库运行在不同的ip地址,每个从库和主库不在一个IP地址上

什么是slots

一个 Redis 集群包含 16384 个插槽(hash slot), 数据库中的每个键都属于这 16384 个插槽的其中一个,

集群使用公式 CRC16(key) % 16384 来计算键 key 属于哪个槽, 其中 CRC16(key) 语句用于计算键 key 的 CRC16 校验和 。

集群中的每个节点负责处理一部分插槽。 举个例子, 如果一个集群可以有主节点, 其中:

节点 A 负责处理 0 号至 5460 号插槽。

节点 B 负责处理 5461 号至 10922 号插槽。

节点 C 负责处理 10923 号至 16383 号插槽。

在集群中录入值

在redis-cli每次录入、查询键值,redis都会计算出该key应该送往的插槽,如果不是该客户端对应服务器的插槽,redis会报错,并告知应前往的redis实例地址和端口。

redis-cli客户端提供了 –c 参数实现自动重定向。

如 redis-cli -c –p 6379 登入后,再录入、查询键值对可以自动重定向。

不在一个slot下的键值,是不能使用mget,mset等多键操作

image-20220204220229372

可以通过{}来定义组的概念,从而使key中{}内相同内容的键值对放到一个slot中去

image-20220204220254734

查询集群中的值

CLUSTER GETKEYSINSLOT 返回 count 个 slot 槽中的键。

image-20220204220420735

故障修复

如果主节点下线?从节点能否自动升为主节点?注意:*15秒超时*

image-20220204221209311

主节点恢复后,主从关系会如何?主节点回来变成从机。

image-20220204221227047

如果所有某一段插槽的主从节点都宕掉,redis服务是否还能继续?

如果某一段插槽的主从都挂掉,而cluster-require-full-coverage 为yes ,那么 ,整个集群都挂掉

如果某一段插槽的主从都挂掉,而cluster-require-full-coverage 为no ,那么,该插槽数据全都不能使用,也无法存储。

集群的jedis开发

及时即使连接的不是主机,集群会自动切换主机存储。主机写,从机读

无中心化主从集群,无论从那台主机写的数据,其他主机也能读到数据

public class JedisClusterTest {
  public static void main(String[] args) { 
     Set<HostAndPort>set =new HashSet<HostAndPort>();
     set.add(new HostAndPort("192.168.31.211",6379));
     JedisCluster jedisCluster=new JedisCluster(set);
     jedisCluster.set("k1", "v1");
     System.out.println(jedisCluster.get("k1"));
  }
}

好处

1、实现扩容

2、分摊压力

3、无中心话配置相对简单

不足

1、多键操作是不被支持的

2、多键的redis事务是不被支持的。lua脚本不支持

3、由于集群方案出现较晚,很多公司已经采用了其他的集群方案,而代理或者客户端分片的方案想要迁移至redis cluster,需要整体迁移而不是逐步过渡,复杂度较大。

应用问题解决

缓存穿透

问题描述

​ key对应的数据在数据源并不存在,每次针对此key的请求从缓存获取不到,请求都会压到数据源,从而可能会压垮数据源。比如用一个不存在的用户id获取用户信息,不论缓存还是数据库都没有,若是黑客利用此漏洞进行攻击可能压垮数据库

解释:

  • 应用服务器压力变大了
  • redis命中率降低,缓存中没有要查询的数据
  • 转而一直查询数据库,会导致数据库崩溃了

image-20220206161423837

现象:

  • redis查询不到数据库
  • 出现很多非正常的URL访问

解决方案

1、对空值缓存:如果一个查询返回的数据为空(不管是数据是否不存在),我们仍然把这个空结果(null)进行缓存,设置空结果的过期时间会很短,最长不超过五分钟

2、设置可访问的名单(白名单):使用bitmaps类型定义一个可以访问的名单,名单id作为bitmaps的偏移量,每次访问和bitmap里面的id进行比较,如果访问ID不在bitmaps里面,进行拦截,不允许访问

3、采用布隆过滤器: (布隆过滤器(Bloom Filter)是1970年由布隆提出的。它实际上是一个很长的二进制向量(位图)和一系列随机映射函数(哈希函数)。

布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。)

将所有可能存在的数据哈希到一个足够大的bitmaps中,一个一定不存在的数据会被 这个bitmaps拦截掉,从而避免了对底层存储系统的查询压力。

4、进行实时监控:当发现Redis的命中率开始急速降低,需要排查访问对象和访问的数据,和运维人员配合,可以设置黑名单限制服务

缓存击穿

问题描述

​ key对应的数据存在,但在redis中过期,此时若有大量并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。

现象:

  • 数据库的访问压力瞬时增加
  • redis里面没有出现大量key过期
  • redis正常运行

解释:

1、redis某个key过期了,大量访问使用这个key,会不断的访问数据库,导致数据库崩溃

image-20220206163040692

解决方案

1、预先设置热门数据:在redis高峰访问之前,把一些热门数据提前存入到redis里面,加大这些热门数据key的时长

2、实时调整:现场监控哪些数据热门,实时调整key的过期时长

3、使用锁:

  • 就是在缓存失效的时候(判断拿出来的值为空),不是立即去load db。

  • 先使用缓存工具的某些带成功操作返回值的操作(比如Redis的SETNX)去set一个mutex key

  • 当操作返回成功时,再进行load db的操作,并回设缓存,最后删除mutex key;

  • 当操作返回失败,证明有线程在load db,当前线程睡眠一段时间再重试整个get缓存的方法。

image-20220206163140147

缓存雪崩

问题描述

​ key对应的数据存在,但在redis中过期,此时若有大量并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。

缓存雪崩与缓存击穿的区别在于这里针对很多key缓存,前者则是某一个key

现象:

1、数据库压力变大

2、导致服务器崩溃

正常访问:

image-20220206164204755

缓存失效瞬间:

image-20220206164239562

解决方案

1、构建多级缓存架构:nginx缓存 + redis缓存 +其他缓存(ehcache等)

2、使用锁或队列

用加锁或者队列的方式保证来保证不会有大量的线程对数据库一次性进行读写,从而避免失效时大量的并发请求落到底层存储系统上。不适用高并发情况

3、设置过期标志更新缓存:

记录缓存数据是否过期(设置提前量),如果过期会触发通知另外的线程在后台去更新实际key的缓存。

4、将缓存失效时间分散开:

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

分布式锁

问题描述

​ 随着业务发展的需要,原单体单机部署的系统被演化成分布式集群系统后,由于分布式系统多线程,多进程并且分布在不同机器上,这将使原单机部署情况下的并发控制锁策略失效,单纯的Java API并不能提供分布式锁的能力。为了解决这个问题就需要一种跨JVM的互斥机制来控制共享资源的访问,这就是分布式锁要解决的问题

分布式锁主流的实现方案:

1、基于数据库实现分布式锁

2、基于缓存(redis等)

3、基于Zookeeper

每一种分布式解决方案都有各自的优缺点:

1、性能:redis最高

2、可靠性:zookeeper最高

基于redis实现分布式锁

1、使用setnx上锁,通过del释放锁

2、锁一直没有释放,设置key的过期时间,自动释放

3、上锁之后出现异常,无法设置过期时间了

上锁的时候同时设置过期时间就可以了

set users 10 nx ex 12
nx:只有键不存在时,才对键进行设置操作。上锁
ex:只在键已经存在时,才对键进行设置操作。设置上锁时间

实现代码:

@GetMapping("testLock")
public void testLock(){
    //1获取锁,setne
    Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock","lock",3,TimeUnit.SECONDS);
    //2获取锁成功、查询num的值
    if(lock){
        Object value = redisTemplate.opsForValue().get("num");
        //2.1判断num为空return
        if(StringUtils.isEmpty(value)){
            return;
        }
        //2.2有值就转成成int
        int num = Integer.parseInt(value+"");
        //2.3把redis的num加1
        redisTemplate.opsForValue().set("num", ++num);
        //2.4释放锁,del
        redisTemplate.delete("lock");

    }else{
        //3获取锁失败、每隔0.1秒再获取
        try {
            Thread.sleep(100);
            testLock();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

设置锁的过期时间

1、通过expire设置过期时间

缺乏原子性,如果在setnx和expire之间出现异常,锁也无法释放

2、在set时指定过期时间

Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", "111",3,TimeUnit.SECONDS);

UUID防止误删

问题描述

1、a先进行操作

  • 上锁
  • 具体操作途中,出现服务器卡顿
  • 锁被自动释放
  • 一段时间后,服务器正常,进行操作,手动释放锁

2、b和c之间争取锁

  • 假如b抢到锁
  • 具体操作时,服务器反应过来,手动释放锁
  • b的锁就会被释放

解决当出现问题时,只会释放自己的锁,不会释放别人的锁

set lock uuid nx ex 10
#通过uuid表示不同的操作

#释放锁的时候,首先判断当前uuid和要释放uuid是否一样
@GetMapping("testLock")
public void testLock(){
    String uuid = UUID.randomUUID().toString();
    //1获取锁,setne
    Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid,3,TimeUnit.SECONDS);
    //2获取锁成功、查询num的值
    if(lock){
        Object value = redisTemplate.opsForValue().get("num");
        //2.1判断num为空return
        if(StringUtils.isEmpty(value)){
            return;
        }
        //2.2有值就转成成int
        int num = Integer.parseInt(value+"");
        //2.3把redis的num加1
        redisTemplate.opsForValue().set("num", ++num);
        //2.4释放锁,del
        //判断
        String lockUuid = (String)redis.Template.opsForValue.get("lock");
        if(lockUuid.equals(uuid)){
            redisTemplate.delete("lock");
        }

    }else{
        //3获取锁失败、每隔0.1秒再获取
        try {
            Thread.sleep(100);
            testLock();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

LUA保证删除原子性

问题描述

删除操作缺乏原子性

删除操作时候,正要删除,还没有删除,锁到了过期时间,自动释放,同时把b的也释放了

@GetMapping("testLockLua")
public void testLockLua() {
    //1 声明一个uuid ,将做为一个value 放入我们的key所对应的值中
    String uuid = UUID.randomUUID().toString();
    //2 定义一个锁:lua 脚本可以使用同一把锁,来实现删除!
    String skuId = "25"; // 访问skuId 为25号的商品 100008348542
    String locKey = "lock:" + skuId; // 锁住的是每个商品的数据

    // 3 获取锁
    Boolean lock = redisTemplate.opsForValue().setIfAbsent(locKey, uuid, 3, TimeUnit.SECONDS);

    // 第一种: lock 与过期时间中间不写任何的代码。
    // redisTemplate.expire("lock",10, TimeUnit.SECONDS);//设置过期时间
    // 如果true
    if (lock) {
        // 执行的业务逻辑开始
        // 获取缓存中的num 数据
        Object value = redisTemplate.opsForValue().get("num");
        // 如果是空直接返回
        if (StringUtils.isEmpty(value)) {
            return;
        }
        // 不是空 如果说在这出现了异常! 那么delete 就删除失败! 也就是说锁永远存在!
        int num = Integer.parseInt(value + "");
        // 使num 每次+1 放入缓存
        redisTemplate.opsForValue().set("num", String.valueOf(++num));
        /*使用lua脚本来锁*/
        // 定义lua 脚本
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        // 使用redis执行lua执行
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
        redisScript.setScriptText(script);
        // 设置一下返回值类型 为Long
        // 因为删除判断的时候,返回的0,给其封装为数据类型。如果不封装那么默认返回String 类型,
        // 那么返回字符串与0 会有发生错误。
        redisScript.setResultType(Long.class);
        // 第一个要是script 脚本 ,第二个需要判断的key,第三个就是key所对应的值。
        redisTemplate.execute(redisScript, Arrays.asList(locKey), uuid);
    } else {
        // 其他线程等待
        try {
            // 睡眠
            Thread.sleep(1000);
            // 睡醒了之后,调用方法。
            testLockLua();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

通过吧lua脚本保证删除操作时的原子性,lua脚本执行中无法打断

为了确保分布式锁可用,我们至少要确保锁的实现同时满足以下四个条件

- 互斥性。在任意时刻,只有一个客户端能持有锁。

- 不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。

- 解铃还须系铃人。加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了。

- 加锁和解锁必须具有原子性。

redis6.0新功能

ACL

简介

Redis ACL是Access Control List(访问控制列表)的缩写,该功能允许根据可以执行的命令和可以访问的键来限制某些连接。

在Redis 5版本之前,Redis 安全规则只有密码控制 还有通过rename 来调整高危命令比如 flushdb , KEYS* , shutdown 等。Redis 6 则提供ACL的功能对用户进行更细粒度的权限控制 :

(1)接入权限:用户名和密码

(2)可以执行的命令

(3)可以操作的 KEY

命令

1、使用acl list命令展现用户权限列表

image-20220206183406118

2、使用acl cat命令

(1)查看添加权限指令类别

image-20220206183444347

(2)加参数类型名可以查看类型下具体命令

image-20220206183506534

3、使用acl whoami命令查看当前用户

image-20220206183532318

4、使用aclsetuser命令创建和编辑用户ACL

(1)ACL规则

下面是有效ACL规则的列表。某些规则只是用于激活或删除标志,或对用户ACL执行给定更改的单个单词。其他规则是字符前缀,它们与命令或类别名称、键模式等连接在一起。

ACL规则
类型 参数 说明
启动和禁用用户 *on* 激活某用户账号
*off* 禁用某用户账号。注意,已验证的连接仍然可以工作。如果默认用户被标记为off,则新连接将在未进行身份验证的情况下启动,并要求用户使用AUTH选项发送AUTH或HELLO,以便以某种方式进行身份验证。
权限的添加删除 *+* 将指令添加到用户可以调用的指令列表中
*-* 从用户可执行指令列表移除指令
*+@* 添加该类别中用户要调用的所有指令,有效类别为@admin、@set、@sortedset…等,通过调用ACL CAT命令查看完整列表。特殊类别@all表示所有命令,包括当前存在于服务器中的命令,以及将来将通过模块加载的命令。
-@ 从用户可调用指令中移除类别
*allcommands* +@all的别名
*nocommand* -@all的别名
可操作键的添加或删除 *~* 添加可作为用户可操作的键的模式。例如~*允许所有的键

(2)通过命令创建新用户默认权限

image-20220206183623870

在上面的示例中,我根本没有指定任何规则。如果用户不存在,这将使用just created的默认属性来创建用户。如果用户已经存在,则上面的命令将不执行任何操作

(3)设置有用户名、密码、ACL权限、并启用的用户

acl setuser user2 on >password ~cached:* +get

image-20220206183712437

(4)切换用户,验证权限

image-20220206183733516

IO多线程

简介

Redis6终于支撑多线程了,告别单线程了吗?

IO多线程其实指****客户端交互部分**网络IO*交互处理模块*多线程*,而非*执行命令多线程****。Redis6执行命令依然是单线程。

原理结构

Redis 6 加入多线程,但跟 Memcached 这种从 IO处理到数据访问多线程的实现模式有些差异。Redis 的多线程部分只是用来处理网络数据的读写和协议解析,执行命令仍然是单线程。之所以这么设计是不想因为多线程而变得复杂,需要去控制 key、lua、事务,LPUSH/LPOP 等等的并发问题。整体的设计大体如下:

image-20220206183900216

另外,多线程IO默认也是不开启的,需要再配置文件中配置

io-threads-do-reads yes

io-threads 4

工具支持 Cluster:

之前老版Redis想要搭集群需要单独安装ruby环境,Redis 5 将 redis-trib.rb 的功能集成到 redis-cli 。另外官方 redis-benchmark 工具开始支持 cluster 模式了,通过多线程的方式对多个分片进行压测。


Author: baiwenhui
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint policy. If reproduced, please indicate source baiwenhui !
  TOC