目录
1.相关链接
1.官网:https://redis.io/.
1.中文网:http://www.redis.cn/.
2. 安装
1.1 Windows安装(一般不用)
1.下载安装包
Redis的github地址: https://github.com/dmajkic/redis/releases.
下载完毕:
2.进行解压:
3.双击运行服务端启动
如果闪退: 解决办法.
然后会出现以下界面
4.双击运行客户端启动
1.2 Linux安装(一般都用linux)
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
11. 如何关闭redis服务
shutdown
exit
2. redis-benchmark性能测试
redis-benchmark + 命令参数
简单测试一下:
# 测试:100个并发连接 100000请求
redis-benchmark -h localhost -p 6379 -c 100 -n 100000
具体怎么看分析:
3. Redis基础知识说明
3.1 基本命令
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 # 获取指定区间的成员数量
注意: 其他的命令可以在中文网站上查看。
3.2 Redis是单线程的
明白Redis是很快的,官方表示,Redis是基于内存操作,CPU不是Redis性能瓶颈,Redis的瓶颈是根据机器的内存和网络带宽,既然可以使用单线程来实现,就使用单线程了!
Redis是C语言写的,官方提供的数据为100000+的QPS,这个完全不比Memecache差。
Redis为什么单线程还这么快?
1.误区1:高性能的服务器一定是多线程的?
2.误区2:多线程(CPU上下文会切换!)一定比单线程效率高?
核心:redis是将所有的数据全部放在内存中的,所以说使用单线程去操作效率就是最高的,多线程(CPU上下文会切换:耗时操作),对于内存系统来说,如果没有上下文切换效率就是最高的!多次读写都是在一个CPU上的,在内存的情况下,这个就是最佳方案。
4. 五大数据类型
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)。
4.1 String
命令 | 解释 |
---|---|
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"
4.2 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 |
移除固定的值
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"
4.3 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 | 并集 |
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"
4.4 Hash
Map集合,key-map(map:<key-value>)!这时候值是一个map集合!本质和String类型没有太大区别,还是一个简单key-value!
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"
4.5 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 | 获取指定区间的成员数量 |
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
5. 三种特殊数据类型
5.1 geospatial(地理位置)
相关命令:
- 1.GEOADD
- 2.GEOPOS
- 3.GEODIST
- 4.GEORADIUS
- 5.GEORADIUSBYMEMBER
- 6.GEOHASH
5.1.1 GEOADD添加地理位置
- 有效的经度从-180度到180度。
- 有效的纬度从-85.05112878度到85.05112878度。
- 一般会先下载城市数据,然后直接用java程序一次性导入
- 参数 key 值(经度、纬度、名称)
# 添加位置信息
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
5.1.2 GEOPOS获取经纬度
# 获得当前定位:一定是一个坐标值
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"
5.1.3 GEODIST返回两个给定位置之间的距离
指定单位的参数 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"
5.1.4 GEORADIUS返回一定半径内的元素
以给定的经纬度为中心, 返回键包含的位置元素当中, 与中心的距离不超过给定最大距离的所有位置元素。
- WITHDIST: 在返回位置元素的同时, 将位置元素与中心之间的距离也一并返回。 距离的单位和用户给定的范围单位保持一致。
- WITHCOORD: 将位置元素的经度和维度也一并返回。
- WITHHASH: 以 52 位有符号整数的形式, 返回位置元素经过原始 geohash 编码的有序集合分值。 这个选项主要用于底层应用或者调试, 实际中的作用并不大。
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"
5.1.5 GEORADIUSBYMEMBER返回一定半径内的元素
这个命令和 GEORADIUS 命令一样, 都可以找出位于指定范围内的元素, 但是 GEORADIUSBYMEMBER 的中心点是由给定的位置元素决定的, 而不是像 GEORADIUS 那样, 使用输入的经度和纬度来决定中心点。
127.0.0.1:6379> GEORADIUSBYMEMBER china:city beijing 1000 km
1) "beijing"
2) "xian"
5.1.6 GEOHASH(用的不多)
命令将返回11个字符的Geohash字符串(将二维的经纬度,转成一维的字符串)
127.0.0.1:6379> geohash china:city beijing chongqing
1) "wx4fbxxfke0"
2) "wm5xzrybty0"
5.1.7 GEO的底层是Zset
所以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"
5.2 hyperloglogs数据结构(用于计数)
什么是基数?
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
5.3 bitmaps
Bitmaps位图,数据结构!都是操作二进制位来进行记录,就只有0和1两个状态!
使用bitmap来记录周一到周日的打卡。
查看某一天是否打卡(周三):
127.0.0.1:6379> getbit sign 3
(integer) 1
统计这周打卡次数:
127.0.0.1:6379> bitcount sign
(integer) 4
6. 事务
Redis事务本质:一组命令的集合!一个事务中的所有命令都会被序列化,在事务执行过程的中,会按照顺序执行!
一次性、顺序性、排他性!执行一些列的命令!
--------队列 set set set 执行-------
Redis事务没有隔离级别的概念!
所有的命令在事务中,并没有直接被执行!只有发起执行命令的时候才会执行!Exec
Redis单条命令保证原子性,但是事务不保证原子性!
6.1 redis的事务
- 开启事务(multi)
- 命令入队(…)
- 执行事务(exec)
放弃事务:discard
6.2 出现异常
编译型异常(代码有问题,命令有错),事务中所有命令都不会执行。
运行时异常,如果事务队列中存在语法性错误,那么执行命令的时候,其他命令是可以正常执行的,错误命令会抛异常!
6.3 悲观锁
- 很悲观,认为什么时候都会出问题,无论做什么都会加锁!(影响性能)
6.4 乐观锁(watch)
- 很乐观,认为什么时候都不会出问题,所以不会上锁!更新数据的时候去判断一下,在此期间是否有人修改过这个数据,version。
- 获取version
- 更新的时候比较version
Redis的监视测试(watch money :监视money对象)
测试多线程修改值,使用watch可以当作redis的乐观锁操作。(执行watch money命令之后,用另外一个进程对money进行修改,则本进程的事务会执行失败)
如果修改失败,获取最新的值就好。(先unwatch)
7. Jedis(原生的api)
Jedis是Redis官方推荐的java连接开发工具!是使用Java操作Redis的中间件!
7.1 导入对应的依赖
<dependencies>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.2.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.73</version>
</dependency>
</dependencies>
7.2 编码测试:
- 连接数据库
- 操作命令
- 断开连接
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();
}
}
常用的API
- String
- List
- Set
- Hash
- Zset
所有命令跟上面的命令好是一样的。
部分命令演示如下:
8. SpringBoot整合
说明:在SpringBoot2.x之后,原来使用的jedis被替换为了lettuce?
jedis :采用的直连,多个线程操作的话,是不安全的,如果想要避免不安全的,使用jedis pool连接池!更像BIO模式。
lettuce :采用netty ,实例可以再多个线程中进行共享,不存在线程不安全的情况!可以减少线程数据了,更像NIO模式。
8.1 导入依赖:
<!-- 操作redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
8.2 配置信息:
# 设置redis配置信息
spring.redis.host=127.0.0.1
spring.redis.port=6379
8.3 测试:
@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();
}
}
8.4 自定义RedisTemplate
-
为什么要自定义呢?如果不自定义,当值是对象时,数据库中的键前面有一串序列。
-
按照如下方式自定义RedisTemplate之后,键前面的序列化字符串现象消失。(为正常的user)
自己写关于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{
// 为了方便开发,一般直接使用<String,Object>
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行一定是能点到自定义配置类那里。
8.5 自定义工具类
自定义配置类: 自定义Redis配置类.
自定义配置类的使用:
9. Redis持久化
Redis是内存数据库,如果不将内存中的数据库状态保存到磁盘,那么一旦服务器进程退出,服务器中的数据库状态也会消失。所以Redis提供了持久化功能! I
9.1 RDB(Redis DataBase)(默认)
在指定的时间间隔内将内存中的数据集快照写入磁盘,也就是行话讲的Snapshot快照,它恢复时是将快照文件直接读到内存里。
Redis会单独创建( fork ) 一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程都结束了, 再用这个临时文件替换。上次持久化好的文件。整个过程中,主进程是不进行任何IO操作的。这就确保了极高的性能。如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效。RDB的缺点是最后一次持久化后的数据可能丢失。
- 默认就是RDB,一般情况下不需要修改要修改这个配置!
- 1.在配置文件中:(意思是3600秒内1次修改、300秒内100次修改。。会触发快照策略)
-
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文件,启动就会自动恢复其中的数据。
- 5.优点
– a.适合大规模的数据恢复!
– b.对数据的完整型要求不高! - 6.缺点
– a.需要一定的时间间隔进行操作!如果redis意外宕机,这个最后一次修改的数据就没有了。
– b.fork进程的时候,会占用一定的内存空间。
9.2 AOF(Append Only File)
将我们的所有命令都记录下来appendonly.aof,恢复的时候再把这个文件的命令都执行一遍。
以日志的形式来记录每个写操作,将Redis执行过的所有指令记录下来(读操作不记录) , 只许追加文件但不可以改写文件, redis启动之初会读取该文件重新构建数据,换言之, redis重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作。
- 1.默认未开启,如果开启就改成yes,追加的文件名为:appendonly.aof(在配置文件中)
- 2.启动之后默认把appendonly.aop文件中的命令执行一遍,以达到数据恢复的效果。
- 3.如果aof文件有错误,这时候redis是启动不起来的,需要修复aof文件,redis提供了一个工具 redis-check-aof --fix appendonly.aof,用该命令修复aof文件。
redis-check-aof --fix appendonly.aof
- 4.优缺点
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持久化!
10. Redis发布订阅
10.1 概念
Redis发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。微信、微博、关注系统!
Redis客户端可以订阅任意数量的频道。
订阅/发布消息图:(1.消息发送者 2.频道 3.消息订阅者)
当有新消息通过PUBLISH命令发送给频道channel1时,这个消息就会被发送给订阅它的三个客户端:
10.2 相关命令
代码小总结:
publish channel message # 发布者发布消息到指定频道
subscribe channel[channel...] # 订阅一个或多个频道
psubscribe pattern[pattern...] # 订阅一个或多个符合给定模式的频道
unsubscribe channel[channel...] # 退订一个或多个频道
punsubscribe channel[channel...]# 退订一个或多个符合给定模式的频道
pubsub subcommand [argument[argument...]] # 查看订阅与发布系统状态
- 1.用户1订阅huige频道:
subscribe huige # 订阅频道
127.0.0.1:6379> subscribe huige
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "huige"
3) (integer) 1
- 2.用户2通过huige频道发布消息:
publish huige "hello,huige" # 向指定频道发布消息
127.0.0.1:6379> publish huige "hello,huige"
(integer) 1
127.0.0.1:6379>
- 3.用户1收到订阅的消息
11. Redis主从复制
11.1 概念
主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(master/leader) ,后者称为从节点(slave/follower) ;数据的复制是单向的,只能由主节点到从节点。Master以写为主,Slave 以读为主。
主从复制,读写分离!80% 的情况下都是在进行读操作!减缓服务器的压力!架构中经常使用!一主二从!
11.2 查看当前库的信息
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
11.3 环境配置(搭一个集群)
- 1.把配置文件多复制几个,分别是79 80 81(79主库,80、81为从库)
[root@xiaohuiCentos huiconfig]# ls
redis79.conf redis80.conf redis81.conf redis.conf
- 2.对配置文件进行修改(三个配置文件都进行相应修改,这里只给出79文件的修改【port pidfile logfile dbfilename】)
- 3.分别启动三个服务
11.4 一主二从
默认情况下,每台Redis服务器都是主节点;一般情况下只用配置从机就好了。
- 1.认老大!一主(79)二从(80,81)
# 让两个从机认老大
slaveof host port
例如: slaveof 127.0.0.1 6379
从机80:
主机(不用操作):
- 2.真实的主从配置应该在配置文件中配置,这样的话是永久的,我们这里使用的是命令,是暂时的!
只需要在从机的配置文件中,配置replicaof <masterip> <masterport> 即可,此时从机一启动就是从机。
11.4 效果
主机能写能读;从机只能读,不能写
11.5 如果主机或者从机断了
- 1.主机断开连接
从机依旧连接到主机的,但是没有写操作,这个时候,主机如果回来了,从机依旧可以直接获取到主机写的信息! - 2.从机断开连接
如果是使用命令行来配置的主从,这个时候如果重启了,从机就会变回主机!只要再次变为从机,数据立马就会从主机那里同步过来。
11.6 主机宕机后手动配置主机
另外一种布局方式:(这时候也可以完成主从复制)
此时如果老大(79)宕机,在没有哨兵模式之前,可以手动选择一个老大出来。
使用下面这个命令,让自己变成主机(比如80),其他节点手动连接到最新的这个主节点。如果此时原来老大(79)修复了,那就重新配置连接。
slaveof no one # 让自己变成主机
11.7 哨兵模式(重点)
- 1.概述
主从切换技术的方法是:当主服务器宕机后,需要手动把一台从服务器切换为主服务器,这就需要人工干预,费事费力,还会造成一段时间内服务不可用。这不是一种推荐的方式 ,更多时候,我们优先考虑哨兵模式。Redis从2.8开始正式提供 了Sentinel (哨兵)架构来解决这个问题。
能够后台监控主机是否故障,如果故障了根据投票数自动将从库转换为主库。
哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程,它会独立运行。其原理是哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例。
为了防止哨兵死了,还要使用多个哨兵互相监督:
假设主服务器宕机,哨兵1先检测到这个结果,系统并不会马上进行failover(故障转移)过程,仅仅是哨兵1主观的认为主服务器不可用,这个现象成为主观下线。当后面的哨兵也检测到主服务器不可用,并且数量达到一定值时,那么哨兵之间就会进行一次投票,投票的结果由一个哨兵发起,进行failover[故障转移]操作。切换成功后,就会通过发布订阅模式,让各个哨兵把自己监控的从服务器实现切换主机,这个过程称为客观下线。 - 2.实现
在配置文件目录下,新建一个sentinel.conf(名字不能错)哨兵配置文件,内容如下:(这是最基本的配置)
sentinel monitor host port 1
然后启动哨兵:
redis-sentinel huiconfig/sentinel.conf
这个时候如果主机宕机,哨兵会把一个从机转成主机(按照一定算法),后续就算从机回来了,也只能当从机。
优点:
1、哨兵集群,基于主从复制模式,所有的主从配置优点,它全有。
2、主从可以切换,故障可以转移,系统的可用性就会更好。
3、哨兵模式就是主从模式的升级,手动到自动,更加健壮!
12. Redis缓存穿透和雪崩
12.1 缓存穿透:(查不到)
缓存穿透的概念很简单,用户想要查询一个数据,发现redis内存数据库没有,也就是缓存没有命中,于是向持久层数据库查询,发现也没有,于是本次查询失败。当用户很多的时候,缓存都没有命中(秒杀! ) , 于是都去请求了持久层数据库。这会给持久层数据库造成很大的压力,这时候就相当于出现了缓存穿透。
解决方案:布隆过滤器
布隆过滤器是一种数据结构,对所有可能查询的参数以hash形式存储,在控制层先进行校验,不符合则丢弃,从而避免了对底层存储系统的查询压力;
12.2 缓存击穿:(量太大,缓存过期)
这里需要注意和缓存击穿的区别,缓存击穿是指一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞。
当某个key在过期的瞬间,有大量的请求并发访问,这类数据一般是热点数据,由于缓存过期,会同时访问数据库来查询最新数据,并且回写缓存,会导使数据库瞬间压力过大。
解决方案:设置热点数据永不过期、加互斥锁。
12.3 缓存雪崩:
缓存雪崩,是指在某一个时间段,缓存集中过期失效。Redis 宕机!
产生雪崩的原因之一,比如在写本文的时候,马上就要到双十二零点,很快就会迎来一波抢购,这波商品时间比较集中的放入了缓存,假设缓存一个小时。那么到了凌晨一点钟的时候,这批商品的缓存就都过期了。而对这批商品的访问查询,都落到了数据库上,对于数据库而言,就会产生周期性的压力波峰。于是所有的请求都会达到存储层,存储层的调用量会暴增,造成存储层也会挂掉的情况。