Redis 笔记 
redis 是一款 NoSQL 数据库,是最常见的一款非关系型数据库,主要使用 key - value 的形式存储数据,和 mysql 不同,redis 并不会直接把数据存储到硬盘中,而是存储在内存中,也正是这样的设定让 redis 的存取操作特别的快。
Redis 的下载及安装 
redis 的下载
 
首先要去 redis 的官网下载他的压缩包,官网 [ 中文 ] 直连地址如下:
 
进入官网后直接下载即可,然后远程连接 服务器/虚拟机 将下载好的文件上传至 /usr/local 目录下
1 2 3 4 #  进入到usr/local目录  cd /usr/local#  上传文件后,执行解压命令  tar -zxvf redis-5.0.5.tar.gz
 
redis 的安装
 
在执行安装命令之前,首选需要安装 gcc 的依赖,如已安装请忽略
 
然后切换到 redis 目录下开始执行安装命令
1 2 3 4 #  切换目录到redis下  cd redis-5.0.5#  执行安装命令  make && make install
 
这样一来,安装就顺利结束了,如果提示 -bash: make: command not found 则代表 gcc 没有装,需要重新安装 gcc 环境
redis 的配置
 
实际上这个时候 redis 已经可以正常运行了,运行 redis 的命令为 redis-server,但是当我们运行的时候会发现,redis 直接将窗口锁定了,我们不能进行任何的操作,一旦解锁也就意味着 redis 停止工作了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 19196:C 15 Jun 2020 22:13:31.574  19196:C 15 Jun 2020 22:13:31.574  0000, modified=0, pid=19196, just started 19196:C 15 Jun 2020 22:13:31.574                  _._                                                              _.-``__ '' -._                                                    _.-``    `.  `_.  '' -._           Redis 5.0.8 (00000000/0) 64 bit   .-`` .-```.  ```\/    _.,_ '' -._                                     (    '      ,       .-`  | `,    )     Running in standalone mode  |`-._`-...-` __...-.``-._|' ` _.-'|     Port: 6379  |    `-._   `._    /     _.-'     |     PID: 19196   `-._    `-._  `-./  _.-'    _.-'                                      |`-._`-._    `-.__.-'    _.-' _.-'|                                    |    `-._`-._        _.-' _.-'    |           http://redis.io           `-._    `-._`-.__.-' _.-'    _.-'                                      |`-._`-._    `-.__.-'    _.-' _.-'|                                    |    `-._`-._        _.-' _.-'    |                                     `-._    `-._`-.__.-' _.-'    _.-'                                           `-._    `-.__.-'    _.-'                                                   `-._        _.-'                                                          `-.__.-'                                                 19196:M 15 Jun 2020 22:13:31.575  19196:M 15 Jun 2020 22:13:31.575  ..................
 
这时我们需要对配置文件做一些手脚
1 2 3 4 5 6 7 8 # #  复制配置文件  cp redis.conf /root/redis.conf cd /root#  编辑配置文件信息  vim redis.conf#  修改为daemonize为yes ,可以后台运行 redis,否则会锁定命令行  daemonize yes
 
运行 redis 并创建连接
 
首先我们来运行服务端 redis-sever,需要注意的是这里要指定配置文件进行启动,否则还是会以默认配置执行,还是会锁定当前窗口
1 2 3 4 5 6 7 8  redis-server redis-server redis.conf 19203:C 15 Jun 2020 22:15:09.851  19203:C 15 Jun 2020 22:15:09.851  19203:C 15 Jun 2020 22:15:09.851 
 
接下来让我们启动客户端 redis-cli
1 2 3 4 5 6 #  如果直接输入则默认连接本地的6379端口  redis-cli 127.0.0.1:6379>#  也可以通过参数来指定目标IP地址或端口号  redis-cli -h 127.0.0.1 -p 6379 127.0.0.1:6379>
 
尝试输入一个 ping 命令,测试服务是否正常工作,如果返回 PONG 就证明一切正常
1 2 127.0.0.1:6379> PING PONG
 
关闭 redis
 
如果不时用任何配置让他默认启动的话,直接 Ctrl + C 就可以关闭 redis,那么后台运行应该怎么关?
1 2 3 4  127.0.0.1:6379> shutdown redis-cli shutdown
 
设置访问密码 
我们刚刚成功连接到了 redis 服务器,但是这仅仅是本地的,我们目前并不能够远程 ip 地址直接访问,因为我们的 redis 默认开启了保护模式,将来我们的项目是要跑在网络上要用 java 代码操作的,想要远程连接必须要先设置密码!
临时设置密码
 
为了可以远程操作,我们可以设置一个临时的密码,连接上 redis 客户端,命令如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16  127.0.0.1:6379> config set  requirepass 123456 OK 127.0.0.1:6379> keys * (error) NOAUTH Authentication required. 127.0.0.1:6379> auth 123456 OK 127.0.0.1:6379> config get requirepass 1) "requirepass"  2) "zhang"  127.0.0.1:6379> config set  requirepass ''  OK
 
这样一个临时的密码就设置成功了,只要 redis 始终保持正常运行,秘密是不会失效的
设置永久性密码
 
如果觉得临时密码并不能够满足你的安全感,那么可以通过修改配置文件来设置密码:
 
配置文件讲解 
学习 redis 最好是跟着官方文档走,记笔记只是为了让自己更加熟练,推荐 redis 中文网:
1 https:// www.redis.net.cn/order/ 
 
刚刚在安装 redis 的时候简单修改了一下 daemonize 属性的值,让他可以不锁定当前命令行且后台运行,redis 有很多需要了解的常规性配置,这里大概记个笔记:
redis 的配置文件中注释非常多,官方很友好的将模块划分开来,这里就记几个我个人觉得常用的配置
######################## NETWORK ######################## 网络
 
 
 
 
 
######################## GENERAL ######################## 常用
 
 
1 2 3  pidfile /var/run/redis_6379.pid
 
 
 
 
######################## SECURITY ######################## 安全
 
 
Redis 基础命令 
redis 中也有库的概念,初始数量为 16 个,可以在配置文件中进行修改,他是按照从 0 开始的递增顺序命名的,和 mysql 不同,redis 不支持为某个数据库单独命名,也不支持为每个数据库设置单独的访问密码,默认我们启动 redis 时使用的是第 0 个数据库。
==切换数据库==应该怎么操作呢?只需要执行 select 命令
1 2 3 4  127.0.0.1:6379> select  1 OK 127.0.0.1:6379[1]>
 
在我们学习 mysql 的时候,需要通过使用 sql 语句对数据进行 CRUD,而在 redis 我们需要记的是命令
验证登录密码
 
1 2 127.0.0.1:6379> auth 123456 OK
 
数据的简单 CRUD
 
1 2 3 4 5 6 7 8 9 10 11 12  127.0.0.1:6379> set  user zhang OK 127.0.0.1:6379> get user"zhang"  127.0.0.1:6379> del user (integer ) 1 127.0.0.1:6379> get user (nil)
 
查看当前库数据总计数
 
1 2 127.0.0.1:6379[1]> dbsize (integer ) 4
 
查看当前库所有的 key,* 代表通配符
 
1 2 3 4 5 127.0.0.1:6379[1]> keys * 1) "user"  2) "sex"  3) "name"  4) "age" 
 
清空当前数据库
 
1 2 127.0.0.1:6379[1]> flushdb OK
 
清空所有数据库
 
1 2 127.0.0.1:6379> flushall OK
 
查看当前库是否存在该 key,只要不返回 0 就代表存在,exists 是否存在
 
1 2 127.0.0.1:6379> exists name (integer ) 1
 
将当前库的数据移动到其他库 move 移动,name 为 key,1 为目标数据库
 
1 2 127.0.0.1:6379> move name 1 (integer ) 1
 
设置消亡时间 ( 过期时间 ),expire 单位/秒,ttl 查询
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18  127.0.0.1:6379> set  user zhanghanzhe OK 127.0.0.1:6379> expire user 10 (integer ) 1 127.0.0.1:6379> get user"zhanghanzhe"  127.0.0.1:6379> tll user (integer ) 3 127.0.0.1:6379> tll user (integer ) -2 127.0.0.1:6379> get user (nil)
 
查看 key 的数据类型
 
1 2 127.0.0.1:6379 > type name string
 
五大基本类型 
在上面我们介绍了最后一个命令,是一个 type 命令,使用它可以查看指定 key 的数据类型,这里接触了一个新的知识,就是数据类型,redis 中有有多少数据类型呢?
String 字符串 
string 就是字符串啦,我们使用简单的 set 命令设置的数据,如果没有指定,默认就是 string 类型的,就像 java 的 api 一样,string 类型有自己专属的命令
string 类型的命令大多是都是以 str 开头的 
字符串操作就是之前学习的 get set 命令,就不多赘述了
组合命令 返回并修改
 
 
数据的批量操作
 
1 2 3 4 5 6 127.0.0.1:6379> mset user zhang name hanzhe age 21 OK 127.0.0.1:6379> mget user name age 1) "zhang"  2) "hanzhe"  3) "21" 
 
带有验证的 setnx
 
1 2 3 4 5 6 7 8  127.0.0.1:6379> set  name zhang (integer ) 1 127.0.0.1:6379> setnx name hanzhe (integer ) 0 127.0.0.1:6379> msetnx view 123 name hanzhe (integer ) 0
 
追加字符 append
 
1 2 3 4 5 6 7 8 127.0.0.1:6379> set  name zhang OK 127.0.0.1:6379> append name " "  (integer ) 6 127.0.0.1:6379> append name hanzhe (integer ) 12 127.0.0.1:6379> get name"zhang hanzhe" 
 
返回字符串长度 strlen
 
1 2 127.0.0.1:6379> strlen name (integer ) 12
 
自增,自减 incr decr
 
在 java 中,如果想要对字符串 +1 需要转换类型,而在 redis 中他们自动为我们进行了处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 127.0.0.1:6379> set  view 1 OK 127.0.0.1:6379> incr view (integer ) 2 127.0.0.1:6379> decr view (integer ) 1 127.0.0.1:6379> incrby view 3 (integer ) 4 127.0.0.1:6379> decrby view 2 (integer ) 2
 
截取字符串 getrange,==截取是将获取的结果进行截取,并不会对 key 本身造成改变==
 
1 2 3 4 5 127.0.0.1:6379> set  name zhanghanzhe OK 127.0.0.1:6379> getrange name 0 4"zhang" 
 
替换字符串 setrange
 
1 2 3 4 5  127.0.0.1:6379> setrange name 5 chunde (integer ) 11 127.0.0.1:6379> get name"zhangchunde" 
 
设置消亡时间 ( 过期时间 ) setex 单位/秒
 
之前我们使用过一个叫 expire 的命令,也是用来设置消亡时间的,但是他执行设置已存在的 key,而这个命令可以在设置变量的同时设置消亡时间
1 2 127.0.0.1:6379> setex name 10 zhang OK
 
List 列表集合 
list 是用来存储 string 的一个列表, ==redis 的列表是双向的==,list 默认从右向左延伸,也就是每次添加元素都会添加在左面,==list 列表允许重复元素的出现==
list中的命令,大多数都是以小写字母 L 开头的 
创建-添加元素 lpush,rpush
 
lpush 默认从左侧添加,如需从右侧添加可以使用 r 开头声明
1 2 3 4 5 6 127.0.0.1:6379> lpush name han (integer ) 1 127.0.0.1:6379> lpush name zhang (integer ) 2 127.0.0.1:6379> rpush name zhe (integer ) 3
 
查看列表 lrange
 
1 2 3 4 5  127.0.0.1:6379> lrange name 0 -1 1) "zhang"  2) "han"  3) "zhe" 
 
添加,追加元素 2
 
之前介绍了 push 命令可以添加元素,通过 l 开头或者 r 开头可以控制左侧添加还是右侧添加,其实添加元素还可以使用 linsert 命令,和 push 不同的是, push 是在首位添加,而 linsert 是从 左面开始在第一个目标 key 的前后添加 
1 2 3 4 5 6 7 8 9 10 11 12 127.0.0.1:6379> lpush name han (integer ) 1 127.0.0.1:6379> linsert name before han zhang (integer ) 2 127.0.0.1:6379> linsert name after han zhe (integer ) 3 127.0.0.1:6379> lrange name 0 -1 1) "zhang"  2) "han"  3) "zhe" 
 
修改指定下标的 value
 
1 2 3 4 5 6 7 8 9 10 11 127.0.0.1:6379> lrange name 0 -1 1) "zhe"  2) "han"  3) "zhe"  127.0.0.1:6379> lset name 0 zhang OK 127.0.0.1:6379> lrange name 0 -1 1) "zhang"  2) "han"  3) "zhe" 
 
移除首尾 pop,移除多个 lrem
 
1 2 3 4 5 6 7 8 9 10 11 12 13  127.0.0.1:6379> lpop name"zhang"  127.0.0.1:6379> rpop name"zhe"  127.0.0.1:6379> lpush name one (integer ) 4 127.0.0.1:6379> rpush name one one (integer ) 6 127.0.0.1:6379> lrem name 2 one (integer ) 2
 
返回长度 llen
 
1 2 3 4 5 6 7 127.0.0.1:6379> lpush name han zhang (integer ) 2 127.0.0.1:6379> rpush name zhe (integer ) 3 127.0.0.1:6379> llen name (integer ) 3
 
获取指定下标的value
 
1 2 3 4 5 6 127.0.0.1:6379> lrange name 0 -1 1) "zhang"  2) "han"  3) "zhe"  127.0.0.1:6379> lindex name 0"zhang" 
 
截取列表 ltrim
 
1 2 3 4 5 6 7 8 9 10 11 12 127.0.0.1:6379> lrange list 0 -1 1) "five"  2) "four"  3) "three"  4) "two"  5) "one"  127.0.0.1:6379> ltrim list 1 2 OK 127.0.0.1:6379> lrange list 0 -1 1) "four"  2) "three" 
 
获取排序结果 sort,不会影响本体,字母排序/alpha,倒序排序/desc
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 127.0.0.1:6379> lpush num 10 30 20 40 (integer ) 4 127.0.0.1:6379> sort  num 1) "10"  2) "20"  3) "30"  4) "40"  127.0.0.1:6379> sort  num desc 1) "40"  2) "30"  3) "20"  4) "10" 
 
1 2 3 4 5 6 7  127.0.0.1:6379> lpush En python java switch (integer ) 3 127.0.0.1:6379> sort  En alpha 1) "java"  2) "python"  3) "switch" 
 
组合命令:移动元素
 
1 2 3 4 5 6 7 8 9 10 11 127.0.0.1:6379> lrange name 0 -1 1) "han"  2) "zhe"  3) "zhang"  127.0.0.1:6379> rpoplpush name name"zhang"  127.0.0.1:6379> lrange name 0 -1 1) "zhang"  2) "han"  3) "zhe" 
 
Set 无序集合 
set 集合是 string 类型的 ==无序集合==,且==不允许存入重复数据==,如果重复存入相同的值会报错
还是老规矩,set 集合的命令特点,就是以 s 开头 
set 集合的 CRUD
 
1 2 3 4 5  127.0.0.1:6379> sadd code java (integer ) 1 127.0.0.1:6379> sadd code python switch (integer ) 2
 
1 2 3 4 5  127.0.0.1:6379> smembers code 1) "python"  2) "java"  3) "switch" 
 
1 2 3  127.0.0.1:6379> srem code switch (integer ) 1
 
查看目标 set 集合长度 scard
 
1 2 127.0.0.1:6379> scard code (integer ) 3
 
检查 set 集合中是否包含指定的值 sismember,存在返回 1,不存在返回 0
 
1 2 127.0.0.1:6379> sismember code java (integer ) 1
 
随机性的获取和移除
 
1 2 3 4 5 6 7 8 9 10 127.0.0.1:6379> sadd num 1 2 3 4 5 6 7 8 9 (integer ) 9 127.0.0.1:6379> srandmember num 1 1) "7"  127.0.0.1:6379> srandmember num 1 1) "3"  127.0.0.1:6379> spop num 1 1) "2" 
 
获取多个集合中的 ==差集==,==交集==,==并集==
 
1 2 3 4 5 6 7  127.0.0.1:6379> sadd key1 1 2 3 4 (integer ) 4 127.0.0.1:6379> sadd key2 3 4 5 6 (integer ) 4 127.0.0.1:6379> sadd key3 1 6 7 8 (integer ) 4
 
差集 sdiff,取出两个集合中不同的元素
1 2 3 4 5 6 7 8  127.0.0.1:6379> sdiff key1 key2 1) "1"  2) "2"  127.0.0.1:6379> sdiff key1 key2 key3 1) "2"  127.0.0.1:6379> sdiff key2 key1 key3 1) "5" 
 
交集 sinter,取出两个集合中相同的元素
1 2 3 4 5 6 7 8  127.0.0.1:6379> sinter key1 key2 1) "3"  2) "4"  127.0.0.1:6379> sinter key1 key3 1) "1"  127.0.0.1:6379> sinter key1 key2 key3 (empty list or set )
 
并集 sunion,取出两个集合中所有的元素
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17  127.0.0.1:6379> sunion key1 key2 1) "1"  2) "2"  3) "3"  4) "4"  5) "5"  6) "6"  127.0.0.1:6379> sunion key1 key2 key3 1) "1"  2) "2"  3) "3"  4) "4"  5) "5"  6) "6"  7) "7"  8) "8" 
 
Hash 散列集合 
hash 类型就像 map,是由一个个的 key-value 组成的对象,而且 ==key 不能重复==,存入相同的 key 和 value 后,最后一个存入的会覆盖之前存入的结果 
老规矩,几乎所有 hash 的指令都以 h 开头 
添加,批量添加,hash 没有修改,覆盖就是修改
 
1 2 3 4 127.0.0.1:6379> hset user name zhanghanzhe (integer) 1 127.0.0.1:6379> hmset user sex nan age 21 OK
 
获取,批量获取
 
1 2 3 4 5 6 7  127.0.0.1:6379> hget user name"zhanghanzhe"  127.0.0.1:6379> hmget user name sex age 1) "zhanghanzhe"  2) "nan"  3) "21" 
 
1 2 3 4 5 6 7 8 9 10  127.0.0.1:6379> hkeys user 1) "name"  2) "sex"  3) "age"  127.0.0.1:6379> hvals user 1) "zhanghanzhe"  2) "nan"  3) "21" 
 
1 2 3 4 5 6 7 8  127.0.0.1:6379> hgetall user 1) "name"  2) "zhanghanzhe"  3) "sex"  4) "nan"  5) "age"  6) "21" 
 
删除指令
 
1 2 3 4 127.0.0.1:6379> hdel user name (integer ) 1 127.0.0.1:6379> hdel user sex age (integer ) 2
 
自增和自减
 
1 2 3 4 5 6 127.0.0.1:6379> hset map key1 5 (integer ) 1 127.0.0.1:6379> hincrby map key1 2 (integer ) 7 127.0.0.1:6379> hincrby map key1 -3 (integer ) 4
 
判断是否存在,存在返回 1,不存在返回 2
 
1 2 127.0.0.1:6379> hexists map key1 (integer ) 1
 
Zset 有序集合 
zset 有序集合和 set 集合类似,都是不允许重复的值出现,只不过相比于 set 集合,zset 多了一个排序的功能,在添加值得时候需要给他一个 分值,分值 可以重复但 value 不允许重复 ,默认按照分值从小到大排序
老规矩,几乎所有 zset 的指令都以 z开头 
添加元素 zadd,第一个是分值,第二个是 value
 
1 2 3 4 127.0.0.1:6379> zadd user 1 zhang (integer ) 1 127.0.0.1:6379> zadd user 2 han 3 zhe (integer ) 2
 
删除元素
 
1 2 127.0.0.1:6379> zrem user zhang han zhe (integer ) 3
 
查看集合
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18  127.0.0.1:6379> zrange user 0 -1 1) "zhang"  2) "han"  3) "zhe"  127.0.0.1:6379> zrevrange user 0 -1 1) "zhe"  2) "han"  3) "zhang"  127.0.0.1:6379> zrange user 0 -1 withscores 1) "zhang"  2) "1"  3) "han"  4) "2"  5) "zhe"  6) "3" 
 
获取集合内元素的数量 zcard zcount
 
这里需要注意:==- inf 代表无穷小,+ inf 代表无穷大==
1 2 3 4 5 6 7 8 9 10 127.0.0.1:6379> zadd user 10 zhang 12 wang 18 li 20 zhao 28 bai 30 guo (integer ) 6 127.0.0.1:6379> zcard user (integer ) 6 127.0.0.1:6379> zcount user -inf +inf (integer ) 6 127.0.0.1:6379> zcount user 10 20 (integer ) 4
 
三大特殊类型 
地理位置 
Geospatial 类型用来存储有关经纬度的地理位置信息,默认经度第一位,维度第二位,redis 中针对经纬度的存储范围有一定的限制:
经度有效范围:-180 ~ 180 
维度有效范围:-85.05112878 ~ 85.05112878 
 
老规矩,几乎所有指令都是以 geo开头的 
添加地理位置信息 geoadd
 
在添加经纬度的时候如果超出了范围会报错
1 2 3 4 127.0.0.1:6379> geoadd point 126.64 45.75 hei (integer ) 1 127.0.0.1:6379> geoadd point 125.32 43.88 ji 123.42 41.79 liao (integer ) 2
 
查看指定地区的经纬度 ( 已存入的 ),同理,添加也是修改
 
1 2 3 4 5 6 7 8 9 10 127.0.0.1:6379> geopos point ji 1) 1) "125.32000154256820679"     2) "43.87999897829567431"  127.0.0.1:6379> geopos point hei ji liao 1) 1) "126.64000242948532104"     2) "45.74999965248261447"  2) 1) "125.32000154256820679"     2) "43.87999897829567431"  3) 1) "123.41999977827072144"     2) "41.78999971580505246" 
 
计算两地的距离 geodist
 
使用 geodist 获取距离是通过经纬度计算出来的 直线距离 ,并不是路途距离,默认获取单位是米,可以通过制定后缀来设置获取的单位: m/米,km/千米,mi英里,ft英尺
1 2 3 4 5  127.0.0.1:6379> geodist point hei ji"232604.0065"  127.0.0.1:6379> geodist point hei ji km"232.6040" 
 
雷达方式获取目标经纬度附近的地理位置 georadius
 
雷达方式,以经纬度为中心,距离为半径按照圆形扫描,类似微信附近的人功能
withdist距离km,withdist经纬度,count 1 显示第一个符合要求的
1 2 3 4  127.0.0.1:6379> georadius point 125 43 300 km 1) "ji"  2) "liao" 
 
1 2 3 4 5 6  127.0.0.1:6379> georadius point 125 43 300 km withdist withcoord count 1 1) 1) "ji"     2) "101.2331"     3) 1) "125.32000154256820679"        2) "43.87999897829567431" 
 
1 2 3 4  127.0.0.1:6379> georadiusbymember point ji 250 km 1) "ji"  2) "hei" 
 
移除元素
 
Geospatial 类型比较特殊,他没有给咱们提供移除的指令,但是 Geospatial 的底层是基于 zset 实现的,我们可以通过 zset 来移除指定的 key 即可。
1 2 3 4 5 6 7 8 127.0.0.1:6379> zrange point 0 -1 1) "liao"  2) "ji"  3) "hei"  127.0.0.1:6379> zrem point hei (integer ) 1 127.0.0.1:6379> geopos point hei 1) (nil)
 
基数统计 
hyperloglog 他是一种专门用来统计及基数的类型,当然其中的元素不允许重复,相比于 set 集合,例如计算网站访问量时,可以交给 hyperloglog 进行处理,他的内存占用是固定的 12KB。
但是因为他仅仅是计算基数的类型,所以并不能像 set 集合一样,获取到元素的具体的值,而且该类型有一定的误差,如果对精准度没有太大要求,那么推荐使用 hyperloglog
老规矩,几乎所有指令都是以 pf开头的 
添加元素 pfadd
 
1 2 3 4 127.0.0.1:6379> pfadd num1 1 2 3 4 5 (integer ) 1 127.0.0.1:6379> pfadd num2 3 4 5 6 7 (integer ) 1
 
查看元素个数
 
1 2 127.0.0.1:6379> pfcount num1 (integer ) 5
 
将两个集合合并为一个集合
 
1 2 3 4 127.0.0.1:6379> pfmerge num3 num1 num2 OK 127.0.0.1:6379> pfcount num3 (integer ) 7
 
进制存储 
bitmap 是基于二进制进行存储的,二进制只有 0 和 1 两个值,可以分别用来代表两种相对不同的状态,例如 打卡 <=> 未打卡,可以抽象的理解为 boolean 类型中的 true 和 false 的感觉。
命令几乎都以 bit 结尾 
添加元素 [ 0 代表未打卡,1 代表打卡 ]
 
1 2 3 4 5 6 7 8 127.0.0.1:6379> setbit sign 0 1 (integer ) 1 127.0.0.1:6379> setbit sign 1 1 (integer ) 0 127.0.0.1:6379> setbit sign 2 0 (integer ) 1 127.0.0.1:6379> setbit sign 3 1 (integer ) 1
 
查看某个状态 – 返回 1 代表打卡,0 代表未打卡
 
1 2 127.0.0.1:6379> getbit sign 1 (integer ) 1
 
查看多少人符合条件
 
1 2 127.0.0.1:6379> bitcount sign (integer ) 3
 
Redis 的事务 
事务就是一组命令的集合,将平时多次执行的命令放在一起,然后按照命令顺序依次执行,而且执行过程中不会被干扰,事务执行结束后不会保留,也就意味着每次执行事务都需要重新创建,可得出 redis 事务的三个特点:顺序性 ,排他性 ,一次性 。
还有几点需要注意:==redis 的事务中,没有原子性和隔离性的概念,也不包含回滚==,所有命令在加入事务的时候,并没有直接执行,而是被放在了执行的队列中,也就不存在隔离性。
事务涉及到的关键字:开启/multi,执行/exec,放弃/discard,监视/watch,关闭监视/unwatch
使用事务 
事务的执行
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14  127.0.0.1:6379> multi OK 127.0.0.1:6379> set  k1 v1 QUEUED 127.0.0.1:6379> mset k2 v2 k3 v3 QUEUED 127.0.0.1:6379> exec  1) OK 2) OK 127.0.0.1:6379> mget k1 k2 k3 1) "v1"  2) "v2"  3) "v3" 
 
1 2 3 4 5 6 7 8 9  127.0.0.1:6379> multi OK 127.0.0.1:6379> set  k1 zhang QUEUED 127.0.0.1:6379> discard OK 127.0.0.1:6379> get k1"v1" 
 
事务的异常处理机制
 
和 java 有点类似,redis 针对事务也分所谓的 编译时异常 和 运行时异常,只不过这里的 编译时异常 指的是命令是否正确,针对不同的异常,redis 事务处理的方式也不一致
1 2 3 4 5 6 7 8 9 10 11 12  127.0.0.1:6379> multi OK 127.0.0.1:6379> set  user zhang QUEUED 127.0.0.1:6379> sett name hanzhe (error) ERR unknown command  `sett`, with args beginning with: `name`, `hanzhe`,  127.0.0.1:6379> exec  (error) EXECABORT Transaction discarded because of previous errors. 127.0.0.1:6379> get user (nil)
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19  127.0.0.1:6379> set  money zhang OK 127.0.0.1:6379> multi OK 127.0.0.1:6379> incrby money 3000 QUEUED 127.0.0.1:6379> decrby money 188 QUEUED 127.0.0.1:6379> set  user zhang QUEUED 127.0.0.1:6379> exec   1) (error) ERR value is not an integer  or out of range 2) (error) ERR value is not an integer  or out of range 3) OK 127.0.0.1:6379> get user"zhang" 
 
因为 redis 的事务管理并不严格,所以 redis 的事务又被人戏称 伪事务 
乐观锁 
在我们执行事务的时候,如果是处在多线程的环境下,我通过事务对某个数据进行改变,但是在我命令缓存完成还没有执行的时候,另一条线程进来对这个数据进行了修改,那么就会发生难以想象的改变。
模拟举例说明:
 
1 2 3 4 5 6 7  127.0.0.1:6379> multi OK 127.0.0.1:6379> incrby money 3000 QUEUED 127.0.0.1:6379> decrby money 188  QUEUED
 
1 2 3  127.0.0.1:6379> set  money 200000 (integer ) 200100
 
1 2 3 4  127.0.0.1:6379> exec  1) (integer ) 203000 2) (integer ) 202812
 
可以发现,在事务执行的过程中数据被改变了,结果也造成影响了
什么是乐观锁?
 
关于锁,有两个概念,一个是 悲观锁,还有一个是 乐观锁,悲观锁类似 java 中的多线程锁 synchronized,将整个方法上锁,无论是否有多条线程访问都会工作,在安全的前提下影响了性能,而 乐观锁 是在指令添加前将被操作的那个 key 监视起来,如果在执行的时候发现目标 key 发生了改变,那么就将当前事务取消不执行。
1 2 3 4 5 6 7 8 9 10 11  127.0.0.1:6379> watch money OK 127.0.0.1:6379> multi OK 127.0.0.1:6379> incrby money 3000 QUEUED 127.0.0.1:6379> decrby money 188 QUEUED
 
1 2 3  127.0.0.1:6379> incrby money 200000 (integer ) 200100
 
1 2 3  127.0.0.1:6379> exec  (nil)
 
可以发现,被监视的 key 发生改变后,事务执行就被中断了,那么之后应该如何处理呢?
乐观锁善后
 
1 2 3 4 5 6 7 8  127.0.0.1:6379> discard OK 127.0.0.1:6379> unwatch OK
 
持久化保存策略 
我们都是到,redis 是操作内存的数据库,正因为这样的特点才让他的存取速度特别快,但是在内存中存放的数据,当我们重新启动服务器的时候就会丢失,这个时候就需要接触到 持久化 这个技术了,持久化就是把当前进程数据生成快照保存到硬盘的过程。
redis 的持久化操作分为两种,分别为 RDB 和 AOF,两种方式只能同时使用一种,redis 使用的 默认持久化方式为 RDB ,
RDB 方式 
RDB ( Redis DataBase ) 持久方式,会单起一条线程,在指定的时间间隔内将内存中的数据以二进制形式写入、临时文件中,写入成功后默认存放到 redis 的安装目录下的 dump.rdb 文件中,如果你使用自己的配置启动的 redis,那么 dump.rdb 会和你的配置文件同级。
RDB 的持久化有两种触发机制,一种是手动命令持久化,一种是自动持久化。
手动持久化 save,bgsave
 
1 2 3 4 127.0.0.1:6379> save OK 127.0.0.1:6379> bgsave Background saving started
 
save 命令会阻塞当前 redis 服务器,期间不能正常提供服务,这一现象直至数据保存完毕后恢复正常。 
bgsave 会执行 fork 子进程负责持久化操作,在创建子进程的时候会有短暂的阻塞,时间很短。 
 
除开这种主动的持久化之外,一些其他的命令也会完成持久化的操作,例如:flushall,shutdown 等等。
自动持久化
 
配置文件中的 SNAPSHOTTING 模块就是用来做 RDB 持久化的,里面有这样几句配置命令:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16  save 900 1 save 300 10 save 60 10000 stop-writes-on-bgsave-error yes  rdbcompression yes  rdbchecksum yes  dbfilename dump.rdbdir  ./
 
一般我们不需要该他的配置文件,预设的就够用了。
#####AOF 方式##### 
同样还是在配置文件中,APPEND ONLY MODE 模块就是负责 AOF 持久化的,和 RDB 不同, AOF 的原理是将所有曾经使用过的存入操作的命令都记录下来,存放到 appendonly.aof 文件中,是可以看个模糊的大概的。
AOF 持久化配置
 
AOF 也可以手动触发,只需要执行 bgrewriteaof 命令即可,但是一般都用配置文件管理,配置信息如下:
1 2 3 4 5 6 7 8 9 10 11 12  appendonly no       appendfsync everysec auto-aof-rewrite-percentage 100 auto-aof-rewrite-min-size 64mb
 
AOF 重写
 
########################################################################## 
 
有关于持久化策略,以后再细学
比较RDB和AOF 
RDB:RDB 是二进制文件,按照时间进行数据同步,每次同步都会执行 fork 操作,如果为了追求数据数据完整性不停的同步,会极大的影响 redis 工作效率,更==适合做定期备份,用于灾难恢复==
AOF:AOF 通过记录命令实现持久化,通过控制参数可以精确到秒级。
########################################################################## 
 
有关于持久化策略,以后再细学
文件损坏修复 
当我们的数据文件损坏导致 redis 无法启动的时候,我们可以尝试运行 redis 的修复工具
1 2 redis-check-aof --fix 配置文件 redis-check-rdb --fix 配置文件
 
########################################################################## 
 
有关于持久化策略,以后再细学
Redis 整合 
所有程序均是以 maven 为基础搭建的
原生 Java 整合 
使用 java 项目整合 redis 进行操作,首先要添加 maven 依赖
正常操作 redis
 
1 2 3 4 5 <dependency >      <groupId > redis.clients</groupId >      <artifactId > jedis</artifactId >      <version > 3.3.0</version > </dependency > 
 
添加依赖完成后,便可以直接通过 java 代码创建连接并操作数据库了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public  class  Demo1  {     public  static  void  main (String[] args)  {                  Jedis  redis  =  new  Jedis ("IP地址" , 6379 );                  redis.auth("你的密码" );                  redis.flushDB();                  System.out.println(redis.ping());         System.out.println(redis.set("user" , "zhang" ));         System.out.println(redis.get("user" ));                  redis.close();     } }
 
直接创建 jedis 对象就可以创建远程连接,而且实例内的方法和 redis 的指令用法等等几乎一模一样,非常好用。
redis 连接池
 
一般来说 jedis 已经可以很好的操作 redis 数据库了,但是在项目中如果频繁的创建和关闭连接,是很耗费服务器资源的,所以这里可以使用 jedis 的连接池进行操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public  class  JedisPoolDemo  {     public  static  void  main (String[] args)  {                  JedisPool  pool  =  new  JedisPool ("IP地址" , 6379 );                  Jedis  redis  =  pool.getResource();                  redis.auth("密码" );         redis.set("name" , "hanzhe" );         System.out.println(redis.get("name" ));                  redis.close();     } }
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public  class  JedisPoolDemo  {     public  static  void  main (String[] args)  {                  JedisPoolConfig  poolConfig  =  new  JedisPoolConfig ();                  poolConfig.setMaxTotal(50 );                  poolConfig.setMaxIdle(10 );                  JedisPool  pool  =  new  JedisPool (poolConfig, "IP地址" , 6379 );                  Jedis  redis  =  pool.getResource();                           redis.close();     } }
 
检查 redis 数据库
 
1 2 3 4 5 6 7 127.0.0.1:6379> keys * 1) "name"  2) "user"  127.0.0.1:6379> mget user name 1) "zhang"  2) "hanzhe"  127.0.0.1:6379> 
 
经过检查发现,java 操作 redis 不存在其他的问题,整合基本完成。
控制台打印警告问题
 
1 2 3 SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder" . SLF4J: Defaulting to no-operation (NOP) logger implementation SLF4J: See http://www.slf4j.org/codes.html
 
如果控制台打印如上的警报信息,可以引入 slf4j 的 maven 依赖进行解决
1 2 3 4 5 <dependency >      <groupId > org.slf4j</groupId >      <artifactId > slf4j-log4j12</artifactId >      <version > 1.7.25</version > </dependency > 
 
Springboot 整合 
springboot 整合同样需要引入对应的 maven 依赖,和 jedis 有些许的不同,springboot 中封装的类并不可以直接调用类似命令的函数,而是对他们进行了二次封装
1 2 3 4 <dependency >      <groupId > org.springframework.boot</groupId >      <artifactId > spring-boot-starter-data-redis</artifactId > </dependency > 
 
修改配置文件
 
1 2 3 4 5 6 7 8 9 10 spring.redis.host =IP地址 spring.redis.port =6379 spring.redis.password =密码 spring.redis.pool.max-active =50 spring.redis.pool.max-idle =10 
 
简单操作数据库
 
操作数据库,需要注入 RedisTemplate 对象进行操作
1 2 3 4 5 6 7 8 9 10 11 12 @SpringBootTest class  Redis2SpringbootApplicationTests  {          @Autowired      private  RedisTemplate redisTemplate;     @Test      void  contextLoads ()  {         redisTemplate.opsForValue().set("user" , "zhang" );                  System.out.println(redisTemplate.opsForValue().get("user" ));     } }
 
RedisTemplate 将数据类型对应的指令函数分别命名为 opsForValue(),opsForList(),opsForSet(),opsForHash(),opsForHyperLogLog() ,opsForZSet()
高级操作数据库
 
上面的方法封装了各种数据类型的简单操作,接下来就是一些高级的操作了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @SpringBootTest class  Redis2SpringbootApplicationTests  {     @Autowired      private  RedisTemplate redisTemplate;     @Test      void  contextLoads ()  {                  RedisConnectionFactory  factory  =  redisTemplate.getConnectionFactory();         RedisConnection  connection  =  factory.getConnection();                  System.out.println(connection.ping());         connection.flushDb();         connection.flushAll();         connection.shutdown();         connection.close();              } }
 
乱码问题
 
1 2 3  redisTemplate.opsForValue().set("name" , "zhang" ); System.out.println(redisTemplate.opsForValue().get("name" ));
 
1 2 3  127.0.0.1:6379> keys * 1) "\xac\xed\x00\x05t\x00\x04name" 
 
我们通过 springboot 向 redis 中插入一个字符串,发现存进去的字符串存在乱码问题,这时候我们可以通过使用 RedisTemplate 的子类 StringRedisTemplate 进行操作
1 2 3 4 5 6 7 8 9 10 @SpringBootTest class  Redis2SpringbootApplicationTests  {     @Autowired      private  StringRedisTemplate string;     @Test      void  contextLoads ()  {         string.opsForValue().set("name" , "zhang" );         System.out.println(string.opsForValue().get("name" ));     } }
 
1 2 3 4 5 6  127.0.0.1:6379> keys * 1) "name"  2) "\xac\xed\x00\x05t\x00\x04name"  127.0.0.1:6379> get name"zhang" 
 
自定义 RedisTemplate
 
字符串可以通过 StringRedisTemplate 来解决问题,但是存入其他类型的还是会出现问题,这时可自己创建一个 RedisTemplate 来代替原本的类工作。代码来自互联网 
1.首先要引入 maven 依赖
1 2 3 4 5 <dependency >      <groupId > com.fasterxml.jackson.core</groupId >      <artifactId > jackson-databind</artifactId >      <version > 2.9.8</version > </dependency > 
 
2.创建配置类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 @Configuration public  class  RedisUtil  {     @Bean      public  RedisTemplate<String, Object> redisTemplate (RedisConnectionFactory factory)  {         RedisTemplate<String, Object> template = new  RedisTemplate ();         template.setConnectionFactory(factory);         Jackson2JsonRedisSerializer  jackson2JsonRedisSerializer  =  new  Jackson2JsonRedisSerializer (Object.class);         ObjectMapper  om  =  new  ObjectMapper ();         om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);         om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);         jackson2JsonRedisSerializer.setObjectMapper(om);         StringRedisSerializer  stringRedisSerializer  =  new  StringRedisSerializer ();                  template.setKeySerializer(stringRedisSerializer);                  template.setHashKeySerializer(stringRedisSerializer);                  template.setValueSerializer(jackson2JsonRedisSerializer);                  template.setHashValueSerializer(jackson2JsonRedisSerializer);         template.afterPropertiesSet();         return  template;     } }
 
这个时候自定义的  就已经完成了,现在再来测试一遍是否乱码
1 2 3 4 5 6 7 8 9 10 @SpringBootTest class  Redis2SpringbootApplicationTests  {     @Autowired      private  RedisTemplate redisTemplate;     @Test      void  contextLoads ()  {         redisTemplate.opsForValue().set("name" , "zhang" );         System.out.println(redisTemplate.opsForValue().get("name" ));     } }
 
1 2 3 4 5  127.0.0.1:6379> keys * 1) "name"  127.0.0.1:6379> get name"\"zhang\"" 
 
通过序列化将对象存储到 redis 中
 
1 2 3 4 5 6 public  class  Person  implements  Serializable  {     private  String username;     private  String password;      }
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @SpringBootTest class  Redis2SpringbootApplicationTests  {     @Autowired      private  RedisTemplate redisTemplate;     @Test      void  contextLoads ()  {         Person  p  =  new  Person ();         p.setUsername("张涵哲" );         p.setPassword("zhang" );         redisTemplate.opsForValue().set("user" , p);         System.out.println(redisTemplate.opsForValue().get("user" ));     } }
 
订阅与发布 
redis 的订阅发布是一种通讯模式,分别为 发送者/sub 和 订阅者/pub 两种身份,发布者负责发送一些信息,然后由订阅者接收,涉及到的命令也非常少
最常用:简单使用订阅和发布完成交互 subscribe,publish
 
1 2 3 4 5 6  127.0.0.1:6379> subscribe s1 Reading messages... (press Ctrl-C to quit) 1) "subscribe"  2) "s1"  3) (integer ) 1
 
1 2 3  127.0.0.1:6379> publish s1 haha (integer ) 1
 
1 2 3 4  1) "message"    2) "s1"       3) "haha"     
 
主从复制 
在我们的项目越做越大的情况下,一个 redis 服务可能已经不支持我们的读写效率了,这个时候我们需要配置多个服务器,在每个服务器上都配置 redis 的环境,让他们分别为一个程序提供服务,这种工作方式被称之为集群
在多个服务器中选中一台服务器为主机 ( Master ),其他为从机 ( Slave ),主机负责写入数据 ( set… ),而从机负责读取数据 ( get ),为了多个服务器之间数据同步的问题,所以有了主从复制的技术。
这里可以通过复制多个配置文件,修改端口号来实现 伪集群,设置 6379 为主机,6380 为从机,后面简称为 79 和 80
通过命令实现 
选择 79 为主机,连接主机输入 info replication 命令即可查看当前机器配置状态
1 2 3 4 5 6 7 8 9 10 11 12 127.0.0.1:6379> info replication role:master connected_slaves:0 master_replid:49e04dfecc70d4b55f17798d80b1d77ac4289c12 master_replid2:0000000000000000000000000000000000000000 master_repl_offset:0 second_repl_offset:-1 repl_backlog_active:0 repl_backlog_size:1048576 repl_backlog_first_byte_offset:0 repl_backlog_histlen:0
 
第二行位置显示 role:master,代表当前端口是主机,==redis 默认每台机器都是主机==,配置主从复制只需要配置从机就可以了。
配置从机 slaveof 认主,只需要找到目标 Redis 服务器作为自己的主人就可以了,这样一来从机就可以获取到主机的数据了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23  127.0.0.1:6380> slaveof localhost 6379 OK 127.0.0.1:6380> info replication role:slave master_host:127.0.0.1 master_port:6379 master_link_status:up master_last_io_seconds_ago:7 master_sync_in_progress:0 slave_repl_offset:434 slave_priority:100 slave_read_only:1 connected_slaves:0 master_replid:5ec4f58da9447b03e682d76edb61cbf5e4cf89a2 master_replid2:0000000000000000000000000000000000000000 master_repl_offset:434 second_repl_offset:-1 repl_backlog_active:1 repl_backlog_size:1048576 repl_backlog_first_byte_offset:1 repl_backlog_histlen:434