本文共 16137 字,大约阅读时间需要 53 分钟。
NoSQL(NoSQL = Not Only SQL),意即“不仅仅是SQL”,泛指非关系型的数据库。随着互联网web2.0网站的兴起,传统的关系数据库在应付web2.0网站,特别是超大规模和高并发的SNS类型的web2.0纯动态网站已经显得力不从心,暴露了很多难以克服的问题,而非关系型的数据库则由于其本身的特点得到了非常迅速的发展。NoSQL数据库的产生就是为了解决大规模数据集合多重数据种类带来的挑战,尤其是大数据应用难题,包括超大规模数据的存储。
谷歌或Facebook每天为他们的用户收集万亿比特的数据,这些类型的数据存储不需要固定的模式,无需多余操作就可以横向扩展。
优势:
聚合模型:
{ "customer":{ "id":1136, "name":"Z3", "billingAddress":[{ "city":"beijing"}], "orders":[ { "id":17, "customerId":1136, "orderItems":[{ "productId":27,"price":77.5,"productName":"thinking in java"}], "shippingAddress":[{ "city":"beijing"}] "orderPayment":[{ "ccinfo":"111-222-333","txnid":"asdfadcd334","billingAddress":{ "city":"beijing"}}], } ] }}
分布式系统不可能同时满足一致性(C:Consistency)、可用性(A:Availability)和分区容忍性(P:Partition Tolerance),最多只能同时满足其中两项。
一致性指的是多个数据副本是否能保持一致的特性,在一致性的条件下,系统在执行数据更新操作之后能够从一致性状态转移到另一个一致性状态。
对系统的一个数据更新成功之后,如果所有用户都能够读取到最新的值,该系统就被认为具有强一致性。
可用性指分布式系统在面对各种异常时可以提供正常服务的能力,可以用系统可用时间占总时间的比值来衡量,4个9的可用性表示系统 99.99% 的时间是可用的。
在可用性条件下,要求系统提供的服务一直处于可用的状态,对于用户的每一个操作请求总是能够在有限的时间内返回结果。
网络分区指分布式系统中的节点被划分为多个区域,每个区域内部可以通信,但是区域之间无法通信。
在分区容忍性条件下,分布式系统在遇到任何网络分区故障的时候,仍然需要能对外提供一致性和可用性的服务,除非是整个网络环境都发生了故障。
在分布式系统中,分区容忍性必不可少,因为需要总是假设网络是不可靠的。因此,CAP 理论实际上是要在可用性和一致性之间做权衡。
可用性和一致性往往是冲突的,很难使它们同时满足。在多个节点之间进行数据同步时,
数据库:
BASE就是为了解决关系数据库强一致性引起的问题而引起的可用性降低而提出的解决方案。
BASE其实是下面三个术语的缩写:
它的思想是通过让系统放松对某一时刻数据一致性的要求来换取系统整体伸缩性和性能上改观,缘由在于大型系统往往由于地域分布和极高性能的要求,不可能采用分布式事务来完成这些指标,要想获得这些指标,我们必须采用另外一种方式来完成,这里BASE就是解决这个问题的办法。
Redis是Remote Dictionary Server(远程数据服务)的缩写
是由意大利人antirez开发的一款内存高速缓存数据库(而MySQL数据库中的数据存放在硬盘中)。该软件采用C语言编写,数据模型为key-value,它具有丰富的数据结构、可持久化,保证了数据的安全。对于新闻页面,可以采用页面缓存,常用在CMS内存管理系统中;
对于商城商品,各个部分业务比较独立,可以采用数据缓存,Redis即是数据缓存,可以减轻数据库的负载; 取最新N个数据的操作,如:可以将最新的10条评论的ID放在Redis的List集合里面。tar zxvf redis-2.6.15
yum -y install gcc
make MALLOC=libc
$ find . -type f -executable./redis-benchmark //用于进行redis性能测试的工具./redis-check-dump //用于修复出问题的dump.rdb文件./redis-cli //redis的客户端./redis-server //redis的服务端./redis-check-aof //用于修复出问题的AOF文件./redis-sentinel //用于集群管理
./redis-server
即在前端启动了Redis
输入Ctrl+C即可停止服务。修改redis.conf配置文件,daemonize = yes 设置为后台启动:
./redis-server redis.conf
简单使用
./redis-cli //启动Redis客户端
redis 127.0.0.1:6379> set name tomOKredis 127.0.0.1:6379> get name"tom"
关闭操作:
redis 127.0.0.1:6379> SHUTDOWNredis 127.0.0.1:6379> EXEC
SELECT <dbid>
命令在连接上指定数据库id;dbsize
查看当前数据库的key的数量flushdb
:清空当前库flushall
:通杀全部库http://redisdoc.com/
redis本质上是一个key-value 数据库,在Redis中,key除了**‘空格’和‘/n’**不能作为名字的组成外,其它均可。
注意:key的相关命令
redis 127.0.0.1:6379> exists name(integer) 1redis 127.0.0.1:6379> type namestringredis 127.0.0.1:6379> keys a*1)"addr"2)"age"redis 127.0.0.1:6379> dbsize(integer) 4redis 127.0.0.1:6379> del jf0ej093jrjfpkefplk%%&*()(integer) 1redis 127.0.0.1:6379> rename age mingziOKredis 127.0.0.1:6379> select 1 //选择第二个数据库(下标从0开始)OKredis 127.0.0.1:6379> flushdbOK
如果只使用redis中的字符串类型,且不使用redis的持久化功能,那redis就memcache类似了,这说明strings类型是一个很基础的数据类型,也是任何存储系统都必备的数据类型。我们甚至可以把一个图片文件的内容作为字符串来存储。
常用操作:
redis 127.0.0.1:6379> mset color1 red color2 green color3 blueOKredis 127.0.0.1:6379> mget color1 color2 color3
incr key :对key的值做加加操作,并返回新的值。注意incr一个不是int的value会返回错误,incr一个不存在的key,则设置key为1。
redis 127.0.0.1:6379> incr num(integer) 1redis 127.0.0.1:6379> get num"1"redis 127.0.0.1:6379> incr num(integer) 2redis 127.0.0.1:6379> get num"2"redis 127.0.0.1:6379> incrby num 30(integer) 32redis 127.0.0.1:6379> append color3 andyellowredis 127.0.0.1:6379> get color3"blueandyellow"redis 127.0.0.1:6379> substr color3 4 6"and"
getrange:获取指定区间范围内的值,类似between…and的关系
从零到负一表示全部;setrange设置指定区间范围内的值:redis 127.0.0.1:6379> set k1 tjtulongOKredis 127.0.0.1:6379> getrange k1 2 8"tulong"
setex:设置带过期时间的key,动态设置。
redis 127.0.0.1:6379> setex k2 10 xiaoOKredis 127.0.0.1:6379> ttl k2(integer) 5redis 127.0.0.1:6379> get k2(nil)
setnx:只有在 key 不存在时设置 key 的值。
redis的list类型其实就是一个每个子元素都是string类型的双向链表。我们可以通过push,pop操作从链表的头部或者尾部添加删除元素。这使得list既可以用作栈,也可以用作队列。
例如:一个网页想获得最近登录系统的前10个用户,如果用MySQL则需要对整个表进行操作,消耗资源,如果用Redis链表即可快速保存和获取。 List常用操作: 保存最新登录的5个用户:redis 127.0.0.1:6379[1]> lpush newlogin tom(integer) 1redis 127.0.0.1:6379[1]> lpush newlogin mary(integer) 2redis 127.0.0.1:6379[1]> lpush newlogin linken(integer) 3redis 127.0.0.1:6379[1]> lpush newlogin xiaoming(integer) 4redis 127.0.0.1:6379[1]> lpush newlogin jack(integer) 5redis 127.0.0.1:6379[1]> lpush newlogin xiaoli(integer) 6redis 127.0.0.1:6379[1]> rpop newlogin"tom"//通过范围查找链表中元素redis 127.0.0.1:6379[1]> lrange newlogin 0 41) "xiaoli"2) "jack"3) "xiaoming"4) "linken"5) "mary"
通过索引获取列表中的元素 lindex key index
ltrim key 开始index 结束index
,截取指定范围的值后再赋值给key
在list某个已有值的前后再添加具体值: linsert key before/after 值1 值2
链表的操作无论是头和尾效率都极高,但假如是对中间元素进行操作,效率较低了。
Redis的Set是String类型的无序集合,Set元素最大可以包含(2的32次方-1)个元素,Set的是通过Hashtable实现的,Hashtable会随着添加或者删除自动的调整大小。
Set集合类型除了基本的添加删除操作,其他有用的操作还包含集合的取并集(union),交集(intersection),差集(difference),可用好友推荐功能。redis 127.0.0.1:6379[1]> sadd tomFri mary(integer) 1redis 127.0.0.1:6379[1]> sadd tomFri jack(integer) 1redis 127.0.0.1:6379[1]> sadd tomFri xiaoming(integer) 1redis 127.0.0.1:6379[1]> sadd tomFri wangwu(integer) 1redis 127.0.0.1:6379[1]> sadd linkenFri yuehan(integer) 1redis 127.0.0.1:6379[1]> sadd linkenFri daxiong(integer) 1redis 127.0.0.1:6379[1]> sadd linkenFri xiaoming(integer) 1redis 127.0.0.1:6379[1]> sadd linkenFri wangwu(integer) 1//取交集和并集redis 127.0.0.1:6379[1]> sinter tomFri linkenFri1) "xiaoming"2) "wangwu"redis 127.0.0.1:6379[1]> sunion tomFri linkenFri1) "yuehan"2) "mary"3) "daxiong"4) "xiaoming"5) "jack"6) "wangwu"redis 127.0.0.1:6379[1]> smove tomFri linkenFri mary(integer) 1redis 127.0.0.1:6379[1]> scard tomFri(integer) 3
和Set一样SortedSet也是String类型元素的集合,不同的是每个元素都会关联一个double类型的score。SortedSet的实现是skip list和Hashtable的混合体。当元素被添加到集合中时,一个元素到score的映射被添加到hash table中,另一个score到元素的映射被添加到skip list并按照score排序,所以就可以有序的获取集合中的元素。
每个元素都是值+权的组合;应用案例:获得热门帖子回复量信息
redis 127.0.0.1:6379[1]> zadd hotmessage 102 11(integer) 1redis 127.0.0.1:6379[1]> zadd hotmessage 141 12(integer) 1redis 127.0.0.1:6379[1]> zadd hotmessage 159 13(integer) 1redis 127.0.0.1:6379[1]> zadd hotmessage 72 14(integer) 1redis 127.0.0.1:6379[1]> zrevrange hotmessage 0 1001) "15"2) "13"3) "12"4) "11"5) "14"//删除排名倒数第一的元素redis 127.0.0.1:6379[1]> zremrangebyrank hotmessage 0 0(integer) 1redis 127.0.0.1:6379[1]> zrevrange hotmessage 0 1001) "15"2) "13"3) "12"4) "11"
hashes,即哈希。哈希是从redis-2.0.0版本之后才有的数据结构。hashes存的是字符串和字符串值之间的映射,比如一个用户要存储其全名、姓氏、年龄等等,就很适合使用哈希。
hset
:建立哈希并赋值
hget
:取值 hmset
:建立哈希表 hmget
:获取值 hgetall
:获取所有 hdel
:删除 //建立哈希,并赋值127.0.0.1:6379> HMSET user:001 username antirez password P1pp0 age 34 OK//列出哈希的内容127.0.0.1:6379> HGETALL user:001 1) "username"2) "antirez"3) "password"4) "P1pp0"5) "age"6) "34"//更改哈希中的某一个值127.0.0.1:6379> HSET user:001 password 12345 (integer) 0//再次列出哈希的内容127.0.0.1:6379> HGETALL user:001 1) "username"2) "antirez"3) "password"4) "12345"5) "age"6) "34"
UNITS
INCLUDES
可以通过includes包含,redis.conf可以作为总闸,包含其他GENERAL通用
设置ip地址,端口,超时时间等通用配置SNAPSHOTTING快照、REPLICATION复制
SECURITY安全
可以设置密码config set requirepass '123456'
LIMITS限制
Redis是一个支持持久化的内存数据库,也就是说redis需要经常将内存中的数据同步到磁盘来保证持久化,这是相对memcache来说的一个大的优势。redis支持两种持久化方式,一种是 Snapshotting(快照)也是默认方式,另一种是Append-only file(缩写aof)的方式。
在指定的时间间隔内将内存中的数据集快照写入磁盘(Snapshot快照),它恢复时是将快照文件直接读到内存里。
Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程是不进行任何IO操作的,这就确保了极高的性能。如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效。RDB的缺点是最后一次持久化后的数据可能丢失,同时fork的时候,内存中的数据被克隆了一份,大致2倍的膨胀性需要考虑。
快照是默认的持久化方式,一次性把Redis中全部数据保存在硬盘上。
快照持久化的储存文件为dump. rmp 快照保存配置: save 900 1 900秒内如果超过1个key被修改,则发起快照保存 save 300 10 300秒内容如超过10个key被修改,则发起快照保存 数据修改的频率非常高,备份的频率也高。在shutdown后会自动存储一次快照,也可以直接save(阻塞),执行flushall命令,也会产生dump.rdb文件,但里面是空的,无意义
手动快照持久化:./redis-cli bgsave
开启AOF持久化(会将以前的数据删除),删除旧进程,开启新进程。
AOF方式是将执行过的写指令记录下来,在数据恢复时按照从前到后的顺序再将指令都执行一遍。默认没有开启。
appendonly yes 开启 appendonly.aof文件保存AOF持久化备份频率非常高
有三种方式如下(默认是:每秒fsync一次)注:dump.rmp和appendonly.aof同时存在时找appendonly.aof
为aof备份做优化处理(重写)
AOF采用文件追加方式,文件会越来越大,为避免出现此种情况,新增了重写机制,当AOF文件的大小超过所设定的阈值时,Redis就会启动AOF文件的内容压缩,只保留可以恢复数据的最小指令集。可以使用命令bgrewriteaof
。 Redis会记录上次重写时的AOF大小,默认配置是当AOF文件大小是上次rewrite后大小的一倍且文件大于64M时触发
./redis-cli bgrewiteaof
因为RDB文件只用作后备用途,建议只在Slave上持久化RDB文件,而且只要15分钟备份一次就够了,只保留save 900 1这条规则。
如果Enalbe AOF,好处是在最恶劣情况下也只会丢失不超过两秒数据,启动脚本较简单只load自己的AOF文件就可以了。代价一是带来了持续的IO,二是AOF rewrite的最后将rewrite过程中产生的新数据写到新文件造成的阻塞几乎是不可避免的。只要硬盘许可,应该尽量减少AOF rewrite的频率,AOF重写的基础大小默认值64M太小了,可以设到5G以上。默认超过原大小100%大小时重写,可以改到适当的数值。
如果不Enable AOF,仅靠Master-Slave Replication实现高可用性也可以,能省掉一大笔IO也减少了rewrite时带来的系统波动。代价是如果Master/Slave同时倒掉,会丢失十几分钟的数据,启动脚本也要比较两个Master/Slave中的RDB文件,载入较新的那个。新浪微博就选用了这种架构。
一个队列中,一次性、顺序性、排他性的执行一系列命令。
redis 事务的相关命令:
redis 127.0.0.1:6379> MULTIOKredis 127.0.0.1:6379> set k1 v1QUEUEDredis 127.0.0.1:6379> set k2 v2QUEUEDredis 127.0.0.1:6379> get k2QUEUEDredis 127.0.0.1:6379> EXEC1) OK2) OK3) "v2"
redis 127.0.0.1:6379> MULTIOKredis 127.0.0.1:6379> set k1 v1QUEUEDredis 127.0.0.1:6379> set k2 22QUEUEDredis 127.0.0.1:6379> set k3 33QUEUEDredis 127.0.0.1:6379> DISCARDOKredis 127.0.0.1:6379> get k2"v2"
redis 127.0.0.1:6379> MULTIOKredis 127.0.0.1:6379> set k1 v1QUEUEDredis 127.0.0.1:6379> set k2 v2QUEUEDredis 127.0.0.1:6379> set k3 v3QUEUEDredis 127.0.0.1:6379> getset k3(error) ERR wrong number of arguments for 'getset' commandredis 127.0.0.1:6379> set k4 v4QUEUEDredis 127.0.0.1:6379> EXEC(error) EXECABORT Transaction discarded because of previous errors.redis 127.0.0.1:6379> get k4(nil)
redis 127.0.0.1:6379> MULTIOKredis 127.0.0.1:6379> incr k1QUEUEDredis 127.0.0.1:6379> set k2 22QUEUEDredis 127.0.0.1:6379> set k3 33QUEUEDredis 127.0.0.1:6379> get k3QUEUEDredis 127.0.0.1:6379> EXEC1) (error) ERR value is not an integer or out of range2) OK3) OK4) "33"
说明Redis对事务部分支持
类似于乐观锁 CAS
client1:
redis 127.0.0.1:6379> WATCH balanceOK
clint2:对balance进行改变
redis 127.0.0.1:6379> set balance 800OK
clint1:事务操作失败
redis 127.0.0.1:6379> MULTIOKredis 127.0.0.1:6379> DECRBY balence 20QUEUEDredis 127.0.0.1:6379> INCRBY debt 20QUEUEDredis 127.0.0.1:6379> EXEC(nil)redis 127.0.0.1:6379> get balence"800"
特性:
主从复制,即主机数据更新后根据配置和策略,自动同步到备机的master/slaver机制,Master以写为主,Slave以读为主。
作用:
从服务器命令
slaveof 192.168.39.159 6379
通过命令info replication
可以得到节点信息:是主还是从。
当主服务器断掉后,两个从服务器还是从服务器,依然只能读不能写;
当从服务器断掉后,再开启时,变为master。
上一个Slave可以是下一个Slave的Master,Slave同样可以接收其他
slaves的连接和同步请求,那么该slave作为了链条中下一个的master, 可以有效减轻master的写压力,去中心化。79------>80------->81
SLAVEOF no one
变为主服务器
slave启动成功连接到master后会发送一个sync命令,Master接到命令启动后台的存盘进程,同时收集所有接收到的用于修改数据集命令,在后台进程执行完毕之后,master将传送整个数据文件到slave,以完成一次完全同步。
全量复制: 而slave服务在接收到数据库文件数据后,将其存盘并加载到内存中。
增量复制: Master继续将新的所有收集到的修改命令依次传给slave,完成同步。 但是只要是重新连接master,一次完全同步(全量复制)将被自动执行概念:反客为主的自动版,能够后台监控主机是否故障,如果故障了根据投票数自动将从库转换为主库。
使用方式:
sentinel monitor 被监控数据库名字(自己起名字) 127.0.0.1 6379 1
,最后一个数字1,表示主机挂掉后salve投票看让谁接替成为主机,得票数多少后成为主机;redis-sentinel /myredis/sentinel.conf
;由于所有的写操作都是先在Master上操作,然后同步更新到Slave上,所以从Master同步到Slave机器有一定的延迟,当系统很繁忙的时候,延迟问题会更加严重,Slave机器数量的增加也会使这个问题更加严重。
Maven坐标
redis.clients jedis 2.9.0
连接服务器:
public class TestPing { public static void main(String[] args) { Jedis jedis = new Jedis("127.0.0.1",6379); System.out.println(jedis.ping()); // pong }}
public class Test02 { public static void main(String[] args) { Jedis jedis = new Jedis("127.0.0.1",6379); //key Setkeys = jedis.keys("*"); for (Iterator iterator = keys.iterator(); iterator.hasNext();) { String key = (String) iterator.next(); System.out.println(key); } System.out.println("jedis.exists====>"+jedis.exists("k2")); System.out.println(jedis.ttl("k1")); //String //jedis.append("k1","myreids"); System.out.println(jedis.get("k1")); jedis.set("k4","k4_redis"); System.out.println("----------------------------------------"); jedis.mset("str1","v1","str2","v2","str3","v3"); System.out.println(jedis.mget("str1","str2","str3")); //list System.out.println("----------------------------------------"); //jedis.lpush("mylist","v1","v2","v3","v4","v5"); List list = jedis.lrange("mylist",0,-1); for (String element : list) { System.out.println(element); } //set jedis.sadd("orders","jd001"); jedis.sadd("orders","jd002"); jedis.sadd("orders","jd003"); Set set1 = jedis.smembers("orders"); for (Iterator iterator = set1.iterator(); iterator.hasNext();) { String string = (String) iterator.next(); System.out.println(string); } jedis.srem("orders","jd002"); System.out.println(jedis.smembers("orders").size()); //hash jedis.hset("hash1","userName","lisi"); System.out.println(jedis.hget("hash1","userName")); Map map = new HashMap (); map.put("telphone","13811814763"); map.put("address","atguigu"); map.put("email","abc@163.com"); jedis.hmset("hash2",map); List result = jedis.hmget("hash2", "telphone","email"); for (String element : result) { System.out.println(element); } //zset jedis.zadd("zset01",60d,"v1"); jedis.zadd("zset01",70d,"v2"); jedis.zadd("zset01",80d,"v3"); jedis.zadd("zset01",90d,"v4"); Set s1 = jedis.zrange("zset01",0,-1); for (Iterator iterator = s1.iterator(); iterator.hasNext();) { String string = (String) iterator.next(); System.out.println(string); } }}
public boolean transMethod() throws InterruptedException { Jedis jedis = new Jedis("127.0.0.1", 6379); int balance;// 可用余额 int debt;// 欠额 int amtToSubtract = 10;// 实刷额度 jedis.watch("balance"); //jedis.set("balance","5");//此句不该出现,讲课方便。模拟其他程序已经修改了该条目 Thread.sleep(7000); balance = Integer.parseInt(jedis.get("balance")); if (balance < amtToSubtract) { jedis.unwatch(); System.out.println("modify"); return false; } else { System.out.println("***********transaction"); Transaction transaction = jedis.multi(); transaction.decrBy("balance", amtToSubtract); transaction.incrBy("debt", amtToSubtract); transaction.exec(); balance = Integer.parseInt(jedis.get("balance")); debt = Integer.parseInt(jedis.get("debt")); System.out.println("*******" + balance); System.out.println("*******" + debt); return true; } }
转载地址:http://ttgmi.baihongyu.com/