zoukankan      html  css  js  c++  java
  • Java秒杀系统方案优化 高性能高并发实战(1)

    首先先把 springboot +thymeleaf 搞起来 ,参考 springboot 官方文档

    本次学习 使用 springboot + thymeleaf+mybatis+redis+RabbitMQ 等实现,未完待继续补充
    
    开发环境: IDEA  
    接口测试: PostMan
    鸭梨测试:JMeter
    
    

    整体结构

    那么使用 idea ——>file -->new project --> maven project

    修改 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>springbootdemo</groupId>
        <artifactId>springbootdemo</artifactId>
        <version>1.0-SNAPSHOT</version>
    
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>1.5.2.RELEASE</version>
        </parent>
    
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-thymeleaf</artifactId>
            </dependency>
    
        </dependencies>
    
        <properties>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
            <!-- set thymeleaf version -->
            <thymeleaf.version>3.0.0.RELEASE</thymeleaf.version>
            <thymeleaf-layout-dialect.version>2.0.0</thymeleaf-layout-dialect.version>
        </properties>
    
    </project>
    
    

    为 项目 书写一个 启动类

    package com.ghc.starter;  //  千万注意,这里将启动类放置在 starter 这个 root 目录下,后面 controller, service,dao 等就不用写注解去 @ComponentScan 了
    
    import org.springframework.boot.*;
    import org.springframework.boot.autoconfigure.*;
    import org.springframework.web.bind.annotation.*;
    
    @RestController
    @SpringBootApplication
    public class Example {
    
        @RequestMapping("/")
        String home() {
            return "Hello World!";
        }
    
        public static void main(String[] args) throws Exception {
            SpringApplication.run(Example.class, args);
        }
    
    }
    

    这里 ,一个官方 简单的 快速搭建就已经实现了

    手动输入 http://localhost:8080 即可访问 返回 页面 Hello World!
    

    接下来,正常开发 是 分离 starter 与 controller 的

    springboot 默认会有一个 application.properties 文件,我们可以在 resources 下 手动创建一个,为其添加 thymeleaf 的配置,实际上,数据源(spring.datasource.name)等都可以在此配置

    #thymelea模板配置
    spring.thymeleaf.prefix=classpath:/templates/
    spring.thymeleaf.suffix=.html
    spring.thymeleaf.mode=HTML5
    spring.thymeleaf.encoding=UTF-8
    #热部署文件,页面不产生缓存,及时更新
    spring.thymeleaf.cache=false
    spring.resources.chain.strategy.content.enabled=true
    spring.resources.chain.strategy.content.paths=/**
    
    

    扩展 数据源的配置

    server.port=8888
    
    spring.datasource.driver-class-name=com.amazon.redshift.jdbc41.Driver
    
    spring.datasource.url=jdbc:xxx
    spring.datasource.username= xxx
    spring.datasource.password= xxx
    
    #自动提交
    spring.datasource.default-auto-commit=true
    spring.datasource.initialSize=5  
    spring.datasource.minIdle=5  
    spring.datasource.maxActive=100
    # 配置获取连接等待超时的时间
    spring.datasource.maxWait=60000
    # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
    spring.datasource.timeBetweenEvictionRunsMillis=60000
    # 配置一个连接在池中最小生存的时间,单位是毫秒
    spring.datasource.minEvictableIdleTimeMillis=300000
    spring.datasource.validationQuery=SELECT 1
    spring.datasource.testWhileIdle=true
    spring.datasource.testOnBorrow=false
    spring.datasource.testOnReturn=false
    
    # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
    spring.datasource.filters=stat,wall,log4j
    spring.datasource.logSlowSql=true
    
    
    mybatis.config-locations=classpath:mybatis/mybatis-config.xml
    mybatis.mapper-locations=classpath:mapper/*.xml
    mybatis.type-aliases-package=com.middleplugin.pojo
    
    

    在 resources 下 创建一个 templates 用来做 视图渲染的 view 层,这里可以是 html,不是 jsp 哦,不要太爽。。

    特别注意这里 <html lang="en" xmlns:th="http://www.thymeleaf.org"> 需要写上 namespace

    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>hello</title>
        <meata http-equiv="Content-Type" content="text/html;charset=UTF-8"/>
    
    </head>
    <body>
     <p th:text="'hello:'+${name}"></p>
    </body>
    </html>
    
    

    创建一个 controller 类,用来验证 thymeleaf 模板引擎起作用了。

    import org.springframework.ui.Model;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @Controller
    @RequestMapping("/center")
    public class centercontroller {
        @RequestMapping("/sayhello")
        public String hello(Model model){
            model.addAttribute("name","frank");
            return "hello";
        }
    }
    
    

    至此,我们可以验证一下,启动 Example 类,在 浏览器端输入 http://localhost:8080/center/sayhello 如果你有自定义 server.port 那么 就用自定义那个端口访问

    整合 mybatis ,参考mybatis 官方文档其实 也没啥整合的 ,比 springmvc 容易整合。。。

    准备我们的 sql 脚本

    create database miaosha;
    
    use miaosha;
    create table user(id int not null auto_increment primary key,name varchar(10));
    
    insert into user(name) values('frank');
    
    select * from user;
    

    修改 pom.xml 添加依赖 springboot-mybatis 整合依赖包, druid 连接池, mysql jdbc 连接驱动

     <!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid</artifactId>
                <version>1.1.12</version>
            </dependency>
    
            <dependency>
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
                <version>1.3.2</version>
            </dependency>
    
            <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>8.0.12</version>
            </dependency>
    

    修改 application.properties ,添加 mybatis, druid 等配置 注意 mybatis.mapper-locations=classpath:dao/*.xml 这个 mapper 文件指定很重要,当然后面会用注解来一次

    
    # mybatis
    # application.properties
    #mybatis.type-aliases-package=com.ghc.starter.domain.model
    #mybatis.type-handlers-package=com.example.typehandler
    mybatis.configuration.map-underscore-to-camel-case=true
    mybatis.configuration.default-fetch-size=100
    mybatis.configuration.default-statement-timeout=3000
    mybatis.mapper-locations=classpath:dao/*.xml
    
    # druid
    spring.datasource.url=jdbc:mysql://localhost/miaosha?useUnicode=true&characterEncoding=utf-8
    spring.datasource.username=root
    spring.datasource.password=xxx
    spring.datasource.driver-class-name=com.mysql.jdbc.Driver
    spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
    spring.datasource.filters=stat
    spring.datasource.maxActive=2
    spring.datasource.initialSize=1
    spring.datasource.maxWait=60000
    spring.datasource.minldle=1
    spring.datasource.timeBetweenEvictionRunsMillis=60000
    spring.datasource.minEvictableldleTimeMillis=300000
    spring.datasource.validationQuery=select 'x'
    spring.datasource.testWhileldle=true
    spring.datasouce.testOnBorrow=false
    spring.datasource.poolPreparedStatements=true
    spring.datasource.maxOpenPreparedStatements=20
    
    

    接下来做个 简单测试

    pojo

    package com.ghc.starter.domain.model;
    
    import org.springframework.stereotype.Component;
    
    @Component
    public class User {
        private int id;
        private String name;
    
        public int getId() {
            return id;
        }
    
        public void setId(int id) {
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    }
    
    

    dao

    package com.ghc.starter.dao;
    
    import com.ghc.starter.domain.model.User;
    import org.apache.ibatis.annotations.Mapper;
    import org.apache.ibatis.annotations.Select;
    
    import java.util.List;
    
    @Mapper
    public interface UserDao {
        @Select("select * from user")
        List<User> getAllUsers();
    }
    
    

    service 接口

    package com.ghc.starter.service;
    
    import com.ghc.starter.domain.model.User;
    
    import java.util.List;
    
    public interface UserService {
        List<User> getAllUsers();
    }
    
    

    service 实现类

    package com.ghc.starter.service.Impl;
    
    import com.ghc.starter.dao.UserDao;
    import com.ghc.starter.domain.model.User;
    import com.ghc.starter.service.UserService;
    import org.apache.log4j.Logger;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.dao.DataAccessException;
    import org.springframework.stereotype.Service;
    
    import java.util.List;
    
    @Service
    public class UserServiceImpl implements UserService{
        @Autowired
        private UserDao userDao;
        private final Logger logger = Logger.getLogger(UserServiceImpl.class);
    
        @Override
        public List<User> getAllUsers() {
            List userList = null;
            try{userList = userDao.getAllUsers();
            }catch(DataAccessException de){
                logger.error(de);
                throw new RuntimeException("数据库访问不到");
            }
            return userList;
        }
    }
    
    

    优雅地 使用 枚举+泛型 封装 返回值类型 Result

    code msg 基接口

    package com.ghc.starter.result;
    
    public interface BaseCodeMsg {
        String msg=null;
        int code=500;
    }
    
    

    code msg 实现类

    package com.ghc.starter.result;
    
    public enum CodeMsg implements  BaseCodeMsg{
        SUCCESS("SUCCESS",200),ERROR("ERROR",500);
        private final String msg;
        private final int code;
        CodeMsg(String msg,int code){
            this.msg = msg;
            this.code = code;
        }
    
        public String getMsg() {
            return msg;
        }
    
        public int getCode() {
            return code;
        }
    }
    
    

    最后 Result 类

    package com.ghc.starter.result;
    
    public class Result<T> {
        private String msg;
        private int code;
        private T data;
        public static <T> Result<T> success(T data){
            return new Result<T>(data);
        }
        public static <T> Result<T> error(){
            Result result = new Result<T>(null);
            result.code = CodeMsg.ERROR.getCode();
            result.msg = CodeMsg.ERROR.getMsg();
            return result;
        }
        private  Result(T data){
            this.data = data;
            this.msg = CodeMsg.SUCCESS.getMsg();
            this.code = CodeMsg.SUCCESS.getCode();
        }
    
        public String getMsg() {
            return msg;
        }
    
        public int getCode() {
            return code;
        }
    
        public T getData() {
            return data;
        }
    }
    
    

    controller 层

    package com.ghc.starter.controller;
    
    import com.ghc.starter.domain.model.User;
    import com.ghc.starter.result.Result;
    import com.ghc.starter.service.UserService;
    import org.mybatis.spring.annotation.MapperScan;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.Model;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import java.util.List;
    
    @RestController
    @RequestMapping("/center")
    public class centercontroller {
        @Autowired
        private UserService userService;
        @RequestMapping("/sayhello")
        public String hello(Model model){
            model.addAttribute("name","frank");
            return "hello";
        }
    
        @RequestMapping("/getAllUsers")
        public Result<List<User>> getAllUsers(){
            List<User> userList = null;
            Result<List<User>> listResult=null;
            try{userList = userService.getAllUsers();
    //            int a = 1/0; //测试下异常
                listResult = Result.success(userList);
            }catch(Exception e){
                listResult = Result.error();
            }
            return listResult;
        }
    }
    
    

    Postman测试访问

    引入 redis 缓存

    基于 redis api 封装自己的 redis 操作类

    修改 application.properties 文件 添加 redis 相关配置
    #redis
    redis.host=192.168.32.129
    redis.port=6379
    redis.timeout=3
    redis.password=frank
    redis.poolMaxTotal=10
    redis.poolMaxldle=10
    # 秒 = ms * 1000
    redis.poolMaxWait=3
    
    
    读取 application.properties 文件 redis 部分 配置。
    package com.ghc.starter.redis;
    
    import org.springframework.boot.context.properties.ConfigurationProperties;
    import org.springframework.stereotype.Component;
    
    @Component
    @ConfigurationProperties(prefix = "redis")
    public class RedisConfig {
        private String host;
        private int port;
        private int timeout;
        private String password;
        private int poolMaxTotal;
        private int poolMaxldle;
        private int poolMaxWait;
    
        public String getHost() {
            return host;
        }
    
        public void setHost(String host) {
            this.host = host;
        }
    
        public int getPort() {
            return port;
        }
    
        public void setPort(int port) {
            this.port = port;
        }
    
        public int getTimeout() {
            return timeout;
        }
    
        public void setTimeout(int timeout) {
            this.timeout = timeout;
        }
    
        public String getPassword() {
            return password;
        }
    
        public void setPassword(String password) {
            this.password = password;
        }
    
        public int getPoolMaxTotal() {
            return poolMaxTotal;
        }
    
        public void setPoolMaxTotal(int poolMaxTotal) {
            this.poolMaxTotal = poolMaxTotal;
        }
    
        public int getPoolMaxldle() {
            return poolMaxldle;
        }
    
        public void setPoolMaxldle(int poolMaxldle) {
            this.poolMaxldle = poolMaxldle;
        }
    
        public int getPoolMaxWait() {
            return poolMaxWait;
        }
    
        public void setPoolMaxWait(int poolMaxWait) {
            this.poolMaxWait = poolMaxWait;
        }
    }
    
    
    redis 连接池 工厂类
    package com.ghc.starter.redis;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Bean;
    import org.springframework.stereotype.Service;
    import redis.clients.jedis.JedisPool;
    import redis.clients.jedis.JedisPoolConfig;
    
    @Service
    public class RedisPoolFactory {
        @Autowired
        private RedisConfig redisConfig;
        @Bean
        public JedisPool jedisPoolFactory(){
            JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
            jedisPoolConfig.setMaxWaitMillis(redisConfig.getPoolMaxWait()*1000); // 配置文件里是秒
            jedisPoolConfig.setMaxTotal(redisConfig.getPoolMaxTotal());
            jedisPoolConfig.setMaxIdle(redisConfig.getPoolMaxldle());
            JedisPool jedisPool = new JedisPool(jedisPoolConfig,redisConfig
                    .getHost(),redisConfig.getPort(),
                    redisConfig.getTimeout(),redisConfig.getPassword(),0);
            return jedisPool;
        }
    }
    
    
    自定义封装的 redis 操作类,主要实现 set / get 方法
    package com.ghc.starter.redis;
    
    import com.alibaba.fastjson.JSON;
    import org.springframework.beans.factory.annotation.Autowired;
    import redis.clients.jedis.Jedis;
    import redis.clients.jedis.JedisPool;
    
    public class RedisService <T> {
        @Autowired
        private JedisPool jedisPool;
    
        public T get(String key,Class<T> clazz){
            Jedis jedis = null;
            T t;
            try{jedis = jedisPool.getResource();
                String value = jedis.get(key);
                t = str2Bean(value,clazz);
            }finally{
                return2Pool(jedis);
            }
            return t;
        }
        public boolean set(String key,T value){
            Jedis jedis = null;
            try{jedis = jedisPool.getResource();
                String str = bean2Str(value);
                if(str==null||str.length()<=0){
                    return false;
                }else{ jedis.set(key,str);
                        return true;
                    }
            }finally{
                return2Pool(jedis);
            }
        }
        @SuppressWarnings("unchecked")
        private String bean2Str(T value){
            Class<?> clazz = value.getClass();
            if(value == null){
                return null;
            }
            else if(clazz == int.class||clazz == Integer.class){
               return ""+value;
            }else if(clazz == String.class){
                return (String)value;
            }else if(clazz == long.class||clazz == Long.class){
                return ""+value;
            }else{
                return JSON.toJSONString(value);
            }
        }
        @SuppressWarnings("unchecked")
        private T str2Bean(String value, Class<T> clazz) {
            if(value==null||value.length()<=0||clazz==null){
                return null;
            }else if(clazz==int.class||clazz == Integer.class){
                return (T)Integer.valueOf(value);
            }else if(clazz==long.class||clazz == Long.class){
                return (T)Long.valueOf(value);
            }else if(clazz==String.class){
                return (T)value;
            }else{ return JSON.toJavaObject(JSON.parseObject(value),clazz);}
        }
    
        private void return2Pool(Jedis jedis) {
            if(jedis!=null){
                jedis.close();
            }
        }
    }
    
    

    junit Test 测试一下

    package com.ghc.starter;
    
    
    import com.ghc.starter.redis.RedisService;
    import org.junit.runner.RunWith;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.test.context.junit4.SpringRunner;
    
    @Configuration
    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class Test {
        @Autowired
        private RedisService redisService;
    
        @org.junit.Test
        public void testRedisService(){
            redisService.set("key1","value1");
            String value = (String)redisService.get("key1",String.class);
            System.out.println("value: "+value);
        }
    
    }
    
    
    发现报 异常如下:
    Exception in thread "main" redis.clients.jedis.exceptions.JedisConnectionException: java.net.SocketTimeoutException: Read timed out
    
    那么经过查询得知需要关闭防火墙
    1、关闭firewall:
    systemctl stop firewalld.service #停止firewall
    systemctl disable firewalld.service #禁止firewall开机启动
    firewall-cmd --state #查看默认防火墙状态(关闭后显示notrunning,开启后显示running)
    firewall-cmd --zone=public --add-port=6379/tcp --permanent  #开放 6379 端口
    

    为了防止 key 混淆 比如 User 的 key 与 Order 的 key ,最好绑定类名以及 id 或 name 的方式 存储一起做key ,也就是 把类名 做成 前缀

    前缀 接口
    package com.ghc.starter.prefix;
    
    public interface KeyPrefix {
        int expireSeconds();
        String getPrefix();
    }
    
    

    前缀接口实现的 抽象类

    package com.ghc.starter.prefix;
    
    public abstract class BaseKeyPrefix implements KeyPrefix {
    
        BaseKeyPrefix(String prefix){
            this(0,prefix);
        }
        private BaseKeyPrefix(int expireSeconds,String prefix){
            this.expireSeconds = expireSeconds;
            this.prefix = prefix;
        }
        private String prefix;
        private int expireSeconds;
        @Override
        public int expireSeconds() {  // 默认 0 代表永不过期
            return expireSeconds;
        }
    
        @Override
        public String getPrefix() {
            return getClass().getSimpleName()+":"+prefix;
        }
    }
    
    
    前缀抽象类的 具体子类之一, 还可以有 OrderKey
    package com.ghc.starter.prefix;
    
    public class UserKey extends BaseKeyPrefix {
        private UserKey(String prefix) {
            super(prefix);
        }
    
        public static UserKey getById = new UserKey("id");
        public static UserKey getByName = new UserKey("name");
    
    }
    
    此时,我们需要对 RedisService 里面的 set , get 方法增加 输入参数 Prefix
     public T get(KeyPrefix keyPrefix,String key, Class<T> clazz){
            Jedis jedis = null;
            T t;
            try{jedis = jedisPool.getResource();
                String realKey = keyPrefix.getPrefix() + key;
                String value = jedis.get(realKey);
                t = str2Bean(value,clazz);
            }finally{
                return2Pool(jedis);
            }
            return t;
        }
        public boolean set(KeyPrefix keyPrefix,String key,T value){
            Jedis jedis = null;
            try{jedis = jedisPool.getResource();
                String str = bean2Str(value);
                if(str==null||str.length()<=0){
                    return false;
                    }else{
                             String realKey = keyPrefix.getPrefix() + key;
                             int expireSeconds = keyPrefix.expireSeconds();
                             if(expireSeconds>0){
                                 jedis.setex(realKey,expireSeconds,str);
                             }
                             else{
                                    jedis.set(realKey,str);
                             }
                     return true;
                    }
            }finally{
                return2Pool(jedis);
            }
        }
    
    接下来,再做一次 JUnit 测试
    package com.ghc.starter;
    
    
    import com.ghc.starter.domain.model.User;
    import com.ghc.starter.prefix.UserKey;
    import com.ghc.starter.redis.RedisService;
    import org.junit.runner.RunWith;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.test.context.junit4.SpringRunner;
    
    @Configuration
    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class Test {
        @Autowired
        private RedisService redisService;
    
        @org.junit.Test
        public void testRedisService(){
            User user = new User();
            user.setId(2);
            user.setName("frank2");
    
            redisService.set(UserKey.getById,"1",user);
            User getUser = (User)redisService.get(UserKey.getById,"1",User.class);
    
            System.out.println(getUser.getName());
        }
    
    }
    
    
    如果有来生,一个人去远行,看不同的风景,感受生命的活力。。。
  • 相关阅读:
    ubuntu 启动 重启 停止 apache
    /usr/bin/env: php: No such file or directory 【xunsearch demo项目体验】【已解决】
    安装mezzanine时报:storing debug log for failure【已解决】
    redhat 安装 setuptools【成功】
    SnowNLP:一个处理中文文本的 Python 类库[转]
    Android 了解1G 2G 3G 知识
    Android-体系架构
    Android-bindService远程服务(Aidl)-初步
    Android-SDCard外部存储文件读写
    Android-文件模式
  • 原文地址:https://www.cnblogs.com/Frank99/p/10007625.html
Copyright © 2011-2022 走看看