目录
1.为什么需要缓存
缓存+mysql+垂直拆分
-
以前只有静态页面的时候,服务器处理的压力比较小所以单机mysql就能够解决问题,但是后来各种动态页面需要生成,请求量变多导致服务器已经无法承受这样的访问。
-
这个时候的网站瓶颈
- 数据量太大,机器放不下
- 索引太多
- 访问量太大
- 这个时候就要引入中间件缓存。就不需要直接去磁盘读取io操作,导致性能下降,能够直接从高速缓存中取出数据(缓存+mysql+垂直拆分(读写分离,数据库功能分开))
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1P6LZ4xf-1635255914774)(…/…/…/…/AppData/Roaming/Typora/typora-user-images/image-20211026110135169.png)]
mysql集群+水平拆分+分库分表
-
分库分表就是为了让数据库访问压力没有那么大,不同服务访问不同的数据库,减少单个数据库的压力,通过微服务来进行整合
-
mysql集群是把这些写数据库和读数据库复制成好几个集群共同给前台提供更好的服务。
-
但是仍然需要nosql去解决图片、地理位置等占用大量空间的数据,防止mysql处理所有的数据存储导致的IO读取非常慢
说说水平拆分和垂直拆分
- 水平拆分就是表数据太多,弄多几张表来存储这些数据,而不是一张存完
- 垂直拆分就是数据列太多,拆分多几个列来存储。防止字段太多
2.什么是nosql
not only sql
- 不仅仅是数据
- 无固定查询语言
- CAP与BASE
- 键值对存储
- 性能非常好,不需要我们去设计表
- 最终一致性
四类nosql
- 键值对 redis
- 文档型
- 列存储
- 图关系
3.Redis入门
redis是什么
- 远程字典服务
- 结构化数据库
- 能够存于内存也可以持久化
redis有什么用
- 内存存储,持久化
- 高速缓存
- 发布订阅
- 地图信息分析
- 计数器
配置环境
- 我用的centos6的版本出现上不了网,etc上面网关必须和虚拟编辑器的一样才能够访问外网
- 如果发现yum出现centos6 Cannot find a valid baseurl for repo: base那么就可以使用这个(具体原理不清楚,可能是换了一个yum源)
sed -i “s|enabled=1|enabled=0|g” /etc/yum/pluginconf.d/fastestmirror.conf
mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup
curl -o /etc/yum.repos.d/CentOS-Base.repo https://www.xmpan.com/Centos-6-Vault-Aliyun.repo
yum clean all
yum makecache
-
下载yum install gcc c++也就是redis需要的运行环境(还是docker会比较方便),接着就是make,必须要在redis的文件夹上去make,接着就是make install
-
上面这个是在usr/local/bin下面的
-
cp /opt/redis-6.2.6/redis.conf kconfig移动到usr/local/bin/kconfig下面保证文件的安全
-
daemonize yes
-
开启server,redis-server 配置文件位置(kconfig/redis.conf),开启客户端redis-cli -p 6379.都是要去到usr/loacl/bin里面去使用这些命令
-
shutdown+exit退出服务和关闭redis
测试性能
能够测试每条命令处理的速度。
redis-benchmark -h localhost -p 6379 -c 100 -n 100000
基本命令
- dbsize数据库大小
- 一共有16个数据库
- key *所有的键值对
- select 数字切换数据库
- flushdb刷新数据库
- flushall刷新全部数据库
redis的性能瓶颈
- 主要是内存和网络带宽
- 而且是单线程,那么为什么单线程还能这么快?
- 因为单线程不需要切换代价。
- 而且在内存不切换执行那么就是最快的。
4.五大数据类型
redis-key
- exists xx是否存在这个key
- expire xx 几秒:超时设置
- move name 移除某个键值对
- get name获取
- set name xx加入键值对
- type name查看name的数据类型
String类型
普通字符串
- append name xx追加xx字符串到name的结尾,如果name不存在那么直接set一个
- strlen name查看长度
- incr 字段:字段如果是数字就自增1
- decr name:自减
- incrby name x:自增x
- decrby name x:自减x
- getrange name 0 3:截取name的0到3的字符串
- setrange name 1 xx:在name的第i个字符开始替换为xx
- setnx(set if not exist) name value:如果是非空那么才能设置
- setex (set with expire)name 时间 value:设置name和value加上超时时间
- mset key1 value1 key2 value2。。:同时设置多个键值对
- mget key1 key2.。:同时获取多个值
- msetnx:同时设置多个键值对,但是必须要一起成功才会插入。非空才会插入
- getset name xx:获取如果是空那么就设置一个,如果不是空就返回
通常可以使用user:1:name或者是user:{id}:{attr}来设置对应的key来存储一个对象
List类型
- lpush list value:加入值,放到列表的头部
- lrange list 0 2:取0到2的值出来
- rpush:从列表的尾部插入
- lpop:头部弹出
- rpop:尾部弹出
- lindex list x:获取类表第x的元素
- llen list:列表长度
- lrem list count value:你需要移除多少个元素
- ltrim list 0 2:修剪到只剩下0到2位置的元素
- rpoplpush list newlist:从list中尾部弹出一个元素,从newlist头部插入这个弹出的元素
- lset list index value:把list第index个元素替换为value
- linsert list after/before (list的元素) (新的元素):如果是after相当于就是在list存在的指定元素后面添加新元素
总结
- lpush,rpop就是消息队列的用法,栈(lpush,lpop)
set集合类型
- sadd set value:加入值
- sismember set value:是不是set里面的元素
- smembers set:遍历元素展示
- srem myset value:移除元素
- srandmember myset:随机抽取元素
- spop set:随机移除一个元素
- smove set set2 value:把set的value移动到set2上面
- sdiff set1 set2:两个集合的差集
- sinter set1 set2:两个集合的交集
- sunion set1 set2:两个集合的并集
总结:可以用在共同好友共同关注上面
Hash
Map< Map <>>这样的结构
-
hset hash key value:设置键值
-
hmset hash k1 v1 k2 v2:设置多个剪枝
-
hmget hash k1 k2:获取多个值
-
hgetall hash:获取所有的键值对
-
hdel hash key:删除键值
-
hlen hash:获取hash的字段数量
-
hexists hash key:是否存在这个key
-
hsetnx hash k1 v1:空才会设置,非空不处理
-
hincrby hash k1 value:自增
Zset
-
相当于比set多了一个标识可以用于排序
-
zadd zset 序号 value:加入
-
zrange zset 0 -1:从小到大遍历
-
zrangebyscore zset -inf +inf withsocres:排序之后遍历出来
-
zrevrange zset 0 -1:从大到小遍历
-
zcount zset 0 2:统计这个区间的序号有多少个
应用场景
- 排行榜,可以使用zset加权排序
- 工资表
5.三种特殊类型
geospatial地理位置
朋友定位,距离计算等
-
geoadd 地方集合 经度 纬度 地方名
-
geopos 地方集合 地方名:显示经度和纬度
-
geodist 地方集合 地方名1 地方名2 m/km/mi/ft:显示距离
-
georadius 地方集合 查询点经度 查询点纬度 搜查距离 搜查距离单位 withdist(地方离点的距离) withcoord(地方的经度和纬度):以点画圆搜索附近的城市
-
georadiusbymember 地方集合 开始搜素的地方名 距离 距离单位:从集合的某个地方开始画圆进行搜索
底层是zset,主要是处理位置的计算。
hyperloglog
什么是基数
- 不重复的元素
作用?
占用内存小,2^64不同元素,统计多少个人访问网站可以使用这个数据类型,通过合并来找到那些用户是重复登录的,不会导致出现一个用户被是使用多次
语法
- pfadd set a b c :添加元素
- pfcount set:统计元素个数
- pfmerge set3 set1 set2:合并两个集合
BitMap
- setbit map key 0/1:设置key的0或者是1
- bitcount map:统计1的个数
应用场景
- 打卡
- 活跃等
6.redis的事务
特点
- 顺序执行、排他性
- 无隔离级别
- 每条指令都是原子性
语法
- multi开启事务
- 指令入队
- exec执行
- discard放弃执行事务
异常
- 如果出现语法异常那么事务取消执行,比如set k1
- 如果只是运行异常假设k1=v1那么执行incr k1,那么就是运行时异常后面的代码还是会继续执行的。
redis的乐观锁
- 通过version来保证事务的一致性。
语法
- watch给对应的的key加上锁
- 然后进入事务
- 如果发现其他会话改变了它那么就会事务失败
那么如何发现改变?
在watch的时候先保存k1的version,然后在修改的时候获取k1当前version和旧version进行比较,如果不同那么就是有其它会话对他进行了修改。
Jedis连接
- 如果要连接linux那么就需要关闭防火墙或者是放这个端口出去。
- 还有redis.conf就是bind 127.0.0.1需要注释掉,不然外部访问不了
事务
- muti里面不能够使用jedis
public class StringTest {
public static void main(String[] args) {
Jedis jedis=new Jedis("192.168.3.100",6379);
jedis.flushDB();
String result="haoren";
Transaction multi =jedis.multi();
try {
multi.set("k1",result);
multi.exec();
} catch (Exception e) {
multi.discard();
e.printStackTrace();
} finally {
System.out.println(jedis.get("k1"));
jedis.close();
}
}
}
源码
@Bean
@ConditionalOnMissingBean(
name = {
"redisTemplate"}
)
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
7.redis配置文件详解
- 单位
- 可以多配置文件
- bind 地址。可以连接redis的地址
- protected-mode保护机制
- port端口号
- pidfile后台运行需要使用的pid文件
- daemonize 是不是守护线程,通常是no,但是为了不退出就关闭,那么就设置yes
- loglevel日志级别
- logfile日志文件
- database 数据库数量
- save 900 1保存快照,900内发生一次修改那么就保存
- stop-writes-on-bgsave-error持久化失败之后是否继续持久化
- rdbcompression是否压缩
- rdbchecksum保存rdb的错误检查
- requirepass修改密码
- appendonly aof使用aof模式,但是默认rdb够用了
- appendfilename持久化文件名字
- appendfsync everysync每秒同步一次,丢失一秒的数据
8.持久化
RDB
- 其实就是redis database
- 原理就是每次隔一段时间fork一个子进程去处理数据同步到磁盘上而已。
- 数据文件是dump.rdb这个就是持久化的文件。
优点
- 大规模数据恢复
- 对数据完整性不高
缺点
- 时间间隔,如果宕机导致最后一次修改失败
- fork子进程需要占用内存空间
AOF
append only file
- 记录每一条写记录,不断地追加文件
- 如果文件出错能够通过redis-check-aof来进行恢复和修改。
优点
- 丢失记录少,因为每秒都会进行记录(或者是每次写,也可以是不同步更新)
缺点
- 需要的文件大,因为每次的写都需要记录(全部),而rdb是每次修改时间间隔才会进行一次保存(部分)
- 修复速度慢,效率很差。
9.redis发布订阅
- 其实就是通过一个链表维护各个频道,然后后台发送消息到频道,如果订阅了它的人那么就能获取消息
- 比如就是卖报纸,订阅了每天都买,那么每天都会有人专门把报纸送到各个家门口
- subscribe 频道名称
- publish 频道名称 发布信息
10.主从复制
- 复制多个配置文件
- 修改pidfile
- 修改port
- 修改logfile
- 修改dbfilename
- 主从复制的作用
- 解决数据冗余
- 故障恢复
- 负载均衡,分配压力到各个redis服务器上
- 高可用,能够通过哨兵等方式来重新选主机
- 解决读多写少,一台主机多台从机,主机写,从机读
- 如果主机断开,从机会一直连着,但是如果从机断开重连那么就会与主机的关系断开
增量复制和全量复制
- 增量复制是从机连接主机的时候主机修改一次就同步一次
- 全量复制是主机把修改数据一下次整理成文件发给从机(每次连接的时候会执行一次)
# Replication
role:master
connected_slaves:0
master_failover_state:no-failover
master_replid:3d9817e8263810a38479fb280968c427ce45d7b5
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.哨兵模式
优点
- 主从复制的升级版
- 能够通过哨兵的监视和投票还有订阅模式来切换主机,使得故障能够转移
缺点
- 但是问题就是扩容比较难,多个哨兵之间处理非常复杂
实践
- 开启多个sever
- 然后设置一个sentinel.conf文件通过redis-sentinel进行加载
- sentinel monitor 哨兵名 host port 1,哨兵文件的配置,还可以加上port
12缓存穿透和雪崩问题
缓存穿透
- 就是没有把数据缓存到内存,导致大量请求冲向数据库
- 解决方案可以使用布隆过滤器,使用hash来保留可能访问参数,并且返回结果是null
- 问题就是null太多导致占用内存
- 而且数据库和缓存的不一致问题
缓存击穿
- 热点key突然过期或者失效,但是大量请求重入数据库访问
解决方案
- 永不过期
- 互斥锁(这个其实就是只放一个线程过去数据库查询,商城项目经常会有这种处理,通过分布式锁,占住坑位不让其他线程过去。)
缓存雪崩
- 大部分key失效,或者是redis宕机
解决方案
- 数据库预热,先把数据查询并且缓存到缓存数据库
- 或者是通过熔断降级
开启多个sever
- 然后设置一个sentinel.conf文件通过redis-sentinel进行加载
- sentinel monitor 哨兵名 host port 1,哨兵文件的配置,还可以加上port
[外链图片转存中…(img-OkW4KbI7-1635255914795)]
[外链图片转存中…(img-pT1BMojI-1635255914795)]
12缓存穿透和雪崩问题
缓存穿透
- 就是没有把数据缓存到内存,导致大量请求冲向数据库
- 解决方案可以使用布隆过滤器,使用hash来保留可能访问参数,并且返回结果是null
- 问题就是null太多导致占用内存
- 而且数据库和缓存的不一致问题
缓存击穿
- 热点key突然过期或者失效,但是大量请求重入数据库访问
解决方案
- 永不过期
- 互斥锁(这个其实就是只放一个线程过去数据库查询,商城项目经常会有这种处理,通过分布式锁,占住坑位不让其他线程过去。)
[外链图片转存中…(img-gwpdYJCT-1635255914796)]
缓存雪崩
- 大部分key失效,或者是redis宕机
解决方案
- 数据库预热,先把数据查询并且缓存到缓存数据库
- 或者是通过熔断降级