Nosql介绍
NoSQL:一类新出现的数据库(not only sql) 泛指非关系型的数据库 不支持SQL语法
存储结构跟传统关系型数据库中的那种关系表完全不同,nosql中存储的数据都是KV形式
NoSQL的世界中没有一种通用的语言,每种nosql数据库都有自己的api和语法,以及擅长的业务场景 NoSQL中的产品种类相当多:
- Mongodb
- Redis
- Hbase hadoop
- Cassandra hadoop
NoSQL和SQL数据库的比较:
适用场景不同:sql数据库适合用于关系特别复杂的数据查询场景,nosql反之
“事务”特性的支持:sql对事务的支持非常完善,而nosql基本不支持事务 两者在不断地取长补短,呈现融合趋势
Redis简介
Redis是一个开源的使用ANSI
C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。从2010年3月15日起,Redis的开发工作由VMware主持。从2013年5月开始,Redis的开发由Pivotal赞助。
Redis是
NoSQL技术阵营中的一员,它通过多种键值数据类型来适应不同场景下的存储需求,借助一些高层级的接口使用其可以胜任,如缓存、队列系统的不同角色
Redis特性
Redis是一个高性能的key-value存储系统。和Memcached类似,它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(sorted
set --有序集合)和hash(哈希类型)。 Redis 很大程度补偿了memcached这类key/value存储的不足,在部
分场合可以对关系数据库起到很好的补充作用。它提供了Python,Ruby,Erlang,PHP客户端,使用很方便。(注:摘自百度全科),
1.主要是支持持久化
2.支持更多数据结构
3.支持主从同步
Redis支持主从同步。数据可以从主服务器向任意数量的从服务器上同步,从服务器可以是关联其他从服务器的主服务器。
Redis 优势
性能极高 – Redis能读的速度是110000次/s,写的速度是81000次/s 。 丰富的数据类型 – Redis支持二进制案例的
Strings, Lists, Hashes, Sets 及 Ordered Sets 数据类型操作。 原子 –
Redis的所有操作都是原子性的,同时Redis还支持对几个操作全并后的原子性执行。 丰富的特性 – Redis还支持
publish/subscribe, 通知, key 过期等等特性。
Redis应用场景
用来做缓存(ehcache/memcached)——redis的所有数据是放在内存中的(内存数据库)
可以在某些特定应用场景下替代传统数据库——比如社交类的应用 在一些大型系统中,巧妙地实现一些特定的功能:session共享、购物车
只要你有丰富的想象力,redis可以用在可以给你无限的惊喜…….
Redis 安装
win
github下载地址:https://github.com/MicrosoftArchive/redis
- 在运行中输入cmd,然后把目录指向解压的Redis目录
- 启动命令
redis-server redis.windows.conf,出现下图显示表示启动成功了。 - 设置Redis服务
设置服务命令
redis-server --service-install redis.windows-service.conf --loglevel verbose - 一个server、一个cli,启动起来就好了
linux
当前redis最新稳定版本是4.0.9 当前ubuntu虚拟机中已经安装好了redis,
常用命令
键(Key)
DEL
DEL key [key …]
移除给定的一个或多个key。
如果key不存在,则忽略该命令。
时间复杂度:
O(N),N为要移除的key的数量。
移除单个字符串类型的key,时间复杂度为O(1)。
移除单个列表、集合、有序集合或哈希表类型的key,时间复杂度为O(M),M为以上数据结构内的元素数量。
返回值:
被移除key的数量。
# 情况1: 删除单个key
redis> SET name huangz # 设置一个值
OK
redis> DEL name # 将其删除
(integer) 1
# 情况2: 删除一个不存在的key
redis> EXISTS phone # 检查一个不存在的key
(integer) 0
redis> DEL phone # 试图删除一个不存在的key,失败
(integer) 0
#情况3: 同时删除多个key
redis> MSET name huangz age 20 blog huangz.iteye.com # 同时设置多个key-value对
OK
redis> DEL name age blog # 同时删除三个key
(integer) 3
KEYS
KEYS pattern
查找符合给定模式的key。
KEYS 命中数据库中所有key。
KEYS h?llo命中hello, hallo and hxllo等。
KEYS hllo命中hllo和heeeeello等。
KEYS h[ae]llo命中hello和hallo,但不命中hillo。
特殊符号用""隔开
时间复杂度
- O(N),N为数据库中key的数量。
返回值:
- 符合给定模式的key列表。
Warning
KEYS的速度非常快,但在一个大的数据库中使用它仍然可能造成性能问题,如果你需要从一个数据集中查找特定的key,你最好还是用集合(Set)。
redis> mset one 1 two 2 three 3 four 4 # 一次设置4个key
OK
redis> keys *o*
1) "four"
2) "two"
3) "one"
redis> keys t??
1) "two"
redis> keys t[w]*
1) "two"
redis> keys * # 匹配数据库内所有key
1) "four"
2) "three"
3) "two"
4) "one"
RANDOMKEY
RANDOMKEY
- 从当前数据库中随机返回(不删除)一个key。
时间复杂度:
- O(1)
返回值:
- 当数据库不为空时,返回一个key。 当数据库为空时,返回nil。
# 情况1:数据库不为空
redis> mset fruit "apple" drink "beer" food "cookies" # 设置多个key
OK
redis> randomkey
"fruit"
redis> randomkey
"food"
redis> keys * # 查看数据库内所有key,证明RANDOMKEY并不删除key
1) "food"
2) "drink"
3) "fruit"
# 情况2:数据库为空
redis> flushdb # 删除当前数据库所有key
OK
redis> randomkey
(nil)
TTL
TTL key
- 返回给定key的剩余生存时间(time to live)(以秒为单位)。
时间复杂度:
- O(1)
返回值:
- key的剩余生存时间(以秒为单位)。 当key不存在或没有设置生存时间时,返回-1 。
# 情况1:带TTL的key
redis> set name "huangz" # 设置一个key
OK
redis> expire name 30 # 设置生存时间为30秒
(integer) 1
redis> get name
"huangz"
redis> ttl name
(integer) 25
redis> ttl name # 30秒过去,name过期
(integer) -1
redis> get name # 过期的key将被删除
(nil)
# 情况2:不带TTL的key
redis> SET site wikipedia.org
OK
redis> TTL wikipedia.org
(integer) -1
# 情况3:不存在的key
redis> EXISTS not_exists_key
(integer) 0
redis> TTL not_exists_key
(integer) -1
EXISTS
EXISTS key
- 检查给定key是否存在。
时间复杂度:
- O(1)
返回值:
- 若key存在,返回1,否则返回0。
redis> set db "redis"
OK
redis> exists db # key存在
(integer) 1
redis> del db # 删除key
(integer) 1
redis> exists db # key不存在
(integer) 0
MOVE
MOVE key db
将当前数据库(默认为0)的key移动到给定的数据库db当中。
如果当前数据库(源数据库)和给定数据库(目标数据库)有相同名字的给定key,或者key不存在于当前数据库,那么MOVE没有任何效果。
因此,也可以利用这一特性,将MOVE当作锁(locking)原语。
时间复杂度:
- O(1)
返回值:
- 移动成功返回1,失败则返回0
# 情况1: key存在于当前数据库
redis> SELECT 0 # redis默认使用数据库0,为了清晰起见,这里再显式指定一次。
OK
redis> SET song "secret base - Zone"
OK
redis> MOVE song 1 # 将song移动到数据库1
(integer) 1
redis> EXISTS song # song已经被移走
(integer) 0
redis> SELECT 1 # 使用数据库1
OK
redis:1> EXISTS song # 证实song被移到了数据库1(注意命令提示符变成了"redis:1",表明正在使用数据库1)
(integer) 1
# 情况2:当key不存在的时候
redis:1> EXISTS fake_key
(integer) 0
redis:1> MOVE fake_key 0 # 试图从数据库1移动一个不存在的key到数据库0,失败
(integer) 0
redis:1> select 0 # 使用数据库0
OK
redis> EXISTS fake_key # 证实fake_key不存在
(integer) 0
# 情况3:当源数据库和目标数据库有相同的key时
redis> SELECT 0 # 使用数据库0
OK
redis> SET favorite_fruit "banana"
OK
redis> SELECT 1 # 使用数据库1
OK
redis:1> SET favorite_fruit "apple"
OK
redis:1> SELECT 0 # 使用数据库0,并试图将favorite_fruit移动到数据库1
OK
redis> MOVE favorite_fruit 1 # 因为两个数据库有相同的key,MOVE失败
(integer) 0
redis> GET favorite_fruit # 数据库0的favorite_fruit没变
"banana"
redis> SELECT 1
OK
redis:1> GET favorite_fruit # 数据库1的favorite_fruit也是
"apple"
RENAME
RENAME key newkey
将key改名为newkey。
当key和newkey相同或者key不存在时,返回一个错误。
当newkey已经存在时,RENAME命令将覆盖旧值。
时间复杂度:
- O(1)
返回值:
- 改名成功时提示OK,失败时候返回一个错误。
# 情况1:key存在且newkey不存在
redis> SET message "hello world"
OK
redis> RENAME message greeting
OK
redis> EXISTS message # message不复存在
(integer) 0
redis> EXISTS greeting # greeting取而代之
(integer) 1
# 情况2:当key不存在时,返回错误
redis> RENAME fake_key never_exists
(error) ERR no such key
# 情况3:newkey已存在时,RENAME会覆盖旧newkey
redis> SET pc "lenovo"
OK
redis> SET personal_computer "dell"
OK
redis> RENAME pc personal_computer
OK
redis> GET pc
(nil)
redis:1> GET personal_computer # dell“没有”了
"lenovo"
TYPE
TYPE key
- 返回key所储存的值的类型。
时间复杂度:
- O(1)
返回值:
- none(key不存在)
- string(字符串)
- list(列表)
- set(集合)
- zset(有序集)
- hash(哈希表)
redis> SET weather "sunny" # 构建一个字符串
OK
redis> TYPE weather
string
redis> LPUSH book_list "programming in scala" # 构建一个列表
(integer) 1
redis> TYPE book_list
list
redis> SADD pat "dog" # 构建一个集合
(integer) 1
redis> TYPE pat
set
EXPIRE
EXPIRE key seconds
为给定key设置生存时间。
当key过期时,它会被自动删除。
在Redis中,带有生存时间的key被称作“易失的”(volatile)。
在低于2.1.3版本的Redis中,已存在的生存时间不可覆盖。
从2.1.3版本开始,key的生存时间可以被更新,也可以被PERSIST命令移除。(详情参见
http://redis.io/topics/expire)。
时间复杂度:
- O(1)
返回值:
- 设置成功返回1。 当key不存在或者不能为key设置生存时间时(比如在低于2.1.3中你尝试更新key的生存时间),返回0。
redis> SET cache_page "www.twitter.com/huangz1990"
OK
redis> EXPIRE cache_page 30 # 设置30秒后过期
(integer) 1
redis> TTL cache_page # 查看给定key的剩余生存时间
(integer) 24
redis> EXPIRE cache_page 30000 # 更新生存时间,30000秒
(integer) 1
redis> TTL cache_page
(integer) 29996
OBJECT
OBJECT subcommand [arguments [arguments]]
OBJECT命令允许从内部察看给定key的Redis对象。
它通常用在除错(debugging)或者了解为了节省空间而对key使用特殊编码的情况。
当将Redis用作缓存程序时,你也可以通过OBJECT命令中的信息,决定key的驱逐策略(eviction policies)。
OBJECT命令有多个子命令:OBJECT REFCOUNT 返回给定key引用所储存的值的次数。此命令主要用于除错。 OBJECT ENCODING
返回给定key锁储存的值所使用的内部表示(representation)。 OBJECT IDLETIME
返回给定key自储存以来的空转时间(idle, 没有被读取也没有被写入),以秒为单位。 对象可以以多种方式编码:
字符串可以被编码为raw(一般字符串)或int(用字符串表示64位数字是为了节约空间)。
列表可以被编码为ziplist或linkedlist。ziplist是为节约大小较小的列表空间而作的特殊表示。
集合可以被编码为intset或者hashtable。intset是只储存数字的小集合的特殊表示。
哈希表可以编码为zipmap或者hashtable。zipmap是小哈希表的特殊表示。
有序集合可以被编码为ziplist或者skiplist格式。ziplist用于表示小的有序集合,而skiplist则用于表示任何大小的有序集合。
假如你做了什么让Redis没办法再使用节省空间的编码时(比如将一个只有1个元素的集合扩展为一个有100万个元素的集合),特殊编码类型(specially
encoded types)会自动转换成通用类型(general type)。
时间复杂度:
- O(1)
返回值:
- REFCOUNT和IDLETIME返回数字。 ENCODING返回相应的编码类型。
redis> SET game "COD" # 设置一个字符串
OK
redis> OBJECT REFCOUNT game # 只有一个引用
(integer) 1
redis> OBJECT IDLETIME game # 等待一阵。。。然后查看空转时间
(integer) 90
redis> GET game # 提取game, 让它处于活跃(active)状态
"COD"
redis> OBJECT IDLETIME game # 不再处于空转
(integer) 0
redis> OBJECT ENCODING game # 字符串的编码方式
"raw"
redis> SET phone 15820123123 # 大的数字也被编码为字符串
OK
redis> OBJECT ENCODING phone
"raw"
redis> SET age 20 # 短数字被编码为int
OK
redis> OBJECT ENCODING age
"int"
RENAMENX
RENAMENX key newkey
当且仅当newkey不存在时,将key改为newkey。
出错的情况和RENAME一样(key不存在时报错)。
时间复杂度:
- O(1)
返回值:
- 修改成功时,返回1。 如果newkey已经存在,返回0。
# 情况1:newkey不存在,成功
redis> SET player "MPlyaer"
OK
redis> EXISTS best_player
(integer) 0
redis> RENAMENX player best_player
(integer) 1
# 情况2:newkey存在时,失败
redis> SET animal "bear"
OK
redis> SET favorite_animal "butterfly"
OK
redis> RENAMENX animal favorite_animal
(integer) 0
redis> get animal
"bear"
redis> get favorite_animal
"butterfly"
EXPIREAT
EXPIREAT key timestamp
EXPIREAT的作用和EXPIRE一样,都用于为key设置生存时间。
不同在于EXPIREAT命令接受的时间参数是UNIX时间戳(unix timestamp)。
时间复杂度:
- O(1)
返回值:
- 如果生存时间设置成功,返回1。 当key不存在或没办法设置生存时间,返回0。
redis> SET cache www.google.com
OK
redis> EXPIREAT cache 1355292000 # 这个key将在2012.12.12过期
(integer) 1
redis> TTL cache
(integer) 45081860
PERSIST
PERSIST key
- 移除给定key的生存时间。
时间复杂度:
- O(1)
返回值:
- 当生存时间移除成功时,返回1. 如果key不存在或key没有设置生存时间,返回0。
redis> SET time_to_say_goodbye "886..."
OK
redis> EXPIRE time_to_say_goodbye 300
(integer) 1
redis> TTL time_to_say_goodbye
(integer) 293
redis> PERSIST time_to_say_goodbye # 移除生存时间
(integer) 1
redis> TTL time_to_say_goodbye # 移除成功
(integer) -1
### SORT
SORT key [BY pattern] [LIMIT offset count] [GET pattern [GET pattern …]] [ASC | DESC] [ALPHA] [STORE destination]
返回或保存给定列表、集合、有序集合key中经过排序的元素。
排序默认以数字作为对象,值被解释为双精度浮点数,然后进行比较。
一般SORT用法
最简单的SORT使用方法是SORT key。
假设today_cost是一个保存数字的列表,SORT命令默认会返回该列表值的递增(从小到大)排序结果。
# 将数据一一加入到列表中
redis> LPUSH today_cost 30
(integer) 1
redis> LPUSH today_cost 1.5
(integer) 2
redis> LPUSH today_cost 10
(integer) 3
redis> LPUSH today_cost 8
(integer) 4
# 排序
redis> SORT today_cost
1) "1.5"
2) "8"
3) "10"
4) "30"
当数据集中保存的是字符串值时,你可以用ALPHA修饰符(modifier)进行排序。
# 将数据一一加入到列表中
redis> LPUSH website "www.reddit.com"
(integer) 1
redis> LPUSH website "www.slashdot.com"
(integer) 2
redis> LPUSH website "www.infoq.com"
(integer) 3
# 默认排序
redis> SORT website
1) "www.infoq.com"
2) "www.slashdot.com"
3) "www.reddit.com"
# 按字符排序
redis> SORT website ALPHA
1) "www.infoq.com"
2) "www.reddit.com"
3) "www.slashdot.com"
如果你正确设置了!LC_COLLATE环境变量的话,Redis能识别UTF-8编码。
排序之后返回的元素数量可以通过LIMIT修饰符进行限制。 LIMIT修饰符接受两个参数:offset和count。
offset指定要跳过的元素数量,count指定跳过offset个指定的元素之后,要返回多少个对象。
以下例子返回排序结果的前5个对象(offset为0表示没有元素被跳过)。
# 将数据一一加入到列表中
redis> LPUSH rank 30
(integer) 1
redis> LPUSH rank 56
(integer) 2
redis> LPUSH rank 42
(integer) 3
redis> LPUSH rank 22
(integer) 4
redis> LPUSH rank 0
(integer) 5
redis> LPUSH rank 11
(integer) 6
redis> LPUSH rank 32
(integer) 7
redis> LPUSH rank 67
(integer) 8
redis> LPUSH rank 50
(integer) 9
redis> LPUSH rank 44
(integer) 10
redis> LPUSH rank 55
(integer) 11
# 排序
redis> SORT rank LIMIT 0 5 # 返回排名前五的元素
1) "0"
2) "11"
3) "22"
4) "30"
5) "32"
修饰符可以组合使用。以下例子返回降序(从大到小)的前5个对象。
redis> SORT rank LIMIT 0 5 DESC
1) "78"
2) "67"
3) "56"
4) "55"
5) "50"
使用外部key进行排序
有时候你会希望使用外部的key作为权重来比较元素,代替默认的对比方法。 假设现在有用户(user)数据如下:
id name level
1 admin 9999
2 huangz 10
59230 jack 3
222 hacker 9999
id数据保存在key名为user_id的列表中。 name数据保存在key名为user_name_{id}的列表中
level数据保存在user_level_{id}的key中。
# 先将要使用的数据加入到数据库中
# admin
redis> LPUSH user_id 1
(integer) 1
redis> SET user_name_1 admin
OK
redis> SET user_level_1 9999
OK
# huangz
redis> LPUSH user_id 2
(integer) 2
redis> SET user_name_2 huangz
OK
redis> SET user_level_2 10
OK
# jack
redis> LPUSH user_id 59230
(integer) 3
redis> SET user_name_59230 jack
OK
redis> SET user_level_59230 3
OK
# hacker
redis> LPUSH user_id 222
(integer) 4
redis> SET user_name_222 hacker
OK
redis> SET user_level_222 9999
OK
如果希望按level从大到小排序user_id,可以使用以下命令:
redis> SORT user_id BY user_level_* DESC
1) "222" # hacker
2) "1" # admin
3) "2" # huangz
4) "59230" # jack
但是有时候只是返回相应的id没有什么用,你可能更希望排序后返回id对应的用户名,这样更友好一点,使用GET选项可以做到这一点:
redis> SORT user_id BY user_level_* DESC GET user_name_*
1) "hacker"
2) "admin"
3) "huangz"
4) "jack"
可以多次地、有序地使用GET操作来获取更多外部key。
比如你不但希望获取用户名,还希望连用户的密码也一并列出,可以使用以下命令:
# 先添加一些测试数据
redis> SET user_password_222 "hey,im in"
OK
redis> SET user_password_1 "a_long_long_password"
OK
redis> SET user_password_2 "nobodyknows"
OK
redis> SET user_password_59230 "jack201022"
OK
# 获取name和password
redis> SORT user_id BY user_level_* DESC GET user_name_* GET user_password_*
1) "hacker" # 用户名
2) "hey,im in" # 密码
3) "jack"
4) "jack201022"
5) "huangz"
6) "nobodyknows"
7) "admin"
8) "a_long_long_password"
# 注意GET操作是有序的,GET user_name_* GET user_password_* 和 GET user_password_* GET user_name_*返回的结果位置不同
redis> SORT user_id BY user_level_* DESC GET user_password_* GET user_name_*
1) "hey,im in" # 密码
2) "hacker" # 用户名
3) "jack201022"
4) "jack"
5) "nobodyknows"
6) "huangz"
7) "a_long_long_password"
8) "admin"
GET还有一个特殊的规则——“GET #”,用于获取被排序对象(我们这里的例子是user_id)的当前元素。
比如你希望user_id按level排序,还要列出id、name和password,可以使用以下命令:
redis> SORT user_id BY user_level_* DESC GET # GET user_name_* GET user_password_*
1) "222" # id
2) "hacker" # name
3) "hey,im in" # password
4) "1"
5) "admin"
6) "a_long_long_password"
7) "2"
8) "huangz"
9) "nobodyknows"
10) "59230"
11) "jack"
12) "jack201022"
只获取对象而不排序
BY修饰符可以将一个不存在的key当作权重,让SORT跳过排序操作。
该方法用于你希望获取外部对象而又不希望引起排序开销时使用。
# 确保fake_key不存在
redis> EXISTS fake_key
(integer) 0
# 以fake_key作BY参数,不排序,只GET name 和 GET password
redis> SORT user_id BY fake_key GET # GET user_name_* GET user_password_*
1) "222" # id
2) "hacker" # user_name
3) "hey,im in" # password
4) "59230"
5) "jack"
6) "jack201022"
7) "2"
8) "huangz"
9) "nobodyknows"
10) "1"
11) "admin"
12) "a_long_long_password"
保存排序结果
默认情况下,SORT操作只是简单地返回排序结果,如果你希望保存排序结果,可以给STORE选项指定一个key作为参数,排序结果将以列表的形式被保存到这个key上。(若指定key已存在,则覆盖。)
redis> EXISTS user_info_sorted_by_level # 确保指定key不存在
(integer) 0
redis> SORT user_id BY user_level_* GET # GET user_name_* GET user_password_* STORE user_info_sorted_by_level # 排序
(integer) 12 # 显示有12条结果被保存了
redis> LRANGE user_info_sorted_by_level 0 11 # 查看排序结果
1) "59230"
2) "jack"
3) "jack201022"
4) "2"
5) "huangz"
6) "nobodyknows"
7) "222"
8) "hacker"
9) "hey,im in"
10) "1"
11) "admin"
12) "a_long_long_password"
一个有趣的用法是将SORT结果保存,用EXPIRE为结果集设置生存时间,这样结果集就成了SORT操作的一个缓存。
这样就不必频繁地调用SORT操作了,只有当结果集过期时,才需要再调用一次SORT操作。
有时候为了正确实现这一用法,你可能需要加锁以避免多个客户端同时进行缓存重建(也就是多个客户端,同一时间进行SORT操作,并保存为结果集),具体参见SETNX命令。
在GET和BY中使用哈希表
可以使用哈希表特有的语法,在SORT命令中进行GET和BY操作。
# 假设现在我们的用户表新增了一个serial项来为作为每个用户的序列号
# 序列号以哈希表的形式保存在serial哈希域内。
redis> HMSET serial 1 23131283 2 23810573 222 502342349 59230 2435829758
OK
# 我们希望以比较serial中的大小来作为排序user_id的方式
redis> SORT user_id BY *->serial
1) "222"
2) "59230"
3) "2"
4) "1"
- 符号"->“用于分割哈希表的关键字(key name)和索引域(hash field),格式为"key->field”。
除此之外,哈希表的BY和GET操作和上面介绍的其他数据结构(列表、集合、有序集合)没有什么不同。
时间复杂度:
- O(N+M*log(M)),N为要排序的列表或集合内的元素数量,M为要返回的元素数量。
如果只是使用SORT命令的GET选项获取数据而没有进行排序,时间复杂度O(N)。
返回值:
- 没有使用STORE参数,返回列表形式的排序结果。 使用STORE参数,返回排序结果的元素数量。