面经训练No1

一面:1. 挑一个项目进行讲解(可以不用作答)

  1. 讲下MySQL事务
  2. 自我介绍
  3. Session、Cookie、JWT的区别
  4. 讲下操作系统里的锁
  5. slice的扩容规则
  6. map是线程安全的么,使用安全map的方法
  7. 讲下go里的锁
  8. 讲下context包
  9. 如何使多个goroutine同时停止或会退出
  10. 讲一下mysql里面的索引
  11. 讲下redis里两种可持久化方式
  12. 讲下redis里的高可用
  13. new与make的区别
  14. 讲下什么场景下会发生panic
  15. 讲下defer的使用场景,以及执行顺序
  16. gorm与sql的区别
  17. 了解beggo么,和 Gin有什么区别

🍔讲下Mysql事务

事务确保一组操作要么全部成功执行,要么在遇到错误时全部撤销,从而保证了数据的完整性和一致性。

mysql事务的四大特征:

  1. 原子性:事务中的所有操作要么全部完成,要么全部不完成,不会结束在中间某个点。事务在执行过程中发生错误,会被完全回滚,就像这个事务从未执行过一样。
  2. 一致性:事务必须保证数据库从一个一致的状态转移到另一个一致的状态。在事务开始之前和事务结束之后,数据库的完整性约束都应该保持一致。
  3. 隔离性:并发执行的事务之间不会互相影响。MySQL提供了不同的隔离级别来解决事务间的并发问题,如脏读、不可重复读和幻读。
  4. 持久性:一旦事务提交,它对数据库的改变就是永久性的,即使系统发生故障也不会丢失。

mysql事务的隔离级别

  • 读未提交:最低的隔离级别,允许事务读取未提交的数据,可能会导致脏读。
  • ==读已提交==:事务只能读取到其他事务已经提交的数据,可以避免脏读,但仍然可能遇到不可重复读的问题。
  • 可重复读:这是MySQL的默认隔离级别。它保证了在同一个事务中,多次读取同一数据的结果是一致的,可以避免不可重复读,但仍然可能遇到幻读。
  • 串行化:最高的隔离级别,事务会依次逐个执行,避免了脏读、不可重复读和幻读,但性能会受到影响。

mysql事务的基本使用命令

  • START TRANSACTIONBEGIN:开始一个新的事务。
  • COMMIT:提交当前事务,使所有更改永久生效。
  • ROLLBACK:回滚当前事务,撤销所有更改

🍟自我介绍

🌭Session,Cookie,JWT的区别

Cookie
  1. 存储:Cookie是由客户端浏览器存储的小型文本文件。
  2. 安全性:默认情况下,Cookie是明文传输的,可以通过设置属性来增强安全性(如HttpOnly、Secure)。
  3. 生命周期:Cookie可以设置过期时间,过期后自动删除;也可以设置为会话Cookie,关闭浏览器时删除。
  4. 大小限制:Cookie的大小通常限制在4KB左右。
  5. 发送机制:每次HTTP请求都会携带Cookie,自动发送给服务器。
Session
  1. 存储:Session存储在服务器端,通过Session ID与客户端关联。
  2. 安全性:比Cookie更安全,因为数据存储在服务器端,客户端无法访问。
  3. 生命周期:Session的生命周期通常与用户的会话时间一致,用户关闭浏览器Session可能仍然存在,除非显式销毁。
  4. 大小限制:Session理论上没有大小限制,但过多的Session会增加服务器的存储压力。
  5. 发送机制:Session ID通过Cookie或URL传递,用户每次请求时需要携带Session ID。
JWT
  1. 存储:JWT是一个包含用户信息的JSON对象,通常以Bearer Token的形式存储在客户端。
  2. 安全性:JWT可以是自包含的,包含签名以验证其完整性。它通常使用HTTPS传输以防止中间人攻击。
  3. 生命周期:JWT有明确的过期时间,过期后需要重新认证。
  4. 大小限制:JWT的大小可能会比较大,因为它包含了用户信息和签名。
  5. 发送机制:每次请求都需要在HTTP请求的Authorization头部中携带JWT。
区别

存储位置:Cookie存储在客户端,Session存储在服务器端,JWT存储在客户端但服务器生成。

安全性:Session通常比Cookie和JWT更安全,因为它存储在服务器端。

传输:Cookie和JWT在每次请求时都需要发送,而Session只需发送Session ID。

大小:Cookie和JWT有大小限制,Session理论上没有,但过多的Session会占用服务器资源。

过期机制:Cookie可以设置过期时间,Session通常与会话时间一致,JWT有明确的过期时间。

使用场景:Cookie适用于存储少量信息,Session适用于需要服务器控制的场景,JWT适用于跨域认证和无状态的RESTful API。

总结来说,

🍿讲下操作系统里的锁

操作系统中的锁是一种同步机制,用于控制多个线程或进程对共享资源的访问,防止因竞争条件(race condition)导致数据不一致或程序错误。锁主要用于确保在同一时间只有一个线程或进程能够访问某个关键资源。

1. 互斥锁 (Mutex)

互斥锁是最常见的锁类型,用于确保只有一个线程可以访问临界区(critical section)。当一个线程获取互斥锁后,其他尝试获取该锁的线程将被阻塞,直到该线程释放锁。

特点:

  • 排他性:同一时刻只有一个线程可以持有锁。
  • 阻塞性:无法获取锁的线程将被阻塞。
2. 读写锁 (Read-Write Lock)

读写锁允许多个线程同时读取共享资源,但在写入时需要独占访问权。这样可以提高读取操作频繁时的并发性能。

特点:

  • 多读单写:多个线程可以同时读取,但写入时必须独占。
  • 读写互斥:读线程与写线程互斥,写线程与写线程互斥。
3. 自旋锁 (Spinlock)

自旋锁是一种忙等待(busy-waiting)锁,线程在尝试获取锁时不会被阻塞,而是不断循环检查锁的状态,直到获取到锁。

特点:

  • 适用于短时间锁定:因为线程不会被阻塞,所以适合锁定时间较短的场景。
  • CPU开销大:自旋期间会占用CPU时间,适用于多核CPU。
4. 递归锁 (Recursive Lock)

递归锁允许同一线程多次获取同一把锁,而不会导致死锁。每次获取锁后需要相应次数的释放锁。

特点:

  • 适用于递归调用:在递归函数或同一线程需要多次锁定的场景中使用。
5. 条件变量 (Condition Variable)

条件变量用于在线程间同步事件,允许线程在等待某个条件满足时释放锁并进入等待状态,条件满足后再重新获取锁并继续执行。

特点:

  • 与互斥锁配合使用:通常与互斥锁一起使用,用于实现复杂的同步场景。
  • 线程等待和唤醒机制:线程可以等待某个条件并被其他线程唤醒。
6. 信号量 (Semaphore)

信号量是一种更为通用的同步原语,可以用来控制对多个资源的访问。信号量的值表示可用资源的数量。

特点:

  • 计数机制:信号量可以用于管理有限数量的资源。
  • 适用于资源池:例如限制数据库连接池的并发数。

🥓slice的扩容规则

  1. 当切片元素超过当前容量时,Go 会创建一个新的底层数组,其容量比旧数组更大,并将旧数组的内容复制到新数组中。然后,切片指向这个新的底层数组。
  2. 如果旧数组的容量小于 1024 元素,新的容量将会是旧容量的两倍。
  3. 如果旧数组的容量大于或等于 1024 元素,新的容量将会增加 25%。

🧇map是线程安全的么,使用安全map的方法

Go语言中,内置的 map 类型并不是线程安全的。在多个goroutine并发读写一个 map 时,可能会导致竞态条件,导致程序崩溃或数据不一致。

使用安全map的方法:

  1. 使用互斥锁(sync.Mutex),使用标准库中的 sync.Mutex 来保护 map 的访问;
var (
	mu   sync.Mutex
	m    = make(map[string]int)
	wg   sync.WaitGroup
)

func main() {
	wg.Add(2)

	go func() {
		defer wg.Done()
		mu.Lock()
		m["key1"] = 1
		mu.Unlock()
	}()

	go func() {
		defer wg.Done()
		mu.Lock()
		value := m["key1"]
		fmt.Println("Value for key1:", value)
		mu.Unlock()
	}()

	wg.Wait()
}

如上示例:在这个示例中,使用 mu.Lock()mu.Unlock() 来确保对 map读写操作是互斥的,从而避免竞态条件。

2.使用读写锁(sync.RWMutex

读操作比写操作更频繁,可以使用 sync.RWMutex,它允许多个读操作并行进行,但写操作是互斥的。

var (
	rwmu sync.RWMutex
	m    = make(map[string]int)
	wg   sync.WaitGroup
)

func main() {
	wg.Add(2)

	go func() {
		defer wg.Done()
		rwmu.Lock()
		m["key1"] = 1
		rwmu.Unlock()
	}()

	go func() {
		defer wg.Done()
		rwmu.RLock()
		value := m["key1"]
		fmt.Println("Value for key1:", value)
		rwmu.RUnlock()
	}()

	wg.Wait()
}
  1. 使用 sync.Map

Go标准库提供了一个线程安全的 map 实现,叫做 sync.Map,适用于并发环境。sync.Map 有别于传统的 map,它没有类型参数,且提供了一些特定的方法,如 StoreLoadDelete

var (
	m  sync.Map
	wg sync.WaitGroup
)

func main() {
	wg.Add(2)

	go func() {
		defer wg.Done()
		m.Store("key1", 1)
	}()

	go func() {
		defer wg.Done()
		value, ok := m.Load("key1")
		if ok {
			fmt.Println("Value for key1:", value)
		}
	}()

	wg.Wait()
}

sync.Map 提供了 Store 方法来存储键值对,Load 方法来加载键值对,Delete 方法来删除键值对。这些方法go子自身已经做到了线程安全处理。

可以总结一下:==在Go语言中,内置的 map 不是线程安全的。==在并发环境中使用 map 时,可以选择使用互斥锁(sync.Mutex)、读写锁(sync.RWMutex)或 sync.Map 来确保线程安全。根据具体的使用场景和需求,选择合适的方法。

🥯讲下go里的锁

锁(Locks)是用于控制多个goroutine对共享资源的访问,以避免竞态条件和确保数据一致性。Go语言主要提供了以下几种锁机制:

1. 互斥锁 (sync.Mutex)

互斥锁用于确保同时只有一个goroutine能够访问共享资源。它提供了简单的加锁和解锁机制。

var (
	mu   sync.Mutex
	data int
	wg   sync.WaitGroup
)

func main() {
	wg.Add(2)

	go func() {
		defer wg.Done()
		mu.Lock()
		data++
		mu.Unlock()
	}()

	go func() {
		defer wg.Done()
		mu.Lock()
		fmt.Println("Data:", data)
		mu.Unlock()
	}()

	wg.Wait()
}

mu.Lock()mu.Unlock()用于保护对data变量的访问,确保只有一个goroutine能够访问该变量。

2.读写锁 (sync.RWMutex)

读写锁允许多个goroutine同时读取共享资源,但写操作是互斥的。它提供了更高的并发性能,特别是在读多写少的场景中。

var (
	rwmu sync.RWMutex
	data int
	wg   sync.WaitGroup
)

func main() {
	wg.Add(3)

	go func() {
		defer wg.Done()
		rwmu.Lock()
		data++
		rwmu.Unlock()
	}()

	go func() {
		defer wg.Done()
		rwmu.RLock()
		fmt.Println("Data:", data)
		rwmu.RUnlock()
	}()

	go func() {
		defer wg.Done()
		rwmu.RLock()
		fmt.Println("Data:", data)
		rwmu.RUnlock()
	}()

	wg.Wait()
}

rwmu.RLock()rwmu.RUnlock()用于读操作,rwmu.Lock()rwmu.Unlock()用于写操作。

3.原子操作 (sync/atomic)

Go的sync/atomic包提供了底层的原子操作,这些操作可以在不使用锁的情况下实现对整数和指针类型的原子操作。它适用于简单的计数器和标志位操作。

var (
	counter int32
	wg      sync.WaitGroup
)

func main() {
	wg.Add(2)

	go func() {
		defer wg.Done()
		atomic.AddInt32(&counter, 1)
	}()

	go func() {
		defer wg.Done()
		atomic.AddInt32(&counter, 1)
	}()

	wg.Wait()
	fmt.Println("Counter:", counter)
}

在这个示例中,使用atomic.AddInt32进行原子加操作,确保counter变量的安全访问。

5.使用 sync.Map

sync.Map是一个线程安全的map,适用于高并发读写的场景。与传统的map不同,sync.Map的API略有不同。

var (
	m sync.Map
	wg sync.WaitGroup
)

func main() {
	wg.Add(2)

	go func() {
		defer wg.Done()
		m.Store("key1", 1)
	}()

	go func() {
		defer wg.Done()
		if value, ok := m.Load("key1"); ok {
			fmt.Println("Value for key1:", value)
		}
	}()

	wg.Wait()
}

在这个示例中,使用m.Store进行写操作,使用m.Load进行读操作,sync.Map保证了线程安全。

sync.Map是单独出的一个方法,为了保证map的线程安全!;

🧀讲下context包

(只了解前两种,其他都没见过),下边都是copy的

context 包是 Go 语言标准库中的一个重要包,用于在多个 goroutine 之间传递上下文信息、控制goroutine的生命周期、超时和取消操作等。context 包的设计目的是简化在多个 goroutine 之间共享取消信号、超时截止时间等值的操作,以避免使用全局变量或手动传递参数的复杂性。

核心组件context包提供了 Context 接口和几个相关的函数:

Context:上下文接口,定义了一组操作上下文的方法,包括获取上下文的值、取消上下文、设置截止时间等。

Background():用于创建根上下文,是所有其他上下文的父节点。

WithCancel(parent Context):创建一个具有取消功能的上下文,当父上下文被取消或调用 cancel 函数时,它的子上下文也会被取消。

WithDeadline(parent Context, d time.Time):创建一个具有截止时间功能的上下文,当指定的截止时间到达或父上下文被取消时,它的子上下文也会被取消。

WithTimeout(parent Context, timeout time.Duration):创建一个具有超时功能的上下文,相当于 WithDeadline(parent, time.Now().Add(timeout))

WithValue(parent Context, key, val interface{}):创建一个具有传递值功能的上下文,可以在上下文中传递键值对。

使用场景:

  1. 控制goroutine的生命周期:可以使用上下文来传递取消信号,以便及时结束goroutine的执行,释放资源。
  2. 超时和取消操作:可以在上下文中设置截止时间或超时时间,一旦超过指定时间,上下文就会自动取消,防止goroutine因等待太久而阻塞。
  3. 传递请求范围的值:可以将一些请求范围的值(例如请求ID、用户信息等)存储在上下文中,方便在整个请求处理链中访问。
  4. 并发控制:可以使用上下文来控制多个goroutine的执行顺序、同步和通信。

注意:

在使用 context 包时,要注意遵循上下文的传递规则,确保每个 goroutine 都能够及时获取到上下文,并正确处理取消信号和超时操作。

不要将上下文存储在结构体中,而是应该将上下文作为函数的第一个参数传递,以便于更好地理解和调试代码。

取消操作应该是幂等的,多次调用 cancel 函数应该具有相同的效果,不会引发错误或异常。

使用 context 包时要小心避免创建过多的上下文,以免造成内存泄漏或性能问题。

🥞如何使多个goroutine同时停止或者退出

有三种方法:从能想到的开始说;

1.使用 sync.WaitGroup

sync.WaitGroup 可以用于等待一组goroutine完成,然后做到统一退出。这个也是大家最熟知的;

2.使用带有关闭信号的通道

创建一个带有关闭信号的通道,每个goroutine都监听该通道,当接收到信号时退出。

3. 使用 context 包

使用context.WithCancel,可以做到同时取消多个goroutine。

前两种较为常见,但是第三种网上说是最便捷和灵活的,如果不了解,得学习一下;

🧈讲一下mysql里面的索引

先列一下常见的索引:

1.主键索引:

每个表只能有一个主键,主键值必须唯一且不能为空。

MySQL会自动为主键创建一个唯一索引。

2.唯一索引:

索引列的值必须唯一,但允许有空值(NULL)。

在创建索引时使用UNIQUE关键字。

3.普通索引:

这是最基本的索引类型,没有任何限制。

可以在一个或多个列上创建。

4.全文索引:

主要用于全文搜索,适用于较长的文本字段。

MySQL的MyISAM和InnoDB存储引擎支持全文索引。(没用过…)

5.组合索引:

在多个列上创建的索引,适用于需要对多列进行查询的场景。

创建顺序很重要,查询时应该按照索引创建时的顺序使用,否则 数据库默认不会使用该索引。

但是,索引并不是用的越多就越好,索引有很多优点也有很多缺点

优点:提高查询速度,进行快速排序和分组操作;

缺点:

  1. 占用空间:索引需要额外的存储空间,尤其是对于大表来说,索引的大小可能非常可观。
  2. 影响写性能:INSERT、UPDATE和DELETE操作会因为需要维护索引而变慢。
  3. 索引维护成本:频繁的表修改(如插入、更新、删除)会导致索引的频繁重建和维护,影响性能。
索引使用建议

最好建立在对查询多修改少的列上,对于一些查询而言,使用组合索引能大大加快查询效率,但使用时必须按组合索引的顺序!!!

🥐讲下redis里两种可持久化方式

RDB和AOF

先讲RDB,因为redis中默认RDB是开启的,通俗的来说,rdb是通过每隔一段时间对数据进行快照创建来保证持久化,而这个快照相当于是将redis中所有数据备份到磁盘中一份(快照备份,占用空间和恢复效率都很快),即使服务器关闭了,在开机后redis中数据仍然可以快速恢复。而这个有个弊端,就是rdb快照间隔时间较长(因为数据量很大,每次都是把redis全部快照一次,你可以减小快照间隔,但是这样做对服务器性能又消耗过大),间隔较长,导致当redis崩溃时,数据只保留到上一次快照的时候,后边的数据就丢失了。

然后是aof,aof使用的是将redis中所有写操作命令记录下来,并追加到记录文件末尾,因为是增量加,所以频率很快,通常能做到以秒为单位,即使redis崩溃了,也可以找回所有数据;但是他也有弊端,那就是,由于是频繁写入,且每次写入都是将具体操作写入,导致文件内存占用过大,且频繁的写也回占用部分的性能;

在现今的企业级开发中,通常会两者结合使用,用aof存重要的数据,rdb缓存全部的数据;

🥨讲下redis里的高可用

先说明一下高可用这个词的意思是:“高可用”(High Availability, HA)是一个系统设计和运维领域的概念,它指的是系统在面对各种故障和异常情况下,依然能够持续运行并提供服务的能力。高可用性的目标是最大限度地减少系统停机时间,确保服务的连续性和稳定性。

具体来说,包含以下几个方面的内容:

  1. 冗余:通过在系统中部署多个相同功能的组件来实现冗余,当一个组件发生故障时,其他组件可以接管其工作。
  2. 故障转移:系统能够自动检测故障并转移服务到备用组件,无需人工干预。
  3. 负载均衡:通过负载均衡技术分散请求到多个服务实例,提高系统的吞吐量和容错能力。
  4. 监控和预警:实时监控系统状态,及时发现问题并发出预警,以便采取预防或修复措施。
  5. 数据备份和恢复:定期备份数据,并确保在发生数据丢失或损坏时能够快速恢复。
  6. 容错设计:设计能够容忍错误并继续运行的系统,例如通过冗余计算、错误检测和纠正机制。
  7. 可扩展性:系统设计允许在需求增加时,通过增加资源来扩展服务能力,而不会导致单点故障。
  8. 灾难恢复:制定灾难恢复计划,确保在重大故障或灾难发生时,系统能够快速恢复到正常运行状态。

而对于redis来说高可用主要表现在这几个方面:

主从复制

简单来说就是通过主从复制,我们可以实现故障转移,读写分离,哨兵模式,负载均衡,数据冗余等,这些都是依靠主从复制来的,具体先说主从复制的概念,即redis分为主库和从库,在redis中称为主节点和从节点,主节点主要负责写操作,从节点复制主节点的数据,主要处理读操作;

还有:主从复制是实现redis其他功能的前提;

故障转移

在主节点发生故障时,可以从节点接管读操作,甚至可以提升一个从节点为新的主节点,实现故障转移。

负载均衡

读操作可以分散到多个从节点上,减轻主节点的负担,提高整个系统的吞吐量

哨兵模式

是分布式主从复制的具体实现,可以监控redis主从集群的状态,可以自动检测主节点故障,并通过投票机制自动进行故障转移,将一个从节点提升为新的主节点

数据冗余

通俗来说,就是一个相同的数据或者组件实现多个,当一个出现错误,另一个顶上;

持久化

即adb和rof;

时间不多了,

🥖new与make的区别

new指向新分配类型对象的指针,用户分配内存,new适用于值类型

make用于创建和初始化内建的引用类型返回一个初始化后的(非零)类型的值,make适用于引用类型;

🥗讲下什么场景下会发生panic

访问空数组时;数据或者切片越界;向关闭的通道发送数据时;类型断言失败时;直接调用panic函数;

🥙讲下defer的使用场景,以及执行顺序

使用场景:

资源管理和释放,如数据库初始化等这种,会写defer,让内存得到释放;

互斥锁解锁defer k.Unlock,可以让函数执行到最后才解锁;

日志调试,可以在每个方法中放一个defer输出语句,输出哪个,说明哪个函数是正确执行了的;

错误处理:结合recover用于捕获和处理panic,实现异常处理。

执行顺序:

先进后出,在一个函数中写三个defer,defer会从下到上运行;

🥪gorm与sql的区别

gorm全城是go语言的orm库,orm是指对象关系映射,可以使开发者使用面向对象的方式进行数据库操作,即使用结构体映射到数据库,从而不需要直接编写sql语句。

🌮了解beggo么,和 Gin有什么区别

beego像是一个全功能的web框架,包含了web,orm,日志,缓存,匹配等多种功能;相对gin只有

web方面的功能来说更加全面;

  • 30
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值