路由規(guī)劃算法

需求分析

兩張?jiān)紨?shù)據(jù)表如下:

station (站點(diǎn)表)

id name
1 上海
2 昆山
3 蘇州
4 常州
5 鎮(zhèn)江
6 南京
7 揚(yáng)州
8 西安
9 鄭州

站點(diǎn)表用于記錄所有分拔與網(wǎng)點(diǎn)的信息

route (路徑表)

station destination_station next_station status
1 6 2 1
2 6 3 1
3 6 4 1
4 6 5 1
5 6 6 1
7 6 6 1
1 8 2 1
2 8 3 1
3 8 4 1
4 8 5 1
5 8 6 1
7 8 6 1
6 8 9 1
9 8 8 1

路由信息的基礎(chǔ)數(shù)據(jù)表练对,用于記錄從當(dāng)前網(wǎng)點(diǎn)(station)前往目前網(wǎng)點(diǎn)(destination_station)的下一網(wǎng)點(diǎn)(next_station)酿联。以及這個(gè)路由的啟動(dòng)狀態(tài)(status ,1,啟動(dòng)究西,2關(guān)閉)

要生成一條完整的路由锄列。為了緩解數(shù)據(jù)庫(kù)壓力图云,加快查詢(xún)速度。決定將原數(shù)據(jù)庫(kù)查詢(xún)更換成純算法實(shí)現(xiàn)邻邮。

解決思跑路

以前實(shí)現(xiàn)是在數(shù)據(jù)庫(kù)中查找竣况,現(xiàn)在想所有數(shù)據(jù)加載到內(nèi)存中,得用數(shù)據(jù)結(jié)構(gòu)與算法筒严,實(shí)現(xiàn)路由查找丹泉。實(shí)現(xiàn)起來(lái)也不復(fù)雜,比起最短路徑或最優(yōu)路徑這個(gè)功能就很簡(jiǎn)單

數(shù)據(jù)結(jié)構(gòu)

public class Station<T> implements Serializable {
    
    private Serializable id;
    private String name;
    private T extra;
    private final Map<Serializable,Path> pathMap = new HashMap<>();
    
    ....getter and setter...
    
    
       /**
     * 連接兩個(gè)station
     * @param destination
     * @param next
     * @param extra
     * @param <E>
     */
    public  <E>  void linkTo(Station destination, Station next, E extra){


        Path p = pathMap.get(destination.getId());
        if(p==null){
            p = new Path<>(this);
        }else{
            detach(destination);
        }


        p.setExtra(extra);
        p.setDestination(destination);
        p.setNext(next);
        pathMap.put(destination.getId(),p);
        Path nextPath = next.getPath(destination);
        if(nextPath==null){
            nextPath = new Path(next);
            nextPath.setDestination(destination);
            next.pathMap.put(destination.getId(),nextPath);
        }
        nextPath.addPrevious(this);
    }

    /**
     * 獲聚首當(dāng)前路是徑
     * @param des
     * @return
     */
    public Path getPath(Station des){
       return pathMap.get(des.getId());
    }



    public void detach(Station des){
        Path path = pathMap.get(des.getId());
        if(path!=null){
            path.removePrevious(this);
        }
        if(path.getPrevious().size()==0) {
            pathMap.remove(des.getId());
        }else{
            path.setNext(null);
        }
    }
}
@Data
public class Path<T> {
    private T extra;
    private Station destination;
    private Station cur;
    private Station next;
    private List<Station> previous = new ArrayList<>();

    public Path(Station cur) {
        this.cur = cur;
    }

    public void addPrevious(Station station){
        if(!previous.contains(station)){
            previous.add(station);
        }
    }

    public void removePrevious(Station station){
        previous.remove(station);
    }

    @Override
    public String toString() {

        StringBuilder sb = new StringBuilder();
        sb.append("[");
        for (Station station : previous) {
            sb.append(",");
            sb.append(station.getId());
        }
        sb.append("]");


        return "Path{" +
                " cur=" + cur.getId() +
                ", destination=" + (destination!=null?destination.getId():"") +
                ", next=" + (next!=null ? next.getId():"") +
                ", previous=" + sb.toString() +

                '}';
    }
}

解決并發(fā)問(wèn)題加鎖方案

PathFinder類(lèi)主要是用來(lái)存儲(chǔ)所有站點(diǎn)的集合鸭蛙,以及提供所有路徑想著的操作(可能我要重新命名)摹恨,因?yàn)闃I(yè)務(wù)中要根據(jù)網(wǎng)點(diǎn)id查詢(xún)網(wǎng)點(diǎn)(或路由信息),所以用map來(lái)存儲(chǔ)網(wǎng)點(diǎn)娶视。路徑信息可能隨時(shí)會(huì)出變動(dòng)晒哄,而每段路徑的變動(dòng),都會(huì)影響到整個(gè)路由肪获。所以我在此處用到了讀寫(xiě)鎖寝凌,每段路徑的變動(dòng),只會(huì)影響到與其相同終點(diǎn)的路徑孝赫,我讓每個(gè)Station都持有一個(gè)讀鎖和寫(xiě)鎖较木。在做讀取或更改操作時(shí),我獲取目的結(jié)點(diǎn)上的鎖青柄,并對(duì)其進(jìn)行加鎖操作(這種不鎖全部伐债,只鎖部門(mén)的思想在LongAdapter和ConcurrentMap中都有應(yīng)用)。

@Service
public class PathFinder<T,E> {
    ConcurrentSkipListMap<Serializable,Station<T>> stationMap = new ConcurrentSkipListMap();
    
     public  void link(Station curt, Station des, Station next, E extra){
        if(curt == next){
            throw new IllegalStateException("當(dāng)前網(wǎng)點(diǎn)與下一網(wǎng)點(diǎn)不可以相同");
        }

        ReentrantReadWriteLock.WriteLock lock = des.writeLock();
        lock.lock();
        try {
            /**
             * 驗(yàn)證是否存在死循環(huán)
             */
            List<Path<E>> route = findRoute(next, des);
            for (Path<E> p : route) {
                if(p.getNext()==curt||p.getCur() == curt){
                    throw new IllegalStateException("網(wǎng)點(diǎn)存在死循環(huán)致开。");
                }
            }
            curt.linkTo(des, next, extra);
        }finally {
            lock.unlock();
        }
    }
    
 public List<Path<E>> findRoute(Station begin,Station des){
        int len = 0;

        List<Path<E>> list = new ArrayList<>();
        ReentrantReadWriteLock.ReadLock lock = des.readLock();
        lock.lock();
        try{
            Path p;
            Station station =begin;
          while((p=station.getPath(des))!=null && (station=p.getNext())!=null){
              list.add(p);
              if((len++)==100){
                  throw new IllegalStateException("鏈路太長(zhǎng)峰锁,可能出現(xiàn)死路:"+list);
              }
          }
        }finally {
            lock.unlock();
        }
        return list;
    }
    
    
}


數(shù)據(jù)全加載時(shí)遇到的問(wèn)題

原計(jì)劃是在程序啟動(dòng)時(shí),加載整張表數(shù)據(jù)喇喉。但測(cè)試之后祖今,2000萬(wàn)條數(shù)據(jù),加載速度很慢拣技,之少要20分鐘之久千诬。

延遲加載及并行加載

數(shù)據(jù)全加載保證了用戶(hù)訪(fǎng)問(wèn)時(shí)的查詢(xún)速度,以及線(xiàn)程安全等問(wèn)題膏斤。但確增長(zhǎng)系統(tǒng)啟動(dòng)時(shí)間徐绑。

懶加載不影響系統(tǒng)啟動(dòng)時(shí)間。但可能會(huì)帶來(lái)兩個(gè)問(wèn)題:

  1. 多線(xiàn)程下數(shù)據(jù)重復(fù)加載問(wèn)題莫辨,比如有幾個(gè)請(qǐng)求同時(shí)請(qǐng)求上海南京的結(jié)點(diǎn)傲茄,可能造成兩個(gè)線(xiàn)程同時(shí)加載數(shù)據(jù)。
  2. 可能會(huì)降代查詢(xún)速度沮榜,用戶(hù)第一次請(qǐng)求查詢(xún)某路徑時(shí)盘榨,因?yàn)橐獜臄?shù)據(jù)庫(kù)加載數(shù)據(jù),可能會(huì)出現(xiàn)延遲蟆融。
  • 狀態(tài)控制及加鎖

    為了避免數(shù)據(jù)重復(fù)加載草巡,可以對(duì)每個(gè)接點(diǎn)添加一個(gè)狀態(tài),表示以此接點(diǎn)為目的接點(diǎn)的數(shù)據(jù)是否已加載型酥,如果未加裁山憨,可以使用tryLock試獲此接點(diǎn)的鎖,如果獲取成功弥喉,則加載數(shù)據(jù)據(jù)郁竟,否則進(jìn)行自旋,不斷獲取加載狀態(tài)由境。一旦數(shù)據(jù)另載成功棚亩,便返回。

  • 并行加載
    懶加載是指在在需要數(shù)據(jù)時(shí)再加載數(shù)據(jù)(從數(shù)據(jù)庫(kù)中讀取所有目的網(wǎng)點(diǎn)的數(shù)據(jù))虏杰,在訪(fǎng)問(wèn)峰值時(shí)讥蟆,會(huì)造成類(lèi)似于緩存的雪崩現(xiàn)像。所以采用預(yù)加載與懶加載并行的方式嘹屯,在程序啟動(dòng)地攻询,另開(kāi)一線(xiàn)程加載數(shù)據(jù),這樣不會(huì)影響到項(xiàng)目的啟動(dòng)時(shí)間州弟。同時(shí)由于上面的狀判斷及懶加載的方案钧栖,也能在數(shù)據(jù)未能加載完成時(shí),對(duì)外提供服務(wù)婆翔。

后繼方案

目前2000個(gè)網(wǎng)點(diǎn)拯杠,生成的結(jié)點(diǎn)數(shù)所約500萬(wàn)條,得用jmap查看內(nèi)存發(fā)現(xiàn)啃奴,總共占用內(nèi)存不到一個(gè)G, 如果以后再增加網(wǎng)點(diǎn)數(shù)潭陪,可以考慮LRU策略進(jìn)行淘汰,目前我存儲(chǔ)網(wǎng)點(diǎn)用的的是ConcurrentSkipListMap,如果要實(shí)現(xiàn)LRU策略依溯,可能要換成HashLinkedMap實(shí)現(xiàn)老厌。當(dāng)然我也可以用數(shù)據(jù)分片,不管是LRU或數(shù)據(jù)分片黎炉,都可以利用目的網(wǎng)點(diǎn)對(duì)數(shù)據(jù)進(jìn)行分割枝秤。這要比傳統(tǒng)的那種尋找最小路徑算法簡(jiǎn)單多了(因?yàn)槿绻莻鹘y(tǒng)的圖,我想不到什么好的辦法慷嗜,對(duì)數(shù)據(jù)進(jìn)行分割)淀弹。因?yàn)椴幌朐黾訌?fù)雜性,我只對(duì)該服務(wù)進(jìn)行單節(jié)點(diǎn)部署(多節(jié)點(diǎn)部署要考慮數(shù)據(jù)一致性問(wèn)題庆械,而且當(dāng)前擁有有的服務(wù)器資源也不適做多接點(diǎn)部署)薇溃,以后如果要做接點(diǎn)部署,可以考慮應(yīng)用數(shù)據(jù)一致性算法缭乘,加樂(lè)觀(guān)鎖機(jī)制沐序。

總結(jié)

算法很簡(jiǎn)單,應(yīng)用的技術(shù)也不算太復(fù)雜忿峻,關(guān)鍵是選擇合適的方案來(lái)解決當(dāng)前的問(wèn)題薄啥。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市逛尚,隨后出現(xiàn)的幾起案子垄惧,更是在濱河造成了極大的恐慌,老刑警劉巖绰寞,帶你破解...
    沈念sama閱讀 221,635評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件到逊,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡滤钱,警方通過(guò)查閱死者的電腦和手機(jī)觉壶,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)件缸,“玉大人铜靶,你說(shuō)我怎么就攤上這事∷叮” “怎么了争剿?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,083評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀(guān)的道長(zhǎng)痊末。 經(jīng)常有香客問(wèn)我蚕苇,道長(zhǎng),這世上最難降的妖魔是什么凿叠? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,640評(píng)論 1 296
  • 正文 為了忘掉前任涩笤,我火速辦了婚禮嚼吞,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘蹬碧。我一直安慰自己舱禽,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,640評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布锰茉。 她就那樣靜靜地躺著呢蔫,像睡著了一般切心。 火紅的嫁衣襯著肌膚如雪飒筑。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 52,262評(píng)論 1 308
  • 那天绽昏,我揣著相機(jī)與錄音协屡,去河邊找鬼。 笑死全谤,一個(gè)胖子當(dāng)著我的面吹牛肤晓,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播认然,決...
    沈念sama閱讀 40,833評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼补憾,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了卷员?” 一聲冷哼從身側(cè)響起盈匾,我...
    開(kāi)封第一講書(shū)人閱讀 39,736評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎毕骡,沒(méi)想到半個(gè)月后削饵,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,280評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡未巫,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,369評(píng)論 3 340
  • 正文 我和宋清朗相戀三年窿撬,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片叙凡。...
    茶點(diǎn)故事閱讀 40,503評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡劈伴,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出握爷,到底是詐尸還是另有隱情跛璧,我是刑警寧澤,帶...
    沈念sama閱讀 36,185評(píng)論 5 350
  • 正文 年R本政府宣布饼拍,位于F島的核電站赡模,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏师抄。R本人自食惡果不足惜漓柑,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,870評(píng)論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧辆布,春花似錦瞬矩、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,340評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至惭蹂,卻和暖如春伞插,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背盾碗。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,460評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工媚污, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人廷雅。 一個(gè)月前我還...
    沈念sama閱讀 48,909評(píng)論 3 376
  • 正文 我出身青樓耗美,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親航缀。 傳聞我的和親對(duì)象是個(gè)殘疾皇子商架,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,512評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容