本片文章续《Spring Boot 入门 (九): 集成 Quartz 定时任务》. 本文主要基于 Redis 实现了 mybatis 二级缓存. 较 Redis 缓存, mybaits 自带缓存存在缺点 (自行谷歌). 本文是基于 docker 安装 Redis 主从模式.
1.Redis 安装
(1) 首先安装 Redis 集群模式, 建立 Redis 目录, 并编写主从模式 docker-compose.YAML 文件
- version: '3.1'
- services:
- master:
- image: Redis
- container_name: Redis-master
- ports:
- - 6379:6379
- slave1:
- image: Redis
- container_name: Redis-slave-1
- ports:
- - 6380:6379
- command: Redis-server --slaveof Redis-master 6379
- slave2:
- image: Redis
- container_name: Redis-slave-2
- ports:
- - 6381:6379
- command: Redis-server --slaveof Redis-master 6379
(2). 启动 docker-compose up -d
(3). 建立 sentinel 文件, 并编写 docker-compose.YAML 文件
- version: '3.1'
- services:
- sentinel1:
- image: Redis
- container_name: Redis-sentinel-1
- ports:
- - 26379:26379
- command: Redis-sentinel /usr/local/etc/Redis/sentinel.conf
- volumes:
- - ./sentinel1.conf:/usr/local/etc/Redis/sentinel.conf
- sentinel2:
- image: Redis
- container_name: Redis-sentinel-2
- ports:
- - 26380:26379
- command: Redis-sentinel /usr/local/etc/Redis/sentinel.conf
- volumes:
- - ./sentinel2.conf:/usr/local/etc/Redis/sentinel.conf
- sentinel3:
- image: Redis
- container_name: Redis-sentinel-3
- ports:
- - 26381:26379
- command: Redis-sentinel /usr/local/etc/Redis/sentinel.conf
- volumes:
- - ./sentinel3.conf:/usr/local/etc/Redis/sentinel.conf
从模式需要 sentinel 的 conf 文件, 我创建了 3 个容器, 所以这里需要 3 份 (sentinel1.conf sentinel2.conf sentinel3.conf), 内容是一模一样
- port 26379
- dir /tmp
- sentinel monitor mymaster 217.0.0.1 6379 2
- sentinel down-after-milliseconds rmaster 30000
- sentinel parallel-syncs mymaster 1
- sentinel failover-timeout mymaster 180000
- sentinel deny-scripts-reconfig yes
(4). 启动: docker-compose up -d
(5). 验证 Redis 是否已经成功启动
进入容器: docker exec -it Redis-sentinel-1 /bin/bash
连接 Redis:Redis-cli -p 26379
如下图, 表示启动成功
也可以通过桌面客户端查看是否启动成功, 如图, 我使用的 RedisDesktopManager 客户端
这里有个问题可以思考: 传统的 Redis 集群, 即 cluster, 也有主从模式, 现在为什么大家都选择 sentinel 主从模式?
2. 编写 RedisCache 工具类
网上一大堆, 根据自己需要选择合适的 utils(有的 utils 中方法很全)
- package com.learn.hello.system.utils;
- import lombok.extern.slf4j.Slf4j;
- import org.apache.ibatis.cache.Cache;
- import org.springframework.data.Redis.core.RedisCallback;
- import org.springframework.data.Redis.core.RedisTemplate;
- import org.springframework.data.Redis.core.ValueOperations;
- import java.util.concurrent.TimeUnit;
- import java.util.concurrent.locks.ReadWriteLock;
- import java.util.concurrent.locks.ReentrantReadWriteLock;
- /**
- * @ClassName RedisCache
- * @Deccription 通过 Redis 实现 mybaits 的二级缓存
- * @Author DZ
- * @Date 2020/1/12 22:41
- **/
- @Slf4j
- public class RedisCache implements Cache {
- private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
- private final String id; // cache instance id
- private RedisTemplate redisTemplate;
- private static final long EXPIRE_TIME_IN_MINUTES = 30; // Redis 过期时间
- public RedisCache(String id) {
- if (id == null) {
- throw new IllegalArgumentException("Cache instances require an ID");
- }
- this.id = id;
- }
- @Override
- public String getId() {
- return id;
- }
- /**
- * Put query result to Redis
- *
- * @param key
- * @param value
- */
- @Override
- public void putObject(Object key, Object value) {
- try {
- RedisTemplate redisTemplate = getRedisTemplate();
- ValueOperations opsForValue = redisTemplate.opsForValue();
- opsForValue.set(key, value, EXPIRE_TIME_IN_MINUTES, TimeUnit.MINUTES);
- log.debug("Put query result to redis");
- } catch (Throwable t) {
- log.error("Redis put failed", t);
- }
- }
- /**
- * Get cached query result from Redis
- *
- * @param key
- * @return
- */
- @Override
- public Object getObject(Object key) {
- try {
- RedisTemplate redisTemplate = getRedisTemplate();
- ValueOperations opsForValue = redisTemplate.opsForValue();
- log.debug("Get cached query result from redis");
- return opsForValue.get(key);
- } catch (Throwable t) {
- log.error("Redis get failed, fail over to db", t);
- return null;
- }
- }
- /**
- * Remove cached query result from Redis
- *
- * @param key
- * @return
- */
- @Override
- @SuppressWarnings("unchecked")
- public Object removeObject(Object key) {
- try {
- RedisTemplate redisTemplate = getRedisTemplate();
- redisTemplate.delete(key);
- log.debug("Remove cached query result from redis");
- } catch (Throwable t) {
- log.error("Redis remove failed", t);
- }
- return null;
- }
- /**
- * Clears this cache instance
- */
- @Override
- public void clear() {
- RedisTemplate redisTemplate = getRedisTemplate();
- redisTemplate.execute((RedisCallback) connection -> {
- connection.flushDb();
- return null;
- });
- log.debug("Clear all the cached query result from redis");
- }
- /**
- * This method is not used
- *
- * @return
- */
- @Override
- public int getSize() {
- return 0;
- }
- @Override
- public ReadWriteLock getReadWriteLock() {
- return readWriteLock;
- }
- private RedisTemplate getRedisTemplate() {
- if (redisTemplate == null) {
- redisTemplate = SpringContextHolder.getBean("redisTemplate");
- }
- return redisTemplate;
- }
- }
3. 在接口类增加 Redis 缓存注解
@CacheNamespace(implementation = RedisCache.class)
例如:
- @CacheNamespace(implementation = RedisCache.class)
- public interface RoleMapper extends MyMapper<Role> {
- List<Role> selectByCondition(ModelMap modelMap);
- Role selectById(int id);
- List<Role> selectAllRole();
- }
这里也可以直接在 MyMapper 父接口中增加注解, 这样, 所有的接口就不需要单独增加这个注解 (根据业务需要自行素选择).
4. 配置 application.YAML
- spring:
- Redis:
- lettuce:
- # 连接池配置
- pool:
- # 连接池中的最小空闲连接, 默认 0
- min-idle: 0
- # 连接池中的最大空闲连接, 默认 8
- max-idle: 8
- # 连接池最大阻塞等待时间 (使用负值表示没有限制), 默认 -1ms
- max-wait: -1ms
- # 连接池最大连接数 (使用负值表示没有限制), 默认 8
- max-active: 8
- # 集群模式
- # cluster:
- # nodes: 192.168.1.12:6379,192.168.1.12:6380,192.168.1.12:6381
- # 哨兵模式
- sentinel:
- master: mymaster
- nodes: 192.168.1.12:26379,192.168.1.12:26380,192.168.1.12:26381
- mybatis:
- mapper-locations: classpath:mapper/*.xml
- # 此配置的作用: xml 中不用写实体类的全路径
- type-aliases-package: com.learn.hello.modules.entity
- # 查询的 null 字段也返回
- configuration:
- call-setters-on-nulls: true
- # 开启二级缓存
- cache-enabled: true
5. 效果
当进行 CURD 操作时, 相关的检索 sql 语句就会缓存到 Redis, 如图:
当再次对相关数据进行 CRUD 时, 就会走缓存
来源: https://www.cnblogs.com/dz-boss/p/12190065.html