Redis

本文详细介绍了 Redis 的基本操作,包括安装、性能测试、数据类型(如 String、List、Set、Hash 和 ZSet)以及 Hyperloglog 和 Bitmaps。还探讨了 Redis 事务、乐观锁的实现以及 Jedis 在 SpringBoot 中的整合。此外,文章还讲解了 Redis 的持久化机制(RDB 和 AOF)以及如何配置 Redis 以适应不同的场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Redis安装

  • 百度一下,你就知道

Redis性能测试(redis-benchmark)

测试:本机100个并发连接,100000个请求

./redis-benchmark -h localhost -p 6379 -c 100 -n 100000

在这里插入图片描述

Redis基础知识

  1. redis默认有16个数据库,我们从配置文件中可以看到。我们默认使用的是第0个数据库
    在这里插入图片描述
  2. 可以使用select命令切换数据库
127.0.0.1:6379> select 3
OK
127.0.0.1:6379[3]> 
  1. 可以使用dbsize查看数据库大小
127.0.0.1:6379[3]> dbsize
(integer) 0
127.0.0.1:6379[3]> set name qxf
OK
127.0.0.1:6379[3]> dbsize
(integer) 1
  1. 可以使用keys *查看所有的key
127.0.0.1:6379[3]> keys *
1) "name"
  1. 可以使用flushall清空所有数据库,flushdb清空当前库
127.0.0.1:6379[3]> select 0
OK
127.0.0.1:6379> flushall
OK
127.0.0.1:6379> select 3
OK
127.0.0.1:6379[3]> keys *
(empty array)
127.0.0.1:6379[1]> select 0
OK
127.0.0.1:6379> keys *
(empty array)
127.0.0.1:6379> set k1 v1
OK
127.0.0.1:6379> keys *
1) "k1"
127.0.0.1:6379> flushdb
OK
127.0.0.1:6379> keys *
(empty array)
  1. redis默认端口号是6379
  2. redis是单线程的,是基于内存操作的,cpu不是redis的性能瓶颈,redis的瓶颈是机器的内存和网络带宽
  3. redis是C语言开发的,官方提高的数据为100000+的QPS,完全不比Memecache差
  4. redis是将所有数据全部放在内存中的,所以说使用单线程操作效率就是最高的,相比于多线程,少了上下文的切换耗时的操作,多次读写都是在一个cpu上的

RedisKey的基本命令

  1. 使用exists可以查看redis中存储的key是否存在
127.0.0.1:6379> set name qxf
OK
127.0.0.1:6379> keys *
1) "name"
127.0.0.1:6379> set age 1
OK
127.0.0.1:6379> keys *
1) "name"
2) "age"
127.0.0.1:6379> exists name
(integer) 1
127.0.0.1:6379> exists name1
(integer) 0
  1. 使用move命令可以将当前数据库的key值移动到指定数据库中,比如下面将name移动到第1个数据库中
127.0.0.1:6379> keys *
1) "name"
2) "age"
127.0.0.1:6379> move name 1
(integer) 1
127.0.0.1:6379> select 1
OK
127.0.0.1:6379[1]> keys *
1) "name"
127.0.0.1:6379[1]> get name
"qxf"
127.0.0.1:6379[1]> select 0
OK
127.0.0.1:6379> keys *
1) "age"
  1. 使用expire命令可以设置过期时间,使用ttl可以查看key的剩余过期时间,比如下面设置age的10秒钟以后过期
127.0.0.1:6379> EXPIRE age 10
(integer) 1
127.0.0.1:6379> ttl age
(integer) 7
127.0.0.1:6379> ttl age
(integer) 5
127.0.0.1:6379> ttl age
(integer) 5
127.0.0.1:6379> ttl age
(integer) 4
127.0.0.1:6379> ttl age
(integer) 3
127.0.0.1:6379> ttl age
(integer) 2
127.0.0.1:6379> ttl age
(integer) 1
127.0.0.1:6379> ttl age
(integer) -2
127.0.0.1:6379> ttl age
(integer) -2
127.0.0.1:6379> get age
(nil)
  1. 使用type查看key的类型
127.0.0.1:6379> set name qxf
OK
127.0.0.1:6379> TYPE name
string

String类型

  1. 使用append命令可以给字符串后面添加值,如果当前key不存在,那么就会新建一个key
127.0.0.1:6379> set k1 v1
OK
127.0.0.1:6379> get k1
"v1"
127.0.0.1:6379> append k1 v2
(integer) 4
127.0.0.1:6379> get k1
"v1v2"
  1. 使用strlen可以查看字符串的长度
127.0.0.1:6379> STRLEN k1
(integer) 4
  1. 使用incr可以自增加一,使用decr可以自减一
127.0.0.1:6379> set views 0
OK
127.0.0.1:6379> get views
"0"
127.0.0.1:6379> INCR views
(integer) 1
127.0.0.1:6379> INCR views
(integer) 2
127.0.0.1:6379> INCR views
(integer) 3
127.0.0.1:6379> DECR views
(integer) 2
127.0.0.1:6379> DECR views
(integer) 1
127.0.0.1:6379> DECR views
(integer) 0
  1. 使用incrby可以指定加多少,使用decrby可以指定减多少
127.0.0.1:6379> INCRBY views 10
(integer) 10
127.0.0.1:6379> get views
"10"
127.0.0.1:6379> DECRBY views 5
(integer) 5
127.0.0.1:6379> get views
"5"
  1. 使用getrange可以获取指定索引长度的字符串
127.0.0.1:6379> set k1 "hello,everyone"
OK
127.0.0.1:6379> get k1
"hello,everyone"
127.0.0.1:6379> GETRANGE k1 0 3 # 获取[0,3]索引之间的字符串
"hell"
127.0.0.1:6379> GETRANGE k1 0 -1 # 获取整个字符串
"hello,everyone"
  1. 使用setrange可以设置索引后的值
127.0.0.1:6379> set k2 abcdefg
OK
127.0.0.1:6379> get k2
"abcdefg"
127.0.0.1:6379> SETRANGE k2 1 xx # 设置k2第1个索引后的值为xx
(integer) 7
127.0.0.1:6379> get k2
"axxdefg"
  1. 使用setex可以设置过期时间并且设置值,setnx如果不存在则设置值,如果存在那么就不设置
127.0.0.1:6379> SETEX k3 30 hello # 设置k3的值为hello,30秒以后过期
OK
127.0.0.1:6379> ttl k3
(integer) 25
127.0.0.1:6379> SETNX mykey redis # 如果mykey不存在,则创建mykey
(integer) 1 # 返回1代表成功
127.0.0.1:6379> SETNX mykey redis # 如果mykey存在,则创建失败
(integer) 0 # 返回0代码不成功
  1. 使用mset可以设置多个key和value,使用mget可以获取多个值
127.0.0.1:6379> MSET k1 v1 k2 v2 k3 v3
OK
127.0.0.1:6379> keys *
1) "k1"
2) "k3"
3) "k2"
127.0.0.1:6379> mget k1 k2 k3
1) "v1"
2) "v2"
3) "v3"
  1. 使用msetnx可以批量设置多个值,如果不存在,则创建,如果存在,则创建失败,而且它是一个原子性操作,比如下面的命令,我们的k1已经存在,k4不存在,但是还是创建失败
127.0.0.1:6379> msetnx k1 v1 k4 v4 # 要么一起成功,要么一起失败
(integer) 0
127.0.0.1:6379> get k4
(nil)
  1. mset的高阶用法
# user:{id}:{filed},如此设计在redis中是完全可以的
127.0.0.1:6379> mset user:1:name zhangsan user:1:age 2
OK
127.0.0.1:6379> mget user:1:name user:1:age
1) "zhangsan"
2) "2"
  1. 使用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 mysql
"redis" # 如果存在,则返回原来的值,并设置新的值
127.0.0.1:6379> get db
"mysql"
  1. String的value除了是字符串,还可以是数字,它的用途:
  • 计数器
  • 统计多单位的数量
  • 粉丝数
  • 对象缓存存储

List(集合)

  1. redis里面,可以把List当成栈,队列,阻塞队列各种数据结构
  2. 所有的List命令都是以L开头的
  3. 使用lpush可以给List里面从左边添加值
127.0.0.1:6379> LPUSH list 1
(integer) 1
127.0.0.1:6379> LPUSH list 2
(integer) 2
127.0.0.1:6379> LPUSH list 3
(integer) 3
127.0.0.1:6379> LRANGE list 0 -1
1) "3"
2) "2"
3) "1"
127.0.0.1:6379> LRANGE list 0 1
1) "3"
2) "2"
  1. 使用rpush可以给List里面从右边添加值
127.0.0.1:6379> RPUSH list2 1
(integer) 1
127.0.0.1:6379> RPUSH list2 2
(integer) 2
127.0.0.1:6379> RPUSH list2 3
(integer) 3
127.0.0.1:6379> LRANGE list2 0 -1
1) "1"
2) "2"
3) "3"
  1. 使用lpop可以从移除第一个元素,使用可以从移除最后一个元素,或者可以理解为:lpop可以从左边移除第一个元素,rpop可以从右边移除第一个元素
127.0.0.1:6379> LRANGE list 0 -1
1) "3"
2) "2"
3) "1"
127.0.0.1:6379> LPOP list
"3"
127.0.0.1:6379> rpop list
"1"
  1. 使用lindex可以获取指定下标索引的值
127.0.0.1:6379> lrange list2 0 -1
1) "1"
2) "2"
3) "3"
127.0.0.1:6379> LINDEX list2 1
"2"
  1. 使用llen可以查看List的长度
127.0.0.1:6379> LLEN list2
(integer) 3
  1. 使用LREM可以移除指定的值
127.0.0.1:6379> lrange list2 0 -1
1) "1"
2) "2"
3) "3"
4) "4"
5) "3"
127.0.0.1:6379> LREM list2 1 1 # 移除list2中的一个1
(integer) 1
127.0.0.1:6379> lrange list2 0 -1
1) "2"
2) "3"
3) "4"
4) "3"
127.0.0.1:6379> LREM list2 2 3 # 移除list2中的2个3
(integer) 2
127.0.0.1:6379> lrange list2 0 -1
1) "2"
2) "4"
  1. 使用ltrim可以截取List,截取以后,原本的List已经被修改了
127.0.0.1:6379> RPUSH list hello
(integer) 1
127.0.0.1:6379> RPUSH list hello1
(integer) 2
127.0.0.1:6379> RPUSH list hello2
(integer) 3
127.0.0.1:6379> RPUSH list hello3
(integer) 4
127.0.0.1:6379> LTRIM list 1 2 # 截取list的[1,2]
OK
127.0.0.1:6379> LRANGE list 0 -1
1) "hello1"
2) "hello2"
  1. rpoppush可以移除List中的最后一个元素,然后移动到新的列表中
127.0.0.1:6379> RPUSH list hello
(integer) 1
127.0.0.1:6379> RPUSH list hello1
(integer) 2
127.0.0.1:6379> RPUSH list hello2
(integer) 3
127.0.0.1:6379> RPUSH list hello3
(integer) 4
127.0.0.1:6379> RPOPLPUSH list otherlist
"hello3"
127.0.0.1:6379> LRANGE list 0 -1
1) "hello"
2) "hello1"
3) "hello2"
127.0.0.1:6379> LRANGE otherlist 0 -1
1) "hello3"
  1. 使用exists可以判断List是否存在,使用lset可以修改List的某个值,但是如果List不存在,那么就会报错
127.0.0.1:6379> exists list # 判断list集合是否存在
(integer) 0
127.0.0.1:6379> lset list 0 item # 设置list集合的第0个索引的值为item
(error) ERR no such key # 因为list不存在,所以会报错
127.0.0.1:6379> lpush list qaq
(integer) 1
127.0.0.1:6379> lrange list 0 -1
1) "qaq"
127.0.0.1:6379> lset list 0 item
OK
127.0.0.1:6379> lrange list 0 -1
1) "item"
  1. 使用linsert可以给List指定元素的前/后插入值
127.0.0.1:6379> rpush list hello
(integer) 1
127.0.0.1:6379> rpush list world
(integer) 2
127.0.0.1:6379> lrange list 0 -1
1) "hello"
2) "world"
127.0.0.1:6379> LINSERT list before world other # 给world的前面插入other
(integer) 3
127.0.0.1:6379> lrange list 0 -1
1) "hello"
2) "other"
3) "world"
127.0.0.1:6379> LINSERT list after world new # 给world的后面插入new
(integer) 4
127.0.0.1:6379> lrange list 0 -1
1) "hello"
2) "other"
3) "world"
4) "new"

Set(集合)

set中的值是不能重复的

  1. 使用SADD可以给set集合添加值,如果添加失败,就会返回0
127.0.0.1:6379> SADD myset "hello"
(integer) 1
127.0.0.1:6379> SADD myset "hello"
(integer) 0
  1. SISMEMBER可以查看set集合
127.0.0.1:6379> SADD myset "hello2"
(integer) 1
127.0.0.1:6379> SADD myset "hello3"
(integer) 1
127.0.0.1:6379> SMEMBERS myset
1) "hello2"
2) "hello1"
3) "hello"
4) "hello3"
  1. SISMEMBER可以判断当前值是否存在于集合中
127.0.0.1:6379> SISMEMBER myset "hello"
(integer) 1
127.0.0.1:6379> SISMEMBER myset "qxf"
(integer) 0
  1. SCARD可以查看set集合的长度
127.0.0.1:6379> SCARD myset
(integer) 5
  1. SREM可以删除set集合中的元素
127.0.0.1:6379> SREM myset "hello3"
(integer) 1
127.0.0.1:6379> SMEMBERS myset
1) "qaq"
2) "hello2"
3) "hello"
4) "hello1"
  1. SRANDMEMBER可以随机选取几个元素
127.0.0.1:6379> SRANDMEMBER myset 2
1) "hello1"
2) "hello2"
127.0.0.1:6379> SRANDMEMBER myset 2
1) "hello1"
2) "hello"
127.0.0.1:6379> SRANDMEMBER myset 2
1) "hello1"
2) "hello"
127.0.0.1:6379> SRANDMEMBER myset 2
1) "hello1"
2) "hello2"
  1. SPOP可以随机删除一个元素
127.0.0.1:6379> SPOP myset
"qaq"
127.0.0.1:6379> SMEMBERS myset
1) "hello2"
2) "hello"
3) "hello1"
127.0.0.1:6379> SPOP myset
"hello1"
127.0.0.1:6379> SMEMBERS myset
1) "hello2"
2) "hello"
  1. SMOVE可以移动一个元素到另一个set集合
127.0.0.1:6379> SADD myset "hello"
(integer) 1
127.0.0.1:6379> SADD myset "hello1"
(integer) 1
127.0.0.1:6379> SADD myset "hello2"
(integer) 1
127.0.0.1:6379> SADD myset "hello3"
(integer) 1
127.0.0.1:6379> SMOVE myset myset2 "hello2"
(integer) 1
127.0.0.1:6379> SMEMBERS myset
1) "hello1"
2) "hello"
3) "hello3"
127.0.0.1:6379> SMEMBERS myset2
1) "hello2"
  1. SDIFF可以取两个集合的差集,SINTER可以取两个集合的交集,SUNION可以取两个集合的并集
127.0.0.1:6379> sadd myset 1
(integer) 1
127.0.0.1:6379> sadd myset 2
(integer) 1
127.0.0.1:6379> sadd myset 3
(integer) 1
127.0.0.1:6379> sadd myset2 3
(integer) 1
127.0.0.1:6379> sadd myset2 4
(integer) 1
127.0.0.1:6379> sadd myset2 5
(integer) 1
127.0.0.1:6379> SDIFF myset myset2 # 取差集
1) "1"
2) "2"
127.0.0.1:6379> SINTER myset myset2 # 取交集,共同好友,共同爱好就可以使用这个实现
1) "3"
127.0.0.1:6379> SUNION myset myset2 # 取并集
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"

Hash

Map集合,key-value集合,这个时候,值是一个map集合,本质和String类型没有太大区别,还是一个简单的key-value

  1. HSET可以给Hash设置值
HSET myhash k1 v1 k2 v2
(integer) 2
  1. HGET可以获取Hash的value值
127.0.0.1:6379> HGET myhash k2
"v2"
127.0.0.1:6379> HGET myhash k1
"v1"
  1. HMSET可以设置多个值,HMGET可以同时获得多个值,HGETALL可以获取所有值
127.0.0.1:6379> HMSET my k1 v1 k2 v2 
OK
127.0.0.1:6379> HMGET my k1 k2
1) "v1"
2) "v2"
127.0.0.1:6379> HGETALL my
1) "k1"
2) "v1"
3) "k2"
4) "v2"
  1. HDEL可以删除指定的key值
127.0.0.1:6379> HDEL myhash my k1
(integer) 1
127.0.0.1:6379> HGETALL myhash
1) "k2"
2) "v2"
  1. HLEN可以获取长度
127.0.0.1:6379> HLEN my
(integer) 2
  1. HEXISTS判断指定字段是否存在
127.0.0.1:6379> hmset myhash k1 v1 k2 v2 k3 v3
OK
127.0.0.1:6379> HGETALL myhash
1) "k1"
2) "v1"
3) "k2"
4) "v2"
5) "k3"
6) "v3"
127.0.0.1:6379> HEXISTS myhash k2
(integer) 1
127.0.0.1:6379> HEXISTS myhash k5
(integer) 0
  1. HKEYS可以获取所有的key值,HVALS可以获取所有的value值
127.0.0.1:6379> HKEYS myhash
1) "k1"
2) "k2"
3) "k3"
127.0.0.1:6379> HVALS myhash
1) "v1"
2) "v2"
3) "v3"
  1. HINCRBY可以使值自增或者自减
127.0.0.1:6379> hset my k1 5
(integer) 1
127.0.0.1:6379> HINCRBY my k1 3
(integer) 8
127.0.0.1:6379> HINCRBY my k1 -1
(integer) 7
  1. hash的高阶用法,hash更加适合存储对象,String适合字符串存储
127.0.0.1:6379> hset user:1 name qxf
(integer) 1
127.0.0.1:6379> HGET user:1 name
"qxf"

127.0.0.1:6379> hset goods:food name bread
(integer) 1
127.0.0.1:6379> hget goods:food name
"bread"

Zset(有序集合)

在Set的基础上,增加了一个值

  1. ZADD可以添加值,但是每次添加值需要添加一个序号
127.0.0.1:6379> ZADD myset 1 one 2 two 3 three
(integer) 3
  1. ZRANGE可以查看set集合
127.0.0.1:6379> ZRANGE myset 0 -1
1) "one"
2) "two"
3) "three"
  1. ZRANGEBYSCORE可以降序排序集合
127.0.0.1:6379> ZADD salary 2500 xiaohong 5000 zhangsan 500 qxf
(integer) 3
127.0.0.1:6379> ZRANGE salary 0 -1
1) "qxf"
2) "xiaohong"
3) "zhangsan"
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf # 从正无穷到负无穷显示salary
1) "qxf"
2) "xiaohong"
3) "zhangsan"
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf withscores # 从正无穷到负无穷带上得分显示salary
1) "qxf"
2) "500"
3) "xiaohong"
4) "2500"
5) "zhangsan"
6) "5000"
127.0.0.1:6379> ZRANGEBYSCORE salary -inf 2500 withscores # 从正无穷到2500带上得分显示salary
1) "qxf"
2) "500"
3) "xiaohong"
4) "2500"
127.0.0.1:6379> ZRANGEBYSCORE salary 0 1000 withscores # 从0到1000带上得分显示salary
1) "qxf"
2) "500"
  1. ZREVRANGE可以升序排列集合
127.0.0.1:6379> ZREVRANGE salary 0 -1 withscores
1) "zhangsan"
2) "5000"
3) "qxf"
4) "500"
  1. ZREM可以删除一个元素
127.0.0.1:6379> ZREM salary xiaohong
(integer) 1
127.0.0.1:6379> zrange salary 0 -1
1) "qxf"
2) "zhangsan"
  1. ZCARD可以查看集合的长度
127.0.0.1:6379> ZCARD salary
(integer) 2
  1. ZCOUNT可以获取某个区间内的成员数量
127.0.0.1:6379> ZCOUNT salary -inf +inf
(integer) 2
  1. Zset可以排序,用于做排行榜或者排序表

geospatial(地理位置)

  1. 用于存储经纬度信息,可以实现附近的人,直线距离计算等
  2. 在Redis3.2版本以后就推出了,可以计算方圆几里的人
  3. GEOADD可以添加地理位置
127.0.0.1:6379> GEOADD china:city 109.247 34.33 xian # 增加西安的经纬度
(integer) 1
127.0.0.1:6379> GEOADD china:city 116.23 40.22 beijing # 增加北京的经纬度
(integer) 1
127.0.0.1:6379> GEOADD china:city 113.88 22.55 shenzhen # 增加深圳的经纬度
(integer) 1
127.0.0.1:6379> GEOADD china:city 121.48 31.40 shanghai # 增加上海的经纬度
(integer) 1
  1. GEOPOS可以获得指定key的经纬度
127.0.0.1:6379> GEOPOS china:city shanghai # 查看上海的经纬度
1) "121.48000091314315796"
2) "31.40000025319353938"
  1. GEODIST可以获取两个经纬度之间的距离
127.0.0.1:6379> GEODIST china:city shanghai beijing km # 查看上海到北京的直线距离
"1088.7854"
  1. GEORADIUS可以查看以当前经纬度为圆心,附近半径内的经纬度
127.0.0.1:6379> GEORADIUS china:city 110 30 1000 km # 查看以经纬度为110 30半径1000km的城市
1) "xian"
2) "shenzhen"
127.0.0.1:6379> GEORADIUS china:city 110 30 1000 km withdist # 查看以经纬度为110 30半径1000km的城市的距离
1) 1) "xian"
   2) "486.7952"
2) 1) "shenzhen"
   2) "914.3335"
127.0.0.1:6379> GEORADIUS china:city 110 30 1000 km withcoord # 查看以经纬度为110 30半径1000km的城市的经纬度
1) 1) "xian"
   2) 1) "109.246998131275177"
      2) "34.33000103844675976"
2) 1) "shenzhen"
   2) 1) "113.87999922037124634"
      2) "22.5500010475923105"
127.0.0.1:6379> GEORADIUS china:city 110 30 1000 km withcoord count 2  #查看以经纬度为110 30半径1000km的城市(展示2个)
1) 1) "xian"
   2) 1) "109.246998131275177"
      2) "34.33000103844675976"
2) 1) "shenzhen"
   2) 1) "113.87999922037124634"
      2) "22.5500010475923105"
  1. GEORADIUSBYMEMBER可以找出指定key值周围的城市
127.0.0.1:6379> GEORADIUSBYMEMBER china:city xian 1000 km
1) "xian"
2) "beijing"
  1. GEO的底层实现原理就是ZSet,我们可以使用ZSet命令来操作GEO
127.0.0.1:6379> ZRANGE china:city 0 -1 # 查看地图中全部元素
1) "xian"
2) "shenzhen"
3) "shanghai"
4) "beijing"
127.0.0.1:6379> zrem china:city beijing # 删除指定元素
(integer) 1
127.0.0.1:6379> ZRANGE china:city 0 -1
1) "xian"
2) "shenzhen"
3) "shanghai"

Hyperloglog(基数统计)

  1. 基数=不重复的元素
  2. 主要用于做基数统计的算法
  3. 比如可以统计网站的UV(一个人多次访问同一个网站,但是还是算做同一个人)
  4. 传统的方式是使用set集合保存用户的id,如果保存大量的用户id,就会比较麻烦,我们的目的是为了计数,而不是为了保存用户id
  5. 优点:占用内存是固定的,2^64不同的元素,只需要12kb的内存,如果要从内存角度来比较的话,Hyperloglog就是我们的首选
  6. 缺点:存在0.81%的错误率,在一定范围内的错误率,我们还是可以接受的,如果不能接受的话,建议使用ZSet
  7. PFADD可以添加元素
127.0.0.1:6379> PFADD userid a b c d e f g h i j k
(integer) 1
  1. PFCOUNT可以统计元素(不重复的)
127.0.0.1:6379> PFCOUNT userid
(integer) 11
127.0.0.1:6379> PFADD userid2 a a a a b
(integer) 1
127.0.0.1:6379> PFCOUNT userid2
(integer) 2
  1. PFMERGE可以合并
127.0.0.1:6379> PFMERGE userid3 userid userid2
OK
127.0.0.1:6379> PFCOUNT userid3
(integer) 11

Bitmaps(位图)

  1. 位存储指的就是 0 1存储
  2. 可以用来统计用户信息(活跃,不活跃;登录,未登录;打卡,未打卡),只有两个状态
  3. 所有操作都是操作二进制位进行记录,就只有0和1两个状态
  4. SETBIT可以设置值(比如我们用Bitmaps来记录打卡记录)
127.0.0.1:6379> SETBIT sign 0 1
(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 1
(integer) 0
127.0.0.1:6379> SETBIT sign 6 1
(integer) 0
  1. GETBIT可以查看某一个key里面的状态
127.0.0.1:6379> GETBIT sign 3
(integer) 1
127.0.0.1:6379> GETBIT sign 0
(integer) 1
  1. BITCOUNT可以统计为1的个数
127.0.0.1:6379> BITCOUNT sign
(integer) 5

redis事务

Redis单条命令能够确保原子性,但是事务不保证原子性~

Redis事务的本质:一组命令一起执行!一个事务中的所有命令都会被序列化,在事务执行的过程中,会按照顺序执行

使用事务的方式:
1,开启事务(multi
2,执行事务(exec

例子如下:

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)> get k2
QUEUED
127.0.0.1:6379(TX)> set k3 v3
QUEUED
127.0.0.1:6379(TX)> EXEC  # 执行事务
1) OK
2) OK
3) "v2"
4) OK

中断事务的例子:

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)> DISCARD  # 中断事务
OK
127.0.0.1:6379> get k1
(nil)

事务中命令如果有异常,事务就不会执行

例如:我们在事务执行的命令中插入一条错误的命令,那么就会报错,当我们执行事务的时候,就会抛出这个错误,且所有命令都不会被执行!

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)> setget k3 v3  # 这是一条错误的命令!
(error) ERR unknown command `setget`, with args beginning with: `k3`, `v3`, 
127.0.0.1:6379(TX)> set k4 v4
QUEUED
127.0.0.1:6379(TX)> EXEC
(error) EXECABORT Transaction discarded because of previous errors.

事务中如果执行的时候有异常,那么事务就会跳过当前命令,执行其他的命令

例如:我们在事务中给一个字符串类型的值进行加1操作,虽然执行命令的时候没有报错,但是提交事务的时候,这个加1操作会抛出异常,那么这个加1的命令就不会执行,而是会执行其他可以执行成功的命令

127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> set k1 "k1"
QUEUED
127.0.0.1:6379(TX)> INCR k1  # 提交事务的时候会抛出异常!
QUEUED
127.0.0.1:6379(TX)> EXEC
1) OK
2) (error) ERR value is not an integer or out of range
127.0.0.1:6379> get k1
"k1"

Redis实现乐观锁

悲观锁

  • 认为什么时候都会出现问题,无论做什么操作都会加锁!

乐观锁

  • 很乐观,认为什么时候都不会出现问题,所以不会上锁,只有更新数据的时候去判断一下,在此期间是否有人修改过这个数据,可以使用version去判断,如果version改变了,那么就会重新去获取最新的数据

redis里面有一个命令关键字watch,可以用来监控我们要锁住的对象或者属性

下面是没有出现任何问题时候的乐观锁:

127.0.0.1:6379> set money 100
OK
127.0.0.1:6379> watch money  # 监视money
OK
127.0.0.1:6379> 
127.0.0.1:6379> 
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> DECRBY money 20
QUEUED
127.0.0.1:6379(TX)> EXEC  # 没有问题,事务正常结束,期间的money没有发生变动
1) (integer) 80

下面我们来模拟一下多个客户端操作同一个watch的对象,看看会发生什么~

首先第一个客户端,给money变量添加watch,在事务中修改完money变量以后,我们没有直接提交事务,而是等待另一个客户端获取到money变量,然后修改money变量的值,然后我们第一个客户端再提交事务

客户端1

127.0.0.1:6379> watch money
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> DECRBY money 20  # 执行完这一步,我们没有直接提交事务,而是等待客户端二修改money的值
QUEUED
127.0.0.1:6379(TX)> exec  # 等待第2个客户端修改完money的值以后,我们再提交事务,会发现返回的是一个空值,也就说明我们的事务没有执行成功
(nil)
127.0.0.1:6379> get money
"1000"

客户端2

127.0.0.1:6379> get money
"80"
127.0.0.1:6379> set money 1000  # 我们在这个地方修改money的值
OK
127.0.0.1:6379> get money
"1000"

那么如果遇到这种事务执行失败的情况,我们应该怎么办?我们可以先执行unwatch,放弃监视这个变量,然后再重新watch这个变量,再重新执行我们的事务,就可以执行成功了

127.0.0.1:6379> UNWATCH  # 首先放弃监视
OK
127.0.0.1:6379> watch money  # 重新开始监视
OK
127.0.0.1:6379> MULTI  # 开启事务
OK
127.0.0.1:6379(TX)> DECR money
QUEUED
127.0.0.1:6379(TX)> EXEC  # 对比version是否发生改变,如果没有发生改变,那么就执行事务,否则就返回空
1) (integer) 999

Jedis

jedis是官方推荐的java连接开发工具,使用java操作redis的中间件

PS:maven的官方仓库:https://mvnrepository.com/tags/maven

第一步:导入jedis的依赖包:

<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>3.3.0</version>
</dependency>

<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.76</version>
</dependency>

第二步:编码测试

如果远程连接不上jedis,那么可以参考下面的链接:
https://www.cnblogs.com/kevin860/p/11364358.html

  • 连接数据库
  • 操作命令
  • 断开连接
    public static void main(String[] args) {
    	// 连接数据库
        Jedis jedis = new Jedis("127.0.0.1", 6379);
        // 执行命令
        System.out.println(jedis.ping());
        jedis.set("k1", "v1");
        System.out.println(jedis.get("k1"));
        // 强制关闭jedis
        jedis.shutdown();
    }

redis执行事务:

    public static void main(String[] args) {
    	// 连接数据库
        Jedis jedis = new Jedis("127.0.0.1", 6379);
        // 构造数据
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("hello", "world");
        jsonObject.put("name", "qxf");
		
		// 开启事务
        Transaction transaction = jedis.multi();
        String result = jsonObject.toJSONString();

        try {
        	// 执行命令
            transaction.set("user1", result);
            transaction.set("user2", result);
            // 提交事务
            transaction.exec();
        } catch (Exception e) {
        	// 如果发生异常,那么事务回滚
            transaction.discard();
            e.printStackTrace();
        } finally {
        	// 打印当前值
            System.out.println(jedis.get("user1"));
            System.out.println(jedis.get("user2"));
            // 关闭客户端
            jedis.close();
        }
    }

执行结果:
在这里插入图片描述

Springboot整合redis

  1. 在SpringBoot2.x之后,原来的jedis被替换为了lettuce
  2. jedis:底层采用的是直连,如果有多个线程操作的话,是不安全的,如果想要避免不安全的,使用jedis pool连接池,如果线程数量太多,就会导致服务端负载太大,相当于BIO
  3. lettuce:底层使用netty,实例可以在多个线程中进行共享,不存在线程不安全的情况,可以减少线程的数量,更像NIO模式
    在这里插入图片描述
  4. RedisTemplate我们可以自定义,因为@ConditionalOnMissingBean(name = "redisTemplate")就是如果redisTemplate不存在,下面的配置才会生效
    在这里插入图片描述

默认的RedisTemplate没有过多的设置,redis对象都是需要序列化的,两个泛型都是Object类型,我们使用的时候都需要强制转换

在这里插入图片描述
上面的这个是针对于String类型的操作

  1. 我们可以看到Redis默认的连接工厂有两个:Jedis和Lettuce在这里插入图片描述

  2. 使用方式:

  • 导入依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
  • 配置文件
spring.redis.host=106.55.170.35
spring.redis.port=6379
  • 连接测试
@SpringBootTest
class RedisDemoApplicationTests {
    @Autowired
    private RedisTemplate redisTemplate;

    @Test
    void contextLoads() {
        redisTemplate.opsForValue().set("qxf", "101");
        System.out.println(redisTemplate.opsForValue().get("qxf"));
    }
}

运行结果:
在这里插入图片描述
我们通过命令行查询发行redis数据库中会乱码:
在这里插入图片描述
原因是这样的:
在这里插入图片描述
在这里插入图片描述

  1. 默认的序列化配置是jdk自带的序列化配置,我们可以定义一个User类,然后我们写一个测试类看下:
package com.demo.pojo;

/**
 * @author qxf101
 */
public class User{
    private String name;
    private int age;

    public User() {
    }

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

然后我们写一个测试类测试序列化:

@Test
public void test() throws JsonProcessingException {
    User user = new User();
    user.setName("qxf");
    user.setAge(27);
    String jsonUser = new ObjectMapper().writeValueAsString(user);
    redisTemplate.opsForValue().set("user", jsonUser);
    System.out.println(redisTemplate.opsForValue().get("user"));
}

运行结果:
在这里插入图片描述

如果我们不序列化,我们可以看一下结果:

@Test
public void test() throws JsonProcessingException {
    User user = new User();
    user.setName("qxf");
    user.setAge(27);
    redisTemplate.opsForValue().set("user", user);
    System.out.println(redisTemplate.opsForValue().get("user"));
}

在这里插入图片描述
我们发现就会报错,提示无法序列化,而且需要序列化以后才可以

  1. 我们也可以自己编写redisTemplate,如下:
@Configuration
public class RedisConfig {
    /**
     * 编写我们自己的redisTemplate
     *
     * @param factory redis连接工厂
     * @return redisTemplate
     */
    @Bean
    @SuppressWarnings("all")
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);
        Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        serializer.setObjectMapper(objectMapper);

        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        template.setKeySerializer(stringRedisSerializer);
        template.setHashKeySerializer(stringRedisSerializer);

        template.setValueSerializer(serializer);
        template.setHashKeySerializer(serializer);
        template.afterPropertiesSet();

        return template;
    }
}

那我们的测试类就可以改成(记得把User类实现Serializable接口):

@SpringBootTest
class RedisDemoApplicationTests {
    @Autowired
    @Qualifier("redisTemplate")
    private RedisTemplate redisTemplate;

    @Test
    void contextLoads() {
        redisTemplate.opsForValue().set("qxf", "101");
        System.out.println(redisTemplate.opsForValue().get("qxf"));
    }

    @Test
    public void test() throws JsonProcessingException {
        User user = new User();
        user.setName("qxf");
        user.setAge(27);
        redisTemplate.opsForValue().set("user", user);
        System.out.println(redisTemplate.opsForValue().get("user"));
    }
}

Redis配置文件详解

  1. redis默认对大小写不敏感
    在这里插入图片描述

  2. redis配置里面可以包含更多的配置文件
    在这里插入图片描述

  3. redis默认是只允许本地应用访问的,我们也可以在配置文件中指定ip地址对redis进行远程访问
    在这里插入图片描述

  4. redis有一个保护模式,默认是开启的,如果没有给redis设置用户名和密码,那么如果开启保护模式,那么远程访问就会失败
    在这里插入图片描述

  5. redis默认的端口号是6379,我们可以在配置文件中修改我们redis的远程连接的端口号
    在这里插入图片描述

  6. redis可以设置redis在后台以守护进程运行
    在这里插入图片描述

  7. 如果我们设置了redis以守护进程运行,那么我们可以设置我们pid的进程名称
    在这里插入图片描述

  8. redis可以设置日志的级别,默认是notice级别
    在这里插入图片描述

  9. 结合上面的日志,我们可以设置我们的日志文件名称
    在这里插入图片描述

  10. redis默认有16个数据库,我们也可以在配置文件中设置数据库的个数
    在这里插入图片描述

  11. redis还可以设置启动时是否显示logo
    在这里插入图片描述

  12. 在redis中,我们还可以自定义我们自己的持久化规则,因为redis是内存数据库,所以我们可以选择把数据持久化到本地
    在这里插入图片描述

save 900 1 # 如果900秒之内,至少有一个key进行了修改,那么我们就进行持久化操作
save 300 10 # 如果300秒之内,至少有10个key进行了修改,那么我们就进行持久化操作
save 60 10000 # 如果60秒之内,至少有10000个key进行了修改,那么我们就进行持久化操作
  1. redis还可以设置持久化失败以后的操作,默认是继续工作
    在这里插入图片描述

  2. redis可以设置是否压缩rdb文件,如果压缩的话,就会消耗一些系统资源在这里插入图片描述

  3. redis保存rdb文件的时候,是否会进行校验
    在这里插入图片描述

  4. redis可以设置持久化的目录,默认是当前目录
    在这里插入图片描述

  5. redis可以设置默认是否需要密码
    在这里插入图片描述

127.0.0.1:6379> config get requirepass # 获取我们当前的密码
1) "requirepass"
2) ""
127.0.0.1:6379> config set requirepass "123456" # 设置我们当前的密码是123456
OK
127.0.0.1:6379> config get requirepass # 如果没有进行密码的校验,就会提示没有权限访问
(error) NOAUTH Authentication required.
127.0.0.1:6379> ping
(error) NOAUTH Authentication required.
127.0.0.1:6379> auth 123456 # 进行密码的认证操作
OK
127.0.0.1:6379> ping # 验证以后就可以进行操作了
PONG
  1. 设置redis默认的最大的连接用户数
    在这里插入图片描述
  2. 设置redis的最大的内存容量
    在这里插入图片描述
  3. redis内存满了以后的操作(一共6种策略)
    在这里插入图片描述

Redis的持久化

  1. Redis是内存数据库,如果不将内存中的数据库中的数据保存到硬盘中,那么一旦服务器进程退出,服务器中的数据库状态也会消失,所以Redis就为我们提供了数据持久化的功能
  2. Redis持久化有两种方式:RDB和AOF
  3. RDB:在指定的时间间隔内将内存中的数据集快照写入磁盘中,也就是行话讲的Snapshot快照,它恢复时是将快照文件直接读入内存中。
  4. Redis会单独的创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程是不进行任何IO操作的,这就确保了极高的性能,如果需要进行大规模数据的恢复,且对数据恢复的完整性不是非常的敏感,那RDB方式要比AOF方式更加的高效,RDB的缺点就是最后一次持久化后的数据可能丢失。
    在这里插入图片描述
  5. RDB保存的文件就是dump.rdb,我们可以在配置文件里面设置
    在这里插入图片描述
  6. 如果我们的redis突然断电或者突然关机,如果我们配置的有RDB方式的持久化,那么即使我们的机器断电或者关机,那么下次重新启动redis的时候,我们的数据也会通过rdb文件进行恢复
  7. rdb文件的生成和配置文件中的save的规则是强相关的,如果满足我们的save规则,就会自动触发我们的rdb规则,生成rdb文件。如果执行flushall命令,也会触发我们的rdb规则,生成rdb文件。退出我们的redis,也会触发我们的rdb规则,生成rdb文件。
  8. 如果要恢复rdb文件,只需要将rdb文件放到我们的redis的启动目录下,redis在启动的时候就会自动扫描我们的rdb文件进行数据的恢复
127.0.0.1:6379> config get dir # 可以查看我们的redis的启动目录
1) "dir"
2) "/usr/local/redis/bin"
  1. RDB的优点:适合大规模的数据恢复,对数据完整性要求不高。缺点:需要一定的时间间隔进程操作,如果redis意外宕机,最后一次修改的数据就没有了。fork进程的时候,会占用一定的内存空间。
  2. 有时候在生产环境中,我们会将这个文件进行备份。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值