# prize **Repository Path**: JKcoding/prize ## Basic Information - **Project Name**: prize - **Description**: 企业年会之红包雨场景实战 - **Primary Language**: Java - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 6 - **Forks**: 3 - **Created**: 2020-04-28 - **Last Updated**: 2022-05-30 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # prize ### 一、介绍 #####1、企业年会之红包雨场景实战 假设公司要开年会,让你设计一套红包雨项目,在某段时间内随机发放不同的礼品,你该如何设计呢?本项目实现了一个完整的红包雨模式抽奖系统,包括管理后台与前端界面。该项目由管理后台配置相关活动和奖品等信息,前端用户通过参与活动,完成抽奖。 ​ 1)电商活动 ​ 互联网的发展中,电商是典型的应用场景。电商,即买卖,就比如要抓住消费者的心理。那么各种促销、活动、成为电商公司必备的业务模块,尤其C端业务,面向普通大众用户。在所有活动中,抽奖是最典型的一种。 ​ 2)红包雨 ​ 阿里春节的红包雨大家都多有参与。很多公司争相模仿,以实现随机派发红包的形式完成宣传与促销。红包雨可以理解为抽奖的一种特殊形式,奖品即红包。 ​ 3)企业年会 ​ 年会基本是每个公司绕不开的话题。公司年会中的抽奖环节更是必不可少。尤其互联网公司,线上抽奖基本成为大家约定俗成的形式。打开手机,参与抽奖。正是本项目所涉及的现实应用场景。 ##### 2、系统要求 ​ 1)并发性 ​ 抽奖系统比如涉及到访问量大的问题。系统涉及所面临的第一关,即活动开始的瞬间,大批用户点击的涌入。怎样设计系统以达到如此高并发情况下的及时响应是本项目的重中之重。 ​ 2)库存控制 ​ 抽奖面临的必然是奖品。数量控制是必须要做到精准吻合。不允许出现设置了5个奖品,最终6人中奖这种类似的问题出现。其中的本质是奖品库存的控制。后续的章节中会详细介绍本项目中如何做到控制库存的。 ​ 3)投放策略 ​ 在活动时间段内,管理员设置好的一堆奖品如何投放?红包何时出现?年会奖品什么时候可以被抽中?这些都涉及到投放策略。本项目中会给大家展示最常见的一种策略,即在活动时间内,奖品随机出现。最后的课程会给出引申,如何灵活扩展实现其他的投放算法。 ​ 4)边界控制 ​ 活动何时开始?何时结束?倒计时如何控制。这涉及到活动的边界。开始前要提防用户提前进入抽奖。结束后要即使反馈结果给用户,告知活动已结束。 ​ 5)活动自由配置 ​ 活动的配置由后台管理员完成,可以自由配置活动的开始结束时间,主题、活动简介、有哪些奖品、不同等级的用户中奖的策略,这就要求系统必须具备足够的业务灵活度。 ​ 6)中奖策略 ​ 每个用户参与抽奖后,要遵从后台管理员所设定的中奖策略,典型的场景是针对用户设置最大中奖数。一旦用户中奖后,要进入计数,达到最大中奖数后,即使活动未结束,用户继续参与,也不能再让其中奖。而是将奖品机会倾向于其他参与者,后面中会为大家展示如何根据后台策略精确控制用户中奖数量。 ### 二、后台管理backend 1. 下载backend项目,并导入idea中 2. 导入resources下的sql到数据库中,并在pom文件的profile中配置数据库信息 (导入时会有几个视图无法创建,可以直接复制sql文件的视图语句到数据库中直接创建即可) 3. 启动 4. http://localhost:8888 ### 三、中间件 1. **redis** 1) **简介** ​ Redis是当前比较热门的NOSQL系统之一,它是一个开源的使用ANSI c语言编写的key-value存储系统。 2) **应用场景** 1. 缓存,这是Redis当今最为人熟知的使用场景。在提升服务器性能方面非常有效; 2. 排行榜,使用传统的关系型数据库(mysql oracle 等)来做这个事,非常的麻烦,而利用Redis的zset(有序集合)数据结构能够简单的搞定; 3. 计算器/限速器,利用Redis中原子性的自增操作,我们可以统计类似用户点赞数、用户访问数等,这类操作如 果用MySQL,频繁的读写会带来相当大的压力;限速器比较典型的使用场景是限制某个用户访问某个API的频率,常用的有抢购时,防止用户疯狂点击带来不必要的压力,在本项目中会用到该功能; 4. 好友关系,利用集合的一些命令,比如求交集、并集、差集等。可以方便解决一些共同好友、共同爱好之类的功能; 5. 简单消息队列,除了Redis自身的发布/订阅模式,我们也可以利用List来实现一个队列机制,比如:到货通知、邮件发送之类的需求,不需要高可靠,但是会带来非常大的DB压力,完全可以用List来完成异步解耦; 6. Session共享,借助spring-session,后端用Redis保存Session后,无论用户落在那台机器上都能够获取到对应的Session信息。 7. 热数据查询,一些频繁被访问的数据,经常被访问的数据如果放在关系型数据库,每次查询的开销都会很 大,而放在redis中,因为redis 是放在内存中的,会得到量级的提升。 3) **数据类型** ​ hset: key-fifield-value结构,用于一组相似类别的k-v值存储。类似java中的hashset工具 ​ list: 队列,可以实现左右进出操作。类比java中的LinkedList,本项目中的抽奖令牌桶,使用的就是list ​ kv: 最典型的缓存存储结构,value可以存储对应的对象。 ​ zset: 有序集合,在排序类,例如搜索词排名场景中会用到。 4) **相关命令** **对KEY操作的命令** ​ exists(key):确认一个key是否存在 ​ del(key):删除一个key ​ type(key):返回值的类型 ​ keys(pattern):返回满足给定pattern的所有key ​ randomkey:随机返回key空间的一个 key ​ rename(oldname, newname):重命名key ​ dbsize:返回当前数据库中key的数目 ​ expire:设定一个key的活动时间(s) ​ ttl:获得一个key的活动时间 ​ move(key, dbindex):移动当前数据库中的key到dbindex数据库 ​ flflushdb:删除当前选择数据库中的所有key ​ flflushall:删除所有数据库中的所有key **对String操作的命令** ​ set(key, value):给数据库中名称为key的string赋予值value ​ get(key):返回数据库中名称为key的string的value ​ getset(key, value):给名称为key的string赋予上一次的value ​ mget(key1, key2,…, key N):返回库中多个string的value ​ setnx(key, value):添加string,名称为key,值为value ​ setex(key, time, value):向库中添加string,设定过期时间time ​ mset(key N, value N):批量设置多个string的值 ​ msetnx(key N, value N):如果所有名称为key i的string都不存在 ​ incr(key):名称为key的string增1操作 ​ incrby(key, integer):名称为key的string增加integer ​ decr(key):名称为key的string减1操作 ​ decrby(key, integer):名称为key的string减少integer ​ append(key, value):名称为key的string的值附加value ​ substr(key, start, end):返回名称为key的string的value的子串 **对List操作的命令** ​ rpush(key, value):在名称为key的list尾添加一个值为value的元素 ​ lpush(key, value):在名称为key的list头添加一个值为value的元素 ​ llen(key):返回名称为key的list的长度 ​ lrange(key, start, end):返回名称为key的list中start至 end之间的元素 ​ ltrim(key, start, end):截取名称为key的list ​ lindex(key, index):返回名称为key的list中index位置的元素 ​ lset(key, index, value):给名称为key的list中index位置的元素赋值 ​ lrem(key, count, value):删除count个key的list中值为value的元素 ​ lpop(key):返回并删除名称为key的list中的首元素 ​ rpop(key):返回并删除名称为key的list中的尾元素 ​ blpop(key1, key2,… key N, timeout):lpop命令的block版本。 ​ brpop(key1, key2,… key N, timeout):rpop的block版本。 **对Set操作的命令** ​ sadd(key, member):向名称为key的set中添加元素member ​ srem(key, member) :删除名称为key的set中的元素member ​ spop(key) :随机返回并删除名称为key的set中一个元素 ​ smove(srckey, dstkey, member) :移到集合元素 ​ scard(key) :返回名称为key的set的基数 ​ sismember(key, member) :member是否是名称为key的set的元素 ​ sinter(key1, key2,…key N) :求交集 ​ sinterstore(dstkey, (keys)) :求交集并将交集保存到dstkey的集合 ​ sunion(key1, (keys)) :求并集 ​ sunionstore(dstkey, (keys)) :求并集并将并集保存到dstkey的集合 ​ sdiffff(key1, (keys)) :求差集 ​ sdiffffstore(dstkey, (keys)) :求差集并将差集保存到dstkey的集合 ​ smembers(key) :返回名称为key的set的所有元素 ​ srandmember(key) :随机返回名称为key的set的一个元素 **对Hash操作的命令** ​ hset(key, fifield, value):向名称为key的hash中添加元素fifield ​ hget(key, fifield):返回名称为key的hash中fifield对应的value ​ hmget(key, (fifields)):返回名称为key的hash中fifield i对应的value ​ hmset(key, (fifields)):向名称为key的hash中添加元素fifield ​ hincrby(key, fifield, integer):将名称为key的hash中fifield的value增加integer ​ hexists(key, fifield):名称为key的hash中是否存在键为fifield的域 ​ hdel(key, fifield):删除名称为key的hash中键为fifield的域 ​ hlen(key):返回名称为key的hash中元素个数 ​ hkeys(key):返回名称为key的hash中所有键 ​ hvals(key):返回名称为key的hash中所有键对应的value ​ hgetall(key):返回名称为key的hash中所有的键(fifield)及其对应的value 5) **三种模式** ​ **主从复制**: 主从模式指的是使用一个redis实例作为主机,其余的实例作为备份机。主机和从机的数据完全一致,主机支持数据的写入和读取等各项操作,而从机则只支持与主机数据的同步和读取。主从模式很好的解决了数据备份问题,并且由于主从服务数据几乎是一致的,因而可以将写入数据的命令发送给主机执行,而读取数据的命令发送给不同的从机执行,从而达到读写分离的目的。 ​ **哨兵**: 哨兵模式是一种特殊的模式,哨兵是一个独立的进程,独立运行。 当哨兵监测到master宕机,会自动将slave切换成master,然后通过发布订阅模式通知其他的从服务器,修改配置文件,让它们切换主机。做到了主从自动切换和高可用。 ​ **集群:** Redis Cluster是一个高性能高可用的分布式系统,由多个Redis实例组成的整体,数据按照Slot存储分布在多个Redis实例上,通过Gossip协议来进行节点之间通信。 6)**持久化** ​ Redis支持RDB和AOF两种持久化机制,持久化功能有效地避免因进程退出造成的数据丢失问题,当下次重启时利用之前持久化文件即可实现数据恢复。 ​ **rdb:** ​ 在指定时间间隔内,将内存中的数据集快照写入磁盘,也就是Snapshot快照,它恢复时是将快照文件直接读到内存中,来达到恢复数据的,rdb也是redis默认的持久化方式。Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写进一个临时文件中,等到持久化过程结束了,再用这个临时文件替换上次持久化好的文件。在这个过程中,只有子进程来负责IO操作,主进程仍然处理客户端的请求,这就确保了极高的性能。 ​ **优点** ​ 适合大规模数据恢复的场景,数据紧凑,易迁移 ​ **缺点** ​ RDB这种持久化方式数据完整性很难保证,虽然我们可以用过修改持久化的频率,但是如果还没有触发快照时,本机就宕机了,那么对数据库所做的写操作就丢失了。 每次进行RDB时,父进程都会fork一个子进程,由子进程来进行实际的持久化操作,数据量大时,那么fork出子进程的这个过程将是非常耗时的。 ​ **aof:** ​ 以日志的形式记录Redis每一个写操作,将Redis执行过的所有写指令记录下来,注意,读操作是不需要记录的,redis启动之后会读取appendonly.aof文件,将之前的操作复现,来完成恢复数据的工作。 ​ **appendfsync always:** ​ 每修改同步,每一次发生数据变更都会持久化到磁盘上,性能较差,但数据完整性较好。 ​ **appendfsync everysec:** ​ 每秒同步,每秒内记录操作,异步操作,如果一秒内宕机,有数据丢失。 ​ **appendfsync no:**不同步。 ​ **重写**:当然如果AOF 文件一直被追加,这就可能导致AOF文件过于庞大。因此,为了避免这种状况,Redis新增了重写机制,当AOF文件的大小超过所指定的阈值时,Redis会自动启用AOF文件的内容压缩,只保留可以恢复数据的最小指令集 ​ **优点** ​ 看上去轻量化,增量形式 ​ 保留了redis的历史操作 ​ 多种策略配置,相对灵活 ​ **缺点** ​ 对于相同的数据集来说,AOF文件要比RDB文件大。 根据所使用的持久化策略来说,AOF的速度要慢与RDB。 2. zookeeper 3. rabbitmq 4. nginx ### 四、系统设计 #### zkui zookeeper的ui界面 1、在config.cfg中配置地址zkServer(根据自己的实际情况) 2、serverPort为ui界面访问端口,默认是 localhost:9090 3、修改配置后,更新配置到zkui.jar文件中, 使用命令: jar uvf zkui.jar config.cfg 4、运行: java -jar zkui.jar #### frontend 1. xxxx 2. xxxx 3. xxxx 1.