1.分布式事务如何保证数据的一致性?
在分布式事务中,我们说一致性往往是指数据最终一致性,没有办法保证一定一致性。主要的方法有两阶段提交和三阶段提交。
两阶段提交是指,首先系统中有一个协调者,他会向所有的参与者去提交 prepare,然后如果都成功之后再去提交 commit,也就是提交事务。如果有执行失败的,就需要进行回滚。如果是部分成功的话,那就需要对其他的可能进行重试。
三阶段提交和两阶段提交的不同之处在于,三阶段提交在执行事务之前,首先得让各个参与者判断自己是否能执行事务,所以说不是直接去开启执行事务。
2.关于分库分表情况下的自增主键。
主键要保障它在多个库中,它也是全局唯一的。设置方法有多种。第一种就是直接按跨步骤去分配,就比如说在表一中,它们都是从 1、11、21 这种,表二就是 20、22 这种。第二种就是使用一个中心的数据库去分配。第三种就是使用雪花算法。雪花算法它会结合时间戳、机器 ID 以及序号,所以说它是能保证唯一的。因为首先时间戳是不一样的,接的机器也是不一样的。如果说在同一个间隔内,它生成的请求比序号的数量多,可能得需要调整段数。
除了使用调整段数的做法,也可以进行简单的限流。但是如果持续的进行任务堆积的话,那就会造成问题。
3.关于分库分表情况下的分页查询。所谓的分页查询就是 limit n offset m 这种形式的查询方式。因为它在不同的主键是存储在不同的表中的,我们没有办法去确定,哪怕在各个库中已经有序了,但是它不是全局有序的。
解决方法有
-
首先是使用局部有序的方法,把这些请求平均分配到多个表中,但是它不是严格符合业务的要求。
-
每个库都返回 N+M 数据,但是这比原始需要的数据多得多。
-
使用二次查询的方式去确定位置。
-
使用禁止跨库的这种分页查找,而是通过记录最大或者最小的 ID,然后根据上次的 ID 来进行继续的查找。
4.关于如何让两个线程交替地去打印,1A 2B 3C 4D 这些内容。
首先肯定是需要多线程的,其次肯定是需要使用锁的。然后使用能够基于 RAII 去管理的锁,比如说 unique lock。在具体使用过程中要结合条件变量,因为每次写一个就要去释放锁。可以使用 CV 点 wait,然后第一个参数是 lock,第二个参数就是我们要判断的条件,就比如说现在是否轮到自己。然后轮到自己,自己执行完之后,比如说输出完 1 之后,需要把 turn 转成另一个,然后通知对方可以去判断了。
这是一个手撕题,我之前遇到过一个真实的面经的手撕,就是让写一种可能产生死锁的场景。我当时是通过 fork,也就是创建子进程,然后他们去争用一个 mutex 去产生死锁的。但是这实际上是错误的,因为 mutex 它的锁粒度是线程级别的。当时面试官提醒我 fork 的作用是什么?其实我知道 fork 会在写时共享变量,但是还是没有联想到,因为变量会复制,写时复制,所以说每一个进程有一个独立的锁,它们是彼此无关的,所以说不可能用一个锁去控制两个进程,所以说这是有问题的。
在使用线程的时候,C++ 11 它提供了非底层的那个 API,非 POSIX 调用的 thread,它使用起来更加方便。可以使用直接使用 thread 关键字,然后去创建,然后参数就是函数。
5.服务的发现
在微服务中可以通过服务注册到注册平台,然后客户端通过和注册平台建立连接去获取服务,然后去获得具体的信息,然后去访问,通过这种形式,在这个过程中要处理问题有。多个方面的,比如说主要是容错,要考虑服务端的容错、客户端的容错,还有就是业务方的容错。当服务端宕机的时候。注册中心通过心跳机制检测之后就会把它标记为不可用,然后之后再检测。然后客户端,虽然说注册中心没有标记不可用,但是如果客户端在真实使用中发现它多次不可用,那就可能需要把它暂时标记为不可用,之后再测试。但是如果注册中心直接告知客户端不可用,那就需要直接把它标记为不可用,需要这些容错手段。
今天总结的主要是在邓明的后端高级面经、面试经验相关学习的内容,感觉都是非常有用的,尤其是这个关于高可用或者关于容错。比如说容错就需要有服务替换、替代去重试,以及对客户端透明的限流。