模拟高并发环境下redis缓存穿透(springboot-redis的一个完整案列)

第一步:模拟高并发环境

package com.ernode.springboot.controller;

import com.owernode.springboot.model.User;
import com.owernode.springboot.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

@Controller
public class UserController {
    @Autowired
    private UserService userService;

    @RequestMapping("/boot/select")
    public @ResponseBody
    List<User> select() {


        List<User> list = null;//  userService.selectAll();
        //定义一个拥有20个并发的线程池对象
        ExecutorService service = Executors.newFixedThreadPool(20);
        //定义一个最终执行任务的线程对象


        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                userService.selectAll();
            }
        };

        for (int i = 0; i < 100; i++) {
            //提交线程,并并发执行
            service.submit(runnable);
        }


        return list;
    }
}

service接口:

package com.bjpowernode.springboot.service;

import com.bjpowernode.springboot.model.User;

import java.util.List;

/**
 * ClassName:UserService
 * Package:com.bjpowernode.springboot.service
 * Description:
 *
 * @date:2018/10/9 9:18
 * @author:bjpowernode.com
 */
public interface UserService {
    List<User> selectAll();
}

service实现类:

package com.rnode.springboot.service.impl;

import com.bjpode.springboot.mapper.UserMapper;
import com.bjode.springboot.model.User;
import com.b ernode.springboot.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.stereotype.Service;

import java.util.List;

@Service("userService")
public class UserServiceImpl implements UserService {
    @Autowired
    private UserMapper userMapper;

    @Autowired
    private RedisTemplate redisTemplate;

    @Override
    public List<User> selectAll() {
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        List<User> list = (List<User>) redisTemplate.opsForValue().get("userAll");
        //在高并发情况下会出现缓存穿透问题,原因是当并发请求同时从redis中获取数据时,这个如果redis中没有数据,这些并发请求
        //会同时获取不到,因此会同同时访问访问数据库,并同时向缓存中存入数据
        /*
           解决方案:
             使用双重检查+局部同步锁
             注意:尽量不要使用方法同步
         */
        if (list == null) {//进入if的请求为第一批的同步请求
            synchronized (this) {//第一批请求排队执行
                list = (List<User>) redisTemplate.opsForValue().get("userAll");
                if (list == null) {//进入if的请求为第一批的第一个请求
                    list = userMapper.selectAll();
                    redisTemplate.opsForValue().set("userAll", list);
                    System.out.println("从数据库中获取数据====================");
                } else {
                    System.out.println("从Reids中获取数据----------------------");
                }
            }
        } else {
            System.out.println("从Reids中获取数据----------------------");
        }
        return list;
    }
}

mapper:

package com.bjpowernode.springboot.mapper;

import com.bjpowernode.springboot.model.User;
import org.apache.ibatis.annotations.Mapper;

import java.util.List;

@Mapper
public interface UserMapper {
    int deleteByPrimaryKey(Long id);

    int insert(User record);

    int insertSelective(User record);

    User selectByPrimaryKey(Long id);

    int updateByPrimaryKeySelective(User record);

    int updateByPrimaryKey(User record);

    List<User> selectAll();
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.bjpowernode.springboot.mapper.UserMapper">
    <resultMap id="BaseResultMap" type="com.bjpowernode.springboot.model.User">
        <id column="id" property="id" jdbcType="BIGINT"/>
        <result column="name" property="name" jdbcType="VARCHAR"/>
        <result column="age" property="age" jdbcType="INTEGER"/>
    </resultMap>
    <sql id="Base_Column_List">
    id, name, age
  </sql>
    <select id="selectByPrimaryKey" resultMap="BaseResultMap" parameterType="java.lang.Long">
        select
        <include refid="Base_Column_List"/>
        from t_user
        where id = #{id,jdbcType=BIGINT}
    </select>

    <select id="selectAll" resultMap="BaseResultMap">
        select
        <include refid="Base_Column_List"/>
        from t_user

    </select>


    <delete id="deleteByPrimaryKey" parameterType="java.lang.Long">
    delete from t_user
    where id = #{id,jdbcType=BIGINT}
  </delete>
    <insert id="insert" parameterType="com.bjpowernode.springboot.model.User">
    insert into t_user (id, name, age)
    values (#{id,jdbcType=BIGINT}, #{name,jdbcType=VARCHAR}, #{age,jdbcType=INTEGER})
  </insert>
    <insert id="insertSelective" parameterType="com.bjpowernode.springboot.model.User">
        insert into t_user
        <trim prefix="(" suffix=")" suffixOverrides=",">
            <if test="id != null">
                id,
            </if>
            <if test="name != null">
                name,
            </if>
            <if test="age != null">
                age,
            </if>
        </trim>
        <trim prefix="values (" suffix=")" suffixOverrides=",">
            <if test="id != null">
                #{id,jdbcType=BIGINT},
            </if>
            <if test="name != null">
                #{name,jdbcType=VARCHAR},
            </if>
            <if test="age != null">
                #{age,jdbcType=INTEGER},
            </if>
        </trim>
    </insert>
    <update id="updateByPrimaryKeySelective" parameterType="com.bjpowernode.springboot.model.User">
        update t_user
        <set>
            <if test="name != null">
                name = #{name,jdbcType=VARCHAR},
            </if>
            <if test="age != null">
                age = #{age,jdbcType=INTEGER},
            </if>
        </set>
        where id = #{id,jdbcType=BIGINT}
    </update>
    <update id="updateByPrimaryKey" parameterType="com.bjpowernode.springboot.model.User">
    update t_user
    set name = #{name,jdbcType=VARCHAR},
      age = #{age,jdbcType=INTEGER}
    where id = #{id,jdbcType=BIGINT}
  </update>
</mapper>

model类:

package com.bjpowernode.springboot.model;

import java.io.Serializable;

public class User implements Serializable {
    private Long id;

    private String name;

    private Integer age;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }
}

Application.java

package com.bjpowernode.springboot;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

逆向工程文件

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
        PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
        "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">

<generatorConfiguration>

    <!-- 指定连接数据库的JDBC驱动包所在位置,指定到你本机的完整路径 -->
    <classPathEntry location="D:\course\repository\mysql\mysql-connector-java\5.1.47\mysql-connector-java-5.1.47.jar"/>

    <!-- 配置table表信息内容体,targetRuntime指定采用MyBatis3的版本 -->
    <context id="tables" targetRuntime="MyBatis3">

        <!-- 抑制生成注释,由于生成的注释都是英文的,可以不让它生成 -->
        <commentGenerator>
            <property name="suppressAllComments" value="true"/>
        </commentGenerator>

        <!-- 配置数据库连接信息 -->
        <jdbcConnection driverClass="com.mysql.jdbc.Driver"
                        connectionURL="jdbc:mysql://192.168.31.128:3306/springboot"
                        userId="root"
                        password="123456">
        </jdbcConnection>

        <!-- 生成model类,targetPackage指定model类的包名, targetProject指定生成的model放在eclipse的哪个工程下面-->
        <javaModelGenerator targetPackage="com.bjpowernode.springboot.model" targetProject="src/main/java">
            <property name="enableSubPackages" value="false"/>
            <property name="trimStrings" value="false"/>
        </javaModelGenerator>

        <!-- 生成MyBatis的Mapper.xml文件,targetPackage指定mapper.xml文件的包名, targetProject指定生成的mapper.xml放在eclipse的哪个工程下面 -->
        <sqlMapGenerator targetPackage="com.bjpowernode.springboot.mapper" targetProject="src/main/java">
            <property name="enableSubPackages" value="false"/>
        </sqlMapGenerator>

        <!-- 生成MyBatis的Mapper接口类文件,targetPackage指定Mapper接口类的包名, targetProject指定生成的Mapper接口放在eclipse的哪个工程下面 -->
        <javaClientGenerator type="XMLMAPPER" targetPackage="com.bjpowernode.springboot.mapper"
                             targetProject="src/main/java">
            <property name="enableSubPackages" value="false"/>
        </javaClientGenerator>

        <!-- 数据库表名及对应的Java模型类名 -->
        <table tableName="t_user"
               domainObjectName="User"
               enableCountByExample="false"
               enableUpdateByExample="false"
               enableDeleteByExample="false"
               enableSelectByExample="false"
               selectByExampleQueryId="false"/>

    </context>

</generatorConfiguration>

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.bjpowernode.springboot</groupId>
    <artifactId>09-springboot-redis02</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>09-springboot-redis02</name>
    <description>Demo project for Spring Boot</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.5.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>


</project>

演示效果: redis缓存穿透

解决方案:双重检查机制+同步代码块

链接创建线程对象的三种方式+lambda表达式

发布了388 篇原创文章 · 获赞 40 · 访问量 10万+

猜你喜欢

转载自blog.csdn.net/qq_30347133/article/details/104763478