กลยุทธ์การใช้งานที่ดีสำหรับการห่อหุ้มข้อมูลที่ใช้ร่วมกันในซอฟต์แวร์ไปป์ไลน์


13

ฉันกำลังดำเนินการเกี่ยวกับการให้บริการเว็บที่มีอยู่อีกครั้ง วิธีที่ API ของบริการถูกนำไปใช้คือการมี "การประมวลผลขั้นตอน" ซึ่งมีงานที่จะดำเนินการตามลำดับ ไม่น่าแปลกใจที่งานในภายหลังอาจต้องการข้อมูลที่คำนวณโดยงานก่อนหน้านี้และในปัจจุบันวิธีที่ทำได้คือการเพิ่มเขตข้อมูลลงในคลาส "ไปป์ไลน์"

ฉันกำลังคิด (และหวัง?) ว่ามีวิธีที่ดีกว่าในการแบ่งปันข้อมูลระหว่างขั้นตอนไปป์ไลน์กว่าการมีออบเจ็กต์ข้อมูลที่มีเขตข้อมูล zillion ซึ่งบางอันเหมาะสมกับขั้นตอนการประมวลผลบางอย่างและไม่ใช่กับผู้อื่น มันจะเป็นความเจ็บปวดที่สำคัญในการทำให้เธรดปลอดภัยระดับนี้ (ฉันไม่รู้ว่ามันจะเป็นไปได้) ไม่มีทางที่จะให้เหตุผลเกี่ยวกับค่าคงที่ของมัน (และเป็นไปได้ว่ามันจะไม่มี)

ฉันเพจผ่านรูปแบบการออกแบบหนังสือของ Gang of Four เพื่อค้นหาแรงบันดาลใจ แต่ฉันไม่รู้สึกว่ามีวิธีแก้ปัญหาในนั้น (Memento ค่อนข้างมีวิญญาณเดียวกัน แต่ไม่มาก) ฉันดูออนไลน์ด้วยเช่นกัน แต่ครั้งที่สองที่คุณค้นหา "ไปป์ไลน์" หรือ "เวิร์กโฟลว์" ที่คุณได้รับนั้นเต็มไปด้วยข้อมูล Unix pipes หรือเอ็นจิ้นเวิร์กโฟลว์และเฟรมเวิร์กที่เป็นกรรมสิทธิ์

คำถามของฉันคือ - คุณจะแก้ไขปัญหาการบันทึกสถานะการดำเนินการของขั้นตอนการประมวลผลซอฟต์แวร์อย่างไรเพื่อให้งานในภายหลังสามารถใช้ข้อมูลที่คำนวณโดยคนก่อนหน้าได้ ฉันเดาว่าความแตกต่างที่สำคัญกับท่อ Unix คือคุณไม่เพียง แต่สนใจผลงานก่อนหน้านี้ทันที


ตามที่ร้องขอบางรหัสเทียมเพื่อแสดงกรณีการใช้งานของฉัน:

วัตถุ "ไปป์ไลน์บริบท" มีพวงของเขตข้อมูลที่ขั้นตอนไปป์ไลน์ที่แตกต่างกันสามารถเติม / อ่าน:

public class PipelineCtx {
    ... // fields
    public Foo getFoo() { return this.foo; }
    public void setFoo(Foo aFoo) { this.foo = aFoo; }
    public Bar getBar() { return this.bar; }
    public void setBar(Bar aBar) { this.bar = aBar; }
    ... // more methods
}

แต่ละขั้นตอนไปป์ไลน์ยังเป็นวัตถุ:

public abstract class PipelineStep {
    public abstract PipelineCtx doWork(PipelineCtx ctx);
}

public class BarStep extends PipelineStep {
    @Override
    public PipelineCtx doWork(PipelieCtx ctx) {
        // do work based on the stuff in ctx
        Bar theBar = ...; // compute it
        ctx.setBar(theBar);

        return ctx;
    }
}

ในทำนองเดียวกันสำหรับสมมติฐานFooStepซึ่งอาจต้องใช้แถบคำนวณโดย BarStep ก่อนหน้านั้นพร้อมกับข้อมูลอื่น ๆ จากนั้นเรามีการเรียก API จริง:

public class BlahOperation extends ProprietaryWebServiceApiBase {
    public BlahResponse handle(BlahRequest request) {
        PipelineCtx ctx = PipelineCtx.from(request);

        // some steps happen here
        // ...

        BarStep barStep = new BarStep();
        barStep.doWork(crx);

        // some more steps maybe
        // ...

        FooStep fooStep = new FooStep();
        fooStep.doWork(ctx);

        // final steps ...

        return BlahResponse.from(ctx);
    }
}

6
อย่าข้ามโพสต์ แต่ตั้งค่าสถานะเพื่อให้ mod เคลื่อน
ratchet freak

1
จะก้าวไปข้างหน้าฉันคิดว่าฉันควรใช้เวลามากขึ้นในการทำความคุ้นเคยกับกฎ ขอบคุณ!
RuslanD

1
คุณหลีกเลี่ยงการจัดเก็บข้อมูลถาวรสำหรับการใช้งานของคุณหรืออะไรจะขึ้นคว้าตอนนี้?
CokoBWare

1
สวัสดี RuslanD และยินดีต้อนรับ! นี่เหมาะสำหรับโปรแกรมเมอร์มากกว่า Stack Overflow ดังนั้นเราจึงลบเวอร์ชัน SO ออก โปรดระลึกถึงสิ่งที่ @ratchetfreak พูดถึงคุณสามารถตั้งค่าสถานะเพื่อให้ความสนใจในการกลั่นกรองและถามคำถามที่จะโยกย้ายไปยังไซต์ที่เหมาะสมกว่าโดยไม่ต้องข้ามโพสต์ กฎง่ายๆสำหรับการเลือกระหว่างสองไซต์คือโปรแกรมเมอร์สำหรับปัญหาที่คุณเผชิญเมื่อคุณอยู่หน้าไวท์บอร์ดที่ออกแบบโครงการของคุณและ Stack Overflow สำหรับปัญหาด้านเทคนิคเพิ่มเติม (เช่นปัญหาการใช้งาน) ดูรายละเอียดเพิ่มเติมของเราคำถามที่พบบ่อย
yannis

1
หากคุณเปลี่ยนสถาปัตยกรรมเป็นการประมวลผล DAG (กราฟ acyclic โดยตรง) แทนไปป์ไลน์คุณสามารถส่งผ่านผลลัพธ์ของขั้นตอนก่อนหน้านี้ได้อย่างชัดเจน
Patrick

คำตอบ:


4

เหตุผลหลักในการใช้การออกแบบไปป์ไลน์คือคุณต้องการแยกขั้นตอนออก อาจเป็นเพราะขั้นตอนเดียวอาจใช้ในหลาย ๆ ท่อ (เช่นเครื่องมือ Unix shell) หรือเพราะคุณได้รับประโยชน์ในการปรับขนาด (เช่นคุณสามารถย้ายจากสถาปัตยกรรมแบบโหนดเดียวไปยังสถาปัตยกรรมแบบหลายโหนด)

ไม่ว่าในกรณีใดขั้นตอนในท่อจะต้องได้รับทุกอย่างที่จำเป็นในการทำงาน ไม่มีเหตุผลที่คุณไม่สามารถใช้ที่จัดเก็บภายนอก (เช่นฐานข้อมูล) แต่ในกรณีส่วนใหญ่จะเป็นการดีกว่าที่จะส่งข้อมูลจากเวทีหนึ่งไปยังอีกเวทีหนึ่ง

อย่างไรก็ตามนั่นไม่ได้หมายความว่าคุณจะต้องหรือควรผ่านวัตถุข้อความขนาดใหญ่หนึ่งรายการกับทุกฟิลด์ที่เป็นไปได้ (แม้ว่าจะดูด้านล่าง) แต่ละขั้นตอนในไปป์ไลน์ควรกำหนดอินเทอร์เฟซสำหรับข้อความอินพุตและเอาต์พุตที่ระบุเฉพาะข้อมูลที่สเตจต้องการ

จากนั้นคุณมีความยืดหยุ่นอย่างมากในการใช้วัตถุข้อความจริงของคุณ วิธีหนึ่งคือการใช้วัตถุข้อมูลขนาดใหญ่ที่ใช้อินเทอร์เฟซที่จำเป็นทั้งหมด Mapอีกประการหนึ่งคือการสร้างคลาสที่ห่อหุ้มรอบที่เรียบง่าย อีกสิ่งหนึ่งคือการสร้างคลาส wrapper รอบฐานข้อมูล


1

มีความคิดเล็ก ๆ น้อย ๆ ที่ก้าวกระโดดไปสู่ความคิดประการแรกคือฉันไม่มีข้อมูลเพียงพอ

  • แต่ละขั้นตอนสร้างข้อมูลที่ใช้นอกเหนือจากไปป์ไลน์หรือไม่หรือเราสนใจเฉพาะผลลัพธ์ของขั้นตอนสุดท้ายเท่านั้นหรือไม่
  • ข้อมูลขนาดใหญ่มีความกังวลหรือไม่? กล่าวคือ ความกังวลเกี่ยวกับหน่วยความจำ, ความกังวลความเร็ว ฯลฯ

คำตอบอาจทำให้ฉันคิดอย่างรอบคอบมากขึ้นเกี่ยวกับการออกแบบอย่างไรก็ตามจากสิ่งที่คุณพูดมี 2 วิธีที่ฉันอาจจะพิจารณาก่อน

จัดโครงสร้างแต่ละสเตจเป็นวัตถุของตัวเอง สเตจที่ n จะมี 1 ถึง n-1 สเตจเป็นรายการของผู้รับมอบสิทธิ์ แต่ละขั้นตอนห่อหุ้มข้อมูลและประมวลผลข้อมูล ลดความซับซ้อนโดยรวมและฟิลด์ภายในแต่ละวัตถุ คุณยังสามารถให้สเตจภายหลังเข้าถึงข้อมูลได้ตามต้องการจากสเตจก่อนหน้านี้มากโดยการข้ามตัวแทน คุณยังคงมีเพศสัมพันธ์แน่นหนาในทุกวัตถุเพราะมันเป็นผลมาจากขั้นตอนต่างๆ (นั่นคือการตอบสนองทั้งหมด) ที่มีความสำคัญ แต่มันลดลงอย่างมากและแต่ละขั้นตอน / วัตถุอาจอ่านและเข้าใจได้ง่ายขึ้น คุณสามารถทำให้เธรดปลอดภัยด้วยการทำรายการผู้รับมอบสิทธิ์ขี้เกียจและใช้คิวเซฟเธรดเพื่อเติมรายการผู้รับมอบสิทธิ์ในแต่ละวัตถุตามต้องการ

หรือฉันอาจจะทำสิ่งที่คล้ายกับสิ่งที่คุณทำ วัตถุข้อมูลขนาดใหญ่ที่ผ่านฟังก์ชั่นที่เป็นตัวแทนของแต่ละขั้นตอน ซึ่งมักจะเร็วกว่าและมีน้ำหนักเบามาก แต่มีความซับซ้อนและข้อผิดพลาดเกิดขึ้นได้ง่ายเนื่องจากมันเป็นเพียงกองข้อมูลขนาดใหญ่ เห็นได้ชัดว่าไม่ปลอดภัยต่อเธรด

สุจริตฉันทำต่อมาบ่อยขึ้นสำหรับ ETL และปัญหาที่คล้ายกันอื่น ๆ ฉันมุ่งเน้นไปที่ประสิทธิภาพเนื่องจากปริมาณของข้อมูลมากกว่าการบำรุงรักษา อีกทั้งยังเป็นแบบครั้งเดียวซึ่งจะไม่ถูกใช้อีก


1

ดูเหมือนว่ารูปแบบโซ่ใน GoF

จุดเริ่มต้นที่ดีคือดูว่าคอมมอนส์เชนอะไร

เทคนิคที่ได้รับความนิยมสำหรับการจัดการการดำเนินการของโฟลว์โพรเซสซิงคือรูปแบบ "Chain of Responsibility" ตามที่อธิบาย (ในที่อื่น ๆ ) ในหนังสือรูปแบบการออกแบบ "Gang of Four" แบบคลาสสิก แม้ว่าสัญญา API พื้นฐานที่จำเป็นต้องใช้ในการออกแบบเสื้อนี้จะง่ายมาก แต่ก็มีประโยชน์ที่จะมี API พื้นฐานที่อำนวยความสะดวกในการใช้รูปแบบและ (สำคัญกว่า) ส่งเสริมการจัดองค์ประกอบของการใช้งานคำสั่งจากหลายแหล่งที่หลากหลาย

ในตอนท้ายนั้น Chain API จำลองการคำนวณเป็นชุดของ "คำสั่ง" ที่สามารถรวมกันเป็น "chain" API สำหรับคำสั่งประกอบด้วยวิธีการเดียว ( execute()) ซึ่งผ่านพารามิเตอร์ "บริบท" ที่มีสถานะไดนามิกของการคำนวณและมีค่าส่งคืนเป็นบูลีนที่พิจารณาว่าการประมวลผลสำหรับโซ่ปัจจุบันเสร็จสมบูรณ์หรือไม่ true) หรือการประมวลผลควรมอบหมายให้คำสั่งถัดไปใน chain (false) หรือไม่

สิ่งที่เป็นนามธรรม "บริบท" ถูกออกแบบมาเพื่อแยกการใช้งานคำสั่งจากสภาพแวดล้อมที่พวกเขากำลังทำงาน (เช่นคำสั่งที่สามารถใช้ใน Servlet หรือ Portlet โดยไม่ต้องเชื่อมโยงโดยตรงกับสัญญา API ของสภาพแวดล้อมเหล่านี้) สำหรับคำสั่งที่จำเป็นต้องจัดสรรทรัพยากรก่อนที่จะมอบหมายและจากนั้นปล่อยพวกเขาเมื่อกลับมา (แม้ว่าคำสั่งมอบหมายให้ไปโยนข้อยกเว้น) ส่วนขยาย "ตัวกรอง" เพื่อ "คำสั่ง" ให้postprocess()วิธีการล้างข้อมูลนี้ ในที่สุดคำสั่งสามารถจัดเก็บและค้นหาใน "แคตตาล็อก" เพื่อให้เลื่อนการตัดสินใจว่าคำสั่งใด (หรือเชน) ดำเนินการจริง

เพื่อให้เกิดประโยชน์สูงสุดของรูปแบบ API ของ Chain of Responsibility นั้นสัญญาอินเทอร์เฟซพื้นฐานจะถูกกำหนดในลักษณะที่มีการพึ่งพาศูนย์อื่น ๆ นอกเหนือจาก JDK ที่เหมาะสม การใช้งานคลาสฐานความสะดวกสบายของ API เหล่านี้มีให้เช่นเดียวกับการใช้งานเฉพาะ (แต่ไม่จำเป็น) สำหรับสภาพแวดล้อมเว็บ (เช่น servlets และพอร์ตเล็ต)

เนื่องจากการใช้งานคำสั่งได้รับการออกแบบมาเพื่อให้สอดคล้องกับคำแนะนำเหล่านี้มันควรจะเป็นไปได้ที่จะใช้ Chain of Responsibility APIs ใน "front controller" ของเว็บแอ็พพลิเคชันเฟรมเวิร์ก (เช่น Struts) แต่ยังสามารถใช้ในธุรกิจได้ ตรรกะและระดับความคงทนในการสร้างแบบจำลองความต้องการการคำนวณที่ซับซ้อนผ่านองค์ประกอบ นอกจากนี้การแยกการคำนวณลงในคำสั่งที่ไม่ต่อเนื่องที่ทำงานบนบริบทวัตถุประสงค์ทั่วไปช่วยให้การสร้างคำสั่งที่ง่ายขึ้นซึ่งเป็นหน่วยที่ทดสอบได้เพราะผลกระทบของการดำเนินการคำสั่งสามารถวัดได้โดยตรงโดยสังเกตการเปลี่ยนแปลงสถานะที่สอดคล้องกันในบริบท ...


0

ทางออกแรกที่ฉันสามารถจินตนาการได้คือการทำให้ขั้นตอนชัดเจน แต่ละคนจะกลายเป็นวัตถุที่สามารถประมวลผลข้อมูลและส่งไปยังวัตถุกระบวนการถัดไป แต่ละกระบวนการผลิตผลิตภัณฑ์ใหม่ (ไม่เปลี่ยนรูปแบบอุดมคติ) เพื่อให้ไม่มีการโต้ตอบระหว่างกระบวนการและจากนั้นไม่มีความเสี่ยงเนื่องจากการแบ่งปันข้อมูล หากกระบวนการบางอย่างใช้เวลานานกว่ากระบวนการอื่น ๆ คุณสามารถวางบัฟเฟอร์ระหว่างสองกระบวนการ หากคุณใช้ประโยชน์จากตัวกำหนดตารางเวลาสำหรับมัลติเธรดอย่างถูกต้องมันจะจัดสรรทรัพยากรเพิ่มเติมเพื่อล้างบัฟเฟอร์

วิธีแก้ปัญหาที่สองคือคิดว่า "ข่าวสาร" แทนที่จะเป็นไปป์ไลน์อาจมีกรอบเฉพาะ จากนั้นคุณมี "นักแสดง" รับข้อความจากนักแสดงคนอื่นและส่งข้อความอื่น ๆ ให้กับนักแสดงคนอื่น คุณจัดระเบียบนักแสดงของคุณในท่อและให้ข้อมูลหลักของคุณกับนักแสดงคนแรกที่เริ่มต้นห่วงโซ่ ไม่มีการแบ่งปันข้อมูลตั้งแต่การแบ่งปันถูกแทนที่ด้วยการส่งข้อความ ฉันรู้ว่าแบบจำลองของนักแสดง Scala สามารถใช้ใน Java ได้เนื่องจากไม่มีสิ่งใดที่ Scala เฉพาะเจาะจงที่นี่ แต่ฉันไม่เคยใช้มันในโปรแกรม Java

โซลูชันมีความคล้ายคลึงกันและคุณสามารถใช้โซลูชันที่สองกับโซลูชันแรก โดยทั่วไปแนวคิดหลักคือการจัดการกับข้อมูลที่ไม่เปลี่ยนรูปแบบเพื่อหลีกเลี่ยงปัญหาแบบดั้งเดิมเนื่องจากการแบ่งปันข้อมูลและเพื่อสร้างเอนทิตีที่ชัดเจนและเป็นอิสระซึ่งเป็นตัวแทนของกระบวนการในขั้นตอนของคุณ หากคุณปฏิบัติตามเงื่อนไขเหล่านี้คุณสามารถสร้างท่อที่ชัดเจนและใช้งานง่ายในโปรแกรมแบบขนาน


สวัสดีฉันอัปเดตคำถามของฉันด้วยรหัสเทียมบางอัน - อันที่จริงเรามีขั้นตอนชัดเจน
RuslanD
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.