Redis是什么
基于键值的高性能存储服务器
可做于缓存数据库使用
数据结构存储
消息中间件
Redis优势/特点
最大的亮点就是速度快,因为数据存在内存中。Redis读的数度是110000次/s,写的数度是81000次/s。(类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1))
支持丰富的数据结构存储。
Redis的所有操作都是原子性。多个操作也支持原子性,(通过MULTI和EXEC指令包起来。)
丰富的特性:可用于缓存,消息,按key设置过期时间,过期后将会自动删除
持久化数据
数据类型
常用:
1、String:是二进制安全的。意思是redis的string可以包含任何数据。比如jpg图片或者序列化的对象 。string类型是Redis最基本的数据类型,一个键最大能存储512MB。
命令:get、set、mget批量获取多个key值(mget name name2 name3 。。。)、incr && incrby 对key对应的值进行加加操作 decr 减减
2、Hash:键值对集合,是一个String类型的field和value的映射表,适合用于存储对象
命令:hset、hget、hgetall。
3、List:链表(底层是双向链表,对两端的操作性能很高,通过下标查询性能很低。按插入顺序排序)
命令:lpush头部添加一个元素、lrange获取key对应的指定范围的元素,-1获取所有、lpop从key对应的尾巴删除一个元素、rpush尾部添加元素、rpop从key对应的尾巴删除一个元素
4、Set:类似list的无序集合,不可重复,底层是一个value为null的hash表
命令:sadd添加、smenbers获取key对应的所有元素、spop随机返回并删除key对应的一个元素
5、Sorter Set:有序集合,不可重复。有每个元素都需要指定一个分数,根据分数对元素进行升序排序,如果多个元素有相同的分数,则以字典序进行升序排序,sorted set因此非常适合实现排名
命令:zadd添加、zrange获取指定范围的元素,-1获取所有、zrem删除key对应的一个元素。
扩展:
6、Bitmaps
7、Hyperlogs
8、Geospatial Indexes
注意事项:
key不要太长,尽量不要超过1024字节,这不仅消耗内存,而且会降低查找的效率;
key也不要太短,太短的话,key的可读性会降低;
详细 http://www.runoob.com/w3cnote/redis-intro-data-structure.html
Redis发布/订阅
他是Redis的一种消息通信,发送者(pub)发送信息,接收者(sub)接收信息。
Redis客户端可以订阅任意数量的频道(有新消息通过 PUBLISH 命令发送给频道时, 这个消息就会被发送给订阅它的客户端)
Redis持久化
RDB快照(redis database)(默认)
在指定时间间隔内,将内存中的数据作为一个快照文件(snapshot)写入到磁盘,读取的时候也是直接读取snapshot文件到内存中
持久化过程:redis单独创建(fork)一个进程来持久化,会先将数据写入临时文件中,待上次持久化结束后,会将该临时文件替上次持久化文件,比AOF高效,但是最后一个数据可能会丢失
fork:在Linux中,fork()会产生一个跟主进程一样的子进程,出于效率考虑,主进程和子进程会公用一段物理内存,当发生改变的时候,才会把主进程复制一份给子进程
Redis备份的文件在redis.conf中设置,dbfilename默认为:dump.rdb
RDB保存策略:
1、900s 1 file change
2、300s 10 file change
3、60s 10000 file change
RDB的备份:
1、config get dir得到备份的文件夹
2、复制备份文件
RDB恢复:
1、关闭redis
2、将备份文件复制到工作目录下
3、启动redis,自动加载
RDB优点:RDB会生成多个数据文件,每个数据文件都代表了某一个时刻中redis的数据,这种多个数据文件的方式,非常适合做冷备(离线备份)。
RDB缺点:数据太大时,比较消耗性能。一段时间保存一次快照,宕机时最后一次可能没有保存
AOF(append of file)
AOF以日志形式记录每个写操作,启动时通过日志恢复操作(每秒记录一次)
1、AOF默认不开启,进入redis.conf找到appendonly yes打开
2、每秒记录一次,如果宕机该秒记可能失效
3、修复AOF:redis-check-aof –fix appendonly.aof
4、Rewrite:bgrewriteaof 因为日志是追加方式,文件会越来越大,当超过了设置的阈值时,日志文件会压缩,保留仅可以恢复的日志
AOP优点:备份机制更加稳健,可读的日志文件,通过AOF恢复更加稳健,可以处理失误
AOP缺点:比RDB更占磁盘。备份速度较慢。每次都同步日志,有性能压力
使用选择
1、对数据要求安全性很高,同时使用两种
2、如果可以承受数分钟以内的数据丢失, 那么你可以只使用 RDB 持久化。
3、不建议单独使用AOF
4、若仅作为缓存使用,可以都不开启
5、aof文件比rdb更新频率高,优先使用aof还原数据。
6、rdb性能比aof好
Redis事务
开启事务模式:输入multi命令进入事物模式,返回一个OK。
当有多个一起要执行的命令,Redis把他们放进队列中,当EXEC被调用时,所有的命令才会被一次性执行。
如果调用了discard命令,则会清除队列中的所有命令,然后退出事务
主要作用:序列化操作,串联多个命令防止别的命令插队
Redis使用乐观锁。利用check-and-set机制实现事务
三大特性:
1、单独的隔离级别:事务中所有命令都会序列化,按循序执行。不会被其他客户端打断
2、没有隔离级别概念:队列中的命令没有提交之前不会被执行,事务外不能查看事务内的更新
3、不能保证原子性:跳过错误,依旧执行,没有回滚
redis不支持回滚,因为只有当语法发送错误了,Redis命令才会执行失败,或者对Kyes赋予了一个类型错误的数据,这些都是程序性错误,在开发的过程中就能够解决掉,几乎不会出现在生产环境中的
扩展:
悲观锁:每次拿到数据的时候都会上锁,或者等待别人处理完再去拿锁,传统的关系型数据库里面很多用到了这种锁机制,比如行锁。表锁、读锁、写锁==
乐观锁:每次拿到数据的时候总认为别人不会修改数据,所以不会上锁,但是更新的时候回去判断别人有没有更改数据,使用版本号机制,乐观锁适用于多读的应用类型,可以提高吞吐量==
Redis架构模式
Redis单副本
采用单个Redis节点部署,没有备用节点同步数据,不提供数据持久化和备份策略,适用于数据可靠性要求不高的纯缓存业务场景,
产生的问题:
1、内存容量有限
2、处理能力有限
3、无法高可用。
Redis主从复制
主从实例部署在不同的(物理)服务器上,根据公司的基础环境配置,可以实现同时对外提供服务和读写分离策略。主机数据更新后会根据配置和策略,自动同步到备份机的master/slaver机制,master写为主,slave读为主
优点:高可靠性、读写分离
当主库出现故障时自动进行主备切换,从服务器提升为主服务器,保证服务继续运行。开启数据持久化功能和配置合理的备份策略,能有效的解决数据库误操作和数据库异常丢失的问题。从节点可以扩展主库节点的读能力,有效的对大并发量的读操作
缺点:主机从机的宕机都会当之前部分读写请求失败 ,需要等待重启或者手动切换前端的IP才能恢复。主机宕机的话,前面部分数据没有及时同步到从机,切换IP后还会引入数据不一致的问题,降低了系统的可用性
Redis Sentinel(哨兵)高可用解决方案
Redis sentinel 是一个分布式系统中用来监控 redis 主从服务器,并在主服务器下线时自动进行故障转移的操作。他是由若干个Sentienl节点组成的分布式集群,Sentinel的节点数量必须是奇数。
三个特性:
监控(Monitoring):Sentinel会不断的监控你的主服务器和从服务器是否运作正常
提醒(Notification):当被监控的某个Redis服务器出现问题时,Sentienl可以通过API向管理员或者其他应用程序发送通知
自动故障迁移(Automatic failover):当一个服务器不能正常工作时,Sentinel会开始一次自动故障迁移操作
优点:保证高可用,监控各个节点,自动故障迁移
缺点:主从模式,切换需要时间丢数据。不能解决读写分离问题,实现起来相对复杂
Redis集群(proxy型)
Twemproxy 是一个 Twitter 开源的一个 redis 和 memcache 快速/轻量级代理服务器; Twemproxy 是一个快速的单线程代理程序,支持 Memcached ASCII 协议和 redis 协议。
特点:
1、多种 hash 算法:MD5、CRC16、CRC32、CRC32a、hsieh、murmur、Jenkins
2、支持失败节点自动删除
3、后端 Sharding 分片逻辑对业务透明,业务方的读写方式和操作单个 Redis 一致
缺点:增加了新的 proxy,需要维护其高可用。
failover 逻辑需要自己实现,其本身不能支持故障的自动转移可扩展性差,进行扩缩容都需要手动干预
集群(直连型)
从redis 3.0之后版本支持redis-cluster集群,Redis-Cluster采用无中心结构,每个节点保存数据和整个集群状态,每个节点都和其他所有节点连接。
特点:
1、无中心架构(不存在哪个节点影响性能瓶颈),少了 proxy 层。
2、数据按照 slot 存储分布在多个节点,节点间数据共享,可动态调整数据分布。
3、可扩展性,可线性扩展到 1000 个节点,节点可动态添加或删除。
4、高可用性,部分节点不可用时,集群仍可用。通过增加 Slave 做备份数据副本
5、实现故障自动 failover,节点之间通过 gossip 协议交换状态信息,用投票机制完成 Slave到 Master 的角色提升。
缺点:
1、资源隔离性较差,容易出现相互影响的情况。
2、数据通过异步复制,不保证数据的强一致性
Redis分区
是将数据分割成多个Redis实例,使每个实例将只包含键子集的过程,它允许更大的数据库,使用多台计算机的内存总和,它允许按比例在多内核和多个计算机计算,以及网络带宽向多台计算机和网络适配器。劣势:涉及多个键时,Redis事务无法使用;数据处理比较复杂;加和删除的容量可能会很复杂。
分区类型:范围分区;散列分区
Redis 数据库可以配置安全保护的,所以任何客户端在连接执行命令时需要进行身份验证。
应用:统计!
为什么使用Redis以及使用redis会遇到的问题
为什么使用Redis
为了提高数据库的性能:在碰到需要执行耗时特别久,且结果不频繁变动的SQL,就特别适合将运行结果放入缓存。这样,后面的请求就去缓存中读取,使得请求能够迅速响应。
并发情况下:在大并发的情况下,所有的请求直接访问数据库,数据库会出现连接异常。这个时候,就需要使用redis做一个缓冲操作,让请求先访问到redis,而不是直接访问数据库。
redis为什么这么快
因为redis是单线程工作模型,避免了频繁的上下文切换,采用了非阻塞I/O多路复用机制,纯内存操作
Redis应用场景
1、Hash:做单点登录的时候,用Hash数据结构存储用户信息,以cookieId作为key,设置缓存过期时间,能很好的模拟出类似session的效果。因为Hash的Value存放的是结构化的对象,比较方便的就是操作其中的某个字段。
2、List:可以做简单的消息队列的功能。另外就是,可以利用lrange命令,做基于redis的分页功能,性能非常好,用户体验也好。还有生产者消费者场景,List可以很好的完成排队,先进先出的原则。
3、Set:因为set堆放的是一堆不重复值的集合。所以可以做全局去重的功能。(为什么不用JVM自带的Set进行去重?因为我们的系统一般都是集群部署,使用JVM自带的Set,比较麻烦,难道为了一个做一个全局去重,再起一个公共服务,太麻烦了。)
(另外,就是利用交集、并集、差集等操作,可以计算共同喜好,全部的喜好,自己独有的喜好等功能。)
4、Sorter set:他多了一个权重参数score,集合中的元素能够按score进行排列。可以做排行榜应用,取TOP N操作。
Redis的过期策略以及内存淘汰机制
redis采用的是定期删除+惰性删除策略
定期删除,redis默认会每隔100s检查,是否有过期key,发现过期就删除。但是redis不是每隔100s将所有的key检查一次,而是随机抽取进行检查。因此如果只采用定期删除,会导致很多key没到时间删除。于是惰性删除派上用场了。也就是说你获取某个key的时候,redis会检查一下,这个key如果设置了过期时间那么就检查一下是否过期了?如果过期了此时就会删除
如果定期删除没有删除key。然后你也没及时去请求key,也就是说惰性删除也没有生效,这样,redis的内存会越来越高。那么就应该采用内存淘汰机制 。在redis.conf中有一行配置 #maxmenmory-policy volatile-lru 该配置就是配内存淘汰策略的
定时删除是用一个定时器来负责key,过期则自动删除,虽然内存及时释放,但是十分消耗CPU资源。在大并发请求下,CPU要将时间应用在处理请求,而不是删除key,因此没有采用这一策略
-----------------------------------------------------分割线--------------------------------------------------------
1)noeviction:当内存不足以容纳新写入数据时,新写入操作会报错。
2)allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key。推荐使用,目前项目在用这种。
3)allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个key。应该也没人用吧,你不删最少使用Key,去随机删。
4)volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的key。这种情况一般是把redis既当缓存,又做持久化存储的时候才用。不推荐
5)volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个key。依然不推荐
6)volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的key优先移除。不推荐
ps:如果没有设置 expire 的key, 不满足先决条件(prerequisites); 那么 volatile-lru, volatile-random 和 volatile-ttl 策略的行为, 和 noeviction(不删除) 基本上一致。
Redis和数据库双写一致性问题
采用延时双删策略:
1、先删除缓存
2、再写入数据库
3、休眠一秒。再次删除缓存。这样做可以将1秒内所造成的缓存脏数据,再次删除。
mysql的读写分离架构怎么保存数据一致性
还是使用双删延时策略。只是,睡眠时间修改为在主从同步的延时时间基础上,加几百ms。
如果对数据有强一致性要求,不能放缓存。我们所做的一切,只能保证最终的一致性。另外,我们所做的方案其实从根本上来说,只能说降低不一致发生的概率,无法完全避免。因此,有强一致性要求的数据,不能放缓存。
如何应对缓存穿透和缓存雪崩问题
缓存穿透:恶意的请求去请求缓存中不存在的数据,导致所有的请求都怼到数据库上,就会对后端系统造成很大的压力。这就叫缓存穿透。
避免:对查询结果为空的情况也进行缓存,缓存时间设置短一点,或者该key对应的数据insert了之后清理缓存。对不一定存在的key进行过滤,可以把所有可能存在的key放到一个大的Bitmap中,查询时通过该bitmap过滤
缓存雪崩:当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候,会给后端系统带来很大压力。导致系统崩溃。
(一)在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。
(二)做二级缓存,A1为原始缓存,A2为拷贝缓存,A1失效时,可以访问A2,A1缓存失效时间设置为短期,A2设置为长期
(三)不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。
从缓存A读数据库,有则直接返回
A没有数据,直接从B读数据,直接返回,并且异步启动一个更新线程。
更新线程同时更新缓存A和缓存B。
解决redis的并发竞争key问题
如果对这个key操作,不要求顺序的情况下,准备一个分布式锁,大家去抢锁,抢到锁就做set操作即可,比较简单。
如果对这个key操作,要求顺序
假设有一个key1,系统A需要将key1设置为valueA,系统B需要将key1设置为valueB,系统C需要将key1设置为valueC.期望按照key1的value值按照 valueA–>valueB–>valueC的顺序变化。这种时候我们在数据写入数据库的时候,需要保存一个时间戳。假设时间戳如下
系统A key 1 {valueA 3:00}
系统B key 1 {valueB 3:05}
系统C key 1 {valueC 3:10}
那么,假设这会系统B先抢到锁,将key1设置为{valueB 3:05}。接下来系统A抢到锁,发现自己的valueA的时间戳早于缓存中的时间戳,那就不做set操作了。以此类推。
分布式锁
控制分布式系统有序的去对共享资源进行操作,通过互斥来保持一致性
实现redis分布式锁
使用redis命令 set key value NX EX max-lock-time 实现加锁
使用redis命令 EVAL 实现解锁
为了确保分布式锁可用,我们至少要确保锁的实现同时满足以下四个条件:
1、互斥性:在任意时刻,只有一个客户端能持有锁。
2、安全性:只有加锁的服务才能有解锁权限。也就是不能让a加的锁,bcd都可以解锁,如果都能解锁那分布式锁就没啥意义了
可能出现的情况就是a去查询发现持有锁,就在准备解锁,这时候忽然a持有的锁过期了,然后b去获得锁,因为a锁过期,b拿到锁,这时候a继续执行第二步进行 解锁如果不加校验,就将b持有的锁就给删除了
3、避免死锁:出现死锁就会导致后续的任何服务都拿不到锁,不能再对共享资源进行任何操作了
4、原子性:保证加锁与解锁操作是原子性操作
什么情况会出现死锁
假设现在a刚实现set操作,程序崩溃了就导致了没给key设置过期时间就导致key一直存在就发生了死锁
public class RedisTool {
private static final String LOCK_SUCCESS = "OK";
private static final String SET_IF_NOT_EXIST = "NX";
private static final String SET_WITH_EXPIRE_TIME = "PX";
/**
* 尝试获取分布式锁
* @param jedis Redis客户端
* @param lockKey 锁
* @param requestId 请求标识
* @param expireTime 超期时间
* @return 是否获取成功
*/
public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) {
//加锁就一行代码
String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
if (LOCK_SUCCESS.equals(result)) {
return true;
}
return false;
}
}
/*
第一个为key,我们使用key来当锁,因为key是唯一的。
第二个为value,我们传的是requestId,分布式锁要满足第四个条件解铃还须系铃人,通过给value赋值为requestId,我们就知道这把锁是哪个 请求加的了,在解锁的时候就可以有依据。requestId可以使用UUID.randomUUID().toString()方法生成。
第三个为nxxx,这个参数我填的是NX,意思是SET IF NOT EXIST,即当key不存在时,我们进行set操作;若key已经存在,则不做任何操作;
第四个为expx,这个参数我们传的是PX,意思是我们要给这个key加一个过期的设置,具体时间由第五个参数决定。
第五个为time,与第四个参数相呼应,代表key的过期时间。
*/
```
## MySQL里有2000万数据,redis中只存20万数据,如何保证redis中的数据都是热点数据
相关知识:redis 内存数据集大小上升到一定大小的时候,就会施行数据淘汰策略。redis 提供 6种数据淘汰策略:
voltile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰
allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
no-enviction(驱逐):禁止驱逐数据
## 缓存的使用/出现的问题
使用缓存一般的步骤:【缓存一般使用场景查询】
1、先去缓存中去取数据,取到直接返回
2、没有的话去数据库中查询,查询到以后写入缓存,然后返回
``` java
//伪代码
public class CacheDemo{
//从缓存中取数据
public object getDataFromCache(String key){
//从缓存中取数据
object value = redis.get(key);
return value;
}
//从数据库中取数据
public object getDateFromDB(String key){
object value = db.get(key);
return value;
}
//写入缓存
public boolean setDateToCache(String key,object value){
boolean flag = redis.set(key,value);
return flag;
}
//业务
public object getDate(String key){
//先从缓存中取数据
object value = null;
try{
value = getDateFromCache(key);
}catch(Exception e){
Logger.error("缓存挂了"+e.getMessage);
}
//判断
if(value==null){
//从数据库中取数据【1、加锁。2、双重判断 ReentrantLock。3、redis锁(setnx)】
synchronized{
value = getDateFromDB(key);
}
//写入缓存
try{
flag = setDateToCache(key,value);
}catch(Exception e){
Logger.error("缓存挂了"+e.getMessage);
}
return value;
}
return value;
}
}
```
网友评论