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

此次对于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;
    }

在不确定返回参数类型的时候,可以使用泛型,等返回参数确定之后,再给其绑定类型。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值