一些redis的学习笔记,供以后浏览
redis
概念 redis以key-value形式进行存储的NoSql数据库,
-
使用c语言编写
-
Redis的数据存在内存中,作为缓存工具,读写性能好
-
存取数据
- 对key进行CRC16算法,得到结果后取16384,之后放入对应的槽中
-
通过Redis Sentinel(哨兵)提供高可用,通过Redis Cluster(集群)提供自动分区
Redis作为缓存工具写代码的流程(边路缓存思想中的一部分)
- 为什么快,是服务器端的速度快了,请求及响应的时间少了
- 什么是边路缓存(一种思想)
-
- 查询的时候先去查询缓存,不存在就去查数据库。
-
- 修改数据的时候,先修改数据库,后修改缓存
-
数据查询的流程图
- 查询key->在不在Redis缓存中,key是否存在,key存在返回value,key不存在去查询sql数据库,数据库返回给主程序,再将数据缓存进主函数。
修改的时候先去修改数据库,再去修改redis
search(String a) => bool
if true => value Map.get(key)
else => value SQL.get(key) => redis.save(key:value)
Redis的单机模式下的一些命令
-
String 类型
- set 修改 覆盖 设置过期时间EX/PX 10
- get 查询
- setnx 没有的话新增,有的话不增
-
Hash
- Hash类型的值,key中包含了多组field value 类似于对象
- key :
- field - value
- field - value
- field - value
- 取出单个
- hset [key] [field] [value]
- hget [key] [field] [value]
- hset people name "kobe"
- 取出多个:
- hmset [key] [field1] [value] [field2] [value]
- hmget [key] [field1] [field2]
- 取出所有key下的field : value对
- hvals [key]
- 结果 field
- value
- 删除掉其中一个field,del删除的是所有的
- hdel [key] [field]
-
List 类似与双端队列
- key :
- value1
- value2
- value3 - 在头部加,尾部加
- Rpush list "b" "c"(重复两次 rpush)
- Rpush list a
- Lpush
- 取前几项
- lrange list 0 3
- 0 -1(末尾)
- llen list
- lrange list 0 3
- 从左往右查,删掉两个1
- lrem [list名] [从左往右的数量] [value]
- lrem list 2 "1"
- key :
-
set 集合
-
sadd key 1 2 3
-
查询所有
-
smembers set
-
查询总数量
-
scard set(key)
-
集合不允许重复,sadd依旧是空
-
set是无序的
-
-
sortedset 有序集合
- 每个value都有分数
- zadd [key] [value] [score]
- zadd set
- zrange set 0 -1 区间从小到大
-
redis 持久化策略
-
Redis每次启动都将数据从磁盘读到内存
-
有两种持久化策略 RDB(Redis DataBase) AOF (AppendOnly File往文件中追加)
-
RDB
- RDB在指定时间间隔内生成数据快照 默认保存到dump.rdb的文件中
- 用户使用save(同步 阻塞线程)(配置文件 save 每900秒 1数据操作,进行记录 save 300 1 save 60 10000) / BGSave(异步)< = 手动保存数据
- 使用RDB性能会高于AOF,恢复数据的效率会高于AOF
- 出乎意料的关闭或者保存点之间数据的关闭,会导致丢失数据。
- 每次保存,都会fork子进程,会比较消耗性能
-
AOF
- 默认不生效,生效后的优先级大于RDB
- 出现增删改查操作(每次的结果记录到日志中)之后,会同步到数据库之中
- 出现问题使用AOF日志
- AOF与硬盘进行交互,影响了性能,稍微慢一点RDB
- 安全
- 相同数据集AOF文件大于RDB,AOF存命令,RDB存数据
-
appendonly no
-
appendfilename ""文件名
-
使用docker建立redis的主从复制、哨兵模式、集群模式方法
-
基于docker的主从复制的建立过程
-
主从
- 读写分离
- master 写,slave读
-
创建三个redis
- docker pull redis
-
创建 redis 使用本机端口=> 映射到本机端口出口 使用版本 /
- docker run --name redis1 6379:6379 -v /opt/redis:/data -d redis
- docker run --name redis2 6380:6379 -v /opt/redis:/data -d redis
- docker run --name redis3 6381:6379 -v /opt/redis:/data -d redis
-
运行redis1 中的redis-cli
sudo docker exec -it redis1 redis-cli -
使用role命令,查询docker redis的角色
-
使用slaveof ip port设定从节点
-
docker inspect redisname 查询到docker下的网络ip从而找到与主节点master的连接
-
主从模式下,主节点具备写的能力,从节点不具备
-
-
哨兵模式
- 监督每一个主从节点的状态,当master节点出现崩溃,他会从中选择一个主出来。
- 单哨兵,多哨兵
- 1个和多个 多个需要投票 人为在配置文件中,设定当多少个redis认定错误的时候master会将其认定为master出现异常,进行替换
-
redis脑裂
- 当出现网络波动时,master可能被哨兵认为是错误的,会被哨兵投票替换掉,从从节点中选择一个当作主节点,但是当网络恢复时,本来已经宕机后的节点恢复,在节点中出现了两个master的时候被称为脑裂现象
- 解决方法 配置文件中修改
- min-slave-to-write 3 // 连接到master的最小 slave数量(当master有一个,而只有三个slave的时候,设置3个最小slave数量表示master发生网络波动将不会影响其他的slave)
- min-slave-max-lag 10 //slave 连接到master的最大延迟时间
-
搭建高性能集群
- 集群搭建完成之后由集群节点平分16384个槽。(不能平分时,前几个节点多一个槽)
- 客户端可以访问集群中的任意一个几个点
- 集群新增或者查询一个键值对时,会对key进行crc16算法,得到一个小于16384的值,然后去操作相应的节点。
- 当集群中的节点超过1/2不可用时,整个集群不可用,为了搭建稳定集群,都采用奇数节点。
- 哨兵会监控主状态,如果出现了问题(在配置文件中确定有多少个节点出现了问题),会进行投票,投票选择一个从当做主,如果后期恢复了,主当做从加入节点,在搭建redis时,内置哨兵策略。
-
集群创建 :
- 配置文件
port ${PORT} cluster-enabled yes cluster-config-file nodes.conf cluster-node-timeout 5000 cluster-announce-ip 192.168.133.3 cluster-announce-port ${PORT} cluster-announce-bus-port 1${PORT} appendonly yes // 设置的是自己的本机
- 集群的shell脚本,在文件夹下创建6个文件夹
for port in `seq 7000 7005`;do \ mkdir -p ./${port}/conf \ && PORT=${port} envsubst < ./redis-cluster.tmpl > \ ./${port}/conf/redis.conf \ && mkdir -p ./${port}/data; \ done
- 建立集群
for port in $(seq 7000 7005); \ do \ docker run -it -d -p ${port}:${port} -p 1${port}:1${port} \ --privileged=true -v /usr/local/redis-cluster/${port}/conf/redis.conf:/usr/local/etc/redis/redis.conf \ --privileged=true -v /usr/local/redis-cluster/${port}/data:/data \ --restart always --name redis-${port} --net redis-net \ --sysctl net.core.somaxconn=1024 redis redis-server /usr/local/etc/redis/redis.conf; \ done
- 执行集群脚本
redis-cli --cluster create \ 192.168.133.3:7000 \ 192.168.133.3:7001 \ 192.168.133.3:7002 \ 192.168.133.3:7003 \ 192.168.133.3:7004 \ 192.168.133.3:7005 \ --cluster-replicas 1 设置一主一从,记得关闭防火墙 sudo ufw disable
- 运行错误,清空docker内容命令
docker stop redis-7000 redis-7001 redis-7002 redis-7003 redis-7004 redis-7005 docker rm redis-7000 redis-7001 redis-7002 redis-7003 redis-7004 redis-7005 rm -r 7000 7001 7002 7003 7004 7005
- 测试,以集群方式-c -p是端口号
redis-cli -c -h 192.168.133.3 -p 7000
spring data redis
-
设计模式:模板方法 .template
-
启动器springbootshartdataredis
-
注入xxxtemplate
-
方法和变量名:
- opsForValue:String值,如果储存Java对象或Java中集合时就需要使用序列化器,进行序列化成json字符串
- opsForList:列表
- opsForHash: 哈希表
- opsForZset: 有序集合 sorted Set
- opsForSet: 集合
-
序列化器(什么是序列化,配置文件@Config怎么写,注解@Bean)
-
序列化器jdkSerializationRedisSerializer
-
String序列化器
-
genericToStringSerializer
- 需要调用者给传递一个对象到字符串互转的Converter(转换器),比较麻烦
-
OxmSerialization序列化器(无人用)
- 使用xml进行序列化存储
- 要自定义xml文件流复杂
-
Jackson2JsonRedisSerializer
- 转换成了json字符串
- 无法转回来,不能使用强转回,要自己
-
GenericJackson2JsonRedisSerializer
- 和jdk的使用相同,但是可以显示中文在redis-manager中,可读性好
- 增加了一个类名信息
-
Spring Cache使用了jdk序列化器,更加方便
-
注:中文字符串使用redis-manager
-
-
步骤
- 添加依赖springdata start redis
- 配置配置文件
- 编写模板类
- 编写代码
- set get
- del exist等
redis缓存的问题
- 缓存穿透(key 不存在,多为null)
- 实际情况下,添加缓存工具的目的是,减少对数据库的访问,增加效率。肯定会出现redis中不存在缓存数据的情况。例如:id=-1,不存在redis中,会访问数据库,
在高并发的情况下,redis依旧频繁访问数据库就叫做缓存穿透
,穿透的是redis,因为redis没有,要去频繁访问数据库。出现情况,多是数据库中没有,查询结果多为null的时候,不被缓存
。 - 解决方法:
- 查询的结果即使为null,依旧缓存到redis中。
- 设置key的ex也就是存在时间比其他的内容短一些
- 代码
- 实际情况下,添加缓存工具的目的是,减少对数据库的访问,增加效率。肯定会出现redis中不存在缓存数据的情况。例如:id=-1,不存在redis中,会访问数据库,
if(list == null){
redisTemplate.opsForValue().set(listName,10,TimeUnit.MINUTES);
}
else{
redisTemplate.opsForValue().set(listName,10,TimeUnit.DAYS);
}
-
缓存击穿(单一的key 存在,但是过期了,有大量的访问都在这个key)
- 考虑redis存放数据的内存压力,都会设置key的有效时间,会出现键值对过期。
当键值对刚刚过期的时候,同一时刻,突然有大量的key去访问刚刚过期的键值对,导致都要去访问数据库,造成缓存击穿
- 解决方法
- 永久数据(懒)内存压力大
- 加锁。防止数据库的并发访问,单列锁的概念
- 锁的概念synchronize锁和lock锁,ReentrantLock锁(重入锁): 按需开锁就是乐观锁,什么也不考虑就是悲观锁: 就是在在锁前用if判断,而上来就用锁,属于悲观锁
- synchronize
- 可以加到方法上
- 不需要自己解锁
- 也可以使用代码块
synchronize(this){ DATABASE.SELECT(); }
- lock锁,需要手动的开锁和解锁
- concurent锁 重入锁
ReentrantLock lock = new ReentrantLock(); lock.lock(); if(lock.islock()){ System.out.println("run"); } thread 具体代码见IDEA
- synchronize
- 考虑redis存放数据的内存压力,都会设置key的有效时间,会出现键值对过期。
-
缓存雪崩(大量数据失效,key访问都要去找数据库)
- 不同于单一key的失效,由于设置的ex可能在短时间内,同时发生失效的可能,导致访问redis的数据时不存在,要大量的访问在数据库的数据,这样一来就发生了缓存雪崩
- 解决方法
- 永久生效
- 自定义算法 随即有效时间
- 一段时间内出现大量的请求是高并发,10000秒以上就不是高并发了
- 代码,设置一个10000的随机数,并设置ex为100+随机数,这样就应付了高并发
int seconds = random.nextInt(10000); redisTemplate.opsValue().set(key,item,100 + seconds, TimeUnit.SECOND);
-
Redis缓存淘汰策略/内存不足如何回收/redis如何进行缓存淘汰策略
- Redis数据放入内存中,内存占用会越来越大,最终导致内存溢出
- Redis内置了缓存淘汰策略,使用在配置文件中
- maxmemory-policy noeviction 缓存最大的阈值 为maxmemory
noeviction
默认策略 默认不删除key,超过时报错 - maxmemory-policy
最大的缓存占用大小 - 其他设置
- volateile-lru从其他设置过期的key中操作,选择使用数量最少的
- allkeys-lru 从所有key中,选择使用次数最少的
- volateile-lfu 对有过期时间的key进行lfu算法
- allkey-lfu 对所有key进行lfu算法
- volateile-random 对有过期时间的key进行随机删除
- allkey-random 对所有的key进行随机删除
- volateile-ttl 在有过期时间的key中删除过期时间最短的key
- noeviction 对超过了缓存最大的阈值,就报错
- maxmemory-policy noeviction 缓存最大的阈值 为maxmemory
-
操作系统相关
-
LRU Least Recently Used 最近未使用算法
- 链表,新增放入链表头
- 修改了哪个数据就放到链表头
- 到达了阈值,删除最后一个数据
-
LFU Least Frequent Used 最近最不经常使用(某一时间段)
- 计数器,新增就计数
- 删除时,最后删除数据访问次数最小的
-
何时淘汰数据
- 消极方式
- 新增,查询时候,才判断是否过期和删除
- 积极方式
- 周期性判断,如果有失效
- 主动删除方式
- 消极方式