NoSql入门和概述
入门概述
为什么要使用NoSql?
1.单机mysql的美好年代2.Memcached(缓存) + MySql + 垂直拆分3.Mysql主从读写分离4.分表分库+水平拆分+mysql集群5.MySql的扩展性瓶颈6.今日的数据7.为什么要使用NoSql
是什么?
NoSQL(NoSQL = Not Only SQL ),意即“不仅仅是SQL”,
泛指非关系型的数据库。随着互联网web2.0网站的兴起,传统的关系数据库在应付web2.0网站,特别是超大规模和高并发的SNS类型的web2.0纯动态网站已经显得力不从心,暴露了很多难以克服的问题,而非关系型的数据库则由于其本身的特点得到了非常迅速的发展。NoSQL数据库的产生就是为了解决大规模数据集合多重数据种类带来的挑战,尤其是大数据应用难题,包括超大规模数据的存储。
(例如谷歌或Facebook每天为他们的用户收集万亿比特的数据)。这些类型的数据存储不需要固定的模式,无需多余操作就可以横向扩展。
特点是什么?
1.易扩展2.大数据量和高性能3.多样灵活的数据模型4.和传统数据库的区别1.高度组织化结构化数据2.结构化查询语言(SQL)3.数据和关系都存储在单独的表中。4.数据操纵语言,数据定义语言5.严格的一致性6.基础事务1.代表着不仅仅是SQL2.没有声明性查询语言3.没有预定义的模式4.键 - 值对存储,列存储,文档存储,图形数据库5.最终一致性,而非ACID属性6.非结构化和不可预知的数据7.CAP定理8.高性能,高可用性和可伸缩性
4V + 4高
大数据时代的4V:Volume(海量)、Variety(多样)、Velocity(速度)、Value(价值)
互联网要求的4高:高并发、高可靠、高可扩、高性能
当下NoSql的应用
现如今是sql和nosql一起使用,我们以阿里巴巴商品信息如何存放来举例:
1.商品基本信息
首先商品信息有名称、价格、出厂日期、生产厂商等等,这些信息则是存储在mysql中。注意:阿里所使用的mysql和开源的mysql不一样,而是里面专业人员对开源mysql进行改造,变成了一套适合他们自己业务的mysql。
2.商品描述、详情、评价信息(多文字类)
多文字信息描述,会导致IO读写性能变差,因此这些数据存在文档数据库Mongodb中。
3.商品的图片
商品的图片则是存储在分布式的文件系统中:淘宝自己的TFS、Google的GFS、Hadoop的HDFS
4.商品的关键字
搜索引擎,淘宝内容:ISearch
5.商品的波段性的热点高频信息
这些信息存在内存数据库:tair、redis、memecached中
6.商品的交易、价格计算、积分累计
外部系统,外部第三方支付接口,比如:支付宝
NoSql数据模型与典型代表
1.key-value键值对
新浪:BerkeleyDB+redis,美团:redis+tair,阿里、百度:memcache+redis
2.bson
BSON是一种类json的一种二进制形式的存储格式,简称Binary JSON,
它和JSON一样,支持内嵌的文档对象和数组对象。比如:CouchDB,MongoDB
3.列族
顾名思义,是按列存储数据的。最大的特点是方便存储结构化和半结构化数据,方便做数据压缩,
对针对某一列或者某几列的查询有非常大的IO优势。如:Cassandra, HBase,分布式文件系统
4.图片
它不是放图形的,放的是关系比如:朋友圈社交网络、广告推荐系统。社交网络,推荐系统等。专注于构建关系图谱。如:Neo4J, InfoGrid
在分布式数据库中CAP原理,CAP+BASE
传统的ACID分别是什么
关系型数据库遵循ACID规则,事务在英文中是transaction,和现实世界中的交易很类似,它有如下四个特性:
1、A (Atomicity) 原子性
原子性很容易理解,也就是说事务里的所有操作要么全部做完,要么都不做,事务成功的条件是事务里的所有操作都成功,只要有一个操作失败,整个事务就失败,需要回滚。比如银行转账,从A账户转100元至B账户,分为两个步骤:1)从A账户取100元;2)存入100元至B账户。这两步要么一起完成,要么一起不完成,如果只完成第一步,第二步失败,钱会莫名其妙少了100元。
2、C (Consistency) 一致性
一致性也比较容易理解,也就是说数据库要一直处于一致的状态,事务的运行不会改变数据库原本的一致性约束。
3、I (Isolation) 独立性
所谓的独立性是指并发的事务之间不会互相影响,如果一个事务要访问的数据正在被另外一个事务修改,只要另外一个事务未提交,它所访问的数据就不受未提交事务的影响。比如现有有个交易是从A账户转100元至B账户,在这个交易还未完成的情况下,如果此时B查询自己的账户,是看不到新增加的100元的
4、D (Durability) 持久性
持久性是指一旦事务提交后,它所做的修改将会永久的保存在数据库上,即使出现宕机也不会丢失。
CAP
1、C:Consistency(强一致性)
2、A:Availability(可用性)
3、P:Partition tolerance(分区容错性)
CAP的3进2理论
CAP的3进2理论就是说在分布式存储系统中,最多只能实现三点上面的两点。
而由于当前的网络硬件肯定会出现延迟丢包等问题,所以:
分区容错性是我们必须需要实现的。
所以我们只能在一致性和可用性之间进行权衡,没有NoSQL系统能同时保证这三点。
C:强一致性、 A:高可用性 、P:分布式容忍性
CA 传统Oracle数据库AP 大多数网站架构的选择CP Redis、Mongodb
一致性与可用性的决择
对于web2.0网站来说,关系数据库的很多主要特性却往往无用武之地
数据库事务一致性需求
很多web实时系统并不要求严格的数据库事务,对读一致性的要求很低, 有些场合对写一致性要求并不高。允许实现最终一致性。
数据库的写实时性和读实时性需求
对关系数据库来说,插入一条数据之后立刻查询,是肯定可以读出来这条数据的,但是对于很多web应用来说,并不要求这么高的实时性,比方说发一条消息之 后,过几秒乃至十几秒之后,我的订阅者才看到这条动态是完全可以接受的。
对复杂的SQL查询,特别是多表关联查询的需求
任何大数据量的web系统,都非常忌讳多个大表的关联查询,以及复杂的数据分析类型的报表查询,特别是SNS类型的网站,从需求以及产品设计角 度,就避免了这种情况的产生。往往更多的只是单表的主键查询,以及单表的简单条件分页查询,SQL的功能被极大的弱化了。
经典的CAP
CAP理论的核心是:一个分布式系统不可能同时很好的满足一致性,可用性和分区容错性这三个需求,
最多只能同时较好的满足两个。
因此,根据 CAP 原理将 NoSQL 数据库分成了满足 CA 原则、满足 CP 原则和满足 AP 原则三 大类:
CA - 单点集群,满足一致性,可用性的系统,通常在可扩展性上不太强大。CP - 满足一致性,分区容忍必的系统,通常性能不是特别高。AP - 满足可用性,分区容忍性的系统,通常可能对一致性要求低一些。
BASE
BASE就是为了解决关系数据库强一致性引起的问题而引起的可用性降低而提出的解决方案。
BASE其实是下面三个术语的缩写:
基本可用(Basically Available)软状态(Soft state)最终一致(Eventually consistent)
它的思想是通过让系统放松对某一时刻数据一致性的要求来换取系统整体伸缩性和性能上改观。为什么这么说呢,缘由就在于大型系统往往由于地域分布和极高性能的要求,不可能采用分布式事务来完成这些指标,要想获得这些指标,我们必须采用另外一种方式来完成,这里BASE就是解决这个问题的办法
分布式+集群简介
分布式系统
分布式系统(distributed system)
由多台计算机和通信的软件组件通过计算机网络连接(本地网络或广域网)组成。分布式系统是建立在网络之上的软件系统。正是因为软件的特性,所以分布式系统具有高度的内聚性和透明性。因此,网络和分布式系统之间的区别更多的在于高层软件(特别是操作系统),而不是硬件。分布式系统可以应用在在不同的平台上如:Pc、工作站、局域网和广域网上等。
简单来讲:
1、分布式:不同的多台服务器上面部署不同的服务模块(工程),他们之间通过Rpc/Rmi之间通信和调用,对外提供服务和组内协作。2、集群:不同的多台服务器上面部署相同的服务模块,通过分布式调度软件进行统一的调度,对外提供服务和访问。
Redis入门介绍
入门概述
1.是什么?
Redis:REmote DIctionary Server(远程字典服务器),是完全开源免费的,用C语言编写的,遵守BSD协议,
是一个高性能的(key/value)分布式内存数据库,基于内存运行
并支持持久化的NoSQL数据库,是当前最热门的NoSql数据库之一,
也被人们称为数据结构服务器
Redis 与其他 key - value 缓存产品相比有以下三个特点
Redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用Redis不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存储Redis支持数据的备份,即master-slave模式的数据备份
2.能干嘛?
内存存储和持久化:redis支持异步将内存中的数据写到硬盘上,同时不影响继续服务取最新N个数据的操作,如:可以将最新的10条评论的ID放在Redis的List集合里面模拟类似于HttpSession这种需要设定过期时间的功能发布、订阅消息系统定时器、计数器
3.去哪里下载?
http://redis.io/http://www.redis.cn/
4.怎么玩?
数据类型、基本操作和配置持久化和复制,RDB/AOF事务的控制复制
redis安装
mkdir redis
去我们上面说的网站,下载redis-5.0.5.tar.gz后将它放入我们的Linux目录/opt/redis,进入到/opt/redis中tar -zxvf redis-5.0.5.tar.gz解压后出现redis-5.0.5目录,进入该目录执行make && make install
然后redis的启动文件就会安装在/usr/local/bin目录中,里面有以下几个工具:
redis-benchmark:性能测试工具,可以在自己本子运行,看看自己本子性能如何,服务启动起来后执行redis-check-aof:修复有问题的AOF文件,rdb和aof后面讲redis-check-dump:修复有问题的dump.rdb文件redis-cli:客户端,操作入口redis-sentinel:redis集群使用redis-server:Redis服务器启动命令
/opt/redis/redis-5.0.5/redis.confredis-server xxx.conf
redis-server启动之后,就代表redis服务启动了,那么便可以通过客户端来连接了,这里的客户端可以是redis-cli、也可以是我们使用的编程语言。
可以看到,我们使用客户端redis-cli连接成功了。
redis启动之后的基础知识
1.单进程
单进程模型来处理客户端的请求。对读写等事件的响应
是通过对epoll函数的包装来做到的。Redis的实际处理速度完全依靠主进程的执行效率。epoll是Linux内核为处理大批量文件描述符而作了改进的epoll,是Linux下多路复用IO接口select/poll的增强版本,
它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。
2.默认16个数据库,类似数组下表从零开始,初始默认使用零号库
3.select:切换数据库
4.dbsize:查看当前数据库的key的数量
5.flushdb:清空当前库
6.flushall:通杀全部库
7.统一密码管理,16个库都是同样密码,要么都OK要么一个也连接不上
8.Redis索引都是从零开始
9.默认端口是6379
Redis数据类型
Redis的五大数据类型
string(字符串)
string是redis最基本的类型,你可以理解成与memcached一模一样的类型,一个key对应一个value。
string类型是二进制安全的。意思是redis的string可以包含任何数据。比如jpg图片或者序列化的对象 。
string类型是Redis最基本的数据类型,一个redis中字符串value最多可以是512M
hash(哈希,类似python的dict)
Redis hash 是一个键值对集合。
Redis hash是一个string类型的field和value的映射表,hash特别适合用于存储对象。
list(列表)
Redis 列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素导列表的头部(左边)或者尾部(右边)。
它的底层实际是个链表
set(集合)
Redis的Set是string类型的无序集合。它是通过HashTable实现实现的
zset(sorted set:有序集合)
Redis zset 和 set 一样也是string类型元素的集合,且不允许重复的成员。
不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。zset的成员是唯一的,但分数(score)却可以重复。
Redis 键(key)
**keys *:查看所有的key,像string、list、hash、set、zset都有自己的key,key不可以重名,比如有一个叫name的string,那么就不可以再有一个还叫name的,不管什么类型,都不能再叫name了。**
这里我们只有一个string类型的key,叫name
exists key的名字:判断某个key是否存在
存在名为name的key,返回1,name1不存在,返回0
ttl key的名字:查看还有多少秒过期,-1表示永不过期,-2表示已过期
key是可以设置过期时间的,如果过期了就不能再用了。我们看到name1这个key压根就不存在,返回的也是-2,因为过期了就相当于不存在了。而name是-1,表示永不过期
expire key的名字 秒钟:为给定的key设置过期时间
这里设置60s的过期时间。另外设置完之后,在过期时间结束之前是可以再次设置的,比如我先设置了60s,然后快结束的时候我再次设置60s,那么还会再持续60s
type key的名字:查看你的key是什么类型
move key的名字 db:从key移动到指定的db中
Redis字符串(string)
redis的string类型是一个key对应一个value
set key value:给指定的key设置一个value
set还可以指定一些默认参数:
set key value ex 60:设置的时候指定过期时间为60秒,等价于setex key 60 valueset key value px 60:设置的时候指定过期时间为60毫秒,等价于psetex key 60 valueset key value nx:只有key不存在的时候才会设置,存在的话则会什么也不做,而如果不加nx则会覆盖。等价于setnx key valueset key value xx:只有key存在的时候才会设置,注意:对于xx来说,没有setxx key value
我们发现默认参数使用set足够了,因此未来可能会移除setex、psetex、setnx
get key:获取指定key对应的value
如果key不存在,那么返回nil,也就是C语言中的NULL,python中的None、golang里的nil。存在的话,则返回key对应的value
del key1 key2···:删除key,可以同时删除多个
会返回删除的key的个数,另外删除一个不存在的key也不会报错
append key value:追加
如果key存在,那么会将value的值追加到key对应的值的末尾,如果不存在,那么会重新设置,类似于set key value
此外我们还可以看到,string类型,默认设置的value都是字符串,age不存在则设置、存在则追加,返回的是字符串的长度。
strlen key:查看对应key的长度
key不存在的话,则返回0
incr key:为key存储的值自增1,必须可以转成整型,否则报错。如果不存在key,默认先设置该key值为0,然后自增1
decr key:为key存储的值自减1,必须可以转成整型,否则报错。如果不存在key,默认先设置该key值为0,然后自减1
incrby key number:为key存储的值自增number,必须可以转成整型,否则报错,如果不存在的话,默认先将该值设置为0,然后自增number
decrby key number:为key存储的值自减number,必须可以转成整型,否则报错,如果不存在的话,默认先将该值设置为0,然后自减number
getrange key start end:获取指定value的同时指定范围,第一个字符为0,最后一个为-1。注意:redis中的索引都是包含结尾的,不管是这里的getrange,还是后面的列表操作,索引都是包含两端的。
getrange word -1 -3
setrange key start value:将key对应值,从索引为start的地方开始、将相应字符替换为value,替换的个数等于value的个数。
\x00
mset key1 value1 key2 value2:同时设置多个key value
这是一个原子性操作,要么都设置成功,要么都设置不成功。注意:这些都是会覆盖原来的值的,如果不想这样的话,可以使用msetnx,这个命令只会在所有的key都不存在的时候才会设置。
mget key1 key2:同时返回多个key对应的value
如果有的key不存在,那么返回nil
getset key value:先返回key的旧值,然后设置新值
如果有的key不存在,那么返回nil,然后设置。
Redis列表(list)
list是一个key对应多个value
lpush key value1 value2···:将多个值设置到列表里面,从左边push
rpush key value1 value2···:将多个值设置到列表里面,从右边push
lrange key start end:遍历列表,索引从0开始,最后一个为-1,且包含两端。
number是从左边push的,所以顺序是反过来的。
lpop key:从列表的左端弹出一个值,列表长度改变。
rpop key:从列表的右端弹出一个值,列表长度改变。
lindex:获取指定索引位置的元素,列表长度不变
llen:获取指定列表的长度
lrem key count value:删除count个value,如果count为0,那么将全部删除
ltrim key start end:从start截取到end,再重新赋值给key
rpoplpush key1 key2:移除key1的最后一个元素,并添加到key2的开头
lset key index value:将key索引为index的元素设置为value
索引越界则报错
linsert key before/after value1 value:在value1的前面或者后面插入一个value2
可以看到这个value1是第一次出现的value1
Redis集合(set)
sadd key value1 value2···:向集合插入多个元素,如果重复会自动去重
smembers key:查看集合的所有元素
sismember key value:查看value是否在集合中
scard key:查看集合的元素个数
srem key value:删除集合中的元素
spop key count:随机弹出集合中count个元素,注意:count是可以省略的,如果省略则弹出1个。另外一旦出去,原来的集合里面也就没有了。
srandmember key count:随机获取集合中count个元素,注意:count是可以省略的,如果省略则获取1个。可以看到类似spop,但是srandmember不会删除集合中的元素。
smove key1 key2 value:将key1当中的value移动到key2当中,因此key1当中的元素会少一个,key2会多一个(如果value在key2中不重复,否则保持一致)。
不再演示
sdiff key1 key2:在key1中,但不在key2中。
sinter key1 key2:即在key1中,又在key2中。
sunion key1 key2:在key1中,或者在key2中。
Redis哈希(hash)
以键值对方式存储
hset key k1 v1 k2 v2···:设置键值对,k1:v1,k2:v2,可设置多个
hget key k:获取hash中k对应的v
hgetall key:获取hash中所有的键值对
hlenkey:获取hash中键值对的个数
hexists key:获取hash中是否存在某个key
hkeys/hvals:获取hash中所有的key和所有的value
hincrby key k number:将key对应的字段k自增number,number必须指定,显然k对应的value要能解析成整型。
hsetnx key k1 v1:每次只能设置一个键值对,不存在则设置,存在则无效。
不再演示
Redis有序集合Zset(sorted set)
在set基础上,加一个score值。
之前set是k v1 v2 v3,
现在zset是k score1 v1 score2 v2。zset中score是可以重复的,但是v不可以重复。
zadd key score1 value1 score2 value2:设置score和value
一个score对应一个value,value不可以重复,因此即便我们这里添加了3个,但是后面两个的value都是n2,所以实际上只有两个元素,并且n2是以后一个score为准,因为相当于覆盖了。
zscore key value:获取value对应的score
zrange key start end:获取所有的value,递增排列
如果结尾加上with scores参数,那么会和score一同返回,注意:score是在下面。我们看到这个zset有点像hash啊,value是hash的k,score是hash的v
zrevrange key start end:获取所有的value,递减排列,同理也有withscores参数
>=开始score <=结束score
>=开始score <=结束score
此外如果在score前面加上了(,那么会不包含边界。
zrem key value1 value2···:移除对应的value
zcard key:获取集合的元素个数
zcount key 开始分数区间 结束分数区间:获取集合指定分数区间内的元素个数
解析配置文件:redis.conf
redis的配置文件默认在redis安装目录下,也就是redis.conf,当前我这个版本里面有1371行,可以说很多,我们还是看最常用的。在下面我把注释删掉了一部分,只保留最近的几行,不然太长了,至于每个配置是干什么的,我也会介绍
不过提到了配置文件,这里再提一个命令,叫做config
config get requirepass查看密码,这里是没有密码的。config get dir查看启动路径,我是在/root下面启动的redis-server。既然有get,那就有set
config set requirepass password表示设置密码,一旦设置,立马生效,此时命令将无法执行。需要输入auth password,然后才可以执行。下面开始介绍配置文件,另外,我这里使用的redis版本目前来说比较高,因此有些配置低版本的redis是没有的,但是大部分都是一样的,只不过在配置文件中的位置不同,但是都在redis.conf里面。
units单位
# Note on units: when memory size is needed, it is possible to specify
# it in the usual form of 1k 5GB 4M and so forth:
#
# 1k => 1000 bytes
# 1kb => 1024 bytes
# 1m => 1000000 bytes
# 1mb => 1024*1024 bytes
# 1g => 1000000000 bytes
# 1gb => 1024*1024*1024 bytes
#
# units are case insensitive so 1GB 1Gb 1gB are all the same.
定义了一些基本单位,而且大小写是不敏感的
includes包含
# include /path/to/local.conf
# include /path/to/other.conf
可以将配置写在其他地方,然后include进来
因为就跟编程语言里面的模块一样,如果都写在一个文件里面,会比较多,因此可以通过导入的方式
network网络
# IF YOU ARE SURE YOU WANT YOUR INSTANCE TO LISTEN TO ALL THE INTERFACES
# JUST COMMENT THE FOLLOWING LINE.
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
默认本地连接,如果想支持其他机器连接的话,那么把127.0.0.1改成0.0.0.0
bind 127.0.0.1
# are explicitly listed using the "bind" directive.
保护模式,为了让其他机器连接,我们会改成no
protected-mode yes
# Accept connections on the specified port, default is 6379 (IANA #815344).
# If port 0 is specified Redis will not listen on a TCP socket.
监听端口
port 6379
# in order to get the desired effect.
设置tcp的backlog,backlog其实是一个连接队列,backlog队列总和=未完成三次握手队列+已完成三次握手队列
在高并发环境下你需要一个高backlog值来避免慢客户端连接问题,注意linux内核会将这个值减小到
/proc/sys/net/core/somaxconn的值,所以需要增大somaxconn和tcp_max_syn_backlog两个值来达到想要的结果
tcp-backlog 511
# Close the connection after a client is idle for N seconds (0 to disable)
当客户端N秒没有活动,那么关闭连接,0表示禁用该功能,一直保持连接
timeout 0
# A reasonable value for this option is 300 seconds, which is the new
# Redis default starting with Redis 3.2.1.
如果是redis集群,那么每隔300秒发送一个信息,确保自己还活着。
tcp-keepalive 300
general通用
# By default Redis does not run as a daemon. Use 'yes' if you need it.
# Note that Redis will write a pid file in /var/run/redis.pid when daemonized.
是否是以守护进程方式启动,默认是no,但是一般我们会改成yes,也就是让redis后台启动
daemonize no
# Creating a pid file is best effort: if Redis is not able to create it
# nothing bad happens, the server will start and run normally.
redis的pid管道文件
pidfile /var/run/redis_6379.pid
# warning (only very important / critical messages are logged)
日志级别:debug、verbose、notice、warning,级别越高,打印的信息越少。刚开发会选择debug,会打印详细日志,上线之后选择notice或者warning
loglevel notice
# Specify the log file name. Also the empty string can be used to force
# Redis to log on the standard output. Note that if you use standard
# output for logging but daemonize, logs will be sent to /dev/null
# 日志名字
logfile ""
# To enable logging to the system logger, just set 'syslog-enabled' to yes,
# and optionally update the other syslog parameters to suit your needs.
# 是否把日志输出到系统日志里面,默认是不输出
# syslog-enabled no
# Specify the syslog identity.
# 指定日志文件标识,这里是redis,所以就是redis.log
# syslog-ident redis
# Specify the syslog facility. Must be USER or between LOCAL0-LOCAL7.
# 指定syslog设备,值可以是user或者local0到local7
# syslog-facility local0
# Set the number of databases. The default database is DB 0, you can select
# a different one on a per-connection basis using SELECT <dbid> where
# dbid is a number between 0 and 'databases'-1
# redis数据库的数量,默认是16个,0-15
databases 16
# By default Redis shows an ASCII art logo only when started to log to the
# standard output and if the standard output is a TTY. Basically this means
# that normally a logo is displayed only in interactive sessions.
#
# However it is possible to force the pre-4.0 behavior and always show a
# ASCII art logo in startup logs by setting the following option to yes.
# 总是显示logo
always-show-logo yes
SECURITY安全
# 设置密码,一旦设置,再使用redis-cli连接的时候就需要指定密码了
# 否则进去之后无法执行命令,可以使用redis-cli -a password,但是这样密码就暴露在终端中
# 尽管能连接,但是redis提示你不安全。当然我们还可以进去之后通过auth password来设置
# requirepass foobared
CLIENT客户端
# 设置客户端的最大连接数量,默认是10000
# maxclients 10000
MEMORY MANAGEMENT内存管理
# 最大内存
# maxmemory <bytes>
# 当你的内存达到极限的时候,肯定要清除缓存,那么你要选择哪种策略
# volatile-lru:使用LRU(最近最少使用)策略移除keys,只针对过期的keys
# allkeys-lru:使用LRU(最近最少使用)策略移除keys
# volatile-lfu:使用LFU(最近最不常使用)策略移除keys,只针对过期的keys
# allkeys-lru:使用LFU(最近最不常使用)策略移除keys
# volatile-random:随机移除一个过期的key
# allkeys-random:随机移除一个任意key
# volatile-ttl:移除ttl值(过期时间)最少的key,即最快要过期的key
# noeviction:不移除任意key,仅仅在写操作的时候返回一个error
# 默认是noeviction
# maxmemory-policy noeviction
# LRU和LFU都并非精确的算法,而是估算值,因此你可以设置样本的大小,默认是5个
# maxmemory-samples 5
# 从节点是否忽略maxmemory配置,针对redis集群,并且是从redis 5开始才有这个配置
# replica-ignore-maxmemory yes
当然还有一部分配置文件没有介绍,不过这是下面的内容,我们会在下面继续介绍。
redis的持久化
redis的一大特点就是可以将数据持久化到磁盘上,我们之前的操作都是基于内存,一旦关闭程序,那么数据就丢失了。因此我们需要在指定的时间间隔内将内存的数据快照写入到磁盘,也就是行话讲的Snapshot快照,它恢复时是将快照文件直接写入到内存里面。而redis为我们提供了两种数据持久化的方式,一种是RDB,另一种是AOF
同步策略:
RDB:可以指定某个时间内发生多少个命令进行同步。比如一分钟内发生了两次命令,就进行一次同步
AOF:每秒同步或者每次发生命令后同步
存储内容:
RDB:存储的是redis里面具体的值
AOF:存储的是执行的写操作命令
而这两者也有优缺点:
优点:
RDB: 1.存储数据到文件中会进行压缩,文件体积比AOF小。2.因为存储的是redis具体的值,并且会经过压缩,因此在恢复的时候比AOF块。3.非常适用于备份
AOF:1.AOF的策略是每秒钟或者每次发生写操作的时候都会同步,因此即使服务器发生故障,也只会丢失一秒的数据。2.AOF存储的是redis命令并且直接追加到aof文件后面,因此每次备份的时候只要添加新的数据进去就可以了。3.如果AOF文件比较大,那么redis会进行重写,只保留最小的命令集合
缺点:
RDB:1.RDB在多少时间内发生了多少写操作的时候就会发出同步机制,因为采用压缩机制,RDB在同步的时候都重新保存整个redis中的数据,因此一般会设置在最少5分钟内才保存一次数据。在这种情况下,一旦服务器故障,就会造成5分钟的数据丢失。2.在数据保存进RDB的时候,redis会fork出一个子进程用来同步,在数据流比较大的时候可能会非常耗时
AOF:1.AOF文件因为没有压缩,因此体积比RDB大。2.AOF是在每秒或者每次写操作都进行备份,因此如果并发量比较大,效率会有点低。3.因为存储的是命令,因此在灾难恢复的时候redis会重新运行AOF文件里的命令,速度不及RDB
rdb配置文件
################################ SNAPSHOTTING ################################
# 如果满足以下策略,就会将数据同步到磁盘
# 900秒内有1次修改、300秒内有10次修改、60秒内有10000次修改,任何一个条件满足,都会将文件写入到磁盘上,当然这数值我们是可以修改的
# 如果想立刻备份,那么直接在命令行输入save即可,会立刻备份,此时会处于阻塞状态,其他所有命令都会阻塞
# 也可以输入bgsave命令进行备份,和save不同的是,bgsave是在后台异步进行快照。并且还可以通过lastsave命令获取最后一次成功执行快照的时间
save 900 1
save 300 10
save 60 10000
# 当快照操作出错时停止写数据到磁盘,这样后面写操作均会失败。
# 比如内存4GB,但是当前主进程使用的3GB,那么将数据写入磁盘、fork一份主进程的时候就又需要额外的3GB,显然内存不够了,因此保存到硬盘的数据也就失败了
# 为了不影响后续写操作,可以将该项值改为no
stop-writes-on-bgsave-error yes
# 是否压缩,默认是yes,采用lzf方式压缩
rdbcompression yes
# 对rdb数据进行校验,耗费CPU资源,会大概增加10%的性能损耗,默认为yes
rdbchecksum yes
# The filename where to dump the DB
# rdb文件名,默认是rdb,并且也是默认开启rdb持久化
# 如果把上面所有的save给注释掉,那么就相当于关闭rdb
dbfilename dump.rdb
# The working directory.
#
# The DB will be written inside this directory, with the filename specified
# above using the 'dbfilename' configuration directive.
#
# The Append Only File will also be created inside this directory.
#
# Note that you must specify a directory here, not a file name.
# 路径,这里的路径,如果你不单独指定,那么默认是redis的启动路径
# 也就是你是在哪个目录下启动的redis-server,那么dir就是哪里
# 但同时这也是rdb文件的所在路径,比如我是在/root下面执行的redis-server,那么dir就是/root,同时rdb文件的路径也是/root/dump.rdb
dir ./
aof配置文件
############################## APPEND ONLY MODE ###############################
# aof意思是append only file,我们看到默认是no关闭的,因此持久化默认使用rdb
appendonly no
# 正如dump.rdb,aof方式持久化也就一个文件名,默认叫appendonly.aof
appendfilename "appendonly.aof"
# 另外aof和rdb持久化方式是可以同时指定的
# 那我们知道当redis启动的时候,会加载文件,读进缓存。
# 如果既有rdb文件又有aof文件,那redis会读取哪个呢?实际上会读取aof文件
# 如果aof被人乱搞了一通,那么会发现redis无法启动,启动了也连接不上。
# 这个时候有两种办法,一种是删除相应的aof文件,但是这样数据就丢了,还有一种方法,就是redis-server所在路径(一般是/usr/local/bin)中,有一个redis-check-aof
# 通过redis-check-aof --fix appendonly.aof可以进行修复,同理还有一个redis-check-rdb,用来修复rdb文件。
# appendfsync:同步策略,支持三个参数
# 1.always:同步持久化,每次发生数据变更会被立即记录到磁盘,并完成同步,性能较差但是数据完整性较好
# 2.everysec:默认设置,异步操作,每秒记录,并完成同步,如果1s内宕机,只丢失1s内的数据
# 3.no:always和everysec都会和磁盘保持同步,而no表示写入aof文件,但并不等待磁盘同步,也就是写入缓冲区
# appendfsync always
appendfsync everysec
# appendfsync no
# 子进程重写aof文件时是否使用appendfsync,用默认no即可,保证数据安全性
# 如果使用yes,那么相当于将appendfsync设置为no,注意这个参数前面有一个no了,设置为yes,才表示不使用appendfsync,这个参数设计的有点绕
# 设置为yes,说明不会写入磁盘,只是写入缓冲区,因此不会造成阻塞,但如果redis挂掉,在linux系统默认设置下,会丢失30s的数据
# 使用no,表示使用appendfsync,表示子进程会写入到磁盘,这时候和主进程之间会有资源上的竞争,因为都要操作磁盘,所以会有阻塞的情况,但是不会丢失数据。
no-appendfsync-on-rewrite no
# aof文件增长比例,指当前aof文件比上次重写的增长比例大小。
# aof重写即在aof文件在一定大小之后,重新将整个内存写到aof文件当中,以反映最新的状态(相当于bgsave)。
# 这样就避免了,aof文件过大而实际内存数据小的问题(频繁修改数据问题).
auto-aof-rewrite-percentage 100
# aof文件重写最小的文件大小,即最开始aof文件必须要达到这个文件时才触发,后面的每次重写就不会根据这个变量了(根据上一次重写完成之后的大小)
# 此变量仅初始化启动redis有效.如果是redis恢复时,则lastSize等于初始aof文件大小.
auto-aof-rewrite-min-size 64mb
# 指redis在恢复时,会忽略最后一条可能存在问题的指令。
# 默认值yes。即在aof写入时,可能存在指令写错的问题(突然断电,写了一半)
# 这种情况下,yes会log并继续,而no会直接恢复失败.
aof-load-truncated yes
# 4.0开始允许使用RDB-AOF混合持久化的方式,结合了两者的优点,通过 aof-use-rdb-preamble 配置项可以打开混合开关
aof-use-rdb-preamble yes
Redis的事务
事务我想已经不需要多做概念上的解析了,并且百分之90以上都会用"转账"的例子来说明事务到底是个什么东西。
一个队列中,一次性、顺序性、排他性地执行一系列命令
redis的事务的相关命令有以下几种:
multi:标记一个事务块的开始
exec:执行所有事务块的命令
discard:取消事务,放弃执行事务块内的所有命令
watch:监视一个或者多个key,如果在事务执行前(多个key的任意一个)key被其他命令所改动,那么事务将被打断
unwatch:取消对所有key的监视
下面我们来操作一波
开启事务
当然事务也是可以取消的
取消事务
另外关键的一点,如果在事务中,命令出错了,那么再执行exec的话,redis会提示由于之前的错误导致整个事务都被取消了。要么都成功,要么一个都别跑。
命令出错
但是,我要说但是了
我们知道name是一个字符串,incr表示自增1,显然会出错,但是下面的命令还是执行成功了,这是为什么?答案很简单,这就跟python中的编译错误和运行时错误是一样的,事务里面的命令不会立刻执行,而是放到队列里面,然后一次性执行。如果是在执行时候出现的错误,那么是不会影响后面的命令的,后面的命令依旧会执行。而第一次事务出现的错误,相当于python中的语法错误,在编译的时候就不让你通过,整个程序都没执行、在编译字节码的时候就把异常抛出了,所以在redis当中出现这种错误直接就把整个事务终止掉了。但是运行时出现错误的话,程序并不会像python一样终止掉,而是会继续执行后面的命令,也就是不会回滚。
watch监控和unwatch取消监控
redis的监控会使用到锁机制,而锁分为悲观锁和乐观锁。
类似于mysql里面的"表锁"和"行锁"。"表锁"就是为了保证数据的一致性,将整张表锁上,这样就只能一个人修改,好比进卫生间,进去之后就把大门锁上了,但这样的结果也可想而知,虽然数据的一致性、安全性好,但是并发性会极差,因为其他人进不去了。比如一张有20万条记录的表,但是你只修改第520行,而另一个哥们修改第250行,本来两者不冲突,但是你把整个表都锁了,那就意味这后面的老铁只能排队了,这样显然效率不高。于是就出现了"行锁","行锁"在mysql中,就类似于表中有一个版本号的字段,假设有一条记录的版本号为1,A和B同时修改这条记录,那么一旦提交,就会改变那个版本号,假设变为2。如果A先提交了,那么数据库中对应记录的版本号已经变了,但是B对应的版本号还是之前的,那么提交之后会立即报错,这样就知道这条记录被人修改了,需要重新获取对应版本号的记录。
悲观锁:
pessimistic lock,顾名思义,就是很悲观,每次拿数据的时候都会认为别人会修改,所以每次拿数据的时候都会上锁,这样别人想拿到这个数据就会block住,直到拿到锁。
乐观锁:
提交版本必须大于记录的当前版本才能更新
下面就来将这个监控,首先watch是需要配置multi事务来使用的。一般是先watch key,然后开启事务对key操作。
上面执行的结果显然没有问题,但是往下看
于是有人觉得,这是因为你在开启事务之前就把money修改了,如果是先开启的事务呢?
因此我们可以得出一个结论,那就是一旦监视了key,那么这个key如果想改变,,只能开启一个事务,在事务中修改,然后exec执行来改变这个key。只要在事务没有执行之前,将watch监视的key修改了,那么不好意思,事务都会失效。正如mysql的行锁一样,两个人都可以对同一条记录做修改,但是一个人先改好之后,另一个人提交就会失败,必须查找到对应的版本号,然后重新查找对应记录,修改才能提交。这在redis中如何实现呢,答案很简单,如果开始事务之前被修改了,那么把取消监视就好了。
另外一点,记住一个watch对应一个事务,如果watch之后,执行了事务,那么对这个key的监视就算结束了。如果想继续监视,那么必须再次watch key
但是,我要说但是了
特性总结
单独的隔离操作:事务中所有的命令都会被序列化,按照顺序执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断
没有隔离级别的状态:队列中的命令在没有提交之前(exec),都不会被实际地执行,因为开启事务之后、事务提交之前,任何指令都不会被实际地执行。也就不存在"事务内的查询要看到更新,事务外查询无法看到"这个让人头疼的问题
不保证原子性:我们之前演示过,如果是在运行时出错,那么后面的命令会继续执行,不会回滚。我们当时还拿python的编译和运行时举的例子
redis的发布和订阅
发布和订阅就类似于微信公众号,只有当你订阅了之后,发布的内容才能收到。既然如此,那么肯定会有发布者(publisher)和订阅者(subscriber)。
所以这有点像消息队列的感觉,本来在缓存方面就已经把memcached给干掉了,但是redis还是比较有野心的,所以在消息的发布订阅、也就是消息队列方向上也想闯一闯。因此redis的发布订阅总结一下,就是进程间的消息通信模式:发送者发送消息,订阅者接收消息。尽管redis支持消息的发布订阅,但它还是使用缓存比较好。因此大公司很少有将redis作为消息队列来使用的,因此消息队列的话一般还是使用rabbitmq、activemq等等。
psubscribe new*
此时程序就卡在了这里
此时开启了订阅功能,程序就卡在了这里
发布:publish,往频道里面发送消息
新开一个终端,往channel里面发送消息
既然有订阅subscribe,就有取消订阅unsubscribe,使用unsubscribe channel1 channel2可以取消订阅多个channel。同理对于psubscribe new*,也有punsubscribe new*取消订阅指定模式的频道
对于redis的消息发布功能,只需要了解有这么一个东西即可,因为在实际的项目生产中几乎没有用redis作为消息队列来使用的。如果是自己临时需要一个消息队列,可以使用redis顶上去一下,但是最好使用rabbitmq等专门的消息队列
redis的主从复制
概念
主从复制,就是主机数据更新之后自动同步到备机上的master/slave机制,master以写为主,slave以读为主。主要为了实现读写分离、容灾恢复。
相关操作
由于我阿里云只有一台机器,所以就用一台机器来模拟。我们将redis的配置文件拷贝三份,配置文件监听的端口分别修改6379,6380,6381。6379是用来写的,6380和6381使用来备份的。
下面我们在连接6379的终端上进行写命令
set k4 v4
我们看到直接获取是没问题的,会自动同步,因为此时已经和指定的master建立联系了。并且显式的slaveof的话,反而会提示我们已经连接到指定的master了,当然如果指定的是新的master的ip和端口的话,那么就会备份新的master。另外一旦slaveof之后,那么这个机器上的redis就已经成为slave了,那么就不可以再写数据了,只能老老实实地自动从主机备份数据。就类似于docker一样,宿主机目录和容器目录关联,但容器是只读的,只能和宿主机上的数据保持同步,但不能写入数据。
另外一旦进行了slaveof,那么主机上的数据都会从头撸到尾,统统备份到主机上。但是,如果备机在slaveof之前就有数据了怎么办呢?我们知道一旦slaveof就会成为备机,成为备机之后就不能再写数据了,但是在成为备机之前,本身就有数据的话,那么已经存在的数据会怎么办呢?试一下就知道了
忘记说了,我们还可以通过info replication来查看状态
发生故障
(针对当前主从复制)
如果是备机挂了呢?其实备机挂了我们之前演示过了,备机挂了再重启,那么会和之前的master失去联系,自己是一个新的master。如果再想备份之前的主机,那么需要显式的执行slaveof命令,主机上数据会自动同步过来,并且成为备机之前单独设置的数据会丢失,毕竟要和主机保持同步嘛,而且不可能把备机上的数据同步到主机上去,否则主机就不叫主机了,因此redis的策略是把备机上原有的数据给删除。
另外两种主从复制
之前是有一个master,两个slave。master写完数据会自动同步到两个slave上面,这是redis的一种主从复制的方式,但是这样是不是增加了主机的负担呢?于是我们想,可不可以master只同步到一个slave上面,然后一个slave再同步到另一个slave上面呢?
所以上一个slave可以是下一个slave的master,slave同样可以接收其他的slave的连接和同步请求,那么该slave作为了slave链条中的下一个"master",可以有效减轻真正master的压力。这是redis的第二种主从复制的方式
将主机数据清空,然后备机重新启动
我们之前说,如果主机挂了,备机会原地待命。但是这样也有局限性,因此我们希望如果主机挂了,备机顶上去成为master,此时要怎么做呢?
复制原理
slave成功启动连接到master之后,会发送sync命令master接到命令启动后台的存盘进程,同时收集所有接收到的用于修改数据集的命令,在后台进程执行完毕之后,master将传送整个数据文件到slave,以完成一次数据同步全量复制:slave在接收到数据库文件数据之后,将其存盘并加载到内存中增量复制:master将继续收集新的所有变更数据的命令,然后传递给slave,以完成数据的同步但是只要是重新连接到master,一次完全同步(全量复制)将被执行。首次是全量,然后是增量
哨兵模式
上面介绍的三种主从复制方式生产上用的不是很多,哨兵模式用的却是很常见,我们来看看哨兵模式是什么。不过我们先把redis服务重新启动一下,让6380和6381继续跟随6379
那么什么是哨兵模式呢?首先之前我们介绍了三种主从复制的方式:
1.master把数据同步到所有的slave上面2.master不把数据同步到所有的slave上面,slave之间也可以进行数据的传递3.master一旦挂了,我们可以使用slave顶上去成为master
slaveof no one
sentinel monitor 自己起个名字 ip 端口 1
number表示主机挂掉之后,让slave之间进行投票,谁的票数多,谁就成为主机
redis-sentinel sentinel.conf
复制的缺点
由于所有的写操作,都是现在master上进行操作,然后同步更新到slave上,所以master机器同步到slave机器上有一定的延迟,当系统很繁忙的时候,延迟问题会更加严重,当然slave机器数量的增加也会使这个问题变得严重。
python连接redis
pip install redis
import redis
client = redis.Redis("127.0.0.1",
6379,
db=0,
# password=None, 如果设置了密码,就加上这个参数
decode_responses="utf-8", # 取出的数据默认是个字节,加上这个参数会自动按照指定的编码解码成字符串
)
# client里面的api和redis-cli使用的api是一致的。
# set name hanser
client.set("name", "hanser") # 里面还支持ex nx xx等参数
# lpush list 1 2 3 4 5 6
client.lpush("list", 1, 2, 3, 4, 5, 6)
print(client.get("name")) # hanser
print(client.lrange("list", 0, -1)) # ['6', '5', '4', '3', '2', '1']
# 其余的api可以自己尝试,不再一一演示,redis-cli用的api,均可通过client操作,而且操作的函数名是一致的。
golang连接redis
go get github.com/garyburd/redigo/redis
package main
import (
"fmt"
"github.com/garyburd/redigo/redis"
)
func main() {
var client redis.Conn
var err error
//创建连接redis的客户端
if client, err = redis.Dial("tcp", "127.0.0.1:6379"); err != nil {
panic(err)
}
//设置值,返回两个结果。设置成功的结果和error
//golang连接redis比较简单粗暴,直接使用Do函数即可,里面输入的是redis的命令
//所以你会发现使用golang和redis-cli,几乎没啥区别,不过是把输入命令的地方给换了
if _, err = client.Do("set", "name", "yousa没有腿", "ex", 60); err != nil {
panic(err)
}
//由于获取得到的是一个字节,因此可以套上一层redis.String,这样拿到的就是字符串
if res, err := redis.String(client.Do("get", "name")); err != nil {
panic(err)
} else {
fmt.Println(res) // yousa没有腿
}
//设置列表
if _, err = client.Do("lpush", "哼哼", 1 , 2, 3, 4); err != nil {
panic(err)
}
//获取列表内容
if res, err := redis.Int64(client.Do("lpop", "哼哼")); err != nil {
panic(err)
} else {
fmt.Println(res) // 4
}
}
golang连接redis更加简单粗暴,连接之后直接Do即可,所有命令都和redis的保持一致。另外真想吐槽一句,golang没有关键字参数真的让人难受。不过这也是由golang的静态语言特性所决定的,至于redis支持的其他api可以自己尝试。
结束
就到这里啦,拜拜。