Redis学习应用

Redis的主要运用场景:

  1. 给热点数据加速查询,热点新闻,热点商品这些高访问量的信息
  2. 任务队列,比如秒杀,抢购,购票排队等
  3. 计时信息查询,各种排行榜,公交车站到站信息等
  4. 时效性信息控制,入验证码控制。
  5. 分布式锁
  6. 消息队列
  7. 分布式数据共享。

redis的安装

安装gcc,yum install gcc-c++
img

解压redis: tar -zxvf redis-5.0.7.tar.gz
img

安装redis: make
img

如果是安装的6.x版本的redis,可能会报错,因为gcc的版本过低,所以需要更新版本,命令如下:
yum -y install centos-release-scl yum -y install devtoolset-9-gcc devtoolset-9-gcc-c++ devtoolset-9-binutils scl enable devtoolset-9 bash

修改环境变量
echo "source /opt/rh/devtoolset-9/enable" >> /etc/profile
gcc -v
make
make install

修改config的启动方式为后台启动
img

启动redis服务,指定配置文件启动:./redis-server ../myredis/redis.conf
img
查看redis进程是否开启 ps -ef|grep redis:
img

关闭redis: shutdown
img

redis的性能压力测试

下图是测试的参数
img

在redis-benchmark所在的目录输入:./redis-benchmark -h localhost -p 6379 -c 500 -n 30000
img

redis的常用命令

set get命令,存值,取值
img
切换数据库,redis默认有16个数据库,默认使用0号数据库,可以通过select切换
img
img
清空当前数据库:flushdb,清空所有数据库:flushall
img
查询所有key: keys\*
img

查询数据库的大小:dbsize
img
查看key是否存在:exists keyName,存在返回1,否则返回0
img
移除Key: move keyName 1
img

为key设置过期时间: expire keyName timeout
img
查看key的剩余存活时间:ttl keyName
img
查看key的类型:type keyName
img

String操作

strlen:求value的长度,append:追加
img
自增,自减操作:incr incrby decr decrby
img
字符串的范围操作:getrange,setrange
img
设置过期时间:setex(如果存在Key则覆盖,不存在则创建),setnx(如果不存在就设置)
img
img
批量设置和获取值:mset,mget, msetnx(操作为原子性,要么都成功,要么都失败)
img
存取对象
img
组合操作:getset先获取再设置,如果不存在就返回nil
img

list操作

左插:lpush,左删:lpop
img
img

右插:Rpush,右删:rpop
img

通过下标获取元素:lindex
img

获取列表长度:llen
img

移除具体的值所在的键值对:lrem 列表 移除几个 移除的具体value
img
截取操作:ltrim 列表 截取的开始下标 截取的结束下标
img
**
rpoplpush:移除列表中的最后一个元素并将这个元素放到一个新的列表中**
img

判断列表是否存在:EXISTS list
img
lset:更新列表中指定下标的元素的value,前提是该下标必须有值,否则报错
img

在列表中插入值:前插和后插:linsert
img

Set操作

set是无序不重复集合
添加:sadd
img

查看所有: smembers
img

查询set的元素个数:scard
img

判断set中是否存在某元素:sismember
img

移除某个元素:srem
img

随机抽取一个元素:SRANDMEMBER
img

随机移除一个元素:spop
img

将一个集合中的元素移动到另一个集合中:smove
img

集合之间求交并补集:sinter sunite sdiff
准备工作:
img

补集(差集)
img
并集
img
交集
img

hash操作

hash的形式:key filed vlaue,相当于java中的map集合
向hash中添加值:hset
img
得到hash中的指定filed的值:hget
img

得到hash中的所有值:hgetall
img

批量添加和获取:hmset hmget
img

删除指定的字段:hdel
img

获取hash的字段长度:hlen
img
判断hash中的字段是否存在:hexist
img

获取所有的key(字段)hkeys,获取所有的value:hvals
img

指定增量:hincrby
img

zset操作

zset是有序不可重复的集合
添加数据:zadd
img

获取所有值,按照索引获取:zrange
img

获取所有值,按照score获取:zrangebyscore
img

移除元素:zrem
img

得到元素的个数:zcard
img
获取指定区间的成员数量:zcount
img

geospatial

添加位置信息:geoadd

有效的经度从-180度到180度。
有效的纬度从-85.05112878度到85.05112878度。
img

获取指定地区的维度和经度:geopos
img
获取两地之间的直线距离:geodist

m 表示单位为米。
km 表示单位为千米。
mi 表示单位为英里。
ft 表示单位为英尺。ft 表示单位为英尺。

img
查找以某一维度经度为中心,半径为指定距离的地区(附近的人):georadius
img
查找以某一城市为中心,半径为指定距离的地区(导航):georadiusbymember
img

bitmaps

bitmap即位存储,适用于只有两面性的内容。如:可以用bitmap存储用户是否登录,是否注册,是否打卡等等。bitmap的值只有0和1两个值。
存储数据:setbit
img
获取指定下标的值:getbit
img
统计bitmap中1的位数:bitcount
img

hyperloglogs

用于统计基数(即不重复的数据),可以用于统计网页访问量
添加:pfadd
img
计数:pfcount
img
合并:pfmerge
img

redis的事务操作

redis的单条命令保证原子性,但是redis的事务不保证原子性

redis的事务没有隔离级别。redis事务实际上就是一组命令的集合,这些命令在事务中会被序列化,然后按照命令的先后顺序一次性全部执行,在执行的过程中不能被打断。
即:redis的事务在执行时是一次性,顺序性和排他性的

开启事务:multi
命令入队:
执行事务:exec

img
放弃事务:discard
img

事务异常:

在开启事务后,如果编写的命令有语法错误,则会立即报错,且整个事务队列中的所有命令都不会执行在开启事务后,如果编写的命令有语法错误,则会立即报错,且整个事务队列中的所有命令都不会执行
img

如果在开启事务后,命令中没有语法错误,但是却有运行错误,即命令能够成功入队,但是不能够执行,则在事务提交后只有该命令抛错,其他命令可以正常执行。
img

redis的乐观锁

乐观锁:就是它会认为所有的事务都不会失败,因此它不会给事务上锁。它只会在更新的时候判断在此期间是否有人修改过数据
悲观锁:它认为所有的事务都会执行失败,因此做任何事情它都会加锁,这样是及其影响效率的。

redis的乐观锁实现命令:watch
单线程下运行:
img
多线程修改值
img
img
img
如果事务执行失败,需要先解锁,再加锁,解锁命令为:unwatch

jedis

注意:jedis操作redis的所有命令与redis的命令并无不同,因此这里只做简单的演示

导入依赖

pom.xml

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

测试连接

注意:这个时候如果连接远程服务器,需要将redis的配置文件中的blind修改为0.0.0.0,否则可能会报错,如果不行可以尝试将防火墙关闭,设置服务器的安全组等。

public class TestJedis {
    public static void main(String[] args) {
        //获取Jedis对象,该对象的所有操作在redis的原生命令中都存在
        //远程连接,也可以本地连接:127.0.0.1,端口号默认为6379
        Jedis jedis = new Jedis("8.140.1.206",6379);
        //测试连接
        System.out.println(jedis.ping());
    }
}

img

简单测试

由于在学习redis基础的时候命令都已经学习过,故这里只进行简单的演示

public class TestJedis {
    public static void main(String[] args) {
        //获取Jedis对象,该对象的所有操作在redis的原生命令中都存在
        //远程连接,也可以本地连接:127.0.0.1,端口号默认为6379
        Jedis jedis = new Jedis("8.140.1.206",6379);
        jedis.flushAll();
        jedis.set("username", "zhangsan");
        jedis.set("password", "1234");
        jedis.mset("k1","v1","k2","v2");
        System.out.println(jedis.mget("username", "password"));
        System.out.println(jedis.keys("*"));
        //测试连接
        System.out.println(jedis.ping());
    }
}

img

springBoot整合redis

环境搭建及简单入门

导入依赖:pom.xml

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.experimental</groupId>
            <artifactId>spring-native</artifactId>
            <version>${spring-native.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

配置文件:application.properties
spring.redis.port=6379
spring.redis.host=8.140.1.206

测试代码

@SpringBootTest
class RedisApplicationTests {
    @Autowired
    private RedisTemplate<String, String> template;
    @Test
    void contextLoads() {
        template.opsForValue().set("username","zhangsan");
        template.opsForValue().set("password","1234");
        System.out.println(template.keys("*"));
    }
}

img

自定义的redisTemplate

新建实体类User:

@Component
public class User {
    private String username;
    private String password;
    //此处省略setter getter tostring 有参无参构造方法
}

测试方法:

  @Test
    public void test01(){
        User user = new User("张三","1234");
        template.opsForValue().set("user", user);
        System.out.println(template.opsForValue().get("user"));
    }

序列化
如果实体类不进行序列化会如下错:
img

在实体类上实现序列化,再次运行

@Component
public class User implements Serializable{
    private String username;
    private String password;
}

img

img
发现虽然程序成功运行,但是redis的客户端key却是转义字符,这是因为redis默认的序列化方式是jdk,因此我们需要自定义序列化方式
img

自定义序列化

简单配置

@Configuration
public class RedisConfiguration {
    @Bean(name = "redisTemplate")
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String , Object> template = new RedisTemplate<>();
        Jackson2JsonRedisSerializer<Object> json = new Jackson2JsonRedisSerializer<Object>(Object.class);
        template.setDefaultSerializer(json);
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }
}

img

详细配置

@Configuration
public class RedisConfiguration {
    @Bean(name = "template")
    @SuppressWarnings("all")
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);
        //使用json序列化数据
        Jackson2JsonRedisSerializer<Object> json = new Jackson2JsonRedisSerializer<Object>(Object.class);
        ObjectMapper mapper = new ObjectMapper();
        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        json.setObjectMapper(mapper);
        //String的序列化
        StringRedisSerializer str = new StringRedisSerializer();
        template.setKeySerializer(str);
        template.setHashKeySerializer(str);
        template.setValueSerializer(json);
        template.setHashValueSerializer(json);
        template.afterPropertiesSet();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }
}

如果想在客户端看到中文原文的话可以这样:./redis-cli --raw
img
否则的话尽管你自定义了序列化,你看到的value也是这样的:
img

持久化

简介

由于redis是内存数据库,所以其数据断电易失,因此需要将数据保存到磁盘中。redis有两种持久化解决方案,分别是:RDBAOF

RDB

采用RDB作为持久化方案时,其会在指定的时间间隔内将内存中的数据集以快照形式写入磁盘,数据恢复时是将快照文件直接读到内存里。

RDB持久化的大致过程

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

优点:

1.RDB文件紧凑,全量备份,非常适合用于进行备份和灾难恢复。
2.生成RDB文件的时候,redis主进程会fork()一个子进程来处理所有保存工作,主进程不需要进行任何磁盘IO操作。
3.RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快。

缺点:

1.需要一定的时间间隔进程操作!如果redis意外宕机了,这个最后一次修改数据就没有的了!
2.fork进程的时候,会占用一定的内容空间!!

RDB的触发机制

1、save的规则满足的情况下,会自动触发rdb规则
2、执行 flushall 命令,也会触发我们的rdb规则!
3、退出redis,也会产生 rdb 文件!

AOF

AOF会将所有的写操作进行日志级别的记录(读操作不记录),它会将这些写操作追加到appendonly.aof文件中。

运行原理
img

持久化机制

(1)每修改同步always:同步持久化 每次发生数据变更会被立即记录到磁盘 性能较差但数据完整性比较好
(2)每秒同步everysec:异步操作,每秒记录 如果一秒内宕机,有数据丢失
(3)不同no:从不同步
img

优点

1)AOF可以更好的保护数据不丢失,一般AOF会每隔1秒,通过一个后台线程执行一次fsync操作,最多丢失1秒钟的数据。(2)AOF日志文件没有任何磁盘寻址的开销,写入性能非常高,文件不容易破损。
3)AOF日志文件即使过大的时候,出现后台重写操作,也不会影响客户端的读写。
4)AOF日志文件的命令通过非常可读的方式进行记录,这个特性非常适合做灾难性的误删除的紧急恢复。比如某人不小心用flushall命令清空了所有数据,只要这个时候后台rewrite还没有发生,那么就可以立即拷贝AOF文件,将最后一条flushall命令给删了,然后再将该AOF文件放回去,就可以通过恢复机制,自动恢复所有数据

缺点

1.对于同一份数据来说,AOF日志文件通常比RDB数据快照文件更大
2.AOF开启后,支持的写QPS会比RDB支持的写QPS低,因为AOF一般会配置成每秒fsync一次日志文件,当然,每秒一次fsync,性能也还是很高的
3.以前AOF发生过bug,就是通过AOF记录的日志,进行数据恢复的时候,没有恢复一模一样的数据出来

发布订阅

简介

Redis 发布订阅 (pub/sub) 是一种消息通信模式:发送者 (pub) 发送消息,订阅者 (sub) 接收消息。Redis 客户端可以订阅任意数量的频道。
下图展示了频道 channel1 , 以及订阅这个频道的三个客户端 —— client2 、 client5 和 client1 之间的关系:

img

当有新消息通过 PUBLISH 命令发送给频道 channel1 时, 这个消息就会被发送给订阅它的三个客户端:

img

Redis 发布订阅命令

img

1.PSUBSCRIBE pattern [pattern …]:订阅一个或多个符合给定模式的频道。
2 PUBSUB subcommand [argument [argument …]]:查看订阅与发布系统状态。
3 PUBLISH channel message:将信息发送到指定的频道。
4 PUNSUBSCRIBE [pattern [pattern …]]:退订所有给定模式的频道。
5 SUBSCRIBE channel [channel …]:订阅给定的一个或多个频道的信息。
6 UNSUBSCRIBE [channel [channel …]]:指退订给定的频道。

实例

首先启动三个redis的客户端
img
让其中两个订阅频道channel
img
向频道channel发送信息,hello,reids
img

观察订阅者客户端
img

主从复制

环境搭建

搭建一主二从集群环境
复制配置环境,redis01.conf是主机环境,redis02.conf,reids03.conf是从机环境
img

修改配置文件,将上述三个文件的配置文件的端口分别改为6379,6380,6381,修改其日志文件的名字,pid,rdb文件名
img
img
img
img

注意:每台redis服务器默认都是主节点
img

配置从机
img
img
img

如果redis有密码,需在从机中配置:masterauth 密码,否则会出现虽然配置了从机,但是主机却连接不上。

注意:采用salveof命令配置的话只是生效一次,redis服务器重新启动后又会变为主机。如果想要永久生效,需要在配置文件中配置:masterauth 主机ip 主机端口
从机只能读不能写,主机可以写,从机会同步主机的所有数据

主从复制复制原理

Slave 启动成功连接到 master 后会发送一个sync命令
Master 接到命令,启动后台的存盘进程,同时收集所有接收到的用于修改数据集命令,在后台进程执行
完毕之后,master将传送整个数据文件到slave,并完成一次完全同步。
全量复制:而slave服务在接收到数据库文件数据后,将其存盘并加载到内存中。
增量复制:Master 继续将新的所有收集到的修改命令依次传给slave,完成同步
但是只要是重新连接master,一次完全同步(全量复制)将被自动执行! 我们的数据一定可以在从机中看到!

哨兵模式

概述
主从切换技术的方法是:当主服务器宕机后,需要手动把一台从服务器切换为主服务器,这就需要人工干预,费事费力,还会造成一段时间内服务不可用。这不是一种推荐的方式,更多时候,我们优先考虑哨兵模式。
哨兵模式能够后台监控主机是否故障,如果故障了根据投票数自动将从库转换为主库。
哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程,它会独立运行。其原理是哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例。

img

这里的哨兵有两个作用
1.通过发送命令,让Redis服务器返回监控其运行状态,包括主服务器和从服务器。
2.当哨兵监测到master宕机,会自动将slave切换成master,然后通过发布订阅模式通知其他的从服务器,修改配置文件,让它们切换主机。
然而一个哨兵进程对Redis服务器进行监控,可能会出现问题,为此,我们可以使用多个哨兵进行监控。各个哨兵之间还会进行监控,这样就形成了多哨兵模式。

假设主服务器宕机,哨兵1先检测到这个结果,系统并不会马上进行failover过程,仅仅是哨兵1主观的认为主服务器不可用,这个现象成为主观下线。当后面的哨兵也检测到主服务器不可用,并且数量达到一定值时,那么哨兵之间就会进行一次投票,投票的结果由一个哨兵发起,进行failover[故障转移]操作。切换成功后,就会通过发布订阅模式,让各个哨兵把自己监控的从服务器实现切换主机,这个过程称为客观下线

配置:
sentinel monitor mysentinel1 172.26.53.216 6379 1
含义

mysentinel1:监控主节点的名称,可自定义
172.26.53.216:主节点主机名
6379:主节点的端口号
1:指的是有1个哨兵发现主节点失效时,才会真正让master结点失效。

img

启动哨兵:./redis-sentinel ../myredis/sentinel.conf
img

注意:如果主从服务器设置了密码,需要主从机服务器的密码一致,并且需要在sentinel.conf配置:sentinel auth-pass mysentinel1 你的reids密码

挂掉6379主机,观察哨兵
img
img
在把原来的主机启动,观察到原来的主机会变为新主机的从机
img

缓存穿透和雪崩

缓存处理流程
img
前台请求,后台先从缓存中取数据,取到直接返回结果,取不到时从数据库中取,数据库取到更新缓存,并返回结果,数据库也没取到,那直接返回空结果。

缓存穿透

描述:

缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。

解决方案
1.布隆过滤器

布隆过滤器是一种数据结构,对所有可能查询的参数以hash形式存储,在控制层先进行校验,不符合则丢弃,从而避免了对底层存储系统的查询压力;

img

2.缓存空对象
img
当存储层不命中后,即使返回的空对象也将其缓存起来,同时会设置一个过期时间,之后再访问这个数据将会从缓存中获取,保护了后端数据源;
缺点:

1、如果空值能够被缓存起来,这就意味着缓存需要更多的空间存储更多的键,因为这当中可能会有很多的空值的键;
2、即使对空值设置了过期时间,还是会存在缓存层和存储层的数据会有一段时间窗口的不一致,这对于需要保持一致性的业务会有影响

缓存击穿

描述:

缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力
解决方案:
1.设置热点数据永远不过期。
2.加互斥锁,互斥锁参考代码如下:

缓存雪崩

描述:

缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。和缓存击穿不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。

解决方案:

1.redis高可用
这个思想的含义是,既然redis有可能挂掉,那我多增设几台redis,这样一台挂掉之后其他的还可以继续工作,其实就是搭建的集群。(异地多活!)
2.限流降级
这个解决方案的思想是,在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。
3.数据预热
数据加热的含义就是在正式部署之前,我先把可能的数据先预先访问一遍,这样部分可能大量访问的数据就会加载到缓存中。在即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值