0%

上一篇博客我们用理论和代码实践介绍了 使用redux 发起action 在reducers里生成state 然后重新渲染组件

Redux 的核心理念是严格的单向数据流,只能通过 dispatch(action) 的方式修改 store,流程如下:

view ->  action -> reducer -> store

但是在业务复杂的以及和api数据对接的过程中肯定会遇到大量的异步操作。我们如何来解决这些场景呢?

redux中间件

什么是redux中间件

redux中间件
这里我们先从redux的中间件说起, 中间件,顾名思义:进行中间处理的物件。类似于面向对象编程的AOP编程思想(不了解AOP的可以忽略这句话)
简单的说:中间件可以控制在store dispatch action之前和之后的业务逻辑。也就是说 中间件实现了改写 store.dispatch 方法实现了action -> reducer的拦截的行为。
如果我们分别注册三个中间件: 中间件A 中间件B 中间件C
那么

中间件A -> 中间件B-> 中间件C-> 原始 dispatch -> 中间件C -> 中间件B -> 中间件A

和异步处理的关系

综上所述:中间件可以领过的改变 dispatch的时机,这样我们就可以很方便的处理异步场景了。
因此各种 redux异步处理中间件应运而生。比较知名的有redux-thunk和redux-saga。

redux-thunk

redux-thunk中间件可以让action创建函数先不返回一个action对象,而是返回一个函数,函数传递两个参数(dispatch,getState),在函数体内进行业务逻辑的封装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function add() {
return {
type: 'ADD',
}
}

function addIfOdd() {
return (dispatch, getState) => {
const currentValue = getState();
if (currentValue % 2 == 0) {
return false;
}
//分发一个任务
dispatch(add())
}
}

详细代码可以查看分支:https://github.com/YahuiWong/react-native-typescript/tree/redux-thunk

redux-saga

sages 采用 Generator 函数来 yield Effects(包含指令的文本对象)。Generator 函数的作用是可以暂停执行,再次执行的时候从上次暂停的地方继续执行。Effect 是一个简单的对象,该对象包含了一些给 middleware 解释执行的信息。你可以通过使用 effects API 如 fork,call,take,put,cancel 等来创建 Effect。( redux-saga API 参考)

如 yield call(fetch, ‘/products’) 即 yield 了下面的对象,call 创建了一条描述结果的信息,然后,redux-saga middleware 将确保执行这些指令并将指令的结果返回给 Generator:

1
2
3
4
5
6
// Effect -> 调用 fetch 函数并传递 `./products` 作为参数
{
type: CALL,
function: fetch,
args: ['./products']
}

与 redux-thunk 不同的是,在 redux-saga 中,UI 组件自身从来不会触发任务,它们总是会 dispatch 一个 action 来通知在 UI 中哪些地方发生了改变,而不需要对 action 进行修改。redux-saga 将异步任务进行了集中处理,且方便测试。

dispacth({ type: ‘FETCH_REQUEST’, url: /* … */} );
所有的东西都必须被封装在 sagas 中。sagas 包含3个部分,用于联合执行任务:

worker saga
做所有的工作,如调用 API,进行异步请求,并且获得返回结果
watcher saga
监听被 dispatch 的 actions,当接收到 action 或者知道其被触发时,调用 worker saga 执行任务
root saga
立即启动 sagas 的唯一入口

☀ 如何使用?
首先,我们得在文件入口中加入 saga 中间件,并且启动它,它会一直运行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//...
import { createStore, applyMiddleware} from 'redux';
import createSagaMiddleware from 'redux-saga';
import appReducer from './reducers';
import rootSaga from './saga';
//...

const sagaMiddleware = createSagaMiddleware()

const store=createStore(rootReducer,applyMiddleware(sagaMiddleware));

sagaMiddleware.run(rootSaga)

render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('app')
);

然后,就可以在 sagas 文件夹中集中写 saga 文件了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import {delay} from 'redux-saga';
import {put,takeEvery,all} from 'redux-saga/effects';
import {ADD} from './actionsTypes';
function* addSync(){
yield delay(1000);
yield put({type:ADD})
}
function* watchaddSync(){
yield takeEvery("addSync",addSync)
}

export default function* rootSaga(){
yield all([
watchaddSync()
])
}

在 redux-saga 中的基本概念就是:sagas 自身不真正执行副作用(如函数 call),但是会构造一个需要执行作用的描述。中间件会执行该副作用并把结果返回给 generator 函数。

对上述例子的说明:

(1)引入的 redux-saga/effects 都是纯函数,每个函数构造一个特殊的对象,其中包含着中间件需要执行的指令,如:call(fetchUrl, url) 返回一个类似于 {type: CALL, function: fetchUrl, args: [url]} 的对象。

(2)在 watcher saga watchFetchRequests中:

首先 yield take(‘FETCH_REQUEST’) 来告诉中间件我们正在等待一个类型为 FETCH_REQUEST 的 action,然后中间件会暂停执行 wacthFetchRequests generator 函数,直到 FETCH_REQUEST action 被 dispatch。一旦我们获得了匹配的 action,中间件就会恢复执行 generator 函数。

下一条指令 fork(fetchUrl, action.url) 告诉中间件去无阻塞调用一个新的 fetchUrl 任务,action.url 作为 fetchUrl 函数的参数传递。中间件会触发 fetchUrl generator 并且不会阻塞 watchFetchRequests。当fetchUrl 开始执行的时候,watchFetchRequests 会继续监听其它的 watchFetchRequests actions。当然,JavaScript 是单线程的,redux-saga 让事情看起来是同时进行的。

(3)在 worker saga fetchUrl 中,call(fetch,url) 指示中间件去调用 fetch 函数,同时,会阻塞fetchUrl 的执行,中间件会停止 generator 函数,直到 fetch 返回的 Promise 被 resolved(或 rejected),然后才恢复执行 generator 函数。

最后,总结一下 redux-saga 的优点:

(1)声明式 Effects:所有的操作以JavaScript对象的方式被 yield,并被 middleware 执行。使得在 saga 内部测试变得更加容易,可以通过简单地遍历 Generator 并在 yield 后的成功值上面做一个 deepEqual 测试。
(2)高级的异步控制流以及并发管理:可以使用简单的同步方式描述异步流,并通过 fork 实现并发任务。
(3)架构上的优势:将所有的异步流程控制都移入到了 sagas,UI 组件不用执行业务逻辑,只需 dispatch action 就行,增强组件复用性。

详细代码可以查看分支:https://github.com/YahuiWong/react-native-typescript/tree/redux-saga

参考:
https://segmentfault.com/a/1190000007248878#articleHeader7

继react-native-redux入门 & 使用Visual Studio Code和typescript 开发调试React Native项目 之后,这次我们继续 React Native Typescript 系列

项目初始化的细节就不一一叙述的,感兴趣的同学可以看下上面两篇博客,我把最终代码放到了 我的github仓库上,里面有每个步骤的修改的代码记录。

原来的样子

首先我们修改我们的src/index.tsx 实现一个简单的计数器 代码如下

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
import React, { Component } from "react";
import {
StyleSheet,
Text,
View,
Button,
Platform
} from "react-native";
interface Props {
}
interface State {
count:number;
}
const instructions = Platform.select({
ios: 'Press Cmd+R to reload,\n' + 'Cmd+D or shake for dev menu',
android:
'Double tap R on your keyboard to reload,\n' +
'Shake or press menu button for dev menu',
});
export default class App extends Component<Props, State> {
constructor(props:Props)
{
super(props);
this.state={count:0};

}
add = ()=>{

this.setState({count:this.state.count+1})
};
render() {
return (
<View style={styles.container}>
<Text style={styles.welcome}>Welcome to React Native&typescript!</Text>
<Text style={styles.instructions}>To get started, edit src/App.js</Text>
<Text style={styles.instructions}>{instructions}</Text>
<Text style={styles.instructions}>{this.state.count}</Text>
<Button onPress={this.add} title="add" ></Button>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
welcome: {
fontSize: 20,
textAlign: 'center',
margin: 10,
},
instructions: {
textAlign: 'center',
color: '#333333',
marginBottom: 5,
},
});

开始redux化

接下来我们按照Redux管理React数据流实现代码
在开始之前我们学习下Redux
react-native-typescript-redux-201877184819
在图中,使用Redux管理React数据流的过程如图所示,Store作为唯一的state树,管理所有组件的state。组件所有的行为通过Actions来触发,然后Action更新Store中的state,Store根据state的变化同步更新React视图组件。
需要注意的是:
在主入口文件index.tsx中,通过Provider标签把Store和视图绑定:

<Provider store={store}>
    //项目代码
</Provider>

actions

指全局发布的动作指令,主要就是定义所有事件行为的

首先声明定义 actionsTypes
actionsTypes.ts

1
export const ADD ="ADD";

新建actions.ts

1
2
3
4
5
6
import {ADD} from './actionsTypes';

const add=()=>({type:ADD})
export {
add
}

reducers

Store是如何具体管理State呢?实际上是通过Reducers根据不同的Action.type来更新不同的state,即(previousState,action) => newState。最后利用Redux提供的函数combineReducers将所有的Reducer进行合并,更新整个State树

会用到的state类型
statesTypes.ts

1
2
3
export class CounterState{
count:number
}

新建reducers.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { combineReducers } from 'redux';
import {ADD} from './actionsTypes';
import {CounterState} from './statesTypes';

const defaultState={
count:0
} as CounterState;
function counter(state=defaultState,action:any){
switch (action.type) {
case ADD:
return {...state,count:state.count+1};
default:
return state;
}
}
export default combineReducers({
counter:counter
});

store

在 Redux 应用中,只允许有一个 store 对象,管理应用的 state.可以理解为一个存放APP内所有组件的state的仓库.
可以通过 combineReducers(reducers) 来实现对 state 管理的逻辑划分(多个 reducer)。

新建store.ts

1
2
3
4
5
import { createStore, applyMiddleware, compose } from 'redux';
import rootReducer from './reducers';

const store=createStore(rootReducer);
export default store;

Provider

Provider 本身并没有做很多事情,只是把 store放在 context 里罢了,本质上 Provider 就是给 connect 提供 store 用的。

新建index.tsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import React, { Component } from 'react';
import { Provider } from 'react-redux';
import Home from './home';
import store from './store';

export default class App extends Component {
render() {
return (
<Provider store={store}>
<Home/>
</Provider>
);
}
}

Container

利用Container容器组件作为桥梁,将React组件和Redux管理的数据流联系起来。Container通过connect函数将Redux的state和action转化成展示组件即React组件所需的Props。

新建home.tsx

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
import React, { Component } from 'react';
import {
StyleSheet,
Text,
View,
Button,
Platform
} from 'react-native';
import { connect, DispatchProp } from 'react-redux';
import { add } from './actions';
import {CounterState} from './statesTypes';

interface State {
}
type Props=CounterState&DispatchProp
const instructions = Platform.select({
ios: 'Press Cmd+R to reload,\n' + 'Cmd+D or shake for dev menu',
android:
'Double tap R on your keyboard to reload,\n' +
'Shake or press menu button for dev menu',
});
class Home extends Component<Props, State> {
constructor(props:Props)
{
super(props);
}
_add = ()=>{

this.props.dispatch(add())
};
render() {
return (
<View style={styles.container}>
<Text style={styles.welcome}>Welcome to React Native&typescript!</Text>
<Text style={styles.instructions}>To get started, edit src/App.js</Text>
<Text style={styles.instructions}>{instructions}</Text>
<Text style={styles.instructions}>{this.props.count}</Text>
<Button onPress={this._add} title="add" ></Button>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
welcome: {
fontSize: 20,
textAlign: 'center',
margin: 10,
},
instructions: {
textAlign: 'center',
color: '#333333',
marginBottom: 5,
},
});

const mapStateToProps = (state:any) => (
state.counter
)

export default connect(mapStateToProps)(Home);

最终代码在 https://github.com/YahuiWong/react-native-typescript/tree/114aefbd3fbb96f9c67db068b340b908ed54276d
可在线查看

什么是.NET?什么是.NET Framework?本文将从上往下,循序渐进的介绍一系列相关.NET的概念,先从类型系统开始讲起,我将通过跨语言操作这个例子来逐渐引入一系列.NET的相关概念,这主要包括:CLS、CTS(CLI)、FCL、Windows下CLR的相关核心组成、Windows下托管程序运行概念、什么是.NET Framework,.NET Core,.NET Standard及一些VS编译器相关杂项和相关阅读链接。完整的从上读到下则你可以理解个大概的.NET体系。

想看到全文 请打开:https://www.cnblogs.com/1996V/p/9037603.html

上次我们说到.Net Core 自动化部署:使用docker版jenkins部署dotnetcore应用,这次我们使用jenkins发布我们的.NET Core站点到docker容器中运行,为后面的的docker集群化部署做准备

jenkins 安装并配置必要的发布插件 Publish Over SSH Plugin

因为linux下ssh连接使用更方便快捷,所以这里建议使用ssh发布插件来完成发布操作

  1. 在插件中心搜索Publish Over SSH关键字可以找到这个插件,安装它然后重启jenkins

  2. 进入 系统管理->系统设置->Publish over SSH 根据您的发布目标服务器的情况配置ip地址 key 和Remote Directory
    注意:Remote Directory既是您发布任务指定目录的根目录/,发布任务的远程目录是以Remote Directory为基础的。

在生产环境部署docker脚本

我已经做了个自动启动和重新构建的docker-compose项目
项目地址是:https://github.com/YahuiWong/docker-netcore

使用方法:

1
2
git clone https://github.com/YahuiWong/docker-netcore
cd docker-netcore
  1. release publish your project to ./publish dir
  2. run docker-compose down remove earlier image & container
  3. run docker-compose build --no-cache build with no-cache
  4. docker-compose up -d

编写代码发布任务

代码发布的代码获取和生成部分我们这次就不详细说明了,可以到.Net Core 自动化部署:使用docker版jenkins部署dotnetcore应用查看,这里着重讲解Publish over SSH在发布任务的配置

  1. 在添加构建后操作新增选项 Send build artifacts over SSH
  2. SSH Server Name选择自己配置好的ssh服务
  3. Source files和Remove prefix和之前的配置一样,这里不细说
  4. Remote directory选择ssh服务器 /docker-netcore/publish 路径
  5. Exec command 填写
1
2
3
4
cd /data/tests/docker-netcore # 进入您的docker-compose.yml位置
docker-compose down
docker-compose build --no-cache
docker-compose up -d

保存您的发布任务,开始发布,运行成功。

什么是ELK

ELK是三个开源软件的缩写,分别表示:Elasticsearch , Logstash, Kibana , 它们都是开源软件。新增了一个FileBeat,它是一个轻量级的日志收集处理工具(Agent),Filebeat占用资源少,适合于在各个服务器上搜集日志后传输给Logstash,官方也推荐此工具。

Elasticsearch

Elasticsearch是个开源分布式搜索引擎,提供搜集、分析、存储数据三大功能。它的特点有:分布式,零配置,自动发现,索引自动分片,索引副本机制,restful风格接口,多数据源,自动搜索负载等。

Logstash

Logstash 主要是用来日志的搜集、分析、过滤日志的工具,支持大量的数据获取方式。一般工作方式为c/s架构,client端安装在需要收集日志的主机上,server端负责将收到的各节点日志进行过滤、修改等操作在一并发往elasticsearch上去。

Kibana

Kibana 也是一个开源和免费的工具,Kibana可以为 Logstash 和 ElasticSearch 提供的日志分析友好的 Web 界面,可以帮助汇总、分析和搜索重要数据日志。

如何使用ELK

ELK环境快速搭建

这里推荐使用docker-compose一键搭建 地址:https://github.com/deviantony/docker-elk.git

使用方法:

1
2
3
git clone https://github.com/deviantony/docker-elk.git
cd docker-elk
docker-compose up -d

安装完之后,浏览器上访问安装服务器的ip:5601 (注意端口号)可以打开kibana管理后台

注意:在logstash/pipeline/logstash.conf您的logstashinput配置是 tcp 5000 后面我们会用到。

net core项目中使用

代码项目中添加

项目引用Nlog 的nuget包NLog.Extensions.Logging

在Startup.cs中添加nlog启动

1
2
3
4
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddNLog();//添加NLog
……

在项目的 nlog.config文件中targets节点
添加子target

1
2
3
4
5
6
7
<!-- Tcp日志target -->
<target xsi:type="Network"
name="ownLog-tcp"
keepConnection="false"
address ="tcp://ELK服务器ip:5000"
layout="#${longdate}#${nodeName}#${logger}#${uppercase:${level}}#${callsite}#${callsite-linenumber}#${aspnet-request-url}#${aspnet-request-method}#${aspnet-mvc-controller}#${aspnet-mvc-action}#${message}#${exception:format=ToString}#"
/>

rules节点添加配置

1
<logger name="*" minlevel="Trace" writeTo="ownLog-tcp" />

启动项目,生成日志

配置kibana 添加index pattern

进入刚刚搭建好的kibana后台,按照指引创建一个index pattern
然后 discover你的log吧

脚本如下

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
-- CREATE FUNCTION fn_Index_CreateIndexName
--drop FUNCTION [dbo].[fn_Index_CreateIndexName];
CREATE FUNCTION [dbo].[fn_Index_CreateIndexName] (@equality_columns NVARCHAR(4000),@Inequality_columns NVARCHAR(4000), @index_handlE INT) RETURNS VARCHAR(128)
AS
BEGIN

DECLARE @IndexName NVARCHAR(MAX)

SET @IndexName = ISNULL(@equality_columns,@Inequality_columns)

SET @IndexName = LTRIM(REPLACE(@IndexName,'[','_'))

SET @IndexName = RTRIM(REPLACE(@IndexName,']','_'))

SET @IndexName = REPLACE(@IndexName,',','')

SET @IndexName = REPLACE(@IndexName,'_ _','_')

IF LEN(@IndexName) > 120
BEGIN

SET @IndexName = SUBSTRING(@IndexName,0,120)

END

SET @IndexName = @IndexName + CAST(@index_handlE AS NVARCHAR(15))

RETURN @IndexName
END

GO

-- CREATE FUNCTION vw_Index_MissingIndex
--drop VIEW [dbo].[vw_Index_MissingIndex]
CREATE VIEW [dbo].[vw_Index_MissingIndex]

AS
SELECT '[' + d.name + ']' as DBName,
[dbo].[fn_Index_CreateIndexName](mid.equality_columns,mid.Inequality_columns,mid.index_handlE) AS ID,
REPLACE(mid.equality_columns,',',' ASC,') AS equality_columns,
REPLACE(mid.Inequality_columns,',',' ASC,') AS Inequality_columns,
mid.Included_columns,
mid.[statement]
FROM sys.dm_db_missing_index_details mid
INNER JOIN sys.databases d
on d.database_id = mid.database_id

GO
--Drop PROCEDURE [dbo].[usp_Index_MissingIndexCreationStatements];
CREATE PROCEDURE [dbo].[usp_Index_MissingIndexCreationStatements]
AS

DECLARE @IndexCreationPlaceholder_Start AS NVARCHAR(MAX)
DECLARE @IndexCreationPlaceholder_End AS NVARCHAR(MAX)

-- PREPARE PLACEHOLDER

SET @IndexCreationPlaceholder_Start = 'IF NOT EXISTS
(SELECT * FROM {2}.sys.indexes WHERE [name] = ''IX_{0}'' )
BEGIN
CREATE NONCLUSTERED INDEX [IX_{0}] ON {1}'

SET @IndexCreationPlaceholder_End = ' WITH (PAD_INDEX = OFF,
STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF,
ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
END;' + char(13) + char(10)

-- STATEMENT CREATION

SELECT
DBName,
CASE
WHEN NOT mid.equality_columns IS NULL AND NOT mid.Inequality_columns IS NULL THEN
REPLACE(REPLACE(REPLACE(@IndexCreationPlaceholder_Start,'{0}', mid.ID),'{1}',mid.[statement]),'{2}',mid.DBName)
+ '
( ' +
COALESCE(mid.equality_columns,'') +
' ASC,' +
COALESCE(mid.Inequality_columns,'') +
' ASC
)' +
COALESCE('INCLUDE ( ' + mid.Included_columns + ' ) ','')
+ @IndexCreationPlaceholder_End

WHEN mid.equality_columns IS NULL AND NOT mid.Inequality_columns IS NULL THEN
REPLACE(REPLACE(REPLACE(@IndexCreationPlaceholder_Start,'{0}', mid.ID),'{1}',mid.[statement]),'{2}',mid.DBName)
+ '
( ' +
COALESCE(mid.Inequality_columns,'') +
' ASC
) ' +
COALESCE('INCLUDE ( ' + mid.Included_columns + ' ) ','')
+ @IndexCreationPlaceholder_End

WHEN NOT mid.equality_columns IS NULL AND mid.Inequality_columns IS NULL THEN
REPLACE(REPLACE(REPLACE(@IndexCreationPlaceholder_Start,'{0}', mid.ID),'{1}',mid.[statement]),'{2}',mid.DBName)
+ '
( ' +
COALESCE(mid.equality_columns,'') + ' ASC
) ' +
COALESCE('INCLUDE ( ' + mid.Included_columns + ' ) ','')
+ @IndexCreationPlaceholder_End
ELSE NULL
END AS Index_Creation_Statement,
' DROP INDEX [IX_' + mid.ID + '] ON ' + mid.[statement] + + char(13) + char(10) AS Index_Drop_Statement
FROM [dbo].[vw_Index_MissingIndex] AS mid

GO

脚本如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
USE YOUR_DBNAME;  
DECLARE @name varchar(100)

DECLARE authors_cursor CURSOR FOR
Select [name] from sysobjects where xtype='u' order by id

OPEN authors_cursor

FETCH NEXT FROM authors_cursor INTO @name

WHILE @@FETCH_STATUS = 0
BEGIN
DBCC DBREINDEX (@name, '', 90)
print @name
FETCH NEXT FROM authors_cursor
INTO @name
END

deallocate authors_cursor

脚本如下

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
-- 卡死中的语句
SELECT TOP 100
[session_id],
[request_id],
[start_time] AS '开始时间',
[status] AS '状态',
[command] AS '命令',
dest.[text] AS 'sql语句',
DB_NAME([database_id]) AS '数据库名',
[blocking_session_id] AS '正在阻塞其他会话的会话ID',
[wait_type] AS '等待资源类型',
[wait_time] AS '等待时间',
[wait_resource] AS '等待的资源',
[reads] AS '物理读次数',
[writes] AS '写次数',
[logical_reads] AS '逻辑读次数',
[row_count] AS '返回结果行数'
FROM sys.[dm_exec_requests] AS der
CROSS APPLY
sys.[dm_exec_sql_text](der.[sql_handle]) AS dest
WHERE [session_id]>50 AND DB_NAME(der.[database_id])='yourdbname'
ORDER BY [cpu_time] DESC

-- 查看具体语句

--在SSMS里选择以文本格式显示结果
SELECT TOP 10
dest.[text] AS 'sql语句'
FROM sys.[dm_exec_requests] AS der
CROSS APPLY
sys.[dm_exec_sql_text](der.[sql_handle]) AS dest
WHERE [session_id]>50
ORDER BY [cpu_time] DESC

--服务器 查看CPU数和user scheduler数目
SELECT cpu_count,scheduler_count FROM sys.dm_os_sys_info
--服务器 查看最大工作线程数
SELECT max_workers_count FROM sys.dm_os_sys_info

--查询缺失索引
SELECT
DatabaseName = DB_NAME(database_id)
,[Number Indexes Missing] = count(*)
FROM sys.dm_db_missing_index_details
GROUP BY DB_NAME(database_id)
ORDER BY 2 DESC;



declare m_cursor cursor scroll for

SELECT TOP 10
[Total Cost] = ROUND(avg_total_user_cost * avg_user_impact * (user_seeks + user_scans),0)
, avg_user_impact
, TableName = statement
, [EqualityUsage] = equality_columns
, [InequalityUsage] = inequality_columns
, [Include Cloumns] = included_columns
FROM sys.dm_db_missing_index_groups g
INNER JOIN sys.dm_db_missing_index_group_stats s
ON s.group_handle = g.index_group_handle
INNER JOIN sys.dm_db_missing_index_details d
ON d.index_handle = g.index_handle
ORDER BY [Total Cost] DESC;

open m_cursor
declare @TotalCost int,@avg_user_impact float, @TableName varchar(50), @EqualityUsage varchar(500),@InequalityUsage varchar(500), @IncludeCloumns varchar(500)
--填充数据
fetch next from m_cursor into @TotalCost,@avg_user_impact,@TableName,@EqualityUsage,@InequalityUsage,@IncludeCloumns
--假如检索到了数据,才处理
while @@FETCH_STATUS=0
begin
print @TableName+@@IncludeCloumns
fetch next from m_cursor into @TotalCost,@avg_user_impact,@TableName,@EqualityUsage,@InequalityUsage,@IncludeCloumns
end
-- 关闭游标
close m_cursor
--释放游标
deallocate m_cursor
----定位问题后,新建非聚集索引
-- ……

脚本如下

1
2
3
4
5
6
7
8
9
SELECT OBJECT_NAME(ind.OBJECT_ID) AS TableName, 
ind.name AS IndexName, indexstats.index_type_desc AS IndexType,
indexstats.avg_fragmentation_in_percent
FROM sys.dm_db_index_physical_stats(DB_ID(), NULL, NULL, NULL, NULL) indexstats
INNER JOIN sys.indexes ind
ON ind.object_id = indexstats.object_id
AND ind.index_id = indexstats.index_id
WHERE indexstats.avg_fragmentation_in_percent > 30
ORDER BY indexstats.avg_fragmentation_in_percent DESC

安装docker版jenkins

因为jenkins的docker版本本身没有 dotnetcore的环境,所以我们需要先自己动手制作下包含dotnet环境的jenkins Docker Container

Dockerfile

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
FROM jenkins/jenkins

# Switch to root to install .NET Core SDK
USER root

# Show distro information!
RUN uname -a && cat /etc/*release

# Based on instructiions at https://www.microsoft.com/net/download/linux-package-manager/debian9/sdk-current
# Install dependency for .NET Core 2
RUN apt-get update
RUN apt-get install -y curl libunwind8 gettext apt-transport-https

# Based on instructions at https://www.microsoft.com/net/download/linux-package-manager/debian9/sdk-current
# Install microsoft.qpg
RUN curl https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > microsoft.gpg
RUN mv microsoft.gpg /etc/apt/trusted.gpg.d/microsoft.gpg
RUN sh -c 'echo "deb [arch=amd64] https://packages.microsoft.com/repos/microsoft-debian-stretch-prod stretch main" > /etc/apt/sources.list.d/dotnetdev.list'

# Install the .NET Core framework
RUN apt-get update
RUN apt-get install -y dotnet-sdk-2.1.4

# Switch back to the jenkins user.
USER jenkins

为了方便我已经把配置信息放到了github上大家可以访问直接使用

https://github.com/YahuiWong/jenkins-dotnet-core

使用步骤

初始化docker环境

1
2
3
4
git clone https://github.com/YahuiWong/jenkins-dotnet-core.git
cd jenkins-dotnet-core
sh init.sh
docker-compose up -d

初始化配置jenkins

  1. Open ip:8080 on the browser

  2. vi jenkins_home/secrets/initialAdminPassword & Set the initialAdminPassword string to your jenkins page

至此安装完毕。

配置jenkins构建dotent core的任务

构建一个自由风格的软件项目

jenkins-docker-dotnet-core-publish-201853118046

配置源码管理资料

jenkins-docker-dotnet-core-publish-201853118236

注意:初次配置的话 需要在 添加一个可以访问代码地址的 Credentials

配置构建脚本

如下图步骤添加打包脚本
jenkins-docker-dotnet-core-publish-201853118512

打包脚本示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
echo '============查看打包环境================' 
pwd
ls
echo $PATH
whoami
which dotnet
dotnet --info
dotnet --version
echo '============================begin restore======================================='
dotnet restore
echo '============================end restore======================================='
echo '============================cd project======================================='
cd ./您的项目路径
echo '============================begin build======================================='
dotnet build # 为了生成XML注释文件 用于swagger注释
rm -rf $WORKSPACE/jenkins_publish
mkdir $WORKSPACE/jenkins_publish
dotnet publish -c:Release -o $WORKSPACE/jenkins_publish # 如果针对给定运行时发布项目带上-r 如:-r centos.7-x64
cp ./bin/Debug/netcoreapp2.0/您的项目路径.xml $WORKSPACE/jenkins_publish/ # 拷贝swagger注释
echo '============================end build======================================='

配置发布途径

发布插件Publish Over FTP使用说明

我这里使用的是 jenkins的Publish Over FTP插件,安装好此插件之后在 系统管理->系统设置->Publish over FTP 里可以新增一个你要发布的ftp服务器信息。为了保证ftp可以正常连接建议尝试下右下角的Test Configuration确认success

上面我们已经配置好Publish Over FTP要用的ftp账号,新增我们新增构建后操作如下图选择此插件
jenkins-docker-dotnet-core-publish-2018531182746

然后选择配置好的ftp选项,配置如下所示
jenkins-docker-dotnet-core-publish-2018531183019

注意:这里的配置信息中 jenkins_publish是和打包脚本对应的

保存打包任务

立即构建

点击立即构建之后,把构建任务开始进行中
jenkins-docker-dotnet-core-publish-2018531184031

如果想看的实时的构建信息可以点击如下图看到控制台输出
jenkins-docker-dotnet-core-publish-2018531184219

构建完毕

打开部署站点地址,发现站点已经运行起来了

jenkins-docker-dotnet-core-publish-2018531184328

重点说明

Nuget配置说明

如果您的.NET CORE项目使用的了第三方或者自建nuget服务,您需要在 配置下 jenkins_home/.nuget/NuGet/NuGet.Config的信息
如我的使用了国内博客园的nuget加速地址和自建的nuget服务配置如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
<add key="nuget.cnblogs.com" value="https://nuget.cnblogs.com/v3/index.json" />
<add key="dotnet.myget.org/F/aspnetcore-dev/api/v3/index.json" value="https://dotnet.myget.org/F/aspnetcore-dev/ap
i/v3/index.json" />
<add key="192.168.1.32:9001/nuget" value="http://192.168.1.32:9001/nuget" />
</packageSources>
<packageRestore>
<add key="enabled" value="True" />
<add key="automatic" value="True" />
</packageRestore>
<bindingRedirects>
<add key="skip" value="False" />
</bindingRedirects>
<packageManagement>
<add key="format" value="0" />
<add key="disabled" value="False" />
</packageManagement>
</configuration>

发布iis站点时,文件被占用

使用 app_offline.htm配置文件解决

可以先用ftp插件上传一个app_offline.htm文件,部署完站点之后删除此文件
具体参考:https://docs.microsoft.com/zh-cn/aspnet/core/host-and-deploy/aspnet-core-module?view=aspnetcore-2.1#app_offlinehtm

执行远程命令 解除占用

上一种解决方案文件被占用的方案有个弊端,app_offline.htm生效时间无法保证,所以最直接方案是直接使用命令行 关闭和启动站点 来解除占用

安装 配置 SSH plugin

搜索到插件安装好插件之后 在 系统设置 -> 全局设置 里面设置 目标服务ssh的ip端口&账号信息
为了保证是可以使用的 请 Test Configuration 通过
需要注意的是:Remote Directory 选项是 该ssh账号的默认目录 在之后的发布任务 设置的目录又是以 Remote Directory 为 根目录为基础的!

构建任务设置顺序 调整

核心模块 调成为:编译打包->ssh远程关闭iis站点->发布到远程根目录->ssh远程开启iis站点

转载请注明出处 http://blog.yahui.wang/2018/05/31/jenkins-docker-dotnet-core-publish/