热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Go实战:抽奖系统核心技术点实现

Go实战:抽奖系统核心技术点实现考验系统并发能力最常见的无非是秒杀、抽奖等业务场景。我们不妨以一个支持高并发的抽奖系统的设计和实现为例来进行Go语言的实战学习。要

Go 实战 :抽奖系统核心技术点实现

考验系统并发能力最常见的无非是秒杀、抽奖等业务场景。我们不妨以一个支持高并发的抽奖系统的设计和实现为例来进行 Go 语言的实战学习。

要实现一个抽奖系统,我们首先需要一个比较合理的抽奖算法,保证每个人抽到奖品的概率是一致的,同时又需要避免奖品被很快抽完的情况。所以本文的第一部分会用三种思路来讲解如何去实现一个合理的抽奖算法。

要具备高并发的能力,Go 的 http 组件是支持高并发能力的(底层由 Go 的协程支持),但是数据存储层也需要支持高并发、高吞吐量的存储系统,mysql 肯定是支持不了的,所以这里采用 Redis 实现。所以本文第二部分会介绍如何使用 Go 去操作 Redis。

抽奖系统中,高并发请求下,很容易出现两个线程或者协程同时对共享内存中的数据进行更改的问题。所以为了保证数据的原子性,我们对数据的操作都是以 CAS 的方式。Redis 中可以实现对数据的原子增减,但是无法实现数据的翻倍或者其他改变。为了支持所有场景,同时为了学习知识,这里我们借用 Redis 的事务来实现 Redis 的 CAS。所以第三部分主要介绍 Go 操作 Redis 来实现 cas

整个抽奖系统概览如下,本章 chat 主要阐述其中的部分实现核心点,包括:使用 Go 实现抽奖算法,Go 操作 Redis ,以及通过 Redis 实现 cas

Go 实现抽奖算法

抽奖算法是决定一个抽奖系统是否成功的关键要素之一

我们先来假设一下场景: 假如目前公司需要举行一个活动,一共有A、B、C 三种奖品。奖品数量分别为 200、2000、5000、需要在 6.1 ~ 6.3 这三种奖品发完。

最容易想到的一种做法是对 奖品 A、B、C 分别设置一个概率,每个用户根据设置好的概率进行派奖。但仔细想想这样会存在一个问题:每个活动我们对于抽奖的人数预测都是未知的。当抽奖人数多或者概率很高时,奖品很容易在很短的时间内被发完。当抽奖人数少或者概率很低时,奖品很有可能到最后还有很多都没发完… 所以完全凭借设置抽奖概率来抽奖是不太靠谱的。

为了避免上述情况,于是我们开始思考,既然抽奖的人数是未知的,我们无法控制抽奖人数,那能不能换个思路,我们来控制奖品的发放时间呢?在上面假设的场景中,我们分别有 A、B、C 三种奖品 200、2000、5000 个。需要在 6.1 日 0:00 ~ 6.3 日 24:00 点这段期间进行抽奖。也就是说一共有 7200 奖品,在 72 个小时内抽完。假如我们把这 72 个小时按照时间片进行分割,是不是就能防止奖品被抽完或者过剩太多的情况了?

由公式

ΔΔ t = endTime−startTimeawardAmountendTime−startTimeawardAmount 计算可以得出时间间隔为 1/100 小时,也就是 36s 随机发一个奖品

此时,在 ΔΔ t 时间内 A 被抽中的概率是 200 / (200 + 2000 + 5000) = 1/36 , B 被抽中的概率是 2000 / (200 + 2000 + 5000) = 10/36 ,C 被抽中的概率是 5000 / (200 + 2000 + 5000) = 25 /36

算法的基本思路确定后,怎么去实现呢?

我们将奖品分成 7200 份之后,每一个奖品都会有一个抽奖时间段,在这个时间段内奖品会在某一个时间点进行发放。假如用户抽奖的时间点在这个时间点之前,因为奖品没有发放,所以无法中奖。在这个时间点之后,则有概率获得奖品,这个概率可以由用户进行配置。假如有一个用户抽中了奖品,则其他用户在这个时间段内再也无法抽中奖品了。

思路一

第一种思路就是通过结构化数据库去实现,例如 mysql

这里需要建立三张表

  • 奖品信息表 : award_info

    • 奖品记录表 : award_record

    活动开始前,需要根据奖品信息表 award_info 里面的数据初始化奖池表 award_pool。用户抽奖时通过以下 sql 进行抽奖

select id from award_pool where award_release_time <&#61; now() and award_release_num > 0 limit 1

如果能够查询到数据&#xff0c;则执行更新 update 语句&#xff0c;否则抽奖失败

update award_pool set award_release_num &#61; award_release_num - 1 where id &#61; #{id} and award_release_num > 0

如果 update 执行成功&#xff0c;用户中奖&#xff0c;写中奖纪录到 award_record 表&#xff0c;否则抽奖失败

思路二

在思路一中&#xff0c;在抽奖之前&#xff0c;我们对需要初始化 awardpool 这张表来进行发奖。抽奖时&#xff0c;需要执行一条 select 语句 和 一条 update 语句。这种做法&#xff0c;在奖品量非常多时&#xff0c;初始化 awardpool 这张表会浪费很多时间&#xff0c;在用户并发请求量非常大&#xff0c;select 语句也会消耗很多性能。于是有了第二种思路&#xff0c;不进行奖品初始化操作&#xff0c;而是通过实时计算的方式进行抽奖。

计算奖品释放时间点 &#xff1a;

具体实现&#xff1a;

func GetAward(awardBatches []AwardBatch) (AwardBatch, error) {startTime , _ :&#61; ParseStringToTime(conf.Award.StartTime)endTime , _ :&#61; ParseStringToTime(conf.Award.EndTime)fmt.Println("startTime : ", startTime)fmt.Println("endTime : ", endTime)award , err :&#61; RandomGetAwardBatch(awardBatches)if err !&#61; nil {return AwardBatch{}, err}totalAmount :&#61; award.GetTotalAmount()totalBalance :&#61; award.GetTotalBalance()updateTime :&#61; award.GetUpdateTime()detaTime :&#61; (endTime - startTime) / totalAmountcurrentTime :&#61; time.Now().Unix()random :&#61; rand.New(rand.NewSource(updateTime))// 计算下一个奖品的释放时间releaseTime :&#61; startTime &#43; (totalAmount - totalBalance) * detaTime &#43; int64(random.Int()) % detaTimefmt.Println("releaseTime : " &#43; fmt.Sprintf("%d", releaseTime) &#43; " currentTime : " &#43; fmt.Sprintf("%d",currentTime))if (currentTime }func RandomGetAwardBatch(awardBatches []AwardBatch) ( AwardBatch , error ) {if len(awardBatches) &#61;&#61; 0 {return AwardBatch{} , errors.New("empty param awardBatches")}weight :&#61; int64(0)for _, awardBatch :&#61; range awardBatches {weight &#43;&#61; awardBatch.GetTotalBalance()}if weight &#61;&#61; 0 {return AwardBatch{}, errors.New("weight is 0")}random :&#61; rand.New(rand.NewSource(weight))num :&#61; random.Int63n(weight)for _, awardBatch :&#61; range awardBatches {num -&#61; awardBatch.GetTotalBalance()if num <0 {return awardBatch , nil}}return AwardBatch{}, errors.New("randomGetAwardBatch should shoot at least one batch")}

思路三

思路二相对于思路一来说&#xff0c;节省了初始化 award_pool 奖池 和 select 操作的时间。但还是会存在一个问题&#xff0c;由于采用的是关系型数据库 mysql 存储&#xff0c;所以能够支持并发数是有限的。当抽奖用户量很大时&#xff0c;数据库肯定是无法支撑的。所以为了能够支持高并发&#xff0c;我们需要摈弃 mysql &#xff0c;采取高性能、支持高并发量的 k/v 式数据库。这里我们使用 Redis

为了达到思路二类似的效果&#xff0c;我们设计两种数据结构 &#xff1a;

奖品种类、奖品总数和中奖时间采用 zset 进行存储&#xff0c;zset 中 value 为奖品名称&#xff0c;value 的分值 score 为奖品的中奖时间。通过 ZRANGE award_info 0 -1 WITHSCORES 即可得到所有奖品以及每种奖品的更新抽奖时间

奖品剩余数量采用哈希表进行存储&#xff0c;这样通过 HGETALL 即可查询到所有奖品以及奖品剩余数量

基于这种思路&#xff0c;我们就有了第三种代码实现如下&#xff08;这里只贴部分核心代码&#xff0c;完整代码请参考 GitHub&#xff09;

// 随机抽取一个奖品func RandomGetAwardBatch() \*AwardBatch {conn :&#61; RedisWrapper.GetConn()if conn &#61;&#61; nil {log.Println("conn is nil")return nil}defer conn.Close()retMap, err :&#61; Redis.Int64Map(conn.Do("HGETALL", getAwardBalanceKey()))if err !&#61; nil || retMap &#61;&#61; nil {log.Println("Redis HGETALL award error", err)return nil}totalBalance :&#61; int64(0)for _, value :&#61; range retMap {totalBalance &#43;&#61; value}fmt.Println("retMap : ", retMap)if totalBalance &#61;&#61; 0 {log.Println("total balance is 0")return nil}log.Println("totalBalance :", totalBalance)awardBatches :&#61; GetAllAwardBatch()for index , awardBatch :&#61; range awardBatches {awardBatches[index].totalBalance &#61; retMap[awardBatch.GetName()]}log.Println("awardBatches :", awardBatches)random :&#61; rand.New(rand.NewSource(totalBalance))num :&#61; random.Int63n(totalBalance)for _ , awardBatch :&#61; range awardBatches {// 奖品已经抽完if awardBatch.GetTotalBalance() <&#61; 0 {log.Println("奖品已经抽完")continue}num &#61; num - awardBatch.GetTotalBalance()if num <0 {return &awardBatch}}return nil}func GetAwardBatch() *AwardBatch {awardBatch :&#61; RandomGetAwardBatch()if awardBatch &#61;&#61; nil {log.Println("sorry, you didn&#39;t win the prize.")return nil}// 判断是否到达奖品释放时间点startTime , _ :&#61; ParseStringToTime(Conf.Award.StartTime)endTime , _ :&#61; ParseStringToTime(Conf.Award.EndTime)totalAmount :&#61; awardBatch.totalAmounttotalBalance :&#61; awardBatch.totalBalancelastUpdateTime :&#61; awardBatch.GetUpdateTime()random :&#61; rand.New(rand.NewSource(lastUpdateTime))detaTime :&#61; (endTime - startTime) / awardBatch.totalAmount// 计算下一个奖品释放的时间点releaseTime :&#61; startTime &#43; (totalAmount - totalBalance) * detaTime &#43; int64(random.Int()) % detaTimelog.Println("relaseTime : ", time.Unix(releaseTime, 0).Format("2006-01-02 15:04:05"))if time.Now().Unix()

以上就是三种实现抽奖算法的思路&#xff0c;以及抽奖算法的 Go 实现。对比思路一、二、三&#xff0c;最终我们决定使用思路三进行实现。在思路上中&#xff0c;牵涉到对 Redis 的操作&#xff0c;所以这里我们需要先了解使用 Go 如何进行 Redis 操作。

Go 操作 Redis

Redis 是一个开源的高性能 key-value 存储系统&#xff0c;被广泛运用于高并发读写请求的业务场景。对 Redis 不了解的同学可以先去熟悉一下&#xff1a;https://Redis.io/documentation

那么 Go 是如何支持 Redis 操作的呢&#xff1f; 这里就不得不提到 rediGo , GitHub 地址为 GitHub.com/Gomodule/rediGo 下面可能会用一个高并发抽奖系统的场景来具体讲解 Redis 的操作和 cas 的实现

rediGo是 Go 语言的一个 Redis 客户端实现

安装 rediGo

Go get GitHub.com/garyburd/rediGo/Redis

对一个第三方包的了解首先从 GitHub 上开始&#xff0c;这里我们进入rediGo GitHub 官方 api https://Godoc.org/GitHub.com/Gomodule/rediGo/Redis 通过Godoc 文档我们很快可以快速上手

建立连接

首先来看官方 Godoc 介绍

ConnectionsThe Conn interface is the primary interface for working with Redis.Applications create connections by calling the Dial,DialWithTimeout or NewConn functions. In the future, functions will be added for creating sharded and other types of connections.

通过 Godoc 可知&#xff0c;Redis 建立连接有三种方式&#xff0c;Dial、DialWithTimeout、NewConn。

我们只需采用其中一种方式就可以快速建立连接 :

package Redis import ( "fmt" "GitHub.com/garyburd/rediGo/Redis" "log" ) // 返回一个 Redis 连接 func GetConn() Redis.Conn{conn , err :&#61; Redis.Dial("tcp", fmt.Sprintf("%s:%d", "127.0.0.1", 6379))if err !&#61; nil {log.Println("connect Redis error ", err)return nil}return conn}

需要注意的是&#xff0c;上述方法返回了一个 Redis 连接&#xff0c;取得这个连接之后&#xff0c;一定要记得关闭。如下 &#xff1a;

conn :&#61; getConn()if conn &#61;&#61; nil {log.Println("conn is nil")}defer conn.Close()

执行 Redis 命令

通过 Godoc 得知&#xff0c;我们有两种方式可以执行 Redis 命令

方式一 &#xff1a; 通过 Conn 接口的 Do(commandName string, args …interface{}) 方法

Do(commandName string, args ...interface{}) (reply interface{}, err error)例如 &#xff1a;n, err :&#61; conn.Do("APPEND", "key", "value")在抽奖系统中&#xff0c;我们需要通过读取 Redis 来获取所有奖品信息&#xff0c;代码如下&#xff1a;// 这里封装了一个方法 返回一个 Redis Connconn :&#61; RedisWrapper.GetConn()if conn &#61;&#61; nil {log.Println("conn is nil")return awardBatches}// 注意关闭 Redis conndefer conn.Close()// 获取存放奖品信息的 Redis keyawardInfoKey :&#61; getAwardInfoKey()// 调用 Do 方法读取对应的奖品信息 &#xff0c;通过 Redis.Values 方法转化为一个数组values , err :&#61; Redis.Values(conn.Do("ZRANGE",awardInfoKey,0,-1,"WITHSCORES"))

方式二 &#xff1a;通过 pipeline

Redis pipeline 可以一次性发送多条命令并在执行完后一次性将结果返回&#xff0c;pipeline 的底层实现是队列&#xff0c;所以是 FIFO&#xff0c;可以保证队列中命令执行的顺序性。

实现一个 pipeline 需要下面三个方法&#xff1a;

// 往队列中添加指令Send(commandName string, args ...interface{}) error// 一次性发送指令到服务端Flush() error// 接收服务端返回的数据Receive() (reply interface{}, err error)

例如在抽奖系统中&#xff0c;我们需要初始化奖品信息&#xff0c;此时我们使用的是 pipeline ,代码如下 &#xff1a;

func InitAwardPool() {// 这里封装了一个方法 返回一个 Redis Connconn :&#61; RedisWrapper.GetConn()if conn &#61;&#61; nil {log.Println("conn is nil")return}defer conn.Close()conn.Send("ZADD", getAwardInfoKey(), time.Now().Unix(), "A")conn.Send("ZADD", getAwardInfoKey(), time.Now().Unix(), "B")conn.Send("ZADD", getAwardInfoKey(), time.Now().Unix(), "C")conn.Send("HSET", getAwardBalanceKey(), "A", Conf.AwardMap["A"])conn.Send("HSET", getAwardBalanceKey(), "B", Conf.AwardMap["B"])conn.Send("HSET", getAwardBalanceKey(), "C", Conf.AwardMap["C"])conn.Flush()for i :&#61; 0 ; i <6; i&#43;&#43; {// 接收数据&#xff0c;这里不需要处理接收的数据&#xff0c;所以用 \__ , err :&#61; conn.Receive()if err !&#61; nil {log.Println("conn send error", err)}}}



实现 CAS

在高并发抽奖系统的场景中&#xff0c;假设我们有一个对当前奖品余量减少 1 的操作。 对奖品余量数的更改&#xff0c;我们希望应该是原子操作。更改包括两个操作&#xff1a;

a. 读出 Redis 余量数 b. 设置余量数为当前余量数 - 1

这里包括一读一写操作。 由于 Redis 是单进程&#xff0c;所以在进行单独读操作和单独写操作都是线程安全的。但是读 &#43; 写就不一定安全了。

这里举个例子&#xff1a;

假如 balance 为 200, 有 A、B 两个客户端请求&#xff0c;都需要进行 balance - 1 操作&#xff0c; 如下图 &#xff1a;

我们希望的结果是 A、B 都按照 读 —— 写 —— 读 —— 写的顺序执行&#xff0c;最终的结果是 198 。但由于 Redis 是单条指令执行&#xff0c;所以有可能 A 读 —— B 读 —— A 写 —— B 写 &#xff0c;这样的执行顺序&#xff0c;A、B 读取出来的 balance 都是 200 &#xff0c;A 执行写入 balance - 1 &#61; 199 , B 同时执行 balance - 1 &#61; 199 , 所以最终 balance &#61; 199 &#xff0c; 而不是我们预期的 198。

要避免上述情况&#xff0c;我们希望读 &#43; 写是一个原子操作。也就是说一定是 读 —— 写 —— 读 —— 写 这种顺序&#xff0c;读和写操作之间不能插入其他的读或者写操作。这其实就是一种 CAS 能力。但是 Redis 是没有提供这种能力的&#xff0c;这就需要我们自己基于 Redis 去实现 CAS

1. 什么是 CAS &#xff1f;

CAS 即 CompareAndSwap &#xff08;比较并替换&#xff09;的缩写

CAS 需要有3个操作数&#xff1a;内存地址 V&#xff0c;旧的-size-adjust:none;-webkit-font-smoothing:antialiased;box-sizing:border-box;margin:0px 0px 1.1em;outline:0px;">2. Redis 事务

要了解 Redis CAS 的实现&#xff0c;首先要了解以下 Redis 的事务机制。

事务一般有三个步骤 &#xff1a; begin、commit、rollback 。 begin 指示事务的开始&#xff0c;commit 指示事务的提交&#xff0c;rollback 指示事务的回滚

在 Redis 中对应的命令是 &#xff1a;multi、exec、discard。 multi 指示事务的开始&#xff0c;exec 指示事务的执行&#xff0c;discard 指示事务的丢弃

这里可以参考一下 Redis 的 文档 , 地址为 &#xff1a; http://Redisdoc.com/transaction/index.html

通过阅读文档可知&#xff0c; Redis 事务并没有实现原子性&#xff0c;即使事务中有某个/某些命令在执行时产生了错误&#xff0c; 事务中的其他命令仍然会继续执行。

例如 &#xff1a;

可以看到&#xff0c;在执行 incr award 这条指令的时候报错了&#xff0c;但是后面的 set book Golang 这条指令仍然能正确执行。

3. watch 命令

在了解了 Redis 的事务机制之后&#xff0c;我们发现 Redis 事务机制并不支持原子性。所以通过 Redis 事务还是无法实现 CAS &#xff0c;这里就需要引入 watch 命令了。

使用 watch 命令可以监控 Redis 的某个 key&#xff0c;假如在 Redis 事务执行 exec 过程中&#xff0c;这个 key 发生了变动&#xff0c;则该事务不会被执行。

为了演示 watch 命令&#xff0c;这里我们新启两个 Redis 客户端&#xff0c;如下&#xff1a;

客户端 1 &#xff1a;

hing:antialiased;box-sizing:border-box;margin:0px 0px 1.1em;outline:0px;">客户端 2 :

在客户端 1 执行完 watch watchedKey 和 multi 命令后&#xff0c;客户端 2 执行 set 命令&#xff0c;我们可以看到此时客户端 1 的 set 命令会执行失败。说明在事务执行过程中&#xff0c;假如 watch 的 key 有更改&#xff0c;则关于该事务会执行失败。

4. Redis CAS 实现

通过 Redis 事务和 watch 命令&#xff0c;我们可以通过 Redis 去实现 CAS&#xff0c;在抽奖系统中&#xff0c;我们也应用了 CAS&#xff0c;相关代码如下 &#xff1a;

// 更新 lastUpdateTime 和 balance&#xff0c; 如果更新不成功&#xff0c;视为抽奖失败conn :&#61; RedisWrapper.GetConn()if conn &#61;&#61; return nil}defer conn.Close()conn.Send("WATCH", getAwardBalanceKey())conn.Send("MULTI")conn.Send("ZADD",getAwardInfoKey(),time.Now().Unix(), awardBatch.GetName())conn.Send("HSET",getAwardBalanceKey(), awardBatch.GetName(), awardBatch.totalBalance - 1)conn.Send("EXEC")err :&#61; conn.Flush()if err !&#61; nil {log.Println("Redis error, " , err)return nil}


抽中奖品之后&#xff0c;我们需要对剩余奖品数进行更新操作&#xff0c;上面代码通过 watch 命令去监视了保存奖品余量的哈希表&#xff0c;假如对奖品剩余数进行更新操作的时候&#xff0c;有其他协程修改奖品剩余数&#xff0c;则事务执行失败。此抽奖过程没有完成&#xff0c;视为用户没有中奖。

结语

至此&#xff0c;我们已经实现了一个支持高并发的抽奖系统的核心代码&#xff0c;要实现一个抽奖系统&#xff0c;除了抽奖算法、使用 Redis 存储数据以及 CAS 之外&#xff0c;还需要会使用 Go 的基本语法&#xff0c;了解 Go 的 http 模块的使用、log 的使用、第三方包的使用、配置文件的解析等。其实总的代码并不复杂&#xff0c;代码量也不多&#xff0c;所以其他部分读者可以自行去研究一下。欢迎读者圈交流


欢迎关注我的公众号&#xff0c;回复关键字“Golang” &#xff0c;将会有大礼相送&#xff01;&#xff01;&#xff01; 祝各位面试成功&#xff01;&#xff01;&#xff01;


推荐阅读
  • 本文介绍了进程的基本概念及其在操作系统中的重要性,探讨了进程与程序的区别,以及如何通过多进程实现并发和并行。文章还详细讲解了Python中的multiprocessing模块,包括Process类的使用方法、进程间的同步与异步调用、阻塞与非阻塞操作,并通过实例演示了进程池的应用。 ... [详细]
  • This article explores the process of integrating Promises into Ext Ajax calls for a more functional programming approach, along with detailed steps on testing these asynchronous operations. ... [详细]
  • 探索CNN的可视化技术
    神经网络的可视化在理论学习与实践应用中扮演着至关重要的角色。本文深入探讨了三种有效的CNN(卷积神经网络)可视化方法,旨在帮助读者更好地理解和优化模型。 ... [详细]
  • GreenPlum采纳ShareNothing的架构,良好的施展了便宜PC的作用。自此IO不在是DW(datawarehouse)的瓶颈,相同网络的压力会大很多。然而GreenPlum的查问优化策略可能防止尽量少的网络替换。对于首次接触GreenPlum的人来说,必定耳目一新。 ... [详细]
  • 基于51单片机的多项目设计实现与优化
    本文探讨了基于51单片机的多个项目的设计与实现,包括PID控制算法的开关电源设计、八音电子琴仿真设计、智能抽奖系统控制设计及停车场车位管理系统设计。每个项目均采用先进的控制技术和算法,旨在提升系统的效率、稳定性和用户体验。 ... [详细]
  • 本文探讨了在Go语言中处理切片并发修改时如何有效避免竞争条件的方法。 ... [详细]
  • 本文介绍如何通过创建数据库触发器来限制Oracle数据库中特定用户的登录IP地址,以增强系统的安全性。示例代码展示了如何阻止非授权IP地址的登录尝试。 ... [详细]
  • 本文将详细介绍如何配置并整合MVP架构、Retrofit网络请求库、Dagger2依赖注入框架以及RxAndroid响应式编程库,构建高效、模块化的Android应用。 ... [详细]
  • 本文汇集了作者在准备研究生入学考试过程中的心得体会,包括备考策略、复习重点及应对考试的心理调适技巧,旨在为即将参加考研的学生提供实用建议。 ... [详细]
  • 使用R语言进行Foodmart数据的关联规则分析与可视化
    本文探讨了如何利用R语言中的arules和arulesViz包对Foodmart数据集进行关联规则的挖掘与可视化。文章首先介绍了数据集的基本情况,然后逐步展示了如何进行数据预处理、规则挖掘及结果的图形化呈现。 ... [详细]
  • 一家位于长沙的知名网络安全企业,现面向全国诚聘高级后端开发工程师,特别欢迎具有一线城市经验的技术精英回归故乡,共创辉煌。 ... [详细]
  • 流处理中的计数挑战与解决方案
    本文探讨了在流处理中进行计数的各种技术和挑战,并基于作者在2016年圣何塞举行的Hadoop World大会上的演讲进行了深入分析。文章不仅介绍了传统批处理和Lambda架构的局限性,还详细探讨了流处理架构的优势及其在现代大数据应用中的重要作用。 ... [详细]
  • Redis:缓存与内存数据库详解
    本文介绍了数据库的基本分类,重点探讨了关系型与非关系型数据库的区别,并详细解析了Redis作为非关系型数据库的特点、工作模式、优点及持久化机制。 ... [详细]
  • Redis 是一个高性能的开源键值存储系统,支持多种数据结构。本文将详细介绍 Redis 中的六种底层数据结构及其在对象系统中的应用,包括字符串对象、列表对象、哈希对象、集合对象和有序集合对象。通过12张图解,帮助读者全面理解 Redis 的数据结构和对象系统。 ... [详细]
  • RocketMQ在秒杀时的应用
    目录一、RocketMQ是什么二、broker和nameserver2.1Broker2.2NameServer三、MQ在秒杀场景下的应用3.1利用MQ进行异步操作3. ... [详细]
author-avatar
用户19910071
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有