mirror of
https://gitee.com/52itstyle/spring-boot-seckill.git
synced 2026-05-13 10:52:33 +00:00
秒杀初交
This commit is contained in:
24
src/main/java/com/itstyle/seckill/Application.java
Normal file
24
src/main/java/com/itstyle/seckill/Application.java
Normal file
@@ -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("项目启动 ");
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 <T> void delete(Class<T> entityClass, Object entityid);
|
||||
|
||||
public <T> void delete(Class<T> entityClass, Object[] entityids);
|
||||
|
||||
|
||||
/**
|
||||
* 查询对象列表,返回List
|
||||
* @param resultClass
|
||||
* @param nativeSql
|
||||
* @param params
|
||||
* @return List<T>
|
||||
* @Date 2018年3月15日
|
||||
* 更新日志
|
||||
* 2018年3月15日 张志朋 首次创建
|
||||
*
|
||||
*/
|
||||
<T> List<T> nativeQueryList(String nativeSql, Object... params);
|
||||
|
||||
/**
|
||||
* 查询对象列表,返回List<Map<key,value>>
|
||||
* @param nativeSql
|
||||
* @param params
|
||||
* @return List<T>
|
||||
* @Date 2018年3月15日
|
||||
* 更新日志
|
||||
* 2018年3月15日 张志朋 首次创建
|
||||
*
|
||||
*/
|
||||
<T> List<T> nativeQueryListMap(String nativeSql,Object... params);
|
||||
|
||||
/**
|
||||
* 查询对象列表,返回List<组合对象>
|
||||
* @param resultClass
|
||||
* @param nativeSql
|
||||
* @param params
|
||||
* @return List<T>
|
||||
* @Date 2018年3月15日
|
||||
* 更新日志
|
||||
* 2018年3月15日 张志朋 首次创建
|
||||
*
|
||||
*/
|
||||
<T> List<T> nativeQueryListModel(Class<T> 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);
|
||||
}
|
||||
@@ -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 <T> void delete(Class<T> entityClass, Object entityid) {
|
||||
delete(entityClass, new Object[] { entityid });
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> void delete(Class<T> 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 <T> List<T> 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 <T> List<T> nativeQueryListModel(Class<T> 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 <T> List<T> 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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
59
src/main/java/com/itstyle/seckill/common/entity/Result.java
Normal file
59
src/main/java/com/itstyle/seckill/common/entity/Result.java
Normal file
@@ -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<String, Object> {
|
||||
|
||||
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<String, Object> 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;
|
||||
}
|
||||
}
|
||||
81
src/main/java/com/itstyle/seckill/common/entity/Seckill.java
Normal file
81
src/main/java/com/itstyle/seckill/common/entity/Seckill.java
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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 );
|
||||
}
|
||||
}
|
||||
@@ -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<Object, Object>
|
||||
* @Date 2017年8月13日
|
||||
* 更新日志
|
||||
* 2017年8月13日 科帮网 首次创建
|
||||
*
|
||||
*/
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
@Bean
|
||||
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
|
||||
RedisTemplate<Object, Object> template = new RedisTemplate<Object, Object>();
|
||||
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;
|
||||
}
|
||||
}
|
||||
130
src/main/java/com/itstyle/seckill/common/redis/RedisUtil.java
Normal file
130
src/main/java/com/itstyle/seckill/common/redis/RedisUtil.java
Normal file
@@ -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<Serializable, Serializable> 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<Serializable, Serializable> 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<Serializable, Serializable> 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<Serializable, Serializable> 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;
|
||||
}
|
||||
}
|
||||
@@ -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<Boolean> 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<Boolean> 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)
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<SuccessKilled> blockingQueue = new LinkedBlockingQueue<SuccessKilled>(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();
|
||||
}
|
||||
}
|
||||
31
src/main/java/com/itstyle/seckill/queue/jvm/TaskRunner.java
Normal file
31
src/main/java/com/itstyle/seckill/queue/jvm/TaskRunner.java
Normal file
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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]));
|
||||
}
|
||||
}
|
||||
@@ -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<String,String> kafkaTemplate;
|
||||
|
||||
/**
|
||||
* 发送消息到kafka
|
||||
*/
|
||||
public void sendChannelMess(String channel, String message){
|
||||
kafkaTemplate.send(channel,message);
|
||||
}
|
||||
}
|
||||
@@ -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]));
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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<Seckill, Long> {
|
||||
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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<Seckill> 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);
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<Seckill> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
203
src/main/java/com/itstyle/seckill/web/SeckillController.java
Normal file
203
src/main/java/com/itstyle/seckill/web/SeckillController.java
Normal file
@@ -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<Runnable>(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();
|
||||
}
|
||||
}
|
||||
@@ -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<Runnable>(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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user