Java秒杀系统实践学习
前言
java秒杀系统核心设计到分布式系统方案、系统的极致优化、深入微服务技能、安全策略,希望通过对他的学习能过对他所涉及到的springboot框架体系结构、redis缓存、mybatis等有进一步了解和认知。
项目框架的搭建
项目框架的搭建分为四部分:
一、 Spring Boot的环境搭建;
通过idea创建springboot项目,点击File再点击project然后选择Spring initializr,勾选mybatis、redis、Mysql、Thymaleaf的依赖,创建springboot的项目
二、 集成Thymeleaf,Result结果封装;
thymeleaf模板引擎是用来替代jsp,默认页面跳转默认路径:src/main/resources/templates,静态资源默认路径:src/main/resources/static.
使用Thymeleaf的依赖,如下所示:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
在配置文件中application.properties添加以下配置信息
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
spring.thymeleaf.cache=false
spring.thymeleaf.servlet.content-type=text/html
spring.thymeleaf.enabled=true
spring.thymeleaf.encoding=UTF-8
spring.thymeleaf.mode= HTML5
其中主要的是spring.thymeleaf.prefix=classpath:/templates/(路径)和spring.thymeleaf.suffix=.html(文件后缀)两条配置信息,有了之后在Controller中返回的字符串会到resources/templates下对对应文件进行映射
在写controller里映射,就可以让其映射到resources/templates下文件hello.html文件
Result结果封装是因为在处理数据后都会返回一个数据值,为方便对返回结果的统一处理而创建的;主要包含两部分,在CodeMsg类中封装状态码和状态信息,也就是状态码(code)、状态信息(msg)变量,然后进一步封装一个CodeMsg类型的静态变量来使用,在Result类中封装信息处理的返回数据,因为具体数据的类型不确定,所以结果类是一个泛型类(Result)
定义各种状态码和信息:
public class CodeMsg {
private int code;
private String msg;
public static CodeMsg SUCCESS = new CodeMsg(0, "success");
public static CodeMsg ERROR = new CodeMsg(500, "服务端异常");
private CodeMsg(int code, String msg) {
this.code = code;
this.msg = msg;
}
public int getCode() {
return code;
}
public String getMsg() {
return msg;
}
}
数据处理返回:
public class Result<T> {
private int code;
private String msg;
private T data;
/**
* 成功时候的调用
* */
public static <T> Result<T> success(T data){
return new Result<T>(data);
}
/**
* 失败时候的调用
* */
public static <T> Result<T> error(CodeMsg cm){
return new Result<T>(cm);
}
/**
* 成功时只设置消息
* */
private Result(T data) {
this.code = 0;
this.msg = "success";
this.data = data;
}
/**
* 将返回消息CodeMsg封装到本类中
* */
private Result(CodeMsg cm) {
if(cm == null) {
return;
}
this.code = cm.getCode();
this.msg = cm.getMsg();
}
public int getCode() {
return code;
}
public String getMsg() {
return msg;
}
public T getData() {
return data;
}
}
三、集成Mybatis+Druid;
使用的依赖信息如下::
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.5</version>
</dependency>
在配置文件中application.properties添加配置数据库连接信息,使用Mybatis注解方式,直接在dao层接口方法上使用mybatis依赖下的注解(连接数据进行测试可能报Error:testWhileIdle is true, validationQuery not set是由于mysql依赖版本引起的,不影响运行)
#mybatis
mybatis.type-aliases-package=com.example.demo.domain
mybatis.configuration.map-underscore-to-camel-case=true
mybatis.configuration.default-fetch-size=100
mybatis.configuration.default-statement-timeout=3000
mybatis.mapper-locations=classpath:com/example/demo/dao/*.xml
#druid
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/miaosha?useSSL=false&serverTimezone=UTC&autoReconnect=true
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driverClassName=com.mysql.cj.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.datasource.testOnBorrow= false
spring.datasource.testOnReturn= false
spring.datasource.poolPreparedStatements =true
spring.datasource.maxOpenPreparedStatements=20
创建数据库和表,写一个用测试数据连接的方法,将结果用Result封装返回:
Controller:
@RequestMapping("/db/get")
@ResponseBody
public Result<User> dbGet (){
User user=userService.getuserByid(1);
return Result.success(user);
}
Service:
@Autowired
UserDao userDao;
public User getuserByid(int id){
return userDao.userByid(id);
}
dao:
@Select("select * from user where id=#{id}")
public User userByid(@Param("id") int id);
测试结果:
四、集成Jedis+Redis的安装+通用缓存Key封装;
Windows下的Redis安装直接使用,修改Redis服务的密码可以在redis.windows.conf或redis.windows-service.conf中修改 requirepass 后面的参数即可,如下所示:
在cmd中进入redis的解压目录,输入redis-server.exe redis.windows.conf可以启动redis.
Reddis简介:Redis是一个速度非常快的非关系数据库(non-relational database),它可以存储键(key)与5种不同类型的值(value)之间的映射(mapping),可以将存储在内存的键值对数据持久化到硬盘,可以使用复制特性来扩展读性能,还可以使用客户端分片来扩展写性能。
Redis与其他数据库及软件之间的区别:
SpringBoot整合Redis的方法:
1、Jedis (具体分为:添加依赖+redis配置信息+JedisPool工厂+RedisService)
2、RedisTemplate(具体分为:添加依赖+redis配置信息+RedisTemplate+序列化+RedisService)
SpringBoot使用Jedis整合Redis:
思路:在RedisConfig里接收配置文件里参数信息,在RedisPoolFactory中用@Bean将JedisPool注入Spring容器,在RedisService中实现Redis服务进行增删改查,通过KeyPrefix 区分模块,对key进行加工,避免团队开发时key被覆盖,采用方式是:接口->抽象类->实现类。
使用Fastjson原因是它可以让Java对象转换为json字符串写到redis的服务器中,序列化后是可读的,方便查看。
在pom.xml添加Jedis和Fastjson依赖信息如下:
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.38</version>
</dependency>
Redis基本配置及工具类
1.创建redis.properties配置文件
注意max-active,max-wait,max-idle,min-idle这几个参数版本不同写法也不一样
#redis配置开始
# Redis数据库索引(默认为0)
spring.redis.database=0
# Redis服务器地址
spring.redis.host=localhost
# Redis服务器连接端口
spring.redis.port=6379
# Redis服务器连接密码(默认为空)
spring.redis.password=123456
# 连接池最大连接数(使用负值表示没有限制)
spring.redis.jedis.pool.max-active=1024
# 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.jedis.pool.max-wait=10000
# 连接池中的最大空闲连接
spring.redis.jedis.pool.max-idle=200
# 连接池中的最小空闲连接
spring.redis.jedis.pool.min-idle=0
# 连接超时时间(毫秒)
spring.redis.timeout=10000
#redis配置结束
spring.redis.block-when-exhausted=true
2.添加配置类RedisConfig.java接收配置文件的参数信息
package com.example.demo.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;
}
}
3.添加RedisPoolFactory类,用@Bean将JedisPool连接池信息注入spring容器
package com.example.demo.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
RedisConfig redisConfig;
@Bean
public JedisPool JedisPoolFactory(){
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxIdle(redisConfig.getPoolMaxldle());
poolConfig.setMaxTotal(redisConfig.getPoolMaxTotal());
poolConfig.setMaxWaitMillis(redisConfig.getPoolMaxWait()* 1000);
JedisPool jp = new JedisPool(poolConfig, redisConfig.getHost(), redisConfig.getPort(),redisConfig.getTimeout()*1000
,redisConfig.getPassword(),0);
return jp;
}
}
4、在RedisService中通过JedisPool创建jedis对象来实现redis的增删改查:
package com.example.demo.redis;
import com.alibaba.fastjson.JSON;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Service;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
@Service
public class RedisSevice {
@Autowired
JedisPool jedisPool;
/*获取当个对象*/
public <T> T get(KeyPrefix prefix,String key,Class<T> clazz){
Jedis jedis=null;
try {
jedis=jedisPool.getResource();
//生成真正的key
String realKey=prefix.getPrefix()+key;
String str=jedis.get(realKey);
T t=stringToBean(str,clazz);
return t;
}finally {
returnToPool(jedis);
}
}
/*设置对象*/
public <T> boolean set(KeyPrefix prefix,String key,T value){
Jedis jedis=null;
try {
jedis=jedisPool.getResource();
String str=stringToBean(value);
if(str==null||str.length()<=0){
return false;
}
//
String realKey=prefix.getPrefix()+key;
int seconds=prefix.expireSecconds();
if (seconds<=0){
jedis.set(realKey,str);
}else{
jedis.setex(realKey,seconds,str);
}
return true;
}finally {
returnToPool(jedis);
}
}
private <T>String stringToBean(T value){
if(value==null){
return null;
}
Class<?> clazz=value.getClass();
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);
}
}
private void returnToPool(Jedis jedis){
if(jedis!=null){
jedis.close();
}
}
private <T> T stringToBean(String str,Class<T> clazz){
if(str==null||str.length()<=0||clazz==null){
return null;
}
if(clazz==int.class||clazz==Integer.class){
return (T)Integer.valueOf(str);
}else if(clazz==String.class){
return (T)str;
}else if(clazz==long.class||clazz==Long.class){
return (T)Long.valueOf(str);
}else{
return JSON.toJavaObject(JSON.parseObject(str),clazz);
}
}
/*判断key是否存在*/
public <T> boolean exists(KeyPrefix prefix,String key){
Jedis jedis=null;
try {
jedis=jedisPool.getResource();
//生成真正的key
String realKey=prefix.getPrefix()+key;
return jedis.exists(realKey);
}finally {
returnToPool(jedis);
}
}
public <T> Long incr(KeyPrefix prefix,String key){
Jedis jedis=null;
try {
jedis=jedisPool.getResource();
//生成真正的key
String realKey=prefix.getPrefix()+key;
return jedis.incr(realKey);
}finally {
returnToPool(jedis);
}
}
public <T> Long decr(KeyPrefix prefix,String key){
Jedis jedis=null;
try {
jedis=jedisPool.getResource();
//生成真正的key
String realKey=prefix.getPrefix()+key;
return jedis.decr(realKey);
}finally {
returnToPool(jedis);
}
}
}
KeyPrefix 设计思路:接口——>抽象——>实现:
目的是在key前面加前缀,避免多人合作时key覆盖使用。
创建接口KeyPrefix :
package com.example.demo.redis;
public interface KeyPrefix {
public int expireSecconds();//有效期
public String getPrefix();//前缀
}
创建抽象类BasePrefix:
package com.example.demo.redis;
public abstract class BasePrefix implements KeyPrefix{
private int expireSeconds;
private String prefix;
public BasePrefix(String prefix) {//0代表永不过期
this(0,prefix);
}
public BasePrefix(int expireSeconds, String prefix) {
this.expireSeconds = expireSeconds;
this.prefix = prefix;
}
@Override
public int expireSecconds() {
return expireSeconds;
}
@Override
public String getPrefix() {
String className=getClass().getSimpleName();
return className+":"+prefix;
}
}
实现Userkey:
package com.example.demo.redis;
public class UserKey extends BasePrefix {
private UserKey( String prefix) {
super(prefix);
}
public static UserKey getById =new UserKey("id");
public static UserKey getByName =new UserKey("name");
}
SpringBoot使用RedisTemplate整合Redis:
添加依赖:
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>${redis.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>${spring.redis.version}</version>
</dependency>
redis的配置文件:
#redis配置开始
# Redis数据库索引(默认为0)
spring.redis.database=0
# Redis服务器地址
spring.redis.host=localhost
# Redis服务器连接端口
spring.redis.port=6379
# Redis服务器连接密码(默认为空)
spring.redis.password=123456
# 连接池最大连接数(使用负值表示没有限制)
spring.redis.jedis.pool.max-active=1024
# 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.jedis.pool.max-wait=10000
# 连接池中的最大空闲连接
spring.redis.jedis.pool.max-idle=200
# 连接池中的最小空闲连接
spring.redis.jedis.pool.min-idle=0
# 连接超时时间(毫秒)
spring.redis.timeout=10000
#redis配置结束
spring.redis.block-when-exhausted=true
RedisTemplate:
@Configuration
@PropertySource("classpath:redis.properties")
@Slf4j
public class RedisConfig {
@Value("${redis.hostName}")
private String hostName;
@Value("${redis.password}")
private String password;
@Value("${redis.port}")
private Integer port;
@Value("${redis.maxIdle}")
private Integer maxIdle;
@Value("${redis.timeout}")
private Integer timeout;
@Value("${redis.maxTotal}")
private Integer maxTotal;
@Value("${redis.maxWaitMillis}")
private Integer maxWaitMillis;
@Value("${redis.minEvictableIdleTimeMillis}")
private Integer minEvictableIdleTimeMillis;
@Value("${redis.numTestsPerEvictionRun}")
private Integer numTestsPerEvictionRun;
@Value("${redis.timeBetweenEvictionRunsMillis}")
private long timeBetweenEvictionRunsMillis;
@Value("${redis.testOnBorrow}")
private boolean testOnBorrow;
@Value("${redis.testWhileIdle}")
private boolean testWhileIdle;
/**
* @auther: zhangyingqi
* @date: 17:52 2018/8/28
* @param: []
* @return: org.springframework.data.redis.connection.jedis.JedisConnectionFactory
* @Description: Jedis配置
*/
@Bean
public JedisConnectionFactory JedisConnectionFactory(){
RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration ();
redisStandaloneConfiguration.setHostName(hostName);
redisStandaloneConfiguration.setPort(port);
//由于我们使用了动态配置库,所以此处省略
//redisStandaloneConfiguration.setDatabase(database);
redisStandaloneConfiguration.setPassword(RedisPassword.of(password));
JedisClientConfiguration.JedisClientConfigurationBuilder jedisClientConfiguration = JedisClientConfiguration.builder();
jedisClientConfiguration.connectTimeout(Duration.ofMillis(timeout));
JedisConnectionFactory factory = new JedisConnectionFactory(redisStandaloneConfiguration,
jedisClientConfiguration.build());
return factory;
}
/**
* @auther: zhangyingqi
* @date: 17:52 2018/8/28
* @param: [redisConnectionFactory]
* @return: com.springboot.demo.base.utils.RedisTemplate
* @Description: 实例化 RedisTemplate 对象
*/
@Bean
public RedisTemplate functionDomainRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
log.info("RedisTemplate实例化成功!");
RedisTemplate redisTemplate = new RedisTemplate();
initDomainRedisTemplate(redisTemplate, redisConnectionFactory);
return redisTemplate;
}
/**
* @auther: zhangyingqi
* @date: 17:52 2018/8/28
* @param: []
* @return: org.springframework.data.redis.serializer.RedisSerializer
* @Description: 引入自定义序列化
*/
@Bean
public RedisSerializer fastJson2JsonRedisSerializer() {
return new FastJson2JsonRedisSerializer<Object>(Object.class);
}
/**
* @auther: zhangyingqi
* @date: 17:51 2018/8/28
* @param: [redisTemplate, factory]
* @return: void
* @Description: 设置数据存入 redis 的序列化方式,并开启事务
*/
private void initDomainRedisTemplate(RedisTemplate redisTemplate, RedisConnectionFactory factory) {
//如果不配置Serializer,那么存储的时候缺省使用String,如果用User类型存储,那么会提示错误User can't cast to String!
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
redisTemplate.setValueSerializer(fastJson2JsonRedisSerializer());
// 开启事务
redisTemplate.setEnableTransactionSupport(true);
redisTemplate.setConnectionFactory(factory);
}
/**
* @auther: zhangyingqi
* @date: 17:51 2018/8/28
* @param: [redisTemplate]
* @return: com.springboot.demo.base.utils.RedisUtil
* @Description: 注入封装RedisTemplate
*/
@Bean(name = "redisUtil")
public RedisUtil redisUtil(RedisTemplate redisTemplate) {
log.info("RedisUtil注入成功!");
RedisUtil redisUtil = new RedisUtil();
redisUtil.setRedisTemplate(redisTemplate);
return redisUtil;
}
}
使用FastJson来实现序列化 :
public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T> {
public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
private Class<T> clazz;
public FastJson2JsonRedisSerializer(Class<T> clazz) {
super(); this.clazz = clazz;
}
@Override
public byte[] serialize(T t) throws SerializationException {
if (t == null) {
return new byte[0];
}
return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
}
@Override
public T deserialize(byte[] bytes) throws SerializationException {
if (bytes == null || bytes.length <= 0) {
return null;
}
String str = new String(bytes, DEFAULT_CHARSET);
return (T) JSON.parseObject(str, clazz);
}
}
RedisService:
public String set(String key, String value,int indexdb) {
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
jedis.select(indexdb);
return jedis.set(key, value);
} catch (Exception e) {
log.error(e.getMessage());
return "0";
} finally {
returnResource(jedisPool, jedis);
}
}