タスクバランサーとWebインターフェイスを備えたGOのシンプルなDICOMクライアント



こんにちはHabr! 最近、私はGO言語の開発に非常に興味を持っています。 エレガントで表現力豊かなプログラミング言語。 私は長い間何か役に立つことをしたいと思っていました。 私の仕事の詳細については、PACSのDACOM画像の医療アーカイブを使用する必要があります。





Github: github.com/Loafter/dtools

Linuxバージョン-amd64github.com/Loafter/dtools/releases/download/1.0/dcmjsser



次の標準操作を実行できる(ブラックジャック..)Webインターフェースを備えたdicomクライアントを作成する時が来たと判断しました。



(それぞれc-echo、c-move、c-store、c-find)。

GrassRoot SDKライブラリーがdicomライブラリーとして選択されました。 クライアントはタスクを並列化します。 go言語はこれによく適合しています。



同様の作業シナリオがhabrahabr.ru/post/198150によって記述されました

シナリオは少し異なります。

dicomサービスのタスクを受け取り、実行の可能性をチェックし、それらを非同期に実行する特定のタスクバランサーがあります。 1000個のタスクが並行して実行される状況を回避するために、アクティブなタスクとスリープ状態のタスクがあるようにタスクキューを実装します。 デフォルトでは、10個のタスクのみがアクティブになります。 そうでなければ、バランサーがまったくなくても、制御なしで愚かにも1000個のタスクを並行して実行できます。

すべてのバランサーコードはjob_ballancer.goファイルにあります。



最初は、ハンドラーインターフェイスの説明です。 作業が正常に完了した場合、エラーが返された場合、およびタスクを処理するプロセス。



type JobDispatcher interface { Dispatch(interface{}) (interface{}, error) } type ErrDispatcher interface { DispatchError(FaJob) error } type CompDispatcher interface { DispatchSuccess(CompJob) error }
      
      







ディスパッチャをインスタンス化するとき、適切なハンドラでディスパッチャを初期化します。

 srv.jbBal.Init(&srv.dDisp, srv, srv) //   type JobBallancer struct { jChan chan interface{} //      acJob map[string]Job //   slJob map[string]Job //   errDisp ErrDispatcher //   error jobDisp JobDispatcher //  compDisp CompDispatcher //    JbDone sync.WaitGroup //    aJobC int //  () } //  func (jbal *JobBallancer) Init(jdis JobDispatcher, cmd CompDispatcher, erd ErrDispatcher) { jbal.errDisp = erd jbal.jobDisp = jdis jbal.compDisp = cmd jbal.acJob = make(map[string]Job) jbal.slJob = make(map[string]Job) jbal.aJobC = 10 jbal.jChan = make(chan interface{}) go jbal.takeJob() //     //  log.Println("info: job ballancer inited") }     .  , ..    ,   takeJob    . func (jbal *JobBallancer) PushJob(jdat interface{}) error { if jbal.checkInit() { return errors.New("error: JobChan is not inited") } uid := genUid() job := Job{JobId: uid, Data: jdat} jbal.jChan <- job return nil } func (jbal *JobBallancer) takeJob() { for { //    recivedTask := <-jbal.jChan log.Println("info: job taken") switch job := recivedTask.(type) { case TermJob: //         log.Println("info: recive terminate dispatch singal") return case Job: //  (             ) if len(jbal.acJob) < jbal.aJobC { jbal.JbDone.Add(1) jbal.addActiveJob(job) go jbal.startJob(job) log.Println("info: normal dispatch") } else { jbal.addSleepJob(job) jbal.JbDone.Add(1) log.Println("info: attend maximum active job") } case CompJob: //   if err := jbal.compDisp.DispatchSuccess(job); err != nil { log.Println("error: failed dispatch success" + job.Job.JobId) } //       jbal.removeJob(job.Job.JobId) jbal.JbDone.Done() jbal.resumeJobs() case FaJob: //   if err := jbal.errDisp.DispatchError(job); err != nil { log.Println("error: failed dispatch error" + job.Job.JobId) } //      jbal.removeJob(job.Job.JobId) jbal.JbDone.Done() jbal.resumeJobs() default: log.Fatalln("error: unknown job type") jbal.JbDone.Done() } } } //   func (jbal *JobBallancer) removeJob(jid string) error { if _, isFind := jbal.acJob[jid]; isFind { delete(jbal.acJob, jid) } else { return errors.New("error: can't remove job because job with id not found") } return nil } //     ,        ,      func (jbal *JobBallancer) TerminateTakeJob() error { if jbal.checkInit() { return errors.New("error: is not inited") } jbal.JbDone.Wait() jbal.jChan <- TermJob{} close(jbal.jChan) if len(jbal.acJob) > 0 { return errors.New("error: list job is not empty") } log.Println("info: greacefully terminate take job") return nil }
      
      







残りの補助機能は考慮しません。 完全なコードを表示できます

github.com/Loafter/dtools/blob/master/dcmjsser/job_ballancer.go



コードは複雑ではないという事実にもかかわらず、私は長い間考えていました。 それでも、信頼性を確認するために、数十のタスクの負荷テストを実装しました。

 testJobDispatcher := TestJobDispatcher{} testErrorDispatcher := TestErrorDispatcher{} testSuccessDispatcher := TestCompletedDispatcher{} jobBallancer := JobBallancer{} jobBallancer.Init(&testJobDispatcher, &testSuccessDispatcher, &testErrorDispatcher) for i := 0; i < 40; i++ { jobBallancer.PushJob("data: " + strconv.Itoa(i)) } jobBallancer.TerminateTakeJob()
      
      





彼はうまく働いた。 すべてのタスクが完了し、すべてのタスクが完了するとTerminateTakeJob関数が完了しました。 完了したタスクを制御するには、sync.WaitGroup JbDone同期オブジェクトを使用して、完了した作業の数をカウントします。 上で述べたように、バランサーコードは普遍的であり、バランサーが異なる動作をするためには、適切なハンドラーでインスタンス化するだけです。



以前の記事のように)(http://habrahabr.ru/post/247727/)アプリケーションインターフェイスをWebインターフェイスの形式で実装しました。





テストには、パブリックdicomアーカイブ213.165.94.158:11112を使用しました。 直接IPがあり、クライアント側でポート11112が開いている場合は、そこから調査をダウンロードできます。

Linuxの動作バージョンをビルドできましたが、残念ながら未亡人の下でビルドできませんでした。 grassrootライブラリは正常にビルドされましたが、アプリケーション自体をリンクするときにエラーが発生します。

cmd / ld:不正な形式のPEファイル:PEセクションの予期しないフラグ。



このエラーについてはgithub.com/golang/go/issues/4069に多くのことが書かれています。

残念ながら、私はアセンブリの複雑さにあまり詳しくないので、バージョンはLinuxでのみ判明しました。 たぶん「ハブラ効果」はこの問題を根本から取り除くでしょう。 それがどのように機能するかを確認したいWindowsユーザーのために、CoreOS(https://yadi.sk/d/y81KC-tyfar6A)に基づいて仮想マシンを準備しました。 デモ機では、dicomクライアントはsystemdサービスとして機能します。

必要に応じて、たとえば、さまざまなdicomノードから調査をダウンロードし、zipアーカイブにアップロードしてダウンロードするサービスを実装できます。 GUIと同様に、jsonメッセージを使用してサービスを管理できます。

そして、あなたは私がしたことをすることができます:いくつかのhtml5ベースのWebビューアーをアプリケーションに固定します。










All Articles