Redis入门及数据类型
概述
Redis是什么?
Redis(Remote Dictionary Server ),即远程字典服务
是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API
redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步
免费和开源! 是当下最热门的Nosql技术之一! 也被称之为结构化数据库!
Rdeis能干嘛?
1.内存存储、持久化,由于内存是断电即失的,所以说持久化很重要(RDB和AOF)!
2.效率高,可以用于高速缓存
3.用于发布订阅功能的实现
4.地图信息分析
5.计时器,计数器 (比如说:微信、微博浏览量!)
6.等等.........
Redis的特性
1.多样的数据类型
2.持久化
3.集群
4.事务
...............
学习中需要用到的东西
1.Redis官网:https://redis.io/
2.中文网:http://www.redis.cn/
3.下载地址:通过官网下载即可
4.注意:Windows版本的要在GitHub上下载(停更很久了!)
Redis推荐都是在Linux服务器上搭建的,我们基于Linux学习!
Windows下安装Redis
1.下载安装包:https://github.com/microsoftarchive/redis/releases/tag/win-3.2.100
2.下载完毕得到压缩包:
3.解压到自己电脑上的环境目录就可以了! Redis十分的小,只有5M
4.开启Redis,双击运行服务即可
5.使用redis客户端来连接redis
记住一句话,Windows下使用确实简单,但是Redis官方推荐我们使用Linux去开发使用!
Linux下安装Redis
其实可以直接用宝塔面板直接傻瓜式安装,下面我们演示在Linux中手动如何安装:
1.下载安装包 redis-5.0.7.tar.gz
到Windows当中
2.我们使用xftp将其先临时上传到/home/kuangshen目录, 然后使用如下命令移动redis-5.0.7.tar.gz文件到/opt目录,并解压Redis的安装包!
mv redis-5.0.7.tar.gz /opt
tar -zxvf redis-5.0.7.tar.gz
3.进入解压后的文件,可以看到我们redis的配置文件:
4.基本的环境安装后再安装redis:
yum install gcc-c++
用gcc -v查看版本
然后输入make命令
如果make完成后继续执行 make install
5.查看默认安装目录:usr/local/bin
/usr 这是一个非常重要的目录,类似于windows下的Program Files,存放用户的程序
6.拷贝配置文件到应用程序目录下新建的kconfig目录
mkdir kconfig
cp /opt/redis-5.0.7/redis.conf kconfig
7.redis默认不是后台启动的,需要修改配置文件
vim redis.conf
-
A、redis.conf配置文件中daemonize守护线程,默认是NO。
-
B、daemonize是用来指定redis是否要用守护线程的方式启动。(设置成yes就是后台启动)
8.启动Redis服务!(以kconfig目录下的配置文件启动)
redis-server kconfig/redis.conf
使用如下命令使用redis客户端测试能不能连通(-h:主机默认本机不用写,只用写端口-p):
redis-cli -p 6379
10.查看redis的进程是否开启:
ps -ef|grep redis
11.如何关闭redis服务呢?
在redis-cli里面执行如下命令,同时关闭redis-cli和redis-server:
shutdown
exit
12.再次查看进程是否存在:
ps -ef|grep redis
13.后面我们会使用单机多Redis启动集群测试!
测试性能
redis-benchmark是一个压力测试工具,官方自带的性能测试工具!
redis 性能测试工具可选参数如下所示:
# 测试一:100个并发连接,100000个请求,检测host为localhost 端口为6379的redis服务器性能
redis-benchmark -h localhost -p 6379 -c 100 -n 100000
如何分析这些测试结果呢?举写入测试例子来看
基础知识
redis默认有16个数据库,redis-conf配置文件里面有写:
我们使用redis客户端redis-cli做演示:
默认使用的是第0个数据库
可以使用select切换数据库
127.0.0.1:6379> select 3 #切换数据库
OK
127.0.0.1:6379[3]> dbsize #查看DB大小!
(integer) 0
127.0.0.1:6379[3]> keys * #查看当前数据库所有的key
1) "name"
127.0.0.1:6379[3]> flushdb #清空当前数据库内容
OK
127.0.0.1:6379> flushall #flushall是清空所有的数据库内容
OK
Redis是单线程的!
明白Redis是很快的,官方表示,Redis是基于内存操作的,CPuU并不是Redis的性能瓶颈,Redis的瓶颈是机器的内存和网络带宽,既然可以使用单线程实现,就没有使用多线程。
但是redis6.0之后开始支持多线程了,只不过它支持的是处理网络IO的多线程!
Redis是C语言写的,官方提供的数据为100000+的QPS,完全不比同样是使用key-value的Memacached差!
Redis为什么单线程还这么快?
1.误区1:高性能的服务器一定是多线程的
2.误区2:多线程一定比单线程效率高
先去对 CPU>内存>硬盘的速度要有所了解!
核心: redis是将所有的数据全部放在内存中的,如果使用多线程CPU上下文切换是一个耗时的操作,而且redis还采用了IO多路复用的机制来增加效率,所以说使用单线程去操作效率就是最高的!
因为一次CPU上下文的切换大概在 1500ns 左右。从内存中读取 1MB 的连续数据,耗时大约为 250us,
假设1MB的数据由多个线程读取了1000次,那么就有1000次时间上下文的切换,那么就有1500ns *
1000 = 1500us ,我单线程的读完1MB数据才250us ,你光时间上下文的切换就用了1500us了,我还不
算你每次读一点数据 的时间。
五大数据类型
官网文档
全段翻译:
Redis是一个开放源代码(BSD许可)的内存中数据结构存储,用作数据库,缓存和消息代理。它支持数
据结构,例如字符串,哈希,列表,集合,带范围查询的排序集合,位图,超日志,带有半径查询和流
的地理空间索引。Redis具有内置的复制,Lua脚本,LRU驱逐,事务和不同级别的磁盘持久性,并通过
Redis Sentinel和Redis Cluster自动分区提供了高可用性。
Redis关于key的命令
下面的这些命令需要全部记住,后面使用springboot,jedis,所有的方法就是这些命令!
127.0.0.1:6379> keys * # 查看所有key
1) "name"
127.0.0.1:6379> flushall # 清空所有库内容
OK
127.0.0.1:6379> set name kuangshen # 设置内容
OK
127.0.0.1:6379> keys *
1) "name"
127.0.0.1:6379> set age 1
OK
127.0.0.1:6379> keys *
127.0.0.1:6379> keys *
1) "name"
2) "age"
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 1 # 移动某个key到几号数据库
(integer) 1
127.0.0.1:6379> keys *
1) "age"
127.0.0.1:6379> select 1
OK
127.0.0.1:6379[1]> keys *
1) "name"
127.0.0.1:6379[1]> select 0
OK
127.0.0.1:6379> keys *
1) "age"
127.0.0.1:6379> set name qinjiang
OK
127.0.0.1:6379> keys *
1) "name"
2) "age"
127.0.0.1:6379> get name #获取当前key的内容
"qinjiang"
127.0.0.1:6379> expire name 10 # 设置当前key的过期时间(单位是秒)
(integer) 1
127.0.0.1:6379> get name
"qinjiang"
127.0.0.1:6379> ttl name # 查看当前key的剩余时间
(integer) -2
127.0.0.1:6379> get name
(nil)
127.0.0.1:6379> set name qinjiang
OK
127.0.0.1:6379> keys *
1) "name"
2) "age"
127.0.0.1:6379> del name # 删除某个key
(integer) 1
127.0.0.1:6379> keys *
1) "age"
127.0.0.1:6379> type name # 查看当前key的类型
string
127.0.0.1:6379> type age
string
后面如果遇到不会的命令,可以再官网查看帮助文档:
String (字符串)
#####################################################################
127.0.0.1:6379> set key1 v1 # 设置字符串值
OK
127.0.0.1:6379> get key1 # 获得值
"v1"
127.0.0.1:6379> keys * # 获得所有的key
1) "key1"
127.0.0.1:6379> exists key1 # 判断某个key是否存在
(integer) 1
127.0.0.1:6379> append key1 "hello" # 往某个key中追加一个字符串,如果当前key不存在,则相当于set了这个key
(integer) 7
127.0.0.1:6379> get key1
"v1hello"
127.0.0.1:6379> strlen key1 # 获取字符串的长度
(integer) 7
127.0.0.1:6379> append key1 ",kuangshen"
(integer) 17
127.0.0.1:6379> strlen key1
(integer) 17
127.0.0.1:6379> get key1
"v1hello,kuangshen"
#####################################################################
# i++
# 步长 i+=
127.0.0.1:6379> set views 0 # 初始浏览量为0
OK
127.0.0.1:6379> get views
"0"
127.0.0.1:6379> incr views # 自增1 浏览量为1
(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> decr views # 自减1 浏览量减1
(integer) -1
127.0.0.1:6379> get views
"-1"
127.0.0.1:6379> INCRBY views 10 # 可以设置步长,指定增量!
(integer) 9
127.0.0.1:6379> INCRBY views 10
(integer) 19
127.0.0.1:6379> DECRBY views 5
(integer) 14
#####################################################################
# 字符串范围 range
127.0.0.1:6379> set key1 "hello,kuangshen" # 设置key1的值
OK
127.0.0.1:6379> get key1
"hello,kuangshen"
127.0.0.1:6379> GETRANGE key1 0 3 # 截取字符串 [0,3]闭区间,0和3位置的字符都是包括的
"hell"
127.0.0.1:6379> GETRANGE key1 0 -1 # 获取全部的字符串 和 get key是一样的
"hello,kuangshen"
# 字符串替换!
127.0.0.1:6379> set key2 abcdefg
OK
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"
#####################################################################
# setex (set with expire) :设置过期时间
# setnx(set if not exist): 假如不存在再设置(在分布式锁中会常常使用)
127.0.0.1:6379> setex key3 30 "hello" # 设置key3的值为hello,30秒后过期
OK
127.0.0.1:6379> ttl key3
(integer) 25
127.0.0.1:6379> get key3
"hello"
127.0.0.1:6379> get key3
"hello"
127.0.0.1:6379> ttl key3
(integer) 4
127.0.0.1:6379> get key3
(nil)
127.0.0.1:6379> setnx mykey "redis # 如果mykey不存在,创建mykey,
(integer) 1
127.0.0.1:6379> keys *
1) "mykey"
2) "key2"
3) "key1"
127.0.0.1:6379> ttl key3
(integer) -2
127.0.0.1:6379> setnx mykey "MongoDB" # 如果mykey存在,就创建失败
(integer) 0
127.0.0.1:6379> get mykey
"redis"
#####################################################################
# mset 同时设置多个值(m代表mutiple)
# mget 同时获取多个值 (m代表mutiple)
127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3 # 同时设置多个值
OK
127.0.0.1:6379> keys *
1) "k1"
2) "k2"
3) "k3"
127.0.0.1:6379> get v2
(nil)
127.0.0.1:6379> get k2
"v2"
127.0.0.1:6379> mget k1 k2 k3 # 同时获取多个值
1) "v1"
2) "v2"
3) "v3"
127.0.0.1:6379> msetnx k1 v8 k4 v4 # msetnx 假如不存在才设置值,存在则设置不进去,是一个原子性操作,要么一起成功,要么一起失败
(integer) 0
127.0.0.1:6379> keys *
1) "k1"
2) "k2"
3) "k3"
#####################################################################
# 对象
# 这是一种存对象的方法:
set user:1 {name:zhangsan,age:3} # 设置一个user:1对象,值为json字符串来保存一个对象!
# 还有一种存储对象的方法如下:
# 这里的key是一个巧妙地设计:user:{id}:{field},如此设计在Redis中是完全ok的!
127.0.0.1:6379> mset user:1:name zhangsan user:1:age 2
OK
127.0.0.1:6379> keys *
1) "user:1:age"
2) "k1"
3) "k2"
4) "k3"
5) "user:1:name"
127.0.0.1:6379> mget user:1:name user:1:age
1) "zhangsan"
2) "2"
127.0.0.1:6379> mset user:1:name zhangsan user:1:age 2
OK
127.0.0.1:6379> keys *
1) "user:1:age"
2) "k1"
3) "k2"
4) "k3"
5) "user:1:name"
127.0.0.1:6379> mget user:1:name user:1:age
1) "zhangsan"
2) "2"
#####################################################################
getset # 先get然后再set
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类似的使用场景:value除了使我们的字符串,还可以是我们的数字!
-
计数器
-
统计多单位的数量(像b站的粉丝数,获赞数等)
-
对象缓存存储!
List(列表)
基本的数据类型,列表
在redis里面,我们可以把list玩成,栈、队列、阻塞队列!
所有的list命令都是用l或者R来开头的,l和R的区别就是头插法和尾插法的区别(Redis命令不区分大小写)
#####################################################################
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中的值,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 right # 将一个值或者多个值,插入到列表的头部(向右插)
(integer) 4
127.0.0.1:6379> LRANGE list 0 -1
1) "three"
2) "two"
3) "one"
4) "right"
#####################################################################
LPOP # 移除列表的第一个元素
RPOP # 移除列表的最后一个元素
127.0.0.1:6379> LRANGE list 0 -1
1) "three"
2) "two"
3) "one"
4) "right"
127.0.0.1:6379> lpop list # 移除列表的第一个元素
"three"
127.0.0.1:6379> LRANGE list 0 -1
1) "two"
2) "one"
3) "right"
127.0.0.1:6379> rpop list # 移除列表的最后一个元素
"right"
127.0.0.1:6379> LRANGE list 0 -1
1) "two"
2) "one"
#####################################################################
Lindex # 通过下表获取列表中的某一个值
127.0.0.1:6379> LRANGE list 0 -1
1) "two"
2) "one"
127.0.0.1:6379> lindex list 1 # 通过下表获取列表中的某一个值
"one"
127.0.0.1:6379> lindex list 0
"two"
#####################################################################
Llen
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
#####################################################################
Lrem # 移除列表中指定的值!
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 # 移除列表中指定个数的value,精确匹配
(integer) 1
127.0.0.1:6379> LRANGE list 0 -1
1) "three"
2) "three"
3) "two"
127.0.0.1:6379> LREM list 1 three
(integer) 1
127.0.0.1:6379> LRANGE list 0 -1
1) "three"
2) "two"
127.0.0.1:6379> LPUSH list three
(integer) 3
127.0.0.1:6379> LREM list 2 three
(integer) 2
127.0.0.1:6379> LRANGE list 0 -1
1) "two"
#####################################################################
trim # 修剪列表,把原列表给变了,截取原列表某一部分作为新列表
127.0.0.1:6379> keys *
(empty list or set)
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"
#####################################################################
rpoplpush # 移除列表的最后一个元素,并将它移动到新的列表中
127.0.0.1:6379> keys *
(empty list or set)
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> rpoplpush mylist myotherlist # 移除列表的最后一个元素,并将它移动到新的列表中
"hello2"
127.0.0.1:6379> LRa
(error) ERR unknown command `LRa`, with args beginning with:
127.0.0.1:6379> LRANGE mylist 0 -1 # 查看原来的列表
1) "hello"
2) "hello1"
127.0.0.1:6379> LRANGE myotherList 0 -1
(empty list or set)
127.0.0.1:6379> LRANGE myotherlist 0 -1 # 查看目标列表中,确实存在该值!
1) "hello2"
#####################################################################
lset # 将列表中指定下标的值替换为另外一个值,相当于是一个更新操作
127.0.0.1:6379> keys *
(empty list or set)
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 value1 # 往列表里面放入一个值,就会存在
(integer) 1
127.0.0.1:6379> LRANGE list 0 0
1) "value1"
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> keys *
(empty list or set)
127.0.0.1:6379> rpush mylist "hello" # 构造一个列表并放入值
(integer) 1
127.0.0.1:6379> rpush mylist "world"
(integer) 2
127.0.0.1:6379> linsert mylist before "world" "other" # 将某个具体的value插入到列表中某个元素的前面或者后面
(integer) 3
127.0.0.1:6379> LRANGE mylist 0 -1
1) "hello"
2) "other"
3) "world"
127.0.0.1:6379> LINSERT mylist after world new # 将某个具体的value插入到列表中某个元素的前面或者后面
(integer) 4
127.0.0.1:6379> LRANGE mylist 0 -1
1) "hello"
2) "other"
3) "world"
4) "new"
#####################################################################
小结
- 它实际是一个链表, before Node after,可以在左边或者右边插入值
- 如果key(这个key代表链表名字)不存在,则需要创建新的链表
- 如果key(这个key代表链表名字)存在,则新增内容
- 如果移除了所有的值,则是空链表,也代表不存在!
- 在两边插入或者改动值,效率最高! 但是如果是对中间元素操作,效率相对来说效率会低一点!
Lpush Rpop :从左边进去,右边出来(队列)
Lpush Lpop:从左边进去,从左边出来(栈)
Set(集合)
set中的值是不能重复的!
#####################################################################
127.0.0.1:6379> sadd myset "hello" # 往set集合中添加元素
(integer) 1
127.0.0.1:6379> sadd myset "kuangshen"
(integer) 1
127.0.0.1:6379> sadd myset "lovekuangshen"
(integer) 1
127.0.0.1:6379> smembers myset # 查看指定set的所有值
1) "hello"
2) "kuangshen"
3) "lovekuangshen"
127.0.0.1:6379> SISMEMBER myset hello # 判断某一个值是不是在set集合中
(integer) 1
127.0.0.1:6379> SISMEMBER myset world # 判断某一个值是不是在set集合中
(integer) 0
#####################################################################
127.0.0.1:6379> SCARD myset # 获取set集合中的内容元素个数
(integer) 4
#####################################################################
srem # 移除set集合中的指定元素
127.0.0.1:6379> srem myset hello # 移除set集合中的指定元素
(integer) 1
127.0.0.1:6379> SCARD myset
(integer) 3
127.0.0.1:6379> SMEMBERS myset
1) "lovekuangshen2"
2) "kuangshen"
3) "lovekuangshen"
#####################################################################
srandmember # 由于set集合是无序不重复的集合,我们可以在抽奖品的场景使用这个命令的功能:抽随机
127.0.0.1:6379> SRANDMEMBER myset # 随机抽选出一个元素
"lovekuangshen"
127.0.0.1:6379> SRANDMEMBER myset
"kuangshen"
127.0.0.1:6379> SRANDMEMBER myset 2 # 随机抽选出指定个数的元素
1) "kuangshen"
2) "lovekuangshen"
#####################################################################
spop # 随机删除set集合的元素
127.0.0.1:6379> SMEMBERS myset
1) "lovekuangshen2"
2) "kuangshen"
3) "lovekuangshen"
127.0.0.1:6379> spop myset # 随机删除set集合的元素
"lovekuangshen"
127.0.0.1:6379> spop myset
"lovekuangshen2"
127.0.0.1:6379> SMEMBERS myset
1) "kuangshen"
#####################################################################
smove # 将一个指定的值,从一个set集合移动到另外一个set集合中
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 "kaungshen"
(integer) 1
127.0.0.1:6379> sadd myset2 "set2"
(integer) 1
127.0.0.1:6379> smove myset myset2 "kaungshen" # 将一个指定的值,从一个set集合移动到另外一个set集合中
(integer) 1
127.0.0.1:6379> SMEMBERS myset
1) "world"
2) "hello"
127.0.0.1:6379> SMEMBERS myset2
1) "set2"
2) "kaungshen"
#####################################################################
微博,B站,共同关注!(交集)
数字集合类:
- 差集
- 交集
- 并集
127.0.0.1:6379> sdiff key1 key2 # 差集:主要看key1集合哪些在key2集合中没有
1) "b"
2) "a"
127.0.0.1:6379> SINTER key1 key2 # 交集:共同关注就可以用这个实现
1) "c"
127.0.0.1:6379> SUNION key1 key2 # 并集
1) "e"
2) "c"
3) "a"
4) "b"
5) "d"
#####################################################################
共同关注,共同爱好,二度好友,推荐好友(六度分割理论!)
Hash(哈希)
我们可以把Hash想象成Map集合:
我们之前学的String、List、Set都是以key-value的形式存储!
而Hash是以key-Map集合的形式存储,可以想象成 key-
#####################################################################
127.0.0.1:6379> hset myhash field1 kuangshen # set一个具体的 key-value
(integer) 1
127.0.0.1:6379> hget myhash field1 # 获取某个key的值
"kuangshen"
127.0.0.1:6379> hmset myhash field1 hello field2 world # set多个 key-value
OK
127.0.0.1:6379> hmget myhash field1 field2 # 获取多个key的值
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 # 删除hash中指定的key,对应的value值也就没有了
(integer) 1
127.0.0.1:6379> HGETALL myhash
1) "field2"
2) "world"
#####################################################################
hlen # 获取hash表的字段数量
127.0.0.1:6379> hmset myhash field1 hello field2 world
OK
127.0.0.1:6379> hgetall myhash
1) "field2"
2) "world"
3) "field1"
4) "hello"
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 # 判断hash中指定字段是否存在!
(integer) 0
#####################################################################
# 只获得所有的key
# 只获得所有的value
127.0.0.1:6379> hkeys myhash # 只获得所有的key
1) "field2"
2) "field1"
127.0.0.1:6379> HVALS myhash # 只获得所有的value
1) "world"
2) "hello"
#####################################################################
incr # 增加操作
127.0.0.1:6379> hset myhash field3 5 # 只当增量
(integer) 1
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
#####################################################################
hash的应用:存一些变更的数据,尤其是用户信息之类或者经常变动的信息! hash更适合于对象的存储,String更适合字符串存储!
Zset(有序集合)
在set的基础上,增加了一个值 zxxx key 标志位 value
#####################################################################
127.0.0.1:6379> keys *
(empty list or set)
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 # 查看zset结合中所有元素
1) "one"
2) "two"
3) "three"
#####################################################################
排序如何实现?
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) "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 # 显示薪水小于2500的用户升序排列!
1) "kuangshen"
2) "500"
3) "xiaohong"
4) "2500"
#####################################################################
移除zset集合中的元素!
127.0.0.1:6379> ZRANGE salary 0 -1
1) "kuangshen"
2) "xiaohong"
3) "zhangsan"
127.0.0.1:6379> zrem salary xiaohong # 移除zset集合中的指定的某个元素
(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
(integer) 1
127.0.0.1:6379> zadd myset 2 world 3 kuangshen
(integer) 2
127.0.0.1:6379> zcount myset 1 3 # 获取指定区间(闭区间)的成员数量!(这个区间指的是标志位)
(integer) 3
127.0.0.1:6379> zcount myset 1 2
(integer) 2
#####################################################################
其余的一些api,通过我们的学习,剩下的如果工作中有需要,这个时候可以去查查看官方文档!
案例思路:zet是set的排序版
- 存储班级成绩表,工资表排序
- 我们可以对某个东西加权重,比如:普通消息,1,重要消息,2,需要带权重进行判断!
- B站排行榜应用实现
三种特殊数据类型
geospatial(地理位置)
它的本质像一个集合
朋友的定位,附近的人,打车距离计算,这些东西是怎么实现出来的呢?
Redis的Geo就可以,它在Redis3.2版本就推出了! 这个功能可以推算地理位置的信息:比如说两地之间的距离,方圆几里的人!
可以查一些测试数据的网站:http://www.jsons.cn/lngcode/
只有六个命令
geoadd
# geoadd 添加地理位置
# 规则:南极和北极无法直接添加,我们一般会下载城市数据,直接通过java程序一次性导入!
# 参数:key 值(经度,纬度,名称)
# 有效的经度介于-180-180度之间,有效的纬度介于-85.05112878 度至 85.05112878 度之间。, 当用户尝试输入一个超出范围的经度或者纬度时,geoadd命令将返回错误。
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 114.08 22.54 shengzheng
(integer) 2
127.0.0.1:6379> geoadd china:city 120.15 30.28 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"
geodist
两人之间的距离
单位:
-
m表示单位为米
-
km表示单位为千米
-
mi表示单位为英里
-
ft表示单位为英尺
127.0.0.1:6379> geodist china:city beijing shanghai km # 查看上海到北京的直线距离
"1067.3788"
127.0.0.1:6379> geodist china:city beijing chongqing km # 查看北京到重庆的直线距离
"1464.0708"
georadius 以给定的经纬度为中心,找出某一半径内的元素
我附近的人这个功能怎么实现?(1.获得所有附近的人的地址,在手机上会打开定位),通过半径来查询!
也可以跟参数count 来获得指定数量的人,如下:
127.0.0.1:6379> GEORADIUS china:city 110 30 1000 km # 以110 30 这个经纬度为中心,寻找方圆1000km内的城市
1) "chongqing"
2) "xian"
3) "shengzheng"
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 1 # 筛选出指定数量的结果
1) 1) "chongqing"
2) "341.9374"
3) 1) "106.49999767541885376"
2) "29.52999957900659211"
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"
127.0.0.1:6379> GEORADIUS china:city 110 30 500 km withdist withcoord count 3 # 筛选出指定数量的结果
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"
127.0.0.1:6379> georadiusbymember china:city shanghai 400 km
1) "hangzhou"
2) "shanghai"
geohash 返回一个或多个位置元素的 Geohash 表示。
该命令将返回11个字符的Geohash字符串!
# 将二维的经纬度转换为一维的hash字符串,如果两个字符串越接近,那么距离越近!
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) "shengzheng"
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) "shengzheng"
4) "hangzhou"
5) "shanghai"
Hyperloglog
什么是基数?
比如说有两个数据集:
A{1,3,5,7,8,7} 基数:5
B{1,3,5,7,8} 基数:5
基数是指一个集合中去掉重复的元素的个数!(去重后的数量)
简介
Redis 2.8.9版本就更新了hyperloglog 数据结构!
Redis hyperloglog是用来做基数统计的算法!
统计网页的UV(UV是Unique Visitor的缩写,即独立访客数,一个人访问网站多次,但是还是算作一个人!)
- 传统的方式:使用set保存用户的id,然后就可以统计set中的元素数量作为标准判断!
这个方式如果保存大量的用户id,比较占内存就会比较麻烦!我们的目的是为了计数,而不是保存用户id;
- 现在我们可以用hyperloglog的方式:
它虽然有0.81%的错误率,但是像统计UV任务等等,它是可以忽略不计的!
优点:占用的内存是固定的,比如它想放2^64个不同元素的基数,只需要费12KB的内存!如果要从内存角度来与set比较的话,Hyperloglog是首选!
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> PFCOUNT mykey2 i j z x c v b n m
(integer) 0
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
(integer) 9
127.0.0.1:6379> PFMERGE mykey3 mykey mykey2 # 合并两组mykey和mykey => mykey3 求并集
OK
127.0.0.1:6379> pfcount mykey3 # 查看合并之后mykey3的数量
(integer) 15
如果允许容错,那么一定可以使用Hyperloglog!
如果不允许容错,就使用set或者自己的数据类型即可!
Bitmap
位存储
统计疫情感染人数:0000010101
活跃人数、不活跃人数
登录、未登录
打卡、365天打卡!
两个状态的,都可以使用Bitmap!
Bitmap总的来说是位图,它也是一种数据结构!都是操作二进制位来进行记录,就只有0和1两个状态!
就比如说 365天 = 365bit 1字节 = 8 bit 大约只需46个字节左右,很省内存!
测试
使用Bitmap来记录 周一到周日的打卡!
周一:1 (打开) 周二:0 (未打卡)。。。。
127.0.0.1:6379> setbit sign 0 1 # 使用Bitmap设置某个下标的bit位数值
(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 0
(integer) 0
查看某一天是否有打卡!
127.0.0.1:6379> getbit sign 3 #得到某个下标的bit位的值
(integer) 1
127.0.0.1:6379> GETBIT sign 6
(integer) 0
统计操作,统计打卡的天数!
127.0.0.1:6379> BITCOUNT sign # 统计这周的打卡记录,就可以看到是否有全勤!
(integer) 3