芋道 Spring Boot Redis 入门(下)

点击上方“芋道源码

做积极的人,而不是积极废人!

源码精品专栏

 

摘要: 原创出处 http://www.iocoder.cn/Spring-Boot/Redis/ 「芋道源码」欢迎转载,保留摘要,谢谢!

  • 4. 项目实践

    • 4.1 Cache Object

    • 4.2 数据访问层

    • 4.3 序列化

  • 5. 示例补充

    • 5.1 Pipeline

    • 5.2 Transaction

    • 5.3 Session

    • 5.4 Pub/Sub

    • 5.5 Script

  • 6. 尝试 Redisson

    • 6.1 快速入门

    • 6.2 Redis 分布式锁

    • 6.3 Redis 限流器

  • 666. 彩蛋


4. 项目实践

本小节,我们来分享我们在生产中的一些实践。关于这块,希望大家可以一起讨论,能够让我们的代码更加优雅干净。

4.1 Cache Object

在我们使用数据库时,我们会创建 dataobject 包,存放 DO(Data Object)数据库实体对象。

那么同理,我们缓存对象,怎么进行对应呢?对于复杂的缓存对象,我们创建了 cacheobject 包,和 dataobject 包同层。如:

service # 业务逻辑层
dao # 数据库访问层
dataobject # DO
cacheobject # 缓存对象

并且所有的 Cache Object 对象使用 CacheObject 结尾,例如说 UserCacheObject、ProductCacheObject 。

4.2 数据访问层

在我们访问数据库时,我们会创建 dao 包,存放每个 DO 对应的 Dao 对应。那么对于每一个 CacheObject 类,我们也会创建一个其对应的 Dao 类。例如说,UserCacheObject 对应 UserCacheObjectDao 类。示例代码如下:

@Repository
public class UserCacheDao {

    private static final String KEY_PATTERN = "user:%d"; // user:用户编号 <1>

    @Resource(name = "redisTemplate")
    @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
    private ValueOperations<String, String> operations; // <2>

    private static String buildKey(Integer id) { // <3>
        return String.format(KEY_PATTERN, id);
    }

    public UserCacheObject get(Integer id) {
        String key = buildKey(id);
        String value = operations.get(key);
        return JSONUtil.parseObject(value, UserCacheObject.class);
    }

    public void set(Integer id, UserCacheObject object) {
        String key = buildKey(id);
        String value = JSONUtil.toJSONString(object);
        operations.set(key, value);
    }

}
  • <1> 处,通过静态变量,声明 KEY 的前缀,并且使用冒号作为间隔

  • <3> 处,声明 KEY_PATTERN 对应的 KEY 拼接方法,避免散落在每个方法中。

  • <2> 处,通过 @Resource 注入指定名字的 RedisTemplate 对应的 Operations 对象,这样明确每个 KEY 的类型。

  • 剩余的,就是每个方法封装对应的操作。

可能会有胖友问,为什么不支持将 RedisTemplate 直接 Service 业务层调用呢?如果这样,我们业务代码里,就容易混杂着很多 Redis 访问代码的细节,导致很脏乱。我们试着把 RedisTemplate 想象成 Spring JDBCTemplate ,我们一定会声明对应的 Dao 类,访问数据库。所以,同理落。

那么还有一个问题,UserCacheDao 放在哪个包下?目前的想法是,将 dao 包下拆成 mysqlredis 包。这样,MySQL 相关的 Dao 放在 mysql 包下,Redis 相关的 Dao 放在 redis 。

4.3 序列化

在 「3. 序列化」 小节中,我们仔细翻看了每个序列化方式,暂时没有一个能够完美的契合我们的需求,所以我们直接使用最简单的 StringRedisSerializer 作为序列化实现类。而真正的序列化,我们在各个 Dao 类里,自己手动来调用。

例如说,在 UserCacheDao 示例中,已经看到了这么做了。这里还有一个细化点,虽然我们是自己手动序列化,可以自己简单封装一个 JSONUtil 类,未来如果我们想换 JSON 库,就比较方便了。其实,这个和 Spring Data Redis 所做的封装是一个思路。

5. 示例补充

像 String、List、Set、ZSet、Geo、HyperLogLog 等等数据结构的操作,胖友自己去用用对应的 Operations 操作类的 API 方法,就非常容易懂了,我们更多的,补充 Pipeline、Transaction、Pub/Sub、Script 等等功能的示例。

5.1 Pipeline

如果胖友没有了解过 Redis 的 Pipeline 机制,可以看看 《Redis 文档 —— Pipeline》 文章,批量操作,提升性能必备神器。

在 RedisTemplate 类中,提供了 2 组四个方法,用于执行 Redis Pipeline 操作。代码如下:

// <1> 基于 Session 执行 Pipeline
@Override
public List<Object> executePipelined(SessionCallback<?> session) {
    return executePipelined(session, valueSerializer);
}
@Override
public List<Object> executePipelined(SessionCallback<?> session, @Nullable RedisSerializer<?> resultSerializer) {
    // ... 省略代码
}

// <2> 直接执行 Pipeline
@Override
public List<Object> executePipelined(RedisCallback<?> action) {
    return executePipelined(action, valueSerializer);
}
@Override
public List<Object> executePipelined(RedisCallback<?> action, @Nullable RedisSerializer<?> resultSerializer) {
    // ... 省略代码
}
  • 两组方法的差异,在于是否是 Session 中执行。那么 Session 是什么呢?卖个关子,在 「5.3 Session」 中来详细解析。本小节,我们只讲 Pipeline + RedisCallback 的组合的方法。

  • 每组方法里,差别在于是否传入 RedisSerializer 参数。如果不传,则使用 RedisTemplate 自己的序列化相关的属性。

5.1.1 源码解读

在看具体的 #executePipelined(RedisCallback<?> action, ...) 方法的示例之前,我们先来看一波源码,这样我们才能更好的理解具体的使用方法。代码如下:

// RedisTemplate.java
@Override
public List<Object> executePipelined(RedisCallback<?> action, @Nullable RedisSerializer<?> resultSerializer) {
    // <1> 执行 Redis 方法
    return execute((RedisCallback<List<Object>>) connection -> {
        // <2> 打开 pipeline
        connection.openPipeline();
        boolean pipelinedClosed = false; // 标记 pipeline 是否关闭
        try {
            // <3> 执行
            Object result = action.doInRedis(connection);
            // <4> 不要返回结果
            if (result != null) {
                throw new InvalidDataAccessApiUsageException(
                        "Callback cannot return a non-null value as it gets overwritten by the pipeline");
            }
            // <5> 提交 pipeline 执行
            List<Object> closePipeline = connection.closePipeline();
            pipelinedClosed = true;
            // <6> 反序列化结果,并返回
            return deserializeMixedResults(closePipeline, resultSerializer, hashKeySerializer, hashValueSerializer);
        } finally {
            if (!pipelinedClosed) {
                connection.closePipeline();
            }
        }
    });
}
  • <1> 处,调用 #execute(RedisCallback action) 方法,执行 Redis 方法。注意,此处传入的 action 参数,不是我们传入的 RedisCallback 参数。我们的会在该 action 中被执行。

  • <2> 处,调用 RedisConnection#openPipeline() 方法,自动打开 Pipeline 模式。这样,我们就不需要手动去打开了。

  • <3> 处,调用我们传入的实现的 RedisCallback#doInRedis(RedisConnection connection) 方法,执行在 Pipeline 中,想要执行的 Redis 操作。

  • <4> 处,不要返回结果。因为 RedisCallback 是统一定义的接口,所以可以返回一个结果。但是在 Pipeline 中,未提交执行时,显然是没有结果,返回也没有意思。简单来说,就是我们在实现 RedisCallback#doInRedis(RedisConnection connection) 方法时,返回 null 即可。

  • <5> 处,调用 RedisConnection#closePipeline() 方法,自动提交 Pipeline 执行,并返回执行结果。

  • <6> 处,反序列化结果,并返回 Pipeline 结果。

至此,Spring Data Redis 对 Pipeline 的封装,我们已经做了一个简单的了解,实际就是经典的“模板方法”设计模式化的应用。下面,在让我们来看看 org.springframework.data.redis.core.RedisCallback<T> 接口,Redis 回调接口。代码如下:

// RedisCallback.java
public interface RedisCallback<T> {

    /**
     * Gets called by {@link RedisTemplate} with an active Redis connection. Does not need to care about activating or
     * closing the connection or handling exceptions.
     *
     * @param connection active Redis connection
     * @return a result object or {@code null} if none
     * @throws DataAccessException
     */
    @Nullable
    T doInRedis(RedisConnection connection) throws DataAccessException;
}
  • 虽然接口名是以 Callback 结尾,但是通过 #doInRedis(RedisConnection connection) 方法可以很容易知道,实际可以理解是 Redis Action ,想要执行的 Redis 操作。

  • 有一点要注意,传入的 connection 参数是 RedisConnection 对象,它提供的 'low level' 更底层的 Redis API 操作。例如说:

    // RedisStringCommands.java
    // RedisConnection 实现 RedisStringCommands 接口
    
    byte[] get(byte[] key);
    
    Boolean set(byte[] key, byte[] value);
    
    • 传入和返回的是二进制

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值