Redis入门学习
一、Redis入门
1.1 Redis是什么
Redis(Remote Dictionary Server ),即远程字典服务,是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。 redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。 Redis 是一个高性能的key-value数据库。 redis的出现,很大程度补偿了memcached这类key/value存储的不足,在部分场合可以对关系数据库起到很好的补充作用。它提供了Java,C/C++,C#,PHP,JavaScript,Perl,Object-C,Python,Ruby,Erlang等客户端,使用很方便。
1.2 Redis能干嘛
- 内存存储,持久化,内存中是断电即失,所以说持久化很重要(rdb、aof)
- 效率高,可以用于高速缓存
- 发布订阅系统
- 地图信息分析
- 计时器、计数器
1.3 Redis的特性
- 多样的数据类型
- 持久化存储
- 集群
- 事物
二、Redis的安装和使用
2.1 在windows下安装Redis(不推荐)
1、双击 redis-server.exe,若闪退则在该目录下打开命令,输入 redis-server.exe redis.windows.conf 后回车,如下图:
2、成功后双击 redis-cli.exe 即可。
2.2 在虚拟机Linux下安装Redis(推荐)
1、下载安装包并解压
# 切换到root目录下
[root@wyc ~]# cd /root
# 在root目录下下载redis的安装包
[root@wyc ~]# wget http://download.redis.io/releases/redis-7.0.5.tar.gz
--2022-10-22 13:30:24-- http://download.redis.io/releases/redis-7.0.5.tar.gz
正在解析主机 download.redis.io (download.redis.io)... 45.60.125.1
正在连接 download.redis.io (download.redis.io)|45.60.125.1|:80... 已连接。
已发出 HTTP 请求,正在等待回应... 200 OK
长度:2968205 (2.8M) [application/octet-stream]
正在保存至: “redis-7.0.5.tar.gz”
100%[=============================================>] 2,968,205 862KB/s 用时 3.4s
2022-10-22 13:30:28 (862 KB/s) - 已保存 “redis-7.0.5.tar.gz” [2968205/2968205])
[root@wyc ~]# ls
anaconda-ks.cfg **redis-7.0.5.tar.gz** 模板 图片 下载 桌面
initial-setup-ks.cfg 公共 视频 文档 音乐
# 切换到opt目录下
[root@wyc ~]# cd /opt
[root@wyc opt]# ls
rh
[root@wyc opt]# cd /root
# 将redis安装包移动到opt目录下
[root@wyc ~]# mv redis-7.0.5.tar.gz /opt
[root@wyc ~]# ls
anaconda-ks.cfg initial-setup-ks.cfg 公共 模板 视频 图片 文档 下载 音乐 桌面
[root@wyc ~]# cd /opt
[root@wyc opt]# ls
**redis-7.0.5.tar.gz** rh
# 解压redis安装包
[root@wyc opt]# tar -zxvf redis-7.0.5.tar.gz
[root@wyc opt]# ls
redis-7.0.5 redis-7.0.5.tar.gz rh
2、进入解压后的文件,可以看到redis的配置文件
3、基本的环境安装
此处用到的命令有:
- yum install gcc-c++
- gcc -v
- make
- make install
[root@wyc redis-7.0.5]# gcc -v
使用内建 specs。
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/libexec/gcc/x86_64-redhat-linux/4.8.5/lto-wrapper
目标:x86_64-redhat-linux
配置为:../configure --prefix=/usr --mandir=/usr/share/man --infodir=/usr/share/info --with-bugurl=http://bugzilla.redhat.com/bugzilla --enable-bootstrap --enable-shared --enable-threads=posix --enable-checking=release --with-system-zlib --enable-__cxa_atexit --disable-libunwind-exceptions --enable-gnu-unique-object --enable-linker-build-id --with-linker-hash-style=gnu --enable-languages=c,c++,objc,obj-c++,java,fortran,ada,go,lto --enable-plugin --enable-initfini-array --disable-libgcj --with-isl=/builddir/build/BUILD/gcc-4.8.5-20150702/obj-x86_64-redhat-linux/isl-install --with-cloog=/builddir/build/BUILD/gcc-4.8.5-20150702/obj-x86_64-redhat-linux/cloog-install --enable-gnu-indirect-function --with-tune=generic --with-arch_32=x86-64 --build=x86_64-redhat-linux
线程模型:posix
gcc 版本 4.8.5 20150623 (Red Hat 4.8.5-44) (GCC)
[root@wyc redis-7.0.5]# yum install gcc-c++
已加载插件:fastestmirror, langpacks
Loading mirror speeds from cached hostfile
* base: [mirrors.ustc.edu.cn](http://mirrors.ustc.edu.cn/)
* extras: [mirrors.ustc.edu.cn](http://mirrors.ustc.edu.cn/)
* updates: [mirrors.ustc.edu.cn](http://mirrors.ustc.edu.cn/)
base | 3.6 kB 00:00:00
extras | 2.9 kB 00:00:00
updates | 2.9 kB 00:00:00
软件包 gcc-c++-4.8.5-44.el7.x86_64 已安装并且是最新版本
无须任何处理
[root@wyc redis-7.0.5]# make
...
[root@wyc redis-7.0.5]# make install
4、redis的默认安装路径 usr/local/bin
5、将redis的配置文件复制到我们当前目录下
6、redis默认不是后台启动,需要修改配置文件
此处用到的命令有:vim redis.conf
将daemonize的no改为yes
7、启动redis服务并使用redis-cli进行连接测试
此处用到的命令有:
- redis-server RedisConfig/redis.conf
- redis-cli -p 6379
8、查看redis的进程是否开启
9、如何关闭redis服务?
10、再次查看进程是否存在(另起一个终端)
此处用到的命令有:ps -ef|grep redis
2.3 查看CentOS的ip地址
输入ip查询命名 ip addr ,会出现如下所示的情况:centos的ip地址是ens33条目中的inet值
[root@wyc ~]# ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether 00:0c:29:70:f6:f4 brd ff:ff:ff:ff:ff:ff
inet 192.168.239.128/24 brd 192.168.239.255 scope global noprefixroute dynamic ens33
valid_lft 1666sec preferred_lft 1666sec
inet6 fe80::29ae:bc:3ee5:cc9f/64 scope link noprefixroute
valid_lft forever preferred_lft forever
3: virbr0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default qlen 1000
link/ether 52:54:00:d0:ea:2f brd ff:ff:ff:ff:ff:ff
inet 192.168.122.1/24 brd 192.168.122.255 scope global virbr0
valid_lft forever preferred_lft forever
4: virbr0-nic: <BROADCAST,MULTICAST> mtu 1500 qdisc pfifo_fast master virbr0 state DOWN group default qlen 1000
link/ether 52:54:00:d0:ea:2f brd ff:ff:ff:ff:ff:ff
2.4 性能测试
redis-benchmark 是一个压力测试工具,官方自带的性能测试工具
简单测试一下
# 100个并发连接 1000个请求
redis-benchmark -h localhost -p 6379 -c 100 -n 1000
如何查看这些分析?
三、Redis基础知识
3.1 Redis的简单操作
redis默认有16个数据库
默认使用的是第0个,可以使用select进行切换数据库
[root@wyc bin]# redis-server RedisConfig/redis.conf
[root@wyc bin]# redis-cli -p 6379
127.0.0.1:6379> ping
PONG
# 切换第四个数据库
127.0.0.1:6379> select 3
OK
# 查看数据库大小
127.0.0.1:6379[3]> dbsize
(integer) 0
127.0.0.1:6379[3]> set age 20
OK
127.0.0.1:6379[3]> dbsize
(integer) 1
127.0.0.1:6379[3]> get age
"20"
127.0.0.1:6379[3]> key *
(error) ERR unknown command 'key', with args beginning with: '*'
127.0.0.1:6379[3]> keys *
1) "age"
清除当前数据库内容 flushdb
127.0.0.1:6379[3]> keys *
1) "age"
127.0.0.1:6379[3]> flushdb
OK
127.0.0.1:6379[3]> keys *
(empty array)
清除全部数据库内容 flushall
127.0.0.1:6379> keys *
1) "mylist"
2) "key:__rand_int__"
3) "counter:__rand_int__"
4) "myhash"
5) "name"
127.0.0.1:6379> select 3
OK
127.0.0.1:6379[3]> set age 20
OK
127.0.0.1:6379[3]> keys *
1) "age"
127.0.0.1:6379[3]> flushall
OK
127.0.0.1:6379[3]> keys *
(empty array)
127.0.0.1:6379[3]> select 0
OK
127.0.0.1:6379> keys *
(empty array)
3.2 Redis是单线程的
官方表示,Redis是基于内存操作,CPU不是Redis的性能瓶颈,Redis的瓶颈是根据机器的内存和网络带宽,既然可以使用单线程来实现,所以就使用了单线程。Redis是C语言写的,官方提供的数据为100000+的QPS,完全不比同样是使用key-value的Memecache差。
Redis为什么单线程还这么快?
误区1:高性能的服务器一定是多线程的?
误区2:多线程(CPU上下文会切换)一定比单线程效率高?
核心:Redis是将所有的数据全部放在内存中的,所以说使用单线程去操作效率是最高的,多线程(CPU上下文会切换:耗时的操作)在设计和实现上会带来更多的复杂度。对于内存系统来说,如果没有上下文切换效率就是最高的。使用单线程可以省去多线程时CPU上下文切换的时间,不用去考虑锁的问题,不存在加锁释放锁等操作,没有死锁问题导致的性能消耗,在内存情况下单线程就是最佳的方案。
3.3 Redis部分常用命令
keys * # 查看所有的key
flushdb # 清空当前数据库,
flushall # 清空所有数据库
exists key # 判断值是否存在
move name 1 # 移动key为name到数据库1
expire name 10 # 设置key为name的值10s后过期
ttl name # 查看当前key剩余秒数
type name # 查看当前key为name的值类型
append key abc # 追加abc到key的末尾
strlen key # 查看key的长度
后面如果遇到不会的命令,可以在官网查看帮助文档
3.4 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)。
3.4.1 String(字符串)
# strlen 获取字符串的长度
# exists 判断key是否存在
127.0.0.1:6379> set name zhangsan # 设置值
OK
127.0.0.1:6379> keys * # 获得所有的key
1) "name"
127.0.0.1:6379> get name # 获得值
"zhangsan"
127.0.0.1:6379> append name Hello # 追加字符串,如果当前key不存在,就相当于set key
(integer) 13
127.0.0.1:6379> get name
"zhangsanHello"
127.0.0.1:6379> append age 18 # 此时相当于set age 18
(integer) 2
127.0.0.1:6379> get age
"18"
127.0.0.1:6379> strlen name # 获取字符串的长度
(integer) 13
127.0.0.1:6379> exists name # 判断key是否存在
(integer) 1
######################################################################
# incr decr incrby decrby 步长
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,浏览量+1
(integer) 1
127.0.0.1:6379> get views
"1"
127.0.0.1:6379> incr views # 自增1,浏览量+1
(integer) 2
127.0.0.1:6379> get views
"2"
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 # 设置步长,指定增加量为10
(integer) 11
127.0.0.1:6379> get views
"11"
127.0.0.1:6379> decrby views 20 # 设置步长,指定减少量为20
(integer) -9
127.0.0.1:6379> get views
"-9"
######################################################################
# getrange 获取指定范围的字符串
127.0.0.1:6379> set key1 "hello,world!" # 设置key1的值
OK
127.0.0.1:6379> get key1
"hello,world!"
127.0.0.1:6379> getrange key1 0 3 # 截取字符串 [0,3]
"hell"
127.0.0.1:6379> getrange key1 0 -1 # 获取全部字符串,和get key一样
"hello,world!"
# setrange 替换指定位置的字符串
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 BCDEFG # 替换指定位置的字符串
(integer) 7
127.0.0.1:6379> get key2
"aBCDEFG"
######################################################################
# setex (set with expire) 设置过期时间
# setnx (set if not exist) 将key设置值为value,只有当key不存在时有效(在分布式锁中常常使用)
127.0.0.1:6379> setex key3 10 good # 设置key的值为good,10秒后过期
OK
127.0.0.1:6379> ttl key3 # 查看剩余时间
(integer) 6
127.0.0.1:6379> setnx key4 "redis" # 如果key4不存在,则创建key4,返回1
(integer) 1
127.0.0.1:6379> keys * # 此时key3已经过期
1) "key1"
2) "key4"
3) "key2"
127.0.0.1:6379> setnx key4 "mongoDB" # 如果key4存在,则创建失败,返回0
(integer) 0
127.0.0.1:6379> get key4
"redis"
######################################################################
# mset 同时设置多个值
# mget 同时获取多个值
# msetnx 只要有一个key已经存在,该操作就不会执行
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 # msetnx是一个原子性的操作,要么一起成功,要么一起失败
(integer) 0
127.0.0.1:6379> get k4
(nil)
# 对象
# 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 20
OK
127.0.0.1:6379> mget user:1:name user:1:age
1) "zhangsan"
2) "20"
######################################################################
# 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小结:
String是Redis中最简单的一种数据结构
String的使用场景如下:
- 计数值
- 统计多单位的数量
- 粉丝数
- 对象缓存存储
3.4.2 List(列表)
# lpush 将值值插入到列表头部(左)
# rpush 将值值插入到列表尾部(右)
# lrange 获取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"
127.0.0.1:6379> lrange list 0 -1 # 获取list中的值
1) "three"
2) "two"
3) "one"
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> lpop list # 移除list的第一个元素
"three"
127.0.0.1:6379> lrange list 0 -1
1) "two"
2) "one"
3) "right"
127.0.0.1:6379> rpop list # 移除list的最后一个元素
"right"
127.0.0.1:6379> lrange list 0 -1
1) "two"
2) "one"
# lrem 移除指定的值
127.0.0.1:6379> lrange list 0 -1 # list中元素可以重复
1) "three"
2) "three"
3) "two"
4) "one"
127.0.0.1:6379> lrem list 2 three # 移除list中的2个three
(integer) 2
127.0.0.1:6379> lrange list 0 -1
1) "two"
2) "one"
######################################################################
# lindex 通过下标索引获取值
127.0.0.1:6379> lindex list 0 # 通过下标获得list中的某一个值
"two"
127.0.0.1:6379> lindex list 1
"one"
######################################################################
# llen 获取长度
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "two"
3) "one"
127.0.0.1:6379> llen list # 返回列表的长度
(integer) 3
######################################################################
# trim 修剪空格
# ltrim 截断并截取
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> lrange mylist 0 -1
1) "hello1"
2) "hello2"
3) "hello3"
127.0.0.1:6379> rpoplpush mylist myotherlist # 移除列表的最后一个元素并将它移动到新的列表中
"hello3"
127.0.0.1:6379> lrange mylist 0 -1 # 查看原来的列表
1) "hello1"
2) "hello2"
127.0.0.1:6379> lrange myotherlist 0 -1 # 查看新列表中确实有该值
1) "hello3"
######################################################################
# 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 hello
(integer) 1
127.0.0.1:6379> lrange list 0 0
1) "hello"
127.0.0.1:6379> lset list 0 world # 如果存在该列表,更新当前下表的值
OK
127.0.0.1:6379> lrange list 0 0
1) "world"
127.0.0.1:6379> lset list 1 good # 如果不存在该下标,则会报错
(error) ERR index out of range
######################################################################
# linsert 将某个具体的value插入到列表中某个元素的前面或后面
127.0.0.1:6379> lpush mylist hello
(integer) 1
127.0.0.1:6379> rpush mylist world
(integer) 2
127.0.0.1:6379> lrange mylist 0 -1
1) "hello"
2) "world"
127.0.0.1:6379> linsert mylist before world beautiful # 在world前面插入beautiful
(integer) 3
127.0.0.1:6379> lrange mylist 0 -1
1) "hello"
2) "beautiful"
3) "world"
127.0.0.1:6379> linsert mylist after world Yeah # 在world后面插入Yeah
(integer) 4
127.0.0.1:6379> lrange mylist 0 -1
1) "hello"
2) "beautiful"
3) "world"
4) "Yeah"
List小结:
所有list的命令都是l开头的,不区分大小写
使用lpush rpop(先进先出)命令则为消息队列,使用lpush lpop(先进后出)命令则为栈
- list实际上是一个链表,before Node after、left、right都可以插入值
- 如果key不存在,创建新的链表
- 如果key存在,新增内容
- 如果移除了所有值,空链表,也代表不存在
- 在两边插入或者改动值效率最高,在中间插入元素,相对来说效率会低一点
3.4.3 Set(无序集合)
# sadd 在集合中添加元素
# smembers 查看指定set的所有值
# sismember 判断某个值是否在set集合中
127.0.0.1:6379> sadd myset hello # set集合中添加元素
(integer) 1
127.0.0.1:6379> sadd myset world
(integer) 1
127.0.0.1:6379> smembers myset # 查看指定set的所有值
1) "world"
2) "hello"
127.0.0.1:6379> sismember myset hello # 判断某个值是否在set集合中,是则返回1,不是则返回0
(integer) 1
127.0.0.1:6379> sismember myset helloworld
(integer) 0
######################################################################
# scard 获取个数
127.0.0.1:6379> scard myset # 获取set集合的个数
(integer) 2
127.0.0.1:6379> sadd myset hello # set集合中的值不能重复
(integer) 0
127.0.0.1:6379> sadd myset good
(integer) 1
127.0.0.1:6379> sadd myset morning
(integer) 1
127.0.0.1:6379> scard myset # 获取set集合的个数
(integer) 4
######################################################################
# srem 移除指定元素
127.0.0.1:6379> srem myset morning # 移除set集合中的morning元素
(integer) 1
127.0.0.1:6379> smembers myset
1) "good"
2) "world"
3) "hello"
######################################################################
# srandmember 随机抽取元素
127.0.0.1:6379> smembers randomset
1) "computer"
2) "iPhone"
3) "iPad"
4) "keyboard"
5) "mouse"
127.0.0.1:6379> srandmember randomset # 随机抽取一个元素
"keyboard"
127.0.0.1:6379> srandmember randomset
"iPhone"
127.0.0.1:6379> srandmember randomset
"computer"
127.0.0.1:6379> srandmember randomset 3 # 随机抽取指定个数的元素
1) "iPhone"
2) "iPad"
3) "keyboard"
127.0.0.1:6379> srandmember randomset 2
1) "computer"
2) "iPad"
######################################################################
# spop 删除指定key中随机的元素
127.0.0.1:6379> smembers randomset
1) "iPhone"
2) "computer"
3) "mouse"
4) "iPad"
5) "keyboard"
127.0.0.1:6379> spop randomset # 随机删除set集合中的一个元素
"iPad"
127.0.0.1:6379> spop randomset
"iPhone"
127.0.0.1:6379> smembers randomset
1) "computer"
2) "mouse"
3) "keyboard"
######################################################################
# smove 将一个指定的值移动到另一个set集合中
127.0.0.1:6379> sadd myset1 world
(integer) 1
127.0.0.1:6379> sadd myset1 good
(integer) 1
127.0.0.1:6379> sadd myset1 morning
(integer) 1
127.0.0.1:6379> sadd myset2 byebye
(integer) 1
127.0.0.1:6379> sadd myset2 seeyou
(integer) 1
127.0.0.1:6379> smove myset1 myset2 good # 将一个指定的值移动到另一个set集合中
(integer) 1
127.0.0.1:6379> smembers myset1
1) "world"
2) "morning"
3) "hello"
127.0.0.1:6379> smembers myset2
1) "good"
2) "seeyou"
3) "byebye"
######################################################################
# 微博和B站:共同关注(交集)
# 数字集合类:
# 交集:sinter
# 并集:sunion
# 差集:sdiff
127.0.0.1:6379> smembers key1
1) "b"
2) "a"
3) "c"
127.0.0.1:6379> smembers key2
1) "d"
2) "b"
3) "c"
127.0.0.1:6379> sdiff key1 key2 # 差集
1) "a"
127.0.0.1:6379> sdiff key2 key1 # 差集
1) "d"
127.0.0.1:6379> sinter key1 key2 # 交集:共同好友
1) "b"
2) "c"
127.0.0.1:6379> sunion key1 key2 # 并集
1) "a"
2) "c"
3) "b"
4) "d"
Set小结:
- Set是无序不重复集合,这是Set集合最大的特点
- Set适用范围:共同关注、共同爱好、二度好友、推荐好友(六度分割理论)
3.4.4 Hash(哈希)
# hset 添加一个key-value键值对
# hget 获取指定key的value值
# hmset 添加多个key-value键值对
# hmget 获取多个key的value值
# hgetall 获取全部key-value键值对
# hdel 删除指定key
127.0.0.1:6379> hset myhash name zhangsan # set一个具体的key-value
(integer) 1
127.0.0.1:6379> hget myhash name # 获取指定key的value值
"zhangsan"
127.0.0.1:6379> hmset myhash name1 lisi name2 wangwu # set多个具体的key-value
OK
127.0.0.1:6379> hmget myhash name name1 name2 # 获取多个key的value值
1) "zhangsan"
2) "lisi"
3) "wangwu"
127.0.0.1:6379> hgetall myhash # 获取全部的key-value
1) "name"
2) "zhangsan"
3) "name1"
4) "lisi"
5) "name2"
6) "wangwu"
127.0.0.1:6379> hdel myhash name # 删除指定的key字段,对应的value值也消失了
(integer) 1
127.0.0.1:6379> hgetall myhash
1) "name1"
2) "lisi"
3) "name2"
4) "wangwu"
######################################################################
# hlen 获取字段数量
127.0.0.1:6379> hgetall myhash
1) "name1"
2) "lisi"
3) "name2"
4) "wangwu"
127.0.0.1:6379> hlen myhash # 获取hash表的字段数量
(integer) 2
127.0.0.1:6379> hmset myhash name3 aaa name4 bbb
OK
127.0.0.1:6379> hlen myhash # 获取hash表的字段数量
(integer) 4
######################################################################
# hexists 判断指定字段是否存在
127.0.0.1:6379> hexists myhash name # 判断指定字段是否存在,存在则返回1,不存在则返回0
(integer) 0
127.0.0.1:6379> hexists myhash name1
(integer) 1
######################################################################
# hkeys 只获得所有的key(field)
# hvals 只获得所有的value
127.0.0.1:6379> hkeys myhash # 只获得所有的key(field)
1) "name1"
2) "name2"
3) "name3"
4) "name4"
127.0.0.1:6379> hvals myhash # 只获得所有的value
1) "lisi"
2) "wangwu"
3) "aaa"
4) "bbb"
######################################################################
# hincrby 指定增量
# hsetnx 只在key指定的hash集中不存在指定的字段时,设置字段的值
127.0.0.1:6379> hset hash field 1
(integer) 1
127.0.0.1:6379> hincrby hash field 10 # 指定增量
(integer) 11
127.0.0.1:6379> hincrby hash field -15
(integer) -4
127.0.0.1:6379> hsetnx hash field hello # 如果存在则不能设置
(integer) 0
127.0.0.1:6379> hsetnx hash field1 hello # 如果不存在则可以设置
(integer) 1
Hash小结:
- Hash是一个键值对集合,本质和String类型没有太大区别,是一个String类型的映射
- Hash更适合于对象的存储,尤其是用户信息等经常变动的信息,String更适合于对字符串的存储。
3.4.5 Zset(有序集合)
# zadd 添加一个值
# zrange 遍历所有值
127.0.0.1:127.0.0.1:6379> zadd zset 1 one # 添加一个值
(integer) 1
127.0.0.1:6379> zadd zset 2 two
(integer) 1
127.0.0.1:6379> zadd zset 3 three
(integer) 1
127.0.0.1:6379> zadd zset 4 four 5 five # 添加多个值
(integer) 2
127.0.0.1:6379> zrange zset 0 -1 # 遍历所有值
1) "one"
2) "two"
3) "three"
4) "four"
5) "five"
######################################################################
# 排序
# zrangebyscore 返回有序集合中指定sroce区间内的成员,score由低到高排序
# zrevrange 返回有序集合中指定sroce区间内的成员,score由高到低排序
127.0.0.1:6379> zadd salary 4000 zhangsan 5000 lisi 3000 wangwu # 添加3个用户
(integer) 3
127.0.0.1:6379> zrangebyscore salary -inf +inf # 显示全部用户,按score由低到高排序
1) "wangwu"
2) "zhangsan"
3) "lisi"
127.0.0.1:6379> zrevrange salary 0 -1 # 显示全部用户,按score由高到低排序
1) "lisi"
2) "zhangsan"
3) "wangwu"
127.0.0.1:6379> zrangebyscore salary -inf +inf withscores # 显示全部用户和score值升序排列
1) "wangwu"
2) "3000"
3) "zhangsan"
4) "4000"
5) "lisi"
6) "5000"
127.0.0.1:6379> zrangebyscore salary -inf 4000 withscores # 显示score小于等于4000的用户和score值升序排列
1) "wangwu"
2) "3000"
3) "zhangsan"
4) "4000"
######################################################################
# zrem 移除元素
# zcard 获取个数
127.0.0.1:6379> zrange salary 0 -1
1) "wangwu"
2) "zhangsan"
3) "lisi"
127.0.0.1:6379> zrem salary zhangsan # 移除有序集合中的指定元素
(integer) 1
127.0.0.1:6379> zrange salary 0 -1
1) "wangwu"
2) "lisi"
127.0.0.1:6379> zcard salary # 获取有序集合中的个数
(integer) 2
######################################################################
# zcount 获取数量
127.0.0.1:6379> zcount salary 3000 4000 # 获取指定区间的成员数量
(integer) 2
127.0.0.1:6379> zcount salary 3000 6000
(integer) 3
Zset小结:
- zset与set类似,都是没有重复元素的集合
- zset在set的基础上增加了score这个参数,score可以重复
其余的一些API,如遇工作中有需要,可以去查官方文档
3.5 三种特殊数据类型
3.5.1 Geospatial地理位置
朋友的定位、附近的人、打车距离计算等如何实现?
Redis的Geo在Redis3.2版本就推出了,这个功能可以推算出地理位置的信息、两地之间的距离、方圆几里的人等。
可以查询一些数据测试:城市经度纬度查询
geospatial有以下六个命令:
- GEOADD
- GEODIST
- GEOHASH
- GEOPOS
- GEORADIUS
- GEORADIUSBYMEMBER
Geoadd
规则:地球两极无法直接添加,我们一般会下载城市数据,直接通过java程序一次性导入。
参数:纬度、经度、名称。
有效的经度从-180度到180度。
有效的纬度从-85.05112878度到85.05112878度。
# 经度纬度写反会报错
127.0.0.1:6379> geoadd China:city 39.904989 116.405285 Beijing
(error) ERR invalid longitude,latitude pair 39.904989,116.405285
# 添加一个地理位置
127.0.0.1:6379> geoadd China:city 116.405285 39.904989 Beijing
(integer) 1
127.0.0.1:6379> geoadd China:city 121.472644 31.231706 Shanghai
(integer) 1
127.0.0.1:6379> geoadd China:city 113.280637 23.125178 Guangzhou
(integer) 1
# 添加多个地理位置
127.0.0.1:6379> geoadd China:city 120.619585 31.299379 Suzhou 120.153576 30.287459 Hangzhou
(integer) 2
Geopos
从 key 里返回所有给定位置元素的位置(经度和纬度)。
geopos 获取当前的定位,一定是一个坐标值。
# 获取一个地理位置的经纬度
127.0.0.1:6379> geopos China:city Beijing
1) 1) "116.40528291463851929"
2) "39.9049884229125027"
# 获取多个地理位置的经纬度
127.0.0.1:6379> geopos China:city Shanghai Suzhou
1) 1) "121.47264629602432251"
2) "31.23170490709807012"
2) 1) "120.61958581209182739"
2) "31.29937942733126022"
Geodist
返回两个给定位置之间的距离。
如果两个位置之间的其中一个不存在, 那么命令返回空值。
指定单位的参数 unit 必须是以下单位的其中一个:
- m 表示单位为米。
- km 表示单位为千米。
- mi 表示单位为英里。
- ft 表示单位为英尺。
# 获取北京到上海的直线距离
127.0.0.1:6379> geodist China:city Beijing Shanghai
"1067597.9668"
# 获取北京到上海的直线距离,以千米为单位
127.0.0.1:6379> geodist China:city Beijing Shanghai km
"1067.5980"
Georadius
以给定的经纬度为中心, 返回键包含的位置元素当中, 与中心的距离不超过给定最大距离的所有位置元素。
范围可以使用以下其中一个单位:
- m 表示单位为米。
- km 表示单位为千米。
- mi 表示单位为英里。
- ft 表示单位为英尺。
附近的人可以通过该命令实现。
# 以110 30经纬度为中心,寻找方圆2000km的城市
127.0.0.1:6379> georadius China:city 110 30 2000 km
1) "Guangzhou"
2) "Hangzhou"
3) "Suzhou"
4) "Shanghai"
5) "Beijing"
127.0.0.1:6379> georadius China:city 110 30 1000 km
1) "Guangzhou"
2) "Hangzhou"
# withdist 将位置元素与中心之间的距离也一并返回
127.0.0.1:6379> georadius China:city 110 30 1000 km withdist
1) 1) "Guangzhou"
2) "831.2636"
2) 1) "Hangzhou"
2) "976.8197"
# withcoord 将位置元素的经度和维度也一并返回
127.0.0.1:6379> georadius China:city 110 30 1000 km withcoord
1) 1) "Guangzhou"
2) 1) "113.28063815832138062"
2) "23.12517743834835215"
2) 1) "Hangzhou"
2) 1) "120.15357345342636108"
2) "30.28745790721532671"
# count 筛选个数
127.0.0.1:6379> georadius China:city 110 30 1000 km withcoord withdist count 1
1) 1) "Guangzhou"
2) "831.2636"
3) 1) "113.28063815832138062"
2) "23.12517743834835215"
Georadiusbymember
与georadius类似,区别在于 Georadius 是由输入的经度和纬度来决定中心点,而Georadiusbymember 是由给定的位置元素决定中心点。
127.0.0.1:6379> georadiusbymember China:city Beijing 1000 km
1) "Beijing"
127.0.0.1:6379> georadiusbymember China:city Shanghai 500 km
1) "Hangzhou"
2) "Suzhou"
3) "Shanghai"
Geohash
返回一个或多个位置元素的 Geohash 表示,该命令将返回11个字符的Geohash字符串。
将二维的经纬度转换为一维的字符串,两个字符串越接近,两地距离越近。
127.0.0.1:6379> geohash China:city Guangzhou Beijing
1) "ws0e9cb3yj0"
2) "wx4g0b7xrt0"
Geo原理
Geo底层的实现原理其实是Zset,我们可以使用Zset命令来操作GEO
# 查看地图中全部元素
127.0.0.1:6379> zrange China:city 0 -1
1) "Guangzhou"
2) "Hangzhou"
3) "Suzhou"
4) "Shanghai"
5) "Beijing"
# 移除指定元素
127.0.0.1:6379> zrem China:city Beijing
(integer) 1
127.0.0.1:6379> zrange China:city 0 -1
1) "Guangzhou"
2) "Hangzhou"
3) "Suzhou"
4) "Shanghai"
3.5.2 Hyperloglogs基数统计
Redis2.8.9版本就更新了Hyperloglogs数据结构,Hyperloglogs是用来做基数统计的算法的。
1、什么是基数
集合中不重复元素的个数即为基数。
2、Hyperloglogs的特点
- Hyperloglogs是一种概率数据结构,用于统计不重复的事物(类似set集合)
- Hyperloglogs的优点是占用内存不会超过12KB,如果从内存角度来讲Hyperloglogs是首选
- Hyperloglogs算法存在一定误差,误差小于1%,大约0.81%,可以忽略不计
测试:
# pfadd 创建第一组元素mykey1
127.0.0.1:6379> pfadd mykey1 a b c d e f g
(integer) 1
# pfcount 统计mykey1元素的基数数量
127.0.0.1:6379> pfcount mykey1
(integer) 7
# pfadd 创建第二组元素mykey2
127.0.0.1:6379> pfadd mykey2 h i j k l m n a b c
(integer) 1
# pfcount 统计mykey2元素的基数数量
127.0.0.1:6379> pfcount mykey2
(integer) 10
# pfmerge 合并两组,并集为mykey3
127.0.0.1:6379> pfmerge mykey3 mykey1 mykey2
OK
# pfcount 统计并集mykey3元素的基数数量
127.0.0.1:6379> pfcount mykey3
(integer) 14
如果允许容错,那么一定可以使用Hyperloglogs;
如果不允许容错,就使用set或者自己的数据类型即可。
3.5.3 Bitmaps位图
Bitmaps主要用于用户打卡,用户活跃天数等场景。
Bitmaps位图是一种数据结构,操作二进制位来进行记录,只有0和1两种状态。
测试:
使用Bitmaps来记录周一到周日的打卡情况:
周一:1、周二:1、周三:1、周四:0、周五:1、周六:0、周日:0
# setbit 设置位操作
127.0.0.1:6379> setbit sign 0 1
(integer) 0
127.0.0.1:6379> setbit sign 1 1
(integer) 0
127.0.0.1:6379> setbit sign 2 1
(integer) 0
127.0.0.1:6379> setbit sign 3 0
(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
查看某一天是否打卡:1为打卡,0为未打卡
# getbit 读取位操作
127.0.0.1:6379> getbit sign 0
(integer) 1
127.0.0.1:6379> getbit sign 2
(integer) 1
127.0.0.1:6379> getbit sign 6
(integer) 0
统计操作,统计一周的打卡天数
# bitcount 统计1的次数
127.0.0.1:6379> bitcount sign
(integer) 4
3.6 事务
讲到事务首先会想到关系型数据库事务的四大特性(ACID):
- 原子性(Atomicity):事务的原子性保证事务中包含的一组更新操作是原子的,不可分割的。简单来说,事务以原子为工作单位,要么全部执行,要么全部不执行,不会停滞在中间环节。
- 一致性(Consistency):事务的一致性要求事务开始前和结束后都必须满足数据库的完整性约束,且事务执行完毕后会将数据库由一个一致性的状态变为另一个一致性的状态。
- 隔离性(Isolation): 事务的隔离性要求事务之间是彼此独立的,隔离的,一个事务的执行不能被其他事务所影响。
- 持久性(Durability):事务的持久性要求事务一旦提交后,它对数据库的改变是永久性的,不可再回滚。
3.6.1 Redis事务的概念
Redis事务本质:一组命令的集合,一个事务中的所有命令都会被序列化,在事务执行过程中,会按照顺序执行,不会被其他命令插入。即:Reids的事务是一次性、顺序性、排他性的执行命令。
Redis事务特点
- Redis事务没有隔离级别的概念
- 所有的命令在事务中,并没有直接被执行,只有发起执行命令的时候才会执行
- Redis单条命令是保证原子性的,但是事务不保证原子性
Redis事务的三个阶段
- 开启事务(multi)
- 命令入队(…)
- 执行事务(exec)
3.6.2 正常执行事务(MULTI、EXEC)
# 开启事务
127.0.0.1:6379> multi
OK
# 命令入队
127.0.0.1:6379(TX)> set key1 value1
QUEUED
127.0.0.1:6379(TX)> set key2 value2
QUEUED
127.0.0.1:6379(TX)> get key1
QUEUED
127.0.0.1:6379(TX)> get key2
QUEUED
# 执行事务
127.0.0.1:6379(TX)> exec
1) OK
2) OK
3) "value1"
4) "value2"
3.6.3 放弃事务(DISCARD)
# 开启事务
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> sadd myset set1
QUEUED
127.0.0.1:6379(TX)> sadd myset set2
QUEUED
# 取消事务
127.0.0.1:6379(TX)> discard
OK
# 事务队列中的命令都不会被执行
127.0.0.1:6379> smembers myset
(empty array)
3.6.4 事务执行时的异常
1、编译型异常
如果代码有异常、命令有错,事务中所有的命令都不会被执行。
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)> set k5 v5
QUEUED
# 执行事务时也会报错
127.0.0.1:6379(TX)> exec
(error) EXECABORT Transaction discarded because of previous errors.
# 所有的命令都不会被执行
127.0.0.1:6379> get k5
(nil)
2、运行时异常
如果事务队列中存在语法错误,那么执行命令的时候其他命令是可以正常执行的,错误的命令会抛出异常。
127.0.0.1:6379> set key "hello"
OK
127.0.0.1:6379> multi
OK
# 命令入队时不报错,但执行时会报错
127.0.0.1:6379(TX)> incr key
QUEUED
127.0.0.1:6379(TX)> set key2 value2
QUEUED
127.0.0.1:6379(TX)> set key3 value3
QUEUED
127.0.0.1:6379(TX)> get key
QUEUED
# 虽然第一条命令报错了,但是依旧正常执行成功了
127.0.0.1:6379(TX)> exec
1) (error) ERR value is not an integer or out of range
2) OK
3) OK
4) "hello"
127.0.0.1:6379> get key2
"value2"
127.0.0.1:6379> get key3
"value3"
3.6.5 监控(WATCH)
悲观锁:很悲观,认为什么时候都会出问题,无论做什么都会加锁。
乐观锁:很乐观,认为什么时候都不会出问题,所以不会上锁。更新数据的时候会去判断一下在此期间是否有人修改过这个数据。即:获取version,更新时比较version。
测试:
1、正常执行成功:
127.0.0.1:6379> set balance 10000
OK
127.0.0.1:6379> set expend 0
OK
# 监视balance对象
127.0.0.1:6379> watch balance
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> decrby balance 100
QUEUED
127.0.0.1:6379(TX)> incrby expend 100
QUEUED
# 事务正常结束,数据期间没有发生变动,这个时候就是正常执行成功
127.0.0.1:6379(TX)> exec
1) (integer) 9900
2) (integer) 100
2、执行失败
测试多线程,使用watch可以当作Redis的乐观锁操作。
# 第一个线程
127.0.0.1:6379> watch balance
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> decrby balance 200
QUEUED
127.0.0.1:6379(TX)> incrby expend 200
QUEUED
# 在第二个线程充值完100元后第一个线程会执行失败
127.0.0.1:6379(TX)> exec
(nil)
######################################################################
# 第二个线程
127.0.0.1:6379> get balance
"9900"
# 在第一个线程未执行前给balance充值100元
127.0.0.1:6379> incrby balance 100
(integer) 10000
如果修改失败,获取最新值即可。
# 如果发现失误执行失败,先unwatch解锁
127.0.0.1:6379> unwatch
OK
# 获取最新的值,再次监视balance (select version)
127.0.0.1:6379> watch balance
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> decrby balance 500
QUEUED
127.0.0.1:6379(TX)> incrby expend 500
QUEUED
# 对比监视的值是否发生变化,如果没有变化,那么可以执行成功,否则就执行失败
127.0.0.1:6379(TX)> exec
1) (integer) 9500
2) (integer) 600
感谢阅读!以上是Redis的基础知识讲解,有不懂的或是有错误的地方,欢迎在评论区讨论!
学完Redis的同学可以继续学习Jedis!
Redis进阶篇之Jedis的介绍及使用