节点通讯存在两种模型:共享内存(Shared memory)和消息传递(Messages passing)。javascript
内存映射文件对于托管世界的开发人员来讲彷佛很陌生,但它确实已是很远古的技术了,并且在操做系统中地位至关。实际上,任何想要共享数据的通讯模型都会在幕后使用它。html
内存映射文件到底是个什么?内存映射文件容许你保留一块地址空间,而后将该物理存储映射到这块内存空间中进行操做。物理存储是文件管理,而内存映射文件是操做系统级内存管理。前端
优点:
1.访问磁盘文件上的数据不需执行I/O操做和缓存操做(当访问文件数据时,做用尤为显著);
2.让运行在同一台机器上的多个进程共享数据(单机多进程间数据通讯效率最高);java
利用文件与内存空间之间的映射,应用程序(包括多个进程)能够经过直接在内存中进行读写来修改文件。.NET Framework 4 用托管代码按照本机Windows函数访问内存映射文件的方式来访问内存映射文件,管理 Win32 中的内存映射文件 。ios
有两种类型的内存映射文件:web
-
持久内存映射文件redis
持久文件是与磁盘上的源文件关联的内存映射文件。在最后一个进程使用完此文件后,数据将保存到磁盘上的源文件中。这些内存映射文件适合用来处理很是大的源文件。数据库
-
非持久内存映射文件c#
非持久文件是未与磁盘上的源文件关联的内存映射文件。当最后一个进程使用完此文件后,数据将丢失,而且垃圾回收功能将回收此文件。这些文件适用于为进程间通讯 (IPC) 建立共享内存。浏览器
1)在多个进程之间进行共享(进程可经过使用由建立同一内存映射文件的进程所指派的公用名来映射到此文件)。
2)若要使用一个内存映射文件,则必须建立该内存映射文件的完整视图或部分视图。还能够建立内存映射文件的同一部分的多个视图,进而建立并发内存。为了使两个视图可以并发,必须基于同一内存映射文件建立这两个视图。
3)若是文件大于应用程序用于内存映射的逻辑内存空间(在 32 位计算机上为2GB),则还须要使用多个视图。
有两种类型的视图:流访问视图和随机访问视图。使用流访问视图可对文件进行顺序访问;在使用持久文件时,随机访问视图是首选方法。
.Net 共享内存 内存映射文件原理:经过操做系统的内存管理器访问的,所以会自动将此文件分隔为多个页,并根据须要对其进行访问。您不须要自行处理内存管理。以下图:
C# .Net 共享内存 演示代码以下:
//持久内存映射文件:基于现有文件建立一个具备指定公用名的内存映射文件
using (var mmf = MemoryMappedFile.CreateFromFile(@"c:\内存映射文件.data", FileMode.Open, "公用名"))
{
//经过指定的 偏移量和大小 建立内存映射文件视图服务器
using (var accessor = mmf.CreateViewAccessor(offset, length)) //偏移量,能够控制数据存储的内存位置;大小,用来控制存储所占用的空间
{
//Marshal提供了一个方法集,这些方法用于分配非托管内存、复制非托管内存块、将托管类型转换为非托管类型,此外还提供了在与非托管代码交互时使用的其余杂项方法。
int size = Marshal.SizeOf(typeof(char));
//修改内存映射文件视图
for (long i = 0; i < length; i += size)
{
char c= accessor.ReadChar(i);
accessor.Write(i, ref c);
}
}
}
//另外一个进程或线程能够,在系统内存中打开一个具备指定名称的现有内存映射文件
using (var mmf = MemoryMappedFile.OpenExisting("公用名"))
{
using (var accessor = mmf.CreateViewAccessor(4000000, 2000000))
{
int size = Marshal.SizeOf(typeof(char));
for (long i = 0; i < length; i += size)
{
char c = accessor.ReadChar(i);
accessor.Write(i, ref c);
}
}
}
//非持久内存映射文件:未映射到磁盘上的现有文件的内存映射文件
using (MemoryMappedFile mmf = MemoryMappedFile.CreateNew("testmap", 10000))
{
bool mutexCreated;
//进程间同步
Mutex mutex = newMutex(true, "testmapmutex", out mutexCreated);
using (var stream = mmf.CreateViewStream()) //建立文件内存视图流 基于流的操做
{
var writer = newBinaryWriter(stream);
writer.Write(1);
}
mutex.ReleaseMutex();
Console.WriteLine("Start Process B and press ENTER to continue.");
Console.ReadLine();
mutex.WaitOne();
using (MemoryMappedViewStream stream = mmf.CreateViewStream())
{
var reader = newBinaryReader(stream);
Console.WriteLine("Process A says: {0}", reader.ReadBoolean());
Console.WriteLine("Process B says: {0}", reader.ReadBoolean());
}
mutex.ReleaseMutex();
}
using (MemoryMappedFile mmf = MemoryMappedFile.OpenExisting("testmap"))
{
Mutex mutex = Mutex.OpenExisting("testmapmutex");
mutex.WaitOne();
using (var stream = mmf.CreateViewStream(1, 0))//注意这里的偏移量
{
var writer = newBinaryWriter(stream);
writer.Write(0);
}
mutex.ReleaseMutex();
}
C# .Net 进程间通讯 共享内存 完整示例: C#共享内存非持久化方式通信的例子,通信时的线程和进程控制也没有问题。以下是实现的代码。
先启动消息服务IMServer_Message,
再启动状态服务IMServer_State,
IMServer_Message回车一次(建立共享内存公用名和公用线程锁,并视图流方式写共享内存),
IMServer_State回车一次(获取共享内存并视图流方式写、视图访问器写入结构体类型)
并马上IMServer_Message再回车一次(读取刚刚写入的信息),
观察IMServer_State屏显变化并等待(线程锁)约5s(线程锁被释放)后
在IMServer_Message上观察屏显(显示刚刚写入共享内存的信息)
IMServer_Message.exe 代码
using System;
using System.IO;
using System.IO.MemoryMappedFiles;
using System.Runtime.InteropServices;
using System.Threading;
namespace IMServer_Message
{
/// <summary>
/// 用于共享内存方式通讯的 值类型 结构体
/// </summary>
public struct ServiceMsg
{
public int Id;
public long NowTime;
}
internal class Program
{
private static void Main(string[] args)
{
Console.Write("请输入共享内存公用名(默认:testmap):");
string shareName = Console.ReadLine();
if (string.IsNullOrEmpty(shareName))
shareName = "testmap";
using (MemoryMappedFile mmf = MemoryMappedFile.CreateOrOpen(shareName, 1024000,MemoryMappedFileAccess.ReadWrite))
{
bool mutexCreated;
//进程间同步
var mutex = new Mutex(true, "testmapmutex", out mutexCreated);
using (MemoryMappedViewStream stream = mmf.CreateViewStream()) //建立文件内存视图流
{
var writer = new BinaryWriter(stream);
for (int i = 0; i < 5; i++)
{
writer.Write(i);
Console.WriteLine("{0}位置写入流:{0}", i);
}
}
mutex.ReleaseMutex();
Console.WriteLine("启动状态服务,按【回车】读取共享内存数据");
Console.ReadLine();
mutex.WaitOne();
using (MemoryMappedViewStream stream = mmf.CreateViewStream())
{
var reader = new BinaryReader(stream);
for (int i = 0; i < 10; i++)
{
Console.WriteLine("{1}位置:{0}", reader.ReadInt32(), i);
}
}
using (MemoryMappedViewAccessor accessor = mmf.CreateViewAccessor(1024, 10240))
{
int colorSize = Marshal.SizeOf(typeof (ServiceMsg));
ServiceMsg color;
for (int i = 0; i < 50; i += colorSize)
{
accessor.Read(i, out color);
Console.WriteLine("{1}\tNowTime:{0}", new DateTime(color.NowTime), color.Id);
}
}
mutex.ReleaseMutex();
}
Console.WriteLine("测试: 我是 即时通信 - 消息服务 我启动啦!!!");
Console.ReadKey();
}
}
}
IMServer_State.exe代码
using System;
using System.IO;
using System.IO.MemoryMappedFiles;
using System.Runtime.InteropServices;
using System.Threading;
namespace IMServer_State
{
/// <summary>
/// 用于共享内存方式通讯的 值类型 结构体
/// </summary>
public struct ServiceMsg
{
public int Id;
public long NowTime;
}
internal class Program
{
private static void Main(string[] args)
{
Console.Write("请输入共享内存公用名(默认:testmap):");
string shareName = Console.ReadLine();
if (string.IsNullOrEmpty(shareName))
shareName = "testmap";
using (MemoryMappedFile mmf = MemoryMappedFile.CreateOrOpen(shareName, 1024000,MemoryMappedFileAccess.ReadWrite))
{
Mutex mutex = Mutex.OpenExisting("testmapmutex");
mutex.WaitOne();
using (MemoryMappedViewStream stream = mmf.CreateViewStream(20, 0)) //注意这里的偏移量
{
var writer = new BinaryWriter(stream);
for (int i = 5; i < 10; i++)
{
writer.Write(i);
Console.WriteLine("{0}位置写入流:{0}", i);
}
}
using (MemoryMappedViewAccessor accessor = mmf.CreateViewAccessor(1024, 10240))
{
int colorSize = Marshal.SizeOf(typeof (ServiceMsg));
var color = new ServiceMsg();
for (int i = 0; i < colorSize*5; i += colorSize)
{
color.Id = i;
color.NowTime = DateTime.Now.Ticks;
//accessor.Read(i, out color);
accessor.Write(i, ref color);
Console.WriteLine("{1}\tNowTime:{0}", new DateTime(color.NowTime), color.Id);
Thread.Sleep(1000);
}
}
Thread.Sleep(5000);
mutex.ReleaseMutex();
}
Console.WriteLine("测试: 我是 即时通信 - 状态服务 我启动啦!!!");
Console.ReadKey();
}
}
}
进程A写数据,进程B读数据;
进程A:
#include "stdafx.h"
#include <Windows.h>
#include <iostream>
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
{
HANDLE lhShareMemory;
char* lpBuffer = NULL;
lhShareMemory = CreateFileMapping(HANDLE(0xFFFFFFFF), NULL, PAGE_READWRITE,
0, 10, "mySharedMemory");
if (NULL == lhShareMemory)
{
if (ERROR_ALREADY_EXISTS == GetLastError())
{
cout << "Already exists!";
}
else
{
cout << "Create Sheared Memory unsuccessfully!";
}
return 0;
}
lpBuffer = (char*)MapViewOfFile(lhShareMemory, FILE_MAP_WRITE, 0, 0, 10);
if (NULL == lpBuffer)
{
cout << "Get Share memory unsuccessfully!";
return 0;
}
strcpy(lpBuffer, "hello");
cout << *(lpBuffer + 40) << endl;
Sleep(600000);
UnmapViewOfFile(lpBuffer);
return 0;
}
进程B:
#include "stdafx.h"
#include <Windows.h>
#include <iostream>
#include <string>
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
{
HANDLE lhShareMemory;
char* lpcBuffer;
lhShareMemory = OpenFileMapping(FILE_MAP_READ, false, "mySharedMemory");
if (NULL == lhShareMemory)
{
cout << "Open share memory unsuccessfully!" << endl;
DWORD ldwError = GetLastError();
cout << ldwError;
return 0;
}
lpcBuffer = (char*)MapViewOfFile(lhShareMemory, FILE_MAP_READ, 0, 0, 100);
if (NULL == lpcBuffer)
{
cout << "Open share memory unsuccessfully!";
return 0;
}
for (int i = 0; i < 100; ++i)
{
cout << *(lpcBuffer + i);
}
UnmapViewOfFile(lpcBuffer);
return 0;
}
.net环境下跨进程、高频率读写数据
1、需求背景
一、最近项目要求高频次地读写数据,数据量也不是很大,多表总共加起来在百万条上下。
单表最大的也在25万左右,历史数据表由于不涉及因此不用考虑,
难点在于这个规模的热点数据,变化很是频繁。
数据来源于一些检测设备的采集数据,一些大表,有可能在极短期内(如几秒钟)可能大部分都会变化,
并且主程序也有一些后台服务须要不断轮询、读写某种类型的设备,因此要求信息交互时间尽量短。
二、以前的解决方案是把全部热点数据,统一加载到共享内存里边,到也可以支撑的住(毫秒级的),可是因为系统架构升级,以前的程序(20年前的)不能兼容。
只能从新写一个,最早想到的是用redis,当时把全部API重写完成后,测试发现效率不行,是的,你没有看错,redis也是有使用范围的。
三、redis读写很是快,可是对于大批量读写操做我以为支持不够,虽然redis支持批量读写,可是效率仍是不够快,
对于字符串(string)类型的批量读写,我测试过;效率比较好的在每批次200 至 250条之间,处理20万条数据耗时5秒左右, (PC机,8G,4核)
而对于有序集合(sorted set)类型,批量写的操做用起来很是别扭,并且没有修改API(若有其余方式请指教),我测试过,效率没string类型那么高
其余类型不适合个人业务场景,就没考虑使用了
四、因此项目组最后决定仍是用回共享内存,先决定在.net环境下使用c#的共享内存,这个功能可能使用的人很少,其实在.net4.0版本就已经集成进来了
在System.IO.MemoryMappedFile命名空间下。这个类库让人很无语,由于里边能用的只有Write、Read这2种方法,并且只是针对字节的操做,
须要很是多的类型转换,很是麻烦!想一想,只能以字节为单位去构建一个须要存放百万级数据的内存数据库,得多麻烦?
须要手动搞定索引功能,由于要支持各类查询,最后花了一天的时间写完DEMO,最后测试后发现效率并无很大提升,由于当时加了互斥量测试,
可是离毫秒级差得远。这个技术点有兴趣的能够了解下,园子里有,如:https://www.cnblogs.com/zeroone/archive/2012/04/18/2454776.html
2、没错,第一节写的太多了
一、最后分析,这应该是c#语言的瓶颈,c#对于这种骚操做是不那么成熟的。
二、最后瞄来瞄去,决定使用VC开发一个dll,在里边封装对内存数据的读写功能,而后c#调用
三、本人的C、C++不那么熟、参考了一些实例,好比园子里的:http://www.cnblogs.com/cwbcwb505/archive/2008/12/08/1350505.html
四、是的,你没有看错,2008年的,我还看到一篇更早的,看来底层开发C、C++那么经久不衰不是没有道理的,不少技术如今都在用
五、看看什么是共享内存
3、开始写代码了
一、首先建2个控制台项目,支持MFC,
二、先这样:一个负责建立共享内存,初始化数据
三、再这样:一个读写数据测试,最后修改
四、最后修改下图片细节,测试一下,看看效果
五、完成了,see, 是否是很简单呀?都会了吗?
4、真的要贴代码了
一、先定义个枚举返回状态
1 typedef enum 2 { 3 Success = 0, 4 AlreadyExists = 1, 5 Error = 2, 6 OverSize = 3 7 }enumMemory;
二、再定义个结构体用来测试
1 typedef struct 2 { 3 int TagID; 4 char TagName[32]; 5 int Area; 6 double EngVal; 7 double UpdateTime; 8 double RawMax; 9 double RawMin; 10 double RawVal; 11 char Name[50]; 12 char Al; 13 double ASTime; 14 char MaskState; 15 double AMTime; 16 char Cf; 17 char Tdf; 18 char AlarmCode[32]; 19 }TENG;
三、开始建立共享内存
1 int Create(UINT size) 2 { 3 // Data 4 HANDLE fileMap = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, size, “Name”); 5 6 if (fileMap == NULL || fileMap == INVALID_HANDLE_VALUE) 7 return Error; 8 9 if (GetLastError() == ERROR_ALREADY_EXISTS) 10 return AlreadyExists; 11 12 // init 13 void *mapView = MapViewOfFile(fileMap, FILE_MAP_WRITE, 0, 0, size); 14 15 if (mapView == NULL) 16 return Error; 17 else 18 memset(mapView, 0, size); 19 20 return Success; 21 }
四、再开始写数据
1 int Write(void *pDate, UINT nSize, UINT offset) 2 { 3 // open 4 HANDLE fileMap = OpenFileMapping(FILE_MAP_WRITE, FALSE, “Name”); 5 6 if (fileMap == NULL) 7 return Error; 8 9 // hander 10 void *mapView = MapViewOfFile(fileMap, FILE_MAP_WRITE, 0, 0, nSize); 11 12 if (mapView == NULL) 13 return Error; 14 else 15 WriteDataPtr = mapView; 16 17 // write 18 memcpy(mapView, pDate, nSize); 19 20 UnmapViewOfFile(pMapView); 21 return Success; 22 }
五、开始读数据
1 int Read(void *pData, UINT nSize, UINT offset) 2 { 3 // open 4 HANDLE fileMap = OpenFileMapping(FILE_MAP_READ, FALSE, GetTableName()); 5 6 if (fileMap == NULL) 7 return Error; 8 9 // hander 10 void *pMapView = MapViewOfFile(fileMap, FILE_MAP_READ, 0, 0, nSize); 11 12 if (pMapView == NULL) 13 return Error; 14 else 15 ReadDataPtr = pMapView; 16 17 memcpy(pData, (pMapView, nSize); 18 19 UnmapViewOfFile(pMapView); 20 return Success; 21 }
六、OK了,不复杂,网上都有这些资料,最后咱们贴上测试程序
1 int _tmain(int argc, TCHAR* argv[], TCHAR* envp[]) 2 { 3 int length = 100000; 4 CEng * ceng = new CEng(); 5 DWORD dwStart = GetTickCount(); 6 7 for (int i = 0; i < length; i++) { 8 TENG eng; 9 ceng->Read(&eng, ceng->size, ceng->size * i); 10 11 eng.EngVal = i; 12 ceng->Write(&eng, ceng->size, (i*ceng->size)); 13 14 if (i % 10000 == 0 || i == length - 1) 15 printf("正在读写的Eng.TagName:%s \n", eng.TagName); 16 } 17 18 printf("总条数%d,耗时:%d 毫秒 \n", length, GetTickCount() - dwStart); 19 20 // 验证数据 21 TENG eng5000; 22 ceng->Read(&eng5000, ceng->size, ceng->size * 5000); 23 printf("\n验证数据 \n"); 24 printf("第5000个Eng的TagID:%d, EngVal:%lf \n", eng5000.TagID, eng5000.EngVal); 25 26 27 scanf_s("按任意键结束"); 28 return 0; 29 }
七、还有写测试程序
1 int _tmain(int argc, TCHAR* argv[], TCHAR* envp[]) 2 { 3 int length = 100000; 4 CEng * ceng = new CEng(); 5 ceng->Create(ceng->size * length); 6 7 DWORD dwStart = GetTickCount(); 8 9 for (int i = 0; i < length; i++) 10 { 11 TENG eng; 12 memset(&eng, 0, ceng->size); 13 14 eng.TagID = i; 15 sprintf_s(eng.AlarmCode, "AlarmCode.%d", i); 16 sprintf_s(eng.TagName, "TagName.%d", i); 17 18 if (i % 10000 == 0 || i == length - 1) 19 printf("正在写入的Eng.TagName:%s \n", eng.TagName); 20 21 ceng->Write(&eng, ceng->size, (i*ceng->size)); 22 } 23 24 25 // print time 26 printf("写入数据完毕,总条数:%d\n", length); 27 printf("初始化值共享内存区耗时:%d 毫秒 \n", GetTickCount() - dwStart); 28 29 30 scanf_s("按任意键结束"); 31 return 0; 32 }
八、固然得再贴一遍啦
5、差点忘记作成DLL了
一、定义外部函数
1 extern "C" __declspec(dllexport) int ReadFromSharedMemory(TENG *pData, int nSize, int offset) 2 { 3 return ceng->Read(pData, nSize, offset); 4 } 5 6 extern "C" __declspec(dllexport) int WriteToSharedMemory(void *pData, int nSize, int offset) 7 { 8 return ceng->Write(pData, nSize, offset); 9 }
二、好了,VC到此为止,能够去领盒饭了,c#进场
1 public class Lib 2 { 3 [DllImport("ConsoleApplication4.dll", CallingConvention = CallingConvention.Cdecl)] 4 public static extern int ReadFromSharedMemory(IntPtr pData, int nSize, int offset); 5 6 [DllImport("ConsoleApplication4.dll", CallingConvention = CallingConvention.Cdecl)] 7 public static extern int WriteToSharedMemory(IntPtr pData, int nSize, int offset); 8 }
三、c#测试一下
1 static void Main(string[] args) 2 { 3 var length = 100000; 4 var startTime = DateTime.Now; 5 var size = Marshal.SizeOf(typeof(TEng)); 6 var intPtrOut = Marshal.AllocHGlobal(size); 7 var intPtrIn = Marshal.AllocHGlobal(size); 8 9 for (var i = 0; i < length; i++) 10 { 11 Lib.ReadFromSharedMemory(intPtrOut, size, size * i); 12 13 var eng = Marshal.PtrToStructure<TEng>(intPtrOut); 14 eng.EngVal = i; 15 16 Marshal.StructureToPtr(eng, intPtrIn, true); 17 Lib.WriteToSharedMemory(intPtrIn, size, size * i); 18 19 if (i % 10000 == 0) 20 Console.WriteLine("eng.TagID:{0}", eng.TagID); 21 } 22 23 Console.WriteLine("总条数{0},耗时:{1} 毫秒", length.ToString(), 24 (DateTime.Now - startTime).TotalMilliseconds.ToString()); 25 26 // 验证数据 27 var intPtr100 = Marshal.AllocHGlobal(size); 28 Lib.ReadFromSharedMemory(intPtr100, size, size * 100); 29 30 var eng100 = Marshal.PtrToStructure<TEng>(intPtr100); 31 32 Console.WriteLine(); 33 Console.WriteLine("验证数据"); 34 Console.WriteLine("第100个Eng的TagID:{0},EngVal:{1}", eng100.TagID, eng100.EngVal); 35 36 Console.ReadKey(); 37 }
四、165毫秒,相比在VC下运行,差了一个数量级,可是,也不错了;
由于c#环境下须要不断的Marshal.PtrToStructure、Marshal.StructureToPtr,频繁地把数据在托管内存俞共享内存之间搬运
是须要耗费时间的,这点有更好处理方式的请指教,
6、由于跨线程、进程,因此要考虑加入互斥量哦
一、很简单,MFC下有现成的类CMutex,加在Write里边在看看效率
互斥量是须要耗费资源的,多了将进100毫秒
二、读写都加上互斥量试试看
又多了80多毫秒,
鱼与熊掌不可兼得啊。要根据实际运用场景以为是否加上互斥量
好了,人家51去游玩、我却宅家里码程序,可见个人趣味仍是挺高的,洗澡、洗衣服、而后去吃饭、一天没进食了,
内存映射
1,Redis操做性能,单机220w每秒,存放数据15亿,至强32核,512G内存
2,C#内存操做,每秒1.6亿次,AMD8核8G内存
3,C#共享内存操做,简称MMF,4000w读写每秒
使用C#开发Android应用之WebApp
近段时间了解了一下VS2017开发安卓应用的一些技术,特意把C#开发WebApp的一些过程记录下来,
欢迎你们一块儿指教、讨论,废话少说,是时候开始表演真正的技术了。。
一、新建空白Android应用
二、拖一个WebView控件进来
三、打开模拟器Genymotion,选择一个系统版本,启动
四、加载网页
4.1 打开MainActivity.cs,在OnCreate方法里添加2行代码
1 protected override void OnCreate(Bundle savedInstanceState) 2 { 3 base.OnCreate(savedInstanceState); 4 5 // Set our view from the "main" layout resource 6 SetContentView(Resource.Layout.Main); 7 8 var web = FindViewById<WebView>(Resource.Id.webView1); 9 web.LoadUrl("http://www.baidu.com"); 10 }
加载网页就是这样简单,F5调试,就能够看到模拟器有了变化,打开了咱们的应用,并如期加载了网页
五、网页端调用手机APP后台方法
5.1 打开MainActivity.cs,重写OnCreate为以下
1 protected override void OnCreate(Bundle savedInstanceState) 2 { 3 base.OnCreate(savedInstanceState); 4 5 var webView = new WebView(this); 6 SetContentView(webView); 7 8 webView.Settings.JavaScriptEnabled = true; 9 webView.AddJavascriptInterface(new CustomJSInterface(this), "CSharp"); 10 webView.LoadUrl("http://192.168.0.113:8080/"); 11 }
标红的是实现前端调用后台方法的关键,新建CustomJSInterface.cs
1 public class CustomJSInterface : Java.Lang.Object 2 { 3 Context context; 4 5 public CustomJSInterface(Context context) 6 { 7 this.context = context; 8 } 9 10 [Export] 11 [JavascriptInterface] 12 public void ShowToast(string message) 13 { 14 Toast.MakeText(context, message, ToastLength.Short).Show(); 15 } 16 }
而"http://192.168.0.113:8080/"是咱们的Web站点,大部分业务逻辑在网站里处理,WebApp只是在外表包了一个壳
5.2 咱们再新建一个本地Web站点
改动首页HTML,主要功能是点击按钮,会调用后台ShowToast,这是个提示功能
1 @{ 2 ViewBag.Title = "Home Page"; 3 } 4 5 <br /> 6 <div class="jumbotron"> 7 <button type="button" onClick="CSharp.ShowToast('hello')">由前端调用C#后台方法</button> 8 </div>
浏览器预览
5.3 VS2017按F5部署,能够看的模拟器也正常把本地站点加载进来了
点击"获取前端JS返回的数据"
六、APP执行前端JS方法
6.1 重写OnCreate
1 // 必须重写WebView客户端 2 webView.SetWebViewClient(new CustomWebViewClient()); 3 // 先打开首页 4 webView.LoadUrl("http://192.168.0.113:8080/"); 5 6 // APP主动获取前端数据 7 var btn = FindViewById<Button>(Resource.Id.button1); 8 btn.Click += delegate 9 { 10 var callback = new ReceiveValueCallback(); 11 callback.OnReceiveValueCallback += (message) => 12 { 13 Toast.MakeText(this.ApplicationContext, message, ToastLength.Short).Show(); 14 }; 15 16 webView.EvaluateJavascript("GetData()", callback); 17 };
6.2 新建CustomWebViewClient.cs
1 class CustomWebViewClient : WebViewClient 2 { 3 public override bool ShouldOverrideUrlLoading(WebView view, String url) 4 { 5 view.LoadUrl(url); 6 return true; 7 } 8 }
6.3 新建ReceiveValueCallback.cs,这个类主要负责处理前端返回的数据
1 public class ReceiveValueCallback : Java.Lang.Object, IValueCallback 2 { 3 public delegate void OnReceiveValueCallbackHandler(string message); 4 public event OnReceiveValueCallbackHandler OnReceiveValueCallback; 5 6 // 重写ReceiveValue方法 7 public void OnReceiveValue(Java.Lang.Object value) 8 { 9 OnReceiveValueCallback(value.ToString()); 10 } 11 }
6.4 修改Index.html
1 @{ 2 ViewBag.Title = "Home Page"; 3 } 4 5 <br /> 6 <div class="jumbotron"> 7 <button type="button" onClick="CSharp.ShowToast('hello')">由前端调用C#后台方法</button> 8 </div> 9 10 <script type="text/javascript"> 11 function GetData() { 12 return "123456789"; 13 } 14 </script>
6.5 VS2017按F5部署
6.6 点击按钮"获取前端JS返回的数据"
七、WebAPP使用疑问
7.1 细心的人可能注意到:前端代码彻底能够本身处理完业务,那还有WebApp什么事情呢?这时的APP彻底就跟一个浏览器差很少!
7.2 确实是这样的WebApp相对与其余安卓APP来讲,是轻量级的,只是一个壳子,可是他也是有其合适的使用范围;
好比:若是前端并无数据持久化功能(如纯JS前端),这时要保存数据只能调用其余的WebApi,而因为JS的特性可能会引发一些安全问题。
或者根本没有第三方API,数据须要保存在手机端,JS也没有这种权限。
因此既兼顾了像升级Web站点那样简便,又有一些手机端的操做权限,WebApp应运而生。
分布式事务之消息补偿解决方案
1、数据库本地事务
先看看数据库事务的定义:单个逻辑工做单元执行的一系列操做,要么彻底地执行,要么彻底地不执行
这个比较容易理解,操做过数据库的通常都懂,既是业务需求涉及到多个数据表操做的时候,须要用到事务
要么一块儿更新,要么一块儿不更新,不会出现只更新了部分数据表的状况,下边看看数据库事务的使用
1 begin tran 2 begin try 3 update Table1 set Field = 1 where ID = 1 4 update Table2 set Field = 2 where ID = 1 5 end try 6 begin catch 7 rollback tran 8 end catch 9 commit tran
上实例在小型项目中通常是问题不大的,由于小型项目通常是单机系统,数据库、Web服务大都在一台服务器上,甚至可能只有一个数据库文件,
这种状况下使用本地事务没有一点问题;
可是本地事务有很大的缺陷,由于开启事务通常是锁表的,事务执行期间会一直锁着,其余的操做通常都要排队等待,对性能要求比较高的系统是不能忍受的。
特别是涉及改动不一样数据库的操做,这会形成跨库事务,性能更加低
若是还涉及到不在同一台服务器、甚至不一样网段部署的数据库,那本地事务简直是系统运行的灾难,是首先须要丢弃的解决方案。
那若是遇到上述状况,该怎么作呢,这就涉及到分布式事务了
2、分段式事务的补偿机制
若是有海量数据须要处理、或者要求高并发请求的话,同步的事务机制已是不现实的了,这种状况下必须采用异步事务机制,既分段式的事务
分段式事务通常作法就是把需求任务分段式地完成,经过事务补偿机制来保证业务最终执行成功,补偿机制通常能够归类为2种:
1 )定时任务补偿:
经过定时任务去跟进后续任务,根据不一样的状态表肯定下一步的操做,从而保证业务最终执行成功,
这种办法可能会涉及到不少的后台服务,维护起来也会比较麻烦,这是应该是早期比较流行的作法
2) 消息补偿:
经过消息中间件触发下一段任务,既经过实时消息通知下一段任务开始执行,执行完毕后的消息回发通知来保证业务最终完成;
固然这也是异步进行的,可是能保证数据最终的完整性、一致性,也是近几年比较热门的作法
定时任务补偿就不说了,这篇文章咱们来讨论一下经过消息补偿来完成分布式事务的通常作法
3、分布式事务之消息补偿
0)咱们以简单的产品下单场景来讲明,(不要较真哈)
1)先来看看分布式异步事务处理流程示意图,APP1与APP2须要互相订阅对方消息
2)首先看数据库,2个,一个库存库,一个已下单成功的库
1 -- 下单通知,主要做用保留已下单操做,消息发送失败能够根据此表从新发送 2 CREATE TABLE [dbo].[ProductMessage]( 3 [ID] [int] IDENTITY(1,1) NOT NULL, 4 [Product] [varchar](50) NULL, 5 [Amount] [int] NULL, 6 [UpdateTime] [datetime] NULL 7 ) 8 -- 库存 9 CREATE TABLE [dbo].[ProductStock]( 10 [ID] [int] IDENTITY(1,1) NOT NULL, 11 [Product] [varchar](50) NULL, 12 [Amount] [int] NULL 13 ) 14 -- 下单成功 15 CREATE TABLE [dbo].[ProductSell]( 16 [ID] [int] IDENTITY(1,1) NOT NULL, 17 [Product] [varchar](50) NULL, 18 [Customer] [int] NULL, 19 [Amount] [int] NULL 20 ) 21 -- 下单成功消息,主要做用防止重复消费 22 CREATE TABLE [dbo].[ProductMessageApply]( 23 [ID] [int] IDENTITY(1,1) NOT NULL, 24 [MesageID] [int] NULL, 25 [CreateTime] [datetime] NULL 26 )
3)项目架构Demo
数据底层访问使用的是Dapper、使用redis做为消息中间件
4)实体层代码
1 public class ProductMessage 2 { 3 [Key] 4 [IgnoreProperty(true)] 5 public int ID { get; set; } 6 public string Product { get; set; } 7 public int Amount { get; set; } 8 public DateTime UpdateTime { get; set; } 9 } 10 public class ProductMessageApply 11 { 12 [Key] 13 [IgnoreProperty(true)] 14 public int ID { get; set; } 15 public int MesageID { get; set; } 16 public DateTime CreateTime { get; set; } 17 } 18 public class ProductSell 19 { 20 [Key] 21 [IgnoreProperty(true)] 22 public int ID { get; set; } 23 public string Product { get; set; } 24 public int Customer { get; set; } 25 public int Amount { get; set; } 26 } 27 public class ProductStock 28 { 29 [Key] 30 [IgnoreProperty(true)] 31 public int ID { get; set; } 32 public string Product { get; set; } 33 public int Amount { get; set; } 34 }
5)服务接口层代码
1 public interface IProductMessageApplyService 2 { 3 void Add(ProductMessageApply entity); 4 ProductMessageApply Get(int id); 5 } 6 public interface IProductMessageService 7 { 8 void Add(ProductMessage entity); 9 IEnumerable<ProductMessage> Gets(object paramPairs = null); 10 void Delete(int id); 11 } 12 public interface IProductSellService 13 { 14 void Add(ProductSell entity); 15 } 16 public interface IProductStockService 17 { 18 void ReduceReserve(int id, int amount); 19 }
6)库存、消息通知
1 public class ProductMessageService : IProductMessageService 2 { 3 private IRepository<ProductMessage> repository; 4 5 public ProductMessageService(IRepository<ProductMessage> repository) 6 { 7 this.repository = repository; 8 } 9 10 public void Add(ProductMessage entity) 11 { 12 this.repository.Add(entity); 13 } 14 15 public IEnumerable<ProductMessage> Gets(object paramPairs = null) 16 { 17 return this.repository.Gets(paramPairs); 18 } 19 20 public void Delete(int id) 21 { 22 this.repository.Delete(id); 23 } 24 } 25 26 public class ProductStockService : IProductStockService 27 { 28 private IRepository<ProductStock> repository; 29 30 public ProductStockService(IRepository<ProductStock> repository) 31 { 32 this.repository = repository; 33 } 34 35 public void ReduceReserve(int id, int amount) 36 { 37 var entity = this.repository.Get(id); 38 if (entity == null) return; 39 40 entity.Amount = entity.Amount - amount; 41 this.repository.Update(entity); 42 } 43 }
7)下单、下单成功消息
1 public class ProductMessageApplyService : IProductMessageApplyService 2 { 3 private IRepository<ProductMessageApply> repository; 4 5 public ProductMessageApplyService(IRepository<ProductMessageApply> repository) 6 { 7 this.repository = repository; 8 } 9 10 public void Add(ProductMessageApply entity) 11 { 12 this.repository.Add(entity); 13 } 14 15 public ProductMessageApply Get(int id) 16 { 17 return this.repository.Get(id); 18 } 19 } 20 21 public class ProductSellService : IProductSellService 22 { 23 private IRepository<ProductSell> repository; 24 25 public ProductSellService(IRepository<ProductSell> repository) 26 { 27 this.repository = repository; 28 } 29 30 public void Add(ProductSell entity) 31 { 32 this.repository.Add(entity); 33 } 34 }
8)下单减库存测试
1 namespace Demo.Reserve.App 2 { 3 class Program 4 { 5 static void Main(string[] args) 6 { 7 Console.WriteLine(string.Format("{0} 程序已启动", DateTime.Now.ToString())); 8 9 Send(); 10 Subscribe(); 11 12 Console.ReadKey(); 13 } 14 15 private static void Send() 16 { 17 var unitOfWork = new UnitOfWork(Enums.Reserve); 18 19 try 20 { 21 var productStockRepository = new BaseRepository<ProductStock>(unitOfWork); 22 var productStockServic = new ProductStockService(productStockRepository); 23 var productMessageRepository = new BaseRepository<ProductMessage>(unitOfWork); 24 var productMessageService = new ProductMessageService(productMessageRepository); 25 26 var id = 1; 27 var amount = 2; 28 var productMessage = new ProductMessage() 29 { 30 Product = "ProductCode", 31 Amount = amount, 32 UpdateTime = DateTime.Now 33 }; 34 35 productStockServic.ReduceReserve(id, amount); 36 productMessageService.Add(productMessage); 37 unitOfWork.Commit(); 38 Console.WriteLine(string.Format("{0} 减库存完成", DateTime.Now.ToString())); 39 Thread.Sleep(1000); 40 41 var message = JsonConvert.SerializeObject(productMessage); 42 RedisConfig.Instrace.Publish("channel.Send", message); 43 Console.WriteLine(string.Format("{0} 发送减库存消息: {1}", DateTime.Now.ToString(), message)); 44 } 45 catch (Exception ex) 46 { 47 //Logger.Error(ex); 48 unitOfWork.Rollback(); 49 } 50 } 51 52 private static void Subscribe() 53 { 54 var client = RedisConfig.Instrace.NewClient(); 55 var subscriber = client.GetSubscriber(); 56 57 subscriber.Subscribe("channel.Success", (chl, message) => 58 { 59 try 60 { 61 var unitOfWork = new UnitOfWork(Enums.Reserve); 62 var productMessageRepository = new BaseRepository<ProductMessage>(unitOfWork); 63 var productMessageService = new ProductMessageService(productMessageRepository); 64 65 var messageID = message.ToString().ToInt(); 66 if (messageID > 0) 67 { 68 productMessageService.Delete(messageID); 69 Console.WriteLine(string.Format("{0} 收到消费成功消息:{1}", DateTime.Now.ToString(), message)); 70 } 71 } 72 catch (Exception ex) 73 { 74 //Logger.Error(ex); 75 } 76 }); 77 } 78 } 79 }
9)下单成功及消息回发测试
1 namespace Demo.Sell.App 2 { 3 class Program 4 { 5 static void Main(string[] args) 6 { 7 Subscribe(); 8 9 Console.WriteLine(string.Format("{0} 程序已启动", DateTime.Now.ToString())); 10 Console.ReadKey(); 11 } 12 13 private static void Subscribe() 14 { 15 var client = RedisConfig.Instrace.NewClient(); 16 var subscriber = client.GetSubscriber(); 17 18 subscriber.Subscribe("channel.Send", (chl, message) => 19 { 20 Consume(message); 21 }); 22 } 23 24 private static void Consume(string message) 25 { 26 var unitOfWork = new UnitOfWork(Enums.Sell); 27 28 try 29 { 30 Console.WriteLine(string.Format("{0} 收到减库存消息: {1}", DateTime.Now.ToString(), message)); 31 32 var productMessage = JsonConvert.DeserializeObject<ProductMessage>(message); 33 34 var productSellRepository = new BaseRepository<ProductSell>(unitOfWork); 35 var productSellService = new ProductSellService(productSellRepository); 36 37 var productMessageApplyRepository = new BaseRepository<ProductMessageApply>(unitOfWork); 38 var productMessageApplyService = new ProductMessageApplyService(productMessageApplyRepository); 39 40 var noExists = productMessageApplyService.Get(productMessage.ID) == null; 41 if (noExists) 42 { 43 productSellService.Add(new ProductSell() 44 { 45 Product = productMessage.Product, 46 Amount = productMessage.Amount, 47 Customer = 123 48 }); 49 50 productMessageApplyService.Add(new ProductMessageApply() 51 { 52 MesageID = productMessage.ID, 53 CreateTime = DateTime.Now 54 }); 55 56 unitOfWork.Commit(); 57 Console.WriteLine(string.Format("{0} 消息消费完成", DateTime.Now.ToString())); 58 Thread.Sleep(1000); 59 } 60 61 RedisConfig.Instrace.Publish("channel.Success", productMessage.ID.ToString()); 62 Console.WriteLine(string.Format("{0} 发送消费完成通知:{1}", DateTime.Now.ToString(), productMessage.ID.ToString())); 63 } 64 catch (Exception ex) 65 { 66 //Logger.Error(ex); 67 unitOfWork.Rollback(); 68 } 69 } 70 } 71 }
10)好了,到了最后检验成果的时候了
先打开Demo.Sell.App.exe、而后打开Demo.Reserve.App.exe
大功告成!