02秒殺商品存入緩存

2.秒殺商品存入緩存

在這裏插入圖片描述

秒殺商品由B端存入Mysql,設置定時任務,每隔一段時間就從Mysql中將符合條件的數據從Mysql中查詢出來並存入緩存中,redis以Hash類型進行數據存儲。

2.1 秒殺服務搭建

1)新建服務changgou_service_seckill
2)添加依賴信息,詳情如下:

 <dependencies>
        <dependency>
            <groupId>com.changgou</groupId>
            <artifactId>changgou_common_db</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>com.changgou</groupId>
            <artifactId>changgou_service_order_api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>com.changgou</groupId>
            <artifactId>changgou_service_seckill_api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>com.changgou</groupId>
            <artifactId>changgou_service_goods_api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.amqp</groupId>
            <artifactId>spring-rabbit</artifactId>
        </dependency>
        <!--oauth依賴-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
        </dependency>
    </dependencies>
  1. 添加啓動類
package com.changgou.seckill;

import com.changgou.util.IdWorker;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericToStringSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.scheduling.annotation.EnableScheduling;
import tk.mybatis.spring.annotation.MapperScan;

@SpringBootApplication
@EnableEurekaClient
@MapperScan(basePackages = {"com.changgou.seckill.dao"})
@EnableScheduling
public class SecKillApplication {

    public static void main(String[] args) {
        SpringApplication.run(SecKillApplication.class,args);
    }

    //idwork
    @Bean
    public IdWorker idWorker(){
        return new IdWorker(1,1);
    }

    //設置redistemplate的序列化
    @Bean
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        // 1.創建 redisTemplate 模版
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        // 2.關聯 redisConnectionFactory
        template.setConnectionFactory(redisConnectionFactory);
        // 3.創建 序列化類
        GenericToStringSerializer genericToStringSerializer = new GenericToStringSerializer(Object.class);
        // 6.序列化類,對象映射設置
        // 7.設置 value 的轉化格式和 key 的轉化格式
        template.setValueSerializer(genericToStringSerializer);
        template.setKeySerializer(new StringRedisSerializer());
        template.afterPropertiesSet();
        return template;
    }
}

  1. 添加application.yml
server:
  port: 9016
spring:
  jackson:
    time-zone: GMT+8
  application:
    name: seckill
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://192.168.200.128:3306/changgou_seckill?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowMultiQueries=true&serverTimezone=GMT%2b8
    username: root
    password: root
  main:
    allow-bean-definition-overriding: true #當遇到同樣名字的時候,是否允許覆蓋註冊
  redis:
    host: 192.168.200.128
  rabbitmq:
    host: 192.168.200.128
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:6868/eureka
  instance:
    prefer-ip-address: true
feign:
  hystrix:
    enabled: true
  client:
    config:
      default:   #配置全局的feign的調用超時時間  如果 有指定的服務配置 默認的配置不會生效
        connectTimeout: 60000 # 指定的是 消費者 連接服務提供者的連接超時時間 是否能連接  單位是毫秒
        readTimeout: 20000  # 指定的是調用服務提供者的 服務 的超時時間()  單位是毫秒
#hystrix 配置
hystrix:
  command:
    default:
      execution:
        timeout:
          #如果enabled設置爲false,則請求超時交給ribbon控制
          enabled: true
        isolation:
          strategy: SEMAPHORE
          thread:
            # 熔斷器超時時間,默認:1000/毫秒
            timeoutInMilliseconds: 20000
  1. 添加公鑰

在這裏插入圖片描述

  1. 添加Oauth配置類
package com.changgou.seckill.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.stream.Collectors;

@Configuration
@EnableResourceServer
//開啓方法上的PreAuthorize註解
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

    //公鑰
    private static final String PUBLIC_KEY = "public.key";

    /***
     * 定義JwtTokenStore
     * @param jwtAccessTokenConverter
     * @return
     */
    @Bean
    public TokenStore tokenStore(JwtAccessTokenConverter jwtAccessTokenConverter) {
        return new JwtTokenStore(jwtAccessTokenConverter);
    }

    /***
     * 定義JJwtAccessTokenConverter
     * @return
     */
    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setVerifierKey(getPubKey());
        return converter;
    }
    /**
     * 獲取非對稱加密公鑰 Key
     * @return 公鑰 Key
     */
    private String getPubKey() {
        Resource resource = new ClassPathResource(PUBLIC_KEY);
        try {
            InputStreamReader inputStreamReader = new InputStreamReader(resource.getInputStream());
            BufferedReader br = new BufferedReader(inputStreamReader);
            return br.lines().collect(Collectors.joining("\n"));
        } catch (IOException ioe) {
            return null;
        }
    }

    /***
     * Http安全配置,對每個到達系統的http請求鏈接進行校驗
     * @param http
     * @throws Exception
     */
    @Override
    public void configure(HttpSecurity http) throws Exception {
        //所有請求必須認證通過
        http.authorizeRequests()
                .antMatchers("/seckillgoods/list/**")
                .permitAll()
                .anyRequest()
                .authenticated();    //其他地址需要認證授權
    }
}

  1. 更改網關路徑過濾類,添加秒殺工程過濾信息
    在這裏插入圖片描述

  2. 更改網關配置文件,添加請求路由轉發

#秒殺微服務
		- id: changgou_seckill_route
		  uri: lb://seckill
		  predicates:
		    - Path=/api/seckill/**
		  filters:
		    - StripPrefix=1

2.2 時間操作

2.2.1 秒殺商品時間段分析

在這裏插入圖片描述

在這裏插入圖片描述

根據產品原型圖結合秒殺商品表設計可以得知,秒殺商品是存在開始時間與結束時間的,當前秒殺商品是按照秒殺時間段進行顯示,如果當前時間在符合條件的時間段範圍之內,則用戶可以秒殺購買當前時間段之內的秒殺商品。
緩存數據加載思路:定義定時任務,每天凌晨會進行當天所有時間段秒殺商品預加載。並且在B端進行限制,添加秒殺商品的話,只能添加當前日期+1的時間限制,比如說:當前日期爲8月5日,則添加秒殺商品時,開始時間必須爲6日的某一個時間段,否則不能添加。

2.2.2 秒殺商品時間段計算

將 資源/DateUtil.java 添加到公共服務中。基於當前工具類可以進行時間段的計算

package com.changgou.util;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;

public class DateUtil {

    /***
     * 從yyyy-MM-dd HH:mm格式轉成yyyyMMddHH格式
     * @param dateStr
     * @return
     */
    public static String formatStr(String dateStr){
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm");
        try {
            Date date = simpleDateFormat.parse(dateStr);
            simpleDateFormat = new SimpleDateFormat("yyyyMMddHH");
            return simpleDateFormat.format(date);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return null;
    }

    /***
     * 獲取指定日期的凌晨
     * @return
     */
    public static Date toDayStartHour(Date date){
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        calendar.set(Calendar.HOUR_OF_DAY, 0);
        calendar.set(Calendar.MINUTE, 0);
        calendar.set(Calendar.SECOND, 0);
        calendar.set(Calendar.MILLISECOND, 0);
        Date start = calendar.getTime();
        return start;
    }


    /***
     * 時間增加N分鐘
     * @param date
     * @param minutes
     * @return
     */
    public static Date addDateMinutes(Date date,int minutes){
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        calendar.add(Calendar.MINUTE, minutes);// 24小時制
        date = calendar.getTime();
        return date;
    }

    /***
     * 時間遞增N小時
     * @param hour
     * @return
     */
    public static Date addDateHour(Date date,int hour){
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        calendar.add(Calendar.HOUR, hour);// 24小時制
        date = calendar.getTime();
        return date;
    }

    /***
     * 獲取時間菜單
     * @return
     */
    public static List<Date> getDateMenus(){
      
        //定義一個List<Date>集合,存儲所有時間段
        List<Date> dates = new ArrayList<Date>();
        
        //循環12次
        Date date = toDayStartHour(new Date()); //凌晨
        for (int i = 0; i <12 ; i++) {
            //每次遞增2小時,將每次遞增的時間存入到List<Date>集合中
            dates.add(addDateHour(date,i*2));
        }

        //判斷當前時間屬於哪個時間範圍
        Date now = new Date();
        for (Date cdate : dates) {
            //開始時間<=當前時間<開始時間+2小時
            if(cdate.getTime()<=now.getTime() && now.getTime()<addDateHour(cdate,2).getTime()){
                now = cdate;
                break;
            }
        }

        //當前需要顯示的時間菜單
        List<Date> dateMenus = new ArrayList<Date>();
        for (int i = 0; i <5 ; i++) {
            dateMenus.add(addDateHour(now,i*2));
        }
        return dateMenus;
    }

    /***
     * 時間轉成yyyyMMddHH
     * @param date
     * @return
     */
    public static String date2Str(Date date){
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMddHH");
        return simpleDateFormat.format(date);
    }

    public static void main(String[] args) {

        //存儲數據結果
        List<Date> dateList = new ArrayList<>();

        //獲取到本日的凌晨時間點
        Date startHour = toDayStartHour(new Date());

        //循環12次
        for(int i=0;i<12;i++){
            dateList.add(addDateHour(startHour,i*2));
        }

        for (Date date : dateList) {
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            String format = simpleDateFormat.format(date);
            System.out.println(format);
        }
    }
}

2.2.3 當前業務整體流程分析

1.查詢所有符合條件的秒殺商品
1) 獲取時間段集合並循環遍歷出每一個時間段
2) 獲取每一個時間段名稱,用於後續redis中key的設置
3) 狀態必須爲審覈通過 status=1
4) 商品庫存個數>0
5) 秒殺商品開始時間>=當前時間段
6) 秒殺商品結束<當前時間段+2小時
7) 排除之前已經加載到Redis緩存中的商品數據
8) 執行查詢獲取對應的結果集
2.將秒殺商品存入緩存

2.3 代碼實現

2.3.2 更改啓動類,添加開啓定時任務註解

2.3.3 定義定時任務類

秒殺工程新建task包,並新建任務類SeckillGoodsPushTask
業務邏輯:
1)獲取秒殺時間段菜單信息
2)遍歷每一個時間段,添加該時間段下秒殺商品
2.1)將當前時間段轉換爲String,作爲redis中的key
2.2)查詢商品信息(狀態爲1,庫存大於0,秒殺商品開始時間大於當前時間段,秒殺商品結束時間小於當前時間段,當前商品的id不在redis中)
3)添加redis

package com.changgou.seckill.task;

import com.changgou.seckill.dao.SeckillGoodsMapper;
import com.changgou.seckill.pojo.SeckillGoods;
import com.changgou.util.DateUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import tk.mybatis.mapper.entity.Example;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Set;

@Component
public class SeckillGoodsPushTask {

    @Autowired
    private SeckillGoodsMapper seckillGoodsMapper;

    @Autowired
    private RedisTemplate redisTemplate;

    public static final String SECKILL_GOODS_KEY="seckill_goods_";

    @Scheduled(cron = "0/30 * * * * ?")
    public void  loadSecKillGoodsToRedis(){
        /**
         * 1.查詢所有符合條件的秒殺商品
         * 	1) 獲取時間段集合並循環遍歷出每一個時間段
         * 	2) 獲取每一個時間段名稱,用於後續redis中key的設置
         * 	3) 狀態必須爲審覈通過 status=1
         * 	4) 商品庫存個數>0
         * 	5) 秒殺商品開始時間>=當前時間段
         * 	6) 秒殺商品結束<當前時間段+2小時
         * 	7) 排除之前已經加載到Redis緩存中的商品數據
         * 	8) 執行查詢獲取對應的結果集
         * 2.將秒殺商品存入緩存
         */

        List<Date> dateMenus = DateUtil.getDateMenus(); // 5個

        for (Date dateMenu : dateMenus) {
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

            String redisExtName = DateUtil.date2Str(dateMenu);

            Example example = new Example(SeckillGoods.class);
            Example.Criteria criteria = example.createCriteria();

            criteria.andEqualTo("status","1");
            criteria.andGreaterThan("stockCount",0);
            criteria.andGreaterThanOrEqualTo("startTime",simpleDateFormat.format(dateMenu));
            criteria.andLessThan("endTime",simpleDateFormat.format(DateUtil.addDateHour(dateMenu,2)));

            Set keys = redisTemplate.boundHashOps(SECKILL_GOODS_KEY + redisExtName).keys();//key field value

            if (keys != null && keys.size()>0){
                criteria.andNotIn("id",keys);
            }

            List<SeckillGoods> seckillGoodsList = seckillGoodsMapper.selectByExample(example);

            //添加到緩存中
            for (SeckillGoods seckillGoods : seckillGoodsList) {
                redisTemplate.opsForHash().put(SECKILL_GOODS_KEY + redisExtName,seckillGoods.getId(),seckillGoods);
            }
        }

    }
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章