Redis

作者: 霍霍霍霍霍霍霍霍霍霍霍 | 来源:发表于2019-03-26 16:12 被阅读0次

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;

}

}

```

相关文章

网友评论

      本文标题:Redis

      本文链接:https://www.haomeiwen.com/subject/eskfmqtx.html