arunfung

arunfung 查看完整档案

上海编辑金华职业技术学院  |  计算机应用 编辑上海慧贤网络科技有限公司  |  项目负责人 主程 编辑 arunfung.com 编辑
编辑

努力做最好的自己

个人动态

arunfung 关注了用户 · 9月18日

Doto丶 @doto_5cf7722c57196

不知名后端工程师,喜欢code,喜欢学习,喜欢记录.

个人公众号[呼延十], 博客地址:http://huyan.couplecoders.tech/

关注 2

arunfung 关注了专栏 · 7月1日

Corwien

为者常成,行者常至!

关注 959

arunfung 收藏了文章 · 6月29日

PHP程序员如何转Java开发?

作为一名有四年开发经验的PHP程序员,对Java强大的生态无比艳羡,尤其在开发一些比较大型的项目时,Java强大成熟的生态可以帮助我们快速完成开发,这是PHP比不了的,而且,最最重要的一点是,Java的薪资普遍比PHP的要高一大截,大厂对高级Java的需求量很大,所以,为了牛奶和面包,我们程序员不要自我设限,多学一门语言,多条路。

从哪里开始?

相信很多准备学习java的同学们一开始都抱着极大的热情,开始买各种java入门书,网上找各种入门的资料,可是对于新手而言,光搞Java的开发环境和IDE配置就很容易晕,更重要的是学习了Java 的基础知识后,不知道后边该学什么,顺序是什么,该学那些框架,有些框架现在已经淘汰不用了,比如 Hibernate,Struts,所以,有一个全面、完整的学习规划是很重要的。

学习路线

最好的学习方法就是由一个完整的实战项目驱动,哪个知识点不会,就补哪个,这样不至于学了很多理论上的东西而产生迷惘。比如我的目标是学习Java web开发,能够搞一个电商网站,目前最流行的就是 SSM 框架,那么,我都要学习那些知识呢?

按顺序学习清单:
1 .JAVA 基础 --- HelloWorld
2 .JAVA 基础 --- 面向对象
3 .JAVA 基础 --- 变量
4 .JAVA 基础 --- 操作符
5 .JAVA 基础 --- 控制流程
6 .JAVA 基础 --- 数组
7 .JAVA 基础 --- 类和对象
8 .JAVA 基础 --- 接口与继承
9 .JAVA 基础 --- 数字与字符串
10 .JAVA 基础 --- 日期
11 .JAVA 中级 --- 异常处理
12 .JAVA 中级 --- I/O
13 .JAVA 中级 --- 集合框架
14 .JAVA 中级 --- 泛型
15 .JAVA 中级 --- JDBC
16 .前端部分 --- HTML
17 .前端部分 --- CSS
18 .前端部分 --- JavaScript
19 .前端部分 --- JSON
20 .前端部分 --- Ajax
21 .前端部分 --- JQuery
22 .前端部分 --- BootStrap
23 .J2EE --- Tomcat
24 .J2EE --- Servlet
25 .J2EE --- HTTP协议
26 .J2EE --- JSP
27 .J2EE --- MVC
28 .J2EE --- Filter
29 .J2EE --- Listener
30 .JAVA 框架 --- Spring
31 .JAVA 框架 --- Spring MVC
32 .JAVA 框架 --- Mybatis
33 .JAVA 框架 --- Spring+Mybatis
34 .JAVA 框架 --- SSM
35 .工具和中间件 --- Git
36 .工具和中间件 --- Intellij IDEA
37 .工具和中间件 --- 部署到Linux
38 .实践项目 --- 天猫整站SSM

完整的路线图:

2DxxmyfpPR.png

注:上图截取于how2j.cn Java学习网站

怎么学习?

在学习之前,我们要对一些基本的概念做一些了解,否则在学习时很容易懵逼。

基础概念

JavaSE: 即J2SE, java标准版, 主要做一般的java应用, 比如, 应用软件/ QQ之类的通信软件等等。
JavaEE: 即J2EE, 主要做企业应用, 比如公司网站, 企业解决方案等。
JavaME: 即J2ME, 主要面向嵌入式等设备应用的开发, 比如手机游戏等。

Javase基础

Javase作为Java的基础尤为的重要,以后你的框架是否可以学懂,完全要看对于Javase的理解,有很多人做了一两年的Java开发,但是对于Javase的理解还是远远不够的,所以一个学习Java的新手,Javase将会成为你的起点。

JavaSE其中重点涵盖了环境搭建、基础语法、面向对象核心、异常、数组、常用类、集合、线程、IO流、反射机制、网络编程。

JavaEE基础

J2ee 基础包括 Servlet,JSP,Filter,Listener 等相关知识。

JavaEE框架
企业级开发,Struts2、Spring框架、Hibernate框架、Maven核心技术、MyBaits框架、SpringMVC、SpringBoot等。

image.png

image.png

注:上图截取于how2j.cn Java学习网站

开发工具

在刚开始学习之时,我们可以使用 Eclipse 进行开发,锻炼我们快速熟悉代码的能力,等学习一段时间之后可以使用IDEA进行开发。

拳不离手曲不离口,最重要的是要敲代码,这样才能将学习的内容消化。

学习资源

过年受疫情影响在家呆的这段时间,发现了一个非常好的Java学习网站,个人极力推荐,我就是看着这个网站学习完整Javaweb 的内容的。学Java,只看这个网站https://how2j.cn 就可以出师了,不用再到其他地方折腾了,站长已经为我们整理了整个路线图,并且有视频讲解和源代码下载,还有更详细的博文说明一步一步引导我们学习,整合的非常完善。

最强Java学习网站推荐:https://how2j.cn?p=126405

image.png

注:上图截取于how2j.cn Java学习网站

小结

骚年,作为2020年的年轻人,要有危机感,不能总吃老本,要多学习一些东西,多掌握一门工具,丰富自己,这样才不会被淘汰。

查看原文

arunfung 收藏了文章 · 5月17日

面试:原来Redis常用的五种数据类型底层结构是这样的

关注我,可以获取最新知识、经典面试题以及微服务技术分享

  在Redis中会涉及很多数据结构,比如SDS,双向链表、字典、压缩列表、整数集合等等。Redis会基于这些数据结构自定义一个对象系统,而且自定义的对象系统有很多好处。

通过对以下的Redis对象系统的学习,可以了解Redis设计原理以及初衷,为了我们在使用Redis的时候,更加能够理解到其原理和定位问题。

Redis 对象

Redis基于上述的数据结构自定义一个Object 系统,Object结构:

redisObject结构:
     typedef struct redisObject{
    //类型
    unsigned type:4;
    //编码
    unsigned encoding:4;
    //指向底层实现数据结构的指针
    void *ptr;
    ….. 
} 

Object 系统包含五种Object:

  • String:字符串对象
  • List:列表对象
  • Hash:哈希对象
  • Set:集合对象
  • ZSet:有序集合

Redis使用对象来表示数据库中的键和值,即每新建一个键值对,至少创建有两个对象,而且使用对象的具有以下好处:

1. redis可以在执行命令前会根据对象的类型判断一个对象是否可以执行给定的命令
2. 针对不同的使用场景,为对象设置不同的数据结构实现,从而优化对象的不同场景夏的使用效率
3. 对象系统还可以基于引用计数计数的内存回收机制,自动释放对象所占用的内存,或者还可以让多个数据库键共享同一个对象来节约内存。
4. redis对象带有访问时间记录信息,使用该信息可以进行优化空转时长较大的key,进行删除!



对象的ptr指针指向对象的底层现实数据结构,而这些数据结构由对象的encoding属性决定,对应关系:

编码常量编码对应的底层数据结构
REDIS_ENCODING_INTlong类型的整数
REDIS_ENCODING_EMBSTRembstr编码的简单动态字符串
REDIS_ENCODING_RAW简单动态字符串
REDIS_ENCODING_HT字典
REDIS_ENCODING_LINKEDLIST双向链表
REDIS_ENCODING_ZIPLIST压缩列表
REDIS_ENCODING_INTSET整数集合
REDIS_ENCODING_SKIPLIST跳跃表和字典



每种Object对象至少有两种不同的编码,对应关系:

类型编码对象
Stringint整数值实现
Stringembstrsds实现 <=39 字节
Stringrawsds实现 > 39字节
Listziplist压缩列表实现
Listlinkedlist双端链表实现
Setintset整数集合使用
Sethashtable字典实现
Hashziplist压缩列表实现
Hashhashtable字典使用
Sorted setziplist压缩列表实现
Sorted setskiplist跳跃表和字典


String 对象

字符串对象编码可以int 、raw或者embstr,如果保存的值为整数值且这个值可以用long类型表示,使用int编码,其他编码类似。

比如:int编码的String Object

redis> set number 520 
 ok
 redis> OBJECT ENCODING number 
"int"

String Object结构:

file

### String 对象之间的编码转换
int编码的字符串对象和embstr编码的字符串对象在条件满足的情况下,会被转换为raw编码的字符串对象。

比如:对int编码的字符串对象进行append命令时,就会使得原来是int变为raw编码字符串


List对象

list对象可以为ziplist或者为linkedlist,对应底层实现ziplist为压缩列表,linkedlist为双向列表。

Redis>RPUSH numbers “Ccww” 520 1

用ziplist编码的List对象结构:
file

用linkedlist编码的List对象结构:

file

List对象的编码转换:

当list对象可以同时满足以下两个条件时,list对象使用的是ziplist编码:

1. list对象保存的所有字符串元素的长度都小于64字节
2. list对象保存的元素数量小于512个,

不能满足这两个条件的list对象需要使用linkedlist编码。

Hash对象

Hash对象的编码可以是ziplist或者hashtable
其中,ziplist底层使用压缩列表实现:

  • 保存同一键值对的两个节点紧靠相邻,键key在前,值vaule在后
  • 先保存的键值对在压缩列表的表头方向,后来在表尾方向

hashtable底层使用字典实现,Hash对象种的每个键值对都使用一个字典键值对保存:

  • 字典的键为字符串对象,保存键key
  • 字典的值也为字符串对象,保存键值对的值

比如:HSET命令

redis>HSET author name  "Ccww"
(integer)

redis>HSET author age  18
(integer)

redis>HSET author sex  "male"
(integer)

ziplist的底层结构:

file

hashtable底层结构:

file

Hash对象的编码转换:

当list对象可以同时满足以下两个条件时,list对象使用的是ziplist编码:

1. list对象保存的所有字符串元素的长度都小于64字节
2. list对象保存的元素数量小于512个,

不能满足这两个条件的hash对象需要使用hashtable编码

Note:这两个条件的上限值是可以修改的,可查看配置文件hash-max-zaiplist-value和hash-max-ziplist-entries


Set对象:

Set对象的编码可以为intset或者hashtable

  • intset编码:使用整数集合作为底层实现,set对象包含的所有元素都被保存在intset整数集合里面
  • hashtable编码:使用字典作为底层实现,字典键key包含一个set元素,而字典的值则都为null

inset编码Set对象结构:

    redis> SAD number  1 3 5 
    

file

hashtable编码Set对象结构:

redis> SAD Dfruits  “apple”  "banana" " cherry"

file

Set对象的编码转换:

使用intset编码:

1. set对象保存的所有元素都是整数值
2. set对象保存的元素数量不超过512个

不能满足这两个条件的Set对象使用hashtable编码

ZSet对象

ZSet对象的编码 可以为ziplist或者skiplist
ziplist编码,每个集合元素使用相邻的两个压缩列表节点保存,一个保存元素成员,一个保存元素的分值,然后根据分数进行从小到大排序。

ziplist编码的ZSet对象结构:

Redis>ZADD price 8.5 apple 5.0 banana 6.0 cherry

file

skiplist编码的ZSet对象使用了zset结构,包含一个字典和一个跳跃表

Type struct zset{

    Zskiplist *zsl;
    dict *dict;
    ...
}

skiplist编码的ZSet对象结构

file

ZSet对象的编码转换

当ZSet对象同时满足以下两个条件时,对象使用ziplist编码

1. 有序集合保存的元素数量小于128个
2. 有序集合保存的所有元素的长度都小于64字节

不能满足以上两个条件的有序集合对象将使用skiplist编码。

Note:可以通过配置文件中zset-max-ziplist-entries和zset-max-ziplist-vaule

查看原文

arunfung 收藏了文章 · 5月11日

系统的讲解 - SSO单点登录

概念

SSO 英文全称 Single Sign On,单点登录。

在多个应用系统中,只需要登录一次,就可以访问其他相互信任的应用系统。

比如:淘宝网(www.taobao.com),天猫网(www.tmall.com),聚划算(ju.taobao.com),飞猪网(www.fliggy.com)等,这些都是阿里巴巴集团的网站。在这些网站中,我们在其中一个网站登录了,再访问其他的网站时,就无需再进行登录,这就是 SSO 的主要用途。

好处

用户角度

用户能够做到一次登录多次使用,无需记录多套用户名和密码,省心。

系统管理员角度

管理员只需维护好一个统一的账号中心就可以了,方便。

新系统开发角度

新系统开发时只需直接对接统一的账号中心即可,简化开发流程,省时。

技术实现

流程图

clipboard.png

流程介绍

如果没这个介绍,看上图肯定是懵懵的。

系统A和系统B都是前后端分离的,比如前端框架用的 React / Vue / Angular,都是通过 NPM 编译后独立部署的,前后端完全通过HTTP接口的方式进行交互,也有可能前后端项目的域名都不一样。

SSO认证中心不是前后端分离的,就是前端代码和后端代码部署在一个项目中。

为什么用这两种情况呢?

其实就是为了,在流程图上出现这两种情况,这样的清楚了,后期改成任何一种就都清楚了。

试想一下:

三个系统都是前后端分离的情况,流程图应该怎么调整?

三个系统都不是前后端分离的情况,流程图应该怎么调整?

对外接口

系统A和系统B:用户退出接口。

SSO 认证中心:用户退出接口和token验证接口。

登录

如上述流程图一致。

系统A和系统B:使用token认证登录。

SSO 认证中心:使用会话认证登录。

前后端分离项目,登录使用token进行解决,前端每次请求接口时都必须传递token参数。

退出

clipboard.png

上图,表示的是从某一个系统退出的流程图。

退出,还可以从SSO认证中心退出,然后调取各个系统的用户退出接口。

当用户再进行操作的时候,就会跳转到SSO的登录界面。

Token 生成方式

创建全局会话可以使用session,将session存储到redis中。

令牌的生成可以使用JWT。

PHP JWT参考地址:https://github.com/lcobucci/jwt

当然还可以自定义token的生成方式。

小结

讲解了什么是SSO,以及SSO的用途与好处,同时根据流程图一步步进行梳理,基本上就可以实现了。

期间遇到任何问题,都可以关注公众号和我进行交流。

扩展

SSO与OAuth的区别

谈到SSO很多人就想到OAuth,也有谈到OAuth想到SSO的,在这里我简单的说一下区别。

通俗的解释,SSO是处理一个公司内的不同应用系统之间的登录问题,比如阿里巴巴旗下有很多应用系统,我们只需要登录一个系统就可以实现不同系统之间的跳转。

OAuth是不同公司遵循的一种授权方案,也是一种授权协议,通常都是由大公司提供,比如腾讯,微博。我们常用的QQ登录,微博登录等,使用OAuth的好处是可以使用其他第三方账号进行登录系统,减少了因用户懒,不愿注册而导致用户流失的风险。

现在一些支付业务也用OAuth,比如微信支付,支付宝支付。

还有一些开放平台也用OAuth,比如百度开放平台,腾讯开放平台。

SSO与RBAC的关系

如果企业有多个管理系统,现由原来的每个系统都有一个登录,调整为统一登录认证。

那么每个管理系统都有权限控制,吸取统一登录认证的经验,我们也可以做一套统一的RBAC权限认证。

推荐阅读

一起学习

查看原文

arunfung 收藏了文章 · 5月11日

MySQL/InnoDB中,乐观锁、悲观锁、共享锁、排它锁、行锁、表锁、死锁概念的理解

MySQL/InnoDB的加锁,一直是一个面试中常问的话题。例如,数据库如果有高并发请求,如何保证数据完整性?产生死锁问题如何排查并解决?我在工作过程中,也会经常用到,乐观锁,排它锁,等。于是今天就对这几个概念进行学习,屡屡思路,记录一下。

注:MySQL是一个支持插件式存储引擎的数据库系统。本文下面的所有介绍,都是基于InnoDB存储引擎,其他引擎的表现,会有较大的区别。

存储引擎查看

MySQL给开发者提供了查询存储引擎的功能,我这里使用的是MySQL5.6.4,可以使用:

SHOW ENGINES

乐观锁

用数据版本(Version)记录机制实现,这是乐观锁最常用的一种实现方式。何谓数据版本?即为数据增加一个版本标识,一般是通过为数据库表增加一个数字类型的 “version” 字段来实现。当读取数据时,将version字段的值一同读出,数据每更新一次,对此version值加1。当我们提交更新的时候,判断数据库表对应记录的当前版本信息与第一次取出来的version值进行比对,如果数据库表当前版本号与第一次取出来的version值相等,则予以更新,否则认为是过期数据。

举例

1、数据库表设计

三个字段,分别是id,value、version

select id,value,version from TABLE where id=#{id}

2、每次更新表中的value字段时,为了防止发生冲突,需要这样操作

update TABLE
set value=2,version=version+1
where id=#{id} and version=#{version};

悲观锁

与乐观锁相对应的就是悲观锁了。悲观锁就是在操作数据时,认为此操作会出现数据冲突,所以在进行每次操作时都要通过获取锁才能进行对相同数据的操作,这点跟java中的synchronized很相似,所以悲观锁需要耗费较多的时间。另外与乐观锁相对应的,悲观锁是由数据库自己实现了的,要用的时候,我们直接调用数据库的相关语句就可以了。

说到这里,由悲观锁涉及到的另外两个锁概念就出来了,它们就是共享锁与排它锁。共享锁和排它锁是悲观锁的不同的实现,它俩都属于悲观锁的范畴。

使用,排它锁 举例

要使用悲观锁,我们必须关闭mysql数据库的自动提交属性,因为MySQL默认使用autocommit模式,也就是说,当你执行一个更新操作后,MySQL会立刻将结果进行提交。

我们可以使用命令设置MySQL为非autocommit模式:

set autocommit=0;

# 设置完autocommit后,我们就可以执行我们的正常业务了。具体如下:

# 1. 开始事务

begin;/begin work;/start transaction; (三者选一就可以)

# 2. 查询表信息

select status from TABLE where id=1 for update;

# 3. 插入一条数据

insert into TABLE (id,value) values (2,2);

# 4. 修改数据为

update TABLE set value=2 where id=1;

# 5. 提交事务

commit;/commit work;

共享锁

共享锁又称读锁 read lock,是读取操作创建的锁。其他用户可以并发读取数据,但任何事务都不能对数据进行修改(获取数据上的排他锁),直到已释放所有共享锁。

如果事务T对数据A加上共享锁后,则其他事务只能对A再加共享锁,不能加排他锁。获得共享锁的事务只能读数据,不能修改数据

打开第一个查询窗口

begin;/begin work;/start transaction;  (三者选一就可以)

SELECT * from TABLE where id = 1  lock in share mode;

然后在另一个查询窗口中,对id为1的数据进行更新

update  TABLE set name="www.souyunku.com" where id =1;

此时,操作界面进入了卡顿状态,过了超时间,提示错误信息

如果在超时前,执行 commit,此更新语句就会成功。

[SQL]update  test_one set name="www.souyunku.com" where id =1;
[Err] 1205 - Lock wait timeout exceeded; try restarting transaction

加上共享锁后,也提示错误信息

update  test_one set name="www.souyunku.com" where id =1 lock in share mode;
[SQL]update  test_one set name="www.souyunku.com" where id =1 lock in share mode;
[Err] 1064 - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'lock in share mode' at line 1

在查询语句后面增加 LOCK IN SHARE MODE,Mysql会对查询结果中的每行都加共享锁,当没有其他线程对查询结果集中的任何一行使用排他锁时,可以成功申请共享锁,否则会被阻塞。其他线程也可以读取使用了共享锁的表,而且这些线程读取的是同一个版本的数据。

加上共享锁后,对于update,insert,delete语句会自动加排它锁。

排它锁

排他锁 exclusive lock(也叫writer lock)又称写锁

排它锁是悲观锁的一种实现,在上面悲观锁也介绍过

若事务 1 对数据对象A加上X锁,事务 1 可以读A也可以修改A,其他事务不能再对A加任何锁,直到事物 1 释放A上的锁。这保证了其他事务在事物 1 释放A上的锁之前不能再读取和修改A。排它锁会阻塞所有的排它锁和共享锁

读取为什么要加读锁呢:防止数据在被读取的时候被别的线程加上写锁,

使用方式:在需要执行的语句后面加上for update就可以了

行锁

行锁又分共享锁排他锁,由字面意思理解,就是给某一行加上锁,也就是一条记录加上锁。

注意:行级锁都是基于索引的,如果一条SQL语句用不到索引是不会使用行级锁的,会使用表级锁。

共享锁:

名词解释:共享锁又叫做读锁,所有的事务只能对其进行读操作不能写操作,加上共享锁后在事务结束之前其他事务只能再加共享锁,除此之外其他任何类型的锁都不能再加了。

SELECT * from TABLE where id = "1"  lock in share mode;  结果集的数据都会加共享锁

排他锁:

名词解释:若某个事物对某一行加上了排他锁,只能这个事务对其进行读写,在此事务结束之前,其他事务不能对其进行加任何锁,其他进程可以读取,不能进行写操作,需等待其释放。

select status from TABLE where id=1 for update;

可以参考之前演示的共享锁,排它锁语句

由于对于表中,id字段为主键,就也相当于索引。执行加锁时,会将id这个索引为1的记录加上锁,那么这个锁就是行锁。

表锁

如何加表锁

innodb 的行锁是在有索引的情况下,没有索引的表是锁定全表的.

Innodb中的行锁与表锁

前面提到过,在Innodb引擎中既支持行锁也支持表锁,那么什么时候会锁住整张表,什么时候或只锁住一行呢?
只有通过索引条件检索数据,InnoDB才使用行级锁,否则,InnoDB将使用表锁!

在实际应用中,要特别注意InnoDB行锁的这一特性,不然的话,可能导致大量的锁冲突,从而影响并发性能。

行级锁都是基于索引的,如果一条SQL语句用不到索引是不会使用行级锁的,会使用表级锁。行级锁的缺点是:由于需要请求大量的锁资源,所以速度慢,内存消耗大。

死锁

死锁(Deadlock) 
所谓死锁:是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。由于资源占用是互斥的,当某个进程提出申请资源后,使得有关进程在无外力协助下,永远分配不到必需的资源而无法继续运行,这就产生了一种特殊现象死锁。

解除正在死锁的状态有两种方法:

第一种

1.查询是否锁表

show OPEN TABLES where In_use > 0;

2.查询进程(如果您有SUPER权限,您可以看到所有线程。否则,您只能看到您自己的线程)

show processlist

3.杀死进程id(就是上面命令的id列)

kill id

第二种

1:查看当前的事务

SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX;

2:查看当前锁定的事务

SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS;

3:查看当前等锁的事务

SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS; 

杀死进程

kill 进程ID

如果系统资源充足,进程的资源请求都能够得到满足,死锁出现的可能性就很低,否则就会因争夺有限的资源而陷入死锁。其次,进程运行推进顺序与速度不同,也可能产生死锁。
产生死锁的四个必要条件:

(1) 互斥条件:一个资源每次只能被一个进程使用。
(2) 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
(3) 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
(4) 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

虽然不能完全避免死锁,但可以使死锁的数量减至最少。将死锁减至最少可以增加事务的吞吐量并减少系统开销,因为只有很少的事务回滚,而回滚会取消事务执行的所有工作。由于死锁时回滚而由应用程序重新提交。

下列方法有助于最大限度地降低死锁:

(1)按同一顺序访问对象。
(2)避免事务中的用户交互。
(3)保持事务简短并在一个批处理中。
(4)使用低隔离级别。
(5)使用绑定连接。

参考 :

https://blog.csdn.net/puhaiyang/article/details/72284702

https://www.jb51.net/article/78088.htm

图片描述

查看原文

arunfung 收藏了文章 · 4月20日

Redis常见7种使用场景(PHP实战)

Redis是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。

本篇文章,主要介绍利用PHP使用Redis,主要的应用场景。

简单字符串缓存实战
$redis->connect('127.0.0.1', 6379);
$strCacheKey  = 'Test_bihu';

//SET 应用
$arrCacheData = [
    'name' => 'job',
    'sex'  => '男',
    'age'  => '30'
];
$redis->set($strCacheKey, json_encode($arrCacheData));
$redis->expire($strCacheKey, 30);  # 设置30秒后过期
$json_data = $redis->get($strCacheKey);
$data = json_decode($json_data);
print_r($data->age); //输出数据

//HSET 应用
$arrWebSite = [
    'google' => [
        'google.com',
        'google.com.hk'
    ],
];
$redis->hSet($strCacheKey, 'google', json_encode($arrWebSite['google']));
$json_data = $redis->hGet($strCacheKey, 'google');
$data = json_decode($json_data);
print_r($data); //输出数据
简单队列实战

$redis->connect('127.0.0.1', 6379);
$strQueueName  = 'Test_bihu_queue';

//进队列
$redis->rpush($strQueueName, json_encode(['uid' => 1,'name' => 'Job']));
$redis->rpush($strQueueName, json_encode(['uid' => 2,'name' => 'Tom']));
$redis->rpush($strQueueName, json_encode(['uid' => 3,'name' => 'John']));
echo "---- 进队列成功 ---- <br /><br />";

//查看队列
$strCount = $redis->lrange($strQueueName, 0, -1);
echo "当前队列数据为: <br />";
print_r($strCount);

//出队列
$redis->lpop($strQueueName);
echo "<br /><br /> ---- 出队列成功 ---- <br /><br />";

//查看队列
$strCount = $redis->lrange($strQueueName, 0, -1);
echo "当前队列数据为: <br />";
print_r($strCount);
简单发布订阅实战
//以下是 pub.php 文件的内容 cli下运行
ini_set('default_socket_timeout', -1);
$redis->connect('127.0.0.1', 6379);
$strChannel = 'Test_bihu_channel';

//发布
$redis->publish($strChannel, "来自{$strChannel}频道的推送");
echo "---- {$strChannel} ---- 频道消息推送成功~ <br/>";
$redis->close();
//以下是 sub.php 文件内容 cli下运行
ini_set('default_socket_timeout', -1);
$redis->connect('127.0.0.1', 6379);
$strChannel = 'Test_bihu_channel';

//订阅
echo "---- 订阅{$strChannel}这个频道,等待消息推送...----  <br/><br/>";
$redis->subscribe([$strChannel], 'callBackFun');
function callBackFun($redis, $channel, $msg)
{
    print_r([
        'redis'   => $redis,
        'channel' => $channel,
        'msg'     => $msg
    ]);
}
简单计数器实战
$redis->connect('127.0.0.1', 6379);
$strKey = 'Test_bihu_comments';

//设置初始值
$redis->set($strKey, 0);

$redis->INCR($strKey);  //+1
$redis->INCR($strKey);  //+1
$redis->INCR($strKey);  //+1

$strNowCount = $redis->get($strKey);

echo "---- 当前数量为{$strNowCount}。 ---- ";
排行榜实战
$redis->connect('127.0.0.1', 6379);
$strKey = 'Test_bihu_score';

//存储数据
$redis->zadd($strKey, '50', json_encode(['name' => 'Tom']));
$redis->zadd($strKey, '70', json_encode(['name' => 'John']));
$redis->zadd($strKey, '90', json_encode(['name' => 'Jerry']));
$redis->zadd($strKey, '30', json_encode(['name' => 'Job']));
$redis->zadd($strKey, '100', json_encode(['name' => 'LiMing']));

$dataOne = $redis->ZREVRANGE($strKey, 0, -1, true);
echo "---- {$strKey}由大到小的排序 ---- <br /><br />";
print_r($dataOne);

$dataTwo = $redis->ZRANGE($strKey, 0, -1, true);
echo "<br /><br />---- {$strKey}由小到大的排序 ---- <br /><br />";
print_r($dataTwo);
简单字符串悲观锁实战

解释:悲观锁(Pessimistic Lock), 顾名思义,就是很悲观。

每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁。

场景:如果项目中使用了缓存且对缓存设置了超时时间。

当并发量比较大的时候,如果没有锁机制,那么缓存过期的瞬间,

大量并发请求会穿透缓存直接查询数据库,造成雪崩效应。

/**
 * 获取锁
 * @param  String  $key    锁标识
 * @param  Int     $expire 锁过期时间
 * @return Boolean
 */
public function lock($key = '', $expire = 5) {
    $is_lock = $this->_redis->setnx($key, time()+$expire);
    //不能获取锁
    if(!$is_lock){
        //判断锁是否过期
        $lock_time = $this->_redis->get($key);
        //锁已过期,删除锁,重新获取
        if (time() > $lock_time) {
            unlock($key);
            $is_lock = $this->_redis->setnx($key, time() + $expire);
        }
    }

    return $is_lock? true : false;
}

/**
 * 释放锁
 * @param  String  $key 锁标识
 * @return Boolean
 */
public function unlock($key = ''){
    return $this->_redis->del($key);
}

// 定义锁标识
$key = 'Test_bihu_lock';

// 获取锁
$is_lock = lock($key, 10);
if ($is_lock) {
    echo 'get lock success<br>';
    echo 'do sth..<br>';
    sleep(5);
    echo 'success<br>';
    unlock($key);
} else { //获取锁失败
    echo 'request too frequently<br>';
}
简单事务的乐观锁实战

解释:乐观锁(Optimistic Lock), 顾名思义,就是很乐观。

每次去拿数据的时候都认为别人不会修改,所以不会上锁。

watch命令会监视给定的key,当exec时候如果监视的key从调用watch后发生过变化,则整个事务会失败。

也可以调用watch多次监视多个key。这样就可以对指定的key加乐观锁了。

注意watch的key是对整个连接有效的,事务也一样。

如果连接断开,监视和事务都会被自动清除。

当然了exec,discard,unwatch命令都会清除连接中的所有监视。

$strKey = 'Test_bihu_age';

$redis->set($strKey,10);

$age = $redis->get($strKey);

echo "---- Current Age:{$age} ---- <br/><br/>";

$redis->watch($strKey);

// 开启事务
$redis->multi();

//在这个时候新开了一个新会话执行
$redis->set($strKey,30);  //新会话

echo "---- Current Age:{$age} ---- <br/><br/>"; //30

$redis->set($strKey,20);

$redis->exec();

$age = $redis->get($strKey);

echo "---- Current Age:{$age} ---- <br/><br/>"; //30

//当exec时候如果监视的key从调用watch后发生过变化,则整个事务会失败

推荐阅读

一起学习

查看原文

arunfung 收藏了文章 · 4月20日

mysql 幻读的详解、实例及解决办法

脏读/不可重复读的概念都比较容易理解和掌握,这里不在讨论

事务隔离级别(tx_isolation)

mysql 有四级事务隔离级别 每个级别都有字符或数字编号

级别symbol描述
读未提交READ-UNCOMMITTED0存在脏读、不可重复读、幻读的问题
读已提交READ-COMMITTED1解决脏读的问题,存在不可重复读、幻读的问题
可重复读REPEATABLE-READ2mysql 默认级别,解决脏读、不可重复读的问题,存在幻读的问题。使用 MMVC机制 实现可重复读
序列化SERIALIZABLE3解决脏读、不可重复读、幻读,可保证事务安全,但完全串行执行,性能最低

我们可以通过以下命令 查看/设置 全局/会话 的事务隔离级别

mysql> SELECT @@global.tx_isolation, @@tx_isolation;
+-----------------------+------------------+
| @@global.tx_isolation | @@tx_isolation   |
+-----------------------+------------------+
| REPEATABLE-READ       | READ-UNCOMMITTED |
+-----------------------+------------------+
1 row in set (0.00 sec)

# 设定全局的隔离级别 设定会话 global 替换为 session 即可 把set语法温习一下
# SET [GLOABL] config_name = 'foobar';
# SET @@[session.|global.]config_name = 'foobar';
# SELECT @@[global.]config_name;

SET @@gloabl.tx_isolation = 0;
SET @@gloabl.tx_isolation = 'READ-UNCOMMITTED';

SET @@gloabl.tx_isolation = 1;
SET @@gloabl.tx_isolation = 'READ-COMMITTED';

SET @@gloabl.tx_isolation = 2;
SET @@gloabl.tx_isolation = 'REPEATABLE-READ';

SET @@gloabl.tx_isolation = 3;
SET @@gloabl.tx_isolation = 'SERIALIZABLE';

幻读

首先我们要搞明白何谓幻读,目前网上的众多解释幻读的博文个人感觉仔细设想一下就能找出推翻的例子,就像博文把 非阻塞IO 等同为 异步IO,然后好多文章都纷纷借用,其实这俩货是完全不同,非阻塞IO 是 同步IO 中的一种模式,并非 异步IO。错误的观点都被大众认同的 "正确化" 了,扯远了,回归主题。

幻读会在 RU / RC / RR 级别下出现,SERIALIZABLE 则杜绝了幻读,但 RU / RC 下还会存在脏读,不可重复读,故我们就以 RR 级别来研究幻读,排除其他干扰。

注意:RR 级别下存在幻读的可能,但也是可以使用对记录手动加 X锁 的方法消除幻读。SERIALIZABLE 正是对所有事务都加 X锁 才杜绝了幻读,但很多场景下我们的业务sql并不会存在幻读的风险。SERIALIZABLE 的一刀切虽然事务绝对安全,但性能会有很多不必要的损失。故可以在 RR 下根据业务需求决定是否加锁,存在幻读风险我们加锁,不存在就不加锁,事务安全与性能兼备,这也是 RR 作为mysql默认隔是个事务离级别的原因,所以需要正确的理解幻读。

幻读错误的理解:说幻读是 事务A 执行两次 select 操作得到不同的数据集,即 select 1 得到 10 条记录,select 2 得到 11 条记录。这其实并不是幻读,这是不可重复读的一种,只会在 R-U R-C 级别下出现,而在 mysql 默认的 RR 隔离级别是不会出现的。

这里给出我对幻读的比较白话的理解:

幻读,并不是说两次读取获取的结果集不同,幻读侧重的方面是某一次的 select 操作得到的结果所表征的数据状态无法支撑后续的业务操作。更为具体一些:select 某记录是否存在,不存在,准备插入此记录,但执行 insert 时发现此记录已存在,无法插入,此时就发生了幻读。

这里给出 mysql 幻读的比较形象的场景(借用我在知乎上的回答):

table users: id primary key

事务T1

图片描述
事务T2
图片描述

step1 T1: SELECT * FROM `users` WHERE `id` = 1;
step2 T2: INSERT INTO `users` VALUES (1, 'big cat');
step3 T1: INSERT INTO `users` VALUES (1, 'big cat');
step4 T1: SELECT * FROM `users` WHERE `id` = 1;

T1 :主事务,检测表中是否有 id 为 1 的记录,没有则插入,这是我们期望的正常业务逻辑。

T2 :干扰事务,目的在于扰乱 T1 的正常的事务执行。

在 RR 隔离级别下,step1、step2 是会正常执行的,step3 则会报错主键冲突,对于 T1 的业务来说是执行失败的,这里 T1 就是发生了幻读,因为 T1 在 step1 中读取的数据状态并不能支撑后续的业务操作,T1:“见鬼了,我刚才读到的结果应该可以支持我这样操作才对啊,为什么现在不可以”。T1 不敢相信的又执行了 step4,发现和 setp1 读取的结果是一样的(RR下的 MMVC机制)。此时,幻读无疑已经发生,T1 无论读取多少次,都查不到 id = 1 的记录,但它的确无法插入这条他通过读取来认定不存在的记录(此数据已被T2插入),对于 T1 来说,它幻读了。

其实 RR 也是可以避免幻读的,通过对 select 操作手动加 行X锁(SELECT ... FOR UPDATE 这也正是 SERIALIZABLE 隔离级别下会隐式为你做的事情),同时还需要知道,即便当前记录不存在,比如 id = 1 是不存在的,当前事务也会获得一把记录锁(因为InnoDB的行锁锁定的是索引,故记录实体存在与否没关系,存在就加 行X锁,不存在就加 next-key lock间隙X锁),其他事务则无法插入此索引的记录,故杜绝了幻读。

在 SERIALIZABLE 隔离级别下,step1 执行时是会隐式的添加 行(X)锁 / gap(X)锁的,从而 step2 会被阻塞,step3 会正常执行,待 T1 提交后,T2 才能继续执行(主键冲突执行失败),对于 T1 来说业务是正确的,成功的阻塞扼杀了扰乱业务的T2,对于T1来说他前期读取的结果是可以支撑其后续业务的。

所以 mysql 的幻读并非什么读取两次返回结果集不同,而是事务在插入事先检测不存在的记录时,惊奇的发现这些数据已经存在了,之前的检测读获取到的数据如同鬼影一般。

这里要灵活的理解读取的意思,第一次select是读取,第二次的 insert 其实也属于隐式的读取,只不过是在 mysql 的机制中读取的,插入数据也是要先读取一下有没有主键冲突才能决定是否执行插入。

不可重复读侧重表达 读-读,幻读则是说 读-写,用写来证实读的是鬼影。

RR级别下防止幻读

RR级别下只要对 SELECT 操作也手动加行(X)锁即可类似 SERIALIZABLE 级别(它会对 SELECT 隐式加锁),即大家熟知的:

# 这里需要用 X锁, 用 LOCK IN SHARE MODE 拿到 S锁 后我们没办法做 写操作
SELECT `id` FROM `users` WHERE `id` = 1 FOR UPDATE;

如果 id = 1 的记录存在则会被加行(X)锁,如果不存在,则会加 next-lock key / gap 锁(范围行锁),即记录存在与否,mysql 都会对记录应该对应的索引加锁,其他事务是无法再获得做操作的。

这里我们就展示下 id = 1 的记录不存在的场景,FOR UPDATE 也会对此 “记录” 加锁,要明白,InnoDB 的行锁(gap锁是范围行锁,一样的)锁定的是记录所对应的索引,且聚簇索引同记录是直接关系在一起的。
clipboard.png

id = 1 的记录不存在,开始执行事务:
step1: T1 查询 id = 1 的记录并对其加 X锁
step2: T2 插入 id = 1 的记录,被阻塞
step3: T1 插入 id = 1 的记录,成功执行(T2 依然被阻塞中),T1 提交(T2 唤醒但主键冲突执行错误)
T1事务符合业务需求成功执行,T2干扰T1失败。

SERIALIZABLE级别杜绝幻读

在此级别下,我们便不需要对 SELECT 操作显式加锁,InnoDB会自动加锁,事务安全,但性能很低

clipboard.png

step1: T1 查询 id = 2 的记录,InnoDB 会隐式的对齐加 X锁
step2: T2 插入 id = 2 的记录,被阻塞
step3: T1 插入 id = 2 的记录,成功执行(T2 依然被阻塞中)
step4: T1 成功提交(T2 此时唤醒但主键冲突执行错误)
T1事务符合业务需求成功执行,T2干扰T1失败。

总结

RR 级别作为 mysql 事务默认隔离级别,是事务安全与性能的折中,可能也符合二八定律(20%的事务存在幻读的可能,80%的事务没有幻读的风险),我们在正确认识幻读后,便可以根据场景灵活的防止幻读的发生。

SERIALIZABLE 级别则是悲观的认为幻读时刻都会发生,故会自动的隐式的对事务所需资源加排它锁,其他事务访问此资源会被阻塞等待,故事务是安全的,但需要认真考虑性能。

InnoDB的行锁锁定的是索引,而不是记录本身,这一点也需要有清晰的认识,故某索引相同的记录都会被加锁,会造成索引竞争,这就需要我们严格设计业务sql,尽可能的使用主键或唯一索引对记录加锁。索引映射的记录如果存在,加行锁,如果不存在,则会加 next-key lock / gap 锁 / 间隙锁,故InnoDB可以实现事务对某记录的预先占用,如果记录存在,它就是本事务的,如果记录不存在,那它也将是本是无的,只要本是无还在,其他事务就别想占有它。

查看原文

arunfung 收藏了文章 · 4月17日

我本以为你们会写简历

然而并不是

裁员的裁员 , 没裁员的正在准备裁员的路上 . 再加上一些人年终奖也已经骗到手了 , 依据优良传统 , 年后正是很多人辞职奔向更好的骗工资岗位的高峰期 . 所以 , 如何编简历 ( 注意是编 , 不是写 , 我认为编这个字十分有内涵 ) ?

其实编简历并不是一件很难的事情 , 这件事情的本质就是 : 你在向一个同行陌生人介绍你在本行的道行 . 如果你的体系已经将对方笼罩住了 , 你暂时可以控场一波儿( 不代表一定会被录用 ) ; 如果你被对方笼罩住了 , 你就是受虐那个 ( 不代表你不会被录用 ) . 总之就是一句话 :

唬住面试官就要50K , 唬不住面试官就要5K

没想到 , 时至今日 , 我竟然需要写这么一篇题目类似于<老李手把手教你编简历>或<跟着永强学编简历>又或者<于巨柱细说编简历>的教程 .

可见这一届PHPer是多么的差劲 .

先从一个靠谱简历的外观说起 :

  • 首先 , 请使用PDF格式 , 不要用doc , docx , doc* 等拙劣的文档格式 . 原因我还是要解释一下的 , 因为我也当过面试官 , 我见到过各种神奇的doc文档打开后错位 , 乱行 , 甚至乱码 , 极其影响视觉感官 . 大家都挺忙的 , 打开这么一坨乱糟糟的玩意心情就很差 , 直接筛掉 . 我知道怎么说都会有杠精的 : " word怎么可能会乱呢 ? 我在我这里打开就不乱 . " 我不歧视杠精 , 也一视同仁地无私奉献一条友情提示 :

  • 其次 , 请文件名请专业一些 , 请采用 " 姓名 - 职业 - 工作年限.pdf "这种格式来命名你的简历 , 切忌 "简历.pdf" , 有些不讲究的人真是文件名连简历两个字都不是 , 直接就是随机的一坨字符 , 诸如 yhc.docx . 还是那句话 , 大家都挺忙的 , 主要是请尽量多提供信息给好看的HR小姐姐 , 让她们减少一些无畏的工作负担 , 次要是因为你的简历名字一眼让人获取到了很多信息 , 第一时间被捡出来 .

然后 , 更重要的地方来了 , 里面写啥 .

个人认为一个简历四大组成部分 , 根据重要程度依次为 : 一是个人信息部分 , 二是技能点说明部分 , 三是项目经历部分 , 四是公司经历 .

下面按照顺序依次说下我觉得需要注意的地方 , 最后收尾我会提一下这个部分的可修饰程度 . 什么叫可修饰程度 ? ? ?

读书人的事儿 , 能叫偷么 ? --- 孔乙己

先说第一部分 , 个人信息部分 . 你出去大保健被警察叔叔逮进局子后让你交代的啥 , 这里就写啥 . 可修饰程度比较低 , 拥有一丝丝可修饰价值 .

再说第二部分 , 个人技能点分配说明 . 这项十分重要 . 面试官会非常注重这一项 , HR一般看不懂 . 很多人这里写的很差 , 这句话的意思就是 : 内涵不错的人这里写的很差 , 没有内涵的人这里写的也很差 , 以致于"内涵不错的人"从简历上看似乎和"没内涵的人"都差不多 . 这个地方最好不要写" 精通 "这两个字 . 虽然HR有可能真的喜欢简历上有" 精通 "字样的人 , 但是你也要知道你这两个字也会给你的面试官带来极大的反感或嘲讽欲 , 总之 , 你结合你自己情况自己看着办 , 万一你真的精通了呢 ? 然后我说下我见过大多数人这个地方都是怎么编的 , 一般都是 :

熟悉PHP , 熟悉YiiLavarel框架
熟悉Linux使用 , 可以搭建XXXX环境
熟悉git或svn版本管理的使用
熟悉MySQL以及对数据库的优化
熟悉Redis或Memcache的使用

我敢说大多数人都是这么写的 , 下面我站在面试官的立场来用行内白话来解释一下当我看到这样的简历后大脑里怎么想的 .

熟悉PHP , 熟悉YiiLavarel框架 ( 复制粘贴 , CURD , 就是干! )
熟悉Linux使用 , 可以搭建XXXX环境 ( 会敲cd , ls命令 , 会apt install nginx )
熟悉git或svn版本管理的使用 ( 会git push , 会git pull )
熟悉MySQL以及对数据库的优化 ( 会select update 和 delete , 会添加索引 )
熟悉Redis或Memcache的使用 ( 会set key , 会get key )

问题是什么 ? 其实问题不在于这些行内白话没有提现你的水准 . 这个问题的关键是 : 大家都这么写 , 凭啥把你的简历挑出来 .

所以这个地方吧 , 可以尝试用下面来表述 , 注意要结合你自己掌握程度 :

PHP : 熟悉PHP语法 , 熟悉PHP面向对象 , 可以根据业务逻辑结合合适的设计模式 . 熟悉PHP SPL标准库 , 对PHP的一些高级用法有所心得体验 , 诸如pcntl多进程模块 , socket模块 . 对SWOOLE所有涉猎 , 有一些自己的积累和经验 . 对于底层 , ZendVM如何如何 .
Redis : 熟悉Redis常用数据结构的使用 , 可结合业务场景选择合适的数据结构 . 熟悉Redis集群 , 对集群实现方案原理有一定掌握 , 对于市面常用的集中集群方案的优缺点比较了解 . 对于底层 , 对Redis SET等底层数据结构的实现有所掌握 .

行了 , 我就举两个例子吧 , 技能点的说明最好用类似上面的说明 , 还是那句话 : 最好不要出现精通 .

那该用什么形容词呢 ? 我替你总结一下常用的几个词语 : 熟悉 , 有所 , 掌握 , 了解 , 有一定 , 心得 等 .

第二部分 : 可修饰程度略高 , 拥有可修饰价值 . 主要是你要能够应对面试官对修饰部分的问题 .

继续说第三部分 , 项目经历部分 . 这一部分实际上是对第二部分技能点分配说明的实战演练说明 , 你要提现出你在这个项目中的两点 :

  • 亮点 . 你觉得这个项目中哪一部分值得自豪或学到新东西了 . 比如项目中用到ECDH , 使用了MySQL中间件等等 .
  • 难点 . 你觉得这个项目哪一部分当时难了你几天 , 然后你通过自己努力解决了以及解决方案是什么 .

然而大多数人都是这么写的 :

负责用户登录注册模块 , 后台管理 , 多角色权限控制 , 负责广告业务模块的管理和筛查 .

你这么写的 , 别人也是这么写的 .

第三部分 : 可修饰程度比较高 , 拥有较高修饰价值 . 主要是你要能够应对面试官对修饰部分的问题 .

最后一点是公司经历了 , 这个也没啥好说的 . 如果可以 , 我建议你合并一些小公司经历直接合并为一家 . 对于一些少数倒霉的同学 , 比如在不到10个人公司干了三个月就辞职的这种 , 我建议修饰成" 去朋友公司帮他临时组件了一个小团队 ".

然而 , 到了最后 , 我还是要告诉你这个世界多么残酷 , 即便你的简历真的比较优秀 , 用词恰当 , 然而如果你面试遇上了傻逼一样的面试官 , 都白搭 . 这里的傻逼理解为两类 :

  • 装逼优越diss你类型的 . 这种的 , 可能依然会录用你 .
  • 看你不顺眼 , 上来说话就带刺类型的 . 根据你的心理承受能力 , 请你自己做出相应动作 .

不得不承认 , 只要看双方对了眼 , 聊的投机了 , 简历什么的是可以抛到一边儿的 .

找到一个合适的工作是你和这家公司的事儿 : 一个愿打 , 一个愿挨 .
能应聘到这个合适的工作是你和面试官的事儿 : wangba看绿豆 -- 对上眼了 .

最近开了一个微信公众号,所有文章都在这里

图片描述

查看原文

认证与成就

  • 获得 7 次点赞
  • 获得 2 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 2 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

注册于 2016-08-28
个人主页被 702 人浏览