golang面试临时抱佛脚版本

/**
js上报 用url请求的方式上传到打点服务器(nginx),传送的数据有当前的url还有refer,时间
nginx的并发性能很好,用ngx_http_empty_gif_module模块 返回一个一个像素大小的gif文件
模块:日志消费-日志处理-分析日志-存储日志
日志消费:逐行读取
日志处理:得到url ,refer,ua,time,uid等头部信息还有list/movie 的资源id信息 通过字符串的切割和url的解析并get操作 url.Parse.query
pv:用户对网页的访问量
uv:统计有多少客户对网页进行访问(用redis 的HyperLogLog进行去重)
日志存储:分成六个纬度
首先是按照uv,pv 的day,hour,min进行分类存储
接着是uv,pv下的资源名称和id的day,hour,min进行分类存储
*/

聊天项目:

##6.1 UDP协议实现分布式

####6.1.1 支持分布式
回顾单体应用
开启ws接收协程recvproc/ws发送协程sendproc
websocket收到消息->dispatch发送给dstid

基于UDP的分布式应用
开启ws接收协程recvproc/ws发送协程sendproc
开启udp接收协程udprecvproc/udp发送协程udpsendproc

websocket收到消息->broadMsg广播到局域网
udp接收到收到消息->dispatch发送给dstid
自己是局域网一份子,所以也能接收到消息

####6.1.2 实现

####6.1.3 nginx反向代理

	upstream wsbackend {
			server 192.168.0.102:8080;
			server 192.168.0.100:8080;
			hash $request_uri;
	}
	map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
	}
    server {
	  listen  80;
	  server_name localhost;
	  location / {
	   proxy_pass http://wsbackend;
	  }
	  location ^~ /chat {
	   proxy_pass http://wsbackend;
	   proxy_connect_timeout 500s;
       proxy_read_timeout 500s;
	   proxy_send_timeout 500s;
	   proxy_set_header Upgrade $http_upgrade;
       proxy_set_header Connection "Upgrade";
	  }
	 }

}

## goalng
**1.字符串转成byte数组,会发生内存拷贝吗?**
答案:那么如果想要在底层转换二者,只需要把 StringHeader 的地址强转成 SliceHeader 就行。那么go有个很强的包叫 unsafe 。

    1.unsafe.Pointer(&a)方法可以得到变量a的地址。
    2.(*reflect.StringHeader)(unsafe.Pointer(&a)) 可以把字符串a转成底层结构的形式。
    3.(*[]byte)(unsafe.Pointer(&ssh)) 可以把ssh底层结构体转成byte的切片的指针。
    4.再通过 *转为指针指向的实际内容。
package main
 
import (
 "fmt"
 "reflect"
 "unsafe"
)
 
func main() {
 a :="aaa"
 ssh := *(*reflect.StringHeader)(unsafe.Pointer(&a))
 b := *(*[]byte)(unsafe.Pointer(&ssh))  
 fmt.Printf("%v",b)
}
 
**2.连nil切片和空切片的区别**
    nil切片和空切片指向的地址不一样。nil空切片引用数组指针地址为0(无指向任何实际地址)
    空切片的引用数组指针地址是有的,且固定为一个值 
**

**3.初始化map与未初始化map的区别**
初始化的方式:
两种方式:map[string]string{}或make(map[string]string)
未初始化的map是nil,它与一个空map基本等价,只是nil的map不允许往里面添加值。(A nil map is equivalent to an empty map except that no elements may be added)
因此,map是nil时,取值是不会报错的(取不到而已),但增加值会报错。
delete 空map和nilmap 均是一个空操作,不会panic
打印俩种map的结果都是:都为map[]

***4.struct默认深拷贝,引用类型(slice map)都是浅拷贝***

***5.slice扩容规则***
如果切片的容量小于1024个元素,那么扩容的时候slice的cap就乘以2;一旦元素个数超过1024个元素,增长因子就变成1.25,即每次增加原来容量的四分之一。

如果扩容之后,还没有触及原数组的容量,那么,切片中的指针指向的位置,就还是原数组,如果扩容之后,超过了原数组的容量,那么,Go就会开辟一块新的内存,把原来的值拷贝过来,这种情况丝毫不会影响到原数组。

**重点来了:**
func main() {
	// 建立容量为 2 的 切片
	addCap := make([]string, 0, 2)
	// 插入一个数,占一个容量
	addCap = append(addCap, "1")
	// 打印此时的地址
	fmt.Println("addCap 1", addCap, cap(addCap), &addCap[0])
	// 插入一个数,占一个容量
	// 再打印此时的地址
	addCap = append(addCap, "1")
	fmt.Println("addCap 2", addCap, cap(addCap), &addCap[0])
	// 将 oth 的指针指向切片地址
	// 再打印此时的地址 和 addCap 一样,即未触及容量时,还是原数组
	oth := addCap[0:1]
	fmt.Println("oth 1",oth,cap(oth),&oth[0])

	//此时修改原数组 oth 所指向的地址不变,但第一个数的值已经更改 3
	addCap[0] = "3"
	fmt.Println("oth 2",oth,cap(oth),&oth[0])

	// 插入一个数,占一个容量
	// 再打印此时的地址
	addCap = append(addCap, "1")
	//此时再修改 已经是扩容后的新地址 原数组将保持不变 即oth 所指向的地址的值不变
	addCap[0] = "4"
	// 此时三个数已经超出容量,那么切片容量将扩容,此时地址也将变成新的地址,第一个数也将修改为 4
	fmt.Println("addCap 3", addCap, cap(addCap), &addCap[0])
	// 但 oth 依然保留着原数组的指针地址,所以依然还是 3
	fmt.Println("oth 3",oth,cap(oth),&oth[0])

***6.make和new的区别与联系***
make与new的异同

相同

    堆空间分配

不同

make: 只用于slice、map以及channel的初始化, 无可替代

new: 用于类型内存分配(初始化值为0), 不常用

make返回的就是初始化类型的本身(因为slice map channel本身就是引用类型)

new分配好内存后,返回一个指向该类型内存地址的指针。同时请注意它同时把分配的内存置为零,也就是类型的零值。

**7.Go中字符串本身是不可修改的,只有转成数组后才能修改:**
OldStr := "abcd"
NewStr := []byte(OldStr)
NewStr[0] = 'A'

**8.翻转含有`中文、数字、英文字母`的字符串**
func main() {
	src := "你好abc啊哈哈"
	dst := reverse([]rune(src))
	fmt.Printf("%v\n", string(dst))
}

func reverse(s []rune) []rune {
	for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
		s[i], s[j] = s[j], s[i]
	}
	return s
}

[]rune可以表示更多的字符

**9.内存逃逸**
一个对象本应该分配在栈上面却分配到了堆上面
常见的四种场景:
1.局部指针返回
在局部方法里定义了一个指针并将这个指针作为返回值返回
2.栈空间不足
比如在一个局部方法里make了一个长度和容量都很大的slice
3.动态类型
被调用参数是interface类型或者是不定参数类型时
4.闭包调用

可以用gcflags命令来检测一下
go build -gcflags -m xx.go

**10.简述go语言GMP调度模型**
G:一个G代表一个goroutine,协程的本质是用户态的线程,用户对其有控制权限,内存占用少,切换代价低。

M:内核态线程,一个M代表了一个内核线程,等同于系统线程,所有的G都要放在M上才能运行。

P:处理器,用来管理和执行goroutine,一个P代表了M所需的上下文环境。P的个数取决于设置的GOMAXPROCS,go新版本默认使用最大内核数,比如你有8核处理器,那么P的数量就是8

三者关系:G需要绑定在M上才能运行,M需要绑定P才能运行。
M的数量和P不一定匹配,可以设置很多M,M和P绑定后才可运行,多余的M处于休眠状态。

系统会通过调度器从全局队列找到G分配给空闲的M,P会选择一个M来运行,M和G的数量不等,P会有一个本地队列表示M未处理的G,M本身有一个正在处理的G,M每次处理完一个G就从本地队列里取一个G,并且更新P的schedtick字段,如果本地队列没有G,则从全局队列一次性取走G/P个数的G,如果全局队列里也没有,就从其他的P的本地队列取走一半。

**11.垃圾回收机制**
起初[Go1.0版本-Go1.3版本],Go语言一直使用的垃圾回收使用的是标记-清除算法。进行垃圾回收时会STW。 因此,Go语言一直被诟病GC性能差。

    STW(Stop The World)
    STW的过程中,CPU不执行用户代码,全部用于垃圾回收,这个过程的影响很大,Golang进行了多次的迭代优化来解决这个问题。
    GC过程中STW是不可避免的。因为如果不暂停程序, 程序的逻辑改变对象引用关系, 这种动作如果在标记阶段做了修改,会影响标记结果的正确性。

    标记清除算法步骤简述
    第一步,暂停程序业务逻辑, 找出不可达的对象,然后做上标记。
    第二步, 开始标记,程序找出它所有可达的对象,并做上标记。
    第三步, 标记完了之后,然后开始清除未标记的对象 。
    第四步, 停止暂停,让程序继续跑。然后循环重复这个过程,直到process程序生命周期结束。
————————————————

1.5版本
Go1.5版本开始,Golang中的垃圾回收主要应用三色标记法,GC过程和其它用户goroutine可并发运行,但仍然需要一定时间的STW(stop the world)。

三色概念只是抽象概念。

    三色标记算法简述【实际上就是通过三个阶段的标记来确定清楚的对象都有哪些】
    第一步 , 就是只要是新创建的对象,默认的颜色都是标记为“白色”。
    第二步, 每次GC回收开始, 然后从根节点开始遍历所有对象,把遍历到的对象从白色集合放入“灰色”集合。
    第三步, 遍历灰色集合,将灰色对象引用的对象从白色集合放入灰色集合,之后将此灰色对象放入黑色集合。
    第四步, 重复第三步, 直到灰色中无任何对象。
    第五步: 回收所有的白色标记表的对象. 也就是回收垃圾。
————————————————
Go1.8版本开始支持混合写屏障(hybrid write barrier)机制。避免了对栈re-scan的过程,极大的减少了STW的时间。结合了插入写屏障和删除写屏障两者的优点。

    混合写屏障→保证变形的弱三色不变式
    具体操作:
    1、GC开始将栈上的对象全部扫描并标记为黑色(之后不再进行第二次重复扫描,无需STW),
    2、GC期间,任何在栈上创建的新对象,均为黑色。
    3、被删除的对象标记为灰色。
    4、被添加的对象标记为灰色。

**
**11.谈谈你对defer的理解**

1.多个defer出现的时候,它是一个“栈”的关系,也就是先进后出。一个函数中,写在前面的defer会比写在后面的defer调用的晚。
2.return之后的语句先执行,defer后的语句后执行


12.golang的context
context最早的背景说明还是来源于官方的 博客,说明如下:

在Go服务器中,每个传入请求都在其自己的goroutine中进行处理。 请求处理程序通常会启动其他goroutine来访问后端,例如数据库和RPC服务。 处理请求的goroutine集合通常需要访问特定于请求的值,例如最终用户的身份,授权令牌和请求的期限。 当一个请求被取消或超时时,处理该请求的所有goroutine应该迅速退出,以便系统可以回收他们正在使用的任何资源。

func main() {
	ctx, cancel := context.WithCancel(context.Background())
	// 开启一个协程运行
	go func() {
		for {
			select {
			case <- ctx.Done():
				fmt.Println("context done")
				return
			}
		}
	}()
	time.Sleep(2*time.Second)
	// 主协程取消
	cancel()
	fmt.Println("main cancel")
	// 为了让ctx.Done的协程能够打印出来context done
	time.Sleep(2*time.Second)

打印结果:
context done
main cancel

带定时的
func main() {
	d := time.Now().Add(50 * time.Millisecond)
	ctx, cancel := context.WithDeadline(context.Background(), d)
	
	defer cancel()

	select {
	case <-time.After(1 * time.Second):
		fmt.Println("overslept")
	case <-ctx.Done():
		fmt.Println(ctx.Err())
	}
}
context deadline exceeded
————————————————

## redis系列

**
**1、Redis 为何这么快?**

1)基于内存;

2)单线程减少上下文切换,同时保证原子性;

3)IO多路复用;

4)高级数据结构(如 SDS、Hash以及跳表等)。

2、为何使用单线程?


因为 Redis 是基于内存的操作,CPU 不会成为 Redis 的瓶颈,而最有可能是机器内存的大小或者网络带宽。既然单线程容易实现,而且 CPU 不会成为瓶颈,那就顺理成章地采用单线程的方案了。

**3.redis 三大缓存问题及解决方法**
1. 缓存穿透

        缓存穿透是指前端请求到达后端服务器后,先从Redis缓存中查询,没有查询到结果,然后查询数据库,数据库查询不到数据则不进行数据缓存。流程图示如下

 

这种在代码逻辑上会先查询Redis,再查数据库。逻辑上Redis有一层缓存,但是实际情况却是Redis并未起到缓存的真是作用,使得请求全部进入数据库查询,数据库也没有查询到数据,数据库没有查询到数据之后也没有进行空对象写入缓存。这种情况,称之为缓存穿透!在低并发情况下,数据库可以承受住压力的话。存在缓存穿透对系统影响不大,但是在高并发的场景下,大量请求对数据库会造成很大的压力,极有可能直接数据库宕机影响系统的使用,这种是需要极力避免的。处理办法有常见的两种,数据库查询后缓存和布隆过滤器
1.1 数据库查询后缓存

        数据库查询后缓存是指,请求先通过Redis查询,没有查询到数据,然后请求查询数据库,将数据库查询结果进行Redis缓存,然后响应前端请求。这种是数据库数据存在数据的场景,一次查询后缓存,后续相同数据查询就会走Redis。另外一种是数据库也不存在对应的数据,这时,数据库为查询到数据,也将null缓存到Redis中。这样后续的请求也是会走到Redis中。但是这种方式存在很大的弊端,请求第一次还是会进入数据库,在高并发的情况下,数据库压力其实也很大。一般不采用这种方式处理。这种请求的图示如下

 1.2 使用布隆过滤器

        布隆过滤器的相关知识,就不在这里说明了,可以参考一下我关于布隆过滤器的理解的文章。布隆过滤器文章。根绝布隆过滤器的特点,如果布隆过滤器判定没有,就一定没有,可以帮助服务端过滤非法数据的请求,即Redis没有相关,数据库也没有相关数据的请求。布隆过滤器的作用效果图示如下:

 在使用布隆过滤器的时候,有个前置条件,就是需要将校验数据提前在布隆过滤器中进行存储。当布隆过滤器判断请求的数据不存在时,直接返回响应,避免对数据库造成很大的压力。以上是处理缓存穿透的两种常见的处理方案。
2. 缓存击穿

        系统中都存在某个或许某些热点数据,访问量特别大,当大量请求进入服务器后,遇上缓存失效,缓存不存在导致大量的直接访问数据库,给数据库造成过大的压力,称为缓存击穿。处理换缓存击穿常见的处理有两种方案。合理的设置热点数据过期时间和加锁访问。
2.1 合理设置热点数据过期时间

        热点数据在缓存的时候,根据实际的业务常见,估算充裕的缓存的失效时间,例如某个活动的投放时间是10个小时,这10个小时候的访问量肯定较大。但是10个小时候,就可以保证并发量一定会出现大幅下降吗?我个人觉得不一定。所以我们在设置这个热点key的过期时间是,可以设置20小时,甚至24小时。这样会更加的保险。具体的时间还得根据具体的场景设置。
2.2 加锁访问

        热点数据设置合理的过期时间,是一个有效方案,但是这个方案对系统压力还是很大。此时为了保护系统的稳定性,还需要有其他的手段来处理,加锁访问就是一种比较有效的手段。加锁的方式有很多种,如果是单节点,Java自带的synchronized就可以实现,但实际情况往往不大可能。现在都是集群部分来保证服务的高可用性。所以在加锁时,需要使用分布式锁。可以使用比较常用,例如:redisson等。

以上是个人总结的为了防止缓存击穿,导致数据库压力过大,影响系统的稳定性的处理方案!
3. 缓存雪崩

        缓存雪崩,字面意思也比较好理解,就是缓存的数据出现雪崩。大面积的缓存数据或者所有的缓存数据都失效,所有的请求全部进入数据库查询的情况。缓存雪崩实际上不大可能是程序问题,更大的概率是由于其他因素导致,例如服务器断电等等。处理缓存雪崩,我个人认为更多的是得从运维的角度来处理,比如服务器集群部署,服务器多区域部署。另外还可以通过服务降级,服务限流等方面入手处理。
1)热点数据不过期

2)随机分散过期时间

**4.redis如何实现高并发**

Redis 通过主从加集群架构,实现读写分离,主节点负责写,并将数据同步给其他从节点,从节点负责读,从而实现高并发。

**5.Redis 如何保证原子性?**

答案很简单,因为 Redis 是单线程的,所以 Redis 提供的 API 也是原子操作。

但我们业务中常常有先 get 后 set 的业务常见,在并发下会导致数据不一致的情况。

如何解决

1)使用 incr、decr、setnx 等原子操作;

2)客户端加锁;
————————————————
6.redis应用场景
有哪些应用场景?

Redis 在互联网产品中使用的场景实在是太多太多,这里分别对 Redis 几种数据类型做了整理:

1)String:缓存、限流、分布式锁、计数器、分布式 Session 等。

2)Hash:用户信息、用户主页访问量、组合查询等。

3)List:简单队列、关注列表时间轴。

4)Set:赞、踩、标签等。

5)ZSet:排行榜、好友关系链表。

7.redis持久化的方式
1、AOF 持久化

AOF(append only file) 持久化,采用日志的形式来记录每个写操作,追加到AOF文件的末尾。

Redis默认情况是不开启AOF的。重启时再重新执行AOF文件中的命令来恢复数据。它主要解决数据持久化的实时性问题。

这两个风险最好的解决方案是折中妙用AOF机制的三种写回策略 appendfsync:

    always,同步写回,每个子命令执行完,都立即将日志写回磁盘。

    everysec,每个命令执行完,只是先把日志写到AOF内存缓冲区,每隔一秒同步到磁盘。

    no:只是先把日志写到AOF内存缓冲区,有操作系统去决定何时写入磁盘。

2.RDB持久化
RDB持久化,是指在指定的时间间隔内,执行指定次数的写操作,将内存中的数据集快照写入磁盘中,它是Redis默认的持久化方式。执行完操作后,在指定目录下会生成一个dump.rdb文件,Redis 重启的时候,通过加载dump.rdb文件来恢复数据。




    RDB的优点:与AOF相比,恢复大数据集的时候会更快,它适合大规模的数据恢复场景,如备份,全量复制等

    缺点:没办法做到实时持久化/秒级持久化。


**

## mysql系列

**
**1.三大范式**
什么是三大范式?

    第一范式(1NF):字段(或属性)是不可分割的最小单元,即不会有重复的列,体现原子性

    第二范式(2NF):满足 1NF 前提下,存在一个候选码,非主属性全部依赖该候选码,即存在主键,体现唯一性,专业术语则是消除部分函数依赖

    第三范式(3NF):满足 2NF 前提下,非主属性必须互不依赖,消除传递依赖


**2.聚簇索引和非聚簇索引**
都是B+树的数据结构

    聚簇索引:将数据存储与索引放到了一块、并且是按照一定的顺序组织的,找到索引也就找到了数
    据,数据的物理存放顺序与索引顺序是一致的,即:只要索引是相邻的,那么对应的数据一定也是 相邻地存放在磁盘上的

    非聚簇索引:叶子节点不存储数据、存储的是数据行地址,也就是说根据索引查找到数据行的位置
    再取磁盘查找数据,这个就有点类似一本树的目录,比如我们要找第三章第一节,那我们先在这个 目录里面找,找到对应的页码后再去对应的页码看文章。

**3.索引类型**
普通索引:MySQL 中的基本索引类型,没有什么限制,允许在定义索引的列中插入重复值和空值,纯粹为了提高查询效率。通过 ALTER TABLE table_name ADD INDEX index_name (column) 创建;

唯一索引:索引列中的值必须是唯一的,但是允许为空值。通过 ALTER TABLE table_name ADD UNIQUE index_name (column) 创建;

主键索引:特殊的唯一索引,也成聚簇索引,不允许有空值,并由数据库帮我们自动创建;

组合索引:组合表中多个字段创建的索引,遵守最左前缀匹配规则;

全文索引:只有在 MyISAM 引擎上才能使用,同时只支持 CHAR、VARCHAR、TEXT 类型字段上使用。

**4.索引创建原则**
   1. 选择唯一性索引;

唯一性索引的值是唯一的,可以更快速的通过该索引来确定某条记录。

   2. 为常作为查询条件的字段建立索引;

如果某个字段经常用来做查询条件,那么该字段的查询速度会影响整个表的查询速度。因此,为这样的字段建立索引,可以提高整个表的查询速度。

    3.为经常需要排序、分组和联合操作的字段建立索引;

经常需要 ORDER BY、GROUP BY、DISTINCT 和 UNION 等操作的字段,排序操作会浪费很多时间。如果为其建立索引,可以有效地避免排序操作。

**5.索引的数据类型:**
索引的数据结构?

索引的数据结构和具体存储引擎的实现有关,MySQL 中常用的是 Hash 和 B+ 树索引。

    Hash 索引底层就是 Hash 表,进行查询时调用 Hash 函数获取到相应的键值(对应地址),然后回表查询获得实际数据.

    B+ 树索引底层实现原理是多路平衡查找树,对于每一次的查询都是从根节点出发,查询到叶子节点方可以获得所查键值,最后查询判断是否需要回表查询.

**6.什么是最左匹配原则?**

顾名思义,最左优先,以最左边为起点任何连续的索引都能匹配上。同时遇到范围查询(>、<、between、like)就会停止匹配。

1. 全值匹配查询时
用到了索引
where子句几个搜索条件顺序调换不影响查询结果,因为Mysql中有查询优化器,会自动优化查询顺序

select * from table_name where a = '1' and b = '2' and c = '3' 
select * from table_name where b = '2' and a = '1' and c = '3' 
select * from table_name where c = '3' and b = '2' and a = '1' 
1
2
3
2. 匹配左边的列时
都从最左边开始连续匹配,用到了索引

select * from table_name where a = '1' 
select * from table_name where a = '1' and b = '2'  
select * from table_name where a = '1' and b = '2' and c = '3'
1
2
3
这些没有从最左边开始,最后查询没有用到索引,用的是全表扫描

select * from table_name where  b = '2' 
select * from table_name where  c = '3'
select * from table_name where  b = '1' and c = '3' 
1
2
3
如果不连续时,只用到了a列的索引,b列和c列都没有用到

select * from table_name where a = '1' and c = '3' 
1
3. 匹配列前缀

如果列是字符型的话它的比较规则是先比较字符串的第一个字符,第一个字符小的那个字符串就比较小,如果两个字符串第一个字符相通,那就再比较第二个字符,第二个字符比较小的那个字符串就比较小,依次类推,比较字符串。

如果a是字符类型,那么前缀匹配用的是索引,后缀和中缀只能全表扫描了

select * from table_name where a like 'As%'; //前缀都是排好序的,走索引查询
select * from table_name where  a like '%As'//全表查询
select * from table_name where  a like '%As%'//全表查询
1
2
3
4 .匹配范围值
可以对最左边的列进行范围查询

select * from table_name where  a > 1 and a < 3
1
多个列同时进行范围查找时,只有对索引最左边的那个列进行范围查找才用到B+树索引,也就是只有a用到索引,在1<a<3的范围内b是无序的,不能用索引,找到1<a<3的记录后,只能根据条件 b > 1继续逐条过滤

select * from table_name where  a > 1 and a < 3 and b > 1;
1
5. 精确匹配某一列并范围匹配另外一列

如果左边的列是精确查找的,右边的列可以进行范围查找

select * from table_name where  a = 1 and b > 3;
1
a=1的情况下b是有序的,进行范围查找走的是联合索引
6. 排序

order by的子句后面的顺序也必须按照索引列的顺序给出,比如

select * from table_name order by a,b,c limit 10;
1
这种颠倒顺序的没有用到索引

select * from table_name order by b,c,a limit 10;
1
这种用到部分索引

select * from table_name order by a limit 10;
select * from table_name order by a,b limit 10;
1
2
联合索引左边列为常量,后边的列排序可以用到索引

select * from table_name where a =1 order by b,c limit 10;
————————————————


7.mysql
一、事务的四个特性

在介绍mysql的四种隔离级别之前,我们首先要对事务有一个基本的了解。

事务具有四个特征:原子性( Atomicity )、一致性( Consistency )、隔离性( Isolation )和持续性( Durability )。这四个特性简称为 ACID 特性。

(1)原子性。事务是数据库的逻辑工作单位,事务中包含的各操作要么都做,要么都不做

(2)一致性。事 务执行的结果必须是使数据库从一个一致性状态变到另一个一致性状态。因此当数据库只包含成功事务提交的结果时,就说数据库处于一致性状态。如果数据库系统 运行中发生故障,有些事务尚未完成就被迫中断,这些未完成事务对数据库所做的修改有一部分已写入物理数据库,这时数据库就处于一种不正确的状态,或者说是 不一致的状态。

(3)隔离性。一个事务的执行不能其它事务干扰。即一个事务内部的操作及使用的数据对其它并发事务是隔离的,并发执行的各个事务之间不能互相干扰。

(4)持续性。也称永久性,指一个事务一旦提交,它对数据库中的数据的改变就应该是永久性的。接下来的其它操作或故障不应该对其执行结果有任何影响。

二、事务的并发问题

我们还需要了解事务在并发情况下,可能会引起哪些问题,这将有助于我们更好地理解mysql的四种事务隔离级别。

(1)脏读:事务A读取了事务B更新的数据,然后B回滚操作,那么A读取到的数据是脏数据

(2)不可重复读:事务 A 多次读取同一数据,事务 B 在事务A多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同一数据时,结果不一致。

(3)幻读:假设系统管理员A将数据库中所有学生的成绩从具体分数改为ABCDE等级,但是系统管理员B就在这个时候插入了一条具体分数的记录,当系统管理员A改结束后发现还有一条记录没有改过来,就好像发生了幻觉一样,这就叫幻读。 

小结:其中不可重复读的和幻读很容易混淆,不可重复读侧重于修改,幻读侧重于新增或删除。

 
三、mysql的四种事务隔离级别

在SQL标准中定义了四种隔离级别,包括了一些具体规则,用来限定事务内外的哪些改变是可见的,哪些是不可见的。低级别的隔离级一般支持更高的并发处理,并拥有更低的系统开销。

(1)Read Uncommitted(读取未提交内容)

在该隔离级别,所有事务都可以看到其他未提交事务的执行结果。本隔离级别很少用于实际应用,因为它的性能也不比其他级别好多少。读取未提交的数据,也被称之为脏读(Dirty Read)。

(2)Read Committed(读取提交内容)

这是大多数数据库系统的默认隔离级别(但不是mysql默认的)。它满足了隔离的简单定义:一个事务只能看见已经提交事务所做的改变。这种隔离级别也支持所谓的不可重复读(Nonrepeatable Read),因为同一事务的其他实例在该实例处理其间可能会有新的commit,所以同一条select语句可能返回不同结果。

(3)Repeatable Read(可重读)

这是mysql的默认事务隔离级别,它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。不过理论上,这会导致另一个棘手的问题:幻读 (Phantom Read)。简单的说,幻读指当用户读取某一范围的数据行时,另一个事务又在该范围内插入了新行,当用户再读取该范围的数据行时,会发现有新的“幻影” 行。InnoDB和Falcon存储引擎通过多版本并发控制(MVCC,Multiversion Concurrency Control)机制解决了该问题。

(4)Serializable(可串行化)

这是最高的隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,它是在每个读的数据行上加上共享锁。在这个级别,可能导致大量的超时现象和锁竞争,因此使用该隔离级别会造成数据库性能的显著下降。

他们之间根据不同情况会有些许区别,MySQL会对count(*)做优化。

         (1)如果列为主键,count(列名)效率优于count(1)  

         (2)如果列不为主键,count(1)效率优于count(列名)  

         (3)如果表中存在主键,count(主键列名)效率最优  

         (4)如果表中只有一列,则count(*)效率最优  

          (5)如果表有多列,且不存在主键,则count(1)效率优于count(*)


四、存储索引
存储引擎
1、有哪些常见的存储引擎?

ref 几种MySQL数据库引擎优缺点对比
2、MyISAM 和 InnoDB 的区别?

1)InnoDB 支持事务,而 MyISAM 不支持。

2)InnoDB 支持外键,而 MyISAM 不支持。因此将一个含有外键的 InnoDB 表 转为 MyISAM 表会失败。

3)InnoDB 和 MyISAM 均支持 B+ Tree 数据结构的索引。但 InnoDB 是聚集索引,而 MyISAM 是非聚集索引。

4)InnoDB 不保存表中数据行数,执行 select count(*) from table 时需要全表扫描。而 MyISAM 用一个变量记录了整个表的行数,速度相当快(注意不能有 WHERE 子句)。

那为什么 InnoDB 没有使用这样的变量呢?
因为InnoDB的事务特性,在同一时刻表中的行数对于不同的事务而言是不一样的。

5)InnoDB 支持表、行(默认)级锁,而 MyISAM 支持表级锁。

InnoDB 的行锁是基于索引实现的,而不是物理行记录上。即访问如果没有命中索引,则也无法使用行锁,将要退化为表锁。

6)InnoDB 必须有唯一索引(如主键),如果没有指定,就会自动寻找或生产一个隐藏列 Row_id 来充当默认主键,而 Myisam 可以没有主键。

五、数据库的锁
    共享锁(Share Lock):S 锁,又称读锁,用于所有的只读数据操作。

S 锁并非独占,允许多个并发事务对同一资源加锁,但加 S 锁的同时不允许加 X 锁,即资源不能被修改。S 锁通常读取结束后立即释放,无需等待事务结束。

    排他锁(Exclusive Lock):X 锁,又称写锁,表示对数据进行写操作。

X 锁仅允许一个事务对同一资源加锁,且直到事务结束才释放,其他任何事务必须等到 X 锁被释放才能对该页进行访问。

使用 select * from table_name for update; 语句产生 X 锁。

    更新锁:U 锁,用来预定要对资源施加 X 锁,允许其他事务读,但不允许再施加 U 锁或 X 锁。

当被读取的页将要被更新时,则升级为 X 锁,U 锁一直到事务结束时才能被释放。故 U 锁用来避免使用共享锁造成的死锁现象。

六、隔离级别和锁的关系?

1)在 Read Uncommitted 级别下,读取数据不需要加共享锁,这样就不会跟被修改的数据上的排他锁冲突;

2)在 Read Committed 级别下,读操作需要加共享锁,但是在语句执行完以后释放共享锁;

3)在 Repeatable Read 级别下,读操作需要加共享锁,但是在事务提交之前并不释放共享锁,也就是必须等待事务执行完毕以后才释放共享锁;

4)在 SERIALIZABLE 级别下,限制性最强,因为该级别锁定整个范围的键,并一直持有锁,直到事务完成。

七、覆盖索引
就是select的数据列只用从索引中就能够取得,不必从数据表中读取
读大表的时候
这是由于 MySQL 并不是跳过 offset 的行数,而是取 offset + limit 行,然后丢弃前 offset 行,返回 limit 行,当offset特别大的时候,效率就非常的低下。

此处我们就可以采用覆盖索引+延迟关联技术来减少偏移量的定位进行优化

八 一些函数
rank()
SELECT score, rank() over(ORDER BY score desc) as 'Rank' 
FROM rank;

    1
    2

结果:

+------+---------+
| score|   Rank  |
+------+---------+
|  100 |       1 |
|  100 |       1 |
|  95  |       3 |
|  95  |       3 |
|  95  |       3 |
|  90  |       6 |
|  89  |       7 |
+------+---------+
SELECT name , 
	score ,
	rank() over(partition by name ORDER BY score desc) as 'Rank' 
	FROM rank;

    首先,PARTITION BY子句按姓名将结果集分成多个分区。
    然后,ORDER BY子句按分数对结果集进行排序。

结果:

+------+------+---------+
| name | score|   Rank  |
+------+------+---------+
|  a   |  100 |       1 |
|  a   |  90  |       2 |
|  a   |  89  |       3 |
|  b   |  100 |       1 |
|  c   |  95  |       1 |
|  d   |  95  |       1 |
|  e   |  95  |       1 
————————————————
SELECT 
 row_number() OVER (
 ORDER BY score
 ) row_num,
 score
 FROM rank;

为分组后的数据按分组进行编号
————————————————

————————————————
dense_rank()
与rank不同的是它的排名是连续的

九、建立行锁需要注意的地方
使用 Innodb 引擎
主键明确 与否
主键明确 where id = #{id} for update 行锁
主键不明确 where id <> #{ id} for update 发生表锁
主键不明确 where id like #{id} for update 发生表锁
无主键 where name =#{name} for update name 不是主键,发生表锁

十、分页数据太多
1、使用表的覆盖索引加速分页查询。由于使用索引查找有优化算法,而且数据在查询索引上,不需要再去找相关的数据地址。

这样可以节省很多时间。

此外Mysql还有相关的索引缓存,在并发高的时候使用缓存效果更好。


select id from product limit 866613, 20

2、使用join。

如果先找到ID,然后关联查询记录,会快很多,因为索引很快就能找到合格的ID。



SELECT * FROM product a

JOIN (select id from product limit 866613, 20) b ON a.ID = b.id

3、使用id>=的形式。



SELECT * FROM product

WHERE ID > =(select id from product limit 866613, 1) limit 20

十一、分区
1、HASH分区(HASH)时间
特点

根据MOD(分区键,分区数)的值把数据行存储到表的不同分区中
数据可以平均的分布在各个分区中
HASH分区的键值必须是一个INT类型的值,或是通过函数可以转为INT类型
如何建立HASH分区表


以INT类型字段 customer_id为分区键

CREATE TABLE `customer_login_log` (
 `customer_id` int(10) unsigned NOT NULL COMMENT '登录用户ID',
 `login_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '用户登录时间',
 `login_ip` int(10) unsigned NOT NULL COMMENT '登录IP',
 `login_type` tinyint(4) NOT NULL COMMENT '登录类型:0未成功 1成功'
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户登录日志表'
 PARTITION BY HASH(customer_id) PARTITIONS 4;以非INT类型字段 login_time 为分区键(需要先转换成INT类型)CREATE TABLE `customer_login_log` ( `customer_id` int(10) unsigned NOT NULL COMMENT '登录用户ID', `login_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '用户登录时间',
 `login_ip` int(10) unsigned NOT NULL COMMENT '登录IP',
 `login_type` tinyint(4) NOT NULL COMMENT '登录类型:0未成功 1成功'
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户登录日志表'
 
PARTITION BY HASH(UNIX_TIMESTAMP(login_time)) PARTITIONS 4;
customer_login_log 表如果不分区,在物理磁盘上文件为

customer_login_log.frm # 存储表原数据信息
customer_login_log.ibd # Innodb数据文件
如果按上面的建HASH分区表,则有五个文件

customer_login_log.frm 
customer_login_log#P#p0.ibd
customer_login_log#P#p1.ibd
customer_login_log#P#p2.ibd
customer_login_log#P#p3.ibd
总结:

使用起来和不分区是一样的,看起来只有一个数据库,其实有多个分区文件,比如我们要插入一条数据,不需要指定分区,MySQL会自动帮我们处理
2、范围分区(RANGE)数字0-9999
RANGE分区特点

根据分区键值的范围把数据行存储到表的不同分区中
多个分区的范围要连续,但是不能重叠
默认情况下使用VALUES LESS THAN属性,即每个分区不包括指定的那个值
如何建立RANGE分区
![在这里插入图片描述](https://img-blog.csdnimg.cn/img_convert/9d56b825f251a9dea02d0cf71a186727.jpeg#pic_center)




如果没有定义p3分区,当插入的customer_id大于29999时会报错,定义了则超过的数据都存入p3中
RANGE分区的适用场景

分区键为日期或是时间类型 (可以使得各个分区表的数据比较均衡,如果按上面的例子中以整型id为分区键,假如活跃用户集中在10000-19999之间,则p1中的数据量就会比其他分区的数据量大很多,这就失去了分区的意义;而且按时间类型分区,如果要按时间顺序进行数据的归档,则只需要对某一个分区进行归档就可以了)
所有查询中都包括分区键(避免跨分区查询)
定期按分区范围清理历史数据
3、LIST分区135 246方式
LIST分区的特点

按分区键取值的列表进行分区
同范围分区一样,各分区的列表值不能重复
每一行数据必须能找到对应的分区列表,否则数据插入失败
如何建立LIST分区
![在这里插入图片描述](https://img-blog.csdnimg.cn/img_convert/ebdc85883935005012d7688d974b8308.jpeg#pic_center)



## 计算机网络:


    序列号seq:占4个字节,用来标记数据段的顺序,TCP把连接中发送的所有数据字节都编上一个序号,第一个字节的编号由本地随机产生;给字节编上序号后,就给每一个报文段指派一个序号;序列号seq就是这个报文段中的第一个字节的数据编号。

    确认号ack:占4个字节,期待收到对方下一个报文段的第一个数据字节的序号;序列号表示报文段携带数据的第一个字节的编号;而确认号指的是期望接收到下一个字节的编号;因此当前报文段最后一个字节的编号+1即为确认号。

    确认ACK:占1位,仅当ACK=1时,确认号字段才有效。ACK=0时,确认号无效

    同步SYN:连接建立时用于同步序号。当SYN=1,ACK=0时表示:这是一个连接请求报文段。若同意连接,则在响应报文段中使得SYN=1,ACK=1。因此,SYN=1表示这是一个连接请求,或连接接受报文。SYN这个标志位只有在TCP建产连接时才会被置1,握手完成后SYN标志位被置0。

    终止FIN:用来释放一个连接。FIN=1表示:此报文段的发送方的数据已经发送完毕,并要求释放运输连接

    PS:ACK、SYN和FIN这些大写的单词表示标志位,其值要么是1,要么是0;ack、seq小写的单词表示序号。
 
字段	含义
URG	紧急指针是否有效。为1,表示某一位需要被优先处理
ACK	确认号是否有效,一般置为1。
PSH	提示接收端应用程序立即从TCP缓冲区把数据读走。
RST	对方要求重新建立连接,复位。
SYN	请求建立连接,并在其序列号的字段进行序列号的初始值设定。建立连接,设置为1
FIN    	希望断开连接。
三次握手过程理解

 

第一次握手:建立连接时,客户端发送syn包(syn=x)到服务器,并进入SYN_SENT状态,等待服务器确认;SYN:同步序列编号(Synchronize Sequence Numbers)。

第二次握手:服务器收到syn包,必须确认客户的SYN(ack=x+1),同时自己也发送一个SYN包(syn=y),即SYN+ACK包,此时服务器进入SYN_RECV状态;

第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=y+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。
四次挥手过程理解 

1)客户端进程发出连接释放报文,并且停止发送数据。释放数据报文首部,FIN=1,其序列号为seq=u(等于前面已经传送过来的数据的最后一个字节的序号加1),此时,客户端进入FIN-WAIT-1(终止等待1)状态。 TCP规定,FIN报文段即使不携带数据,也要消耗一个序号。
2)服务器收到连接释放报文,发出确认报文,ACK=1,ack=u+1,并且带上自己的序列号seq=v,此时,服务端就进入了CLOSE-WAIT(关闭等待)状态。TCP服务器通知高层的应用进程,客户端向服务器的方向就释放了,这时候处于半关闭状态,即客户端已经没有数据要发送了,但是服务器若发送数据,客户端依然要接受。这个状态还要持续一段时间,也就是整个CLOSE-WAIT状态持续的时间。
3)客户端收到服务器的确认请求后,此时,客户端就进入FIN-WAIT-2(终止等待2)状态,等待服务器发送连接释放报文(在这之前还需要接受服务器发送的最后的数据)。
4)服务器将最后的数据发送完毕后,就向客户端发送连接释放报文,FIN=1,ack=u+1,由于在半关闭状态,服务器很可能又发送了一些数据,假定此时的序列号为seq=w,此时,服务器就进入了LAST-ACK(最后确认)状态,等待客户端的确认。
5)客户端收到服务器的连接释放报文后,必须发出确认,ACK=1,ack=w+1,而自己的序列号是seq=u+1,此时,客户端就进入了TIME-WAIT(时间等待)状态。注意此时TCP连接还没有释放,必须经过2∗∗MSL(最长报文段寿命)的时间后,当客户端撤销相应的TCB后,才进入CLOSED状态。
6)服务器只要收到了客户端发出的确认,立即进入CLOSED状态。同样,撤销TCB后,就结束了这次的TCP连接。可以看到,服务器结束TCP连接的时间要比客户端早一些。
 常见面试题

【问题1】为什么连接的时候是三次握手,关闭的时候却是四次握手?

答:因为当Server端收到Client端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。但是关闭连接时,当Server端收到FIN报文时,很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉Client端,"你发的FIN报文我收到了"。只有等到我Server端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四步握手。

【问题2】为什么TIME_WAIT状态需要经过2MSL(最大报文段生存时间)才能返回到CLOSE状态?

答:虽然按道理,四个报文都发送完毕,我们可以直接进入CLOSE状态了,但是我们必须假象网络是不可靠的,有可以最后一个ACK丢失。所以TIME_WAIT状态就是用来重发可能丢失的ACK报文。在Client发送出最后的ACK回复,但该ACK可能丢失。Server如果没有收到ACK,将不断重复发送FIN片段。所以Client不能立即关闭,它必须确认Server接收到了该ACK。Client会在发送出ACK之后进入到TIME_WAIT状态。Client会设置一个计时器,等待2MSL的时间。如果在该时间内再次收到FIN,那么Client会重发ACK并再次等待2MSL。所谓的2MSL是两倍的MSL(Maximum Segment Lifetime)。MSL指一个片段在网络中最大的存活时间,2MSL就是一个发送和一个回复所需的最大时间。如果直到2MSL,Client都没有再次收到FIN,那么Client推断ACK已经被成功接收,则结束TCP连接。

【问题3】为什么不能用两次握手进行连接?

答:3次握手完成两个重要的功能,既要双方做好发送数据的准备工作(双方都知道彼此已准备好),也要允许双方就初始序列号进行协商,这个序列号在握手过程中被发送和确认。

       现在把三次握手改成仅需要两次握手,死锁是可能发生的。作为例子,考虑计算机S和C之间的通信,假定C给S发送一个连接请求分组,S收到了这个分组,并发 送了确认应答分组。按照两次握手的协定,S认为连接已经成功地建立了,可以开始发送数据分组。可是,C在S的应答分组在传输中被丢失的情况下,将不知道S 是否已准备好,不知道S建立什么样的序列号,C甚至怀疑S是否收到自己的连接请求分组。在这种情况下,C认为连接还未建立成功,将忽略S发来的任何数据分 组,只等待连接确认应答分组。而S在发出的分组超时后,重复发送同样的分组。这样就形成了死锁。

【问题4】如果已经建立了连接,但是客户端突然出现故障了怎么办?

TCP还设有一个保活计时器,显然,客户端如果出现故障,服务器不能一直等下去,白白浪费资源。服务器每收到一次客户端的请求后都会重新复位这个计时器,时间通常是设置为2小时,若两小时还没有收到客户端的任何数据,服务器就会发送一个探测报文段,以后每隔75秒钟发送一次。若一连发送10个探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭连接。

http请求全过程
1. 客户端进行DNS域名解析,得到对应的IP地址
2. 根据这个IP,找到对应的服务器建立连接(三次握手)
3. 建立TCP连接后发起HTTP请求(一个完整的http请求报文)
4. 服务器响应HTTP请求,客户端得到html代码
5. 客户端解析html代码,用html代码中的资源(如js,css,图片等等)渲染页面。
6. 服务器关闭TCP连接(四次挥手)

http状态码:
    301 redirect: 301 代表永久性转移(Permanently Moved)

    302 redirect: 302 代表暂时性转移(Temporarily Moved )
   
   401表示用户未通过身份授权、验证,
   403表示用户可能通过了身份验证,但缺少指定权限


501:服务器无法识别 服务器不支持当前请求所需要的某个功能。当服务器无法识别请求的方法,并且无法支持其对任何资源的请求。

502:错误网关 作为网关或者工作的服务器尝试执行请求时,从上游服务器接收到无效的响应。


http请求头组成

HTTP请求头提供了关于请求,响应或者其他的发送实体的信息。HTTP的头信息包括通用头、请求头、响应头和实体头四个部分。每个头域由一个域名,冒号(:)和域值三部分组成。

分别来解释一下这四部分是什么意思吧!

    通用头标:即可用于请求,也可用于响应,是作为一个整体而不是特定资源与事务相关联。

    请求头标:允许客户端传递关于自身的信息和希望的响应形式。

    响应头标:服务器和于传递自身信息的响应。

    实体头标:定义被传送资源的信息。即可用于请求,也可用于响应。


根据上面的分类我们可以把他们分为:Request和Response两部分。
HTTP Request Header 请求头

Accept:指定客户端能够接收的内容类型。

Accept-Charset:浏览器可以接受的字符编码集。

Accept-Encoding:指定浏览器可以支持的web服务器返回内容压缩编码类型。

Accept-Language:浏览器可接受的语言。

Accept-Ranges:可以请求网页实体的一个或者多个子范围字段。

AuthorizationHTTP:授权的授权证书。

Cache-Control:指定请求和响应遵循的缓存机制。

Connection:表示是否需要持久连接。(HTTP 1.1默认进行持久连接)

CookieHTTP:请求发送时,会把保存在该请求域名下的所有cookie值一起发送给web服务器。

Content-Length:请求的内容长度。

Content-Type:请求的与实体对应的MIME信息。

Date:请求发送的日期和时间。


响应头部

Accept-Ranges:表明服务器是否支持指定范围请求及哪种类型的分段请求。

Age:从原始服务器到代理缓存形成的估算时间(以秒计,非负)。

Allow:对某网络资源的有效的请求行为,不允许则返回405。

Cache-Control:告诉所有的缓存机制是否可以缓存及哪种类型。

Content-Encodingweb:服务器支持的返回内容压缩编码类型。。

Content-Language:响应体的语言。

Content-Length:响应体的长度。

Content-Location:请求资源可替代的备用的另一地址。

Content-MD5:返回资源的MD5校验值。

Content-Range:在整个返回体中本部分的字节位置。

Content-Type:返回内容的MIME类型。

Date:原始服务器消息发出的时间。

ETag:请求变量的实体标签的当前值。

Expires:响应过期的日期和时间。
## grpc与Beego
什么是gRPC

gRPC是rpc框架中的一种,是rpc中的大哥

是一个高性能,开源和通用的RPC框架,基于Protobuf序列化协议开发,且支持众多开发语言。

面向服务端和协议端,基于http/2设计,带来诸如双向流,流控,头部压缩,单TCP连接上的多路复用请求等特性。这些特性使得其在移动设备上表现的更好,更省电和节省空间。

在gPRC里客户端可以向调用本地对象一样直接调用另一台不同机器上服务端应用的方法,使得您能够更容易地创建分布式应用和服务。

与许多RPC系统类似,gRPC也是基于以下理念:定义一个服务,指定其能够被远程调用的方法(包含参数和返回类型)。在服务端实现这个接口。并运行一个gRPC服务器来处理客户端调用。在客户端拥有一个存根能够向服务端一样的方法。
补充一个知识点(HTTP/2 与HTTP1.X的区别)

用于数据传输的二进制分帧

HTTP/2采用二进制格式传输协议,而非HTTP/1.x的文本格式。

img

多路复用

HTTP/2支持通过同一个连接发送多个并发的请求。

而HTTP/1.x虽然通过pipeline也能并发请求,但多个请求之间的响应依然会被阻塞。

 

服务端推送

服务端推送是一种在客户端请求之前发送数据的机制。在HTTP/2中,服务器可以对客户端的一个请求发送多个响应。而不像HTTP/1.X一样,只能通过客户端发起request,服务端才产生对应的response。

减少网络流量的头部压缩。

HTTP/2对消息头进行了压缩传输,能够节省消息头占用的网络流量。至于如何压缩的,可以查看这篇:HPACK: Header Compression for HTTP/2[1]
2.gRPC大致请求流程

1.客户端(gRPC Stub)调用A方法,发起RPC调用

2.对请求信息使用Protobuf进行对象序列化压缩(IDL)

3.服务端(gPRC Server)接收到请求后,解码请求体,进行业务逻辑处理并返回。

4.对响应结果使用Protobuf进行对象序列化压缩(IDL)

5.客户端接受到服务端响应,解码请求体。回调被调用的A方法,唤醒正在等待响应(阻塞)的客户端调用并返回响应结果

3.gRPC的优势
性能

gRPC消息使用一种有效的二进制消息格式protobuf继续宁序列化。Protobuf在服务器和客户机上的序列化非常快。Protobuf序列化之后的消息体积很小,能够有效负载,在移动应用程序等有限宽带场景中显得很重要。与采用文本格式的json相比,采用二进制格式的protobuf在速度上可以达到前者的5倍
代码生成

所有gRPC框架都为代码生成提供了一流的支持。gRPC的开发核心是*.proto文件,它定义了gRPC服务和消息的约定。根据这个文件,gRP框架将生成服务基类,消息和完整的客户端代码。

通过在服务器和客户端之间共享*.proto文件,可以从端到端生成消息和客户端代码。客户端的代码生成消除了客户端和服务器上的重复消息,并为您创建了一个强类型的客户端。无需编写客户端代码,可在具有许多服务和应用程序中节省大量开发时间。
严格的规范

不存在具有JSON的HTTP API的正事规范。开发人员不需要讨论URL,HTTP动词和响应代码的最佳格式。(不需要考虑用post还是get,get还是put)

该gRPC规范是规定有关gPRC服务必须遵循的格式。gRPC消除了争论并节省了开发人员的时间,因为gRPC在各个平台上和实现之间是一致的
流

gRPC服务支持所有流组合:

一元(没有媒体流):最简单的rpc调用,一个请求对象对应一个返回对象。客户端发起一次请求客户端相应一个数据,即标准的RPC通信。

服务器到客户端流:客户端流式rpc客户端传入多个请求对象。服务端返回一个响应结果。应用场景:物联网终端向服务器报送数据。

客户端到服务器流:服务端流式rpc一个请求对象,服务端可以传回多个结果对象。服务端流PRC下,客户端发出一个请求,但不会立即得到一个响应,而是在服务器与客户端之间建立一个单向的流,不断获取响应直到流关闭。应用场景举例:典型的例子是客户端向服务端发送一个股票代码,服务端就把该股票的实时数据源源不断的返回客户端

双向流媒体:双向流式RPC结合客户端流式RPC和服务端流式RPC,可以传入多个对象,返回多个响应对象。应用场景:聊天应用
截至时间/超时和取消

gRPC允许客户端指定他们愿意等待RPC完成时间。该期限被发送到服务端,服务端可以决定在超出了期限时采取什么行动,例如,服务器可能会在超时时取消正在进行的gPRC/HTTP/数据库请求

通过子gRPC调用截至时间和取消操作有助于实施资源使用限制
4.gRPC的劣势
浏览器支持有限

当下,不能从浏览器调用gRPC服务 ,

gRPC Web是gRPC团队的一项附加技术,它在浏览器中提供有限的gRPC支持。gRPC Web由两部分组成:支持所有现代浏览器的JavaScript客户端和服务器上的gRPC Web代理。gRPC Web客户端调用代理,代理将在gRPC请求上转发到gRPC服务器。

gRPC Web并非支持所有gRPC功能。不支持客户端和双向流,并且对服务器流的支持有限。
不是人类可读的

HTTP API请求以文本形式发送,可以由人读取和创建。

默认情况下,gRPC消息使用protobuf编码。虽然protobuf的发送和接收效率很高,但它的二进制格式是不可读的。protobuf需要在.proto文件中指定的消息接口描述才能正确反序列化。需要额外的工具来分析线路上的Protobuf有效负载,并手工编写请求。

存在诸如服务器反射和gRPC命令行工具等功能,以帮助处理二进制protobuf消息。另外,Protobuf消息支持与JSON之间的转换。内置的JSON转换提供了一种有效的方法,可以在调试时将Protobuf消息转换为可读的形式。
5.使用场景

建议使用的场景:

微服务:gRPC设计为低延迟和高吞吐量通信,非常适用效率至关重要的轻型微服务

点对点实时通信:gRPC可以实时推送消息而无需轮询

多语言混合开发环境:支持所有流行开发语言

网络受限环境:使用Protobuf(一种轻量级消息格式)序列化gRPC消息。gRPC消息始终小于等效的JSON消息

不建议使用场景:

浏览器可访问的API:浏览器不支持gRPC,gRPC-Web有局限性而且还引入了服务器代理

广播实时通信

进程间通信

docker

1、什么是Docker?

    Docker是一个容器化平台,它以容器的形式将你的应用程序及所有的依赖项打包在一起,以确保你的应用程序在任何环境中无缝运行。

2、什么是Docker镜像?

    Docker镜像是Docker容器的源代码,Docker镜像用于闯将容器,使用Build命令创建镜像。

3、什么是Docker容器?

    Docker容器包括应用程序及所有的依赖项,作为操作系统的独立进程运行。

4、Docker容器有几种状态?

    四种状态:运行、已停止、重新启动、已退出。

5、DockerFile中最常见的指定是什么?
指令	备注
FROM	指定基础镜像
LABEL	功能为镜像指定标签
RUN	运行指定命令
CMD	容器启动时要运行的命令
6、DockerFile中的命令COPY和ADD命令有什么区别?

    COPY和ADD的区别时COPY的SRC只能是本地文件,其他用法一致。

7、Docker的常用命令?
命令	备注
docker pull	拉去或更新指定的镜像
docker push	将镜像推送到远程仓库
docker rm	删除容器
docker rmi	删除镜像
docker images	列出所有镜像
docker ps	列出所有容器
8、容器与主机之间的数据拷贝命令?

    Docker cp命令用于穷奇与主机之间的数据拷贝 
        主机到哦容器:docker cp /www 96f7f14e99ab:/www/
        容器到主机:docker cp 96f7f14e99ab:/www /tmp

9、启动nginx容器(随机端口映射),并挂载本地文件目录到容器html的命令?

        Docker run -d -p --name nginx2 -v /home/nginx:/usr/share/nginx/html nginx

    1

10、解释一下dockerfile的ONBUILD指令?

    当镜像用作另一个镜像构建的基础时,ONBUILD指令像镜像添加将在稍后执行的触发指令。如果要构建将用作构建其他镜像的基础的镜像(例如,可以使用特定于用户的配置自定义的应用程序构建环境或守护程序),这将非常有用。

11、什么是docker Swarm?

    Docker Swarm是docker的本地群集。它将docker主机池转变为单个虚拟docker主机。Docjer Swarm提供标准的docker API,任何已经与docker守护进程通信的工具都可以使用Swarm透明地扩展到多个主机。

12、如何在生产中监控docker?

    Docker提供docker:stats和docker事件等工具来监控生产中的docker。我们可以使用这些命令获取重要统计数据的报告。
    Docker统计数据:当我们使用容器ID调用docker stats时,我们获得容器的CPU,内存使用情况等。它类似于Linux中的top命令。
    Docker事件:docker事件是一个命令,用于查看docker守护程序中正在进行的活动流。一些常见的docker事件是:attach,commit,die,detach,rename,destroy等。我们还可以使用各种选项来限制或过滤我们感性其的事件。

13、Docker如何在非Linux系统中运行容器?

    通过添加到Linux内核版本2.6.24的名称空间功能,可以实现容器的概念。容器将其ID添加到每个进程,并向每个系统调用添加新的访问控制检查。它由clone()系统调用访问,该调用允许创建先前全局命名空间的单独实例。
    如果由于Linux内核中可用的功能而可以使用容器,那么显而易见的问题是非Linux系统如何运行容器。Docker for Mac和Windows都使用Linux VM来运行容器。Docker Toolbox用于在Virtual Box VM中运行容器。但是,罪行的docker早Windows中使用Hyper-V,在MAC中使用Hypervisor.framework。

镜像相关
1、如何批量清理临时镜像文件?

    可以使用sudo docker rmi $(sudo docker images -q -f danging=true)命令

2、如何查看镜像支持的环境变量?

    使用sudo docker run IMAGE env

3、本地的镜像文件都存放在哪里?

    于docker相关的本地资源存在/var/lib/docker/目录下,其中container目录存放容器信息,graph目录存放镜像信息,aufs目录下存放具体的镜像底层文件。

4、构建docker镜像应该遵循哪些原则?

整体原则上,尽量保持镜像功能的明确和内容的精简,要点包括:

    尽量选取满足需求但较小的基础系统镜像,建议选择debian:wheezy镜像,仅有86MB大小。
    清理编译生成文件、安装包的缓存等临时文件。
    安装哥哥软件时候要指定准确的版本号,并避免引入不需要的依赖。
    从安全的角度考虑,应用尽量使用系统的库和依赖。
    使用dockerfile创建镜像时候要添加.dockerignore文件或使用干净的工作目录。

容器相关
1、容器退出后,通过docker ps命令查看不到,数据会丢失么?

    容器退出后会处于终止(exited)状态,此时可以通过docker ps -a查看,其中数据不会丢失,还可以通过docker start来启动,只要删除容器才会清除数据。

2、如何停止所有正在运行的容器?

        docker kill $(sudo docker ps -q)

    1

3、如何清理批量后台停止容器?

        docker rm$(sudo docker ps -a -q)

    1

4、如何临时退出一个正在交互的容器的终端,而不终止它?

    按Ctrl+p,后按Ctrl+q,如果按Ctrl+c会使容器内的应用进程终止,进而会使容器终止。

5、很多应用容器都是默认后台运行的,怎么查看他们的输出和日志信息?

    使用docker logs,后面跟容器的名称或者ID信息

6、使用docker port命令映射容器的端口时,系统报错Error:NO public port ‘80’ published for …,是什么意思?

    创建镜像时dockerfile要指定正确的EXPOSE的端口,容器启动时指定PublishAllport=true

7、可以在一个容器中同时运行多个应用进程吗?

    一般不推荐在用以容器内运行多个应用进程,如果有类似需求,可以用过额外的进程管理机制,比如supervisord来管理所运行的进程。

8、如何控制容器占用系统资源(CPU,内存)的份额?

    在使用docker create命令创建容器或使用docker run 创建并运行容器的时候,可以使用-c|-spu-shares[=0]参数来调整同期使用SPU的权重,使用-m|-memory参数来调整容器使用内存的大小。

仓库相关
1、仓库(Repository)、注册服务器(Registry)、注册 索引(Index)有和关系?

    首先,仓库事存放一组关联镜像的集合,比如同一个应用的不同版本的镜像,注册服务器时存放实际的镜像的地方,注册索引则负责维护用户的账号、权限、搜索、标签等管理。注册服务器利用注册索引来实现认证等管理。

2、从非官方仓库(如:dl.dockerpool.com)下载镜像的时候,有时候会提示”Error:Invaild registry endpoint https://dl.docker.com:5000/v1/…”?

    Docker自1.3.0版本往后以来,加强了对镜像安全性的验证,需要手动添加对非官方仓库的信任。DOCKER_ORTS=”-insecure-registry dl.dockerpool.com:5000”重启docker服务。

配置相关
1、Docker的配置文件放在那里。如何修改配置?

    Ubuntu系统下Docker的配置文件是/etc/default/docker,CentOS系统配置文件存放在/etc/sysconfig/docker。

2、如何更改docker的默认存储设置?

    Docker的默认存放位置是/var/lib/docker,如果希望将docker的本地文件存储到其他分区,可以使用Linux软连接的方式来做。

Docker与虚拟化
1、docker与LXC(Linux Container)有何不同?

LXC利用Linux上相关技术实现容器,docker则在如下的几个方面进行了改进:
容器特性	备注
移植性	通过抽象容器配置,容器可以实现一个平台移植到另一个平台
镜像系统	基于AUFS的镜像系统为容器的分发带来了很多的便利,通是共同的镜像层只需要存储一份,实现高效率的存储
版本管理	类似于GIT的版本管理理念,用户可以更方便的创建、管理镜像文件
仓库系统	仓库系统大大降低了镜像的分发和管理的成本
周边工具	各种现有的工具(配置管理、云平台)对docker的支持,以及基于docker的pass、Cl等系统,让docker的应用更加方便和多样
2、Docker于Vagrant有何不同?

两者的定位完全不同

    Vagrant类似于Boot2Docker(一款运行Docker的最小内核),是一套虚拟机的管理环境,Vagrant可以在多种系统上和虚拟机软件中运行,可以在Windows、Mac等非Linux平台上为Docker支持,自身具有较好的包装性和移植性。原生Docker自身只能运行在Linux平台上,但启动和运行的性能比虚拟机要快,往往更适合快速开发和部署应用的场景。

3、开发环境中Docker与Vagrant该如何选择?

    Docker不是虚拟机,而是进程隔离,对于资源的消耗很少,单一开发环境下Vagrant是虚拟机上的封装,虚拟机本身会消耗资源.

Other FAQ
1、如何将一台宿主机的docker环境迁移到另外一台宿主机?
    停止docker服务,将整个docker存储文件复制到另外一太宿主机上,然后调整另外一台宿主机的配置即可。

2、Docker容器创建后,删除了/var/run/netns目录下的网络名字空间文件,可以手动恢复它:

查看容器进程ID,比如1234

        Sudo docker inspect --format=’{{. State.pid}}’ $container_id 1234

    1

    到proc目录下,把对应的网络名字空间文字链接到/var/run/netns,然后通过正常的系统命令查看操作容器的名字空间


1.1 什么是Dockerfile
Dockerfile 是一个文本文件,其内包含了一条条的指令(Instruction),用于构建镜像。每一条指令构建一层镜像,因此每一条指令的内容,就是描述该层镜像应当如何构建

Beego:
conf文件夹:放的是项目有关的配置文件

controllers:存放主要的业务代码

main.go:项目的入口文件

models:存放的是数据库有关内容

routers:存放路由文件,路由作用是根据不同的请求指定不同的控制器

static:存放静态资源,包括图片,html页面,css样式,js文件等

tests:测试文件

views:存放视图有关内容


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值