java防止缓存穿透_Java模拟并解决缓存穿透问题

什么叫做缓存穿透

缓存穿透只会发生在高并发的时候,就是当有10000个并发进行查询数据的时候,我们一般都会先去redis里面查询进行数据,但是如果redis里面没有这个数据的时候,那么这10000个并发里面就会有很大一部分并发会一下子都去mysql数据库里面进行查询了

解决缓存穿透

首先我模拟一下缓存穿透

比如下面的代码

e28bc09b0ff2cae9ac0fc2c689c04cb4.png

Pom.xml代码

xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

4.0.0

com.example

springboot

0.0.1-SNAPSHOT

jar

springboot

Demo project for Spring Boot

org.springframework.boot

spring-boot-starter-parent

2.1.1.RELEASE

UTF-8

UTF-8

1.8

org.springframework.boot

spring-boot-starter-web

org.springframework.boot

spring-boot-devtools

org.springframework.boot

spring-boot-starter-test

test

org.mybatis.spring.boot

mybatis-spring-boot-starter

1.1.1

mysql

mysql-connector-java

org.springframework.boot

spring-boot-maven-plugin

Application.properties

server.port=8081

#DB Configuration:

spring.datasource.driverClassName=com.mysql.jdbc.Driver

spring.datasource.url=jdbc:mysql://47.91.248.236:3306/hello?useUnicode=true&characterEncoding=utf8

spring.datasource.username=root

spring.datasource.password=root

#spring集成Mybatis环境

#pojo别名扫描包

mybatis.type-aliases-package=com.itheima.domain

#加载Mybatis映射文件

mybatis.mapper-locations=classpath:mapper/*Mapper.xml

MyController代码,下面的蓝色代码是模仿10000个并发线程

/**

* sinture.com Inc.

* Copyright (c) 2016-2018 All Rights Reserved.

*/

package com.itheima.controller;

import com.itheima.mapper.UserMapper;

import com.itheima.domain.User;

import com.itheima.service.UserService;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.web.bind.annotation.PathVariable;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.ResponseBody;

import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;

import java.util.List;

import java.util.Map;

/**

* @author xinzhu

* @version Id: MyController.java, v 0.1 2018年12月05日 下午6:29 xinzhu Exp $

*/

@RestController

public class MyController {

@Autowired

private UserService userService;

@RequestMapping("/hello/{id}")

@ResponseBody

public User queryUser(@PathVariable Integer id){

// 蓝色代码注释开始

new Thread(){

@Override

public void run() {

for (int x = 0; x < 10000; x++) {

userService.queryUser(id);

}

}

}.start();

// 蓝色代码注释结束

return userService.queryUser(id);

}

}

User类

/**

* sinture.com Inc.

* Copyright (c) 2016-2018 All Rights Reserved.

*/

package com.itheima.domain;

/**

* @author xinzhu

* @version Id: User.java, v 0.1 2018年12月06日 下午1:40 xinzhu Exp $

*/

public class User {

// 主键

private Integer id;

// 用户名

private String username;

// 密码

private String password;

// 姓名

private String name;

public void setId(Integer id) {

this.id = id;

}

@Override

public String toString() {

return "User{" +

"id=" + id +

", username='" + username + '\'' +

", password='" + password + '\'' +

", name='" + name + '\'' +

'}';

}

public Integer getId() {

return id;

}

public String getUsername() {

return username;

}

public void setUsername(String username) {

this.username = username;

}

public String getPassword() {

return password;

}

public void setPassword(String password) {

this.password = password;

}

public String getName() {

return name;

}

public void setName(String name) {

this.name = name;

}

}

UserService

package com.itheima.service;

import com.itheima.domain.User;

public interface UserService {

public User queryUser(Integer id);

}

UserServiceImpl,下面的蓝色代码就是模仿redis,此时要注意下面的模拟redis的map集合必须放到下面的queryUser的外面,也就是说下面的userMap变量必须是成员变量,不然的话,因为redis是被多个线程共享的,如果你放到下面的queryUser()方法里面,那么就是多个线程有多个userMap集合,下面的代码就是如果查询到数据,那么就用redis里面的,如果查询不到就用数据库里面的

package com.itheima.service;

import com.itheima.domain.User;

import com.itheima.mapper.UserMapper;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.stereotype.Service;

import java.util.HashMap;

import java.util.Map;

@Service

public class UserServiceImpl implements UserService {

@Autowired

private UserMapper userMapper;

// 蓝色代码注释开始

static Map userMap=new HashMap();

static {

User huancun_user =new User();

huancun_user.setId(1);

huancun_user.setName("zhangsan");

huancun_user.setPassword("123");

huancun_user.setName("张三");

userMap.put(1,huancun_user);

}

// 蓝色代码注释结束

public User queryUser(Integer id){

User user=userMap.get(id);

if(user==null){

user= userMapper.queryUser(id);

System.out.println("查询数据库");

userMap.put(user.getId(),user);

}else{

System.out.println("查询缓存");

}

return user;

};

}

SpringbootApplication代码

package com.itheima;

import org.springframework.boot.SpringApplication;

import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication

public class SpringbootApplication {

public static void main(String[] args) {

SpringApplication.run(SpringbootApplication.class, args);

}

}

数据库里面的数据如下

-- ----------------------------

-- Table structure for `user`

-- ----------------------------

DROP TABLE IF EXISTS `user`;

CREATE TABLE `user` (

`id` int(11) NOT NULL AUTO_INCREMENT,

`username` varchar(50) DEFAULT NULL,

`password` varchar(50) DEFAULT NULL,

`name` varchar(50) DEFAULT NULL,

PRIMARY KEY (`id`)

) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8;

-- ----------------------------

-- Records of user

-- ----------------------------

INSERT INTO `user` VALUES ('1', 'zhangsan', '123', '张三');

INSERT INTO `user` VALUES ('2', 'lisi', '123', '李四');

然后我们查询下面的链接,因为此时上面的模拟redis的map集合里面没有id值是2的数据,所以此时都是查询数据库,你想这一下子10000并发过去,数据库会有很大压力的,

1c7fb498f05edcd2a383a780181e3961.png

然后打印结果如下,就是打印了很多查询数据库和查询缓存,此时也就说明10000个并发里面有很多去查询了数据库,这个是要避免的,至于为什么有查询缓存的打印,因为我们把查询的数据给放到模拟的redis里面了啊,所以刚开始的一大部分线程都是查询数据库,然后剩下的都是查询模拟的redis缓存里面的数据

查询数据库

查询数据库

查询数据库

查询数据库

查询数据库

查询数据库

查询数据库

查询数据库

查询数据库

查询数据库

查询数据库

查询数据库

查询数据库

查询缓存

查询缓存

查询缓存

查询缓存

查询缓存

查询缓存

查询缓存

查询缓存

查询缓存

查询缓存

然后我们使用双重检测锁来解决上面的缓存穿透

我们怎么解决缓存穿透呢,即使10000个并发过来,然后这10000个并发需要的数据在redis里面都没有,那么我们应该第一个线程查询数据里面的数据,然后把这个数据给放到redis里面,然后剩下的9999个线程都到redis里面查询,这样就解决了缓存穿透,所以我们可以把上面的代码变成下面这样

比如下面的代码

e28bc09b0ff2cae9ac0fc2c689c04cb4.png

Pom.xml代码

xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

4.0.0

com.example

springboot

0.0.1-SNAPSHOT

jar

springboot

Demo project for Spring Boot

org.springframework.boot

spring-boot-starter-parent

2.1.1.RELEASE

UTF-8

UTF-8

1.8

org.springframework.boot

spring-boot-starter-web

org.springframework.boot

spring-boot-devtools

org.springframework.boot

spring-boot-starter-test

test

org.mybatis.spring.boot

mybatis-spring-boot-starter

1.1.1

mysql

mysql-connector-java

org.springframework.boot

spring-boot-maven-plugin

Application.properties

server.port=8081

#DB Configuration:

spring.datasource.driverClassName=com.mysql.jdbc.Driver

spring.datasource.url=jdbc:mysql://47.91.248.236:3306/hello?useUnicode=true&characterEncoding=utf8

spring.datasource.username=root

spring.datasource.password=root

#spring集成Mybatis环境

#pojo别名扫描包

mybatis.type-aliases-package=com.itheima.domain

#加载Mybatis映射文件

mybatis.mapper-locations=classpath:mapper/*Mapper.xml

MyController代码,下面的蓝色代码是模仿10000个并发线程

/**

* sinture.com Inc.

* Copyright (c) 2016-2018 All Rights Reserved.

*/

package com.itheima.controller;

import com.itheima.mapper.UserMapper;

import com.itheima.domain.User;

import com.itheima.service.UserService;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.web.bind.annotation.PathVariable;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.ResponseBody;

import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;

import java.util.List;

import java.util.Map;

/**

* @author xinzhu

* @version Id: MyController.java, v 0.1 2018年12月05日 下午6:29 xinzhu Exp $

*/

@RestController

public class MyController {

@Autowired

private UserService userService;

@RequestMapping("/hello/{id}")

@ResponseBody

public User queryUser(@PathVariable Integer id){

// 蓝色代码注释开始

new Thread(){

@Override

public void run() {

for (int x = 0; x < 10000; x++) {

userService.queryUser(id);

}

}

}.start();

// 蓝色代码注释结束

return userService.queryUser(id);

}

}

User类

/**

* sinture.com Inc.

* Copyright (c) 2016-2018 All Rights Reserved.

*/

package com.itheima.domain;

/**

* @author xinzhu

* @version Id: User.java, v 0.1 2018年12月06日 下午1:40 xinzhu Exp $

*/

public class User {

// 主键

private Integer id;

// 用户名

private String username;

// 密码

private String password;

// 姓名

private String name;

public void setId(Integer id) {

this.id = id;

}

@Override

public String toString() {

return "User{" +

"id=" + id +

", username='" + username + '\'' +

", password='" + password + '\'' +

", name='" + name + '\'' +

'}';

}

public Integer getId() {

return id;

}

public String getUsername() {

return username;

}

public void setUsername(String username) {

this.username = username;

}

public String getPassword() {

return password;

}

public void setPassword(String password) {

this.password = password;

}

public String getName() {

return name;

}

public void setName(String name) {

this.name = name;

}

}

UserService

package com.itheima.service;

import com.itheima.domain.User;

public interface UserService {

public User queryUser(Integer id);

}

UserServiceImpl,下面的蓝色代码就是模仿redis,此时要注意下面的模拟redis的map集合必须放到下面的queryUser的外面,也就是说下面的userMap变量必须是成员变量,不然的话,因为redis是被多个线程共享的,如果你放到下面的queryUser()方法里面,那么就是多个线程有多个userMap集合,下面的代码就是如果查询到数据,那么就用redis里面的,如果查询不到就用数据库里面的

然后下面的红色代码就是解决上面的缓存穿透问题,使用锁来解决缓存穿透问题,而且叫做双重检测锁,为什么叫做双重检测锁呢,因为有两个if语句,第一个if语句就是为了减少走红色代码里面的同步代码块,因为如果换成里面存在想要的数据,那么就不需要走下面的红色代码里面的同步代码块了,所以有两个if语句,至于为什么要有下面的 user= userMap.get(id);,是因为第一次线程查询把数据放到模仿的redis缓存里面之后,剩下的线程当走到下面的同步代码块的时候,需要在查询一下缓存里面的数据就会发现刚刚第一个线程放到redis里面的数据了,所以才会有下面的红色代码里面的 user= userMap.get(id);

package com.itheima.service;

import com.itheima.domain.User;

import com.itheima.mapper.UserMapper;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.stereotype.Service;

import java.util.HashMap;

import java.util.Map;

@Service

public class UserServiceImpl implements UserService {

@Autowired

private UserMapper userMapper;

// 蓝色代码注释开始

static Map userMap=new HashMap();

static {

User huancun_user =new User();

huancun_user.setId(1);

huancun_user.setName("zhangsan");

huancun_user.setPassword("123");

huancun_user.setName("张三");

userMap.put(1,huancun_user);

}

// 蓝色代码注释结束

public User queryUser(Integer id){

User user=userMap.get(id);

// 红色代码注释开始

if(user==null){

synchronized (this) {

user= userMap.get(id);

if (null == user) {

user= userMapper.queryUser(id);

System.out.println("查询数据库");

userMap.put(user.getId(),user);

}else{

System.out.println("查询缓存");

}

}

}else{

System.out.println("查询缓存");

}

//红色代码注释结束

return user;

};

}

数据库里面的数据如下

-- ----------------------------

-- Table structure for `user`

-- ----------------------------

DROP TABLE IF EXISTS `user`;

CREATE TABLE `user` (

`id` int(11) NOT NULL AUTO_INCREMENT,

`username` varchar(50) DEFAULT NULL,

`password` varchar(50) DEFAULT NULL,

`name` varchar(50) DEFAULT NULL,

PRIMARY KEY (`id`)

) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8;

-- ----------------------------

-- Records of user

-- ----------------------------

INSERT INTO `user` VALUES ('1', 'zhangsan', '123', '张三');

INSERT INTO `user` VALUES ('2', 'lisi', '123', '李四');

然后我们查询下面的链接,因为此时上面的模拟redis的map集合里面没有id值是2的数据,所以此时都是查询数据库,你想这一下子10000并发过去,数据库会有很大压力的,

1c7fb498f05edcd2a383a780181e3961.png

然后打印结果如下,就是就只有第一个打印了查询数据库,然后剩下的都是查询缓存了,这就是解决缓存穿透

查询数据库

查询缓存

查询缓存

查询缓存

查询缓存

查询缓存

查询缓存

查询缓存

查询缓存

查询缓存

查询缓存

查询缓存

查询缓存

查询缓存

查询缓存

查询缓存

查询缓存

查询缓存

查询缓存

查询缓存

查询缓存

查询缓存

总结

以上所述是小编给大家介绍的Java模拟并解决缓存穿透问题,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对脚本之家网站的支持!

如果你觉得本文对你有帮助,欢迎转载,烦请注明出处,谢谢!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值