在传统的 Java Web 项目中,使用数据库进行存储数据,但是有一些致命的弊端,这些弊端主要来自于性能方面。

  由于数据库持久化数据主要是面向磁盘,而磁盘的读/写比较慢,在一般管理系统中,由于不存在高并发,因此往往没有瞬间需要读/写大量数据的要求,这个时候使用数据库进行读/写时没有太大的问题的,但是在互联网中,往往存在大数据量的需求,比如,需要在极短的时间内完成成千上万次的读/写操作,这个时候往往不是数据库能够承受的,极其容易造成数据库系统瘫痪,导致服务宕机的严重生产问题。

  为了克服这些问题,Java Web 项目往往就引入了 NoSQL 技术,NoSQL 工具也是一种简易的数据库,它主要是一种基于内存的数据库,并提供了一定的持久化功能。比如Redis和MongoDB。

  Redis 的性能十分优越,可以支持每秒十几万次的读/写操作,其性能远超数据库,并且支持集群、分布式、主从同步等配置,原则上可以无限扩展,同时还支持一定的事务能力。

  Redis 性能优越主要来自于3个方面:

  • 基于ANSI C 语言编写,接近于汇编语言的机器语言,运行十分快速
  • 基于内存的读/写。
  • 数据库结构只有6种数据类型,数据结构比较简单,因此规则较少,而数据库则是范式,完整性、规范性需要考虑的规则比较多,处理业务会比较复杂。\

  NoSQL为什么不能代替数据库:

  • NoSQL 的数据主要存储在内存中,而数据库主要是磁盘。
  • NoSQL 数据库结构比较简单,虽然能处理很多的问题,但是其功能毕竟有限,不如数据库的SQL语句强大,支持更为复杂的计算
  • NoSQL 并不完全安全稳定,由于它基于内存,一旦停电或者机器故障数据就很容易丢失,其持久化能力也是有限的,而基于磁盘的数据库则不胡出现这样的问题
  • NoSQL 其数据完整性、事务能力、安全性、可靠性以及可扩展性都远不及数据库。

  

  一、Redis 在 Java Web 中的应用

  一般而言 Redis 在 Java Web 应用中存在两个主要的场景:一个是缓存常用的数据,另一个是在需要高速读/写的场合使用它快速读写。

  1.缓存

  在对数据库的读/写操作中,读操作远超写操作,一般是9:1到7:3的比例,所以需要读的可能性比写的可能性多得多。

  当发送SQL去数据进行读取时,数据库就会去磁盘把对应的数据索引回来,而索引磁盘是一个相对缓慢的过程。如果把数据直接放在运行在内存中的Redis服务器上,那么就不需要去读/写磁盘了,而是直接读取内存,显然速度会快得多,而且会极大减轻数据库的压力。

  而使用内存进行存储数据开销也是比较大的,应该考虑在Redis中存储哪些数据,需要从3个方面进行考虑:

  • 业务数据常用与否以及命中率大小。如果命中率很低,就没有必要写入缓存。
  • 该业务数据是读操作多,还是写操作多,如果写操作多,频繁需要写入数据库,也没有必要使用缓存
  • 如果要存储几百兆字节的文件,会给缓存带来很大的压力,没有必要。

  (1)读操作流程

  

  • 当第一次读取数据的时候,读取Redis的数据就会失败,此时会触发程序读取数据库,把数据读取出来,并且写入Redis
  • 当第二次以及以后读取数据时,就直接读取Redis,读到数据后就结束了流程,这样速度就大大提高了。

  (2)写操作流程

  

  如果业务数据写操作次数远远大于读操作次数,那么没有必要使用 Redis。

  2.高速读/写场合

  高速读/写场合例如:秒杀商品、抢红包、抢票等。这类场合在一瞬间成千上万的请求就会达到服务器,如果使用的数据库,很容易造成数据库瘫痪。

  解决办法是异步写入数据库,即在高速读/写的场合单单使用 Redis 去应对,把这些需要高速读/写的数据缓存到 Redis 中,而在满足一定的条件下,触发这些缓存的数据写入数据库中。

  

  当一个请求到达服务器,只是把业务数据先在 Redis 读/写,而没有进行任何对数据库的操作。

  由于一般缓存不能持久化,或者所持久化的数据不太规范,因此需要把这些业务数据存入数据库,所以在一个请求操作完 Redis 的读/写后,会去判断该高速读/写业务是否结束,这个判断的条件往往就是秒杀商品剩余个数为0,抢红包金额为0,如果不成立,则不会操作数据库;如果成立,则触发事件将 Redis 缓存的数据以批量的形式一次性写入数据库,从而完成持久化操作。

  二、在 Java 中使用 Redis

  1.下载 jedis.jar、spring-data.redis.jar 和 commons-pool2-2.5.0.jar

  2.在 Java 中使用 Redis,一般采用连接池方式获取连接

  1. package com.ssm.chapter17.jedis;
  2.  
  3. import redis.clients.jedis.Jedis;
  4. import redis.clients.jedis.JedisPool;
  5. import redis.clients.jedis.JedisPoolConfig;
  6.  
  7. public class JedisTest {
  8.  
  9. public void testJedis() {
  10. // 从连接池中获取Redis连接
  11. Jedis jedis = testPool().getResource();
  12. int i = 0;// 记录操作次数
  13. try {
  14. long start = System.currentTimeMillis();// 开始毫秒数
  15. while (true) {
  16. long end = System.currentTimeMillis();
  17. if (end - start >= 1000) {// 当操作1秒时,结束操作
  18. break;
  19. }
  20. i++;
  21. jedis.set("test" + i, i + "");
  22. }
  23. } finally {//
  24. jedis.close();
  25. }
  26. System.out.println("redis 每秒操作: " + i + "次");// 打印1秒内对 Redis 的操作次数
  27. }
  28.  
  29. private JedisPool testPool() {
  30. JedisPoolConfig poolCfg = new JedisPoolConfig();
  31. // 设置最大空闲数
  32. poolCfg.setMaxIdle(50);
  33. // 设置最大连接数
  34. poolCfg.setMaxTotal(100);
  35. // 设置最大等待毫秒数
  36. poolCfg.setMaxWaitMillis(20000);
  37. // 使用配置创建连接池
  38. JedisPool pool = new JedisPool(poolCfg, "localhost");
  39. return pool;
  40. }
  41.  
  42. public static void main(String[] args) {
  43. new JedisTest().testJedis();
  44. }
  45. }

在Java中使用Redis

  3.在 Spring 中使用Redis

  (1)先用 Spring 配置一个 JedisPoolConfig 对象

  1. <bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
  2. <!--最大空闲数 -->
  3. <property name="maxIdle" value="50" />
  4. <!--最大连接数 -->
  5. <property name="maxTotal" value="100" />
  6. <!--最大等待时间 -->
  7. <property name="maxWaitMillis" value="20000" />
  8. </bean>

  (2)在使用 Spring 提供的RedisTemplate之前需要配置Spring所提供的连接工厂,在 Spring Data Redis 方案中有4种工厂模型:选择其中的一种,JedisConnectionFactory

  1. <bean id="connectionFactory"
  2. class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
  3. <property name="hostName" value="localhost" />
  4. <property name="port" value="6379" />
  5. <property name="poolConfig" ref="poolConfig" />
  6. </bean>

  (3)普通的连接使用没有办法把 Java 对象直接存入 Redis,可以使用 Spring 内部提供的 RedisSerializer 接口和一些实现类实现序列化和反序列化。

  JdkSerializationRedisSerializer是使用 JDK 的序列化器进行转换,而StringRedisSerializer使用字符串进行序列化

  1. <bean id="jdkSerializationRedisSerializer"
  2. class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer" />
  3.  
  4. <bean id="stringRedisSerializer"
  5. class="org.springframework.data.redis.serializer.StringRedisSerializer" />

  (4)由于需要配置key和value两个不同的序列化方式,那么可以指定各自使用的序列化器。至此,就可以得到一个Spring提供的RedisTemplate来进行操作Redis

  1. <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
  2. <property name="connectionFactory" ref="connectionFactory" />
  3. <property name="keySerializer" ref="stringRedisSerializer" />
  4. <property name="valueSerializer" ref="jdkSerializationRedisSerializer" />
  5. </bean>

  (5)创建POJO类,必须实现Serializable接口

  1. package com.ssm.chapter17.pojo;
  2.  
  3. import java.io.Serializable;
  4.  
  5. public class Role implements Serializable {
  6.  
  7. private static final long serialVersionUID = 6977402643848374753L;
  8.  
  9. private long id;
  10. private String roleName;
  11. private String note;
  12.   /*****************************getter and setter**************************************/
  13. }

  (6)使用RedisTemplate操作Redis

  1. private static void testSpring() {
  2. ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
  3. RedisTemplate redisTemplate = applicationContext.getBean(RedisTemplate.class);
  4. Role role = new Role();
  5. role.setId(1L);
  6. role.setRoleName("role_name_1");
  7. role.setNote("note_1");
  8. redisTemplate.opsForValue().set("role_1", role);
  9. Role role1 = (Role) redisTemplate.opsForValue().get("role_1");
  10. System.out.println(role1.getRoleName());
  11. }

  然而,这样的方式可能存在问题:执行set和get方法的Redis连接对象可能来自同一个Redis连接池的不同Redis的连接。为了使得set和get操作都来自同一个连接,可以使用SessionCallback

  (7)使用SessionCallback来将多个命令放入到同一个 Redis 连接中执行

  这里使用匿名类的方式,还可以使用 Lambda 的方式进行编写SessionCallback的业务逻辑。

  由于前后使用的都是同一个连接,因此对于资源损耗就比较小,在使用Redis操作多个命令或者使用事务的时候也会用到它。

  1. private static void testSessionCallback() {
  2. ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
  3. RedisTemplate redisTemplate = applicationContext.getBean(RedisTemplate.class);
  4. Role role = new Role();
  5. role.setId(1);
  6. role.setRoleName("role_name_1");
  7. role.setNote("role_note_1");
  8. SessionCallback callBack = new SessionCallback<Role>() {
  9. @Override
  10. public Role execute(RedisOperations ops) throws DataAccessException {
  11. ops.boundValueOps("role_1").set(role);
  12. return (Role) ops.boundValueOps("role_1").get();
  13. }
  14. };
  15. Role savedRole = (Role) redisTemplate.execute(callBack);
  16. System.out.println(savedRole.getId());
  17. }

  例如,简化成Lambda表达式为:

  1. private static void testSessionCallback() {
  2. ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
  3. RedisTemplate redisTemplate = applicationContext.getBean(RedisTemplate.class);
  4. Role role = new Role();
  5. role.setId(1);
  6. role.setRoleName("role_name_1");
  7. role.setNote("role_note_1");
  8.  
  9. Role savedRole = (Role) redisTemplate.execute((RedisOperations ops) -> {
  10. ops.boundValueOps("role_4").set(role);
  11. return (Role) ops.boundValueOps("role_4").get();
  12. });
  13.  
  14. System.out.println(savedRole.getId());
  15. }
  16.  
  17. }

Redis(十四)Redis 在Java Web 中的应用的更多相关文章

  1. “全栈2019”Java第二十四章:流程控制语句中决策语句switch下篇

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...

  2. 进击的Python【第十四章】:Web前端基础之Javascript

    进击的Python[第十四章]:Web前端基础之Javascript 一.javascript是什么 JavaScript 是一种轻量级的编程语言. JavaScript 是可插入 HTML 页面的编 ...

  3. 【中文乱码】深入分析 Java Web 中的中文编码问题

    深入分析 Java Web 中的中文编码问题 1.几种常见的编码格式 1.1 为什么要编码 在计算机中存储信息的最小单元是 1 个字节,即 8 个 bit, 所以能表示的字符范围是 0 ~ 255 个 ...

  4. Java Web 中 过滤器与拦截器的区别

    过滤器,是在java web中,你传入的request,response提前过滤掉一些信息,或者提前设置一些参数,然后再传入servlet或者struts的 action进行业务逻辑,比如过滤掉非法u ...

  5. JAVA WEB 中的编码分析

    JAVA WEB 中的编码分析 */--> pre.src {background-color: #292b2e; color: #b2b2b2;} pre.src {background-co ...

  6. Java web中常见编码乱码问题(一)

    最近在看Java web中中文编码问题,特此记录下. 本文将会介绍常见编码方式和Java web中遇到中文乱码问题的常见解决方法: 一.常见编码方式: 1.ASCII 码 众所周知,这是最简单的编码. ...

  7. Java web中常见编码乱码问题(二)

    根据上篇记录Java web中常见编码乱码问题(一), 接着记录乱码案例: 案例分析:   2.输出流写入内容或者输入流读取内容时乱码(内容中有中文) 原因分析: a. 如果是按字节写入或读取时乱码, ...

  8. 深入分析Java Web中的编码问题

    编码问题一直困扰着我,每次遇到乱码或者编码问题,网上一查,问题解决了,但是实际的原理并没有搞懂,每次遇到,都是什么头疼. 决定彻彻底底的一次性解决编码问题. 1.为什么要编码 计算机的基本单元是字节, ...

  9. 解决java web中safari浏览器下载后文件中文乱码问题

    解决java web中safari浏览器下载后文件中文乱码问题 String fileName = "测试文件.doc"; String userAgent = request.g ...

随机推荐

  1. Spring boot 官网学习笔记 - Spring Boot 属性配置和使用(转)-application.properties

    Spring Boot uses a very particular PropertySource order that is designed to allow sensible overridin ...

  2. MYSQL-用户密码修改

    解决方法如下:1.终端中结束当前正在运行的mysql进程.# sudo /etc/init.d/mysql stop2.用mysql安全模式运行并跳过权限验证.# sudo /usr/bin/mysq ...

  3. C#2匿名方法中的捕获变量

    乍一接触"匿名方法中的捕获变量"这一术语可能会优点蒙,那什么是"匿名方法中的捕获变量"呢?在章节未开始之前,我们先定义一个委托:public delegate  ...

  4. request.getAttribute()和request.getParameter()

    request.getParameter()取得是通过容器的实现来取得通过类似post,get等方式传入的数据,request.setAttribute()和getAttribute()只是在web容 ...

  5. HashMap底层数据结构详解

    一.HashMap底层数据结构 JDK1.7及之前:数组+链表 JDK1.8:数组+链表+红黑树 关于HashMap基本的大家都知道,但是为什么数组的长度必须是2的指数次幂,为什么HashMap的加载 ...

  6. 【NOIP2011】选择客栈

    题文: 丽江河边有n 家很有特色的客栈,客栈按照其位置顺序从1 到n 编号.每家客栈都按照某一种色调进行装饰(总共k 种,用整数0 ~ k-1 表示),且每家客栈都设有一家咖啡店,每家咖啡店均有各自的 ...

  7. 【DP合集】合并 union

    给出一个 1 ∼ N 的序列 A ( A 1 , A 2 , ..., A N ) .你每次可以将两个相邻的元素合并,合并后的元素权值即为 这两个元素的权值之和.求将 A 变为一个非降序列,最少需要多 ...

  8. BZOJ 4392 卡牌游戏

    Description 奶牛贝茜是卡牌游戏的狂热爱好者, 但是令人吃惊的, 她缺乏对手. 不幸的是, 任何牧 群里的其他牛都不是好对手. 他们实在是太差了 , 实际上, 他们玩卡牌游戏时会遵循一种完全 ...

  9. SQL SERVER数据库批量替换某个数据表里的数据update

    批量替换:将A表CMC里面所有包含a替换成b而不影响其他内容UPDATE A SET CMC=REPLACE(CMC,'a','b')

  10. 性能测试:Jmeter压测过程中的短信验证码读取

    问题背景 现如今国内的大部分软件或者网站应用,普遍流行使用短信业务,比如登录.注册以及特定的业务通知等. 对于这些业务,在使用Jmeter进行性能测试的过程中,就会需要自动获取和填入短信验证码,否则性 ...