2023年4月

作者:京东零售 于泷

一、背景

在高并发场景中,为防止大量请求直接访问数据库,缓解数据库压力,常用的方式一般会增加缓存层起到缓冲作用,减少数据库压力。引入缓存,就会涉及到缓存与数据库中数据如何保持一致性问题,本文将对几种缓存与数据库保证数据一致性的使用方式进行分析。
为保证高并发性能,以下分析场景不考虑执行的原子性及加锁等强一致性要求的场景,仅追求最终一致性。

二、读取过程

• 读缓存

• 如果缓存里没有值,那就读取数据库的值

• 同时把这个值写进缓存中

三、更新过程

更新操作有多种策略,各有优劣,主要针对此场景进行分析

策略1:先更新db,再删除缓存(常用的Cache-Aside Pattern旁路缓存)

问题:

1.如果更新db成功,删缓存失败,将导致数据不一致

2.极端场景,请求A读,B写

1)此时缓存刚好失效 2)A查库得到旧值 3)B更新DB成功

4)B删除缓存 5)A将查到的旧值更新到缓存中

此场景的发生需要步骤2)查db 始终慢于 3)的更新db,才能导致4)先于5)执行,通常db的查询是要快于写入的,所以此极端场景的产生过于严格,不易发生

策略2:先更新db,再更新缓存

问题:

1.并发更新场景下,更新缓存会导致数据不一致

2.根据读写比,考虑是否有必要频繁同步更新缓存,而且,如果构造缓存中数据过于复杂,或者数据更新频繁,但是读取并不频繁的情况,还会造成不必要的性能损耗

此种方式不推荐

策略3:先更新缓存,再更新db

同上,不推荐

策略4:先删缓存,再更新db

先删缓存,虽然解决了策略1中,后删缓存如果失败的场景,但也会发生不一致的问题

例如:请求 A 删除缓存,这时请求B来查,就会击穿到数据库,B读取到旧的值后写入缓存,A正常更新db,由于时间差导致数据不一致的情况

策略5:缓存延时双删

该策略兼容了策略1和策略4,解决了先删缓存还是后删缓存的问题,如策略1中,更新db后删缓存失败和策略4中的不一致场景,该策略可以将延时时间内(比如延时10ms)所造成的缓存脏数据,再次删除。但是,如果延时删缓存失败,策略4中不一致问题还会发生,同时延时的实现,如创建线程,或者引入mq异步,可能会增加系统复杂度问题。

策略6:变种双删,前置缓存过期时间

该策略针对策略1中后删缓存失败的场景,前置一层缓存数据过期时间(具体时间根据自身系统本身评估,如可覆盖db读写耗时或一致性容忍度等),更新db后就算删缓存失败,在expire时间后也能保证缓存中无数据。同时,前置expire失败,或者更新db失败,都不会影响数据一致。

能够解决策略4中的问题:请求 A 删除缓存,这时请求B来查,就会击穿到数据库,B读取到旧的值后写入缓存,A正常更新db,由于时间差导致数据不一致的情况,描述图如下:

本策略中步骤1为expire缓存,不会发生击穿缓存到数据库的情况,数据将直接返回。除非更极端情况,如下图:

expire时间没有覆盖住更新db的耗时,类似策略1中极端场景,此处不赘述

四、总结

对于每种方案策略,各有利弊,但一致性问题始终存在(文章开头排除了原子性和锁),只是发生的几率在一点点慢慢变小了,方案的评估不仅要根据自身系统的业务场景,如读写比、并发量、一致性容忍度,还要考虑系统复杂度,投入产出比等,寻找最合适的方案。

简介

flutter中有默认的Route组件,叫做MaterialPageRoute,一般情况下我们在flutter中进行跳转的话,只需要向Navigator中传入一个MaterialPageRoute就可以了。

但是MaterialPageRoute太普通了,如果我们想要做点不同的跳转特效应该如何处理呢?

一起来看看吧。

自定义跳转使用

正常情况下,我们进行路由跳转需要用到Navigator和MaterialPageRoute,如下所示:

 Navigator.push(context, MaterialPageRoute(builder: (context) {
            return const NextPage();

如果要实现特定的路由动画,那么需要进行路由的自定义。

在flutter中也就是要使用PageRouteBuilder来自定义一个Route。

先来看下PageRouteBuilder的定义:

class PageRouteBuilder<T> extends PageRoute<T> {

  PageRouteBuilder({
    super.settings,
    required this.pageBuilder,
    this.transitionsBuilder = _defaultTransitionsBuilder,
    this.transitionDuration = const Duration(milliseconds: 300),
    this.reverseTransitionDuration = const Duration(milliseconds: 300),
    this.opaque = true,
    this.barrierDismissible = false,
    this.barrierColor,
    this.barrierLabel,
    this.maintainState = true,
    super.fullscreenDialog,
  })

PageRouteBuilder也是PageRoute的一种,在构建PageRouteBuilder的时候,通过控制不同的属性值,我们可以自由控制pageBuilder,transitionsBuilder,transitionDuration,reverseTransitionDuration等特性。

可以看到自由程度还是非常高的。

其中pageBuilder是路由将会跳转的页面,这个是必须要指定的,要不然路由也就没有意义了。

另外路由转换的效果可以经由transitionsBuilder来设置。

这里的RouteTransitionsBuilder是一个Function,返回一个Widget:

typedef RouteTransitionsBuilder = Widget Function(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child);

所以理论上,我们可以返回任何widget,但是一般来说,我们会返回一个AnimatedWidget,表示一个动画效果。

flutter动画基础

flutter中有个专门的动画包叫做flutter/animation.dart, flutter中所有动画的核心叫做Animation。

Animation中定义了很多listener用来监控动画的变动情况,并且还提供了一个AnimationStatus来存储当前的动画状态:

abstract class Animation<T> extends Listenable implements ValueListenable<T> {
  const Animation();

  AnimationWithParentMixin<T>

  @override
  void addListener(VoidCallback listener);

  @override
  void removeListener(VoidCallback listener);

  void addStatusListener(AnimationStatusListener listener);

  void removeStatusListener(AnimationStatusListener listener);

  AnimationStatus get status;

AnimationStatus是一个枚举类,它包含了现在动画的各种状态:

enum AnimationStatus {
  dismissed,

  forward,

  reverse,

  completed,
}

dismissed表示动画暂停在开头。

forward表示动画在从头到尾播放。

reverse表示动画在从尾到头播放。

completed表示动画播放完毕,停在了结尾。

有了动画的表示之后,如何对动画进行控制呢?这里就需要用到AnimationController了。

AnimationController可以控制动画的duration,动画的最低值lowerBound默认是0.0,动画的最高值upperBound默认是1.0等等。

默认情况AnimationController中从最低值到最高值是线性变化的,如果你想设置不同的Bound值,那么可以尝试自定义 Animatable, 如果你想动画的变动是非线性的,那么可以尝试继承Animation来实现自己的变动曲线。

实现一个自定义的route

这里我们使用flutter中的SlideTransition,SlideTransition是一个AnimatedWidget,它表示的是一个组件的位置变化的动画。

class SlideTransition extends AnimatedWidget {
  const SlideTransition({
    super.key,
    required Animation<Offset> position,
    this.transformHitTests = true,
    this.textDirection,
    this.child,
  }) : assert(position != null),
       super(listenable: position);

看下它的构造函数,可以看到SlideTransition需要一个position的属性,这个position是一个Animation对象,里面包含的是Offset。

同时这个position是一个listenable对象,通过监听里面Offset的变化,从而重新build对应的widget从而实现动画的效果。

Offset是一个表示位置的类,(0,0) 表示这个widget的左顶点在屏幕的左上角,同样的(1,1)表示这个widget的左顶点在屏幕的右下角。

因为route过后是一个新的页面,我们希望出现一个页面从右下角移动到左上角的动画,那么我们可以这样做:

Route customRoute() {
  return PageRouteBuilder(
    pageBuilder: (context, animation, secondaryAnimation) => const SecondPage(),
    transitionsBuilder: (context, animation, secondaryAnimation, child) {
      const begin = Offset(1.0, 1.0);
      const end = Offset.zero;
      const curve = Curves.easeOut;

      var tween = Tween(begin: begin, end: end).chain(CurveTween(curve: curve));

      return SlideTransition(
        position: animation.drive(tween),
        child: child,
      );
    },
  );
}

这里的begin和end表示widget从屏幕的右下角移动到了屏幕的左上角。

Tween表示的是开始值和结束值之间的线性插值,是一个动态过程,另外我们还可以这个插值变动的曲线,这里使用了CurveTween,选中了Curves.easeOut这种曲线类型。

最后调用animation.drive方法把Tween和Animation关联起来,这样一个路由动画就完成了。

总结

最后程序运行的结果如下:

其实flutter中的动画很简单,大家记住就是widget位置沿不同的曲线变化即可。

本文的例子:
https://github.com/ddean2009/learn-flutter.git

今天带大家将本地.Net6.0项目部署到Linux系统中,其中有用到Nginx反向代理和PM2进程管理工具,希望本偏文章能对你有所帮助,成为你成功路上的垫脚石!

背景:

在.Net 5.0横空出世之后,.Net已经支持夸平台了,身为一名合格的码农,你敢说你不会用Linux? 哈哈哈开个玩笑,因为工作最近接手的项目是部署到Linux中,需要使用到Linux , Nginx和PM2 的一些操作,我就开始倒腾了一两天试试能不能把自己的项目也整一套放到Linux中,Linux 在日常工作中可以说是非常普遍,也是主流,看一百遍不如做一遍,所以希望还没有接触到的童鞋,看完这篇文章后也能去试着部署一下,一起进步!!! 由于本人也是第一次部署,本文如有不足之处还请各位大佬及时指出~

1.打包代码,并存放到Linux中

切记!,在Program中将这三段代码,从if中拿出来!,这个的if的意思是只有开发环境才会执行,如没有拿出来,即便发布成功也无法访问Swagger UI,
本人就被这个小问题折磨了有一上午!!!

在appsettings.json文件中指定访问端口,端口可以随便填,不重复的即可

随后右击API层选择发布

最后将打包好的代码文件发布到Linux 中即可,这里我使用的文件传输工具是 FileZilla

2.Linux中安装.Net 6.0运行环境

安装.net环境运行时可以直接看微软官方无脑复制即可:
在 .NET CentOS 上安装 - .NET | Microsoft Learn

最后配置完成之后我们CD 到打包好代码的目录中 开启Api服务

//开启服务  WeTool.Api.dll换成启动项目的dll名称即可
dotnet WeTool.Api.dll//关闭服务 ctrl+c 即可

像这样,恭喜你已经成功啦!!!

当然不要高兴的太早啦,到这里你只成功了一半!!!,现在我们面临两个问题:

1.我们虽然可以使用 ip?swagger/index.html访问,但如果我这个ip很多个地方需要用到怎么办,当然是用域名啦,所以我们需要用到nginx 将我们的服务指向我们的域名

2.我们每次开启或者关闭服务都需要进入到发布的目录中去开一下 还不能看到服务的状态,一个服务还好,如果有很多个呢,所以这里我们需要用到进程管理工具PM2

3.安装并配置Nginx

1 //一键安装上面四个依赖
2 yum -y install gcc zlib zlib-devel pcre-devel openssl openssl-devel8 //下载tar包
9 wget http://nginx.org/download/nginx-1.13.7.tar.gz
10 tar -xvf nginx-1.13.7.tar.g

我们进入到/etc/nginx/conf.d目录下,创建.conf文件,随后编辑文件,指定域名即可

8880换成.NET 中appsettings.json文件中urls指向端口

1 server {2     listen        80;3 server_name   域名;4     location /{5         proxy_pass   http://127.0.0.1:8880;
6         proxy_http_version 1.1;7 proxy_set_header   Upgrade $http_upgrade;8         proxy_set_header   Connection keep-alive;9 proxy_set_header   Host $host;10 proxy_cache_bypass $http_upgrade;11         proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;12         proxy_set_header   X-Forwarded-Proto $scheme;13 }14 }

配置好之后重启下Nginx即可 命令:  systemctl restart nginx

好了,我们终于到了最后一步了!!!配置PM2 进程管理工具

4.安装配置PM2进程管理工具

1 #安装nodejs2 yum install nodejs3 #若遇到No package alien available.Error: Nothing to do,可先执行以下命令再安装nodejs4 yum install epel-release5 #查看npm版本6 npm -v7 
8 #1、安装pm2(npm版本太低可能会导致安装失败,升级步骤见后文)9 npm install -g pm210 #查看pm2的安装路径11 sudo whereis pm212 #查看版本13  pm2 -v14  
15 #2、更新PM216 #首先确保您正确保存了所有进程:17 sudo pm2 save18 #然后从NPM安装最新的PM2版本:19 sudo npm install pm2 -g20 #最后更新内存中的PM2进程:21 sudo pm2 update22 
23 
24 #启动进程(需要进入该项目所在目录下)25 pm2 start 进程名26 #启动成功后可进行访问,若不能访问检查一下端口的下行规则是否开放27 
28 #查看进程29 pm2 list30 #停止、删除进程31 pm2 stop/delete 进程名32 #查看日志33 pm2 logs

以我现在这个项目为例创建一个进程: pm2 start 'webtool.Api.dll' --name webtoolApi  记住需要进入到进程目录哦,就是代码打包目录,创建好之后我们输入pm2 list 看看吧~

现在我们的进程是开着的Swagger UI 是可以访问的

当我们输入 pm2 stop 0(进程id)关闭之后,Swagger UI将无法访问,如下:

结尾:

好啦,今天的教程就到这儿啦,希望对各位看官有所帮助,还是那句话,希望各位能直接上手实操一遍,因为看着可能不难,但真正实操时会遇到很多的问题和困难,就像本篇文章哈哈哈重装3次服务器,一个小问题卡一天,不过真正弄出来之后还是很开心的!!!

目录

前景提示

  • 想在windows10上安装一个docker容器的desktop版本,但是,总是安装wsl不好使,同时,windows store没有了,用命令行安装的linux系统无法启动,也无法连接,之前想到的方案是安装旧的版本,然后,快速关闭wsl的验证功能,勉强可以跑起来,但是,存在大量问题和功能无法使用。

  • 最终一跺脚,一狠心决定重装操作系统,将系统恢复到了出厂状态,然后,又在系统更新了选择了更新,遇到最坑的问题就是window安全中心在实时运行,旧的版本的软件不让运行,有破解的软件的破解脚本全部被认证为病毒和垃圾文件,最后,找到了Microsoft Defender Antivirus Service这个服务,开机就会启动,启动就会一直检测,很耗费cpu资源和内存,找到其路径。

本来的目标是改变其文件的名称,然后,在任务管理器里关闭,或者关机重启干掉它,但是,它不让你改文件的权限。

最后下载火绒,将MsMpEng.exe这个改不了,关不掉的程序暴力粉碎了,才彻底关闭了这个安全服务。

记录下经常玩系统,经常给系统整崩溃的操作,这个华为的操作系统很多绑定的,类似于在后台一直扫描定时操作,一旦关闭不当就会cpu执行100%,然后要么关机,要么重做系统,这也是以前没遇到的操作系统,(华为Matebook D 14)系统装的都是家庭版,但是,开发很多需要专业或者旗舰企业版才有的功能,因此,只能魔改升级。

windows 10 内置的linux系统

1.这个内置系统一定要在windows store里安装,否则,
无法使用,这是重点
。进入商店,搜索linux。

2.一般画圈这些都可以使用。

3.这里选择的最新版安装。

4.安装会让你输入微软账户密码(首次)。

5.静静等待,本作的这个大概550M左右。

6.装好后,会生成一个图标(像应用程序一样,双击打开).

7.需要开启 设置--> 应用程序 --> 相关设置(程序与功能),点击启动与关闭windows功能,把这个对勾点上.

8.启动Unbuntu的linux.(首次进入,需要等待一下,然后,创建账户名和密码,这个版本中不给用户超级管理员权限,必须自己新建一个账户,否则无法使用.)

9.第三方工具连接本机的linux系统.(这里使用MobaXterm)

  • 这个会自动连接自带的系统,如何手动.(点击session)

  • 点击wsl(说明其内部也是wsl文件连接的)

  • 选择下拉框,选择Unbuntu.


  • 选择ok,就可以了.

  • 成功界面.

WSl 安装遇到的问题。

1.打开管理员权限的powershell,快捷键win + x

2.输入查看版本命令。

wsl -l -v

3.修改命令很多,有用的只有这个命令

wsl --set-default Ubuntu-22.04 2

  • wsl --set-default 后边接的是你下载的本地的linux的版本号,最后的2是wsl的版本。

  • 这个命令有时候不好使,会出现如下的内容,或者没什么反应就是没成功.(下图两种情况都是失败了)


  • 这里选择旧版本安装。代开后向下找到setp 4,点击如图二位置的下载,下载后就下一步下一步安装。


4、重新执行步骤三的命令,安装后再次再命令行执行命令这个没看到相关文章介绍,官网也没说,官网让执行命令:
wsl --set-default-version 2
,这个命令本作者的机器一直无效,然后,输入完命令正常会卡顿一会下载,耐心等待,卡顿结束就是安装好了。

5、这个命令就没好使过,不知道其他人如何,反正作者这里没有好使过。

wsl --set-default-version 2

最新版的DockerDeskTop安装

1. 官网搜索或直接访问 :
Docker: Accelerated, Containerized Application Development
(
https://www.docker.com/
)

2.选则系统,这里是windows.

3.浏览器下载会在下载位置弹出下载选择,选择下载就好了.


4.开启系统的虚拟服务,还是设置--> 应用程序 --> 相关设置(程序与功能),点击启动与关闭windows功能,把这个对勾点上.




5.安装位置转移

  • docker会默认全部安装在C盘,会不断占用操作系统的资源,所以这里采用创建快捷键绑定的方式,让其转移到其他盘符。

  • 打开管理员权限下的命令cmd,输入如下的命令。


# 修改实际安装目录
mklink /j "C:\Program Files\Docker" "D:\Program Files\Docker"


# 修改相关文件安装目录
mklink /j "C:\Users\你的用户名\AppData\Local\Docker" "D:\Program Files\Docker\Local"

mklink /j "C:\Users\你的用户名\AppData\Roaming\Docker" "D:\Program Files\Docker\Roaming\Docker"

mklink /j "C:\Users\你的用户名\AppData\Roaming\Docker Desktop" "D:\Program Files\Docker\Roaming\Docker Desktop"


问题

​ 1.“当文件已存在,无法创建”:保证相同路径下没有同名文件夹(c盘那些路径里)

​ 2.”找不到指定路径“ :说明前后输入的路径有错误,仔细检查下。

6.找到下好的位置后,双击其.exe

7.进入安装界面,安装成功后不要点击右上角的×,要点中间的stop和restart(自动关机重启,记得保存好当前操作的其他事情再点击).装好后
必须关机重启
,否则点击也无法启动.


8.关机重启后,再桌面找到如图的图标,双击启动

9.进入同意界面和一个问答界面,点击就可以了.



10、稍微卡顿一下就进入主页了,看到主页,点击下其他选项,无误就是安装成功了。(需要登录和注册账户)

11.相关设置。

  • 点击进入设置

  • 常规设置(还可以自行设置界面是黑色的)

  • 资源设置。



  • 拉去镜像设置。


完整文档。

{
  "builder": {
    "gc": {
      "defaultKeepStorage": "20GB",
      "enabled": true
    }
  },
  "experimental": false,
  "features": {
    "buildkit": true
  },
  ## 这里是主要添加的
  "registry-mirrors": [
    "http://mirrors.aliyun.com",
    "https://mirrors.tuna.tsinghua.edu.cn",
     "https://1nj0zren.mirror.aliyuncs.com",
    "https://docker.mirrors.ustc.edu.cn",
    "http://f1361db2.m.daocloud.io",
    "https://registry.docker-cn.com"
  ]
}
  • 关闭自动更新。


彩蛋

  • Windows 10 的可视化。

  • 可以任意拖拽有没有很开心。

后续

  • docker安装很多问题都是操作系统本身问题,所以要做好在源头解决问题,不是把问题一步步拖到最后,越到最后,问题难度越会升级,作为开发,最好的解决问题就是源头解决问题。

先决条件:

  1. 更换DNS
  2. 更换apt的镜像源

Ubuntu下永久性修改DNS

vi /etc/systemd/resolved.conf

DNS字段取消注释,并修改DNS为223.5.5.5
223.5.5.5是一个IP地址,是Alibaba Cloud提供的免费DNS服务器的IP地址。
修改后保存。

systemctl restart systemd-resolved

清华镜像源

https://mirrors.tuna.tsinghua.edu.cn/help/ubuntu/
建议使用http的sources,再下载

sudo apt install apt-transport-https
sudo apt install ca-certificates

再更改回https的sources

部署Kubesphere

操作文档

以集群方式部署k8s和kubesphere。我们采用的工具是KubeKey。
操作文档(含环境要求):
https://kubesphere.io/zh/docs/v3.3/installing-on-linux/introduction/multioverview/

注意事项

  1. k8s建议版本选择v1.21.5,k8sp选择 v3.3.0,否则可能出现安装过程中无法下载镜像的情况。
  2. 建议先不启用插件。等安装完毕以后再启用可插拔插件,否则容易下载失败、下载速度慢。

更改Docker镜像源

KubeKey 会默认安装最新版本的 Docker,所以安装好kubesphere的时候,Docker也安装好了。

cd /etc/docker
vim daemon.json

更改
registry-mirrors
字段

 "registry-mirrors" : [
    "https://registry.docker-cn.com",
    "http://hub-mirror.c.163.com",
    "https://docker.mirrors.ustc.edu.cn",
    "https://mirror.ccs.tencentyun.com"
  ]

更改好以后重启docker。

systemctl daemon-reload
systemctl restart docker.service