
获得徽章 0
- #青训营 x 字节后端训练营# ## 参数带默认值的函数
``` c++
int add(int a = 10, int b = 20);
int sum(int a = 10, int b = 20) {
return a + b;
}
int main() {
int a = 10;
int b = 20;
int ret = sum(a, b);
cout << "ret = " << ret << endl;
// push 14H
// mov ecx, dword ptr[ebp-4]
// push ecx
// call sum
ret = sum(a);
ret = sum();
return 0;
}
```
1. 给默认值的时候从右向左给
`int sum(int a = 10, b)`这种写法不能通过编译
2. 效率有所提升, 立即数少一次取值
3. 定义处可以给形参默认值, 声明也可以给默认值
4. 形参给默认值时, 不管是定义处给还是声明处给, 形参默认值只能出现一次
## inline函数
和普通函数的区别
- 在编译过程中, 就没有函数的调用开销了, 在函数的调用点直接把函数的代码进行展开处理
- inline函数不再产生相应的函数符号
- inline只是建议编译器吧函数处理成内联函数
- 不是所有的inline都会被编译器处理成内联函数 - 递归
- debug版本上, inline函数不起作用, 只有在release下才有
``` c++
inline int sum(int x, int y) {
return x + y;
}
int main() {
int a = 10;
int b = 20;
int ret = sum(a, b);
// 此处有标准的函数调用过程 参数压栈, 函数栈帧的开辟和回退的过程
// 有函数调用的开销, 当有简单函数的时候, 调用函数的开销和函数指令差不多, 很影响效率
}展开评论点赞 - #青训营 x 字节后端训练营# 动态库代码不会被打包到可执行程序中
程序启动后, 被动态加载到内存
## 命名规则
- Linux - libxxx.so
lib: 前缀 固定
xxx: 库名字
.so: 后缀固定
在Linux下是个可执行文件
- Windows - libxxx.dll
## 制作
- gcc得到.o文件, 得到和位置无关的代码
`gcc -c -fpic/-fPIC a.c b.c`
- 得到动态库
`gcc -shared a.o b.o -o libcalc.so`
## 使用
头文件也要分发给用户
`gcc main.c -I ./include/ -l calc -L ./lib/ -o app`
## 加载原理
- LDD(List Dynamic Dependencies)
`ldd app` 检查`app`的依赖关系
系统加载可执行代码时, 直到依赖库的名称, 但还需要具体绝对路径, 此时需要系统动态载入器获取绝对路径.
elf格式的可执行程序通过ld-linux.so完成
DT_RPATH -> 环境变量 LD_LIBRARY_PATH -> /etc/ld.so.cache文件列表 -> /lib/, /usr/lib目录找到库文件后将其载入内存.
通过修改环境变量实现
- export LD_LIBRARY_PAHT=$LD_LIBRARY_PATH:/home/ubuntu/lib/ (临时, 用户级)
- `. .bashrc` = `source .bashrc`
- 系统级 `/etc/profile`
## 静态库优缺点
优点
- 被打包到应用程序中, 加载速度快
- 发布程序无需提供静态库, 移植方便
缺点
- 消耗系统资源, 浪费内存(引用同一个静态库, 但内存中需要多份)
- 更新部署发布麻烦(库更新要重新编译)
## 动态库优缺点
优点
- 可以实现进程间资源共享(共享库)
- 更新部署发布简单
- 可以控制何时加载动态库
缺点
- 加载速度比静态库慢
- 发布程序时需要提供依赖的动态库展开赞过评论1 - #青训营 x 字节后端训练营#
>索引的时候可能会用到二叉搜索树, 但当节点很多的时候, 二叉搜索树的高度会很高. 当节点多到内存放不下的时候, 需要将节点放到硬盘中, 但放到硬盘中每增加一次层高就多一次磁盘寻址, 非常耗时. 因此需要引入多叉树, 减少磁盘索引.
为了快速找到符合条件的数据需要进行索引, 索引在最初会使用线性表.
- 线性表
线性表线性存储, 可以随机访问, 进行等值查找非常快速, 但当存在插入删除的时候, 频繁挪动数据性能非常差劲.
- 哈希表
会存在Hash冲突, 散列不分散, 效率低. 等值查找比较快速, 但范围查找比较缓慢
- 二搜索叉树
插入的时候有序, 但如果从小到大插入, 就会退化到线性排列, 效率很低.
- 平衡二叉树(AVL)
为了避免二叉搜索树线性排列的问题, 对树高进行限制. 但只是把插入的效率弥补到查找上, 当有频繁的增删时, 平衡二叉树就会频繁的做出左旋右旋等操作, 效率较低
- 红黑树
红黑树为了弥补平衡二叉树的缺陷, 将条件放宽一点, 要求最长子树不超过最短子树的二倍即可(减少大量的旋转).
> 1. 根节点是黑色
> 2. 每个红色节点的两个子节点都是黑色.
> 3. 从任一节点到其每个叶子所有的路径包含相同数目的黑色结点.
数据量太大之后, 内存放不下, 就需要放到磁盘. 放到磁盘后, 层高加一, 就需要多一次磁盘IO, 而磁盘IO极为耗时, 所以需要降低树高. 而限制树高的原因在于二叉树, 所以需要引入多叉树.展开评论点赞 - #青训营 x 字节后端训练营# ## 数据一致性问题
只更新数据库, 但未更新缓存, 就会存在不一致问题
### 定时器, 定时同步MySQL数据到Redis中
适合数据量比较少的场景.
实时性不好, 在定时器间隔内, 仍不会更新缓存, 存在60s内的数据不一致.
### 同时更新数据库和Redis缓存, "并发双写"
在更新MySQL的同时, 更新缓存
单线程无所谓, 但高并发场景下就存在一致性问题: 并发双写
加锁, 会损失性能.
### 删除缓存
#### 先删除Redis缓存, 再更新数据库, "延迟双删"
再次查询的时候, 缓存内无相关内容, 会进行缓存重建.
一般不会使用, 高并发下做延迟太损失性能.
#### 先更新数据库, 再删除Redis缓存, "异步更新缓存"
会有较短时间(几百毫秒)内的数据不一致.
先删除缓存, 数据不一致的情况比较严重写. 所以一般使用后删除缓存. 但在删除缓存失败时也会存在问题
这种情况, 可以通过给key添加过期时间解决.展开评论点赞 - #青训营 x 字节后端训练营# # REdis Serialization Protocol (RESP)
## 正常回复
- 以`+`开头, 以`\r\n`结尾的字符串形式
- eg. `+OK\r\n`
## 错误回复
- 以`-`开头, 以`\r\n`结尾的字符串形式
- eg. `-Error message\r\n`
## 整数
- 以`:`开头, 以`\r\n`结尾的字符串形式
- eg. `:123456\r\n`
## 多行字符串
- 以`$`开头, 后跟实际发送字节数, 以`\r\n`结尾
- eg. 发送`Bing`, `$4\r\nBing\r\n`
- eg. 发送空字符串, `$0\r\n\r\n`
- eg. 发送`B\r\ning`, `$6\r\nB\r\ning\r\n` -- 可以透明传输
## 数组
- 以`*`开头, 后跟成员个数
- eg. `SET key value`, `*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n`展开评论点赞 - #青训营 x 字节后端训练营# ## 静态库vs动态库
静态库在程序链接阶段被复制到程序中
动态链接库在链接阶段没有被复制到程序中, 而是程序在运行时由系统动态加载到内存中供程序调用.
## 命名规则
Linux - libxxx.a
- lib 前缀固定
- xxx 库名称
- .a 后缀
Windows - libxxx.lib
## 制作
- gcc获得.o文件
`gcc -c add.c div.c mult.c sub.c`
- 将.o文件打包, 使用ar工具(archive)
`ar rcs libxxx.a xxx.o xxx.o`
- r - 将文件插入备存文件
- c - 建立备存文件
- s - 索引
`ar rcs libcalc.a add.o sub.o mult.o div.o`
## 使用
同时把*.a(lib文件夹下)和header.h(include下)给到用户
`gcc -I ./include/ -l calc -L ./lib/ main.c -o app`展开评论点赞 - #青训营 x 字节后端训练营# ## redis 事务lua脚本
### multi exec
基本不会使用
- multi - 开启事务
相当于MySQL中的begin 或者 start transaction
- exec - 提交事务
相当于 commit
- discard - 取消事务
rollback
- watch - 检测事务中key的变化, key变化则取消事务, 返回nil
事务实现 加倍操作
``` redis
WATCH score:10001
val = GET score:10001
MULTI
SET score:10001 val*2
EXEC
```
### Lua脚本
1. 减少网络传输
2. 实现原子性
实现原子性, Redis中嵌入了Lua虚拟机, 可以写多个语句;
当有一个脚本正在被执行的时候, 不会有其他命令被执行, 具备原子性
sha1 40位字符串, 减少网络传输; 保存sha1 -> xx.lua
1. 项目启动, 建立连接并验证后, 先加载所有项目中使用的lua脚本(script load)
2. 项目中若需要热更新, 通过redis-cli script flush; 然后可以通过订阅发布功能通知所有服务器重新加载lua脚本;
3. 若项目中lua脚本发生阻塞, 可通过script kil暂停当前阻塞脚本你的执行;展开评论点赞 - #青训营 x 字节后端训练营# 如何保证Redis中只存放1%的热点商品数据?
1. LRU算法
2. 设置key的过期时间
## 缓存例子
以Spring Boot为Web服务框架
模板引擎: 解析HTML中的动态数据
控制器(Controller): 接收并解析前端请求, 调用业务层获取数据, 并将数据给模板引擎
业务层(Service): 调用Dao拿到数据库数据, 并根据业务计算需要的数据
数据访问层(Dao): 操作数据库(MySQL)
### Spring Boot操作Redis
RedisTemplate
三个客户端:
1. Jedis
``` java
// 建立TCP连接
Jedis jedis = new Jedis("127.0.0.1", 6379);
jedis.auth("123456");
jedis.select(2);
// 操作Redis
jedis.set("name", "bing");
String res = jedis.get("name");
System.out.println(res);
// 释放连接
jedis.close();展开评论点赞 - #青训营 x 字节后端训练营# MVCC 读操作
1. 快照读(snapshot read)
> 读的是记录的可见版本, 不用加锁, select
2. 当前读(current read)
> 读取最新版本, insert / delete / update / select ... in share mode/for update
快照内容读取原则:
1. 版本未提交无法读取生成快照
2. 版本已提交,但是在快照创建后提交的,无法读取
3. 版本已提交,但是在快照创建前提交的,可以读取
4. 当前事务内自己的更新,可以读到
## 已提交读
每次执行语句的时候重新生成一次快照(read view), 每次select查询时
- 解决了脏读问题
每一次select 都会产生一次数据快照. prepare未commit的数据不会被快照读到
数据已经被事务正确commit提交过了
- 没有解决不可重复读
因为每一次slct都会重新产生一次数据快照,其它事务更新后而且已提交的数据,可以实时反馈到当前事务的select结果当中!
- 为什么无法解决”幻读“?
因为每一次selecti都会重新产生一次数据快照,其它事务增加了和当前事务查询条件相同的新的数据并且已成功commit提交,一致当前年斜以样的条件查询时,数据多了!
### 可重复读
同一个事务开始的时候生成一个当前事务全局性的快照(Read View), 第一次select查询时, 而且只产生一次(不是在事务开启的时候产生快照)
解决了"脏读"问题
为什么解决了"不可重复读问题"
为什么部分解决了"幻读"
虽然进行了快照读, 但自己更新的数据还是可以看到展开评论点赞