9.SpringCache+MyBatisPlus整合
9.1.SpringCache+MyBatisPlus整合
(1)SpringCache简介
文档:https://spring.io/guides/gs/caching/
- 自Spring3.1起,提供了类似于@Transactional注解事务的注解Cache支持,且提供了Cache抽象
- 提供基本的Cache抽象,方便切换各种底层Cache
- 只需要更少的代码就可以完成业务数据的缓存
- 提供事务回滚时也自动回滚缓存,支持比较复杂的缓存逻辑
- 核心
- 一个是Cache接口,缓存操作的API
- 一个是CacheManager管理各类缓存,有多个缓存框架实现
(2)项目中引入cache的starter
<!--springCache依赖包--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency>
(3)配置文件指定缓存类型
spring: cache: type: redis
(4)启动类开启缓存注解
@EnableCaching
(5)添加数据库依赖
<!--mybatis plus和springboot整合--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.4.0</version> </dependency> <!--数据库驱动--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.15</version> </dependency>
(6)增加数据库配置以及mybatisplus日志打印的配置
#配置plus打印sql⽇志 mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl spring: #数据库连接配置 datasource: url: jdbc:mysql://127.0.0.1:3306/redis_test?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver
(7)数据库表建立
CREATE TABLE `product` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `title` varchar(128) DEFAULT NULL COMMENT '标 题', `cover_img` varchar(128) DEFAULT NULL COMMENT '封⾯图', `detail` varchar(256) DEFAULT '' COMMENT '详 情', `amount` int(10) DEFAULT NULL COMMENT '新价 格', `stock` int(11) DEFAULT NULL COMMENT '库存', `create_time` datetime DEFAULT NULL COMMENT '创建时间', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4; ==================================================== INSERT INTO `product` (`id`, `title`, `cover_img`, `detail`, `amount`, `stock`, `create_time`) VALUES (1, 'AlibabaCloud', 'https://file.xdclass.net/video/2020/alibabaclo ud/zt-alibabacloud.png', 'https://file.xdclass.net/video/2021/60- MLS/summary.jpeg', 213, 100, '2021-09-12 00:00:00'), (2, 'Linux', 'https://file.xdclass.net/video/2020/alibabaclo ud/zt-alibabacloud.png', 'https://file.xdclass.net/video/2021/59- Postman/summary.jpeg', 42, 100, '2021-03-12 00:00:00'), (3, 'Docker', 'https://file.xdclass.net/video/2020/alibabaclo ud/zt-alibabacloud.png', 'https://file.xdclass.net/video/2021/60- MLS/summary.jpeg', 12, 20, '2022-09-22 00:00:00'), (4, 'Nginx', 'https://file.xdclass.net/video/2020/alibabaclo ud/zt-alibabacloud.png', 'https://file.xdclass.net/video/2021/60- MLS/summary.jpeg', 14, 20, '2022-11-12 00:00:00');
(8)数据库表对应的实体类编写
@Data @TableName("product") public class ProductDO { @TableId(value = "id", type = IdType.AUTO) private Long id; /** * 标题 */ private String title; /** * 封⾯图 */ private String coverImg; /** * 详情 */ private String detail; /** * 新价格 */ private Integer amount; /** * 库存 */ private Integer stock; /** * 创建时间 */ private Date createTime; }
(9)开发商品的CRUD和分页查询
- 主类开启对Mapper的支持
//mapper所在的路径 @MapperScan("xxx.xxx.xxx")
- 编写Mapper
public interface ProductMapper extends BaseMapper<ProductDO> { }
- 编写service
@Service public class ProductServiceImpl implements ProductService { @Autowired private ProductMapper productMapper; @Override public int save(ProductDO productDO) { int insert = productMapper.insert(productDO); return insert; } @Override public int del(int id) { int i = productMapper.deleteById(id); return i; } @Override public int update(ProductDO productDO) { int i = productMapper.updateById(productDO); return i; } @Override public ProductDO findById(int id) { return productMapper.selectById(id); } @Override public Map<String, Object> page(int page, int size) { Page pageInfo = new Page<>(page,size); IPage<ProductDO> iPage = productMapper.selectPage(pageInfo, null); Map<String,Object> pageMap = new HashMap<>(3); pageMap.put("total_record",iPage.getTotal()); pageMap.put("total_page",iPage.getPages()); pageMap.put("current_total",iPage.getRecords()); return pageMap; } }
- 编写controller
@RestController @RequestMapping("/api/v1/product") public class ProductController { @Autowired private ProductService productService; @PostMapping("/add") public JsonData add(@RequestBody ProductDO productDO){ int save = productService.save(productDO); return JsonData.buildSuccess(save); } @PostMapping("/update") public JsonData update(@RequestBody ProductDO productDO){ int save = productService.update(productDO); return JsonData.buildSuccess(save); } @GetMapping("/findById") public JsonData findById(@RequestParam("product_id") int id){ ProductDO productDO = productService.findById(id); return JsonData.buildSuccess(productDO); } @DeleteMapping("/del") public JsonData del(@RequestParam("product_id") int id){ int i = productService.del(id); return JsonData.buildSuccess(i); } @GetMapping("/page") public JsonData page(@RequestParam("page") int page,@RequestParam("size") int size){ Map<String, Object> map = productService.page(page, size); return JsonData.buildSuccess(map); } }
9.2.Cacheable注解
(1)Cacheable注解
- 标记在一个方法上,也可以标记在一个类上
- 缓存标注对象的返回结果,标注咋i方法上缓存该方法的返回值,标注在类上缓存所有方法的返回值
- value缓存名称,可以有多个
- key缓存的key规则,可以用springEL表达式,默认是方法参数组合
- condition缓存条件,使用springEL编写,返回true才缓存
(2)用法案例
//对象 @Cacheable(value = {"product"},key="#root.methodName") //分⻚ @Cacheable(value ={"product_page"},key="#root.methodName +#page+'_'+#size")
(3)spEL表达式
- methodName当前被调用的方法名
- root.methodname
- args当前被调用的方法的参数列表
- root.args[0]
- result方法执行后的返回值
- result
9.3.自定义CacheManager
- 在springBoot的configuration类中添加修改redis缓存序列化器和配置manager过期时间
/** * 修改Redis缓存序列化器和配置manager过期时间 */ @Primary @Bean public RedisCacheManager cacheManager1Hour(RedisConnectionFactory connectionFactory){ RedisCacheConfiguration config = instanceConfig(3600L); return RedisCacheManager.builder(connectionFactory).cacheDefaults(config).transactionAware().build(); } @Bean public RedisCacheManager cacheManager1Day(RedisConnectionFactory connectionFactory){ RedisCacheConfiguration config = instanceConfig(3600 * 24L); return RedisCacheManager.builder(connectionFactory).cacheDefaults(config).transactionAware().build(); } @Bean public RedisCacheManager cacheManager10Min(RedisConnectionFactory connectionFactory){ RedisCacheConfiguration config = instanceConfig(600L); return RedisCacheManager.builder(connectionFactory).cacheDefaults(config).transactionAware().build(); } /** * 序列化机制 * @param ttl * @return */ private RedisCacheConfiguration instanceConfig(Long ttl) { Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class); ObjectMapper objectMapper = new ObjectMapper(); objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); objectMapper.registerModule(new JavaTimeModule()); // 去掉各种@JsonSerialize注解的解析 objectMapper.configure(MapperFeature.USE_ANNOTATIONS, false); // 只针对⾮空的值进⾏序列化 objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); // 将类型序列化到属性json字符串中 objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY); jackson2JsonRedisSerializer.setObjectMapper(objectMapper); return RedisCacheConfiguration.defaultCacheConfig() .entryTtl(Duration.ofSeconds(ttl)) .disableCachingNullValues() .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer)); }
9.4.自定义缓存KeyGenerator
- key规则定义麻烦,支持自定义的规则,同样在springBoot的configuration中进行配置
/** * keyGenerator自定义key的规则 */ @Bean public KeyGenerator springCacheDefaultKeyGenerator(){ return new KeyGenerator() { @Override public Object generate(Object o, Method method, Object... objects) { return o.getClass().getSimpleName()+":"+method.getName()+":"+ StringUtils.arrayToDelimitedString(objects,":"); } }; }
java代码实现
key 属性和keyGenerator属性只能⼆选⼀
@Cacheable(value = {"product"},keyGenerator ="springCacheCustomKeyGenerator", cacheManager ="cacheManager1Minute")
9.5.CachePut注解
- CachePut注解
- 根据方法的请求参数对其结果进行缓存,每次都会触发真实方法的调用
- value缓存名称,可以有多个
- key缓存的key规则,可以用springEL表达式,默认是方法参数组合
- condition缓存条件,使用springEL编写,返回true才缓存
@CachePut(value = {"product"},key = "#productDO.id")//常用于修改的方法上,修改数据库,然后修改对应的缓存
9.6.CacheEvict注解
- CacheEvict注解
- 从缓存中移除相应数据,触发缓存删除的操作
- value缓存名称,可以有多个
- key 缓存的key规则,可以⽤springEL表达式,默认是⽅法参数组合
- beforeInvocation = false
- 缓存的清除是否在方法之前执行,默认代表缓存清除操作是在方法执行之后执行
- 如果出现异常缓存就不会清除
- beforeInvocation = true
- 代表清除缓存操作实在方法执行之前,无论方法是否出现异常,缓存都清除
@CacheEvict(value = {"product"},key = "#root.args[0]")
9.7.Caching注解
- Caching注解
- 组合多个Cache注解使用
- 允许在同一方法上使用多个@Cacheable、@CachePut、@CacheEvict注释
@Caching( cacheable = { @Cacheable = (value = "product",key = "#id"), }, put = { @CachePut(value ="product",key = "#id"), @CachePut(value ="product",key = "'stock:'+#id") } )
10.Redis6持久化配置-RDB和AOF
10.1.Redis6.x持久化操作-RDB
(1)Redis持久化介绍
- Redis时一个内存数据库,如果没有配置持久化,redis重启后数据就会全部丢失。
- 因此开启redis的持久化功能,将数据保存到磁盘上,当redis重启后,可以从磁盘中恢复数据。
(2)两种持久化方式
- RDB(Redis DataBase)
- AOF(append only file)
- (3)RDB持久化介绍
- 在指定的时间间隔内将内存中的数据集快照写入磁盘
- 默认的文件名为dump.rdb
- 产生快照的情况
- save
- 会阻塞当前Redis服务器,执行save命令期间,Redis不能处理其他命令,直到RDB过程完成为止
- bgsave
- fork创建子进程,RDB持久化过程由子进程负责,会在后台异步进行快照操作,快照同时还可以响应客户端请求
- 自动化
- 配置文件来完成,配置redis触发Redis的RDB持久化条件,比如“save m n”。表示m秒内数据集存在n次修改时,会自动触发bgsave
- 主从架构
- 从服务器同步数据的时候,会发送sync执行同步操作,master主服务器就会执行bgsave
(4)优点和缺点
优点
- RDB文件紧凑,全量备份,适合用于进行备份和灾难恢复
- 在恢复大数据集时的速度比AOF的恢复速度要快
- 生成的是一个紧凑的文件
缺点
- 每次快照时一次全量的备份,fork子进程进行后台操作,子进程存在开销
- 在快照持久化期间修改的数据不会被保存,可能丢失数据
- (5)核心配置
- dir 持久化文件的路径
- dbfilename 文件名
#任何ip可以访问 bind 0.0.0.0 #守护进程 daemonize yes #密码 requirepass 123456 #日志文件 logfile "/user/local/redis/log/redis.log" #持久化文件名 dbfilename xdclass.rdb #持久化文件路径 dir /usr/local/redis/data #关闭rdb #save "" #持久化策略,10s内有1个key改动,执行快照 save 10 1 #导出rdb数据库文件压缩字符串和对象,默认时yes,会浪费CPU但是节省空间 rdbcompression yes #导入时是否检查 rdbchecksum yes
(6)配置文件触发
#关闭RDB save "" #10秒2个key变动触发RDB save 10 2 #100秒5个key变动触发RDB save 100 5
(7)Linux内存分配策略
0 表示内核将检查是否有足够的可用内存供应用进程使用,如果有足够的可用内存,内存申请允许,否则,内存申请失败,并把错误返回给前台 1 表示内核允许分配所有的物理内存,而不管当前内存状态如何 2 表示内核允许分配超过所有物理内存和交换空间总和的内存 解决方式 echo > /proc/sys/vm/overcommit_memory 持久化配置 vim /etc/sysctl.conf 改为 vm.overcommit_memory=1 修改sysctl.conf后,需要执⾏ sysctl -p 以使⽣效
10.2.Redis6.x持久化操作-AOF
(1)AOF持久化介绍
- append only file ,追加文件的方式,文件容易被人读懂
- 以独立日志的方式记录每次写命令,重启时在重新执行AOF中的命令达到恢复数据的目的
- 写入过程中宕机,也不会影响之前的数据,可以通过redis-check-aof检查修复问题
(2)配置
- appendonly yes,默认不开启,开启aof持久化
- AOF文件名通过appendfilename配置设置,默认文件名是appendonly.aof
- 存储路径同RDB持久化方式一致,使用dir配置
(3)核心原理
- Redis每次写入命令会追加到aof_buf(缓冲区)
- AOF缓冲区根据对应的策略向磁盘做同步操作
- 高频的AOF会带来影响,特别是每次刷盘
- (4)提供了3种同步方式,在性能和安全方面做出平衡
- appendfsync always:每次有数据修改发生时都会写入AOF文件,消耗性能多
- appendfsync everysec:每秒同步一次,该策略为AOF的缺省策略
- appendfsync no:不主从同步,有草祖宗系统自动调度刷磁盘,性能是最好的,但是最不安全
appendonly yes appendfilename "xdclass.aof" appendfsync everysec
(5)rewrite重写介绍
- AOF文件越来越大,需要定期对AOF文件进行重写达到压缩
- 旧的AOF文件含有无效命令会被忽略,保留最新的数据命令
- 多条写命令可以合并为一个
- AOF重写降低了文件占用空间
- 更小的AOF文件可以更快的被Redis加载
(6)重写触发配置
- 手动触发
- 直接调用bgrewriteaof命令
- 自动触发
- auto-aof-rewrite-min-size:表示运行AOF重写时文件最小体积,默认64mb
- auto-aof-rewrite-percentage:代表当前AOF文件空间和上一次重写后AOF文件空间(aof_base_size)的比值。
(7)aof常用配置
# 是否开启aof appendonly yes # ⽂件名称 appendfilename "appendonly.aof" # 同步⽅式 appendfsync everysec # aof重写期间是否同步 no-appendfsync-on-rewrite no # 重写触发配置 #AOF文件最小重写大小,只有AOF文件大小大于该值的时候才可以重写,默认64mb auto-aof-rewrite-min-size 64mb #当前AOF文件大小和最后一次重写后的大小之间的比率等于指定的增长率重写,100 表示当最后一次压缩为150 ,那么就300的时候进行压缩 auto-aof-rewrite-percentage 100 # 加载aof时如果有错如何处理 # yes表示如果aof尾部⽂件出问题,写log记录并继续执⾏。 #no表示提示写⼊等待修复后写⼊ aof-load-truncated yes
(8)重写前后对比
10.3.AOF和RDB的选择和混合模式
(1)Redis提供了不同的持久化选项
- RDB持久化以指定的时间间隔执行数据集的时间点快照。
- AOF持久化记录服务器接收的每个写入操作,将在服务器启动时再次读取,
- 重建原始数据集。使与Redis本身相同的格式以仅追加的方式记录命令,当文件太大时,Redis能够重写。
(2)RDB的优缺点
- 优点:
- RDB最⼤限度地提⾼了Redis的性能,⽗进程不需要参与磁盘I/O
- RDB⽂件紧凑,全量备份,适合⽤于进⾏备份和灾难恢复
- 在恢复⼤数据集时的速度⽐ AOF 的恢复速度要快
- ⽣成的是⼀个紧凑压缩的⼆进制⽂件
- 缺点:
- 如果您需要在Redis停⽌⼯作时(例如断电后)将数据丢失的可能性降⾄最低,则RDB并不好
- RDB经常需要fork才能使⽤⼦进程持久存储在磁盘上。如果数据集很⼤,Fork可能会⾮常耗时
(3)AOF的优缺点
- 优点:
- 数据更加安全
- 当Redis AOF⽂件太⼤时,Redis能够在后台⾃动重写AOF
- AOF以易于理解和解析的格式,⼀个接⼀个地包含所有操作的⽇志
- 缺点:
- AOF⽂件通常⽐同⼀数据集的等效RDB⽂件⼤
- 根据确切的fsync策略,恢复的时候AOF可能⽐RDB慢
(4)线上系统怎末处理
- RDB持久化与AOF持久化⼀起使⽤
- 如果Redis中的数据并不是特别敏感或者可以通过其它⽅式重写⽣成数据
- 集群中可以关闭AOF持久化,靠集群的备份⽅式保证可⽤性
- ⾃⼰制定策略定期检查Redis的情况,然后可以⼿动触发备份、重写数据
- 采⽤集群和主从同步
- (5)Redis4.0后开始的rewrite支持混合模式
- 就是rdb和aof⼀起⽤
- 直接将rdb持久化的⽅式来操作将⼆进制内容覆盖到aof⽂件中,rdb是⼆进制,所以很⼩
- 有写⼊的话还是继续append追加到⽂件原始命令,等下次⽂件过⼤的时候再次rewrite
- 默认是开启状态
- 好处:
- 混合持久化结合了RDB持久化和AOF持续化的优点,采取了rdb的文件小易于灾难恢复
- 同时结合AOF,增量的数据以AOF⽅式保存了,数据更少的丢失
- 坏处:
- 前部分是RDB格式,是⼆进制,所以阅读性较差
- 数据恢复:
- 先看是否存在aof⽂件,若存在则先按照aof⽂件恢复,aof⽐rdb全,且aof⽂件也rewrite成rdb⼆进制格式
- 若aof不存在,则才会查找rdb是否存在
11.Redis6服务端配置info+config命令
11.1.info命令介绍
- 服务器的各种信息和统计数值
Server: #有关redis服务器的常规信息 redis_mode:standalone #运行模式,单机或者集群 multiplexing_api:epoll #redis所使用的事件处理机制 run_id:3abd26c33dfd059e87a0279defc4c96c13962e #redis服务器的随机标识符(用于sentinel和集群) config_file:/usr/local/redis/conf/redis/conf #配置文件路径 Clinets: #客户端连接部分 connected_clients:10 #已连接客户端的数量(不包括slave连接的客户端) Memory: #内存消耗相关信息 userd_memory:874152 #使用内存 used_memory_human:853.66K #以⼈类可读的格式返回 Redis 分配的内存总量 user_memory_rss:2834432 #系统给redis分配的内存即常驻内存,和top 、 ps 等命令的输出⼀致 used_memory_rss_human:2.70M # 以⼈类可读的格式返回系统redis分配的常驻内存top、ps等命令的输出⼀致 used_memory_peak:934040 #系统使用的峰值大小 used_memory_peak_human:912.15K total_system_memory:1039048704 #操作系统的总字节 total_system_memory_human:990.91M used_memory_lua:37888 # lua引擎使⽤的内存 used_memory_lua_human:37.00K maxmemory:0 #最大内存的配置值,0表示不限制 maxmemory_human:0B maxmemory_policy:noeviction #达到最⼤内存配置值后的策略 Persistence: #rdb和aof相关信息 rdb_bgsave_in_progress:0 #标识rdb save是否进⾏中 rdb_last_bgsave_status:ok # 上次的save操作状态 rdb_last_bgsave_time_sec:-1 # 上次rdb save操作使⽤的时间(单位s) rdb_current_bgsave_time_sec:-1 #如果rdbsave操作正在进⾏,则是所使⽤的时间 aof_enabled:1 #是否开启aof,默认没开启 aof_rewrite_in_progress:0 # 标识aof的rewrite操作是否在进⾏中 aof_last_rewrite_time_sec:-1 #上次rewrite操作使⽤的时间(单位s) aof_current_rewrite_time_sec:-1 #如果rewrite操作正在进⾏,则记录所使⽤的时间 aof_last_bgrewrite_status:ok #上次rewrite操作的状态 aof_current_size:0 # aof当前⼤⼩ Stats: #一版统计 evicted_keys:0 #因为内存⼤⼩限制,⽽被驱逐出去的键的个数 Replication: #主从同步信息 role:master #角色 connected_slaves:1 #连接的从库数 master_sync_in_progress:0 #标识主redis正在同步到从redis Cluster: #集群部分 cluster_enabled:0 # 实例是否启⽤集群模式 Keyspace: #数据库相关统计 db0:keys=4,expires=0,avg_ttl=0 # db0的key的数量,带有⽣存期的key的数,平均存活时间
11.2.config命令介绍
- 可以动态的调整Redis服务器的配置(configuration)而无需重启
- config get xxx ,config set key “xxxx”
timeout #客户端连接时的超时时间,单位为秒。当客户端在这段时间内没有发出任何指令,那么关闭该连接 databases #设置数据库的个数,可以使⽤ SELECT 命令来切换数据库。默认使⽤的数据库是 0 save #设置 Redis 进⾏rdb持久化数据库镜像的频率。 rdbcompression #在进⾏镜像备份时,是否进⾏压缩 slaveof #设置该数据库为其他数据库的从数据库 masterauth #当主数据库连接需要密码验证时,在这⾥配置 maxclients #限制同时连接的客户数量,当连接数超过这个值时,redis 将不再接收其他连接请求,返回error maxmemory #设置 redis 能够使⽤的最⼤内存,
- maxmemory #设置redis能够使用的最大内存
- 备注
- 防止所有内存超过服务器物理内存,maxmemory限制的时Redis实际使用的内存量,也就是used_memory统计项对应的内存
- 由于内存碎片率的存在,实际消耗的内存可能会比maxmemory设置的更⼤,实际使⽤时要小心这部分内存溢出
- 默认⽆限使⽤服务器内存, 为防⽌极端情况下导致系统内存耗尽, 建议所有的Redis进程都要配置maxmemory
- 在64bit系统下,maxmemory设置为0表示不限制Redis内存使⽤,在32bit系统下,maxmemory不能超过3GB
- 注意:redis在占用的内存超过指定的maxmemory之后,通过maxmemory_policy确定redis是否释放内存以及如何释放内存
12.Redis6的key过期时间删除策略
(1)背景
- redis的key配置了过期时间,这个是怎么被删除的
- redis数据明明过期了,怎末还占用内存
- redis只能用10G,往里面写20G会发生什么
(2)Redis key过期策略
- 定期删除+惰性删除
(3)Redis如何淘汰过期的key
- 定期删除:
- 隔一段时间,就会随机抽取一些设置了过期时间的key,检查是否过期,如果过期了就删除
- 定期删除可能会导致很多过期的key到了时间但是并没有被删除,这块就用到惰性删除
- 惰性删除:
- 当一些用户尝试访问它时,key会被发现并主动的过期,这会惰性删除算法会删除key
- 放任键过期不管,但是每次从键空间中获取键时,都检查取得的键是否过期,如果过期了就删除
- Redis服务器实际使⽤的是惰性删除和定期删除两种策略:通过配合使⽤这两种删除策略,服务器可以很好地在合理使⽤CPU时间和避免浪费内存空间之间取得平衡。
问题:
如果定期删除漏掉了很多过期key,然后你也没有及时去查,也就没有走惰性删除,这回大量的过期key就会堆积在内存中,导致redis内存消耗尽了,就需要走内存淘汰机制。 注意:设计缓存中间件,可以参考redis的key过期淘汰方式和内存不足淘汰方式
(4)Redis key内存淘汰策略
redis在占⽤的内存超过指定的maxmemory之后,通过maxmemory_policy确定redis是否释放内存以及如何释放内存
- 策略
volatile-lru(least recently used) 最近最少使⽤算法,从设置了过期时间的键中选择空转时间最⻓的键值对清除掉; volatile-lfu(least frequently used) 最近最不经常使⽤算法,从设置了过期时间的键中选择某段时间之内使⽤频次最⼩的键值对清除掉; volatile-ttl 从设置了过期时间的键中选择过期时间最早的键值对清除 (删除即将过期的) volatile-random 从设置了过期时间的键中,随机选择键进⾏清除; allkeys-lru 最近最少使⽤算法,从所有的键中选择空转时间最⻓的键值对清除; allkeys-lfu 最近最不经常使⽤算法,从所有的键中选择某段时间之内使⽤频次最少的键值对清除; allkeys-random 所有的键中,随机选择键进⾏删除; noeviction 不做任何的清理⼯作,在redis的内存超过限制之后,所有的写⼊操作都会返回错误;但是读操作都能正常的进⾏;
注意:config配置的时候 下划线_的key需要⽤中横线- 127.0.0.1:6379> config set maxmemory_policy volatile-lru (error) ERR Unsupported CONFIG parameter:maxmemory_policy 127.0.0.1:6379> config set maxmemory-policy volatile-lru OK
8种 (1)不做处理(默认的) (2)从所有key中随机删除 (3)从所有key中找出访问次数少的,不怎么使用的 (4)从所有key中找出最近空转时间最长的 (5)从设置过期的key中随机删除 (6)从设置过期key中最近空转时间最长的 (7)从过期key中,快要到期的key中删除 (8)从过期key中删除最近不常用的
13.Redis高可用之主从复制
13.1.Redis6主从复制+读写分离
- 架构:一主二从架构搭建
(1)背景
- 单机部署简单,但是可靠性低,其不能很好的利用CPU多核处理
- 生产环境-必须要保证高可用-一般不可能单机部署
- 读写分离时可用性要求不高、性能要求不高、数据规模小的情况
(2)目标
- 读写分离,扩展主节点的读能力,分担主节点读压力
- 容灾恢复,一旦主节点宕机,从节点作为主节点的备份可以随时顶上来
(3)主从复制架构环境搭建准备
- 配置
#创建三个存放redis.conf的配置文件 mkdir -p /data/redis/master/data mkdir -p /data/redis/slave1/data mkdir -p /data/redis/slave2/data #从节点设置只读(默认) replica-read-only yes #从节点访问主节点的密码,和requirepass一样,注意主节点也要配下这个,因为主节点宕机后会重新选取主节点 masterauth 123456 #哪个主节点进行复制 replicaof 8.129.113.233 6379
- 创建配置文件主节点的redis.conf
bind 0.0.0.0 port 6379 daemonize yes requirepass "123456" logfile "/usr/local/redis/log/redis_master.log" dbfilename "xdclass_master.rdb" dir "/usr/local/redis/data" appendonly yes appendfilename "appendonly_master.aof" masterauth "123456"
- 创建两个从节点配置⽂件redis.conf
bind 0.0.0.0 port 6380 daemonize yes requirepass "123456" logfile "/usr/local/redis/log/redis_slave1.log" dbfilename "xdclass_slave1.rdb" dir "/usr/local/redis/data" appendonly yes appendfilename "appendonly_slave1.aof" replicaof 8.129.113.233 6379 masterauth "123456"
bind 0.0.0.0 port 6381 daemonize yes requirepass "123456" logfile "/usr/local/redis/log/redis_slave2.log" dbfilename "xdclass_slave2.rdb" dir "/usr/local/redis/data" appendonly yes appendfilename "appendonly_slave2.aof" replicaof 8.129.113.233 6379 masterauth "123456"
(4)启动主节点和从节点
#启动主 ./redis-server /data/redis/master/data/redis.conf #启动从 ./redis-server /data/redis/slave1/data/redis.conf ./redis-server /data/redis/slave2/data/redis.conf
13.2.主从复制读写分离原理解析
(1)主从复制分两种(主从刚连接时,进行全量同步,全量同步结束后,进行增量同步)
- 全量复制
- 刚连接时,从节点会向主节点发送一个sync指令,master服务器会开启一个后台进程用于redis的数据生成一个rdb文件
主服务器会缓存所有接受到的来自客户端的写命令,当后台保存进程处理完毕后,会将该rdb文件传递给slave
slave服务器会将rdb文件保存在磁盘并通过读取该文件将数据加载到内存
在此之后master服务器会将此期间缓存的命令通过redis传输协议发送给slave服务器
然后slave服务器将这些命令依次作用在自己的服务器上,保证主从数据的一致性
- 增量复制
- slave初始化后开始正常工作时主服务器发生的写操作同步到从服务器的过程
- 服务器每执行一个写命令就会向从服务器发送相同的写命令,从服务器接受并且执行
- (2)特点
- 主从复制对于主/从redis服务器来说是非阻塞的,所以同步期间都可以正常处理外界请求
- 一个主redis可以包含多个从redis,每个从redis可以接受来其他从redis服务器的连接
- 从节点不会让key过期,而是主节点的key过期删除后,成为del命令传到从节点进行删除
(3)加速复制
- 完全重新同步需要在磁盘上创建⼀个RDB⽂件,然后加载这个⽂件以便为从服务器发送数据
- 在⽐较低速的磁盘,这种操作会给主服务器带来较⼤的压⼒
- 新版支持无磁盘的复制,子进程直接将RDB通过网络发送给从服务器,不使用磁盘作为中间存储
- repl-diskless-sync yes(默认是no)
(4)主从断开连接
- 如果遭遇连接断开,重新连接之后可以从中断处继续进⾏复制,⽽不必重新同步
- 2.8版本后 部分重新同步这个新特性内部使⽤PSYNC命令,旧的实现中使⽤SYNC命令
14.Redis6节点高可用监控之Sentinel
(1)背景
- Redis主从复制,当主机宕机后,需要手动将从服务器切换成主服务器,人工干预费事费力,还会造成一段时间内服务不可用
- (2)哨兵模式介绍
- Redis提供了哨兵的命令,是一个独立的进程
- 原理:哨兵通过发送命令给多个节点,等待Redis服务器响应,从⽽监控运⾏的多个Redis实例的运⾏情况
- 当哨兵监测到master宕机,会⾃动将slave切换成master,通过通知其他的从服务器,修改配置⽂件切换主机
(3)Sentinel三大工作任务
- **监控:**Sentinel会不断的检查你的主服务器和从服务器是否运行正常
- **提醒:**当被监控的某个redis服务器出现问题时,Sentinel可以通过API向管理员或者其他应用程序发送通知
- **自动故障迁移:**当一个主服务器不能正常工作时,
Sentinel会开始一次自动故障迁移操作,它会将失效主服务器的其中一个从服务器升级为新的主服务器,并让失效主服务器的其他从服务器改为连接新的主服务器,当客户端试图连接失效的主服务器时,集群也会向客户端返回新的服务器地址。
- 注意:一般使用多个哨兵进行监控,各个哨兵之间还会进行监控,形成哨兵模式
(4)多哨兵模式下线名称介绍
- 主观下线(Subjectively Down, 简称 SDOWN)
- 是单个Sentinel 实例对服务器做出的下线判断,⽐如⽹络问题接收不到通知等
⼀个服务器没有在 down-after-milliseconds 选项所指定的时间内, 对向它发送 PING 命令的 Sentinel返回⼀个有效回复(valid reply), 那么 Sentinel就会将这个服务器标记为主观下线
客观下线(Objectively Down, 简称 ODOWN)
- 指的是多个 Sentinel 实例在对同⼀个服务器做出SDOWN 判断, 并且通过 SENTINEL is-master-down-by-addr 命令互相交流之后, 得出的服务器下线判断
- ⼀个 Sentinel 可以通过向另⼀个 Sentinel 发送SENTINEL is-master-down-by-addr 命令来询问对⽅是否认为给定的服务器已下线
- 客观下线条件只适⽤于主服务器
- 仲裁 qurum
- Sentinel 在给定的时间范围内, 从其他 Sentinel 那⾥接收到了【⾜够数量】的主服务器下线报告, 那么 Sentinel 就会将主服务器的状态从主观下线改变为客观下线
- 这个【⾜够数量】就是配置⽂件⾥⾯的值,⼀般是Sentinel个数的⼀半加1,⽐如3个Sentinel则就设置为2
down-after-milliseconds 是⼀个哨兵在超过规定时间依旧没有得到响应后,会⾃⼰认为主机不可⽤
当拥有认为主观下线的哨兵达到sentinel monitor所配置的数量时,就会发起⼀次投票,进⾏failover
(5)核心流程
- 每秒ping,超过时间不响应则任务主管下线
- 满足多个,则认为是客观下线
- 投票选择主节点
- 如果没有足够的节点同意master下线,则状态会被移除
- (6)环境准备
- 配置三个哨兵,每个哨兵的配置都是一样的
- 启动顺序,先启动主节点在启动从节点,最后启动三个哨兵
- 哨兵端口是【26379】记得开发
#不限制ip bind 0.0.0.0 # 让sentinel服务后台运⾏ daemonize yes # 配置监听的主服务器,mymaster代表服务器的名称,⾃定义,172.18.172.109 代表监控的主服务器,6379代表端⼝,2代表只有两个或两个以上的哨兵认为主服务器不可⽤的时候,才会进⾏failover操作。 sentinel monitor mymaster 172.18.172.109 6379 2 # sentinel auth-pass定义服务的密码,mymaster是服务名称,123456是Redis服务器密码 sentinel auth-pass mymaster 123456 #超过5秒master还没有连接上,则认为master已经停⽌ sentinel down-after-milliseconds mymaster 5000 #如果该时间内没完成failover操作,则认为本次failover失败 sentinel failover-timeout mymaster 30000
- 在/usr/local/redis/conf创建三个文件sentinel-1.conf、sentinel-2.conf、sentinel-3.conf
port 26379 bind 0.0.0.0 daemonize yes pidfile "/var/run/redis-sentinel-1.pid" logfile "/var/log/redis/sentinel_26379.log" dir "/tmp" sentinel monitor mymaster 8.129.113.233 6379 2 sentinel down-after-milliseconds mymaster 5000 sentinel auth-pass mymaster 123456 sentinel failover-timeout mymaster 30000
port 26380 bind 0.0.0.0 daemonize yes pidfile "/var/run/redis-sentinel-2.pid" logfile "/var/log/redis/sentinel_26380.log" dir "/tmp" sentinel monitor mymaster 8.129.113.233 6379 2 sentinel down-after-milliseconds mymaster 5000 sentinel auth-pass mymaster 123456 sentinel failover-timeout mymaster 30000
port 26381 bind 0.0.0.0 daemonize yes pidfile "/var/run/redis-sentinel-3.pid" logfile "/var/log/redis/sentinel_26381.log" dir "/tmp" sentinel monitor mymaster 8.129.113.233 6379 2 sentinel down-after-milliseconds mymaster 5000 sentinel auth-pass mymaster 123456 sentinel failover-timeout mymaster 30000
(7)启动哨兵集群
./redis-server /usr/local/redis/conf/sentinel-1.conf --sentinel ./redis-server /usr/local/redis/conf/sentinel-2.conf --sentinel ./redis-server /usr/local/redis/conf/sentinel-3.conf --sentinel
(8)SpringBoot/微服务cloud整合Redis主从+Sentinel哨兵
- 注释掉host和port
- 新增配置
redis: #host: 8.140.116.67 #port: 6379 sentinel: master: mymaster nodes: 8.140.116.67:26379,8.140.116.67:26380,8.140.116.67:26381 password: 123456 client-type: jedis
15.Redis6节点高可用之Cluster集群
(1)背景
- Sentinel解决了主从架构故障自动迁移的问题
- 但是Master主节点的写能力和存储能力依旧受限
- 使用Redis的集群Cluster就是为了解决单机Redis容量有限的问题,将数据一定的规划分配到多台机器
(2)什么是集群Cluster
- 是一组项目独立的、通过高速网络互联的计算机,它们构成了⼀个组,并以单⼀系统的模式加以管理
- (3)Redis集群模式介绍
- Cluster模式是Redis3.0开始推出
- 采用务无中心结构,每个节点保存数据和整个集群状态,每个节点都和其他所有节点连接
- 官⽅要求:⾄少6个节点才可以保证⾼可⽤,即3主3从;扩展性强、更好做到⾼可⽤
- 各个节点会互相通信,采⽤gossip协议交换节点元数据信息
- 数据分散存储到各个节点上
(4)Cluster数据分片和虚拟哈希槽介绍
- 常见的数据分区算法
- 哈希取模
- 对选择的partitioning key计算其哈希值,得到的哈希值就是对应的分区
- 范围分片
- 通过确定分区键是否在某个范围内来选择分区
- 一致性Hash分区
- redis cluster集群没有采用一致性哈希方案,而是采用【数据分片】中的哈希槽来进行数据存储与读取的
(5)什么是Redis的哈希槽slot
- Redis集群预分好16384个槽,当需要在Redis集群中放置一个key-value时,根据CRC16(key)mod16384的值,决定key放在哪个桶中
(6)大体流程
假设主节点的数量为3,将16384个曹魏按照【用户自己的规则】取分配这三个节点,每个节点复制一部分槽位
- 节点1的槽位区间范围为0-5460
- 节点2的槽位区间范围为5461-10922
- 节点3的槽位区间范围为10923-16383
注意:从节点是没有槽位的,只有主节点才有
(7)存储查找
对要存储查找的键进行crc16哈希运算,得到一个值,并取模16384,判断这个值在哪个节点的范围区间,假设crc16(“test_key”)%16384=3000,就是节点1,crc16算法不是简单的hash算法,是一种校验算法
- 使⽤哈希槽的好处就在于可以⽅便的添加或移除节点。
- 当需要增加节点时,只需要把其他节点的某些哈希槽挪到新节点就可以了
- 当需要移除节点时,只需要把移除节点上的哈希槽挪到其他节点就⾏了
(8)Cluster集群环境准备
- 旧版本需要使用ruby语言进行构建,新版5之后直接redis-cli即可
- 6个节点,三主双从,主从节点会自动分配,不是人工指定
- 主节点故障后,从节点会替换主节点
- 节点:
6381、6382 6383、6384 6385、6386
- 配置
bind 0.0.0.0 port 6381 daemonize yes requirepass "123456" dbfilename "xdclass_6381.rdb" logfile "/usr/local/redis/log/redis_6381.log" dir "/usr/local/redis/data" appendonly yes appendfilename "appendonly_6381.aof" masterauth "123456" #是否开启集群 cluster-enabled yes #生成node文件,记录集群节点信息,默认为nodes.conf,防止冲突,改为nodes-6381.conf cluster-config-file nodes-6381.conf #节点连接超时时间 cluster-node-timeout 20000 #集群节点的ip,当前节点ip cluster-announce-ip 172.18.172.109 #集群节点映射端口 cluster-announce-bus-port 16381 #集群节点总线端口,节点之间互相通信,常规端口+1万 cluster-announce-bus-port 16381
注意:阿里云开放网络安全组
(9)Cluster集群三主三从搭建实战
- 启动六个节点
./redis-server ../conf/cluster/redis_6381.conf ./redis-server ../conf/cluster/redis_6382.conf ./redis-server ../conf/cluster/redis_6383.conf![在这里插入图片描述](https://ucc.alicdn.com/images/user-upload-01/ccf4aa46ba0b451a933bd2be22c3fab8.jpeg#pic_center) ./redis-server ../conf/cluster/redis_6384.conf ./redis-server ../conf/cluster/redis_6385.conf ./redis-server ../conf/cluster/redis_6386.conf
- 现在启动还没开启集群
- 加入集群
--cluster #构建集群中全部节点信息 --cluster-replicas 1 #主从节点的比例,1表示1主1从的方式 ./redis-cli -a 123456 --cluster create 8.140.116.67:6381 8.140.116.67:6382 8.140.116.67:6383 8.140.116.67:6384 8.140.116.67:6385 8.140.116.67:6386 --cluster-replicas 1
- 检查状态信息(其中一个节点执行即可)
./redis-cli -a 123456 --cluster check 8.140.116.67:6381
(10)Cluster集群读写命令
./redis-cli -c -a 123456 -p 6379 #集群信息 cluster info #节点信息 cluster nodes
- 测试集群读写命令set/get
key哈希运算计算槽位置 槽在当前节点的话直接插入/读取。否则自动转向对应的节点 操作都是主节点,从节点只是备份
- 流程解析
- 主节点
- 从节点
启动应用->加入集群->从节点请求复制主节点(主从复制一样)
(11)Cluster集群整合SpringBoot2.X
- 修改配置文件
redis: #host: 8.140.116.67 #port: 6379 # sentinel: # master: mymaster # nodes: 8.140.116.67:26379,8.140.116.67:26380,8.140.116.67:26381 cluster: #命名的最多转发次数 max-redirects: 3 nodes: 8.140.116.67:6381,8.140.116.67:6382,8.140.116.67:6383,8.140.116.67:6384,8.140.116.67:6385,8.140.116.67:6386
注意:一定要在同一个网段
- 当kill掉主节点
- 从节点会尝试连接,知道连不上,把自己置成主节点
- 当原本的主节点挂掉之后,重新恢复,成为从节点,复制主节点的数据
16.新版Redis6核心特性
16.1.Redis6新特性-多线程
(1)支持多线程
- redis6多线程只是用来处理网络数据的读写和协议解析上,底层数据操作还是单线程
- 执行命令依旧是单线程,之所以这么设计是不想因为多线程而变的复杂,需要去控制key,lua,事务等等并发问题
(2)默认不开启
io-threads-do-reads yes io-threads 线程数
4核的机器建议设置为 2 或 3 个线程
8核的建议设置为 4或6个线程
注意:开启多线程后,是否会存在线程并发安全问题?
不会有安全问题,Redis的多线程部分只是用来吃力网络数据的读写和协议解析,执行命令仍然是单线程顺序执行
16.2.Redis6新特性-acl权限控制
(1)引入ACL(Access Control List)
之前的redis没有⽤户的概念,redis6引⼊了acl 可以给每个⽤户分配不同的权限来控制权限 通过限制对命令和密钥的访问来提⾼安全性,以使不受信任的客户端⽆法访问 提⾼操作安全性,以防⽌由于软件错误或⼈为错误⽽导致进程或⼈员访问 Redis,从⽽损坏数据或配置
(2)常用命令
acl list #当前启用的ACL规则 acl cat #支持的权限分类列表 acl cat hash #返回指定类别中的命令 acl setuser #创建和修改用户命令 acl deluser #删除用户命令
+<command>:将命令添加到⽤户可以调⽤的命令列表中,如+@hash -<command>:将命令从⽤户可以调⽤的命令列表中移除 #切换默认用户 auth default 123456 #例⼦ 密码 123 ,全部key,全部权限 acl setuser jack on >123 ~* +@all #例⼦ 密码 123 ,全部key,get权限 acl setuser jack on >123 ~* +get
参数 | 说明 |
user | 用户 |
default | 示默认⽤户名,或则⾃⼰定义的⽤户名 |
on | 表示是否启⽤该⽤户,默认为off(禁⽤) |
~* | 表示可以访问的Key(正则匹配) |
+@ | 表示⽤户的权限,“+”表示授权权限,有权限操作或访问,“-”表示还是没有权限; @为权限分类,可以通过 ACL CAT 查询⽀持的分类。+@all表示所有权限,nocommands 表示不给与任何命令的操作权限。 |
16.3.Redis6新特性-客户端缓存
client side caching客户端缓存