最流行的NoSQL---Redis

redis是什么?有什么特点?能干啥?

redis即remote dictionary server远程字典服务器,高性能(key/value)分布式内存数据库,基于内存运行并支持持久化.
三个特点:

  1. redis支持数据持久化,重启时可以加载重用
  2. redis支持更多的数据类型,kv,list,set,zset,hash等
  3. redis支持数据备份,即主从模式

能干啥?
redis支持异步将内存写入磁盘,不影响服务;发布订阅消息系统;定时器,计时.

redis的优点

  • 响应快:基于ANSI C语言编写,接近于汇编语言的机器语言
  • 基于内存的读/写
  • 数据结构简单,只有6种数据类型,因此规则较少,而数据库则是范式,完整性,规范性等要考虑的规则较多
  • 操作都是原子的,从而确保多个客户同时访问redis服务器时得到的是最新值,在高并发场合下可以考虑使用redis
    的事务.
  • 支持"发布-订阅"的消息模式
  • 易扩展:nosql数据库种类繁多,但共同特点就是去掉关系型特性,数据之间无关系

redis和mysql的区别

1.mysql和redis的数据库类型
mysql是关系型数据库,主要用于存放持久化数据,将数据存储在硬盘中,读取速度较慢。
redis是NOSQL,即非关系型数据库,也是缓存数据库,即将数据存储在缓存中,缓存的读取速度快,能够大大的提高运行效率,但是保存时间有限
2.mysql的运行机制
mysql作为持久化存储的关系型数据库,相对薄弱的地方在于每次请求访问数据库时,都存在着I/O操作,如果反复频繁的访问数据库。第一:会在反复链接数据库上花费大量时间,从而导致运行效率过慢;第二:反复的访问数据库也会导致数据库的负载过高,那么此时缓存的概念就衍生了出来。
3.缓存
缓存就是数据交换的缓冲区(cache),当浏览器执行请求时,首先会对在缓存中进行查找,如果存在,就获取;否则就访问数据库。
缓存的好处就是读取速度快
4.redis数据库
redis数据库就是一款缓存数据库,用于存储使用频繁的数据,这样减少访问数据库的次数,提高运行效率。
5.原理
传统的关系型数据库ACID:atomicity原子性,consistency一致性,isolation隔离性,durability持久性.
nosql分布式数据库的CAP原理:caonsistency强一致性,availability可用性,partition tolerance分区容错性.而只能三选二,redis选择cp.
6.redis和mysql的区别总结
(1)类型上
从类型上来说,mysql是关系型数据库,redis是缓存数据库
(2)作用上
mysql用于持久化的存储数据到硬盘,功能强大,但是速度较慢
redis用于存储使用较为频繁的数据到缓存中,读取速度快
(3)需求上
mysql和redis因为需求的不同,一般都是配合使用
(4)结构上
nosql数据结构简单,不如数据库sql语句强大,支持更为复杂的计算
(5)安全上
nosql并不完全安全稳定,由于他基于内存,一旦停机或者机器故障数据就很容易丢失,其持久化能力也是有限的,而数据库不会出现这样的问题

数据结构及常用命令

一.字符串string
可以包含任何数据,所以二进制安全,一个key对应一个value.犹如java的map结构.
命令:

set key value
get key
del key
getrange key start end:获取子串
append key value:将新的字符串追加到原来key指向的字符串末
getset key value:修改原来key的对应值,并将原旧值返回

二.哈希hash
一个对象里面有许多键值对,特别适合存储对象,如同java里的map.hash的键值对在内存中是一种无序的状态.
命令:

hdel key field1:删除hash结构中的某个或某些字段
hgetall key:获取所有hash结构中的键值
hincrby key field increment:指定给hash结构中的某个字段值加上一个increment整数值
hmget key field1:返回hash中指定的键的值可以是多个
hsetnx key field value:当hash结构中不存在对应的键时才设置值

三.列表list
redis列表是简单的字符串列表,单值多value.可以左右加数据.底层是个双向链表.优势在于插入和删除的遍历.
命令:

lpush key node1:把节点加到链表最左边
rpop key:删除右边第一个节点并返回
ltrim key start end:截取start到end范围内的值,其余删掉
//链表的阻塞命令
blpop key timeout:移除列表的第一个元素,如果链表没有元素会阻塞列表直到等待超时或发现有可弹出的元素为止

四.集合set
无序无重复,通过hashtable实现,集合中的每个元素都是string类型.集合是通过哈希表实现的,所以增删查的复杂度都是o(1).
命令:

sadd key num1:给键为key的集合增加成员
scard key:统计key的集合成员数
sdiff key1 [key2]:找出两个集合的差集
sinter key1 [key2]:求两个集合的交集
sunion key1 [key2]:求两个集合的并集
spop key:随机弹出集合的一个元素

五.有序集合zset
它和集合的主要区别在于每个元素会多一个double类型的分数,根据分数可以有序的排序.有序不重复,但是分数可以重复.
命令:

zadd key score1 value1:增加一个或多个成员
zcount key min max:根据分数返回对应的成员列表
zscore key member:返回成员的分数值

六.基数hyperloglog
基数是一种算法.比如给一个重复集合去重,作用是评估大约需要多少个存储单元去存储数据.
命令:

pfadd key element:添加指定元素到hyperloglog中,如果已经存储该元素则返回0,添加失败
pfcount key:返回hyperloglog的基数值

事务

可以一次性执行多个命令,本质上是一组命令的集合,所有的命令都会序列化,按顺序地串行化执行而不会被其他命令插入.在一个队列中一次性,顺序性,排他性的执行一系列命令.
怎么玩?
分三个阶段:开启,入队,执行.
MULTI:开启事务
EXEC:提交执行
DISCARD:放弃事务
全体连坐:若事务中有错则全体失败.(这个错是指入队时就报错,类似语法错误,EXEC前就报错)
冤头债主:某步的错不影响其他步的执行(这个特指在EXEC后报错,类似运行时异常)
所以说redis是部分支持事务的,并非强一致性.
若在watch监控指令后而事务前,数据被别人改动了,则事务被打断.则要先unwatch(默认对所以key)再watch key(一个或多个).一旦执行EXEC,之前加的监控锁都会被取消掉,watch指令类似于乐观锁.
特性:
单独的隔离操作:会序列化
没有隔离级别:命令提交前都不会被执行
不保证原子性:若一条命令失败,不影响其他命令,无回滚.

发布订阅

它是进程间的一种消息通信模式.先订阅后发布
订阅多个:subscribe c1 c2 c3
消息发布:publish c2 hello
通配符:psubscribe new*
发消息:publish new12 hello2

redis的复制机制(master/slave)

是什么?
主从复制,读写分离.主机数据更新后根据配置和策略自动同步到备机.master以写为主,slave以读为主.
能干啥?
读写分离,容灾恢复
怎么玩?

  • 配从不配主.
  • slaveof 主库ip 主库端口.每次断开都要重连,除非配到配置文件中.
  • 常用三种方式:
  1. 一主二仆:
    (1)从机备份主机的所有数据
    (2)从机不能写主机写过的key,从机只读
    (3)若主机down掉,从机原地待命,若主机活了,则自动连接到主机上
    (4)若从机down掉,要重连主机
  2. 薪火相传:
    去中心化,上一个slave可以是下一个slave的master.
    中途变更转向:会消除之前的数据,重新建立,拷贝最新的.
    中间的master其实就是slave,显示slave.
  3. 反客为主:
    slaveof no one
    使当前数据库停止与其他数据库的同步,转成主库.

常用命令:info replication
复制原理
slave启动成功连接到master后会发送一个sync命令,master接到命令后启动后台的存盘进程.同时收集所有接收到的用于修改数据集命令,在后台进程执行完毕后,master将整个数据文件传送到slave,已完成一次完全同步.
注意:全量复制:slave接收到数据库文件之后,将其存盘并加载到内存.
增量复制:master继续将新的修改命令依次传给slave.
但是只要重连master,全量复制将被自动执行.
哨兵模式
反客为主的自动版
监控,能够后台监控主机是否故障,若故障根据投票数自动将从库转为主库,然后通过发布订阅模式通知其他从服务器,修改配置文件切换主机.当主机重生后会跟着新老大当slave,不会双主冲突.
新加文件touch sentinel.conf
文件里写sentinel monitor 被监控名字 主机ip 端口号 数字1(谁的票数多余1票以上,多的成为主机)
启动哨兵:redis-sentinel /myredis/sentinel.conf
一组sentinel能同时监控多个master.
复制的缺点:复制延迟,因为所有写都先在master上操作,再同步更新到slave上,会有延迟.

配置文件

日志级别:debug—verbose(默认)—notice—warning
启用守护进程运行:daemonize Yes
缓存过期清洁策略:默认MaxMemory-policy noeviction

  1. volatile-lru:使用lru算法移除key,只对设置了过期时间的键
  2. allkeys-lru:对全部key用lru算法,最近使用最少移除
  3. volatile-random:在过期集合中随机移除key
  4. allkeys-random:随机移除key
  5. volatile-ttl:移除ttl值最小的key,即最近要过期的key
  6. noeviction:不移除,针对写操作,只是返回错误信息

lru和ttl都非精确,maxmemory-samples 5 :默认选5个样本估值.

持久化-----RDB(Redis DataBase)

是什么?
在一时间段内将内存中的数据块快照写入磁盘,即snapshot快照.
恢复:将快照文件从磁盘读到内存中.
redis会单独创建fork一个子进程来进行持久化,先将数据写入一个临时文件,待持久化过程结束,再用这个临时文件替换上次持久化好的文件.整个过程中,主进程不进行IO操作.
如果需要大规模数据恢复且对完整性要求不高,使用RDB,缺点是如果意外down掉会,丢失最后一次持久化后的数据,失去最后一次快照修改.fork时,内存中数据克隆一份,影响内存空间性能.
fork?
复制一个与当前进程一样的进程,所有数据一致,并作为原进程的子进程.
当执行flushAll或者shutdown时,迅速停止内存情况,存入dump.rdb文件磁盘中.
快照?
save 秒数 写操作次数 :save命令迅速备份
默认:1分钟内改1万次 或5分钟内改10次 或15分钟内改1次.
rdbcompression:对于磁盘中的快照,是否用LZF算法压缩.
rdbchecksum:使用CRC64算法进行数据校验
dbfilename:dump.rdb
如何触发快照?
默认配置(即上面的默认几分钟内改几次触发),冷拷贝后再重新用,即从主机拷贝到备机后;
save或bgsave(后台异步快照,快照的同时也可以响应客户端请求);
flushall也会产生dump.rdb但是里面是空的,无意义.
恢复?
将备份文件dump.rdb移到redis安装目录并启动即可自动读回内存.
总结:
RDB是个非常紧凑的文件.保存RDB文件时父进程唯一要做的就是fork一个子进程.有数据丢失风险.fork大时非常耗时.

持久化-----AOF(Append Only File)

是什么?
以日志形式记录每个写操作,只允许追加文件不允许改写.redis重启的话就根据日志文件将写指令从前到后执行一次以完成数据的恢复.
默认关闭AOF
先找appendonly.aof文件再加载dump.rdb
aof和rdb可以同时工作.若aof文件有坏,可以执行redis-check-aof --fix appendonly.aof 修复
配置策略:
appendfsync:默认everysec
(always),同步持久化,每次发生数据变化就会立即记录到磁盘
(everysec),异步操作,每秒记录一次
(no)
rewrite:为了避免追加文件越来越大,增加了重写机制,当aof文件大小超过设置的阈值时,redis就会启动aof文件内容的压缩,只保留可以恢复数据的最小指令集,可以使用命令bgrewriteaof
重写原理:会fork出一条新进程将文件重写,重写aof文件的操作中,并没有读取旧的aof,而是将整个内存中的数据库内容用命令方式重写一个新的aof文件.
触发机制:redis会记录上次重写时的aof大小,默认配置即当前aof文件大小是上次rewrite后大小的一倍,且文件大小大于64M时才触发.
优点:可以选择同步频率,即 每秒同步-----若一秒内有宕机则会有数据丢失; 每次修改同步; 不同步
缺点:aof文件远远大于rdb文件大小,恢复速度较慢.
aof因为fsync策略运行效率比rdb慢,不同步效率和rdb相同.
总结:
aof文件是一个只追加的日志文件.
后台自动重写.
aof文件有序地保存了对数据库执行的写操作,并以redis协议格式保存,易读.
若只做缓存,也可以不用任何持久化方式.

Lua语言

弥补了redis命令计算能力不强这个不足.lua语言是原子性的,即执行lua时候是不会被中断的,这个特性有助于redis对并发数据一致性的支持.
支持两种方式运行lua脚本:
一.直接输入一些lua程序代码
命名格式为:

eval  lua-script  key-num  [key1  key2...]  [value1  value2...]

其中:

  • eval代表执行lua语言的命令
  • lua-script代表lua语言的脚本
  • key-num代表参数中有多少的key,如果没有key参数,那么写0

对于采用简单脚本的,redis支持缓存脚本,只是,它会使用SHA-1算法对脚本进行签名,然后把SHA-1标识返回回来,只要通过这个标识运行就可以了.
二.执行lua文件
当lua脚本存在较多逻辑时候就有必要单独编写一个独立的lua文件.
注意,执行命令中 键和参数相邻之间是使用逗号分隔的而不是空格.

文章的最后有个问题需要讨论一下

如果key超时了,redis会回收key的存储空间吗?
答案:不会.redis的key超时不会被其自动回收,它只会标识哪些键值对超时了.这样做的好处是如果一个很大的键值对超时(存在数以百万个元素),要对其回收需要很长时间,可能会产生停顿.而坏处是,这些超时的键值也会浪费很多的空间.
redis提供了两种方式回收这些超时键值对:

  • 定时回收:指在确定的某一时间触发一段代码,回收超时的键值对.(优点是可以完全回收那些超时的键值对,但是一般选择在没有业务发生时刻触发定时回收,避免停顿)
  • 惰性回收:指当一个超时的键,被再次用get命令访问时,将触发redis将其从内存中清空.(优点是可以指定回收超时键值对,缺点是要执行莫名其妙的get操作)

============================================
补充个面试题
redis底层存储原理?
先摆个底层源码:

typedef struct dict {
   dictType *type;
   void *privdata;
   dictht ht[2];
   long rehashidx; /* rehashing not in progress if rehashidx == -1 */
   unsigned long iterators; /* number of iterators currently running */
} dict;
/* This is our hash table structure. Every dictionary has two of this as we
* implement incremental rehashing, for the old to the new table. */
typedef struct dictht {
   dictEntry **table;
   unsigned long size;
   unsigned long sizemask;
   unsigned long used;
} dictht;
typedef struct dictEntry {
   void *key;
   union {
       void *val;
       uint64_t u64;
       int64_t s64;
       double d;
   } v;
   struct dictEntry *next;
} dictEntry;
//redisObject是真正存储redis各种类型的结构,定义如下:
#define REDIS_STRING 0  
#define REDIS_LIST 1  
#define REDIS_SET 2  
#define REDIS_ZSET 3  
#define REDIS_HASH 4  
typedef struct redisObject {  
  unsigned type:1; //逻辑类型  
  unsigned notused:2;     /* Not used */  
  unsigned encoding:4; //物理存储类型  
  unsigned lru:22;        /* lru time (relative to server.lruclock) */  
  int refcount;  
  void *ptr;  //具体数据  
} robj;  

由上面的代码可以看到redis的数据结构声明,并且可以看出它是由c语言写的. 一个dict有两个dictht,一个dictht有一个dictEntry数组,每个数组有一个next指针,因此是一个链表结构,并且redis是用拉链法解决冲突的哈希表结构. redisObject是真正存储redis各种类型的结构.其中为了避免hash碰撞,redis用"双dictht",正常情况下使用一个dictht,当发现碰撞剧烈时(判断当前槽位和key数对比),分配一个更大的dictht,然后逐步将数据从老的dictht迁移到新的dictht上,这里就需要rehash.
而rehash不是一次性完成的,而是采用渐进式方式,避免一次性执行过多的rehash操作而给服务器带来过大负担. 渐进式rehash通过记录dict中的rehashidx完成,它从0开始每次执行一次rehash都会递增. 在rehash期间,每次对字典执行增删改查时都会执行一次渐进式rehash. 采用渐进式rehash会导致字典中的数据分散在两个dictht上,因此应该在对应的dictht上去执行对应的字典操作. 那么底层数据的有序性怎么保证?
使用跳跃表实现有序集合. 跳跃表基于多指针有序链表实现,可以看成是多个有序链表.在redis中的有序集合类型的底层数据结构一个是跳跃表另一个是字典. 跳跃表与红黑树等平衡树相比,有以下优点:插入快(不需要平衡树的旋转), 支持无锁操作, 更易实现.
下面即是rehash方法的源码:

int dictRehash(dict *d, int n) {
   int empty_visits = n * 10; /* Max number of empty buckets to visit. */
   if (!dictIsRehashing(d)) return 0;

   while (n-- && d->ht[0].used != 0) {
       dictEntry *de, *nextde;

       /* Note that rehashidx can't overflow as we are sure there are more
        * elements because ht[0].used != 0 */
       assert(d->ht[0].size > (unsigned long) d->rehashidx);
       while (d->ht[0].table[d->rehashidx] == NULL) {
           d->rehashidx++;
           if (--empty_visits == 0) return 1;
       }
       de = d->ht[0].table[d->rehashidx];
       /* Move all the keys in this bucket from the old to the new hash HT */
       while (de) {
           uint64_t h;

           nextde = de->next;
           /* Get the index in the new hash table */
           h = dictHashKey(d, de->key) & d->ht[1].sizemask;
           de->next = d->ht[1].table[h];
           d->ht[1].table[h] = de;
           d->ht[0].used--;
           d->ht[1].used++;
           de = nextde;
       }
       d->ht[0].table[d->rehashidx] = NULL;
       d->rehashidx++;
   }

   /* Check if we already rehashed the whole table... */
   if (d->ht[0].used == 0) {
       zfree(d->ht[0].table);
       d->ht[0] = d->ht[1];
       _dictReset(&d->ht[1]);
       d->rehashidx = -1;
       return 0;
   }

   /* More to rehash... */
   return 1;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值