文章目录
Nosql概述
为什么要用Nosql
-
我们正处于大数据时代。对于大数据,一般的数据库无法进行分析处理
-
思考一下,整个网站的瓶颈是什么?
- 数据量如果太大、一台机器放不下了
- 数据的索引 (B+ Tree),一个机器内存放不下
- 访问量(读写混合),一个服务器承受不了
-
网站80%的情况都是在读,每次都要去查询数据库的话就十分的麻烦,所以我们需要使用缓存减轻服务器的压力,保证效率
-
优化服务器发展过程:优化数据结构和索引->文件缓存->Memcached(当时最热门的技术)
-
再然后,分库分表 + 水平拆分 + MySQL集群
- 早些年MyISAM:表锁(100万条 找张三密码 会把这个表全部锁住),十分影响效率,高并发下会出现严重的锁问题
- 转战Innodb:行锁
- 慢慢的就开始使用分库分表来解决写的压力。MySQL在那个年代推出了表分区,这个并没有多少公司使用
- MySQL的集群,很好满足那个年代的大部分需求
-
如今,数据量很多,变化很快,MySQL等关系型数据库就不够用了
-
有的使用MySQL来存一些比较大的文件,博客,图关系等等。数据库表很大,效率就低了,如果有一种数据库来专门处理这种数据,MySQL压力就变得十分小(研究如何处理这些问题)大数据的IO压力下,表几乎没法更大,1亿条数据动态加个列,相当于要加1亿条数据
-
目前一个基本的互联网项目
-
为什么要用NoSQL
- 用户的个人信息,社交网络,地理位置,用户自己产生的数据,用户日志等等爆发式增长
-
这个时候我们就需要使用NoSQL数据库了,NoSQL可以很好的处理以上的情况
什么是NoSQL
- NoSQL = Not Only SQL (不仅仅是SQL)
- 关系型数据库:表格,行,列
- 泛指非关系型数据库,随着web2.0互联网的诞生,传统的关系型数据库很难对付web2.0时代,尤其是超大规模的高并发的社区,会暴露出来很多难以克服的问题,NoSQL在当今大数据环境下发展的十分迅速,Redis是发展最快的,而且是我们当下必须要掌握的一个技术
- 很多的数据类型用户的个人信息,社交网络,地理位置,这些数据类型的存储不需要一个固定的格式,不需要多余的操作就可以横向扩展的,如万能的
Map<String, Object>
使用键值对来控制
NoSQL特点
- 方便扩展 (数据之间没有关系,很好扩展。解耦)
- 大数据量,高性能 (Redis一秒可以写8W次,读取11W次,NoSQL的缓存记录级,是一种细粒度的缓存,性能会比较高)
- 数据类型是多样型的 (不需要事先设计数据库,随取随用,如果表数据量十分大,很多人就无从设计了)
- 传统RDBMS和NoSQL的区别
- RDBMS:
- 结构化组织
- SQL
- 数据和关系都存在单独的表中
- 操作,定义语言
- 严格的一致性
- 基础的事务操作
- …
- NoSQL:
- 不仅仅是数据
- 没有固定的查询语言
- 键值对存储,列存储,文档存储,图关系数据库 (社交关系)
- 最终一致性
- CAP定理和BASE理论 (异地多活)
- 高性能,高可用,高可扩展
- …
- RDBMS:
- 了解3V + 3高
- 大数据时代的3V:主要是描述问题的
- 海量Volume
- 多样Variety
- 实时Velocity
- 大数据时代的3高:主要是解决问题的
- 高并发
- 高可扩 (随时水平拆分)
- 高性能 (保证用户体验和效率)
- 大数据时代的3V:主要是描述问题的
- 真正的在公司中的实践:NoSQL + RDBMS 一起使用才是最强的
NoSQL的四大分类
KV键值对
- 新浪:Redis
- 美团:Redis + Tair
- 阿里、百度:Redis + memcache
文档型数据库
- 文档型数据库是bson格式,和json差不多
-
MongoDB (一般必须要掌握)
- MongoDB是一个基于分布式文件存储的数据库,C++编写,主要用来处理大量的文档
- MongoDB是一个介于关系型数据库和非关系型数据库中间的产品,MongoDB是非关系型数据库中功能最丰富,最像关系型数据库的非关系型数据库
- ConthDB
列存储
- HBase
- 分布式文件系统
图关系数据库
- 它不是存图形,放的是关系,比如:朋友圈社交网络,广告推荐
- Neo4j,InfoGrid
四者对比
分类 | Examples举例 | 典型应用场景 | 数据模型 | 优点 | 缺点 |
---|---|---|---|---|---|
键值对(key-value) | Tokyo Cabinet/Tyrant, Redis, Voldemort, Oracle BDB | 内容缓存,主要用于处理大量数据的高访问负载,也用于一些日志系统等等 | Key 指向 Value 的键值对,通常用hash table来实现 | 查找速度快 | 数据无结构化,通常只被当作字符串或者二进制数据 |
列存储数据库 | Cassandra, HBase, Riak | 分布式的文件系统 | 以列簇式存储,将同一列数据存在一起 | 查找速度快,可扩展性强,更容易进行分布式扩展 | 功能相对局限 |
文档型数据库 | CouchDB, MongoDB | Web应用(与Key-Value类似,Value是结构化的,不同的是数据库能够了解Value的内容) | Key-Value对应的键值对,Value为结构化数据 | 数据结构要求不严格,表结构可变,不需要像关系型数据库一样需要预先定义表结构 | 查询性能不高,而且缺乏统一的查询语法 |
图形(Graph)数据库 | Neo4J, InfoGrid, Infinite Graph | 社交网络,推荐系统等。专注于构建关系图谱 | 图结构 | 利用图结构相关算法。比如最短路径寻址,N度关系查找等 | 很多时候需要对整个图做计算才能得出需要的信息,而且这种结构不太好做分布式的集群 |
Redis入门
概述
- Redis是什么
- Redis(Remote Dictionary Server ),即远程字典服务。是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API
- redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步
- 免费和开源,当下最热门的NoSQL技术之一,也被人们称之为结构化数据库
- Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。 它支持多种类型的数据结构,如 字符串(strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 与范围查询, bitmaps, hyperloglogs 和 地理空间(geospatial) 索引半径查询。 Redis 内置了 复制(replication),LUA脚本(Lua scripting), LRU驱动事件(LRU eviction),事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过 Redis哨兵(Sentinel)和自动分区(Cluster)提供高可用性(high availability)
- Redis能干嘛
- 内存存储、持久化,内存是断电即失、所以说持久化很重要 (rdb、aof)
- 效率高,可以用于高速缓存
- 发布订阅系统
- 地图信息分析
- 计时器、计数器 (浏览量)
- …
- 特性
- 多样的数据类型
- 持久化
- 集群
- 事务
- …
- Redis推荐都是在Linux服务器上搭建的
安装
Windows安装
- 下载压缩包
- 解压,Redis十分的小
- 开启Redis,双击运行服务即可
- 使用Redis客户端来连接Redis
- ping命令测试连通
Linux安装
正常安装
- 下载安装包
redis-6.2.4.tar.gz
- 解压Redis安装包,程序一般放
/opt
目录下 - 安装基本环境
sudo yum install centos-release-scl
sudo yum install devtoolset-7-gcc*
scl enable devtoolset-7 bash
make
-
make install
确认安装完毕
- redis的默认安装路径
/usr/local/bin
- 将redis配置文件复制到当前目录
cp /opt/redis-6.2.4/redis.conf testconfig
- redis默认不是后台启动的,去配置文件把
daemonize no改成yes
- 回到默认安装路径,
redis-server testconfig/redis.conf
通过指定的配置文件启动redis -
redis-cli -p 6379
使用Redis客户端进行连接 - ping测试
-
查看redis进程状态
ps -ef|grep redis
-
shutdown
关闭redis
docker安装
-
docker run --name testRedis -d -p 6379:6379 redis
拉取最新版镜像并后台启动容器,测试用,没配置挂载 -
docker exec -it testRedis /bin/bash
进入容器 -
redis-cli
使用Redis客户端进行连接 - ping测试
-
shutdown
关闭redis
测试性能
-
redis-benchmark是一个官方自带的压力测试工具
-
redis-benchmark命令参数
-
简单测试
# 测试:100个并发 100 000个请求 redis-benchmark -h loaclhost -p 6379 -c 100 -n 100000
-
- 第一行:10w个请求写入测试
- 第二行:100个并发客户端
- 第三行:每次写入3个字节
- 第四行:只有一台服务器处理
基础知识
-
redis默认有16个数据库,默认使用的是第0个。
select 2
切换到第三个数据库127.0.0.1:6379> dbsize # 查看数据库已用容量 (integer) 4 127.0.0.1:6379> keys * # 查看所有key 1) "test" 2) "counter:__rand_int__" 3) "mylist" 4) "key:__rand_int__" 127.0.0.1:6379> select 1 # 切换数据库 OK 127.0.0.1:6379[1]> dbsize (integer) 0
-
清除当前数据库
flushdb
,清除所有数据库flushall
127.0.0.1:6379> flushdb OK 127.0.0.1:6379> keys * (empty array) 127.0.0.1:6379> set name cbc OK 127.0.0.1:6379> get name "cbc" 127.0.0.1:6379> select 2 OK 127.0.0.1:6379[2]> flushall OK 127.0.0.1:6379[2]> select 0 OK 127.0.0.1:6379> keys * (empty array)
-
思考:为什么redis是6379 (了解一下即可)
- 6379在是手机按键上MERZ对应的号码,而MERZ取自意大利歌女Alessia Merz的名字
-
Redis是单线程的。Redis4.0之前是单线程运行的,Redis4.0后开始支持数据异步删除,Redis6.0后支持多线程
- 官方表示,redis是基于内存操作的,CPU不是redis性能瓶颈,redis的瓶颈是根据机器的内存和网络带宽,可以不用多线程
- 使用单线程模式的Redis,其开发和维护更简单,因为单线程模式方便开发和调试
- 即使使用单线程模型也能够并发地处理多客户端的请求,主要是因为Redis内部使用了基于epoll的多路复用
-
Redis为什么单线程还这么快
- 误区1:高性能服务器一定是多线程的
- 误区2:多线程(上下文切换)一定比单线程效率高
- 核心:
- 纯内存操作:避免大量访问数据库,减少直接读取磁盘数据,redis将数据储存在内存里面,读写数据的时候都不会受到硬盘 I/O 速度的限制,所以速度快
- 单线程操作:避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗
- 采用了非阻塞I/O多路复用机制
五大数据类型
-
Redis-Key
127.0.0.1:6379> set name cbc OK 127.0.0.1:6379> set age 1 OK 127.0.0.1:6379> exists name # 判断是否有该键,是返回1,否返回2 (integer) 1 127.0.0.1:6379> exists name1 (integer) 0 # rename重命名k 127.0.0.1:6379> rename k key OK # 移动键值到其他数据库 127.0.0.1:6379> move name 2 # 移入3号数据库 (integer) 1 127.0.0.1:6379> select 2 OK 127.0.0.1:6379[2]> keys * # 查看所有key 1) "name" # 设置键值的过期时间 127.0.0.1:6379[2]> expire name 10 # 设置10s后过期 (integer) 1 127.0.0.1:6379[2]> ttl name # time to live 查看该键剩余时间 (integer) 5 127.0.0.1:6379[2]> ttl name (integer) 2 127.0.0.1:6379[2]> ttl name (integer) 0 127.0.0.1:6379[2]> ttl name (integer) -2 # -2代表已经没了,-1代表不会过期 127.0.0.1:6379> type age # 查看当前key的类型 string
String (字符串)
127.0.0.1:6379> get age
"1"
127.0.0.1:6379> append age 1 # append字符串拼接,动态修改存的字符串,如果当前key不存在,就相当于set了一个key
(integer) 2 # 拼接后字符串长度
127.0.0.1:6379> get age
"11"
127.0.0.1:6379> append age "1" # 这样写也行," "可用来追加空格
(integer) 3
127.0.0.1:6379> get age
"111"
127.0.0.1:6379> strlen age # strlen获取字符串长度
(integer) 3
127.0.0.1:6379> set views 0
OK
127.0.0.1:6379> incr views # incr自增
(integer) 1
127.0.0.1:6379> get views
"1"
127.0.0.1:6379> decr views # decr自减
(integer) 0
127.0.0.1:6379> incrby views 10 # 自增10
(integer) 10
127.0.0.1:6379> decrby views 10 # 自减10
(integer) 0
127.0.0.1:6379> getrange key1 0 3 # getrange截取字符串,[0,3]
"hell"
127.0.0.1:6379> getrange key1 0 -1 # -1代表到结尾
"hello,world"
127.0.0.1:6379> getrange key1 -1 -1 # 最后一个字符
"d"
127.0.0.1:6379> get key2
"abcdefg"
127.0.0.1:6379> setrange key2 1 xx # setrange替换字符串,从下标1开始的两个字符替换成xx
(integer) 7 # 替换后的字符串长度
127.0.0.1:6379> get key2
"axxdefg"
# setex (set with expire) 设置过期时间
# setnx (set if not exist) 不存在再设置 (在分布式锁中会常常使用)
127.0.0.1:6379> setex key3 30 hello # setex设置并使30s后过期
OK
127.0.0.1:6379> setnx mykey redis # 不存在mykey再设置
(integer) 1
127.0.0.1:6379> setnx mykey mongodb
(integer) 0 # key已存在,设置失败
127.0.0.1:6379> get mykey
"redis"
127.0.0.1:6379> del key1 # del k1 k2...(批量)删除键值对
(integer) 1
# mset一次设置多个键值
127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3
OK
127.0.0.1:6379> keys *
1) "k1"
2) "k3"
3) "k2"
127.0.0.1:6379> mget k1 k2 k3 # mget批量取值
1) "v1"
2) "v2"
3) "v3"
127.0.0.1:6379> msetnx k1 v1 k4 v4 # msetnx批量不存在再设置
(integer) 0 # 因为k1存在,所以失败
127.0.0.1:6379> get k4
(nil) # 发现k4也没设置成功,说明msetnx具有原子性
127.0.0.1:6379> set user:1 {name:zhangsan,age:3} # 存可转化为json对象的字符串
OK
127.0.0.1:6379> get user:1
"{name:zhangsan,age:3}"
127.0.0.1:6379> getset db redis # getset先get再set,取旧值设新值,如果之前的值不存在则相当于直接set
(nil)
127.0.0.1:6379> getset db mongodb
"redis"
127.0.0.1:6379> get db
"mongodb"
- String类似的使用场景:
- 计数器
- 统计多单位的数量
List (列表)
-
列表,在redis里,我们可以把list当做栈、队列、阻塞队列。大部分list命令都是以
l
开头的127.0.0.1:6379> lpush list one # list插入数据 (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> rpush list right # 想象成从左往右读的表,lpush是头插往left方向放值,rpush是尾插往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 1 # lpop左移除值,移除一个 1) "three" 127.0.0.1:6379> rpop list 1 # rpop右移除值 1) "right" 127.0.0.1:6379> lrange list 0 -1 1) "two" 2) "one" 127.0.0.1:6379> lindex list 0 # lindex通过下标获取list中某一个值 "two" 127.0.0.1:6379> llen list # llen返回列表的长度 (integer) 2 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 # lrem移除一个one (integer) 1 127.0.0.1:6379> lrem list 1 three # 移除一个three (integer) 1 127.0.0.1:6379> lrange list 0 -1 # 还有一个three 1) "three" 2) "two" 127.0.0.1:6379> lpush list three (integer) 3 127.0.0.1:6379> lrem list 2 three # 移除两个three (integer) 2 127.0.0.1:6379> lrange list 0 -1 1) "two" 127.0.0.1:6379> lrange list 0 -1 1) "hello123" 2) "hello12" 3) "hello1" 4) "hello" 127.0.0.1:6379> ltrim list 1 3 # ltrim截取list中两个下标中间的值,会改变list OK 127.0.0.1:6379> lrange list 0 -1 1) "hello12" 2) "hello1" 3) "hello" 127.0.0.1:6379> rpoplpush list myotherlist # rpoplpush移除列表最右元素并放入另一个列表最左,另一个列表不存在则会创建 "hello1" 127.0.0.1:6379> lrange list 0 -1 1) "hello123" 2) "hello12" 127.0.0.1:6379> lrange myotherlist 0 -1 1) "hello1" 127.0.0.1:6379> exists mylist # exists判断列表是否存在,存在1,不存在0 (integer) 0 127.0.0.1:6379> lset mylist 0 item # 不能往不存在的list用下标存值 (error) ERR no such key 127.0.0.1:6379> lrange list 0 -1 1) "value1" 2) "item" 3) "hello12" 127.0.0.1:6379> lset list 0 value100 # lset,可以用lset把list指定下标的值替换掉 更新 OK 127.0.0.1:6379> lrange list 0 -1 1) "value100" 2) "item" 3) "hello12" 127.0.0.1:6379> lset list 3 test # lset不能设置没有值的下标 (error) ERR index out of range 127.0.0.1:6379> lpush list hello (integer) 1 127.0.0.1:6379> lpush list world (integer) 2 # linsert key before|after pivot value,若存在重复值,则在最左侧开始第一个匹配到的进行插入 127.0.0.1:6379> linsert list before world other # linsert在list的某个值的前或后插入值 (integer) 3 127.0.0.1:6379> lrange list 0 -1 1) "other" 2) "world" 3) "hello"
-
小结
- 它实际上是一个链表,before, after Node, left, right 都可以插入值
- push如果key不存在,则创建新的链表
- push如果key存在,则新增内容
- 如果移除了所有值,空链表,也代表不存在
- 在两边插入或者改动值,效率最高,中间元素效率会相对低点
Set (集合)
-
set中的值是不可以重复的,看起来是根据字母顺序排列的,set命令基本都是以
s
开头的127.0.0.1:6379> sadd myset hello # sadd添加元素 (integer) 1 127.0.0.1:6379> smembers myset # smembers查看所有元素 1) "cbc" 2) "hello" 3) "sb" 127.0.0.1:6379> sismember myset hello # sismember查看某元素是否在该集合中 是为1,否为2 (integer) 1 127.0.0.1:6379> sismember myset lll (integer) 0 127.0.0.1:6379> scard myset # scard获取set集合中元素的个数 (integer) 6 127.0.0.1:6379> srem myset asdf # srem在集合中删除指定元素 (integer) 1 127.0.0.1:6379> srandmember myset # srandmember随机抽出一个元素 "cbc" 127.0.0.1:6379> SRANDMEMBER myset 2 # 随机抽出指定个数元素 1) "hello" 2) "zbc" 127.0.0.1:6379> spop myset # spop随机干掉一位元素 "aa" 127.0.0.1:6379> spop myset 2 # 随机干掉指定位元素 1) "cbc" 2) "sb" 127.0.0.1:6379> SMEMBERS myset 1) "hello" 2) "zbc" 127.0.0.1:6379> smove myset myset2 hello # smove转移指定元素到另一个集合 (integer) 1 127.0.0.1:6379> SMEMBERS myset2 1) "hello" 127.0.0.1:6379> SMEMBERS myset 1) "zbc" 127.0.0.1:6379> SMEMBERS key1 1) "c" 2) "b" 3) "a" 127.0.0.1:6379> SMEMBERS key2 1) "a" 2) "3" 3) "2" 4) "1" 127.0.0.1:6379> SDIFF key1 key2 # sdiff查看不同元素,key1中与key2不同的元素 1) "c" 2) "b" 127.0.0.1:6379> SDIFF key2 key1 # key2中与key1不同的元素 1) "1" 2) "2" 3) "3" 127.0.0.1:6379> sinter key1 key2 # sinter查看两个集合中相同的元素 共同好友可以这样实现 1) "a" 127.0.0.1:6379> sinter key2 key1 # 等价于上 1) "a" 127.0.0.1:6379> SUNION key1 key2 # sunion合并去重 1) "3" 2) "a" 3) "b" 4) "c" 5) "1" 6) "2"
-
微博,A用户将所有关注的人放在一个set集合中,将它的粉丝也放在一个集合中
-
共同关注,共同爱好,二度好友 、推荐好友(六度分割理论)
Hash (哈希)
-
Map的集合,
key-<key,value>
,本质和string类型没有太大区别,hash命令基本都是以h
开头的127.0.0.1:6379> hset myhash field1 cbc # hset设置哈希中的元素 (integer) 1 127.0.0.1:6379> hget myhash field1 # hget获取哈希中的元素 "cbc" 127.0.0.1:6379> hmset myhash field1 hello field2 world # hmset批量设置哈希中的元素,4.0后已被弃用,hset也可以做到 OK 127.0.0.1:6379> hmget myhash field1 field2 # hmget批量获取哈希中的元素,4.0后已被弃用,hget也可以做到 1) "hello" 2) "world" 127.0.0.1:6379> hgetall myhash # hgetall获取哈希中全部值,以k-v的形式 1) "field1" 2) "hello" 3) "field2" 4) "world" 127.0.0.1:6379> hdel myhash field1 # hdel删除哈希中特定k的一对k-v (integer) 1 127.0.0.1:6379> hgetall myhash 1) "field2" 2) "world" 127.0.0.1:6379> hlen myhash # hlen查看哈希中有多少个元素(键值对) (integer) 1 127.0.0.1:6379> HEXISTS myhash field1 # hexists判断哈希中是否存在该键,是1,否0 (integer) 0 127.0.0.1:6379> HEXISTS myhash field2 (integer) 1 127.0.0.1:6379> hkeys myhash # hkeys获取哈希中所有的键 1) "field2" 2) "field1" 127.0.0.1:6379> hvals myhash # hkeys获取哈希中所有的值 1) "world" 2) "hello" 127.0.0.1:6379> hset myhash field3 5 (integer) 1 127.0.0.1:6379> hincrby myhash field3 1 # hincrby自增1 (integer) 6 127.0.0.1:6379> hincrby myhash field3 -1 # 自减1 (integer) 5 127.0.0.1:6379> hsetnx myhash field4 hello # hsetnx不存在则创建, (integer) 1 127.0.0.1:6379> hsetnx myhash field4 world # 存在则创建失败 (integer) 0
-
hash可以存变更的数据user: name age,尤其是用户信息之类的,可以当做一个实体类来存些基本数据,经常变动的信息
-
hash更适合于对象的存储,string更适合于字符串存储
Zset (有序集合)
-
在set的基础上增加了一个值,
zset k1 score1 v1
,zset命令基本都是以z
开头的127.0.0.1:6379> zadd myset 1 one # zadd添加一个值 (integer) 1 127.0.0.1:6379> zadd myset 2 two 3 three # 添加多个值 (integer) 2 127.0.0.1:6379> ZRANGE myset 0 -1 # 遍历myset所有值,从小到大排序 1) "one" 2) "two" 3) "three" 127.0.0.1:6379> zadd salary 2500 xiaohong 5000 zhangsan 500 cbc # 格式zadd k score1 v1 score2 v2 score3 v3 (integer) 3 127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf # zrangebyscore按score从小到大排序,范围为负无穷(-inf)到正无穷(+inf) 1) "cbc" 2) "xiaohong" 3) "zhangsan" 127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf withscores # 按score排序并显示k对应的score 1) "cbc" 2) "500" 3) "xiaohong" 4) "2500" 5) "zhangsan" 6) "5000" 127.0.0.1:6379> ZRANGEBYSCORE salary -inf 2500 withscores # 范围只在负无穷到2500(闭区间),(2500指开区间 1) "cbc" 2) "500" 3) "xiaohong" 4) "2500" 127.0.0.1:6379> ZREVRANGE salary 0 -1 withscores # zrevrange遍历myset所有值,从大到小排序 1) "zhangsan" 2) "5000" 3) "cbc" 4) "500" 5) "cbc" 6) "500" 127.0.0.1:6379> ZREVRANGEBYSCORE salary +inf -inf withscores # zrevrangebyscore从大到小排序,范围为正无穷到负无穷 1) "zhangsan" 2) "5000" 3) "xiaohong" 4) "2500" 5) "cbc" 6) "500" 127.0.0.1:6379> zrange salary 0 -1 1) "cbc" 2) "xiaohong" 3) "zhangsan" 127.0.0.1:6379> zrem salary xiaohong # zrem移除元素 (integer) 1 127.0.0.1:6379> zrange salary 0 -1 1) "cbc" 2) "zhangsan" 127.0.0.1:6379> zcard salary # zcard获取集合中元素的个数 (integer) 2 127.0.0.1:6379> zrange myset 0 -1 1) "hello" 2) "world" 3) "cbc" 4) "sb" 5) "666" 127.0.0.1:6379> zcount myset (1 3 # zcount获取score区间的元素个数 (integer) 2
-
set的排序版,存储班级成绩表,工资表排序。消息带权重,排行榜
-
这些只是基础API,其他API若有需要,去官方文档查看是最好的
三种特殊数据类型
geospatial (地理位置)
-
朋友的定位,附近的人,打车距离计算
-
Redis的Geo在Redis3.2版本就推出了,这个功能可以推算地理位置的信息,两地之间的距离,方圆几里的人
-
相关命令
-
GEOADD
# getadd k 经度 纬度 v 添加地理位置 # 规则:两级无法直接添加。我们一般会下载城市数据,直接通过java程序一次性导入 127.0.0.1:6379> geoadd china:city 116.40 39.90 beijing (integer) 1
-
GEODIST
# geodist获取两个位置间的距离,单位 m:米 km:千米 mi:英里 ft:英尺,默认为米 127.0.0.1:6379> GEODIST china:city beijing xian # geodist计算两地之间的距离 "910056.5237" 127.0.0.1:6379> geodist china:city beijing xian km # 千米做单位 "910.0565"
-
-
GEOHASH
-
返回11个字符的Geohash字符串
-
GEOPOS
127.0.0.1:6379> geopos china:city beijing # geopos获取指定的城市的经纬度 1) 1) "116.39999896287918091" 2) "39.90000009167092543" 127.0.0.1:6379> GEOPOS china:city beijing xian # 获取多个城市的经纬度 1) 1) "116.39999896287918091" 2) "39.90000009167092543" 2) 1) "108.96000176668167114" 2) "34.25999964418929977"
-
-
GEORADIUS
# 以给定的经度纬度为中心,找出某一半径内的元素 127.0.0.1:6379> GEORADIUS china:city 110 30 500 km # 以110,30经纬度为中心查找半径500km内的元素 1) "changqin" 2) "xian" 127.0.0.1:6379> GEORADIUS china:city 110 30 500 km withdist withcoord # 并显示距离和经纬度 1) 1) "changqin" 2) "341.9374" # 距离 3) 1) "106.49999767541885376" # 经纬度 2) "29.52999957900659211" 2) 1) "xian" 2) "483.8340" 3) 1) "108.96000176668167114" 2) "34.25999964418929977" 127.0.0.1:6379> GEORADIUS china:city 110 30 500 km count 1 # 限制只取一个 1) "changqin"
-
GEORADIUSBYMEMBER
- 这个命令和 GEORADIUS 命令一样, 都可以找出位于指定范围内的元素, 但是 GEORADIUSBYMEMBER 的中心点是由给定的位置元素决定的, 而不是像 GEORADIUS 那样, 使用输入的经度和纬度来决定中心点
-
GEOSEARCH
# 该命令扩展了GEORADIUS命令,因此除了在圆形区域内搜索外,它还支持在矩形区域内搜索。此命令代替了现已弃用的GEORADIUS和GEORADIUSBYMEMBER geosearch key frommember|fromlonlat v byradius|bybox asc|desc withcoord withdist withhash # FROMMEMBER: 使用元素作为中心。FROMLONLAT: 使用经纬度作为中心。 # ASC:相对于中心,从最近到最远对返回的项目进行排序。DESC:相对于中心,从最远到最近对返回的项目进行排序。
-
GEOSEARCHSTORE
- 此命令类似于GEOSEARCH,但将结果存储在目标键中。默认情况下,它将结果destination与其地理空间信息一起存储在排序集中。使用该STOREDIST选项时,该命令将项目存储在一个排序集中,其中填充了它们与圆或框中心的距离,作为浮点数,以为该形状指定的相同单位
-
-
GEO底层实现原理其实就是 Zset ,我们可以使用 Zset 命令来操作GEO
127.0.0.1:6379> type china:city zset 127.0.0.1:6379> zrange china:city 0 -1 1) "changqin" 2) "xian" 3) "shengzheng" 4) "hangzhou" 5) "shanghai" 6) "beijing" 127.0.0.1:6379> zrem china:city changqin # zrem删除GEO的元素 (integer) 1 127.0.0.1:6379> zrange china:city 0 -1 1) "xian" 2) "shengzheng" 3) "hangzhou" 4) "shanghai" 5) "beijing"
hyperloglog
-
什么是基数
- 基数为一个集合内不重复的元素的个数,可以接受误差
-
简介
- Redis 2.8.9 版本就更新了hyperloglog数据结构
- hyperloglog基数统计算法可以用来计算网页的UV (一个人访问一个网站多次,但是还是算作一个人)
- 传统方式,用set保存用户id,这个方式如果保存大量的用户id,就会很麻烦,我们的目的是为了计数,而不是保存用户id
- hyperloglog优点:占用的内存很小。但会有0.81%的错误率,对于统计UV任务可以忽略不计
127.0.0.1:6379> PFADD mykey a b c d e f g h i j k # fadd创建一组元素 (integer) 1 127.0.0.1:6379> PFCOUNT mykey # pfcount统计元素基数数量 (integer) 11 127.0.0.1:6379> PFADD mykey5 a a a a (integer) 1 127.0.0.1:6379> PFCOUNT mykey5 (integer) 1 127.0.0.1:6379> PFADD mykey2 i j z x c v b d (integer) 1 127.0.0.1:6379> PFCOUNT mykey2 (integer) 8 127.0.0.1:6379> pfmerge mykey3 mykey mykey2 # pfmerge合并去重mykey和mykey2元素至mykey3中 OK 127.0.0.1:6379> PFCOUNT mykey3 (integer) 14
-
如果允许hyperloglog的错误率,建议使用,不允许的话使用set或自己的数据类型
bitmap
-
位存储
-
统计用户信息,活跃,不活跃。登录,未登录。打卡,未打卡
-
bitmap位图,都是操作二进制位来进行记录,只有0和1
# 使用bitmap来记录周一到周日的打卡 # 周一:1 周二:0 周三:0 周四:1 周五:1 周六:0 周天:1 127.0.0.1:6379> setbit sign 0 1 # setbit key offset value (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 # getbit查看某天是否打卡 127.0.0.1:6379> getbit sign 3 (integer) 1 # bitcount统计打卡的天数 127.0.0.1:6379> bitcount sign # bitcount key [start] [end],[start]和[end]代表起始和结束字节数 (integer) 4
-