竞争无处不在,青春永不言败!专业撸代码,副业修bug

Talk is cheap , show me the code!



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());
    }

}

posted @ 2018-11-23 14:55  云雾散人  阅读(1091)  评论(0编辑  收藏  举报

Your attitude not your aptitude will determine your altitude!

如果有来生,一个人去远行,看不同的风景,感受生命的活力!