搶號源碼
❶ 網上商城源碼
網上商城系統開發主要以J2EE技術為主,該語言具有功能強大和簡單易用兩個特徵,同時開發人員也可以運用許多不同的框架來創建web項目。例如醫療救護、保險、教育、國防以及其他的不同部門網站都是以java為基礎來開發的,也有採用第三方系統,像shop++、javashop等等使用廣泛。
自定服務品牌,獨立經營
商城系統運營者可以自定品牌、自主開通商店、對每個商店進行管理和授權,完全獨立經營!
每個商店相對獨立,互不幹擾,安全性好
通過商城系統開通的每一個商店都使用獨立的資料庫和獨立的目錄,可以解析獨立域名。每個商店都有獨立的管理許可權控制體系,數據保密性強。必要時還可購買獨立版商店,遷出數據到商家自己的主機空間。
商店模版豐富、功能強大、通用性強,適合建立各種商店
商店系統使用的是改進後的網上商店系統v4.3版本,功能強大。有24套商店模版可選擇。商店系統對不同的商品特性可以使用不同的訂單項目和流程,並可指定不同的商品屬性類型,因此適合建立各種類型的商店,用戶群很廣。
完備的使用、開通和控制體系
商城系統運營者可以設置默認的試用時間、試用時是否可直接訪問、試用時空間限制、向商城發布商品時是否審核等試用參數;可以對每一個商店設置到期時間、空間大小、許可向商城發布商品數量等參數,用來制定多種建店套餐。使用在線支付還可以實時開通商店。
商城主站內容豐富、功能強大、交互性強
商城主站不僅具備管理下屬商店和推薦商品的功能,同時也具備多套文章系統、下載系統、商務社區等內容管理功能和商店購物的全部功能,也可以直接銷售自備商品。另外,商城系統主站特別加強了文章、商品欄目的內容交互功能,使商城主站更具大站風范。
去中心化越來越明顯
隨著移動互聯網的飛躍式發展,信息壟斷與信息不對稱將會被徹底打破,移動互聯網出現了越來越明顯的去中心化趨勢,變得多元化、扁平化,真正走向了用戶時代。
優秀網店系統的八大特徵:
1、SEO優化: 對於不是很懂網路推廣和SEO的朋友來說,有些的網店系統需要有推廣入口和網站自帶的SEO設置,包括:各類搜索引擎、分類信息網站、導航網站等,這樣才能快速有效的將網店推向市場。
2、整合營銷: 營銷手段包括郵件、簡訊等,方式從團購到限時搶購不等,快速引發用戶購物慾望。
3、豐富模板: 豐富模板,可以進行可視化編輯,滿足圖片廣告、網站公告、分類展示商品等各種需求。
4、淘寶對接: 這個是非常重要的一項功能,優秀的網店系統可以和淘寶網店進行對接,同步管理獨立網店和淘寶店鋪,使商品、訂單、庫存等各類信息得到同步。
5、支付方式: 很實際的一項需求,以滿足各類不同用戶,包括支付寶、財付通、網銀、貨到付款等主流方式。
6、快捷登錄: 注冊帳號什麼的最麻煩了,所以優秀的網店系統還必須還有快捷登錄功能:QQ、淘寶、微博、人人網等等。
7、會員營銷: 針對不同消費者發放不同積分,不同等級採用不同郵件、簡訊進行營銷,還能實現預存款等更優質功能。
8、數據報表: 運營統計數據是我們了解網站運營情況的基石。網店系統需要提供UV、PV、銷售額、銷量等運營數據,我們根據這些數據分析網站、發現問題、改變策略、不斷優化。
基本具備以上特徵的便是優秀的網店系統,這樣才能滿足我們日趨多樣的需求,豐富我們我們的網站,最終支持網站獲取良好的銷售業績。
適合用戶群體: 有意向建立和已經在網上建立網店系統的企業和個人。
❷ 求五子棋C源代碼
這個是稍微好一點的了,以前沒事試過
/*
五子棋
*/
#include<stdio.h>
#include<stdlib.h>
#include<graphics.h>
#include<bios.h>
#include<conio.h>
#define LEFT 0x4b00
#define RIGHT 0x4d00
#define DOWN 0x5000
#define UP 0x4800
#define ESC 0x011b
#define SPACE 0x3920
#define BILI 20
#define JZ 4
#define JS 3
#define N 19
int box[N][N];
int step_x,step_y ;
int key ;
int flag=1 ;
void draw_box();
void draw_cicle(int x,int y,int color);
void change();
void judgewho(int x,int y);
void judgekey();
int judgeresult(int x,int y);
void attentoin();
void attention()
{
char ch ;
window(1,1,80,25);
textbackground(LIGHTBLUE);
textcolor(YELLOW);
clrscr();
gotoxy(15,2);
printf("游戲操作規則:");
gotoxy(15,4);
printf("Play Rules:");
gotoxy(15,6);
printf("1、按左右上下方向鍵移動棋子");
gotoxy(15,8);
printf("1. Press Left,Right,Up,Down Key to move Piece");
gotoxy(15,10);
printf("2、按空格確定落棋子");
gotoxy(15,12);
printf("2. Press Space to place the Piece");
gotoxy(15,14);
printf("3、禁止在棋盤外按空格");
gotoxy(15,16);
printf("3. DO NOT press Space outside of the chessboard");
gotoxy(15,18);
printf("你是否接受上述的游戲規則(Y/N)");
gotoxy(15,20);
printf("Do you accept the above Playing Rules? [Y/N]:");
while(1)
{
gotoxy(60,20);
ch=getche();
if(ch=='Y'||ch=='y')
break ;
else if(ch=='N'||ch=='n')
{
window(1,1,80,25);
textbackground(BLACK);
textcolor(LIGHTGRAY);
clrscr();
exit(0);
}
gotoxy(51,12);
printf(" ");
}
}
void draw_box()
{
int x1,x2,y1,y2 ;
setbkcolor(LIGHTBLUE);
setcolor(YELLOW);
gotoxy(7,2);
printf("Left, Right, Up, Down KEY to move, Space to put, ESC-quit.");
for(x1=1,y1=1,y2=18;x1<=18;x1++)
line((x1+JZ)*BILI,(y1+JS)*BILI,(x1+JZ)*BILI,(y2+JS)*BILI);
for(x1=1,y1=1,x2=18;y1<=18;y1++)
line((x1+JZ)*BILI,(y1+JS)*BILI,(x2+JZ)*BILI,(y1+JS)*BILI);
for(x1=1;x1<=18;x1++)
for(y1=1;y1<=18;y1++)
box[x1][y1]=0 ;
}
void draw_circle(int x,int y,int color)
{
setcolor(color);
setlinestyle(SOLID_LINE,0,1);
x=(x+JZ)*BILI ;
y=(y+JS)*BILI ;
circle(x,y,8);
}
void judgekey()
{
int i ;
int j ;
switch(key)
{
case LEFT :
if(step_x-1<0)
break ;
else
{
for(i=step_x-1,j=step_y;i>=1;i--)
if(box[i][j]==0)
{
draw_circle(step_x,step_y,LIGHTBLUE);
break ;
}
if(i<1)break ;
step_x=i ;
judgewho(step_x,step_y);
break ;
}
case RIGHT :
if(step_x+1>18)
break ;
else
{
for(i=step_x+1,j=step_y;i<=18;i++)
if(box[i][j]==0)
{
draw_circle(step_x,step_y,LIGHTBLUE);
break ;
}
if(i>18)break ;
step_x=i ;
judgewho(step_x,step_y);
break ;
}
case DOWN :
if((step_y+1)>18)
break ;
else
{
for(i=step_x,j=step_y+1;j<=18;j++)
if(box[i][j]==0)
{
draw_circle(step_x,step_y,LIGHTBLUE);
break ;
}
if(j>18)break ;
step_y=j ;
judgewho(step_x,step_y);
break ;
}
case UP :
if((step_y-1)<0)
break ;
else
{
for(i=step_x,j=step_y-1;j>=1;j--)
if(box[i][j]==0)
{
draw_circle(step_x,step_y,LIGHTBLUE);
break ;
}
if(j<1)break ;
step_y=j ;
judgewho(step_x,step_y);
break ;
}
case ESC :
break ;
case SPACE :
if(step_x>=1&&step_x<=18&&step_y>=1&&step_y<=18)
{
if(box[step_x][step_y]==0)
{
box[step_x][step_y]=flag ;
if(judgeresult(step_x,step_y)==1)
{
sound(1000);
delay(1000);
nosound();
gotoxy(30,4);
if(flag==1)
{
setbkcolor(BLUE);
cleardevice();
setviewport(100,100,540,380,1);
/*定義一個圖形窗口*/
setfillstyle(1,2);
/*綠色以實填充*/
setcolor(YELLOW);
rectangle(0,0,439,279);
floodfill(50,50,14);
setcolor(12);
settextstyle(1,0,5);
/*三重筆劃字體, 水平放?5倍*/
outtextxy(20,20,"The White Win !");
setcolor(15);
settextstyle(3,0,5);
/*無襯筆劃字體, 水平放大5倍*/
outtextxy(120,120,"The White Win !");
setcolor(14);
settextstyle(2,0,8);
getch();
closegraph();
exit(0);
}
if(flag==2)
{
setbkcolor(BLUE);
cleardevice();
setviewport(100,100,540,380,1);
/*定義一個圖形窗口*/
setfillstyle(1,2);
/*綠色以實填充*/
setcolor(YELLOW);
rectangle(0,0,439,279);
floodfill(50,50,14);
setcolor(12);
settextstyle(1,0,8);
/*三重筆劃字體, 水平放大8倍*/
outtextxy(20,20,"The Red Win !");
setcolor(15);
settextstyle(3,0,5);
/*無襯筆劃字體, 水平放大5倍*/
outtextxy(120,120,"The Red Win !");
setcolor(14);
settextstyle(2,0,8);
getch();
closegraph();
exit(0);
}
}
change();
break ;
}
}
else
break ;
}
}
void change()
{
if(flag==1)
flag=2 ;
else
flag=1 ;
}
void judgewho(int x,int y)
{
if(flag==1)
draw_circle(x,y,15);
if(flag==2)
draw_circle(x,y,4);
}
int judgeresult(int x,int y)
{
int j,k,n1,n2 ;
while(1)
{
n1=0 ;
n2=0 ;
/*水平向左數*/
for(j=x,k=y;j>=1;j--)
{
if(box[j][k]==flag)
n1++;
else
break ;
}
/*水平向右數*/
for(j=x,k=y;j<=18;j++)
{
if(box[j][k]==flag)
n2++;
else
break ;
}
if(n1+n2-1>=5)
{
return(1);
break ;
}
/*垂直向上數*/
n1=0 ;
n2=0 ;
for(j=x,k=y;k>=1;k--)
{
if(box[j][k]==flag)
n1++;
else
break ;
}
/*垂直向下數*/
for(j=x,k=y;k<=18;k++)
{
if(box[j][k]==flag)
n2++;
else
break ;
}
if(n1+n2-1>=5)
{
return(1);
break ;
}
/*向左上方數*/
n1=0 ;
n2=0 ;
for(j=x,k=y;j>=1,k>=1;j--,k--)
{
if(box[j][k]==flag)
n1++;
else
break ;
}
/*向右下方數*/
for(j=x,k=y;j<=18,k<=18;j++,k++)
{
if(box[j][k]==flag)
n2++;
else
break ;
}
if(n1+n2-1>=5)
{
return(1);
break ;
}
/*向右上方數*/
n1=0 ;
n2=0 ;
for(j=x,k=y;j<=18,k>=1;j++,k--)
{
if(box[j][k]==flag)
n1++;
else
break ;
}
/*向左下方數*/
for(j=x,k=y;j>=1,k<=18;j--,k++)
{
if(box[j][k]==flag)
n2++;
else
break ;
}
if(n1+n2-1>=5)
{
return(1);
break ;
}
return(0);
break ;
}
}
void main()
{
int gdriver=VGA,gmode=VGAHI;
clrscr();
attention();
initgraph(&gdriver,&gmode,"c:\\tc");
/* setwritemode(XOR_PUT);*/
flag=1 ;
draw_box();
do
{
step_x=0 ;
step_y=0 ;
/*draw_circle(step_x,step_y,8); */
judgewho(step_x-1,step_y-1);
do
{
while(bioskey(1)==0);
key=bioskey(0);
judgekey();
}
while(key!=SPACE&&key!=ESC);
}
while(key!=ESC);
closegraph();
}
❸ 怎麼用易語言做一個搶購軟體,最好能給我個源碼或者思路
源碼不可能有人給你。但是可以給你思路。
第一步,模擬登錄,去**網站點擊登錄 游覽器右鍵查看源代碼 或者審核元素 推薦審核元素。
獲取到登錄的form表單信息。提交一次。F12開發者工具監聽post的地址和參數 。獲取返回的信息和存儲cookie。這樣第一步模擬登錄完成。
第二步,游覽器右鍵查看源代碼 或者審核元素 推薦審核元素。找到關鍵參數 http讀文件獲取到參數的信息。點擊搶購。F12開發者工具監聽post的地址和參數 。獲取返回的信息。
這樣 你需要的搶購軟體就基本成形了 。
第三步,完善軟體 啟動多線程搶購模式。
到此軟體完成
❹ XXL admin 源碼解析
xxl-job 的 admin 服務是 xxl-job 的調度中心,負責管理和調度注冊的 job,關於 xxl-job 的使用,可以閱讀 「參考閱讀」 中的《XXL-JOB分布式調度框架全面詳解》,這里主要是介紹 admin 中的源碼。
admin 服務除了管理頁面上的一些介面外,還有一些核心功能,比如:
1、根據 job 的配置,自動調度 job;
2、接收 executor 實例的請求,實現注冊和下線;
3、監視失敗的 job,進行重試;
4、結束一些異常的 job;
5、清理和統計日誌;
這些功能都是在 admin 服務啟動後,在後台自動運行的,下面將詳細介紹 admin 服務這些功能敏罩的實現。
XxlJobAdminConfig 是 admin 服務的配置類,在 admin 服務啟動時,它除了配置 admin 服務的一些參數外,還會啟動 admin 服務的所有後台線程。
該類的屬性主要分為5類:
1、配置文件中的參數,比如 accessToken;
2、DAO 層各個數據表的 mapper;
3、Spring 容器中的一些 Bean,比如 JobAlarmer、DataSource 等;
4、私有變數 XxlJobScheler 對象;
5、私有靜態變數 adminConfig,指向實例自身。
該類有兩個重要方法,分別實現自介面 InitializingBean、DisposableBean,作用如下:
這兩個方法分別調用了 XxlJobScheler 對象的 init 、 destroy 方法,源碼如下:
XxlJobAdminConfig 作為 admin 服務的配置類,作用就是在 Spring 容器啟動時,調用 XxlJobScheler 的初始化方法,來初始化和啟動 admin 服務的功能。
XxlJobScheler 的作用就是調用各個輔助類(xxxHelper)來啟動和結束不同的線程和功能,初始化方法 init 的代碼如下:
下面我們主要介紹 init 中各個類及其作用,最後再簡單一下介紹 destroy 的作用纖拿森。
當 admin 服務向 executor 實例發出一個調度請求來執行 job 時,會調用 XxlJobTrigger.trigger() 方法把要傳輸的參數(比如 job_id、jobHandler、job_log_id、阻塞策略等,包裝成 TriggerParam 對象)傳給 ExecutorBiz 對象來執行一次調度。
xxl-job 對調度過程做了兩個優化:
JobTriggerPoolHelper 在 toStart 方法中初始化了它的兩個線程池屬性,代碼如下:
每次有調度請求時,就會在這兩個線程池中創建線程,創建線程的邏輯在 addTrigger 方法中。
不同 job 存在執行時長的差異,為了避免不同耗時 job 之間相互阻塞,xxl-job 根據 job 的響應時間,對 job 進行了區分,主要體現在:
如果快 job 與調用頻繁的慢 job 在同一個線程池中創建線程,慢 job 會佔用大量的線程,導致快 job 線程不能及時運行,降低了線程池和線程的利用率。xxl-job 通過快慢隔離,避免了這個問題。
不能,因為慢 job 還是會佔用大量線程,搶佔了快 job 的線程資源;增加線程池中的線程數不但沒有提升利用率,還會導致大量線程看空閑,利用率反而降低了。最好的方法還是用兩個線程池把兩者隔離,可以合理地使用各自線程池的資源。
為了記錄慢 job 的超時次毀畝數,代碼中使用一個 map(變數 jobTimeoutCountMap )來記錄一分鍾內 job 超時次數,key 值是 job_id,value 是超時次數。在調用 XxlJobTrigger.trigger() 方法之前,會先判斷 map 中,該 job_id 的超時次數是否大於 10,如果大於10,就是使用 slowTriggerPool,代碼如下:
調用 XxlJobTrigger.trigger() 方法後,根據兩個值來更新 jobTimeoutCountMap 的值:
和上面的代碼相結合,一個 job 在一分鍾內有10次調用超過 500 毫秒,就認為該 job 是一個 頻繁調度且耗時的 job。
代碼如下:
在該類中,屬性變數 minTim 和 jobTimeoutCountMap 都使用 volatile 來修飾,保證了並發調用 addTrigger 時數據的一致性和可見性。
admin 服務發起 job 調度請求時,是在靜態方法 public static void trigger() 中調用靜態變數 private static JobTriggerPoolHelper helper 的 addTrigger 方法來發起請求的。minTim 和 jobTimeoutCountMap 雖然不是 static 修飾的,但可以看做是全局唯一的(因為持有它們的對象是全局唯一的),因此這兩個參數維護的是 admin 服務全局的調度時間和超時次數,為了避免記錄的數據量過大,需要每分鍾清空一次數據的操作。
admin 服務提供了介面給 executor 來注冊和下線,另外,當 executor 長時間(90秒)沒有發心跳時,要把 executor 自動下線。前一個功能通過暴露一個介面來接收請求,後一個功能需要開啟一個線程,定時更新過期 executor 的狀態。
xxl-job 為了提升 admin 服務的性能,在前一個功能的介面接收到 executor 的請求時,不是同步執行,而是在線程池中開啟一個線程,非同步執行 executor 的注冊和下線請求。
JobRegistryHelper 類就負責管理這個線程池和定時線程的。
線程池的定義和初始化代碼如下:
executor 實例在發起注冊和下線請求時,會調用 AdminBizImpl 類的對應方法,該類的方法如下:
可以看到,AdminBizImpl 類的兩個方法都是調用了 JobRegistryHelper 方法來實現,其中 JobRegistryHelper.registry 方法代碼如下(registryRemove 代碼與之相似):
這兩個方法是通過在線程池 registryOrRemoveThreadPool 中創建線程來非同步執行請求,然後把數據更新或新建到數據表 xxl_job_registry 中。
當 executor 注冊到 admin 服務後(數據入庫到 xxl_job_registry 表),是不會在頁面上顯示的,需要要用戶手動添加 job_group 數據(添加到 xxl_job_group 表),admin 服務會自動把用戶添加的 job_group 數據與 xxl_job_registry 數據關聯。這就需要 admin 定時從 xxl_job_group 表讀取數據,關聯 xxl_job_registry 表和 xxl_job_group 表的數據。
這個功能是與 「executor 自動下線」 功能在同一個線程中實現,該線程的主要邏輯是:
相關代碼如下:
從這里可以看出,如果是對外介面(接收請求等)的功能,使用線程池和非同步線程來實現;如果是一些自動任務,則是通過一個線程來定時執行。
如果一個 Job 調度後,沒有響應返回,需要定時重試。作為一種「自動執行」的任務,很顯然可以像前面 JobRegistryHelper 一樣,使用一個線程定時重試。
在這個類中,定義了一個監視線程,以每10 秒一次的頻率運行,對失敗的 job 進行重試。如果 job 剩餘的重試次數大於0,就會 job 進行重試,並把發送告警信息。線程的定義如下:
在這個線程中,它利用 「資料庫執行 UPDATE 語句時會加上互斥鎖」 的特性,使用了 「基於資料庫的分布式鎖」,代碼如下所示:
在這個語句中,會把 jobLog 的狀態設置為 -1,這是一個無效狀態值,當其他線程通過有效狀態值來搜索失敗記錄時,會略過該記錄,這樣該記錄就不會被其他線程重試,達到的分布式鎖的功能(這個鎖是一個行鎖)。或者說,-1狀態類似於 java 中的對象頭的鎖標志位,表明該記錄已經被加鎖了,其他線程會「忽略」該記錄。
在 try 代碼塊中加鎖和解鎖,如果加鎖後重試時拋出異常,會導致該記錄永遠無法解鎖。所以,應該在 finnally 塊中執行解鎖操作,或者使用 redis 給鎖加一個過期時間來實現分布式鎖。
從失敗的日誌中取出 jobId,查詢出對應的 jobInfo 數據,如果日誌中的剩餘重試次數大於 0,就執行重試。代碼如下:
調度任務使用的就是前面介紹的 JobTriggerPoolHelper.trigger 方法,最後更新 jobLog 的 alarm_status 值,有兩個作用:
這個類與 JobRegistryHelper 類似,都有一個線程池、一個線程,通過前面 JobRegistryHelper 的學習,可以大膽猜測:
實際上,該類中線程池和線程的作用就是用來 「完成」 一個 job。
當 executor 接收到 admin 的調度請求後,會非同步執行 job,並立刻返回一個回調。
admin 接受到回調後,和前面的 「注冊、下線」 一樣,在線程池中創建線程來處理回調,主要是更新 job 和日誌。
當有回調請求時, public callback 方法(該方法被 AdminBizImpl 調用)會在線程池中創建一個線程,遍歷回調請求的參數列表,依次處理回調參數,代碼如下:
從代碼可以看出,最後調用 XxlJobCompleter.updateHandleInfoAndFinish 方法完成回調邏輯。
如果一個 job 較長時間前被調度,但是一直處於 「運行中」 且它所屬的 executor 已經超過 90 秒沒有心跳了,那麼可以認為該 job 已經丟失了,需要把該 job 結束掉。這個就是線程 monitorThread 的主要功能。
monitorThread 會以 60秒 一次的頻率,從 xxl_job_log 表中找出 10分鍾前調度、仍處於」運行中「狀態、executor 已經下線 的 job,然後調用 XxlJobCompleter.updateHandleInfoAndFinish 來更新 handler 的信息和結束 job,代碼如下:
從代碼可以看出,上面的兩個功能最後都調用了 XxlJobCompleter.updateHandleInfoAndFinish 方法,關於該方法的介紹,可以看後面 XxlJobCompleter 部分的介紹,這里不詳細展開。
如果去看 XxlJobTrigger.triger 方法,會發現每次調度 job 時,都會先新增一個 jobLog 記錄,這也是為什麼 JobFailMonitorHelper 中的線程在重試時,先查詢 jobLog 的原因。
JobLog 作為 job 的調度記錄,還可以用來統計一段時間內 job 的調度次數、成功數等;另外,會清理超出有效期(配置的參數 logretentiondays )的日誌,避免日誌數據過大。很顯然,這又是一個 」自動任務「,可以使用一個線程定時完成。
該類持有一個線程變數,線程以 每分鍾一次的頻率,執行兩個操作:
在線程 run 方法的前半部分,線程會統計 3 天內,每天的調度次數、運行次數、成功運行數、失敗次數;然後更新或新增 xxl_job_log_report 表的數據。
在線程 run 方法的後半部分,線程按天對日誌進行清理,如果當前時間與上次清理的時間相隔超過一天,就會清理日誌記錄,代碼如下:
如果不使用參數 lastCleanLogTime 來記錄上次清理的時間,只是清理一天前創建的數據記錄。那麼該線程每分鍾執行一次時,都會刪除前天當前時刻的數據,導致前一年的數不完整。
使用參數 lastCleanLogTime 來記錄上次清理的時間,並且與當前時間相差超過一天時才清理,能保證前一天的日誌是完整的。
不明白為什麼清理日誌時,不是一次性刪除全部的過期日誌,而是每次刪除 1000條。按理說,這些舊的日誌數據應該已經不在 buffer pool 中了,trigger_time 欄位又是普通索引,那麼 DELETE 操作會先更新到 change buffer 中,之後再合並。現在先查詢再刪除,相當於多了一次 IO 且沒有使用到 change buffer。
admin 服務是用來管理和調度 job 的,用戶也可以在它的管理後台新建一個 job,配置 CRON 和 JobHandler,然後 admin 服務就會按照配置的參數來調度 job。很顯然,這種「自動化工作」也是由線程定時執行的。
1、如果使用線程調度 Job,存在的第一個問題是:如果某個 Job 在調度時比較耗時,就可能阻塞後續的 Job,導致後續 job 的執行有延遲,怎麼解決這個問題?
在前面 JobTriggerPoolHelper 我們已經知道,admin 在調度 job 時是 」使用線程池、線程「 非同步執行調度任務,避免了主線程的阻塞。
2、使用線程定時調度 job,存在的第二個問題是:怎麼保證 job 在指定的時間執行,而不會出現大量延遲?
admin 使用 」預讀「 的方式,提前讀取在未來一段時間內要執行的 job,提前取到內存中,並使用 「時間輪演算法」 按時間分組 job,把未來要執行的 job 下一個時間段執行。
3、還隱藏第三個問題:admin 服務是可以多實例部署的,在這種情況下該怎麼避免一個 job 被多個實例重復調度?
admin 把一張數據表作為 「分布式鎖」 來保證只有一個 admin 實例能執行 job 調度,又通過隨機 sleep 線程一段時間,來降低線程之間的競爭。
下面我們就通過代碼來了解 xxl-job 是怎麼解決上述問題的。
在該類中,定義了一個調度線程,用來調度要執行的 job 和已經過期一段時間的 job,定義代碼如下:
該線程會預讀出 「下次執行時間 <= now + 5000 毫秒內」 的部分 job,根據它們下一次執行時間劃分成三段,執行三種不同的邏輯。
1、下次執行時間在 (- , now - 5000) 范圍內
說明過期時間已經大於 5000 毫秒,這時如果過期策略要求調度,就調度一次。代碼如下:
2、下次執行時間在 [now - 5000, now) 范圍內
說明過期時間小於5000毫秒,只能算是延遲不能算是過期,直接調度一次,代碼如下:
如果 job 的下一次執行時間在 5000 毫秒以內,為了省下下次預讀的 IO 耗時,這里會記錄下 job id,等待後面的調度。
3、下次執行時間在 [now, now + 5000) 范圍內
說明還沒到執行時間,先記錄下 job id, 等待後面的調度 ,代碼如下:
上面的3個步驟結束後,會更新 jobInfo 的 trigger_last_time、trigger_next_time、trigger_status 欄位:
可以看到,通過預讀,一方面會把過期一小段時間的 job 執行一遍,另一方面會把未來一小段時間內要執行的 job 取出,保存進一個 map 對象 ringData 中,等待另一個線程調度。這樣就避免了某些 job 到了時間還沒執行。
因為 admin 是可以多實例部署的,所以在調度 job 時,需要考慮怎麼避免 job 被多次調度。
xxl-job 在前面 JobFailMonitorHelper 中遍歷失敗的 job 時,會對每個 job 設置一個無效的狀態作為 」分布式行鎖「,如果設置失敗就跳過。而在這里,如果還使用該方法,有可能出現,一個 job 被設置為無效狀態後,線程就崩潰了,導致該 job 永遠無法被調度。因此,要盡量避免對 job 狀態的修改。
在這里,admin 服務使用一張表 xxl_job_lock 作為分布式鎖,每個 admin 實例都要先嘗試獲取該表的鎖,獲取成功才能繼續執行;同時,為了降低不同實例之間的競爭,會在線程開始執勤隨機 sleep 一段時間。
如何獲取分布式鎖?
在線程中會開啟一個事務,設置為手動提交,然後對表 xxl_job_lock 執行 FOR UPDATE 查詢。如果該線程執行語句成功,其他實例的線程就會排隊等待該表的鎖,實現了分布式鎖功能。代碼如下:
怎麼降低鎖的競爭?
為了降低鎖競爭,在線程開始前會先 sleep 4000 5000 毫秒的隨機值(不能大於 5000 毫秒,5000 毫秒是預讀的時間范圍);在線程結束當前循環時,會根據耗時和是否有預讀數據,選擇不同的 sleep 策略:
代碼如下:
在前面的線程中,對即將要開始的 job,不是立刻調度,而是按照執行的時刻(秒),把 job id 保存進一個 map 中,然後由 ringThread 線程按時刻進行調度,這只典型的「時間輪演算法」。代碼如下:
每次輪詢調度時,只取出當前時刻(秒)、前一秒內的 job,不會去調度與現在相隔太久的 job。
在執行輪詢調度前,有一個時間在 0 1000 毫秒范圍內的 sleep。如果沒有這個 sleep,該線程會一直執行,而 ringData 中當前時刻(秒)的數據可能已經為空,會導致大量無效的操作;增加了這個 sleep 之後,可以避免這種無效的操作。之所以 sleep 時間在 1000 毫秒以內,是因為調度時刻最小精確到秒,一秒的 sleep 可以避免 job 的延遲。
因為在前面的 scheleThread 線程中,最後一個操作是把 job 的 next_trigger_time 值更新為大於 now + 5000 毫秒,其他 admin 實例 scheleThread 線程的查詢條件是:next_trigger_time < now + 5000,不會查詢出這里調度的 job,所以不需要加分布式鎖。
至此,XxlJobScheler-init 方法的作用我們介紹完畢,下面我們簡單介紹一下 XxlJobScheler-destroy 方法
destroy 方法很簡單,就是銷毀前面初始化的線程池和線程,它銷毀的順序與前面啟動的順序相反。
代碼如下:
因為各個 toStop 方法都很相似,所以我們只介紹 JobScheleHelper 的 toStop 方法。
該方法的步驟如下:
1、設置停止標志位為 true;
2、sleep 一段時間,讓出 CPU 時間片給線程執行任務;
3、如果線程不是終止狀態(線程正在 sleep),中斷它;
4、線程執行 join 方法,直到線程結束,執行最後一次。
代碼如下:
至此,JobScheleHelper 的主要功能就介紹完了,可以看出, admin 服務在啟動時,啟動了多個線程池和線程,非同步執行任務和非同步響應 executor 的請求。
下面,我們介紹前面涉及到的 XxlJobTrigger 和 XxlJobCompleter。
XxlJobTrigger 是調度 job 時的封裝類,它主要工作就是接受傳入的 jobId、調度參數等,查詢對應的 jobGroup、jobInfo,然後調用 ExecutorBiz 對象來執行調度(run 方法)。
該類中三個核心方法及其調用關系如下: trigger -> processTrigger -> runExecutor ,
該方法的功能比較簡單,就是根據傳入的參數查詢 jobGroup 和 jobInfo 對象,設置相關的欄位值,然後調用 processTrigger 方法。
該方法的主要工作分為以下幾步:
1、保存一條調度日誌;
2、從 jobInfo、jobGroup 中取出欄位值,構造 TriggerParam 對象;
3、根據 jobInfo 的路由策略,從 jobGroup 中取出要調度的 executor 地址;
4、調用 runExecutor 方法執行調度;
5、保存調度參數、設置調度信息、更新日誌。
這里不會修改 jobInfo、jobGroup 對象的欄位值,只取出欄位值來使用,對這兩個對象欄位的修改,是在前一步 trigger 方法中進行的。
該方法會執行調度,並返回調度結果,它的核心代碼如下:
這里使用 XxlJobScheler 類取出 ExecutorBiz 對象,以 「懶載入」 的方式給每個 address 創建一個 ExecutorBiz 對象,代碼如下:
可以看出,該類中的三個方法其實可以歸類為:pre -> execute -> post,在執行前、執行時、執行後做一些前置和收尾工作。
該類在前面 JobCompleteHelper 中被使用,最終 job 的完成就是在該類中執行的,該類有兩個主要方法:
下面主要介紹 finishJob 方法。
finishJob 的主要功能是:如果當前任務執行成功了,就調度它的所有子任務,最後把子任務的調度消息添加到當前 job 的日誌中。代碼如下:
需要注意的是:
1、這里依賴於 JobTriggerPoolHelper 來調度 job,所以在 JobCompleteHelper 的監視線程開始時,有一個 50 秒的等待,就是等待 JobTriggerPoolHelper 啟動完成;
2、在 finishJob 方法中,調度子任務的時候,默認子任務的調度結果是成功,注意,這里是指 「調度」 這個行為是成功的,而不是指子任務執行是成功的。
1、XxlJobAdminConfig 作為 admin 服務的啟動入口,要盡可能保持簡潔,作用類似於一個倉庫,來管理和持有所有的類和對象,並不會去啟動具體的線程,它只需要「按下啟動器的按鈕」就可以了;
2、XxlJobScheler 是 admin 服務的啟動器類,它會調用各個輔助類(xxxHelper)來啟動對應的線程;
3、對外的介面,比如調度 job、接收注冊或下線等,都是使用線程池 + 線程 的非同步方式實現,避免 job 對主線程的阻塞;
4、對「自動任務「類的功能,都是使用線程定時執行;
XXL-JOB分布式調度框架全面詳解: https://juejin.cn/post/6948397386926391333
時間輪演算法:https://spongecaptain.cool/post/widget/timingwheel
一個開源的時間輪演算法介紹:https://spongecaptain.cool/post/widget/timingwheel2
❺ 易語言小米手機搶購軟體,求源碼
源碼沒有,這搶購無非就是POST,再簡單不過了。用網頁封包,查看POST信息,在易語言中網頁訪問,然後帶上自己的COOKIE就OK了
❻ 軟體開發的一般流程是什麼_
軟體開發流程分為: 需求確認——概要設計——詳細設計——編碼——單元測試——集成測試——系統測試——維護
軟體開發是一項包括需求捕捉、需求分析、設計、實現和測試的系統工程。軟體一般是用某種程序設計語言來實現的。通常採用軟體開發工具可以進行開發。軟體分為系統軟體和應用軟體,並不只是包括可以在計算機上運行的程序,與這些程序相關的文件一般也被認為是軟體的一部分。
軟體設計思路和方法的一般過程,包括設計軟體的功能和實現的演算法和方法、軟體的總體結構設計和模塊設計、編程和調試、程序聯調和測試以及編寫、提交程序。
(6)搶號源碼擴展閱讀
軟體開發方面的工作。具體可分為以下方面:
1可視化編程掌握程序設計方法及可視化技術,精通一種可視化平台及其軟體開發技術。獲取Delphi程序員系列、Java初級或VB開發能手認證。 就業方向:企業、政府、社區、各類學校等可視化編程程序員。
2 WEB應用程序設計 具有美工基礎和網頁動畫設計能力,掌握互動式網頁程序的設計技術,能進行網站建設和維護。獲取Macromedia多媒體互動設計師或Delphi初級程序員或Delphi快速網路開發工程師認證。 就業方向:企業、政府、社區、各類學校等WEB應用程序員。
3軟體測試掌握軟體測試的基本原理、方法和組織管理,精通軟體測試工具。獲取ATA軟體測試工程師或Delphi初級程序員或Java初級程序員認證。 就業方向:企業、政府、社區、各類學校等軟體測試員。
4 資料庫管理 能應用關系範式進行資料庫設計,精通SQL語言,勝任資料庫伺服器管理與應用工作。獲取Oracle資料庫管理或SQL Server資料庫應用或Windows XP應用認證。 就業方向:企業、政府、社區、各類學校等部門的中、大型資料庫管理員。
5 圖形圖像製作 精通國際上流行的圖形/圖像製作工具(如CorelDraw、Photoshop、Pagemaker等)。獲取平面設計師相關的認證。 就業方向:廣告製作公司、建築設計公司、包裝裝璜設計公司、居室裝修公司、出版印刷公司。