String数据类型、List 数据类型、Hash数据类型(散列类型)、set数据类型(无序集合)、Sorted Set数据类型 (zset、有序集合)。
1、String是 redis 最基本的类型,最大能存储 512MB 的数据,String类型是二进制安全的,即可以存储任何数据、比如数字、图片、序列化对象等。INCR key: key值递增加1 ( key值必须为整数)、DECR key: key值递增减1 (key值必须为整数)、GETSET key value: 获取key值并返回,同时给key设置新值。
2、Hash数据类型(散列类型)、hash用于存储对象。可以采用这样的命名方式:对象类别和ID构成键名,使用字段表示对象的属性,而字段值则存储属性值。 如:存储 ID 为 2 的汽车对象。如果Hash中包含很少的字段,那么该类型的数据也将仅占用很少的磁盘空间。每一个Hash可以存储4294967295个键值对。
3、Sorted Set数据类型,有序集合,元素类型为Sting,元素具有唯一性, 不能重复每个元素都会关联–个double类型的分数score(表示权重),可以通过权重的大小排序,元素的score可以相同应用范围:可以用于一个大型在线游戏的积分排行榜。每当玩家的分数发生变化时,可以执行ZADD命 令更新玩家的分数,此后再通过ZRANGE命令获取积分TOP10的用户信息。
4、Redis内部使用一个redisObject对象来表示所有的key和value,type代表一个value对象具体是何种数据类型,encoding是不同数据类型在redis内部的存储方式,比如:type=string代表value存储的是一个普通字符串,那么对应的encoding可以是raw或者是int,如果是int则代表实际redis内部是按数值型类存储和表示这个字符串的,当然前提是这个字符串本身可以用数值表示,比如:"123" "456"这样的字符串。
问:redis为什么快
1、完全基于内存,绝大部分请求是纯粹的内存操作,非常快速。数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1);
2、数据结构简单,对数据操作也简单,Redis中的数据结构是专门进行设计的;
3、采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗;
4、使用多路I/O复用模型,非阻塞IO;
5、使用底层模型不同,它们之间底层实现方式以及与客户端之间通信的应用协议不一样,Redis直接自己构建了VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求;
什么是Redis持久化?Redis有哪几种持久化方式?优缺点是什么?
持久化就是把内存的数据写到磁盘中去,防止服务宕机了内存数据丢失。
Redis 提供了两种持久化方式:RDB(默认) 和AOF
比较:
1、aof文件比rdb更新频率高,优先使用aof还原数据。
2、aof比rdb更安全也更大
3、rdb性能比aof好
4、如果两个都配了优先加载AOF
redis通讯协议(RESP ),能解释下什么是RESP?有什么特点?
RESP 是redis客户端和服务端之前使用的一种通讯协议;
RESP 的特点:实现简单、快速解析、可读性好
什么是缓存穿透?如何避免?什么是缓存雪崩?何如避免?
缓存穿透
一般的缓存系统,都是按照key去缓存查询,如果不存在对应的value,就应该去后端系统查找(比如DB)。一些恶意的请求会故意查询不存在的key,请求量很大,就会对后端系统造成很大的压力。这就叫做缓存穿透。
如何避免?
1:对查询结果为空的情况也进行缓存,缓存时间设置短一点,或者该key对应的数据insert了之后清理缓存。
2:对一定不存在的key进行过滤。可以把所有的可能存在的key放到一个大的Bitmap中,查询时通过该bitmap过滤。
缓存雪崩
当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候,会给后端系统带来很大压力。导致系统崩溃。
如何避免?
1:在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。
2:做二级缓存,A1为原始缓存,A2为拷贝缓存,A1失效时,可以访问A2,A1缓存失效时间设置为短期,A2设置为长期
3:不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。
三、redis的命中率
用户在访问缓存中的数据时,并不是100%会返回的,可能有不返回的情况,这种情况下就得去DB查询数据,为了提高系统的性能,我们需要提高缓存的命中率。redis中info
命令可以查询到基本信息。
命中率 = keyspace_hits / (keyspace_misses + keyspace_hits)
合理的redis配置可以提高命中率
如何保证mysql和redis数据一致性
1.延迟双删 在写库前后都进行【redis.del(key)】操作,并且设定合理的超时时间;
2.异步更新缓存(基于订阅binlog的同步机制)
技术整体思路:
MySQL binlog增量订阅消费+消息队列+增量数据更新到redis
1)读Redis:热数据基本都在Redis
2)写MySQL:增删改都是操作MySQL
3)更新Redis数据:MySQ的数据操作binlog,来更新到Redis
Redis更新
(1)数据操作主要分为两大块:
一个是全量(将全部数据一次写入到redis)一个是增量(实时更新)
这里说的是增量,指的是mysql的update、insert、delate变更数据。
(2)读取binlog后分析 ,利用消息队列,推送更新各台的redis缓存数据。
这样一旦MySQL中产生了新的写入、更新、删除等操作,就可以把binlog相关的消息推送至Redis,Redis再根据binlog中的记录,对Redis进行更新。
其实这种机制,很类似MySQL的主从备份机制,因为MySQL的主备也是通过binlog来实现的数据一致性。
这里可以结合使用canal(阿里的一款开源框架),通过该框架可以对MySQL的binlog进行订阅,而canal正是模仿了mysql的slave数据库的备份请求,使得Redis的数据更新达到了相同的效果。
当然,这里的消息推送工具你也可以采用别的第三方:kafka、rabbitMQ等来实现推送更新Redis
Redis 的数据淘汰策略有哪些
a、noeviction:返回错误当内存限制达到,并且客户端尝试执行会让更多内存被使用的命令。
b、allkeys-lru: 尝试回收最少使用的键(LRU),使得新添加的数据有空间存放。
c、volatile-lru: 尝试回收最少使用的键(LRU),但仅限于在过期集合的键,使得新添加的数据有空间存放。
d、allkeys-random: 回收随机的键使得新添加的数据有空间存放。
e、volatile-random: 回收随机的键使得新添加的数据有空间存放,但仅限于在过期集合的键
f、volatile-ttl: 回收在过期集合的键,并且优先回收存活时间(TTL)较短的键,使得新添加的数据有空间存放。
redis实现延时队列的两种方式
一,redis的过期key监控
1.开启过期key监听 在redis的配置里把这个注释去掉 notify-keyspace-events Ex 然后重启redis
2. 使用redis过期监听实现延迟队列 继承KeyExpirationEventMessageListener类,实现父类的方法,就可以监听key过期时间了。当有key过期,就会执行这里。这里就把需要的key过滤出来,然后发送给消息队列。
注意:尽量单机运行,因为多台机器都会执行,浪费cpu,增加数据库负担。二是,机器频繁部署的时候,如果有时间间隔,会出现数据的漏处理。
一,redis的zset实现延迟队列
1.生产者很简单,其实就是利用zset的特性,给一个zset添加元素而已,而时间就是它的score。
1 public void produce(Integer taskId, long exeTime) { 2 System.out.println("加入任务, taskId: " + taskId + ", exeTime: " + exeTime + ", 当前时间:" + LocalDateTime.now()); 3 4 RedisOps.getJedis().zadd(RedisOps.key, exeTime, String.valueOf(taskId)); 5 }
2.消费者代码,把已经过期的zset中的元素给删除掉,然后处理数据。
1 public void consumer() { 2 Executors.newSingleThreadExecutor().submit(new Runnable() { 3 @Override 4 public void run() { 5 while (true) { 6 Set<String> taskIdSet = RedisOps.getJedis().zrangeByScore(RedisOps.key, 0, System.currentTimeMillis(), 0, 1); 7 if (taskIdSet == null || taskIdSet.isEmpty()) { 8 System.out.println("没有任务"); 9 10 } else { 11 taskIdSet.forEach(id -> { 12 long result = RedisOps.getJedis().zrem(RedisOps.key, id); 13 if (result == 1L) { 14 System.out.println("从延时队列中获取到任务,taskId:" + id + " , 当前时间:" + LocalDateTime.now()); 15 } 16 }); 17 } 18 try { 19 TimeUnit.MILLISECONDS.sleep(100); 20 } catch (InterruptedException e) { 21 e.printStackTrace(); 22 } 23 } 24 } 25 }); 26 }