当前位置:网站首页>MyCat-分库分表
MyCat-分库分表
2022-06-11 10:58:00 【笑一笑0628】
MyCat安装部署
官网下载地址http://dl.mycat.org.cn/
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Nx9XvRzx-1654602524000)(https://gitee.com/czshh0628/blog-images/raw/master/202206071942863.png)]](/img/21/aa58bdec1da19faae4e111952c4f33.png)
三个配置文件:schema.xml定义逻辑库、表、分片节点等内容、rule.xml定义分片规则、server.xml定义用户以及系统相关变量如端口
MyCat对接MySQL
修改配置文件server.xml中的用户信息,配置逻辑库的访问用户和库名
<user name="mycat">
<property name="password">mycat</property>
<property name="schemas">TESTDB</property>
<property name="readOnly">true</property>
</user>
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hJhcssZP-1654602524001)(https://gitee.com/czshh0628/blog-images/raw/master/202206071942627.png)]](/img/d0/ecc9cdadcb109a112741e84d75f5d0.png)
修改配置文件schema.xml
<schema name="TESTDB" checkSQLschema="false" sqlMaxLimit="100" dataNode="dn1">
</schema>
<dataNode name="dn1" dataHost="bigdata" database="hudi" />
<dataHost name="bigdata" maxCon="1000" minCon="10" balance="0" writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100">
<heartbeat>select user()</heartbeat>
<!-- can have multi write hosts -->
<writeHost host="hostM1" url="bigdata:3306" user="root" password="root">
<!-- can have multi read hosts -->
<readHost host="hostS2" url="bigdata:3306" user="root" password="root" />
</writeHost>
</dataHost>
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GCYwzWoi-1654602524002)(https://gitee.com/czshh0628/blog-images/raw/master/202206071942133.png)]](/img/e5/0a0a457a25545d521999f573d6738b.png)
验证数据库访问情况
#如远程访问报错,请建对应用户
grant all privileges on *.* to root@'缺少的host' identified by '密码';
启动程序
# 控制台启动
$MYCAT_HOME/bin/mycat console
# 后台启动
$MYCAT_HOME/bin/mycat start
登录后台管理窗口:用于管理维护MyCat
mysql -umycat -pmycat -P9066 -hbigdata
# 常用命令
show database
show @@help
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-d9HSkKXX-1654602524002)(https://gitee.com/czshh0628/blog-images/raw/master/202206071942887.png)]](/img/e7/47125da5fe2944fac00141acfb3449.png)
登录数据窗口:用于通过MyCat查询数据
mysql -umycat -pmycat -P8066 -hbigdata
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bNEjPQi0-1654602524002)(https://gitee.com/czshh0628/blog-images/raw/master/202206071942246.png)]](/img/1c/5e69df35ad548937d4deb786056675.png)
使用MyCat搭建读写分离-MySQL一主一从
一个主机用于处理所有写请求,一台从机负责所有读请求,架构图如下
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Vw3GYNfM-1654602524003)(https://gitee.com/czshh0628/blog-images/raw/master/202206071942054.png)]](/img/28/d92796b83d54f58353ea68335c9526.png)
修改MySQL配置文件/etc/my.cnf
主机配置(ip:host79)
# 主服务器唯一ID
server-id=1
# 启用二进制日志
log-bin=mysql-bin
# 设置不要复制的数据库(可设置多个)
binlog-ignore-db=mysql
binlog-ignore-db=information_schema
# 设置需要复制的数据库
binlog-do-db=需要复制的主数据库名字
# 设置logbin格式
binlog_format=STATEMENT
从机配置(ip:host80)
# 从服务器唯一ID
server-id=2
# 启用中继日志
relay-log=mysql-relay
MySQL实现主从复制
并且主从都需要重启MYSQL服务、以及关闭防火墙
systemctl restart mysqld
systemctl status mysqld
在主机上建立账户并授权slave
#在主机MySQL里执行授权命令
GRANT REPLICATION SLAVE ON *.* TO 'slave'@'%' IDENTIFIED BY '123123';
# 查看master状态
show master status
# 记录下File的Position的值,执行完此步骤后不操作主MySQL,防止主服务器状态值变化
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qygHf6Vt-1654602524003)(https://gitee.com/czshh0628/blog-images/raw/master/202206071942328.png)]](/img/a6/ffcba9a91eda2970574452bdf95462.png)
在从机上配置需要复制的主机
# 复制主机的命令
CHANGE MASTER TO MASTER_HOST='主机的IP地址',
MASTER_USER='slave',
MASTER_PASSWORD='123123',
MASTER_LOG_FILE='mysql-bin.具体数字',MASTER_LOG_POS=具体值;
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-b7bqhwCo-1654602524004)(https://gitee.com/czshh0628/blog-images/raw/master/202206071942591.png)]](/img/1d/69c0ef639fdf70374637d5a6c2b852.png)
# 启动从服务器复制功能
start slave;
# 查看从服务器状态
show slave status\G;
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Q3D3KWo1-1654602524004)(https://gitee.com/czshh0628/blog-images/raw/master/202206071942630.png)]](/img/5a/727fcede720d718f2d9b4a8000914d.png)
主机新建库、新建表、insert记录、从机复制
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jccf65rL-1654602524005)(https://gitee.com/czshh0628/blog-images/raw/master/202206071942479.png)]](/img/bc/a13c49054fc22c44afdfa236c99c5a.png)
停止主从复制功能
stop slave;
重新配置主从
stop slave;
reset master;
通过MyCat实现读写分离
修改MyCat的配置文件schema.xml中的dataHost标签的balance属性,通过此属性配置读写分离类型
balance="0", 不开启读写分离机制,所有读操作都发送到当前可用的 writeHost 上
balance="1",全部的 readHost 与 stand by writeHost 参与 select 语句的负载均衡,简单的说,当双主双从模式(M1->S1,M2->S2,并且 M1 与 M2 互为主备),正常情况下,M2,S1,S2 都参与 select 语句的负载均衡
balance="2",所有读操作都随机的在 writeHost、readhost 上分发
balance="3",所有读请求随机的分发到 readhost 执行,writerHost 不负担读压力
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FwF5fjhW-1654602524005)(https://gitee.com/czshh0628/blog-images/raw/master/202206071942734.png)]](/img/d6/d5b5cac337a800df7e398481a0d548.png)
<dataHost name="host1" maxCon="1000" minCon="10" balance="2"
writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100">
验证方式
验证方式:由于配置的binlog_format=STATEMENT ,所以在使用系统变量或者时间函数时会造成不一致,通过这种方式插入系统IP,查看是否是同一个服务读写即可验证。
# 启动MyCat
# 在写主机插入
insert into mytbl values (1,@@hostname);
# 在MyCat里查询
select * from mytbl;
# 在Mycat里查询mytbl表,可以看到查询语句在主从两个主机间切换
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iF1ftyz2-1654602524005)(https://gitee.com/czshh0628/blog-images/raw/master/202206071942426.png)]](/img/2a/d650507c0d2da720050b5084e6c028.png)
使用MyCat搭建读写分离-MySQL双主双从
一个主机M1用于处理所有的写请求,它的从机S1和另一台主机M2还有它的从机S2负责所有读请求。当M1主机宕机后,M2主机负责写请求,M1、M2互为备机。架构图如下
| 编号 | 角色 | IP 地址 | 机器名 |
|---|---|---|---|
| 1 | Master1 | 192.168.140.128 | host79 |
| 2 | Slave1 | 192.168.140.127 | host80 |
| 3 | Master2 | 192.168.140.126 | host81 |
| 4 | Slave2 | 192.168.140.125 | host82 |
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yCQJXOHa-1654602524006)(https://gitee.com/czshh0628/blog-images/raw/master/202206071943279.png)]](/img/aa/7271957329802dbc766f41d19824fb.png)
修改MySQL配置文件/etc/my.cnf
Master1配置
# 主服务器唯一ID
server-id=1
# 启用二进制日志
log-bin=mysql-bin
# 设置不要复制的数据库(可设置多个)
binlog-ignore-db=mysql
binlog-ignore-db=information_schema
# 设置需要复制的数据库
binlog-do-db=需要复制的主数据库名字
# 设置logbin格式
binlog_format=STATEMENT
# 在作为从数据库的时候,有写入操作也要更新二进制日志文件
log-slave-updates
# 表示自增长字段每次递增的量,指自增字段的起始值,其默认值是1,取值范围是1 .. 65535
auto-increment-increment=1
# 表示自增长字段从哪个数开始,指字段一次递增多少,他的取值范围是1 .. 65535
auto-increment-offset=1
Master2配置
# 主服务器唯一ID
server-id=3
# 启用二进制日志
log-bin=mysql-bin
# 设置不要复制的数据库(可设置多个)
binlog-ignore-db=mysql
binlog-ignore-db=information_schema
# 设置需要复制的数据库
binlog-do-db=需要复制的主数据库名字
# 设置logbin格式
binlog_format=STATEMENT
# 在作为从数据库的时候,有写入操作也要更新二进制日志文件
log-slave-updates
# 表示自增长字段每次递增的量,指自增字段的起始值,其默认值是1,取值范围是1 .. 65535
auto-increment-increment=1
# 表示自增长字段从哪个数开始,指字段一次递增多少,他的取值范围是1 .. 65535
auto-increment-offset=1
Slave1配置
# 从服务器唯一ID
server-id=2
# 启用中继日志
relay-log=mysql-relay
Slave2配置
# 从服务器唯一ID
server-id=4
# 启用中继日志
relay-log=mysql-relay
并且主从都需要重启MYSQL服务、以及关闭防火墙
systemctl restart mysqld
systemctl status mysqld
在两台主机上建立账户并授权slave
#在主机MySQL里执行授权命令
GRANT REPLICATION SLAVE ON *.* TO 'slave'@'%' IDENTIFIED BY '123123';
#查询Master1的状态
show master status;
# 记录下File的Position的值,执行完此步骤后不操作主MySQL,防止主服务器状态值变化
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9K27OhEK-1654602524006)(https://gitee.com/czshh0628/blog-images/raw/master/202206071943463.png)]](/img/2f/125c452acad82e2e42e0b3c1dc84d4.png)
在从机上配置需要复制的主机
Slave1复制Master1、Slave2复制Master2
#复制主机的命令
CHANGE MASTER TO MASTER_HOST='主机的IP地址',
MASTER_USER='slave',
MASTER_PASSWORD='123123',
MASTER_LOG_FILE='mysql-bin.具体数字',MASTER_LOG_POS=具体值;
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WNlj0cMr-1654602524007)(https://gitee.com/czshh0628/blog-images/raw/master/202206071943044.png)]](/img/4d/33efc42620ec73e9bf42f5be65f58d.png)
#启动两台从服务器复制功能
start slave;
#查看从服务器状态
show slave status\G;
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EBzwh6w9-1654602524007)(https://gitee.com/czshh0628/blog-images/raw/master/202206071943677.png)]](/img/ee/320e8af48b67768144ee0f8005d906.png)
两个主机互相复制
Master2 复制 Master1,Master1 复制 Master2
#启动两台主服务器复制功能
start slave;
#查看从服务器状态
show slave status\G;
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eO0Ma80u-1654602524007)(https://gitee.com/czshh0628/blog-images/raw/master/202206071943337.png)]](/img/4d/33efc42620ec73e9bf42f5be65f58d.png)
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZswUWbyS-1654602524008)(../AppData/Roaming/Typora/typora-user-images/image-20220607101006940.png)]](/img/ed/5a19a1303af7c372a8dd1ed7a4ec42.png)
验证测试
Master1主机新建库、新建表、insert 记录,Master2和从机复制
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VAhZPiPB-1654602524008)(https://gitee.com/czshh0628/blog-images/raw/master/202206071943199.png)]](/img/a8/88c526d5830e1133445d06549c6129.png)
通过MyCat实现读写分离
<dataHost name="host1" maxCon="1000" minCon="10" balance="1" writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100" >
<heartbeat>select user()</heartbeat>
<!-- can have multi write hosts -->
<writeHost host="hostM1" url="192.168.140.128:3306" user="root"password="123123">
<!-- can have multi read hosts -->
<readHost host="hostS1" url="192.168.140.127:3306" user="root" password="123123" />
</writeHost>
<writeHost host="hostM2" url="192.168.140.126:3306" user="root"password="123123">
<!-- can have multi read hosts -->
<readHost host="hostS2" url="192.168.140.125:3306" user="root" password="123123" />
</writeHost>
</dataHost>
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-G0BZeKhD-1654602524008)(https://gitee.com/czshh0628/blog-images/raw/master/202206071943736.png)]](/img/f3/fec327e985976ea03758dc5096ea38.png)
# 启动MyCat
# 在写主机插入
insert into mytbl values (3,@@hostname);
# 在MyCat里查询
select * from mytbl;
# Master1宕机
systemctl stop mysqld
# 在Mycat里插入数据依然成功,Master2自动切换为写主机
# Master1、Master2 互做备机,负责写的主机宕机,备机切换负责写操作,保证数据库读写分离高可用性。
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mQWrmmMh-1654602524009)(https://gitee.com/czshh0628/blog-images/raw/master/202206071943026.png)]](/img/90/25bf2b448a1737bbd0e6ac180c71fa.png)
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ojIwG9FH-1654602524009)(https://gitee.com/czshh0628/blog-images/raw/master/202206071943974.png)]](/img/09/21055937cdf1dd6be7073ef0281d56.png)
MyCat垂直拆分-分库
分库划分
一个数据库由很多表构成,每个表对应着不同的业务,垂直切分是按照业务将表进行分类,分到不同的数据库上面,这样可以将数据或者说压力分摊到不同的库上面。划分表的原则:有紧密关联关系的表应该在一个库里,相互没有关联关系的表可以分到不同的库里。
实现分库
修改schema.xml配置文件
<schema name="TESTDB" checkSQLschema="false" sqlMaxLimit="100" dataNode="dn1">
<table name="customer" dataNode="dn2" ></table>
</schema>
<dataNode name="dn1" dataHost="host1" database="orders" />
<dataNode name="dn2" dataHost="host2" database="orders" />
<dataHost name="host1" maxCon="1000" minCon="10" balance="0"writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100">
<heartbeat>select user()</heartbeat>
<!-- can have multi write hosts -->
<writeHost host="hostM1" url="192.168.140.128:3306" user="root"password="123123">
</writeHost>
</dataHost>
<dataHost name="host2" maxCon="1000" minCon="10" balance="0"writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100">
<heartbeat>select user()</heartbeat>
<!-- can have multi write hosts -->
<writeHost host="hostM2" url="192.168.140.127:3306" user="root"password="123123">
</writeHost>
</dataHost>
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GplQpPqR-1654602524009)(https://gitee.com/czshh0628/blog-images/raw/master/202206071943626.png)]](/img/c7/549d7337e84cbf2dea507b7a4fba78.png)
# 访问 Mycat
mysql -umycat -p123456 -h 192.168.140.128 -P 8066
# 切换到 TESTDB
# 创建 4 张表
# 查看表信息,可以看到成功分库
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rXh3g2St-1654602524010)(https://gitee.com/czshh0628/blog-images/raw/master/202206071943558.png)]](https://img-blog.csdnimg.cn/4d7cc154eca64ff99512ab77236a5284.png)
MyCat水平拆分-分表
分表划分
相对于垂直拆分,水平拆分不是将表做分类,而是按照某个字段的某种规则来分散到多个库之中,每个表中包含一部分数据。即按照数据行的切分,就是将表中的某些行切分到一个数据库,而另外的某些行又切分到其他的数据库中。
实现分表
第一步:选择要拆分的表,MySQL单表达到1000w条数据就到达了瓶颈,会影响查询效率。
第二步:分表字段,一般是主键ID、外键ID或者是时间戳。
第三步:修改配置文件schema.xml
# 为orders表设置数据节点为dn1、dn2,并指定分片规则为mod_rule(自定义的名字)
<table name="orders" dataNode="dn1,dn2" rule="mod_rule" ></table>
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nSEFQZpU-1654602524010)(https://gitee.com/czshh0628/blog-images/raw/master/202206071943478.png)]](/img/1a/48c29da52d12de3f2a880fa8994499.png)
第四步:修改配置文件rule.xml
# 在 rule 配置文件里新增分片规则 mod_rule,并指定规则适用字段为 customer_id,
# 还有选择分片算法 mod-long(对字段求模运算),customer_id 对两个节点求模,根据结果分片
# 配置算法 mod-long 参数 count 为 2,两个节点
<tableRule name="mod_rule">
<rule>
<columns>customer_id</columns>
<algorithm>mod-long</algorithm>
</rule>
</tableRule>
<function name="mod-long" class="io.mycat.route.function.PartitionByMod">
<!-- how many data nodes -->
<property name="count">2</property>
</function>
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SO4SSXBo-1654602524011)(https://gitee.com/czshh0628/blog-images/raw/master/202206071943708.png)]](/img/e9/cfb0e81c53554a2ae63f52c2d1b669.png)
第五步:重启MyCat让配置生效,访问测试分片
#在 mycat 里向 orders 表插入数据,INSERT 字段不能省略
INSERT INTO orders(id,order_type,customer_id,amount) VALUES (1,101,100,100100);
INSERT INTO orders(id,order_type,customer_id,amount) VALUES(2,101,100,100300);
INSERT INTO orders(id,order_type,customer_id,amount) VALUES(3,101,101,120000);
INSERT INTO orders(id,order_type,customer_id,amount) VALUES(4,101,101,103000);
INSERT INTO orders(id,order_type,customer_id,amount) VALUES(5,102,101,100400);
INSERT INTO orders(id,order_type,customer_id,amount) VALUES(6,102,100,100020);
#在mycat、dn1、dn2中查看orders表数据,分表成功
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LIZK8GXw-1654602524011)(../AppData/Roaming/Typora/typora-user-images/image-20220607185742161.png)]](/img/74/c036331f4261ddba312cf197d2272d.png)
MyCat安全设置
User标签权限控制
| 标签属性 | 说明 |
|---|---|
| name | 应用连接中间件逻辑库的用户名 |
| password | 该用户对应的密码 |
| TESTDB | 应用当前连接的逻辑库中所对应的逻辑表。schemas 中可以配置一个或多个 |
| readOnly | 应用连接中间件逻辑库所具有的权限。true 为只读,false 为读写都有,默认为 false |
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OdZcwBAn-1654602524011)(https://gitee.com/czshh0628/blog-images/raw/master/202206071943077.png)]](/img/35/b90087dd3fc0a5e695cec950ca657b.png)
Privileges标签权限控制
在 user 标签下的 privileges 标签可以对逻辑库(schema)、表(table)进行精细化的 DML 权限控制。
privileges 标签下的 check 属性,如为 true 开启权限检查,为 false 不开启,默认为 false。
由于 Mycat 一个用户的 schemas 属性可配置多个逻辑库(schema) ,所以 privileges 的下级节点 schema 节点同样可配置多个,对多库多表进行细粒度的 DML 权限控制。

| DML 权限 | 增加(insert) | 更新(update) | 查询(select) | 删除(select) |
|---|---|---|---|---|
| 0000 | 禁止 | 禁止 | 禁止 | 禁止 |
| 0010 | 禁止 | 禁止 | 可以 | 禁止 |
| 1110 | 可以 | 禁止 | 禁止 | 禁止 |
| 1111 | 可以 | 可以 | 可以 | 可以 |
SQL拦截
白名单:可以通过设置白名单,实现某主机某用户可以访问 Mycat,而其他主机用户禁止访问。

黑名单:可以通过设置黑名单,实现 Mycat 对具体 SQL 操作的拦截,如增删改查等操作的拦截
边栏推荐
- Mn Monet pagoda host system v1.5 release
- CAP理论听起来很高大上,其实很简单
- 没有财富就不能自由吗?
- Common construction and capacity operation of string class
- 把程序写进微控制器里可以更方便快捷的控制电机正反转
- RxJs fromEvent 工作原理分析
- Encrypt and decrypt strings using RSA and Base64
- Rxjs Observable.pipe 传入多个 operators 的执行逻辑分析
- MySQL optimized learning diary 10 - locking mechanism
- 5. read the specified pathname -dirname
猜你喜欢

Cloud development MBTI personality type test assistant wechat applet source code

Electron桌面端开发(开发一个闹钟【完结】)

使用Yolov5训练自己制作的数据集,快速上手

Why is Web3 a game changer for the "creator economy"

Want to be iron man? It is said that many big men use it to get started

小 P 周刊 Vol.08

Don't be a fake worker

封装组件系列-(一)-插槽及动态组件

Interpreting USB3.0 test items
![Jerry's ble chip power supply range and anti burn chip measures [chapter]](/img/25/f35ca0366d31a70cd5e487347bb814.png)
Jerry's ble chip power supply range and anti burn chip measures [chapter]
随机推荐
Encrypt and decrypt strings using RSA and Base64
Jerry's ble chip power supply range and anti burn chip measures [chapter]
How programmers do sidelines
C language course design topic
Rxjs Observable. Execute logical analysis of pipe passing in multiple operators
An introduction to creating VOC datasets or Yolo datasets using labelimg
After 95, programmers in big factories were sentenced for deleting databases! Dissatisfied with the leaders because the project was taken over
Is the securities account opened by qiniu Gang safe and reliable?
Exploration of kangaroo cloud data stack on spark SQL optimization based on CBO
杰理之BLE SPP 开启 pin_code 功能【篇】
Leetcode (Sword finger offer) - 10- ii Frog jumping on steps
C language campus tour guide consultation system
Jerry's acquisition of ble to check the causes of abnormal conditions such as abnormal code reset [chapter]
Why does a ddrx power supply design require a VTT power supply
SAP Spartacus Reference App Structure
杰理之获取 BLE 区分复位跟唤醒【篇】
[games101] operation 2 -- triangle rasterization
js中Array.prototype.find() 方法在对象数组上无效果,捉急。。。
NFT will change data ownership in the metauniverse
AI security and Privacy Forum issue 11 - stable learning: finding common ground between causal reasoning and machine learning