一、是什么?
redis是高性能key value非关系型数据库,C语言开发,基于内存的,速度极快,用作分布式缓存和分布式锁,每秒处理10万次读写。支持事务持久化lua脚本集群。
5种数据类型:String,list,set,sorted set,hash(全部通过redisObject对象进行存储)
- String,字符串操作,整数和浮点数自增或自减操作,用作键值对缓存。也可以存图片或序列化对象
- List,列表型数据,粉丝列表,文章列表类的,最新消息排行,可以当做消息队列用。双向链表,实现反向查找和遍历,带来了额外的内存开销
- Set,交并差操作。无顺序不重复。判断元素是否在集合汇总。
- Zset,排序不重复。排行榜,带权重的消息队列。内部使用HashMap和跳跃表(SkipList)来保证数据的存储与有序,HashMap放成员到Score的映射,跳跃表放所有成员,排序依据是HashMap中的Score,使用跳跃表的数据结构可以获得较高的查询效率,并且实现上简单。Redis缓存实现排序功能 海量积分数据实时排名(数据结构)
返回用户7日内的文章,且文章要求不重复 ,zadd userId score title 这里的score为当前时间的时间戳; ZREMRANGEBYSCORE key 0 score 这里的score设为当前时间前7天对应的时间的时间戳;(具体时间戳可以用java Calander类计算得到)这里可以启动一个定时任务去定时调用这个命令即可 - Hash,操作键值对,特适合存储对象。存储读取修改用户属性
二、特性
支持丰富的数据类型,速度快(读11万次每秒,写8万次每秒),可持久化(AOF,RDB)
单线程,Redis使用队列技术,将并发访问变为串行访问,消除了传统数据库串行控制的开销,消除了并发竞争
支持事务和主从复制
数据库容量受内存限制,不能用作海量数据的高性能读写。
不具备自动容错和恢复功能,主机从机的宕机都会导致前端部分请求失败,需要等待机器重启或主动切换前端IP
主机宕机,切换IP后还会导致数据不一致,降低系统可用性
1、Redis为什么这么快?
完全基于内存,绝大部分请求是存粹内存操作,速度非常快。数据结构简单。单线程避免了不必要的上下文切换和竞争条件,不用考虑锁的问题以及加锁释放锁,不存在死锁导致性能开销。使用IO多路复用模型,是非阻塞IO。Redis直接构建了VM机制,一般系统调用函数,会浪费性能。
三、使用场景
- 网站排行榜
- 计数器
- 点赞数,关注数,粉丝数,已读数,未读数,帖子数,收藏数,阅读数,接口一分钟限制多少请求
- 特点:实时性要求高,写的频率高
-
@SpringBootTest public class Redis3ApplicationTests { @Test public void increase() throws InterruptedException { long currentTimeMillis = System.currentTimeMillis(); Thread thread = new Thread(new Runnable() { Jedis jedis = RedisUtil.getJedis(); @Override public void run() { for (int i = 0; i < 10000; i++) { Long num = jedis.incr("num"); } } }); thread.start(); thread.join(); System.out.println("耗时"+(System.currentTimeMillis() - currentTimeMillis)); } } 计数器
package com.yhq.redis3.util; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; import redis.clients.jedis.JedisPoolConfig; public class RedisUtil { //Redis服务器IP private static String ADDR = "127.0.0.1"; //Redis的端口号 private static int PORT = 6379; //访问密码 private static String AUTH = "root123456"; //可用连接实例的最大数目,默认值为8; //如果赋值为-1,则表示不限制;如果pool已经分配了maxActive个jedis实例,则此时pool的状态为exhausted(耗尽)。 private static int MAX_TOTAL = 8; //最小空闲连接数, 默认0 private static int MIN_IDLE=0; //控制一个pool最多有多少个状态为idle(空闲的)的jedis实例,默认值也是8。 //最大空闲连接数, 默认8个 private static int MAX_IDLE = 8; //获取连接时的最大等待毫秒数(如果设置为阻塞时BlockWhenExhausted),如果超时就抛异常, 小于零:阻塞不确定的时间, 默认-1 //等待可用连接的最大时间,单位毫秒,默认值为-1,表示永不超时。如果超过等待时间,则直接抛出JedisConnectionException; private static int MAX_WAIT = -1; private static int TIMEOUT = 10000; //连接耗尽时是否阻塞, false报异常,ture阻塞直到超时, 默认true private static boolean BLOCK_WHEN_EXHAUSTED = false; //设置的逐出策略类名, 默认DefaultEvictionPolicy(当连接超过最大空闲时间,或连接数超过最大空闲连接数) private static String EVICTION_POLICY_CLASSNAME="org.apache.commons.pool2.impl.DefaultEvictionPolicy"; //是否启用pool的jmx管理功能, 默认true private static boolean JMX_ENABLED=true; //MBean ObjectName = new ObjectName("org.apache.commons.pool2:type=GenericObjectPool,name=" + "pool" + i); 默认为"pool", JMX不熟,具体不知道是干啥的...默认就好. private static String JMX_NAME_PREFIX="pool"; //是否启用后进先出, 默认true private static boolean LIFO=true; //逐出连接的最小空闲时间 默认1800000毫秒(30分钟) private static long MIN_EVICTABLE_IDLE_TIME_MILLIS=1800000L; //对象空闲多久后逐出, 当空闲时间>该值 且 空闲连接>最大空闲数 时直接逐出,不再根据MinEvictableIdleTimeMillis判断 (默认逐出策略) private static long SOFT_MIN_EVICTABLE_IDLE_TIME_MILLIS=1800000L; //每次逐出检查时 逐出的最大数目 如果为负数就是 : 1/abs(n), 默认3 private static int NUM_TESTS_PER_EVICYION_RUN=3; //在borrow一个jedis实例时,是否提前进行validate操作;如果为true,则得到的jedis实例均是可用的; //在获取连接的时候检查有效性, 默认false private static boolean TEST_ON_BORROW = false; //在空闲时检查有效性, 默认false private static boolean TEST_WHILEIDLE=false; //逐出扫描的时间间隔(毫秒) 如果为负数,则不运行逐出线程, 默认-1 private static long TIME_BERWEEN_EVICTION_RUNS_MILLIS=-1; private static JedisPool jedisPool = null; /** * 初始化Redis连接池 */ static { try { JedisPoolConfig config = new JedisPoolConfig(); config.setBlockWhenExhausted(BLOCK_WHEN_EXHAUSTED); config.setEvictionPolicyClassName(EVICTION_POLICY_CLASSNAME); config.setJmxEnabled(JMX_ENABLED); config.setJmxNamePrefix(JMX_NAME_PREFIX); config.setLifo(LIFO); config.setMaxIdle(MAX_IDLE); config.setMaxTotal(MAX_TOTAL); config.setMaxWaitMillis(MAX_WAIT); config.setMinEvictableIdleTimeMillis(MIN_EVICTABLE_IDLE_TIME_MILLIS); config.setMinIdle(MIN_IDLE); config.setNumTestsPerEvictionRun(NUM_TESTS_PER_EVICYION_RUN); config.setSoftMinEvictableIdleTimeMillis(SOFT_MIN_EVICTABLE_IDLE_TIME_MILLIS); config.setTestOnBorrow(TEST_ON_BORROW); config.setTestWhileIdle(TEST_WHILEIDLE); config.setTimeBetweenEvictionRunsMillis(TIME_BERWEEN_EVICTION_RUNS_MILLIS); jedisPool = new JedisPool(config, ADDR, PORT, TIMEOUT, AUTH); } 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 close(final Jedis jedis) { if (jedis != null) { jedis.close(); } } } jedispool 和 计数器
- 时间轴
- 队列实现
- rpush生产消息,lpop消费消息。当lpop没有消息的时候,要适当sleep一会再重试
- 分布式锁实现
- Redlock算法
- session统一缓存
- 网站访问限流
- 手写redis
- 单点登录
- 发布订阅
四、Redis线程模型
Redis 基于 Reactor 模式开发了自己的网络事件处理器: 这个处理器被称为文件事件处理器(file event handler)。文件事件处理器包括4个组成部分:套接字、I/O多路复用程序、文件事件分派器以及事件处理器。
- 为了对连接服务器的各个客户端进行应答, 服务器要为监听套接字关联连接应答处理器。
- 为了接收客户端传来的命令请求, 服务器要为客户端套接字关联命令请求处理器。
- 为了向客户端返回命令的执行结果, 服务器要为客户端套接字关联命令回复处理器。
- 当主服务器和从服务器进行复制操作时, 主从服务器都需要关联特别为复制功能编写的复制处理器。
- 等等。
时间事件应用实例:持续运行的redis服务器需要定期对自身的资源和状态进行检查和调整,从而确保服务器可以长期稳定的进行,这些定期操作主要工作包括:
- 更新服务器的各种统计信息,比如时间、内存占用、数据库占用等。
- 清理数据库中的过期键值对
- 关闭和清理连接失效的客户端
- 尝试进行AOF和RDB持久化操作
- 如果服务器是主服务器,那么对从服务器进行定期同步
- 如果处于集群模式,对集群进行定期同步和连接测试
五、缓存一致性问题
六、Redis持久化
持久化方案:Rdb和Aof。分为手动触发和自动触发两种。默认开启RDB,要实现AOF,需要在配置文件中配置 appendonly yes。
RDB方案,文件紧凑,体积小,网络传输快,适合全量复制,恢复速度比AOF快很多,对性能的影响相对较小。做不到实时持久化,容易造成数据大量丢失。切RDB文件格式兼容性差。
AOF方案,主流,文件大,恢复速度慢,对性能影响大,但支持秒级持久化,兼容性好。
选择策略:数据丢失没关系,则可不进行持久化,实时性要去不高,选择RDB,如果是秒级,则选AOF,但大多数会配置主从环境。
master最好不做持久化工作,如RDB内存快照和AOF文件。
redis服务器进程就是一个事件循环,这个循环中的文件时间负责接收客户端的命令请求以及向客户端发送命令回复,而时间事件则负责执行像serverCron函数这样需要定时运行的函数。
AOF是通过保存被执行的写命令来记录数据库状态,所以AOF文件中的内容会越来越多,文件的体积也越来越大,对redis服务器、宿主机、AOF还原都有影响,因此,redis提供AOF文件重写功能,通过重写,redis服务器可以创建一个新的AOF文件来替代现有的AOF文件,新AOF不会有冗余命令。AOF重写并不需要对现有AOF文件进行任何读取、分析、写入操作,这个功能是通过服务器当前的数据库状态来实现的。因为redis是单线程处理命令,如果由服务器直接调用重写命令,那么重写执行期间将无法处理客户端发来的请求,故放到子进程里执行,这样做既可以达到父进程继续处理客户端命令的请求,而且紫禁城带有服务器进程的数据副本,使用子进程而不是线程,可以避免使用锁的情况下,保证数据的安全性。但如何保证一致性呢,redis服务器执行完一个写命令之后,会将这个写命令发送给AOF缓冲区和AOF重写缓冲区,AOF缓冲区会被定期写入到AOF文件,现有AOF文件的处理工作正常进行。当子进程完成重写工作后,会向父进程发送一个信号,父进程在接收到该信号之后,会调用一个信号处理函数(将AOF重写缓冲区的内容写入到新AOF文件中,对新的AOF改名,覆盖现有AOF文件,该函数会造成阻塞)
七、Redis与Spring结合,序列化
RedisTemplate(序列化java对象)和StringRedisTemplate主要区别是使用的序列化类,RedisTemplate试用JdkSerializationRedisSerializer序列化对象,StringRedisTemplate使用的是StringRedisSerializer序列化String。
JacksonJsonRedisSerializer(序列化object对象为json字符串)
jdkSerializationRedisSerializer 序列化后长度最小,jackson2JsonRedisSerializer效率最高。推荐key使用stringRedisSerializer,value用Jackson2JsonRedisSerializer,如果空间敏感,用JdkSerializationRedisSerializer
genericJackson2JsonRedisSerializer序列化时间:64 序列化后的大小2786 genericJackson2JsonRedisSerializer反序列化时间:48 jackson2JsonRedisSerializer序列化时间:2 序列化后的大小1711 jackson2JsonRedisSerializer反序列化时间:2 jdkSerializationRedisSerializer序列化时间:22 序列化后的大小415 jdkSerializationRedisSerializer反序列化时间:3 stringRedisSerializer序列化时间:0 序列化后的大小415 stringRedisSerializer反序列化时间:0
@Bean(name = "redisTemplate") public RedisTemplate<String, Object> getRedisTemplate(RedisConnectionFactory factory) { RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>(); redisTemplate.setConnectionFactory(factory); redisTemplate.setKeySerializer(new StringRedisSerializer()); // key的序列化类型 Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); ObjectMapper objectMapper = new ObjectMapper(); objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); jackson2JsonRedisSerializer.setObjectMapper(objectMapper); redisTemplate.setValueSerializer(jackson2JsonRedisSerializer); // value的序列化类型 return redisTemplate; } RedisTemplate
八、Redis与事务
不支持事务回滚,不支持事务原子性,事务中任意命令执行失败,其余命令扔回执行
事务:原子性,一致性,隔离性,持久性 。 redis事务只支持一致性和隔离性
redis事务的隔离性,redis是单进程程序,不会对事务进行终端,事务可以直接运行完事务队列的全部命令。因此,redis事务总带有隔离性
redis事务就是一次性、顺序性、排他性的执行一个队列中的一系列命令。
相关命令:
- watch命令 CAS监听多个键,一旦键被改,之后的事务不会执行,监控一直持续到exec命令
- multi命令 开启事务,执行之后,客户端发送的命令被放到队列中,直到exec命令
- exec命令 执行事务块的命令,按顺序返回命令的返回值,当操作被打断,返回nil
- discard命令 清空事务队列,放弃事务
- unwatch 命令 取消对key的监控
事务传播机制(默认required):
- required, A调B,无事务再创建,有事务,则使用相同事务,B若回滚,则整个事务回滚
- required_new, 和1比,无论事务是否存在,永远开启新事务,B回滚,不会导致A回滚
- nested, 和required_new 类似
- supports, 方法调用时,有事务则用事务,无事务就不用事务
- not_supported , 强制方法不在事务中执行,若有事务,在方法调用到结束阶段先挂起事务
- never 强制不能有事务,有事务,则抛出异常
- mandatory 强制必须有事务,如果没有事务就抛出异常
九、Redis与Lua脚本
十、Pipeline管道
一次操作可能需要执行多个命令,一个一个去执行命令会浪费很多网络消耗时间,并不是原子性执行。
十一、Redis与发布订阅
十二、Reids复制
主从复制。复制是高可用Redis的基础,哨兵和集群都是在复制基础上实现高可用,复制主要实现多机备份,以及对读操作的负载均衡和简单的故障恢复。缺陷:故障恢复无法自动化,写操作无法负载均衡,存储能力受到单机的限制。
操作:配置文件添加: masterauth root123456。 向从服务器发送命令:slaveof 127.0.0.1 6379
十三、Redis集群(重点)
redis通讯协议,RESP是redis客户端和服务端之间的通讯协议
十四、哨兵机制(重点)
在复制的基础上,哨兵实现了自动化的故障恢复。
哨兵也是分布式的,哨兵集群,互相协调工作,故障转移时,判断master是否宕机,需要分布式选举,大部分哨兵同意才行。哨兵+主从架构,只是保证redis的高可用。
哨兵的功能:
- 集群监控:负责监控redis master和slave进程是否正常工作
- 消息通知:如果redis实例有故障,哨兵负责发送消息作为报警通知给管理员
- 故障转移:master挂掉了,会自动移到slave
- 配置中心:如果故障转移发生了,通知client客户端新的master地址
十五、Spring与哨兵结合
十六、缓存击穿、缓存雪崩
setnx,可以不存在时,设置成功,返回1,否则返回0。etnx来争抢锁,抢到之后,再用expire给锁加一个过期时间防止锁忘记了释放
缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力
缓存击穿方案,缓存失效时,不loaddb,setnx返回1则loaddb并设置缓存,否则重试get缓存
缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。和缓存击穿不同的是, 缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。
十七、布隆过滤器
类似于hashset,快速判断元素是否在集合中
十八、海量数据
利用SCAN系列命令(SCAN、SSCAN、HSCAN、ZSCAN)完成数据迭代
十九、数据恢复与转移
二十、Redis6种淘汰策略
- 不删除策略
- LRU(less recently used)最少使用的key
- LRU volatile 只针对过期key的部分
- random
- random volatile
- volatile-ttl : 只限于设置了expire的部分
删除策略
- 定时删除
- 保证过期键尽快被删除,内存友好。占用cpu,影响服务的响应时间和吞吐量
- 惰性删除
- 获取键时进行过期键检查并是否删除,不浪费cpu,但是内存也不会释放,造成内存泄漏
- 定期删除
- 限制删除的执行时长和频率,减少CPU的影响
二十一、Redis内存划分
二十二、Redis应用实战
二十三、慢查询分析,性能测试
二十四、Redis 与Spring boot Cache
Redis之Spring boot集成redis和Spring cache
二十五、redis 连接池
二十六、bitmap
二十七、Lettuce
二十八、Redis常见配置
- appendonly no:是否开启AOF
- appendfilename "appendonly.aof":AOF文件名
- dir ./:RDB文件和AOF文件所在目录
- appendfsync everysec:fsync持久化策略
- no-appendfsync-on-rewrite no:AOF重写期间是否禁止fsync;如果开启该选项,可以减轻文件重写时CPU和硬盘的负载(尤其是硬盘),但是可能会丢失AOF重写期间的数据;需要在负载和安全性之间进行平衡
- auto-aof-rewrite-percentage 100:文件重写触发条件之一
- auto-aof-rewrite-min-size 64mb:文件重写触发提交之一
- aof-load-truncated yes:如果AOF文件结尾损坏,Redis启动时是否仍载入AOF文件
Redis常用管理命令
# dbsize 返回当前数据库 key 的数量。 # info 返回当前 redis 服务器状态和一些统计信息。 # monitor 实时监听并返回redis服务器接收到的所有请求信息。 # shutdown 把数据同步保存到磁盘上,并关闭redis服务。 # config get parameter 获取一个 redis 配置参数信息。(个别参数可能无法获取) # config set parameter value 设置一个 redis 配置参数信息。(个别参数可能无法获取) # config resetstat 重置 info 命令的统计信息。(重置包括:keyspace 命中数、 # keyspace 错误数、 处理命令数,接收连接数、过期 key 数) # debug object key 获取一个 key 的调试信息。 # debug segfault 制造一次服务器当机。 # flushdb 删除当前数据库中所有 key,此方法不会失败。小心慎用 # flushall 删除全部数据库中所有 key,此方法不会失败。小心慎用
Redis工具命令
#redis-server:Redis 服务器的 daemon 启动程序 #redis-cli:Redis 命令行操作工具。当然,你也可以用 telnet 根据其纯文本协议来操作 #redis-benchmark:Redis 性能测试工具,测试 Redis 在你的系统及你的配置下的读写性能 $redis-benchmark -n 100000 –c 50 #模拟同时由 50 个客户端发送 100000 个 SETs/GETs 查询 #redis-check-aof:更新日志检查 #redis-check-dump:本地数据库检查