Redis应用

centos准备工作

安装完成后进入centos 设置core

  • 输入1 按回车
  • 输入2 按回车
  • 输入q 按回车
  • 输入yes回车

配置ssh

vi /etc/ssh/sshd_config

把端口和两个连接地址的注释去掉,还有允许root远程登录以及密码验证注释去掉

开启服务 这玩意和Ubuntu不一样 开启的是sshd

service  sshd start

查看当前服务列表

systemctl list-units --type=service

换阿里源步骤

mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup
#备份一下原来的文件
wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo
#更换配置文件
wget -O /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repo
#添加epel
#清空原来的缓存 
yum clean all
#生成新缓存
yum makecache

redis基于centos7安装

安装gcc
yum install gcc	
下载redis安装包
wget http://download.redis.io/releases/redis-5.0.5.tar.gz
创建一个安装目录
mkdir /usr/local/redis
解压到安装目录
tar -zxvf redis-5.0.5.tar.gz -C /usr/local/redis
进入安装目录去编译
cd /usr/local/redis/redis-5.0.5
make
进入当前目录下的src去安装
cd src 
make install
修改配置文件

回到上级目录

进入redis.conf修改如下

  1. 注释掉 bind 127.0.0.1 这一行(解决只能特定网段连接的限制)

  2. 将 protected-mode 属性改为 no (关闭保护模式,不然会阻止远程访问)

  3. 将 daemonize 属性改为 yes (这样启动时就在后台启动)

  4. 设置密码

    添加 requirepass 123456即可 当然这步完全看心情

开放6379端口
firewall-cmd --zone=public --add-port=6379/tcp --permanent
firewall-cmd --reload
启动,停止,重启
启动
redis-server /usr/local/redis/redis-5.0.5/redis.conf

连接redis
redis-cli
auth 密码

停止redis
shutdown
exit

重启redis服务

#直接kill掉服务重启即可

设置开机自启(注册服务)
  • 去etc下创建个redis目录

  • copy一份配置文件到这个目录

    cp /usr/local/redis/redis-5.0.5/redis.conf /etc/redis/6379.conf
    
    
  • copy一份初始化脚本到本目录

    cp /usr/local/redis/redis-5.0.5/utils/redis_init_script /etc/init.d/redisd
    
    
  • 进入init.d设置一下开机自启

    cd /etc/init.d
    chkconfig redisd on
    
    
  • 最后就可以使用service愉快的玩耍了

    service redisd start
    service redisd stop
    
    

redis是什么

什么是Redis?

Redis 是开源免费的,遵守BSD协议,是一个高性能的key-value非关系型数据库。

redis单线程问题

所谓的单线程指的是网络请求模块使用了一个线程(所以不需考虑并发安全性),即一个线程处理所有网络请求,其他模块仍用了多个线程。

redis采用多路复用机制:即多个网络socket复用一个io线程,实际是单个线程通过记录跟踪每一个Sock(I/O流)的状态来同时管理多个I/O流.

Redis特点:

Redis支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用。

Redis不仅仅支持简单的key-value类型的数据,同时还提供String,list,set,zset,hash等数据结构的存储。

Redis支持数据的备份,即master-slave模式的数据备份。

性能极高 – Redis能读的速度是110000次/s,写的速度是81000次/s 。

原子 – Redis的所有操作都是原子性的,同时Redis还支持对几个操作全并后的原子性执行。

丰富的特性 – Redis还支持设置key有效期等等特性。

redis作用:

可以减轻数据库压力,查询内存比查询数据库效率高。

key系列命令

key系列
  • DEL key [key …]
    • 删除给定的一个或者多个key,不存在就会忽略
    • 返回值为被删除的数量
  • exists key名
    • 判断key是否存在(返回为存在的个数,可多个判断)
  • move key名 数据库号
    • 移动key到对应的数据库(0-15)
  • expire key名 秒
    • 过多少秒key就会失效(删除) (key必须是存在)
  • ttl key名
    • 查询key名还有多少时间过期(-1表示永不过期 -2已过期)
  • type key名
    • 判断类型
  • **keys ***
    • 查询所有数据
切换数据库
  • select 1
    • 切到1号数据库就是第二个

redis基本数据类型

是单线程的,原子性的所以都是线程安全的

字符串string

string 可以存字符串,数值和位图

常用命令
set key名称 值  #一般来说  key名不超过1024 值不超过512mb
get key名称
append name ,world   #将  ‘,world追加到name结尾’
strlen key名称      #字节长度
incr key名称         #将key+1
decr  key名称       #将key-1
setbit key名称  偏移  1 #01000000  下标为1二进制位  (偏移为1)因为最高位是用来区分字符集的

二进制安全:存储东西时候没有类型的概念(序列化成字节数组 不会因为数据很大而产生问题)全部识别为字节 不会去转换字符集这就是二进制安全

ascii是基本字符集 其他是扩展字符集

数值的应用场景
  • 作为计数器
    • 网站限流
    • 秒杀
    • 购物数量
位图的场景需求
  • 统计任意时间段内用户登录次数
  • 统计任意时间段内活跃用户数量
字典hash

其实就可以想象成hashmap

hset hwx name hwx  #设置key名称为hwx 的name的value为hwx
hset hwx age 18  #同上
hgetall hwx  会把hwx这个key里边的内容全部取出
hkeys hwx  #取出hwx key里边的所有key名
hvals hwx  #取出hwx key里边的所有val值
hincrby hwx age 1 #给hwx里头的key为age的值+1
hget  hwx age  #查看key为hwx 的key为age的值

应用场景:

  • 展示聚合数据
  • 购物车商品展示
    • 当用户购买完成之后查看购物车,由于是不同数据库的买的东西,导致去查看时候需要多个库去查询,为了提高效率可以在用户加入购物车的时候去把数据放到redis删除成功的时候去redis删除一条数据,大大提 高处理速度
列表list

list的底层是双向链表

可以模拟出数据结构 同向就是栈 逆向就是队列 还有数组

lpush key名称  a b c d #abcd是value 从左往右push
rpush key名称  a b c d #从右往左push
lpop  key名称 #从左弹栈
rpop  key名称 #从右弹栈
lrange key名称 0  -1#查看数据  0  -1表示区间
ltrim key名称  0  -1#删除给定区间外的东西

应用场景

  • 微博评论
集合set

特点

  • 无序
  • 去重
  • 集合
sadd key名称 a b c d #向集合添加数据机是添加重复的也会自动去重
SMEMBERS key名称 #取集合所有东西
SRANDMEMBER key名称 count(多少个)#count有这么几种情况 是正数 返回不重复的数据  负数 返回可能有重复的
SPOP key名称 count  #取出不放回
SINTER k1 k2 #取k1 k2 的交集
SUNION K1 K2 #取k1 k2 的并集
SDIFF  k1 k2 #取k1 k2的左差集
SDIFF  K1 K2 #取k1 k2的右差集

应用场景

  • 一些随机事件(抽奖)
  • 取出不放回的抽奖
    • 使用spop
  • 取交集、并集、差集
    • 推荐系统
      • 共同好友(交集)
      • 可能认识的人(差集)
有序集合SortedSet (zset)

特点

  • 有序 (是有正序和倒序的)
  • 集合
  • 去重
## 创建集合时候必须带分值
ZADD k1 2 a 3 b 1 c 6 d 5 e# k1为key的名称 2 为a的权重
ZRANGE k1 0 -1 withscores #查看排序好的数据
ZRANGE k1 0 2 #取从小到大的的前三名
ZREVRANGE k1 0 2 #取从大到小的前三名
ZINCRBY k1 4 a#可以改变权重值 为a的权重值增加4 而且可以做到动态排序

应用场景

  • 排行榜
    • 动态分值增加 总是倒序去除n条数据
  • 评论分页
    • 比如以时间戳为score 内容为评论内容

SortedSet排序 skiplist(跳跃表)

redis的数据持久化

RDB(快照)
阻塞(save 停机维护)

redis变成阻塞状态,对外无法提供服务

然后去写当前时间段的东西

非阻塞(bgsave)
  • redis继续对外提供服务
  • 将数据落地
  • 通过系统调用fork()和内核机制写时复制来实现

redis比如在8点要去使用rdb的时候去持久化数据

  • 首先会fork出当前时间点的副本(仅仅是拷贝的指针,并没有去拷贝数据所以速度很快)
  • 然后子进程的作用就是通过指针去读当前的映射数据,然后写到硬盘,这个子进程并不会发生修改操作
  • 之后父进程该运行运行,当某个数据发生改变的时候,会触发写时复制,把修改的数据复制一份到物理内存的另外一个地址,之后修改指针指向。
配置文件
save <seconds> <changes>
    Redis默认配置文件中提供了三个条件:
    save 900 1
    save 300 10
    save 60 10000
    分别表示900秒(15分钟)内有1个更改,300秒(5分钟)内有10个更改以及60秒内有10000个更改

指定本地数据库文件名,默认值为dump.rdb
    dbfilename dump.rdb
12. 指定本地数据库存放目录
    dir ./
弊端
  • 全量落地
  • 数据丢失相对多一些(由于是全量 上一天的刚拿到rdb,今天的数据刚准备持久化的时候,服务器被炸了,丢了一天的)
管道技术(理解什么是父子进程)
管道解释
  • 在Linux操作系统中 | 符号的作用通常是连接左右两条命令 符号左边的输出会作为右边的输入(左边是一个进程,右边是一个进程)
  • 管道会触发创建子进程($$ 的优先级高于管道)
[root@x1a0g x1a0g]# num=2
[root@x1a0g x1a0g]# echo $num
2
[root@x1a0g x1a0g]# ((num++))
[root@x1a0g x1a0g]# echo $num
3
[root@x1a0g x1a0g]# ((num++)) | echo ok
ok
[root@x1a0g x1a0g]# echo $num
3
[root@x1a0g x1a0g]# ((num++)) | echo $num
3
[root@x1a0g x1a0g]# echo $BAHSPID

[root@x1a0g x1a0g]# echo $BASHPID
77121
[root@x1a0g x1a0g]# echo $BASHPID | more
77351
[root@x1a0g x1a0g]# echo $BASHPID | echo $BASHPID
77362
[root@x1a0g x1a0g]# echo $BASHPID | more
77363
[root@x1a0g x1a0g]# 

Linux的父子进程的概念
  • 在Linux中进程之间是数据是相互隔离的(最终概念)
  • 但是环境变量是所有进程可见的(export关键字)
  • 父进程对于数据的修改不会影响到子进程
  • 子进程对于数据的修改也不会影响到父进程
关于系统调用fork()

进程创建时候有两个成本:1.内存大小 2. 速度

fork会在内存中创建出一个子进程(并不会把所有数据来拷贝,仅仅是指针的指向)

  • 速度快
  • 空间小
内核机制 copy on write(写时复制)必须记住 进程时隔离的,互相修改时候不会影响对方
  • 创建子进程并不会发生复制
  • 在进程去写该数据时候才会发生复制(复制进程速度变快)
AOF(日志)
概念
  • redis的写操作记录到日志中(丢失数据少)
  • redis中aof和rdb可以同时开启,但是恢复数据时候只会使用aof恢复()
问题
  • 由于aof是以append 的形式去追加记录,所以会造成数据量过大,恢复过慢
    • 解决方案
      • 假入能让日志不会很大,就可以愉快的使用
      • 4.0之前,使用重写的方式,删除抵消的命令(set 1下 get一下),合并重复的命令(push很多次),最终的文件也是一个纯指令的日志文件
      • 4.0之后,使用重写的方式,将老的数据rdb到aof中,将增量的数据以指令的形式追加到AOF ,最终的文件是一个混合文件
redis作为内存数据库 写日志触发io问题
  • redis中有默认三个级别的flush
appendfsync everysec  表示每秒同步一次(折中,默认值)
appendfsync no        表示等操作系统进行数据缓存同步到磁盘(快)
appendfsync always    表示每次更新操作后手动调用fsync()将数据写到磁盘(慢,安全)
指定是否在每次更新操作后进行日志记录,默认为no
appendonly no    #关闭aof
指定更新日志文件名,默认为appendonly.aof
appendfilename appendonly.aof


auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
演示aof和rdb

因为5.0是默认开启aof与rdb混合,可以先关掉

redis-server ./redis.conf     这个配置文件是/usr/local/redis/redis-5.0.5下的

使用事前记得把aof日志清一下 路径 /var/redis/

执行bgrewrite之后 aof中写入了rdb

这些东西就是增量就是aof与rdb的混合形式[

redis集群

单机/单实例/单节点存在的问题
  • 单点故障(被炸了就没了)
  • 容量有限
  • 压力大
AKF(微服务四大拆分原则之一)
  • x轴 全量 镜像(主从复制 读写分离)解决单点故障
  • y轴 根据业务功能拆分redis(一个放订单、一个放登陆、一个放表单提交,表现为MySQL为分库分表) 解决容量不足
  • z轴 根据优先级,逻辑继续拆分(比如y轴0-1000为一个redis 1000-2000一个 ) 减轻压力
通过AKF一变多
  • 数据一致性问题如何解决?

    • 所有节点阻塞直到所有数据一致(强一致性,成本太高)同步阻塞方式
      • 强一致性可能破坏可用性,假如我一个备机和主机网络发生故障,导致连接超时,那么所有的备机包括主机发生数据回滚,表示当前操作失败,也就是说服务不可用了
    • 强一致性降级,容忍数据失去一部分(异步方式)
  • 最终一致性

  • kafka或者其他东西,必须保证他是可靠的,响应速度快的,要保证可靠,他也必须是一个集群(不能出现单点故障)

  • 我们 一变多 就是为了解决***单点故障*** 或者说解决可用性的问题

  • 最终一致性也有可能出现问题 ,加入后头那俩redis还没消费数据,可能会导致数据不一致的问题

主从和主备
  • 主备:假如构建出主备 (4台机子)
    • 备机不会参与业务,用户只能访问到主机(主机挂了才会启用备机)
  • 主从复制:假如构建出主备 (4台机子)
    • 常用主从复制,客户端无论主机,从机都能访问
  • 主机通常是读写操作,但是这样主机就又变成了单点,所以一般会对主机做 HA ( 高可用 ),如果主机挂掉 其他备机顶上变成主机,剩余的备机追随这个变成主机的备机(如何做呢,下头的监控来了)
监控机制的原理

假如redis主机挂掉,那么是什么来判断这个主机是挂的,来看看这个图

  • 既然要来作为监控,那么这个监控一定是高可用的,也就是说是一定是集群的,如上图

  • 其实可以把这个图反转一下,也是一样的

  • 那么随之而来的就是第一个问题,假如我有三台监控,那么如何判定我的redis是挂的

    • 假如3台都说挂才是挂,那么就又会是强一致性,出现可用性问题

    • 假如1台说我挂了,但是可能由于其他问题其他两台看的没挂,就会出现统计不准确,或者说势力范围不够。会造成的现象是出现 网络分区 也可以说出现精神分裂(脑裂),就是说我同一个服务,用户一个数据进来返回不一样的结果

      但是网络分区是可以容忍的(分区容忍性)《注册发现例子》,不是很要求数据强一致性

      假如2台说我挂了,会进行一个投票,说挂的会结成势力范围,在当前场景下说挂的两台的势力范围是2,那么剩下的一台势力范围是1,1的就没有决策权,那么当前的就只会有两种状态,挂或者不挂,不会出现中间态

  • 最后解决网络分区问题,监控判断挂不挂的条件是2n+1台,通俗来说 过半

  • 一般来说 集群的数量都是奇数台

    • 比如 3台 和 4台 他俩承担的风险是一样的,都是只能允许1台发生故障,但是4台出现故障的情况比三台高
CAP定理

CAP原则又称CAP定理,指的是在一个分布式系统中,一致性(Consistency)、可用性(Availability)、分区容错性(Partition tolerance)。CAP 原则指的是,这三个要素最多只能同时实现两点,不可能三者兼顾 (比如强一致性与可用性不能同时满足)

搭建redis伪集群

本机启动配置如下

redis-6379  service redisd start

redis-6380  路径:/usr/local/redis-6381是端口为6380的配置文件名字起错了
执行   redis-server  ./redis.conf

redis-6381  路径  /usr/local/redis-5.0.5 
执行   redis-server  ./redis.conf

如何修改?

  • 先copy几份出来
  • 修改端口 搜索port
  • 修改pid 搜索pidfile
  • 修改log位置 搜索logfile
  • 修改dir 搜索 dir
  • 修改守护进程 搜索 daem
主从复制

服务器配置,要前台阻塞,看实时效果

  • 关闭守护进程
  • 关掉aof
  • 关掉日志文件

然后这次测试的三台机器全在 /home/x1a0g/test 目录下,演示之前记得清一下rdb文件

如果主机设置了密码,必须在从机中添加一条 masterauth 123456

首先是主从复制

让  6380 和 6381 两台机器追随  6379  即  6379为主,6381、6381为从
命令如下:
6380从机执行:
REPLICAOF 127.0.0.1 6379
这种方式常用  :redis-server ./6380.conf --replicaof 127.0.0.1 6379
6381从机执行
REPLICAOF 127.0.0.1 6379

可以看到 当从机第一次挂到主机上时候,一定会先把自己的老数据删除掉,之后会落一个rdb到从机,从而保证数据同步,

在这时候,如果从机突然挂了,再次连回来的时候,就不会再去落全量的rdb,只会去把它挂掉这一段时间的增量给他

flushing 这个操作就是删除老数据

这个截图是主机 的 可以看到6380先lost 就是挂了 下头那句是6380又通了 主机发了一个offset过去,就是做的增量同步 并没有去落rdb

加入 我 这么启动 redis-server ./6380.conf --appendonly yes 就是开启aof 会发现我又开始落rdb了 ,这是因为有aof的时候,会以重新把rdb写到aof

  • 如果主从做好之后,从机默认无法写,这个改一下配置文件就ok

  • 主机身上 可以知道有多少个从机,加入从挂了 我们在没有哨兵的情况下:

    • 手动切换方式

      6379挂了 ,假如我想把6380变成主 然后6381追随6380,操作如下

      6380执行:REPLICAOF no one

      6381执行: REPLICAOF 127.0.0.1 6380

      当6379在跑起来的时候让他去追随6380即可:REPLICAOF 127.0.0.1 6380

  • 然后就完成了

哨兵机制测试环境
  • 环境同上,在起三个哨兵服务即可

redis开发

缓存击穿

在什么时候发生的?作为缓存的时候(一定发生了高并发的情况)

  • redis作为缓存,key拥有过期时间
  • 做了lru(访问时间)或者lfu(访问次数)这些内存淘汰策略

key过期或者被清理掉了,造成并发访问数据库

并发有了,阻止并发到达db,redis中又没有key

假如有5000的并发

jedis连接redis

pom依赖

<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
    <dependency>
      <groupId>redis.clients</groupId>
      <artifactId>jedis</artifactId>
      <version>3.2.0</version>
    </dependency>

基本连接

@Test
public void test03(){
    Jedis jedis = new Jedis("192.168.70.131",6379);
    jedis.auth("123456");
    String k1 = jedis.get("key");
    System.out.println(k1);
}

配置连接池

package com.dysc.utils;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

public class RedisUtil {

    //Redis服务器IP
    private static String ADDR = "192.168.70.131";
    //Redis的端口号
    private static int PORT = 6379;
    //redis的密码
    private static String PASS = "123456";
    //可用连接实例的最大数目,默认值为8;
    //如果赋值为-1,则表示不限制;如果pool已经分配了maxActive个jedis实例,则此时pool的状态为exhausted(耗尽)。
    private static int MAX_ACTIVE = 1024;
    //控制一个pool最多有多少个状态为idle(空闲的)的jedis实例,默认值也是8。
    private static int MAX_IDLE = 200;
    //等待可用连接的最大时间,单位毫秒,默认值为-1,表示永不超时。如果超过等待时间,则直接抛出JedisConnectionException;
    private static int MAX_WAIT = 10000;

    //在borrow一个jedis实例时,是否提前进行validate操作;如果为true,则得到的jedis实例均是可用的;
    private static boolean TEST_ON_BORROW = true;
    private static JedisPool jedisPool = null;
    /**
     * 初始化Redis连接池
     */
    static {
        try {
            JedisPoolConfig config = new JedisPoolConfig();
            config.setMaxTotal(MAX_ACTIVE);
            config.setMaxIdle(MAX_IDLE);
            config.setMaxWaitMillis(MAX_WAIT);
            config.setTestOnBorrow(TEST_ON_BORROW);
            jedisPool = new JedisPool(config, ADDR, PORT,10000,PASS);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取Jedis实例
     * @return
     */
    public synchronized static Jedis getJedis() {
        try {
            if (jedisPool != null) {
                Jedis resource = jedisPool.getResource();
                return resource;
            } else {
                return null;
            }
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 释放jedis资源
     * @param jedis
     */
    public static void returnResource(final Jedis jedis) {
        if (jedis != null) {
            jedisPool.close();
        }
    }
}

常用api

方法解释
new Jedis(host, port)创建jedis对象,参数host是redis服务器地址,参数port是redis服务端口
set(key,value)设置字符串类型的数据
get(key)获得字符串类型的数据
hset(key,field,value)设置哈希类型的数据
hget(key,field)获得哈希类型的数据
lpush(key,values)设置列表类型的数据
lpop(key)列表左面弹栈
rpop(key)列表右面弹栈
del(key)删除指定的key

管道 pipeline

有时我们会在短时间内发送大量互不依赖的命令(例如:后执行的命令不需要使用前面返回的结果)。由于网络传输不可避免的会造成一定的延迟,特别是在跨机器远程访问redis的时候,如果使用常规的方式,一条命令对应一次请求和响应的话,大量命令累计的延迟会显得很高。redis的设计者考虑到这一点,在底层的通信协议上,通过支持"管道(pipeline)"来解决这一问题。

关键词

Redis Pipelining: 客户端可以向服务器发送多个请求而无需等待回复, 最后只需一步即可读取回复.

RTT(Round Trip Time): 往返时间.

Pipeline 操作:request request  request  — — — — — response response response
单次操作:request —response  request —response request —response

jdeis测试

@Test
public void test04(){
    Jedis jedis = RedisUtil.getJedis();
    Pipeline pipelined = jedis.pipelined();
    long start = System.currentTimeMillis();
    pipelined.set("tes","1");
    for (int i = 0; i < 10000 ; i++) {
        pipelined.incr("tes");
    }
    pipelined.sync();
    long end = System.currentTimeMillis();
    System.out.println("tes为:"+jedis.get("tes"));
    System.out.println("管道技术的时间"+(end-start));


    long start2 = System.currentTimeMillis();
    jedis.set("tes2","1");
    for (int i = 0; i < 10000 ; i++) {
        jedis.incr("tes2");
    }
    long end2 = System.currentTimeMillis();
    System.out.println("tes2为:"+jedis.get("tes2"));
    System.out.println("普通时间"+(end2-start2));

    RedisUtil.returnResource(jedis);
}




结果

tes为:10001
管道技术的时间43
tes2为:10001
普通时间4270

redis事务

基本概念

Redis 事务的本质是一组命令的集合。事务支持一次执行多个命令,一个事务中所有命令都会被序列化。在事务执行过程,会按照顺序串行化执行队列中的命令,其他客户端提交的命令请求不会插入到事务执行命令序列中。

总结说:redis事务就是一次性、顺序性、排他性的执行一个队列中的一系列命令

三个阶段

一个事务从开始到执行会经历以下三个阶段:

  • 开始事务。
  • 命令入队。
  • 执行事务。

Redis事务没有隔离级别的概念:

批量操作在发送 EXEC 命令前被放入队列缓存,并不会被实际执行,也就不存在事务内的查询要看到事务里的更新,事务外查询不能看到。

Redis不保证原子性:

Redis中,单条命令是原子性执行的,但事务不保证原子性,且没有回滚。事务中任意命令执行失败,其余的命令仍会被执行。

例子

redis 127.0.0.1:7000> multi
OK
redis 127.0.0.1:7000> set a aaa
QUEUED
redis 127.0.0.1:7000> set b bbb
QUEUED
redis 127.0.0.1:7000> set c ccc
QUEUED
redis 127.0.0.1:7000> exec
1) OK
2) OK
3) OK

redis三大常见问题

缓存击穿

当用户高频访问key1的时候,key1突然失效,然后就会出现用户大量访问key1的请求全部打到db

如果说缓存雪崩是缓存这个桶彻底没了,那么缓存击穿就是桶上破了一个洞把

解决办法

  • 设置热点数据永远不过期。或者加上互斥锁

缓存穿透

缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求

比如用户一直去请求 id=-1 每一次都可以绕开缓存直接请求数据库 ,然后并发到达一定高度数据库也就挂了

解决办法:

  • 对于参数进行校验
    • 在接口层增加校验,比如用户鉴权校验,参数做校验,不合法的参数直接代码Return,比如:id 做基础校验,id <=0的直接拦截等
  • 布隆过滤器
    • 利用高效的数据结构和算法快速判断出你这个Key是否在数据库中存在,不存在你return就好了,存在你就去查了DB刷新KV再return。

缓存雪崩

大面积缓存失效,从而一下子打崩db

就电商来说吗,大多数热点key是采用定时任务的方式来进行刷新,或者是查不到之后去更新的

​ 同一时间大面积失效,那一瞬间Redis跟没有一样,那这个数量级别的请求直接打到数据库几乎是灾难性的,你想想如果打挂的是一个用户服务的库,那其他依赖他的库所有的接口几乎都会报错,如果没做熔断等策略基本上就是瞬间挂一片的节奏,你怎么重启用户都会把你打挂,等你能重启的时候,用户早就睡觉去了,并且对你的产品失去了信心,什么垃圾产品。

处理缓存雪崩简单,在批量往Redis存数据的时候,把每个Key的失效时间都加个随机值就好了,这样可以保证数据不会在同一时间大面积失效,我相信,Redis这点流量还是顶得住的。

setRedis(Key,value,time + Math.random() * 10000);

原文链接:https://blog.csdn.net/qq_35190492/article/details/102889333

redis和mysql的双写一致性问题

当用户数据发生修改的时候,mysql中的数据首先需要更新,之后删除redis中的记录,然后当修改完成之后再去修改redis中的记录

reids 分布式锁

什么是分布式锁

分布式锁是控制分布式系统或不同系统之间共同访问共享资源的一种锁实现,如果不同的系统或同一个系统的不同主机之间共享了某个资源时,往往需要互斥来防止彼此干扰来保证一致性。

分布式锁需要具备的条件

  • **互斥性。**在任意时刻,只有一个客户端能持有锁。
  • **不会发生死锁。**即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。
  • **具有容错性。**只要大部分的Redis节点正常运行,客户端就可以加锁和解锁。
  • **解铃还须系铃人。**加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了。

命令简介

setnx(SET if Not eXists)

为了保持数据的一致性,经常碰到需要对资源加锁的情形。 利用redis来实现分布式锁就是其中的一种实现方案。

setnx key value

将 key 的值设为 value ,当且仅当 key 不存在。

若给定的 key 已经存在,则 SETNX 不做任何动作。

设置成功,返回 1 。
设置失败,返回 0 。

原理探究

我们使用上面这个命令配合删除key的命令可以做到如下效果

redis> SETNX lock true    # 获得锁成功设置key
(integer) 1
进行的操作
redis> DEL lock    # 释放锁就是删除key
(integer) 1

这样的话存在问题 :在del之前报错就没办法释放锁

解决办法:

  • 为这个key设置过期时间
redis> SETNX lock true    # 获得锁成功
(integer) 1
redis> EXPIRE lock 5    # 设置5秒的过期时间
(integer) 1
进行的操作
redis> DEL lock    # 释放锁
(integer) 1

还有个问题:如果在SETNX和EXPIRE之间程序又发生了错误,当前锁又无法释放。所以说到底还是需要一个原子的操作,在获得锁的同时能够同时设置锁的过期时间。

注意在reids2.8之后set命令已经更为全面,在这只是作为原理探究

简单实现

在redis2.8之后set指令的参数更加全面 可以这么使用

在SET命令中,有很多选项可用来修改命令的行为。 以下是SET命令可用选项的基本语法。

redis 127.0.0.1:6379> SET KEY VALUE [EX seconds] [PX milliseconds] [NX|XX]

(1)设置指定的到期时间(以秒为单位)。

ShellEX seconds −

(2)设置指定的到期时间(以毫秒为单位)

PX milliseconds

(3)仅在键不存在时设置键

NX

(4)只有在键已存在时才设置

XX

示例

redis 127.0.0.1:6379> SET lock “requestid” EX 60 NX
OK
#意思是  ex表示过期时间为60秒 在lock不存在时才进行设置

将 key 的值设为 value ,当且仅当 key 不存在。

若给定的 key 已经存在,则 SETNX 不做任何动作。

设置成功,返回 1 。
设置失败,返回 0 。

原理探究

我们使用上面这个命令配合删除key的命令可以做到如下效果

redis> SETNX lock true    # 获得锁成功设置key
(integer) 1
进行的操作
redis> DEL lock    # 释放锁就是删除key
(integer) 1

这样的话存在问题 :在del之前报错就没办法释放锁

解决办法:

  • 为这个key设置过期时间
redis> SETNX lock true    # 获得锁成功
(integer) 1
redis> EXPIRE lock 5    # 设置5秒的过期时间
(integer) 1
进行的操作
redis> DEL lock    # 释放锁
(integer) 1

还有个问题:如果在SETNX和EXPIRE之间程序又发生了错误,当前锁又无法释放。所以说到底还是需要一个原子的操作,在获得锁的同时能够同时设置锁的过期时间。

注意在reids2.8之后set命令已经更为全面,在这只是作为原理探究

简单实现

在redis2.8之后set指令的参数更加全面 可以这么使用

在SET命令中,有很多选项可用来修改命令的行为。 以下是SET命令可用选项的基本语法。

redis 127.0.0.1:6379> SET KEY VALUE [EX seconds] [PX milliseconds] [NX|XX]

(1)设置指定的到期时间(以秒为单位)。

ShellEX seconds −

(2)设置指定的到期时间(以毫秒为单位)

PX milliseconds

(3)仅在键不存在时设置键

NX

(4)只有在键已存在时才设置

XX

示例

redis 127.0.0.1:6379> SET lock “requestid” EX 60 NX
OK
#意思是  ex表示过期时间为60秒 在lock不存在时才进行设置
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值