Redis 实战

3 篇文章 0 订阅

Redis

Redis 是一个开源的,高级的键值对存储和一个适用的解决方案,用于构建高性能,可扩展的 Web 应用程序。

场景:在互联网中经常用来缓存热点数据:1. redis 数据在内存中,可以保证读取的高效(接近每秒数十万次);2. 减少下层持久层数据库读取压力,像 mongodb,每秒近千次就有压力;3. redis 单线程运行,天然具备读写的原子性

使用:1. 先 get 读取 redis,没有读到再去 db;将 db 读到的数据 set 到 redis 中,返回数据。2. 更新:del 掉 redis 的数据,写数据库。

Redis 有六(五)种基本数据结构,分别是:string(字符串)、list(列表)、hash(字典)、set(集合)、zset(有序集合)和 Stream(流)。

Spring整合了整合了 Redis ,我们可以导入 Spring Data Redis 来实现对 Redis 的使用。

Spring Data Redis 提供了统一的操作模版(RedisTemplate)封装了对 Jedis、Lettuce 的 API 操作,所以实际上访问的还是 Jedis、Lettuce 等 API。目前,Spring Data Redis 暂时只支持 Jedis、Lettuce 的内部封装,而 Redisson 是由 redisson-spring-data 来支持。

1. 在实际开发中,大部分还是使用 Jedis。
<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
            <exclusions>
                <!-- 去掉 Lettuce 的依赖,因为 Spring Boot 优先使用 Lettuce 作为 Redis 客户端 -->
                <exclusion>
                    <groupId>io.lettuce</groupId>
                    <artifactId>lettuce-core</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <!-- 引入 Jedis 的依赖,使 Spring Boot 实现对 Jedis 的自动化配置-->
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
        </dependency>

        <!-- 实现对 json 序列化工具-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.70</version>
        </dependency>
        <!---->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.13.2.2</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

配置文件:

spring:
  redis:
    host: 127.0.0.1
    port: 6379
    password:
    database: 0
    timeout: 0
    jedis:
      pool:
        max-active: 8
        max-idle: 8
        min-idle: 0
        max-wait: -1
2. RedisTemplate

RedisTemplate 提供了 Redis 操作不同类型的数据的模版,所以这个类一定要看一下。

3. 序列化

存储在 Redis 中的数据是二进制数据。这里用到了 org.springframework.data.redis.serializer.RedisSerializer<T>

	@Nullable
	byte[] serialize(@Nullable T t) throws SerializationException;

	@Nullable
	T deserialize(@Nullable byte[] bytes) throws SerializationException;

衍生出的序列化有四种方式:JDK 序列化方式(基本不用,存储带有十六进制信息)、String 序列化方式(常用)、JSON 序列化(常用)和 XML 序列化方式。

String 序列化:org.springframework.data.redis.serializer.StringRedisSerializer

	@Override
	public String deserialize(@Nullable byte[] bytes) {
		return (bytes == null ? null : new String(bytes, charset));
	}

	@Override
	public byte[] serialize(@Nullable String string) {
		return (string == null ? null : string.getBytes(charset));
	}

JSON 序列化:com.alibaba.fastjson.support.spring.GenericFastJsonRedisSerializer

	public byte[] serialize(Object object) throws SerializationException {
        if (object == null) {
            return new byte[0];
        } else {
            try {
                return JSON.toJSONBytes(object, new SerializerFeature[]{SerializerFeature.WriteClassName});
            } catch (Exception var3) {
                throw new SerializationException("Could not serialize: " + var3.getMessage(), var3);
            }
        }
    }

    public Object deserialize(byte[] bytes) throws SerializationException {
        if (bytes != null && bytes.length != 0) {
            try {
                return JSON.parseObject(new String(bytes, IOUtils.UTF8), Object.class, defaultRedisConfig, new Feature[0]);
            } catch (Exception var3) {
                throw new SerializationException("Could not deserialize: " + var3.getMessage(), var3);
            }
        } else {
            return null;
        }
    }

在实际使用中,我们会将 redis 的 key 和 value 都使用 String 序列化。这里我们采用将 key 使用 String 序列化和 value 使用 Json 序列化的方式,具体使用如下:

在配置文件中配置:

/**
 * 使用配置文件将 Redis 默认的序列化方式修改为自己需要的,因为在传输过程中不能只是使用原数据直接存储到 redis 中
 * 这里对于 key 的序列化使用了 string 序列化
 * value 使用了 json 序列化
 */
@Configuration
public class RedisConfiguration {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {

        //创建 RedisTemplate 对象
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        // 设置 RedisFactory ,它就是实现多种 Java Redis 客户端接入的工厂,针对四种类型有4个连接实现,这样就可以满足不同连接请求
        redisTemplate.setConnectionFactory(factory);
        // 使用 String 序列化方式序列化 key
        redisTemplate.setKeySerializer(RedisSerializer.string());
        // 使用 JSON 序列化方式序列化 value(json 序列化结果会包含类名(@class)
        redisTemplate.setValueSerializer(RedisSerializer.json());
        return redisTemplate;
    }

  /**
   * redis 提供了 Pub/Sub 功能,可以实现简单的订阅功能。
   *
   */
    @Bean
    public RedisMessageListenerContainer listenerContainer(RedisConnectionFactory factory) {
        // 创建对象
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        // 设置工厂
        container.setConnectionFactory(factory);
        // 添加监听器
        container.addMessageListener(new TestPatternTopicMessageListener(), new ChannelTopic("TEST"));
        container.addMessageListener(new TestPatternTopicMessageListener(), new ChannelTopic("AOTEMAN"));
        return container;
    }
}

在访问数据库时,我们会创建 dao 包,存放每个 DO 对应的 Dao 对应。那么,对于每一个 CacheObject 类,我们也应该创建一个对应的 Dao 类。

/**
 * dao 包下存放每个 DO 对应的 Dao。对应 UserCacheObject
 */
@Repository
public class UserCacheDAO {

    /**
     * user:用户编号,通过静态变量,声明 KEY 的前缀,并且用冒号作为分割
     */
    private static final String KEY_PATTERN = "user:%d";

    /**
     * 通过 @Resource 注入指定名字的 RedisTemplate 对应的 operations 对象
     * 用以明确每个 KEY 的类型
     */
    @Resource(name = "redisTemplate")
    @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
    private ValueOperations<String, String> operations;

    /**
     * 声明 KEY_PATTERN 对应的 KEY 拼接方法,避免散落在每个方法中。
     * @param id ID
     * @return key 拼接
     */
    public static String buildKey(Integer id) {
        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);
    }
}

测试:

		@Resource
    private StringRedisTemplate stringRedisTemplate;
		@Test
    public void testJDKSerializer() {
        redisTemplate.opsForValue().set("jdk","value");
    }

    @Test
    public void testStringSetKeyUserCache() {
        UserCacheObject object = new UserCacheObject();
        object.setId(1);
        object.setName("悲伤");
        object.setGender(2);
        String format = String.format("user:%d", object.getId());
        redisTemplate.opsForValue().set(format, object);
    }

    @Test
    public void testStringGetKeyUserCache() {
        String key = String.format("user:%d", 1);
        Object value = redisTemplate.opsForValue().get(key);
        System.out.println(value);
    }

    @Test
    public void testPipe() {
        stringRedisTemplate.executePipelined((RedisCallback<Object>) connection -> {
            for (int i = 0; i < 3; i++) {
                connection.set(String.format("gui:%d", i).getBytes(), ("pipe" + i).getBytes());
            }

            for (int i = 0; i < 3; i++) {
                connection.get(String.format("gui:%d", i).getBytes());
            }
            return null;
        });
    }

    @Test
    public void testSession() {
        String resultsRed = stringRedisTemplate.execute(new SessionCallback<String>() {

            @Override
            public String execute(RedisOperations operations) throws DataAccessException {
                for (int i = 0; i < 100; i++) {
                    operations.opsForValue().set(String.format("Gui:%d",i),"cool");
                }
                return (String) operations.opsForValue().get(String.format("Gui:%d", 0));
            }
        });
        System.out.println(resultsRed);
    }

    @Test
    public void testListener() throws InterruptedException {
        for (int i = 0; i < 10; i++) {
          // 发布消息
            stringRedisTemplate.convertAndSend("TEST", "gui" + i);
            Thread.sleep(1000l);
        }
    }
}
4. Script

Redis 提供 Lua 脚本,满足我们希望组合排列使用 Redis 的命令,保证串行执行的过程中,不存在并发的问题。同时,通过将多个命令组合在同一个 Lua 脚本中,一次请求,直接处理。

创建 resources/compareAndSet.lua 脚本,实现 CAS 功能。

if redis.call('GET', KEYS[1]) ~= ARGV[1] then

    return 0
    end
    redis.call('SET',KEYS[1],ARGV[2])
    return 1

测试:

		@Test
    public void testLua() throws IOException {
        List<String> strings = Files.readAllLines(Paths.get("src/main/resources/lua","compareAndSet.lua"));
        //
        StringBuilder stringBuilder = new StringBuilder();
        strings.forEach(stringBuilder::append);
        DefaultRedisScript<Long> script = new DefaultRedisScript<>(stringBuilder.toString(), Long.class);
        //
        Long execute = stringRedisTemplate.execute(script, Collections.singletonList("gui:1"));
        System.out.println(execute);
    }
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值