Redis总结
1、Redis:数据库、缓存、消息中间件MQ
2、Redis默认16个数据库,Redis默认是单线程的,在内存上操作
3、单线程为什么效率高:
因为Redis是基于内存操作的,cpu不是redis的瓶颈,内存和带宽才是,如果使用多线程,会涉及到线程的上下文切换,耗费时间
4、Redis的基本类型
String: set
List列表:可以做消息队列,左进右出;栈,左进左出 lpush
set集合:不重复,可以用来计数 sadd
hash散列:适合存用户信息 hset
Zset有序集合:排行榜应用 zadd
5、Redis的特殊类型
geospatial(地理位置):朋友的定位,附近的人,打车距离的计算 geoadd
hyperloglog基数统计:网站UV计算访问量 使用set会存储大量的用户id,hyperloglog只是计数,不存储用户ID,节省大量空间 pfadd
bitmaps:数据结构 利用二进制记录事务的状态 打卡 记录每天的上班状态 0上班 1没上班 setbit
6、悲观锁:
很悲观,无论什么时候,都会出问题,无论做什么都要加锁
7、乐观锁:
很乐观,认为不会出问题,所以不会上锁!更新数据的时候去判断一下,在此期间是否有人修改过数据,获取version,更新的时候比较version
8、Redis事务:
redis单条命令是保持原子性的,但是事务不保证原子性
编译型异常:命令有错,事务中所有的命令都不会被执行
运行时异常:事务队列中存在语法错误,那么执行命令时其他命令可以正常执行,错误命令会返回异常
9、事务的四大特性:
原子性:对数据库操作的最小单元
一致性:事务中的增删改查,同时成功或失败
隔离性:事务之间互不影响
持久性:事务成功之后,数据持久到数据库中
10、Redis内存达到上限的六大策略:
volatile-lru:只对设置了过期时间的key进行LRU(默认值) LRU:近期很少使用
allkeys-lru : 删除lru算法的key
volatile-random:随机删除即将过期key
allkeys-random:随机删除
volatile-ttl : 删除即将过期的
noeviction : 永不过期,返回错误
11、Redis持久化
rdb:
默认是rdb,
Redis会单独的fork一个子进程来进行持久化,会先将数据入到一个临时文件中,待持久化过程都结束了,在用这个临时文件替换上次持久化好的文件。
整个过程中,主进程是不进行任何IO操作的,这就确保了极高的性能。
如果需要进行大规模的数据的恢复,却对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效。
RDB的缺点是最后一次持久化后的数据可能丢失。
aof:
将我们的所有命令都记录下来,恢复的时候把这个文件全部执行一遍
每一次修改都同步,文件的完整性会更好
12、Redis订阅功能
13、Redis主从复制:
数据的复制都是单向的,都是从主节点到从节点
Master以写为主,Slave以读为主,80%都是读操作
复制原理:
全量复制:只要重新连到master主机,就会进行一次全量复制
增量复制:当主从机再正常状态,主机写操作,会进行增量复制到从机,保证主从机数据的一致性
哨兵模式:
当主机宕机之后,自动选择主机的模式
原理:
假设主服务器宕机,哨兵1先检测到这个结果,系统并不会马上进行failover【故障转移】过程,仅仅是哨兵1主观的
认为主服务器不可用,这个现象称为**主观下线**
当后面的哨兵也检测到主服务器宕机时,那么哨兵就会进行一次投票,投票结果由一个哨兵发起,进行failover操作;
切换成功后,就会通过发布订阅者模式,让各个哨兵把自己监控的从服务器切换成主机,这个过程叫做**客观下线**
14、缓存穿透和击穿(针对于某一个key的访问)
缓存穿透:当用户去请求数据的时候,会先从缓存中寻找,如果没有,则会从数据库中寻找,如果有人恶意攻击,
绕过缓存,去大量的请求数据库服务器,就会对数据库造成很大的压力,导致宕机,这种现象成为`缓存穿透`
缓存击穿:当缓存中的一条数据,被请求了很多次,这条数据设置的过期时间60秒,当数据过期还没有重新set的时候,
大量的用户请求就会绕过缓存,直接请求数据库服务器,也可能会造成数据库服务器的宕机,这种现象称为`缓存击穿`
解决方案:
设置热点key永不过期 (比较容易出问题,不可能永远不过期)
加互斥锁,当请求绕过缓存,请求数据库服务器的时候,设置线程锁,只允许一个线程去请求数据库,其他线程进行等待
15、缓存雪崩(大量的key过期,或者宕机)
当缓存中大量的key同时过期,或者redis宕机
解决方案:
redis的高可用,多设置几台redis服务器
限流降级,在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量,比如某一个key只允许一个线程去查询数据,写缓存,其他线程等待
数据预热,把可能的数据预先访问一遍,这样大部分的数据都会预设到缓存中,设置不同的过期时间,尽量均匀
Nosql概述
为什么要用Nosql?
大数据时代,传统的数据库已经很难应付大量的数据
大数据的IO压力下,表几乎无法更大,如果有几亿条数据,增加一个列,就意味着要修改上亿条数据。
为什么要用Nosql?
用户的个人信息、社交网络、地理位置。用户自己产生的数据,用户日志等爆发式增长!
这时候我们就需要使用Nosql数据库,Nosql数据库可以很好的处理以上情况
Nosql 不止是sql
泛指非关系型数据库
关系型数据库:表格 行 列
Nosql的特点
1. 方便扩展(数据之间没有关系,很好扩展)
2. 大数据量高性能(Redis一秒写8万次,读11万次,Nosql的缓存记录级,是一种细粒度的缓存,性能会比较高)
3. 数据类型是多样型的(不需要事先设计数据库,如果数据量十分庞大,就没有办法很好的设计数据库了)
4. 不仅仅是数据
5. 没有固定的查询语言
6. 键值对存储,列存储,文档存储,图形数据库(社交关系拓扑图)
7. 最终一致性,过程允许有误差
8. CAP定理和BASE(异地多活)
9. 高性能,高可用,高可扩
大数据时代的3V:主要是描述问题的
- 海量Volume
- 多样Variety
- 实时Velocity
大数据时代的3高:主要是对程序的要求
- 高并发
- 高可扩
- 高性能
真正的公司实践一定是Nosql+RDBMS(关系型数据库)一起使用
NoSQL的四大分类
- 键值对
新浪:Redis
美团:Redis+Tair
阿里、百度:Redis+memeCache
- 文档型数据库(bson和json格式一样)
MongoDB(必须掌握)
是基于分布式文件存储的数据库,主要用来处理大量的文档。
MongoDB是介于关系型数据库和非关系型数据库的中间的产品
MongoDB是非关系型数据库中功能最丰富的,最像关系型数据库的数据库
- 列存储的数据库
HBase
分布式文件系统
- 图形关系数据库
存储社交网络拓扑图,存的不是图形,存的是社交网络关系、广告推荐等
Neo4j、InfoGrid
Redis
远程字典服务Remote Dictionary Server
redis能干嘛(数据库、缓存、消息中间件MQ)
- 内存存储、持久化,内存是断电即失的,持久化很重要
- 效率高,可以用于告诉缓存
- 发布订阅系统,消息队列
- 地图信息分析
- 计时器、计数器(浏览量)
特性:
- 多样的数据类型
- 持久化
- 集群
- 事务
基于linux学习redis
windows下的redis有兼容性问题,不建议使用
windows下安装:
Linux下安装redis
-
官网下载安装包
-
解压安装包 tar -zxvf
-
基本的环境安装
c++环境(redis是用c++写的):yum install gcc-c++ 查看gcc版本: gcc -v redis目录下安装执行安装命令 :make
-
默认安装路径为/usr/local/bin下
-
拷贝redis.conf文件到bin目录下,方便我们修改redis的配置
-
redis默认不是后台启动的,我们需要修改配置文件
-
启动redis
# redis-server redis.conf 启动redis-server服务 以redis.conf配置文件启动
-
启动客户端
# redis-cli -p 6379
-
关闭redis
# shutdown # exit
-
后期我们会使用单机启动redis集群测试!
redis-benchmark性能测试工具
redis-benchmark是一个压力测试工具,管方自带的性能测试工具
可选参数如下:
简单测试下:
# 测试:100个并发连接 每个并发100000个请求
在linux中输入如下命令
# redis-benchmark -h localhost -p 6379 -c 100 -n 100000
redis的基础知识
1、redis默认有16个数据库
2、默认使用的是第0个数据库
3、可以使用select 数字 来切换数据库
127.0.0.1:6379> select 3 #切换数据库
OK
127.0.0.1:6379[3]> DBSIZE #查看数据库大小
(integer) 0
127.0.0.1:6379[3]>
4、redis是单线程的
原因:redis是基于内存操作的,CPU不是redis的瓶颈,redis的瓶颈时内存和带宽,既然可以使用单线程来
实现,就使用单线程了
redis时c语言写的,官方提供的数据为100000+的QPS,完全不比同key-value的MemeCache差
为什么单线程还那么快?
误区1:高性能的服务一定时多线程的?
误区2:多线程(CPU上下文切换,每切换一次需要1500纳秒)一定比单线程效率高?
核心:redis是将所有的数据全部放在内存中的,对于多线程,CPU会上下文切换耗费时间,对于内存系
统来说,没有上下文切换效率就是最高的
redis的基础语法
Redis-key
select 3 #切换数据库
set name wanghan #set key为name 值为王罕的键值对
get name #获取key为name 的键值对
keys * #获取所有的key
flushdb #清空数据库
flushall #清空全部数据库
exists name #是否存在一个key名为name
move name #移除这个key
expire name 10 #设置这个key 10秒过期
ttl name #查看这个key的剩余寿命 值为-2时代表已过期
type name #查看这个key 的值类型
五大基本类型:
String:字符串
List:列表
Set:集合
Hashes:散列
Zset: 有序集合
三种特殊数据类型:
geospatial
hyperloglog
bitmaps
五大基本类型:
1、String
value除了字符串,也可以是数字
#####################################
append key " " #给key的值追加字符串 如果这个key不存在,则新建key-value
strlen key #key值得字符串长度
#####################################
127.0.0.1:6379> set cout 4
OK
127.0.0.1:6379> incr cout 递增
(integer) 5
127.0.0.1:6379> decr cout 递减
(integer) 4
#步长:
127.0.0.1:6379> incrby count 10 步长10递增
(integer) 14
127.0.0.1:6379> decrby count 5 步长5递减
(integer) 9
127.0.0.1:6379>
##################################
#截取字符串范围
127.0.0.1:6379> get name
"wanghan ai chenmeng"
127.0.0.1:6379> getrange name 2 5 #截取字符串范围
"ngha"
127.0.0.1:6379> getrange name 0 -1 # 0 -1代表全部
"wanghan ai chenmeng"
127.0.0.1:6379>
##################################
#替换
127.0.0.1:6379> get name
"wanghan ai chenmeng"
127.0.0.1:6379> setrange name 3 xxx #替换指定位置得字符串
(integer) 19
127.0.0.1:6379> get name
"wanxxxn ai chenmeng"
#setex 如果存在 setex key 30 "hello" # 如果存在,设置值为hello 30秒过期
#setnx 如果不存在 setnx key "hello" # 如果不存在,设置key的值为hello,如果存在,则不修改key 的值(常用在分布式锁中)
##################################
#批量设置
#mset key1 v1 key2 v2 key3 v3
#msetnx key1 v1 key4 v4 #设置失败,因为存在key1,所以整条信息set失败,是原子性操作,一起成功一起失败
#################################
#对象
可以set对象,但还需要解析
这里有一个key的巧妙的设计:user:{id}{field},如此设计在redis中完全ok 的
127.0.0.1:6379> mset user:1:name wanghan user:1:age 23
OK
127.0.0.1:6379> mget user:1
1) (nil)
127.0.0.1:6379> mget user:1:name user:1:age
1) "wanghan"
2) "23"
127.0.0.1:6379>
#################################
#先get再set
#getset
127.0.0.1:6379> getset db redis #如果没有值,则返回null,并set值
(nil)
127.0.0.1:6379> get db
"redis"
127.0.0.1:6379> getset db mongodb #如果有值,则获取值,再set新值
"redis"
2、list
在list中可以完成栈、队列、阻塞队列!
127.0.0.1:6379> lpush list one 左push l代表left
(integer) 1
127.0.0.1:6379> lpush list two
(integer) 2
127.0.0.1:6379> lpush list three
(integer) 3
127.0.0.1:6379> lrange list 0 -1 查询从左往右查 l代表list
1) "three"
2) "two"
3) "one"
127.0.0.1:6379> rpush list aaa 右push,注意查询出来,该值的位置 r代表right
(integer) 4
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "two"
3) "one"
4) "aaa"
##################################
弹出
127.0.0.1:6379> lpop list 左弹出第一个元素
"three"
127.0.0.1:6379> rpop list 右弹出右边第一个元素(最后一个元素)
"aaa"
127.0.0.1:6379> lrange list 0 -1
1) "two"
2) "one"
##################################
lindex 下表取值
127.0.0.1:6379> lindex list 0 通过下标获取列表中的某一个值
"two"
##################################
llen list长度
127.0.0.1:6379> llen list
(integer) 2
#################################
移除某一个值
lrem
127.0.0.1:6379> lrem list 2 two 移除list中 2 个值为two的值
(integer) 1
####################################
ltrim截取
127.0.0.1:6379> lrange mylist 0 -1
1) "hello1"
2) "hello2"
3) "hello3"
4) "hello4"
127.0.0.1:6379> ltrim mylist 1 2 通过下标截取list元素
OK
127.0.0.1:6379> lrange mylist 0 -1
1) "hello2"
2) "hello3"
127.0.0.1:6379>
########################################
rpoplpush 从一个list取最后一个值,左push到另一个list中
127.0.0.1:6379> rpoplpush mylist yourlist 从一个资源取出,存到另一个资源
"hello2"
127.0.0.1:6379> lrange mylist 0 -1
1) "hello0"
2) "hello1"
127.0.0.1:6379> lrange yourlist 0 -1
1) "hello2"
127.0.0.1:6379>
######################################
lset 更新一个list某下标的值,如果list不存在,或者不存在下标,则报错
127.0.0.1:6379> lset mylist 1 changename 更新某list中某下标的值
OK
127.0.0.1:6379> lrange mylist 0 -1
1) "hello0"
2) "changename"
######################################
linsert 插入
127.0.0.1:6379> linsert mylist before hello0 hello-1 将某个具体的值插入到列表中某个元素的前面或后面
(integer) 3
127.0.0.1:6379> lrange mylist 0 -1
1) "hello-1"
2) "hello0"
3) "changename"
list小结:
- 实际上就是一个链表,before node after right、left都可以插入值
- 如果key不存在,则新建链表,如果存在,新增内容
- 如果移除所有的值,空链表也代表不存在
- 两边插入或改动值,效率最高;如果是操作中间元素,效率相对较低
消息队列(Lpush Rpop)
栈(Lpush Lpop)
3、Set(集合)
set中的值不能重复
新增
sadd myset "hello"
查看set
smembers myset
查看元素是否存在
smembers myset hello 如果存在返回1 不存在返回0
获取set中元素个数
scard myset
移除myset元素
srem myset hello
#########################################
set无序 不重复集合!抽随机
srandmember myset 随机抽取myset中的元素
srandmember myset 2 随机抽取myset中指定个数的元素
#########################################
随机的删除元素
spop myset
########################################
将集合中的某个元素移动到另一个集合
smove myset youset hello
########################################
差集、并集、交集 微博B站共同关注
sdiff myset youset 查看 myset集合中与youset不同的元素(理解重点在myset的差集)
sinter myset youset 查看两者的交集
sunion myset youset 查看两者并集
4、Hash(哈希)
map集合 key - < key-value> key-map
127.0.0.1:6379> hset myhash name wanghan 设置一个集合 放入x
(integer) 1
127.0.0.1:6379> hget myhash name
"wanghan"
127.0.0.1:6379> hmset myhash age 13 sex man #设置多个key-value
OK
127.0.0.1:6379> hmget myhash name age sex #同时获取多个值
1) "wanghan"
2) "13"
3) "man"
127.0.0.1:6379> hgetall myhash #获取全部的数据
1) "name"
2) "wanghan"
3) "age"
4) "13"
5) "sex"
6) "man"
#############################################################
删除某一个key-value
127.0.0.1:6379> hdel myhash name #删除hash指定的key,对应的value也就删除了
(integer) 1
#############################################################
获取hash的长度,有多少个键值对
hlen myhash
#############################################################
判断hash中的某个key是否存在
hexists myhash name
#############################################################
只获得key
hkeys myhash
#############################################################
只获得value
hvals myhash
#############################################################
给某个key的value值设置步长
127.0.0.1:6379> hset myhash num 1
(integer) 1
127.0.0.1:6379> hincrby myhash num 4 #key为num的值加4
(integer) 5
127.0.0.1:6379> hget myhash num
"5"
#############################################################
hsetnx myhash name wanghan #如果不存在则设置,如果存在则报错
hash更适合存用户信息,经常变动的信息,String适合存字符串
5、Zset(有序集合)
在set的基础上,增加了顺序标识
新增zset
127.0.0.1:6379> zadd myset 1 one 新增 按标识顺序
(integer) 1
127.0.0.1:6379> zadd myset 2 two 3 three
(integer) 2
127.0.0.1:6379> zrange myset 0 -1 查询
1) "one"
2) "two"
3) "three"
如果新增的元素,位置标识已经有了值,则这两个值按照字母排序排列
例如工资的排序
127.0.0.1:6379> zadd salary 5000 wanghan
(integer) 1
127.0.0.1:6379> zadd salary 2500 chenmeng
(integer) 1
127.0.0.1:6379> zadd salary 7000 wangjian
(integer) 1
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf
1) "chenmeng"
2) "wanghan"
3) "wangjian"
###################################################
移除元素
127.0.0.1:6379> zrem salary wangjian #移除元素
(integer) 1
查看多少个元素
127.0.0.1:6379> zcard salary
(integer) 2
获取指定成员之间的元素个数
127.0.0.1:6379> zcount myset 3 6
(integer) 5
其余的API可以去查管方文档
案例:
set排序 排行榜应用
三种特殊数据类型:
1、geospatial(地理位置)
朋友的定位,附近的人,打车距离的计算
两地之间的距离,方圆几里的人
只有6个命令
#geoadd:添加地理位置
#规则:两极无法直接添加,我们一般会下载城市数据,直接通过java程序导入
127.0.0.1:6379> geoadd China:city 113.88 22.55 深圳 #geoadd key 经度 纬度 位置名称
(integer) 1
127.0.0.1:6379> geoadd China:city 104.10 30.65 成都 113.27 23.15 广州
(integer) 2
#geopos:查询地理位置坐标
127.0.0.1:6379> geopos China:city 北京
1) 1) "116.23000055551528931"
2) "40.2200010338739844"
#geodist:两人之间的距离
单位:
- m 米
- km 千米
- mi 英里
- ft 英尺
127.0.0.1:6379> geodist China:city 北京 广州 km
"1918.8645"
#georadius:以给定的经纬度为中心,找出某一半径内的元素
#withdist :显示距离 withcoord:显示经纬度 cout 3:查找最多3个元素
#以110 30 经纬度为中心,查询出房源1000 km距离内的3个地理位置信息(距离信息,坐标信息)
127.0.0.1:6379> georadius China:city 110 30 1000 km withdist withcoord count 3
1) 1)"北京"
2) "340.8679"
3) 1) "106.54000014066696167"
2) "29.39999880018641676"
2) 1) "上海"
2) "570.9717"
3) 1) "104.09999996423721313"
2) "30.6499990746355806"
3) 1) "成都"
2) "828.2964"
3) 1) "113.27000051736831665"
2) "23.14999996266175941"
#georadiusbymember 找出指定元素周围多少距离的元素
127.0.0.1:6379> GEORADIUSBYMEMBER China:city 北京 1000 km
1) "成都"
2) "重庆"
#geohash 将二维的经纬度,转化为一维的字符串,如果两个字符串越相似,那么距离越近
127.0.0.1:6379> geohash China:city 北京 成都
1) "wx4sucu47r0"
2) "wm6n2gem1v0"
geo的底层实现原理其实就是Zset!我们可以使用Zset命令来操作geo
zrange China:city 0 -1 查看地图中全部元素
zrem China:city 北京 移除地图中北京的元素
2、hyperloglog基数统计
什么是基数:就是只不重复的元素个数
比如{1,2,3,4,5,1,2,4,},这个集合中有8个元素,但是基数为5,另外3个元素与其中的元素重复,不计算在个数中
传统的网站UV计算访问量会有set集合
set集合放入用户ID,多次访问,只是用户ID只会覆盖,可以用来计数
使用hyperloglog的优点:
在于内存的占用,2^64个元素,只需要12kb内存
不会像set那样,储存大量的用户ID,我们的目的是计数,而不是保存大量的用户ID
错误率在0.81% ,统计UV任务可以忽略不计
命令就3个
#pfadd 添加
#pfcount 计数key中元素个数
#pfmerge 新建key 并合并后面的n个key
127.0.0.1:6379> pfadd myset1 a b c d e f g
(integer) 1
127.0.0.1:6379> pfadd myset2 h i j k a b c
(integer) 1
127.0.0.1:6379> pfadd myset3 o p q r a b c
(integer) 1
127.0.0.1:6379> pfcount myset 3
(integer) 0
127.0.0.1:6379> pfcount myset3
(integer) 7
127.0.0.1:6379> pfmerge myset myset1 myset2 myset3
OK
127.0.0.1:6379> pfcount myset
(integer) 15
3、Bitmaps
Bitmaps 数据结构 利用二进制记录事务的状态 0 1
比如记录打卡天数
#setbit
#记录一个月中的的打卡记录 1-5号每天的打卡记录
127.0.0.1:6379> setbit work 1 0
(integer) 0
127.0.0.1:6379> setbit work 2 0
(integer) 0
127.0.0.1:6379> setbit work 3 1
(integer) 0
127.0.0.1:6379> setbit work 4 0
(integer) 0
127.0.0.1:6379> setbit work 5 1
(integer) 0
......
#getbite #获取某个记录
127.0.0.1:6379> getbit work 3 获取3号的打卡记录
(integer) 1
#bitcount 计算key中 全部元素值为1的元素个数
127.0.0.1:6379> bitcount work
(integer) 2
Redis的基本事务
redis事务本质:一组命令的集合,一个事务的所有命令都会被序列化,在事务的执行过程中,命令会按照顺序执行
一次性、顺序性、排他性
redis事务没有隔离级别的概念
所有命令在事务中并没有被直接执行,只有发起执行命令的时候才会执行
redis单条命令是保持原子性的,但是事务不保证原子性
redis的事务:
- 开启事务(multi)
- 命令入队()
- 执行事务(exec)
- 放弃事务(discard)
127.0.0.1:6379> multi #开启事务
OK
127.0.0.1:6379> set key1 v1 #命令入队 QUEUED
QUEUED
127.0.0.1:6379> set key2 v2
QUEUED
127.0.0.1:6379> set key3 v3
QUEUED
127.0.0.1:6379> get key2
QUEUED
127.0.0.1:6379> exec #执行事务
1) OK
2) OK
3) OK
4) "v2"
#discard 放弃事务
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set key1 v1
QUEUED
127.0.0.1:6379> set key4 v4
QUEUED
127.0.0.1:6379> discard #放弃事务
OK
127.0.0.1:6379> get key4 #命令未执行
(nil)
异常:
- 编译型异常:命令有错,事务中所有的命令都不会被执行
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> exec #执行事务报错,所有命令都不会被执行
(error) EXECABORT Transaction discarded because of previous errors.
- 运行时异常: 事务队列中存在语法错误,那么执行命令时其他命令可以正常执行,错误命令会返回异常
127.0.0.1:6379> set k1 "string"
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> incr k1 #对k1的值自增,应为值为字符串,所以语法会报错
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> get k2
QUEUED
127.0.0.1:6379> exec
1) (error) ERR value is not an integer or out of range #返回异常信息
2) OK #正常执行
3) "v2"
悲观锁:
- 很悲观,无论什么时候,都会出问题,无论做什么都要枷锁
乐观锁:
- 很乐观,认为不会出问题,所以不会上锁!更新数据的时候去判断一下,在此期间是否有人修改过数据
- 获取version
- 更新的时候比较version
Redis的监控测试
监控money 正常执行成功
127.0.0.1:6379> set money 100
OK
127.0.0.1:6379> set out 0
OK
127.0.0.1:6379> watch 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
模拟多线程,当一个客户端开启事务对对象进行操作,如果过程中其他客户端,对该对象进行了修改,则本客户端在提交事务的时候,会执行失败
127.0.0.1:6379> get money
"100"
127.0.0.1:6379> watch money
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> DECRBY money 20 #如果在事务列队期间,其他线程对money进行了修改,由100增加到200
QUEUED
127.0.0.1:6379> INCRBY out 20
QUEUED
127.0.0.1:6379> exec #则在执行事务的时候会不成功
(nil)
原理:监视money,当开启事务的时候,money值为100(version),当执行事务的时候,再去判断money的值,如果值不是100,则事务执行失败。
127.0.0.1:6379> unwatch #解锁
Jedis
我们要使用java来操作redis
什么是Jedis,是redis官方推荐的java连接开发工具,使用java操作redis的中间件!
- 导包
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.3.0</version>
</dependency>
- 编码测试
Jedis jedis = new Jedis("127.0.0.1", 6379);
//jedis的所有命令就是我们之前学习的所有指令
System.out.println(jedis.ping());
jedis.close();
常用API:就是redis的操作指令
五大数据类型
三大特殊数据类型
Jedis中操作事务
JSONObject jsonObject = new JSONObject();
jsonObject.put("a", "b");
String result = jsonObject.toJSONString();
Jedis jedis = new Jedis("127.0.0.1", 6379);
//开启事务
Transaction multi = jedis.multi();
try {
multi.set("k1", result);
} catch (Exception e) {
multi.discard();
}finally {
multi.exec();
System.out.println(jedis.get("k1"));
}
springboot整合redis
说明:在springboot2.x之后,原来使用jedis被替换为lettuce
jedis:采用的是直连,多线程操作不安全,如果想要避免不安全,需要使用jedis pool连接池 BIO模式
lettuce:底层采用netty,实例可以在多个线程内中共享,不存在线程不安全的情况,可以减少线程数量。 NIO模式
源码分析:
@Bean
@ConditionalOnMissingBean(name = "redisTemplate")//我们可以自定义一个redisTemplate来替代这个默认的
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
//默认的redisTemplate没有过多的设置,redis对象都需要序列化
//两个泛型都是object,我们使用后需要强转<String Object>类型
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean //由于String是redis中最常使用的类型,所以单独提出来一个bean
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
导包
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
配置redis
#springboot的所有配置类,都有一直自动配置类
#配置类都会绑定一个properties配置文件
#配置redis
spring:
redis:
host: 127.0.0.1
port: 6379
测试
使用RedisTemplate来操作redis
@Resource
RedisTemplate redisTemplate;
@Test
void contextLoads() {
//opsForValue操作字符串 类似String
redisTemplate.opsForValue().append("k1","v1");
//opsForList()操作list的
redisTemplate.opsForList().leftPop("k1");
....类似于redis的操作命令
}
获取redis的连接对象
//获取redis的连接对象
RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
connection.flushDb();
connection.flushAll();
自定义解决序列化问题(默认使用jdk序列化,修改后使用json序列化)
@Configuration
public class RedisConfig {
//固定的模板,公司开发中,拿过去用就可以
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
//为了开发方便,直接修改成<String Object>类型
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
//配置Json序列化
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper om = new ObjectMapper();// 需要导jackson的依赖
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
//String的序列化
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
//ke采用String的序列化方式
template.setKeySerializer(stringRedisSerializer);
template.setHashKeySerializer(stringRedisSerializer);
//value采用jackson的序列化方式
template.setValueSerializer(jackson2JsonRedisSerializer);
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}
RedisUtils工具类
实际工作中常用的api都会封装到一个工具类中
Redis的配置文件
解析redis.conf配置文件
- 单位,对大小写不敏感
# 1k => 1000 bytes
# 1kb => 1024 bytes
# 1m => 1000000 bytes
# 1mb => 1024*1024 bytes
# 1g => 1000000000 bytes
# 1gb => 1024*1024*1024 bytes
#
# units are case insensitive so 1GB 1Gb 1gB are all the same.
- 包含其他配置文件
################################## INCLUDES ###################################
# Include one or more other config files here. This is useful if you
# have a standard template that goes to all Redis servers but also need
# to customize a few per-server settings. Include files can include
# other files, so use this wisely.
#
# Notice option "include" won't be rewritten by command "CONFIG REWRITE"
# from admin or Redis Sentinel. Since Redis always uses the last processed
# line as value of a configuration directive, you'd better put includes
# at the beginning of this file to avoid overwriting config change at runtime.
#
# If instead you are interested in using includes to override configuration
# options, it is better to use include as the last line.
#
# include /path/to/local.conf
# include /path/to/other.conf
- 网络
#后台方式启动
bind 127.0.0.1 #绑定IP
protected-mode yes #开启保护模式
port 6379
- 通用配置
#后台方式启动
daemonize yes
pidfile /var/run/redis_6379.pid #如果以后台方式运行,我们就需要配置指定一个pid文件
loglevel notice #日志级别
logfile "" #日志的文件位置名
databases 16 #默认16个数据库
always-show-logo yes #是否在启动的时候展示logo
- 快照
#后台方式启动
持久化:在规定的时间内,执行了多少次操作,则会持久化到文件 .rdb .aof
持久化规则:redis内存数据库,如果没有持久化,断电即失,需要持久化
save 900 1 #如果900秒内,如果至少一个key进行了修改,我们即进行持久化操作
save 300 10 #如果300秒内,如果至少10个key进行了修改,我们即进行持久化操作
save 60 10000 # 如果在60秒内,如果由10000个key进行了修改,我们即进行持久化操作
#可以自己定义
stop-writes-on-bgsave-error yes #如果持久化出错,是否需要继续工作
rdbcompression yes #是否压缩rdb(持久化)文件
rdbchecksum yes #保存rdb文件的时候,进行错误的检查校验
dbfilename dump.rdb #持久化文件名称
dir ./ #持久化文件保存目录
- REPLICATION (主从复制)
replicaof <masterip> <masterport> #设置为某个主机的从机
masterauth <master-password> #主机的密码
replica-serve-stale-data yes
replica-read-only yes
repl-diskless-sync no
repl-diskless-sync-delay 5
- SECURITY(安全)
# requirepass foobared #连接默认无限制
#可以通过修改文件
requirepass 123456
#也可以通过redis命令行
127.0.0.1:6379> config get requirepass
1) "requirepass"
2) ""
127.0.0.1:6379> config set requirepass root
OK
127.0.0.1:6379> config get requirepass
(error) NOAUTH Authentication required.
127.0.0.1:6379> auth root #设置好之后需要输入密码登陆
OK
- 客户端的限制
maxclients 10000 #设置能连接上redis的最大客户端数量
maxmemory <bytes> #redis配置最大内存容量
maxmemory-policy noeviction #当内存到大上限之后的处理策略
六大策略:
1、volatile-lru:只对设置了过期时间的key进行LRU(默认值)
2、allkeys-lru : 删除lru算法的key
3、volatile-random:随机删除即将过期key
4、allkeys-random:随机删除
5、volatile-ttl : 删除即将过期的
6、noeviction : 永不过期,返回错误
- aof 持久化模式 APPEND ONLY MODE
appendonly no #默认是不开启aof模式的,默认使用rdb方式持久化,在大部分所有的情况下,rdb完全够用
appendfilename "appendonly.aof" #aof文件名称
appendfsync always #每次修改都会同步,性能消耗严重
appendfsync everysec #每秒执行一次同步,可能会丢失这1秒的数据
appendfsync no #不执行同步,操作系统自己同步数据,速度最快
Redis持久化
RDB
Redis会单独的fork一个子进程来进行持久化,会先将数据入到一个临时文件中,待持久化过程都结束了,
在用这个临时文件替换上次持久化好的文件。整个过程中,主进程是不进行任何IO操作的,这就确保了极高
的性能。如果需要进行大规模的数据的恢复,却对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效。
RDB的缺点是最后一次持久化后的数据可能丢失。
我们默认就是使用RDB模式,一般情况下不需要修改这个配置。
rdb保存的文件是dump.rdb
触发机制:
1、save的规则满足的情况下,会自动触发rdb规则
2、执行flushall命令,也会产生rdb文件
3、退出redis,也会产生rdb文件
如何恢复rdb文件
只需要将rdb文件放到redis启动目录就可以,redis启动的时候会自动检查dump.rdb文件,恢复其中的数据
127.0.0.1:6379> config get dir
1) "dir"
2) "/usr/local/bin" #这个目录就是配置文件目录
优点:
1、适合大规模的数据恢复!
2、如果你对数据的完整性要求不高
缺点:
1、需要一定的时间间隔操作持久化,如果redis意外宕机了,最后一次修改的数据可能就没有了
2、fork进程的时候,会占用一定的内容空间
AOF
append only file (追加内容)
将我们的所有命令都记录下来,恢复的时候把这个文件全部执行一遍
redis配置文件需要启用aof持久化,重启生效
appendonly yes
aof保存的文件是appendonly.aof
1、如果aof的文件有错误,这个时候redis是启动不起来的,我们需要修复这个aof文件
2、redis 给我们提供了一个工具 redis-check-aof --fix ;(其实就是删掉了出问题的数据)
[root@192 bin]# redis-check-aof --fix appendonly.aof
0x 87: Expected \r\n, got: 6473
AOF analyzed: size=160, ok_up_to=110, diff=50
This will shrink the AOF from 160 bytes, with 50 bytes, to 110 bytes
Continue? [y/N]: y
Successfully truncated AOF
优点
1、每一次修改都同步,文件的完整性会更好
2、每秒同步一次,可能会丢失一秒的数据
3、从不同步,效率最高
缺点
1、相对于数据文件来说,aof远远大于rdb,修复的速度也比rdb慢
2、aof的运行效率也要比rdb慢,所以默认选择rdb
aof默认的是文件的无限追加
可以设置当aof文件大于一定数值之后,重写aof文件
重写:比如对一个对象进行两次追加,则合并成一个命令,追加一次
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
Redis订阅
命令:
1、subscribe #订阅某个频道
2、publish #发布消息
3、unsubscribe # 取消订阅频道
4、psubscribe #订阅一个或多个给定模式的频道
5、pubsub #查看订阅与发布系统
订阅者
127.0.0.1:6379> subscribe wanghan
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "wanghan"
3) (integer) 1
1) "message"
2) "wanghan"
3) "wanghanaichenmeng"
发布者
127.0.0.1:6379> publish wanghan "wanghanaichenmeng"
(integer) 1
Redis主从复制
数据的复制都是单向的,都是从主节点到从节点
Master以写为主,Slave以读为主,80%都是读操作
主从 复制的作用:
1、数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式
2、故障恢复
3、负载均衡
4、高可用
最基本的配置:一主二从
环境配置
1、多个redis服务器配置文件修改
127.0.0.1:6379> info replication
# Replication
role:master #角色
connected_slaves:0 #没有从机
master_replid:222f6becba95af5041e3b67169969e80d92bd284
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
复制3个配置文件
redis79.conf
redis80.conf
redis81.conf
修改对应的信息
1、端口
2、pid文件名
3、log文件名
4、dump文件名
2、配置主从复制
默认情况下每台主机都是master主机
需要配置才能成为某台主机的从机
配置从机命令salveof
127.0.0.1:6380> SLAVEOF 127.0.0.1 6379 #成为ip 端口号的从机
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:14
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:cec44e4fcca81d6701cf7578e9dcb8d1c5a9e0f4
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:14
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:14
127.0.0.1:6380>
真实的主从配置是再配置文件中进行配置的
replicaof <masterip> <masterport> #设置为某个主机的从机
masterauth <master-password> #主机的密码
复制原理:
全量复制:只要重新连到master主机,就会进行一次全量复制
增量复制:当主从机再正常状态,主机写操作,会进行增量复制到从机,保证主从机数据的一致性
思考:
链式主从结构:
下一个主机是自己的从机
第一个主机是master,其他的主机仍然为从机。
如果一个从机执行slaveof no one 那他就会从一个从机变为主机,他下面的从机还是他的从机
当一个主机宕机时,从机可以执行 slave no one ,成为一个主机
哨兵模式
当主机宕机之后,自动选择主机的模式
哨兵是一个独立的进程,独立运行
原理:哨兵通过发送命令,等待redis服务响应,从而监控多个运行的redis实例
当一个哨兵监听多个redis实例,如果哨兵宕机,可能会出现问题,因此就需要搭建哨兵集群
哨兵集群和redis集群,相辅相成
假设主服务器宕机,哨兵1先检测到这个结果,系统并不会马上进行failover【故障转移】过程,仅仅是哨兵1主观的认为主
服务器不可用,这个现象称为**主观下线**
当后面的哨兵也检测到主服务器宕机时,那么哨兵就会进行一次投票,投票结果由一个哨兵发起,进行failover操作;
切换成功后,就会通过发布订阅者模式,让各个哨兵把自己监控的从服务器切换成主机,这个过程叫做**客观下线**
- 创建哨兵的配置文件sentinel.conf文件
#配置哨兵监听主服务器的ip和端口 为监测的主机设置名称:myredis 1代表当主服务器宕机,实行选举模式
sentinel monitor myredis 127.0.0.1 6379 1
- 启动哨兵
[root@192 bin]# redis-sentinel redisconf/sentinel.conf
14533:X 21 Nov 2020 19:34:51.185 # +monitor master myredis 127.0.0.1 6379 quorum 1
14533:X 21 Nov 2020 19:34:51.186 * +slave slave 127.0.0.1:6380 127.0.0.1 6380 @ myredis 127.0.0.1 6379
14533:X 21 Nov 2020 19:34:51.187 * +slave slave 127.0.0.1:6381 127.0.0.1 6381 @ myredis 127.0.0.1 6379
测试主机崩掉之后
#哨兵监测到主机宕机
14533:X 21 Nov 2020 19:38:17.688 # +sdown master myredis 127.0.0.1 6379
#全部哨兵检测到主机宕机
14533:X 21 Nov 2020 19:38:17.688 # +odown master myredis 127.0.0.1 6379 #quorum 1/1
14533:X 21 Nov 2020 19:38:17.688 # +new-epoch 1
14533:X 21 Nov 2020 19:38:17.688 # +try-failover master myredis 127.0.0.1 6379
14533:X 21 Nov 2020 19:38:17.690 # +vote-for-leader 47af3a2c6bf1d6a87e28b65d169e39c5f9e3b3e6 1
14533:X 21 Nov 2020 19:38:17.690 # +elected-leader master myredis 127.0.0.1 6379
14533:X 21 Nov 2020 19:38:17.690 # +failover-state-select-slave master myredis 127.0.0.1 6379
14533:X 21 Nov 2020 19:38:17.761 # +selected-slave slave 127.0.0.1:6380 127.0.0.1 6380 @ myredis 127.0.0.1 6379
14533:X 21 Nov 2020 19:38:17.761 * +failover-state-send-slaveof-noone slave 127.0.0.1:6380 127.0.0.1 6380 @ myredis 127.0.0.1 6379
14533:X 21 Nov 2020 19:38:17.833 * +failover-state-wait-promotion slave 127.0.0.1:6380 127.0.0.1 6380 @ myredis 127.0.0.1 6379
14533:X 21 Nov 2020 19:38:17.836 # +promoted-slave slave 127.0.0.1:6380 127.0.0.1 6380 @ myredis 127.0.0.1 6379
14533:X 21 Nov 2020 19:38:17.836 # +failover-state-reconf-slaves master myredis 127.0.0.1 6379
14533:X 21 Nov 2020 19:38:17.885 * +slave-reconf-sent slave 127.0.0.1:6381 127.0.0.1 6381 @ myredis 127.0.0.1 6379
14533:X 21 Nov 2020 19:38:18.886 * +slave-reconf-inprog slave 127.0.0.1:6381 127.0.0.1 6381 @ myredis 127.0.0.1 6379
14533:X 21 Nov 2020 19:38:18.887 * +slave-reconf-done slave 127.0.0.1:6381 127.0.0.1 6381 @ myredis 127.0.0.1 6379
14533:X 21 Nov 2020 19:38:18.962 # +failover-end master myredis 127.0.0.1 6379
#主机从6379切换到6380
14533:X 21 Nov 2020 19:38:18.962 # +switch-master myredis 127.0.0.1 6379 127.0.0.1 6380
14533:X 21 Nov 2020 19:38:18.963 * +slave slave 127.0.0.1:6381 127.0.0.1 6381 @ myredis 127.0.0.1 6380
14533:X 21 Nov 2020 19:38:18.963 * +slave slave 127.0.0.1:6379 127.0.0.1 6379 @ myredis 127.0.0.1 6380
14533:X 21 Nov 2020 19:38:48.992 # +sdown slave 127.0.0.1:6379 127.0.0.1 6379 @ myredis 127.0.0.1 6380
当主机宕机之后,投票选举一个从机成为主机,当主机再次启动,会自动变为从机
哨兵模式:
优点
1、哨兵集群基于主从复制,所有主从复制的优点他都由
2、主从可以自动切换,故障可以转移,系统的可用性会更好
3、哨兵模式就是主从复制的升级,从手动到自动,更加健壮
缺点
1、redis不好在线扩容,集群容量一旦到达上限,在线扩容就很麻烦
2、实现哨兵模式的配置属性繁多
哨兵模式全部配置:
#禁止保护模式
protected-mode no
#配置监听的主服务器,这里 sentinel monitor 代表监控
#mymaster代表服务器名称,可以自定义
#192.168.11.128代表监控的主服务器
#6379代表端口
#2代表只有两个或者两个以上的哨兵认为主服务器不可用的时候,才会做故障切换操作
sentinel monitor mymaster 192.168.11.128 6379 2
#sentinel auth-pass 定义服务的密码
#mymaster服务名称
#abcdefg Redis服务器密码
sentinel auth-pass mymaster abcdefg
#哨兵进程端口
port
#哨兵进程服务临时文件夹,默认为 /tmp,要保证有可写入的权限
dir /tmp
#指定哨兵在监测 Redis 服务时,当 Redis 服务在一个亳秒数内都无 法回答时,单个哨兵认为的主观下线时间,默认为 30000(30秒)
sentinel down-after-milliseconds
#指定可以有多少 Redis 服务同步新的主机,一般而言,这个数字越 小同步时间就越长,而越大,则对网络资源要求则越高
sentinel parallel-syncs
#指定在故障切换允许的亳秒数,当超过这个亳秒数的时候,就认为 切换故障失败,默认为 3 分钟
sentinel failover-timeout
#指定 sentinel 检测到该监控的 redis 实例指向的实例异常时,调用的 报警脚本。该配置项可选,比较常用
sentinel notification-script
缓存穿透和雪崩(面试高频)
缓存穿透和击穿
缓存穿透:当用户去请求数据的时候,会先从缓存中寻找,如果没有,则会从数据库中寻找,如果有人恶意攻击,
绕过缓存,去大量的请求数据库服务器,就会对数据库造成很大的压力,导致宕机,这种现象成为`缓存穿透`
缓存击穿:当缓存中的一条数据,被请求了很多次,这条数据设置的过期时间60秒,当数据过期还没有重新set的时候,
大量的用户请求就会绕过缓存,直接请求数据库服务器,也可能会造成数据库服务器的宕机,这种现象称为`缓存击穿`
例如:微博的热点(宕机)
解决方案:
1、设置热点key永不过期 (比较容易出问题,不可能永远不过期)
2、加互斥锁,当请求绕过缓存,请求数据库服务器的时候,设置线程锁,只允许一个线程去请求数据库,其他线程进行等待
缓存雪崩
当缓存中大量的key同时过期,或者redis宕机
产生雪崩的原因:
比如双十一零点抢购之前,这波商品会集中的放入到缓存中,假设缓存一个小时过期,那么一个小时之后,
这些商品的查询都会集中到数据库中,对于数据库而言会产生周期性的压力波动,出现宕机的风险
解决方案:
1、redis的高可用
多设置几台redis服务器
2、限流降级
在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量,比如某一个key只允许一个线程去查询数据,
写缓存,其他线程等待
3、数据预热
把可能的数据预先访问一遍,这样大部分的数据都会预设到缓存中,设置不同的过期时间,尽量均匀