缓存服务的意义

为什么要使用缓存?说到底是为了提高系统的运行速度。将用户频繁访问的内容存放在离用户最近,访问速度最快的地方,提高用户的响应速度。一个 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 等缓存的用法大同小异。

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

时间: 2019-06-09

java redis 缓存_如何在 Java 中实现一个 redis 缓存服务相关推荐

  1. ai中如何插入签名_如何在PDF中插入一个或多个空白页?

    在编辑或修改PDF文档时,一般都需要新增空白页后再添加文本或图片等内容,那么如何在PDF中插入一个或多个空白页呢? 首先打开极速PDF编辑器,或打开需要编辑的PDF文档后,点击右上角"文档& ...

  2. 让一个图片填满一个控件_如何在Android中实现一个全景图控件(二)

    一.背景 在 如何在Android中实现一个全景图控件(一)中,介绍了项目的一些基本情况(有 demo 演示),如果项目对你有帮助,希望文章赏个赞,项目 star 一下. 项目地址:https://g ...

  3. element 搜索匹配_如何在Element-ui中实现一个远程搜索功能

    如何在Element-ui中实现一个远程搜索功能 发布时间:2021-01-29 14:50:00 来源:亿速云 阅读:87 作者:Leah 这篇文章给大家介绍如何在Element-ui中实现一个远程 ...

  4. java 正则表达式 开头_如何在Java中修复表达式的非法开头

    java 正则表达式 开头 您是否遇到过这个令人难以置信的错误,想知道如何解决它? 让我们仔细阅读一下,研究如何解决表达式Java非法开头错误. 这是一个动态错误,这意味着编译器会发现某些不符合Jav ...

  5. java soap 头_如何在Java中添加Soap标头

    我有一个来自oracle的NO.net Web服务,要访问,我需要添加soap标头.如何在Java中添加soap标头? Authenticator.setDefault(new ProxyAuthen ...

  6. java实现递归算法_如何在Java中实现二进制搜索算法而无需递归

    java实现递归算法 by javinpaul 由javinpaul 流行的二进制搜索算法的迭代实现,用于在排序数组中查找元素. (An Iterative implementation of the ...

  7. java jcombobox长度_如何在JToolBar中设定JComboBox的大小?

    如何在JToolBar中设定JComboBox的大小? 我设计了一个JToolBar,并且在上边添加了一个JComboBox,可是我发现这个JComboBox长度无法控制,它将JToolBar上剩余空 ...

  8. java 全局数组_如何在Java中声明全局数组?

    我有一个程序在Java中乘以两个矩阵.我在全局错误声明中发现了一些错误. 这里是我的代码如何在Java中声明全局数组? import java.util.Scanner; /**WAP in Java ...

  9. maven 对于java的要求_如何在Maven中表达对Java EE功能的依赖以过渡到Java 9?

    该模块系统讲的无名模块作为在从类路径加载应用的情况下构建模块图的方式.此外,从文档本身来看: 当编译器在未命名模块中编译代码,或者调用Java启动程序并将应用程序的主类从类路径加载到应用程序类加载器的 ...

最新文章

  1. 德国虚拟主机 linux,细说Linux虚拟主机的搭建及配置
  2. 电子商务时间戳服务解决方案
  3. 五、队列(Queue)
  4. linux(windows)之svn重定向地址
  5. java dfs算法蓝桥杯题_【蓝桥杯省赛JavaB组真题详解】四平方和(2016)_疼疼蛇的博客-CSDN博客...
  6. 如何快速学习一门新的编程语言?零基础开始学编程,就可以这样!
  7. python实现knn分类_knn分类算法底层实现(python)
  8. OpenShift 4 之Service Mesh教程(1)- 创建ServiceMesh环境,部署Istio的微服务
  9. javascript中构造StringBuffer实例
  10. GSON简单实用及常用方法(附 .jar 地址)
  11. 景德镇昌江一中2021年高考成绩查询,2021年景德镇中考分数线公布填报志愿时间及录取结果什么时候出...
  12. 问题来了,个人用户可以使用短信接口吗?答案很意外
  13. 【电脑视频录制软件】各种录屏软件优缺点,OBS的格式转化,清晰度调节,基本使用,下载链接
  14. GPU图形加速型云服务器是什么?
  15. wms仓库管理系统中条形码管理的两大优势
  16. 区分 Linux 的硬链接与软链接
  17. 为什么手机里的小爱音响app里搜不到家里的小爱音箱_水哥岁末诚意奉献:基于米家App的家庭智能安全方案详解...
  18. NVidia GPU 无法调节风扇转速
  19. 二维数组的查找(每一行每一列都是递增顺序)?
  20. 【mathematica画三维空间坐标系箭头】

热门文章

  1. Android 3分钟带你入门开发测试
  2. c语言 长整型的输出
  3. 计算机考研最后四十天,考研只剩最后一个月,千万别放弃!(上)时间过得很快,眼看距离...
  4. C/C++动态创建对象
  5. 蜀山剑侠传打开服务器更新第一个文件失败,《蜀山剑侠传》冲级达人侠岚勇闯“左元洞”...
  6. Javascript内置的可迭代对象
  7. 浙江省2002年高校招生录取第一批院校第一志愿投档分数线
  8. 天猫品牌自荐需要准备什么?
  9. Hog特征提取原理过程细节
  10. Room实现数据统一管理-Repo