Redis
1、NoSQL
NoSQL = Not Only SQL(不仅仅是SQL)
泛指非关系型数据库的,随着web2.0互联网的诞生!传统的关系型数据库很难对付web2.0时代!尤其是超大规模的高并发的社区!暴露出来很多难以克服的问题,NoSQL在当今大数据环境下发展的十分迅速,Redis是发展最快的,也是当下我们必须掌握的技术。
很多的数据类型用户的个人信息,社交网络,地理位置。这些数据类型的存储不需要一个固定的格式!不需要多月的操作就可以横向扩展的!
NoSQL特点
1、方便扩展(数据之间没有关系,很好扩展!)
2、大数据量高性能(Redis1秒写8万次,读11万次,NoSQL的缓存记录级,是一种细粒度的缓存,性能会比较高!)
3、数据类型是多样型的。(不需要事先设计数据库!随取随用!)
4、传统RDBMS和NoSQL
传统RDBMS
- 结构化组织
- SQL
- 数据和关系都存在单独的表中
- 数据操作,数据定义语言
- 严格的一致性
- 基础的事务
- ......
NoSQL
- 不仅仅是数据
- 没有固定的查询语言
- 键值对存储,列存储,文档存储,图形数据库
- 最终一致性
- CAP定理和BASE(异地多活)
- 高性能,高可用,高可扩
- ......
2、NoSQL的四大分类
KV键值对
- Redis
文档型数据库
- MongoDB:是一个基于分布式文件存储的数据库,主要用来处理大量的文档
- MongoDB是一个介于关系型数据库和非关系型数据库数据中中间的产物,MongoDB是非关系型数据库中功能最丰富,最像关系型数据库的。
列存储数据库
- HBase
- 分布式文件系统
图关系数据库
- Neo4j,InfoGrid
3、Redis入门
概述
Redis是什么?
Redis(Remote Dictionary Server),即远程字典服务!
Redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。
Redis能干嘛?
1、内存存储、持久化,内存中是断电即失,所以说持久化很重要(rdb、aof)
2、效率高,可以用于高速缓存
3、发布订阅系统
4、地图信息分析
5、计数器(浏览量!)
6、…
特性
1、多样的数据类型
2、持久化
3、集群
4、事务
…
测试性能
redis-benchmark是一个压力测试工具
官方自带的性能测试工具
redis-benchmark命令参数
简单测试:
# 测试:100个并发连接 100000请求
redis-benchmark -h localhost -p 6379 -c 100 -n 100000
如何查看这些分析呢?
基础知识
redis默认数据库有16个数据库
默认使用的是第0个
可以使用select进行切换
127.0.0.1:6379> select 3 #切换数据库
OK
127.0.0.1:6379[3]> dbsize #查看数据库大小
(integer) 0
127.0.0.1:6379> set name khazix #设置key-value
OK
127.0.0.1:6379> get name #获取key响应的value
"khazix"
127.0.0.1:6379> keys * #查看所有的key
1) "name"
2) "myhash"
3) "mylist"
4) "key:__rand_int__"
5) "counter:__rand_int__"
127.0.0.1:6379> dbsize
(integer) 5
127.0.0.1:6379> flushall # 清空所有数据库内容
OK
127.0.0.1:6379> flushdb # 清空当前数据库内容
OK
127.0.0.1:6379> dbsize
(integer) 0
Redis是单线程的
Redis是很快的,是基于内存操作,CPU不是Redis性能瓶颈,Redis的瓶颈是根据机器的内存和网络带宽,既然可以使用单线程来实现,就使用单线程了!所以就使用了单线程了!
Redis为什么单线程那么快?
Redis是C语言写的,官方提供的数据为100000——的QPS,完全不比同样是使用key-value的Memecache差。
核心:Redis是将所有的数据放在内存中的,所以使用单线程去操作效率就是最高的,多线程CPU上下文会切换(耗时的操作),对于内存系统来说,如果没有上下文切换效率就是最高的。多次读写都是在一个CPU上的,在内存情况下,这个就是最佳的方案!
1、误区1:高性能的服务器不一定是多线程的。
2、误区2:多线程不一定比单线程效率高
4、五大数据类型
Redis是一个开源的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件MQ。它支持多种类型的数据结构,如字符串(Strings),散列(hashes),列表(lists),集合(set),有序集合(sorted sets)与范围查询,bitmaps,hyperloglogs和地理空间(geospatial)索引半径查询。Redis内置了复制(replication),LUA脚本(Lua scripting),LRU驱动事件(LRU eviction),事务(transactions)和不同级别的磁盘持久化(persistence),并通过Redis哨兵(Sentinel)和自动分区(Cluster)提供高可用性(high avaliability)。
Redis-Key
127.0.0.1:6379> set name khazix
OK
127.0.0.1:6379> set age 20
OK
127.0.0.1:6379> exists name #判断当前的key是否存在
(integer) 1
127.0.0.1:6379> exists name1
(integer) 0
127.0.0.1:6379> move name 2 #移除当前的key
(integer) 1
127.0.0.1:6379> get name
(nil)
127.0.0.1:6379> set name khazix
OK
127.0.0.1:6379> expire name 10 # 设置过期时间,单位为秒
(integer) 1
127.0.0.1:6379> ttl name #查看当前key的剩余时间
(integer) 7
127.0.0.1:6379> ttl name
(integer) 5
127.0.0.1:6379> ttl name
(integer) 4
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> get name
(nil)
127.0.0.1:6379> type age #查看当前key的类型
string
String(字符串)
127.0.0.1:6379> set key1 v1
OK
127.0.0.1:6379> append key1 "hello" #追加字符串,如果当前key不存在就相当于添加了一个key
(integer) 7
127.0.0.1:6379> get key1
"v1hello"
127.0.0.1:6379> strlen key1 #获得当前key对应值的长度
(integer) 7
127.0.0.1:6379> append key1 ",khazix"
(integer) 14
127.0.0.1:6379> get key1
"v1hello,khazix"
******************************************************
127.0.0.1:6379> set views 0 #设置访问量
OK
127.0.0.1:6379> get views
"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> decr views #访问量自减1
(integer) 1
### 设置步长
127.0.0.1:6379> incrby views 5 #访问量自增5
(integer) 6
127.0.0.1:6379> decrby views 5 #访问量自减5
(integer) 1
******************************************************
127.0.0.1:6379> flushall
OK
127.0.0.1:6379> keys *
(empty array)
127.0.0.1:6379> set key1 "hello,khazix"
OK
127.0.0.1:6379> getrange key1 0 4 #截取字符串[0,3]
"hello"
127.0.0.1:6379> getrange key1 0 -1 #获取全部的字符串
"hello,khazix"
127.0.0.1:6379> get key2
"abcdefg"
127.0.0.1:6379> setrange key2 1 xx #替换指定位置开始的字符串
(integer) 7
127.0.0.1:6379> get key2
"axxdefg"
*****************************************************
127.0.0.1:6379> setex key3 30 "hello" #设置过期时间
OK
127.0.0.1:6379> ttl key3
(integer) 22
127.0.0.1:6379> setnx mykey "redis" #不存在再设置(在分布式锁中会常常使用)
(integer) 1
127.0.0.1:6379> setnx mykey "MongoDB" #如果mykey存在,创建失败
(integer) 0
127.0.0.1:6379> keys *
1) "key1"
2) "mykey"
3) "key2"
*****************************************************
127.0.0.1:6379> flushdb
OK
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 v1 k4 v4 #已经存在k1,创建失败。msetnx是一个原子性的操作,要么一起成功,要么一起失败
(integer) 0
127.0.0.1:6379> get k4
(nil)
*****************************************************
# 对象
set user:1 {name:zhangsan,age:3} # 设置一个user:1对象 值为json字符来保存一个对象!
# user:{id}:{field}
127.0.0.1:6379> mset user:1:name zhangsan user:1:age 23
OK
127.0.0.1:6379> mget user:1:name user:1:age
1) "zhangsan"
2) "23"
*****************************************************
127.0.0.1:6379> getset db redis # 如果不存在值,则返回nil
(nil)
127.0.0.1:6379> get db
"redis"
127.0.0.1:6379> getset db mongodb # 如果存在值,获取原来的值,并设置原来的值
"redis"
127.0.0.1:6379> get db
"mongodb"
String类似的使用场景:除了是我们的字符串还可以是我们的数字!
- 计数器
- 统计多单位的数量
- 粉丝数
- 对象缓存存储
List(列表)
基本的数据类型,列表
在redis里面,我们可以把list变成栈、队列、阻塞队列。
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) "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 right # 将一个值或者多个值放入列表的尾部(右)
(integer) 4
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "two"
3) "one"
4) "right"
*****************************************************
127.0.0.1:6379> Lpop list # 移除list的第一个元素
"three"
127.0.0.1:6379> Rpop list # 移除list的最后一个元素
"right"
127.0.0.1:6379> Lrange list 0 -1
1) "two"
2) "one"
*****************************************************
127.0.0.1:6379> Lrange list 0 -1
1) "two"
2) "one"
127.0.0.1:6379> lindex list 0 # 通过下标获取list中的值
"two"
127.0.0.1:6379> lindex list 1
"one"
*****************************************************
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> Llen list # 获取列表的长度
(integer) 3
*****************************************************
127.0.0.1:6379> lrange list 0 -1
1) "four"
2) "four"
3) "three"
4) "two"
5) "one"
127.0.0.1:6379> lrem list 2 four # 移除list集合中指定个数的value
(integer) 2
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "two"
3) "one"
*****************************************************
127.0.0.1:6379> flushdb
OK
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> ltrim mylist 1 2 # 通过下标截取指定的长度,这个list已经改变,截断了只剩下截取的元素
OK
127.0.0.1:6379> lrange mylist 0 -1
1) "hello1"
2) "hello2"
*****************************************************
127.0.0.1:6379> flushdb
OK
127.0.0.1:6379> rpush list "hello"
(integer) 1
127.0.0.1:6379> rpush list "hello1"
(integer) 2
127.0.0.1:6379> rpush list "hello2"
(integer) 3
127.0.0.1:6379> rpoplpush list otherlist # 移除列表的最后一个元素,将它移动到新的列表中!
"hello2"
127.0.0.1:6379> keys *
1) "list"
2) "otherlist"
127.0.0.1:6379> lrange list 0 -1
1) "hello"
2) "hello1"
127.0.0.1:6379> lrange otherlist 0 -1
1) "hello2"
*****************************************************
127.0.0.1:6379> exists mylist # 判断这个列表是否存在
(integer) 0
127.0.0.1:6379> lpush mylist value1
(integer) 1
127.0.0.1:6379> lrange mylist 0 0
1) "value1"
127.0.0.1:6379> lset mylist 0 item # 将list中指定下标的值替换为另外一个值,更新操作
OK
127.0.0.1:6379> lrange mylist 0 0
1) "item"
*****************************************************
127.0.0.1:6379> flushdb
OK
127.0.0.1:6379> rpush list "hello"
(integer) 1
127.0.0.1:6379> rpush list "world"
(integer) 2
127.0.0.1:6379> linsert list before "world" "other" # 将某一个具体的value插入到列表中某个元素的前面或者后面
(integer) 3
127.0.0.1:6379> lrange list 0 -1
1) "hello"
2) "other"
3) "world"
127.0.0.1:6379> linsert list after "world" "new"
(integer) 4
127.0.0.1:6379> lrange list 0 -1
1) "hello"
2) "other"
3) "world"
4) "new"
小结
- 他实际上是一个链表,before Node after,left,right都可以插入值
- 如果key不存在,创建新的链表
- 如果key存在,新增内容
- 如果移除了所有值,空链表,也代表不存在
- 在两边插入或者值改动,效率最高!中间元素,相对来说效率会低一点~
Set(集合)
set中的值是不能重复的!
127.0.0.1:6379> sadd myset "hello" # set集合中添加元素
(integer) 1
127.0.0.1:6379> sadd myset "khazix"
(integer) 1
127.0.0.1:6379> smembers myset # 查看指定set的所有值
1) "khazix"
2) "hello"
127.0.0.1:6379> sismember myset hello # 判断某一个值是不是在set集合中
(integer) 1
127.0.0.1:6379> sismember myset world
(integer) 0
127.0.0.1:6379> scard myset # 获取set集合中的内容元素个数!
(integer) 2
*****************************************************
127.0.0.1:6379> srem myset "hello" # 移除set集合中的指定元素
(integer) 1
127.0.0.1:6379> smembers myset
1) "khazix2"
2) "khazix"
127.0.0.1:6379> scard myset
(integer) 2
*****************************************************
set 无序不重复集合 抽取随机值
127.0.0.1:6379> srandmember myset #随机抽选出一个元素
"khazix"
127.0.0.1:6379> srandmember myset
"khazix2"
127.0.0.1:6379> srandmember myset
"khazix"
127.0.0.1:6379> srandmember myset
"khazix2"
127.0.0.1:6379> srandmember myset
"khazix"
127.0.0.1:6379> srandmember myset
"khazix"
127.0.0.1:6379> srandmember myset
"khazix2"
127.0.0.1:6379> srandmember myset
"khazix"
127.0.0.1:6379> srandmember myset 2 # 随机抽选出指定个数元素
1) "khazix2"
2) "khazix"
127.0.0.1:6379> srandmember myset 10 # 当set集合中的元素个数少于指定数量时显示全部
1) "khazix2"
2) "khazix"
*****************************************************
127.0.0.1:6379> smembers myset
1) "khazix2"
2) "khazix"
127.0.0.1:6379> spop myset # 随机删除一些set集合中的元素
"khazix"
127.0.0.1:6379> smembers myset
1) "khazix2
*****************************************************
127.0.0.1:6379> sadd myset "hello"
(integer) 1
127.0.0.1:6379> sadd myset "world"
(integer) 1
127.0.0.1:6379> sadd myset "khazix"
(integer) 1
127.0.0.1:6379> sadd myset2 "set2"
(integer) 1
127.0.0.1:6379> smove myset myset2 "khazix" # 将一个指定的值移动到另外一个set集合
(integer) 1
127.0.0.1:6379> smembers myset
1) "world"
2) "hello"
127.0.0.1:6379> smembers myset2
1) "set2"
2) "khazix"
*****************************************************
微博、B站等,共同关注!(使用到了并集)
数字集合类:
- 差集
- 交集
- 并集
127.0.0.1:6379> sadd key1 a
(integer) 1
127.0.0.1:6379> sadd key1 b
(integer) 1
127.0.0.1:6379> sadd key1 c
(integer) 1
127.0.0.1:6379> sadd key2 c
(integer) 1
127.0.0.1:6379> sadd key2 d
(integer) 1
127.0.0.1:6379> sadd key2 e
(integer) 1
127.0.0.1:6379> smembers key2
1) "e"
2) "c"
3) "d"
127.0.0.1:6379> sdiff key1 key2 # key1集合中和key2集合中不同的元素
1) "b"
2) "a"
127.0.0.1:6379> sinter key1 key2 # key1集合中和key2集合中相同的元素
1) "c"
127.0.0.1:6379> sunion key1 key2 # key1集合中和key2集合中相交的元素
1) "b"
2) "a"
3) "c"
4) "e"
5) "d"
微博,A用户将所有关注的人放在一个set集合中!将它的粉丝也放在一个集合中!
共同关注,共同爱好,二度好友,推荐好友!(六度分割理论)
Hash(哈希)
Map集合,key-Map,这个值是一个map集合。本质和String类型没有太大的区别,还是一个简单的key-value!
set myhash field value
127.0.0.1:6379> hset myhash field1 khazix #set一个具体的key-value
(integer) 1
127.0.0.1:6379> hget myhash field1 # 获取一个字段值
"khazix"
127.0.0.1:6379> hmset myhash field1 hello field world # set多个key-value
OK
127.0.0.1:6379> hmget myhash field1 field # 获取多个字段值
1) "hello"
2) "world"
127.0.0.1:6379> hgetall myhash #获取全部的数据
1) "field1"
2) "hello"
3) "field"
4) "world"
127.0.0.1:6379> hdel myhash field1 # 删除hash指定key字段!对应的value值也就消失了!
(integer) 1
127.0.0.1:6379> hgetall myhash
1) "field"
2) "world"
*****************************************************
127.0.0.1:6379> hgetall myhash
1) "field1"
2) "hello"
3) "field2"
4) "world"
127.0.0.1:6379> hlen myhash #获取hash表的字段数量
(integer) 2
127.0.0.1:6379> hexists myhash field1 #判断hash中指定字段是否存在
(integer) 1
127.0.0.1:6379> hexists myhash field3
(integer) 0
*****************************************************
127.0.0.1:6379> hkeys myhash # 只获得所有field
1) "field1"
2) "field2"
127.0.0.1:6379> hvals myhash # 只获得所有value
1) "hello"
2) "world"
*****************************************************
127.0.0.1:6379> hset myhash field3 5 # 自增
(integer) 1
127.0.0.1:6379> hincrby myhash field3 2
(integer) 7
127.0.0.1:6379> hincrby myhash field3 -1
(integer) 6
*****************************************************
127.0.0.1:6379> hsetnx myhash field4 hello # 如果不存在则可用设置
(integer) 1
127.0.0.1:6379> hsetnx myhash field4 world # 如果存在则不能设置
(integer) 0
类似使用场景:
hash变更的数据 user name age,尤其是用户信息之类的,经常变动的信息!hash更适合于对象的存储,String更加适合字符串存储!
Zset(有序集合)
在set的基础上,增加了一个值
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
(integer) 1
127.0.0.1:6379> zadd myset 4 four 5 five #添加多个值
(integer) 2
*****************************************************
排序如何实现?
127.0.0.1:6379> zadd salary 2500 zwq
(integer) 1
127.0.0.1:6379> zadd salary 3000 syq
(integer) 1
127.0.0.1:6379> zadd salary 5000 zxd
(integer) 1
127.0.0.1:6379> zadd salary 500 xjj
(integer) 1
127.0.0.1:6379> zrangebyscore salary -inf +inf # 排序
1) "xjj"
2) "zwq"
3) "syq"
4) "zxd"
127.0.0.1:6379> zrevrange salary 0 -1 # 倒序
1) "zxd"
2) "syq"
3) "zwq"
4) "xjj"
127.0.0.1:6379> zrangebyscore salary -inf +inf withscores #带数据排序
1) "xjj"
2) "500"
3) "zwq"
4) "2500"
5) "syq"
6) "3000"
7) "zxd"
8) "5000"
127.0.0.1:6379> zrangebyscore salary -inf 3000 withscores #显示工资小于等于3000的员工
1) "xjj"
2) "500"
3) "zwq"
4) "2500"
5) "syq"
6) "3000
*****************************************************
移除rem中的元素
127.0.0.1:6379> zrange salary 0 -1
1) "xjj"
2) "zwq"
3) "syq"
4) "zxd"
127.0.0.1:6379> zrem salary xjj # 移除有序集合中的指定集合
(integer) 1
127.0.0.1:6379> zrange salary 0 -1
1) "zwq"
2) "syq"
3) "zxd"
127.0.0.1:6379> zcard salary # 获取有序集合中的个数
(integer) 3
*****************************************************
127.0.0.1:6379> zadd myset 1 hello
(integer) 1
127.0.0.1:6379> zadd myset 2 world 3 khazix
(integer) 2
127.0.0.1:6379> zcount myset 1 2 # 获取指定区间间的成员数量
(integer) 2
127.0.0.1:6379> zcount myset 1 3
(integer) 3
更多API查看官方文档
案例思路:set 排序 存储班级成绩表,工资表排序
普通消息 1 ,重要消息 2 。带权重进行判断
排行榜应用实现,取TOP N实现
5、三种特殊数据类型
geospatial地理位置
Redis的Geo在Redis3.2版本就推出了!这个功能可以推算地理位置的信息,两地之间的距离,方圆几里的人!
可以查询一些测试数据: http://www.jsons.cn/lngcodeinfo
getadd
# getadd 添加地理位置
# 规则: 两极无法直接添加,我们一般会下载城市数据,直接通过java程序一次性导入
# 参数 key 值(纬度)(精度)
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
(integer) 1
127.0.0.1:6379> geoadd china:city 120.16 30.24 hangzhou 108.96 34.26 xian
(integer) 2
geopos
127.0.0.1:6379> geopos china:city beijing # 获取指定的城市的经度和纬度
1) 1) "116.39999896287918091"
2) "39.90000009167092543"
127.0.0.1:6379> geopos china:city beijing chongqing
1) 1) "116.39999896287918091"
2) "39.90000009167092543"
2) 1) "106.49999767541885376"
2) "29.52999957900659211"
127.0.0.1:6379> geopos china:city beijin
geodist
两人之间的距离!
单位:
- m表示单位为米
- km表示单位为千米
- mi表示单位为英里
- ft表示单位为英尺
127.0.0.1:6379> geodist china:city beijing shanghai # 查看上海到北京的距离,默认单位为m
"1067378.7564"
127.0.0.1:6379> geodist china:city beijing shanghai km # 查看上海到北京的直线距离
"1067.3788"
127.0.0.1:6379> geodist china:city beijing hangzhou km # 查看杭州到北京的直线距离
"1127.3378"
georadius
我附近的人(获得所有附近的人的地址,定位),通过半径来查询
获得指定数量的人
所有的数据应该都录入:china:city,才会更加准确
127.0.0.1:6379> georadius china:city 110 30 1000 km # 以100,30这个经纬度为中心,寻找方圆1000km内的城市
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 withdist withcoord count 2 # 筛选出指定的结果
1) 1) "chongqing"
2) "341.9374"
3) 1) "106.49999767541885376"
2) "29.52999957900659211"
2) 1) "xian"
2) "483.8340"
3) 1) "108.96000176668167114"
2) "34.25999964418929977"
georadiusbymember
# 找出位于指定元素周围的其他元素
127.0.0.1:6379> georadiusbymember china:city beijing 1000 km
1) "beijing"
2) "xian"
geohash
该命令将返回11个字符的Geohash字符串
# 将二维的经纬度转换为一维的字符串
127.0.0.1:6379> geohash china:city beijing chongqing
1) "wx4fbxxfke0"
2) "wm5xzrybty0"
geo底层的实现原理其实就是Zset!我们可以使用Zset命令来操作geo
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
简介
Redis2.8.9版本就更新了Hyperloglog数据结构!
Redis Hyperloglog 基数统计的算法
优点:占用的内存是固定的,2^64不同的元素的技术,只需要废12KB内存!如果要从内存角度来比较的话Hyperloglog首选!
网页的UV(一个人访问一个网站多次,但是还是算作一个人!)
传统的方式,set保存用户的id,然后就可以统计set中的元素数量作为标准判断
0.81%错误率!统计UV任务,可以忽略不计!
测试使用
127.0.0.1:6379> PFadd mykey a b c d e f g h i j # 创建第一组元素 mykey
(integer) 1
127.0.0.1:6379> pfcount mykey # 统计mykey元素的基数数量
(integer) 10
127.0.0.1:6379> PFadd mykey2 i j z x c v b n m # 创建第二组元素 mykey2
(integer) 1
127.0.0.1:6379> pfcount mykey2 # 统计mykey2元素的基数数量
(integer) 9
127.0.0.1:6379> pfmerge mykey3 mykey mykey2 # 合并两组 mykey mykey2 => mykey3 并集
OK
127.0.0.1:6379> pfcount mykey3 # 查看并集数量
(integer) 15
如果允许容错,那么一定可以使用Hyperloglog!
如果不允许容错,就使用set或者自己的数据类型即可
Bitmaps
位存储
两个状态的都可以使用bitmaps,例如统计用户信息:活跃、不活跃,登录、未登录,打卡等
Bitmaps位图,数据结构!都是操作二进制位来进行记录,就只有0和1两个状态!
测试
使用bitmaps来记录周一到周日的打卡!
周一:1 周二:0 周三 0…
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 1
(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 1
(integer) 0
查看某一天是否打卡
127.0.0.1:6379> getbit sign 3 # 查看第三天是否打卡
(integer) 1
127.0.0.1:6379> getbit sign 6
(integer) 1
127.0.0.1:6379> bitcount sign # 统计这周的打卡记录
(integer) 4
6、事务
Redis 事务本质:一组命令的集合!(一起执行)所有的命令都会被序列化,在事务执行过程中,会按照顺序执行!
一次性、顺序性、排他性!执行一系列的命令
Redis事务没有隔离级别的概念,只有发起执行命令的时候才会执行
Redis单条命令式保存原子性的,但是事务不保证原子性!
Redis的事务:
- 开启事务(multi)
- 命令入队
- 执行事务
正常执行事务!
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
取消事务
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 key4 #事务队列中命令都不会被执行
(nil)
编译型异常
代码有错误!命令有错!事务中所有的命令都不会被执行。
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)> exec # 执行事务报错
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get k4 # 所有的命令都不会被执行
(nil)
运行时异常
如果事务队列中存在语法性,那么执行命令的时候,其他命令式可以正常执行,错误命令抛出异常
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)> exec
1) (error) ERR value is not an integer or out of range # 虽然第一条命令报错了,但是依旧执行成功了
2) OK
3) OK
127.0.0.1:6379> get k2
"v2"
127.0.0.1:6379> get k3
"v3"
监控! Watch
乐观锁:
- 获取version
- 更新时比较version
Redis测监视测试
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 # 监视 money 对象
OK
127.0.0.1:6379> clear
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的乐观锁操作
127.0.0.1:6379> watch money # 监视 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 # 获取最新的值,再次监视,select version
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> decrby money 30
QUEUED
127.0.0.1:6379(TX)> incrby out 30
QUEUED
127.0.0.1:6379(TX)> exec # 比对监视的值是否发生了变化,如果没有发生变化,那么可以执行成功,如果变了就执行失败!
1) (integer) 970
2) (integer) 50
7、Jedis
使用java来操作Redis
Jedis是Redis官方推荐的java连接开发工具!使用java操作Redis中间件!如果你要使用java操作redis,那么一定要对Jedis十分的熟悉!
测试
1、导入对应的依赖
<!--导入Jedis的包-->
<dependencies>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.2.0</version>
</dependency>
<!--导入fastjson-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.62</version>
</dependency>
</dependencies>
2、编码测试:
- 连接数据库
- 操作命令
- 断开连接
import redis.clients.jedis.Jedis;
public class TestPing {
public static void main(String[] args) {
// 1、new Jedis 对象即可
Jedis jedis = new Jedis(URL,6379); // URL就是远程服务器的ip
//Jedis所有的命令就是之前学习的所有指令
String ping = jedis.ping();
System.out.println(ping);
}
}
结果为:PONG
8、SpringBoot整合
在SpringBoot2.x之后,原来使用的Jedis被替换为了lettuce
jedis:采用的直连,多个线程操作的话,是不安全的,如果想要避免不安全的,使用Jedis pool连接池。
1、导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2、配置连接
spring.redis.host=47.98.60.130
spring.redis.port=6379
3、测试
@SpringBootTest
class DemoApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
void contextLoads() {
redisTemplate.opsForValue().set("name","khazix");
redisTemplate.opsForValue().set("hello","编程");
System.out.println(redisTemplate.opsForValue().get("name"));
System.out.println(redisTemplate.opsForValue().get("hello"));
}
}
在SpringBoot中的测试代码也和Redis中的代码差不多。
在远程服务器中获取值:
127.0.0.1:6379> keys *
1) "\xac\xed\x00\x05t\x00\x04name"
2) "\xac\xed\x00\x05t\x00\x05hello"
我们可以编写自己的RedisTemplate
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.net.UnknownHostException;
@Configuration
public class RedisConfig {
// 编写我们自己的redisTemplate
@Bean
@SuppressWarnings("all")
public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory factory)
throws UnknownHostException {
// 我们为了开发方便,一般直接使用<String,Object>
RedisTemplate<String,Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
// 序列化配置
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
// String的序列化
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// key采用String的序列化方式
template.setKeySerializer(stringRedisSerializer);
// hash的key也采用String的序列化方式
template.setHashKeySerializer(stringRedisSerializer);
// value序列化方式采用jackson
template.setValueSerializer(jackson2JsonRedisSerializer);
// hash的value序列化方式采用jackson
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}