论坛系统
项目介绍本项目是一套前后端不分离的论坛系统,基于目前主流 Java Web 技术栈(SpringBoot + MyBatis + MySQL + Redis + Kafka + Elasticsearch + Spring Security + …),包含了帖子、评论、私信、系统通知、点赞、关注、搜索、用户设置、数据统计等模块。
可访问地址:吃瓜论坛
相关技术
项目结构后端本项目后端采用 MVC 模式,使用现在流行的 SpringBoot 框架。
entity
User:数据库模型,对应表 user
Comment:数据库模型,对应表 comment
DiscussPost:数据库模型,对应表 discuss_post
Message:数据库模型,对应表 message
Page:分页模型
Event:事件模型(Spring Kafka)
LoginTicket:登录凭证
dao
CommentMapper:控制 Comment 的相关数据库操作
DiscussPostMapper:控制 DiscussPost 的相关数据库操作
MessageMapper:控制 Message ...
Redis问题汇总和相关解决方案
本文收集了一些 Redis 使用中经常遇到的一些问题,和与之相对应的解决方案。
缓存雪崩缓存雪崩是指在短时间内,有大量缓存同时过期,导致大量的请求直接查询数据库,从而对数据库造成了巨大的压力,严重情况下可能会导致数据库宕机的情况叫做缓存雪崩。
我们先来看下正常情况下和缓存雪崩时程序的执行流程图,正常情况下系统的执行流程如下图所示:
缓存雪崩的执行流程,如下图所示:
以上对比图可以看出缓存雪崩对系统造成的影响,那如何解决缓存雪崩的问题?
缓存雪崩的常用解决方案有以下几个。
加锁排队加锁排队可以起到缓冲的作用,防止大量的请求同时操作数据库,但它的缺点是增加了系统的响应时间,降低了系统的吞吐量,牺牲了一部分用户体验。
加锁排队的代码实现,如下所示:
1234567891011121314151617181920// 缓存 keyString cacheKey = "userlist";// 查询缓存String data = jedis.get(cacheKey);if (StringUtils.isNotBlank(data)) { // 查询到数据 ...
Redis是如何执行的
命令执行流程一条命令的执行过程有很多细节,但大体可分为:客户端先将用户输入的命令,转化为 Redis 相关的通讯协议,再用 socket 连接的方式将内容发送给服务器端,服务器端在接收到相关内容之后,先将内容转化为具体的执行命令,再判断用户授权信息和其他相关信息,当验证通过之后会执行最终命令,命令执行完之后,会进行相关的信息记录和数据统计,然后再把执行结果发送给客户端,这样一条命令的执行流程就结束了。如果是集群模式的话,主节点还会将命令同步至子节点,下面我们一起来看更加具体的执行流程。
步骤一:用户输入一条命令
步骤二:客户端先将命令转换成 Redis 协议,然后再通过 socket 连接发送给服务器端
客户端和服务器端是基于 socket 通信的,服务器端在初始化时会创建了一个 socket 监听,用于监测链接客户端的 socket 链接,源码如下:
12345678void initServer(void) { //...... // 开启 Socket 事件监听 if (server.port != 0 && liste ...
Redis持久化
Redis 的读写都是在内存中,所以它的性能较高,但在内存中的数据会随着服务器的重启而丢失,为了保证数据不丢失,我们需要将内存中的数据存储到磁盘,以便 Redis 重启时能够从磁盘中恢复原有的数据,而整个过程就叫做 Redis 持久化。
Redis 持久化也是 Redis 和 Memcached 的主要区别之一,因为 Memcached 不具备持久化功能。
持久化的几种方式Redis 持久化拥有以下三种方式:
快照方式(RDB, Redis DataBase)将某一个时刻的内存数据,以二进制的方式写入磁盘;
文件追加方式(AOF, Append Only File),记录所有的操作命令,并以文本的形式追加到文件中;
混合持久化方式,Redis 4.0 之后新增的方式,混合持久化是结合了 RDB 和 AOF 的优点,在写入的时候,先把当前的数据以 RDB 的形式写入文件的开头,再将后续的操作命令以 AOF 的格式存入文件,这样既能保证 Redis 重启时的速度,又能减低数据丢失的风险。
因为每种持久化方案,都有特定的使用场景,让我们先从 RDB 持久化说起吧。
Redis持久化 ...
Redis 高可用方式
Redis 主从同步主从同步(主从复制)是 Redis 高可用服务的基石,也是多机运行中最基础的一个。我们把主要存储数据的节点叫做主节点 (master),把其他通过复制主节点数据的副本节点叫做从节点 (slave),如下图所示:
在 Redis 中一个主节点可以拥有多个从节点,一个从节点也可以是其他服务器的主节点,如下图所示:
主从同步的优点主从同步具有以下三个优点:
性能方面:有了主从同步之后,可以把查询任务分配给从服务器,用主服务器来执行写操作,这样极大的提高了程序运行的效率,把所有压力分摊到各个服务器了;
高可用:当有了主从同步之后,当主服务器节点宕机之后,可以很迅速的把从节点提升为主节点,为 Redis 服务器的宕机恢复节省了宝贵的时间;
防止数据丢失:当主服务器磁盘坏掉之后,其他从服务器还保留着相关的数据,不至于数据全部丢失。
既然主从同步有这么多的优点,那接下来我们来看如何开启和使用主从同步功能。
开启主从同步运行中设置从服务器在 Redis 运行过程中,我们可以使用 replicaof host port 命令,把自己设置为目标 IP 的从服务器,执行命令如下: ...
Redis常用数据类型
Redis 发展到现在已经有 9 种数据类型了,其中最基础、最常用的数据类型有 5 种,它们分别是:字符串类型、哈希表类型、列表类型、集合类型、有序集合类型,而在这 5 种数据类型中最常用的是字符串类型,所以我们先从字符串的使用开始说起。
字符串类型的全称是 Simple Dynamic Strings 简称 SDS,中文意思是:简单动态字符串。它是以键值对 key-value 的形式进行存储的,根据 key 来存储和获取 value 值,它的使用相对来说比较简单,但在实际项目中应用非常广泛。
字符串使用与内部实现原理字符串类型能做什么?字符串类型的使用场景有很多,但从功能的角度来区分,大致可分为以下两种:
字符串存储和操作;
整数类型和浮点类型的存储和计算。
字符串最常用的业务场景有以下几个。
页面数据缓存我们知道,一个系统最宝贵的资源就是数据库资源,随着公司业务的发展壮大,数据库的存储量也会越来越大,并且要处理的请求也越来越多,当数据量和并发量到达一定级别之后,数据库就变成了拖慢系统运行的“罪魁祸首”,为了避免这种情况的发生,我们可以把查询结果放入缓存(Redis)中,让下次同 ...
深入浅出索引
提到数据库索引,大家都不陌生。比如某一个SQL查询比较慢,分析完原因之后,你可能就会说“给某个字段加个索引吧”之类的解决方案。但到底什么是索引,索引又是如何工作的呢?
一句话简单来说,索引的出现其实就是为了提高数据查询的效率,就像书的目录一样。一本500页的书,如果你想快速找到其中的某一个知识点,在不借助目录的情况下,那我估计你可得找一会儿。同样,对于数据库的表而言,索引其实就是它的“目录”。
索引的常见模型索引的出现是为了提高查询效率,但是实现索引的方式却有很多种,所以这里也就引入了索引模型的概念。可以用于提高读写效率的数据结构很多,这里我先给你介绍三种常见、也比较简单的数据结构,它们分别是哈希表、有序数组和搜索树。
下面主要从使用的角度,简单分析一下这三种模型的区别。
哈希表是一种以键-值(key-value)存储数据的结构,我们只要输入待查找的值即key,就可以找到其对应的值即Value。哈希的思路很简单,把值放在数组里,用一个哈希函数把key换算成一个确定的位置,然后把value放在数组的这个位置。
不可避免地,多个key值经过哈希函数的换算,会出现同一个值的情况。处理这种情况 ...
事务隔离
提到事务,你肯定不陌生,和数据库打交道的时候,我们总是会用到事务。最经典的例子就是转账,你要给朋友小王转100块钱,而此时你的银行卡只有100块钱。
转账过程具体到程序里会有一系列的操作,比如查询余额、做加减法、更新余额等,这些操作必须保证是一体的,不然等程序查完之后,还没做减法之前,你这100块钱,完全可以借着这个时间差再查一次,然后再给另外一个朋友转账,如果银行这么整,不就乱了么?这时就要用到“事务”这个概念了。
简单来说,事务就是要保证一组数据库操作,要么全部成功,要么全部失败。在MySQL中,事务支持是在引擎层实现的。你现在知道,MySQL是一个支持多引擎的系统,但并不是所有的引擎都支持事务。比如MySQL原生的MyISAM引擎就不支持事务,这也是MyISAM被InnoDB取代的重要原因之一。
本文将会以InnoDB为例,剖析MySQL在事务支持方面的特定实现,并基于原理给出相应的实践建议,加深对MySQL事务原理的理解。
隔离性与隔离级别提到事务,你肯定会想到ACID(Atomicity、Consistency、Isolation、Durability,即原子性、一致性、隔离 ...
日志系统:一条SQL更新语句是如何执行的?
一条更新语句的执行流程是怎样的呢?
之前你可能经常听 DBA 同事说,MySQL 可以恢复到半个月内任意一秒的状态,惊叹的同时,你是不是心中也会不免会好奇,这是怎样做到的呢?
我们还是从一个表的一条更新语句说起,下面是这个表的创建语句,这个表有一个主键 ID 和一个整型字段 c:
1mysql> create table T(ID int primary key, c int);
如果要将 ID=2 这一行的值加 1,SQL 语句就会这么写:
1mysql> update T set c=c+1 where ID=2;
首先,可以确定的说,查询语句的那一套流程,更新语句也是同样会走一遍。
执行语句前要先连接数据库,这是连接器的工作。
前面我们说过,在一个表上有更新的时候,跟这个表有关的查询缓存会失效,所以这条语句就会把表 T 上所有缓存结果都清空。这也就是我们一般不建议使用查询缓存的原因。
接下来,分析器会通过词法和语法解析知道这是一条更新语句。优化器决定要使用 ID 这个索引。然后,执行器负责具体执行,找到这一行,然后更新。
与查询流程不一样的是,更新流程还涉及两个 ...
基础架构:一条SQL查询语句是如何执行的?
平时我们使用数据库,看到的通常都是一个整体。比如,一个最简单的表,表里只有一个 ID 字段,在执行下面这个查询语句时:
1mysql> select * from T where ID=10;
我们看到的只是输入一条语句,返回一个结果,却不知道这条语句在 MySQL 内部的执行过程。
下面给出的是 MySQL 的基本架构示意图,从中可以清楚地看到 SQL 语句在 MySQL 的各个功能模块中的执行过程。
大体来说,MySQL 可以分为 Server 层和存储引擎层两部分。
Server 层包括连接器、查询缓存、分析器、优化器、执行器等,涵盖 MySQL 的大多数核心服务功能,以及所有的内置函数(如日期、时间、数学和加密函数等),所有跨存储引擎的功能都在这一层实现,比如存储过程、触发器、视图等。
而存储引擎层负责数据的存储和提取。其架构模式是插件式的,支持 InnoDB、MyISAM、Memory 等多个存储引擎。现在最常用的存储引擎是 InnoDB,它从 MySQL 5.5.5 版本开始成为了默认存储引擎。
也就是说,执行 create table 建表的时候,如果不指定引擎 ...