在90年代,一个网站的访问量一般都不大,用单个数据库完全可以轻松应付。 在那个时候,更多的都是静态网页,动态交互类型的网站不多。
DAL : Data Access Layer(数据访问层 – Hibernate,MyBatis)
后来,随着访问量的上升,几乎大部分使用 MySQL
架构的网站在数据库上都开始出现了性能问题
B+ Tree
)一个机器的内存放不下时。NoSQL (Not only SQL)不仅仅是SQL。
泛指非关系型的数据库。随着互联网web2.0
网站的兴起,传统的关系数据库在应付web2.0
网站,特别是超大规模和高并发的SNS
类型的web2.0
纯动态网站已经显得力不从心,暴露了很多难以克服的问题,而非关系型的数据库则由于其本身的特点得到了非常迅速的发展。NoSQL
数据库的产生就是为了解决大规模数据集合多重数据种类带来的挑战,尤其是大数据应用难题,包括超大规模数据的存储。
Redis
一秒写8万次,读取11万, NoSQL的缓存记录级,是一种细粒度的缓存,性能会比较高! )它不是放图形的,放的是关系比如:朋友圈社交网络、广告推荐系统 社交网络,推荐系统等。专注于构建关系图谱
如:Neo4J、InfoGrid
Redis
(==Re==mote ==Di==ctionary ==S==erver ),即远程字典服务! 是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。是当下最热门的NoSQL技术之一! 也被人们称之为结构化数据库
!
redis
会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave
(主从)同步。
Redis
能支持超过100K+每秒的读写频率;Redis
支持二进制案例的Strings
,Lists
,Hashes
,Sets
及Ordered Sets
数据类型操作;Redis
的所有操作都是原子性的,同时Redis
还支持对几个操作全并后的原子性执行;Redis
还支持publish/subscribe
, 通知, key
过期等等特性。默认是16个数据库
ray@ubuntu:~$ redis-cli # 打开 redis 客户端
127.0.0.1:6379> SELECT 2 # 切换数据库
OK
127.0.0.1:6379[2]> DBSIZE # 查看当前数据库大小
(integer) 0
127.0.0.1:6379[2]> SELECT 0 # 切换数据库
OK
127.0.0.1:6379> KEYS * # 查看当前数据库所有的KEY
(empty list or set)
127.0.0.1:6379> FLUSHDB # 清空当前数据库
OK
127.0.0.1:6379> FLUSHALL # 清空所有数据库(慎重)
OK
127.0.0.1:6379>
127.0.0.1:6379> set name ray # 设置 key value
OK
127.0.0.1:6379> EXISTS name # 判断 key 是否存在
(integer) 1 # 返回 1 表示存在
127.0.0.1:6379> TYPE name # 查看 key 类型
string
127.0.0.1:6379> EXPIRE name 10 # 设置 key 值的过期时间
(integer) 1
127.0.0.1:6379> TTL name # 获取存活时间
(integer) 6
127.0.0.1:6379> TTL name
(integer) -2
127.0.0.1:6379> KEYS * # 查看当前数据库所有的KEY
(empty list or set)
127.0.0.1:6379>
Redis
是基于内存操作, CPU
不是Redis
性能瓶颈, Redis
的瓶颈是根据机器的内存和网络带宽。
Redis是一个开源(BSD许可),内存存储的数据结构服务器,可用作数据库,高速缓存和消息队列代理。它支持字符串、哈希表、列表、集合、有序集合,位图,hyperloglogs等数据类型。内置复制、Lua脚本、LRU收回、事务以及不同级别磁盘持久化功能,同时通过Redis Sentinel提供高可用,通过Redis Cluster提供自动分区。
ray@ubuntu:~$ redis-cli
127.0.0.1:6379> clear
127.0.0.1:6379> set k1 v1 # 设置 key 和 value
OK
127.0.0.1:6379> get k1 # 根据 key 获取值
"v1"
127.0.0.1:6379> append k1 hello # 往 key 追加 value
(integer) 7
127.0.0.1:6379> get k1
"v1hello"
127.0.0.1:6379> append k2 v2 # 如果 key 不存在,相当于 set
(integer) 2
127.0.0.1:6379> get k2
"v2"
127.0.0.1:6379>
tips:日常使用中,最常用的就是string的普通get、set方法
127.0.0.1:6379> set mykey "hello world" # 设置 key 和 value
OK
127.0.0.1:6379> STRLEN mykey # key 存在,返回字符串值的长度
(integer) 11
127.0.0.1:6379> STRLEN mykey1 # key 不存在,返回 0
(integer) 0
127.0.0.1:6379>
tips: 返回字符串的长度
127.0.0.1:6379> set views 0 # 设置 key 和 value
OK
127.0.0.1:6379> get views # 根据 key 获取值
"0"
127.0.0.1:6379> incr views # 增加 1
(integer) 1
127.0.0.1:6379> incr views
(integer) 2
127.0.0.1:6379> get views
"2"
127.0.0.1:6379> decr views # 减去 1
(integer) 1
127.0.0.1:6379> decr views
(integer) 0
127.0.0.1:6379> get views
"0"
127.0.0.1:6379>
tips:可以理解为粉丝加一、减一操作
127.0.0.1:6379> get views # 根据 key 获取值
"0"
127.0.0.1:6379> incrby views 10 # 指定增加步长
(integer) 10
127.0.0.1:6379> get views
"10"
127.0.0.1:6379> decrby views 9 # 指定减去步长
(integer) 1
127.0.0.1:6379> get views
"1"
127.0.0.1:6379>
tips:可以理解为批量添加或取消关注
127.0.0.1:6379> get k1 # 根据 key 获取值
"v1hello"
127.0.0.1:6379> getrange k1 2 6 # 截取字符串
"hello"
127.0.0.1:6379> getrange k1 2 -1 # 截取字符串 -1 相当于从最后算起
"hello"
127.0.0.1:6379>
127.0.0.1:6379> setex k3 30 v3 # 如果键k3不存在,则设置键为k3,值为v3,30秒过期
OK
127.0.0.1:6379> get k3 # 根据 key 获取值
"v3"
127.0.0.1:6379> ttl k3 # 获取存活时间
(integer) 24
127.0.0.1:6379> setex k3 30 redis # 如果键k3存在,则替换值为redis,30秒过期
OK
127.0.0.1:6379> get k3
"redis"
127.0.0.1:6379> ttl k3 # 获取存活时间
(integer) 24
127.0.0.1:6379>
## SETEX 命令的效果和以下两个命令的效果是相同的
127.0.0.1:6379> set k4 v4
OK
127.0.0.1:6379> EXPIRE k4 30
(integer) 1
tips:相当于是java程序中的如果存在则替换,如果不存在则新增。但是必须注意 seconds
参数不合法时, 命令将返回一个错误;seconds
参数以秒为单位进行设置。
127.0.0.1:6379> PSETEX mykey 10000 hello # 如果键mykey不存在,则设置键为mykey,值为hello,10秒过期
OK
127.0.0.1:6379> ttl mykey # 获取存活时间
(integer) 8
127.0.0.1:6379> get mykey # 根据 key 获取值
"hello"
127.0.0.1:6379> get mykey
(nil)
127.0.0.1:6379>
tips: 这个命令和 SETEX
命令相似, 但它以毫秒为单位设置 key
的生存时间, 而不是像 SETEX
命令那样以秒为单位进行设置。
127.0.0.1:6379> set k1 redis # 设置 key 和 value
OK
127.0.0.1:6379> get k1 # 根据 key 获取值
"redis"
127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3 # 设置多个key,会覆盖已经存在的key
OK
127.0.0.1:6379> keys *
1) "k3"
2) "k2"
3) "k1"
127.0.0.1:6379> mget k1 k2 k3 # 根据多个 key 获取值
1) "v1"
2) "v2"
3) "v3"
127.0.0.1:6379>
tips: 如果某个给定键已经存在, 那么 MSET
将使用新值去覆盖旧值, 如果这不是你所希望的效果, 请考虑使用 MSETNX
命令, 这个命令只会在所有给定键都不存在的情况下进行设置。
127.0.0.1:6379> set k1 redis # 设置 key 和 value
OK
127.0.0.1:6379> get k1 # 根据 key 获取值
"redis"
127.0.0.1:6379> MSETNX k1 v1 k2 v2 # 设置多个key,不会覆盖已经存在的key,是一个原子操作,要么一起成功,要么一起失败
(integer) 0 # 设置失败
127.0.0.1:6379> keys *
1) "k1"
127.0.0.1:6379> get k1
"redis"
127.0.0.1:6379> MSETNX k2 v2 k3 v3
(integer) 1 # 设置成功
127.0.0.1:6379> keys *
1) "k3"
2) "k2"
3) "k1"
127.0.0.1:6379>
tips:MSETNX
是一个原子性(atomic)操作, 所有给定键要么就全部都被设置, 要么就全部都不设置, 不可能出现第三种状态。
127.0.0.1:6379> GETSET db redis # 如果 key 不存在,返回 null
(nil)
127.0.0.1:6379> set db redis # 设置 key 和 value
OK
127.0.0.1:6379> get db # 根据 key 获取值
"redis"
127.0.0.1:6379> getset db mongodb # 如果 key 存在,获取旧值,并设置新值
"redis"
127.0.0.1:6379> get db
"mongodb"
127.0.0.1:6379>
tips: 将键 key
的值设为 value
, 并返回键 key
在被设置之前的旧值。
# 对象的方式使用
127.0.0.1:6379> mset user:1:name admin user:1:age 18 # 设置多个key: user:id:name 和 user:id:age
OK
127.0.0.1:6379> mget user:1:name user:1:age # 根据多个key获取值
1) "admin"
2) "18"
127.0.0.1:6379>
tips: 日常使用中,我们可以将key值设置为对象的方式使用
命令 | 描述 |
---|---|
Redis Setnx 命令 | 只有在 key 不存在时设置 key 的值。 |
Redis Getrange 命令 | 返回 key 中字符串值的子字符 |
Redis Mset 命令 | 同时设置一个或多个 key-value 对。 |
Redis Setex 命令 | 将值 value 关联到 key ,并将 key 的过期时间设为 seconds (以秒为单位)。 |
Redis SET 命令 | 设置指定 key 的值 |
Redis Get 命令 | 获取指定 key 的值。 |
Redis Getbit 命令 | 对 key 所储存的字符串值,获取指定偏移量上的位(bit)。 |
Redis Setbit 命令 | 对 key 所储存的字符串值,设置或清除指定偏移量上的位(bit)。 |
Redis Decr 命令 | 将 key 中储存的数字值减一。 |
Redis Decrby 命令 | key 所储存的值减去给定的减量值(decrement) 。 |
Redis Strlen 命令 | 返回 key 所储存的字符串值的长度。 |
Redis Msetnx 命令 | 同时设置一个或多个 key-value 对,当且仅当所有给定 key 都不存在。 |
Redis Incrby 命令 | 将 key 所储存的值加上给定的增量值(increment) 。 |
Redis Incrbyfloat 命令 | 将 key 所储存的值加上给定的浮点增量值(increment) 。 |
Redis Setrange 命令 | 用 value 参数覆写给定 key 所储存的字符串值,从偏移量 offset 开始。 |
Redis Psetex 命令 | 这个命令和 SETEX 命令相似,但它以毫秒为单位设置 key 的生存时间,而不是像 SETEX 命令那样,以秒为单位。 |
Redis Append 命令 | 如果 key 已经存在并且是一个字符串, APPEND 命令将 value 追加到 key 原来的值的末尾。 |
Redis Getset 命令 | 将给定 key 的值设为 value ,并返回 key 的旧值(old value)。 |
Redis Mget 命令 | 获取所有(一个或多个)给定 key 的值。 |
Redis Incr 命令 | 将 key 中储存的数字值增一。 |
127.0.0.1:6379> LPUSH list one # 在队列左侧插入
(integer) 1
127.0.0.1:6379> LPUSH list two
(integer) 2
127.0.0.1:6379> LPUSH list three
(integer) 3
127.0.0.1:6379> LRANGE list 0 -1 # 获取list区间的值,-1代表最后一位
1) "three"
2) "two"
3) "one"
127.0.0.1:6379> LRANGE list 0 1 # 获取list区间的值
1) "three"
2) "two"
127.0.0.1:6379>
127.0.0.1:6379>
127.0.0.1:6379> RPUSH list zero # 在队列右侧插入
(integer) 4
127.0.0.1:6379> LRANGE list 0 -1
1) "three"
2) "two"
3) "one"
4) "zero"
127.0.0.1:6379> LINDEX list 1 # 通过下标获取值
"two"
127.0.0.1:6379>
tips: 在队列左侧插入,内存角度可以理解为栈数据结构(FILO - 先进后出),生活角度可以理解为最先放入厢式货车的货物,最后才能取出。
# 对空列表执行 LPUSHX
127.0.0.1:6379> LLEN greet # greet 是一个空列表
(integer) 0
127.0.0.1:6379> LPUSHX greet "hello" # 尝试 LPUSHX,失败,因为列表为空
(integer) 0
# 对非空列表执行 LPUSHX
127.0.0.1:6379> LPUSH greet "hello" # 先用 LPUSH 创建一个有一个元素的列表
(integer) 1
127.0.0.1:6379> LPUSHX greet "good morning" # 这次 LPUSHX 执行成功
(integer) 2
127.0.0.1:6379> LRANGE greet 0 -1 # 获取list区间的值,-1代表最后一位
1) "good morning"
2) "hello"
127.0.0.1:6379>
tips: 当 key
不存在时, LPUSHX 命令什么也不做。
127.0.0.1:6379> LLEN course # course 是一个空列表
(integer) 0
127.0.0.1:6379> RPUSH course redis # 在队列右侧插入
(integer) 1
127.0.0.1:6379> RPUSH course mongodb
(integer) 2
127.0.0.1:6379> LRANGE course 0 -1 # 获取list区间的值,-1代表最后一位
1) "redis"
2) "mongodb"
127.0.0.1:6379> LPOP course # 移除头元素
"redis"
127.0.0.1:6379> LPOP course
"mongodb"
127.0.0.1:6379>
127.0.0.1:6379> lpush list v1 # 在队列左侧插入
(integer) 1
127.0.0.1:6379> lpush list v2
(integer) 2
127.0.0.1:6379> lpush list v3
(integer) 3
127.0.0.1:6379> lpush list v3
(integer) 4
127.0.0.1:6379> LRANGE list 0 -1 # 获取list区间的值,-1代表最后一位
1) "v3"
2) "v3"
3) "v2"
4) "v1"
127.0.0.1:6379> LLEN list # 返回 list 长度
(integer) 4
127.0.0.1:6379> LREM list 1 v1 # 移除list列表中1个v1
(integer) 1
127.0.0.1:6379> LREM list 2 v3 # 移除list列表中2个v3
(integer) 2
127.0.0.1:6379> lrange list 0 -1
1) "v2"
127.0.0.1:6379>
tips: count
的值可以有三种情况。
127.0.0.1:6379> RPUSH list v1 # 在队列右侧插入
(integer) 1
127.0.0.1:6379> RPUSH list v2
(integer) 2
127.0.0.1:6379> RPUSH list v3
(integer) 3
127.0.0.1:6379> RPUSH list v4
(integer) 4
127.0.0.1:6379> LRANGE list 0 -1 # 获取list区间的值,-1代表最后一位
1) "v1"
2) "v2"
3) "v3"
4) "v4"
127.0.0.1:6379> LTRIM list 1 2 # 修剪,表示只保留列表 list 的第1个到第2个元素
OK
127.0.0.1:6379> LRANGE list 0 -1
1) "v2"
2) "v3"
127.0.0.1:6379>
tips: 举个例子,执行命令 LTRIM list 0 2
,表示只保留列表 list
的前三个元素,其余元素全部删除。
127.0.0.1:6379> LPUSH list v1 # 在队列左侧插入
(integer) 1
127.0.0.1:6379> LPUSH list v2
(integer) 2
127.0.0.1:6379> LPUSH list v3
(integer) 3
127.0.0.1:6379> LPUSH list v4
(integer) 4
127.0.0.1:6379> LRANGE list 0 -1 # 获取list区间的值,-1代表最后一位
1) "v4"
2) "v3"
3) "v2"
4) "v1"
127.0.0.1:6379> RPOPLPUSH list mylist # 移除列表的最后一个元素,添加到新的列表中
"v1"
127.0.0.1:6379> LRANGE list 0 -1
1) "v4"
2) "v3"
3) "v2"
127.0.0.1:6379> LRANGE mylist 0 -1
1) "v1"
127.0.0.1:6379>
## 旋转(rotation)操作
127.0.0.1:6379> LRANGE list 0 -1
1) "v4"
2) "v3"
3) "v2"
4) "v1" # 尾部
127.0.0.1:6379> RPOPLPUSH list list # 将列表中的尾部元素移动到头部
"v1"
127.0.0.1:6379> LRANGE list 0 -1
1) "v1" # 头部
2) "v4"
3) "v3"
4) "v2"
127.0.0.1:6379>
tips: 如果 source
旧列表 和 destination
新列表相同,则列表中的表尾元素被移动到表头,并返回该元素,可以把这种特殊情况视作列表的旋转(rotation)操作。
127.0.0.1:6379> EXISTS list # 判断 list 是否存在
(integer) 0 # 不存在
127.0.0.1:6379> RPUSH list v1 # 在队列右侧插入
(integer) 1
127.0.0.1:6379> LSET list 0 v0 # 设置索引位置0的值为v0
OK
127.0.0.1:6379> LRANGE list 0 -1 # 获取list区间的值,-1代表最后一位
1) "v0"
127.0.0.1:6379> LSET list 1 v2 # 设置不存在的值,报错err
(error) ERR index out of range
127.0.0.1:6379>
tips: 当 index
参数超出范围,或对一个空列表( key
不存在)进行 LSET 时,返回一个错误。
127.0.0.1:6379> LRANGE list 0 -1 # 获取list区间的值,-1代表最后一位
1) "v1"
2) "v4"
3) "v3"
4) "v2"
127.0.0.1:6379> LINSERT list before v2 oo # 将值oo插入到列表list中值为v2的前面
(integer) 5
127.0.0.1:6379> LRANGE list 0 -1
1) "v1"
2) "v4"
3) "v3"
4) "oo"
5) "v2"
127.0.0.1:6379> LINSERT list after v4 kk # 将值kk插入到列表list中值为v4的后面
(integer) 6
127.0.0.1:6379> LRANGE list 0 -1
1) "v1"
2) "v4"
3) "kk"
4) "v3"
5) "oo"
6) "v2"
127.0.0.1:6379>
tips: 将值 value
插入到列表 key
当中,位于值 pivot
之前或之后。
命令 | 描述 |
---|---|
Redis Lindex 命令 | 通过索引获取列表中的元素 |
Redis Rpush 命令 | 在列表中添加一个或多个值 |
Redis Lrange 命令 | 获取列表指定范围内的元素 |
Redis Rpoplpush 命令 | 移除列表的最后一个元素,并将该元素添加到另一个列表并返回 |
Redis Blpop 命令 | 移出并获取列表的第一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。 |
Redis Brpop 命令 | 移出并获取列表的最后一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。 |
Redis Brpoplpush 命令 | 从列表中弹出一个值,将弹出的元素插入到另外一个列表中并返回它; 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。 |
Redis Lrem 命令 | 移除列表元素 |
Redis Llen 命令 | 获取列表长度 |
Redis Ltrim 命令 | 对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除。 |
Redis Lpop 命令 | 移出并获取列表的第一个元素 |
Redis Lpushx 命令 | 将一个或多个值插入到已存在的列表头部 |
Redis Linsert 命令 | 在列表的元素前或者后插入元素 |
Redis Rpop 命令 | 移除并获取列表最后一个元素 |
Redis Lset 命令 | 通过索引设置列表元素的值 |
Redis Lpush 命令 | 将一个或多个值插入到列表头部 |
Redis Rpushx 命令 | 为已存在的列表添加值 |
127.0.0.1:6379> SADD list v1 # 添加单个元素
(integer) 1
127.0.0.1:6379> SADD list v2
(integer) 1
127.0.0.1:6379> SADD list v3
(integer) 1
127.0.0.1:6379> SADD list v4
(integer) 1
127.0.0.1:6379> SADD list v4 # 添加重复元素
(integer) 0
127.0.0.1:6379> SMEMBERS list # 返回集合 list 中的所有成员
1) "v3"
2) "v2"
3) "v4"
4) "v1"
127.0.0.1:6379> SISMEMBER list v2 # 判断集合 list 是否包含 v2 元素
(integer) 1
127.0.0.1:6379> SCARD list
(integer) 4
127.0.0.1:6379> SREM list v2
(integer) 1
127.0.0.1:6379> SMEMBERS list
1) "v3"
2) "v1"
3) "v4"
127.0.0.1:6379>
127.0.0.1:6379> SRANDMEMBER list # 返回集合中的一个随机元素
"v3"
127.0.0.1:6379> SRANDMEMBER list 2 # 返回集合中的n个随机元素
1) "v3"
2) "v4"
127.0.0.1:6379>
tips: 如果 count
为正数,且小于集合基数,那么命令返回一个包含 count
个元素的数组,数组中的元素各不相同。如果 count
大于等于集合基数,那么返回整个集合。如果 count
为负数,那么命令返回一个数组,数组中的元素可能会重复出现多次,而数组的长度为 count
的绝对值。
127.0.0.1:6379> SPOP list # 随机移除一个元素
"v1"
127.0.0.1:6379> spop list 2 # 随机移除n个元素
1) "v3"
2) "v4"
127.0.0.1:6379> SMEMBERS list
(empty list or set)
127.0.0.1:6379> SADD list v1 v2 v3 v4 # 一次添加多个值
(integer) 4
127.0.0.1:6379> SMEMBERS list
1) "v3"
2) "v2"
3) "v4"
4) "v1"
127.0.0.1:6379> SMEMBERS list2
(empty list or set)
127.0.0.1:6379> SMOVE list list2 v4 # 将 v4 元素从 list 集合移动到 list2 集合。
(integer) 1
127.0.0.1:6379> SMEMBERS list
1) "v3"
2) "v2"
3) "v1"
127.0.0.1:6379> SMEMBERS list2
1) "v4"
127.0.0.1:6379>
tips: 如果 member
元素被成功移除,返回 1
。如果 member
元素不是 source
集合的成员,并且没有任何操作对 destination
集合执行,那么返回 0
。
127.0.0.1:6379> sadd group1 v1 v2 v3
(integer) 3
127.0.0.1:6379> SMEMBERS group1
1) "v3"
2) "v2"
3) "v1"
127.0.0.1:6379> sadd group2 v3 v4 v5
(integer) 3
127.0.0.1:6379> SMEMBERS group2
1) "v3"
2) "v5"
3) "v4"
127.0.0.1:6379> SINTER group1 group2 # 返回 group1 和 group2 集合中的交集
1) "v3"
127.0.0.1:6379>
127.0.0.1:6379> SADD song1 v1
(integer) 1
127.0.0.1:6379> SADD song2 v2
(integer) 1
127.0.0.1:6379> SUNION song1 song2 # 返回 song1 和 song2 集合中的并集
1) "v2"
2) "v1"
127.0.0.1:6379>
127.0.0.1:6379> sadd k1 v1 v2
(integer) 2
127.0.0.1:6379> SMEMBERS k1
1) "v2"
2) "v1"
127.0.0.1:6379> sadd k2 v2 v3
(integer) 2
127.0.0.1:6379> SMEMBERS k2
1) "v3"
2) "v2"
127.0.0.1:6379> SDIFF k1 k2 # 返回 k1 和 k2 集合中的差集,以k1为准
1) "v1"
127.0.0.1:6379> SDIFF k2 k1 # 返回 k2 和 k1 集合中的差集,以k2为准
1) "v3"
127.0.0.1:6379>
命令 | 描述 |
---|---|
Redis Sunion 命令 | 返回所有给定集合的并集 |
Redis Scard 命令 | 获取集合的成员数 |
Redis Srandmember 命令 | 返回集合中一个或多个随机数 |
Redis Smembers 命令 | 返回集合中的所有成员 |
Redis Sinter 命令 | 返回给定所有集合的交集 |
Redis Srem 命令 | 移除集合中一个或多个成员 |
Redis Smove 命令 | 将 member 元素从 source 集合移动到 destination 集合 |
Redis Sadd 命令 | 向集合添加一个或多个成员 |
Redis Sismember 命令 | 判断 member 元素是否是集合 key 的成员 |
Redis Sdiffstore 命令 | 返回给定所有集合的差集并存储在 destination 中 |
Redis Sdiff 命令 | 返回给定所有集合的差集 |
Redis Sscan 命令 | 迭代集合中的元素 |
Redis Sinterstore 命令 | 返回给定所有集合的交集并存储在 destination 中 |
Redis Sunionstore 命令 | 所有给定集合的并集存储在 destination 集合中 |
Redis Spop 命令 | 移除并返回集合中的一个随机元素 |
127.0.0.1:6379> hset myhash field1 ray # 将哈希表 hash 中域 field 的值设置为 value
(integer) 1 # 设置成功,返回1
127.0.0.1:6379> hget myhash field1 # 返回哈希表中给定域的值
"ray"
127.0.0.1:6379> hset myhash field1 new
(integer) 0 # 覆盖旧值,返回0
127.0.0.1:6379> hget myhash field1
"new"
127.0.0.1:6379> hmset myhash field1 hello field2 world # 同时将多个 field-value (域-值)对设置到哈希表 key 中
OK
127.0.0.1:6379> hmget myhash field1 field2 # 返回哈希表 key 中,一个或多个给定域的值
1) "hello"
2) "world"
127.0.0.1:6379> HGETALL myhash # 返回哈希表 key 中,所有的域和值
1) "field1"
2) "hello"
3) "field2"
4) "world"
127.0.0.1:6379>
127.0.0.1:6379> hdel myhash field1 # 删除哈希表 key 中的一个或多个指定域,不存在的域将被忽略
(integer) 1
127.0.0.1:6379> HGETALL myhash
1) "field2"
2) "world"
127.0.0.1:6379> HLEN myhash # 返回哈希表 key 中域的数量
(integer) 1
127.0.0.1:6379> HSTRLEN myhash field2 # 返回哈希表 key 中域的字符串长度
(integer) 5
127.0.0.1:6379> HEXISTS myhash field1 # 检查给定域 field 是否存在于哈希表 hash 当中。
(integer) 0 # 不存在
127.0.0.1:6379> HEXISTS myhash field2
(integer) 1 # 存在
127.0.0.1:6379>
127.0.0.1:6379> HKEYS myhash # 返回哈希表 key 中的所有域
1) "field2"
127.0.0.1:6379> HVALS myhash # 返回哈希表 key 中所有域的值
1) "world"
127.0.0.1:6379>
127.0.0.1:6379> hset myhash field3 5
(integer) 1
127.0.0.1:6379> HINCRBY myhash field3 1 # 为哈希表 key 中的域加上增量
(integer) 6
127.0.0.1:6379> HINCRBY myhash field3 -1 # 为哈希表 key 中的域减去增量
(integer) 5
127.0.0.1:6379> hsetnx myhash field4 hello # 如果域不存在,设置成功
(integer) 1
127.0.0.1:6379> hsetnx myhash field4 hello2 # 如果域存在,设置失败,不会改变域原来的值
(integer) 0
# 对象的方式使用
127.0.0.1:6379> hset user:1 name admin # 将哈希表 hash 中域 field 的值设置为 value
(integer) 1
127.0.0.1:6379> hset user:1 age 18
(integer) 1
127.0.0.1:6379> HGETALL user:1 # 返回哈希表 key 中,所有的域和值
1) "name"
2) "admin"
3) "age"
4) "18"
127.0.0.1:6379>
tips: Hash比String更适合于对象的存储,String适合于字符串存储。
命令 | 描述 |
---|---|
Redis Hmset 命令 | 同时将多个 field-value (域-值)对设置到哈希表 key 中。 |
Redis Hmget 命令 | 获取所有给定字段的值 |
Redis Hset 命令 | 将哈希表 key 中的字段 field 的值设为 value 。 |
Redis Hgetall 命令 | 获取在哈希表中指定 key 的所有字段和值 |
Redis Hget 命令 | 获取存储在哈希表中指定字段的值/td> |
Redis Hexists 命令 | 查看哈希表 key 中,指定的字段是否存在。 |
Redis Hincrby 命令 | 为哈希表 key 中的指定字段的整数值加上增量 increment 。 |
Redis Hlen 命令 | 获取哈希表中字段的数量 |
Redis Hdel 命令 | 删除一个或多个哈希表字段 |
Redis Hvals 命令 | 获取哈希表中所有值 |
Redis Hincrbyfloat 命令 | 为哈希表 key 中的指定字段的浮点数值加上增量 increment 。 |
Redis Hkeys 命令 | 获取所有哈希表中的字段 |
Redis Hsetnx 命令 | 只有在字段 field 不存在时,设置哈希表字段的值。 |
127.0.0.1:6379> zadd myset 1 one # 添加一个值
(integer) 1
127.0.0.1:6379> zadd myset 2 two 3 three # 添加多个值
(integer) 2
127.0.0.1:6379> ZRANGE myset 0 -1 # 返回有序集 key 中,指定区间内的成员
1) "one"
2) "two"
3) "three"
127.0.0.1:6379> ZSCORE salary xiaohong # 返回 score 值
"2500" # 注意返回值是字符串
127.0.0.1:6379> zscore salary xiaohong
"2500"
127.0.0.1:6379> ZINCRBY salary 2000 xiaohong # 为 score 值加上增量
"4500"
127.0.0.1:6379>
tips: 相当于日常生活中的加薪啦!
127.0.0.1:6379> ZRANGE salary 0 -1
1) "xiaohong"
2) "zhangsan"
127.0.0.1:6379> zcard salary # 返回有序集 key 的基数
(integer) 2
127.0.0.1:6379> zcount salary 3000 5000 # 计算薪水在 3000-5000 之间的人数
(integer) 2
127.0.0.1:6379> zcount salary 5000 7000
(integer) 1
127.0.0.1:6379> zrange salary 0 -1 withscores # 显示所有成员及其 score 值
1) "xiaohong"
2) "4500"
3) "zhangsan"
4) "5000"
127.0.0.1:6379> ZREVRANK salary xiaohong # xiaohong 的工资排第二
(integer) 1
127.0.0.1:6379> zadd salary 2500 xiaohong # 添加三个用户和对应的工资
(integer) 1
127.0.0.1:6379> zadd salary 5000 zhangsan
(integer) 1
127.0.0.1:6379> zadd salary 500 ray
(integer) 1
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf # 显示全部用户,按工资从小到大排序
1) "ray"
2) "xiaohong"
3) "zhangsan"
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf withscores # 显示全部用户和工资,按工资从小到大排序
1) "ray"
2) "500"
3) "xiaohong"
4) "2500"
5) "zhangsan"
6) "5000"
127.0.0.1:6379> ZRANGEBYSCORE salary -inf 2500 withscores # 显示工资小于等于2500的用户,按工资从小到大排序
1) "ray"
2) "500"
3) "xiaohong"
4) "2500"
127.0.0.1:6379> ZREVRANGEBYSCORE salary +inf -inf # 显示全部用户,按工资从大到小排序
1) "zhangsan"
2) "xiaohong"
3) "ray"
127.0.0.1:6379> ZREVRANGEBYSCORE salary +inf -inf withscores # 显示全部用户和工资,按工资从大到小排序
1) "zhangsan"
2) "5000"
3) "xiaohong"
4) "2500"
5) "ray"
6) "500"
127.0.0.1:6379>
127.0.0.1:6379> zrange salary 0 -1
1) "ray"
2) "xiaohong"
3) "zhangsan"
127.0.0.1:6379> ZREM salary ray # 移除有序集 key 中的一个或多个成员,不存在的成员将被忽略。
(integer) 1
127.0.0.1:6379> zrange salary 0 -1
1) "xiaohong"
2) "zhangsan"
127.0.0.1:6379>
排行榜:新书榜、畅销榜、关注榜等
命令 | 描述 |
---|---|
Redis Zrevrank 命令 | 返回有序集合中指定成员的排名,有序集成员按分数值递减(从大到小)排序 |
Redis Zlexcount 命令 | 在有序集合中计算指定字典区间内成员数量 |
Redis Zunionstore 命令 | 计算给定的一个或多个有序集的并集,并存储在新的 key 中 |
Redis Zremrangebyrank 命令 | 移除有序集合中给定的排名区间的所有成员 |
Redis Zcard 命令 | 获取有序集合的成员数 |
Redis Zrem 命令 | 移除有序集合中的一个或多个成员 |
Redis Zinterstore 命令 | 计算给定的一个或多个有序集的交集并将结果集存储在新的有序集合 key 中 |
Redis Zrank 命令 | 返回有序集合中指定成员的索引 |
Redis Zincrby 命令 | 有序集合中对指定成员的分数加上增量 increment |
Redis Zrangebyscore 命令 | 通过分数返回有序集合指定区间内的成员 |
Redis Zrangebylex 命令 | 通过字典区间返回有序集合的成员 |
Redis Zscore 命令 | 返回有序集中,成员的分数值 |
Redis Zremrangebyscore 命令 | 移除有序集合中给定的分数区间的所有成员 |
Redis Zscan 命令 | 迭代有序集合中的元素(包括元素成员和元素分值) |
Redis Zrevrangebyscore 命令 | 返回有序集中指定分数区间内的成员,分数从高到低排序 |
Redis Zremrangebylex 命令 | 移除有序集合中给定的字典区间的所有成员 |
Redis Zrevrange 命令 | 返回有序集中指定区间内的成员,通过索引,分数从高到底 |
Redis Zrange 命令 | 通过索引区间返回有序集合成指定区间内的成员 |
Redis Zcount 命令 | 计算在有序集合中指定区间分数的成员数 |
Redis Zadd 命令 | 向有序集合添加一个或多个成员,或者更新已存在成员的分数 |
朋友定位、附近的人、打车距离计算
# geoadd 添加地理位置
# 一般我们会下载城市数据,直接通过java程序一次性导入
# 有效的经度介于 -180 度至 180 度之间
# 有效的纬度介于 -85.05112878 度至 85.05112878 度之间
# 当用户尝试输入一个超出范围的经度或者纬度时, GEOADD 命令将返回一个错误
127.0.0.1:6379> GEOADD china:city 116.434164 39.905637 beijing # 北京
(integer) 1
127.0.0.1:6379> GEOADD china:city 117.262043 39.02148 tianjin # 天津
(integer) 1
127.0.0.1:6379> GEOADD china:city 114.520846 38.05333 shijiazhuang # 石家庄
(integer) 1
127.0.0.1:6379> GEOADD china:city 113.288227 23.16694 guangzhou # 广州
(integer) 1
127.0.0.1:6379> GEOADD china:city 112.957075 28.248229 changsha # 长沙
(integer) 1
127.0.0.1:6379>
tips: GEOADD
命令以标准的 x,y
格式接受参数, 所以用户必须先输入经度, 然后再输入纬度。
127.0.0.1:6379> GEOPOS china:city guangzhou # 获取指定城市的位置(经度和纬度)。
1) 1) "113.28822880983352661"
2) "23.16693950416961911"
127.0.0.1:6379> GEOPOS china:city beijing tianjin
1) 1) "116.43416494131088257"
2) "39.90563731152928995"
2) 1) "117.26204484701156616"
2) "39.02148094617099616"
127.0.0.1:6379>
tips: 获取指定城市的定位,一定是一个坐标。
指定单位的参数 unit
必须是以下单位的其中一个:
m
表示单位为米。km
表示单位为千米。mi
表示单位为英里。ft
表示单位为英尺。127.0.0.1:6379> GEODIST china:city beijing guangzhou km # 返回北京到广州的直线距离
"1885.1579"
(0.67s)
127.0.0.1:6379> GEODIST china:city guangzhou changsha km # 返回广州到长沙的直线距离
"566.1455"
tips: 获取两座城市的直线距离!
范围可以使用以下其中一个单位:
m
表示单位为米。km
表示单位为千米。mi
表示单位为英里。ft
表示单位为英尺。# 所有的城市数据应该都录入:china:city ,才会让数据更准确
127.0.0.1:6379> GEORADIUS china:city 112.50 25.50 1000 km # 以 112.50,25.50 这个经纬度为中心,寻找方圆1000km内的城市
1) "guangzhou"
2) "changsha"
127.0.0.1:6379> GEORADIUS china:city 112.50 25.50 500 km
1) "guangzhou"
2) "changsha"
127.0.0.1:6379> GEORADIUS china:city 112.50 25.50 300 km
1) "guangzhou"
127.0.0.1:6379> GEORADIUS china:city 112.50 25.50 1000 km withdist # 显示到中心距离的直线位置,相当于离你多远
1) 1) "guangzhou"
2) "271.5123"
2) 1) "changsha"
2) "309.0197"
127.0.0.1:6379> GEORADIUS china:city 112.50 25.50 1000 km withcoord # 显示城市的经纬度,相当于他人的地理位置
1) 1) "guangzhou"
2) 1) "113.28822880983352661"
2) "23.16693950416961911"
2) 1) "changsha"
2) 1) "112.95707255601882935"
2) "28.24822910954627275"
127.0.0.1:6379> GEORADIUS china:city 112.50 25.50 1000 km count 1 # 筛选出指定的结果
1) "guangzhou"
127.0.0.1:6379> GEORADIUS china:city 112.50 25.50 1000 km withdist withcoord count 1
1) 1) "guangzhou"
2) "271.5123"
3) 1) "113.28822880983352661"
2) "23.16693950416961911"
127.0.0.1:6379>
tips: 1. 可以以你为中心,寻找附近的人;2. 查看附近的人离你多远; 3. 可以查看附近的人的位置
这个命令和 GEORADIUS
命令一样, 都可以找出位于指定范围内的元素, 但是 GEORADIUSBYMEMBER
的中心点是由给定的位置元素决定的, 而不是像 GEORADIUS
那样, 使用输入的经度和纬度来决定中心点。
127.0.0.1:6379> GEORADIUSBYMEMBER china:city beijing 1000 km # 以北京为中心,寻找方圆1000km内的城市
1) "shijiazhuang"
2) "tianjin"
3) "beijing"
127.0.0.1:6379> GEORADIUSBYMEMBER china:city changsha 500 km
1) "changsha"
127.0.0.1:6379> GEORADIUSBYMEMBER china:city changsha 1000 km
1) "guangzhou"
2) "changsha"
127.0.0.1:6379>
# 将二维的经纬度转换为一维的字符串,如果两个字符串越接近,那么则距离越近!
127.0.0.1:6379> GEOHASH china:city beijing shijiazhuang
1) "wx4g188g0t0"
2) "wwc2mzrs9n0"
127.0.0.1:6379>
GEO 底层的实现原理其实就是Zset!
# 使用 Zset 命令来操作 GEO
127.0.0.1:6379> ZRANGE china:city 0 -1 # 查看、移除等操作,跟 Zset 一样
1) "guangzhou"
2) "changsha"
3) "shijiazhuang"
4) "tianjin"
5) "beijing"
命令 | 描述 |
---|---|
Redis GEOHASH 命令 | 返回一个或多个位置元素的 Geohash 表示 |
Redis GEOPOS 命令 | 从key里返回所有给定位置元素的位置(经度和纬度) |
Redis GEODIST 命令 | 返回两个给定位置之间的距离 |
Redis GEORADIUS 命令 | 以给定的经纬度为中心, 找出某一半径内的元素 |
Redis GEOADD 命令 | 将指定的地理空间位置(纬度、经度、名称)添加到指定的key中 |
Redis GEORADIUSBYMEMBER 命令 | 找出位于指定范围内的元素,中心点是由给定的位置元素决定 |
HyperLogLog 算法的原理讲解以及 Redis 是如何应用它的
基数就是指一个集合中不同值的数目,比如[a,b,c,d]的基数就是4,[a,b,c,d,a]的基数还是4,因为a重复了一个,不算。基数也可以称之为Distinct Value,简称DV。
统计 APP或网页 的一个页面,每天有多少用户点击进入的次数。同一个用户的反复点击进入记为 1 次。
聪明的你可能会马上想到,用 HashMap
这种数据结构就可以了,也满足了去重。的确,这是一种解决方法,除此之外还有其它的解决方案。
问题虽不难,但当参与问题中的变量达到一定数量级的时候,再简单的问题都会变成一个难题。假设 APP 中日活用户达到百万
或千万以上级别
的话,我们采用 HashMap
的做法,就会导致程序中占用大量的内存。
我们下面尝试估算下 HashMap
的在应对上述问题时候的内存占用。假设定义HashMap
中 Key
为 string
类型,value
为 bool
。key
对应用户的Id
,value
是是否点击进入
。明显地,当百万不同用户访问的时候。此HashMap
的内存占用空间为:100万 * (string + bool)
。
请记住,我们的目的是为了统计次数,而不是保存用户id。
可以说,在上述问题目前现有的解决方案中,HashMap
是内存占用量最多的一种。如果统计量不多,那么可以使用这种方法解决问题,实现起来也简单。
除此之外还有B+ 树
,Bitmap 位图
,以及该文章主要介绍的 HyperLogLog
算法解决方案。
在一定条件允许下,如果允许统计在巨量数据面前的误差率在可接受的范围内,1000万浏览量允许最终统计出少了一两万这样子,那么就可以采用HyperLogLog
算法来解决上面的计数类似问题。
HyperLogLog
,下面简称为HLL
,它是 LogLog
算法的升级版,作用是能够提供不精确的去重计数。存在以下的特点:
Redis
中实现的 HyperLogLog
,只需要12K
内存就能统计2^64
个数据。辅助计算因子
进行降低。稍微对编程中的基础数据类型内存占用有了解的同学,应该会对其只需要12K
内存就能统计2^64
个数据而感到惊讶。为什么这样说呢,下面我们举下例子:
取 Java
语言来说,一般long
占用8字节,而一字节有8位,即:1 byte = 8 bit,即long
数据类型最大可以表示的数是:2^63-1
。对应上面的2^64
个数,假设此时有2^63-1
这么多个数,从 0 ~ 2^63-1
,按照long
以及1k = 1024字节
的规则来计算内存总数,就是:((2^63-1) * 8/1024)K
,这是很庞大的一个数,存储空间远远超过12K
。而 HyperLogLog
却可以用 12K
就能统计完。
127.0.0.1:6379> PFADD userId 1 2 3 4 5 6 7 8 9 # 创建第一组元素 userId,并将任意数量的元素添加到指定的 HyperLogLog 里面
(integer) 1
127.0.0.1:6379> PFADD userId2 8 9 10 11 12 13 14
(integer) 1
127.0.0.1:6379> PFCOUNT userId # 统计第一组元素 userId 的近似基数,基数并不是精确值, 而是一个带有 0.81% 标准错误
(integer) 9
127.0.0.1:6379> PFCOUNT userId2
(integer) 7
127.0.0.1:6379> PFMERGE userId3 userId userId2 # 合并两组元素 userId 和 UserId2 生成 userId3
OK
127.0.0.1:6379> PFCOUNT userId3
(integer) 14
127.0.0.1:6379>
命令 | 描述 |
---|---|
Redis Pgmerge 命令 | 将多个 HyperLogLog 合并为一个 HyperLogLog |
Redis Pfadd 命令 | 添加指定元素到 HyperLogLog 中。 |
Redis Pfcount 命令 | 返回给定 HyperLogLog 的基数估算值。 |
位存储 参考文章
统计用户信息(已打卡、未打卡)。一般是两个状态,都可以使用 Bitmap!
Bitmap 位图,数据结构!都是操作二进制位来进行记录,就只有 0 和 1 两个状态!
# 以打开为例 第一个数是周几,第二个数是是否打卡(0未打卡,1已打卡)
127.0.0.1:6379> SETBIT sign 0 1 # 周一 已打卡
(integer) 0
127.0.0.1:6379> SETBIT sign 1 0 # 周二 未打卡
(integer) 0
127.0.0.1:6379> SETBIT sign 2 1
(integer) 0
127.0.0.1:6379> SETBIT sign 3 0
(integer) 0
127.0.0.1:6379> SETBIT sign 4 1
(integer) 0
127.0.0.1:6379> SETBIT sign 5 0
(integer) 0
127.0.0.1:6379> SETBIT sign 6 0
(integer) 0
127.0.0.1:6379> GETBIT sign 3 # 查看周四是否打卡
(integer) 0 # 未打卡
127.0.0.1:6379> GETBIT sign 4
(integer) 1
127.0.0.1:6379> BITCOUNT sign # 统计已打卡的天数
(integer) 3
127.0.0.1:6379> BITCOUNT sign 0 4 # 统计指定范围内已打卡的天数
(integer) 3
127.0.0.1:6379>
tips: 这些在生活中或者开发中,都有十分多的应用场景,学习了就是多一个思路!
提到redis的事务,相信很多初学的朋友会对它的理解和使用有些模糊不清,想着和我们常见的关系型数据库(mysql 、mssql等)中的事务相同,也支持回滚,但这样理解就进入了一个误区。
总结:关系型数据中的事务都是原子性的,而 Redis 的事务是非原子性的。
Redis 事务的本质是一组命令的集合。事务支持一次执行多个命令,一个事务中所有命令都会被序列化。在事务执行过程,会按照顺序串行化执行队列中的命令,其他客户端提交的命令请求不会插入到事务执行命令序列中。
总结:redis事务就是一次性、顺序性、排他性的执行一个队列中的一系列命令。
Redis事务相关命令:
127.0.0.1:6379> MULTI # 开始事务
OK
127.0.0.1:6379> set k1 v1 # 命令入队
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> get k2
QUEUED
127.0.0.1:6379> EXEC # 执行事务
1) OK
2) OK
3) "v2"
127.0.0.1:6379>
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> set k4 v4
QUEUED
127.0.0.1:6379> DISCARD # 取消事务
OK
127.0.0.1:6379> get k4 # 事务队列中的命令都不会被执行!
(nil)
127.0.0.1:6379>
事务入队期间的代码发生错误(可见),所有命令都不会被执行!
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> getset k3 v3
QUEUED
127.0.0.1:6379> getset k4 # 错误的命令
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379> exec # 执行事务报错
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get k3 # 所有的命令都不会被执行
(nil)
127.0.0.1:6379>
127.0.0.1:6379> set k1 "v1" # 设置键为k1,值为字符串v1
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> INCR k1 # 由于值为字符串v1,不能执行增量,在执行的时候会失败!
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> get k2
QUEUED
127.0.0.1:6379> EXEC
1) (error) ERR value is not an integer or out of range # 虽然第一条命令报错了,但是后面的依然正常执行了!
2) OK
3) "v2"
127.0.0.1:6379> get k2
"v2"
127.0.0.1:6379>