如何在 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 等缓存的用法大同小异。

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

(0)

相关推荐

  • Java客户端利用Jedis操作redis缓存示例代码

    前言 Redis是一个开源的Key-Value数据缓存,和Memcached类似.Redis多种类型的value,包括string(字符串).list(链表).set(集合).zset(sorted set --有序集合)和hash(哈希类型). Jedis 是 Redis 官方首选的 Java 客户端开发包.下面就来给大家详细关于Java客户端利用Jedis操作redis缓存的相关内容,话不多说,直接来看示例代码吧. 示例代码: //连接redis ,redis的默认端口是6379 Jedis

  • 【Redis缓存机制】详解Java连接Redis_Jedis_事务

    Jedis事务 我们使用JDBC连接Mysql的时候,每次执行sql语句之前,都需要开启事务:在MyBatis中,也需要使用openSession()来获取session事务对象,来进行sql执行.查询等操作.当我们对数据库的操作结束的时候,是事务对象负责关闭数据库连接. 事务对象用于管理.执行各种数据库操作的动作.它能够开启和关闭数据库连接,执行sql语句,回滚错误的操作. 我们的Redis也有事务管理对象,其位于redis.clients.jedis.Transaction下. Jedis事

  • 在Java中使用redisTemplate操作缓存的方法示例

    背景 在最近的项目中,有一个需求是对一个很大的数据库进行查询,数据量大概在几千万条.但同时对查询速度的要求也比较高. 这个数据库之前在没有使用Presto的情况下,使用的是Hive,使用Hive进行一个简单的查询,速度可能在几分钟.当然几分钟也并不完全是跑SQL的时间,这里面包含发请求,查询数据并且返回数据的时间的总和.但是即使这样,这样的速度明显不能满足交互式的查询需求. 我们的下一个解决方案就是Presto,在使用了Presto之后,查询速度降到了秒级.但是对于一个前端查询界面的交互式查询来

  • Java自定义注解实现Redis自动缓存的方法

    在实际开发中,可能经常会有这样的需要:从MySQL中查询一条数据(比如用户信息),此时需要将用户信息保存至Redis. 刚开始我们可能会在查询的业务逻辑之后再写一段Redis相关操作的代码,时间长了后发现这部分代码实际上仅仅做了Redis的写入动作,跟业务逻辑没有实质的联系,那么有没有什么方法能让我们省略这些重复劳动呢? 首先想到用AOP,在查询到某些数据这一切入点(Pointcut)完成我们的切面相关处理(也就是写入Redis).那么,如何知道什么地方需要进行缓存呢,也就是什么地方需要用到AO

  • java中对Redis的缓存进行操作的示例代码

    Redis 是一个NoSQL数据库,也是一个高性能的key-value数据库.一般我们在做Java项目的时候,通常会了加快查询效率,减少和数据库的连接次数,我们都会在代码中加入缓存功能.Redis的高效缓存功能给我们解决了难题.下面我主要讲讲在Java项目中怎么去连接Redis服务器以及需要注意的事项. 1.导入必须的Jar包 使用Java操作Redis需要两个必须的Jar包:jedis-2.5.1.jar 和  commons-pool2-2.0.jar .每个版本可以不一样,根据你自己下载的

  • 详解在Java程序中运用Redis缓存对象的方法

    这段时间一直有人问如何在Redis中缓存Java中的List 集合数据,其实很简单,常用的方式有两种: 1. 利用序列化,把对象序列化成二进制格式,Redis 提供了 相关API方法存储二进制,取数据时再反序列化回来,转换成对象. 2. 利用 Json与java对象之间可以相互转换的方式进行存值和取值. 正面针对这两种方法,特意写了一个工具类,来实现数据的存取功能. 1. 首页在Spring框架中配置 JedisPool 连接池对象,此对象可以创建 Redis的连接 Jedis对象.当然,必须导

  • 详解Java在redis中进行对象的缓存

    Java在redis中进行对象的缓存一般有两种方法,这里介绍序列化的方法,个人感觉比较方便,不需要转来转去. 一.首先,在存储的对象上实现序列化的接口 package com.cy.example.entity.system; import java.util.List; import com.baomidou.mybatisplus.annotations.TableField; import com.baomidou.mybatisplus.annotations.TableName; im

  • 详解JavaEE 使用 Redis 数据库进行内容缓存和高访问负载

    NoSQL(Not Only SQL),泛指非关系型数据库,是为了处理高并发读写.海量数据的高效率存储和访问.高扩展性和高可用性而产生的. 分类 相关产品 典型应用 数据模型 优点 缺点 键值对(Key-Value)存储 Redis.Voldemort.Berkeley DB 内容缓存.处理高访问负载 一系列键值对 快速查询 存储的数据缺少结构化 列存储数据库 Cassandra.HBase.Riak 分布式文件系统 以列簇式存储,将同一列数据存在一起 查询速度快,可扩展性强,更容易进行分布式扩

  • javaWeb中使用Redis缓存实例解析

    直接进入主题: 一:serviceImpl定义: @Service public class JedisClientSingleService implements JedisClient { @Autowired private JedisPool jedisPool; @Override public String get(String key) { Jedis jedis = jedisPool.getResource(); String string = jedis.get(key);

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

    缓存服务的意义 为什么要使用缓存?说到底是为了提高系统的运行速度.将用户频繁访问的内容存放在离用户最近,访问速度最快的地方,提高用户的响应速度.一个 web 应用的简单结构如下图. web 应用典型架构 在这个结构中,用户的请求通过用户层来到业务层,业务层在从数据层获取数据,返回给用户层.在用户量小,数据量不太大的情况下,这个系统运行得很顺畅.但是随着用户量越来越大,数据库中的数据越来越多,系统的用户响应速度就越来越慢.系统的瓶颈一般都在数据库访问上.这个时候可能会将上面的架构改成下面的来缓解数

  • 如何在Java中实现一个散列表

    目录 前言: 优化1 优化2 优化3 如何实现 总结 前言: 假设现在有一篇很长的文档,如果希望统计文档中每个单词在文档中出现了多少次,应该怎么做呢? 很简单! 我们可以建一个HashMap,以String类型为Key,Int类型为Value: 遍历文档中的每个单词 word ,找到键值对中key为 word 的项,并对相关的value进行自增操作. 如果该key= word 的项在 HashMap中不存在,我们就插入一个(word,1)的项表示新增. 这样每组键值对表示的就是某个单词对应的数量

  • 如何在Java中判断两个Long类型是否相等

    目录 一.为什么同样的类型,同样的值,却不相等呢? 1.探索一下源码 二.解决方案 1.可以使用.longValue() 2.equals()进行比较 三.例子 一.为什么同样的类型,同样的值,却不相等呢? 1.探索一下源码 源码中显示,Long中有一个静态的内部类LongCache,专门用于缓存-128至127之间的值,一共256个元素. 如果值在[-128, 127]之间,会放在缓存里面,而超过这个范围就要new一个新的对象,也就是说==不能判断对象是否相等.当然,如果值是在[-128, 1

  • 如何在JAVA中使用Synchronized

    <编程思想之多线程与多进程(1)--以操作系统的角度述说线程与进程>一文详细讲述了线程.进程的关系及在操作系统中的表现,这是多线程学习必须了解的基础.本文将接着讲一下Java线程同步中的一个重要的概念synchronized. 在Java中,synchronized关键字是用来控制线程同步的,就是在多线程的环境下,控制synchronized代码段不被多个线程同时执行. synchronized是Java中的关键字,是一种同步锁.它修饰的对象有以下几种: 1. 修饰一个代码块,被修饰的代码块称

  • 如何在 C++ 中实现一个单例类模板

    单例模式是最简单的设计模式之一.在实际工程中,如果一个类的对象重复持有资源的成本很高,且对外接口是线程安全的,我们往往倾向于将其以单例模式管理. 此篇我们在 C++ 中实现正确的单例模式. 选型 在 C++ 中,单例模式有两种方案可选. 一是实现一个没有可用的公开构造函数的基类,并提供 GetInstance 之类的静态接口,以便访问子类唯一的对象.由于子类构造必须调用基类构造,但基类无公开构造函数可用,这使得子类对象只能由基类及基类的友元来构造,从而在机制上保证单例. 二是实现一个类模板,其模

  • 详解如何在Java中调用Python程序

    Java中调用Python程序 1.新建一个Maven工程,导入如下依赖 <dependency> <groupId>org.python</groupId> <artifactId>jython-standalone</artifactId> <version>2.7.0</version> </dependency> 2.在java中直接执行python代码片段 import org.python.util

  • 如何在Java中调用python文件执行详解

    目录 一.Java内置Jpython库(不推荐) 1.1 下载与使用 1.2 缺陷 二.使用Runtime.getRuntime()执行脚本⽂件 2.1 使用 2.2 缺陷 三.利用cmd调用python文件 3.1 使用 3.2 优化 总结 一.Java内置Jpython库(不推荐) 1.1 下载与使用 可以在官网下载jar包,官网:http://ftp.cuhk.edu.hk/pub/packages/apache.org/ 或者使用maven进行jar包下载 <dependency> &

  • 如何在Java中使用正则表达式API

    目录 Java正则表达式包 简单的例子 Meta Characters元字符 Character类 OR NOR Range类 Union类 Intersection类 Subtraction类 前言: 在正则表达式的世界中,有许多不同的风格可供选择,比如grep.Perl.Python.PHP.awk等等.这意味着在一种编程语言中工作的正则表达式可能在另一种编程语言中不工作.Java中的正则表达式语法与Perl中的最相似.要在Java中使用正则表达式,我们不需要任何特殊设置.JDK包含一个特殊

  • 详解如何在Java中加密和解密zip文件

    目录 依赖 压缩一个文件 压缩多个文件 压缩一个目录 创建一个分割的压缩文件 提取所有文件 提取单个文件 总结 依赖 让我们先把 zip4j 依赖关系添加到我们的 pom.xml 文件中. <dependency>     <groupId>net.lingala.zip4j</groupId>     <artifactId>zip4j</artifactId>     <version>2.9.0</version>

随机推荐