项目问题啊

平台项目

1.介绍一下

这个项目的整体结构来源于今日头条网页版,我们是借鉴并且自己做出了一些拓展,采用前后端分离的模式开发的。主要是使用了springboot,mybaits mysql,redis,kafka 等工具。只要是有两大模块,前台和后台,前台的话主要是实现了用户的登录,注册,发布文章,点赞,收藏,评论,搜索,按照点击量排序等功能,后台的话主要是系统权限的动态管理,还有一些数据的维护,热点数据图的展示等。另外引入redis来提升网站的性能,实现了用户注册,点赞,收藏,关注等。基于kafka实现了系统通知,让用户点赞评论后可以获得系统的通知,还实现了一些比如说使用aop来完成ip地址的记录,敏感词的过滤等等。主要是这些功能。

2.项目负责什么

我是负责登录注册模块,敏感词过滤模块,点赞收藏模块,发送系统消息模块,热点数据的展示,还有后台的权限动态管理的设计,还负责部署项目等等。

3.项目难点,怎么解决

难点的话我觉得话费时间比较长的是点赞模块的设计,因为点赞这个操作第一次写点时候是直接写到数据库里面的,然后大家就开始讨论逻辑是否合理,

如果按照第一次这样写的话,那么每次刷新页面就要去访问数据库,还有就是如果说重复点赞的话,对数据库的压力也是比较大的。后来我们就讨论是否可以用缓存来减少一些消耗,第

二次重写逻辑,使用redis缓存来存放一个点赞记录,这样的话如果说进入页面显示数据的时候,如果点过赞了就不用去数据库查询了 因为在点赞发时候就会存放一条点赞记录放进redia里面,重复点赞的话那么如果说缓存里面有数据,那么就是取消点赞了 进行操作数据库和前端的高亮显示。是提升了一部分效率。

然后再进行复盘 看看还有没有什么可以优化,于是大家一起讨论并且去网络上搜索一些比较成熟的逻辑去看看人家是怎么做的,然后对比现有逻辑,看是否需要更改。然后发现这样还是有一些多余的操作,我们可以把点赞记录数据放到缓存里面,每次前端的响应都去访问缓存,这样的话就不用去给数据库压力了。然后使用定时任务去将缓存中的数据写入到数据库中。使用quartz定时任务来完成缓存和数据库的数据的同步。经过几次逻辑的更新,才达到一个相对来说还行的实现。

4.登录怎么做的

cookie,session,jwttoken

cookie和session区别
cookie存放于客户端,session放于服务器端
cookie存放容量<=4kb,session无线
cookie对客户端是可见的,session不可见,比cookie安全一点
cookie浏览器管理后可以不失效,session浏览器管理了就失效了。
cookie支持跨域,sesion不支持。
session存在服务器端,每个用户都有一个,并发量大的情况下,会有消耗。
cookie和session各自的使用场景

对于敏感数据,应存放在session里面,因为cookie不安全,
对于普通数据,放在cookie里面,这样可以减少对服务器资源的占用。
session工作原理
session是依赖于cookie的,当客户端首次访问服务器时 ,服务器会为其创建一个session对象,该对象有一个唯一标识sessionid,并且在响应阶段,服务器回创建一个cookie,并将sessionid存入其中。客户端通过响应的cookie从而拿到sessionid,当他再次访问服务器时,会通过cookie携带这个sessionid,服务器取到sessionid后,就可以找到与之对应的session对象,进而进入这个session中获取改客户端的状态。
cookie原理
由服务器产生内容,浏览器收到请求后保存在本地;当浏览器再次访问时,浏览器会自动带上cookie,这样服务器就能通过cookie的内容来判断这个是“谁”了。

jwt基本原理

JWT是json web token缩写。它将用户信息加密到token里,服务器不保存任何用户信息。服务器通过使用保存的密钥验证JWTToken的正确性,只要正确即通过验证。

JWT包含三个部分: Header头部,Payload负载和Signature签名。由三部分生成JwtToken,三部分之间用“.”号做分割。 校验也是JWT内部自己实现的 ,并且可以将你存储时候的信息从JwtToken中取出来无须查库

客户端使用用户名跟密码请求登录

服务端收到请求,去验证用户名与密码

验证成功,服务端会签发一个JwtToken,无须存储到服务器,直接再把这个JwtToken发送给客户端

客户端收到JwtToken以后可以把它存储起来,比如放在 Cookie 里或者 Local Storage 里

客户端每次向服务端请求资源的时候需要带着服务端签发的JwtToken

服务端收到请求,验证密客户端请求里面带着的 JwtToken, 如果验证成功,就向客户端返回请求的数据.

token基本原理

Token基本原理
Token(就是加密的字符串,使用MD5,等不可逆加密算法,一定要保证唯一性)

客户端使用用户名跟密码请求登录

服务端收到请求,去验证用户名与密码

验证成功,服务端会签发一个Token保存到(Session,redis,mysql…)中,然后再把这个 Token 发送给客户端

客户端收到 Token 以后可以把它存储起来,比如放在 Cookie 里或者 Local Storage 里

客户端每次向服务端请求资源的时候需要带着服务端签发的 Token

服务端收到请求,验证密客户端请求里面带着的 Token和服务器中保存的Token进行对比效验, 如果验证成功,就向客户端返回请求

token和jwttoken区别

服务端验证客户端发来的token信息要进行数据的查询操作;

JWT验证客户端发来的token信息就不用, 在服务端使用密钥校验就可以,不用数据库的查询。

5.跨域怎么解决

1.在SpringBoot中注入Cors配置类,可根据需求自行设定。

2.实现WebMvcConfigurer并配置addCorsMappings方法中的跨域条件

6.reids怎么用的

在注册的时候,我们采用的是邮箱验证码来代替手机验证码的功能,但是通常验证码不是长久有用的数据,他只会在几分钟之内有效,失效之后就要删除他,redis正好有自动删除功能,所以验证码首先是存在redis里面的。使用邮箱来当做key,验证码当做value。设置了1分钟的有效时间。
还有就是点赞的时候,我们把一些数据存到了red is。会把点赞记录都放进redis里面,redis里面的计数器可以很好的支持点赞这个场景。
还有就是文章排行榜,zset排序
和浏览量通过计数器点开就自增等等

7.为什么用kafka,不用其他的mq,

我们这个就是系统通知,我认为是比较看中消息的顺序,和吞吐量这些特性的,在了解了kafka和rabbitmq这两款之后,我们都假设了用会出现什么情况,对于rabbitmq来说,当他发消息的时候,会为每一个消费者建立一个对应的队列,也就是说有10个人点赞就有10个队列,我们紧接着面临的问题就是,我们应该在系统内部启动多少线程去从消息队列中获取消息。
如果只是单线程去获取消息,那自然没有什么好说的。但是多线程情况,可能就会有问题了。

rabbitmq官方说自己是不保证多线程消费同一个队列的消息,一定保证顺序的。而不保证的原因,是因为多线程时,当一个线程消费消息报错的时候,RabbitMQ 会把消费失败的消息再入队,此时就可能出现乱序的情况。也就是说有3个问题,1.为了实现发布订阅功能,从而使用的消息复制,会降低性能并耗费更多资源。2.多个消费者无法严格保证消息顺序3.吞吐量不太高。
而对应kafka的话,Kafka 的发布订阅并不会复制消息,因为 Kafka 的发布订阅就是消费者直接去获取被 Kafka 保存在日志文件中的消息就好。无论是多少消费者,他们只需要主动去找到消息在文件中的位置即可。
其次,Kafka 不会出现消费者出错后,把消息重新入队的现象。
最后,Kafka 可以对订单进行分区,把不同订单分到多个分区中保存,这样,吞吐量能更好。
所以,对于这个需求 Kafka 更合适。

kakfa架构

在这里插入图片描述

  • Broker:一个从生产者接受并存储消息的客户端

Topic

Topic 被称为主题,在 Kafka 中,使用一个类别属性来划分消息的所属类,划分消息的这个类称为 Topic。

Topic 相当于消息的分配标签,是一个逻辑概念。主题好比是数据库的表,或者文件系统中的文件夹。

Partition

Partition 译为分区,Topic 中的消息被分割为一个或多个的 Partition,它是一个物理概念,对应到系统上就是一个或若干个目录,一个分区就是一个提交日志。消息以追加的形式写入分区,先后以顺序的方式读取。

为提高吞吐量和拓展,一个topic分为多个partion

配合分区的设计,提出消费者组的概念,组内每个消费者并行消费

为了提高可用,每个partion有若个副本,一个挂了还能用副本。

leader:只对leader进行消费,和follwer没关系,

follower;当leader挂了,follower可以变为leader。

zookeeper是记录服务器节点运行的状态,记录分区leader的相关信息。

kafka和其他mq区别

1.语言不同:kafka是scala语言开发的,主要是用于处理活跃的流式数据,大数据量的数据处理上。

rabbitmq是erlang语言的,用在实时的对消息可靠性比较高得到消息传递时。

kafka如何保证消息不丢失

kafka丢失消息的话分为几种情况,比如说生产者端丢消息,kafka自己丢消息,消费者端丢消息等。
生产者丢消息就是生产者内有设置响应的策略,发送过程中丢失数据。
kafka自己丢消息是比较场景的一个场景,就是kafka的某个broke宕机了,然后重新选举partiton的leader时,如果此时follow还没来得及同步数据,leader就挂了,然后某个follow成为了leader,他就少了一部分数据。
消费端丢消息 消费者消费到了这个数据,然后消费者自己又提交了offset,让kafka知道你已经消费了这个消息,当你准备处理这个消息时,自己挂掉了,那么这条消息就丢了。

处理方案:

避免丢失的方法,生产者丢消息,关闭自动提交offset,在自己处理完毕之后手动提交offset,这样就不会丢失数据。

kafka自己丢消息的话,一般要求设置4个参数来保证消息不丢失,1.给topic设置replication.factory参数,这个值必须大于1,表示要求每个partition必须要有2个副本。2.在kafka端设置replicas参数大于1,
表示要求一个leader至少一个follow在跟自己保持联系正常同步数据,这样可以保证leader挂了之后还有一个follower。
3.在生产者端设置acks=all,表示要求每条数据必须是写入所有的replica副本之后,才能认为是写入成功了。4.在生产者端设置retires=max 表示这个是要求一担写入事变,就无限重试。
消费端丢消息,如果说已经设置了ack=all ,则一定不会丢失数据,你的leader接受到消息,所有的follow都同步到了这个消息之后,才认为本次写成功了,如果不满足的话,生产者会自动不断的重试,重试无限,

kafka如何保证顺序消费

对于kafka来说,一个topic下面的同一个partition中的消息肯定都是有序的,生产者在写的时候可以指定一个key,通常我们会用订单号来作为key,这个key对应的消息都会发送到同一个partiton中,所以消费者消费到的消息也一定是有序的。
那么kafka为什么还会出现消息错乱的问题呢,问题是出现在消费者身上,通常我们消费到同一个key的多条消息后,会使用多线程技术去并发处理来提高消息的处理速度,否则一条消息的处理需要几十毫秒,1秒钟也就只能处理几十条消息,吞吐量就太低了,而多线程并发处理的话,binlog执行到数据库就不一定是原来的顺序。
kafka从生产者到消费者消费消息这一整个过程其实都是可以保证有序的,导致最终乱序是由于消费者端需要使用多线程并发处理消息来提高吞吐量,比如消费者消费到了消息以后,开启32个线程处理消息,每个线程处理消息的快慢是不一致的,所以才会导致最终的消息有可能不一致。
对于kafka的消息顺序性保证,其实我们只需要保证同一个订单号的消息被同一个线程处理即可。因此我们可以在线程处理之前增加一个内存队列,每个线程只负责处理其中一个内存队列中的消息,同一个订单号的消息发送到同一个内存队列中即可。

8.敏感词过滤是怎么做的

敏感词过滤敏感词过滤是通过trie字典树来实现的,字典树的特点
1.根节点不包含字符,除根节点外每一个节点都只包含一个字符

2.从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串

3.每个节点的所有子节点包含的字符都不相同

大概逻辑就是:

在操作之前,设计了一个init方法,使用postconstruct注解,让bean初始化之前执行,init方法是使用字符流读取存放敏感词的文件,读取每一个单词,并把它们添加到字典树中。添加到字典树是把每一个字母都添加到树中。然后设置每一个节点的标记,在长度达到敏感词长度的时候,也就是最有一个字母的时候,标记为true,其余都是flase.然后进行搜索即可

搜索原理:1.从根节点开始搜索,取得要查找的关键词的第一个字母,并根据该字母选择对应的子树并转到对应的子树进行查找

2.再子树上, 关键词的第二个字母,并选择对应的子树查找第三个字母。

3.一直迭代上面的过程

4.如果在某个节点处,关键词的所有字母都被取出来了,则读取路径上的信息,完成查找

5.敏感词对比的话,如果查找过程中某一个字母在子树中不存在,则第一个字母开头的敏感词就不存在,就从第一个字母的下一个字母为首开始查找

6.如果句子结束了,敏感词还没查找完毕,就算没找到敏感词,7如果找到了,那么就找到了一个敏感词,把从第一个字母到最后一个字母替换为**。

刚开始处理的时候,有一个问题是一个敏感词是另一个敏感词前缀的问题,例如hello,是敏感词,hell也是敏感词,hel也是敏感词,如果把这三个单词都加入字典树中,就只会过滤hello,hell和hel就无法识别。后来解决给每一个节点都加上一个标记true或者false,是否是结束字母,例如hello五个节点,前四个都是false,最后一个是true,代表字母结束。再过滤的时候,回去检查这个标记,如果是true,则记录下来,后面的有找到就再更新记录,保证是最长的敏感词,如果找到了叶子节点,就是没到最后一个,例如找到了hel,l结尾,则hel就是敏感词,如果没有,例如he就继续往下一个节点找。

9.权限模块

权限管理权限管理的话是使用的是管理员,角色,菜单三个部分来实现的,就是管理员或者其他用户可以有相对应的角色,而他们的角色可以分配不同的菜单操作权限来进行操作。这样可以动态的改变权限,更好的操作。5张表,admin,role,menu,admin_role,role_menu

商城项目

1.项目介绍

整个项目是我们实习时做的一个项目。采用springboot+mybaits+ thymeleaf+reids+rabbitmq来开发的。

主要是包含三大模块,用户模块,商品模块和下单模块。用户模块的话它主要是实现了登录注册,用户信息包括订单记录,收货地址等功能;在商品模块包含商品搜索,商品的展示,商品的管理,订单页面的数据等功能;再下单模块的话是包括商品正常的下单,秒杀商品的秒杀,退货等功能的实现.这就是主要的功能。

2.项目负责什么,

我是参与了从需求分析到测试的一系列流程,在代码方面的话,主要是实现了用户的登录,注册,对用户密码的二次加密,分布式session来解决session不共享的问题。再商品模块,主要是实现了商品的下单,商品的秒杀,还有就是对秒杀的一些优化,来提高效率。主要就是这些。

3.难点,怎么解决

我觉得商城项目还是比较复杂的,就我负责的部分的话,我觉得比较难一点的就是一些性能上的优化,因为如果说仅仅是吧逻辑写出来的话,我觉得也不是那么的难,因为基础的逻辑也就那么几大步骤,但是秒杀这一块是一个高并发的场景,他很容易会压垮数据库或者redis的。而且再真是使用的时候,肯定是对用户有比较好的体验,不然我觉得设计的就会很失败。如果说没有优化的话,那么系统就会很卡的,用户体验也就不太好了。

对性能的优化,我们在讨论和综合网上资料以后,我们小组提出了3条优化方向。

1.是页面的静态化,因为每个用户如果说都进入页面,页面就要重新加载一下,但是页面又是一模一样的,所以这就造成了大量的消耗。我们把页面和商品信息还有一些比较重要的数据都放入redis缓存里面。

2.是接口逻辑的优化,首先就是缓存预减少再redsi里面,然后如果后面没库存了,那么一直访问redis也不好,采用内存标记确定是否访问redis。再下单的时候,把逻辑放入消息队列来实现一个削锋的作用,等等。

3.是安全方面的优化,比如书接口限流,防止压垮数据库,地址隐藏,放在提前抢购,验证码校验,放在脚本操作等。

就感觉这一块还是比较吃力的,但是最后也是合作解决了问题,做完这些我觉得对我的思维逻辑的提升也是比较大的。

4.优化

静态化:页面静态化,包括对秒杀商品的静态化,商品详情页面的静态化,

接口优化:redis优化,redis进行预减库存,如果后面没库存了,那么一直访问redis也不好,采用内存标记确定是否访问redis。

下单逻辑优化,请求进入队列缓冲,异步下单,减少损耗

安全优化:接口限流,地址隐藏,验证码校验

5.超卖

在进行初始化的时候就把商品的信息包含数量添加到redis里面。每次秒杀成功在redis里面预减库存,减少数据库压力,每次秒杀前从redis里面查库存数量,如果说库存数量为0了,那么就不能继续秒杀了,都得返回秒杀失 败。

如果说有库存的话,把请求封装到消息队列里面生成订单。订单入队,出队,然后操作数据库,慢慢处理。

异步的好处可以进行流量削峰,

客户端一直轮询判断是否秒杀成功,告诉用户结果。

遇见库存是采用了一个分布式锁来实现的,

lua脚本可以在redis里面原子性的执行多条命令,而且redis性能受网络影响稍微大一点,我们可以使用lua脚本让多个命令依次执行,这样可以有效的解决网络带来的一些性能问题。

再redistemplate里面,启动前,把lua脚本提前发给redis,让他做一个缓存,然后会返回一个redisScript对象。我们就可以掉这个对象了。

1.在系统初始化时,将商品以及对应的库存数量预先加载到Redis缓存中;(缓存预热)
2.接收到秒杀请求时,在Redis中进行预减库存(decrement),当Redis中的库存不足时,直接返回秒杀失败,否则继续进行第3步;
3.将请求放入异步队列中,返回正在排队中;
4.服务端异步队列(MQ)将请求出队,出队成功的请求可以生成秒杀订单,减少数据库库存,返回秒杀订单详情。

6.rabbitmq消息模式

rabbitmq里面5种工作模式

1、简单队列,一个生产者对应一个消费者!!

2、work 模式

一个生产者对应多个消费者,但是一条消息只能有一个消费者获得消息!!!
轮询分发就是将消息队列中的消息,依次发送给所有消费者。一个消息只能被一个消费者获取。

3、发布/订阅模式

一个消费者将消息首先发送到交换器,交换器绑定到多个队列,然后被监听该队列的消费者所接收并消费。

4、路由模式

生产者将消息发送到direct交换器,在绑定队列和交换器的时候有一个路由key,生产者发送的消息会指定一个路由key,那么消息只会发送到相应key相同的队列,接着监听该队列的消费者消费消息。

也就是让消费者有选择性的接收消息。
路由模式,是以路由规则为导向,引导消息存入符合规则的队列中。再由队列的消费者进行消费的。

5、主题模式

上面的路由模式是根据路由key进行完整的匹配(完全相等才发送消息),这里的通配符模式通俗的来讲就是模糊匹配。

这五种工作模式,可以归为三类:

生产者,消息队列,一个消费者;
生产者,消息队列,多个消费者;
生产者,交换机,多个消息队列,多个消费者;

4种交换机

1、direct 如果路由键完全匹配的话,消息才会被投放到相应的队列。

2、fanout 当发送一条消息到fanout交换器上时,它会把消息投放到所有附加在此交换器上的队列。

3、topic 设置模糊的绑定方式,“*”操作符将“.”视为分隔符,匹配单个字符;“#”操作符没有分块的概念,它将任意“.”均视为关键字的匹配部分,能够匹配多个字符。

4、header 消息的 header 而非路由键,除此之外,header 交换器和 direct 交换器完全一致,但是性能却差很多,因此基本上不会用到该交换器

rabbitmq如何保证顺序消费

对于rabbitmq,导致上面顺序错乱的原因通常是消费者是集群部署,不同的消费者消费到了同一个订单的不同的消息。例如a执行了增加,b执行了修改,c执行了删除,但是c执行比b执行快,b又比a快,就会导致消费binlog执行到数据库的时候顺序错乱,执行顺序本来是增加,修改,删除,变成了 删除,修改,增加。

这主要是由于不同的消息都发送到了同一个queue中,多个消费者都消费到了同一个queue的消息。

我们可以给rabbitmq创建多个queue,每个消费者固定消费一个queue的消息,生产者发送消息的时候,同一个订单号的消息发送到同一个queue中,由于同一个queue的消息一定是会保证有序的,那么同一个订单号的消息就只会被一个消费者顺序消费,从而可以保证有序性。

redis分布式锁是怎么实现的:

实现分布式锁需要实现获取锁和释放锁

获取锁的话,是要保证互斥的,即保证一次只能有一个线程获得锁,可以使用sset lock thread1nx ex 20,顺便给锁加上超时自动释放,避免线程阻塞锁不能正常释放,造成死锁问题。

释放锁的话就是手动释放锁,也就是删除锁。释放锁的时候要先判断线程的标识是否和自己是一致的,避免删错锁了,如果是一致的则可以删除锁。

使用set nx可以满足互斥性。

加上过期时间可以保证锁的释放

用redis集群保证高可用和高并发。

redisson 也是现成的分布式锁

可重入:利用hash结构记录线程id和重入次数

可重试:利用信号量和pubsub功能实现等待,唤醒,获取锁失败的重试时机。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值