การชนกันเมื่อสร้าง UUID ใน JavaScript?


95

สิ่งนี้เกี่ยวข้องกับคำถามนี้ ฉันใช้รหัสด้านล่างจากคำตอบนี้เพื่อสร้าง UUID ใน JavaScript:

'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
    var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
    return v.toString(16);
});

โซลูชันนี้ดูเหมือนจะใช้งานได้ดี แต่ฉันได้รับการชนกัน นี่คือสิ่งที่ฉันมี:

  • เว็บแอปที่ทำงานใน Google Chrome
  • ผู้ใช้ 16 คน
  • ผู้ใช้เหล่านี้สร้าง UUID ประมาณ 4000 รายการในช่วง 2 เดือนที่ผ่านมา
  • ฉันได้รับการชนกันประมาณ 20 ครั้ง - เช่น UUID ใหม่ที่สร้างขึ้นในวันนี้เหมือนกับเมื่อประมาณ 2 เดือนที่แล้ว (ผู้ใช้คนละคน)

อะไรเป็นสาเหตุของปัญหานี้และฉันจะหลีกเลี่ยงได้อย่างไร


2
รวมตัวเลขสุ่มที่ดีกับเวลาปัจจุบัน (ในหน่วยมิลลิวินาที) อัตราต่อรองของจำนวนสุ่มที่ชนกันในเวลาเดียวกันนั้นต่ำมากจริงๆ
jfriend00

7
@ jfriend00 หากคุณต้องการทำเช่นนั้นมันก็ไม่ใช่ "ตัวเลขสุ่มที่ดี" ไม่ใช่แม้แต่ตัวเลขสุ่มหลอกที่ดี
Attila O.

2
อะไร(r&0x3|0x8)ส่วนการประเมินผลเฉลี่ย / เพื่อ?
Kristian

แล้วการต่อท้าย Date.now (). toString () เข้ากับมันล่ะ?
Vitim.us

4
มีปัญหาใหญ่ในสถาปัตยกรรมของคุณซึ่งไม่เกี่ยวข้องกับ UUID - ไคลเอนต์อาจสร้าง ID ที่ชนกันโดยเจตนา สร้าง ID โดยระบบที่คุณเชื่อถือเท่านั้น อย่างไรก็ตามวิธีแก้ปัญหาให้เพิ่ม ID ที่ไคลเอ็นต์สร้างขึ้นด้วย user_id เพื่อให้ไคลเอนต์ที่เป็นปฏิปักษ์ / ผิดพลาดสามารถชนกันเองเท่านั้น (และจัดการที่ฝั่งเซิร์ฟเวอร์)
Dzmitry Lazerka

คำตอบ:


36

การคาดเดาที่ดีที่สุดของฉันคือMath.random()ระบบของคุณเสียด้วยเหตุผลบางอย่าง (แปลกประหลาดเมื่อฟังดู) นี่เป็นรายงานแรกที่ฉันเห็นว่ามีใครชนกัน

node-uuidมีสายรัดทดสอบที่คุณสามารถใช้เพื่อทดสอบการแจกแจงของเลขฐานสิบหกในรหัสนั้น หากสิ่งนั้นดูดีแสดงว่าไม่ใช่Math.random()ดังนั้นให้ลองแทนที่การใช้งาน UUID ที่คุณใช้ในuuid()วิธีการดังกล่าวและดูว่าคุณยังคงได้ผลลัพธ์ที่ดีหรือไม่

[อัปเดต: เพิ่งเห็นรายงานของ Veselinเกี่ยวกับข้อบกพร่องMath.random()เมื่อเริ่มต้น เนื่องจากปัญหาเกิดขึ้นเมื่อเริ่มต้นเท่านั้นการnode-uuidทดสอบจึงไม่น่าจะเป็นประโยชน์ ฉันจะแสดงความคิดเห็นโดยละเอียดในลิงค์ devoluk.com]


1
ขอบคุณตอนนี้ฉันจะใช้ uuid.js เนื่องจากใช้ crypto ที่แข็งแกร่งของเบราว์เซอร์หากมี จะดูว่ามีการชนกันหรือไม่.
Muxa

คุณสามารถระบุลิงก์ไปยังโค้ด uuid.js ที่คุณอ้างถึงได้หรือไม่ (ขออภัยไม่แน่ใจว่าคุณหมายถึง lib ใด)
broofa

10
ไม่มีการชนกันเลย :)
Muxa

อย่างไรก็ตามหากเป็น Chrome และเมื่อเริ่มต้นเท่านั้นแอปของคุณสามารถสร้างและละทิ้งคำแนะนำสิบแถวโดยใช้ฟังก์ชันด้านบน :)
Vinko Vrsalovic

ปัญหาคือเอนโทรปีที่ จำกัด ที่คุณได้รับจาก Math.random () สำหรับบางเบราว์เซอร์เอนโทรปีต่ำเพียง 41 บิตด้วยกัน การเรียก Math.random () หลาย ๆ ครั้งจะไม่เพิ่มเอนโทรปี หากคุณต้องการ UUID v4 ที่ไม่ซ้ำใครจริงๆคุณต้องใช้ RNG ที่แข็งแกร่งในการเข้ารหัสซึ่งสร้างเอนโทรปีอย่างน้อย 122 บิตต่อ UUID ที่สร้างขึ้น
mlehmk

36

แน่นอนว่ามีการชนกัน แต่ภายใต้ Google Chrome เท่านั้น ตรวจสอบประสบการณ์ของฉันในหัวข้อที่นี่

http://devoluk.com/google-chrome-math-random-issue.html

(ลิงก์เสียเมื่อปี 2019 ลิงก์ที่เก็บถาวร: https://web.archive.org/web/20190121220947/http://devoluk.com/google-chrome-math-random-issue.html )

ดูเหมือนว่าการชนกันจะเกิดขึ้นกับ Math.random สองสามสายแรกเท่านั้น สาเหตุถ้าคุณเพิ่งเรียกใช้เมธอด createGUID / testGUIDs ด้านบน (ซึ่งเห็นได้ชัดว่าเป็นสิ่งแรกที่ฉันลอง) มันก็ใช้งานได้โดยไม่มีการชนกันใด ๆ

ดังนั้นในการทำการทดสอบแบบเต็มต้องรีสตาร์ท Google Chrome สร้าง 32 ไบต์รีสตาร์ท Chrome สร้างรีสตาร์ทสร้าง ...


2
ค่อนข้างน่าเป็นห่วง - มีใครรายงานข้อผิดพลาดหรือไม่?
UpTheCreek

1
โดยเฉพาะอย่างยิ่งเช่นลิงก์ไปยังเครื่องกำเนิดตัวเลขสุ่มที่ดีกว่าใน javascript: baagoe.com/en/RandomMusings/javascript
Leopd

น่าเศร้าที่ลิงก์ดังกล่าวใช้งานไม่ได้แล้ว :(
Gus


7
มีใครยืนยันได้หรือไม่ว่าข้อบกพร่องนี้ได้รับการแก้ไขแล้ว
Xdrone

21

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

ในที่สุดฉันก็พบว่าปัญหา (เกือบ?) เกี่ยวข้องกับบอทโปรแกรมรวบรวมข้อมูลเว็บของ Google โดยเฉพาะ ทันทีที่ฉันเริ่มเพิกเฉยต่อคำขอของ "googlebot" ในช่อง user-agent การชนกันก็หายไป ฉันคาดเดาว่าพวกเขาต้องแคชผลลัพธ์ของสคริปต์ JS ด้วยวิธีกึ่งอัจฉริยะโดยผลลัพธ์สุดท้ายที่เบราว์เซอร์ spishing ของพวกเขาไม่สามารถนับได้ว่าจะทำงานแบบที่เบราว์เซอร์ปกติทำ

เพียงแค่ FYI


2
พบปัญหาเดียวกันกับระบบเมตริกของเรา พบการชนกันของ UUID หลายพันครั้งโดยใช้โมดูล 'node-uuid' เพื่อสร้างรหัสเซสชันในเบราว์เซอร์ ปรากฎว่าเป็น googlebot มาตลอด ขอบคุณ!
domkck

4

ฉันต้องการโพสต์สิ่งนี้เป็นความคิดเห็นสำหรับคำถามของคุณ แต่ดูเหมือนว่า StackOverflow จะไม่ยอมให้ฉัน

ฉันเพิ่งทำการทดสอบพื้นฐานของการทำซ้ำ 100,000 ครั้งใน Chrome โดยใช้อัลกอริทึม UUID ที่คุณโพสต์และไม่มีการชนกัน นี่คือข้อมูลโค้ด:

var createGUID = function() {
    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
        var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
        return v.toString(16);
    });
}

var testGUIDs = function(upperlimit) {
    alert('Doing collision test on ' + upperlimit + ' GUID creations.');
    var i=0, guids=[];
    while (i++<upperlimit) {
        var guid=createGUID();
        if (guids.indexOf(guid)!=-1) {
            alert('Collision with ' + guid + ' after ' + i + ' iterations');
        }
        guids.push(guid);
    }
    alert(guids.length + ' iterations completed.');
}

testGUIDs(100000);

แน่ใจหรือว่าไม่มีอย่างอื่นเกิดขึ้นที่นี่?


4
ใช่ฉันทำการทดสอบในพื้นที่ด้วยและไม่มีการชนใด ๆ การชนกันเกิดขึ้นระหว่าง UUID ซึ่งสร้างขึ้นในเครื่องของผู้ใช้ที่แตกต่างกัน ฉันอาจต้องสร้างข้อมูลบางอย่างในเครื่องอื่นและตรวจสอบการชนกัน
Muxa

2
นอกจากนี้ฉันสังเกตเห็นว่าการชนกันระหว่าง UUID ซึ่งห่างกัน 3-4 สัปดาห์
Muxa

แปลกมาก. คุณกำลังทำงานบนแพลตฟอร์มใด
user533676

1
ดูเหมือนว่าไม่น่าจะมีข้อบกพร่องพื้นฐานใน Math.random () ของ V8 แต่ Chromium 11 ได้เพิ่มการสนับสนุนสำหรับการสร้างตัวเลขสุ่มที่แข็งแกร่งโดยใช้ window.crypto.getRandomValues ​​API หากคุณต้องการลองใช้แทน ดูblog.chromium.org/2011/06/… .
user533676

ทำงานร่วมกันระหว่าง Windows 7 และ Windows XP
Muxa

3

คำตอบที่โพสต์ครั้งแรกโซลูชัน UUID นี้ได้รับการอัปเดตเมื่อ 2017-06-28:

บทความดีดีจากนักพัฒนา Chromeถกรัฐที่มีคุณภาพ Math.random PRNG ใน Chrome, Firefox และ Safari tl; dr - เมื่อปลายปี 2015 "ค่อนข้างดี" แต่ไม่ใช่คุณภาพการเข้ารหัส เพื่อแก้ไขปัญหาดังกล่าวต่อไปนี้เป็นเวอร์ชันอัปเดตของโซลูชันข้างต้นซึ่งใช้ ES6, cryptoAPI และวิซาร์ด JS เล็กน้อยที่ฉันไม่สามารถให้เครดิตได้ :

function uuidv4() {
  return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, c =>
    (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
  )
}

console.log(uuidv4());


0

คำตอบที่นี่คือ "สาเหตุของปัญหาคืออะไร" (Chrome Math.random seed issue) แต่ไม่ใช่ "ฉันจะหลีกเลี่ยงได้อย่างไร"

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

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