一、Nosql概述
为什么要用Nosql
1、单机MySQL
90年代,一个基本的网站访问量一般不会很大,单个数据库完全足够,那个时候更多的去使用静态页面html,服务器根本没有太大压力,这种情况下网站的瓶颈是什么?
(1)数据量如果太大,一个机器就放不下
(2)数据的索引(B+Tree),一个机器内存也放不下
(3)访问量(读写混合),一个服务器承受不了
只要出现以上三种情况,你就必须要晋级。
2、Memcached(缓存)+MySQL+垂直拆分(读写分离)
网站80%的情况都是在读,每次都要去查询数据库就很麻烦,所以我们希望减轻数据的压力,我们可以使用缓存来保证效率。
发展过程:优化数据结构和索引->文件缓存(IO)->Memcached(当时最热门的技术)
3、分库分表+水平拆分+MySQL集群
技术和业务在发展的同时,对人的要求也越来越高!
本质:数据库(读、写)
早些年MyISAM:表锁,十分影响效率!高并发下就会出现严重的锁问题
转战Innodb:行锁
慢慢的就开始使用分库分表来解决写的压力!MySQL在那个年代推出了表分区!这个并没有多少公司使用。
MySQL的集群,很好满足了那个年代的所有需求!
4、如今最近的年代
2010-2020十年之间,世界已经发生了翻天覆地的变化(定位。也是一种数据,音乐,热榜)
MySQL等关系型数据库就不够用了,数据量很多,变化很快
MySQL有的使用它来存储一些比较大的文件,博客,图片。数据库表很大,效率就低了,如果有一种数据库来专门处理这种数据,MySQL压力就变得十分小(研究如何处理这些问题)大数据IO压力下,表几乎没法更大!
用户个人信息,社交网络、地理位置。用户自己产生的数据,用户日志等爆发式增长,这时候我们就需要使用NoSQL数据库的,Nosql可以很好的处理以上的情况。
什么是NoSQL
NoSQL
NoSQL = Not Only SQL
关系型数据库:表格,行,列
泛指非关系型数据库的,随着web2.0互联网的诞生!传统的关系型数据库很难对付web2.0时代!尤其是超大规模的高并发的社区!暴露出来很多难以克服的问题,NoSQL在当今大数据环境下发展的十分迅速,Redis是发展最快的,而且是我们当下必须要掌握的一个技术!
很多的数据类型用户的个人信息,社交网络,地理位置。这些数据类型的存储不需要一个固定的格式!不需要多月的操作就可以横向扩展的!Map<String,Object>使用键值对来控制!
NoSQL特点
1、方便扩展(数据之间没有关系,很好扩展)
2、大数据量高性能(Redis一秒写8万次,读取11万次,NoSQL的缓存记录级,是一种细粒度的缓存,性能会比较高!)
3、数据类型是多样型的!(不需要事先设计数据库!随时取用!如果是数据量十分大的表,很多人就无法设计了)
4、传统RDBMS和NoSQL
传统的RDBMS:
-结构化组织
-SQL
-数据和关系都存在单独的表中 row col
-操作,数据定义语言
-严格的一致性
-基础的事物
NoSQL
-不仅仅是数据
-没有固定的查询语言
-键值对存储、列存储、文档存储、图形数据库(社交关系)
-最终一致性
-CAP定理和BASE(异地多活)初级架构师
-高性能、高可用、高可扩
了解:3V+3高
大数据时代的3v:主要是描述问题(1、海量Volume 2、多样Variety 3、实时Velocity)
大数据时代的3高:主要是对程序的要求(1、高并发 2、高可拓 3、高性能)
阿里巴巴演进分析
1、商品的基本信息
名称、价格、商家信息;
关系型数据库就可以解决了!MySQL/Oracle
淘宝内部的MySQL不是大家用的MySQL
2、商品的描述、评论(文字比较多)
文档型数据库中,MongDB
3、图片
分布式文件系统FastDFS
淘宝自己的TFS
Gooale的DFS
Hadoop HDFS
阿里云的 OSS
4、商品的关键字(搜索)
搜索引擎 solr elasticsearch
ISerach:多隆(技术员)
5、商品热门的波段信息
内存数据库
Redis Tair、Memache...
6、商品的交易、外部的交易接口
三方应用
大型互联网应用问题:
数据类型太多了、数据源繁多,经常重构、数据改造,大面积改造?
NoSQL的四大分类
KV键值对:
新浪:Redis
美团:Redis+Tair
阿里、百度:Redis+memecache
文档型数据库(bson格式和json一样)
MongoDB(一般必须要掌握)
MongoDB是一个基于分布式文件存储的数据库,c++编写,主要用来处理大量的文件!
MongoDB是一个介于关系型数据库和菲关系型数据库中中间的产品!MongoDB是非关系型数据库中功能最丰富的,最像关系型数据库的!
列存储数据库
HBase(在大数据中出现)
分布式文件系统
图关系数据库
它不是存图形,放的是关系,比如:朋友圈社交网络,广告推荐!
Neo4j、InfoGrid;
二、Redis入门
概述
Redis是什么
是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。免费和开源!是当下最热门的NoSQL技术之一!也被人们称之为结构化数据!
Redis能干嘛?
1、内存存储、持久化,内存中是断电即失、所以说持久化很重要(rdb、aof)
2、效率高,可以用于高速缓存
3、发布订阅系统
4、地图信息分析
5、计时器、计数器(浏览量!)
6、。。。
特性
1、多样的数据类型
2、持久化
3、集群
4、事物
学习中需要用到的东风西
1、官网:Redis
2、中文网:redis中文官方网站
3、下载地址:通过官网下载即可
注意:redis适合在linux上开发。
Windows安装
1、在官网下载redis压缩包(5M左右)
2、双击redis-server开启redis(端口号6379)
3、使用redis客户端来连接redis
4、
Linux安装
1、打开官网Redis
2、用xftp下载到Linux中
3、解压Redis的安装包,把压缩包移动到opt
4、进入解压后的文件,可以看到我们redis的配置文件
5、基本的环境安装
yum install gcc-c++
gcc -v查看环境
make
安装完毕
再次make
make install
6、查看默认的安装路径 /usr/local/bin
7、将redis的配置文件复制到当前目录下
8、redis默认不是后台启动的,修改配置文件
9、启动redis服务!
10、 开启客户端,使用redis-cli进行连接测试
11、查看redis的进程是否开启!
12、如何关闭redis服务呢?shutdown
或者
./redis-cli shutdown
13、再次查看进程是否存在
14、修改配置文件实现在任何位置都可以启动redis
export JAVA_HOME=/usr/java/jdk1.8.0_301-amd64
export REDIS_HOME=/usr/local/bin
export PATH=$REDIS_HOME/src:$JAVA_HOME/bin:$PATH
15、后面使用单机多Redis启动集群测试!
测试性能
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[3]> set name qinjiang #设置key value
OK
127.0.0.1:6379[3]> dbsize
(integer) 1
127.0.0.1:6379> select 7
OK
127.0.0.1:6379[7]> dbsize
(integer) 0
127.0.0.1:6379[7]> get name
(nil)
127.0.0.1:6379[7]> select 3
OK
127.0.0.1:6379[3]> get name #get值
"qinjiang"
127.0.0.1:6379[3]> keys * #查看所有的value
1) "name"
清楚当前数据库 flushdb
清楚全部数据库的内容 flushall
127.0.0.1:6379[3]> flushdb
OK
127.0.0.1:6379[3]> keys *
(empty array)
Redis是单线程的!
Redis是基于内存操作,CPU不是Redis性能瓶颈,Redis的瓶颈是根据机器的内存和网络带宽,既然可以使用单线程来实现,就使用单线程了!
Redis是C语言写的,官方提供的数据为100000+的QPS,完全不比同样是使用key-value 的Memecache差!
Redis为什么单线程还这么快?
1、误区1:高性能的服务器一定是多线程的?
2、误区2:多线程(CPU上下文会切换!)一定比单线程效率高!
速度:CPU>内存>硬盘
核心:redis是将所有的数据全部存放在内存中,所以说使用单线程去操作效率就是最高的,多线程(会出现CPU上下文切换),对于内存系统来说,如果没有上下文切换就是效率最高的!多次读写都是在一个CPU上的,在内存情况下,这就是最佳方案!
五大数据类型
官网文档 Redis
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).
现在将的命令记住,后面使用SpringBoot、Jedis所有的方法就是这些命令!
单点登录可以把一些数据放到Redis里面
Redis-Key
127.0.0.1:6379[3]> keys *
(empty array)
127.0.0.1:6379[3]> set name xingxing
OK
127.0.0.1:6379[3]> get name
"xingxing"
127.0.0.1:6379[3]> set age 18
OK
127.0.0.1:6379[3]> keys *
1) "age"
2) "name"
127.0.0.1:6379[3]> exists name #name是否存在,1存在,0不存在
(integer) 1
127.0.0.1:6379[3]> exists name1
(integer) 0
127.0.0.1:6379[3]> move name1 1 #move移除key,后面的1代表当前数据库
(integer) 0
127.0.0.1:6379[3]> move name
(error) ERR wrong number of arguments for 'move' command
127.0.0.1:6379[3]> move name 1
(integer) 1
127.0.0.1:6379[3]> kys *
(error) ERR unknown command `kys`, with args beginning with: `*`,
127.0.0.1:6379[3]> keys *
1) "age"
127.0.0.1:6379[3]> expire age 10 #在几秒后移除age
(integer) 1
127.0.0.1:6379[3]> ttl age #查看当前秒数
(integer) 5
127.0.0.1:6379[3]> type name #查看key类型
string
127.0.0.1:6379[3]> type age
string
后面如果遇到不会的命令,可以在官网查看帮助文档。
String
127.0.0.1:6379[3]> flushdb #清空当前数据库
OK
127.0.0.1:6379[3]> set name xingxing #设置值
OK
127.0.0.1:6379[3]> exists name
(integer) 1
127.0.0.1:6379[3]> append name nihao #追加字符串,如果当前key不存在,就相当于setkey
(integer) 13
127.0.0.1:6379[3]> get name
"xingxingnihao"
127.0.0.1:6379[3]> strlen name #获取字符串的长度
(integer) 13
# 增加减少
127.0.0.1:6379[3]> set views 0
OK
127.0.0.1:6379[3]> get views
"0"
127.0.0.1:6379[3]> incr views #增加1
(integer) 1
127.0.0.1:6379[3]> incr views
(integer) 2
127.0.0.1:6379[3]> get views
"2"
127.0.0.1:6379[3]> decr views #减掉1
(integer) 1
127.0.0.1:6379[3]> decr views
(integer) 0
127.0.0.1:6379[3]> decr views
(integer) -1
127.0.0.1:6379[3]> get views
"-1"
127.0.0.1:6379[3]> incrby views 5 #增加5
(integer) 4
127.0.0.1:6379[3]> incrby views 5
(integer) 9
127.0.0.1:6379[3]> decrby views 5 #减去5
(integer) 4
127.0.0.1:6379[3]> decrby views 5
(integer) -1
#字符串范围 range
127.0.0.1:6379[3]> get name
"xingxingnihao"
127.0.0.1:6379[3]> getrange name 0 3 #截取[0,3]
"xing"
127.0.0.1:6379[3]> getrange name 0 -1 #截取所有
"xingxingnihao"
127.0.0.1:6379[3]> setrange name 8 hello #替换指定位置的字符串
(integer) 13
127.0.0.1:6379[3]> get name
"xingxinghello"
# setex(set with expire) #设置过期时间
# setnx(set if not exist) #不存在则设置(在分布式锁中会常常使用)
127.0.0.1:6379[3]> set name xinxin
OK
127.0.0.1:6379[3]> setex name 60 hello #设置name的值为hello,在60s后过期删除掉name
OK
127.0.0.1:6379[3]> ttl name
(integer) 56
127.0.0.1:6379[3]> get name
"hello"
127.0.0.1:6379[3]> setnx myname redis #如果mykey不存在,则创建mykey
(integer) 1
127.0.0.1:6379[3]> keys *
1) "myname"
2) "name"
3) "views"
127.0.0.1:6379[3]> ttl name
(integer) 13
127.0.0.1:6379[3]> get myname
"redis"
127.0.0.1:6379[3]> setnx myname he #如果mykey存在,则创建失败!
(integer) 0
127.0.0.1:6379[3]> get myname
"redis"
mset mget
127.0.0.1:6379[3]> mset k1 v1 k2 v2 k3 v3 # 同时设置多个值
OK
127.0.0.1:6379[3]> keys *
3) "k3"
4) "k1"
5) "k2"
127.0.0.1:6379[3]> mget k1 k2 k3 # 同时获取多个值
1) "v1"
2) "v2"
3) "v3"
127.0.0.1:6379[3]> msetnx k1 v1 k4 v4 # msetnx是一个原子性操作,要么一起成功要么一起失败当所有 key 都成功设置,返回 1 。 如果所有给定 key 都设置失败(至少有一个 key 已经存在),那么返回 0 。
(integer) 0
127.0.0.1:6379[3]> keys *
3) "k3"
4) "k1"
5) "k2"
127.0.0.1:6379[3]> get v4
(nil)
# 对象
set user:1 {name:zhangsan,age:3} # 设置一个user:1 对象值为json字符来保存一个对象!
# 这里的key是一个巧妙的设计: user:{id}:{filed},如此设计在Redis中是完全ok的
127.0.0.1:6379[3]> mset user:1:name zhangsan user:1:age 2
OK
127.0.0.1:6379[3]> mget user:1:name user:1:age
1) "zhangsan"
2) "2"
getset # 先get后set
127.0.0.1:6381> getset k2 v2
(nil)
127.0.0.1:6381> get k2
"v2"
127.0.0.1:6381> getset k2 h2
"v2"
127.0.0.1:6381> get k2
"h2"
数据结构是相同的!
String类似的使用场景:value除了是我们的字符串还可以是我们的数字!
计数器
- 统计多单位的数量
- 粉丝数
- 对象缓存存储
List(列表)
在redis里面,我们可以把list完成,栈、队列、阻塞队列!
所有的list命令都是l开头的,Redis不区分大小写命令
lpush 左边插入 rpush 右边插入
lpush 左边插入 rpush 右边插入
127.0.0.1:6379> lpush list a b c d # 将一个值或者多个值,插入到列表头部(左)
(integer) 4
127.0.0.1:6379> lrange list 0 -1
1) "d"
2) "c"
3) "b"
4) "a"
127.0.0.1:6379> rpush list -a -b # 将一个值或者多个值,插入到列表尾部(右)
(integer) 6
127.0.0.1:6379> lrange list 0 -1
1) "d"
2) "c"
3) "b"
4) "a"
5) "-a"
6) "-b"
lpop 左边移除 rpop右边移除
127.0.0.1:6379> lpop list 2
1) "d"
2) "c"
127.0.0.1:6379> lrange list 0 -1
1) "b"
2) "a"
3) "-a"
4) "-b"
127.0.0.1:6379> rpop list 2
1) "-b"
2) "-a"
127.0.0.1:6379> lrange list 0 -1
1) "b"
2) "a"
lindex 通过下标获取list中的某个值
127.0.0.1:6379> lrange list 0 -1
1) "b"
2) "a"
127.0.0.1:6379> lindex list 1
"a"
127.0.0.1:6379> lindex list 0
"b"
llen 获取列表长度
127.0.0.1:6379> llen list
(integer) 2
lrem 移除指定的值
127.0.0.1:6379> lrange list 0 -1
1) "b"
2) "b"
3) "a"
127.0.0.1:6379> lrem list 3 b
(integer) 2
127.0.0.1:6379> lrange list 0 -1
1) "a"
ltrim 修剪:list截断
127.0.0.1:6379> rpush list one two
(integer) 2
127.0.0.1:6379> lrange list 0 -1
1) "one"
2) "two"
127.0.0.1:6379> rpush list three four
(integer) 4
127.0.0.1:6379> lrange list 0 -1
1) "one"
2) "two"
3) "three"
4) "four"
127.0.0.1:6379> ltrim list 1 2 # 通过下标截取指定的长度,这个list被改变了,只剩下截取的元素
OK
127.0.0.1:6379> lrange list 0 -1
1) "two"
2) "three"
rpoplpush 移除列表的最后一个元素,将他移动到新的列表中
127.0.0.1:6379> lrange list 0 -1
1) "one"
2) "two"
3) "three"
127.0.0.1:6379> rpoplpush list newlist
"three"
127.0.0.1:6379> lrange list 0 -1
1) "one"
2) "two"
127.0.0.1:6379> lrange newlist 0 -1
1) "three"
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 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
linsert 将某个具体的value插入到列中某个元素的前面或者后面!
127.0.0.1:6379> rpush list hello world
(integer) 2
127.0.0.1:6379> lrange list 0 -1
1) "hello"
2) "world"
127.0.0.1:6379> linsert list before hello first
(integer) 3
127.0.0.1:6379> lrange list 0 -1
1) "first"
2) "hello"
3) "world"
127.0.0.1:6379> linsert list after hello java
(integer) 4
127.0.0.1:6379> lrange list 0 -1
1) "first"
2) "hello"
3) "java"
4) "world"
小结
- 实际上是一个链表
- 如果key不存在,创建新的链表
- 如果key存在,新增内容
- 如果移除了所有值,空链表,也代表不存在
- 在两边插入或者改动值,效率最高!中间元素,相对效率低
Set(集合)
set中的值不能重复!set是无序不重复集合!
sadd 设置set集合的值 smembers 查看set的所有值 sismember 判断值是否在set中
127.0.0.1:6379> sadd set "hello" # set集合中添加匀速
(integer) 1
127.0.0.1:6379> sadd set kuangshen
(integer) 1
127.0.0.1:6379> sadd set good
(integer) 1
127.0.0.1:6379> smembers set # 查看指定set的所有值
1) "kuangshen"
2) "hello"
3) "good"
127.0.0.1:6379> exists set # 判断是否存在key
(integer) 1
127.0.0.1:6379> sismember set hello # 判断某一个值是不是在set集合中
(integer) 1
127.0.0.1:6379> sismember set hi
(integer) 0
scard key 获取key的值
127.0.0.1:6379> scard set
(integer) 3
srem key value 移除key中的value
127.0.0.1:6379> smembers set
1) "kuangshen"
2) "hello"
3) "good"
127.0.0.1:6379> srem set kuangshen
(integer) 1
127.0.0.1:6379> smembers set
1) "hello"
2) "good"
srandmember 随机抽取key中的一个value
127.0.0.1:6379> SRANDMEMBER set # 随机抽取set中的一个元素
"good"
127.0.0.1:6379> SRANDMEMBER set 2
1) "hello"
2) "good"
spop 随机删除集合中的元素
127.0.0.1:6379> SMEMBERS set
1) "hello"
2) "good"
127.0.0.1:6379> SPOP set
"hello"
smove 将指定的值从一个集合移动到另一个集合
127.0.0.1:6379> SMEMBERS set
1) "java"
2) "world"
3) "hello"
4) "good"
127.0.0.1:6379> SMEMBERS newset
1) "hi"
127.0.0.1:6379> SMOVE set newset java # 指定一个值,移动到另一个set集合
(integer) 1
127.0.0.1:6379> SMEMBERS set
1) "world"
2) "hello"
3) "good"
127.0.0.1:6379> SMEMBERS newset
1) "java"
2) "hi"
差集 sdiff 交集 sinter 并集sunion
127.0.0.1:6379> SDIFF set1 set2 # 差集
1) "a"
2) "c"
127.0.0.1:6379> sinter set1 set2 # 交集
1) "b"
127.0.0.1:6379> sunion set1 set2 # 并集
1) "d"
2) "a"
3) "c"
4) "b"
127.0.0.1:6379> SMEMBERS set1
1) "c"
2) "a"
3) "b"
127.0.0.1:6379> SMEMBERS set2
1) "d"
2) "b"
Hash(哈希)
Map集合,keyMap集合
hset hget hmset hmget
127.0.0.1:6379> hset myhash field1 a # 设置一个字段值
(integer) 1
127.0.0.1:6379> hget myhash field1
"a"
127.0.0.1:6379> hmset myhash field1 b field2 c # 设置多个字段值
(integer) 1
127.0.0.1:6379> hget myhash field1
"b"
127.0.0.1:6379> hget myhash field2
"c"
127.0.0.1:6379> hmget myhash field1 field2 # 获取多个字段值
1) "b"
2) "c"
127.0.0.1:6379> hgetall myhash # 获取全部的数据
1) "field1"
2) "b"
3) "field2"
4) "c"
127.0.0.1:6379> hdel myhash field1 # 删除hash指定的key字段
(integer) 1
127.0.0.1:6379> hgetall myhash
1) "field2"
2) "c"
hlen 获取长度
127.0.0.1:6379> hgetall myhash
1) "field2"
2) "c"
3) "field1"
4) "a"
127.0.0.1:6379> hlen myhash
(integer) 2
hexists 判断hash中的指定字段是否存在
127.0.0.1:6379> HEXISTS myhash field1
(integer) 1
获取所有field,获取所有value
127.0.0.1:6379> hkeys myhash
1) "field2"
2) "field1"
127.0.0.1:6379> HVALS myhash
1) "c"
2) "a"
hincrby 自增
127.0.0.1:6379> hset myhash field3 2
(integer) 1
127.0.0.1:6379> HINCRBY myhash field3 2 # 增加2
(integer) 4
127.0.0.1:6379> HINCRBY myhash field3 -2 # 减少2
(integer) 2
127.0.0.1:6379> HSETNX myhash field4 hello # field4不存在则创建
(integer) 1
127.0.0.1:6379> HSETNX myhash field4 hi # field4存在则失败
(integer) 0
hash变更的数据user name age,尤其是用户信息之类,经常变动的信息!hash更适合对象的存储,String更适合字符串存储!
Zset(有序集合)
zset k1 score1 v1
127.0.0.1:6379> zadd myset 1 one # 增加一个值
(integer) 1
127.0.0.1:6379> zadd myset 2 two 3 three # 添加多个值
(integer) 2
127.0.0.1:6379> zrange myset 0 -1
1) "one"
2) "two"
3) "three"
zrangebyscore
127.0.0.1:6379> ZRANGEBYSCORE myset -inf +inf # 从小到大排序
1) "one"
2) "two"
3) "three"
127.0.0.1:6379> ZREVRANGE myset 0 -1 # 从大到小排序
1) "three"
2) "two"
3) "one"
127.0.0.1:6379> ZRANGEBYSCORE myset -inf +inf withscores #显示全部的用户从小到大
1) "one"
2) "1"
3) "two"
4) "2"
5) "three"
6) "3"
127.0.0.1:6379> ZRANGEBYSCORE myset -inf +inf withscores
1) "newone"
2) "1"
3) "one"
4) "1"
5) "two"
6) "2"
7) "three"
8) "3"
移除rem中的元素
127.0.0.1:6379> zrem myset newone
(integer) 1
127.0.0.1:6379> zrange myset 0 -1
1) "one"
2) "two"
3) "three"
127.0.0.1:6379> zcard myset # 获取有序集合的个数
(integer) 3
获取指定区间的数量
127.0.0.1:6379> ZCOUNT myset 1 2
(integer) 2
案例思路:set排序 存储班级成绩表,工资表排序!
普通消息,1,重要消息,2,带权重进行判断
排行榜应用实现,取Top N测试!