0%

参考数据迁移,不停机上线的正确姿势

staging slot 切换

新建staging库,从旧数据库同步数据,同步数据结束后在空闲时段停服,将流量切换至新库

不停机

  1. 准备新库 表结构更新或分库分表操作
  2. 开启双写,服务端同时向新老两个库写入数据 此阶段需要联查历史数据进行写入的操作可以先查老数据库 再写入新库
  3. 双写过程中,开始历史数据迁移,将某一时间点的历史数据迁移到新库,迁移范围要覆盖到双写数据范围以避免数据遗漏
  4. 数据校验,确认没有遗漏 以及写入失败的个别情况
  5. 开启双读,流量逐步过渡到新库
  6. 关闭老库写功能
  7. 删除双写双读等业务无关逻辑

Get和Post的区别


总结:get 用于获取信息,无副作用,幂等,且可缓存。
post 用于修改服务器上的数据,有副作用,非幂等,不可缓存

其实HTTP协议本身并没有对URL和BODY的长度限制,对URL限制的大多是浏览器和服务端自己限制的。

传参方式不受TCP传输限制 Get使用url Post使用Body的方式是约定俗成 服务端可自行制定规则从header或body获取参数

幂等性 (Idempotence)

Put vs Post

Data URL

最初见于css插入图片资源 data协议数据格式形如

1
data:[<mediatype>][;base64],<data>

其中mediatype 是个 MIME 类型的字符串,例如 ‘image/jpeg’ 表示 JPEG 图像文件。如果被省略,则默认值为 text/plain;charset=US-ASCII。

RAIL模型

使用 RAIL 模型衡量性能 即对于应用的 相应 — 动画 — 空闲 — 加载 四种不同场景,用户会对性能有不一样的期望

以用户为中心的性能指标

  • First Paint 首次绘制(FP) 记录从空白页到任意像素的呈现所需时间
  • First contentful paint 首次内容绘制 (FCP) 首次页面加载完成
  • Largest contentful paint 最大内容绘制 (LCP) 任意访问和交互过程中 呈现内容所需的最长时间
  • First input delay 首次输入延迟 (FID) 从第一次交互到第一次响应
  • Time to Interactive 可交互时间 (TTI) 衡量页面从内容渲染完成到可以相应用户交互所需时间
  • Total blocking time 总阻塞时间 (TBT) 即FCP与TTI之间的时差
  • Cumulative layout shift 累积布局偏移 (CLS) 这是一个评分 衡量页面出现意外的加载内容偏移的程度

页面渲染的过程

优化

http2

在你的应用中安装sdk向Azure Application Insight服务发送遥测数据,支持web客户端,web服务和后台服务等
对应用性能影响小。非阻塞,单独线程

The instrumentation monitors your app and directs the telemetry data to an Azure Application Insights Resource using a unique GUID that we refer to as an Instrumentation Key. You can instrument not only the web service application, but also any background components, and the JavaScript in the web pages themselves. The application and its components can run anywhere - it doesn’t have to be hosted in Azure. 使用Guid作为Instrumentation Key(ikey)标识作为监视器的Azure Insights资源。 监测web服务应用或后台组件亦或页面js 这些应用或组件、命令不必托管在Azure中

创建Azure App Insights资源

Azure Portal -> Application Insights -> Create

添加Javascript SDK

1
npm i --save @microsoft/applicationinsights-web

使用ikey或连接字符串创建客户端instance

1
2
3
4
5
6
7
8
9
import { ApplicationInsights } from '@microsoft/applicationinsights-web'

const appInsights = new ApplicationInsights({ config: {
instrumentationKey: 'YOUR_INSTRUMENTATION_KEY_GOES_HERE' /* 使用ikey */
/* 或使用 connectionString: 'YOUR_CONNECTION_STRING_GOES_HERE' */
/* ...Other Configuration Options... */
} });
appInsights.loadAppInsights();
appInsights.trackPageView(); // Manually call trackPageView to establish the current user/session/pageview

React Plugin
1
2
npm install @microsoft/applicationinsights-react-js
npm install @microsoft/applicationinsights-web

创建实例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// AppInsights.js
import { ApplicationInsights } from '@microsoft/applicationinsights-web';
import { ReactPlugin } from '@microsoft/applicationinsights-react-js';
import { createBrowserHistory } from 'history';

const browserHistory = createBrowserHistory({ basename: '' });
const reactPlugin = new ReactPlugin();
const appInsights = new ApplicationInsights({
config: {
instrumentationKey: 'YOUR_INSTRUMENTATION_KEY_GOES_HERE',
extensions: [reactPlugin],
extensionConfig: {
[reactPlugin.identifier]: { history: browserHistory }
}
}
});
appInsights.loadAppInsights();
export { reactPlugin, appInsights };

在AppComponent使用Context.Provider 使所有子组件内可使用 useContext hook调用AppInsights实例
1
2
3
4
5
6
7
8
9
10
11
import React from "react";
import { AppInsightsContext } from "@microsoft/applicationinsights-react-js";
import { reactPlugin } from "./AppInsights";

const App = () => {
return (
<AppInsightsContext.Provider value={reactPlugin}>
/* your application here */
</AppInsightsContext.Provider>
);
};

子组件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import React from "react";
import { useAppInsightsContext } from "@microsoft/applicationinsights-react-js";

const MyComponent = () => {
const appInsights = useAppInsightsContext();

appInsights.trackMetric("Component 'MyComponent' is in use");
appInsights.trackEvent({ name: 'Component Init', properties: { 'mydata' } });

return (
<h1>My Component</h1>
);
}
export default MyComponent;

useTrackMetric

1
2
3
4
5
6
7
8
const MyComponent = () => {
const appInsights = useAppInsightsContext();
const trackComponent = useTrackMetric(appInsights, "MyComponent");

return (
<h1 onHover={trackComponent} onClick={trackComponent}>My Component</h1>
);
}

useTrackEvent

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import React, { useState, useEffect } from "react";
import { useAppInsightsContext, useTrackEvent } from "@microsoft/applicationinsights-react-js";

const MyComponent = () => {
const appInsights = useAppInsightsContext();
const [cart, setCart] = useState([]);
const trackCheckout = useTrackEvent(appInsights, "Checkout", cart);
const trackCartUpdate = useTrackEvent(appInsights, "Cart Updated", cart);
useEffect(() => {
trackCartUpdate({ cartCount: cart.length });
}, [cart]);

const performCheckout = () => {
trackCheckout();
// submit data
};

return (
<div>
<ul>
<li>Product 1 <button onClick={() => setCart([...cart, "Product 1"])}>Add to Cart</button></li>
<li>Product 2 <button onClick={() => setCart([...cart, "Product 2"])}>Add to Cart</button></li>
<li>Product 3 <button onClick={() => setCart([...cart, "Product 3"])}>Add to Cart</button></li>
<li>Product 4 <button onClick={() => setCart([...cart, "Product 4"])}>Add to Cart</button></li>
</ul>
<button onClick={performCheckout}>Checkout</button>
</div>
);
}

export default MyComponent;

Click Analytics Auto-collection plugin

自动跟踪网页上的单击事件,并使用 HTML 元素上的 data-* 属性来填充事件遥测数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { ApplicationInsights } from '@microsoft/applicationinsights-web';
import { ClickAnalyticsPlugin } from '@microsoft/applicationinsights-clickanalytics-js';

const clickPluginInstance = new ClickAnalyticsPlugin();
// Click Analytics configuration
const clickPluginConfig = {
autoCapture: true
};
// Application Insights Configuration
const configObj = {
instrumentationKey: "YOUR INSTRUMENTATION KEY",
extensions: [clickPluginInstance],
extensionConfig: {
[clickPluginInstance.identifier]: clickPluginConfig
},
};

const appInsights = new ApplicationInsights({ config: configObj });
appInsights.loadAppInsights();

监视对象

  • User 用浏览器cookie中存储的匿名id区分用户
    JavaScript SDK 自动生成匿名用户和会话 ID,然后在从应用发送这些 ID 后使用这些 ID 填充遥测事件。
  • Session 不活动半小时重新记Session 活动24h后重新记Session
  • Event 每次执行trackEvent逻辑 Event参数已加入一组标准属性,包括匿名用户id(anonymous user ID)QQs存疑!

    其他

    cookie处理 visit time

简写为SOLID

单一职责(Sigle Responsibility Principle)

避免设计对象(类,方法)承担多项职责,在对职责1操作或修改时可能会造成职责2的异常。

  • 在需要if else时考虑划分为两个类或方法
  • 在需求变更导致职责细分时考虑将类或方法划分

开放关闭(Open Closed Principle)

设计对象应该根据需求被扩展,而不应该因需求而做修改,即对类进行抽象和继承
违反开放关闭原则:

1
2
3
4
5
6
7
8
9
10
11
class Factory {
public Computer produceComputer(String type) {
Computer c = null;
if(type.equals("macbook")){
c = new Macbook();
}else if(type.equals("surface")){
c = new Surface();
}
return c;
}
}

抽象出produce接口
1
2
3
4
5
6
7
8
9
10
11
12
13
interface Factory {
public Computer produceComputer();
}
class AppleFactory implements Factory {
public Computer produceComputer() {
return new Macbook();
}
}
class MSFactory implements Factory {
public Computer produceComputer() {
return new Surface();
}
}

里氏替换(Liskov Substitution Principle)

所有引用基类的地方必须能透明地使用其子类的对象。

A的子类B继承父类时,不应改变原功能,只做扩展

接口隔离(Interface Segregation Principle)

合理设计接口的粒度,避免类型依赖它不需要的接口

依赖倒置(Dependency Injection Principle)

高层模块不应依赖低层模块,以免因为低层修改而牵扯高层,两者应该共同依赖接口,将改动限制在接口的实现上
即最少知道,类型对其直接引用的对象A保持最少的了解,不会通过该对象与第三者对象B建立间接的调用关系,要求A将必要的方法封装为public提供外部调用,而不暴露其引用了B的事实

  • 触发器是一类存储过程
  • 由数据表的事件(如insert update delete)触发,而不是手动调用

登录触发器

官网例子 场景:如果登录名login_test 已经创建了三个用户会话,触发器将拒绝该用户的登录尝试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
USE master;  
GO
CREATE LOGIN login_test WITH PASSWORD = N'3KHJ6dhx(0xVYsdf' MUST_CHANGE,
CHECK_EXPIRATION = ON;
GO
GRANT VIEW SERVER STATE TO login_test;
GO
CREATE TRIGGER connection_limit_trigger
ON ALL SERVER WITH EXECUTE AS N'login_test'
FOR LOGON
AS
BEGIN
IF ORIGINAL_LOGIN()= N'login_test' AND
(SELECT COUNT(*) FROM sys.dm_exec_sessions
WHERE is_user_process = 1 AND
original_login_name = N'login_test') > 3
ROLLBACK;
END;

可知登录触发器在身份认证之后,建立会话之前触发
多个触发器的顺序,即支持指定the first和the last 见Microsoft Docs

DDL

data define language 数据定义语言
即在使用会改变数据库数据结构的语句时触发,如CREATE、ALTER、DROP、GRANT、DENY、REVOKE 或 UPDATE STATISTICS 开头的 Transact-SQL 语句
场景

  • 防止对数据库架构进行某些更改。
  • 希望数据库中发生某种情况以响应数据库架构的更改。
  • 记录数据库架构的更改或事件。

    DML

    data manipulation language (DML) 数据操作语言 INSERT、UPDATE 或 DELETE 语句
    after 触发器
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    CREATE TRIGGER schemaA.SyncData 
    ON schemaA.TableA
    AFTER INSERT
    AS
    BEGIN
    -- SET NOCOUNT ON added to prevent extra result sets from
    -- interfering with SELECT statements.

    -- Insert statements for trigger here
    insert into schemaB.TableB(Name,TrustedId,EmailAddress,Logo,Type,Enable,RecordStatus,RecordCreated,RecordLastUpdated)
    select name,record_pk,email,logo,'1',0,0,SYSDATETIME(),SYSDATETIME() from schemaA.TableA
    SET NOCOUNT ON;
    END
    GO
    instead of 触发器

  1. DOCTYPE 有什么作用?怎么写?
    1
    <!DOCTYPE html>
    html5规范的文档声明,示意浏览器以相应的标准解析文档,使支持html5规范如新的标签等
  2. 列出常见的标签,并简单介绍这些标签用在什么场景?
    canvas
  3. 页面出现了乱码,是怎么回事?如何解决?
    1
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> 
    可能是即字符编码不匹配造成
  4. title 属性和 alt 属性分别有什么作用?
  5. HTML 的注释怎样写?
  6. data- 属性的作用?
    自定义数据属性data-*
    1
    2
    var box = document.getElementById("box"); // <div id="box" data-user-name="QQs"></div>
    var username = box.dataset.userName;
  7. \ 的 title 和 alt 有什么区别?
  8. Web 标准以及 W3C 标准是什么? web标准
  9. HTML 全局属性(Global Attribute)有哪些?
    id class style title name data-* contenteditable translate
  10. meta 有哪些常见的值?charset
  11. meta viewport 是做什么用的,怎么写?\
  12. 列出常见的标签,并简单介绍这些标签用在什么场景?
  13. 如何在 HTML 页面上展示
    这几个字符?
  14. 你是如何理解 HTML 语义化的?
  15. 前端需要注意哪些 SEO?

VBoxManage

ubuntu可ssh远程用此命令行工具管理虚拟机

  • VBoxManage list vms/runningvms
  • VBoxManage startvm MyUbuntu
    1
    vboxmanage startvm MyUbuntu --type headless #在宿主机端隐藏图形界面 
  • VBoxManage controlvm MyUbuntu poweroff

OData和GraphQL

用了微软家的OData,就不得不再看一遍GraphQL,两者都让前端调用api获得了很大的自由度
相比OData的底层渗透性,GraphQL只在Http接口层做文章,实际上将底层模型封装成schema,开放给接口,这个过程更大程度上做到了安全性和前端业务的可控。

Quick Start

见官方GraphQL Doc:各种语言实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
var express = require('express');
var {graphqlHTTP} = require('express-graphql');
var { buildSchema } = require('graphql');

var schema = buildSchema(`
type Query {
heros: [Hero]
battle(name:String): String
random: Float!
}
type Hero{
name:String
abilities: [String]
}
`);

var root = { heros: () => ([
{
name: 'Luke skywalker',
abilities: ['light sward', 'force']
},
{
name: 'Anakin skywalker',
abilities: ['light sward', 'force', 'dark']
}
]),
battle: (name) => {
return 'I am your father';
},
random: () => {
return Math.random();
}
};

var app = express();
app.use('/graphql', graphqlHTTP({
schema: schema,
rootValue: root,
graphiql: true,
}));
app.listen(3000, () => console.log('Now browse to localhost:3000/graphql'));

关于graphqlHTTP(也就是GraphiQL客户端)的Options:
schema是查询涉及的类型声明, rootValue api的查询方法的集,详见下文章节

schema 类型声明

1
2
3
4
5
6
7
var schema = buildSchema(`
type Hero {
name: String!
abilities: [String!]!
length(unit: LengthUnit = METER): Float
}
`);

标量类型

  • ID
  • String
  • Boolean
  • Int Float
    注意 !表示非空
    枚举
    1
    2
    3
    4
    5
    enum Episode {
    NEWHOPE
    EMPIRE
    JEDI
    }

接口和实现接口的类型

1
2
3
4
5
6
7
8
type Human implements Character {
id: ID!
name: String!
friends: [Character]
appearsIn: [Episode]!
starships: [Starship]
totalCredits: Int
}

联合类型

1
union SearchResult = Human | Droid | Starship

操作类型

  • query 查询:获取数据、查找
  • mutation 变更:对数据进行变更,比如增加、删除、修改
  • substription 订阅:当数据发生更改,进行消息推送

GraphQL client

前面的express-graphQL启动后打开GraphiQL页面,GraphiQL就是一个客户端,使用GraphQL client向 GraphQL 服务器上的入口端点发送一个 HTTP POST 请求,其中将 GraphQL 查询作为 JSON 载荷的 query 字段,就能调用 GraphQL 服务器。
其js实现大致是

1
2
3
4
5
6
7
8
9
10
fetch('/graphql', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
body: JSON.stringify({query: "{ hello }"})
})
.then(r => r.json())
.then(data => console.log('data returned:', data));

查询参数

schema 中的 Query声明了若干查询方法,查询方法的具体实现在root中定义,其参数类型的指定格式与typescript相同!
调用格式如下

1
2
3
{
battle(name:"dark lord")
}