Redis学习笔记
Nosal概述
为什么要用Nosal
我们现在处于大数据时代;
大数据一般的数据库无法进行分析处理了,2006年 Hadoop
1、单机Mysql的年代
90年代,一个基本的网站,访问量不会太大,单个数据库完全足够了。
更多的去使用静态页面 HTML 服务器没有太大的压力
这种情况下:整个网站的瓶颈:
- 数据量如果太大,一个机器放不下了。
- 数据的索引(B+Tree) 300w就要建立索引了 ,一个机器内存也放不下。
- 访问量(读写混合),一个服务器承受不了。
只要开始出现三种情况之一,那么就要考虑升级了
2、Memcached(缓存) + Mysql + 垂直拆分(读写分离)
网站80%的情况都是在读,每次都要去查询数据库的话就十分的麻烦! 所以我们希望减轻数据的压力,我们可以使用缓存来保证效率!
发展过程:优化Mysql底层结构和索引----->文件(IO)----->Memcached(当时最热门的技术!)
3、分库分表 + 水平拆分 + Mysql集群
本质:数据库(读,写)
早些年:MyISAM: 表锁 ,十分影响效率,高并发下就会出现严重的锁的问题。
早些年:Innodb:行级锁
User 表 字段拆分 (分表)
订单、用户、支付 放在不同的数据库。(分库)
4、如今最近的年代
Mysql 等关系型数据库就不够用了,数据量很多,变化很快
刷微信,文章浏览量,文章发布,用户刷浏览量,浏览量在缓存+1,设定一定的时间,将数据持久化到本地。
目前一个基本的互联网项目
企业防火墙,路由网关
为什么要用NOSQL!
用户的个人信息,社交网络,地理位置。用户自己产生的数据,用户日志等等爆发式增长
关系型数据库无法处理了,这时候我们就需要使用NOSQL数据库了,NoSQL可以很好的处理以上的问题。
什么是NoSQL
NoSQL
NoSQL=Not Only SQL (不仅仅是SQL)
关系型数据库: 表格 ------ Row-Col
泛指非关系型数据库,随着Web2.0互联网的诞生,传统的关系型数据库很难对付web2.0时代,尤其是超大规模的高并发的社区!暴露出来很多难以克服的问题,NoSQL在当今大数据环境下发展的十分迅速,Redis 是发展最快的。
很多的数据类型 如:用户的个人信息,社交网络,地理位置。这些数据类型的存储不需要一个固定的格式!不需要多余的操作就可以横向扩展的。 Map<String,Object> 使用键值对来控制!
NoSQL特点
- 方便扩展(数据之间没有关系,很好扩展)
- 大数据量高性能(Redis 一秒写8w次,读取11w次,NoSQL缓存,是一种细粒度的缓存,性能会比较高)
- 数据类型是多样性的(不需要事先设计数据库,随取随用,如果是数据量十分大的表,很多人就无法设计了)
传统的 RDBMS
- 结构化组织
- SQL
- 数据和关系都存在单独的表中
- 数据操作语言 数据定义语言
- 严格的一致性
- 基础的事务
- ....
NoSQL
- 不仅仅是数据
- 没有固定的查询语言
- 键值对存储 列存储 文档存储 图像数据库(社交关系)
- 最终一致性
- CAP定理 和 BASE (异地多活 保证服务器不会宕机)
- 高性能 高可用 高可拓
- ....
了解: 3V+3高
大数据时代的3V:主要是描述问题的
-
海量:Volume
-
多样:Variety
-
实时:Velocity
大数据时代的3高:主要是对程序的
-
高并发
-
高可拓 (随时可水平拆分,机器不够了,可以扩展机器来解决)
-
高性能(保证用户体验和性能)
真正在公司中的实践: NoSQL + RDBMS 一起使用才是最强的。
阿里巴巴演进分析
#1、商品的基本信息
名称、价格、商家信息:
关系型数据库就可以解决了! MySQL/Oracle (淘宝早年就去IOE了)
淘宝内部的Mysql 不是大家用的Mysql
#2、商品的描述/评论(文字比较多)
文档型数据库中,MongoDB
#3、图片
分布式文件系统 FastDFS
- 淘宝自己的 TFS
- Google GFS
- Hadoop HDFS
- 阿里云的 OSS
#4、商品的关键字(搜索)
- 搜索引擎 solr elasticsearch
- Isearch: 多隆
#5、热门的波段信息
- 内存数据库
- Redis Tair Memache ...
#6、商品的交易,外部的支付接口
- 三方应用
一个网页背后的技术是很复杂的
大型互联网应用问题:
- 数据类型太多了
- 数据源繁多,经常重构!
- 数据要改造,大面积改造 ? mysql
解决问题:
NoSQL 的四大分类
KV键值对:
- 新浪:Redis
- 美团:Redis + Tair
- 阿里、百度:Redis + Tair + Memecache
文档型数据库(bson格式和json一样):
- MongoDB
- MongoDB 是一个基于分布式文件存储的数据库,c++编写,主要用来处理大量的文档
- MongoDB是一个介于关系型数据库和非关系型数据库中间的产品!MongoDB是非关系型数据库中功能最丰富,最像关系型数据库的。
- ConthDB
列存储数据库
- Hbase
- 分布式文件系统
图关系数据库
- 他不是村图形,放的是关系,比如:朋友圈社交网络,广告推荐
- Neo4j,InfoGrid
他们的差别
Redis入门
Redis是什么?
Redis(Remote Dictionary Server)远程字典服务;是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。
Redis能干嘛?
- 内存存储,持久化,内存中是断电即失,所以说持久化很重要(RDB,AOF)
- 效率高,可以用于高速缓存
- 发布订阅系统
- 地图信息分析
- 计时器、计数器(浏览量)
- .....
特性
-
多样的数据类型
-
持久化
-
集群
-
事务
.....
基础知识
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[3]> keys *
(empty list or set)
127.0.0.1:6379[3]> set name shizuwei
OK
127.0.0.1:6379[3]> keys *
1) "name"
127.0.0.1:6379[3]> flushdb #清空当前库 flushall 清空所有的数据库
OK
127.0.0.1:6379[3]> keys *
(empty list or set)
127.0.0.1:6379[3]>
Redis是单线程的
Redis是很快的,官方表示,Redis是基于内存操作的,CPU不是redis的性能瓶颈,Redis的瓶颈是根据服务器的内存和网络带宽,既然可以使用单线程来实现,就使用单线程了。
五大数据类型
Redis-Key
127.0.0.1:6379[3]> keys * #查看所有的key
(empty list or set)
127.0.0.1:6379[3]> set name shizuwei #设置一个key
OK
127.0.0.1:6379[3]> keys *
1) "name"
127.0.0.1:6379[3]> set age 1
OK
127.0.0.1:6379[3]> keys *
1) "age"
2) "name"
127.0.0.1:6379[3]> EXISTS name #判断key 是否存在
(integer) 1
127.0.0.1:6379[3]> EXISTS name1
(integer) 0
127.0.0.1:6379[3]> MOVE name 1 #移动key 到某一个数据库
(integer) 1
127.0.0.1:6379[3]> KEYS *
1) "age"
127.0.0.1:6379[3]> set name shizuwei
OK
127.0.0.1:6379[3]> KEYS *
1) "age"
2) "name"
127.0.0.1:6379[3]> get name
"shizuwei"
127.0.0.1:6379[3]> EXPIRE name 10 #设置过期实践
(integer) 1
127.0.0.1:6379[3]> TTL name #查看key的剩余时间
(integer) 2
127.0.0.1:6379[3]> TTL name
(integer) 1
127.0.0.1:6379[3]> TTL name
(integer) 0
127.0.0.1:6379[3]> TTL name
(integer) -2
127.0.0.1:6379[3]> GET name
(nil)
127.0.0.1:6379[3]> TYPE age #查看key的类型
string
127.0.0.1:6379[3]>
后面如果遇到不会的命令,官网有帮助文档
String(字符串)
#############################
127.0.0.1:6379> set key1 v1
OK
127.0.0.1:6379> get key1
"v1"
127.0.0.1:6379> APPEND key1 hello #追加字符串
(integer) 7
127.0.0.1:6379> get key1
"v1hello"
127.0.0.1:6379> STRLEN key1
(integer) 7
127.0.0.1:6379> get key1
"v1hello"
#############################
127.0.0.1:6379> set views 0
OK
127.0.0.1:6379> INCR views
(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
(integer) 1
127.0.0.1:6379> get views
"1"
127.0.0.1:6379> INCRBY views 10
(integer) 11
127.0.0.1:6379> get views
"11"
127.0.0.1:6379>
#############################
#符串范围 range
127.0.0.1:6379> get key1
"helloShizuwei"
127.0.0.1:6379> GETRANGE key1 1 4
"ello"
127.0.0.1:6379> GETRANGE key1 0 -1
"helloShizuwei"
127.0.0.1:6379>
#替换
127.0.0.1:6379> get key1
"helloShizuwei"
127.0.0.1:6379> SETRANGE key1 2 xx
(integer) 13
127.0.0.1:6379> get key1
"hexxoShizuwei"
127.0.0.1:6379>
#############################
#setex(set with expire) #设置过期时间
127.0.0.1:6379> get key1
"helloShizuwei"
127.0.0.1:6379> SETRANGE key1 2 xx
(integer) 13
127.0.0.1:6379> get key1
"hexxoShizuwei"
127.0.0.1:6379>
#setnx(set if not exist) #不存在 再设置 (在分布式锁中会常常使用)
127.0.0.1:6379> SETNX name redis #如果name 不存在,创建name
(integer) 1
127.0.0.1:6379> KEYS *
1) "name"
127.0.0.1:6379> SETNX name mogodb #如果name 存在,创建失败
(integer) 0
127.0.0.1:6379> GET name
"redis"
127.0.0.1:6379>
#############################
#mset #mget
127.0.0.1:6379> KEYS *
(empty list or set)
127.0.0.1:6379> MSET name shizuwei age 19 desciption kukudi
OK
127.0.0.1:6379> KEYS *
1) "age"
2) "desciption"
3) "name"
127.0.0.1:6379> MGET name age description
1) "shizuwei"
2) "19"
3) "kukudi"
127.0.0.1:6379> MSETNX name awei school yangzteUniversity #msetnx 是一个原子性的操作,要么一起成功,要么一起失败!
(integer) 0
127.0.0.1:6379> MGET name age description school
1) "shizuwei"
2) "19"
3) "kukudi"
4) (nil)
#对象
set user:1{name:shizuwei,age:19} #设置一个user:1 对象 值为json字符串来保存一个对象!
#这里的key 是一个巧妙的设计: user:{id}:{field} 如此设计在Redis中是完全OK的
127.0.0.1:6379> MSET user:1:name shizuwei user:1:age 19
OK
127.0.0.1:6379> MGET user:1:name user:1:age
1) "shizuwei"
2) "19"
127.0.0.1:6379>
#############################
#getset 先get 在set
127.0.0.1:6379> GETSET db redis
(nil)
127.0.0.1:6379> GET db
"redis"
127.0.0.1:6379> GETSET db mogodb
"redis"
127.0.0.1:6379> get db
"mogodb"
数据结构是相同的!
String类似的使用场景:value除了是我们的字符串还可以是我们的数字!
- 计数器
- 统计多单位的数量 uid:952564468:follow 0
- 粉丝数
- 对象缓存存储
List
基本的数据类型,列表
在Redis里面,可以把list当作 栈 队列 阻塞队列
所有的list命令都是l开头的,Redis不区分大小写
#############################
127.0.0.1:6379> KEYS *
(empty list or set)
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"
127.0.0.1:6379>
#############################
#pop lpop rpop
127.0.0.1:6379> LRANGE list 0 -1
1) "three"
2) "two"
3) "one"
4) "four"
127.0.0.1:6379> LPOP list #移除list的第一个元素
"three"
127.0.0.1:6379> RPOP list #一处list的最后一个元素
"four"
127.0.0.1:6379> LRANGE list 0 -1
1) "two"
2) "one"
127.0.0.1:6379>
#############################
#Lindex 通过下表获取list中的某一个值
127.0.0.1:6379> LINDEX list 0
"two"
127.0.0.1:6379>
#############################
#Llen 获取list 长度
127.0.0.1:6379> LLEN list
(integer) 2
127.0.0.1:6379>
#############################
#移除指定的值
#取关 uid
127.0.0.1:6379> LRANGE list 0 -1
1) "three"
2) "three"
3) "two"
4) "one"
127.0.0.1:6379> LREM list 2 three
(integer) 2
127.0.0.1:6379> LRANGE list 0 -1
1) "two"
2) "one"
127.0.0.1:6379>
#############################
#trim
127.0.0.1:6379> RPUSH list "hello1"
(integer) 1
127.0.0.1:6379> RPUSH list "hello2"
(integer) 2
127.0.0.1:6379> RPUSH list "hello3"
(integer) 3
127.0.0.1:6379> RPUSH list "hello4"
(integer) 4
127.0.0.1:6379> RPUSH list "hello5"
(integer) 5
127.0.0.1:6379> LRANGE list 0 -1
1) "hello1"
2) "hello2"
3) "hello3"
4) "hello4"
5) "hello5"
127.0.0.1:6379> LTRIM list 1 2
OK
127.0.0.1:6379> LRANGE list 0 -1
1) "hello2"
2) "hello3"
127.0.0.1:6379>
#############################
#rpoplpush 移除列表的最后一个元素,将他移动到新的列表中!
127.0.0.1:6379> RPUSH mylist hello1
(integer) 1
127.0.0.1:6379> RPUSH mylist hello2
(integer) 2
127.0.0.1:6379> RPUSH mylist hello
(integer) 3
127.0.0.1:6379> RPOPLPUSH mylist otherList #移除列表的最后一个元素,将他移动到新的列表中
"hello"
127.0.0.1:6379> LRANGE mylist 0 -1 #查看原来的列表
1) "hello1"
2) "hello2"
127.0.0.1:6379> LRANGE otherList 0 -1 #查看目标列表中,确实存在
1) "hello"
127.0.0.1:6379>
#############################
#lset 将列表中指定下标的值替换为另外的值 更新操作
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 -1
1) "value"
127.0.0.1:6379> LSET list 0 item
OK
127.0.0.1:6379> LRANGE list 0 -1
1) "item"
127.0.0.1:6379> LSET list 1 other
(error) ERR index out of range
127.0.0.1:6379>
#############################
#linsert #将某个具体的value 插入到列把你中某个元素的前面或者后面
127.0.0.1:6379> LPUSH list hello
(integer) 1
127.0.0.1:6379> LPUSH list word
(integer) 2
127.0.0.1:6379> LINSERT list before word shizuwei
(integer) 3
127.0.0.1:6379> LRANGE list 0 -1
1) "shizuwei"
2) "word"
3) "hello"
127.0.0.1:6379> LINSERT list after word new
(integer) 4
127.0.0.1:6379> LRANGE list 0 -1
1) "shizuwei"
2) "word"
3) "new"
4) "hello"
127.0.0.1:6379>
小结
- 它实际是一个链表,before Node after ,left ,right 都可以插入值
- 如果key 不存在,创建新的链表
- 如果key存在,新增内容
- 如果移除了所有的值,空链表,也代表不存在
- 在两边插入,或者改动值 效率最高!中间元素----相对来说效率低一点
Set(集合)
set中的值是不能重复的
#############################
127.0.0.1:6379> SADD set hello
(integer) 1
127.0.0.1:6379> SADD set shizuwei
(integer) 1
127.0.0.1:6379> SADD set dadawei
(integer) 1
127.0.0.1:6379> SMEMBERS set
1) "shizuwei"
2) "dadawei"
3) "hello"
127.0.0.1:6379> SISMEMBER set hello
(integer) 1
127.0.0.1:6379> SISMEMBER set word
(integer) 0
127.0.0.1:6379> SCARD set #获取set 集合中的个数值
(integer) 3
127.0.0.1:6379> SMEMBERS set
1) "shizuwei"
2) "dadawei"
3) "hello"
127.0.0.1:6379> SREM set hello #移除set中的元素
(integer) 1
127.0.0.1:6379> SMEMBERS set
1) "shizuwei"
2) "dadawei"
127.0.0.1:6379>
#############################
#set 无序不重复 ,随机抽取
127.0.0.1:6379> SMEMBERS set
1) "calinda"
2) "shizuwei"
3) "dadawei"
4) "yifan"
127.0.0.1:6379> SRANDMEMBER set
"dadawei"
127.0.0.1:6379> SRANDMEMBER set
"yifan"
127.0.0.1:6379> SRANDMEMBER set
"calinda"
127.0.0.1:6379> SRANDMEMBER set
"calinda"
127.0.0.1:6379>
#############################
#删除 指定的key 随机删除key
127.0.0.1:6379> SMEMBERS set
1) "calinda"
2) "shizuwei"
3) "dadawei"
4) "yifan"
127.0.0.1:6379> SPOP set #随机删除set集合中的元素
"yifan"
127.0.0.1:6379> SPOP set
"calinda"
127.0.0.1:6379> SMEMBERS set
1) "shizuwei"
2) "dadawei"
127.0.0.1:6379>
#############################
#将一个指定的值,移动到另外一个set集合
127.0.0.1:6379> SADD set hello
(integer) 1
127.0.0.1:6379> SADD set hello1
(integer) 1
127.0.0.1:6379> SADD set hello2
(integer) 1
127.0.0.1:6379> SADD set hello3
(integer) 1
127.0.0.1:6379> SMOVE set set1 hello3
(integer) 1
127.0.0.1:6379> SMEMBERS set
1) "hello2"
2) "hello1"
3) "hello"
127.0.0.1:6379> SMEMBERS set1
1) "hello3"
127.0.0.1:6379>
#############################
微博,B站,共同关注! (交集)
数字集合类:
- 差集
-交集
-并集
127.0.0.1:6379> SADD k1 a
(integer) 1
127.0.0.1:6379> SADD k1 b
(integer) 1
127.0.0.1:6379> SADD k1 c
(integer) 1
127.0.0.1:6379> SADD k2 c
(integer) 1
127.0.0.1:6379> SADD k2 d
(integer) 1
127.0.0.1:6379> SADD k2 e
(integer) 1
127.0.0.1:6379> SDIFF k1 k2
1) "a"
2) "b"
127.0.0.1:6379> SINTER k1 k2
1) "c"
127.0.0.1:6379> SUNION k1 k2
1) "a"
2) "c"
3) "b"
4) "d"
5) "e"
127.0.0.1:6379>
微博,A用户将所有关注的人放在一个set集合中! 将他的粉丝也放在一个集合中。
共同关注,共同爱好,二度好友,推荐好友!(六度分割理论)
Hash(哈希)
Map集合,key-Map集合 key---(key---map)value 是一个map集合
127.0.0.1:6379> HSET stu1 sName shizuwei
(integer) 1
127.0.0.1:6379> HSET stu1 sAge 19
(integer) 1
127.0.0.1:6379> HSET stu1 sSex 男
(integer) 1
127.0.0.1:6379> HGET stu1 sName
"shizuwei"
127.0.0.1:6379> HMSET stu1 sCando dosome sDec balabala
OK
127.0.0.1:6379> HMGET stu1 sName sage sdec
1) "shizuwei"
2) (nil)
3) (nil)
127.0.0.1:6379> HMGET stu1 sName sAge sDec
1) "shizuwei"
2) "19"
3) "balabala"
127.0.0.1:6379> HGETALL stu1
1) "sName"
2) "shizuwei"
3) "sAge"
4) "19"
5) "sSex"
6) "\xe7\x94\xb7"
7) "sCando"
8) "dosome"
9) "sDec"
10) "balabala"
127.0.0.1:6379> HDEL stu1 sSex
(integer) 1
127.0.0.1:6379> HGETALL stu1
1) "sName"
2) "shizuwei"
3) "sAge"
4) "19"
5) "sCando"
6) "dosome"
7) "sDec"
8) "balabala"
127.0.0.1:6379>
#############################
#获取一个hash 中有多少键值对 hash的长度 HLEN
127.0.0.1:6379> HGETALL stu1
1) "sName"
2) "shizuwei"
3) "sAge"
4) "19"
5) "sCando"
6) "dosome"
7) "sDec"
8) "balabala"
127.0.0.1:6379> HLEN stu1
(integer) 4
127.0.0.1:6379>
#############################
#判断hash中是否存在 key HEXISTS
127.0.0.1:6379> HEXISTS stu1 sName
(integer) 1
127.0.0.1:6379> HEXISTS stu1 sSex
(integer) 0
127.0.0.1:6379>
#############################
#只获取key 所对应的 filed
#只获取filed 所对应的value
127.0.0.1:6379> HKEYS stu1
1) "sName"
2) "sAge"
3) "sCando"
4) "sDec"
127.0.0.1:6379> HVALS stu1
1) "shizuwei"
2) "19"
3) "dosome"
4) "balabala"
127.0.0.1:6379>
#############################
127.0.0.1:6379> HSET stu age 19
(integer) 1
127.0.0.1:6379> HINCRBY stu age 1
(integer) 20
127.0.0.1:6379> HINCRBY stu age -1
(integer) 19
127.0.0.1:6379> HSETNX stu name shizuwei #如果不存在,则设置
(integer) 1
127.0.0.1:6379> HSETNX stu name calinda #如果存在,则不设置
(integer) 0
127.0.0.1:6379> HKEYS stu
1) "age"
2) "name"
127.0.0.1:6379>
hash变更的数据 user name-age,尤其是用户信息之类的保存,经常变动的信息
更适合对象存储
Zset(有序集合)
在set基础上增加了一个值,set k1 v1, zset k1 score1 v1 加一些标识
127.0.0.1:6379> ZADD set 1 one
(integer) 1
127.0.0.1:6379> ZADD set 2 two 3 three
(integer) 2
127.0.0.1:6379> ZRANGE set 0 -1
1) "one"
2) "two"
3) "three"
127.0.0.1:6379>
#############################
#排序如何实现
127.0.0.1:6379> ZADD salary 500 awei #500 即为 score
(integer) 1
127.0.0.1:6379> ZADD salary 2500 calinda
(integer) 1
127.0.0.1:6379> ZADD salary 5000 zhangsan
(integer) 1
127.0.0.1:6379> ZRANGE salary 0 -1
1) "awei"
2) "calinda"
3) "zhangsan"
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf #从负无穷到正无穷 从小到大
1) "awei"
2) "calinda"
3) "zhangsan"
127.0.0.1:6379> ZREVRANGE salary 0 -1 #从大到小排序
1) "calinda"
2) "awei"
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf withscores #输出带上scores
1) "awei"
2) "500"
3) "calinda"
4) "2500"
5) "zhangsan"
6) "5000"
127.0.0.1:6379> ZRANGEBYSCORE salary -inf 2500
1) "awei"
2) "calinda"
127.0.0.1:6379>
#############################
#移除一个元素 zrem
127.0.0.1:6379> ZRANGE salary 0 -1
1) "awei"
2) "calinda"
3) "zhangsan"
127.0.0.1:6379> ZREM salary zhangsan
(integer) 1
127.0.0.1:6379> ZRANGE salary 0 -1
1) "awei"
2) "calinda"
127.0.0.1:6379> ZCARD salary #获取有序集合中的个数
(integer) 2
127.0.0.1:6379>
#############################
#zcount 获取指定区间的数量
127.0.0.1:6379> ZADD myset 1 hello 2 world 3 calinda
(integer) 3
127.0.0.1:6379> ZCOUNT myset 1 3
(integer) 3
127.0.0.1:6379> ZCOUNT myset 1 2
(integer) 2
127.0.0.1:6379>
案例思路: set +排序=zset
存储班级成绩表,工资表排序
普通消息设置为1,重要消息设置为2 (带权重进行判断)
排行榜应用实现,取Top N测试
三种特殊数据类型
geospatial(地理位置)
朋友的定位,附近的人,打车距离计算???
Redis 的Geo在Redis3.2版本就推出了 !可以推算地理位置信息,两地之间的距离,方圆几里的人。
只有六个命令:
GEOADD
官方文档:https://www.redis.net.cn/order/3685.html
#geoadd 添加地理位置
#规则: 两级无法直接添加(北极,南极) 我们一般会下载城市数据,通过java程序一次性导入
#有效的经度从-180度到180度。
#有效的纬度从-85.05112878度到85.05112878度。
#当坐标位置超出上述指定范围时,该命令将会返回一个错误。
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 chongqin
(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
127.0.0.1:6379>
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 chongqin
1) 1) "106.49999767541885376"
2) "29.52999957900659211"
127.0.0.1:6379>
GEODIST
两人之间的距离!
单位:
- m 表示单位为米。
- km 表示单位为千米。
- mi 表示单位为英里。
- ft 表示单位为英尺。
如果用户没有显式地指定单位参数, 那么 GEODIST
默认使用米作为单位。
GEODIST
命令在计算距离时会假设地球为完美的球形, 在极限情况下, 这一假设最大会造成 0.5% 的误差。
返回值
计算出的距离会以双精度浮点数的形式被返回。 如果给定的位置元素不存在, 那么命令返回空值。
例子
127.0.0.1:6379> GEODIST china:city beijing chongqin
"1464070.8051"
127.0.0.1:6379> GEODIST china:city beijing chongqin km
"1464.0708"
127.0.0.1:6379>
GEORADIUS
我附近的人? (获得所有附近的人的地址,定位)通过半径来查询!
以给定的经纬度为中心, 返回键包含的位置元素当中, 与中心的距离不超过给定最大距离的所有位置元素。
范围可以使用以下其中一个单位:
- m 表示单位为米。
- km 表示单位为千米。
- mi 表示单位为英里。
- ft 表示单位为英尺。
在给定以下可选项时, 命令会返回额外的信息:
WITHDIST
: 在返回位置元素的同时, 将位置元素与中心之间的距离也一并返回。 距离的单位和用户给定的范围单位保持一致。WITHCOORD
: 将位置元素的经度和维度也一并返回。WITHHASH
: 以 52 位有符号整数的形式, 返回位置元素经过原始 geohash 编码的有序集合分值。 这个选项主要用于底层应用或者调试, 实际中的作用并不大。
命令默认返回未排序的位置元素。 通过以下两个参数, 用户可以指定被返回位置元素的排序方式:
ASC
: 根据中心的位置, 按照从近到远的方式返回位置元素。DESC
: 根据中心的位置, 按照从远到近的方式返回位置元素。
在默认情况下, GEORADIUS 命令会返回所有匹配的位置元素。 虽然用户可以使用 COUNT <count>
选项去获取前 N 个匹配元素, 但是因为命令在内部可能会需要对所有被匹配的元素进行处理, 所以在对一个非常大的区域进行搜索时, 即使只使用 COUNT
选项去获取少量元素, 命令的执行速度也可能会非常慢。 但是从另一方面来说, 使用 COUNT
选项去减少需要返回的元素数量, 对于减少带宽来说仍然是非常有用的。
返回值
- 在没有给定任何
WITH
选项的情况下, 命令只会返回一个像 [“New York”,”Milan”,”Paris”] 这样的线性(linear)列表。 - 在指定了
WITHCOORD
、WITHDIST
、WITHHASH
等选项的情况下, 命令返回一个二层嵌套数组, 内层的每个子数组就表示一个元素。
在返回嵌套数组时, 子数组的第一个元素总是位置元素的名字。 至于额外的信息, 则会作为子数组的后续元素, 按照以下顺序被返回:
- 以浮点数格式返回的中心与位置元素之间的距离, 单位与用户指定范围时的单位一致。
- geohash 整数。
- 由两个元素组成的坐标,分别为经度和纬度。
例子
127.0.0.1:6379> GEOPOS china:hangzhou
(empty list or set)
127.0.0.1:6379> GEOPOS china:city hangzhou
1) 1) "120.1600000262260437"
2) "30.2400003229490224"
127.0.0.1:6379> GEOPOS china:city shanghai
1) 1) "121.47000163793563843"
2) "31.22999903975783553"
127.0.0.1:6379> GEODIST china:city hangzhou shanghai km
"166.7613"
127.0.0.1:6379> GEORADIUS china:city 120 30 200 km withcoord withdist #以120 30 这个经纬度为中心,寻找方圆200km内的城市
1) 1) "hangzhou"
2) "30.8146"
3) 1) "120.1600000262260437"
2) "30.2400003229490224"
2) 1) "shanghai"
2) "196.2512"
3) 1) "121.47000163793563843"
2) "31.22999903975783553"
127.0.0.1:6379> GEORADIUS china:city 120 30 200 km withcoord withdist count 1 #获取指定的数目
1) 1) "hangzhou"
2) "30.8146"
3) 1) "120.1600000262260437"
2) "30.2400003229490224"
127.0.0.1:6379> GEORADIUS china:city 120 30 200 km withcoord withdist count 2
1) 1) "hangzhou"
2) "30.8146"
3) 1) "120.1600000262260437"
2) "30.2400003229490224"
2) 1) "shanghai"
2) "196.2512"
3) 1) "121.47000163793563843"
2) "31.22999903975783553"
127.0.0.1:6379> GEORADIUS china:city 120 30 200 km withcoord withdist count 3
1) 1) "hangzhou"
2) "30.8146"
3) 1) "120.1600000262260437"
2) "30.2400003229490224"
2) 1) "shanghai"
2) "196.2512"
3) 1) "121.47000163793563843"
2) "31.22999903975783553"
127.0.0.1:6379>
GEORADIUSBYMEMBER
这个命令和 GEORADIUS 命令一样, 都可以找出位于指定范围内的元素, 但是 GEORADIUSBYMEMBER
的中心点是由给定的位置元素决定的, 而不是像 GEORADIUS 那样, 使用输入的经度和纬度来决定中心点
指定成员的位置被用作查询的中心。
例子
127.0.0.1:6379> GEORADIUSBYMEMBER china:city hangzhou 200 km
1) "hangzhou"
2) "shanghai"
127.0.0.1:6379>
GEOHASH 命令—返回一个或多个位置元素的GEOHASH表示
该命令将返回11个字符的Geohash 字符串!
#将二维的经纬度转换为一维的字符串,两个字符串越像,地址越接近
127.0.0.1:6379> GEOHASH china:city beijing shanghai
1) "wx4fbxxfke0"
2) "wtw3sj5zbj0"
127.0.0.1:6379>
GEO 底层的实现原理其实就是Zset,我们可以使用zset命令来操作geo
127.0.0.1:6379> ZRANGE china:city 0 -1
1) "chongqin"
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) "chongqin"
2) "xian"
3) "shenzhen"
4) "hangzhou"
5) "shanghai"
127.0.0.1:6379>
hyperloglog
什么是基数?
A{1,3,5,7,8,7} B{1,3,5,7,8}
基数(不重复的元素的个数)=5 可以接受误差
简介
Redis2.8.9 版本就更新了 Hyperloglog 数据结构
Redis Hyperloglog 基数统计的算法!、
优点:占用的内存是固定的,2^64 不同的元素的技术,只需要 12kb的内存,如果要从内存角度来比较的话,Hyperloglog首选!
网页的UV (页面访问量)一个人访问一个网站多次,还是算作一个人
传统的方式 ,set(不允许重复) 保存用户的id ,就可以统计set中的元素数量作为标准判断!
这个方式如果保存大量的用户id,就会比较麻烦,占用内存,我们的目的为了计数,而不是保存用户id;
0.81% 错误率! 统计UV任务,可以忽略不计的!
测试使用
127.0.0.1:6379> PFADD mykey a b c d e f g
(integer) 1
127.0.0.1:6379> PFCOUNT mykey
(integer) 7
127.0.0.1:6379> PFADD mykey1 f g a k i l b j
(integer) 1
127.0.0.1:6379> PFCOUNT mykey1
(integer) 8
127.0.0.1:6379> PFMERGE mykey3 mykey mykey1
OK
127.0.0.1:6379> PFCOUNT mykey3
(integer) 11
127.0.0.1:6379>
bitmap
位存储
统计疫情感染人数: 0 1 0 1 1 0
统计用户信息,活跃,不活跃 ! 登录,未登录! 365打卡 两个状态的都可以使用Bitmap
Bitmap 位图,数据结构! 所有都是操作二进制位来进行记录,就只有0 和1 两个状态。
365天=365bit 1byte=8bit 46个byte左右
测试
使用bitmap 来记录周一到周日的打卡!
周一:1 周二:0 周三:1 。。。。。
查看某一天是否有打卡!
127.0.0.1:6379> GETBIT sign 3
(integer) 1
127.0.0.1:6379> GETBIT sign 2
(integer) 1
127.0.0.1:6379> GETBIT sign 1
(integer) 0
127.0.0.1:6379>
统计操作,统计打卡的天数
127.0.0.1:6379> BITCOUNT sign
(integer) 5
127.0.0.1:6379>
事务
事务:一组命令的集合! 一个事务中的所有命令都会被序列化,在事务执行的过程中,会按照顺序执行
一次性、顺序性、排他性! 执行一系列的命令
----- 队列set set set -----
Redis单条命令是保证原子性的,但是事务是不保证原子性的
Redis事务没有隔离级别的概念
所有的命令在事务中,并没有直接被执行! 只有发起执行命令的时候才会执行
RDBMS 要么同时成功,要么同时失败,原子性!
Redis的事务:
- 开启事务(Multi)
- 命令入队()
- 执行事务(exec)
锁:Redis可以事先乐观锁,watch
正常执行事务
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> set name shizuwei
QUEUED
127.0.0.1:6379> set age 19
QUEUED
127.0.0.1:6379> get name
QUEUED
127.0.0.1:6379> set dec qushishuai
QUEUED
127.0.0.1:6379> exec
1) OK
2) OK
3) "shizuwei"
4) OK
127.0.0.1:6379>
放弃事务
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> SET k1 v
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> get k2
QUEUED
127.0.0.1:6379> DISCARD
OK
127.0.0.1:6379> get k2
(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 k2
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> exec
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get k1
(nil)
127.0.0.1:6379>
运行时异常(1/0) 如果事务队列中存在语法性,那么执行命令的时候,其他命令是可以正常执行的,错误命令会抛出异常
127.0.0.1:6379> set k1 "v1"
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> INCR k1
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>
监控! watch
悲观锁:
- 很悲观,什么时候都会出问题,无论做什么都会加锁!(影响性能)
乐观锁:
- 很乐观,认为什么时候都不会出问题,所以不会上锁!更新数据时候去判断一下在此期间是否有人修改过这个数据,version
- 获取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> MULTI #事务正常结束,数据期间没有发生变动,这个时候就正常执行成功
OK
127.0.0.1:6379> DECRBY money 20
QUEUED
127.0.0.1:6379> INCRBY out 20
QUEUED
127.0.0.1:6379> exec
1) (integer) 80
2) (integer) 20
127.0.0.1:6379>
测试多线程修改值,使用watch可以当作redis的乐观锁操作
#事务没有执行时候,另外一个线程插队,修改了money的值
127.0.0.1:6379> set money 100
OK
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> DECRBY money 20
QUEUED
127.0.0.1:6379> INCRBY out 20
QUEUED
127.0.0.1:6379> exec #事务执行就会不成功
(nil)
127.0.0.1:6379>
如果修改失败,获取最新的值就好
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> DECRBY money 10
QUEUED
127.0.0.1:6379> INCRBY out 10
QUEUED
127.0.0.1:6379> exec #比对监视的值是否发生了变化,如果没有变化,那么可以执行成功
1) (integer) 90
2) (integer) 10
127.0.0.1:6379>
Jedis
使用java操作Redis
Jedis 是Redis官方推荐的java连接开发工具!
测试
-
导入对应的依赖
<!--jedis--> <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>
-
编码测试:
- 链接数据库
- 操作命令
- 断开连接
public static void main(String[] args) { Jedis jedis = new Jedis("192.168.222.128", 6379); System.out.println(jedis.ping()); System.out.println("清空数据:" + jedis.flushDB()); System.out.println("判断某个键是否存在" + jedis.exists("username")); System.out.println("新增<'username','awei'>的键值对:" + jedis.set("username", "awei")); System.out.println("新增<'password','123'>的键值对:" + jedis.set("password", "123")); System.out.println("系统中所有的键如下:"); Set<String> keys = jedis.keys("*"); System.out.println(keys); System.out.println("删除键password" + jedis.del("password")); System.out.println("判断password是否存在" + jedis.exists("password")); System.out.println("查看键username所存储的值的类型:" + jedis.type("username")); System.out.println("随机返回key 空间的一个:" + jedis.randomKey()); System.out.println("重命名key:" + jedis.rename("username", "name")); System.out.println("去除改后的name:" + jedis.get("name")); System.out.println("按索引查询:" + jedis.select(0)); System.out.println("返回当前库中key的数目: " + jedis.dbSize()); }
SpringBoot整合
说明:在SpringBoot 2.x 以后,原来使用的 Jedis 被替换成了 lettuce?
jedis:采用的直连,多个线程操作的话,是不安全的,如果想要避免线程不安全,使用 jedis pool
连接池!Bio
模式
lettuce:采用 netty,实例可以在多个线程中进行共享,不存在线程不安全的情况!可以减少线程数量,更像 Nio
模式
@Bean
@ConditionalOnMissingBean(name = "redisTemplate") // 没有才会创建,我们可以自己定义一个 RedisTemplate 来替换这个默认
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
// 默认的 RedisTempate 没有过多的设置, redis 对象都是需要序列化!
// 两个范型都是 Object, Object 的类型,我们后使用需要强制转换 <String,Object>
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean // 由于 String 类型是 Redis 中最常使用的类型,所以单独提出来一个 Bean
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
return new StringRedisTemplate(redisConnectionFactory);
}
整合测试
1、 导入依赖
<!-- 操作redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.15</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
2、配置连接
# 配置redis
spring:
redis:
database: 1
port: 6379
host: localhost
3、测试
package com.jalivv.redis.md02springbootredis;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisTemplate;
@SpringBootTest
class SpringbootRedisApplication {
@Autowired
RedisTemplate redisTemplate;
@Test
void contextLoads() {
/**
* redisTemplate 操作不同的数据类型
* opsForValue() 操作字符串的
* opsForHash()
* opsForSet()
* opsForZset()
* opsForGeo()
* 除了基本的操作,我们常用的方法都可以直接通过 redisTemplate 操作,比如事务和基本的增删改查
*/
//获取redis 的连接对象
//RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
//connection.flushDb();
//connection.flushAll();
//redisTemplate.opsForValue().set("bootKey", "jalivv is a boy");
//System.out.println(redisTemplate.opsForValue().get("bootKey"));
redisTemplate.opsForValue().set("bootKey", "中国");
System.out.println(redisTemplate.opsForValue().get("bootKey"));
}
}
4、自定义RedisTemplate ,向容器中放入自定义的 redisTemplate
@Bean("redisTemplate")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
template.setStringSerializer(RedisSerializer.string());
template.setValueSerializer(RedisSerializer.json());
template.setDefaultSerializer(RedisSerializer.string());
template.afterPropertiesSet();
return template;
}