Redis
 sql >> डेटाबेस >  >> NoSQL >> Redis

डेटा स्टोर के रूप में Redis के साथ एक एप्लिकेशन डिज़ाइन करना। क्या? क्यों?

1) परिचय

सभी को नमस्कार! बहुत से लोग जानते हैं कि रेडिस क्या है, और यदि आप नहीं जानते हैं, तो आधिकारिक साइट आपको अपडेट कर सकती है।
अधिकांश रेडिस के लिए एक कैश और कभी-कभी एक संदेश कतार होती है।
लेकिन क्या होगा अगर हम थोड़ा पागल हो जाएं और डेटा स्टोरेज के रूप में केवल रेडिस का उपयोग करके पूरे एप्लिकेशन को डिज़ाइन करने का प्रयास करें? रेडिस के साथ हम किन कार्यों को हल कर सकते हैं?
हम इस लेख में इन सवालों के जवाब देने की कोशिश करेंगे।

हम यहां क्या नहीं देखेंगे?

  • हर रेडिस डेटा संरचना विस्तार से यहां नहीं होगी। आपको किन उद्देश्यों के लिए विशेष लेख या दस्तावेज़ पढ़ना चाहिए।
  • यहां कोई प्रोडक्शन-रेडी कोड भी नहीं होगा जिसे आप अपने काम में इस्तेमाल कर सकें।

हम यहां क्या देखेंगे?

  • डेटिंग एप्लिकेशन के विभिन्न कार्यों को लागू करने के लिए हम विभिन्न रेडिस डेटा संरचनाओं का उपयोग करेंगे।
  • यहां कोटलिन + स्प्रिंग बूट कोड उदाहरण होंगे।

2) उपयोगकर्ता प्रोफ़ाइल बनाना और क्वेरी करना सीखें।

  • सबसे पहले, आइए जानें कि उपयोगकर्ता प्रोफाइल को उनके नाम, पसंद आदि के साथ कैसे बनाया जाता है।

    ऐसा करने के लिए, हमें एक साधारण की-वैल्यू स्टोर की आवश्यकता है। यह कैसे करना है?

  • बस। एक रेडिस की एक डेटा संरचना होती है - एक हैश। संक्षेप में, यह हम सभी के लिए एक परिचित हैश मैप है।

रेडिस क्वेरी भाषा कमांड यहां और यहां पाई जा सकती है।
दस्तावेज़ में इन आदेशों को सीधे पृष्ठ पर निष्पादित करने के लिए एक इंटरैक्टिव विंडो भी है। और पूरी कमांड सूची यहां पाई जा सकती है।
इसी तरह के लिंक बाद के सभी आदेशों के लिए काम करते हैं जिन पर हम विचार करेंगे।

कोड में, हम लगभग हर जगह RedisTemplate का उपयोग करते हैं। स्प्रिंग इकोसिस्टम में रेडिस के साथ काम करने के लिए यह एक बुनियादी बात है।

यहां मानचित्र से एक अंतर यह है कि हम "फ़ील्ड" को पहले तर्क के रूप में पास करते हैं। "फ़ील्ड" हमारे हैश का नाम है।

fun addUser(user: User) {
        val hashOps: HashOperations<String, String, User> = userRedisTemplate.opsForHash()
        hashOps.put(Constants.USERS, user.name, user)
    }

fun getUser(userId: String): User {
        val userOps: HashOperations<String, String, User> = userRedisTemplate.opsForHash()
        return userOps.get(Constants.USERS, userId)?: throw NotFoundException("Not found user by $userId")
    }

ऊपर एक उदाहरण है कि स्प्रिंग के पुस्तकालयों का उपयोग करके कोटलिन में यह कैसा दिख सकता है।

उस लेख के सभी कोड आप Github पर पा सकते हैं।

3) Redis सूचियों का उपयोग करके उपयोगकर्ता पसंद को अपडेट करना।

  • महान!। हमारे पास पसंद के बारे में उपयोगकर्ता और जानकारी है।

    अब हमें एक तरीका खोजना चाहिए कि उस पसंद को कैसे अपडेट किया जाए।

    हम मानते हैं कि घटनाएं बहुत बार हो सकती हैं। तो आइए कुछ कतार के साथ एक अतुल्यकालिक दृष्टिकोण का उपयोग करें। और हम कतार से जानकारी को एक शेड्यूल पर पढ़ेंगे।

  • Redis में इस तरह के आदेशों के साथ एक सूची डेटा संरचना है। आप Redis सूचियों का उपयोग FIFO कतार और LIFO स्टैक दोनों के रूप में कर सकते हैं।

वसंत ऋतु में हम RedisTemplate से ListOperations प्राप्त करने के समान दृष्टिकोण का उपयोग करते हैं।

हमें दाहिनी ओर लिखना है। क्योंकि यहां हम दाएं से बाएं फीफो कतार का अनुकरण कर रहे हैं।

fun putUserLike(userFrom: String, userTo: String, like: Boolean) {
        val userLike = UserLike(userFrom, userTo, like)
        val listOps: ListOperations<String, UserLike> = userLikeRedisTemplate.opsForList()
        listOps.rightPush(Constants.USER_LIKES, userLike)
}

अब हम अपना काम तय समय पर चलाने जा रहे हैं।

हम केवल एक रेडिस डेटा संरचना से दूसरे में जानकारी स्थानांतरित कर रहे हैं। यह हमारे लिए एक उदाहरण के तौर पर काफी है।

fun processUserLikes() {
        val userLikes = getUserLikesLast(USERS_BATCH_LIMIT).filter{ it.isLike}
        userLikes.forEach{updateUserLike(it)}
}

उपयोगकर्ता अद्यतन यहाँ वास्तव में आसान है। पिछले भाग से हैशऑपरेशन को नमस्ते करें।

private fun updateUserLike(userLike: UserLike) {
        val userOps: HashOperations<String, String, User> = userLikeRedisTemplate.opsForHash()
        val fromUser = userOps.get(Constants.USERS, userLike.fromUserId)?: throw UserNotFoundException(userLike.fromUserId)
        fromUser.fromLikes.add(userLike)
        val toUser = userOps.get(Constants.USERS, userLike.toUserId)?: throw UserNotFoundException(userLike.toUserId)
        toUser.fromLikes.add(userLike)

        userOps.putAll(Constants.USERS, mapOf(userLike.fromUserId to fromUser, userLike.toUserId to toUser))
    }

और अब हम दिखाते हैं कि सूची से डेटा कैसे प्राप्त करें। हम इसे बाईं ओर से प्राप्त कर रहे हैं। सूची से डेटा का एक गुच्छा प्राप्त करने के लिए हम एक range . का उपयोग करेंगे तरीका।
और एक महत्वपूर्ण बिंदु है। रेंज विधि केवल सूची से डेटा प्राप्त करेगी, लेकिन इसे हटा नहीं देगी।

इसलिए हमें डेटा डिलीट करने के लिए दूसरे तरीके का इस्तेमाल करना होगा। trim कर दो। (और वहां आपके कुछ प्रश्न हो सकते हैं)।

private fun getUserLikesLast(number: Long): List<UserLike> {
        val listOps: ListOperations<String, UserLike> = userLikeRedisTemplate.opsForList()
        return (listOps.range(Constants.USER_LIKES, 0, number)?:mutableListOf()).filterIsInstance(UserLike::class.java)
            .also{
listOps.trim(Constants.USER_LIKES, number, -1)
}
}

और सवाल ये हैं:

  • सूची से डेटा को कई थ्रेड्स में कैसे प्राप्त करें?
  • और कैसे सुनिश्चित करें कि त्रुटि के मामले में डेटा खो नहीं जाएगा? बॉक्स से - कुछ भी नहीं। आपको सूची से डेटा एक थ्रेड में प्राप्त करना होगा। और आपको अपने आप पैदा होने वाली सभी बारीकियों को संभालना होगा।

4) पब/उप का उपयोग करने वाले उपयोगकर्ताओं को पुश नोटिफिकेशन भेजना

  • आगे बढ़ते रहें!
    हमारे पास पहले से ही उपयोगकर्ता प्रोफ़ाइल हैं। हमें पता चला कि इन उपयोगकर्ताओं की पसंद की धारा को कैसे संभालना है।

    लेकिन उस मामले की कल्पना करें जब आप किसी उपयोगकर्ता को एक पुश सूचना भेजना चाहते हैं, जिस क्षण हमें पसंद आया।
    आप क्या करने वाले हैं?

  • पसंद को संभालने के लिए हमारे पास पहले से ही एक एसिंक्रोनस प्रक्रिया है, तो चलिए वहां केवल पुश नोटिफिकेशन भेजने का निर्माण करते हैं। हम निश्चित रूप से उस उद्देश्य के लिए WebSocket का उपयोग करेंगे। और हम इसे केवल वेबसाकेट के माध्यम से भेज सकते हैं जहां हमें एक पसंद मिलता है। लेकिन क्या होगा अगर हम भेजने से पहले लंबे समय से चल रहे कोड को निष्पादित करना चाहते हैं? या क्या होगा यदि हम WebSocket के साथ किसी अन्य घटक को कार्य सौंपना चाहते हैं?
  • हम अपना डेटा एक रेडिस डेटा संरचना (सूची) से दूसरे (पब/उप) में फिर से लेंगे और स्थानांतरित करेंगे।
fun processUserLikes() {
        val userLikes = getUserLikesLast(USERS_BATCH_LIMIT).filter{ it.isLike}
                pushLikesToUsers(userLikes)
        userLikes.forEach{updateUserLike(it)}
}

private fun pushLikesToUsers(userLikes: List<UserLike>) {
  GlobalScope.launch(Dispatchers.IO){
        userLikes.forEach {
            pushProducer.publish(it)
        }
  }
}
@Component
class PushProducer(val redisTemplate: RedisTemplate<String, String>, val pushTopic: ChannelTopic, val objectMapper: ObjectMapper) {

    fun publish(userLike: UserLike) {
        redisTemplate.convertAndSend(pushTopic.topic, objectMapper.writeValueAsString(userLike))
    }
}

विषय के लिए बाध्यकारी श्रोता विन्यास में स्थित है।
अब, हम अपने श्रोता को एक अलग सेवा में ले जा सकते हैं।

@Component
class PushListener(val objectMapper: ObjectMapper): MessageListener {
    private val log = KotlinLogging.logger {}

    override fun onMessage(userLikeMessage: Message, pattern: ByteArray?) {
        // websocket functionality would be here
        log.info("Received: ${objectMapper.readValue(userLikeMessage.body, UserLike::class.java)}")
    }
}

5) भौगोलिक संचालन के माध्यम से निकटतम उपयोगकर्ताओं को ढूँढना।

  • हम पसंद के साथ कर रहे हैं। लेकिन किसी दिए गए बिंदु पर निकटतम उपयोगकर्ताओं को खोजने की क्षमता के बारे में क्या।

  • जियोऑपरेशंस इसमें हमारी मदद करेगी। हम की-वैल्यू पेयर को स्टोर करेंगे, लेकिन अब हमारा वैल्यू यूजर कोऑर्डिनेट है। खोजने के लिए हम [radius](https://redis.io/commands/georadius) का इस्तेमाल करेंगे तरीका। हम उपयोगकर्ता आईडी को खोजने के लिए और खोज त्रिज्या को ही पास करते हैं।

हमारे यूजर आईडी सहित रेडिस रिटर्न परिणाम।

fun getNearUserIds(userId: String, distance: Double = 1000.0): List<String> {
    val geoOps: GeoOperations<String, String> = stringRedisTemplate.opsForGeo()
    return geoOps.radius(USER_GEO_POINT, userId, Distance(distance, RedisGeoCommands.DistanceUnit.KILOMETERS))
        ?.content?.map{ it.content.name}?.filter{ it!= userId}?:listOf()
}

6) स्ट्रीम के माध्यम से उपयोगकर्ताओं के स्थान को अपडेट करना

  • हमने लगभग हर उस चीज को लागू किया जिसकी हमें जरूरत है। लेकिन अब हमारे पास फिर से एक स्थिति है जब हमें डेटा को अपडेट करना होगा जो जल्दी से संशोधित हो सकता है।

    इसलिए हमें फिर से एक कतार का उपयोग करना होगा, लेकिन कुछ और स्केलेबल होना अच्छा होगा।

  • Redis धाराएं इस समस्या को हल करने में मदद कर सकती हैं।
  • शायद आप काफ्का के बारे में जानते हैं और शायद आप काफ्का धाराओं के बारे में भी जानते हैं, लेकिन यह रेडिस धाराओं के समान नहीं है। लेकिन काफ्का अपने आप में रेडिस स्ट्रीम के समान ही है। यह एक लॉग फॉरवर्ड डेटा संरचना भी है जिसमें उपभोक्ता समूह और ऑफसेट है। यह एक अधिक जटिल डेटा संरचना है, लेकिन यह हमें समानांतर में डेटा प्राप्त करने और प्रतिक्रियाशील दृष्टिकोण का उपयोग करने की अनुमति देती है।

विवरण के लिए रेडिस स्ट्रीम दस्तावेज़ देखें।

<ब्लॉकक्वॉट>

रेडिस डेटा संरचनाओं के साथ काम करने के लिए स्प्रिंग में ReactiveRedisTemplate और RedisTemplate है। हमारे लिए मूल्य लिखने के लिए RedisTemplate और पढ़ने के लिए ReactiveRedisTemplate का उपयोग करना अधिक सुविधाजनक होगा। अगर हम धाराओं के बारे में बात करते हैं। लेकिन ऐसे मामलों में कुछ भी काम नहीं करेगा।
अगर किसी को पता है कि यह इस तरह क्यों काम करता है, तो स्प्रिंग या रेडिस के कारण, टिप्पणियों में लिखें।

fun publishUserPoint(userPoint: UserPoint) {
    val userPointRecord = ObjectRecord.create(USER_GEO_STREAM_NAME, userPoint)
    reactiveRedisTemplate
        .opsForStream<String, Any>()
        .add(userPointRecord)
        .subscribe{println("Send RecordId: $it")}
}

हमारी श्रोता विधि इस तरह दिखेगी:

@Service
class UserPointsConsumer(
    private val userGeoService: UserGeoService
): StreamListener<String, ObjectRecord<String, UserPoint>> {

    override fun onMessage(record: ObjectRecord<String, UserPoint>) {
        userGeoService.addUserPoint(record.value)
    }
}

हम बस अपने डेटा को भौगोलिक डेटा संरचना में ले जाते हैं।

7) HyperLogLog का उपयोग करके अद्वितीय सत्रों की गणना करें।

  • और अंत में, आइए कल्पना करें कि हमें यह गणना करने की आवश्यकता है कि प्रति दिन कितने उपयोगकर्ताओं ने एप्लिकेशन में प्रवेश किया है।
  • इसके अलावा, ध्यान रखें कि हमारे पास बहुत सारे उपयोगकर्ता हो सकते हैं। इसलिए, हैश मैप का उपयोग करने वाला एक सरल विकल्प हमारे लिए उपयुक्त नहीं है क्योंकि यह बहुत अधिक मेमोरी की खपत करेगा। हम कम संसाधनों का उपयोग करके ऐसा कैसे कर सकते हैं?
  • एक संभाव्य डेटा संरचना HyperLogLog वहां चलन में आती है। आप इसके बारे में विकिपीडिया पृष्ठ पर अधिक पढ़ सकते हैं। एक प्रमुख विशेषता यह है कि यह डेटा संरचना हमें हैश मैप वाले विकल्प की तुलना में काफी कम मेमोरी का उपयोग करके समस्या को हल करने की अनुमति देती है।


fun uniqueActivitiesPerDay(): Long {
    val hyperLogLogOps: HyperLogLogOperations<String, String> = stringRedisTemplate.opsForHyperLogLog()
    return hyperLogLogOps.size(Constants.TODAY_ACTIVITIES)
}

fun userOpenApp(userId: String): Long {
    val hyperLogLogOps: HyperLogLogOperations<String, String> = stringRedisTemplate.opsForHyperLogLog()
    return hyperLogLogOps.add(Constants.TODAY_ACTIVITIES, userId)
}

8) निष्कर्ष

इस लेख में, हमने विभिन्न रेडिस डेटा संरचनाओं को देखा। इतने लोकप्रिय भू संचालन और हाइपरलॉग सहित नहीं।
हमने वास्तविक समस्याओं को हल करने के लिए उनका इस्तेमाल किया।

हमने टिंडर को लगभग डिजाइन कर दिया है, यह इसके बाद FAANG में संभव है)))
साथ ही, हमने मुख्य बारीकियों और समस्याओं पर प्रकाश डाला जो रेडिस के साथ काम करते समय सामने आ सकती हैं।

रेडिस एक बहुत ही कार्यात्मक डेटा भंडारण है। और अगर आपके पास यह पहले से ही आपके बुनियादी ढांचे में है, तो रेडिस को अनावश्यक जटिलताओं के बिना आपके अन्य कार्यों को हल करने के लिए एक उपकरण के रूप में देखने लायक हो सकता है।

पुनश्च:
सभी कोड उदाहरण जीथब पर देखे जा सकते हैं।

अगर आपको कोई गलती नजर आती है तो कमेंट में लिखें।
कुछ तकनीक का उपयोग करके वर्णन करने के तरीके के बारे में नीचे एक टिप्पणी छोड़ें। क्या आपको यह पसंद है या नहीं?

और मुझे ट्विटर पर फॉलो करें:🐦@de____ro


  1. Redis
  2.   
  3. MongoDB
  4.   
  5. Memcached
  6.   
  7. HBase
  8.   
  9. CouchDB
  1. रेडिस मेमोरी और सीपीयू स्पाइक्स

  2. FindByIndexNameSessionRepository की बीन कैसे बनाएं?

  3. एक सीमित सीमा से अद्वितीय आईडी बनाने के लिए रेडिस का उपयोग करें

  4. स्प्रिंग एप्लिकेशन में लेन-देन के अंदर Async का उपयोग करना

  5. पाइथन का उपयोग करके रेडिस में कॉम्प्लेक्स नेस्टेड JSON को कैसे स्टोर करें