一、背景介绍
(一)课程预览
Ø What?
基于Spring boot 框架 + Cassandra数据库,实现一个支持海量用户 的用户管理系统。
Ø Why?
Spring Boot大幅简化了应用搭建和开发的成本,Cassandra作为开源分布式数据库,既具备NoSQL数据库高性能,高可用,海量无限扩展的特点,又具备类似关系型数据库的数据模型,非常适合开发在线应用。
Ø Who?
Web开发入门者,希望深入了解Cassandra设计和使用的用户。
Ø How?
本文将从设计开始,手把手教用户实现一个用户管理系统
(二)Spring Data for Cassandra简介
1.Cassandra
- 去中心化,海量数据扩展能力;
- Zero Down Time;
- 支持二级索引,物化视图,集合等实用功能;
- 被Apple,Netflix,360,华为等大公司广泛采用。
2.Spring Data
- 强大的存储库和灵活的抽象映射;
- 便于与Spring MVC controller 集成;
- 支持MySQL,Redis,Redis,ES,Cassandra等数据库。
3.Spring Data for Cassandra
- 降低CQL语言的学习成本,提升开发速度;
- 支持同步,反应式和异步等多种模式;
- 支持用户自定义复杂查询;
- 一套代码支持MySQL冷热分离,ES加速查询,Redis缓存等多种玩法。
(三)准备工作
准备工作主要分为以下三个步骤:
1. 购买Cassandra集群
1)购买Cassandra云服务
2)用户密码:云Cassandra控制台-> 集群详情->账号管理页面
3)链接URL:在数据库连接页面
2. 配置集群
1) 云Cassandra控制台 -> 集群详情-> 数据库连接页面开通公网链接
2) 云Cassandra控制台-> 集群详情-> 数据安全页面配置本机IP到白名单
3. 配置开发环境
1)下载并安装Idea开发环境
2)在Spring模版生成器中生成项目模版
3)将生成好的项目导入Idea
当完成上述步骤后,用户可以获得一个工程,如下所示:
这个工程是空的,里面只有一个Main函数可以执行,其他功能需要后续操作进行添加。
二、系统设计
(一)需求分析
需求分析主要分为三部分:数据需求、功能需求和其他需求。
1.数据需求
由于要做的是用户管理系统,用户的数据存储在Cassandra数据库中。用户数据可分为以下几个属性:用户ID、用户名、密码与用户年龄,后续可根据需求扩展更多用户信息,例如email,电话号码,住址等。
2.功能需求
功能一般分为增删改查4个部分。
增指的是在系统中增加一个新的用户,删指的是把用户信息删除,改指的是修改用户信息,查则较为复杂。查询可以包含很多场景,例如基于用户ID查询,基于用户年龄查询,或者要展示全部用户的信息。
3.其他需求
除了上述基本功能需求外,还需要注意以下四点:
1)高并发高性能
用户可能会频繁更新同一条数据,或者系统需要支撑100万个用户同时注册的场景,或者当有1亿个用户的时候,能不能快速展示全部用户,以上都属于高并发高性能场景。
2)可扩展性
系统功能在设计初期,可能有一些地方没有考虑到,例如初期没有设计通过电子邮箱地址搜索用户的功能,后续有需求需要添加这个功能,因此对可扩展性有一定的要求。
3)开发敏捷
系统最开始的功能可能开发起来并不会特别复杂,但随着系统的累加,功能会越来越复杂,因此需要注意开发敏捷性。
4)美观
该系统作为外部服务,会存在与用户交互的界面,因此希望这个界面对用户足够友好。
(二)Schema设计
需求完成后,我们接下来需要做的事情是设计一个Schema。
这里需要注意,一定要先完成Schema的设然后再去实现功能,否则可能会导致系统性能会出现一些瓶颈。
分析刚才的数据,由于ID字段是唯一的,因此可以把ID字段放在主键的位置。
主键有几个要求,首先要符合用户查询常见维度,例如上方提到的用户查询,有可能通过用户ID、年龄等信息进行查询。
将ID作为主键而不是年龄的原因在于,一方面是年龄的区分度不够,很多用户拥有同样的年龄,如果以年龄做主键的话,可能会导致相同年龄的数据很多,必然要年龄后面再结合一个其他字段去保证主键的唯一。
其次,年龄作为主键可能会存在热点问题,比如所有的用户都是20~25岁,那么数据都会集中在分片,导致分片非常不均匀。
那么,把ID放在主键是不是就可以解决这些问题了呢?
这要看ID本身是否足够打散,如果是随机生成一些ID,则数据本身非常均匀。如果ID是自增的,这对于Cassandra来说并不是一个特别好的选择。对于Cassandra的ID,我们希望尽可能去打散。当然,如果说ID把它放在Partition Key的话,因为它本身是有一些哈希可以去做打散的,因此也是可行的。
但是如果把ID放在Partition Key的话,Partition Key本身是不做排序,因此想查询ID在5~100范围内的所有用户,这个查询无法实现。由于这次的系统设计较为简易,没有排序的要求,因此设计当中没有包含这项功能。
除此之外,有一个基于age字段做查询,age字段不在主键中出现。如果现在不对它建一个二级索引,它本身会转化成一个过滤逻辑,类似于全表扫描,如果数据量较小则没有问题。由于设计的目标是一个较大的用户管理系统,数据存储可能是几千万条甚至几亿条,在这些数据当中过滤出来年龄符合一定要求的存在一定难度,因此建一个二级索引加速查询。
创建二级索引的DDL如下所示:
(三)CQL设计
CQL设计遵循增删改查原则,实现方式如下。
1.增
insert into test_keyspace.user(id, age, username, password) values(20, 18,'apple','banana');
2.删
可以根据具体id进行删除,如:
delete from test_keyspace.user where id=20;
3.改
更改分为两个部分,第一部分是更改的字段,第二部分是更改的数据,如要把id为2的用户年龄改为20,则实现语句如下:
update test_keyspace.user set age=20 where id=2;
4.查
查询分为多个方式,如果查询所有数据,则:
select * from test_keyspace.user ;
如果根据ID查询,例如id为2的用户:
select * from test_keyspace.user where id=2 ;
如果根据年龄查询,例如年龄为2的用户:
select * from test_keyspace.user where age=2 ;
如果数据量少,不需要二级索引,通过年龄进行过滤查询:
select * from test_keyspace.user where age=2 allow filtering ;
n 此处必须加上allow filtering属性,否则Cassandra可能会拒绝查询。
三、编码实战
(一)代码框架
Ø 框架主要由以下几个部分构成:
1)UserController:控制层,负责与前段交互,页面跳转
2)UserService:业务层,负责处理业务逻辑
3)User Repo:DAO层,负责访问数据库获取数据
4)CassandraConfig:存储Cassandra集群的访问配置
5)Resource:前端页面位置
整个代码按照MVC的设计分成数个模块。前端模块会提供三个网页,一个是list.html,用来展示User;第二个是userAdd.html,增加一个user;最后一个是userEdit.html,表示更新一个user。
所有的html会把请求发到控制层和前端做交互,包括获取数据发到前端做展示,跳转到某个页面,这些都是在控制层进行。
控制层下面是业务层,传统的业务层更多的是处理一些业务的请求。我们这次的系统相对简单,业务层主要承担的作用是把DAO层的数据返回给上面的控制层。同时,业务层也会做一些简单的处理,比如统计一些用户的数目,然后将这些信息发给控制层。
最下面是DAO层,可以跟Cassandra做一一映射,负责Cassandra数据库的访问和链接。
(二)增删改查
1.Cassandra访问配置
在访问Cassandra之前,首先要进行访问配置。CassandraConfig本身就是在Spring Data框架里,需要继承AbstractCassandraConfiguration去定义需要的信息。
这里面包括了hostList,即数据库的连接串,这些信息可以在Cassandra管理页面找到。
第二个是keyspaceName,即访问的keyspace的具体名字。
第三个就是访问的用户名和密码,做权限的验证。
最后一个是datacenter,它其实也可以在云控制台里面去找到。在云控制台上它叫数据中心ID,不叫数据中心名称,通常情况是一个CN开头的字符串。
值得一提的是底下会有一个cassandraSession实现,它发送用户名和密码帮助Cassandra连接。
2.DAO层
Ø 定义User对象,并指定id对应user表的PrimaryKey
完成配置后开始定义数据。刚才存的是User,这个类对应到Cassandra当中是Table表,它有4个字段,分别是ID,UserName,Password和Age。这里面有一个@PrimaryKey的定义,作用是告知Cassandra框架,ID字段是一个PrimaryKey,它是一个BIGINT。除此之外,这个实体实现了一些简单的Get和Set的方法。
Ø DAO层只需要继承CassandraRepository即可实现基本的增删改查,对于带条件的增删改查通过@Query的方式支持。
如何把实体对象真正的去与Cassandra进行读取?对于DAO层,Spring Data也做了很多事情。
如上图所示,CassandraRepository实现了很多基本的Cassandra访问,比如findAll是获取一张表里所有的数据,findAllById是可以通过ID获取一个数据,insert是插入一条数据。有了这些之后,可以实现基本的增删改操作。
如果想实现的查询比较复杂,例如基于年龄做查询,可以做一个新的interface,让userRepository继承CassandraRepository,在里面定义一种新的查询叫findByAge。Spring Data支持@Query关键词,它定义了要访问的数据。
3.Service层
Ø 通过AutoWired标记链接DAU层;
Ø 本例子中业务逻辑比较简单,并未包括复杂的处理,实际开发过程中Service中可以处理复杂业务逻辑。
这里有几个需要关注的点:
第一个是类本身为@Service关键词进行标记。第二个是AutoWired属性,可以将UserRepository和Service层链接。第三个是实现了getUserNum,可以去userRepository拿到所有的数据,然后调size。
4.Controller层
Ø @RequestMapping来映射请求,也就是通过它来指定控制器 可以处理哪些URL请求, 函数返回值标记跳转的页面地址;
Ø 返回值通过model.addAttribute返回前端。
(三)前端页面
前端页面的实现代码可在GitHub上查看。
左下角的add按键可以添加新用户,后右上角的Edit和Delete,可以对用户进行修改与删除。下方的搜索用户功能,可以根据年龄搜索用户。
四、总结
(一)功能演示
1.前端页面
2.添加用户
3.修改用户
4.按年龄搜索用户
(二)演示总结
通过上述讲解与演示,可以总结出设计系统主要有以下步骤组成:
1. 明确需求
- 需要存储什么数据
- 需要什么查询语句
2. 设计表结构
- 主键设计
- 二级索引
3. MVC实现
- 基于Model实现控制器
- 实现前端展示
n 进阶思考:
1)系统需要处理高并发,如这是一个用户点击统计,有一列是点击次数,如何能统计出用户点击的精确次数?
2)如果该用户系统需要支持100w tps/qps每秒,应该如何继续迭代?
(三)更多高级功能 - Serverless Cassandra
阿里云推出了一个新的服务叫Serverless Cassandra,核心是解决用户的资源管理成本和稳定性问题。
传统模式存在几个问题:
第一,可能用户的服务本身体量很小,如果购买数据库服务的话,起步门槛较高,成本对于用户来说不太友好。
第二,如果在使用Cassandra的过程中,用户的网站做大了,此时传统数据库需要不停地运维和规划,需要通过人为触发来扩展数据库。
第三,传统资源管理的方式需要提前规划资源,可能造成较大的资源浪费。例如用户的服务在晚上请求量较低,但在配置时需要按照高请求量场景进行配置,成本较高。
Serverless Cassandra无需任何数据库管理,实例运行于庞大的资源池 ,按使用量计费,如用电一般,随取随用,适用于不频发的、间歇性的或不可预测的工作负载。
Ø Serverless Cassandra适用场景
1)变化或不可预测的工作负载
2)定时处理任务的场景
3)新上线应用
4)完全免运维的用户
Ø Serverless Cassandra三大特性
1)简单易用
全托管,无需数据库管理;
开源标准接口,应用0 改造;
丰富生态和周边工具配套
2)经济高效
超低门槛
存储计算独立计费
按使用量计费
多存储/ 可用性级别
3)按需弹性
存储计算独立伸缩
无需容量规划,自动伸缩