秒杀初交

This commit is contained in:
小柒2012
2018-05-15 18:30:33 +08:00
parent efa656ec9e
commit 786dc6c311
51 changed files with 2861 additions and 0 deletions

View 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("项目启动 ");
}
}

View File

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

View File

@@ -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;
}
}

View File

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

View File

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

View File

@@ -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 {
}

View 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;
}
}

View 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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

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

View File

@@ -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;
}
}

View 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;
}
}

View File

@@ -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
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

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

View File

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

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

View File

@@ -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]));
}
}

View File

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

View File

@@ -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]));
}
}

View File

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

View File

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

View File

@@ -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> {
}

View File

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

View File

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

View File

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

View File

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

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

View File

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