学习视频地址:【狂神说Java】Redis最新超详细版教程通俗易懂
为什么要用NoSQL 用户的个人信息、社交网络、地理位置、用户自己产生的数据、用户日志等等爆发式增长。使用NoSQL数据库可以很好的解决上述情况。
什么是NoSQL NoSQL=Not Only SQL。泛指非关系型数据库。随着web2.0互联网的诞生,传统的关系型数据库很难应付此时的需求。尤其是超大规模,高并发的社区。NoSQL在当今大数据时代下十分通用,Redis是发展最快的。
很多的数据类型用户的个人信息、社交网络、地理位置等数据的存储不需要一个固定的格式。不需要多余的操作就可以横向扩展(使用键值对来存储)。
特点:
方便扩展(数据之间没有关系,很好扩展)
大数据条件下性能高(Redis一秒写8万次,读取11万次,NoSQL的缓存)
数据类型多样,不需要事先设计数据库,随取随用
传统的RDBMS和NoSQL
1 2 3 4 5 6 传统的RDBMS - 结构化组织 - SQL - 数据和关系都在单独的表中 - 数据操作语言,数据定义语言 - 严格的一致性
1 2 3 4 5 6 7 NoSQL - 不仅仅是数据 - 没有固定的查询语言 - 键值对存储、列存储、文档存储、图形数据库(社交关系) - 最终一致性 - CAP定理和BASE(异地多活) - 高性能、高可用、高可扩展
NoSQL的四大分类:
kv键值对:Redis 、memcache
文档型数据库(bson和json格式):
MangoDB
MangoDB是一个基于分布式文件存储的数据库,C++编写,主要用来处理大量的文档
MangoDB是一个介于关系型数据库和非关系型数据库之间的产品,是非关系型数据库中功能最丰富的,最像关系型数据库的
CouchDB
列存储数据库
图关系数据库(存放关系,比如:朋友圈,广告推荐)
Mac下Redis的运行环境 在Mac中,Redis的安装目录在/usr/local/bin
下,Redis默认不是后台启动的,修改配置文件redis.conf
中的daemonize
项为yes,如下图:
启动Redis 在/usr/local/bin
目录下启动Redis,并使用之前修改后的配置文件:
使用redis-cli
进行连接并验证Redis服务是否正常启动:
输入Ping
后返回Pong
说明连接成功。
查看redis进程是否开启:
关闭Redis服务 使用shutdown
命令关闭服务:
Redis-benchmark 官方自带的性能测试工具,redis 性能测试工具可选参数如下所示:
简单测试下:
1 2 redis-benchmark -h localhost -p 6379 -c 100 -n 100000
测试结果如下图所示:
Redis的基础知识 Redis有16个数据库,默认使用第一个数据库,可以使用select切换
1 2 3 4 127.0.0.1:6379> select 3 OK 127.0.0.1:6379[3]> dbsize (integer ) 0
切换回0号数据库后查看其大小
1 2 3 4 5 6 127.0.0.1:6379[3]> select 0 OK 127.0.0.1:6379> dbsize (integer ) 5 127.0.0.1:6379> get name "hongjiahao"
查看数据库所有的key:
1 2 3 4 5 6 127.0.0.1:6379> keys * 1) "mylist" 2) "myhash" 3) "key:__rand_int__" 4) "name" 5) "counter:__rand_int__"
清空当前数据库:
1 2 3 4 5 6 127.0.0.1:6379> flushdb OK 127.0.0.1:6379> dbsize (integer ) 0 127.0.0.1:6379> keys * (empty array)
清空全部数据库:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 127.0.0.1:6379> set name hongjiahao OK 127.0.0.1:6379> get name "hongjiahao" 127.0.0.1:6379> keys * 1) "name" 127.0.0.1:6379> select 3 OK 127.0.0.1:6379[3]> flushall OK 127.0.0.1:6379[3]> select 0 OK 127.0.0.1:6379> keys * (empty array)
Redis是单线程的!
Redis很快,官方表示Redis是基于内存操作的,CPU不是其性能瓶颈,Redis的性能瓶颈时根据机器的内存和网络带宽决定的,所以可以使用单线程的方案来实现。
Redis是C语言写的,官方提供的数据是100000+ QPS,完全不比同样是使用key-value的Memecache差。
Redis为什么是单线程但还很快?
误区一:高性能的服务器一定是多线程的
误区二:多线程一定比单线程快
核心:Redis将全部的数据放在内存中,所以使用单线程来操作效率就是最高的,如果使用多线程,反而会因为上下文切换而降低其效率。
Redis五大数据类型
Redis是一个开源(BSD许可)的内存数据结构存储,被用作数据库、缓存和消息代理。Redis提供的数据结构包括:字符串、哈希值、列表、集合、带范围查询的排序集合、位图、超日志、地理空间索引和流。Redis有内置的复制、Lua脚本、LRU驱逐、事务和不同级别的磁盘持久性,并通过Redis Sentinel和Redis Cluster的自动分区提供高可用性。
查看某个key是否存在&移除某个键值:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 127.0.0.1:6379> set name hongjiahao OK 127.0.0.1:6379> set age 24 OK 127.0.0.1:6379> keys * 1) "age" 2) "name" 127.0.0.1:6379> exists name (integer ) 1 127.0.0.1:6379> exists name1 (integer ) 0 127.0.0.1:6379> move name 1 (integer ) 1 127.0.0.1:6379> keys * 1) "age"
设置过期时间:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 127.0.0.1:6379> set name hongjiahao OK 127.0.0.1:6379> keys * 1) "age" 2) "name" 127.0.0.1:6379> get name "hongjiahao" 127.0.0.1:6379> expire name 10 (integer ) 1 127.0.0.1:6379> ttl name (integer ) 3 127.0.0.1:6379> ttl name (integer ) 2 127.0.0.1:6379> ttl name (integer ) 1 127.0.0.1:6379> ttl name (integer ) -2 127.0.0.1:6379> ttl name (integer ) -2 127.0.0.1:6379> get name (nil)
查看当前key的类型:
1 2 3 4 5 6 7 8 9 127.0.0.1:6379> set name hongjiahao OK 127.0.0.1:6379> keys * 1) "age" 2) "name" 127.0.0.1:6379> type name string 127.0.0.1:6379> type age string
String类型 append、strlen 1 2 3 4 5 6 7 8 127.0.0.1:6379> get name "hongjiahao" 127.0.0.1:6379> append name 1997 (integer ) 14 127.0.0.1:6379> get name "hongjiahao1997" 127.0.0.1:6379> strlen name (integer ) 14
incr、decr、incrby、decrby 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 127.0.0.1:6379> set views 0 OK 127.0.0.1:6379> get views "0" 127.0.0.1:6379> incr views (integer ) 1 127.0.0.1:6379> get views "1" 127.0.0.1:6379> decr views (integer ) 0 127.0.0.1:6379> get views "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 5 (integer ) 5 127.0.0.1:6379> get views "5"
getrange、strange、setex、sent、meet、mget、对象、getset 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 127.0.0.1:6379> set k1 hello,hongjiahao OK 127.0.0.1:6379> getrange k1 0 3 "hell" 127.0.0.1:6379> getrange k1 0 -1 "hello,hongjiahao" 127.0.0.1:6379> setrange k1 1 a (integer ) 16 127.0.0.1:6379> get k1 "hallo,hongjiahao" 127.0.0.1:6379> setrange k1 1 aaa (integer ) 16 127.0.0.1:6379> get k1 "haaao,hongjiahao" 127.0.0.1:6379> setex k2 30 "hello" OK 127.0.0.1:6379> get k2 "hello" 127.0.0.1:6379> ttl k2 (integer ) 23 127.0.0.1:6379> ttl k2 (integer ) 18 127.0.0.1:6379> setnx k3 "Redis" (integer ) 1 127.0.0.1:6379> get k3 "Redis" 127.0.0.1:6379> setnx k3 "MangoDB" (integer ) 0 127.0.0.1:6379> get k3 "Redis" 127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3 OK 127.0.0.1:6379> mget k1 k2 k3 1) "v1" 2) "v2" 3) "v3" 127.0.0.1:6379> msetnx k1 vv1 k2 vv2 k3 vv3 k4 vv4 (integer ) 0 127.0.0.1:6379> mget k1 k2 k3 k4 1) "v1" 2) "v2" 3) "v3" 4) (nil) 127.0.0.1:6379> set user:1 {name:lisi,age:13} OK 127.0.0.1:6379> get user:1 "{name:lisi,age:13}" 127.0.0.1:6379> mset user:1:name zhangsan user:1:age 12 OK 127.0.0.1:6379> mget user:1:name user:1:age 1) "zhangsan" 2) "12" 127.0.0.1:6379> getset db Redis (nil) 127.0.0.1:6379> get db "Redis" 127.0.0.1:6379> getset db mangodb "Redis" 127.0.0.1:6379> get db "mangodb"
String类型的使用场景:value除了是字符串还能是数字
计数器
统计多单位的数量(eg.: set User:9999:follow 20000)
对象缓存存储
List类型(列表) 基本的数据类型,列表。在Redis中,可以使用List实现栈、队列、阻塞队列。所有的List命令都是L开头的。
LPUSH、RPUSH、LRANGE 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 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 1) "three" 2) "two" 3) "one" 127.0.0.1:6379> LRANGE list 0 1 1) "three" 2) "two" 127.0.0.1:6379> RPUSH list four (integer ) 4 127.0.0.1:6379> LRANGE list 0 -1 1) "three" 2) "two" 3) "one" 4) "four"
LPOP、LINDEX 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 127.0.0.1:6379> LPOP list "three" 127.0.0.1:6379> LRANGE list 0 -1 1) "two" 2) "one" 3) "four" 127.0.0.1:6379> RPOP list "four" 127.0.0.1:6379> LRANGE list 0 -1 1) "two" 2) "one" 127.0.0.1:6379> LINDEX list 0 "two"
Llen、Lrem、Ltrim 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 127.0.0.1:6379> LRANGE list 0 -1 1) "three" 2) "two" 3) "one" 127.0.0.1:6379> Llen list (integer ) 3 127.0.0.1:6379> LRANGE list 0 -1 1) "three" 2) "three" 3) "two" 4) "one" 127.0.0.1:6379> Lrem list 1 one (integer ) 1 127.0.0.1:6379> LRANGE list 0 -1 1) "three" 2) "three" 3) "two" 127.0.0.1:6379> Lrem list 2 three (integer ) 2 127.0.0.1:6379> LRANGE list 0 -1 1) "two" 127.0.0.1:6379> RPUSH mylist "hello" (integer ) 1 127.0.0.1:6379> RPUSH mylist "hello1" (integer ) 2 127.0.0.1:6379> RPUSH mylist "hello2" (integer ) 3 127.0.0.1:6379> RPUSH mylist "hello3" (integer ) 4 127.0.0.1:6379> LRANGE mylist 0 -1 1) "hello" 2) "hello1" 3) "hello2" 4) "hello3" 127.0.0.1:6379> ltrim mylist 1 2 OK 127.0.0.1:6379> LRANGE mylist 0 -1 1) "hello1" 2) "hello2"
组合命令,例如RPOPLPUSH 1 2 3 4 5 6 7 8 9 10 127.0.0.1:6379> RPOPLPUSH mylist myotherlist "hello2" 127.0.0.1:6379> LRANGE mylist 0 -1 1) "hello1" 127.0.0.1:6379> LRANGE myotherlist 0 -1 1) "hello2" 127.0.0.1:6379>
更新Lset、插入Linsert 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 127.0.0.1:6379> exists list (integer ) 0 127.0.0.1:6379> lset list 0 item (error) ERR no such key 127.0.0.1:6379> lpush list value (integer ) 1 127.0.0.1:6379> lrange list 0 0 1) "value" 127.0.0.1:6379> lset list 0 item OK 127.0.0.1:6379> lrange list 0 0 1) "item" 127.0.0.1:6379> lset list 1 other (error) ERR index out of range 127.0.0.1:6379> LPUSH mylist hello (integer ) 1 127.0.0.1:6379> RPUSH mylist world (integer ) 2 127.0.0.1:6379> linsert mylist before "world" "," (integer ) 3 127.0.0.1:6379> lrange mylist 0 -1 1) "hello" 2) "," 3) "world" 127.0.0.1:6379> linsert mylist after world ! (integer ) 4 127.0.0.1:6379> lrange mylist 0 -1 1) "hello" 2) "," 3) "world" 4) "!"
实际上是一个双端链表,可以在节点的前后插入新节点
如果key不存在,需要先创建之后再更改或添加
如果移除了一个链表中的所有值,那么就变成了空链表,也就代表它不存在了
在链表的两端插入或改动值,效率最高。中间的元素效率会相对低一些
作为消息队列:LPUSH RPOP;作为栈:LPUSH LPOP
Set类型(集合) Set中的值是不能重复的!Set的命令开头基本都是S。
Sadd、Smembers、Sismember 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 127.0.0.1:6379> sadd myset "hellohongjiahao" (integer ) 1 127.0.0.1:6379> sadd myset "hello" (integer ) 1 127.0.0.1:6379> sadd myset "hognjiahao" (integer ) 1 127.0.0.1:6379> smembers myset 1) "hello" 2) "hognjiahao" 3) "hellohongjiahao" 127.0.0.1:6379> sismember myset hello (integer ) 1 127.0.0.1:6379> sismember myset hhh (integer ) 0 127.0.0.1:6379> sadd myset "hello" (integer ) 0
Scard、Srem 1 2 3 4 5 6 7 8 9 127.0.0.1:6379> scard myset (integer ) 3 127.0.0.1:6379> srem myset hello (integer ) 1 127.0.0.1:6379> sismember myset hello (integer ) 0
Srandmember、Spop 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 127.0.0.1:6379> srandmember myset "hellohongjiahao" 127.0.0.1:6379> srandmember myset "hellohongjiahao" 127.0.0.1:6379> srandmember myset "hognjiahao" 127.0.0.1:6379> srandmember myset "hellohongjiahao" 127.0.0.1:6379> smembers myset 1) "wuxingyi" 2) "hello" 3) "hognjiahao" 4) "hellohongjiahao" 127.0.0.1:6379> spop myset "hognjiahao" 127.0.0.1:6379> smembers myset 1) "wuxingyi" 2) "hello" 3) "hellohongjiahao"
Smove 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 127.0.0.1:6379> sadd myset1 hello world ! (integer ) 3 127.0.0.1:6379> sadd myset2 xixi haha fuck (integer ) 3 127.0.0.1:6379> smove myset1 myset2 hello (integer ) 1 127.0.0.1:6379> smembers myset1 1) "world" 2) "!" 127.0.0.1:6379> smembers myset2 1) "haha" 2) "xixi" 3) "hello" 4) "fuck"
集合
1 2 3 4 5 6 7 127.0.0.1:6379> sadd myset1 a b c d (integer ) 4 127.0.0.1:6379> sadd myset2 a w d s c (integer ) 5
1 2 3 4 5 6 7 127.0.0.1:6379> sinter myset1 myset2 1) "c" 2) "d" 3) "a"
1 2 3 4 5 6 7 8 9 10 127.0.0.1:6379> sunion myset1 myset2 1) "a" 2) "w" 3) "b" 4) "s" 5) "c" 6) "d"
应用:
可以将A用户所有的关注up的uid放在一个set中,A的粉丝的uid放在另一个set中。
例如:共同关注,共同爱好,推荐好友
Hash类型(哈希) Map集合,key-map集合,类似key-{{key1,value1},{key1,value2},...}
,但本质和string类型没有太大区别,还是一个简单的key-value。
Hset、Hget、Hmset、Hmget、Hgetall、Hdel 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 set myhash field1 hongjiahao(integer ) 1 127.0.0.1:6379> hget myhash field1 "hongjiahao" hmset myhash field1 hello field2 world OK 127.0.0.1:6379> hmget myhash field1 field2 1) "hello" 2) "world" 127.0.0.1:6379> hgetall myhash 1) "field1" 2) "hello" 3) "field2" 4) "world" 127.0.0.1:6379> hdel myhash field1 (integer ) 1 127.0.0.1:6379> hgetall myhash 1) "field2" 2) "world"
Hlen、Hexists、Hkeys、Hvals 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 127.0.0.1:6379> hmset myhash field1 hello field2 world OK 127.0.0.1:6379> hlen myhash (integer ) 2 127.0.0.1:6379> hexists myhash field1 (integer ) 1 127.0.0.1:6379> hexists myhash field3 (integer ) 0 127.0.0.1:6379> hkeys myhash 1) "field1" 2) "field2" 127.0.0.1:6379> hvals myhash 1) "hello" 2) "world"
Hincrby、Hsetnx 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 127.0.0.1:6379> hset myhash field3 5 (integer ) 0 127.0.0.1:6379> hincrby myhash field3 1 (integer ) 6 127.0.0.1:6379> hincrby myhash field3 -1 (integer ) 5 127.0.0.1:6379> hsetnx myhash field4 hello (integer ) 1 127.0.0.1:6379> hsetnx myhash field4 world (integer ) 0 127.0.0.1:6379> hgetall myhash 1) "field1" 2) "hello" 3) "field2" 4) "world" 5) "field3" 6) "5" 7) "field4" 8) "hello"
hash用于变更的数据 user{ name, age, hobby, …},尤其是用户信息的保存,或经常变化的信息。hash更适合对象的存储,string更加适合字符串的存储。
Zset类型(有序集合) 在set的基础上,增加了一个值score
用于记录key的优先级。
Zadd、Zrange、Zrangebyscore、Zrevrange 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 127.0.0.1:6379> zadd myset 1 one (integer ) 1 127.0.0.1:6379> zadd myset 2 two (integer ) 1 127.0.0.1:6379> zadd myset 3 three 4 four (integer ) 2 127.0.0.1:6379> zrange myset 0 -1 1) "one" 2) "two" 3) "three" 4) "four" 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 kuangshen (integer ) 1 127.0.0.1:6379> zrangebyscore salary -inf +inf 1) "kuangshen" 2) "xiaohong" 3) "zhangsan" 127.0.0.1:6379> zrevrange salary 0 -1 1) "zhangsan" 2) "xiaohong" 3) "kuangshen" 127.0.0.1:6379> zrangebyscore salary -inf +inf withscores 1) "kuangshen" 2) "500" 3) "xiaohong" 4) "2500" 5) "zhangsan" 6) "5000" 127.0.0.1:6379> zrangebyscore salary -inf 2500 withscores 1) "kuangshen" 2) "500" 3) "xiaohong" 4) "2500" 127.0.0.1:6379> zrangebyscore salary -inf (2500 withscores 1) "kuangshen" 2) "500" 127.0.0.1:6379> zrangebyscore salary (500 2500 withscores 1) "xiaohong" 2) "2500"
Zrem、Zcard、Zcount 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 127.0.0.1:6379> zrem salary xiaohong (integer ) 1 127.0.0.1:6379> zrange salary 0 -1 1) "kuangshen" 2) "zhangsan" 127.0.0.1:6379> zcard salary (integer ) 2 127.0.0.1:6379> zadd myset 1 hello 2 world 3 hongjiahao (integer ) 3 127.0.0.1:6379> zcount myset 1 3 (integer ) 3 127.0.0.1:6379> zcount myset 1 2 (integer ) 2
其余的一些API可以通过官方文档查看
案例思路:set 排序(用于存储班级成绩表,工资表排序)
普通消息(1),重要消息(2),带权重进行判断
排行榜应用实现
三种特殊数据类型 geospatial地理位置 朋友的定位,附近的人,打车距离计算
Redis的Geo在Redis的3.2版本就已经推出了。该功能可以推算地理位置的信息,两地之间的距离,方圆几里的人。
查询城市经纬度的网站:http://www.jsons.cn/lngcode/
只有6个命令。
geoadd:添加地理位置 1 2 3 4 5 6 7 8 9 10 11 12 127.0.0.1:6379> geoadd china:city 116.40 39.90 beijing (integer ) 1 127.0.0.1:6379> geoadd china:city 121.47 31.23 shanghai (integer ) 1 127.0.0.1:6379> geoadd china:city 106.50 29.53 chongqing (integer ) 1 127.0.0.1:6379> geoadd china:city 114.05 22.52 shenzhen 120.16 30.24 hangzhou 108.96 34.26 xian (integer ) 3
geopos:查找指定的成员位置 获得当前定位:一定是个坐标值
1 2 3 4 5 6 7 127.0.0.1:6379> geopos china:city beijing chongqing 1) 1) "116.39999896287918091" 2) "39.90000009167092543" 2) 1) "106.49999767541885376" 2) "29.52999957900659211"
geodist:两人之间的直线距离 1 2 3 4 127.0.0.1:6379> geodist china:city beijing shanghai "1067378.7564" 127.0.0.1:6379> geodist china:city beijing shanghai km "1067.3788"
georadius:以给定的经纬度为中心,找出某一半径内的元素 我附近的人?(获得所有附近的人的地址:定位),然后通过半径来查询
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 127.0.0.1:6379> georadius china:city 110 30 1000 km 1) "chongqing" 2) "xian" 3) "shenzhen" 4) "hangzhou" 127.0.0.1:6379> georadius china:city 110 30 500 km 1) "chongqing" 2) "xian" 127.0.0.1:6379> georadius china:city 110 30 500 km withdist 1) 1) "chongqing" 2) "341.9374" 2) 1) "xian" 2) "483.8340" 127.0.0.1:6379> georadius china:city 110 30 500 km withcoord 1) 1) "chongqing" 2) 1) "106.49999767541885376" 2) "29.52999957900659211" 2) 1) "xian" 2) 1) "108.96000176668167114" 2) "34.25999964418929977" 127.0.0.1:6379> georadius china:city 110 30 500 km withcoord count 1 1) 1) "chongqing" 2) 1) "106.49999767541885376" 2) "29.52999957900659211" 127.0.0.1:6379> georadius china:city 110 30 500 km withcoord count 2 1) 1) "chongqing" 2) 1) "106.49999767541885376" 2) "29.52999957900659211" 2) 1) "xian" 2) 1) "108.96000176668167114" 2) "34.25999964418929977"
georadiusbymember:根据具体的元素查询 具体功能和上面一致,不过使用具体的成员代替坐标位置
1 2 3 4 5 127.0.0.1:6379> GEORADIUSBYMEMBER china:city beijing 1000 km 1) "beijing" 2) "xian"
geohash:返回一个或多个位置元素的Geohash表示 该命令将返回11个字符的Geohash字符串
1 2 3 4 5 127.0.0.1:6379> geohash china:city beijing chongqing 1) "wx4fbxxfke0" 2) "wm5xzrybty0"
底层的实现原理Zset 可以使用Zset命令来操作Geo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 127.0.0.1:6379> zrange china:city 0 -1 1) "chongqing" 2) "xian" 3) "shenzhen" 4) "hangzhou" 5) "shanghai" 6) "beijing" 127.0.0.1:6379> zrem china:city beijing (integer ) 1 127.0.0.1:6379> zrange china:city 0 -1 1) "chongqing" 2) "xian" 3) "shenzhen" 4) "hangzhou" 5) "shanghai"
Hyperloglog 什么是基数?
A{1,3,5,7,8,7}
B{1,3,5,7,8}
基数(不重复的元素的数量):A的基数:4;B的基数:5
Redis Hyperloglog 用于基数统计
优点:占用的内存是固定的,2^64不同的元素,只需要12KB内存。如果要从内存的角度来比较,Hyperloglog就是首选。
有0.81%的错误率。
使用场景:
网页的UV(unique visitor):一个人访问一个网站多次,但还是只算做一个访问者。传统的方式中,用set保存用户的id,然后可以统计set中的元素数量作为判断标准。这个方式如果保存大量的用户id,就会占用较大的存储。我们的目的是计数,而不是保存用户id。
PFadd、PFcount、PFmerge 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 127.0.0.1:6379> PFadd mykey a b c d e f g h i j (integer ) 1 127.0.0.1:6379> PFcount mykey (integer ) 10 127.0.0.1:6379> PFadd mykey2 i j z x c v b n m (integer ) 1 127.0.0.1:6379> PFcount mykey2 (integer ) 9 127.0.0.1:6379> PFMERGE mykeys mykey mykey2 OK 127.0.0.1:6379> PFcount mykeys (integer ) 15
Bitmaps:位存储 setbit 使用bitmaps记录周一到周日的打卡情况
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 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 0 (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 1 (integer ) 0 127.0.0.1:6379> setbit sign 6 0 (integer ) 0 127.0.0.1:6379> setbit sign 7 0 (integer ) 0
getbit 查看某一天是否打卡
1 2 3 4 127.0.0.1:6379> getbit sign 3 (integer ) 0 127.0.0.1:6379> getbit sign 4 (integer ) 1
bitcount 统计打卡的天数
1 2 127.0.0.1:6379> bitcount sign (integer ) 3
事务 原子性:要么同时成功,要么同时失败!
关系型数据库保证了原子性,Redis的单条命令是保证原子性的,但是事务不保证原子性。
Redis事务的本质:一组命令的集合。一个事务中的所有命令都会被序列化,在事务的执行过程中,会按照顺序执行。一次性、顺序性、排他性。Redis事务没有隔离级别的概念,所有的命令在事务中,没有直接执行,只有发起执行命令的时候才会执行。
Redis的事务:
开启事务(Multi)
命令入队(…)
执行事务(exec)
锁:Redis可以实现乐观锁
正常执行事务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 127.0.0.1:6379> multi OK 127.0.0.1:6379(TX)> set k1 v1 QUEUED 127.0.0.1:6379(TX)> set k2 v2 QUEUED 127.0.0.1:6379(TX)> get k2 QUEUED 127.0.0.1:6379(TX)> set k3 v3 QUEUED 127.0.0.1:6379(TX)> EXEC 1) OK 2) OK 3) "v2" 4) OK
放弃事务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 127.0.0.1:6379> multi OK 127.0.0.1:6379(TX)> set k1 v1 QUEUED 127.0.0.1:6379(TX)> set k2 v2 QUEUED 127.0.0.1:6379(TX)> set k4 v4 QUEUED 127.0.0.1:6379(TX)> DISCARD OK 127.0.0.1:6379> get k4 (nil)
编译性错误:命令有错,事务中的所有命令都不会被执行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 127.0.0.1:6379> MULTI OK 127.0.0.1:6379(TX)> set k1 v1 QUEUED 127.0.0.1:6379(TX)> set k2 v2 QUEUED 127.0.0.1:6379(TX)> set k3 v3 QUEUED 127.0.0.1:6379(TX)> getset k3 (error) ERR wrong number of arguments for 'getset' command 127.0.0.1:6379(TX)> set k4 v4 QUEUED 127.0.0.1:6379(TX)> set k5 v5 QUEUED 127.0.0.1:6379(TX)> exec (error) EXECABORT Transaction discarded because of previous errors. 127.0.0.1:6379> get k5 (nil)
运行时异常,如果事务队列中存在语法性错误,那么执行命令的时候,其他命令还可以正常执行,错误的命令会抛出异常。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 127.0.0.1:6379> set k1 "v1" OK 127.0.0.1:6379> MULTI OK 127.0.0.1:6379(TX)> incr k1 QUEUED 127.0.0.1:6379(TX)> set k2 v2 QUEUED 127.0.0.1:6379(TX)> set k3 v3 QUEUED 127.0.0.1:6379(TX)> get k3 QUEUED 127.0.0.1:6379(TX)> EXEC 1) (error) ERR value is not an integer or out of range 2) OK 3) OK 4) "v3"
监控 悲观锁:
悲观锁:很悲观,什么时候都会出问题,无论做什么都加锁
乐观锁:
乐观锁:很乐观,认为什么时候都不会出现问题,所以不会上锁。更新数据的时候判断一下,在此期间是否有人修改过这个数据
获取version
更新的时候比较version
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 127.0.0.1:6379> set money 100 OK 127.0.0.1:6379> set out 0 OK 127.0.0.1:6379> watch money OK 127.0.0.1:6379> MULTI OK 127.0.0.1:6379(TX)> DECRBY money 20 QUEUED 127.0.0.1:6379(TX)> INCRBY out 20 QUEUED 127.0.0.1:6379(TX)> EXEC 1) (integer ) 80 2) (integer ) 20
正常执行成功
测试多线程修改完值,使用watch可以当作redis的乐观锁操作
1 2 3 4 5 127.0.0.1:6379> get money "80" 127.0.0.1:6379> set money 1000 OK
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 127.0.0.1:6379> watch money OK 127.0.0.1:6379> MULTI OK 127.0.0.1:6379(TX)> DECRBY money 10 QUEUED 127.0.0.1:6379(TX)> INCRBY out 10 QUEUED 127.0.0.1:6379(TX)> EXEC (nil) 127.0.0.1:6379> UNWATCH OK 127.0.0.1:6379> watch money OK 127.0.0.1:6379> MULTI OK 127.0.0.1:6379(TX)> DECRBY money 1 QUEUED 127.0.0.1:6379(TX)> INCRBY money 1 QUEUED 127.0.0.1:6379(TX)> EXEC 1) (integer ) 999 2) (integer ) 1000
如果修改失败,获取最新的值就好。
Redis.conf详解 1、配置文件 unit单位,对大小写不敏感
2、包含
3、网络
1 2 3 bind 127.0.0.1 protected-mode yes port 6379
4、通用设置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 daemonize yes pidfile /var/run/redis_6379.pid loglevel notice logfile "" databases 16 always-show-logo no
5、快照
redis是内存数据库,如果没有持久化,那么数据断电即失
持久化:在规定的时间内执行了多少次操作后,会持久化到 .rdb .aof
1 2 3 4 5 6 7 8 9 10 11 12 save 3600 1 save 300 100 save 60 10000 stop-writes-on-bgsave-error yes rdbcompression yes rdbchecksum yes dir /usr/local /var/db/redis/
6、REPLICATION(主从复制)
7、安全
可以设置redis的密码,默认是没有密码的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 127.0.0.1:6379> ping PONG 127.0.0.1:6379> config get requirepass 1) "requirepass" 2) "" 127.0.0.1:6379> config set requirepass "hjh1314lvwxy" OK 127.0.0.1:6379> config get requirepass 1) "requirepass" 2) "hjh1314lvwxy" 127.0.0.1:6379> config get requirepass (error) NOAUTH Authentication required. 127.0.0.1:6379> auth hjh1314lvwxy OK 127.0.0.1:6379> config get requirepass 1) "requirepass" 2) "hjh1314lvwxy"
8、CLIENTS限制
1 2 3 maxclients 10000 maxmemory <bytes> maxmemory-policy noeviction
9、APPEND ONLY MODE aof配置
1 2 3 4 5 6 appendonly no appendfilename "appendonly.aof" appendfsync everysec
Redis的持久化 RDB(Redis Database) Redis是内存数据库,如果不将内存中的数据库状态保存在磁盘,一旦服务器退出进程,服务器中的数据库状态也会消失。所以Redis提供了持久化功能。
在指定的时间间隔内将内存中的数据集快照写入磁盘,也就是Snapshot快照,它会腐蚀是将快照文件直接读到内存当中。
Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入一个临时文件中,待持久化过程都结束了,再用这个临时文件替换掉上次持久化好的文件。整个过程中,主进程不进行任何OI操作。这就确保了极高的性能。如果需进行大规模的数据恢复,且对于数据恢复的完整性不是特别敏感,那RDB方式要比AOF更加高效。RDB的缺点是最后一次持久化的数据可能丢失。
rdb保存的文件是dump.rdb,可以在配置文件的快照项下修改。修改持久化的频率为:每60秒进行了5次操作就保存一次。
下图设置了dump.rdb的保存地址。
触发机制 1、save的规则满足的情况下,会自动触发rdb规则
2、执行flushall命令,会触发rdb规则
3、退出redis,会产生rdb文件
恢复rdb文件 1、只需将rdb文件放到redis的启动项中
2、查看需要存放的位置
1 2 3 127.0.0.1:6379> config get dir 1) "dir" 2) "/usr/local/var/db/redis"
优点 1、适合大规模的数据恢复
2、对数据的完整性要求不高
缺点 1、需要一定的时间间隔进行自动化保存,如果redis意外宕机了,最后一次修改的数据就没了
2、fork进程的时候,会占用一定的内存空间
AOF(Append Only File) 将我们的所有命令记录下来(history),恢复的时候再重新执行记录的命令。
以日志的形式来记录每个写操作,将Redis执行过的所有指令记录下来(读操作不记录),只许追加文件但不可以改写文件,redis启动之初会读取该文件重新构建数据结构,换言之,redis重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作。
AOF保存的是appendonly.aof文件。AOF模式默认是不开启的,需要手动设置开启。只需要将appendonly改为yes就可以开启aof。
appendonly.aof的保存路径如下:
1 2 3 127.0.0.1:6379> config get dir 1) "dir" 2) "/usr/local/var/db/redis"
写入的频率
1 2 3 4 5 6 7 appendfsync everysec no-appendfsync-on-rewrite no auto-aof-rewrite-percentage 100 auto-aof-rewrite-min-size 64mb
如果aof文件的大小大雨64mB,会fork一个新的进程来讲文件进行重写。aof默认文件无限追加,文件会越来越大。
如果appendonly.aof文件有错误,此时redis是启动不起来的,需要先修复这个aof文件。redis为我们提供了一个工具:redis-check-aof
1 redis-check-aof --fix appendonly.aof
如果文件正常,重启即可直接恢复了。
优点 1、每一次修改都同步,文件的完整性会更加好
2、每秒同步一次,可能只会丢失一秒的数据
3、从不同步,效率最高
缺点 1、相对于数据文件来说,aof远远大于rdb,恢复的速度也比rdb慢
2、aof运行效率也要比rdb慢,所以redis默认采用rdb持久化
扩展
RDb持久化方式能够在指定的时间间隔内对数据进行快照存储
AOF持久化方式记录每次对服务器的写操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据,AOF命令以Redis协议追加每次写的操作到文件末尾,Redis还能对后台进行重写,是的AOF文件的体积不至于过大
只做缓存,如果你希望你的数据只在服务器端存在,可以不使用任何持久化
同时开启两种持久化方式
在这种情况下,当redis重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要更完整
RDB的数据不实时,同时使用两者时服务器重启也只会找AOF文件。但RDB更适合用于备份数据库(AOF在不断变化不好备份),快速重启,而且不会有AOF可能潜在的bug。
性能建议
因为RDB文件只用做后备用途,见一直在slave上持久化RDB文件,而且只要15分钟备份一次就够了,只保留save 900 1 这条规则
如果Enable AOF,好处是在最恶劣的情况下也只会丢失不超多2秒的数据,启动脚本较简单,只需要load自己的AOF文件就可以了,代价一是带来了持续的IO,二是AOF rewrite的最后将 rewrite 过程中产生的新数据写到新文件造成的阻塞几乎是不可避免的,只要硬盘许可,应该尽量减少AOF rewrite的频率,AOF重写默认64mB太小了,可以设到5GB以上,默认超过原大小100%大小重写可以改到适当的数值。
如果不Enable AOF,仅靠 Mater-Slave Replication 实现高可用性也可以,能省掉一大笔IO,也减少了rewrite时带来的系统波动。代价是如果Master/Slave同时倒掉,会丢失十几分钟的数据,启动脚本也要比较两个Master/Slave中的RDB文件,载入较新的那个。
Redis订阅发布 订阅端:
1 2 3 4 5 6 7 8 9 10 11 12 127.0.0.1:6379> subscribe JiahaoHong1997 Reading messages... (press Ctrl-C to quit) 1) "subscribe" 2) "JiahaoHong1997" 3) (integer ) 1 1) "message" 2) "JiahaoHong1997" 3) "hello,jiahao" 1) "message" 2) "JiahaoHong1997" 3) "ganbadie"
发送端:
1 2 3 4 127.0.0.1:6379> publish JiahaoHong1997 "hello,jiahao" (integer ) 1 127.0.0.1:6379> publish JiahaoHong1997 "ganbadie" (integer ) 1
原理
Redis是用C实现的,通过分析Redis源码里的pubsub.c文件,了解发布和订阅机制的底层实现,借此加深对Redis的理解。
Redis通过publish、subscribe和psubscribe等命令实现发布和订阅功能。通过subscribe命令订阅某频道后,redis-server里维护了一个字典,字典的关键词是一个个channel,而字典的值则是一个链表,链表中保存了所有订阅这个channel的客户端。subscribe命令的关键,就是将客户端添加到给定的channel的订阅列表中。
通过publish命令向订阅者发送消息,redis-server会使用给定的频道作为键,在它所维护的channel字典中朝着记录了订阅这个频道的所有客户端的链表,链里这个链表,将消息发布给订阅者。
在Redis中,可以设定对某一个key值进行消息发布及消息订阅,当一个key值上进行了消息发布后,所有的订阅它的客户端会收到相应的消息。这一功能最明显的用法就是用作实时消息系统,比如普通的即时聊天,群聊等功能。
使用场景:
1、实时消息系统
2、实时聊天(频道当作聊天室,将信息回给所有人即可)
3、订阅、关注系统
稍微复杂的场景通常使用消息队列。