1.官网:https://redis.io/.
1.中文网:http://www.redis.cn/.
1.下载安装包
Redis的github地址: https://github.com/dmajkic/redis/releases.
下载完毕:
2.进行解压:
3.双击运行服务端启动
如果闪退: 解决办法.
然后会出现以下界面
4.双击运行客户端启动
1.下载安装包(在官网下载),并上传到linux系统。
2.解压安装包(解压完成如下)
3.进入解压后的文件,可以看到redis的配置文件
4.基本环境安装
# gcc环境安转
yum install gcc-c++
# make命令
make
# 安装命令
make install
5. redis默认的安装路径 /usr/local/bin
6.将redis配置文件,复制到当前目录下的huiconfig(自己建的文件夹)
7.默认不是后台启动的,修改配置文件
vim redis.conf
8.启动redis服务
在/user/local/bin目录下启动服务端和客户端
# 开启服务 对应的配置文件
redis-server huiconfig/redis.conf
#开启客户端 -p 端口号
redis-cli -p 6379
9.使用cli进行连接测试(基本操作):
10.查看redis的进程是否开启
ps -ef | grep redis
shutdown
exit
redis-benchmark + 命令参数
# 测试:100个并发连接 100000请求
redis-benchmark -h localhost -p 6379 -c 100 -n 100000
Redis默认有16个数据库,默认使用第0个。
一些命令总结:
redis默认没有密码
config get requirepass # 查看密码
config set requirepass 123456 # 设置密码
config set requirepass "" # 取消密码
auth 123456 # 如果有密码,先执行该命令,然后进行操作
select 3 # 切换到3号数据库
dbsize # 查看数据库大小
keys * # 查看当前数据库所有的key
flushdb # 清空当前数据库
flushall # 清空所有数据库
exists name # 判断name字段是否存在
move name 1 # 移除name字段,一般不用
get name # 得到name字段
expire name 10 # 给name字段设置过期时间10秒
ttl name # 查看name字段过期的剩余时间
type name # 查看name字段类型
# 关于字符串类型
append name hello # 给字符串类型字段name追加“hello”值,如果name字段本身不存在,相当于set命令,追加的值带不带双引号都可以
strlen name # 查看name字段字符串的长度
incr age # age值自加1
incrby age 4 # age值自加4
decr age # age值自减1
decrby age 4 # age值自减4
getrange name 0 3 # 取出指定范围字符串,前后闭区间[]
setrange name 1 xx # 从第一个字符开始,替换成xx
setex # 设置过期时间
setnx # 如果不存在才会设置,不然不生效
mset k1 v2 k2 v3 k3 v3 # 批量设置多个值
mget k1 k2 k3 # 批量获取多个值
msetnx # 如果不存在才会批量设置,不然不生效,该操作是原子操作,一个失败便都失败
set user:1 {name:zhangsan,age:21} # 使用json字符串的形式存储对象
getset name "zhangsan" # 先get再set,如果不存在值,则返回nil,然后赋值为后面的值;如果存在值,则返回值,然后赋值为后面的值
# 关于List类型
lpush mylist hello # 左边加入hello节点
rpush mylist hello # 右边加入hello节点
lrange mylist 0 1 # 通过区间获得具体的值
lpop mylist # 移除列表第一个元素(左)
rpop mylist # 移除列表最后一个元素(右)
lindex mylist 0 # 获取指定下标的值
llen mylist # 获取list的长度
lrem mylist 1 hello # 移除指定个数的节点
ltrim mylist 1 2 # 通过下标截取指定的长度
rpoplpush mylist myotherlist # 把mylist最后一个元素弹出,放入另一个list中
lset mylist 0 hello # 将列表中指定下标的值替换为另外一个值,更新操作(如果列表不存在,或者下标不存在都会报错)
linsert mylist before/after 已有节点 新节点 # 可以用befor或者after
# 关于Set类型
sadd myset value # 加入新值
smembers myset # 查看set中所有值
sismember myset value # 查看set中是否存在指定值
scard myset # 获取set中元素的个数
srem myset hello # 移除set集合中指定的元素
srandmember myset # 获取myset中随机值
spop myset # 随机弹出myset中的元素
smove myset myset2 “huige” # 将myset中的huige移动到myset2中
sdiff myset1 myset2 # 差集
sinter myset1 myset2 # 交集
sunion myset1 myset2 # 并集
# 关于Hash类型
hset myhash field1 zhangsan # set一个具体的key-value
hget myhash field1 # 获得一个字段值
hmset myhash field1 hello field2 world # 批量赋值
hmget myhash field1 field2 # 获得多个值
hgetall myhash # 得到所有的值
hdel myhash field1 # 删除hash指定的key字段,对应的value也消失了
hlen myhash # 获取myhash的长度
hexists myhash field1 # 判断myhash指定字段是否存在
hkeys myhash # 获取所有的字段
hvals myhash # 获取所有的value
hincr、hdecr、hincrby、hdecrby # 与string中自增自减用法一样
hsetnx # 与string中setnx用法差不多
# 关于Zset类型
zadd myset 1 one # 添加一个值,这个1是为了以后排序增加的标志
zadd myset 3 three 4 four # 添加多个值
zrange myset 0 -1 # 获取一定区间的元素
zrangebyscore myset -inf +inf # zrangebyscore key min max [withscores] 从小到大排序[并附带值]
zrevrange myset 0 -1 # 从大到小排序
zrem myset xiaohong # 移除指定用户
zcard myset # 获取元素个数
zcount myset 1 3 # 获取指定区间的成员数量
明白Redis是很快的,官方表示,Redis是基于内存操作,CPU不是Redis性能瓶颈,Redis的瓶颈是根据机器的内存和网络带宽,既然可以使用单线程来实现,就使用单线程了!
Redis是C语言写的,官方提供的数据为100000+的QPS,这个完全不比Memecache差。
Redis为什么单线程还这么快?
1.误区1:高性能的服务器一定是多线程的?
2.误区2:多线程(CPU上下文会切换!)一定比单线程效率高?
核心:redis是将所有的数据全部放在内存中的,所以说使用单线程去操作效率就是最高的,多线程(CPU上下文会切换:耗时操作),对于内存系统来说,如果没有上下文切换效率就是最高的!多次读写都是在一个CPU上的,在内存的情况下,这个就是最佳方案。
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)。
命令 | 解释 |
---|---|
append name hello | 给字符串类型字段name追加“hello”值, 如果name字段本身不存在,相当于set命令, 追加的值带不带双引号都可以 |
strlen name | 查看name字段字符串的长度 |
incr age | age值自加1 |
incrby age 4 | age值自加4 |
decr age | age值自减1 |
decrby age 4 | age值自减4 |
getrange name 0 3 | 取出指定范围字符串,前后闭区间[] |
setrange name 1 xx | 从第指定字符开始,替换成xx |
setex key3 30 “hello” | 设置过期时间 |
setnx key3 “lala” | 如果不存在才会设置,不然不生效 |
mset k1 v2 k2 v3 k3 v3 | 批量设置多个值 |
mget k1 k2 k3 | 批量获取多个值 |
msetnx | 如果不存在才会批量设置,不然不生效 该操作是原子操作,一个失败便都失败 |
set user:1 {name:zhangsan,age:21} | 使用json字符串的形式存储对象 |
getset name “zhangsan” | 先get再set,如果不存在值,则返回nil,然后赋值为后面的值;如果存在值,则返回值,然后赋值为后面的值 |
setex
setnx
127.0.0.1:6379> setex key3 30 "hellohello" # 设置字段key3,并在30秒后过期
OK
127.0.0.1:6379> setnx mykey "huihui" # 如果该字段不存在,则设置成功
(integer) 1
127.0.0.1:6379> setnx mykey "lala" # 如果该字段存在,则设置不生效
(integer) 0
127.0.0.1:6379> get mykey
"huihui"
###############################################################
mset
mget
127.0.0.1:6379> mset k1 v2 k2 v3 k3 v3 # 批量设置多个值
OK
127.0.0.1:6379> keys *
1) "k2"
2) "k3"
3) "k1"
127.0.0.1:6379> mget k1 k2 k3 # 批量获取多个值
1) "v2"
2) "v3"
3) "v3"
###############################################################
对象
# 使用json字符串的形式存储对象
127.0.0.1:6379> set user:1 {name:zhangsan,age:21}
OK
127.0.0.1:6379> get user:1
"{name:zhangsan,age:21}"
###############################################################
getset
127.0.0.1:6379> getset name hui # 如果不存在值,则返回nil,然后赋值为后面的值
(nil)
127.0.0.1:6379> get name
"hui"
127.0.0.1:6379> getset name zhangsan # 如果存在值,则返回值,然后赋值为后面的值
"hui"
127.0.0.1:6379> get name
"zhangsan"
命令 | 解释 |
---|---|
lpush mylist hello | 左边加入hello节点 |
rpush mylist hello | 右边加入hello节点 |
lrange mylist 0 1 | 通过区间获得具体的值 |
lpop mylist | 移除列表第一个元素(左) |
rpop mylist | 移除列表最后一个元素(右) |
lindex mylist 0 | 获取指定下标的值 |
llen mylist | 获取list的长度 |
lrem mylist 1 hello | 移除指定个数的节点 |
ltrim mylist 1 2 | 通过下标截取指定的长度 |
rpoplpush mylist myotherlist | 把mylist最后一个元素弹出,放入另一个list中 |
lset mylist 0 hello | 将列表中指定下标的值替换为另外一个值,更新操作(如果列表不存在,或者下标不存在都会报错) |
linsert mylist before/after 已有节点 新节点 | 可以用befor或者after |
移除固定的值
lrem
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "three"
3) "two"
4) "one"
127.0.0.1:6379> lrem list 1 one # 移除list集合中指定个数的value,精确匹配
(integer) 1
127.0.0.1:6379> lrem list 2 three# 此时three列表中有两个,可以同时移除
(integer) 2
127.0.0.1:6379> lrange list 0 -1
1) "two"
#####################################################################
trim 修建
127.0.0.1:6379> lrange mylist 0 -1
1) "hello"
2) "hello1"
3) "hello2"
4) "hello3"
127.0.0.1:6379> ltrim mylist 1 2 # 通过下标截取指定的长度,截断了只剩下截取的元素
OK
127.0.0.1:6379> lrange mylist 0 -1
1) "hello1"
2) "hello2"
#####################################################################
rpoplpush # 组合命令,把最右边的弹出来,并加到新指定的list中
127.0.0.1:6379> lrange mylist 0 -1
1) "hello"
2) "hello1"
3) "hello2"
127.0.0.1:6379> rpoplpush mylist myotherlist #
"hello2"
127.0.0.1:6379> lrange mylist 0 -1
1) "hello"
2) "hello1"
127.0.0.1:6379> lrange myotherlist 0 -1
1) "hello2"
#####################################################################
lset :将列表中指定下标的值替换为另外一个值,更新操作(如果列表不存在,或者下标不存在都会报错)
127.0.0.1:6379> lpush list value1
(integer) 1
127.0.0.1:6379> lrange list 0 -1
1) "value1"
127.0.0.1:6379> lset list 0 item
OK
127.0.0.1:6379> lrange list 0 -1
1) "item"
#####################################################################
linsert 插入
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> linsert list before world other # 可以用befor或者after
(integer) 3
127.0.0.1:6379> lrange list 0 -1
1) "hello"
2) "other"
3) "world"
命令 | 含义 |
---|---|
sadd myset value | 加入新值 |
smembers myset | 查看set中所有值 |
sismember myset value | 查看set中是否存在指定值 |
scard myset | 获取set中元素的个数 |
srem myset hello | 移除set集合中指定的元素 |
srandmember myset | 获取myset中随机值 |
spop myset | 随机弹出myset中的元素 |
smove myset myset2 “huige” | 将myset中的huige移动到myset2中 |
sdiff myset1 myset2 | 差集 |
sinter myset1 myset2 | 交集 |
sunion myset1 myset2 | 并集 |
127.0.0.1:6379> sadd myset "hello" # 加入新值
(integer) 1
127.0.0.1:6379> sadd myset "world"
(integer) 1
127.0.0.1:6379> sadd myset "zhangsan"
(integer) 1
127.0.0.1:6379> smembers myset # 查看set中所有值
1) "zhangsan"
2) "world"
3) "hello"
127.0.0.1:6379> sismember myset hello # 查看set中是否存在指定值
(integer) 1
127.0.0.1:6379> scard myset # 获取set中元素的个数
(integer) 3
127.0.0.1:6379> srem myset hello # 移除set集合中指定的元素
(integer) 1
127.0.0.1:6379> srandmember myset # 获取随机值
"world"
127.0.0.1:6379> smembers myset
1) "zhangsan"
2) "world"
127.0.0.1:6379> spop myset # 随机弹出myset中的元素
"zhangsan"
127.0.0.1:6379> smembers myset
1) "world"
127.0.0.1:6379> smove myset myset2 "huige" # 将myset中的huige移动到myset2中
(integer) 1
127.0.0.1:6379> sdiff key1 key2 # 差集
1) "a"
2) "b"
127.0.0.1:6379> sinter key1 key2 # 交集
1) "c"
127.0.0.1:6379> sunion key1 key2 # 并集
1) "e"
2) "b"
3) "c"
4) "a"
5) "d"
Map集合,key-map(map:
set myhash field “zhangsan”
命令 | 解释 |
---|---|
hset myhash field1 zhangsan | set一个具体的key-value |
hget myhash field1 | 获得一个字段值 |
hmset myhash field1 hello field2 world | 批量赋值 |
hmget myhash field1 field2 | 获得多个值 |
hgetall myhash | 得到所有的值 |
hdel myhash field1 | 删除hash指定的key字段,对应的value也消失了 |
hlen myhash | 获取myhash的长度 |
hexists myhash field1 | 判断myhash指定字段是否存在 |
hkeys myhash | 获取所有的字段 |
hvals myhash | 获取所有的value |
hincr、hdecr、hincrby、hdecrby | 与string中自增自减用法一样 |
hsetnx | 与string中setnx用法差不多 |
127.0.0.1:6379> hset myhash field1 zhangsan # set一个具体的key-value
(integer) 1
127.0.0.1:6379> hget myhash field1 # 获得一个字段值
"zhangsan"
127.0.0.1:6379> hmset myhash field1 hello field2 world # 批量赋值
OK
127.0.0.1:6379> hmget myhash field1 field2 # 获得多个值
1) "hello"
2) "world"
127.0.0.1:6379> hgetall myhash # 得到所有的值
1) "field1"
2) "hello"
3) "field2"
4) "world"
127.0.0.1:6379> hdel myhash field1 # 删除hash指定的key字段,对应的value也消失了
(integer) 1
127.0.0.1:6379> hgetall myhash
1) "field2"
2) "world"
127.0.0.1:6379> hlen myhash # 获取myhash的长度
(integer) 2
127.0.0.1:6379> hexists myhash field1 # 判断myhash指定字段是否存在
(integer) 1
127.0.0.1:6379> hkeys myhash # 获取所有的字段
1) "field2"
2) "field1"
127.0.0.1:6379> hvals myhash # 获取所有的value
1) "world"
2) "hello"
命令 | 解释 |
---|---|
zadd myset 1 one | 添加一个值,这个1是为了以后排序增加的标志 |
zadd myset 3 three 4 four | 添加多个值 |
zrange myset 0 -1 | 获取一定区间的元素 |
zrangebyscore myset -inf +inf | zrangebyscore key min max [withscores] 从小到大排序[并附带值] |
zrevrange myset 0 -1 | 从大到小排序 |
zrem myset xiaohong | 移除指定用户 |
zcard myset | 获取元素个数 |
zcount myset 1 3 | 获取指定区间的成员数量 |
127.0.0.1:6379> zadd myset 1 one # 添加一个值
(integer) 1
127.0.0.1:6379> zadd myset 2 two
(integer) 1
127.0.0.1:6379> zadd myset 3 three 4 four # 添加多个值
(integer) 2
127.0.0.1:6379> zrange myset 0 -1 # 获取一定区间的元素
1) "one"
2) "two"
3) "three"
4) "four"
127.0.0.1:6379> zadd salary 2500 xiaohong
(integer) 1
127.0.0.1:6379> zadd salary 3000 zhangsan
(integer) 1
127.0.0.1:6379> zadd salary 500 huige
(integer) 1
127.0.0.1:6379> zrangebyscore salary -inf +inf #从小到大排序
1) "huige"
2) "xiaohong"
3) "zhangsan"
127.0.0.1:6379> zrangebyscore salary -inf +inf withscores#从小到大排序并附带值
1) "huige"
2) "500"
3) "xiaohong"
4) "2500"
5) "zhangsan"
6) "3000"
127.0.0.1:6379> zrevrange salary 0 -1 # 从大到小排序
1) "zhangsan"
2) "huige"
127.0.0.1:6379> zrem salary xiaohong # 移除指定用户
(integer) 1
127.0.0.1:6379> zcard salary # 获取元素个数
(integer) 2
127.0.0.1:6379> zcount myset 1 3 # 获取指定区间的成员数量
(integer) 3
相关命令:
# 添加位置信息
127.0.0.1:6379> geoadd china:city 116.40 39.90 beijing
(integer) 1
127.0.0.1:6379> geoadd china:city 121.47 31.23 shanghai
(integer) 1
127.0.0.1:6379> geoadd china:city 106.50 29.53 chongqing
(integer) 1
127.0.0.1:6379> geoadd china:city 114.05 22.52 shenzhen
(integer) 1
127.0.0.1:6379> geoadd china:city 120.16 30.24 hangzhou
(integer) 1
127.0.0.1:6379> geoadd china:city 108.96 34.26 xian
(integer) 1
# 获得当前定位:一定是一个坐标值
127.0.0.1:6379> geopos china:city beijing
1) 1) "116.39999896287918091"
2) "39.90000009167092543"
127.0.0.1:6379> geopos china:city beijing chongqing
1) 1) "116.39999896287918091"
2) "39.90000009167092543"
2) 1) "106.49999767541885376"
2) "29.52999957900659211"
指定单位的参数 unit 必须是以下单位的其中一个:
m 表示单位为米。
km 表示单位为千米。
mi 表示单位为英里。
ft 表示单位为英尺。
127.0.0.1:6379> geodist china:city beijing shanghai
"1067378.7564"
127.0.0.1:6379> geodist china:city beijing shanghai km # 指定单位
"1067.3788"
以给定的经纬度为中心, 返回键包含的位置元素当中, 与中心的距离不超过给定最大距离的所有位置元素。
127.0.0.1:6379> georadius china:city 110 30 1000 km # 查询一定半径内的城市
1) "chongqing"
2) "xian"
3) "shenzhen"
4) "hangzhou"
# # 查询一定半径内的城市,并带上距离与经纬度。
127.0.0.1:6379> georadius china:city 110 30 500 km withdist withcoord count 1
1) 1) "chongqing"
2) "341.9374"
3) 1) "106.49999767541885376"
2) "29.52999957900659211"
这个命令和 GEORADIUS 命令一样, 都可以找出位于指定范围内的元素, 但是 GEORADIUSBYMEMBER 的中心点是由给定的位置元素决定的, 而不是像 GEORADIUS 那样, 使用输入的经度和纬度来决定中心点。
127.0.0.1:6379> GEORADIUSBYMEMBER china:city beijing 1000 km
1) "beijing"
2) "xian"
命令将返回11个字符的Geohash字符串(将二维的经纬度,转成一维的字符串)
127.0.0.1:6379> geohash china:city beijing chongqing
1) "wx4fbxxfke0"
2) "wm5xzrybty0"
所以Zset所有命令都可以使用,也可以删除位置信息等操作。
127.0.0.1:6379> zrange china:city 0 -1
1) "chongqing"
2) "xian"
3) "shenzhen"
4) "hangzhou"
5) "shanghai"
6) "beijing"
127.0.0.1:6379> zrem china:city beijing
(integer) 1
127.0.0.1:6379> zrange china:city 0 -1
1) "chongqing"
2) "xian"
3) "shenzhen"
4) "hangzhou"
5) "shanghai"
什么是基数?
A{1,3,5,7,8,7}
B{1,3,5,7,8}
基数(不重复的元素)=5,可以接收误差!
Redis Hyperloglogs基数统计的算法!
优点:占用的内存是固定的,2^64不同的元素的技术,只需要废12KB的内存!如果从内存的角度,Hyperloglogs首选!
网页的UV(一个人访问一个网站多次,但还是算作一个人!)
1.传统方式,set保存用户的id,然后就可以通过set中用户数量作为标准判断!
这个方式如果保存大量的用户id,就会比较麻烦!我们的目的是计数,而不是保存用户id;
2.使用hyperloglogs(0.81%错误率!统计UV任务,可以忽略不计的!)
127.0.0.1:6379> PFadd mykey a b c d e f g h i j
(integer) 1
127.0.0.1:6379> PFcount mykey
(integer) 10
127.0.0.1:6379> PFadd mykey2 i j z x c v b n m
(integer) 1
127.0.0.1:6379> PFcount mykey2
(integer) 9
127.0.0.1:6379> pfmerge mykey3 mykey mykey2 # 合并mykey和mykey2到mykey3中
OK
127.0.0.1:6379> pfcount mykey3
(integer) 15
Bitmaps位图,数据结构!都是操作二进制位来进行记录,就只有0和1两个状态!
使用bitmap来记录周一到周日的打卡。
查看某一天是否打卡(周三):
127.0.0.1:6379> getbit sign 3
(integer) 1
统计这周打卡次数:
127.0.0.1:6379> bitcount sign
(integer) 4
Redis事务本质:一组命令的集合!一个事务中的所有命令都会被序列化,在事务执行过程的中,会按照顺序执行!
一次性、顺序性、排他性!执行一些列的命令!
--------队列 set set set 执行-------
Redis事务没有隔离级别的概念!
所有的命令在事务中,并没有直接被执行!只有发起执行命令的时候才会执行!Exec
Redis单条命令保证原子性,但是事务不保证原子性!
编译型异常(代码有问题,命令有错),事务中所有命令都不会执行。
运行时异常,如果事务队列中存在语法性错误,那么执行命令的时候,其他命令是可以正常执行的,错误命令会抛异常!
Redis的监视测试(watch money :监视money对象)
测试多线程修改值,使用watch可以当作redis的乐观锁操作。(执行watch money命令之后,用另外一个进程对money进行修改,则本进程的事务会执行失败)
如果修改失败,获取最新的值就好。(先unwatch)
Jedis是Redis官方推荐的java连接开发工具!是使用Java操作Redis的中间件!
<dependencies>
<dependency>
<groupId>redis.clientsgroupId>
<artifactId>jedisartifactId>
<version>3.2.0version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
<version>1.2.73version>
dependency>
dependencies>
public class TestPing {
public static void main(String[] args) {
// 1. new Jedis 对象即可
Jedis jedis = new Jedis("127.0.0.1", 6379);
// Jedis所有的命令就是我们之前学习的所有指令!
String ping = jedis.ping();
System.out.println(ping);
jedis.close();
}
}
说明:在SpringBoot2.x之后,原来使用的jedis被替换为了lettuce?
jedis :采用的直连,多个线程操作的话,是不安全的,如果想要避免不安全的,使用jedis pool连接池!更像BIO模式。
lettuce :采用netty ,实例可以再多个线程中进行共享,不存在线程不安全的情况!可以减少线程数据了,更像NIO模式。
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
# 设置redis配置信息
spring.redis.host=127.0.0.1
spring.redis.port=6379
@SpringBootTest
class Redis02SpringbootApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
void contextLoads() {
// redisTemplate 操作不同的数据类型
//opsForValue 操作字符串 类似String
//opsForList 操作字符串 类似List
//opsForSet 操作字符串 类似Set
//opsForHash 操作字符串 类似Hash
//opsForZSet 操作字符串 类似Zset
//opsForGeo 操作字符串 类似geospatial
//opsForHyperLogLog 操作字符串 类似hyperloglogs
redisTemplate.opsForValue().set("user","huige");
System.out.println(redisTemplate.opsForValue().get("user"));
// 除了基本的操作,我们常用的方法都可以直接redisTemplate来操作,比如事务和基本的 CRUD
// redisTemplate.multi();
// redisTemplate.exec();
// redisTemplate.discard();
// 关于数据库的操作
// RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
// connection.flushDb();
// connection.flushAll();
}
}
自己写关于RedisTemplate的java配置类:(可以直接拿去用)
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.net.UnknownHostException;
@Configuration
public class RedisConfig {
// 这是一个固定的模板,可以直接使用
@Bean
@SuppressWarnings("all")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) throws UnknownHostException{
// 为了方便开发,一般直接使用
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
// Json序列化配置
Jackson2JsonRedisSerializer<Object> 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);
// String 的序列化
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// 配置具体的序列化方式
// key采用String的序列化方式
template.setKeySerializer(stringRedisSerializer);
// hash的key也采用String的序列化方式
template.setHashKeySerializer(stringRedisSerializer);
template.setValueSerializer(stringRedisSerializer);
template.afterPropertiesSet();
return template;
}
}
测试代码的使用:
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.hui.pojo.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;
@SpringBootTest
class Redis02SpringbootApplicationTests {
@Autowired
@Qualifier("redisTemplate") // 这里一定要能跳到自定义的配置类那里才行
private RedisTemplate redisTemplate;
@Test
public void test() throws JsonProcessingException {
// 真实的开发一般都使用json来传递对象
User user = new User("辉哥",22);
String jsonUser = new ObjectMapper().writeValueAsString(user);
redisTemplate.opsForValue().set("user",jsonUser);
System.out.println(redisTemplate.opsForValue().get("user"));
}
}
注意: 第15行一定是能点到自定义配置类那里。
自定义配置类: 自定义Redis配置类.
自定义配置类的使用:
Redis是内存数据库,如果不将内存中的数据库状态保存到磁盘,那么一旦服务器进程退出,服务器中的数据库状态也会消失。所以Redis提供了持久化功能! I
在指定的时间间隔内将内存中的数据集快照写入磁盘,也就是行话讲的Snapshot快照,它恢复时是将快照文件直接读到内存里。
Redis会单独创建( fork ) 一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程都结束了, 再用这个临时文件替换。上次持久化好的文件。整个过程中,主进程是不进行任何IO操作的。这就确保了极高的性能。如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效。RDB的缺点是最后一次持久化后的数据可能丢失。
2.rdb保存的文件是dump.rdb,都是在配置文件中快照中进行配置的,在符合触发机制的时候,本地就会生成dump.rdb文件快照!
如图,本地生成该文件。
3.触发机制
– save的规则满足的情况下,会自动触发rdb规则!
– 执行flushall命令,也会触发我们的rdb规则!
– 退出redis ,也会产生rdb文件!
备份就会自动生成一个dump.rdb
4.如何恢复rdb文件
– a.只需要将rdb文件放在redis启动目录就可以, redis启动的时候会自动检查dump.rdb 恢复其中的数据!
– b.查看需要存在的位置
127.0.0.1:6379> config get dir
1) "dir"
2) "/usr/local/bin" # 如果这个目录下存在dump.rdb文件,启动就会自动恢复其中的数据。
将我们的所有命令都记录下来appendonly.aof,恢复的时候再把这个文件的命令都执行一遍。
以日志的形式来记录每个写操作,将Redis执行过的所有指令记录下来(读操作不记录) , 只许追加文件但不可以改写文件, redis启动之初会读取该文件重新构建数据,换言之, redis重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作。
redis-check-aof --fix appendonly.aof
appendonly no #默认是不开启aof模式的,默认是使用rdb方式持久化的,在大部分所有的情况下,rdb完全够用!
appendfilename "appendonly.aof" # 持久化的文件的名字
# appendfsync always #每次修改都会sync。消耗性能
appendfsync everysec #每秒执行一次sync,可能会丢失这1s的数据!
# appendfsync no #不执行sync,这个时候操作系统自己同步数据,速度最快!
优点:
a.每一次修改都同步,文件的完整会更加好!
b.每秒同步一次,可能会丢失一秒的数据
c.从不同步,效率最高的!
缺点:
a.相对于数据文件来说, aof远远大于rdb ,修复的速度也比rdb慢!
b.aof运行效率也要比rdb慢,所以redis默认的配置就是rdb持久化!
Redis发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。微信、微博、关注系统!
Redis客户端可以订阅任意数量的频道。
订阅/发布消息图:(1.消息发送者 2.频道 3.消息订阅者)
当有新消息通过PUBLISH命令发送给频道channel1时,这个消息就会被发送给订阅它的三个客户端:
代码小总结:
publish channel message # 发布者发布消息到指定频道
subscribe channel[channel...] # 订阅一个或多个频道
psubscribe pattern[pattern...] # 订阅一个或多个符合给定模式的频道
unsubscribe channel[channel...] # 退订一个或多个频道
punsubscribe channel[channel...]# 退订一个或多个符合给定模式的频道
pubsub subcommand [argument[argument...]] # 查看订阅与发布系统状态
subscribe huige # 订阅频道
127.0.0.1:6379> subscribe huige
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "huige"
3) (integer) 1
publish huige "hello,huige" # 向指定频道发布消息
127.0.0.1:6379> publish huige "hello,huige"
(integer) 1
127.0.0.1:6379>
主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(master/leader) ,后者称为从节点(slave/follower) ;数据的复制是单向的,只能由主节点到从节点。Master以写为主,Slave 以读为主。
主从复制,读写分离!80% 的情况下都是在进行读操作!减缓服务器的压力!架构中经常使用!一主二从!
info replication # 查看当前库的信息
127.0.0.1:6379> info replication # 查看当前库的信息
# Replication
role:master # 角色:master
connected_slaves:0 # 连接的从库个数
master_failover_state:no-failover
master_replid:3135c763c3ca0d265acf93ffffbe93f9ef731eb1
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
[root@xiaohuiCentos huiconfig]# ls
redis79.conf redis80.conf redis81.conf redis.conf
默认情况下,每台Redis服务器都是主节点;一般情况下只用配置从机就好了。
# 让两个从机认老大
slaveof host port
例如: slaveof 127.0.0.1 6379
另外一种布局方式:(这时候也可以完成主从复制)
此时如果老大(79)宕机,在没有哨兵模式之前,可以手动选择一个老大出来。
使用下面这个命令,让自己变成主机(比如80),其他节点手动连接到最新的这个主节点。如果此时原来老大(79)修复了,那就重新配置连接。
slaveof no one # 让自己变成主机
sentinel monitor host port 1
然后启动哨兵:
redis-sentinel huiconfig/sentinel.conf
这个时候如果主机宕机,哨兵会把一个从机转成主机(按照一定算法),后续就算从机回来了,也只能当从机。
优点:
1、哨兵集群,基于主从复制模式,所有的主从配置优点,它全有。
2、主从可以切换,故障可以转移,系统的可用性就会更好。
3、哨兵模式就是主从模式的升级,手动到自动,更加健壮!
缓存穿透的概念很简单,用户想要查询一个数据,发现redis内存数据库没有,也就是缓存没有命中,于是向持久层数据库查询,发现也没有,于是本次查询失败。当用户很多的时候,缓存都没有命中(秒杀! ) , 于是都去请求了持久层数据库。这会给持久层数据库造成很大的压力,这时候就相当于出现了缓存穿透。
解决方案:布隆过滤器
布隆过滤器是一种数据结构,对所有可能查询的参数以hash形式存储,在控制层先进行校验,不符合则丢弃,从而避免了对底层存储系统的查询压力;
这里需要注意和缓存击穿的区别,缓存击穿是指一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞。
当某个key在过期的瞬间,有大量的请求并发访问,这类数据一般是热点数据,由于缓存过期,会同时访问数据库来查询最新数据,并且回写缓存,会导使数据库瞬间压力过大。
解决方案:设置热点数据永不过期、加互斥锁。
缓存雪崩,是指在某一个时间段,缓存集中过期失效。Redis 宕机!
产生雪崩的原因之一,比如在写本文的时候,马上就要到双十二零点,很快就会迎来一波抢购,这波商品时间比较集中的放入了缓存,假设缓存一个小时。那么到了凌晨一点钟的时候,这批商品的缓存就都过期了。而对这批商品的访问查询,都落到了数据库上,对于数据库而言,就会产生周期性的压力波峰。于是所有的请求都会达到存储层,存储层的调用量会暴增,造成存储层也会挂掉的情况。