Redis学习


2021/4/10 java学习日记

一、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)。

二、使用步骤

1.启动Rdis服务,指定配置文件启动:

redis-server 

在这里插入图片描述

2.查看Redis进程是否开启

ps -ef|grep redis

在这里插入图片描述

3.关闭Redis

shutdown

在这里插入图片描述

三、Redis的常用命令

set get命令,存取值

在这里插入图片描述

切换数据库,redis默认有16个数据库,默认使用0号数据库,可以通过select切换

在这里插入图片描述
在这里插入图片描述

flushdb 清空当前数据库 flushall 清空所有数据库

在这里插入图片描述

keys* 查询所有key

在这里插入图片描述

dbsize 查询数据库的大小

在这里插入图片描述

exists keyName 查看key是否存在,存在返回1,否则返回0

在这里插入图片描述

move keyName 1 移除指定Key

在这里插入图片描述

expire keyName timeout 为key设置过期时间

在这里插入图片描述

ttl keyName 查看key的剩余存活时间

在这里插入图片描述

type keyName 查看key的类型

在这里插入图片描述

四、五大数据类型

1.String操作

strlen:求value的长度,append:追加

在这里插入图片描述

incr incrby decr decrby 自增,自减操作

在这里插入图片描述
getrange,setrange 字符串的范围操作

在这里插入图片描述

setex(如果存在Key则覆盖,不存在则创建)setnx(如果不存在就设置,不能覆盖) 设置过期时间

在这里插入图片描述

在这里插入图片描述

mset,mget, msetnx(具有为原子性,要么都成功,要么都失败)批量设置和获取值

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> mget k1 k2 k4
1) "v1"
2) "v2"
3) (nil)
127.0.0.1:6379> MSETNX k1 v1 
(integer) 0
127.0.0.1:6379> MSETNX k1 v1 k4 v4
(integer) 0
127.0.0.1:6379> MSETNX k4 v4 k5 v5
(integer) 1
127.0.0.1:6379> mget k1 k2 k3 k4 k5
1) "v1"
2) "v2"
3) "v3"
4) "v4"
5) "v5"
127.0.0.1:6379> 

msetmget存取对象

127.0.0.1:6379> mset user:u:name baxxi user:u:age 20
OK
127.0.0.1:6379> mget user:u:name user:u:age
1) "baxxi"
2) "20"

getset(先获取再设置,如果不存在就返回nil)

127.0.0.1:6379> getset k1 v1
(nil)
127.0.0.1:6379> getset k1 v2
"v1"
127.0.0.1:6379> get k1
"v2"

2.list操作

在这里插入图片描述

lpush在左侧插入 lpop 从左侧删除

127.0.0.1:6379> LPUSH list v1 v2 v3
(integer) 3
127.0.0.1:6379> LRANGE list 0 -1
1) "v3"
2) "v2"
3) "v1"
127.0.0.1:6379> LPOP list 
"v3"
127.0.0.1:6379> LRANGE list 0 -1
1) "v2"
2) "v1"

rpush在右侧插入 rpop 从右侧删除

127.0.0.1:6379> RPUSH list v1 v2 v3
(integer) 3
127.0.0.1:6379> LRANGE list 0 -1
1) "v1"
2) "v2"
3) "v3"
127.0.0.1:6379> RPOP list
"v3"
127.0.0.1:6379> LRANGE list 0 -1
1) "v1"
2) "v2"

lindex通过下标获取元素

127.0.0.1:6379> LRANGE list 0 -1
1) "v1"
2) "v2"
127.0.0.1:6379> LINDEX list 1
"v2"
127.0.0.1:6379> LINDEX list 0
"v1"
127.0.0.1:6379> 

llen获取列表长度

127.0.0.1:6379> RPUSH list v1 v2 v3
(integer) 3
127.0.0.1:6379> LLEN list
(integer) 3

lrem移除具体的值所在的键值对

127.0.0.1:6379> RPUSH list v1 v2 v3 v3 v4
(integer) 5
127.0.0.1:6379> LRANGE list 0 -1
1) "v1"
2) "v2"
3) "v3"
4) "v3"
5) "v4"
127.0.0.1:6379> LREM list 2 v3
(integer) 2
127.0.0.1:6379> LRANGE list 0 -1
1) "v1"
2) "v2"
3) "v4"

ltrim 截取队列 截取的开始下标 截取的结束下标

127.0.0.1:6379> RPUSH list v1 v2 v3 v3 v4 v5
(integer) 6
127.0.0.1:6379> LRANGE list 0 -1
1) "v1"
2) "v2"
3) "v3"
4) "v3"
5) "v4"
6) "v5"
127.0.0.1:6379> LTRIM list 2 4
OK
127.0.0.1:6379> LRANGE list 0 -1
1) "v3"
2) "v3"
3) "v4"

rpoplpush移除列表中的最后一个元素并将这个元素放到一个新的列表中

127.0.0.1:6379> RPUSH list v1 v2 v3 v4
(integer) 4
127.0.0.1:6379> RPUSH list1 v5 v6
(integer) 2
127.0.0.1:6379> LRANGE list 0 -1
1) "v1"
2) "v2"
3) "v3"
4) "v4"
127.0.0.1:6379> LRANGE list1 0 -1
1) "v5"
2) "v6"
127.0.0.1:6379> RPOPLPUSH list list1
"v4"
127.0.0.1:6379> LRANGE list1 0 -1
1) "v4"
2) "v5"
3) "v6"
127.0.0.1:6379> LRANGE list 0 -1
1) "v1"
2) "v2"
3) "v3"

EXISTS list判断列表是否存在

127.0.0.1:6379> EXISTS list
(integer) 1

lset更新列表中指定下标的元素的value,前提是该下标必须有值,否则报错

127.0.0.1:6379> LRANGE list 0 -1
1) "v1"
2) "v2"
3) "v3"
127.0.0.1:6379> LSET list 1 123
OK
127.0.0.1:6379> LRANGE list 0 -1
1) "v1"
2) "123"
3) "v3"
127.0.0.1:6379> LSET list 3 123
(error) ERR index out of range

linsert在列表中插入值:前插和后插

127.0.0.1:6379> RPUSH list v1 v2 v3 v4
(integer) 4
127.0.0.1:6379> LRANGE list 0 -1
1) "v1"
2) "v2"
3) "v3"
4) "v4"
127.0.0.1:6379> LINSERT list before v1 hello
(integer) 5
127.0.0.1:6379> LRANGE list 0 -1
1) "hello"
2) "v1"
3) "v2"
4) "v3"
5) "v4"
127.0.0.1:6379> LINSERT list after v2 baxxi
(integer) 6
127.0.0.1:6379> LRANGE list 0 -1
1) "hello"
2) "v1"
3) "v2"
4) "baxxi"
5) "v3"
6) "v4"

3.set操作

set:无序不重复的集合

sadd 添加

127.0.0.1:6379> sadd set v1 v2 v3 v4
(integer) 4

smembers 查看所有

127.0.0.1:6379> SMEMBERS set
1) "v3"
2) "v1"
3) "v4"
4) "v2"

scard查询set的元素个数

127.0.0.1:6379> SCARD set
(integer) 4

sismember判断set中是否存在某元素

127.0.0.1:6379> SISMEMBER set v3
(integer) 1
127.0.0.1:6379> SISMEMBER set v5
(integer) 0

srem移除某个元素

127.0.0.1:6379> SREM set v3 v2
(integer) 2
127.0.0.1:6379> SMEMBERS set
1) "v1"
2) "v4"

SRANDMEMBER 随机抽取一个元素

127.0.0.1:6379> clear
127.0.0.1:6379> SADD set v1 v2 v3 v4 v5
(integer) 5
127.0.0.1:6379> SRANDMEMBER set
"v2"
127.0.0.1:6379> SRANDMEMBER set
"v3"
127.0.0.1:6379> SRANDMEMBER set
"v3"
127.0.0.1:6379> SRANDMEMBER set 2
1) "v5"
2) "v1"
127.0.0.1:6379> SRANDMEMBER set 2
1) "v2"
2) "v3"

spop 随机移除一个元素

127.0.0.1:6379> SMEMBERS set
1) "v2"
2) "v1"
3) "v3"
4) "v5"
5) "v4"
127.0.0.1:6379> SPOP set
"v1"
127.0.0.1:6379> SMEMBERS set
1) "v2"
2) "v3"
3) "v5"
4) "v4"
127.0.0.1:6379> SPOP set 2
1) "v2"
2) "v4"
127.0.0.1:6379> SMEMBERS set
1) "v3"
2) "v5"

smove将一个集合中的元素移动到另一个集合中

127.0.0.1:6379> SMEMBERS set1
1) "v3"
2) "v1"
3) "v2"
127.0.0.1:6379> SMEMBERS set2
1) "v5"
2) "v6"
3) "v4"
127.0.0.1:6379> SMOVE set1 set2 v1
(integer) 1
127.0.0.1:6379> SMOVE set2 set1 v6
(integer) 1
127.0.0.1:6379> SMEMBERS set1
1) "v3"
2) "v6"
3) "v2"
127.0.0.1:6379> SMEMBERS set2
1) "v1"
2) "v5"
3) "v4"

sinter补集

127.0.0.1:6379> SMEMBERS set1
1) "v3"
2) "v1"
3) "v2"
127.0.0.1:6379> SMEMBERS set2
1) "v3"
2) "v4"
3) "v2"
127.0.0.1:6379> SDIFF set1 set2
1) "v1"
127.0.0.1:6379> SDIFF set2 set1
1) "v4"

sunion并集

127.0.0.1:6379> SUNION set1 set2
1) "v1"
2) "v3"
3) "v4"
4) "v2"

sinter交集

127.0.0.1:6379> SINTER set1 set2
1) "v3"
2) "v2"

4.hash操作

hash的形式:key filed vlaue,相当于java中的map集合

hset向hash中添加值

127.0.0.1:6379> hset user name baxxi age 20 addr guangzhou
(integer) 3

hgetall得到hash中的指定filed的值

127.0.0.1:6379> hget user name 
"baxxi"
127.0.0.1:6379> hget user age
"20"
127.0.0.1:6379> hget user addr
"guangzhou"

hgetall得到hash中的所有值

127.0.0.1:6379> HGETALL user
1) "name"
2) "baxxi"
3) "age"
4) "20"
5) "addr"
6) "guangzhou"

hmset hmget批量添加和获取

127.0.0.1:6379> HMSET user name baxxi age 20
OK
127.0.0.1:6379> HMGET user name age
1) "baxxi"
2) "20"

hdel删除指定的字段

127.0.0.1:6379> HDEL user age
(integer) 1
127.0.0.1:6379> HMGET user name age
1) "baxxi"
2) (nil)
127.0.0.1:6379> HDEL user name
(integer) 1
127.0.0.1:6379> HMGET user name age
1) (nil)
2) (nil)

hlen获取hash的字段长度

127.0.0.1:6379> HSET user name baxxi age 20 addr guangzhou
(integer) 3
127.0.0.1:6379> HLEN user
(integer) 3

hexist判断hash中的字段是否存在

127.0.0.1:6379> HEXISTS user name
(integer) 1
127.0.0.1:6379> HEXISTS user aaa
(integer) 0

hkeys获取所有的key(字段),hvals,获取所有的value

127.0.0.1:6379> HKEYS user
1) "name"
2) "age"
3) "addr"
127.0.0.1:6379> HVALS user
1) "baxxi"
2) "20"
3) "guangzhou"

hincrby指定增量

127.0.0.1:6379> HINCRBY user age 1
(integer) 21
127.0.0.1:6379> HINCRBY user age 1
(integer) 22
127.0.0.1:6379> HINCRBY user age 2
(integer) 24

5.zset操作

zset是有序不可重复的集合

zadd添加数据

127.0.0.1:6379> zadd num 20 a
(integer) 1
127.0.0.1:6379> zadd num 30 b
(integer) 1
127.0.0.1:6379> zadd num 90 c
(integer) 1

zrange获取所有值,按照索引获取

127.0.0.1:6379> zrange num 0 -1 withscores
1) "a"
2) "20"
3) "b"
4) "30"
5) "c"
6) "90"
127.0.0.1:6379> zrange num 0 -1 
1) "a"
2) "b"
3) "c"

zrangebyscore获取所有值,按照score获取

127.0.0.1:6379> ZRANGEBYSCORE num -inf +inf withscores
1) "a"
2) "20"
3) "b"
4) "30"
5) "c"
6) "90"

ZREVRANGE倒序获取

127.0.0.1:6379> ZREVRANGE num 0 -1
1) "c"
2) "b"
3) "a"
127.0.0.1:6379> ZREVRANGE num 0 -1 withscores
1) "c"
2) "90"
3) "b"
4) "30"
5) "a"
6) "20"

zrem移除指定元素

127.0.0.1:6379> ZREM num a
(integer) 1
127.0.0.1:6379> ZREVRANGE num 0 -1 withscores
1) "c"
2) "90"
3) "b"
4) "30"

zcard得到元素的个数
zcount获取指定区间的成员数量

127.0.0.1:6379> ZCARD num
(integer) 2
127.0.0.1:6379> ZCOUNT num 20 40
(integer) 1

五、三种特殊数据类型

Geospatial (地理空间)

应用场景

  • 朋友的定位、附近的人、打车的距离计算.
  • Redis的Geospatial在Redis3.2版本就推出了!这个功能可以推算地理位置的信息,两地之间的距离.

GEOADD 将指定的地理空间位置(纬度、经度、名称)添加到指定的key中。

# getadd key 经度 纬度 名称
# 规则:两极无法添加,有效的经度从-180度到180度。 有效的纬度从-85.05112878度到85.05112878度。
# 一般会下载城市数据,直接通过Java程序一次性导入!

> geoadd china:city 116.23128 40.22077 beijing #导入城市数据
1
> geoadd china:city 121.48941 31.40527 shanghai
1
> geoadd china:city 106.54041 29.40268 chongqing
1
> geoadd china:city 113.88308 22.55329 shenzheng
1
> geoadd china:city 120.21201 30.2084 hangzhou
1
> geoadd china:city 108.93425 34.23053 xian
1

GEOPOS 获取指定地址的位置,一定是一个坐标值


> geopos china:city beijing #获取指定地址的经度和纬度
116.23128265142441
40.220769054385265
> geopos china:city beijing chongqing #一次性获取多个城市的经纬度
116.23128265142441
40.220769054385265
106.54040783643723
29.402680535172998

GEODIST 返回两个给定位置之间的距离

# 如果用户没有显式地指定单位参数, 那么 GEODIST 默认使用米作为单位。

> geodist china:city beijin shanghai #北京和上海之间的直线距离(默认单位为米)
1088644.3544
> geodist china:city beijin shanghai km #指定单位为km
1088.6444

GEORADIUS 以给定的经纬度为中心, 找出某一半径内的元素


> georadius china:city 110 30 1000 km #获取经度110 纬度30 方圆半径100km的所有城市(人)
chongqing
xian
shenzheng
hangzhou
> georadius china:city 110 30 500 km #获取经度110 纬度30 方圆半径500km的所有城市(人)
chongqing
xian
> georadius china:city 110 30 500 km withdist #指定坐标方圆半径500km的所有城市(人) 并带上距离
chongqing
340.7667
enshi
57.7962
xian
481.1278
> georadius china:city 110 30 500 km withdist  count 1  #指定坐标半径500km内所有城市(人)限制返回个数
enshi
57.7962
> georadius china:city 110 30 500 km withdist  count 2 #限制2个
enshi
57.7962
chongqing
340.7667

GEORADIUSBYMEMBER 找出位于指定范围内的元素,中心点是由给定的位置元素决定


> GEORADIUSBYMEMBER china:city beijin 1000 km #找出北京为中心点半径1000km之内的城市
beijin
xian
> GEORADIUSBYMEMBER china:city beijin 1000 km withdist #找出北京为中心点半径1000km之内的城市显示距离
beijin
0.0000
xian
927.5371

GEOHASH 返回一个或多个位置元素的 Geohash 表示

Hyperloglog (基数统计)

基数:不重复的元素!可以接受误差

  • 优点:占用的内存是固定的,2^64不同的元素的基数,只需要12kb内存!如果要从内存角度来比较的话那么Hyperloglog首选!
  • 缺点:有0.81%的错误率,一般可以忽略不记

业务场景

  • 网站的UV(一个人访问一个网站多次,但是还是算作一个人!)
  • 传统方式:set保存用户的id,然后就可以统计set中的元素数量作为标准判断!
  • 这个方式如果保存大量的用户id,就会比较麻烦!我们的目的是为了计数,而不是保存用户id

pfadd添加数据


> pfadd key1 a b c a d e f #添加7个带有重复值的数据
1

pfcount 输出不重复的元素个数


> pfcount key1 #输出不重复的元素个数,上面数据a重复所以结果为6
6

pfmergekey1key2 的内容合并到key3

> pfadd key2 s k w a r l e #添加第2个数据
1
> pfcount key2 
7
> pfmerge key3 key1 key2 #将mykey1 和 mykey2 的内容合并到mykey3
OK
> pfcount key3 # a b c d e f k l r s w
11
Bitmap (位图场景详解)

使用场景

  • 统计疫情感染人数:先全部14亿人全部置为0(内存小),如果不幸感染就变成1
  • 活跃用户标记
  • 登录和未登录
  • 用户365天打卡
  • 两个状态的都可以使用Bitmaps,都是操作二进制位来进行记录,只有0和1两个状态

setbit getbit bitcount

> setbit sign 0 1 # 周一 打卡了
0
> setbit sign 1 0 # 周二 没打卡
0
> setbit sign 2 0 # 周三 没打卡
0
> setbit sign 3 0 # 周四 没打卡
0
> setbit sign 4 0 # 周五 没打卡
0
> setbit sign 5 0 # 周六 没打卡
0
> setbit sign 6 0 # 周日 没打卡
0
############# 任务:查看某天是否打卡 #############
> getbit sign 3 # 周四 没打卡
0
> getbit sign 0 # 周一 打卡了
1
############# 任务:查看打卡的天数 #############
> bitcount sign # 打卡了一天,查看数字就可以知道是否全勤
1

六、事务

MySQL:ACID原则

ACID是关系型数据库系统采纳的原则,其代表的含义分别是:

  • 原子性(Atomicity):是指一个事务要么全部执行,要么完全不执行。
  • 一致性(Consistency): 事务在开始和结束时,应该始终满足一致性约束。比如系统要求A+B=100,那么事务如果改变了A的数值,则B的数值也要相应修改来满足这样一致性要求;与CAP中的C代表的含义是不同的。
  • 事务独立(Isolation):如果有多个事务同时执行,彼此之间不需要知晓对方的存在,而且执行时互不影响,事务之间需要序列化执行,有时间顺序。
  • 持久性(Durability):事务的持久性是指事务运行成功以后,对系统状态的更新是永久的,不会无缘无故回滚撤销。

  • Redis 单条命令是保证原子性的 但是Redis事务不保证原子性
  • Redis 事务没有隔离级别的概念
  • 所有的命令在事务中,并没有直接被执行!只有发起执行命令的时候才会执行
  • redis的事务:
    • 开启事务( multi )
    • 命令入队( )
    • 执行事务( exec )

正常执行事务


> multi # 开启事务
OK
###### 命令入队 #####
> set k1 v2
QUEUED
> set k2 v2
QUEUED
> get k2
QUEUED
####### 执行事务 #######
> exec # 执行事务,按照入队顺序一步一步执行,执行一次之后事务就结束了,如果需要重新执行事务需要重新开启,命令入队操作
OK
OK
v2

取消事务

127.0.0.1:6379> multi #开启事务
OK
###### 命令入队 #####
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
####### 取消事务 #######
127.0.0.1:6379> discard #取消事务后之前队内所有的命令都不会被执行了
OK
127.0.0.1:6379> get k2
(nil)

编译型异常(命令有错!),事务中所有的命令都不会被执行!

127.0.0.1:6379> multi #开启事务
OK
###### 命令入队 #####
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> getset k3
(error) ERR wrong number of arguments for 'getset' command #这里因为命令有错,但是不会中断事务,所以下面可以继续写
127.0.0.1:6379> set k4 v4
QUEUED
127.0.0.1:6379> set k5 v5
QUEUED
127.0.0.1:6379> exec #执行事务
(error) EXECABORT Transaction discarded because of previous errors. #执行事务报错,所有的命令都不会被执行
127.0.0.1:6379> get k5 #获取值为nil
(nil)

运行时异常,如果事务队列中存在语法性错误,命令执行的时候,其他命令是可以正常执行的,错误命令抛出异常

127.0.0.1:6379> multi # 开启事务
OK
127.0.0.1:6379> set k1 "v1" #设置字符串值
QUEUED
127.0.0.1:6379> incr k1 #字符串值自增,会报错
QUEUED
127.0.0.1:6379> set k2 v2 #接着执行
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> exec
1) OK
2) (error) ERR value is not an integer or out of range #字符串不是整形不能自增,有异常!但是不影响其他事务!
3) OK
4) OK
127.0.0.1:6379> get k2 #依旧可以获取到值
"v2"

监视 Watch (相当于乐观锁)

乐观锁:总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,只在更新的时候会判断一下在此期间别人有没有去更新这个数据。
在这里插入图片描述

悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞,直到它拿到锁。
在这里插入图片描述

乐观锁测试

127.0.0.1:6379> set money 100 #设置零花钱为100
OK
127.0.0.1:6379> set out 0 #设置已经花出去的钱为0
OK
127.0.0.1:6379> watch money #监视money,事务执行成功之后,监控会自动取消
OK
127.0.0.1:6379> multi #开启事务,事务正常结束,数据期间没有发生变动,这个时候就正常执行成功
OK
##### 命令入队 #####
127.0.0.1:6379> decrby money 20
QUEUED
127.0.0.1:6379> incrby out 20
QUEUED
127.0.0.1:6379> exec #执行事务
1) (integer) 80
2) (integer) 20

测试多线程修改值,使用watch可以当作redis的乐观锁操作:

线程1去执行事务,在执行命令之前线程2修改了原来的money,导致线程1事务执行失败

#线程1
127.0.0.1:6379> watch money #监控money
OK
127.0.0.1:6379> multi #开启事务
OK
127.0.0.1:6379> decrby money 10 #money减10
QUEUED
127.0.0.1:6379> incrby out 10 #out加10
QUEUED
127.0.0.1:6379> exec #执行事务失败(在此步之前有线程2修改了money的值)
(nil)
#线程2
127.0.0.1:6379> get money #线程1事务执行之前,获取money的值为80
"80"
127.0.0.1:6379> set money 1000 #线程1事务执行之前,修改money的值为1000
OK

线程1拿到的money是80,执行事务操作之后得到的结果应该是70,但是与线程2修改的1000比较,两个值不一致,导致事务执行失败

如果事务执行失败则重新监视获取最新值即可,unwatch先解锁,然后再次监视,再去创建、执行事务

127.0.0.1:6379> unwatch #解锁
127.0.0.1:6379> watch money #再次开启监视
OK
127.0.0.1:6379> multi #再次开启事务
OK
127.0.0.1:6379> decrby money 10
QUEUED
127.0.0.1:6379> incrby out 10
QUEUED
127.0.0.1:6379> exec
1) (integer) 990
2) (integer) 30

七、Jedis使用步骤

Jedis 是 Redis 官方首选的 Java 客户端开发包

1、idea创建maven项目

在这里插入图片描述
2、导入jedis、fastjson依赖包

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>3.3.0</version>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.71</version>
</dependency>

3、启动Redis服务

在这里插入图片描述

4、测试连接

public class TestPing {
    public static void main(String[] args) {
        //1 new Jedis 对象
        Jedis jedis=new Jedis("127.0.0.1",6379);
        //jedis所有的命令就是之前学习的所有指令
        System.out.println(jedis.ping());
    }
}

在这里插入图片描述

2021/4/12 java学习日记

一、SpringBoot集成Redis

1、创建SpringBoot项目
在这里插入图片描述
2、选好相关依赖
在这里插入图片描述
3、编写配置文件application.properties

# 应用名称
spring.application.name=redis-02-springboot411
# 应用服务 WEB 访问端口
server.port=8080
# SpringBoot 所有配置类 都是一个自动配置类 RedisAutoConfiguration
# 自动配置类都会绑定一个properties配置文件 RedisProperties
spring.redis.host=127.0.0.1
spring.redis.port=6379

jedis:采用的直连,多个线程操作的话,是不安全的,如果要避免不安全的,使用jedis pool连接池,更像BIO模式

lettuce:采用netty,实例可以在多个线程中进行共享,不存在线程不安全的情况!可以减少线程数据了,更像NIO模式

4、观察源码
在导入的依赖中的spring.factories文件可以看到有三个Redis的配置类
在这里插入图片描述
其中RedisAutoConfiguration中有

 @Bean
    @ConditionalOnMissingBean(
        name = {"redisTemplate"}
    )//我们可以自己定义一个redistemplate来替换这个默认的!
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
        //默认的redistemplate没有过多的设置,redis对象都需要序列化
        //两个泛型都是object,object的类型,我们使用之后需要强制转换<string,object>
        RedisTemplate<Object, Object> template = new RedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }
    @Bean
    @ConditionalOnMissingBean//由于string是redis中最常用的类型,所以就单独提出来一个bean
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
        StringRedisTemplate template = new StringRedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }

我们也可以在自己的config文件夹中创建一个RedisConfig类来自定义一个RedisTemplate:

@Configuration
public class RedisConfig {
    @Bean(name = "redisTemplate")
    @SuppressWarnings("all")
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        //为了方便 直接使用 string object
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);
        //使用json序列化数据
        Jackson2JsonRedisSerializer<Object> json = new Jackson2JsonRedisSerializer<Object>(Object.class);
        ObjectMapper mapper = new ObjectMapper();
        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        json.setObjectMapper(mapper);
        //String的序列化
        StringRedisSerializer str = new StringRedisSerializer();
        //key采用string的序列化形式
        template.setKeySerializer(str);
        //hash的key采用string的序列化形式
        template.setHashKeySerializer(str);
        //value采用json的序列化形式
        template.setValueSerializer(json);
        //hash的value采用json的序列化形式
        template.setHashValueSerializer(json);
        template.afterPropertiesSet();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }
}

5、进行测试

首先创建一个pojo:

@Component
@Data
@NoArgsConstructor
@AllArgsConstructor
//在企业中 所有的pojo都要序列化 implements Serializable
public class User implements Serializable {
    private String name;
    private Integer age;
}

测试一下

    @Autowired
    @Qualifier("redisTemplate")
    private RedisTemplate redisTemplate;
    @Test
    void  test() throws JsonProcessingException {
        //redisTemplate 操作不同的数据类型 api和指令一样
        //opsForValue 操作字符串 类似String
        //opsForList 操作list 类似list
        //...

        //获取redis的连接对象
//        RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
//        connection.flushDb();
//        connection.flushAll();
        //真实开发一般都使用json来传递对象
        User user = new User("baxxi", 20);
//        String jsonUser = new ObjectMapper().writeValueAsString(user);
        redisTemplate.opsForValue().set("user",user);
        System.out.println(redisTemplate.opsForValue().get("user"));
    }

输出结果:
在这里插入图片描述
6、代码优化
由于RedisTemplate类中的方法比较繁琐,为了便利,对RedisTemplate进行了封装,为Redis创建一个工具类RedisUtil。
因为来代码比较长,所以这里就给到链接 RedisUtils工具类

再来测试一下:

    @Autowired
    private RedisUtil redisUtil;
    @Test
    void  test1() throws JsonProcessingException {
        redisUtil.set("name","baxxi");
        System.out.println(redisUtil.get("name"));
    }

输出结果:
在这里插入图片描述

二、Redis.conf详解

网络

bind 172.0.0.1 #绑定的ip
protected-mode yes #保护模式一般是开启的
port 6379    #redis的默认端口

快照

持久化,在规定的时间内,执行了多少次,则持久化到文件.rdp .aof
持久化规则

#如果在900s内,如果至少1个key进行了数据修改,进行数据持久化操作
save 900 1
#如果在300s内,如果至少10个key进行了数据修改,进行数据持久化操作
save 300 10
#如果在60s内,如果至少10000个key进行了数据修改,进行数据持久化操作
save 60 10000
stop-writes-on-bgsave-error yes #持久化如果出错,是否还继续工作
rdbcompression yes #是否压缩rdb文件,需要消耗一点cpu资源
rdbchecksum yes #保存rdb文件的时候,进行错误检查校验!
dir ./ #rdb文件保存的目录!

SECURITY 安全

requirepass    #设置redis认证auth
#也可以通过命令去设置密码
config set requirepass #设置密码
config get requirepass #获得密码

CLIENTS 客户端限制

maxclients 10000#最大客户端连接数
maxmemory <bytes> #设置redis配置内存的最大容量
maxmemory-policy noeviction #内存到达上限之后的处理策略
maxmemory-policy 六种方式
1、volatile-lru:只对设置了过期时间的key进行LRU(默认值) 
2、allkeys-lru : 删除lru算法的key   
3、volatile-random:随机删除即将过期key   
4、allkeys-random:随机删除   
5、volatile-ttl : 删除即将过期的   
6、noeviction : 永不过期,返回错误

APPEND ONLY MODE AOF配置

appendonly no #默认是不开启aof的,默认是使用rdb方式持久化的。
appendfilename "appendonly .aof" #持久化文件的名字
#appendfsync always    #每次修改都会sync.消耗慢
appendfsync everysec #每一秒执行一次sync,可能会丢失这1秒的数据
#appendfsync no #不执行sync,这个时候操作系统自己同步数据

三、Redis持久化

RDB(Redis DataBase)

Redis是内存数据库,如果不将内存中的数据库状态保存到磁盘,那么一旦服务进程退出,服务器中的数据库状态也会消失。所以Redis提供了持久化功能!
RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘。也是默认的持久化方式,这种方式是就是将内存中数据以快照的方式写入到二进制文件中,默认的文件名为dump.rdb

RDB触发规则:

  • save规则满足的情况下,会自动触发rdb规则
  • 使用flushall命令也会触发rdb规则
  • 退出redis,也会产生rdb文件

备份自动生成一个dump.rdb文件

优点:

  • 适合大规模的数据恢复
  • 对数据完整性要求不高

缺点:

  • 需要一定的时间间隔进程操作!如果redis意外宕机了,这个最后一次修改的数据就没有了
  • fork进程的时候,会占用一定的内存空间

AOF(Append Only File)

全量备份总是耗时的,有时候我们提供一种更加高效的方式AOF,工作机制很简单,redis会将每一个收到的写命令都通过write函数追加到文件中。通俗的理解就是日志记录。
AOF保存的是appendonly.aof文件,每当有一个写命令过来时,就直接保存在我们的AOF文件中。

Redis.conf文件中

appendonly no #默认是不开启aof的,默认是使用rdb方式持久化的。改为yes开启aof持久化

修改配置文件后需要重启服务!

如果aof文件有错误,那么Redis是启动不起来的,我们需要修复这个文件,redis给我们提供了一个工具redis-check-aof --fixe appendonly.aof来修复aof文件,,aof默认就是文件无线追加,文件会越来越大

优点:

  • 每次修改都会同步,文件的完整性会更好。
  • 每秒同步一次,可能会丢失一秒的数据。
  • 可以不同步,效率最高

缺点:

  • 相对于数据文件来说,aof文件远大于rdb文件,修复的速度会比rdb文件慢
  • aof运行效率也要比rdb慢,所以Redis默认的配置就是rdb持久化。

四、Redis发布订阅

Redis 发布订阅 (pub/sub) 是一种消息通信模式:发送者 (pub) 发送消息,订阅者 (sub) 接收消息。

Redis 客户端可以订阅任意数量的频道。

命令及描述:

订阅者:

127.0.0.1:6379> SUBSCRIBE baxxi
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "baxxi"
3) (integer) 1
1) "message"
2) "baxxi"
3) "hello"
1) "message"
2) "baxxi"
3) "hello,redis"

发生者:

127.0.0.1:6379> PUBLISH baxxi hello
(integer) 1
127.0.0.1:6379> PUBLISH baxxi hello,redis
(integer) 1

五、Redis主从复制

在这里插入图片描述
从主复制,是指从一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(master/leader),后者称为从节点(slave/follower);数据的复制是单向的,只能有主节点到从节点。Master以写为主,Slave以读为主。

默认情况下,每台Redis服务器都是主节点;且一个主节点可以有多个从节点或(没有从节点),但是从节点只能有一个主节点。

主从复制的作用主要包括:

  • 数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。
  • 故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复;实际上是一种服务的冗余。
  • 负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务(即写Redis数据时应用连接主节点,读- Redis数据时应用连接从节点),分担服务器负载;尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高Redis服务器的并发量。
  • 高可用基石:除了上述作用以外,主从复制还是哨兵和集群能够实施的基础,因此说主从复制是Redis高可用的基础。

info replication:查看复制信息

127.0.0.1:6379> INFO replication
# Replication
role:master         //角色是主机
connected_slaves:0  //从机数量为0
master_failover_state:no-failover
master_replid:8953f6c3aac3b0269abf23e7aa2248a7bbc70f12
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

单机集群环境搭建
复制多个配置文件,修改对应的端口号然后启动服务。

redis.conf修改点:

  • port
  • pid进程号
  • log名字
  • dump.rdb

分别通过配置文件,启动对应的服务

默认情况下每一台主机都是主节点,只需要在从机上配置,认老大

从机1:

127.0.0.1:6380> SLAVEOF 127.0.0.1 6379
OK
127.0.0.1:6380> info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6379
master_link_status:up
master_last_io_seconds_ago:4
master_sync_in_progress:0
slave_repl_offset:0
slave_priority:100
slave_read_only:1
connected_slaves:0
master_failover_state:no-failover
master_replid:2dd348bda51387cc1555a1baca82a5731694f3da
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:0

从机2:

127.0.0.1:6381> SLAVEOF host 6379
OK
127.0.0.1:6381> info replication
# Replication
role:slave
master_host:host 
master_port:6379
master_link_status:down
master_last_io_seconds_ago:-1
master_sync_in_progress:0
slave_repl_offset:0
master_link_down_since_seconds:1618192855
slave_priority:100
slave_read_only:1
connected_slaves:0
master_failover_state:no-failover
master_replid:ad3d8aa6966a4b6205b868eca475027b18de8622
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

主机master:

127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:2
slave0:ip=127.0.0.1,port=6380,state=online,offset=196,lag=0
slave1:ip=127.0.0.1,port=6381,state=online,offset=196,lag=0
master_failover_state:no-failover
master_replid:2dd348bda51387cc1555a1baca82a5731694f3da
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:196
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:196

复制原理:
Slave启动成功连接到master后会发送一个sync命令

Master接到命令,启动后台的存盘进程,同时收集所有接收到的用于修改数据集命令,在后台进程执行完毕后,master将传送整个文件到slave,并完成一次同步。

  • 全量复制:而slave服务在接受到数据库文件数据后,将其存盘并加载到内存中。
  • 增量复制:Master继续将新的所有收集到的修改命令依次传给slave,完成同步,但是只要重新连接master,一次完全同步(全量复制)将被自动执行

六、哨兵模式(自动选取主节点)

vim sentinel.conf编写配置文件

sentinel monitor [监视名称] [host] [port] 1
#后面这个1代表投票选举主机

redis-sentinel redis-config/sentinel.conf启动哨兵模式

哨兵模式的主要配置

# Example sentinel.conf
# 哨兵sentinel实例运行的端口 默认26379
port 26379
# 哨兵sentinel的工作目录
dir /tmp
# 哨兵sentinel监控的redis主节点的ip port
# master-name 可以自己命名的主节点 只能由字母A-z,数字0-9,这三个字符".-_"组成。
# quorum 配置多少个sentinel哨兵统一认为master节点失联 那么这是客观上认为主节点失联了
# sentinel monitor <master-name> <ip> <redis-port> <quorum>
sentinel monitor mymaster 127.0.0.1 6379 2
#当在redis实例中开启了reuirepass foobared 授权密码 这样所有连接redis实例的客户端都要提供密码
#设置哨兵sentinel连接主从的密码 注意必须为主从设置一样的验证密码
# sentinel auth-pass <master-name> <password>
sentinel auth-pass mymaster 123456
#指定多少毫秒之后 主节点没有答应哨兵sentinel此时 哨兵主观上认为主节点下线 默认30秒
# sentinel down-after-milliseconds <master-name> <milliseconds>
sentinel down-after-milliseconds mymaster 30000
#这个配置指定了在发生failover主备切换时最多有多少个slave同时对新的master进行 同步。
这个数字越小,完成failover所需要的时间越长
但是如果这个数字越大,就意味越多的slave因为replication而不可用
可以通过这个值设为1 来保证每次只有一个slave处于不能处理命令请求的状态
#sentinel parallel-syncs <master-name> <numslave>
sentinel parallel-syncs mymaster 1
#故障转移的超时时间failover-timeout 可以用在以下这些方面:
#1.同一个sentinel对同一个master两次failover之间的间隔时间。
#2.当一个slave从一个错误master那里同步开始时间,直到slave被纠正为向正确的master那里同步数据时。
#3.当想要取消一个正在进行failover所需要的时间
#4.当进行failover时,配置所有slave指向新的master所需的最大时间,不过,即使过了这个超时,slaves依然会被正确配置为指向master,但是就不按parallel-syncs所配置的规则来了
# 默认三分钟
# sentinel failover-timeout <master-name> <milliseconds>
setinel failover-timeout mymaster 180000
#scripts execution
#配置当某一事件发生时所需要执行的脚本,可以通过脚本来通知管理员,例如当系统运行不正常时发邮件通知相关人员
#对于脚本的运行结果有以下规则:
#若脚本执行后返回1,那么该脚本稍后将会被再次执行,重复次数目前默认10
#若脚本执行后返回2,或者比2更高的一个返回值,脚本将不会重复执行
#若脚本执行过程中由于受到系统中断信号被终止了,则同返回值为1时的行为相同
#一个脚本的最大执行时间为60s,如果超过这个时间,脚本将会被sigkill信号终止,之后重新执行。
#通知型脚本:当sentinel有任何警告级别的事件发生时(比如说redis实例的主观失效和客观失效等等),将会上调用这个脚本,这时这个脚本应该通过邮件,sms等方式去通知系统管理员关于系统不正常运行的信息,调用该脚本时,将传给脚本两个参数,一个是事件的类型,一件是事件的描述,如果sentinel.conf配置文件中配置了这个脚本路径,那么必须保证这个脚本存在于这个路径,并且是可执行的,否则sentinel无法正常启动成功。
#通知脚本
#sentinel notification-script <master-name> <script-path>
sentinel notification-script mymaster /var/redis/notify.sh
#客户端重新配置主节点参数脚本
#当一个master由于failover而发生改变是,这个脚本将会被调用,通知相关客户端关于master地址已经发生改变的信息。
#以下参数将会在调用脚本时传给脚本:
#<master-name> <role> <state> <from-ip> <from-port> <to-ip> <to-port>
#目前<state>总是"failover",
#<role>是"leader"或者"observer"中的其中一个。
#参数 from-ip,from-port,to-ip,to-port是用来和旧的master和新的master(即旧的slave)通信的
#这个脚本应该是通用的,能多次调用,不是针对性的。
#sentinel client-reconfig-script <master-name> <script-path>
sentinel client-reconfig-script mymaster /var/redis/reconfig.sh

七、Redis缓存穿透和雪崩

1.缓存穿透:key对应的数据在数据源并不存在,每次针对此key的请求从缓存获取不到,请求都会到数据源,从而可能压垮数据源。比如用一个不存在的用户id获取用户信息,不论缓存还是数据库都没有,若黑客利用此漏洞进行攻击可能压垮数据库。

在这里插入图片描述

解决方法

  • 设置布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截掉,从避免了对底层存储系统的查询压力。
  • 如果一个查询返回的数据为空,不管是数据不存在还是系统故障,我们仍然把这个结果进行缓存,但是它的过期时间会很短最长不超过5分钟。

2.缓存击穿:key对应的数据存在,但在redis中过期,此时若有大量并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。

3.缓存雪崩:当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候,也会给后端系统(比如DB)带来很大压力。

在这里插入图片描述

解决方法

  • 设置redis集群和DB集群的高可用,如果redis出现宕机情况,可以立即由别的机器顶替上来。这样可以防止一部分的风险。
  • 使用互斥锁:在缓存失效后,通过加锁或者队列来控制读和写数据库的线程数量。比如:对某个key只允许一个线程查询数据和写缓存,其他线程等待。单机的话,可以使用synchronized或者lock来解决,如果是分布式环境,可以是用redis的setnx命令来解决。
  • 不同的key,可以设置不同的过期时间,让缓存失效的时间点不一致,尽量达到平均分布。
  • 永远不过期
    redis中设置永久不过期,这样就保证了,不会出现热点问题,也就是物理上不过期。
  • 资源保护:使用netflix的hystrix,可以做各种资源的线程池隔离,从而保护主线程池。

总结

经过这几天对Redis的学习,对Redis有了一个新的了解,现在还处于入门阶段,对于Redis的底层还是需要多多去探究。
学习路径:狂神Redis

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值