您的位置:首页 > 数据库 > Redis

基于本地redis、protostuff序列化对于数据层的优化及java中对于泛型的使用

2018-03-29 17:08 896 查看
此次对于redis、protostuff的应用是在一个高并发的秒杀系统中实现的。

在高并发的秒杀系统的优化中主要有以下几个方面:

1.对于获取秒杀地址的接口的优化

每次获取秒杀接口我们都要访问数据库,在高并发的系统中我们可以使用redis缓存进行优化,不需要每次都访问数据库,从而减小数据库的压力。而同时使用Protostuff对于需要传递的数据进行序列化,这样传递的数据量就会大大减小。从而减少并发时间。

2.对于秒杀操作的优化

秒杀操作是和数据库事务有关的,在秒杀操作中我们主要执行insert和update两条语句。

而对同一条数据进行update操作的时候,mysql存在行级锁等待的情况,一条update语句必须等待前一天update执行完之后才可以拿到lock锁。

所以这里优化主要针对的是行级锁事务的等待,网络的延迟和GC回收!

这次我们主要讲的是对于秒杀地址的优化。

实体类Seckill,记录了秒杀商品id,商品name,商品库存,开始时间结束时间,创建时间。

public class Seckill
{
private long seckillId;
private String name;
private int number;
private Date startTime;
private Date endTime;
private Date createTime;

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 Date getStartTime() {
return startTime;
}

public void setStartTime(Date startTime) {
this.startTime = startTime;
}

public Date getEndTime() {
return endTime;
}

public void setEndTime(Date endTime) {
this.endTime = endTime;
}

public Date getCreateTime() {
return createTime;
}

public void setCreateTime(Date createTime) {
this.createTime = createTime;
}

@Override
public String toString() {
return "Seckill{" +
"seckillId=" + seckillId +
", name='" + name + '\'' +
", number=" + number +
", startTime=" + startTime +
", endTime=" + endTime +
", createTime=" + createTime +
'}';
}
}


实体类Exposer,存放了是否秒杀,MD5加密,当前系统时间,秒杀开始时间、秒杀结束时间等信息

/**
* Created by yliu on 18/03/27.
* 暴露秒杀地址(接口)DTO
*/
public class Exposer {

//是否开启秒杀
private boolean exposed;

//加密措施
private String md5;

private long seckillId;

//系统当前时间(毫秒)
private long now;

//秒杀的开启时间
private long start;

//秒杀的结束时间
private long end;

//构造函数。秒杀成功则返回true,已经经过md5加密后的地址,以及商品id,表示该商品已经开启秒杀
public Exposer(boolean exposed, String md5, long seckillId) {
this.exposed = exposed;
this.md5 = md5;
this.seckillId = seckillId;
}

//构造函数。若失败则返回false,可以返回商品id和系统时间、开始时间、结束时间、判断秒杀何时开启
public Exposer(boolean exposed, long seckillId,long now, long start, long end) {
this.exposed = exposed;
this.seckillId=seckillId;
this.now = now;
this.start = start;
this.end = end;
}
//构造函数
public Exposer(boolean exposed, long seckillId) {
this.exposed = exposed;
this.seckillId = seckillId;
}

public boolean isExposed() {
return exposed;
}

public void setExposed(boolean exposed) {
this.exposed = exposed;
}

public String getMd5() {
return md5;
}

public void setMd5(String md5) {
this.md5 = md5;
}

public long getSeckillId() {
return seckillId;
}

public void setSeckillId(long seckillId) {
this.seckillId = seckillId;
}

public long getNow() {
return now;
}

public void setNow(long now) {
this.now = now;
}

public long getStart() {
return start;
}

public void setStart(long start) {
this.start = start;
}

public long getEnd() {
return end;
}

public void setEnd(long end) {
this.end = end;
}

@Override
public String toString() {
return "Exposer{" +
"exposed=" + exposed +
", md5='" + md5 + '\'' +
", seckillId=" + seckillId +
", now=" + now +
", start=" + start +
", end=" + end +
'}';
}
}


实体类:秒杀结果类,里面存放了请求是否成功,成功则返回一个泛型T的数据,失败则返回错误信息:

/**
* Created by yliu on 18/03/28.
*/
//将所有的ajax请求返回类型,全部封装成json数据
public class SeckillResult<T> {

//请求是否成功
private boolean success;
private T data;
private String error;

public SeckillResult(boolean success, T data) {
this.success = success;
this.data = data;
}

public SeckillResult(boolean success, String error) {
this.success = success;
this.error = error;
}

public boolean isSuccess() {
return success;
}

public void setSuccess(boolean success) {
this.success = success;
}

public T getData() {
return data;
}

public void setData(T data) {
this.data = data;
}

public String getError() {
return error;
}

public void setError(String error) {
this.error = error;
}
}


RedisDao中定义了put、get redis中数据的方法:

/**
* Created by yliu on 18/3/27.
*/
public class RedisDao {
private final JedisPool jedisPool;//redis连接池

//redisDao的构造器,传入redis的地址和端口,实现连接池的初始化
public RedisDao(String ip, int port) {
jedisPool = new JedisPool(ip, port);
}

//Protostuff序列化所要传入的参数
private RuntimeSchema<Seckill> schema = RuntimeSchema.createFrom(Seckill.class);

public Seckill getSeckill(long seckillId) {
//redis操作逻辑
try {
Jedis jedis = jedisPool.getResource();
try {
String key = "seckill:" + seckillId;
//并没有实现哪部序列化操作
//采用自定义序列化 protostuff: pojo.
//通过key得到存放在redis中经过序列化后的Seckill类,经过序列化后存放的是字节类型的数据
byte[] bytes = jedis.get(key.getBytes());
//缓存重获取到
if (bytes != null) {
Seckill seckill=schema.newMessage();
ProtostuffIOUtil.mergeFrom(bytes,seckill,schema);
//反序列化得到Seckill 并返回

return seckill;
}
}finally {
//最后不要忘记关闭redis连接
jedis.close();
}
}catch (Exception e) {

}
return null;
}

public String putSeckill(Seckill seckill) {
try {
Jedis jedis = jedisPool.getResource();
try {
String key = "seckill:" + seckill.getSeckillId();
//将seckill类序列化成字节,大小为默认的大小
byte[] bytes = ProtostuffIOUtil.toByteArray(seckill, schema,
LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE));
//超时缓存
int timeout = 60 * 60;//1小时
//将序列化后的字节存放在redis中,存放时间为1小时,返回string类型的result,根据返回的结果可判断存放是否成功
String result = jedis.setex(key.getBytes(),timeout,bytes);

return result;
}finally {
jedis.close();
}
}catch (Exception e) {

}

return null;
}
}


暴露秒杀接口地址的方法,会返回一个Exposer的类型:

//加入一个混淆字符串(秒杀接口)的salt,为了我避免用户猜出我们的md5值,值任意给,越复杂越好
private final String salt="shsdssljdd'l.";
public Exposer exportSeckillUrl(long seckillId) {
//优化点:缓存优化:超时的基础上维护一致性
//1。首先访问redis,如果redis中存放了秒杀接口的数据,则直接从redis中取出,如果没有存放,则从数据库中取出数据,并存放到redis中,方便下次获取秒杀接口。

Seckill seckill = redisDao.getSeckill(seckillId);
if (seckill == null) {
//2.访问数据库
seckill = seckillDao.queryById(seckillId);
if (seckill == null) {//说明查不到这个秒杀产品的记录
//返回商品id 和false表示该商品未开启秒杀
return new Exposer(false, seckillId);
}else {
//3,放入redis
redisDao.putSeckill(seckill);
}

}

//根据返回的seckill 类中存放的秒杀开启时间判断是否开启秒杀
Date startTime=seckill.getStartTime();
Date endTime=seckill.getEndTime();
//系统当前时间
Date nowTime=new Date();
if (startTime.getTime()>nowTime.getTime() || endTime.getTime()<nowTime.getTime())
{
//系统当前时间比开始时间小,或者超过结束时间,说明没有开启秒杀,返回false及其他参数
return new Exposer(false,seckillId,nowTime.getTime(),startTime.getTime(),endTime.getTime());
}

//秒杀开启,返回秒杀商品的id、用给接口加密的md5
String md5=getMD5(seckillId);
return new Exposer(true,md5,seckillId);
}

//对秒杀商品的id进行加密,通过与salt盐值的拼接,返回复杂的md5
private String getMD5(long seckillId)
{
String base=seckillId+"/"+salt;
//调用spring中自带的md5工具
String md5= DigestUtils.md5DigestAsHex(base.getBytes());
return md5;
}


控制器controller中

@RequestMapping(value = "/{seckillId}/exposer",
method = RequestMethod.GET,
produces = {"application/json;charset=UTF-8"})
@ResponseBody
public SeckillResult<Exposer> exposer(@PathVariable("seckillId") Long seckillId)
{
//返回类型是seckillResult并设置了泛型Exposer,则在实体类中seckillResult所定义的泛型变量data是Exposer类型的数据
SeckillResult<Exposer> result;
try{
//根据秒杀商品id返回商品exposer类变量,中存放了是否开启秒杀,秒杀开启时间,md5等数据
Exposer exposer=seckillService.exportSeckillUrl(seckillId);
//在seckillResult定义了构造函数
/* public SeckillResult(boolean success, T data) {
this.success = success;
this.data = data;
} */
result=new SeckillResult<Exposer>(true,exposer);//请求成功 返回秒杀商品的信息,即是否开启秒杀等信息
}catch (Exception e)
{
//请求错误,抛出异常,并返回错误信息
e.printStackTrace();
result=new SeckillResult<Exposer>(false,e.getMessage());
}

return result;
}


在不确定返回参数类型的时候,可以使用泛型,等返回参数确定之后,再给其绑定类型。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  java redis 优化