maven 配置與安裝css
下載maven 文件
解壓以後,將文件移動到\Applications
文件夾內html
使用cd ~
進入根目錄文件夾 用ls -a
列出文件夾內的全部文件,找到一個名稱爲.bash_profile
的文件,打開該文件來配置環境變量前端
export JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk-10.0.2.jdk/Contents/Home export MAVEN_HOME=/Applications/apache-maven-3.5.4 export PATH=$PATH:$MAVEN_HOME/bin
配置完以後 添加/調整
商家 -> 庫存
<-
發貨/核帳source .bash_profile
編譯一下 而後在命令行之中用mvn - v
來檢查一下是否安裝成功java
配置環境變量
注意默認的cd ~
可能不是根目錄而是當前用戶的目錄 User\auguskong\
mysql
mvn archetype:generate -DgroupId=org.seckill -DartifactId=seckill -DarchetypeArtifactId=maven-archetype-webapp
web
修改servlet版本爲3.1redis
添加(+)/調整(+/-)
商家 -> 庫存
<-
發貨/核帳spring
付款(-)/退貨(+)
用戶 -> 庫存
<-
秒殺/預售sql
秒殺業務的核心在於庫存的處理數據庫
用戶:
【減庫存 + 記錄購買明細 】
|
【完整事務】 誰 + 時間/有效期 + 付款/發貨信息
|
【數據落地】
爲何須要事務? -> 事務機制依然是最可靠的數據落地方案
考慮一下情景:
減庫存沒有記錄購買明細?
記錄了購買明細可是沒有減庫存?
出現超賣/少買?
秒殺系統難點分析 -> 競爭
同一時間多個用戶購買
事務 + 行級鎖
Start Transaction
Update 庫存數量
Insert 購買明細
Commit
update table set num = num - 1 where id = 10 and num > 1
數據庫路徑設置
export PATH=$PATH:/usr/local/mysql/bin/
BUG!! 中英文輸入法的問號意義不同 必須使用英文的問號!!
使用 mysql -u root -p
來打開mysql面板
show create table seckill\G
查看如何建立這個表
手寫DDL
記錄每次上線的DDL修改
--上線V1.1
ALTER TABLE seckill
DROP INDEX idx_create_time,
add index idx_c_s(start_time, create_time);
TABLE -> ENTITY 表對應java類 列對應java類當中的屬性
Command + N
打開generate 可讓編譯器自動生成getter 和 setter 方法
MyBatis 映射
數據庫 -> 映射 -> 對象
爲何能夠過濾重複? 聯合主鍵是??
聯合主鍵裏面有電話號碼,由主鍵不容許重複的特性保證了不存在兩個相同的電話號碼,能夠防止一個用戶重複秒殺
4-3 基於MyBatis實現DAO理論
MyBatis, Hibernate, JDBC 都是作映射的工做 對象關係映射?
須要提供參數 + SQL(SQL必須用戶來寫)
SQL寫在哪?
1.【推薦】XML當中
2.註解提供SQL (註解自己仍是Java源碼 且SQL存在拼接時很麻煩) @SQL
Java 1.5以後提供
註解的缺點: 註解本質上也是Java源碼,須要編譯以後才能執行 複雜拼接邏輯會很複雜
如何實現DAO接口?
1.【推薦】Mapper 自動實現DAO接口 這樣能夠只關注於SQL接口,
2.API編程方式實現DAO接口
寫三個SQL
本身控制SQL
目的:
使用更少的編碼: 原則是隻寫接口,不寫實現 (實現是MyBatis自動實現)
Seckill queryById(long id)
接口參數
名字表明行爲
接口返回類型表示結果集
更少的配置:
* MyBatis 自動找到包名 減小了包名的代碼
* 自動掃描配置文件 減去配置維護成本
保存足夠的靈活性:本身定製 SQL + 自由傳參
默認
import org.junit
文件包不存在的問題
//使用@Param 註解來告知MyBatis 相關參數的名字 List<Seckill> queryAll(@Param("offset") int offet, @Param("limit") int limit);
MyBatis和Spring的整合就在於配置文件的設置
public void insertSuccessKilled() { long id = 1001L; long phone = 13421234569L; int insertCount = successKilledDao.insertSuccessKilled(id, phone); System.out.println("insertCount= " + insertCount); }
報錯空指針異常
***
ifconfig
能夠查看本地IP地址
MySQL 8.0當中設置密碼的新語法 SET PASSWORD FOR 'jeffrey'@'localhost' = 'auth_string';
按兩次shift
快速查找project當中的文件
URL is not registered
mapper裏面筆誤 parameterType 改成resultType
mybatis config文件裏面筆誤
肯定mysql的版本是和本機版本相同 進入mysql命令行以後 輸入select version()
或 直接在進入時看初始加載信息
數據庫鏈接失敗可能緣由:
GRANT
進行相關操做mysql-connector-java-5.1.6-bin.jar
由於沒有設定時區而致使了bug, 這個有點扯淡了
jdbc.url=jdbc:mysql://localhost/seckill?useSSL=false&serverTimezone=UTC&useUnicode=true&characterEncoding=utf8
DAO層編碼
編寫接口
邏輯代碼一行沒寫
有什麼好處?
DAO層演變爲接口設計 + SQL編寫
代碼和SQL的分離
DAO拼接等邏輯在Service層完成 邏輯放在Service層當中
對遠程存儲系統作操做的 放在DAO層
常量中的數據字典? 使用枚舉來表示常量數據字段??
站在使用者的角度設計接口
配置是一次性工做
不優雅的代碼
return new SeckillExecution(seckillId, 1, "秒殺成功", successKilled);
數據字典使用枚舉進行優化
Spring IOC功能理解
對象工廠: 建立對象的過程 SeckillService
依賴管理:
經過使用Spring 獲得一致性的訪問接口
爲何要用IOC:
Spring-IOC注入方式和使用場景
|XML | 註解 | Java配置類
-----| ----- | ------
舉例| MyBatis整合時 | DAO Service類中加入註解 | Java配置註解
場景| Bean 實現類來自第三方類庫時2.須要命名空間配置,如context,aop,mvc等 | 項目當中自身開發使用的類,能夠直接在代碼中使用註解 @Service@Controller | 不常見經過代碼控制對象建立邏輯: 自定義修改依賴類庫
本項目IOC使用:
Service層的依賴注入, 包掃描 + 添加註解@Autowired
//新建一個spring-service.xml文件 <!-- 掃描Service包下全部使用註解的類型 --> <context:component-scan base-package="org.seckill.service"/>
// 注入Service依賴 @Autowired //Spring一直使用的註解 自動在DAO中查找seckillDao 其它@Resource, @Inject private SeckillDao seckillDao; @Autowired private SuccessKilledDao successKilledDao;
目的:不用寫支持事務操做的代碼 交給第三方框架進行操做
流程:
ProxyFactoryBean + XML -> 早期2.0使用方式
tx: advice+aop命名空間方式 -> 一次配置永久生效
【推薦:掌握必定的控制權】註解@Transactional -> 註解控制
事務方法嵌套: 聲明式事務獨有的概念,和MySQL自己事務無關
傳播行爲默認爲propagation_required,當有一個新的事務加入進來,先判斷是不是已經存在的事務,若是已經存在直接加,不然新建一個新事物
何時回滾事務?
bug log: 配置文件的順序不對,致使沒法定位<tx:annotation-driven transaction-manager="transactionManager"/>
語句
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
logback官方文檔配置
https://logback.qos.ch/manual/configuration.html
try catch語句來改變動改集成測試的成功失敗斷定標準
//集成測試代碼 @Test public void testSeckillLogic() { long id = 1000; Exposer exposer = seckillService.exportSeckillUrl(id); if (exposer.isExposed()) { logger.info("exposer={}",exposer); long phone = 12343232331L; // 將Md5的生成和使用集合到一塊兒 String md5 = exposer.getMd5(); // 定義兩個可容許的異常: 1.重複秒殺 和 2.秒殺已關閉異常 只提出警告而不認爲屬於測試失敗 try { SeckillExecution execution = seckillService.executeSeckill(id, phone, md5); logger.info("result={}", execution); } catch (RepeatKillException e) { logger.error(e.getMessage()); } catch (SeckillCloseException e) { logger.error(e.getMessage()); } } else { // 秒殺未開啓 logger.warn("exposer={}",exposer); } }
交互設計
Restful - Swagger?
SpringMVC配置設計與實現
Bootstrap + jQuery
產品 -> 前端 -> 後端(存儲 + 展現)
前端頁面流程:
列表頁 -> 詳情頁 -> login -> 展現邏輯 ->
-> 登陸操做 ->寫入cookie ->展現邏輯
Restful接口設計:
興起於Rails,一種優雅的URI表述方式,表示一種資源的狀態和狀態轉移
幾個示例:
GET /seckill/list
WRONG: POST /seckill/execute/{seckillId}
RIGHT: POST /seckill/{seckillId}/execution
seckill
:資源的模塊 Id:表示具體的秒殺資源 execution: 執行器 執行秒殺操做 用名詞 POST表示狀態的轉移
WRONG: GET /seckill/delete/{id}
RIGHT: DELETE /seckill/{id}/delete
Restful 規範:
GET: 查詢
POST: 添加和修改 非冪等
PUT: 修改操做 冪等
DELETE: 刪除操做
URL設計
/模塊/資源/{標識符}/集合{id}/
/user/{uid}/friends
好友列表
/user/{uid}/followers
粉絲列表
秒殺API設計
GET /seckill/list
秒殺列表
GET /seckill/{id}/detail
詳情頁
GET /seckill/time/now
系統時間
POST /seckill/{id}/exposer
暴露秒殺
POST /seckill/{id}/{md5}/execution
執行秒殺
基於系統業務進行設計 能夠工程師部門進行交互直接經過URL體現 接口使用者 外部系統 用戶
圍繞Handler進行開發
Handler的產出包括:
SpringMVC運行流程:
1.用戶發送請求 -> DispatcherServlet 攔截全部請求
/seckill/list
會產生一個秒殺產品的list頁面 並交給DispatcherServletHTTP 請求先發送到 Servlet容器之中(如Tomcat, Jetty) SpringMVC有一個Mapping 用註解作映射 Annotation Handler Mapping -> 對應到一個Handler 方法之中
@RequestMapping註解:
1.支持標準的URL
2.Ant風格URL ?一個 *任意個 **任意URL路徑
3.帶{xxx}佔位符的URL
例如:
/user/*/creation
匹配 user/aaa/creation /user/bbb/creation 等
/user/{userId}
匹配 user/123
user/abc
等
/company/{companyId}/user/{userId}/detail
匹配 /company/123/user/456/detail
等
@RequestMapping(value="/{seckillId}/detail", method = RequestMethod.GET) //@PathVariable 來完成URL參數的綁定 method = ... 來實現方法的綁定 public String detail(@PathVariable("seckillId") Long seckillId, Model model) { if (seckillId == null) { //redirect 和 forward 關鍵詞表示衝頂縣仍是轉發 return "redirect:/seckill/list"; } Seckill seckill = seckillService.getById(seckillId); if (seckill == null) { return "forward:/seckill/list"; } //model關鍵字調用 model.addAttribute("seckill", seckill); //model //默認配置將"detail"字符串轉換爲一個 jsp文件 return "detail"; //view
//如何返回JSON類型文件 @RequestMapping(value = "/{seckillId}/{md5}/execution", method = RequestMethod.POST, produces = { "application/json;charset=UTF-8"}) //告訴瀏覽器這個application json類型 @ResponseBody // Response Body就是返回JSON數據 public SeckillResult<Exposer> exportSeckillURL(@PathVariable("id") long id) { SeckillResult<Exposer> result; // 將數據封裝爲json 類型 try { Exposer exposer = ... } catch (Exception e) { logger.error(e.getMessage(), e); result = new SeckillResult<Exposer>(false, e.getMessage()); } return result; }
// Cookie訪問 @RequestMapping(value = "/{seckillId}/{md5}/execution", method = RequestMethod.POST) public SeckillResult<SeckillExecution> execute(@PathVariable("seckillId") long seckillId, @PathVariable("md5") String md5, @CookieValue(value = "killPhone", required = false) Long phone) { if (phone == null) { return new SeckillResult<SeckillExecution> (false, "未註冊電話"); } SeckillExecution execution = seckillService.executeSeckillByProcedure(seckillId, phone, md5); SeckillResult<SeckillExecution> result = new SeckillResult<SeckillExecution>(true, execution); return result; }
Tomcat 服務器部署配置
https://www.jianshu.com/p/07f1f99f73ad
先下載tomcat , 再將tomcat加入到intellij編譯器之中
war模式—-將WEB工程以包的形式上傳到服務器
war exploded模式—-將WEB工程以當前文件夾的位置關係上傳到服務器
靜態包含 與 動態包含:
靜態包含: 包含的jsp文件的內容會與主文件合併
動態包含: jsp會獨立servlet, 把servlet的運行結果與主文件運行結果合併
發生在詳情頁: 最感興趣的商品
詳情頁 系統時間 地址暴露接口 執行秒殺操做
爲何要單獨獲取系統時間?
詳情頁 -> 用戶大量刷新這個頁面 -> 詳情頁部署在CDN節點上 detail頁靜態化,靜態資源 css, js , 訪問detail頁面 和 css, js資源是不須要訪問秒殺系統,因此須要一個單獨拿到
Content Distribution Network 能夠是靜態資源 也能夠是動態資源 加速用戶獲取數據的系統
通常部署在離用戶最近的網絡節點上,
命中了CDN是不須要訪問到後端服務器
互聯網公司會本身搭建或租用
獲取系統時間不須要優化: Java訪問內存10ns, 1s = 10億納秒 = 每秒能夠作1億次
秒殺地址接口是在變化就不可以再CDN緩存,適合服務器緩存 redis( 10萬/qps, 集羣化以後能夠作百萬qps)
請求地址 -> redis -> Mysql 一致性維護: 超時穿透/ 主動更新
秒殺操做優化分析: 大部分的寫操做 和 全部核心操做沒有辦法進行緩存
後端緩存困難: 庫存問題 不可能在緩存之中進行減庫存操做
1行數據競爭, 熱點商品裏面的競爭, MySQL大量減庫存競爭
其它的方案分析:
執行秒殺 -> 原子計數器 用redis/NoSQL來實現 減庫存 就是在原子計數器上減
記錄行爲信息(誰減了庫存) -> 分佈式MQ 單個MQ能夠支持幾十萬qps
後端服務消費消息並落地 -> MySQL 當中
成本過高:
運維成本和穩定性: 支持分佈式組件
開發成本:
冪等性很難保證: 重複秒殺問題 當減庫存時,不知道該用戶是否秒殺過, 通常操做是維護另外一個MQ訪問方案來
不適合新手的架構: 微信搶紅包 淘寶秒殺
運維成本和穩定性: NoSQL 和 MQ
爲何不認爲MySQL解決? 認爲MySQL真的低效麼? update 壓力測試 1s 4000次
Java控制事務行爲分析 update table set num = num - 1 WHERE id = 10 and num > 0
insert一個購買明細
UPDATE table set num = num - 1 WHERE id = 10 and num > 0
等待行級鎖 得到鎖Lock
等待行級鎖
變爲一個串行化的操做
可能存在Java客戶端等待 Java和數據庫通訊之中存在網絡延遲 全部SQL+網絡+GC 多是2ms, 1s以內500次
每一個線程完成的操做:
update減庫存 網絡延遲問題 + GC
insert 購買明細: 網絡延遲 + GC
commit/rollback
優化分析: 行級鎖在commit以後釋放
延遲問題很關鍵
Java
優化總結:
Seckill seckill = seckillDao.queryById(seckillId)
get from cache
if null
get db
else
put cache
logic code
序列化:
Java序列化對比
protostuff 建立序列化速度很高 字節數更少,
採用自定義序列化方式(開源社區方案)
portostuff-core
protostuff-runtime
public Seckill getSeckill(long seckillId) { try { Jedis jedis = jedisPool.getResource(); try { String key = "seckill:" + seckillId; byte[] bytes = jedis.get(key.getBytes()); if (bytes != null) { Seckill seckill = schema.newMessage(); ProtostuffIOUtil.mergeFrom(bytes, seckill, schema); return seckill; } } finally { jedis.close(); } } catch