Redis
1. redis的安装
将redis上传到服务器,一般程序放到opt目录下,进入opt文件解压 ,使用以下命令
-
进入opt目录
# 进入opt目录 cd /opt
-
解压缩文件
# 解压缩文件 tar -zxvf redis-5.0.8.tar.gz
-
进入解压缩的redis目录
cd /redis-5.0.8
-
进行环境的安装
yum install gcc-c++
-
查看是否安装成功,成功会显示版本
gcc -v
-
再运行make命令
make
redis的默认安装路径为:usr/local/bin
目录下,将redis-5.0.8目录下的redis.conf配置文件复制到bin目录下
# 在bin目录下,创建了一个名为yconfig的文件夹,并将redis.conf文件复制到此目录下
mkdir yconfig
cp /opt/redis-5.0.8/redis.conf yconfig
修改当前的配置文件
cd yconfig
# 修改配文件
vim redis.conf
将daemonize 修改为 yes,是为了让redis后台运行
启动redis
# 在bin目录下启动
redis-server yconfig/redis.conf
# 连接redis
redis-cli -p 6379
结束redis
# SHUTDOWM 命令
SHUTDOWM
# 并退出
exit
2. redis-benchmark压力测试工具
-
redis 性能测试工具可选参数如下所示:
-
-
测试
-
# 测试 100个并发连接 100000请求 redis-benchmark -h localhost -p 6379 -c 100 -n 100000
-
3. redis基本知识
3.1 redis默认有16个数据库(在配置文件redis.conf中)
-
-
切换数据库的命令
-
# 切换到哪个就写对应的数字 select 2 # 查看db的大小 dbsize
-
-
清除数据库数据
-
# 清除当前数据库 flushdb # 清除所有数据库 flushall
-
3.2 redis是单线程的
- redis使基于内存操作,CPU不是redis的性能瓶颈,redis的性能瓶颈使根据机器的内存和网络带宽
- redis是将所有的数据全部放在内存中的,所以说单线程去操作的效率就是最高的,多线程(CPU会上下文切换,消耗性能和时间),对于系统来说,如果没有上下文的切换效率就是最高的,多次读写都是在一个CPU上,在内存进行读取就是最佳的方案
3.3 redis的启动
-
进入
usr/local/bin
目录,使用以下命令启动 -
# 启动redis服务 redis-server yconfig/redis.conf # 连接redis服务 redis-cli -p 6379
4. redis-key基本命令
-
# 存入值(键:name 值:yogurt;键:age 值:2) set name yogurt set age 2 # 获取所有的键 keys *
-
# 移除某一个键值对 move name 1 # 1表示移除
-
# 键是否存在 exists name # 设置过期时间 expire name 10 #表示10秒后过期 # 查看过期的详细信息 ttl name
-
# 查看键的类型 type age
5. 五大数据类型
5.1 String(字符串)类型
-
# 设置值 格式:set key value set name xin # 查看 格式:get key get name # 查看当前长度 格式:STRLEN key STRLEN name # 追加字符串 格式:append key value (如果不存在key,则相当于set) append name hello "xinhello" set count 0 # 自增1 格式:incr count incr count # 自减1 decr count # 自增指定的步长 格式:incrby count 步长 incrby count 10 # 自减指定的步长 格式:decrby count 步长 decrby count 10 # 字符串的范围 格式:getrange key 开始位置 结束位置 [0,2] GETRANGE name 0 2 # 查看全部字符串的值 格式:getrange key 0 -1 GETRANGE name 0 -1 # 替换字符串 格式:setrange key 开始位置 替换内容 SETRANGE name 3 xxxxx # 从第三个位置开始全部替换为xxxxx, hello -> helxxxxx # set with seconds (exists简写) 格式:setex key seconds value setex name 10 yogurt # 10秒后会自动过期 # setnx 如果不存在会创建 格式:setnx key value setnx age 10 # 范围设置值 mset k1 v1 k2 v2 k3 v3 # 范围获取值 mget k1 k2 k3 # msetnx用法 msetnx k1 v11 k4 v4 #redis是原子性的,要么一起成功,要么一起失败(此设置失败,因为k1已经存在) # 对象保存 user:id:key value mset user:1:name yogurt user:1:age 2 # 对象获取 mget user:1:name user:1:age 1) "yogurt" 2) "2" # getset 先获取值,后设置值 getset db redis 127.0.0.1:6379> getset db redis (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"
5.2 List类型
-
可以添加一个元素到列表的头部(左边)或列表的尾部(右边)
-
添加:
-
lpush key value : 添加元素到列表的左边
-
127.0.0.1:6379> lpush list one (integer) 1 127.0.0.1:6379> lpush list two (integer) 2 127.0.0.1:6379> lpush list three (integer) 3
-
-
rpush key value : 添加元素到列表的右边
-
127.0.0.1:6379> rpush list zero (integer) 4
-
-
-
获取
-
lrange key start end 全部获取 范围是 0 -1
-
127.0.0.1:6379> lrange list 0 -1 1) "three" 2) "two" 3) "one" 4) "zero"
-
-
lindex key index(根据下标查询元素)
-
127.0.0.1:6379> lindex list 0 "three"
-
-
-
删除
-
lpop key :删除列表最左边的元素,并将元素返回
-
rpop key :删除列表最右边的元素,并将元素返回
-
lrem key 个数 value:移除list集合中指定个数的value
-
127.0.0.1:6379> LRANGE list 0 -1 1) "two" 2) "three" 3) "three" 4) "two" 5) "one" 6) "zero"
-
127.0.0.1:6379> lrem list 1 two # 移除一个two (integer) 1 127.0.0.1:6379> LRANGE list 0 -1 1) "three" 2) "three" 3) "two" 4) "one" 5) "zero"
-
127.0.0.1:6379> lrem list 2 three # 移除2个three (integer) 2 127.0.0.1:6379> LRANGE list 0 -1 1) "two" 2) "one" 3) "zero"
-
-
-
list长度
-
Llen key:返回当前集合的长度
-
127.0.0.1:6379> llen list (integer) 4
-
-
ltrim key start end:截取list
-
# 添加数据 127.0.0.1:6379> LRANGE list 0 -1 1) "zero" 2) "one" 3) "two" 4) "three" 5) "four"
-
127.0.0.1:6379> ltrim list 0 3 # 从下标为0的开始截取,截取3个 OK 127.0.0.1:6379> LRANGE list 0 -1 1) "zero" 2) "one" 3) "two" 4) "three"
-
-
-
移除最后一个元素并将它移到到新的list中
-
127.0.0.1:6379> 127.0.0.1:6379> LRANGE list 0 -1 1) "zero" 2) "one" 3) "two" 4) "three" # 命令:rpoplpush source destination rpoplpush list newlist # 移除three到newlist中 # 查看list元素 127.0.0.1:6379> LRANGE list 0 -1 1) "zero" 2) "one" 3) "two" # 查看newlist元素 127.0.0.1:6379> LRANGE newlist 0 -1 1) "three"
-
-
更新当前下标值 (lset key index value)
-
# newlist中只有一个three元素 127.0.0.1:6379> LRANGE newlist 0 -1 1) "three" # 更新值 127.0.0.1:6379> lset newlist 0 three-1 OK 127.0.0.1:6379> LRANGE newlist 0 -1 1) "three-1" # 成功修改为three-1
-
如果列表不存在或者当前下标对应的不存在,则会更新失败
-
-
linsert:(LINSERT key BEFORE|AFTER pivot value)将某个值插入到list列表的前面或者后面
# 原先list中的值为
127.0.0.1:6379> lrange list 0 -1
1) "zero"
2) "one"
3) "two"
# 在key为list的 one元素 之前插入1
127.0.0.1:6379> LINSERT list before one 1
(integer) 4
127.0.0.1:6379> lrange list 0 -1
1) "zero"
2) "1"
3) "one"
4) "two"
# 在key为list的 two元素 之后插入2
127.0.0.1:6379> LINSERT list after two 2
(integer) 5
127.0.0.1:6379> lrange list 0 -1
1) "zero"
2) "1"
3) "one"
4) "two"
5) "2"
5.3 Set集合类型
set中的值不能重复,并且没有顺序
1.set中添加元素、获取元素、是否包含指定的元素
127.0.0.1:6379> sadd myset hello # 在set中添加元素
(integer) 1
127.0.0.1:6379> sadd myset world
(integer) 1
127.0.0.1:6379> smembers myset # 获取set中所有的元素
1) "world"
2) "hello"
127.0.0.1:6379> sismember myset hi # set中是否存在hi元素,0表示不存在
(integer) 0
127.0.0.1:6379> sismember myset hello # set中是否存在hello元素,1表示存在
(integer) 1
127.0.0.1:6379>
2.查看set中元素的个数
127.0.0.1:6379> scard myset # 查看set中元素的个数 只有2个
(integer) 2
127.0.0.1:6379> smembers myset
1) "world"
2) "hello"
3.随机抽取元素
# set中存在的元素
127.0.0.1:6379> SMEMBERS myset
1) "hello"
2) "zero"
3) "one"
4) "three"
5) "world"
6) "two"
127.0.0.1:6379> srandmember myset # 随机抽取1个元素
"zero"
127.0.0.1:6379> srandmember myset
"zero"
127.0.0.1:6379> srandmember myset
"two"
127.0.0.1:6379> srandmember myset 2 # 随机抽取2个元素
1) "world"
2) "zero"
127.0.0.1:6379> srandmember myset 2 # 指定数字为几,就随机抽取几个
1) "two"
2) "zero"
4.移除set中的元素
127.0.0.1:6379> SPOP myset # 随机移除
"hello"
5.交集(sinter)、差集(sdiff)、并集(sunion)
# 两个set集合,key1 和 key2 分别添加元素
127.0.0.1:6379> SMEMBERS key1
1) "c"
2) "b"
3) "a"
127.0.0.1:6379> SMEMBERS key2
1) "d"
2) "e"
3) "c"
# key1与key2的差集
127.0.0.1:6379> SDIFF key1 key2
1) "a"
2) "b"
# key2与key1的差集
127.0.0.1:6379> SDIFF key2 key1
1) "d"
2) "e"
# key1与key2的交集
127.0.0.1:6379> SINTER key1 key2
1) "c"
127.0.0.1:6379> SINTER key2 key1
1) "c"
# key1与key2的并集
127.0.0.1:6379> SUNION key1 key2
1) "c"
2) "a"
3) "b"
4) "d"
5) "e"
5.4 Hash(Map集合)
1.以键值对的方式出现,添加、获取、批量添加、获取
# 集合名为myhash,键为 name ,值为 zhangsan
127.0.0.1:6379> hset myhash name zhangsan
(integer) 1
# 集合名为myhash,键为 password ,值为 123
127.0.0.1:6379> hset myhash password 123
(integer) 1
# 使用hget key field 来获取指定键的值
127.0.0.1:6379> hget myhash name
"zhangsan"
127.0.0.1:6379> hget myhash password
"123"
# hmset 通知设置多个键值对
127.0.0.1:6379> hmset myhash age 18 sex 1
OK
# hmget 同时获取多个键值对
127.0.0.1:6379> HMGET myhash age sex
1) "18"
2) "1"
# hgetall 获取所有的键值对
127.0.0.1:6379> hgetall myhash
1) "name"
2) "zhangsan"
3) "password"
4) "123"
5) "age"
6) "18"
7) "sex"
8) "1"
2.hlen key 获取hash中的键值对个数
127.0.0.1:6379> hgetall myhash
1) "name"
2) "zhangsan"
3) "password"
4) "123"
5) "age"
6) "18"
7) "sex"
8) "1"
127.0.0.1:6379> hlen myhash
(integer) 4
3.删除hash中的键值对 hdel key
127.0.0.1:6379> HDEL myhash age # 删除myhash中 key为age的键和值
(integer) 1
127.0.0.1:6379> hgetall myhash
1) "name"
2) "zhangsan"
3) "password"
4) "123"
5) "sex"
6) "1"
127.0.0.1:6379> hdel myhash sex password # 删除key为sex 和 password 的键和值
(integer) 2
127.0.0.1:6379> hgetall myhash
1) "name"
2) "zhangsan"
4.判断hash中指定的字段是否存在
127.0.0.1:6379> hexists myhash name # name字段存在,返回1
(integer) 1
127.0.0.1:6379> hexists myhash password # password字段不存在 返回0
(integer) 0
5.获取所有的key(键),获取所有的value(值)
127.0.0.1:6379> hmset myhash password 123 age 12
OK
127.0.0.1:6379> hgetall myhash
1) "name"
2) "zhangsan"
3) "password"
4) "123"
5) "age"
6) "12"
127.0.0.1:6379> hkeys myhash
1) "name"
2) "password"
3) "age"
127.0.0.1:6379> hvals myhash
1) "zhangsan"
2) "123"
3) "12"
6.元素的自增 hincrby key field 步长
127.0.0.1:6379> HINCRBY myhash age 1 # age对应的原来是12 ,自增1,为13
(integer) 13
127.0.0.1:6379> HINCRBY myhash age 2 # 自增2,为15
(integer) 15
# hash中没有decrby,如要想要自减的话,就自增为负的
127.0.0.1:6379> HINCRBY myhash age -5 # 自减-5,即为减5 为10
(integer) 10
127.0.0.1:6379> hgetall myhash
1) "name"
2) "zhangsan"
3) "password"
4) "123"
5) "age"
6) "10"
7.hsetnx key field value 如果不存在,则会新添加,存在,会添加失败
127.0.0.1:6379> HSETNX myhash name lisi
(integer) 0
127.0.0.1:6379> hgetall myhash
1) "name"
2) "zhangsan"
3) "password"
4) "123"
5) "age"
6) "10"
127.0.0.1:6379> HSETNX myhash sex 1
(integer) 1
127.0.0.1:6379> hgetall myhash
1) "name"
2) "zhangsan"
3) "password"
4) "123"
5) "age"
6) "10"
7) "sex"
8) "1"
5.5 Zset(有序集合 sortedSet)
可以设置一个分数,来排序
1.默认排序为升序
127.0.0.1:6379> zadd salary 1000 zhangsan
(integer) 1
127.0.0.1:6379> zadd salary 2000 lisi
(integer) 1
127.0.0.1:6379> zadd salary 500 yasuo
(integer) 1
127.0.0.1:6379> zadd salary 3000 lesee
(integer) 1
127.0.0.1:6379> zrange salary 0 -1 # 默认不显示分数
1) "yasuo"
2) "zhangsan"
3) "lisi"
4) "lesee"
127.0.0.1:6379> zrange salary 0 -1 withscores # 显示分数
1) "yasuo"
2) "500"
3) "zhangsan"
4) "1000"
5) "lisi"
6) "2000"
7) "lesee"
8) "3000"
2.指定一个范围的排序(升序)
# zrangebyscore -inf 负无穷 +inf 正无穷
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf withscores
1) "yasuo"
2) "500"
3) "zhangsan"
4) "1000"
5) "lisi"
6) "2000"
7) "lesee"
8) "3000"
# 从负无穷到2300升序排序
127.0.0.1:6379> ZRANGEBYSCORE salary -inf 2300 withscores
1) "yasuo"
2) "500"
3) "zhangsan"
4) "1000"
5) "lisi"
6) "2000"
3.指定一个范围的排序(降序) zrevrange
127.0.0.1:6379> ZREVRANGE salary 0 -1 withscores # 降序排列
1) "lesee"
2) "3000"
3) "lisi"
4) "2000"
5) "zhangsan"
6) "1000"
7) "yasuo"
8) "500"
# 指定范围降序排列 正无穷 到500
127.0.0.1:6379> ZREVRANGEBYSCORE salary +inf 500 withscores
1) "lesee"
2) "3000"
3) "lisi"
4) "2000"
5) "zhangsan"
6) "1000"
7) "yasuo"
8) "500"
4.移除指定的元素 zrem
127.0.0.1:6379> zrem salary yasuo
(integer) 1
127.0.0.1:6379> ZRANGE salary 0 -1 withscores
1) "zhangsan"
2) "1000"
3) "lisi"
4) "2000"
5) "lesee"
6) "3000"
5.集中的元素个数
127.0.0.1:6379> zcard salary
(integer) 3
6.集合中的范围内元素个数
127.0.0.1:6379> ZCOUNT salary 500 2000
(integer) 2
127.0.0.1:6379> ZCOUNT salary 500 3000
(integer) 3
6. geospatial地理位置
- 有效的经度从-180度到180度。
- 有效的纬度从-85.05112878度到85.05112878度
6.1 geoadd与geopos
添加经纬度和城市的名称 geopos 获取城市的经纬度
# 集合名为china:city
127.0.0.1:6379> GEOADD china:city 116.405285 39.904989 beijing
(integer) 1
127.0.0.1:6379> GEOADD china:city 121.472644 31.22337 shanghai
(integer) 1
127.0.0.1:6379> GEOADD china:city 113.280637 23.125178 guangzhou
(integer) 1
127.0.0.1:6379> GEOADD china:city 114.085947 22.547 shenzhen
(integer) 1
127.0.0.1:6379> keys *
1) "salary"
2) "china:city"
# 北京的经纬度
127.0.0.1:6379> GEOPOS china:city beijing
1) 1) "116.40528291463851929"
2) "39.9049884229125027"
# 上海的经纬度
127.0.0.1:6379> GEOPOS china:city shanghai
1) 1) "121.47264629602432251"
2) "31.22337074392616074"
# 广州的经纬度
127.0.0.1:6379> GEOPOS china:city guangzhou
1) 1) "113.28063815832138062"
2) "23.12517743834835215"
# 深圳的经纬度
127.0.0.1:6379> GEOPOS china:city shenzhen
1) 1) "114.08594459295272827"
2) "22.54699993773966327"
6.2 geodist
查看两地之间的距离,单位有:
- m 米
- km 千米
- mi 英里
- ft 英尺
127.0.0.1:6379> GEODIST china:city beijing shanghai km # 北京到上海的距离
"1068.4450"
127.0.0.1:6379> GEODIST china:city guangzhou shenzhen km # 广州到深圳的距离
"104.6426"
6.3 georadius
以给定的经纬度为中心, 返回键包含的位置元素当中, 与中心的距离不超过给定最大距离的所有位置元素
- **WITHDIST:**在返回位置元素的同时, 将位置元素与中心之间的距离也一并返回。 距离的单位和用户给定的范围单位保持一致
- **WITHCOORD:**将位置元素的经度和维度也一并返回
- **WITHHASH:**以 52 位有符号整数的形式, 返回位置元素经过原始 geohash 编码的有序集合分值。 这个选项主要用于底层应用或者调试, 实际中的作用并不大
127.0.0.1:6379> GEORADIUS china:city 116 23 1000 km # 以110经度,23纬度 1000km范围内查询所有的城市
1) "shenzhen"
2) "guangzhou"
127.0.0.1:6379> GEORADIUS china:city 116 23 2000 km # 在2000km范围内查询所有的城市
1) "shenzhen"
2) "guangzhou"
3) "shanghai"
4) "beijing"
# withdist 返回与中心位置的直线距离
127.0.0.1:6379> georadius china:city 116 23 2000 km withdist
1) 1) "shenzhen"
2) "202.6579"
2) 1) "guangzhou"
2) "278.6348"
3) 1) "shanghai"
2) "1062.6443"
4) 1) "beijing"
2) "1880.6664"
# withcoord 返回当前中心范围内城市的经纬度
127.0.0.1:6379> georadius china:city 116 23 2000 km withcoord
1) 1) "shenzhen"
2) 1) "114.08594459295272827"
2) "22.54699993773966327"
2) 1) "guangzhou"
2) 1) "113.28063815832138062"
2) "23.12517743834835215"
3) 1) "shanghai"
2) 1) "121.47264629602432251"
2) "31.22337074392616074"
4) 1) "beijing"
2) 1) "116.40528291463851929"
2) "39.9049884229125027"
# 同时返回与中心的距离和经纬度
127.0.0.1:6379> georadius china:city 116 23 2000 km withdist withcoord
1) 1) "shenzhen"
2) "202.6579"
3) 1) "114.08594459295272827"
2) "22.54699993773966327"
2) 1) "guangzhou"
2) "278.6348"
3) 1) "113.28063815832138062"
2) "23.12517743834835215"
3) 1) "shanghai"
2) "1062.6443"
3) 1) "121.47264629602432251"
2) "31.22337074392616074"
4) 1) "beijing"
2) "1880.6664"
3) 1) "116.40528291463851929"
2) "39.9049884229125027"
# 限定符合条件的城市数量,只显示2个
127.0.0.1:6379> georadius china:city 116 23 2000 km withdist withcoord count 2
1) 1) "shenzhen"
2) "202.6579"
3) 1) "114.08594459295272827"
2) "22.54699993773966327"
2) 1) "guangzhou"
2) "278.6348"
3) 1) "113.28063815832138062"
2) "23.12517743834835215"
6.4 georadiusbymember
这个命令和 GEORADIUS
命令一样, 都可以找出位于指定范围内的元素, 但是 GEORADIUSBYMEMBER
的中心点是由给定的位置元素决定的, 而不是像GEORADIUS
那样, 使用输入的经度和纬度来决定中心点来指定成员的位置被用作查询的中心
127.0.0.1:6379> georadiusbymember china:city beijing 1500 km # 以北京为中心点,查询方圆1500km内的城市
1) "shanghai"
2) "beijing"
127.0.0.1:6379> georadiusbymember china:city shanghai 1800 km # 以上海为中心点,查询1800km范围内的城市
1) "shenzhen"
2) "guangzhou"
3) "shanghai"
4) "beijing"
6.5 geohash
返回一个或多个位置元素的geohash表示
通常使用表示位置的元素使用不同的技术,使用Geohash位置52点整数编码。由于编码和解码过程中所使用的初始最小和最大坐标不同,编码的编码也不同于标准。此命令返回一个标准的Geohash
127.0.0.1:6379> GEOHASH china:city beijing
1) "wx4g0b7xrt0"
127.0.0.1:6379> GEOHASH china:city shanghai
1) "wtw3shj9mf0"
127.0.0.1:6379> GEOHASH china:city guangzhou
1) "ws0e9cb3yj0"
127.0.0.1:6379> GEOHASH china:city shenzhen
1) "ws10k0dcg10"
127.0.0.1:6379> GEOHASH china:city beijing shanghai
1) "wx4g0b7xrt0"
2) "wtw3shj9mf0"
7. hyperloglog
可以统计UV任务,具有0.81%的容错率
127.0.0.1:6379> PFADD myset 1 2 3 4 5 6 # 在myset种添加6个元素
(integer) 1
127.0.0.1:6379> PFCOUNT myset # 获取元素的个数,共6个
(integer) 6
127.0.0.1:6379> PFADD myset1 1 3 5 7 8 9 # 在myset1种添加6个元素
(integer) 1
127.0.0.1:6379> PFCOUNT myset1 # 元素个数也为6个
(integer) 6
127.0.0.1:6379> PFMERGE myset myset1 # 将两个集合种的元素合并,并去除重复的元素,去除1,3,5,
# 合并进myset中,为1,2,3,4,5,6,7,8,9
OK
127.0.0.1:6379> PFCOUNT myset
(integer) 9
# 将两个集合合并为一个新的集合
127.0.0.1:6379> PFADD myset a b c # 集合中为 a b c
(integer) 1
127.0.0.1:6379> PFADD myset1 c d e # 集合中为 c d e
(integer) 1
127.0.0.1:6379> PFMERGE newSet myset myset1 # 合并为新的集合newSet中,去重(c),共5个,为a b c d e
OK
127.0.0.1:6379> PFCOUNT newSet
(integer) 5
8. Bitmap 位图
位存储
统计用户信息,登录,未登录,打卡,未打卡,具有两个状态的,都可以使用Bitmaps
Bitmap 操作二进制位来进行记录,就只有0和1两个状态
127.0.0.1:6379> SETBIT day 1 1 # 模拟周一 1表示打卡 0 表示未打卡
(integer) 0
127.0.0.1:6379> SETBIT day 2 0
(integer) 0
127.0.0.1:6379> SETBIT day 3 0
(integer) 0
127.0.0.1:6379> GETBIT day 1 # 获取周一是否打卡,1打卡了 0未打卡
(integer) 1
127.0.0.1:6379> GETBIT day 2
(integer) 0
127.0.0.1:6379> GETBIT day 3
(integer) 0
9. 事务
Redis事务的本质:一组命令的集合,一个事务中所有的命令都会被序列化,在事务执行的过程中,会按照顺序执行!
一次性、顺序性、排他性执行一些命令!
redis事务没有隔离级别的概念
所有的命令在事务中,并没有直接被执行,只有发起执行命令的时候才会执行 Exec
redis单条命令是原子性的,但事务不保证原子性
redis事务:
- 开启事务(multi)
- 命令入队(一些命令)
- 执行事务(Exec)
正常执行事务
127.0.0.1:6379> multi # 开启事务
OK
# 命令入队
127.0.0.1:6379> set name zhangsan
QUEUED
127.0.0.1:6379> set age 18
QUEUED
127.0.0.1:6379> get name
QUEUED
127.0.0.1:6379> get age
QUEUED
127.0.0.1:6379> exec # 执行事务
1) OK
2) OK
3) "zhangsan"
4) "18"
编译型异常(命令行有错误),事务中的所有命令都不会被执行
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> 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> get k1
QUEUED
127.0.0.1:6379> exec
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get k1 # 所以在这里不能获取到值
(nil)
127.0.0.1:6379> get k4 # 所以在这里不能获取到值
(nil)
运行时异常,命令语法没有错误,当命令执行到某一行的时候,这一行会出现异常,但不会影响其他命令,其他的会正常执行
127.0.0.1:6379> set name zs
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> incr name # 对一个字符串不能进行加减操作,此命令在运行时会出错,不会影响其他命令,其他命令正常执行
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> get name
QUEUED
127.0.0.1:6379> get k1
QUEUED
127.0.0.1:6379> get k2
QUEUED
127.0.0.1:6379> exec
1) OK
2) (error) ERR value is not an integer or out of range # 只有此命令失败,其他命令正常执行
3) OK
4) "zs"
5) "v1"
6) "v2"
放弃事务(discard)
127.0.0.1:6379> multi
OK
127.0.0.1:6379> discard
OK
10. 乐观锁、悲观锁
悲观锁:
- 很悲观,认为任何时候都会出问题,所以无论做什么都会加锁
乐观锁:
-
很乐观,认为什么时候都不会出问题,所以不会上锁。更新数据的时候先进行判断,在此期间这条数据是否被修改了
-
获取version
-
更新的时候比较version
测试监视
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 10 # 花了10块钱,钱包减10块
QUEUED
127.0.0.1:6379> INCRBY out 10 # 花了10块
QUEUED
127.0.0.1:6379> exec
1) (integer) 90 # 剩余90
2) (integer) 10 # 支出10
测试多线程修改值,使用watch可以当作redis乐观锁操作
127.0.0.1:6379> set money 100
OK
127.0.0.1:6379> set out 0
OK
127.0.0.1:6379> get money
"100"
127.0.0.1:6379> get out
"0"
127.0.0.1:6379>
127.0.0.1:6379> watch money # 监视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 # 在事务执行之前,下面的线程插队修改了money的值,导致执行失败,为nil
(nil)
其他线程插队修改了值
127.0.0.1:6379> get money # money初始值为100
"100"
127.0.0.1:6379> get out
"0"
127.0.0.1:6379> set money 50 # 修改了money值为50
OK
127.0.0.1:6379> get money
"50"
如果发现事务执行失败,就先解锁unwatch
,再去监视watch,进而执行其他操作;如果没有使用watch监视的话,其他线程修改会对本次的事务操作产生影响,会执行成功,而不是为nil
11. redis持久化
有RDB和AOF两种方式
11.1 RDB(Redis DataBase)
优点:
- 适合大规模的数据恢复
- 对数据的完整性要求不高
缺点:
- 需要一定的时间间隔进行操作,如果redis意外宕机了,最后修改的数据就丢失了
- fork进程的时候,会占用一定的内存空间
11.2 AOF(Append Only File)
优点:
- 每一次修改都同步,文件的完整会更加完好
- 每秒同步一次,可能会丢失一秒的数据
- 不执行sync,这时自己同步数据,速度最快
缺点:
- 相对于数据文件的大小来说,AOF远远大于RDB,修复的速度也比RDB慢
- AOF运行效率也比RDB慢,redis默认持久化方式为RDB
扩展:
1、RDB持久化方式能够在指定的时间间隔对数据进行快照存储
2、AOF持久化方式记录每次对服务器写的操作,当服务器重启的时候回重新执行这些命令来恢复原始的数据,AOF命令以Redis协议追加保存每次写的操作到文件末尾,Redis还能对AOF文件进行后台重写,使得AOF文件的体积不至于过大
3、只做缓存,如果你只希望你的数据在服务器运行的时候存在,你也可以不使用任何持久化
4、同时开启两种持久化方式
- 在这种情况下,当redis重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF文件保存的数据要比RDB文件保存的数据集要完整
- RDB的数据不实时,同时使用两者时度完全重启也只会找AOF文件,建议不要只使用AOF,因为RDB更适合用于备份数据库(AOF在不断变化不好备份),快速重启,而且不会有AOF可能潜在的Bug,留着作为一个万一的手段。
12. redis订阅发布
12.1 基本命令
- PSUBSCRIBE pattern[pattern…]:订阅一个或多个符合给定模式的频道
- PUBSUB:查看订阅与发布系统的状态
- PUBLISH channel message:将信息发送到指定的频道
- PUNSUBSCRIBE pattern[pattern…]:退订所有给定模式的频道
- SUBSCRIBE channel [channel…]:订阅给定的一个或多个频道的信息
- UNSUBSCRIBE [channel [channel…]]:指退订给定的频道
测试
# 订阅端
127.0.0.1:6379> SUBSCRIBE yogurt # 订阅一个名为yogurt的频道
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "yogurt"
3) (integer) 1
1) "message" # 消息
2) "yogurt" # 哪个频道的消息
3) "hello" # 消息的具体内容
1) "message"
2) "yogurt"
3) "hi"
# 发布端
127.0.0.1:6379> PUBLISH yogurt hello # 发布者发送消息到频道,向yogurt频道发送了hello
(integer) 1
127.0.0.1:6379> PUBLISH yogurt hi # 向yogurt频道发送了hi
(integer) 1
13. redis主从复制
13.1 查看状态
info replication
13.2 从机连接到主机
slaveof host port # 主机的host地址和端口
从机连接好主机,主机中保存的数据从机都可以拿到,如:set name zhangsan,所有的从机都可以通过get name得到值,当主机断开连接时,从机依然连接到了主机,当主机重新连接回来,从机依然可以拿到主机保存的数据
如果从机断开连接(是使用命令行连接到的主句,而不是通过配置文件连接到的主机 ,从机重新连接后,会自动变回主机,这样拿不到值,当将此主机变为从机时,就可以拿到主机中的值)
13.3 复制原理
slave启动成功连接到master后会发送一个sync同步命令
Master接到命令,启动后台的存盘进程,同时收集所有接收到的用于修改数据集的命令,在后台进程执行完毕后,master将传送整个数据文件到slave,并完成一次完全同步
**全量复制:**slave服务在接收到数据库文件数据后,将其存盘并加载到内存中
**增量复制:**master继续将新的所有收集到的修改命令依次传给slave,完成同步
只要时重新连接master,一次完全同步(全量复制)会被自动执行
14. 哨兵模式
14.1 哨兵模式的配置文件
进入 yconfig目录下,新建sentinel.conf文件,进行配置
[root@yogurt ~]# cd /usr/local/bin
[root@yogurt bin]# ls
dump.rdb redis-check-aof redis-cli redis-server
redis-benchmark redis-check-rdb redis-sentinel yconfig
sentinel.conf配置如下
# sentinel monitor 被监视的名称 host port 1
sentinel monitor myredis 127.0.0.1 6379 1
数字1表示主机挂了,slave投票看让谁接替成为主机,票数最多的,成为主机
14.2 启动哨兵模式
[root@yogurt bin]# redis-sentinel yconfig/sentinel.conf
哨兵模式的好处是:如果Master(主机)连接断开了,这时就会从 从机中投票选择一个作为主机(底层投票算法)
如果主机重新连接回来了,只能当作从机,主机还是投票选出来的那个
15. redis缓存穿透击穿和雪崩
15.1 缓存穿透
缓存穿透,用户想要查询一个数据,redis缓存中没有,于是像持久层数据库进行查询,发现也没有,本次查询失败。当用户很多的时候,都去查询,缓存中都没有,都去请求数据库进行查询,这给数据库造成了很大的压力,这相当于缓存穿透
解决方案:
布隆过滤器
是一种数据结构,对所有可能查询的参数以hash形式存储,在控制层先进行校验,不符合则丢弃,从而避免了对底层存储系统的压力
缓存空对象
当从数据库中没有查询到时,将返回的空对象也缓存起来,同时设置一个过期时间,之后再访问这个数据则直接从缓存中取,保护了后端数据源
产生的问题:
1、如果空值也被缓存起来,这就需要缓存来使用更多的空间来存储更多的键,因为这当中可能存在很多空值的键
2、即使对空值设置了过期时间,还是会存在 再缓存层和数据层会有一段时间的数据不一致,这对于需要保持一致性的业务会产生影响
15.2 缓存击穿
缓存击穿,是指一个key非常热点,在不停的扛着高并发,高并发集中对这一个点集中访问,当这个key在失效的瞬间,持续的高并发就穿破缓存,直接请求数据库
当某个key在过期的瞬间,有大量的并发请求访问,这类数据一般是热点数据,由于缓存过期,会访问数据库来查询最新数据,并且回写缓存,会导致数据库瞬间压力过大
解决方案
设置热点数据永不过期从缓存层面来看,没有设置过期时间,所以不会出现热点key过期后产生的问题
加互斥锁使用分布式锁,保证每个key同时只有一个线程去查询后端服务,其他线程没有获得分布式锁的权限,因此只需要等待即可。这种方式将高并发的压力转移到了分布式锁,一次对分布式锁的考验很大
15.3 缓存雪崩
概念
缓存雪崩,是指在某一时间段,缓存集中过期失效
产生雪崩的原因有:当双十二零点马上就要来到,一波优惠商品即将开卖,这些商品的时间集中放到了缓存,假设缓存一个小时,凌晨一点钟,这些商品的缓存全部过期,在缓存过期之后,对这些商品的访问查询则直接去从数据库查询,对于数据库而言,就会产生周期性的压力波峰,所有的请求都会到达存储层,存储层的调用量会暴涨,造成存储层出现问题
解决方案
redis高可用
redis服务器可能出现问题,那就多设几台redis服务器,当某一个出现问题后,其他的还可以使用(搭建集群)
限流降级
在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某一个key只允许一个线程查询数据和写缓存,其他线程等待
数据预热
数据预热的含义就是在正式部署之前,可以先把可能要访问的数据先去访问一遍,这些有可能要大量访问的数据就会加载到缓存中,在即将发生高并发前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间的尽量均匀