diff --git a/.classpath b/.classpath new file mode 100644 index 0000000..64f1662 --- /dev/null +++ b/.classpath @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.project b/.project new file mode 100644 index 0000000..5a89e97 --- /dev/null +++ b/.project @@ -0,0 +1,42 @@ + + + spring-boot-seckill + + + + + + org.eclipse.wst.jsdt.core.javascriptValidator + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.wst.common.project.facet.core.builder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + org.eclipse.wst.validation.validationbuilder + + + + + + org.eclipse.jem.workbench.JavaEMFNature + org.eclipse.wst.common.modulecore.ModuleCoreNature + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + org.eclipse.wst.common.project.facet.core.nature + org.eclipse.wst.jsdt.core.jsNature + + diff --git a/.settings/.jsdtscope b/.settings/.jsdtscope new file mode 100644 index 0000000..585c967 --- /dev/null +++ b/.settings/.jsdtscope @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..9c4403f --- /dev/null +++ b/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,13 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=1.7 +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning +org.eclipse.jdt.core.compiler.source=1.7 diff --git a/.settings/org.eclipse.m2e.core.prefs b/.settings/org.eclipse.m2e.core.prefs new file mode 100644 index 0000000..14b697b --- /dev/null +++ b/.settings/org.eclipse.m2e.core.prefs @@ -0,0 +1,4 @@ +activeProfiles= +eclipse.preferences.version=1 +resolveWorkspaceProjects=true +version=1 diff --git a/.settings/org.eclipse.wst.common.component b/.settings/org.eclipse.wst.common.component new file mode 100644 index 0000000..f2e5363 --- /dev/null +++ b/.settings/org.eclipse.wst.common.component @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/.settings/org.eclipse.wst.common.project.facet.core.xml b/.settings/org.eclipse.wst.common.project.facet.core.xml new file mode 100644 index 0000000..8988a5e --- /dev/null +++ b/.settings/org.eclipse.wst.common.project.facet.core.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/.settings/org.eclipse.wst.jsdt.ui.superType.container b/.settings/org.eclipse.wst.jsdt.ui.superType.container new file mode 100644 index 0000000..3bd5d0a --- /dev/null +++ b/.settings/org.eclipse.wst.jsdt.ui.superType.container @@ -0,0 +1 @@ +org.eclipse.wst.jsdt.launching.baseBrowserLibrary \ No newline at end of file diff --git a/.settings/org.eclipse.wst.jsdt.ui.superType.name b/.settings/org.eclipse.wst.jsdt.ui.superType.name new file mode 100644 index 0000000..05bd71b --- /dev/null +++ b/.settings/org.eclipse.wst.jsdt.ui.superType.name @@ -0,0 +1 @@ +Window \ No newline at end of file diff --git a/.settings/org.eclipse.wst.validation.prefs b/.settings/org.eclipse.wst.validation.prefs new file mode 100644 index 0000000..6f1cba6 --- /dev/null +++ b/.settings/org.eclipse.wst.validation.prefs @@ -0,0 +1,2 @@ +disabled=06target +eclipse.preferences.version=1 diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..372a16b --- /dev/null +++ b/pom.xml @@ -0,0 +1,106 @@ + + 4.0.0 + com.itstyle.seckill + spring-boot-seckill + jar + 0.0.1-SNAPSHOT + spring-boot-seckill Maven + http://maven.apache.org + + UTF-8 + springboot + + + org.springframework.boot + spring-boot-starter-parent + 1.5.10.RELEASE + + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.springframework.boot + spring-boot-starter-thymeleaf + + + net.sourceforge.nekohtml + nekohtml + + + org.springframework.boot + spring-boot-starter-data-jpa + + + mysql + mysql-connector-java + + + + org.springframework.boot + spring-boot-starter-redis + 1.4.7.RELEASE + + + + io.springfox + springfox-swagger2 + 2.7.0 + + + io.springfox + springfox-swagger-ui + 2.7.0 + + + + org.redisson + redisson + 2.11.5 + + + org.apache.commons + commons-lang3 + 3.7 + + + + org.springframework.kafka + spring-kafka + 1.3.5.RELEASE + + + + org.apache.curator + curator-recipes + 2.10.0 + + + + spring-boot-seckill + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.springframework + springloaded + 1.2.7.RELEASE + + + + + + diff --git a/src/main/java/com/itstyle/seckill/Application.java b/src/main/java/com/itstyle/seckill/Application.java new file mode 100644 index 0000000..5d32a9e --- /dev/null +++ b/src/main/java/com/itstyle/seckill/Application.java @@ -0,0 +1,24 @@ +package com.itstyle.seckill; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +/** + * 启动类 + * 创建者 科帮网 + * 创建时间 2018年5月12日 + * API接口测试:http://localhost:8080/seckill/swagger-ui.html + */ +@SpringBootApplication +public class Application { + private final static Logger LOGGER = LoggerFactory.getLogger(Application.class); + /** + * 1. 数据库乐观锁;2. 基于Redis的分布式锁;3. 基于ZooKeeper的分布式锁 + * 4. redis 订阅监听;5.kafka消息队列 + */ + public static void main(String[] args) throws InterruptedException { + SpringApplication.run(Application.class, args); + LOGGER.info("项目启动 "); + } +} \ No newline at end of file diff --git a/src/main/java/com/itstyle/seckill/common/api/SwaggerConfig.java b/src/main/java/com/itstyle/seckill/common/api/SwaggerConfig.java new file mode 100644 index 0000000..870a336 --- /dev/null +++ b/src/main/java/com/itstyle/seckill/common/api/SwaggerConfig.java @@ -0,0 +1,27 @@ +package com.itstyle.seckill.common.api; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import springfox.documentation.builders.ApiInfoBuilder; +import springfox.documentation.builders.PathSelectors; +import springfox.documentation.builders.RequestHandlerSelectors; +import springfox.documentation.service.ApiInfo; +import springfox.documentation.service.Contact; +import springfox.documentation.spi.DocumentationType; +import springfox.documentation.spring.web.plugins.Docket; +import springfox.documentation.swagger2.annotations.EnableSwagger2; +@Configuration +@EnableSwagger2 +public class SwaggerConfig { + @Bean + public Docket userApi() { + return new Docket(DocumentationType.SWAGGER_2).groupName("秒杀案例").apiInfo(apiInfo()).select() + .apis(RequestHandlerSelectors.basePackage("com.itstyle.seckill.web")).paths(PathSelectors.any()).build(); + } + // 预览地址:swagger-ui.html + private ApiInfo apiInfo() { + return new ApiInfoBuilder().title("Spring 中使用Swagger2构建文档").termsOfServiceUrl("https://blog.52itstyle.com") + .contact(new Contact("科帮网 ", "https://blog.52itstyle.com/", "345849402@qq.com")).version("1.1").build(); + } +} diff --git a/src/main/java/com/itstyle/seckill/common/config/IndexController.java b/src/main/java/com/itstyle/seckill/common/config/IndexController.java new file mode 100644 index 0000000..37bc911 --- /dev/null +++ b/src/main/java/com/itstyle/seckill/common/config/IndexController.java @@ -0,0 +1,35 @@ +package com.itstyle.seckill.common.config; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +/** + * 通用访问拦截匹配 + * 创建者 科帮网 + * 创建时间 2018年4月3日 + */ +@Controller +public class IndexController { + + /** + * 页面跳转 + * @param module + * @param url + * @return + */ + @RequestMapping("{url}.shtml") + public String page(@PathVariable("url") String url) { + return url; + } + /** + * 页面跳转(二级目录) + * @param module + * @param function + * @param url + * @return + */ + @RequestMapping("{module}/{url}.shtml") + public String page(@PathVariable("module") String module,@PathVariable("url") String url) { + return module + "/" + url; + } + +} diff --git a/src/main/java/com/itstyle/seckill/common/dynamicquery/DynamicQuery.java b/src/main/java/com/itstyle/seckill/common/dynamicquery/DynamicQuery.java new file mode 100644 index 0000000..e03c3fe --- /dev/null +++ b/src/main/java/com/itstyle/seckill/common/dynamicquery/DynamicQuery.java @@ -0,0 +1,80 @@ +package com.itstyle.seckill.common.dynamicquery; +import java.util.List; +/** + * 扩展SpringDataJpa, 支持动态jpql/nativesql查询并支持分页查询 + * 使用方法:注入ServiceImpl + * 创建者 张志朋 + * 创建时间 2018年3月8日 + */ +public interface DynamicQuery { + + public void save(Object entity); + + public void update(Object entity); + + public void delete(Class entityClass, Object entityid); + + public void delete(Class entityClass, Object[] entityids); + + + /** + * 查询对象列表,返回List + * @param resultClass + * @param nativeSql + * @param params + * @return List + * @Date 2018年3月15日 + * 更新日志 + * 2018年3月15日 张志朋 首次创建 + * + */ + List nativeQueryList(String nativeSql, Object... params); + + /** + * 查询对象列表,返回List> + * @param nativeSql + * @param params + * @return List + * @Date 2018年3月15日 + * 更新日志 + * 2018年3月15日 张志朋 首次创建 + * + */ + List nativeQueryListMap(String nativeSql,Object... params); + + /** + * 查询对象列表,返回List<组合对象> + * @param resultClass + * @param nativeSql + * @param params + * @return List + * @Date 2018年3月15日 + * 更新日志 + * 2018年3月15日 张志朋 首次创建 + * + */ + List nativeQueryListModel(Class resultClass, String nativeSql, Object... params); + + /** + * 执行nativeSql统计查询 + * @param nativeSql + * @param params 占位符参数(例如?1)绑定的参数值 + * @return 统计条数 + */ + Object nativeQueryObject(String nativeSql, Object... params); + /** + * 执行nativeSql统计查询 + * @param nativeSql + * @param params 占位符参数(例如?1)绑定的参数值 + * @return 统计条数 + */ + Object[] nativeQueryArray(String nativeSql, Object... params); + + /** + * 执行nativeSql的update,delete操作 + * @param nativeSql + * @param params + * @return + */ + int nativeExecuteUpdate(String nativeSql, Object... params); +} diff --git a/src/main/java/com/itstyle/seckill/common/dynamicquery/DynamicQueryImpl.java b/src/main/java/com/itstyle/seckill/common/dynamicquery/DynamicQueryImpl.java new file mode 100644 index 0000000..c15a7b8 --- /dev/null +++ b/src/main/java/com/itstyle/seckill/common/dynamicquery/DynamicQueryImpl.java @@ -0,0 +1,101 @@ +package com.itstyle.seckill.common.dynamicquery; +import java.util.List; + +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import javax.persistence.Query; + +import org.hibernate.SQLQuery; +import org.hibernate.transform.Transformers; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Repository; +/** + * 动态jpql/nativesql查询的实现类 + * 创建者 张志朋 + * 创建时间 2018年3月8日 + */ +@Repository +public class DynamicQueryImpl implements DynamicQuery { + + Logger logger = LoggerFactory.getLogger(DynamicQueryImpl.class); + + @PersistenceContext + private EntityManager em; + + public EntityManager getEntityManager() { + return em; + } + + @Override + public void save(Object entity) { + em.persist(entity); + } + + @Override + public void update(Object entity) { + em.merge(entity); + } + + @Override + public void delete(Class entityClass, Object entityid) { + delete(entityClass, new Object[] { entityid }); + } + + @Override + public void delete(Class entityClass, Object[] entityids) { + for (Object id : entityids) { + em.remove(em.getReference(entityClass, id)); + } + } + private Query createNativeQuery(String sql, Object... params) { + Query q = em.createNativeQuery(sql); + if (params != null && params.length > 0) { + for (int i = 0; i < params.length; i++) { + q.setParameter(i + 1, params[i]); // 与Hiberante不同,jpa + // query从位置1开始 + } + } + return q; + } + + @SuppressWarnings("unchecked") + @Override + public List nativeQueryList(String nativeSql, Object... params) { + Query q = createNativeQuery(nativeSql, params); + q.unwrap(SQLQuery.class).setResultTransformer(Transformers.TO_LIST); + return q.getResultList(); + } + + @SuppressWarnings("unchecked") + @Override + public List nativeQueryListModel(Class resultClass, + String nativeSql, Object... params) { + Query q = createNativeQuery(nativeSql, params);; + q.unwrap(SQLQuery.class).setResultTransformer(Transformers.aliasToBean(resultClass)); + return q.getResultList(); + } + + @SuppressWarnings("unchecked") + @Override + public List nativeQueryListMap(String nativeSql, Object... params) { + Query q = createNativeQuery(nativeSql, params); + q.unwrap(SQLQuery.class).setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP); + return q.getResultList(); + } + + @Override + public Object nativeQueryObject(String nativeSql, Object... params) { + return createNativeQuery(nativeSql, params).getSingleResult(); + } + @Override + public int nativeExecuteUpdate(String nativeSql, Object... params) { + return createNativeQuery(nativeSql, params).executeUpdate(); + } + + @Override + public Object[] nativeQueryArray(String nativeSql, Object... params) { + return (Object[]) createNativeQuery(nativeSql, params).getSingleResult(); + } + +} diff --git a/src/main/java/com/itstyle/seckill/common/dynamicquery/NativeQueryResultEntity.java b/src/main/java/com/itstyle/seckill/common/dynamicquery/NativeQueryResultEntity.java new file mode 100644 index 0000000..7e242ce --- /dev/null +++ b/src/main/java/com/itstyle/seckill/common/dynamicquery/NativeQueryResultEntity.java @@ -0,0 +1,11 @@ +package com.itstyle.seckill.common.dynamicquery; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface NativeQueryResultEntity { + +} diff --git a/src/main/java/com/itstyle/seckill/common/entity/Result.java b/src/main/java/com/itstyle/seckill/common/entity/Result.java new file mode 100644 index 0000000..e21b6fe --- /dev/null +++ b/src/main/java/com/itstyle/seckill/common/entity/Result.java @@ -0,0 +1,59 @@ +package com.itstyle.seckill.common.entity; + +import java.util.HashMap; +import java.util.Map; +/** + * 页面响应entity + * 创建者 张志朋 + * 创建时间 2018年3月8日 + */ +public class Result extends HashMap { + + private static final long serialVersionUID = 1L; + + public Result() { + put("code", 0); + } + + public static Result error() { + return error(500, "未知异常,请联系管理员"); + } + + public static Result error(String msg) { + return error(500, msg); + } + + public static Result error(int code, String msg) { + Result r = new Result(); + r.put("code", code); + r.put("msg", msg); + return r; + } + public static Result error(Object msg) { + Result r = new Result(); + r.put("msg", msg); + return r; + } + public static Result ok(Object msg) { + Result r = new Result(); + r.put("msg", msg); + return r; + } + + + public static Result ok(Map map) { + Result r = new Result(); + r.putAll(map); + return r; + } + + public static Result ok() { + return new Result(); + } + + @Override + public Result put(String key, Object value) { + super.put(key, value); + return this; + } +} \ No newline at end of file diff --git a/src/main/java/com/itstyle/seckill/common/entity/Seckill.java b/src/main/java/com/itstyle/seckill/common/entity/Seckill.java new file mode 100644 index 0000000..bbde052 --- /dev/null +++ b/src/main/java/com/itstyle/seckill/common/entity/Seckill.java @@ -0,0 +1,81 @@ +package com.itstyle.seckill.common.entity; + +import java.io.Serializable; +import java.sql.Timestamp; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; +import javax.persistence.Version; +@Entity +@Table(name = "seckill") +public class Seckill implements Serializable { + private static final long serialVersionUID = 1L; + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + @Column(name = "seckill_id", nullable = false) + private long seckillId; + private String name; + private int number; + private Timestamp startTime; + private Timestamp endTime; + private Timestamp createTime; + @Version + private int version; + + public long getSeckillId() { + return seckillId; + } + public void setSeckillId(long seckillId) { + this.seckillId = seckillId; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getNumber() { + return number; + } + + public void setNumber(int number) { + this.number = number; + } + + public Timestamp getStartTime() { + return startTime; + } + + public void setStartTime(Timestamp startTime) { + this.startTime = startTime; + } + + public Timestamp getEndTime() { + return endTime; + } + + public void setEndTime(Timestamp endTime) { + this.endTime = endTime; + } + + public Timestamp getCreateTime() { + return createTime; + } + + public void setCreateTime(Timestamp createTime) { + this.createTime = createTime; + } + public int getVersion() { + return version; + } + public void setVersion(int version) { + this.version = version; + } +} diff --git a/src/main/java/com/itstyle/seckill/common/entity/SuccessKilled.java b/src/main/java/com/itstyle/seckill/common/entity/SuccessKilled.java new file mode 100644 index 0000000..c36f811 --- /dev/null +++ b/src/main/java/com/itstyle/seckill/common/entity/SuccessKilled.java @@ -0,0 +1,54 @@ +package com.itstyle.seckill.common.entity; + +import java.io.Serializable; +import java.sql.Timestamp; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; +@Entity +@Table(name = "success_killed") +public class SuccessKilled implements Serializable{ + private static final long serialVersionUID = 1L; + @Id + @Column(name = "seckill_id", nullable = false) + private long seckillId; + @Id + private long userId; + private short state; + private Timestamp createTime; + + public long getSeckillId() { + return seckillId; + } + + public void setSeckillId(long seckillId) { + this.seckillId = seckillId; + } + + public long getUserId() { + return userId; + } + + public void setUserId(long userId) { + this.userId = userId; + } + + public short getState() { + return state; + } + + public void setState(short state) { + this.state = state; + } + + public Timestamp getCreateTime() { + return createTime; + } + + public void setCreateTime(Timestamp createTime) { + this.createTime = createTime; + } + +} diff --git a/src/main/java/com/itstyle/seckill/common/enums/SeckillStatEnum.java b/src/main/java/com/itstyle/seckill/common/enums/SeckillStatEnum.java new file mode 100644 index 0000000..9325c8e --- /dev/null +++ b/src/main/java/com/itstyle/seckill/common/enums/SeckillStatEnum.java @@ -0,0 +1,40 @@ +package com.itstyle.seckill.common.enums; + +public enum SeckillStatEnum { + + SUCCESS(1,"秒杀成功"), + END(0,"秒杀结束"), + REPEAT_KILL(-1,"重复秒杀"), + INNER_ERROR(-2,"系统异常"), + DATE_REWRITE(-3,"数据篡改"); + + private int state; + private String info; + + SeckillStatEnum(int state, String info) { + this.state = state; + this.info = info; + } + + public int getState() { + return state; + } + + + public String getInfo() { + return info; + } + + + public static SeckillStatEnum stateOf(int index) + { + for (SeckillStatEnum state : values()) + { + if (state.getState()==index) + { + return state; + } + } + return null; + } +} diff --git a/src/main/java/com/itstyle/seckill/common/interceptor/MyAdapter.java b/src/main/java/com/itstyle/seckill/common/interceptor/MyAdapter.java new file mode 100644 index 0000000..20ca087 --- /dev/null +++ b/src/main/java/com/itstyle/seckill/common/interceptor/MyAdapter.java @@ -0,0 +1,19 @@ +package com.itstyle.seckill.common.interceptor; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.Ordered; +import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; +/** + * 配置首页 + * 创建者 小柒2012 + * 创建时间 2017年9月7日 + */ +@Configuration +public class MyAdapter extends WebMvcConfigurerAdapter{ + @Override + public void addViewControllers( ViewControllerRegistry registry ) { + registry.addViewController( "/" ).setViewName( "forward:/login.shtml" ); + registry.setOrder( Ordered.HIGHEST_PRECEDENCE ); + super.addViewControllers( registry ); + } +} diff --git a/src/main/java/com/itstyle/seckill/common/redis/RedisConfig.java b/src/main/java/com/itstyle/seckill/common/redis/RedisConfig.java new file mode 100644 index 0000000..ef426e9 --- /dev/null +++ b/src/main/java/com/itstyle/seckill/common/redis/RedisConfig.java @@ -0,0 +1,90 @@ +package com.itstyle.seckill.common.redis; + +import java.lang.reflect.Method; + +import org.springframework.cache.CacheManager; +import org.springframework.cache.annotation.CachingConfigurerSupport; +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.cache.interceptor.KeyGenerator; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.cache.RedisCacheManager; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.PropertyAccessor; +import com.fasterxml.jackson.databind.ObjectMapper; + +@Configuration +@EnableCaching +public class RedisConfig extends CachingConfigurerSupport { + /** + * 自定义key(消息队列 暂时用不到 自行忽略) + * 此方法将会根据类名+方法名+所有参数的值生成唯一的一个key,即使@Cacheable中的value属性一样,key也会不一样。 + * @Author 科帮网 + * @return + * @Date 2017年8月13日 + * 更新日志 + * 2017年8月13日 科帮网 首次创建 + * + */ + @Bean + public KeyGenerator keyGenerator() { + return new KeyGenerator() { + @Override + public Object generate(Object target, Method method, + Object... params) { + StringBuilder sb = new StringBuilder(); + sb.append(target.getClass().getName()); + sb.append(method.getName()); + for (Object obj : params) { + sb.append(obj.toString()); + } + return sb.toString(); + } + }; + } + /** + * 缓存管理器 + * @Author 科帮网 + * @param redisTemplate + * @return CacheManager + * @Date 2017年8月13日 + * 更新日志 + * 2017年8月13日 科帮网 首次创建 + */ + @SuppressWarnings("rawtypes") + @Bean + public CacheManager cacheManager(RedisTemplate redisTemplate) { + return new RedisCacheManager(redisTemplate); + } + /** + * 序列化Java对象 + * @Author 科帮网 + * @param factory + * @return RedisTemplate + * @Date 2017年8月13日 + * 更新日志 + * 2017年8月13日 科帮网 首次创建 + * + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Bean + public RedisTemplate redisTemplate(RedisConnectionFactory connectionFactory) { + RedisTemplate template = new RedisTemplate(); + template.setConnectionFactory(connectionFactory); + //使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值(默认使用JDK的序列化方式) + Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer(Object.class); + ObjectMapper mapper = new ObjectMapper(); + mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); + mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); + serializer.setObjectMapper(mapper); + template.setValueSerializer(serializer); + template.setKeySerializer(new StringRedisSerializer()); + template.afterPropertiesSet(); + return template; + } +} diff --git a/src/main/java/com/itstyle/seckill/common/redis/RedisUtil.java b/src/main/java/com/itstyle/seckill/common/redis/RedisUtil.java new file mode 100644 index 0000000..3703640 --- /dev/null +++ b/src/main/java/com/itstyle/seckill/common/redis/RedisUtil.java @@ -0,0 +1,130 @@ +package com.itstyle.seckill.common.redis; + +import java.io.Serializable; +import java.util.concurrent.TimeUnit; + +import javax.annotation.Resource; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.ValueOperations; +import org.springframework.stereotype.Component; +/** + * 缓存工具类 + * 创建者 科帮网 + * 创建时间 2018年4月8日 + */ +@Component +public class RedisUtil { + + private static final Logger logger = LoggerFactory.getLogger(RedisUtil.class); + + @Resource + private RedisTemplate redisTemplate; + + /** + * 前缀 + */ + public static final String KEY_PREFIX_VALUE = "itstyle:seckill:value:"; + + + /** + * 缓存value操作 + * @param k + * @param v + * @param time + * @return + */ + public boolean cacheValue(String k, Serializable v, long time) { + String key = KEY_PREFIX_VALUE + k; + try { + ValueOperations valueOps = redisTemplate.opsForValue(); + valueOps.set(key, v); + if (time > 0) redisTemplate.expire(key, time, TimeUnit.SECONDS); + return true; + } catch (Throwable t) { + logger.error("缓存[{}]失败, value[{}]",key,v,t); + } + return false; + } + /** + * 缓存value操作 + * @Author 科帮网 + * @param k + * @param v + * @param time + * @param unit + * @return boolean + * @Date 2017年12月23日 + * 更新日志 + * 2017年12月23日 科帮网 首次创建 + * + */ + public boolean cacheValue(String k, Serializable v, long time,TimeUnit unit) { + String key = KEY_PREFIX_VALUE + k; + try { + ValueOperations valueOps = redisTemplate.opsForValue(); + valueOps.set(key, v); + if (time > 0) redisTemplate.expire(key, time, unit); + return true; + } catch (Throwable t) { + logger.error("缓存[{}]失败, value[{}]",key,v,t); + } + return false; + } + + /** + * 缓存value操作 + * @param k + * @param v + * @return + */ + public boolean cacheValue(String k, Serializable v) { + return cacheValue(k, v, -1); + } + + /** + * 判断缓存是否存在 + * @param k + * @return + */ + public boolean containsValueKey(String k) { + String key = KEY_PREFIX_VALUE + k; + try { + return redisTemplate.hasKey(key); + } catch (Throwable t) { + logger.error("判断缓存存在失败key[" + key + ", error[" + t + "]"); + } + return false; + } + /** + * 获取缓存 + * @param k + * @return + */ + public Serializable getValue(String k) { + try { + ValueOperations valueOps = redisTemplate.opsForValue(); + return valueOps.get(KEY_PREFIX_VALUE + k); + } catch (Throwable t) { + logger.error("获取缓存失败key[" + KEY_PREFIX_VALUE + k + ", error[" + t + "]"); + } + return null; + } + /** + * 移除缓存 + * @param k + * @return + */ + public boolean removeValue(String k) { + String key = KEY_PREFIX_VALUE + k; + try { + redisTemplate.delete(key); + return true; + } catch (Throwable t) { + logger.error("获取缓存失败key[" + key + ", error[" + t + "]"); + } + return false; + } +} diff --git a/src/main/java/com/itstyle/seckill/distributedlock/redis/RedissLockDemo.java b/src/main/java/com/itstyle/seckill/distributedlock/redis/RedissLockDemo.java new file mode 100644 index 0000000..dcd7099 --- /dev/null +++ b/src/main/java/com/itstyle/seckill/distributedlock/redis/RedissLockDemo.java @@ -0,0 +1,143 @@ +package com.itstyle.seckill.distributedlock.redis; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + +import org.redisson.RedissonMultiLock; +import org.redisson.RedissonRedLock; +import org.redisson.api.RLock; +import org.redisson.api.RedissonClient; + +/** + * redis分布式锁Demo + * @author 科帮网 By https://blog.52itstyle.com + */ +public class RedissLockDemo { + /** + * 可重入锁(Reentrant Lock) + * Redisson的分布式可重入锁RLock Java对象实现了java.util.concurrent.locks.Lock接口,同时还支持自动过期解锁 + * @param redisson + */ + public void testReentrantLock(RedissonClient redisson) { + RLock lock = redisson.getLock("anyLock"); + try { + // 1. 最常见的使用方法 + // lock.lock(); + // 2. 支持过期解锁功能,10秒钟以后自动解锁, 无需调用unlock方法手动解锁 + // lock.lock(10, TimeUnit.SECONDS); + // 3. 尝试加锁,最多等待3秒,上锁以后10秒自动解锁 + boolean res = lock.tryLock(3, 10, TimeUnit.SECONDS); + if (res) { // 成功 + // do your business + } + } catch (InterruptedException e) { + e.printStackTrace(); + } finally { + lock.unlock(); + } + } + /** + * Redisson同时还为分布式锁提供了异步执行的相关方法 + * @param redisson + */ + public void testAsyncReentrantLock(RedissonClient redisson) { + RLock lock = redisson.getLock("anyLock"); + try { + lock.lockAsync(); + lock.lockAsync(10, TimeUnit.SECONDS); + Future res = lock.tryLockAsync(3, 10, TimeUnit.SECONDS); + if (res.get()) { + // do your business + } + } catch (InterruptedException e) { + e.printStackTrace(); + } catch (ExecutionException e) { + e.printStackTrace(); + } finally { + lock.unlock(); + } + } + /** + * 公平锁(Fair Lock) + * Redisson分布式可重入公平锁也是实现了java.util.concurrent.locks.Lock接口的一种RLock对象。 + * 在提供了自动过期解锁功能的同时,保证了当多个Redisson客户端线程同时请求加锁时,优先分配给先发出请求的线程。 + * @param redisson + */ + public void testFairLock(RedissonClient redisson){ + RLock fairLock = redisson.getFairLock("anyLock"); + try{ + // 最常见的使用方法 + fairLock.lock(); + // 支持过期解锁功能, 10秒钟以后自动解锁,无需调用unlock方法手动解锁 + fairLock.lock(10, TimeUnit.SECONDS); + // 尝试加锁,最多等待100秒,上锁以后10秒自动解锁 + boolean res = fairLock.tryLock(100, 10, TimeUnit.SECONDS); + if (res) { + // do your business + } + } catch (InterruptedException e) { + e.printStackTrace(); + } finally { + fairLock.unlock(); + } +// Redisson同时还为分布式可重入公平锁提供了异步执行的相关方法: +// RLock fairLock = redisson.getFairLock("anyLock"); +// fairLock.lockAsync(); +// fairLock.lockAsync(10, TimeUnit.SECONDS); +// Future res = fairLock.tryLockAsync(100, 10, TimeUnit.SECONDS); + } + /** + * 联锁(MultiLock) + * Redisson的RedissonMultiLock对象可以将多个RLock对象关联为一个联锁,每个RLock对象实例可以来自于不同的Redisson实例 + * @param redisson1 + * @param redisson2 + * @param redisson3 + */ + public void testMultiLock(RedissonClient redisson1,RedissonClient redisson2, RedissonClient redisson3){ + RLock lock1 = redisson1.getLock("lock1"); + RLock lock2 = redisson2.getLock("lock2"); + RLock lock3 = redisson3.getLock("lock3"); + RedissonMultiLock lock = new RedissonMultiLock(lock1, lock2, lock3); + try { + // 同时加锁:lock1 lock2 lock3, 所有的锁都上锁成功才算成功。 + lock.lock(); + // 尝试加锁,最多等待100秒,上锁以后10秒自动解锁 + boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS); + if (res) { + // do your business + } + } catch (InterruptedException e) { + e.printStackTrace(); + } finally { + lock.unlock(); + } + } + /** + * 红锁(RedLock) + * Redisson的RedissonRedLock对象实现了Redlock介绍的加锁算法。该对象也可以用来将多个RLock对象关联为一个红锁,每个RLock对象实例可以来自于不同的Redisson实例 + * @param redisson1 + * @param redisson2 + * @param redisson3 + */ + public void testRedLock(RedissonClient redisson1,RedissonClient redisson2, RedissonClient redisson3){ + RLock lock1 = redisson1.getLock("lock1"); + RLock lock2 = redisson2.getLock("lock2"); + RLock lock3 = redisson3.getLock("lock3"); + RedissonRedLock lock = new RedissonRedLock(lock1, lock2, lock3); + try { + // 同时加锁:lock1 lock2 lock3, 红锁在大部分节点上加锁成功就算成功。 + lock.lock(); + // 尝试加锁,最多等待100秒,上锁以后10秒自动解锁 + boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS); + if (res) { + // do your business + } + } catch (InterruptedException e) { + e.printStackTrace(); + } finally { + lock.unlock(); + } + } + //读写锁(ReadWriteLock)、信号量(Semaphore)、可过期性信号量(PermitExpirableSemaphore)、闭锁(CountDownLatch) +} \ No newline at end of file diff --git a/src/main/java/com/itstyle/seckill/distributedlock/redis/RedissLockUtil.java b/src/main/java/com/itstyle/seckill/distributedlock/redis/RedissLockUtil.java new file mode 100644 index 0000000..74d0e31 --- /dev/null +++ b/src/main/java/com/itstyle/seckill/distributedlock/redis/RedissLockUtil.java @@ -0,0 +1,101 @@ +package com.itstyle.seckill.distributedlock.redis; +import java.util.concurrent.TimeUnit; + +import org.redisson.api.RLock; +import org.redisson.api.RedissonClient; + +/** + * redis分布式锁帮助类 + * @author 科帮网 By https://blog.52itstyle.com + */ +public class RedissLockUtil { + private static RedissonClient redissonClient; + + public void setRedissonClient(RedissonClient locker) { + redissonClient = locker; + } + + /** + * 加锁 + * @param lockKey + * @return + */ + public static RLock lock(String lockKey) { + RLock lock = redissonClient.getLock(lockKey); + lock.lock(); + return lock; + } + + /** + * 释放锁 + * @param lockKey + */ + public static void unlock(String lockKey) { + RLock lock = redissonClient.getLock(lockKey); + lock.unlock(); + } + + /** + * 释放锁 + * @param lock + */ + public static void unlock(RLock lock) { + lock.unlock(); + } + + /** + * 带超时的锁 + * @param lockKey + * @param timeout 超时时间 单位:秒 + */ + public static RLock lock(String lockKey, int timeout) { + RLock lock = redissonClient.getLock(lockKey); + lock.lock(timeout, TimeUnit.SECONDS); + return lock; + } + + /** + * 带超时的锁 + * @param lockKey + * @param unit 时间单位 + * @param timeout 超时时间 + */ + public static RLock lock(String lockKey, TimeUnit unit ,int timeout) { + RLock lock = redissonClient.getLock(lockKey); + lock.lock(timeout, unit); + return lock; + } + + /** + * 尝试获取锁 + * @param lockKey + * @param waitTime 最多等待时间 + * @param leaseTime 上锁后自动释放锁时间 + * @return + */ + public static boolean tryLock(String lockKey, int waitTime, int leaseTime) { + RLock lock = redissonClient.getLock(lockKey); + try { + return lock.tryLock(waitTime, leaseTime, TimeUnit.SECONDS); + } catch (InterruptedException e) { + return false; + } + } + + /** + * 尝试获取锁 + * @param lockKey + * @param unit 时间单位 + * @param waitTime 最多等待时间 + * @param leaseTime 上锁后自动释放锁时间 + * @return + */ + public static boolean tryLock(String lockKey, TimeUnit unit, int waitTime, int leaseTime) { + RLock lock = redissonClient.getLock(lockKey); + try { + return lock.tryLock(waitTime, leaseTime, unit); + } catch (InterruptedException e) { + return false; + } + } +} \ No newline at end of file diff --git a/src/main/java/com/itstyle/seckill/distributedlock/redis/RedissonAutoConfiguration.java b/src/main/java/com/itstyle/seckill/distributedlock/redis/RedissonAutoConfiguration.java new file mode 100644 index 0000000..691752d --- /dev/null +++ b/src/main/java/com/itstyle/seckill/distributedlock/redis/RedissonAutoConfiguration.java @@ -0,0 +1,75 @@ +package com.itstyle.seckill.distributedlock.redis; + +import org.apache.commons.lang3.StringUtils; +import org.redisson.Redisson; +import org.redisson.api.RedissonClient; +import org.redisson.config.Config; +//import org.redisson.config.SentinelServersConfig; +import org.redisson.config.SingleServerConfig; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ConditionalOnClass(Config.class) +@EnableConfigurationProperties(RedissonProperties.class) +public class RedissonAutoConfiguration { + + @Autowired + private RedissonProperties redssionProperties; + + /** + * 哨兵模式自动装配 + * @return + */ +// @Bean +// @ConditionalOnProperty(name="redisson.master-name") +// RedissonClient redissonSentinel() { +// Config config = new Config(); +// SentinelServersConfig serverConfig = config.useSentinelServers().addSentinelAddress(redssionProperties.getSentinelAddresses()) +// .setMasterName(redssionProperties.getMasterName()) +// .setTimeout(redssionProperties.getTimeout()) +// .setMasterConnectionPoolSize(redssionProperties.getMasterConnectionPoolSize()) +// .setSlaveConnectionPoolSize(redssionProperties.getSlaveConnectionPoolSize()); +// +// if(StringUtils.isNotBlank(redssionProperties.getPassword())) { +// serverConfig.setPassword(redssionProperties.getPassword()); +// } +// return Redisson.create(config); +// } + + /** + * 单机模式自动装配 + * @return + */ + @Bean + @ConditionalOnProperty(name="redisson.address") + RedissonClient redissonSingle() { + Config config = new Config(); + SingleServerConfig serverConfig = config.useSingleServer() + .setAddress(redssionProperties.getAddress()) + .setTimeout(redssionProperties.getTimeout()) + .setConnectionPoolSize(redssionProperties.getConnectionPoolSize()) + .setConnectionMinimumIdleSize(redssionProperties.getConnectionMinimumIdleSize()); + if(StringUtils.isNotBlank(redssionProperties.getPassword())) { + serverConfig.setPassword(redssionProperties.getPassword()); + } + + return Redisson.create(config); + } + + /** + * 装配locker类,并将实例注入到RedissLockUtil中 + * @return + */ + @Bean + RedissLockUtil redissLockUtil(RedissonClient redissonClient) { + RedissLockUtil redissLockUtil = new RedissLockUtil(); + redissLockUtil.setRedissonClient(redissonClient); + return redissLockUtil; + } + +} diff --git a/src/main/java/com/itstyle/seckill/distributedlock/redis/RedissonProperties.java b/src/main/java/com/itstyle/seckill/distributedlock/redis/RedissonProperties.java new file mode 100644 index 0000000..d901bfe --- /dev/null +++ b/src/main/java/com/itstyle/seckill/distributedlock/redis/RedissonProperties.java @@ -0,0 +1,97 @@ +package com.itstyle.seckill.distributedlock.redis; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "redisson") +public class RedissonProperties { + + private int timeout = 3000; + + private String address; + + private String password; + + private int connectionPoolSize = 64; + + private int connectionMinimumIdleSize=10; + + private int slaveConnectionPoolSize = 250; + + private int masterConnectionPoolSize = 250; + + private String[] sentinelAddresses; + + private String masterName; + + public int getTimeout() { + return timeout; + } + + public void setTimeout(int timeout) { + this.timeout = timeout; + } + + public int getSlaveConnectionPoolSize() { + return slaveConnectionPoolSize; + } + + public void setSlaveConnectionPoolSize(int slaveConnectionPoolSize) { + this.slaveConnectionPoolSize = slaveConnectionPoolSize; + } + + public int getMasterConnectionPoolSize() { + return masterConnectionPoolSize; + } + + public void setMasterConnectionPoolSize(int masterConnectionPoolSize) { + this.masterConnectionPoolSize = masterConnectionPoolSize; + } + + public String[] getSentinelAddresses() { + return sentinelAddresses; + } + + public void setSentinelAddresses(String sentinelAddresses) { + this.sentinelAddresses = sentinelAddresses.split(","); + } + + public String getMasterName() { + return masterName; + } + + public void setMasterName(String masterName) { + this.masterName = masterName; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + + public int getConnectionPoolSize() { + return connectionPoolSize; + } + + public void setConnectionPoolSize(int connectionPoolSize) { + this.connectionPoolSize = connectionPoolSize; + } + + public int getConnectionMinimumIdleSize() { + return connectionMinimumIdleSize; + } + + public void setConnectionMinimumIdleSize(int connectionMinimumIdleSize) { + this.connectionMinimumIdleSize = connectionMinimumIdleSize; + } +} diff --git a/src/main/java/com/itstyle/seckill/distributedlock/zookeeper/ZkLockUtil.java b/src/main/java/com/itstyle/seckill/distributedlock/zookeeper/ZkLockUtil.java new file mode 100644 index 0000000..07a838d --- /dev/null +++ b/src/main/java/com/itstyle/seckill/distributedlock/zookeeper/ZkLockUtil.java @@ -0,0 +1,59 @@ +package com.itstyle.seckill.distributedlock.zookeeper; + +import org.apache.curator.RetryPolicy; +import org.apache.curator.framework.CuratorFramework; +import org.apache.curator.framework.CuratorFrameworkFactory; +import org.apache.curator.framework.recipes.locks.InterProcessMutex; +import org.apache.curator.retry.ExponentialBackoffRetry; +import org.springframework.beans.factory.annotation.Value; + +/** + * zookeeper 分布式锁 + * @author 科帮网 By https://blog.52itstyle.com + */ +public class ZkLockUtil{ + + @Value("${zookeeper.address}") + private static String address; + + public static CuratorFramework client; + + static{ + RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3); + client = CuratorFrameworkFactory.newClient(address, retryPolicy); + client.start(); + } + /** + * 私有的默认构造子,保证外界无法直接实例化 + */ + private ZkLockUtil(){}; + /** + * 类级的内部类,也就是静态的成员式内部类,该内部类的实例与外部类的实例 + * 没有绑定关系,而且只有被调用到才会装载,从而实现了延迟加载 + */ + private static class SingletonHolder{ + /** + * 静态初始化器,由JVM来保证线程安全 + */ + private static InterProcessMutex mutex = new InterProcessMutex(client, "/curator/lock"); + } + public static InterProcessMutex getMutex(){ + return SingletonHolder.mutex; + } + //获得了锁 + public static void acquire(){ + try { + getMutex().acquire(); + } catch (Exception e) { + e.printStackTrace(); + } + } + //释放锁 + public static void release(){ + try { + getMutex().release(); + } catch (Exception e) { + e.printStackTrace(); + } + } +} diff --git a/src/main/java/com/itstyle/seckill/queue/jvm/SeckillQueue.java b/src/main/java/com/itstyle/seckill/queue/jvm/SeckillQueue.java new file mode 100644 index 0000000..b66a724 --- /dev/null +++ b/src/main/java/com/itstyle/seckill/queue/jvm/SeckillQueue.java @@ -0,0 +1,55 @@ +package com.itstyle.seckill.queue.jvm; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; + +import com.itstyle.seckill.common.entity.SuccessKilled; +/** + * 秒杀队列(固定长度为100) + * @author 科帮网 By https://blog.52itstyle.com + * 创建时间 2018年5月10日 + */ +public class SeckillQueue { + //队列大小 + static final int QUEUE_MAX_SIZE = 100; + + static BlockingQueue blockingQueue = new LinkedBlockingQueue(QUEUE_MAX_SIZE); + + /** + * 私有的默认构造子,保证外界无法直接实例化 + */ + private SeckillQueue(){}; + /** + * 类级的内部类,也就是静态的成员式内部类,该内部类的实例与外部类的实例 + * 没有绑定关系,而且只有被调用到才会装载,从而实现了延迟加载 + */ + private static class SingletonHolder{ + /** + * 静态初始化器,由JVM来保证线程安全 + */ + private static SeckillQueue queue = new SeckillQueue(); + } + //单例队列 + public static SeckillQueue getMailQueue(){ + return SingletonHolder.queue; + } + /** + * 生产入队 + * @param kill + * @throws InterruptedException + * add(e) 队列未满时,返回true;队列满则抛出IllegalStateException(“Queue full”)异常——AbstractQueue + * put(e) 队列未满时,直接插入没有返回值;队列满时会阻塞等待,一直等到队列未满时再插入。 + * offer(e) 队列未满时,返回true;队列满时返回false。非阻塞立即返回。 + * offer(e, time, unit) 设定等待的时间,如果在指定时间内还不能往队列中插入数据则返回false,插入成功返回true。 + */ + public Boolean produce(SuccessKilled kill) throws InterruptedException { + return blockingQueue.offer(kill); + } + //消费出队 + public SuccessKilled consume() throws InterruptedException { + return blockingQueue.take(); + } + // 获取队列大小 + public int size() { + return blockingQueue.size(); + } +} diff --git a/src/main/java/com/itstyle/seckill/queue/jvm/TaskRunner.java b/src/main/java/com/itstyle/seckill/queue/jvm/TaskRunner.java new file mode 100644 index 0000000..36e112f --- /dev/null +++ b/src/main/java/com/itstyle/seckill/queue/jvm/TaskRunner.java @@ -0,0 +1,31 @@ +package com.itstyle.seckill.queue.jvm; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.ApplicationArguments; +import org.springframework.boot.ApplicationRunner; +import org.springframework.stereotype.Component; + +import com.itstyle.seckill.common.entity.SuccessKilled; +import com.itstyle.seckill.service.ISeckillService; +/** + * 消费秒杀队列 + * 创建者 科帮网 + * 创建时间 2018年4月3日 + */ +@Component +public class TaskRunner implements ApplicationRunner{ + + @Autowired + private ISeckillService seckillService; + + @Override + public void run(ApplicationArguments var) throws Exception{ + while(true){ + //进程内队列 + SuccessKilled kill = SeckillQueue.getMailQueue().consume(); + if(kill!=null){ + seckillService.startSeckil(kill.getSeckillId(), kill.getUserId()); + } + } + } +} \ No newline at end of file diff --git a/src/main/java/com/itstyle/seckill/queue/kafka/KafkaConsumer.java b/src/main/java/com/itstyle/seckill/queue/kafka/KafkaConsumer.java new file mode 100644 index 0000000..e972f11 --- /dev/null +++ b/src/main/java/com/itstyle/seckill/queue/kafka/KafkaConsumer.java @@ -0,0 +1,26 @@ +package com.itstyle.seckill.queue.kafka; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.kafka.annotation.KafkaListener; +import org.springframework.stereotype.Component; + +import com.itstyle.seckill.service.ISeckillService; +/** + * 消费者 spring-kafka 2.0 + 依赖JDK8 + * @author 科帮网 By https://blog.52itstyle.com + */ +@Component +public class KafkaConsumer { + @Autowired + private ISeckillService seckillService; + /** + * 监听seckill主题,有消息就读取 + * @param message + */ + @KafkaListener(topics = {"seckill"}) + public void receiveMessage(String message){ + //收到通道的消息之后执行秒杀操作 + String[] array = message.split(";"); + seckillService.startSeckil(Long.parseLong(array[0]), Long.parseLong(array[1])); + } +} \ No newline at end of file diff --git a/src/main/java/com/itstyle/seckill/queue/kafka/KafkaSender.java b/src/main/java/com/itstyle/seckill/queue/kafka/KafkaSender.java new file mode 100644 index 0000000..942adb0 --- /dev/null +++ b/src/main/java/com/itstyle/seckill/queue/kafka/KafkaSender.java @@ -0,0 +1,21 @@ +package com.itstyle.seckill.queue.kafka; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.kafka.core.KafkaTemplate; +import org.springframework.stereotype.Component; +/** + * 生产者 + * @author 科帮网 By https://blog.52itstyle.com + */ +@Component +public class KafkaSender { + @Autowired + private KafkaTemplate kafkaTemplate; + + /** + * 发送消息到kafka + */ + public void sendChannelMess(String channel, String message){ + kafkaTemplate.send(channel,message); + } +} diff --git a/src/main/java/com/itstyle/seckill/queue/redis/RedisConsumer.java b/src/main/java/com/itstyle/seckill/queue/redis/RedisConsumer.java new file mode 100644 index 0000000..308be46 --- /dev/null +++ b/src/main/java/com/itstyle/seckill/queue/redis/RedisConsumer.java @@ -0,0 +1,22 @@ +package com.itstyle.seckill.queue.redis; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.itstyle.seckill.service.ISeckillService; +/** + * 消费者 + * @author 科帮网 By https://blog.52itstyle.com + */ +@Service +public class RedisConsumer { + + @Autowired + private ISeckillService seckillService; + + public void receiveMessage(String message) { + //收到通道的消息之后执行秒杀操作(超卖) + String[] array = message.split(";"); + seckillService.startSeckil(Long.parseLong(array[0]), Long.parseLong(array[1])); + } +} \ No newline at end of file diff --git a/src/main/java/com/itstyle/seckill/queue/redis/RedisSender.java b/src/main/java/com/itstyle/seckill/queue/redis/RedisSender.java new file mode 100644 index 0000000..fa54a4e --- /dev/null +++ b/src/main/java/com/itstyle/seckill/queue/redis/RedisSender.java @@ -0,0 +1,18 @@ +package com.itstyle.seckill.queue.redis; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.stereotype.Service; +/** + * 生产者 + * @author 科帮网 By https://blog.52itstyle.com + */ +@Service +public class RedisSender { + @Autowired + private StringRedisTemplate stringRedisTemplate; + //向通道发送消息的方法 + public void sendChannelMess(String channel, String message) { + stringRedisTemplate.convertAndSend(channel, message); + } +} diff --git a/src/main/java/com/itstyle/seckill/queue/redis/RedisSubListenerConfig.java b/src/main/java/com/itstyle/seckill/queue/redis/RedisSubListenerConfig.java new file mode 100644 index 0000000..d0a90b2 --- /dev/null +++ b/src/main/java/com/itstyle/seckill/queue/redis/RedisSubListenerConfig.java @@ -0,0 +1,32 @@ +package com.itstyle.seckill.queue.redis; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.data.redis.listener.PatternTopic; +import org.springframework.data.redis.listener.RedisMessageListenerContainer; +import org.springframework.data.redis.listener.adapter.MessageListenerAdapter; + +@Configuration +public class RedisSubListenerConfig { + //初始化监听器 + @Bean + RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory, + MessageListenerAdapter listenerAdapter) { + RedisMessageListenerContainer container = new RedisMessageListenerContainer(); + container.setConnectionFactory(connectionFactory); + container.addMessageListener(listenerAdapter, new PatternTopic("seckill")); + return container; + } + //利用反射来创建监听到消息之后的执行方法 + @Bean + MessageListenerAdapter listenerAdapter(RedisConsumer redisReceiver) { + return new MessageListenerAdapter(redisReceiver, "receiveMessage"); + } + //使用默认的工厂初始化redis操作模板 + @Bean + StringRedisTemplate template(RedisConnectionFactory connectionFactory) { + return new StringRedisTemplate(connectionFactory); + } +} \ No newline at end of file diff --git a/src/main/java/com/itstyle/seckill/repository/SeckillRepository.java b/src/main/java/com/itstyle/seckill/repository/SeckillRepository.java new file mode 100644 index 0000000..9e2f89b --- /dev/null +++ b/src/main/java/com/itstyle/seckill/repository/SeckillRepository.java @@ -0,0 +1,9 @@ +package com.itstyle.seckill.repository; +import org.springframework.data.jpa.repository.JpaRepository; + +import com.itstyle.seckill.common.entity.Seckill; + +public interface SeckillRepository extends JpaRepository { + + +} diff --git a/src/main/java/com/itstyle/seckill/service/ISeckillDistributedService.java b/src/main/java/com/itstyle/seckill/service/ISeckillDistributedService.java new file mode 100644 index 0000000..d566be6 --- /dev/null +++ b/src/main/java/com/itstyle/seckill/service/ISeckillDistributedService.java @@ -0,0 +1,31 @@ +package com.itstyle.seckill.service; + +import com.itstyle.seckill.common.entity.Result; + +public interface ISeckillDistributedService { + + /** + * 秒杀 一 单个商品 + * @param seckillId 秒杀商品ID + * @param userId 用户ID + * @return + */ + Result startSeckilRedisLock(long seckillId,long userId); + /** + * 秒杀 一 单个商品 + * @param seckillId 秒杀商品ID + * @param userId 用户ID + * @return + */ + Result startSeckilZksLock(long seckillId,long userId); + + /** + * 秒杀 二 多个商品 + * @param seckillId 秒杀商品ID + * @param userId 用户ID + * @param number 秒杀商品数量 + * @return + */ + Result startSeckilLock(long seckillId,long userId,long number); + +} diff --git a/src/main/java/com/itstyle/seckill/service/ISeckillService.java b/src/main/java/com/itstyle/seckill/service/ISeckillService.java new file mode 100644 index 0000000..3a8e5bd --- /dev/null +++ b/src/main/java/com/itstyle/seckill/service/ISeckillService.java @@ -0,0 +1,73 @@ +package com.itstyle.seckill.service; + +import java.util.List; + +import com.itstyle.seckill.common.entity.Result; +import com.itstyle.seckill.common.entity.Seckill; + +public interface ISeckillService { + + /** + * 查询全部的秒杀记录 + * @return + */ + List getSeckillList(); + + /** + * 查询单个秒杀记录 + * @param seckillId + * @return + */ + Seckill getById(long seckillId); + /** + * 查询秒杀售卖商品 + * @param seckillId + * @return + */ + Long getSeckillCount(long seckillId); + /** + * 删除秒杀售卖商品记录 + * @param seckillId + * @return + */ + void deleteSeckill(long seckillId); + + /** + * 秒杀 一、会出现数量错误 + * @param seckillId + * @param userId + * @return + */ + Result startSeckil(long seckillId,long userId); + + /** + * 秒杀 二、程序锁 + * @param seckillId + * @param userId + * @return + */ + Result startSeckilLock(long seckillId,long userId); + + /** + * 秒杀 二、数据库悲观锁 + * @param seckillId + * @param userId + * @return + */ + Result startSeckilDBPCC_ONE(long seckillId,long userId); + /** + * 秒杀 三、数据库悲观锁 + * @param seckillId + * @param userId + * @return + */ + Result startSeckilDBPCC_TWO(long seckillId,long userId); + /** + * 秒杀 三、数据库悲观锁 + * @param seckillId + * @param userId + * @return + */ + Result startSeckilDBOCC(long seckillId,long userId,long number); + +} diff --git a/src/main/java/com/itstyle/seckill/service/impl/SeckillDistributedServiceImpl.java b/src/main/java/com/itstyle/seckill/service/impl/SeckillDistributedServiceImpl.java new file mode 100644 index 0000000..20b2ddf --- /dev/null +++ b/src/main/java/com/itstyle/seckill/service/impl/SeckillDistributedServiceImpl.java @@ -0,0 +1,114 @@ +package com.itstyle.seckill.service.impl; + +import java.sql.Timestamp; +import java.util.Date; +import java.util.concurrent.TimeUnit; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.itstyle.seckill.common.dynamicquery.DynamicQuery; +import com.itstyle.seckill.common.entity.Result; +import com.itstyle.seckill.common.entity.SuccessKilled; +import com.itstyle.seckill.common.enums.SeckillStatEnum; +import com.itstyle.seckill.distributedlock.redis.RedissLockUtil; +import com.itstyle.seckill.distributedlock.zookeeper.ZkLockUtil; +import com.itstyle.seckill.service.ISeckillDistributedService; +@Service +public class SeckillDistributedServiceImpl implements ISeckillDistributedService { + + @Autowired + private DynamicQuery dynamicQuery; + + @Override + @Transactional + public Result startSeckilRedisLock(long seckillId,long userId) { + boolean res=false; + try { + //尝试获取锁,最多等待10秒,上锁以后10秒自动解锁(实际项目中推荐这种,以防出现死锁) + res = RedissLockUtil.tryLock(seckillId+"", TimeUnit.SECONDS, 20, 10); + String nativeSql = "SELECT number FROM seckill WHERE seckill_id=?"; + Object object = dynamicQuery.nativeQueryObject(nativeSql, new Object[]{seckillId}); + Long number = ((Number) object).longValue(); + if(number>0){ + SuccessKilled killed = new SuccessKilled(); + killed.setSeckillId(seckillId); + killed.setUserId(userId); + killed.setState((short)0); + killed.setCreateTime(new Timestamp(new Date().getTime())); + dynamicQuery.save(killed); + nativeSql = "UPDATE seckill SET number=number-1 WHERE seckill_id=? AND number>0"; + dynamicQuery.nativeExecuteUpdate(nativeSql, new Object[]{seckillId}); + }else{ + return Result.error(SeckillStatEnum.END); + } + } catch (Exception e) { + e.printStackTrace(); + } finally{ + if(res){//释放锁 + RedissLockUtil.unlock(seckillId+""); + } + } + return Result.ok(SeckillStatEnum.SUCCESS); + } + @Override + @Transactional + public Result startSeckilZksLock(long seckillId, long userId) { + try { + ZkLockUtil.acquire(); + String nativeSql = "SELECT number FROM seckill WHERE seckill_id=?"; + Object object = dynamicQuery.nativeQueryObject(nativeSql, new Object[]{seckillId}); + Long number = ((Number) object).longValue(); + if(number>0){ + SuccessKilled killed = new SuccessKilled(); + killed.setSeckillId(seckillId); + killed.setUserId(userId); + killed.setState((short)0); + killed.setCreateTime(new Timestamp(new Date().getTime())); + dynamicQuery.save(killed); + nativeSql = "UPDATE seckill SET number=number-1 WHERE seckill_id=? AND number>0"; + dynamicQuery.nativeExecuteUpdate(nativeSql, new Object[]{seckillId}); + }else{ + return Result.error(SeckillStatEnum.END); + } + } catch (Exception e) { + e.printStackTrace(); + } finally{ + ZkLockUtil.release(); + } + return Result.ok(SeckillStatEnum.SUCCESS); + } + + @Override + public Result startSeckilLock(long seckillId, long userId, long number) { + boolean res=false; + try { + //尝试获取锁,最多等待3秒,上锁以后10秒自动解锁(实际项目中推荐这种,以防出现死锁) + res = RedissLockUtil.tryLock(seckillId+"", TimeUnit.SECONDS, 3, 10); + String nativeSql = "SELECT number FROM seckill WHERE seckill_id=?"; + Object object = dynamicQuery.nativeQueryObject(nativeSql, new Object[]{seckillId}); + Long count = ((Number) object).longValue(); + if(count>=number){ + SuccessKilled killed = new SuccessKilled(); + killed.setSeckillId(seckillId); + killed.setUserId(userId); + killed.setState((short)0); + killed.setCreateTime(new Timestamp(new Date().getTime())); + dynamicQuery.save(killed); + nativeSql = "UPDATE seckill SET number=number-? WHERE seckill_id=? AND number>0"; + dynamicQuery.nativeExecuteUpdate(nativeSql, new Object[]{number,seckillId}); + }else{ + return Result.error(SeckillStatEnum.END); + } + } catch (Exception e) { + e.printStackTrace(); + } finally{ + if(res){//释放锁 + RedissLockUtil.unlock(seckillId+""); + } + } + return Result.ok(SeckillStatEnum.SUCCESS); + } + +} diff --git a/src/main/java/com/itstyle/seckill/service/impl/SeckillServiceImpl.java b/src/main/java/com/itstyle/seckill/service/impl/SeckillServiceImpl.java new file mode 100644 index 0000000..50ff493 --- /dev/null +++ b/src/main/java/com/itstyle/seckill/service/impl/SeckillServiceImpl.java @@ -0,0 +1,173 @@ +package com.itstyle.seckill.service.impl; + +import java.sql.Timestamp; +import java.util.Date; +import java.util.List; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.itstyle.seckill.common.dynamicquery.DynamicQuery; +import com.itstyle.seckill.common.entity.Result; +import com.itstyle.seckill.common.entity.Seckill; +import com.itstyle.seckill.common.entity.SuccessKilled; +import com.itstyle.seckill.common.enums.SeckillStatEnum; +import com.itstyle.seckill.repository.SeckillRepository; +import com.itstyle.seckill.service.ISeckillService; +@Service +public class SeckillServiceImpl implements ISeckillService { + /** + * 思考:为什么不用synchronized + * service 默认是单例的,并发下lock只有一个实例 + */ + private Lock lock = new ReentrantLock(true);//互斥锁 参数默认false,不公平锁 + + @Autowired + private DynamicQuery dynamicQuery; + @Autowired + private SeckillRepository seckillRepository; + + @Override + public List getSeckillList() { + return seckillRepository.findAll(); + } + + @Override + public Seckill getById(long seckillId) { + return seckillRepository.findOne(seckillId); + } + + @Override + public Long getSeckillCount(long seckillId) { + String nativeSql = "SELECT count(*) FROM success_killed WHERE seckill_id=?"; + Object object = dynamicQuery.nativeQueryObject(nativeSql, new Object[]{seckillId}); + return ((Number) object).longValue(); + } + @Override + @Transactional + public void deleteSeckill(long seckillId) { + String nativeSql = "DELETE FROM success_killed WHERE seckill_id=?"; + dynamicQuery.nativeExecuteUpdate(nativeSql, new Object[]{seckillId}); + nativeSql = "UPDATE seckill SET number =100 WHERE seckill_id=?"; + dynamicQuery.nativeExecuteUpdate(nativeSql, new Object[]{seckillId}); + } + @Override + @Transactional + public Result startSeckil(long seckillId,long userId) { + //校验库存 + String nativeSql = "SELECT number FROM seckill WHERE seckill_id=?"; + Object object = dynamicQuery.nativeQueryObject(nativeSql, new Object[]{seckillId}); + Long number = ((Number) object).longValue(); + if(number>0){ + //扣库存 + nativeSql = "UPDATE seckill SET number=number-1 WHERE seckill_id=?"; + dynamicQuery.nativeExecuteUpdate(nativeSql, new Object[]{seckillId}); + //创建订单 + SuccessKilled killed = new SuccessKilled(); + killed.setSeckillId(seckillId); + killed.setUserId(userId); + killed.setState((short)0); + killed.setCreateTime(new Timestamp(new Date().getTime())); + dynamicQuery.save(killed); + //支付 + return Result.ok(SeckillStatEnum.SUCCESS); + }else{ + return Result.error(SeckillStatEnum.END); + } + } + @Override + @Transactional + public Result startSeckilLock(long seckillId, long userId) { + try { + lock.lock(); + //这里、不清楚为啥、总是会被超卖101、难道锁不起作用、lock是同一个对象 + String nativeSql = "SELECT number FROM seckill WHERE seckill_id=?"; + Object object = dynamicQuery.nativeQueryObject(nativeSql, new Object[]{seckillId}); + Long number = ((Number) object).longValue(); + if(number>0){ + nativeSql = "UPDATE seckill SET number=number-1 WHERE seckill_id=?"; + dynamicQuery.nativeExecuteUpdate(nativeSql, new Object[]{seckillId}); + SuccessKilled killed = new SuccessKilled(); + killed.setSeckillId(seckillId); + killed.setUserId(userId); + killed.setState(Short.parseShort(number+"")); + killed.setCreateTime(new Timestamp(new Date().getTime())); + dynamicQuery.save(killed); + }else{ + return Result.error(SeckillStatEnum.END); + } + } catch (Exception e) { + e.printStackTrace(); + }finally { + lock.unlock(); + } + return Result.ok(SeckillStatEnum.SUCCESS); + } + @Override + @Transactional + public Result startSeckilDBPCC_ONE(long seckillId, long userId) { + //单用户抢购一件商品或者多件都没有问题 + String nativeSql = "SELECT number FROM seckill WHERE seckill_id=? FOR UPDATE"; + Object object = dynamicQuery.nativeQueryObject(nativeSql, new Object[]{seckillId}); + Long number = ((Number) object).longValue(); + if(number>0){ + nativeSql = "UPDATE seckill SET number=number-1 WHERE seckill_id=?"; + dynamicQuery.nativeExecuteUpdate(nativeSql, new Object[]{seckillId}); + SuccessKilled killed = new SuccessKilled(); + killed.setSeckillId(seckillId); + killed.setUserId(userId); + killed.setState((short)0); + killed.setCreateTime(new Timestamp(new Date().getTime())); + dynamicQuery.save(killed); + return Result.ok(SeckillStatEnum.SUCCESS); + }else{ + return Result.error(SeckillStatEnum.END); + } + } + + @Override + @Transactional + public Result startSeckilDBPCC_TWO(long seckillId, long userId) { + //单用户抢购一件商品没有问题、但是抢购多件商品不建议这种写法 + String nativeSql = "UPDATE seckill SET number=number-1 WHERE seckill_id=? AND number>0";//UPDATE锁表 + int count = dynamicQuery.nativeExecuteUpdate(nativeSql, new Object[]{seckillId}); + if(count>0){ + SuccessKilled killed = new SuccessKilled(); + killed.setSeckillId(seckillId); + killed.setUserId(userId); + killed.setState((short)0); + killed.setCreateTime(new Timestamp(new Date().getTime())); + dynamicQuery.save(killed); + return Result.ok(SeckillStatEnum.SUCCESS); + }else{ + return Result.error(SeckillStatEnum.END); + } + } + + @Override + @Transactional + public Result startSeckilDBOCC(long seckillId, long userId, long number) { + Seckill kill = seckillRepository.findOne(seckillId); + if(kill.getNumber()>0){ + //乐观锁 + String nativeSql = "UPDATE seckill SET number=number-?,version=version+1 WHERE seckill_id=? AND version = ?"; + int count = dynamicQuery.nativeExecuteUpdate(nativeSql, new Object[]{number,seckillId,kill.getVersion()}); + if(count>0){ + SuccessKilled killed = new SuccessKilled(); + killed.setSeckillId(seckillId); + killed.setUserId(userId); + killed.setState((short)0); + killed.setCreateTime(new Timestamp(new Date().getTime())); + dynamicQuery.save(killed); + return Result.ok(SeckillStatEnum.SUCCESS); + }else{ + return Result.error(SeckillStatEnum.END); + } + }else{ + return Result.error(SeckillStatEnum.END); + } + } +} diff --git a/src/main/java/com/itstyle/seckill/web/SeckillController.java b/src/main/java/com/itstyle/seckill/web/SeckillController.java new file mode 100644 index 0000000..eab0bec --- /dev/null +++ b/src/main/java/com/itstyle/seckill/web/SeckillController.java @@ -0,0 +1,203 @@ +package com.itstyle.seckill.web; + +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; + +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.itstyle.seckill.common.entity.Result; +import com.itstyle.seckill.common.entity.SuccessKilled; +import com.itstyle.seckill.queue.jvm.SeckillQueue; +import com.itstyle.seckill.service.ISeckillService; +@Api(tags ="秒杀") +@RestController +@RequestMapping("/seckill") +public class SeckillController { + private final static Logger LOGGER = LoggerFactory.getLogger(SeckillController.class); + + private static int corePoolSize = Runtime.getRuntime().availableProcessors(); + //调整队列数 拒绝服务 + private static ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize, corePoolSize+1, 10l, TimeUnit.SECONDS, + new LinkedBlockingQueue(1000)); + + @Autowired + private ISeckillService seckillService; + + @ApiOperation(value="秒杀一(最low实现)",nickname="科帮网") + @PostMapping("/start") + public Result start(long seckillId){ + seckillService.deleteSeckill(seckillId); + final long killId = seckillId; + LOGGER.info("开始秒杀一(会出现超卖)"); + for(int i=0;i<1000;i++){ + final long userId = i; + Runnable task = new Runnable() { + @Override + public void run() { + Result result = seckillService.startSeckil(killId, userId); + LOGGER.info("用户:{}{}",userId,result.get("msg")); + } + }; + executor.execute(task); + } + try { + Thread.sleep(10000); + Long seckillCount = seckillService.getSeckillCount(seckillId); + LOGGER.info("一共秒杀出{}件商品",seckillCount); + } catch (InterruptedException e) { + e.printStackTrace(); + } + return Result.ok(); + } + @ApiOperation(value="秒杀二(程序锁)",nickname="科帮网") + @PostMapping("/startLock") + public Result startLock(long seckillId){ + seckillService.deleteSeckill(seckillId); + final long killId = seckillId; + LOGGER.info("开始秒杀二(正常)"); + for(int i=0;i<1000;i++){ + final long userId = i; + Runnable task = new Runnable() { + @Override + public void run() { + Result result = seckillService.startSeckilLock(killId, userId); + LOGGER.info("用户:{}{}",userId,result.get("msg")); + } + }; + executor.execute(task); + } + try { + Thread.sleep(10000); + Long seckillCount = seckillService.getSeckillCount(seckillId); + LOGGER.info("一共秒杀出{}件商品",seckillCount); + } catch (InterruptedException e) { + e.printStackTrace(); + } + return Result.ok(); + } + @ApiOperation(value="秒杀三(数据库悲观锁)",nickname="科帮网") + @PostMapping("/startDBPCC_ONE") + public Result startDBPCC_ONE(long seckillId){ + seckillService.deleteSeckill(seckillId); + final long killId = seckillId; + LOGGER.info("开始秒杀三(正常)"); + for(int i=0;i<1000;i++){ + final long userId = i; + Runnable task = new Runnable() { + @Override + public void run() { + Result result = seckillService.startSeckilDBPCC_ONE(killId, userId); + LOGGER.info("用户:{}{}",userId,result.get("msg")); + } + }; + executor.execute(task); + } + try { + Thread.sleep(10000); + Long seckillCount = seckillService.getSeckillCount(seckillId); + LOGGER.info("一共秒杀出{}件商品",seckillCount); + } catch (InterruptedException e) { + e.printStackTrace(); + } + return Result.ok(); + } + @ApiOperation(value="秒杀四(数据库悲观锁)",nickname="科帮网") + @PostMapping("/startDPCC_TWO") + public Result startDPCC_TWO(long seckillId){ + seckillService.deleteSeckill(seckillId); + final long killId = seckillId; + LOGGER.info("开始秒杀四(正常、数据库锁最优实现)"); + for(int i=0;i<1000;i++){ + final long userId = i; + Runnable task = new Runnable() { + @Override + public void run() { + Result result = seckillService.startSeckilDBPCC_TWO(killId, userId); + LOGGER.info("用户:{}{}",userId,result.get("msg")); + } + }; + executor.execute(task); + } + try { + Thread.sleep(10000); + Long seckillCount = seckillService.getSeckillCount(seckillId); + LOGGER.info("一共秒杀出{}件商品",seckillCount); + } catch (InterruptedException e) { + e.printStackTrace(); + } + return Result.ok(); + } + @ApiOperation(value="秒杀五(数据库乐观锁)",nickname="科帮网") + @PostMapping("/startDBOCC") + public Result startDBOCC(long seckillId){ + seckillService.deleteSeckill(seckillId); + final long killId = seckillId; + LOGGER.info("开始秒杀五(正常、数据库锁最优实现)"); + for(int i=0;i<1000;i++){ + final long userId = i; + Runnable task = new Runnable() { + @Override + public void run() { + Result result = seckillService.startSeckilDBOCC(killId, userId,4); + LOGGER.info("用户:{}{}",userId,result.get("msg")); + } + }; + executor.execute(task); + } + try { + Thread.sleep(10000); + Long seckillCount = seckillService.getSeckillCount(seckillId); + LOGGER.info("一共秒杀出{}件商品",seckillCount); + } catch (InterruptedException e) { + e.printStackTrace(); + } + return Result.ok(); + } + @ApiOperation(value="秒杀六(进程内队列)",nickname="科帮网") + @PostMapping("/startQueue") + public Result startQueue(long seckillId){ + seckillService.deleteSeckill(seckillId); + final long killId = seckillId; + LOGGER.info("开始秒杀六(正常)"); + for(int i=0;i<1000;i++){ + final long userId = i; + Runnable task = new Runnable() { + @Override + public void run() { + SuccessKilled kill = new SuccessKilled(); + kill.setSeckillId(killId); + kill.setUserId(userId); + try { + Boolean flag = SeckillQueue.getMailQueue().produce(kill); + if(flag){ + LOGGER.info("用户:{}{}",kill.getUserId(),"秒杀成功"); + }else{ + LOGGER.info("用户:{}{}",userId,"秒杀失败"); + } + } catch (InterruptedException e) { + e.printStackTrace(); + LOGGER.info("用户:{}{}",userId,"秒杀失败"); + } + } + }; + executor.execute(task); + } + try { + Thread.sleep(10000); + Long seckillCount = seckillService.getSeckillCount(seckillId); + LOGGER.info("一共秒杀出{}件商品",seckillCount); + } catch (InterruptedException e) { + e.printStackTrace(); + } + return Result.ok(); + } +} diff --git a/src/main/java/com/itstyle/seckill/web/SeckillDistributedController.java b/src/main/java/com/itstyle/seckill/web/SeckillDistributedController.java new file mode 100644 index 0000000..11c4d3b --- /dev/null +++ b/src/main/java/com/itstyle/seckill/web/SeckillDistributedController.java @@ -0,0 +1,145 @@ +package com.itstyle.seckill.web; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; + +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.itstyle.seckill.common.entity.Result; +import com.itstyle.seckill.queue.kafka.KafkaSender; +import com.itstyle.seckill.queue.redis.RedisSender; +import com.itstyle.seckill.service.ISeckillDistributedService; +import com.itstyle.seckill.service.ISeckillService; +@Api(tags ="分布式秒杀") +@RestController +@RequestMapping("/seckillDistributed") +public class SeckillDistributedController { + private final static Logger LOGGER = LoggerFactory.getLogger(SeckillDistributedController.class); + + private static int corePoolSize = Runtime.getRuntime().availableProcessors(); + //调整队列数 拒绝服务 + private static ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize, corePoolSize+1, 10l, TimeUnit.SECONDS, + new LinkedBlockingQueue(10000)); + + @Autowired + private ISeckillService seckillService; + @Autowired + private ISeckillDistributedService seckillDistributedService; + @Autowired + private RedisSender redisSender; + @Autowired + private KafkaSender kafkaSender; + + @ApiOperation(value="秒杀一(Rediss分布式锁)",nickname="科帮网") + @PostMapping("/startRedisLock") + public Result startRedisLock(long seckillId){ + seckillService.deleteSeckill(seckillId); + final long killId = seckillId; + LOGGER.info("开始秒杀一"); + for(int i=0;i<1000;i++){ + final long userId = i; + Runnable task = new Runnable() { + @Override + public void run() { + Result result = seckillDistributedService.startSeckilRedisLock(killId, userId); + LOGGER.info("用户:{}{}",userId,result.get("msg")); + } + }; + executor.execute(task); + } + try { + Thread.sleep(10000); + Long seckillCount = seckillService.getSeckillCount(seckillId); + LOGGER.info("一共秒杀出{}件商品",seckillCount); + } catch (InterruptedException e) { + e.printStackTrace(); + } + return Result.ok(); + } + @ApiOperation(value="秒杀二(zookeeper分布式锁)",nickname="科帮网") + @PostMapping("/startZkLock") + public Result startZkLock(long seckillId){ + seckillService.deleteSeckill(seckillId); + final long killId = seckillId; + LOGGER.info("开始秒杀二"); + for(int i=0;i<1000;i++){ + final long userId = i; + Runnable task = new Runnable() { + @Override + public void run() { + Result result = seckillDistributedService.startSeckilZksLock(killId, userId); + LOGGER.info("用户:{}{}",userId,result.get("msg")); + } + }; + executor.execute(task); + } + try { + Thread.sleep(10000); + Long seckillCount = seckillService.getSeckillCount(seckillId); + LOGGER.info("一共秒杀出{}件商品",seckillCount); + } catch (InterruptedException e) { + e.printStackTrace(); + } + return Result.ok(); + } + @ApiOperation(value="秒杀三(Redis分布式队列-订阅监听)",nickname="科帮网") + @PostMapping("/startRedisQueue") + public Result startRedisQueue(long seckillId){ + seckillService.deleteSeckill(seckillId); + final long killId = seckillId; + LOGGER.info("开始秒杀三"); + for(int i=0;i<1000;i++){ + final long userId = i; + Runnable task = new Runnable() { + @Override + public void run() { + //思考如何返回给用户信息ws + redisSender.sendChannelMess("seckill",killId+";"+userId); + } + }; + executor.execute(task); + } + try { + Thread.sleep(10000); + Long seckillCount = seckillService.getSeckillCount(seckillId); + LOGGER.info("一共秒杀出{}件商品",seckillCount); + } catch (InterruptedException e) { + e.printStackTrace(); + } + return Result.ok(); + } + @ApiOperation(value="秒杀四(Kafka分布式队列)",nickname="科帮网") + @PostMapping("/startKafkaQueue") + public Result startKafkaQueue(long seckillId){ + seckillService.deleteSeckill(seckillId); + final long killId = seckillId; + LOGGER.info("开始秒杀四"); + for(int i=0;i<1000;i++){ + final long userId = i; + Runnable task = new Runnable() { + @Override + public void run() { + //思考如何返回给用户信息ws + kafkaSender.sendChannelMess("seckill",killId+";"+userId); + } + }; + executor.execute(task); + } + try { + Thread.sleep(10000); + Long seckillCount = seckillService.getSeckillCount(seckillId); + LOGGER.info("一共秒杀出{}件商品",seckillCount); + } catch (InterruptedException e) { + e.printStackTrace(); + } + return Result.ok(); + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties new file mode 100644 index 0000000..c49f3a3 --- /dev/null +++ b/src/main/resources/application.properties @@ -0,0 +1,82 @@ +# \u9879\u76eecontextPath \u79d1\u5e2e\u7f51https://blog.52itstyle.com +server.context-path=/seckill +# \u670d\u52a1\u7aef\u53e3 +server.port=8080 +# session\u6700\u5927\u8d85\u65f6\u65f6\u95f4(\u5206\u949f)\uff0c\u9ed8\u8ba4\u4e3a30 +server.session-timeout=60 +# tomcat\u6700\u5927\u7ebf\u7a0b\u6570\uff0c\u9ed8\u8ba4\u4e3a200 +server.tomcat.max-threads=100 +# tomcat\u7684URI\u7f16\u7801 +server.tomcat.uri-encoding=UTF-8 + +#spring boot\u4ece\u63a7\u5236\u53f0\u6253\u5370\u51fa\u6765\u7684\u65e5\u5fd7\u7ea7\u522b\u53ea\u6709ERROR, WARN \u8fd8\u6709INFO\uff0c\u5982\u679c\u4f60\u60f3\u8981\u6253\u5370debug\u7ea7\u522b\u7684\u65e5\u5fd7 +#debug=true +logging.level.root=INFO + +spring.thymeleaf.mode=LEGACYHTML5 +#dev tools +spring.devtools.livereload.enabled=true +spring.thymeleaf.cache=false +spring.thymeleaf.cache-period=0 +spring.thymeleaf.template.cache=false +# \u9759\u6001\u6587\u4ef6\u8bf7\u6c42\u5339\u914d\u65b9\u5f0f +spring.mvc.static-path-pattern=/** + +#\u6ce8\u610f\u4e2d\u6587\u4e71\u7801 +spring.datasource.url=jdbc:mysql://localhost:3306/seckill?characterEncoding=utf-8&useSSL=false +spring.datasource.username=root +spring.datasource.password=123456 +spring.datasource.driver-class-name=com.mysql.jdbc.Driver +# Specify the DBMS +spring.jpa.database = MYSQL +# Show or not log for each sql query +spring.jpa.show-sql = false +# DDL mode. This is actually a shortcut for the "hibernate.hbm2ddl.auto" property. Default to "create-drop" when using an embedded database, "none" otherwise. +spring.jpa.hibernate.ddl-auto = update +# Hibernate 4 naming strategy fully qualified name. Not supported with Hibernate 5. +spring.jpa.hibernate.naming.strategy = org.hibernate.cfg.ImprovedNamingStrategy +# stripped before adding them to the entity manager) +spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5Dialect + +# Redis +# \u6570\u636e\u5e93\u7d22\u5f15\uff08\u9ed8\u8ba4\u4e3a0\uff09 +spring.redis.database=0 +# \u670d\u52a1\u5668\u5730\u5740 \u53d8\u66f4\u4e3a\u81ea\u5df1\u7684 +spring.redis.host=192.168.1.180 +# \u670d\u52a1\u5668\u8fde\u63a5\u7aef\u53e3 +spring.redis.port=6379 +# \u670d\u52a1\u5668\u8fde\u63a5\u5bc6\u7801\uff08\u9ed8\u8ba4\u4e3a\u7a7a\uff09\u5982\u679c\u6709\u53d8\u66f4\u4e3a\u81ea\u5df1\u7684 +spring.redis.password=6347097 +# \u8fde\u63a5\u6c60\u6700\u5927\u8fde\u63a5\u6570\uff08\u4f7f\u7528\u8d1f\u503c\u8868\u793a\u6ca1\u6709\u9650\u5236\uff09 +spring.redis.pool.max-active=8 +# \u8fde\u63a5\u6c60\u6700\u5927\u963b\u585e\u7b49\u5f85\u65f6\u95f4\uff08\u4f7f\u7528\u8d1f\u503c\u8868\u793a\u6ca1\u6709\u9650\u5236\uff09 +spring.redis.pool.max-wait=-1 +# \u8fde\u63a5\u6c60\u4e2d\u7684\u6700\u5927\u7a7a\u95f2\u8fde\u63a5 +spring.redis.pool.max-idle=8 +# \u8fde\u63a5\u6c60\u4e2d\u7684\u6700\u5c0f\u7a7a\u95f2\u8fde\u63a5 +spring.redis.pool.min-idle=0 +# \u8fde\u63a5\u8d85\u65f6\u65f6\u95f4\uff08\u6beb\u79d2\uff09 +spring.redis.timeout=30000 + + +# redisson lock +redisson.address=redis://192.168.1.180:6379 +redisson.password=6347097 + + +#kafka\u76f8\u5173\u914d\u7f6e +spring.kafka.bootstrap-servers=192.168.1.180:9092 +#\u8bbe\u7f6e\u4e00\u4e2a\u9ed8\u8ba4\u7ec4 +spring.kafka.consumer.group-id=0 +#key-value\u5e8f\u5217\u5316\u53cd\u5e8f\u5217\u5316 +spring.kafka.consumer.key-deserializer=org.apache.kafka.common.serialization.StringDeserializer +spring.kafka.consumer.value-deserializer=org.apache.kafka.common.serialization.StringDeserializer +spring.kafka.producer.key-serializer=org.apache.kafka.common.serialization.StringSerializer +spring.kafka.producer.value-serializer=org.apache.kafka.common.serialization.StringSerializer +#\u6bcf\u6b21\u6279\u91cf\u53d1\u9001\u6d88\u606f\u7684\u6570\u91cf +spring.kafka.producer.batch-size=65536 +spring.kafka.producer.buffer-memory=524288 + + +#zookeeper.address +zookeeper.address = 192.168.1.180:2181 \ No newline at end of file diff --git a/src/main/resources/logback-spring.xml b/src/main/resources/logback-spring.xml new file mode 100644 index 0000000..be6b1fb --- /dev/null +++ b/src/main/resources/logback-spring.xml @@ -0,0 +1,82 @@ + + + + spring-boot-seckill + + + + log/spring-boot-seckill-info.log + + + + log/spring-boot-seckill-info-%d{yyyy-MM-dd}.%i.log + + 128MB + + + 30 + + true + + %d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger Line:%-3L - %msg%n + utf-8 + + + info + ACCEPT + DENY + + + + + log/spring-boot-seckill-error.log + + + + log/spring-boot-seckill-error-%d{yyyy-MM-dd}.%i.log + + 2MB + + + 180 + + true + + + %d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger Line:%-3L - %msg%n + utf-8 + + + + + ERROR + + ACCEPT + + DENY + + + + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger Line:%-3L - %msg%n + utf-8 + + + + INFO + + + + + + + + + + + + + + diff --git a/src/main/resources/sql/seckill.sql b/src/main/resources/sql/seckill.sql new file mode 100644 index 0000000..fcd6479 --- /dev/null +++ b/src/main/resources/sql/seckill.sql @@ -0,0 +1,55 @@ +/* +SQLyog 企业版 - MySQL GUI v8.14 +MySQL - 5.7.17-log : Database - seckill +********************************************************************* +*/ + +/*!40101 SET NAMES utf8 */; + +/*!40101 SET SQL_MODE=''*/; + +/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; +/*Table structure for table `seckill` */ + +DROP TABLE IF EXISTS `seckill`; + +CREATE TABLE `seckill` ( + `seckill_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '商品库存id', + `name` varchar(120) NOT NULL COMMENT '商品名称', + `number` int(11) NOT NULL COMMENT '库存数量', + `start_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '秒杀开启时间', + `end_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '秒杀结束时间', + `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `version` int(11) NOT NULL COMMENT '版本号', + PRIMARY KEY (`seckill_id`), + KEY `idx_start_time` (`start_time`), + KEY `idx_end_time` (`end_time`), + KEY `idx_create_time` (`create_time`) +) ENGINE=InnoDB AUTO_INCREMENT=1004 DEFAULT CHARSET=utf8 COMMENT='秒杀库存表'; + +/*Data for the table `seckill` */ + +insert into `seckill`(`seckill_id`,`name`,`number`,`start_time`,`end_time`,`create_time`,`version`) values (1000,'1000元秒杀iphone8',100,'2018-05-10 15:31:53','2018-05-10 15:31:53','2018-05-10 15:31:53',0),(1001,'500元秒杀ipad2',100,'2018-05-10 15:31:53','2018-05-10 15:31:53','2018-05-10 15:31:53',0),(1002,'300元秒杀小米4',100,'2018-05-10 15:31:53','2018-05-10 15:31:53','2018-05-10 15:31:53',0),(1003,'200元秒杀红米note',100,'2018-05-10 15:31:53','2018-05-10 15:31:53','2018-05-10 15:31:53',0); + +/*Table structure for table `success_killed` */ + +DROP TABLE IF EXISTS `success_killed`; + +CREATE TABLE `success_killed` ( + `seckill_id` bigint(20) NOT NULL COMMENT '秒杀商品id', + `user_id` bigint(20) NOT NULL COMMENT '用户Id', + `state` tinyint(4) NOT NULL COMMENT '状态标示:-1指无效,0指成功,1指已付款', + `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + PRIMARY KEY (`seckill_id`,`user_id`), + KEY `idx_create_time` (`create_time`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='秒杀成功明细表'; + +/*Data for the table `success_killed` */ + +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; +/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; +/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; +/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 0000000..9f88c1f --- /dev/null +++ b/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,7 @@ + + + + Archetype Created Web Application + diff --git a/src/main/webapp/index.jsp b/src/main/webapp/index.jsp new file mode 100644 index 0000000..c38169b --- /dev/null +++ b/src/main/webapp/index.jsp @@ -0,0 +1,5 @@ + + +

Hello World!

+ + diff --git a/target/classes/application.properties b/target/classes/application.properties new file mode 100644 index 0000000..c49f3a3 --- /dev/null +++ b/target/classes/application.properties @@ -0,0 +1,82 @@ +# \u9879\u76eecontextPath \u79d1\u5e2e\u7f51https://blog.52itstyle.com +server.context-path=/seckill +# \u670d\u52a1\u7aef\u53e3 +server.port=8080 +# session\u6700\u5927\u8d85\u65f6\u65f6\u95f4(\u5206\u949f)\uff0c\u9ed8\u8ba4\u4e3a30 +server.session-timeout=60 +# tomcat\u6700\u5927\u7ebf\u7a0b\u6570\uff0c\u9ed8\u8ba4\u4e3a200 +server.tomcat.max-threads=100 +# tomcat\u7684URI\u7f16\u7801 +server.tomcat.uri-encoding=UTF-8 + +#spring boot\u4ece\u63a7\u5236\u53f0\u6253\u5370\u51fa\u6765\u7684\u65e5\u5fd7\u7ea7\u522b\u53ea\u6709ERROR, WARN \u8fd8\u6709INFO\uff0c\u5982\u679c\u4f60\u60f3\u8981\u6253\u5370debug\u7ea7\u522b\u7684\u65e5\u5fd7 +#debug=true +logging.level.root=INFO + +spring.thymeleaf.mode=LEGACYHTML5 +#dev tools +spring.devtools.livereload.enabled=true +spring.thymeleaf.cache=false +spring.thymeleaf.cache-period=0 +spring.thymeleaf.template.cache=false +# \u9759\u6001\u6587\u4ef6\u8bf7\u6c42\u5339\u914d\u65b9\u5f0f +spring.mvc.static-path-pattern=/** + +#\u6ce8\u610f\u4e2d\u6587\u4e71\u7801 +spring.datasource.url=jdbc:mysql://localhost:3306/seckill?characterEncoding=utf-8&useSSL=false +spring.datasource.username=root +spring.datasource.password=123456 +spring.datasource.driver-class-name=com.mysql.jdbc.Driver +# Specify the DBMS +spring.jpa.database = MYSQL +# Show or not log for each sql query +spring.jpa.show-sql = false +# DDL mode. This is actually a shortcut for the "hibernate.hbm2ddl.auto" property. Default to "create-drop" when using an embedded database, "none" otherwise. +spring.jpa.hibernate.ddl-auto = update +# Hibernate 4 naming strategy fully qualified name. Not supported with Hibernate 5. +spring.jpa.hibernate.naming.strategy = org.hibernate.cfg.ImprovedNamingStrategy +# stripped before adding them to the entity manager) +spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5Dialect + +# Redis +# \u6570\u636e\u5e93\u7d22\u5f15\uff08\u9ed8\u8ba4\u4e3a0\uff09 +spring.redis.database=0 +# \u670d\u52a1\u5668\u5730\u5740 \u53d8\u66f4\u4e3a\u81ea\u5df1\u7684 +spring.redis.host=192.168.1.180 +# \u670d\u52a1\u5668\u8fde\u63a5\u7aef\u53e3 +spring.redis.port=6379 +# \u670d\u52a1\u5668\u8fde\u63a5\u5bc6\u7801\uff08\u9ed8\u8ba4\u4e3a\u7a7a\uff09\u5982\u679c\u6709\u53d8\u66f4\u4e3a\u81ea\u5df1\u7684 +spring.redis.password=6347097 +# \u8fde\u63a5\u6c60\u6700\u5927\u8fde\u63a5\u6570\uff08\u4f7f\u7528\u8d1f\u503c\u8868\u793a\u6ca1\u6709\u9650\u5236\uff09 +spring.redis.pool.max-active=8 +# \u8fde\u63a5\u6c60\u6700\u5927\u963b\u585e\u7b49\u5f85\u65f6\u95f4\uff08\u4f7f\u7528\u8d1f\u503c\u8868\u793a\u6ca1\u6709\u9650\u5236\uff09 +spring.redis.pool.max-wait=-1 +# \u8fde\u63a5\u6c60\u4e2d\u7684\u6700\u5927\u7a7a\u95f2\u8fde\u63a5 +spring.redis.pool.max-idle=8 +# \u8fde\u63a5\u6c60\u4e2d\u7684\u6700\u5c0f\u7a7a\u95f2\u8fde\u63a5 +spring.redis.pool.min-idle=0 +# \u8fde\u63a5\u8d85\u65f6\u65f6\u95f4\uff08\u6beb\u79d2\uff09 +spring.redis.timeout=30000 + + +# redisson lock +redisson.address=redis://192.168.1.180:6379 +redisson.password=6347097 + + +#kafka\u76f8\u5173\u914d\u7f6e +spring.kafka.bootstrap-servers=192.168.1.180:9092 +#\u8bbe\u7f6e\u4e00\u4e2a\u9ed8\u8ba4\u7ec4 +spring.kafka.consumer.group-id=0 +#key-value\u5e8f\u5217\u5316\u53cd\u5e8f\u5217\u5316 +spring.kafka.consumer.key-deserializer=org.apache.kafka.common.serialization.StringDeserializer +spring.kafka.consumer.value-deserializer=org.apache.kafka.common.serialization.StringDeserializer +spring.kafka.producer.key-serializer=org.apache.kafka.common.serialization.StringSerializer +spring.kafka.producer.value-serializer=org.apache.kafka.common.serialization.StringSerializer +#\u6bcf\u6b21\u6279\u91cf\u53d1\u9001\u6d88\u606f\u7684\u6570\u91cf +spring.kafka.producer.batch-size=65536 +spring.kafka.producer.buffer-memory=524288 + + +#zookeeper.address +zookeeper.address = 192.168.1.180:2181 \ No newline at end of file diff --git a/target/classes/logback-spring.xml b/target/classes/logback-spring.xml new file mode 100644 index 0000000..be6b1fb --- /dev/null +++ b/target/classes/logback-spring.xml @@ -0,0 +1,82 @@ + + + + spring-boot-seckill + + + + log/spring-boot-seckill-info.log + + + + log/spring-boot-seckill-info-%d{yyyy-MM-dd}.%i.log + + 128MB + + + 30 + + true + + %d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger Line:%-3L - %msg%n + utf-8 + + + info + ACCEPT + DENY + + + + + log/spring-boot-seckill-error.log + + + + log/spring-boot-seckill-error-%d{yyyy-MM-dd}.%i.log + + 2MB + + + 180 + + true + + + %d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger Line:%-3L - %msg%n + utf-8 + + + + + ERROR + + ACCEPT + + DENY + + + + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger Line:%-3L - %msg%n + utf-8 + + + + INFO + + + + + + + + + + + + + + diff --git a/target/classes/sql/seckill.sql b/target/classes/sql/seckill.sql new file mode 100644 index 0000000..fcd6479 --- /dev/null +++ b/target/classes/sql/seckill.sql @@ -0,0 +1,55 @@ +/* +SQLyog 企业版 - MySQL GUI v8.14 +MySQL - 5.7.17-log : Database - seckill +********************************************************************* +*/ + +/*!40101 SET NAMES utf8 */; + +/*!40101 SET SQL_MODE=''*/; + +/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; +/*Table structure for table `seckill` */ + +DROP TABLE IF EXISTS `seckill`; + +CREATE TABLE `seckill` ( + `seckill_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '商品库存id', + `name` varchar(120) NOT NULL COMMENT '商品名称', + `number` int(11) NOT NULL COMMENT '库存数量', + `start_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '秒杀开启时间', + `end_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '秒杀结束时间', + `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `version` int(11) NOT NULL COMMENT '版本号', + PRIMARY KEY (`seckill_id`), + KEY `idx_start_time` (`start_time`), + KEY `idx_end_time` (`end_time`), + KEY `idx_create_time` (`create_time`) +) ENGINE=InnoDB AUTO_INCREMENT=1004 DEFAULT CHARSET=utf8 COMMENT='秒杀库存表'; + +/*Data for the table `seckill` */ + +insert into `seckill`(`seckill_id`,`name`,`number`,`start_time`,`end_time`,`create_time`,`version`) values (1000,'1000元秒杀iphone8',100,'2018-05-10 15:31:53','2018-05-10 15:31:53','2018-05-10 15:31:53',0),(1001,'500元秒杀ipad2',100,'2018-05-10 15:31:53','2018-05-10 15:31:53','2018-05-10 15:31:53',0),(1002,'300元秒杀小米4',100,'2018-05-10 15:31:53','2018-05-10 15:31:53','2018-05-10 15:31:53',0),(1003,'200元秒杀红米note',100,'2018-05-10 15:31:53','2018-05-10 15:31:53','2018-05-10 15:31:53',0); + +/*Table structure for table `success_killed` */ + +DROP TABLE IF EXISTS `success_killed`; + +CREATE TABLE `success_killed` ( + `seckill_id` bigint(20) NOT NULL COMMENT '秒杀商品id', + `user_id` bigint(20) NOT NULL COMMENT '用户Id', + `state` tinyint(4) NOT NULL COMMENT '状态标示:-1指无效,0指成功,1指已付款', + `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + PRIMARY KEY (`seckill_id`,`user_id`), + KEY `idx_create_time` (`create_time`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='秒杀成功明细表'; + +/*Data for the table `success_killed` */ + +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; +/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; +/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; +/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;