当前位置: 首页 > 编程笔记 >

如何在 Java 中实现一个 redis 缓存服务

厍胤运
2023-03-14
本文向大家介绍如何在 Java 中实现一个 redis 缓存服务,包括了如何在 Java 中实现一个 redis 缓存服务的使用技巧和注意事项,需要的朋友参考一下

缓存服务的意义

为什么要使用缓存?说到底是为了提高系统的运行速度。将用户频繁访问的内容存放在离用户最近,访问速度最快的地方,提高用户的响应速度。一个 web 应用的简单结构如下图。

web 应用典型架构

在这个结构中,用户的请求通过用户层来到业务层,业务层在从数据层获取数据,返回给用户层。在用户量小,数据量不太大的情况下,这个系统运行得很顺畅。但是随着用户量越来越大,数据库中的数据越来越多,系统的用户响应速度就越来越慢。系统的瓶颈一般都在数据库访问上。这个时候可能会将上面的架构改成下面的来缓解数据库的压力。

一主多从结构

在这个架构中,将数据库的读请求和写请求进行分离。数量众多的读请求都分配到从数据库上,主数据库只负责写请求。从库保持主动和主库保持同步。这个架构比上一个有了很大的改进,一般的互联网应用。这个架构就能够很好的支持了。他的一个缺点是比较复杂,主从库之间保持高效实时,或者准实时的同步是一个不容易做到的事情。所以我们有了另一个思路,采用一个缓存服务器来存储热点数据,而关系数据用来存储持久化的数据。结构如下图所示

采用缓存服务器读的架构

采用缓存服务器读的架构

在这个架构中,当读取数据的时候,先从缓存服务器中获取数据,如果获取调,则直接返回该数据。如果没有获取调,则从数据库中获取数据。获取到后,将该数据缓存到换出数据库中,供下次访问使用。当插入或者更新数据的时候,先将数据写入到关系数据库中,然后再更新缓存数据库中的数据。
当然了,为了应付更大规模的访问量,我们还可以将上面两个改进的架构组合起来使用,既有读写分离的关系数据库,又有可以高速访问的缓存服务。
以上缓存服务器架构的前提就是从缓存服务器中获取数据的效率大大高于从关系型数据库中获取的效率。否则缓存服务器就没有任何意义了。redis 的数据是保存在内存中的,能够保证从 redis 中获取数据的时间效率比从关系数据库中获取高出很多。

基于 redis 缓存服务的实现

这一章节用一个实例来说明如何来在 Java 中实现一个 redis 的缓存服务。

建立 maven 工程并引入依赖

定义接口类com.x9710.common.redis.CacheService

在这个接口类中,主要定了下面的接口

  • void putObject(String key, Object value);
  • void putObject(String key, Object value, int expiration);
  • Object pullObject(String key);
  • Long ttl(String key);
  • boolean delObject(String key);
  • boolean expire(String key, int expireSecond);
  • void clearObject();

这些接口分别用于存储不过期的对象、存储将来过期对象、获取缓存对象、获取缓存对象剩余存活时间、删除缓存对象、设置缓存对象过期时间、清除所有缓存对象的功能

package com.x9710.common.redis;
/**
* 缓存服务接口
*
* @author 杨高超
* @since 2017-12-09
*/
public interface CacheService {
/**
* 将对象存放到缓存中
*
* @param key 存放的key
* @param value 存放的值
*/
void putObject(String key, Object value);
/**
* 将对象存放到缓存中
*
* @param key 存放的key
* @param value 存放的值
* @param expiration 过期时间,单位秒
*/
void putObject(String key, Object value, int expiration);
/**
* 从缓存中获取对象
*
* @param key 要获取对象的key
* @return 如果存在,返回对象,否则,返回null
*/
Object pullObject(String key);
/**
* 给缓存对象设置过期秒数
*
* @param key 要获取对象的key
* @param expireSecond 过期秒数
* @return 如果存在,返回对象,否则,返回null
*/
boolean expire(String key, int expireSecond);
/**
* 获取缓存对象过期秒数
*
* @param key 要获取对象的key
* @return 如果对象不存在,返回-2,如果对象没有过期时间,返回-1,否则返回实际过期时间
*/
Long ttl(String key);
/**
* 从缓存中删除对象
*
* @param key 要删除对象的key
* @return 如果出现错误,返回 false,否则返回true
*/
boolean delObject(String key);
/**
* 从缓存中清除对象
*/
void clearObject();
}

定义序列号辅助类com.x9710.common.redis.SerializeUtil

所有要保存到 redis 数据库中的对象需要先序列号为二进制数组,这个类的作用是将 Java 对象序列号为二级制数组或者将二级制数组反序列化为对象。

package com.x9710.common.redis;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
/**
* 对象序列化工具类
*
* @author 杨高超
* @since 2017-10-09
*/
public class SerializeUtil {
/**
* 将一个对象序列化为二进制数组
*
* @param object 要序列化的对象,该必须实现java.io.Serializable接口
* @return 被序列化后的二进制数组
*/
public static byte[] serialize(Object object) {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(object);
return baos.toByteArray();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 将一个二进制数组反序列化为一个对象。程序不检查反序列化过程中的对象类型。
*
* @param bytes 要反序列化的二进制数
* @return 反序列化后的对象
*/
public static Object unserialize(byte[] bytes) {
try {
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(bais);
return ois.readObject();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}

实现 redis 缓存服务类 com.x9710.common.redis.impl.CacheServiceRedisImpl

package com.x9710.common.redis.impl;
import com.x9710.common.redis.CacheService;
import com.x9710.common.redis.RedisConnection;
import com.x9710.common.redis.SerializeUtil;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import redis.clients.jedis.Jedis;
/**
* 缓存服务 redis 实现类
*
* @author 杨高超
* @since 2017-12-09
*/
public class CacheServiceRedisImpl implements CacheService {
private static Log log = LogFactory.getLog(CacheServiceRedisImpl.class);
private RedisConnection redisConnection;
private Integer dbIndex;
public void setRedisConnection(RedisConnection redisConnection) {
this.redisConnection = redisConnection;
}
public void setDbIndex(Integer dbIndex) {
this.dbIndex = dbIndex;
}
public void putObject(String key, Object value) {
putObject(key, value, -1);
}
public void putObject(String key, Object value, int expiration) {
Jedis jedis = null;
try {
jedis = redisConnection.getJedis();
jedis.select(dbIndex);
if (expiration > 0) {
jedis.setex(key.getBytes(), expiration, SerializeUtil.serialize(value));
} else {
jedis.set(key.getBytes(), SerializeUtil.serialize(value));
}
} catch (Exception e) {
log.warn(e.getMessage(), e);
} finally {
if (jedis != null) {
jedis.close();
}
}
}
public Object pullObject(String key) {

log.trace("strar find cache with " + key);
Jedis jedis = null;
try {
jedis = redisConnection.getJedis();
jedis.select(dbIndex);
byte[] result = jedis.get(key.getBytes());
if (result == null) {
log.trace("can not find caceh with " + key);
return null;
} else {
log.trace("find cache success with " + key);
return SerializeUtil.unserialize(result);
}
} catch (Exception e) {
log.warn(e.getMessage(), e);
} finally {
if (jedis != null) {
jedis.close();
}
}
return null;
}
public boolean expire(String key, int expireSecond) {
log.trace("strar set expire " + key);
Jedis jedis = null;
try {
jedis = redisConnection.getJedis();
jedis.select(dbIndex);
return jedis.expire(key, expireSecond) == 1;
} catch (Exception e) {
log.warn(e.getMessage(), e);
} finally {
if (jedis != null) {
jedis.close();
}
}
return false;
}
public Long ttl(String key) {
log.trace("get set expire " + key);
Jedis jedis = null;
try {
jedis = redisConnection.getJedis();
jedis.select(dbIndex);
return jedis.ttl(key);
} catch (Exception e) {
log.warn(e.getMessage(), e);
} finally {
if (jedis != null) {
jedis.close();
}
}
return -2L;
}
public boolean delObject(String key) {
log.trace("strar delete cache with " + key);
Jedis jedis = null;
try {
jedis = redisConnection.getJedis();
jedis.select(dbIndex);
return jedis.del(key.getBytes()) > 0;
} catch (Exception e) {
log.warn(e.getMessage(), e);
} finally {
if (jedis != null) {
jedis.close();
}
}
return false;
}
public void clearObject() {
Jedis jedis = null;
try {
jedis = redisConnection.getJedis();
jedis.select(dbIndex);
jedis.flushDB();
} catch (Exception e) {
log.warn(e.getMessage(), e);
} finally {
if (jedis != null) {
jedis.close();
}
}
}
}

编写测试用例

package com.x9710.common.redis.test;
import com.x9710.common.redis.RedisConnection;
import com.x9710.common.redis.impl.CacheServiceRedisImpl;
import com.x9710.common.redis.test.domain.Student;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
/**
* 缓存服务测试类
*
* @author 杨高超
* @since 2017-12-09
*/
public class RedisCacheTest {
private CacheServiceRedisImpl cacheService;
@Before
public void before() {
RedisConnection redisConnection = RedisConnectionUtil.create();
cacheService = new CacheServiceRedisImpl();
cacheService.setDbIndex(2);
cacheService.setRedisConnection(redisConnection);
}
@Test
public void testStringCache() {
String key = "name";
String value = "grace";
cacheService.putObject(key, value);
String cachValue = (String) cacheService.pullObject(key);
//检查从缓存中获取的字符串是否等于原始的字符串
Assert.assertTrue(value.equals(cachValue));
//检查从缓存删除已有对象是否返回 true
Assert.assertTrue(cacheService.delObject(key));
//检查从缓存删除已有对象是否返回 false
Assert.assertFalse(cacheService.delObject(key + "1"));
//检查从缓存获取已删除对象是否返回 null
Assert.assertTrue(cacheService.pullObject(key) == null);
}
@Test
public void testObjectCache() {
Student oriStudent = new Student();
oriStudent.setId("2938470s9d8f0");
oriStudent.setName("柳白猿");
oriStudent.setAge(36);
cacheService.putObject(oriStudent.getId(), oriStudent);
Student cacheStudent = (Student) cacheService.pullObject(oriStudent.getId());
Assert.assertTrue(oriStudent.equals(cacheStudent));
Assert.assertTrue(cacheService.delObject(oriStudent.getId()));
Assert.assertTrue(cacheService.pullObject(oriStudent.getId()) == null);
}
@Test
public void testExpireCache() {
String key = "name";
String value = "grace";
cacheService.putObject(key, value);
cacheService.expire(key, 300);
String cachValue = (String) cacheService.pullObject(key);
Assert.assertTrue(value.equals(cachValue));
Long ttl = cacheService.ttl(key);
Assert.assertTrue(ttl > 250 && ttl <= 300);
Assert.assertTrue(value.equals(cachValue));
Assert.assertTrue(cacheService.delObject(key));
}
}

测试结果

测试结果

redis 作为缓存服务是一个最基本的用法。这里只实现了基于 k-value 数据的缓存。其余的 Hash、Set、List 等缓存的用法大同小异。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持小牛知识库。

 类似资料:
  • 问题内容: 不要说EHCache或OSCache等。出于这个问题的目的,假设我想仅使用SDK实现自己的实现(边做边学)。考虑到缓存将在多线程环境中使用,你将使用哪些数据结构?我已经使用LinkedHashMap和Collections#synchronizedMap实现了一个,但是我很好奇是否有任何新的并发集合会更好。 更新:当我发现这个块时,我只是在阅读Yegge的最新文章: 如果你需要固定时间

  • 本文向大家介绍如何在 Java 中利用 redis 实现 LBS 服务,包括了如何在 Java 中利用 redis 实现 LBS 服务的使用技巧和注意事项,需要的朋友参考一下 前言 LBS(基于位置的服务) 服务是现在移动互联网中比较常用的功能。例如外卖服务中常用的我附近的店铺的功能,通常是以用户当前的位置坐标为基础,查询一定距离范围类的店铺,按照距离远近进行倒序排序。 自从 redis 4 版本

  • 1)我想从缓存中获得数据,如果缓存中没有数据,那么应该从数据库中获取数据。 2)如果我点击/api/cacherefresh,控制器将刷新所有表。

  • 本文向大家介绍spring结合redis如何实现数据的缓存,包括了spring结合redis如何实现数据的缓存的使用技巧和注意事项,需要的朋友参考一下 1、实现目标   通过redis缓存数据。(目的不是加快查询的速度,而是减少数据库的负担)   2、所需jar包     注意:jdies和commons-pool两个jar的版本是有对应关系的,注意引入jar包是要配对使用,否则将会报错。因为co

  • 问题内容: 我一直在阅读一些Redis文档,并在http://try.redis-db.com/上尝试了该教程。到目前为止,我看不到Redis和诸如Velocity或Enterprise Library Caching Framework之类的缓存技术之间的任何区别。 您实际上只是在使用唯一键将对象添加到内存数据存储中。似乎没有任何关系语义… 我想念什么? 问题答案: 不,Redis不仅仅是缓存。

  • 实际上,您只是使用唯一键将对象添加到内存中的数据存储中。似乎没有任何关系语义... 我错过了什么?